avian3d/dynamics/integrator/
semi_implicit_euler.rs1use super::*;
29
30#[allow(clippy::too_many_arguments)]
35pub fn integrate_velocity(
36 lin_vel: &mut Vector,
37 ang_vel: &mut AngularValue,
38 force: Vector,
39 torque: TorqueValue,
40 mass: ComputedMass,
41 angular_inertia: &ComputedAngularInertia,
42 #[cfg(feature = "3d")] global_angular_inertia: &GlobalAngularInertia,
43 #[cfg(feature = "3d")] rotation: Rotation,
44 locked_axes: LockedAxes,
45 gravity: Vector,
46 delta_seconds: Scalar,
47) {
48 let lin_acc = linear_acceleration(force, mass, locked_axes, gravity);
50
51 let next_lin_vel = *lin_vel + lin_acc * delta_seconds;
54 if next_lin_vel != *lin_vel {
55 *lin_vel = next_lin_vel;
56 }
57
58 #[cfg(feature = "2d")]
60 let ang_acc = angular_acceleration(torque, angular_inertia, locked_axes);
61 #[cfg(feature = "3d")]
62 let ang_acc = angular_acceleration(torque, global_angular_inertia, locked_axes);
63
64 #[allow(unused_mut)]
67 let mut delta_ang_vel = ang_acc * delta_seconds;
68
69 #[cfg(feature = "3d")]
70 {
71 let delta_ang_vel_gyro = solve_gyroscopic_torque(
83 *ang_vel,
84 rotation.0,
85 angular_inertia.tensor(),
86 delta_seconds,
87 );
88 delta_ang_vel += locked_axes.apply_to_angular_velocity(delta_ang_vel_gyro);
89 }
90
91 if delta_ang_vel != AngularVelocity::ZERO.0 && delta_ang_vel.is_finite() {
92 *ang_vel += delta_ang_vel;
93 }
94}
95
96pub fn integrate_position(
101 pos: &mut Vector,
102 rot: &mut Rotation,
103 lin_vel: Vector,
104 ang_vel: AngularValue,
105 locked_axes: LockedAxes,
106 delta_seconds: Scalar,
107) {
108 let lin_vel = locked_axes.apply_to_vec(lin_vel);
109
110 let next_pos = *pos + lin_vel * delta_seconds;
112
113 if next_pos != *pos && next_pos.is_finite() {
114 *pos = next_pos;
115 }
116
117 let ang_vel = locked_axes.apply_to_angular_velocity(ang_vel);
119
120 #[cfg(feature = "2d")]
122 {
123 let delta_rot = Rotation::radians(ang_vel * delta_seconds);
124 if delta_rot != Rotation::IDENTITY && delta_rot.is_finite() {
125 *rot *= delta_rot;
126 *rot = rot.fast_renormalize();
127 }
128 }
129 #[cfg(feature = "3d")]
130 {
131 let scaled_axis = ang_vel * delta_seconds;
134 if scaled_axis != AngularVelocity::ZERO.0 && scaled_axis.is_finite() {
135 let delta_rot = Quaternion::from_scaled_axis(scaled_axis);
136 rot.0 = delta_rot * rot.0;
137 *rot = rot.fast_renormalize();
138 }
139 }
140}
141
142pub fn linear_acceleration(
144 force: Vector,
145 mass: ComputedMass,
146 locked_axes: LockedAxes,
147 gravity: Vector,
148) -> Vector {
149 let effective_inverse_mass = locked_axes.apply_to_vec(Vector::splat(mass.inverse()));
151
152 if effective_inverse_mass != Vector::ZERO && effective_inverse_mass.is_finite() {
153 force * effective_inverse_mass + locked_axes.apply_to_vec(gravity)
163 } else {
164 Vector::ZERO
165 }
166}
167
168#[cfg_attr(
170 feature = "3d",
171 doc = "
172Note that this does not account for gyroscopic motion. To compute the gyroscopic angular velocity
173correction, use `solve_gyroscopic_torque`."
174)]
175pub fn angular_acceleration(
176 torque: TorqueValue,
177 global_angular_inertia: &ComputedAngularInertia,
178 locked_axes: LockedAxes,
179) -> AngularValue {
180 let effective_angular_inertia = locked_axes.apply_to_angular_inertia(*global_angular_inertia);
182
183 if effective_angular_inertia != ComputedAngularInertia::INFINITY
184 && effective_angular_inertia.is_finite()
185 {
186 effective_angular_inertia.inverse() * torque
194 } else {
195 AngularValue::ZERO
196 }
197}
198
199#[cfg(feature = "3d")]
203pub fn solve_gyroscopic_torque(
204 ang_vel: Vector,
205 rotation: Quaternion,
206 local_inertia: Matrix,
207 delta_seconds: Scalar,
208) -> Vector {
209 let local_ang_vel = rotation.inverse() * ang_vel;
214
215 let angular_momentum = local_inertia * local_ang_vel;
217
218 let jacobian = local_inertia
220 + delta_seconds
221 * (skew_symmetric_mat3(local_ang_vel) * local_inertia
222 - skew_symmetric_mat3(angular_momentum));
223
224 let f = delta_seconds * local_ang_vel.cross(angular_momentum);
226
227 let delta_ang_vel = -jacobian.inverse() * f;
229
230 rotation * delta_ang_vel
232}
233
234#[cfg(test)]
235mod tests {
236 use approx::assert_relative_eq;
237
238 use super::*;
239
240 #[test]
241 fn semi_implicit_euler() {
242 let mut position = Vector::ZERO;
243 let mut rotation = Rotation::default();
244
245 let mut linear_velocity = Vector::ZERO;
246 #[cfg(feature = "2d")]
247 let mut angular_velocity = 2.0;
248 #[cfg(feature = "3d")]
249 let mut angular_velocity = Vector::Z * 2.0;
250
251 let mass = ComputedMass::new(1.0);
252 #[cfg(feature = "2d")]
253 let angular_inertia = ComputedAngularInertia::new(1.0);
254 #[cfg(feature = "3d")]
255 let angular_inertia = ComputedAngularInertia::new(Vector::ONE);
256
257 let gravity = Vector::NEG_Y * 9.81;
258
259 for _ in 0..100 {
261 integrate_velocity(
262 &mut linear_velocity,
263 &mut angular_velocity,
264 default(),
265 default(),
266 mass,
267 &angular_inertia,
268 #[cfg(feature = "3d")]
269 &GlobalAngularInertia::new(angular_inertia, rotation),
270 #[cfg(feature = "3d")]
271 rotation,
272 default(),
273 gravity,
274 1.0 / 10.0,
275 );
276 integrate_position(
277 &mut position,
278 &mut rotation,
279 linear_velocity,
280 angular_velocity,
281 default(),
282 1.0 / 10.0,
283 );
284 }
285
286 assert_relative_eq!(position, Vector::NEG_Y * 490.5, epsilon = 10.0);
288
289 #[cfg(feature = "2d")]
290 assert_relative_eq!(
291 rotation.as_radians(),
292 Rotation::radians(20.0).as_radians(),
293 epsilon = 0.00001
294 );
295 #[cfg(feature = "3d")]
296 assert_relative_eq!(
297 rotation.0,
298 Quaternion::from_rotation_z(20.0),
299 epsilon = 0.01
300 );
301
302 assert_relative_eq!(linear_velocity, Vector::NEG_Y * 98.1, epsilon = 0.0001);
303 #[cfg(feature = "2d")]
304 assert_relative_eq!(angular_velocity, 2.0, epsilon = 0.00001);
305 #[cfg(feature = "3d")]
306 assert_relative_eq!(angular_velocity, Vector::Z * 2.0, epsilon = 0.00001);
307 }
308}