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