前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >平面几何:求向量 a 到向量 b扫过的夹角

平面几何:求向量 a 到向量 b扫过的夹角

作者头像
前端西瓜哥
发布2024-03-22 13:06:30
930
发布2024-03-22 13:06:30
举报

大家好,我是前端西瓜哥。

今天我们来学习如何求向量 a 到向量 b扫过的弧度,或者也可以说是角度,转换一下就好了。

求两向量的夹角

求两向量的夹角很简单,用点积公式。

变换一下:

代码实现:

代码语言:javascript
复制
const getAngle = (a, b) => {
  // 点积
  const dot = a.x * b.x + a.y * b.y;
  const d = Math.sqrt(a.x * a.x + a.y * a.y) * Math.sqrt(b.x * b.x + b.y * b.y);
  const cosTheta = dot / d;
  return Math.acos(cosTheta);
};

需要注意的是,这个夹角是没有方向的,为大于等于 0 小于 180 度,我们不知道其中一个向量在另一个向量的哪一次。

边缘场景

上面的代码有两个 corner case 需要处理。

(1)有至少一个向量为零向量

零向量没有方向,和其他向量没法构成夹角。参与运算时也会导致除数为零,最后会返回 NaN。

这个怎么处理?自行决定。

比如可以返回角度 0;或者返回 NaN;或者直接报错,要求使用者在使用该方法前先自己判断是否为零向量,否则不能传进来。

代码语言:javascript
复制
const d = Math.sqrt(a.x * a.x + a.y * a.y) * Math.sqrt(b.x * b.x + b.y * b.y);
if (d === 0) {
  return null
}

(2)余弦值误差

然后一个比较难发现的,就是浮点数误差,导致角度余弦值 cosTheta 略微超出 [-1, 1] 的范围,比如 1.00000001,这个用 Math.acos 进行反余弦运算,得到的是。。。NaN。

为什么我会知道?

因为我写一个复杂算法的时候,发现在某个极限场景下拿到了 NaN,一步步 debugger 发现是这个误差问题,真的没想到还有这个坑。

修正回 [-1, 1] 范围即可:

代码语言:javascript
复制
// 修正精度问题导致的 cosTheta 超出 [-1, 1] 的范围
// 导致 Math.acos(cosTheta) 的结果为 NaN
if (cosTheta > 1) {
  cosTheta = 1;
} else if (cosTheta < -1) {
  cosTheta = -1;
}

向量 a 到向量 b 扫过的夹角

但很多的情况下,角度是有方向的:逆时针或顺时针。

我们往往想知道的是 向量 A 沿着特定方向旋转,要旋转多少角度才能到达向量 B 的位置

我们要求的角度在 -180 到 180 范围,负数表示沿反方向旋转多少多少度。(也可以不用负数,只能沿正方向扫过去,用 0 到 360 表示)

为了判断方向,我们需要使用叉积。叉积在图形学中经常用来判断左右或内外

三维中两个向量 a、b 的叉积运算,会使用 a x b 表示,其结果也是一个向量 c。向量 c 会同时垂直于向量 a、b,或者可以理解为垂直于它们形成的平面)。

叉积运算出来的结果向量的方向,在右手坐标系(二维坐标中,我们习惯的 x 向右,y 向上,z 朝脸上)中,满足 右手定则,见下图:

这个二维向量也能用,叉积是一个标量,即一个数字,对应三维空间中,第三个维度 z 的值。

对于叉积 a x b,如果结果为正值,则 b 在 a 的左边;如果结果为负值,则 b 在 a 的左边;如果结果为 0,表示他们向量相同,属于 corner case,左右随便选一个。

但是 Canvas、SVG 这些,都是左手坐标系(x 轴向右,y 轴向下,z 朝脸上),在用它们时用的是左手定则,a x b 和前面说的刚好反过来。

注意叉积不满足交换律,交换后就反向了,

回到算法。

这里假设角度的正方向为顺时针方向,则如果 a x b 为正值,则 b 在 a 的右边,不需要修正;如果 b 在 a 的左边,就要取负值,进行修正:

代码语言:javascript
复制
// 通过叉积判断方向,如果 b 在 a 的左边,则取负值
if (a.x * b.y - a.y * b.x < 0) {
  theta = -theta;
}

完整代码

代码语言:javascript
复制
/**
 * 求向量 a 到向量 b 扫过的夹角
 * 这里假设顺时针方向为正
 */
const getSweepAngle = (a, b) => {
  // 点乘求夹角
  const dot = a.x * b.x + a.y * b.y;
  const d = Math.sqrt(a.x * a.x + a.y * a.y) * Math.sqrt(b.x * b.x + b.y * b.y);
  // 零向量特殊处理
  if (d === 0) {
    return undefined;
  }
  let cosTheta = dot / d;
  // 修正精度问题导致的 cosTheta 超出 [-1, 1] 的范围
  // 导致 Math.acos(cosTheta) 的结果为 NaN
  if (cosTheta > 1) {
    cosTheta = 1;
  } else if (cosTheta < -1) {
    cosTheta = -1;
  }

  let theta = Math.acos(cosTheta);
  // 叉积判断方向,如果 b 在 a 的左边,取反
  if (a.x * b.y - a.y * b.x < 0) {
    theta = -theta;
  }

  return theta;
};

可视化交互

demo 地址:

https://codepen.io/F-star/pen/ZEZpgpq

结尾

我是前端西瓜哥,关注我,学习更多平面几何知识。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2024-03-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端西瓜哥 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 求两向量的夹角
  • 边缘场景
  • 向量 a 到向量 b 扫过的夹角
  • 完整代码
    • 可视化交互
    • 结尾
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档