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}