SpaceMath 旋转 API 全解:从欧拉角到四元数,60 个旋转接口的功能、用法与注意事项
SpaceMath 旋转 API 全解:从欧拉角到四元数,60 个旋转接口的功能、用法与注意事项
一、引言
三维空间旋转是图形学、机器人、航空航天姿态解算的核心问题。同一个旋转可以用欧拉角、旋转矩阵、四元数、轴角四种表示描述,它们各有优劣:
| 表示 | 优点 | 缺点 |
|---|---|---|
| 欧拉角 | 直观、人类易读 | 万向锁、转序敏感、插值不平滑 |
| 旋转矩阵 | 无歧义、可直接乘向量 | 参数冗余(9个数)、不直观 |
| 四元数 | 无万向锁、插值平滑、紧凑 | 不直观、有 q≡-q 双重覆盖 |
| 轴角 | 最直观(轴+角) | 角度接近 0/π 时不稳定 |
SpaceMath 的 rotation.c 正是为了解决"四种表示之间的互联互通"而存在——它提供了 60 个旋转相关 API,覆盖:基本旋转构造、四种表示两两互转、由向量对求旋转、相对旋转计算、以及"用任意表示旋转任意对象"的统一应用接口。
本文将逐一解析这些 API 的功能、用法和注意事项,并给出可直接编译运行的 C 语言示例。
二、命名约定与整体设计
在逐个介绍 API 之前,先理解 SpaceMath 的三条核心设计约定,它们贯穿所有 60 个接口。
2.1 单字母类型编码
| 字母 | 类型 | 全称 |
|---|---|---|
a | axis_st | 轴角(Axis-angle) |
e | eula_st | 欧拉角(Euler angles) |
m | mat3_st | 旋转矩阵(Matrix) |
q | quat_st | 四元数(Quaternion) |
v | vec3_st | 三维向量(Vector) |
2.2 内旋约定(Intrinsic Rotation)
欧拉角到矩阵/四元数的转换采用内旋约定:旋转顺序字符串从左到右表示绕自身当前坐标轴依次旋转。矩阵组合顺序为 R = R₃ · R₂ · R₁,即先执行最右侧的旋转。
例如转序 "zyx" 表示:先绕自身 Z 轴旋转,再绕新的 Y 轴,最后绕新的 X 轴。
2.3 值语义 + 级联调用
所有接口入参和返回值均为结构体值(无指针),可像数学公式一样级联书写:
// 用四元数旋转向量,再单位化,再与参考方向求夹角
ftype_t a = vec3_angle(vec3_unit(rotation_qRv(q, v)), vref);
三、基本旋转操作(16 个)
这组 API 用于构造"绕坐标轴的基本旋转"或求"逆旋转",是所有旋转计算的原子积木。
3.1 绕坐标轴旋转构造(12 个)
每种表示都有 rx/ry/rz 三个接口,输入角度(弧度),返回对应表示的旋转。
| 轴角 | 欧拉角 | 矩阵 | 四元数 |
|---|---|---|---|
rotation_a_rx | rotation_e_rx | rotation_m_rx | rotation_q_rx |
rotation_a_ry | rotation_e_ry | rotation_m_ry | rotation_q_ry |
rotation_a_rz | rotation_e_rz | rotation_m_rz | rotation_q_rz |
示例:构造绕 Z 轴旋转 90° 的各种表示
#include "spacemath.h"
#include <stdio.h>
int main(void) {
ftype_t angle = MPI_2; // 90°,库提供的 π/2 常量
axis_st a = rotation_a_rz(angle); // 轴角:轴(0,0,1),角π/2
eula_st e = rotation_e_rz(angle); // 欧拉角:(0, 0, π/2)
mat3_st m = rotation_m_rz(angle); // 旋转矩阵
quat_st q = rotation_q_rz(angle); // 四元数
printf("Euler: (%.3f, %.3f, %.3f)\n", e.x, e.y, e.z);
printf("Quat : (%.3f, %.3f, %.3f, %.3f)\n", q.x, q.y, q.z, q.w);
return 0;
}
输出:
Euler: (0.000, 0.000, 1.571)
Quat : (0.000, 0.000, 0.707, 0.707)
3.2 逆旋转(4 个)
| 接口 | 实现方式 |
|---|---|
rotation_a_rvs(a) | 旋转轴取反,角度不变 |
rotation_e_rvs(e) | 三个角度分量全部取反 |
rotation_m_rvs(m) | 矩阵转置(旋转矩阵的逆 = 转置) |
rotation_q_rvs(q) | 四元数共轭 |
示例:验证逆旋转的性质
// 构造一个旋转 q 和它的逆 q_inv
quat_st q = rotation_q_rz(MPI_4); // 绕 Z 轴 45°
quat_st q_inv = rotation_q_rvs(q); // 逆旋转
// q 之后施加 q_inv,应回到单位旋转(等价于 q * q_inv = identity)
quat_st result = rotation_qRq(q_inv, q); // 用 q_inv 旋转 q
// 此时 result 应为单位四元数 (0,0,0,1)
注意:四元数的逆旋转是共轭而非取负。
q和-q表示同一个旋转,-q不是逆旋转。
四、旋转表示互转
4.1 显式指定转序的欧拉角转换(4 个)
| 接口 | 功能 |
|---|---|
rotation_e2m_order(e, order) | 欧拉角 → 旋转矩阵(指定转序) |
rotation_m2e_order(m, order) | 旋转矩阵 → 欧拉角(指定转序) |
rotation_e2q_order(e, order) | 欧拉角 → 四元数(指定转序) |
rotation_q2e_order(q, order) | 四元数 → 欧拉角(指定转序) |
支持的 6 种转序:"xyz"、"xzy"、"yxz"、"yzx"、"zxy"、"zyx"。
示例:用 zyx 转序将欧拉角转矩阵
eula_st e = eula(MPI_4, MPI_6, MPI_3); // roll=45°, pitch=30°, yaw=60°
mat3_st m = rotation_e2m_order(e, "zyx");
// 验证:用 m 旋转向量,再转回欧拉角应一致
eula_st e_back = rotation_m2e_order(m, "zyx");
万向锁处理
rotation_m2e_order 是实现最复杂的函数。当第二个旋转角接近 ±π/2 时会发生万向锁(Gimbal Lock),此时第一和第三个旋转轴重合,自由度丢失。代码对 6 种转序分别做了万向锁检测与处理:
// 以 zyx 转序为例,当 m13 接近 ±1 时判定为万向锁
if (MFABS(m.m13 + 1.0) < epsilon) {
e.y = -MPI_2; // pitch = -90°
e.x = 0.0; // roll 置零
e.z = MATAN2(m.m21, m.m22); // yaw 取合并值
} else if (MFABS(m.m13 - 1.0) < epsilon) {
e.y = MPI_2;
e.x = 0.0;
e.z = MATAN2(-m.m21, -m.m22);
}
注意:万向锁下欧拉角分解不唯一,库将第一个角置零、把合并量放到第三个角。如需避免万向锁问题,建议使用四元数或轴角表示。
4.2 默认转序的两两互转(12 个)
这是最常用的一组接口,使用 spacemath.h 中 CFG_EULER_ORDER(默认 "yxz")指定的转序。
a e m q
┌─────────────────────────────────┐
a │ - a2e a2m a2q │
e │ e2a - e2m e2q │
m │ m2a m2e - m2q │
q │ q2a q2e q2m - │
└─────────────────────────────────┘
核心实现原理:
rotation_m2q:Shepperd 算法,按矩阵迹的正负和主对角线最大元分 4 种情况计算,避免数值不稳定rotation_q2m:四元数归一化后代入标准公式rotation_m2a:angle = acos((trace-1)/2),轴从反对称部分提取,做除零保护rotation_q2a:angle = 2*acos(w),轴为(x,y,z)归一化,角度用remainder规范到[-π, π]rotation_a2m:直接用罗德里格斯公式R = I + sinθ·K + (1-cosθ)·K²
示例:四元数与欧拉角互转
// 欧拉角 → 四元数
eula_st e = eula(0.0, 0.0, MPI_2); // 绕 Z 轴 90°
quat_st q = rotation_e2q(e);
// 四元数 → 欧拉角
eula_st e2 = rotation_q2e(q);
// e2 应等于 (0, 0, π/2)
示例:矩阵转四元数的数值稳定性
// 单位矩阵的迹=3>0,走第一分支,直接得到单位四元数
mat3_st I = mat3_identity();
quat_st q = rotation_m2q(I); // → (0, 0, 0, 1)
// 旋转 180° 的矩阵迹=-1,会走到其他分支
mat3_st m180 = rotation_m_rx(MPI); // 绕 X 轴 180°
quat_st q2 = rotation_m2q(m180); // → (1, 0, 0, 0) 附近
注意:
rotation_m2q和rotation_q2a在角度接近 0 或 π 时会做特殊处理,避免除以零。rotation_q2a对零角返回axis_zero()。
4.3 由向量对求旋转(4 个)
| 接口 | 功能 |
|---|---|
rotation_vv2a(to, from) | 求将 from 旋转到 to 的轴角 |
rotation_vv2e(to, from) | 求将 from 旋转到 to 的欧拉角 |
rotation_vv2m(to, from) | 求将 from 旋转到 to 的旋转矩阵 |
rotation_vv2q(to, from) | 求将 from 旋转到 to 的四元数 |
参数顺序:第一个是目标向量 to,第二个是起始向量 from。
示例:将 X 轴旋转到 Y 轴
vec3_st from = vec3(1.0, 0.0, 0.0); // 起始方向
vec3_st to = vec3(0.0, 1.0, 0.0); // 目标方向
quat_st q = rotation_vv2q(to, from); // 旋转四元数
// 验证:用 q 旋转 from,应得到 to
vec3_st result = rotation_qRv(q, from);
// result ≈ (0, 1, 0)
注意:当
from和to平行(夹角 0° 或 180°)时,旋转轴不唯一。quat_from_vv内部会处理这种退化情况。
4.4 相对旋转(4 个)
计算从 from 姿态到 to 姿态的"旋转增量"。
| 接口 | 功能 |
|---|---|
rotation_aa2a(to, from) | 两轴角之间的相对旋转轴角 |
rotation_ee2e(to, from) | 两欧拉角之间的相对旋转欧拉角 |
rotation_mm2m(to, from) | 两矩阵之间的相对旋转矩阵 |
rotation_qq2q(to, from) | 两四元数之间的相对旋转四元数 |
数学原理:to = R_relative · from,因此 R_relative = to · from⁻¹。
- 四元数:
R = to * inverse(from) - 矩阵:
R = to * transpose(from) - 欧拉角/轴角:先转四元数或矩阵,计算后转回
示例:计算两姿态之间的相对旋转
quat_st from = quat_identity(); // 起始姿态
quat_st to = rotation_q_rz(MPI_2); // 目标姿态:绕 Z 转 90°
quat_st rel = rotation_qq2q(to, from); // 相对旋转 = 绕 Z 转 90°
// 验证:用 rel 旋转 from,应得到 to
quat_st check = rotation_qRq(rel, from);
// check ≈ to
注意:相对旋转的参数顺序是
(to, from),与直觉可能相反——先写目标,再写起始。
五、旋转应用接口 rotation_XRY(20 个)
这是 SpaceMath 最具特色的一组 API:用任意一种表示去旋转任意一种表示或向量,共 4×5 = 20 个接口。
命名规则:rotation_XRY,其中 X 是旋转量的表示,Y 是被旋转对象的表示。
参数顺序:第一个参数是旋转量,第二个是被旋转对象。
被旋转对象 → a e m q v
旋转量 ↓
a aRa aRe aRm aRq aRv
e eRa eRe eRm eRq eRv
m mRa mRe mRm mRq mRv
q qRa qRe qRm qRq qRv
5.1 旋转向量(最常用,4 个)
rotation_aRv、rotation_eRv、rotation_mRv、rotation_qRv
示例:用四元数旋转向量
quat_st q = rotation_q_rz(MPI_2); // 绕 Z 轴 90°
vec3_st v = vec3(1.0, 0.0, 0.0); // X 轴
vec3_st v2 = rotation_qRv(q, v); // 用 q 旋转 v
// v2 ≈ (0, 1, 0),即 Y 轴
示例:用欧拉角旋转向量
eula_st e = eula(0.0, 0.0, MPI_2); // 绕 Z 轴 90°
vec3_st v = vec3(1.0, 0.0, 0.0);
vec3_st v2 = rotation_eRv(e, v);
// v2 ≈ (0, 1, 0)
内部实现:
rotation_aRv/rotation_qRv走四元数分量法quat_rotate_vec3_components;rotation_eRv/rotation_mRv先转矩阵再乘向量。
5.2 旋转四元数(4 个)
rotation_aRq、rotation_eRq、rotation_mRq、rotation_qRq
示例:连续施加两个旋转
quat_st q1 = rotation_q_rz(MPI_2); // 先绕 Z 转 90°
quat_st q2 = rotation_q_ry(MPI_2); // 再绕 Y 转 90°
// 组合:先 q1 后 q2(注意四元数乘法顺序)
quat_st combined = rotation_qRq(q2, q1);
// 含义:用 q2 去旋转 q1,等价于 q2 * q1
注意:四元数旋转的组合顺序。
rotation_qRq(rq, q)内部是quat_mul(rq, q),即rq左乘q,表示"先施加 q,再施加 rq"。
5.3 旋转矩阵(4 个)
rotation_aRm、rotation_eRm、rotation_mRm、rotation_qRm
示例:用旋转矩阵旋转旋转矩阵
mat3_st rz = rotation_m_rz(MPI_2); // 旋转量:绕 Z 90°
mat3_st m = mat3_identity(); // 被旋转对象:单位矩阵
mat3_st result = rotation_mRm(rz, m);
// result ≈ rz(单位矩阵被旋转后等于旋转量本身)
5.4 旋转欧拉角(4 个)
rotation_aRe、rotation_eRe、rotation_mRe、rotation_qRe
示例:用四元数旋转欧拉角
quat_st q = rotation_q_rz(MPI_2); // 旋转量
eula_st e = eula_zero(); // 被旋转对象:零欧拉角
eula_st result = rotation_qRe(q, e);
// result ≈ (0, 0, π/2)
5.5 旋转轴角(4 个)
rotation_aRa、rotation_eRa、rotation_mRa、rotation_qRa
示例:用轴角旋转轴角
axis_st ra = rotation_a_rz(MPI_2); // 旋转量
axis_st a = axis_zero(); // 被旋转对象:零轴角
axis_st result = rotation_aRa(ra, a);
// result ≈ ra
内部实现:轴角和欧拉角的旋转组合统一通过四元数中转(先转四元数、相乘、再转回),避免矩阵/欧拉角的数值问题,且自动归一化。
六、注意事项汇总
6.1 欧拉角转序
- 默认转序由
CFG_EULER_ORDER宏控制(当前为"yxz"),修改它会影响所有不带_order后缀的欧拉角转换接口。 - 航天领域常用
"zyx"(yaw-pitch-roll),如需切换可改宏或使用*_order接口。 - 六种有效转序:
xyz、xzy、yxz、yzx、zxy、zyx。转序字符串必须由小写xyz组成且三轴不重复。
6.2 万向锁
- 欧拉角在第二个角接近 ±90° 时发生万向锁,
rotation_m2e_order已做处理但分解不唯一。 - 建议:需要频繁插值或避免奇异的场合,用四元数;只在人机交互或最终输出时用欧拉角。
6.3 四元数双重覆盖
q和-q表示同一个旋转。rotation_m2q返回的四元数符号由算法分支决定,可能与预期相反。- 比较两个四元数是否表示同一旋转,应使用
quat_is_equal(库内部会处理符号),而非逐分量==。
6.4 归一化
rotation_q2m、rotation_q2a内部会先归一化四元数,输入未归一化的四元数也能正确工作。rotation_XRq系列(旋转四元数的结果)会自动归一化输出。- 但作为旋转量传入的四元数,建议事先归一化以获得最佳精度。
6.5 角度单位
- 所有角度参数和返回值均为弧度。角度转弧度用
MDEG2RAD,弧度转角度用MRAD2DEG,或用eula_deg2rad/eula_rad2deg。
6.6 零旋转处理
rotation_a2q、rotation_a2m对零轴角返回单位四元数/单位矩阵。rotation_q2a、rotation_m2a对接近零的旋转角返回axis_zero(),避免除零。
6.7 旋转组合顺序
- 矩阵:
rotation_mRm(rm, m) = rm * m,表示先施加m再施加rm。 - 四元数:
rotation_qRq(rq, q) = rq * q,表示先施加q再施加rq。 - 统一规律:第一个参数后施加,第二个参数先施加。
七、综合示例:姿态解算中的旋转链式计算
下面是一个贴近实际的示例:已知传感器测得的角速度,用四元数递推姿态,然后转换为欧拉角输出,并计算相对起始姿态的偏航角。
#include "spacemath.h"
#include <stdio.h>
int main(void) {
// 1. 初始姿态:单位四元数
quat_st q = quat_identity();
// 2. 模拟角速度(弧度/秒)和时间步长
vec3_st omega = vec3(MDEG2RAD(10.0), MDEG2RAD(20.0), MDEG2RAD(30.0));
ftype_t dt = MFLOAT_C(0.01); // 10ms
// 3. 用角速度积分递推四元数(库内置接口)
q = quat_integrate_w(q, omega, dt);
// 4. 四元数 → 欧拉角(默认 yxz 转序),用于显示
eula_st e = rotation_q2e(q);
printf("Attitude (deg): roll=%.2f pitch=%.2f yaw=%.2f\n",
e.x * MRAD2DEG, e.y * MRAD2DEG, e.z * MRAD2DEG);
// 5. 计算相对起始姿态的旋转(偏航增量)
quat_st rel = rotation_qq2q(q, quat_identity());
eula_st rel_e = rotation_q2e(rel);
printf("Yaw delta (deg): %.2f\n", rel_e.z * MRAD2DEG);
// 6. 将当前姿态的 X 轴方向旋转到世界坐标系
vec3_st body_x = vec3(1.0, 0.0, 0.0);
vec3_st world_x = rotation_qRv(q, body_x);
printf("Body X in world: (%.3f, %.3f, %.3f)\n",
world_x.x, world_x.y, world_x.z);
return 0;
}
编译运行(64 位双精度):
gcc -O2 -I./src demo.c src/*.c -lm -o demo && ./demo
八、API 速查表
基本旋转操作(16)
| 类别 | 接口 |
|---|---|
| 逆旋转 | rotation_a_rvs rotation_e_rvs rotation_m_rvs rotation_q_rvs |
| 绕X轴 | rotation_a_rx rotation_e_rx rotation_m_rx rotation_q_rx |
| 绕Y轴 | rotation_a_ry rotation_e_ry rotation_m_ry rotation_q_ry |
| 绕Z轴 | rotation_a_rz rotation_e_rz rotation_m_rz rotation_q_rz |
表示互转(24)
| 类别 | 接口 |
|---|---|
| 指定转序 | rotation_e2m_order rotation_m2e_order rotation_e2q_order rotation_q2e_order |
| 两两互转 | rotation_a2e rotation_a2m rotation_a2q rotation_e2a rotation_e2m rotation_e2q rotation_m2a rotation_m2e rotation_m2q rotation_q2a rotation_q2e rotation_q2m |
| 向量对求旋转 | rotation_vv2a rotation_vv2e rotation_vv2m rotation_vv2q |
| 相对旋转 | rotation_aa2a rotation_ee2e rotation_mm2m rotation_qq2q |
旋转应用 rotation_XRY(20)
| 旋转量 \ 对象 | 轴角 a | 欧拉 e | 矩阵 m | 四元数 q | 向量 v |
|---|---|---|---|---|---|
| 轴角 a | aRa | aRe | aRm | aRq | aRv |
| 欧拉 e | eRa | eRe | eRm | eRq | eRv |
| 矩阵 m | mRa | mRe | mRm | mRq | mRv |
| 四元数 q | qRa | qRe | qRm | qRq | qRv |
九、总结
SpaceMath rotation.c 的 60 个旋转 API 形成了一个完整、自洽的旋转计算体系:
- 全方向互转:四种表示两两可达,无需绕道"中间表示"。
- 统一命名:
rotation_XRY一眼可读,参数顺序一致(旋转量在前,对象在后)。 - 数值稳健:矩阵转四元数用 Shepperd 算法分 4 分支;轴角提取做除零保护;欧拉角转换处理 6 种转序的万向锁。
- 内旋约定:转序字符串从左到右为内旋顺序,矩阵组合
R = R₃·R₂·R₁。 - 值语义级联:无指针参数,可像数学公式一样嵌套调用。
掌握这些接口后,姿态解算、坐标变换、旋转插值等常见任务都能用一两行级联代码完成,无需再纠结四元数乘法顺序或欧拉转序约定。
参考链接:
- SpaceMath 源码仓库:https://github.com/scilogyhunter/SpaceMath
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)