Skip to main content

smooth_frame/
frame.rs

1use crate::corner::SmoothCorner;
2use crate::error::SmoothError;
3use crate::geometry::Point;
4use crate::math::EPSILON;
5use crate::path::{CubicSegment, SmoothPath};
6
7/// 闭合凸 polygon / frame。
8#[derive(Debug, Clone, PartialEq)]
9pub struct SmoothFrame {
10    points: Vec<Point>,
11    radius: f64,
12    smoothing: f64,
13}
14
15impl SmoothFrame {
16    /// 创建闭合 frame。
17    ///
18    /// 当前版本要求输入点组成非退化凸多边形。
19    #[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    /// 设置每个角的核心圆半径。
29    #[must_use]
30    pub fn with_radius(mut self, radius: f64) -> Self {
31        self.radius = radius;
32        self
33    }
34
35    /// 设置每个角的 Sketch-like smoothing,计算时会 clamp 到 `[0, 1]`。
36    #[must_use]
37    pub fn with_smoothing(mut self, smoothing: f64) -> Self {
38        self.smoothing = smoothing;
39        self
40    }
41
42    /// 返回 frame 输入点。
43    #[must_use]
44    pub fn points(&self) -> &[Point] {
45        &self.points
46    }
47
48    /// 生成闭合 smooth frame 路径。
49    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}