///|
/// Pseudo-random number generation for Monte Carlo rendering.
struct Rng {
state0 : UInt64
state1 : UInt64
}
/// Create a new RNG with the given seed.
pub fn new_rng(seed : UInt64) -> Rng {
let s0 = splitmix64(seed)
let s1 = splitmix64(s0)
{ state0: s0, state1: s1 }
}
/// Create a new RNG with a default seed.
pub fn default_rng() -> Rng {
new_rng(123456789UL)
}
fn splitmix64(state : UInt64) -> UInt64 {
let mut z = state + 0x9e3779b97f4a7c15UL
z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9UL
z = (z ^ (z >> 27)) * 0x94d049bb133111ebUL
z ^ (z >> 31)
}
fn Rng::next_u64(self : Rng) -> (UInt64, Rng) {
let s1 = self.state0
let s0 = self.state1
let result = s0 + s1
let new_state0 = s0
let mut new_s1 = s1 ^ (s1 << 23)
new_s1 = new_s1 ^ (new_s1 >> 17)
new_s1 = new_s1 ^ s0
new_s1 = new_s1 ^ (s0 >> 26)
(result, { state0: new_state0, state1: new_s1 })
}
/// Generate a random Double in [0, 1).
pub fn Rng::random_double(self : Rng) -> (Double, Rng) {
let (val, new_rng) = self.next_u64()
(val.to_double() / 18446744073709551616.0, new_rng)
}
/// Generate a random Double in [min, max).
pub fn Rng::random_double_range(self : Rng, min~ : Double, max~ : Double) -> (Double, Rng) {
let (val, new_rng) = self.random_double()
(min + (max - min) * val, new_rng)
}
/// Generate a random integer in [0, n).
pub fn Rng::random_int(self : Rng, n : Int) -> (Int, Rng) {
let (val, new_rng) = self.random_double()
let i = (val * n.to_double()).to_int()
(if i >= n { n - 1 } else { i }, new_rng)
}
/// Generate a random point inside the unit sphere (recursive rejection sampling).
pub fn Rng::random_in_unit_sphere(self : Rng) -> (Vec3, Rng) {
let (x, rng1) = self.random_double_range(min=-1.0, max=1.0)
let (y, rng2) = rng1.random_double_range(min=-1.0, max=1.0)
let (z, rng3) = rng2.random_double_range(min=-1.0, max=1.0)
let p = { x, y, z }
if p.length_squared() < 1.0 {
(p, rng3)
} else {
rng3.random_in_unit_sphere()
}
}
/// Generate a random unit vector (on the unit sphere surface, uniform).
pub fn Rng::random_unit_vector(self : Rng) -> (Vec3, Rng) {
let (p, new_rng) = self.random_in_unit_sphere()
(p.normalize(), new_rng)
}
/// Generate a random point on the hemisphere oriented by the given normal.
pub fn Rng::random_on_hemisphere(self : Rng, normal : Vec3) -> (Vec3, Rng) {
let (p, new_rng) = self.random_in_unit_sphere()
if p.dot(normal) > 0.0 {
(p, new_rng)
} else {
(-p, new_rng)
}
}
/// Generate a random point inside the unit disk (for defocus blur).
pub fn Rng::random_in_unit_disk(self : Rng) -> (Vec3, Rng) {
let (x, rng1) = self.random_double_range(min=-1.0, max=1.0)
let (y, rng2) = rng1.random_double_range(min=-1.0, max=1.0)
let p = { x, y, z: 0.0 }
if p.length_squared() < 1.0 {
(p, rng2)
} else {
rng2.random_in_unit_disk()
}
}
/// Cosine-weighted hemisphere sampling (Lambertian).
pub fn Rng::random_cosine_direction(self : Rng) -> (Vec3, Rng) {
let (r1, rng1) = self.random_double()
let (r2, rng2) = rng1.random_double()
let z = (1.0 - r2).sqrt()
let phi = 2.0 * @math.PI * r1
let x = @math.cos(phi) * r2.sqrt()
let y = @math.sin(phi) * r2.sqrt()
({ x, y, z }, rng2)
}