Skip to main content

lotus_shared/
animation.rs

1#[cfg(feature = "bevy")]
2use bevy::prelude::Component;
3use glam::Vec3;
4#[cfg(feature = "ffi")]
5use lotus_script_sys::FfiObject;
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, thiserror::Error)]
9pub enum AnimationError {
10    #[error("animation not found")]
11    AnimationNotFound = 65536,
12    #[error("unknown error")]
13    Unknown = 0,
14}
15
16impl From<u32> for AnimationError {
17    fn from(value: u32) -> Self {
18        match value {
19            65536 => AnimationError::AnimationNotFound,
20            _ => AnimationError::Unknown,
21        }
22    }
23}
24
25#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
26#[cfg_attr(feature = "bevy", derive(Component))]
27pub struct AccelerationVelocity {
28    pub linear_velocity: Vec3,
29    pub linear_acceleration: Vec3,
30    pub angular_velocity: Vec3,
31    pub angular_acceleration: Vec3,
32}
33
34impl AccelerationVelocity {
35    /// Builds axle-local kinematics in LOTUS coordinates (X = lateral, Y = longitudinal, Z = vertical)
36    /// and rotates them into the global simulation frame.
37    pub fn from_rail_axle_local(
38        longitudinal_velocity: f32,
39        longitudinal_acceleration: f32,
40        inv_radius: f32,
41        elevation_derivation: f32,
42        elevation_second_derivation: f32,
43        axle_rotation: glam::Quat,
44    ) -> Self {
45        let v = longitudinal_velocity;
46        let a_long = longitudinal_acceleration;
47        let lateral_acceleration = v * v * inv_radius;
48        let vertical_acceleration =
49            elevation_second_derivation * v * v + elevation_derivation * a_long;
50
51        let local = Self {
52            linear_velocity: Vec3::new(0.0, v, elevation_derivation * v),
53            linear_acceleration: Vec3::new(lateral_acceleration, a_long, vertical_acceleration),
54            angular_velocity: Vec3::new(0.0, 0.0, v * inv_radius),
55            angular_acceleration: Vec3::new(0.0, 0.0, a_long * inv_radius),
56        };
57
58        local.transform_axes_to_global(axle_rotation)
59    }
60
61    /// Rotates linear/angular velocity and acceleration vectors into the global frame.
62    pub fn transform_axes_to_global(self, rotation: glam::Quat) -> Self {
63        Self {
64            linear_velocity: rotation * self.linear_velocity,
65            linear_acceleration: rotation * self.linear_acceleration,
66            angular_velocity: rotation * self.angular_velocity,
67            angular_acceleration: rotation * self.angular_acceleration,
68        }
69    }
70
71    /// Rotates linear/angular velocity and acceleration vectors into a local frame.
72    pub fn transform_axes_to_local(self, rotation: glam::Quat) -> Self {
73        let inv = rotation.inverse();
74        Self {
75            linear_velocity: inv * self.linear_velocity,
76            linear_acceleration: inv * self.linear_acceleration,
77            angular_velocity: inv * self.angular_velocity,
78            angular_acceleration: inv * self.angular_acceleration,
79        }
80    }
81
82    pub fn average(self, other: Self) -> Self {
83        Self {
84            linear_velocity: 0.5 * (self.linear_velocity + other.linear_velocity),
85            linear_acceleration: 0.5 * (self.linear_acceleration + other.linear_acceleration),
86            angular_velocity: 0.5 * (self.angular_velocity + other.angular_velocity),
87            angular_acceleration: 0.5 * (self.angular_acceleration + other.angular_acceleration),
88        }
89    }
90
91    pub fn relative_to_parent(
92        self,
93        parent: &AccelerationVelocity,
94        position_relative_to_parent: Vec3,
95    ) -> Self {
96        Self {
97            angular_velocity: parent.angular_velocity + self.angular_velocity,
98            angular_acceleration: parent.angular_acceleration
99                + self.angular_acceleration
100                + parent.angular_velocity.cross(self.angular_velocity),
101            linear_velocity: parent.linear_velocity
102                + parent.angular_velocity.cross(position_relative_to_parent)
103                + self.linear_velocity,
104
105            linear_acceleration: parent.linear_acceleration
106                + parent
107                    .angular_acceleration
108                    .cross(position_relative_to_parent)
109                + parent
110                    .angular_velocity
111                    .cross(parent.angular_velocity.cross(position_relative_to_parent))
112                + (2.0 * parent.angular_velocity).cross(self.linear_velocity)
113                + self.linear_acceleration,
114        }
115    }
116
117    /// Linear and angular acceleration at a fixed point `local_offset` (animation-unit local frame),
118    /// expressed in the animation-unit local frame.
119    ///
120    /// Includes Euler (`α × r`) and centripetal (`ω × (ω × r)`) contributions for rigid-body motion.
121    pub fn acceleration_at_local_point(self, local_offset: Vec3) -> LocalPointAcceleration {
122        LocalPointAcceleration {
123            linear_acceleration: self.linear_acceleration
124                + self.angular_acceleration.cross(local_offset)
125                + self.angular_velocity.cross(self.angular_velocity.cross(local_offset)),
126            angular_acceleration: self.angular_acceleration,
127        }
128    }
129}
130
131#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
132pub struct LocalPointAcceleration {
133    pub linear_acceleration: Vec3,
134    pub angular_acceleration: Vec3,
135}
136
137#[cfg(feature = "ffi")]
138#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
139pub struct Animation {
140    index: usize,
141}
142
143#[cfg(feature = "ffi")]
144impl Animation {
145    pub fn index(&self) -> usize {
146        self.index
147    }
148
149    pub fn get(name: &str) -> Result<Self, AnimationError> {
150        let name = FfiObject::new(&name);
151
152        match unsafe { lotus_script_sys::animation::get_animation_index(name.packed()) } {
153            65536 => Err(AnimationError::AnimationNotFound),
154            index => Ok(Self {
155                index: index as usize,
156            }),
157        }
158    }
159
160    pub fn get_animation_global_acceleration_velocity(self) -> AccelerationVelocity {
161        let state = unsafe {
162            lotus_script_sys::animation::get_animation_global_acceleration_velocity(
163                self.index as i32,
164            )
165        };
166
167        FfiObject::from_packed(state).deserialize()
168    }
169}