Cesium 轴拖拽 + 旋转圈拖拽 核心数学知识

结合你整套 Cesium 轴拖拽 + 旋转圈拖拽 代码,我把里面用到的全部核心数学知识,按「概念→几何意义→代码对应→为什么这么算」完整讲一遍,不讲虚的,全部和代码一一绑定,学完就能彻底看懂每一行向量 / 矩阵运算的来历。

一、先统一基础:空间点与向量(Cesium 里一切的基础)

1. 点 vs 向量

  • Cartesian3 :空间里一个固定位置,如 centercurrentPoint
  • 向量 Cartesian3:有方向、有长度的箭头,描述 "从哪指向哪"。

在 Cesium 里共用同一个类 Cartesian3,但意义不同:

  • p:点
  • a - b:从点 b 指向点 a 的向量

2. 最基础运算(代码里到处都是)

(1)向量减法:两点之间的向量

复制代码
// 从 center 指向 currentPoint 的向量
const v = Cesium.Cartesian3.subtract(currentPoint, center, new Cesium.Cartesian3());
  • 数学:v=Pcurrent−Pcenter
  • 几何:从中心点拉一条箭头到当前点

(2)向量加法:点的平移

复制代码
Cesium.Cartesian3.add(p, translation, result)
  • 数学:Pnew=Pold+t
  • 几何:把点 Pold 沿着 t 方向移动一段距离

(3)数乘:缩放向量长度

复制代码
Cesium.Cartesian3.multiplyByScalar(axis, delta, result)
  • 数学:vnew=k⋅vold
  • 几何:保持方向不变,把向量拉长 / 缩短 k

二、点乘(Dot Product)------ 你代码里的 "投影神器"

点乘是你轴拖拽的灵魂,所有 "只沿轴移动" 都靠它。

1. 数学定义

对两个三维向量 a=(ax​,ay​,az​)、b=(bx​,by​,bz​):a⋅b=ax​bx​+ay​by​+az​bz​

2. 几何意义(最关键)

a⋅b=∥a∥∥b∥cosθ

  • θ:两向量夹角
  • ∥a∥:向量长度(模)

两个极端情况:

  1. 两向量同向平行:θ=0, cosθ=1 → 点乘 = 长度乘积
  2. 两向量垂直:θ=90∘, cosθ=0 → 点乘 = 0

3. 代码里的核心用法:投影

当 b 是单位向量 (长度 = 1,你代码里都做了 normalize):a⋅b=向量 a 在 b 方向上的投影长度

对应你代码:

复制代码
const currentScalar = Cesium.Cartesian3.dot(this.dragAxis, v);
  • dragAxis:单位化轴向量(东 / 北 / 天)
  • v:从中心到当前点的向量
  • currentScalarv 在轴方向上的投影长度→ 也就是「只看这个轴方向,偏移了多少米」

这就是为什么拖 Z 轴只会改高度,X/Y 分量被完全过滤掉。

4. 另一个用途:求夹角余弦

复制代码
const cos = Cesium.Cartesian3.dot(this.dragStartVector, currentVector);
  • 两个都是单位向量
  • 直接得到夹角的 cosθ,用于后面算旋转角度

三、叉乘(Cross Product)------ 你旋转圈的灵魂

叉乘专门用来:

  • 求垂直于两个向量的新方向
  • 计算旋转方向(顺时针 / 逆时针)
  • 构造平面法向量

1. 数学定义

a×b=​ay​bz​−az​by​az​bx​−ax​bz​ax​by​−ay​bx​​​

2. 几何意义

  1. 结果向量 a×b 同时垂直于 a 和 b
  2. 长度:∥a×b∥=∥a∥∥b∥sinθ
  3. 方向满足右手定则,决定旋转正负方向。

3. 你代码里的两大用途

(1)构造约束平面的法向量(轴拖拽)

复制代码
let normal = Cesium.Cartesian3.cross(axis, cameraDir, result);
  • 想要平面包含轴 axis,且尽量面向相机
  • 平面法向量必须同时垂直轴和相机方向 → 只能用叉乘

(2)求旋转方向与正弦值(旋转圈)

复制代码
const cross = Cesium.Cartesian3.cross(this.dragStartVector, currentVector, result);
const sin = Cesium.Cartesian3.dot(this.dragAxis, cross);
  • cross 垂直于起始向量和当前向量
  • 再和旋转轴点乘,得到 sinθ
  • 同时携带正负号,表示顺时针 / 逆时针

四、单位向量与归一化

复制代码
Cesium.Cartesian3.normalize(v, v);

数学:v^=∥v∥v​,∥v^∥=1

为什么你代码里处处归一化?

  1. 点乘结果直接 = 投影长度,不用再除以长度
  2. 旋转角度计算只关心方向,不关心长度
  3. 平面、矩阵计算不会引入缩放误差

你代码里 getAxisVectorgetAxisDragPlaneprepareRingDrag 全都做了这一步,是工程上必须的规范。


五、平面方程与射线 - 平面求交(拖拽 "不飘" 的数学保证)

1. 平面一般方程

Ax+By+Cz+D=0

  • n=(A,B,C):单位法向量
  • 平面上任意一点 (x,y,z) 都满足方程

2. Cesium.Plane 构造

复制代码
new Cesium.Plane(normal, D);

代码里:

复制代码
return new Cesium.Plane(normal, -Cesium.Cartesian3.dot(normal, center));
  • 令 D=−n⋅Pcenter
  • 代入平面方程:n⋅P−n⋅Pcenter=0⇒n⋅(P−Pcenter)=0
  • 几何意义:平面必定经过 center 点

3. 射线 - 平面求交

复制代码
const currentPoint = Cesium.IntersectionTests.rayPlane(ray, plane);
  • 射线:从相机出发,穿过鼠标屏幕点的一条直线
  • 求它和约束平面的交点,就把鼠标的 "屏幕 2D 运动" 映射成 "平面上的 3D 点"
  • 这一步保证:所有拖拽都被限制在平面内,不会乱跑

六、旋转全套数学:从轴角 → 四元数 → 矩阵

你旋转代码是标准、工程化最强的 3D 旋转写法,我把链条完整拆开。

1. 旋转描述方式:轴角表示

绕单位向量 u 旋转 θ 角,这是最直观的旋转描述。

对应代码:

复制代码
const q = Cesium.Quaternion.fromAxisAngle(axis, angle);

2. 为什么用四元数?

  • 避免万向锁(Gimbal Lock):欧拉角会出现旋转自由度丢失
  • 插值平滑、数值稳定
  • Cesium 内部大量使用四元数

3. 四元数转旋转矩阵

复制代码
const m = Cesium.Matrix3.fromQuaternion(q);
  • 四元数便于计算与插值
  • 矩阵便于批量点旋转

4. 空间点绕任意轴旋转的标准三步法(你代码的核心)

对任意点 P,绕轴 u、中心 C 旋转 θ:

第 1 步:平移到局部原点(以中心为原点)

Plocal​=P−C

复制代码
const local = Cesium.Cartesian3.subtract(p, center, res);

第 2 步:绕原点旋转

Prot​=M⋅Plocal​

复制代码
const rotated = Cesium.Matrix3.multiplyByVector(m, local, res);

第 3 步:平移回世界中心

Pnew​=Prot​+C

复制代码
return Cesium.Cartesian3.add(rotated, center, res);

这就是 applyRotationrotatePoint 函数的完整数学含义。


七、旋转角度计算:atan2(sin, cos) 完整版数学

你旋转圈角度计算是图形学标准写法,很多人只会抄不懂原理,这里彻底拆开:

已知:

  • v0:起始方向(单位)
  • v1:当前方向(单位)
  • u:旋转轴(单位)

1. 求 cos

cosθ=v0​⋅v1​

复制代码
const cos = Cesium.Cartesian3.dot(this.dragStartVector, currentVector);

2. 求 sin(带方向)

v0​×v1​=usinθ再点乘旋转轴:sinθ=u⋅(v0​×v1​)

复制代码
const cross = Cesium.Cartesian3.cross(v0, v1, res);
const sin = Cesium.Cartesian3.dot(axis, cross);

3. 求带符号的真实角度

θ=atan2(sinθ, cosθ)

复制代码
const angle = Math.atan2(sin, cos);

意义:

  • 同时得到角度大小旋转方向(正负)
  • 范围:−π∼π,完美对应拖拽顺时针 / 逆时针
  • 这是 3D 旋转交互里最稳健、无歧义的角度计算方式

八、ENU 坐标系数学(为什么不用世界坐标系)

1. ENU:East-North-Up

以地面点 P 为原点:

  • E(X):切平面内向东
  • N(Y):切平面内向北
  • U(Z):沿椭球法向量向上(垂直当地地面)

2. 变换矩阵

复制代码
const enuTransform = Cesium.Transforms.eastNorthUpToFixedFrame(center);

这是一个 4x4 齐次变换矩阵,含义:

  • 列 0:E 方向单位向量
  • 列 1:N 方向单位向量
  • 列 2:U 方向单位向量
  • 列 3:平移分量(原点 center)

3. 提取轴向量

复制代码
const east  = Cesium.Matrix4.getColumn(enuTransform, 0, res); // X
const north = Cesium.Matrix4.getColumn(enuTransform, 1, res); // Y
const up    = Cesium.Matrix4.getColumn(enuTransform, 2, res); // Z

这就是 getAxisVector 的全部数学来源。


九、把数学串回你整套代码(一张总表)

代码片段 对应数学 作用
subtract(a,b) v=a−b 两点间向量
dot(axis, v) 投影长度 提取沿轴偏移,过滤其他方向
cross(a,b) 垂直于 a、b 的向量 构造平面法向量、求旋转方向
normalize(v) 单位化 点乘 = 投影、角度计算无缩放误差
Plane(normal, D) 平面方程 约束拖拽范围
rayPlane 射线与平面求交 鼠标 2D→平面上 3D 点
fromAxisAngle 轴角→四元数 描述绕轴旋转
Matrix3.multiplyByVector 矩阵 × 向量 点旋转
atan2(sin,cos) 带符号夹角 旋转角度与方向
eastNorthUpToFixedFrame ENU 局部坐标系 贴合地形的轴方向
相关推荐
测试者家园2 小时前
测试用例智能生成:是效率革命,还是“垃圾进,垃圾出”的新挑战?
人工智能·职场和发展·测试用例·测试策略·质量效能·智能化测试·用例设计
njsgcs2 小时前
dqn和cnn有什么区别 dqn怎么保存训练经验到本地
人工智能·神经网络·cnn
m0_686041612 小时前
C++中的适配器模式变体
开发语言·c++·算法
txzrxz2 小时前
结构体排序,双指针,单调栈
数据结构·算法·双指针算法·单调栈·结构体排序
AndrewHZ2 小时前
【AI黑话日日新】什么是AI智能体?
人工智能·算法·语言模型·大模型·llm·ai智能体
wWYy.2 小时前
算法:二叉树最大路径和
数据结构·算法
葱明撅腚2 小时前
利用Python挖掘城市数据
python·算法·gis·聚类
We་ct2 小时前
LeetCode 36. 有效的数独:Set实现哈希表最优解
前端·算法·leetcode·typescript·散列表
weixin_395448912 小时前
main.c_cursor_0129
前端·网络·算法