Skip to main content

kvas_engine/
random.rs

1//! A tiny deterministic random generator for scripts (xorshift64*), so gameplay does not need
2//! an external crate. Keep one [`Rng`] in your script struct for a repeatable stream.
3//!
4//! ```
5//! use kvas_engine::random::Rng;
6//! let mut rng = Rng::new(42);
7//! let a = rng.f32();
8//! assert!((0.0..1.0).contains(&a));
9//! assert_eq!(Rng::new(42).f32(), a, "same seed, same stream");
10//! assert!((5..10).contains(&rng.range_i32(5, 10)));
11//! ```
12
13use glam::Vec3;
14
15/// Deterministic pseudo-random numbers. Not suitable for cryptography.
16#[derive(Clone, Debug)]
17pub struct Rng {
18    state: u64,
19}
20
21impl Default for Rng {
22    fn default() -> Self {
23        Self::new(0x9E37_79B9_7F4A_7C15)
24    }
25}
26
27impl Rng {
28    /// A generator for the given seed (any value, zero included).
29    pub fn new(seed: u64) -> Self {
30        // SplitMix64 scramble so nearby seeds give unrelated streams.
31        let mut z = seed.wrapping_add(0x9E37_79B9_7F4A_7C15);
32        z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
33        z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
34        Self { state: (z ^ (z >> 31)) | 1 }
35    }
36
37    /// Next raw 64 random bits.
38    pub fn next_u64(&mut self) -> u64 {
39        let mut x = self.state;
40        x ^= x >> 12;
41        x ^= x << 25;
42        x ^= x >> 27;
43        self.state = x;
44        x.wrapping_mul(0x2545_F491_4F6C_DD1D)
45    }
46
47    /// Uniform in `0.0..1.0`.
48    pub fn f32(&mut self) -> f32 {
49        (self.next_u64() >> 40) as f32 / (1u64 << 24) as f32
50    }
51
52    /// Uniform in `min..max` (swapped bounds are accepted).
53    pub fn range(&mut self, min: f32, max: f32) -> f32 {
54        min + (max - min) * self.f32()
55    }
56
57    /// Uniform integer in `min..max` (exclusive end); returns `min` for an empty range.
58    pub fn range_i32(&mut self, min: i32, max: i32) -> i32 {
59        if max <= min {
60            return min;
61        }
62        min + (self.next_u64() % (max as i64 - min as i64) as u64) as i32
63    }
64
65    /// True with probability `p` (0..1).
66    pub fn chance(&mut self, p: f32) -> bool {
67        self.f32() < p
68    }
69
70    /// A random element, or `None` for an empty slice.
71    pub fn pick<'a, T>(&mut self, items: &'a [T]) -> Option<&'a T> {
72        if items.is_empty() {
73            None
74        } else {
75            items.get(self.range_i32(0, items.len() as i32) as usize)
76        }
77    }
78
79    /// A uniformly distributed unit vector.
80    pub fn unit_vec3(&mut self) -> Vec3 {
81        let z = self.range(-1.0, 1.0);
82        let a = self.range(0.0, std::f32::consts::TAU);
83        let r = (1.0 - z * z).sqrt();
84        Vec3::new(r * a.cos(), z, r * a.sin())
85    }
86
87    /// A uniformly distributed point inside a sphere of the given radius.
88    pub fn in_sphere(&mut self, radius: f32) -> Vec3 {
89        self.unit_vec3() * radius * self.f32().cbrt()
90    }
91
92    /// A random point on a horizontal circle (XZ plane) of the given radius.
93    pub fn on_circle(&mut self, radius: f32) -> Vec3 {
94        let a = self.range(0.0, std::f32::consts::TAU);
95        Vec3::new(a.cos(), 0.0, a.sin()) * radius
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn streams_are_distinct_and_in_range() {
105        let mut a = Rng::new(1);
106        let mut b = Rng::new(2);
107        assert_ne!(a.next_u64(), b.next_u64());
108        for _ in 0..1000 {
109            assert!((0.0..1.0).contains(&a.f32()));
110            assert!((-3..3).contains(&a.range_i32(-3, 3)));
111            assert!(a.in_sphere(2.0).length() <= 2.0001);
112        }
113        assert_eq!(a.range_i32(4, 4), 4);
114        assert!(a.pick::<i32>(&[]).is_none());
115    }
116}