deep_time/physics/velocity.rs
1//! Velocity vector in meters per second.
2
3use crate::{C_SQUARED, Real, sqrt};
4
5/// A 3-dimensional velocity vector expressed in Cartesian coordinates (vx, vy, vz)
6/// with units of meters per second (SI).
7#[derive(Clone, Copy, Debug, PartialEq)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9#[cfg_attr(feature = "tsify", derive(tsify::Tsify))]
10pub struct Velocity {
11 /// X-component of velocity in meters per second (SI).
12 pub vx: Real,
13 /// Y-component of velocity in meters per second (SI).
14 pub vy: Real,
15 /// Z-component of velocity in meters per second (SI).
16 pub vz: Real,
17}
18
19impl Velocity {
20 /// Creates a new [`Velocity`] directly from its Cartesian components in m/s.
21 #[inline]
22 pub const fn new(vx: Real, vy: Real, vz: Real) -> Velocity {
23 Self { vx, vy, vz }
24 }
25
26 /// The zero velocity vector (at rest in the coordinate frame).
27 pub const ZERO: Self = Self::new(f!(0.0), f!(0.0), f!(0.0));
28
29 /// Creates a [`Velocity`] from its scalar speed (magnitude) in m/s.
30 ///
31 /// Direction is set along the x-axis because only the speed enters the
32 /// interval (`beta()`, `norm_squared()`, etc.).
33 #[inline]
34 pub const fn from_speed(speed_m_s: Real) -> Velocity {
35 Self::new(speed_m_s, f!(0.0), f!(0.0))
36 }
37
38 /// Returns the squared Euclidean norm (v²).
39 #[inline]
40 pub const fn norm_squared(self) -> Real {
41 self.vx * self.vx + self.vy * self.vy + self.vz * self.vz
42 }
43
44 /// Speed in m/s (Euclidean magnitude).
45 #[inline]
46 pub const fn speed(self) -> Real {
47 sqrt(self.norm_squared().max(f!(0.0)))
48 }
49
50 /// Spatial velocity as a fraction of light speed: \(\beta = |v|/c\).
51 ///
52 /// Spatial velocity \(v\) is this vector in metres of travel through space
53 /// per one second of the coordinate time \(t\) in which `(vx, vy, vz)` were
54 /// measured. [`Spacetime`](super::Spacetime) uses β in \(d\tau/dt\).
55 #[inline]
56 pub const fn beta(self) -> Real {
57 sqrt((self.norm_squared() / C_SQUARED).max(f!(0.0)))
58 }
59}
60
61#[cfg(feature = "wire")]
62impl Velocity {
63 /// Size of the canonical wire representation in bytes (24 bytes).
64 pub const WIRE_SIZE: usize = 24;
65
66 /// Serializes this [`Velocity`] into a fixed 24-byte buffer.
67 ///
68 /// All fields are stored as little-endian IEEE 754 `f64`.
69 pub fn to_wire_bytes(&self) -> [u8; Self::WIRE_SIZE] {
70 let mut buf = [0u8; Self::WIRE_SIZE];
71 buf[0..8].copy_from_slice(&self.vx.to_le_bytes());
72 buf[8..16].copy_from_slice(&self.vy.to_le_bytes());
73 buf[16..24].copy_from_slice(&self.vz.to_le_bytes());
74 buf
75 }
76
77 /// Deserializes a [`Velocity`] from exactly 24 bytes.
78 ///
79 /// ## Security
80 ///
81 /// Accepts any [`Real`] bit pattern (including `NaN`/`Inf`) to match the
82 /// type’s own invariants. Fixed size makes it immune to length-based
83 /// attacks. Safe for untrusted input.
84 pub fn from_wire_bytes(bytes: &[u8]) -> Option<Self> {
85 if bytes.len() != Self::WIRE_SIZE {
86 return None;
87 }
88 let vx = Real::from_le_bytes([
89 bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
90 ]);
91 let vy = Real::from_le_bytes([
92 bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15],
93 ]);
94 let vz = Real::from_le_bytes([
95 bytes[16], bytes[17], bytes[18], bytes[19], bytes[20], bytes[21], bytes[22], bytes[23],
96 ]);
97 Some(Self { vx, vy, vz })
98 }
99}