unity_random/
random.rs

1use std::time::{SystemTime, UNIX_EPOCH};
2
3#[cfg(feature = "serde")]
4use serde_crate::{Deserialize, Serialize};
5
6use crate::crypto::Crypto;
7
8/// The internal state of the random number generator.
9#[cfg_attr(
10    feature = "serde",
11    derive(Serialize, Deserialize),
12    serde(crate = "serde_crate")
13)]
14#[derive(Copy, Clone)]
15pub struct State {
16    pub s0: u32,
17    pub s1: u32,
18    pub s2: u32,
19    pub s3: u32,
20}
21
22/// An implementation of Unity's seeded PRNG, which aims to be faster than
23/// .NET's `System.Random`.
24#[cfg_attr(
25    feature = "serde",
26    derive(Serialize, Deserialize),
27    serde(crate = "serde_crate")
28)]
29pub struct Random {
30    /// Gets or sets the full internal state of the random number generator.
31    pub state: State,
32}
33
34impl Random {
35    /// Initializes the PRNG with the current time.
36    pub fn new() -> Random {
37        let timestamp = Random::get_timestamp() as u32;
38
39        Random {
40            state: Crypto::init_state(timestamp),
41        }
42    }
43
44    /// (Re)-initializes the PRNG with a given seed.
45    pub fn init_state(&mut self, seed: i32) {
46        self.state = Crypto::init_state(seed as u32);
47    }
48
49    /// Returns a random `i32` within `[min..max]`.
50    ///
51    /// Minimum is inclusive, maximum is exclusive.
52    pub fn range_int(&mut self, mut min: i32, mut max: i32) -> i32 {
53        if min > max {
54            (min, max) = (max, min);
55        }
56
57        let diff = (max - min) as u32;
58
59        if diff > 0 {
60            min + (self.next_u32().rem_euclid(diff)) as i32
61        } else {
62            min
63        }
64    }
65
66    /// Returns a random `f32` within `[min..max]` (range is inclusive).
67    pub fn range_float(&mut self, min: f32, max: f32) -> f32 {
68        Crypto::precision_f32(self.range_float_intern(min, max), 7)
69    }
70
71    /// Returns a random `f32` within `[0..1]` (range is inclusive).
72    pub fn value(&mut self) -> f32 {
73        Crypto::precision_f32(self.next_f32(), 7)
74    }
75
76    /// Returns a random point inside or on a circle with radius 1.0.
77    ///
78    /// Note that the probability space includes the perimeter of the circle because `next_f32`,
79    /// which is inclusive to 1.0, is used to acquire a random radius.
80    pub fn inside_unit_circle(&mut self) -> (f32, f32) {
81        let theta = self.range_float_intern(0., std::f32::consts::TAU);
82        let radius = (self.range_float_intern(0., 1.)).sqrt();
83
84        let x = Crypto::precision_f32(radius * theta.cos(), 7);
85        let y = Crypto::precision_f32(radius * theta.sin(), 7);
86
87        (x, y)
88    }
89
90    /// Returns a random point on the surface of a sphere with radius 1.0.
91    pub fn on_unit_sphere(&mut self) -> (f32, f32, f32) {
92        let (mut x, mut y, mut z) = self.on_unit_sphere_intern();
93
94        x = Crypto::precision_f32(x, 7);
95        y = Crypto::precision_f32(y, 7);
96        z = Crypto::precision_f32(z, 7);
97
98        (x, y, z)
99    }
100
101    /// Returns a random point inside or on a sphere with radius 1.0.
102    ///
103    /// Note that the probability space includes the surface of the sphere because `next_f32`,
104    /// which is inclusive to 1.0, is used to acquire a random radius.
105    pub fn inside_unit_sphere(&mut self) -> (f32, f32, f32) {
106        let (mut x, mut y, mut z) = self.on_unit_sphere_intern();
107
108        let dist = self.next_f32().powf(1. / 3.);
109
110        x = Crypto::precision_f32(x * dist, 7);
111        y = Crypto::precision_f32(y * dist, 7);
112        z = Crypto::precision_f32(z * dist, 7);
113
114        (x, y, z)
115    }
116
117    /// Returns a random rotation.
118    ///
119    /// Randomize the x, y, z, and w of a Quaternion each to `[-1.0..1.0]` (inclusive)
120    /// via Range and normalize the result.
121    ///
122    /// See also `rotation_uniform` for a slower but higher quality algorithm.
123    pub fn rotation(&mut self) -> (f32, f32, f32, f32) {
124        let mut x = self.range_float_intern(-1., 1.);
125        let mut y = self.range_float_intern(-1., 1.);
126        let mut z = self.range_float_intern(-1., 1.);
127        let mut w = self.range_float_intern(-1., 1.);
128
129        let mut mag = (x.powi(2) + y.powi(2) + z.powi(2) + w.powi(2)).sqrt();
130
131        if w < 0. {
132            mag = -mag;
133        }
134
135        x = Crypto::precision_f32(x / mag, 7);
136        y = Crypto::precision_f32(y / mag, 7);
137        z = Crypto::precision_f32(z / mag, 7);
138        w = Crypto::precision_f32(w / mag, 7);
139
140        (x, y, z, w)
141    }
142
143    /// Returns a random rotation with uniform distribution.
144    ///
145    /// Employs Hopf fibration to return a random Quaternion
146    /// within a uniformly distributed selection space.
147    ///
148    /// Gives higher quality results compared to the more naive approach employed by rotation,
149    /// though at a 40% performance cost.
150    pub fn rotation_uniform(&mut self) -> (f32, f32, f32, f32) {
151        let u1 = self.range_float_intern(0., 1.);
152        let u2 = self.range_float_intern(0., std::f32::consts::TAU);
153        let u3 = self.range_float_intern(0., std::f32::consts::TAU);
154
155        let sqrt = (u1).sqrt();
156        let inv = (1. - u1).sqrt();
157
158        let mut x = inv * u2.sin();
159        let mut y = inv * u2.cos();
160        let mut z = sqrt * u3.sin();
161        let mut w = sqrt * u3.cos();
162
163        if w < 0. {
164            x = -x;
165            y = -y;
166            z = -z;
167            w = -w;
168        }
169
170        x = Crypto::precision_f32(x, 7);
171        y = Crypto::precision_f32(y, 7);
172        z = Crypto::precision_f32(z, 7);
173        w = Crypto::precision_f32(w, 7);
174
175        (x, y, z, w)
176    }
177
178    /// Generates a random RGBA color.
179    ///
180    /// This may produce inaccurate results due to an issue with .NET versions before 5.x.
181    ///
182    /// Unity uses a custom build of Mono, which is based on an older .NET version.
183    pub fn color(&mut self) -> (f32, f32, f32, f32) {
184        self.color_hsva(0., 1., 0., 1., 0., 1., 1., 1.)
185    }
186
187    /// Generates a random RGBA color from hue ranges.
188    ///
189    /// This may produce inaccurate results due to an issue with .NET versions before 5.x.
190    ///
191    /// Unity uses a custom build of Mono, which is based on an older .NET version.
192    pub fn color_h(&mut self, hue_min: f32, hue_max: f32) -> (f32, f32, f32, f32) {
193        self.color_hsva(hue_min, hue_max, 0., 1., 0., 1., 1., 1.)
194    }
195
196    /// Generates a random RGBA color from hue and saturation ranges.
197    ///
198    /// This may produce inaccurate results due to an issue with .NET versions before 5.x.
199    ///
200    /// Unity uses a custom build of Mono, which is based on an older .NET version.
201    pub fn color_hs(
202        &mut self,
203        hue_min: f32,
204        hue_max: f32,
205        saturation_min: f32,
206        saturation_max: f32,
207    ) -> (f32, f32, f32, f32) {
208        self.color_hsva(
209            hue_min,
210            hue_max,
211            saturation_min,
212            saturation_max,
213            0.,
214            1.,
215            1.,
216            1.,
217        )
218    }
219
220    /// Generates a random RGBA color from HSV ranges.
221    ///
222    /// This may produce inaccurate results due to an issue with .NET versions before 5.x.
223    ///
224    /// Unity uses a custom build of Mono, which is based on an older .NET version.
225    pub fn color_hsv(
226        &mut self,
227        hue_min: f32,
228        hue_max: f32,
229        saturation_min: f32,
230        saturation_max: f32,
231        value_min: f32,
232        value_max: f32,
233    ) -> (f32, f32, f32, f32) {
234        self.color_hsva(
235            hue_min,
236            hue_max,
237            saturation_min,
238            saturation_max,
239            value_min,
240            value_max,
241            1.,
242            1.,
243        )
244    }
245
246    /// Generates a random RGBA color from HSV and alpha ranges.
247    ///
248    /// This may produce inaccurate results due to an issue with .NET versions before 5.x.
249    ///
250    /// Unity uses a custom build of Mono, which is based on an older .NET version.
251    pub fn color_hsva(
252        &mut self,
253        hue_min: f32,
254        hue_max: f32,
255        saturation_min: f32,
256        saturation_max: f32,
257        value_min: f32,
258        value_max: f32,
259        alpha_min: f32,
260        alpha_max: f32,
261    ) -> (f32, f32, f32, f32) {
262        let hue = Crypto::lerp(hue_min, hue_max, self.next_f32());
263        let sat = Crypto::lerp(saturation_min, saturation_max, self.next_f32());
264        let val = Crypto::lerp(value_min, value_max, self.next_f32());
265
266        let (mut r, mut g, mut b, _) = Crypto::hsv_to_rbg(hue, sat, val, true);
267        let mut a = Crypto::lerp(alpha_min, alpha_max, self.next_f32());
268
269        r = Crypto::precision_f32(r, 7);
270        g = Crypto::precision_f32(g, 7);
271        b = Crypto::precision_f32(b, 7);
272        a = Crypto::precision_f32(a, 7);
273
274        (r, g, b, a)
275    }
276
277    /// Generates the next u32.
278    fn next_u32(&mut self) -> u32 {
279        Crypto::next_u32(&mut self.state)
280    }
281
282    /// Generates the next f32.
283    fn next_f32(&mut self) -> f32 {
284        Crypto::next_f32(&mut self.state)
285    }
286
287    /// Generates the next f32, converting it to the range `[min..max]` (inclusive).
288    fn range_float_intern(&mut self, min: f32, max: f32) -> f32 {
289        let next = self.next_f32();
290
291        (1. - next) * max + next * min
292    }
293
294    /// Returns a random point on the surface of a sphere with radius 1.0.
295    fn on_unit_sphere_intern(&mut self) -> (f32, f32, f32) {
296        let dist = self.range_float_intern(-1., 1.);
297        let rad = self.range_float_intern(0., std::f32::consts::TAU);
298        let radius_xy = (1. - dist.powi(2)).sqrt();
299
300        let x = rad.cos() * radius_xy;
301        let y = rad.sin() * radius_xy;
302        let z = dist;
303
304        (x, y, z)
305    }
306
307    /// Returns the current timestamp down to the second.
308    fn get_timestamp() -> u64 {
309        SystemTime::now()
310            .duration_since(UNIX_EPOCH)
311            .expect("SystemTime before UNIX Epoch!")
312            .as_secs()
313    }
314}