1use 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 singly_periodic_sphere_cap_polygons(
20 face: &FaceRecord,
21 closed_u: bool,
22 closed_v: bool,
23 domain: [f64; 4],
24) -> Result<Option<Vec<Vec<[f64; 2]>>>, String> {
25 if !matches!(
26 face.surface.analytic(),
27 Some(crate::AnalyticSurface::Sphere { .. })
28 ) || (closed_u, closed_v) != (true, false)
29 || face.loops.len() != 2
30 {
31 return Ok(None);
32 }
33 let [u0, u1, v0, v1] = domain;
34 let period = u1 - u0;
35 let v_span = v1 - v0;
36 struct Rim {
37 points: Vec<[f64; 2]>,
38 winding: f64,
39 vmin: f64,
40 vmax: f64,
41 }
42 let mut rims = Vec::with_capacity(2);
43 for loop_record in &face.loops {
44 let mut points = Vec::new();
45 for coedge in &loop_record.coedges {
46 let [start, end] = coedge.pcurve.domain()?;
47 for index in 0..=64 {
48 let parameter = start + (end - start) * index as f64 / 64.0;
49 let point = coedge.pcurve.evaluate(parameter)?;
50 points.push([point.x, point.y]);
51 }
52 }
53 if points.len() < 2 {
54 return Ok(None);
55 }
56 let winding = points.last().unwrap()[0] - points[0][0];
57 let (mut vmin, mut vmax) = (f64::INFINITY, f64::NEG_INFINITY);
58 for point in &points {
59 vmin = vmin.min(point[1]);
60 vmax = vmax.max(point[1]);
61 }
62 rims.push(Rim {
63 points,
64 winding,
65 vmin,
66 vmax,
67 });
68 }
69 if rims
70 .iter()
71 .any(|rim| (rim.winding.abs() - period).abs() > 0.05 * period)
72 || rims[0].winding * rims[1].winding >= 0.0
73 {
74 return Ok(None);
75 }
76 let flat = |rim: &Rim| rim.vmax - rim.vmin <= 1e-6 * v_span;
77 let pole = match (flat(&rims[0]), flat(&rims[1])) {
78 (true, false) => &rims[0],
79 (false, true) => &rims[1],
80 _ => return Ok(None),
81 };
82 let pole_v = 0.5 * (pole.vmin + pole.vmax);
83 if (pole_v - v0).abs() > 1e-6 * v_span && (pole_v - v1).abs() > 1e-6 * v_span {
84 return Ok(None);
85 }
86
87 let (lower, upper) = if rims[0].vmax <= rims[1].vmin {
88 (&rims[0], &rims[1])
89 } else if rims[1].vmax <= rims[0].vmin {
90 (&rims[1], &rims[0])
91 } else {
92 return Ok(None);
93 };
94 let mut lower_points = lower.points.clone();
95 if lower.winding < 0.0 {
96 lower_points.reverse();
97 }
98 let mut upper_points = upper.points.clone();
99 if upper.winding > 0.0 {
100 upper_points.reverse();
101 }
102 let shift = ((lower_points[0][0] - upper_points.last().unwrap()[0]) / period).round() * period;
103 for point in &mut upper_points {
104 point[0] += shift;
105 }
106 lower_points.extend(upper_points);
107 Ok(Some(vec![lower_points]))
108}
109
110pub(super) fn trim_polygons_unwrapped(
116 face: &FaceRecord,
117 closed_u: bool,
118 closed_v: bool,
119 u_period: f64,
120 v_period: f64,
121) -> Result<(Vec<Vec<[f64; 2]>>, bool), String> {
122 let mut polygons = Vec::new();
123 let mut unwrapped = false;
124 for loop_record in &face.loops {
125 let offsets = crate::topology::loop_seam_offsets(
126 &loop_record.coedges,
127 closed_u,
128 closed_v,
129 u_period,
130 v_period,
131 )?;
132 let mut polygon = Vec::new();
133 for (index, coedge) in loop_record.coedges.iter().enumerate() {
134 let offset = offsets[index];
135 if offset[0] != 0.0 || offset[1] != 0.0 {
136 unwrapped = true;
137 }
138 sample_coedge(coedge, offset, &mut polygon)?;
139 }
140 polygons.push(polygon);
141 }
142 Ok((polygons, unwrapped))
143}
144
145pub(super) fn singly_periodic_wall_polygons(
163 face: &FaceRecord,
164 closed_u: bool,
165 closed_v: bool,
166 domain: [f64; 4],
167) -> Result<Option<Vec<Vec<[f64; 2]>>>, String> {
168 if closed_u == closed_v {
171 return Ok(None);
172 }
173 let [u0, u1, v0, v1] = domain;
174 let p_is_u = closed_u;
175 let period = if p_is_u { u1 - u0 } else { v1 - v0 };
176 if !(period > 0.0) {
177 return Ok(None);
178 }
179 let (q_dom_lo, q_dom_hi) = if p_is_u { (v0, v1) } else { (u0, u1) };
180 let q_extent = (q_dom_hi - q_dom_lo).abs();
181 if !(q_extent > 0.0) {
182 return Ok(None);
183 }
184 let coord = |p: [f64; 2]| if p_is_u { (p[0], p[1]) } else { (p[1], p[0]) };
186
187 let mut rim_indices: Vec<usize> = Vec::new();
188 let mut rim_levels: Vec<f64> = Vec::new();
189 for (index, loop_record) in face.loops.iter().enumerate() {
190 let mut points: Vec<[f64; 2]> = Vec::new();
192 for coedge in &loop_record.coedges {
193 let [d0, d1] = coedge.pcurve.domain()?;
194 let samples = 16;
195 for k in 0..=samples {
196 let t = d0 + (d1 - d0) * k as f64 / samples as f64;
197 let p = coedge.pcurve.evaluate(t)?;
198 points.push([p.x, p.y]);
199 }
200 }
201 if points.len() < 2 {
202 return Ok(None);
203 }
204 let (mut qmin, mut qmax) = (f64::INFINITY, f64::NEG_INFINITY);
205 let mut net = 0.0;
206 for pair in points.windows(2) {
207 let (p0, q0) = coord(pair[0]);
208 let (p1, _q1) = coord(pair[1]);
209 qmin = qmin.min(q0);
210 qmax = qmax.max(q0);
211 let mut delta = p1 - p0;
212 if delta > 0.5 * period {
213 delta -= period;
214 } else if delta < -0.5 * period {
215 delta += period;
216 }
217 net += delta;
218 }
219 if let Some(last) = points.last() {
220 let (_p, q) = coord(*last);
221 qmin = qmin.min(q);
222 qmax = qmax.max(q);
223 }
224 let is_rim = net.abs() > 0.75 * period && (qmax - qmin) <= 0.02 * q_extent;
227 if is_rim {
228 rim_indices.push(index);
229 rim_levels.push(0.5 * (qmin + qmax));
230 }
231 }
232 if rim_levels.len() != 2 {
233 return Ok(None);
234 }
235 let (q_lo, q_hi) = if rim_levels[0] <= rim_levels[1] {
236 (rim_levels[0], rim_levels[1])
237 } else {
238 (rim_levels[1], rim_levels[0])
239 };
240 let tol = 0.02 * q_extent;
244 if (q_lo - q_dom_lo).abs() > tol || (q_hi - q_dom_hi).abs() > tol {
245 return Ok(None);
246 }
247 if (q_hi - q_lo) <= 0.5 * q_extent {
248 return Ok(None);
249 }
250
251 let (r_ulo, r_uhi, r_vlo, r_vhi) = if p_is_u {
253 (u0, u1, q_lo, q_hi)
254 } else {
255 (q_lo, q_hi, v0, v1)
256 };
257 let rectangle = vec![
258 [r_ulo, r_vlo],
259 [r_uhi, r_vlo],
260 [r_uhi, r_vhi],
261 [r_ulo, r_vhi],
262 ];
263 let mut polygons: Vec<Vec<[f64; 2]>> = vec![rectangle];
264 for (index, loop_record) in face.loops.iter().enumerate() {
267 if rim_indices.contains(&index) {
268 continue;
269 }
270 let offsets = crate::topology::loop_seam_offsets(
271 &loop_record.coedges,
272 closed_u,
273 closed_v,
274 u1 - u0,
275 v1 - v0,
276 )?;
277 let mut polygon = Vec::new();
278 for (coedge_index, coedge) in loop_record.coedges.iter().enumerate() {
279 sample_coedge(coedge, offsets[coedge_index], &mut polygon)?;
280 }
281 polygons.push(polygon);
282 }
283 Ok(Some(polygons))
284}
285
286pub(super) fn doubly_periodic_seam_band_polygons(
300 face: &FaceRecord,
301 domain: [f64; 4],
302) -> Result<Option<Vec<Vec<[f64; 2]>>>, String> {
303 let (closed_u, closed_v) = face.surface.closed_directions()?;
304 if !(closed_u && closed_v) {
305 return Ok(None);
306 }
307 let mut loops_uv: Vec<Vec<[f64; 2]>> = Vec::with_capacity(face.loops.len());
309 for loop_record in &face.loops {
310 let mut points: Vec<[f64; 2]> = Vec::new();
311 for coedge in &loop_record.coedges {
312 let [d0, d1] = coedge.pcurve.domain()?;
313 let samples = 24;
314 for k in 0..=samples {
315 let t = d0 + (d1 - d0) * k as f64 / samples as f64;
316 let p = coedge.pcurve.evaluate(t)?;
317 points.push([p.x, p.y]);
318 }
319 }
320 loops_uv.push(points);
321 }
322 let Some(band) =
323 crate::topology::analyze_doubly_periodic_seam_band(&loops_uv, domain, face.same_sense)
324 else {
325 return Ok(None);
326 };
327 let polygon = crate::topology::seam_band_uv_polygon(&loops_uv, domain, &band);
328 Ok(Some(vec![polygon]))
329}
330
331pub(super) fn is_untrimmed(face: &FaceRecord) -> Result<bool, String> {
332 if face.loops.len() != 1 {
333 return Ok(false);
334 }
335 let ku = crate::KnotVector::new(face.surface.knots_u.clone(), face.surface.degree_u)?;
336 let kv = crate::KnotVector::new(face.surface.knots_v.clone(), face.surface.degree_v)?;
337 let [u0, u1] = ku.domain();
338 let [v0, v1] = kv.domain();
339 let domain_area = (u1 - u0) * (v1 - v0);
340 Ok((parameter_space_area(face)?.abs() - domain_area).abs() <= 1e-6 * domain_area)
341}
342
343pub(super) const TRIANGLE_CUBATURE: [([f64; 3], f64); 7] = [
346 ([1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0], 0.225),
347 (
348 [0.101286507323456, 0.101286507323456, 0.797426985353087],
349 0.125939180544827,
350 ),
351 (
352 [0.101286507323456, 0.797426985353087, 0.101286507323456],
353 0.125939180544827,
354 ),
355 (
356 [0.797426985353087, 0.101286507323456, 0.101286507323456],
357 0.125939180544827,
358 ),
359 (
360 [0.470142064105115, 0.470142064105115, 0.059715871789770],
361 0.132394152788506,
362 ),
363 (
364 [0.470142064105115, 0.059715871789770, 0.470142064105115],
365 0.132394152788506,
366 ),
367 (
368 [0.059715871789770, 0.470142064105115, 0.470142064105115],
369 0.132394152788506,
370 ),
371];
372
373pub(super) fn polygon_signed_area(polygon: &[[f64; 2]]) -> f64 {
374 let mut area = 0.0;
375 for index in 0..polygon.len() {
376 let a = polygon[index];
377 let b = polygon[(index + 1) % polygon.len()];
378 area += a[0] * b[1] - b[0] * a[1];
379 }
380 area * 0.5
381}
382
383pub(super) fn clip_polygon_to_cell(polygon: &[[f64; 2]], cell: [f64; 4]) -> Vec<[f64; 2]> {
389 let [u_low, u_high, v_low, v_high] = cell;
390 let mut current = polygon.to_vec();
391 for (axis, bound, keep_below) in [
393 (0, u_low, false),
394 (0, u_high, true),
395 (1, v_low, false),
396 (1, v_high, true),
397 ] {
398 if current.len() < 3 {
399 return Vec::new();
400 }
401 let inside = |point: &[f64; 2]| {
402 if keep_below {
403 point[axis] <= bound
404 } else {
405 point[axis] >= bound
406 }
407 };
408 let mut next = Vec::with_capacity(current.len() + 4);
409 for index in 0..current.len() {
410 let a = current[index];
411 let b = current[(index + 1) % current.len()];
412 let a_in = inside(&a);
413 let b_in = inside(&b);
414 if a_in {
415 next.push(a);
416 }
417 if a_in != b_in {
418 let t = (bound - a[axis]) / (b[axis] - a[axis]);
419 let mut crossing = [0.0; 2];
420 crossing[axis] = bound;
421 crossing[1 - axis] = a[1 - axis] + t * (b[1 - axis] - a[1 - axis]);
422 next.push(crossing);
423 }
424 }
425 current = next;
426 }
427 current
428}
429
430pub(super) fn cell_breaks(knots: &[f64], degree: usize, low: f64, high: f64) -> Vec<f64> {
431 let mut breaks = vec![low, high];
432 breaks.extend(interior_knots(knots, degree));
433 breaks.sort_by(f64::total_cmp);
434 breaks.dedup_by(|a, b| (*a - *b).abs() <= (high - low) * 1e-12);
435 const MINIMUM_CELLS: usize = 8;
438 let target = (high - low) / MINIMUM_CELLS as f64;
439 let mut refined = Vec::with_capacity(breaks.len() * 2);
440 for pair in breaks.windows(2) {
441 refined.push(pair[0]);
442 let span = pair[1] - pair[0];
443 let pieces = (span / target).ceil().max(1.0) as usize;
444 for piece in 1..pieces {
445 refined.push(pair[0] + span * piece as f64 / pieces as f64);
446 }
447 }
448 refined.push(high);
449 refined
450}
451
452pub(super) fn tiled_breaks(base: &[f64], lo: f64, hi: f64, period: f64) -> Vec<f64> {
457 if period <= 0.0 || base.len() < 2 {
458 return base.to_vec();
459 }
460 let origin = base[0];
461 let first = ((lo - origin) / period).floor() as i64;
462 let last = ((hi - origin) / period).ceil() as i64;
463 let mut breaks = Vec::new();
464 for tile in first..=last {
465 let shift = tile as f64 * period;
466 for &value in base {
467 let shifted = value + shift;
468 if breaks.last().map_or(true, |&previous: &f64| {
469 (shifted - previous).abs() > 1e-12 * period
470 }) {
471 breaks.push(shifted);
472 }
473 }
474 }
475 breaks
476}