1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
use std::fmt;

use anyhow::Result;
use serde::{Deserialize, Serialize};

use crate::{Angle, Bounds, Distance, Polygon, Pt2D, Ring};

const TRIANGLES_PER_CIRCLE: usize = 60;

/// A circle, defined by a center and radius.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Circle {
    pub center: Pt2D,
    pub radius: Distance,
}

impl Circle {
    /// Creates a circle.
    pub fn new(center: Pt2D, radius: Distance) -> Circle {
        Circle { center, radius }
    }

    /// True if the point is inside the circle.
    pub fn contains_pt(&self, pt: Pt2D) -> bool {
        // avoid sqrt by squaring radius instead
        (pt.x() - self.center.x()).powi(2) + (pt.y() - self.center.y()).powi(2)
            < self.radius.inner_meters().powi(2)
    }

    /// Get the boundary containing this circle.
    pub fn get_bounds(&self) -> Bounds {
        Bounds {
            min_x: self.center.x() - self.radius.inner_meters(),
            max_x: self.center.x() + self.radius.inner_meters(),
            min_y: self.center.y() - self.radius.inner_meters(),
            max_y: self.center.y() + self.radius.inner_meters(),
        }
    }

    /// Renders the circle as a polygon.
    pub fn to_polygon(&self) -> Polygon {
        self.to_ring().into_polygon()
    }

    /// Renders some percent, between [0, 1], of the circle as a polygon. The polygon starts from 0
    /// degrees. Be warned the resulting polygon doesn't have a ring as its boundary!
    pub fn to_partial_polygon(&self, percent_full: f64) -> Polygon {
        #![allow(clippy::float_cmp)]
        assert!((0. ..=1.).contains(&percent_full));
        let mut pts = vec![self.center];
        let mut indices = Vec::new();
        for i in 0..TRIANGLES_PER_CIRCLE {
            pts.push(self.center.project_away(
                self.radius,
                Angle::degrees((i as f64) / (TRIANGLES_PER_CIRCLE as f64) * percent_full * 360.0),
            ));
            indices.push(0);
            indices.push(i + 1);
            if i != TRIANGLES_PER_CIRCLE - 1 {
                indices.push(i + 2);
            } else if percent_full == 1.0 {
                indices.push(1);
            } else {
                indices.pop();
                indices.pop();
            }
        }
        Polygon::precomputed(pts, indices)
    }

    /// Returns the ring around the circle.
    fn to_ring(&self) -> Ring {
        let mut pts: Vec<Pt2D> = (0..=TRIANGLES_PER_CIRCLE)
            .map(|i| {
                self.center.project_away(
                    self.radius,
                    Angle::degrees((i as f64) / (TRIANGLES_PER_CIRCLE as f64) * 360.0),
                )
            })
            .collect();
        // With some radii, we get duplicate adjacent points
        pts.dedup();
        Ring::must_new(pts)
    }

    /// Creates an outline around the circle, strictly contained with the circle's original radius.
    pub fn to_outline(&self, thickness: Distance) -> Result<Polygon> {
        if self.radius <= thickness {
            bail!(
                "Can't make Circle outline with radius {} and thickness {}",
                self.radius,
                thickness
            );
        }

        let bigger = self.to_ring();
        let smaller = Circle::new(self.center, self.radius - thickness).to_ring();
        Ok(Polygon::with_holes(bigger, vec![smaller]))
    }
}

impl fmt::Display for Circle {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Circle({}, {})", self.center, self.radius)
    }
}