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
use std::fmt;

use serde::{Deserialize, Serialize};

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

const TRIANGLES_PER_CIRCLE: usize = 60;

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Circle {
    pub center: Pt2D,
    pub radius: Distance,
}

impl Circle {
    pub fn new(center: Pt2D, radius: Distance) -> Circle {
        Circle { center, radius }
    }

    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)
    }

    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(),
        }
    }

    pub fn to_polygon(&self) -> Polygon {
        self.to_partial_polygon(1.0)
    }

    pub fn to_partial_polygon(&self, percent_full: f64) -> Polygon {
        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::new_degs((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)
    }

    fn to_ring(&self) -> Ring {
        Ring::must_new(
            (0..=TRIANGLES_PER_CIRCLE)
                .map(|i| {
                    self.center.project_away(
                        self.radius,
                        Angle::new_degs((i as f64) / (TRIANGLES_PER_CIRCLE as f64) * 360.0),
                    )
                })
                .collect(),
        )
    }

    pub fn outline(center: Pt2D, radius: Distance, thickness: Distance) -> Result<Polygon, String> {
        if radius <= thickness {
            return Err(format!(
                "Can't make Circle outline with radius {} and thickness {}",
                radius, thickness
            ));
        }

        let bigger = Circle::new(center, radius).to_ring();
        let smaller = Circle::new(center, 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)
    }
}