poisson2d/algorithm/
bridson.rs

1use glam::Vec2;
2use rand::distributions::Uniform;
3use rand::Rng;
4use rand_distr::StandardNormal;
5use sphere::sphere_volume;
6
7use crate::algorithm::{Algorithm, Creator};
8use crate::utils::*;
9use crate::Builder;
10
11/// Generates approximately uniform non-maximal Poisson disk samplings with O(n) time and O(n) space complexity relative to the number of samples generated.
12/// Based on Bridson, Robert. "Fast Poisson disk sampling in arbitrary dimensions." SIGGRAPH Sketches. 2007.
13#[derive(Debug, Clone, Copy)]
14pub struct Bridson;
15
16impl Creator for Bridson {
17    type Algo = Algo;
18
19    fn create(poisson: &Builder) -> Self::Algo {
20        Algo {
21            grid: Grid::new(poisson.radius, poisson.poisson_type),
22            active_samples: vec![],
23            outside: vec![],
24            success: 0,
25        }
26    }
27}
28
29/// Implementation for the Bridson algorithm
30pub struct Algo {
31    grid: Grid,
32    active_samples: Vec<Vec2>,
33    outside: Vec<Vec2>,
34    success: usize,
35}
36
37impl Algorithm for Algo {
38    fn next<R>(&mut self, poisson: &mut Builder, rng: &mut R) -> Option<mint::Vector2<f32>>
39    where
40        R: Rng,
41    {
42        while !self.active_samples.is_empty() {
43            let index = rng.sample(Uniform::new(0, self.active_samples.len()));
44            let cur = self.active_samples[index].clone();
45            for _ in 0..30 {
46                let min = 2.0 * poisson.radius;
47                let max = 4.0 * poisson.radius;
48                let sample = cur.clone() + random_point_annulus(rng, min, max).into();
49                if (0..2)
50                    .map(|n| sample[n])
51                    .all(|c| 0.0 <= c && c < 1.0)
52                {
53                    let index = sample_to_index(&sample, self.grid.side());
54                    if self.insert_if_valid(poisson, index, sample.clone()) {
55                        return Some(sample.into());
56                    }
57                }
58            }
59            self.active_samples.swap_remove(index);
60        }
61        while self.success == 0 {
62            let cell = rng.sample(Uniform::new(0, self.grid.cells()));
63            let index: Vec2 = decode(cell, self.grid.side()).expect(
64                "Because we are decoding random index within grid \
65                 this should work.",
66            );
67            let sample = choose_random_sample(rng, &self.grid, index.clone(), 0);
68            if self.insert_if_valid(poisson, index, sample.clone()) {
69                return Some(sample.into());
70            }
71        }
72        None
73    }
74
75    fn size_hint(&self, poisson: &Builder) -> (usize, Option<usize>) {
76        // Calculating upper bound should work because there is this many places left in the grid and no more can fit into it.
77        let upper = if self.grid.cells() > self.success {
78            self.grid.cells() - self.success
79        } else {
80            0
81        };
82        // Calculating lower bound should work because we calculate how much volume is left to be filled at worst case and
83        // how much sphere can fill it at best case and just figure out how many fills are still needed.
84        let spacing = self.grid.cell();
85        let grid_volume = (upper as f32) * spacing.powi(2);
86        let sphere_volume = sphere_volume(2.0 * poisson.radius, 2);
87        let lower: f32 = grid_volume / sphere_volume;
88        let mut lower = lower.floor() as usize;
89        if lower > 0 {
90            lower -= 1;
91        }
92        (lower, Some(upper))
93    }
94
95    fn restrict(&mut self, sample: mint::Vector2<f32>) {
96        let sample: Vec2 = sample.into();
97        self.success += 1;
98        let index = sample_to_index(&sample, self.grid.side());
99        if let Some(g) = self.grid.get_mut(index) {
100            g.push(sample);
101        } else {
102            self.outside.push(sample);
103        }
104    }
105
106    fn stays_legal(&self, poisson: &Builder, sample: mint::Vector2<f32>) -> bool {
107        let sample: Vec2 = sample.into();
108        let index = sample_to_index(&sample, self.grid.side());
109        is_disk_free(&self.grid, poisson, index, 0, sample.clone(), &self.outside)
110    }
111}
112
113impl Algo {
114    fn insert_if_valid(&mut self, poisson: &mut Builder, index: Vec2, sample: Vec2) -> bool {
115        if is_disk_free(
116            &self.grid,
117            poisson,
118            index.clone(),
119            0,
120            sample.clone(),
121            &self.outside,
122        ) {
123            self.active_samples.push(sample.clone());
124            self.grid
125                .get_mut(index)
126                .expect("Because the sample is in [0, 1), indexing it should work.")
127                .push(sample);
128            self.success += 1;
129            true
130        } else {
131            false
132        }
133    }
134}
135
136fn random_point_annulus<R>(rand: &mut R, min: f32, max: f32) -> Vec2
137where
138    R: Rng,
139{
140    loop {
141        let mut result = Vec2::zero();
142        for n in 0..2 {
143            result[n] = rand.sample(StandardNormal);
144        }
145        let result = result.normalize() * rand.gen::<f32>() * max;
146        if result.length() >= min {
147            return result;
148        }
149    }
150}
151
152#[test]
153fn random_point_annulus_does_not_generate_outside_annulus() {
154    use rand::{rngs::SmallRng, SeedableRng};
155    let mut rng = SmallRng::seed_from_u64(42);
156    for _ in 0..10000 {
157        let result = random_point_annulus(&mut rng, 1.0, 2.0);
158        assert!(result.length() >= 1.0);
159        assert!(result.length() <= 2.0);
160    }
161}
162
163#[test]
164fn random_point_annulus_generates_all_quadrants() {
165    use rand::{rngs::SmallRng, SeedableRng};
166    let mut rng = SmallRng::seed_from_u64(42);
167    let (mut top_left, mut top_right, mut bottom_left, mut bottom_right) =
168        (false, false, false, false);
169    for _ in 0..10000 {
170        let result = random_point_annulus(&mut rng, 1.0, 2.0);
171        if result.y() < 0.0 {
172            if result.x() < 0.0 {
173                bottom_left = true;
174            } else {
175                bottom_right = true;
176            }
177        } else {
178            if result.x() < 0.0 {
179                top_left = true;
180            } else {
181                top_right = true;
182            }
183        }
184    }
185    assert!(top_left);
186    assert!(top_right);
187    assert!(bottom_left);
188    assert!(bottom_right);
189}