moonlight-triangulation 1.2.0.1 → 1.3.0.0
raw patch · 91 files changed
+11730/−5338 lines, 91 filesdep +aesondep +directorydep +filepathdep ~basedep ~containersdep ~moonlight-triangulationnew-component:exe:moonlight-triangulation-category-observatory-exportnew-component:exe:moonlight-triangulation-delaunay-comparenew-component:exe:moonlight-triangulation-delaunay-picturesPVP ok
version bump matches the API change (PVP)
Dependencies added: aeson, directory, filepath, moonlight-algebra, moonlight-category, moonlight-homology, process, tasty, tasty-bench, tasty-hunit, unix
Dependency ranges changed: base, containers, moonlight-triangulation, transformers
API changes (from Hackage documentation)
- Moonlight.Triangulation: constrainedExtensionConstraintOutcomes :: ConstrainedExtensionResult vertex directed undirected face -> Vector ConstraintOutcome
- Moonlight.Triangulation: constrainedExtensionConstraintStats :: ConstrainedExtensionResult vertex directed undirected face -> ConstraintBatchStats
- Moonlight.Triangulation: constrainedExtensionTriangulation :: ConstrainedExtensionResult vertex directed undirected face -> Triangulation 'Constrained vertex directed undirected face
+ Moonlight.Triangulation: BoundaryClockwise :: BoundaryOrientation
+ Moonlight.Triangulation: BoundaryCounterClockwise :: BoundaryOrientation
+ Moonlight.Triangulation: boundaryLoopOrientation :: BoundaryLoop -> BoundaryOrientation
+ Moonlight.Triangulation: componentBoundaryLoops :: forall (mode :: ConstraintMode) vertex directed undirected face. Triangulation mode vertex directed undirected face -> FaceComponent -> Either BoundaryObstruction (NonEmpty BoundaryLoop)
+ Moonlight.Triangulation: constrainedExtensionConstraintBatch :: ConstrainedExtensionResult vertex directed undirected face -> ConstraintBatchResult vertex directed undirected face
+ Moonlight.Triangulation: constraintBatchOutcomes :: ConstraintBatchResult vertex directed undirected face -> Vector ConstraintOutcome
+ Moonlight.Triangulation: constraintBatchStats :: ConstraintBatchResult vertex directed undirected face -> ConstraintBatchStats
+ Moonlight.Triangulation: constraintBatchTriangulation :: ConstraintBatchResult vertex directed undirected face -> Triangulation 'Constrained vertex directed undirected face
+ Moonlight.Triangulation: data BoundaryOrientation
+ Moonlight.Triangulation: data ConstraintBatchResult vertex directed undirected face
+ Moonlight.Triangulation: planarValuationsPerimeter :: PlanarValuations -> Either ValuationError ExactLengthMeasurement
+ Moonlight.Triangulation.HintGenerator: buildHierarchyHint :: forall (mode :: ConstraintMode) vertex directed undirected face. Int -> Triangulation mode vertex directed undirected face -> Either BuildError HierarchyHint
+ Moonlight.Triangulation.HintGenerator: data HierarchyHint
+ Moonlight.Triangulation.HintGenerator: data LastUsedHint
+ Moonlight.Triangulation.HintGenerator: defaultHierarchyBranchFactor :: Int
+ Moonlight.Triangulation.HintGenerator: emptyLastUsedHint :: LastUsedHint
+ Moonlight.Triangulation.HintGenerator: hierarchyBaseCount :: HierarchyHint -> Int
+ Moonlight.Triangulation.HintGenerator: hierarchyBranchFactor :: HierarchyHint -> Int
+ Moonlight.Triangulation.HintGenerator: hierarchyHint :: HierarchyHint -> QueryPoint -> Maybe LocationHint
+ Moonlight.Triangulation.HintGenerator: hierarchyLevelCount :: HierarchyHint -> Int
+ Moonlight.Triangulation.HintGenerator: hierarchyVertexCount :: HierarchyHint -> Int
+ Moonlight.Triangulation.HintGenerator: instance Control.DeepSeq.NFData Moonlight.Triangulation.HintGenerator.HierarchyHint
+ Moonlight.Triangulation.HintGenerator: instance GHC.Internal.Classes.Eq Moonlight.Triangulation.HintGenerator.HierarchyHint
+ Moonlight.Triangulation.HintGenerator: instance GHC.Internal.Classes.Eq Moonlight.Triangulation.HintGenerator.LastUsedHint
+ Moonlight.Triangulation.HintGenerator: instance GHC.Internal.Classes.Ord Moonlight.Triangulation.HintGenerator.LastUsedHint
+ Moonlight.Triangulation.HintGenerator: instance GHC.Internal.Generics.Generic Moonlight.Triangulation.HintGenerator.HierarchyHint
+ Moonlight.Triangulation.HintGenerator: instance GHC.Internal.Show.Show Moonlight.Triangulation.HintGenerator.HierarchyHint
+ Moonlight.Triangulation.HintGenerator: instance GHC.Internal.Show.Show Moonlight.Triangulation.HintGenerator.LastUsedHint
+ Moonlight.Triangulation.HintGenerator: lastUsedHint :: LastUsedHint -> Maybe LocationHint
+ Moonlight.Triangulation.HintGenerator: rebuildHierarchyHint :: forall (mode :: ConstraintMode) vertex directed undirected face. HierarchyHint -> Triangulation mode vertex directed undirected face -> Either BuildError HierarchyHint
+ Moonlight.Triangulation.HintGenerator: rememberVertex :: VertexId -> LastUsedHint -> LastUsedHint
+ Moonlight.Triangulation.HintGenerator: removeManyWithHierarchy :: forall (mode :: ConstraintMode) vertex directed undirected face. HierarchyHint -> Triangulation mode vertex directed undirected face -> Vector Point -> Either BuildError (Vector (Maybe (RemovalOutcome vertex)), Triangulation mode vertex directed undirected face, HierarchyHint)
+ Moonlight.Triangulation.HintGenerator: updateHierarchyAfterInsertion :: HierarchyHint -> Point -> VertexId -> InsertionDisposition -> Either BuildError HierarchyHint
+ Moonlight.Triangulation.HintGenerator: updateHierarchyAfterRemoval :: HierarchyHint -> Maybe (VertexId, Point) -> Either BuildError HierarchyHint
+ Moonlight.Triangulation.Internal.Minkowski.Convex: addExactPoints :: ExactPoint -> ExactPoint -> ExactPoint
+ Moonlight.Triangulation.Internal.Minkowski.Convex: admittedConvexLoop :: ExactLoop -> Maybe ConvexPolygon
+ Moonlight.Triangulation.Internal.Minkowski.Convex: convexHullPolygon :: NonEmpty ExactPoint -> Either MinkowskiError ConvexPolygon
+ Moonlight.Triangulation.Internal.Minkowski.Convex: convexMinkowskiPolygon :: ConvexPolygon -> ConvexPolygon -> ConvexPolygon
+ Moonlight.Triangulation.Internal.Minkowski.Convex: convexMinkowskiSum :: ConvexPolygon -> ConvexPolygon -> PlanarRegion
+ Moonlight.Triangulation.Internal.Minkowski.Convex: convexPolygon :: NonEmpty ExactPoint -> Either MinkowskiError ConvexPolygon
+ Moonlight.Triangulation.Internal.Minkowski.Convex: convexPolygonCentroid :: ConvexPolygon -> Either MinkowskiError ExactPoint
+ Moonlight.Triangulation.Internal.Minkowski.Convex: convexPolygonPoints :: ConvexPolygon -> NonEmpty ExactPoint
+ Moonlight.Triangulation.Internal.Minkowski.Convex: convexPolygonRegion :: ConvexPolygon -> PlanarRegion
+ Moonlight.Triangulation.Internal.Minkowski.Convex: erodeConvexBy :: ConvexPolygon -> ConvexPolygon -> Either MinkowskiError (Maybe ConvexPolygon)
+ Moonlight.Triangulation.Internal.Minkowski.Convex: reflectConvexPolygon :: ConvexPolygon -> ConvexPolygon
+ Moonlight.Triangulation.Internal.Minkowski.Convex: structuringElement :: ConvexPolygon -> Either MinkowskiError StructuringElement
+ Moonlight.Triangulation.Internal.Minkowski.Convex: structuringElementPolygon :: StructuringElement -> ConvexPolygon
+ Moonlight.Triangulation.Internal.Minkowski.Convex: subtractExactPoints :: ExactPoint -> ExactPoint -> ExactPoint
+ Moonlight.Triangulation.Internal.Minkowski.Types: ConvexPolygon :: ExactLoop -> ConvexPolygon
+ Moonlight.Triangulation.Internal.Minkowski.Types: MinkowskiAddition :: MinkowskiOperation
+ Moonlight.Triangulation.Internal.Minkowski.Types: MinkowskiCandidateCellMissing :: OverlayCellId -> MinkowskiError
+ Moonlight.Triangulation.Internal.Minkowski.Types: MinkowskiClosing :: MinkowskiOperation
+ Moonlight.Triangulation.Internal.Minkowski.Types: MinkowskiConvexHullDegenerate :: [ExactPoint] -> MinkowskiError
+ Moonlight.Triangulation.Internal.Minkowski.Types: MinkowskiErosion :: MinkowskiOperation
+ Moonlight.Triangulation.Internal.Minkowski.Types: MinkowskiExactArithmetic :: ExactArithmeticError -> MinkowskiError
+ Moonlight.Triangulation.Internal.Minkowski.Types: MinkowskiFaceArity :: FaceId -> Int -> MinkowskiError
+ Moonlight.Triangulation.Internal.Minkowski.Types: MinkowskiInclusionAmbiguous :: OverlayCellId -> ExactPoint -> MinkowskiError
+ Moonlight.Triangulation.Internal.Minkowski.Types: MinkowskiInvalidConvexLoop :: RegionValidationError -> MinkowskiError
+ Moonlight.Triangulation.Internal.Minkowski.Types: MinkowskiInvalidSegment :: ExactGeometryError -> MinkowskiError
+ Moonlight.Triangulation.Internal.Minkowski.Types: MinkowskiLineIntersection :: ExactIntersectionError -> MinkowskiError
+ Moonlight.Triangulation.Internal.Minkowski.Types: MinkowskiNonConvexTurn :: Int -> Ordering -> MinkowskiError
+ Moonlight.Triangulation.Internal.Minkowski.Types: MinkowskiOpening :: MinkowskiOperation
+ Moonlight.Triangulation.Internal.Minkowski.Types: MinkowskiOriginOutside :: RegionPointLocation -> MinkowskiError
+ Moonlight.Triangulation.Internal.Minkowski.Types: MinkowskiOverlayCellWitness :: OverlayCellWitness -> MinkowskiError
+ Moonlight.Triangulation.Internal.Minkowski.Types: MinkowskiOverlayFailed :: OverlayError Bool Bool -> MinkowskiError
+ Moonlight.Triangulation.Internal.Minkowski.Types: MinkowskiPublicationFailed :: RegionPublicationError -> MinkowskiError
+ Moonlight.Triangulation.Internal.Minkowski.Types: MinkowskiReceipt :: MinkowskiOperation -> Int -> Int -> Int -> Int -> Int -> Int -> Int -> Int -> MinkowskiReceipt
+ Moonlight.Triangulation.Internal.Minkowski.Types: StructuringElement :: ConvexPolygon -> StructuringElement
+ Moonlight.Triangulation.Internal.Minkowski.Types: [minkowskiConvexPieces] :: MinkowskiReceipt -> Int
+ Moonlight.Triangulation.Internal.Minkowski.Types: [minkowskiExactCoordinateBitGrowth] :: MinkowskiReceipt -> Int
+ Moonlight.Triangulation.Internal.Minkowski.Types: [minkowskiExactCrossings] :: MinkowskiReceipt -> Int
+ Moonlight.Triangulation.Internal.Minkowski.Types: [minkowskiGeneratedConvolutionEdges] :: MinkowskiReceipt -> Int
+ Moonlight.Triangulation.Internal.Minkowski.Types: [minkowskiGeneratedPieces] :: MinkowskiReceipt -> Int
+ Moonlight.Triangulation.Internal.Minkowski.Types: [minkowskiInputComponents] :: MinkowskiReceipt -> Int
+ Moonlight.Triangulation.Internal.Minkowski.Types: [minkowskiOperation] :: MinkowskiReceipt -> MinkowskiOperation
+ Moonlight.Triangulation.Internal.Minkowski.Types: [minkowskiOutputCells] :: MinkowskiReceipt -> Int
+ Moonlight.Triangulation.Internal.Minkowski.Types: [minkowskiOverlayPasses] :: MinkowskiReceipt -> Int
+ Moonlight.Triangulation.Internal.Minkowski.Types: data MinkowskiError
+ Moonlight.Triangulation.Internal.Minkowski.Types: data MinkowskiOperation
+ Moonlight.Triangulation.Internal.Minkowski.Types: data MinkowskiReceipt
+ Moonlight.Triangulation.Internal.Minkowski.Types: instance Control.DeepSeq.NFData Moonlight.Triangulation.Internal.Minkowski.Types.ConvexPolygon
+ Moonlight.Triangulation.Internal.Minkowski.Types: instance Control.DeepSeq.NFData Moonlight.Triangulation.Internal.Minkowski.Types.MinkowskiError
+ Moonlight.Triangulation.Internal.Minkowski.Types: instance Control.DeepSeq.NFData Moonlight.Triangulation.Internal.Minkowski.Types.MinkowskiOperation
+ Moonlight.Triangulation.Internal.Minkowski.Types: instance Control.DeepSeq.NFData Moonlight.Triangulation.Internal.Minkowski.Types.MinkowskiReceipt
+ Moonlight.Triangulation.Internal.Minkowski.Types: instance Control.DeepSeq.NFData Moonlight.Triangulation.Internal.Minkowski.Types.StructuringElement
+ Moonlight.Triangulation.Internal.Minkowski.Types: instance GHC.Internal.Classes.Eq Moonlight.Triangulation.Internal.Minkowski.Types.ConvexPolygon
+ Moonlight.Triangulation.Internal.Minkowski.Types: instance GHC.Internal.Classes.Eq Moonlight.Triangulation.Internal.Minkowski.Types.MinkowskiError
+ Moonlight.Triangulation.Internal.Minkowski.Types: instance GHC.Internal.Classes.Eq Moonlight.Triangulation.Internal.Minkowski.Types.MinkowskiOperation
+ Moonlight.Triangulation.Internal.Minkowski.Types: instance GHC.Internal.Classes.Eq Moonlight.Triangulation.Internal.Minkowski.Types.MinkowskiReceipt
+ Moonlight.Triangulation.Internal.Minkowski.Types: instance GHC.Internal.Classes.Eq Moonlight.Triangulation.Internal.Minkowski.Types.StructuringElement
+ Moonlight.Triangulation.Internal.Minkowski.Types: instance GHC.Internal.Classes.Ord Moonlight.Triangulation.Internal.Minkowski.Types.ConvexPolygon
+ Moonlight.Triangulation.Internal.Minkowski.Types: instance GHC.Internal.Classes.Ord Moonlight.Triangulation.Internal.Minkowski.Types.MinkowskiOperation
+ Moonlight.Triangulation.Internal.Minkowski.Types: instance GHC.Internal.Classes.Ord Moonlight.Triangulation.Internal.Minkowski.Types.MinkowskiReceipt
+ Moonlight.Triangulation.Internal.Minkowski.Types: instance GHC.Internal.Classes.Ord Moonlight.Triangulation.Internal.Minkowski.Types.StructuringElement
+ Moonlight.Triangulation.Internal.Minkowski.Types: instance GHC.Internal.Generics.Generic Moonlight.Triangulation.Internal.Minkowski.Types.ConvexPolygon
+ Moonlight.Triangulation.Internal.Minkowski.Types: instance GHC.Internal.Generics.Generic Moonlight.Triangulation.Internal.Minkowski.Types.MinkowskiError
+ Moonlight.Triangulation.Internal.Minkowski.Types: instance GHC.Internal.Generics.Generic Moonlight.Triangulation.Internal.Minkowski.Types.MinkowskiOperation
+ Moonlight.Triangulation.Internal.Minkowski.Types: instance GHC.Internal.Generics.Generic Moonlight.Triangulation.Internal.Minkowski.Types.MinkowskiReceipt
+ Moonlight.Triangulation.Internal.Minkowski.Types: instance GHC.Internal.Generics.Generic Moonlight.Triangulation.Internal.Minkowski.Types.StructuringElement
+ Moonlight.Triangulation.Internal.Minkowski.Types: instance GHC.Internal.Show.Show Moonlight.Triangulation.Internal.Minkowski.Types.ConvexPolygon
+ Moonlight.Triangulation.Internal.Minkowski.Types: instance GHC.Internal.Show.Show Moonlight.Triangulation.Internal.Minkowski.Types.MinkowskiError
+ Moonlight.Triangulation.Internal.Minkowski.Types: instance GHC.Internal.Show.Show Moonlight.Triangulation.Internal.Minkowski.Types.MinkowskiOperation
+ Moonlight.Triangulation.Internal.Minkowski.Types: instance GHC.Internal.Show.Show Moonlight.Triangulation.Internal.Minkowski.Types.MinkowskiReceipt
+ Moonlight.Triangulation.Internal.Minkowski.Types: instance GHC.Internal.Show.Show Moonlight.Triangulation.Internal.Minkowski.Types.StructuringElement
+ Moonlight.Triangulation.Internal.Minkowski.Types: newtype ConvexPolygon
+ Moonlight.Triangulation.Internal.Minkowski.Types: newtype StructuringElement
+ Moonlight.Triangulation.Internal.Overlay.Arrangement: ArrangementMetrics :: Int -> Int -> Int -> Int -> Int -> Int -> Int -> ArrangementMetrics
+ Moonlight.Triangulation.Internal.Overlay.Arrangement: AtomicEdge :: ExactSegment -> OverlayEdgeOrigin -> Maybe (leftLabel, leftLabel) -> Maybe (rightLabel, rightLabel) -> AtomicEdge leftLabel rightLabel
+ Moonlight.Triangulation.Internal.Overlay.Arrangement: CertifiedArrangement :: Vector (AtomicEdge leftLabel rightLabel) -> Vector OverlayVertexSeed -> Vector (Int, Int) -> Set ExactPoint -> ArrangementMetrics -> CertifiedArrangement leftLabel rightLabel
+ Moonlight.Triangulation.Internal.Overlay.Arrangement: OverlayVertexSeed :: ExactPoint -> Point -> OverlayVertexOrigin -> OverlayVertexSeed
+ Moonlight.Triangulation.Internal.Overlay.Arrangement: [arrangementEmbeddingCandidates] :: ArrangementMetrics -> Int
+ Moonlight.Triangulation.Internal.Overlay.Arrangement: [arrangementExactCrossings] :: ArrangementMetrics -> Int
+ Moonlight.Triangulation.Internal.Overlay.Arrangement: [arrangementInputSegments] :: ArrangementMetrics -> Int
+ Moonlight.Triangulation.Internal.Overlay.Arrangement: [arrangementOverlapIntervals] :: ArrangementMetrics -> Int
+ Moonlight.Triangulation.Internal.Overlay.Arrangement: [arrangementRelationEvents] :: ArrangementMetrics -> Int
+ Moonlight.Triangulation.Internal.Overlay.Arrangement: [arrangementSweepMaximumHeight] :: ArrangementMetrics -> Int
+ Moonlight.Triangulation.Internal.Overlay.Arrangement: [arrangementTotalRelationChecks] :: ArrangementMetrics -> Int
+ Moonlight.Triangulation.Internal.Overlay.Arrangement: [atomicEdgeLeftTransition] :: AtomicEdge leftLabel rightLabel -> Maybe (leftLabel, leftLabel)
+ Moonlight.Triangulation.Internal.Overlay.Arrangement: [atomicEdgeOrigin] :: AtomicEdge leftLabel rightLabel -> OverlayEdgeOrigin
+ Moonlight.Triangulation.Internal.Overlay.Arrangement: [atomicEdgeRightTransition] :: AtomicEdge leftLabel rightLabel -> Maybe (rightLabel, rightLabel)
+ Moonlight.Triangulation.Internal.Overlay.Arrangement: [atomicEdgeSegment] :: AtomicEdge leftLabel rightLabel -> ExactSegment
+ Moonlight.Triangulation.Internal.Overlay.Arrangement: [certifiedAtomicEdges] :: CertifiedArrangement leftLabel rightLabel -> Vector (AtomicEdge leftLabel rightLabel)
+ Moonlight.Triangulation.Internal.Overlay.Arrangement: [certifiedConstraints] :: CertifiedArrangement leftLabel rightLabel -> Vector (Int, Int)
+ Moonlight.Triangulation.Internal.Overlay.Arrangement: [certifiedInexactEmbeddingPoints] :: CertifiedArrangement leftLabel rightLabel -> Set ExactPoint
+ Moonlight.Triangulation.Internal.Overlay.Arrangement: [certifiedMetrics] :: CertifiedArrangement leftLabel rightLabel -> ArrangementMetrics
+ Moonlight.Triangulation.Internal.Overlay.Arrangement: [certifiedVertexSeeds] :: CertifiedArrangement leftLabel rightLabel -> Vector OverlayVertexSeed
+ Moonlight.Triangulation.Internal.Overlay.Arrangement: [seedEmbeddedPoint] :: OverlayVertexSeed -> Point
+ Moonlight.Triangulation.Internal.Overlay.Arrangement: [seedExactPoint] :: OverlayVertexSeed -> ExactPoint
+ Moonlight.Triangulation.Internal.Overlay.Arrangement: [seedOrigin] :: OverlayVertexSeed -> OverlayVertexOrigin
+ Moonlight.Triangulation.Internal.Overlay.Arrangement: atomicEdgeFrom :: AtomicEdge leftLabel rightLabel -> ExactPoint
+ Moonlight.Triangulation.Internal.Overlay.Arrangement: atomicEdgeTo :: AtomicEdge leftLabel rightLabel -> ExactPoint
+ Moonlight.Triangulation.Internal.Overlay.Arrangement: atomicKey :: AtomicEdge leftLabel rightLabel -> ExactEdgeKey
+ Moonlight.Triangulation.Internal.Overlay.Arrangement: canonicalEdgeKey :: ExactPoint -> ExactPoint -> ExactEdgeKey
+ Moonlight.Triangulation.Internal.Overlay.Arrangement: certifyArrangement :: (Ord leftLabel, Ord rightLabel) => PlanarLayer leftLabel -> PlanarLayer rightLabel -> Either (OverlayError leftLabel rightLabel) (CertifiedArrangement leftLabel rightLabel)
+ Moonlight.Triangulation.Internal.Overlay.Arrangement: compareAround :: ExactPoint -> ExactPoint -> ExactPoint -> Ordering
+ Moonlight.Triangulation.Internal.Overlay.Arrangement: data ArrangementMetrics
+ Moonlight.Triangulation.Internal.Overlay.Arrangement: data AtomicEdge leftLabel rightLabel
+ Moonlight.Triangulation.Internal.Overlay.Arrangement: data CertifiedArrangement leftLabel rightLabel
+ Moonlight.Triangulation.Internal.Overlay.Arrangement: data OverlayVertexSeed
+ Moonlight.Triangulation.Internal.Overlay.Arrangement: instance GHC.Internal.Classes.Eq Moonlight.Triangulation.Internal.Overlay.Arrangement.OverlayVertexSeed
+ Moonlight.Triangulation.Internal.Overlay.Arrangement: instance GHC.Internal.Classes.Ord Moonlight.Triangulation.Internal.Overlay.Arrangement.OverlayVertexSeed
+ Moonlight.Triangulation.Internal.Overlay.Arrangement: instance GHC.Internal.Generics.Generic Moonlight.Triangulation.Internal.Overlay.Arrangement.OverlayVertexSeed
+ Moonlight.Triangulation.Internal.Overlay.Arrangement: instance GHC.Internal.Show.Show Moonlight.Triangulation.Internal.Overlay.Arrangement.OverlayVertexSeed
+ Moonlight.Triangulation.Internal.Overlay.Arrangement: instance Moonlight.Triangulation.Internal.Types.HasPosition Moonlight.Triangulation.Internal.Overlay.Arrangement.OverlayVertexSeed
+ Moonlight.Triangulation.Internal.Overlay.Arrangement: type ExactEdgeKey = (ExactPoint, ExactPoint)
+ Moonlight.Triangulation.Internal.Overlay.Embedding: DraftId :: Int -> DraftId (entity :: DraftEntity)
+ Moonlight.Triangulation.Internal.Overlay.Embedding: DraftIncidence :: DraftSegmentId -> DraftSegmentId -> SegmentRelation -> DraftIncidence
+ Moonlight.Triangulation.Internal.Overlay.Embedding: DraftNeighborhood :: DraftVertexId -> NonEmpty DraftVertexId -> DraftNeighborhood
+ Moonlight.Triangulation.Internal.Overlay.Embedding: DraftReferenceMissing :: DraftReference -> OverlayEmbeddingObstruction
+ Moonlight.Triangulation.Internal.Overlay.Embedding: DraftSegmentReference :: DraftSegmentId -> DraftReference
+ Moonlight.Triangulation.Internal.Overlay.Embedding: DraftVertexReference :: DraftVertexId -> DraftReference
+ Moonlight.Triangulation.Internal.Overlay.Embedding: ExactArrangementDraft :: Map DraftVertexId ExactPoint -> Map DraftSegmentId (DraftVertexId, DraftVertexId) -> Map DraftSourceId [(ExactRational, DraftVertexId)] -> [DraftIncidence] -> [DraftNeighborhood] -> ExactArrangementDraft
+ Moonlight.Triangulation.Internal.Overlay.Embedding: GlobalNoNewCrossing :: EmbeddingObligation
+ Moonlight.Triangulation.Internal.Overlay.Embedding: GlobalRelationAdded :: DraftSegmentId -> DraftSegmentId -> SegmentRelation -> OverlayEmbeddingObstruction
+ Moonlight.Triangulation.Internal.Overlay.Embedding: GlobalRelationChanged :: DraftSegmentId -> DraftSegmentId -> SegmentRelation -> SegmentRelation -> OverlayEmbeddingObstruction
+ Moonlight.Triangulation.Internal.Overlay.Embedding: GlobalRelationRemoved :: DraftSegmentId -> DraftSegmentId -> SegmentRelation -> OverlayEmbeddingObstruction
+ Moonlight.Triangulation.Internal.Overlay.Embedding: IncidenceRelationChanged :: DraftSegmentId -> DraftSegmentId -> SegmentRelation -> SegmentRelation -> SegmentRelation -> OverlayEmbeddingObstruction
+ Moonlight.Triangulation.Internal.Overlay.Embedding: LocalEmbeddingCertificate :: Int -> Int -> Int -> Int -> Map DraftVertexId Point -> EmbeddingResidual -> LocalEmbeddingCertificate
+ Moonlight.Triangulation.Internal.Overlay.Embedding: NeighborhoodRotationChanged :: DraftVertexId -> NonEmpty DraftVertexId -> NonEmpty DraftVertexId -> OverlayEmbeddingObstruction
+ Moonlight.Triangulation.Internal.Overlay.Embedding: ProjectedSegmentCollapsed :: DraftSegmentId -> DraftVertexId -> DraftVertexId -> OverlayEmbeddingObstruction
+ Moonlight.Triangulation.Internal.Overlay.Embedding: RoundedVerticesCollide :: DraftVertexId -> DraftVertexId -> Point -> OverlayEmbeddingObstruction
+ Moonlight.Triangulation.Internal.Overlay.Embedding: SplitOrderNotPreserved :: DraftSourceId -> DraftVertexId -> DraftVertexId -> OverlayEmbeddingObstruction
+ Moonlight.Triangulation.Internal.Overlay.Embedding: VertexProjectionRefused :: DraftVertexId -> PointValidationError -> OverlayEmbeddingObstruction
+ Moonlight.Triangulation.Internal.Overlay.Embedding: [certificateIncidenceRelationPreservationCount] :: LocalEmbeddingCertificate -> Int
+ Moonlight.Triangulation.Internal.Overlay.Embedding: [certificateNeighborhoodRotationPreservationCount] :: LocalEmbeddingCertificate -> Int
+ Moonlight.Triangulation.Internal.Overlay.Embedding: [certificateResidual] :: LocalEmbeddingCertificate -> EmbeddingResidual
+ Moonlight.Triangulation.Internal.Overlay.Embedding: [certificateRoundedVertexDistinctnessCount] :: LocalEmbeddingCertificate -> Int
+ Moonlight.Triangulation.Internal.Overlay.Embedding: [certificateRoundedVertices] :: LocalEmbeddingCertificate -> Map DraftVertexId Point
+ Moonlight.Triangulation.Internal.Overlay.Embedding: [certificateSplitOrderPreservationCount] :: LocalEmbeddingCertificate -> Int
+ Moonlight.Triangulation.Internal.Overlay.Embedding: [draftIncidenceFirstSegment] :: DraftIncidence -> DraftSegmentId
+ Moonlight.Triangulation.Internal.Overlay.Embedding: [draftIncidenceRelation] :: DraftIncidence -> SegmentRelation
+ Moonlight.Triangulation.Internal.Overlay.Embedding: [draftIncidenceSecondSegment] :: DraftIncidence -> DraftSegmentId
+ Moonlight.Triangulation.Internal.Overlay.Embedding: [draftIncidences] :: ExactArrangementDraft -> [DraftIncidence]
+ Moonlight.Triangulation.Internal.Overlay.Embedding: [draftNeighborhoodCenter] :: DraftNeighborhood -> DraftVertexId
+ Moonlight.Triangulation.Internal.Overlay.Embedding: [draftNeighborhoodNeighbors] :: DraftNeighborhood -> NonEmpty DraftVertexId
+ Moonlight.Triangulation.Internal.Overlay.Embedding: [draftNeighborhoods] :: ExactArrangementDraft -> [DraftNeighborhood]
+ Moonlight.Triangulation.Internal.Overlay.Embedding: [draftSegments] :: ExactArrangementDraft -> Map DraftSegmentId (DraftVertexId, DraftVertexId)
+ Moonlight.Triangulation.Internal.Overlay.Embedding: [draftSourceMemberships] :: ExactArrangementDraft -> Map DraftSourceId [(ExactRational, DraftVertexId)]
+ Moonlight.Triangulation.Internal.Overlay.Embedding: [draftVertices] :: ExactArrangementDraft -> Map DraftVertexId ExactPoint
+ Moonlight.Triangulation.Internal.Overlay.Embedding: certifyLocalEmbedding :: ExactArrangementDraft -> Either (NonEmpty OverlayEmbeddingObstruction) LocalEmbeddingCertificate
+ Moonlight.Triangulation.Internal.Overlay.Embedding: data DraftIncidence
+ Moonlight.Triangulation.Internal.Overlay.Embedding: data DraftNeighborhood
+ Moonlight.Triangulation.Internal.Overlay.Embedding: data DraftReference
+ Moonlight.Triangulation.Internal.Overlay.Embedding: data EmbeddingObligation
+ Moonlight.Triangulation.Internal.Overlay.Embedding: data EmbeddingResidual
+ Moonlight.Triangulation.Internal.Overlay.Embedding: data ExactArrangementDraft
+ Moonlight.Triangulation.Internal.Overlay.Embedding: data LocalEmbeddingCertificate
+ Moonlight.Triangulation.Internal.Overlay.Embedding: data OverlayEmbeddingObstruction
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance Control.DeepSeq.NFData (Moonlight.Triangulation.Internal.Overlay.Embedding.DraftId entity)
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance Control.DeepSeq.NFData Moonlight.Triangulation.Internal.Overlay.Embedding.DraftIncidence
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance Control.DeepSeq.NFData Moonlight.Triangulation.Internal.Overlay.Embedding.DraftNeighborhood
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance Control.DeepSeq.NFData Moonlight.Triangulation.Internal.Overlay.Embedding.DraftReference
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance Control.DeepSeq.NFData Moonlight.Triangulation.Internal.Overlay.Embedding.EmbeddingObligation
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance Control.DeepSeq.NFData Moonlight.Triangulation.Internal.Overlay.Embedding.EmbeddingResidual
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance Control.DeepSeq.NFData Moonlight.Triangulation.Internal.Overlay.Embedding.ExactArrangementDraft
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance Control.DeepSeq.NFData Moonlight.Triangulation.Internal.Overlay.Embedding.LocalEmbeddingCertificate
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance Control.DeepSeq.NFData Moonlight.Triangulation.Internal.Overlay.Embedding.OverlayEmbeddingObstruction
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance GHC.Internal.Base.Applicative Moonlight.Triangulation.Internal.Overlay.Embedding.Validation
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance GHC.Internal.Base.Functor Moonlight.Triangulation.Internal.Overlay.Embedding.Validation
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance GHC.Internal.Classes.Eq (Moonlight.Triangulation.Internal.Overlay.Embedding.DraftId entity)
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance GHC.Internal.Classes.Eq Moonlight.Triangulation.Internal.Overlay.Embedding.DraftIncidence
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance GHC.Internal.Classes.Eq Moonlight.Triangulation.Internal.Overlay.Embedding.DraftNeighborhood
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance GHC.Internal.Classes.Eq Moonlight.Triangulation.Internal.Overlay.Embedding.DraftReference
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance GHC.Internal.Classes.Eq Moonlight.Triangulation.Internal.Overlay.Embedding.EmbeddingObligation
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance GHC.Internal.Classes.Eq Moonlight.Triangulation.Internal.Overlay.Embedding.EmbeddingResidual
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance GHC.Internal.Classes.Eq Moonlight.Triangulation.Internal.Overlay.Embedding.ExactArrangementDraft
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance GHC.Internal.Classes.Eq Moonlight.Triangulation.Internal.Overlay.Embedding.LocalEmbeddingCertificate
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance GHC.Internal.Classes.Eq Moonlight.Triangulation.Internal.Overlay.Embedding.OverlayEmbeddingObstruction
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance GHC.Internal.Classes.Ord (Moonlight.Triangulation.Internal.Overlay.Embedding.DraftId entity)
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance GHC.Internal.Classes.Ord Moonlight.Triangulation.Internal.Overlay.Embedding.DraftIncidence
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance GHC.Internal.Classes.Ord Moonlight.Triangulation.Internal.Overlay.Embedding.DraftNeighborhood
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance GHC.Internal.Classes.Ord Moonlight.Triangulation.Internal.Overlay.Embedding.DraftReference
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance GHC.Internal.Classes.Ord Moonlight.Triangulation.Internal.Overlay.Embedding.EmbeddingObligation
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance GHC.Internal.Classes.Ord Moonlight.Triangulation.Internal.Overlay.Embedding.EmbeddingResidual
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance GHC.Internal.Enum.Bounded Moonlight.Triangulation.Internal.Overlay.Embedding.EmbeddingObligation
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance GHC.Internal.Enum.Enum Moonlight.Triangulation.Internal.Overlay.Embedding.EmbeddingObligation
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance GHC.Internal.Generics.Generic (Moonlight.Triangulation.Internal.Overlay.Embedding.DraftId entity)
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance GHC.Internal.Generics.Generic Moonlight.Triangulation.Internal.Overlay.Embedding.DraftIncidence
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance GHC.Internal.Generics.Generic Moonlight.Triangulation.Internal.Overlay.Embedding.DraftNeighborhood
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance GHC.Internal.Generics.Generic Moonlight.Triangulation.Internal.Overlay.Embedding.DraftReference
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance GHC.Internal.Generics.Generic Moonlight.Triangulation.Internal.Overlay.Embedding.EmbeddingObligation
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance GHC.Internal.Generics.Generic Moonlight.Triangulation.Internal.Overlay.Embedding.EmbeddingResidual
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance GHC.Internal.Generics.Generic Moonlight.Triangulation.Internal.Overlay.Embedding.ExactArrangementDraft
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance GHC.Internal.Generics.Generic Moonlight.Triangulation.Internal.Overlay.Embedding.LocalEmbeddingCertificate
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance GHC.Internal.Generics.Generic Moonlight.Triangulation.Internal.Overlay.Embedding.OverlayEmbeddingObstruction
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance GHC.Internal.Show.Show (Moonlight.Triangulation.Internal.Overlay.Embedding.DraftId entity)
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance GHC.Internal.Show.Show Moonlight.Triangulation.Internal.Overlay.Embedding.DraftIncidence
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance GHC.Internal.Show.Show Moonlight.Triangulation.Internal.Overlay.Embedding.DraftNeighborhood
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance GHC.Internal.Show.Show Moonlight.Triangulation.Internal.Overlay.Embedding.DraftReference
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance GHC.Internal.Show.Show Moonlight.Triangulation.Internal.Overlay.Embedding.EmbeddingObligation
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance GHC.Internal.Show.Show Moonlight.Triangulation.Internal.Overlay.Embedding.EmbeddingResidual
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance GHC.Internal.Show.Show Moonlight.Triangulation.Internal.Overlay.Embedding.ExactArrangementDraft
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance GHC.Internal.Show.Show Moonlight.Triangulation.Internal.Overlay.Embedding.LocalEmbeddingCertificate
+ Moonlight.Triangulation.Internal.Overlay.Embedding: instance GHC.Internal.Show.Show Moonlight.Triangulation.Internal.Overlay.Embedding.OverlayEmbeddingObstruction
+ Moonlight.Triangulation.Internal.Overlay.Embedding: milestoneOneResidual :: EmbeddingResidual
+ Moonlight.Triangulation.Internal.Overlay.Embedding: newtype DraftId (entity :: DraftEntity)
+ Moonlight.Triangulation.Internal.Overlay.Embedding: residualUndischargedObligations :: EmbeddingResidual -> NonEmpty EmbeddingObligation
+ Moonlight.Triangulation.Internal.Overlay.Embedding: type DraftSegmentId = DraftId 'DraftSegmentEntity
+ Moonlight.Triangulation.Internal.Overlay.Embedding: type DraftSourceId = DraftId 'DraftSourceEntity
+ Moonlight.Triangulation.Internal.Overlay.Embedding: type DraftVertexId = DraftId 'DraftVertexEntity
+ Moonlight.Triangulation.Internal.Overlay.Resident: CanonicalOverlayDiagonals :: OverlayDiagonalSchedule
+ Moonlight.Triangulation.Internal.Overlay.Resident: FlipFirstAdmissibleDiagonal :: OverlayDiagonalSchedule
+ Moonlight.Triangulation.Internal.Overlay.Resident: data OverlayDiagonalSchedule
+ Moonlight.Triangulation.Internal.Overlay.Resident: edgeSupport :: (Ord leftLabel, Ord rightLabel) => OverlayResult leftLabel rightLabel -> UndirectedEdgeId -> Either OverlayCellWitness (OverlayCellSupport leftLabel rightLabel)
+ Moonlight.Triangulation.Internal.Overlay.Resident: faceCarriesExactArea :: OverlayResult leftLabel rightLabel -> FaceId -> Bool
+ Moonlight.Triangulation.Internal.Overlay.Resident: faceLabels :: OverlayResult leftLabel rightLabel -> FaceId -> Either OverlayCellWitness (leftLabel, rightLabel)
+ Moonlight.Triangulation.Internal.Overlay.Resident: instance GHC.Internal.Classes.Eq Moonlight.Triangulation.Internal.Overlay.Resident.OverlayDiagonalSchedule
+ Moonlight.Triangulation.Internal.Overlay.Resident: instance GHC.Internal.Classes.Ord Moonlight.Triangulation.Internal.Overlay.Resident.OverlayDiagonalSchedule
+ Moonlight.Triangulation.Internal.Overlay.Resident: instance GHC.Internal.Show.Show Moonlight.Triangulation.Internal.Overlay.Resident.OverlayDiagonalSchedule
+ Moonlight.Triangulation.Internal.Overlay.Resident: regionFaceLabels :: OverlayResult leftLabel rightLabel -> FaceId -> Either RegionPublicationError (leftLabel, rightLabel)
+ Moonlight.Triangulation.Internal.Overlay.Resident: residentOverlay :: (Ord leftLabel, Ord rightLabel) => OverlayDiagonalSchedule -> (leftLabel, rightLabel) -> CertifiedArrangement leftLabel rightLabel -> Either (OverlayError leftLabel rightLabel) (OverlayResult leftLabel rightLabel)
+ Moonlight.Triangulation.Internal.Overlay.Resident: vertexSupport :: (Ord leftLabel, Ord rightLabel) => OverlayResult leftLabel rightLabel -> VertexId -> Either OverlayCellWitness (OverlayCellSupport leftLabel rightLabel)
+ Moonlight.Triangulation.Internal.Overlay.Types: BoundaryHoleLoop :: Int -> BoundaryLoopRef
+ Moonlight.Triangulation.Internal.Overlay.Types: BoundaryOuterLoop :: BoundaryLoopRef
+ Moonlight.Triangulation.Internal.Overlay.Types: BoundaryRef :: Int -> BoundaryLoopRef -> Int -> BoundaryRef (feature :: BoundaryFeature) (operand :: OverlayOperand)
+ Moonlight.Triangulation.Internal.Overlay.Types: BoundedOverlayCell :: PolygonComponent -> OverlayCellGeometry
+ Moonlight.Triangulation.Internal.Overlay.Types: ClosedIntersectionSelection :: OverlaySelectionKind
+ Moonlight.Triangulation.Internal.Overlay.Types: ClosedUnionSelection :: OverlaySelectionKind
+ Moonlight.Triangulation.Internal.Overlay.Types: LeftOverlayOperand :: OverlayOperand
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlayArrangementInvalid :: OverlayArrangementObstruction leftLabel rightLabel -> OverlayError leftLabel rightLabel
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlayAtomicConstraintMissing :: ExactPoint -> ExactPoint -> OverlayCellWitness
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlayAtomicConstraintOrientationMismatch :: UndirectedEdgeId -> ExactPoint -> ExactPoint -> OverlayCellWitness
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlayBoundary :: OverlayEdgeOrigin -> OverlayEdge
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlayBoundaryEdgeNotConstrained :: UndirectedEdgeId -> OverlayCellWitness
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlayBuildFailed :: CdtError -> OverlayError leftLabel rightLabel
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlayCell :: leftLabel -> rightLabel -> OverlayCellGeometry -> OverlayCell leftLabel rightLabel
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlayCellCycleDidNotClose :: ExactPoint -> ExactPoint -> OverlayArrangementObstruction leftLabel rightLabel
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlayCellFace :: OverlayCellId -> OverlayFace
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlayCellId :: Int -> OverlayCellId
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlayCellPayloadMissing :: OverlayCellId -> OverlayCellWitness
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlayCellSupport :: OverlaySupport leftLabel -> OverlaySupport rightLabel -> OverlayCellSupport leftLabel rightLabel
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlayCollapsedFace :: OverlayCellId -> OverlayFace
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlayCollapsedFacesAmbiguous :: NonEmpty FaceId -> NonEmpty OverlayCellId -> OverlayArrangementObstruction leftLabel rightLabel
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlayCollapsedFacesUnowned :: NonEmpty FaceId -> OverlayArrangementObstruction leftLabel rightLabel
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlayDiagonal :: OverlayEdge
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlayDuplicateCellSignature :: PolygonComponent -> OverlayArrangementObstruction leftLabel rightLabel
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlayEdgeOrigin :: [BoundaryEdgeRef 'LeftOverlayOperand] -> [BoundaryEdgeRef 'RightOverlayOperand] -> OverlayEdgeOrigin
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlayEdgeSupportMissing :: UndirectedEdgeId -> OverlayCellWitness
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlayEmbeddedVertexCountMismatch :: Int -> Int -> OverlayCellWitness
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlayEmbeddingRefused :: NonEmpty OverlayEmbeddingObstruction -> OverlayError leftLabel rightLabel
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlayExactArithmetic :: ExactArithmeticError -> OverlayError leftLabel rightLabel
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlayExactVertexMissing :: ExactPoint -> OverlayCellWitness
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlayFaceComponentEmpty :: OverlayArrangementObstruction leftLabel rightLabel
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlayLeftSourceSideConflict :: OverlayEdgeOrigin -> NonEmpty leftLabel -> OverlayArrangementObstruction leftLabel rightLabel
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlayProvenanceIncomplete :: OverlayCellWitness -> OverlayError leftLabel rightLabel
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlayReceipt :: Int -> Int -> Int -> Int -> Int -> Int -> Int -> Int -> Int -> Int -> Int -> OverlayReceipt
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlayRegionPublicationFailed :: RegionPublicationError -> OverlayError leftLabel rightLabel
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlayResidentFaceArity :: FaceId -> Int -> OverlayArrangementObstruction leftLabel rightLabel
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlayResidentFaceLabelConflict :: FaceId -> UndirectedEdgeId -> (leftLabel, rightLabel) -> (leftLabel, rightLabel) -> OverlayArrangementObstruction leftLabel rightLabel
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlayResidentFaceOrientationReversed :: FaceId -> OverlayArrangementObstruction leftLabel rightLabel
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlayResidentFaceUnassigned :: FaceId -> OverlayCellWitness
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlayResult :: Triangulation 'Constrained OverlayVertex () OverlayEdge OverlayFace -> Vector (OverlayCell leftLabel rightLabel) -> (leftLabel, rightLabel) -> OverlayReceipt -> OverlayResult leftLabel rightLabel
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlayRightSourceSideConflict :: OverlayEdgeOrigin -> NonEmpty rightLabel -> OverlayArrangementObstruction leftLabel rightLabel
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlayRotationDegenerate :: ExactPoint -> OverlayArrangementObstruction leftLabel rightLabel
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlaySegmentEventsInvalid :: ExactSegmentEventObstruction -> OverlayError leftLabel rightLabel
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlaySelectionContainsUnboundedCell :: OverlaySelectionKind -> OverlaySelectionError
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlaySelectionInvalid :: CellSelectionError -> OverlaySelectionError
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlaySelectionProvenance :: OverlayCellWitness -> OverlaySelectionError
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlaySupport :: NonEmpty label -> OverlaySupport label
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlayTransitionSourceMismatch :: FaceId -> UndirectedEdgeId -> (leftLabel, rightLabel) -> (leftLabel, rightLabel) -> OverlayArrangementObstruction leftLabel rightLabel
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlayUnexpectedConstraint :: UndirectedEdgeId -> OverlayCellWitness
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlayVertex :: ExactPoint -> OverlayVertexOrigin -> OverlayVertex
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlayVertexOrigin :: [BoundaryVertexRef 'LeftOverlayOperand] -> [BoundaryVertexRef 'RightOverlayOperand] -> [BoundaryEdgeRef 'LeftOverlayOperand] -> [BoundaryEdgeRef 'RightOverlayOperand] -> OverlayVertexOrigin
+ Moonlight.Triangulation.Internal.Overlay.Types: OverlayVertexSupportMissing :: VertexId -> OverlayCellWitness
+ Moonlight.Triangulation.Internal.Overlay.Types: RegularizedDifferenceSelection :: OverlaySelectionKind
+ Moonlight.Triangulation.Internal.Overlay.Types: RightOverlayOperand :: OverlayOperand
+ Moonlight.Triangulation.Internal.Overlay.Types: UnboundedOverlayCell :: [ExactLoop] -> OverlayCellGeometry
+ Moonlight.Triangulation.Internal.Overlay.Types: [boundaryRefComponent] :: BoundaryRef (feature :: BoundaryFeature) (operand :: OverlayOperand) -> Int
+ Moonlight.Triangulation.Internal.Overlay.Types: [boundaryRefLocalIndex] :: BoundaryRef (feature :: BoundaryFeature) (operand :: OverlayOperand) -> Int
+ Moonlight.Triangulation.Internal.Overlay.Types: [boundaryRefLoop] :: BoundaryRef (feature :: BoundaryFeature) (operand :: OverlayOperand) -> BoundaryLoopRef
+ Moonlight.Triangulation.Internal.Overlay.Types: [overlayArrangementCells] :: OverlayReceipt -> Int
+ Moonlight.Triangulation.Internal.Overlay.Types: [overlayAtomicEdges] :: OverlayReceipt -> Int
+ Moonlight.Triangulation.Internal.Overlay.Types: [overlayCellGeometry] :: OverlayCell leftLabel rightLabel -> OverlayCellGeometry
+ Moonlight.Triangulation.Internal.Overlay.Types: [overlayCellLeft] :: OverlayCell leftLabel rightLabel -> leftLabel
+ Moonlight.Triangulation.Internal.Overlay.Types: [overlayCellRight] :: OverlayCell leftLabel rightLabel -> rightLabel
+ Moonlight.Triangulation.Internal.Overlay.Types: [overlayEdgeLeftSources] :: OverlayEdgeOrigin -> [BoundaryEdgeRef 'LeftOverlayOperand]
+ Moonlight.Triangulation.Internal.Overlay.Types: [overlayEdgeRightSources] :: OverlayEdgeOrigin -> [BoundaryEdgeRef 'RightOverlayOperand]
+ Moonlight.Triangulation.Internal.Overlay.Types: [overlayEmbeddingCandidates] :: OverlayReceipt -> Int
+ Moonlight.Triangulation.Internal.Overlay.Types: [overlayExactCrossings] :: OverlayReceipt -> Int
+ Moonlight.Triangulation.Internal.Overlay.Types: [overlayExactPoint] :: OverlayVertex -> ExactPoint
+ Moonlight.Triangulation.Internal.Overlay.Types: [overlayFaceCellId] :: OverlayFace -> OverlayCellId
+ Moonlight.Triangulation.Internal.Overlay.Types: [overlayInputSegments] :: OverlayReceipt -> Int
+ Moonlight.Triangulation.Internal.Overlay.Types: [overlayOriginLeftEdges] :: OverlayVertexOrigin -> [BoundaryEdgeRef 'LeftOverlayOperand]
+ Moonlight.Triangulation.Internal.Overlay.Types: [overlayOriginLeftVertices] :: OverlayVertexOrigin -> [BoundaryVertexRef 'LeftOverlayOperand]
+ Moonlight.Triangulation.Internal.Overlay.Types: [overlayOriginRightEdges] :: OverlayVertexOrigin -> [BoundaryEdgeRef 'RightOverlayOperand]
+ Moonlight.Triangulation.Internal.Overlay.Types: [overlayOriginRightVertices] :: OverlayVertexOrigin -> [BoundaryVertexRef 'RightOverlayOperand]
+ Moonlight.Triangulation.Internal.Overlay.Types: [overlayOutputVertices] :: OverlayReceipt -> Int
+ Moonlight.Triangulation.Internal.Overlay.Types: [overlayOverlapIntervals] :: OverlayReceipt -> Int
+ Moonlight.Triangulation.Internal.Overlay.Types: [overlayRelationEvents] :: OverlayReceipt -> Int
+ Moonlight.Triangulation.Internal.Overlay.Types: [overlayResidentFaces] :: OverlayReceipt -> Int
+ Moonlight.Triangulation.Internal.Overlay.Types: [overlayResultCells] :: OverlayResult leftLabel rightLabel -> Vector (OverlayCell leftLabel rightLabel)
+ Moonlight.Triangulation.Internal.Overlay.Types: [overlayResultOutsideLabels] :: OverlayResult leftLabel rightLabel -> (leftLabel, rightLabel)
+ Moonlight.Triangulation.Internal.Overlay.Types: [overlayResultReceipt] :: OverlayResult leftLabel rightLabel -> OverlayReceipt
+ Moonlight.Triangulation.Internal.Overlay.Types: [overlayResultTriangulation] :: OverlayResult leftLabel rightLabel -> Triangulation 'Constrained OverlayVertex () OverlayEdge OverlayFace
+ Moonlight.Triangulation.Internal.Overlay.Types: [overlaySupportLeft] :: OverlayCellSupport leftLabel rightLabel -> OverlaySupport leftLabel
+ Moonlight.Triangulation.Internal.Overlay.Types: [overlaySupportRight] :: OverlayCellSupport leftLabel rightLabel -> OverlaySupport rightLabel
+ Moonlight.Triangulation.Internal.Overlay.Types: [overlaySweepMaximumHeight] :: OverlayReceipt -> Int
+ Moonlight.Triangulation.Internal.Overlay.Types: [overlayTotalRelationChecks] :: OverlayReceipt -> Int
+ Moonlight.Triangulation.Internal.Overlay.Types: [overlayVertexOrigin] :: OverlayVertex -> OverlayVertexOrigin
+ Moonlight.Triangulation.Internal.Overlay.Types: data BoundaryLoopRef
+ Moonlight.Triangulation.Internal.Overlay.Types: data BoundaryRef (feature :: BoundaryFeature) (operand :: OverlayOperand)
+ Moonlight.Triangulation.Internal.Overlay.Types: data OverlayArrangementObstruction leftLabel rightLabel
+ Moonlight.Triangulation.Internal.Overlay.Types: data OverlayCell leftLabel rightLabel
+ Moonlight.Triangulation.Internal.Overlay.Types: data OverlayCellGeometry
+ Moonlight.Triangulation.Internal.Overlay.Types: data OverlayCellSupport leftLabel rightLabel
+ Moonlight.Triangulation.Internal.Overlay.Types: data OverlayCellWitness
+ Moonlight.Triangulation.Internal.Overlay.Types: data OverlayEdge
+ Moonlight.Triangulation.Internal.Overlay.Types: data OverlayEdgeOrigin
+ Moonlight.Triangulation.Internal.Overlay.Types: data OverlayError leftLabel rightLabel
+ Moonlight.Triangulation.Internal.Overlay.Types: data OverlayFace
+ Moonlight.Triangulation.Internal.Overlay.Types: data OverlayOperand
+ Moonlight.Triangulation.Internal.Overlay.Types: data OverlayReceipt
+ Moonlight.Triangulation.Internal.Overlay.Types: data OverlayResult leftLabel rightLabel
+ Moonlight.Triangulation.Internal.Overlay.Types: data OverlaySelectionError
+ Moonlight.Triangulation.Internal.Overlay.Types: data OverlaySelectionKind
+ Moonlight.Triangulation.Internal.Overlay.Types: data OverlayVertex
+ Moonlight.Triangulation.Internal.Overlay.Types: data OverlayVertexOrigin
+ Moonlight.Triangulation.Internal.Overlay.Types: instance (Control.DeepSeq.NFData leftLabel, Control.DeepSeq.NFData rightLabel) => Control.DeepSeq.NFData (Moonlight.Triangulation.Internal.Overlay.Types.OverlayArrangementObstruction leftLabel rightLabel)
+ Moonlight.Triangulation.Internal.Overlay.Types: instance (Control.DeepSeq.NFData leftLabel, Control.DeepSeq.NFData rightLabel) => Control.DeepSeq.NFData (Moonlight.Triangulation.Internal.Overlay.Types.OverlayCell leftLabel rightLabel)
+ Moonlight.Triangulation.Internal.Overlay.Types: instance (Control.DeepSeq.NFData leftLabel, Control.DeepSeq.NFData rightLabel) => Control.DeepSeq.NFData (Moonlight.Triangulation.Internal.Overlay.Types.OverlayCellSupport leftLabel rightLabel)
+ Moonlight.Triangulation.Internal.Overlay.Types: instance (Control.DeepSeq.NFData leftLabel, Control.DeepSeq.NFData rightLabel) => Control.DeepSeq.NFData (Moonlight.Triangulation.Internal.Overlay.Types.OverlayError leftLabel rightLabel)
+ Moonlight.Triangulation.Internal.Overlay.Types: instance (Control.DeepSeq.NFData leftLabel, Control.DeepSeq.NFData rightLabel) => Control.DeepSeq.NFData (Moonlight.Triangulation.Internal.Overlay.Types.OverlayResult leftLabel rightLabel)
+ Moonlight.Triangulation.Internal.Overlay.Types: instance (GHC.Internal.Classes.Eq leftLabel, GHC.Internal.Classes.Eq rightLabel) => GHC.Internal.Classes.Eq (Moonlight.Triangulation.Internal.Overlay.Types.OverlayArrangementObstruction leftLabel rightLabel)
+ Moonlight.Triangulation.Internal.Overlay.Types: instance (GHC.Internal.Classes.Eq leftLabel, GHC.Internal.Classes.Eq rightLabel) => GHC.Internal.Classes.Eq (Moonlight.Triangulation.Internal.Overlay.Types.OverlayCell leftLabel rightLabel)
+ Moonlight.Triangulation.Internal.Overlay.Types: instance (GHC.Internal.Classes.Eq leftLabel, GHC.Internal.Classes.Eq rightLabel) => GHC.Internal.Classes.Eq (Moonlight.Triangulation.Internal.Overlay.Types.OverlayCellSupport leftLabel rightLabel)
+ Moonlight.Triangulation.Internal.Overlay.Types: instance (GHC.Internal.Classes.Eq leftLabel, GHC.Internal.Classes.Eq rightLabel) => GHC.Internal.Classes.Eq (Moonlight.Triangulation.Internal.Overlay.Types.OverlayError leftLabel rightLabel)
+ Moonlight.Triangulation.Internal.Overlay.Types: instance (GHC.Internal.Classes.Ord leftLabel, GHC.Internal.Classes.Ord rightLabel) => GHC.Internal.Classes.Ord (Moonlight.Triangulation.Internal.Overlay.Types.OverlayCell leftLabel rightLabel)
+ Moonlight.Triangulation.Internal.Overlay.Types: instance (GHC.Internal.Classes.Ord leftLabel, GHC.Internal.Classes.Ord rightLabel) => GHC.Internal.Classes.Ord (Moonlight.Triangulation.Internal.Overlay.Types.OverlayCellSupport leftLabel rightLabel)
+ Moonlight.Triangulation.Internal.Overlay.Types: instance (GHC.Internal.Show.Show leftLabel, GHC.Internal.Show.Show rightLabel) => GHC.Internal.Show.Show (Moonlight.Triangulation.Internal.Overlay.Types.OverlayArrangementObstruction leftLabel rightLabel)
+ Moonlight.Triangulation.Internal.Overlay.Types: instance (GHC.Internal.Show.Show leftLabel, GHC.Internal.Show.Show rightLabel) => GHC.Internal.Show.Show (Moonlight.Triangulation.Internal.Overlay.Types.OverlayCell leftLabel rightLabel)
+ Moonlight.Triangulation.Internal.Overlay.Types: instance (GHC.Internal.Show.Show leftLabel, GHC.Internal.Show.Show rightLabel) => GHC.Internal.Show.Show (Moonlight.Triangulation.Internal.Overlay.Types.OverlayCellSupport leftLabel rightLabel)
+ Moonlight.Triangulation.Internal.Overlay.Types: instance (GHC.Internal.Show.Show leftLabel, GHC.Internal.Show.Show rightLabel) => GHC.Internal.Show.Show (Moonlight.Triangulation.Internal.Overlay.Types.OverlayError leftLabel rightLabel)
+ Moonlight.Triangulation.Internal.Overlay.Types: instance Control.DeepSeq.NFData (Moonlight.Triangulation.Internal.Overlay.Types.BoundaryRef feature operand)
+ Moonlight.Triangulation.Internal.Overlay.Types: instance Control.DeepSeq.NFData Moonlight.Triangulation.Internal.Overlay.Types.BoundaryLoopRef
+ Moonlight.Triangulation.Internal.Overlay.Types: instance Control.DeepSeq.NFData Moonlight.Triangulation.Internal.Overlay.Types.OverlayCellGeometry
+ Moonlight.Triangulation.Internal.Overlay.Types: instance Control.DeepSeq.NFData Moonlight.Triangulation.Internal.Overlay.Types.OverlayCellId
+ Moonlight.Triangulation.Internal.Overlay.Types: instance Control.DeepSeq.NFData Moonlight.Triangulation.Internal.Overlay.Types.OverlayCellWitness
+ Moonlight.Triangulation.Internal.Overlay.Types: instance Control.DeepSeq.NFData Moonlight.Triangulation.Internal.Overlay.Types.OverlayEdge
+ Moonlight.Triangulation.Internal.Overlay.Types: instance Control.DeepSeq.NFData Moonlight.Triangulation.Internal.Overlay.Types.OverlayEdgeOrigin
+ Moonlight.Triangulation.Internal.Overlay.Types: instance Control.DeepSeq.NFData Moonlight.Triangulation.Internal.Overlay.Types.OverlayFace
+ Moonlight.Triangulation.Internal.Overlay.Types: instance Control.DeepSeq.NFData Moonlight.Triangulation.Internal.Overlay.Types.OverlayReceipt
+ Moonlight.Triangulation.Internal.Overlay.Types: instance Control.DeepSeq.NFData Moonlight.Triangulation.Internal.Overlay.Types.OverlaySelectionError
+ Moonlight.Triangulation.Internal.Overlay.Types: instance Control.DeepSeq.NFData Moonlight.Triangulation.Internal.Overlay.Types.OverlaySelectionKind
+ Moonlight.Triangulation.Internal.Overlay.Types: instance Control.DeepSeq.NFData Moonlight.Triangulation.Internal.Overlay.Types.OverlayVertex
+ Moonlight.Triangulation.Internal.Overlay.Types: instance Control.DeepSeq.NFData Moonlight.Triangulation.Internal.Overlay.Types.OverlayVertexOrigin
+ Moonlight.Triangulation.Internal.Overlay.Types: instance Control.DeepSeq.NFData label => Control.DeepSeq.NFData (Moonlight.Triangulation.Internal.Overlay.Types.OverlaySupport label)
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Classes.Eq (Moonlight.Triangulation.Internal.Overlay.Types.BoundaryRef feature operand)
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Classes.Eq Moonlight.Triangulation.Internal.Overlay.Types.BoundaryLoopRef
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Classes.Eq Moonlight.Triangulation.Internal.Overlay.Types.OverlayCellGeometry
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Classes.Eq Moonlight.Triangulation.Internal.Overlay.Types.OverlayCellId
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Classes.Eq Moonlight.Triangulation.Internal.Overlay.Types.OverlayCellWitness
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Classes.Eq Moonlight.Triangulation.Internal.Overlay.Types.OverlayEdge
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Classes.Eq Moonlight.Triangulation.Internal.Overlay.Types.OverlayEdgeOrigin
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Classes.Eq Moonlight.Triangulation.Internal.Overlay.Types.OverlayFace
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Classes.Eq Moonlight.Triangulation.Internal.Overlay.Types.OverlayReceipt
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Classes.Eq Moonlight.Triangulation.Internal.Overlay.Types.OverlaySelectionError
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Classes.Eq Moonlight.Triangulation.Internal.Overlay.Types.OverlaySelectionKind
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Classes.Eq Moonlight.Triangulation.Internal.Overlay.Types.OverlayVertex
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Classes.Eq Moonlight.Triangulation.Internal.Overlay.Types.OverlayVertexOrigin
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Classes.Eq label => GHC.Internal.Classes.Eq (Moonlight.Triangulation.Internal.Overlay.Types.OverlaySupport label)
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Classes.Ord (Moonlight.Triangulation.Internal.Overlay.Types.BoundaryRef feature operand)
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Classes.Ord Moonlight.Triangulation.Internal.Overlay.Types.BoundaryLoopRef
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Classes.Ord Moonlight.Triangulation.Internal.Overlay.Types.OverlayCellGeometry
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Classes.Ord Moonlight.Triangulation.Internal.Overlay.Types.OverlayCellId
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Classes.Ord Moonlight.Triangulation.Internal.Overlay.Types.OverlayEdge
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Classes.Ord Moonlight.Triangulation.Internal.Overlay.Types.OverlayEdgeOrigin
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Classes.Ord Moonlight.Triangulation.Internal.Overlay.Types.OverlayFace
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Classes.Ord Moonlight.Triangulation.Internal.Overlay.Types.OverlayReceipt
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Classes.Ord Moonlight.Triangulation.Internal.Overlay.Types.OverlaySelectionKind
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Classes.Ord Moonlight.Triangulation.Internal.Overlay.Types.OverlayVertex
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Classes.Ord Moonlight.Triangulation.Internal.Overlay.Types.OverlayVertexOrigin
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Classes.Ord label => GHC.Internal.Classes.Ord (Moonlight.Triangulation.Internal.Overlay.Types.OverlaySupport label)
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Generics.Generic (Moonlight.Triangulation.Internal.Overlay.Types.BoundaryRef feature operand)
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Generics.Generic (Moonlight.Triangulation.Internal.Overlay.Types.OverlayArrangementObstruction leftLabel rightLabel)
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Generics.Generic (Moonlight.Triangulation.Internal.Overlay.Types.OverlayCell leftLabel rightLabel)
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Generics.Generic (Moonlight.Triangulation.Internal.Overlay.Types.OverlayCellSupport leftLabel rightLabel)
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Generics.Generic (Moonlight.Triangulation.Internal.Overlay.Types.OverlayError leftLabel rightLabel)
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Generics.Generic (Moonlight.Triangulation.Internal.Overlay.Types.OverlayResult leftLabel rightLabel)
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Generics.Generic (Moonlight.Triangulation.Internal.Overlay.Types.OverlaySupport label)
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Generics.Generic Moonlight.Triangulation.Internal.Overlay.Types.BoundaryLoopRef
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Generics.Generic Moonlight.Triangulation.Internal.Overlay.Types.OverlayCellGeometry
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Generics.Generic Moonlight.Triangulation.Internal.Overlay.Types.OverlayCellId
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Generics.Generic Moonlight.Triangulation.Internal.Overlay.Types.OverlayCellWitness
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Generics.Generic Moonlight.Triangulation.Internal.Overlay.Types.OverlayEdge
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Generics.Generic Moonlight.Triangulation.Internal.Overlay.Types.OverlayEdgeOrigin
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Generics.Generic Moonlight.Triangulation.Internal.Overlay.Types.OverlayFace
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Generics.Generic Moonlight.Triangulation.Internal.Overlay.Types.OverlayReceipt
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Generics.Generic Moonlight.Triangulation.Internal.Overlay.Types.OverlaySelectionError
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Generics.Generic Moonlight.Triangulation.Internal.Overlay.Types.OverlaySelectionKind
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Generics.Generic Moonlight.Triangulation.Internal.Overlay.Types.OverlayVertex
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Generics.Generic Moonlight.Triangulation.Internal.Overlay.Types.OverlayVertexOrigin
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Show.Show (Moonlight.Triangulation.Internal.Overlay.Types.BoundaryRef feature operand)
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Show.Show Moonlight.Triangulation.Internal.Overlay.Types.BoundaryLoopRef
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Show.Show Moonlight.Triangulation.Internal.Overlay.Types.OverlayCellGeometry
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Show.Show Moonlight.Triangulation.Internal.Overlay.Types.OverlayCellId
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Show.Show Moonlight.Triangulation.Internal.Overlay.Types.OverlayCellWitness
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Show.Show Moonlight.Triangulation.Internal.Overlay.Types.OverlayEdge
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Show.Show Moonlight.Triangulation.Internal.Overlay.Types.OverlayEdgeOrigin
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Show.Show Moonlight.Triangulation.Internal.Overlay.Types.OverlayFace
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Show.Show Moonlight.Triangulation.Internal.Overlay.Types.OverlayReceipt
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Show.Show Moonlight.Triangulation.Internal.Overlay.Types.OverlaySelectionError
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Show.Show Moonlight.Triangulation.Internal.Overlay.Types.OverlaySelectionKind
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Show.Show Moonlight.Triangulation.Internal.Overlay.Types.OverlayVertex
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Show.Show Moonlight.Triangulation.Internal.Overlay.Types.OverlayVertexOrigin
+ Moonlight.Triangulation.Internal.Overlay.Types: instance GHC.Internal.Show.Show label => GHC.Internal.Show.Show (Moonlight.Triangulation.Internal.Overlay.Types.OverlaySupport label)
+ Moonlight.Triangulation.Internal.Overlay.Types: newtype OverlayCellId
+ Moonlight.Triangulation.Internal.Overlay.Types: newtype OverlaySupport label
+ Moonlight.Triangulation.Internal.Overlay.Types: overlaySupportLabels :: OverlaySupport label -> NonEmpty label
+ Moonlight.Triangulation.Internal.Overlay.Types: type BoundaryEdgeRef (operand :: OverlayOperand) = BoundaryRef 'BoundaryEdgeFeature operand
+ Moonlight.Triangulation.Internal.Overlay.Types: type BoundaryVertexRef (operand :: OverlayOperand) = BoundaryRef 'BoundaryVertexFeature operand
+ Moonlight.Triangulation.Minkowski: MinkowskiAddition :: MinkowskiOperation
+ Moonlight.Triangulation.Minkowski: MinkowskiCandidateCellMissing :: OverlayCellId -> MinkowskiError
+ Moonlight.Triangulation.Minkowski: MinkowskiClosing :: MinkowskiOperation
+ Moonlight.Triangulation.Minkowski: MinkowskiConvexHullDegenerate :: [ExactPoint] -> MinkowskiError
+ Moonlight.Triangulation.Minkowski: MinkowskiErosion :: MinkowskiOperation
+ Moonlight.Triangulation.Minkowski: MinkowskiExactArithmetic :: ExactArithmeticError -> MinkowskiError
+ Moonlight.Triangulation.Minkowski: MinkowskiFaceArity :: FaceId -> Int -> MinkowskiError
+ Moonlight.Triangulation.Minkowski: MinkowskiInclusionAmbiguous :: OverlayCellId -> ExactPoint -> MinkowskiError
+ Moonlight.Triangulation.Minkowski: MinkowskiInvalidConvexLoop :: RegionValidationError -> MinkowskiError
+ Moonlight.Triangulation.Minkowski: MinkowskiInvalidSegment :: ExactGeometryError -> MinkowskiError
+ Moonlight.Triangulation.Minkowski: MinkowskiLineIntersection :: ExactIntersectionError -> MinkowskiError
+ Moonlight.Triangulation.Minkowski: MinkowskiNonConvexTurn :: Int -> Ordering -> MinkowskiError
+ Moonlight.Triangulation.Minkowski: MinkowskiOpening :: MinkowskiOperation
+ Moonlight.Triangulation.Minkowski: MinkowskiOriginOutside :: RegionPointLocation -> MinkowskiError
+ Moonlight.Triangulation.Minkowski: MinkowskiOverlayCellWitness :: OverlayCellWitness -> MinkowskiError
+ Moonlight.Triangulation.Minkowski: MinkowskiOverlayFailed :: OverlayError Bool Bool -> MinkowskiError
+ Moonlight.Triangulation.Minkowski: MinkowskiPublicationFailed :: RegionPublicationError -> MinkowskiError
+ Moonlight.Triangulation.Minkowski: MinkowskiReceipt :: MinkowskiOperation -> Int -> Int -> Int -> Int -> Int -> Int -> Int -> Int -> MinkowskiReceipt
+ Moonlight.Triangulation.Minkowski: [minkowskiConvexPieces] :: MinkowskiReceipt -> Int
+ Moonlight.Triangulation.Minkowski: [minkowskiExactCoordinateBitGrowth] :: MinkowskiReceipt -> Int
+ Moonlight.Triangulation.Minkowski: [minkowskiExactCrossings] :: MinkowskiReceipt -> Int
+ Moonlight.Triangulation.Minkowski: [minkowskiGeneratedConvolutionEdges] :: MinkowskiReceipt -> Int
+ Moonlight.Triangulation.Minkowski: [minkowskiGeneratedPieces] :: MinkowskiReceipt -> Int
+ Moonlight.Triangulation.Minkowski: [minkowskiInputComponents] :: MinkowskiReceipt -> Int
+ Moonlight.Triangulation.Minkowski: [minkowskiOperation] :: MinkowskiReceipt -> MinkowskiOperation
+ Moonlight.Triangulation.Minkowski: [minkowskiOutputCells] :: MinkowskiReceipt -> Int
+ Moonlight.Triangulation.Minkowski: [minkowskiOverlayPasses] :: MinkowskiReceipt -> Int
+ Moonlight.Triangulation.Minkowski: closeWith :: StructuringElement -> PlanarRegion -> Either MinkowskiError (PlanarRegion, MinkowskiReceipt)
+ Moonlight.Triangulation.Minkowski: convexMinkowskiSum :: ConvexPolygon -> ConvexPolygon -> PlanarRegion
+ Moonlight.Triangulation.Minkowski: convexPolygon :: NonEmpty ExactPoint -> Either MinkowskiError ConvexPolygon
+ Moonlight.Triangulation.Minkowski: convexPolygonPoints :: ConvexPolygon -> NonEmpty ExactPoint
+ Moonlight.Triangulation.Minkowski: data ConvexPolygon
+ Moonlight.Triangulation.Minkowski: data MinkowskiError
+ Moonlight.Triangulation.Minkowski: data MinkowskiOperation
+ Moonlight.Triangulation.Minkowski: data MinkowskiReceipt
+ Moonlight.Triangulation.Minkowski: data StructuringElement
+ Moonlight.Triangulation.Minkowski: erodeBy :: StructuringElement -> PlanarRegion -> Either MinkowskiError (PlanarRegion, MinkowskiReceipt)
+ Moonlight.Triangulation.Minkowski: minkowskiSum :: PlanarRegion -> PlanarRegion -> Either MinkowskiError (PlanarRegion, MinkowskiReceipt)
+ Moonlight.Triangulation.Minkowski: openWith :: StructuringElement -> PlanarRegion -> Either MinkowskiError (PlanarRegion, MinkowskiReceipt)
+ Moonlight.Triangulation.Minkowski: polygonInset :: StructuringElement -> PlanarRegion -> Either MinkowskiError (PlanarRegion, MinkowskiReceipt)
+ Moonlight.Triangulation.Minkowski: polygonOffset :: StructuringElement -> PlanarRegion -> Either MinkowskiError (PlanarRegion, MinkowskiReceipt)
+ Moonlight.Triangulation.Minkowski: structuringElement :: ConvexPolygon -> Either MinkowskiError StructuringElement
+ Moonlight.Triangulation.Overlay: BoundaryHoleLoop :: Int -> BoundaryLoopRef
+ Moonlight.Triangulation.Overlay: BoundaryOuterLoop :: BoundaryLoopRef
+ Moonlight.Triangulation.Overlay: BoundedOverlayCell :: PolygonComponent -> OverlayCellGeometry
+ Moonlight.Triangulation.Overlay: ClosedIntersectionSelection :: OverlaySelectionKind
+ Moonlight.Triangulation.Overlay: ClosedUnionSelection :: OverlaySelectionKind
+ Moonlight.Triangulation.Overlay: OverlayArrangementInvalid :: OverlayArrangementObstruction leftLabel rightLabel -> OverlayError leftLabel rightLabel
+ Moonlight.Triangulation.Overlay: OverlayAtomicConstraintMissing :: ExactPoint -> ExactPoint -> OverlayCellWitness
+ Moonlight.Triangulation.Overlay: OverlayAtomicConstraintOrientationMismatch :: UndirectedEdgeId -> ExactPoint -> ExactPoint -> OverlayCellWitness
+ Moonlight.Triangulation.Overlay: OverlayBoundary :: OverlayEdgeOrigin -> OverlayEdge
+ Moonlight.Triangulation.Overlay: OverlayBoundaryEdgeNotConstrained :: UndirectedEdgeId -> OverlayCellWitness
+ Moonlight.Triangulation.Overlay: OverlayBuildFailed :: CdtError -> OverlayError leftLabel rightLabel
+ Moonlight.Triangulation.Overlay: OverlayCell :: leftLabel -> rightLabel -> OverlayCellGeometry -> OverlayCell leftLabel rightLabel
+ Moonlight.Triangulation.Overlay: OverlayCellCycleDidNotClose :: ExactPoint -> ExactPoint -> OverlayArrangementObstruction leftLabel rightLabel
+ Moonlight.Triangulation.Overlay: OverlayCellFace :: OverlayCellId -> OverlayFace
+ Moonlight.Triangulation.Overlay: OverlayCellId :: Int -> OverlayCellId
+ Moonlight.Triangulation.Overlay: OverlayCellPayloadMissing :: OverlayCellId -> OverlayCellWitness
+ Moonlight.Triangulation.Overlay: OverlayCellSupport :: OverlaySupport leftLabel -> OverlaySupport rightLabel -> OverlayCellSupport leftLabel rightLabel
+ Moonlight.Triangulation.Overlay: OverlayCollapsedFace :: OverlayCellId -> OverlayFace
+ Moonlight.Triangulation.Overlay: OverlayCollapsedFacesAmbiguous :: NonEmpty FaceId -> NonEmpty OverlayCellId -> OverlayArrangementObstruction leftLabel rightLabel
+ Moonlight.Triangulation.Overlay: OverlayCollapsedFacesUnowned :: NonEmpty FaceId -> OverlayArrangementObstruction leftLabel rightLabel
+ Moonlight.Triangulation.Overlay: OverlayDiagonal :: OverlayEdge
+ Moonlight.Triangulation.Overlay: OverlayDuplicateCellSignature :: PolygonComponent -> OverlayArrangementObstruction leftLabel rightLabel
+ Moonlight.Triangulation.Overlay: OverlayEdgeSupportMissing :: UndirectedEdgeId -> OverlayCellWitness
+ Moonlight.Triangulation.Overlay: OverlayEmbeddedVertexCountMismatch :: Int -> Int -> OverlayCellWitness
+ Moonlight.Triangulation.Overlay: OverlayEmbeddingRefused :: NonEmpty OverlayEmbeddingObstruction -> OverlayError leftLabel rightLabel
+ Moonlight.Triangulation.Overlay: OverlayExactArithmetic :: ExactArithmeticError -> OverlayError leftLabel rightLabel
+ Moonlight.Triangulation.Overlay: OverlayExactVertexMissing :: ExactPoint -> OverlayCellWitness
+ Moonlight.Triangulation.Overlay: OverlayFaceComponentEmpty :: OverlayArrangementObstruction leftLabel rightLabel
+ Moonlight.Triangulation.Overlay: OverlayLeftSourceSideConflict :: OverlayEdgeOrigin -> NonEmpty leftLabel -> OverlayArrangementObstruction leftLabel rightLabel
+ Moonlight.Triangulation.Overlay: OverlayProvenanceIncomplete :: OverlayCellWitness -> OverlayError leftLabel rightLabel
+ Moonlight.Triangulation.Overlay: OverlayReceipt :: Int -> Int -> Int -> Int -> Int -> Int -> Int -> Int -> Int -> Int -> Int -> OverlayReceipt
+ Moonlight.Triangulation.Overlay: OverlayRegionPublicationFailed :: RegionPublicationError -> OverlayError leftLabel rightLabel
+ Moonlight.Triangulation.Overlay: OverlayResidentFaceArity :: FaceId -> Int -> OverlayArrangementObstruction leftLabel rightLabel
+ Moonlight.Triangulation.Overlay: OverlayResidentFaceLabelConflict :: FaceId -> UndirectedEdgeId -> (leftLabel, rightLabel) -> (leftLabel, rightLabel) -> OverlayArrangementObstruction leftLabel rightLabel
+ Moonlight.Triangulation.Overlay: OverlayResidentFaceOrientationReversed :: FaceId -> OverlayArrangementObstruction leftLabel rightLabel
+ Moonlight.Triangulation.Overlay: OverlayResidentFaceUnassigned :: FaceId -> OverlayCellWitness
+ Moonlight.Triangulation.Overlay: OverlayRightSourceSideConflict :: OverlayEdgeOrigin -> NonEmpty rightLabel -> OverlayArrangementObstruction leftLabel rightLabel
+ Moonlight.Triangulation.Overlay: OverlayRotationDegenerate :: ExactPoint -> OverlayArrangementObstruction leftLabel rightLabel
+ Moonlight.Triangulation.Overlay: OverlaySegmentEventsInvalid :: ExactSegmentEventObstruction -> OverlayError leftLabel rightLabel
+ Moonlight.Triangulation.Overlay: OverlaySelectionContainsUnboundedCell :: OverlaySelectionKind -> OverlaySelectionError
+ Moonlight.Triangulation.Overlay: OverlaySelectionInvalid :: CellSelectionError -> OverlaySelectionError
+ Moonlight.Triangulation.Overlay: OverlaySelectionProvenance :: OverlayCellWitness -> OverlaySelectionError
+ Moonlight.Triangulation.Overlay: OverlayTransitionSourceMismatch :: FaceId -> UndirectedEdgeId -> (leftLabel, rightLabel) -> (leftLabel, rightLabel) -> OverlayArrangementObstruction leftLabel rightLabel
+ Moonlight.Triangulation.Overlay: OverlayUnexpectedConstraint :: UndirectedEdgeId -> OverlayCellWitness
+ Moonlight.Triangulation.Overlay: OverlayVertex :: ExactPoint -> OverlayVertexOrigin -> OverlayVertex
+ Moonlight.Triangulation.Overlay: OverlayVertexSupportMissing :: VertexId -> OverlayCellWitness
+ Moonlight.Triangulation.Overlay: RegularizedDifferenceSelection :: OverlaySelectionKind
+ Moonlight.Triangulation.Overlay: UnboundedOverlayCell :: [ExactLoop] -> OverlayCellGeometry
+ Moonlight.Triangulation.Overlay: [overlayArrangementCells] :: OverlayReceipt -> Int
+ Moonlight.Triangulation.Overlay: [overlayAtomicEdges] :: OverlayReceipt -> Int
+ Moonlight.Triangulation.Overlay: [overlayCellGeometry] :: OverlayCell leftLabel rightLabel -> OverlayCellGeometry
+ Moonlight.Triangulation.Overlay: [overlayCellLeft] :: OverlayCell leftLabel rightLabel -> leftLabel
+ Moonlight.Triangulation.Overlay: [overlayCellRight] :: OverlayCell leftLabel rightLabel -> rightLabel
+ Moonlight.Triangulation.Overlay: [overlayEmbeddingCandidates] :: OverlayReceipt -> Int
+ Moonlight.Triangulation.Overlay: [overlayExactCrossings] :: OverlayReceipt -> Int
+ Moonlight.Triangulation.Overlay: [overlayExactPoint] :: OverlayVertex -> ExactPoint
+ Moonlight.Triangulation.Overlay: [overlayFaceCellId] :: OverlayFace -> OverlayCellId
+ Moonlight.Triangulation.Overlay: [overlayInputSegments] :: OverlayReceipt -> Int
+ Moonlight.Triangulation.Overlay: [overlayOutputVertices] :: OverlayReceipt -> Int
+ Moonlight.Triangulation.Overlay: [overlayOverlapIntervals] :: OverlayReceipt -> Int
+ Moonlight.Triangulation.Overlay: [overlayRelationEvents] :: OverlayReceipt -> Int
+ Moonlight.Triangulation.Overlay: [overlayResidentFaces] :: OverlayReceipt -> Int
+ Moonlight.Triangulation.Overlay: [overlaySupportLeft] :: OverlayCellSupport leftLabel rightLabel -> OverlaySupport leftLabel
+ Moonlight.Triangulation.Overlay: [overlaySupportRight] :: OverlayCellSupport leftLabel rightLabel -> OverlaySupport rightLabel
+ Moonlight.Triangulation.Overlay: [overlaySweepMaximumHeight] :: OverlayReceipt -> Int
+ Moonlight.Triangulation.Overlay: [overlayTotalRelationChecks] :: OverlayReceipt -> Int
+ Moonlight.Triangulation.Overlay: [overlayVertexOrigin] :: OverlayVertex -> OverlayVertexOrigin
+ Moonlight.Triangulation.Overlay: boundaryRefComponent :: BoundaryRef feature operand -> Int
+ Moonlight.Triangulation.Overlay: boundaryRefLocalIndex :: BoundaryRef feature operand -> Int
+ Moonlight.Triangulation.Overlay: boundaryRefLoop :: BoundaryRef feature operand -> BoundaryLoopRef
+ Moonlight.Triangulation.Overlay: data BoundaryLoopRef
+ Moonlight.Triangulation.Overlay: data BoundaryRef (feature :: BoundaryFeature) (operand :: OverlayOperand)
+ Moonlight.Triangulation.Overlay: data OverlayArrangementObstruction leftLabel rightLabel
+ Moonlight.Triangulation.Overlay: data OverlayCell leftLabel rightLabel
+ Moonlight.Triangulation.Overlay: data OverlayCellGeometry
+ Moonlight.Triangulation.Overlay: data OverlayCellSupport leftLabel rightLabel
+ Moonlight.Triangulation.Overlay: data OverlayCellWitness
+ Moonlight.Triangulation.Overlay: data OverlayEdge
+ Moonlight.Triangulation.Overlay: data OverlayEdgeOrigin
+ Moonlight.Triangulation.Overlay: data OverlayError leftLabel rightLabel
+ Moonlight.Triangulation.Overlay: data OverlayFace
+ Moonlight.Triangulation.Overlay: data OverlayOperand
+ Moonlight.Triangulation.Overlay: data OverlayReceipt
+ Moonlight.Triangulation.Overlay: data OverlayResult leftLabel rightLabel
+ Moonlight.Triangulation.Overlay: data OverlaySelectionError
+ Moonlight.Triangulation.Overlay: data OverlaySelectionKind
+ Moonlight.Triangulation.Overlay: data OverlaySupport label
+ Moonlight.Triangulation.Overlay: data OverlayVertex
+ Moonlight.Triangulation.Overlay: data OverlayVertexOrigin
+ Moonlight.Triangulation.Overlay: newtype OverlayCellId
+ Moonlight.Triangulation.Overlay: overlayArrangementEdges :: OverlayResult leftLabel rightLabel -> [(UndirectedEdgeId, OverlayEdgeOrigin)]
+ Moonlight.Triangulation.Overlay: overlayArrangementVertices :: OverlayResult leftLabel rightLabel -> [(VertexId, OverlayVertex)]
+ Moonlight.Triangulation.Overlay: overlayCells :: OverlayResult leftLabel rightLabel -> [(OverlayCellId, OverlayCell leftLabel rightLabel)]
+ Moonlight.Triangulation.Overlay: overlayClosedIntersection :: (Ord leftLabel, Ord rightLabel) => (leftLabel -> Bool) -> (rightLabel -> Bool) -> OverlayResult leftLabel rightLabel -> Either OverlaySelectionError ExactCellSet
+ Moonlight.Triangulation.Overlay: overlayClosedUnion :: (Ord leftLabel, Ord rightLabel) => (leftLabel -> Bool) -> (rightLabel -> Bool) -> OverlayResult leftLabel rightLabel -> Either OverlaySelectionError ExactCellSet
+ Moonlight.Triangulation.Overlay: overlayEdgeLeftSources :: OverlayEdgeOrigin -> [BoundaryEdgeRef 'LeftOverlayOperand]
+ Moonlight.Triangulation.Overlay: overlayEdgeRightSources :: OverlayEdgeOrigin -> [BoundaryEdgeRef 'RightOverlayOperand]
+ Moonlight.Triangulation.Overlay: overlayEmbeddedTriangulation :: OverlayResult leftLabel rightLabel -> Triangulation 'Constrained OverlayVertex () OverlayEdge OverlayFace
+ Moonlight.Triangulation.Overlay: overlayLayers :: (Ord leftLabel, Ord rightLabel) => PlanarLayer leftLabel -> PlanarLayer rightLabel -> Either (OverlayError leftLabel rightLabel) (OverlayResult leftLabel rightLabel)
+ Moonlight.Triangulation.Overlay: overlayOriginLeftEdges :: OverlayVertexOrigin -> [BoundaryEdgeRef 'LeftOverlayOperand]
+ Moonlight.Triangulation.Overlay: overlayOriginLeftVertices :: OverlayVertexOrigin -> [BoundaryVertexRef 'LeftOverlayOperand]
+ Moonlight.Triangulation.Overlay: overlayOriginRightEdges :: OverlayVertexOrigin -> [BoundaryEdgeRef 'RightOverlayOperand]
+ Moonlight.Triangulation.Overlay: overlayOriginRightVertices :: OverlayVertexOrigin -> [BoundaryVertexRef 'RightOverlayOperand]
+ Moonlight.Triangulation.Overlay: overlayPlanarLayer :: (Ord leftLabel, Ord rightLabel) => OverlayResult leftLabel rightLabel -> PlanarLayer (leftLabel, rightLabel)
+ Moonlight.Triangulation.Overlay: overlayReceipt :: OverlayResult leftLabel rightLabel -> OverlayReceipt
+ Moonlight.Triangulation.Overlay: overlayRegularizedDifference :: (leftLabel -> Bool) -> (rightLabel -> Bool) -> OverlayResult leftLabel rightLabel -> Either OverlaySelectionError ExactCellSet
+ Moonlight.Triangulation.Overlay: overlaySelectedRegion :: ((leftLabel, rightLabel) -> Bool) -> OverlayResult leftLabel rightLabel -> Either RegionPublicationError PlanarRegion
+ Moonlight.Triangulation.Overlay: overlaySupportLabels :: OverlaySupport label -> NonEmpty label
+ Moonlight.Triangulation.Overlay: type BoundaryEdgeRef (operand :: OverlayOperand) = BoundaryRef 'BoundaryEdgeFeature operand
+ Moonlight.Triangulation.Overlay: type BoundaryVertexRef (operand :: OverlayOperand) = BoundaryRef 'BoundaryVertexFeature operand
Files
- CHANGELOG.md +56/−0
- README.md +122/−23
- app/category-observatory/Main.hs +562/−0
- bench/build/Moonlight/Triangulation/BuildBench.hs +38/−6
- bench/delaunay-compare/Main.hs +27/−0
- bench/delaunay-compare/Moonlight/Triangulation/Bench/DelaunayCompare/Domain.hs +244/−0
- bench/delaunay-compare/Moonlight/Triangulation/Bench/DelaunayCompare/Native.hs +307/−0
- bench/delaunay-compare/Moonlight/Triangulation/Bench/DelaunayCompare/Picture.hs +526/−0
- bench/delaunay-compare/Moonlight/Triangulation/Bench/DelaunayCompare/Suite.hs +260/−0
- bench/delaunay-compare/PictureMain.hs +75/−0
- bench/delaunay-compare/README.md +183/−0
- bench/delaunay-compare/rust/Cargo.lock +264/−0
- bench/delaunay-compare/rust/Cargo.toml +20/−0
- bench/delaunay-compare/rust/src/lib.rs +397/−0
- bench/support/BenchSupport.hs +18/−8
- bindings/README.md +62/−26
- bindings/python/pyproject.toml +2/−2
- bindings/python/src/moonlight_triangulation/__init__.py +457/−80
- bindings/python/tests/test_binding.py +41/−6
- bindings/rust/Cargo.toml +2/−2
- bindings/rust/src/lib.rs +628/−35
- bindings/rust/tests/binding.rs +74/−7
- bindings/typescript/package.json +2/−2
- bindings/typescript/src/index.ts +527/−74
- bindings/typescript/test/binding.test.ts +57/−21
- cbits/moonlight-triangulation.def +21/−4
- cbits/moonlight_runtime.c +1/−1
- include/moonlight_triangulation.h +100/−6
- moonlight-triangulation.cabal +157/−32
- src-build/Moonlight/Triangulation/BulkLoad.hs +43/−55
- src-build/Moonlight/Triangulation/Cdt.hs +6/−4
- src-build/Moonlight/Triangulation/Insertion.hs +12/−1
- src-build/Moonlight/Triangulation/Internal/Cdt/Batch.hs +1/−0
- src-build/Moonlight/Triangulation/Internal/Cdt/Build.hs +9/−12
- src-build/Moonlight/Triangulation/Internal/Cdt/Combinators.hs +1/−5
- src-build/Moonlight/Triangulation/Internal/Cdt/Recovery.hs +3/−1
- src-build/Moonlight/Triangulation/Internal/Cdt/Segment.hs +49/−72
- src-build/Moonlight/Triangulation/Internal/Cdt/Split.hs +54/−65
- src-build/Moonlight/Triangulation/Internal/Cdt/Types.hs +27/−52
- src-build/Moonlight/Triangulation/Internal/Cdt/Union.hs +13/−13
- src-build/Moonlight/Triangulation/Internal/CircleSweep.hs +31/−41
- src-build/Moonlight/Triangulation/Internal/Excision.hs +5/−1
- src-build/Moonlight/Triangulation/Internal/Join/Seam.hs +3/−2
- src-build/Moonlight/Triangulation/Internal/Location.hs +2/−6
- src-build/Moonlight/Triangulation/Internal/Minkowski/Convex.hs +0/−326
- src-build/Moonlight/Triangulation/Internal/Minkowski/Types.hs +0/−80
- src-build/Moonlight/Triangulation/Internal/Overlay/Arrangement.hs +0/−807
- src-build/Moonlight/Triangulation/Internal/Overlay/Resident.hs +0/−869
- src-build/Moonlight/Triangulation/Internal/Overlay/Types.hs +0/−267
- src-build/Moonlight/Triangulation/Internal/Refinement.hs +3/−6
- src-build/Moonlight/Triangulation/Internal/Transaction.hs +44/−5
- src-build/Moonlight/Triangulation/Minkowski.hs +0/−584
- src-build/Moonlight/Triangulation/Overlay.hs +0/−291
- src-build/Moonlight/Triangulation/Refinement.hs +26/−41
- src-capi/Moonlight/Triangulation/Foreign/Exports.hs +58/−17
- src-cell-complex/Moonlight/Triangulation/CellComplex.hs +100/−0
- src-core/Moonlight/Triangulation/Internal/ExactRational.hs +1/−1
- src-core/Moonlight/Triangulation/Scalar.hs +3/−1
- src-dcel/Moonlight/Triangulation/FloodFillIterator.hs +203/−52
- src-dcel/Moonlight/Triangulation/Handles.hs +0/−11
- src-dcel/Moonlight/Triangulation/Handles/Iterators.hs +0/−13
- src-dcel/Moonlight/Triangulation/Internal/BoundaryCycle.hs +123/−5
- src-dcel/Moonlight/Triangulation/Internal/DcelOperations.hs +0/−83
- src-dcel/Moonlight/Triangulation/Internal/DcelOperations/CandidateArena.hs +1/−4
- src-dcel/Moonlight/Triangulation/Internal/DcelOperations/FlipRule.hs +1/−7
- src-dcel/Moonlight/Triangulation/Internal/DcelOperations/Hull.hs +1/−1
- src-dcel/Moonlight/Triangulation/Internal/DcelOperations/Legalize.hs +2/−5
- src-dcel/Moonlight/Triangulation/Internal/DcelOperations/Normalize.hs +2/−5
- src-dcel/Moonlight/Triangulation/Internal/Mutable.hs +5/−0
- src-dcel/Moonlight/Triangulation/Internal/OperationState.hs +5/−0
- src-dcel/Moonlight/Triangulation/Validation.hs +1/−3
- src-dcel/Moonlight/Triangulation/Valuation.hs +7/−4
- src-dual/Moonlight/Triangulation/HintGenerator.hs +0/−492
- src-embedding/Moonlight/Triangulation/Internal/Overlay/Embedding.hs +0/−502
- src-ffi/Moonlight/Triangulation/Foreign/ABI.hs +762/−105
- src-planar/Moonlight/Triangulation/Internal/Minkowski/Convex.hs +326/−0
- src-planar/Moonlight/Triangulation/Internal/Minkowski/Types.hs +80/−0
- src-planar/Moonlight/Triangulation/Internal/Overlay/Arrangement.hs +814/−0
- src-planar/Moonlight/Triangulation/Internal/Overlay/Embedding.hs +533/−0
- src-planar/Moonlight/Triangulation/Internal/Overlay/Resident.hs +923/−0
- src-planar/Moonlight/Triangulation/Internal/Overlay/Types.hs +274/−0
- src-planar/Moonlight/Triangulation/Minkowski.hs +595/−0
- src-planar/Moonlight/Triangulation/Overlay.hs +296/−0
- src-public/Moonlight/Triangulation.hs +18/−6
- src-public/Moonlight/Triangulation/HintGenerator.hs +492/−0
- test/algebra/Moonlight/Triangulation/MinkowskiSpec.hs +58/−0
- test/cell-complex/Main.hs +95/−0
- test/ffi/Main.hs +227/−24
- test/native/Moonlight/Triangulation/ExactEmbeddingSpec.hs +85/−33
- test/native/Moonlight/Triangulation/NativeSpec.hs +76/−21
- weeder.toml +1/−2
CHANGELOG.md view
@@ -6,6 +6,62 @@ The serialization format carries its own version tag, independent of the package version; any change to it is recorded here explicitly. +## 1.3.0.0++* Add the public GHC-9.14 `cell-complex` component. It interprets an admitted+ `ExactCellSet` as Homology's generic `CellComplex2D` without copying the mesh+ or introducing a second cell inventory, and the accompanying observatory+ executable derives its incidence category and normalized flag nerve.+* Keep the LLVM-optimized hot modules buildable with GHC 9.8 by restoring the+ legacy LLVM pass manager that its supported LLVM 15 toolchain requires.+* Withdraw `constrainedExtensionTriangulation`,+ `constrainedExtensionConstraintOutcomes` and+ `constrainedExtensionConstraintStats`. A constrained extension now publishes+ one `ConstraintBatchResult` through `constrainedExtensionConstraintBatch`,+ read with `constraintBatchTriangulation`, `constraintBatchOutcomes` and+ `constraintBatchStats`; `constrainedExtensionBuildStats` is unchanged. The+ batch is the same value `recoverConstraints` returns, so extension and+ standalone recovery are now read through one accessor set instead of two.+* Withdraw the aggregator modules `Moonlight.Triangulation.Handles`,+ `Moonlight.Triangulation.Handles.Iterators` and+ `Moonlight.Triangulation.Internal.DcelOperations` from the exposed module+ list. Every leaf they re-exported remains exposed under its own name; import+ the leaf.+* Add `BoundaryOrientation`, `boundaryLoopOrientation` and+ `componentBoundaryLoops`, so a face component publishes all of its boundary+ loops with each loop's orientation rather than only the outer one.+* Add `planarValuationsPerimeter`, the certified perimeter of a planar region+ taken directly from its valuations.+* Advance the C boundary to ABI version 2 with opaque exact-region and reusable+ structuring-element handles, one-call bulk authoring/projection, exact region+ union/intersection/difference/symmetric difference, point location, exact+ area plus Euler and certified perimeter observations, and existing polygonal+ Minkowski morphology with fixed-width receipts.+* Rename the former mesh Boolean symbols and Python, TypeScript, and Rust+ methods to `site_*`; they combine Delaunay site sets and are deliberately not+ compatibility aliases for the new polygonal region operations.+* Restore `-O0` for ordinary test bodies while retaining the filtered-predicate+ allocation witness at module-local `-O1`.+* Move the existing exact Overlay/Minkowski implementations from the `build`+ sublibrary into the main library and move `HintGenerator` from `dual` into+ that same apex owner. The `dual` sublibrary is now dcel-only and can compile+ concurrently with `build`; direct `:dual` consumers of `HintGenerator` must+ depend on the main library instead. No geometry type or runtime operation is+ duplicated.+* Reserve the planar DCEL bound rather than the general one when joining two+ separated triangulations. The separated seam copies two planar sources and+ then only adds, so its peak is its published result; the general reservation+ was a third again as much arena as the merge can ever reach. Measured at+ twenty thousand sites over twenty-one processes per arm, the separated join+ lanes fall 36.4% to 41.3% in allocation and 10.2% to 22.0% in elapsed time,+ with every unaffected lane byte-identical.+* Stop materializing an intermediate validated vector in `mesh_insert_many_f64`.+ Admission is unchanged — a malformed point anywhere still refuses the whole+ batch before any insertion — but the canonical coordinate is now applied+ where the point is used. Against a fifty-thousand-site mesh, allocation falls+ 5.3% at a thousand added points and 26.5% at fifty thousand; wall time is+ unmoved, because geometric insertion, not admission, is the critical path.+ ## 1.2.0.1 * Admit GHC 9.8 as a tested compiler by spelling the package language as
README.md view
@@ -36,6 +36,7 @@ | `cellValuations` / `regionValuations` | intrinsic measurement | Exact Euler characteristic and rational area plus symbolic radical length with certified bounds. | | `minkowskiSum` | polygonal convolution | Exact convex or general polygonal Minkowski addition, with work recorded in a receipt. | | `erodeBy` / `openWith` / `closeWith` | regularized morphology | Full-dimensional polygonal erosion and its opening/closing compositions for an origin-anchored convex kernel. |+| `fromExactCellSet` | topology interpretation | An opaque `DCELComplex` view of the already-admitted cell inventory; no mesh copy or second validation authority. | ### Choose the operation @@ -70,16 +71,55 @@ | Keep Delaunay faces below an alpha threshold | `alphaShapeContainsFace threshold mesh` | This is the filled-face filtration, not palette logic and not a claim to expose every simplex of a full alpha-complex carrier. | | Require construction-independent numbering | `canonicalize` at the observation boundary | Do not canonicalize every intermediate value; it is intentionally global work. | +### Cell-complex and category interpretation++The `cell-complex` component interprets an `ExactCellSet` as+Homology's generic `CellComplex2D`. The carrier is opaque and retains the+authoritative selected handles and resident DCEL; `fromExactCellSet` is total+because `ExactCellSet` construction has already discharged downward closure and+handle validity.++The interpretation is available on GHC 9.14 and newer. Existing Triangulation+components retain the package's GHC 9.8–9.14 support range: below 9.14 Cabal+marks only `cell-complex`, its focused test, and the category-observatory+exporter unbuildable. Hackage aggregates dependencies from every component on+the package page, but an ordinary main-library consumer does not inherit the+Homology or Category closure.++```cabal+-- the consumer package+build-depends:+ moonlight-triangulation:cell-complex >= 1.3 && < 1.4,+ moonlight-homology:cell-category >= 0.1 && < 0.2+```++```haskell+import Moonlight.Homology.Pure.Topology.CellCategory (complexCategory)+import Moonlight.Triangulation.CellComplex (fromExactCellSet)++incidenceCategory = complexCategory . fromExactCellSet+```++`moonlight-triangulation-category-observatory-export` is the executable witness:+it constructs a real Delaunay mesh, selects its bounded cells, derives the finite+incidence category and normalized nerve, validates their overlap counts, and emits+only the checked visualization projection.+ ## Foreign bindings The `moonlight-triangulation-c` foreign-library component exposes opaque-immutable geometry meshes, batch insertion, finite-set operations, and dense-vertex and triangle projections through one versioned C ABI. Python,-TypeScript, and Rust bindings live beneath [`bindings/`](bindings/README.md)-and descend through that same ABI; none restates the triangulation engine or-introduces a second mesh representation.+immutable geometry meshes, admitted exact planar regions, and reusable+polygonal structuring elements through ABI version 2. Mesh `site_*` operations+combine Delaunay sites; region operations perform actual polygonal Boolean+selection through the exact overlay owner. Regions cross in one bulk call as+coordinates plus loop/component counts and project back as components, loops,+and binary64 rendering points. Exact area remains lossless as a reduced ratio;+Euler is integral and perimeter carries certified bounds. Python, TypeScript,+and Rust bindings live beneath [`bindings/`](bindings/README.md) and descend+through that same ABI; none restates overlay, publication, valuation, or+morphology. -`union a a` is `a`; commutativity and associativity hold after explicit+For the Haskell mesh algebra, `union a a` is `a`; commutativity and associativity hold after explicit `canonicalize`; and a join adds no sites: the result carries `|A| + |B| − |A ∩ B|` of them. Structural `Eq` remains exact resident equality for caches and serialization rather than secretly rebuilding the mesh.@@ -103,19 +143,54 @@ | `extendConstrainedWith` | Copy-on-write only for a base of at least 200,000 sites, at most 128 incoming sites, exactly one incoming segment, and a pre-thaw corridor with no resident intersection. Every unmeasured or resident-corridor case stays dense. | | `refineWithinDomain` | Dense publication. The local transaction candidate preserved semantics but did not improve wall time, so it was removed. | -### Benchmark highlights+### Overall Moonlight–Spade comparison -Fresh source build on 2026-08-12. The comparison binaries passed exact-canonical-output agreement before seven interleaved fresh-process timing-rounds; brackets are the observed range. Lower is better.+The overall referent is broader than the construction board below. Its live+inventory contains 17 parity lanes and eight diagnostic cliff lanes. Every+timing descends through its declared artifact gate first: cross-language+canonical equality where the result is unique, and pinned per-side artifacts+where valid refinement or floating-point accumulation may differ. -| Operation | Workload | Moonlight | Spade 2.15.1 | Result |-| --- | ---: | ---: | ---: | ---: |-| Removal | remove 125k / 500k sites | 522.067 ms [514.481–539.392] | 16.576 s [15.304–17.308] | **31.75× faster** |-| Removal | remove 25k / 100k sites | 72.164 ms [67.604–78.116] | 360.899 ms [264.639–406.783] | **5.00× faster** median |-| Natural-neighbour interpolation | 2k queries / 400k sites | 192.792 ms [178.064–201.805] | 233.688 ms [214.695–250.986] | **1.21× faster** |-| Incremental insertion | 500k sites | 33.403 s [32.592–36.813] | 30.506 s [28.809–31.688] | within **1.09×** |+| Surface | Parity workloads |+| --- | --- |+| Construction | bulk load at 1k and 10k sites; incremental insertion at 1k, 10k, and 500k sites |+| Search and dual | 5k nearest-neighbour queries over 20k sites; Voronoi dual sweep over 1k sites; DCEL traversal over 2k sites |+| Constraints | recover 800 constraints over 8k sites; refine with 625 and 2,500 Steiner-site budgets |+| Persistent update | remove 250 / 1k, 2.5k / 10k, and 25k / 100k sites |+| Natural-neighbour interpolation | 1k queries over 10k sites; 2k queries over 100k sites |+| Segment traversal | 500 line intersections over 10k sites | +The eight diagnostic lanes cover one-at-a-time constraint recovery, constraint+splitting, hierarchy insertion, duplicate insertion, hierarchy removal,+single-angle sweep collapse, exactly collinear construction with face-less+location, and line intersection from outside the hull. They expose known+complexity cliffs; they are not omitted merely because the numbers are ugly.++The package-owned [operation suite](bench/spade-compare/README.md) documents the+complete inventory, semantic descent, timing protocol, and evidence rules. Its+`scorecard.sh` entry point consumes the closed Haskell lane registry directly;+`list` exposes the operation surface, while only a complete, attributable+`refresh` may create a retained performance board.++```console+compiler/foundation/moonlight-triangulation/bench/spade-compare/scorecard.sh list+compiler/foundation/moonlight-triangulation/bench/spade-compare/scorecard.sh check+```++### Package-owned Delaunay construction board++The Haskell-owned port of Spade's construction matrix lives with the+triangulation library at [`bench/delaunay-compare`](bench/delaunay-compare/README.md).+It appends Moonlight to Spade, Spade hierarchy, `cdt`, and `delaunator`, checks+all 24 fixture summaries across all five implementations before timing, and+derives both pictures from the retained 120-case CSV.++++The [benchmark README](bench/delaunay-compare/README.md) owns the single+retained board, its result, receipt, big-point picture, and reproduction+procedure. This package overview does not maintain a second benchmark history.+ Spade exposes no public triangulation set algebra corresponding to these operations. Their independently witnessed Moonlight publication costs are: @@ -199,10 +274,11 @@ * One half-edge mesh underneath. `Cdt` is a mode index on it, not a second structure. -The Haskell facade carries the exact region, overlay, valuation, and morphology-algebra. `ExactCellSet` preserves closed point, edge, and face selections;-`PlanarRegion` is its full-dimensional polygonal publication. The C ABI and-Python, TypeScript, and Rust bindings carry the immutable binary64 mesh surface.+The Haskell facade is the authority for exact region, overlay, valuation, and+morphology. `ExactCellSet` preserves closed point, edge, and face selections;+`PlanarRegion` is its full-dimensional polygonal publication. ABI version 2+retains that admitted region behind an opaque handle; foreign binary64 points+are authoring/rendering boundaries, not a competing exact representation. ## Use @@ -517,11 +593,34 @@ | --- | --- | | `core` | Exact-arithmetic scalars (`Scalar`, `LineSideInfo`) over paged, copy-on-write storage (`Internal.Dyadic`, `.Paged`, `.BoxedPaged`, `.PageDirectory`, `.Growable`, `.PackedIndex`, `.FaceQueue`). Depends only on `base`/`containers`/`deepseq`/`vector` — no other sublibrary. | | `dcel` | The finite half-edge mesh and its whole read surface: `Types`, `Math`, exact geometry, closed `CellSet`, `Region`, `Valuation`, `Interop`, `Dcel`, `Payload`, `JoinSemilattice`, `Handles` with its iterator family, `PointLocation`, `Validation`, `FloodFillIterator`, `IntersectionIterator`. Adds `primitive` and `vector-algorithms` over `core`. |-| `build` | Everything that *constructs*: `BulkLoad`, `Session`, `Removal`, typed `SetAlgebra`, `Cdt` constraint recovery, `Overlay`, `Minkowski`, and `Refinement`. Adds no external dependency over `core` and `dcel`. |+| `build` | The construction kernel: `BulkLoad`, `Session`, `Removal`, typed `SetAlgebra`, `Cdt` constraint recovery, and `Refinement`. Adds no external dependency over `core` and `dcel`. | | `parallel` | Concurrent evaluation of the pure union plan. Adds `async` at this effect boundary rather than below it. | | `serialize` | `Serialization` — the versioned binary envelope, and the only sublibrary that costs you `binary`, `bytestring`, and `transformers`. Deliberately absent from the facade. |-| `dual` | The Voronoi dual and what reads it: `Voronoi`, `Voronoi.Handles`, `Interpolation` (natural-neighbour), `HintGenerator` (Delaunay hierarchy hints). Sits over `core`, `dcel` and `build`. |-| facade (`moonlight-triangulation`) | `Moonlight.Triangulation` alone: the equational surface, one export list stating a theory. It re-exports selected names from `core`/`dcel`/`build`/`dual` — and *not* `serialize`. A caller who wants more than the theory names the machine-room module directly. |+| `dual` | The dcel-only Voronoi dual and its readers: `Voronoi`, `Voronoi.Handles`, and `Interpolation` (natural-neighbour). It can compile concurrently with `build`. |+| facade (`moonlight-triangulation`) | The equational surface plus the two real overlaps: exact `Overlay`/`Minkowski` operations that descend through `build`, and `HintGenerator`, which glues construction to the dual. It re-exports the theory and deliberately excludes `serialize`; there is no duplicate facade implementation beneath it. |++### Fast local feedback++Run exact components through the repository build guard so a local edit never+silently expands into the complete package. From the repository's `compiler/`+directory, the optimized construction kernel is:++ ../scripts/safe-cabal.sh build moonlight-triangulation:lib:build \+ --project-file=cabal.project.triangulation-dev \+ --builddir=dist-newstyle-triangulation-dev++For type-and-interface feedback where no benchmarkable artifact is required,+use the same component and cache at `-O0`:++ ../scripts/safe-cabal.sh build moonlight-triangulation:lib:build \+ --project-file=cabal.project.triangulation-dev \+ --builddir=dist-newstyle-triangulation-fast \+ --disable-optimization++The full facade is still an admitted five-component closure. Release and+runtime claims use the optimized profile; the `-O0` command is deliberately a+different feedback workload, not counterfeit evidence of a faster production+build. ## Surface
+ app/category-observatory/Main.hs view
@@ -0,0 +1,562 @@+{-# LANGUAGE DeriveAnyClass #-}++module Main (main) where++import Data.Aeson (ToJSON)+import Data.Aeson qualified as Aeson+import Data.Bifunctor (first)+import Data.ByteString.Lazy.Char8 qualified as LazyByteString+import Data.Containers.ListUtils (nubOrd)+import Data.List.NonEmpty (NonEmpty (..))+import Data.Vector qualified as Vector+import GHC.Generics (Generic)+import Moonlight.Homology.Pure.Topology.CellCategory+ ( ComplexCat,+ ComplexMor (..),+ ComplexOb (..),+ complexCategory,+ )+import Moonlight.Triangulation.CellComplex+ ( DCELComplex,+ fromExactCellSet,+ )+import Moonlight.Category+ ( Category (..),+ FiniteComposableCategory (..),+ chainVertices,+ )+import Moonlight.Category.Simplicial+ ( GeneratedSSet,+ NerveSimplex,+ TruncatedNormalizedSSet,+ generatedSimplicesAtDimension,+ nerveSimplexChain,+ normalizedNerve,+ simplicesAtDimension,+ unnormalizedNerve,+ )+import Moonlight.Homology.Pure.Topology.CellComplex+ ( CellComplex2D (..),+ CellTypes (..),+ OrientedEdge (..),+ )+import Moonlight.Triangulation.BulkLoad (delaunayGeometry)+import Moonlight.Triangulation.CellSet+ ( CellSelectionError,+ ExactCellSet,+ closeFaceCellSet,+ exactCellSetEdgeCount,+ exactCellSetFaceCount,+ exactCellSetVertexCount,+ )+import Moonlight.Triangulation.Dcel+ ( faceVertices,+ undirectedEndpoints,+ vertexPoint,+ )+import Moonlight.Triangulation.Handles.HandleDefs+ ( FaceId,+ UndirectedEdgeId,+ VertexId,+ unFaceId,+ unUndirectedEdgeId,+ unVertexId,+ )+import Moonlight.Triangulation.Handles.Iterators.FixedIterators (innerFaces)+import Moonlight.Triangulation.Types+ ( BuildError,+ DelaunayTriangulation,+ Point (..),+ )+import Numeric.Natural (Natural)+import System.Exit (die)++main :: IO ()+main =+ either+ (die . renderExhibitError)+ (LazyByteString.putStrLn . Aeson.encode)+ buildExhibit++data ExhibitError+ = ExhibitTriangulationBuildFailed BuildError+ | ExhibitCellSelectionFailed CellSelectionError+ | ExhibitMorphismEndpointUnavailable MorphismEndpoint Int+ | ExhibitFaceWithoutVertices FaceId+ | ExhibitMalformedTwoSimplex Int+ | ExhibitInventoryMismatch (Int, Int, Int) (Int, Int, Int)+ | ExhibitCategoryEnumerationMismatch (Int, Int, Int, Int) (Int, Int, Int, Int)+ | ExhibitNerveCountMismatch Int Int+ deriving stock (Show)++data MorphismEndpoint+ = MorphismSource+ | MorphismTarget+ deriving stock (Show)++renderExhibitError :: ExhibitError -> String+renderExhibitError = show++data Exhibit = Exhibit+ { schemaVersion :: Int,+ exhibitTitle :: String,+ mesh :: MeshProjection,+ category :: CategoryProjection,+ nerve :: NerveProjection+ }+ deriving stock (Generic)+ deriving anyclass (ToJSON)++data MeshProjection = MeshProjection+ { meshVertices :: [MeshVertex],+ meshEdges :: [MeshEdge],+ meshFaces :: [MeshFace]+ }+ deriving stock (Generic)+ deriving anyclass (ToJSON)++data MeshVertex = MeshVertex+ { meshVertexIdentifier :: String,+ meshVertexObjectIdentifier :: String,+ meshVertexPosition :: Position+ }+ deriving stock (Generic)+ deriving anyclass (ToJSON)++data MeshEdge = MeshEdge+ { meshEdgeIdentifier :: String,+ meshEdgeObjectIdentifier :: String,+ meshEdgeVertexIdentifiers :: [String]+ }+ deriving stock (Generic)+ deriving anyclass (ToJSON)++data MeshFace = MeshFace+ { meshFaceIdentifier :: String,+ meshFaceObjectIdentifier :: String,+ meshFaceVertexIdentifiers :: [String]+ }+ deriving stock (Generic)+ deriving anyclass (ToJSON)++data Position = Position+ { positionX :: Double,+ positionY :: Double+ }+ deriving stock (Generic)+ deriving anyclass (ToJSON)++data CategoryProjection = CategoryProjection+ { categoryObjects :: [CategoryObject],+ categoryMorphisms :: [CategoryMorphism]+ }+ deriving stock (Generic)+ deriving anyclass (ToJSON)++data CategoryObject = CategoryObject+ { categoryObjectIdentifier :: String,+ categoryObjectKind :: CategoryObjectKind,+ categoryObjectLabel :: String,+ categoryObjectPosition :: Position+ }+ deriving stock (Generic)+ deriving anyclass (ToJSON)++data CategoryObjectKind+ = VertexObject+ | EdgeObject+ | FaceObject+ deriving stock (Generic)+ deriving anyclass (ToJSON)++data CategoryMorphism = CategoryMorphism+ { categoryMorphismIdentifier :: String,+ categoryMorphismKind :: CategoryMorphismKind,+ categoryMorphismSourceObject :: String,+ categoryMorphismTargetObject :: String+ }+ deriving stock (Generic)+ deriving anyclass (ToJSON)++data CategoryMorphismKind+ = IdentityMorphism+ | FaceEdgeMorphism+ | EdgeVertexMorphism+ | FaceVertexComposite+ deriving stock (Generic)+ deriving anyclass (ToJSON)++data NerveProjection = NerveProjection+ { nerveTwoSimplices :: [NerveTwoSimplex],+ nerveSimplexCounts :: [NerveSimplexCount]+ }+ deriving stock (Generic)+ deriving anyclass (ToJSON)++data NerveSimplexCount = NerveSimplexCount+ { nerveDimension :: Natural,+ normalizedSimplexCount :: Int,+ unnormalizedSimplexCount :: Int+ }+ deriving stock (Generic)+ deriving anyclass (ToJSON)++data NerveTwoSimplex = NerveTwoSimplex+ { nerveSimplexIdentifier :: String,+ nerveSimplexObjectIdentifiers :: [String]+ }+ deriving stock (Generic)+ deriving anyclass (ToJSON)++buildExhibit :: Either ExhibitError Exhibit+buildExhibit = do+ triangulation <- first ExhibitTriangulationBuildFailed (delaunayGeometry exhibitPoints)+ selectedCells <-+ first ExhibitCellSelectionFailed+ (closeFaceCellSet triangulation (innerFaces triangulation))+ let complexValue = fromExactCellSet selectedCells+ categoryValue = complexCategory complexValue+ validateInventory selectedCells complexValue+ validateCategoryEnumeration complexValue categoryValue+ categoryProjection <- projectCategory triangulation categoryValue+ nerveProjection <- projectNerve categoryValue+ validateNerveCount complexValue nerveProjection+ pure+ Exhibit+ { schemaVersion = 2,+ exhibitTitle = "Triangulation, incidence category, and flag nerve",+ mesh = projectMesh triangulation complexValue,+ category = categoryProjection,+ nerve = nerveProjection+ }++validateInventory :: ExactCellSet -> DCELComplex -> Either ExhibitError ()+validateInventory cellSet complexValue =+ let expectedCounts =+ ( exactCellSetVertexCount cellSet,+ exactCellSetEdgeCount cellSet,+ exactCellSetFaceCount cellSet+ )+ observedCounts =+ ( length (vertices complexValue),+ length (edges complexValue),+ length (faces complexValue)+ )+ in if observedCounts == expectedCounts+ then Right ()+ else Left (ExhibitInventoryMismatch expectedCounts observedCounts)++validateCategoryEnumeration ::+ DCELComplex ->+ ComplexCat DCELComplex ->+ Either ExhibitError ()+validateCategoryEnumeration complexValue categoryValue =+ let morphisms = enumerateMorphisms categoryValue+ expectedCounts =+ ( length (vertices complexValue) + length (edges complexValue) + length (faces complexValue),+ sum (fmap (length . faceBoundary complexValue) (faces complexValue)),+ sum (fmap (edgeEndpointCount complexValue) (edges complexValue)),+ sum (fmap (faceFlagCount complexValue) (faces complexValue))+ )+ observedCounts =+ ( length (filter isIdentityMorphism morphisms),+ length (filter isFaceEdgeMorphism morphisms),+ length (filter isEdgeVertexMorphism morphisms),+ length (filter isFaceVertexMorphism morphisms)+ )+ in if observedCounts == expectedCounts+ then Right ()+ else Left (ExhibitCategoryEnumerationMismatch expectedCounts observedCounts)++validateNerveCount :: DCELComplex -> NerveProjection -> Either ExhibitError ()+validateNerveCount complexValue nerveProjection =+ let expectedCount = sum (fmap (faceFlagCount complexValue) (faces complexValue))+ observedCount = length (nerveTwoSimplices nerveProjection)+ in if observedCount == expectedCount+ then Right ()+ else Left (ExhibitNerveCountMismatch expectedCount observedCount)++edgeEndpointCount :: DCELComplex -> Edge DCELComplex -> Int+edgeEndpointCount complexValue edgeValue =+ let (sourceVertex, targetVertex) = edgeBoundary complexValue edgeValue+ in length (nubOrd [sourceVertex, targetVertex])++faceFlagCount :: DCELComplex -> Face DCELComplex -> Int+faceFlagCount complexValue faceValue =+ sum+ ( fmap+ (edgeEndpointCount complexValue . orientedEdge)+ (faceBoundary complexValue faceValue)+ )++isIdentityMorphism :: ComplexMor DCELComplex -> Bool+isIdentityMorphism morphismValue =+ case morphismValue of+ IdentityMor _ -> True+ _ -> False++isFaceEdgeMorphism :: ComplexMor DCELComplex -> Bool+isFaceEdgeMorphism morphismValue =+ case morphismValue of+ FaceToEdge _ _ _ -> True+ _ -> False++isEdgeVertexMorphism :: ComplexMor DCELComplex -> Bool+isEdgeVertexMorphism morphismValue =+ case morphismValue of+ EdgeToVertex _ _ -> True+ _ -> False++isFaceVertexMorphism :: ComplexMor DCELComplex -> Bool+isFaceVertexMorphism morphismValue =+ case morphismValue of+ FaceToVertex _ _ _ -> True+ _ -> False++projectMesh :: DelaunayTriangulation () -> DCELComplex -> MeshProjection+projectMesh triangulation complexValue =+ MeshProjection+ { meshVertices = fmap (projectMeshVertex triangulation) (vertices complexValue),+ meshEdges = fmap (projectMeshEdge triangulation) (edges complexValue),+ meshFaces = fmap (projectMeshFace triangulation) (faces complexValue)+ }++projectMeshVertex :: DelaunayTriangulation () -> VertexId -> MeshVertex+projectMeshVertex triangulation vertexValue =+ MeshVertex+ { meshVertexIdentifier = vertexIdentifier vertexValue,+ meshVertexObjectIdentifier = vertexObjectIdentifier vertexValue,+ meshVertexPosition = pointPosition (vertexPoint triangulation vertexValue)+ }++projectMeshEdge :: DelaunayTriangulation () -> UndirectedEdgeId -> MeshEdge+projectMeshEdge triangulation edgeValue =+ let (sourceVertex, targetVertex) = undirectedEndpoints triangulation edgeValue+ in MeshEdge+ { meshEdgeIdentifier = edgeIdentifier edgeValue,+ meshEdgeObjectIdentifier = edgeObjectIdentifier edgeValue,+ meshEdgeVertexIdentifiers = fmap vertexIdentifier [sourceVertex, targetVertex]+ }++projectMeshFace :: DelaunayTriangulation () -> FaceId -> MeshFace+projectMeshFace triangulation faceValue =+ MeshFace+ { meshFaceIdentifier = faceIdentifier faceValue,+ meshFaceObjectIdentifier = faceObjectIdentifier faceValue,+ meshFaceVertexIdentifiers = fmap vertexIdentifier (faceVertices triangulation faceValue)+ }++projectCategory ::+ DelaunayTriangulation () ->+ ComplexCat DCELComplex ->+ Either ExhibitError CategoryProjection+projectCategory triangulation categoryValue = do+ objectProjections <-+ traverse+ (projectCategoryObject triangulation)+ (enumerateObjects categoryValue)+ morphismProjections <-+ traverse+ (projectCategoryMorphism categoryValue)+ (zip [0 ..] (enumerateMorphisms categoryValue))+ pure+ CategoryProjection+ { categoryObjects = objectProjections,+ categoryMorphisms = morphismProjections+ }++projectCategoryObject ::+ DelaunayTriangulation () ->+ ComplexOb DCELComplex ->+ Either ExhibitError CategoryObject+projectCategoryObject triangulation objectValue = do+ objectPosition <- positionForObject triangulation objectValue+ pure+ CategoryObject+ { categoryObjectIdentifier = objectIdentifier objectValue,+ categoryObjectKind = objectKind objectValue,+ categoryObjectLabel = objectLabel objectValue,+ categoryObjectPosition = objectPosition+ }++projectCategoryMorphism ::+ ComplexCat DCELComplex ->+ (Int, ComplexMor DCELComplex) ->+ Either ExhibitError CategoryMorphism+projectCategoryMorphism categoryValue (morphismIndex, morphismValue) = do+ sourceObject <-+ first+ (const (ExhibitMorphismEndpointUnavailable MorphismSource morphismIndex))+ (source categoryValue morphismValue)+ targetObject <-+ first+ (const (ExhibitMorphismEndpointUnavailable MorphismTarget morphismIndex))+ (target categoryValue morphismValue)+ pure+ CategoryMorphism+ { categoryMorphismIdentifier = "morphism-" <> show morphismIndex,+ categoryMorphismKind = morphismKind morphismValue,+ categoryMorphismSourceObject = objectIdentifier sourceObject,+ categoryMorphismTargetObject = objectIdentifier targetObject+ }++projectNerve :: ComplexCat DCELComplex -> Either ExhibitError NerveProjection+projectNerve categoryValue = do+ let normalizedNerveValue = normalizedNerve categoryValue nerveObservationBound+ unnormalizedNerveValue = unnormalizedNerve categoryValue nerveObservationBound+ twoSimplices <-+ traverse+ projectTwoSimplex+ (zip [0 ..] (simplicesAtDimension normalizedNerveValue nerveObservationBound))+ pure+ NerveProjection+ { nerveTwoSimplices = twoSimplices,+ nerveSimplexCounts =+ fmap+ (projectNerveSimplexCount normalizedNerveValue unnormalizedNerveValue)+ nerveObservationDimensions+ }++nerveObservationBound :: Natural+nerveObservationBound = 2++nerveObservationDimensions :: [Natural]+nerveObservationDimensions = [0, 1, 2]++projectNerveSimplexCount ::+ TruncatedNormalizedSSet (NerveSimplex (ComplexCat DCELComplex)) ->+ GeneratedSSet (NerveSimplex (ComplexCat DCELComplex)) ->+ Natural ->+ NerveSimplexCount+projectNerveSimplexCount normalizedNerveValue unnormalizedNerveValue dimensionValue =+ NerveSimplexCount+ { nerveDimension = dimensionValue,+ normalizedSimplexCount = length (simplicesAtDimension normalizedNerveValue dimensionValue),+ unnormalizedSimplexCount = length (generatedSimplicesAtDimension unnormalizedNerveValue dimensionValue)+ }++projectTwoSimplex ::+ (Int, NerveSimplex (ComplexCat DCELComplex)) ->+ Either ExhibitError NerveTwoSimplex+projectTwoSimplex (simplexIndex, simplexValue) =+ case chainVertices (nerveSimplexChain simplexValue) of+ faceObject :| [edgeObject, vertexObject] ->+ pure+ NerveTwoSimplex+ { nerveSimplexIdentifier = "simplex-" <> show simplexIndex,+ nerveSimplexObjectIdentifiers =+ fmap objectIdentifier [faceObject, edgeObject, vertexObject]+ }+ _ -> Left (ExhibitMalformedTwoSimplex simplexIndex)++positionForObject ::+ DelaunayTriangulation () ->+ ComplexOb DCELComplex ->+ Either ExhibitError Position+positionForObject triangulation objectValue =+ case objectValue of+ VertexOb vertexValue ->+ Right (pointPosition (vertexPoint triangulation vertexValue))+ EdgeOb edgeValue ->+ let (sourceVertex, targetVertex) = undirectedEndpoints triangulation edgeValue+ in Right+ ( midpoint+ (pointPosition (vertexPoint triangulation sourceVertex))+ (pointPosition (vertexPoint triangulation targetVertex))+ )+ FaceOb faceValue ->+ centroid faceValue+ (fmap (pointPosition . vertexPoint triangulation) (faceVertices triangulation faceValue))++centroid :: FaceId -> [Position] -> Either ExhibitError Position+centroid faceValue positions =+ case positions of+ [] -> Left (ExhibitFaceWithoutVertices faceValue)+ _ ->+ let (totalX, totalY) =+ foldl'+ (\(accumulatedX, accumulatedY) positionValue ->+ ( accumulatedX + positionX positionValue,+ accumulatedY + positionY positionValue+ )+ )+ (0, 0)+ positions+ positionCount = fromIntegral (length positions)+ in Right (Position (totalX / positionCount) (totalY / positionCount))++midpoint :: Position -> Position -> Position+midpoint firstPosition secondPosition =+ Position+ { positionX = (positionX firstPosition + positionX secondPosition) / 2,+ positionY = (positionY firstPosition + positionY secondPosition) / 2+ }++pointPosition :: Point -> Position+pointPosition pointValue =+ Position+ { positionX = pointX pointValue,+ positionY = pointY pointValue+ }++objectIdentifier :: ComplexOb DCELComplex -> String+objectIdentifier objectValue =+ case objectValue of+ VertexOb vertexValue -> vertexObjectIdentifier vertexValue+ EdgeOb edgeValue -> edgeObjectIdentifier edgeValue+ FaceOb faceValue -> faceObjectIdentifier faceValue++objectKind :: ComplexOb DCELComplex -> CategoryObjectKind+objectKind objectValue =+ case objectValue of+ VertexOb _ -> VertexObject+ EdgeOb _ -> EdgeObject+ FaceOb _ -> FaceObject++objectLabel :: ComplexOb DCELComplex -> String+objectLabel objectValue =+ case objectValue of+ VertexOb vertexValue -> "v" <> show (unVertexId vertexValue)+ EdgeOb edgeValue -> "e" <> show (unUndirectedEdgeId edgeValue)+ FaceOb faceValue -> "f" <> show (unFaceId faceValue)++morphismKind :: ComplexMor DCELComplex -> CategoryMorphismKind+morphismKind morphismValue =+ case morphismValue of+ IdentityMor _ -> IdentityMorphism+ FaceToEdge _ _ _ -> FaceEdgeMorphism+ EdgeToVertex _ _ -> EdgeVertexMorphism+ FaceToVertex _ _ _ -> FaceVertexComposite++vertexIdentifier :: VertexId -> String+vertexIdentifier vertexValue = "vertex-" <> show (unVertexId vertexValue)++edgeIdentifier :: UndirectedEdgeId -> String+edgeIdentifier edgeValue = "edge-" <> show (unUndirectedEdgeId edgeValue)++faceIdentifier :: FaceId -> String+faceIdentifier faceValue = "face-" <> show (unFaceId faceValue)++vertexObjectIdentifier :: VertexId -> String+vertexObjectIdentifier vertexValue = "object-" <> vertexIdentifier vertexValue++edgeObjectIdentifier :: UndirectedEdgeId -> String+edgeObjectIdentifier edgeValue = "object-" <> edgeIdentifier edgeValue++faceObjectIdentifier :: FaceId -> String+faceObjectIdentifier faceValue = "object-" <> faceIdentifier faceValue++exhibitPoints :: Vector.Vector Point+exhibitPoints =+ Vector.fromList+ [ Point 80 72,+ Point 222 48,+ Point 362 92,+ Point 52 208,+ Point 176 166,+ Point 310 190,+ Point 414 232,+ Point 112 330,+ Point 258 314,+ Point 380 354+ ]
bench/build/Moonlight/Triangulation/BuildBench.hs view
@@ -18,12 +18,13 @@ import qualified Data.Vector as V import Moonlight.Triangulation import Moonlight.Triangulation.BulkLoad (empty, insert)-import Moonlight.Triangulation.Cdt (constraintBatchStats, recoverConstraints)+import Moonlight.Triangulation.Cdt (recoverConstraints)+import Moonlight.Triangulation.Foreign.ABI (insertGeometryBatch) -- The facade withholds this constructor. The benchmark indexes the builder's -- own input mapping, so every handle it forges is one the builder issued, and -- it owns that obligation explicitly by naming the module that grants it. import Moonlight.Triangulation.Handles.HandleDefs (VertexId (VertexId))-import Moonlight.Triangulation.Session (insertVertex, withLocalSession, withSession)+import Moonlight.Triangulation.Session (insertVertex, insertVertexAt, withLocalSession, withSession) import Moonlight.Triangulation.Types ( InsertionResult (insertionTriangulation) , refinementStats@@ -38,9 +39,38 @@ putStrLn "moonlight-triangulation native construction benchmark" forM_ [1_000, 10_000, 50_000] benchmarkConstruction forM_ [1_000, 10_000, 50_000, 100_000, 1_000_000] benchmarkSingletonInsertionCrossover- benchmarkConstraints 8_000 800+ forM_ [(50_000, 1_000), (50_000, 10_000), (50_000, 50_000)] benchmarkGeometryBatchInsertion+ benchmarkConstraints benchmarkRefinement 2_500 +-- | What the geometry-only foreign batch entrance costs against the session+-- insertion it is built out of. The two arms differ by exactly one thing: the+-- ABI route validates every point into an intermediate boxed vector before it+-- opens the session, and the session arm receives the same points with that+-- pass already paid. Their difference is therefore the price of admission,+-- and it is the number that decides whether a fused geometry entrance is worth+-- designing at all. Inserting into an existing mesh is not the law of fresh+-- circle sweep, so nothing here transfers from the construction lanes above.+benchmarkGeometryBatchInsertion :: (Int, Int) -> IO ()+benchmarkGeometryBatchInsertion (baseCount, addedCount) = do+ let label suffix = "geometry-batch-" <> suffix <> "/" <> show baseCount <> "+" <> show addedCount+ basePoints = V.fromList (randomPoints 0x9e3779b97f4a7c15 baseCount)+ addedPoints = V.fromList (randomPoints 0xbf58476d1ce4e5b9 addedCount)+ base <- evaluate . force =<< requireRight (delaunayGeometry basePoints)+ _ <- evaluate (force addedPoints)+ admitted <- timedValue (label "abi") $ requireRight (insertGeometryBatch base addedPoints)+ (_, sessioned, _) <- timedValue (label "session") $+ requireRight+ ( withSession base (V.length addedPoints) $+ V.mapM_ (\point -> () <$ insertVertexAt point ()) addedPoints+ )+ admittedCanonical <- requireRight (canonicalize admitted)+ sessionedCanonical <- requireRight (canonicalize sessioned)+ equal <- evaluate (force (admittedCanonical == sessionedCanonical))+ unless equal $+ fail (label "witness" <> ": the admitted and session arms disagree")+ putStrLn (label "witness" <> ": ok")+ benchmarkConstruction :: Int -> IO () benchmarkConstruction count = do let points = V.fromList (randomPoints 0x9e3779b97f4a7c15 count)@@ -74,8 +104,10 @@ fail ("scheduled and local singleton insertion disagree semantically at " <> show count <> " sites") putStrLn ("singleton-insertion-crossover/" <> show count <> "-semantic-witness: ok") -benchmarkConstraints :: Int -> Int -> IO ()-benchmarkConstraints pointCount constraintCount = do+benchmarkConstraints :: IO ()+benchmarkConstraints = do+ let pointCount = 8_000+ constraintCount = 800 built <- requireRight (delaunay unitElementDefaults (V.fromList (randomPoints 0x94d049bb133111eb pointCount))) let cdt = fromDelaunay (buildTriangulation built) inputMapping = buildInputVertices built@@ -83,7 +115,7 @@ V.fromList ( take constraintCount [ (a, b)- | index <- [0 ..]+ | index <- [0 .. pointCount * constraintCount - 1] , let a = index `mod` pointCount b = (index * 6151 + pointCount `quot` 2) `mod` pointCount , a /= b
+ bench/delaunay-compare/Main.hs view
@@ -0,0 +1,27 @@+-- | Haskell owner of the upstream-compatible construction comparison.+module Main (main) where++import Moonlight.Triangulation.Bench.DelaunayCompare.Domain (renderCompareObstruction)+import Moonlight.Triangulation.Bench.DelaunayCompare.Native (withNativeApi)+import Moonlight.Triangulation.Bench.DelaunayCompare.Suite+ ( preflightSuite+ , suiteAgreementMessage+ , suiteBenchmarks+ , withPreparedSuite+ )+import System.Exit (die)+import Test.Tasty.Bench (defaultMain)++main :: IO ()+main = do+ outcome <-+ withNativeApi $ \api ->+ withPreparedSuite api $ \suite -> do+ agreement <- preflightSuite api suite+ case agreement of+ Left obstruction -> pure (Left obstruction)+ Right () -> do+ putStrLn suiteAgreementMessage+ defaultMain (suiteBenchmarks api suite)+ pure (Right ())+ either (die . renderCompareObstruction) pure outcome
+ bench/delaunay-compare/Moonlight/Triangulation/Bench/DelaunayCompare/Domain.hs view
@@ -0,0 +1,244 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE NumericUnderscores #-}++-- | The closed case matrix and typed obstruction surface for the comparison.+module Moonlight.Triangulation.Bench.DelaunayCompare.Domain+ ( SizeBand (..)+ , allSizeBands+ , sizeBandLabel+ , PointDistribution (..)+ , allPointDistributions+ , pointDistributionLabel+ , Implementation (..)+ , allImplementations+ , implementationLabel+ , NativeImplementation (..)+ , nativeImplementationLabel+ , implementationOfNative+ , PointCount+ , pointCountValue+ , pointCountsFor+ , FixtureSpec (..)+ , allFixtureSpecs+ , DelaunaySummary (..)+ , NativeStatus (..)+ , NativePreparationFailure (..)+ , NativeRunFailure (..)+ , CompareObstruction (..)+ , renderCompareObstruction+ ) where++import Control.DeepSeq (NFData)+import Data.List.NonEmpty (NonEmpty)+import qualified Data.List.NonEmpty as NonEmpty+import GHC.Generics (Generic)+import Moonlight.Triangulation.Types (BuildError)++data SizeBand+ = Small+ | Big+ deriving stock (Eq, Ord, Show, Generic)+ deriving anyclass (NFData)++allSizeBands :: [SizeBand]+allSizeBands = [Small, Big]++sizeBandLabel :: SizeBand -> String+sizeBandLabel = \case+ Small -> "small"+ Big -> "big"++data PointDistribution+ = LocalInsertion+ | Uniform+ deriving stock (Eq, Ord, Show, Generic)+ deriving anyclass (NFData)++allPointDistributions :: [PointDistribution]+allPointDistributions = [LocalInsertion, Uniform]++pointDistributionLabel :: PointDistribution -> String+pointDistributionLabel = \case+ LocalInsertion -> "local insertion"+ Uniform -> "uniform"++data Implementation+ = Spade+ | SpadeHierarchy+ | Cdt+ | Delaunator+ | Moonlight+ deriving stock (Eq, Ord, Show, Generic)+ deriving anyclass (NFData)++-- | Upstream order, with Moonlight appended rather than interposed.+allImplementations :: [Implementation]+allImplementations = [Spade, SpadeHierarchy, Cdt, Delaunator, Moonlight]++implementationLabel :: Implementation -> String+implementationLabel = \case+ Spade -> "spade 2"+ SpadeHierarchy -> "spade 2 hierarchy"+ Cdt -> "cdt"+ Delaunator -> "delaunator"+ Moonlight -> "moonlight-triangulation"++data NativeImplementation+ = NativeSpade+ | NativeSpadeHierarchy+ | NativeCdt+ | NativeDelaunator+ deriving stock (Eq, Ord, Show, Generic)+ deriving anyclass (NFData)++nativeImplementationLabel :: NativeImplementation -> String+nativeImplementationLabel = implementationLabel . implementationOfNative++implementationOfNative :: NativeImplementation -> Implementation+implementationOfNative = \case+ NativeSpade -> Spade+ NativeSpadeHierarchy -> SpadeHierarchy+ NativeCdt -> Cdt+ NativeDelaunator -> Delaunator++newtype PointCount = PointCount Int+ deriving stock (Eq, Ord, Show, Generic)+ deriving anyclass (NFData)++pointCountValue :: PointCount -> Int+pointCountValue (PointCount count) = count++data FixtureSpec = FixtureSpec+ { fixtureSizeBand :: !SizeBand+ , fixturePointDistribution :: !PointDistribution+ , fixturePointCount :: !PointCount+ }+ deriving stock (Eq, Ord, Show, Generic)+ deriving anyclass (NFData)++allFixtureSpecs :: [FixtureSpec]+allFixtureSpecs =+ concatMap+ ( \sizeBand ->+ liftA2+ (FixtureSpec sizeBand)+ allPointDistributions+ (pointCountsFor sizeBand)+ )+ allSizeBands++pointCountsFor :: SizeBand -> [PointCount]+pointCountsFor = \case+ Small -> PointCount <$> [2_000, 4_000, 6_000, 8_000, 10_000, 12_000, 14_000]+ Big -> PointCount <$> [50_000, 100_000, 150_000, 200_000, 250_000]++data DelaunaySummary = DelaunaySummary+ { summaryVertexCount :: !Int+ , summaryTriangleCount :: !Int+ }+ deriving stock (Eq, Ord, Show, Generic)+ deriving anyclass (NFData)++data NativeStatus+ = NativeNullPointer+ | NativeUnknownTag+ | NativeCoordinateCountMismatch+ | NativeDistributionConstructionFailed+ | NativeTriangulationFailed+ | NativePanicked+ | NativeUnknownStatus !Int+ deriving stock (Eq, Ord, Show, Generic)+ deriving anyclass (NFData)++data NativePreparationFailure+ = NativePreparationStatus !NativeStatus+ | NativePreparationReturnedNull+ deriving stock (Eq, Ord, Show, Generic)+ deriving anyclass (NFData)++data NativeRunFailure+ = NativeRunStatus !NativeStatus+ | NativeCountExceedsHaskellInt !Integer+ deriving stock (Eq, Ord, Show, Generic)+ deriving anyclass (NFData)++data CompareObstruction+ = RustManifestNotFound !(NonEmpty FilePath)+ | CargoInvocationFailed !FilePath !String+ | CargoBuildFailed !FilePath !Int !String+ | UnsupportedDynamicLibraryHost !String+ | DynamicLibraryOpenFailed !FilePath !String+ | DynamicSymbolLoadFailed !FilePath !String !String+ | FixtureGenerationFailed !FixtureSpec !NativeStatus+ | FixturePreparationFailed !FixtureSpec !NativeImplementation !NativeStatus+ | NativePreparedNull !FixtureSpec !NativeImplementation+ | MoonlightConstructionFailed !FixtureSpec !BuildError+ | NativeConstructionFailed !FixtureSpec !NativeImplementation !NativeRunFailure+ | SummaryDisagreement !FixtureSpec !Implementation !DelaunaySummary !DelaunaySummary+ | ComparisonObstructions !(NonEmpty CompareObstruction)+ deriving stock (Eq, Show, Generic)+ deriving anyclass (NFData)++renderCompareObstruction :: CompareObstruction -> String+renderCompareObstruction = \case+ RustManifestNotFound candidates ->+ "Rust referent manifest not found; checked " <> show (NonEmpty.toList candidates)+ CargoInvocationFailed manifest details ->+ "could not invoke Cargo for " <> manifest <> ": " <> details+ CargoBuildFailed manifest exitCode details ->+ "Cargo failed for "+ <> manifest+ <> " with exit code "+ <> show exitCode+ <> if null details then "" else ":\n" <> details+ UnsupportedDynamicLibraryHost host ->+ "delaunay-compare does not know the dynamic-library suffix for host " <> show host+ DynamicLibraryOpenFailed path details ->+ "could not load Rust referent library " <> path <> ": " <> details+ DynamicSymbolLoadFailed path symbol details ->+ "could not load symbol " <> symbol <> " from " <> path <> ": " <> details+ FixtureGenerationFailed fixture status ->+ "upstream fixture generation failed for " <> renderFixture fixture <> ": " <> show status+ FixturePreparationFailed fixture implementation status ->+ "native preparation failed for "+ <> nativeImplementationLabel implementation+ <> " on "+ <> renderFixture fixture+ <> ": "+ <> show status+ NativePreparedNull fixture implementation ->+ "native preparation returned a null handle for "+ <> nativeImplementationLabel implementation+ <> " on "+ <> renderFixture fixture+ MoonlightConstructionFailed fixture buildError ->+ "Moonlight construction failed for " <> renderFixture fixture <> ": " <> show buildError+ NativeConstructionFailed fixture implementation failure ->+ "native construction failed for "+ <> nativeImplementationLabel implementation+ <> " on "+ <> renderFixture fixture+ <> ": "+ <> show failure+ SummaryDisagreement fixture implementation expected observed ->+ "construction summary disagreement for "+ <> implementationLabel implementation+ <> " on "+ <> renderFixture fixture+ <> "; Moonlight="+ <> show expected+ <> ", observed="+ <> show observed+ ComparisonObstructions obstructions ->+ unlines ("delaunay comparison obstructed:" : fmap ((" - " <>) . renderCompareObstruction) (NonEmpty.toList obstructions))++renderFixture :: FixtureSpec -> String+renderFixture fixture =+ sizeBandLabel (fixtureSizeBand fixture)+ <> "/"+ <> pointDistributionLabel (fixturePointDistribution fixture)+ <> "/"+ <> show (pointCountValue (fixturePointCount fixture))
+ bench/delaunay-compare/Moonlight/Triangulation/Bench/DelaunayCompare/Native.hs view
@@ -0,0 +1,307 @@+{-# LANGUAGE ForeignFunctionInterface #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}++-- | Resource-safe dynamic boundary to the Rust crates under comparison.+module Moonlight.Triangulation.Bench.DelaunayCompare.Native+ ( NativeApi+ , NativeFixture+ , GeneratedCoordinates+ , withNativeApi+ , generateCoordinates+ , generatedCoordinatePairs+ , prepareNativeFixture+ , runNativeFixture+ , releaseNativeFixture+ ) where++import Control.Exception (IOException, bracket, displayException, try)+import Control.Monad (filterM)+import Control.Monad.Trans.Except (ExceptT (ExceptT), runExceptT)+import Data.Bifunctor (first)+import Data.Int (Int32)+import Data.List (unfoldr)+import Data.List.NonEmpty (NonEmpty (..))+import qualified Data.List.NonEmpty as NonEmpty+import Moonlight.Triangulation.Bench.DelaunayCompare.Domain+import Foreign.C.Types (CDouble, CInt (..), CSize (..))+import Foreign.Marshal.Alloc (alloca)+import Foreign.Ptr (FunPtr, Ptr, nullPtr)+import Foreign.Storable (peek, poke)+import qualified Data.Vector as Vector+import qualified Data.Vector.Storable as StorableVector+import qualified Data.Vector.Storable.Mutable as MutableStorableVector+import System.Directory (doesFileExist, makeAbsolute)+import System.Environment (lookupEnv)+import System.Exit (ExitCode (..))+import System.FilePath ((</>), takeDirectory)+import System.Info (os)+import System.Posix.DynamicLinker+ ( DL+ , RTLDFlags (RTLD_LOCAL, RTLD_NOW)+ , dlclose+ , dlopen+ , dlsym+ )+import System.Process+ ( CreateProcess (cwd)+ , proc+ , readCreateProcessWithExitCode+ )++type GenerateFunction = CInt -> CSize -> Ptr CDouble -> CSize -> IO CInt+type PrepareFunction = CInt -> Ptr CDouble -> CSize -> Ptr (Ptr ()) -> IO CInt+type RunFunction = Ptr () -> Ptr CSize -> Ptr CSize -> IO CInt+type ReleaseFunction = Ptr () -> IO ()++foreign import ccall safe "dynamic"+ bindGenerateFunction :: FunPtr GenerateFunction -> GenerateFunction++foreign import ccall safe "dynamic"+ bindPrepareFunction :: FunPtr PrepareFunction -> PrepareFunction++foreign import ccall safe "dynamic"+ bindRunFunction :: FunPtr RunFunction -> RunFunction++foreign import ccall unsafe "dynamic"+ bindReleaseFunction :: FunPtr ReleaseFunction -> ReleaseFunction++data NativeApi = NativeApi+ { nativeGenerate :: !GenerateFunction+ , nativePrepare :: !PrepareFunction+ , nativeRun :: !RunFunction+ , nativeRelease :: !ReleaseFunction+ }++newtype NativeFixture = NativeFixture (Ptr ())++data GeneratedCoordinates = GeneratedCoordinates+ { generatedPointCount :: !PointCount+ , generatedCoordinateValues :: !(StorableVector.Vector CDouble)+ }++withNativeApi+ :: (NativeApi -> IO (Either CompareObstruction result))+ -> IO (Either CompareObstruction result)+withNativeApi action = do+ manifestResult <- resolveRustManifest+ case manifestResult of+ Left obstruction -> pure (Left obstruction)+ Right manifest -> do+ libraryResult <- buildRustLibrary manifest+ case libraryResult of+ Left obstruction -> pure (Left obstruction)+ Right libraryPath -> withLoadedNativeApi libraryPath action++resolveRustManifest :: IO (Either CompareObstruction FilePath)+resolveRustManifest = do+ configured <- lookupEnv "MOONLIGHT_DELAUNAY_COMPARE_RUST_MANIFEST"+ let candidates =+ case configured of+ Just path -> path :| []+ Nothing ->+ "bench/delaunay-compare/rust/Cargo.toml"+ :| [ "foundation/moonlight-triangulation/bench/delaunay-compare/rust/Cargo.toml"+ , "compiler/foundation/moonlight-triangulation/bench/delaunay-compare/rust/Cargo.toml"+ ]+ existing <- filterM doesFileExist (NonEmpty.toList candidates)+ case existing of+ manifest : _ -> Right <$> makeAbsolute manifest+ [] -> pure (Left (RustManifestNotFound candidates))++buildRustLibrary :: FilePath -> IO (Either CompareObstruction FilePath)+buildRustLibrary manifest = do+ libraryNameResult <- dynamicLibraryName+ case libraryNameResult of+ Left obstruction -> pure (Left obstruction)+ Right libraryName -> do+ let rustRoot = takeDirectory manifest+ targetDirectory = rustRoot </> "target"+ command =+ ( proc+ "cargo"+ [ "build"+ , "--quiet"+ , "--release"+ , "--lib"+ , "--manifest-path"+ , manifest+ , "--target-dir"+ , targetDirectory+ ]+ )+ { cwd = Just rustRoot+ }+ invocation <- try @IOException (readCreateProcessWithExitCode command "")+ pure $+ case invocation of+ Left exception ->+ Left (CargoInvocationFailed manifest (displayException exception))+ Right (ExitFailure exitCode, standardOutput, standardError) ->+ Left+ ( CargoBuildFailed+ manifest+ exitCode+ (standardError <> standardOutput)+ )+ Right (ExitSuccess, _, _) ->+ Right (targetDirectory </> "release" </> libraryName)++dynamicLibraryName :: IO (Either CompareObstruction FilePath)+dynamicLibraryName =+ pure $+ case os of+ "darwin" -> Right "libmoonlight_delaunay_compare_referents.dylib"+ "linux" -> Right "libmoonlight_delaunay_compare_referents.so"+ host -> Left (UnsupportedDynamicLibraryHost host)++withLoadedNativeApi+ :: FilePath+ -> (NativeApi -> IO (Either CompareObstruction result))+ -> IO (Either CompareObstruction result)+withLoadedNativeApi libraryPath action = do+ opened <- try @IOException (dlopen libraryPath [RTLD_NOW, RTLD_LOCAL])+ case opened of+ Left exception ->+ pure (Left (DynamicLibraryOpenFailed libraryPath (displayException exception)))+ Right handle ->+ bracket+ (pure handle)+ dlclose+ ( \loadedHandle -> do+ apiResult <- loadNativeApi libraryPath loadedHandle+ case apiResult of+ Left obstruction -> pure (Left obstruction)+ Right api -> action api+ )++loadNativeApi :: FilePath -> DL -> IO (Either CompareObstruction NativeApi)+loadNativeApi libraryPath handle =+ runExceptT $+ NativeApi+ <$> loadSymbol libraryPath handle "delaunay_compare_generate" bindGenerateFunction+ <*> loadSymbol libraryPath handle "delaunay_compare_prepare" bindPrepareFunction+ <*> loadSymbol libraryPath handle "delaunay_compare_run" bindRunFunction+ <*> loadSymbol libraryPath handle "delaunay_compare_release" bindReleaseFunction++loadSymbol+ :: FilePath+ -> DL+ -> String+ -> (FunPtr function -> boundFunction)+ -> ExceptT CompareObstruction IO boundFunction+loadSymbol libraryPath handle symbol bind =+ ExceptT $ do+ loaded <- try @IOException (dlsym handle symbol)+ pure $+ first+ (DynamicSymbolLoadFailed libraryPath symbol . displayException)+ (bind <$> loaded)++generateCoordinates+ :: NativeApi+ -> FixtureSpec+ -> IO (Either NativeStatus GeneratedCoordinates)+generateCoordinates api fixture = do+ let pointCount = fixturePointCount fixture+ coordinateCount = 2 * pointCountValue pointCount+ mutableCoordinates <- MutableStorableVector.new coordinateCount+ statusCode <-+ MutableStorableVector.unsafeWith mutableCoordinates $ \coordinatePointer ->+ nativeGenerate api+ (distributionTag (fixturePointDistribution fixture))+ (fromIntegral (pointCountValue pointCount))+ coordinatePointer+ (fromIntegral coordinateCount)+ case decodeNativeStatus statusCode of+ Left status -> pure (Left status)+ Right () -> do+ coordinates <- StorableVector.unsafeFreeze mutableCoordinates+ pure (Right (GeneratedCoordinates pointCount coordinates))++generatedCoordinatePairs :: GeneratedCoordinates -> Vector.Vector (Double, Double)+generatedCoordinatePairs generated =+ Vector.fromList+ ( unfoldr+ takeCoordinatePair+ (realToFrac <$> StorableVector.toList (generatedCoordinateValues generated))+ )+ where+ takeCoordinatePair :: [Double] -> Maybe ((Double, Double), [Double])+ takeCoordinatePair = \case+ x : y : remaining -> Just ((x, y), remaining)+ _ -> Nothing++prepareNativeFixture+ :: NativeApi+ -> NativeImplementation+ -> GeneratedCoordinates+ -> IO (Either NativePreparationFailure NativeFixture)+prepareNativeFixture api implementation coordinates =+ alloca $ \preparedOutput -> do+ poke preparedOutput nullPtr+ statusCode <-+ StorableVector.unsafeWith (generatedCoordinateValues coordinates) $ \coordinatePointer ->+ nativePrepare api+ (implementationTag implementation)+ coordinatePointer+ (fromIntegral (pointCountValue (generatedPointCount coordinates)))+ preparedOutput+ case decodeNativeStatus statusCode of+ Left status -> pure (Left (NativePreparationStatus status))+ Right () -> do+ prepared <- peek preparedOutput+ pure $+ if prepared == nullPtr+ then Left NativePreparationReturnedNull+ else Right (NativeFixture prepared)++runNativeFixture+ :: NativeApi+ -> NativeFixture+ -> IO (Either NativeRunFailure DelaunaySummary)+runNativeFixture api (NativeFixture prepared) =+ alloca $ \vertexCountOutput ->+ alloca $ \triangleCountOutput -> do+ statusCode <- nativeRun api prepared vertexCountOutput triangleCountOutput+ case decodeNativeStatus statusCode of+ Left status -> pure (Left (NativeRunStatus status))+ Right () -> do+ vertexCount <- peek vertexCountOutput+ triangleCount <- peek triangleCountOutput+ pure $ DelaunaySummary <$> cSizeToInt vertexCount <*> cSizeToInt triangleCount++releaseNativeFixture :: NativeApi -> NativeFixture -> IO ()+releaseNativeFixture api (NativeFixture prepared) = nativeRelease api prepared++decodeNativeStatus :: CInt -> Either NativeStatus ()+decodeNativeStatus statusCode =+ case fromIntegral statusCode :: Int32 of+ 0 -> Right ()+ 1 -> Left NativeNullPointer+ 2 -> Left NativeUnknownTag+ 3 -> Left NativeCoordinateCountMismatch+ 4 -> Left NativeDistributionConstructionFailed+ 5 -> Left NativeTriangulationFailed+ 6 -> Left NativePanicked+ unknown -> Left (NativeUnknownStatus (fromIntegral unknown))++cSizeToInt :: CSize -> Either NativeRunFailure Int+cSizeToInt value =+ let integerValue = toInteger value+ in if integerValue > toInteger (maxBound :: Int)+ then Left (NativeCountExceedsHaskellInt integerValue)+ else Right (fromInteger integerValue)++distributionTag :: PointDistribution -> CInt+distributionTag = \case+ LocalInsertion -> 0+ Uniform -> 1++implementationTag :: NativeImplementation -> CInt+implementationTag = \case+ NativeSpade -> 0+ NativeSpadeHierarchy -> 1+ NativeCdt -> 2+ NativeDelaunator -> 3
+ bench/delaunay-compare/Moonlight/Triangulation/Bench/DelaunayCompare/Picture.hs view
@@ -0,0 +1,526 @@+{-# LANGUAGE LambdaCase #-}++-- | Typed receipt parsing and pure Spade-style SVG projection.+module Moonlight.Triangulation.Bench.DelaunayCompare.Picture+ ( BenchmarkReceipt+ , PictureObstruction (..)+ , parseBenchmarkReceipt+ , pictureFileName+ , renderComparisonPicture+ , renderPictureObstruction+ ) where++import Data.List (group, sort, sortOn)+import qualified Data.Map.Strict as Map+import Data.Maybe (mapMaybe)+import qualified Data.Set as Set+import Moonlight.Triangulation.Bench.DelaunayCompare.Domain+import Numeric (showFFloat)+import Text.Read (readMaybe)++data ObservationKey = ObservationKey+ { observationFixture :: !FixtureSpec+ , observationImplementation :: !Implementation+ }+ deriving stock (Eq, Ord, Show)++data BenchmarkObservation = BenchmarkObservation+ { benchmarkKey :: !ObservationKey+ , benchmarkMeanPicoseconds :: !Double+ , benchmarkSpreadPicoseconds :: !Double+ }+ deriving stock (Eq, Show)++newtype BenchmarkReceipt = BenchmarkReceipt+ { receiptObservations :: Map.Map ObservationKey BenchmarkObservation+ }++data PictureObstruction+ = MissingCsvHeader+ | UnexpectedCsvHeader !String+ | MalformedCsvRow !Int !String+ | UnknownBenchmarkName !Int !String+ | InvalidBenchmarkScalar !Int !String !String+ | DuplicateBenchmarkObservations ![ObservationKey]+ | MissingBenchmarkObservations ![ObservationKey]+ deriving stock (Eq, Show)++parseBenchmarkReceipt :: String -> Either PictureObstruction BenchmarkReceipt+parseBenchmarkReceipt source =+ case lines source of+ [] -> Left MissingCsvHeader+ header : rows+ | header /= expectedCsvHeader -> Left (UnexpectedCsvHeader header)+ | otherwise -> do+ observations <- traverse (uncurry parseObservationRow) (zip [2 ..] rows)+ let keys = benchmarkKey <$> observations+ duplicateKeys =+ mapMaybe+ ( \case+ key : _ : _ -> Just key+ _ -> Nothing+ )+ (group (sort keys))+ observationMap = Map.fromList ((\observation -> (benchmarkKey observation, observation)) <$> observations)+ missingKeys = Set.toList (expectedObservationKeys `Set.difference` Map.keysSet observationMap)+ case (duplicateKeys, missingKeys) of+ (duplicate : duplicates, _) -> Left (DuplicateBenchmarkObservations (duplicate : duplicates))+ ([], missing : remaining) -> Left (MissingBenchmarkObservations (missing : remaining))+ ([], []) -> Right (BenchmarkReceipt observationMap)++renderComparisonPicture+ :: SizeBand+ -> BenchmarkReceipt+ -> String+renderComparisonPicture sizeBand receipt =+ renderSvg sizeBand pointCounts axisMaximum panels+ where+ pointCounts = pointCountValue <$> pointCountsFor sizeBand+ panels = picturePanel receipt sizeBand <$> allPointDistributions+ allObservations = concatMap (concatMap pictureSeriesObservations . picturePanelSeries) panels+ maximumMilliseconds =+ foldr+ (max . observationUpperMilliseconds)+ 0+ allObservations+ axisMaximum = niceCeiling maximumMilliseconds++pictureFileName :: SizeBand -> FilePath+pictureFileName = \case+ Small -> "moonlight-delaunay-compare-small.svg"+ Big -> "moonlight-delaunay-compare-big.svg"++renderPictureObstruction :: PictureObstruction -> String+renderPictureObstruction = \case+ MissingCsvHeader -> "benchmark CSV has no header"+ UnexpectedCsvHeader header ->+ "benchmark CSV header was " <> show header <> "; expected " <> show expectedCsvHeader+ MalformedCsvRow lineNumber row ->+ "benchmark CSV row " <> show lineNumber <> " is not a three-column tasty-bench row: " <> show row+ UnknownBenchmarkName lineNumber name ->+ "benchmark CSV row " <> show lineNumber <> " names an unknown comparison case: " <> show name+ InvalidBenchmarkScalar lineNumber field value ->+ "benchmark CSV row "+ <> show lineNumber+ <> " has invalid "+ <> field+ <> ": "+ <> show value+ DuplicateBenchmarkObservations keys ->+ "benchmark CSV repeats comparison cases: " <> show keys+ MissingBenchmarkObservations keys ->+ "benchmark CSV omits comparison cases: " <> show keys++expectedCsvHeader :: String+expectedCsvHeader = "Name,Mean (ps),2*Stdev (ps)"++expectedObservationKeys :: Set.Set ObservationKey+expectedObservationKeys =+ Set.fromList (liftA2 ObservationKey allFixtureSpecs allImplementations)++expectedBenchmarkNames :: Map.Map String ObservationKey+expectedBenchmarkNames =+ Map.fromList ((\key -> (benchmarkName key, key)) <$> Set.toList expectedObservationKeys)++benchmarkName :: ObservationKey -> String+benchmarkName key =+ "All.comparison: creation benchmark ("+ <> sizeBandLabel (fixtureSizeBand fixture)+ <> ")."+ <> implementationLabel (observationImplementation key)+ <> "."+ <> pointDistributionLabel (fixturePointDistribution fixture)+ <> "."+ <> show (pointCountValue (fixturePointCount fixture))+ where+ fixture = observationFixture key++parseObservationRow :: Int -> String -> Either PictureObstruction BenchmarkObservation+parseObservationRow lineNumber row =+ case csvTriple row of+ Nothing -> Left (MalformedCsvRow lineNumber row)+ Just (name, rawMean, rawSpread) -> do+ key <- maybe (Left (UnknownBenchmarkName lineNumber name)) Right (Map.lookup name expectedBenchmarkNames)+ meanPicoseconds <- parseNonNegativeScalar lineNumber "mean picoseconds" rawMean+ spreadPicoseconds <- parseNonNegativeScalar lineNumber "two-standard-deviation spread" rawSpread+ pure+ BenchmarkObservation+ { benchmarkKey = key+ , benchmarkMeanPicoseconds = meanPicoseconds+ , benchmarkSpreadPicoseconds = spreadPicoseconds+ }++csvTriple :: String -> Maybe (String, String, String)+csvTriple row =+ case break (== ',') row of+ (firstColumn, ',' : remaining) ->+ case break (== ',') remaining of+ (secondColumn, ',' : thirdColumn)+ | ',' `notElem` thirdColumn -> Just (firstColumn, secondColumn, thirdColumn)+ _ -> Nothing+ _ -> Nothing++parseNonNegativeScalar :: Int -> String -> String -> Either PictureObstruction Double+parseNonNegativeScalar lineNumber field rawValue =+ case readMaybe rawValue of+ Just value+ | value >= 0 && not (isInfinite value) && not (isNaN value) -> Right value+ _ -> Left (InvalidBenchmarkScalar lineNumber field rawValue)++data PictureSeries = PictureSeries+ { pictureSeriesImplementation :: !Implementation+ , pictureSeriesObservations :: ![BenchmarkObservation]+ }++data PicturePanel = PicturePanel+ { picturePanelDistribution :: !PointDistribution+ , picturePanelSeries :: ![PictureSeries]+ }++picturePanel+ :: BenchmarkReceipt+ -> SizeBand+ -> PointDistribution+ -> PicturePanel+picturePanel receipt sizeBand distribution =+ PicturePanel distribution (pictureSeries receipt sizeBand distribution <$> allImplementations)++pictureSeries+ :: BenchmarkReceipt+ -> SizeBand+ -> PointDistribution+ -> Implementation+ -> PictureSeries+pictureSeries receipt sizeBand distribution implementation =+ PictureSeries implementation observations+ where+ observations =+ sortOn (pointCountValue . fixturePointCount . observationFixture . benchmarkKey)+ [ observation+ | observation <- Map.elems (receiptObservations receipt)+ , let key = benchmarkKey observation+ fixture = observationFixture key+ , fixtureSizeBand fixture == sizeBand+ , fixturePointDistribution fixture == distribution+ , observationImplementation key == implementation+ ]++data PictureLayout = PictureLayout+ { pictureWidth :: !Double+ , pictureHeight :: !Double+ , picturePlotTop :: !Double+ , picturePlotBottom :: !Double+ , picturePanelWidth :: !Double+ , picturePanelLefts :: ![Double]+ }++pictureLayout :: PictureLayout+pictureLayout =+ PictureLayout+ { pictureWidth = 1280+ , pictureHeight = 720+ , picturePlotTop = 168+ , picturePlotBottom = 610+ , picturePanelWidth = 540+ , picturePanelLefts = [70, 670]+ }++renderSvg :: SizeBand -> [Int] -> Double -> [PicturePanel] -> String+renderSvg sizeBand pointCounts axisMaximum panels =+ unlines+ ( [ "<?xml version=\"1.0\" encoding=\"utf-8\"?>"+ , "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 "+ <> coordinate (pictureWidth pictureLayout)+ <> " "+ <> coordinate (pictureHeight pictureLayout)+ <> "\" role=\"img\" aria-labelledby=\"title description\">"+ , "<title id=\"title\">" <> pictureTitle sizeBand <> "</title>"+ , "<desc id=\"description\">CPU construction time for Spade, cdt, delaunator, and Moonlight over the upstream Delaunay comparison fixtures. Lower lines are faster.</desc>"+ , "<defs>"+ , " <pattern id=\"triangulation-mesh\" width=\"54\" height=\"46\" patternUnits=\"userSpaceOnUse\">"+ , " <path d=\"M0 46L27 0L54 46ZM0 46L54 46M27 0L27 46\" fill=\"none\" stroke=\"rgb(255,140,0)\" stroke-width=\"0.7\"/>"+ , " </pattern>"+ , " <clipPath id=\"header-mesh-clip\"><rect x=\"955\" y=\"8\" width=\"290\" height=\"82\" rx=\"20\"/></clipPath>"+ , "</defs>"+ , "<rect width=\""+ <> coordinate (pictureWidth pictureLayout)+ <> "\" height=\""+ <> coordinate (pictureHeight pictureLayout)+ <> "\" fill=\"white\"/>"+ , "<rect x=\"955\" y=\"8\" width=\"290\" height=\"82\" fill=\"url(#triangulation-mesh)\" opacity=\"0.10\" clip-path=\"url(#header-mesh-clip)\"/>"+ , svgText 640 34 "middle" 24 "500" (pictureTitle sizeBand)+ , svgText 640 61 "middle" 13 "400" "Moonlight against Spade's construction referents · CPU time · lower is faster"+ ]+ <> renderLegend+ <> concat (zipWith (renderPanel pointCounts axisMaximum) (picturePanelLefts pictureLayout) panels)+ <> [ svgText 640 704 "middle" 11 "400" "Whiskers show tasty-bench's reported 2× standard-deviation spread."+ , "</svg>"+ ]+ )++pictureTitle :: SizeBand -> String+pictureTitle = \case+ Small -> "Moonlight Delaunay construction · small point sets"+ Big -> "Moonlight Delaunay construction · big point sets"++renderLegend :: [String]+renderLegend =+ concat+ ( zipWith+ renderLegendItem+ [105, 315, 560, 735, 940]+ allImplementations+ )++renderLegendItem :: Double -> Implementation -> [String]+renderLegendItem x implementation =+ [ "<g aria-label=\"" <> implementationLabel implementation <> "\">"+ , " <line x1=\"" <> coordinate x <> "\" y1=\"101\" x2=\"" <> coordinate (x + 34) <> "\" y2=\"101\" stroke=\"" <> implementationColor implementation <> "\" stroke-width=\"" <> seriesStrokeWidth implementation <> "\"" <> seriesDash implementation <> "/>"+ , " " <> renderMarker implementation (x + 17) 101+ , " " <> svgText (x + 43) 105 "start" 12 "400" (implementationLabel implementation)+ , "</g>"+ ]++renderPanel :: [Int] -> Double -> Double -> PicturePanel -> [String]+renderPanel pointCounts axisMaximum panelLeft panel =+ [ "<g aria-label=\"" <> panelLabel <> "\">"+ , svgText (panelLeft + panelWidth / 2) 144 "middle" 15 "500" panelLabel+ ]+ <> concatMap (renderHorizontalGrid panelLeft panelWidth axisMaximum) [0 .. 5]+ <> concatMap (renderVerticalTick panelLeft panelWidth pointCounts) pointCounts+ <> [ "<rect x=\"" <> coordinate panelLeft <> "\" y=\"" <> coordinate plotTop <> "\" width=\"" <> coordinate panelWidth <> "\" height=\"" <> coordinate plotHeight <> "\" fill=\"none\" stroke=\"rgb(25,25,25)\" stroke-width=\"1\"/>"+ , svgText (panelLeft + panelWidth / 2) 664 "middle" 12 "400" "input size (points)"+ , "<text x=\"" <> coordinate (panelLeft - 55) <> "\" y=\"" <> coordinate (plotTop + plotHeight / 2) <> "\" text-anchor=\"middle\" font-family=\"Helvetica, Arial, sans-serif\" font-size=\"12\" fill=\"rgb(20,20,20)\" transform=\"rotate(-90 " <> coordinate (panelLeft - 55) <> " " <> coordinate (plotTop + plotHeight / 2) <> ")\">CPU time (ms)</text>"+ ]+ <> concatMap (renderSeries panelLeft panelWidth pointCounts axisMaximum) (picturePanelSeries panel)+ <> maybe+ []+ (renderAdvantageCallout panelLeft panelWidth pointCounts axisMaximum)+ (bestReportedSpreadAdvantage (picturePanelSeries panel))+ <> ["</g>"]+ where+ panelLabel = pointDistributionLabel (picturePanelDistribution panel)+ panelWidth = picturePanelWidth pictureLayout+ plotTop = picturePlotTop pictureLayout+ plotHeight = picturePlotBottom pictureLayout - plotTop++renderHorizontalGrid :: Double -> Double -> Double -> Int -> [String]+renderHorizontalGrid panelLeft panelWidth axisMaximum tickIndex =+ [ "<line x1=\"" <> coordinate panelLeft <> "\" y1=\"" <> coordinate y <> "\" x2=\"" <> coordinate (panelLeft + panelWidth) <> "\" y2=\"" <> coordinate y <> "\" stroke=\"rgb(170,170,170)\" stroke-width=\"0.7\" stroke-dasharray=\"2 4\"/>"+ , svgText (panelLeft - 10) (y + 4) "end" 11 "400" (axisLabel tickValue)+ ]+ where+ tickValue = axisMaximum * fromIntegral tickIndex / 5+ y = pictureY axisMaximum tickValue++renderVerticalTick :: Double -> Double -> [Int] -> Int -> [String]+renderVerticalTick panelLeft panelWidth pointCounts pointCount =+ [ "<line x1=\"" <> coordinate x <> "\" y1=\"610\" x2=\"" <> coordinate x <> "\" y2=\"616\" stroke=\"rgb(25,25,25)\" stroke-width=\"1\"/>"+ , svgText x 635 "middle" 11 "400" (pointCountLabel pointCount)+ ]+ where+ x = pictureX panelLeft panelWidth pointCounts pointCount++renderSeries :: Double -> Double -> [Int] -> Double -> PictureSeries -> [String]+renderSeries panelLeft panelWidth pointCounts axisMaximum series =+ [ "<g aria-label=\"" <> implementationLabel implementation <> "\">"+ , " <polyline points=\"" <> unwords (observationPoint <$> observations) <> "\" fill=\"none\" stroke=\"" <> implementationColor implementation <> "\" stroke-width=\"" <> seriesStrokeWidth implementation <> "\" stroke-linecap=\"round\" stroke-linejoin=\"round\"" <> seriesDash implementation <> "/>"+ ]+ <> concatMap renderObservation observations+ <> ["</g>"]+ where+ implementation = pictureSeriesImplementation series+ observations = pictureSeriesObservations series+ observationPoint observation =+ coordinate (observationX observation) <> "," <> coordinate (observationY observation)+ observationX =+ pictureX panelLeft panelWidth pointCounts+ . pointCountValue+ . fixturePointCount+ . observationFixture+ . benchmarkKey+ observationY = pictureY axisMaximum . observationMeanMilliseconds+ renderObservation observation =+ [ " <line x1=\"" <> coordinate x <> "\" y1=\"" <> coordinate upperY <> "\" x2=\"" <> coordinate x <> "\" y2=\"" <> coordinate lowerY <> "\" stroke=\"" <> implementationColor implementation <> "\" stroke-width=\"0.9\" opacity=\"0.48\"/>"+ , " <line x1=\"" <> coordinate (x - 3) <> "\" y1=\"" <> coordinate upperY <> "\" x2=\"" <> coordinate (x + 3) <> "\" y2=\"" <> coordinate upperY <> "\" stroke=\"" <> implementationColor implementation <> "\" stroke-width=\"0.9\" opacity=\"0.48\"/>"+ , " <line x1=\"" <> coordinate (x - 3) <> "\" y1=\"" <> coordinate lowerY <> "\" x2=\"" <> coordinate (x + 3) <> "\" y2=\"" <> coordinate lowerY <> "\" stroke=\"" <> implementationColor implementation <> "\" stroke-width=\"0.9\" opacity=\"0.48\"/>"+ , " " <> renderMarker implementation x y+ ]+ where+ x = observationX observation+ y = observationY observation+ upperY = pictureY axisMaximum (observationUpperMilliseconds observation)+ lowerY = pictureY axisMaximum (observationLowerMilliseconds observation)++data ReportedSpreadAdvantage = ReportedSpreadAdvantage+ { advantagePointCount :: !Int+ , advantageMoonlight :: !BenchmarkObservation+ , advantageCompetitor :: !Implementation+ , advantageReduction :: !Double+ }++bestReportedSpreadAdvantage :: [PictureSeries] -> Maybe ReportedSpreadAdvantage+bestReportedSpreadAdvantage series = foldr selectLargerReduction Nothing reportedSpreadAdvantages+ where+ moonlightObservations =+ concatMap pictureSeriesObservations (filter ((== Moonlight) . pictureSeriesImplementation) series)+ spadeSeries =+ filter+ ((`elem` [Spade, SpadeHierarchy]) . pictureSeriesImplementation)+ series+ reportedSpreadAdvantages =+ [ ReportedSpreadAdvantage+ { advantagePointCount = pointCountValue (fixturePointCount moonlightFixture)+ , advantageMoonlight = moonlight+ , advantageCompetitor = pictureSeriesImplementation competitorSeries+ , advantageReduction = 1 - benchmarkMeanPicoseconds moonlight / benchmarkMeanPicoseconds competitor+ }+ | moonlight <- moonlightObservations+ , let moonlightFixture = observationFixture (benchmarkKey moonlight)+ , competitorSeries <- spadeSeries+ , competitor <- pictureSeriesObservations competitorSeries+ , fixturePointCount (observationFixture (benchmarkKey competitor)) == fixturePointCount moonlightFixture+ , benchmarkMeanPicoseconds moonlight + benchmarkSpreadPicoseconds moonlight+ < benchmarkMeanPicoseconds competitor - benchmarkSpreadPicoseconds competitor+ ]+ selectLargerReduction candidate = \case+ Nothing -> Just candidate+ Just incumbent ->+ Just+ ( if advantageReduction candidate > advantageReduction incumbent+ then candidate+ else incumbent+ )++renderAdvantageCallout :: Double -> Double -> [Int] -> Double -> ReportedSpreadAdvantage -> [String]+renderAdvantageCallout panelLeft panelWidth pointCounts axisMaximum advantage =+ [ "<g aria-label=\"Moonlight comparison highlight\">"+ , " <line x1=\"" <> coordinate pointX <> "\" y1=\"" <> coordinate pointY <> "\" x2=\"" <> coordinate (labelX + 8) <> "\" y2=\"" <> coordinate (labelY + 18) <> "\" stroke=\"rgb(255,140,0)\" stroke-width=\"1.2\"/>"+ , " <rect x=\"" <> coordinate labelX <> "\" y=\"" <> coordinate labelY <> "\" width=\"250\" height=\"38\" rx=\"6\" fill=\"white\" stroke=\"rgb(255,140,0)\" stroke-width=\"1.2\"/>"+ , " " <> svgText (labelX + 10) (labelY + 16) "start" 11 "500" (show reductionPercent <> "% less time than " <> implementationLabel (advantageCompetitor advantage))+ , " " <> svgText (labelX + 10) (labelY + 31) "start" 10 "400" (pointCountLabel (advantagePointCount advantage) <> " points · reported spreads do not overlap")+ , "</g>"+ ]+ where+ pointX = pictureX panelLeft panelWidth pointCounts (advantagePointCount advantage)+ pointY = pictureY axisMaximum (observationMeanMilliseconds (advantageMoonlight advantage))+ labelX = min (panelLeft + panelWidth - 258) (pointX + 28)+ labelY = max (picturePlotTop pictureLayout + 12) (pointY - 58)+ reductionPercent = round (100 * advantageReduction advantage) :: Int++implementationColor :: Implementation -> String+implementationColor = \case+ Spade -> "rgb(0,0,139)"+ SpadeHierarchy -> "rgb(139,0,139)"+ Cdt -> "rgb(178,34,34)"+ Delaunator -> "rgb(0,139,139)"+ Moonlight -> "rgb(255,140,0)"++seriesStrokeWidth :: Implementation -> String+seriesStrokeWidth = \case+ Moonlight -> "3.2"+ _ -> "2.0"++seriesDash :: Implementation -> String+seriesDash = \case+ SpadeHierarchy -> " stroke-dasharray=\"7 4\""+ _ -> ""++renderMarker :: Implementation -> Double -> Double -> String+renderMarker implementation x y =+ case implementation of+ Spade -> circle 3.2+ SpadeHierarchy ->+ "<path d=\"M " <> coordinate x <> " " <> coordinate (y - 4.3) <> " L " <> coordinate (x - 4.1) <> " " <> coordinate (y + 3.5) <> " L " <> coordinate (x + 4.1) <> " " <> coordinate (y + 3.5) <> " Z\" fill=\"white\" stroke=\"" <> color <> "\" stroke-width=\"1.6\"/>"+ Cdt ->+ "<rect x=\"" <> coordinate (x - 3.2) <> "\" y=\"" <> coordinate (y - 3.2) <> "\" width=\"6.4\" height=\"6.4\" fill=\"white\" stroke=\"" <> color <> "\" stroke-width=\"1.6\"/>"+ Delaunator ->+ "<path d=\"M " <> coordinate x <> " " <> coordinate (y - 4.2) <> " L " <> coordinate (x + 4.2) <> " " <> coordinate y <> " L " <> coordinate x <> " " <> coordinate (y + 4.2) <> " L " <> coordinate (x - 4.2) <> " " <> coordinate y <> " Z\" fill=\"white\" stroke=\"" <> color <> "\" stroke-width=\"1.6\"/>"+ Moonlight ->+ "<path d=\"M " <> coordinate x <> " " <> coordinate (y - 5.4) <> " L " <> coordinate (x + 1.7) <> " " <> coordinate (y - 1.7) <> " L " <> coordinate (x + 5.4) <> " " <> coordinate y <> " L " <> coordinate (x + 1.7) <> " " <> coordinate (y + 1.7) <> " L " <> coordinate x <> " " <> coordinate (y + 5.4) <> " L " <> coordinate (x - 1.7) <> " " <> coordinate (y + 1.7) <> " L " <> coordinate (x - 5.4) <> " " <> coordinate y <> " L " <> coordinate (x - 1.7) <> " " <> coordinate (y - 1.7) <> " Z\" fill=\"" <> color <> "\" stroke=\"white\" stroke-width=\"1\"/>"+ where+ color = implementationColor implementation+ circle radius =+ "<circle cx=\"" <> coordinate x <> "\" cy=\"" <> coordinate y <> "\" r=\"" <> coordinate radius <> "\" fill=\"white\" stroke=\"" <> color <> "\" stroke-width=\"1.6\"/>"++pictureX :: Double -> Double -> [Int] -> Int -> Double+pictureX panelLeft panelWidth pointCounts pointCount =+ case pointCounts of+ [] -> panelLeft+ firstCount : remainingCounts ->+ let minimumCount = firstCount+ maximumCount = foldr max firstCount remainingCounts+ countRange = maximumCount - minimumCount+ in if countRange == 0+ then panelLeft + panelWidth / 2+ else+ panelLeft+ + panelWidth+ * fromIntegral (pointCount - minimumCount)+ / fromIntegral countRange++pictureY :: Double -> Double -> Double+pictureY axisMaximum milliseconds =+ picturePlotBottom pictureLayout+ - plotHeight * min axisMaximum (max 0 milliseconds) / axisMaximum+ where+ plotHeight = picturePlotBottom pictureLayout - picturePlotTop pictureLayout++observationMeanMilliseconds :: BenchmarkObservation -> Double+observationMeanMilliseconds = picosecondsToMilliseconds . benchmarkMeanPicoseconds++observationUpperMilliseconds :: BenchmarkObservation -> Double+observationUpperMilliseconds observation =+ picosecondsToMilliseconds+ (benchmarkMeanPicoseconds observation + benchmarkSpreadPicoseconds observation)++observationLowerMilliseconds :: BenchmarkObservation -> Double+observationLowerMilliseconds observation =+ picosecondsToMilliseconds+ (max 0 (benchmarkMeanPicoseconds observation - benchmarkSpreadPicoseconds observation))++picosecondsToMilliseconds :: Double -> Double+picosecondsToMilliseconds picoseconds = picoseconds / 1.0e9++niceCeiling :: Double -> Double+niceCeiling value+ | value <= 0 = 1+ | otherwise = niceFraction * magnitude+ where+ magnitude = 10 ** fromIntegral (floor (logBase 10 value) :: Int)+ fraction = value / magnitude+ niceFraction+ | fraction <= 1 = 1+ | fraction <= 2 = 2+ | fraction <= 5 = 5+ | otherwise = 10++axisLabel :: Double -> String+axisLabel value+ | value >= 10 = showFFloat (Just 0) value ""+ | value >= 1 = showFFloat (Just 1) value ""+ | otherwise = showFFloat (Just 2) value ""++pointCountLabel :: Int -> String+pointCountLabel pointCount+ | pointCount `mod` 1000 == 0 = show (pointCount `div` 1000) <> "k"+ | otherwise = show pointCount++coordinate :: Double -> String+coordinate value = showFFloat (Just 1) value ""++svgText :: Double -> Double -> String -> Int -> String -> String -> String+svgText x y anchor fontSize fontWeight content =+ "<text x=\""+ <> coordinate x+ <> "\" y=\""+ <> coordinate y+ <> "\" text-anchor=\""+ <> anchor+ <> "\" font-family=\"Helvetica, Arial, sans-serif\" font-size=\""+ <> show fontSize+ <> "\" font-weight=\""+ <> fontWeight+ <> "\" fill=\"rgb(20,20,20)\">"+ <> content+ <> "</text>"
+ bench/delaunay-compare/Moonlight/Triangulation/Bench/DelaunayCompare/Suite.hs view
@@ -0,0 +1,260 @@+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE DerivingStrategies #-}++-- | Fixture descent, compatibility gluing, and the authoritative benchmark view.+module Moonlight.Triangulation.Bench.DelaunayCompare.Suite+ ( PreparedSuite+ , withPreparedSuite+ , preflightSuite+ , suiteBenchmarks+ , suiteAgreementMessage+ ) where++import Control.DeepSeq (force)+import Control.Exception (bracket, evaluate)+import Data.Bifunctor (first)+import Data.Either (lefts, rights)+import Data.Foldable (toList, traverse_)+import qualified Data.List.NonEmpty as NonEmpty+import qualified Data.Vector as Vector+import Moonlight.Triangulation.Bench.DelaunayCompare.Domain+import Moonlight.Triangulation.Bench.DelaunayCompare.Native+import Moonlight.Triangulation.BulkLoad (delaunayGeometry)+import Moonlight.Triangulation.Dcel (numInnerFaces, numVertices)+import Moonlight.Triangulation.Types (BuildError, Point (Point))+import Test.Tasty.Bench (Benchmark, bench, bgroup, nf, nfIO)++data NativeSections value = NativeSections+ { nativeSpadeSection :: !value+ , nativeSpadeHierarchySection :: !value+ , nativeCdtSection :: !value+ , nativeDelaunatorSection :: !value+ }+ deriving stock (Functor, Foldable, Traversable)++nativeImplementationSections :: NativeSections NativeImplementation+nativeImplementationSections =+ NativeSections+ { nativeSpadeSection = NativeSpade+ , nativeSpadeHierarchySection = NativeSpadeHierarchy+ , nativeCdtSection = NativeCdt+ , nativeDelaunatorSection = NativeDelaunator+ }++nativeSectionAt :: NativeImplementation -> NativeSections value -> value+nativeSectionAt implementation sections =+ case implementation of+ NativeSpade -> nativeSpadeSection sections+ NativeSpadeHierarchy -> nativeSpadeHierarchySection sections+ NativeCdt -> nativeCdtSection sections+ NativeDelaunator -> nativeDelaunatorSection sections++data PreparedFixture = PreparedFixture+ { preparedSpec :: !FixtureSpec+ , preparedMoonlightPoints :: !(Vector.Vector Point)+ , preparedNativeFixtures :: !(NativeSections NativeFixture)+ }++newtype PreparedSuite = PreparedSuite [PreparedFixture]++withPreparedSuite+ :: NativeApi+ -> (PreparedSuite -> IO (Either CompareObstruction result))+ -> IO (Either CompareObstruction result)+withPreparedSuite api action = do+ prepared <- prepareSuite api+ case prepared of+ Left obstruction -> pure (Left obstruction)+ Right suite -> bracket (pure suite) (releaseSuite api) action++prepareSuite :: NativeApi -> IO (Either CompareObstruction PreparedSuite)+prepareSuite api = do+ outcomes <- traverse (prepareFixture api) allFixtureSpecs+ case NonEmpty.nonEmpty (lefts outcomes) of+ Just obstructions -> do+ traverse_ (releasePreparedFixture api) (rights outcomes)+ pure (Left (ComparisonObstructions obstructions))+ Nothing -> pure (Right (PreparedSuite (rights outcomes)))++prepareFixture :: NativeApi -> FixtureSpec -> IO (Either CompareObstruction PreparedFixture)+prepareFixture api fixture = do+ generated <- generateCoordinates api fixture+ case generated of+ Left status -> pure (Left (FixtureGenerationFailed fixture status))+ Right coordinates -> do+ nativeFixtures <- prepareNativeFixtures api fixture coordinates+ pure $+ PreparedFixture+ fixture+ (Vector.map (uncurry Point) (generatedCoordinatePairs coordinates))+ <$> nativeFixtures++prepareNativeFixtures+ :: NativeApi+ -> FixtureSpec+ -> GeneratedCoordinates+ -> IO (Either CompareObstruction (NativeSections NativeFixture))+prepareNativeFixtures api fixture coordinates = do+ outcomes <- traverse prepareSection nativeImplementationSections+ case sequenceA outcomes of+ Right nativeFixtures -> pure (Right nativeFixtures)+ Left obstruction -> do+ traverse_ (releaseNativeFixture api) (rights (toList outcomes))+ pure (Left obstruction)+ where+ prepareSection implementation = do+ prepared <- prepareNativeFixture api implementation coordinates+ pure $+ first+ (preparationObstruction implementation)+ prepared++ preparationObstruction implementation failure =+ case failure of+ NativePreparationStatus status -> FixturePreparationFailed fixture implementation status+ NativePreparationReturnedNull -> NativePreparedNull fixture implementation++releaseSuite :: NativeApi -> PreparedSuite -> IO ()+releaseSuite api (PreparedSuite fixtures) =+ traverse_ (releasePreparedFixture api) fixtures++releasePreparedFixture :: NativeApi -> PreparedFixture -> IO ()+releasePreparedFixture api = releasePreparedNativeFixtures api . preparedNativeFixtures++releasePreparedNativeFixtures :: NativeApi -> NativeSections NativeFixture -> IO ()+releasePreparedNativeFixtures api = traverse_ (releaseNativeFixture api)++preflightSuite :: NativeApi -> PreparedSuite -> IO (Either CompareObstruction ())+preflightSuite api (PreparedSuite fixtures) = do+ outcomes <- traverse (preflightFixture api) fixtures+ pure $+ case NonEmpty.nonEmpty (lefts outcomes) of+ Just obstructions -> Left (ComparisonObstructions obstructions)+ Nothing -> Right ()++preflightFixture :: NativeApi -> PreparedFixture -> IO (Either CompareObstruction ())+preflightFixture api fixture = do+ moonlightResult <-+ evaluate+ ( force+ (moonlightConstructionSummary (preparedMoonlightPoints fixture))+ )+ nativeOutcomes <- traverse runSection nativeImplementationSections+ pure $ do+ moonlightSummary <-+ first+ (MoonlightConstructionFailed spec)+ moonlightResult+ nativeSummaries <- sequenceA nativeOutcomes+ traverse_+ ( \implementation ->+ requireSummaryAgreement+ spec+ moonlightSummary+ ( implementationOfNative implementation+ , nativeSectionAt implementation nativeSummaries+ )+ )+ nativeImplementationSections+ where+ spec = preparedSpec fixture+ nativeFixtures = preparedNativeFixtures fixture+ runSection implementation =+ first (NativeConstructionFailed spec implementation)+ <$> runNativeFixture api (nativeSectionAt implementation nativeFixtures)++requireSummaryAgreement+ :: FixtureSpec+ -> DelaunaySummary+ -> (Implementation, DelaunaySummary)+ -> Either CompareObstruction ()+requireSummaryAgreement fixture expected (implementation, observed) =+ if observed == expected+ then Right ()+ else Left (SummaryDisagreement fixture implementation expected observed)++moonlightConstructionSummary+ :: Vector.Vector Point+ -> Either BuildError DelaunaySummary+moonlightConstructionSummary points = do+ triangulation <- delaunayGeometry points+ pure+ DelaunaySummary+ { summaryVertexCount = numVertices triangulation+ , summaryTriangleCount = numInnerFaces triangulation+ }++suiteBenchmarks :: NativeApi -> PreparedSuite -> [Benchmark]+suiteBenchmarks api (PreparedSuite fixtures) =+ fmap (sizeBandBenchmarks api fixtures) allSizeBands++sizeBandBenchmarks+ :: NativeApi+ -> [PreparedFixture]+ -> SizeBand+ -> Benchmark+sizeBandBenchmarks api fixtures sizeBand =+ bgroup+ ("comparison: creation benchmark (" <> sizeBandLabel sizeBand <> ")")+ (fmap (implementationBenchmarks api fixtures sizeBand) allImplementations)++implementationBenchmarks+ :: NativeApi+ -> [PreparedFixture]+ -> SizeBand+ -> Implementation+ -> Benchmark+implementationBenchmarks api fixtures sizeBand implementation =+ bgroup+ (implementationLabel implementation)+ ( fmap+ (distributionBenchmarks api fixtures sizeBand implementation)+ allPointDistributions+ )++distributionBenchmarks+ :: NativeApi+ -> [PreparedFixture]+ -> SizeBand+ -> Implementation+ -> PointDistribution+ -> Benchmark+distributionBenchmarks api fixtures sizeBand implementation distribution =+ bgroup+ (pointDistributionLabel distribution)+ ( fmap+ (fixtureBenchmark api implementation)+ ( filter+ (matchesFixture sizeBand distribution . preparedSpec)+ fixtures+ )+ )++matchesFixture :: SizeBand -> PointDistribution -> FixtureSpec -> Bool+matchesFixture sizeBand distribution fixture =+ fixtureSizeBand fixture == sizeBand+ && fixturePointDistribution fixture == distribution++fixtureBenchmark :: NativeApi -> Implementation -> PreparedFixture -> Benchmark+fixtureBenchmark api implementation fixture =+ bench (show (pointCountValue (fixturePointCount (preparedSpec fixture)))) $+ case implementation of+ Moonlight -> nf moonlightConstructionSummary (preparedMoonlightPoints fixture)+ Spade -> nativeBenchmark NativeSpade+ SpadeHierarchy -> nativeBenchmark NativeSpadeHierarchy+ Cdt -> nativeBenchmark NativeCdt+ Delaunator -> nativeBenchmark NativeDelaunator+ where+ nativeFixtures = preparedNativeFixtures fixture+ nativeBenchmark nativeImplementation =+ nfIO (runNativeFixture api (nativeSectionAt nativeImplementation nativeFixtures))++suiteAgreementMessage :: String+suiteAgreementMessage =+ "delaunay-compare agreement: "+ <> show (length allFixtureSpecs)+ <> " fixtures agree on vertex and triangle counts across "+ <> show (length allImplementations)+ <> " implementations"
+ bench/delaunay-compare/PictureMain.hs view
@@ -0,0 +1,75 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE TypeApplications #-}++-- | Thin effect boundary for rendering benchmark receipts as SVG pictures.+module Main (main) where++import Control.Exception (IOException, displayException, try)+import Control.Monad.Trans.Except (ExceptT (ExceptT), except, runExceptT)+import Data.Bifunctor (first)+import Data.Foldable (traverse_)+import Moonlight.Triangulation.Bench.DelaunayCompare.Domain (allSizeBands)+import Moonlight.Triangulation.Bench.DelaunayCompare.Picture+import System.Directory (createDirectoryIfMissing)+import System.Environment (getArgs)+import System.Exit (die)+import System.FilePath ((</>))+import System.IO (readFile')++data PictureCommandFailure+ = PictureCommandUsage+ | PictureCommandReadFailed !FilePath !String+ | PictureCommandCreateDirectoryFailed !FilePath !String+ | PictureCommandReceiptObstructed !PictureObstruction+ | PictureCommandWriteFailed !FilePath !String++main :: IO ()+main = do+ arguments <- getArgs+ outcome <- runExceptT (runPictureCommand arguments)+ either (die . renderPictureCommandFailure) pure outcome++runPictureCommand :: [String] -> ExceptT PictureCommandFailure IO ()+runPictureCommand = \case+ [inputCsv, outputDirectory] -> do+ source <-+ ExceptT+ ( first+ (PictureCommandReadFailed inputCsv . displayException)+ <$> try @IOException (readFile' inputCsv)+ )+ receipt <- except (first PictureCommandReceiptObstructed (parseBenchmarkReceipt source))+ let pictures =+ ( \sizeBand ->+ (pictureFileName sizeBand, renderComparisonPicture sizeBand receipt)+ )+ <$> allSizeBands+ ExceptT+ ( first+ (PictureCommandCreateDirectoryFailed outputDirectory . displayException)+ <$> try @IOException (createDirectoryIfMissing True outputDirectory)+ )+ traverse_ (writePicture outputDirectory) pictures+ _ -> except (Left PictureCommandUsage)++writePicture :: FilePath -> (FilePath, String) -> ExceptT PictureCommandFailure IO ()+writePicture outputDirectory (fileName, svgDocument) =+ let outputPath = outputDirectory </> fileName+ in ExceptT+ ( first+ (PictureCommandWriteFailed outputPath . displayException)+ <$> try @IOException (writeFile outputPath svgDocument)+ )++renderPictureCommandFailure :: PictureCommandFailure -> String+renderPictureCommandFailure = \case+ PictureCommandUsage ->+ "usage: moonlight-triangulation-delaunay-pictures INPUT.csv OUTPUT_DIRECTORY"+ PictureCommandReadFailed path details ->+ "could not read benchmark CSV " <> path <> ": " <> details+ PictureCommandCreateDirectoryFailed path details ->+ "could not create picture directory " <> path <> ": " <> details+ PictureCommandReceiptObstructed obstruction ->+ renderPictureObstruction obstruction+ PictureCommandWriteFailed path details ->+ "could not write comparison picture " <> path <> ": " <> details
+ bench/delaunay-compare/README.md view
@@ -0,0 +1,183 @@+# Moonlight Delaunay Compare++`moonlight-triangulation-delaunay-compare` is Moonlight's package-owned port of Spade's+[`delaunay_compare`](https://github.com/Stoeoef/spade/tree/c8befc96bbbc1898a89cb19f9f3104a848936374/delaunay_compare)+construction suite. It retains the upstream case matrix and appends+`moonlight-triangulation` to the implementation list:++1. `spade 2`+2. `spade 2 hierarchy`+3. `cdt`+4. `delaunator`+5. `moonlight-triangulation`++Haskell owns fixture selection, grouping, compatibility descent, timing, and+reporting. The Rust library under `rust/src/lib.rs` is only a typed foreign+boundary to the four Rust implementations and to the exact upstream `rand`+fixture stream; it contains no benchmark policy.++This executable is the construction slice, not the package's overall+comparison. The [package benchmark overview](../../README.md#overall-moonlightspade-comparison)+also exposes insertion, removal, nearest-neighbour search, CDT recovery,+refinement, natural-neighbour interpolation, Voronoi, DCEL, and segment+traversal lanes.++## Pictures++Both pictures are derived from the single retained board.++++++These retain Spade's plain GNUPLOT idiom: white field, Helvetica, dashed grey+grid, jewel-coloured point series, and the same small/big split. Moonlight is+the heavier orange star. The faint triangulation mesh is the sole bit of+levity; benchmark charts need not resemble tax forms.++## Result++- timed cases: `120`+- fixture summaries agreeing across all five implementations: `24 / 24`+- Moonlight mean below plain Spade: `12 / 24`+- Moonlight mean below Spade hierarchy: `17 / 24`+- Moonlight below plain Spade with non-overlapping reported 2σ: `0 / 24`+- Moonlight below Spade hierarchy with non-overlapping reported 2σ: `1 / 24`+- lowest fixture mean: `delaunator` `23 / 24`; `cdt` `1 / 24`+- median reported 2σ / mean: Moonlight `7.86%`; plain Spade `6.74%`++Selected means are in milliseconds. Δ is+`100 × (Moonlight / competitor − 1)`.++| fixture | points | Moonlight | plain Spade | Δ | Spade hierarchy | Δ |+|---|---:|---:|---:|---:|---:|---:|+| local insertion | 4,000 | 0.663 | 0.748 | −11.3% | 0.784 | −15.4% |+| local insertion | 6,000 | 1.121 | 1.245 | −10.0% | 1.320 | −15.1% |+| local insertion | 8,000 | 1.552 | 1.736 | −10.6% | 1.824 | −14.9% |+| local insertion | 250,000 | 68.017 | 71.675 | −5.1% | 76.208 | −10.7% |+| uniform | 4,000 | 0.676 | 0.693 | −2.4% | 0.729 | −7.3% |+| uniform | 14,000 | 3.591 | 2.959 | +21.4% | 3.171 | +13.3% |+| uniform | 250,000 | 73.329 | 72.076 | +1.7% | 76.447 | −4.1% |++The measured Moonlight path is the canonical circle-sweep bulk loader. Its+local descent carries an already-read hull angle through candidate search,+rejects incompatible closure sections before paying for exact orientation,+and compiles the exact predicate and repair kernels at their LLVM `-O3`+boundary.++Receipt identity:++- command completed: `2026-08-17T10:39:30Z`, 297.39 s over 120 cases+- host: Apple M4 Pro, arm64, macOS 26.5.2, GHC 9.14.1, rustc 1.92.0+- Haskell build: Cabal `-O1` package profile, comparison executable `-O2`, hot+ predicate, sweep, and repair modules LLVM `-O3`+- checkout HEAD: `6da6feb1a6`, with seven modified files in the tree, none of+ them on the measured path+- CSV: [`moonlight-delaunay-compare-2026-08-17.csv`](results/moonlight-delaunay-compare-2026-08-17.csv), SHA-256+ `eba77eb960790f2e1cbe3f82c112e8690721de4e5aa3c69961925670e0822162`+- benchmark runtime-source hashes:+ [`moonlight-delaunay-compare-2026-08-17.source-sha256`](results/moonlight-delaunay-compare-2026-08-17.source-sha256)++The Haskell projection in+`Moonlight.Triangulation.Bench.DelaunayCompare.Picture` parses the tasty-bench+CSV against the exact closed 120-case registry and reports typed obstructions+for malformed, unknown, duplicated, or missing observations before gluing+either picture.++## Run++From the repository root, list the complete benchmark tree:++```console+scripts/safe-cabal.sh run \+ moonlight-triangulation:exe:moonlight-triangulation-delaunay-compare \+ -- --list-tests+```++Run the comparison on one uncontended thread with wall-clock timing:++```console+scripts/safe-cabal.sh run \+ moonlight-triangulation:exe:moonlight-triangulation-delaunay-compare \+ -- --time-mode wall -j1+```++The Haskell executable builds the pinned Rust library through Cargo before it+loads the library. Cargo's incremental no-op is cheap after the first run. Set+`MOONLIGHT_DELAUNAY_COMPARE_RUST_MANIFEST` only when invoking an installed executable+outside this repository layout.++## Reproduce a board++Every candidate run uses one CPU-time worker, a 30-second per-case timeout, and+tasty-bench's `--stdev 5` calibration target. Name the candidate by the date it+completed; after validation, it replaces the retained CSV and source manifest+rather than accumulating a benchmark diary.++```console+caffeinate -i scripts/safe-cabal.sh run \+ moonlight-triangulation:exe:moonlight-triangulation-delaunay-compare -- \+ --stdev 5 --timeout 30s --time-mode cpu -j1 \+ --csv foundation/moonlight-triangulation/bench/delaunay-compare/results/moonlight-delaunay-compare-YYYY-MM-DD.csv \+ --color never --hide-progress --min-duration-to-report 1h+```++Both pictures are then regenerated from the candidate CSV, which is the only+CSV they may be derived from:++```console+scripts/safe-cabal.sh run \+ moonlight-triangulation:exe:moonlight-triangulation-delaunay-pictures -- \+ foundation/moonlight-triangulation/bench/delaunay-compare/results/moonlight-delaunay-compare-YYYY-MM-DD.csv \+ foundation/moonlight-triangulation/bench/delaunay-compare/results+```++The runtime-source manifest beside the CSV is the list of files that entered+timed actions, hashed at run time; picture sources and package-only metadata+are excluded because they do not. The receipt and SVGs stay package-owned+beside the benchmark but are not Cabal package inputs, because taking a+measurement must not rebuild the triangulation library.++## Upstream-compatible fixtures++Both distributions use the 32-byte `StdRng` seed from upstream, including its+embedded newline byte. Rust unit tests pin the first three points of both+streams by their exact binary64 bit patterns.++- `local insertion` starts at `(0, 1)` and adds independent inclusive steps+ from `[-1, 1]`.+- `uniform` draws each coordinate independently and inclusively from+ `[-1e9, 1e9]`.+- `small` contains 2,000 through 14,000 points in increments of 2,000.+- `big` contains 50,000 through 250,000 points in increments of 50,000.++Native input conversion happens once during suite preparation, outside every+timed action, exactly as upstream's `DelaunayCrate.init` separates conversion+from `run_creation`. Spade still clones its owned vertex vector inside each+construction call because that is what its bulk-load API and upstream adapter+require; `cdt`, `delaunator`, and Moonlight consume their prepared vectors by+reference.++## Compatibility gate++Before `tasty-bench` runs, every one of the 24 fixtures descends across all five+implementations. The sections glue only when vertex and inner-triangle counts+agree. A generator, preparation, construction, foreign-boundary, or summary+failure is a typed obstruction and terminates the command before timing.++This count gate is deliberately not a second topology authority. The stronger+Moonlight-versus-Spade canonical-edge and operation agreement owner is the+[`spade-compare` operation suite](../spade-compare/README.md); use it when making semantic parity claims. This command answers+the narrower upstream question: bulk construction time over the upstream point+distributions and sizes.++The upstream `examples/real_data_benchmark.rs` CDT/shapefile program is a+separate executable and is not folded into this creation suite. No 45 MB dataset+or network fetch is concealed in benchmark startup.++## Harness difference++Upstream uses Rust Criterion. This port uses Haskell `tasty-bench`, so it does+not pretend Criterion's warm-up seconds or sample-count knobs map one-to-one to+another calibrator. The inputs, sizes, implementation calls, and timed/setup+boundary are preserved; calibration and reporting are honestly Haskell.
+ bench/delaunay-compare/rust/Cargo.lock view
@@ -0,0 +1,264 @@+# This file is automatically @generated by Cargo.+# It is not intended for manual editing.+version = 4++[[package]]+name = "allocator-api2"+version = "0.2.21"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"++[[package]]+name = "autocfg"+version = "1.5.1"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"++[[package]]+name = "cdt"+version = "0.1.0"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "b91b872294f63ef586b32aa94141561681aa35ca2d703960cca4f661f4e18184"+dependencies = [+ "geometry-predicates",+ "thiserror",+]++[[package]]+name = "cfg-if"+version = "1.0.4"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"++[[package]]+name = "delaunator"+version = "1.1.0"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "3e1ee323c1275374f7e612d3724d12707079fb6a2117349fe144def656f5a880"+dependencies = [+ "robust",+]++[[package]]+name = "equivalent"+version = "1.0.2"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"++[[package]]+name = "foldhash"+version = "0.2.0"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"++[[package]]+name = "geometry-predicates"+version = "0.3.0"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "8dba84198cf199c11b83f1cb9243eaeb70dc50b719d2835ebf34bf2481bca010"++[[package]]+name = "getrandom"+version = "0.3.4"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"+dependencies = [+ "cfg-if",+ "libc",+ "r-efi",+ "wasip2",+]++[[package]]+name = "hashbrown"+version = "0.16.1"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"+dependencies = [+ "allocator-api2",+ "equivalent",+ "foldhash",+]++[[package]]+name = "libc"+version = "0.2.189"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"++[[package]]+name = "moonlight-delaunay-compare-referents"+version = "0.1.0"+dependencies = [+ "cdt",+ "delaunator",+ "rand",+ "spade",+]++[[package]]+name = "num-traits"+version = "0.2.19"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"+dependencies = [+ "autocfg",+]++[[package]]+name = "ppv-lite86"+version = "0.2.21"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"+dependencies = [+ "zerocopy",+]++[[package]]+name = "proc-macro2"+version = "1.0.107"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"+dependencies = [+ "unicode-ident",+]++[[package]]+name = "quote"+version = "1.0.47"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"+dependencies = [+ "proc-macro2",+]++[[package]]+name = "r-efi"+version = "5.3.0"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"++[[package]]+name = "rand"+version = "0.9.5"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41"+dependencies = [+ "rand_chacha",+ "rand_core",+]++[[package]]+name = "rand_chacha"+version = "0.9.0"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"+dependencies = [+ "ppv-lite86",+ "rand_core",+]++[[package]]+name = "rand_core"+version = "0.9.5"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"+dependencies = [+ "getrandom",+]++[[package]]+name = "robust"+version = "1.2.0"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "4e27ee8bb91ca0adcf0ecb116293afa12d393f9c2b9b9cd54d33e8078fe19839"++[[package]]+name = "smallvec"+version = "1.15.2"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"++[[package]]+name = "spade"+version = "2.15.1"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "9699399fd9349b00b184f5635b074f9ec93afffef30c853f8c875b32c0f8c7fa"+dependencies = [+ "hashbrown",+ "num-traits",+ "robust",+ "smallvec",+]++[[package]]+name = "syn"+version = "2.0.119"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"+dependencies = [+ "proc-macro2",+ "quote",+ "unicode-ident",+]++[[package]]+name = "thiserror"+version = "1.0.69"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"+dependencies = [+ "thiserror-impl",+]++[[package]]+name = "thiserror-impl"+version = "1.0.69"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"+dependencies = [+ "proc-macro2",+ "quote",+ "syn",+]++[[package]]+name = "unicode-ident"+version = "1.0.24"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"++[[package]]+name = "wasip2"+version = "1.0.4+wasi-0.2.12"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487"+dependencies = [+ "wit-bindgen",+]++[[package]]+name = "wit-bindgen"+version = "0.57.1"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"++[[package]]+name = "zerocopy"+version = "0.8.56"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb"+dependencies = [+ "zerocopy-derive",+]++[[package]]+name = "zerocopy-derive"+version = "0.8.56"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1"+dependencies = [+ "proc-macro2",+ "quote",+ "syn",+]
+ bench/delaunay-compare/rust/Cargo.toml view
@@ -0,0 +1,20 @@+[workspace]++[package]+name = "moonlight-delaunay-compare-referents"+version = "0.1.0"+edition = "2021"++[lib]+name = "moonlight_delaunay_compare_referents"+crate-type = ["cdylib"]++[dependencies]+cdt = "0.1"+delaunator = "1.0.1"+rand = "0.9.1"+spade = "2.15"++[profile.release]+opt-level = 3+lto = "thin"
+ bench/delaunay-compare/rust/src/lib.rs view
@@ -0,0 +1,397 @@+//! Rust referent boundary for Moonlight's Delaunay construction comparison.+//!+//! Fixture choice, benchmark grouping, agreement, timing, and reporting belong+//! to Haskell. This library retains only the operations that cannot move there:+//! Spade's exact seeded generator and direct calls into the Rust crates under+//! comparison.++use core::ffi::c_void;+use core::hint::black_box;+use core::ptr;+use rand::distr::{Distribution, Uniform};+use rand::rngs::StdRng;+use rand::SeedableRng;+use spade::{HierarchyHintGenerator, Point2, Triangulation};+use std::panic::{catch_unwind, AssertUnwindSafe};++const UPSTREAM_SEED: [u8; 32] = [+ 0xfb, 0xdc, 0x4e, 0xa0, 0x30, 0xde, 0x82, 0xba, 0x69, 0x97, 0x3c, 0x52, 0x49, 0x4d, 0x00, 0xca,+ 0x0a, 0x5c, 0x21, 0xa3, 0x8d, 0x5c, 0xf2, 0x34, 0x4e, 0x58, 0x7d, 0x80, 0x16, 0x66, 0x23, 0x30,+];++const UNIFORM_RANGE: f64 = 1.0e9;+const LOCAL_STEP_RANGE: f64 = 1.0;++type PlainSpade = spade::DelaunayTriangulation<Point2<f64>>;+type HierarchySpade =+ spade::DelaunayTriangulation<Point2<f64>, (), (), (), HierarchyHintGenerator<f64>>;++#[repr(i32)]+#[derive(Clone, Copy, Debug)]+enum BoundaryStatus {+ Success = 0,+ NullPointer = 1,+ UnknownTag = 2,+ CoordinateCountMismatch = 3,+ DistributionConstructionFailed = 4,+ TriangulationFailed = 5,+ Panicked = 6,+}++impl BoundaryStatus {+ const fn code(self) -> i32 {+ self as i32+ }+}++#[derive(Clone, Copy)]+enum PointDistribution {+ LocalInsertion,+ Uniform,+}++impl TryFrom<i32> for PointDistribution {+ type Error = BoundaryStatus;++ fn try_from(tag: i32) -> Result<Self, Self::Error> {+ match tag {+ 0 => Ok(Self::LocalInsertion),+ 1 => Ok(Self::Uniform),+ _ => Err(BoundaryStatus::UnknownTag),+ }+ }+}++#[derive(Clone, Copy)]+enum NativeImplementation {+ Spade,+ SpadeHierarchy,+ Cdt,+ Delaunator,+}++impl TryFrom<i32> for NativeImplementation {+ type Error = BoundaryStatus;++ fn try_from(tag: i32) -> Result<Self, Self::Error> {+ match tag {+ 0 => Ok(Self::Spade),+ 1 => Ok(Self::SpadeHierarchy),+ 2 => Ok(Self::Cdt),+ 3 => Ok(Self::Delaunator),+ _ => Err(BoundaryStatus::UnknownTag),+ }+ }+}++enum PreparedImplementation {+ Spade(Vec<Point2<f64>>),+ SpadeHierarchy(Vec<Point2<f64>>),+ Cdt(Vec<(f64, f64)>),+ Delaunator(Vec<delaunator::Point>),+}++#[derive(Clone, Copy)]+struct ConstructionSummary {+ vertices: usize,+ triangles: usize,+}++impl PreparedImplementation {+ fn from_coordinates(implementation: NativeImplementation, coordinates: &[[f64; 2]]) -> Self {+ match implementation {+ NativeImplementation::Spade => {+ Self::Spade(coordinates.iter().copied().map(Point2::from).collect())+ }+ NativeImplementation::SpadeHierarchy => {+ Self::SpadeHierarchy(coordinates.iter().copied().map(Point2::from).collect())+ }+ NativeImplementation::Cdt => {+ Self::Cdt(coordinates.iter().map(|[x, y]| (*x, *y)).collect())+ }+ NativeImplementation::Delaunator => Self::Delaunator(+ coordinates+ .iter()+ .map(|[x, y]| delaunator::Point { x: *x, y: *y })+ .collect(),+ ),+ }+ }++ fn run(&self) -> Result<ConstructionSummary, BoundaryStatus> {+ match self {+ Self::Spade(vertices) => {+ let triangulation = PlainSpade::bulk_load(vertices.clone())+ .map_err(|_| BoundaryStatus::TriangulationFailed)?;+ let summary = ConstructionSummary {+ vertices: triangulation.num_vertices(),+ triangles: triangulation.num_inner_faces(),+ };+ black_box(triangulation);+ Ok(summary)+ }+ Self::SpadeHierarchy(vertices) => {+ let triangulation = HierarchySpade::bulk_load(vertices.clone())+ .map_err(|_| BoundaryStatus::TriangulationFailed)?;+ let summary = ConstructionSummary {+ vertices: triangulation.num_vertices(),+ triangles: triangulation.num_inner_faces(),+ };+ black_box(triangulation);+ Ok(summary)+ }+ Self::Cdt(vertices) => {+ let triangles = cdt::triangulate_points(vertices)+ .map_err(|_| BoundaryStatus::TriangulationFailed)?;+ let summary = ConstructionSummary {+ vertices: vertices.len(),+ triangles: triangles.len(),+ };+ black_box(triangles);+ Ok(summary)+ }+ Self::Delaunator(vertices) => {+ let triangulation = delaunator::triangulate(vertices);+ let summary = ConstructionSummary {+ vertices: vertices.len(),+ triangles: triangulation.triangles.len() / 3,+ };+ black_box(triangulation);+ Ok(summary)+ }+ }+ }+}++fn boundary_status(action: impl FnOnce() -> Result<(), BoundaryStatus>) -> i32 {+ match catch_unwind(AssertUnwindSafe(action)) {+ Ok(Ok(())) => BoundaryStatus::Success.code(),+ Ok(Err(status)) => status.code(),+ Err(_) => BoundaryStatus::Panicked.code(),+ }+}++fn coordinate_count(point_count: usize) -> Result<usize, BoundaryStatus> {+ point_count+ .checked_mul(2)+ .ok_or(BoundaryStatus::CoordinateCountMismatch)+}++fn uniform_samples(point_count: usize) -> Result<impl Iterator<Item = [f64; 2]>, BoundaryStatus> {+ let distribution = Uniform::new_inclusive(-UNIFORM_RANGE, UNIFORM_RANGE)+ .map_err(|_| BoundaryStatus::DistributionConstructionFailed)?;+ let mut generator = StdRng::from_seed(UPSTREAM_SEED);+ Ok(core::iter::from_fn(move || {+ Some([+ distribution.sample(&mut generator),+ distribution.sample(&mut generator),+ ])+ })+ .take(point_count))+}++fn local_insertion_samples(+ point_count: usize,+) -> Result<impl Iterator<Item = [f64; 2]>, BoundaryStatus> {+ let distribution = Uniform::new_inclusive(-LOCAL_STEP_RANGE, LOCAL_STEP_RANGE)+ .map_err(|_| BoundaryStatus::DistributionConstructionFailed)?;+ let mut generator = StdRng::from_seed(UPSTREAM_SEED);+ let mut previous = [0.0, 1.0];+ Ok(core::iter::from_fn(move || {+ previous = [+ previous[0] + distribution.sample(&mut generator),+ previous[1] + distribution.sample(&mut generator),+ ];+ Some(previous)+ })+ .take(point_count))+}++fn write_coordinates(+ distribution: PointDistribution,+ point_count: usize,+ output: &mut [f64],+) -> Result<(), BoundaryStatus> {+ let expected_count = coordinate_count(point_count)?;+ if output.len() != expected_count {+ return Err(BoundaryStatus::CoordinateCountMismatch);+ }++ let samples: Box<dyn Iterator<Item = [f64; 2]>> = match distribution {+ PointDistribution::LocalInsertion => Box::new(local_insertion_samples(point_count)?),+ PointDistribution::Uniform => Box::new(uniform_samples(point_count)?),+ };++ output+ .chunks_exact_mut(2)+ .zip(samples)+ .for_each(|(coordinate_pair, [x, y])| {+ coordinate_pair.copy_from_slice(&[x, y]);+ });+ Ok(())+}++/// Fill `coordinates` with Spade's exact seeded benchmark distribution.+///+/// # Safety+///+/// A non-null `coordinates` pointer must be writable for `coordinate_count`+/// consecutive `f64` values.+#[no_mangle]+pub unsafe extern "C" fn delaunay_compare_generate(+ distribution_tag: i32,+ point_count: usize,+ coordinates: *mut f64,+ supplied_coordinate_count: usize,+) -> i32 {+ boundary_status(|| {+ let distribution = PointDistribution::try_from(distribution_tag)?;+ let expected_count = coordinate_count(point_count)?;+ if supplied_coordinate_count != expected_count {+ return Err(BoundaryStatus::CoordinateCountMismatch);+ }+ if coordinates.is_null() && supplied_coordinate_count != 0 {+ return Err(BoundaryStatus::NullPointer);+ }+ let output = if supplied_coordinate_count == 0 {+ &mut []+ } else {+ // SAFETY: the caller contract and checks above establish a non-null+ // writable region of exactly `supplied_coordinate_count` values.+ unsafe { core::slice::from_raw_parts_mut(coordinates, supplied_coordinate_count) }+ };+ write_coordinates(distribution, point_count, output)+ })+}++/// Prepare the implementation-specific native input outside the timed region.+///+/// # Safety+///+/// A non-null `coordinates` pointer must be readable for `2 * point_count`+/// consecutive `f64` values. `prepared_output` must be writable for one pointer.+#[no_mangle]+pub unsafe extern "C" fn delaunay_compare_prepare(+ implementation_tag: i32,+ coordinates: *const f64,+ point_count: usize,+ prepared_output: *mut *mut c_void,+) -> i32 {+ boundary_status(|| {+ let implementation = NativeImplementation::try_from(implementation_tag)?;+ if prepared_output.is_null() {+ return Err(BoundaryStatus::NullPointer);+ }+ let supplied_coordinate_count = coordinate_count(point_count)?;+ if coordinates.is_null() && supplied_coordinate_count != 0 {+ return Err(BoundaryStatus::NullPointer);+ }+ let flat_coordinates = if supplied_coordinate_count == 0 {+ &[]+ } else {+ // SAFETY: the caller contract and checks above establish a non-null+ // readable region of exactly `supplied_coordinate_count` values.+ unsafe { core::slice::from_raw_parts(coordinates, supplied_coordinate_count) }+ };+ let coordinate_pairs = flat_coordinates.as_chunks::<2>().0;+ let prepared = PreparedImplementation::from_coordinates(implementation, coordinate_pairs);+ // SAFETY: `prepared_output` was checked non-null and points to writable+ // storage for one opaque pointer by the caller contract.+ unsafe {+ ptr::write(prepared_output, Box::into_raw(Box::new(prepared)).cast());+ }+ Ok(())+ })+}++/// Construct one triangulation from a prepared native input.+///+/// # Safety+///+/// `prepared` must be a live pointer returned by `delaunay_compare_prepare`.+/// Both output pointers must be writable for one `usize`.+#[no_mangle]+pub unsafe extern "C" fn delaunay_compare_run(+ prepared: *const c_void,+ vertex_count_output: *mut usize,+ triangle_count_output: *mut usize,+) -> i32 {+ boundary_status(|| {+ if prepared.is_null() || vertex_count_output.is_null() || triangle_count_output.is_null() {+ return Err(BoundaryStatus::NullPointer);+ }+ // SAFETY: the caller contract requires a live pointer produced by the+ // matching prepare function and keeps it alive for this call.+ let prepared_implementation = unsafe { &*prepared.cast::<PreparedImplementation>() };+ let summary = prepared_implementation.run()?;+ // SAFETY: both output pointers were checked non-null and the caller+ // contract gives writable storage for one value at each address.+ unsafe {+ ptr::write(vertex_count_output, summary.vertices);+ ptr::write(triangle_count_output, summary.triangles);+ }+ Ok(())+ })+}++/// Release one prepared native input. A null pointer is a no-op.+///+/// # Safety+///+/// A non-null pointer must have been returned by `delaunay_compare_prepare`+/// and must not have been released before.+#[no_mangle]+pub unsafe extern "C" fn delaunay_compare_release(prepared: *mut c_void) {+ if !prepared.is_null() {+ // SAFETY: the caller contract transfers the one remaining ownership of+ // the allocation produced by `Box::into_raw` in the prepare function.+ unsafe {+ drop(Box::from_raw(prepared.cast::<PreparedImplementation>()));+ }+ }+}++#[cfg(test)]+mod tests {+ use super::{local_insertion_samples, uniform_samples};++ #[test]+ fn local_insertion_prefix_matches_upstream_fixture_bits() {+ let observed = local_insertion_samples(3)+ .expect("the fixed local-insertion distribution is valid")+ .flatten()+ .map(f64::to_bits)+ .collect::<Vec<_>>();+ assert_eq!(+ observed,+ [+ 0xbfe28b04856ac6db,+ 0x3ff8c718b563a19a,+ 0x3fc014ab09d22a14,+ 0x4003a1ee45dc4712,+ 0xbfd097c41d82adc4,+ 0x4008d420538d5c3d,+ ]+ );+ }++ #[test]+ fn uniform_prefix_matches_upstream_fixture_bits() {+ let observed = uniform_samples(3)+ .expect("the fixed uniform distribution is valid")+ .flatten()+ .map(f64::to_bits)+ .collect::<Vec<_>>();+ assert_eq!(+ observed,+ [+ 0xc1c1450134a5bd5a,+ 0x41c0598b1e249a72,+ 0x41c5037dbf1c0b38,+ 0x41cafc1cf53845c8,+ 0xc1b6f1036f0b1386,+ 0x41c35b5d4df8b356,+ ]+ );+ }+}
bench/support/BenchSupport.hs view
@@ -18,8 +18,9 @@ import Data.Word (Word64) import qualified Data.Vector as V import GHC.Clock (getMonotonicTimeNSec)+import GHC.Conc.Sync (getAllocationCounter) import GHC.Stats- ( RTSStats (allocated_bytes, max_live_bytes)+ ( RTSStats (max_live_bytes) , getRTSStats , getRTSStatsEnabled )@@ -40,24 +41,33 @@ -- on one capability the two agree, and where they diverge the ratio is the -- parallelism actually obtained. Reporting CPU alone, as this did, would let a -- change that halves elapsed time and doubles total work read as a regression.+-- Allocation comes from the per-thread counter, not from @RTSStats@. The RTS+-- field is only refreshed at a collection, so a lane whose whole allocation+-- fits between two GCs reports a delta of exactly zero — the instrument+-- saturates at the bottom and reads as a perfect result. The thread counter is+-- block-granular and monotone, so a lane that allocates less says so.+--+-- @max_live_bytes@ is left as the RTS reports it and is a whole-process running+-- maximum sampled at major collections, never this lane's residency. Read it+-- as the watermark up to here, and only against a run with the same GC+-- schedule; @-G1@ is what makes two schedules comparable. timedValue :: NFData value => String -> IO value -> IO value timedValue label action = do statsEnabled <- getRTSStatsEnabled- before <- if statsEnabled then Just <$> getRTSStats else pure Nothing+ allocationStart <- getAllocationCounter wallStart <- getMonotonicTimeNSec cpuStart <- getCPUTime value <- action >>= evaluate . force cpuEnd <- getCPUTime wallEnd <- getMonotonicTimeNSec+ allocationEnd <- getAllocationCounter after <- if statsEnabled then Just <$> getRTSStats else pure Nothing putStrLn (label <> "-elapsed: " <> show (fromIntegral (wallEnd - wallStart) / 1.0e9 :: Double) <> "s") putStrLn (label <> "-cpu: " <> show (fromIntegral (cpuEnd - cpuStart) / 1.0e12 :: Double) <> "s")- case (before, after) of- (Just left, Just right) ->- do- putStrLn (label <> "-allocated-bytes: " <> show (allocated_bytes right - allocated_bytes left))- putStrLn (label <> "-max-live-bytes: " <> show (max_live_bytes right))- _ -> pure ()+ putStrLn (label <> "-allocated-bytes: " <> show (allocationStart - allocationEnd))+ case after of+ Just right -> putStrLn (label <> "-process-max-live-bytes: " <> show (max_live_bytes right))+ Nothing -> pure () pure value requireRight :: Show error => Either error value -> IO value
bindings/README.md view
@@ -1,55 +1,91 @@ # Foreign bindings -`moonlight-triangulation.cabal` owns a private Haskell `ffi` sublibrary and the+`moonlight-triangulation.cabal` owns the private Haskell `ffi` sublibrary and `moonlight-triangulation-c` foreign library. Python, TypeScript, and Rust are-leaf bindings over that sole C ABI; none restates the engine or mesh.+thin lifecycle and bulk-layout leaves over that sole ABI; none implements+triangulation, overlay, publication, valuation, or morphology. From `compiler`, build and locate the shared library: ```bash-cabal build moonlight-triangulation:flib:moonlight-triangulation-c --enable-shared --project-file=cabal.project.triangulation-dev-cabal list-bin moonlight-triangulation:flib:moonlight-triangulation-c --enable-shared --project-file=cabal.project.triangulation-dev+../scripts/safe-cabal.sh build \+ moonlight-triangulation:flib:moonlight-triangulation-c \+ --project-file=cabal.project.triangulation-dev --enable-shared -j1+cabal list-bin moonlight-triangulation:flib:moonlight-triangulation-c \+ --project-file=cabal.project.triangulation-dev --enable-shared ``` Set `MOONLIGHT_TRIANGULATION_LIBRARY` to that `.dylib`, `.so`, or `.dll`; Rust linking also reads its directory from `MOONLIGHT_TRIANGULATION_LIB_DIR`. -The ABI accepts interleaved binary64 coordinates and publishes opaque immutable-mesh handles. Set operations and batch insertion return new handles, leaving-inputs valid until freed. Dense vertices and triangles follow vertex-handle and-bounded-face-handle order. Every status-returning call accepts an optional-`ml_obstruction` distinguishing API misuse, capacity refusal, geometric-obstruction, and runtime failure, with retained index, value, coordinate, and-message witnesses. `UINT64_MAX` means no input index applies.+## ABI version 2 -Batch related edits into one `insert_many` call; each call publishes a new mesh,-so repeated singleton calls pay repeated publication costs.+Three opaque immutable carriers cross the boundary: -`ml_runtime_initialize` is idempotent and process-lifetime. There is no shutdown-call because GHC cannot reliably restart after its outermost `hs_exit`.+- `ml_mesh` retains a binary64 Delaunay mesh. Its `ml_mesh_site_*` algebra+ combines sites, not polygon interiors.+- `ml_region` retains the authoritative exact rational `PlanarRegion`.+ `ml_region_create_f64` admits every input coordinate exactly as its binary64+ dyadic value in one call, using point counts per loop and loop counts per+ component. The inverse bulk projection returns component/loop offsets and+ binary64 rendering points; it does not replace the exact retained geometry.+- `ml_structuring_element` retains one admitted origin-containing convex+ polygon for repeated offset, inset, opening, and closing calls. +Region union, intersection, difference, and symmetric difference call the one+Haskell overlay and grouped-publication path. Point location is exact. A single+valuation call returns Euler characteristic, exact reduced area as+`numerator/denominator`, and certified conventional-perimeter bounds. Morphology+returns a new region plus the existing operation/work receipt.++Every status-returning call accepts an optional `ml_obstruction` distinguishing+pointer/count/buffer failures from region layout, validation, overlay,+publication, valuation, projection, morphology, and runtime failures. Handles+remain valid until explicitly freed; every operation publishes a fresh output+handle. `ml_runtime_initialize` is idempotent and process-lifetime.++Batch related mesh edits into one `insert_many` call. Author a whole region with+one component list; there is deliberately no mutable builder or per-vertex FFI+surface.+ ## Python ```python-from moonlight_triangulation import Moonlight-mesh = Moonlight().delaunay([(0, 0), (1, 0), (0, 1)])-print(mesh.vertices, mesh.triangles)-mesh.close()+from fractions import Fraction+from moonlight_triangulation import Moonlight, PolygonComponent++moonlight = Moonlight()+left = moonlight.region([+ PolygonComponent(((0, 0), (2, 0), (2, 2), (0, 2)))+])+right = moonlight.region([+ PolygonComponent(((1, 0), (3, 0), (3, 2), (1, 2)))+])+intersection = left.intersection(right)+assert intersection.valuations.area == Fraction(2, 1) ``` ## TypeScript ```typescript-import { Moonlight } from "@moonlight/triangulation";-const mesh = new Moonlight().delaunay([[0, 0], [1, 0], [0, 1]]);-console.log(mesh.vertices(), mesh.triangles());-mesh.close();+import { Moonlight, RegionLocation } from "@moonlight/triangulation";++const moonlight = new Moonlight();+const region = moonlight.region([+ { outer: [[0, 0], [2, 0], [2, 2], [0, 2]] },+]);+console.log(region.valuations().area, region.locate([1, 1]) === RegionLocation.Interior); ``` ## Rust ```rust-use moonlight_triangulation::Moonlight;-let mesh = Moonlight::initialize()?.delaunay(&[[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]])?;-println!("{:?} {:?}", mesh.vertices()?, mesh.triangles()?);+use moonlight_triangulation::{Moonlight, PolygonComponent};++let moonlight = Moonlight::initialize()?;+let region = moonlight.region(&[PolygonComponent {+ outer: vec![[0.0, 0.0], [2.0, 0.0], [2.0, 2.0], [0.0, 2.0]],+ holes: vec![],+}])?;+println!("{:?}", region.valuations()?.area); ```
bindings/python/pyproject.toml view
@@ -4,8 +4,8 @@ [project] name = "moonlight-triangulation"-version = "1.0.0"-description = "Python bindings for Moonlight's immutable Delaunay mesh algebra."+version = "2.0.0"+description = "Python bindings for Moonlight exact regions and immutable Delaunay meshes." requires-python = ">=3.11" license = { text = "MIT" }
bindings/python/src/moonlight_triangulation/__init__.py view
@@ -3,9 +3,13 @@ import ctypes import os import weakref-from collections.abc import Callable, Sequence+from array import array+from collections.abc import Callable, Iterable, Sequence+from dataclasses import dataclass+from enum import IntEnum+from fractions import Fraction from pathlib import Path-from typing import Final+from typing import Final, Self class _Obstruction(ctypes.Structure):@@ -23,6 +27,21 @@ ] +class _NativeMinkowskiReceipt(ctypes.Structure):+ _fields_ = [+ ("operation", ctypes.c_uint32),+ ("reserved", ctypes.c_uint32),+ ("input_components", ctypes.c_uint64),+ ("convex_pieces", ctypes.c_uint64),+ ("generated_pieces", ctypes.c_uint64),+ ("generated_convolution_edges", ctypes.c_uint64),+ ("overlay_passes", ctypes.c_uint64),+ ("exact_crossings", ctypes.c_uint64),+ ("output_cells", ctypes.c_uint64),+ ("exact_coordinate_bit_growth", ctypes.c_uint64),+ ]++ class MoonlightError(RuntimeError): def __init__(self, status: int, obstruction: _Obstruction) -> None: self.status = status@@ -40,32 +59,94 @@ class _NativeApi: _OK: Final = 0+ _ABI_VERSION: Final = 2 def __init__(self, library_path: Path) -> None: library = ctypes.CDLL(str(library_path))- mesh = ctypes.c_void_p- mesh_output = ctypes.POINTER(mesh)+ handle = ctypes.c_void_p+ handle_output = ctypes.POINTER(handle) obstruction = ctypes.POINTER(_Obstruction) count_output = ctypes.POINTER(ctypes.c_size_t)+ receipt_output = ctypes.POINTER(_NativeMinkowskiReceipt)+ binary_operation = (handle, handle, handle_output, obstruction)+ morphology_operation = (handle, handle, handle_output, receipt_output, obstruction)+ _configure(library, "ml_abi_version", ()) _configure(library, "ml_runtime_initialize", ())- _configure(library, "ml_delaunay_f64", (ctypes.POINTER(ctypes.c_double), ctypes.c_size_t, mesh_output, obstruction))- _configure(library, "ml_mesh_insert_many_f64", (mesh, ctypes.POINTER(ctypes.c_double), ctypes.c_size_t, mesh_output, obstruction))- _configure(library, "ml_mesh_union", (mesh, mesh, mesh_output, obstruction))- _configure(library, "ml_mesh_intersection", (mesh, mesh, mesh_output, obstruction))- _configure(library, "ml_mesh_difference", (mesh, mesh, mesh_output, obstruction))- _configure(library, "ml_mesh_symmetric_difference", (mesh, mesh, mesh_output, obstruction))- _configure(library, "ml_mesh_vertex_count", (mesh, count_output, obstruction))- _configure(library, "ml_mesh_triangle_count", (mesh, count_output, obstruction))- _configure(library, "ml_mesh_copy_vertices_f64", (mesh, ctypes.POINTER(ctypes.c_double), ctypes.c_size_t, count_output, obstruction))- _configure(library, "ml_mesh_copy_triangles_u32", (mesh, ctypes.POINTER(ctypes.c_uint32), ctypes.c_size_t, count_output, obstruction))- _configure(library, "ml_mesh_free", (mesh,), None)+ _configure(library, "ml_delaunay_f64", (ctypes.POINTER(ctypes.c_double), ctypes.c_size_t, handle_output, obstruction))+ _configure(library, "ml_mesh_insert_many_f64", (handle, ctypes.POINTER(ctypes.c_double), ctypes.c_size_t, handle_output, obstruction))+ _configure(library, "ml_mesh_site_union", binary_operation)+ _configure(library, "ml_mesh_site_intersection", binary_operation)+ _configure(library, "ml_mesh_site_difference", binary_operation)+ _configure(library, "ml_mesh_site_symmetric_difference", binary_operation)+ _configure(library, "ml_mesh_vertex_count", (handle, count_output, obstruction))+ _configure(library, "ml_mesh_triangle_count", (handle, count_output, obstruction))+ _configure(library, "ml_mesh_copy_vertices_f64", (handle, ctypes.POINTER(ctypes.c_double), ctypes.c_size_t, count_output, obstruction))+ _configure(library, "ml_mesh_copy_triangles_u32", (handle, ctypes.POINTER(ctypes.c_uint32), ctypes.c_size_t, count_output, obstruction))+ _configure(library, "ml_mesh_free", (handle,), None) + _configure(+ library,+ "ml_region_create_f64",+ (+ ctypes.POINTER(ctypes.c_double),+ ctypes.c_size_t,+ ctypes.POINTER(ctypes.c_size_t),+ ctypes.c_size_t,+ ctypes.POINTER(ctypes.c_size_t),+ ctypes.c_size_t,+ handle_output,+ obstruction,+ ),+ )+ _configure(library, "ml_region_counts", (handle, count_output, count_output, count_output, obstruction))+ _configure(+ library,+ "ml_region_copy_f64",+ (+ handle,+ ctypes.POINTER(ctypes.c_double),+ ctypes.c_size_t,+ ctypes.POINTER(ctypes.c_size_t),+ ctypes.c_size_t,+ ctypes.POINTER(ctypes.c_size_t),+ ctypes.c_size_t,+ obstruction,+ ),+ )+ _configure(library, "ml_region_union", binary_operation)+ _configure(library, "ml_region_intersection", binary_operation)+ _configure(library, "ml_region_difference", binary_operation)+ _configure(library, "ml_region_symmetric_difference", binary_operation)+ _configure(library, "ml_region_locate_point_f64", (handle, ctypes.c_double, ctypes.c_double, ctypes.POINTER(ctypes.c_uint32), obstruction))+ _configure(+ library,+ "ml_region_measure",+ (+ handle,+ ctypes.POINTER(ctypes.c_int64),+ ctypes.POINTER(ctypes.c_char),+ ctypes.c_size_t,+ count_output,+ ctypes.POINTER(ctypes.c_double),+ ctypes.POINTER(ctypes.c_double),+ obstruction,+ ),+ )+ _configure(library, "ml_region_free", (handle,), None)+ _configure(library, "ml_structuring_element_create_f64", (ctypes.POINTER(ctypes.c_double), ctypes.c_size_t, handle_output, obstruction))+ _configure(library, "ml_structuring_element_free", (handle,), None)+ _configure(library, "ml_region_minkowski_sum", morphology_operation)+ _configure(library, "ml_region_offset", morphology_operation)+ _configure(library, "ml_region_inset", morphology_operation)+ _configure(library, "ml_region_open", morphology_operation)+ _configure(library, "ml_region_close", morphology_operation)+ status = int(library.ml_runtime_initialize()) if status != self._OK: raise RuntimeError(f"Moonlight runtime initialization failed with status {status}") abi_version = int(library.ml_abi_version())- if abi_version != 1:+ if abi_version != self._ABI_VERSION: raise RuntimeError(f"unsupported Moonlight ABI version {abi_version}") self.library = library @@ -77,8 +158,48 @@ Point = tuple[float, float] Triangle = tuple[int, int, int] _BinaryNativeOperation = Callable[[ctypes.c_void_p, ctypes.c_void_p, object, object], int]+_MorphologyNativeOperation = Callable[[ctypes.c_void_p, ctypes.c_void_p, object, object, object], int] +@dataclass(frozen=True)+class PolygonComponent:+ outer: tuple[Point, ...]+ holes: tuple[tuple[Point, ...], ...] = ()+++class RegionLocation(IntEnum):+ EXTERIOR = 0+ BOUNDARY = 1+ INTERIOR = 2+++class MinkowskiOperation(IntEnum):+ ADDITION = 0+ EROSION = 1+ OPENING = 2+ CLOSING = 3+++@dataclass(frozen=True)+class RegionValuations:+ euler_characteristic: int+ area: Fraction+ perimeter_bounds: tuple[float, float]+++@dataclass(frozen=True)+class MinkowskiReceipt:+ operation: MinkowskiOperation+ input_components: int+ convex_pieces: int+ generated_pieces: int+ generated_convolution_edges: int+ overlay_passes: int+ exact_crossings: int+ output_cells: int+ exact_coordinate_bit_growth: int++ class Moonlight: def __init__(self, library_path: str | os.PathLike[str] | None = None) -> None: configured_path = library_path or os.environ.get("MOONLIGHT_TRIANGULATION_LIBRARY")@@ -88,38 +209,82 @@ def delaunay(self, points: Sequence[Point]) -> Mesh: coordinates, pointer = _coordinate_buffer(points)- output = ctypes.c_void_p()- obstruction = _Obstruction()- status = int(- self._native.library.ml_delaunay_f64(- pointer,- len(points),- ctypes.byref(output),- ctypes.byref(obstruction),- )+ handle = _produce_handle(+ self._native,+ lambda output, obstruction: self._native.library.ml_delaunay_f64(+ pointer, len(points), output, obstruction+ ), )- self._native.check(status, obstruction)- return Mesh(self._native, _required_handle(output))+ return Mesh(self._native, handle) + def region(self, components: Sequence[PolygonComponent]) -> Region:+ loops, loop_counts, component_counts = _component_layout(components)+ coordinates, coordinate_pointer = _coordinate_buffer(+ point for loop in loops for point in loop+ )+ loop_buffer = _size_buffer(loop_counts)+ component_buffer = _size_buffer(component_counts)+ handle = _produce_handle(+ self._native,+ lambda output, obstruction: self._native.library.ml_region_create_f64(+ coordinate_pointer,+ sum(loop_counts),+ loop_buffer,+ len(loop_counts),+ component_buffer,+ len(component_counts),+ output,+ obstruction,+ ),+ )+ return Region(self._native, handle) -class Mesh:- __slots__ = ("_native", "_handle", "_finalizer", "__weakref__")+ def structuring_element(self, points: Sequence[Point]) -> StructuringElement:+ coordinates, pointer = _coordinate_buffer(points)+ handle = _produce_handle(+ self._native,+ lambda output, obstruction: self._native.library.ml_structuring_element_create_f64(+ pointer, len(points), output, obstruction+ ),+ )+ return StructuringElement(self._native, handle) - def __init__(self, native: _NativeApi, handle: ctypes.c_void_p) -> None:++class _OwnedHandle:+ __slots__ = ("_native", "_handle", "_finalizer", "_kind", "__weakref__")++ def __init__(self, native: _NativeApi, handle: ctypes.c_void_p, free: Callable[[ctypes.c_void_p], None], kind: str) -> None: self._native = native self._handle = handle- self._finalizer = weakref.finalize(self, native.library.ml_mesh_free, handle)+ self._kind = kind+ self._finalizer = weakref.finalize(self, free, handle) def close(self) -> None: self._finalizer() self._handle = ctypes.c_void_p() - def __enter__(self) -> Mesh:+ def __enter__(self) -> Self: return self def __exit__(self, _type: object, _value: object, _traceback: object) -> None: self.close() + def _live_handle(self) -> ctypes.c_void_p:+ if not self._finalizer.alive:+ raise RuntimeError(f"{self._kind} is closed")+ return self._handle++ def _require_same_runtime(self, other: _OwnedHandle) -> None:+ if self._native is not other._native:+ raise ValueError("both values must belong to the same Moonlight runtime")+++class Mesh(_OwnedHandle):+ __slots__ = ()++ def __init__(self, native: _NativeApi, handle: ctypes.c_void_p) -> None:+ super().__init__(native, handle, native.library.ml_mesh_free, "mesh")+ @property def vertex_count(self) -> int: return self._count(self._native.library.ml_mesh_vertex_count)@@ -136,11 +301,7 @@ obstruction = _Obstruction() status = int( self._native.library.ml_mesh_copy_vertices_f64(- self._live_handle(),- output,- count,- ctypes.byref(written),- ctypes.byref(obstruction),+ self._live_handle(), output, count, ctypes.byref(written), ctypes.byref(obstruction) ) ) self._native.check(status, obstruction)@@ -154,11 +315,7 @@ obstruction = _Obstruction() status = int( self._native.library.ml_mesh_copy_triangles_u32(- self._live_handle(),- output,- count,- ctypes.byref(written),- ctypes.byref(obstruction),+ self._live_handle(), output, count, ctypes.byref(written), ctypes.byref(obstruction) ) ) self._native.check(status, obstruction)@@ -169,72 +326,279 @@ def insert_many(self, points: Sequence[Point]) -> Mesh: coordinates, pointer = _coordinate_buffer(points)- output = ctypes.c_void_p()+ handle = _produce_handle(+ self._native,+ lambda output, obstruction: self._native.library.ml_mesh_insert_many_f64(+ self._live_handle(), pointer, len(points), output, obstruction+ ),+ )+ return Mesh(self._native, handle)++ def site_union(self, other: Mesh) -> Mesh:+ return self._binary(other, self._native.library.ml_mesh_site_union)++ def site_intersection(self, other: Mesh) -> Mesh:+ return self._binary(other, self._native.library.ml_mesh_site_intersection)++ def site_difference(self, other: Mesh) -> Mesh:+ return self._binary(other, self._native.library.ml_mesh_site_difference)++ def site_symmetric_difference(self, other: Mesh) -> Mesh:+ return self._binary(other, self._native.library.ml_mesh_site_symmetric_difference)++ def _binary(self, other: Mesh, operation: _BinaryNativeOperation) -> Mesh:+ self._require_same_runtime(other)+ handle = _produce_handle(+ self._native,+ lambda output, obstruction: operation(+ self._live_handle(), other._live_handle(), output, obstruction+ ),+ )+ return Mesh(self._native, handle)++ def _count(self, operation: Callable[[ctypes.c_void_p, object, object], int]) -> int:+ output = ctypes.c_size_t() obstruction = _Obstruction()+ status = int(operation(self._live_handle(), ctypes.byref(output), ctypes.byref(obstruction)))+ self._native.check(status, obstruction)+ return int(output.value)+++class Region(_OwnedHandle):+ __slots__ = ()++ def __init__(self, native: _NativeApi, handle: ctypes.c_void_p) -> None:+ super().__init__(native, handle, native.library.ml_region_free, "region")++ @property+ def components(self) -> tuple[PolygonComponent, ...]:+ component_count, loop_count, point_count = self._counts()+ coordinates = (ctypes.c_double * (point_count * 2))()+ loop_offsets = (ctypes.c_size_t * (loop_count + 1))()+ component_offsets = (ctypes.c_size_t * (component_count + 1))()+ obstruction = _Obstruction() status = int(- self._native.library.ml_mesh_insert_many_f64(+ self._native.library.ml_region_copy_f64( self._live_handle(),- pointer,- len(points),- ctypes.byref(output),+ coordinates,+ point_count,+ loop_offsets,+ loop_count + 1,+ component_offsets,+ component_count + 1, ctypes.byref(obstruction), ) ) self._native.check(status, obstruction)- return Mesh(self._native, _required_handle(output))+ points = tuple((float(coordinates[index * 2]), float(coordinates[index * 2 + 1])) for index in range(point_count))+ loops = tuple(+ points[int(loop_offsets[index]) : int(loop_offsets[index + 1])]+ for index in range(loop_count)+ )+ component_ranges = tuple(+ (int(component_offsets[index]), int(component_offsets[index + 1]))+ for index in range(component_count)+ )+ if any(start >= end for start, end in component_ranges):+ raise RuntimeError("Moonlight returned a component without an outer loop")+ return tuple(+ PolygonComponent(loops[start], loops[start + 1 : end])+ for start, end in component_ranges+ ) - def union(self, other: Mesh) -> Mesh:- return self._binary(other, self._native.library.ml_mesh_union)+ @property+ def valuations(self) -> RegionValuations:+ return self._measure_with_capacity(128) - def intersection(self, other: Mesh) -> Mesh:- return self._binary(other, self._native.library.ml_mesh_intersection)+ def locate(self, point: Point) -> RegionLocation:+ output = ctypes.c_uint32()+ obstruction = _Obstruction()+ status = int(+ self._native.library.ml_region_locate_point_f64(+ self._live_handle(), point[0], point[1], ctypes.byref(output), ctypes.byref(obstruction)+ )+ )+ self._native.check(status, obstruction)+ return RegionLocation(output.value) - def difference(self, other: Mesh) -> Mesh:- return self._binary(other, self._native.library.ml_mesh_difference)+ def union(self, other: Region) -> Region:+ return self._binary(other, self._native.library.ml_region_union) - def symmetric_difference(self, other: Mesh) -> Mesh:- return self._binary(other, self._native.library.ml_mesh_symmetric_difference)+ def intersection(self, other: Region) -> Region:+ return self._binary(other, self._native.library.ml_region_intersection) - def _binary(self, other: Mesh, operation: _BinaryNativeOperation) -> Mesh:- if self._native is not other._native:- raise ValueError("both meshes must belong to the same Moonlight runtime")- output = ctypes.c_void_p()+ def difference(self, other: Region) -> Region:+ return self._binary(other, self._native.library.ml_region_difference)++ def symmetric_difference(self, other: Region) -> Region:+ return self._binary(other, self._native.library.ml_region_symmetric_difference)++ def minkowski_sum(self, other: Region) -> tuple[Region, MinkowskiReceipt]:+ self._require_same_runtime(other)+ return self._morph(+ lambda output, receipt, obstruction: self._native.library.ml_region_minkowski_sum(+ self._live_handle(), other._live_handle(), output, receipt, obstruction+ )+ )++ def offset(self, element: StructuringElement) -> tuple[Region, MinkowskiReceipt]:+ return self._with_element(element, self._native.library.ml_region_offset)++ def inset(self, element: StructuringElement) -> tuple[Region, MinkowskiReceipt]:+ return self._with_element(element, self._native.library.ml_region_inset)++ def open(self, element: StructuringElement) -> tuple[Region, MinkowskiReceipt]:+ return self._with_element(element, self._native.library.ml_region_open)++ def close_with(self, element: StructuringElement) -> tuple[Region, MinkowskiReceipt]:+ return self._with_element(element, self._native.library.ml_region_close)++ def _binary(self, other: Region, operation: _BinaryNativeOperation) -> Region:+ self._require_same_runtime(other)+ handle = _produce_handle(+ self._native,+ lambda output, obstruction: operation(+ self._live_handle(), other._live_handle(), output, obstruction+ ),+ )+ return Region(self._native, handle)++ def _with_element(+ self, element: StructuringElement, operation: _MorphologyNativeOperation+ ) -> tuple[Region, MinkowskiReceipt]:+ self._require_same_runtime(element)+ return self._morph(+ lambda output, receipt, obstruction: operation(+ element._live_handle(), self._live_handle(), output, receipt, obstruction+ )+ )++ def _morph(+ self,+ operation: Callable[[object, object, object], int],+ ) -> tuple[Region, MinkowskiReceipt]:+ handle, native_receipt = _produce_morphology(self._native, operation)+ return Region(self._native, handle), _receipt(native_receipt)++ def _counts(self) -> tuple[int, int, int]:+ component_count = ctypes.c_size_t()+ loop_count = ctypes.c_size_t()+ point_count = ctypes.c_size_t() obstruction = _Obstruction() status = int(- operation(+ self._native.library.ml_region_counts( self._live_handle(),- other._live_handle(),- ctypes.byref(output),+ ctypes.byref(component_count),+ ctypes.byref(loop_count),+ ctypes.byref(point_count), ctypes.byref(obstruction), ) ) self._native.check(status, obstruction)- return Mesh(self._native, _required_handle(output))+ return int(component_count.value), int(loop_count.value), int(point_count.value) - def _count(self, operation: Callable[[ctypes.c_void_p, object, object], int]) -> int:- output = ctypes.c_size_t()+ def _measure_with_capacity(self, capacity: int) -> RegionValuations:+ euler = ctypes.c_int64()+ area = ctypes.create_string_buffer(capacity)+ area_bytes = ctypes.c_size_t()+ lower = ctypes.c_double()+ upper = ctypes.c_double() obstruction = _Obstruction()- status = int(operation(self._live_handle(), ctypes.byref(output), ctypes.byref(obstruction)))+ status = int(+ self._native.library.ml_region_measure(+ self._live_handle(),+ ctypes.byref(euler),+ area,+ capacity,+ ctypes.byref(area_bytes),+ ctypes.byref(lower),+ ctypes.byref(upper),+ ctypes.byref(obstruction),+ )+ )+ if status == 3 and obstruction.code == 102:+ return self._measure_with_capacity(int(area_bytes.value) + 1) self._native.check(status, obstruction)- return int(output.value)+ numerator, separator, denominator = area.value.decode("ascii").partition("/")+ if separator != "/":+ raise RuntimeError("Moonlight returned a malformed exact-area ratio")+ return RegionValuations(int(euler.value), Fraction(int(numerator), int(denominator)), (lower.value, upper.value)) - def _live_handle(self) -> ctypes.c_void_p:- if not self._finalizer.alive:- raise RuntimeError("mesh is closed")- return self._handle +class StructuringElement(_OwnedHandle):+ __slots__ = () -def _coordinate_buffer(points: Sequence[Point]) -> tuple[object, object]:- values = tuple(component for x, y in points for component in (x, y))- if not values:- return (), None- coordinates = (ctypes.c_double * len(values))(*values)- return coordinates, coordinates+ def __init__(self, native: _NativeApi, handle: ctypes.c_void_p) -> None:+ super().__init__(native, handle, native.library.ml_structuring_element_free, "structuring element") +def _component_layout(+ components: Sequence[PolygonComponent],+) -> tuple[tuple[Sequence[Point], ...], tuple[int, ...], tuple[int, ...]]:+ loops = tuple(+ loop+ for component in components+ for loop in (component.outer, *component.holes)+ )+ loop_counts = tuple(len(loop) for loop in loops)+ component_counts = tuple(len(component.holes) + 1 for component in components)+ return loops, loop_counts, component_counts+++def _coordinate_buffer(points: Iterable[Point]) -> tuple[array, object]:+ coordinates = array("d", (component for x, y in points for component in (x, y)))+ if coordinates.itemsize != ctypes.sizeof(ctypes.c_double):+ raise RuntimeError("Python's native double width does not match the Moonlight ABI")+ if not coordinates:+ return coordinates, None+ pointer = (ctypes.c_double * len(coordinates)).from_buffer(coordinates)+ return coordinates, pointer+++def _size_buffer(values: Sequence[int]) -> object:+ return (ctypes.c_size_t * len(values))(*values)+++def _produce_handle(+ native: _NativeApi,+ operation: Callable[[object, object], int],+) -> ctypes.c_void_p:+ output = ctypes.c_void_p()+ obstruction = _Obstruction()+ status = int(operation(ctypes.byref(output), ctypes.byref(obstruction)))+ native.check(status, obstruction)+ return _required_handle(output)+++def _produce_morphology(+ native: _NativeApi,+ operation: Callable[[object, object, object], int],+) -> tuple[ctypes.c_void_p, _NativeMinkowskiReceipt]:+ output = ctypes.c_void_p()+ receipt = _NativeMinkowskiReceipt()+ obstruction = _Obstruction()+ status = int(operation(ctypes.byref(output), ctypes.byref(receipt), ctypes.byref(obstruction)))+ native.check(status, obstruction)+ return _required_handle(output), receipt+++def _receipt(native: _NativeMinkowskiReceipt) -> MinkowskiReceipt:+ return MinkowskiReceipt(+ MinkowskiOperation(native.operation),+ int(native.input_components),+ int(native.convex_pieces),+ int(native.generated_pieces),+ int(native.generated_convolution_edges),+ int(native.overlay_passes),+ int(native.exact_crossings),+ int(native.output_cells),+ int(native.exact_coordinate_bit_growth),+ )++ def _required_handle(handle: ctypes.c_void_p) -> ctypes.c_void_p: if not handle.value:- raise RuntimeError("Moonlight returned success without a mesh handle")+ raise RuntimeError("Moonlight returned success without a handle") return handle @@ -244,4 +608,17 @@ setattr(function, "restype", result) -__all__ = ["Mesh", "Moonlight", "MoonlightError", "Point", "Triangle"]+__all__ = [+ "Mesh",+ "MinkowskiOperation",+ "MinkowskiReceipt",+ "Moonlight",+ "MoonlightError",+ "Point",+ "PolygonComponent",+ "Region",+ "RegionLocation",+ "RegionValuations",+ "StructuringElement",+ "Triangle",+]
bindings/python/tests/test_binding.py view
@@ -3,9 +3,16 @@ import math import os import unittest+from fractions import Fraction from typing import ClassVar -from moonlight_triangulation import Moonlight, MoonlightError+from moonlight_triangulation import (+ MinkowskiOperation,+ Moonlight,+ MoonlightError,+ PolygonComponent,+ RegionLocation,+) class MoonlightBindingTest(unittest.TestCase):@@ -15,13 +22,13 @@ def setUpClass(cls) -> None: cls.engine = Moonlight(os.environ["MOONLIGHT_TRIANGULATION_LIBRARY"]) - def test_immutable_mesh_algebra_and_dense_projection(self) -> None:+ def test_immutable_site_set_algebra_and_dense_projection(self) -> None: left = self.engine.delaunay([(0, 0), (2, 0), (0, 2), (2, 2)]) right = self.engine.delaunay([(2, 0), (4, 0), (2, 2), (4, 2)])- union = left.union(right)- intersection = left.intersection(right)- difference = left.difference(right)- symmetric = left.symmetric_difference(right)+ union = left.site_union(right)+ intersection = left.site_intersection(right)+ difference = left.site_difference(right)+ symmetric = left.site_symmetric_difference(right) extended = left.insert_many([(1, 1), (3, 1)]) self.addCleanup(left.close) self.addCleanup(right.close)@@ -40,6 +47,34 @@ self.assertEqual(len(left.vertices), left.vertex_count) self.assertEqual(len(left.triangles), left.triangle_count) self.assertTrue(all(max(triangle) < left.vertex_count for triangle in left.triangles))++ def test_exact_region_boolean_valuation_and_morphology(self) -> None:+ left = self.engine.region([PolygonComponent(((0, 0), (2, 0), (2, 2), (0, 2)))])+ right = self.engine.region([PolygonComponent(((1, 0), (3, 0), (3, 2), (1, 2)))])+ intersection = left.intersection(right)+ difference = left.difference(right)+ symmetric = left.symmetric_difference(right)+ kernel = self.engine.structuring_element(((-0.5, -0.5), (0.5, -0.5), (0.5, 0.5), (-0.5, 0.5)))+ offset, receipt = left.offset(kernel)+ self.addCleanup(left.close)+ self.addCleanup(right.close)+ self.addCleanup(intersection.close)+ self.addCleanup(difference.close)+ self.addCleanup(symmetric.close)+ self.addCleanup(kernel.close)+ self.addCleanup(offset.close)++ self.assertEqual(len(left.components), 1)+ self.assertEqual(len(left.components[0].outer), 4)+ self.assertEqual(intersection.valuations.area, Fraction(2, 1))+ self.assertEqual(difference.valuations.euler_characteristic, 1)+ self.assertEqual(symmetric.valuations.euler_characteristic, 2)+ self.assertEqual(left.locate((1, 1)), RegionLocation.INTERIOR)+ self.assertEqual(left.locate((0, 1)), RegionLocation.BOUNDARY)+ self.assertEqual(left.locate((3, 1)), RegionLocation.EXTERIOR)+ self.assertEqual(offset.valuations.area, Fraction(9, 1))+ self.assertEqual(receipt.operation, MinkowskiOperation.ADDITION)+ self.assertGreaterEqual(receipt.generated_pieces, 1) def test_invalid_coordinate_preserves_typed_witness(self) -> None: with self.assertRaises(MoonlightError) as raised:
bindings/rust/Cargo.toml view
@@ -1,9 +1,9 @@ [package] name = "moonlight-triangulation"-version = "1.0.0"+version = "2.0.0" edition = "2024" license = "MIT"-description = "Rust bindings for Moonlight's immutable Delaunay mesh algebra."+description = "Rust bindings for Moonlight exact regions and immutable Delaunay meshes." repository = "https://github.com/PaleRoses/moonlight.git" build = "build.rs" links = "moonlight-triangulation-c"
bindings/rust/src/lib.rs view
@@ -6,7 +6,8 @@ use std::rc::Rc; const STATUS_OK: u32 = 0;-const ABI_VERSION: u32 = 1;+const STATUS_BUFFER_TOO_SMALL: u32 = 3;+const ABI_VERSION: u32 = 2; #[repr(C)] struct NativeMesh {@@ -14,6 +15,16 @@ } #[repr(C)]+struct NativeRegion {+ _private: [u8; 0],+}++#[repr(C)]+struct NativeStructuringElement {+ _private: [u8; 0],+}++#[repr(C)] struct NativeObstruction { code: u32, coordinate_error: u32,@@ -44,6 +55,21 @@ } } +#[repr(C)]+#[derive(Default)]+struct NativeMinkowskiReceipt {+ operation: u32,+ reserved: u32,+ input_components: u64,+ convex_pieces: u64,+ generated_pieces: u64,+ generated_convolution_edges: u64,+ overlay_passes: u64,+ exact_crossings: u64,+ output_cells: u64,+ exact_coordinate_bit_growth: u64,+}+ #[link(name = "moonlight-triangulation-c")] unsafe extern "C" { fn ml_abi_version() -> c_uint;@@ -61,25 +87,25 @@ result: *mut *mut NativeMesh, obstruction: *mut NativeObstruction, ) -> c_uint;- fn ml_mesh_union(+ fn ml_mesh_site_union( left: *const NativeMesh, right: *const NativeMesh, result: *mut *mut NativeMesh, obstruction: *mut NativeObstruction, ) -> c_uint;- fn ml_mesh_intersection(+ fn ml_mesh_site_intersection( left: *const NativeMesh, right: *const NativeMesh, result: *mut *mut NativeMesh, obstruction: *mut NativeObstruction, ) -> c_uint;- fn ml_mesh_difference(+ fn ml_mesh_site_difference( left: *const NativeMesh, right: *const NativeMesh, result: *mut *mut NativeMesh, obstruction: *mut NativeObstruction, ) -> c_uint;- fn ml_mesh_symmetric_difference(+ fn ml_mesh_site_symmetric_difference( left: *const NativeMesh, right: *const NativeMesh, result: *mut *mut NativeMesh,@@ -110,6 +136,119 @@ obstruction: *mut NativeObstruction, ) -> c_uint; fn ml_mesh_free(mesh: *mut NativeMesh);++ fn ml_region_create_f64(+ coordinates: *const c_double,+ point_count: usize,+ loop_point_counts: *const usize,+ loop_count: usize,+ component_loop_counts: *const usize,+ component_count: usize,+ result: *mut *mut NativeRegion,+ obstruction: *mut NativeObstruction,+ ) -> c_uint;+ fn ml_region_counts(+ region: *const NativeRegion,+ component_count: *mut usize,+ loop_count: *mut usize,+ point_count: *mut usize,+ obstruction: *mut NativeObstruction,+ ) -> c_uint;+ fn ml_region_copy_f64(+ region: *const NativeRegion,+ coordinates: *mut c_double,+ point_capacity: usize,+ loop_point_offsets: *mut usize,+ loop_offset_capacity: usize,+ component_loop_offsets: *mut usize,+ component_offset_capacity: usize,+ obstruction: *mut NativeObstruction,+ ) -> c_uint;+ fn ml_region_union(+ left: *const NativeRegion,+ right: *const NativeRegion,+ result: *mut *mut NativeRegion,+ obstruction: *mut NativeObstruction,+ ) -> c_uint;+ fn ml_region_intersection(+ left: *const NativeRegion,+ right: *const NativeRegion,+ result: *mut *mut NativeRegion,+ obstruction: *mut NativeObstruction,+ ) -> c_uint;+ fn ml_region_difference(+ left: *const NativeRegion,+ right: *const NativeRegion,+ result: *mut *mut NativeRegion,+ obstruction: *mut NativeObstruction,+ ) -> c_uint;+ fn ml_region_symmetric_difference(+ left: *const NativeRegion,+ right: *const NativeRegion,+ result: *mut *mut NativeRegion,+ obstruction: *mut NativeObstruction,+ ) -> c_uint;+ fn ml_region_locate_point_f64(+ region: *const NativeRegion,+ x: c_double,+ y: c_double,+ location: *mut u32,+ obstruction: *mut NativeObstruction,+ ) -> c_uint;+ fn ml_region_measure(+ region: *const NativeRegion,+ euler_characteristic: *mut i64,+ area_ratio_utf8: *mut c_char,+ area_capacity: usize,+ area_bytes_written: *mut usize,+ perimeter_lower: *mut c_double,+ perimeter_upper: *mut c_double,+ obstruction: *mut NativeObstruction,+ ) -> c_uint;+ fn ml_region_free(region: *mut NativeRegion);++ fn ml_structuring_element_create_f64(+ coordinates: *const c_double,+ point_count: usize,+ result: *mut *mut NativeStructuringElement,+ obstruction: *mut NativeObstruction,+ ) -> c_uint;+ fn ml_structuring_element_free(element: *mut NativeStructuringElement);+ fn ml_region_minkowski_sum(+ left: *const NativeRegion,+ right: *const NativeRegion,+ result: *mut *mut NativeRegion,+ receipt: *mut NativeMinkowskiReceipt,+ obstruction: *mut NativeObstruction,+ ) -> c_uint;+ fn ml_region_offset(+ element: *const NativeStructuringElement,+ region: *const NativeRegion,+ result: *mut *mut NativeRegion,+ receipt: *mut NativeMinkowskiReceipt,+ obstruction: *mut NativeObstruction,+ ) -> c_uint;+ fn ml_region_inset(+ element: *const NativeStructuringElement,+ region: *const NativeRegion,+ result: *mut *mut NativeRegion,+ receipt: *mut NativeMinkowskiReceipt,+ obstruction: *mut NativeObstruction,+ ) -> c_uint;+ fn ml_region_open(+ element: *const NativeStructuringElement,+ region: *const NativeRegion,+ result: *mut *mut NativeRegion,+ receipt: *mut NativeMinkowskiReceipt,+ obstruction: *mut NativeObstruction,+ ) -> c_uint;+ fn ml_region_close(+ element: *const NativeStructuringElement,+ region: *const NativeRegion,+ result: *mut *mut NativeRegion,+ receipt: *mut NativeMinkowskiReceipt,+ obstruction: *mut NativeObstruction,+ ) -> c_uint; } type BinaryMeshOperation = unsafe extern "C" fn(@@ -119,6 +258,21 @@ *mut NativeObstruction, ) -> c_uint; +type BinaryRegionOperation = unsafe extern "C" fn(+ *const NativeRegion,+ *const NativeRegion,+ *mut *mut NativeRegion,+ *mut NativeObstruction,+) -> c_uint;++type ElementRegionOperation = unsafe extern "C" fn(+ *const NativeStructuringElement,+ *const NativeRegion,+ *mut *mut NativeRegion,+ *mut NativeMinkowskiReceipt,+ *mut NativeObstruction,+) -> c_uint;+ #[derive(Debug, Clone, PartialEq)] pub struct MoonlightError { pub status: u32,@@ -141,6 +295,53 @@ impl Error for MoonlightError {} +#[derive(Debug, Clone, PartialEq)]+pub struct PolygonComponent {+ pub outer: Vec<[f64; 2]>,+ pub holes: Vec<Vec<[f64; 2]>>,+}++#[derive(Debug, Clone, Copy, PartialEq, Eq)]+pub enum RegionLocation {+ Exterior,+ Boundary,+ Interior,+}++#[derive(Debug, Clone, PartialEq, Eq)]+pub struct ExactRational {+ pub numerator: String,+ pub denominator: String,+}++#[derive(Debug, Clone, PartialEq)]+pub struct RegionValuations {+ pub euler_characteristic: i64,+ pub area: ExactRational,+ pub perimeter_bounds: [f64; 2],+}++#[derive(Debug, Clone, Copy, PartialEq, Eq)]+pub enum MinkowskiOperation {+ Addition,+ Erosion,+ Opening,+ Closing,+}++#[derive(Debug, Clone, PartialEq, Eq)]+pub struct MinkowskiReceipt {+ pub operation: MinkowskiOperation,+ pub input_components: u64,+ pub convex_pieces: u64,+ pub generated_pieces: u64,+ pub generated_convolution_edges: u64,+ pub overlay_passes: u64,+ pub exact_crossings: u64,+ pub output_cells: u64,+ pub exact_coordinate_bit_growth: u64,+}+ pub struct Moonlight; impl Moonlight {@@ -168,10 +369,58 @@ pub fn delaunay(&self, points: &[[f64; 2]]) -> Result<Mesh, MoonlightError> { let coordinates = flatten_points(points);- create_mesh(|output, obstruction| unsafe {+ create_handle(|output, obstruction| unsafe { ml_delaunay_f64(coordinates.as_ptr(), points.len(), output, obstruction) })+ .map(Mesh::from_handle) }++ pub fn region(&self, components: &[PolygonComponent]) -> Result<Region, MoonlightError> {+ let loop_point_counts = components+ .iter()+ .flat_map(|component| std::iter::once(&component.outer).chain(component.holes.iter()))+ .map(Vec::len)+ .collect::<Vec<_>>();+ let component_loop_counts = components+ .iter()+ .map(|component| component.holes.len() + 1)+ .collect::<Vec<_>>();+ let coordinates = components+ .iter()+ .flat_map(|component| std::iter::once(&component.outer).chain(component.holes.iter()))+ .flat_map(|loop_points| loop_points.iter())+ .flat_map(|[x, y]| [*x, *y])+ .collect::<Vec<_>>();+ create_handle(|output, obstruction| unsafe {+ ml_region_create_f64(+ coordinates.as_ptr(),+ coordinates.len() / 2,+ loop_point_counts.as_ptr(),+ loop_point_counts.len(),+ component_loop_counts.as_ptr(),+ components.len(),+ output,+ obstruction,+ )+ })+ .map(Region::from_handle)+ }++ pub fn structuring_element(+ &self,+ points: &[[f64; 2]],+ ) -> Result<StructuringElement, MoonlightError> {+ let coordinates = flatten_points(points);+ create_handle(|output, obstruction| unsafe {+ ml_structuring_element_create_f64(+ coordinates.as_ptr(),+ points.len(),+ output,+ obstruction,+ )+ })+ .map(StructuringElement::from_handle)+ } } pub struct Mesh {@@ -180,6 +429,13 @@ } impl Mesh {+ fn from_handle(handle: NonNull<NativeMesh>) -> Self {+ Self {+ handle,+ _thread_affinity: PhantomData,+ }+ }+ pub fn vertex_count(&self) -> Result<usize, MoonlightError> { self.count(ml_mesh_vertex_count) }@@ -204,10 +460,7 @@ }; status_result(status, obstruction)?; coordinates.truncate(written * 2);- Ok(coordinates- .chunks_exact(2)- .map(|point| [point[0], point[1]])- .collect())+ coordinate_pairs(&coordinates) } pub fn triangles(&self) -> Result<Vec<[u32; 3]>, MoonlightError> {@@ -226,15 +479,21 @@ }; status_result(status, obstruction)?; triangles.truncate(written * 3);- Ok(triangles+ triangles .chunks_exact(3)- .map(|triangle| [triangle[0], triangle[1], triangle[2]])- .collect())+ .map(|triangle| {+ Ok([+ *triangle.first().ok_or_else(projection_shape_error)?,+ *triangle.get(1).ok_or_else(projection_shape_error)?,+ *triangle.get(2).ok_or_else(projection_shape_error)?,+ ])+ })+ .collect() } pub fn insert_many(&self, points: &[[f64; 2]]) -> Result<Self, MoonlightError> { let coordinates = flatten_points(points);- create_mesh(|output, obstruction| unsafe {+ create_handle(|output, obstruction| unsafe { ml_mesh_insert_many_f64( self.handle.as_ptr(), coordinates.as_ptr(),@@ -243,26 +502,27 @@ obstruction, ) })+ .map(Self::from_handle) } - pub fn union(&self, other: &Self) -> Result<Self, MoonlightError> {- self.binary(other, ml_mesh_union)+ pub fn site_union(&self, other: &Self) -> Result<Self, MoonlightError> {+ self.binary(other, ml_mesh_site_union) } - pub fn intersection(&self, other: &Self) -> Result<Self, MoonlightError> {- self.binary(other, ml_mesh_intersection)+ pub fn site_intersection(&self, other: &Self) -> Result<Self, MoonlightError> {+ self.binary(other, ml_mesh_site_intersection) } - pub fn difference(&self, other: &Self) -> Result<Self, MoonlightError> {- self.binary(other, ml_mesh_difference)+ pub fn site_difference(&self, other: &Self) -> Result<Self, MoonlightError> {+ self.binary(other, ml_mesh_site_difference) } - pub fn symmetric_difference(&self, other: &Self) -> Result<Self, MoonlightError> {- self.binary(other, ml_mesh_symmetric_difference)+ pub fn site_symmetric_difference(&self, other: &Self) -> Result<Self, MoonlightError> {+ self.binary(other, ml_mesh_site_symmetric_difference) } fn binary(&self, other: &Self, operation: BinaryMeshOperation) -> Result<Self, MoonlightError> {- create_mesh(|output, obstruction| unsafe {+ create_handle(|output, obstruction| unsafe { operation( self.handle.as_ptr(), other.handle.as_ptr(),@@ -270,6 +530,7 @@ obstruction, ) })+ .map(Self::from_handle) } fn count(@@ -294,26 +555,351 @@ } } +pub struct Region {+ handle: NonNull<NativeRegion>,+ _thread_affinity: PhantomData<Rc<()>>,+}++impl Region {+ fn from_handle(handle: NonNull<NativeRegion>) -> Self {+ Self {+ handle,+ _thread_affinity: PhantomData,+ }+ }++ pub fn components(&self) -> Result<Vec<PolygonComponent>, MoonlightError> {+ let (component_count, loop_count, point_count) = self.counts()?;+ let mut coordinates = vec![0.0; point_count * 2];+ let mut loop_point_offsets = vec![0; loop_count + 1];+ let mut component_loop_offsets = vec![0; component_count + 1];+ let mut obstruction = NativeObstruction::default();+ let status = unsafe {+ ml_region_copy_f64(+ self.handle.as_ptr(),+ coordinates.as_mut_ptr(),+ point_count,+ loop_point_offsets.as_mut_ptr(),+ loop_point_offsets.len(),+ component_loop_offsets.as_mut_ptr(),+ component_loop_offsets.len(),+ &mut obstruction,+ )+ };+ status_result(status, obstruction)?;+ let points = coordinate_pairs(&coordinates)?;+ component_loop_offsets+ .windows(2)+ .map(|window| {+ let (start, end) = offset_window(window)?;+ if start >= end {+ return Err(projection_shape_error());+ }+ let outer = projected_loop(&points, &loop_point_offsets, start)?;+ let holes = (start + 1..end)+ .map(|index| projected_loop(&points, &loop_point_offsets, index))+ .collect::<Result<Vec<_>, _>>()?;+ Ok(PolygonComponent { outer, holes })+ })+ .collect()+ }++ pub fn valuations(&self) -> Result<RegionValuations, MoonlightError> {+ self.measure_with_capacity(128)+ }++ pub fn locate(&self, [x, y]: [f64; 2]) -> Result<RegionLocation, MoonlightError> {+ let mut location = 0;+ let mut obstruction = NativeObstruction::default();+ let status = unsafe {+ ml_region_locate_point_f64(self.handle.as_ptr(), x, y, &mut location, &mut obstruction)+ };+ status_result(status, obstruction)?;+ region_location(location)+ }++ pub fn union(&self, other: &Self) -> Result<Self, MoonlightError> {+ self.binary(other, ml_region_union)+ }++ pub fn intersection(&self, other: &Self) -> Result<Self, MoonlightError> {+ self.binary(other, ml_region_intersection)+ }++ pub fn difference(&self, other: &Self) -> Result<Self, MoonlightError> {+ self.binary(other, ml_region_difference)+ }++ pub fn symmetric_difference(&self, other: &Self) -> Result<Self, MoonlightError> {+ self.binary(other, ml_region_symmetric_difference)+ }++ pub fn minkowski_sum(&self, other: &Self) -> Result<(Self, MinkowskiReceipt), MoonlightError> {+ produce_morphology(|output, receipt, obstruction| unsafe {+ ml_region_minkowski_sum(+ self.handle.as_ptr(),+ other.handle.as_ptr(),+ output,+ receipt,+ obstruction,+ )+ })+ }++ pub fn offset(+ &self,+ element: &StructuringElement,+ ) -> Result<(Self, MinkowskiReceipt), MoonlightError> {+ self.with_element(element, ml_region_offset)+ }++ pub fn inset(+ &self,+ element: &StructuringElement,+ ) -> Result<(Self, MinkowskiReceipt), MoonlightError> {+ self.with_element(element, ml_region_inset)+ }++ pub fn open(+ &self,+ element: &StructuringElement,+ ) -> Result<(Self, MinkowskiReceipt), MoonlightError> {+ self.with_element(element, ml_region_open)+ }++ pub fn close_with(+ &self,+ element: &StructuringElement,+ ) -> Result<(Self, MinkowskiReceipt), MoonlightError> {+ self.with_element(element, ml_region_close)+ }++ fn binary(+ &self,+ other: &Self,+ operation: BinaryRegionOperation,+ ) -> Result<Self, MoonlightError> {+ create_handle(|output, obstruction| unsafe {+ operation(+ self.handle.as_ptr(),+ other.handle.as_ptr(),+ output,+ obstruction,+ )+ })+ .map(Self::from_handle)+ }++ fn with_element(+ &self,+ element: &StructuringElement,+ operation: ElementRegionOperation,+ ) -> Result<(Self, MinkowskiReceipt), MoonlightError> {+ produce_morphology(|output, receipt, obstruction| unsafe {+ operation(+ element.handle.as_ptr(),+ self.handle.as_ptr(),+ output,+ receipt,+ obstruction,+ )+ })+ }++ fn counts(&self) -> Result<(usize, usize, usize), MoonlightError> {+ let mut component_count = 0;+ let mut loop_count = 0;+ let mut point_count = 0;+ let mut obstruction = NativeObstruction::default();+ let status = unsafe {+ ml_region_counts(+ self.handle.as_ptr(),+ &mut component_count,+ &mut loop_count,+ &mut point_count,+ &mut obstruction,+ )+ };+ status_result(status, obstruction)?;+ Ok((component_count, loop_count, point_count))+ }++ fn measure_with_capacity(&self, capacity: usize) -> Result<RegionValuations, MoonlightError> {+ let mut euler_characteristic = 0;+ let mut area_ratio = vec![0; capacity];+ let mut area_bytes_written = 0;+ let mut perimeter_lower = 0.0;+ let mut perimeter_upper = 0.0;+ let mut obstruction = NativeObstruction::default();+ let status = unsafe {+ ml_region_measure(+ self.handle.as_ptr(),+ &mut euler_characteristic,+ area_ratio.as_mut_ptr(),+ capacity,+ &mut area_bytes_written,+ &mut perimeter_lower,+ &mut perimeter_upper,+ &mut obstruction,+ )+ };+ if status == STATUS_BUFFER_TOO_SMALL && obstruction.code == 102 {+ return self.measure_with_capacity(area_bytes_written + 1);+ }+ status_result(status, obstruction)?;+ let area_bytes = area_ratio+ .get(..area_bytes_written)+ .ok_or_else(projection_shape_error)?+ .iter()+ .map(|byte| *byte as u8)+ .collect::<Vec<_>>();+ let area_text = String::from_utf8(area_bytes)+ .map_err(|failure| synthetic_error(5, failure.to_string(), 0, 0))?;+ let (numerator, denominator) = area_text+ .split_once('/')+ .ok_or_else(|| synthetic_error(5, "malformed exact-area ratio", 0, 0))?;+ Ok(RegionValuations {+ euler_characteristic,+ area: ExactRational {+ numerator: numerator.to_owned(),+ denominator: denominator.to_owned(),+ },+ perimeter_bounds: [perimeter_lower, perimeter_upper],+ })+ }+}++impl Drop for Region {+ fn drop(&mut self) {+ unsafe { ml_region_free(self.handle.as_ptr()) }+ }+}++pub struct StructuringElement {+ handle: NonNull<NativeStructuringElement>,+ _thread_affinity: PhantomData<Rc<()>>,+}++impl StructuringElement {+ fn from_handle(handle: NonNull<NativeStructuringElement>) -> Self {+ Self {+ handle,+ _thread_affinity: PhantomData,+ }+ }+}++impl Drop for StructuringElement {+ fn drop(&mut self) {+ unsafe { ml_structuring_element_free(self.handle.as_ptr()) }+ }+}+ fn flatten_points(points: &[[f64; 2]]) -> Vec<f64> { points.iter().flat_map(|[x, y]| [*x, *y]).collect() } -#[inline]-fn create_mesh(- operation: impl FnOnce(*mut *mut NativeMesh, *mut NativeObstruction) -> u32,-) -> Result<Mesh, MoonlightError> {+fn coordinate_pairs(coordinates: &[f64]) -> Result<Vec<[f64; 2]>, MoonlightError> {+ coordinates+ .chunks_exact(2)+ .map(|point| {+ Ok([+ *point.first().ok_or_else(projection_shape_error)?,+ *point.get(1).ok_or_else(projection_shape_error)?,+ ])+ })+ .collect()+}++fn offset_window(window: &[usize]) -> Result<(usize, usize), MoonlightError> {+ Ok((+ *window.first().ok_or_else(projection_shape_error)?,+ *window.get(1).ok_or_else(projection_shape_error)?,+ ))+}++fn projected_loop(+ points: &[[f64; 2]],+ offsets: &[usize],+ index: usize,+) -> Result<Vec<[f64; 2]>, MoonlightError> {+ let start = *offsets.get(index).ok_or_else(projection_shape_error)?;+ let end = *offsets.get(index + 1).ok_or_else(projection_shape_error)?;+ points+ .get(start..end)+ .map(<[_]>::to_vec)+ .ok_or_else(projection_shape_error)+}++fn create_handle<Native>(+ operation: impl FnOnce(*mut *mut Native, *mut NativeObstruction) -> u32,+) -> Result<NonNull<Native>, MoonlightError> { let mut output = std::ptr::null_mut(); let mut obstruction = NativeObstruction::default(); let status = operation(&mut output, &mut obstruction); status_result(status, obstruction)?;- NonNull::new(output)- .map(|handle| Mesh {- handle,- _thread_affinity: PhantomData,- })- .ok_or_else(success_without_handle_error)+ NonNull::new(output).ok_or_else(success_without_handle_error) } +fn produce_morphology(+ operation: impl FnOnce(+ *mut *mut NativeRegion,+ *mut NativeMinkowskiReceipt,+ *mut NativeObstruction,+ ) -> u32,+) -> Result<(Region, MinkowskiReceipt), MoonlightError> {+ let mut output = std::ptr::null_mut();+ let mut receipt = NativeMinkowskiReceipt::default();+ let mut obstruction = NativeObstruction::default();+ let status = operation(&mut output, &mut receipt, &mut obstruction);+ status_result(status, obstruction)?;+ let handle = NonNull::new(output).ok_or_else(success_without_handle_error)?;+ Ok((Region::from_handle(handle), minkowski_receipt(receipt)?))+}++fn minkowski_receipt(receipt: NativeMinkowskiReceipt) -> Result<MinkowskiReceipt, MoonlightError> {+ Ok(MinkowskiReceipt {+ operation: minkowski_operation(receipt.operation)?,+ input_components: receipt.input_components,+ convex_pieces: receipt.convex_pieces,+ generated_pieces: receipt.generated_pieces,+ generated_convolution_edges: receipt.generated_convolution_edges,+ overlay_passes: receipt.overlay_passes,+ exact_crossings: receipt.exact_crossings,+ output_cells: receipt.output_cells,+ exact_coordinate_bit_growth: receipt.exact_coordinate_bit_growth,+ })+}++fn minkowski_operation(code: u32) -> Result<MinkowskiOperation, MoonlightError> {+ match code {+ 0 => Ok(MinkowskiOperation::Addition),+ 1 => Ok(MinkowskiOperation::Erosion),+ 2 => Ok(MinkowskiOperation::Opening),+ 3 => Ok(MinkowskiOperation::Closing),+ _ => Err(synthetic_error(+ 5,+ format!("unknown morphology operation {code}"),+ u64::from(code),+ 0,+ )),+ }+}++fn region_location(code: u32) -> Result<RegionLocation, MoonlightError> {+ match code {+ 0 => Ok(RegionLocation::Exterior),+ 1 => Ok(RegionLocation::Boundary),+ 2 => Ok(RegionLocation::Interior),+ _ => Err(synthetic_error(+ 5,+ format!("unknown region location {code}"),+ u64::from(code),+ 0,+ )),+ }+}+ fn status_result(status: u32, obstruction: NativeObstruction) -> Result<(), MoonlightError> { if status == STATUS_OK { Ok(())@@ -329,7 +915,10 @@ .iter() .position(|character| *character == 0) .unwrap_or(obstruction.message.len());- let message_bytes = obstruction.message[..message_end]+ let message_bytes = obstruction+ .message+ .get(..message_end)+ .unwrap_or_default() .iter() .map(|character| *character as u8) .collect::<Vec<_>>();@@ -348,8 +937,12 @@ } } +fn projection_shape_error() -> MoonlightError {+ synthetic_error(5, "Moonlight returned a malformed bulk projection", 0, 0)+}+ fn success_without_handle_error() -> MoonlightError {- synthetic_error(5, "Moonlight returned success without a mesh handle", 0, 0)+ synthetic_error(5, "Moonlight returned success without a handle", 0, 0) } fn synthetic_error(
bindings/rust/tests/binding.rs view
@@ -1,7 +1,9 @@-use moonlight_triangulation::Moonlight;+use moonlight_triangulation::{+ ExactRational, MinkowskiOperation, Moonlight, PolygonComponent, RegionLocation,+}; #[test]-fn immutable_mesh_algebra_and_dense_projection() {+fn immutable_site_set_algebra_and_dense_projection() { let engine = Moonlight::initialize().expect("runtime"); let left = engine .delaunay(&[[0.0, 0.0], [2.0, 0.0], [0.0, 2.0], [2.0, 2.0]])@@ -9,12 +11,12 @@ let right = engine .delaunay(&[[2.0, 0.0], [4.0, 0.0], [2.0, 2.0], [4.0, 2.0]]) .expect("right mesh");- let union = left.union(&right).expect("union");- let intersection = left.intersection(&right).expect("intersection");- let difference = left.difference(&right).expect("difference");+ let union = left.site_union(&right).expect("site union");+ let intersection = left.site_intersection(&right).expect("site intersection");+ let difference = left.site_difference(&right).expect("site difference"); let symmetric = left- .symmetric_difference(&right)- .expect("symmetric difference");+ .site_symmetric_difference(&right)+ .expect("site symmetric difference"); let extended = left .insert_many(&[[1.0, 1.0], [3.0, 1.0]]) .expect("batch insertion");@@ -33,6 +35,71 @@ left.triangles().expect("triangles").len(), left.triangle_count().expect("triangle count") );+}++#[test]+fn exact_region_boolean_valuation_and_morphology() {+ let engine = Moonlight::initialize().expect("runtime");+ let left = engine+ .region(&[PolygonComponent {+ outer: vec![[0.0, 0.0], [2.0, 0.0], [2.0, 2.0], [0.0, 2.0]],+ holes: vec![],+ }])+ .expect("left region");+ let right = engine+ .region(&[PolygonComponent {+ outer: vec![[1.0, 0.0], [3.0, 0.0], [3.0, 2.0], [1.0, 2.0]],+ holes: vec![],+ }])+ .expect("right region");+ let intersection = left.intersection(&right).expect("intersection");+ let symmetric = left+ .symmetric_difference(&right)+ .expect("symmetric difference");+ let kernel = engine+ .structuring_element(&[[-0.5, -0.5], [0.5, -0.5], [0.5, 0.5], [-0.5, 0.5]])+ .expect("structuring element");+ let (offset, receipt) = left.offset(&kernel).expect("offset");++ assert_eq!(left.components().expect("components").len(), 1);+ assert_eq!(+ intersection+ .valuations()+ .expect("intersection valuation")+ .area,+ ExactRational {+ numerator: "2".to_owned(),+ denominator: "1".to_owned(),+ }+ );+ assert_eq!(+ symmetric+ .valuations()+ .expect("symmetric valuation")+ .euler_characteristic,+ 2+ );+ assert_eq!(+ left.locate([1.0, 1.0]).expect("interior"),+ RegionLocation::Interior+ );+ assert_eq!(+ left.locate([0.0, 1.0]).expect("boundary"),+ RegionLocation::Boundary+ );+ assert_eq!(+ left.locate([3.0, 1.0]).expect("exterior"),+ RegionLocation::Exterior+ );+ assert_eq!(+ offset.valuations().expect("offset valuation").area,+ ExactRational {+ numerator: "9".to_owned(),+ denominator: "1".to_owned(),+ }+ );+ assert_eq!(receipt.operation, MinkowskiOperation::Addition);+ assert!(receipt.generated_pieces >= 1); } #[test]
bindings/typescript/package.json view
@@ -1,7 +1,7 @@ { "name": "@moonlight/triangulation",- "version": "1.0.0",- "description": "TypeScript bindings for Moonlight's immutable Delaunay mesh algebra.",+ "version": "2.0.0",+ "description": "TypeScript bindings for Moonlight exact regions and immutable Delaunay meshes.", "type": "module", "main": "./dist/src/index.js", "types": "./dist/src/index.d.ts",
bindings/typescript/src/index.ts view
@@ -1,13 +1,56 @@ import koffi from "koffi"; const STATUS_OK = 0;-const ABI_VERSION = 1;+const STATUS_BUFFER_TOO_SMALL = 3;+const ABI_VERSION = 2; export type Point = readonly [x: number, y: number]; export type Triangle = readonly [first: number, second: number, third: number]; -type MeshHandle = object;+export interface PolygonComponent {+ readonly outer: readonly Point[];+ readonly holes?: readonly (readonly Point[])[];+}++export enum RegionLocation {+ Exterior = 0,+ Boundary = 1,+ Interior = 2,+}++export interface ExactRational {+ readonly numerator: bigint;+ readonly denominator: bigint;+}++export interface RegionValuations {+ readonly eulerCharacteristic: bigint;+ readonly area: ExactRational;+ readonly perimeterBounds: readonly [lower: number, upper: number];+}++export enum MinkowskiOperation {+ Addition = 0,+ Erosion = 1,+ Opening = 2,+ Closing = 3,+}++export interface MinkowskiReceipt {+ readonly operation: MinkowskiOperation;+ readonly inputComponents: bigint;+ readonly convexPieces: bigint;+ readonly generatedPieces: bigint;+ readonly generatedConvolutionEdges: bigint;+ readonly overlayPasses: bigint;+ readonly exactCrossings: bigint;+ readonly outputCells: bigint;+ readonly exactCoordinateBitGrowth: bigint;+}++type NativeHandle = object; type NativeInteger = number | bigint;+type HandleOutput = Array<NativeHandle | null>; interface NativeObstruction { code?: number;@@ -22,33 +65,103 @@ message?: string | readonly number[]; } -type BuildOperation = (coordinates: Float64Array, pointCount: number, output: Array<MeshHandle | null>, obstruction: NativeObstruction) => number;-type InsertOperation = (mesh: MeshHandle, coordinates: Float64Array, pointCount: number, output: Array<MeshHandle | null>, obstruction: NativeObstruction) => number;-type BinaryOperation = (left: MeshHandle, right: MeshHandle, output: Array<MeshHandle | null>, obstruction: NativeObstruction) => number;-type CountOperation = (mesh: MeshHandle, output: number[], obstruction: NativeObstruction) => number;+interface NativeMinkowskiReceipt {+ operation?: number;+ reserved?: number;+ input_components?: NativeInteger;+ convex_pieces?: NativeInteger;+ generated_pieces?: NativeInteger;+ generated_convolution_edges?: NativeInteger;+ overlay_passes?: NativeInteger;+ exact_crossings?: NativeInteger;+ output_cells?: NativeInteger;+ exact_coordinate_bit_growth?: NativeInteger;+} +type BuildOperation = (coordinates: Float64Array, pointCount: number, output: HandleOutput, obstruction: NativeObstruction) => number;+type InsertOperation = (mesh: NativeHandle, coordinates: Float64Array, pointCount: number, output: HandleOutput, obstruction: NativeObstruction) => number;+type BinaryOperation = (left: NativeHandle, right: NativeHandle, output: HandleOutput, obstruction: NativeObstruction) => number;+type CountOperation = (handle: NativeHandle, output: NativeInteger[], obstruction: NativeObstruction) => number;+type MorphologyOperation = (+ first: NativeHandle,+ second: NativeHandle,+ output: HandleOutput,+ receipt: NativeMinkowskiReceipt,+ obstruction: NativeObstruction,+) => number;+ interface NativeApi { readonly abiVersion: () => number; readonly runtimeInitialize: () => number; readonly delaunay: BuildOperation; readonly insertMany: InsertOperation;- readonly union: BinaryOperation;- readonly intersection: BinaryOperation;- readonly difference: BinaryOperation;- readonly symmetricDifference: BinaryOperation;+ readonly meshSiteUnion: BinaryOperation;+ readonly meshSiteIntersection: BinaryOperation;+ readonly meshSiteDifference: BinaryOperation;+ readonly meshSiteSymmetricDifference: BinaryOperation; readonly vertexCount: CountOperation; readonly triangleCount: CountOperation;- readonly copyVertices: (mesh: MeshHandle, coordinates: Float64Array, capacity: number, written: number[], obstruction: NativeObstruction) => number;- readonly copyTriangles: (mesh: MeshHandle, triangles: Uint32Array, capacity: number, written: number[], obstruction: NativeObstruction) => number;- readonly free: (mesh: MeshHandle) => void;+ readonly copyVertices: (mesh: NativeHandle, coordinates: Float64Array, capacity: number, written: NativeInteger[], obstruction: NativeObstruction) => number;+ readonly copyTriangles: (mesh: NativeHandle, triangles: Uint32Array, capacity: number, written: NativeInteger[], obstruction: NativeObstruction) => number;+ readonly meshFree: (mesh: NativeHandle) => void;+ readonly regionCreate: (+ coordinates: Float64Array,+ pointCount: number,+ loopPointCounts: BigUint64Array,+ loopCount: number,+ componentLoopCounts: BigUint64Array,+ componentCount: number,+ output: HandleOutput,+ obstruction: NativeObstruction,+ ) => number;+ readonly regionCounts: (+ region: NativeHandle,+ componentCount: NativeInteger[],+ loopCount: NativeInteger[],+ pointCount: NativeInteger[],+ obstruction: NativeObstruction,+ ) => number;+ readonly regionCopy: (+ region: NativeHandle,+ coordinates: Float64Array,+ pointCapacity: number,+ loopPointOffsets: BigUint64Array,+ loopOffsetCapacity: number,+ componentLoopOffsets: BigUint64Array,+ componentOffsetCapacity: number,+ obstruction: NativeObstruction,+ ) => number;+ readonly regionUnion: BinaryOperation;+ readonly regionIntersection: BinaryOperation;+ readonly regionDifference: BinaryOperation;+ readonly regionSymmetricDifference: BinaryOperation;+ readonly regionLocate: (region: NativeHandle, x: number, y: number, location: number[], obstruction: NativeObstruction) => number;+ readonly regionMeasure: (+ region: NativeHandle,+ euler: NativeInteger[],+ areaRatio: Buffer,+ areaCapacity: number,+ areaBytes: NativeInteger[],+ perimeterLower: number[],+ perimeterUpper: number[],+ obstruction: NativeObstruction,+ ) => number;+ readonly regionFree: (region: NativeHandle) => void;+ readonly structuringElementCreate: BuildOperation;+ readonly structuringElementFree: (element: NativeHandle) => void;+ readonly regionMinkowskiSum: MorphologyOperation;+ readonly regionOffset: MorphologyOperation;+ readonly regionInset: MorphologyOperation;+ readonly regionOpen: MorphologyOperation;+ readonly regionClose: MorphologyOperation; } interface FinalizerState {- readonly free: (mesh: MeshHandle) => void;- readonly handle: MeshHandle;+ readonly free: (handle: NativeHandle) => void;+ readonly handle: NativeHandle; } -const meshFinalizer = new FinalizationRegistry<FinalizerState>(({ free, handle }) => free(handle));+const handleFinalizer = new FinalizationRegistry<FinalizerState>(({ free, handle }) => free(handle)); export class MoonlightError extends Error { readonly status: number;@@ -96,49 +209,117 @@ } delaunay(points: readonly Point[]): Mesh {- const output: Array<MeshHandle | null> = [null];- const obstruction: NativeObstruction = {};- const status = this.#native.delaunay(flattenPoints(points), points.length, output, obstruction);- checkStatus(status, obstruction);- return new Mesh(this.#native, requiredHandle(output[0]));+ const coordinates = flattenPoints(points);+ return new Mesh(+ this.#native,+ produceHandle((output, obstruction) =>+ this.#native.delaunay(coordinates, points.length, output, obstruction),+ ),+ ); }++ region(components: readonly PolygonComponent[]): Region {+ const loops = components.flatMap(({ outer, holes = [] }) => [outer, ...holes]);+ const loopPointCounts = BigUint64Array.from(loops.map((loop) => BigInt(loop.length)));+ const componentLoopCounts = BigUint64Array.from(+ components.map(({ holes = [] }) => BigInt(holes.length + 1)),+ );+ const coordinates = flattenLoops(loops);+ return new Region(+ this.#native,+ produceHandle((output, obstruction) =>+ this.#native.regionCreate(+ coordinates,+ coordinates.length / 2,+ loopPointCounts,+ loops.length,+ componentLoopCounts,+ components.length,+ output,+ obstruction,+ ),+ ),+ );+ }++ structuringElement(points: readonly Point[]): StructuringElement {+ const coordinates = flattenPoints(points);+ return new StructuringElement(+ this.#native,+ produceHandle((output, obstruction) =>+ this.#native.structuringElementCreate(coordinates, points.length, output, obstruction),+ ),+ );+ } } -export class Mesh {+abstract class OwnedHandle { readonly #native: NativeApi;- #handle: MeshHandle | null;+ readonly #free: (handle: NativeHandle) => void;+ readonly #kind: string;+ #handle: NativeHandle | null; - constructor(native: NativeApi, handle: MeshHandle) {+ protected constructor(+ native: NativeApi,+ handle: NativeHandle,+ free: (handle: NativeHandle) => void,+ kind: string,+ ) { this.#native = native;+ this.#free = free;+ this.#kind = kind; this.#handle = handle;- meshFinalizer.register(this, { free: native.free, handle }, this);+ handleFinalizer.register(this, { free, handle }, this); } close(): void { const handle = this.#handle; if (handle !== null) {- meshFinalizer.unregister(this);- this.#native.free(handle);+ handleFinalizer.unregister(this);+ this.#free(handle); this.#handle = null; } } + native(): NativeApi {+ return this.#native;+ }++ nativeHandle(): NativeHandle {+ if (this.#handle === null) {+ throw new Error(`${this.#kind} is closed`);+ }+ return this.#handle;+ }++ requireSameRuntime(other: OwnedHandle): void {+ if (this.#native !== other.#native) {+ throw new Error("both values must belong to the same Moonlight runtime");+ }+ }+}++export class Mesh extends OwnedHandle {+ constructor(native: NativeApi, handle: NativeHandle) {+ super(native, handle, native.meshFree, "mesh");+ }+ vertexCount(): number {- return this.#count(this.#native.vertexCount);+ return this.count(this.native().vertexCount); } triangleCount(): number {- return this.#count(this.#native.triangleCount);+ return this.count(this.native().triangleCount); } vertices(): readonly Point[] { const count = this.vertexCount(); const coordinates = new Float64Array(count * 2);- const written = [0];+ const written: NativeInteger[] = [0]; const obstruction: NativeObstruction = {};- const status = this.#native.copyVertices(this.#liveHandle(), coordinates, count, written, obstruction);+ const status = this.native().copyVertices(this.nativeHandle(), coordinates, count, written, obstruction); checkStatus(status, obstruction);- return Array.from({ length: written[0] ?? 0 }, (_unused, index): Point => [+ return Array.from({ length: toSafeNumber(written[0]) }, (_unused, index): Point => [ coordinates[index * 2] ?? 0, coordinates[index * 2 + 1] ?? 0, ]);@@ -147,11 +328,11 @@ triangles(): readonly Triangle[] { const count = this.triangleCount(); const triangles = new Uint32Array(count * 3);- const written = [0];+ const written: NativeInteger[] = [0]; const obstruction: NativeObstruction = {};- const status = this.#native.copyTriangles(this.#liveHandle(), triangles, count, written, obstruction);+ const status = this.native().copyTriangles(this.nativeHandle(), triangles, count, written, obstruction); checkStatus(status, obstruction);- return Array.from({ length: written[0] ?? 0 }, (_unused, index): Triangle => [+ return Array.from({ length: toSafeNumber(written[0]) }, (_unused, index): Triangle => [ triangles[index * 3] ?? 0, triangles[index * 3 + 1] ?? 0, triangles[index * 3 + 2] ?? 0,@@ -159,61 +340,235 @@ } insertMany(points: readonly Point[]): Mesh {- const output: Array<MeshHandle | null> = [null];+ const coordinates = flattenPoints(points);+ return new Mesh(+ this.native(),+ produceHandle((output, obstruction) =>+ this.native().insertMany(this.nativeHandle(), coordinates, points.length, output, obstruction),+ ),+ );+ }++ siteUnion(other: Mesh): Mesh {+ return this.binary(other, this.native().meshSiteUnion);+ }++ siteIntersection(other: Mesh): Mesh {+ return this.binary(other, this.native().meshSiteIntersection);+ }++ siteDifference(other: Mesh): Mesh {+ return this.binary(other, this.native().meshSiteDifference);+ }++ siteSymmetricDifference(other: Mesh): Mesh {+ return this.binary(other, this.native().meshSiteSymmetricDifference);+ }++ private binary(other: Mesh, operation: BinaryOperation): Mesh {+ this.requireSameRuntime(other);+ return new Mesh(+ this.native(),+ produceHandle((output, obstruction) =>+ operation(this.nativeHandle(), other.nativeHandle(), output, obstruction),+ ),+ );+ }++ private count(operation: CountOperation): number {+ const output: NativeInteger[] = [0]; const obstruction: NativeObstruction = {};- const status = this.#native.insertMany(this.#liveHandle(), flattenPoints(points), points.length, output, obstruction);+ const status = operation(this.nativeHandle(), output, obstruction); checkStatus(status, obstruction);- return new Mesh(this.#native, requiredHandle(output[0]));+ return toSafeNumber(output[0]); }+} - union(other: Mesh): Mesh {- return this.#binary(other, this.#native.union);+export class Region extends OwnedHandle {+ constructor(native: NativeApi, handle: NativeHandle) {+ super(native, handle, native.regionFree, "region"); } - intersection(other: Mesh): Mesh {- return this.#binary(other, this.#native.intersection);+ components(): readonly PolygonComponent[] {+ const [componentCount, loopCount, pointCount] = this.counts();+ const coordinates = new Float64Array(pointCount * 2);+ const loopPointOffsets = new BigUint64Array(loopCount + 1);+ const componentLoopOffsets = new BigUint64Array(componentCount + 1);+ const obstruction: NativeObstruction = {};+ const status = this.native().regionCopy(+ this.nativeHandle(),+ coordinates,+ pointCount,+ loopPointOffsets,+ loopCount + 1,+ componentLoopOffsets,+ componentCount + 1,+ obstruction,+ );+ checkStatus(status, obstruction);+ const points = Array.from({ length: pointCount }, (_unused, index): Point => [+ coordinates[index * 2] ?? 0,+ coordinates[index * 2 + 1] ?? 0,+ ]);+ const loops = Array.from({ length: loopCount }, (_unused, index) =>+ points.slice(toSafeNumber(loopPointOffsets[index]), toSafeNumber(loopPointOffsets[index + 1])),+ );+ return Array.from({ length: componentCount }, (_unused, index): PolygonComponent => {+ const start = toSafeNumber(componentLoopOffsets[index]);+ const end = toSafeNumber(componentLoopOffsets[index + 1]);+ const outer = loops[start];+ if (outer === undefined || start >= end) {+ throw new Error("Moonlight returned a component without an outer loop");+ }+ return { outer, holes: loops.slice(start + 1, end) };+ }); } - difference(other: Mesh): Mesh {- return this.#binary(other, this.#native.difference);+ valuations(): RegionValuations {+ return this.measureWithCapacity(128); } - symmetricDifference(other: Mesh): Mesh {- return this.#binary(other, this.#native.symmetricDifference);+ locate([x, y]: Point): RegionLocation {+ const location = [0];+ const obstruction: NativeObstruction = {};+ const status = this.native().regionLocate(this.nativeHandle(), x, y, location, obstruction);+ checkStatus(status, obstruction);+ return locationCode(location[0]); } - #binary(other: Mesh, operation: BinaryOperation): Mesh {- if (this.#native !== other.#native) {- throw new Error("both meshes must belong to the same Moonlight runtime");- }- const output: Array<MeshHandle | null> = [null];+ union(other: Region): Region {+ return this.binary(other, this.native().regionUnion);+ }++ intersection(other: Region): Region {+ return this.binary(other, this.native().regionIntersection);+ }++ difference(other: Region): Region {+ return this.binary(other, this.native().regionDifference);+ }++ symmetricDifference(other: Region): Region {+ return this.binary(other, this.native().regionSymmetricDifference);+ }++ minkowskiSum(other: Region): readonly [Region, MinkowskiReceipt] {+ this.requireSameRuntime(other);+ return this.morph(this.native().regionMinkowskiSum, this.nativeHandle(), other.nativeHandle());+ }++ offset(element: StructuringElement): readonly [Region, MinkowskiReceipt] {+ return this.withElement(element, this.native().regionOffset);+ }++ inset(element: StructuringElement): readonly [Region, MinkowskiReceipt] {+ return this.withElement(element, this.native().regionInset);+ }++ open(element: StructuringElement): readonly [Region, MinkowskiReceipt] {+ return this.withElement(element, this.native().regionOpen);+ }++ closeWith(element: StructuringElement): readonly [Region, MinkowskiReceipt] {+ return this.withElement(element, this.native().regionClose);+ }++ private binary(other: Region, operation: BinaryOperation): Region {+ this.requireSameRuntime(other);+ return new Region(+ this.native(),+ produceHandle((output, obstruction) =>+ operation(this.nativeHandle(), other.nativeHandle(), output, obstruction),+ ),+ );+ }++ private withElement(+ element: StructuringElement,+ operation: MorphologyOperation,+ ): readonly [Region, MinkowskiReceipt] {+ this.requireSameRuntime(element);+ return this.morph(operation, element.nativeHandle(), this.nativeHandle());+ }++ private morph(+ operation: MorphologyOperation,+ first: NativeHandle,+ second: NativeHandle,+ ): readonly [Region, MinkowskiReceipt] {+ const [handle, receipt] = produceMorphology((output, nativeReceipt, obstruction) =>+ operation(first, second, output, nativeReceipt, obstruction),+ );+ return [new Region(this.native(), handle), receipt];+ }++ private counts(): readonly [number, number, number] {+ const componentCount: NativeInteger[] = [0];+ const loopCount: NativeInteger[] = [0];+ const pointCount: NativeInteger[] = [0]; const obstruction: NativeObstruction = {};- const status = operation(this.#liveHandle(), other.#liveHandle(), output, obstruction);+ const status = this.native().regionCounts(+ this.nativeHandle(),+ componentCount,+ loopCount,+ pointCount,+ obstruction,+ ); checkStatus(status, obstruction);- return new Mesh(this.#native, requiredHandle(output[0]));+ return [toSafeNumber(componentCount[0]), toSafeNumber(loopCount[0]), toSafeNumber(pointCount[0])]; } - #count(operation: CountOperation): number {- const output = [0];+ private measureWithCapacity(capacity: number): RegionValuations {+ const euler: NativeInteger[] = [0];+ const areaRatio = Buffer.alloc(capacity);+ const areaBytes: NativeInteger[] = [0];+ const perimeterLower = [0];+ const perimeterUpper = [0]; const obstruction: NativeObstruction = {};- const status = operation(this.#liveHandle(), output, obstruction);+ const status = this.native().regionMeasure(+ this.nativeHandle(),+ euler,+ areaRatio,+ capacity,+ areaBytes,+ perimeterLower,+ perimeterUpper,+ obstruction,+ );+ if (status === STATUS_BUFFER_TOO_SMALL && obstruction.code === 102) {+ return this.measureWithCapacity(toSafeNumber(areaBytes[0]) + 1);+ } checkStatus(status, obstruction);- return output[0] ?? 0;+ const ratio = areaRatio.subarray(0, toSafeNumber(areaBytes[0])).toString("ascii");+ const [numerator, denominator, remainder] = ratio.split("/");+ if (numerator === undefined || denominator === undefined || remainder !== undefined) {+ throw new Error("Moonlight returned a malformed exact-area ratio");+ }+ return {+ eulerCharacteristic: toBigInt(euler[0]),+ area: { numerator: BigInt(numerator), denominator: BigInt(denominator) },+ perimeterBounds: [perimeterLower[0] ?? 0, perimeterUpper[0] ?? 0],+ }; }+} - #liveHandle(): MeshHandle {- if (this.#handle === null) {- throw new Error("mesh is closed");- }- return this.#handle;+export class StructuringElement extends OwnedHandle {+ constructor(native: NativeApi, handle: NativeHandle) {+ super(native, handle, native.structuringElementFree, "structuring element"); } } function createNativeApi(libraryPath: string): NativeApi { const library = koffi.load(libraryPath); const mesh = koffi.opaque();+ const region = koffi.opaque();+ const structuringElement = koffi.opaque(); const meshPointer = koffi.pointer(mesh);+ const regionPointer = koffi.pointer(region);+ const structuringElementPointer = koffi.pointer(structuringElement); const meshOutput = koffi.out(koffi.pointer(mesh, 2));+ const regionOutput = koffi.out(koffi.pointer(region, 2));+ const structuringElementOutput = koffi.out(koffi.pointer(structuringElement, 2)); const obstruction = koffi.struct({ code: "uint32_t", coordinate_error: "uint32_t",@@ -226,22 +581,56 @@ point_y: "double", message: koffi.array("char", 256), });+ const receipt = koffi.struct({+ operation: "uint32_t",+ reserved: "uint32_t",+ input_components: "uint64_t",+ convex_pieces: "uint64_t",+ generated_pieces: "uint64_t",+ generated_convolution_edges: "uint64_t",+ overlay_passes: "uint64_t",+ exact_crossings: "uint64_t",+ output_cells: "uint64_t",+ exact_coordinate_bit_growth: "uint64_t",+ }); const obstructionOutput = koffi.out(koffi.pointer(obstruction));- const meshCountOutput = koffi.out(koffi.pointer("size_t"));+ const receiptOutput = koffi.out(koffi.pointer(receipt));+ const sizeOutput = koffi.out(koffi.pointer("size_t"));+ const uint32Output = koffi.out(koffi.pointer("uint32_t"));+ const int64Output = koffi.out(koffi.pointer("int64_t"));+ const doubleOutput = koffi.out(koffi.pointer("double"));+ const sizeArray = koffi.pointer("size_t"); return { abiVersion: library.func("ml_abi_version", "uint32_t", []), runtimeInitialize: library.func("ml_runtime_initialize", "uint32_t", []), delaunay: library.func("ml_delaunay_f64", "uint32_t", [koffi.pointer("double"), "size_t", meshOutput, obstructionOutput]), insertMany: library.func("ml_mesh_insert_many_f64", "uint32_t", [meshPointer, koffi.pointer("double"), "size_t", meshOutput, obstructionOutput]),- union: library.func("ml_mesh_union", "uint32_t", [meshPointer, meshPointer, meshOutput, obstructionOutput]),- intersection: library.func("ml_mesh_intersection", "uint32_t", [meshPointer, meshPointer, meshOutput, obstructionOutput]),- difference: library.func("ml_mesh_difference", "uint32_t", [meshPointer, meshPointer, meshOutput, obstructionOutput]),- symmetricDifference: library.func("ml_mesh_symmetric_difference", "uint32_t", [meshPointer, meshPointer, meshOutput, obstructionOutput]),- vertexCount: library.func("ml_mesh_vertex_count", "uint32_t", [meshPointer, meshCountOutput, obstructionOutput]),- triangleCount: library.func("ml_mesh_triangle_count", "uint32_t", [meshPointer, meshCountOutput, obstructionOutput]),- copyVertices: library.func("ml_mesh_copy_vertices_f64", "uint32_t", [meshPointer, koffi.out(koffi.pointer("double")), "size_t", meshCountOutput, obstructionOutput]),- copyTriangles: library.func("ml_mesh_copy_triangles_u32", "uint32_t", [meshPointer, koffi.out(koffi.pointer("uint32_t")), "size_t", meshCountOutput, obstructionOutput]),- free: library.func("ml_mesh_free", "void", [meshPointer]),+ meshSiteUnion: library.func("ml_mesh_site_union", "uint32_t", [meshPointer, meshPointer, meshOutput, obstructionOutput]),+ meshSiteIntersection: library.func("ml_mesh_site_intersection", "uint32_t", [meshPointer, meshPointer, meshOutput, obstructionOutput]),+ meshSiteDifference: library.func("ml_mesh_site_difference", "uint32_t", [meshPointer, meshPointer, meshOutput, obstructionOutput]),+ meshSiteSymmetricDifference: library.func("ml_mesh_site_symmetric_difference", "uint32_t", [meshPointer, meshPointer, meshOutput, obstructionOutput]),+ vertexCount: library.func("ml_mesh_vertex_count", "uint32_t", [meshPointer, sizeOutput, obstructionOutput]),+ triangleCount: library.func("ml_mesh_triangle_count", "uint32_t", [meshPointer, sizeOutput, obstructionOutput]),+ copyVertices: library.func("ml_mesh_copy_vertices_f64", "uint32_t", [meshPointer, koffi.out(koffi.pointer("double")), "size_t", sizeOutput, obstructionOutput]),+ copyTriangles: library.func("ml_mesh_copy_triangles_u32", "uint32_t", [meshPointer, koffi.out(koffi.pointer("uint32_t")), "size_t", sizeOutput, obstructionOutput]),+ meshFree: library.func("ml_mesh_free", "void", [meshPointer]),+ regionCreate: library.func("ml_region_create_f64", "uint32_t", [koffi.pointer("double"), "size_t", sizeArray, "size_t", sizeArray, "size_t", regionOutput, obstructionOutput]),+ regionCounts: library.func("ml_region_counts", "uint32_t", [regionPointer, sizeOutput, sizeOutput, sizeOutput, obstructionOutput]),+ regionCopy: library.func("ml_region_copy_f64", "uint32_t", [regionPointer, koffi.out(koffi.pointer("double")), "size_t", koffi.out(sizeArray), "size_t", koffi.out(sizeArray), "size_t", obstructionOutput]),+ regionUnion: library.func("ml_region_union", "uint32_t", [regionPointer, regionPointer, regionOutput, obstructionOutput]),+ regionIntersection: library.func("ml_region_intersection", "uint32_t", [regionPointer, regionPointer, regionOutput, obstructionOutput]),+ regionDifference: library.func("ml_region_difference", "uint32_t", [regionPointer, regionPointer, regionOutput, obstructionOutput]),+ regionSymmetricDifference: library.func("ml_region_symmetric_difference", "uint32_t", [regionPointer, regionPointer, regionOutput, obstructionOutput]),+ regionLocate: library.func("ml_region_locate_point_f64", "uint32_t", [regionPointer, "double", "double", uint32Output, obstructionOutput]),+ regionMeasure: library.func("ml_region_measure", "uint32_t", [regionPointer, int64Output, koffi.out(koffi.pointer("char")), "size_t", sizeOutput, doubleOutput, doubleOutput, obstructionOutput]),+ regionFree: library.func("ml_region_free", "void", [regionPointer]),+ structuringElementCreate: library.func("ml_structuring_element_create_f64", "uint32_t", [koffi.pointer("double"), "size_t", structuringElementOutput, obstructionOutput]),+ structuringElementFree: library.func("ml_structuring_element_free", "void", [structuringElementPointer]),+ regionMinkowskiSum: library.func("ml_region_minkowski_sum", "uint32_t", [regionPointer, regionPointer, regionOutput, receiptOutput, obstructionOutput]),+ regionOffset: library.func("ml_region_offset", "uint32_t", [structuringElementPointer, regionPointer, regionOutput, receiptOutput, obstructionOutput]),+ regionInset: library.func("ml_region_inset", "uint32_t", [structuringElementPointer, regionPointer, regionOutput, receiptOutput, obstructionOutput]),+ regionOpen: library.func("ml_region_open", "uint32_t", [structuringElementPointer, regionPointer, regionOutput, receiptOutput, obstructionOutput]),+ regionClose: library.func("ml_region_close", "uint32_t", [structuringElementPointer, regionPointer, regionOutput, receiptOutput, obstructionOutput]), }; } @@ -249,21 +638,85 @@ return Float64Array.from(points.flatMap(([x, y]) => [x, y])); } +function flattenLoops(loops: readonly (readonly Point[])[]): Float64Array {+ return Float64Array.from(loops.flatMap((loop) => loop.flatMap(([x, y]) => [x, y])));+}++function produceHandle(+ operation: (output: HandleOutput, obstruction: NativeObstruction) => number,+): NativeHandle {+ const output: HandleOutput = [null];+ const obstruction: NativeObstruction = {};+ checkStatus(operation(output, obstruction), obstruction);+ return requiredHandle(output[0]);+}++function produceMorphology(+ operation: (+ output: HandleOutput,+ receipt: NativeMinkowskiReceipt,+ obstruction: NativeObstruction,+ ) => number,+): readonly [NativeHandle, MinkowskiReceipt] {+ const output: HandleOutput = [null];+ const receipt: NativeMinkowskiReceipt = {};+ const obstruction: NativeObstruction = {};+ checkStatus(operation(output, receipt, obstruction), obstruction);+ return [requiredHandle(output[0]), morphologyReceipt(receipt)];+}++function morphologyReceipt(receipt: NativeMinkowskiReceipt): MinkowskiReceipt {+ return {+ operation: operationCode(receipt.operation),+ inputComponents: toBigInt(receipt.input_components),+ convexPieces: toBigInt(receipt.convex_pieces),+ generatedPieces: toBigInt(receipt.generated_pieces),+ generatedConvolutionEdges: toBigInt(receipt.generated_convolution_edges),+ overlayPasses: toBigInt(receipt.overlay_passes),+ exactCrossings: toBigInt(receipt.exact_crossings),+ outputCells: toBigInt(receipt.output_cells),+ exactCoordinateBitGrowth: toBigInt(receipt.exact_coordinate_bit_growth),+ };+}++function operationCode(value: number | undefined): MinkowskiOperation {+ if (value === undefined || value < MinkowskiOperation.Addition || value > MinkowskiOperation.Closing) {+ throw new Error(`Moonlight returned an unknown morphology operation ${value ?? "missing"}`);+ }+ return value;+}++function locationCode(value: number | undefined): RegionLocation {+ if (value === undefined || value < RegionLocation.Exterior || value > RegionLocation.Interior) {+ throw new Error(`Moonlight returned an unknown region location ${value ?? "missing"}`);+ }+ return value;+}+ function checkStatus(status: number, obstruction: NativeObstruction): void { if (status !== STATUS_OK) { throw new MoonlightError(status, obstruction); } } -function requiredHandle(handle: MeshHandle | null | undefined): MeshHandle {+function requiredHandle(handle: NativeHandle | null | undefined): NativeHandle { if (handle === null || handle === undefined) {- throw new Error("Moonlight returned success without a mesh handle");+ throw new Error("Moonlight returned success without a handle"); } return handle; } function toBigInt(value: NativeInteger | undefined): bigint { return value === undefined ? 0n : BigInt(value);+}++function toSafeNumber(value: NativeInteger | undefined): number {+ const exact = toBigInt(value);+ const projected = Number(exact);+ if (!Number.isSafeInteger(projected)) {+ throw new Error(`Moonlight count ${exact} exceeds JavaScript's safe integer range`);+ }+ return projected; } function obstructionMessage(obstruction: NativeObstruction): string {
bindings/typescript/test/binding.test.ts view
@@ -1,31 +1,67 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { Moonlight, MoonlightError } from "../src/index.js";+import {+ MinkowskiOperation,+ Moonlight,+ MoonlightError,+ RegionLocation,+} from "../src/index.js"; -test("immutable mesh algebra and dense projection", () => {+test("immutable site-set algebra and dense projection", () => { const engine = new Moonlight(); const left = engine.delaunay([[0, 0], [2, 0], [0, 2], [2, 2]]); const right = engine.delaunay([[2, 0], [4, 0], [2, 2], [4, 2]]);- const meshes = [- left,- right,- left.union(right),- left.intersection(right),- left.difference(right),- left.symmetricDifference(right),- left.insertMany([[1, 1], [3, 1]]),- ];- const [original, _right, union, intersection, difference, symmetric, extended] = meshes;- assert.equal(original?.vertexCount(), 4);- assert.equal(union?.vertexCount(), 6);- assert.equal(intersection?.vertexCount(), 2);- assert.equal(difference?.vertexCount(), 2);- assert.equal(symmetric?.vertexCount(), 4);- assert.equal(extended?.vertexCount(), 6);- assert.equal(original?.vertices().length, original?.vertexCount());- assert.equal(original?.triangles().length, original?.triangleCount());- meshes.forEach((mesh) => mesh.close());+ const union = left.siteUnion(right);+ const intersection = left.siteIntersection(right);+ const difference = left.siteDifference(right);+ const symmetric = left.siteSymmetricDifference(right);+ const extended = left.insertMany([[1, 1], [3, 1]]);++ assert.equal(left.vertexCount(), 4);+ assert.equal(union.vertexCount(), 6);+ assert.equal(intersection.vertexCount(), 2);+ assert.equal(difference.vertexCount(), 2);+ assert.equal(symmetric.vertexCount(), 4);+ assert.equal(extended.vertexCount(), 6);+ assert.equal(left.vertices().length, left.vertexCount());+ assert.equal(left.triangles().length, left.triangleCount());++ left.close();+ right.close();+ union.close();+ intersection.close();+ difference.close();+ symmetric.close();+ extended.close();+});++test("exact region Boolean, valuation, and morphology", () => {+ const engine = new Moonlight();+ const left = engine.region([{ outer: [[0, 0], [2, 0], [2, 2], [0, 2]] }]);+ const right = engine.region([{ outer: [[1, 0], [3, 0], [3, 2], [1, 2]] }]);+ const intersection = left.intersection(right);+ const symmetric = left.symmetricDifference(right);+ const kernel = engine.structuringElement([[-0.5, -0.5], [0.5, -0.5], [0.5, 0.5], [-0.5, 0.5]]);+ const [offset, receipt] = left.offset(kernel);++ assert.equal(left.components().length, 1);+ assert.equal(left.components()[0]?.outer.length, 4);+ assert.deepEqual(intersection.valuations().area, { numerator: 2n, denominator: 1n });+ assert.equal(symmetric.valuations().eulerCharacteristic, 2n);+ assert.equal(left.locate([1, 1]), RegionLocation.Interior);+ assert.equal(left.locate([0, 1]), RegionLocation.Boundary);+ assert.equal(left.locate([3, 1]), RegionLocation.Exterior);+ assert.deepEqual(offset.valuations().area, { numerator: 9n, denominator: 1n });+ assert.equal(receipt.operation, MinkowskiOperation.Addition);+ assert(receipt.generatedPieces >= 1n);++ left.close();+ right.close();+ intersection.close();+ symmetric.close();+ kernel.close();+ offset.close(); }); test("invalid coordinate preserves typed witness", () => {
cbits/moonlight-triangulation.def view
@@ -4,12 +4,29 @@ ml_runtime_initialize ml_delaunay_f64 ml_mesh_insert_many_f64- ml_mesh_union- ml_mesh_intersection- ml_mesh_difference- ml_mesh_symmetric_difference+ ml_mesh_site_union+ ml_mesh_site_intersection+ ml_mesh_site_difference+ ml_mesh_site_symmetric_difference ml_mesh_vertex_count ml_mesh_triangle_count ml_mesh_copy_vertices_f64 ml_mesh_copy_triangles_u32 ml_mesh_free+ ml_region_create_f64+ ml_region_counts+ ml_region_copy_f64+ ml_region_union+ ml_region_intersection+ ml_region_difference+ ml_region_symmetric_difference+ ml_region_locate_point_f64+ ml_region_measure+ ml_region_free+ ml_structuring_element_create_f64+ ml_structuring_element_free+ ml_region_minkowski_sum+ ml_region_offset+ ml_region_inset+ ml_region_open+ ml_region_close
cbits/moonlight_runtime.c view
@@ -44,5 +44,5 @@ #endif ML_API uint32_t ml_abi_version(void) {- return 1;+ return 2; }
include/moonlight_triangulation.h view
@@ -15,16 +15,20 @@ #endif typedef struct ml_mesh ml_mesh;+typedef struct ml_region ml_region;+typedef struct ml_structuring_element ml_structuring_element; typedef uint32_t ml_status; typedef uint32_t ml_obstruction_code; typedef uint32_t ml_coordinate_error;+typedef uint32_t ml_region_location;+typedef uint32_t ml_minkowski_operation; enum { ML_STATUS_OK = 0, ML_STATUS_NULL_POINTER = 1, ML_STATUS_COUNT_OVERFLOW = 2, ML_STATUS_BUFFER_TOO_SMALL = 3,- ML_STATUS_BUILD_OBSTRUCTION = 4,+ ML_STATUS_GEOMETRY_OBSTRUCTION = 4, ML_STATUS_RUNTIME_FAILURE = 5 }; @@ -84,10 +88,18 @@ ML_OBSTRUCTION_FACE_CAPACITY_EXCEEDED = 52, ML_OBSTRUCTION_PAYLOAD_STORAGE_FAILURE = 53, ML_OBSTRUCTION_COORDINATE_PAYLOAD_COUNT_MISMATCH = 54,+ ML_OBSTRUCTION_CIRCLE_SWEEP_REQUIRES_DENSE_STORAGE = 55, ML_OBSTRUCTION_NULL_POINTER = 100, ML_OBSTRUCTION_COUNT_OVERFLOW = 101, ML_OBSTRUCTION_BUFFER_TOO_SMALL = 102,- ML_OBSTRUCTION_RUNTIME_FAILURE = 103+ ML_OBSTRUCTION_RUNTIME_FAILURE = 103,+ ML_OBSTRUCTION_REGION_LAYOUT_INVALID = 200,+ ML_OBSTRUCTION_REGION_VALIDATION_FAILED = 201,+ ML_OBSTRUCTION_OVERLAY_FAILED = 202,+ ML_OBSTRUCTION_REGION_PUBLICATION_FAILED = 203,+ ML_OBSTRUCTION_VALUATION_FAILED = 204,+ ML_OBSTRUCTION_MINKOWSKI_FAILED = 205,+ ML_OBSTRUCTION_REGION_PROJECTION_FAILED = 206 }; enum {@@ -98,6 +110,19 @@ ML_COORDINATE_ERROR_TOO_LARGE = 4 }; +enum {+ ML_REGION_EXTERIOR = 0,+ ML_REGION_BOUNDARY = 1,+ ML_REGION_INTERIOR = 2+};++enum {+ ML_MINKOWSKI_ADDITION = 0,+ ML_MINKOWSKI_EROSION = 1,+ ML_MINKOWSKI_OPENING = 2,+ ML_MINKOWSKI_CLOSING = 3+};+ typedef struct ml_obstruction { uint32_t code; uint32_t coordinate_error;@@ -111,19 +136,88 @@ char message[256]; } ml_obstruction; +typedef struct ml_minkowski_receipt {+ uint32_t operation;+ uint32_t reserved;+ uint64_t input_components;+ uint64_t convex_pieces;+ uint64_t generated_pieces;+ uint64_t generated_convolution_edges;+ uint64_t overlay_passes;+ uint64_t exact_crossings;+ uint64_t output_cells;+ uint64_t exact_coordinate_bit_growth;+} ml_minkowski_receipt;+ ML_API uint32_t ml_abi_version(void); ML_API ml_status ml_runtime_initialize(void);++/* Immutable Delaunay meshes; these operations combine site sets, not regions. */ ML_API ml_status ml_delaunay_f64(const double *coordinates, size_t point_count, ml_mesh **result, ml_obstruction *obstruction); ML_API ml_status ml_mesh_insert_many_f64(const ml_mesh *mesh, const double *coordinates, size_t point_count, ml_mesh **result, ml_obstruction *obstruction);-ML_API ml_status ml_mesh_union(const ml_mesh *left, const ml_mesh *right, ml_mesh **result, ml_obstruction *obstruction);-ML_API ml_status ml_mesh_intersection(const ml_mesh *left, const ml_mesh *right, ml_mesh **result, ml_obstruction *obstruction);-ML_API ml_status ml_mesh_difference(const ml_mesh *left, const ml_mesh *right, ml_mesh **result, ml_obstruction *obstruction);-ML_API ml_status ml_mesh_symmetric_difference(const ml_mesh *left, const ml_mesh *right, ml_mesh **result, ml_obstruction *obstruction);+ML_API ml_status ml_mesh_site_union(const ml_mesh *left, const ml_mesh *right, ml_mesh **result, ml_obstruction *obstruction);+ML_API ml_status ml_mesh_site_intersection(const ml_mesh *left, const ml_mesh *right, ml_mesh **result, ml_obstruction *obstruction);+ML_API ml_status ml_mesh_site_difference(const ml_mesh *left, const ml_mesh *right, ml_mesh **result, ml_obstruction *obstruction);+ML_API ml_status ml_mesh_site_symmetric_difference(const ml_mesh *left, const ml_mesh *right, ml_mesh **result, ml_obstruction *obstruction); ML_API ml_status ml_mesh_vertex_count(const ml_mesh *mesh, size_t *count, ml_obstruction *obstruction); ML_API ml_status ml_mesh_triangle_count(const ml_mesh *mesh, size_t *count, ml_obstruction *obstruction); ML_API ml_status ml_mesh_copy_vertices_f64(const ml_mesh *mesh, double *coordinates, size_t point_capacity, size_t *points_written, ml_obstruction *obstruction); ML_API ml_status ml_mesh_copy_triangles_u32(const ml_mesh *mesh, uint32_t *triangles, size_t triangle_capacity, size_t *triangles_written, ml_obstruction *obstruction); ML_API void ml_mesh_free(ml_mesh *mesh);++/*+ * Bulk exact-region authoring. loop_point_counts gives the number of points in+ * each loop; component_loop_counts gives the number of loops in each component.+ * Within each component, the first loop is a CCW outer loop and subsequent+ * loops are CW holes.+ */+ML_API ml_status ml_region_create_f64(+ const double *coordinates,+ size_t point_count,+ const size_t *loop_point_counts,+ size_t loop_count,+ const size_t *component_loop_counts,+ size_t component_count,+ ml_region **result,+ ml_obstruction *obstruction+);+ML_API ml_status ml_region_counts(const ml_region *region, size_t *component_count, size_t *loop_count, size_t *point_count, ml_obstruction *obstruction);+/* Capacities count points or offset entries; copied coordinates are a derived binary64 rendering projection. */+ML_API ml_status ml_region_copy_f64(+ const ml_region *region,+ double *coordinates,+ size_t point_capacity,+ size_t *loop_point_offsets,+ size_t loop_offset_capacity,+ size_t *component_loop_offsets,+ size_t component_offset_capacity,+ ml_obstruction *obstruction+);+ML_API ml_status ml_region_union(const ml_region *left, const ml_region *right, ml_region **result, ml_obstruction *obstruction);+ML_API ml_status ml_region_intersection(const ml_region *left, const ml_region *right, ml_region **result, ml_obstruction *obstruction);+ML_API ml_status ml_region_difference(const ml_region *left, const ml_region *right, ml_region **result, ml_obstruction *obstruction);+ML_API ml_status ml_region_symmetric_difference(const ml_region *left, const ml_region *right, ml_region **result, ml_obstruction *obstruction);+ML_API ml_status ml_region_locate_point_f64(const ml_region *region, double x, double y, ml_region_location *location, ml_obstruction *obstruction);+/* area_bytes_written excludes the trailing NUL; area_capacity includes it. */+ML_API ml_status ml_region_measure(+ const ml_region *region,+ int64_t *euler_characteristic,+ char *area_ratio_utf8,+ size_t area_capacity,+ size_t *area_bytes_written,+ double *perimeter_lower,+ double *perimeter_upper,+ ml_obstruction *obstruction+);+ML_API void ml_region_free(ml_region *region);++ML_API ml_status ml_structuring_element_create_f64(const double *coordinates, size_t point_count, ml_structuring_element **result, ml_obstruction *obstruction);+ML_API void ml_structuring_element_free(ml_structuring_element *element);+ML_API ml_status ml_region_minkowski_sum(const ml_region *left, const ml_region *right, ml_region **result, ml_minkowski_receipt *receipt, ml_obstruction *obstruction);+ML_API ml_status ml_region_offset(const ml_structuring_element *element, const ml_region *region, ml_region **result, ml_minkowski_receipt *receipt, ml_obstruction *obstruction);+ML_API ml_status ml_region_inset(const ml_structuring_element *element, const ml_region *region, ml_region **result, ml_minkowski_receipt *receipt, ml_obstruction *obstruction);+ML_API ml_status ml_region_open(const ml_structuring_element *element, const ml_region *region, ml_region **result, ml_minkowski_receipt *receipt, ml_obstruction *obstruction);+ML_API ml_status ml_region_close(const ml_structuring_element *element, const ml_region *region, ml_region **result, ml_minkowski_receipt *receipt, ml_obstruction *obstruction); #ifdef __cplusplus }
moonlight-triangulation.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.4 name: moonlight-triangulation-version: 1.2.0.1+version: 1.3.0.0 synopsis: Delaunay meshes and exact planar-region algebra. description: Delaunay and constrained Delaunay triangulation as a lawful finite-set algebra, together with exact rational planar@@ -19,6 +19,8 @@ with natural-neighbour interpolation and Delaunay hierarchy hints; a bounded-concurrency interpreter for the join tournament; and a versioned binary serialization surface.+ On GHC 9.14, a public cell-complex component interprets+ admitted exact cell selections for Homology and Category. Failure is values: every refusal names its witness. license: MIT license-file: LICENSE@@ -38,6 +40,12 @@ CHANGELOG.md extra-source-files: weeder.toml+ bench/delaunay-compare/README.md+ -- Receipts and their SVG projections remain beside the benchmark, but are+ -- not package inputs: taking a new measurement must not rebuild the library.+ bench/delaunay-compare/rust/Cargo.lock+ bench/delaunay-compare/rust/Cargo.toml+ bench/delaunay-compare/rust/src/lib.rs include/moonlight_triangulation.h bindings/README.md bindings/python/pyproject.toml@@ -58,6 +66,12 @@ location: https://github.com/PaleRoses/moonlight.git subdir: moonlight-triangulation +source-repository this+ type: git+ location: https://github.com/PaleRoses/moonlight.git+ tag: moonlight-triangulation-1.3.0.0+ subdir: moonlight-triangulation+ flag warnings-as-errors description: Treat compiler warnings as errors in package validation. default: False@@ -78,11 +92,19 @@ -Wno-missing-import-lists if flag(warnings-as-errors) ghc-options: -Werror+ -- The hot modules that request LLVM O3 replace GHC's default optimiser+ -- recipe. GHC 9.8 therefore needs its supported legacy pass manager stated+ -- explicitly; LLVM 15 otherwise combines the new manager with old TBAA+ -- pass flags and refuses before code generation.+ if impl(ghc < 9.10)+ ghc-options: -optlo-enable-new-pm=0 -- ── the tower ────────────────────────────────────────────────────────────────--- The main tower is core <- dcel <- build <- dual <- facade. Serialize branches+-- The main construction tower is core <- dcel <- build. Dual branches directly+-- from dcel; the apex owns only the genuine overlaps: planar algebra descends+-- through build, while hierarchy hints glue build and dual. Serialize branches -- from dcel and parallel branches from build, keeping their effect dependencies--- out of the tower. The graph is acyclic and uses no @.hs-boot@ files.+-- out of the tower. The graph is acyclic and uses no @.hs-boot@ files. library core import: shared-properties@@ -121,10 +143,8 @@ Moonlight.Triangulation.Dcel Moonlight.Triangulation.Payload Moonlight.Triangulation.JoinSemilattice- Moonlight.Triangulation.Handles Moonlight.Triangulation.Handles.HandleDefs Moonlight.Triangulation.Handles.Dynamic- Moonlight.Triangulation.Handles.Iterators Moonlight.Triangulation.Handles.Iterators.CircularIterator Moonlight.Triangulation.Handles.Iterators.DynamicIterators Moonlight.Triangulation.Handles.Iterators.FixedIterators@@ -146,7 +166,6 @@ Moonlight.Triangulation.Internal.Mutable Moonlight.Triangulation.Internal.OperationState Moonlight.Triangulation.Internal.Probe- Moonlight.Triangulation.Internal.DcelOperations Moonlight.Triangulation.Internal.DcelOperations.CandidateArena Moonlight.Triangulation.Internal.DcelOperations.Chain Moonlight.Triangulation.Internal.DcelOperations.FlipRewrite@@ -169,27 +188,42 @@ , moonlight-triangulation:core ghc-options: -fexpose-all-unfoldings +-- An ExactCellSet already carries every handle and closure witness needed to+-- interpret it as a generic finite cell complex. This component is the+-- canonical owner of that interpretation and deliberately remains outside the+-- portable geometry tower because its Homology dependency requires GHC 9.14.+library cell-complex+ import: shared-properties+ visibility: public+ hs-source-dirs: src-cell-complex+ default-extensions:+ TypeFamilies+ exposed-modules:+ Moonlight.Triangulation.CellComplex+ if impl(ghc < 9.14)+ buildable: False+ build-depends:+ base >= 4.22 && < 5+ , containers >= 0.6 && < 0.9+ , moonlight-algebra:abstract >= 0.1 && < 0.2+ , moonlight-homology:cell-complex >= 0.1 && < 0.2+ , moonlight-triangulation:dcel+ library build import: shared-properties visibility: public- hs-source-dirs:- src-build- src-embedding+ hs-source-dirs: src-build exposed-modules: Moonlight.Triangulation.BulkLoad Moonlight.Triangulation.Removal Moonlight.Triangulation.Session Moonlight.Triangulation.Cdt- Moonlight.Triangulation.Minkowski- Moonlight.Triangulation.Overlay Moonlight.Triangulation.Refinement Moonlight.Triangulation.SetAlgebra Moonlight.Triangulation.Internal.Cdt.Build Moonlight.Triangulation.Internal.Cdt.Query Moonlight.Triangulation.Internal.Cdt.Types Moonlight.Triangulation.Internal.Cdt.Union- Moonlight.Triangulation.Internal.Overlay.Arrangement- Moonlight.Triangulation.Internal.Overlay.Resident Moonlight.Triangulation.Internal.Join Moonlight.Triangulation.Internal.Join.Seam other-modules:@@ -213,10 +247,6 @@ Moonlight.Triangulation.Internal.Join.Plan Moonlight.Triangulation.Internal.Join.Rebuild Moonlight.Triangulation.Internal.Join.SiteSet- Moonlight.Triangulation.Internal.Overlay.Embedding- Moonlight.Triangulation.Internal.Overlay.Types- Moonlight.Triangulation.Internal.Minkowski.Convex- Moonlight.Triangulation.Internal.Minkowski.Types build-depends: base >= 4.19 && < 5 , containers >= 0.8 && < 0.9@@ -240,7 +270,6 @@ base >= 4.19 && < 5 , async >= 2.2 && < 2.3 , deepseq >= 1.5 && < 1.6- , moonlight-triangulation:core , moonlight-triangulation:build , moonlight-triangulation:dcel ghc-options: -fexpose-all-unfoldings@@ -277,7 +306,6 @@ Moonlight.Triangulation.Voronoi Moonlight.Triangulation.Voronoi.Handles Moonlight.Triangulation.Interpolation- Moonlight.Triangulation.HintGenerator other-modules: Moonlight.Triangulation.Internal.InterpolationWorkspace build-depends:@@ -287,16 +315,30 @@ , vector >= 0.13 && < 0.14 , moonlight-triangulation:core , moonlight-triangulation:dcel- , moonlight-triangulation:build ghc-options: -fexpose-all-unfoldings library import: shared-properties- hs-source-dirs: src-public+ hs-source-dirs:+ src-public+ src-planar exposed-modules: Moonlight.Triangulation+ Moonlight.Triangulation.HintGenerator+ Moonlight.Triangulation.Minkowski+ Moonlight.Triangulation.Overlay+ Moonlight.Triangulation.Internal.Overlay.Arrangement+ Moonlight.Triangulation.Internal.Overlay.Embedding+ Moonlight.Triangulation.Internal.Overlay.Resident+ other-modules:+ Moonlight.Triangulation.Internal.Minkowski.Convex+ Moonlight.Triangulation.Internal.Minkowski.Types+ Moonlight.Triangulation.Internal.Overlay.Types build-depends: base >= 4.19 && < 5+ , containers >= 0.8 && < 0.9+ , deepseq >= 1.5 && < 1.6+ , vector >= 0.13 && < 0.14 , moonlight-triangulation:core , moonlight-triangulation:dcel , moonlight-triangulation:build@@ -311,6 +353,7 @@ Moonlight.Triangulation.Foreign.ABI build-depends: base >= 4.19 && < 5+ , containers >= 0.8 && < 0.9 , vector >= 0.13 && < 0.14 , moonlight-triangulation , moonlight-triangulation:build@@ -330,7 +373,7 @@ moonlight_triangulation.h build-depends: base >= 4.19 && < 5- , moonlight-triangulation:ffi >= 1.2 && < 1.3+ , moonlight-triangulation:ffi >= 1.3 && < 1.4 ghc-options: -threaded if os(windows) options: standalone@@ -338,7 +381,7 @@ else extra-libraries: pthread if os(linux)- lib-version-info: 1:0:0+ lib-version-info: 2:0:0 -- ── test slices ────────────────────────────────────────────────────────────── @@ -350,28 +393,22 @@ common triangulation-test-properties import: shared-properties+ ghc-options: -O0 build-depends: base >= 4.19 && < 5- , deepseq >= 1.5 && < 1.6 , vector >= 0.13 && < 0.14- , primitive >= 0.9 && < 0.10 , moonlight-triangulation common triangulation-test-support-slice other-modules: Support common triangulation-native-test-slice- -- The focused milestone spec reaches into the dedicated @src-embedding@- -- root for exactly this one hidden certifier. Keeping that source root- -- singular prevents package modules from becoming duplicate test home modules.- hs-source-dirs: src-embedding other-modules: Moonlight.Triangulation.NativeSpec Moonlight.Triangulation.FilteredPredicateOptimizationSpec Moonlight.Triangulation.ExactEmbeddingSpec Moonlight.Triangulation.OverlaySpec Moonlight.Triangulation.RegionSpec- Moonlight.Triangulation.Internal.Overlay.Embedding -- The RTS options this module needs are named on the SUITES rather than -- here, which is the one place the slice-owns-its-requirements rule cannot -- hold. @-with-rtsopts@ is CONCATENATED by GHC across repetitions, so a@@ -382,6 +419,8 @@ ghc-options: -threaded -rtsopts build-depends: containers >= 0.8 && < 0.9+ , deepseq >= 1.5 && < 1.6+ , primitive >= 0.9 && < 0.10 , moonlight-triangulation:core , moonlight-triangulation:dcel , moonlight-triangulation:build@@ -392,6 +431,7 @@ build-depends: binary >= 0.8 && < 0.9 , bytestring >= 0.12 && < 0.13+ , deepseq >= 1.5 && < 1.6 , moonlight-triangulation:dcel , moonlight-triangulation:serialize @@ -420,14 +460,12 @@ containers >= 0.8 && < 0.9 , moonlight-triangulation:core , moonlight-triangulation:dcel- , moonlight-triangulation:build common triangulation-minkowski-law-slice other-modules: Moonlight.Triangulation.MinkowskiSpec build-depends: moonlight-triangulation:core , moonlight-triangulation:dcel- , moonlight-triangulation:build -- The agreement between the seam schedule and the reference rebuild. It names -- an internal schedule, so it reaches below the wall and travels with the@@ -441,6 +479,25 @@ ghc-options: -threaded -rtsopts build-depends: moonlight-triangulation:parallel +test-suite moonlight-triangulation-cell-complex-test+ import: shared-properties+ type: exitcode-stdio-1.0+ main-is: Main.hs+ hs-source-dirs: test/cell-complex+ default-extensions:+ TypeFamilies+ if impl(ghc < 9.14)+ buildable: False+ build-depends:+ base >= 4.22 && < 5+ , moonlight-homology:cell-complex >= 0.1 && < 0.2+ , moonlight-triangulation:build+ , moonlight-triangulation:cell-complex+ , moonlight-triangulation:dcel+ , tasty >= 1.4 && < 1.6+ , tasty-hunit >= 0.10 && < 0.11+ , vector >= 0.13 && < 0.14+ -- The single statement of the RTS environment every suite runs under: -N so -- that the concurrent tournament has more than one capability to be scheduled -- onto, -T so that the filtered-predicate test can read allocation counters.@@ -570,6 +627,7 @@ primitive >= 0.9 && < 0.10 , moonlight-triangulation:dcel , moonlight-triangulation:build+ , moonlight-triangulation:ffi common triangulation-dcel-benchmark-slice other-modules: Moonlight.Triangulation.DcelBench@@ -591,7 +649,74 @@ containers >= 0.8 && < 0.9 , moonlight-triangulation:core , moonlight-triangulation:dcel++-- The package-owned external construction board deliberately uses tasty-bench:+-- unlike the native work-counter slices above, its only lawful common metric+-- across Haskell and four Rust referents is elapsed or CPU time. The Rust+-- crate is a foreign adapter; Moonlight owns the case algebra, agreement+-- descent, timing, receipt parser, and derived pictures here.+common triangulation-delaunay-compare-properties+ import: shared-properties+ ghc-options: -O2+ build-depends:+ base >= 4.19 && < 5+ , deepseq >= 1.5 && < 1.6 , moonlight-triangulation:build+ , moonlight-triangulation:dcel++executable moonlight-triangulation-delaunay-compare+ import: triangulation-delaunay-compare-properties+ ghc-options: -threaded -rtsopts+ main-is: Main.hs+ hs-source-dirs: bench/delaunay-compare+ other-modules:+ Moonlight.Triangulation.Bench.DelaunayCompare.Domain+ Moonlight.Triangulation.Bench.DelaunayCompare.Native+ Moonlight.Triangulation.Bench.DelaunayCompare.Suite+ build-depends:+ directory >= 1.3 && < 1.4+ , filepath >= 1.4 && < 1.6+ , process >= 1.6 && < 1.7+ , tasty-bench >= 0.3 && < 0.6+ , transformers >= 0.5 && < 0.7+ , unix >= 2.8 && < 2.9+ , vector >= 0.13 && < 0.14++executable moonlight-triangulation-delaunay-pictures+ import: triangulation-delaunay-compare-properties+ main-is: PictureMain.hs+ hs-source-dirs: bench/delaunay-compare+ other-modules:+ Moonlight.Triangulation.Bench.DelaunayCompare.Domain+ Moonlight.Triangulation.Bench.DelaunayCompare.Picture+ build-depends:+ containers >= 0.8 && < 0.9+ , directory >= 1.3 && < 1.4+ , filepath >= 1.4 && < 1.6+ , transformers >= 0.5 && < 0.7++executable moonlight-triangulation-category-observatory-export+ import: shared-properties+ main-is: Main.hs+ hs-source-dirs: app/category-observatory+ ghc-options: -Wall -Wcompat+ default-extensions:+ TypeFamilies+ if impl(ghc < 9.14)+ buildable: False+ build-depends:+ aeson >= 2.2 && < 2.4+ , base >= 4.22 && < 5+ , bytestring >= 0.12 && < 0.13+ , containers >= 0.6 && < 0.9+ , moonlight-category >= 1.1.0.0 && < 1.2+ , moonlight-category:simplicial >= 1.1.0.0 && < 1.2+ , moonlight-homology:cell-category >= 0.1 && < 0.2+ , moonlight-homology:cell-complex >= 0.1 && < 0.2+ , moonlight-triangulation:build+ , moonlight-triangulation:cell-complex+ , moonlight-triangulation:dcel+ , vector >= 0.13 && < 0.14 benchmark moonlight-triangulation-build-bench import:
src-build/Moonlight/Triangulation/BulkLoad.hs view
@@ -627,61 +627,49 @@ -> Either BuildError (BuildResult mode vertex directed undirected face) insertMany triangulation input = do validateVertices input- ensureCapacity (numVertices triangulation + V.length input)- runST $ do- mutable <-- thawTriangulationDense- (numVertices triangulation + V.length input)- triangulation- operation <- newOperationState (halfEdgeCapacity mutable)- table <- newMutablePointIndex (numVertices triangulation + V.length input)- seeded <- seedPointTable mutable table- case seeded of- Left failure -> pure (Left failure)- Right () -> do- mapping <- newPrimArray (V.length input)- freshBuffer <- MUV.new (V.length input)- filled <- fill mutable operation table mapping freshBuffer 0 0 0 0- case filled of- Left failure -> pure (Left failure)- Right (sumX, sumY, freshCount) -> do- let !existingCount = V.length input - freshCount- setCounter operation CounterInputPoints (V.length input)- setCounter operation CounterUniquePoints freshCount- setCounter operation CounterExistingPoints existingCount- setCounter operation CounterDuplicatePoints existingCount- inserted <-- if freshCount == 0- then pure (Right 0)- else do- let !scale = recip (fromIntegral freshCount)- arena <-- fillRadialArena- mutable- (sumX * scale)- (sumY * scale)- (MUV.unsafeRead freshBuffer)- freshCount- orderedArena <- radiallyOrderArena arena- circleSweepInsert mutable operation orderedArena- case inserted of- Left failure -> pure (Left failure)- Right seedCount -> do- setCounter operation CounterSpatialSeedPoints seedCount- frozenOutcome <- freezeTriangulation mutable- case frozenOutcome of- Left failure -> pure (Left failure)- Right frozen -> do- mapped <- unsafeFreezePrimArray mapping- stats <- freezeBuildStats operation- pure- ( Right- BuildResult- { buildTriangulation = frozen- , buildInputVertices = mapped- , buildStats = stats- }- )+ (mapped, frozen, stats) <-+ runTransaction id DenseTransaction triangulation (V.length input) $ \mutable operation -> do+ table <- newMutablePointIndex (pointCapacity mutable)+ seeded <- seedPointTable mutable table+ case seeded of+ Left failure -> pure (Left failure)+ Right () -> do+ mapping <- newPrimArray (V.length input)+ freshBuffer <- MUV.new (V.length input)+ filled <- fill mutable operation table mapping freshBuffer 0 0 0 0+ case filled of+ Left failure -> pure (Left failure)+ Right (sumX, sumY, freshCount) -> do+ let !existingCount = V.length input - freshCount+ setCounter operation CounterInputPoints (V.length input)+ setCounter operation CounterUniquePoints freshCount+ setCounter operation CounterExistingPoints existingCount+ setCounter operation CounterDuplicatePoints existingCount+ inserted <-+ if freshCount == 0+ then pure (Right 0)+ else do+ let !scale = recip (fromIntegral freshCount)+ arena <-+ fillRadialArena+ mutable+ (sumX * scale)+ (sumY * scale)+ (MUV.unsafeRead freshBuffer)+ freshCount+ orderedArena <- radiallyOrderArena arena+ circleSweepInsert mutable operation orderedArena+ case inserted of+ Left failure -> pure (Left failure)+ Right seedCount -> do+ setCounter operation CounterSpatialSeedPoints seedCount+ Right <$> unsafeFreezePrimArray mapping+ pure+ BuildResult+ { buildTriangulation = frozen+ , buildInputVertices = mapped+ , buildStats = stats+ } where fill :: forall s
src-build/Moonlight/Triangulation/Cdt.hs view
@@ -4,7 +4,8 @@ ( ConstrainedDelaunayTriangulation , CdtError (..) , CorridorObstruction (..)- , ConstraintResult (..)+ , ConstraintRecoveryResult (..)+ , ConstraintResult , ConstraintOutcome (..) , ConstraintBatchStats (..) , ConstraintBatchResult (..)@@ -28,7 +29,7 @@ , constrainedSeamRightConstraintEvidence , constrainedSeamConstraintStats , constrainedSeamBuildStats- , ConstraintSplitBatchResult (..)+ , ConstraintSplitBatchResult , CdtBuildResult (..) , constrainedDelaunay , constrainedDelaunayMaximal@@ -104,8 +105,9 @@ , ConstrainedSeamSource (..) , ConstraintConflict (..) , ConstraintOutcome (..)- , ConstraintResult (..)- , ConstraintSplitBatchResult (..)+ , ConstraintRecoveryResult (..)+ , ConstraintResult+ , ConstraintSplitBatchResult , CorridorObstruction (..) ) import Moonlight.Triangulation.Internal.Cdt.Union
src-build/Moonlight/Triangulation/Insertion.hs view
@@ -14,7 +14,18 @@ import Control.Monad.ST (ST) import Moonlight.Triangulation.Handles.HandleDefs (VertexId (..))-import Moonlight.Triangulation.Internal.DcelOperations+import Moonlight.Triangulation.Internal.DcelOperations.Chain+ ( extendLine+ , lineToArea+ , setupFirstVertex+ , setupSecondVertex+ , splitLineEdge+ )+import Moonlight.Triangulation.Internal.DcelOperations.Hull (insertOutsideHull)+import Moonlight.Triangulation.Internal.DcelOperations.Subdivide+ ( insertIntoFace+ , insertOnEdge+ ) import Moonlight.Triangulation.Internal.Location import Moonlight.Triangulation.Internal.Mutable import Moonlight.Triangulation.Internal.OperationState
src-build/Moonlight/Triangulation/Internal/Cdt/Batch.hs view
@@ -6,6 +6,7 @@ module Moonlight.Triangulation.Internal.Cdt.Batch ( recoverConstraints , recoverConstraintBatch+ , finalizeConstraintBatch , initialConstraintBatchStats , interpretConstraintRequest , interpretConstraintRequests
src-build/Moonlight/Triangulation/Internal/Cdt/Build.hs view
@@ -9,13 +9,13 @@ , constrainedDelaunayMaximal ) where +import Data.Bifunctor (first) import qualified Data.Vector as V import Data.Primitive.PrimArray (PrimArray, indexPrimArray, sizeofPrimArray) import Data.Word (Word32) import Moonlight.Triangulation.BulkLoad (delaunay) import Moonlight.Triangulation.Handles.HandleDefs import Moonlight.Triangulation.Internal.Cdt.Batch (recoverConstraints)-import Moonlight.Triangulation.Internal.Cdt.Combinators (mapLeft) import Moonlight.Triangulation.Internal.Cdt.Types import Moonlight.Triangulation.Internal.Representation import Moonlight.Triangulation.Internal.Types@@ -37,13 +37,7 @@ constrainedDelaunay defaults inputVertices constraints = do result <- constrainedDelaunayMaximal defaults inputVertices constraints if V.null (cdtRejectedConstraints result)- then- Right- BuildResult- { buildTriangulation = cdtBuildTriangulation result- , buildInputVertices = cdtBuildInputVertices result- , buildStats = cdtBuildStats result- }+ then Right (cdtAcceptedBuild result) else Left (ConstraintInputConflicts (cdtRejectedConstraints result)) -- | Stable constrained bulk loading. Duplicate input vertices are rerouted to@@ -56,7 +50,7 @@ -> V.Vector (Int, Int) -> Either (CdtError) (CdtBuildResult vertex directed undirected face) constrainedDelaunayMaximal defaults inputVertices constraints = do- built <- mapLeft CdtBuildError (delaunay defaults inputVertices)+ built <- first CdtBuildError (delaunay defaults inputVertices) let !mapping = buildInputVertices built !initial = fromDelaunay (buildTriangulation built) requests <- V.mapM (mapConstraintRequest mapping) constraints@@ -71,9 +65,12 @@ (V.zip constraints (constraintBatchOutcomes batch)) pure CdtBuildResult- { cdtBuildTriangulation = constraintBatchTriangulation batch- , cdtBuildInputVertices = mapping- , cdtBuildStats = buildStats built+ { cdtAcceptedBuild =+ BuildResult+ { buildTriangulation = constraintBatchTriangulation batch+ , buildInputVertices = mapping+ , buildStats = buildStats built+ } , cdtRejectedConstraints = rejected } where
src-build/Moonlight/Triangulation/Internal/Cdt/Combinators.hs view
@@ -7,7 +7,6 @@ ( foldWhileM , bindMutable , asConstraintStep- , mapLeft , vertexInt , directedInt ) where@@ -57,11 +56,8 @@ -- | Relabel a step whose refusal is a build failure, so it composes with the -- constraint layer's own. asConstraintStep :: Functor f => f (Either BuildError a) -> f (Either (CdtError) a)-asConstraintStep = fmap (mapLeft CdtBuildError)+asConstraintStep = fmap (either (Left . CdtBuildError) Right) {-# INLINE asConstraintStep #-}--mapLeft :: (left -> right) -> Either left value -> Either right value-mapLeft convert = either (Left . convert) Right vertexInt :: VertexId -> Int vertexInt (VertexId value) = fromIntegral value
src-build/Moonlight/Triangulation/Internal/Cdt/Recovery.hs view
@@ -32,7 +32,9 @@ ) import Moonlight.Triangulation.Internal.Cdt.Query (findMutableEdge) import Moonlight.Triangulation.Internal.Cdt.Types-import Moonlight.Triangulation.Internal.DcelOperations+import Moonlight.Triangulation.Internal.DcelOperations.FlipRewrite (flipEdge)+import Moonlight.Triangulation.Internal.DcelOperations.FlipRule (isFlippableEdge)+import Moonlight.Triangulation.Internal.DcelOperations.Legalize (legalizeEdges) import Moonlight.Triangulation.Internal.Growable (GrowableWord32) import Moonlight.Triangulation.Internal.Mutable import Moonlight.Triangulation.Internal.OperationState
src-build/Moonlight/Triangulation/Internal/Cdt/Segment.hs view
@@ -6,21 +6,17 @@ module Moonlight.Triangulation.Internal.Cdt.Segment ( addConstraintEdge , addConstraintEdges- , applyConstraintToExistingEndpoints- , applyAndPublishConstraint- , publishConstraintResult- , insertPolylineVertices , removeConstraintEdge , retireConstraintEdge ) where -import Control.Monad.ST (ST, runST)+import Control.Monad.ST (ST)+import Data.Bifunctor (first) import qualified Data.Vector as V import Data.Primitive.PrimArray (indexPrimArray, sizeofPrimArray) import Moonlight.Triangulation.BulkLoad (insertMany) import qualified Moonlight.Triangulation.Dcel as Dcel import Moonlight.Triangulation.Handles.HandleDefs-import Moonlight.Triangulation.Internal.Capacity (ensureCapacity) import Moonlight.Triangulation.Internal.Cdt.Admission ( ConstraintAdmission (..) , constraintAdmission@@ -29,7 +25,6 @@ import Moonlight.Triangulation.Internal.Cdt.Combinators ( asConstraintStep , bindMutable- , mapLeft ) import Moonlight.Triangulation.Internal.Cdt.Recovery (applyMutableConstraint) import Moonlight.Triangulation.Internal.Cdt.Site@@ -37,19 +32,16 @@ , placeConstraintEndpoint ) import Moonlight.Triangulation.Internal.Cdt.Types-import Moonlight.Triangulation.Internal.DcelOperations+import Moonlight.Triangulation.Internal.DcelOperations.Legalize (legalizeEdges) import Moonlight.Triangulation.Internal.Growable ( GrowableWord32 , newGrowableWord32 ) import Moonlight.Triangulation.Internal.Mutable-import Moonlight.Triangulation.Internal.OperationState- ( Counter (..)- , OperationState- , addCounter- , newOperationState- )+import Moonlight.Triangulation.Internal.OperationState (OperationState)+import Moonlight.Triangulation.Internal.Paged (TransactionShape (LocalTransaction)) import Moonlight.Triangulation.Internal.Representation+import Moonlight.Triangulation.Internal.Transaction (runUnmeasuredTransaction) import Moonlight.Triangulation.Internal.Types import Moonlight.Triangulation.Math @@ -63,8 +55,8 @@ -> vertex -> Either (CdtError) (ConstraintResult vertex directed undirected face) addConstraintEdge triangulation fromVertex toVertex = do- _ <- mapLeft CdtBuildError (validatePoint Nothing fromPoint)- _ <- mapLeft CdtBuildError (validatePoint Nothing toPoint)+ _ <- first CdtBuildError (validatePoint Nothing fromPoint)+ _ <- first CdtBuildError (validatePoint Nothing toPoint) result <- case ( lookupExistingConstraintEndpoint triangulation fromPoint@@ -85,23 +77,21 @@ where !fromPoint = position fromVertex !toPoint = position toVertex- -- Both endpoints may be new; neither may be. The reservation is for the peak.- !capacity = Dcel.numVertices triangulation + 2 addConstraintWithEndpointPlacement = do- mapLeft CdtBuildError (ensureCapacity capacity)- runST $ do- mutable <- thawTriangulation capacity triangulation- operation <- newOperationState (halfEdgeCapacity mutable)- programWords <- newGrowableWord32 256- asConstraintStep (placeConstraintEndpoint mutable operation Nothing fromPoint fromVertex)- `bindMutable` \from ->- asConstraintStep (placeConstraintEndpoint mutable operation Nothing toPoint toVertex)- `bindMutable` \to ->- applyAndPublishConstraint programWords mutable operation from to+ (request, frozen) <-+ runUnmeasuredTransaction CdtBuildError LocalTransaction triangulation 2 $ \mutable operation -> do+ programWords <- newGrowableWord32 256+ asConstraintStep (placeConstraintEndpoint mutable operation Nothing fromPoint fromVertex)+ `bindMutable` \from ->+ asConstraintStep (placeConstraintEndpoint mutable operation Nothing toPoint toVertex)+ `bindMutable` \to ->+ recoverConstraintRequest programWords mutable operation from to+ pure (publishConstraintResult frozen request)+ -- | The common singleton case already owns both sites. Resolve them before--- opening topology, then retain the original payload and counter semantics in--- the transaction. The corridor worker is unchanged; only two redundant point+-- opening topology, then retain the original payload semantics in the+-- transaction. The corridor worker is unchanged; only two redundant point -- locations and two unused vertex-capacity reservations disappear. applyConstraintToExistingEndpoints :: Triangulation 'Constrained vertex directed undirected face@@ -111,55 +101,42 @@ -> vertex -> Either (CdtError) (ConstraintResult vertex directed undirected face) applyConstraintToExistingEndpoints triangulation from@(VertexId rawFrom) fromPayload to@(VertexId rawTo) toPayload =- runST $ do- mutable <- thawTriangulation (Dcel.numVertices triangulation) triangulation- operation <- newOperationState (halfEdgeCapacity mutable)- programWords <- newGrowableWord32 256- addCounter operation CounterInputPoints 2- addCounter operation CounterExistingPoints 2- addCounter operation CounterDuplicatePoints 2- writeVertexData mutable (fromIntegral rawFrom) fromPayload- writeVertexData mutable (fromIntegral rawTo) toPayload- applyAndPublishConstraint programWords mutable operation from to+ do+ (request, frozen) <-+ runUnmeasuredTransaction CdtBuildError LocalTransaction triangulation 0 $ \mutable operation -> do+ programWords <- newGrowableWord32 256+ writeVertexData mutable (fromIntegral rawFrom) fromPayload+ writeVertexData mutable (fromIntegral rawTo) toPayload+ recoverConstraintRequest programWords mutable operation from to+ pure (publishConstraintResult frozen request) -applyAndPublishConstraint+recoverConstraintRequest :: GrowableWord32 s -> MutableDcel s vertex directed undirected face -> OperationState s -> VertexId -> VertexId- -> ST s (Either (CdtError) (ConstraintResult vertex directed undirected face))-applyAndPublishConstraint programWords mutable operation from to =+ -> ST s (Either CdtError ConstraintRequestAccumulator)+recoverConstraintRequest programWords mutable operation from to = applyMutableConstraint programWords mutable operation from to `bindMutable` \applied -> case applied of MutableConstraintRejected blocking -> pure (Left (ConstraintIntersection blocking))- MutableConstraintAccepted request ->- publishConstraintResult- mutable- (accumulatedRequestPath request)- (accumulatedRequestAddedEdges request)-{-# INLINE applyAndPublishConstraint #-}+ MutableConstraintAccepted request -> pure (Right request)+{-# INLINE recoverConstraintRequest #-} --- | Close the transaction on an accepted request. The accumulated path runs--- newest-first, so it is reversed exactly once, here.+-- | Attach one accepted local result to the mesh published by the transaction. publishConstraintResult- :: MutableDcel s vertex directed undirected face- -> [DirectedEdgeId]- -> Int- -> ST s (Either (CdtError) (ConstraintResult vertex directed undirected face))-publishConstraintResult mutable reversedPath added = do- frozenOutcome <- freezeTriangulation mutable- pure $ case frozenOutcome of- Left obstruction -> Left (CdtBuildError obstruction)- Right frozen ->- Right- ConstraintResult- { constraintTriangulation = frozen- , constraintPath = V.fromList (reverse reversedPath)- , constraintAddedEdges = added- }+ :: Triangulation 'Constrained vertex directed undirected face+ -> ConstraintRequestAccumulator+ -> ConstraintResult vertex directed undirected face+publishConstraintResult frozen request =+ ConstraintRecoveryResult+ { constraintRecoveryTriangulation = frozen+ , constraintRecoveryPathReceipt = V.fromList (reverse (accumulatedRequestPath request))+ , constraintRecoveryAddedEdges = accumulatedRequestAddedEdges request+ } -- | Insert a polyline's vertices and recover each adjacent segment as a -- constraint, optionally closing the final segment back to the first.@@ -199,7 +176,7 @@ -> V.Vector vertex -> Either (CdtError) (Triangulation 'Constrained vertex directed undirected face, V.Vector VertexId) insertPolylineVertices triangulation points = do- result <- mapLeft CdtBuildError (insertMany triangulation points)+ result <- first CdtBuildError (insertMany triangulation points) let !mapping = buildInputVertices result pure (buildTriangulation result, V.generate (sizeofPrimArray mapping) (VertexId . indexPrimArray mapping)) @@ -214,11 +191,11 @@ -- An edge carrying no constraint has nothing to retire, and answering that -- without thawing is the difference between O(1) and a republished mesh. | not (Dcel.isConstraintEdge triangulation edge) = Right triangulation- | otherwise = runST $ do- mutable <- thawTriangulation (Dcel.numVertices triangulation) triangulation- operation <- newOperationState (halfEdgeCapacity mutable)- retireConstraintEdge mutable operation edge `bindMutable` \() ->- fmap (mapLeft CdtBuildError) (freezeTriangulation mutable)+ | otherwise = do+ (_, frozen) <-+ runUnmeasuredTransaction CdtBuildError LocalTransaction triangulation 0 $ \mutable operation ->+ retireConstraintEdge mutable operation edge+ pure frozen where edgeCount = Dcel.numUndirectedEdges triangulation
src-build/Moonlight/Triangulation/Internal/Cdt/Split.hs view
@@ -1,39 +1,24 @@ {-# LANGUAGE BangPatterns #-} {-# LANGUAGE DataKinds #-}+{-# LANGUAGE ScopedTypeVariables #-} -- | The splitting program: corridors that divide every constraint they cross, -- driven across as few sealed transactions as the vertex reservation allows. module Moonlight.Triangulation.Internal.Cdt.Split- ( SplitChunkShape (..)- , SplitReceipt (..)- , SplitChunkOutcome (..)- , SplitRequest (..)- , SplitCursor (..)- , SplitStep (..)- , addConstraintAndSplit+ ( addConstraintAndSplit , addConstraintsAndSplit- , splitCensus- , driveConstraintSplits- , runSplitChunk- , splitConstraintCorridor- , mutableEdgeWasSplit- , mutableConstraintBetween- , repairRoundedSplit- , lineIntersection ) where -import Control.Monad.ST (ST, runST)+import Control.Monad.ST (ST) import Data.Bits (xor) import qualified Data.List as List import qualified Data.Vector as V import qualified Moonlight.Triangulation.Dcel as Dcel import Moonlight.Triangulation.Handles.HandleDefs-import Moonlight.Triangulation.Internal.Capacity (ensureCapacity) import Moonlight.Triangulation.Internal.Cdt.Combinators ( asConstraintStep , bindMutable , directedInt- , mapLeft , vertexInt ) import Moonlight.Triangulation.Internal.Cdt.Corridor@@ -57,11 +42,12 @@ , newGrowableWord32 ) import Moonlight.Triangulation.Internal.Mutable-import Moonlight.Triangulation.Internal.OperationState- ( OperationState- , newOperationState+import Moonlight.Triangulation.Internal.OperationState (OperationState)+import Moonlight.Triangulation.Internal.Paged+ ( TransactionShape (DenseTransaction, LocalTransaction) ) import Moonlight.Triangulation.Internal.Representation+import Moonlight.Triangulation.Internal.Transaction (runUnmeasuredTransaction) import Moonlight.Triangulation.Internal.Types import Moonlight.Triangulation.Math @@ -95,22 +81,12 @@ = SplitSettled !SplitCursor | SplitSuspended !SplitCursor --- | The singleton opens corridor-local copy-on-write pages; the batch pays one--- dense copy up front and writes flat.-data SplitChunkShape = SparseSplitChunk | DenseSplitChunk- -- | What one settled request leaves behind. data SplitReceipt = SplitReceipt { splitReceiptPath :: !(V.Vector DirectedEdgeId) , splitReceiptAdded :: {-# UNPACK #-} !Int } -data SplitChunkOutcome vertex directed undirected face = SplitChunkOutcome- { splitChunkTriangulation :: !(Triangulation 'Constrained vertex directed undirected face)- , splitChunkReceipts :: ![SplitReceipt]- , splitChunkSuspended :: !(Maybe SplitCursor)- }- -- | Glue the requested segment, dividing every constraint it crosses at the -- crossing point rather than refusing it. One transaction carries the whole -- corridor: a singleton is a batch of one under the shared driver.@@ -123,19 +99,19 @@ addConstraintAndSplit makeVertex triangulation from to = do validateEndpoints triangulation from to if from == to- then pure (ConstraintResult triangulation V.empty 0)+ then pure (ConstraintRecoveryResult triangulation V.empty 0) else do (published, receipts) <-- driveConstraintSplits SparseSplitChunk makeVertex triangulation (V.singleton (from, to))+ driveConstraintSplits LocalTransaction makeVertex triangulation (V.singleton (from, to)) pure ( case receipts V.!? 0 of Just receipt ->- ConstraintResult- { constraintTriangulation = published- , constraintPath = splitReceiptPath receipt- , constraintAddedEdges = splitReceiptAdded receipt+ ConstraintRecoveryResult+ { constraintRecoveryTriangulation = published+ , constraintRecoveryPathReceipt = splitReceiptPath receipt+ , constraintRecoveryAddedEdges = splitReceiptAdded receipt }- Nothing -> ConstraintResult published V.empty 0+ Nothing -> ConstraintRecoveryResult published V.empty 0 ) -- | Divide every requested segment inside as few transactions as the vertex@@ -151,12 +127,12 @@ -> V.Vector (VertexId, VertexId) -> Either (CdtError) (ConstraintSplitBatchResult vertex directed undirected face) addConstraintsAndSplit makeVertex triangulation requests = do- (published, receipts) <- driveConstraintSplits DenseSplitChunk makeVertex triangulation requests+ (published, receipts) <- driveConstraintSplits DenseTransaction makeVertex triangulation requests pure- ConstraintSplitBatchResult- { splitBatchTriangulation = published- , splitBatchPaths = V.map splitReceiptPath receipts- , splitBatchAddedEdges = V.sum (V.map splitReceiptAdded receipts)+ ConstraintRecoveryResult+ { constraintRecoveryTriangulation = published+ , constraintRecoveryPathReceipt = V.map splitReceiptPath receipts+ , constraintRecoveryAddedEdges = V.sum (V.map splitReceiptAdded receipts) } -- | The obstruction census for one request against a published mesh: how many@@ -191,7 +167,8 @@ -- that census ran against the very mesh the chunk thawed and is exact before -- any in-session rewrite. Termination follows. driveConstraintSplits- :: SplitChunkShape+ :: forall vertex directed undirected face.+ TransactionShape -> (Point -> vertex) -> Triangulation 'Constrained vertex directed undirected face -> V.Vector (VertexId, VertexId)@@ -204,6 +181,16 @@ V.mapM_ (uncurry (validateEndpoints base)) requests advance base 0 [] Nothing where+ advance+ :: Triangulation 'Constrained vertex directed undirected face+ -> Int+ -> [SplitReceipt]+ -> Maybe SplitCursor+ -> Either+ CdtError+ ( Triangulation 'Constrained vertex directed undirected face+ , V.Vector SplitReceipt+ ) advance triangulation start settled resumed | start >= V.length requests = Right (triangulation, V.fromList (reverse settled))@@ -222,24 +209,32 @@ | firstRound = V.replicate (V.length remaining) (1, Nothing) | otherwise = V.map (splitCensus triangulation) remaining !reservedSites = V.sum (V.map fst censuses)- !capacity = Dcel.numVertices triangulation + 2 * reservedSites + 8+ !additionalCapacity = 2 * reservedSites + 8 -- The walk budget bounds divisions per request; constraints born -- inside the chunk are covered by the reservation term. !budget = 2 * (Dcel.numConstraints triangulation + 2 * reservedSites + V.length remaining) + Dcel.numUndirectedEdges triangulation + 8- mapLeft CdtBuildError (ensureCapacity capacity)- outcome <- runST $ do- mutable <- case shape of- SparseSplitChunk -> thawTriangulation capacity triangulation- DenseSplitChunk -> thawTriangulationDense capacity triangulation- operation <- newOperationState (halfEdgeCapacity mutable)- programWords <- newGrowableWord32 32- runSplitChunk makeVertex mutable operation programWords remaining censuses capacity budget resumed- case outcome of- SplitChunkOutcome published receipts suspended ->- advance published (start + length receipts) (receipts ++ settled) suspended+ ((receipts, suspended), published) <-+ runUnmeasuredTransaction+ CdtBuildError+ shape+ triangulation+ additionalCapacity+ $ \mutable operation -> do+ programWords <- newGrowableWord32 32+ runSplitChunk+ makeVertex+ mutable+ operation+ programWords+ remaining+ censuses+ (pointCapacity mutable)+ budget+ resumed+ advance published (start + length receipts) (receipts ++ settled) suspended -- | One sealed transaction over a prefix of the remaining requests. Receipts -- run newest-first; a suspension carries no receipt, so the settled count is@@ -254,12 +249,12 @@ -> Int -> Int -> Maybe SplitCursor- -> ST s (Either (CdtError) (SplitChunkOutcome vertex directed undirected face))+ -> ST s (Either CdtError ([SplitReceipt], Maybe SplitCursor)) runSplitChunk makeVertex mutable operation programWords requests censuses capacity budget resumed = go 0 [] resumed where go offset receipts pending- | offset >= V.length requests = seal receipts Nothing+ | offset >= V.length requests = pure (Right (receipts, Nothing)) | otherwise = let (from, to) = requests V.! offset in case pending of@@ -285,19 +280,13 @@ SplitSettled settledCursor -> go (offset + 1) (receiptOf settledCursor : receipts) Nothing SplitSuspended suspendedCursor ->- seal receipts (Just suspendedCursor)+ pure (Right (receipts, Just suspendedCursor)) receiptOf cursor = SplitReceipt { splitReceiptPath = V.fromList (reverse (splitCursorPath cursor)) , splitReceiptAdded = splitCursorAdded cursor }-- seal receipts suspended = do- frozenOutcome <- freezeTriangulation mutable- pure $ case frozenOutcome of- Left obstruction -> Left (CdtBuildError obstruction)- Right frozen -> Right (SplitChunkOutcome frozen receipts suspended) splitConstraintCorridor :: SplitRequest vertex
src-build/Moonlight/Triangulation/Internal/Cdt/Types.hs view
@@ -16,12 +16,13 @@ , ConstrainedSeamConstraintEvidence (..) , ConstrainedSeamResult (..) , CorridorObstruction (..)- , ConstraintResult (..)+ , ConstraintRecoveryResult (..)+ , ConstraintResult , ConstraintOutcome (..) , ConstraintBatchStats (..) , ConstraintBatchResult (..) , ConstrainedExtensionResult (..)- , ConstraintSplitBatchResult (..)+ , ConstraintSplitBatchResult , ConstraintBatchAccumulator (..) , ConstraintRequestAccumulator (..) , ConstraintWorkspace (..)@@ -37,8 +38,6 @@ import Control.DeepSeq (NFData) import Data.List.NonEmpty (NonEmpty) import qualified Data.Vector as V-import Data.Primitive.PrimArray (PrimArray)-import Data.Word (Word32) import Moonlight.Triangulation.Handles.HandleDefs ( DirectedEdgeId , FaceId@@ -46,7 +45,7 @@ , VertexId ) import Moonlight.Triangulation.Internal.Growable (GrowableWord32)-import Moonlight.Triangulation.Internal.Representation (Triangulation)+import Moonlight.Triangulation.Internal.Representation (BuildResult, Triangulation) import Moonlight.Triangulation.Internal.Types import Moonlight.Triangulation.IntersectionIterator (Intersection) import GHC.Generics (Generic)@@ -216,25 +215,31 @@ deriving stock (Eq, Show, Generic) deriving anyclass (NFData) --- | Atomic result of admitting one constraint segment.-data ConstraintResult vertex directed undirected face = ConstraintResult- { constraintTriangulation :: !(Triangulation 'Constrained vertex directed undirected face)+-- | One constrained publication and its path receipt. The path carrier names+-- whether the caller requested one recovered path or an ordered family; the+-- mesh and edge count have one representation in either case.+data ConstraintRecoveryResult pathReceipt vertex directed undirected face = ConstraintRecoveryResult+ { constraintRecoveryTriangulation :: !(Triangulation 'Constrained vertex directed undirected face) -- ^ Published constrained triangulation.- , constraintPath :: !(V.Vector DirectedEdgeId)- -- ^ Directed-edge path representing the segment at publication time.- , constraintAddedEdges :: {-# UNPACK #-} !Int- -- ^ Edges created while recovering the path.+ , constraintRecoveryPathReceipt :: !pathReceipt+ -- ^ Recovered path evidence at the operation's requested multiplicity.+ , constraintRecoveryAddedEdges :: {-# UNPACK #-} !Int+ -- ^ Edges created while recovering the path evidence. } deriving stock (Generic) deriving anyclass (NFData) deriving stock instance- (Eq vertex, Eq directed, Eq undirected, Eq face)- => Eq (ConstraintResult vertex directed undirected face)+ (Eq pathReceipt, Eq vertex, Eq directed, Eq undirected, Eq face)+ => Eq (ConstraintRecoveryResult pathReceipt vertex directed undirected face) deriving stock instance- (Show vertex, Show directed, Show undirected, Show face)- => Show (ConstraintResult vertex directed undirected face)+ (Show pathReceipt, Show vertex, Show directed, Show undirected, Show face)+ => Show (ConstraintRecoveryResult pathReceipt vertex directed undirected face) +-- | Atomic result of admitting one constraint segment.+type ConstraintResult vertex directed undirected face =+ ConstraintRecoveryResult (V.Vector DirectedEdgeId) vertex directed undirected face+ -- | Per-request outcome in a constraint batch. data ConstraintOutcome = ConstraintAccepted@@ -288,12 +293,8 @@ -- interpreted. A later refinement is deliberately a separate operation over -- this immutable result, not a hidden continuation of this transaction. data ConstrainedExtensionResult vertex directed undirected face = ConstrainedExtensionResult- { constrainedExtensionTriangulation :: !(Triangulation 'Constrained vertex directed undirected face)- -- ^ Published extension.- , constrainedExtensionConstraintOutcomes :: !(V.Vector ConstraintOutcome)- -- ^ Outcomes for incoming constraints in request order.- , constrainedExtensionConstraintStats :: !ConstraintBatchStats- -- ^ Constraint work performed by the extension.+ { constrainedExtensionConstraintBatch :: {-# UNPACK #-} !(ConstraintBatchResult vertex directed undirected face)+ -- ^ Published extension and incoming-constraint receipts. , constrainedExtensionBuildStats :: !BuildStats -- ^ Topology work performed by the extension. }@@ -311,23 +312,8 @@ -- per-request receipts in traversal order; a later request may have rewritten -- topology an earlier path names, which is the same as-traversed reading the -- singleton path already carries.-data ConstraintSplitBatchResult vertex directed undirected face = ConstraintSplitBatchResult- { splitBatchTriangulation :: !(Triangulation 'Constrained vertex directed undirected face)- -- ^ Published constrained triangulation.- , splitBatchPaths :: !(V.Vector (V.Vector DirectedEdgeId))- -- ^ Recovered paths in request order.- , splitBatchAddedEdges :: {-# UNPACK #-} !Int- -- ^ Edges created across all requested divisions.- }- deriving stock (Generic)- deriving anyclass (NFData)--deriving stock instance- (Eq vertex, Eq directed, Eq undirected, Eq face)- => Eq (ConstraintSplitBatchResult vertex directed undirected face)-deriving stock instance- (Show vertex, Show directed, Show undirected, Show face)- => Show (ConstraintSplitBatchResult vertex directed undirected face)+type ConstraintSplitBatchResult vertex directed undirected face =+ ConstraintRecoveryResult (V.Vector (V.Vector DirectedEdgeId)) vertex directed undirected face data ConstraintBatchAccumulator = ConstraintBatchAccumulator { accumulatedConstraintOutcomes :: ![ConstraintOutcome]@@ -386,21 +372,10 @@ -- | Result of maximal constrained construction, including rejected requests. data CdtBuildResult vertex directed undirected face = CdtBuildResult- { cdtBuildTriangulation :: !(Triangulation 'Constrained vertex directed undirected face)- -- ^ Published constrained triangulation.- , cdtBuildInputVertices :: !(PrimArray Word32)- -- ^ Input-to-vertex handle projection.- , cdtBuildStats :: !BuildStats- -- ^ Topology work performed by construction.+ { cdtAcceptedBuild :: {-# UNPACK #-} !(BuildResult 'Constrained vertex directed undirected face)+ -- ^ Canonical build result containing every admitted constraint. , cdtRejectedConstraints :: !(V.Vector (Int, Int)) -- ^ Input-index pairs that could not be admitted. } deriving stock (Generic) deriving anyclass (NFData)--deriving stock instance- (Eq vertex, Eq directed, Eq undirected, Eq face)- => Eq (CdtBuildResult vertex directed undirected face)-deriving stock instance- (Show vertex, Show directed, Show undirected, Show face)- => Show (CdtBuildResult vertex directed undirected face)
src-build/Moonlight/Triangulation/Internal/Cdt/Union.hs view
@@ -21,6 +21,7 @@ import Control.Monad (foldM) import Control.Monad.ST (ST)+import qualified Data.Bifunctor as Bifunctor import Data.Either (isRight) import Data.List (sort) import qualified Data.List.NonEmpty as NonEmpty@@ -33,11 +34,12 @@ import Moonlight.Triangulation.Internal.Canonical (canonicalize) import Moonlight.Triangulation.Insertion (insertPointCombining) import Moonlight.Triangulation.Internal.Cdt.Batch- ( interpretConstraintRequests+ ( finalizeConstraintBatch+ , interpretConstraintRequests , recoverConstraints ) import Moonlight.Triangulation.Internal.Cdt.Build (fromDelaunay)-import Moonlight.Triangulation.Internal.Cdt.Combinators (foldWhileM, mapLeft)+import Moonlight.Triangulation.Internal.Cdt.Combinators (foldWhileM) import Moonlight.Triangulation.Internal.Cdt.Query (constraintEdges) import Moonlight.Triangulation.Internal.Cdt.Types import Moonlight.Triangulation.Internal.Join.Rebuild (rebuildCanonicalSiteSet)@@ -102,12 +104,12 @@ Just witnesses -> Left (ConstraintUnionConflicts witnesses) Nothing -> do unconstrained <-- mapLeft+ Bifunctor.first (ConstraintUnionConstructionFailed . CdtBuildError) (rebuildCanonicalSiteSet unionSites) requests <- traverse (segmentRequest unconstrained) (V.toList segments) recovered <-- mapLeft ConstraintUnionConstructionFailed+ Bifunctor.first ConstraintUnionConstructionFailed (recoverConstraints (fromDelaunay unconstrained) (V.fromList requests)) case firstRejected (constraintBatchOutcomes recovered) of Just blocking ->@@ -116,7 +118,7 @@ (ConstraintIntersection blocking) ) Nothing ->- mapLeft+ Bifunctor.first (ConstraintUnionConstructionFailed . CdtBuildError) (canonicalize (constraintBatchTriangulation recovered)) where@@ -161,7 +163,7 @@ joinSeparatedConstrained left right = do seamPlan <- maybe (Left ConstraintUnionNotSeparated) Right (planSeam left right) seamExecution <-- mapLeft+ Bifunctor.first (ConstraintUnionConstructionFailed . CdtBuildError) (executeConstrainedSeam seamPlan left right) let copied = seamExecutionTriangulation seamExecution@@ -172,7 +174,7 @@ V.filter (`Set.notMember` copiedSegments) (leftSegments V.++ rightSegments) requests <- traverse (segmentRequest copied) (V.toList missingSegments) recovered <-- mapLeft ConstraintUnionConstructionFailed+ Bifunctor.first ConstraintUnionConstructionFailed (recoverConstraints copied (V.fromList requests)) case firstRejected (constraintBatchOutcomes recovered) of Just blocking ->@@ -182,7 +184,7 @@ ) Nothing -> do published <-- mapLeft+ Bifunctor.first (ConstraintUnionConstructionFailed . CdtBuildError) (canonicalize (constraintBatchTriangulation recovered)) targetFaces <- targetFaceIndex published@@ -368,9 +370,7 @@ (insertAndRecoverExtension combine extensionSites extensionSegments) pure ConstrainedExtensionResult- { constrainedExtensionTriangulation = extended- , constrainedExtensionConstraintOutcomes = accumulatorOutcomes completed- , constrainedExtensionConstraintStats = accumulatedConstraintStats completed+ { constrainedExtensionConstraintBatch = finalizeConstraintBatch extended completed , constrainedExtensionBuildStats = buildStats } where@@ -408,7 +408,7 @@ Right requests -> do interpreted <- fmap- (mapLeft ConstraintUnionConstructionFailed)+ (Bifunctor.first ConstraintUnionConstructionFailed) (interpretConstraintRequests (V.fromList requests) mutable operation) case interpreted of Left obstruction -> pure (Left obstruction)@@ -447,7 +447,7 @@ insertOne rejected@(Left _) _ = pure rejected insertOne (Right accumulated) (point, annotation) = fmap- ( mapLeft (ConstraintUnionConstructionFailed . CdtBuildError)+ ( Bifunctor.first (ConstraintUnionConstructionFailed . CdtBuildError) . fmap (\(vertex, _) -> (point, VertexId (fromIntegral vertex)) : accumulated) )
src-build/Moonlight/Triangulation/Internal/CircleSweep.hs view
@@ -18,7 +18,7 @@ import Data.Word (Word32) import Moonlight.Triangulation.Handles.HandleDefs (DirectedEdgeId (..)) import Moonlight.Triangulation.Insertion (insertExistingVertex)-import Moonlight.Triangulation.Internal.DcelOperations+import Moonlight.Triangulation.Internal.DcelOperations.Hull ( ReservedSweepCells , SweepCellCursor , SweepInsertion (..)@@ -447,17 +447,16 @@ -- | Whether an outer edge's key orders at or before the stated query key, -- settled field by field without materializing either key.-edgeAtMost+edgeAtMostAtAngle :: DenseMutableDcel s vertex directed undirected face- -> Hull s -> Int -> Double -> Double -> Double+ -> Double -> Int -> ST s Bool-edgeAtMost dense hull edge queryAngle queryX queryY tie = do- angle <- readAngle hull edge+edgeAtMostAtAngle dense edge angle queryAngle queryX queryY tie = case compare angle queryAngle of LT -> pure True GT -> pure False@@ -473,6 +472,7 @@ LT -> pure True GT -> pure False EQ -> pure (edge <= tie)+{-# INLINE edgeAtMostAtAngle #-} -- | Reconcile the derived angular index once after the local topology section -- has glued. Replacing one outer edge by two adds one active edge; every closed@@ -516,22 +516,21 @@ else adjustFromBoundary bucket (fromIntegral raw) where adjustFromBoundary !bucket !initial = do- initialAtMost <- cyclicAtMost bucket initial+ initialAngle <- readAngle hull initial+ initialAtMost <- cyclicAtMost bucket initial initialAngle if initialAtMost- then advance active initial+ then advance active initial initialAngle else retreat active initial where- cyclicAtMost boundaryBucket edge = do- angle <- readAngle hull edge+ cyclicAtMost boundaryBucket edge angle = if boundaryBucket == 0 && angle > queryAngle then pure True- else edgeAtMost dense hull edge queryAngle queryX queryY maxBound+ else edgeAtMostAtAngle dense edge angle queryAngle queryX queryY maxBound - advance !remaining !edge+ advance !remaining !edge !edgeAngle | remaining <= 0 = pure initial | otherwise = do following <- denseReadNext dense edge- edgeAngle <- readAngle hull edge followingAngle <- readAngle hull following -- Only the bucket-zero anchor may precede the query by crossing the -- angular seam. Once the walk leaves that anchor, ordinary key order@@ -540,18 +539,19 @@ -- entire ring. let crossesSeam = followingAngle < edgeAngle seamPermitted = bucket == 0 && edgeAngle > queryAngle- followingAtMost <- edgeAtMost dense hull following queryAngle queryX queryY maxBound+ followingAtMost <- edgeAtMostAtAngle dense following followingAngle queryAngle queryX queryY maxBound if (not crossesSeam || seamPermitted) && followingAtMost- then advance (remaining - 1) following+ then advance (remaining - 1) following followingAngle else pure edge retreat !remaining !edge | remaining <= 0 = pure initial | otherwise = do previous <- denseReadPrevious dense edge- previousAtMost <- cyclicAtMost bucket previous+ previousAngle <- readAngle hull previous+ previousAtMost <- cyclicAtMost bucket previous previousAngle if previousAtMost- then advance remaining previous+ then advance remaining previous previousAngle else retreat (remaining - 1) previous insertDeferred@@ -677,17 +677,15 @@ fromY <- denseReadPointY dense fromVertex middleX <- denseReadPointX dense middleVertex middleY <- denseReadPointY dense middleVertex- if orient2dWide fromX fromY middleX middleY insertedX insertedY /= GT+ middleAngle <- readAngle hull second+ -- Same-ray/acute compatibility rejects most local sections. Settle that+ -- cheap obstruction before paying for the exact orientation predicate; the+ -- conjunction is unchanged, only its evaluation order is less profligate.+ if+ middleAngle /= insertedAngle+ && not (acuteAtMiddle fromX fromY middleX middleY insertedX insertedY) then pure False- else do- -- The second edge begins at the middle vertex and stands on the outer- -- cycle, so its cached key already is that vertex's pseudo-angle;- -- same-ray is a slot read rather than a second angle.- middleAngle <- readAngle hull second- pure- ( middleAngle == insertedAngle- || acuteAtMiddle fromX fromY middleX middleY insertedX insertedY- )+ else pure (orient2dCoordinates fromX fromY middleX middleY insertedX insertedY == GT) {-# INLINE shouldCloseLeftTurn #-} -- | The symmetric right-hand test, where the inserted point is the first@@ -709,23 +707,15 @@ middleY <- denseReadPointY dense middleVertex targetX <- denseReadPointX dense targetVertex targetY <- denseReadPointY dense targetVertex- if orient2dWide insertedX insertedY middleX middleY targetX targetY /= GT+ middleAngle <- readAngle hull second+ -- Symmetric to the left-hand descent above: compatibility first, exact+ -- orientation only for sections that can actually glue.+ if+ middleAngle /= insertedAngle+ && not (acuteAtMiddle insertedX insertedY middleX middleY targetX targetY) then pure False- else do- middleAngle <- readAngle hull second- pure- ( middleAngle == insertedAngle- || acuteAtMiddle insertedX insertedY middleX middleY targetX targetY- )+ else pure (orient2dCoordinates insertedX insertedY middleX middleY targetX targetY == GT) {-# INLINE shouldCloseRightTurn #-}---- The deferred turn test deliberately runs the widened Binary64 predicate--- rather than the exact binary64 one; only the terminal Graham pass owns exact--- convexity. This is the class's Double instance called by name, which is--- what the boxed 'orient2d' on widened points resolved to.-orient2dWide :: Double -> Double -> Double -> Double -> Double -> Double -> Ordering-orient2dWide = orient2dCoordinates-{-# INLINE orient2dWide #-} -- Spade's deferred-convexity rule is local: close the turn when the angle at -- the shared hull vertex is strictly below 90 degrees. Requiring the entire
src-build/Moonlight/Triangulation/Internal/Excision.hs view
@@ -11,7 +11,11 @@ import Data.Bits (xor) import Data.Foldable (traverse_) import qualified Data.IntSet as IntSet-import Moonlight.Triangulation.Internal.DcelOperations (flipEdge, legalizeCavityFanScratch, legalizeEdges)+import Moonlight.Triangulation.Internal.DcelOperations.FlipRewrite (flipEdge)+import Moonlight.Triangulation.Internal.DcelOperations.Legalize+ ( legalizeCavityFanScratch+ , legalizeEdges+ ) import Moonlight.Triangulation.Handles.HandleDefs import Moonlight.Triangulation.Internal.Mutable import Moonlight.Triangulation.Internal.OperationState
src-build/Moonlight/Triangulation/Internal/Join/Seam.hs view
@@ -37,7 +37,8 @@ , UndirectedEdgeId (..) , VertexId (..) )-import Moonlight.Triangulation.Internal.DcelOperations (closeOuterTurn, legalizeEdges)+import Moonlight.Triangulation.Internal.DcelOperations.Hull (closeOuterTurn)+import Moonlight.Triangulation.Internal.DcelOperations.Legalize (legalizeEdges) import Moonlight.Triangulation.Internal.Cdt.Query (constraintEdges) import Moonlight.Triangulation.Internal.Mutable import Moonlight.Triangulation.Internal.OperationState@@ -334,7 +335,7 @@ BuildError (Triangulation outputMode vertex () () (), BuildStats) mergeSeparated constraintSections left right (SeamTangents lowerLeft lowerRight upperLeft upperRight) = runST $ do- mutable <- newMutableDcel unitElementDefaults (generalDcelCapacity totalVertices)+ mutable <- newMutableDcel unitElementDefaults (planarDcelCapacity totalVertices) pointCapacityOutcome <- ensurePointCapacity mutable totalVertices cellCapacityOutcome <- ensureCellCapacity
src-build/Moonlight/Triangulation/Internal/Location.hs view
@@ -9,8 +9,9 @@ ) where import Control.Monad.ST (ST)-import Data.Bits (xor, (.&.))+import Data.Bits ((.&.)) import Data.STRef (readSTRef, writeSTRef)+import Moonlight.Triangulation.Internal.DcelOperations.Twin (reverseIndex) import Moonlight.Triangulation.Internal.FaceProbe import Moonlight.Triangulation.Internal.Mutable import Moonlight.Triangulation.Internal.OperationState@@ -30,10 +31,6 @@ | MutableOutsideHull {-# UNPACK #-} !Int deriving stock (Eq, Ord, Show) -reverseIndex :: Int -> Int-reverseIndex edge = edge `xor` 1-{-# INLINE reverseIndex #-}- locateMutable :: MutableDcel s vertex directed undirected face -> OperationState s@@ -316,5 +313,4 @@ else do edgeNext <- readNext mutable edge if edgeNext == start then pure (maybe edge id fallback') else go (remaining - 1) start edgeNext fallback'-
− src-build/Moonlight/Triangulation/Internal/Minkowski/Convex.hs
@@ -1,326 +0,0 @@--- | Pure exact convex-polygon algebra: admission, linear edge-angle--- convolution, reflection, hull construction, and support-half-plane erosion.-module Moonlight.Triangulation.Internal.Minkowski.Convex- ( convexPolygon- , convexPolygonPoints- , convexPolygonRegion- , admittedConvexLoop- , structuringElement- , structuringElementPolygon- , convexMinkowskiSum- , convexMinkowskiPolygon- , convexHullPolygon- , reflectConvexPolygon- , convexPolygonCentroid- , erodeConvexBy- , addExactPoints- , subtractExactPoints- ) where--import Control.Monad (foldM)-import Data.Bifunctor (first)-import qualified Data.List as List-import Data.List.NonEmpty (NonEmpty (..))-import qualified Data.List.NonEmpty as NonEmpty-import qualified Data.Set as Set-import Moonlight.Triangulation.Exact- ( ExactPoint- , ExactVector (..)- , addExactVectors- , compareExactVectorAngle- , exactVectorFromPoints- , exactSegment- , exactSupportingLineIntersection- , exactOrient2d- , exactPoint- , exactPointCoordinates- , translateExactPoint- )-import Moonlight.Triangulation.Internal.BoundaryCycle- ( cyclePairs- , cyclePairsNonEmpty- , cyclicTriples- , rotateCycleLeast- , rotateCycleLeastBy- )-import Moonlight.Triangulation.Internal.ExactRational- ( ExactRational- , exactDivide- )-import Moonlight.Triangulation.Internal.Minkowski.Types- ( ConvexPolygon (..)- , MinkowskiError (..)- , StructuringElement (..)- )-import Moonlight.Triangulation.Internal.Region.Types- ( ExactLoop (..)- , PlanarRegion (..)- , PolygonComponent (..)- , RegionPointLocation (..)- )-import Moonlight.Triangulation.Region- ( exactLoop- , exactLoopPoints- , regionPointLocation- )--convexPolygon- :: NonEmpty ExactPoint- -> Either MinkowskiError ConvexPolygon-convexPolygon submitted = do- loop <- first MinkowskiInvalidConvexLoop (exactLoop submitted)- let points = exactLoopPoints loop- case- [ (index, turn)- | (index, (previous, current, next)) <-- zip [0 :: Int ..] (cyclicTriples (NonEmpty.toList points))- , let turn = exactOrient2d previous current next- , turn /= GT- ] of- (index, turn) : _ -> Left (MinkowskiNonConvexTurn index turn)- [] -> Right (ConvexPolygon loop)--convexPolygonPoints :: ConvexPolygon -> NonEmpty ExactPoint-convexPolygonPoints (ConvexPolygon loop) = exactLoopPoints loop--convexPolygonRegion :: ConvexPolygon -> PlanarRegion-convexPolygonRegion (ConvexPolygon loop) =- PlanarRegion [PolygonComponent loop []]--admittedConvexLoop :: ExactLoop -> Maybe ConvexPolygon-admittedConvexLoop loop- | all ((== GT) . orderedTurn) (cyclicTriples (NonEmpty.toList (exactLoopPoints loop))) =- Just (ConvexPolygon loop)- | otherwise = Nothing- where- orderedTurn (previous, current, next) =- exactOrient2d previous current next--structuringElement- :: ConvexPolygon- -> Either MinkowskiError StructuringElement-structuringElement polygon =- let origin = exactPoint 0 0- location = regionPointLocation (convexPolygonRegion polygon) origin- in case location of- RegionExterior -> Left (MinkowskiOriginOutside location)- _ -> Right (StructuringElement polygon)--structuringElementPolygon :: StructuringElement -> ConvexPolygon-structuringElementPolygon (StructuringElement polygon) = polygon--convexMinkowskiSum- :: ConvexPolygon- -> ConvexPolygon- -> PlanarRegion-convexMinkowskiSum left right =- convexPolygonRegion (convexMinkowskiPolygon left right)--convexMinkowskiPolygon- :: ConvexPolygon- -> ConvexPolygon- -> ConvexPolygon-convexMinkowskiPolygon left right =- let leftPoints = rotateCycleLeastBy pointSweepKey (convexPolygonPoints left)- rightPoints = rotateCycleLeastBy pointSweepKey (convexPolygonPoints right)- start = addExactPoints (NonEmpty.head leftPoints) (NonEmpty.head rightPoints)- directions =- mergeDirections- (edgeDirections leftPoints)- (edgeDirections rightPoints)- directionList = NonEmpty.toList directions- scanned = scanl translateExactPoint start directionList- resultPoints = start :| take (length directionList - 1) (drop 1 scanned)- in ConvexPolygon (ExactLoop (rotateCycleLeast resultPoints))--convexHullPolygon- :: NonEmpty ExactPoint- -> Either MinkowskiError ConvexPolygon-convexHullPolygon submitted =- let points = NonEmpty.toList submitted- in case convexHullPoints points of- Nothing -> Left (MinkowskiConvexHullDegenerate points)- Just hullPoints -> Right (ConvexPolygon (ExactLoop (rotateCycleLeast hullPoints)))--reflectConvexPolygon :: ConvexPolygon -> ConvexPolygon-reflectConvexPolygon polygon =- ConvexPolygon- ( ExactLoop- ( rotateCycleLeast- (NonEmpty.reverse (fmap negateExactPoint (convexPolygonPoints polygon)))- )- )--convexPolygonCentroid- :: ConvexPolygon- -> Either MinkowskiError ExactPoint-convexPolygonCentroid polygon = do- let points = convexPolygonPoints polygon- count = fromIntegral (NonEmpty.length points)- (sumX, sumY) =- List.foldl'- (\(accumulatedX, accumulatedY) point ->- let (x, y) = exactPointCoordinates point- in (accumulatedX + x, accumulatedY + y))- (0, 0)- points- x <- first MinkowskiExactArithmetic (exactDivide sumX count)- y <- first MinkowskiExactArithmetic (exactDivide sumY count)- pure (exactPoint x y)---- | Erode one convex polygon by another through the strongest translated--- support half-plane for each source edge. Sutherland--Hodgman descent keeps--- the construction exact; a lower-dimensional residual is represented by the--- empty polygonal region at this two-dimensional publication boundary.-erodeConvexBy- :: ConvexPolygon- -> ConvexPolygon- -> Either MinkowskiError (Maybe ConvexPolygon)-erodeConvexBy source kernel = do- let sourcePoints = convexPolygonPoints source- kernelPoints = convexPolygonPoints kernel- firstKernel = NonEmpty.head kernelPoints- initial =- map- (`subtractExactPoints` firstKernel)- (NonEmpty.toList sourcePoints)- halfPlanes =- [ strongestHalfPlane kernelPoints from to- | (from, to) <- cyclePairs sourcePoints- ]- clipped <- foldM clipPolygon initial halfPlanes- pure (ConvexPolygon . ExactLoop . rotateCycleLeast <$> convexHullPoints clipped)--edgeDirections :: NonEmpty ExactPoint -> NonEmpty ExactVector-edgeDirections = fmap (uncurry exactVectorFromPoints) . cyclePairsNonEmpty--mergeDirections- :: NonEmpty ExactVector- -> NonEmpty ExactVector- -> NonEmpty ExactVector-mergeDirections (left :| leftTail) (right :| rightTail) =- case compareExactVectorAngle left right of- LT -> left :| mergeRemaining leftTail (right : rightTail)- GT -> right :| mergeRemaining (left : leftTail) rightTail- EQ -> addExactVectors left right :| mergeRemaining leftTail rightTail--mergeRemaining :: [ExactVector] -> [ExactVector] -> [ExactVector]-mergeRemaining [] right = right-mergeRemaining left [] = left-mergeRemaining left@(leftHead : leftTail) right@(rightHead : rightTail) =- case compareExactVectorAngle leftHead rightHead of- LT -> leftHead : mergeRemaining leftTail right- GT -> rightHead : mergeRemaining left rightTail- EQ -> addExactVectors leftHead rightHead : mergeRemaining leftTail rightTail--pointSweepKey :: ExactPoint -> (ExactRational, ExactRational)-pointSweepKey point =- let (x, y) = exactPointCoordinates point- in (y, x)--convexHullPoints :: [ExactPoint] -> Maybe (NonEmpty ExactPoint)-convexHullPoints submitted =- case Set.toAscList (Set.fromList submitted) of- firstPoint : secondPoint : thirdPoint : remaining ->- let ordered = firstPoint : secondPoint : thirdPoint : remaining- lower = dropFinal (reverse (List.foldl' hullStep [] ordered))- upper = dropFinal (reverse (List.foldl' hullStep [] (reverse ordered)))- in case lower <> upper of- firstHullPoint : secondHullPoint : thirdHullPoint : hullTail ->- Just (firstHullPoint :| (secondHullPoint : thirdHullPoint : hullTail))- _ -> Nothing- _ -> Nothing--hullStep :: [ExactPoint] -> ExactPoint -> [ExactPoint]-hullStep (current : previous : remaining) candidate- | exactOrient2d previous current candidate /= GT =- hullStep (previous : remaining) candidate-hullStep hull candidate = candidate : hull--dropFinal :: [value] -> [value]-dropFinal values =- case reverse values of- _ : remaining -> reverse remaining- [] -> []--strongestHalfPlane- :: NonEmpty ExactPoint- -> ExactPoint- -> ExactPoint- -> (ExactPoint, ExactPoint)-strongestHalfPlane kernelPoints from to =- let direction = exactVectorFromPoints from to- supportPoint =- case kernelPoints of- initial :| remaining ->- List.foldl'- (\selected candidate ->- if directionPointCross direction candidate- < directionPointCross direction selected- then candidate- else selected)- initial- remaining- in ( subtractExactPoints from supportPoint- , subtractExactPoints to supportPoint- )--directionPointCross :: ExactVector -> ExactPoint -> ExactRational-directionPointCross (ExactVector directionX directionY) point =- let (x, y) = exactPointCoordinates point- in directionX * y - directionY * x--clipPolygon- :: [ExactPoint]- -> (ExactPoint, ExactPoint)- -> Either MinkowskiError [ExactPoint]-clipPolygon [] _ = Right []-clipPolygon polygon halfPlane =- concat <$> traverse (clipEdge halfPlane) (cyclePairsList polygon)--clipEdge- :: (ExactPoint, ExactPoint)- -> (ExactPoint, ExactPoint)- -> Either MinkowskiError [ExactPoint]-clipEdge (boundaryFrom, boundaryTo) (from, to) =- case (inside from, inside to) of- (True, True) -> Right [to]- (True, False) -> (: []) <$> supportingLineIntersection from to boundaryFrom boundaryTo- (False, True) -> do- crossing <- supportingLineIntersection from to boundaryFrom boundaryTo- pure [crossing, to]- (False, False) -> Right []- where- inside point = exactOrient2d boundaryFrom boundaryTo point /= LT--supportingLineIntersection- :: ExactPoint- -> ExactPoint- -> ExactPoint- -> ExactPoint- -> Either MinkowskiError ExactPoint-supportingLineIntersection lineFrom lineTo boundaryFrom boundaryTo = do- clippedSegment <- first MinkowskiInvalidSegment (exactSegment lineFrom lineTo)- boundarySegment <- first MinkowskiInvalidSegment (exactSegment boundaryFrom boundaryTo)- first MinkowskiLineIntersection- (exactSupportingLineIntersection clippedSegment boundarySegment)--addExactPoints :: ExactPoint -> ExactPoint -> ExactPoint-addExactPoints left right =- let (leftX, leftY) = exactPointCoordinates left- (rightX, rightY) = exactPointCoordinates right- in exactPoint (leftX + rightX) (leftY + rightY)--subtractExactPoints :: ExactPoint -> ExactPoint -> ExactPoint-subtractExactPoints left right =- let (leftX, leftY) = exactPointCoordinates left- (rightX, rightY) = exactPointCoordinates right- in exactPoint (leftX - rightX) (leftY - rightY)--negateExactPoint :: ExactPoint -> ExactPoint-negateExactPoint point =- let (x, y) = exactPointCoordinates point- in exactPoint (negate x) (negate y)--cyclePairsList :: [value] -> [(value, value)]-cyclePairsList = maybe [] cyclePairs . NonEmpty.nonEmpty
− src-build/Moonlight/Triangulation/Internal/Minkowski/Types.hs
@@ -1,80 +0,0 @@-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE DerivingStrategies #-}---- | Closed vocabulary and invariant carriers for exact polygonal morphology.-module Moonlight.Triangulation.Internal.Minkowski.Types- ( ConvexPolygon (..)- , StructuringElement (..)- , MinkowskiOperation (..)- , MinkowskiError (..)- , MinkowskiReceipt (..)- ) where--import Control.DeepSeq (NFData)-import GHC.Generics (Generic)-import Moonlight.Triangulation.Exact- ( ExactGeometryError- , ExactIntersectionError- , ExactPoint- )-import Moonlight.Triangulation.Handles.HandleDefs (FaceId)-import Moonlight.Triangulation.Internal.ExactRational (ExactArithmeticError)-import Moonlight.Triangulation.Internal.Overlay.Types- ( OverlayCellId- , OverlayCellWitness- , OverlayError- )-import Moonlight.Triangulation.Internal.Region.Types- ( ExactLoop- , RegionPublicationError- , RegionPointLocation- , RegionValidationError- )--newtype ConvexPolygon = ConvexPolygon ExactLoop- deriving stock (Eq, Ord, Show, Generic)- deriving anyclass (NFData)--newtype StructuringElement = StructuringElement ConvexPolygon- deriving stock (Eq, Ord, Show, Generic)- deriving anyclass (NFData)--data MinkowskiOperation- = MinkowskiAddition- | MinkowskiErosion- | MinkowskiOpening- | MinkowskiClosing- deriving stock (Eq, Ord, Show, Generic)- deriving anyclass (NFData)--data MinkowskiError- = MinkowskiInvalidConvexLoop !RegionValidationError- | MinkowskiInvalidSegment !ExactGeometryError- | MinkowskiNonConvexTurn !Int !Ordering- | MinkowskiOriginOutside !RegionPointLocation- | MinkowskiExactArithmetic !ExactArithmeticError- | MinkowskiLineIntersection !ExactIntersectionError- | MinkowskiOverlayFailed !(OverlayError Bool Bool)- | MinkowskiPublicationFailed !RegionPublicationError- | MinkowskiOverlayCellWitness !OverlayCellWitness- | MinkowskiFaceArity !FaceId !Int- | MinkowskiCandidateCellMissing !OverlayCellId- | MinkowskiInclusionAmbiguous !OverlayCellId !ExactPoint- | MinkowskiConvexHullDegenerate ![ExactPoint]- deriving stock (Eq, Show, Generic)- deriving anyclass (NFData)--data MinkowskiReceipt = MinkowskiReceipt- { minkowskiOperation :: !MinkowskiOperation- , minkowskiInputComponents :: !Int- , minkowskiConvexPieces :: !Int- , minkowskiGeneratedPieces :: !Int- , minkowskiGeneratedConvolutionEdges :: !Int- , minkowskiOverlayPasses :: !Int- , minkowskiExactCrossings :: !Int- , minkowskiOutputCells :: !Int- , minkowskiExactCoordinateBitGrowth :: !Int- }- deriving stock (Eq, Ord, Show, Generic)- deriving anyclass (NFData)
− src-build/Moonlight/Triangulation/Internal/Overlay/Arrangement.hs
@@ -1,807 +0,0 @@-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE DerivingStrategies #-}---- | Exact source normalization and binary64 embedding certification. This--- module ends at the arrangement/resident seam: no DCEL face state crosses it.-module Moonlight.Triangulation.Internal.Overlay.Arrangement- ( ExactEdgeKey- , AtomicEdge (..)- , atomicEdgeFrom- , atomicEdgeTo- , OverlayVertexSeed (..)- , ArrangementMetrics (..)- , CertifiedArrangement (..)- , certifyArrangement- , canonicalEdgeKey- , atomicKey- , compareAround- ) where--import Data.Bifunctor (first)-import Data.List (sortBy)-import qualified Data.List as List-import Data.List.NonEmpty (NonEmpty (..))-import qualified Data.List.NonEmpty as NonEmpty-import qualified Data.Map.Strict as Map-import Data.Map.Strict (Map)-import qualified Data.Set as Set-import Data.Set (Set)-import qualified Data.Vector as V-import GHC.Generics (Generic)-import Moonlight.Triangulation.Exact- ( ExactGeometryError- , ExactPoint- , ExactSegment- , SegmentRelation (SegmentsShareEndpoint)- , compareExactVectorAngle- , exactPointCoordinates- , exactPointFromPoint- , exactSegment- , exactSegmentEndpoints- , exactVectorFromPoints- )-import Moonlight.Triangulation.Internal.BoundaryCycle- ( consecutivePairs- , orderedPair- , unorderedPairs- )-import Moonlight.Triangulation.Internal.ExactRational- ( ExactRational- , exactDivide- , exactRationalIsZero- )-import Moonlight.Triangulation.Internal.ExactSegmentEvents- ( ExactSegmentEvent (..)- , ExactSegmentEventPlan- , ExactSweepSegmentId (..)- , exactSegmentEventPlan- , exactSegmentEvents- , exactSegmentPairChecks- , exactSegmentRelationMap- , exactSegmentSplitPoints- , exactSegmentSweepMaximumHeight- )-import Moonlight.Triangulation.Internal.Overlay.Embedding- ( DraftIncidence (..)- , DraftNeighborhood (..)- , DraftReference (..)- , DraftSegmentId (..)- , DraftSourceId (..)- , DraftVertexId (..)- , ExactArrangementDraft (..)- , LocalEmbeddingCertificate (..)- , OverlayEmbeddingObstruction (..)- , certifyLocalEmbedding- )-import Moonlight.Triangulation.Internal.Overlay.Types-import Moonlight.Triangulation.Internal.Types (HasPosition (..), Point)-import Moonlight.Triangulation.Region- ( ExactLoop- , PlanarLayer- , PolygonComponent- , exactLoopPoints- , planarLayerOutsideLabel- , planarLayerRegions- , planarRegionComponents- , polygonHoleLoops- , polygonOuterLoop- )--type ExactEdgeKey = (ExactPoint, ExactPoint)--data SourceBoundary leftLabel rightLabel- = LeftSourceBoundary- !ExactSegment- !leftLabel- !(BoundaryVertexRef 'LeftOverlayOperand)- !(BoundaryVertexRef 'LeftOverlayOperand)- !(BoundaryEdgeRef 'LeftOverlayOperand)- | RightSourceBoundary- !ExactSegment- !rightLabel- !(BoundaryVertexRef 'RightOverlayOperand)- !(BoundaryVertexRef 'RightOverlayOperand)- !(BoundaryEdgeRef 'RightOverlayOperand)--type SourceBoundaryConstructor operand label leftLabel rightLabel =- ExactSegment- -> label- -> BoundaryVertexRef operand- -> BoundaryVertexRef operand- -> BoundaryEdgeRef operand- -> SourceBoundary leftLabel rightLabel--data AtomicContribution leftLabel rightLabel =- AtomicContribution !(SourceBoundary leftLabel rightLabel) !Bool--data AtomicEdge leftLabel rightLabel = AtomicEdge- { atomicEdgeSegment :: !ExactSegment- , atomicEdgeOrigin :: !OverlayEdgeOrigin- , atomicEdgeLeftTransition :: !(Maybe (leftLabel, leftLabel))- , atomicEdgeRightTransition :: !(Maybe (rightLabel, rightLabel))- }--data OriginAccumulation = OriginAccumulation- { accumulatedLeftVertices :: !(Set (BoundaryVertexRef 'LeftOverlayOperand))- , accumulatedRightVertices :: !(Set (BoundaryVertexRef 'RightOverlayOperand))- , accumulatedLeftEdges :: !(Set (BoundaryEdgeRef 'LeftOverlayOperand))- , accumulatedRightEdges :: !(Set (BoundaryEdgeRef 'RightOverlayOperand))- }--data OverlayVertexSeed = OverlayVertexSeed- { seedExactPoint :: !ExactPoint- , seedEmbeddedPoint :: !Point- , seedOrigin :: !OverlayVertexOrigin- }- deriving stock (Eq, Ord, Show, Generic)--instance HasPosition OverlayVertexSeed where- position = seedEmbeddedPoint--data ArrangementMetrics = ArrangementMetrics- { arrangementInputSegments :: !Int- , arrangementRelationEvents :: !Int- , arrangementExactCrossings :: !Int- , arrangementOverlapIntervals :: !Int- , arrangementEmbeddingCandidates :: !Int- , arrangementTotalRelationChecks :: !Int- , arrangementSweepMaximumHeight :: !Int- }--data CertifiedArrangement leftLabel rightLabel = CertifiedArrangement- { certifiedAtomicEdges :: !(V.Vector (AtomicEdge leftLabel rightLabel))- , certifiedVertexSeeds :: !(V.Vector OverlayVertexSeed)- , certifiedConstraints :: !(V.Vector (Int, Int))- , certifiedMetrics :: !ArrangementMetrics- }--certifyArrangement- :: (Ord leftLabel, Ord rightLabel)- => PlanarLayer leftLabel- -> PlanarLayer rightLabel- -> Either- (OverlayError leftLabel rightLabel)- (CertifiedArrangement leftLabel rightLabel)-certifyArrangement leftLayer rightLayer = do- sources <- flattenLayers leftLayer rightLayer- sourcePlan <-- first OverlaySegmentEventsInvalid- (exactSegmentEventPlan (V.map sourceExactSegment sources))- atomicEdges <- normalizeAtomicEdges leftLayer rightLayer sources sourcePlan- let atomicVector = V.fromList atomicEdges- pointIds = exactPointIds atomicVector- origins = vertexOrigins sources atomicVector- atomicRelations = atomicEndpointRelations atomicVector- draft <- exactArrangementDraft sources sourcePlan atomicVector pointIds atomicRelations- localCertificate <- first OverlayEmbeddingRefused (certifyLocalEmbedding draft)- projectedPoints <- projectedExactPoints localCertificate- projectedSegments <- projectedAtomicSegments atomicVector pointIds projectedPoints- projectedPlan <-- first OverlaySegmentEventsInvalid- (exactSegmentEventPlan projectedSegments)- dischargeGlobalRelations atomicRelations projectedPlan- seeds <- overlayVertexSeeds pointIds origins localCertificate- constraints <- atomicConstraints pointIds atomicVector- let events = exactSegmentEvents sourcePlan- pure- CertifiedArrangement- { certifiedAtomicEdges = atomicVector- , certifiedVertexSeeds = seeds- , certifiedConstraints = constraints- , certifiedMetrics =- ArrangementMetrics- { arrangementInputSegments = V.length sources- , arrangementRelationEvents = length events- , arrangementExactCrossings =- length [() | ExactProperCrossing {} <- events]- , arrangementOverlapIntervals =- length [() | ExactCollinearOverlap {} <- events]- , arrangementEmbeddingCandidates = Map.size pointIds- , arrangementTotalRelationChecks =- exactSegmentPairChecks sourcePlan- + exactSegmentPairChecks projectedPlan- , arrangementSweepMaximumHeight =- max- (exactSegmentSweepMaximumHeight sourcePlan)- (exactSegmentSweepMaximumHeight projectedPlan)- }- }-flattenLayers- :: PlanarLayer leftLabel- -> PlanarLayer rightLabel- -> Either (OverlayError leftLabel rightLabel) (V.Vector (SourceBoundary leftLabel rightLabel))-flattenLayers leftLayer rightLayer = do- leftSources <- flattenLayer LeftSourceBoundary leftLayer- rightSources <- flattenLayer RightSourceBoundary rightLayer- pure (V.fromList (leftSources <> rightSources))--flattenLayer- :: SourceBoundaryConstructor operand label leftLabel rightLabel- -> PlanarLayer label- -> Either (OverlayError leftLabel rightLabel) [SourceBoundary leftLabel rightLabel]-flattenLayer makeBoundary layer =- fmap concat- ( traverse- (\(componentIndex, (label, component)) ->- flattenComponent makeBoundary componentIndex label component)- (zip [0 ..] (labelledComponents layer))- )-{-# INLINE flattenLayer #-}--labelledComponents :: PlanarLayer label -> [(label, PolygonComponent)]-labelledComponents layer =- [ (label, component)- | (label, region) <- Map.toAscList (planarLayerRegions layer)- , component <- planarRegionComponents region- ]--flattenComponent- :: SourceBoundaryConstructor operand label leftLabel rightLabel- -> Int- -> label- -> PolygonComponent- -> Either (OverlayError leftLabel rightLabel) [SourceBoundary leftLabel rightLabel]-flattenComponent makeBoundary componentIndex label component = do- outer <-- flattenLoop- makeBoundary- componentIndex- BoundaryOuterLoop- label- (polygonOuterLoop component)- holes <-- fmap concat- ( traverse- (\(holeIndex, loop) ->- flattenLoop- makeBoundary- componentIndex- (BoundaryHoleLoop holeIndex)- label- loop)- (zip [0 ..] (polygonHoleLoops component))- )- pure (outer <> holes)-{-# INLINE flattenComponent #-}--flattenLoop- :: SourceBoundaryConstructor operand label leftLabel rightLabel- -> Int- -> BoundaryLoopRef- -> label- -> ExactLoop- -> Either (OverlayError leftLabel rightLabel) [SourceBoundary leftLabel rightLabel]-flattenLoop makeBoundary componentIndex loopRef label loop =- traverse constructSource (indexedCycle (exactLoopPoints loop))- where- constructSource (edgeIndex, fromIndex, from, toIndex, to) = do- segment <- first (sourceGeometryError from) (exactSegment from to)- pure- ( makeBoundary- segment- label- (BoundaryVertexRef componentIndex loopRef fromIndex)- (BoundaryVertexRef componentIndex loopRef toIndex)- (BoundaryEdgeRef componentIndex loopRef edgeIndex)- )-{-# INLINE flattenLoop #-}--sourceGeometryError- :: ExactPoint- -> ExactGeometryError- -> OverlayError leftLabel rightLabel-sourceGeometryError point _ = OverlayArrangementInvalid (OverlayRotationDegenerate point)--indexedCycle :: NonEmpty value -> [(Int, Int, value, Int, value)]-indexedCycle (firstValue :| remaining) =- let values = firstValue : remaining- count = length values- in [ (index, index, from, (index + 1) `mod` count, to)- | (index, (from, to)) <- zip [0 ..] (zip values (remaining <> [firstValue]))- ]--sourceExactSegment :: SourceBoundary leftLabel rightLabel -> ExactSegment-sourceExactSegment (LeftSourceBoundary segment _ _ _ _) = segment-sourceExactSegment (RightSourceBoundary segment _ _ _ _) = segment--normalizeAtomicEdges- :: (Ord leftLabel, Ord rightLabel)- => PlanarLayer leftLabel- -> PlanarLayer rightLabel- -> V.Vector (SourceBoundary leftLabel rightLabel)- -> ExactSegmentEventPlan- -> Either (OverlayError leftLabel rightLabel) [AtomicEdge leftLabel rightLabel]-normalizeAtomicEdges leftLayer rightLayer sources plan =- fmap concat- ( traverse- (resolveAtomicContributions leftOutside rightOutside)- (Map.toAscList grouped)- )- where- leftOutside = planarLayerOutsideLabel leftLayer- rightOutside = planarLayerOutsideLabel rightLayer- grouped =- V.ifoldl'- (\groups sourceIndex source ->- List.foldl'- (insertAtomic source)- groups- (consecutivePairs (exactSegmentSplitPoints plan (ExactSweepSegmentId sourceIndex))))- Map.empty- sources- insertAtomic- :: SourceBoundary leftLabel' rightLabel'- -> Map ExactEdgeKey [AtomicContribution leftLabel' rightLabel']- -> (ExactPoint, ExactPoint)- -> Map ExactEdgeKey [AtomicContribution leftLabel' rightLabel']- insertAtomic source groups (from, to)- | from == to = groups- | otherwise =- let key@(canonicalFrom, _) = canonicalEdgeKey from to- contribution = AtomicContribution source (from == canonicalFrom)- in Map.insertWith (<>) key [contribution] groups--resolveAtomicContributions- :: (Ord leftLabel, Ord rightLabel)- => leftLabel- -> rightLabel- -> (ExactEdgeKey, [AtomicContribution leftLabel rightLabel])- -> Either (OverlayError leftLabel rightLabel) [AtomicEdge leftLabel rightLabel]-resolveAtomicContributions leftOutside rightOutside ((from, to), contributions) = do- let- ( leftLabelsOnLeft- , leftLabelsOnRight- , rightLabelsOnLeft- , rightLabelsOnRight- , leftSources- , rightSources- ) =- List.foldl'- collectContribution- (Set.empty, Set.empty, Set.empty, Set.empty, Set.empty, Set.empty)- contributions- origin =- OverlayEdgeOrigin- (Set.toAscList leftSources)- (Set.toAscList rightSources)- leftTransition <-- resolveTransition- OverlayLeftSourceSideConflict- origin- leftOutside- leftLabelsOnLeft- leftLabelsOnRight- rightTransition <-- resolveTransition- OverlayRightSourceSideConflict- origin- rightOutside- rightLabelsOnLeft- rightLabelsOnRight- if transitionIsIdentity leftTransition && transitionIsIdentity rightTransition- then Right []- else do- segment <- first (sourceGeometryError from) (exactSegment from to)- Right- [ AtomicEdge- { atomicEdgeSegment = segment- , atomicEdgeOrigin = origin- , atomicEdgeLeftTransition = leftTransition- , atomicEdgeRightTransition = rightTransition- }- ]- where- collectContribution- :: (Ord leftLabel', Ord rightLabel')- => ( Set leftLabel'- , Set leftLabel'- , Set rightLabel'- , Set rightLabel'- , Set (BoundaryEdgeRef 'LeftOverlayOperand)- , Set (BoundaryEdgeRef 'RightOverlayOperand)- )- -> AtomicContribution leftLabel' rightLabel'- -> ( Set leftLabel'- , Set leftLabel'- , Set rightLabel'- , Set rightLabel'- , Set (BoundaryEdgeRef 'LeftOverlayOperand)- , Set (BoundaryEdgeRef 'RightOverlayOperand)- )- collectContribution- ( !leftLeft- , !leftRight- , !rightLeft- , !rightRight- , !leftSources- , !rightSources- )- contribution =- case contribution of- AtomicContribution (LeftSourceBoundary _ label _ _ reference) follows ->- ( if follows then Set.insert label leftLeft else leftLeft- , if follows then leftRight else Set.insert label leftRight- , rightLeft- , rightRight- , Set.insert reference leftSources- , rightSources- )- AtomicContribution (RightSourceBoundary _ label _ _ reference) follows ->- ( leftLeft- , leftRight- , if follows then Set.insert label rightLeft else rightLeft- , if follows then rightRight else Set.insert label rightRight- , leftSources- , Set.insert reference rightSources- )--resolveTransition- :: (OverlayEdgeOrigin -> NonEmpty label -> OverlayArrangementObstruction leftLabel rightLabel)- -> OverlayEdgeOrigin- -> label- -> Set label- -> Set label- -> Either (OverlayError leftLabel rightLabel) (Maybe (label, label))-resolveTransition sideConflict origin outside labelsOnLeft labelsOnRight- | Set.null labelsOnLeft && Set.null labelsOnRight = Right Nothing- | otherwise =- Just- <$> ((,)- <$> resolveSide sideConflict origin outside labelsOnLeft- <*> resolveSide sideConflict origin outside labelsOnRight)-{-# INLINE resolveTransition #-}--transitionIsIdentity :: Eq label => Maybe (label, label) -> Bool-transitionIsIdentity Nothing = True-transitionIsIdentity (Just (leftLabel, rightLabel)) = leftLabel == rightLabel--resolveSide- :: (OverlayEdgeOrigin -> NonEmpty label -> OverlayArrangementObstruction leftLabel rightLabel)- -> OverlayEdgeOrigin- -> label- -> Set label- -> Either (OverlayError leftLabel rightLabel) label-resolveSide sideConflict origin outside labels =- case Set.toAscList labels of- [] -> Right outside- [label] -> Right label- firstLabel : remaining ->- Left- ( OverlayArrangementInvalid- (sideConflict origin (firstLabel :| remaining))- )-{-# INLINE resolveSide #-}--exactPointIds- :: V.Vector (AtomicEdge leftLabel rightLabel)- -> Map ExactPoint DraftVertexId-exactPointIds edges =- Map.fromAscList- ( zip- (Set.toAscList (V.foldl' collect Set.empty edges))- (map DraftVertexId [0 ..])- )- where- collect- :: Set ExactPoint- -> AtomicEdge leftLabel rightLabel- -> Set ExactPoint- collect points edge = Set.insert (atomicEdgeFrom edge) (Set.insert (atomicEdgeTo edge) points)--vertexOrigins- :: V.Vector (SourceBoundary leftLabel rightLabel)- -> V.Vector (AtomicEdge leftLabel rightLabel)- -> Map ExactPoint OverlayVertexOrigin-vertexOrigins sources atomicEdges =- Map.map finalizeOrigin- ( V.foldl'- addAtomicOrigin- (V.foldl' addSourceEndpoints Map.empty sources)- atomicEdges- )--emptyOrigin :: OriginAccumulation-emptyOrigin = OriginAccumulation Set.empty Set.empty Set.empty Set.empty--mergeOrigin :: OriginAccumulation -> OriginAccumulation -> OriginAccumulation-mergeOrigin left right =- OriginAccumulation- { accumulatedLeftVertices = accumulatedLeftVertices left <> accumulatedLeftVertices right- , accumulatedRightVertices = accumulatedRightVertices left <> accumulatedRightVertices right- , accumulatedLeftEdges = accumulatedLeftEdges left <> accumulatedLeftEdges right- , accumulatedRightEdges = accumulatedRightEdges left <> accumulatedRightEdges right- }--addSourceEndpoints- :: Map ExactPoint OriginAccumulation- -> SourceBoundary leftLabel rightLabel- -> Map ExactPoint OriginAccumulation-addSourceEndpoints origins source =- case source of- LeftSourceBoundary segment _ fromReference toReference _ ->- let (from, to) = exactSegmentEndpoints segment- in insertOrigin to (emptyOrigin{accumulatedLeftVertices = Set.singleton toReference})- (insertOrigin from (emptyOrigin{accumulatedLeftVertices = Set.singleton fromReference}) origins)- RightSourceBoundary segment _ fromReference toReference _ ->- let (from, to) = exactSegmentEndpoints segment- in insertOrigin to (emptyOrigin{accumulatedRightVertices = Set.singleton toReference})- (insertOrigin from (emptyOrigin{accumulatedRightVertices = Set.singleton fromReference}) origins)--addAtomicOrigin- :: Map ExactPoint OriginAccumulation- -> AtomicEdge leftLabel rightLabel- -> Map ExactPoint OriginAccumulation-addAtomicOrigin origins edge =- let origin = atomicEdgeOrigin edge- accumulation =- emptyOrigin- { accumulatedLeftEdges = Set.fromList (overlayEdgeLeftSources origin)- , accumulatedRightEdges = Set.fromList (overlayEdgeRightSources origin)- }- in insertOrigin (atomicEdgeTo edge) accumulation- (insertOrigin (atomicEdgeFrom edge) accumulation origins)--insertOrigin- :: ExactPoint- -> OriginAccumulation- -> Map ExactPoint OriginAccumulation- -> Map ExactPoint OriginAccumulation-insertOrigin = Map.insertWith mergeOrigin--finalizeOrigin :: OriginAccumulation -> OverlayVertexOrigin-finalizeOrigin accumulated =- OverlayVertexOrigin- { overlayOriginLeftVertices = Set.toAscList (accumulatedLeftVertices accumulated)- , overlayOriginRightVertices = Set.toAscList (accumulatedRightVertices accumulated)- , overlayOriginLeftEdges = Set.toAscList (accumulatedLeftEdges accumulated)- , overlayOriginRightEdges = Set.toAscList (accumulatedRightEdges accumulated)- }--exactArrangementDraft- :: V.Vector (SourceBoundary leftLabel rightLabel)- -> ExactSegmentEventPlan- -> V.Vector (AtomicEdge leftLabel rightLabel)- -> Map ExactPoint DraftVertexId- -> Map (ExactSweepSegmentId, ExactSweepSegmentId) SegmentRelation- -> Either (OverlayError leftLabel rightLabel) ExactArrangementDraft-exactArrangementDraft sources sourcePlan atomicEdges pointIds atomicRelations = do- draftSegmentsMap <-- Map.fromList- <$> traverse- (\(segmentIndex, edge) -> do- from <- requireDraftVertex pointIds (atomicEdgeFrom edge)- to <- requireDraftVertex pointIds (atomicEdgeTo edge)- pure (DraftSegmentId segmentIndex, (from, to)))- (V.toList (V.indexed atomicEdges))- memberships <-- Map.fromList- <$> traverse- (\(sourceIndex, source) -> do- values <-- traverse- (\point -> do- parameter <- exactSourceParameter source point- vertex <- requireDraftVertex pointIds point- pure (parameter, vertex))- (exactSegmentSplitPoints sourcePlan (ExactSweepSegmentId sourceIndex))- pure (DraftSourceId sourceIndex, values))- (V.toList (V.indexed sources))- neighborhoods <- buildDraftNeighborhoods atomicEdges pointIds- let incidences =- [ DraftIncidence- (DraftSegmentId leftIndex)- (DraftSegmentId rightIndex)- relation- | ((ExactSweepSegmentId leftIndex, ExactSweepSegmentId rightIndex), relation) <-- Map.toAscList atomicRelations- ]- pure- ExactArrangementDraft- { draftVertices = Map.fromList [(vertexId, point) | (point, vertexId) <- Map.toAscList pointIds]- , draftSegments = draftSegmentsMap- , draftSourceMemberships = memberships- , draftIncidences = incidences- , draftNeighborhoods = neighborhoods- }--requireDraftVertex- :: Map ExactPoint DraftVertexId- -> ExactPoint- -> Either (OverlayError leftLabel rightLabel) DraftVertexId-requireDraftVertex pointIds point =- case Map.lookup point pointIds of- Just vertex -> Right vertex- Nothing -> Left (OverlayProvenanceIncomplete (OverlayExactVertexMissing point))--exactSourceParameter- :: SourceBoundary leftLabel rightLabel- -> ExactPoint- -> Either (OverlayError leftLabel rightLabel) ExactRational-exactSourceParameter source point =- let (from, to) = exactSegmentEndpoints (sourceExactSegment source)- (fromX, fromY) = exactPointCoordinates from- (toX, toY) = exactPointCoordinates to- (pointX, pointY) = exactPointCoordinates point- (numerator, denominator) =- if exactRationalIsZero (toX - fromX)- then (pointY - fromY, toY - fromY)- else (pointX - fromX, toX - fromX)- in first OverlayExactArithmetic (exactDivide numerator denominator)--buildDraftNeighborhoods- :: V.Vector (AtomicEdge leftLabel rightLabel)- -> Map ExactPoint DraftVertexId- -> Either (OverlayError leftLabel rightLabel) [DraftNeighborhood]-buildDraftNeighborhoods edges pointIds =- traverse neighborhood (Map.toAscList pointIds)- where- neighborhood (centerPoint, centerId) = do- let neighbors = Map.findWithDefault Set.empty centerPoint adjacency- orderedPoints <-- case NonEmpty.nonEmpty (sortBy (compareAround centerPoint) (Set.toList neighbors)) of- Just points -> Right points- Nothing -> Left (OverlayArrangementInvalid (OverlayRotationDegenerate centerPoint))- orderedIds <- traverse (requireDraftVertex pointIds) orderedPoints- pure (DraftNeighborhood centerId orderedIds)- adjacency =- V.foldl'- (\graph edge ->- Map.insertWith Set.union (atomicEdgeTo edge) (Set.singleton (atomicEdgeFrom edge))- (Map.insertWith Set.union (atomicEdgeFrom edge) (Set.singleton (atomicEdgeTo edge)) graph))- Map.empty- edges--compareAround :: ExactPoint -> ExactPoint -> ExactPoint -> Ordering-compareAround center left right =- case- compareExactVectorAngle- (exactVectorFromPoints center left)- (exactVectorFromPoints center right) of- EQ -> compare left right- ordering -> ordering---- | Normalization splits every source at every exact event and coalesces--- duplicate intervals. Distinct atomics can therefore meet only at a stored--- endpoint; their entire relation section is the endpoint-incidence index.-atomicEndpointRelations- :: V.Vector (AtomicEdge leftLabel rightLabel)- -> Map (ExactSweepSegmentId, ExactSweepSegmentId) SegmentRelation-atomicEndpointRelations edges =- Map.fromList- [ ( orderedPair- (ExactSweepSegmentId leftIndex)- (ExactSweepSegmentId rightIndex)- , SegmentsShareEndpoint- )- | incident <- Map.elems incidenceByPoint- , (leftIndex, rightIndex) <- unorderedPairs (Set.toAscList incident)- ]- where- incidenceByPoint =- V.ifoldl'- (\incidence index edge ->- Map.insertWith Set.union (atomicEdgeTo edge) (Set.singleton index)- ( Map.insertWith Set.union- (atomicEdgeFrom edge)- (Set.singleton index)- incidence- ))- Map.empty- edges-projectedExactPoints- :: LocalEmbeddingCertificate- -> Either (OverlayError leftLabel rightLabel) (Map DraftVertexId ExactPoint)-projectedExactPoints certificate =- Map.traverseWithKey- (\vertex point ->- first- (\projectionError ->- OverlayEmbeddingRefused- (VertexProjectionRefused vertex projectionError :| []))- (exactPointFromPoint point))- (certificateRoundedVertices certificate)--projectedAtomicSegments- :: V.Vector (AtomicEdge leftLabel rightLabel)- -> Map ExactPoint DraftVertexId- -> Map DraftVertexId ExactPoint- -> Either (OverlayError leftLabel rightLabel) (V.Vector ExactSegment)-projectedAtomicSegments edges pointIds projected =- V.imapM project edges- where- project segmentIndex edge = do- fromId <- requireDraftVertex pointIds (atomicEdgeFrom edge)- toId <- requireDraftVertex pointIds (atomicEdgeTo edge)- from <- requireProjected fromId- to <- requireProjected toId- first- (\_ ->- OverlayEmbeddingRefused- ( ProjectedSegmentCollapsed- (DraftSegmentId segmentIndex)- fromId- toId- :| []- ))- (exactSegment from to)- requireProjected vertex =- case Map.lookup vertex projected of- Just point -> Right point- Nothing ->- Left- ( OverlayEmbeddingRefused- (DraftReferenceMissing (DraftVertexReference vertex) :| [])- )--dischargeGlobalRelations- :: Map (ExactSweepSegmentId, ExactSweepSegmentId) SegmentRelation- -> ExactSegmentEventPlan- -> Either (OverlayError leftLabel rightLabel) ()-dischargeGlobalRelations exactRelations projectedPlan =- case NonEmpty.nonEmpty obstructions of- Nothing -> Right ()- Just failures -> Left (OverlayEmbeddingRefused failures)- where- projectedRelations = exactSegmentRelationMap projectedPlan- keys = Set.toAscList (Map.keysSet exactRelations <> Map.keysSet projectedRelations)- obstructions = concatMap compareRelation keys- compareRelation key@(ExactSweepSegmentId leftId, ExactSweepSegmentId rightId) =- case (Map.lookup key exactRelations, Map.lookup key projectedRelations) of- (Nothing, Just projected) ->- [GlobalRelationAdded (DraftSegmentId leftId) (DraftSegmentId rightId) projected]- (Just exact, Nothing) ->- [GlobalRelationRemoved (DraftSegmentId leftId) (DraftSegmentId rightId) exact]- (Just exact, Just projected)- | exact /= projected ->- [GlobalRelationChanged (DraftSegmentId leftId) (DraftSegmentId rightId) exact projected]- _ -> []--overlayVertexSeeds- :: Map ExactPoint DraftVertexId- -> Map ExactPoint OverlayVertexOrigin- -> LocalEmbeddingCertificate- -> Either (OverlayError leftLabel rightLabel) (V.Vector OverlayVertexSeed)-overlayVertexSeeds pointIds origins certificate =- V.fromList- <$> traverse- (\(point, vertexId) -> do- embedded <-- case Map.lookup vertexId (certificateRoundedVertices certificate) of- Just value -> Right value- Nothing ->- Left- ( OverlayEmbeddingRefused- (DraftReferenceMissing (DraftVertexReference vertexId) :| [])- )- origin <-- case Map.lookup point origins of- Just value -> Right value- Nothing -> Left (OverlayProvenanceIncomplete (OverlayExactVertexMissing point))- pure (OverlayVertexSeed point embedded origin))- (Map.toAscList pointIds)--atomicConstraints- :: Map ExactPoint DraftVertexId- -> V.Vector (AtomicEdge leftLabel rightLabel)- -> Either (OverlayError leftLabel rightLabel) (V.Vector (Int, Int))-atomicConstraints pointIds =- V.mapM- (\edge -> do- DraftVertexId from <- requireDraftVertex pointIds (atomicEdgeFrom edge)- DraftVertexId to <- requireDraftVertex pointIds (atomicEdgeTo edge)- pure (from, to))---canonicalEdgeKey :: ExactPoint -> ExactPoint -> ExactEdgeKey-canonicalEdgeKey = orderedPair--atomicEdgeFrom :: AtomicEdge leftLabel rightLabel -> ExactPoint-atomicEdgeFrom = fst . exactSegmentEndpoints . atomicEdgeSegment--atomicEdgeTo :: AtomicEdge leftLabel rightLabel -> ExactPoint-atomicEdgeTo = snd . exactSegmentEndpoints . atomicEdgeSegment--atomicKey :: AtomicEdge leftLabel rightLabel -> ExactEdgeKey-atomicKey edge = (atomicEdgeFrom edge, atomicEdgeTo edge)
− src-build/Moonlight/Triangulation/Internal/Overlay/Resident.hs
@@ -1,869 +0,0 @@-{-# LANGUAGE DataKinds #-}---- | Resident DCEL descent and exact-cell gluing. The certified arrangement is--- authoritative at this seam; diagonal schedules may change only its resident--- triangulation, never its exact cell descriptors.-module Moonlight.Triangulation.Internal.Overlay.Resident- ( OverlayDiagonalSchedule (..)- , residentOverlay- , faceLabels- , regionFaceLabels- , vertexSupport- , edgeSupport- ) where--import Control.Monad (foldM)-import Data.Bifunctor (first)-import Data.Foldable (traverse_)-import Data.List (partition, sort, sortBy)-import qualified Data.List as List-import Data.List.NonEmpty (NonEmpty (..))-import qualified Data.List.NonEmpty as NonEmpty-import qualified Data.Map.Strict as Map-import Data.Map.Strict (Map)-import Data.Maybe (listToMaybe, mapMaybe)-import qualified Data.Sequence as Seq-import qualified Data.Set as Set-import Data.Set (Set)-import qualified Data.Vector as V-import qualified Moonlight.Triangulation.Dcel as Dcel-import Moonlight.Triangulation.Dcel- ( faceData- , faceDirectedEdges- , imapUndirectedEdges- , incidentFace- , isConstraintEdge- , numInnerFaces- , numVertices- , outerFace- , undirectedEndpoints- , vertexData- , vertexOutgoingEdges- )-import Moonlight.Triangulation.Exact (ExactPoint, exactOrient2d)-import Moonlight.Triangulation.FloodFillIterator- ( FaceComponent- , componentBoundary- , faceComponentFaces- , faceComponents- )-import Moonlight.Triangulation.Handles.HandleDefs- ( DirectedEdgeId- , FaceId- , UndirectedEdgeId- , VertexId- , asUndirected- , directedPair- , reverseEdge- )-import Moonlight.Triangulation.Handles.Iterators.FixedIterators- ( allFaces- , innerFaces- , undirectedEdges- )-import Moonlight.Triangulation.Internal.BoxedPaged (boxedFromVector)-import Moonlight.Triangulation.Internal.BoundaryCycle (consecutivePairs)-import Moonlight.Triangulation.Internal.Canonical (canonicalize)-import Moonlight.Triangulation.Internal.Cdt.Build (constrainedDelaunay)-import Moonlight.Triangulation.Internal.Cdt.Types (CdtError (..))-import Moonlight.Triangulation.Internal.Overlay.Arrangement- ( ArrangementMetrics (..)- , AtomicEdge (..)- , atomicEdgeFrom- , atomicEdgeTo- , CertifiedArrangement (..)- , ExactEdgeKey- , OverlayVertexSeed (..)- , atomicKey- , canonicalEdgeKey- , compareAround- )-import Moonlight.Triangulation.Internal.Overlay.Types-import Moonlight.Triangulation.Internal.Region.Publication- ( polygonComponentFromBoundaryCoordinates- )-import Moonlight.Triangulation.Internal.Region.Types- ( ExactLoop- , PolygonComponent- , RegionPublicationError (..)- )-import Moonlight.Triangulation.Internal.Representation- ( BuildResult (..)- , Triangulation (..)- )-import Moonlight.Triangulation.Internal.Types- ( ConstraintMode (Constrained)- , ElementDefaults (..)- )-import Moonlight.Triangulation.Region (exactLoop)--data OverlayDiagonalSchedule- = CanonicalOverlayDiagonals- | FlipFirstAdmissibleDiagonal- deriving stock (Eq, Ord, Show)--data ComponentDraft leftLabel rightLabel = ComponentDraft- { componentDraftLabels :: !(leftLabel, rightLabel)- , componentDraftFaces :: !FaceComponent- , componentDraftPolygon :: !PolygonComponent- , componentDraftTouchesOuter :: !Bool- }--residentOverlay- :: (Ord leftLabel, Ord rightLabel)- => OverlayDiagonalSchedule- -> (leftLabel, rightLabel)- -> CertifiedArrangement leftLabel rightLabel- -> Either- (OverlayError leftLabel rightLabel)- (OverlayResult leftLabel rightLabel)-residentOverlay diagonalSchedule outsidePair certified = do- let atomicVector = certifiedAtomicEdges certified- seeds = certifiedVertexSeeds certified- constraints = certifiedConstraints certified- metrics = certifiedMetrics certified- defaults = ElementDefaults () () ()- built <- first OverlayBuildFailed (constrainedDelaunay defaults seeds constraints)- let resident = buildTriangulation built- if numVertices resident == V.length seeds- then Right ()- else- Left- ( OverlayProvenanceIncomplete- (OverlayEmbeddedVertexCountMismatch (V.length seeds) (numVertices resident))- )- canonicalResident <-- first (OverlayBuildFailed . CdtBuildError) (canonicalize resident)- let atomicByKey =- Map.fromList- [ (atomicKey edge, edge)- | edge <- V.toList atomicVector- ]- validateAtomicConstraints canonicalResident atomicByKey- scheduledResident <-- applyDiagonalSchedule- diagonalSchedule- defaults- seeds- constraints- canonicalResident- labelledFaces <- labelResidentFaces outsidePair scheduledResident atomicByKey- componentDrafts <- residentComponentDrafts scheduledResident labelledFaces- let (unboundedDrafts, boundedDrafts) =- partitionDrafts outsidePair componentDrafts- numberedBounded <- numberBoundedComponents boundedDrafts- let cellIdByFace =- Map.fromList- ( [ (face, OverlayCellId 0)- | draftComponent <- unboundedDrafts- , face <- faceComponentFaces (componentDraftFaces draftComponent)- ]- <> [ (face, cellId)- | (cellId, draftComponent) <- numberedBounded- , face <- faceComponentFaces (componentDraftFaces draftComponent)- ]- )- unboundedLoops <-- unboundedCellBoundaryLoops scheduledResident atomicByKey cellIdByFace- let cells =- V.fromList- ( OverlayCell- { overlayCellLeft = fst outsidePair- , overlayCellRight = snd outsidePair- , overlayCellGeometry = UnboundedOverlayCell unboundedLoops- }- : [ OverlayCell- { overlayCellLeft = fst (componentDraftLabels draftComponent)- , overlayCellRight = snd (componentDraftLabels draftComponent)- , overlayCellGeometry =- BoundedOverlayCell (componentDraftPolygon draftComponent)- }- | (_, draftComponent) <- numberedBounded- ]- )- withFaces <- attachFaceCells cellIdByFace scheduledResident- let withVertices = attachOverlayVertices withFaces- withEdges =- imapUndirectedEdges- OverlayDiagonal- (\edge _ ->- case Map.lookup (residentEdgeKey withVertices edge) atomicByKey of- Nothing -> OverlayDiagonal- Just atomic -> OverlayBoundary (atomicEdgeOrigin atomic))- withVertices- receipt =- OverlayReceipt- { overlayInputSegments = arrangementInputSegments metrics- , overlayRelationEvents = arrangementRelationEvents metrics- , overlayExactCrossings = arrangementExactCrossings metrics- , overlayOverlapIntervals = arrangementOverlapIntervals metrics- , overlayAtomicEdges = V.length atomicVector- , overlayOutputVertices = numVertices withEdges- , overlayArrangementCells = V.length cells- , overlayResidentFaces = numInnerFaces withEdges- , overlayEmbeddingCandidates = arrangementEmbeddingCandidates metrics- , overlayTotalRelationChecks = arrangementTotalRelationChecks metrics- , overlaySweepMaximumHeight = arrangementSweepMaximumHeight metrics- }- pure- OverlayResult- { overlayResultTriangulation = withEdges- , overlayResultCells = cells- , overlayResultOutsideLabels = outsidePair- , overlayResultReceipt = receipt- }--applyDiagonalSchedule- :: OverlayDiagonalSchedule- -> ElementDefaults () () ()- -> V.Vector OverlayVertexSeed- -> V.Vector (Int, Int)- -> Triangulation 'Constrained OverlayVertexSeed () () ()- -> Either- (OverlayError leftLabel rightLabel)- (Triangulation 'Constrained OverlayVertexSeed () () ())-applyDiagonalSchedule CanonicalOverlayDiagonals _ _ _ triangulation =- Right triangulation-applyDiagonalSchedule- FlipFirstAdmissibleDiagonal- defaults- seeds- constraints- triangulation =- case firstAlternativeDiagonal triangulation of- Nothing -> Right triangulation- Just (from, to) -> do- let indexByPoint =- Map.fromList- [ (seedExactPoint seed, index)- | (index, seed) <- V.toList (V.indexed seeds)- ]- fromIndex <- requireSeedIndex indexByPoint from- toIndex <- requireSeedIndex indexByPoint to- rebuilt <-- first OverlayBuildFailed- (constrainedDelaunay defaults seeds (V.snoc constraints (fromIndex, toIndex)))- first (OverlayBuildFailed . CdtBuildError)- (canonicalize (buildTriangulation rebuilt))--requireSeedIndex- :: Map ExactPoint Int- -> ExactPoint- -> Either (OverlayError leftLabel rightLabel) Int-requireSeedIndex indexByPoint point =- case Map.lookup point indexByPoint of- Just index -> Right index- Nothing -> Left (OverlayProvenanceIncomplete (OverlayExactVertexMissing point))--firstAlternativeDiagonal- :: Triangulation 'Constrained OverlayVertexSeed () () ()- -> Maybe ExactEdgeKey-firstAlternativeDiagonal triangulation =- listToMaybe- (mapMaybe (alternativeDiagonal triangulation) (undirectedEdges triangulation))--alternativeDiagonal- :: Triangulation 'Constrained OverlayVertexSeed () () ()- -> UndirectedEdgeId- -> Maybe ExactEdgeKey-alternativeDiagonal triangulation edge- | isConstraintEdge triangulation edge = Nothing- | leftFace == outerFace || rightFace == outerFace = Nothing- | exactOrient2d c d b == GT && exactOrient2d d c a == GT =- Just (canonicalEdgeKey c d)- | otherwise = Nothing- where- (forward, backward) = directedPair edge- leftFace = incidentFace triangulation forward- rightFace = incidentFace triangulation backward- a = seedExactPoint (vertexData triangulation (Dcel.origin triangulation forward))- b = seedExactPoint (vertexData triangulation (Dcel.origin triangulation backward))- c =- seedExactPoint- (vertexData triangulation (Dcel.origin triangulation (Dcel.previous triangulation forward)))- d =- seedExactPoint- (vertexData triangulation (Dcel.origin triangulation (Dcel.previous triangulation backward)))-labelResidentFaces- :: (Ord leftLabel, Ord rightLabel)- => (leftLabel, rightLabel)- -> Triangulation 'Constrained OverlayVertexSeed () () ()- -> Map ExactEdgeKey (AtomicEdge leftLabel rightLabel)- -> Either- (OverlayError leftLabel rightLabel)- (Map FaceId (leftLabel, rightLabel))-labelResidentFaces outsideLabels triangulation atomicByKey = do- labelled <-- descendFaceTransitions- triangulation- atomicByKey- (Map.singleton outerFace outsideLabels)- (Seq.singleton outerFace)- case- [ face- | face <- innerFaces triangulation- , Map.notMember face labelled- ] of- missing : _ ->- Left (OverlayProvenanceIncomplete (OverlayResidentFaceUnassigned missing))- [] -> Right (Map.delete outerFace labelled)--seedDirectedEndpoints- :: Triangulation mode OverlayVertexSeed directed undirected face- -> DirectedEdgeId- -> ExactEdgeKey-seedDirectedEndpoints triangulation edge =- ( seedExactPoint (vertexData triangulation (Dcel.origin triangulation edge))- , seedExactPoint (vertexData triangulation (Dcel.destination triangulation edge))- )--swapTransition :: (label, label) -> (label, label)-swapTransition (fromLabel, toLabel) = (toLabel, fromLabel)--descendFaceTransitions- :: (Eq leftLabel, Eq rightLabel)- => Triangulation 'Constrained OverlayVertexSeed () () ()- -> Map ExactEdgeKey (AtomicEdge leftLabel rightLabel)- -> Map FaceId (leftLabel, rightLabel)- -> Seq.Seq FaceId- -> Either- (OverlayError leftLabel rightLabel)- (Map FaceId (leftLabel, rightLabel))-descendFaceTransitions triangulation atomicByKey labelled queued =- case Seq.viewl queued of- Seq.EmptyL -> Right labelled- face Seq.:< remaining ->- case Map.lookup face labelled of- Nothing ->- Left (OverlayProvenanceIncomplete (OverlayResidentFaceUnassigned face))- Just current -> do- (nextLabels, nextQueue) <-- foldM- (descendFaceTransition triangulation atomicByKey face current)- (labelled, remaining)- (faceDirectedEdges triangulation face)- descendFaceTransitions triangulation atomicByKey nextLabels nextQueue--descendFaceTransition- :: (Eq leftLabel, Eq rightLabel)- => Triangulation 'Constrained OverlayVertexSeed () () ()- -> Map ExactEdgeKey (AtomicEdge leftLabel rightLabel)- -> FaceId- -> (leftLabel, rightLabel)- -> (Map FaceId (leftLabel, rightLabel), Seq.Seq FaceId)- -> DirectedEdgeId- -> Either- (OverlayError leftLabel rightLabel)- (Map FaceId (leftLabel, rightLabel), Seq.Seq FaceId)-descendFaceTransition triangulation atomicByKey source current (labelled, queued) directed = do- let edge = asUndirected directed- destinationFace = incidentFace triangulation (reverseEdge directed)- (leftTransition, rightTransition) <-- case Map.lookup (residentSeedEdgeKey triangulation edge) atomicByKey of- Nothing -> Right (Nothing, Nothing)- Just atomic -> orientedTransitions edge directed atomic- let- expected =- ( maybe (fst current) fst leftTransition- , maybe (snd current) fst rightTransition- )- derived =- ( maybe (fst current) snd leftTransition- , maybe (snd current) snd rightTransition- )- if current == expected- then- case Map.lookup destinationFace labelled of- Nothing ->- Right- ( Map.insert destinationFace derived labelled- , queued Seq.|> destinationFace- )- Just existing- | existing == derived -> Right (labelled, queued)- | otherwise ->- Left- ( OverlayArrangementInvalid- ( OverlayResidentFaceLabelConflict- destinationFace- edge- existing- derived- )- )- else- Left- ( OverlayArrangementInvalid- (OverlayTransitionSourceMismatch source edge current expected)- )- where- orientedTransitions edge candidateDirection atomic- | directedEndpoints == (atomicEdgeFrom atomic, atomicEdgeTo atomic) =- Right- ( atomicEdgeLeftTransition atomic- , atomicEdgeRightTransition atomic- )- | directedEndpoints == (atomicEdgeTo atomic, atomicEdgeFrom atomic) =- Right- ( swapTransition <$> atomicEdgeLeftTransition atomic- , swapTransition <$> atomicEdgeRightTransition atomic- )- | otherwise =- Left- ( OverlayProvenanceIncomplete- ( OverlayAtomicConstraintOrientationMismatch- edge- (atomicEdgeFrom atomic)- (atomicEdgeTo atomic)- )- )- where- directedEndpoints = seedDirectedEndpoints triangulation candidateDirection--residentComponentDrafts- :: (Ord leftLabel, Ord rightLabel)- => Triangulation 'Constrained OverlayVertexSeed () () ()- -> Map FaceId (leftLabel, rightLabel)- -> Either- (OverlayError leftLabel rightLabel)- [ComponentDraft leftLabel rightLabel]-residentComponentDrafts triangulation labels =- traverse convert (faceComponents triangulation (`Map.lookup` labels))- where- exactPointAt vertex =- Right (seedExactPoint (vertexData triangulation vertex))- convert (maybeLabels, component) = do- componentLabels <-- case maybeLabels of- Just value -> Right value- Nothing ->- case faceComponentFaces component of- face : _ -> Left (OverlayProvenanceIncomplete (OverlayResidentFaceUnassigned face))- [] -> Left (OverlayArrangementInvalid OverlayFaceComponentEmpty)- boundary <-- first- (OverlayRegionPublicationFailed . RegionBoundaryObstruction)- (componentBoundary triangulation component)- polygon <-- first OverlayRegionPublicationFailed- (polygonComponentFromBoundaryCoordinates exactPointAt boundary)- pure- ComponentDraft- { componentDraftLabels = componentLabels- , componentDraftFaces = component- , componentDraftPolygon = polygon- , componentDraftTouchesOuter = touchesOuter triangulation component- }--touchesOuter- :: Triangulation mode vertex directed undirected face- -> FaceComponent- -> Bool-touchesOuter triangulation component =- any- ( any ((== outerFace) . incidentFace triangulation . reverseEdge)- . faceDirectedEdges triangulation- )- (faceComponentFaces component)--partitionDrafts- :: (Eq leftLabel, Eq rightLabel)- => (leftLabel, rightLabel)- -> [ComponentDraft leftLabel rightLabel]- -> ([ComponentDraft leftLabel rightLabel], [ComponentDraft leftLabel rightLabel])-partitionDrafts outsidePair =- partition- (\draftComponent ->- componentDraftTouchesOuter draftComponent- && componentDraftLabels draftComponent == outsidePair)--sortComponentDrafts- :: (Ord leftLabel, Ord rightLabel)- => [ComponentDraft leftLabel rightLabel]- -> [ComponentDraft leftLabel rightLabel]-sortComponentDrafts =- sortBy- (\left right ->- compare- (componentDraftPolygon left, componentDraftLabels left)- (componentDraftPolygon right, componentDraftLabels right))--numberBoundedComponents- :: (Ord leftLabel, Ord rightLabel)- => [ComponentDraft leftLabel rightLabel]- -> Either- (OverlayError leftLabel rightLabel)- [(OverlayCellId, ComponentDraft leftLabel rightLabel)]-numberBoundedComponents drafts =- case- [ componentDraftPolygon left- | (left, right) <- consecutivePairs ordered- , componentSignature left == componentSignature right- ] of- duplicate : _ ->- Left- ( OverlayArrangementInvalid- (OverlayDuplicateCellSignature duplicate)- )- [] ->- Right- ( zipWith- (\index component -> (OverlayCellId index, component))- [1 ..]- ordered- )- where- ordered = sortComponentDrafts drafts- componentSignature- :: ComponentDraft leftLabel' rightLabel'- -> (PolygonComponent, (leftLabel', rightLabel'))- componentSignature component =- (componentDraftPolygon component, componentDraftLabels component)---- | Trace the finite boundary cycles of the unbounded exact cell from atomic--- edges only. Resident hull edges and Delaunay diagonals are absent by--- construction; the DCEL contributes only the already-proved incident-cell--- gluing needed to orient each atomic edge with cell zero on its left.-unboundedCellBoundaryLoops- :: Triangulation 'Constrained OverlayVertexSeed () () ()- -> Map ExactEdgeKey (AtomicEdge leftLabel rightLabel)- -> Map FaceId OverlayCellId- -> Either (OverlayError leftLabel rightLabel) [ExactLoop]-unboundedCellBoundaryLoops triangulation atomicByKey cellIdByFace = do- orientedEdges <-- Set.fromList . concat- <$> traverse orientAtomicEdge (undirectedEdges triangulation)- traceBoundaryCycles orientedEdges- where- orientAtomicEdge edge- | Map.notMember (residentSeedEdgeKey triangulation edge) atomicByKey = Right []- | otherwise = do- let (forward, backward) = directedPair edge- forwardCell <- faceCellId (incidentFace triangulation forward)- backwardCell <- faceCellId (incidentFace triangulation backward)- pure- ( case (forwardCell == OverlayCellId 0, backwardCell == OverlayCellId 0) of- (True, False) -> [seedDirectedEndpoints triangulation forward]- (False, True) -> [seedDirectedEndpoints triangulation backward]- _ -> []- )- faceCellId face- | face == outerFace = Right (OverlayCellId 0)- | otherwise =- case Map.lookup face cellIdByFace of- Just cellId -> Right cellId- Nothing -> Left (OverlayProvenanceIncomplete (OverlayResidentFaceUnassigned face))--traceBoundaryCycles- :: Set ExactEdgeKey- -> Either (OverlayError leftLabel rightLabel) [ExactLoop]-traceBoundaryCycles orientedEdges = descend orientedEdges []- where- outgoing =- Map.mapWithKey orderAroundOrigin- ( Set.foldl'- (\byOrigin edge@(from, _) -> Map.insertWith (<>) from [edge] byOrigin)- Map.empty- orientedEdges- )- orderAroundOrigin :: ExactPoint -> [ExactEdgeKey] -> [ExactEdgeKey]- orderAroundOrigin origin =- sortBy (\(_, left) (_, right) -> compareAround origin left right)- descend remaining cycles =- case Set.lookupMin remaining of- Nothing -> Right (sort cycles)- Just seed ->- let (untraced, circuit) = boundaryEulerCircuit outgoing remaining (fst seed)- in case splitBoundaryCircuit circuit of- Nothing ->- Left- ( OverlayArrangementInvalid- (uncurry OverlayCellCycleDidNotClose seed)- )- Just pointCycles -> do- loops <-- traverse- ( first- (OverlayRegionPublicationFailed . RegionValidationObstruction)- . exactLoop- )- pointCycles- descend untraced (loops <> cycles)---- | Consume one directed Eulerian boundary component. Each edge is deleted--- exactly once; circuit extraction therefore cannot acquire the exponential--- branch factor of simple-cycle backtracking at point contacts.-boundaryEulerCircuit- :: Map ExactPoint [ExactEdgeKey]- -> Set ExactEdgeKey- -> ExactPoint- -> (Set ExactEdgeKey, [ExactEdgeKey])-boundaryEulerCircuit outgoing remaining start =- descend remaining [start] [] []- where- descend untraced vertexStack incomingEdges circuit =- case vertexStack of- [] -> (untraced, circuit)- vertex : previousVertices ->- case nextUntracedEdge vertex untraced of- Just edge ->- descend- (Set.delete edge untraced)- (snd edge : vertexStack)- (edge : incomingEdges)- circuit- Nothing ->- case incomingEdges of- edge : previousEdges ->- descend- untraced- previousVertices- previousEdges- (edge : circuit)- [] -> (untraced, circuit)- nextUntracedEdge vertex untraced =- case- [ edge- | edge <- Map.findWithDefault [] vertex outgoing- , Set.member edge untraced- ] of- edge : _ -> Just edge- [] -> Nothing---- | Split an Euler circuit at repeated vertices. The maintained path is--- simple; closing against any resident path vertex emits one simple cycle and--- deletes precisely that suffix before descent continues.-splitBoundaryCircuit :: [ExactEdgeKey] -> Maybe [NonEmpty ExactPoint]-splitBoundaryCircuit circuit =- case circuit of- [] -> Nothing- (start, _) : _ ->- reverse- <$> descend- start- 0- (Map.singleton start 0)- [start]- []- []- circuit- where- descend- :: ExactPoint- -> Int- -> Map ExactPoint Int- -> [ExactPoint]- -> [ExactEdgeKey]- -> [NonEmpty ExactPoint]- -> [ExactEdgeKey]- -> Maybe [NonEmpty ExactPoint]- descend- current- currentDepth- depthByPoint- reversedPathPoints- reversedPathEdges- cycles- remainingEdges =- case remainingEdges of- []- | currentDepth == 0- , [_] <- reversedPathPoints- , null reversedPathEdges -> Just cycles- | otherwise -> Nothing- edge@(fromPoint, toPoint) : rest- | fromPoint /= current -> Nothing- | otherwise ->- case Map.lookup toPoint depthByPoint of- Nothing ->- descend- toPoint- (currentDepth + 1)- (Map.insert toPoint (currentDepth + 1) depthByPoint)- (toPoint : reversedPathPoints)- (edge : reversedPathEdges)- cycles- rest- Just repeatedDepth ->- let cyclePathLength = currentDepth - repeatedDepth- removedPoints = take cyclePathLength reversedPathPoints- cycleEdges = reverse (edge : take cyclePathLength reversedPathEdges)- remainingPathPoints = drop cyclePathLength reversedPathPoints- remainingPathEdges = drop cyclePathLength reversedPathEdges- remainingDepths =- foldr Map.delete depthByPoint removedPoints- in case NonEmpty.nonEmpty (map fst cycleEdges) of- Nothing -> Nothing- Just cyclePoints ->- descend- toPoint- repeatedDepth- remainingDepths- remainingPathPoints- remainingPathEdges- (cyclePoints : cycles)- rest--attachFaceCells- :: Map FaceId OverlayCellId- -> Triangulation 'Constrained OverlayVertexSeed () () ()- -> Either- (OverlayError leftLabel rightLabel)- (Triangulation 'Constrained OverlayVertexSeed () () OverlayFace)-attachFaceCells cellIdByFace triangulation = do- payloads <- V.fromList <$> traverse facePayload (allFaces triangulation)- let outerPayload = OverlayFace (OverlayCellId 0)- defaults = triElementDefaults triangulation- pure- triangulation- { triFaceData = boxedFromVector (Just outerPayload) payloads- , triElementDefaults = defaults{defaultFaceData = outerPayload}- }- where- facePayload face- | face == outerFace = Right (OverlayFace (OverlayCellId 0))- | otherwise =- case Map.lookup face cellIdByFace of- Just cellId -> Right (OverlayFace cellId)- Nothing ->- Left- ( OverlayProvenanceIncomplete- (OverlayResidentFaceUnassigned face)- )--attachOverlayVertices- :: Triangulation 'Constrained OverlayVertexSeed () () face- -> Triangulation 'Constrained OverlayVertex () () face-attachOverlayVertices =- Dcel.mapVertices- (\seed -> OverlayVertex (seedExactPoint seed) (seedOrigin seed))--cellPayload- :: V.Vector (OverlayCell leftLabel rightLabel)- -> OverlayCellId- -> Either OverlayCellWitness (OverlayCell leftLabel rightLabel)-cellPayload cells cellId@(OverlayCellId index) =- case cells V.!? index of- Just cell -> Right cell- Nothing -> Left (OverlayCellPayloadMissing cellId)--faceLabels- :: OverlayResult leftLabel rightLabel- -> FaceId- -> Either OverlayCellWitness (leftLabel, rightLabel)-faceLabels result face = do- cell <-- cellPayload- (overlayResultCells result)- (overlayFaceCellId (faceData (overlayResultTriangulation result) face))- pure (overlayCellLeft cell, overlayCellRight cell)--regionFaceLabels- :: OverlayResult leftLabel rightLabel- -> FaceId- -> Either RegionPublicationError (leftLabel, rightLabel)-regionFaceLabels result face =- case faceLabels result face of- Right labels -> Right labels- Left _ -> Left (RegionFaceLabelMissing face)--vertexSupport- :: (Ord leftLabel, Ord rightLabel)- => OverlayResult leftLabel rightLabel- -> VertexId- -> Either OverlayCellWitness (OverlayCellSupport leftLabel rightLabel)-vertexSupport result vertex = do- labels <-- traverse- (faceLabels result . incidentFace triangulation)- (vertexOutgoingEdges triangulation vertex)- case supportFromPairs labels of- Just support -> Right support- Nothing -> Left (OverlayVertexSupportMissing vertex)- where- triangulation = overlayResultTriangulation result--edgeSupport- :: (Ord leftLabel, Ord rightLabel)- => OverlayResult leftLabel rightLabel- -> UndirectedEdgeId- -> Either OverlayCellWitness (OverlayCellSupport leftLabel rightLabel)-edgeSupport result edge = do- pairs <- traverse (faceLabels result . incidentFace triangulation) [forward, backward]- case supportFromPairs pairs of- Just support -> Right support- Nothing -> Left (OverlayEdgeSupportMissing edge)- where- triangulation = overlayResultTriangulation result- (forward, backward) = directedPair edge--supportFromPairs- :: (Ord leftLabel, Ord rightLabel)- => [(leftLabel, rightLabel)]- -> Maybe (OverlayCellSupport leftLabel rightLabel)-supportFromPairs pairs = do- let (leftLabels, rightLabels) =- List.foldl'- (\(left, right) (leftLabel, rightLabel) ->- (Set.insert leftLabel left, Set.insert rightLabel right))- (Set.empty, Set.empty)- pairs- leftSupport <- nonEmptySupport leftLabels- rightSupport <- nonEmptySupport rightLabels- pure (OverlayCellSupport leftSupport rightSupport)--nonEmptySupport :: Set label -> Maybe (OverlaySupport label)-nonEmptySupport labels = OverlaySupport <$> NonEmpty.nonEmpty (Set.toAscList labels)--validateAtomicConstraints- :: Triangulation- 'Constrained- OverlayVertexSeed- ()- ()- ()- -> Map ExactEdgeKey (AtomicEdge leftLabel rightLabel)- -> Either (OverlayError leftLabel rightLabel) ()-validateAtomicConstraints triangulation atomicByKey = do- traverse_ requireAtomic (Map.keys atomicByKey)- traverse_ requireExpectedConstraint (undirectedEdges triangulation)- where- residentByKey =- Map.fromList- [ (residentSeedEdgeKey triangulation edge, edge)- | edge <- undirectedEdges triangulation- ]- requireAtomic key@(from, to) =- case Map.lookup key residentByKey of- Nothing -> Left (OverlayProvenanceIncomplete (OverlayAtomicConstraintMissing from to))- Just edge- | isConstraintEdge triangulation edge -> Right ()- | otherwise -> Left (OverlayProvenanceIncomplete (OverlayBoundaryEdgeNotConstrained edge))- requireExpectedConstraint edge- | isConstraintEdge triangulation edge- && Map.notMember (residentSeedEdgeKey triangulation edge) atomicByKey =- Left (OverlayProvenanceIncomplete (OverlayUnexpectedConstraint edge))- | otherwise = Right ()--residentSeedEdgeKey- :: Triangulation mode OverlayVertexSeed directed undirected face- -> UndirectedEdgeId- -> ExactEdgeKey-residentSeedEdgeKey = residentEdgeKeyBy seedExactPoint--residentEdgeKey- :: Triangulation mode OverlayVertex directed undirected face- -> UndirectedEdgeId- -> ExactEdgeKey-residentEdgeKey = residentEdgeKeyBy overlayExactPoint--residentEdgeKeyBy- :: (vertex -> ExactPoint)- -> Triangulation mode vertex directed undirected face- -> UndirectedEdgeId- -> ExactEdgeKey-residentEdgeKeyBy exactPointAt triangulation edge =- let (from, to) = undirectedEndpoints triangulation edge- in canonicalEdgeKey- (exactPointAt (vertexData triangulation from))- (exactPointAt (vertexData triangulation to))
− src-build/Moonlight/Triangulation/Internal/Overlay/Types.hs
@@ -1,267 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE DerivingStrategies #-}-{-# LANGUAGE KindSignatures #-}---- | Closed vocabulary and opaque carrier representation for exact labelled--- overlay. Construction lives in the public build-tier module; this module--- exists so every invariant-bearing payload shares one owner.-module Moonlight.Triangulation.Internal.Overlay.Types- ( BoundaryLoopRef (..)- , OverlayOperand (..)- , BoundaryVertexRef (..)- , BoundaryEdgeRef (..)- , OverlayVertexOrigin (..)- , OverlayEdgeOrigin (..)- , OverlaySupport (..)- , overlaySupportLabels- , OverlayCellSupport (..)- , OverlayCellId (..)- , OverlayCellGeometry (..)- , OverlayCell (..)- , OverlayFace (..)- , OverlayVertex (..)- , OverlayEdge (..)- , OverlayReceipt (..)- , OverlayArrangementObstruction (..)- , OverlayCellWitness (..)- , OverlayError (..)- , OverlayResult (..)- , OverlaySelectionKind (..)- , OverlaySelectionError (..)- ) where--import Control.DeepSeq (NFData)-import Data.List.NonEmpty (NonEmpty)-import Data.Vector (Vector)-import GHC.Generics (Generic)-import Moonlight.Triangulation.CellSet (CellSelectionError)-import Moonlight.Triangulation.Exact- ( ExactPoint- )-import Moonlight.Triangulation.Handles.HandleDefs- ( FaceId- , UndirectedEdgeId- , VertexId- )-import Moonlight.Triangulation.Internal.Cdt.Types (CdtError)-import Moonlight.Triangulation.Internal.ExactRational (ExactArithmeticError)-import Moonlight.Triangulation.Internal.ExactSegmentEvents (ExactSegmentEventObstruction)-import Moonlight.Triangulation.Internal.Overlay.Embedding (OverlayEmbeddingObstruction)-import Moonlight.Triangulation.Internal.Region.Types- ( ExactLoop- , PolygonComponent- , RegionPublicationError- )-import Moonlight.Triangulation.Internal.Representation (Triangulation)-import Moonlight.Triangulation.Internal.Types- ( ConstraintMode (Constrained)- )---- | Which cycle inside a polygon component supplied a source reference.-data BoundaryLoopRef- = BoundaryOuterLoop- | BoundaryHoleLoop !Int- deriving stock (Eq, Ord, Show, Generic)- deriving anyclass (NFData)--data OverlayOperand- = LeftOverlayOperand- | RightOverlayOperand---- | A boundary vertex reference whose phantom operand prevents left/right--- provenance from being interchanged while sharing their identical payload.-data BoundaryVertexRef (operand :: OverlayOperand) = BoundaryVertexRef- { boundaryVertexComponent :: !Int- , boundaryVertexLoop :: !BoundaryLoopRef- , boundaryVertexLocalIndex :: !Int- }- deriving stock (Eq, Ord, Show, Generic)- deriving anyclass (NFData)---- | The corresponding typed reference for one source boundary edge.-data BoundaryEdgeRef (operand :: OverlayOperand) = BoundaryEdgeRef- { boundaryEdgeComponent :: !Int- , boundaryEdgeLoop :: !BoundaryLoopRef- , boundaryEdgeLocalIndex :: !Int- }- deriving stock (Eq, Ord, Show, Generic)- deriving anyclass (NFData)---- | Complete typed source closure of one arrangement vertex.-data OverlayVertexOrigin = OverlayVertexOrigin- { overlayOriginLeftVertices :: ![BoundaryVertexRef 'LeftOverlayOperand]- , overlayOriginRightVertices :: ![BoundaryVertexRef 'RightOverlayOperand]- , overlayOriginLeftEdges :: ![BoundaryEdgeRef 'LeftOverlayOperand]- , overlayOriginRightEdges :: ![BoundaryEdgeRef 'RightOverlayOperand]- }- deriving stock (Eq, Ord, Show, Generic)- deriving anyclass (NFData)---- | Nonempty typed source-edge provenance of one exact atomic interval.-data OverlayEdgeOrigin = OverlayEdgeOrigin- { overlayEdgeLeftSources :: ![BoundaryEdgeRef 'LeftOverlayOperand]- , overlayEdgeRightSources :: ![BoundaryEdgeRef 'RightOverlayOperand]- }- deriving stock (Eq, Ord, Show, Generic)- deriving anyclass (NFData)---- | Sorted nonempty labels whose closures contain one relatively open cell.-newtype OverlaySupport label = OverlaySupport (NonEmpty label)- deriving stock (Eq, Ord, Show, Generic)- deriving anyclass (NFData)--overlaySupportLabels :: OverlaySupport label -> NonEmpty label-overlaySupportLabels (OverlaySupport labels) = labels--data OverlayCellSupport leftLabel rightLabel = OverlayCellSupport- { overlaySupportLeft :: !(OverlaySupport leftLabel)- , overlaySupportRight :: !(OverlaySupport rightLabel)- }- deriving stock (Eq, Ord, Show, Generic)- deriving anyclass (NFData)--newtype OverlayCellId = OverlayCellId Int- deriving stock (Eq, Ord, Show, Generic)- deriving anyclass (NFData)--data OverlayCellGeometry- = BoundedOverlayCell !PolygonComponent- | UnboundedOverlayCell ![ExactLoop]- deriving stock (Eq, Ord, Show, Generic)- deriving anyclass (NFData)--data OverlayCell leftLabel rightLabel = OverlayCell- { overlayCellLeft :: !leftLabel- , overlayCellRight :: !rightLabel- , overlayCellGeometry :: !OverlayCellGeometry- }- deriving stock (Eq, Ord, Show, Generic)- deriving anyclass (NFData)---- | A resident face stores only its authoritative dense-cell reference.--- Labels and geometry belong to 'OverlayCell'; duplicating them here made--- every observation carry a reconciliation obligation with no additional law.-newtype OverlayFace = OverlayFace- { overlayFaceCellId :: OverlayCellId- }- deriving stock (Eq, Ord, Show, Generic)- deriving anyclass (NFData)---- | Exact geometry and provenance are intrinsic to a vertex. Label support is--- the finite union of incident cell descriptors and is therefore a view.-data OverlayVertex = OverlayVertex- { overlayExactPoint :: !ExactPoint- , overlayVertexOrigin :: !OverlayVertexOrigin- }- deriving stock (Eq, Ord, Show, Generic)- deriving anyclass (NFData)---- | Boundary provenance is intrinsic; label support is derived from the two--- incident face-cell references.-data OverlayEdge- = OverlayBoundary !OverlayEdgeOrigin- | OverlayDiagonal- deriving stock (Eq, Ord, Show, Generic)- deriving anyclass (NFData)--data OverlayReceipt = OverlayReceipt- { overlayInputSegments :: !Int- , overlayRelationEvents :: !Int- , overlayExactCrossings :: !Int- , overlayOverlapIntervals :: !Int- , overlayAtomicEdges :: !Int- , overlayOutputVertices :: !Int- , overlayArrangementCells :: !Int- , overlayResidentFaces :: !Int- , overlayEmbeddingCandidates :: !Int- , overlayTotalRelationChecks :: !Int- , overlaySweepMaximumHeight :: !Int- }- deriving stock (Eq, Ord, Show, Generic)- deriving anyclass (NFData)--data OverlayArrangementObstruction leftLabel rightLabel- = OverlayLeftSourceSideConflict- !OverlayEdgeOrigin- !(NonEmpty leftLabel)- | OverlayRightSourceSideConflict- !OverlayEdgeOrigin- !(NonEmpty rightLabel)- | OverlayRotationDegenerate !ExactPoint- | OverlayFaceComponentEmpty- | OverlayCellCycleDidNotClose !ExactPoint !ExactPoint- | OverlayTransitionSourceMismatch- !FaceId- !UndirectedEdgeId- !(leftLabel, rightLabel)- !(leftLabel, rightLabel)- | OverlayResidentFaceLabelConflict- !FaceId- !UndirectedEdgeId- !(leftLabel, rightLabel)- !(leftLabel, rightLabel)- | OverlayDuplicateCellSignature !PolygonComponent- deriving stock (Eq, Show, Generic)- deriving anyclass (NFData)--data OverlayCellWitness- = OverlayAtomicConstraintMissing !ExactPoint !ExactPoint- | OverlayAtomicConstraintOrientationMismatch- !UndirectedEdgeId- !ExactPoint- !ExactPoint- | OverlayUnexpectedConstraint !UndirectedEdgeId- | OverlayBoundaryEdgeNotConstrained !UndirectedEdgeId- | OverlayResidentFaceUnassigned !FaceId- | OverlayCellPayloadMissing !OverlayCellId- | OverlayVertexSupportMissing !VertexId- | OverlayEdgeSupportMissing !UndirectedEdgeId- | OverlayEmbeddedVertexCountMismatch !Int !Int- | OverlayExactVertexMissing !ExactPoint- deriving stock (Eq, Show, Generic)- deriving anyclass (NFData)--data OverlayError leftLabel rightLabel- = OverlayExactArithmetic !ExactArithmeticError- | OverlaySegmentEventsInvalid !ExactSegmentEventObstruction- | OverlayArrangementInvalid !(OverlayArrangementObstruction leftLabel rightLabel)- | OverlayEmbeddingRefused !(NonEmpty OverlayEmbeddingObstruction)- | OverlayBuildFailed !CdtError- | OverlayRegionPublicationFailed !RegionPublicationError- | OverlayProvenanceIncomplete !OverlayCellWitness- deriving stock (Eq, Show, Generic)- deriving anyclass (NFData)---- | The singular exact subdivision carrier. Its embedded DCEL is a derived--- binary64 realization; exact coordinates remain in the vertex payload plane.-data OverlayResult leftLabel rightLabel = OverlayResult- { overlayResultTriangulation- :: !( Triangulation- 'Constrained- OverlayVertex- ()- OverlayEdge- OverlayFace- )- , overlayResultCells :: !(Vector (OverlayCell leftLabel rightLabel))- , overlayResultOutsideLabels :: !(leftLabel, rightLabel)- , overlayResultReceipt :: !OverlayReceipt- }- deriving stock (Generic)- deriving anyclass (NFData)--data OverlaySelectionKind- = ClosedUnionSelection- | ClosedIntersectionSelection- | RegularizedDifferenceSelection- deriving stock (Eq, Ord, Show, Generic)- deriving anyclass (NFData)--data OverlaySelectionError- = OverlaySelectionContainsUnboundedCell !OverlaySelectionKind- | OverlaySelectionProvenance !OverlayCellWitness- | OverlaySelectionInvalid !CellSelectionError- deriving stock (Eq, Show, Generic)- deriving anyclass (NFData)
src-build/Moonlight/Triangulation/Internal/Refinement.hs view
@@ -19,7 +19,6 @@ import Control.Monad (filterM, forM_, unless, when) import Control.Monad.ST (ST) import Data.Foldable (traverse_)-import Data.Bits (xor) import qualified Data.IntSet as IntSet import Data.List (find) import qualified Data.Map.Strict as Map@@ -28,7 +27,9 @@ import qualified Data.Vector.Unboxed.Mutable as MUV import Data.Word (Word32) import Moonlight.Triangulation.Handles.HandleDefs (FaceId (..), UndirectedEdgeId (..))-import Moonlight.Triangulation.Internal.DcelOperations (insertOnEdge, legalizeEdges)+import Moonlight.Triangulation.Internal.DcelOperations.Legalize (legalizeEdges)+import Moonlight.Triangulation.Internal.DcelOperations.Subdivide (insertOnEdge)+import Moonlight.Triangulation.Internal.DcelOperations.Twin (reverseIndex) import Moonlight.Triangulation.Internal.Growable ( GrowableWord32 , clearGrowable@@ -169,10 +170,6 @@ , refinementDomainInputEdgeCount :: {-# UNPACK #-} !Int } deriving stock (Eq, Show)--reverseIndex :: Int -> Int-reverseIndex edge = edge `xor` 1-{-# INLINE reverseIndex #-} -- | Refine the existing finite DCEL. Fixed edges (constraints and the convex -- hull) are legal barriers: a candidate whose prospective cavity meets one
src-build/Moonlight/Triangulation/Internal/Transaction.hs view
@@ -10,6 +10,7 @@ -- forgeable. module Moonlight.Triangulation.Internal.Transaction ( runTransaction+ , runUnmeasuredTransaction ) where import Control.Monad.ST (ST, runST)@@ -43,8 +44,46 @@ -> Int -> (forall s. MutableDcel s vertex directed undirected face -> OperationState s -> ST s (Either failure result)) -> Either failure (result, Triangulation mode vertex directed undirected face, BuildStats)-runTransaction mapBuildFailure shape triangulation additional action = do- let !capacity = numVertices triangulation + max 0 additional+runTransaction = runTransactionWithReceipt freezeBuildStats+{-# INLINE runTransaction #-}++-- | Publish a transaction whose caller observes no instrumentation. Avoiding+-- the statistics fold matters for singleton constraint verbs: their public+-- result has no statistics field, so reading every counter would be dead work.+runUnmeasuredTransaction+ :: (BuildError -> failure)+ -> TransactionShape+ -> Triangulation mode vertex directed undirected face+ -> Int+ -> (forall s. MutableDcel s vertex directed undirected face -> OperationState s -> ST s (Either failure result))+ -> Either failure (result, Triangulation mode vertex directed undirected face)+runUnmeasuredTransaction mapBuildFailure shape triangulation additional action =+ fmap+ (\(result, frozen, ()) -> (result, frozen))+ ( runTransactionWithReceipt+ (const (pure ()))+ mapBuildFailure+ shape+ triangulation+ additional+ action+ )+{-# INLINE runUnmeasuredTransaction #-}++runTransactionWithReceipt+ :: (forall s. OperationState s -> ST s receipt)+ -> (BuildError -> failure)+ -> TransactionShape+ -> Triangulation mode vertex directed undirected face+ -> Int+ -> (forall s. MutableDcel s vertex directed undirected face -> OperationState s -> ST s (Either failure result))+ -> Either failure (result, Triangulation mode vertex directed undirected face, receipt)+runTransactionWithReceipt freezeReceipt mapBuildFailure shape triangulation additional action = do+ let !currentCapacity = numVertices triangulation+ !requestedAdditional = max 0 additional+ !capacity+ | requestedAdditional > maxBound - currentCapacity = maxBound+ | otherwise = currentCapacity + requestedAdditional case ensureCapacity capacity of Left failure -> Left (mapBuildFailure failure) Right () -> pure ()@@ -62,6 +101,6 @@ case frozenOutcome of Left obstruction -> pure (Left (mapBuildFailure obstruction)) Right frozen -> do- stats <- freezeBuildStats operation- pure (Right (value, frozen, stats))-{-# INLINE runTransaction #-}+ receipt <- freezeReceipt operation+ pure (Right (value, frozen, receipt))+{-# INLINE runTransactionWithReceipt #-}
− src-build/Moonlight/Triangulation/Minkowski.hs
@@ -1,584 +0,0 @@-{-# LANGUAGE DataKinds #-}---- | Exact polygonal Minkowski addition and regularized two-dimensional--- morphology. Convex convolution is direct; nonconvex construction descends--- through the existing exact overlay, resident CDT, and grouped publication--- owners. Lower-dimensional erosion residuals cannot inhabit 'PlanarRegion'--- and therefore publish as empty rather than being forged as polygons.-module Moonlight.Triangulation.Minkowski- ( ConvexPolygon- , convexPolygon- , convexPolygonPoints- , StructuringElement- , structuringElement- , MinkowskiOperation (..)- , MinkowskiError (..)- , MinkowskiReceipt (..)- , convexMinkowskiSum- , minkowskiSum- , erodeBy- , openWith- , closeWith- , polygonOffset- , polygonInset- ) where--import Control.Applicative ((<|>))-import Control.Monad (filterM)-import Data.Bifunctor (first)-import qualified Data.IntMap.Strict as IntMap-import qualified Data.List as List-import qualified Data.Map.Strict as Map-import Data.List.NonEmpty (NonEmpty (..))-import qualified Data.List.NonEmpty as NonEmpty-import Data.Maybe (fromMaybe)-import qualified Data.Set as Set-import qualified Data.Vector as V-import Moonlight.Triangulation.Dcel- ( faceData- , faceVertices- , vertexData- )-import Moonlight.Triangulation.Exact- ( ExactPoint- , exactPointCoordinates- )-import Moonlight.Triangulation.Handles.HandleDefs- ( FaceId- )-import Moonlight.Triangulation.Handles.Iterators.FixedIterators (innerFaces)-import Moonlight.Triangulation.Internal.ExactRational- ( ExactRational- , exactRationalDenominator- , exactRationalNumerator- )-import Moonlight.Triangulation.Internal.BoundaryCycle (cyclePairs)-import Moonlight.Triangulation.Internal.Dyadic (integerBitLength)-import Moonlight.Triangulation.Internal.Minkowski.Convex- ( addExactPoints- , admittedConvexLoop- , convexHullPolygon- , convexMinkowskiPolygon- , convexMinkowskiSum- , convexPolygon- , convexPolygonCentroid- , convexPolygonPoints- , convexPolygonRegion- , erodeConvexBy- , reflectConvexPolygon- , structuringElement- , structuringElementPolygon- )-import Moonlight.Triangulation.Internal.Minkowski.Types-import Moonlight.Triangulation.Internal.Overlay.Resident (faceLabels)-import Moonlight.Triangulation.Internal.Overlay.Types- ( OverlayCell (..)- , OverlayCellGeometry (..)- , OverlayCellId (..)- , OverlayFace (..)- , OverlayResult (..)- , OverlayVertex (..)- )-import Moonlight.Triangulation.Overlay- ( OverlayReceipt (..)- , overlayLayers- , overlayReceipt- , overlaySelectedRegion- )-import Moonlight.Triangulation.Region- ( PlanarLayer- , PlanarRegion- , PolygonComponent- , RegionPointLocation (..)- , emptyPlanarRegion- , exactLoopPoints- , planarLayerRegions- , planarRegionComponents- , polygonHoleLoops- , polygonOuterLoop- , regionPointLocation- )-import Moonlight.Triangulation.Internal.Region.Publication- ( labelledPlanarLayerFromExactCoordinates- , planarLayerFromAdmittedComponents- )--data MorphologyMetrics = MorphologyMetrics- { metricOverlayPasses :: !Int- , metricExactCrossings :: !Int- , metricOutputCells :: !(Maybe Int)- }--emptyMetrics :: MorphologyMetrics-emptyMetrics = MorphologyMetrics 0 0 Nothing--appendMetrics :: MorphologyMetrics -> MorphologyMetrics -> MorphologyMetrics-appendMetrics left right =- MorphologyMetrics- { metricOverlayPasses = metricOverlayPasses left + metricOverlayPasses right- , metricExactCrossings = metricExactCrossings left + metricExactCrossings right- , metricOutputCells = metricOutputCells right <|> metricOutputCells left- }--minkowskiSum- :: PlanarRegion- -> PlanarRegion- -> Either MinkowskiError (PlanarRegion, MinkowskiReceipt)-minkowskiSum left right = do- (leftPieces, leftMetrics) <- decomposeRegion left- (rightPieces, rightMetrics) <- decomposeRegion right- let generated =- [ convexMinkowskiPolygon leftPiece rightPiece- | leftPiece <- leftPieces- , rightPiece <- rightPieces- ]- generatedRegions = map convexPolygonRegion generated- convolutionEdges = sum (map (NonEmpty.length . convexPolygonPoints) generated)- (result, unionMetrics) <- unionRegions generatedRegions- let metrics = leftMetrics `appendMetrics` rightMetrics `appendMetrics` unionMetrics- pure- ( result- , MinkowskiReceipt- { minkowskiOperation = MinkowskiAddition- , minkowskiInputComponents =- length (planarRegionComponents left)- + length (planarRegionComponents right)- , minkowskiConvexPieces = length leftPieces + length rightPieces- , minkowskiGeneratedPieces = length generated- , minkowskiGeneratedConvolutionEdges = convolutionEdges- , minkowskiOverlayPasses = metricOverlayPasses metrics- , minkowskiExactCrossings = metricExactCrossings metrics- , minkowskiOutputCells = fromMaybe 0 (metricOutputCells metrics)- , minkowskiExactCoordinateBitGrowth =- coordinateBitGrowth [left, right] result- }- )---- | Erode a polygonal region by an origin-anchored convex kernel and publish--- the regularized full-dimensional result. A residual consisting only of--- points or segments is represented by 'emptyPlanarRegion'.-erodeBy- :: StructuringElement- -> PlanarRegion- -> Either MinkowskiError (PlanarRegion, MinkowskiReceipt)-erodeBy element source =- case singleConvexRegion source of- Just sourcePolygon -> convexErosion element source sourcePolygon- Nothing -> generalErosion element source--openWith- :: StructuringElement- -> PlanarRegion- -> Either MinkowskiError (PlanarRegion, MinkowskiReceipt)-openWith element source = do- (eroded, erosionReceipt) <- erodeBy element source- (opened, additionReceipt) <- polygonOffset element eroded- pure- ( opened- , composeReceipts- MinkowskiOpening- (length (planarRegionComponents source))- erosionReceipt- additionReceipt- )--closeWith- :: StructuringElement- -> PlanarRegion- -> Either MinkowskiError (PlanarRegion, MinkowskiReceipt)-closeWith element source = do- (expanded, additionReceipt) <- polygonOffset element source- (closed, erosionReceipt) <- erodeBy element expanded- pure- ( closed- , composeReceipts- MinkowskiClosing- (length (planarRegionComponents source))- additionReceipt- erosionReceipt- )--polygonOffset- :: StructuringElement- -> PlanarRegion- -> Either MinkowskiError (PlanarRegion, MinkowskiReceipt)-polygonOffset element source =- minkowskiSum source (convexPolygonRegion (structuringElementPolygon element))--polygonInset- :: StructuringElement- -> PlanarRegion- -> Either MinkowskiError (PlanarRegion, MinkowskiReceipt)-polygonInset = erodeBy--decomposeRegion- :: PlanarRegion- -> Either MinkowskiError ([ConvexPolygon], MorphologyMetrics)-decomposeRegion region =- case traverse convexComponent (planarRegionComponents region) of- Just convexPieces -> Right (convexPieces, emptyMetrics)- Nothing -> triangulatedPieces region--convexComponent :: PolygonComponent -> Maybe ConvexPolygon-convexComponent component =- case polygonHoleLoops component of- [] -> admittedConvexLoop (polygonOuterLoop component)- _ -> Nothing--singleConvexRegion :: PlanarRegion -> Maybe ConvexPolygon-singleConvexRegion region =- case planarRegionComponents region of- [component] -> convexComponent component- _ -> Nothing--triangulatedPieces- :: PlanarRegion- -> Either MinkowskiError ([ConvexPolygon], MorphologyMetrics)-triangulatedPieces region = do- let sourceLayer = morphologyLayer region- result <- first MinkowskiOverlayFailed (overlayLayers sourceLayer emptyMorphologyLayer)- selectedFaces <-- filterM- ( fmap fst- . first MinkowskiOverlayCellWitness- . faceLabels result- )- (innerFaces (overlayResultTriangulation result))- pieces <- traverse (faceConvexPolygon result) selectedFaces- pure (pieces, metricsFromOverlay result (length pieces))--faceConvexPolygon- :: OverlayResult leftLabel rightLabel- -> FaceId- -> Either MinkowskiError ConvexPolygon-faceConvexPolygon result face =- case- map- (overlayExactPoint . vertexData triangulation)- (faceVertices triangulation face) of- [firstPoint, secondPoint, thirdPoint] ->- convexHullPolygon (firstPoint :| [secondPoint, thirdPoint])- vertices -> Left (MinkowskiFaceArity face (length vertices))- where- triangulation = overlayResultTriangulation result--unionRegions- :: [PlanarRegion]- -> Either MinkowskiError (PlanarRegion, MorphologyMetrics)-unionRegions [] = Right (emptyPlanarRegion, emptyMetrics)-unionRegions [region] =- Right- ( region- , emptyMetrics{metricOutputCells = Just (length (planarRegionComponents region))}- )-unionRegions regions = do- let (leftRegions, rightRegions) = splitAt (length regions `div` 2) regions- left <- unionRegions leftRegions- right <- unionRegions rightRegions- glueRegionUnion left right--glueRegionUnion- :: (PlanarRegion, MorphologyMetrics)- -> (PlanarRegion, MorphologyMetrics)- -> Either MinkowskiError (PlanarRegion, MorphologyMetrics)-glueRegionUnion (left, leftMetrics) (right, rightMetrics)- | null (planarRegionComponents left) = Right (right, leftMetrics `appendMetrics` rightMetrics)- | null (planarRegionComponents right) = Right (left, leftMetrics `appendMetrics` rightMetrics)- | otherwise = do- result <-- first MinkowskiOverlayFailed- (overlayLayers (morphologyLayer left) (morphologyLayer right))- published <-- first MinkowskiPublicationFailed- ( overlaySelectedRegion- (uncurry (||))- result- )- let selectedCells =- V.foldl'- (\count cell ->- case overlayCellGeometry cell of- BoundedOverlayCell _- | overlayCellLeft cell || overlayCellRight cell ->- count + 1- _ -> count)- 0- (overlayResultCells result)- pure- ( published- , leftMetrics- `appendMetrics` rightMetrics- `appendMetrics` metricsFromOverlay result selectedCells- )--convexErosion- :: StructuringElement- -> PlanarRegion- -> ConvexPolygon- -> Either MinkowskiError (PlanarRegion, MinkowskiReceipt)-convexErosion element source sourcePolygon = do- eroded <- erodeConvexBy sourcePolygon (structuringElementPolygon element)- let result = maybe emptyPlanarRegion convexPolygonRegion eroded- outputCells = maybe 0 (const 1) eroded- generatedEdges = maybe 0 (NonEmpty.length . convexPolygonPoints) eroded- pure- ( result- , MinkowskiReceipt- { minkowskiOperation = MinkowskiErosion- , minkowskiInputComponents = 1- , minkowskiConvexPieces = 2- , minkowskiGeneratedPieces = outputCells- , minkowskiGeneratedConvolutionEdges = generatedEdges- , minkowskiOverlayPasses = 0- , minkowskiExactCrossings = 0- , minkowskiOutputCells = outputCells- , minkowskiExactCoordinateBitGrowth =- coordinateBitGrowth- [ source- , convexPolygonRegion (structuringElementPolygon element)- ]- result- }- )--generalErosion- :: StructuringElement- -> PlanarRegion- -> Either MinkowskiError (PlanarRegion, MinkowskiReceipt)-generalErosion element source- | null sourceEdges = Right (emptyPlanarRegion, emptyErosionReceipt)- | otherwise = do- sweptPolygons <- traverse (sweepBoundaryEdge reflectedKernel) sourceEdges- let sweptRegions = map convexPolygonRegion sweptPolygons- generatedEdges =- sum (map (NonEmpty.length . convexPolygonPoints) sweptPolygons)- (contactRegion, unionMetrics) <- unionRegions sweptRegions- candidateOverlay <-- first MinkowskiOverlayFailed- (overlayLayers (morphologyLayer contactRegion) emptyMorphologyLayer)- kernelWitness <- convexPolygonCentroid kernel- let representativeFaceByCell = representativeFaces candidateOverlay- selectedCellIds <-- Set.fromList- <$> filterM- ( classifyCandidateCell- source- kernelWitness- representativeFaceByCell- candidateOverlay- )- (boundedOutsideCellIds candidateOverlay)- published <- publishCellSelection selectedCellIds candidateOverlay- let candidateMetrics =- metricsFromOverlay candidateOverlay (Set.size selectedCellIds)- metrics = unionMetrics `appendMetrics` candidateMetrics- pure- ( published- , MinkowskiReceipt- { minkowskiOperation = MinkowskiErosion- , minkowskiInputComponents = length (planarRegionComponents source)- , minkowskiConvexPieces = 1- , minkowskiGeneratedPieces = length sweptPolygons- , minkowskiGeneratedConvolutionEdges = generatedEdges- , minkowskiOverlayPasses = metricOverlayPasses metrics- , minkowskiExactCrossings = metricExactCrossings metrics- , minkowskiOutputCells = Set.size selectedCellIds- , minkowskiExactCoordinateBitGrowth =- coordinateBitGrowth- [source, convexPolygonRegion kernel]- published- }- )- where- kernel = structuringElementPolygon element- reflectedKernel = reflectConvexPolygon kernel- sourceEdges = regionBoundaryEdges source- emptyErosionReceipt =- MinkowskiReceipt- { minkowskiOperation = MinkowskiErosion- , minkowskiInputComponents = 0- , minkowskiConvexPieces = 1- , minkowskiGeneratedPieces = 0- , minkowskiGeneratedConvolutionEdges = 0- , minkowskiOverlayPasses = 0- , minkowskiExactCrossings = 0- , minkowskiOutputCells = 0- , minkowskiExactCoordinateBitGrowth = 0- }--sweepBoundaryEdge- :: ConvexPolygon- -> (ExactPoint, ExactPoint)- -> Either MinkowskiError ConvexPolygon-sweepBoundaryEdge reflectedKernel (from, to) =- case convexPolygonPoints reflectedKernel of- firstKernelPoint :| remainingKernelPoints ->- convexHullPolygon- ( addExactPoints from firstKernelPoint- :| ( map (addExactPoints from) remainingKernelPoints- <> map (addExactPoints to) kernelPoints- )- )- where- kernelPoints = NonEmpty.toList (convexPolygonPoints reflectedKernel)--boundedOutsideCellIds- :: OverlayResult Bool Bool- -> [OverlayCellId]-boundedOutsideCellIds result =- V.ifoldr- (\index cell selected ->- case overlayCellGeometry cell of- BoundedOverlayCell _- | not (overlayCellLeft cell)- && not (overlayCellRight cell) ->- OverlayCellId index : selected- _ -> selected)- []- (overlayResultCells result)--classifyCandidateCell- :: PlanarRegion- -> ExactPoint- -> IntMap.IntMap FaceId- -> OverlayResult Bool Bool- -> OverlayCellId- -> Either MinkowskiError Bool-classifyCandidateCell source kernelWitness representativeFaceByCell result cellId = do- face <-- maybe- (Left (MinkowskiCandidateCellMissing cellId))- Right- (IntMap.lookup (overlayCellIndex cellId) representativeFaceByCell)- candidate <- convexPolygonCentroid =<< faceConvexPolygon result face- let inclusionWitness = addExactPoints candidate kernelWitness- case regionPointLocation source inclusionWitness of- RegionInterior -> Right True- RegionExterior -> Right False- RegionOnBoundary -> Left (MinkowskiInclusionAmbiguous cellId inclusionWitness)--representativeFaces- :: OverlayResult leftLabel rightLabel- -> IntMap.IntMap FaceId-representativeFaces result =- IntMap.fromListWith min- [ (overlayCellIndex (overlayFaceCellId (faceData triangulation face)), face)- | face <- innerFaces triangulation- ]- where- triangulation = overlayResultTriangulation result--overlayCellIndex :: OverlayCellId -> Int-overlayCellIndex (OverlayCellId index) = index--publishCellSelection- :: Set.Set OverlayCellId- -> OverlayResult leftLabel rightLabel- -> Either MinkowskiError PlanarRegion-publishCellSelection selected result = do- layer <-- first MinkowskiPublicationFailed- ( labelledPlanarLayerFromExactCoordinates- False- triangulation- (\vertex -> Right (overlayExactPoint (vertexData triangulation vertex)))- (\face ->- Right- ( Set.member- (overlayFaceCellId (faceData triangulation face))- selected- ))- )- pure (Map.findWithDefault emptyPlanarRegion True (planarLayerRegions layer))- where- triangulation = overlayResultTriangulation result--morphologyLayer- :: PlanarRegion- -> PlanarLayer Bool-morphologyLayer region =- planarLayerFromAdmittedComponents- False- [(True, component) | component <- planarRegionComponents region]--emptyMorphologyLayer :: PlanarLayer Bool-emptyMorphologyLayer = morphologyLayer emptyPlanarRegion--regionBoundaryEdges :: PlanarRegion -> [(ExactPoint, ExactPoint)]-regionBoundaryEdges region =- concatMap- (\component ->- concatMap- (cyclePairs . exactLoopPoints)- (polygonOuterLoop component : polygonHoleLoops component))- (planarRegionComponents region)--metricsFromOverlay- :: OverlayResult leftLabel rightLabel- -> Int- -> MorphologyMetrics-metricsFromOverlay result outputCells =- MorphologyMetrics- { metricOverlayPasses = 1- , metricExactCrossings = overlayExactCrossings (overlayReceipt result)- , metricOutputCells = Just outputCells- }--composeReceipts- :: MinkowskiOperation- -> Int- -> MinkowskiReceipt- -> MinkowskiReceipt- -> MinkowskiReceipt-composeReceipts operation inputComponents firstReceipt secondReceipt =- MinkowskiReceipt- { minkowskiOperation = operation- , minkowskiInputComponents = inputComponents- , minkowskiConvexPieces =- minkowskiConvexPieces firstReceipt- + minkowskiConvexPieces secondReceipt- , minkowskiGeneratedPieces =- minkowskiGeneratedPieces firstReceipt- + minkowskiGeneratedPieces secondReceipt- , minkowskiGeneratedConvolutionEdges =- minkowskiGeneratedConvolutionEdges firstReceipt- + minkowskiGeneratedConvolutionEdges secondReceipt- , minkowskiOverlayPasses =- minkowskiOverlayPasses firstReceipt- + minkowskiOverlayPasses secondReceipt- , minkowskiExactCrossings =- minkowskiExactCrossings firstReceipt- + minkowskiExactCrossings secondReceipt- , minkowskiOutputCells = minkowskiOutputCells secondReceipt- , minkowskiExactCoordinateBitGrowth =- max- (minkowskiExactCoordinateBitGrowth firstReceipt)- (minkowskiExactCoordinateBitGrowth secondReceipt)- }--coordinateBitGrowth :: [PlanarRegion] -> PlanarRegion -> Int-coordinateBitGrowth inputs output =- max 0- ( regionCoordinateBits output- - List.foldl' (\maximumBits -> max maximumBits . regionCoordinateBits) 0 inputs- )--regionCoordinateBits :: PlanarRegion -> Int-regionCoordinateBits = List.foldl' componentBits 0 . planarRegionComponents- where- componentBits maximumBits component =- List.foldl'- loopBits- maximumBits- (polygonOuterLoop component : polygonHoleLoops component)- loopBits maximumBits =- List.foldl' pointBits maximumBits . exactLoopPoints- pointBits maximumBits point =- let (x, y) = exactPointCoordinates point- in max maximumBits (max (rationalBits x) (rationalBits y))--rationalBits :: ExactRational -> Int-rationalBits value =- max- (integerBitLength (abs (exactRationalNumerator value)))- (integerBitLength (exactRationalDenominator value))
− src-build/Moonlight/Triangulation/Overlay.hs
@@ -1,291 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE DerivingStrategies #-}---- | Exact labelled common refinement. Source boundaries descend through one--- exact segment-event plan, glue into canonical atomic constraints, and are--- admitted only when their binary64 DCEL realization preserves every exact--- relation.-module Moonlight.Triangulation.Overlay- ( BoundaryLoopRef (..)- , OverlayOperand- , BoundaryVertexRef- , boundaryVertexComponent- , boundaryVertexLoop- , boundaryVertexLocalIndex- , BoundaryEdgeRef- , boundaryEdgeComponent- , boundaryEdgeLoop- , boundaryEdgeLocalIndex- , OverlayVertexOrigin- , overlayOriginLeftVertices- , overlayOriginRightVertices- , overlayOriginLeftEdges- , overlayOriginRightEdges- , OverlayEdgeOrigin- , overlayEdgeLeftSources- , overlayEdgeRightSources- , OverlaySupport- , overlaySupportLabels- , OverlayCellSupport (..)- , OverlayCellId (..)- , OverlayCellGeometry (..)- , OverlayCell (..)- , OverlayFace (..)- , OverlayVertex (..)- , OverlayEdge (..)- , OverlayReceipt (..)- , OverlayArrangementObstruction (..)- , OverlayCellWitness (..)- , OverlayError (..)- , OverlayResult- , OverlaySelectionKind (..)- , OverlaySelectionError (..)- , overlayLayers- , overlayEmbeddedTriangulation- , overlayReceipt- , overlayCells- , overlayArrangementVertices- , overlayArrangementEdges- , overlayPlanarLayer- , overlaySelectedRegion- , overlayClosedUnion- , overlayClosedIntersection- , overlayRegularizedDifference- ) where--import Data.Bifunctor (first)-import Control.Monad (filterM)-import qualified Data.Map.Strict as Map-import qualified Data.Vector as V-import Moonlight.Triangulation.CellSet (ExactCellSet)-import qualified Moonlight.Triangulation.Dcel as Dcel-import Moonlight.Triangulation.Dcel (vertexData)-import Moonlight.Triangulation.Handles.HandleDefs- ( UndirectedEdgeId- , VertexId- )-import Moonlight.Triangulation.Handles.Iterators.FixedIterators- ( innerFaces- , undirectedEdges- , vertices- )-import Moonlight.Triangulation.Internal.CellSet (closeExactCellSetWith)-import Moonlight.Triangulation.Internal.Overlay.Arrangement- ( certifyArrangement- )-import Moonlight.Triangulation.Internal.Overlay.Resident- ( OverlayDiagonalSchedule (CanonicalOverlayDiagonals)- , edgeSupport- , faceLabels- , regionFaceLabels- , residentOverlay- , vertexSupport- )-import Moonlight.Triangulation.Internal.Overlay.Types-import Moonlight.Triangulation.Internal.Representation (Triangulation)-import Moonlight.Triangulation.Internal.Types- ( ConstraintMode (Constrained)- )-import Moonlight.Triangulation.Region- ( PlanarLayer- , PlanarRegion- , RegionPublicationError (..)- , emptyPlanarRegion- , planarLayerOutsideLabel- , planarLayerRegions- )-import Moonlight.Triangulation.Internal.Region.Publication- ( labelledPlanarLayerFromExactCoordinates- , planarLayerFromAdmittedComponents- )---- | Construct the exact common refinement and its one faithful resident DCEL.-overlayLayers- :: (Ord leftLabel, Ord rightLabel)- => PlanarLayer leftLabel- -> PlanarLayer rightLabel- -> Either- (OverlayError leftLabel rightLabel)- (OverlayResult leftLabel rightLabel)-overlayLayers leftLayer rightLayer = do- certified <- certifyArrangement leftLayer rightLayer- residentOverlay- CanonicalOverlayDiagonals- (planarLayerOutsideLabel leftLayer, planarLayerOutsideLabel rightLayer)- certified---- | The binary64 realization used by existing DCEL observations. Exact overlay--- operations accept 'OverlayResult', never this projection.-overlayEmbeddedTriangulation- :: OverlayResult leftLabel rightLabel- -> Triangulation- 'Constrained- OverlayVertex- ()- OverlayEdge- OverlayFace-overlayEmbeddedTriangulation = overlayResultTriangulation--overlayReceipt :: OverlayResult leftLabel rightLabel -> OverlayReceipt-overlayReceipt = overlayResultReceipt--overlayCells- :: OverlayResult leftLabel rightLabel- -> [(OverlayCellId, OverlayCell leftLabel rightLabel)]-overlayCells result =- V.toList- (V.imap (\index cell -> (OverlayCellId index, cell)) (overlayResultCells result))--overlayArrangementVertices- :: OverlayResult leftLabel rightLabel- -> [(VertexId, OverlayVertex)]-overlayArrangementVertices result =- let triangulation = overlayResultTriangulation result- in [(vertex, vertexData triangulation vertex) | vertex <- vertices triangulation]--overlayArrangementEdges- :: OverlayResult leftLabel rightLabel- -> [(UndirectedEdgeId, OverlayEdgeOrigin)]-overlayArrangementEdges result =- let triangulation = overlayResultTriangulation result- in [ (edge, origin)- | edge <- undirectedEdges triangulation- , OverlayBoundary origin <- [Dcel.undirectedEdgeData triangulation edge]- ]---- | Publish the already-admitted bounded cell geometry. The resident DCEL is--- a realization of these exact cells, not a second authoring source.-overlayPlanarLayer- :: (Ord leftLabel, Ord rightLabel)- => OverlayResult leftLabel rightLabel- -> PlanarLayer (leftLabel, rightLabel)-overlayPlanarLayer result =- planarLayerFromAdmittedComponents- (overlayResultOutsideLabels result)- [ ( (overlayCellLeft cell, overlayCellRight cell)- , component- )- | cell <- V.toList (overlayResultCells result)- , (overlayCellLeft cell, overlayCellRight cell)- /= overlayResultOutsideLabels result- , BoundedOverlayCell component <- [overlayCellGeometry cell]- ]---- | Publish the selected two-dimensional cells. Internal arrangement edges--- between differently labelled but jointly selected cells dissolve because--- selection precedes component descent.-overlaySelectedRegion- :: ((leftLabel, rightLabel) -> Bool)- -> OverlayResult leftLabel rightLabel- -> Either RegionPublicationError PlanarRegion-overlaySelectedRegion selected result- | selected (overlayResultOutsideLabels result) = Left RegionUnboundedSelection- | otherwise = do- published <-- labelledPlanarLayerFromExactCoordinates- False- triangulation- exactPointAt- labelFace- pure (Map.findWithDefault emptyPlanarRegion True (planarLayerRegions published))- where- triangulation = overlayResultTriangulation result- exactPointAt vertex = Right (overlayExactPoint (vertexData triangulation vertex))- labelFace face =- selected <$> regionFaceLabels result face--overlayClosedUnion- :: (Ord leftLabel, Ord rightLabel)- => (leftLabel -> Bool)- -> (rightLabel -> Bool)- -> OverlayResult leftLabel rightLabel- -> Either OverlaySelectionError ExactCellSet-overlayClosedUnion selectLeft selectRight =- selectClosedCells- ClosedUnionSelection- (\support -> supportAny selectLeft (overlaySupportLeft support) || supportAny selectRight (overlaySupportRight support))- (\leftLabel rightLabel -> selectLeft leftLabel || selectRight rightLabel)--overlayClosedIntersection- :: (Ord leftLabel, Ord rightLabel)- => (leftLabel -> Bool)- -> (rightLabel -> Bool)- -> OverlayResult leftLabel rightLabel- -> Either OverlaySelectionError ExactCellSet-overlayClosedIntersection selectLeft selectRight =- selectClosedCells- ClosedIntersectionSelection- (\support -> supportAny selectLeft (overlaySupportLeft support) && supportAny selectRight (overlaySupportRight support))- (\leftLabel rightLabel -> selectLeft leftLabel && selectRight rightLabel)--overlayRegularizedDifference- :: (leftLabel -> Bool)- -> (rightLabel -> Bool)- -> OverlayResult leftLabel rightLabel- -> Either OverlaySelectionError ExactCellSet-overlayRegularizedDifference selectLeft selectRight result =- let outsidePair = overlayResultOutsideLabels result- selectFace leftLabel rightLabel = selectLeft leftLabel && not (selectRight rightLabel)- in if uncurry selectFace outsidePair- then Left (OverlaySelectionContainsUnboundedCell RegularizedDifferenceSelection)- else closeSelectedCells [] [] selectFace result--selectClosedCells- :: (Ord leftLabel, Ord rightLabel)- => OverlaySelectionKind- -> (OverlayCellSupport leftLabel rightLabel -> Bool)- -> (leftLabel -> rightLabel -> Bool)- -> OverlayResult leftLabel rightLabel- -> Either OverlaySelectionError ExactCellSet-selectClosedCells selectionKind selectSupport selectFace result =- let outsidePair = overlayResultOutsideLabels result- triangulation = overlayResultTriangulation result- in if uncurry selectFace outsidePair- then Left (OverlaySelectionContainsUnboundedCell selectionKind)- else do- selectedVertices <-- filterM- ( fmap selectSupport- . first OverlaySelectionProvenance- . vertexSupport result- )- (vertices triangulation)- selectedEdges <-- filterM- (\edge ->- case Dcel.undirectedEdgeData triangulation edge of- OverlayDiagonal -> Right False- OverlayBoundary _ ->- selectSupport- <$> first OverlaySelectionProvenance (edgeSupport result edge))- (undirectedEdges triangulation)- closeSelectedCells selectedVertices selectedEdges selectFace result--closeSelectedCells- :: [VertexId]- -> [UndirectedEdgeId]- -> (leftLabel -> rightLabel -> Bool)- -> OverlayResult leftLabel rightLabel- -> Either OverlaySelectionError ExactCellSet-closeSelectedCells selectedVertices selectedEdges selectFace result =- let triangulation = overlayResultTriangulation result- exactPointAt vertex = Right (overlayExactPoint (vertexData triangulation vertex))- in do- selectedFaces <-- filterM- ( fmap (uncurry selectFace)- . first OverlaySelectionProvenance- . faceLabels result- )- (innerFaces triangulation)- first OverlaySelectionInvalid- ( closeExactCellSetWith- exactPointAt- triangulation- selectedVertices- selectedEdges- selectedFaces- )--supportAny :: (label -> Bool) -> OverlaySupport label -> Bool-supportAny predicate = any predicate . overlaySupportLabels
src-build/Moonlight/Triangulation/Refinement.hs view
@@ -11,7 +11,7 @@ , radiusEdgeRatioForAngle ) where -import Control.Monad.ST (ST, runST)+import Control.Monad.ST (ST) import Data.Foldable (traverse_) import qualified Data.IntSet as IntSet import Data.List (sort)@@ -30,15 +30,15 @@ , vertexPoint ) import Moonlight.Triangulation.FloodFillIterator (facesAtEvenBarrierDepth)-import Moonlight.Triangulation.Handles+import Moonlight.Triangulation.Handles.HandleDefs ( FaceId (..) , UndirectedEdgeId (..) , directedPair ) import Moonlight.Triangulation.Internal.Mutable-import Moonlight.Triangulation.Internal.Capacity (ensureCapacity)-import Moonlight.Triangulation.Internal.OperationState (freezeBuildStats, newOperationState)+import Moonlight.Triangulation.Internal.Paged (TransactionShape (DenseTransaction)) import Moonlight.Triangulation.Internal.Refinement+import Moonlight.Triangulation.Internal.Transaction (runTransaction) import Moonlight.Triangulation.Types import Moonlight.Triangulation.Validation (validateTopology) @@ -200,7 +200,6 @@ [] -> pure () let originalCount = numVertices triangulation budget = max 0 (fromMaybe (10 * max 1 originalCount) (refineMaxAdditionalVertices parameters))- maximumVerticesInteger = toInteger originalCount + toInteger budget initialExcludedFaces = if refineExcludeOuterFaces parameters then@@ -212,19 +211,13 @@ (isConstraintEdge triangulation) ] else IntSet.empty- let maximumVertices =- if maximumVerticesInteger > toInteger (maxBound :: Int)- then maxBound- else fromInteger maximumVerticesInteger- ensureCapacity maximumVertices- runST $ do- mutable <-- thawTriangulationDense- maximumVertices- triangulation- operation <- newOperationState (halfEdgeCapacity mutable)+ ( outcome+ , frozen+ , stats+ ) <-+ runTransaction id DenseTransaction triangulation budget $ \mutable operation -> do installInterfaceBarriers mutable domain- outcome <-+ refinement <- refineMutable makeVertex mutable@@ -235,30 +228,22 @@ initialSeed domain removeInterfaceBarriers mutable triangulation domain- case outcome of- Left failure -> pure (Left failure)- Right (complete, added, excluded, visited, interfaceBoundaryReads, boundaryCrossingAttempts) -> do- frozenOutcome <- freezeTriangulation mutable- case frozenOutcome of- Left obstruction -> pure (Left obstruction)- Right frozen -> do- stats <- freezeBuildStats operation- pure- ( Right- RefinementExecution- { refinementExecutionResult =- RefinementResult- { refinedTriangulation = frozen- , refinementStats = stats- , refinementAddedVertices = added- , refinementComplete = complete- , refinementExcludedFaces = V.fromList (map (FaceId . fromIntegral) excluded)- }- , refinementExecutionVisitedFaces = visited- , refinementExecutionInterfaceBoundaryReads = interfaceBoundaryReads- , refinementExecutionBoundaryCrossingAttempts = boundaryCrossingAttempts- }- )+ pure refinement+ let (complete, added, excluded, visited, interfaceBoundaryReads, boundaryCrossingAttempts) = outcome+ pure+ RefinementExecution+ { refinementExecutionResult =+ RefinementResult+ { refinedTriangulation = frozen+ , refinementStats = stats+ , refinementAddedVertices = added+ , refinementComplete = complete+ , refinementExcludedFaces = V.fromList (map (FaceId . fromIntegral) excluded)+ }+ , refinementExecutionVisitedFaces = visited+ , refinementExecutionInterfaceBoundaryReads = interfaceBoundaryReads+ , refinementExecutionBoundaryCrossingAttempts = boundaryCrossingAttempts+ } installInterfaceBarriers :: MutableDcel s vertex directed undirected face
src-capi/Moonlight/Triangulation/Foreign/Exports.hs view
@@ -3,32 +3,73 @@ module Moonlight.Triangulation.Foreign.Exports where +import Data.Int (Int64) import Data.Word (Word32)-import Foreign.C.Types (CDouble, CSize (..), CUInt (..))+import Foreign.C.Types (CChar, CDouble (..), CSize (..), CUInt (..)) import Foreign.Ptr (Ptr)-import Moonlight.Triangulation.Foreign.ABI (CObstruction)+import Moonlight.Triangulation.Foreign.ABI+ ( CMesh+ , CMinkowskiReceipt+ , CObstruction+ , CRegion+ , CStructuringElement+ ) import qualified Moonlight.Triangulation.Foreign.ABI as ABI delaunayF64 = ABI.delaunayF64 meshInsertManyF64 = ABI.meshInsertManyF64-meshUnion = ABI.meshUnion-meshIntersection = ABI.meshIntersection-meshDifference = ABI.meshDifference-meshSymmetricDifference = ABI.meshSymmetricDifference+meshSiteUnion = ABI.meshSiteUnion+meshSiteIntersection = ABI.meshSiteIntersection+meshSiteDifference = ABI.meshSiteDifference+meshSiteSymmetricDifference = ABI.meshSiteSymmetricDifference meshVertexCount = ABI.meshVertexCount meshTriangleCount = ABI.meshTriangleCount meshCopyVerticesF64 = ABI.meshCopyVerticesF64 meshCopyTrianglesU32 = ABI.meshCopyTrianglesU32 meshFree = ABI.meshFree+regionCreateF64 = ABI.regionCreateF64+regionCounts = ABI.regionCounts+regionCopyF64 = ABI.regionCopyF64+regionUnion = ABI.regionUnion+regionIntersection = ABI.regionIntersection+regionDifference = ABI.regionDifference+regionSymmetricDifference = ABI.regionSymmetricDifference+regionLocatePointF64 = ABI.regionLocatePointF64+regionMeasure = ABI.regionMeasure+regionFree = ABI.regionFree+structuringElementCreateF64 = ABI.structuringElementCreateF64+structuringElementFree = ABI.structuringElementFree+regionMinkowskiSum = ABI.regionMinkowskiSum+regionOffset = ABI.regionOffset+regionInset = ABI.regionInset+regionOpen = ABI.regionOpen+regionClose = ABI.regionClose -foreign export ccall "ml_delaunay_f64" delaunayF64 :: Ptr CDouble -> CSize -> Ptr (Ptr ()) -> Ptr CObstruction -> IO CUInt-foreign export ccall "ml_mesh_insert_many_f64" meshInsertManyF64 :: Ptr () -> Ptr CDouble -> CSize -> Ptr (Ptr ()) -> Ptr CObstruction -> IO CUInt-foreign export ccall "ml_mesh_union" meshUnion :: Ptr () -> Ptr () -> Ptr (Ptr ()) -> Ptr CObstruction -> IO CUInt-foreign export ccall "ml_mesh_intersection" meshIntersection :: Ptr () -> Ptr () -> Ptr (Ptr ()) -> Ptr CObstruction -> IO CUInt-foreign export ccall "ml_mesh_difference" meshDifference :: Ptr () -> Ptr () -> Ptr (Ptr ()) -> Ptr CObstruction -> IO CUInt-foreign export ccall "ml_mesh_symmetric_difference" meshSymmetricDifference :: Ptr () -> Ptr () -> Ptr (Ptr ()) -> Ptr CObstruction -> IO CUInt-foreign export ccall "ml_mesh_vertex_count" meshVertexCount :: Ptr () -> Ptr CSize -> Ptr CObstruction -> IO CUInt-foreign export ccall "ml_mesh_triangle_count" meshTriangleCount :: Ptr () -> Ptr CSize -> Ptr CObstruction -> IO CUInt-foreign export ccall "ml_mesh_copy_vertices_f64" meshCopyVerticesF64 :: Ptr () -> Ptr CDouble -> CSize -> Ptr CSize -> Ptr CObstruction -> IO CUInt-foreign export ccall "ml_mesh_copy_triangles_u32" meshCopyTrianglesU32 :: Ptr () -> Ptr Word32 -> CSize -> Ptr CSize -> Ptr CObstruction -> IO CUInt-foreign export ccall "ml_mesh_free" meshFree :: Ptr () -> IO ()+foreign export ccall "ml_delaunay_f64" delaunayF64 :: Ptr CDouble -> CSize -> Ptr (Ptr CMesh) -> Ptr CObstruction -> IO CUInt+foreign export ccall "ml_mesh_insert_many_f64" meshInsertManyF64 :: Ptr CMesh -> Ptr CDouble -> CSize -> Ptr (Ptr CMesh) -> Ptr CObstruction -> IO CUInt+foreign export ccall "ml_mesh_site_union" meshSiteUnion :: Ptr CMesh -> Ptr CMesh -> Ptr (Ptr CMesh) -> Ptr CObstruction -> IO CUInt+foreign export ccall "ml_mesh_site_intersection" meshSiteIntersection :: Ptr CMesh -> Ptr CMesh -> Ptr (Ptr CMesh) -> Ptr CObstruction -> IO CUInt+foreign export ccall "ml_mesh_site_difference" meshSiteDifference :: Ptr CMesh -> Ptr CMesh -> Ptr (Ptr CMesh) -> Ptr CObstruction -> IO CUInt+foreign export ccall "ml_mesh_site_symmetric_difference" meshSiteSymmetricDifference :: Ptr CMesh -> Ptr CMesh -> Ptr (Ptr CMesh) -> Ptr CObstruction -> IO CUInt+foreign export ccall "ml_mesh_vertex_count" meshVertexCount :: Ptr CMesh -> Ptr CSize -> Ptr CObstruction -> IO CUInt+foreign export ccall "ml_mesh_triangle_count" meshTriangleCount :: Ptr CMesh -> Ptr CSize -> Ptr CObstruction -> IO CUInt+foreign export ccall "ml_mesh_copy_vertices_f64" meshCopyVerticesF64 :: Ptr CMesh -> Ptr CDouble -> CSize -> Ptr CSize -> Ptr CObstruction -> IO CUInt+foreign export ccall "ml_mesh_copy_triangles_u32" meshCopyTrianglesU32 :: Ptr CMesh -> Ptr Word32 -> CSize -> Ptr CSize -> Ptr CObstruction -> IO CUInt+foreign export ccall "ml_mesh_free" meshFree :: Ptr CMesh -> IO ()+foreign export ccall "ml_region_create_f64" regionCreateF64 :: Ptr CDouble -> CSize -> Ptr CSize -> CSize -> Ptr CSize -> CSize -> Ptr (Ptr CRegion) -> Ptr CObstruction -> IO CUInt+foreign export ccall "ml_region_counts" regionCounts :: Ptr CRegion -> Ptr CSize -> Ptr CSize -> Ptr CSize -> Ptr CObstruction -> IO CUInt+foreign export ccall "ml_region_copy_f64" regionCopyF64 :: Ptr CRegion -> Ptr CDouble -> CSize -> Ptr CSize -> CSize -> Ptr CSize -> CSize -> Ptr CObstruction -> IO CUInt+foreign export ccall "ml_region_union" regionUnion :: Ptr CRegion -> Ptr CRegion -> Ptr (Ptr CRegion) -> Ptr CObstruction -> IO CUInt+foreign export ccall "ml_region_intersection" regionIntersection :: Ptr CRegion -> Ptr CRegion -> Ptr (Ptr CRegion) -> Ptr CObstruction -> IO CUInt+foreign export ccall "ml_region_difference" regionDifference :: Ptr CRegion -> Ptr CRegion -> Ptr (Ptr CRegion) -> Ptr CObstruction -> IO CUInt+foreign export ccall "ml_region_symmetric_difference" regionSymmetricDifference :: Ptr CRegion -> Ptr CRegion -> Ptr (Ptr CRegion) -> Ptr CObstruction -> IO CUInt+foreign export ccall "ml_region_locate_point_f64" regionLocatePointF64 :: Ptr CRegion -> CDouble -> CDouble -> Ptr CUInt -> Ptr CObstruction -> IO CUInt+foreign export ccall "ml_region_measure" regionMeasure :: Ptr CRegion -> Ptr Int64 -> Ptr CChar -> CSize -> Ptr CSize -> Ptr CDouble -> Ptr CDouble -> Ptr CObstruction -> IO CUInt+foreign export ccall "ml_region_free" regionFree :: Ptr CRegion -> IO ()+foreign export ccall "ml_structuring_element_create_f64" structuringElementCreateF64 :: Ptr CDouble -> CSize -> Ptr (Ptr CStructuringElement) -> Ptr CObstruction -> IO CUInt+foreign export ccall "ml_structuring_element_free" structuringElementFree :: Ptr CStructuringElement -> IO ()+foreign export ccall "ml_region_minkowski_sum" regionMinkowskiSum :: Ptr CRegion -> Ptr CRegion -> Ptr (Ptr CRegion) -> Ptr CMinkowskiReceipt -> Ptr CObstruction -> IO CUInt+foreign export ccall "ml_region_offset" regionOffset :: Ptr CStructuringElement -> Ptr CRegion -> Ptr (Ptr CRegion) -> Ptr CMinkowskiReceipt -> Ptr CObstruction -> IO CUInt+foreign export ccall "ml_region_inset" regionInset :: Ptr CStructuringElement -> Ptr CRegion -> Ptr (Ptr CRegion) -> Ptr CMinkowskiReceipt -> Ptr CObstruction -> IO CUInt+foreign export ccall "ml_region_open" regionOpen :: Ptr CStructuringElement -> Ptr CRegion -> Ptr (Ptr CRegion) -> Ptr CMinkowskiReceipt -> Ptr CObstruction -> IO CUInt+foreign export ccall "ml_region_close" regionClose :: Ptr CStructuringElement -> Ptr CRegion -> Ptr (Ptr CRegion) -> Ptr CMinkowskiReceipt -> Ptr CObstruction -> IO CUInt
+ src-cell-complex/Moonlight/Triangulation/CellComplex.hs view
@@ -0,0 +1,100 @@+{-# LANGUAGE EmptyDataDeriving #-}++-- | The admitted Moonlight triangulation cell section as a generic+-- 'CellComplex2D'. The 'ExactCellSet' remains the semantic owner: this module+-- only supplies the incidence interpretation required by downstream topology.+module Moonlight.Triangulation.CellComplex+ ( DCELComplex,+ DCELError,+ fromExactCellSet,+ )+where++import Data.IntMap.Strict qualified as IntMap+import Data.IntSet qualified as IntSet+import Moonlight.Algebra.Pure.Orientation (Orientation (..))+import Moonlight.Homology.Pure.Topology.CellComplex+ ( CellComplex2D (..),+ CellTypes (..),+ OrientedEdge (..),+ ValidateComplex2D (..),+ )+import Moonlight.Triangulation.Dcel qualified as Dcel+import Moonlight.Triangulation.Handles.HandleDefs+ ( FaceId (..),+ UndirectedEdgeId (..),+ VertexId (..),+ asUndirected,+ directedPair,+ isNormalized,+ )+import Moonlight.Triangulation.Internal.CellSet (ExactCellSet (..))+import Moonlight.Triangulation.Types (Triangulation)++-- | An incidence view of one already-validated, downward-closed exact cell+-- selection. It deliberately has no independent cell inventory.+newtype DCELComplex = DCELComplex ExactCellSet++-- | 'ExactCellSet' construction discharges every closure and handle+-- obligation before this view exists, so there are no residual validation+-- failures for the adapter to manufacture.+data DCELError+ deriving stock (Eq, Show)++fromExactCellSet :: ExactCellSet -> DCELComplex+fromExactCellSet = DCELComplex++instance CellTypes DCELComplex where+ type Vertex DCELComplex = VertexId+ type Edge DCELComplex = UndirectedEdgeId+ type Face DCELComplex = FaceId++instance CellComplex2D DCELComplex where+ vertices (DCELComplex (ExactCellSet _ selectedVertices _ _)) =+ fmap (VertexId . fromIntegral) (IntMap.keys selectedVertices)++ edges (DCELComplex (ExactCellSet _ _ selectedEdges _)) =+ fmap (UndirectedEdgeId . fromIntegral) (IntSet.toAscList selectedEdges)++ faces (DCELComplex (ExactCellSet _ _ _ selectedFaces)) =+ fmap (FaceId . fromIntegral) (IntSet.toAscList selectedFaces)++ edgeBoundary (DCELComplex (ExactCellSet triangulation _ _ _)) =+ Dcel.undirectedEndpoints triangulation++ faceBoundary (DCELComplex (ExactCellSet triangulation _ _ _)) face =+ fmap orientedBoundaryEdge (Dcel.faceDirectedEdges triangulation face)+ where+ orientedBoundaryEdge directedEdge =+ OrientedEdge+ { orientedEdge = asUndirected directedEdge,+ edgeOrientation =+ if isNormalized directedEdge+ then Positive+ else Negative+ }++ edgesAtVertex complexValue@(DCELComplex (ExactCellSet triangulation _ _ _)) vertex =+ filter (edgeContainsVertex triangulation vertex) (edges complexValue)++ facesAtEdge (DCELComplex (ExactCellSet triangulation _ _ selectedFaces)) edge =+ let (forward, backward) = directedPair edge+ selectedIncidentFace directedEdge =+ let face@(FaceId rawFace) = Dcel.incidentFace triangulation directedEdge+ in if IntSet.member (fromIntegral rawFace) selectedFaces+ then Just face+ else Nothing+ in (selectedIncidentFace forward, selectedIncidentFace backward)++instance ValidateComplex2D DCELComplex where+ type ValidationIssue DCELComplex = DCELError+ validateComplex _ = []++edgeContainsVertex ::+ Triangulation mode vertex directed undirected face ->+ VertexId ->+ UndirectedEdgeId ->+ Bool+edgeContainsVertex triangulation vertex edge =+ let (sourceVertex, targetVertex) = Dcel.undirectedEndpoints triangulation edge+ in vertex == sourceVertex || vertex == targetVertex
src-core/Moonlight/Triangulation/Internal/ExactRational.hs view
@@ -25,7 +25,7 @@ import GHC.Generics (Generic) -- | A checked wrapper around a reduced ratio with a strictly positive--- denominator. 'Data.Ratio' owns normalization, including the unique zero+-- denominator. 'Ratio' owns normalization, including the unique zero -- representation @0 / 1@. newtype ExactRational = ExactRational (Ratio Integer) deriving stock (Eq, Ord, Show, Generic)
src-core/Moonlight/Triangulation/Scalar.hs view
@@ -1,4 +1,4 @@-{-# OPTIONS_GHC -fllvm -optlo-O3 -optlc-O3 #-}+{-# OPTIONS_GHC -O3 -fllvm -optlo-O3 -optlc-O3 #-} -- | The binary64 coordinate kernel and exact predicate boundary. module Moonlight.Triangulation.Scalar@@ -86,6 +86,7 @@ :: Double -> Double -> Double -> Double -> Double -> Double -> Ordering orient2dCoordinates = filteredOrient2dDouble+{-# INLINE orient2dCoordinates #-} -- | Exact in-circle ordering of four binary64 coordinate pairs. inCircleCoordinates@@ -93,6 +94,7 @@ -> Double -> Double -> Double -> Double -> Ordering inCircleCoordinates = filteredInCircle scalarInCircleErrorBound+{-# INLINE inCircleCoordinates #-} -- | Closed exact circumradius membership at a finite, non-negative binary64 -- threshold. Invalid thresholds and collinear triples are outside.
src-dcel/Moonlight/Triangulation/FloodFillIterator.hs view
@@ -25,14 +25,18 @@ , facesAtEvenBarrierDepth , FaceComponent , faceComponentFaces+ , BoundaryOrientation (..) , BoundaryLoop+ , boundaryLoopOrientation , boundaryLoopVertices , RegionBoundary , regionBoundaryOuterLoop , regionBoundaryHoleLoops , BoundaryObstruction (..) , faceComponents+ , faceComponentsBy , labelledRegionBoundaries+ , componentBoundaryLoops , componentBoundary , RadiusSquared , mkRadiusSquared@@ -46,13 +50,19 @@ import Data.List (partition, unfoldr) import qualified Data.List as List import Data.List.NonEmpty (NonEmpty (..))+import qualified Data.List.NonEmpty as NonEmpty+import qualified Data.Map.Strict as Map import qualified Data.Sequence as Seq+import qualified Data.Set as Set import qualified Data.Vector as V import GHC.Generics (Generic) import Moonlight.Triangulation.Dcel import Moonlight.Triangulation.Handles.HandleDefs import Moonlight.Triangulation.Handles.Iterators.FixedIterators (undirectedEdges)-import Moonlight.Triangulation.Internal.BoundaryCycle (simplifyBoundaryCycle)+import Moonlight.Triangulation.Internal.BoundaryCycle+ ( simplifyBoundaryCycle+ , traceOrientedBoundaryCircuits+ ) import Moonlight.Triangulation.Math import Moonlight.Triangulation.PointLocation import Moonlight.Triangulation.Scalar (circumradiusSquaredWithinCoordinates)@@ -68,10 +78,18 @@ faceComponentFaces (FaceComponent faces) = fmap (FaceId . fromIntegral) (IntSet.toAscList faces) --- | One non-empty simple boundary loop. Outer loops are counter-clockwise and--- hole loops clockwise.-newtype BoundaryLoop = BoundaryLoop- { boundaryLoopVertices :: NonEmpty VertexId+-- | Winding carried explicitly by a simple boundary loop.+data BoundaryOrientation+ = BoundaryCounterClockwise+ | BoundaryClockwise+ deriving stock (Eq, Ord, Show, Generic)+ deriving anyclass (NFData)++-- | One non-empty simple boundary loop. Counter-clockwise loops contribute+-- filled area under nonzero winding; clockwise loops subtract holes.+data BoundaryLoop = BoundaryLoop+ { boundaryLoopOrientation :: !BoundaryOrientation+ , boundaryLoopVertices :: !(NonEmpty VertexId) } deriving stock (Eq, Show, Generic) deriving anyclass (NFData)@@ -84,7 +102,8 @@ deriving stock (Eq, Show, Generic) deriving anyclass (NFData) --- | Typed failure to descend boundary half-edges into simple polygon loops.+-- | Typed failure to descend boundary half-edges or project their oriented+-- loops into one strict polygon component. data BoundaryObstruction = BoundaryComponentFaceOutOfRange !FaceId {-# UNPACK #-} !Int | BoundaryPinch !VertexId !DirectedEdgeId !DirectedEdgeId@@ -94,6 +113,15 @@ deriving stock (Eq, Show, Generic) deriving anyclass (NFData) +-- | Complete boundary edges refined by whether each vertex has one successor.+data BoundaryGraph+ = SimpleBoundaryGraph !IntSet.IntSet !(IntMap.IntMap DirectedEdgeId)+ | PinchedBoundaryGraph+ !IntSet.IntSet+ !VertexId+ !DirectedEdgeId+ !DirectedEdgeId+ -- | An admitted finite, non-negative squared radius. newtype RadiusSquared = RadiusSquared Double deriving stock (Eq, Ord, Show, Generic)@@ -292,15 +320,46 @@ => Triangulation mode vertex directed undirected face -> (FaceId -> label) -> [(label, FaceComponent)]-faceComponents triangulation labelFace = unfoldr descend initialUnvisited+faceComponents triangulation labelFace =+ faceComponentsFromLabels triangulation labelAt (const True) where labels = V.generate (numInnerFaces triangulation) (\index -> labelFace (FaceId (fromIntegral (index + 1))))+ labelAt faceIndex = labels V.!? (faceIndex - 1)++-- | Connected components over a selected face section and admitted adjacency.+-- A missing label removes a face from the section; the edge predicate states+-- which overlaps glue. This is the common descent used by ordinary labelled+-- regions and exact-overlay charts whose representation diagonals alone may+-- connect collapsed resident faces.+faceComponentsBy+ :: Eq label+ => Triangulation mode vertex directed undirected face+ -> (FaceId -> Maybe label)+ -> (UndirectedEdgeId -> Bool)+ -> [(label, FaceComponent)]+faceComponentsBy triangulation labelFace =+ faceComponentsFromLabels triangulation labelAt+ where+ labels =+ V.generate+ (numInnerFaces triangulation)+ (\index -> labelFace (FaceId (fromIntegral (index + 1))))+ labelAt faceIndex = labels V.!? (faceIndex - 1) >>= id++faceComponentsFromLabels+ :: Eq label+ => Triangulation mode vertex directed undirected face+ -> (Int -> Maybe label)+ -> (UndirectedEdgeId -> Bool)+ -> [(label, FaceComponent)]+faceComponentsFromLabels triangulation labelAt canCross =+ unfoldr descend initialUnvisited+ where initialUnvisited = IntSet.fromRange (1, numFaces triangulation - 1)- labelAt faceIndex = labels V.!? (faceIndex - 1) descend remaining = case IntSet.minView remaining of@@ -332,7 +391,8 @@ admitAdjacent componentLabel (queued, unvisited) edge = if- adjacent /= outerFace+ canCross (asUndirected edge)+ && adjacent /= outerFace && IntSet.member adjacentIndex unvisited && labelAt adjacentIndex == Just componentLabel then@@ -344,6 +404,7 @@ adjacent@(FaceId adjacentRaw) = incidentFace triangulation (reverseEdge edge) adjacentIndex = fromIntegral adjacentRaw+{-# INLINE faceComponentsFromLabels #-} -- | Descend every equally labelled bounded-face component through the one -- authoritative boundary tracer. Components are converted independently;@@ -358,14 +419,47 @@ (\(label, component) -> (label,) <$> componentBoundary triangulation component) (faceComponents triangulation labelFace) --- | Extract all simple boundary loops of a component from the DCEL. Boundary--- half-edges retain their incident component face on the left; this gives the--- outer loop counter-clockwise and holes clockwise without a later guess.+-- | Extract the non-empty oriented boundary chain of one component. Boundary+-- half-edges retain their incident component face on the left. If several+-- boundary arms meet at one vertex, Euler descent consumes every half-edge+-- once and repeated-vertex splitting publishes finitely many simple loops.+componentBoundaryLoops+ :: Triangulation mode vertex directed undirected face+ -> FaceComponent+ -> Either BoundaryObstruction (NonEmpty BoundaryLoop)+componentBoundaryLoops triangulation component = do+ graph <- componentBoundaryGraph triangulation component+ loops <-+ case graph of+ SimpleBoundaryGraph edges outgoing ->+ traceBoundaryLoops triangulation outgoing edges+ PinchedBoundaryGraph edges _ _ _ ->+ tracePinchedBoundaryLoops triangulation edges+ case NonEmpty.nonEmpty loops of+ Just nonEmptyLoops -> Right nonEmptyLoops+ Nothing -> Left (BoundaryOuterLoopCardinality 0)++-- | Project one component into a strict polygon boundary. A pinched oriented+-- chain remains available through 'componentBoundaryLoops', but it is not one+-- lawful 'RegionBoundary' and is refused here with its first pinch witness. componentBoundary :: Triangulation mode vertex directed undirected face -> FaceComponent -> Either BoundaryObstruction RegionBoundary-componentBoundary triangulation (FaceComponent componentFaces) = do+componentBoundary triangulation component = do+ graph <- componentBoundaryGraph triangulation component+ case graph of+ PinchedBoundaryGraph _ vertex firstEdge secondEdge ->+ Left (BoundaryPinch vertex firstEdge secondEdge)+ SimpleBoundaryGraph edges outgoing ->+ traceBoundaryLoops triangulation outgoing edges+ >>= regionBoundaryFromLoops++componentBoundaryGraph+ :: Triangulation mode vertex directed undirected face+ -> FaceComponent+ -> Either BoundaryObstruction BoundaryGraph+componentBoundaryGraph triangulation (FaceComponent componentFaces) = case IntSet.lookupGE (numFaces triangulation) componentFaces of Just invalid -> Left@@ -373,16 +467,55 @@ (FaceId (fromIntegral invalid)) (numFaces triangulation) )- Nothing -> Right ()- (boundaryEdges, outgoingSuccessor) <-- IntSet.foldl'- collectFaceBoundaryEdges- (Right (IntSet.empty, IntMap.empty))- componentFaces- orientedLoops <- traceBoundaryLoops triangulation outgoingSuccessor boundaryEdges- let (outer, holes) = partition ((== GT) . fst) orientedLoops- outerLoops = fmap snd outer- holeLoops = fmap snd holes+ Nothing ->+ Right+ ( IntSet.foldl'+ collectFace+ (SimpleBoundaryGraph IntSet.empty IntMap.empty)+ componentFaces+ )+ where+ collectFace graph (face :: Int) =+ foldFaceDirectedEdges'+ triangulation+ (FaceId (fromIntegral face))+ insertBoundaryEdge+ graph++ insertBoundaryEdge graph edge+ | IntSet.member adjacentIndex componentFaces = graph+ | otherwise =+ case graph of+ PinchedBoundaryGraph edges pinchVertex firstEdge secondEdge ->+ PinchedBoundaryGraph+ (insertEdge edges)+ pinchVertex+ firstEdge+ secondEdge+ SimpleBoundaryGraph edges outgoing ->+ case IntMap.lookup vertexIndex outgoing of+ Nothing ->+ SimpleBoundaryGraph+ (insertEdge edges)+ (IntMap.insert vertexIndex edge outgoing)+ Just previousEdge ->+ PinchedBoundaryGraph+ (insertEdge edges)+ vertex+ previousEdge+ edge+ where+ FaceId adjacent = incidentFace triangulation (reverseEdge edge)+ adjacentIndex = fromIntegral adjacent+ vertex@(VertexId rawVertex) = origin triangulation edge+ vertexIndex = fromIntegral rawVertex+ DirectedEdgeId rawEdge = edge+ insertEdge = IntSet.insert (fromIntegral rawEdge)++regionBoundaryFromLoops+ :: [BoundaryLoop]+ -> Either BoundaryObstruction RegionBoundary+regionBoundaryFromLoops loops = case outerLoops of [outerLoop] -> Right@@ -392,35 +525,44 @@ } _ -> Left (BoundaryOuterLoopCardinality (length outerLoops)) where- collectFaceBoundaryEdges boundaryGraph face =- foldFaceDirectedEdges'- triangulation- (FaceId (fromIntegral face))- insertBoundaryEdge- boundaryGraph+ (outerLoops, holeLoops) =+ partition+ ((== BoundaryCounterClockwise) . boundaryLoopOrientation)+ loops - insertBoundaryEdge outcome edge = do- graph@(edges, successors) <- outcome- let FaceId adjacent = incidentFace triangulation (reverseEdge edge)- if IntSet.member (fromIntegral adjacent) componentFaces- then Right graph- else- let vertex@(VertexId rawVertex) = origin triangulation edge- vertexIndex = fromIntegral rawVertex- DirectedEdgeId rawEdge = edge- in case IntMap.lookup vertexIndex successors of- Just previousEdge -> Left (BoundaryPinch vertex previousEdge edge)- Nothing ->- Right- ( IntSet.insert (fromIntegral rawEdge) edges- , IntMap.insert vertexIndex edge successors- )+tracePinchedBoundaryLoops+ :: Triangulation mode vertex directed undirected face+ -> IntSet.IntSet+ -> Either BoundaryObstruction [BoundaryLoop]+tracePinchedBoundaryLoops triangulation boundaryEdges = do+ cycles <-+ traceOrientedBoundaryCircuits+ (origin triangulation)+ (destination triangulation)+ BoundaryCycleDidNotClose+ outgoingByVertex+ orientedEdges+ traverse+ (simplifyBoundaryLoop triangulation . NonEmpty.toList)+ cycles+ where+ orientedEdges =+ Set.fromDistinctAscList+ ( fmap+ (DirectedEdgeId . fromIntegral)+ (IntSet.toAscList boundaryEdges)+ )+ outgoingByVertex =+ Set.foldr+ (\edge -> Map.insertWith (<>) (origin triangulation edge) [edge])+ Map.empty+ orientedEdges traceBoundaryLoops :: Triangulation mode vertex directed undirected face -> IntMap.IntMap DirectedEdgeId -> IntSet.IntSet- -> Either BoundaryObstruction [(Ordering, BoundaryLoop)]+ -> Either BoundaryObstruction [BoundaryLoop] traceBoundaryLoops triangulation outgoingByVertex = descend [] where descend loops unvisited =@@ -429,8 +571,8 @@ Just (rawStart, _) -> do let start = DirectedEdgeId (fromIntegral rawStart) (vertices, remaining) <- traceCycle start start unvisited []- oriented <- simplifyBoundaryLoop triangulation vertices- descend (oriented : loops) remaining+ loop <- simplifyBoundaryLoop triangulation vertices+ descend (loop : loops) remaining traceCycle start current unvisited reversedVertices = let DirectedEdgeId rawCurrent = current@@ -449,10 +591,19 @@ simplifyBoundaryLoop :: Triangulation mode vertex directed undirected face -> [VertexId]- -> Either BoundaryObstruction (Ordering, BoundaryLoop)-simplifyBoundaryLoop triangulation =- fmap (\(orientation, vertices) -> (orientation, BoundaryLoop vertices))- . simplifyBoundaryCycle BoundaryLoopDegenerate redundant winding key+ -> Either BoundaryObstruction BoundaryLoop+simplifyBoundaryLoop triangulation vertices = do+ (windingOrder, simplifiedVertices) <-+ simplifyBoundaryCycle+ BoundaryLoopDegenerate+ redundant+ winding+ key+ vertices+ case windingOrder of+ GT -> Right (BoundaryLoop BoundaryCounterClockwise simplifiedVertices)+ LT -> Right (BoundaryLoop BoundaryClockwise simplifiedVertices)+ EQ -> Left (BoundaryLoopDegenerate (NonEmpty.toList simplifiedVertices)) where point vertex = vertexPoint triangulation vertex redundant previousVertex current nextVertex =
− src-dcel/Moonlight/Triangulation/Handles.hs
@@ -1,11 +0,0 @@--- | The handle surface as one import: identifiers, their dynamic views, and--- the iterator family.-module Moonlight.Triangulation.Handles- ( module Moonlight.Triangulation.Handles.HandleDefs- , module Moonlight.Triangulation.Handles.Dynamic- , module Moonlight.Triangulation.Handles.Iterators- ) where--import Moonlight.Triangulation.Handles.Dynamic-import Moonlight.Triangulation.Handles.HandleDefs-import Moonlight.Triangulation.Handles.Iterators
− src-dcel/Moonlight/Triangulation/Handles/Iterators.hs
@@ -1,13 +0,0 @@--- | The iterator family over a mesh: whole-mesh ranges, incidence fans, the--- circular walk they are built from, and the hull.-module Moonlight.Triangulation.Handles.Iterators- ( module Moonlight.Triangulation.Handles.Iterators.CircularIterator- , module Moonlight.Triangulation.Handles.Iterators.DynamicIterators- , module Moonlight.Triangulation.Handles.Iterators.FixedIterators- , module Moonlight.Triangulation.Handles.Iterators.HullIterator- ) where--import Moonlight.Triangulation.Handles.Iterators.CircularIterator-import Moonlight.Triangulation.Handles.Iterators.DynamicIterators-import Moonlight.Triangulation.Handles.Iterators.FixedIterators-import Moonlight.Triangulation.Handles.Iterators.HullIterator
src-dcel/Moonlight/Triangulation/Internal/BoundaryCycle.hs view
@@ -1,8 +1,8 @@--- | The shared algebra for simplifying and classifying an already-traced--- boundary cycle. Topology traversal remains in 'FloodFillIterator'; this--- module owns only fixed-point collinear deletion and winding observation.+-- | Shared pure algebra for descending oriented boundary graphs into simple+-- cycles, then simplifying and classifying those cycles. module Moonlight.Triangulation.Internal.BoundaryCycle- ( simplifyBoundaryCycle+ ( traceOrientedBoundaryCircuits+ , simplifyBoundaryCycle , rotateCycleLeast , rotateCycleLeastBy , consecutivePairs@@ -13,10 +13,128 @@ , cyclicTriples ) where -import Data.List.NonEmpty (NonEmpty (..)) import Data.List (tails) import qualified Data.List as List+import Data.List.NonEmpty (NonEmpty (..)) import qualified Data.List.NonEmpty as NonEmpty+import qualified Data.Map.Strict as Map+import Data.Map.Strict (Map)+import qualified Data.Set as Set+import Data.Set (Set)++-- | Consume every supplied oriented edge exactly once and split point contacts+-- into vertex-simple cycles. The outgoing lists carry the caller's required+-- local angular or identifier order. A malformed graph is translated directly+-- through the caller's obstruction constructor; this shared worker owns no+-- disposable error vocabulary.+traceOrientedBoundaryCircuits+ :: (Ord vertex, Ord edge)+ => (edge -> vertex)+ -> (edge -> vertex)+ -> (edge -> edge -> obstruction)+ -> Map vertex [edge]+ -> Set edge+ -> Either obstruction [NonEmpty vertex]+traceOrientedBoundaryCircuits edgeOrigin edgeDestination obstruction outgoing edges =+ descend edges []+ where+ descend remaining cycles =+ case Set.lookupMin remaining of+ Nothing -> Right (reverse cycles)+ Just seed ->+ let (untraced, circuitEdges) =+ eulerCircuit remaining (edgeOrigin seed)+ in case NonEmpty.nonEmpty circuitEdges of+ Nothing -> Left (obstruction seed seed)+ Just circuit ->+ case splitCircuit circuit of+ Left failedEdge -> Left (obstruction seed failedEdge)+ Right splitCycles ->+ descend+ untraced+ (reverse (NonEmpty.toList splitCycles) <> cycles)++ -- Hierholzer descent: delete each chosen edge, then prepend it while+ -- backtracking. No graph-state stream or branch search is materialized.+ eulerCircuit remaining start = walk remaining start [] []+ where+ walk untraced current incomingEdges circuit =+ case+ List.find+ (`Set.member` untraced)+ (Map.findWithDefault [] current outgoing) of+ Just edge ->+ walk+ (Set.delete edge untraced)+ (edgeDestination edge)+ (edge : incomingEdges)+ circuit+ Nothing ->+ case incomingEdges of+ edge : previousEdges ->+ walk+ untraced+ (edgeOrigin edge)+ previousEdges+ (edge : circuit)+ [] -> (untraced, circuit)++ -- The resident path stays simple. Closing at a resident vertex emits and+ -- removes exactly that suffix, partitioning the Euler circuit into cycles.+ splitCircuit circuit =+ walk+ start+ 0+ (Map.singleton start 0)+ []+ []+ (NonEmpty.toList circuit)+ where+ start = edgeOrigin (NonEmpty.head circuit)++ walk current depth depths reversedEdges cycles remaining =+ case remaining of+ [] ->+ case+ ( depth+ , reversedEdges+ , NonEmpty.nonEmpty (reverse cycles)+ ) of+ (0, [], Just simpleCycles) -> Right simpleCycles+ _ -> Left (NonEmpty.last circuit)+ edge : rest+ | edgeOrigin edge /= current -> Left edge+ | otherwise ->+ let target = edgeDestination edge+ in case Map.lookup target depths of+ Nothing ->+ walk+ target+ (depth + 1)+ (Map.insert target (depth + 1) depths)+ (edge : reversedEdges)+ cycles+ rest+ Just repeatedDepth ->+ let suffixLength = depth - repeatedDepth+ suffixEdges = take suffixLength reversedEdges+ simpleCycle =+ fmap edgeOrigin+ ( NonEmpty.reverse+ (edge :| suffixEdges)+ )+ in walk+ target+ repeatedDepth+ ( foldr+ (Map.delete . edgeDestination)+ depths+ suffixEdges+ )+ (drop suffixLength reversedEdges)+ (simpleCycle : cycles)+ rest+{-# INLINABLE traceOrientedBoundaryCircuits #-} -- | Remove precisely the vertices admitted by @isRedundant@ until a fixed -- point is reached, then classify the winding at the least keyed retained
− src-dcel/Moonlight/Triangulation/Internal/DcelOperations.hs
@@ -1,83 +0,0 @@-{-# LANGUAGE AllowAmbiguousTypes #-}-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE NamedFieldPuns #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeApplications #-}---- | The topology-mutating primitives, gathered from their role modules.-module Moonlight.Triangulation.Internal.DcelOperations- ( setupFirstVertex- , setupSecondVertex- , splitLineEdge- , extendLine- , lineToArea- , insertIntoFace- , insertOnEdge- , ReservedSweepCells- , SweepCellCursor- , SweepInsertion (..)- , reserveSweepCells- , initialSweepCellCursor- , commitReservedSweepConnections- , insertOutsideHull- , insertOutsideHullAtEdge- , closeOuterTurn- , closeOuterTurnReserved- , fixHullConvexity- , flipEdge- , legalizeScratch- , legalizeStarEdge- , legalizeEdges- , legalizeCavityFanScratch- , LegalizationLaw (..)- , seedStarScratch- , seedGenericEdges- , drainLegalization- , CandidateDiscipline (..)- , isFlippableEdge- , collectLineChain- ) where--import Moonlight.Triangulation.Internal.DcelOperations.CandidateArena- ( CandidateDiscipline (..)- , seedGenericEdges- , seedStarScratch- )-import Moonlight.Triangulation.Internal.DcelOperations.Chain- ( collectLineChain- , extendLine- , lineToArea- , setupFirstVertex- , setupSecondVertex- , splitLineEdge- )-import Moonlight.Triangulation.Internal.DcelOperations.FlipRewrite (flipEdge)-import Moonlight.Triangulation.Internal.DcelOperations.FlipRule- ( LegalizationLaw (..)- , isFlippableEdge- )-import Moonlight.Triangulation.Internal.DcelOperations.Hull- ( ReservedSweepCells- , SweepCellCursor- , SweepInsertion (..)- , closeOuterTurn- , closeOuterTurnReserved- , commitReservedSweepConnections- , fixHullConvexity- , initialSweepCellCursor- , insertOutsideHull- , insertOutsideHullAtEdge- , reserveSweepCells- )-import Moonlight.Triangulation.Internal.DcelOperations.Legalize- ( legalizeCavityFanScratch- , legalizeEdges- , legalizeScratch- , legalizeStarEdge- )-import Moonlight.Triangulation.Internal.DcelOperations.Normalize (drainLegalization)-import Moonlight.Triangulation.Internal.DcelOperations.Subdivide- ( insertIntoFace- , insertOnEdge- )
src-dcel/Moonlight/Triangulation/Internal/DcelOperations/CandidateArena.hs view
@@ -21,6 +21,7 @@ ( LegalizationArena (..) , OperationState , legalizationArena+ , legalizationArenaLength , readScratch , storeLegalizationArena )@@ -89,7 +90,3 @@ where !current = MUV.length values {-# INLINE growLegalizationArena #-}--legalizationArenaLength :: LegalizationArena s -> Int-legalizationArenaLength (LegalizationArena values) = MUV.length values-{-# INLINE legalizationArenaLength #-}
src-dcel/Moonlight/Triangulation/Internal/DcelOperations/FlipRule.hs view
@@ -11,10 +11,10 @@ , diagonalFires , illegalDiagonal , isFlippableEdge- , orderedPair ) where import Control.Monad.ST (ST)+import Moonlight.Triangulation.Internal.BoundaryCycle (orderedPair) import Moonlight.Triangulation.Internal.DcelOperations.Twin (reverseIndex) import Moonlight.Triangulation.Internal.Mutable ( MutableDcel@@ -124,9 +124,3 @@ data LegalizationLaw = ValidMesh | CavityRepair {-# UNPACK #-} !Int--orderedPair :: Ord a => a -> a -> (a, a)-orderedPair left right- | left <= right = (left, right)- | otherwise = (right, left)-{-# INLINE orderedPair #-}
src-dcel/Moonlight/Triangulation/Internal/DcelOperations/Hull.hs view
@@ -4,7 +4,7 @@ {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-}-{-# OPTIONS_GHC -fllvm -optlo-O3 -optlc-O3 #-}+{-# OPTIONS_GHC -O3 -fllvm -optlo-O3 -optlc-O3 #-} -- | Growth outside the hull: visible ranges, turn closure, and convexity repair. module Moonlight.Triangulation.Internal.DcelOperations.Hull
src-dcel/Moonlight/Triangulation/Internal/DcelOperations/Legalize.hs view
@@ -4,7 +4,7 @@ {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-}-{-# OPTIONS_GHC -fllvm -optlo-O3 -optlc-O3 #-}+{-# OPTIONS_GHC -O3 -fllvm -optlo-O3 -optlc-O3 #-} -- | The seeding entry points that drive one legalization epoch. module Moonlight.Triangulation.Internal.DcelOperations.Legalize@@ -42,6 +42,7 @@ , OperationState , addCounter , legalizationArena+ , legalizationArenaLength , maxCounter , readScratch , storeLegalizationArena@@ -134,7 +135,3 @@ MUV.unsafeWrite values 0 (packIndex edge) drainDenseUnconstrainedStarLegalization dense arena 1 vertex {-# NOINLINE legalizeDenseStarEdgeInArena #-}--legalizationArenaLength :: LegalizationArena s -> Int-legalizationArenaLength (LegalizationArena values) = MUV.length values-{-# INLINE legalizationArenaLength #-}
src-dcel/Moonlight/Triangulation/Internal/DcelOperations/Normalize.hs view
@@ -4,7 +4,7 @@ {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-}-{-# OPTIONS_GHC -fllvm -optlo-O3 -optlc-O3 #-}+{-# OPTIONS_GHC -O3 -fllvm -optlo-O3 -optlc-O3 #-} -- | The normalization procedure of the flip rewrite system. module Moonlight.Triangulation.Internal.DcelOperations.Normalize@@ -41,6 +41,7 @@ , LegalizationArena (..) , OperationState , legalizationArena+ , legalizationArenaLength , storeLegalizationArena ) import Moonlight.Triangulation.Internal.PackedIndex (packIndex)@@ -347,7 +348,3 @@ GenericCandidates ValidMesh {-# INLINE drainDenseUnconstrainedGenericLegalization #-}--legalizationArenaLength :: LegalizationArena s -> Int-legalizationArenaLength (LegalizationArena values) = MUV.length values-{-# INLINE legalizationArenaLength #-}
src-dcel/Moonlight/Triangulation/Internal/Mutable.hs view
@@ -211,6 +211,11 @@ -- | Tight fresh planar capacity. Circle sweep allocates monotonically and no -- rewrite abandons a cell, so Euler's bounds plus seed slack are authoritative: -- at most @6n@ directed edges and @2n@ faces.+--+-- The separated seam merge is the second lawful caller. It copies two planar+-- sources and then only adds: stitching creates seam cells, legalization+-- rewires without allocating, and nothing is abandoned. Its peak is therefore+-- its published result, a planar triangulation on the summed vertex count. planarDcelCapacity :: Int -> DcelCapacity planarDcelCapacity maximumVertices = let !vertices = max 1 maximumVertices
src-dcel/Moonlight/Triangulation/Internal/OperationState.hs view
@@ -15,6 +15,7 @@ , OperationState , newOperationState , legalizationArena+ , legalizationArenaLength , storeLegalizationArena , writeScratch , readScratch@@ -127,6 +128,10 @@ legalizationArena :: OperationState s -> ST s (LegalizationArena s) legalizationArena = readSTRef . osLegalizationArena {-# INLINE legalizationArena #-}++legalizationArenaLength :: LegalizationArena s -> Int+legalizationArenaLength (LegalizationArena values) = MUV.length values+{-# INLINE legalizationArenaLength #-} storeLegalizationArena :: OperationState s -> LegalizationArena s -> ST s () storeLegalizationArena = writeSTRef . osLegalizationArena
src-dcel/Moonlight/Triangulation/Validation.hs view
@@ -14,6 +14,7 @@ import Data.List (nub) import qualified Data.IntSet as IntSet import Moonlight.Triangulation.Internal.BoxedPaged (boxedPagedLength)+import Moonlight.Triangulation.Internal.BoundaryCycle (orderedPair) import Moonlight.Triangulation.Internal.Paged (pagedFoldl', pagedLength, pagedUnsafeIndex) import Moonlight.Triangulation.Dcel import Moonlight.Triangulation.Handles.HandleDefs@@ -264,6 +265,3 @@ where angle left right opposite = acos (clamp ((left * left + right * right - opposite * opposite) / (2 * left * right))) * 180 / pi clamp = max (-1) . min 1--orderedPair :: Ord a => a -> a -> (a, a)-orderedPair a b = if a <= b then (a, b) else (b, a)
src-dcel/Moonlight/Triangulation/Valuation.hs view
@@ -27,6 +27,7 @@ , ValuationError (..) , cellValuations , regionValuations+ , planarValuationsPerimeter , cellSetPerimeter , regionPerimeter ) where@@ -226,18 +227,20 @@ -> Either ValuationError ExactLengthMeasurement cellSetPerimeter cellSet | exactCellSetIsFaceClosure cellSet =- cellValuations cellSet >>= conventionalPerimeter+ cellValuations cellSet >>= planarValuationsPerimeter | otherwise = Left ValuationCellSetNotPureRegion regionPerimeter :: PlanarRegion -> Either ValuationError ExactLengthMeasurement-regionPerimeter region = regionValuations region >>= conventionalPerimeter+regionPerimeter region = regionValuations region >>= planarValuationsPerimeter -conventionalPerimeter+-- | Derive conventional boundary length from an already-computed intrinsic+-- valuation without traversing the source geometry again.+planarValuationsPerimeter :: PlanarValuations -> Either ValuationError ExactLengthMeasurement-conventionalPerimeter valuations =+planarValuationsPerimeter valuations = measureLength (scaleLengthExpression 2 (exactLengthExpression (valuationIntrinsic1 valuations)))
− src-dual/Moonlight/Triangulation/HintGenerator.hs
@@ -1,492 +0,0 @@-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE DerivingStrategies #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE NamedFieldPuns #-}---- | Reusable point-location hints and their topology-preserving maintenance.-module Moonlight.Triangulation.HintGenerator- ( LastUsedHint- , emptyLastUsedHint- , lastUsedHint- , rememberVertex- , HierarchyHint- , defaultHierarchyBranchFactor- , buildHierarchyHint- , hierarchyHint- , hierarchyBranchFactor- , hierarchyBaseCount- , hierarchyLevelCount- , hierarchyVertexCount- , updateHierarchyAfterInsertion- , updateHierarchyAfterRemoval- , rebuildHierarchyHint- , removeManyWithHierarchy- ) where--import qualified Data.Vector as V-import Control.DeepSeq (NFData)-import Data.List (sort)-import Data.Word (Word32)-import Moonlight.Triangulation.BulkLoad (delaunay, insert)-import Moonlight.Triangulation.Dcel (numUndirectedEdges, numVertices, undirectedEndpoints, vertexPoint)-import Moonlight.Triangulation.Handles.HandleDefs (UndirectedEdgeId (..), VertexId (..))-import Moonlight.Triangulation.Interpolation (nearestNeighbor)-import Moonlight.Triangulation.Math (canonicalPoint, validatePoint)-import Moonlight.Triangulation.Removal (RemovalOutcome, removalTriangulation, removeVertex)-import Moonlight.Triangulation.Session (removeManyAtNear, withSession)-import Moonlight.Triangulation.Types-import GHC.Generics (Generic)---- | Most recently admitted vertex, suitable as the next descent seed.-newtype LastUsedHint = LastUsedHint (Maybe VertexId)- deriving stock (Show)- deriving newtype (Eq, Ord)---- | A last-used hint with no remembered vertex.-emptyLastUsedHint :: LastUsedHint-emptyLastUsedHint = LastUsedHint Nothing---- | Project a remembered vertex into a location hint.-lastUsedHint :: LastUsedHint -> Maybe LocationHint-lastUsedHint (LastUsedHint vertex) = VertexHint <$> vertex---- | Replace the remembered vertex.-rememberVertex :: VertexId -> LastUsedHint -> LastUsedHint-rememberVertex vertex _ = LastUsedHint (Just vertex)---- | A Delaunay hierarchy for logarithmic expected random point location.--- The first vector element is the finest sparse level; the final element is--- the coarsest. The base triangulation is not duplicated.------ The levels are nested by a single arithmetic law rather than by a stored--- correspondence. Level @i@ holds every @branch^(i+1)@-th base vertex in base--- order, so a level-local handle @j@ names handle @j * branch@ one level finer--- — and at level 0 the finer level is the base mesh itself, under the very--- same multiplication. Descent therefore never rediscovers a handle it has--- already computed, and no level carries an index vector.------ 'hierarchyBaseCount' is the base cardinality the levels were sampled from.--- It is the hierarchy's claim about which triangulation it answers for, and--- every maintenance entry is stated against it: a base whose cardinality is not--- the one recorded here plus the movement the operation performs is not the--- base this hierarchy describes, and is rebuilt for rather than patched.-data HierarchyHint = HierarchyHint- { hierarchyBranchFactor :: {-# UNPACK #-} !Int- -- ^ Sampling stride between adjacent hierarchy levels.- , hierarchyBaseCount :: {-# UNPACK #-} !Int- -- ^ Cardinality of the base mesh described by the hierarchy.- , hierarchyLevels :: !(V.Vector (Triangulation 'Unconstrained (Point) () () ()))- }- deriving stock (Generic)- deriving anyclass (NFData)--instance Show (HierarchyHint) where- showsPrec precedence hierarchy =- showParen (precedence > 10) $- showString "HierarchyHint "- . shows (hierarchyBranchFactor hierarchy)- . showString " "- . shows (hierarchyBaseCount hierarchy)- . showString " "- . shows (hierarchyLevelCount hierarchy)- . showString " "- . shows (hierarchyVertexCount hierarchy)--instance Eq (HierarchyHint) where- left == right =- hierarchyBranchFactor left == hierarchyBranchFactor right- && hierarchyBaseCount left == hierarchyBaseCount right- && hierarchyLevelCount left == hierarchyLevelCount right- && V.and (V.zipWith sameSparseMesh (hierarchyLevels left) (hierarchyLevels right))---- Two sparse levels are the same hint when they carry the same points and the--- same undirected edges. Half-edge index labelling records the order--- construction happened to visit, so a level rebuilt from scratch and a level--- extended in place are structurally equal while their arrays are not.-sameSparseMesh- :: Triangulation 'Unconstrained (Point) () () ()- -> Triangulation 'Unconstrained (Point) () () ()- -> Bool-sameSparseMesh left right =- numVertices left == numVertices right- && numUndirectedEdges left == numUndirectedEdges right- && meshPoints left == meshPoints right- && meshEdges left == meshEdges right- where- meshPoints- :: Triangulation mode vertex directed undirected face- -> [Point]- meshPoints triangulation =- sort [vertexPoint triangulation (VertexId (fromIntegral index)) | index <- [0 .. numVertices triangulation - 1]]-- meshEdges- :: Triangulation mode vertex directed undirected face- -> [(Point, Point)]- meshEdges triangulation =- sort- [ if from <= to then (from, to) else (to, from)- | index <- [0 .. numUndirectedEdges triangulation - 1]- , let (fromVertex, toVertex) = undirectedEndpoints triangulation (UndirectedEdgeId (fromIntegral index))- from = vertexPoint triangulation fromVertex- to = vertexPoint triangulation toVertex- ]---- | Default sampling stride between hierarchy levels.-defaultHierarchyBranchFactor :: Int-defaultHierarchyBranchFactor = 16---- | Number of stored sparse levels.-hierarchyLevelCount :: HierarchyHint -> Int-hierarchyLevelCount = V.length . hierarchyLevels---- | Total vertices retained across every sparse level.-hierarchyVertexCount :: HierarchyHint -> Int-hierarchyVertexCount =- V.foldl' (\total level -> total + numVertices level) 0 . hierarchyLevels---- | Build nested sparse Delaunay levels. A branch factor of 16 mirrors Spade's--- default and gives O(log n) expected descent on uniformly distributed input.------ Each level is bulk loaded from the sampled points in base order. Base--- vertices carry pairwise distinct positions, so the load deduplicates nothing--- and assigns local handle @j@ to sample @j@ — which is what makes the nesting--- law on 'hierarchyLevels' an identity rather than a lookup.-buildHierarchyHint- :: Int- -> Triangulation mode vertex directed undirected face- -> Either BuildError (HierarchyHint)-buildHierarchyHint requestedBranch triangulation =- HierarchyHint branch count <$> V.mapM buildLevel levelDivisors- where- !branch = max 2 requestedBranch- !count = numVertices triangulation- levelDivisors =- V.unfoldr- (\candidate ->- case candidate of- Nothing -> Nothing- Just divisor ->- let !population = samplePopulation count divisor- next =- if population <= 1- then Nothing- else Just (safeMultiply divisor branch)- in Just (divisor, next)- )- (if count <= 0 then Nothing else Just branch)-- buildLevel divisor =- buildTriangulation- <$> delaunay- unitElementDefaults- ( V.generate- (samplePopulation count divisor)- (\index ->- vertexPoint- triangulation- (VertexId (fromIntegral (index * divisor)))- )- )---- | Descend from the coarsest sparse triangulation. The handle a level returns--- is carried to the next finer level by one multiplication, and the same--- multiplication at level 0 names the base vertex. Nothing is relocated: the--- coarse answer is not searched for again, it is computed.-hierarchyHint :: HierarchyHint -> QueryPoint -> Maybe LocationHint-hierarchyHint HierarchyHint{hierarchyBranchFactor, hierarchyLevels} query =- VertexHint <$> descend (V.length hierarchyLevels - 1) Nothing- where- !branch = fromIntegral hierarchyBranchFactor :: Word32-- descend !levelIndex !coarse- | levelIndex < 0 = Nothing- | otherwise =- case nearestNeighbor (hierarchyLevels V.! levelIndex) coarse query of- Nothing -> Nothing- Just (VertexId local, _) ->- let !finer = VertexId (local * branch)- in if levelIndex == 0 then Just finer else descend (levelIndex - 1) (Just finer)----- | Update the nested hierarchy from an insertion's own report: the point the--- insertion was asked for, the handle it answered, and whether it created a--- site. Only the levels selected by the branch divisibility rule are changed.--- Unaffected levels are structurally shared.------ No triangulation is named. The hierarchy walks its own levels and nothing--- else, so a base was only ever a lookup table for three facts — its--- cardinality, the stored position of the new vertex, and the position of--- vertex zero — and every one of them is in the report or already in the--- levels, because level-local handle zero is base handle zero at every level.--- A caller maintaining the hierarchy across a run of insertions therefore--- never has to publish a mesh to be allowed to speak to it, which is the whole--- cost of the arrangement this replaces: one full arena copy per step, paid--- only to name the thing that was just edited.------ An insertion that found its point already present created no site, and a--- hierarchy valid for a triangulation is valid for that same triangulation, so--- the answer is the argument, unexamined.-updateHierarchyAfterInsertion- :: HierarchyHint- -> Point- -> VertexId- -> InsertionDisposition- -> Either BuildError (HierarchyHint)-updateHierarchyAfterInsertion hierarchy requested vertex disposition =- case disposition of- AlreadyPresent -> Right hierarchy- Inserted- | vertexIndex vertex /= baseIndex ->- Left- ( HierarchyInsertionHandleMismatch- (VertexId (fromIntegral baseIndex))- vertex- )- | otherwise -> do- updated <- updateLevels branch (V.toList (hierarchyLevels hierarchy))- topped <- ensureSingletonTop updated- pure- hierarchy- { hierarchyBaseCount = baseIndex + 1- , hierarchyLevels = V.fromList topped- }- where- !branch = hierarchyBranchFactor hierarchy- !baseIndex = hierarchyBaseCount hierarchy- -- A level holds what the mesh stores, not what the caller wrote: storage- -- rounds a signed zero, and a level built from the unrounded point would- -- settle a distance tie against a different handle.- !point = canonicalPoint requested-- -- An update is the only operation that can break the nesting law, so the law- -- is stated here as an equation rather than trusted: a level admitted to the- -- new base vertex must already hold exactly @baseIndex / divisor@ samples,- -- and must receive the vertex at its end.- updateLevels !_ [] = Right []- updateLevels !divisor levels@(level : rest)- | baseIndex `rem` divisor /= 0 = Right levels- | safeMultiply (numVertices level) divisor /= baseIndex =- Left- ( HierarchyLevelPopulationMismatch- divisor- (numVertices level)- baseIndex- )- | otherwise = do- inserted <- insert level point- if vertexIndex (insertionVertex inserted) /= numVertices level- then- Left- ( HierarchyInsertionHandleMismatch- (VertexId (fromIntegral (numVertices level)))- (insertionVertex inserted)- )- else (insertionTriangulation inserted :) <$> updateLevels (safeMultiply divisor branch) rest-- -- With no levels at all the base was empty, so the vertex just appended is- -- vertex zero. Otherwise vertex zero is the finest level's local vertex- -- zero, under the same law that makes the descent a multiplication.- ensureSingletonTop [] = pure <$> singletonLevel point- ensureSingletonTop levels@(finest : _) =- case reverse levels of- top : _- | numVertices top <= 1 -> Right levels- | otherwise ->- (\first -> levels ++ [first])- <$> singletonLevel (vertexPoint finest (VertexId 0))- [] -> Right levels---- | Repair the nested hierarchy from a removal's swap report — the slot--- compaction freed and the position of the vertex it moved into that slot, or--- 'Nothing' when the removal took the last vertex and compaction moved--- nothing — rather than rebuilding because removal renumbers.------ No triangulation is named, for the reason 'updateHierarchyAfterInsertion'--- gives: the only base position this repair cannot find in its own levels is--- the relocated vertex's, and that is what the report carries.------ Swap compaction moves exactly one vertex — the former last one, into the slot--- the removed vertex vacated — so a level's sample sequence changes in at most--- one place, and which place is decided by the two divisibilities the level's--- divisor gives the freed slot and the vacated last index:------ * neither is sampled: the level, and every coarser level above it, is--- untouched, because a divisor that divides neither index is divided by no--- multiple of itself either;--- * the vacated index is sampled and the freed slot is not: the level loses its--- last sample and nothing else;--- * both are sampled: the level loses its last sample and that sample's point--- lands in the freed slot's local position — which is the level's own swap--- removal, mirroring the base's;--- * the freed slot is sampled and the vacated index is not: the level keeps its--- population and substitutes the relocated position at an interior local--- slot. No removal expresses a substitution, so that level is rebuilt — from--- its own points and the reported one, never from a mesh.------ The last case is the only one that pays a build, and it is the rarest: it--- needs the freed slot to be sampled and the vacated index not to be.-updateHierarchyAfterRemoval- :: HierarchyHint- -> Maybe (VertexId, Point)- -> Either BuildError (HierarchyHint)-updateHierarchyAfterRemoval hierarchy swap- | baseCount <= 0 = Left (RemovalEmptyTriangulation (maybe (VertexId 0) fst swap))- | otherwise = do- repaired <- repairLevels branch (V.toList (hierarchyLevels hierarchy))- pure- hierarchy- { hierarchyBaseCount = surviving- , hierarchyLevels = V.fromList (levelsThroughSingleton repaired)- }- where- !branch = hierarchyBranchFactor hierarchy- !baseCount = hierarchyBaseCount hierarchy- !surviving = baseCount - 1- -- The freed slot is the removed vertex's own index, and it is where the- -- former last vertex now stands. A removal that took the last vertex frees- -- no slot and reports none, and its removed index is that last index.- !freedSlot = maybe surviving (vertexIndex . fst) swap- !vacatedIndex = surviving-- -- The nesting law is stated here as an equation for the same reason the- -- insertion path states it: a repair is the other operation that can break- -- it. A level the removal reaches must hold exactly the samples the- -- pre-removal base owed it.- repairLevels !_ [] = Right []- repairLevels !divisor levels@(level : rest)- | not freedSampled && not vacatedSampled = Right levels- | numVertices level /= population =- Left- ( HierarchyLevelPopulationMismatch- divisor- (numVertices level)- baseCount- )- | otherwise =- case swap of- -- The two divisibilities differ only when the two indices do, so a- -- level reaching the substitution has a relocation to substitute:- -- the guard cannot hold while compaction moved nothing.- Just (_, relocated)- | freedSampled && not vacatedSampled -> do- substituted <- substituteSample level (freedSlot `quot` divisor) relocated- (substituted :) <$> repairLevels (safeMultiply divisor branch) rest- _ -> do- shrunk <- removeVertex level (VertexId (fromIntegral localSample))- (removalTriangulation shrunk :) <$> repairLevels (safeMultiply divisor branch) rest- where- !freedSampled = freedSlot `rem` divisor == 0- !vacatedSampled = vacatedIndex `rem` divisor == 0- !population = samplePopulation baseCount divisor- !localSample- | freedSampled = freedSlot `quot` divisor- | otherwise = population - 1-- -- A build stops at the first level holding one sample, and removal only- -- shrinks populations, so the shape a rebuild would answer with is this list- -- cut after its first singleton.- levelsThroughSingleton- :: [Triangulation mode vertex directed undirected face]- -> [Triangulation mode vertex directed undirected face]- levelsThroughSingleton [] = []- levelsThroughSingleton (level : rest)- | numVertices level <= 0 = []- | numVertices level <= 1 = [level]- | otherwise = level : levelsThroughSingleton rest---- | The coarsest level a growing hierarchy needs: one sample, the base's--- vertex zero.-singletonLevel- :: Point- -> Either BuildError (Triangulation 'Unconstrained (Point) () () ())-singletonLevel origin =- buildTriangulation <$> delaunay unitElementDefaults (V.singleton origin)---- | The level a substitution asks for: the same samples in the same local--- order, one slot carrying the relocated position instead of the one that--- left. Stated over the level's own points, so no mesh is consulted.-substituteSample- :: Triangulation 'Unconstrained (Point) () () ()- -> Int- -> Point- -> Either BuildError (Triangulation 'Unconstrained (Point) () () ())-substituteSample level localSlot relocated =- buildTriangulation- <$> delaunay- unitElementDefaults- ( V.generate- (numVertices level)- (\index ->- if index == localSlot- then relocated- else vertexPoint level (VertexId (fromIntegral index))- )- )---- | Rebuild a hierarchy after an operation that may renumber vertices, such as--- removal. The branch factor remains canonical.-rebuildHierarchyHint- :: HierarchyHint- -> Triangulation mode vertex directed undirected face- -> Either BuildError (HierarchyHint)-rebuildHierarchyHint hierarchy = buildHierarchyHint (hierarchyBranchFactor hierarchy)---- | Remove many points, each locate starting from the hierarchy's nearest--- sample instead of the mesh boundary. One session publishes once; the--- hierarchy is rebuilt against the surviving mesh and returned alongside it.-removeManyWithHierarchy- :: HierarchyHint- -> Triangulation mode vertex directed undirected face- -> V.Vector (Point)- -> Either- BuildError- ( V.Vector (Maybe (RemovalOutcome vertex))- , Triangulation mode vertex directed undirected face- , HierarchyHint- )-removeManyWithHierarchy hierarchy triangulation points = do- queryPoints <- traverse (validatePoint Nothing) points- let guesses = fmap hierarchyGuess queryPoints- (outcomes, surviving, _) <-- withSession triangulation 0 (removeManyAtNear guesses points)- repaired <- rebuildHierarchyHint hierarchy surviving- pure (outcomes, surviving, repaired)- where- hierarchyGuess queryPoint =- case hierarchyHint hierarchy queryPoint of- Just (VertexHint vertex) -> Just vertex- _ -> Nothing--safeMultiply :: Int -> Int -> Int-safeMultiply left right- | left > maxBound `quot` right = maxBound- | otherwise = left * right---- | How many samples a divisor takes from a base of this size. Sampling takes--- index zero and every @divisor@-th index after it; the direct--- vector generator above states the same ceiling without constructing an--- intermediate handle list.-samplePopulation :: Int -> Int -> Int-samplePopulation count divisor- | count <= 0 = 0- | otherwise = 1 + (count - 1) `quot` divisor--vertexIndex :: VertexId -> Int-vertexIndex (VertexId value) = fromIntegral value---- The hierarchy is consulted once per query from another package, so its--- polymorphic entries expose their unfoldings for the same reason the search--- itself does.------ The two level constructors are listed for a second reason, and the list is--- not complete without them: they are overloaded and the entries call them, so--- an entry specialised in the consumer that reaches an unspecialised--- constructor threads the dictionary right back into the build it was--- specialised to avoid. Only a stable unfolding is a specialisation candidate--- across a package boundary; the optimised one GHC publishes on its own is not.--- These two were 'where' bindings before they were named, and a 'where' binding--- is specialised with the function that encloses it — so naming them is what--- put the dictionary in, and this is what takes it back out.
− src-embedding/Moonlight/Triangulation/Internal/Overlay/Embedding.hs
@@ -1,502 +0,0 @@-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE DerivingStrategies #-}---- | Bounded certification of local binary64 embedding obligations for a--- declared exact arrangement draft.-module Moonlight.Triangulation.Internal.Overlay.Embedding- ( DraftVertexId (..)- , DraftSegmentId (..)- , DraftSourceId (..)- , DraftIncidence (..)- , DraftNeighborhood (..)- , ExactArrangementDraft (..)- , DraftReference (..)- , OverlayEmbeddingObstruction (..)- , EmbeddingObligation (..)- , EmbeddingResidual- , residualUndischargedObligations- , milestoneOneResidual- , LocalEmbeddingCertificate (..)- , certifyLocalEmbedding- ) where--import Control.DeepSeq (NFData)-import Data.List (sort, tails)-import qualified Data.List as List-import Data.List.NonEmpty (NonEmpty (..))-import qualified Data.List.NonEmpty as NonEmpty-import Data.Map.Strict (Map)-import qualified Data.Map.Strict as Map-import GHC.Generics (Generic)-import Moonlight.Triangulation.Exact- ( ExactPoint- , SegmentRelation- , exactOrient2d- , exactPointToEmbeddingCandidate- , exactSegmentRelation- )-import Moonlight.Triangulation.Internal.BoundaryCycle (cyclePairs)-import Moonlight.Triangulation.Internal.ExactRational (ExactRational)-import qualified Moonlight.Triangulation.Math as Math- ( orient2d- , segmentRelation- )-import Moonlight.Triangulation.Types- ( Point (..)- , PointValidationError- )---- | Draft-local vertex label.-newtype DraftVertexId = DraftVertexId Int- deriving stock (Eq, Ord, Show, Generic)- deriving anyclass (NFData)---- | Draft-local atomic-segment label.-newtype DraftSegmentId = DraftSegmentId Int- deriving stock (Eq, Ord, Show, Generic)- deriving anyclass (NFData)---- | Draft-local source-segment label.-newtype DraftSourceId = DraftSourceId Int- deriving stock (Eq, Ord, Show, Generic)- deriving anyclass (NFData)---- | A declared relation between two atomic draft segments.-data DraftIncidence = DraftIncidence- { -- | First declared atomic segment.- draftIncidenceFirstSegment :: !DraftSegmentId- , -- | Second declared atomic segment.- draftIncidenceSecondSegment :: !DraftSegmentId- , -- | Relation declared to hold in both exact and rounded geometry.- draftIncidenceRelation :: !SegmentRelation- }- deriving stock (Eq, Ord, Show, Generic)- deriving anyclass (NFData)---- | One exact vertex and the cyclic neighbor order declared around it.-data DraftNeighborhood = DraftNeighborhood- { -- | Center of the declared local rotation.- draftNeighborhoodCenter :: !DraftVertexId- , -- | Neighbors in cyclic rotation order.- draftNeighborhoodNeighbors :: !(NonEmpty DraftVertexId)- }- deriving stock (Eq, Ord, Show, Generic)- deriving anyclass (NFData)---- | A hand-built exact arrangement draft. It contains only declared local--- structure; no arrangement or global crossing search is derived here.-data ExactArrangementDraft = ExactArrangementDraft- { -- | Exact coordinates keyed by draft-local vertex identity.- draftVertices :: !(Map DraftVertexId ExactPoint)- , -- | Atomic segment endpoint identities.- draftSegments :: !(Map DraftSegmentId (DraftVertexId, DraftVertexId))- , -- | Exact split parameters and vertices in source-segment order.- draftSourceMemberships :: !(Map DraftSourceId [(ExactRational, DraftVertexId)])- , -- | Segment relations declared to remain invariant under projection.- draftIncidences :: ![DraftIncidence]- , -- | Cyclic local rotations declared to remain orientation-stable.- draftNeighborhoods :: ![DraftNeighborhood]- }- deriving stock (Eq, Show, Generic)- deriving anyclass (NFData)---- | The typed identity of a missing draft reference.-data DraftReference- = DraftVertexReference !DraftVertexId- | DraftSegmentReference !DraftSegmentId- deriving stock (Eq, Ord, Show, Generic)- deriving anyclass (NFData)---- | A witness that prevents local embedding certification.-data OverlayEmbeddingObstruction- = DraftReferenceMissing !DraftReference- | VertexProjectionRefused !DraftVertexId !PointValidationError- | RoundedVerticesCollide !DraftVertexId !DraftVertexId !Point- | SplitOrderNotPreserved !DraftSourceId !DraftVertexId !DraftVertexId- | IncidenceRelationChanged- !DraftSegmentId- !DraftSegmentId- !SegmentRelation- !SegmentRelation- !SegmentRelation- | NeighborhoodOrientationChanged- !DraftVertexId- !DraftVertexId- !DraftVertexId- !Ordering- !Ordering- | GlobalRelationAdded- !DraftSegmentId- !DraftSegmentId- !SegmentRelation- | GlobalRelationRemoved- !DraftSegmentId- !DraftSegmentId- !SegmentRelation- | GlobalRelationChanged- !DraftSegmentId- !DraftSegmentId- !SegmentRelation- !SegmentRelation- | ProjectedSegmentCollapsed- !DraftSegmentId- !DraftVertexId- !DraftVertexId- deriving stock (Eq, Show, Generic)- deriving anyclass (NFData)---- | An embedding obligation not discharged by the bounded local certifier.-data EmbeddingObligation- = GlobalNoNewCrossing- deriving stock (Bounded, Enum, Eq, Ord, Show, Generic)- deriving anyclass (NFData)---- | A nonempty collection of obligations deferred to a later owner.-newtype EmbeddingResidual = EmbeddingResidual (NonEmpty EmbeddingObligation)- deriving stock (Eq, Ord, Show, Generic)- deriving anyclass (NFData)---- | Read the obligations that remain explicitly undischarged.-residualUndischargedObligations- :: EmbeddingResidual- -> NonEmpty EmbeddingObligation-residualUndischargedObligations (EmbeddingResidual obligations) = obligations---- | The Milestone 1 residual: the arrangement sweep has not yet proved global--- absence of new crossings.-milestoneOneResidual :: EmbeddingResidual-milestoneOneResidual = EmbeddingResidual (GlobalNoNewCrossing :| [])---- | A certificate for exactly four local obligations on a declared draft:--- vertex distinctness, source split order, declared incidences, and local--- neighborhood orientation. Global absence of new crossings is unproved until--- the Milestone 2 arrangement sweep supplies the complete obligation set.-data LocalEmbeddingCertificate = LocalEmbeddingCertificate- { -- | Number of rounded vertex-pair distinctness checks discharged.- certificateRoundedVertexDistinctnessCount :: !Int- , -- | Number of adjacent source split-order checks discharged.- certificateSplitOrderPreservationCount :: !Int- , -- | Number of declared incidence checks discharged.- certificateIncidenceRelationPreservationCount :: !Int- , -- | Number of consecutive neighborhood orientation checks discharged.- certificateNeighborhoodOrientationStabilityCount :: !Int- , -- | The candidate projection certified by the four local obligations.- certificateRoundedVertices :: !(Map DraftVertexId Point)- , -- | The necessarily nonempty global obligation residual.- certificateResidual :: !EmbeddingResidual- }- deriving stock (Eq, Show, Generic)- deriving anyclass (NFData)---- | Certify the four bounded local embedding obligations, collecting every--- witness within each obligation and preserving obligation order.-certifyLocalEmbedding- :: ExactArrangementDraft- -> Either (NonEmpty OverlayEmbeddingObstruction) LocalEmbeddingCertificate-certifyLocalEmbedding draft =- case NonEmpty.nonEmpty (structuralObstructions draft) of- Just obstructions -> Left obstructions- Nothing ->- case projectVertices (draftVertices draft) of- Invalid obstructions -> Left obstructions- Valid projectedVertices ->- certifyProjectedDraft draft projectedVertices--data Validation value- = Invalid !(NonEmpty OverlayEmbeddingObstruction)- | Valid value--instance Functor Validation where- fmap _ (Invalid obstructions) = Invalid obstructions- fmap transform (Valid value) = Valid (transform value)--instance Applicative Validation where- pure = Valid- Invalid left <*> Invalid right = Invalid (left <> right)- Invalid obstructions <*> Valid _ = Invalid obstructions- Valid _ <*> Invalid obstructions = Invalid obstructions- Valid transform <*> Valid value = Valid (transform value)--invalid :: OverlayEmbeddingObstruction -> Validation value-invalid obstruction = Invalid (obstruction :| [])--data ProjectedVertex = ProjectedVertex- { projectedExactPoint :: !ExactPoint- , projectedRoundedPoint :: !Point- }--data ResolvedSegment = ResolvedSegment- { resolvedSegmentId :: !DraftSegmentId- , resolvedSegmentFrom :: !ResolvedVertex- , resolvedSegmentTo :: !ResolvedVertex- }--data ResolvedVertex = ResolvedVertex- { resolvedVertexId :: !DraftVertexId- , resolvedProjectedVertex :: !ProjectedVertex- }--data ResolvedMembership = ResolvedMembership- { resolvedMembershipParameter :: !ExactRational- , resolvedMembershipVertex :: !ResolvedVertex- }--structuralObstructions- :: ExactArrangementDraft- -> [OverlayEmbeddingObstruction]-structuralObstructions draft =- segmentEndpointObstructions- <> sourceMembershipObstructions- <> incidenceObstructions- <> neighborhoodObstructions- where- vertices = draftVertices draft- segments = draftSegments draft- missingVertex vertexId =- [ DraftReferenceMissing (DraftVertexReference vertexId)- | Map.notMember vertexId vertices- ]- missingSegment segmentId =- [ DraftReferenceMissing (DraftSegmentReference segmentId)- | Map.notMember segmentId segments- ]- segmentEndpointObstructions =- concatMap- (\(_, (from, to)) -> missingVertex from <> missingVertex to)- (Map.toAscList segments)- sourceMembershipObstructions =- concatMap- (concatMap (missingVertex . snd) . snd)- (Map.toAscList (draftSourceMemberships draft))- incidenceObstructions =- concatMap- ( \incidence ->- missingSegment (draftIncidenceFirstSegment incidence)- <> missingSegment (draftIncidenceSecondSegment incidence)- )- (draftIncidences draft)- neighborhoodObstructions =- concatMap- ( \neighborhood ->- missingVertex (draftNeighborhoodCenter neighborhood)- <> concatMap missingVertex (draftNeighborhoodNeighbors neighborhood)- )- (draftNeighborhoods draft)--projectVertices- :: Map DraftVertexId ExactPoint- -> Validation (Map DraftVertexId ProjectedVertex)-projectVertices =- Map.traverseWithKey- ( \vertexId point ->- case exactPointToEmbeddingCandidate point of- Left projectionError ->- invalid (VertexProjectionRefused vertexId projectionError)- Right roundedPoint -> Valid (ProjectedVertex point roundedPoint)- )--certifyProjectedDraft- :: ExactArrangementDraft- -> Map DraftVertexId ProjectedVertex- -> Either (NonEmpty OverlayEmbeddingObstruction) LocalEmbeddingCertificate-certifyProjectedDraft draft projectedVertices =- case resolvedFailures of- Left obstruction -> Left (obstruction :| [])- Right localFailures ->- case NonEmpty.nonEmpty (collisionFailures <> localFailures) of- Just obstructions -> Left obstructions- Nothing ->- Right- LocalEmbeddingCertificate- { certificateRoundedVertexDistinctnessCount = distinctnessCount- , certificateSplitOrderPreservationCount = splitOrderCount- , certificateIncidenceRelationPreservationCount = incidenceCount- , certificateNeighborhoodOrientationStabilityCount = neighborhoodCount- , certificateRoundedVertices = Map.map projectedRoundedPoint projectedVertices- , certificateResidual = milestoneOneResidual- }- where- collisionFailures = roundedVertexCollisionObstructions projectedVertices- resolvedFailures = do- splitFailures <- traverse (uncurry resolveSource) (Map.toAscList (draftSourceMemberships draft))- incidenceFailures <- traverse resolveIncidence (draftIncidences draft)- neighborhoodFailures <- traverse resolveNeighborhood (draftNeighborhoods draft)- pure (concat splitFailures <> concat incidenceFailures <> concat neighborhoodFailures)- resolveVertex vertexId =- case Map.lookup vertexId projectedVertices of- Nothing -> Left (DraftReferenceMissing (DraftVertexReference vertexId))- Just projectedVertex -> Right (ResolvedVertex vertexId projectedVertex)- resolveSegment segmentId =- case Map.lookup segmentId (draftSegments draft) of- Nothing -> Left (DraftReferenceMissing (DraftSegmentReference segmentId))- Just (from, to) ->- ResolvedSegment segmentId- <$> resolveVertex from- <*> resolveVertex to- resolveSource sourceId memberships =- splitOrderObstructions sourceId- <$> traverse- ( \(parameter, vertexId) ->- ResolvedMembership parameter <$> resolveVertex vertexId- )- memberships- resolveIncidence incidence =- incidenceRelationObstructions- (draftIncidenceRelation incidence)- <$> resolveSegment (draftIncidenceFirstSegment incidence)- <*> resolveSegment (draftIncidenceSecondSegment incidence)- resolveNeighborhood neighborhood =- neighborhoodOrientationObstructions- <$> resolveVertex (draftNeighborhoodCenter neighborhood)- <*> traverse resolveVertex (draftNeighborhoodNeighbors neighborhood)- vertexCount = Map.size projectedVertices- distinctnessCount = vertexCount * (vertexCount - 1) `quot` 2- splitOrderCount =- sum- ( map- (max 0 . subtract 1 . length)- (Map.elems (draftSourceMemberships draft))- )- incidenceCount = length (draftIncidences draft)- neighborhoodCount =- sum- ( map- (length . draftNeighborhoodNeighbors)- (draftNeighborhoods draft)- )--roundedVertexCollisionObstructions- :: Map DraftVertexId ProjectedVertex- -> [OverlayEmbeddingObstruction]-roundedVertexCollisionObstructions projectedVertices =- [ RoundedVerticesCollide leftId rightId roundedPoint- | (roundedPoint, vertexIds) <- Map.toAscList verticesByRoundedPoint- , (leftId : remainingIds) <- tails (sort vertexIds)- , rightId <- remainingIds- ]- where- verticesByRoundedPoint =- Map.fromListWith (<>)- [ (projectedRoundedPoint projectedVertex, [vertexId])- | (vertexId, projectedVertex) <- Map.toAscList projectedVertices- ]--splitOrderObstructions- :: DraftSourceId- -> [ResolvedMembership]- -> [OverlayEmbeddingObstruction]-splitOrderObstructions sourceId memberships =- case memberships of- firstMembership : secondMembership : remainingMemberships ->- let finalMembership =- List.foldl' (\_ current -> current) secondMembership remainingMemberships- sourceFrom = roundedMembershipPoint firstMembership- sourceTo = roundedMembershipPoint finalMembership- in [ SplitOrderNotPreserved- sourceId- (resolvedVertexId (resolvedMembershipVertex leftMembership))- (resolvedVertexId (resolvedMembershipVertex rightMembership))- | (leftMembership, rightMembership) <-- zip- memberships- (drop 1 memberships)- , compare- (resolvedMembershipParameter leftMembership)- (resolvedMembershipParameter rightMembership)- /= roundedOrderAlong- sourceFrom- sourceTo- (roundedMembershipPoint leftMembership)- (roundedMembershipPoint rightMembership)- ]- _ -> []--roundedMembershipPoint :: ResolvedMembership -> Point-roundedMembershipPoint =- projectedRoundedPoint- . resolvedProjectedVertex- . resolvedMembershipVertex--roundedOrderAlong :: Point -> Point -> Point -> Point -> Ordering-roundedOrderAlong- (Point sourceFromX sourceFromY)- (Point sourceToX sourceToY)- (Point leftX leftY)- (Point rightX rightY) =- let directionX = sourceToX - sourceFromX- directionY = sourceToY - sourceFromY- in if abs directionX >= abs directionY- then- if directionX >= 0- then compare leftX rightX- else compare rightX leftX- else- if directionY >= 0- then compare leftY rightY- else compare rightY leftY--incidenceRelationObstructions- :: SegmentRelation- -> ResolvedSegment- -> ResolvedSegment- -> [OverlayEmbeddingObstruction]-incidenceRelationObstructions declaredRelation firstSegment secondSegment =- [ IncidenceRelationChanged- (resolvedSegmentId firstSegment)- (resolvedSegmentId secondSegment)- declaredRelation- exactRelation- roundedRelation- | exactRelation /= declaredRelation || roundedRelation /= declaredRelation- ]- where- exactRelation =- relationFor exactSegmentRelation projectedExactPoint firstSegment secondSegment- roundedRelation =- relationFor Math.segmentRelation projectedRoundedPoint firstSegment secondSegment--relationFor- :: (point -> point -> point -> point -> SegmentRelation)- -> (ProjectedVertex -> point)- -> ResolvedSegment- -> ResolvedSegment- -> SegmentRelation-relationFor relation project firstSegment secondSegment =- relation- (project (resolvedProjectedVertex (resolvedSegmentFrom firstSegment)))- (project (resolvedProjectedVertex (resolvedSegmentTo firstSegment)))- (project (resolvedProjectedVertex (resolvedSegmentFrom secondSegment)))- (project (resolvedProjectedVertex (resolvedSegmentTo secondSegment)))--neighborhoodOrientationObstructions- :: ResolvedVertex- -> NonEmpty ResolvedVertex- -> [OverlayEmbeddingObstruction]-neighborhoodOrientationObstructions center neighbors =- [ NeighborhoodOrientationChanged- (resolvedVertexId center)- (resolvedVertexId leftNeighbor)- (resolvedVertexId rightNeighbor)- exactOrientation- roundedOrientation- | (leftNeighbor, rightNeighbor) <-- cyclePairs neighbors- , let exactOrientation =- exactOrient2d- (exactVertexPoint center)- (exactVertexPoint leftNeighbor)- (exactVertexPoint rightNeighbor)- roundedOrientation =- Math.orient2d- (roundedVertexPoint center)- (roundedVertexPoint leftNeighbor)- (roundedVertexPoint rightNeighbor)- , exactOrientation /= roundedOrientation- ]--exactVertexPoint :: ResolvedVertex -> ExactPoint-exactVertexPoint = projectedExactPoint . resolvedProjectedVertex--roundedVertexPoint :: ResolvedVertex -> Point-roundedVertexPoint = projectedRoundedPoint . resolvedProjectedVertex
src-ffi/Moonlight/Triangulation/Foreign/ABI.hs view
@@ -1,27 +1,57 @@ {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TypeFamilies #-} module Moonlight.Triangulation.Foreign.ABI ( CObstruction (..)+ , CMinkowskiReceipt (..)+ , CMesh+ , CRegion+ , CStructuringElement , delaunayF64 , meshInsertManyF64- , meshUnion- , meshIntersection- , meshDifference- , meshSymmetricDifference+ , -- | Exported for measurement. @meshInsertManyF64@ is a pointer boundary and+ -- cannot be timed against a Haskell arm without dragging marshalling into+ -- one side only; this is the same route with the pointers already resolved.+ insertGeometryBatch+ , meshSiteUnion+ , meshSiteIntersection+ , meshSiteDifference+ , meshSiteSymmetricDifference , meshVertexCount , meshTriangleCount , meshCopyVerticesF64 , meshCopyTrianglesU32 , meshFree+ , regionCreateF64+ , regionCounts+ , regionCopyF64+ , regionUnion+ , regionIntersection+ , regionDifference+ , regionSymmetricDifference+ , regionLocatePointF64+ , regionMeasure+ , regionFree+ , structuringElementCreateF64+ , structuringElementFree+ , regionMinkowskiSum+ , regionOffset+ , regionInset+ , regionOpen+ , regionClose ) where import Control.Exception (SomeException, displayException, try) import Control.Monad (void)-import Data.Foldable (traverse_)+import Data.Bifunctor (first)+import Data.Int (Int64)+import Data.List.NonEmpty (NonEmpty (..))+import qualified Data.List.NonEmpty as NonEmpty+import qualified Data.Map.Strict as Map import Data.Word (Word32, Word64) import Foreign.C.String (peekCString, withCStringLen)-import Foreign.C.Types (CDouble (..), CSize (..), CUInt (..))+import Foreign.C.Types (CChar, CDouble (..), CSize (..), CUInt (..)) import Foreign.Marshal.Utils (copyBytes, fillBytes) import Foreign.Ptr (Ptr, castPtr, nullPtr, plusPtr) import Foreign.StablePtr@@ -35,11 +65,20 @@ import Foreign.Storable (Storable (..)) import qualified Data.Vector as V import qualified Moonlight.Triangulation as T-import Moonlight.Triangulation.Math (validatePoint)+import Moonlight.Triangulation.Math (canonicalPoint, validatePoint) import qualified Moonlight.Triangulation.Session as Session type GeometryMesh = T.DelaunayTriangulation () +data CMesh+data CRegion+data CStructuringElement++type family HandleValue carrier where+ HandleValue CMesh = GeometryMesh+ HandleValue CRegion = T.PlanarRegion+ HandleValue CStructuringElement = T.StructuringElement+ data CObstruction = CObstruction { obstructionCode :: !Word32 , obstructionCoordinateError :: !Word32@@ -56,7 +95,7 @@ instance Storable CObstruction where sizeOf _ = 320- alignment _ = alignment (undefined :: Word64)+ alignment _ = alignment (0 :: Word64) peek pointer = do obstructionCode <- peekByteOff pointer 0 obstructionCoordinateError <- peekByteOff pointer 4@@ -79,19 +118,59 @@ pokeByteOff pointer 40 obstructionSecondValue pokeByteOff pointer 48 obstructionPointX pokeByteOff pointer 56 obstructionPointY- let messagePointer = castPtr pointer `plusPtr` 64+ let messagePointer = castPtr pointer `plusPtr` 64 :: Ptr CChar fillBytes messagePointer 0 256 withCStringLen obstructionMessage $ \(source, lengthInBytes) -> copyBytes messagePointer source (min 255 lengthInBytes) +-- | Fixed-width projection of the existing Haskell morphology receipt.+data CMinkowskiReceipt = CMinkowskiReceipt+ { receiptOperation :: !Word32+ , receiptInputComponents :: !Word64+ , receiptConvexPieces :: !Word64+ , receiptGeneratedPieces :: !Word64+ , receiptGeneratedConvolutionEdges :: !Word64+ , receiptOverlayPasses :: !Word64+ , receiptExactCrossings :: !Word64+ , receiptOutputCells :: !Word64+ , receiptExactCoordinateBitGrowth :: !Word64+ }+ deriving stock (Eq, Show)++instance Storable CMinkowskiReceipt where+ sizeOf _ = 72+ alignment _ = alignment (0 :: Word64)+ peek pointer = do+ receiptOperation <- peekByteOff pointer 0+ receiptInputComponents <- peekByteOff pointer 8+ receiptConvexPieces <- peekByteOff pointer 16+ receiptGeneratedPieces <- peekByteOff pointer 24+ receiptGeneratedConvolutionEdges <- peekByteOff pointer 32+ receiptOverlayPasses <- peekByteOff pointer 40+ receiptExactCrossings <- peekByteOff pointer 48+ receiptOutputCells <- peekByteOff pointer 56+ receiptExactCoordinateBitGrowth <- peekByteOff pointer 64+ pure CMinkowskiReceipt {..}+ poke pointer CMinkowskiReceipt {..} = do+ pokeByteOff pointer 0 receiptOperation+ pokeByteOff pointer 4 (0 :: Word32)+ pokeByteOff pointer 8 receiptInputComponents+ pokeByteOff pointer 16 receiptConvexPieces+ pokeByteOff pointer 24 receiptGeneratedPieces+ pokeByteOff pointer 32 receiptGeneratedConvolutionEdges+ pokeByteOff pointer 40 receiptOverlayPasses+ pokeByteOff pointer 48 receiptExactCrossings+ pokeByteOff pointer 56 receiptOutputCells+ pokeByteOff pointer 64 receiptExactCoordinateBitGrowth+ data AbiFailure = AbiFailure !CUInt !CObstruction -statusOk, statusNullPointer, statusCountOverflow, statusBufferTooSmall, statusBuildObstruction, statusRuntimeFailure :: CUInt+statusOk, statusNullPointer, statusCountOverflow, statusBufferTooSmall, statusGeometryObstruction, statusRuntimeFailure :: CUInt statusOk = 0 statusNullPointer = 1 statusCountOverflow = 2 statusBufferTooSmall = 3-statusBuildObstruction = 4+statusGeometryObstruction = 4 statusRuntimeFailure = 5 emptyObstruction :: CObstruction@@ -113,6 +192,10 @@ apiFailure status code message = AbiFailure status emptyObstruction {obstructionCode = code, obstructionMessage = message} +geometryFailure :: Show obstruction => Word32 -> obstruction -> AbiFailure+geometryFailure code obstruction =+ apiFailure statusGeometryObstruction code (show obstruction)+ nullPointerFailure :: String -> AbiFailure nullPointerFailure label = apiFailure statusNullPointer 100 (label <> " must not be null") @@ -180,99 +263,127 @@ pure (T.Point x y) ) -prepareMeshOutput :: Ptr (Ptr ()) -> IO (Either AbiFailure ())-prepareMeshOutput pointer =+prepareHandleOutput :: Ptr (Ptr carrier) -> IO (Either AbiFailure ())+prepareHandleOutput pointer = case requirePointer "result" pointer of Left failure -> pure (Left failure) Right () -> poke pointer nullPtr >> pure (Right ()) -publishMesh :: Ptr (Ptr ()) -> GeometryMesh -> IO ()-publishMesh output mesh = do- stable <- newStablePtr mesh- poke output (castStablePtrToPtr stable)+publishHandle :: Ptr (Ptr carrier) -> HandleValue carrier -> IO ()+publishHandle output value = do+ stable <- newStablePtr value+ poke output (castPtr (castStablePtrToPtr stable)) -produceMesh :: Ptr (Ptr ()) -> IO (Either AbiFailure (Either T.BuildError GeometryMesh)) -> IO (Either AbiFailure ())-produceMesh output obtain = do- prepared <- prepareMeshOutput output+produceHandle+ :: Ptr (Ptr carrier)+ -> IO (Either AbiFailure (HandleValue carrier))+ -> IO (Either AbiFailure ())+produceHandle output obtain = do+ prepared <- prepareHandleOutput output case prepared of Left failure -> pure (Left failure) Right () -> do outcome <- obtain case outcome of Left failure -> pure (Left failure)- Right (Left obstruction) ->- pure (Left (AbiFailure statusBuildObstruction (buildErrorObstruction obstruction)))- Right (Right mesh) -> publishMesh output mesh >> pure (Right ())+ Right value -> publishHandle output value >> pure (Right ()) -delaunayF64 :: Ptr CDouble -> CSize -> Ptr (Ptr ()) -> Ptr CObstruction -> IO CUInt+dereferenceHandle :: Ptr carrier -> IO (HandleValue carrier)+dereferenceHandle pointer = deRefStablePtr (castPtrToStablePtr (castPtr pointer))++freeHandle :: Ptr carrier -> IO ()+freeHandle pointer+ | pointer == nullPtr = pure ()+ | otherwise = freeStablePtr (castPtrToStablePtr (castPtr pointer) :: StablePtr ())++buildFailure :: T.BuildError -> AbiFailure+buildFailure obstruction =+ AbiFailure statusGeometryObstruction (buildErrorObstruction obstruction)++delaunayF64 :: Ptr CDouble -> CSize -> Ptr (Ptr CMesh) -> Ptr CObstruction -> IO CUInt delaunayF64 coordinates rawCount output obstructionPointer =- runBoundary obstructionPointer $ produceMesh output $ do+ runBoundary obstructionPointer $ produceHandle output $ do case checkedCount 2 rawCount of Left failure -> pure (Left failure)- Right count -> fmap (fmap T.delaunayGeometry) (readPoints coordinates count)+ Right count -> do+ points <- readPoints coordinates count+ pure (points >>= first buildFailure . T.delaunayGeometry) -meshInsertManyF64 :: Ptr () -> Ptr CDouble -> CSize -> Ptr (Ptr ()) -> Ptr CObstruction -> IO CUInt+meshInsertManyF64 :: Ptr CMesh -> Ptr CDouble -> CSize -> Ptr (Ptr CMesh) -> Ptr CObstruction -> IO CUInt meshInsertManyF64 meshPointer coordinates rawCount output obstructionPointer =- runBoundary obstructionPointer $ produceMesh output $ do- case (requirePointer "mesh" meshPointer, checkedCount 2 rawCount) of- (Left failure, _) -> pure (Left failure)- (_, Left failure) -> pure (Left failure)- (Right (), Right count) -> do- pointsOutcome <- readPoints coordinates count- case pointsOutcome of+ runBoundary obstructionPointer $ produceHandle output $ do+ case requirePointer "mesh" meshPointer >> checkedCount 2 rawCount of+ Left failure -> pure (Left failure)+ Right count -> do+ points <- readPoints coordinates count+ case points of Left failure -> pure (Left failure)- Right points -> do- mesh <- dereferenceMesh meshPointer- pure (Right (insertGeometryBatch mesh points))+ Right admitted -> do+ mesh <- dereferenceHandle meshPointer+ pure (first buildFailure (insertGeometryBatch mesh admitted)) +-- | Admission stays where it was — every point is validated before any point+-- is inserted, so a malformed input at the end still refuses the whole batch —+-- but it no longer materializes a second vector to carry the canonical+-- coordinates. @imapM@ over 'Either' cannot fill in place, so the discarded+-- form is the one that costs nothing; canonicalization is exactly what+-- @queryPointValue . validatePoint@ returned, applied where the point is used. insertGeometryBatch :: GeometryMesh -> V.Vector T.Point -> Either T.BuildError GeometryMesh insertGeometryBatch mesh points = do- normalized <-- V.imapM- (\index point -> T.queryPointValue <$> validatePoint (Just index) point)- points+ V.imapM_ (\index point -> () <$ validatePoint (Just index) point) points (_, revised, _) <- Session.withSession mesh- (V.length normalized)- (traverse_ (\point -> void (Session.insertVertexAt point ())) normalized)+ (V.length points)+ (V.mapM_ (\point -> void (Session.insertVertexAt (canonicalPoint point) ())) points) pure revised -meshUnion, meshIntersection, meshDifference, meshSymmetricDifference :: Ptr () -> Ptr () -> Ptr (Ptr ()) -> Ptr CObstruction -> IO CUInt-meshUnion = binaryMeshOperation T.union-meshIntersection = binaryMeshOperation T.intersection-meshDifference = binaryMeshOperation T.difference-meshSymmetricDifference = binaryMeshOperation T.symmetricDifference+meshSiteUnion, meshSiteIntersection, meshSiteDifference, meshSiteSymmetricDifference :: Ptr CMesh -> Ptr CMesh -> Ptr (Ptr CMesh) -> Ptr CObstruction -> IO CUInt+meshSiteUnion = binaryMeshOperation T.union+meshSiteIntersection = binaryMeshOperation T.intersection+meshSiteDifference = binaryMeshOperation T.difference+meshSiteSymmetricDifference = binaryMeshOperation T.symmetricDifference -binaryMeshOperation :: (GeometryMesh -> GeometryMesh -> Either T.BuildError GeometryMesh) -> Ptr () -> Ptr () -> Ptr (Ptr ()) -> Ptr CObstruction -> IO CUInt+binaryMeshOperation+ :: (GeometryMesh -> GeometryMesh -> Either T.BuildError GeometryMesh)+ -> Ptr CMesh+ -> Ptr CMesh+ -> Ptr (Ptr CMesh)+ -> Ptr CObstruction+ -> IO CUInt binaryMeshOperation operation leftPointer rightPointer output obstructionPointer =- runBoundary obstructionPointer $ produceMesh output $ do- case (requirePointer "left mesh" leftPointer, requirePointer "right mesh" rightPointer) of- (Left failure, _) -> pure (Left failure)- (_, Left failure) -> pure (Left failure)- (Right (), Right ()) -> do- left <- dereferenceMesh leftPointer- right <- dereferenceMesh rightPointer- pure (Right (operation left right))+ runBoundary obstructionPointer $ produceHandle output $ do+ case requirePointer "left mesh" leftPointer >> requirePointer "right mesh" rightPointer of+ Left failure -> pure (Left failure)+ Right () -> do+ left <- dereferenceHandle leftPointer+ right <- dereferenceHandle rightPointer+ pure (first buildFailure (operation left right)) -meshVertexCount, meshTriangleCount :: Ptr () -> Ptr CSize -> Ptr CObstruction -> IO CUInt-meshVertexCount = meshCount T.numVertices-meshTriangleCount = meshCount (V.length . T.innerFaceVertexTriples)+meshVertexCount, meshTriangleCount :: Ptr CMesh -> Ptr CSize -> Ptr CObstruction -> IO CUInt+meshVertexCount = handleCount "mesh" T.numVertices+meshTriangleCount = handleCount "mesh" (V.length . T.innerFaceVertexTriples) -meshCount :: (GeometryMesh -> Int) -> Ptr () -> Ptr CSize -> Ptr CObstruction -> IO CUInt-meshCount observe meshPointer output obstructionPointer =+handleCount+ :: String+ -> (HandleValue carrier -> Int)+ -> Ptr carrier+ -> Ptr CSize+ -> Ptr CObstruction+ -> IO CUInt+handleCount handleLabel observe handlePointer output obstructionPointer = runBoundary obstructionPointer $- case (requirePointer "mesh" meshPointer, requirePointer "count" output) of- (Left failure, _) -> pure (Left failure)- (_, Left failure) -> pure (Left failure)- (Right (), Right ()) -> do- mesh <- dereferenceMesh meshPointer- poke output (fromIntegral (observe mesh))+ case requirePointer handleLabel handlePointer >> requirePointer "count" output of+ Left failure -> pure (Left failure)+ Right () -> do+ value <- dereferenceHandle handlePointer+ poke output (fromIntegral (observe value)) pure (Right ()) -meshCopyVerticesF64 :: Ptr () -> Ptr CDouble -> CSize -> Ptr CSize -> Ptr CObstruction -> IO CUInt+meshCopyVerticesF64 :: Ptr CMesh -> Ptr CDouble -> CSize -> Ptr CSize -> Ptr CObstruction -> IO CUInt meshCopyVerticesF64 =- copyMeshProjection+ copyHandleProjection+ "mesh" "points_written" "coordinates" T.vertexPoints@@ -281,31 +392,39 @@ pokeElemOff output (index * 2 + 1) (CDouble y) ) -meshCopyTrianglesU32 :: Ptr () -> Ptr Word32 -> CSize -> Ptr CSize -> Ptr CObstruction -> IO CUInt+meshCopyTrianglesU32 :: Ptr CMesh -> Ptr Word32 -> CSize -> Ptr CSize -> Ptr CObstruction -> IO CUInt meshCopyTrianglesU32 =- copyMeshProjection+ copyHandleProjection+ "mesh" "triangles_written" "triangles" T.innerFaceVertexTriples- ( \output index (first, second, third) -> do- pokeElemOff output (index * 3) (T.unVertexId first)- pokeElemOff output (index * 3 + 1) (T.unVertexId second)- pokeElemOff output (index * 3 + 2) (T.unVertexId third)+ ( \output index (firstVertex, secondVertex, thirdVertex) -> do+ pokeElemOff output (index * 3) (T.unVertexId firstVertex)+ pokeElemOff output (index * 3 + 1) (T.unVertexId secondVertex)+ pokeElemOff output (index * 3 + 2) (T.unVertexId thirdVertex) ) -copyMeshProjection- :: String -> String -> (GeometryMesh -> V.Vector item) -> (Ptr element -> Int -> item -> IO ())- -> Ptr () -> Ptr element -> CSize -> Ptr CSize -> Ptr CObstruction -> IO CUInt-{-# INLINE copyMeshProjection #-}-copyMeshProjection writtenLabel outputLabel project writeItem meshPointer output rawCapacity written obstructionPointer =+copyHandleProjection+ :: String+ -> String+ -> String+ -> (HandleValue carrier -> V.Vector item)+ -> (Ptr element -> Int -> item -> IO ())+ -> Ptr carrier+ -> Ptr element+ -> CSize+ -> Ptr CSize+ -> Ptr CObstruction+ -> IO CUInt+{-# INLINE copyHandleProjection #-}+copyHandleProjection handleLabel writtenLabel outputLabel project writeItem handlePointer output rawCapacity written obstructionPointer = runBoundary obstructionPointer $- case (requirePointer "mesh" meshPointer, requirePointer writtenLabel written, checkedCount 1 rawCapacity) of- (Left failure, _, _) -> pure (Left failure)- (_, Left failure, _) -> pure (Left failure)- (_, _, Left failure) -> pure (Left failure)- (Right (), Right (), Right capacity) -> do- mesh <- dereferenceMesh meshPointer- let items = project mesh+ case requirePointer handleLabel handlePointer >> requirePointer writtenLabel written >> checkedCount 1 rawCapacity of+ Left failure -> pure (Left failure)+ Right capacity -> do+ value <- dereferenceHandle handlePointer+ let items = project value required = V.length items poke written (fromIntegral required) case requireOutputCapacity outputLabel output required capacity of@@ -318,14 +437,552 @@ | required > 0 = requirePointer label output | otherwise = Right () -dereferenceMesh :: Ptr () -> IO GeometryMesh-dereferenceMesh = deRefStablePtr . (castPtrToStablePtr :: Ptr () -> StablePtr GeometryMesh)+meshFree :: Ptr CMesh -> IO ()+meshFree = freeHandle -meshFree :: Ptr () -> IO ()-meshFree pointer- | pointer == nullPtr = pure ()- | otherwise = freeStablePtr ((castPtrToStablePtr pointer) :: StablePtr GeometryMesh)+data RegionCountKind+ = LoopPointCounts+ | ComponentLoopCounts+ deriving stock (Eq, Show) +data RegionLayoutError+ = RegionGroupEmpty !RegionCountKind !Int+ | RegionCountTotalMismatch !RegionCountKind !Integer !Integer+ | RegionStructuringElementEmpty+ deriving stock (Eq, Show)++regionLayoutFailure :: RegionLayoutError -> AbiFailure+regionLayoutFailure layoutError =+ AbiFailure statusGeometryObstruction (layoutObstruction layoutError)++layoutObstruction :: RegionLayoutError -> CObstruction+layoutObstruction layoutError =+ (case layoutError of+ RegionCountTotalMismatch _ actual expected ->+ base+ { obstructionFirstIndex = fromIntegral actual+ , obstructionSecondIndex = fromIntegral expected+ }+ RegionGroupEmpty _ index -> base {obstructionInputIndex = fromIntegral index}+ RegionStructuringElementEmpty -> base+ )+ {obstructionMessage = show layoutError}+ where+ base = emptyObstruction {obstructionCode = 200}++regionValidationFailure :: T.RegionValidationError -> AbiFailure+regionValidationFailure = geometryFailure 201++overlayFailure :: T.OverlayError Bool Bool -> AbiFailure+overlayFailure = geometryFailure 202++regionPublicationFailure :: T.RegionPublicationError -> AbiFailure+regionPublicationFailure = geometryFailure 203++valuationFailure :: T.ValuationError -> AbiFailure+valuationFailure = geometryFailure 204++minkowskiFailure :: T.MinkowskiError -> AbiFailure+minkowskiFailure = geometryFailure 205++pointInputFailure :: Int -> T.Point -> T.PointValidationError -> AbiFailure+pointInputFailure index (T.Point x y) pointError =+ AbiFailure statusGeometryObstruction emptyObstruction+ { obstructionCode = 1+ , obstructionCoordinateError = coordinateErrorCode reason+ , obstructionInputIndex = fromIntegral index+ , obstructionFirstValue = invalidValue+ , obstructionPointX = x+ , obstructionPointY = y+ , obstructionMessage = show pointError+ }+ where+ (invalidValue, reason) =+ case pointError of+ T.InvalidPointX coordinateError -> (x, coordinateError)+ T.InvalidPointY coordinateError -> (y, coordinateError)++projectionFailure :: Int -> T.PointValidationError -> AbiFailure+projectionFailure index pointError =+ AbiFailure statusGeometryObstruction emptyObstruction+ { obstructionCode = 206+ , obstructionCoordinateError =+ coordinateErrorCode+ (case pointError of+ T.InvalidPointX coordinateError -> coordinateError+ T.InvalidPointY coordinateError -> coordinateError)+ , obstructionInputIndex = fromIntegral index+ , obstructionMessage = show pointError+ }++readCounts+ :: String+ -> Ptr CSize+ -> Int+ -> IO (Either AbiFailure (V.Vector Int))+readCounts _ _ 0 = pure (Right V.empty)+readCounts label pointer count+ | pointer == nullPtr = pure (Left (nullPointerFailure label))+ | otherwise = do+ rawCounts <- V.generateM count (peekElemOff pointer)+ pure (V.mapM (checkedCount 1) rawCounts)++validateCounts+ :: RegionCountKind+ -> Int+ -> V.Vector Int+ -> Either RegionLayoutError ()+validateCounts kind expectedTotal counts =+ case V.findIndex (== 0) counts of+ Just index -> Left (RegionGroupEmpty kind index)+ Nothing+ | observedTotal /= toInteger expectedTotal ->+ Left (RegionCountTotalMismatch kind observedTotal (toInteger expectedTotal))+ | otherwise -> Right ()+ where+ observedTotal = V.foldl' (\total count -> total + toInteger count) 0 counts++regionCreateF64+ :: Ptr CDouble+ -> CSize+ -> Ptr CSize+ -> CSize+ -> Ptr CSize+ -> CSize+ -> Ptr (Ptr CRegion)+ -> Ptr CObstruction+ -> IO CUInt+regionCreateF64 coordinates rawPointCount loopPointCounts rawLoopCount componentLoopCounts rawComponentCount output obstructionPointer =+ runBoundary obstructionPointer $+ produceHandle output $+ readRegionF64+ coordinates+ rawPointCount+ loopPointCounts+ rawLoopCount+ componentLoopCounts+ rawComponentCount++readRegionF64+ :: Ptr CDouble+ -> CSize+ -> Ptr CSize+ -> CSize+ -> Ptr CSize+ -> CSize+ -> IO (Either AbiFailure T.PlanarRegion)+readRegionF64 coordinates rawPointCount loopPointCounts rawLoopCount componentLoopCounts rawComponentCount =+ case checkedRegionInputCounts rawPointCount rawLoopCount rawComponentCount of+ Left failure -> pure (Left failure)+ Right (pointCount, loopCount, componentCount) -> do+ points <- readPoints coordinates pointCount+ loopCounts <- readCounts "loop_point_counts" loopPointCounts loopCount+ componentCounts <- readCounts "component_loop_counts" componentLoopCounts componentCount+ pure $ do+ admittedPoints <- points+ admittedLoopCounts <- loopCounts+ admittedComponentCounts <- componentCounts+ first regionLayoutFailure (validateCounts LoopPointCounts pointCount admittedLoopCounts)+ first regionLayoutFailure (validateCounts ComponentLoopCounts loopCount admittedComponentCounts)+ buildRegion admittedPoints admittedLoopCounts admittedComponentCounts++checkedRegionInputCounts+ :: CSize+ -> CSize+ -> CSize+ -> Either AbiFailure (Int, Int, Int)+checkedRegionInputCounts rawPointCount rawLoopCount rawComponentCount = do+ pointCount <- checkedCount 2 rawPointCount+ loopCount <- checkedCount 1 rawLoopCount+ componentCount <- checkedCount 1 rawComponentCount+ pure (pointCount, loopCount, componentCount)++buildRegion+ :: V.Vector T.Point+ -> V.Vector Int+ -> V.Vector Int+ -> Either AbiFailure T.PlanarRegion+buildRegion points loopCounts componentCounts = do+ exactPoints <-+ V.imapM+ (\index point -> first (pointInputFailure index point) (T.exactPointFromPoint point))+ points+ loops <-+ V.imapM+ (buildLoop exactPoints)+ (adjacentOffsets loopCounts)+ components <-+ V.imapM+ (buildComponent loops)+ (adjacentOffsets componentCounts)+ first regionValidationFailure (T.planarRegion (V.toList components))++adjacentOffsets :: V.Vector Int -> V.Vector (Int, Int)+adjacentOffsets counts =+ let offsets = V.scanl' (+) 0 counts+ in V.zip offsets (V.drop 1 offsets)++buildLoop+ :: V.Vector T.ExactPoint+ -> Int+ -> (Int, Int)+ -> Either AbiFailure T.ExactLoop+buildLoop points loopIndex (start, end) =+ case NonEmpty.nonEmpty (V.toList (V.slice start (end - start) points)) of+ Nothing -> Left (regionLayoutFailure (RegionGroupEmpty LoopPointCounts loopIndex))+ Just submitted -> first regionValidationFailure (T.exactLoop submitted)++buildComponent+ :: V.Vector T.ExactLoop+ -> Int+ -> (Int, Int)+ -> Either AbiFailure T.PolygonComponent+buildComponent loops componentIndex (start, end) =+ case NonEmpty.nonEmpty (V.toList (V.slice start (end - start) loops)) of+ Nothing -> Left (regionLayoutFailure (RegionGroupEmpty ComponentLoopCounts componentIndex))+ Just (outer :| holes) -> first regionValidationFailure (T.polygonComponent outer holes)++regionShape+ :: T.PlanarRegion+ -> ([T.PolygonComponent], [[T.ExactLoop]], [T.ExactLoop])+regionShape region =+ let components = T.planarRegionComponents region+ componentLoops component = T.polygonOuterLoop component : T.polygonHoleLoops component+ loopsByComponent = map componentLoops components+ in (components, loopsByComponent, concat loopsByComponent)++regionCounts+ :: Ptr CRegion+ -> Ptr CSize+ -> Ptr CSize+ -> Ptr CSize+ -> Ptr CObstruction+ -> IO CUInt+regionCounts regionPointer componentCountOutput loopCountOutput pointCountOutput obstructionPointer =+ runBoundary obstructionPointer $+ case+ requirePointer "region" regionPointer+ >> requirePointer "component_count" componentCountOutput+ >> requirePointer "loop_count" loopCountOutput+ >> requirePointer "point_count" pointCountOutput+ of+ Left failure -> pure (Left failure)+ Right () -> do+ region <- dereferenceHandle regionPointer+ let (components, _, loops) = regionShape region+ pointCount = sum (map (NonEmpty.length . T.exactLoopPoints) loops)+ poke componentCountOutput (fromIntegral (length components))+ poke loopCountOutput (fromIntegral (length loops))+ poke pointCountOutput (fromIntegral pointCount)+ pure (Right ())++data RegionProjection = RegionProjection+ { projectionPoints :: !(V.Vector T.Point)+ , projectionLoopPointOffsets :: !(V.Vector CSize)+ , projectionComponentLoopOffsets :: !(V.Vector CSize)+ }++regionProjection :: T.PlanarRegion -> Either AbiFailure RegionProjection+regionProjection region = do+ let (_, loopsByComponent, loops) = regionShape region+ exactPoints = concatMap (NonEmpty.toList . T.exactLoopPoints) loops+ loopPointOffsets = scanl (+) 0 (map (NonEmpty.length . T.exactLoopPoints) loops)+ componentLoopOffsets = scanl (+) 0 (map length loopsByComponent)+ projectedPoints <-+ V.fromList+ <$> traverse+ (uncurry projectPoint)+ (zip [0 ..] exactPoints)+ pure+ RegionProjection+ { projectionPoints = projectedPoints+ , projectionLoopPointOffsets = V.fromList (map fromIntegral loopPointOffsets)+ , projectionComponentLoopOffsets = V.fromList (map fromIntegral componentLoopOffsets)+ }+ where+ projectPoint index point =+ first (projectionFailure index) (T.exactPointToEmbeddingCandidate point)++regionCopyF64+ :: Ptr CRegion+ -> Ptr CDouble+ -> CSize+ -> Ptr CSize+ -> CSize+ -> Ptr CSize+ -> CSize+ -> Ptr CObstruction+ -> IO CUInt+regionCopyF64 regionPointer coordinates rawPointCapacity loopPointOffsets rawLoopOffsetCapacity componentLoopOffsets rawComponentOffsetCapacity obstructionPointer =+ runBoundary obstructionPointer $+ case+ ( (,,)+ <$> (requirePointer "region" regionPointer >> checkedCount 2 rawPointCapacity)+ <*> checkedCount 1 rawLoopOffsetCapacity+ <*> checkedCount 1 rawComponentOffsetCapacity+ )+ of+ Left failure -> pure (Left failure)+ Right (pointCapacity, loopOffsetCapacity, componentOffsetCapacity) -> do+ region <- dereferenceHandle regionPointer+ case regionProjection region of+ Left failure -> pure (Left failure)+ Right RegionProjection {..} ->+ case+ requireOutputCapacity "coordinates" coordinates (V.length projectionPoints) pointCapacity+ >> requireOutputCapacity "loop_point_offsets" loopPointOffsets (V.length projectionLoopPointOffsets) loopOffsetCapacity+ >> requireOutputCapacity "component_loop_offsets" componentLoopOffsets (V.length projectionComponentLoopOffsets) componentOffsetCapacity+ of+ Left failure -> pure (Left failure)+ Right () -> do+ V.imapM_+ (\index (T.Point x y) -> do+ pokeElemOff coordinates (index * 2) (CDouble x)+ pokeElemOff coordinates (index * 2 + 1) (CDouble y))+ projectionPoints+ V.imapM_ (pokeElemOff loopPointOffsets) projectionLoopPointOffsets+ V.imapM_ (pokeElemOff componentLoopOffsets) projectionComponentLoopOffsets+ pure (Right ())++regionUnion, regionIntersection, regionDifference, regionSymmetricDifference :: Ptr CRegion -> Ptr CRegion -> Ptr (Ptr CRegion) -> Ptr CObstruction -> IO CUInt+regionUnion = binaryRegionOperation (\(left, right) -> left || right)+regionIntersection = binaryRegionOperation (\(left, right) -> left && right)+regionDifference = binaryRegionOperation (\(left, right) -> left && not right)+regionSymmetricDifference = binaryRegionOperation (uncurry (/=))++binaryRegionOperation+ :: ((Bool, Bool) -> Bool)+ -> Ptr CRegion+ -> Ptr CRegion+ -> Ptr (Ptr CRegion)+ -> Ptr CObstruction+ -> IO CUInt+binaryRegionOperation selected leftPointer rightPointer output obstructionPointer =+ runBoundary obstructionPointer $ produceHandle output $ do+ case requirePointer "left region" leftPointer >> requirePointer "right region" rightPointer of+ Left failure -> pure (Left failure)+ Right () -> do+ left <- dereferenceHandle leftPointer+ right <- dereferenceHandle rightPointer+ pure (exactRegionBoolean selected left right)++exactRegionBoolean+ :: ((Bool, Bool) -> Bool)+ -> T.PlanarRegion+ -> T.PlanarRegion+ -> Either AbiFailure T.PlanarRegion+exactRegionBoolean selected left right = do+ leftLayer <- first regionValidationFailure (T.planarLayer False (Map.singleton True left))+ rightLayer <- first regionValidationFailure (T.planarLayer False (Map.singleton True right))+ overlay <- first overlayFailure (T.overlayLayers leftLayer rightLayer)+ first regionPublicationFailure (T.overlaySelectedRegion selected overlay)++regionLocatePointF64+ :: Ptr CRegion+ -> CDouble+ -> CDouble+ -> Ptr CUInt+ -> Ptr CObstruction+ -> IO CUInt+regionLocatePointF64 regionPointer (CDouble x) (CDouble y) output obstructionPointer =+ runBoundary obstructionPointer $+ case+ requirePointer "region" regionPointer+ >> requirePointer "location" output+ >> first (pointInputFailure 0 (T.Point x y)) (T.exactPointFromPoint (T.Point x y))+ of+ Left failure -> pure (Left failure)+ Right query -> do+ region <- dereferenceHandle regionPointer+ poke output (regionLocationCode (T.regionPointLocation region query))+ pure (Right ())++regionLocationCode :: T.RegionPointLocation -> CUInt+regionLocationCode location =+ case location of+ T.RegionExterior -> 0+ T.RegionOnBoundary -> 1+ T.RegionInterior -> 2++regionMeasure+ :: Ptr CRegion+ -> Ptr Int64+ -> Ptr CChar+ -> CSize+ -> Ptr CSize+ -> Ptr CDouble+ -> Ptr CDouble+ -> Ptr CObstruction+ -> IO CUInt+regionMeasure regionPointer eulerOutput areaRatioOutput rawAreaCapacity areaBytesWritten perimeterLowerOutput perimeterUpperOutput obstructionPointer =+ runBoundary obstructionPointer $+ case+ requirePointer "region" regionPointer+ >> requirePointer "euler_characteristic" eulerOutput+ >> requirePointer "area_bytes_written" areaBytesWritten+ >> requirePointer "perimeter_lower" perimeterLowerOutput+ >> requirePointer "perimeter_upper" perimeterUpperOutput+ >> checkedCount 1 rawAreaCapacity+ of+ Left failure -> pure (Left failure)+ Right areaCapacity -> do+ region <- dereferenceHandle regionPointer+ case regionMeasurements region of+ Left failure -> pure (Left failure)+ Right (valuations, perimeter) -> do+ let area = T.exactAreaValue (T.valuationArea valuations)+ areaText =+ show (T.exactRationalNumerator area)+ <> "/"+ <> show (T.exactRationalDenominator area)+ bounds = T.exactLengthBounds perimeter+ poke eulerOutput (fromIntegral (T.eulerCharacteristicValue (T.valuationEuler valuations)))+ poke perimeterLowerOutput (CDouble (T.intervalLower bounds))+ poke perimeterUpperOutput (CDouble (T.intervalUpper bounds))+ copyCStringOutput areaText areaRatioOutput areaCapacity areaBytesWritten++regionMeasurements+ :: T.PlanarRegion+ -> Either AbiFailure (T.PlanarValuations, T.ExactLengthMeasurement)+regionMeasurements region = do+ valuations <- first valuationFailure (T.regionValuations region)+ perimeter <- first valuationFailure (T.planarValuationsPerimeter valuations)+ pure (valuations, perimeter)++copyCStringOutput+ :: String+ -> Ptr CChar+ -> Int+ -> Ptr CSize+ -> IO (Either AbiFailure ())+copyCStringOutput value output capacity bytesWritten =+ withCStringLen value $ \(source, byteCount) -> do+ poke bytesWritten (fromIntegral byteCount)+ case requireOutputCapacity "area_ratio_utf8" output (byteCount + 1) capacity of+ Left failure -> pure (Left failure)+ Right () -> do+ copyBytes output source byteCount+ pokeElemOff output byteCount 0+ pure (Right ())++regionFree :: Ptr CRegion -> IO ()+regionFree = freeHandle++structuringElementCreateF64+ :: Ptr CDouble+ -> CSize+ -> Ptr (Ptr CStructuringElement)+ -> Ptr CObstruction+ -> IO CUInt+structuringElementCreateF64 coordinates rawPointCount output obstructionPointer =+ runBoundary obstructionPointer $ produceHandle output $ do+ case checkedCount 2 rawPointCount of+ Left failure -> pure (Left failure)+ Right pointCount -> do+ points <- readPoints coordinates pointCount+ pure (points >>= buildStructuringElement)++buildStructuringElement+ :: V.Vector T.Point+ -> Either AbiFailure T.StructuringElement+buildStructuringElement points = do+ exactPoints <-+ V.imapM+ (\index point -> first (pointInputFailure index point) (T.exactPointFromPoint point))+ points+ submitted <-+ maybe+ (Left (regionLayoutFailure RegionStructuringElementEmpty))+ Right+ (NonEmpty.nonEmpty (V.toList exactPoints))+ polygon <- first minkowskiFailure (T.convexPolygon submitted)+ first minkowskiFailure (T.structuringElement polygon)++structuringElementFree :: Ptr CStructuringElement -> IO ()+structuringElementFree = freeHandle++regionMinkowskiSum+ :: Ptr CRegion+ -> Ptr CRegion+ -> Ptr (Ptr CRegion)+ -> Ptr CMinkowskiReceipt+ -> Ptr CObstruction+ -> IO CUInt+regionMinkowskiSum leftPointer rightPointer output receiptOutput obstructionPointer =+ runBoundary obstructionPointer $+ produceRegionWithReceipt output receiptOutput $ do+ case requirePointer "left region" leftPointer >> requirePointer "right region" rightPointer of+ Left failure -> pure (Left failure)+ Right () -> do+ left <- dereferenceHandle leftPointer+ right <- dereferenceHandle rightPointer+ pure (first minkowskiFailure (T.minkowskiSum left right))++regionOffset, regionInset, regionOpen, regionClose :: Ptr CStructuringElement -> Ptr CRegion -> Ptr (Ptr CRegion) -> Ptr CMinkowskiReceipt -> Ptr CObstruction -> IO CUInt+regionOffset = structuringElementOperation T.polygonOffset+regionInset = structuringElementOperation T.polygonInset+regionOpen = structuringElementOperation T.openWith+regionClose = structuringElementOperation T.closeWith++structuringElementOperation+ :: (T.StructuringElement -> T.PlanarRegion -> Either T.MinkowskiError (T.PlanarRegion, T.MinkowskiReceipt))+ -> Ptr CStructuringElement+ -> Ptr CRegion+ -> Ptr (Ptr CRegion)+ -> Ptr CMinkowskiReceipt+ -> Ptr CObstruction+ -> IO CUInt+structuringElementOperation operation elementPointer regionPointer output receiptOutput obstructionPointer =+ runBoundary obstructionPointer $+ produceRegionWithReceipt output receiptOutput $ do+ case requirePointer "structuring element" elementPointer >> requirePointer "region" regionPointer of+ Left failure -> pure (Left failure)+ Right () -> do+ element <- dereferenceHandle elementPointer+ region <- dereferenceHandle regionPointer+ pure (first minkowskiFailure (operation element region))++produceRegionWithReceipt+ :: Ptr (Ptr CRegion)+ -> Ptr CMinkowskiReceipt+ -> IO (Either AbiFailure (T.PlanarRegion, T.MinkowskiReceipt))+ -> IO (Either AbiFailure ())+produceRegionWithReceipt output receiptOutput obtain = do+ prepared <- prepareHandleOutput output+ case (prepared, requirePointer "receipt" receiptOutput) of+ (Left failure, _) -> pure (Left failure)+ (_, Left failure) -> pure (Left failure)+ (Right (), Right ()) -> do+ outcome <- obtain+ case outcome of+ Left failure -> pure (Left failure)+ Right (region, receipt) -> do+ poke receiptOutput (minkowskiReceiptProjection receipt)+ publishHandle output region+ pure (Right ())++minkowskiReceiptProjection :: T.MinkowskiReceipt -> CMinkowskiReceipt+minkowskiReceiptProjection receipt =+ CMinkowskiReceipt+ { receiptOperation = minkowskiOperationCode (T.minkowskiOperation receipt)+ , receiptInputComponents = fromIntegral (T.minkowskiInputComponents receipt)+ , receiptConvexPieces = fromIntegral (T.minkowskiConvexPieces receipt)+ , receiptGeneratedPieces = fromIntegral (T.minkowskiGeneratedPieces receipt)+ , receiptGeneratedConvolutionEdges = fromIntegral (T.minkowskiGeneratedConvolutionEdges receipt)+ , receiptOverlayPasses = fromIntegral (T.minkowskiOverlayPasses receipt)+ , receiptExactCrossings = fromIntegral (T.minkowskiExactCrossings receipt)+ , receiptOutputCells = fromIntegral (T.minkowskiOutputCells receipt)+ , receiptExactCoordinateBitGrowth = fromIntegral (T.minkowskiExactCoordinateBitGrowth receipt)+ }++minkowskiOperationCode :: T.MinkowskiOperation -> Word32+minkowskiOperationCode operation =+ case operation of+ T.MinkowskiAddition -> 0+ T.MinkowskiErosion -> 1+ T.MinkowskiOpening -> 2+ T.MinkowskiClosing -> 3+ buildErrorObstruction :: T.BuildError -> CObstruction buildErrorObstruction failure = (case failure of@@ -340,13 +997,13 @@ T.LocationWalkExhausted (T.Point x y) steps -> (pointObstruction 3 x y) {obstructionFirstIndex = fromIntegral steps} T.RefinementInputTopologyInvalid _ -> codeOnly 4- T.FreshInsertionMatchedExistingVertex first second -> indices 5 (T.unVertexId first) (T.unVertexId second)+ T.FreshInsertionMatchedExistingVertex firstVertex secondVertex -> indices 5 (T.unVertexId firstVertex) (T.unVertexId secondVertex) T.DegenerateLineEndpointMissingOutgoing vertex -> firstIndex 6 (T.unVertexId vertex) T.DegenerateLineEndpointTurnMissing index -> firstIndex 7 index T.DegenerateLineConnectedVertexMissing index -> firstIndex 8 index T.HullStartNotVisible edge -> firstIndex 9 (T.unDirectedEdgeId edge)- T.OuterRangeDidNotTerminate first second steps ->- (indices 10 (T.unDirectedEdgeId first) (T.unDirectedEdgeId second))+ T.OuterRangeDidNotTerminate firstEdge secondEdge steps ->+ (indices 10 (T.unDirectedEdgeId firstEdge) (T.unDirectedEdgeId secondEdge)) {obstructionFirstValue = fromIntegral steps} T.OuterRangeContainsInnerEdge edge face -> indices 11 (T.unDirectedEdgeId edge) (T.unFaceId face) T.ConstrainedEdgeFlipRefused edge -> firstIndex 12 (T.unUndirectedEdgeId edge)@@ -365,8 +1022,8 @@ (indices 22 (T.unVertexId vertex) (T.unDirectedEdgeId edge)) {obstructionFirstValue = fromIntegral steps} T.CircleSweepHullEmpty -> codeOnly 23- T.OuterCycleDidNotTerminate first second steps ->- (indices 24 (T.unDirectedEdgeId first) (T.unDirectedEdgeId second))+ T.OuterCycleDidNotTerminate firstEdge secondEdge steps ->+ (indices 24 (T.unDirectedEdgeId firstEdge) (T.unDirectedEdgeId secondEdge)) {obstructionFirstValue = fromIntegral steps} T.HierarchyLevelPopulationMismatch level expected observed -> (indices 25 expected observed) {obstructionFirstValue = fromIntegral level}@@ -408,18 +1065,18 @@ firstIndex :: Integral index => Word32 -> index -> CObstruction firstIndex code index = (codeOnly code) {obstructionFirstIndex = fromIntegral index} indices :: (Integral first, Integral second) => Word32 -> first -> second -> CObstruction- indices code first second =+ indices code firstIndexValue secondIndexValue = (codeOnly code)- { obstructionFirstIndex = fromIntegral first- , obstructionSecondIndex = fromIntegral second+ { obstructionFirstIndex = fromIntegral firstIndexValue+ , obstructionSecondIndex = fromIntegral secondIndexValue } firstValue :: Word32 -> Double -> CObstruction firstValue code value = (codeOnly code) {obstructionFirstValue = value} values :: Word32 -> Double -> Double -> CObstruction- values code first second =+ values code firstCoordinateValue secondCoordinateValue = (codeOnly code)- { obstructionFirstValue = first- , obstructionSecondValue = second+ { obstructionFirstValue = firstCoordinateValue+ , obstructionSecondValue = secondCoordinateValue } pointObstruction :: Word32 -> Double -> Double -> CObstruction pointObstruction code x y =
+ src-planar/Moonlight/Triangulation/Internal/Minkowski/Convex.hs view
@@ -0,0 +1,326 @@+-- | Pure exact convex-polygon algebra: admission, linear edge-angle+-- convolution, reflection, hull construction, and support-half-plane erosion.+module Moonlight.Triangulation.Internal.Minkowski.Convex+ ( convexPolygon+ , convexPolygonPoints+ , convexPolygonRegion+ , admittedConvexLoop+ , structuringElement+ , structuringElementPolygon+ , convexMinkowskiSum+ , convexMinkowskiPolygon+ , convexHullPolygon+ , reflectConvexPolygon+ , convexPolygonCentroid+ , erodeConvexBy+ , addExactPoints+ , subtractExactPoints+ ) where++import Control.Monad (foldM)+import Data.Bifunctor (first)+import qualified Data.List as List+import Data.List.NonEmpty (NonEmpty (..))+import qualified Data.List.NonEmpty as NonEmpty+import qualified Data.Set as Set+import Moonlight.Triangulation.Exact+ ( ExactPoint+ , ExactVector (..)+ , addExactVectors+ , compareExactVectorAngle+ , exactVectorFromPoints+ , exactSegment+ , exactSupportingLineIntersection+ , exactOrient2d+ , exactPoint+ , exactPointCoordinates+ , translateExactPoint+ )+import Moonlight.Triangulation.Internal.BoundaryCycle+ ( cyclePairs+ , cyclePairsNonEmpty+ , cyclicTriples+ , rotateCycleLeast+ , rotateCycleLeastBy+ )+import Moonlight.Triangulation.Internal.ExactRational+ ( ExactRational+ , exactDivide+ )+import Moonlight.Triangulation.Internal.Minkowski.Types+ ( ConvexPolygon (..)+ , MinkowskiError (..)+ , StructuringElement (..)+ )+import Moonlight.Triangulation.Internal.Region.Types+ ( ExactLoop (..)+ , PlanarRegion (..)+ , PolygonComponent (..)+ , RegionPointLocation (..)+ )+import Moonlight.Triangulation.Region+ ( exactLoop+ , exactLoopPoints+ , regionPointLocation+ )++convexPolygon+ :: NonEmpty ExactPoint+ -> Either MinkowskiError ConvexPolygon+convexPolygon submitted = do+ loop <- first MinkowskiInvalidConvexLoop (exactLoop submitted)+ let points = exactLoopPoints loop+ case+ [ (index, turn)+ | (index, (previous, current, next)) <-+ zip [0 :: Int ..] (cyclicTriples (NonEmpty.toList points))+ , let turn = exactOrient2d previous current next+ , turn /= GT+ ] of+ (index, turn) : _ -> Left (MinkowskiNonConvexTurn index turn)+ [] -> Right (ConvexPolygon loop)++convexPolygonPoints :: ConvexPolygon -> NonEmpty ExactPoint+convexPolygonPoints (ConvexPolygon loop) = exactLoopPoints loop++convexPolygonRegion :: ConvexPolygon -> PlanarRegion+convexPolygonRegion (ConvexPolygon loop) =+ PlanarRegion [PolygonComponent loop []]++admittedConvexLoop :: ExactLoop -> Maybe ConvexPolygon+admittedConvexLoop loop+ | all ((== GT) . orderedTurn) (cyclicTriples (NonEmpty.toList (exactLoopPoints loop))) =+ Just (ConvexPolygon loop)+ | otherwise = Nothing+ where+ orderedTurn (previous, current, next) =+ exactOrient2d previous current next++structuringElement+ :: ConvexPolygon+ -> Either MinkowskiError StructuringElement+structuringElement polygon =+ let origin = exactPoint 0 0+ location = regionPointLocation (convexPolygonRegion polygon) origin+ in case location of+ RegionExterior -> Left (MinkowskiOriginOutside location)+ _ -> Right (StructuringElement polygon)++structuringElementPolygon :: StructuringElement -> ConvexPolygon+structuringElementPolygon (StructuringElement polygon) = polygon++convexMinkowskiSum+ :: ConvexPolygon+ -> ConvexPolygon+ -> PlanarRegion+convexMinkowskiSum left right =+ convexPolygonRegion (convexMinkowskiPolygon left right)++convexMinkowskiPolygon+ :: ConvexPolygon+ -> ConvexPolygon+ -> ConvexPolygon+convexMinkowskiPolygon left right =+ let leftPoints = rotateCycleLeastBy pointSweepKey (convexPolygonPoints left)+ rightPoints = rotateCycleLeastBy pointSweepKey (convexPolygonPoints right)+ start = addExactPoints (NonEmpty.head leftPoints) (NonEmpty.head rightPoints)+ directions =+ mergeDirections+ (edgeDirections leftPoints)+ (edgeDirections rightPoints)+ directionList = NonEmpty.toList directions+ scanned = scanl translateExactPoint start directionList+ resultPoints = start :| take (length directionList - 1) (drop 1 scanned)+ in ConvexPolygon (ExactLoop (rotateCycleLeast resultPoints))++convexHullPolygon+ :: NonEmpty ExactPoint+ -> Either MinkowskiError ConvexPolygon+convexHullPolygon submitted =+ let points = NonEmpty.toList submitted+ in case convexHullPoints points of+ Nothing -> Left (MinkowskiConvexHullDegenerate points)+ Just hullPoints -> Right (ConvexPolygon (ExactLoop (rotateCycleLeast hullPoints)))++reflectConvexPolygon :: ConvexPolygon -> ConvexPolygon+reflectConvexPolygon polygon =+ ConvexPolygon+ ( ExactLoop+ ( rotateCycleLeast+ (NonEmpty.reverse (fmap negateExactPoint (convexPolygonPoints polygon)))+ )+ )++convexPolygonCentroid+ :: ConvexPolygon+ -> Either MinkowskiError ExactPoint+convexPolygonCentroid polygon = do+ let points = convexPolygonPoints polygon+ count = fromIntegral (NonEmpty.length points)+ (sumX, sumY) =+ List.foldl'+ (\(accumulatedX, accumulatedY) point ->+ let (x, y) = exactPointCoordinates point+ in (accumulatedX + x, accumulatedY + y))+ (0, 0)+ points+ x <- first MinkowskiExactArithmetic (exactDivide sumX count)+ y <- first MinkowskiExactArithmetic (exactDivide sumY count)+ pure (exactPoint x y)++-- | Erode one convex polygon by another through the strongest translated+-- support half-plane for each source edge. Sutherland--Hodgman descent keeps+-- the construction exact; a lower-dimensional residual is represented by the+-- empty polygonal region at this two-dimensional publication boundary.+erodeConvexBy+ :: ConvexPolygon+ -> ConvexPolygon+ -> Either MinkowskiError (Maybe ConvexPolygon)+erodeConvexBy source kernel = do+ let sourcePoints = convexPolygonPoints source+ kernelPoints = convexPolygonPoints kernel+ firstKernel = NonEmpty.head kernelPoints+ initial =+ map+ (`subtractExactPoints` firstKernel)+ (NonEmpty.toList sourcePoints)+ halfPlanes =+ [ strongestHalfPlane kernelPoints from to+ | (from, to) <- cyclePairs sourcePoints+ ]+ clipped <- foldM clipPolygon initial halfPlanes+ pure (ConvexPolygon . ExactLoop . rotateCycleLeast <$> convexHullPoints clipped)++edgeDirections :: NonEmpty ExactPoint -> NonEmpty ExactVector+edgeDirections = fmap (uncurry exactVectorFromPoints) . cyclePairsNonEmpty++mergeDirections+ :: NonEmpty ExactVector+ -> NonEmpty ExactVector+ -> NonEmpty ExactVector+mergeDirections (left :| leftTail) (right :| rightTail) =+ case compareExactVectorAngle left right of+ LT -> left :| mergeRemaining leftTail (right : rightTail)+ GT -> right :| mergeRemaining (left : leftTail) rightTail+ EQ -> addExactVectors left right :| mergeRemaining leftTail rightTail++mergeRemaining :: [ExactVector] -> [ExactVector] -> [ExactVector]+mergeRemaining [] right = right+mergeRemaining left [] = left+mergeRemaining left@(leftHead : leftTail) right@(rightHead : rightTail) =+ case compareExactVectorAngle leftHead rightHead of+ LT -> leftHead : mergeRemaining leftTail right+ GT -> rightHead : mergeRemaining left rightTail+ EQ -> addExactVectors leftHead rightHead : mergeRemaining leftTail rightTail++pointSweepKey :: ExactPoint -> (ExactRational, ExactRational)+pointSweepKey point =+ let (x, y) = exactPointCoordinates point+ in (y, x)++convexHullPoints :: [ExactPoint] -> Maybe (NonEmpty ExactPoint)+convexHullPoints submitted =+ case Set.toAscList (Set.fromList submitted) of+ firstPoint : secondPoint : thirdPoint : remaining ->+ let ordered = firstPoint : secondPoint : thirdPoint : remaining+ lower = dropFinal (reverse (List.foldl' hullStep [] ordered))+ upper = dropFinal (reverse (List.foldl' hullStep [] (reverse ordered)))+ in case lower <> upper of+ firstHullPoint : secondHullPoint : thirdHullPoint : hullTail ->+ Just (firstHullPoint :| (secondHullPoint : thirdHullPoint : hullTail))+ _ -> Nothing+ _ -> Nothing++hullStep :: [ExactPoint] -> ExactPoint -> [ExactPoint]+hullStep (current : previous : remaining) candidate+ | exactOrient2d previous current candidate /= GT =+ hullStep (previous : remaining) candidate+hullStep hull candidate = candidate : hull++dropFinal :: [value] -> [value]+dropFinal values =+ case reverse values of+ _ : remaining -> reverse remaining+ [] -> []++strongestHalfPlane+ :: NonEmpty ExactPoint+ -> ExactPoint+ -> ExactPoint+ -> (ExactPoint, ExactPoint)+strongestHalfPlane kernelPoints from to =+ let direction = exactVectorFromPoints from to+ supportPoint =+ case kernelPoints of+ initial :| remaining ->+ List.foldl'+ (\selected candidate ->+ if directionPointCross direction candidate+ < directionPointCross direction selected+ then candidate+ else selected)+ initial+ remaining+ in ( subtractExactPoints from supportPoint+ , subtractExactPoints to supportPoint+ )++directionPointCross :: ExactVector -> ExactPoint -> ExactRational+directionPointCross (ExactVector directionX directionY) point =+ let (x, y) = exactPointCoordinates point+ in directionX * y - directionY * x++clipPolygon+ :: [ExactPoint]+ -> (ExactPoint, ExactPoint)+ -> Either MinkowskiError [ExactPoint]+clipPolygon [] _ = Right []+clipPolygon polygon halfPlane =+ concat <$> traverse (clipEdge halfPlane) (cyclePairsList polygon)++clipEdge+ :: (ExactPoint, ExactPoint)+ -> (ExactPoint, ExactPoint)+ -> Either MinkowskiError [ExactPoint]+clipEdge (boundaryFrom, boundaryTo) (from, to) =+ case (inside from, inside to) of+ (True, True) -> Right [to]+ (True, False) -> (: []) <$> supportingLineIntersection from to boundaryFrom boundaryTo+ (False, True) -> do+ crossing <- supportingLineIntersection from to boundaryFrom boundaryTo+ pure [crossing, to]+ (False, False) -> Right []+ where+ inside point = exactOrient2d boundaryFrom boundaryTo point /= LT++supportingLineIntersection+ :: ExactPoint+ -> ExactPoint+ -> ExactPoint+ -> ExactPoint+ -> Either MinkowskiError ExactPoint+supportingLineIntersection lineFrom lineTo boundaryFrom boundaryTo = do+ clippedSegment <- first MinkowskiInvalidSegment (exactSegment lineFrom lineTo)+ boundarySegment <- first MinkowskiInvalidSegment (exactSegment boundaryFrom boundaryTo)+ first MinkowskiLineIntersection+ (exactSupportingLineIntersection clippedSegment boundarySegment)++addExactPoints :: ExactPoint -> ExactPoint -> ExactPoint+addExactPoints left right =+ let (leftX, leftY) = exactPointCoordinates left+ (rightX, rightY) = exactPointCoordinates right+ in exactPoint (leftX + rightX) (leftY + rightY)++subtractExactPoints :: ExactPoint -> ExactPoint -> ExactPoint+subtractExactPoints left right =+ let (leftX, leftY) = exactPointCoordinates left+ (rightX, rightY) = exactPointCoordinates right+ in exactPoint (leftX - rightX) (leftY - rightY)++negateExactPoint :: ExactPoint -> ExactPoint+negateExactPoint point =+ let (x, y) = exactPointCoordinates point+ in exactPoint (negate x) (negate y)++cyclePairsList :: [value] -> [(value, value)]+cyclePairsList = maybe [] cyclePairs . NonEmpty.nonEmpty
+ src-planar/Moonlight/Triangulation/Internal/Minkowski/Types.hs view
@@ -0,0 +1,80 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingStrategies #-}++-- | Closed vocabulary and invariant carriers for exact polygonal morphology.+module Moonlight.Triangulation.Internal.Minkowski.Types+ ( ConvexPolygon (..)+ , StructuringElement (..)+ , MinkowskiOperation (..)+ , MinkowskiError (..)+ , MinkowskiReceipt (..)+ ) where++import Control.DeepSeq (NFData)+import GHC.Generics (Generic)+import Moonlight.Triangulation.Exact+ ( ExactGeometryError+ , ExactIntersectionError+ , ExactPoint+ )+import Moonlight.Triangulation.Handles.HandleDefs (FaceId)+import Moonlight.Triangulation.Internal.ExactRational (ExactArithmeticError)+import Moonlight.Triangulation.Internal.Overlay.Types+ ( OverlayCellId+ , OverlayCellWitness+ , OverlayError+ )+import Moonlight.Triangulation.Internal.Region.Types+ ( ExactLoop+ , RegionPublicationError+ , RegionPointLocation+ , RegionValidationError+ )++newtype ConvexPolygon = ConvexPolygon ExactLoop+ deriving stock (Eq, Ord, Show, Generic)+ deriving anyclass (NFData)++newtype StructuringElement = StructuringElement ConvexPolygon+ deriving stock (Eq, Ord, Show, Generic)+ deriving anyclass (NFData)++data MinkowskiOperation+ = MinkowskiAddition+ | MinkowskiErosion+ | MinkowskiOpening+ | MinkowskiClosing+ deriving stock (Eq, Ord, Show, Generic)+ deriving anyclass (NFData)++data MinkowskiError+ = MinkowskiInvalidConvexLoop !RegionValidationError+ | MinkowskiInvalidSegment !ExactGeometryError+ | MinkowskiNonConvexTurn !Int !Ordering+ | MinkowskiOriginOutside !RegionPointLocation+ | MinkowskiExactArithmetic !ExactArithmeticError+ | MinkowskiLineIntersection !ExactIntersectionError+ | MinkowskiOverlayFailed !(OverlayError Bool Bool)+ | MinkowskiPublicationFailed !RegionPublicationError+ | MinkowskiOverlayCellWitness !OverlayCellWitness+ | MinkowskiFaceArity !FaceId !Int+ | MinkowskiCandidateCellMissing !OverlayCellId+ | MinkowskiInclusionAmbiguous !OverlayCellId !ExactPoint+ | MinkowskiConvexHullDegenerate ![ExactPoint]+ deriving stock (Eq, Show, Generic)+ deriving anyclass (NFData)++data MinkowskiReceipt = MinkowskiReceipt+ { minkowskiOperation :: !MinkowskiOperation+ , minkowskiInputComponents :: !Int+ , minkowskiConvexPieces :: !Int+ , minkowskiGeneratedPieces :: !Int+ , minkowskiGeneratedConvolutionEdges :: !Int+ , minkowskiOverlayPasses :: !Int+ , minkowskiExactCrossings :: !Int+ , minkowskiOutputCells :: !Int+ , minkowskiExactCoordinateBitGrowth :: !Int+ }+ deriving stock (Eq, Ord, Show, Generic)+ deriving anyclass (NFData)
+ src-planar/Moonlight/Triangulation/Internal/Overlay/Arrangement.hs view
@@ -0,0 +1,814 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingStrategies #-}++-- | Exact source normalization and binary64 embedding certification. This+-- module ends at the arrangement/resident seam: no DCEL face state crosses it.+module Moonlight.Triangulation.Internal.Overlay.Arrangement+ ( ExactEdgeKey+ , AtomicEdge (..)+ , atomicEdgeFrom+ , atomicEdgeTo+ , OverlayVertexSeed (..)+ , ArrangementMetrics (..)+ , CertifiedArrangement (..)+ , certifyArrangement+ , canonicalEdgeKey+ , atomicKey+ , compareAround+ ) where++import Data.Bifunctor (first)+import Data.List (sortBy)+import qualified Data.List as List+import Data.List.NonEmpty (NonEmpty (..))+import qualified Data.List.NonEmpty as NonEmpty+import qualified Data.Map.Strict as Map+import Data.Map.Strict (Map)+import qualified Data.Set as Set+import Data.Set (Set)+import qualified Data.Vector as V+import GHC.Generics (Generic)+import Moonlight.Triangulation.Exact+ ( ExactGeometryError+ , ExactPoint+ , ExactSegment+ , SegmentRelation (SegmentsShareEndpoint)+ , compareExactVectorAngle+ , exactPointCoordinates+ , exactPointFromPoint+ , exactSegment+ , exactSegmentEndpoints+ , exactVectorFromPoints+ )+import Moonlight.Triangulation.Internal.BoundaryCycle+ ( consecutivePairs+ , orderedPair+ , unorderedPairs+ )+import Moonlight.Triangulation.Internal.ExactRational+ ( ExactRational+ , exactDivide+ , exactRationalIsZero+ )+import Moonlight.Triangulation.Internal.ExactSegmentEvents+ ( ExactSegmentEvent (..)+ , ExactSegmentEventPlan+ , ExactSweepSegmentId (..)+ , exactSegmentEventPlan+ , exactSegmentEvents+ , exactSegmentPairChecks+ , exactSegmentRelationMap+ , exactSegmentSplitPoints+ , exactSegmentSweepMaximumHeight+ )+import Moonlight.Triangulation.Internal.Overlay.Embedding+ ( DraftId (..)+ , DraftIncidence (..)+ , DraftNeighborhood (..)+ , DraftReference (..)+ , DraftVertexId+ , ExactArrangementDraft (..)+ , LocalEmbeddingCertificate (..)+ , OverlayEmbeddingObstruction (..)+ , certifyLocalEmbedding+ )+import Moonlight.Triangulation.Internal.Overlay.Types+import Moonlight.Triangulation.Internal.Types (HasPosition (..), Point)+import Moonlight.Triangulation.Region+ ( ExactLoop+ , PlanarLayer+ , PolygonComponent+ , exactLoopPoints+ , planarLayerOutsideLabel+ , planarLayerRegions+ , planarRegionComponents+ , polygonHoleLoops+ , polygonOuterLoop+ )++type ExactEdgeKey = (ExactPoint, ExactPoint)++data SourceBoundary leftLabel rightLabel+ = LeftSourceBoundary+ !ExactSegment+ !leftLabel+ !(BoundaryVertexRef 'LeftOverlayOperand)+ !(BoundaryVertexRef 'LeftOverlayOperand)+ !(BoundaryEdgeRef 'LeftOverlayOperand)+ | RightSourceBoundary+ !ExactSegment+ !rightLabel+ !(BoundaryVertexRef 'RightOverlayOperand)+ !(BoundaryVertexRef 'RightOverlayOperand)+ !(BoundaryEdgeRef 'RightOverlayOperand)++type SourceBoundaryConstructor operand label leftLabel rightLabel =+ ExactSegment+ -> label+ -> BoundaryVertexRef operand+ -> BoundaryVertexRef operand+ -> BoundaryEdgeRef operand+ -> SourceBoundary leftLabel rightLabel++data AtomicContribution leftLabel rightLabel =+ AtomicContribution !(SourceBoundary leftLabel rightLabel) !Bool++data AtomicEdge leftLabel rightLabel = AtomicEdge+ { atomicEdgeSegment :: !ExactSegment+ , atomicEdgeOrigin :: !OverlayEdgeOrigin+ , atomicEdgeLeftTransition :: !(Maybe (leftLabel, leftLabel))+ , atomicEdgeRightTransition :: !(Maybe (rightLabel, rightLabel))+ }++data OriginAccumulation = OriginAccumulation+ { accumulatedLeftVertices :: !(Set (BoundaryVertexRef 'LeftOverlayOperand))+ , accumulatedRightVertices :: !(Set (BoundaryVertexRef 'RightOverlayOperand))+ , accumulatedLeftEdges :: !(Set (BoundaryEdgeRef 'LeftOverlayOperand))+ , accumulatedRightEdges :: !(Set (BoundaryEdgeRef 'RightOverlayOperand))+ }++data OverlayVertexSeed = OverlayVertexSeed+ { seedExactPoint :: !ExactPoint+ , seedEmbeddedPoint :: !Point+ , seedOrigin :: !OverlayVertexOrigin+ }+ deriving stock (Eq, Ord, Show, Generic)++instance HasPosition OverlayVertexSeed where+ position = seedEmbeddedPoint++data ArrangementMetrics = ArrangementMetrics+ { arrangementInputSegments :: !Int+ , arrangementRelationEvents :: !Int+ , arrangementExactCrossings :: !Int+ , arrangementOverlapIntervals :: !Int+ , arrangementEmbeddingCandidates :: !Int+ , arrangementTotalRelationChecks :: !Int+ , arrangementSweepMaximumHeight :: !Int+ }++data CertifiedArrangement leftLabel rightLabel = CertifiedArrangement+ { certifiedAtomicEdges :: !(V.Vector (AtomicEdge leftLabel rightLabel))+ , certifiedVertexSeeds :: !(V.Vector OverlayVertexSeed)+ , certifiedConstraints :: !(V.Vector (Int, Int))+ , certifiedInexactEmbeddingPoints :: !(Set ExactPoint)+ , certifiedMetrics :: !ArrangementMetrics+ }++certifyArrangement+ :: (Ord leftLabel, Ord rightLabel)+ => PlanarLayer leftLabel+ -> PlanarLayer rightLabel+ -> Either+ (OverlayError leftLabel rightLabel)+ (CertifiedArrangement leftLabel rightLabel)+certifyArrangement leftLayer rightLayer = do+ sources <- flattenLayers leftLayer rightLayer+ sourcePlan <-+ first OverlaySegmentEventsInvalid+ (exactSegmentEventPlan (V.map sourceExactSegment sources))+ atomicEdges <- normalizeAtomicEdges leftLayer rightLayer sources sourcePlan+ let atomicVector = V.fromList atomicEdges+ pointIds = exactPointIds atomicVector+ origins = vertexOrigins sources atomicVector+ atomicRelations = atomicEndpointRelations atomicVector+ draft <- exactArrangementDraft sources sourcePlan atomicVector pointIds atomicRelations+ localCertificate <- first OverlayEmbeddingRefused (certifyLocalEmbedding draft)+ projectedPoints <- projectedExactPoints localCertificate+ let inexactEmbeddingPoints =+ Set.fromList+ [ exactPoint+ | (exactPoint, vertexId) <- Map.toAscList pointIds+ , Map.lookup vertexId projectedPoints /= Just exactPoint+ ]+ projectedSegments <- projectedAtomicSegments atomicVector pointIds projectedPoints+ projectedPlan <-+ first OverlaySegmentEventsInvalid+ (exactSegmentEventPlan projectedSegments)+ dischargeGlobalRelations atomicRelations projectedPlan+ seeds <- overlayVertexSeeds pointIds origins localCertificate+ constraints <- atomicConstraints pointIds atomicVector+ let events = exactSegmentEvents sourcePlan+ pure+ CertifiedArrangement+ { certifiedAtomicEdges = atomicVector+ , certifiedVertexSeeds = seeds+ , certifiedConstraints = constraints+ , certifiedInexactEmbeddingPoints = inexactEmbeddingPoints+ , certifiedMetrics =+ ArrangementMetrics+ { arrangementInputSegments = V.length sources+ , arrangementRelationEvents = length events+ , arrangementExactCrossings =+ length [() | ExactProperCrossing {} <- events]+ , arrangementOverlapIntervals =+ length [() | ExactCollinearOverlap {} <- events]+ , arrangementEmbeddingCandidates = Map.size pointIds+ , arrangementTotalRelationChecks =+ exactSegmentPairChecks sourcePlan+ + exactSegmentPairChecks projectedPlan+ , arrangementSweepMaximumHeight =+ max+ (exactSegmentSweepMaximumHeight sourcePlan)+ (exactSegmentSweepMaximumHeight projectedPlan)+ }+ }+flattenLayers+ :: PlanarLayer leftLabel+ -> PlanarLayer rightLabel+ -> Either (OverlayError leftLabel rightLabel) (V.Vector (SourceBoundary leftLabel rightLabel))+flattenLayers leftLayer rightLayer = do+ leftSources <- flattenLayer LeftSourceBoundary leftLayer+ rightSources <- flattenLayer RightSourceBoundary rightLayer+ pure (V.fromList (leftSources <> rightSources))++flattenLayer+ :: SourceBoundaryConstructor operand label leftLabel rightLabel+ -> PlanarLayer label+ -> Either (OverlayError leftLabel rightLabel) [SourceBoundary leftLabel rightLabel]+flattenLayer makeBoundary layer =+ fmap concat+ ( traverse+ (\(componentIndex, (label, component)) ->+ flattenComponent makeBoundary componentIndex label component)+ (zip [0 ..] (labelledComponents layer))+ )+{-# INLINE flattenLayer #-}++labelledComponents :: PlanarLayer label -> [(label, PolygonComponent)]+labelledComponents layer =+ [ (label, component)+ | (label, region) <- Map.toAscList (planarLayerRegions layer)+ , component <- planarRegionComponents region+ ]++flattenComponent+ :: SourceBoundaryConstructor operand label leftLabel rightLabel+ -> Int+ -> label+ -> PolygonComponent+ -> Either (OverlayError leftLabel rightLabel) [SourceBoundary leftLabel rightLabel]+flattenComponent makeBoundary componentIndex label component = do+ outer <-+ flattenLoop+ makeBoundary+ componentIndex+ BoundaryOuterLoop+ label+ (polygonOuterLoop component)+ holes <-+ fmap concat+ ( traverse+ (\(holeIndex, loop) ->+ flattenLoop+ makeBoundary+ componentIndex+ (BoundaryHoleLoop holeIndex)+ label+ loop)+ (zip [0 ..] (polygonHoleLoops component))+ )+ pure (outer <> holes)+{-# INLINE flattenComponent #-}++flattenLoop+ :: SourceBoundaryConstructor operand label leftLabel rightLabel+ -> Int+ -> BoundaryLoopRef+ -> label+ -> ExactLoop+ -> Either (OverlayError leftLabel rightLabel) [SourceBoundary leftLabel rightLabel]+flattenLoop makeBoundary componentIndex loopRef label loop =+ traverse constructSource (indexedCycle (exactLoopPoints loop))+ where+ constructSource (edgeIndex, fromIndex, from, toIndex, to) = do+ segment <- first (sourceGeometryError from) (exactSegment from to)+ pure+ ( makeBoundary+ segment+ label+ (BoundaryRef componentIndex loopRef fromIndex)+ (BoundaryRef componentIndex loopRef toIndex)+ (BoundaryRef componentIndex loopRef edgeIndex)+ )+{-# INLINE flattenLoop #-}++sourceGeometryError+ :: ExactPoint+ -> ExactGeometryError+ -> OverlayError leftLabel rightLabel+sourceGeometryError point _ = OverlayArrangementInvalid (OverlayRotationDegenerate point)++indexedCycle :: NonEmpty value -> [(Int, Int, value, Int, value)]+indexedCycle (firstValue :| remaining) =+ let values = firstValue : remaining+ count = length values+ in [ (index, index, from, (index + 1) `mod` count, to)+ | (index, (from, to)) <- zip [0 ..] (zip values (remaining <> [firstValue]))+ ]++sourceExactSegment :: SourceBoundary leftLabel rightLabel -> ExactSegment+sourceExactSegment (LeftSourceBoundary segment _ _ _ _) = segment+sourceExactSegment (RightSourceBoundary segment _ _ _ _) = segment++normalizeAtomicEdges+ :: (Ord leftLabel, Ord rightLabel)+ => PlanarLayer leftLabel+ -> PlanarLayer rightLabel+ -> V.Vector (SourceBoundary leftLabel rightLabel)+ -> ExactSegmentEventPlan+ -> Either (OverlayError leftLabel rightLabel) [AtomicEdge leftLabel rightLabel]+normalizeAtomicEdges leftLayer rightLayer sources plan =+ fmap concat+ ( traverse+ (resolveAtomicContributions leftOutside rightOutside)+ (Map.toAscList grouped)+ )+ where+ leftOutside = planarLayerOutsideLabel leftLayer+ rightOutside = planarLayerOutsideLabel rightLayer+ grouped =+ V.ifoldl'+ (\groups sourceIndex source ->+ List.foldl'+ (insertAtomic source)+ groups+ (consecutivePairs (exactSegmentSplitPoints plan (ExactSweepSegmentId sourceIndex))))+ Map.empty+ sources+ insertAtomic+ :: SourceBoundary leftLabel' rightLabel'+ -> Map ExactEdgeKey [AtomicContribution leftLabel' rightLabel']+ -> (ExactPoint, ExactPoint)+ -> Map ExactEdgeKey [AtomicContribution leftLabel' rightLabel']+ insertAtomic source groups (from, to)+ | from == to = groups+ | otherwise =+ let key@(canonicalFrom, _) = canonicalEdgeKey from to+ contribution = AtomicContribution source (from == canonicalFrom)+ in Map.insertWith (<>) key [contribution] groups++resolveAtomicContributions+ :: (Ord leftLabel, Ord rightLabel)+ => leftLabel+ -> rightLabel+ -> (ExactEdgeKey, [AtomicContribution leftLabel rightLabel])+ -> Either (OverlayError leftLabel rightLabel) [AtomicEdge leftLabel rightLabel]+resolveAtomicContributions leftOutside rightOutside ((from, to), contributions) = do+ let+ ( leftLabelsOnLeft+ , leftLabelsOnRight+ , rightLabelsOnLeft+ , rightLabelsOnRight+ , leftSources+ , rightSources+ ) =+ List.foldl'+ collectContribution+ (Set.empty, Set.empty, Set.empty, Set.empty, Set.empty, Set.empty)+ contributions+ origin =+ OverlayEdgeOrigin+ (Set.toAscList leftSources)+ (Set.toAscList rightSources)+ leftTransition <-+ resolveTransition+ OverlayLeftSourceSideConflict+ origin+ leftOutside+ leftLabelsOnLeft+ leftLabelsOnRight+ rightTransition <-+ resolveTransition+ OverlayRightSourceSideConflict+ origin+ rightOutside+ rightLabelsOnLeft+ rightLabelsOnRight+ if transitionIsIdentity leftTransition && transitionIsIdentity rightTransition+ then Right []+ else do+ segment <- first (sourceGeometryError from) (exactSegment from to)+ Right+ [ AtomicEdge+ { atomicEdgeSegment = segment+ , atomicEdgeOrigin = origin+ , atomicEdgeLeftTransition = leftTransition+ , atomicEdgeRightTransition = rightTransition+ }+ ]+ where+ collectContribution+ :: (Ord leftLabel', Ord rightLabel')+ => ( Set leftLabel'+ , Set leftLabel'+ , Set rightLabel'+ , Set rightLabel'+ , Set (BoundaryEdgeRef 'LeftOverlayOperand)+ , Set (BoundaryEdgeRef 'RightOverlayOperand)+ )+ -> AtomicContribution leftLabel' rightLabel'+ -> ( Set leftLabel'+ , Set leftLabel'+ , Set rightLabel'+ , Set rightLabel'+ , Set (BoundaryEdgeRef 'LeftOverlayOperand)+ , Set (BoundaryEdgeRef 'RightOverlayOperand)+ )+ collectContribution+ ( !leftLeft+ , !leftRight+ , !rightLeft+ , !rightRight+ , !leftSources+ , !rightSources+ )+ contribution =+ case contribution of+ AtomicContribution (LeftSourceBoundary _ label _ _ reference) follows ->+ ( if follows then Set.insert label leftLeft else leftLeft+ , if follows then leftRight else Set.insert label leftRight+ , rightLeft+ , rightRight+ , Set.insert reference leftSources+ , rightSources+ )+ AtomicContribution (RightSourceBoundary _ label _ _ reference) follows ->+ ( leftLeft+ , leftRight+ , if follows then Set.insert label rightLeft else rightLeft+ , if follows then rightRight else Set.insert label rightRight+ , leftSources+ , Set.insert reference rightSources+ )++resolveTransition+ :: (OverlayEdgeOrigin -> NonEmpty label -> OverlayArrangementObstruction leftLabel rightLabel)+ -> OverlayEdgeOrigin+ -> label+ -> Set label+ -> Set label+ -> Either (OverlayError leftLabel rightLabel) (Maybe (label, label))+resolveTransition sideConflict origin outside labelsOnLeft labelsOnRight+ | Set.null labelsOnLeft && Set.null labelsOnRight = Right Nothing+ | otherwise =+ Just+ <$> ((,)+ <$> resolveSide sideConflict origin outside labelsOnLeft+ <*> resolveSide sideConflict origin outside labelsOnRight)+{-# INLINE resolveTransition #-}++transitionIsIdentity :: Eq label => Maybe (label, label) -> Bool+transitionIsIdentity Nothing = True+transitionIsIdentity (Just (leftLabel, rightLabel)) = leftLabel == rightLabel++resolveSide+ :: (OverlayEdgeOrigin -> NonEmpty label -> OverlayArrangementObstruction leftLabel rightLabel)+ -> OverlayEdgeOrigin+ -> label+ -> Set label+ -> Either (OverlayError leftLabel rightLabel) label+resolveSide sideConflict origin outside labels =+ case Set.toAscList labels of+ [] -> Right outside+ [label] -> Right label+ firstLabel : remaining ->+ Left+ ( OverlayArrangementInvalid+ (sideConflict origin (firstLabel :| remaining))+ )+{-# INLINE resolveSide #-}++exactPointIds+ :: V.Vector (AtomicEdge leftLabel rightLabel)+ -> Map ExactPoint DraftVertexId+exactPointIds edges =+ Map.fromAscList+ ( zip+ (Set.toAscList (V.foldl' collect Set.empty edges))+ (map DraftId [0 ..])+ )+ where+ collect+ :: Set ExactPoint+ -> AtomicEdge leftLabel rightLabel+ -> Set ExactPoint+ collect points edge = Set.insert (atomicEdgeFrom edge) (Set.insert (atomicEdgeTo edge) points)++vertexOrigins+ :: V.Vector (SourceBoundary leftLabel rightLabel)+ -> V.Vector (AtomicEdge leftLabel rightLabel)+ -> Map ExactPoint OverlayVertexOrigin+vertexOrigins sources atomicEdges =+ Map.map finalizeOrigin+ ( V.foldl'+ addAtomicOrigin+ (V.foldl' addSourceEndpoints Map.empty sources)+ atomicEdges+ )++emptyOrigin :: OriginAccumulation+emptyOrigin = OriginAccumulation Set.empty Set.empty Set.empty Set.empty++mergeOrigin :: OriginAccumulation -> OriginAccumulation -> OriginAccumulation+mergeOrigin left right =+ OriginAccumulation+ { accumulatedLeftVertices = accumulatedLeftVertices left <> accumulatedLeftVertices right+ , accumulatedRightVertices = accumulatedRightVertices left <> accumulatedRightVertices right+ , accumulatedLeftEdges = accumulatedLeftEdges left <> accumulatedLeftEdges right+ , accumulatedRightEdges = accumulatedRightEdges left <> accumulatedRightEdges right+ }++addSourceEndpoints+ :: Map ExactPoint OriginAccumulation+ -> SourceBoundary leftLabel rightLabel+ -> Map ExactPoint OriginAccumulation+addSourceEndpoints origins source =+ case source of+ LeftSourceBoundary segment _ fromReference toReference _ ->+ let (from, to) = exactSegmentEndpoints segment+ in insertOrigin to (emptyOrigin{accumulatedLeftVertices = Set.singleton toReference})+ (insertOrigin from (emptyOrigin{accumulatedLeftVertices = Set.singleton fromReference}) origins)+ RightSourceBoundary segment _ fromReference toReference _ ->+ let (from, to) = exactSegmentEndpoints segment+ in insertOrigin to (emptyOrigin{accumulatedRightVertices = Set.singleton toReference})+ (insertOrigin from (emptyOrigin{accumulatedRightVertices = Set.singleton fromReference}) origins)++addAtomicOrigin+ :: Map ExactPoint OriginAccumulation+ -> AtomicEdge leftLabel rightLabel+ -> Map ExactPoint OriginAccumulation+addAtomicOrigin origins edge =+ let origin = atomicEdgeOrigin edge+ accumulation =+ emptyOrigin+ { accumulatedLeftEdges = Set.fromList (overlayEdgeLeftSources origin)+ , accumulatedRightEdges = Set.fromList (overlayEdgeRightSources origin)+ }+ in insertOrigin (atomicEdgeTo edge) accumulation+ (insertOrigin (atomicEdgeFrom edge) accumulation origins)++insertOrigin+ :: ExactPoint+ -> OriginAccumulation+ -> Map ExactPoint OriginAccumulation+ -> Map ExactPoint OriginAccumulation+insertOrigin = Map.insertWith mergeOrigin++finalizeOrigin :: OriginAccumulation -> OverlayVertexOrigin+finalizeOrigin accumulated =+ OverlayVertexOrigin+ { overlayOriginLeftVertices = Set.toAscList (accumulatedLeftVertices accumulated)+ , overlayOriginRightVertices = Set.toAscList (accumulatedRightVertices accumulated)+ , overlayOriginLeftEdges = Set.toAscList (accumulatedLeftEdges accumulated)+ , overlayOriginRightEdges = Set.toAscList (accumulatedRightEdges accumulated)+ }++exactArrangementDraft+ :: V.Vector (SourceBoundary leftLabel rightLabel)+ -> ExactSegmentEventPlan+ -> V.Vector (AtomicEdge leftLabel rightLabel)+ -> Map ExactPoint DraftVertexId+ -> Map (ExactSweepSegmentId, ExactSweepSegmentId) SegmentRelation+ -> Either (OverlayError leftLabel rightLabel) ExactArrangementDraft+exactArrangementDraft sources sourcePlan atomicEdges pointIds atomicRelations = do+ draftSegmentsMap <-+ Map.fromList+ <$> traverse+ (\(segmentIndex, edge) -> do+ from <- requireDraftVertex pointIds (atomicEdgeFrom edge)+ to <- requireDraftVertex pointIds (atomicEdgeTo edge)+ pure (DraftId segmentIndex, (from, to)))+ (V.toList (V.indexed atomicEdges))+ memberships <-+ Map.fromList+ <$> traverse+ (\(sourceIndex, source) -> do+ values <-+ traverse+ (\point -> do+ parameter <- exactSourceParameter source point+ vertex <- requireDraftVertex pointIds point+ pure (parameter, vertex))+ (exactSegmentSplitPoints sourcePlan (ExactSweepSegmentId sourceIndex))+ pure (DraftId sourceIndex, values))+ (V.toList (V.indexed sources))+ neighborhoods <- buildDraftNeighborhoods atomicEdges pointIds+ let incidences =+ [ DraftIncidence+ (DraftId leftIndex)+ (DraftId rightIndex)+ relation+ | ((ExactSweepSegmentId leftIndex, ExactSweepSegmentId rightIndex), relation) <-+ Map.toAscList atomicRelations+ ]+ pure+ ExactArrangementDraft+ { draftVertices = Map.fromList [(vertexId, point) | (point, vertexId) <- Map.toAscList pointIds]+ , draftSegments = draftSegmentsMap+ , draftSourceMemberships = memberships+ , draftIncidences = incidences+ , draftNeighborhoods = neighborhoods+ }++requireDraftVertex+ :: Map ExactPoint DraftVertexId+ -> ExactPoint+ -> Either (OverlayError leftLabel rightLabel) DraftVertexId+requireDraftVertex pointIds point =+ case Map.lookup point pointIds of+ Just vertex -> Right vertex+ Nothing -> Left (OverlayProvenanceIncomplete (OverlayExactVertexMissing point))++exactSourceParameter+ :: SourceBoundary leftLabel rightLabel+ -> ExactPoint+ -> Either (OverlayError leftLabel rightLabel) ExactRational+exactSourceParameter source point =+ let (from, to) = exactSegmentEndpoints (sourceExactSegment source)+ (fromX, fromY) = exactPointCoordinates from+ (toX, toY) = exactPointCoordinates to+ (pointX, pointY) = exactPointCoordinates point+ (numerator, denominator) =+ if exactRationalIsZero (toX - fromX)+ then (pointY - fromY, toY - fromY)+ else (pointX - fromX, toX - fromX)+ in first OverlayExactArithmetic (exactDivide numerator denominator)++buildDraftNeighborhoods+ :: V.Vector (AtomicEdge leftLabel rightLabel)+ -> Map ExactPoint DraftVertexId+ -> Either (OverlayError leftLabel rightLabel) [DraftNeighborhood]+buildDraftNeighborhoods edges pointIds =+ traverse neighborhood (Map.toAscList pointIds)+ where+ neighborhood (centerPoint, centerId) = do+ let neighbors = Map.findWithDefault Set.empty centerPoint adjacency+ orderedPoints <-+ case NonEmpty.nonEmpty (sortBy (compareAround centerPoint) (Set.toList neighbors)) of+ Just points -> Right points+ Nothing -> Left (OverlayArrangementInvalid (OverlayRotationDegenerate centerPoint))+ orderedIds <- traverse (requireDraftVertex pointIds) orderedPoints+ pure (DraftNeighborhood centerId orderedIds)+ adjacency =+ V.foldl'+ (\graph edge ->+ Map.insertWith Set.union (atomicEdgeTo edge) (Set.singleton (atomicEdgeFrom edge))+ (Map.insertWith Set.union (atomicEdgeFrom edge) (Set.singleton (atomicEdgeTo edge)) graph))+ Map.empty+ edges++compareAround :: ExactPoint -> ExactPoint -> ExactPoint -> Ordering+compareAround center left right =+ case+ compareExactVectorAngle+ (exactVectorFromPoints center left)+ (exactVectorFromPoints center right) of+ EQ -> compare left right+ ordering -> ordering++-- | Normalization splits every source at every exact event and coalesces+-- duplicate intervals. Distinct atomics can therefore meet only at a stored+-- endpoint; their entire relation section is the endpoint-incidence index.+atomicEndpointRelations+ :: V.Vector (AtomicEdge leftLabel rightLabel)+ -> Map (ExactSweepSegmentId, ExactSweepSegmentId) SegmentRelation+atomicEndpointRelations edges =+ Map.fromList+ [ ( orderedPair+ (ExactSweepSegmentId leftIndex)+ (ExactSweepSegmentId rightIndex)+ , SegmentsShareEndpoint+ )+ | incident <- Map.elems incidenceByPoint+ , (leftIndex, rightIndex) <- unorderedPairs (Set.toAscList incident)+ ]+ where+ incidenceByPoint =+ V.ifoldl'+ (\incidence index edge ->+ Map.insertWith Set.union (atomicEdgeTo edge) (Set.singleton index)+ ( Map.insertWith Set.union+ (atomicEdgeFrom edge)+ (Set.singleton index)+ incidence+ ))+ Map.empty+ edges+projectedExactPoints+ :: LocalEmbeddingCertificate+ -> Either (OverlayError leftLabel rightLabel) (Map DraftVertexId ExactPoint)+projectedExactPoints certificate =+ Map.traverseWithKey+ (\vertex point ->+ first+ (\projectionError ->+ OverlayEmbeddingRefused+ (VertexProjectionRefused vertex projectionError :| []))+ (exactPointFromPoint point))+ (certificateRoundedVertices certificate)++projectedAtomicSegments+ :: V.Vector (AtomicEdge leftLabel rightLabel)+ -> Map ExactPoint DraftVertexId+ -> Map DraftVertexId ExactPoint+ -> Either (OverlayError leftLabel rightLabel) (V.Vector ExactSegment)+projectedAtomicSegments edges pointIds projected =+ V.imapM project edges+ where+ project segmentIndex edge = do+ fromId <- requireDraftVertex pointIds (atomicEdgeFrom edge)+ toId <- requireDraftVertex pointIds (atomicEdgeTo edge)+ from <- requireProjected fromId+ to <- requireProjected toId+ first+ (\_ ->+ OverlayEmbeddingRefused+ ( ProjectedSegmentCollapsed+ (DraftId segmentIndex)+ fromId+ toId+ :| []+ ))+ (exactSegment from to)+ requireProjected vertex =+ case Map.lookup vertex projected of+ Just point -> Right point+ Nothing ->+ Left+ ( OverlayEmbeddingRefused+ (DraftReferenceMissing (DraftVertexReference vertex) :| [])+ )++dischargeGlobalRelations+ :: Map (ExactSweepSegmentId, ExactSweepSegmentId) SegmentRelation+ -> ExactSegmentEventPlan+ -> Either (OverlayError leftLabel rightLabel) ()+dischargeGlobalRelations exactRelations projectedPlan =+ case NonEmpty.nonEmpty obstructions of+ Nothing -> Right ()+ Just failures -> Left (OverlayEmbeddingRefused failures)+ where+ projectedRelations = exactSegmentRelationMap projectedPlan+ keys = Set.toAscList (Map.keysSet exactRelations <> Map.keysSet projectedRelations)+ obstructions = concatMap compareRelation keys+ compareRelation key@(ExactSweepSegmentId leftId, ExactSweepSegmentId rightId) =+ case (Map.lookup key exactRelations, Map.lookup key projectedRelations) of+ (Nothing, Just projected) ->+ [GlobalRelationAdded (DraftId leftId) (DraftId rightId) projected]+ (Just exact, Nothing) ->+ [GlobalRelationRemoved (DraftId leftId) (DraftId rightId) exact]+ (Just exact, Just projected)+ | exact /= projected ->+ [GlobalRelationChanged (DraftId leftId) (DraftId rightId) exact projected]+ _ -> []++overlayVertexSeeds+ :: Map ExactPoint DraftVertexId+ -> Map ExactPoint OverlayVertexOrigin+ -> LocalEmbeddingCertificate+ -> Either (OverlayError leftLabel rightLabel) (V.Vector OverlayVertexSeed)+overlayVertexSeeds pointIds origins certificate =+ V.fromList+ <$> traverse+ (\(point, vertexId) -> do+ embedded <-+ case Map.lookup vertexId (certificateRoundedVertices certificate) of+ Just value -> Right value+ Nothing ->+ Left+ ( OverlayEmbeddingRefused+ (DraftReferenceMissing (DraftVertexReference vertexId) :| [])+ )+ origin <-+ case Map.lookup point origins of+ Just value -> Right value+ Nothing -> Left (OverlayProvenanceIncomplete (OverlayExactVertexMissing point))+ pure (OverlayVertexSeed point embedded origin))+ (Map.toAscList pointIds)++atomicConstraints+ :: Map ExactPoint DraftVertexId+ -> V.Vector (AtomicEdge leftLabel rightLabel)+ -> Either (OverlayError leftLabel rightLabel) (V.Vector (Int, Int))+atomicConstraints pointIds =+ V.mapM+ (\edge -> do+ DraftId from <- requireDraftVertex pointIds (atomicEdgeFrom edge)+ DraftId to <- requireDraftVertex pointIds (atomicEdgeTo edge)+ pure (from, to))+++canonicalEdgeKey :: ExactPoint -> ExactPoint -> ExactEdgeKey+canonicalEdgeKey = orderedPair++atomicEdgeFrom :: AtomicEdge leftLabel rightLabel -> ExactPoint+atomicEdgeFrom = fst . exactSegmentEndpoints . atomicEdgeSegment++atomicEdgeTo :: AtomicEdge leftLabel rightLabel -> ExactPoint+atomicEdgeTo = snd . exactSegmentEndpoints . atomicEdgeSegment++atomicKey :: AtomicEdge leftLabel rightLabel -> ExactEdgeKey+atomicKey edge = (atomicEdgeFrom edge, atomicEdgeTo edge)
+ src-planar/Moonlight/Triangulation/Internal/Overlay/Embedding.hs view
@@ -0,0 +1,533 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingStrategies #-}++-- | Bounded certification of local binary64 embedding obligations for a+-- declared exact arrangement draft.+module Moonlight.Triangulation.Internal.Overlay.Embedding+ ( DraftId (..)+ , DraftVertexId+ , DraftSegmentId+ , DraftSourceId+ , DraftIncidence (..)+ , DraftNeighborhood (..)+ , ExactArrangementDraft (..)+ , DraftReference (..)+ , OverlayEmbeddingObstruction (..)+ , EmbeddingObligation (..)+ , EmbeddingResidual+ , residualUndischargedObligations+ , milestoneOneResidual+ , LocalEmbeddingCertificate (..)+ , certifyLocalEmbedding+ ) where++import Control.DeepSeq (NFData)+import Data.List (sort, tails)+import qualified Data.List as List+import Data.List.NonEmpty (NonEmpty (..))+import qualified Data.List.NonEmpty as NonEmpty+import Data.Map.Strict (Map)+import qualified Data.Map.Strict as Map+import GHC.Generics (Generic)+import Moonlight.Triangulation.Exact+ ( ExactPoint+ , SegmentRelation+ , exactPointToEmbeddingCandidate+ , exactSegmentRelation+ )+import Moonlight.Triangulation.Internal.BoundaryCycle (cyclePairs)+import Moonlight.Triangulation.Internal.ExactRational (ExactRational)+import qualified Moonlight.Triangulation.Math as Math+ ( orient2d+ , segmentRelation+ )+import Moonlight.Triangulation.Types+ ( Point (..)+ , PointValidationError+ )++-- | Phantom draft kind; three incompatible identifiers share one scalar owner.+data DraftEntity = DraftVertexEntity | DraftSegmentEntity | DraftSourceEntity++newtype DraftId (entity :: DraftEntity) = DraftId Int+ deriving stock (Eq, Ord, Show, Generic)+ deriving anyclass (NFData)++type DraftVertexId = DraftId 'DraftVertexEntity+type DraftSegmentId = DraftId 'DraftSegmentEntity+type DraftSourceId = DraftId 'DraftSourceEntity++-- | A declared relation between two atomic draft segments.+data DraftIncidence = DraftIncidence+ { -- | First declared atomic segment.+ draftIncidenceFirstSegment :: !DraftSegmentId+ , -- | Second declared atomic segment.+ draftIncidenceSecondSegment :: !DraftSegmentId+ , -- | Relation declared to hold in both exact and rounded geometry.+ draftIncidenceRelation :: !SegmentRelation+ }+ deriving stock (Eq, Ord, Show, Generic)+ deriving anyclass (NFData)++-- | One exact vertex and the cyclic neighbor order declared around it.+data DraftNeighborhood = DraftNeighborhood+ { -- | Center of the declared local rotation.+ draftNeighborhoodCenter :: !DraftVertexId+ , -- | Neighbors in cyclic rotation order.+ draftNeighborhoodNeighbors :: !(NonEmpty DraftVertexId)+ }+ deriving stock (Eq, Ord, Show, Generic)+ deriving anyclass (NFData)++-- | A hand-built exact arrangement draft. It contains only declared local+-- structure; no arrangement or global crossing search is derived here.+data ExactArrangementDraft = ExactArrangementDraft+ { -- | Exact coordinates keyed by draft-local vertex identity.+ draftVertices :: !(Map DraftVertexId ExactPoint)+ , -- | Atomic segment endpoint identities.+ draftSegments :: !(Map DraftSegmentId (DraftVertexId, DraftVertexId))+ , -- | Exact split parameters and vertices in source-segment order.+ draftSourceMemberships :: !(Map DraftSourceId [(ExactRational, DraftVertexId)])+ , -- | Segment relations declared to remain invariant under projection.+ draftIncidences :: ![DraftIncidence]+ , -- | Cyclic local rotations declared to retain their neighbor order.+ draftNeighborhoods :: ![DraftNeighborhood]+ }+ deriving stock (Eq, Show, Generic)+ deriving anyclass (NFData)++-- | The typed identity of a missing draft reference.+data DraftReference+ = DraftVertexReference !DraftVertexId+ | DraftSegmentReference !DraftSegmentId+ deriving stock (Eq, Ord, Show, Generic)+ deriving anyclass (NFData)++-- | A witness that prevents local embedding certification.+data OverlayEmbeddingObstruction+ = DraftReferenceMissing !DraftReference+ | VertexProjectionRefused !DraftVertexId !PointValidationError+ | RoundedVerticesCollide !DraftVertexId !DraftVertexId !Point+ | SplitOrderNotPreserved !DraftSourceId !DraftVertexId !DraftVertexId+ | IncidenceRelationChanged+ !DraftSegmentId+ !DraftSegmentId+ !SegmentRelation+ !SegmentRelation+ !SegmentRelation+ | NeighborhoodRotationChanged+ !DraftVertexId+ !(NonEmpty DraftVertexId)+ !(NonEmpty DraftVertexId)+ | GlobalRelationAdded+ !DraftSegmentId+ !DraftSegmentId+ !SegmentRelation+ | GlobalRelationRemoved+ !DraftSegmentId+ !DraftSegmentId+ !SegmentRelation+ | GlobalRelationChanged+ !DraftSegmentId+ !DraftSegmentId+ !SegmentRelation+ !SegmentRelation+ | ProjectedSegmentCollapsed+ !DraftSegmentId+ !DraftVertexId+ !DraftVertexId+ deriving stock (Eq, Show, Generic)+ deriving anyclass (NFData)++-- | An embedding obligation not discharged by the bounded local certifier.+data EmbeddingObligation+ = GlobalNoNewCrossing+ deriving stock (Bounded, Enum, Eq, Ord, Show, Generic)+ deriving anyclass (NFData)++-- | A nonempty collection of obligations deferred to a later owner.+newtype EmbeddingResidual = EmbeddingResidual (NonEmpty EmbeddingObligation)+ deriving stock (Eq, Ord, Show, Generic)+ deriving anyclass (NFData)++-- | Read the obligations that remain explicitly undischarged.+residualUndischargedObligations+ :: EmbeddingResidual+ -> NonEmpty EmbeddingObligation+residualUndischargedObligations (EmbeddingResidual obligations) = obligations++-- | The Milestone 1 residual: the arrangement sweep has not yet proved global+-- absence of new crossings.+milestoneOneResidual :: EmbeddingResidual+milestoneOneResidual = EmbeddingResidual (GlobalNoNewCrossing :| [])++-- | A certificate for exactly four local obligations on a declared draft:+-- vertex distinctness, source split order, declared incidences, and local+-- neighborhood rotation. Global absence of new crossings is unproved until+-- the Milestone 2 arrangement sweep supplies the complete obligation set.+data LocalEmbeddingCertificate = LocalEmbeddingCertificate+ { -- | Number of rounded vertex-pair distinctness checks discharged.+ certificateRoundedVertexDistinctnessCount :: !Int+ , -- | Number of adjacent source split-order checks discharged.+ certificateSplitOrderPreservationCount :: !Int+ , -- | Number of declared incidence checks discharged.+ certificateIncidenceRelationPreservationCount :: !Int+ , -- | Number of neighborhood rotation entries certified.+ certificateNeighborhoodRotationPreservationCount :: !Int+ , -- | The candidate projection certified by the four local obligations.+ certificateRoundedVertices :: !(Map DraftVertexId Point)+ , -- | The necessarily nonempty global obligation residual.+ certificateResidual :: !EmbeddingResidual+ }+ deriving stock (Eq, Show, Generic)+ deriving anyclass (NFData)++-- | Certify the four bounded local embedding obligations, collecting every+-- witness within each obligation and preserving obligation order.+certifyLocalEmbedding+ :: ExactArrangementDraft+ -> Either (NonEmpty OverlayEmbeddingObstruction) LocalEmbeddingCertificate+certifyLocalEmbedding draft =+ case NonEmpty.nonEmpty (structuralObstructions draft) of+ Just obstructions -> Left obstructions+ Nothing ->+ case projectVertices (draftVertices draft) of+ Invalid obstructions -> Left obstructions+ Valid projectedVertices ->+ certifyProjectedDraft draft projectedVertices++data Validation value+ = Invalid !(NonEmpty OverlayEmbeddingObstruction)+ | Valid value++instance Functor Validation where+ fmap _ (Invalid obstructions) = Invalid obstructions+ fmap transform (Valid value) = Valid (transform value)++instance Applicative Validation where+ pure = Valid+ Invalid left <*> Invalid right = Invalid (left <> right)+ Invalid obstructions <*> Valid _ = Invalid obstructions+ Valid _ <*> Invalid obstructions = Invalid obstructions+ Valid transform <*> Valid value = Valid (transform value)++invalid :: OverlayEmbeddingObstruction -> Validation value+invalid obstruction = Invalid (obstruction :| [])++data ProjectedVertex = ProjectedVertex+ { projectedExactPoint :: !ExactPoint+ , projectedRoundedPoint :: !Point+ }++data ResolvedSegment = ResolvedSegment+ { resolvedSegmentId :: !DraftSegmentId+ , resolvedSegmentFrom :: !ResolvedVertex+ , resolvedSegmentTo :: !ResolvedVertex+ }++data ResolvedVertex = ResolvedVertex+ { resolvedVertexId :: !DraftVertexId+ , resolvedProjectedVertex :: !ProjectedVertex+ }++data ResolvedMembership = ResolvedMembership+ { resolvedMembershipParameter :: !ExactRational+ , resolvedMembershipVertex :: !ResolvedVertex+ }++structuralObstructions+ :: ExactArrangementDraft+ -> [OverlayEmbeddingObstruction]+structuralObstructions draft =+ segmentEndpointObstructions+ <> sourceMembershipObstructions+ <> incidenceObstructions+ <> neighborhoodObstructions+ where+ vertices = draftVertices draft+ segments = draftSegments draft+ missingVertex vertexId =+ [ DraftReferenceMissing (DraftVertexReference vertexId)+ | Map.notMember vertexId vertices+ ]+ missingSegment segmentId =+ [ DraftReferenceMissing (DraftSegmentReference segmentId)+ | Map.notMember segmentId segments+ ]+ segmentEndpointObstructions =+ concatMap+ (\(_, (from, to)) -> missingVertex from <> missingVertex to)+ (Map.toAscList segments)+ sourceMembershipObstructions =+ concatMap+ (concatMap (missingVertex . snd) . snd)+ (Map.toAscList (draftSourceMemberships draft))+ incidenceObstructions =+ concatMap+ ( \incidence ->+ missingSegment (draftIncidenceFirstSegment incidence)+ <> missingSegment (draftIncidenceSecondSegment incidence)+ )+ (draftIncidences draft)+ neighborhoodObstructions =+ concatMap+ ( \neighborhood ->+ missingVertex (draftNeighborhoodCenter neighborhood)+ <> concatMap missingVertex (draftNeighborhoodNeighbors neighborhood)+ )+ (draftNeighborhoods draft)++projectVertices+ :: Map DraftVertexId ExactPoint+ -> Validation (Map DraftVertexId ProjectedVertex)+projectVertices =+ Map.traverseWithKey+ ( \vertexId point ->+ case exactPointToEmbeddingCandidate point of+ Left projectionError ->+ invalid (VertexProjectionRefused vertexId projectionError)+ Right roundedPoint -> Valid (ProjectedVertex point roundedPoint)+ )++certifyProjectedDraft+ :: ExactArrangementDraft+ -> Map DraftVertexId ProjectedVertex+ -> Either (NonEmpty OverlayEmbeddingObstruction) LocalEmbeddingCertificate+certifyProjectedDraft draft projectedVertices =+ case resolvedFailures of+ Left obstruction -> Left (obstruction :| [])+ Right localFailures ->+ case NonEmpty.nonEmpty (collisionFailures <> localFailures) of+ Just obstructions -> Left obstructions+ Nothing ->+ Right+ LocalEmbeddingCertificate+ { certificateRoundedVertexDistinctnessCount = distinctnessCount+ , certificateSplitOrderPreservationCount = splitOrderCount+ , certificateIncidenceRelationPreservationCount = incidenceCount+ , certificateNeighborhoodRotationPreservationCount = neighborhoodCount+ , certificateRoundedVertices = Map.map projectedRoundedPoint projectedVertices+ , certificateResidual = milestoneOneResidual+ }+ where+ collisionFailures = roundedVertexCollisionObstructions projectedVertices+ resolvedFailures = do+ splitFailures <- traverse (uncurry resolveSource) (Map.toAscList (draftSourceMemberships draft))+ incidenceFailures <- traverse resolveIncidence (draftIncidences draft)+ neighborhoodFailures <- traverse resolveNeighborhood (draftNeighborhoods draft)+ pure (concat splitFailures <> concat incidenceFailures <> concat neighborhoodFailures)+ resolveVertex vertexId =+ case Map.lookup vertexId projectedVertices of+ Nothing -> Left (DraftReferenceMissing (DraftVertexReference vertexId))+ Just projectedVertex -> Right (ResolvedVertex vertexId projectedVertex)+ resolveSegment segmentId =+ case Map.lookup segmentId (draftSegments draft) of+ Nothing -> Left (DraftReferenceMissing (DraftSegmentReference segmentId))+ Just (from, to) ->+ ResolvedSegment segmentId+ <$> resolveVertex from+ <*> resolveVertex to+ resolveSource sourceId memberships =+ splitOrderObstructions sourceId+ <$> traverse+ ( \(parameter, vertexId) ->+ ResolvedMembership parameter <$> resolveVertex vertexId+ )+ memberships+ resolveIncidence incidence =+ incidenceRelationObstructions+ (draftIncidenceRelation incidence)+ <$> resolveSegment (draftIncidenceFirstSegment incidence)+ <*> resolveSegment (draftIncidenceSecondSegment incidence)+ resolveNeighborhood neighborhood =+ neighborhoodRotationObstructions+ <$> resolveVertex (draftNeighborhoodCenter neighborhood)+ <*> traverse resolveVertex (draftNeighborhoodNeighbors neighborhood)+ vertexCount = Map.size projectedVertices+ distinctnessCount = vertexCount * (vertexCount - 1) `quot` 2+ splitOrderCount =+ sum+ ( map+ (max 0 . subtract 1 . length)+ (Map.elems (draftSourceMemberships draft))+ )+ incidenceCount = length (draftIncidences draft)+ neighborhoodCount =+ sum+ ( map+ (length . draftNeighborhoodNeighbors)+ (draftNeighborhoods draft)+ )++roundedVertexCollisionObstructions+ :: Map DraftVertexId ProjectedVertex+ -> [OverlayEmbeddingObstruction]+roundedVertexCollisionObstructions projectedVertices =+ [ RoundedVerticesCollide leftId rightId roundedPoint+ | (roundedPoint, vertexIds) <- Map.toAscList verticesByRoundedPoint+ , (leftId : remainingIds) <- tails (sort vertexIds)+ , rightId <- remainingIds+ ]+ where+ verticesByRoundedPoint =+ Map.fromListWith (<>)+ [ (projectedRoundedPoint projectedVertex, [vertexId])+ | (vertexId, projectedVertex) <- Map.toAscList projectedVertices+ ]++splitOrderObstructions+ :: DraftSourceId+ -> [ResolvedMembership]+ -> [OverlayEmbeddingObstruction]+splitOrderObstructions sourceId memberships =+ case memberships of+ firstMembership : secondMembership : remainingMemberships ->+ let finalMembership =+ List.foldl' (\_ current -> current) secondMembership remainingMemberships+ sourceFrom = roundedMembershipPoint firstMembership+ sourceTo = roundedMembershipPoint finalMembership+ in [ SplitOrderNotPreserved+ sourceId+ (resolvedVertexId (resolvedMembershipVertex leftMembership))+ (resolvedVertexId (resolvedMembershipVertex rightMembership))+ | (leftMembership, rightMembership) <-+ zip+ memberships+ (drop 1 memberships)+ , compare+ (resolvedMembershipParameter leftMembership)+ (resolvedMembershipParameter rightMembership)+ /= roundedOrderAlong+ sourceFrom+ sourceTo+ (roundedMembershipPoint leftMembership)+ (roundedMembershipPoint rightMembership)+ ]+ _ -> []++roundedMembershipPoint :: ResolvedMembership -> Point+roundedMembershipPoint =+ projectedRoundedPoint+ . resolvedProjectedVertex+ . resolvedMembershipVertex++roundedOrderAlong :: Point -> Point -> Point -> Point -> Ordering+roundedOrderAlong+ (Point sourceFromX sourceFromY)+ (Point sourceToX sourceToY)+ (Point leftX leftY)+ (Point rightX rightY) =+ let directionX = sourceToX - sourceFromX+ directionY = sourceToY - sourceFromY+ in if abs directionX >= abs directionY+ then+ if directionX >= 0+ then compare leftX rightX+ else compare rightX leftX+ else+ if directionY >= 0+ then compare leftY rightY+ else compare rightY leftY++incidenceRelationObstructions+ :: SegmentRelation+ -> ResolvedSegment+ -> ResolvedSegment+ -> [OverlayEmbeddingObstruction]+incidenceRelationObstructions declaredRelation firstSegment secondSegment =+ [ IncidenceRelationChanged+ (resolvedSegmentId firstSegment)+ (resolvedSegmentId secondSegment)+ declaredRelation+ exactRelation+ roundedRelation+ | exactRelation /= declaredRelation || roundedRelation /= declaredRelation+ ]+ where+ exactRelation =+ relationFor exactSegmentRelation projectedExactPoint firstSegment secondSegment+ roundedRelation =+ relationFor Math.segmentRelation projectedRoundedPoint firstSegment secondSegment++relationFor+ :: (point -> point -> point -> point -> SegmentRelation)+ -> (ProjectedVertex -> point)+ -> ResolvedSegment+ -> ResolvedSegment+ -> SegmentRelation+relationFor relation project firstSegment secondSegment =+ relation+ (project (resolvedProjectedVertex (resolvedSegmentFrom firstSegment)))+ (project (resolvedProjectedVertex (resolvedSegmentTo firstSegment)))+ (project (resolvedProjectedVertex (resolvedSegmentFrom secondSegment)))+ (project (resolvedProjectedVertex (resolvedSegmentTo secondSegment)))++neighborhoodRotationObstructions+ :: ResolvedVertex+ -> NonEmpty ResolvedVertex+ -> [OverlayEmbeddingObstruction]+neighborhoodRotationObstructions center exactRotation =+ [ NeighborhoodRotationChanged+ (resolvedVertexId center)+ exactVertexRotation+ roundedVertexRotation+ | roundedRotationDescents > 1+ ]+ where+ exactVertexRotation = fmap resolvedVertexId exactRotation+ roundedRotationDescents =+ List.foldl'+ (\descentCount (left, right) ->+ descentCount+ + if compareRoundedAround center left right == GT+ then 1+ else 0)+ (0 :: Int)+ (cyclePairs exactRotation)+ roundedVertexRotation =+ fmap resolvedVertexId+ (NonEmpty.sortBy (compareRoundedAround center) exactRotation)++compareRoundedAround+ :: ResolvedVertex+ -> ResolvedVertex+ -> ResolvedVertex+ -> Ordering+compareRoundedAround center left right =+ compareRoundedAroundWith+ ( Math.orient2d+ (roundedVertexPoint center)+ (roundedVertexPoint left)+ (roundedVertexPoint right)+ )+ center+ left+ right++compareRoundedAroundWith+ :: Ordering+ -> ResolvedVertex+ -> ResolvedVertex+ -> ResolvedVertex+ -> Ordering+compareRoundedAroundWith roundedOrientation center left right =+ case compare (roundedVectorHalf center left) (roundedVectorHalf center right) of+ EQ ->+ case roundedOrientation of+ GT -> LT+ LT -> GT+ EQ -> compare (roundedVertexPoint left) (roundedVertexPoint right)+ ordering -> ordering++roundedVectorHalf :: ResolvedVertex -> ResolvedVertex -> Bool+roundedVectorHalf center neighbor =+ let Point centerX centerY = roundedVertexPoint center+ Point neighborX neighborY = roundedVertexPoint neighbor+ deltaX = neighborX - centerX+ deltaY = neighborY - centerY+ in deltaY < 0 || (deltaY == 0 && deltaX < 0)++roundedVertexPoint :: ResolvedVertex -> Point+roundedVertexPoint = projectedRoundedPoint . resolvedProjectedVertex
+ src-planar/Moonlight/Triangulation/Internal/Overlay/Resident.hs view
@@ -0,0 +1,923 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE ScopedTypeVariables #-}++-- | Resident DCEL descent and exact-cell gluing. The certified arrangement is+-- authoritative at this seam; diagonal schedules may change only its resident+-- triangulation, never its exact cell descriptors.+module Moonlight.Triangulation.Internal.Overlay.Resident+ ( OverlayDiagonalSchedule (..)+ , residentOverlay+ , faceLabels+ , faceCarriesExactArea+ , regionFaceLabels+ , vertexSupport+ , edgeSupport+ ) where++import Control.Monad (foldM)+import Data.Bifunctor (first)+import Data.Foldable (traverse_)+import Data.List (partition, sort, sortBy)+import qualified Data.List as List+import qualified Data.List.NonEmpty as NonEmpty+import qualified Data.Map.Strict as Map+import Data.Map.Strict (Map)+import Data.Maybe (listToMaybe, mapMaybe)+import qualified Data.Sequence as Seq+import qualified Data.Set as Set+import Data.Set (Set)+import qualified Data.Vector as V+import qualified Moonlight.Triangulation.Dcel as Dcel+import Moonlight.Triangulation.Dcel+ ( faceData+ , faceDirectedEdges+ , faceVertices+ , imapUndirectedEdges+ , incidentFace+ , isConstraintEdge+ , numInnerFaces+ , numVertices+ , outerFace+ , undirectedEndpoints+ , vertexData+ , vertexOutgoingEdges+ )+import Moonlight.Triangulation.Exact (ExactPoint, exactOrient2d)+import Moonlight.Triangulation.FloodFillIterator+ ( FaceComponent+ , componentBoundary+ , faceComponentFaces+ , faceComponentsBy+ )+import Moonlight.Triangulation.Handles.HandleDefs+ ( DirectedEdgeId+ , FaceId+ , UndirectedEdgeId+ , VertexId+ , asUndirected+ , directedPair+ , reverseEdge+ )+import Moonlight.Triangulation.Handles.Iterators.FixedIterators+ ( allFaces+ , innerFaces+ , undirectedEdges+ )+import Moonlight.Triangulation.Internal.BoxedPaged (boxedFromVector)+import Moonlight.Triangulation.Internal.BoundaryCycle+ ( consecutivePairs+ , traceOrientedBoundaryCircuits+ )+import Moonlight.Triangulation.Internal.Canonical (canonicalize)+import Moonlight.Triangulation.Internal.Cdt.Build (constrainedDelaunay)+import Moonlight.Triangulation.Internal.Cdt.Types (CdtError (..))+import Moonlight.Triangulation.Internal.Overlay.Arrangement+ ( ArrangementMetrics (..)+ , AtomicEdge (..)+ , atomicEdgeFrom+ , atomicEdgeTo+ , CertifiedArrangement (..)+ , ExactEdgeKey+ , OverlayVertexSeed (..)+ , atomicKey+ , canonicalEdgeKey+ , compareAround+ )+import Moonlight.Triangulation.Internal.Overlay.Types+import Moonlight.Triangulation.Internal.Region.Publication+ ( polygonComponentFromBoundaryCoordinates+ )+import Moonlight.Triangulation.Internal.Region.Types+ ( ExactLoop+ , PolygonComponent+ , RegionPublicationError (..)+ )+import Moonlight.Triangulation.Internal.Representation+ ( BuildResult (..)+ , Triangulation (..)+ )+import Moonlight.Triangulation.Internal.Types+ ( ConstraintMode (Constrained)+ , ElementDefaults (..)+ )+import Moonlight.Triangulation.Region (exactLoop)++data OverlayDiagonalSchedule+ = CanonicalOverlayDiagonals+ | FlipFirstAdmissibleDiagonal+ deriving stock (Eq, Ord, Show)++data ComponentDraft leftLabel rightLabel = ComponentDraft+ { componentDraftLabels :: !(leftLabel, rightLabel)+ , componentDraftFaces :: !FaceComponent+ , componentDraftPolygon :: !PolygonComponent+ , componentDraftTouchesOuter :: !Bool+ }++residentOverlay+ :: (Ord leftLabel, Ord rightLabel)+ => OverlayDiagonalSchedule+ -> (leftLabel, rightLabel)+ -> CertifiedArrangement leftLabel rightLabel+ -> Either+ (OverlayError leftLabel rightLabel)+ (OverlayResult leftLabel rightLabel)+residentOverlay diagonalSchedule outsidePair certified = do+ let atomicVector = certifiedAtomicEdges certified+ seeds = certifiedVertexSeeds certified+ constraints = certifiedConstraints certified+ metrics = certifiedMetrics certified+ defaults = ElementDefaults () () ()+ built <- first OverlayBuildFailed (constrainedDelaunay defaults seeds constraints)+ let resident = buildTriangulation built+ if numVertices resident == V.length seeds+ then Right ()+ else+ Left+ ( OverlayProvenanceIncomplete+ (OverlayEmbeddedVertexCountMismatch (V.length seeds) (numVertices resident))+ )+ canonicalResident <-+ first (OverlayBuildFailed . CdtBuildError) (canonicalize resident)+ let atomicByKey =+ Map.fromList+ [ (atomicKey edge, edge)+ | edge <- V.toList atomicVector+ ]+ validateAtomicConstraints canonicalResident atomicByKey+ scheduledResident <-+ applyDiagonalSchedule+ diagonalSchedule+ defaults+ seeds+ constraints+ canonicalResident+ labelledFaces <- labelResidentFaces outsidePair scheduledResident atomicByKey+ (fullDimensionalFaces, collapsedFaces) <-+ if Set.null (certifiedInexactEmbeddingPoints certified)+ then+ Right+ ( Set.fromList (innerFaces scheduledResident)+ , Set.empty+ )+ else+ exactFaceDimensions+ (certifiedInexactEmbeddingPoints certified)+ scheduledResident+ componentDrafts <-+ residentComponentDrafts+ scheduledResident+ labelledFaces+ fullDimensionalFaces+ let (unboundedDrafts, boundedDrafts) =+ partitionDrafts outsidePair componentDrafts+ numberedBounded <- numberBoundedComponents boundedDrafts+ let fullCellIdByFace =+ Map.fromList+ ( [ (face, OverlayCellId 0)+ | draftComponent <- unboundedDrafts+ , face <- faceComponentFaces (componentDraftFaces draftComponent)+ ]+ <> [ (face, cellId)+ | (cellId, draftComponent) <- numberedBounded+ , face <- faceComponentFaces (componentDraftFaces draftComponent)+ ]+ )+ collapsedCellIds <-+ descendCollapsedFaces+ scheduledResident+ atomicByKey+ fullCellIdByFace+ collapsedFaces+ let cellIdByFace = fullCellIdByFace <> collapsedCellIds+ unboundedLoops <-+ unboundedCellBoundaryLoops scheduledResident atomicByKey cellIdByFace+ let cells =+ V.fromList+ ( OverlayCell+ { overlayCellLeft = fst outsidePair+ , overlayCellRight = snd outsidePair+ , overlayCellGeometry = UnboundedOverlayCell unboundedLoops+ }+ : [ OverlayCell+ { overlayCellLeft = fst (componentDraftLabels draftComponent)+ , overlayCellRight = snd (componentDraftLabels draftComponent)+ , overlayCellGeometry =+ BoundedOverlayCell (componentDraftPolygon draftComponent)+ }+ | (_, draftComponent) <- numberedBounded+ ]+ )+ withFaces <- attachFaceCells collapsedFaces cellIdByFace scheduledResident+ let withVertices = attachOverlayVertices withFaces+ withEdges =+ imapUndirectedEdges+ OverlayDiagonal+ (\edge _ ->+ case Map.lookup (residentEdgeKey withVertices edge) atomicByKey of+ Nothing -> OverlayDiagonal+ Just atomic -> OverlayBoundary (atomicEdgeOrigin atomic))+ withVertices+ receipt =+ OverlayReceipt+ { overlayInputSegments = arrangementInputSegments metrics+ , overlayRelationEvents = arrangementRelationEvents metrics+ , overlayExactCrossings = arrangementExactCrossings metrics+ , overlayOverlapIntervals = arrangementOverlapIntervals metrics+ , overlayAtomicEdges = V.length atomicVector+ , overlayOutputVertices = numVertices withEdges+ , overlayArrangementCells = V.length cells+ , overlayResidentFaces = numInnerFaces withEdges+ , overlayEmbeddingCandidates = arrangementEmbeddingCandidates metrics+ , overlayTotalRelationChecks = arrangementTotalRelationChecks metrics+ , overlaySweepMaximumHeight = arrangementSweepMaximumHeight metrics+ }+ pure+ OverlayResult+ { overlayResultTriangulation = withEdges+ , overlayResultCells = cells+ , overlayResultOutsideLabels = outsidePair+ , overlayResultReceipt = receipt+ }++applyDiagonalSchedule+ :: OverlayDiagonalSchedule+ -> ElementDefaults () () ()+ -> V.Vector OverlayVertexSeed+ -> V.Vector (Int, Int)+ -> Triangulation 'Constrained OverlayVertexSeed () () ()+ -> Either+ (OverlayError leftLabel rightLabel)+ (Triangulation 'Constrained OverlayVertexSeed () () ())+applyDiagonalSchedule CanonicalOverlayDiagonals _ _ _ triangulation =+ Right triangulation+applyDiagonalSchedule+ FlipFirstAdmissibleDiagonal+ defaults+ seeds+ constraints+ triangulation =+ case firstAlternativeDiagonal triangulation of+ Nothing -> Right triangulation+ Just (from, to) -> do+ let indexByPoint =+ Map.fromList+ [ (seedExactPoint seed, index)+ | (index, seed) <- V.toList (V.indexed seeds)+ ]+ fromIndex <- requireSeedIndex indexByPoint from+ toIndex <- requireSeedIndex indexByPoint to+ rebuilt <-+ first OverlayBuildFailed+ (constrainedDelaunay defaults seeds (V.snoc constraints (fromIndex, toIndex)))+ first (OverlayBuildFailed . CdtBuildError)+ (canonicalize (buildTriangulation rebuilt))++requireSeedIndex+ :: Map ExactPoint Int+ -> ExactPoint+ -> Either (OverlayError leftLabel rightLabel) Int+requireSeedIndex indexByPoint point =+ case Map.lookup point indexByPoint of+ Just index -> Right index+ Nothing -> Left (OverlayProvenanceIncomplete (OverlayExactVertexMissing point))++firstAlternativeDiagonal+ :: Triangulation 'Constrained OverlayVertexSeed () () ()+ -> Maybe ExactEdgeKey+firstAlternativeDiagonal triangulation =+ listToMaybe+ (mapMaybe (alternativeDiagonal triangulation) (undirectedEdges triangulation))++alternativeDiagonal+ :: Triangulation 'Constrained OverlayVertexSeed () () ()+ -> UndirectedEdgeId+ -> Maybe ExactEdgeKey+alternativeDiagonal triangulation edge+ | isConstraintEdge triangulation edge = Nothing+ | leftFace == outerFace || rightFace == outerFace = Nothing+ | exactOrient2d c d b == GT && exactOrient2d d c a == GT =+ Just (canonicalEdgeKey c d)+ | otherwise = Nothing+ where+ (forward, backward) = directedPair edge+ leftFace = incidentFace triangulation forward+ rightFace = incidentFace triangulation backward+ a = seedExactPoint (vertexData triangulation (Dcel.origin triangulation forward))+ b = seedExactPoint (vertexData triangulation (Dcel.origin triangulation backward))+ c =+ seedExactPoint+ (vertexData triangulation (Dcel.origin triangulation (Dcel.previous triangulation forward)))+ d =+ seedExactPoint+ (vertexData triangulation (Dcel.origin triangulation (Dcel.previous triangulation backward)))+labelResidentFaces+ :: (Ord leftLabel, Ord rightLabel)+ => (leftLabel, rightLabel)+ -> Triangulation 'Constrained OverlayVertexSeed () () ()+ -> Map ExactEdgeKey (AtomicEdge leftLabel rightLabel)+ -> Either+ (OverlayError leftLabel rightLabel)+ (Map FaceId (leftLabel, rightLabel))+labelResidentFaces outsideLabels triangulation atomicByKey = do+ labelled <-+ descendFaceTransitions+ triangulation+ atomicByKey+ (Map.singleton outerFace outsideLabels)+ (Seq.singleton outerFace)+ case+ [ face+ | face <- innerFaces triangulation+ , Map.notMember face labelled+ ] of+ missing : _ ->+ Left (OverlayProvenanceIncomplete (OverlayResidentFaceUnassigned missing))+ [] -> Right (Map.delete outerFace labelled)++seedDirectedEndpoints+ :: Triangulation mode OverlayVertexSeed directed undirected face+ -> DirectedEdgeId+ -> ExactEdgeKey+seedDirectedEndpoints triangulation edge =+ ( seedExactPoint (vertexData triangulation (Dcel.origin triangulation edge))+ , seedExactPoint (vertexData triangulation (Dcel.destination triangulation edge))+ )++swapTransition :: (label, label) -> (label, label)+swapTransition (fromLabel, toLabel) = (toLabel, fromLabel)++descendFaceTransitions+ :: (Eq leftLabel, Eq rightLabel)+ => Triangulation 'Constrained OverlayVertexSeed () () ()+ -> Map ExactEdgeKey (AtomicEdge leftLabel rightLabel)+ -> Map FaceId (leftLabel, rightLabel)+ -> Seq.Seq FaceId+ -> Either+ (OverlayError leftLabel rightLabel)+ (Map FaceId (leftLabel, rightLabel))+descendFaceTransitions triangulation atomicByKey labelled queued =+ case Seq.viewl queued of+ Seq.EmptyL -> Right labelled+ face Seq.:< remaining ->+ case Map.lookup face labelled of+ Nothing ->+ Left (OverlayProvenanceIncomplete (OverlayResidentFaceUnassigned face))+ Just current -> do+ (nextLabels, nextQueue) <-+ foldM+ (descendFaceTransition triangulation atomicByKey face current)+ (labelled, remaining)+ (faceDirectedEdges triangulation face)+ descendFaceTransitions triangulation atomicByKey nextLabels nextQueue++descendFaceTransition+ :: (Eq leftLabel, Eq rightLabel)+ => Triangulation 'Constrained OverlayVertexSeed () () ()+ -> Map ExactEdgeKey (AtomicEdge leftLabel rightLabel)+ -> FaceId+ -> (leftLabel, rightLabel)+ -> (Map FaceId (leftLabel, rightLabel), Seq.Seq FaceId)+ -> DirectedEdgeId+ -> Either+ (OverlayError leftLabel rightLabel)+ (Map FaceId (leftLabel, rightLabel), Seq.Seq FaceId)+descendFaceTransition triangulation atomicByKey source current (labelled, queued) directed = do+ let edge = asUndirected directed+ destinationFace = incidentFace triangulation (reverseEdge directed)+ (leftTransition, rightTransition) <-+ case Map.lookup (residentSeedEdgeKey triangulation edge) atomicByKey of+ Nothing -> Right (Nothing, Nothing)+ Just atomic -> orientedTransitions edge directed atomic+ let+ expected =+ ( maybe (fst current) fst leftTransition+ , maybe (snd current) fst rightTransition+ )+ derived =+ ( maybe (fst current) snd leftTransition+ , maybe (snd current) snd rightTransition+ )+ if current == expected+ then+ case Map.lookup destinationFace labelled of+ Nothing ->+ Right+ ( Map.insert destinationFace derived labelled+ , queued Seq.|> destinationFace+ )+ Just existing+ | existing == derived -> Right (labelled, queued)+ | otherwise ->+ Left+ ( OverlayArrangementInvalid+ ( OverlayResidentFaceLabelConflict+ destinationFace+ edge+ existing+ derived+ )+ )+ else+ Left+ ( OverlayArrangementInvalid+ (OverlayTransitionSourceMismatch source edge current expected)+ )+ where+ orientedTransitions edge candidateDirection atomic+ | directedEndpoints == (atomicEdgeFrom atomic, atomicEdgeTo atomic) =+ Right+ ( atomicEdgeLeftTransition atomic+ , atomicEdgeRightTransition atomic+ )+ | directedEndpoints == (atomicEdgeTo atomic, atomicEdgeFrom atomic) =+ Right+ ( swapTransition <$> atomicEdgeLeftTransition atomic+ , swapTransition <$> atomicEdgeRightTransition atomic+ )+ | otherwise =+ Left+ ( OverlayProvenanceIncomplete+ ( OverlayAtomicConstraintOrientationMismatch+ edge+ (atomicEdgeFrom atomic)+ (atomicEdgeTo atomic)+ )+ )+ where+ directedEndpoints = seedDirectedEndpoints triangulation candidateDirection++exactFaceDimensions+ :: Set ExactPoint+ -> Triangulation 'Constrained OverlayVertexSeed directed undirected face+ -> Either+ (OverlayError leftLabel rightLabel)+ (Set FaceId, Set FaceId)+exactFaceDimensions inexactEmbeddingPoints triangulation = do+ orientations <-+ traverse+ ( \face ->+ (face,)+ <$> exactFaceOrientation+ inexactEmbeddingPoints+ triangulation+ face+ )+ (innerFaces triangulation)+ case [face | (face, LT) <- orientations] of+ reversed : _ ->+ Left+ ( OverlayArrangementInvalid+ (OverlayResidentFaceOrientationReversed reversed)+ )+ [] ->+ Right+ ( Set.fromList [face | (face, GT) <- orientations]+ , Set.fromList [face | (face, EQ) <- orientations]+ )++exactFaceOrientation+ :: Set ExactPoint+ -> Triangulation mode OverlayVertexSeed directed undirected face+ -> FaceId+ -> Either (OverlayError leftLabel rightLabel) Ordering+exactFaceOrientation inexactEmbeddingPoints triangulation face =+ case+ map+ (seedExactPoint . vertexData triangulation)+ (faceVertices triangulation face) of+ points@[firstPoint, secondPoint, thirdPoint]+ | any (`Set.member` inexactEmbeddingPoints) points ->+ Right (exactOrient2d firstPoint secondPoint thirdPoint)+ | otherwise -> Right GT+ vertices ->+ Left+ ( OverlayArrangementInvalid+ (OverlayResidentFaceArity face (length vertices))+ )++residentComponentDrafts+ :: forall leftLabel rightLabel.+ (Ord leftLabel, Ord rightLabel)+ => Triangulation 'Constrained OverlayVertexSeed () () ()+ -> Map FaceId (leftLabel, rightLabel)+ -> Set FaceId+ -> Either+ (OverlayError leftLabel rightLabel)+ [ComponentDraft leftLabel rightLabel]+residentComponentDrafts triangulation labels fullDimensionalFaces =+ traverse+ convert+ (faceComponentsBy triangulation labelFullDimensionalFace (const True))+ where+ exactPointAt :: VertexId -> Either RegionPublicationError ExactPoint+ exactPointAt vertex =+ Right (seedExactPoint (vertexData triangulation vertex))+ labelFullDimensionalFace :: FaceId -> Maybe (leftLabel, rightLabel)+ labelFullDimensionalFace face+ | Set.member face fullDimensionalFaces = Map.lookup face labels+ | otherwise = Nothing+ convert+ :: ((leftLabel, rightLabel), FaceComponent)+ -> Either+ (OverlayError leftLabel rightLabel)+ (ComponentDraft leftLabel rightLabel)+ convert (componentLabels, component) = do+ boundary <-+ first+ (OverlayRegionPublicationFailed . RegionBoundaryObstruction)+ (componentBoundary triangulation component)+ polygon <-+ first OverlayRegionPublicationFailed+ (polygonComponentFromBoundaryCoordinates exactPointAt boundary)+ pure+ ComponentDraft+ { componentDraftLabels = componentLabels+ , componentDraftFaces = component+ , componentDraftPolygon = polygon+ , componentDraftTouchesOuter = touchesOuter triangulation component+ }++touchesOuter+ :: Triangulation mode vertex directed undirected face+ -> FaceComponent+ -> Bool+touchesOuter triangulation component =+ any+ ( any ((== outerFace) . incidentFace triangulation . reverseEdge)+ . faceDirectedEdges triangulation+ )+ (faceComponentFaces component)++partitionDrafts+ :: (Eq leftLabel, Eq rightLabel)+ => (leftLabel, rightLabel)+ -> [ComponentDraft leftLabel rightLabel]+ -> ([ComponentDraft leftLabel rightLabel], [ComponentDraft leftLabel rightLabel])+partitionDrafts outsidePair =+ partition+ (\draftComponent ->+ componentDraftTouchesOuter draftComponent+ && componentDraftLabels draftComponent == outsidePair)++sortComponentDrafts+ :: (Ord leftLabel, Ord rightLabel)+ => [ComponentDraft leftLabel rightLabel]+ -> [ComponentDraft leftLabel rightLabel]+sortComponentDrafts =+ sortBy+ (\left right ->+ compare+ (componentDraftPolygon left, componentDraftLabels left)+ (componentDraftPolygon right, componentDraftLabels right))++numberBoundedComponents+ :: (Ord leftLabel, Ord rightLabel)+ => [ComponentDraft leftLabel rightLabel]+ -> Either+ (OverlayError leftLabel rightLabel)+ [(OverlayCellId, ComponentDraft leftLabel rightLabel)]+numberBoundedComponents drafts =+ case+ [ componentDraftPolygon left+ | (left, right) <- consecutivePairs ordered+ , componentSignature left == componentSignature right+ ] of+ duplicate : _ ->+ Left+ ( OverlayArrangementInvalid+ (OverlayDuplicateCellSignature duplicate)+ )+ [] ->+ Right+ ( zipWith+ (\index component -> (OverlayCellId index, component))+ [1 ..]+ ordered+ )+ where+ ordered = sortComponentDrafts drafts+ componentSignature+ :: ComponentDraft leftLabel' rightLabel'+ -> (PolygonComponent, (leftLabel', rightLabel'))+ componentSignature component =+ (componentDraftPolygon component, componentDraftLabels component)++-- | A binary64 triangle whose authoritative vertices are collinear is not an+-- exact two-cell. It may remain in the resident triangulation only when its+-- representation diagonals descend uniquely to one full-dimensional exact+-- cell; this prevents the artifact from gluing exact components through a+-- zero-area wedge.+descendCollapsedFaces+ :: forall leftLabel rightLabel.+ Triangulation 'Constrained OverlayVertexSeed () () ()+ -> Map ExactEdgeKey (AtomicEdge leftLabel rightLabel)+ -> Map FaceId OverlayCellId+ -> Set FaceId+ -> Either+ (OverlayError leftLabel rightLabel)+ (Map FaceId OverlayCellId)+descendCollapsedFaces triangulation atomicByKey fullCellIds collapsedFaces =+ Map.fromList . concat <$> traverse resolveComponent collapsedComponents+ where+ exactCellIds = Map.insert outerFace (OverlayCellId 0) fullCellIds+ collapsedComponents =+ fmap snd+ ( faceComponentsBy+ triangulation+ labelCollapsedFace+ isRepresentationDiagonal+ )++ labelCollapsedFace face+ | Set.member face collapsedFaces = Just ()+ | otherwise = Nothing++ isRepresentationDiagonal edge =+ Map.notMember (residentSeedEdgeKey triangulation edge) atomicByKey++ resolveComponent component =+ case faceComponentFaces component of+ [] ->+ Left+ ( OverlayArrangementInvalid+ OverlayFaceComponentEmpty+ )+ componentFaces@(firstFace : remainingFaces) -> do+ let componentWitness = firstFace NonEmpty.:| remainingFaces+ adjacentCellIds =+ Set.fromList+ [ cellId+ | face <- componentFaces+ , directed <- faceDirectedEdges triangulation face+ , isRepresentationDiagonal (asUndirected directed)+ , Just cellId <-+ [Map.lookup (incidentFace triangulation (reverseEdge directed)) exactCellIds]+ ]+ cellId <-+ case Set.toAscList adjacentCellIds of+ [singleCellId] -> Right singleCellId+ [] ->+ Left+ ( OverlayArrangementInvalid+ (OverlayCollapsedFacesUnowned componentWitness)+ )+ firstCellId : remainingCellIds ->+ Left+ ( OverlayArrangementInvalid+ ( OverlayCollapsedFacesAmbiguous+ componentWitness+ (firstCellId NonEmpty.:| remainingCellIds)+ )+ )+ pure (fmap (,cellId) componentFaces)++-- | Trace the finite boundary cycles of the unbounded exact cell from atomic+-- edges only. Resident hull edges and Delaunay diagonals are absent by+-- construction; the DCEL contributes only the already-proved incident-cell+-- gluing needed to orient each atomic edge with cell zero on its left.+unboundedCellBoundaryLoops+ :: Triangulation 'Constrained OverlayVertexSeed () () ()+ -> Map ExactEdgeKey (AtomicEdge leftLabel rightLabel)+ -> Map FaceId OverlayCellId+ -> Either (OverlayError leftLabel rightLabel) [ExactLoop]+unboundedCellBoundaryLoops triangulation atomicByKey cellIdByFace = do+ orientedEdges <-+ Set.fromList . concat+ <$> traverse orientAtomicEdge (undirectedEdges triangulation)+ traceBoundaryCycles orientedEdges+ where+ orientAtomicEdge edge+ | Map.notMember (residentSeedEdgeKey triangulation edge) atomicByKey = Right []+ | otherwise = do+ let (forward, backward) = directedPair edge+ forwardCell <- faceCellId (incidentFace triangulation forward)+ backwardCell <- faceCellId (incidentFace triangulation backward)+ pure+ ( case (forwardCell == OverlayCellId 0, backwardCell == OverlayCellId 0) of+ (True, False) -> [seedDirectedEndpoints triangulation forward]+ (False, True) -> [seedDirectedEndpoints triangulation backward]+ _ -> []+ )+ faceCellId face+ | face == outerFace = Right (OverlayCellId 0)+ | otherwise =+ case Map.lookup face cellIdByFace of+ Just cellId -> Right cellId+ Nothing -> Left (OverlayProvenanceIncomplete (OverlayResidentFaceUnassigned face))++traceBoundaryCycles+ :: Set ExactEdgeKey+ -> Either (OverlayError leftLabel rightLabel) [ExactLoop]+traceBoundaryCycles orientedEdges = do+ pointCycles <-+ traceOrientedBoundaryCircuits+ fst+ snd+ (\seed _ ->+ OverlayArrangementInvalid+ (uncurry OverlayCellCycleDidNotClose seed))+ outgoing+ orientedEdges+ sort+ <$> traverse+ ( first+ (OverlayRegionPublicationFailed . RegionValidationObstruction)+ . exactLoop+ )+ pointCycles+ where+ outgoing =+ Map.mapWithKey orderAroundOrigin+ ( Set.foldl'+ (\byOrigin edge@(from, _) -> Map.insertWith (<>) from [edge] byOrigin)+ Map.empty+ orientedEdges+ )+ orderAroundOrigin :: ExactPoint -> [ExactEdgeKey] -> [ExactEdgeKey]+ orderAroundOrigin origin =+ sortBy (\(_, left) (_, right) -> compareAround origin left right)++attachFaceCells+ :: forall leftLabel rightLabel.+ Set FaceId+ -> Map FaceId OverlayCellId+ -> Triangulation 'Constrained OverlayVertexSeed () () ()+ -> Either+ (OverlayError leftLabel rightLabel)+ (Triangulation 'Constrained OverlayVertexSeed () () OverlayFace)+attachFaceCells collapsedFaces cellIdByFace triangulation = do+ payloads <- V.fromList <$> traverse facePayload (allFaces triangulation)+ let outerPayload = OverlayCellFace (OverlayCellId 0)+ defaults = triElementDefaults triangulation+ pure+ triangulation+ { triFaceData = boxedFromVector (Just outerPayload) payloads+ , triElementDefaults = defaults{defaultFaceData = outerPayload}+ }+ where+ facePayload+ :: FaceId+ -> Either (OverlayError leftLabel rightLabel) OverlayFace+ facePayload face+ | face == outerFace = Right (OverlayCellFace (OverlayCellId 0))+ | otherwise =+ case Map.lookup face cellIdByFace of+ Just cellId+ | Set.member face collapsedFaces ->+ Right (OverlayCollapsedFace cellId)+ | otherwise -> Right (OverlayCellFace cellId)+ Nothing ->+ Left+ ( OverlayProvenanceIncomplete+ (OverlayResidentFaceUnassigned face)+ )++attachOverlayVertices+ :: Triangulation 'Constrained OverlayVertexSeed () () face+ -> Triangulation 'Constrained OverlayVertex () () face+attachOverlayVertices =+ Dcel.mapVertices+ (\seed -> OverlayVertex (seedExactPoint seed) (seedOrigin seed))++cellPayload+ :: V.Vector (OverlayCell leftLabel rightLabel)+ -> OverlayCellId+ -> Either OverlayCellWitness (OverlayCell leftLabel rightLabel)+cellPayload cells cellId@(OverlayCellId index) =+ case cells V.!? index of+ Just cell -> Right cell+ Nothing -> Left (OverlayCellPayloadMissing cellId)++faceLabels+ :: OverlayResult leftLabel rightLabel+ -> FaceId+ -> Either OverlayCellWitness (leftLabel, rightLabel)+faceLabels result face = do+ cell <-+ cellPayload+ (overlayResultCells result)+ (overlayFaceCellId (faceData (overlayResultTriangulation result) face))+ pure (overlayCellLeft cell, overlayCellRight cell)++-- | Whether a resident triangle has nonzero area under the authoritative+-- exact coordinate section. Binary64-only wedges are representation charts,+-- never exact two-cells.+faceCarriesExactArea+ :: OverlayResult leftLabel rightLabel+ -> FaceId+ -> Bool+faceCarriesExactArea result face =+ case faceData (overlayResultTriangulation result) face of+ OverlayCellFace {} -> True+ OverlayCollapsedFace {} -> False++regionFaceLabels+ :: OverlayResult leftLabel rightLabel+ -> FaceId+ -> Either RegionPublicationError (leftLabel, rightLabel)+regionFaceLabels result face =+ case faceLabels result face of+ Right labels -> Right labels+ Left _ -> Left (RegionFaceLabelMissing face)++vertexSupport+ :: (Ord leftLabel, Ord rightLabel)+ => OverlayResult leftLabel rightLabel+ -> VertexId+ -> Either OverlayCellWitness (OverlayCellSupport leftLabel rightLabel)+vertexSupport result vertex = do+ labels <-+ traverse+ (faceLabels result . incidentFace triangulation)+ (vertexOutgoingEdges triangulation vertex)+ case supportFromPairs labels of+ Just support -> Right support+ Nothing -> Left (OverlayVertexSupportMissing vertex)+ where+ triangulation = overlayResultTriangulation result++edgeSupport+ :: (Ord leftLabel, Ord rightLabel)+ => OverlayResult leftLabel rightLabel+ -> UndirectedEdgeId+ -> Either OverlayCellWitness (OverlayCellSupport leftLabel rightLabel)+edgeSupport result edge = do+ pairs <- traverse (faceLabels result . incidentFace triangulation) [forward, backward]+ case supportFromPairs pairs of+ Just support -> Right support+ Nothing -> Left (OverlayEdgeSupportMissing edge)+ where+ triangulation = overlayResultTriangulation result+ (forward, backward) = directedPair edge++supportFromPairs+ :: (Ord leftLabel, Ord rightLabel)+ => [(leftLabel, rightLabel)]+ -> Maybe (OverlayCellSupport leftLabel rightLabel)+supportFromPairs pairs = do+ let (leftLabels, rightLabels) =+ List.foldl'+ (\(left, right) (leftLabel, rightLabel) ->+ (Set.insert leftLabel left, Set.insert rightLabel right))+ (Set.empty, Set.empty)+ pairs+ leftSupport <- nonEmptySupport leftLabels+ rightSupport <- nonEmptySupport rightLabels+ pure (OverlayCellSupport leftSupport rightSupport)++nonEmptySupport :: Set label -> Maybe (OverlaySupport label)+nonEmptySupport labels = OverlaySupport <$> NonEmpty.nonEmpty (Set.toAscList labels)++validateAtomicConstraints+ :: Triangulation+ 'Constrained+ OverlayVertexSeed+ ()+ ()+ ()+ -> Map ExactEdgeKey (AtomicEdge leftLabel rightLabel)+ -> Either (OverlayError leftLabel rightLabel) ()+validateAtomicConstraints triangulation atomicByKey = do+ traverse_ requireAtomic (Map.keys atomicByKey)+ traverse_ requireExpectedConstraint (undirectedEdges triangulation)+ where+ residentByKey =+ Map.fromList+ [ (residentSeedEdgeKey triangulation edge, edge)+ | edge <- undirectedEdges triangulation+ ]+ requireAtomic key@(from, to) =+ case Map.lookup key residentByKey of+ Nothing -> Left (OverlayProvenanceIncomplete (OverlayAtomicConstraintMissing from to))+ Just edge+ | isConstraintEdge triangulation edge -> Right ()+ | otherwise -> Left (OverlayProvenanceIncomplete (OverlayBoundaryEdgeNotConstrained edge))+ requireExpectedConstraint edge+ | isConstraintEdge triangulation edge+ && Map.notMember (residentSeedEdgeKey triangulation edge) atomicByKey =+ Left (OverlayProvenanceIncomplete (OverlayUnexpectedConstraint edge))+ | otherwise = Right ()++residentSeedEdgeKey+ :: Triangulation mode OverlayVertexSeed directed undirected face+ -> UndirectedEdgeId+ -> ExactEdgeKey+residentSeedEdgeKey = residentEdgeKeyBy seedExactPoint++residentEdgeKey+ :: Triangulation mode OverlayVertex directed undirected face+ -> UndirectedEdgeId+ -> ExactEdgeKey+residentEdgeKey = residentEdgeKeyBy overlayExactPoint++residentEdgeKeyBy+ :: (vertex -> ExactPoint)+ -> Triangulation mode vertex directed undirected face+ -> UndirectedEdgeId+ -> ExactEdgeKey+residentEdgeKeyBy exactPointAt triangulation edge =+ let (from, to) = undirectedEndpoints triangulation edge+ in canonicalEdgeKey+ (exactPointAt (vertexData triangulation from))+ (exactPointAt (vertexData triangulation to))
+ src-planar/Moonlight/Triangulation/Internal/Overlay/Types.hs view
@@ -0,0 +1,274 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE KindSignatures #-}++-- | Closed vocabulary and opaque carrier representation for exact labelled+-- overlay. Construction lives in the public build-tier module; this module+-- exists so every invariant-bearing payload shares one owner.+module Moonlight.Triangulation.Internal.Overlay.Types+ ( BoundaryLoopRef (..)+ , OverlayOperand (..)+ , BoundaryRef (..)+ , BoundaryVertexRef+ , BoundaryEdgeRef+ , OverlayVertexOrigin (..)+ , OverlayEdgeOrigin (..)+ , OverlaySupport (..)+ , overlaySupportLabels+ , OverlayCellSupport (..)+ , OverlayCellId (..)+ , OverlayCellGeometry (..)+ , OverlayCell (..)+ , OverlayFace (..)+ , OverlayVertex (..)+ , OverlayEdge (..)+ , OverlayReceipt (..)+ , OverlayArrangementObstruction (..)+ , OverlayCellWitness (..)+ , OverlayError (..)+ , OverlayResult (..)+ , OverlaySelectionKind (..)+ , OverlaySelectionError (..)+ ) where++import Control.DeepSeq (NFData)+import Data.List.NonEmpty (NonEmpty)+import Data.Vector (Vector)+import GHC.Generics (Generic)+import Moonlight.Triangulation.CellSet (CellSelectionError)+import Moonlight.Triangulation.Exact+ ( ExactPoint+ )+import Moonlight.Triangulation.Handles.HandleDefs+ ( FaceId+ , UndirectedEdgeId+ , VertexId+ )+import Moonlight.Triangulation.Internal.Cdt.Types (CdtError)+import Moonlight.Triangulation.Internal.ExactRational (ExactArithmeticError)+import Moonlight.Triangulation.Internal.ExactSegmentEvents (ExactSegmentEventObstruction)+import Moonlight.Triangulation.Internal.Overlay.Embedding (OverlayEmbeddingObstruction)+import Moonlight.Triangulation.Internal.Region.Types+ ( ExactLoop+ , PolygonComponent+ , RegionPublicationError+ )+import Moonlight.Triangulation.Internal.Representation (Triangulation)+import Moonlight.Triangulation.Internal.Types+ ( ConstraintMode (Constrained)+ )++-- | Which cycle inside a polygon component supplied a source reference.+data BoundaryLoopRef+ = BoundaryOuterLoop+ | BoundaryHoleLoop !Int+ deriving stock (Eq, Ord, Show, Generic)+ deriving anyclass (NFData)++data OverlayOperand+ = LeftOverlayOperand+ | RightOverlayOperand++-- | Phantom source cell kind; both provenance axes share one physical payload.+data BoundaryFeature = BoundaryVertexFeature | BoundaryEdgeFeature++data BoundaryRef (feature :: BoundaryFeature) (operand :: OverlayOperand) = BoundaryRef+ { boundaryRefComponent :: !Int+ , boundaryRefLoop :: !BoundaryLoopRef+ , boundaryRefLocalIndex :: !Int+ }+ deriving stock (Eq, Ord, Show, Generic)+ deriving anyclass (NFData)++type BoundaryVertexRef operand = BoundaryRef 'BoundaryVertexFeature operand+type BoundaryEdgeRef operand = BoundaryRef 'BoundaryEdgeFeature operand++-- | Complete typed source closure of one arrangement vertex.+data OverlayVertexOrigin = OverlayVertexOrigin+ { overlayOriginLeftVertices :: ![BoundaryVertexRef 'LeftOverlayOperand]+ , overlayOriginRightVertices :: ![BoundaryVertexRef 'RightOverlayOperand]+ , overlayOriginLeftEdges :: ![BoundaryEdgeRef 'LeftOverlayOperand]+ , overlayOriginRightEdges :: ![BoundaryEdgeRef 'RightOverlayOperand]+ }+ deriving stock (Eq, Ord, Show, Generic)+ deriving anyclass (NFData)++-- | Nonempty typed source-edge provenance of one exact atomic interval.+data OverlayEdgeOrigin = OverlayEdgeOrigin+ { overlayEdgeLeftSources :: ![BoundaryEdgeRef 'LeftOverlayOperand]+ , overlayEdgeRightSources :: ![BoundaryEdgeRef 'RightOverlayOperand]+ }+ deriving stock (Eq, Ord, Show, Generic)+ deriving anyclass (NFData)++-- | Sorted nonempty labels whose closures contain one relatively open cell.+newtype OverlaySupport label = OverlaySupport (NonEmpty label)+ deriving stock (Eq, Ord, Show, Generic)+ deriving anyclass (NFData)++overlaySupportLabels :: OverlaySupport label -> NonEmpty label+overlaySupportLabels (OverlaySupport labels) = labels++data OverlayCellSupport leftLabel rightLabel = OverlayCellSupport+ { overlaySupportLeft :: !(OverlaySupport leftLabel)+ , overlaySupportRight :: !(OverlaySupport rightLabel)+ }+ deriving stock (Eq, Ord, Show, Generic)+ deriving anyclass (NFData)++newtype OverlayCellId = OverlayCellId Int+ deriving stock (Eq, Ord, Show, Generic)+ deriving anyclass (NFData)++data OverlayCellGeometry+ = BoundedOverlayCell !PolygonComponent+ | UnboundedOverlayCell ![ExactLoop]+ deriving stock (Eq, Ord, Show, Generic)+ deriving anyclass (NFData)++data OverlayCell leftLabel rightLabel = OverlayCell+ { overlayCellLeft :: !leftLabel+ , overlayCellRight :: !rightLabel+ , overlayCellGeometry :: !OverlayCellGeometry+ }+ deriving stock (Eq, Ord, Show, Generic)+ deriving anyclass (NFData)++-- | A resident triangle either realizes an exact two-cell or collapses under+-- the authoritative exact coordinates. A collapsed triangle descends to the+-- unique adjacent exact cell through representation diagonals, but exact+-- region operations must not mistake its binary64 area for exact area.+data OverlayFace+ = OverlayCellFace+ { overlayFaceCellId :: !OverlayCellId+ }+ | OverlayCollapsedFace+ { overlayFaceCellId :: !OverlayCellId+ }+ deriving stock (Eq, Ord, Show, Generic)+ deriving anyclass (NFData)++-- | Exact geometry and provenance are intrinsic to a vertex. Label support is+-- the finite union of incident cell descriptors and is therefore a view.+data OverlayVertex = OverlayVertex+ { overlayExactPoint :: !ExactPoint+ , overlayVertexOrigin :: !OverlayVertexOrigin+ }+ deriving stock (Eq, Ord, Show, Generic)+ deriving anyclass (NFData)++-- | Boundary provenance is intrinsic; label support is derived from the two+-- incident face-cell references.+data OverlayEdge+ = OverlayBoundary !OverlayEdgeOrigin+ | OverlayDiagonal+ deriving stock (Eq, Ord, Show, Generic)+ deriving anyclass (NFData)++data OverlayReceipt = OverlayReceipt+ { overlayInputSegments :: !Int+ , overlayRelationEvents :: !Int+ , overlayExactCrossings :: !Int+ , overlayOverlapIntervals :: !Int+ , overlayAtomicEdges :: !Int+ , overlayOutputVertices :: !Int+ , overlayArrangementCells :: !Int+ , overlayResidentFaces :: !Int+ , overlayEmbeddingCandidates :: !Int+ , overlayTotalRelationChecks :: !Int+ , overlaySweepMaximumHeight :: !Int+ }+ deriving stock (Eq, Ord, Show, Generic)+ deriving anyclass (NFData)++data OverlayArrangementObstruction leftLabel rightLabel+ = OverlayLeftSourceSideConflict+ !OverlayEdgeOrigin+ !(NonEmpty leftLabel)+ | OverlayRightSourceSideConflict+ !OverlayEdgeOrigin+ !(NonEmpty rightLabel)+ | OverlayRotationDegenerate !ExactPoint+ | OverlayFaceComponentEmpty+ | OverlayCellCycleDidNotClose !ExactPoint !ExactPoint+ | OverlayTransitionSourceMismatch+ !FaceId+ !UndirectedEdgeId+ !(leftLabel, rightLabel)+ !(leftLabel, rightLabel)+ | OverlayResidentFaceLabelConflict+ !FaceId+ !UndirectedEdgeId+ !(leftLabel, rightLabel)+ !(leftLabel, rightLabel)+ | OverlayResidentFaceArity !FaceId !Int+ | OverlayResidentFaceOrientationReversed !FaceId+ | OverlayCollapsedFacesUnowned !(NonEmpty FaceId)+ | OverlayCollapsedFacesAmbiguous+ !(NonEmpty FaceId)+ !(NonEmpty OverlayCellId)+ | OverlayDuplicateCellSignature !PolygonComponent+ deriving stock (Eq, Show, Generic)+ deriving anyclass (NFData)++data OverlayCellWitness+ = OverlayAtomicConstraintMissing !ExactPoint !ExactPoint+ | OverlayAtomicConstraintOrientationMismatch+ !UndirectedEdgeId+ !ExactPoint+ !ExactPoint+ | OverlayUnexpectedConstraint !UndirectedEdgeId+ | OverlayBoundaryEdgeNotConstrained !UndirectedEdgeId+ | OverlayResidentFaceUnassigned !FaceId+ | OverlayCellPayloadMissing !OverlayCellId+ | OverlayVertexSupportMissing !VertexId+ | OverlayEdgeSupportMissing !UndirectedEdgeId+ | OverlayEmbeddedVertexCountMismatch !Int !Int+ | OverlayExactVertexMissing !ExactPoint+ deriving stock (Eq, Show, Generic)+ deriving anyclass (NFData)++data OverlayError leftLabel rightLabel+ = OverlayExactArithmetic !ExactArithmeticError+ | OverlaySegmentEventsInvalid !ExactSegmentEventObstruction+ | OverlayArrangementInvalid !(OverlayArrangementObstruction leftLabel rightLabel)+ | OverlayEmbeddingRefused !(NonEmpty OverlayEmbeddingObstruction)+ | OverlayBuildFailed !CdtError+ | OverlayRegionPublicationFailed !RegionPublicationError+ | OverlayProvenanceIncomplete !OverlayCellWitness+ deriving stock (Eq, Show, Generic)+ deriving anyclass (NFData)++-- | The singular exact subdivision carrier. Its embedded DCEL is a derived+-- binary64 realization; exact coordinates remain in the vertex payload plane.+data OverlayResult leftLabel rightLabel = OverlayResult+ { overlayResultTriangulation+ :: !( Triangulation+ 'Constrained+ OverlayVertex+ ()+ OverlayEdge+ OverlayFace+ )+ , overlayResultCells :: !(Vector (OverlayCell leftLabel rightLabel))+ , overlayResultOutsideLabels :: !(leftLabel, rightLabel)+ , overlayResultReceipt :: !OverlayReceipt+ }+ deriving stock (Generic)+ deriving anyclass (NFData)++data OverlaySelectionKind+ = ClosedUnionSelection+ | ClosedIntersectionSelection+ | RegularizedDifferenceSelection+ deriving stock (Eq, Ord, Show, Generic)+ deriving anyclass (NFData)++data OverlaySelectionError+ = OverlaySelectionContainsUnboundedCell !OverlaySelectionKind+ | OverlaySelectionProvenance !OverlayCellWitness+ | OverlaySelectionInvalid !CellSelectionError+ deriving stock (Eq, Show, Generic)+ deriving anyclass (NFData)
+ src-planar/Moonlight/Triangulation/Minkowski.hs view
@@ -0,0 +1,595 @@+{-# LANGUAGE DataKinds #-}++-- | Exact polygonal Minkowski addition and regularized two-dimensional+-- morphology. Convex convolution is direct; nonconvex construction descends+-- through the existing exact overlay, resident CDT, and grouped publication+-- owners. Lower-dimensional erosion residuals cannot inhabit 'PlanarRegion'+-- and therefore publish as empty rather than being forged as polygons.+module Moonlight.Triangulation.Minkowski+ ( ConvexPolygon+ , convexPolygon+ , convexPolygonPoints+ , StructuringElement+ , structuringElement+ , MinkowskiOperation (..)+ , MinkowskiError (..)+ , MinkowskiReceipt (..)+ , convexMinkowskiSum+ , minkowskiSum+ , erodeBy+ , openWith+ , closeWith+ , polygonOffset+ , polygonInset+ ) where++import Control.Applicative ((<|>))+import Control.Monad (filterM)+import Data.Bifunctor (first)+import qualified Data.IntMap.Strict as IntMap+import qualified Data.List as List+import qualified Data.Map.Strict as Map+import Data.List.NonEmpty (NonEmpty (..))+import qualified Data.List.NonEmpty as NonEmpty+import Data.Maybe (fromMaybe)+import qualified Data.Set as Set+import qualified Data.Vector as V+import Moonlight.Triangulation.Dcel+ ( faceData+ , faceVertices+ , vertexData+ )+import Moonlight.Triangulation.Exact+ ( ExactPoint+ , exactPointCoordinates+ )+import Moonlight.Triangulation.Handles.HandleDefs+ ( FaceId+ )+import Moonlight.Triangulation.Handles.Iterators.FixedIterators (innerFaces)+import Moonlight.Triangulation.Internal.ExactRational+ ( ExactRational+ , exactRationalDenominator+ , exactRationalNumerator+ )+import Moonlight.Triangulation.Internal.BoundaryCycle (cyclePairs)+import Moonlight.Triangulation.Internal.Dyadic (integerBitLength)+import Moonlight.Triangulation.Internal.Minkowski.Convex+ ( addExactPoints+ , admittedConvexLoop+ , convexHullPolygon+ , convexMinkowskiPolygon+ , convexMinkowskiSum+ , convexPolygon+ , convexPolygonCentroid+ , convexPolygonPoints+ , convexPolygonRegion+ , erodeConvexBy+ , reflectConvexPolygon+ , structuringElement+ , structuringElementPolygon+ )+import Moonlight.Triangulation.Internal.Minkowski.Types+import Moonlight.Triangulation.Internal.Overlay.Resident+ ( faceCarriesExactArea+ , faceLabels+ )+import Moonlight.Triangulation.Internal.Overlay.Types+ ( OverlayCell (..)+ , OverlayCellGeometry (..)+ , OverlayCellId (..)+ , OverlayFace (..)+ , OverlayResult (..)+ , OverlayVertex (..)+ )+import Moonlight.Triangulation.Overlay+ ( OverlayReceipt (..)+ , overlayLayers+ , overlayReceipt+ , overlaySelectedRegion+ )+import Moonlight.Triangulation.Region+ ( PlanarLayer+ , PlanarRegion+ , PolygonComponent+ , RegionPointLocation (..)+ , emptyPlanarRegion+ , exactLoopPoints+ , planarLayerRegions+ , planarRegionComponents+ , polygonHoleLoops+ , polygonOuterLoop+ , regionPointLocation+ )+import Moonlight.Triangulation.Internal.Region.Publication+ ( labelledPlanarLayerFromExactCoordinates+ , planarLayerFromAdmittedComponents+ )++data MorphologyMetrics = MorphologyMetrics+ { metricOverlayPasses :: !Int+ , metricExactCrossings :: !Int+ , metricOutputCells :: !(Maybe Int)+ }++emptyMetrics :: MorphologyMetrics+emptyMetrics = MorphologyMetrics 0 0 Nothing++appendMetrics :: MorphologyMetrics -> MorphologyMetrics -> MorphologyMetrics+appendMetrics left right =+ MorphologyMetrics+ { metricOverlayPasses = metricOverlayPasses left + metricOverlayPasses right+ , metricExactCrossings = metricExactCrossings left + metricExactCrossings right+ , metricOutputCells = metricOutputCells right <|> metricOutputCells left+ }++minkowskiSum+ :: PlanarRegion+ -> PlanarRegion+ -> Either MinkowskiError (PlanarRegion, MinkowskiReceipt)+minkowskiSum left right = do+ (leftPieces, leftMetrics) <- decomposeRegion left+ (rightPieces, rightMetrics) <- decomposeRegion right+ let generated =+ [ convexMinkowskiPolygon leftPiece rightPiece+ | leftPiece <- leftPieces+ , rightPiece <- rightPieces+ ]+ generatedRegions = map convexPolygonRegion generated+ convolutionEdges = sum (map (NonEmpty.length . convexPolygonPoints) generated)+ (result, unionMetrics) <- unionRegions generatedRegions+ let metrics = leftMetrics `appendMetrics` rightMetrics `appendMetrics` unionMetrics+ pure+ ( result+ , MinkowskiReceipt+ { minkowskiOperation = MinkowskiAddition+ , minkowskiInputComponents =+ length (planarRegionComponents left)+ + length (planarRegionComponents right)+ , minkowskiConvexPieces = length leftPieces + length rightPieces+ , minkowskiGeneratedPieces = length generated+ , minkowskiGeneratedConvolutionEdges = convolutionEdges+ , minkowskiOverlayPasses = metricOverlayPasses metrics+ , minkowskiExactCrossings = metricExactCrossings metrics+ , minkowskiOutputCells = fromMaybe 0 (metricOutputCells metrics)+ , minkowskiExactCoordinateBitGrowth =+ coordinateBitGrowth [left, right] result+ }+ )++-- | Erode a polygonal region by an origin-anchored convex kernel and publish+-- the regularized full-dimensional result. A residual consisting only of+-- points or segments is represented by 'emptyPlanarRegion'.+erodeBy+ :: StructuringElement+ -> PlanarRegion+ -> Either MinkowskiError (PlanarRegion, MinkowskiReceipt)+erodeBy element source =+ case singleConvexRegion source of+ Just sourcePolygon -> convexErosion element source sourcePolygon+ Nothing -> generalErosion element source++openWith+ :: StructuringElement+ -> PlanarRegion+ -> Either MinkowskiError (PlanarRegion, MinkowskiReceipt)+openWith element source = do+ (eroded, erosionReceipt) <- erodeBy element source+ (opened, additionReceipt) <- polygonOffset element eroded+ pure+ ( opened+ , composeReceipts+ MinkowskiOpening+ (length (planarRegionComponents source))+ erosionReceipt+ additionReceipt+ )++closeWith+ :: StructuringElement+ -> PlanarRegion+ -> Either MinkowskiError (PlanarRegion, MinkowskiReceipt)+closeWith element source = do+ (expanded, additionReceipt) <- polygonOffset element source+ (closed, erosionReceipt) <- erodeBy element expanded+ pure+ ( closed+ , composeReceipts+ MinkowskiClosing+ (length (planarRegionComponents source))+ additionReceipt+ erosionReceipt+ )++polygonOffset+ :: StructuringElement+ -> PlanarRegion+ -> Either MinkowskiError (PlanarRegion, MinkowskiReceipt)+polygonOffset element source =+ minkowskiSum source (convexPolygonRegion (structuringElementPolygon element))++polygonInset+ :: StructuringElement+ -> PlanarRegion+ -> Either MinkowskiError (PlanarRegion, MinkowskiReceipt)+polygonInset = erodeBy++decomposeRegion+ :: PlanarRegion+ -> Either MinkowskiError ([ConvexPolygon], MorphologyMetrics)+decomposeRegion region =+ case traverse convexComponent (planarRegionComponents region) of+ Just convexPieces -> Right (convexPieces, emptyMetrics)+ Nothing -> triangulatedPieces region++convexComponent :: PolygonComponent -> Maybe ConvexPolygon+convexComponent component =+ case polygonHoleLoops component of+ [] -> admittedConvexLoop (polygonOuterLoop component)+ _ -> Nothing++singleConvexRegion :: PlanarRegion -> Maybe ConvexPolygon+singleConvexRegion region =+ case planarRegionComponents region of+ [component] -> convexComponent component+ _ -> Nothing++triangulatedPieces+ :: PlanarRegion+ -> Either MinkowskiError ([ConvexPolygon], MorphologyMetrics)+triangulatedPieces region = do+ let sourceLayer = morphologyLayer region+ result <- first MinkowskiOverlayFailed (overlayLayers sourceLayer emptyMorphologyLayer)+ selectedFaces <-+ filterM+ (\face ->+ if faceCarriesExactArea result face+ then+ fmap fst+ ( first MinkowskiOverlayCellWitness+ (faceLabels result face)+ )+ else Right False)+ (innerFaces (overlayResultTriangulation result))+ pieces <- traverse (faceConvexPolygon result) selectedFaces+ pure (pieces, metricsFromOverlay result (length pieces))++faceConvexPolygon+ :: OverlayResult leftLabel rightLabel+ -> FaceId+ -> Either MinkowskiError ConvexPolygon+faceConvexPolygon result face =+ case+ map+ (overlayExactPoint . vertexData triangulation)+ (faceVertices triangulation face) of+ [firstPoint, secondPoint, thirdPoint] ->+ convexHullPolygon (firstPoint :| [secondPoint, thirdPoint])+ vertices -> Left (MinkowskiFaceArity face (length vertices))+ where+ triangulation = overlayResultTriangulation result++unionRegions+ :: [PlanarRegion]+ -> Either MinkowskiError (PlanarRegion, MorphologyMetrics)+unionRegions [] = Right (emptyPlanarRegion, emptyMetrics)+unionRegions [region] =+ Right+ ( region+ , emptyMetrics{metricOutputCells = Just (length (planarRegionComponents region))}+ )+unionRegions regions = do+ let (leftRegions, rightRegions) = splitAt (length regions `div` 2) regions+ left <- unionRegions leftRegions+ right <- unionRegions rightRegions+ glueRegionUnion left right++glueRegionUnion+ :: (PlanarRegion, MorphologyMetrics)+ -> (PlanarRegion, MorphologyMetrics)+ -> Either MinkowskiError (PlanarRegion, MorphologyMetrics)+glueRegionUnion (left, leftMetrics) (right, rightMetrics)+ | null (planarRegionComponents left) = Right (right, leftMetrics `appendMetrics` rightMetrics)+ | null (planarRegionComponents right) = Right (left, leftMetrics `appendMetrics` rightMetrics)+ | otherwise = do+ result <-+ first MinkowskiOverlayFailed+ (overlayLayers (morphologyLayer left) (morphologyLayer right))+ published <-+ first MinkowskiPublicationFailed+ ( overlaySelectedRegion+ (uncurry (||))+ result+ )+ let selectedCells =+ V.foldl'+ (\count cell ->+ case overlayCellGeometry cell of+ BoundedOverlayCell _+ | overlayCellLeft cell || overlayCellRight cell ->+ count + 1+ _ -> count)+ 0+ (overlayResultCells result)+ pure+ ( published+ , leftMetrics+ `appendMetrics` rightMetrics+ `appendMetrics` metricsFromOverlay result selectedCells+ )++convexErosion+ :: StructuringElement+ -> PlanarRegion+ -> ConvexPolygon+ -> Either MinkowskiError (PlanarRegion, MinkowskiReceipt)+convexErosion element source sourcePolygon = do+ eroded <- erodeConvexBy sourcePolygon (structuringElementPolygon element)+ let result = maybe emptyPlanarRegion convexPolygonRegion eroded+ outputCells = maybe 0 (const 1) eroded+ generatedEdges = maybe 0 (NonEmpty.length . convexPolygonPoints) eroded+ pure+ ( result+ , MinkowskiReceipt+ { minkowskiOperation = MinkowskiErosion+ , minkowskiInputComponents = 1+ , minkowskiConvexPieces = 2+ , minkowskiGeneratedPieces = outputCells+ , minkowskiGeneratedConvolutionEdges = generatedEdges+ , minkowskiOverlayPasses = 0+ , minkowskiExactCrossings = 0+ , minkowskiOutputCells = outputCells+ , minkowskiExactCoordinateBitGrowth =+ coordinateBitGrowth+ [ source+ , convexPolygonRegion (structuringElementPolygon element)+ ]+ result+ }+ )++generalErosion+ :: StructuringElement+ -> PlanarRegion+ -> Either MinkowskiError (PlanarRegion, MinkowskiReceipt)+generalErosion element source+ | null sourceEdges = Right (emptyPlanarRegion, emptyErosionReceipt)+ | otherwise = do+ sweptPolygons <- traverse (sweepBoundaryEdge reflectedKernel) sourceEdges+ let sweptRegions = map convexPolygonRegion sweptPolygons+ generatedEdges =+ sum (map (NonEmpty.length . convexPolygonPoints) sweptPolygons)+ (contactRegion, unionMetrics) <- unionRegions sweptRegions+ candidateOverlay <-+ first MinkowskiOverlayFailed+ (overlayLayers (morphologyLayer contactRegion) emptyMorphologyLayer)+ kernelWitness <- convexPolygonCentroid kernel+ let representativeFaceByCell = representativeFaces candidateOverlay+ selectedCellIds <-+ Set.fromList+ <$> filterM+ ( classifyCandidateCell+ source+ kernelWitness+ representativeFaceByCell+ candidateOverlay+ )+ (boundedOutsideCellIds candidateOverlay)+ published <- publishCellSelection selectedCellIds candidateOverlay+ let candidateMetrics =+ metricsFromOverlay candidateOverlay (Set.size selectedCellIds)+ metrics = unionMetrics `appendMetrics` candidateMetrics+ pure+ ( published+ , MinkowskiReceipt+ { minkowskiOperation = MinkowskiErosion+ , minkowskiInputComponents = length (planarRegionComponents source)+ , minkowskiConvexPieces = 1+ , minkowskiGeneratedPieces = length sweptPolygons+ , minkowskiGeneratedConvolutionEdges = generatedEdges+ , minkowskiOverlayPasses = metricOverlayPasses metrics+ , minkowskiExactCrossings = metricExactCrossings metrics+ , minkowskiOutputCells = Set.size selectedCellIds+ , minkowskiExactCoordinateBitGrowth =+ coordinateBitGrowth+ [source, convexPolygonRegion kernel]+ published+ }+ )+ where+ kernel = structuringElementPolygon element+ reflectedKernel = reflectConvexPolygon kernel+ sourceEdges = regionBoundaryEdges source+ emptyErosionReceipt =+ MinkowskiReceipt+ { minkowskiOperation = MinkowskiErosion+ , minkowskiInputComponents = 0+ , minkowskiConvexPieces = 1+ , minkowskiGeneratedPieces = 0+ , minkowskiGeneratedConvolutionEdges = 0+ , minkowskiOverlayPasses = 0+ , minkowskiExactCrossings = 0+ , minkowskiOutputCells = 0+ , minkowskiExactCoordinateBitGrowth = 0+ }++sweepBoundaryEdge+ :: ConvexPolygon+ -> (ExactPoint, ExactPoint)+ -> Either MinkowskiError ConvexPolygon+sweepBoundaryEdge reflectedKernel (from, to) =+ case convexPolygonPoints reflectedKernel of+ firstKernelPoint :| remainingKernelPoints ->+ convexHullPolygon+ ( addExactPoints from firstKernelPoint+ :| ( map (addExactPoints from) remainingKernelPoints+ <> map (addExactPoints to) kernelPoints+ )+ )+ where+ kernelPoints = NonEmpty.toList (convexPolygonPoints reflectedKernel)++boundedOutsideCellIds+ :: OverlayResult Bool Bool+ -> [OverlayCellId]+boundedOutsideCellIds result =+ V.ifoldr+ (\index cell selected ->+ case overlayCellGeometry cell of+ BoundedOverlayCell _+ | not (overlayCellLeft cell)+ && not (overlayCellRight cell) ->+ OverlayCellId index : selected+ _ -> selected)+ []+ (overlayResultCells result)++classifyCandidateCell+ :: PlanarRegion+ -> ExactPoint+ -> IntMap.IntMap FaceId+ -> OverlayResult Bool Bool+ -> OverlayCellId+ -> Either MinkowskiError Bool+classifyCandidateCell source kernelWitness representativeFaceByCell result cellId = do+ face <-+ maybe+ (Left (MinkowskiCandidateCellMissing cellId))+ Right+ (IntMap.lookup (overlayCellIndex cellId) representativeFaceByCell)+ candidate <- convexPolygonCentroid =<< faceConvexPolygon result face+ let inclusionWitness = addExactPoints candidate kernelWitness+ case regionPointLocation source inclusionWitness of+ RegionInterior -> Right True+ RegionExterior -> Right False+ RegionOnBoundary -> Left (MinkowskiInclusionAmbiguous cellId inclusionWitness)++representativeFaces+ :: OverlayResult leftLabel rightLabel+ -> IntMap.IntMap FaceId+representativeFaces result =+ IntMap.fromListWith min+ [ (overlayCellIndex (overlayFaceCellId (faceData triangulation face)), face)+ | face <- innerFaces triangulation+ , faceCarriesExactArea result face+ ]+ where+ triangulation = overlayResultTriangulation result++overlayCellIndex :: OverlayCellId -> Int+overlayCellIndex (OverlayCellId index) = index++publishCellSelection+ :: Set.Set OverlayCellId+ -> OverlayResult leftLabel rightLabel+ -> Either MinkowskiError PlanarRegion+publishCellSelection selected result = do+ layer <-+ first MinkowskiPublicationFailed+ ( labelledPlanarLayerFromExactCoordinates+ False+ triangulation+ (\vertex -> Right (overlayExactPoint (vertexData triangulation vertex)))+ (\face ->+ if faceCarriesExactArea result face+ then+ Right+ ( Set.member+ (overlayFaceCellId (faceData triangulation face))+ selected+ )+ else Right False)+ )+ pure (Map.findWithDefault emptyPlanarRegion True (planarLayerRegions layer))+ where+ triangulation = overlayResultTriangulation result++morphologyLayer+ :: PlanarRegion+ -> PlanarLayer Bool+morphologyLayer region =+ planarLayerFromAdmittedComponents+ False+ [(True, component) | component <- planarRegionComponents region]++emptyMorphologyLayer :: PlanarLayer Bool+emptyMorphologyLayer = morphologyLayer emptyPlanarRegion++regionBoundaryEdges :: PlanarRegion -> [(ExactPoint, ExactPoint)]+regionBoundaryEdges region =+ concatMap+ (\component ->+ concatMap+ (cyclePairs . exactLoopPoints)+ (polygonOuterLoop component : polygonHoleLoops component))+ (planarRegionComponents region)++metricsFromOverlay+ :: OverlayResult leftLabel rightLabel+ -> Int+ -> MorphologyMetrics+metricsFromOverlay result outputCells =+ MorphologyMetrics+ { metricOverlayPasses = 1+ , metricExactCrossings = overlayExactCrossings (overlayReceipt result)+ , metricOutputCells = Just outputCells+ }++composeReceipts+ :: MinkowskiOperation+ -> Int+ -> MinkowskiReceipt+ -> MinkowskiReceipt+ -> MinkowskiReceipt+composeReceipts operation inputComponents firstReceipt secondReceipt =+ MinkowskiReceipt+ { minkowskiOperation = operation+ , minkowskiInputComponents = inputComponents+ , minkowskiConvexPieces =+ minkowskiConvexPieces firstReceipt+ + minkowskiConvexPieces secondReceipt+ , minkowskiGeneratedPieces =+ minkowskiGeneratedPieces firstReceipt+ + minkowskiGeneratedPieces secondReceipt+ , minkowskiGeneratedConvolutionEdges =+ minkowskiGeneratedConvolutionEdges firstReceipt+ + minkowskiGeneratedConvolutionEdges secondReceipt+ , minkowskiOverlayPasses =+ minkowskiOverlayPasses firstReceipt+ + minkowskiOverlayPasses secondReceipt+ , minkowskiExactCrossings =+ minkowskiExactCrossings firstReceipt+ + minkowskiExactCrossings secondReceipt+ , minkowskiOutputCells = minkowskiOutputCells secondReceipt+ , minkowskiExactCoordinateBitGrowth =+ max+ (minkowskiExactCoordinateBitGrowth firstReceipt)+ (minkowskiExactCoordinateBitGrowth secondReceipt)+ }++coordinateBitGrowth :: [PlanarRegion] -> PlanarRegion -> Int+coordinateBitGrowth inputs output =+ max 0+ ( regionCoordinateBits output+ - List.foldl' (\maximumBits -> max maximumBits . regionCoordinateBits) 0 inputs+ )++regionCoordinateBits :: PlanarRegion -> Int+regionCoordinateBits = List.foldl' componentBits 0 . planarRegionComponents+ where+ componentBits maximumBits component =+ List.foldl'+ loopBits+ maximumBits+ (polygonOuterLoop component : polygonHoleLoops component)+ loopBits maximumBits =+ List.foldl' pointBits maximumBits . exactLoopPoints+ pointBits maximumBits point =+ let (x, y) = exactPointCoordinates point+ in max maximumBits (max (rationalBits x) (rationalBits y))++rationalBits :: ExactRational -> Int+rationalBits value =+ max+ (integerBitLength (abs (exactRationalNumerator value)))+ (integerBitLength (exactRationalDenominator value))
+ src-planar/Moonlight/Triangulation/Overlay.hs view
@@ -0,0 +1,296 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DerivingStrategies #-}++-- | Exact labelled common refinement. Source boundaries descend through one+-- exact segment-event plan, glue into canonical atomic constraints, and are+-- admitted only when their binary64 DCEL realization preserves every exact+-- relation.+module Moonlight.Triangulation.Overlay+ ( BoundaryLoopRef (..)+ , OverlayOperand+ , BoundaryRef+ , BoundaryVertexRef+ , BoundaryEdgeRef+ , boundaryRefComponent+ , boundaryRefLoop+ , boundaryRefLocalIndex+ , OverlayVertexOrigin+ , overlayOriginLeftVertices+ , overlayOriginRightVertices+ , overlayOriginLeftEdges+ , overlayOriginRightEdges+ , OverlayEdgeOrigin+ , overlayEdgeLeftSources+ , overlayEdgeRightSources+ , OverlaySupport+ , overlaySupportLabels+ , OverlayCellSupport (..)+ , OverlayCellId (..)+ , OverlayCellGeometry (..)+ , OverlayCell (..)+ , OverlayFace (..)+ , OverlayVertex (..)+ , OverlayEdge (..)+ , OverlayReceipt (..)+ , OverlayArrangementObstruction (..)+ , OverlayCellWitness (..)+ , OverlayError (..)+ , OverlayResult+ , OverlaySelectionKind (..)+ , OverlaySelectionError (..)+ , overlayLayers+ , overlayEmbeddedTriangulation+ , overlayReceipt+ , overlayCells+ , overlayArrangementVertices+ , overlayArrangementEdges+ , overlayPlanarLayer+ , overlaySelectedRegion+ , overlayClosedUnion+ , overlayClosedIntersection+ , overlayRegularizedDifference+ ) where++import Data.Bifunctor (first)+import Control.Monad (filterM)+import qualified Data.Map.Strict as Map+import qualified Data.Vector as V+import Moonlight.Triangulation.CellSet (ExactCellSet)+import qualified Moonlight.Triangulation.Dcel as Dcel+import Moonlight.Triangulation.Dcel (vertexData)+import Moonlight.Triangulation.Handles.HandleDefs+ ( UndirectedEdgeId+ , VertexId+ )+import Moonlight.Triangulation.Handles.Iterators.FixedIterators+ ( innerFaces+ , undirectedEdges+ , vertices+ )+import Moonlight.Triangulation.Internal.CellSet (closeExactCellSetWith)+import Moonlight.Triangulation.Internal.Overlay.Arrangement+ ( certifyArrangement+ )+import Moonlight.Triangulation.Internal.Overlay.Resident+ ( OverlayDiagonalSchedule (CanonicalOverlayDiagonals)+ , edgeSupport+ , faceCarriesExactArea+ , faceLabels+ , regionFaceLabels+ , residentOverlay+ , vertexSupport+ )+import Moonlight.Triangulation.Internal.Overlay.Types+import Moonlight.Triangulation.Internal.Representation (Triangulation)+import Moonlight.Triangulation.Internal.Types+ ( ConstraintMode (Constrained)+ )+import Moonlight.Triangulation.Region+ ( PlanarLayer+ , PlanarRegion+ , RegionPublicationError (..)+ , emptyPlanarRegion+ , planarLayerOutsideLabel+ , planarLayerRegions+ )+import Moonlight.Triangulation.Internal.Region.Publication+ ( labelledPlanarLayerFromExactCoordinates+ , planarLayerFromAdmittedComponents+ )++-- | Construct the exact common refinement and its one faithful resident DCEL.+overlayLayers+ :: (Ord leftLabel, Ord rightLabel)+ => PlanarLayer leftLabel+ -> PlanarLayer rightLabel+ -> Either+ (OverlayError leftLabel rightLabel)+ (OverlayResult leftLabel rightLabel)+overlayLayers leftLayer rightLayer = do+ certified <- certifyArrangement leftLayer rightLayer+ residentOverlay+ CanonicalOverlayDiagonals+ (planarLayerOutsideLabel leftLayer, planarLayerOutsideLabel rightLayer)+ certified++-- | The binary64 realization used by existing DCEL observations. Exact overlay+-- operations accept 'OverlayResult', never this projection.+overlayEmbeddedTriangulation+ :: OverlayResult leftLabel rightLabel+ -> Triangulation+ 'Constrained+ OverlayVertex+ ()+ OverlayEdge+ OverlayFace+overlayEmbeddedTriangulation = overlayResultTriangulation++overlayReceipt :: OverlayResult leftLabel rightLabel -> OverlayReceipt+overlayReceipt = overlayResultReceipt++overlayCells+ :: OverlayResult leftLabel rightLabel+ -> [(OverlayCellId, OverlayCell leftLabel rightLabel)]+overlayCells result =+ V.toList+ (V.imap (\index cell -> (OverlayCellId index, cell)) (overlayResultCells result))++overlayArrangementVertices+ :: OverlayResult leftLabel rightLabel+ -> [(VertexId, OverlayVertex)]+overlayArrangementVertices result =+ let triangulation = overlayResultTriangulation result+ in [(vertex, vertexData triangulation vertex) | vertex <- vertices triangulation]++overlayArrangementEdges+ :: OverlayResult leftLabel rightLabel+ -> [(UndirectedEdgeId, OverlayEdgeOrigin)]+overlayArrangementEdges result =+ let triangulation = overlayResultTriangulation result+ in [ (edge, origin)+ | edge <- undirectedEdges triangulation+ , OverlayBoundary origin <- [Dcel.undirectedEdgeData triangulation edge]+ ]++-- | Publish the already-admitted bounded cell geometry. The resident DCEL is+-- a realization of these exact cells, not a second authoring source.+overlayPlanarLayer+ :: (Ord leftLabel, Ord rightLabel)+ => OverlayResult leftLabel rightLabel+ -> PlanarLayer (leftLabel, rightLabel)+overlayPlanarLayer result =+ planarLayerFromAdmittedComponents+ (overlayResultOutsideLabels result)+ [ ( (overlayCellLeft cell, overlayCellRight cell)+ , component+ )+ | cell <- V.toList (overlayResultCells result)+ , (overlayCellLeft cell, overlayCellRight cell)+ /= overlayResultOutsideLabels result+ , BoundedOverlayCell component <- [overlayCellGeometry cell]+ ]++-- | Publish the selected two-dimensional cells. Internal arrangement edges+-- between differently labelled but jointly selected cells dissolve because+-- selection precedes component descent.+overlaySelectedRegion+ :: ((leftLabel, rightLabel) -> Bool)+ -> OverlayResult leftLabel rightLabel+ -> Either RegionPublicationError PlanarRegion+overlaySelectedRegion selected result+ | selected (overlayResultOutsideLabels result) = Left RegionUnboundedSelection+ | otherwise = do+ published <-+ labelledPlanarLayerFromExactCoordinates+ False+ triangulation+ exactPointAt+ labelFace+ pure (Map.findWithDefault emptyPlanarRegion True (planarLayerRegions published))+ where+ triangulation = overlayResultTriangulation result+ exactPointAt vertex = Right (overlayExactPoint (vertexData triangulation vertex))+ labelFace face+ | faceCarriesExactArea result face =+ selected <$> regionFaceLabels result face+ | otherwise = Right False++overlayClosedUnion+ :: (Ord leftLabel, Ord rightLabel)+ => (leftLabel -> Bool)+ -> (rightLabel -> Bool)+ -> OverlayResult leftLabel rightLabel+ -> Either OverlaySelectionError ExactCellSet+overlayClosedUnion selectLeft selectRight =+ selectClosedCells+ ClosedUnionSelection+ (\support -> supportAny selectLeft (overlaySupportLeft support) || supportAny selectRight (overlaySupportRight support))+ (\leftLabel rightLabel -> selectLeft leftLabel || selectRight rightLabel)++overlayClosedIntersection+ :: (Ord leftLabel, Ord rightLabel)+ => (leftLabel -> Bool)+ -> (rightLabel -> Bool)+ -> OverlayResult leftLabel rightLabel+ -> Either OverlaySelectionError ExactCellSet+overlayClosedIntersection selectLeft selectRight =+ selectClosedCells+ ClosedIntersectionSelection+ (\support -> supportAny selectLeft (overlaySupportLeft support) && supportAny selectRight (overlaySupportRight support))+ (\leftLabel rightLabel -> selectLeft leftLabel && selectRight rightLabel)++overlayRegularizedDifference+ :: (leftLabel -> Bool)+ -> (rightLabel -> Bool)+ -> OverlayResult leftLabel rightLabel+ -> Either OverlaySelectionError ExactCellSet+overlayRegularizedDifference selectLeft selectRight result =+ let outsidePair = overlayResultOutsideLabels result+ selectFace leftLabel rightLabel = selectLeft leftLabel && not (selectRight rightLabel)+ in if uncurry selectFace outsidePair+ then Left (OverlaySelectionContainsUnboundedCell RegularizedDifferenceSelection)+ else closeSelectedCells [] [] selectFace result++selectClosedCells+ :: (Ord leftLabel, Ord rightLabel)+ => OverlaySelectionKind+ -> (OverlayCellSupport leftLabel rightLabel -> Bool)+ -> (leftLabel -> rightLabel -> Bool)+ -> OverlayResult leftLabel rightLabel+ -> Either OverlaySelectionError ExactCellSet+selectClosedCells selectionKind selectSupport selectFace result =+ let outsidePair = overlayResultOutsideLabels result+ triangulation = overlayResultTriangulation result+ in if uncurry selectFace outsidePair+ then Left (OverlaySelectionContainsUnboundedCell selectionKind)+ else do+ selectedVertices <-+ filterM+ ( fmap selectSupport+ . first OverlaySelectionProvenance+ . vertexSupport result+ )+ (vertices triangulation)+ selectedEdges <-+ filterM+ (\edge ->+ case Dcel.undirectedEdgeData triangulation edge of+ OverlayDiagonal -> Right False+ OverlayBoundary _ ->+ selectSupport+ <$> first OverlaySelectionProvenance (edgeSupport result edge))+ (undirectedEdges triangulation)+ closeSelectedCells selectedVertices selectedEdges selectFace result++closeSelectedCells+ :: [VertexId]+ -> [UndirectedEdgeId]+ -> (leftLabel -> rightLabel -> Bool)+ -> OverlayResult leftLabel rightLabel+ -> Either OverlaySelectionError ExactCellSet+closeSelectedCells selectedVertices selectedEdges selectFace result =+ let triangulation = overlayResultTriangulation result+ exactPointAt vertex = Right (overlayExactPoint (vertexData triangulation vertex))+ in do+ selectedFaces <-+ filterM+ (\face ->+ if faceCarriesExactArea result face+ then+ fmap (uncurry selectFace)+ ( first OverlaySelectionProvenance+ (faceLabels result face)+ )+ else Right False)+ (innerFaces triangulation)+ first OverlaySelectionInvalid+ ( closeExactCellSetWith+ exactPointAt+ triangulation+ selectedVertices+ selectedEdges+ selectedFaces+ )++supportAny :: (label -> Bool) -> OverlaySupport label -> Bool+supportAny predicate = any predicate . overlaySupportLabels
src-public/Moonlight/Triangulation.hs view
@@ -98,10 +98,12 @@ , conflictFirstSegment , conflictSecondSegment , ConstrainedUnionError (..)+ , ConstraintBatchResult+ , constraintBatchTriangulation+ , constraintBatchOutcomes+ , constraintBatchStats , ConstrainedExtensionResult- , constrainedExtensionTriangulation- , constrainedExtensionConstraintOutcomes- , constrainedExtensionConstraintStats+ , constrainedExtensionConstraintBatch , constrainedExtensionBuildStats , ConstrainedSeamSource (..) , ConstrainedSeamFaceEvidence@@ -186,13 +188,16 @@ -- ** Face regions and alpha filtration , FaceComponent , faceComponentFaces+ , BoundaryOrientation (..) , BoundaryLoop+ , boundaryLoopOrientation , boundaryLoopVertices , RegionBoundary , regionBoundaryOuterLoop , regionBoundaryHoleLoops , BoundaryObstruction (..) , faceComponents+ , componentBoundaryLoops , componentBoundary , RadiusSquared , RadiusSquaredError (..)@@ -289,6 +294,7 @@ , ValuationError (..) , cellValuations , regionValuations+ , planarValuationsPerimeter , cellSetPerimeter , regionPerimeter , ConvexPolygon@@ -355,13 +361,16 @@ import Moonlight.Triangulation.FloodFillIterator ( BoundaryLoop , BoundaryObstruction (..)+ , BoundaryOrientation (..) , FaceComponent , RadiusSquared , RegionBoundary , RadiusSquaredError (..) , alphaShapeContainsFace+ , boundaryLoopOrientation , boundaryLoopVertices , componentBoundary+ , componentBoundaryLoops , faceComponentFaces , faceComponents , facesAtEvenBarrierDepth@@ -419,12 +428,14 @@ , ConstrainedSeamFaceEvidence , ConstrainedSeamResult , ConstrainedSeamSource (..)+ , ConstraintBatchResult , ConstraintConflict , CorridorObstruction (..) , constrainedExtensionBuildStats- , constrainedExtensionConstraintOutcomes- , constrainedExtensionConstraintStats- , constrainedExtensionTriangulation+ , constrainedExtensionConstraintBatch+ , constraintBatchOutcomes+ , constraintBatchStats+ , constraintBatchTriangulation , constrainedSeamBuildStats , constrainedSeamConstraintRecovery , constrainedSeamConstraintSegment@@ -602,6 +613,7 @@ , exactLengthExpression , exactLengthTerms , lengthCoefficient+ , planarValuationsPerimeter , regionPerimeter , regionValuations , squaredLength
+ src-public/Moonlight/Triangulation/HintGenerator.hs view
@@ -0,0 +1,492 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE NamedFieldPuns #-}++-- | Reusable point-location hints and their topology-preserving maintenance.+module Moonlight.Triangulation.HintGenerator+ ( LastUsedHint+ , emptyLastUsedHint+ , lastUsedHint+ , rememberVertex+ , HierarchyHint+ , defaultHierarchyBranchFactor+ , buildHierarchyHint+ , hierarchyHint+ , hierarchyBranchFactor+ , hierarchyBaseCount+ , hierarchyLevelCount+ , hierarchyVertexCount+ , updateHierarchyAfterInsertion+ , updateHierarchyAfterRemoval+ , rebuildHierarchyHint+ , removeManyWithHierarchy+ ) where++import qualified Data.Vector as V+import Control.DeepSeq (NFData)+import Data.List (sort)+import Data.Word (Word32)+import Moonlight.Triangulation.BulkLoad (delaunay, insert)+import Moonlight.Triangulation.Dcel (numUndirectedEdges, numVertices, undirectedEndpoints, vertexPoint)+import Moonlight.Triangulation.Handles.HandleDefs (UndirectedEdgeId (..), VertexId (..))+import Moonlight.Triangulation.Interpolation (nearestNeighbor)+import Moonlight.Triangulation.Math (canonicalPoint, validatePoint)+import Moonlight.Triangulation.Removal (RemovalOutcome, removalTriangulation, removeVertex)+import Moonlight.Triangulation.Session (removeManyAtNear, withSession)+import Moonlight.Triangulation.Types+import GHC.Generics (Generic)++-- | Most recently admitted vertex, suitable as the next descent seed.+newtype LastUsedHint = LastUsedHint (Maybe VertexId)+ deriving stock (Show)+ deriving newtype (Eq, Ord)++-- | A last-used hint with no remembered vertex.+emptyLastUsedHint :: LastUsedHint+emptyLastUsedHint = LastUsedHint Nothing++-- | Project a remembered vertex into a location hint.+lastUsedHint :: LastUsedHint -> Maybe LocationHint+lastUsedHint (LastUsedHint vertex) = VertexHint <$> vertex++-- | Replace the remembered vertex.+rememberVertex :: VertexId -> LastUsedHint -> LastUsedHint+rememberVertex vertex _ = LastUsedHint (Just vertex)++-- | A Delaunay hierarchy for logarithmic expected random point location.+-- The first vector element is the finest sparse level; the final element is+-- the coarsest. The base triangulation is not duplicated.+--+-- The levels are nested by a single arithmetic law rather than by a stored+-- correspondence. Level @i@ holds every @branch^(i+1)@-th base vertex in base+-- order, so a level-local handle @j@ names handle @j * branch@ one level finer+-- — and at level 0 the finer level is the base mesh itself, under the very+-- same multiplication. Descent therefore never rediscovers a handle it has+-- already computed, and no level carries an index vector.+--+-- 'hierarchyBaseCount' is the base cardinality the levels were sampled from.+-- It is the hierarchy's claim about which triangulation it answers for, and+-- every maintenance entry is stated against it: a base whose cardinality is not+-- the one recorded here plus the movement the operation performs is not the+-- base this hierarchy describes, and is rebuilt for rather than patched.+data HierarchyHint = HierarchyHint+ { hierarchyBranchFactor :: {-# UNPACK #-} !Int+ -- ^ Sampling stride between adjacent hierarchy levels.+ , hierarchyBaseCount :: {-# UNPACK #-} !Int+ -- ^ Cardinality of the base mesh described by the hierarchy.+ , hierarchyLevels :: !(V.Vector (Triangulation 'Unconstrained (Point) () () ()))+ }+ deriving stock (Generic)+ deriving anyclass (NFData)++instance Show (HierarchyHint) where+ showsPrec precedence hierarchy =+ showParen (precedence > 10) $+ showString "HierarchyHint "+ . shows (hierarchyBranchFactor hierarchy)+ . showString " "+ . shows (hierarchyBaseCount hierarchy)+ . showString " "+ . shows (hierarchyLevelCount hierarchy)+ . showString " "+ . shows (hierarchyVertexCount hierarchy)++instance Eq (HierarchyHint) where+ left == right =+ hierarchyBranchFactor left == hierarchyBranchFactor right+ && hierarchyBaseCount left == hierarchyBaseCount right+ && hierarchyLevelCount left == hierarchyLevelCount right+ && V.and (V.zipWith sameSparseMesh (hierarchyLevels left) (hierarchyLevels right))++-- Two sparse levels are the same hint when they carry the same points and the+-- same undirected edges. Half-edge index labelling records the order+-- construction happened to visit, so a level rebuilt from scratch and a level+-- extended in place are structurally equal while their arrays are not.+sameSparseMesh+ :: Triangulation 'Unconstrained (Point) () () ()+ -> Triangulation 'Unconstrained (Point) () () ()+ -> Bool+sameSparseMesh left right =+ numVertices left == numVertices right+ && numUndirectedEdges left == numUndirectedEdges right+ && meshPoints left == meshPoints right+ && meshEdges left == meshEdges right+ where+ meshPoints+ :: Triangulation mode vertex directed undirected face+ -> [Point]+ meshPoints triangulation =+ sort [vertexPoint triangulation (VertexId (fromIntegral index)) | index <- [0 .. numVertices triangulation - 1]]++ meshEdges+ :: Triangulation mode vertex directed undirected face+ -> [(Point, Point)]+ meshEdges triangulation =+ sort+ [ if from <= to then (from, to) else (to, from)+ | index <- [0 .. numUndirectedEdges triangulation - 1]+ , let (fromVertex, toVertex) = undirectedEndpoints triangulation (UndirectedEdgeId (fromIntegral index))+ from = vertexPoint triangulation fromVertex+ to = vertexPoint triangulation toVertex+ ]++-- | Default sampling stride between hierarchy levels.+defaultHierarchyBranchFactor :: Int+defaultHierarchyBranchFactor = 16++-- | Number of stored sparse levels.+hierarchyLevelCount :: HierarchyHint -> Int+hierarchyLevelCount = V.length . hierarchyLevels++-- | Total vertices retained across every sparse level.+hierarchyVertexCount :: HierarchyHint -> Int+hierarchyVertexCount =+ V.foldl' (\total level -> total + numVertices level) 0 . hierarchyLevels++-- | Build nested sparse Delaunay levels. A branch factor of 16 mirrors Spade's+-- default and gives O(log n) expected descent on uniformly distributed input.+--+-- Each level is bulk loaded from the sampled points in base order. Base+-- vertices carry pairwise distinct positions, so the load deduplicates nothing+-- and assigns local handle @j@ to sample @j@ — which is what makes the nesting+-- law on 'hierarchyLevels' an identity rather than a lookup.+buildHierarchyHint+ :: Int+ -> Triangulation mode vertex directed undirected face+ -> Either BuildError (HierarchyHint)+buildHierarchyHint requestedBranch triangulation =+ HierarchyHint branch count <$> V.mapM buildLevel levelDivisors+ where+ !branch = max 2 requestedBranch+ !count = numVertices triangulation+ levelDivisors =+ V.unfoldr+ (\candidate ->+ case candidate of+ Nothing -> Nothing+ Just divisor ->+ let !population = samplePopulation count divisor+ next =+ if population <= 1+ then Nothing+ else Just (safeMultiply divisor branch)+ in Just (divisor, next)+ )+ (if count <= 0 then Nothing else Just branch)++ buildLevel divisor =+ buildTriangulation+ <$> delaunay+ unitElementDefaults+ ( V.generate+ (samplePopulation count divisor)+ (\index ->+ vertexPoint+ triangulation+ (VertexId (fromIntegral (index * divisor)))+ )+ )++-- | Descend from the coarsest sparse triangulation. The handle a level returns+-- is carried to the next finer level by one multiplication, and the same+-- multiplication at level 0 names the base vertex. Nothing is relocated: the+-- coarse answer is not searched for again, it is computed.+hierarchyHint :: HierarchyHint -> QueryPoint -> Maybe LocationHint+hierarchyHint HierarchyHint{hierarchyBranchFactor, hierarchyLevels} query =+ VertexHint <$> descend (V.length hierarchyLevels - 1) Nothing+ where+ !branch = fromIntegral hierarchyBranchFactor :: Word32++ descend !levelIndex !coarse+ | levelIndex < 0 = Nothing+ | otherwise =+ case nearestNeighbor (hierarchyLevels V.! levelIndex) coarse query of+ Nothing -> Nothing+ Just (VertexId local, _) ->+ let !finer = VertexId (local * branch)+ in if levelIndex == 0 then Just finer else descend (levelIndex - 1) (Just finer)+++-- | Update the nested hierarchy from an insertion's own report: the point the+-- insertion was asked for, the handle it answered, and whether it created a+-- site. Only the levels selected by the branch divisibility rule are changed.+-- Unaffected levels are structurally shared.+--+-- No triangulation is named. The hierarchy walks its own levels and nothing+-- else, so a base was only ever a lookup table for three facts — its+-- cardinality, the stored position of the new vertex, and the position of+-- vertex zero — and every one of them is in the report or already in the+-- levels, because level-local handle zero is base handle zero at every level.+-- A caller maintaining the hierarchy across a run of insertions therefore+-- never has to publish a mesh to be allowed to speak to it, which is the whole+-- cost of the arrangement this replaces: one full arena copy per step, paid+-- only to name the thing that was just edited.+--+-- An insertion that found its point already present created no site, and a+-- hierarchy valid for a triangulation is valid for that same triangulation, so+-- the answer is the argument, unexamined.+updateHierarchyAfterInsertion+ :: HierarchyHint+ -> Point+ -> VertexId+ -> InsertionDisposition+ -> Either BuildError (HierarchyHint)+updateHierarchyAfterInsertion hierarchy requested vertex disposition =+ case disposition of+ AlreadyPresent -> Right hierarchy+ Inserted+ | vertexIndex vertex /= baseIndex ->+ Left+ ( HierarchyInsertionHandleMismatch+ (VertexId (fromIntegral baseIndex))+ vertex+ )+ | otherwise -> do+ updated <- updateLevels branch (V.toList (hierarchyLevels hierarchy))+ topped <- ensureSingletonTop updated+ pure+ hierarchy+ { hierarchyBaseCount = baseIndex + 1+ , hierarchyLevels = V.fromList topped+ }+ where+ !branch = hierarchyBranchFactor hierarchy+ !baseIndex = hierarchyBaseCount hierarchy+ -- A level holds what the mesh stores, not what the caller wrote: storage+ -- rounds a signed zero, and a level built from the unrounded point would+ -- settle a distance tie against a different handle.+ !point = canonicalPoint requested++ -- An update is the only operation that can break the nesting law, so the law+ -- is stated here as an equation rather than trusted: a level admitted to the+ -- new base vertex must already hold exactly @baseIndex / divisor@ samples,+ -- and must receive the vertex at its end.+ updateLevels !_ [] = Right []+ updateLevels !divisor levels@(level : rest)+ | baseIndex `rem` divisor /= 0 = Right levels+ | safeMultiply (numVertices level) divisor /= baseIndex =+ Left+ ( HierarchyLevelPopulationMismatch+ divisor+ (numVertices level)+ baseIndex+ )+ | otherwise = do+ inserted <- insert level point+ if vertexIndex (insertionVertex inserted) /= numVertices level+ then+ Left+ ( HierarchyInsertionHandleMismatch+ (VertexId (fromIntegral (numVertices level)))+ (insertionVertex inserted)+ )+ else (insertionTriangulation inserted :) <$> updateLevels (safeMultiply divisor branch) rest++ -- With no levels at all the base was empty, so the vertex just appended is+ -- vertex zero. Otherwise vertex zero is the finest level's local vertex+ -- zero, under the same law that makes the descent a multiplication.+ ensureSingletonTop [] = pure <$> singletonLevel point+ ensureSingletonTop levels@(finest : _) =+ case reverse levels of+ top : _+ | numVertices top <= 1 -> Right levels+ | otherwise ->+ (\first -> levels ++ [first])+ <$> singletonLevel (vertexPoint finest (VertexId 0))+ [] -> Right levels++-- | Repair the nested hierarchy from a removal's swap report — the slot+-- compaction freed and the position of the vertex it moved into that slot, or+-- 'Nothing' when the removal took the last vertex and compaction moved+-- nothing — rather than rebuilding because removal renumbers.+--+-- No triangulation is named, for the reason 'updateHierarchyAfterInsertion'+-- gives: the only base position this repair cannot find in its own levels is+-- the relocated vertex's, and that is what the report carries.+--+-- Swap compaction moves exactly one vertex — the former last one, into the slot+-- the removed vertex vacated — so a level's sample sequence changes in at most+-- one place, and which place is decided by the two divisibilities the level's+-- divisor gives the freed slot and the vacated last index:+--+-- * neither is sampled: the level, and every coarser level above it, is+-- untouched, because a divisor that divides neither index is divided by no+-- multiple of itself either;+-- * the vacated index is sampled and the freed slot is not: the level loses its+-- last sample and nothing else;+-- * both are sampled: the level loses its last sample and that sample's point+-- lands in the freed slot's local position — which is the level's own swap+-- removal, mirroring the base's;+-- * the freed slot is sampled and the vacated index is not: the level keeps its+-- population and substitutes the relocated position at an interior local+-- slot. No removal expresses a substitution, so that level is rebuilt — from+-- its own points and the reported one, never from a mesh.+--+-- The last case is the only one that pays a build, and it is the rarest: it+-- needs the freed slot to be sampled and the vacated index not to be.+updateHierarchyAfterRemoval+ :: HierarchyHint+ -> Maybe (VertexId, Point)+ -> Either BuildError (HierarchyHint)+updateHierarchyAfterRemoval hierarchy swap+ | baseCount <= 0 = Left (RemovalEmptyTriangulation (maybe (VertexId 0) fst swap))+ | otherwise = do+ repaired <- repairLevels branch (V.toList (hierarchyLevels hierarchy))+ pure+ hierarchy+ { hierarchyBaseCount = surviving+ , hierarchyLevels = V.fromList (levelsThroughSingleton repaired)+ }+ where+ !branch = hierarchyBranchFactor hierarchy+ !baseCount = hierarchyBaseCount hierarchy+ !surviving = baseCount - 1+ -- The freed slot is the removed vertex's own index, and it is where the+ -- former last vertex now stands. A removal that took the last vertex frees+ -- no slot and reports none, and its removed index is that last index.+ !freedSlot = maybe surviving (vertexIndex . fst) swap+ !vacatedIndex = surviving++ -- The nesting law is stated here as an equation for the same reason the+ -- insertion path states it: a repair is the other operation that can break+ -- it. A level the removal reaches must hold exactly the samples the+ -- pre-removal base owed it.+ repairLevels !_ [] = Right []+ repairLevels !divisor levels@(level : rest)+ | not freedSampled && not vacatedSampled = Right levels+ | numVertices level /= population =+ Left+ ( HierarchyLevelPopulationMismatch+ divisor+ (numVertices level)+ baseCount+ )+ | otherwise =+ case swap of+ -- The two divisibilities differ only when the two indices do, so a+ -- level reaching the substitution has a relocation to substitute:+ -- the guard cannot hold while compaction moved nothing.+ Just (_, relocated)+ | freedSampled && not vacatedSampled -> do+ substituted <- substituteSample level (freedSlot `quot` divisor) relocated+ (substituted :) <$> repairLevels (safeMultiply divisor branch) rest+ _ -> do+ shrunk <- removeVertex level (VertexId (fromIntegral localSample))+ (removalTriangulation shrunk :) <$> repairLevels (safeMultiply divisor branch) rest+ where+ !freedSampled = freedSlot `rem` divisor == 0+ !vacatedSampled = vacatedIndex `rem` divisor == 0+ !population = samplePopulation baseCount divisor+ !localSample+ | freedSampled = freedSlot `quot` divisor+ | otherwise = population - 1++ -- A build stops at the first level holding one sample, and removal only+ -- shrinks populations, so the shape a rebuild would answer with is this list+ -- cut after its first singleton.+ levelsThroughSingleton+ :: [Triangulation mode vertex directed undirected face]+ -> [Triangulation mode vertex directed undirected face]+ levelsThroughSingleton [] = []+ levelsThroughSingleton (level : rest)+ | numVertices level <= 0 = []+ | numVertices level <= 1 = [level]+ | otherwise = level : levelsThroughSingleton rest++-- | The coarsest level a growing hierarchy needs: one sample, the base's+-- vertex zero.+singletonLevel+ :: Point+ -> Either BuildError (Triangulation 'Unconstrained (Point) () () ())+singletonLevel origin =+ buildTriangulation <$> delaunay unitElementDefaults (V.singleton origin)++-- | The level a substitution asks for: the same samples in the same local+-- order, one slot carrying the relocated position instead of the one that+-- left. Stated over the level's own points, so no mesh is consulted.+substituteSample+ :: Triangulation 'Unconstrained (Point) () () ()+ -> Int+ -> Point+ -> Either BuildError (Triangulation 'Unconstrained (Point) () () ())+substituteSample level localSlot relocated =+ buildTriangulation+ <$> delaunay+ unitElementDefaults+ ( V.generate+ (numVertices level)+ (\index ->+ if index == localSlot+ then relocated+ else vertexPoint level (VertexId (fromIntegral index))+ )+ )++-- | Rebuild a hierarchy after an operation that may renumber vertices, such as+-- removal. The branch factor remains canonical.+rebuildHierarchyHint+ :: HierarchyHint+ -> Triangulation mode vertex directed undirected face+ -> Either BuildError (HierarchyHint)+rebuildHierarchyHint hierarchy = buildHierarchyHint (hierarchyBranchFactor hierarchy)++-- | Remove many points, each locate starting from the hierarchy's nearest+-- sample instead of the mesh boundary. One session publishes once; the+-- hierarchy is rebuilt against the surviving mesh and returned alongside it.+removeManyWithHierarchy+ :: HierarchyHint+ -> Triangulation mode vertex directed undirected face+ -> V.Vector (Point)+ -> Either+ BuildError+ ( V.Vector (Maybe (RemovalOutcome vertex))+ , Triangulation mode vertex directed undirected face+ , HierarchyHint+ )+removeManyWithHierarchy hierarchy triangulation points = do+ queryPoints <- traverse (validatePoint Nothing) points+ let guesses = fmap hierarchyGuess queryPoints+ (outcomes, surviving, _) <-+ withSession triangulation 0 (removeManyAtNear guesses points)+ repaired <- rebuildHierarchyHint hierarchy surviving+ pure (outcomes, surviving, repaired)+ where+ hierarchyGuess queryPoint =+ case hierarchyHint hierarchy queryPoint of+ Just (VertexHint vertex) -> Just vertex+ _ -> Nothing++safeMultiply :: Int -> Int -> Int+safeMultiply left right+ | left > maxBound `quot` right = maxBound+ | otherwise = left * right++-- | How many samples a divisor takes from a base of this size. Sampling takes+-- index zero and every @divisor@-th index after it; the direct+-- vector generator above states the same ceiling without constructing an+-- intermediate handle list.+samplePopulation :: Int -> Int -> Int+samplePopulation count divisor+ | count <= 0 = 0+ | otherwise = 1 + (count - 1) `quot` divisor++vertexIndex :: VertexId -> Int+vertexIndex (VertexId value) = fromIntegral value++-- The hierarchy is consulted once per query from another package, so its+-- polymorphic entries expose their unfoldings for the same reason the search+-- itself does.+--+-- The two level constructors are listed for a second reason, and the list is+-- not complete without them: they are overloaded and the entries call them, so+-- an entry specialised in the consumer that reaches an unspecialised+-- constructor threads the dictionary right back into the build it was+-- specialised to avoid. Only a stable unfolding is a specialisation candidate+-- across a package boundary; the optimised one GHC publishes on its own is not.+-- These two were 'where' bindings before they were named, and a 'where' binding+-- is specialised with the function that encloses it — so naming them is what+-- put the dictionary in, and this is what takes it back out.
test/algebra/Moonlight/Triangulation/MinkowskiSpec.hs view
@@ -1,12 +1,18 @@ -- | Exact convex convolution and residual-morphology laws. module Moonlight.Triangulation.MinkowskiSpec (tests) where +import Data.List.NonEmpty (NonEmpty (..)) import Moonlight.Triangulation.AlgebraFixtures ( annulusRegion , polygonRegion , rectangleComponent , rectangleRegion )+import Moonlight.Triangulation.Exact (ExactPoint, exactPoint)+import Moonlight.Triangulation.Internal.ExactRational+ ( ExactRational+ , exactRational+ ) import Moonlight.Triangulation.Minkowski ( MinkowskiOperation (..) , closeWith@@ -40,6 +46,7 @@ tests = do testConvexConvolution testGeneralAddition+ testOffsetClosure testConvexMorphology testGeneralErosion testHoledAndNeckedErosion@@ -111,6 +118,57 @@ if minkowskiOverlayPasses concaveReceipt > 0 then pure () else fail "nonconvex addition bypassed CDT decomposition and overlay union"++-- Dilation must be closed over its own exact published image. This fixture+-- used to admit the first offset and then refuse the second when rounded+-- resident wedges were mistaken for exact two-cells.+testOffsetClosure :: IO ()+testOffsetClosure = do+ source <-+ polygonRegion+ [ (-9, -1)+ , (-5, -6)+ , (0, -13)+ , (7, -9)+ , (9, 1)+ , (10, 10)+ , (5, 12)+ , (-7, 8)+ ]+ shoulder <-+ requireRight+ "unit octagon shoulder"+ (exactRational 1592262918131443 2251799813685248)+ kernel <-+ requireRight+ "unit octagon kernel"+ (convexPolygon (centeredOctagon 1 shoulder))+ element <- requireRight "unit octagon element" (structuringElement kernel)+ (firstOffset, _) <-+ requireRight "first closure-regression offset" (polygonOffset element source)+ (secondOffset, secondReceipt) <-+ requireRight+ "offset remains closed over its own published image"+ (polygonOffset element firstOffset)+ assertEqual+ "offset-of-offset remains one full-dimensional component"+ 1+ (length (planarRegionComponents secondOffset))+ if minkowskiOverlayPasses secondReceipt > 0+ then pure ()+ else fail "offset closure regression did not exercise overlay descent"++centeredOctagon :: ExactRational -> ExactRational -> NonEmpty ExactPoint+centeredOctagon radius shoulder =+ exactPoint radius 0+ :| [ exactPoint shoulder shoulder+ , exactPoint 0 radius+ , exactPoint (-shoulder) shoulder+ , exactPoint (-radius) 0+ , exactPoint (-shoulder) (-shoulder)+ , exactPoint 0 (-radius)+ , exactPoint shoulder (-shoulder)+ ] testConvexMorphology :: IO () testConvexMorphology = do
+ test/cell-complex/Main.hs view
@@ -0,0 +1,95 @@+module Main (main) where++import Data.Foldable (traverse_)+import Data.Vector qualified as Vector+import Moonlight.Triangulation.CellComplex (DCELComplex, fromExactCellSet)+import Moonlight.Homology.Pure.Topology.CellComplex+ ( CellComplex2D (..),+ CellTypes (..),+ OrientedEdge (..),+ ValidateComplex2D (..),+ eulerCharacteristic,+ isBoundaryEdge,+ )+import Moonlight.Triangulation.BulkLoad (delaunayGeometry)+import Moonlight.Triangulation.CellSet+ ( ExactCellSet,+ closeFaceCellSet,+ exactCellSetEdgeCount,+ exactCellSetFaceCount,+ exactCellSetVertexCount,+ )+import Moonlight.Triangulation.Handles.Iterators.FixedIterators (innerFaces)+import Moonlight.Triangulation.Types (Point (..))+import Test.Tasty (TestTree, defaultMain, testGroup)+import Test.Tasty.HUnit ((@?=), Assertion, assertBool, assertFailure, testCase)++main :: IO ()+main = defaultMain tests++tests :: TestTree+tests =+ testGroup+ "ExactCellSet bridge"+ [ testCase "preserves the admitted cell inventory" preserveCellInventory,+ testCase "preserves downward-closed incidence" preserveClosedIncidence,+ testCase "marks the triangular exterior as absent" preserveExteriorAdjacency+ ]++preserveCellInventory :: Assertion+preserveCellInventory =+ withTriangleComplex $ \cellSet complexValue -> do+ length (vertices complexValue) @?= exactCellSetVertexCount cellSet+ length (edges complexValue) @?= exactCellSetEdgeCount cellSet+ length (faces complexValue) @?= exactCellSetFaceCount cellSet+ (length (vertices complexValue), length (edges complexValue), length (faces complexValue))+ @?= (3, 3, 1)+ eulerCharacteristic complexValue @?= 1+ validateComplex complexValue @?= []++preserveClosedIncidence :: Assertion+preserveClosedIncidence =+ withTriangleComplex $ \_ complexValue -> do+ traverse_ (assertSelectedEdgeEndpoints complexValue) (edges complexValue)+ traverse_ (assertSelectedFaceBoundary complexValue) (faces complexValue)+ fmap (length . edgesAtVertex complexValue) (vertices complexValue) @?= [2, 2, 2]++preserveExteriorAdjacency :: Assertion+preserveExteriorAdjacency =+ withTriangleComplex $ \_ complexValue ->+ assertBool+ "every edge of a single selected triangle has one exterior incident face"+ (all (isBoundaryEdge complexValue) (edges complexValue))++assertSelectedEdgeEndpoints :: DCELComplex -> Edge DCELComplex -> Assertion+assertSelectedEdgeEndpoints complexValue edgeValue = do+ let selectedVertices = vertices complexValue+ (sourceVertex, targetVertex) = edgeBoundary complexValue edgeValue+ assertBool "edge source is selected" (sourceVertex `elem` selectedVertices)+ assertBool "edge target is selected" (targetVertex `elem` selectedVertices)++assertSelectedFaceBoundary :: DCELComplex -> Face DCELComplex -> Assertion+assertSelectedFaceBoundary complexValue faceValue = do+ let selectedEdges = edges complexValue+ boundary = faceBoundary complexValue faceValue+ length boundary @?= 3+ assertBool+ "every oriented boundary edge is selected"+ (all ((`elem` selectedEdges) . orientedEdge) boundary)++withTriangleComplex :: (ExactCellSet -> DCELComplex -> Assertion) -> Assertion+withTriangleComplex assertion =+ case delaunayGeometry trianglePoints of+ Left buildFailure -> assertFailure ("triangle construction failed: " <> show buildFailure)+ Right triangulation ->+ case closeFaceCellSet triangulation (innerFaces triangulation) of+ Left selectionFailure -> assertFailure ("triangle selection failed: " <> show selectionFailure)+ Right cellSet -> assertion cellSet (fromExactCellSet cellSet)++trianglePoints :: Vector.Vector Point+trianglePoints =+ Vector.fromList+ [ Point 0 0,+ Point 2 0,+ Point 0 2+ ]
test/ffi/Main.hs view
@@ -2,7 +2,9 @@ import Control.Exception (bracket) import Control.Monad (unless)+import Data.Int (Int64) import Data.Word (Word32)+import Foreign.C.String (peekCString) import Foreign.C.Types (CDouble (..), CSize (..), CUInt) import Foreign.Marshal.Alloc (alloca) import Foreign.Marshal.Array (allocaArray, peekArray, withArray)@@ -10,34 +12,49 @@ import Foreign.Storable (Storable (sizeOf), peek) import Moonlight.Triangulation.Foreign.ABI -type MeshPointer = Ptr ()+type MeshPointer = Ptr CMesh+type RegionPointer = Ptr CRegion+type Point = (Double, Double)+type Loop = [Point]+data ComponentInput = ComponentInput !Loop ![Loop] +data RegionMeasurement = RegionMeasurement+ { measuredEuler :: !Int64+ , measuredArea :: !String+ , measuredPerimeterLower :: !Double+ , measuredPerimeterUpper :: !Double+ }+ main :: IO () main = do- unless (sizeOf (undefined :: CObstruction) == 320) $+ unless (sizeOf obstructionLayoutWitness == 320) $ fail "C obstruction layout changed"+ unless (sizeOf receiptLayoutWitness == 72) $+ fail "C Minkowski receipt layout changed" bracket (buildMesh [(0, 0), (2, 0), (0, 2), (2, 2)]) meshFree $ \left -> bracket (buildMesh [(2, 0), (4, 0), (2, 2), (4, 2)]) meshFree $ \right -> do requireMeshCount "left vertex count" meshVertexCount left 4 requireMeshCount "left triangle count" meshTriangleCount left 2 testDenseCopies left testImmutableBatch left- testBinaryAlgebra left right+ testSiteSetAlgebra left right+ testExactRegionSurface testTypedCoordinateRefusal testCoordinateCountOverflow testNullPointerRefusal+ testRegionLayoutRefusal putStrLn "ffi: ok" -buildMesh :: [(Double, Double)] -> IO MeshPointer+obstructionLayoutWitness :: CObstruction+obstructionLayoutWitness = CObstruction 0 0 0 0 0 0 0 0 0 ""++receiptLayoutWitness :: CMinkowskiReceipt+receiptLayoutWitness = CMinkowskiReceipt 0 0 0 0 0 0 0 0 0++buildMesh :: [Point] -> IO MeshPointer buildMesh points = withPointArray points $ \coordinates ->- alloca $ \output ->- alloca $ \obstruction -> do- status <- delaunayF64 coordinates (fromIntegral (length points)) output obstruction- requireStatus "delaunay" 0 status obstruction- handle <- peek output- unless (handle /= nullPtr) (fail "delaunay returned a null handle")- pure handle+ produceHandle "delaunay" (delaunayF64 coordinates (fromIntegral (length points))) testDenseCopies :: MeshPointer -> IO () testDenseCopies mesh = do@@ -68,24 +85,195 @@ testImmutableBatch original = withPointArray [(1, 1), (3, 1)] $ \coordinates -> bracket- (produceMesh "batch insert" (meshInsertManyF64 original coordinates 2))+ (produceHandle "batch insert" (meshInsertManyF64 original coordinates 2)) meshFree (\revised -> do requireMeshCount "original after batch" meshVertexCount original 4- requireMeshCount "revised after batch" meshVertexCount revised 6- )+ requireMeshCount "revised after batch" meshVertexCount revised 6) -testBinaryAlgebra :: MeshPointer -> MeshPointer -> IO ()-testBinaryAlgebra left right = do- test "union" meshUnion 6- test "intersection" meshIntersection 2- test "difference" meshDifference 2- test "symmetric difference" meshSymmetricDifference 4+testSiteSetAlgebra :: MeshPointer -> MeshPointer -> IO ()+testSiteSetAlgebra left right = do+ test "site union" meshSiteUnion 6+ test "site intersection" meshSiteIntersection 2+ test "site difference" meshSiteDifference 2+ test "site symmetric difference" meshSiteSymmetricDifference 4 where test label operation expected =- bracket (produceMesh label (operation left right)) meshFree $ \result ->+ bracket (produceHandle label (operation left right)) meshFree $ \result -> requireMeshCount label meshVertexCount result expected +testExactRegionSurface :: IO ()+testExactRegionSurface =+ bracket (buildRegion [ComponentInput leftSquare []]) regionFree $ \left ->+ bracket (buildRegion [ComponentInput rightSquare []]) regionFree $ \right -> do+ testRegionProjection left+ testRegionBooleans left right+ testRegionPointLocation left+ testRegionMorphology left right+ where+ leftSquare = [(0, 0), (2, 0), (2, 2), (0, 2)]+ rightSquare = [(1, 0), (3, 0), (3, 2), (1, 2)]++testRegionProjection :: RegionPointer -> IO ()+testRegionProjection region =+ alloca $ \componentCountOutput ->+ alloca $ \loopCountOutput ->+ alloca $ \pointCountOutput ->+ alloca $ \obstruction -> do+ status <- regionCounts region componentCountOutput loopCountOutput pointCountOutput obstruction+ requireStatus "region counts" 0 status obstruction+ componentCount <- fromIntegral <$> peek componentCountOutput+ loopCount <- fromIntegral <$> peek loopCountOutput+ pointCount <- fromIntegral <$> peek pointCountOutput+ unless ((componentCount, loopCount, pointCount) == (1, 1, 4)) $+ fail "region counts lost component/loop/point structure"+ allocaArray (pointCount * 2) $ \coordinates ->+ allocaArray (loopCount + 1) $ \loopOffsets ->+ allocaArray (componentCount + 1) $ \componentOffsets -> do+ copyStatus <-+ regionCopyF64+ region+ coordinates+ (fromIntegral pointCount)+ loopOffsets+ (fromIntegral (loopCount + 1))+ componentOffsets+ (fromIntegral (componentCount + 1))+ obstruction+ requireStatus "region copy" 0 copyStatus obstruction+ copiedLoopOffsets <- peekArray (loopCount + 1) loopOffsets+ copiedComponentOffsets <- peekArray (componentCount + 1) componentOffsets+ unless (copiedLoopOffsets == [0, 4] && copiedComponentOffsets == [0, 1]) $+ fail "region copy changed the bulk offset topology"++testRegionBooleans :: RegionPointer -> RegionPointer -> IO ()+testRegionBooleans left right = do+ test "region union" regionUnion 1 "6/1" 10+ test "region intersection" regionIntersection 1 "2/1" 6+ test "region difference" regionDifference 1 "2/1" 6+ test "region symmetric difference" regionSymmetricDifference 2 "4/1" 12+ where+ test label operation expectedEuler expectedArea expectedPerimeter =+ bracket (produceHandle label (operation left right)) regionFree $ \result -> do+ measurement <- measureRegion result+ unless (measuredEuler measurement == expectedEuler && measuredArea measurement == expectedArea) $+ fail (label <> " returned the wrong exact valuation")+ requireCertifiedContainment+ label+ expectedPerimeter+ (measuredPerimeterLower measurement)+ (measuredPerimeterUpper measurement)++testRegionPointLocation :: RegionPointer -> IO ()+testRegionPointLocation region = do+ requireRegionLocation "interior location" region (1, 1) 2+ requireRegionLocation "boundary location" region (0, 1) 1+ requireRegionLocation "exterior location" region (3, 1) 0++testRegionMorphology :: RegionPointer -> RegionPointer -> IO ()+testRegionMorphology left right =+ withPointArray [(-0.5, -0.5), (0.5, -0.5), (0.5, 0.5), (-0.5, 0.5)] $ \coordinates ->+ bracket+ (produceHandle "structuring element" (structuringElementCreateF64 coordinates 4))+ structuringElementFree+ (\element -> do+ test "Minkowski sum" regionMinkowskiSum left right 0 "16/1"+ test "offset" regionOffset element left 0 "9/1"+ test "inset" regionInset element left 1 "1/1"+ test "open" regionOpen element left 2 "4/1"+ test "close" regionClose element left 3 "4/1")+ where+ test+ :: String+ -> (first -> RegionPointer -> Ptr RegionPointer -> Ptr CMinkowskiReceipt -> Ptr CObstruction -> IO CUInt)+ -> first+ -> RegionPointer+ -> Word32+ -> String+ -> IO ()+ test label operation firstInput secondInput expectedOperation expectedArea =+ bracket+ (produceRegionWithReceipt label (operation firstInput secondInput))+ (regionFree . fst)+ (\(result, receipt) -> do+ measurement <- measureRegion result+ unless (receiptOperation receipt == expectedOperation && measuredArea measurement == expectedArea) $+ fail (label <> " lost its result or morphology receipt"))++measureRegion :: RegionPointer -> IO RegionMeasurement+measureRegion region =+ alloca $ \eulerOutput ->+ allocaArray 64 $ \areaOutput ->+ alloca $ \areaBytesWritten ->+ alloca $ \perimeterLowerOutput ->+ alloca $ \perimeterUpperOutput ->+ alloca $ \obstruction -> do+ status <-+ regionMeasure+ region+ eulerOutput+ areaOutput+ 64+ areaBytesWritten+ perimeterLowerOutput+ perimeterUpperOutput+ obstruction+ requireStatus "region measure" 0 status obstruction+ measuredEuler <- peek eulerOutput+ measuredArea <- peekCString areaOutput+ CDouble measuredPerimeterLower <- peek perimeterLowerOutput+ CDouble measuredPerimeterUpper <- peek perimeterUpperOutput+ pure (RegionMeasurement measuredEuler measuredArea measuredPerimeterLower measuredPerimeterUpper)++requireCertifiedContainment :: String -> Double -> Double -> Double -> IO ()+requireCertifiedContainment label expected lower upper =+ unless (lower <= expected && expected <= upper) $+ fail (label <> " perimeter certificate excludes the exact perimeter")++requireRegionLocation :: String -> RegionPointer -> Point -> CUInt -> IO ()+requireRegionLocation label region (x, y) expected =+ alloca $ \output ->+ alloca $ \obstruction -> do+ status <- regionLocatePointF64 region (CDouble x) (CDouble y) output obstruction+ requireStatus label 0 status obstruction+ observed <- peek output+ unless (observed == expected) $+ fail (label <> " returned location " <> show observed)++buildRegion :: [ComponentInput] -> IO RegionPointer+buildRegion components =+ let loopsByComponent = map (\(ComponentInput outer holes) -> outer : holes) components+ loops = concat loopsByComponent+ points = concat loops+ loopPointCounts = map length loops+ componentLoopCounts = map length loopsByComponent+ in withPointArray points $ \coordinates ->+ withArray (map fromIntegral loopPointCounts) $ \loopCounts ->+ withArray (map fromIntegral componentLoopCounts) $ \componentCounts ->+ produceHandle+ "region create"+ ( regionCreateF64+ coordinates+ (fromIntegral (length points))+ loopCounts+ (fromIntegral (length loops))+ componentCounts+ (fromIntegral (length components))+ )++testRegionLayoutRefusal :: IO ()+testRegionLayoutRefusal =+ withPointArray [(0, 0), (1, 0), (1, 1), (0, 1)] $ \coordinates ->+ withArray [5] $ \loopCounts ->+ withArray [1] $ \componentCounts ->+ alloca $ \output ->+ alloca $ \obstruction -> do+ status <- regionCreateF64 coordinates 4 loopCounts 1 componentCounts 1 output obstruction+ requireStatus "invalid region layout" 4 status obstruction+ refusal <- peek obstruction+ unless (obstructionCode refusal == 200) $+ fail "invalid region layout lost its typed obstruction"+ testTypedCoordinateRefusal :: IO () testTypedCoordinateRefusal = withPointArray [(0, 0), (0 / 0, 1), (1, 0)] $ \coordinates ->@@ -128,8 +316,8 @@ unless (observed == fromIntegral expected) $ fail (label <> " produced " <> show observed <> ", expected " <> show expected) -produceMesh :: String -> (Ptr MeshPointer -> Ptr CObstruction -> IO CUInt) -> IO MeshPointer-produceMesh label operation =+produceHandle :: String -> (Ptr (Ptr carrier) -> Ptr CObstruction -> IO CUInt) -> IO (Ptr carrier)+produceHandle label operation = alloca $ \output -> alloca $ \obstruction -> do status <- operation output obstruction@@ -138,6 +326,21 @@ unless (handle /= nullPtr) (fail (label <> " returned a null handle")) pure handle +produceRegionWithReceipt+ :: String+ -> (Ptr RegionPointer -> Ptr CMinkowskiReceipt -> Ptr CObstruction -> IO CUInt)+ -> IO (RegionPointer, CMinkowskiReceipt)+produceRegionWithReceipt label operation =+ alloca $ \output ->+ alloca $ \receiptOutput ->+ alloca $ \obstruction -> do+ status <- operation output receiptOutput obstruction+ requireStatus label 0 status obstruction+ handle <- peek output+ receipt <- peek receiptOutput+ unless (handle /= nullPtr) (fail (label <> " returned a null region"))+ pure (handle, receipt)+ requireStatus :: String -> CUInt -> CUInt -> Ptr CObstruction -> IO () requireStatus label expected observed obstruction | observed == expected = pure ()@@ -145,5 +348,5 @@ refusal <- peek obstruction fail (label <> " returned status " <> show observed <> ": " <> obstructionMessage refusal) -withPointArray :: [(Double, Double)] -> (Ptr CDouble -> IO result) -> IO result+withPointArray :: [Point] -> (Ptr CDouble -> IO result) -> IO result withPointArray points = withArray (concatMap (\(x, y) -> [CDouble x, CDouble y]) points)
test/native/Moonlight/Triangulation/ExactEmbeddingSpec.hs view
@@ -38,10 +38,10 @@ ) import Moonlight.Triangulation.Internal.Overlay.Embedding ( DraftIncidence (..)+ , DraftId (..) , DraftNeighborhood (..)- , DraftSegmentId (..)- , DraftSourceId (..)- , DraftVertexId (..)+ , DraftSegmentId+ , DraftVertexId , EmbeddingObligation (..) , ExactArrangementDraft (..) , LocalEmbeddingCertificate (..)@@ -75,6 +75,7 @@ , testExactArithmeticReceipt , testEmbeddingAdmissionReceipt , testOrdinaryDraftCertificate+ , testCollinearNeighborhoodRotation , testFrozenBinary64RelationOracle ] @@ -124,9 +125,9 @@ emptyDraft { draftVertices = Map.fromList- [ (DraftVertexId 0, firstCrossing)- , (DraftVertexId 1, secondCrossing)- , (DraftVertexId 2, thirdCrossing)+ [ (DraftId 0, firstCrossing)+ , (DraftId 1, secondCrossing)+ , (DraftId 2, thirdCrossing) ] } assertLeftContains@@ -150,21 +151,22 @@ outerTo = exactPoint zero upper innerFrom = exactPoint zero innerLower innerTo = exactPoint zero innerUpper- outerSegmentId = DraftSegmentId 0- innerSegmentId = DraftSegmentId 1+ outerSegmentId, innerSegmentId :: DraftSegmentId+ outerSegmentId = DraftId 0+ innerSegmentId = DraftId 1 draft = emptyDraft { draftVertices = Map.fromList- [ (DraftVertexId 0, outerFrom)- , (DraftVertexId 1, outerTo)- , (DraftVertexId 2, innerFrom)- , (DraftVertexId 3, innerTo)+ [ (DraftId 0, outerFrom)+ , (DraftId 1, outerTo)+ , (DraftId 2, innerFrom)+ , (DraftId 3, innerTo) ] , draftSegments = Map.fromList- [ (outerSegmentId, (DraftVertexId 0, DraftVertexId 1))- , (innerSegmentId, (DraftVertexId 2, DraftVertexId 3))+ [ (outerSegmentId, (DraftId 0, DraftId 1))+ , (innerSegmentId, (DraftId 2, DraftId 3)) ] , draftIncidences = [ DraftIncidence@@ -219,7 +221,7 @@ unprojectableDraft = emptyDraft { draftVertices =- Map.singleton (DraftVertexId 0) unprojectablePoint+ Map.singleton (DraftId 0) unprojectablePoint } assertLeftContains "unprojectable exact draft vertex"@@ -248,15 +250,17 @@ testOrdinaryDraftCertificate :: IO () testOrdinaryDraftCertificate = do half <- requireRight "ordinary draft half parameter" (exactRational 1 2)- let west = DraftVertexId 0- center = DraftVertexId 1- east = DraftVertexId 2- south = DraftVertexId 3- north = DraftVertexId 4- westCenter = DraftSegmentId 0- centerEast = DraftSegmentId 1- southCenter = DraftSegmentId 2- centerNorth = DraftSegmentId 3+ let west, center, east, south, north :: DraftVertexId+ west = DraftId 0+ center = DraftId 1+ east = DraftId 2+ south = DraftId 3+ north = DraftId 4+ westCenter, centerEast, southCenter, centerNorth :: DraftSegmentId+ westCenter = DraftId 0+ centerEast = DraftId 1+ southCenter = DraftId 2+ centerNorth = DraftId 3 draft = ExactArrangementDraft { draftVertices =@@ -276,13 +280,13 @@ ] , draftSourceMemberships = Map.fromList- [ ( DraftSourceId 0+ [ ( DraftId 0 , [ (0, west) , (half, center) , (1, east) ] )- , ( DraftSourceId 1+ , ( DraftId 1 , [ (0, south) , (half, center) , (1, north)@@ -312,7 +316,7 @@ assertEqual "ordinary neighborhood obligations" 4- (certificateNeighborhoodOrientationStabilityCount certificate)+ (certificateNeighborhoodRotationPreservationCount certificate) assertEqual "ordinary residual" (GlobalNoNewCrossing :| [])@@ -323,7 +327,7 @@ ( certificateRoundedVertexDistinctnessCount certificate , certificateSplitOrderPreservationCount certificate , certificateIncidenceRelationPreservationCount certificate- , certificateNeighborhoodOrientationStabilityCount certificate+ , certificateNeighborhoodRotationPreservationCount certificate ) ) putStrLn@@ -331,6 +335,54 @@ <> show (residualUndischargedObligations (certificateResidual certificate)) ) +-- Independent binary64 projection bends the exact straight-through pair at+-- the center, but the cyclic neighbor order is unchanged. The local topology+-- obligation is rotation preservation, not literal preservation of a zero+-- determinant.+testCollinearNeighborhoodRotation :: IO ()+testCollinearNeighborhoodRotation = do+ oneThird <- requireRight "collinear rotation coordinate" (exactRational 1 3)+ let center, right, branch, left :: DraftVertexId+ center = DraftId 0+ right = DraftId 1+ branch = DraftId 2+ left = DraftId 3+ centerRight, centerBranch, centerLeft :: DraftSegmentId+ centerRight = DraftId 0+ centerBranch = DraftId 1+ centerLeft = DraftId 2+ draft =+ emptyDraft+ { draftVertices =+ Map.fromList+ [ (center, exactPoint 1 oneThird)+ , (right, integerPoint 3 1)+ , (branch, integerPoint 1 2)+ , (left, integerPoint 0 0)+ ]+ , draftSegments =+ Map.fromList+ [ (centerRight, (center, right))+ , (centerBranch, (center, branch))+ , (centerLeft, (center, left))+ ]+ , draftIncidences =+ [ DraftIncidence centerRight centerBranch SegmentsShareEndpoint+ , DraftIncidence centerBranch centerLeft SegmentsShareEndpoint+ , DraftIncidence centerLeft centerRight SegmentsShareEndpoint+ ]+ , draftNeighborhoods =+ [DraftNeighborhood center (right :| [branch, left])]+ }+ certificate <-+ requireRight+ "collinear neighborhood retains its cyclic rotation"+ (certifyLocalEmbedding draft)+ assertEqual+ "collinear neighborhood rotation obligations"+ 3+ (certificateNeighborhoodRotationPreservationCount certificate)+ testExactArithmeticReceipt :: IO () testExactArithmeticReceipt = do enabled <- getRTSStatsEnabled@@ -386,23 +438,23 @@ emptyDraft { draftVertices = Map.fromList- [ (DraftVertexId 0, integerPoint 0 0)- , (DraftVertexId 1, integerPoint 1 0)+ [ (DraftId 0, integerPoint 0 0)+ , (DraftId 1, integerPoint 1 0) ] } collisionDraft = emptyDraft { draftVertices = Map.fromList- [ (DraftVertexId 0, exactPoint firstCollision 0)- , (DraftVertexId 1, exactPoint secondCollision 0)+ [ (DraftId 0, exactPoint firstCollision 0)+ , (DraftId 1, exactPoint secondCollision 0) ] } projectionDraft = emptyDraft { draftVertices = Map.singleton- (DraftVertexId 0)+ (DraftId 0) (exactPoint (fromInteger (10 ^ (400 :: Int))) 0) } outcomes =
test/native/Moonlight/Triangulation/NativeSpec.hs view
@@ -36,7 +36,8 @@ import Moonlight.Triangulation.Dcel import qualified Moonlight.Triangulation.Dcel as Dcel import Moonlight.Triangulation.FloodFillIterator-import Moonlight.Triangulation.Handles+import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Handles.Iterators.FixedIterators import Moonlight.Triangulation.HintGenerator import Moonlight.Triangulation.Interpolation import Moonlight.Triangulation.IntersectionIterator@@ -1679,7 +1680,7 @@ ) split <- requireRight "constraint split" (addConstraintAndSplit id cdt1 (VertexId 1) (VertexId 3))- let splitCdt = constraintTriangulation split+ let splitCdt = constraintRecoveryTriangulation split unless (numVertices splitCdt > numVertices cdt1) $ fail "constraint split did not insert an intersection vertex" assertCdtValid "constraint split" splitCdt @@ -1704,9 +1705,10 @@ requireRight "split band base" (constrainedDelaunayMaximal unitElementDefaults bandVertices V.empty)- let bandMapping = cdtBuildInputVertices bandBuild+ let bandAcceptedBuild = cdtAcceptedBuild bandBuild+ bandMapping = buildInputVertices bandAcceptedBuild bandHandle input = VertexId (indexPrimArray bandMapping input)- bandBase = cdtBuildTriangulation bandBuild+ bandBase = buildTriangulation bandAcceptedBuild bandRequests = V.fromList ( (bandHandle 0, bandHandle 1)@@ -1719,7 +1721,7 @@ -> (VertexId, VertexId) -> Either (CdtError) (ConstrainedDelaunayTriangulation (Point)) replaySplit triangulation request =- constraintTriangulation+ constraintRecoveryTriangulation <$> uncurry (addConstraintAndSplit id triangulation) request bandBatch <- requireRight@@ -1732,12 +1734,12 @@ assertEqual "split batch topology equals singleton descent" bandDescent- (splitBatchTriangulation bandBatch)+ (constraintRecoveryTriangulation bandBatch) assertEqual "split batch added one vertex per crossing" (numVertices bandBase + length bandColumns)- (numVertices (splitBatchTriangulation bandBatch))- assertCdtValid "split batch" (splitBatchTriangulation bandBatch)+ (numVertices (constraintRecoveryTriangulation bandBatch))+ assertCdtValid "split batch" (constraintRecoveryTriangulation bandBatch) -- The same law with the reservation exhausting inside one corridor rather -- than between corridors: the closing horizontal crosses two constraints@@ -1760,9 +1762,10 @@ requireRight "split lace base" (constrainedDelaunayMaximal unitElementDefaults laceVertices laceBuiltIn)- let laceMapping = cdtBuildInputVertices laceBuild+ let laceAcceptedBuild = cdtAcceptedBuild laceBuild+ laceMapping = buildInputVertices laceAcceptedBuild laceHandle input = VertexId (indexPrimArray laceMapping input)- laceBase = cdtBuildTriangulation laceBuild+ laceBase = buildTriangulation laceAcceptedBuild laceRequests = V.fromList ( [ (laceHandle (2 * column + 2), laceHandle (2 * column + 3))@@ -1781,22 +1784,23 @@ assertEqual "split lace batch topology equals singleton descent" laceDescent- (splitBatchTriangulation laceBatch)+ (constraintRecoveryTriangulation laceBatch) assertEqual "split lace batch added one vertex per crossing" (numVertices laceBase + length laceColumns)- (numVertices (splitBatchTriangulation laceBatch))- assertCdtValid "split lace batch" (splitBatchTriangulation laceBatch)+ (numVertices (constraintRecoveryTriangulation laceBatch))+ assertCdtValid "split lace batch" (constraintRecoveryTriangulation laceBatch) let verticesInput :: V.Vector (Point) verticesInput = V.fromList [Point 0 0, Point 4 0, Point 4 4, Point 0 4, Point 0 0] constraintsInput = V.fromList [(0, 2), (1, 3), (4, 1)] bulk <- requireRight "stable CDT bulk load" (constrainedDelaunayMaximal unitElementDefaults verticesInput constraintsInput)- let bulkMapping = cdtBuildInputVertices bulk- unless (sizeofPrimArray bulkMapping > 4) $ fail "cdtBuildInputVertices out of bounds"+ let bulkAcceptedBuild = cdtAcceptedBuild bulk+ bulkMapping = buildInputVertices bulkAcceptedBuild+ unless (sizeofPrimArray bulkMapping > 4) $ fail "buildInputVertices out of bounds" assertEqual "stable duplicate reroute" (VertexId (indexPrimArray bulkMapping 0)) (VertexId (indexPrimArray bulkMapping 4)) assertEqual "conflict reporting" 1 (V.length (cdtRejectedConstraints bulk))- assertCdtValid "stable CDT bulk load" (cdtBuildTriangulation bulk)+ assertCdtValid "stable CDT bulk load" (buildTriangulation bulkAcceptedBuild) testAnnotatedConstrainedUnion :: IO () testAnnotatedConstrainedUnion = do@@ -1954,8 +1958,9 @@ requireRight "asymmetric constrained extension" (extendConstrainedWith Set.union base extension)- let extended = constrainedExtensionTriangulation extendedResult- receipt = constrainedExtensionConstraintStats extendedResult+ let constraintBatch = constrainedExtensionConstraintBatch extendedResult+ extended = constraintBatchTriangulation constraintBatch+ receipt = constraintBatchStats constraintBatch buildReceipt = constrainedExtensionBuildStats extendedResult actualAnnotations = Map.fromList@@ -2001,7 +2006,7 @@ assertEqual "asymmetric extension returns one outcome for each incoming constraint" (V.length (constraintSegments extension))- (V.length (constrainedExtensionConstraintOutcomes extendedResult))+ (V.length (constraintBatchOutcomes constraintBatch)) assertEqual "asymmetric extension admits every incoming constraint" (V.length (constraintSegments extension))@@ -2109,7 +2114,8 @@ requireRight "large asymmetric constrained extension" (extendConstrainedWith Set.union base extension)- let extended = constrainedExtensionTriangulation extensionResult+ let constraintBatch = constrainedExtensionConstraintBatch extensionResult+ extended = constraintBatchTriangulation constraintBatch extendedConstraints = Set.fromList (V.toList (constraintSegments extended)) extendedVertexSnapshot = Map.fromList@@ -2147,7 +2153,7 @@ assertEqual "large asymmetric extension replays only the incoming constraints" (V.length extensionConstraints)- (constraintBatchRequests (constrainedExtensionConstraintStats extensionResult))+ (constraintBatchRequests (constraintBatchStats constraintBatch)) assertCdtValid "large asymmetric constrained extension" extended case extendConstrainedWith Set.union base conflicting of Left (ConstraintUnionConstructionFailed (ConstraintIntersection _)) -> pure ()@@ -2703,11 +2709,23 @@ "component provenance mismatch is typed before DCEL lookup" (Left (BoundaryComponentFaceOutOfRange (FaceId 2) 2)) (componentBoundary triangle squareComponent)+ assertEqual+ "component loop provenance mismatch is typed before DCEL lookup"+ (Left (BoundaryComponentFaceOutOfRange (FaceId 2) 2))+ (componentBoundaryLoops triangle squareComponent) squareBoundary <- requireRight "uniform square boundary" (componentBoundary square squareComponent)+ squareLoops <-+ requireRight+ "uniform square oriented boundary loops"+ (componentBoundaryLoops square squareComponent) repeatedSquareBoundary <- requireRight "repeated uniform square boundary" (componentBoundary square squareComponent) assertEqual "boundary extraction is deterministic" squareBoundary repeatedSquareBoundary assertEqual+ "ordinary oriented loops agree with strict boundary"+ (regionBoundaryOuterLoop squareBoundary :| regionBoundaryHoleLoops squareBoundary)+ squareLoops+ assertEqual "uniform square drops its Delaunay diagonal" (Set.fromList [Point 0 0, Point 2 0, Point 2 2, Point 0 2]) (loopPointSet square (regionBoundaryOuterLoop squareBoundary))@@ -2794,6 +2812,35 @@ "pinched selected component" True (faceComponents pinched pinchedSelection)+ pinchedLoops <-+ requireRight+ "pinched oriented boundary loops"+ (componentBoundaryLoops pinched pinchedComponent)+ let pinchedLoopList = toList pinchedLoops+ assertEqual "pinched simple loop count" 2 (length pinchedLoopList)+ assertEqual+ "pinched simple loop vertex counts"+ [4, 6]+ (sort (fmap (length . boundaryLoopVertices) pinchedLoopList))+ assertEqual+ "pinched loop orientations"+ [BoundaryCounterClockwise, BoundaryClockwise]+ (sort (fmap boundaryLoopOrientation pinchedLoopList))+ traverse_+ (\loop -> do+ let loopVertexIds = toList (boundaryLoopVertices loop)+ assertEqual+ "pinched loop has no repeated vertex"+ (length loopVertexIds)+ (Set.size (Set.fromList loopVertexIds))+ assertLoopWinding+ "pinched loop winding agrees with orientation"+ (case boundaryLoopOrientation loop of+ BoundaryCounterClockwise -> GT+ BoundaryClockwise -> LT)+ pinched+ loop)+ pinchedLoopList case componentBoundary pinched pinchedComponent of Left (BoundaryPinch vertex firstEdge secondEdge) -> do assertEqual "pinch vertex" (Point 1 1) (vertexPoint pinched vertex)@@ -3000,6 +3047,10 @@ -> RegionBoundary -> IO () assertBoundaryShape label triangulation outerVertexCount holeVertexCounts boundary = do+ assertEqual+ (label <> " outer orientation")+ BoundaryCounterClockwise+ (boundaryLoopOrientation (regionBoundaryOuterLoop boundary)) assertEqual (label <> " outer vertex count") outerVertexCount (length (boundaryLoopVertices (regionBoundaryOuterLoop boundary))) assertLoopWinding (label <> " outer winding") GT triangulation@@ -3008,6 +3059,10 @@ assertEqual (label <> " hole count") (length holeVertexCounts) (length holes) traverse_ (\(vertexCount, hole) -> do+ assertEqual+ (label <> " hole orientation")+ BoundaryClockwise+ (boundaryLoopOrientation hole) assertEqual (label <> " hole vertex count") vertexCount (length (boundaryLoopVertices hole)) assertLoopWinding (label <> " hole winding") LT triangulation hole)
weeder.toml view
@@ -13,16 +13,15 @@ '^Moonlight\.Triangulation\.Math$', '^Moonlight\.Triangulation\.Exact$', '^Moonlight\.Triangulation\.CellSet$',+ '^Moonlight\.Triangulation\.CellComplex$', '^Moonlight\.Triangulation\.Region$', '^Moonlight\.Triangulation\.Valuation$', '^Moonlight\.Triangulation\.Interop$', '^Moonlight\.Triangulation\.Dcel$', '^Moonlight\.Triangulation\.Payload$', '^Moonlight\.Triangulation\.JoinSemilattice$',- '^Moonlight\.Triangulation\.Handles$', '^Moonlight\.Triangulation\.Handles\.HandleDefs$', '^Moonlight\.Triangulation\.Handles\.Dynamic$',- '^Moonlight\.Triangulation\.Handles\.Iterators$', '^Moonlight\.Triangulation\.Handles\.Iterators\.CircularIterator$', '^Moonlight\.Triangulation\.Handles\.Iterators\.DynamicIterators$', '^Moonlight\.Triangulation\.Handles\.Iterators\.FixedIterators$',