1use core::ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign};
24
25use glam::DVec3;
26use serde::{Deserialize, Deserializer, Serialize, Serializer};
27
28use crate::{Damping, Force, Length, Mass, Qty, Stiffness, Time, Velocity};
29
30#[derive(Clone, Copy, PartialEq, Default)]
32pub struct QVec3<
33 const L: i8,
34 const M: i8,
35 const T: i8,
36 const I: i8,
37 const K: i8,
38 const N: i8,
39 const J: i8,
40>(DVec3);
41
42impl<const L: i8, const M: i8, const T: i8, const I: i8, const K: i8, const N: i8, const J: i8>
43 QVec3<L, M, T, I, K, N, J>
44{
45 pub const ZERO: Self = QVec3(DVec3::ZERO);
47
48 pub fn from_si(v: DVec3) -> Self {
50 QVec3(v)
51 }
52
53 pub fn to_si(self) -> DVec3 {
55 self.0
56 }
57
58 pub fn new(
60 x: Qty<L, M, T, I, K, N, J>,
61 y: Qty<L, M, T, I, K, N, J>,
62 z: Qty<L, M, T, I, K, N, J>,
63 ) -> Self {
64 QVec3(DVec3::new(x.to_si(), y.to_si(), z.to_si()))
65 }
66
67 pub fn splat(v: Qty<L, M, T, I, K, N, J>) -> Self {
69 QVec3(DVec3::splat(v.to_si()))
70 }
71
72 pub fn x(self) -> Qty<L, M, T, I, K, N, J> {
74 Qty::from_si(self.0.x)
75 }
76
77 pub fn y(self) -> Qty<L, M, T, I, K, N, J> {
79 Qty::from_si(self.0.y)
80 }
81
82 pub fn z(self) -> Qty<L, M, T, I, K, N, J> {
84 Qty::from_si(self.0.z)
85 }
86
87 pub fn length(self) -> Qty<L, M, T, I, K, N, J> {
89 Qty::from_si(self.0.length())
90 }
91
92 pub fn normalize(self) -> DVec3 {
95 self.0.normalize_or_zero()
96 }
97
98 pub fn along(self, direction: DVec3) -> Qty<L, M, T, I, K, N, J> {
101 Qty::from_si(self.0.dot(direction))
102 }
103
104 pub fn perpendicular_to(self, direction: DVec3) -> Self {
106 QVec3(self.0 - direction * self.0.dot(direction))
107 }
108
109 pub fn is_finite(self) -> bool {
111 self.0.is_finite()
112 }
113
114 pub fn lerp(self, other: Self, t: f64) -> Self {
120 QVec3(self.0 + (other.0 - self.0) * t)
121 }
122}
123
124macro_rules! generic_vec_op {
125 ($trait:ident, $method:ident, $op:tt) => {
126 impl<
127 const L: i8,
128 const M: i8,
129 const T: i8,
130 const I: i8,
131 const K: i8,
132 const N: i8,
133 const J: i8,
134 > $trait for QVec3<L, M, T, I, K, N, J>
135 {
136 type Output = Self;
137 fn $method(self, rhs: Self) -> Self {
138 QVec3(self.0 $op rhs.0)
139 }
140 }
141 };
142}
143
144generic_vec_op!(Add, add, +);
145generic_vec_op!(Sub, sub, -);
146
147impl<const L: i8, const M: i8, const T: i8, const I: i8, const K: i8, const N: i8, const J: i8>
148 AddAssign for QVec3<L, M, T, I, K, N, J>
149{
150 fn add_assign(&mut self, rhs: Self) {
151 self.0 += rhs.0;
152 }
153}
154
155impl<const L: i8, const M: i8, const T: i8, const I: i8, const K: i8, const N: i8, const J: i8>
156 SubAssign for QVec3<L, M, T, I, K, N, J>
157{
158 fn sub_assign(&mut self, rhs: Self) {
159 self.0 -= rhs.0;
160 }
161}
162
163impl<const L: i8, const M: i8, const T: i8, const I: i8, const K: i8, const N: i8, const J: i8> Neg
164 for QVec3<L, M, T, I, K, N, J>
165{
166 type Output = Self;
167 fn neg(self) -> Self {
168 QVec3(-self.0)
169 }
170}
171
172impl<const L: i8, const M: i8, const T: i8, const I: i8, const K: i8, const N: i8, const J: i8>
173 Mul<f64> for QVec3<L, M, T, I, K, N, J>
174{
175 type Output = Self;
176 fn mul(self, k: f64) -> Self {
177 QVec3(self.0 * k)
178 }
179}
180
181impl<const L: i8, const M: i8, const T: i8, const I: i8, const K: i8, const N: i8, const J: i8>
182 Div<f64> for QVec3<L, M, T, I, K, N, J>
183{
184 type Output = Self;
185 fn div(self, k: f64) -> Self {
186 QVec3(self.0 / k)
187 }
188}
189
190impl<const L: i8, const M: i8, const T: i8, const I: i8, const K: i8, const N: i8, const J: i8>
191 Mul<QVec3<L, M, T, I, K, N, J>> for f64
192{
193 type Output = QVec3<L, M, T, I, K, N, J>;
194 fn mul(self, v: QVec3<L, M, T, I, K, N, J>) -> QVec3<L, M, T, I, K, N, J> {
195 QVec3(v.0 * self)
196 }
197}
198
199impl<const L: i8, const M: i8, const T: i8, const I: i8, const K: i8, const N: i8, const J: i8>
200 core::fmt::Debug for QVec3<L, M, T, I, K, N, J>
201{
202 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
203 write!(f, "[{}, {}, {}", self.0.x, self.0.y, self.0.z)?;
204 for (symbol, exponent) in [
205 ("m", L),
206 ("kg", M),
207 ("s", T),
208 ("A", I),
209 ("K", K),
210 ("mol", N),
211 ("cd", J),
212 ] {
213 match exponent {
214 0 => {}
215 1 => write!(f, "·{symbol}")?,
216 e => write!(f, "·{symbol}^{e}")?,
217 }
218 }
219 write!(f, "]")
220 }
221}
222
223impl<const L: i8, const M: i8, const T: i8, const I: i8, const K: i8, const N: i8, const J: i8>
224 Serialize for QVec3<L, M, T, I, K, N, J>
225{
226 fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
227 [self.0.x, self.0.y, self.0.z].serialize(s)
228 }
229}
230
231impl<
232 'de,
233 const L: i8,
234 const M: i8,
235 const T: i8,
236 const I: i8,
237 const K: i8,
238 const N: i8,
239 const J: i8,
240 > Deserialize<'de> for QVec3<L, M, T, I, K, N, J>
241{
242 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
243 <[f64; 3]>::deserialize(d).map(|[x, y, z]| QVec3(DVec3::new(x, y, z)))
244 }
245}
246
247pub type LengthVec = QVec3<1, 0, 0, 0, 0, 0, 0>;
250pub type VelocityVec = QVec3<1, 0, -1, 0, 0, 0, 0>;
252pub type AccelerationVec = QVec3<1, 0, -2, 0, 0, 0, 0>;
254pub type ForceVec = QVec3<1, 1, -2, 0, 0, 0, 0>;
256pub type MomentumVec = QVec3<1, 1, -1, 0, 0, 0, 0>;
260
261macro_rules! scaled_by {
263 ($vec:ty, $scalar:ty => $out:ty) => {
264 impl Mul<$scalar> for $vec {
265 type Output = $out;
266 fn mul(self, k: $scalar) -> $out {
267 QVec3(self.0 * k.to_si())
268 }
269 }
270 impl Mul<$vec> for $scalar {
271 type Output = $out;
272 fn mul(self, v: $vec) -> $out {
273 QVec3(v.0 * self.to_si())
274 }
275 }
276 impl Div<$scalar> for $out {
277 type Output = $vec;
278 fn div(self, k: $scalar) -> $vec {
279 QVec3(self.0 / k.to_si())
280 }
281 }
282 };
283}
284
285scaled_by!(VelocityVec, Time => LengthVec);
286scaled_by!(AccelerationVec, Time => VelocityVec);
287scaled_by!(ForceVec, Time => MomentumVec);
288scaled_by!(VelocityVec, Mass => MomentumVec);
289scaled_by!(AccelerationVec, Mass => ForceVec);
290scaled_by!(LengthVec, Stiffness => ForceVec);
293scaled_by!(VelocityVec, Damping => ForceVec);
294
295impl LengthVec {
296 pub fn mm(x: f64, y: f64, z: f64) -> LengthVec {
298 QVec3(DVec3::new(x, y, z) * 1e-3)
299 }
300 pub fn m(x: f64, y: f64, z: f64) -> LengthVec {
302 QVec3(DVec3::new(x, y, z))
303 }
304 pub fn in_mm(self) -> DVec3 {
306 self.0 * 1e3
307 }
308}
309
310impl VelocityVec {
311 pub fn mm_per_s(x: f64, y: f64, z: f64) -> VelocityVec {
313 QVec3(DVec3::new(x, y, z) * 1e-3)
314 }
315 pub fn m_per_s(x: f64, y: f64, z: f64) -> VelocityVec {
317 QVec3(DVec3::new(x, y, z))
318 }
319}
320
321pub fn distance(a: LengthVec, b: LengthVec) -> Length {
323 (a - b).length()
324}
325
326pub fn newton_second(mass: Mass, acceleration: AccelerationVec) -> ForceVec {
328 mass * acceleration
329}
330
331pub fn momentum(mass: Mass, velocity: VelocityVec) -> MomentumVec {
333 mass * velocity
334}
335
336pub fn kinetic_energy(mass: Mass, velocity: VelocityVec) -> crate::Energy {
339 let v = velocity.to_si().length();
340 Qty::from_si(0.5 * mass.to_si() * v * v)
341}
342
343pub fn free_travel(v0: VelocityVec, a: AccelerationVec, t: Time) -> (VelocityVec, LengthVec) {
345 let v = v0 + a * t;
346 let x = v0 * t + (a * t) * t * 0.5;
347 (v, x)
348}
349
350pub fn centripetal(mass: Mass, speed: Velocity, radius: Length) -> Force {
353 Qty::from_si(mass.to_si() * speed.to_si() * speed.to_si() / radius.to_si())
354}
355
356#[cfg(test)]
357mod tests {
358 use super::*;
359 use crate::{Energy, Temperature};
360
361 #[test]
364 fn normalising_strips_the_dimension() {
365 let d = LengthVec::mm(3.0, 4.0, 0.0);
366 assert!((d.length().in_mm() - 5.0).abs() < 1e-12);
367 let dir: DVec3 = d.normalize();
368 assert!((dir.length() - 1.0).abs() < 1e-15);
369 assert!((dir - DVec3::new(0.6, 0.8, 0.0)).length() < 1e-15);
370 assert_eq!(LengthVec::ZERO.normalize(), DVec3::ZERO);
372 }
373
374 #[test]
377 fn projection_keeps_the_dimension() {
378 let v = VelocityVec::mm_per_s(120.0, 0.0, -5.0);
379 let axis = DVec3::X;
380 let along: Velocity = v.along(axis);
381 assert!((along.to_si() - 0.12).abs() < 1e-15);
382 let across = v.perpendicular_to(axis);
383 assert!((across.along(axis).to_si()).abs() < 1e-15);
384 let rebuilt = across + VelocityVec::from_si(axis * along.to_si());
386 assert!((rebuilt - v).length().to_si() < 1e-15);
387 }
388
389 #[test]
393 fn constant_acceleration_is_dimensionally_checked() {
394 let a = AccelerationVec::from_si(DVec3::new(0.0, -crate::G0.to_si(), 0.0));
395 let (v, x) = free_travel(VelocityVec::ZERO, a, Time::s(2.0));
396 assert!((v.length().to_si() - 19.6133).abs() < 1e-3, "{v:?}");
397 assert!((x.length().to_si() - 19.6133).abs() < 1e-3, "{x:?}");
398 assert!(v.y().to_si() < 0.0 && x.y().to_si() < 0.0);
400 }
401
402 #[test]
405 fn work_equals_the_kinetic_energy_it_bought() {
406 let m = Mass::kg(2.0);
407 let a = AccelerationVec::from_si(DVec3::X * 3.0);
408 let f: ForceVec = newton_second(m, a);
409 assert!((f.length().to_si() - 6.0).abs() < 1e-12);
410
411 let t = Time::s(4.0);
412 let (v, x) = free_travel(VelocityVec::ZERO, a, t);
413 let work: Energy = Qty::from_si(f.along(DVec3::X).to_si() * x.along(DVec3::X).to_si());
414 let ke = kinetic_energy(m, v);
415 assert!(
416 (work - ke).abs().to_si() < 1e-9,
417 "work {work:?} should equal kinetic energy {ke:?}"
418 );
419 }
420
421 #[test]
424 fn momentum_adds_across_a_collision() {
425 let p1 = momentum(Mass::kg(2.0), VelocityVec::m_per_s(3.0, 0.0, 0.0));
426 let p2 = momentum(Mass::kg(1.0), VelocityVec::m_per_s(-4.0, 0.0, 0.0));
427 let total: MomentumVec = p1 + p2;
428 assert!((total.along(DVec3::X).to_si() - 2.0).abs() < 1e-12);
429 let after: VelocityVec = total / Mass::kg(3.0);
431 assert!((after.along(DVec3::X).to_si() - 2.0 / 3.0).abs() < 1e-12);
432 }
433
434 #[test]
435 fn vectors_round_trip_through_json() {
436 let v = LengthVec::mm(1.0, 2.0, 3.0);
437 let json = serde_json::to_string(&v).unwrap();
438 assert_eq!(json, "[0.001,0.002,0.003]");
439 assert_eq!(serde_json::from_str::<LengthVec>(&json).unwrap(), v);
440 }
441
442 #[test]
443 fn debug_shows_the_dimension() {
444 assert_eq!(
445 format!("{:?}", ForceVec::from_si(DVec3::new(1.0, 0.0, 0.0))),
446 "[1, 0, 0·m·kg·s^-2]"
447 );
448 }
449
450 #[test]
453 fn wrong_dimensions_do_not_compile() {
454 let _ = LengthVec::mm(1.0, 0.0, 0.0);
455 let _ = VelocityVec::mm_per_s(1.0, 0.0, 0.0);
456 let _ = Temperature::kelvin(300.0);
457 }
461}