smooth_frame/input/
corner.rs1use std::f64::consts::PI;
2
3use crate::errors::SmoothError;
4use crate::output::path::CubicSegment;
5use crate::process::Processor;
6use crate::process::corner::{SmoothCornerGeometry, SmoothCornerProcessor, SmoothCornerRequest};
7use crate::types::{Point, Vector};
8use crate::utils::{EPSILON, clamp01};
9
10#[derive(Debug, Clone, Copy, PartialEq)]
12pub struct SmoothCorner {
13 origin: Point,
14 incoming_axis: Vector,
15 outgoing_axis: Vector,
16 radius: f64,
17 smoothing: f64,
18 incoming_limit: f64,
19 outgoing_limit: f64,
20}
21
22impl SmoothCorner {
23 #[must_use]
27 pub fn new(origin: Point, incoming_axis: Vector, outgoing_axis: Vector) -> Self {
28 Self {
29 origin,
30 incoming_axis,
31 outgoing_axis,
32 radius: 0.0,
33 smoothing: 0.0,
34 incoming_limit: f64::INFINITY,
35 outgoing_limit: f64::INFINITY,
36 }
37 }
38
39 #[must_use]
41 pub fn with_radius(mut self, radius: f64) -> Self {
42 self.radius = radius;
43 self
44 }
45
46 #[must_use]
48 pub fn with_smoothing(mut self, smoothing: f64) -> Self {
49 self.smoothing = smoothing;
50 self
51 }
52
53 #[must_use]
55 pub fn with_limits(mut self, incoming_limit: f64, outgoing_limit: f64) -> Self {
56 self.incoming_limit = incoming_limit;
57 self.outgoing_limit = outgoing_limit;
58 self
59 }
60
61 pub fn geometry(&self) -> Result<SmoothCornerGeometry, SmoothError> {
63 Ok(SmoothCornerProcessor::new(self.to_request()?)
64 .process()
65 .geometry)
66 }
67
68 pub fn to_cubics(&self) -> Result<Vec<CubicSegment>, SmoothError> {
72 Ok(SmoothCornerProcessor::new(self.to_request()?)
73 .process()
74 .cubics)
75 }
76
77 fn to_request(&self) -> Result<SmoothCornerRequest, SmoothError> {
79 validate_corner_inputs(self)?;
80
81 let incoming_axis = self
82 .incoming_axis
83 .normalized()
84 .ok_or(SmoothError::DegenerateAxis)?;
85 let outgoing_axis = self
86 .outgoing_axis
87 .normalized()
88 .ok_or(SmoothError::DegenerateAxis)?;
89 let angle = incoming_axis
90 .angle_between(outgoing_axis)
91 .ok_or(SmoothError::DegenerateAxis)?;
92
93 if angle <= EPSILON || PI - angle <= EPSILON {
94 return Err(SmoothError::InvalidAngle);
95 }
96
97 Ok(SmoothCornerRequest {
98 origin: self.origin,
99 incoming_axis,
100 outgoing_axis,
101 radius: self.radius,
102 smoothing: clamp01(self.smoothing),
103 incoming_limit: self.incoming_limit,
104 outgoing_limit: self.outgoing_limit,
105 angle,
106 })
107 }
108}
109
110fn validate_corner_inputs(corner: &SmoothCorner) -> Result<(), SmoothError> {
112 if !corner.origin.is_finite()
113 || !corner.incoming_axis.is_finite()
114 || !corner.outgoing_axis.is_finite()
115 || !corner.radius.is_finite()
116 || !corner.smoothing.is_finite()
117 || corner.incoming_limit.is_nan()
118 || corner.outgoing_limit.is_nan()
119 {
120 return Err(SmoothError::NonFiniteInput);
121 }
122 if corner.radius < 0.0 || corner.incoming_limit < 0.0 || corner.outgoing_limit < 0.0 {
123 return Err(SmoothError::NegativeInput);
124 }
125 Ok(())
126}