1use bincode::{Decode, Encode};
2use core::fmt::Debug;
3use serde::{Deserialize, Serialize};
4use uom::si::angular_velocity::radian_per_second;
5use uom::si::f32::AngularVelocity as AngularVelocity32;
6use uom::si::f32::Velocity as Velocity32;
7use uom::si::f64::AngularVelocity as AngularVelocity64;
8use uom::si::f64::Velocity as Velocity64;
9use uom::si::velocity::meter_per_second;
10
11#[derive(Debug, Clone, Encode, Decode, Serialize, Deserialize)]
41pub struct VelocityTransform<T: Copy + Debug + 'static> {
42 pub linear: [T; 3],
44 pub angular: [T; 3],
46}
47
48impl<T: Copy + Debug + Default + num_traits::Zero + 'static> VelocityTransform<T> {
49 pub fn zero() -> Self {
51 Self {
52 linear: [T::zero(); 3],
53 angular: [T::zero(); 3],
54 }
55 }
56}
57
58impl<T> VelocityTransform<T>
60where
61 T: Copy
62 + Debug
63 + Default
64 + 'static
65 + std::ops::Add<Output = T>
66 + std::ops::Sub<Output = T>
67 + std::ops::Mul<Output = T>
68 + std::ops::Div<Output = T>
69 + std::ops::Neg<Output = T>,
70{
71 pub fn add(&self, other: &Self) -> Self {
73 Self {
74 linear: [
75 self.linear[0] + other.linear[0],
76 self.linear[1] + other.linear[1],
77 self.linear[2] + other.linear[2],
78 ],
79 angular: [
80 self.angular[0] + other.angular[0],
81 self.angular[1] + other.angular[1],
82 self.angular[2] + other.angular[2],
83 ],
84 }
85 }
86
87 pub fn sub(&self, other: &Self) -> Self {
89 Self {
90 linear: [
91 self.linear[0] - other.linear[0],
92 self.linear[1] - other.linear[1],
93 self.linear[2] - other.linear[2],
94 ],
95 angular: [
96 self.angular[0] - other.angular[0],
97 self.angular[1] - other.angular[1],
98 self.angular[2] - other.angular[2],
99 ],
100 }
101 }
102
103 pub fn scale(&self, scalar: T) -> Self {
105 Self {
106 linear: [
107 self.linear[0] * scalar,
108 self.linear[1] * scalar,
109 self.linear[2] * scalar,
110 ],
111 angular: [
112 self.angular[0] * scalar,
113 self.angular[1] * scalar,
114 self.angular[2] * scalar,
115 ],
116 }
117 }
118
119 pub fn negate(&self) -> Self {
121 Self {
122 linear: [-self.linear[0], -self.linear[1], -self.linear[2]],
123 angular: [-self.angular[0], -self.angular[1], -self.angular[2]],
124 }
125 }
126}
127
128impl VelocityTransform<f64> {
130 pub fn linear_velocity(&self) -> [Velocity64; 3] {
132 [
133 Velocity64::new::<meter_per_second>(self.linear[0]),
134 Velocity64::new::<meter_per_second>(self.linear[1]),
135 Velocity64::new::<meter_per_second>(self.linear[2]),
136 ]
137 }
138
139 pub fn angular_velocity(&self) -> [AngularVelocity64; 3] {
141 [
142 AngularVelocity64::new::<radian_per_second>(self.angular[0]),
143 AngularVelocity64::new::<radian_per_second>(self.angular[1]),
144 AngularVelocity64::new::<radian_per_second>(self.angular[2]),
145 ]
146 }
147}
148
149impl VelocityTransform<f32> {
150 pub fn linear_velocity(&self) -> [Velocity32; 3] {
152 [
153 Velocity32::new::<meter_per_second>(self.linear[0]),
154 Velocity32::new::<meter_per_second>(self.linear[1]),
155 Velocity32::new::<meter_per_second>(self.linear[2]),
156 ]
157 }
158
159 pub fn angular_velocity(&self) -> [AngularVelocity32; 3] {
161 [
162 AngularVelocity32::new::<radian_per_second>(self.angular[0]),
163 AngularVelocity32::new::<radian_per_second>(self.angular[1]),
164 AngularVelocity32::new::<radian_per_second>(self.angular[2]),
165 ]
166 }
167}
168
169impl<T: Copy + Debug + Default> Default for VelocityTransform<T> {
170 fn default() -> Self {
171 Self {
172 linear: [T::default(); 3],
173 angular: [T::default(); 3],
174 }
175 }
176}
177
178impl<T> std::ops::Add for VelocityTransform<T>
180where
181 T: Copy + Debug + Default + 'static + std::ops::Add<Output = T>,
182{
183 type Output = Self;
184
185 fn add(self, other: Self) -> Self::Output {
186 Self {
187 linear: [
188 self.linear[0] + other.linear[0],
189 self.linear[1] + other.linear[1],
190 self.linear[2] + other.linear[2],
191 ],
192 angular: [
193 self.angular[0] + other.angular[0],
194 self.angular[1] + other.angular[1],
195 self.angular[2] + other.angular[2],
196 ],
197 }
198 }
199}
200
201impl<T> std::ops::Sub for VelocityTransform<T>
203where
204 T: Copy + Debug + Default + 'static + std::ops::Sub<Output = T>,
205{
206 type Output = Self;
207
208 fn sub(self, other: Self) -> Self::Output {
209 Self {
210 linear: [
211 self.linear[0] - other.linear[0],
212 self.linear[1] - other.linear[1],
213 self.linear[2] - other.linear[2],
214 ],
215 angular: [
216 self.angular[0] - other.angular[0],
217 self.angular[1] - other.angular[1],
218 self.angular[2] - other.angular[2],
219 ],
220 }
221 }
222}
223
224impl<T> std::ops::Neg for VelocityTransform<T>
226where
227 T: Copy + Debug + Default + 'static + std::ops::Neg<Output = T>,
228{
229 type Output = Self;
230
231 fn neg(self) -> Self::Output {
232 Self {
233 linear: [-self.linear[0], -self.linear[1], -self.linear[2]],
234 angular: [-self.angular[0], -self.angular[1], -self.angular[2]],
235 }
236 }
237}
238
239pub fn transform_velocity<T>(
254 velocity: &VelocityTransform<T>,
255 transform: &crate::Transform3D<T>,
256 position: &[T; 3],
257) -> VelocityTransform<T>
258where
259 T: Copy
260 + Debug
261 + Default
262 + 'static
263 + std::ops::Add<Output = T>
264 + std::ops::Mul<Output = T>
265 + std::ops::Sub<Output = T>
266 + num_traits::NumCast,
267{
268 let mat = transform.to_matrix();
270 let rot = [
271 [mat[0][0], mat[0][1], mat[0][2]],
272 [mat[1][0], mat[1][1], mat[1][2]],
273 [mat[2][0], mat[2][1], mat[2][2]],
274 ];
275
276 let mut transformed_angular = [T::default(); 3];
278
279 #[allow(clippy::needless_range_loop)]
281 for i in 0..3 {
282 for j in 0..3 {
283 transformed_angular[i] = transformed_angular[i] + rot[i][j] * velocity.angular[j];
284 }
285 }
286
287 let mut transformed_linear = [T::default(); 3];
290 #[allow(clippy::needless_range_loop)]
291 for i in 0..3 {
292 for j in 0..3 {
293 transformed_linear[i] = transformed_linear[i] + rot[i][j] * velocity.linear[j];
294 }
295 }
296
297 let mut rotated_position = [T::default(); 3];
300 for i in 0..3 {
301 for (j, pos) in position.iter().enumerate() {
302 rotated_position[i] = rotated_position[i] + rot[i][j] * *pos;
303 }
304 }
305
306 let cross_product = [
309 transformed_angular[1] * rotated_position[2] - transformed_angular[2] * rotated_position[1],
310 transformed_angular[2] * rotated_position[0] - transformed_angular[0] * rotated_position[2],
311 transformed_angular[0] * rotated_position[1] - transformed_angular[1] * rotated_position[0],
312 ];
313
314 transformed_linear[0] = transformed_linear[0] + cross_product[0];
316 transformed_linear[1] = transformed_linear[1] + cross_product[1];
317 transformed_linear[2] = transformed_linear[2] + cross_product[2];
318
319 VelocityTransform {
320 linear: transformed_linear,
321 angular: transformed_angular,
322 }
323}
324
325#[cfg(test)]
326mod tests {
327 use super::*;
328
329 #[test]
330 fn test_velocity_transform_default() {
331 let vel: VelocityTransform<f32> = VelocityTransform::default();
332 assert_eq!(vel.linear, [0.0, 0.0, 0.0]);
333 assert_eq!(vel.angular, [0.0, 0.0, 0.0]);
334 }
335
336 #[test]
337 fn test_velocity_transform_zero() {
338 let vel = VelocityTransform::<f64>::zero();
339 assert_eq!(vel.linear, [0.0, 0.0, 0.0]);
340 assert_eq!(vel.angular, [0.0, 0.0, 0.0]);
341
342 let vel_f32 = VelocityTransform::<f32>::zero();
344 assert_eq!(vel_f32.linear, [0.0, 0.0, 0.0]);
345 assert_eq!(vel_f32.angular, [0.0, 0.0, 0.0]);
346 }
347
348 #[test]
349 fn test_velocity_with_units() {
350 let vel = VelocityTransform::<f32> {
351 linear: [1.0, 2.0, 3.0],
352 angular: [0.1, 0.2, 0.3],
353 };
354
355 let linear_vel = vel.linear_velocity();
356 let angular_vel = vel.angular_velocity();
357
358 assert_eq!(linear_vel[0].get::<meter_per_second>(), 1.0);
360 assert_eq!(linear_vel[1].get::<meter_per_second>(), 2.0);
361 assert_eq!(linear_vel[2].get::<meter_per_second>(), 3.0);
362
363 assert_eq!(angular_vel[0].get::<radian_per_second>(), 0.1);
364 assert_eq!(angular_vel[1].get::<radian_per_second>(), 0.2);
365 assert_eq!(angular_vel[2].get::<radian_per_second>(), 0.3);
366 }
367
368 #[test]
369 fn test_transform_velocity() {
370 let vel_a = VelocityTransform {
372 linear: [1.0f32, 0.0, 0.0], angular: [0.0, 0.0, 1.0], };
375
376 let transform_a_to_b = crate::Transform3D::from_matrix([
379 [0.0, -1.0, 0.0, 2.0], [1.0, 0.0, 0.0, 3.0],
381 [0.0, 0.0, 1.0, 0.0],
382 [0.0, 0.0, 0.0, 1.0],
383 ]);
384
385 let position_a = [0.0f32, 0.0, 0.0]; let vel_b = transform_velocity(&vel_a, &transform_a_to_b, &position_a);
390
391 let epsilon = 1e-5;
395 assert!(
396 (vel_b.linear[0] - 0.0).abs() < epsilon,
397 "X velocity should be 0 after rotation"
398 );
399 assert!(
400 (vel_b.linear[1] - 1.0).abs() < epsilon,
401 "Y velocity should be 1 after rotation"
402 );
403 assert!(
404 (vel_b.linear[2] - 0.0).abs() < epsilon,
405 "Z velocity should remain 0"
406 );
407
408 assert!(
409 (vel_b.angular[0] - 0.0).abs() < epsilon,
410 "X angular velocity should be 0"
411 );
412 assert!(
413 (vel_b.angular[1] - 0.0).abs() < epsilon,
414 "Y angular velocity should be 0"
415 );
416 assert!(
417 (vel_b.angular[2] - 1.0).abs() < epsilon,
418 "Z angular velocity should remain 1"
419 );
420 }
421
422 #[test]
423 fn test_transform_velocity_with_offset_point() {
424 let vel_a = VelocityTransform {
426 linear: [0.0f32, 0.0, 0.0], angular: [0.0, 0.0, 1.0], };
429
430 let transform_a_to_b = crate::Transform3D::from_matrix([
432 [1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0],
434 [0.0, 0.0, 1.0, 0.0],
435 [0.0, 0.0, 0.0, 1.0],
436 ]);
437
438 let position_a = [1.0f32, 0.0, 0.0];
440
441 let vel_b = transform_velocity(&vel_a, &transform_a_to_b, &position_a);
443
444 let epsilon = 1e-5;
448 assert!(
449 (vel_b.linear[0] - 0.0).abs() < epsilon,
450 "X velocity should be 0"
451 );
452 assert!(
453 (vel_b.linear[1] - 1.0).abs() < epsilon,
454 "Y velocity should be 1 due to cross product"
455 );
456 assert!(
457 (vel_b.linear[2] - 0.0).abs() < epsilon,
458 "Z velocity should be 0"
459 );
460
461 assert!(
463 (vel_b.angular[0] - 0.0).abs() < epsilon,
464 "X angular velocity should be 0"
465 );
466 assert!(
467 (vel_b.angular[1] - 0.0).abs() < epsilon,
468 "Y angular velocity should be 0"
469 );
470 assert!(
471 (vel_b.angular[2] - 1.0).abs() < epsilon,
472 "Z angular velocity should remain 1"
473 );
474 }
475}