1use axiolid_brep::ExactBRep;
41use axiolid_core::{Frame2, Interval, Point2, Point3, Scalar, Tolerance, Vec2};
42use axiolid_curve::{Curve2, Curve3, Ellipse2, Line2, Sinusoid2};
43use axiolid_evaluate::curve::{derivative2, evaluate2, locate2, locate3, second_derivative2};
44use axiolid_evaluate::evaluate3;
45use axiolid_evaluate::surface::{evaluate as surface_point, locate};
46use axiolid_measure::FaceDomain;
47use axiolid_surface::Surface;
48use axiolid_topology::{EdgeId, FaceBound, FaceId, Orientation};
49use core::f64::consts::{PI, TAU};
50
51use crate::report::{self, ToleranceDecisionKind};
52use crate::section::SectionEdge;
53use crate::support::{periods, window};
54use crate::BooleanError;
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub enum PieceSource {
59 Boundary(EdgeId),
61 Section(usize),
63 Collapsed,
67}
68
69#[derive(Debug, Clone, PartialEq)]
72pub struct Piece {
73 pub curve: Curve3,
75 pub span: Interval,
78 pub pcurve: Curve2,
80 pub pspan: Interval,
82 pub source: PieceSource,
84}
85
86#[derive(Debug, Clone, PartialEq)]
88pub struct Region {
89 pub outer: Vec<Piece>,
91 pub holes: Vec<Vec<Piece>>,
93 pub against: bool,
97}
98
99pub fn split_face(
113 brep: &ExactBRep,
114 face: FaceId,
115 sections: &[SectionEdge],
116 first: bool,
117 cuts: &[Point3],
118 tolerance: Tolerance,
119) -> Result<Vec<Region>, BooleanError> {
120 let session = report::open(&[brep], tolerance);
121 let regions = split_face_in_session(brep, face, sections, first, cuts, tolerance)?;
122 session.finish()?;
123 Ok(regions)
124}
125
126fn split_face_in_session(
128 brep: &ExactBRep,
129 face: FaceId,
130 sections: &[SectionEdge],
131 first: bool,
132 cuts: &[Point3],
133 tolerance: Tolerance,
134) -> Result<Vec<Region>, BooleanError> {
135 let topology = brep.topology();
136 let record = topology
137 .faces()
138 .get(face.index())
139 .ok_or(BooleanError::DanglingReference)?;
140 let surface = record
141 .surface
142 .and_then(|id| brep.surfaces().get(id.index()))
143 .ok_or(BooleanError::DanglingReference)?;
144 let domain = FaceDomain::new(brep, face, tolerance)
145 .map_err(BooleanError::Measure)?
146 .ok_or(BooleanError::UnsupportedTrim)?;
147 let (lo, hi) = domain.bounds();
148
149 let mut pieces: Vec<(Piece, bool)> = Vec::new();
152 let reach = crate::bounds::face_box(surface, lo, hi, tolerance);
155 let mut ends: Vec<Point3> = cuts
156 .iter()
157 .copied()
158 .filter(|p| reach.as_ref().is_none_or(|b| b.contains(*p)))
159 .collect();
160 let mut seen: Vec<&SectionEdge> = Vec::new();
163 let mut traces = Traces::default();
164 for (index, section) in sections.iter().enumerate() {
165 ends.push(section.start);
166 ends.push(section.end);
167 let (along, other) = if first {
168 (section.along_a, §ion.other_a)
169 } else {
170 (section.along_b, §ion.other_b)
171 };
172 if along
173 || seen
174 .iter()
175 .any(|other| same_stretch(other, section, tolerance))
176 {
177 continue;
178 }
179 seen.push(section);
180 let piece = section_piece(
181 surface,
182 other,
183 section,
184 index,
185 lo,
186 hi,
187 &mut traces,
188 tolerance,
189 )?;
190 pieces.push((piece, true));
191 }
192
193 for bound in &record.bounds {
195 let uses = bound_uses(brep, bound)?;
196 let closed = close_poles(surface, uses, tolerance)?;
197 for piece in closed {
198 for part in split_use(surface, piece, &ends, tolerance)? {
199 pieces.push((part, false));
200 }
201 }
202 }
203
204 let mut pole_ends: Vec<Point2> = Vec::new();
207 for (piece, section) in &pieces {
208 if *section {
209 for t in [piece.pspan.start, piece.pspan.end] {
210 pole_ends.push(evaluate2(&piece.pcurve, t).map_err(|_| BooleanError::Evaluation)?);
211 }
212 }
213 }
214 let mut split_pieces = Vec::with_capacity(pieces.len());
215 for (piece, section) in pieces {
216 if piece.source != PieceSource::Collapsed {
217 split_pieces.push((piece, section));
218 continue;
219 }
220 let (a, b) = (
221 evaluate2(&piece.pcurve, piece.pspan.start).map_err(|_| BooleanError::Evaluation)?,
222 evaluate2(&piece.pcurve, piece.pspan.end).map_err(|_| BooleanError::Evaluation)?,
223 );
224 let slack = 1e-9 * (1.0 + a.x.abs().max(b.x.abs()));
225 let mut cuts: Vec<Scalar> = pole_ends
226 .iter()
227 .filter(|p| (p.y - a.y).abs() <= slack)
228 .filter(|p| p.x > a.x.min(b.x) + slack && p.x < a.x.max(b.x) - slack)
229 .map(|p| (p.x - a.x) / (b.x - a.x))
230 .collect();
231 cuts.sort_by(Scalar::total_cmp);
232 cuts.dedup_by(|x, y| (*x - *y).abs() <= 1e-12);
233 let mut from = piece.pspan.start;
234 for c in cuts.into_iter().chain(std::iter::once(piece.pspan.end)) {
235 split_pieces.push((
236 Piece {
237 pspan: Interval::new(from, c),
238 ..piece.clone()
239 },
240 false,
241 ));
242 from = c;
243 }
244 }
245 let mut pieces = split_pieces;
246
247 let mut swept = 0.0;
251 for (piece, section) in &pieces {
252 if !section {
253 swept += sweep(piece)?;
254 }
255 }
256 let against = swept < 0.0;
257 if against {
258 for (piece, section) in &mut pieces {
259 if !*section {
260 *piece = directed(piece, true);
261 }
262 }
263 }
264 let mut regions = trace(surface, &pieces, tolerance)?;
265 for region in &mut regions {
266 region.against = against;
267 }
268 Ok(regions)
269}
270
271fn bound_uses(brep: &ExactBRep, bound: &FaceBound) -> Result<Vec<Piece>, BooleanError> {
273 let topology = brep.topology();
274 let wire = topology
275 .loops()
276 .get(bound.loop_id.index())
277 .ok_or(BooleanError::DanglingReference)?;
278 let mut uses: Vec<Piece> = Vec::with_capacity(wire.edges.len());
279 for (index, use_) in wire.edges.iter().enumerate() {
280 let edge = &topology.edges()[use_.edge.index()];
281 let curve = edge
282 .curve
283 .and_then(|id| brep.curves3().get(id.index()))
284 .ok_or(BooleanError::DanglingReference)?
285 .clone();
286 let span = brep
287 .edge_interval(use_.edge)
288 .ok_or(BooleanError::DanglingReference)?;
289 let span = match use_.orientation {
290 Orientation::Forward => span,
291 Orientation::Reversed => Interval::new(span.end, span.start),
292 };
293 let pcurve = use_
294 .pcurve
295 .and_then(|id| brep.curves2().get(id.index()))
296 .ok_or(BooleanError::DanglingReference)?
297 .clone();
298 let pspan = brep
299 .pcurve_interval(bound.loop_id, index)
300 .ok_or(BooleanError::DanglingReference)?;
301 uses.push(Piece {
302 curve,
303 span,
304 pcurve,
305 pspan,
306 source: PieceSource::Boundary(use_.edge),
307 });
308 }
309 if bound.orientation == Orientation::Reversed {
310 uses.reverse();
311 for piece in &mut uses {
312 piece.span = Interval::new(piece.span.end, piece.span.start);
313 piece.pspan = Interval::new(piece.pspan.end, piece.pspan.start);
314 }
315 }
316 Ok(uses)
317}
318
319pub(crate) fn face_against(brep: &ExactBRep, face: FaceId) -> Result<bool, BooleanError> {
329 let record = brep
330 .topology()
331 .faces()
332 .get(face.index())
333 .ok_or(BooleanError::DanglingReference)?;
334 let mut swept = 0.0;
335 for bound in &record.bounds {
336 let uses = bound_uses(brep, bound)?;
337 for (i, piece) in uses.iter().enumerate() {
338 swept += sweep(piece)?;
339 let next = &uses[(i + 1) % uses.len()];
340 let a =
341 evaluate2(&piece.pcurve, piece.pspan.end).map_err(|_| BooleanError::Evaluation)?;
342 let b =
343 evaluate2(&next.pcurve, next.pspan.start).map_err(|_| BooleanError::Evaluation)?;
344 swept += 0.5 * (a.x * b.y - b.x * a.y);
345 }
346 }
347 Ok(swept < 0.0)
348}
349
350fn sweep(piece: &Piece) -> Result<Scalar, BooleanError> {
353 let n = 64;
354 let mut total = 0.0;
355 let mut previous =
356 evaluate2(&piece.pcurve, piece.pspan.start).map_err(|_| BooleanError::Evaluation)?;
357 for i in 1..=n {
358 let p =
359 piece.pspan.start + (piece.pspan.end - piece.pspan.start) * i as Scalar / n as Scalar;
360 let q = evaluate2(&piece.pcurve, p).map_err(|_| BooleanError::Evaluation)?;
361 total += 0.5 * (previous.x * q.y - q.x * previous.y);
362 previous = q;
363 }
364 Ok(total)
365}
366
367fn close_poles(
372 surface: &Surface,
373 uses: Vec<Piece>,
374 tolerance: Tolerance,
375) -> Result<Vec<Piece>, BooleanError> {
376 let n = uses.len();
377 let mut out = Vec::with_capacity(n + 2);
378 for i in 0..n {
379 let next = &uses[(i + 1) % n];
380 let a =
381 evaluate2(&uses[i].pcurve, uses[i].pspan.end).map_err(|_| BooleanError::Evaluation)?;
382 let b = evaluate2(&next.pcurve, next.pspan.start).map_err(|_| BooleanError::Evaluation)?;
383 out.push(uses[i].clone());
384 let slack = 1e-7 * (1.0 + a.x.abs().max(a.y.abs()));
385 if (a - b).length() <= slack {
386 continue;
387 }
388 let pa = axiolid_evaluate::surface::evaluate(surface, a.x, a.y)
389 .map_err(|_| BooleanError::Evaluation)?;
390 let pb = axiolid_evaluate::surface::evaluate(surface, b.x, b.y)
391 .map_err(|_| BooleanError::Evaluation)?;
392 let (su, sv) = axiolid_evaluate::surface::partials(surface, a.x, a.y)
393 .map_err(|_| BooleanError::Evaluation)?;
394 let scale = 1.0 + sv.length();
395 let pole = su.length() <= 1e-9 * scale
396 && report::near(
397 ToleranceDecisionKind::IncidentPoint,
398 (pa - pb).length(),
399 tolerance,
400 );
401 if !pole {
402 return Err(BooleanError::UnclosedSplit);
405 }
406 out.push(Piece {
407 curve: Curve3::Line(axiolid_curve::Line3 {
408 origin: pa,
409 direction: axiolid_core::Vec3::ZERO,
410 }),
411 span: Interval::new(0.0, 1.0),
412 pcurve: Curve2::Line(Line2 {
413 origin: a,
414 direction: b - a,
415 }),
416 pspan: Interval::new(0.0, 1.0),
417 source: PieceSource::Collapsed,
418 });
419 }
420 Ok(out)
421}
422
423fn same_stretch(a: &SectionEdge, b: &SectionEdge, tolerance: Tolerance) -> bool {
425 let gap = |p: Point3, q: Point3| (p - q).length();
426 let mid = |e: &SectionEdge| evaluate3(&e.curve, 0.5 * (e.span.start + e.span.end));
427 let (Ok(ma), Ok(mb)) = (mid(a), mid(b)) else {
428 return false;
429 };
430 let residue = gap(a.start, b.start)
431 .max(gap(a.end, b.end))
432 .min(gap(a.start, b.end).max(gap(a.end, b.start)))
433 .max(gap(ma, mb));
434 report::near(ToleranceDecisionKind::MergedPoints, residue, tolerance)
435}
436
437#[derive(Default)]
440struct Traces {
441 done: Vec<(Surface, Option<Vec<axiolid_curve::ImplicitCurve2>>)>,
442}
443
444impl Traces {
445 fn of(
446 &mut self,
447 surface: &Surface,
448 other: &Surface,
449 lo: Point2,
450 hi: Point2,
451 ) -> Result<&[axiolid_curve::ImplicitCurve2], BooleanError> {
452 let index = match self.done.iter().position(|(s, _)| s == other) {
453 Some(index) => index,
454 None => {
455 let curves =
456 axiolid_nurbs::trace_section_pcurves(surface, other, window(surface, lo, hi))
457 .ok();
458 self.done.push((other.clone(), curves));
459 self.done.len() - 1
460 }
461 };
462 self.done[index]
463 .1
464 .as_deref()
465 .ok_or(BooleanError::UnsupportedSplit)
466 }
467}
468
469#[allow(clippy::too_many_arguments)]
475fn section_piece(
476 surface: &Surface,
477 other: &Surface,
478 section: &SectionEdge,
479 index: usize,
480 lo: Point2,
481 hi: Point2,
482 traces: &mut Traces,
483 tolerance: Tolerance,
484) -> Result<Piece, BooleanError> {
485 let closed_form = iso_curve(surface, §ion.curve, tolerance)
486 || matches!(
487 (surface, §ion.curve),
488 (
489 Surface::Plane(_),
490 Curve3::Line(_) | Curve3::Circle(_) | Curve3::Ellipse(_)
491 ) | (
492 Surface::Cylinder(_) | Surface::EllipticalCylinder(_),
493 Curve3::Line(_) | Curve3::Circle(_) | Curve3::Ellipse(_)
494 )
495 );
496 if !closed_form {
497 if let Some(piece) = own_piece(surface, section, index, lo, hi) {
498 return Ok(piece);
499 }
500 return implicit_piece(surface, other, section, index, lo, hi, traces, tolerance);
501 }
502 let first = match place(surface, section.start, lo, hi, tolerance) {
504 Ok(p) => p,
505 Err(_) => {
506 let t = section.span.start + 0.01 * (section.span.end - section.span.start);
507 let p = evaluate3(§ion.curve, t).map_err(|_| BooleanError::Evaluation)?;
508 place(surface, p, lo, hi, tolerance)?
509 }
510 };
511 let slack = 1e-9 * (1.0 + lo.x.abs().max(hi.x.abs()));
512 let mut candidates = vec![first];
513 if periods(surface).0 {
514 for shift in [TAU, -TAU] {
515 let other_turn = Point2::new(first.x + shift, first.y);
516 if other_turn.x >= lo.x - slack && other_turn.x <= hi.x + slack {
517 candidates.push(other_turn);
518 }
519 }
520 }
521 let mut fallback = None;
522 for start_uv in candidates {
523 let piece = section_piece_from(surface, section, index, start_uv, tolerance)?;
524 let mid = evaluate2(&piece.pcurve, 0.5 * (piece.pspan.start + piece.pspan.end))
525 .map_err(|_| BooleanError::Evaluation)?;
526 if mid.x >= lo.x - slack && mid.x <= hi.x + slack {
527 return Ok(piece);
528 }
529 fallback.get_or_insert(piece);
530 }
531 fallback.ok_or(BooleanError::Evaluation)
532}
533
534fn carrier_of(surface: &Surface) -> Option<axiolid_curve::Carrier> {
537 Some(match surface {
538 Surface::Plane(p) => axiolid_curve::Carrier::Plane(p.frame),
539 Surface::Cylinder(c) => axiolid_curve::Carrier::Ruled(axiolid_curve::RuledCarrier {
540 frame: c.frame,
541 x_radius: c.radius,
542 y_radius: c.radius,
543 slope: 0.0,
544 }),
545 Surface::EllipticalCylinder(c) => {
546 axiolid_curve::Carrier::Ruled(axiolid_curve::RuledCarrier {
547 frame: c.frame,
548 x_radius: c.semi_axis_x,
549 y_radius: c.semi_axis_y,
550 slope: 0.0,
551 })
552 }
553 Surface::Cone(c) => axiolid_curve::Carrier::Ruled(axiolid_curve::RuledCarrier {
554 frame: c.frame,
555 x_radius: c.radius,
556 y_radius: c.radius,
557 slope: c.semi_angle.tan(),
558 }),
559 Surface::Sphere(s) => axiolid_curve::Carrier::Sphere {
560 frame: s.frame,
561 radius: s.radius,
562 },
563 Surface::Torus(t) => axiolid_curve::Carrier::Torus(axiolid_curve::TorusCarrier {
564 frame: t.frame,
565 major_radius: t.major_radius,
566 minor_radius: t.minor_radius,
567 }),
568 Surface::BSpline(b) => axiolid_curve::Carrier::Spline(Box::new(b.clone())),
571 _ => return None,
572 })
573}
574
575fn own_piece(
582 surface: &Surface,
583 section: &SectionEdge,
584 index: usize,
585 lo: Point2,
586 hi: Point2,
587) -> Option<Piece> {
588 let Curve3::ImplicitSection(traced) = §ion.curve else {
589 return None;
590 };
591 if carrier_of(surface).as_ref() != Some(&traced.carrier) {
592 return None;
593 }
594 let (pu, pv) = periods(surface);
595 let middle = traced
596 .curve
597 .point(0.5 * (section.span.start + section.span.end))?;
598 let into = |x: Scalar, a: Scalar, b: Scalar, periodic: bool| {
599 if !periodic || (x >= a - 1e-9 && x <= b + 1e-9) {
600 0.0
601 } else {
602 ((0.5 * (a + b) - x) / TAU).round() * TAU
603 }
604 };
605 let pcurve = traced.curve.shifted(
606 into(middle.x, lo.x, hi.x, pu),
607 into(middle.y, lo.y, hi.y, pv),
608 );
609 Some(Piece {
610 curve: section.curve.clone(),
611 span: section.span,
612 pcurve: Curve2::Implicit(pcurve),
613 pspan: section.span,
614 source: PieceSource::Section(index),
615 })
616}
617
618fn lifted_piece(
622 surface: &Surface,
623 section: &SectionEdge,
624 index: usize,
625 lo: Point2,
626 hi: Point2,
627 tolerance: Tolerance,
628) -> Result<Piece, BooleanError> {
629 let carrier = carrier_of(surface).ok_or(BooleanError::UnsupportedSplit)?;
630 if let Curve3::PairSection(pair) = §ion.curve {
631 let first = pair.side(&carrier).ok_or(BooleanError::UnsupportedSplit)?;
632 let (t0, t1) = (section.span.start, section.span.end);
633 let n = 4 * pair.nodes.len();
634 let mut guide = Vec::with_capacity(n + 1);
635 for i in 0..=n {
636 let (a, b, _) = pair
637 .solve(t0 + (t1 - t0) * i as Scalar / n as Scalar)
638 .ok_or(BooleanError::Evaluation)?;
639 guide.push(if first { a } else { b });
640 }
641 return Ok(Piece {
642 curve: section.curve.clone(),
643 span: section.span,
644 pcurve: Curve2::Lifted(axiolid_curve::LiftedCurve2 {
645 curve: Box::new(section.curve.clone()),
646 carrier,
647 start: t0,
648 end: t1,
649 guide,
650 }),
651 pspan: section.span,
652 source: PieceSource::Section(index),
653 });
654 }
655 let (pu, pv) = periods(surface);
656 let n = 128;
657 let (t0, t1) = (section.span.start, section.span.end);
658 let mut guide: Vec<Point2> = Vec::with_capacity(n + 1);
659 for i in 0..=n {
660 let t = t0 + (t1 - t0) * i as Scalar / n as Scalar;
661 let p = evaluate3(§ion.curve, t).map_err(|_| BooleanError::Evaluation)?;
662 let (mut u, mut v) =
663 locate(surface, p, report::floored(tolerance)).map_err(|_| BooleanError::Evaluation)?;
664 if let Some(last) = guide.last() {
665 if pu {
666 u += ((last.x - u) / TAU).round() * TAU;
667 }
668 if pv {
669 v += ((last.y - v) / TAU).round() * TAU;
670 }
671 }
672 guide.push(Point2::new(u, v));
673 }
674 let middle = guide[n / 2];
676 let into = |x: Scalar, a: Scalar, b: Scalar, periodic: bool| {
677 if !periodic || (x >= a - 1e-9 && x <= b + 1e-9) {
678 0.0
679 } else {
680 ((0.5 * (a + b) - x) / TAU).round() * TAU
681 }
682 };
683 let shift = Vec2::new(
684 into(middle.x, lo.x, hi.x, pu),
685 into(middle.y, lo.y, hi.y, pv),
686 );
687 for p in &mut guide {
688 *p += shift;
689 }
690 Ok(Piece {
691 curve: section.curve.clone(),
692 span: section.span,
693 pcurve: Curve2::Lifted(axiolid_curve::LiftedCurve2 {
694 curve: Box::new(section.curve.clone()),
695 carrier,
696 start: t0,
697 end: t1,
698 guide,
699 }),
700 pspan: section.span,
701 source: PieceSource::Section(index),
702 })
703}
704
705#[allow(clippy::too_many_arguments)]
709fn implicit_piece(
710 surface: &Surface,
711 other: &Surface,
712 section: &SectionEdge,
713 index: usize,
714 lo: Point2,
715 hi: Point2,
716 traces: &mut Traces,
717 tolerance: Tolerance,
718) -> Result<Piece, BooleanError> {
719 let uv = |p: Point3| -> Result<Point2, BooleanError> {
720 let (u, v) =
721 locate(surface, p, report::floored(tolerance)).map_err(|_| BooleanError::Evaluation)?;
722 Ok(Point2::new(u, v))
723 };
724 let at = |f: Scalar| {
725 evaluate3(
726 §ion.curve,
727 section.span.start + f * (section.span.end - section.span.start),
728 )
729 .map_err(|_| BooleanError::Evaluation)
730 };
731 let closed = report::near(
732 ToleranceDecisionKind::MergedPoints,
733 (section.start - section.end).length(),
734 tolerance,
735 );
736 let curves = match traces.of(surface, other, lo, hi) {
737 Ok(curves) => curves,
738 Err(BooleanError::UnsupportedSplit) => {
743 return lifted_piece(surface, section, index, lo, hi, tolerance);
744 }
745 Err(e) => return Err(e),
746 };
747 let stretch = axiolid_nurbs::extract_stretch(
748 curves,
749 periods(surface),
750 uv(section.start)?,
751 [uv(at(1.0 / 3.0)?)?, uv(at(2.0 / 3.0)?)?],
752 uv(section.end)?,
753 closed,
754 )
755 .ok_or(BooleanError::UnsupportedSplit)?;
756 let (pu, pv) = periods(surface);
758 let middle = stretch
759 .point(0.5 * stretch.end())
760 .ok_or(BooleanError::Evaluation)?;
761 let into = |x: Scalar, a: Scalar, b: Scalar, periodic: bool| {
762 if !periodic || (x >= a - 1e-9 && x <= b + 1e-9) {
763 return 0.0;
764 }
765 let k = ((0.5 * (a + b) - x) / TAU).round();
766 k * TAU
767 };
768 let stretch = stretch.shifted(
769 into(middle.x, lo.x, hi.x, pu),
770 into(middle.y, lo.y, hi.y, pv),
771 );
772 let end = stretch.end();
773 Ok(Piece {
774 curve: section.curve.clone(),
775 span: section.span,
776 pcurve: Curve2::Implicit(stretch),
777 pspan: Interval::new(0.0, end),
778 source: PieceSource::Section(index),
779 })
780}
781
782fn iso_curve(surface: &Surface, curve: &Curve3, tolerance: Tolerance) -> bool {
787 let parallel = |a: axiolid_core::Vec3, b: axiolid_core::Vec3| {
788 a.normalize().cross(b.normalize()).length() <= 1e-12
789 };
790 let off_axis =
791 |p: Point3, o: Point3, z: axiolid_core::Vec3| (p - o).cross(z.normalize()).length();
792 let residue: Option<Scalar> = match (surface, curve) {
795 (Surface::Sphere(sp), Curve3::Circle(c)) => {
796 let n = c.frame.x.cross(c.frame.y);
797 let meridian = (n.normalize().dot(sp.frame.z.normalize()).abs() <= 1e-12).then(|| {
798 (c.frame.origin - sp.frame.origin)
799 .length()
800 .max((c.radius - sp.radius).abs())
801 });
802 let latitude = parallel(n, sp.frame.z)
803 .then(|| off_axis(c.frame.origin, sp.frame.origin, sp.frame.z));
804 least(meridian, latitude)
805 }
806 (Surface::Cone(k), Curve3::Line(l)) => {
807 let slope = k.semi_angle.tan();
808 let apex = k.frame.origin - k.frame.z.normalize() * (k.radius / slope);
809 let d = l.direction.normalize();
810 let axis = k.frame.z.normalize();
811 ((d.dot(axis).abs() - k.semi_angle.cos().abs()).abs() <= 1e-12)
812 .then(|| (apex - l.origin).cross(d).length())
813 }
814 (Surface::Cone(k), Curve3::Circle(c)) => {
815 let n = c.frame.x.cross(c.frame.y);
816 parallel(n, k.frame.z).then(|| off_axis(c.frame.origin, k.frame.origin, k.frame.z))
817 }
818 (Surface::Torus(t), Curve3::Circle(c)) => {
819 let n = c.frame.x.cross(c.frame.y);
820 let z = t.frame.z.normalize();
821 let ring = parallel(n, z).then(|| off_axis(c.frame.origin, t.frame.origin, z));
822 let d = c.frame.origin - t.frame.origin;
823 let tube = (n.normalize().dot(z).abs() <= 1e-12).then(|| {
824 d.dot(z)
825 .abs()
826 .max((d.length() - t.major_radius).abs())
827 .max((c.radius - t.minor_radius).abs())
828 });
829 least(ring, tube)
830 }
831 _ => None,
832 };
833 residue.is_some_and(|r| report::near(ToleranceDecisionKind::IsoCurve, r, tolerance))
834}
835
836fn least(a: Option<Scalar>, b: Option<Scalar>) -> Option<Scalar> {
838 match (a, b) {
839 (Some(a), Some(b)) => Some(a.min(b)),
840 (a, b) => a.or(b),
841 }
842}
843
844fn affine_pcurve(
848 surface: &Surface,
849 section: &SectionEdge,
850 start_uv: Point2,
851 tolerance: Tolerance,
852) -> Result<(Curve2, Interval), BooleanError> {
853 let (t0, t1) = (section.span.start, section.span.end);
854 let (ta, tb) = (t0 + 0.25 * (t1 - t0), t0 + 0.75 * (t1 - t0));
855 let (pu, pv) = periods(surface);
856 let uv = |t: Scalar, near: Point2| -> Result<Point2, BooleanError> {
857 let p = evaluate3(§ion.curve, t).map_err(|_| BooleanError::Evaluation)?;
858 let (mut u, mut v) =
859 locate(surface, p, report::floored(tolerance)).map_err(|_| BooleanError::Evaluation)?;
860 if pu {
861 u += ((near.x - u) / TAU).round() * TAU;
862 }
863 if pv {
864 v += ((near.y - v) / TAU).round() * TAU;
865 }
866 Ok(Point2::new(u, v))
867 };
868 let a = uv(ta, start_uv)?;
869 let b = uv(tb, a)?;
870 let direction = (b - a) / (tb - ta);
871 Ok((
872 Curve2::Line(Line2 {
873 origin: a - direction * ta,
874 direction,
875 }),
876 section.span,
877 ))
878}
879
880fn section_piece_from(
882 surface: &Surface,
883 section: &SectionEdge,
884 index: usize,
885 start_uv: Point2,
886 tolerance: Tolerance,
887) -> Result<Piece, BooleanError> {
888 let (pcurve, pspan) = match (surface, §ion.curve) {
889 _ if iso_curve(surface, §ion.curve, tolerance) => {
890 affine_pcurve(surface, section, start_uv, tolerance)?
891 }
892 (Surface::Plane(p), curve) => {
893 let f = p.frame;
894 let local = |q: Point3| Point2::new((q - f.origin).dot(f.x), (q - f.origin).dot(f.y));
895 let dir = |d: axiolid_core::Vec3| Vec2::new(d.dot(f.x), d.dot(f.y));
896 let pcurve = match curve {
897 Curve3::Line(l) => Curve2::Line(Line2 {
898 origin: local(l.origin),
899 direction: dir(l.direction),
900 }),
901 Curve3::Circle(c) => Curve2::Ellipse(Ellipse2 {
902 frame: Frame2 {
903 origin: local(c.frame.origin),
904 x: dir(c.frame.x),
905 y: dir(c.frame.y),
906 },
907 semi_axis_x: c.radius,
908 semi_axis_y: c.radius,
909 }),
910 Curve3::Ellipse(e) => Curve2::Ellipse(Ellipse2 {
911 frame: Frame2 {
912 origin: local(e.frame.origin),
913 x: dir(e.frame.x),
914 y: dir(e.frame.y),
915 },
916 semi_axis_x: e.semi_axis_x,
917 semi_axis_y: e.semi_axis_y,
918 }),
919 _ => return Err(BooleanError::UnsupportedSplit),
920 };
921 (pcurve, section.span)
923 }
924 (Surface::Cylinder(c), Curve3::Line(l)) => {
925 let pcurve = Curve2::Line(Line2 {
927 origin: start_uv - Vec2::new(0.0, l.direction.dot(c.frame.z)) * section.span.start,
928 direction: Vec2::new(0.0, l.direction.dot(c.frame.z)),
929 });
930 (pcurve, section.span)
931 }
932 (Surface::Cylinder(c), Curve3::Circle(circle)) => {
933 let turn = circle.frame.x.cross(circle.frame.y).dot(c.frame.z).signum();
936 let pcurve = Curve2::Line(Line2 {
937 origin: start_uv - Vec2::new(turn, 0.0) * section.span.start,
938 direction: Vec2::new(turn, 0.0),
939 });
940 (pcurve, section.span)
941 }
942 (Surface::Cylinder(c), Curve3::Ellipse(ellipse)) => {
943 let n = ellipse.frame.x.cross(ellipse.frame.y);
946 let nz = n.dot(c.frame.z);
947 if nz == 0.0 {
948 return Err(BooleanError::UnsupportedSplit);
949 }
950 let wave = Sinusoid2 {
951 mean: n.dot(ellipse.frame.origin - c.frame.origin) / nz,
952 cosine: -c.radius * n.dot(c.frame.x) / nz,
953 sine: -c.radius * n.dot(c.frame.y) / nz,
954 };
955 let pcurve = Curve2::Sinusoid(wave);
956 let span = angle_span(surface, section, start_uv.x, tolerance)?;
957 (pcurve, span)
958 }
959 (Surface::EllipticalCylinder(c), Curve3::Line(l)) => {
960 let rise = l.direction.dot(c.frame.z);
962 let pcurve = Curve2::Line(Line2 {
963 origin: start_uv - Vec2::new(0.0, rise) * section.span.start,
964 direction: Vec2::new(0.0, rise),
965 });
966 (pcurve, section.span)
967 }
968 (Surface::EllipticalCylinder(c), Curve3::Circle(_) | Curve3::Ellipse(_)) => {
969 let (origin, n) = match §ion.curve {
974 Curve3::Circle(circle) => {
975 (circle.frame.origin, circle.frame.x.cross(circle.frame.y))
976 }
977 Curve3::Ellipse(ellipse) => {
978 (ellipse.frame.origin, ellipse.frame.x.cross(ellipse.frame.y))
979 }
980 _ => return Err(BooleanError::UnsupportedSplit),
981 };
982 let nz = n.dot(c.frame.z);
983 if nz == 0.0 {
984 return Err(BooleanError::UnsupportedSplit);
985 }
986 let wave = Sinusoid2 {
987 mean: n.dot(origin - c.frame.origin) / nz,
988 cosine: -c.semi_axis_x * n.dot(c.frame.x) / nz,
989 sine: -c.semi_axis_y * n.dot(c.frame.y) / nz,
990 };
991 let span = angle_span(surface, section, start_uv.x, tolerance)?;
992 (Curve2::Sinusoid(wave), span)
993 }
994 _ => return Err(BooleanError::UnsupportedSplit),
995 };
996 Ok(Piece {
997 curve: section.curve.clone(),
998 span: section.span,
999 pcurve,
1000 pspan,
1001 source: PieceSource::Section(index),
1002 })
1003}
1004
1005fn angle_span(
1008 surface: &Surface,
1009 section: &SectionEdge,
1010 start: Scalar,
1011 tolerance: Tolerance,
1012) -> Result<Interval, BooleanError> {
1013 let at = |t: Scalar| -> Result<Scalar, BooleanError> {
1014 let p = evaluate3(§ion.curve, t).map_err(|_| BooleanError::Evaluation)?;
1015 Ok(locate(surface, p, report::floored(tolerance))
1016 .map_err(|_| BooleanError::Evaluation)?
1017 .0)
1018 };
1019 let near = |raw: Scalar, reference: Scalar| raw + TAU * ((reference - raw) / TAU).round();
1020 let (t0, t1) = (section.span.start, section.span.end);
1021 let full = (t1 - t0).abs() >= TAU - 1e-12;
1022 let q1 = near(at(t0 + 0.25 * (t1 - t0))?, start);
1023 let q2 = near(at(t0 + 0.5 * (t1 - t0))?, q1);
1024 let q3 = near(at(t0 + 0.75 * (t1 - t0))?, q2);
1025 let end = if full {
1026 start + TAU * (q1 - start).signum()
1027 } else {
1028 near(at(t1)?, q3)
1029 };
1030 Ok(Interval::new(start, end))
1031}
1032
1033fn place(
1036 surface: &Surface,
1037 point: Point3,
1038 lo: Point2,
1039 hi: Point2,
1040 tolerance: Tolerance,
1041) -> Result<Point2, BooleanError> {
1042 let (mut u, mut v) =
1043 locate(surface, point, report::floored(tolerance)).map_err(|_| BooleanError::Evaluation)?;
1044 let (pu, pv) = periods(surface);
1045 let slack = 1e-9;
1046 let wrap = |x: &mut Scalar, a: Scalar, b: Scalar| {
1047 while *x < a - slack {
1048 *x += TAU;
1049 }
1050 while *x > b + slack {
1051 *x -= TAU;
1052 }
1053 };
1054 if pu {
1055 wrap(&mut u, lo.x, hi.x);
1056 }
1057 if pv {
1058 wrap(&mut v, lo.y, hi.y);
1059 }
1060 Ok(Point2::new(u, v))
1061}
1062
1063fn split_use(
1065 surface: &Surface,
1066 piece: Piece,
1067 ends: &[Point3],
1068 tolerance: Tolerance,
1069) -> Result<Vec<Piece>, BooleanError> {
1070 let mut cuts: Vec<(Scalar, Scalar)> = Vec::new();
1071 let (lo, hi) = (
1072 piece.span.start.min(piece.span.end),
1073 piece.span.start.max(piece.span.end),
1074 );
1075 let slack = 1e-9 * (1.0 + lo.abs().max(hi.abs()));
1076 let mut taken = Vec::with_capacity(ends.len() + 2);
1081 for t in [piece.span.start, piece.span.end] {
1082 taken.push(evaluate3(&piece.curve, t).map_err(|_| BooleanError::Evaluation)?);
1083 }
1084 for &point in ends {
1085 let nearest = taken
1086 .iter()
1087 .map(|q: &Point3| (point - *q).length())
1088 .fold(Scalar::INFINITY, Scalar::min);
1089 if report::near(ToleranceDecisionKind::MergedPoints, nearest, tolerance) {
1090 continue;
1091 }
1092 let Ok(t) = locate3(&piece.curve, point, report::floored(tolerance)) else {
1093 continue;
1094 };
1095 let t = if matches!(piece.curve, Curve3::Circle(_) | Curve3::Ellipse(_)) {
1096 [t - TAU, t, t + TAU, t + 2.0 * TAU]
1097 .into_iter()
1098 .find(|c| *c > lo + slack && *c < hi - slack)
1099 } else {
1100 (t > lo + slack && t < hi - slack).then_some(t)
1101 };
1102 let Some(t) = t else { continue };
1103 let on = evaluate3(&piece.curve, t).map_err(|_| BooleanError::Evaluation)?;
1104 if !report::near(
1105 ToleranceDecisionKind::IncidentPoint,
1106 (on - point).length(),
1107 tolerance,
1108 ) {
1109 continue;
1110 }
1111 let (u, v) = locate(surface, point, report::floored(tolerance))
1113 .map_err(|_| BooleanError::Evaluation)?;
1114 let (pu, pv) = periods(surface);
1115 let turns: &[Scalar] = &[0.0, TAU, -TAU, 2.0 * TAU, -2.0 * TAU];
1116 let mut shifts = Vec::new();
1117 for &a in if pu { turns } else { &turns[..1] } {
1118 for &b in if pv { turns } else { &turns[..1] } {
1119 shifts.push((a, b));
1120 }
1121 }
1122 let mut found = None;
1123 for (du, dv) in shifts {
1124 if let Ok(p) = locate2(
1125 &piece.pcurve,
1126 Point2::new(u + du, v + dv),
1127 report::floored(tolerance),
1128 ) {
1129 let (plo, phi) = (
1130 piece.pspan.start.min(piece.pspan.end),
1131 piece.pspan.start.max(piece.pspan.end),
1132 );
1133 let lands = |c: Scalar| -> Result<bool, BooleanError> {
1141 if c < plo - slack || c > phi + slack {
1142 return Ok(false);
1143 }
1144 let at = evaluate2(&piece.pcurve, c).map_err(|_| BooleanError::Evaluation)?;
1145 let back =
1146 surface_point(surface, at.x, at.y).map_err(|_| BooleanError::Evaluation)?;
1147 Ok((back - on).length() <= 2.0 * report::floored(tolerance).linear())
1148 };
1149 for c in [p - TAU, p, p + TAU] {
1150 if lands(c)? {
1151 found = Some(c);
1152 break;
1153 }
1154 }
1155 if found.is_some() {
1156 break;
1157 }
1158 }
1159 }
1160 let p = found.ok_or(BooleanError::Evaluation)?;
1161 if !cuts.iter().any(|(c, _)| (c - t).abs() <= slack) {
1162 cuts.push((t, p));
1163 taken.push(on);
1164 }
1165 }
1166 if cuts.is_empty() {
1167 return Ok(vec![piece]);
1168 }
1169 let forward = piece.span.end >= piece.span.start;
1170 cuts.sort_by(|a, b| {
1171 let order = a.0.total_cmp(&b.0);
1172 if forward {
1173 order
1174 } else {
1175 order.reverse()
1176 }
1177 });
1178 let mut out = Vec::with_capacity(cuts.len() + 1);
1179 let (mut t0, mut p0) = (piece.span.start, piece.pspan.start);
1180 for (t, p) in cuts {
1181 out.push(Piece {
1182 span: Interval::new(t0, t),
1183 pspan: Interval::new(p0, p),
1184 ..piece.clone()
1185 });
1186 (t0, p0) = (t, p);
1187 }
1188 out.push(Piece {
1189 span: Interval::new(t0, piece.span.end),
1190 pspan: Interval::new(p0, piece.pspan.end),
1191 ..piece
1192 });
1193 Ok(out)
1194}
1195
1196#[derive(Debug, Clone, Copy)]
1198struct Half {
1199 piece: usize,
1200 reversed: bool,
1201 from: usize,
1202 to: usize,
1203 leave: Scalar,
1205 arrive: Scalar,
1206 bend_leave: Scalar,
1209 bend_arrive: Scalar,
1210 probe_leave: [Point2; 3],
1215 probe_arrive: [Point2; 3],
1216}
1217
1218const PROBES: [Scalar; 3] = [1e-4, 1e-3, 1e-2];
1220
1221fn directed(piece: &Piece, reversed: bool) -> Piece {
1222 if reversed {
1223 Piece {
1224 span: Interval::new(piece.span.end, piece.span.start),
1225 pspan: Interval::new(piece.pspan.end, piece.pspan.start),
1226 ..piece.clone()
1227 }
1228 } else {
1229 piece.clone()
1230 }
1231}
1232
1233fn angle_of(v: Vec2) -> Scalar {
1234 v.y.atan2(v.x)
1235}
1236
1237fn tangent(piece: &Piece, at_start: bool) -> Result<Vec2, BooleanError> {
1240 let p = if at_start {
1241 piece.pspan.start
1242 } else {
1243 piece.pspan.end
1244 };
1245 let d = derivative2(&piece.pcurve, p).map_err(|_| BooleanError::Evaluation)?;
1246 let sign = if piece.pspan.end >= piece.pspan.start {
1247 1.0
1248 } else {
1249 -1.0
1250 };
1251 Ok(d * sign)
1252}
1253
1254fn bend(piece: &Piece, at_start: bool) -> Result<Scalar, BooleanError> {
1257 let p = if at_start {
1258 piece.pspan.start
1259 } else {
1260 piece.pspan.end
1261 };
1262 let d = derivative2(&piece.pcurve, p).map_err(|_| BooleanError::Evaluation)?;
1263 let dd = second_derivative2(&piece.pcurve, p).map_err(|_| BooleanError::Evaluation)?;
1264 let speed = d.length();
1265 if speed == 0.0 {
1266 return Err(BooleanError::Evaluation);
1267 }
1268 let sign = if piece.pspan.end >= piece.pspan.start {
1270 1.0
1271 } else {
1272 -1.0
1273 };
1274 Ok(sign * (d.x * dd.y - d.y * dd.x) / (speed * speed * speed))
1275}
1276
1277fn trace(
1278 surface: &Surface,
1279 pieces: &[(Piece, bool)],
1280 tolerance: Tolerance,
1281) -> Result<Vec<Region>, BooleanError> {
1282 let reach = report::floored(tolerance).linear();
1289 let mut vertices: Vec<(Point2, Point3)> = Vec::new();
1290 let mut vertex = |p: Point2| -> Result<usize, BooleanError> {
1291 let slack = 1e-7 * (1.0 + p.x.abs().max(p.y.abs()));
1292 let at = surface_point(surface, p.x, p.y).map_err(|_| BooleanError::Evaluation)?;
1293 if let Some(i) = vertices
1294 .iter()
1295 .position(|(q, b)| (*q - p).length() <= slack && (*b - at).length() <= reach)
1296 {
1297 return Ok(i);
1298 }
1299 vertices.push((p, at));
1300 Ok(vertices.len() - 1)
1301 };
1302 let mut halves = Vec::new();
1303 for (index, (piece, both_ways)) in pieces.iter().enumerate() {
1304 let a =
1305 evaluate2(&piece.pcurve, piece.pspan.start).map_err(|_| BooleanError::Evaluation)?;
1306 let b = evaluate2(&piece.pcurve, piece.pspan.end).map_err(|_| BooleanError::Evaluation)?;
1307 let (va, vb) = (vertex(a)?, vertex(b)?);
1308 let leave = angle_of(tangent(piece, true)?);
1309 let arrive = angle_of(tangent(piece, false)?);
1310 let (bend_leave, bend_arrive) = (bend(piece, true)?, bend(piece, false)?);
1311 let (t0, t1) = (piece.pspan.start, piece.pspan.end);
1312 let probe = |f: Scalar| -> Result<Point2, BooleanError> {
1313 evaluate2(&piece.pcurve, t0 + (t1 - t0) * f).map_err(|_| BooleanError::Evaluation)
1314 };
1315 let near_start = [probe(PROBES[0])?, probe(PROBES[1])?, probe(PROBES[2])?];
1316 let near_end = [
1317 probe(1.0 - PROBES[0])?,
1318 probe(1.0 - PROBES[1])?,
1319 probe(1.0 - PROBES[2])?,
1320 ];
1321 halves.push(Half {
1322 piece: index,
1323 reversed: false,
1324 from: va,
1325 to: vb,
1326 leave,
1327 arrive,
1328 bend_leave,
1329 bend_arrive,
1330 probe_leave: near_start,
1331 probe_arrive: near_end,
1332 });
1333 if *both_ways {
1334 halves.push(Half {
1335 piece: index,
1336 reversed: true,
1337 from: vb,
1338 to: va,
1339 leave: arrive + PI,
1340 arrive: leave + PI,
1341 bend_leave: -bend_arrive,
1342 bend_arrive: -bend_leave,
1343 probe_leave: near_end,
1344 probe_arrive: near_start,
1345 });
1346 }
1347 }
1348
1349 let mut used = vec![false; halves.len()];
1351 let mut loops: Vec<Vec<usize>> = Vec::new();
1352 for first in 0..halves.len() {
1353 if used[first] {
1354 continue;
1355 }
1356 let mut walk = Vec::new();
1357 let mut current = first;
1358 loop {
1359 if used[current] {
1360 if current == first {
1361 break;
1362 }
1363 return Err(BooleanError::UnclosedSplit);
1364 }
1365 used[current] = true;
1366 walk.push(current);
1367 let here = halves[current];
1368 let back = here.arrive + PI;
1373 let back_bend = -here.bend_arrive;
1374 let at = vertices[here.to].0;
1375 let chord_turns = |c: &Half| -> [Scalar; 3] {
1378 [0, 1, 2].map(|k| {
1379 let (b, o) = (here.probe_arrive[k] - at, c.probe_leave[k] - at);
1380 (angle_of(b) - angle_of(o)).rem_euclid(TAU)
1381 })
1382 };
1383 let mut keyed: Vec<((Scalar, Scalar), usize)> = Vec::new();
1384 for (index, candidate) in halves.iter().enumerate() {
1385 if candidate.from != here.to {
1386 continue;
1387 }
1388 let is_twin = candidate.piece == here.piece && candidate.reversed != here.reversed;
1389 let turn = (back - candidate.leave).rem_euclid(TAU);
1390 let delta = back_bend - candidate.bend_leave;
1393 let key = if is_twin {
1394 (TAU, Scalar::INFINITY)
1395 } else if !(1e-9..=TAU - 1e-9).contains(&turn) {
1396 if delta.abs() <= 1e-9 * (1.0 + back_bend.abs()) {
1397 let c = chord_turns(candidate);
1400 let Some(t) = c.into_iter().find(|t| *t > 1e-12 && *t < TAU - 1e-12) else {
1401 return Err(BooleanError::TangentSplit);
1402 };
1403 keyed.push(((if t < PI { t.min(1e-10) } else { TAU }, t), index));
1404 continue;
1405 }
1406 if delta > 0.0 {
1407 (0.0, delta)
1408 } else {
1409 (TAU, delta)
1410 }
1411 } else {
1412 (turn, delta)
1413 };
1414 keyed.push((key, index));
1415 }
1416 let same_turn = |x: Scalar, y: Scalar| (x - y).abs() < 1e-9;
1417 let same_bend =
1418 |x: Scalar, y: Scalar| (x - y).abs() <= 1e-9 * (1.0 + x.abs().min(1e12));
1419 let chords = |a: usize, b: usize| -> core::cmp::Ordering {
1422 let (ca, cb) = (chord_turns(&halves[a]), chord_turns(&halves[b]));
1423 for k in 0..3 {
1424 if (ca[k] - cb[k]).abs() > 1e-12 {
1425 return ca[k].total_cmp(&cb[k]);
1426 }
1427 }
1428 core::cmp::Ordering::Equal
1429 };
1430 let order = |x: &((Scalar, Scalar), usize), y: &((Scalar, Scalar), usize)| {
1431 let ((xt, xb), (yt, yb)) = (x.0, y.0);
1432 if !same_turn(xt, yt) {
1433 xt.total_cmp(&yt)
1434 } else if !same_bend(xb, yb) {
1435 xb.total_cmp(&yb)
1436 } else {
1437 chords(x.1, y.1)
1438 }
1439 };
1440 keyed.sort_by(order);
1441 if let [first_entry, second_entry, ..] = keyed.as_slice() {
1442 let ((ft, fb), (st, sb)) = (first_entry.0, second_entry.0);
1443 if same_turn(ft, st)
1444 && same_bend(fb, sb)
1445 && ft < TAU
1446 && chords(first_entry.1, second_entry.1) == core::cmp::Ordering::Equal
1447 {
1448 return Err(BooleanError::TangentSplit);
1449 }
1450 }
1451 let best = keyed.first().copied();
1452 current = best.ok_or(BooleanError::UnclosedSplit)?.1;
1453 }
1454 loops.push(walk);
1455 }
1456
1457 let as_pieces = |walk: &[usize]| -> Vec<Piece> {
1459 walk.iter()
1460 .map(|&h| directed(&pieces[halves[h].piece].0, halves[h].reversed))
1461 .collect()
1462 };
1463 let mut outers: Vec<(Vec<Piece>, Scalar, Vec<Point2>)> = Vec::new();
1464 let mut holes: Vec<(Vec<Piece>, Vec<Point2>)> = Vec::new();
1465 for walk in &loops {
1466 let loop_pieces = as_pieces(walk);
1467 let polygon = sample(&loop_pieces)?;
1468 let area = shoelace(&polygon);
1469 if area > 0.0 {
1470 outers.push((loop_pieces, area, polygon));
1471 } else {
1472 holes.push((loop_pieces, polygon));
1473 }
1474 }
1475 let mut regions: Vec<Region> = outers
1476 .iter()
1477 .map(|(pieces, _, _)| Region {
1478 outer: pieces.clone(),
1479 holes: Vec::new(),
1480 against: false,
1481 })
1482 .collect();
1483 for (hole, polygon) in holes {
1484 let piece = &hole[0];
1488 let t = 0.5 * (piece.pspan.start + piece.pspan.end);
1489 let at = evaluate2(&piece.pcurve, t).map_err(|_| BooleanError::Evaluation)?;
1490 let mut d = derivative2(&piece.pcurve, t).map_err(|_| BooleanError::Evaluation)?;
1491 if piece.pspan.end < piece.pspan.start {
1492 d = -d;
1493 }
1494 let size = {
1495 let (mut lo, mut hi) = (polygon[0], polygon[0]);
1496 for p in &polygon {
1497 lo = lo.min(*p);
1498 hi = hi.max(*p);
1499 }
1500 (hi - lo).length()
1501 };
1502 let left = Vec2::new(-d.y, d.x).normalize_or_zero();
1503 let probe = at + left * (1e-6 * size.max(1e-9));
1504 let owner = outers
1505 .iter()
1506 .enumerate()
1507 .filter(|(_, (_, _, outer))| inside_polygon(outer, probe))
1508 .min_by(|a, b| a.1 .1.total_cmp(&b.1 .1))
1509 .map(|(index, _)| index)
1510 .ok_or(BooleanError::UnclosedSplit)?;
1511 regions[owner].holes.push(hole);
1512 }
1513 Ok(regions)
1514}
1515
1516fn sample(pieces: &[Piece]) -> Result<Vec<Point2>, BooleanError> {
1518 let mut out = Vec::new();
1519 for piece in pieces {
1520 for i in 0..32 {
1521 let p = piece.pspan.start + (piece.pspan.end - piece.pspan.start) * i as Scalar / 32.0;
1522 out.push(evaluate2(&piece.pcurve, p).map_err(|_| BooleanError::Evaluation)?);
1523 }
1524 }
1525 Ok(out)
1526}
1527
1528fn shoelace(polygon: &[Point2]) -> Scalar {
1529 let mut area = 0.0;
1530 for i in 0..polygon.len() {
1531 let (p, q) = (polygon[i], polygon[(i + 1) % polygon.len()]);
1532 area += p.x * q.y - q.x * p.y;
1533 }
1534 0.5 * area
1535}
1536
1537pub(crate) fn inside_polygon(polygon: &[Point2], p: Point2) -> bool {
1538 let mut inside = false;
1539 for i in 0..polygon.len() {
1540 let (a, b) = (polygon[i], polygon[(i + 1) % polygon.len()]);
1541 if (a.y > p.y) != (b.y > p.y) {
1542 let x = a.x + (p.y - a.y) / (b.y - a.y) * (b.x - a.x);
1543 if x > p.x {
1544 inside = !inside;
1545 }
1546 }
1547 }
1548 inside
1549}