1#[derive(Debug, Clone, PartialEq)]
8pub struct LinearState {
9 pub position: f64,
11 pub velocity: f64,
13 pub acceleration: f64,
15}
16
17#[derive(Debug, Clone, PartialEq)]
19pub struct RotationalState {
20 pub angle: f64,
22 pub omega: f64,
24 pub alpha: f64,
26}
27
28pub struct Kinematics;
30
31impl Default for Kinematics {
32 fn default() -> Self {
33 Self::new()
34 }
35}
36
37impl Kinematics {
38 pub fn new() -> Self {
40 Kinematics
41 }
42
43 pub fn velocity_at(&self, state: &LinearState, t: f64) -> f64 {
49 state.velocity + state.acceleration * t
50 }
51
52 pub fn position_at(&self, state: &LinearState, t: f64) -> f64 {
54 state.position + state.velocity * t + 0.5 * state.acceleration * t * t
55 }
56
57 pub fn velocity_from_displacement(&self, state: &LinearState, dx: f64) -> Option<f64> {
63 let discriminant = state.velocity * state.velocity + 2.0 * state.acceleration * dx;
64 if discriminant < 0.0 {
65 return None;
66 }
67 Some(discriminant.sqrt())
68 }
69
70 pub fn time_to_velocity(&self, state: &LinearState, v: f64) -> Option<f64> {
75 let delta_v = v - state.velocity;
76 if state.acceleration.abs() < f64::EPSILON {
77 if delta_v.abs() < f64::EPSILON {
78 return Some(0.0);
79 }
80 return None;
81 }
82 Some(delta_v / state.acceleration)
83 }
84
85 pub fn propagate_linear(&self, state: &LinearState, t: f64) -> LinearState {
87 LinearState {
88 position: self.position_at(state, t),
89 velocity: self.velocity_at(state, t),
90 acceleration: state.acceleration,
91 }
92 }
93
94 pub fn projectile_range(&self, v0: f64, angle_rad: f64, g: f64) -> f64 {
100 v0 * v0 * (2.0 * angle_rad).sin() / g
101 }
102
103 pub fn projectile_max_height(&self, v0: f64, angle_rad: f64, g: f64) -> f64 {
105 let vy = v0 * angle_rad.sin();
106 vy * vy / (2.0 * g)
107 }
108
109 pub fn projectile_time_of_flight(&self, v0: f64, angle_rad: f64, g: f64) -> f64 {
111 2.0 * v0 * angle_rad.sin() / g
112 }
113
114 pub fn angular_velocity_at(&self, state: &RotationalState, t: f64) -> f64 {
120 state.omega + state.alpha * t
121 }
122
123 pub fn angle_at(&self, state: &RotationalState, t: f64) -> f64 {
125 state.angle + state.omega * t + 0.5 * state.alpha * t * t
126 }
127
128 pub fn propagate_rotational(&self, state: &RotationalState, t: f64) -> RotationalState {
130 RotationalState {
131 angle: self.angle_at(state, t),
132 omega: self.angular_velocity_at(state, t),
133 alpha: state.alpha,
134 }
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141 use std::f64::consts::{FRAC_PI_4, FRAC_PI_6, PI};
142
143 const EPS: f64 = 1e-10;
144 const G: f64 = 9.81;
145
146 fn kin() -> Kinematics {
147 Kinematics::new()
148 }
149
150 fn linear(pos: f64, vel: f64, acc: f64) -> LinearState {
151 LinearState {
152 position: pos,
153 velocity: vel,
154 acceleration: acc,
155 }
156 }
157
158 fn rotational(angle: f64, omega: f64, alpha: f64) -> RotationalState {
159 RotationalState {
160 angle,
161 omega,
162 alpha,
163 }
164 }
165
166 #[test]
171 fn test_velocity_at_zero_time() {
172 let k = kin();
173 let s = linear(0.0, 5.0, 2.0);
174 assert!((k.velocity_at(&s, 0.0) - 5.0).abs() < EPS);
175 }
176
177 #[test]
178 fn test_velocity_at_positive_time() {
179 let k = kin();
180 let s = linear(0.0, 0.0, 10.0);
181 assert!((k.velocity_at(&s, 3.0) - 30.0).abs() < EPS);
183 }
184
185 #[test]
186 fn test_velocity_at_deceleration() {
187 let k = kin();
188 let s = linear(0.0, 20.0, -5.0);
189 assert!((k.velocity_at(&s, 2.0) - 10.0).abs() < EPS);
191 }
192
193 #[test]
194 fn test_velocity_at_zero_acceleration() {
195 let k = kin();
196 let s = linear(100.0, 15.0, 0.0);
197 assert!((k.velocity_at(&s, 7.0) - 15.0).abs() < EPS);
198 }
199
200 #[test]
205 fn test_position_at_zero_time() {
206 let k = kin();
207 let s = linear(50.0, 3.0, 2.0);
208 assert!((k.position_at(&s, 0.0) - 50.0).abs() < EPS);
209 }
210
211 #[test]
212 fn test_position_at_constant_velocity() {
213 let k = kin();
214 let s = linear(0.0, 10.0, 0.0);
215 assert!((k.position_at(&s, 5.0) - 50.0).abs() < EPS);
217 }
218
219 #[test]
220 fn test_position_at_with_acceleration() {
221 let k = kin();
222 let s = linear(0.0, 0.0, 2.0);
223 assert!((k.position_at(&s, 4.0) - 16.0).abs() < EPS);
225 }
226
227 #[test]
228 fn test_position_at_full_equation() {
229 let k = kin();
230 let s = linear(10.0, 5.0, 2.0);
231 assert!((k.position_at(&s, 3.0) - 34.0).abs() < EPS);
233 }
234
235 #[test]
240 fn test_propagate_linear_preserves_acceleration() {
241 let k = kin();
242 let s = linear(0.0, 5.0, 3.0);
243 let next = k.propagate_linear(&s, 2.0);
244 assert!((next.acceleration - 3.0).abs() < EPS);
245 }
246
247 #[test]
248 fn test_propagate_linear_position() {
249 let k = kin();
250 let s = linear(0.0, 0.0, 2.0);
251 let next = k.propagate_linear(&s, 3.0);
252 assert!((next.position - 9.0).abs() < EPS);
254 }
255
256 #[test]
257 fn test_propagate_linear_velocity() {
258 let k = kin();
259 let s = linear(0.0, 5.0, 4.0);
260 let next = k.propagate_linear(&s, 2.0);
261 assert!((next.velocity - 13.0).abs() < EPS);
262 }
263
264 #[test]
265 fn test_propagate_linear_zero_time_identity() {
266 let k = kin();
267 let s = linear(7.0, 3.0, -1.5);
268 let next = k.propagate_linear(&s, 0.0);
269 assert!((next.position - s.position).abs() < EPS);
270 assert!((next.velocity - s.velocity).abs() < EPS);
271 }
272
273 #[test]
278 fn test_velocity_from_displacement_basic() {
279 let k = kin();
280 let s = linear(0.0, 0.0, 10.0);
281 let v = k.velocity_from_displacement(&s, 5.0).expect("ok");
283 assert!((v - 10.0).abs() < EPS);
284 }
285
286 #[test]
287 fn test_velocity_from_displacement_negative_discriminant() {
288 let k = kin();
289 let s = linear(0.0, 1.0, -10.0);
291 assert!(k.velocity_from_displacement(&s, 100.0).is_none());
292 }
293
294 #[test]
295 fn test_velocity_from_displacement_zero() {
296 let k = kin();
297 let s = linear(0.0, 3.0, 0.0);
298 let v = k.velocity_from_displacement(&s, 0.0).expect("ok");
300 assert!((v - 3.0).abs() < EPS);
301 }
302
303 #[test]
304 fn test_velocity_from_displacement_returns_positive() {
305 let k = kin();
306 let s = linear(0.0, 4.0, 0.0);
307 let v = k.velocity_from_displacement(&s, 0.0).expect("ok");
308 assert!(v >= 0.0);
309 }
310
311 #[test]
316 fn test_time_to_velocity_basic() {
317 let k = kin();
318 let s = linear(0.0, 0.0, 5.0);
319 let t = k.time_to_velocity(&s, 25.0).expect("ok");
321 assert!((t - 5.0).abs() < EPS);
322 }
323
324 #[test]
325 fn test_time_to_velocity_already_there() {
326 let k = kin();
327 let s = linear(0.0, 10.0, 3.0);
328 let t = k.time_to_velocity(&s, 10.0).expect("ok");
329 assert!((t).abs() < EPS);
330 }
331
332 #[test]
333 fn test_time_to_velocity_zero_accel_same_vel() {
334 let k = kin();
335 let s = linear(0.0, 5.0, 0.0);
336 let t = k.time_to_velocity(&s, 5.0);
337 assert_eq!(t, Some(0.0));
338 }
339
340 #[test]
341 fn test_time_to_velocity_zero_accel_different_vel() {
342 let k = kin();
343 let s = linear(0.0, 5.0, 0.0);
344 assert!(k.time_to_velocity(&s, 10.0).is_none());
346 }
347
348 #[test]
349 fn test_time_to_velocity_deceleration() {
350 let k = kin();
351 let s = linear(0.0, 20.0, -4.0);
352 let t = k.time_to_velocity(&s, 0.0).expect("ok");
354 assert!((t - 5.0).abs() < EPS);
355 }
356
357 #[test]
362 fn test_projectile_range_45_degrees() {
363 let k = kin();
364 let v0 = 20.0;
365 let range = k.projectile_range(v0, FRAC_PI_4, G);
366 let expected = v0 * v0 / G;
368 assert!((range - expected).abs() < 1e-9);
369 }
370
371 #[test]
372 fn test_projectile_max_height_45_degrees() {
373 let k = kin();
374 let v0 = 10.0;
375 let h = k.projectile_max_height(v0, FRAC_PI_4, G);
376 let expected = v0 * v0 / (4.0 * G);
378 assert!((h - expected).abs() < 1e-9);
379 }
380
381 #[test]
382 fn test_projectile_time_of_flight_45_degrees() {
383 let k = kin();
384 let v0 = 10.0;
385 let tof = k.projectile_time_of_flight(v0, FRAC_PI_4, G);
386 let expected = 2.0 * v0 * FRAC_PI_4.sin() / G;
387 assert!((tof - expected).abs() < 1e-9);
388 }
389
390 #[test]
391 fn test_projectile_range_90_degrees_zero() {
392 let k = kin();
394 let range = k.projectile_range(10.0, PI / 2.0, G);
395 assert!(range.abs() < 1e-9);
396 }
397
398 #[test]
399 fn test_projectile_range_30_degrees() {
400 let k = kin();
401 let v0 = 30.0;
402 let range = k.projectile_range(v0, FRAC_PI_6, G);
403 let expected = 900.0 * (PI / 3.0).sin() / G;
405 assert!((range - expected).abs() < 1e-6);
406 }
407
408 #[test]
409 fn test_projectile_max_height_vertical() {
410 let k = kin();
411 let v0 = 10.0;
412 let h = k.projectile_max_height(v0, PI / 2.0, G);
414 let expected = v0 * v0 / (2.0 * G);
415 assert!((h - expected).abs() < 1e-9);
416 }
417
418 #[test]
419 fn test_projectile_time_of_flight_positive() {
420 let k = kin();
421 let tof = k.projectile_time_of_flight(20.0, FRAC_PI_4, G);
422 assert!(tof > 0.0);
423 }
424
425 #[test]
430 fn test_angular_velocity_at_zero_time() {
431 let k = kin();
432 let s = rotational(0.0, 3.0, 1.0);
433 assert!((k.angular_velocity_at(&s, 0.0) - 3.0).abs() < EPS);
434 }
435
436 #[test]
437 fn test_angular_velocity_at_positive_time() {
438 let k = kin();
439 let s = rotational(0.0, 2.0, 4.0);
440 assert!((k.angular_velocity_at(&s, 3.0) - 14.0).abs() < EPS);
442 }
443
444 #[test]
445 fn test_angle_at_zero_time() {
446 let k = kin();
447 let s = rotational(1.5, 2.0, 0.5);
448 assert!((k.angle_at(&s, 0.0) - 1.5).abs() < EPS);
449 }
450
451 #[test]
452 fn test_angle_at_positive_time() {
453 let k = kin();
454 let s = rotational(0.0, 0.0, 2.0);
455 assert!((k.angle_at(&s, 4.0) - 16.0).abs() < EPS);
457 }
458
459 #[test]
460 fn test_angle_at_full_equation() {
461 let k = kin();
462 let s = rotational(1.0, 3.0, 2.0);
463 assert!((k.angle_at(&s, 2.0) - 11.0).abs() < EPS);
465 }
466
467 #[test]
468 fn test_propagate_rotational_preserves_alpha() {
469 let k = kin();
470 let s = rotational(0.0, 1.0, 2.0);
471 let next = k.propagate_rotational(&s, 3.0);
472 assert!((next.alpha - 2.0).abs() < EPS);
473 }
474
475 #[test]
476 fn test_propagate_rotational_angle() {
477 let k = kin();
478 let s = rotational(0.0, 0.0, 2.0);
479 let next = k.propagate_rotational(&s, 3.0);
480 assert!((next.angle - 9.0).abs() < EPS);
482 }
483
484 #[test]
485 fn test_propagate_rotational_omega() {
486 let k = kin();
487 let s = rotational(0.0, 1.0, 3.0);
488 let next = k.propagate_rotational(&s, 4.0);
489 assert!((next.omega - 13.0).abs() < EPS);
491 }
492
493 #[test]
494 fn test_propagate_rotational_zero_time_identity() {
495 let k = kin();
496 let s = rotational(2.5, 1.0, -0.5);
497 let next = k.propagate_rotational(&s, 0.0);
498 assert!((next.angle - s.angle).abs() < EPS);
499 assert!((next.omega - s.omega).abs() < EPS);
500 }
501
502 #[test]
507 fn test_linear_rotational_analogy() {
508 let k = kin();
509 let lin = linear(1.0, 3.0, 0.5);
511 let rot = rotational(1.0, 3.0, 0.5);
512 assert!((k.position_at(&lin, 2.0) - k.angle_at(&rot, 2.0)).abs() < EPS);
513 assert!((k.velocity_at(&lin, 2.0) - k.angular_velocity_at(&rot, 2.0)).abs() < EPS);
514 }
515
516 #[test]
521 fn test_default() {
522 let k = Kinematics;
523 let s = linear(0.0, 5.0, 0.0);
524 assert!((k.velocity_at(&s, 1.0) - 5.0).abs() < EPS);
525 }
526
527 #[test]
528 fn test_linear_state_clone() {
529 let s = linear(1.0, 2.0, 3.0);
530 let c = s.clone();
531 assert_eq!(s, c);
532 }
533
534 #[test]
535 fn test_rotational_state_clone() {
536 let s = rotational(0.1, 0.2, 0.3);
537 let c = s.clone();
538 assert_eq!(s, c);
539 }
540}