use crate::{
Classification, Contour2, ContourPointLocation, CurveError, CurvePolicy, CurveResult,
CurveString2, Point2, PreparedRegionView2, RegionPointLocation, RegionView2, Segment2,
UncertaintyReason,
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct RetainedPlanarSurfaceIdentity2 {
source_index: u64,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PlanarPcurveImageRelation2 {
SameDirected,
SameReversed,
SurfaceMismatch,
Different,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PlanarPcurveImageEqualityReport2 {
relation: PlanarPcurveImageRelation2,
surface: Option<RetainedPlanarSurfaceIdentity2>,
segment_count: usize,
}
#[derive(Clone, Debug, PartialEq)]
pub struct RetainedPlanarPcurve2 {
surface: RetainedPlanarSurfaceIdentity2,
curve: CurveString2,
}
#[derive(Clone, Debug, PartialEq)]
pub struct RetainedPlanarTrimLoop2 {
surface: RetainedPlanarSurfaceIdentity2,
contour: Contour2,
}
#[derive(Clone, Debug, PartialEq)]
pub struct RetainedPlanarFace2 {
surface: RetainedPlanarSurfaceIdentity2,
material_loops: Vec<RetainedPlanarTrimLoop2>,
hole_loops: Vec<RetainedPlanarTrimLoop2>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct PreparedRetainedPlanarFace2<'a> {
face: &'a RetainedPlanarFace2,
region: PreparedRegionView2<'a>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RetainedPlanarFacePointLocation2 {
SurfaceMismatch,
Outside,
Boundary,
Inside,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RetainedPlanarFacePointReport2 {
location: RetainedPlanarFacePointLocation2,
surface: Option<RetainedPlanarSurfaceIdentity2>,
material_loop_count: usize,
hole_loop_count: usize,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RetainedPlanarTrimLoopRole2 {
Material,
Hole,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RetainedPlanarFaceEdgeUseRelation2 {
SurfaceMismatch,
BoundarySameDirected,
BoundarySameReversed,
NotTrimBoundary,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RetainedPlanarFaceEdgeUseReport2 {
relation: RetainedPlanarFaceEdgeUseRelation2,
surface: Option<RetainedPlanarSurfaceIdentity2>,
trim_role: Option<RetainedPlanarTrimLoopRole2>,
trim_loop_index: Option<usize>,
trim_segment_index: Option<usize>,
segment_count: usize,
trim_role_loop_count: Option<usize>,
trim_loop_segment_count: Option<usize>,
}
impl RetainedPlanarSurfaceIdentity2 {
pub const fn new(source_index: u64) -> Self {
Self { source_index }
}
pub const fn source_index(self) -> u64 {
self.source_index
}
}
impl PlanarPcurveImageRelation2 {
pub const fn is_same_image(self) -> bool {
matches!(self, Self::SameDirected | Self::SameReversed)
}
pub const fn is_reversed(self) -> bool {
matches!(self, Self::SameReversed)
}
}
impl PlanarPcurveImageEqualityReport2 {
pub fn new(
relation: PlanarPcurveImageRelation2,
surface: Option<RetainedPlanarSurfaceIdentity2>,
segment_count: usize,
) -> CurveResult<Self> {
validate_planar_pcurve_image_report(relation, surface, segment_count)?;
Ok(Self {
relation,
surface,
segment_count,
})
}
pub const fn relation(&self) -> PlanarPcurveImageRelation2 {
self.relation
}
pub const fn surface(&self) -> Option<RetainedPlanarSurfaceIdentity2> {
self.surface
}
pub const fn segment_count(&self) -> usize {
self.segment_count
}
}
impl RetainedPlanarPcurve2 {
pub const fn new(surface: RetainedPlanarSurfaceIdentity2, curve: CurveString2) -> Self {
Self { surface, curve }
}
pub const fn surface(&self) -> RetainedPlanarSurfaceIdentity2 {
self.surface
}
pub const fn curve(&self) -> &CurveString2 {
&self.curve
}
pub fn image_equality_report(
&self,
other: &Self,
) -> CurveResult<PlanarPcurveImageEqualityReport2> {
if self.surface != other.surface {
return PlanarPcurveImageEqualityReport2::new(
PlanarPcurveImageRelation2::SurfaceMismatch,
None,
0,
);
}
let relation = if same_directed_segments(self.curve.segments(), other.curve.segments()) {
PlanarPcurveImageRelation2::SameDirected
} else if same_reversed_segments(self.curve.segments(), other.curve.segments()) {
PlanarPcurveImageRelation2::SameReversed
} else {
PlanarPcurveImageRelation2::Different
};
let segment_count = usize::from(relation.is_same_image()) * self.curve.len();
PlanarPcurveImageEqualityReport2::new(relation, Some(self.surface), segment_count)
}
}
impl RetainedPlanarTrimLoop2 {
pub const fn new(surface: RetainedPlanarSurfaceIdentity2, contour: Contour2) -> Self {
Self { surface, contour }
}
pub const fn surface(&self) -> RetainedPlanarSurfaceIdentity2 {
self.surface
}
pub const fn contour(&self) -> &Contour2 {
&self.contour
}
pub fn image_equality_report(
&self,
other: &Self,
) -> CurveResult<PlanarPcurveImageEqualityReport2> {
if self.surface != other.surface {
return PlanarPcurveImageEqualityReport2::new(
PlanarPcurveImageRelation2::SurfaceMismatch,
None,
0,
);
}
let relation =
if same_directed_segment_cycle(self.contour.segments(), other.contour.segments()) {
PlanarPcurveImageRelation2::SameDirected
} else if same_reversed_segment_cycle(self.contour.segments(), other.contour.segments())
{
PlanarPcurveImageRelation2::SameReversed
} else {
PlanarPcurveImageRelation2::Different
};
let segment_count = usize::from(relation.is_same_image()) * self.contour.len();
PlanarPcurveImageEqualityReport2::new(relation, Some(self.surface), segment_count)
}
}
impl RetainedPlanarFace2 {
pub fn try_new(
surface: RetainedPlanarSurfaceIdentity2,
material_loops: Vec<RetainedPlanarTrimLoop2>,
hole_loops: Vec<RetainedPlanarTrimLoop2>,
) -> CurveResult<Self> {
if material_loops.is_empty() {
return Err(CurveError::InvalidPlanarFace);
}
if material_loops
.iter()
.chain(hole_loops.iter())
.any(|trim| trim.surface != surface)
{
return Err(CurveError::InvalidPlanarFace);
}
validate_planar_face_simple_trim_loops(&material_loops)?;
validate_planar_face_simple_trim_loops(&hole_loops)?;
validate_planar_face_distinct_trim_loops(&material_loops, &hole_loops)?;
validate_planar_face_same_role_trim_separation(&material_loops)?;
validate_planar_face_same_role_trim_separation(&hole_loops)?;
validate_planar_face_hole_ownership(&material_loops, &hole_loops)?;
Ok(Self {
surface,
material_loops,
hole_loops,
})
}
pub const fn surface(&self) -> RetainedPlanarSurfaceIdentity2 {
self.surface
}
pub fn material_loops(&self) -> &[RetainedPlanarTrimLoop2] {
&self.material_loops
}
pub fn hole_loops(&self) -> &[RetainedPlanarTrimLoop2] {
&self.hole_loops
}
pub fn prepare_point_queries(&self, policy: &CurvePolicy) -> PreparedRetainedPlanarFace2<'_> {
let material = self
.material_loops
.iter()
.map(|trim| trim.contour())
.collect::<Vec<_>>();
let holes = self
.hole_loops
.iter()
.map(|trim| trim.contour())
.collect::<Vec<_>>();
let region = RegionView2::from_contours(material, holes);
PreparedRetainedPlanarFace2 {
face: self,
region: PreparedRegionView2::from_region_view(®ion, policy),
}
}
pub fn prepare_topology_queries(
&self,
policy: &CurvePolicy,
) -> PreparedRetainedPlanarFace2<'_> {
self.prepare_point_queries(policy)
}
pub fn classify_uv_point(
&self,
query_surface: RetainedPlanarSurfaceIdentity2,
uv: &Point2,
policy: &CurvePolicy,
) -> CurveResult<Classification<RetainedPlanarFacePointReport2>> {
if query_surface != self.surface {
return Ok(Classification::Decided(
RetainedPlanarFacePointReport2::new(
RetainedPlanarFacePointLocation2::SurfaceMismatch,
None,
self.material_loops.len(),
self.hole_loops.len(),
)?,
));
}
let material = self
.material_loops
.iter()
.map(|trim| trim.contour())
.collect::<Vec<_>>();
let holes = self
.hole_loops
.iter()
.map(|trim| trim.contour())
.collect::<Vec<_>>();
let region = RegionView2::from_contours(material, holes);
face_point_report_from_region_classification(
region.classify_point(uv, policy),
self.surface,
self.material_loops.len(),
self.hole_loops.len(),
)
}
pub fn edge_use_report(
&self,
pcurve: &RetainedPlanarPcurve2,
) -> CurveResult<RetainedPlanarFaceEdgeUseReport2> {
if pcurve.surface != self.surface {
return RetainedPlanarFaceEdgeUseReport2::new(
RetainedPlanarFaceEdgeUseRelation2::SurfaceMismatch,
None,
None,
None,
None,
0,
);
}
face_edge_use_report_from_loops(self, pcurve.curve.segments())
}
}
impl<'a> PreparedRetainedPlanarFace2<'a> {
pub const fn face(&self) -> &'a RetainedPlanarFace2 {
self.face
}
pub const fn surface(&self) -> RetainedPlanarSurfaceIdentity2 {
self.face.surface
}
pub const fn prepared_region(&self) -> &PreparedRegionView2<'a> {
&self.region
}
pub fn material_loop_count(&self) -> usize {
self.face.material_loops.len()
}
pub fn hole_loop_count(&self) -> usize {
self.face.hole_loops.len()
}
pub fn classify_uv_point(
&self,
query_surface: RetainedPlanarSurfaceIdentity2,
uv: &Point2,
policy: &CurvePolicy,
) -> CurveResult<Classification<RetainedPlanarFacePointReport2>> {
if query_surface != self.face.surface {
return Ok(Classification::Decided(
RetainedPlanarFacePointReport2::new(
RetainedPlanarFacePointLocation2::SurfaceMismatch,
None,
self.material_loop_count(),
self.hole_loop_count(),
)?,
));
}
face_point_report_from_region_classification(
self.region.classify_point(uv, policy),
self.face.surface,
self.material_loop_count(),
self.hole_loop_count(),
)
}
pub fn edge_use_report(
&self,
pcurve: &RetainedPlanarPcurve2,
) -> CurveResult<RetainedPlanarFaceEdgeUseReport2> {
if pcurve.surface != self.face.surface {
return RetainedPlanarFaceEdgeUseReport2::new(
RetainedPlanarFaceEdgeUseRelation2::SurfaceMismatch,
None,
None,
None,
None,
0,
);
}
face_edge_use_report_from_loops(self.face, pcurve.curve.segments())
}
}
fn validate_planar_face_distinct_trim_loops(
material_loops: &[RetainedPlanarTrimLoop2],
hole_loops: &[RetainedPlanarTrimLoop2],
) -> CurveResult<()> {
for (index, trim) in material_loops.iter().enumerate() {
if material_loops[index + 1..].contains(trim) || hole_loops.contains(trim) {
return Err(CurveError::InvalidPlanarFace);
}
}
for (index, trim) in hole_loops.iter().enumerate() {
if hole_loops[index + 1..].contains(trim) {
return Err(CurveError::InvalidPlanarFace);
}
}
Ok(())
}
fn validate_planar_face_simple_trim_loops(loops: &[RetainedPlanarTrimLoop2]) -> CurveResult<()> {
let policy = CurvePolicy::certified();
for trim in loops {
match trim.contour.has_self_contacts(&policy)? {
Classification::Decided(false) => {}
Classification::Decided(true) | Classification::Uncertain(_) => {
return Err(CurveError::InvalidPlanarFace);
}
}
}
Ok(())
}
fn validate_planar_face_same_role_trim_separation(
loops: &[RetainedPlanarTrimLoop2],
) -> CurveResult<()> {
let policy = CurvePolicy::certified();
for (index, trim) in loops.iter().enumerate() {
for other in &loops[index + 1..] {
if !trim
.contour
.intersect_contour(&other.contour, &policy)?
.is_empty()
{
return Err(CurveError::InvalidPlanarFace);
}
}
}
Ok(())
}
fn validate_planar_face_hole_ownership(
material_loops: &[RetainedPlanarTrimLoop2],
hole_loops: &[RetainedPlanarTrimLoop2],
) -> CurveResult<()> {
let policy = CurvePolicy::certified();
for hole in hole_loops {
let Some(point) = hole
.contour
.segments()
.first()
.map(|segment| segment.start())
else {
return Err(CurveError::InvalidPlanarFace);
};
let mut owned_by_material = false;
for material in material_loops {
if !material
.contour
.intersect_contour(&hole.contour, &policy)?
.is_empty()
{
return Err(CurveError::InvalidPlanarFace);
}
match material.contour.classify_point(point, &policy) {
Classification::Decided(ContourPointLocation::Inside) => {
owned_by_material = true;
}
Classification::Decided(
ContourPointLocation::Boundary | ContourPointLocation::Outside,
) => {}
Classification::Uncertain(_) => return Err(CurveError::InvalidPlanarFace),
}
}
if !owned_by_material {
return Err(CurveError::InvalidPlanarFace);
}
}
Ok(())
}
impl RetainedPlanarFacePointLocation2 {
pub const fn is_trim_classification(self) -> bool {
!matches!(self, Self::SurfaceMismatch)
}
}
impl RetainedPlanarTrimLoopRole2 {
pub const fn is_material(self) -> bool {
matches!(self, Self::Material)
}
pub const fn is_hole(self) -> bool {
matches!(self, Self::Hole)
}
}
impl RetainedPlanarFaceEdgeUseRelation2 {
pub const fn is_boundary(self) -> bool {
matches!(
self,
Self::BoundarySameDirected | Self::BoundarySameReversed
)
}
pub const fn is_reversed(self) -> bool {
matches!(self, Self::BoundarySameReversed)
}
}
impl RetainedPlanarFaceEdgeUseReport2 {
pub fn new(
relation: RetainedPlanarFaceEdgeUseRelation2,
surface: Option<RetainedPlanarSurfaceIdentity2>,
trim_role: Option<RetainedPlanarTrimLoopRole2>,
trim_loop_index: Option<usize>,
trim_segment_index: Option<usize>,
segment_count: usize,
) -> CurveResult<Self> {
validate_planar_face_edge_use_report(
relation,
surface,
trim_role,
trim_loop_index,
trim_segment_index,
segment_count,
None,
None,
)?;
Ok(Self {
relation,
surface,
trim_role,
trim_loop_index,
trim_segment_index,
segment_count,
trim_role_loop_count: None,
trim_loop_segment_count: None,
})
}
#[allow(clippy::too_many_arguments)]
fn new_with_face_extent_evidence(
relation: RetainedPlanarFaceEdgeUseRelation2,
surface: RetainedPlanarSurfaceIdentity2,
trim_role: RetainedPlanarTrimLoopRole2,
trim_loop_index: usize,
trim_segment_index: usize,
segment_count: usize,
trim_role_loop_count: usize,
trim_loop_segment_count: usize,
) -> CurveResult<Self> {
validate_planar_face_edge_use_report(
relation,
Some(surface),
Some(trim_role),
Some(trim_loop_index),
Some(trim_segment_index),
segment_count,
Some(trim_role_loop_count),
Some(trim_loop_segment_count),
)?;
Ok(Self {
relation,
surface: Some(surface),
trim_role: Some(trim_role),
trim_loop_index: Some(trim_loop_index),
trim_segment_index: Some(trim_segment_index),
segment_count,
trim_role_loop_count: Some(trim_role_loop_count),
trim_loop_segment_count: Some(trim_loop_segment_count),
})
}
pub const fn relation(&self) -> RetainedPlanarFaceEdgeUseRelation2 {
self.relation
}
pub const fn surface(&self) -> Option<RetainedPlanarSurfaceIdentity2> {
self.surface
}
pub const fn trim_role(&self) -> Option<RetainedPlanarTrimLoopRole2> {
self.trim_role
}
pub const fn trim_loop_index(&self) -> Option<usize> {
self.trim_loop_index
}
pub const fn trim_segment_index(&self) -> Option<usize> {
self.trim_segment_index
}
pub const fn segment_count(&self) -> usize {
self.segment_count
}
}
impl RetainedPlanarFacePointReport2 {
pub fn new(
location: RetainedPlanarFacePointLocation2,
surface: Option<RetainedPlanarSurfaceIdentity2>,
material_loop_count: usize,
hole_loop_count: usize,
) -> CurveResult<Self> {
validate_planar_face_point_report(location, surface, material_loop_count)?;
Ok(Self {
location,
surface,
material_loop_count,
hole_loop_count,
})
}
pub const fn location(&self) -> RetainedPlanarFacePointLocation2 {
self.location
}
pub const fn surface(&self) -> Option<RetainedPlanarSurfaceIdentity2> {
self.surface
}
pub const fn material_loop_count(&self) -> usize {
self.material_loop_count
}
pub const fn hole_loop_count(&self) -> usize {
self.hole_loop_count
}
}
fn validate_planar_pcurve_image_report(
relation: PlanarPcurveImageRelation2,
surface: Option<RetainedPlanarSurfaceIdentity2>,
segment_count: usize,
) -> CurveResult<()> {
match relation {
PlanarPcurveImageRelation2::SurfaceMismatch => {
if surface.is_some() || segment_count != 0 {
return Err(CurveError::Topology(
"surface-mismatch pcurve image report must not carry image evidence".into(),
));
}
}
PlanarPcurveImageRelation2::Different => {
if surface.is_none() || segment_count != 0 {
return Err(CurveError::Topology(
"different pcurve image report must carry only matching-surface evidence"
.into(),
));
}
}
PlanarPcurveImageRelation2::SameDirected | PlanarPcurveImageRelation2::SameReversed => {
if surface.is_none() || segment_count == 0 {
return Err(CurveError::Topology(
"same-image pcurve report must carry surface and positive segment evidence"
.into(),
));
}
}
}
Ok(())
}
fn validate_planar_face_point_report(
location: RetainedPlanarFacePointLocation2,
surface: Option<RetainedPlanarSurfaceIdentity2>,
material_loop_count: usize,
) -> CurveResult<()> {
if material_loop_count == 0 {
return Err(CurveError::Topology(
"retained planar face point report must reference a face with material loops".into(),
));
}
match location {
RetainedPlanarFacePointLocation2::SurfaceMismatch => {
if surface.is_some() {
return Err(CurveError::Topology(
"surface-mismatch point report must not carry trim-classification surface evidence"
.into(),
));
}
}
RetainedPlanarFacePointLocation2::Outside
| RetainedPlanarFacePointLocation2::Boundary
| RetainedPlanarFacePointLocation2::Inside => {
if surface.is_none() {
return Err(CurveError::Topology(
"trim-classified point report must carry matching surface evidence".into(),
));
}
}
}
Ok(())
}
fn validate_planar_face_edge_use_report(
relation: RetainedPlanarFaceEdgeUseRelation2,
surface: Option<RetainedPlanarSurfaceIdentity2>,
trim_role: Option<RetainedPlanarTrimLoopRole2>,
trim_loop_index: Option<usize>,
trim_segment_index: Option<usize>,
segment_count: usize,
trim_role_loop_count: Option<usize>,
trim_loop_segment_count: Option<usize>,
) -> CurveResult<()> {
match relation {
RetainedPlanarFaceEdgeUseRelation2::SurfaceMismatch => {
if surface.is_some()
|| trim_role.is_some()
|| trim_loop_index.is_some()
|| trim_segment_index.is_some()
|| segment_count != 0
|| trim_role_loop_count.is_some()
|| trim_loop_segment_count.is_some()
{
return Err(CurveError::Topology(
"surface-mismatch edge-use report must not carry trim evidence".into(),
));
}
}
RetainedPlanarFaceEdgeUseRelation2::NotTrimBoundary => {
if surface.is_none()
|| trim_role.is_some()
|| trim_loop_index.is_some()
|| trim_segment_index.is_some()
|| segment_count != 0
|| trim_role_loop_count.is_some()
|| trim_loop_segment_count.is_some()
{
return Err(CurveError::Topology(
"non-boundary edge-use report must carry only matching-surface evidence".into(),
));
}
}
RetainedPlanarFaceEdgeUseRelation2::BoundarySameDirected
| RetainedPlanarFaceEdgeUseRelation2::BoundarySameReversed => {
if surface.is_none()
|| trim_role.is_none()
|| trim_loop_index.is_none()
|| trim_segment_index.is_none()
|| segment_count == 0
|| trim_role_loop_count.is_none()
|| trim_loop_segment_count.is_none()
{
return Err(CurveError::Topology(
"boundary edge-use report must carry complete positive trim evidence".into(),
));
}
let (
Some(trim_loop_index),
Some(trim_segment_index),
Some(trim_role_loop_count),
Some(trim_loop_segment_count),
) = (
trim_loop_index,
trim_segment_index,
trim_role_loop_count,
trim_loop_segment_count,
)
else {
return Err(CurveError::Topology(
"boundary edge-use report must carry complete positive trim evidence".into(),
));
};
if trim_role_loop_count == 0
|| trim_loop_segment_count == 0
|| trim_loop_index >= trim_role_loop_count
|| trim_segment_index >= trim_loop_segment_count
|| segment_count > trim_loop_segment_count
{
return Err(CurveError::Topology(
"boundary edge-use report trim indices must be certified by face extent evidence"
.into(),
));
}
}
}
Ok(())
}
fn same_directed_segments(first: &[Segment2], second: &[Segment2]) -> bool {
first == second
}
fn same_reversed_segments(first: &[Segment2], second: &[Segment2]) -> bool {
first.len() == second.len()
&& first
.iter()
.zip(second.iter().rev())
.all(|(left, right)| left == &right.reversed())
}
fn same_directed_segment_cycle(first: &[Segment2], second: &[Segment2]) -> bool {
let len = first.len();
if len != second.len() {
return false;
}
(0..len).any(|offset| {
first
.iter()
.enumerate()
.all(|(index, segment)| segment == &second[(offset + index) % len])
})
}
fn same_reversed_segment_cycle(first: &[Segment2], second: &[Segment2]) -> bool {
let len = first.len();
if len != second.len() {
return false;
}
(0..len).any(|offset| {
first.iter().enumerate().all(|(index, segment)| {
let reversed_index = (offset + len - 1 - index) % len;
segment == &second[reversed_index].reversed()
})
})
}
fn face_edge_use_report_from_loops(
face: &RetainedPlanarFace2,
query_segments: &[Segment2],
) -> CurveResult<RetainedPlanarFaceEdgeUseReport2> {
for (loop_index, trim) in face.material_loops.iter().enumerate() {
if let Some((relation, segment_index)) =
segment_subchain_relation(query_segments, trim.contour.segments())
{
return RetainedPlanarFaceEdgeUseReport2::new_with_face_extent_evidence(
relation,
face.surface,
RetainedPlanarTrimLoopRole2::Material,
loop_index,
segment_index,
query_segments.len(),
face.material_loops.len(),
trim.contour.len(),
);
}
}
for (loop_index, trim) in face.hole_loops.iter().enumerate() {
if let Some((relation, segment_index)) =
segment_subchain_relation(query_segments, trim.contour.segments())
{
return RetainedPlanarFaceEdgeUseReport2::new_with_face_extent_evidence(
relation,
face.surface,
RetainedPlanarTrimLoopRole2::Hole,
loop_index,
segment_index,
query_segments.len(),
face.hole_loops.len(),
trim.contour.len(),
);
}
}
RetainedPlanarFaceEdgeUseReport2::new(
RetainedPlanarFaceEdgeUseRelation2::NotTrimBoundary,
Some(face.surface),
None,
None,
None,
0,
)
}
fn segment_subchain_relation(
query_segments: &[Segment2],
loop_segments: &[Segment2],
) -> Option<(RetainedPlanarFaceEdgeUseRelation2, usize)> {
if query_segments.is_empty() || query_segments.len() > loop_segments.len() {
return None;
}
if let Some(segment_index) = directed_segment_subchain_start(query_segments, loop_segments) {
return Some((
RetainedPlanarFaceEdgeUseRelation2::BoundarySameDirected,
segment_index,
));
}
reversed_segment_subchain_start(query_segments, loop_segments).map(|segment_index| {
(
RetainedPlanarFaceEdgeUseRelation2::BoundarySameReversed,
segment_index,
)
})
}
fn directed_segment_subchain_start(
query_segments: &[Segment2],
loop_segments: &[Segment2],
) -> Option<usize> {
let len = loop_segments.len();
(0..len).find(|&offset| {
query_segments
.iter()
.enumerate()
.all(|(index, segment)| segment == &loop_segments[(offset + index) % len])
})
}
fn reversed_segment_subchain_start(
query_segments: &[Segment2],
loop_segments: &[Segment2],
) -> Option<usize> {
let len = loop_segments.len();
(0..len).find(|&offset| {
query_segments.iter().enumerate().all(|(index, segment)| {
let loop_index = (offset + len - index) % len;
segment == &loop_segments[loop_index].reversed()
})
})
}
fn face_point_report_from_region_classification(
classification: Classification<RegionPointLocation>,
surface: RetainedPlanarSurfaceIdentity2,
material_loop_count: usize,
hole_loop_count: usize,
) -> CurveResult<Classification<RetainedPlanarFacePointReport2>> {
let location = match classification {
Classification::Decided(RegionPointLocation::Outside) => {
RetainedPlanarFacePointLocation2::Outside
}
Classification::Decided(RegionPointLocation::Boundary) => {
RetainedPlanarFacePointLocation2::Boundary
}
Classification::Decided(RegionPointLocation::Inside) => {
RetainedPlanarFacePointLocation2::Inside
}
Classification::Uncertain(UncertaintyReason::Boundary) => {
RetainedPlanarFacePointLocation2::Boundary
}
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
Ok(Classification::Decided(
RetainedPlanarFacePointReport2::new(
location,
Some(surface),
material_loop_count,
hole_loop_count,
)?,
))
}