Skip to main content

long_screen/kernels/
geometry.rs

1//! Integer/float conventions shared with the adapter. JavaScript's `Math.round` rounds halves toward
2//! +∞ and `Math.floor(x + .5)` does not (it fails just below a half), so the exact form is spelled out.
3
4#[derive(Clone, Copy, Debug, PartialEq)]
5pub struct Rect {
6    pub x: f64,
7    pub y: f64,
8    pub width: f64,
9    pub height: f64,
10}
11
12impl Rect {
13    #[inline]
14    pub fn contains(&self, x: f64, y: f64) -> bool {
15        x >= self.x && y >= self.y && x < self.x + self.width && y < self.y + self.height
16    }
17}
18
19/// `Math.round`: nearest integer, halves toward +∞.
20#[inline]
21pub fn js_round(value: f64) -> i32 {
22    let floor = value.floor();
23    let rounded = if value - floor >= 0.5 {
24        floor + 1.0
25    } else {
26        floor
27    };
28    rounded as i32
29}
30
31#[inline]
32pub fn js_floor(value: f64) -> i32 {
33    value.floor() as i32
34}
35
36#[inline]
37pub fn js_ceil(value: f64) -> i32 {
38    value.ceil() as i32
39}
40
41/// `Math.hypot(x, y)` as V8 and JavaScriptCore compute it: scale by the larger magnitude, Kahan-sum the
42/// squares, multiply back. Not the correctly rounded libm `hypot`; poses compared against `dmin` and sorted
43/// by distance must round exactly as the historical adapter did.
44#[inline]
45pub fn js_hypot(x: f64, y: f64) -> f64 {
46    let (ax, ay) = (x.abs(), y.abs());
47    if ax.is_infinite() || ay.is_infinite() {
48        return f64::INFINITY;
49    }
50    if ax.is_nan() || ay.is_nan() {
51        return f64::NAN;
52    }
53    let max = ax.max(ay);
54    if max == 0.0 {
55        return 0.0;
56    }
57    let mut sum = 0.0f64;
58    let mut compensation = 0.0f64;
59    for v in [ax, ay] {
60        let n = v / max;
61        let summand = n * n - compensation;
62        let preliminary = sum + summand;
63        compensation = (preliminary - sum) - summand;
64        sum = preliminary;
65    }
66    sum.sqrt() * max
67}
68
69/// Xorshift32 with the exact `| 0` / `>>> 0` semantics of `src/core/math.ts::rng`.
70pub struct Rng(u32);
71
72impl Rng {
73    pub fn new(seed: u32) -> Self {
74        Rng(seed)
75    }
76    pub fn next_f64(&mut self) -> f64 {
77        let mut x = self.0;
78        x ^= x << 13;
79        x ^= x >> 17;
80        x ^= x << 5;
81        self.0 = x;
82        x as f64 / 4294967296.0
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    #[test]
91    fn rounding_matches_javascript() {
92        assert_eq!(js_round(0.5), 1);
93        assert_eq!(js_round(-0.5), 0);
94        assert_eq!(js_round(-1.5), -1);
95        assert_eq!(js_round(0.49999999999999994), 0);
96        assert_eq!(js_round(-0.5000000000000001), -1);
97        assert_eq!(js_round(2.5), 3);
98    }
99
100    #[test]
101    fn rng_sequence_is_deterministic() {
102        let mut a = Rng::new(0x9e3779b9);
103        let mut b = Rng::new(0x9e3779b9);
104        for _ in 0..1000 {
105            assert_eq!(a.next_f64(), b.next_f64());
106        }
107    }
108
109    #[test]
110    fn hypot_handles_exact_and_degenerate_inputs() {
111        assert_eq!(js_hypot(3.0, 4.0), 5.0);
112        assert_eq!(js_hypot(0.0, 0.0), 0.0);
113        assert_eq!(js_hypot(-7.0, 0.0), 7.0);
114        assert_eq!(js_hypot(f64::INFINITY, f64::NAN), f64::INFINITY);
115        assert!(js_hypot(1.0, f64::NAN).is_nan());
116    }
117}