Skip to main content

deep_time/physics/
position.rs

1//! 3D position vector (in meters) for physics / trajectory inputs.
2
3use crate::{Real, hypot};
4
5/// A 3-dimensional position vector expressed in Cartesian coordinates (x, y, z)
6/// with units of meters (SI).
7///
8/// Used with [`Velocity`](crate::physics::Velocity) and gravitational potentials
9/// when building proper-time rates and trajectory samples. The caller chooses
10/// the reference frame (e.g. Earth-centered or barycentric); this type does not
11/// tag the frame.
12#[derive(Clone, Copy, Debug, PartialEq)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
14#[cfg_attr(feature = "tsify", derive(tsify::Tsify))]
15pub struct Position {
16    /// X-coordinate in meters (SI).
17    pub x: Real,
18    /// Y-coordinate in meters (SI).
19    pub y: Real,
20    /// Z-coordinate in meters (SI).
21    pub z: Real,
22}
23
24impl Position {
25    /// Creates a new `Position` directly from its Cartesian components in meters.
26    #[inline]
27    pub const fn new(x: Real, y: Real, z: Real) -> Position {
28        Self { x, y, z }
29    }
30
31    /// The zero vector (origin of the chosen coordinate system).
32    pub const ZERO: Self = Self::new(f!(0.0), f!(0.0), f!(0.0));
33
34    /// Creates a `Position` from coordinates expressed in Astronomical Units (AU),
35    /// converting them to meters using the IAU 2012 definition
36    /// (1 AU = 149 597 870 700 m).
37    ///
38    /// Especially convenient when working with planetary ephemerides or solar-system
39    /// models that are natively given in AU.
40    #[inline]
41    pub const fn from_au(x: Real, y: Real, z: Real) -> Position {
42        const AU: Real = f!(1.495978707e11);
43        Self {
44            x: x * AU,
45            y: y * AU,
46            z: z * AU,
47        }
48    }
49
50    /// Returns the Euclidean norm (distance from the origin).
51    #[inline]
52    pub const fn norm(&self) -> Real {
53        hypot(hypot(self.x, self.y), self.z)
54    }
55
56    /// Straight-line (Euclidean) distance to another position.
57    pub const fn distance_to(&self, other: &Self) -> Real {
58        let dx = self.x - other.x;
59        let dy = self.y - other.y;
60        let dz = self.z - other.z;
61        hypot(hypot(dx, dy), dz)
62    }
63
64    /// Returns a new position that lies a fraction `t` of the way along the straight
65    /// line between `self` and `other`.
66    ///
67    /// This is known as linear interpolation (lerp). It is useful when you need
68    /// an intermediate position along a straight-line segment between two known points.
69    ///
70    /// ## Parameters
71    ///
72    /// - `other` – the ending position
73    /// - `t` – interpolation parameter (0.0 = start point, 1.0 = end point).
74    ///   Values outside [0, 1] are allowed and will extrapolate.
75    ///
76    /// ## Examples
77    ///
78    /// ```rust
79    /// use deep_time::physics::Position;
80    ///
81    /// let a = Position::new(0.0, 0.0, 0.0);
82    /// let b = Position::new(10.0, 20.0, 30.0);
83    ///
84    /// let midpoint = a.lerp(&b, 0.5);           // (5.0, 10.0, 15.0)
85    /// let quarter   = a.lerp(&b, 0.25);         // (2.5, 5.0, 7.5)
86    /// let beyond    = a.lerp(&b, 1.5);          // (15.0, 30.0, 45.0)
87    /// ```
88    #[inline]
89    pub const fn lerp(&self, other: &Self, t: Real) -> Position {
90        Self::new(
91            self.x * (f!(1.0) - t) + other.x * t,
92            self.y * (f!(1.0) - t) + other.y * t,
93            self.z * (f!(1.0) - t) + other.z * t,
94        )
95    }
96}
97
98#[cfg(feature = "wire")]
99impl Position {
100    /// Size of the canonical wire representation in bytes (24 bytes).
101    pub const WIRE_SIZE: usize = 24;
102
103    /// Serializes this [[`Position`] into a fixed 24-byte buffer.
104    ///
105    /// All fields are stored as little-endian IEEE 754 `f64`.
106    pub fn to_wire_bytes(&self) -> [u8; Self::WIRE_SIZE] {
107        let mut buf = [0u8; Self::WIRE_SIZE];
108        buf[0..8].copy_from_slice(&self.x.to_le_bytes());
109        buf[8..16].copy_from_slice(&self.y.to_le_bytes());
110        buf[16..24].copy_from_slice(&self.z.to_le_bytes());
111        buf
112    }
113
114    /// Deserializes a [`Position`] from exactly 24 bytes.
115    ///
116    /// ## Security
117    ///
118    /// Accepts any `f64` bit pattern (including `NaN`/`Inf`) to match the
119    /// type’s own invariants. Fixed size makes it immune to length-based
120    /// attacks. Safe for untrusted input.
121    pub fn from_wire_bytes(bytes: &[u8]) -> Option<Self> {
122        if bytes.len() != Self::WIRE_SIZE {
123            return None;
124        }
125        let x = Real::from_le_bytes([
126            bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
127        ]);
128        let y = Real::from_le_bytes([
129            bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15],
130        ]);
131        let z = Real::from_le_bytes([
132            bytes[16], bytes[17], bytes[18], bytes[19], bytes[20], bytes[21], bytes[22], bytes[23],
133        ]);
134        Some(Self { x, y, z })
135    }
136}