1use std::f64::consts::PI;
2
3use crate::error::SmoothError;
4use crate::geometry::{Point, Vector};
5use crate::math::{clamp01, EPSILON};
6use crate::path::CubicSegment;
7
8#[derive(Debug, Clone, Copy, PartialEq)]
10pub struct SmoothCornerGeometry {
11 pub origin: Point,
13 pub incoming_axis: Vector,
15 pub outgoing_axis: Vector,
17 pub radius: f64,
19 pub smoothing: f64,
21 pub incoming_limit: f64,
23 pub outgoing_limit: f64,
25 pub angle: f64,
27 pub base_tangent: f64,
29 pub incoming_influence: f64,
31 pub outgoing_influence: f64,
33 pub alpha0: f64,
35 pub alpha1: f64,
37 pub middle_arc_angle: f64,
39 pub start: Point,
41 pub end: Point,
43}
44
45#[derive(Debug, Clone, Copy, PartialEq)]
47pub struct SmoothCorner {
48 origin: Point,
49 incoming_axis: Vector,
50 outgoing_axis: Vector,
51 radius: f64,
52 smoothing: f64,
53 incoming_limit: f64,
54 outgoing_limit: f64,
55}
56
57impl SmoothCorner {
58 #[must_use]
62 pub fn new(origin: Point, incoming_axis: Vector, outgoing_axis: Vector) -> Self {
63 Self {
64 origin,
65 incoming_axis,
66 outgoing_axis,
67 radius: 0.0,
68 smoothing: 0.0,
69 incoming_limit: f64::INFINITY,
70 outgoing_limit: f64::INFINITY,
71 }
72 }
73
74 #[must_use]
76 pub fn with_radius(mut self, radius: f64) -> Self {
77 self.radius = radius;
78 self
79 }
80
81 #[must_use]
83 pub fn with_smoothing(mut self, smoothing: f64) -> Self {
84 self.smoothing = smoothing;
85 self
86 }
87
88 #[must_use]
90 pub fn with_limits(mut self, incoming_limit: f64, outgoing_limit: f64) -> Self {
91 self.incoming_limit = incoming_limit;
92 self.outgoing_limit = outgoing_limit;
93 self
94 }
95
96 pub fn geometry(&self) -> Result<SmoothCornerGeometry, SmoothError> {
98 if !self.origin.is_finite()
99 || !self.incoming_axis.is_finite()
100 || !self.outgoing_axis.is_finite()
101 || !self.radius.is_finite()
102 || !self.smoothing.is_finite()
103 || self.incoming_limit.is_nan()
104 || self.outgoing_limit.is_nan()
105 {
106 return Err(SmoothError::NonFiniteInput);
107 }
108 if self.radius < 0.0 || self.incoming_limit < 0.0 || self.outgoing_limit < 0.0 {
109 return Err(SmoothError::NegativeInput);
110 }
111
112 let incoming_axis = self
113 .incoming_axis
114 .normalized()
115 .ok_or(SmoothError::DegenerateAxis)?;
116 let outgoing_axis = self
117 .outgoing_axis
118 .normalized()
119 .ok_or(SmoothError::DegenerateAxis)?;
120 let angle = incoming_axis
121 .angle_between(outgoing_axis)
122 .ok_or(SmoothError::DegenerateAxis)?;
123
124 if angle <= EPSILON || PI - angle <= EPSILON {
125 return Err(SmoothError::InvalidAngle);
126 }
127
128 let smoothing = clamp01(self.smoothing);
129 let max_limit = self.incoming_limit.min(self.outgoing_limit);
130 let max_radius = max_limit * (angle / 2.0).tan();
131 let radius = self.radius.min(max_radius);
132
133 if radius <= EPSILON {
134 return Ok(SmoothCornerGeometry {
135 origin: self.origin,
136 incoming_axis,
137 outgoing_axis,
138 radius: 0.0,
139 smoothing,
140 incoming_limit: self.incoming_limit,
141 outgoing_limit: self.outgoing_limit,
142 angle,
143 base_tangent: 0.0,
144 incoming_influence: 0.0,
145 outgoing_influence: 0.0,
146 alpha0: 0.0,
147 alpha1: 0.0,
148 middle_arc_angle: angle,
149 start: self.origin,
150 end: self.origin,
151 });
152 }
153
154 let base_tangent = radius / (angle / 2.0).tan();
155 let raw_influence = (1.0 + smoothing) * base_tangent;
156 let incoming_influence = raw_influence.min(self.incoming_limit);
157 let outgoing_influence = raw_influence.min(self.outgoing_limit);
158
159 let alpha0 = clamp01(incoming_influence / base_tangent - 1.0) * angle / 2.0;
160 let alpha1 = clamp01(outgoing_influence / base_tangent - 1.0) * angle / 2.0;
161 let middle_arc_angle = (angle - alpha0 - alpha1).max(0.0);
162
163 Ok(SmoothCornerGeometry {
164 origin: self.origin,
165 incoming_axis,
166 outgoing_axis,
167 radius,
168 smoothing,
169 incoming_limit: self.incoming_limit,
170 outgoing_limit: self.outgoing_limit,
171 angle,
172 base_tangent,
173 incoming_influence,
174 outgoing_influence,
175 alpha0,
176 alpha1,
177 middle_arc_angle,
178 start: self.origin + incoming_axis * incoming_influence,
179 end: self.origin + outgoing_axis * outgoing_influence,
180 })
181 }
182
183 pub fn to_cubics(&self) -> Result<Vec<CubicSegment>, SmoothError> {
187 let geometry = self.geometry()?;
188 if geometry.radius <= EPSILON {
189 return Ok(Vec::new());
190 }
191
192 let center = geometry.origin
193 + (geometry.incoming_axis + geometry.outgoing_axis)
194 .normalized()
195 .ok_or(SmoothError::InvalidAngle)?
196 * (geometry.radius / (geometry.angle / 2.0).sin());
197 let incoming_tangent = geometry.origin + geometry.incoming_axis * geometry.base_tangent;
198 let e0 = (incoming_tangent - center) / geometry.radius;
199 let e1 = -geometry.incoming_axis;
200
201 let p1 = circle_point(center, geometry.radius, e0, e1, geometry.alpha0);
202 let p2 = circle_point(
203 center,
204 geometry.radius,
205 e0,
206 e1,
207 geometry.angle - geometry.alpha1,
208 );
209
210 let tangent0 = geometry.base_tangent - geometry.radius * (geometry.alpha0 / 2.0).tan();
211 let handle0 = (geometry.incoming_influence - tangent0) / 3.0;
212
213 let tangent1 = geometry.base_tangent - geometry.radius * (geometry.alpha1 / 2.0).tan();
214 let handle1 = (geometry.outgoing_influence - tangent1) / 3.0;
215
216 let arc_handle = if geometry.middle_arc_angle <= EPSILON {
217 0.0
218 } else {
219 (4.0 / 3.0) * (geometry.middle_arc_angle / 4.0).tan() * geometry.radius
220 };
221
222 let arc_tangent0 = circle_tangent(e0, e1, geometry.alpha0);
223 let arc_tangent1 = circle_tangent(e0, e1, geometry.angle - geometry.alpha1);
224
225 let c1 = CubicSegment {
226 from: geometry.start,
227 ctrl1: geometry.origin
228 + geometry.incoming_axis * (geometry.incoming_influence - 2.0 * handle0),
229 ctrl2: geometry.origin + geometry.incoming_axis * tangent0,
230 to: p1,
231 };
232 let c2 = CubicSegment {
233 from: p1,
234 ctrl1: p1 + arc_tangent0 * arc_handle,
235 ctrl2: p2 - arc_tangent1 * arc_handle,
236 to: p2,
237 };
238 let c3 = CubicSegment {
239 from: p2,
240 ctrl1: geometry.origin + geometry.outgoing_axis * tangent1,
241 ctrl2: geometry.origin + geometry.outgoing_axis * (tangent1 + handle1),
242 to: geometry.end,
243 };
244
245 Ok(vec![c1, c2, c3])
246 }
247}
248
249fn circle_point(center: Point, radius: f64, e0: Vector, e1: Vector, angle: f64) -> Point {
250 center + e0 * (radius * angle.cos()) + e1 * (radius * angle.sin())
251}
252
253fn circle_tangent(e0: Vector, e1: Vector, angle: f64) -> Vector {
254 -e0 * angle.sin() + e1 * angle.cos()
255}