如何在C语言中使用结构体与四元数实现三维向量绕另一向量的指定角度旋转
如何在C语言中使用结构体与四元数实现三维向量绕另一向量的指定角度旋转
嗨,看起来你已经通过Rodrigues旋转公式结合数组完成了三维向量绕轴旋转的需求,现在想要改用结构体和四元数来实现——这确实是个更优雅、数值稳定性也更好的方案!我会一步步带你实现这个功能,从结构体封装到四元数旋转的完整流程。
四元数旋转的核心逻辑
用四元数实现向量绕轴旋转的步骤很清晰,核心是利用四元数的乘法来表示旋转:
- 归一化旋转轴:四元数旋转要求旋转轴是单位向量,所以首先要把输入的旋转轴K归一化
- 构造旋转四元数:给定旋转轴单位向量$\mathbf{k}=(k_x,k_y,k_z)$和旋转角度$\theta$,旋转四元数$Q$的形式为:
$$Q = \cos(\theta/2) + \sin(\theta/2) \cdot (k_x\mathbf{i} + Ky\mathbf{j} + Kz\mathbf{k})$$ - 向量转纯四元数:把待旋转的三维向量$\mathbf{U}$转换成纯四元数(实部为0,虚部为向量分量)
- 执行四元数乘法:旋转后的四元数$V_q = Q \cdot U_q \cdot Q*$,其中$Q*$是$Q$的共轭四元数(实部不变,虚部取反)
- 提取结果向量:$V_q$的虚部就是最终旋转后的三维向量$\mathbf{V}$
第一步:用结构体封装向量和四元数
用结构体可以让代码语义更清晰,避免数组下标带来的混淆:
#include <stdio.h> #include <math.h> #include <stdlib.h> // 三维向量结构体,直观表示x/y/z分量 typedef struct { double x, y, z; } Vec3; // 四元数结构体:w为实部,x/y/z为虚部对应i/j/k分量 typedef struct { double w, x, y, z; } Quaternion;
第二步:实现基础运算工具函数
我们需要先实现向量归一化、四元数乘法、共轭等基础操作,这些是旋转的核心依赖:
// 计算三维向量的模长 double vec3_magnitude(Vec3 v) { return sqrt(v.x*v.x + v.y*v.y + v.z*v.z); } // 归一化三维向量(避免除以0的边界处理) Vec3 vec3_normalize(Vec3 v) { double mag = vec3_magnitude(v); if (mag < 1e-9) { fprintf(stderr, "错误:无法归一化零向量!\n"); exit(EXIT_FAILURE); } return (Vec3){v.x/mag, v.y/mag, v.z/mag}; } // 把三维向量转换为纯四元数(实部为0) Quaternion vec3_to_quat(Vec3 v) { return (Quaternion){0.0, v.x, v.y, v.z}; } // 四元数乘法:q1 * q2,遵循四元数乘法规则 Quaternion quat_multiply(Quaternion q1, Quaternion q2) { Quaternion res; res.w = q1.w*q2.w - q1.x*q2.x - q1.y*q2.y - q1.z*q2.z; res.x = q1.w*q2.x + q1.x*q2.w + q1.y*q2.z - q1.z*q2.y; res.y = q1.w*q2.y - q1.x*q2.z + q1.y*q2.w + q1.z*q2.x; res.z = q1.w*q2.z + q1.x*q2.y - q1.y*q2.x + q1.z*q2.w; return res; } // 计算四元数的共轭(实部不变,虚部取反) Quaternion quat_conjugate(Quaternion q) { return (Quaternion){q.w, -q.x, -q.y, -q.z}; } // 从旋转轴(单位向量)和角度构造旋转四元数 Quaternion quat_from_axis_angle(Vec3 axis_unit, double angle_rad) { double half_angle = angle_rad / 2.0; double c = cos(half_angle); double s = sin(half_angle); return (Quaternion){ c, s * axis_unit.x, s * axis_unit.y, s * axis_unit.z }; }
第三步:实现四元数旋转核心函数
把前面的步骤整合起来,实现最终的旋转函数:
// 四元数旋转三维向量:U绕轴K旋转angle_rad弧度,结果存入V指针 void vec3_rotate_quat(Vec3 U, Vec3 K, double angle_rad, Vec3* V) { // 1. 归一化旋转轴 Vec3 K_unit = vec3_normalize(K); // 2. 构造旋转四元数 Quaternion Q = quat_from_axis_angle(K_unit, angle_rad); // 3. 把待旋转向量转为纯四元数 Quaternion Uq = vec3_to_quat(U); // 4. 计算旋转四元数的共轭 Quaternion Q_conj = quat_conjugate(Q); // 5. 执行四元数乘法:Vq = Q * Uq * Q_conj Quaternion temp = quat_multiply(Q, Uq); Quaternion Vq = quat_multiply(temp, Q_conj); // 6. 提取四元数的虚部作为旋转后的三维向量 V->x = Vq.x; V->y = Vq.y; V->z = Vq.z; }
完整测试示例
下面是包含交互式输入的完整代码,和你之前的数组版本用法一致:
#include <stdio.h> #include <math.h> #include <stdlib.h> // 三维向量结构体 typedef struct { double x, y, z; } Vec3; // 四元数结构体 typedef struct { double w, x, y, z; } Quaternion; // 计算向量模长 double vec3_magnitude(Vec3 v) { return sqrt(v.x*v.x + v.y*v.y + v.z*v.z); } // 归一化向量 Vec3 vec3_normalize(Vec3 v) { double mag = vec3_magnitude(v); if (mag < 1e-9) { fprintf(stderr, "错误:无法归一化零向量!\n"); exit(EXIT_FAILURE); } return (Vec3){v.x/mag, v.y/mag, v.z/mag}; } // 向量转纯四元数 Quaternion vec3_to_quat(Vec3 v) { return (Quaternion){0.0, v.x, v.y, v.z}; } // 四元数乘法 Quaternion quat_multiply(Quaternion q1, Quaternion q2) { Quaternion res; res.w = q1.w*q2.w - q1.x*q2.x - q1.y*q2.y - q1.z*q2.z; res.x = q1.w*q2.x + q1.x*q2.w + q1.y*q2.z - q1.z*q2.y; res.y = q1.w*q2.y - q1.x*q2.z + q1.y*q2.w + q1.z*q2.x; res.z = q1.w*q2.z + q1.x*q2.y - q1.y*q2.x + q1.z*q2.w; return res; } // 四元数共轭 Quaternion quat_conjugate(Quaternion q) { return (Quaternion){q.w, -q.x, -q.y, -q.z}; } // 从轴角构造旋转四元数 Quaternion quat_from_axis_angle(Vec3 axis_unit, double angle_rad) { double half_angle = angle_rad / 2.0; double c = cos(half_angle); double s = sin(half_angle); return (Quaternion){ c, s * axis_unit.x, s * axis_unit.y, s * axis_unit.z }; } // 四元数旋转向量 void vec3_rotate_quat(Vec3 U, Vec3 K, double angle_rad, Vec3* V) { Vec3 K_unit = vec3_normalize(K); Quaternion Q = quat_from_axis_angle(K_unit, angle_rad); Quaternion Uq = vec3_to_quat(U); Quaternion Q_conj = quat_conjugate(Q); Quaternion temp = quat_multiply(Q, Uq); Quaternion Vq = quat_multiply(temp, Q_conj); V->x = Vq.x; V->y = Vq.y; V->z = Vq.z; } int main(void) { printf("=== 四元数三维向量旋转测试 ===\n"); for (;;) { Vec3 U, K, V; double angle; char axes[] = "xyz"; // 输入向量U printf("\n请输入向量U的分量:\n"); for (int i = 0; i < 3; i++) { printf("U的%c分量: ", axes[i]); if (i == 0) { if (scanf("%lf", &U.x) != 1) break; } else if (i == 1) { if (scanf("%lf", &U.y) != 1) break; } else { if (scanf("%lf", &U.z) != 1) break; } } // 输入旋转轴K printf("\n请输入旋转轴K的分量:\n"); for (int i = 0; i < 3; i++) { printf("K的%c分量: ", axes[i]); if (i == 0) { if (scanf("%lf", &K.x) != 1) break; } else if (i == 1) { if (scanf("%lf", &K.y) != 1) break; } else { if (scanf("%lf", &K.z) != 1) break; } } // 输入旋转角度 printf("\n请输入旋转角度(弧度): "); if (scanf("%lf", &angle) != 1) break; // 执行旋转 vec3_rotate_quat(U, K, angle, &V); // 输出结果 printf("\n旋转后的向量V:\n"); for (int i = 0; i < 3; i++) { if (i == 0) { printf("V的x分量: %.8lf\n", V.x); } else if (i == 1) { printf("V的y分量: %.8lf\n", V.y); } else { printf("V的z分量: %.8lf\n", V.z); } } } return 0; }
为什么四元数方案更优?
和你之前用的Rodrigues公式(数组+三角函数)相比,四元数方案有这些优势:
- 数值稳定性更高:避免了Rodrigues公式中旋转角度接近0或π时的数值退化问题
- 代码可读性更好:结构体封装让向量和四元数的语义更明确,比数组下标更易维护
- 组合旋转更高效:如果需要多次连续旋转,四元数乘法比矩阵乘法计算量更小,也更容易组合
内容来源于stack exchange
相关产品推荐
相关产品推荐

