use crate::dynamics::RigidBody;
use crate::environment::Environment;
use crate::math::{Quaternion, Vec3};
pub trait Integrator {
fn step(&self, body: &mut RigidBody, dt: f64, env: &Environment);
}
pub struct EulerIntegrator;
impl Integrator for EulerIntegrator {
fn step(&self, body: &mut RigidBody, dt: f64, env: &Environment) {
let gravity = env.gravity_force(body.mass);
body.apply_force(gravity, None);
if body.drag_coefficient > 0.0 {
let drag = env.drag_force(
body.velocity,
body.drag_coefficient,
body.cross_section_area,
);
body.apply_force(drag, None);
}
let accel = body.linear_acceleration();
body.velocity = body.velocity + accel * dt;
body.position = body.position + body.velocity * dt;
let alpha = body.angular_acceleration();
body.angular_velocity = body.angular_velocity + alpha * dt;
let w = body.angular_velocity;
let omega_quat = Quaternion::new(0.0, w.x, w.y, w.z);
let dq = omega_quat.multiply(body.orientation);
body.orientation = Quaternion::new(
body.orientation.w + 0.5 * dq.w * dt,
body.orientation.x + 0.5 * dq.x * dt,
body.orientation.y + 0.5 * dq.y * dt,
body.orientation.z + 0.5 * dq.z * dt,
)
.normalize();
body.clear_forces();
}
}
pub struct RK4Integrator;
#[derive(Clone)]
struct State {
position: Vec3,
velocity: Vec3,
orientation: Quaternion,
angular_velocity: Vec3,
}
struct Derivative {
dx: Vec3, dv: Vec3, dq: Quaternion,
dw: Vec3, }
impl RK4Integrator {
fn compute_derivative(body: &RigidBody, state: &State, env: &Environment) -> Derivative {
let gravity = env.gravity_force(body.mass);
let mut force = body.force + gravity;
if body.drag_coefficient > 0.0 {
let drag = env.drag_force(
state.velocity,
body.drag_coefficient,
body.cross_section_area,
);
force = force + drag;
}
let accel = force * (1.0 / body.mass);
let alpha = Vec3::new(
body.torque.x / body.inertia.x,
body.torque.y / body.inertia.y,
body.torque.z / body.inertia.z,
);
let w = state.angular_velocity;
let omega_quat = Quaternion::new(0.0, w.x, w.y, w.z);
let dq_raw = omega_quat.multiply(state.orientation);
let dq = Quaternion::new(
0.5 * dq_raw.w,
0.5 * dq_raw.x,
0.5 * dq_raw.y,
0.5 * dq_raw.z,
);
Derivative {
dx: state.velocity,
dv: accel,
dq,
dw: alpha,
}
}
}
impl Integrator for RK4Integrator {
fn step(&self, body: &mut RigidBody, dt: f64, env: &Environment) {
let initial = State {
position: body.position,
velocity: body.velocity,
orientation: body.orientation,
angular_velocity: body.angular_velocity,
};
let k1 = Self::compute_derivative(body, &initial, env);
let s2 = State {
position: initial.position + k1.dx * (dt * 0.5),
velocity: initial.velocity + k1.dv * (dt * 0.5),
orientation: Quaternion::new(
initial.orientation.w + k1.dq.w * (dt * 0.5),
initial.orientation.x + k1.dq.x * (dt * 0.5),
initial.orientation.y + k1.dq.y * (dt * 0.5),
initial.orientation.z + k1.dq.z * (dt * 0.5),
)
.normalize(),
angular_velocity: initial.angular_velocity + k1.dw * (dt * 0.5),
};
let k2 = Self::compute_derivative(body, &s2, env);
let s3 = State {
position: initial.position + k2.dx * (dt * 0.5),
velocity: initial.velocity + k2.dv * (dt * 0.5),
orientation: Quaternion::new(
initial.orientation.w + k2.dq.w * (dt * 0.5),
initial.orientation.x + k2.dq.x * (dt * 0.5),
initial.orientation.y + k2.dq.y * (dt * 0.5),
initial.orientation.z + k2.dq.z * (dt * 0.5),
)
.normalize(),
angular_velocity: initial.angular_velocity + k2.dw * (dt * 0.5),
};
let k3 = Self::compute_derivative(body, &s3, env);
let s4 = State {
position: initial.position + k3.dx * dt,
velocity: initial.velocity + k3.dv * dt,
orientation: Quaternion::new(
initial.orientation.w + k3.dq.w * dt,
initial.orientation.x + k3.dq.x * dt,
initial.orientation.y + k3.dq.y * dt,
initial.orientation.z + k3.dq.z * dt,
)
.normalize(),
angular_velocity: initial.angular_velocity + k3.dw * dt,
};
let k4 = Self::compute_derivative(body, &s4, env);
let sixth = 1.0 / 6.0;
body.position =
initial.position + (k1.dx + k2.dx * 2.0 + k3.dx * 2.0 + k4.dx) * (dt * sixth);
body.velocity =
initial.velocity + (k1.dv + k2.dv * 2.0 + k3.dv * 2.0 + k4.dv) * (dt * sixth);
body.orientation = Quaternion::new(
initial.orientation.w
+ (k1.dq.w + k2.dq.w * 2.0 + k3.dq.w * 2.0 + k4.dq.w) * (dt * sixth),
initial.orientation.x
+ (k1.dq.x + k2.dq.x * 2.0 + k3.dq.x * 2.0 + k4.dq.x) * (dt * sixth),
initial.orientation.y
+ (k1.dq.y + k2.dq.y * 2.0 + k3.dq.y * 2.0 + k4.dq.y) * (dt * sixth),
initial.orientation.z
+ (k1.dq.z + k2.dq.z * 2.0 + k3.dq.z * 2.0 + k4.dq.z) * (dt * sixth),
)
.normalize();
body.angular_velocity =
initial.angular_velocity + (k1.dw + k2.dw * 2.0 + k3.dw * 2.0 + k4.dw) * (dt * sixth);
body.clear_forces();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rk4_freefall_accuracy() {
let env = Environment::earth();
let integrator = RK4Integrator;
let mut body = RigidBody::new(1.0);
let dt = 0.001;
let steps = 2000;
for _ in 0..steps {
integrator.step(&mut body, dt, &env);
}
let t = 2.0;
let expected_y = -0.5 * 9.81 * t * t;
let error = ((body.position.y - expected_y) / expected_y).abs();
assert!(
error < 0.001,
"RK4 free-fall error: {:.6}%, pos.y={}, expected={}",
error * 100.0,
body.position.y,
expected_y
);
}
}