1use crate::{
12 Classification, Contour2, ContourPointLocation, CurveError, CurvePolicy, CurveResult,
13 CurveString2, Point2, PreparedRegionView2, RegionPointLocation, RegionView2, Segment2,
14 UncertaintyReason,
15};
16
17#[derive(Clone, Copy, Debug, Eq, PartialEq)]
19pub struct RetainedPlanarSurfaceIdentity2 {
20 source_index: u64,
21}
22
23#[derive(Clone, Copy, Debug, Eq, PartialEq)]
25pub enum PlanarPcurveImageRelation2 {
26 SameDirected,
29 SameReversed,
32 SurfaceMismatch,
35 Different,
38}
39
40#[derive(Clone, Debug, Eq, PartialEq)]
42pub struct PlanarPcurveImageEqualityReport2 {
43 relation: PlanarPcurveImageRelation2,
44 surface: Option<RetainedPlanarSurfaceIdentity2>,
45 segment_count: usize,
46}
47
48#[derive(Clone, Debug, PartialEq)]
50pub struct RetainedPlanarPcurve2 {
51 surface: RetainedPlanarSurfaceIdentity2,
52 curve: CurveString2,
53}
54
55#[derive(Clone, Debug, PartialEq)]
57pub struct RetainedPlanarTrimLoop2 {
58 surface: RetainedPlanarSurfaceIdentity2,
59 contour: Contour2,
60}
61
62#[derive(Clone, Debug, PartialEq)]
64pub struct RetainedPlanarFace2 {
65 surface: RetainedPlanarSurfaceIdentity2,
66 material_loops: Vec<RetainedPlanarTrimLoop2>,
67 hole_loops: Vec<RetainedPlanarTrimLoop2>,
68}
69
70#[derive(Clone, Debug, PartialEq)]
80pub struct PreparedRetainedPlanarFace2<'a> {
81 face: &'a RetainedPlanarFace2,
82 region: PreparedRegionView2<'a>,
83}
84
85#[derive(Clone, Copy, Debug, Eq, PartialEq)]
87pub enum RetainedPlanarFacePointLocation2 {
88 SurfaceMismatch,
90 Outside,
92 Boundary,
94 Inside,
96}
97
98#[derive(Clone, Debug, Eq, PartialEq)]
100pub struct RetainedPlanarFacePointReport2 {
101 location: RetainedPlanarFacePointLocation2,
102 surface: Option<RetainedPlanarSurfaceIdentity2>,
103 material_loop_count: usize,
104 hole_loop_count: usize,
105}
106
107#[derive(Clone, Copy, Debug, Eq, PartialEq)]
109pub enum RetainedPlanarTrimLoopRole2 {
110 Material,
112 Hole,
114}
115
116#[derive(Clone, Copy, Debug, Eq, PartialEq)]
118pub enum RetainedPlanarFaceEdgeUseRelation2 {
119 SurfaceMismatch,
121 BoundarySameDirected,
124 BoundarySameReversed,
127 NotTrimBoundary,
130}
131
132#[derive(Clone, Debug, Eq, PartialEq)]
134pub struct RetainedPlanarFaceEdgeUseReport2 {
135 relation: RetainedPlanarFaceEdgeUseRelation2,
136 surface: Option<RetainedPlanarSurfaceIdentity2>,
137 trim_role: Option<RetainedPlanarTrimLoopRole2>,
138 trim_loop_index: Option<usize>,
139 trim_segment_index: Option<usize>,
140 segment_count: usize,
141 trim_role_loop_count: Option<usize>,
142 trim_loop_segment_count: Option<usize>,
143}
144
145impl RetainedPlanarSurfaceIdentity2 {
146 pub const fn new(source_index: u64) -> Self {
148 Self { source_index }
149 }
150
151 pub const fn source_index(self) -> u64 {
153 self.source_index
154 }
155}
156
157impl PlanarPcurveImageRelation2 {
158 pub const fn is_same_image(self) -> bool {
160 matches!(self, Self::SameDirected | Self::SameReversed)
161 }
162
163 pub const fn is_reversed(self) -> bool {
165 matches!(self, Self::SameReversed)
166 }
167}
168
169impl PlanarPcurveImageEqualityReport2 {
170 pub fn new(
172 relation: PlanarPcurveImageRelation2,
173 surface: Option<RetainedPlanarSurfaceIdentity2>,
174 segment_count: usize,
175 ) -> CurveResult<Self> {
176 validate_planar_pcurve_image_report(relation, surface, segment_count)?;
177 Ok(Self {
178 relation,
179 surface,
180 segment_count,
181 })
182 }
183
184 pub const fn relation(&self) -> PlanarPcurveImageRelation2 {
186 self.relation
187 }
188
189 pub const fn surface(&self) -> Option<RetainedPlanarSurfaceIdentity2> {
191 self.surface
192 }
193
194 pub const fn segment_count(&self) -> usize {
196 self.segment_count
197 }
198}
199
200impl RetainedPlanarPcurve2 {
201 pub const fn new(surface: RetainedPlanarSurfaceIdentity2, curve: CurveString2) -> Self {
203 Self { surface, curve }
204 }
205
206 pub const fn surface(&self) -> RetainedPlanarSurfaceIdentity2 {
208 self.surface
209 }
210
211 pub const fn curve(&self) -> &CurveString2 {
213 &self.curve
214 }
215
216 pub fn image_equality_report(
224 &self,
225 other: &Self,
226 ) -> CurveResult<PlanarPcurveImageEqualityReport2> {
227 if self.surface != other.surface {
228 return PlanarPcurveImageEqualityReport2::new(
229 PlanarPcurveImageRelation2::SurfaceMismatch,
230 None,
231 0,
232 );
233 }
234 let relation = if same_directed_segments(self.curve.segments(), other.curve.segments()) {
235 PlanarPcurveImageRelation2::SameDirected
236 } else if same_reversed_segments(self.curve.segments(), other.curve.segments()) {
237 PlanarPcurveImageRelation2::SameReversed
238 } else {
239 PlanarPcurveImageRelation2::Different
240 };
241 let segment_count = usize::from(relation.is_same_image()) * self.curve.len();
242 PlanarPcurveImageEqualityReport2::new(relation, Some(self.surface), segment_count)
243 }
244}
245
246impl RetainedPlanarTrimLoop2 {
247 pub const fn new(surface: RetainedPlanarSurfaceIdentity2, contour: Contour2) -> Self {
249 Self { surface, contour }
250 }
251
252 pub const fn surface(&self) -> RetainedPlanarSurfaceIdentity2 {
254 self.surface
255 }
256
257 pub const fn contour(&self) -> &Contour2 {
259 &self.contour
260 }
261
262 pub fn image_equality_report(
269 &self,
270 other: &Self,
271 ) -> CurveResult<PlanarPcurveImageEqualityReport2> {
272 if self.surface != other.surface {
273 return PlanarPcurveImageEqualityReport2::new(
274 PlanarPcurveImageRelation2::SurfaceMismatch,
275 None,
276 0,
277 );
278 }
279 let relation =
280 if same_directed_segment_cycle(self.contour.segments(), other.contour.segments()) {
281 PlanarPcurveImageRelation2::SameDirected
282 } else if same_reversed_segment_cycle(self.contour.segments(), other.contour.segments())
283 {
284 PlanarPcurveImageRelation2::SameReversed
285 } else {
286 PlanarPcurveImageRelation2::Different
287 };
288 let segment_count = usize::from(relation.is_same_image()) * self.contour.len();
289 PlanarPcurveImageEqualityReport2::new(relation, Some(self.surface), segment_count)
290 }
291}
292
293impl RetainedPlanarFace2 {
294 pub fn try_new(
303 surface: RetainedPlanarSurfaceIdentity2,
304 material_loops: Vec<RetainedPlanarTrimLoop2>,
305 hole_loops: Vec<RetainedPlanarTrimLoop2>,
306 ) -> CurveResult<Self> {
307 if material_loops.is_empty() {
308 return Err(CurveError::InvalidPlanarFace);
309 }
310 if material_loops
311 .iter()
312 .chain(hole_loops.iter())
313 .any(|trim| trim.surface != surface)
314 {
315 return Err(CurveError::InvalidPlanarFace);
316 }
317 validate_planar_face_simple_trim_loops(&material_loops)?;
318 validate_planar_face_simple_trim_loops(&hole_loops)?;
319 validate_planar_face_distinct_trim_loops(&material_loops, &hole_loops)?;
320 validate_planar_face_same_role_trim_separation(&material_loops)?;
321 validate_planar_face_same_role_trim_separation(&hole_loops)?;
322 validate_planar_face_hole_ownership(&material_loops, &hole_loops)?;
323 Ok(Self {
324 surface,
325 material_loops,
326 hole_loops,
327 })
328 }
329
330 pub const fn surface(&self) -> RetainedPlanarSurfaceIdentity2 {
332 self.surface
333 }
334
335 pub fn material_loops(&self) -> &[RetainedPlanarTrimLoop2] {
337 &self.material_loops
338 }
339
340 pub fn hole_loops(&self) -> &[RetainedPlanarTrimLoop2] {
342 &self.hole_loops
343 }
344
345 pub fn prepare_point_queries(&self, policy: &CurvePolicy) -> PreparedRetainedPlanarFace2<'_> {
353 let material = self
354 .material_loops
355 .iter()
356 .map(|trim| trim.contour())
357 .collect::<Vec<_>>();
358 let holes = self
359 .hole_loops
360 .iter()
361 .map(|trim| trim.contour())
362 .collect::<Vec<_>>();
363 let region = RegionView2::from_contours(material, holes);
364 PreparedRetainedPlanarFace2 {
365 face: self,
366 region: PreparedRegionView2::from_region_view(®ion, policy),
367 }
368 }
369
370 pub fn prepare_topology_queries(
377 &self,
378 policy: &CurvePolicy,
379 ) -> PreparedRetainedPlanarFace2<'_> {
380 self.prepare_point_queries(policy)
381 }
382
383 pub fn classify_uv_point(
391 &self,
392 query_surface: RetainedPlanarSurfaceIdentity2,
393 uv: &Point2,
394 policy: &CurvePolicy,
395 ) -> CurveResult<Classification<RetainedPlanarFacePointReport2>> {
396 if query_surface != self.surface {
397 return Ok(Classification::Decided(
398 RetainedPlanarFacePointReport2::new(
399 RetainedPlanarFacePointLocation2::SurfaceMismatch,
400 None,
401 self.material_loops.len(),
402 self.hole_loops.len(),
403 )?,
404 ));
405 }
406
407 let material = self
408 .material_loops
409 .iter()
410 .map(|trim| trim.contour())
411 .collect::<Vec<_>>();
412 let holes = self
413 .hole_loops
414 .iter()
415 .map(|trim| trim.contour())
416 .collect::<Vec<_>>();
417 let region = RegionView2::from_contours(material, holes);
418 face_point_report_from_region_classification(
419 region.classify_point(uv, policy),
420 self.surface,
421 self.material_loops.len(),
422 self.hole_loops.len(),
423 )
424 }
425
426 pub fn edge_use_report(
436 &self,
437 pcurve: &RetainedPlanarPcurve2,
438 ) -> CurveResult<RetainedPlanarFaceEdgeUseReport2> {
439 if pcurve.surface != self.surface {
440 return RetainedPlanarFaceEdgeUseReport2::new(
441 RetainedPlanarFaceEdgeUseRelation2::SurfaceMismatch,
442 None,
443 None,
444 None,
445 None,
446 0,
447 );
448 }
449
450 face_edge_use_report_from_loops(self, pcurve.curve.segments())
451 }
452}
453
454impl<'a> PreparedRetainedPlanarFace2<'a> {
455 pub const fn face(&self) -> &'a RetainedPlanarFace2 {
457 self.face
458 }
459
460 pub const fn surface(&self) -> RetainedPlanarSurfaceIdentity2 {
462 self.face.surface
463 }
464
465 pub const fn prepared_region(&self) -> &PreparedRegionView2<'a> {
467 &self.region
468 }
469
470 pub fn material_loop_count(&self) -> usize {
472 self.face.material_loops.len()
473 }
474
475 pub fn hole_loop_count(&self) -> usize {
477 self.face.hole_loops.len()
478 }
479
480 pub fn classify_uv_point(
487 &self,
488 query_surface: RetainedPlanarSurfaceIdentity2,
489 uv: &Point2,
490 policy: &CurvePolicy,
491 ) -> CurveResult<Classification<RetainedPlanarFacePointReport2>> {
492 if query_surface != self.face.surface {
493 return Ok(Classification::Decided(
494 RetainedPlanarFacePointReport2::new(
495 RetainedPlanarFacePointLocation2::SurfaceMismatch,
496 None,
497 self.material_loop_count(),
498 self.hole_loop_count(),
499 )?,
500 ));
501 }
502
503 face_point_report_from_region_classification(
504 self.region.classify_point(uv, policy),
505 self.face.surface,
506 self.material_loop_count(),
507 self.hole_loop_count(),
508 )
509 }
510
511 pub fn edge_use_report(
519 &self,
520 pcurve: &RetainedPlanarPcurve2,
521 ) -> CurveResult<RetainedPlanarFaceEdgeUseReport2> {
522 if pcurve.surface != self.face.surface {
523 return RetainedPlanarFaceEdgeUseReport2::new(
524 RetainedPlanarFaceEdgeUseRelation2::SurfaceMismatch,
525 None,
526 None,
527 None,
528 None,
529 0,
530 );
531 }
532
533 face_edge_use_report_from_loops(self.face, pcurve.curve.segments())
534 }
535}
536
537fn validate_planar_face_distinct_trim_loops(
538 material_loops: &[RetainedPlanarTrimLoop2],
539 hole_loops: &[RetainedPlanarTrimLoop2],
540) -> CurveResult<()> {
541 for (index, trim) in material_loops.iter().enumerate() {
542 if material_loops[index + 1..].contains(trim) || hole_loops.contains(trim) {
543 return Err(CurveError::InvalidPlanarFace);
544 }
545 }
546 for (index, trim) in hole_loops.iter().enumerate() {
547 if hole_loops[index + 1..].contains(trim) {
548 return Err(CurveError::InvalidPlanarFace);
549 }
550 }
551 Ok(())
552}
553
554fn validate_planar_face_simple_trim_loops(loops: &[RetainedPlanarTrimLoop2]) -> CurveResult<()> {
555 let policy = CurvePolicy::certified();
556 for trim in loops {
557 match trim.contour.has_self_contacts(&policy)? {
558 Classification::Decided(false) => {}
559 Classification::Decided(true) | Classification::Uncertain(_) => {
560 return Err(CurveError::InvalidPlanarFace);
561 }
562 }
563 }
564 Ok(())
565}
566
567fn validate_planar_face_same_role_trim_separation(
568 loops: &[RetainedPlanarTrimLoop2],
569) -> CurveResult<()> {
570 let policy = CurvePolicy::certified();
571 for (index, trim) in loops.iter().enumerate() {
572 for other in &loops[index + 1..] {
573 if !trim
574 .contour
575 .intersect_contour(&other.contour, &policy)?
576 .is_empty()
577 {
578 return Err(CurveError::InvalidPlanarFace);
579 }
580 }
581 }
582 Ok(())
583}
584
585fn validate_planar_face_hole_ownership(
586 material_loops: &[RetainedPlanarTrimLoop2],
587 hole_loops: &[RetainedPlanarTrimLoop2],
588) -> CurveResult<()> {
589 let policy = CurvePolicy::certified();
590 for hole in hole_loops {
591 let Some(point) = hole
592 .contour
593 .segments()
594 .first()
595 .map(|segment| segment.start())
596 else {
597 return Err(CurveError::InvalidPlanarFace);
598 };
599 let mut owned_by_material = false;
600 for material in material_loops {
601 if !material
602 .contour
603 .intersect_contour(&hole.contour, &policy)?
604 .is_empty()
605 {
606 return Err(CurveError::InvalidPlanarFace);
607 }
608 match material.contour.classify_point(point, &policy) {
609 Classification::Decided(ContourPointLocation::Inside) => {
610 owned_by_material = true;
611 }
612 Classification::Decided(
613 ContourPointLocation::Boundary | ContourPointLocation::Outside,
614 ) => {}
615 Classification::Uncertain(_) => return Err(CurveError::InvalidPlanarFace),
616 }
617 }
618 if !owned_by_material {
619 return Err(CurveError::InvalidPlanarFace);
620 }
621 }
622 Ok(())
623}
624
625impl RetainedPlanarFacePointLocation2 {
626 pub const fn is_trim_classification(self) -> bool {
628 !matches!(self, Self::SurfaceMismatch)
629 }
630}
631
632impl RetainedPlanarTrimLoopRole2 {
633 pub const fn is_material(self) -> bool {
635 matches!(self, Self::Material)
636 }
637
638 pub const fn is_hole(self) -> bool {
640 matches!(self, Self::Hole)
641 }
642}
643
644impl RetainedPlanarFaceEdgeUseRelation2 {
645 pub const fn is_boundary(self) -> bool {
647 matches!(
648 self,
649 Self::BoundarySameDirected | Self::BoundarySameReversed
650 )
651 }
652
653 pub const fn is_reversed(self) -> bool {
655 matches!(self, Self::BoundarySameReversed)
656 }
657}
658
659impl RetainedPlanarFaceEdgeUseReport2 {
660 pub fn new(
666 relation: RetainedPlanarFaceEdgeUseRelation2,
667 surface: Option<RetainedPlanarSurfaceIdentity2>,
668 trim_role: Option<RetainedPlanarTrimLoopRole2>,
669 trim_loop_index: Option<usize>,
670 trim_segment_index: Option<usize>,
671 segment_count: usize,
672 ) -> CurveResult<Self> {
673 validate_planar_face_edge_use_report(
674 relation,
675 surface,
676 trim_role,
677 trim_loop_index,
678 trim_segment_index,
679 segment_count,
680 None,
681 None,
682 )?;
683 Ok(Self {
684 relation,
685 surface,
686 trim_role,
687 trim_loop_index,
688 trim_segment_index,
689 segment_count,
690 trim_role_loop_count: None,
691 trim_loop_segment_count: None,
692 })
693 }
694
695 #[allow(clippy::too_many_arguments)]
696 fn new_with_face_extent_evidence(
697 relation: RetainedPlanarFaceEdgeUseRelation2,
698 surface: RetainedPlanarSurfaceIdentity2,
699 trim_role: RetainedPlanarTrimLoopRole2,
700 trim_loop_index: usize,
701 trim_segment_index: usize,
702 segment_count: usize,
703 trim_role_loop_count: usize,
704 trim_loop_segment_count: usize,
705 ) -> CurveResult<Self> {
706 validate_planar_face_edge_use_report(
707 relation,
708 Some(surface),
709 Some(trim_role),
710 Some(trim_loop_index),
711 Some(trim_segment_index),
712 segment_count,
713 Some(trim_role_loop_count),
714 Some(trim_loop_segment_count),
715 )?;
716 Ok(Self {
717 relation,
718 surface: Some(surface),
719 trim_role: Some(trim_role),
720 trim_loop_index: Some(trim_loop_index),
721 trim_segment_index: Some(trim_segment_index),
722 segment_count,
723 trim_role_loop_count: Some(trim_role_loop_count),
724 trim_loop_segment_count: Some(trim_loop_segment_count),
725 })
726 }
727
728 pub const fn relation(&self) -> RetainedPlanarFaceEdgeUseRelation2 {
730 self.relation
731 }
732
733 pub const fn surface(&self) -> Option<RetainedPlanarSurfaceIdentity2> {
735 self.surface
736 }
737
738 pub const fn trim_role(&self) -> Option<RetainedPlanarTrimLoopRole2> {
740 self.trim_role
741 }
742
743 pub const fn trim_loop_index(&self) -> Option<usize> {
745 self.trim_loop_index
746 }
747
748 pub const fn trim_segment_index(&self) -> Option<usize> {
753 self.trim_segment_index
754 }
755
756 pub const fn segment_count(&self) -> usize {
758 self.segment_count
759 }
760}
761
762impl RetainedPlanarFacePointReport2 {
763 pub fn new(
765 location: RetainedPlanarFacePointLocation2,
766 surface: Option<RetainedPlanarSurfaceIdentity2>,
767 material_loop_count: usize,
768 hole_loop_count: usize,
769 ) -> CurveResult<Self> {
770 validate_planar_face_point_report(location, surface, material_loop_count)?;
771 Ok(Self {
772 location,
773 surface,
774 material_loop_count,
775 hole_loop_count,
776 })
777 }
778
779 pub const fn location(&self) -> RetainedPlanarFacePointLocation2 {
781 self.location
782 }
783
784 pub const fn surface(&self) -> Option<RetainedPlanarSurfaceIdentity2> {
786 self.surface
787 }
788
789 pub const fn material_loop_count(&self) -> usize {
791 self.material_loop_count
792 }
793
794 pub const fn hole_loop_count(&self) -> usize {
796 self.hole_loop_count
797 }
798}
799
800fn validate_planar_pcurve_image_report(
801 relation: PlanarPcurveImageRelation2,
802 surface: Option<RetainedPlanarSurfaceIdentity2>,
803 segment_count: usize,
804) -> CurveResult<()> {
805 match relation {
806 PlanarPcurveImageRelation2::SurfaceMismatch => {
807 if surface.is_some() || segment_count != 0 {
808 return Err(CurveError::Topology(
809 "surface-mismatch pcurve image report must not carry image evidence".into(),
810 ));
811 }
812 }
813 PlanarPcurveImageRelation2::Different => {
814 if surface.is_none() || segment_count != 0 {
815 return Err(CurveError::Topology(
816 "different pcurve image report must carry only matching-surface evidence"
817 .into(),
818 ));
819 }
820 }
821 PlanarPcurveImageRelation2::SameDirected | PlanarPcurveImageRelation2::SameReversed => {
822 if surface.is_none() || segment_count == 0 {
823 return Err(CurveError::Topology(
824 "same-image pcurve report must carry surface and positive segment evidence"
825 .into(),
826 ));
827 }
828 }
829 }
830 Ok(())
831}
832
833fn validate_planar_face_point_report(
834 location: RetainedPlanarFacePointLocation2,
835 surface: Option<RetainedPlanarSurfaceIdentity2>,
836 material_loop_count: usize,
837) -> CurveResult<()> {
838 if material_loop_count == 0 {
839 return Err(CurveError::Topology(
840 "retained planar face point report must reference a face with material loops".into(),
841 ));
842 }
843 match location {
844 RetainedPlanarFacePointLocation2::SurfaceMismatch => {
845 if surface.is_some() {
846 return Err(CurveError::Topology(
847 "surface-mismatch point report must not carry trim-classification surface evidence"
848 .into(),
849 ));
850 }
851 }
852 RetainedPlanarFacePointLocation2::Outside
853 | RetainedPlanarFacePointLocation2::Boundary
854 | RetainedPlanarFacePointLocation2::Inside => {
855 if surface.is_none() {
856 return Err(CurveError::Topology(
857 "trim-classified point report must carry matching surface evidence".into(),
858 ));
859 }
860 }
861 }
862 Ok(())
863}
864
865fn validate_planar_face_edge_use_report(
866 relation: RetainedPlanarFaceEdgeUseRelation2,
867 surface: Option<RetainedPlanarSurfaceIdentity2>,
868 trim_role: Option<RetainedPlanarTrimLoopRole2>,
869 trim_loop_index: Option<usize>,
870 trim_segment_index: Option<usize>,
871 segment_count: usize,
872 trim_role_loop_count: Option<usize>,
873 trim_loop_segment_count: Option<usize>,
874) -> CurveResult<()> {
875 match relation {
876 RetainedPlanarFaceEdgeUseRelation2::SurfaceMismatch => {
877 if surface.is_some()
878 || trim_role.is_some()
879 || trim_loop_index.is_some()
880 || trim_segment_index.is_some()
881 || segment_count != 0
882 || trim_role_loop_count.is_some()
883 || trim_loop_segment_count.is_some()
884 {
885 return Err(CurveError::Topology(
886 "surface-mismatch edge-use report must not carry trim evidence".into(),
887 ));
888 }
889 }
890 RetainedPlanarFaceEdgeUseRelation2::NotTrimBoundary => {
891 if surface.is_none()
892 || trim_role.is_some()
893 || trim_loop_index.is_some()
894 || trim_segment_index.is_some()
895 || segment_count != 0
896 || trim_role_loop_count.is_some()
897 || trim_loop_segment_count.is_some()
898 {
899 return Err(CurveError::Topology(
900 "non-boundary edge-use report must carry only matching-surface evidence".into(),
901 ));
902 }
903 }
904 RetainedPlanarFaceEdgeUseRelation2::BoundarySameDirected
905 | RetainedPlanarFaceEdgeUseRelation2::BoundarySameReversed => {
906 if surface.is_none()
907 || trim_role.is_none()
908 || trim_loop_index.is_none()
909 || trim_segment_index.is_none()
910 || segment_count == 0
911 || trim_role_loop_count.is_none()
912 || trim_loop_segment_count.is_none()
913 {
914 return Err(CurveError::Topology(
915 "boundary edge-use report must carry complete positive trim evidence".into(),
916 ));
917 }
918 let (
919 Some(trim_loop_index),
920 Some(trim_segment_index),
921 Some(trim_role_loop_count),
922 Some(trim_loop_segment_count),
923 ) = (
924 trim_loop_index,
925 trim_segment_index,
926 trim_role_loop_count,
927 trim_loop_segment_count,
928 )
929 else {
930 return Err(CurveError::Topology(
931 "boundary edge-use report must carry complete positive trim evidence".into(),
932 ));
933 };
934 if trim_role_loop_count == 0
935 || trim_loop_segment_count == 0
936 || trim_loop_index >= trim_role_loop_count
937 || trim_segment_index >= trim_loop_segment_count
938 || segment_count > trim_loop_segment_count
939 {
940 return Err(CurveError::Topology(
941 "boundary edge-use report trim indices must be certified by face extent evidence"
942 .into(),
943 ));
944 }
945 }
946 }
947 Ok(())
948}
949
950fn same_directed_segments(first: &[Segment2], second: &[Segment2]) -> bool {
951 first == second
952}
953
954fn same_reversed_segments(first: &[Segment2], second: &[Segment2]) -> bool {
955 first.len() == second.len()
956 && first
957 .iter()
958 .zip(second.iter().rev())
959 .all(|(left, right)| left == &right.reversed())
960}
961
962fn same_directed_segment_cycle(first: &[Segment2], second: &[Segment2]) -> bool {
963 let len = first.len();
964 if len != second.len() {
965 return false;
966 }
967 (0..len).any(|offset| {
968 first
969 .iter()
970 .enumerate()
971 .all(|(index, segment)| segment == &second[(offset + index) % len])
972 })
973}
974
975fn same_reversed_segment_cycle(first: &[Segment2], second: &[Segment2]) -> bool {
976 let len = first.len();
977 if len != second.len() {
978 return false;
979 }
980 (0..len).any(|offset| {
981 first.iter().enumerate().all(|(index, segment)| {
982 let reversed_index = (offset + len - 1 - index) % len;
983 segment == &second[reversed_index].reversed()
984 })
985 })
986}
987
988fn face_edge_use_report_from_loops(
989 face: &RetainedPlanarFace2,
990 query_segments: &[Segment2],
991) -> CurveResult<RetainedPlanarFaceEdgeUseReport2> {
992 for (loop_index, trim) in face.material_loops.iter().enumerate() {
993 if let Some((relation, segment_index)) =
994 segment_subchain_relation(query_segments, trim.contour.segments())
995 {
996 return RetainedPlanarFaceEdgeUseReport2::new_with_face_extent_evidence(
997 relation,
998 face.surface,
999 RetainedPlanarTrimLoopRole2::Material,
1000 loop_index,
1001 segment_index,
1002 query_segments.len(),
1003 face.material_loops.len(),
1004 trim.contour.len(),
1005 );
1006 }
1007 }
1008 for (loop_index, trim) in face.hole_loops.iter().enumerate() {
1009 if let Some((relation, segment_index)) =
1010 segment_subchain_relation(query_segments, trim.contour.segments())
1011 {
1012 return RetainedPlanarFaceEdgeUseReport2::new_with_face_extent_evidence(
1013 relation,
1014 face.surface,
1015 RetainedPlanarTrimLoopRole2::Hole,
1016 loop_index,
1017 segment_index,
1018 query_segments.len(),
1019 face.hole_loops.len(),
1020 trim.contour.len(),
1021 );
1022 }
1023 }
1024
1025 RetainedPlanarFaceEdgeUseReport2::new(
1026 RetainedPlanarFaceEdgeUseRelation2::NotTrimBoundary,
1027 Some(face.surface),
1028 None,
1029 None,
1030 None,
1031 0,
1032 )
1033}
1034
1035fn segment_subchain_relation(
1036 query_segments: &[Segment2],
1037 loop_segments: &[Segment2],
1038) -> Option<(RetainedPlanarFaceEdgeUseRelation2, usize)> {
1039 if query_segments.is_empty() || query_segments.len() > loop_segments.len() {
1040 return None;
1041 }
1042 if let Some(segment_index) = directed_segment_subchain_start(query_segments, loop_segments) {
1043 return Some((
1044 RetainedPlanarFaceEdgeUseRelation2::BoundarySameDirected,
1045 segment_index,
1046 ));
1047 }
1048 reversed_segment_subchain_start(query_segments, loop_segments).map(|segment_index| {
1049 (
1050 RetainedPlanarFaceEdgeUseRelation2::BoundarySameReversed,
1051 segment_index,
1052 )
1053 })
1054}
1055
1056fn directed_segment_subchain_start(
1057 query_segments: &[Segment2],
1058 loop_segments: &[Segment2],
1059) -> Option<usize> {
1060 let len = loop_segments.len();
1061 (0..len).find(|&offset| {
1062 query_segments
1063 .iter()
1064 .enumerate()
1065 .all(|(index, segment)| segment == &loop_segments[(offset + index) % len])
1066 })
1067}
1068
1069fn reversed_segment_subchain_start(
1070 query_segments: &[Segment2],
1071 loop_segments: &[Segment2],
1072) -> Option<usize> {
1073 let len = loop_segments.len();
1074 (0..len).find(|&offset| {
1075 query_segments.iter().enumerate().all(|(index, segment)| {
1076 let loop_index = (offset + len - index) % len;
1077 segment == &loop_segments[loop_index].reversed()
1078 })
1079 })
1080}
1081
1082fn face_point_report_from_region_classification(
1083 classification: Classification<RegionPointLocation>,
1084 surface: RetainedPlanarSurfaceIdentity2,
1085 material_loop_count: usize,
1086 hole_loop_count: usize,
1087) -> CurveResult<Classification<RetainedPlanarFacePointReport2>> {
1088 let location = match classification {
1089 Classification::Decided(RegionPointLocation::Outside) => {
1090 RetainedPlanarFacePointLocation2::Outside
1091 }
1092 Classification::Decided(RegionPointLocation::Boundary) => {
1093 RetainedPlanarFacePointLocation2::Boundary
1094 }
1095 Classification::Decided(RegionPointLocation::Inside) => {
1096 RetainedPlanarFacePointLocation2::Inside
1097 }
1098 Classification::Uncertain(UncertaintyReason::Boundary) => {
1099 RetainedPlanarFacePointLocation2::Boundary
1100 }
1101 Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
1102 };
1103 Ok(Classification::Decided(
1104 RetainedPlanarFacePointReport2::new(
1105 location,
1106 Some(surface),
1107 material_loop_count,
1108 hole_loop_count,
1109 )?,
1110 ))
1111}