1pub(super) use crate::curve::interior_knots;
2use super::*;
3
4pub(crate) fn curve_breaks(curve: &NurbsCurve) -> Result<Vec<f64>, String> {
5 let [start, end] = curve.domain()?;
6 let mut result = vec![start];
7 result.extend(interior_knots(&curve.knots, curve.degree));
8 result.push(end);
9 Ok(result)
10}
11
12pub fn parameter_space_area(face: &FaceRecord) -> Result<f64, String> {
13 let mut area = 0.0;
14 for loop_record in &face.loops {
15 for coedge in &loop_record.coedges {
16 for pair in curve_breaks(&coedge.pcurve)?.windows(2) {
17 for panel in rule::curve_panels(&coedge.pcurve, pair[0], pair[1])? {
18 for (parameter, weight) in panel.stations() {
19 let (point, tangent) = coedge.pcurve.deriv1(parameter)?;
20 area += weight * 0.5 * (point.x * tangent.y - point.y * tangent.x);
21 }
22 }
23 }
24 }
25 }
26 Ok(area)
27}
28
29fn open_planar_loops() -> bool {
33 debug_flag!("BREP_OPEN_PLANAR_LOOPS")
34}
35
36pub(super) fn loop_closing_chords(face: &FaceRecord) -> Result<Vec<(Vec3, Vec3)>, String> {
40 let mut chords = Vec::new();
41 if open_planar_loops() {
42 return Ok(chords);
43 }
44 for loop_record in &face.loops {
45 let count = loop_record.coedges.len();
46 for index in 0..count {
47 let [_, end] = loop_record.coedges[index].pcurve.domain()?;
48 let [start, _] = loop_record.coedges[(index + 1) % count].pcurve.domain()?;
49 chords.push((
50 loop_record.coedges[index].pcurve.evaluate(end)?,
51 loop_record.coedges[(index + 1) % count].pcurve.evaluate(start)?,
52 ));
53 }
54 }
55 Ok(chords)
56}
57
58pub(crate) fn closed_parameter_space_area(face: &FaceRecord) -> Result<f64, String> {
78 let mut area = parameter_space_area(face)?;
79 for (end, start) in loop_closing_chords(face)? {
80 area += 0.5 * (crate::polygon::area_term([end.x, end.y], [start.x, start.y]));
81 }
82 Ok(area)
83}
84
85const SEAM_COINCIDENCE_REL: f64 = 1e-12;
95
96pub(crate) fn closed_parameter_space_area_on_carrier(face: &FaceRecord) -> Result<f64, String> {
112 let mut area = parameter_space_area(face)?;
113 let mut coincidence: Option<f64> = None;
114 for loop_record in &face.loops {
115 let count = loop_record.coedges.len();
116 for index in 0..count {
117 let [_, end] = loop_record.coedges[index].pcurve.domain()?;
118 let [start, _] = loop_record.coedges[(index + 1) % count].pcurve.domain()?;
119 let here = loop_record.coedges[index].pcurve.evaluate(end)?;
120 let next = loop_record.coedges[(index + 1) % count].pcurve.evaluate(start)?;
121 if here.x == next.x && here.y == next.y {
122 continue;
123 }
124 let bar = *coincidence.get_or_insert_with(|| {
125 SEAM_COINCIDENCE_REL
126 * crate::tolerance::model_scale(
127 face.surface
128 .control_points
129 .iter()
130 .flatten()
131 .filter_map(|control| control.point().ok()),
132 )
133 });
134 let (image_here, image_next) = (
135 face.surface.evaluate_extended(here.x, here.y)?,
136 face.surface.evaluate_extended(next.x, next.y)?,
137 );
138 if image_here.sub(image_next).length() <= bar {
139 continue;
140 }
141 area += 0.5 * (crate::polygon::area_term([here.x, here.y], [next.x, next.y]));
142 }
143 }
144 Ok(area)
145}
146
147pub(super) fn is_affine(surface: &NurbsSurface) -> Result<bool, String> {
148 surface.is_affine()
150}
151
152pub(crate) fn surface_breaks(surface: &NurbsSurface) -> Result<(Vec<f64>, Vec<f64>), String> {
153 let ku = crate::KnotVector::new(surface.knots_u.clone(), surface.degree_u)?;
154 let kv = crate::KnotVector::new(surface.knots_v.clone(), surface.degree_v)?;
155 let [u0, u1] = ku.domain();
156 let [v0, v1] = kv.domain();
157 let mut u = vec![u0];
158 u.extend(interior_knots(&surface.knots_u, surface.degree_u));
159 u.push(u1);
160 let mut v = vec![v0];
161 v.extend(interior_knots(&surface.knots_v, surface.degree_v));
162 v.push(v1);
163 Ok((u, v))
164}
165
166pub(super) fn integrand_value(kind: Integrand, point: Vec3, weighted_normal: Vec3) -> f64 {
167 let (x, y, z) = (point.x, point.y, point.z);
168 match kind {
169 Integrand::Area => weighted_normal.length(),
170 Integrand::Volume => point.dot(weighted_normal),
171 Integrand::VolumeAbout(reference) => point.sub(reference).dot(weighted_normal),
172 Integrand::NormalX => weighted_normal.x,
173 Integrand::NormalY => weighted_normal.y,
174 Integrand::NormalZ => weighted_normal.z,
175 Integrand::MomentX => 0.5 * x * x * weighted_normal.x,
176 Integrand::MomentY => 0.5 * y * y * weighted_normal.y,
177 Integrand::MomentZ => 0.5 * z * z * weighted_normal.z,
178 Integrand::SecondXX => x * x * x / 3.0 * weighted_normal.x,
179 Integrand::SecondYY => y * y * y / 3.0 * weighted_normal.y,
180 Integrand::SecondZZ => z * z * z / 3.0 * weighted_normal.z,
181 Integrand::ProductXY => 0.5 * x * x * y * weighted_normal.x,
182 Integrand::ProductXZ => 0.5 * x * x * z * weighted_normal.x,
183 Integrand::ProductYZ => 0.5 * y * y * z * weighted_normal.y,
184 }
185}
186
187
188pub(super) const GAUSS_COUNT: usize = GAUSS_X.len();
190
191pub(super) fn tensor_blocks_on() -> bool {
197 switch!("BREP_MASS_TENSOR_BLOCK", true)
198}
199
200pub(super) fn integrate_cell_scaled(
214 face: &FaceRecord,
215 cell: [f64; 4],
216 scale: f64,
217 kinds: &[Integrand],
218 totals: &mut [f64],
219 rules: &mut rule::SurfaceRules,
220) -> Result<usize, String> {
221 let (u_panels, v_panels) = rules.panels(&face.surface, cell)?;
225 let area_slot = if area_adaptive_on() {
226 kinds.iter().position(|kind| matches!(kind, Integrand::Area))
227 } else {
228 None
229 };
230 let before = area_slot.map(|slot| totals[slot]);
231 let mut stations = 0;
232 for u_panel in &u_panels {
233 for v_panel in &v_panels {
234 integrate_block(face, *u_panel, *v_panel, scale, kinds, totals, rules.station_identity)?;
235 stations += u_panel.order * v_panel.order;
236 }
237 }
238 if let (Some(slot), Some(before)) = (area_slot, before) {
239 let coarse = (totals[slot] - before) / scale;
244 totals[slot] = before + scale * adaptive_cell_area(face, cell, coarse, rules, AREA_ADAPTIVE_DEPTH, &mut stations)?;
245 }
246 Ok(stations)
247}
248
249pub(super) fn area_adaptive_on() -> bool {
261 switch!("BREP_MASS_AREA_ADAPTIVE", true)
262}
263
264const AREA_ADAPTIVE_DEPTH: usize = 6;
266
267const AREA_ADAPTIVE_TOLERANCE: f64 = 1e-11;
270
271fn adaptive_cell_area(
275 face: &FaceRecord,
276 cell: [f64; 4],
277 coarse: f64,
278 rules: &mut rule::SurfaceRules,
279 depth: usize,
280 stations: &mut usize,
281) -> Result<f64, String> {
282 if depth == 0 {
283 return Ok(coarse);
284 }
285 let [u_lo, u_hi, v_lo, v_hi] = cell;
286 let (u_mid, v_mid) = (0.5 * (u_lo + u_hi), 0.5 * (v_lo + v_hi));
287 let quadrants = [
288 [u_lo, u_mid, v_lo, v_mid],
289 [u_mid, u_hi, v_lo, v_mid],
290 [u_lo, u_mid, v_mid, v_hi],
291 [u_mid, u_hi, v_mid, v_hi],
292 ];
293 let mut readings = [0.0f64; 4];
294 for (index, quadrant) in quadrants.iter().enumerate() {
295 let (u_panels, v_panels) = rules.panels(&face.surface, *quadrant)?;
296 let mut area = [0.0f64];
297 for u_panel in &u_panels {
298 for v_panel in &v_panels {
299 integrate_block(face, *u_panel, *v_panel, 1.0, &[Integrand::Area], &mut area, rules.station_identity)?;
300 *stations += u_panel.order * v_panel.order;
301 }
302 }
303 readings[index] = area[0];
304 }
305 let fine: f64 = readings.iter().sum();
306 if (fine - coarse).abs() <= AREA_ADAPTIVE_TOLERANCE * fine.abs() {
307 return Ok(fine);
308 }
309 let mut total = 0.0;
310 for (index, quadrant) in quadrants.iter().enumerate() {
311 total += adaptive_cell_area(face, *quadrant, readings[index], rules, depth - 1, stations)?;
312 }
313 Ok(total)
314}
315
316fn integrate_block(
318 face: &FaceRecord,
319 u_panel: rule::Panel,
320 v_panel: rule::Panel,
321 scale: f64,
322 kinds: &[Integrand],
323 totals: &mut [f64],
324 station_identity: Option<[u64; 2]>,
325) -> Result<(), String> {
326 let half_u = (u_panel.hi - u_panel.lo) * 0.5;
327 let middle_u = (u_panel.hi + u_panel.lo) * 0.5;
328 let half_v = (v_panel.hi - v_panel.lo) * 0.5;
329 let middle_v = (v_panel.hi + v_panel.lo) * 0.5;
330 let (u_x, u_w) = u_panel.nodes();
331 let (v_x, v_w) = v_panel.nodes();
332 let sign = if face.same_sense { 1.0 } else { -1.0 };
333 let base_rule = u_x.len() == GAUSS_COUNT && v_x.len() == GAUSS_COUNT;
339 if base_rule && tensor_blocks_on() && face.surface.deriv1_tensor_supported() {
340 let mut u_stations = [0.0f64; GAUSS_COUNT];
341 let mut v_stations = [0.0f64; GAUSS_COUNT];
342 for index in 0..GAUSS_COUNT {
343 u_stations[index] = middle_u + half_u * GAUSS_X[index];
344 v_stations[index] = middle_v + half_v * GAUSS_X[index];
345 }
346 return super::cell_memo::each(&face.surface, station_identity, &u_stations, &v_stations, |i, j, point, su, sv| {
347 let weighted_normal = su.cross(sv).scale(sign);
348 for (slot, kind) in kinds.iter().enumerate() {
349 totals[slot] += scale
350 * GAUSS_W[i]
351 * GAUSS_W[j]
352 * half_u
353 * half_v
354 * integrand_value(*kind, point, weighted_normal);
355 }
356 Ok(())
357 });
358 }
359 if base_rule && !face.surface.deriv1_tensor_supported() {
360 for i in 0..GAUSS_COUNT {
361 for j in 0..GAUSS_COUNT {
362 let (point, su, sv) = face
363 .surface
364 .deriv1(middle_u + half_u * GAUSS_X[i], middle_v + half_v * GAUSS_X[j])?;
365 let weighted_normal = su.cross(sv).scale(sign);
366 for (slot, kind) in kinds.iter().enumerate() {
367 totals[slot] += scale
368 * GAUSS_W[i]
369 * GAUSS_W[j]
370 * half_u
371 * half_v
372 * integrand_value(*kind, point, weighted_normal);
373 }
374 }
375 }
376 return Ok(());
377 }
378 if tensor_blocks_on() && face.surface.deriv1_tensor_supported() {
379 let mut u_stations = [0.0f64; rule::MAX_ORDER];
380 let mut v_stations = [0.0f64; rule::MAX_ORDER];
381 for index in 0..u_x.len() {
382 u_stations[index] = middle_u + half_u * u_x[index];
383 }
384 for index in 0..v_x.len() {
385 v_stations[index] = middle_v + half_v * v_x[index];
386 }
387 return super::cell_memo::each(
388 &face.surface, station_identity,
389 &u_stations[..u_x.len()],
390 &v_stations[..v_x.len()],
391 |i, j, point, su, sv| {
392 let weighted_normal = su.cross(sv).scale(sign);
393 for (slot, kind) in kinds.iter().enumerate() {
394 totals[slot] += scale
395 * u_w[i]
396 * v_w[j]
397 * half_u
398 * half_v
399 * integrand_value(*kind, point, weighted_normal);
400 }
401 Ok(())
402 },
403 );
404 }
405 for i in 0..u_x.len() {
406 for j in 0..v_x.len() {
407 let (point, su, sv) = face
408 .surface
409 .deriv1(middle_u + half_u * u_x[i], middle_v + half_v * v_x[j])?;
410 let weighted_normal = su.cross(sv).scale(sign);
411 for (slot, kind) in kinds.iter().enumerate() {
412 totals[slot] += scale
413 * u_w[i]
414 * v_w[j]
415 * half_u
416 * half_v
417 * integrand_value(*kind, point, weighted_normal);
418 }
419 }
420 }
421 Ok(())
422}
423
424pub(super) fn integrate_untrimmed_multi(
429 face: &FaceRecord,
430 kinds: &[Integrand],
431) -> Result<Vec<f64>, String> {
432 let (u_breaks, v_breaks) = surface_breaks(&face.surface)?;
433 let mut totals = vec![0.0f64; kinds.len()];
434 let mut rules = rule::SurfaceRules::new(&face.surface)?;
435 for upair in u_breaks.windows(2) {
436 for vpair in v_breaks.windows(2) {
437 integrate_cell_scaled(
438 face,
439 [upair[0], upair[1], vpair[0], vpair[1]],
440 1.0,
441 kinds,
442 &mut totals,
443 &mut rules,
444 )?;
445 }
446 }
447 Ok(totals)
448}
449
450pub(super) fn integrate_iso_rectangle_multi(
461 face: &FaceRecord,
462 rectangle: [f64; 4],
463 kinds: &[Integrand],
464) -> Result<Vec<f64>, String> {
465 let (u_breaks, v_breaks) = surface_breaks(&face.surface)?;
466 let mut totals = vec![0.0f64; kinds.len()];
467 let mut rules = rule::SurfaceRules::new(&face.surface)?;
468 for upair in u_breaks.windows(2) {
469 let (u_lo, u_hi) = (upair[0].max(rectangle[0]), upair[1].min(rectangle[1]));
470 if u_hi <= u_lo {
471 continue;
472 }
473 for vpair in v_breaks.windows(2) {
474 let (v_lo, v_hi) = (vpair[0].max(rectangle[2]), vpair[1].min(rectangle[3]));
475 if v_hi <= v_lo {
476 continue;
477 }
478 integrate_cell_scaled(face, [u_lo, u_hi, v_lo, v_hi], 1.0, kinds, &mut totals, &mut rules)?;
479 }
480 }
481 Ok(totals)
482}
483
484pub(super) fn integrate_untrimmed(face: &FaceRecord, kind: Integrand) -> Result<f64, String> {
485 Ok(integrate_untrimmed_multi(face, &[kind])?[0])
486}
487
488#[derive(Clone, Copy)]
495pub(super) struct BiBand {
496 pub(super) p_is_u: bool,
498 pub(super) q_lo: f64,
500 pub(super) q_hi: f64,
501 pub(super) complement: bool,
505 pub(super) rim_wobble: f64,
512}
513
514pub(super) const BAND_RIM_LEVEL_TOLERANCE_REL: f64 = 1e-9;
528
529pub(super) fn biperiodic_band_range(face: &FaceRecord) -> Result<Option<BiBand>, String> {
536 let surface = &face.surface;
537 let (closed_u, closed_v) = surface.closed_directions()?;
538 if !(closed_u && closed_v) {
539 return Ok(None);
540 }
541 let [u0, u1] = surface.domain_u()?;
542 let [v0, v1] = surface.domain_v()?;
543 let u_span = (u1 - u0).abs().max(1e-30);
544 let v_span = (v1 - v0).abs().max(1e-30);
545 let mut loop_points: Vec<Vec<[f64; 2]>> = Vec::with_capacity(2);
551 for loop_record in &face.loops {
552 let mut points = Vec::new();
553 for coedge in &loop_record.coedges {
554 let [d0, d1] = coedge.pcurve.domain()?;
555 let samples = 32;
558 for k in 0..=samples {
559 let t = d0 + (d1 - d0) * k as f64 / samples as f64;
560 let p = coedge.pcurve.evaluate(t)?;
561 points.push([p.x, p.y]);
562 }
563 }
564 if points.len() < 2 {
565 return Ok(None);
566 }
567 let (mut umin, mut umax, mut vmin, mut vmax) = (
568 f64::INFINITY,
569 f64::NEG_INFINITY,
570 f64::INFINITY,
571 f64::NEG_INFINITY,
572 );
573 for pt in &points {
574 umin = umin.min(pt[0]);
575 umax = umax.max(pt[0]);
576 vmin = vmin.min(pt[1]);
577 vmax = vmax.max(pt[1]);
578 }
579 if (umax - umin) <= 1e-3 * u_span && (vmax - vmin) <= 1e-3 * v_span {
580 continue; }
582 loop_points.push(points);
583 }
584 if loop_points.len() != 2 {
585 return Ok(None);
586 }
587 for p_is_u in [true, false] {
588 let (period, _p0, _p1) = if p_is_u {
589 (u1 - u0, u0, u1)
590 } else {
591 (v1 - v0, v0, v1)
592 };
593 let (q_dom_lo, q_dom_hi) = if p_is_u { (v0, v1) } else { (u0, u1) };
594 let q_extent = (q_dom_hi - q_dom_lo).abs().max(1e-30);
595 if !(period > 0.0) {
596 continue;
597 }
598 let coord = |pt: &[f64; 2]| -> (f64, f64) {
599 if p_is_u {
600 (pt[0], pt[1])
601 } else {
602 (pt[1], pt[0])
603 }
604 };
605 let mut rings: Vec<(f64, i32)> = Vec::with_capacity(2);
608 let mut clean = true;
609 let mut rim_wobble = 0.0f64;
610 for points in &loop_points {
611 let (mut pmin, mut pmax, mut qmin, mut qmax) = (
612 f64::INFINITY,
613 f64::NEG_INFINITY,
614 f64::INFINITY,
615 f64::NEG_INFINITY,
616 );
617 for pt in points {
618 let (p, q) = coord(pt);
619 pmin = pmin.min(p);
620 pmax = pmax.max(p);
621 qmin = qmin.min(q);
622 qmax = qmax.max(q);
623 }
624 if (pmax - pmin) < 0.6 * period || (qmax - qmin) > 0.05 * q_extent {
625 clean = false;
626 break;
627 }
628 rim_wobble = rim_wobble.max((qmax - qmin) / q_extent);
629 let mut net = 0.0;
630 for pair in points.windows(2) {
631 let mut delta = coord(&pair[1]).0 - coord(&pair[0]).0;
632 if delta > 0.5 * period {
633 delta -= period;
634 } else if delta < -0.5 * period {
635 delta += period;
636 }
637 net += delta;
638 }
639 let direction = if net > 0.25 * period {
640 1
641 } else if net < -0.25 * period {
642 -1
643 } else {
644 0
645 };
646 rings.push((0.5 * (qmin + qmax), direction));
647 }
648 if !clean || rings.len() != 2 {
649 continue;
650 }
651 rings.sort_by(|a, b| a.0.total_cmp(&b.0));
652 let (q_lo, lower_dir) = rings[0];
653 let (q_hi, upper_dir) = rings[1];
654 let inconclusive = lower_dir == 0 || upper_dir == 0 || lower_dir == upper_dir;
655 let between_rims_is_ccw_uv = if p_is_u { lower_dir > 0 } else { lower_dir < 0 };
656 let complement = !inconclusive && between_rims_is_ccw_uv != face.same_sense;
657 return Ok(Some(BiBand {
658 p_is_u,
659 q_lo,
660 q_hi,
661 complement,
662 rim_wobble,
663 }));
664 }
665 Ok(None)
666}
667
668pub(super) fn integrate_rectangle(
671 face: &FaceRecord,
672 u_lo: f64,
673 u_hi: f64,
674 v_lo: f64,
675 v_hi: f64,
676 kind: Integrand,
677) -> Result<f64, String> {
678 let (u_full, v_full) = surface_breaks(&face.surface)?;
679 let clamp = |breaks: &[f64], lo: f64, hi: f64| -> Vec<f64> {
680 let eps = 1e-9 * (hi - lo).abs().max(1e-30);
681 let mut out = vec![lo];
682 for &b in breaks {
683 if b > lo + eps && b < hi - eps {
684 out.push(b);
685 }
686 }
687 out.push(hi);
688 out
689 };
690 let u_breaks = clamp(&u_full, u_lo, u_hi);
691 let v_breaks = clamp(&v_full, v_lo, v_hi);
692 let mut totals = [0.0f64];
693 let mut rules = rule::SurfaceRules::new(&face.surface)?;
694 for upair in u_breaks.windows(2) {
695 for vpair in v_breaks.windows(2) {
696 integrate_cell_scaled(
697 face,
698 [upair[0], upair[1], vpair[0], vpair[1]],
699 1.0,
700 &[kind],
701 &mut totals,
702 &mut rules,
703 )?;
704 }
705 }
706 Ok(totals[0])
707}
708
709pub(super) fn biperiodic_band_integral(
726 face: &FaceRecord,
727 kinds: &[Integrand],
728) -> Result<Option<Vec<f64>>, String> {
729 let Some(band) = biperiodic_band_range(face)? else {
730 return Ok(None);
731 };
732 let [u0, u1] = face.surface.domain_u()?;
733 let [v0, v1] = face.surface.domain_v()?;
734 let (u_lo, u_hi, v_lo, v_hi) = if band.p_is_u {
735 (u0, u1, band.q_lo, band.q_hi)
736 } else {
737 (band.q_lo, band.q_hi, v0, v1)
738 };
739 let strips: Vec<f64> = if band.rim_wobble <= BAND_RIM_LEVEL_TOLERANCE_REL {
740 kinds
741 .iter()
742 .map(|&kind| integrate_rectangle(face, u_lo, u_hi, v_lo, v_hi, kind))
743 .collect::<Result<_, _>>()?
744 } else {
745 super::winding::winding_loops_integral(face, kinds, band.p_is_u, 1)?
746 };
747 let mut out = Vec::with_capacity(kinds.len());
748 for (&kind, strip) in kinds.iter().zip(strips) {
749 let value = if band.complement {
750 integrate_untrimmed(face, kind)? - strip
751 } else {
752 strip
753 };
754 out.push(value);
755 }
756 Ok(Some(out))
757}