Skip to main content

geop_core_topology/contains/
rng.rs

1//! A tiny, seedable, deterministic PRNG for picking random ray directions in
2//! containment queries — no external `rand` dependency.
3
4use geop_core_math::{
5    scalars::Scalar,
6    vector::{Vector2, Vector3},
7};
8
9/// xorshift64* generator.
10pub struct Rng {
11    state: u64,
12}
13
14impl Rng {
15    pub fn new(seed: u64) -> Self {
16        // xorshift64* requires a nonzero state.
17        Self {
18            state: if seed == 0 { 0x9E3779B97F4A7C15 } else { seed },
19        }
20    }
21
22    pub fn next_u64(&mut self) -> u64 {
23        let mut x = self.state;
24        x ^= x >> 12;
25        x ^= x << 25;
26        x ^= x >> 27;
27        self.state = x;
28        x.wrapping_mul(0x2545_F491_4F6C_DD1D)
29    }
30
31    /// Uniform float in `[0, 1)`.
32    pub fn next_f64(&mut self) -> f64 {
33        (self.next_u64() >> 11) as f64 * (1.0 / (1u64 << 53) as f64)
34    }
35
36    /// Uniform float in `[lo, hi)`.
37    pub fn next_range(&mut self, lo: f64, hi: f64) -> f64 {
38        lo + self.next_f64() * (hi - lo)
39    }
40
41    /// A random direction, uniformly distributed on the unit circle.
42    pub fn next_direction2<S: Scalar>(&mut self) -> Vector2<S> {
43        let theta = self.next_range(0.0, std::f64::consts::TAU);
44        Vector2::from_array([S::from_f64(theta.cos()), S::from_f64(theta.sin())])
45    }
46
47    /// A random direction, uniformly distributed on the unit sphere
48    /// (Archimedes' cylindrical projection: uniform `z`, uniform angle).
49    pub fn next_direction3<S: Scalar>(&mut self) -> Vector3<S> {
50        let z = self.next_range(-1.0, 1.0);
51        let theta = self.next_range(0.0, std::f64::consts::TAU);
52        let r = (1.0 - z * z).max(0.0).sqrt();
53        Vector3::from_array([
54            S::from_f64(r * theta.cos()),
55            S::from_f64(r * theta.sin()),
56            S::from_f64(z),
57        ])
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use super::Rng;
64
65    #[test]
66    fn same_seed_is_deterministic() {
67        let mut a = Rng::new(42);
68        let mut b = Rng::new(42);
69        for _ in 0..100 {
70            assert_eq!(a.next_u64(), b.next_u64());
71        }
72    }
73
74    #[test]
75    fn different_seeds_diverge() {
76        let mut a = Rng::new(1);
77        let mut b = Rng::new(2);
78        assert_ne!(a.next_u64(), b.next_u64());
79    }
80
81    #[test]
82    fn f64_stays_in_unit_range() {
83        let mut r = Rng::new(7);
84        for _ in 0..1000 {
85            let x = r.next_f64();
86            assert!(x >= 0.0 && x < 1.0);
87        }
88    }
89}