Skip to main content

renew_fixed/
vector.rs

1//! Vectors over [`Fixed`].
2//!
3//! Two dimensions and three, as separate concrete types rather than one
4//! generic over dimension. The physics contract makes the same choice for the
5//! same reason: dimension-generic vocabularies produce signatures nobody can
6//! read, and the cost of writing `dot` twice is smaller than the cost of every
7//! caller reading a bound.
8
9use core::ops::{Add, Mul, Neg, Sub};
10
11use crate::Fixed;
12
13/// How far to shift a direction left so normalising it keeps its precision.
14///
15/// Normalising is unchanged by scaling, and **shifting a fixed-point value left is
16/// exact** — no rounding, no loss. So a short direction is scaled up before
17/// its length is taken, which is the difference between a normal that is
18/// unit to a thousandth of a percent and one that is forty per cent wrong.
19///
20/// The target is 2³⁸ for the largest component: big enough that squaring
21/// keeps every significant bit, small enough that three squared components
22/// summed stay inside what the type holds (3 × 2⁶⁰ < 2⁶²).
23fn normalising_shift(largest: u64) -> u32 {
24    // A value with `k` significant bits has `64 - k` leading zeros, so
25    // shifting by `64 - k - 26` leaves it with 38. The 26 was 25 in the
26    // first version, which targets 2^39 rather than 2^38 — and three
27    // squared 2^39 components summed overflow an i64, so 3D normalisation
28    // saturated and returned a normal a quarter of a per cent off unit.
29    // Caught by a property test whose generator had just been widened to
30    // reach short vectors; the arithmetic was one bit out and the comment
31    // above was right all along.
32    largest.leading_zeros().saturating_sub(26)
33}
34
35/// A two-dimensional vector.
36///
37/// # Contract
38///
39/// - **Every operation is deterministic**, because every operation is
40///   [`Fixed`] arithmetic and nothing else.
41/// - **Saturating throughout**, inheriting the scalar's behaviour: a component
42///   that overflows clamps and is counted rather than wrapping.
43/// - **`Eq` and `Hash`**, so a vector can be a map key or enter a state hash
44///   directly — which is the thing a float vector cannot offer.
45#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
46pub struct Vec2 {
47    pub x: Fixed,
48    pub y: Fixed,
49}
50
51/// A three-dimensional vector. See [`Vec2`] for the contract; it is the same.
52#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
53pub struct Vec3 {
54    pub x: Fixed,
55    pub y: Fixed,
56    pub z: Fixed,
57}
58
59impl Vec2 {
60    /// The origin.
61    pub const ZERO: Self = Self {
62        x: Fixed::ZERO,
63        y: Fixed::ZERO,
64    };
65    /// One unit along x.
66    pub const X: Self = Self {
67        x: Fixed::ONE,
68        y: Fixed::ZERO,
69    };
70    /// One unit along y.
71    pub const Y: Self = Self {
72        x: Fixed::ZERO,
73        y: Fixed::ONE,
74    };
75
76    /// From components.
77    #[must_use]
78    pub const fn new(x: Fixed, y: Fixed) -> Self {
79        Self { x, y }
80    }
81
82    /// From whole numbers, which is how most call sites write a constant.
83    #[must_use]
84    pub const fn from_ints(x: i32, y: i32) -> Self {
85        Self {
86            x: Fixed::from_int(x),
87            y: Fixed::from_int(y),
88        }
89    }
90
91    /// The dot product.
92    #[must_use]
93    pub fn dot(self, other: Self) -> Fixed {
94        self.x.saturating_mul(other.x) + self.y.saturating_mul(other.y)
95    }
96
97    /// The 2D cross product: a scalar, the z of the 3D cross of these vectors
98    /// lifted into the plane. Positive when `other` is counter-clockwise of
99    /// `self`, which is what a winding test reads.
100    #[must_use]
101    pub fn cross(self, other: Self) -> Fixed {
102        self.x.saturating_mul(other.y) - self.y.saturating_mul(other.x)
103    }
104
105    /// The squared length.
106    ///
107    /// Preferred over [`Vec2::length`] wherever a comparison will do, and not
108    /// only for speed: this is exact where the length is rounded, so two
109    /// vectors that compare equal by squared length may compare unequal by
110    /// length.
111    #[must_use]
112    pub fn length_squared(self) -> Fixed {
113        self.dot(self)
114    }
115
116    /// The squared length at full width, which cannot overflow and cannot
117    /// round.
118    ///
119    /// The form to compare with. [`Vec2::length_squared`] narrows to a
120    /// `Fixed` and therefore has a floor: a vector whose components are all
121    /// below 182 raw units squares to *zero* there, which is how a direction
122    /// can appear to have no length at all. Nothing is lost here.
123    #[must_use]
124    pub fn length_squared_wide(self) -> crate::Wide {
125        self.x.wide_mul(self.x) + self.y.wide_mul(self.y)
126    }
127
128    /// The length, floored to the representable value below the exact one.
129    ///
130    /// Computed through the full-width square, so it is exact for short
131    /// vectors where narrowing first would have lost them entirely — a
132    /// one-raw-unit vector has length one here and had length zero before.
133    #[must_use]
134    pub fn length(self) -> Fixed {
135        self.length_squared_wide().sqrt()
136    }
137
138    /// The distance to another point.
139    #[must_use]
140    pub fn distance(self, other: Self) -> Fixed {
141        (self - other).length()
142    }
143
144    /// A unit vector in the same direction, or `None` for the zero vector.
145    ///
146    /// Fallible rather than asserting, because the zero vector is a value a
147    /// simulation legitimately produces — a body at rest, a contact between
148    /// coincident points — and refusing it would put an assertion on a path
149    /// that runs every frame.
150    ///
151    /// **The result is unit-length to within four parts in 65536**, which is
152    /// asserted by a property test over every magnitude including the
153    /// shortest. Callers wanting an exact equality should compare squared
154    /// lengths against a tolerance rather than expecting [`Fixed::ONE`].
155    ///
156    /// The direction is scaled up before its length is taken, and that is
157    /// not an optimisation. Shifting a fixed-point value left is exact, and
158    /// without it a short direction is divided by a length that rounded to
159    /// something far too coarse: before this, a direction of 41 raw units
160    /// came back as a normal forty-one per cent too long, and anything whose
161    /// components were all below 181 raw had no direction at all.
162    #[must_use]
163    pub fn normalize(self) -> Option<Self> {
164        let largest = self
165            .x
166            .to_bits()
167            .unsigned_abs()
168            .max(self.y.to_bits().unsigned_abs());
169        if largest == 0 {
170            return None;
171        }
172        let shift = normalising_shift(largest);
173        let scaled = Self::new(
174            Fixed::from_bits(self.x.to_bits() << shift),
175            Fixed::from_bits(self.y.to_bits() << shift),
176        );
177        // Non-zero after the check above, and rescaling is what makes that
178        // true: the largest component carries 38 significant bits, so its
179        // square alone exceeds 2^60 and the length cannot round to zero.
180        // Before rescaling this needed a second zero check, and that check
181        // was the bug — it turned a short direction into no direction.
182        let length = scaled.length();
183        Some(Self::new(
184            scaled.x.saturating_div(length),
185            scaled.y.saturating_div(length),
186        ))
187    }
188
189    /// Linear interpolation, `t` clamped to `[0, 1]`.
190    ///
191    /// Written as `a + (b - a) * t` rather than `a*(1-t) + b*t`: the second is
192    /// the numerically better form in floating point and the worse one here,
193    /// because it rounds twice as often and neither form gains anything from
194    /// exactness at the endpoints — this one is exact at both by construction.
195    #[must_use]
196    pub fn lerp(self, other: Self, t: Fixed) -> Self {
197        let t = t.clamp(Fixed::ZERO, Fixed::ONE);
198        self + (other - self) * t
199    }
200
201    /// The component of `self` along `direction`, which must be unit-length.
202    ///
203    /// The building block of move-and-slide: removing this from a
204    /// displacement is what makes a body slide along a wall rather than stop
205    /// at it.
206    #[must_use]
207    pub fn project_onto_unit(self, direction: Self) -> Self {
208        direction * self.dot(direction)
209    }
210
211    /// `self` with its component along `normal` removed.
212    ///
213    /// `normal` must be unit-length. This is the slide operation itself, named
214    /// so the physics implementation does not spell it out at each call site
215    /// and get the sign wrong at one of them.
216    #[must_use]
217    pub fn slide_along(self, normal: Self) -> Self {
218        self - self.project_onto_unit(normal)
219    }
220
221    /// Rotated counter-clockwise by `angle`.
222    ///
223    /// The standard rotation, in fixed point: `(x cos − y sin, x sin + y
224    /// cos)`. Each component is two rounded products, so a rotated vector
225    /// keeps its length to a few parts in 65536 rather than exactly — the
226    /// tests state the bound.
227    ///
228    /// [`Vec2::perpendicular`] remains for the quarter turn, and is not the
229    /// same thing: it is exact, where this rounds.
230    #[must_use]
231    pub fn rotate(self, angle: crate::Angle) -> Self {
232        let (sin, cos) = angle.sin_cos();
233        Self::new(
234            self.x.saturating_mul(cos) - self.y.saturating_mul(sin),
235            self.x.saturating_mul(sin) + self.y.saturating_mul(cos),
236        )
237    }
238
239    /// Perpendicular, rotated a quarter turn counter-clockwise.
240    ///
241    /// Exact — a quarter turn is a swap and a negation, needing no
242    /// trigonometry, which is why this is available when general rotation is
243    /// not.
244    #[must_use]
245    pub fn perpendicular(self) -> Self {
246        Self::new(-self.y, self.x)
247    }
248}
249
250impl Vec3 {
251    /// The origin.
252    pub const ZERO: Self = Self {
253        x: Fixed::ZERO,
254        y: Fixed::ZERO,
255        z: Fixed::ZERO,
256    };
257
258    /// From components.
259    #[must_use]
260    pub const fn new(x: Fixed, y: Fixed, z: Fixed) -> Self {
261        Self { x, y, z }
262    }
263
264    /// From whole numbers.
265    #[must_use]
266    pub const fn from_ints(x: i32, y: i32, z: i32) -> Self {
267        Self {
268            x: Fixed::from_int(x),
269            y: Fixed::from_int(y),
270            z: Fixed::from_int(z),
271        }
272    }
273
274    /// The dot product.
275    #[must_use]
276    pub fn dot(self, other: Self) -> Fixed {
277        self.x.saturating_mul(other.x)
278            + self.y.saturating_mul(other.y)
279            + self.z.saturating_mul(other.z)
280    }
281
282    /// The cross product: a vector perpendicular to both.
283    #[must_use]
284    pub fn cross(self, other: Self) -> Self {
285        Self::new(
286            self.y.saturating_mul(other.z) - self.z.saturating_mul(other.y),
287            self.z.saturating_mul(other.x) - self.x.saturating_mul(other.z),
288            self.x.saturating_mul(other.y) - self.y.saturating_mul(other.x),
289        )
290    }
291
292    /// The squared length. See [`Vec2::length_squared`] on why to prefer it.
293    #[must_use]
294    pub fn length_squared(self) -> Fixed {
295        self.dot(self)
296    }
297
298    /// The squared length at full width. See [`Vec2::length_squared_wide`].
299    #[must_use]
300    pub fn length_squared_wide(self) -> crate::Wide {
301        self.x.wide_mul(self.x) + self.y.wide_mul(self.y) + self.z.wide_mul(self.z)
302    }
303
304    /// The length, floored. Computed through the full-width square.
305    #[must_use]
306    pub fn length(self) -> Fixed {
307        self.length_squared_wide().sqrt()
308    }
309
310    /// The distance to another point.
311    #[must_use]
312    pub fn distance(self, other: Self) -> Fixed {
313        (self - other).length()
314    }
315
316    /// A unit vector in the same direction, or `None` for the zero vector.
317    /// See [`Vec2::normalize`] on how close to unit the result is.
318    #[must_use]
319    pub fn normalize(self) -> Option<Self> {
320        let largest = self
321            .x
322            .to_bits()
323            .unsigned_abs()
324            .max(self.y.to_bits().unsigned_abs())
325            .max(self.z.to_bits().unsigned_abs());
326        if largest == 0 {
327            return None;
328        }
329        let shift = normalising_shift(largest);
330        let scaled = Self::new(
331            Fixed::from_bits(self.x.to_bits() << shift),
332            Fixed::from_bits(self.y.to_bits() << shift),
333            Fixed::from_bits(self.z.to_bits() << shift),
334        );
335        // Non-zero after the check above, and rescaling is what makes that
336        // true: the largest component carries 38 significant bits, so its
337        // square alone exceeds 2^60 and the length cannot round to zero.
338        // Before rescaling this needed a second zero check, and that check
339        // was the bug — it turned a short direction into no direction.
340        let length = scaled.length();
341        Some(Self::new(
342            scaled.x.saturating_div(length),
343            scaled.y.saturating_div(length),
344            scaled.z.saturating_div(length),
345        ))
346    }
347
348    /// Linear interpolation, `t` clamped to `[0, 1]`.
349    #[must_use]
350    pub fn lerp(self, other: Self, t: Fixed) -> Self {
351        let t = t.clamp(Fixed::ZERO, Fixed::ONE);
352        self + (other - self) * t
353    }
354
355    /// `self` with its component along a unit `normal` removed.
356    #[must_use]
357    pub fn slide_along(self, normal: Self) -> Self {
358        self - normal * self.dot(normal)
359    }
360}
361
362// The operators, rather than inherent `add`/`sub`/`neg`/`scale`. For a vector
363// these read the way the maths does, and inherent methods by those names
364// shadow the traits confusingly enough that the linter says so.
365
366impl Add for Vec2 {
367    type Output = Self;
368    fn add(self, other: Self) -> Self {
369        Self::new(self.x + other.x, self.y + other.y)
370    }
371}
372
373impl Sub for Vec2 {
374    type Output = Self;
375    fn sub(self, other: Self) -> Self {
376        Self::new(self.x - other.x, self.y - other.y)
377    }
378}
379
380impl Neg for Vec2 {
381    type Output = Self;
382    fn neg(self) -> Self {
383        Self::new(-self.x, -self.y)
384    }
385}
386
387/// Scaled by a scalar. Saturating componentwise, like everything else here.
388impl Mul<Fixed> for Vec2 {
389    type Output = Self;
390    fn mul(self, factor: Fixed) -> Self {
391        Self::new(self.x.saturating_mul(factor), self.y.saturating_mul(factor))
392    }
393}
394
395impl Add for Vec3 {
396    type Output = Self;
397    fn add(self, other: Self) -> Self {
398        Self::new(self.x + other.x, self.y + other.y, self.z + other.z)
399    }
400}
401
402impl Sub for Vec3 {
403    type Output = Self;
404    fn sub(self, other: Self) -> Self {
405        Self::new(self.x - other.x, self.y - other.y, self.z - other.z)
406    }
407}
408
409impl Neg for Vec3 {
410    type Output = Self;
411    fn neg(self) -> Self {
412        Self::new(-self.x, -self.y, -self.z)
413    }
414}
415
416impl Mul<Fixed> for Vec3 {
417    type Output = Self;
418    fn mul(self, factor: Fixed) -> Self {
419        Self::new(
420            self.x.saturating_mul(factor),
421            self.y.saturating_mul(factor),
422            self.z.saturating_mul(factor),
423        )
424    }
425}