如何求解绕非正交向量c、a的旋转角度以得到目标向量v的朝向
非正交双轴旋转角度求解方案
前提约定
- 所有参与计算的向量(c、a、初始参考向量v₀、目标向量v)均先做单位归一化处理,朝向计算不受长度影响
- 旋转顺序固定为:先绕c轴旋转θ(对应
RotationC)得到中间向量v₁,再绕a轴旋转φ(对应RotationA)得到目标向量v - 罗德里格斯旋转公式:绕单位轴u旋转α角后的向量为
v' = v*cosα + (u×v)*sinα + u*(u·v)*(1-cosα)
求解步骤
步骤1:求解绕c轴的旋转角θ
绕a轴旋转不会改变向量与a的点积,因此可得到仅含θ的约束方程:(R(c,θ) * v₀) · a = v · a
将罗德里格斯公式代入后整理为三角函数标准形式:A*cosθ + B*sinθ = C
其中各系数计算方式:
A = Vector3.Dot(v0, a) - Vector3.Dot(c, a) * Vector3.Dot(c, v0)B = Vector3.Dot(Vector3.Cross(c, v0), a)C = Vector3.Dot(v, a) - Vector3.Dot(c, a) * Vector3.Dot(c, v0)
求解该方程即可得到θ,方程通解为:θ = Atan2(B, A) ± Acos(C / sqrt(A² + B²))
可根据实际业务场景选择符合预期的解即可,若C² > A²+B²说明目标向量v无法通过该双轴旋转得到。
步骤2:求解绕a轴的旋转角φ
代入已求得的θ,用罗德里格斯公式计算得到中间向量v₁:v1 = RotateAroundAxis(v0, c, θ)
将v₁和v分别投影到垂直于a的平面上,消除a方向分量的影响:
p1 = v1 - a * Vector3.Dot(v1, a)p2 = v - a * Vector3.Dot(v, a)
若p1、p2的模长接近0,说明v与a平行,φ可取任意默认值(通常设为0);否则对p1、p2做归一化后,构造正交基计算φ:x = Vector3.Cross(a, p1)RotationA = Math.Atan2(Vector3.Dot(p2, x), Vector3.Dot(p2, p1))
参考实现(C#风格)
// 工具方法:绕指定轴旋转向量 Vector3 RotateAroundAxis(Vector3 vec, Vector3 axis, double angle) { double cos = Math.Cos(angle); double sin = Math.Sin(angle); double dot = Vector3.Dot(vec, axis); Vector3 cross = Vector3.Cross(axis, vec); return vec * cos + cross * sin + axis * dot * (1 - cos); } // 主求解逻辑 (double RotationC, double RotationA) SolveRotationAngles(Vector3 c, Vector3 a, Vector3 v0, Vector3 v) { // 预处理:所有向量归一化 c = Vector3.Normalize(c); a = Vector3.Normalize(a); v0 = Vector3.Normalize(v0); v = Vector3.Normalize(v); // 计算系数求解θ double ca = Vector3.Dot(c, a); double cv0 = Vector3.Dot(c, v0); double A = Vector3.Dot(v0, a) - ca * cv0; double B = Vector3.Dot(Vector3.Cross(c, v0), a); double C = Vector3.Dot(v, a) - ca * cv0; double sqrtAB = Math.Sqrt(A * A + B * B); if (sqrtAB < 1e-6 || Math.Abs(C) > sqrtAB + 1e-6) { throw new ArgumentException("目标朝向无法通过当前双轴旋转得到"); } double alpha = Math.Atan2(B, A); double beta = Math.Acos(Math.Clamp(C / sqrtAB, -1, 1)); // 此处取第一个解,可根据需求调整为减beta的解 double theta = alpha + beta; // 求解φ Vector3 v1 = RotateAroundAxis(v0, c, theta); Vector3 p1 = v1 - a * Vector3.Dot(v1, a); Vector3 p2 = v - a * Vector3.Dot(v, a); if (p1.Length() < 1e-6 || p2.Length() < 1e-6) { return (theta, 0); } p1 = Vector3.Normalize(p1); p2 = Vector3.Normalize(p2); Vector3 x = Vector3.Cross(a, p1); double phi = Math.Atan2(Vector3.Dot(p2, x), Vector3.Dot(p2, p1)); return (theta, phi); }
参考示意图

内容的提问来源于stack exchange,提问作者PetrasVestartasEPFL
相关产品推荐
相关产品推荐

