circle_packer/
lib.rs

1//! A circle packing utility.
2//! 
3//! 
4
5#[cfg(not(feature = "serde"))]
6#[derive(Debug, Clone, PartialEq, Default)]
7pub struct Circle {
8    pub x: f32,
9    pub y: f32,
10    pub radius: f32,
11}
12
13#[cfg(feature = "serde")]
14#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize)]
15pub struct Circle {
16    pub x: f32,
17    pub y: f32,
18    pub radius: f32,
19}
20
21impl Circle {
22    pub fn distance_to_origin(&self) -> f32 {
23        (self.x * self.x + self.y * self.y).sqrt()
24    }
25    fn distance(&self, other: &Circle) -> f32 {
26        let x = self.x - other.x;
27        let y = self.y - other.y;
28        (x * x + y * y).sqrt()
29    }
30    fn intersect(&self, other: &Circle) -> bool {
31        // Epsilon Error for floating point errors
32        self.distance(other) + 1e-4 < self.radius + other.radius
33    }
34    fn new_node(radius: f32, c_m: &Circle, c_n: &Circle) -> (Circle, Circle) {
35        let dist = c_n.distance(c_m);
36        // Non-right angle trig to figure out the angles
37        let phi1 = (c_n.y - c_m.y).atan2(c_n.x - c_m.x);
38        let phi2 = ((dist.powi(2) + (c_m.radius + radius).powi(2) - (c_n.radius + radius).powi(2))
39            / (2.0 * (c_m.radius + radius) * dist))
40            .acos();
41        // The sin & cos of the angles give the x & y coords on unit circle. Expand and translate.
42        let solution_1 = Circle {
43            x: c_m.x + (c_m.radius + radius) * (phi1 + phi2).cos(),
44            y: c_m.y + (c_m.radius + radius) * (phi1 + phi2).sin(),
45            radius,
46        };
47        // Other solution
48        let solution_2 = Circle {
49            x: c_m.x + (c_m.radius + radius) * (phi1 - phi2).cos(),
50            y: c_m.y + (c_m.radius + radius) * (phi1 - phi2).sin(),
51            radius,
52        };
53        (solution_1, solution_2)
54    }
55
56    fn initialise(radii: &[f32]) -> Vec<Circle> {
57        match radii.len() {
58            0 => Vec::new(),
59            1 => vec![Circle {
60                x: 0.0,
61                y: 0.0,
62                radius: radii[0],
63            }],
64            // Place the two right next to each other
65            2 => {
66                let r0 = radii[0];
67                let r1 = radii[1];
68                let ray = r0 + r1;
69                vec![
70                    Circle {
71                        x: ray / 2.0,
72                        y: 0.0,
73                        radius: r0,
74                    },
75                    Circle {
76                        x: -ray / 2.0,
77                        y: 0.0,
78                        radius: r1,
79                    },
80                ]
81            }
82            // There are 3 or more nodes. We ignore the rest and place the first 3 so they touch.
83            _ => {
84                let r0 = radii[0];
85                let r1 = radii[1];
86                let r2 = radii[2];
87                let mut front = vec![
88                    Circle {
89                        x: 0.0,
90                        y: 0.0,
91                        radius: r0,
92                    },
93                    Circle {
94                        x: r0 + r1,
95                        y: 0.0,
96                        radius: r1,
97                    },
98                ];
99                {
100                    let (sol_1, sol_2) = Self::new_node(r2, &front[0], &front[1]);
101                    if sol_1.y < 0.0 {
102                        front.push(sol_1);
103                    } else {
104                        front.push(sol_2)
105                    }
106                }
107                // Center of mass calculation, we put this at the origin
108                let (a, b, c) = (r0 + r1, r0 + r2, r1 + r2);
109                let s = (a + b + c) / 2.0;
110                let delta = (s * (s - a) * (s - b) * (s - c)).sqrt();
111                let (l1, l2, l3) = (
112                    a + delta / (s - a),
113                    b + delta / (s - b),
114                    c + delta / (s - c),
115                );
116                let l_sum = l1 + l2 + l3;
117                let (l1, l2, l3) = (l1 / l_sum, l2 / l_sum, l3 / l_sum);
118                let x_correction = l1 * front[2].x + l2 * front[1].x + l3 * front[0].x;
119                let y_correction = l1 * front[2].y + l2 * front[1].y + l3 * front[0].y;
120                front
121                    .drain(..)
122                    .map(|mut c| {
123                        c.x = c.x - x_correction;
124                        c.y = c.y - y_correction;
125                        c
126                    })
127                    .collect()
128            }
129        }
130    }
131}
132
133
134#[derive(Debug, Clone, Default)]
135pub struct CirclePacker {
136    radii: Vec<f32>,
137    circles: Vec<Circle>,
138    front: Vec<usize>,
139}
140
141impl CirclePacker {
142    /// Adds a radius to the packer.
143    pub fn push(&mut self, radius: f32) {
144        self.radii.push(radius);
145
146        if self.radii.len() <= 3 {
147            self.circles = Circle::initialise(&self.radii);
148            self.front = (0..self.circles.len()).collect();
149        } else {
150            let mut index = self.front_closest();
151            let mut next_index = self.front_get_next_index(index);
152            let mut candidate = self.candidate(radius, index, next_index);
153            while let Some(i) = self.front_get_intersection(index, &candidate) {
154                let (front_dist, back_dist) = self.front_distance_to_node(index, i);
155
156                if front_dist < back_dist {
157                    let (break_start, break_end) = self.front_remove(index, i);
158                    candidate = self.candidate(
159                        radius,
160                        break_start,
161                        break_end,
162                    );
163                    index = break_start;
164                    next_index = break_end;
165                } else {
166                    let (break_start, break_end) = self.front_remove(i, next_index);
167                    candidate = self.candidate(
168                        radius,
169                        break_start,
170                        break_end,
171                    );
172                    index = break_start;
173                    next_index = break_end;
174                }
175            }
176            self.insert(candidate, next_index);
177        }
178    }
179
180    /// Returns a copy of the circles accumulated so far.
181    pub fn circles(&self) -> Vec<Circle> {
182        let (center_x, center_y) = self.center_of_mass();
183        self.circles.iter().map(|c| {
184            Circle {
185                x: c.x - center_x,
186                y: c.y - center_y,
187                radius: c.radius,
188            }
189        }).collect()
190    }
191
192    /// Returns a copy of the circles accumulated so far, embedded in the provided circle.
193    pub fn circles_in(&self, embedding_circle: &Circle) -> Vec<Circle> {
194        let mut center_circles = self.circles();
195        if let Some(radius) = center_circles.iter().map(|c| c.distance_to_origin() + c.radius).max_by(|a,b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)) {
196            let radius_ratio = embedding_circle.radius / radius;
197            center_circles.iter_mut().map(|c| {
198                c.x = c.x*radius_ratio + embedding_circle.x;
199                c.y = c.y*radius_ratio + embedding_circle.y;
200                c.radius = c.radius * radius_ratio;
201            }).collect()
202        }
203        center_circles
204    }
205
206    fn candidate(&self, radius: f32, index: usize, next_index: usize) -> Circle {
207        let (solution_1, solution_2) = Circle::new_node(radius, &self.circles[self.front[index]], &self.circles[self.front[next_index]]);
208        let c1 = &self.circles[self.front[index]]; 
209        let c2 = &self.circles[self.front[next_index]];
210        // The determiniant tells us if a change of basis will flip the orientation or keep it steady. 
211        // We want the chosen solution to be on the right side of the vector from c1 to c2's centers. 
212        // This means we want the determinant to be negative. 
213        let determinate = (solution_1.x - c1.x)*(c2.y - c1.y) - (solution_1.y - c1.y)*(c2.x - c1.x);
214        if determinate > 0.0 {
215            solution_2
216        } else {
217            solution_1
218        }
219    }
220
221    /// Inserts a node
222    fn insert(&mut self, node: Circle, front_break_end: usize) {
223        let front_index = self.circles.len();
224        self.circles.push(node);
225        if 0 != front_break_end {
226            self.front.insert(front_break_end, front_index);
227        } else {
228            self.front.push(front_index);
229        }
230    }
231
232    /// The closest node in the front to the origin
233    fn front_closest(&self) -> usize {
234        self.front.iter()
235                .enumerate()
236                .min_by(|(_, m), (_, n)| {
237                    self.circles[**m].distance_to_origin()
238                        .partial_cmp(&self.circles[**n].distance_to_origin())
239                        .unwrap_or(std::cmp::Ordering::Equal)
240                })
241                .unwrap()
242                .0
243    }
244
245    /// Wrapping 
246    fn front_get_next_index(&self, start_index: usize) -> usize {
247        (start_index + 1) % self.front.len()
248    }
249    /// Find the closest intersecting node in the front to the start index.
250    fn front_get_intersection(&self, start_index: usize, candidate: &Circle) -> Option<usize> {
251        let mut intersecting_nodes = self.front
252            .iter()
253            .enumerate()
254            .filter_map(|(i, n)| {
255                if self.circles[*n].intersect(candidate) {
256                    Some(i)
257                } else {
258                    None
259                }
260            })
261            .collect::<Vec<usize>>();
262        intersecting_nodes.sort_by(|a, b| {
263            let (a_b, a_f) = self.front_distance_to_node(start_index, *a);
264            let a = a_b.min(a_f);
265            let (b_b, b_f) = self.front_distance_to_node(start_index, *b);
266            let b = b_b.min(b_f);
267            a.partial_cmp(&b).unwrap_or(std::cmp::Ordering::Equal)
268        });
269        intersecting_nodes.first().map(|f| *f)
270    }
271    /// Distance along the chain from the start to the end.
272    fn front_distance_to_node(&self, start_index: usize, end_index: usize) -> (f32, f32) {
273        let total_dist: f32 = self.front.iter().map(|n| self.circles[*n].radius).sum();
274        match start_index.cmp(&end_index) {
275            std::cmp::Ordering::Less => {
276                let front_dist: f32 = self.front[start_index..end_index].iter().map(|i| self.circles[*i].radius).sum();
277                (
278                    2.0 * front_dist,
279                    2.0 * total_dist - 2.0 * front_dist - 2.0 * self.circles[self.front[end_index]].radius,
280                )
281            }
282            std::cmp::Ordering::Equal => (0.0, total_dist),
283            std::cmp::Ordering::Greater => {
284                let back_dist: f32 = self.front[end_index..start_index].iter().map(|i| self.circles[*i].radius).sum();
285                (
286                    2.0 * total_dist - 2.0 * back_dist - 2.0 * self.circles[self.front[start_index]].radius,
287                    2.0 * back_dist - 2.0 * self.circles[self.front[end_index]].radius,
288                )
289            }
290        }
291    }
292    /// Remove the bits between the start and end and returns the new indexes of the start and end.
293    fn front_remove(&mut self, start_index: usize, end_index: usize) -> (usize, usize) {
294        if start_index < end_index {
295            self.front.drain((start_index + 1)..end_index);
296            (start_index, self.front_get_next_index(start_index))
297        } else {
298            self.front.drain((start_index + 1)..);
299            self.front.drain(..end_index);
300            (
301                self.front.len() - 1,
302                0,
303            )
304        }
305    }
306
307    fn center_of_mass(&self) -> (f32, f32) {
308        let x: f32 = self.circles.iter().map(|c| c.x * (std::f32::consts::PI * c.radius.powi(2))).sum();
309        let y: f32 = self.circles.iter().map(|c| c.y * (std::f32::consts::PI * c.radius.powi(2))).sum();
310        let radius: f32 = self.circles.iter().map(|c| (std::f32::consts::PI * c.radius.powi(2))).sum();
311        (x/radius, y/radius)
312    }
313}
314
315/// Sorts the radii for you for better results. This is sorted, so it isn't stable (the order changes). 
316/// The packing can change drastically depending on the relative size of the provided radii.
317/// Sorting the radii like this tends to produce a more efficient packing.
318#[derive(Debug, Clone, Default)]
319pub struct CircleSortedPacker {
320    radii: Vec<(usize,f32)>,
321}
322
323impl CircleSortedPacker {
324    /// Adds a circle to the packer.
325    pub fn push(&mut self, radius: f32) {
326        self.radii.push((self.radii.len(),radius));
327        self.radii.sort_by(|a,b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
328    }
329
330    /// This makes a packer, then packs all the radii provided so far in descending order. It returns the circles back in the provided order.
331    pub fn circles(&self) -> Vec<Circle> {
332        let mut circles =  vec![Circle::default(); self.radii.len()];
333        let mut packer = CirclePacker::default();
334        for (_,f) in &self.radii {
335            packer.push(*f);
336        }
337        packer.circles().drain(..).zip(&self.radii).for_each(|(circle, (i,_))| {
338            circles[*i] = circle;
339        });
340        circles
341    }
342
343    /// Same thing as the `circles` method, but embeds it in the provided circle.
344    pub fn circles_in(&self, embedding_circle: &Circle) -> Vec<Circle> {
345        let mut circles =  vec![Circle::default(); self.radii.len()];
346        let mut packer = CirclePacker::default();
347        for (_,f) in &self.radii {
348            packer.push(*f);
349        }
350        packer.circles_in(embedding_circle).drain(..).zip(&self.radii).for_each(|(circle, (i,_))| {
351            circles[*i] = circle;
352        });
353        circles
354    }
355}
356
357
358#[cfg(test)]
359mod tests {
360    use crate::{Circle, CirclePacker};
361    use rand::{prelude::*, rngs::SmallRng};
362    use assert_approx_eq::assert_approx_eq;
363    fn has_nan(circle: &Circle) -> bool {
364        circle.x.is_nan() || circle.y.is_nan() || circle.radius.is_nan()
365    }
366
367    fn front_touches(packer: &CirclePacker) {
368        tracing::info!("Packer: {:?}", packer);
369        for i in 1..packer.front.len() {
370            assert_eq!(i,packer.front_get_next_index(i-1));
371            let cm = &packer.circles[packer.front[i-1]];
372            let cn = &packer.circles[packer.front[i]];
373            assert_approx_eq!(cm.distance(cn), cm.radius + cn.radius, 1e-4f32);
374        }
375        if 1 < packer.front.len() {
376            assert_eq!(0,packer.front_get_next_index(packer.front.len() - 1));
377            let cm = &packer.circles[packer.front[packer.front.len() - 1]];
378            let cn = &packer.circles[packer.front[0]];
379            assert_approx_eq!(cm.distance(cn), cm.radius + cn.radius, 1e-4f32);
380        }
381    }
382    #[tracing_test::traced_test]
383    #[test]
384    pub fn new_node_does_not_intersect_touching() {
385        let mut rng = SmallRng::from_seed([0; 32]);
386        for _ in 0..20 {
387            let mut circle_1 = Circle {
388                x: rng.gen(),
389                y: rng.gen(),
390                radius: rng.gen(),
391            };
392            
393            let mut circle_2 = Circle {
394                x: rng.gen(),
395                y: rng.gen(),
396                radius: 0.0,
397            };
398            while circle_1.distance(&circle_2) < circle_1.radius {
399                circle_1.x = rng.gen();
400                circle_1.y = rng.gen();
401                circle_1.radius = rng.gen();
402                circle_2.x = rng.gen();
403                circle_2.y = rng.gen();
404            }
405            let dist = circle_1.distance(&circle_2);
406            circle_2.radius = dist - circle_1.radius;
407
408            let radius: f32 = rng.gen();
409
410            let (circle_3, circle_4) = Circle::new_node(radius, &circle_1, &circle_2);
411            assert!(!circle_3.x.is_nan(), "Candidate Circle has a NAN");
412            assert!(!circle_3.y.is_nan(), "Candidate Circle has a NAN");
413            assert!(!circle_4.x.is_nan(), "Candidate Circle has a NAN");
414            assert!(!circle_4.y.is_nan(), "Candidate Circle has a NAN");
415            assert_approx_eq!(circle_1.distance(&circle_2), circle_1.radius + circle_2.radius);
416            assert_approx_eq!(circle_1.distance(&circle_3), circle_1.radius + circle_3.radius);
417            assert_approx_eq!(circle_1.distance(&circle_4), circle_1.radius + circle_4.radius);
418            assert_approx_eq!(circle_2.distance(&circle_3), circle_2.radius + circle_3.radius);
419            assert_approx_eq!(circle_2.distance(&circle_4), circle_2.radius + circle_4.radius);
420            assert!(!circle_1.intersect(&circle_2));
421            assert!(!circle_1.intersect(&circle_3));
422            assert!(!circle_1.intersect(&circle_4));
423            assert!(!circle_2.intersect(&circle_3));
424            assert!(!circle_2.intersect(&circle_4));
425        }
426    }
427
428    fn radii_work(radii: &[f32]) {
429        let mut packer = CirclePacker::default();
430        for radius in radii {
431            let span = tracing::info_span!("Radius: {}", radius);
432            let _entered = span.enter();
433            packer.push(*radius);
434            front_touches(&packer);
435            let circles = packer.circles();
436            for i in 0..circles.len() {
437                assert!(!has_nan(&circles[i]), "Circle {} has a nan on radius {}", i, radius);
438                for j in 0..i {
439                    assert!(!circles[i].intersect(&circles[j]), "{} {:?} intersects with {} {:?} on radius {}", i, &circles[i], j, &circles[j], radius);
440                }
441                for j in (i+1)..circles.len() {
442                    assert!(!circles[i].intersect(&circles[j]), "{} {:?} intersects with {} {:?} on radius {}", i, &circles[i], j, &circles[j], radius);
443                }
444            }
445        }
446    }
447
448    #[tracing_test::traced_test]
449    #[test]
450    pub fn it_works() {
451        radii_work(&[50.0, 100.1, 40.2, 30.3, 60.4, 40.5, 80.6, 100.7, 50.8])
452    }
453
454    #[tracing_test::traced_test]
455    #[test]
456    pub fn it_works_2() {
457        radii_work(&[32.0, 38.1, 35.2, 1.3, 49.4, 2.5, 85.6])
458    }
459
460    #[tracing_test::traced_test]
461    #[test]
462    pub fn it_works_3() {
463        radii_work(&[89.0, 96.1, 8.2, 71.3])
464    }
465
466    #[tracing_test::traced_test]
467    #[test]
468    pub fn it_works_4() {
469        radii_work(&[27.0, 56.1, 15.2, 89.3, 91.4, 13.5, 27.6, 86.7])
470    }
471
472    #[tracing_test::traced_test]
473    #[test]
474    pub fn it_works_fuzz() {
475        let mut rng = SmallRng::from_seed([0; 32]);
476        for _ in 0..20 {
477            let mut packer = CirclePacker::default();
478            for _ in 0..50 {
479                let radius: f32 = rng.gen();
480                packer.push(100.0f32 * radius);
481                front_touches(&packer);
482                let circles = packer.circles();
483                for i in 0..circles.len() {
484                    assert!(!has_nan(&circles[i]), "Circle {} has a nan on radius {}", i, radius);
485                    for j in 0..i {
486                        assert!(!circles[i].intersect(&circles[j]), "{} {:?} intersects with {} {:?} on radius {}", i, &circles[i], j, &circles[j], radius);
487                    }
488                    for j in (i+1)..circles.len() {
489                        assert!(!circles[i].intersect(&circles[j]), "{} {:?} intersects with {} {:?} on radius {}", i, &circles[i], j, &circles[j], radius);
490                    }
491                }
492            }
493        }
494    }
495
496    #[tracing_test::traced_test]
497    #[test]
498    pub fn contained() {
499        let mut packer = CirclePacker::default();
500        for radius in [32.0, 38.1, 35.2, 1.3, 49.4, 2.5, 85.6, 84.7, 29.8, 7.9, 31.10, 6.11, 11.12] {
501            packer.push(radius);
502        }
503        let embedding_circle = Circle {x: 10.0, y: 100.0, radius: 350.0};
504        let circles = packer.circles_in(&embedding_circle);
505        for circle in circles {
506            assert!(circle.distance(&embedding_circle) + circle.radius <= 350.0 + 1e-4);
507        }
508    }
509}