Skip to main content

deep_time/physics/
position.rs

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