简单记录一个计算机图形学方面的算法。即计算 2D 平面中,从点 A 到点 B 形成的向量的夹角。
这算是游戏里面比较底层的算法,一般游戏引擎都会包装好这个方法,如 GMS2 的 point_direction 方法,常用于实现旋转一个物体,比如拨动时钟的指针、人物的武器使用鼠标瞄准敌人。
本文代码使用 JavaScript 实现。
问题描述
如图,求这里朝上的白色虚线到红线的角度。
我们规定角度区间为 (-180, 180]
。当向量朝上的时候,角度为0,向量正朝下时,角度为 180。当然你也可以规定为 [0, 360),只需要在代码得到的角度 angle 的基础上,计算 (angle + 360) % 360
即可。
另外要注意这里的坐标系的 y 轴是朝下的(一般计算机屏幕的坐标系都是y轴朝下的),x 轴朝向为右。
计算
已知中心点 cx、cy,光标的坐标 x,y,求角度。
说到底,求的是向量 a (x - cx, y - cy) 和向量 b (0, -1) 的夹角。这里我们需要用到 点积公式:
我们要求的是角度,于是得到下面公式:
假设向量 a 为 (x, y)
, 向量 b 为 (x2, y2)
,则 a · b
为 x * x2 + y * y2
。
|a|
指的是向量 a 的模,即向量 a 的 x 和 y 平方和并开方,即 Math.sqrt(x * x + y * y)
。
通过公式,我们得到了余弦值 cos,然后反余弦 Math.acos()
,得到弧度,再进行单位换算得到角度。
function calCos(a, b) {
// 点积
let dotProduct = a[0] * b[0] + a[1] * b[1];
let d = Math.sqrt(a[0] * a[0] + a[1] * a[1]) * Math.sqrt(b[0] * b[0] + b[1] * b[1]);
return dotProduct/d;
}
let angle = Math.acos(radian) * 180 / Math.PI;
我们先看看 余弦函数 和 反余弦函数的示意图:
![图片](https://mmbiz.qpic.cn/mmbiz_png/Axu3yFGzpWibLO9dMv7IN91xX4giaib2PS5FwjjvztsR5D9nEugUIdVvyGsvDz0yTUyvLLCsj9y50CndWQL4jqQHw/640?wx_fmt=gif)
由图中的反余弦函数可知,求得的角度范围为 \[0, 180\]。那 \[-180, 0) 的范围如何获取呢?我们还有个办法,就是通过 **判断光标的 x 坐标大于还是小于 中心点的 x 坐标** 来设置正负,如果大于,角度为正;反之为负。
let angle = Math.acos(radian) * 180 / Math.PI;
if (x < cx) angle = -angle;
完整代码
----
function calAngle(cx, cy, x, y) {
const radian = getCosBy2pt(x, y, cx, cy);
let angle = Math.acos(radian) * 180 / Math.PI;
if (x < cx) angle = -angle;
return angle;
// 计算 点1指点2形成 的向量
function getCosBy2pt(x, y, cx, cy) {
let a = [x - cx, y - cy];
let b = [0, -1];
return calCos(a, b);
}
function calCos(a, b) {
// 点积
let dotProduct = a[0] * b[0] + a[1] * b[1];
let d = Math.sqrt(a[0] * a[0] + a[1] * a[1]) * Math.sqrt(b[0] * b[0] + b[1] * b[1]);
return dotProduct/d;
}
}
演示
--
本人使用 svg 做了一个演示 demo 出来,使用了 svgjs 库。
https://f-star.github.io/my-test/calAngle/
这里你会发现我这里有个 蓝色的弧线 表示了角度经过的路径,是不是很好玩,其实这个弧的绘制也是通过 **点积公式** 计算出中心点到光标的连线上的一个点的坐标来实现的。有兴趣你可以自己研究一下。
**相关文章:**
[图形编辑器——矩形选区是如何实现选中多个图形的?](http://mp.weixin.qq.com/s?__biz=MzI0NTc2NTEyNA==&mid=2247483989&idx=1&sn=ebd008733a7a799369e4af45560c41fd&chksm=e948c73ede3f4e28d3e2e44b36640da4c9589db569840e58eeea738d21088114d1c7781a8ea0&scene=21#wechat_redirect)
[在容器内显示图片的五种方案:contain、cover、fill、none、scale-down](http://mp.weixin.qq.com/s?__biz=MzI0NTc2NTEyNA==&mid=2247483971&idx=1&sn=e67ee2754c2cd19c5538bd43d0067bfc&chksm=e948c728de3f4e3ee9e0f749ad4edb49ff080142c181b184d13b182908fbdb1aa5263329f69b&scene=21#wechat_redirect)
![](https://img-blog.csdnimg.cn/img_convert/6ad96d2dda185bee5e1b80b452790dde.png)
**前端西瓜哥**
坚持日更原创,分享前端知识。
112篇原创内容
公众号