引言
陀螺是经典的旋转玩具,高速旋转时能在尖端保持直立而不倒,这一现象涉及刚体转动动力学中的进动与章动。本文用Java实现一个三维陀螺物理模拟系统,核心算法包括欧拉角姿态表示、基于欧拉方程的刚体旋转动力学数值积分,以及用PID控制器实现主动姿态稳定,同时分析角动量守恒条件下的陀螺稳定性判据。
核心算法思路
1. 刚体旋转动力学(欧拉方程)
对于绕固定点旋转的刚体,在随体坐标系(主轴坐标系)中,角速度 ω = (ωx, ωy, ωz) 满足欧拉方程:
– Ix * dωx/dt = (Iy - Iz) * ωy * ωz + τx
– Iy * dωy/dt = (Iz - Ix) * ωz * ωx + τy
– Iz * dωz/dt = (Ix - Iy) * ωx * ωy + τz
其中 Ix, Iy, Iz 为主转动惯量,τ 为外力矩(重力矩)。对于对称陀螺(Ix = Iy ≠ Iz),方程可大幅简化。
2. 四元数姿态更新
为避免欧拉角的万向节锁问题,使用四元数表示姿态。设当前姿态四元数为 q,角速度为 ω,则:
– dq/dt = 0.5 * q ⊗ (0, ωx, ωy, ωz)
每步积分后需归一化四元数以保持单位长度。
3. 重力矩计算
设陀螺质心到支点的距离为 l,质量为 m,则重力矩:
– τ = r × mg
在随体坐标系中,需先将重力向量转换到随体坐标系再计算叉乘。
4. 稳定性判据
对称陀螺绕竖直轴高速旋转时,当自转角速度 ψ̇ 满足:
– ψ̇² > 4 * m * g * l * Ix / Iz²
陀螺的直立旋转是稳定的(Sleeping Top条件)。这一判据可用于设计陀螺的初始参数。
5. PID姿态稳定控制
若陀螺有驱动电机,可通过测量倾角误差,用PID控制器输出控制力矩:
– τ_control = Kp * e + Ki * ∫e dt + Kd * de/dt
其中 e 为当前姿态与目标姿态(直立)的偏差。
完整Java实现
import java.util.*;
/**
* 三维陀螺物理模拟与姿态稳定控制系统
* 核心算法:欧拉方程 + 四元数姿态积分 + PID稳定控制
*/
public class GyroscopeSimulator {
static final double GRAVITY = 9.8;
static final double DT = 0.001; // 1ms积分步长
// 三维向量
static class Vec3 {
double x, y, z;
Vec3(double x, double y, double z) { this.x = x; this.y = y; this.z = z; }
Vec3() { this(0, 0, 0); }
Vec3 add(Vec3 v) { return new Vec3(x + v.x, y + v.y, z + v.z); }
Vec3 sub(Vec3 v) { return new Vec3(x - v.x, y - v.y, z - v.z); }
Vec3 scale(double s) { return new Vec3(x * s, y * s, z * s); }
Vec3 cross(Vec3 v) {
return new Vec3(
y * v.z - z * v.y,
z * v.x - x * v.z,
x * v.y - y * v.x
);
}
double dot(Vec3 v) { return x * v.x + y * v.y + z * v.z; }
double norm() { return Math.sqrt(x*x + y*y + z*z); }
Vec3 normalize() {
double n = norm();
if (n < 1e-10) return new Vec3(0, 0, 1);
return scale(1.0 / n);
}
}
// 四元数
static class Quaternion {
double w, x, y, z;
Quaternion(double w, double x, double y, double z) {
this.w = w; this.x = x; this.y = y; this.z = z;
}
Quaternion() { this(1, 0, 0, 0); }
Quaternion multiply(Quaternion q) {
return new Quaternion(
w*q.w - x*q.x - y*q.y - z*q.z,
w*q.x + x*q.w + y*q.z - z*q.y,
w*q.y - x*q.z + y*q.w + z*q.x,
w*q.z + x*q.y - y*q.x + z*q.w
);
}
Quaternion conjugate() { return new Quaternion(w, -x, -y, -z); }
Quaternion normalize() {
double n = Math.sqrt(w*w + x*x + y*y + z*z);
return new Quaternion(w/n, x/n, y/n, z/n);
}
// 将向量从随体坐标系旋转到惯性系
Vec3 rotate(Vec3 v) {
Quaternion qv = new Quaternion(0, v.x, v.y, v.z);
Quaternion result = this.multiply(qv).multiply(this.conjugate());
return new Vec3(result.x, result.y, result.z);
}
// 将向量从惯性系旋转到随体坐标系
Vec3 rotateInverse(Vec3 v) {
Quaternion qv = new Quaternion(0, v.x, v.y, v.z);
Quaternion result = this.conjugate().multiply(qv).multiply(this);
return new Vec3(result.x, result.y, result.z);
}
}
// 陀螺状态
static class GyroState {
Quaternion q; // 姿态四元数(随体->惯性)
Vec3 omegaBody; // 随体坐标系角速度
double Ix, Iy, Iz; // 主转动惯量
double mass; // 质量
double comLength; // 质心到支点距离
GyroState(double Ix, double Iy, double Iz, double mass, double comLength) {
this.q = new Quaternion();
this.omegaBody = new Vec3();
this.Ix = Ix; this.Iy = Iy; this.Iz = Iz;
this.mass = mass;
this.comLength = comLength;
}
// 角动量(惯性系)
Vec3 angularMomentum() {
Vec3 Lbody = new Vec3(Ix * omegaBody.x, Iy * omegaBody.y, Iz * omegaBody.z);
return q.rotate(Lbody);
}
// 动能
double kineticEnergy() {
return 0.5 * (Ix * omegaBody.x * omegaBody.x +
Iy * omegaBody.y * omegaBody.y +
Iz * omegaBody.z * omegaBody.z);
}
// 势能(以支点为参考)
double potentialEnergy() {
// 质心位置 = q.rotate((0,0,comLength))
Vec3 comDir = q.rotate(new Vec3(0, 0, 1));
return mass * GRAVITY * comDir.z * comLength;
}
}
// PID控制器
static class PIDController {
double Kp, Ki, Kd;
double integral = 0;
double prevError = 0;
PIDController(double Kp, double Ki, double Kd) {
this.Kp = Kp; this.Ki = Ki; this.Kd = Kd;
}
double update(double error, double dt) {
integral += error * dt;
double derivative = (error - prevError) / dt;
prevError = error;
return Kp * error + Ki * integral + Kd * derivative;
}
void reset() {
integral = 0;
prevError = 0;
}
}
private GyroState gyro;
private PIDController pidX, pidY;
private boolean activeControl; // 是否启用主动控制
private List<double[]> history; // 历史记录
public GyroscopeSimulator(GyroState gyro, boolean activeControl) {
this.gyro = gyro;
this.activeControl = activeControl;
this.pidX = new PIDController(2.0, 0.5, 0.8);
this.pidY = new PIDController(2.0, 0.5, 0.8);
this.history = new ArrayList<>();
}
/**
* 计算重力矩(在随体坐标系中)
*/
private Vec3 computeGravityTorque() {
// 重力在惯性系中为 (0, 0, -mg)
Vec3 gravityInertial = new Vec3(0, 0, -gyro.mass * GRAVITY);
// 转换到随体坐标系
Vec3 gravityBody = gyro.q.rotateInverse(gravityInertial);
// 质心在随体坐标系中为 (0, 0, comLength)
Vec3 comBody = new Vec3(0, 0, gyro.comLength);
// 力矩 = r × F
return comBody.cross(gravityBody);
}
/**
* 单步积分:龙格-库塔4阶(RK4)
*/
public void stepRK4() {
// 状态向量: [omega_x, omega_y, omega_z, q_w, q_x, q_y, q_z]
// 为简化,这里使用半隐式方法:先积分角速度,再积分姿态
Vec3 tau = computeGravityTorque();
// 主动控制力矩
if (activeControl) {
// 计算当前倾角误差(从竖直方向z轴的偏离)
Vec3 bodyZ = gyro.q.rotate(new Vec3(0, 0, 1));
// 误差为x和y方向的倾斜分量
double errorX = bodyZ.x;
double errorY = bodyZ.y;
double controlTx = pidX.update(errorX, DT);
double controlTy = pidY.update(errorY, DT);
// 控制力矩施加在随体坐标系的x和y轴
tau = tau.add(new Vec3(controlTx, controlTy, 0));
}
// RK4积分角速度(欧拉方程)
Vec3 omega = gyro.omegaBody;
Vec3 k1 = eulerEquation(omega, tau);
Vec3 k2 = eulerEquation(omega.add(k1.scale(DT * 0.5)), tau);
Vec3 k3 = eulerEquation(omega.add(k2.scale(DT * 0.5)), tau);
Vec3 k4 = eulerEquation(omega.add(k3.scale(DT)), tau);
Vec3 deltaOmega = k1.add(k2.scale(2)).add(k3.scale(2)).add(k4).scale(DT / 6.0);
gyro.omegaBody = omega.add(deltaOmega);
// 四元数更新(一阶近似,RK4对四元数较复杂,这里用一阶+小步长补偿)
Vec3 omegaInertial = gyro.q.rotate(gyro.omegaBody);
Quaternion deltaQ = new Quaternion(
0,
0.5 * omegaInertial.x * DT,
0.5 * omegaInertial.y * DT,
0.5 * omegaInertial.z * DT
);
gyro.q = gyro.q.addQuaternion(deltaQ).normalize();
}
private Vec3 eulerEquation(Vec3 omega, Vec3 tau) {
double dwx = ((gyro.Iy - gyro.Iz) * omega.y * omega.z + tau.x) / gyro.Ix;
double dwy = ((gyro.Iz - gyro.Ix) * omega.z * omega.x + tau.y) / gyro.Iy;
double dwz = ((gyro.Ix - gyro.Iy) * omega.x * omega.y + tau.z) / gyro.Iz;
return new Vec3(dwx, dwy, dwz);
}
// 四元数加法辅助
private Quaternion addQuaternion(Quaternion a, Quaternion b) {
return new Quaternion(a.w + b.w, a.x + b.x, a.y + b.y, a.z + b.z);
}
/**
* 长时间模拟
*/
public void simulate(double duration) {
int steps = (int)(duration / DT);
for (int i = 0; i < steps; i++) {
stepRK4();
// 每100步记录一次
if (i % 100 == 0) {
Vec3 comDir = gyro.q.rotate(new Vec3(0, 0, 1));
double tiltAngle = Math.toDegrees(Math.acos(Math.min(1, Math.abs(comDir.z))));
history.add(new double[]{
i * DT,
tiltAngle,
gyro.omegaBody.z, // 自转角速度
gyro.kineticEnergy()
});
}
}
}
/**
* 稳定性判据检验
*/
public boolean checkStabilityCriterion() {
double criticalOmega = Math.sqrt(4 * gyro.mass * GRAVITY * gyro.comLength * gyro.Ix) / gyro.Iz;
double spinRate = Math.abs(gyro.omegaBody.z);
System.out.printf("稳定性判据: 当前自转%.2f rad/s, 临界值%.2f rad/s\n", spinRate, criticalOmega);
return spinRate > criticalOmega;
}
public void printResults() {
System.out.println("=== 陀螺模拟结果 ===");
System.out.println("记录点数: " + history.size());
double maxTilt = 0;
for (double[] h : history) {
maxTilt = Math.max(maxTilt, h[1]);
}
System.out.printf("最大倾斜角: %.2f°\n", maxTilt);
if (!history.isEmpty()) {
double[] first = history.get(0);
double[] last = history.get(history.size() - 1);
System.out.printf("初始倾斜: %.2f°, 最终倾斜: %.2f°\n", first[1], last[1]);
System.out.printf("初始自转: %.2f rad/s, 最终自转: %.2f rad/s\n", first[2], last[2]);
}
}
public static void main(String[] args) {
// 创建对称陀螺:Ix = Iy, Iz为轴向转动惯量
// 参数参考典型金属陀螺
double mass = 0.1; // 100g
double comLength = 0.03; // 质心距3cm
double Ix = 1.5e-4; // kg·m²
double Iy = 1.5e-4;
double Iz = 3.0e-5; // 轴向转动惯量较小
GyroState gyro = new GyroState(Ix, Iy, Iz, mass, comLength);
// 初始条件:高速自转 + 微小倾斜
gyro.omegaBody = new Vec3(0.5, 0, 30); // x方向微扰,z方向高速自转
// 初始姿态:轻微倾斜5度
double tiltRad = Math.toRadians(5);
gyro.q = new Quaternion(
Math.cos(tiltRad / 2),
Math.sin(tiltRad / 2), 0, 0
).normalize();
System.out.println("=== 无控制模拟 ===");
GyroscopeSimulator sim1 = new GyroscopeSimulator(gyro, false);
sim1.checkStabilityCriterion();
sim1.simulate(2.0); // 模拟2秒
sim1.printResults();
// 重新初始化用于控制模拟
GyroState gyro2 = new GyroState(Ix, Iy, Iz, mass, comLength);
gyro2.omegaBody = new Vec3(0.5, 0, 30);
gyro2.q = new Quaternion(
Math.cos(tiltRad / 2),
Math.sin(tiltRad / 2), 0, 0
).normalize();
System.out.println("\n=== PID主动控制模拟 ===");
GyroscopeSimulator sim2 = new GyroscopeSimulator(gyro2, true);
sim2.simulate(2.0);
sim2.printResults();
// 低速自转测试(应不稳定)
System.out.println("\n=== 低速自转测试(应倾倒)===");
GyroState gyro3 = new GyroState(Ix, Iy, Iz, mass, comLength);
gyro3.omegaBody = new Vec3(0.5, 0, 5); // 低速自转
gyro3.q = new Quaternion(
Math.cos(tiltRad / 2),
Math.sin(tiltRad / 2), 0, 0
).normalize();
GyroscopeSimulator sim3 = new GyroscopeSimulator(gyro3, false);
sim3.checkStabilityCriterion();
sim3.simulate(2.0);
sim3.printResults();
}
}
算法复杂度分析
| 步骤 | 时间复杂度 | 空间复杂度 | 说明 |
|---|---|---|---|
| 单步RK4积分 | O(1) | O(1) | 固定向量运算 |
| 模拟N步 | O(N) | O(N/S) | S为记录间隔 |
| PID控制计算 | O(1) | O(1) | 固定公式计算 |
| 稳定性判据 | O(1) | O(1) | 直接公式判断 |
扩展方向
- 可视化渲染:用JavaFX或LWJGL实时渲染陀螺三维姿态,绘制进动轨迹
- 双陀螺耦合:两个陀螺通过框架连接,形成陀螺仪,模拟导航应用
- 摩擦力模型:加入支点处的滚动摩擦和空气阻力,更精确地模拟能量耗散
- 数据驱动辨识:用传感器数据(陀螺仪、加速度计)通过扩展卡尔曼滤波实时估计姿态
总结
本文从欧拉方程出发,建立了对称陀螺的旋转动力学模型,使用四元数避免了欧拉角奇异问题,并采用RK4数值积分保证长时间模拟的精度。推导并验证了陀螺直立旋转的稳定性判据,同时展示了PID控制器在主动姿态稳定中的有效性。这一框架不仅适用于游戏模拟,也是理解惯性导航和航天器姿态控制的基础。