1use crate::corner::SmoothCorner;
2use crate::error::SmoothError;
3use crate::geometry::Point;
4use crate::math::EPSILON;
5use crate::path::{CubicSegment, SmoothPath};
6
7#[derive(Debug, Clone, PartialEq)]
9pub struct SmoothFrame {
10 points: Vec<Point>,
11 radius: f64,
12 smoothing: f64,
13}
14
15impl SmoothFrame {
16 #[must_use]
20 pub fn closed(points: impl IntoIterator<Item = Point>) -> Self {
21 Self {
22 points: points.into_iter().collect(),
23 radius: 0.0,
24 smoothing: 0.0,
25 }
26 }
27
28 #[must_use]
30 pub fn with_radius(mut self, radius: f64) -> Self {
31 self.radius = radius;
32 self
33 }
34
35 #[must_use]
37 pub fn with_smoothing(mut self, smoothing: f64) -> Self {
38 self.smoothing = smoothing;
39 self
40 }
41
42 #[must_use]
44 pub fn points(&self) -> &[Point] {
45 &self.points
46 }
47
48 pub fn to_path(&self) -> Result<SmoothPath, SmoothError> {
50 self.validate_frame()?;
51
52 if self.radius < 0.0 {
53 return Err(SmoothError::NegativeInput);
54 }
55 if !self.radius.is_finite() || !self.smoothing.is_finite() {
56 return Err(SmoothError::NonFiniteInput);
57 }
58
59 if self.radius <= EPSILON {
60 return Ok(self.sharp_path());
61 }
62
63 let mut corners = Vec::with_capacity(self.points.len());
64 for index in 0..self.points.len() {
65 let prev = self.points[(index + self.points.len() - 1) % self.points.len()];
66 let origin = self.points[index];
67 let next = self.points[(index + 1) % self.points.len()];
68 let incoming = prev - origin;
69 let outgoing = next - origin;
70 let incoming_length = incoming.length();
71 let outgoing_length = outgoing.length();
72 let corner = SmoothCorner::new(origin, incoming, outgoing)
73 .with_radius(self.radius)
74 .with_smoothing(self.smoothing)
75 .with_limits(incoming_length / 2.0, outgoing_length / 2.0);
76 corners.push((corner.geometry()?, corner.to_cubics()?));
77 }
78
79 if corners
80 .iter()
81 .all(|(geometry, _)| geometry.radius <= EPSILON)
82 {
83 return Ok(self.sharp_path());
84 }
85
86 let mut path = SmoothPath::new();
87 path.move_to(corners[0].0.end);
88
89 for (geometry, cubics) in corners.iter().skip(1) {
90 path.line_to(geometry.start);
91 push_cubics(&mut path, cubics);
92 }
93
94 path.line_to(corners[0].0.start);
95 push_cubics(&mut path, &corners[0].1);
96 path.close();
97
98 Ok(path)
99 }
100
101 fn sharp_path(&self) -> SmoothPath {
102 let mut path = SmoothPath::new();
103 path.move_to(self.points[0]);
104 for point in self.points.iter().copied().skip(1) {
105 path.line_to(point);
106 }
107 path.close();
108 path
109 }
110
111 fn validate_frame(&self) -> Result<(), SmoothError> {
112 if self.points.len() < 3 {
113 return Err(SmoothError::TooFewPoints);
114 }
115 if self.points.iter().any(|point| !point.is_finite()) {
116 return Err(SmoothError::NonFiniteInput);
117 }
118
119 let mut area = 0.0;
120 for index in 0..self.points.len() {
121 let a = self.points[index];
122 let b = self.points[(index + 1) % self.points.len()];
123 if (b - a).length() <= EPSILON {
124 return Err(SmoothError::DegenerateFrame);
125 }
126 area += a.x * b.y - b.x * a.y;
127 }
128 if area.abs() <= EPSILON {
129 return Err(SmoothError::DegenerateFrame);
130 }
131 if has_self_intersection(&self.points) {
132 return Err(SmoothError::SelfIntersectingFrame);
133 }
134
135 let mut turn_sign = 0.0_f64;
136 for index in 0..self.points.len() {
137 let prev = self.points[(index + self.points.len() - 1) % self.points.len()];
138 let origin = self.points[index];
139 let next = self.points[(index + 1) % self.points.len()];
140 let incoming = (prev - origin)
141 .normalized()
142 .ok_or(SmoothError::DegenerateFrame)?;
143 let outgoing = (next - origin)
144 .normalized()
145 .ok_or(SmoothError::DegenerateFrame)?;
146 let cross = incoming.cross(outgoing);
147 if cross.abs() <= EPSILON {
148 return Err(SmoothError::DegenerateFrame);
149 }
150 let sign = cross.signum();
151 if turn_sign == 0.0 {
152 turn_sign = sign;
153 } else if sign != turn_sign {
154 return Err(SmoothError::ConcaveFrame);
155 }
156 }
157
158 Ok(())
159 }
160}
161
162fn push_cubics(path: &mut SmoothPath, cubics: &[CubicSegment]) {
163 for cubic in cubics {
164 path.cubic_to(cubic.ctrl1, cubic.ctrl2, cubic.to);
165 }
166}
167
168fn has_self_intersection(points: &[Point]) -> bool {
169 for first in 0..points.len() {
170 let first_start = points[first];
171 let first_end = points[(first + 1) % points.len()];
172
173 for second in (first + 1)..points.len() {
174 if are_adjacent_edges(first, second, points.len()) {
175 continue;
176 }
177
178 let second_start = points[second];
179 let second_end = points[(second + 1) % points.len()];
180 if segments_intersect(first_start, first_end, second_start, second_end) {
181 return true;
182 }
183 }
184 }
185
186 false
187}
188
189fn are_adjacent_edges(first: usize, second: usize, count: usize) -> bool {
190 first == second || (first + 1) % count == second || (second + 1) % count == first
191}
192
193fn segments_intersect(a: Point, b: Point, c: Point, d: Point) -> bool {
194 let ab_c = orientation(a, b, c);
195 let ab_d = orientation(a, b, d);
196 let cd_a = orientation(c, d, a);
197 let cd_b = orientation(c, d, b);
198
199 if ab_c.abs() <= EPSILON && point_on_segment(c, a, b) {
200 return true;
201 }
202 if ab_d.abs() <= EPSILON && point_on_segment(d, a, b) {
203 return true;
204 }
205 if cd_a.abs() <= EPSILON && point_on_segment(a, c, d) {
206 return true;
207 }
208 if cd_b.abs() <= EPSILON && point_on_segment(b, c, d) {
209 return true;
210 }
211
212 ab_c.signum() != ab_d.signum() && cd_a.signum() != cd_b.signum()
213}
214
215fn orientation(a: Point, b: Point, c: Point) -> f64 {
216 (b - a).cross(c - a)
217}
218
219fn point_on_segment(point: Point, start: Point, end: Point) -> bool {
220 point.x >= start.x.min(end.x) - EPSILON
221 && point.x <= start.x.max(end.x) + EPSILON
222 && point.y >= start.y.min(end.y) - EPSILON
223 && point.y <= start.y.max(end.y) + EPSILON
224}