use crate::error::DynamicsError;
use crate::linear_algebra::{Matrix3D, Vector, Vector3D};
use crate::ode::ExponentialMap;
use crate::scalar::Numeric;
use crate::spatial::{FreeJointState, Quaternion, SE3, SO3, SpatialInertia, Twist, Wrench};
pub const STATE_DIMENSION: usize = 13;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct RigidBody<T: Numeric = f64> {
inertia: SpatialInertia<T>,
inverse_rotational_inertia: Matrix3D<T>,
gravity: Vector3D<T>,
}
impl<T: Numeric> RigidBody<T> {
pub fn new(inertia: SpatialInertia<T>, gravity: Vector3D<T>) -> Result<Self, DynamicsError> {
if !inertia.is_finite() || !gravity.is_finite() {
return Err(DynamicsError::NonFinite);
}
let rotational_inertia = inertia.rotational_inertia();
if rotational_inertia.cholesky().is_err() {
return Err(DynamicsError::NonPositiveInertia);
}
let inverse_rotational_inertia = rotational_inertia.inverse()?;
Ok(RigidBody {
inertia,
inverse_rotational_inertia,
gravity,
})
}
#[inline]
#[must_use]
pub fn inertia(self) -> SpatialInertia<T> {
self.inertia
}
#[inline]
pub fn gravity(self) -> Vector3D<T> {
self.gravity
}
#[must_use]
pub fn accelerations(
self,
orientation: SO3<T>,
angular_rate: Vector3D<T>,
applied_wrench: Wrench<T>,
) -> RigidBodyAcceleration<T> {
let mass = self.inertia.mass();
let balance_point = self.inertia.center_of_mass();
let rotational_inertia = self.inertia.rotational_inertia();
let force = applied_wrench.force();
let turn_about_balance_point = applied_wrench.torque() - balance_point.cross(force);
let spin_resistance = angular_rate.cross(rotational_inertia * angular_rate);
let angular =
self.inverse_rotational_inertia * (turn_about_balance_point - spin_resistance);
let balance_point_acceleration = orientation.act(force).scale(T::ONE / mass) + self.gravity;
let swing =
angular.cross(balance_point) + angular_rate.cross(angular_rate.cross(balance_point));
let linear = balance_point_acceleration - orientation.act(swing);
RigidBodyAcceleration { linear, angular }
}
#[must_use]
pub fn stepped(
self,
state: FreeJointState<T>,
applied_wrench: Wrench<T>,
dt: T,
) -> FreeJointState<T> {
let pose = state.pose();
let orientation = pose.rotation();
let position = pose.translation();
let velocity = state.velocity();
let linear_velocity = velocity.linear();
let angular_rate = velocity.angular();
let half = dt * T::HALF;
let at_start = self.accelerations(orientation, angular_rate, applied_wrench);
let half_way_orientation = ExponentialMap::attitude_step(orientation, angular_rate, half);
let half_way_linear_velocity = linear_velocity + at_start.linear() * half;
let half_way_angular_rate = angular_rate + at_start.angular() * half;
let half_way =
self.accelerations(half_way_orientation, half_way_angular_rate, applied_wrench);
let next_orientation =
ExponentialMap::attitude_step(orientation, half_way_angular_rate, dt);
let next_position = position + half_way_linear_velocity * dt;
let next_linear_velocity = linear_velocity + half_way.linear() * dt;
let next_angular_rate = angular_rate + half_way.angular() * dt;
FreeJointState::new(
SE3::from_parts(next_orientation, next_position),
Twist::new(next_linear_velocity, next_angular_rate),
)
}
pub fn state_derivative(
self,
state: &Vector<STATE_DIMENSION, T>,
applied_wrench: Wrench<T>,
) -> Vector<STATE_DIMENSION, T> {
let stored = Quaternion::new(state[3], state[4], state[5], state[6]);
let Some(unit) = stored.try_normalized() else {
return Vector::zeros();
};
let angular_rate = Vector::new([state[10], state[11], state[12]]);
let acceleration =
self.accelerations(SO3::from_quaternion(unit), angular_rate, applied_wrench);
let (w, x, y, z) = (stored.w(), stored.x(), stored.y(), stored.z());
let [rate_x, rate_y, rate_z] = *angular_rate.as_array();
let facing = [
T::HALF * (-x * rate_x - y * rate_y - z * rate_z),
T::HALF * (w * rate_x + y * rate_z - z * rate_y),
T::HALF * (w * rate_y + z * rate_x - x * rate_z),
T::HALF * (w * rate_z + x * rate_y - y * rate_x),
];
let linear = acceleration.linear();
let angular = acceleration.angular();
Vector::new([
state[7], state[8], state[9], facing[0], facing[1], facing[2], facing[3], linear[0],
linear[1], linear[2], angular[0], angular[1], angular[2],
])
}
}
pub fn state_vector_from_free_joint<T: Numeric>(
state: FreeJointState<T>,
) -> Vector<STATE_DIMENSION, T> {
let place = state.generalized_position();
let motion = state.generalized_velocity();
Vector::from_fn(|index| {
if index < 7 {
place[index]
} else {
motion[index - 7]
}
})
}
#[must_use]
pub fn free_joint_from_state_vector<T: Numeric>(
state: &Vector<STATE_DIMENSION, T>,
) -> Option<FreeJointState<T>> {
let mut place = [T::ZERO; 7];
let mut motion = [T::ZERO; 6];
for (index, value) in place.iter_mut().enumerate() {
*value = state[index];
}
for (index, value) in motion.iter_mut().enumerate() {
*value = state[index + 7];
}
FreeJointState::from_generalized_vectors(place, motion)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct RigidBodyAcceleration<T: Numeric = f64> {
linear: Vector3D<T>,
angular: Vector3D<T>,
}
impl<T: Numeric> RigidBodyAcceleration<T> {
#[inline]
#[must_use]
pub fn new(linear: Vector3D<T>, angular: Vector3D<T>) -> Self {
RigidBodyAcceleration { linear, angular }
}
#[inline]
pub fn linear(self) -> Vector3D<T> {
self.linear
}
#[inline]
pub fn angular(self) -> Vector3D<T> {
self.angular
}
}