1pub(super) const DISTANCE_EPSILON: f32 = 1e-4;
19pub(super) const ANGLE_EPSILON: f32 = 1e-6;
21const PI: f32 = std::f32::consts::PI;
22
23pub(super) fn cos(x: f32) -> f32 {
26 f64::from(x).cos() as f32
27}
28pub(super) fn sin(x: f32) -> f32 {
29 f64::from(x).sin() as f32
30}
31pub(super) fn atan2(y: f32, x: f32) -> f32 {
32 f64::from(y).atan2(f64::from(x)) as f32
33}
34
35pub(super) fn interpolate(start: f32, stop: f32, fraction: f32) -> f32 {
36 (1. - fraction) * start + fraction * stop
37}
38
39pub(super) fn positive_modulo(num: f32, modulus: f32) -> f32 {
40 (num % modulus + modulus) % modulus
41}
42
43#[derive(Clone, Copy, Debug, Default, PartialEq)]
45pub(super) struct Pt {
46 pub x: f32,
47 pub y: f32,
48}
49
50pub(super) const fn pt(x: f32, y: f32) -> Pt {
51 Pt { x, y }
52}
53
54impl Pt {
55 pub fn distance(self) -> f32 {
56 (self.x * self.x + self.y * self.y).sqrt()
57 }
58 pub fn distance_squared(self) -> f32 {
59 self.x * self.x + self.y * self.y
60 }
61 fn dot(self, other: Self) -> f32 {
62 self.x * other.x + self.y * other.y
63 }
64 fn dot_xy(self, x: f32, y: f32) -> f32 {
65 self.x * x + self.y * y
66 }
67 fn clockwise(self, other: Self) -> bool {
68 self.x * other.y - self.y * other.x > 0.
69 }
70 fn direction(self) -> Self {
72 let distance = self.distance();
73 if distance > 0. { self / distance } else { self }
74 }
75 fn rotate90(self) -> Self {
76 pt(-self.y, self.x)
77 }
78}
79
80impl std::ops::Add for Pt {
81 type Output = Self;
82 fn add(self, other: Self) -> Self {
83 pt(self.x + other.x, self.y + other.y)
84 }
85}
86impl std::ops::Sub for Pt {
87 type Output = Self;
88 fn sub(self, other: Self) -> Self {
89 pt(self.x - other.x, self.y - other.y)
90 }
91}
92impl std::ops::Mul<f32> for Pt {
93 type Output = Self;
94 fn mul(self, operand: f32) -> Self {
95 pt(self.x * operand, self.y * operand)
96 }
97}
98impl std::ops::Div<f32> for Pt {
99 type Output = Self;
100 fn div(self, operand: f32) -> Self {
101 pt(self.x / operand, self.y / operand)
102 }
103}
104
105fn direction_of(x: f32, y: f32) -> Pt {
106 pt(x, y).direction()
107}
108
109fn lerp_pt(start: Pt, stop: Pt, fraction: f32) -> Pt {
110 pt(
111 interpolate(start.x, stop.x, fraction),
112 interpolate(start.y, stop.y, fraction),
113 )
114}
115
116#[derive(Clone, Copy, Debug, Default, PartialEq)]
119pub struct Cubic {
120 pub points: [f32; 8],
122}
123
124impl Cubic {
125 pub(super) const fn new(points: [f32; 8]) -> Self {
126 Self { points }
127 }
128
129 fn from_points(anchor0: Pt, control0: Pt, control1: Pt, anchor1: Pt) -> Self {
130 Self::new([
131 anchor0.x, anchor0.y, control0.x, control0.y, control1.x, control1.y, anchor1.x,
132 anchor1.y,
133 ])
134 }
135
136 pub fn anchor0(&self) -> (f32, f32) {
138 (self.points[0], self.points[1])
139 }
140 pub fn control0(&self) -> (f32, f32) {
142 (self.points[2], self.points[3])
143 }
144 pub fn control1(&self) -> (f32, f32) {
146 (self.points[4], self.points[5])
147 }
148 pub fn anchor1(&self) -> (f32, f32) {
150 (self.points[6], self.points[7])
151 }
152
153 pub fn point_at(&self, t: f32) -> (f32, f32) {
155 let p = self.point_on_curve(t);
156 (p.x, p.y)
157 }
158
159 pub(super) fn point_on_curve(&self, t: f32) -> Pt {
160 let u = 1. - t;
161 let [a0x, a0y, c0x, c0y, c1x, c1y, a1x, a1y] = self.points;
162 pt(
163 a0x * (u * u * u) + c0x * (3. * t * u * u) + c1x * (3. * t * t * u) + a1x * (t * t * t),
164 a0y * (u * u * u) + c0y * (3. * t * u * u) + c1y * (3. * t * t * u) + a1y * (t * t * t),
165 )
166 }
167
168 fn zero_length(&self) -> bool {
169 (self.points[0] - self.points[6]).abs() < DISTANCE_EPSILON
170 && (self.points[1] - self.points[7]).abs() < DISTANCE_EPSILON
171 }
172
173 pub(super) fn bounds(&self, approximate: bool) -> [f32; 4] {
176 let [a0x, a0y, c0x, c0y, c1x, c1y, a1x, a1y] = self.points;
177 if self.zero_length() {
178 return [a0x, a0y, a0x, a0y];
179 }
180 let (mut min_x, mut max_x) = (a0x.min(a1x), a0x.max(a1x));
181 let (mut min_y, mut max_y) = (a0y.min(a1y), a0y.max(a1y));
182 if approximate {
183 return [
184 min_x.min(c0x.min(c1x)),
185 min_y.min(c0y.min(c1y)),
186 max_x.max(c0x.max(c1x)),
187 max_y.max(c0y.max(c1y)),
188 ];
189 }
190 let extrema = |a0: f32, c0: f32, c1: f32, a1: f32, each: &mut dyn FnMut(f32)| {
192 let a = -a0 + 3. * c0 - 3. * c1 + a1;
193 let b = 2. * a0 - 4. * c0 + 2. * c1;
194 let c = -a0 + c0;
195 if a.abs() < DISTANCE_EPSILON {
196 if b != 0. {
197 each(2. * c / (-2. * b));
198 }
199 } else {
200 let s = b * b - 4. * a * c;
201 if s >= 0. {
202 each((-b + s.sqrt()) / (2. * a));
203 each((-b - s.sqrt()) / (2. * a));
204 }
205 }
206 };
207 extrema(a0x, c0x, c1x, a1x, &mut |t| {
208 if (0.0..=1.0).contains(&t) {
209 let x = self.point_on_curve(t).x;
210 min_x = min_x.min(x);
211 max_x = max_x.max(x);
212 }
213 });
214 extrema(a0y, c0y, c1y, a1y, &mut |t| {
215 if (0.0..=1.0).contains(&t) {
216 let y = self.point_on_curve(t).y;
217 min_y = min_y.min(y);
218 max_y = max_y.max(y);
219 }
220 });
221 [min_x, min_y, max_x, max_y]
222 }
223
224 pub(super) fn split(&self, t: f32) -> (Self, Self) {
225 let u = 1. - t;
226 let at = self.point_on_curve(t);
227 let [a0x, a0y, c0x, c0y, c1x, c1y, a1x, a1y] = self.points;
228 (
229 Self::new([
230 a0x,
231 a0y,
232 a0x * u + c0x * t,
233 a0y * u + c0y * t,
234 a0x * (u * u) + c0x * (2. * u * t) + c1x * (t * t),
235 a0y * (u * u) + c0y * (2. * u * t) + c1y * (t * t),
236 at.x,
237 at.y,
238 ]),
239 Self::new([
240 at.x,
241 at.y,
242 c0x * (u * u) + c1x * (2. * u * t) + a1x * (t * t),
243 c0y * (u * u) + c1y * (2. * u * t) + a1y * (t * t),
244 c1x * u + a1x * t,
245 c1y * u + a1y * t,
246 a1x,
247 a1y,
248 ]),
249 )
250 }
251
252 fn reversed(&self) -> Self {
253 let [a0x, a0y, c0x, c0y, c1x, c1y, a1x, a1y] = self.points;
254 Self::new([a1x, a1y, c1x, c1y, c0x, c0y, a0x, a0y])
255 }
256
257 fn straight_line(x0: f32, y0: f32, x1: f32, y1: f32) -> Self {
258 Self::new([
259 x0,
260 y0,
261 interpolate(x0, x1, 1. / 3.),
262 interpolate(y0, y1, 1. / 3.),
263 interpolate(x0, x1, 2. / 3.),
264 interpolate(y0, y1, 2. / 3.),
265 x1,
266 y1,
267 ])
268 }
269
270 fn circular_arc(center: Pt, x0: f32, y0: f32, x1: f32, y1: f32) -> Self {
271 let p0d = direction_of(x0 - center.x, y0 - center.y);
272 let p1d = direction_of(x1 - center.x, y1 - center.y);
273 let rotated_p0 = p0d.rotate90();
274 let rotated_p1 = p1d.rotate90();
275 let clockwise = rotated_p0.dot_xy(x1 - center.x, y1 - center.y) >= 0.;
276 let cosa = p0d.dot(p1d);
277 if cosa > 0.999 {
278 return Self::straight_line(x0, y0, x1, y1);
279 }
280 let k = pt(x0 - center.x, y0 - center.y).distance() * 4. / 3.
281 * ((2. * (1. - cosa)).max(0.).sqrt() - (1. - cosa * cosa).max(0.).sqrt())
282 / (1. - cosa)
283 * if clockwise { 1. } else { -1. };
284 Self::new([
285 x0,
286 y0,
287 x0 + rotated_p0.x * k,
288 y0 + rotated_p0.y * k,
289 x1 - rotated_p1.x * k,
290 y1 - rotated_p1.y * k,
291 x1,
292 y1,
293 ])
294 }
295
296 fn transformed(&self, f: &impl Fn(f32, f32) -> (f32, f32)) -> Self {
297 let mut points = self.points;
298 for [x, y] in points.as_chunks_mut::<2>().0 {
299 (*x, *y) = f(*x, *y);
300 }
301 Self::new(points)
302 }
303}
304
305#[derive(Clone, Copy, Debug, Default, PartialEq)]
307pub struct CornerRounding {
308 pub radius: f32,
310 pub smoothing: f32,
313}
314
315impl CornerRounding {
316 pub const UNROUNDED: Self = Self::new(0.);
318
319 pub const fn new(radius: f32) -> Self {
321 Self {
322 radius,
323 smoothing: 0.,
324 }
325 }
326
327 pub const fn smoothed(radius: f32, smoothing: f32) -> Self {
329 Self { radius, smoothing }
330 }
331}
332
333#[derive(Clone, Debug)]
336pub(super) struct Feature {
337 pub cubics: Vec<Cubic>,
338 pub corner: Option<bool>,
340}
341
342#[derive(Clone, Debug)]
345pub struct RoundedPolygon {
346 pub(super) features: Vec<Feature>,
347 center: Pt,
348 cubics: Vec<Cubic>,
349}
350
351impl RoundedPolygon {
352 fn from_features(features: Vec<Feature>, center: Pt) -> Self {
353 let cubics = flatten(&features, center);
354 Self {
355 features,
356 center,
357 cubics,
358 }
359 }
360
361 pub fn from_vertices(
368 vertices: &[(f32, f32)],
369 rounding: CornerRounding,
370 per_vertex: Option<&[CornerRounding]>,
371 center: Option<(f32, f32)>,
372 ) -> Option<Self> {
373 let n = vertices.len();
374 if n < 3
375 || vertices
376 .iter()
377 .any(|v| !v.0.is_finite() || !v.1.is_finite())
378 || per_vertex.is_some_and(|rounds| rounds.len() != n)
379 {
380 return None;
381 }
382 let at = |i: usize| pt(vertices[i].0, vertices[i].1);
383 let corners: Vec<RoundedCorner> = (0..n)
384 .map(|i| {
385 RoundedCorner::new(
386 at((i + n - 1) % n),
387 at(i),
388 at((i + 1) % n),
389 per_vertex.map_or(rounding, |rounds| rounds[i]),
390 )
391 })
392 .collect();
393
394 let cut_adjusts: Vec<(f32, f32)> = (0..n)
397 .map(|i| {
398 let (corner, next) = (&corners[i], &corners[(i + 1) % n]);
399 let expected_round_cut = corner.expected_round_cut + next.expected_round_cut;
400 let expected_cut = corner.expected_cut() + next.expected_cut();
401 let side = (at(i) - at((i + 1) % n)).distance();
402 if expected_round_cut > side {
403 (side / expected_round_cut, 0.)
404 } else if expected_cut > side {
405 (
406 1.,
407 (side - expected_round_cut) / (expected_cut - expected_round_cut),
408 )
409 } else {
410 (1., 1.)
411 }
412 })
413 .collect();
414
415 let corner_cubics: Vec<Vec<Cubic>> = (0..n)
416 .map(|i| {
417 let allowed = |delta: usize| {
418 let (round_ratio, cut_ratio) = cut_adjusts[(i + n - 1 + delta) % n];
419 corners[i].expected_round_cut * round_ratio
420 + (corners[i].expected_cut() - corners[i].expected_round_cut) * cut_ratio
421 };
422 corners[i].cubics(allowed(0), allowed(1))
423 })
424 .collect();
425
426 let mut features = Vec::with_capacity(n * 2);
427 for i in 0..n {
428 let (previous, current, next) = (at((i + n - 1) % n), at(i), at((i + 1) % n));
429 features.push(Feature {
430 cubics: corner_cubics[i].clone(),
431 corner: Some((current - previous).clockwise(next - current)),
432 });
433 let (end, start) = (
434 corner_cubics[i].last().copied().unwrap_or_default(),
435 corner_cubics[(i + 1) % n]
436 .first()
437 .copied()
438 .unwrap_or_default(),
439 );
440 features.push(Feature {
441 cubics: vec![Cubic::straight_line(
442 end.points[6],
443 end.points[7],
444 start.points[0],
445 start.points[1],
446 )],
447 corner: None,
448 });
449 }
450 let center = center.map_or_else(
451 || {
452 let (sum_x, sum_y) = vertices
453 .iter()
454 .fold((0_f32, 0_f32), |(x, y), v| (x + v.0, y + v.1));
455 pt(sum_x / n as f32, sum_y / n as f32)
456 },
457 |(x, y)| pt(x, y),
458 );
459 Some(Self::from_features(features, center))
460 }
461
462 pub fn regular(
465 sides: usize,
466 radius: f32,
467 center: (f32, f32),
468 rounding: CornerRounding,
469 per_vertex: Option<&[CornerRounding]>,
470 ) -> Option<Self> {
471 let vertices: Vec<(f32, f32)> = (0..sides)
472 .map(|i| {
473 let angle = PI / sides as f32 * 2. * i as f32;
474 (
475 cos(angle) * radius + center.0,
476 sin(angle) * radius + center.1,
477 )
478 })
479 .collect();
480 Self::from_vertices(&vertices, rounding, per_vertex, Some(center))
481 }
482
483 pub fn circle(sides: usize, radius: f32, center: (f32, f32)) -> Option<Self> {
486 if sides < 3 {
487 return None;
488 }
489 let polygon_radius = radius / cos(PI / sides as f32);
490 Self::regular(
491 sides,
492 polygon_radius,
493 center,
494 CornerRounding::new(radius),
495 None,
496 )
497 }
498
499 pub fn rectangle(
502 width: f32,
503 height: f32,
504 rounding: CornerRounding,
505 per_vertex: Option<&[CornerRounding]>,
506 center: (f32, f32),
507 ) -> Option<Self> {
508 let (left, top) = (center.0 - width / 2., center.1 - height / 2.);
509 let (right, bottom) = (center.0 + width / 2., center.1 + height / 2.);
510 Self::from_vertices(
511 &[(right, bottom), (left, bottom), (left, top), (right, top)],
512 rounding,
513 per_vertex,
514 Some(center),
515 )
516 }
517
518 pub fn star(
522 points: usize,
523 radius: f32,
524 inner_radius: f32,
525 rounding: CornerRounding,
526 inner_rounding: Option<CornerRounding>,
527 center: (f32, f32),
528 ) -> Option<Self> {
529 if radius.is_nan() || inner_radius.is_nan() {
530 return None;
531 }
532 if radius <= 0. || inner_radius <= 0. || inner_radius >= radius {
533 return None;
534 }
535 let mut vertices = Vec::with_capacity(points * 2);
536 for i in 0..points {
537 let angle = PI / points as f32 * 2. * i as f32;
538 vertices.push((
539 cos(angle) * radius + center.0,
540 sin(angle) * radius + center.1,
541 ));
542 let angle = PI / points as f32 * (2 * i + 1) as f32;
543 vertices.push((
544 cos(angle) * inner_radius + center.0,
545 sin(angle) * inner_radius + center.1,
546 ));
547 }
548 let per_vertex: Option<Vec<CornerRounding>> =
549 inner_rounding.map(|inner| (0..points).flat_map(|_| [rounding, inner]).collect());
550 Self::from_vertices(&vertices, rounding, per_vertex.as_deref(), Some(center))
551 }
552
553 pub fn pill(width: f32, height: f32, smoothing: f32, center: (f32, f32)) -> Option<Self> {
556 if width.is_nan() || height.is_nan() || width <= 0. || height <= 0. {
557 return None;
558 }
559 let (half_w, half_h) = (width / 2., height / 2.);
560 Self::from_vertices(
561 &[
562 (half_w + center.0, half_h + center.1),
563 (-half_w + center.0, half_h + center.1),
564 (-half_w + center.0, -half_h + center.1),
565 (half_w + center.0, -half_h + center.1),
566 ],
567 CornerRounding::smoothed(half_w.min(half_h), smoothing),
568 None,
569 Some(center),
570 )
571 }
572
573 pub fn cubics(&self) -> &[Cubic] {
575 &self.cubics
576 }
577
578 pub fn center(&self) -> (f32, f32) {
580 (self.center.x, self.center.y)
581 }
582
583 pub fn transformed(&self, f: impl Fn(f32, f32) -> (f32, f32)) -> Self {
586 let (x, y) = f(self.center.x, self.center.y);
587 let features = self
588 .features
589 .iter()
590 .map(|feature| Feature {
591 cubics: feature.cubics.iter().map(|c| c.transformed(&f)).collect(),
592 corner: feature.corner,
593 })
594 .collect();
595 Self::from_features(features, pt(x, y))
596 }
597
598 pub fn rotated(&self, degrees: f32) -> Self {
600 let radians = f64::from(degrees) * (std::f64::consts::PI / 180.);
601 let (s, c) = (radians.sin() as f32, radians.cos() as f32);
602 self.transformed(move |x, y| (c * x + -s * y, s * x + c * y))
603 }
604
605 pub fn normalized(&self) -> Self {
608 let [left, top, right, bottom] = self.bounds(true);
609 let (width, height) = (right - left, bottom - top);
610 let side = width.max(height);
611 if side.is_nan() || side <= 0. {
612 return self.clone();
613 }
614 let offset_x = (side - width) / 2. - left;
615 let offset_y = (side - height) / 2. - top;
616 self.transformed(move |x, y| ((x + offset_x) / side, (y + offset_y) / side))
617 }
618
619 pub fn bounds(&self, approximate: bool) -> [f32; 4] {
626 let mut result = [f32::MAX, f32::MAX, f32::from_bits(1), f32::from_bits(1)];
627 for cubic in &self.cubics {
628 let bounds = cubic.bounds(approximate);
629 result[0] = result[0].min(bounds[0]);
630 result[1] = result[1].min(bounds[1]);
631 result[2] = result[2].max(bounds[2]);
632 result[3] = result[3].max(bounds[3]);
633 }
634 result
635 }
636
637 pub fn max_bounds(&self) -> [f32; 4] {
640 let mut max_squared = 0_f32;
641 for cubic in &self.cubics {
642 let anchor = (pt(cubic.points[0], cubic.points[1]) - self.center).distance_squared();
643 let middle = (cubic.point_on_curve(0.5) - self.center).distance_squared();
644 max_squared = max_squared.max(anchor.max(middle));
645 }
646 let distance = max_squared.sqrt();
647 [
648 self.center.x - distance,
649 self.center.y - distance,
650 self.center.x + distance,
651 self.center.y + distance,
652 ]
653 }
654}
655
656fn flatten(features: &[Feature], center: Pt) -> Vec<Cubic> {
659 let mut out = Vec::new();
660 let mut first: Option<Cubic> = None;
661 let mut last: Option<Cubic> = None;
662 let split = features
663 .first()
664 .filter(|feature| feature.cubics.len() == 3)
665 .map(|feature| {
666 let (start, end) = feature.cubics[1].split(0.5);
667 ([feature.cubics[0], start], [end, feature.cubics[2]])
668 });
669 for i in 0..=features.len() {
670 let run: &[Cubic] = match &split {
671 Some(split) if i == 0 => &split.1,
672 Some(split) if i == features.len() => &split.0,
673 None if i == features.len() => break,
674 _ => &features[i].cubics,
675 };
676 for cubic in run {
677 if !cubic.zero_length() {
678 if let Some(last) = last {
679 out.push(last);
680 }
681 last = Some(*cubic);
682 first.get_or_insert(*cubic);
683 } else if let Some(last) = &mut last {
684 last.points[6] = cubic.points[6];
686 last.points[7] = cubic.points[7];
687 }
688 }
689 }
690 match (first, last) {
691 (Some(first), Some(mut last)) => {
692 last.points[6] = first.points[0];
693 last.points[7] = first.points[1];
694 out.push(last);
695 }
696 _ => out.push(Cubic::new([
698 center.x, center.y, center.x, center.y, center.x, center.y, center.x, center.y,
699 ])),
700 }
701 out
702}
703
704struct RoundedCorner {
705 p0: Pt,
706 p1: Pt,
707 p2: Pt,
708 d1: Pt,
709 d2: Pt,
710 radius: f32,
711 smoothing: f32,
712 expected_round_cut: f32,
713}
714
715impl RoundedCorner {
716 fn new(p0: Pt, p1: Pt, p2: Pt, rounding: CornerRounding) -> Self {
717 let (v01, v21) = (p0 - p1, p2 - p1);
718 let (d01, d21) = (v01.distance(), v21.distance());
719 if d01 > 0. && d21 > 0. {
720 let (d1, d2) = (v01 / d01, v21 / d21);
721 let cos_angle = d1.dot(d2);
722 let sin_angle = (1. - cos_angle * cos_angle).max(0.).sqrt();
723 let expected_round_cut = if f64::from(sin_angle) > 1e-3 {
724 rounding.radius * (cos_angle + 1.) / sin_angle
725 } else {
726 0.
727 };
728 Self {
729 p0,
730 p1,
731 p2,
732 d1,
733 d2,
734 radius: rounding.radius,
735 smoothing: rounding.smoothing,
736 expected_round_cut,
737 }
738 } else {
739 Self {
740 p0,
741 p1,
742 p2,
743 d1: Pt::default(),
744 d2: Pt::default(),
745 radius: 0.,
746 smoothing: 0.,
747 expected_round_cut: 0.,
748 }
749 }
750 }
751
752 fn expected_cut(&self) -> f32 {
753 (1. + self.smoothing) * self.expected_round_cut
754 }
755
756 fn cubics(&self, allowed_cut0: f32, allowed_cut1: f32) -> Vec<Cubic> {
757 let allowed_cut = allowed_cut0.min(allowed_cut1);
758 if self.expected_round_cut < DISTANCE_EPSILON
759 || allowed_cut < DISTANCE_EPSILON
760 || self.radius < DISTANCE_EPSILON
761 {
762 let p = self.p1;
763 return vec![Cubic::straight_line(p.x, p.y, p.x, p.y)];
764 }
765 let round_cut = allowed_cut.min(self.expected_round_cut);
766 let smoothing0 = self.actual_smoothing(allowed_cut0);
767 let smoothing1 = self.actual_smoothing(allowed_cut1);
768 let radius = self.radius * round_cut / self.expected_round_cut;
769 let center_distance = (radius * radius + round_cut * round_cut).sqrt();
770 let center = self.p1 + ((self.d1 + self.d2) / 2.).direction() * center_distance;
771 let intersection0 = self.p1 + self.d1 * round_cut;
772 let intersection2 = self.p1 + self.d2 * round_cut;
773 let flanking0 = flanking_curve(
774 round_cut,
775 smoothing0,
776 self.p1,
777 self.p0,
778 intersection0,
779 intersection2,
780 center,
781 radius,
782 );
783 let flanking2 = flanking_curve(
784 round_cut,
785 smoothing1,
786 self.p1,
787 self.p2,
788 intersection2,
789 intersection0,
790 center,
791 radius,
792 )
793 .reversed();
794 let arc = Cubic::circular_arc(
795 center,
796 flanking0.points[6],
797 flanking0.points[7],
798 flanking2.points[0],
799 flanking2.points[1],
800 );
801 vec![flanking0, arc, flanking2]
802 }
803
804 fn actual_smoothing(&self, allowed_cut: f32) -> f32 {
805 if allowed_cut > self.expected_cut() {
806 self.smoothing
807 } else if allowed_cut > self.expected_round_cut {
808 self.smoothing * (allowed_cut - self.expected_round_cut)
809 / (self.expected_cut() - self.expected_round_cut)
810 } else {
811 0.
812 }
813 }
814}
815
816#[allow(clippy::too_many_arguments)] fn flanking_curve(
818 round_cut: f32,
819 smoothing: f32,
820 corner: Pt,
821 side_start: Pt,
822 intersection: Pt,
823 other_intersection: Pt,
824 center: Pt,
825 radius: f32,
826) -> Cubic {
827 let side_direction = (side_start - corner).direction();
828 let curve_start = corner + side_direction * round_cut * (1. + smoothing);
829 let p = lerp_pt(
830 intersection,
831 (intersection + other_intersection) / 2.,
832 smoothing,
833 );
834 let curve_end = center + direction_of(p.x - center.x, p.y - center.y) * radius;
835 let tangent = (curve_end - center).rotate90();
836 let anchor_end =
837 line_intersection(side_start, side_direction, curve_end, tangent).unwrap_or(intersection);
838 let anchor_start = (curve_start + anchor_end * 2.) / 3.;
840 Cubic::from_points(curve_start, anchor_start, anchor_end, curve_end)
841}
842
843fn line_intersection(p0: Pt, d0: Pt, p1: Pt, d1: Pt) -> Option<Pt> {
844 let rotated = d1.rotate90();
845 let den = d0.dot(rotated);
846 if den.abs() < DISTANCE_EPSILON {
847 return None;
848 }
849 let num = (p1 - p0).dot(rotated);
850 if den.abs() < DISTANCE_EPSILON * num.abs() {
851 return None;
852 }
853 Some(p0 + d0 * (num / den))
854}
855
856pub(super) fn angle_degrees(p: Pt) -> f32 {
858 atan2(p.y, p.x) * 180. / PI
859}
860
861#[cfg(test)]
862mod tests {
863 use super::*;
864 use core::prelude::v1::test;
865
866 #[test]
867 fn bad_input_is_refused_instead_of_panicking() {
868 let none = CornerRounding::UNROUNDED;
869 assert!(RoundedPolygon::from_vertices(&[(0., 0.), (1., 0.)], none, None, None).is_none());
870 let nan = [(0., 0.), (1., f32::NAN), (1., 1.)];
871 assert!(RoundedPolygon::from_vertices(&nan, none, None, None).is_none());
872 assert!(RoundedPolygon::circle(2, 1., (0., 0.)).is_none());
873 assert!(RoundedPolygon::regular(0, 1., (0., 0.), none, None).is_none());
874 assert!(RoundedPolygon::star(5, 1., 2., none, None, (0., 0.)).is_none());
875 assert!(RoundedPolygon::star(5, f32::NAN, 0.5, none, None, (0., 0.)).is_none());
876 assert!(RoundedPolygon::pill(0., 1., 0., (0., 0.)).is_none());
877 let short = [CornerRounding::new(0.1)];
878 let triangle = [(0., 0.), (1., 0.), (0., 1.)];
879 assert!(RoundedPolygon::from_vertices(&triangle, none, Some(&short), None).is_none());
880 }
881
882 #[test]
883 fn a_zero_sized_polygon_is_a_single_point_curve() {
884 let point = [(1., 1.); 4];
885 let polygon = RoundedPolygon::from_vertices(&point, CornerRounding::new(0.2), None, None)
886 .expect("finite");
887 assert_eq!(polygon.cubics().len(), 1);
888 assert_eq!(polygon.cubics()[0].anchor0(), (1., 1.));
889 assert_eq!(polygon.normalized().cubics()[0].anchor0(), (1., 1.));
891 }
892
893 #[test]
894 fn the_outline_closes_exactly() {
895 let polygon = RoundedPolygon::star(
896 6,
897 1.,
898 0.5,
899 CornerRounding::smoothed(0.1, 0.5),
900 None,
901 (0., 0.),
902 )
903 .expect("valid");
904 let cubics = polygon.cubics();
905 for pair in cubics.windows(2) {
906 let (a, b) = (pair[0].anchor1(), pair[1].anchor0());
907 assert!((a.0 - b.0).abs() < DISTANCE_EPSILON && (a.1 - b.1).abs() < DISTANCE_EPSILON);
908 }
909 assert_eq!(
910 cubics.last().map(Cubic::anchor1),
911 cubics.first().map(Cubic::anchor0)
912 );
913 }
914}