SpaceMath 旋转矩阵 API 全解
SpaceMath 旋转矩阵 API 全解:mat3_st 列优先布局与 61 个矩阵接口详解
一、引言
旋转矩阵是三维旋转四种表示(欧拉角、旋转矩阵、四元数、轴角)中唯一无歧义、可直接作用于向量的表示:9 个数唯一确定一个旋转,v' = R·v 一次矩阵乘向量即完成旋转。它也是计算机图形学、机器人运动学、惯性导航姿态解算中最底层的计算形式——欧拉角和四元数最终往往都要落成矩阵去变换点云。
SpaceMath 中与旋转矩阵相关的接口分布在两个文件中:
| 文件 | 接口数 | 职责 |
|---|---|---|
matrix.c | 39(38 个 mat3_* + 主构造 mat3()) | 通用 3×3 矩阵线性代数:构造、判定、度量、算术、求逆、级数、正交化、插值、基本旋转矩阵构造 |
rotation.c | 22 | 矩阵与其他旋转表示的互转、相对旋转、矩阵作为/被旋转量 |
本文逐一解析这 61 个接口的功能、用法和注意事项,并给出可直接编译运行的 C 语言示例。文中示例均在 CFG_FLOATWORD_BITSIZE = 64(双精度)构建下实测验证。
二、数据结构 mat3_st:列优先存储与四重视图
理解 mat3_st 的内存布局是用好一切矩阵接口的前提。
2.1 列优先(Column-Major)布局
mat3_st 是一个联合体(union),同一块 9 元素内存提供四套访问视图(见 spacemath.h L296-328):
数学布局(行×列): 内存布局(列优先,9 个连续 ftype_t):
[ m11 m12 m13 ] [ m11 m21 m31 m12 m22 m32 m13 m23 m33 ]
[ m21 m22 m23 ] = [ v[0] v[1] v[2] v[3] v[4] v[5] v[6] v[7] v[8] ]
[ m31 m32 m33 ] [ vv[0][0] vv[0][1] vv[0][2] vv[1][0] ... ]
[ v3[0].x v3[0].y v3[0].z v3[1].x ... ]
四套视图对照:
| 视图 | 类型 | 索引含义 | 示例 |
|---|---|---|---|
| 具名成员 | m.mij | 第 i 行第 j 列 | m.m12 是第 1 行第 2 列 |
| 一维数组 | m.v[9] | 列优先展开 | m.v[1] 是 m21,不是 m12! |
| 二维数组 | m.vv[3][3] | vv[列][行] | m.vv[1][0] 是 m12 |
| 列向量 | m.v3[3] | 三个 vec3_st 列 | m.v3[0] 是第 1 列 |
头文件还提供了 MINDEX_M11 ... MINDEX_M33 枚举常量,用于按列优先索引 v[9](如 MINDEX_M12 == 3)。
2.2 为什么用列优先
- 物理意义直白:旋转矩阵的三个列向量就是坐标系三个基向量旋转后的像。
m.v3[0]直接给出"X 轴转到了哪里",做姿态可视化、方向提取(如机体系前向矢量)时零计算量。 - 符合数学惯例:与 Fortran、MATLAB、OpenGL 的列主序传统一致,
v' = R·v中 v 按列向量理解。 - cache 友好:按列遍历时内存连续,利于 CPU 缓存预取。
2.3 数组视图的自洽性(已修复)
mat3_st 的 v[9]、vv[3][3]、v3[3] 三套视图均为列优先。对应的三个数组构造函数 mat3_v、mat3_vv、mat3_v3 现已统一按列优先读取入参,因此按视图原样重建矩阵恒成立:
mat3_v(m.v) == m; // ✓ 自洽
mat3_vv(m.vv) == m; // ✓ 自洽
mat3_v3(m.v3) == m; // ✓ 自洽
三、构造函数(7 个)
| 接口 | 功能 | 入参约定 |
|---|---|---|
mat3(m11,...,m33) | 9 分量构造 | 按数学行序传参(m11, m12, m13, m21, …) |
mat3_v(v) | 一维数组构造 | 按列优先读取,v[0…2]=第1列,v[3…5]=第2列,v[6…8]=第3列 |
mat3_vv(vv) | 二维数组构造 | 按列优先读取,vv[列][行] |
mat3_v3(v3) | 三列向量构造 | v3[0..2] 依次为第 1/2/3 列 |
mat3_zero() | 全零矩阵 | — |
mat3_one() | 全 1 矩阵 | — |
mat3_identity() | 单位矩阵 | — |
3.1 主构造 mat3()
9 个参数按数学书写的行序排列,与直觉一致:
// 手写绕 Z 轴转 90° 的旋转矩阵
// [ 0 -1 0 ]
// [ 1 0 0 ]
// [ 0 0 1 ]
mat3_st m = mat3(0, -1, 0,
1, 0, 0,
0, 0, 1);
3.2 数组构造的统一约定(列优先)
三个数组构造函数现已统一按列优先读取入参,与 mat3_st 的三套视图一一对应,不再有约定不一致的问题:
| 函数 | 实现行为 | 是否自洽 |
|---|---|---|
mat3_v(v) | v[0..2]→第1列(m11,m21,m31),v[3..5]→第2列,v[6..8]→第3列 | mat3_v(m.v) == m ✓ |
mat3_vv(vv) | vv[列][行],vv[0][0]→m11, vv[1][0]→m12... | mat3_vv(m.vv) == m ✓ |
mat3_v3(v3) | v3[i] 直接赋给第 i 列 | mat3_v3(m.v3) == m ✓ |
示例:三种自洽的构造方式
#include "spacemath.h"
#include <stdio.h>
int main(void)
{
// 同一个旋转矩阵:绕 Z 轴 90°
mat3_st m1 = mat3(0, -1, 0, 1, 0, 0, 0, 0, 1); // 行序参数
ftype_t v[9] = {0, 1, 0, -1, 0, 0, 0, 0, 1}; // 列优先:第1列(0,1,0) 第2列(-1,0,0)
mat3_st m2 = mat3_v(v);
ftype_t vv[3][3] = {{0, 1, 0}, {-1, 0, 0}, {0, 0, 1}}; // vv[列][行]
mat3_st m3 = mat3_vv(vv);
vec3_st cols[3] = {vec3(0, 1, 0), vec3(-1, 0, 0), vec3(0, 0, 1)};
mat3_st m4 = mat3_v3(cols);
printf("m1 == m2: %d\n", mat3_is_equal(m1, m2)); // 1
printf("m1 == m3: %d\n", mat3_is_equal(m1, m3)); // 1
printf("m1 == m4: %d\n", mat3_is_equal(m1, m4)); // 1
// 列向量视图:旋转后基向量的像
printf("X轴的像: (%.4f, %.4f, %.4f)\n", m1.v3[0].x, m1.v3[0].y, m1.v3[0].z);
return 0;
}
输出:
m1 == m2: 1
m1 == m3: 1
m1 == m4: 1
X轴的像: (0.0000, 1.0000, 0.0000)
注意:
mat3_zero()返回的零矩阵不是合法旋转矩阵(行列式为 0、不正交),仅用作占位或累加初值。表示"无旋转"请用mat3_identity()。
四、判定函数(8 个)
| 接口 | 功能 | 容差 |
|---|---|---|
mat3_is_zero(m) | 是否零矩阵 | MEPSILON |
mat3_is_one(m) | 是否全 1 矩阵 | MEPSILON |
mat3_is_identity(m) | 是否单位矩阵 | MEPSILON |
mat3_is_equal(m0, m1) | 两矩阵是否相等 | MEPSILON |
mat3_is_equal_eps(m0, m1, eps) | 自定义容差判等 | 调用者指定 |
mat3_is_symmetric(m) | 是否对称矩阵(mij = mji) | MEPSILON |
mat3_is_orthogonal(m) | 是否正交矩阵(M·Mᵀ ≈ I) | MEPSILON |
mat3_is_diagonal(m) | 是否对角矩阵 | MEPSILON |
32 位构建下 MEPSILON 为 FLT_EPSILON ≈ 1.19e-7,64 位为 DBL_EPSILON ≈ 2.22e-16。
4.1 mat3_is_orthogonal 的两个注意事项
实现为:计算 M·Mᵀ,再用 mat3_is_equal(容差 MEPSILON)与单位矩阵比较。
- 不查行列式:反射矩阵(如
diag(1,1,-1),det = −1)同样满足 M·Mᵀ = I,会被判为"正交"。数学上"旋转矩阵"还需满足 det = +1,严格校验旋转合法性应同时检查:
bool is_rotation = mat3_is_orthogonal(m) && (mat3_det(m) > MFLOAT_C(0.0));
- 容差恰在误差量级:容差取的是机器精度本身(64 位下
DBL_EPSILON ≈ 2.22e-16),恰处于单次浮点运算的误差量级。直接构造的精确矩阵能通过;但经插值、正交化等运算累积误差后,M·Mᵀ 与 I 的偏差很容易达到甚至超过该容差,导致误判为不正交(本文第十三章的综合示例实测触发了这一边界:正交化结果的 M·Mᵀ 对角元偏差恰为 −2.22e-16,is_orthogonal返回 false)。工程上建议改用自定义容差自行判定:
mat3_st p = mat3_mul(m, mat3_transpose(m));
bool ortho = mat3_is_equal_eps(p, mat3_identity(), MFLOAT_C(1.0e-4));
4.2 示例
mat3_st rz90 = mat3_rotation_z(MPI_2);
printf("正交: %d\n", mat3_is_orthogonal(rz90)); // 1
printf("对称: %d\n", mat3_is_symmetric(rz90)); // 0
printf("对角: %d\n", mat3_is_diagonal(rz90)); // 0
printf("det = %.4f\n", mat3_det(rz90)); // 1.0000
五、度量函数(4 个)
| 接口 | 功能 | 实现 |
|---|---|---|
mat3_det(m) | 行列式 | Sarrus 法则(6 项乘积和) |
mat3_trace(m) | 迹(对角和) | m11 + m22 + m33 |
mat3_norm(m, type) | 矩阵范数 | type=0:Frobenius;type=1:1-范数;type=2:∞-范数 |
mat3_cond(m) | 条件数 | ‖M‖_F · ‖M⁻¹‖_F |
5.1 mat3_norm 的 type 约定
| type | 范数 | 定义 |
|---|---|---|
| 0 | Frobenius 范数 | 全部元素平方和开根号 |
| 1 | 1-范数 | 列元素绝对值和的最大值 |
| 2 | ∞-范数 | 行元素绝对值和的最大值 |
| 其他 | — | 返回 0 |
注意:type=2 是 ∞-范数,不是谱范数(最大奇异值 σmax)。需要谱范数需自行对 MᵀM 求特征值,库中未提供。
5.2 mat3_cond 的约定
- 使用 Frobenius 范数(不是常见的 2-范数条件数)。对正交矩阵:谱条件数 = 1,F 范数条件数 = ‖R‖_F·‖Rᵀ‖_F = √3·√3 = 3,不要因数值不为 1 而误判矩阵病态。
- 内部调用
mat3_inverse,而mat3_inverse对奇异矩阵静默返回单位矩阵(见第六章),因此奇异矩阵的 cond 会给出误导性的有限值‖M‖_F·√3,而非无穷大。判奇请直接看mat3_det。
5.3 示例
mat3_st s = mat3_scale_matrix(2.0, 4.0, 4.0); // 对角缩放矩阵
printf("det = %.4f\n", mat3_det(s)); // 32.0000
printf("trace = %.4f\n", mat3_trace(s)); // 10.0000
printf("F-norm = %.4f\n", mat3_norm(s, 0)); // 6.0000
printf("1-norm = %.4f\n", mat3_norm(s, 1)); // 4.0000
printf("inf-norm = %.4f\n", mat3_norm(s, 2)); // 4.0000
printf("cond = %.4f\n", mat3_cond(s)); // 3.6742
(cond 验算:‖S‖_F = √(4+16+16) = 6,S⁻¹ = diag(0.5, 0.25, 0.25),‖S⁻¹‖_F = √0.375 ≈ 0.6124,乘积 ≈ 3.6742。)
六、单目运算:取负、转置、求逆(3 个)
| 接口 | 功能 | 备注 |
|---|---|---|
mat3_negate(m) | 全元素取负 | −M |
mat3_transpose(m) | 转置 | 旋转矩阵的转置 = 逆旋转 |
mat3_inverse(m) | 求逆 | 伴随矩阵法;奇异时静默返回单位矩阵 |
6.1 mat3_inverse 的静默降级(重点)
实现用伴随矩阵法:M⁻¹ = adj(M) / det(M)。但开头有保护:
if (MFABS(det) < MEPSILON) {
return mat3_identity(); /* 奇异矩阵:返回单位矩阵作为安全值 */
}
这意味着调用者无法从返回值区分"求逆成功得到单位阵"和"矩阵奇异求逆失败"。对来源不可信的矩阵,务必先检查行列式:
if (MFABS(mat3_det(m)) > MFLOAT_C(1.0e-6)) {
mat3_st inv = mat3_inverse(m);
/* 安全使用 inv */
} else {
/* 矩阵奇异或接近奇异,走降级路径 */
}
6.2 求逆与转置的关系
对旋转矩阵(正交且 det=+1),R⁻¹ = Rᵀ,转置远比伴随矩阵法廉价且数值稳定。这正是 rotation.c 中 rotation_m_rvs(m) 直接用 mat3_transpose(m) 的原因。但对一般矩阵(含缩放、剪切)必须用 mat3_inverse。
mat3_st r = mat3_rotation_z(MPI_3); // 旋转矩阵
mat3_st inv1 = mat3_inverse(r); // 通用求逆
mat3_st inv2 = mat3_transpose(r); // 旋转矩阵专用捷径
// inv1 ≈ inv2(求逆法精度略差且慢)
七、算术运算(9 个)
| 接口 | 功能 | 数学式 |
|---|---|---|
mat3_add(m0, m1) | 矩阵加法 | M0 + M1 |
mat3_add_f(m, f) | 全元素加标量 | M + f |
mat3_sub(m0, m1) | 矩阵减法 | M0 − M1 |
mat3_sub_f(m, f) | 全元素减标量 | M − f |
mat3_mul_vec(m, v) | 矩阵乘向量 | M·v(列向量) |
mat3_mul(m0, m1) | 矩阵乘法 | M0·M1 |
mat3_mul_f(m, f) | 标量乘 | f·M |
mat3_scale(m, scalar) | 缩放 | 同 mat3_mul_f(包装) |
mat3_scale_matrix(sx, sy, sz) | 构造对角缩放矩阵 | diag(sx, sy, sz) |
7.1 mat3_mul 的复合顺序(重点)
mat3_mul(m0, m1) 计算 M0·M1。作用于向量时:
mat3_mul_vec(mat3_mul(m0, m1), v) == mat3_mul_vec(m0, mat3_mul_vec(m1, v))
即先对 v 施加 m1,再施加 m0(右乘先作用)。组合旋转时顺序不可交换:
mat3_st rz = mat3_rotation_z(MPI_2); // 绕 Z 转 90°
mat3_st ry = mat3_rotation_y(MPI_2); // 绕 Y 转 90°
vec3_st v = vec3(1.0, 0.0, 0.0);
// 先绕 Y 转再绕 Z 转
vec3_st r1 = mat3_mul_vec(mat3_mul(rz, ry), v); // → (0, 0, -1)
// 先绕 Z 转再绕 Y 转
vec3_st r2 = mat3_mul_vec(mat3_mul(ry, rz), v); // → (0, 1, 0)
两者结果完全不同,这就是"旋转不可交换"的代码体现。
7.2 加减法对旋转矩阵无意义
mat3_add/mat3_sub 是逐元素算术,两个旋转矩阵相加一般不再是旋转矩阵(行列式、正交性全部破坏)。它们服务于一般线性代数(如级数展开、mat3_exp 内部),不要用来"叠加旋转"——旋转的复合是乘法。
八、矩阵级数:exp / log / pow(3 个)
| 接口 | 功能 | 级数 |
|---|---|---|
mat3_exp(m) | 矩阵指数 | 泰勒展开前 5 项:I + M + M²/2! + M³/3! + M⁴/4! |
mat3_log(m) | 矩阵对数 | log(I+A) ≈ A − A²/2 + A³/3 − A⁴/4,A = M − I |
mat3_pow(m, p) | 矩阵幂 | M^p = exp(p·log(M)) |
8.1 mat3_exp 系数已修复(历史缺陷)
mat3_exp 实现泰勒级数 exp(M) ≈ I + M + M²/2! + M³/3! + M⁴/4!,各项系数现已与阶乘一致:
/* I + M */
result = mat3_add(result, term);
/* + M²/2! */
term = mat3_mul_f(term, 0.5);
result = mat3_add(result, term);
/* + M³/3! = M³/6 */
term = mat3_mul(term, m);
term = mat3_mul_f(term, 1.0/6.0); /* 3! = 6 */
result = mat3_add(result, term);
/* + M⁴/4! = M⁴/24 */
term = mat3_mul(term, m);
term = mat3_mul_f(term, 1.0/24.0); /* 4! = 24 */
result = mat3_add(result, term);
8.2 mat3_log 的收敛域限制
mat3_log 的级数系数(1, −1/2, 1/3, −1/4)是正确的,但该级数仅在 ‖M − I‖ < 1 时收敛,且只取了 4 项。即它只适合接近单位矩阵的 M(小旋转、小形变)。对旋转 90° 的矩阵,‖M−I‖ ≈ 1.41 已超出收敛半径,结果不可信。
8.3 小结
这三个级数函数目前是"简化实现"(源码注释自述),仅适合小量级矩阵的近似计算。涉及旋转的指数/对数映射,正确路径是:
指数映射:反对称矩阵 → 轴角 → rotation_a2m
对数映射:旋转矩阵 → rotation_m2a → 轴 × 角 构造反对称矩阵
九、工具函数:正交归一化与插值(2 个)
| 接口 | 功能 |
|---|---|
mat3_orthonormalization(basis) | 对三个列向量做 Gram-Schmidt 正交归一化 |
mat3_lerp(from, to, t) | 逐元素线性插值 |
9.1 mat3_orthonormalization:数值漂移的修复器
旋转矩阵经过多次乘法累积浮点误差后会"漂移"(列向量不再严格单位正交)。此函数对列做施密特正交化,把矩阵拉回正交群附近:
mat3_st fixed = mat3_orthonormalization(drifted_rotation);
注意事项:
- 按列处理:第一列方向保持不变,第二列减去在第一列上的投影,第三列减去在前两列上的投影。
- 线性相关时静默原样返回:三列若共面(行列式为 0),不做任何处理直接返回输入,调用方无感知。
- 不保证 det = +1:若输入是反射(det = −1),输出仍是反射。它只保证正交归一,不保证是"旋转"。
- 对漂移严重的矩阵,Gram-Schmidt 的几何意义是"偏向第一列",误差集中在后两列;更优方案是转四元数归一化后再转回(
rotation_q2m(quat_normalize(rotation_m2q(m)))),误差在三列间均摊。
9.2 mat3_lerp:逐元素插值不保证正交
result.mij = from.mij + (to.mij - from.mij) * t;
对 9 个元素各自线性插值。两个旋转矩阵的 lerp 结果几乎必然不是旋转矩阵(列向量长度被压缩、不再正交):
mat3_st a = mat3_identity();
mat3_st b = mat3_rotation_z(MPI_2);
mat3_st mid = mat3_lerp(a, b, MFLOAT_C(0.5));
printf("det = %.4f\n", mat3_det(mid)); // 0.5000(不是 1)
printf("正交: %d\n", mat3_is_orthogonal(mid)); // 0(不正交)
姿态插值的正确做法是用四元数球面插值 quat_slerp(等角速度、全程保持旋转性)。mat3_lerp 适合非旋转用途(如材质、形变参数混合)。若条件所限必须用矩阵插值,可在 lerp 后接 mat3_orthonormalization 近似修复(对小夹角效果尚可,且会偏向 from 的第一列方向)。
十、基本旋转矩阵构造(3 个)
| 接口 | 功能 | 非零元素 |
|---|---|---|
mat3_rotation_x(f) | 绕 X 轴旋转 f 弧度 | m22=c, m23=−s, m32=s, m33=c |
mat3_rotation_y(f) | 绕 Y 轴旋转 f 弧度 | m11=c, m13=s, m31=−s, m33=c |
mat3_rotation_z(f) | 绕 Z 轴旋转 f 弧度 | m11=c, m12=−s, m21=s, m22=c |
采用主动旋转、右手系、列向量约定 v' = R·v:拇指指向轴正方向,四指方向为正角度旋转方向。
以绕 Z 转 90° 为例:
mat3_st rz = mat3_rotation_z(MPI_2);
vec3_st v = mat3_mul_vec(rz, vec3(1.0, 0.0, 0.0)); // X 轴 → Y 轴
// v ≈ (0, 1, 0)
注意:
rotation.c中的rotation_m_rx/ry/rz就是这三个函数的薄包装,二者完全等价。mat3_rotation_y的符号布局(m13=s, m31=−s)是右手系标准形式,与某些采用行向量约定的教材符号相反,混用资料时注意。
十一、rotation.c 中的矩阵相关接口(22 个)
矩阵与欧拉角/四元数/轴角的互转全部集中在 rotation.c,详细原理见《SpaceMath 旋转 API 技术博客》,这里按功能分组列出与矩阵直接相关的 22 个。
11.1 基本操作(4 个)
| 接口 | 功能 | 实现 |
|---|---|---|
rotation_m_rvs(m) | 逆旋转 | mat3_transpose(m)(旋转矩阵的逆 = 转置) |
rotation_m_rx(angle) | 绕 X 基本旋转 | mat3_rotation_x(angle) |
rotation_m_ry(angle) | 绕 Y 基本旋转 | mat3_rotation_y(angle) |
rotation_m_rz(angle) | 绕 Z 基本旋转 | mat3_rotation_z(angle) |
11.2 其他表示 → 矩阵(5 个)
| 接口 | 功能 | 实现要点 |
|---|---|---|
rotation_a2m(a) | 轴角 → 矩阵 | 罗德里格斯公式 R = I + sinθ·K + (1−cosθ)·K² 解析直算 |
rotation_e2m(e) | 欧拉角 → 矩阵(默认转序 CFG_EULER_ORDER = “yxz”) | e2m_order 包装 |
rotation_e2m_order(e, order) | 欧拉角 → 矩阵(指定 6 种转序之一) | 按转序连乘三个基本旋转 |
rotation_q2m(q) | 四元数 → 矩阵 | 先归一化,再代标准公式 |
rotation_vv2m(to, from) | 向量对 → 矩阵 | q2m(vv2q(to, from)),求将 from 转到 to 的旋转 |
11.3 矩阵 → 其他表示(4 个)
| 接口 | 功能 | 实现要点 |
|---|---|---|
rotation_m2a(m) | 矩阵 → 轴角 | 走四元数路径 q2a(m2q(m)),角度规范到 [0, π] |
rotation_m2e(m) | 矩阵 → 欧拉角(默认转序) | m2e_order 包装 |
rotation_m2e_order(m, order) | 矩阵 → 欧拉角(指定转序) | 6 分支解析提取,含万向锁处理 |
rotation_m2q(m) | 矩阵 → 四元数 | Shepperd 方法:按迹符号与最大对角元分 4 支,数值稳定 |
为什么
m2a不直接用angle = acos((trace−1)/2)? 32 位构建下旋转角接近 π 时(trace−1)/2在浮点数上坍缩为 −1.0f,角度信息丢失;且轴提取需除以2·sin(angle),θ→π 时分母是垃圾值,轴方向完全错误。四元数路径(Shepperd +2·atan2(|v|, w))在 θ→0 和 θ→π 两端都数值稳定。这是本库实测随机测试暴露并修复过的真实缺陷。
11.4 相对旋转(1 个)
| 接口 | 功能 | 实现 |
|---|---|---|
rotation_mm2m(to, from) | 求 from 姿态到 to 姿态的相对旋转 | mat3_mul(to, mat3_transpose(from)),即 R_rel = R_to · R_fromᵀ |
参数顺序是 (to, from)——目标在前,起始在后。验证:rotation_mRm(rotation_mm2m(to, from), from) == to。
11.5 矩阵作为旋转量(5 个)
rotation_mRY(rm, Y):用旋转矩阵 rm 去旋转对象 Y。
| 接口 | 功能 | 实现 |
|---|---|---|
rotation_mRa(rm, a) | 矩阵旋转轴角 | 转四元数路径复合 |
rotation_mRe(rm, e) | 矩阵旋转欧拉角 | 转矩阵复合后转回 |
rotation_mRm(rm, m) | 矩阵旋转矩阵 | mat3_mul(rm, m)——rm 左乘,先施加 m 再施加 rm |
rotation_mRq(rm, q) | 矩阵旋转四元数 | 转四元数路径复合 |
rotation_mRv(rm, v) | 矩阵旋转向量 | mat3_mul_vec(rm, v),最直接的用法 |
11.6 矩阵被其他表示旋转(3 个)
| 接口 | 功能 | 含义 |
|---|---|---|
rotation_aRm(ra, m) | 用轴角 ra 旋转矩阵 m | a2m(ra) · m |
rotation_eRm(re, m) | 用欧拉角 re 旋转矩阵 m | e2m(re) · m |
rotation_qRm(rq, m) | 用四元数 rq 旋转矩阵 m | q2m(rq) · m |
三者都是"旋转量左乘被旋转矩阵"。
十二、注意事项汇总
- 列优先布局:
m.v[1]是 m21 不是 m12;m.vv[i][j]第 i 列第 j 行;m.v3[i]是第 i 列。写跨库数据交换代码(如对接行优先的 OpenGL ES 手写数组)时务必先确认顺序。 mat3_v()已改为列优先读入参,与v[9]视图自洽(mat3_v(m.v) == m恒成立)。三个数组构造函数mat3_v/mat3_vv/mat3_v3现已统一按列优先。mat3_inverse奇异时静默返回单位矩阵,无错误信号。不可信输入先查mat3_det。mat3_cond用 Frobenius 范数:正交矩阵 cond = 3 而非 1;奇异矩阵 cond 给出误导性有限值。mat3_norm(m, 2)是 ∞-范数不是谱范数;谱范数未提供。非法 type 返回 0。mat3_is_orthogonal不查 det = +1,反射矩阵也会判 true;且容差MEPSILON恰为机器精度量级,经插值、正交化等运算的矩阵可能恰好超界误判(综合示例实测:偏差 −2.22e-16 被判否),工程判定建议用mat3_is_equal_eps(..., 1e-4)自定义容差。mat3_exp系数已修复(历史缺陷):M³ 项现用1/6、M⁴ 项现用1/24,与阶乘一致。剩余误差来自仅取 5 项的截断;对旋转指数映射仍建议用rotation_a2m(罗德里格斯精确解析式)。mat3_log仅对 ‖M−I‖ < 1 收敛(且只取 4 项),只适合近单位矩阵。mat3_lerp逐元素插值,结果一般不再是旋转矩阵;姿态插值应使用quat_slerp。mat3_orthonormalization对列向量做 Gram-Schmidt:三列线性相关时静默原样返回;不保证 det = +1;误差偏向保持第一列。- 矩阵复合顺序:
mat3_mul(m0, m1)·v是先对 v 施加 m1 再施加 m0(右乘先作用),旋转不可交换。 - 零矩阵不是旋转矩阵:
mat3_zero()仅作占位/累加初值,"无旋转"用mat3_identity()。 rotation_mm2m(to, from)参数顺序是目标在前;所有rotation_XRY接口都是旋转量在前、被旋转对象在后。rotation_m2a已改走四元数路径,避免了acos((trace−1)/2)在 θ→π 的浮点坍缩(32 位构建下实测触发);升级旧代码时无需修改调用,角度范围仍是 [0, π]。
十三、综合示例
下面程序完整演示:构造、视图访问、度量、求逆、旋转向量、相对旋转、转轴角、正交化修复插值漂移。可用 gcc -I./src demo.c src/*.c -o demo 直接编译运行。
#include "spacemath.h"
#include <stdio.h>
static void print_mat3(const char *name, mat3_st m)
{
printf("%s =\n", name);
printf(" [ %7.4f %7.4f %7.4f ]\n", (double)m.m11, (double)m.m12, (double)m.m13);
printf(" [ %7.4f %7.4f %7.4f ]\n", (double)m.m21, (double)m.m22, (double)m.m23);
printf(" [ %7.4f %7.4f %7.4f ]\n", (double)m.m31, (double)m.m32, (double)m.m33);
}
int main(void)
{
/* ========== 1. 构造:函数生成 vs 手写分量 ========== */
mat3_st rz90 = mat3_rotation_z(MPI_2);
mat3_st manual = mat3(0, -1, 0, 1, 0, 0, 0, 0, 1); /* 行序参数 */
print_mat3("rz90", rz90);
/* 双精度下 cos(pi/2) = 6.1e-17,与手写的 0 相差 < MEPSILON(2.22e-16) */
printf("rz90 与手写矩阵相等: %d\n\n", mat3_is_equal(rz90, manual));
/* ========== 2. 列向量视图:基向量旋转后的像 ========== */
printf("X轴的像: (%.4f, %.4f, %.4f)\n",
(double)manual.v3[0].x, (double)manual.v3[0].y, (double)manual.v3[0].z);
printf("Y轴的像: (%.4f, %.4f, %.4f)\n\n",
(double)manual.v3[1].x, (double)manual.v3[1].y, (double)manual.v3[1].z);
/* ========== 3. 判定与度量 ========== */
printf("正交: %d, det = %.4f, trace = %.4f, F范数 = %.4f\n\n",
mat3_is_orthogonal(manual),
(double)mat3_det(manual),
(double)mat3_trace(manual),
(double)mat3_norm(manual, 0));
/* ========== 4. 矩阵旋转向量 ========== */
vec3_st v = rotation_mRv(manual, vec3(1.0, 0.0, 0.0));
printf("Rz90 * (1,0,0) = (%.4f, %.4f, %.4f)\n\n",
(double)v.x, (double)v.y, (double)v.z);
/* ========== 5. 相对旋转:Rz45 -> Rz90 的增量 ========== */
mat3_st rz45 = mat3_rotation_z(MPI_4);
mat3_st rel = rotation_mm2m(manual, rz45); /* = Rz90 * Rz45^T = Rz45 */
print_mat3("rel (应为Rz45)", rel);
/* ========== 6. 矩阵转轴角 ========== */
axis_st a = rotation_m2a(manual);
printf("m2a: 轴 = (%.4f, %.4f, %.4f), 角 = %.4f rad\n\n",
(double)a.v3.x, (double)a.v3.y, (double)a.v3.z, (double)a.angle);
/* ========== 7. 求逆验证:M * M^-1 = I ========== */
mat3_st prod = mat3_mul(manual, mat3_inverse(manual));
printf("M * M^-1 是单位阵: %d\n\n", mat3_is_identity(prod));
/* ========== 8. lerp 漂移与正交化修复 ========== */
mat3_st mid = mat3_lerp(mat3_identity(), manual, MFLOAT_C(0.5));
printf("lerp中点: det = %.4f, 正交 = %d (漂移,不是旋转矩阵)\n",
(double)mat3_det(mid), mat3_is_orthogonal(mid));
mat3_st fixed = mat3_orthonormalization(mid);
print_mat3("正交化修复后", fixed);
printf("修复后 det = %.4f\n", (double)mat3_det(fixed));
printf("is_orthogonal(严容差): %d\n", mat3_is_orthogonal(fixed));
printf("is_equal_eps(自定义1e-4): %d\n",
mat3_is_equal_eps(mat3_mul(fixed, mat3_transpose(fixed)),
mat3_identity(), MFLOAT_C(1.0e-4)));
return 0;
}
预期输出(64 位构建,%.4f 精度,关键值均经手工验算与实机运行核对):
rz90 =
[ 0.0000 -1.0000 0.0000 ]
[ 1.0000 0.0000 0.0000 ]
[ 0.0000 0.0000 1.0000 ]
rz90 与手写矩阵相等: 1
X轴的像: (0.0000, 1.0000, 0.0000)
Y轴的像: (-1.0000, 0.0000, 0.0000)
正交: 1, det = 1.0000, trace = 1.0000, F范数 = 1.7321
Rz90 * (1,0,0) = (0.0000, 1.0000, 0.0000)
rel (应为Rz45) =
[ 0.7071 -0.7071 0.0000 ]
[ 0.7071 0.7071 0.0000 ]
[ 0.0000 0.0000 1.0000 ]
m2a: 轴 = (0.0000, 0.0000, 1.0000), 角 = 1.5708 rad
M * M^-1 是单位阵: 1
lerp中点: det = 0.5000, 正交 = 0 (漂移,不是旋转矩阵)
正交化修复后 =
[ 0.7071 -0.7071 0.0000 ]
[ 0.7071 0.7071 0.0000 ]
[ 0.0000 0.0000 1.0000 ]
修复后 det = 1.0000
is_orthogonal(严容差): 0
is_equal_eps(自定义1e-4): 1
第 1 节中
rz90与手写精确矩阵判等通过,是因为双精度cos(π/2) ≈ 6.1e-17的残差小于MEPSILON。第 8 节出现耐人寻味的对照:lerp 中点经正交化后 det 精确回到 1、与 Rz(45°) 在 1e-4 容差下完全一致,但is_orthogonal用DBL_EPSILON严容差判定却返回 0——其 M·Mᵀ 对角元偏差恰为 −2.22e-16,落在"严格小于"的边界之外。这正是注意事项第 6 条的实证:mat3_is_orthogonal的机器级容差对经过运算的矩阵偏严,工程判定应使用mat3_is_equal_eps自定义容差。另外,lerp 中点修复后恰为 Rz(45°) 是 from/to 关于 Z 轴对称的特例,一般情形修复结果只是"接近"理想中值旋转;生产代码的姿态插值仍应使用quat_slerp。
十四、API 速查表
matrix.c(39 个)
| 分类 | 接口 |
|---|---|
| 构造(7) | mat3 mat3_v mat3_vv mat3_v3 mat3_zero mat3_one mat3_identity |
| 判定(8) | mat3_is_zero mat3_is_one mat3_is_identity mat3_is_equal mat3_is_equal_eps mat3_is_symmetric mat3_is_orthogonal mat3_is_diagonal |
| 度量(4) | mat3_det mat3_trace mat3_norm mat3_cond |
| 单目(3) | mat3_negate mat3_transpose mat3_inverse |
| 算术(9) | mat3_add mat3_add_f mat3_sub mat3_sub_f mat3_mul_vec mat3_mul mat3_mul_f mat3_scale mat3_scale_matrix |
| 级数(3) | mat3_exp mat3_log mat3_pow |
| 工具(2) | mat3_orthonormalization mat3_lerp |
| 旋转构造(3) | mat3_rotation_x mat3_rotation_y mat3_rotation_z |
rotation.c 矩阵相关(22 个)
| 分类 | 接口 |
|---|---|
| 基本(4) | rotation_m_rvs rotation_m_rx rotation_m_ry rotation_m_rz |
| 转入(5) | rotation_a2m rotation_e2m rotation_e2m_order rotation_q2m rotation_vv2m |
| 转出(4) | rotation_m2a rotation_m2e rotation_m2e_order rotation_m2q |
| 相对(1) | rotation_mm2m |
| 作旋转量(5) | rotation_mRa rotation_mRe rotation_mRm rotation_mRq rotation_mRv |
| 被旋转(3) | rotation_aRm rotation_eRm rotation_qRm |
十五、总结
matrix.c 与 rotation.c 共同构成 SpaceMath 的矩阵能力:matrix.c 提供通用 3×3 线性代数底座(构造、判定、度量、算术、求逆、级数、正交化、插值、基本旋转矩阵),rotation.c 提供矩阵与欧拉角/四元数/轴角的互转及旋转语义封装。
使用中牢记五条主线:
- 布局意识:列优先四视图,三个数组构造函数
mat3_v/mat3_vv/mat3_v3已统一按列优先、与各自视图自洽(mat3_v(m.v) == m)。 - 旋转合法性:正交 + det=+1 才是旋转矩阵;
is_orthogonal只查前者。 - 静默降级:
inverse奇异返回单位阵、orthonormalization线性相关原样返回、norm非法 type 返回 0——边界情况都不报错,调用方需自行前置校验。 - 级数函数是简化实现:
mat3_exp系数已修复(M³/6、M⁴/24),但仅取前 5 项截断误差较大;mat3_log收敛域有限。旋转的指数/对数映射应走轴角路径(rotation_a2m/rotation_m2a)。 - 插值不归矩阵管:
mat3_lerp只做逐元素混合,姿态插值交给quat_slerp。
掌握这些,矩阵这一"旋转计算的最终落点"就能用得既正确又高效。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)