svgbob/buffer/fragment_buffer/fragment/
circle.rs

1use crate::{fragment::Bounds, util, Cell, Point};
2use nalgebra::Point2;
3use parry2d::shape::{ConvexPolygon, Polyline};
4use std::{
5    cmp::Ordering,
6    fmt,
7    hash::{Hash, Hasher},
8};
9
10use sauron::{
11    html::attributes::*,
12    svg::{attributes::*, *},
13    Node,
14};
15
16/// TODO: Add an is_broken field when there is a presence of `~` or `!` in the span
17#[derive(Debug, Clone)]
18pub struct Circle {
19    pub radius: f32,
20    pub center: Point,
21    pub is_filled: bool,
22}
23
24impl Hash for Circle {
25    fn hash<H: Hasher>(&self, state: &mut H) {
26        ((self.radius * 2.0) as i32).hash(state);
27    }
28}
29
30impl Circle {
31    pub(crate) fn new(center: Point, radius: f32, is_filled: bool) -> Self {
32        Circle {
33            center,
34            radius,
35            is_filled,
36        }
37    }
38
39    /// the top most point of this circle for sorting.
40    /// center.y - radius
41    fn top_left_bound(&self) -> Point {
42        Point::new(self.center.x - self.radius, self.center.y - self.radius)
43    }
44
45    fn top_right_bound(&self) -> Point {
46        Point::new(self.center.x + self.radius, self.center.y - self.radius)
47    }
48
49    fn bottom_right_bound(&self) -> Point {
50        Point::new(self.center.x + self.radius, self.center.y + self.radius)
51    }
52
53    fn bottom_left_bound(&self) -> Point {
54        Point::new(self.center.x - self.radius, self.center.y + self.radius)
55    }
56
57    /// offset the circles parameter from the arg cell
58    pub(crate) fn absolute_position(&self, cell: Cell) -> Self {
59        Circle {
60            center: cell.absolute_position(self.center),
61            ..*self
62        }
63    }
64
65    pub fn scale(&self, scale: f32) -> Self {
66        Circle {
67            center: self.center.scale(scale),
68            radius: self.radius * scale,
69            ..*self
70        }
71    }
72}
73
74impl Bounds for Circle {
75    fn bounds(&self) -> (Point, Point) {
76        (self.top_left_bound(), self.bottom_right_bound())
77    }
78}
79
80impl fmt::Display for Circle {
81    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
82        write!(f, "C {} {}", self.center, self.radius)
83    }
84}
85
86impl<MSG> From<Circle> for Node<MSG> {
87    fn from(c: Circle) -> Node<MSG> {
88        circle(
89            [
90                cx(c.center.x),
91                cy(c.center.y),
92                r(c.radius),
93                classes_flag([
94                    ("filled", c.is_filled),
95                    ("nofill", !c.is_filled),
96                ]),
97            ],
98            [],
99        )
100    }
101}
102
103impl Eq for Circle {}
104
105///This is needed since circle contains radius which is an f32 which rust doesn't provide trait
106///implementation for Eq
107impl Ord for Circle {
108    fn cmp(&self, other: &Self) -> Ordering {
109        self.mins()
110            .cmp(&other.mins())
111            .then(self.maxs().cmp(&other.maxs()))
112            .then(util::ord(self.radius, other.radius))
113            .then(self.is_filled.cmp(&other.is_filled))
114    }
115}
116
117impl PartialOrd for Circle {
118    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
119        Some(self.cmp(other))
120    }
121}
122
123impl PartialEq for Circle {
124    fn eq(&self, other: &Self) -> bool {
125        self.cmp(other) == Ordering::Equal
126    }
127}
128
129impl From<Circle> for Polyline {
130    fn from(c: Circle) -> Polyline {
131        let points: Vec<Point2<f32>> = extract_circle_points(c.radius, 64)
132            .into_iter()
133            .map(|p| Point2::new(p.x + c.center.x, p.y + c.center.y))
134            .collect();
135
136        Polyline::new(points, None)
137    }
138}
139
140impl From<Circle> for ConvexPolygon {
141    fn from(c: Circle) -> ConvexPolygon {
142        let points: Vec<Point2<f32>> = extract_circle_points(c.radius, 64)
143            .into_iter()
144            .map(|p| Point2::new(p.x + c.center.x, p.y + c.center.y))
145            .collect();
146
147        ConvexPolygon::from_convex_polyline(points)
148            .expect("must create a convex polygon")
149    }
150}
151
152fn extract_circle_points(radius: f32, nsubdivs: u32) -> Vec<Point> {
153    let two_pi = std::f32::consts::TAU;
154    let dtheta = two_pi / nsubdivs as f32;
155    push_xy_arc(radius, nsubdivs, dtheta)
156}
157
158/// Pushes a discretized counterclockwise circle to a buffer.
159/// The circle is contained on the plane spanned by the `x` and `y` axis.
160fn push_xy_arc(radius: f32, nsubdiv: u32, dtheta: f32) -> Vec<Point> {
161    let mut out: Vec<Point> = vec![];
162    let mut curr_theta: f32 = 0.0;
163
164    for _ in 0..nsubdiv {
165        let x = curr_theta.cos() * radius;
166        let y = curr_theta.sin() * radius;
167        out.push(Point::new(x, y));
168
169        curr_theta += dtheta;
170    }
171    out
172}