brepkit_math/
polygon2d.rs1use crate::vec::Point2;
4
5#[must_use]
7pub fn cross_2d(a: Point2, b: Point2, c: Point2) -> f64 {
8 (b.x() - a.x()) * (c.y() - a.y()) - (b.y() - a.y()) * (c.x() - a.x())
9}
10
11fn line_intersect_2d(a1: Point2, a2: Point2, b1: Point2, b2: Point2) -> Option<Point2> {
12 let dx_a = a2.x() - a1.x();
13 let dy_a = a2.y() - a1.y();
14 let dx_b = b2.x() - b1.x();
15 let dy_b = b2.y() - b1.y();
16 let denom = dx_a * dy_b - dy_a * dx_b;
17 if denom.abs() < 1e-15 {
18 return None;
19 }
20 let t = ((b1.x() - a1.x()) * dy_b - (b1.y() - a1.y()) * dx_b) / denom;
21 Some(Point2::new(a1.x() + t * dx_a, a1.y() + t * dy_a))
22}
23
24fn point_to_line_dist_sq_2d(p: Point2, a: Point2, b: Point2) -> f64 {
25 let dx = b.x() - a.x();
26 let dy = b.y() - a.y();
27 let len_sq = dx * dx + dy * dy;
28 if len_sq < 1e-30 {
29 let ex = p.x() - a.x();
30 let ey = p.y() - a.y();
31 return ex * ex + ey * ey;
32 }
33 let cross = (p.x() - a.x()) * dy - (p.y() - a.y()) * dx;
34 (cross * cross) / len_sq
35}
36
37#[must_use]
39pub fn sutherland_hodgman_clip(subject: &[Point2], clip: &[Point2]) -> Vec<Point2> {
40 let mut output: Vec<Point2> = subject.to_vec();
41
42 for i in 0..clip.len() {
43 if output.is_empty() {
44 return output;
45 }
46 let edge_start = clip[i];
47 let edge_end = clip[(i + 1) % clip.len()];
48 let input = output;
49 output = Vec::new();
50
51 for j in 0..input.len() {
52 let current = input[j];
53 let previous = input[(j + input.len() - 1) % input.len()];
54
55 let curr_inside = cross_2d(edge_start, edge_end, current) >= 0.0;
56 let prev_inside = cross_2d(edge_start, edge_end, previous) >= 0.0;
57
58 if curr_inside {
59 if !prev_inside
60 && let Some(p) = line_intersect_2d(previous, current, edge_start, edge_end)
61 {
62 output.push(p);
63 }
64 output.push(current);
65 } else if prev_inside
66 && let Some(p) = line_intersect_2d(previous, current, edge_start, edge_end)
67 {
68 output.push(p);
69 }
70 }
71 }
72
73 output
74}
75
76#[must_use]
78pub fn find_common_segments(a: &[Point2], b: &[Point2], tolerance: f64) -> Vec<(Point2, Point2)> {
79 let mut results = Vec::new();
80 let tol_sq = tolerance * tolerance;
81
82 for i in 0..a.len() {
83 let a1 = a[i];
84 let a2 = a[(i + 1) % a.len()];
85 for j in 0..b.len() {
86 let b1 = b[j];
87 let b2 = b[(j + 1) % b.len()];
88
89 let dist_b1 = point_to_line_dist_sq_2d(b1, a1, a2);
92 let dist_b2 = point_to_line_dist_sq_2d(b2, a1, a2);
93
94 if dist_b1 < tol_sq && dist_b2 < tol_sq {
95 let dx = a2.x() - a1.x();
97 let dy = a2.y() - a1.y();
98 let len_sq = dx * dx + dy * dy;
99 if len_sq < tol_sq {
100 continue;
101 }
102 let t1 = ((b1.x() - a1.x()) * dx + (b1.y() - a1.y()) * dy) / len_sq;
103 let t2 = ((b2.x() - a1.x()) * dx + (b2.y() - a1.y()) * dy) / len_sq;
104 let t_min = t1.min(t2).max(0.0);
105 let t_max = t1.max(t2).min(1.0);
106 if t_max - t_min > tolerance / len_sq.sqrt() {
107 results.push((
108 Point2::new(a1.x() + t_min * dx, a1.y() + t_min * dy),
109 Point2::new(a1.x() + t_max * dx, a1.y() + t_max * dy),
110 ));
111 }
112 }
113 }
114 }
115 results
116}
117
118#[must_use]
120pub fn fillet_polygon_2d(polygon: &[Point2], radius: f64) -> Vec<Point2> {
121 let n = polygon.len();
122 if n < 3 {
123 return polygon.to_vec();
124 }
125
126 let arc_segments = 8; let mut result = Vec::with_capacity(n * (arc_segments + 1));
128
129 for i in 0..n {
130 let prev = polygon[(i + n - 1) % n];
131 let curr = polygon[i];
132 let next = polygon[(i + 1) % n];
133
134 let d_prev = ((prev.x() - curr.x()).powi(2) + (prev.y() - curr.y()).powi(2)).sqrt();
135 let d_next = ((next.x() - curr.x()).powi(2) + (next.y() - curr.y()).powi(2)).sqrt();
136
137 let max_r = (d_prev.min(d_next) / 2.0).min(radius);
138
139 if max_r < 1e-10 {
140 result.push(curr);
141 continue;
142 }
143
144 let dir_prev_x = (prev.x() - curr.x()) / d_prev;
146 let dir_prev_y = (prev.y() - curr.y()) / d_prev;
147 let dir_next_x = (next.x() - curr.x()) / d_next;
148 let dir_next_y = (next.y() - curr.y()) / d_next;
149
150 let t1 = Point2::new(curr.x() + dir_prev_x * max_r, curr.y() + dir_prev_y * max_r);
152 let t2 = Point2::new(curr.x() + dir_next_x * max_r, curr.y() + dir_next_y * max_r);
153
154 for k in 0..=arc_segments {
156 let t = k as f64 / arc_segments as f64;
157 let x = t2.x().mul_add(t, t1.x() * (1.0 - t));
158 let y = t2.y().mul_add(t, t1.y() * (1.0 - t));
159
160 let mid_x = f64::midpoint(t1.x(), t2.x());
162 let mid_y = f64::midpoint(t1.y(), t2.y());
163 let to_corner_x = curr.x() - mid_x;
164 let to_corner_y = curr.y() - mid_y;
165 let corner_dist = (to_corner_x * to_corner_x + to_corner_y * to_corner_y).sqrt();
166
167 if corner_dist > 1e-10 {
168 let chord_half =
170 ((t2.x() - t1.x()).powi(2) + (t2.y() - t1.y()).powi(2)).sqrt() / 2.0;
171 let sagitta = if max_r > chord_half {
172 max_r - (max_r * max_r - chord_half * chord_half).sqrt()
173 } else {
174 0.0
175 };
176
177 let blend = 4.0 * t * (1.0 - t); let push = sagitta * blend;
180
181 let nx = to_corner_x / corner_dist;
182 let ny = to_corner_y / corner_dist;
183 result.push(Point2::new(x + nx * push, y + ny * push));
184 } else {
185 result.push(Point2::new(x, y));
186 }
187 }
188 }
189
190 result
191}
192
193#[must_use]
195pub fn chamfer_polygon_2d(polygon: &[Point2], distance: f64) -> Vec<Point2> {
196 let n = polygon.len();
197 if n < 3 {
198 return polygon.to_vec();
199 }
200
201 let mut result = Vec::with_capacity(n * 2);
202
203 for i in 0..n {
204 let prev = polygon[(i + n - 1) % n];
205 let curr = polygon[i];
206 let next = polygon[(i + 1) % n];
207
208 let d_prev = ((prev.x() - curr.x()).powi(2) + (prev.y() - curr.y()).powi(2)).sqrt();
209 let d_next = ((next.x() - curr.x()).powi(2) + (next.y() - curr.y()).powi(2)).sqrt();
210
211 let d = (d_prev.min(d_next) / 2.0).min(distance);
212
213 if d < 1e-10 {
214 result.push(curr);
215 continue;
216 }
217
218 result.push(Point2::new(
220 curr.x() + (prev.x() - curr.x()) / d_prev * d,
221 curr.y() + (prev.y() - curr.y()) / d_prev * d,
222 ));
223 result.push(Point2::new(
224 curr.x() + (next.x() - curr.x()) / d_next * d,
225 curr.y() + (next.y() - curr.y()) / d_next * d,
226 ));
227 }
228
229 result
230}