svgbob/buffer/fragment_buffer/fragment/
circle.rs1use 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#[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 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 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
105impl 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
158fn 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}