brep_kernel/props/mass_properties/
polygons.rs1use super::*;
2
3pub fn trim_polygons(face: &FaceRecord) -> Result<Vec<Vec<[f64; 2]>>, String> {
4 let mut polygons = Vec::new();
5 for loop_record in &face.loops {
6 let mut polygon = Vec::new();
7 for coedge in &loop_record.coedges {
8 sample_coedge(coedge, [0.0, 0.0], &mut polygon)?;
9 }
10 polygons.push(polygon);
11 }
12 Ok(polygons)
13}
14
15pub(super) fn trim_polygons_unwrapped(
21 face: &FaceRecord,
22 closed_u: bool,
23 closed_v: bool,
24 u_period: f64,
25 v_period: f64,
26) -> Result<(Vec<Vec<[f64; 2]>>, bool), String> {
27 let mut polygons = Vec::new();
28 let mut unwrapped = false;
29 for loop_record in &face.loops {
30 let offsets = crate::topology::loop_seam_offsets(
31 &loop_record.coedges,
32 closed_u,
33 closed_v,
34 u_period,
35 v_period,
36 )?;
37 let mut polygon = Vec::new();
38 for (index, coedge) in loop_record.coedges.iter().enumerate() {
39 let offset = offsets[index];
40 if offset[0] != 0.0 || offset[1] != 0.0 {
41 unwrapped = true;
42 }
43 sample_coedge(coedge, offset, &mut polygon)?;
44 }
45 polygons.push(polygon);
46 }
47 Ok((polygons, unwrapped))
48}
49
50pub(super) fn singly_periodic_wall_polygons(
68 face: &FaceRecord,
69 closed_u: bool,
70 closed_v: bool,
71 domain: [f64; 4],
72) -> Result<Option<Vec<Vec<[f64; 2]>>>, String> {
73 if closed_u == closed_v {
76 return Ok(None);
77 }
78 let [u0, u1, v0, v1] = domain;
79 let p_is_u = closed_u;
80 let period = if p_is_u { u1 - u0 } else { v1 - v0 };
81 if !(period > 0.0) {
82 return Ok(None);
83 }
84 let (q_dom_lo, q_dom_hi) = if p_is_u { (v0, v1) } else { (u0, u1) };
85 let q_extent = (q_dom_hi - q_dom_lo).abs();
86 if !(q_extent > 0.0) {
87 return Ok(None);
88 }
89 let coord = |p: [f64; 2]| if p_is_u { (p[0], p[1]) } else { (p[1], p[0]) };
91
92 let mut rim_indices: Vec<usize> = Vec::new();
93 let mut rim_levels: Vec<f64> = Vec::new();
94 for (index, loop_record) in face.loops.iter().enumerate() {
95 let mut points: Vec<[f64; 2]> = Vec::new();
97 for coedge in &loop_record.coedges {
98 let [d0, d1] = coedge.pcurve.domain()?;
99 let samples = 16;
100 for k in 0..=samples {
101 let t = d0 + (d1 - d0) * k as f64 / samples as f64;
102 let p = coedge.pcurve.evaluate(t)?;
103 points.push([p.x, p.y]);
104 }
105 }
106 if points.len() < 2 {
107 return Ok(None);
108 }
109 let (mut qmin, mut qmax) = (f64::INFINITY, f64::NEG_INFINITY);
110 let mut net = 0.0;
111 for pair in points.windows(2) {
112 let (p0, q0) = coord(pair[0]);
113 let (p1, _q1) = coord(pair[1]);
114 qmin = qmin.min(q0);
115 qmax = qmax.max(q0);
116 let mut delta = p1 - p0;
117 if delta > 0.5 * period {
118 delta -= period;
119 } else if delta < -0.5 * period {
120 delta += period;
121 }
122 net += delta;
123 }
124 if let Some(last) = points.last() {
125 let (_p, q) = coord(*last);
126 qmin = qmin.min(q);
127 qmax = qmax.max(q);
128 }
129 let is_rim = net.abs() > 0.75 * period && (qmax - qmin) <= 0.02 * q_extent;
132 if is_rim {
133 rim_indices.push(index);
134 rim_levels.push(0.5 * (qmin + qmax));
135 }
136 }
137 if rim_levels.len() != 2 {
138 return Ok(None);
139 }
140 let (q_lo, q_hi) = if rim_levels[0] <= rim_levels[1] {
141 (rim_levels[0], rim_levels[1])
142 } else {
143 (rim_levels[1], rim_levels[0])
144 };
145 let tol = 0.02 * q_extent;
149 if (q_lo - q_dom_lo).abs() > tol || (q_hi - q_dom_hi).abs() > tol {
150 return Ok(None);
151 }
152 if (q_hi - q_lo) <= 0.5 * q_extent {
153 return Ok(None);
154 }
155
156 let (r_ulo, r_uhi, r_vlo, r_vhi) = if p_is_u {
158 (u0, u1, q_lo, q_hi)
159 } else {
160 (q_lo, q_hi, v0, v1)
161 };
162 let rectangle = vec![
163 [r_ulo, r_vlo],
164 [r_uhi, r_vlo],
165 [r_uhi, r_vhi],
166 [r_ulo, r_vhi],
167 ];
168 let mut polygons: Vec<Vec<[f64; 2]>> = vec![rectangle];
169 for (index, loop_record) in face.loops.iter().enumerate() {
172 if rim_indices.contains(&index) {
173 continue;
174 }
175 let offsets = crate::topology::loop_seam_offsets(
176 &loop_record.coedges,
177 closed_u,
178 closed_v,
179 u1 - u0,
180 v1 - v0,
181 )?;
182 let mut polygon = Vec::new();
183 for (coedge_index, coedge) in loop_record.coedges.iter().enumerate() {
184 sample_coedge(coedge, offsets[coedge_index], &mut polygon)?;
185 }
186 polygons.push(polygon);
187 }
188 Ok(Some(polygons))
189}
190
191pub(super) fn doubly_periodic_seam_band_polygons(
205 face: &FaceRecord,
206 domain: [f64; 4],
207) -> Result<Option<Vec<Vec<[f64; 2]>>>, String> {
208 let (closed_u, closed_v) = face.surface.closed_directions()?;
209 if !(closed_u && closed_v) {
210 return Ok(None);
211 }
212 let mut loops_uv: Vec<Vec<[f64; 2]>> = Vec::with_capacity(face.loops.len());
214 for loop_record in &face.loops {
215 let mut points: Vec<[f64; 2]> = Vec::new();
216 for coedge in &loop_record.coedges {
217 let [d0, d1] = coedge.pcurve.domain()?;
218 let samples = 24;
219 for k in 0..=samples {
220 let t = d0 + (d1 - d0) * k as f64 / samples as f64;
221 let p = coedge.pcurve.evaluate(t)?;
222 points.push([p.x, p.y]);
223 }
224 }
225 loops_uv.push(points);
226 }
227 let Some(band) =
228 crate::topology::analyze_doubly_periodic_seam_band(&loops_uv, domain, face.same_sense)
229 else {
230 return Ok(None);
231 };
232 let polygon = crate::topology::seam_band_uv_polygon(&loops_uv, domain, &band);
233 Ok(Some(vec![polygon]))
234}
235
236pub(super) fn is_untrimmed(face: &FaceRecord) -> Result<bool, String> {
237 if face.loops.len() != 1 {
238 return Ok(false);
239 }
240 let ku = crate::KnotVector::new(face.surface.knots_u.clone(), face.surface.degree_u)?;
241 let kv = crate::KnotVector::new(face.surface.knots_v.clone(), face.surface.degree_v)?;
242 let [u0, u1] = ku.domain();
243 let [v0, v1] = kv.domain();
244 let domain_area = (u1 - u0) * (v1 - v0);
245 Ok((parameter_space_area(face)?.abs() - domain_area).abs() <= 1e-6 * domain_area)
246}
247
248pub(super) const TRIANGLE_CUBATURE: [([f64; 3], f64); 7] = [
251 ([1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0], 0.225),
252 (
253 [0.059715871789770, 0.059715871789770, 0.880568256420460],
254 0.125939180544827,
255 ),
256 (
257 [0.059715871789770, 0.880568256420460, 0.059715871789770],
258 0.125939180544827,
259 ),
260 (
261 [0.880568256420460, 0.059715871789770, 0.059715871789770],
262 0.125939180544827,
263 ),
264 (
265 [0.470142064105115, 0.470142064105115, 0.059715871789770],
266 0.132394152788506,
267 ),
268 (
269 [0.470142064105115, 0.059715871789770, 0.470142064105115],
270 0.132394152788506,
271 ),
272 (
273 [0.059715871789770, 0.470142064105115, 0.470142064105115],
274 0.132394152788506,
275 ),
276];
277
278pub(super) fn polygon_signed_area(polygon: &[[f64; 2]]) -> f64 {
279 let mut area = 0.0;
280 for index in 0..polygon.len() {
281 let a = polygon[index];
282 let b = polygon[(index + 1) % polygon.len()];
283 area += a[0] * b[1] - b[0] * a[1];
284 }
285 area * 0.5
286}
287
288pub(super) fn clip_polygon_to_cell(polygon: &[[f64; 2]], cell: [f64; 4]) -> Vec<[f64; 2]> {
294 let [u_low, u_high, v_low, v_high] = cell;
295 let mut current = polygon.to_vec();
296 for (axis, bound, keep_below) in [
298 (0, u_low, false),
299 (0, u_high, true),
300 (1, v_low, false),
301 (1, v_high, true),
302 ] {
303 if current.len() < 3 {
304 return Vec::new();
305 }
306 let inside = |point: &[f64; 2]| {
307 if keep_below {
308 point[axis] <= bound
309 } else {
310 point[axis] >= bound
311 }
312 };
313 let mut next = Vec::with_capacity(current.len() + 4);
314 for index in 0..current.len() {
315 let a = current[index];
316 let b = current[(index + 1) % current.len()];
317 let a_in = inside(&a);
318 let b_in = inside(&b);
319 if a_in {
320 next.push(a);
321 }
322 if a_in != b_in {
323 let t = (bound - a[axis]) / (b[axis] - a[axis]);
324 let mut crossing = [0.0; 2];
325 crossing[axis] = bound;
326 crossing[1 - axis] = a[1 - axis] + t * (b[1 - axis] - a[1 - axis]);
327 next.push(crossing);
328 }
329 }
330 current = next;
331 }
332 current
333}
334
335pub(super) fn cell_breaks(knots: &[f64], degree: usize, low: f64, high: f64) -> Vec<f64> {
336 let mut breaks = vec![low, high];
337 breaks.extend(interior_knots(knots, degree));
338 breaks.sort_by(f64::total_cmp);
339 breaks.dedup_by(|a, b| (*a - *b).abs() <= (high - low) * 1e-12);
340 const MINIMUM_CELLS: usize = 8;
343 let target = (high - low) / MINIMUM_CELLS as f64;
344 let mut refined = Vec::with_capacity(breaks.len() * 2);
345 for pair in breaks.windows(2) {
346 refined.push(pair[0]);
347 let span = pair[1] - pair[0];
348 let pieces = (span / target).ceil().max(1.0) as usize;
349 for piece in 1..pieces {
350 refined.push(pair[0] + span * piece as f64 / pieces as f64);
351 }
352 }
353 refined.push(high);
354 refined
355}
356
357pub(super) fn tiled_breaks(base: &[f64], lo: f64, hi: f64, period: f64) -> Vec<f64> {
362 if period <= 0.0 || base.len() < 2 {
363 return base.to_vec();
364 }
365 let origin = base[0];
366 let first = ((lo - origin) / period).floor() as i64;
367 let last = ((hi - origin) / period).ceil() as i64;
368 let mut breaks = Vec::new();
369 for tile in first..=last {
370 let shift = tile as f64 * period;
371 for &value in base {
372 let shifted = value + shift;
373 if breaks.last().map_or(true, |&previous: &f64| {
374 (shifted - previous).abs() > 1e-12 * period
375 }) {
376 breaks.push(shifted);
377 }
378 }
379 }
380 breaks
381}