packages feed

moonlight-triangulation 1.4.0.2 → 1.4.0.3

raw patch · 86 files changed

+3445/−2768 lines, 86 filesdep ~basedep ~moonlight-triangulationPVP: minor bump suggested

API additions: PVP suggests at least a minor version bump

Dependency ranges changed: base, moonlight-triangulation

API changes (from Hackage documentation)

+ Moonlight.Triangulation: AlreadyPresent :: InsertionDisposition
+ Moonlight.Triangulation: BuildStats :: Int -> Int -> Int -> Int -> Int -> Int -> Int -> Int -> Int -> Int -> Int -> Int -> Int -> Int -> Int -> Int -> Int -> Int -> Int -> Int -> Int -> Int -> BuildStats
+ Moonlight.Triangulation: CdtBuildResult :: BuildResult 'Constrained vertex directed undirected face -> Vector (Int, Int) -> CdtBuildResult vertex directed undirected face
+ Moonlight.Triangulation: ConstraintAccepted :: Vector DirectedEdgeId -> Int -> ConstraintOutcome
+ Moonlight.Triangulation: ConstraintBatchStats :: Int -> Int -> Int -> Int -> Int -> Int -> ConstraintBatchStats
+ Moonlight.Triangulation: ConstraintRecoveryResult :: Triangulation 'Constrained vertex directed undirected face -> pathReceipt -> Int -> ConstraintRecoveryResult pathReceipt vertex directed undirected face
+ Moonlight.Triangulation: ConstraintRejected :: UndirectedEdgeId -> ConstraintOutcome
+ Moonlight.Triangulation: EdgeIntersection :: DirectedEdgeId -> Intersection
+ Moonlight.Triangulation: EdgeOverlap :: DirectedEdgeId -> Intersection
+ Moonlight.Triangulation: EmptyTriangulation :: Location
+ Moonlight.Triangulation: ExactVector :: ExactRational -> ExactRational -> ExactVector
+ Moonlight.Triangulation: InFace :: FaceId -> Location
+ Moonlight.Triangulation: Inserted :: InsertionDisposition
+ Moonlight.Triangulation: InvalidCircleCenter :: PointValidationError -> CircleMetricError
+ Moonlight.Triangulation: InvalidCircleRadius :: RadiusSquaredError -> CircleMetricError
+ Moonlight.Triangulation: InvalidRectangleCenter :: PointValidationError -> RectangleMetricError
+ Moonlight.Triangulation: InvalidRectangleLower :: PointValidationError -> RectangleMetricError
+ Moonlight.Triangulation: InvalidRectangleUpper :: PointValidationError -> RectangleMetricError
+ Moonlight.Triangulation: LocationStats :: Int -> Bool -> LocationStats
+ Moonlight.Triangulation: OnEdge :: DirectedEdgeId -> Location
+ Moonlight.Triangulation: OnVertex :: VertexId -> Location
+ Moonlight.Triangulation: OutsideConvexHull :: Maybe DirectedEdgeId -> Location
+ Moonlight.Triangulation: RemovalOutcome :: Point -> vertex -> Maybe (VertexId, Point) -> RemovalOutcome vertex
+ Moonlight.Triangulation: RemovalResult :: Triangulation mode vertex directed undirected face -> RemovalOutcome vertex -> BuildStats -> RemovalResult (mode :: ConstraintMode) vertex directed undirected face
+ Moonlight.Triangulation: VertexIntersection :: VertexId -> Intersection
+ Moonlight.Triangulation: [cdtAcceptedBuild] :: CdtBuildResult vertex directed undirected face -> BuildResult 'Constrained vertex directed undirected face
+ Moonlight.Triangulation: [cdtRejectedConstraints] :: CdtBuildResult vertex directed undirected face -> Vector (Int, Int)
+ Moonlight.Triangulation: [constraintBatchAccepted] :: ConstraintBatchStats -> Int
+ Moonlight.Triangulation: [constraintBatchCorridors] :: ConstraintBatchStats -> Int
+ Moonlight.Triangulation: [constraintBatchCrossedEdges] :: ConstraintBatchStats -> Int
+ Moonlight.Triangulation: [constraintBatchRejected] :: ConstraintBatchStats -> Int
+ Moonlight.Triangulation: [constraintBatchRequests] :: ConstraintBatchStats -> Int
+ Moonlight.Triangulation: [constraintBatchReusedFaces] :: ConstraintBatchStats -> Int
+ Moonlight.Triangulation: [constraintRecoveryAddedEdges] :: ConstraintRecoveryResult pathReceipt vertex directed undirected face -> Int
+ Moonlight.Triangulation: [constraintRecoveryPathReceipt] :: ConstraintRecoveryResult pathReceipt vertex directed undirected face -> pathReceipt
+ Moonlight.Triangulation: [constraintRecoveryTriangulation] :: ConstraintRecoveryResult pathReceipt vertex directed undirected face -> Triangulation 'Constrained vertex directed undirected face
+ Moonlight.Triangulation: [locationUsedFallback] :: LocationStats -> Bool
+ Moonlight.Triangulation: [locationWalkSteps] :: LocationStats -> Int
+ Moonlight.Triangulation: [removalOutcomeData] :: RemovalOutcome vertex -> vertex
+ Moonlight.Triangulation: [removalOutcomePoint] :: RemovalOutcome vertex -> Point
+ Moonlight.Triangulation: [removalOutcomeSwap] :: RemovalOutcome vertex -> Maybe (VertexId, Point)
+ Moonlight.Triangulation: [removalOutcome] :: RemovalResult (mode :: ConstraintMode) vertex directed undirected face -> RemovalOutcome vertex
+ Moonlight.Triangulation: [removalStats] :: RemovalResult (mode :: ConstraintMode) vertex directed undirected face -> BuildStats
+ Moonlight.Triangulation: [removalTriangulation] :: RemovalResult (mode :: ConstraintMode) vertex directed undirected face -> Triangulation mode vertex directed undirected face
+ Moonlight.Triangulation: [statBoundaryEdgeSplits] :: BuildStats -> Int
+ Moonlight.Triangulation: [statDuplicatePoints] :: BuildStats -> Int
+ Moonlight.Triangulation: [statEdgeFlips] :: BuildStats -> Int
+ Moonlight.Triangulation: [statExistingPoints] :: BuildStats -> Int
+ Moonlight.Triangulation: [statFaceSplits] :: BuildStats -> Int
+ Moonlight.Triangulation: [statHullInsertions] :: BuildStats -> Int
+ Moonlight.Triangulation: [statInputPoints] :: BuildStats -> Int
+ Moonlight.Triangulation: [statInteriorEdgeSplits] :: BuildStats -> Int
+ Moonlight.Triangulation: [statLegalizationMaxStack] :: BuildStats -> Int
+ Moonlight.Triangulation: [statLineExtensions] :: BuildStats -> Int
+ Moonlight.Triangulation: [statLineSplits] :: BuildStats -> Int
+ Moonlight.Triangulation: [statLineToAreaTransitions] :: BuildStats -> Int
+ Moonlight.Triangulation: [statLocationFallbacks] :: BuildStats -> Int
+ Moonlight.Triangulation: [statLocationMaxWalk] :: BuildStats -> Int
+ Moonlight.Triangulation: [statLocationWalkSteps] :: BuildStats -> Int
+ Moonlight.Triangulation: [statRefinementFaceChecks] :: BuildStats -> Int
+ Moonlight.Triangulation: [statRefinementQueuePops] :: BuildStats -> Int
+ Moonlight.Triangulation: [statSpatialSeedPoints] :: BuildStats -> Int
+ Moonlight.Triangulation: [statSteinerPoints] :: BuildStats -> Int
+ Moonlight.Triangulation: [statSweepFastPoints] :: BuildStats -> Int
+ Moonlight.Triangulation: [statSweepSkippedPoints] :: BuildStats -> Int
+ Moonlight.Triangulation: [statUniquePoints] :: BuildStats -> Int
+ Moonlight.Triangulation: addConstraintAndSplit :: (Point -> vertex) -> Triangulation 'Constrained vertex directed undirected face -> VertexId -> VertexId -> Either CdtError (ConstraintResult vertex directed undirected face)
+ Moonlight.Triangulation: addConstraintEdge :: HasPosition vertex => Triangulation 'Constrained vertex directed undirected face -> vertex -> vertex -> Either CdtError (ConstraintResult vertex directed undirected face)
+ Moonlight.Triangulation: addConstraintEdges :: HasPosition vertex => Triangulation 'Constrained vertex directed undirected face -> Vector vertex -> Bool -> Either CdtError (Triangulation 'Constrained vertex directed undirected face)
+ Moonlight.Triangulation: addConstraintsAndSplit :: (Point -> vertex) -> Triangulation 'Constrained vertex directed undirected face -> Vector (VertexId, VertexId) -> Either CdtError (ConstraintSplitBatchResult vertex directed undirected face)
+ Moonlight.Triangulation: addExactVectors :: ExactVector -> ExactVector -> ExactVector
+ Moonlight.Triangulation: adjacentEdge :: forall (mode :: ConstraintMode) vertex directed undirected face. Triangulation mode vertex directed undirected face -> FaceId -> Maybe DirectedEdgeId
+ Moonlight.Triangulation: allFaces :: forall (mode :: ConstraintMode) vertex directed undirected face. Triangulation mode vertex directed undirected face -> [FaceId]
+ Moonlight.Triangulation: allSegmentRelations :: [SegmentRelation]
+ Moonlight.Triangulation: barycentricCoordinates :: Point -> Point -> Point -> Point -> Maybe (Double, Double, Double)
+ Moonlight.Triangulation: buildStats :: BuildResult mode vertex directed undirected face -> BuildStats
+ Moonlight.Triangulation: canAddConstraint :: Triangulation 'Constrained vertex directed undirected face -> VertexId -> VertexId -> Bool
+ Moonlight.Triangulation: centroid :: Point -> Point -> Point -> Point
+ Moonlight.Triangulation: circleMetric :: Point -> Double -> Either CircleMetricError CircleMetric
+ Moonlight.Triangulation: circumcenter :: Point -> Point -> Point -> Maybe Point
+ Moonlight.Triangulation: class Coordinate2 value
+ Moonlight.Triangulation: class DistanceMetric metric
+ Moonlight.Triangulation: clear :: forall (mode :: ConstraintMode) vertex directed undirected face. Triangulation mode vertex directed undirected face -> Triangulation mode vertex directed undirected face
+ Moonlight.Triangulation: clockwise :: forall (mode :: ConstraintMode) vertex directed undirected face. Triangulation mode vertex directed undirected face -> DirectedEdgeId -> DirectedEdgeId
+ Moonlight.Triangulation: compareExactVectorAngle :: ExactVector -> ExactVector -> Ordering
+ Moonlight.Triangulation: conflictingEdges :: [Intersection] -> [DirectedEdgeId]
+ Moonlight.Triangulation: constrainedDelaunayMaximal :: HasPosition vertex => ElementDefaults directed undirected face -> Vector vertex -> Vector (Int, Int) -> Either CdtError (CdtBuildResult vertex directed undirected face)
+ Moonlight.Triangulation: constraintStorageBytes :: Triangulation 'Constrained vertex directed undirected face -> Integer
+ Moonlight.Triangulation: counterClockwise :: forall (mode :: ConstraintMode) vertex directed undirected face. Triangulation mode vertex directed undirected face -> DirectedEdgeId -> DirectedEdgeId
+ Moonlight.Triangulation: data BuildStats
+ Moonlight.Triangulation: data CdtBuildResult vertex directed undirected face
+ Moonlight.Triangulation: data CircleMetric
+ Moonlight.Triangulation: data CircleMetricError
+ Moonlight.Triangulation: data ConstraintBatchStats
+ Moonlight.Triangulation: data ConstraintOutcome
+ Moonlight.Triangulation: data ConstraintRecoveryResult pathReceipt vertex directed undirected face
+ Moonlight.Triangulation: data ExactVector
+ Moonlight.Triangulation: data InsertionDisposition
+ Moonlight.Triangulation: data InsertionResult (mode :: ConstraintMode) vertex directed undirected face
+ Moonlight.Triangulation: data Intersection
+ Moonlight.Triangulation: data Location
+ Moonlight.Triangulation: data LocationStats
+ Moonlight.Triangulation: data RectangleMetric
+ Moonlight.Triangulation: data RectangleMetricError
+ Moonlight.Triangulation: data RemovalOutcome vertex
+ Moonlight.Triangulation: data RemovalResult (mode :: ConstraintMode) vertex directed undirected face
+ Moonlight.Triangulation: data Session s vertex directed undirected face a
+ Moonlight.Triangulation: directedEdgeData :: forall (mode :: ConstraintMode) vertex directed undirected face. Triangulation mode vertex directed undirected face -> DirectedEdgeId -> directed
+ Moonlight.Triangulation: directedEdges :: forall (mode :: ConstraintMode) vertex directed undirected face. Triangulation mode vertex directed undirected face -> [DirectedEdgeId]
+ Moonlight.Triangulation: directedPayloads :: forall (mode :: ConstraintMode) vertex directed undirected face directed' f. Applicative f => (directed -> f directed') -> Triangulation mode vertex directed undirected face -> f (Triangulation mode vertex directed' undirected face)
+ Moonlight.Triangulation: distance :: Point -> Point -> Double
+ Moonlight.Triangulation: edgesInCircle :: forall (mode :: ConstraintMode) vertex directed undirected face. Triangulation mode vertex directed undirected face -> Point -> Double -> Either CircleMetricError [UndirectedEdgeId]
+ Moonlight.Triangulation: edgesInRectangle :: forall (mode :: ConstraintMode) vertex directed undirected face. Triangulation mode vertex directed undirected face -> Point -> Point -> Either RectangleMetricError [UndirectedEdgeId]
+ Moonlight.Triangulation: edgesInShape :: forall metric (mode :: ConstraintMode) vertex directed undirected face. DistanceMetric metric => Triangulation mode vertex directed undirected face -> metric -> [UndirectedEdgeId]
+ Moonlight.Triangulation: empty :: forall directed undirected face (mode :: ConstraintMode) vertex. ElementDefaults directed undirected face -> Triangulation mode vertex directed undirected face
+ Moonlight.Triangulation: emptyBuildStats :: BuildStats
+ Moonlight.Triangulation: emptyLocationStats :: LocationStats
+ Moonlight.Triangulation: exactCross :: ExactVector -> ExactVector -> ExactRational
+ Moonlight.Triangulation: exactPointCross :: ExactPoint -> ExactPoint -> ExactRational
+ Moonlight.Triangulation: exactSupportingLineIntersection :: ExactSegment -> ExactSegment -> Either ExactIntersectionError ExactPoint
+ Moonlight.Triangulation: exactVectorFromPoints :: ExactPoint -> ExactPoint -> ExactVector
+ Moonlight.Triangulation: existsConstraint :: Triangulation 'Constrained vertex directed undirected face -> VertexId -> VertexId -> Bool
+ Moonlight.Triangulation: faceArea :: forall (mode :: ConstraintMode) vertex directed undirected face. Triangulation mode vertex directed undirected face -> FaceId -> Maybe Double
+ Moonlight.Triangulation: faceComponentsBy :: forall label (mode :: ConstraintMode) vertex directed undirected face. Eq label => Triangulation mode vertex directed undirected face -> (FaceId -> Maybe label) -> (UndirectedEdgeId -> Bool) -> [(label, FaceComponent)]
+ Moonlight.Triangulation: faceData :: forall (mode :: ConstraintMode) vertex directed undirected face. Triangulation mode vertex directed undirected face -> FaceId -> face
+ Moonlight.Triangulation: faceMinimumAngleDegrees :: forall (mode :: ConstraintMode) vertex directed undirected face. Triangulation mode vertex directed undirected face -> FaceId -> Maybe Double
+ Moonlight.Triangulation: facePayloads :: forall (mode :: ConstraintMode) vertex directed undirected face face' f. Applicative f => (face -> f face') -> Triangulation mode vertex directed undirected face -> f (Triangulation mode vertex directed undirected face')
+ Moonlight.Triangulation: foldAllFaces' :: forall (mode :: ConstraintMode) vertex directed undirected face a. Triangulation mode vertex directed undirected face -> (a -> FaceId -> a) -> a -> a
+ Moonlight.Triangulation: foldCorridorBetweenPoints :: forall (mode :: ConstraintMode) vertex directed undirected face state answer. Triangulation mode vertex directed undirected face -> QueryPoint -> QueryPoint -> (state -> Intersection -> Either answer state) -> state -> Maybe (Either answer state)
+ Moonlight.Triangulation: foldCorridorBetweenVertices :: forall (mode :: ConstraintMode) vertex directed undirected face state answer. Triangulation mode vertex directed undirected face -> VertexId -> VertexId -> (state -> Intersection -> Either answer state) -> state -> Maybe (Either answer state)
+ Moonlight.Triangulation: foldDirectedEdges' :: forall (mode :: ConstraintMode) vertex directed undirected face a. Triangulation mode vertex directed undirected face -> (a -> DirectedEdgeId -> a) -> a -> a
+ Moonlight.Triangulation: foldFaceDirectedEdges' :: forall (mode :: ConstraintMode) vertex directed undirected face a. Triangulation mode vertex directed undirected face -> FaceId -> (a -> DirectedEdgeId -> a) -> a -> a
+ Moonlight.Triangulation: foldInnerFaces' :: forall (mode :: ConstraintMode) vertex directed undirected face a. Triangulation mode vertex directed undirected face -> (a -> FaceId -> a) -> a -> a
+ Moonlight.Triangulation: foldPayloads :: Monoid summary => PayloadTraversal source source payload payload -> (payload -> summary) -> source -> summary
+ Moonlight.Triangulation: foldUndirectedEdges' :: forall (mode :: ConstraintMode) vertex directed undirected face a. Triangulation mode vertex directed undirected face -> (a -> UndirectedEdgeId -> a) -> a -> a
+ Moonlight.Triangulation: foldVertexOutgoingEdges' :: forall (mode :: ConstraintMode) vertex directed undirected face a. Triangulation mode vertex directed undirected face -> VertexId -> (a -> DirectedEdgeId -> a) -> a -> a
+ Moonlight.Triangulation: foldVertices' :: forall (mode :: ConstraintMode) vertex directed undirected face a. Triangulation mode vertex directed undirected face -> (a -> VertexId -> a) -> a -> a
+ Moonlight.Triangulation: fromPoint :: Coordinate2 value => Point -> value
+ Moonlight.Triangulation: getConflictingEdgesBetweenPoints :: Triangulation 'Constrained vertex directed undirected face -> QueryPoint -> QueryPoint -> [DirectedEdgeId]
+ Moonlight.Triangulation: getConflictingEdgesBetweenVertices :: Triangulation 'Constrained vertex directed undirected face -> VertexId -> VertexId -> [DirectedEdgeId]
+ Moonlight.Triangulation: hullEdges :: forall (mode :: ConstraintMode) vertex directed undirected face. Triangulation mode vertex directed undirected face -> [DirectedEdgeId]
+ Moonlight.Triangulation: hullVertices :: forall (mode :: ConstraintMode) vertex directed undirected face. Triangulation mode vertex directed undirected face -> [VertexId]
+ Moonlight.Triangulation: imapFaces :: forall face' face (mode :: ConstraintMode) vertex directed undirected. face' -> (FaceId -> face -> face') -> Triangulation mode vertex directed undirected face -> Triangulation mode vertex directed undirected face'
+ Moonlight.Triangulation: imapUndirectedEdges :: forall undirected' undirected (mode :: ConstraintMode) vertex directed face. undirected' -> (UndirectedEdgeId -> undirected -> undirected') -> Triangulation mode vertex directed undirected face -> Triangulation mode vertex directed undirected' face
+ Moonlight.Triangulation: inCircle :: Point -> Point -> Point -> Point -> Ordering
+ Moonlight.Triangulation: innerFaceDirectedEdges :: forall (mode :: ConstraintMode) vertex directed undirected face. Triangulation mode vertex directed undirected face -> FaceId -> Maybe (DirectedEdgeId, DirectedEdgeId, DirectedEdgeId)
+ Moonlight.Triangulation: innerFaceVertices :: forall (mode :: ConstraintMode) vertex directed undirected face. Triangulation mode vertex directed undirected face -> FaceId -> Maybe (VertexId, VertexId, VertexId)
+ Moonlight.Triangulation: insert :: forall vertex (mode :: ConstraintMode) directed undirected face. HasPosition vertex => Triangulation mode vertex directed undirected face -> vertex -> Either BuildError (InsertionResult mode vertex directed undirected face)
+ Moonlight.Triangulation: insertAt :: forall (mode :: ConstraintMode) vertex directed undirected face. Triangulation mode vertex directed undirected face -> Point -> vertex -> Either BuildError (InsertionResult mode vertex directed undirected face)
+ Moonlight.Triangulation: insertMany :: forall (mode :: ConstraintMode) vertex directed undirected face. HasPosition vertex => Triangulation mode vertex directed undirected face -> Vector vertex -> Either BuildError (BuildResult mode vertex directed undirected face)
+ Moonlight.Triangulation: insertVertex :: HasPosition vertex => vertex -> Session s vertex directed undirected face VertexId
+ Moonlight.Triangulation: insertVertexAt :: Point -> vertex -> Session s vertex directed undirected face (VertexId, InsertionDisposition)
+ Moonlight.Triangulation: insertVertexAtNear :: FaceId -> Point -> vertex -> Session s vertex directed undirected face (VertexId, InsertionDisposition)
+ Moonlight.Triangulation: insertionDisposition :: InsertionResult mode vertex directed undirected face -> InsertionDisposition
+ Moonlight.Triangulation: insertionStats :: InsertionResult mode vertex directed undirected face -> BuildStats
+ Moonlight.Triangulation: insertionTriangulation :: InsertionResult mode vertex directed undirected face -> Triangulation mode vertex directed undirected face
+ Moonlight.Triangulation: insertionVertex :: InsertionResult mode vertex directed undirected face -> VertexId
+ Moonlight.Triangulation: intersectsConstraint :: Triangulation 'Constrained vertex directed undirected face -> QueryPoint -> QueryPoint -> Bool
+ Moonlight.Triangulation: isOuterDirectedEdge :: forall (mode :: ConstraintMode) vertex directed undirected face. Triangulation mode vertex directed undirected face -> DirectedEdgeId -> Bool
+ Moonlight.Triangulation: labelledRegionBoundaries :: forall label (mode :: ConstraintMode) vertex directed undirected face. Eq label => Triangulation mode vertex directed undirected face -> (FaceId -> label) -> Either BoundaryObstruction [(label, RegionBoundary)]
+ Moonlight.Triangulation: lineIntersections :: forall (mode :: ConstraintMode) vertex directed undirected face. Triangulation mode vertex directed undirected face -> QueryPoint -> QueryPoint -> [Intersection]
+ Moonlight.Triangulation: lineIntersectionsBetweenVertices :: forall (mode :: ConstraintMode) vertex directed undirected face. Triangulation mode vertex directed undirected face -> VertexId -> VertexId -> [Intersection]
+ Moonlight.Triangulation: locateAndRemove :: forall (mode :: ConstraintMode) vertex directed undirected face. Triangulation mode vertex directed undirected face -> Point -> Either BuildError (Maybe (RemovalResult mode vertex directed undirected face))
+ Moonlight.Triangulation: locatePoint :: forall (mode :: ConstraintMode) vertex directed undirected face. Triangulation mode vertex directed undirected face -> QueryPoint -> Location
+ Moonlight.Triangulation: locatePointWithHint :: forall (mode :: ConstraintMode) vertex directed undirected face. Triangulation mode vertex directed undirected face -> Maybe LocationHint -> QueryPoint -> (Location, LocationStats)
+ Moonlight.Triangulation: mapCoordinate2 :: (Coordinate2 input, Coordinate2 output) => (Point -> Point) -> input -> output
+ Moonlight.Triangulation: mapDirectedEdges :: forall directed directed' (mode :: ConstraintMode) vertex undirected face. (directed -> directed') -> Triangulation mode vertex directed undirected face -> Triangulation mode vertex directed' undirected face
+ Moonlight.Triangulation: mapFaces :: forall face face' (mode :: ConstraintMode) vertex directed undirected. (face -> face') -> Triangulation mode vertex directed undirected face -> Triangulation mode vertex directed undirected face'
+ Moonlight.Triangulation: mapUndirectedEdges :: forall undirected undirected' (mode :: ConstraintMode) vertex directed face. (undirected -> undirected') -> Triangulation mode vertex directed undirected face -> Triangulation mode vertex directed undirected' face
+ Moonlight.Triangulation: metricContainsPoint :: DistanceMetric metric => metric -> Point -> Bool
+ Moonlight.Triangulation: metricIntersectsEdge :: DistanceMetric metric => metric -> Point -> Point -> Bool
+ Moonlight.Triangulation: metricStartPoint :: DistanceMetric metric => metric -> QueryPoint
+ Moonlight.Triangulation: midpoint :: Point -> Point -> Point
+ Moonlight.Triangulation: next :: forall (mode :: ConstraintMode) vertex directed undirected face. Triangulation mode vertex directed undirected face -> DirectedEdgeId -> DirectedEdgeId
+ Moonlight.Triangulation: numConstraints :: forall (mode :: ConstraintMode) vertex directed undirected face. Triangulation mode vertex directed undirected face -> Int
+ Moonlight.Triangulation: numDirectedEdges :: forall (mode :: ConstraintMode) vertex directed undirected face. Triangulation mode vertex directed undirected face -> Int
+ Moonlight.Triangulation: numInnerFaces :: forall (mode :: ConstraintMode) vertex directed undirected face. Triangulation mode vertex directed undirected face -> Int
+ Moonlight.Triangulation: orient2d :: Point -> Point -> Point -> Ordering
+ Moonlight.Triangulation: outerFaceFloodFill :: forall (mode :: ConstraintMode) vertex directed undirected face. Triangulation mode vertex directed undirected face -> (UndirectedEdgeId -> Bool) -> [FaceId]
+ Moonlight.Triangulation: outerRegionFaces :: Triangulation 'Constrained vertex directed undirected face -> [FaceId]
+ Moonlight.Triangulation: overPayloads :: PayloadTraversal source target payload payload' -> (payload -> payload') -> source -> target
+ Moonlight.Triangulation: payloadList :: PayloadTraversal source source payload payload -> source -> [payload]
+ Moonlight.Triangulation: previous :: forall (mode :: ConstraintMode) vertex directed undirected face. Triangulation mode vertex directed undirected face -> DirectedEdgeId -> DirectedEdgeId
+ Moonlight.Triangulation: recoverConstraints :: Triangulation 'Constrained vertex directed undirected face -> Vector (VertexId, VertexId) -> Either CdtError (ConstraintBatchResult vertex directed undirected face)
+ Moonlight.Triangulation: rectangleMetric :: Point -> Point -> Either RectangleMetricError RectangleMetric
+ Moonlight.Triangulation: refinementStats :: RefinementResult mode vertex directed undirected face -> BuildStats
+ Moonlight.Triangulation: refuse :: BuildError -> Session s vertex directed undirected face a
+ Moonlight.Triangulation: removeAt :: Point -> Session s vertex directed undirected face (Maybe (RemovalOutcome vertex))
+ Moonlight.Triangulation: removeAtNear :: VertexId -> Point -> Session s vertex directed undirected face (Maybe (RemovalOutcome vertex))
+ Moonlight.Triangulation: removeConstraintEdge :: Triangulation 'Constrained vertex directed undirected face -> UndirectedEdgeId -> Either CdtError (Triangulation 'Constrained vertex directed undirected face)
+ Moonlight.Triangulation: removeManyAt :: Vector Point -> Session s vertex directed undirected face (Vector (Maybe (RemovalOutcome vertex)))
+ Moonlight.Triangulation: removeManyAtNear :: Vector (Maybe VertexId) -> Vector Point -> Session s vertex directed undirected face (Vector (Maybe (RemovalOutcome vertex)))
+ Moonlight.Triangulation: removeVertex :: forall (mode :: ConstraintMode) vertex directed undirected face. Triangulation mode vertex directed undirected face -> VertexId -> Either BuildError (RemovalResult mode vertex directed undirected face)
+ Moonlight.Triangulation: segmentIntersectsNonCollinear :: Point -> Point -> Point -> Point -> Bool
+ Moonlight.Triangulation: segmentRelation :: Point -> Point -> Point -> Point -> SegmentRelation
+ Moonlight.Triangulation: segmentsIntersect :: Point -> Point -> Point -> Point -> Bool
+ Moonlight.Triangulation: segmentsProperlyCross :: Point -> Point -> Point -> Point -> Bool
+ Moonlight.Triangulation: setDirectedEdgeData :: forall (mode :: ConstraintMode) vertex directed undirected face. Triangulation mode vertex directed undirected face -> DirectedEdgeId -> directed -> Triangulation mode vertex directed undirected face
+ Moonlight.Triangulation: setFaceData :: forall (mode :: ConstraintMode) vertex directed undirected face. Triangulation mode vertex directed undirected face -> FaceId -> face -> Triangulation mode vertex directed undirected face
+ Moonlight.Triangulation: setUndirectedEdgeData :: forall (mode :: ConstraintMode) vertex directed undirected face. Triangulation mode vertex directed undirected face -> UndirectedEdgeId -> undirected -> Triangulation mode vertex directed undirected face
+ Moonlight.Triangulation: sideQuery :: Point -> Point -> Point -> LineSideInfo
+ Moonlight.Triangulation: squaredDistance :: Point -> Point -> Double
+ Moonlight.Triangulation: toPoint :: Coordinate2 value => value -> Point
+ Moonlight.Triangulation: translateExactPoint :: ExactPoint -> ExactVector -> ExactPoint
+ Moonlight.Triangulation: triangleArea :: Point -> Point -> Point -> Double
+ Moonlight.Triangulation: triangulationIsValid :: forall (mode :: ConstraintMode) vertex directed undirected face. Triangulation mode vertex directed undirected face -> Bool
+ Moonlight.Triangulation: type ConstraintResult vertex directed undirected face = ConstraintRecoveryResult Vector DirectedEdgeId vertex directed undirected face
+ Moonlight.Triangulation: type ConstraintSplitBatchResult vertex directed undirected face = ConstraintRecoveryResult Vector Vector DirectedEdgeId vertex directed undirected face
+ Moonlight.Triangulation: type PayloadTraversal source target payload payload' = forall (f :: Type -> Type). Applicative f => payload -> f payload' -> source -> f target
+ Moonlight.Triangulation: undirectedEdgeData :: forall (mode :: ConstraintMode) vertex directed undirected face. Triangulation mode vertex directed undirected face -> UndirectedEdgeId -> undirected
+ Moonlight.Triangulation: undirectedPayloads :: forall (mode :: ConstraintMode) vertex directed undirected face undirected' f. Applicative f => (undirected -> f undirected') -> Triangulation mode vertex directed undirected face -> f (Triangulation mode vertex directed undirected' face)
+ Moonlight.Triangulation: validateDelaunay :: forall (mode :: ConstraintMode) vertex directed undirected face. Triangulation mode vertex directed undirected face -> [InvariantViolation]
+ Moonlight.Triangulation: validateTopology :: forall (mode :: ConstraintMode) vertex directed undirected face. Triangulation mode vertex directed undirected face -> [InvariantViolation]
+ Moonlight.Triangulation: vertexOutEdge :: forall (mode :: ConstraintMode) vertex directed undirected face. Triangulation mode vertex directed undirected face -> VertexId -> Maybe DirectedEdgeId
+ Moonlight.Triangulation: vertexPayloads :: forall (mode :: ConstraintMode) vertex directed undirected face vertex' f. Applicative f => (vertex -> f vertex') -> Triangulation mode vertex directed undirected face -> f (Triangulation mode vertex' directed undirected face)
+ Moonlight.Triangulation: verticesInCircle :: forall (mode :: ConstraintMode) vertex directed undirected face. Triangulation mode vertex directed undirected face -> Point -> Double -> Either CircleMetricError [VertexId]
+ Moonlight.Triangulation: verticesInRectangle :: forall (mode :: ConstraintMode) vertex directed undirected face. Triangulation mode vertex directed undirected face -> Point -> Point -> Either RectangleMetricError [VertexId]
+ Moonlight.Triangulation: verticesInShape :: forall metric (mode :: ConstraintMode) vertex directed undirected face. DistanceMetric metric => Triangulation mode vertex directed undirected face -> metric -> [VertexId]
+ Moonlight.Triangulation: withSession :: forall (mode :: ConstraintMode) vertex directed undirected face result. Triangulation mode vertex directed undirected face -> Int -> (forall s. () => Session s vertex directed undirected face result) -> Either BuildError (result, Triangulation mode vertex directed undirected face, BuildStats)

Files

CHANGELOG.md view
@@ -10,6 +10,19 @@ The serialization format carries its own version tag, independent of the package version; any change to it is recorded here explicitly. +## 1.4.0.3 - 2026-08-28++* Restore public `dcel` and `build` sublibraries as safe restrictions of their+  private implementation owners. The default library reexports the same lawful+  modules, so consumers may choose the broad surface or a smaller dependency+  cone without receiving a second implementation.+* Publish incremental insertion/removal, sealed edit sessions, typed point and+  shape queries, owning handles, payload traversals, dynamic constraint+  operations, and whole-mesh diagnostics through the canonical Haskell facade.+* Keep raw identifier constructors, resident representation, admitted-input+  construction, local transaction scheduling, seam witnesses, and local+  validation closures private.+ ## 1.4.0.2 - 2026-08-28  * Consolidate usage guidance behind one operation index and exact set-operation
README.md view
@@ -30,11 +30,15 @@ | --- | --- | --- | --- | | `delaunayGeometry` | Coordinates own the input | `Vector Point` | Geometry-only mesh / `BuildError` | | `delaunayFromCoordinates` | Payloads have a separate plane | Defaults, points, payloads, duplicate policy | `BuildResult` / `BuildError` |+| `insert` / `insertAt` / `insertMany` | An immutable mesh gains sites or payload replacements | Mesh plus payloads or explicit points | `InsertionResult` / `BuildResult` / `BuildError` |+| `withSession` | Many inserts and removals should publish once | Mesh, peak added-site count, composed `Session` | Result, mesh, and `BuildStats` / `BuildError` |+| `removeVertex` / `locateAndRemove` | A resident handle or exact position must be removed | Mesh plus `VertexId` / `Point` | `RemovalResult` / optional result / `BuildError` | | `siteRelation` | Supports need exact classification | Two meshes | `SiteRelation` | | `union` / `unions` | Unconstrained supports must join | Two meshes / mesh list | Union mesh / `BuildError` | | `intersection` / `intersectionWith` | Shared support is required | Two meshes; optional payload combiner | Common-site mesh / `BuildError` | | `difference` / `symmetricDifference` | Left-only / exclusive support is required | Source and mask / two meshes | Result mesh / `BuildError` | | `constrainedDelaunay` | Segments author topology | Defaults, positioned vertices, index pairs | `BuildResult` / `CdtError` |+| `addConstraintEdge(s)` / `removeConstraintEdge` | Constraints change on a resident mesh | Mesh plus vertex pair(s) / edge | Constraint receipt or mesh / `CdtError` | | `unionConstrained` / `unionConstrainedWith` | Arbitrary constrained meshes must join | Two meshes; optional payload combiner | Constrained mesh / `ConstrainedUnionError` | | `extendConstrainedWith` | One constrained mesh owns identity | Combiner, base, extension | `ConstrainedExtensionResult` / `ConstrainedUnionError` | | `joinSeparatedConstrained` | Inputs are strictly x-separated | Face predicate, refinement parameters, two meshes | `ConstrainedSeamResult` / `ConstrainedUnionError` |@@ -42,6 +46,9 @@ | `refineWithinDomain` | Quality applies to admitted faces | Vertex constructor, parameters, face set, mesh | `RefinementDomainResult` / `BuildError` | | `faceComponents` | Bounded faces must descend by label | Mesh, `FaceId -> label` | `[(label, FaceComponent)]` | | `componentBoundary` | One component must publish as loops | Mesh, `FaceComponent` | `RegionBoundary` / `BoundaryObstruction` |+| `locatePoint` / `locatePointWithHint` | An admitted point needs an exact cell classification | Mesh, `QueryPoint`, optional hint | `Location`, optionally with `LocationStats` |+| `lineIntersections` | A segment needs its ordered mesh crossings | Mesh and admitted endpoints | `[Intersection]` |+| `verticesInCircle` / `verticesInRectangle` | A metric window selects resident sites | Mesh and circle / rectangle | Handles / typed metric error | | `overlayLayers` | Layers need one exact arrangement | Two `PlanarLayer`s | `OverlayResult` / `OverlayError` | | `overlayClosedUnion` / `overlayClosedIntersection` / `overlayRegularizedDifference` | Boolean output may retain lower cells | Two label predicates, `OverlayResult` | `ExactCellSet` / `OverlaySelectionError` | | `overlaySelectedRegion` | Selected faces must publish as polygons | Label-pair predicate, `OverlayResult` | `PlanarRegion` / `RegionPublicationError` |@@ -112,8 +119,9 @@  ## Use -Use the main facade with explicit `Vector` inputs. Construction and set algebra-retain their typed obstruction in `Either`:+Use the broad default library with explicit `Vector` inputs. It exposes the+facade and the safe leaf modules; construction and set algebra retain their+typed obstruction in `Either`:  ```cabal build-depends:@@ -127,6 +135,15 @@ [example index](docs/examples/README.md) adds constrained interiors, alpha-boundary descent, exact overlay and valuations, and polygonal morphology. +For a smaller compile/dependency cone, depend on only the component modules you+import:++```cabal+build-depends:+  moonlight-triangulation:dcel >= 1.4.1 && < 1.5,+  moonlight-triangulation:build >= 1.4.1 && < 1.5+```+ ## Architecture  The resident DCEL is immutable structure-of-arrays over paged copy-on-write@@ -137,14 +154,16 @@ ## Consumer libraries  The Cabal manifest owns component visibility and dependencies; this is its-consumer-facing projection. External packages normally use the main library-and add only the public sublibraries whose modules they import. The finite DCEL-and construction kernel remain private behind the main facade.+consumer-facing projection. The default library reexports the complete safe+geometry surface. Use a public sublibrary directly when its smaller dependency+cone matters; the trusted implementation units remain private.  | Dependency | Imports | Purpose | | --- | --- | --- |-| `moonlight-triangulation` | `Moonlight.Triangulation`, `.HintGenerator`, `.Overlay`, `.Minkowski` | Construction, observations, set algebra, CDT, refinement, alpha geometry, and exact planar operations. |+| `moonlight-triangulation` | `Moonlight.Triangulation` and every safe `dcel`, `build`, and `dual` module | Broad construction, observation, set algebra, CDT, refinement, alpha, Voronoi, interpolation, and exact planar surface. | | `moonlight-triangulation:core` | `.Scalar`, `.LineSideInfo` | Low-level exact scalars and paged storage primitives. |+| `moonlight-triangulation:dcel` | `.Types`, `.Dcel`, `.Handles.*`, `.PointLocation`, `.FloodFillIterator`, `.IntersectionIterator`, `.Alpha`, `.Exact`, `.Region`, `.Valuation` | Immutable mesh vocabulary, observations, typed spatial queries, exact geometry, and owning handles; no construction kernel. |+| `moonlight-triangulation:build` | `.BulkLoad`, `.Session`, `.Removal`, `.Cdt`, `.Refinement`, `.SetAlgebra` | Construction and persistent editing over the DCEL; no dual, serialization, concurrency, or Homology dependency. | | `moonlight-triangulation:dual` | `.Voronoi`, `.Voronoi.Handles`, `.Interpolation` | Voronoi observations and natural-neighbour interpolation. | | `moonlight-triangulation:parallel` | `.Parallel` | Bounded concurrent union; adds `async`. | | `moonlight-triangulation:serialize` | `.Serialization` | Versioned binary envelopes; adds `binary`, `bytestring`, and `transformers`. |
bench/build/Moonlight/Triangulation/BuildBench.hs view
@@ -23,8 +23,8 @@ -- 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, insertVertexAt, withLocalSession, withSession)+import Moonlight.Triangulation.Internal.HandleDefs (VertexId (VertexId))+import Moonlight.Triangulation.Internal.Session (insertVertex, insertVertexAt, withLocalSession, withSession) import Moonlight.Triangulation.Types   ( InsertionResult (insertionTriangulation)   , refinementStats
bench/region/Moonlight/Triangulation/RegionBench.hs view
@@ -28,7 +28,7 @@   ) import Moonlight.Triangulation.Dcel (numInnerFaces) import Moonlight.Triangulation.Exact (ExactPoint, exactPoint)-import Moonlight.Triangulation.Handles.HandleDefs (VertexId (..))+import Moonlight.Triangulation.Internal.HandleDefs (VertexId (..)) import Moonlight.Triangulation.Overlay   ( OverlayReceipt (..)   , overlayClosedIntersection
bench/spade-compare/hs/Main.hs view
@@ -24,7 +24,7 @@ import Moonlight.Triangulation import Moonlight.Triangulation.BulkLoad import Moonlight.Triangulation.Cdt-import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.HandleDefs import Moonlight.Triangulation.HintGenerator import Moonlight.Triangulation.Interpolation import Moonlight.Triangulation.PointLocation
bench/spade-compare/hs/Moonlight/Triangulation/Bench/SpadeCompare/Constraint.hs view
@@ -8,7 +8,7 @@ import qualified Data.Vector as V import Moonlight.Triangulation import Moonlight.Triangulation.Cdt-import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.HandleDefs import Moonlight.Triangulation.Bench.SpadeCompare.Support import Moonlight.Triangulation.Bench.SpadeCompare.Input (randomPoints) 
bench/spade-compare/hs/Moonlight/Triangulation/Bench/SpadeCompare/Removal.hs view
@@ -12,7 +12,7 @@ import qualified Data.Vector as V import Moonlight.Triangulation import Moonlight.Triangulation.Dcel-import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.HandleDefs import Moonlight.Triangulation.Math import Moonlight.Triangulation.PointLocation import Moonlight.Triangulation.Removal
bench/support/BenchSupport.hs view
@@ -29,7 +29,7 @@   ( faceVertices   , vertexPoint   )-import Moonlight.Triangulation.Handles.HandleDefs (FaceId)+import Moonlight.Triangulation.Internal.HandleDefs (FaceId) import Moonlight.Triangulation.Types   ( Point (Point)   , Triangulation
docs/README.md view
@@ -6,6 +6,7 @@ | Question | Canonical owner | | --- | --- | | Which public operation should I call? | [Package operation index](../README.md#operations) |+| Which library token should I depend on? | [Consumer libraries](../README.md#consumer-libraries) | | What algebraic laws do meshes satisfy? | [Package algebraic contract](../README.md#algebraic-contract) | | What are the set-operation types? | [Package type signatures](../README.md#set-operation-types) | | Which execution schedule publishes each result? | [Mesh publication guide](./mesh-publication.md) |
docs/category-observatory/Moonlight/Triangulation/Exhibit/CategoryObservatory/Geometry.hs view
@@ -29,7 +29,7 @@     MeshVertex (..),     Position (..),   )-import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.HandleDefs   ( FaceId,     UndirectedEdgeId,     VertexId,
docs/category-observatory/Moonlight/Triangulation/Exhibit/CategoryObservatory/Identifier.hs view
@@ -21,7 +21,7 @@   ( CategoryMorphismKind (..),     CategoryObjectKind (..),   )-import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.HandleDefs   ( FaceId,     UndirectedEdgeId,     VertexId,
docs/category-observatory/Moonlight/Triangulation/Exhibit/CategoryObservatory/Model.hs view
@@ -26,7 +26,7 @@ import Data.Aeson (ToJSON) import GHC.Generics (Generic) import Moonlight.Triangulation.CellSet (CellSelectionError)-import Moonlight.Triangulation.Handles.HandleDefs (FaceId)+import Moonlight.Triangulation.Internal.HandleDefs (FaceId) import Moonlight.Triangulation.Types (BuildError) import Numeric.Natural (Natural) 
docs/persistence-rose/Moonlight/Triangulation/Exhibit/PersistenceRose.hs view
@@ -116,7 +116,7 @@     faceComponentsBy,     outerFaceFloodFill,   )-import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.HandleDefs   ( FaceId (..),     UndirectedEdgeId,     VertexId,
ffi/abi/Moonlight/Triangulation/Foreign/Mesh.hs view
@@ -36,7 +36,7 @@ import Moonlight.Triangulation.Foreign.Contract (CMesh, CObstruction) import Moonlight.Triangulation.Foreign.Obstruction (buildFailure) import Moonlight.Triangulation.Math (validatePoint)-import qualified Moonlight.Triangulation.Session as Session+import qualified Moonlight.Triangulation.Internal.Session as Session  delaunayF64 :: Ptr CDouble -> CSize -> Ptr (Ptr CMesh) -> Ptr CObstruction -> IO CUInt delaunayF64 coordinates rawCount output obstructionPointer =
moonlight-triangulation.cabal view
@@ -1,6 +1,6 @@ cabal-version:       3.4 name:                moonlight-triangulation-version:             1.4.0.2+version:             1.4.0.3 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@@ -10,13 +10,16 @@                      union, intersection and difference return triangulations                      and refinement composes after them rather than replacing                      them. One structure-of-arrays half-edge mesh carries the-                     constrained and unconstrained layers. A private finite-DCEL-                     and construction tower owns its handles, iterators,-                     circle-sweep build, incremental edits, constraint recovery,-                     and refinement. Public opt-in sublibraries expose the exact-                     scalar core, Voronoi and natural-neighbour dual, bounded-                     concurrent join interpreter, and versioned serialization-                     surface beside the main construction facade.+                     constrained and unconstrained layers. Private finite-DCEL+                     and construction kernels own trusted representations and+                     admitted fast paths. Public dcel and build sublibraries+                     expose their lawful immutable observations, typed queries,+                     construction, incremental edits, constraint recovery, and+                     refinement without duplicating those owners. Further opt-in+                     sublibraries expose the exact scalar core, Voronoi and+                     natural-neighbour dual, bounded concurrent join interpreter,+                     and versioned serialization surface beside the broad main+                     facade.                      On GHC 9.14, a public cell-complex component interprets                      admitted exact cell selections for Homology and Category                      and lowers exact Delaunay alpha filtrations into persistent@@ -103,7 +106,7 @@ source-repository this   type:     git   location: https://github.com/PaleRoses/moonlight.git-  tag:      moonlight-triangulation-1.4.0.2+  tag:      moonlight-triangulation-1.4.0.3   subdir:   moonlight-triangulation  flag warnings-as-errors@@ -134,11 +137,11 @@     ghc-options: -optlo-enable-new-pm=0  -- ── the tower ────────────────────────────────────────────────────────────────--- 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.+-- The implementation tower is core <- dcel-internal <- build-internal. Public+-- dcel and build components are restrictions of those owners: they reexport+-- lawful modules without copying implementations or publishing trusted+-- constructors. Dual branches directly from the DCEL owner; the apex owns only+-- the genuine overlaps. The graph is acyclic and uses no @.hs-boot@ files.  library core   import: shared-properties@@ -162,7 +165,7 @@     , vector >= 0.13 && < 0.14   ghc-options: -fexpose-all-unfoldings -library dcel+library dcel-internal   import: shared-properties   visibility: private   hs-source-dirs: src-dcel@@ -176,9 +179,11 @@     Moonlight.Triangulation.Valuation     Moonlight.Triangulation.Interop     Moonlight.Triangulation.Dcel+    Moonlight.Triangulation.Canonical     Moonlight.Triangulation.Payload     Moonlight.Triangulation.JoinSemilattice     Moonlight.Triangulation.Handles.HandleDefs+    Moonlight.Triangulation.Internal.HandleDefs     Moonlight.Triangulation.Handles.Dynamic     Moonlight.Triangulation.Handles.Iterators.CircularIterator     Moonlight.Triangulation.Handles.Iterators.DynamicIterators@@ -186,7 +191,9 @@     Moonlight.Triangulation.Handles.Iterators.HullIterator     Moonlight.Triangulation.PointLocation     Moonlight.Triangulation.Validation+    Moonlight.Triangulation.Internal.Validation     Moonlight.Triangulation.FloodFillIterator+    Moonlight.Triangulation.Internal.FloodFillIterator     Moonlight.Triangulation.IntersectionIterator     Moonlight.Triangulation.Internal.FaceProbe     Moonlight.Triangulation.Internal.CellSet@@ -223,6 +230,37 @@     , moonlight-triangulation:core   ghc-options: -fexpose-all-unfoldings +-- Public immutable-DCEL and exact-geometry surface. Every module is the module+-- compiled by dcel-internal; reexporting preserves one type and implementation+-- identity while leaving representation modules hidden.+library dcel+  import: shared-properties+  visibility: public+  reexported-modules:+    Moonlight.Triangulation.Alpha+    , Moonlight.Triangulation.Types+    , Moonlight.Triangulation.Math+    , Moonlight.Triangulation.Exact+    , Moonlight.Triangulation.CellSet+    , Moonlight.Triangulation.Region+    , Moonlight.Triangulation.Valuation+    , Moonlight.Triangulation.Interop+    , Moonlight.Triangulation.Dcel+    , Moonlight.Triangulation.Canonical+    , Moonlight.Triangulation.Payload+    , Moonlight.Triangulation.JoinSemilattice+    , Moonlight.Triangulation.Handles.HandleDefs+    , Moonlight.Triangulation.Handles.Dynamic+    , Moonlight.Triangulation.Handles.Iterators.DynamicIterators+    , Moonlight.Triangulation.Handles.Iterators.FixedIterators+    , Moonlight.Triangulation.Handles.Iterators.HullIterator+    , Moonlight.Triangulation.PointLocation+    , Moonlight.Triangulation.Validation+    , Moonlight.Triangulation.FloodFillIterator+    , Moonlight.Triangulation.IntersectionIterator+  build-depends:+    moonlight-triangulation:dcel-internal+ -- 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 of the alpha-birth lowering into@@ -244,17 +282,19 @@     , moonlight-algebra:abstract >= 0.1 && < 0.2     , moonlight-homology >= 0.1.0.2 && < 0.2     , moonlight-homology:cell-complex >= 0.1.0.2 && < 0.2-    , moonlight-triangulation:dcel+    , moonlight-triangulation:dcel-internal     , vector >= 0.13 && < 0.14 -library build+library build-internal   import: shared-properties   visibility: private   hs-source-dirs: src-build   exposed-modules:     Moonlight.Triangulation.BulkLoad+    Moonlight.Triangulation.Internal.BulkLoad     Moonlight.Triangulation.Removal     Moonlight.Triangulation.Session+    Moonlight.Triangulation.Internal.Session     Moonlight.Triangulation.Cdt     Moonlight.Triangulation.Refinement     Moonlight.Triangulation.SetAlgebra@@ -292,9 +332,25 @@     , vector >= 0.13 && < 0.14     , vector-algorithms >= 0.9 && < 0.10     , moonlight-triangulation:core-    , moonlight-triangulation:dcel+    , moonlight-triangulation:dcel-internal   ghc-options: -fexpose-all-unfoldings +-- Public construction surface over the private kernel. Trusted admitted-input+-- and local-transaction entries live only in build-internal; the modules below+-- are their typed, immutable restrictions.+library build+  import: shared-properties+  visibility: public+  reexported-modules:+    Moonlight.Triangulation.BulkLoad+    , Moonlight.Triangulation.Removal+    , Moonlight.Triangulation.Session+    , Moonlight.Triangulation.Cdt+    , Moonlight.Triangulation.Refinement+    , Moonlight.Triangulation.SetAlgebra+  build-depends:+    moonlight-triangulation:build-internal+ -- Concurrency is an effect boundary over the pure build planner. Keeping it in -- its own sublibrary prevents @async@ from infecting the geometry core. library parallel@@ -307,8 +363,8 @@     base >= 4.19 && < 5     , async >= 2.2 && < 2.3     , deepseq >= 1.5 && < 1.6-    , moonlight-triangulation:build-    , moonlight-triangulation:dcel+    , moonlight-triangulation:build-internal+    , moonlight-triangulation:dcel-internal   ghc-options: -fexpose-all-unfoldings  -- Its own sublibrary so that @binary@ and @bytestring@ stay out of the minimal@@ -328,7 +384,7 @@     , transformers >= 0.6 && < 0.7     , vector >= 0.13 && < 0.14     , moonlight-triangulation:core-    , moonlight-triangulation:dcel+    , moonlight-triangulation:dcel-internal   -- The @Binary@ instances for the identifier and point types are orphans by   -- construction: the types belong to @dcel@ and the class to @binary@, and the   -- whole purpose of this component is that neither one has to know about the@@ -351,7 +407,7 @@     , primitive >= 0.9 && < 0.10     , vector >= 0.13 && < 0.14     , moonlight-triangulation:core-    , moonlight-triangulation:dcel+    , moonlight-triangulation:dcel-internal   ghc-options: -fexpose-all-unfoldings  library@@ -366,6 +422,40 @@     Moonlight.Triangulation.Internal.Overlay.Arrangement     Moonlight.Triangulation.Internal.Overlay.Embedding     Moonlight.Triangulation.Internal.Overlay.Resident+  -- The default library is the broad safe surface. These are the exact modules+  -- compiled by the focused components, not facade copies; consumers that want+  -- a smaller dependency cone can name :dcel, :build, or :dual directly.+  reexported-modules:+    Moonlight.Triangulation.Alpha+    , Moonlight.Triangulation.Types+    , Moonlight.Triangulation.Math+    , Moonlight.Triangulation.Exact+    , Moonlight.Triangulation.CellSet+    , Moonlight.Triangulation.Region+    , Moonlight.Triangulation.Valuation+    , Moonlight.Triangulation.Interop+    , Moonlight.Triangulation.Dcel+    , Moonlight.Triangulation.Canonical+    , Moonlight.Triangulation.Payload+    , Moonlight.Triangulation.JoinSemilattice+    , Moonlight.Triangulation.Handles.HandleDefs+    , Moonlight.Triangulation.Handles.Dynamic+    , Moonlight.Triangulation.Handles.Iterators.DynamicIterators+    , Moonlight.Triangulation.Handles.Iterators.FixedIterators+    , Moonlight.Triangulation.Handles.Iterators.HullIterator+    , Moonlight.Triangulation.PointLocation+    , Moonlight.Triangulation.Validation+    , Moonlight.Triangulation.FloodFillIterator+    , Moonlight.Triangulation.IntersectionIterator+    , Moonlight.Triangulation.BulkLoad+    , Moonlight.Triangulation.Removal+    , Moonlight.Triangulation.Session+    , Moonlight.Triangulation.Cdt+    , Moonlight.Triangulation.Refinement+    , Moonlight.Triangulation.SetAlgebra+    , Moonlight.Triangulation.Voronoi+    , Moonlight.Triangulation.Voronoi.Handles+    , Moonlight.Triangulation.Interpolation   other-modules:     Moonlight.Triangulation.Internal.Minkowski.Convex     Moonlight.Triangulation.Internal.Minkowski.Types@@ -376,8 +466,8 @@     , deepseq >= 1.5 && < 1.6     , vector >= 0.13 && < 0.14     , moonlight-triangulation:core-    , moonlight-triangulation:dcel-    , moonlight-triangulation:build+    , moonlight-triangulation:dcel-internal+    , moonlight-triangulation:build-internal     , moonlight-triangulation:dual   ghc-options: -fexpose-all-unfoldings @@ -398,8 +488,8 @@     , containers >= 0.8 && < 0.9     , vector >= 0.13 && < 0.14     , moonlight-triangulation-    , moonlight-triangulation:build-    , moonlight-triangulation:dcel+    , moonlight-triangulation:build-internal+    , moonlight-triangulation:dcel-internal  foreign-library moonlight-triangulation-c   import: shared-properties@@ -474,8 +564,8 @@     , deepseq >= 1.5 && < 1.6     , primitive >= 0.9 && < 0.10     , moonlight-triangulation:core-    , moonlight-triangulation:dcel-    , moonlight-triangulation:build+    , moonlight-triangulation:dcel-internal+    , moonlight-triangulation:build-internal     , moonlight-triangulation:dual  common triangulation-serialization-test-slice@@ -484,7 +574,7 @@     binary >= 0.8 && < 0.9     , bytestring >= 0.12 && < 0.13     , deepseq >= 1.5 && < 1.6-    , moonlight-triangulation:dcel+    , moonlight-triangulation:dcel-internal     , moonlight-triangulation:serialize  -- The operand meshes both algebra slices are stated over.  It sits at the@@ -511,20 +601,20 @@   build-depends:     containers >= 0.8 && < 0.9     , moonlight-triangulation:core-    , moonlight-triangulation:dcel+    , moonlight-triangulation:dcel-internal  common triangulation-minkowski-law-slice   other-modules: Moonlight.Triangulation.MinkowskiSpec   build-depends:     moonlight-triangulation:core-    , moonlight-triangulation:dcel+    , moonlight-triangulation:dcel-internal  -- 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 -- schedule it names: a replacement kernel carries its own copy of this. common triangulation-algebra-schedule-slice   other-modules: Moonlight.Triangulation.ScheduleAgreementSpec-  build-depends: moonlight-triangulation:build+  build-depends: moonlight-triangulation:build-internal  common triangulation-parallel-test-slice   other-modules: Moonlight.Triangulation.ParallelSpec@@ -546,9 +636,9 @@     , moonlight-homology >= 0.1.0.2 && < 0.2     , moonlight-homology:cell-complex >= 0.1.0.2 && < 0.2     , moonlight-triangulation:core-    , moonlight-triangulation:build+    , moonlight-triangulation:build-internal     , moonlight-triangulation:cell-complex-    , moonlight-triangulation:dcel+    , moonlight-triangulation:dcel-internal     , tasty >= 1.4 && < 1.6     , tasty-hunit >= 0.10 && < 0.11     , vector >= 0.13 && < 0.14@@ -569,6 +659,20 @@     Moonlight.Triangulation.Example.SetAlgebra   build-depends: containers >= 0.8 && < 0.9 +-- A real external shape over only the two focused public components. It proves+-- that immutable observation and construction/editing compose without either+-- the broad facade or a private implementation token.+test-suite moonlight-triangulation-public-components-test+  import: shared-properties+  type: exitcode-stdio-1.0+  main-is: Main.hs+  hs-source-dirs: test/public-components+  build-depends:+    base >= 4.19 && < 5+    , vector >= 0.13 && < 0.14+    , moonlight-triangulation:dcel+    , moonlight-triangulation:build+ -- One pure, source-grounded projection of the alpha, dual, interpolation, -- planar-algebra, and persistence owners. The test shares the internal exhibit -- module; neither component creates a public visualization API.@@ -590,7 +694,7 @@     , moonlight-homology >= 0.1.0.2 && < 0.2     , moonlight-triangulation     , moonlight-triangulation:cell-complex-    , moonlight-triangulation:dcel+    , moonlight-triangulation:dcel-internal     , moonlight-triangulation:dual     , tasty >= 1.4 && < 1.6     , tasty-hunit >= 0.10 && < 0.11@@ -719,19 +823,19 @@  common triangulation-benchmark-support-slice   other-modules: BenchSupport-  build-depends: moonlight-triangulation:dcel+  build-depends: moonlight-triangulation:dcel-internal  common triangulation-build-benchmark-slice   other-modules: Moonlight.Triangulation.BuildBench   build-depends:     primitive >= 0.9 && < 0.10-    , moonlight-triangulation:dcel-    , moonlight-triangulation:build+    , moonlight-triangulation:dcel-internal+    , moonlight-triangulation:build-internal     , moonlight-triangulation:ffi  common triangulation-dcel-benchmark-slice   other-modules: Moonlight.Triangulation.DcelBench-  build-depends: moonlight-triangulation:dcel+  build-depends: moonlight-triangulation:dcel-internal  common triangulation-dual-benchmark-slice   other-modules: Moonlight.Triangulation.DualBench@@ -742,15 +846,15 @@   build-depends:     containers >= 0.8 && < 0.9     , moonlight-triangulation:core-    , moonlight-triangulation:dcel-    , moonlight-triangulation:build+    , moonlight-triangulation:dcel-internal+    , moonlight-triangulation:build-internal  common triangulation-region-benchmark-slice   other-modules: Moonlight.Triangulation.RegionBench   build-depends:     containers >= 0.8 && < 0.9     , moonlight-triangulation:core-    , moonlight-triangulation:dcel+    , moonlight-triangulation:dcel-internal  -- The package-owned external construction board deliberately uses tasty-bench: -- unlike the native work-counter slices above, its only lawful common metric@@ -763,8 +867,8 @@   build-depends:     base >= 4.19 && < 5     , deepseq >= 1.5 && < 1.6-    , moonlight-triangulation:build-    , moonlight-triangulation:dcel+    , moonlight-triangulation:build-internal+    , moonlight-triangulation:dcel-internal  executable moonlight-triangulation-delaunay-compare   import: triangulation-delaunay-compare-properties@@ -825,9 +929,9 @@     , moonlight-category:simplicial >= 1.1.0.0 && < 1.2     , moonlight-homology:cell-category >= 0.1.0.2 && < 0.2     , moonlight-homology:cell-complex >= 0.1.0.2 && < 0.2-    , moonlight-triangulation:build+    , moonlight-triangulation:build-internal     , moonlight-triangulation:cell-complex-    , moonlight-triangulation:dcel+    , moonlight-triangulation:dcel-internal     , transformers >= 0.5 && < 0.7     , vector >= 0.13 && < 0.14 @@ -848,7 +952,7 @@     , moonlight-homology >= 0.1.0.2 && < 0.2     , moonlight-triangulation     , moonlight-triangulation:cell-complex-    , moonlight-triangulation:dcel+    , moonlight-triangulation:dcel-internal     , moonlight-triangulation:dual     , text >= 2.0 && < 2.2     , transformers >= 0.5 && < 0.7@@ -900,8 +1004,8 @@     , vector >= 0.13 && < 0.14     , primitive >= 0.9 && < 0.10     , moonlight-triangulation-    , moonlight-triangulation:dcel-    , moonlight-triangulation:build+    , moonlight-triangulation:dcel-internal+    , moonlight-triangulation:build-internal     , moonlight-triangulation:dual  -- The alpha comparison is deliberately opt-in: ordinary scorecard listing and@@ -937,9 +1041,9 @@     , deepseq >= 1.4 && < 1.6     , vector >= 0.13 && < 0.14     , moonlight-homology >= 0.1 && < 0.2-    , moonlight-triangulation:build+    , moonlight-triangulation:build-internal     , moonlight-triangulation:cell-complex-    , moonlight-triangulation:dcel+    , moonlight-triangulation:dcel-internal  -- One exact Delaunay alpha filtration versus independently restricting and -- reducing the same chain complex at every critical radius. Construction of@@ -961,9 +1065,9 @@     , containers >= 0.6 && < 0.9     , deepseq >= 1.5 && < 1.6     , moonlight-homology >= 0.1.0.2 && < 0.2-    , moonlight-triangulation:build+    , moonlight-triangulation:build-internal     , moonlight-triangulation:cell-complex-    , moonlight-triangulation:dcel+    , moonlight-triangulation:dcel-internal     , vector >= 0.13 && < 0.14  benchmark moonlight-triangulation-build-bench
src-build/Moonlight/Triangulation/BulkLoad.hs view
@@ -1,12 +1,4 @@-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeApplications #-}-{-# OPTIONS_GHC -O3 -fllvm -optlo-O3 -optlc-O3 #-}---- | Generation. @delaunay@ builds a mesh from a whole site set by circle sweep;--- the insertion verbs extend an existing mesh one site at a time.+-- | Whole-support construction and persistent incremental insertion. module Moonlight.Triangulation.BulkLoad   ( empty   , clear@@ -14,929 +6,9 @@   , delaunayGeometry   , DuplicatePayloadPolicy (..)   , delaunayFromCoordinates-  , delaunayFromAdmittedDistinctAscending   , insert   , insertAt   , insertMany   ) where -import Control.Monad (forM_)-import Control.Monad.ST (ST, runST)-import qualified Data.IntSet as IntSet-import Data.Primitive.PrimArray-  ( MutablePrimArray-  , newPrimArray-  , readPrimArray-  , unsafeFreezePrimArray-  , writePrimArray-  )-import qualified Data.Vector as V-import qualified Data.Vector.Unboxed as U-import qualified Data.Vector.Unboxed.Mutable as MUV-import Data.Word (Word32)-import Moonlight.Triangulation.Dcel (numInnerFaces, numVertices)-import Moonlight.Triangulation.Handles.HandleDefs (DirectedEdgeId (..), FaceId (..), VertexId (..))-import Moonlight.Triangulation.Internal.BoxedPaged (boxedFromVector, boxedUpdate, emptyBoxedPaged)-import Moonlight.Triangulation.Internal.Capacity (ensureCapacity)-import Moonlight.Triangulation.Insertion (insertExistingVertexAtLocation, insertVertexAtPoint)-import Moonlight.Triangulation.Internal.Location (MutableLocation (..))-import Moonlight.Triangulation.Internal.Mutable-import Moonlight.Triangulation.Internal.OperationState-  ( Counter (..)-  , OperationState-  , addCounter-  , freezeBuildStats-  , newOperationState-  , setCounter-  )-import Moonlight.Triangulation.Internal.CircleSweep-  ( RadiallyOrderedArena-  , circleSweepInsert-  , radiallyOrderArena-  , radiallyOrderGeometry-  )-import Moonlight.Triangulation.Internal.PointIndex-  ( MutablePointIndex-  , emptyPointIndex-  , newMutablePointIndex-  , resolveMutablePoint-  , seedMutablePointIndex-  )-import Moonlight.Triangulation.Math (canonicalCoordinate, validatePoint)-import Moonlight.Triangulation.PointLocation (locatePointWithHint)-import Moonlight.Triangulation.Internal.Probe (Probe (..))-import Moonlight.Triangulation.Internal.Representation (Triangulation (..))-import Moonlight.Triangulation.Internal.PackedIndex (noIndex)-import Moonlight.Triangulation.Internal.Paged (TransactionShape (DenseTransaction, LocalTransaction), emptyPaged, fromVector)-import Moonlight.Triangulation.Internal.Transaction (runTransaction)-import Moonlight.Triangulation.Types---- | The vertexless triangulation: the outer face and nothing else. This is the--- canonical origin of the type — bulk loading, incremental insertion and--- refinement all agree with growing this value.-empty-  :: ElementDefaults directed undirected face-  -> Triangulation mode vertex directed undirected face-empty defaults@ElementDefaults{defaultDirectedEdgeData, defaultUndirectedEdgeData, defaultFaceData} =-  Triangulation-    { triPointX = emptyPaged-    , triPointY = emptyPaged-    , triPointIndex = emptyPointIndex-    , triVertexOut = emptyPaged-    , triVertexData = emptyBoxedPaged Nothing-    , triHalfTopology = emptyPaged-    , triDirectedData = emptyBoxedPaged (Just defaultDirectedEdgeData)-    , triUndirectedData = emptyBoxedPaged (Just defaultUndirectedEdgeData)-    , triFaceEdge = fromVector noIndex (U.singleton noIndex)-    , triFaceData = boxedFromVector (Just defaultFaceData) (V.singleton defaultFaceData)-    , triConstraint = emptyPaged-    , triConstraintCount = 0-    , triConstraintEdges = IntSet.empty-    , triSeamFrontier = Nothing-    , triElementDefaults = defaults-    }---- | Discard every vertex while retaining the element defaults the--- triangulation was built with.-clear-  :: Triangulation mode vertex directed undirected face-  -> Triangulation mode vertex directed undirected face-clear = empty . triElementDefaults---- | How a canonical bulk source combines payloads whose exact coordinates--- coincide. Geometry identity is settled independently by the point index.-data DuplicatePayloadPolicy vertex-  = KeepFirstPayload-  | CombineDuplicatePayload !(vertex -> vertex -> vertex)---- | The two lawful ingress sections for the one bulk construction engine.--- Raw callers need coordinate admission and duplicate classification; an exact--- SiteSet has already established canonical, distinct, lexicographically--- ascending 'QueryPoint's. The latter remains package-internal through the--- private build component, rather than becoming a second constructor.-data BulkInput vertex-  = RawPossiblyDuplicate-      !(Int -> Point)-      !(DuplicatePayloadPolicy vertex)-  | AdmittedDistinctAscending !(Int -> QueryPoint)---- | The local identity verdict for one input position. A fresh verdict carries--- the canonical coordinates already admitted to the mutable DCEL, so ingress--- never rereads its own writes merely to accumulate the sweep centre.-data PositionClaim-  = ResidentPosition !Int-  | FreshPosition !Int !Double !Double---- | Build a finite Delaunay DCEL while preserving the first input payload at--- every duplicate position. The returned mapping relates every input slot to--- the canonical stored vertex.-delaunay-  :: forall vertex directed undirected face-   . HasPosition vertex-  => ElementDefaults directed undirected face-  -> V.Vector vertex-  -> Either BuildError (BuildResult 'Unconstrained vertex directed undirected face)-delaunay defaults input =-  buildDelaunayFromSource-    defaults-    (V.length input)-    (RawPossiblyDuplicate (position . (input V.!)) KeepFirstPayload)-    (input V.!)---- | Build the geometry-only Delaunay triangulation of a coordinate vector.--- Exact duplicate positions collapse to one site. Use 'delaunay' or--- 'delaunayFromCoordinates' when vertex annotations or the input-to-vertex--- mapping are part of the result.-delaunayGeometry-  :: V.Vector Point-  -> Either BuildError (DelaunayTriangulation ())-delaunayGeometry coordinates-  | inputCount == 0 = Right (empty unitElementDefaults)-  | otherwise = do-    V.iforM_ coordinates (\index point -> () <$ validatePoint (Just index) point)-    ensureCapacity inputCount-    let !canonicalArena =-          U.generate inputCount $ \index ->-            case coordinates V.! index of-              Point rawX rawY ->-                ( 0-                , canonicalCoordinate rawX-                , canonicalCoordinate rawY-                , fromIntegral index-                )-        (!inputSumX, !inputSumY) =-          U.foldl'-            (\(!sumX, !sumY) (_, x, y, _) -> (sumX + x, sumY + y))-            (0, 0)-            canonicalArena-    runST $ do-      inputArena <- U.unsafeThaw canonicalArena-      let !inputScale = recip (fromIntegral inputCount)-          !inputCenterX = inputSumX * inputScale-          !inputCenterY = inputSumY * inputScale-          (distanceArena, pointXMutable, pointYMutable, inputOrder) =-            MUV.unzip4 inputArena-      assignGeometryRadialDistances-        distanceArena-        pointXMutable-        pointYMutable-        inputCenterX-        inputCenterY-      distances <- U.unsafeFreeze distanceArena-      pointXs <- U.unsafeFreeze pointXMutable-      pointYs <- U.unsafeFreeze pointYMutable-      orderedInputArena <--        radiallyOrderGeometry distances pointXs pointYs inputOrder-      defaultedOutcome <--        newMutableDcelWithVertexDefault-          ()-          unitElementDefaults-          (planarDcelCapacity inputCount)-      case defaultedOutcome of-        Left failure -> pure (Left failure)-        Right defaultedMutable -> do-          let !mutable = defaultedVertexDcel defaultedMutable-              !dense = defaultedDenseVertexDcel defaultedMutable-          operation <- newOperationState (halfEdgeCapacity mutable)-          unique <- appendSortedGeometry defaultedMutable pointXs pointYs inputOrder-          orderedArena <--            if unique == inputCount-              then pure orderedInputArena-              else do-                let !scale = recip (fromIntegral unique)-                    !uniqueOrder = MUV.unsafeSlice 0 unique inputOrder-                (sumX, sumY) <- sumGeometryVertices dense uniqueOrder-                uniqueArena <--                  fillRadialArena-                    mutable-                    (sumX * scale)-                    (sumY * scale)-                    (MUV.unsafeRead uniqueOrder)-                    unique-                radiallyOrderArena uniqueArena-          inserted <- circleSweepInsert mutable operation orderedArena-          case inserted of-            Left failure -> pure (Left failure)-            Right _ -> freezeTriangulation mutable- where-  !inputCount = V.length coordinates--  assignGeometryRadialDistances-    :: forall s-     . MUV.MVector s Double-    -> MUV.MVector s Double-    -> MUV.MVector s Double-    -> Double-    -> Double-    -> ST s ()-  assignGeometryRadialDistances distances pointXs pointYs centerX centerY =-    MUV.imapM_-      (\index _ -> do-        x <- MUV.unsafeRead pointXs index-        y <- MUV.unsafeRead pointYs index-        let !deltaX = centerX - x-            !deltaY = centerY - y-        MUV.unsafeWrite distances index (deltaX * deltaX + deltaY * deltaY)-      )-      distances--  appendSortedGeometry-    :: forall s-     . DefaultedVertexDcel s () () () ()-    -> U.Vector Double-    -> U.Vector Double-    -> MUV.MVector s Word32-    -> ST s Int-  appendSortedGeometry defaultedMutable pointXs pointYs order-    | inputCount == 0 = pure 0-    | otherwise = do-        firstInput <- MUV.unsafeRead order 0-        let !x = U.unsafeIndex pointXs (fromIntegral firstInput)-            !y = U.unsafeIndex pointYs (fromIntegral firstInput)-        denseWriteFreshDefaultPoint defaultedMutable 0 x y-        MUV.unsafeWrite order 0 0-        (_, _, unique) <--          MUV.foldM'-            (\(!previousX, !previousY, !uniqueCount) nextInput ->-               let !nextX = U.unsafeIndex pointXs (fromIntegral nextInput)-                   !nextY = U.unsafeIndex pointYs (fromIntegral nextInput)-                in if nextX == previousX && nextY == previousY-                     then pure (previousX, previousY, uniqueCount)-                     else do-                       denseWriteFreshDefaultPoint defaultedMutable uniqueCount nextX nextY-                       MUV.unsafeWrite order uniqueCount (fromIntegral uniqueCount)-                       pure (nextX, nextY, uniqueCount + 1)-            )-            (x, y, 1)-            (MUV.unsafeSlice 1 (inputCount - 1) order)-        unique <$ denseCommitFreshDefaultPoints defaultedMutable unique--  sumGeometryVertices-    :: forall s-     . DenseMutableDcel s () () () ()-    -> MUV.MVector s Word32-    -> ST s (Double, Double)-  sumGeometryVertices dense vertices =-    MUV.foldM'-      (\(!sumX, !sumY) raw -> do-         x <- denseReadPointX dense (fromIntegral raw)-         y <- denseReadPointY dense (fromIntegral raw)-         pure (sumX + x, sumY + y)-      )-      (0, 0)-      vertices---- | Canonical construction from separate geometry and annotation sources.--- The coordinate vector remains the only geometry in ingress; payloads never--- acquire a fabricated 'HasPosition' instance merely to reach the loader.-delaunayFromCoordinates-  :: forall vertex directed undirected face-   . ElementDefaults directed undirected face-  -> V.Vector (Point)-  -> V.Vector vertex-  -> DuplicatePayloadPolicy vertex-  -> Either BuildError (BuildResult 'Unconstrained vertex directed undirected face)-delaunayFromCoordinates defaults coordinates payloads duplicatePolicy-  | coordinateCount /= payloadCount =-      Left (CoordinatePayloadCountMismatch coordinateCount payloadCount)-  | otherwise =-      buildDelaunayFromSource-        defaults-        coordinateCount-        (RawPossiblyDuplicate (coordinates V.!) duplicatePolicy)-        (payloads V.!)- where-  !coordinateCount = V.length coordinates-  !payloadCount = V.length payloads---- | Construction from an already-admitted exact site section. The caller--- supplies Map-ascending, distinct 'QueryPoint's; this skips only raw--- admission, duplicate classification, and unique-site recentering. It still--- derives the radial execution order and runs the one circle sweep.-delaunayFromAdmittedDistinctAscending-  :: forall vertex directed undirected face-   . ElementDefaults directed undirected face-  -> V.Vector (QueryPoint, vertex)-  -> Either BuildError (BuildResult 'Unconstrained vertex directed undirected face)-delaunayFromAdmittedDistinctAscending defaults input =-  buildDelaunayFromSource-    defaults-    (V.length input)-    (AdmittedDistinctAscending (\index -> fst (input V.! index)))-    (\index -> snd (input V.! index))-{-# INLINE delaunayFromAdmittedDistinctAscending #-}--buildDelaunayFromSource-  :: forall vertex directed undirected face-   . ElementDefaults directed undirected face-  -> Int-  -> BulkInput vertex-  -> (Int -> vertex)-  -> Either BuildError (BuildResult 'Unconstrained vertex directed undirected face)-buildDelaunayFromSource defaults inputCount input payloadAtInput = do-  ensureCapacity inputCount-  runST $ do-    inputArena <- MUV.new inputCount-    admitted <- initializeInputArena inputArena-    case admitted of-      Left failure -> pure (Left failure)-      Right (inputSumX, inputSumY) -> do-        mutable <- newMutableDcel defaults (planarDcelCapacity inputCount)-        operation <- newOperationState (halfEdgeCapacity mutable)-        (mapping, unique, orderedArena) <--          case input of-            RawPossiblyDuplicate pointAtInput duplicatePolicy ->-              prepareRawInput-                inputArena-                mutable-                pointAtInput-                duplicatePolicy-                inputSumX-                inputSumY-            AdmittedDistinctAscending _ ->-              prepareAdmittedDistinctInput inputArena mutable inputSumX inputSumY-        setCounter operation CounterInputPoints inputCount-        setCounter operation CounterUniquePoints unique-        setCounter operation CounterDuplicatePoints (inputCount - unique)-        inserted <--          if unique == 0-            then pure (Right 0)-            else 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-                        }-                  )- where-  -- Raw sources validate and canonicalize at this one ingress. Admitted-  -- sources merely project their retained QueryPoint evidence into the sweep-  -- arena; neither route reconstructs coordinate identity downstream.-  initializeInputArena-    :: forall s-     . MUV.MVector s (Double, Double, Double, Word32)-    -> ST s (Either BuildError (Double, Double))-  initializeInputArena arena =-    case input of-      RawPossiblyDuplicate pointAtInput _ ->-        initializeRawInputArena arena pointAtInput 0 0 0-      AdmittedDistinctAscending pointAtInput ->-        initializeAdmittedInputArena arena pointAtInput--  initializeRawInputArena-    :: forall s-     . MUV.MVector s (Double, Double, Double, Word32)-    -> (Int -> Point)-    -> Int-    -> Double-    -> Double-    -> ST s (Either BuildError (Double, Double))-  initializeRawInputArena arena pointAtInput !index !sumX !sumY-    | index >= inputCount = pure (Right (sumX, sumY))-    | otherwise =-        case validatePoint (Just index) (pointAtInput index) of-          Left failure -> pure (Left failure)-          Right admitted ->-            case queryPointValue admitted of-              Point x y -> do-                MUV.unsafeWrite arena index (0, x, y, fromIntegral index)-                initializeRawInputArena-                  arena-                  pointAtInput-                  (index + 1)-                  (sumX + x)-                  (sumY + y)--  initializeAdmittedInputArena-    :: forall s-     . MUV.MVector s (Double, Double, Double, Word32)-    -> (Int -> QueryPoint)-    -> ST s (Either BuildError (Double, Double))-  initializeAdmittedInputArena arena pointAtInput =-    Right-      <$> MUV.ifoldM'-        (\(!sumX, !sumY) index _ ->-           case queryPointValue (pointAtInput index) of-             Point x y -> do-               MUV.unsafeWrite arena index (0, x, y, fromIntegral index)-               pure (sumX + x, sumY + y)-        )-        (0, 0)-        arena--  prepareRawInput-    :: forall s-     . MUV.MVector s (Double, Double, Double, Word32)-    -> MutableDcel s vertex directed undirected face-    -> (Int -> Point)-    -> DuplicatePayloadPolicy vertex-    -> Double-    -> Double-    -> ST s (MutablePrimArray s Word32, Int, RadiallyOrderedArena s)-  prepareRawInput inputArena mutable pointAtInput duplicatePolicy inputSumX inputSumY = do-    let !inputScale = if inputCount == 0 then 0 else recip (fromIntegral inputCount)-        !inputCenterX = inputSumX * inputScale-        !inputCenterY = inputSumY * inputScale-    assignRadialDistances inputArena inputCenterX inputCenterY 0-    orderedInputArena <- radiallyOrderArena inputArena-    mapping <- newPrimArray inputCount-    unique <- classifySortedInputs mapping inputArena-    (sumX, sumY) <--      appendClassifiedInputs mutable mapping pointAtInput duplicatePolicy 0 0 0-    orderedArena <--      if unique == inputCount-        then pure orderedInputArena-        else do-          let !scale = recip (fromIntegral unique)-              !uniqueArena = MUV.unsafeSlice 0 unique inputArena-          rewriteCompactedArena-            mapping-            (sumX * scale)-            (sumY * scale)-            uniqueArena-            0-          radiallyOrderArena uniqueArena-    pure (mapping, unique, orderedArena)--  prepareAdmittedDistinctInput-    :: forall s-     . MUV.MVector s (Double, Double, Double, Word32)-    -> MutableDcel s vertex directed undirected face-    -> Double-    -> Double-    -> ST s (MutablePrimArray s Word32, Int, RadiallyOrderedArena s)-  prepareAdmittedDistinctInput inputArena mutable inputSumX inputSumY = do-    mapping <- newPrimArray inputCount-    appendAdmittedDistinctInputs mutable mapping inputArena-    let !inputScale = if inputCount == 0 then 0 else recip (fromIntegral inputCount)-        !inputCenterX = inputSumX * inputScale-        !inputCenterY = inputSumY * inputScale-    assignRadialDistances inputArena inputCenterX inputCenterY 0-    orderedArena <- radiallyOrderArena inputArena-    pure (mapping, inputCount, orderedArena)--  -- An admitted SiteSet is already distinct. Sequential append therefore-  -- transports the identity class map directly into authoritative vertex ids,-  -- without reopening duplicate policy or recentering a compacted arena.-  appendAdmittedDistinctInputs-    :: forall s-     . MutableDcel s vertex directed undirected face-    -> MutablePrimArray s Word32-    -> MUV.MVector s (Double, Double, Double, Word32)-    -> ST s ()-  appendAdmittedDistinctInputs mutable mapping arena =-    MUV.imapM_-      (\index (_, x, y, _) -> do-         vertex <- appendVertexCoordinates mutable x y (payloadAtInput index)-         writePrimArray mapping index (fromIntegral vertex)-         MUV.unsafeWrite arena index (0, x, y, fromIntegral vertex)-      )-      arena--  -- The all-input centre seeds the first radial ordering. Duplicate-  -- equivalence classes become adjacent in that total order; after descent,-  -- the compacted section is recentered over unique sites and ordered once-  -- more only when required.-  assignRadialDistances-    :: forall s-     . MUV.MVector s (Double, Double, Double, Word32)-    -> Double-    -> Double-    -> Int-    -> ST s ()-  assignRadialDistances arena centerX centerY !index-    | index >= inputCount = pure ()-    | otherwise = do-        (_, x, y, inputOrdinal) <- MUV.unsafeRead arena index-        let !deltaX = centerX - x-            !deltaY = centerY - y-        MUV.unsafeWrite arena index (deltaX * deltaX + deltaY * deltaY, x, y, inputOrdinal)-        assignRadialDistances arena centerX centerY (index + 1)--  -- Classify the sorted local sections by exact canonical position. The-  -- mapping first names each class by its earliest input slot. Compacting the-  -- unique radial representatives in place cannot overwrite an unread slot.-  classifySortedInputs-    :: forall s-     . MutablePrimArray s Word32-    -> MUV.MVector s (Double, Double, Double, Word32)-    -> ST s Int-  classifySortedInputs mapping arena-    | inputCount == 0 = pure 0-    | otherwise = do-        first@(_, firstX, firstY, firstInput) <- MUV.unsafeRead arena 0-        writePrimArray mapping (fromIntegral firstInput) firstInput-        classifyFrom first firstX firstY firstInput 1 1-   where-    classifyFrom-      :: (Double, Double, Double, Word32)-      -> Double-      -> Double-      -> Word32-      -> Int-      -> Int-      -> ST s Int-    classifyFrom !_ !previousX !previousY !classInput !readIndex !uniqueCount-      | readIndex >= inputCount = pure uniqueCount-      | otherwise = do-          record@(_, x, y, inputOrdinal) <- MUV.unsafeRead arena readIndex-          if x == previousX && y == previousY-            then do-              writePrimArray mapping (fromIntegral inputOrdinal) classInput-              classifyFrom record previousX previousY classInput (readIndex + 1) uniqueCount-            else do-              writePrimArray mapping (fromIntegral inputOrdinal) inputOrdinal-              if uniqueCount == readIndex-                then pure ()-                else MUV.unsafeWrite arena uniqueCount record-              classifyFrom record x y inputOrdinal (readIndex + 1) (uniqueCount + 1)--  -- Materialize in original order, preserving the established handle-  -- assignment and duplicate-payload law. The equivalence mapping for a-  -- duplicate always points backward to an already materialized class owner.-  appendClassifiedInputs-    :: forall s-     . MutableDcel s vertex directed undirected face-    -> MutablePrimArray s Word32-    -> (Int -> Point)-    -> DuplicatePayloadPolicy vertex-    -> Int-    -> Double-    -> Double-    -> ST s (Double, Double)-  appendClassifiedInputs mutable mapping pointAtInput duplicatePolicy !index !sumX !sumY-    | index >= inputCount = pure (sumX, sumY)-    | otherwise = do-        classInput <- readPrimArray mapping index-        let !vertexData = payloadAtInput index-        if fromIntegral classInput == index-          then case pointAtInput index of-            Point x y -> do-              let !canonicalX = canonicalCoordinate x-                  !canonicalY = canonicalCoordinate y-              vertex <- appendVertexCoordinates mutable canonicalX canonicalY vertexData-              writePrimArray mapping index (fromIntegral vertex)-              appendClassifiedInputs-                mutable-                mapping-                pointAtInput-                duplicatePolicy-                (index + 1)-                (sumX + canonicalX)-                (sumY + canonicalY)-          else do-            resident <- fromIntegral <$> readPrimArray mapping (fromIntegral classInput)-            writePrimArray mapping index (fromIntegral resident)-            case duplicatePolicy of-              KeepFirstPayload -> pure ()-              CombineDuplicatePayload combine -> do-                residentData <- vertexDataAt mutable resident-                writeVertexData mutable resident (combine residentData vertexData)-            appendClassifiedInputs-              mutable-              mapping-              pointAtInput-              duplicatePolicy-              (index + 1)-              sumX-              sumY--  -- Once duplicates have shortened the vertex arena, translate each compacted-  -- representative from its input-class name to its authoritative vertex and-  -- restate its radial key around the exact unique-site centre.-  rewriteCompactedArena-    :: forall s-     . MutablePrimArray s Word32-    -> Double-    -> Double-    -> MUV.MVector s (Double, Double, Double, Word32)-    -> Int-    -> ST s ()-  rewriteCompactedArena mapping centerX centerY arena !index-    | index >= MUV.length arena = pure ()-    | otherwise = do-        (_, x, y, classInput) <- MUV.unsafeRead arena index-        vertex <- readPrimArray mapping (fromIntegral classInput)-        let !deltaX = centerX - x-            !deltaY = centerY - y-        MUV.unsafeWrite arena index (deltaX * deltaX + deltaY * deltaY, x, y, vertex)-        rewriteCompactedArena mapping centerX centerY arena (index + 1)---- | Insert or replace a vertex payload. A payload at an existing position is--- overwritten without changing topology.------ The published mesh is independent of the one passed in. A singleton below--- ten thousand resident sites copies densely; larger bases publish through--- copy-on-write pages. A caller inserting a sequence wants one--- 'Moonlight.Triangulation.Session.withSession' over--- 'Moonlight.Triangulation.Session.insertVertex' instead — see 'insertAt'.-insert-  :: HasPosition vertex-  => Triangulation mode vertex directed undirected face-  -> vertex-  -> Either BuildError (InsertionResult mode vertex directed undirected face)-insert triangulation vertexData = insertAt triangulation (position vertexData) vertexData---- | Insert at a stated point. 'insert' is this with the point read out of the--- payload, which is what a caller holding only a payload wants; a caller that--- computed the point — a constraint split, a Steiner refinement — wants to say--- so rather than build a payload and hope the round trip through 'HasPosition'--- returns what it started with.------ This is one shaped transaction over a single insertion. Replacing a fold of--- it with one session is sound because the two agree on every mesh and differ--- only in how many intermediate meshes they publish. A fold publishes @k@--- meshes and pays a thaw for each, so it runs in Θ(n·k); the session pays one--- thaw and runs in O(k·log n) expected.-insertAt-  :: Triangulation mode vertex directed undirected face-  -> Point-  -> vertex-  -> Either BuildError (InsertionResult mode vertex directed undirected face)-insertAt triangulation rawPoint vertexData = do-  queryPoint <- validatePoint Nothing rawPoint-  case locatePointWithHint triangulation Nothing queryPoint of-    (OnVertex resident, walked) ->-      Right (replaceResidentPayload triangulation resident walked vertexData)-    (located, walked) -> do-      let transactionShape =-            if numVertices triangulation < 10_000-              then DenseTransaction-              else LocalTransaction-      ((vertex, disposition), frozen, stats) <--        runTransaction-          id-          transactionShape-          triangulation-          1-          (\mutable operation -> do-             addCounter operation CounterInputPoints 1-             inserted <--               case located of-                 -- A frozen degenerate-line location points at an arbitrary visible-                 -- segment, while the line extension interpreter requires a terminal-                 -- edge. The frozen section carries no terminal witness, so retain-                 -- the existing mutable line locator for this one non-lawful case.-                 OutsideConvexHull (Just _)-                   | numInnerFaces triangulation == 0 ->-                       insertVertexAtPoint @'ProbeOff mutable operation Nothing (queryPointValue queryPoint) vertexData-                 _ -> do-                   capacityOutcome <- ensurePointCapacity mutable 1-                   case capacityOutcome of-                     Left failure -> pure (Left failure)-                     Right () -> do-                       vertex <- appendVertex mutable (queryPointValue queryPoint) vertexData-                       let thawedLocation =-                             case located of-                               EmptyTriangulation -> MutableEmpty-                               OnEdge (DirectedEdgeId raw) -> MutableOnEdge (fromIntegral raw)-                               InFace (FaceId raw) -> MutableInFace (fromIntegral raw)-                               -- The frozen locator emits no edge only for a-                               -- singleton mesh. Its mutable interpreter ignores-                               -- this sentinel while constructing the second vertex.-                               OutsideConvexHull Nothing -> MutableOutsideHull 0-                               OutsideConvexHull (Just (DirectedEdgeId raw)) -> MutableOutsideHull (fromIntegral raw)-                       ((vertex, Inserted) <$) <$> insertExistingVertexAtLocation @'ProbeOff mutable operation vertex thawedLocation-             case inserted of-               Left failure -> pure (Left failure)-               Right (vertex, disposition) -> do-                 case disposition of-                   Inserted -> addCounter operation CounterUniquePoints 1-                   AlreadyPresent -> do-                     writeVertexData mutable vertex vertexData-                     addCounter operation CounterExistingPoints 1-                     addCounter operation CounterDuplicatePoints 1-                 pure (Right (vertex, disposition))-          )-      pure-        InsertionResult-          { insertionTriangulation = frozen-          , insertionVertex = VertexId (fromIntegral vertex)-          , insertionDisposition = disposition-          , insertionStats = withFrozenLocationStats walked stats-          }---- | Publish a payload replacement without opening a transaction.------ A position already resident changes exactly one thing: the payload slot the--- vertex already occupies. No coordinate, no half-edge, no constraint flag and--- no face record differs, so the five unboxed planes are the ones the argument--- already holds rather than copies taken out of it — which is what a thaw costs--- and what this exists to refuse. They are immutable values; nothing reached--- from here is a mutable buffer, and 'boxedUpdate' materializes a fresh page--- for the one it rewrites, leaving the argument's own directory intact.------ The location counters are the frozen walk's, not a thawed walk's. They--- describe the walk that actually ran.-replaceResidentPayload-  :: Triangulation mode vertex directed undirected face-  -> VertexId-  -> LocationStats-  -> vertex-  -> InsertionResult mode vertex directed undirected face-replaceResidentPayload triangulation resident@(VertexId raw) walked vertexData =-  InsertionResult-    { insertionTriangulation =-        triangulation-          { triVertexData =-              boxedUpdate (fromIntegral raw) vertexData (triVertexData triangulation)-          }-    , insertionVertex = resident-    , insertionDisposition = AlreadyPresent-    , insertionStats =-        withFrozenLocationStats-          walked-          emptyBuildStats-            { statInputPoints = 1-            , statExistingPoints = 1-            , statDuplicatePoints = 1-            }-    }---- | Add the frozen locator's observation to the local topology interpreter's--- operation-owned counters. Direct frozen-site insertion contributes no mutable--- walk; the degenerate fallback contributes its real mutable walk rather than--- having it erased from the published result.-withFrozenLocationStats :: LocationStats -> BuildStats -> BuildStats-withFrozenLocationStats walked stats =-  stats-    { statLocationWalkSteps = locationWalkSteps walked + statLocationWalkSteps stats-    , statLocationMaxWalk = max (locationWalkSteps walked) (statLocationMaxWalk stats)-    , statLocationFallbacks = (if locationUsedFallback walked then 1 else 0) + statLocationFallbacks stats-    }---- | Apply a batch in one page transaction. Duplicate positions are processed--- in input order, so their last payload wins exactly as repeated 'insert'--- calls would, while topology is inserted only once per new position.-insertMany-  :: forall mode vertex directed undirected face-   . HasPosition vertex-  => Triangulation mode vertex directed undirected face-  -> V.Vector vertex-  -> Either BuildError (BuildResult mode vertex directed undirected face)-insertMany triangulation input = do-  validateVertices input-  (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-     . MutableDcel s vertex directed undirected face-    -> OperationState s-    -> MutablePointIndex s-    -> MutablePrimArray s Word32-    -> MUV.MVector s Word32-    -> Int-    -> Double-    -> Double-    -> Int-    -> ST s (Either BuildError (Double, Double, Int))-  fill mutable operation table mapping freshBuffer !index !sumX !sumY !freshCount-    | index >= V.length input = pure (Right (sumX, sumY, freshCount))-    | otherwise = do-        let !vertexData = input V.! index-        claimed <- claimPosition mutable table (position vertexData) vertexData-        case claimed of-          Left failure -> pure (Left failure)-          Right (FreshPosition vertex canonicalX canonicalY) -> do-            writePrimArray mapping index (fromIntegral vertex)-            MUV.unsafeWrite freshBuffer freshCount (fromIntegral vertex)-            fill-              mutable-              operation-              table-              mapping-              freshBuffer-              (index + 1)-              (sumX + canonicalX)-              (sumY + canonicalY)-              (freshCount + 1)-          Right (ResidentPosition vertex) -> do-            writePrimArray mapping index (fromIntegral vertex)-            writeVertexData mutable vertex vertexData-            fill mutable operation table mapping freshBuffer (index + 1) sumX sumY freshCount---- | One packed radial record per swept vertex — the derived sort fields and--- the vertex handle, nothing else — filled straight from the coordinate--- arenas and consumed in place by the sweep. The squared distance is stated--- against the ingress-accumulated centre, in the widened comparison format.-fillRadialArena-  :: MutableDcel s vertex directed undirected face-  -> Double-  -> Double-  -> (Int -> ST s Word32)-  -> Int-  -> ST s (MUV.MVector s (Double, Double, Double, Word32))-fillRadialArena mutable centerX centerY lookupId count = do-  arena <- MUV.new count-  forM_ [0 .. count - 1] $ \index -> do-    raw <- lookupId index-    x <- readPointX mutable (fromIntegral raw)-    y <- readPointY mutable (fromIntegral raw)-    let !wideX = x-        !wideY = y-        !deltaX = centerX - wideX-        !deltaY = centerY - wideY-    MUV.unsafeWrite arena index (deltaX * deltaX + deltaY * deltaY, wideX, wideY, raw)-  pure arena---- | Claim a position for the vertex the arena would append next, or answer the--- vertex already holding it. The claim is written before the append, so the two--- must stay adjacent: nothing may consume a vertex slot in between.-claimPosition-  :: MutableDcel s vertex directed undirected face-  -> MutablePointIndex s-  -> Point-  -> vertex-  -> ST s (Either BuildError PositionClaim)-claimPosition mutable table rawPoint vertexData =-  case rawPoint of-    Point x y -> do-      let !canonicalX = canonicalCoordinate x-          !canonicalY = canonicalCoordinate y-      slot <- nextVertexSlot mutable-      let !candidate = nextVertexSlotIndex slot-      owner <--        resolveMutablePoint-          table-          (readPointX mutable)-          (readPointY mutable)-          canonicalX-          canonicalY-          candidate-      case owner of-        Left failure -> pure (Left failure)-        Right (Just existing) -> pure (Right (ResidentPosition existing))-        Right Nothing -> do-          vertex <- appendVertexCoordinatesAtSlot mutable slot canonicalX canonicalY vertexData-          pure (Right (FreshPosition vertex canonicalX canonicalY))--- | Enter the positions a batch inherits from the triangulation it extends, so--- that an input repeating one of them maps to the vertex already there.-seedPointTable-  :: MutableDcel s vertex directed undirected face-  -> MutablePointIndex s-  -> ST s (Either BuildError ())-seedPointTable mutable table = do-  existing <- pointCount mutable-  seedMutablePointIndex-    table-    existing-    (readPointX mutable)-    (readPointY mutable)--validateVertices :: HasPosition vertex => V.Vector vertex -> Either BuildError ()-validateVertices vertices =-  V.iforM_ vertices (\index vertexData -> validatePoint (Just index) (position vertexData))+import Moonlight.Triangulation.Internal.BulkLoad
src-build/Moonlight/Triangulation/Insertion.hs view
@@ -14,7 +14,7 @@   ) where  import Control.Monad.ST (ST)-import Moonlight.Triangulation.Handles.HandleDefs (VertexId (..))+import Moonlight.Triangulation.Internal.HandleDefs (VertexId (..)) import Moonlight.Triangulation.Internal.DcelOperations.Chain   ( extendLine   , lineToArea
+ src-build/Moonlight/Triangulation/Internal/BulkLoad.hs view
@@ -0,0 +1,942 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# OPTIONS_GHC -O3 -fllvm -optlo-O3 -optlc-O3 #-}++-- | Generation. @delaunay@ builds a mesh from a whole site set by circle sweep;+-- the insertion verbs extend an existing mesh one site at a time.+module Moonlight.Triangulation.Internal.BulkLoad+  ( empty+  , clear+  , delaunay+  , delaunayGeometry+  , DuplicatePayloadPolicy (..)+  , delaunayFromCoordinates+  , delaunayFromAdmittedDistinctAscending+  , insert+  , insertAt+  , insertMany+  ) where++import Control.Monad (forM_)+import Control.Monad.ST (ST, runST)+import qualified Data.IntSet as IntSet+import Data.Primitive.PrimArray+  ( MutablePrimArray+  , newPrimArray+  , readPrimArray+  , unsafeFreezePrimArray+  , writePrimArray+  )+import qualified Data.Vector as V+import qualified Data.Vector.Unboxed as U+import qualified Data.Vector.Unboxed.Mutable as MUV+import Data.Word (Word32)+import Moonlight.Triangulation.Dcel (numInnerFaces, numVertices)+import Moonlight.Triangulation.Internal.HandleDefs (DirectedEdgeId (..), FaceId (..), VertexId (..))+import Moonlight.Triangulation.Internal.BoxedPaged (boxedFromVector, boxedUpdate, emptyBoxedPaged)+import Moonlight.Triangulation.Internal.Capacity (ensureCapacity)+import Moonlight.Triangulation.Insertion (insertExistingVertexAtLocation, insertVertexAtPoint)+import Moonlight.Triangulation.Internal.Location (MutableLocation (..))+import Moonlight.Triangulation.Internal.Mutable+import Moonlight.Triangulation.Internal.OperationState+  ( Counter (..)+  , OperationState+  , addCounter+  , freezeBuildStats+  , newOperationState+  , setCounter+  )+import Moonlight.Triangulation.Internal.CircleSweep+  ( RadiallyOrderedArena+  , circleSweepInsert+  , radiallyOrderArena+  , radiallyOrderGeometry+  )+import Moonlight.Triangulation.Internal.PointIndex+  ( MutablePointIndex+  , emptyPointIndex+  , newMutablePointIndex+  , resolveMutablePoint+  , seedMutablePointIndex+  )+import Moonlight.Triangulation.Math (canonicalCoordinate, validatePoint)+import Moonlight.Triangulation.PointLocation (locatePointWithHint)+import Moonlight.Triangulation.Internal.Probe (Probe (..))+import Moonlight.Triangulation.Internal.Representation (Triangulation (..))+import Moonlight.Triangulation.Internal.PackedIndex (noIndex)+import Moonlight.Triangulation.Internal.Paged (TransactionShape (DenseTransaction, LocalTransaction), emptyPaged, fromVector)+import Moonlight.Triangulation.Internal.Transaction (runTransaction)+import Moonlight.Triangulation.Types++-- | The vertexless triangulation: the outer face and nothing else. This is the+-- canonical origin of the type — bulk loading, incremental insertion and+-- refinement all agree with growing this value.+empty+  :: ElementDefaults directed undirected face+  -> Triangulation mode vertex directed undirected face+empty defaults@ElementDefaults{defaultDirectedEdgeData, defaultUndirectedEdgeData, defaultFaceData} =+  Triangulation+    { triPointX = emptyPaged+    , triPointY = emptyPaged+    , triPointIndex = emptyPointIndex+    , triVertexOut = emptyPaged+    , triVertexData = emptyBoxedPaged Nothing+    , triHalfTopology = emptyPaged+    , triDirectedData = emptyBoxedPaged (Just defaultDirectedEdgeData)+    , triUndirectedData = emptyBoxedPaged (Just defaultUndirectedEdgeData)+    , triFaceEdge = fromVector noIndex (U.singleton noIndex)+    , triFaceData = boxedFromVector (Just defaultFaceData) (V.singleton defaultFaceData)+    , triConstraint = emptyPaged+    , triConstraintCount = 0+    , triConstraintEdges = IntSet.empty+    , triSeamFrontier = Nothing+    , triElementDefaults = defaults+    }++-- | Discard every vertex while retaining the element defaults the+-- triangulation was built with.+clear+  :: Triangulation mode vertex directed undirected face+  -> Triangulation mode vertex directed undirected face+clear = empty . triElementDefaults++-- | How a canonical bulk source combines payloads whose exact coordinates+-- coincide. Geometry identity is settled independently by the point index.+data DuplicatePayloadPolicy vertex+  = KeepFirstPayload+  | CombineDuplicatePayload !(vertex -> vertex -> vertex)++-- | The two lawful ingress sections for the one bulk construction engine.+-- Raw callers need coordinate admission and duplicate classification; an exact+-- SiteSet has already established canonical, distinct, lexicographically+-- ascending 'QueryPoint's. The latter remains package-internal through the+-- private build component, rather than becoming a second constructor.+data BulkInput vertex+  = RawPossiblyDuplicate+      !(Int -> Point)+      !(DuplicatePayloadPolicy vertex)+  | AdmittedDistinctAscending !(Int -> QueryPoint)++-- | The local identity verdict for one input position. A fresh verdict carries+-- the canonical coordinates already admitted to the mutable DCEL, so ingress+-- never rereads its own writes merely to accumulate the sweep centre.+data PositionClaim+  = ResidentPosition !Int+  | FreshPosition !Int !Double !Double++-- | Build a finite Delaunay DCEL while preserving the first input payload at+-- every duplicate position. The returned mapping relates every input slot to+-- the canonical stored vertex.+delaunay+  :: forall vertex directed undirected face+   . HasPosition vertex+  => ElementDefaults directed undirected face+  -> V.Vector vertex+  -> Either BuildError (BuildResult 'Unconstrained vertex directed undirected face)+delaunay defaults input =+  buildDelaunayFromSource+    defaults+    (V.length input)+    (RawPossiblyDuplicate (position . (input V.!)) KeepFirstPayload)+    (input V.!)++-- | Build the geometry-only Delaunay triangulation of a coordinate vector.+-- Exact duplicate positions collapse to one site. Use 'delaunay' or+-- 'delaunayFromCoordinates' when vertex annotations or the input-to-vertex+-- mapping are part of the result.+delaunayGeometry+  :: V.Vector Point+  -> Either BuildError (DelaunayTriangulation ())+delaunayGeometry coordinates+  | inputCount == 0 = Right (empty unitElementDefaults)+  | otherwise = do+    V.iforM_ coordinates (\index point -> () <$ validatePoint (Just index) point)+    ensureCapacity inputCount+    let !canonicalArena =+          U.generate inputCount $ \index ->+            case coordinates V.! index of+              Point rawX rawY ->+                ( 0+                , canonicalCoordinate rawX+                , canonicalCoordinate rawY+                , fromIntegral index+                )+        (!inputSumX, !inputSumY) =+          U.foldl'+            (\(!sumX, !sumY) (_, x, y, _) -> (sumX + x, sumY + y))+            (0, 0)+            canonicalArena+    runST $ do+      inputArena <- U.unsafeThaw canonicalArena+      let !inputScale = recip (fromIntegral inputCount)+          !inputCenterX = inputSumX * inputScale+          !inputCenterY = inputSumY * inputScale+          (distanceArena, pointXMutable, pointYMutable, inputOrder) =+            MUV.unzip4 inputArena+      assignGeometryRadialDistances+        distanceArena+        pointXMutable+        pointYMutable+        inputCenterX+        inputCenterY+      distances <- U.unsafeFreeze distanceArena+      pointXs <- U.unsafeFreeze pointXMutable+      pointYs <- U.unsafeFreeze pointYMutable+      orderedInputArena <-+        radiallyOrderGeometry distances pointXs pointYs inputOrder+      defaultedOutcome <-+        newMutableDcelWithVertexDefault+          ()+          unitElementDefaults+          (planarDcelCapacity inputCount)+      case defaultedOutcome of+        Left failure -> pure (Left failure)+        Right defaultedMutable -> do+          let !mutable = defaultedVertexDcel defaultedMutable+              !dense = defaultedDenseVertexDcel defaultedMutable+          operation <- newOperationState (halfEdgeCapacity mutable)+          unique <- appendSortedGeometry defaultedMutable pointXs pointYs inputOrder+          orderedArena <-+            if unique == inputCount+              then pure orderedInputArena+              else do+                let !scale = recip (fromIntegral unique)+                    !uniqueOrder = MUV.unsafeSlice 0 unique inputOrder+                (sumX, sumY) <- sumGeometryVertices dense uniqueOrder+                uniqueArena <-+                  fillRadialArena+                    mutable+                    (sumX * scale)+                    (sumY * scale)+                    (MUV.unsafeRead uniqueOrder)+                    unique+                radiallyOrderArena uniqueArena+          inserted <- circleSweepInsert mutable operation orderedArena+          case inserted of+            Left failure -> pure (Left failure)+            Right _ -> freezeTriangulation mutable+ where+  !inputCount = V.length coordinates++  assignGeometryRadialDistances+    :: forall s+     . MUV.MVector s Double+    -> MUV.MVector s Double+    -> MUV.MVector s Double+    -> Double+    -> Double+    -> ST s ()+  assignGeometryRadialDistances distances pointXs pointYs centerX centerY =+    MUV.imapM_+      (\index _ -> do+        x <- MUV.unsafeRead pointXs index+        y <- MUV.unsafeRead pointYs index+        let !deltaX = centerX - x+            !deltaY = centerY - y+        MUV.unsafeWrite distances index (deltaX * deltaX + deltaY * deltaY)+      )+      distances++  appendSortedGeometry+    :: forall s+     . DefaultedVertexDcel s () () () ()+    -> U.Vector Double+    -> U.Vector Double+    -> MUV.MVector s Word32+    -> ST s Int+  appendSortedGeometry defaultedMutable pointXs pointYs order+    | inputCount == 0 = pure 0+    | otherwise = do+        firstInput <- MUV.unsafeRead order 0+        let !x = U.unsafeIndex pointXs (fromIntegral firstInput)+            !y = U.unsafeIndex pointYs (fromIntegral firstInput)+        denseWriteFreshDefaultPoint defaultedMutable 0 x y+        MUV.unsafeWrite order 0 0+        (_, _, unique) <-+          MUV.foldM'+            (\(!previousX, !previousY, !uniqueCount) nextInput ->+               let !nextX = U.unsafeIndex pointXs (fromIntegral nextInput)+                   !nextY = U.unsafeIndex pointYs (fromIntegral nextInput)+                in if nextX == previousX && nextY == previousY+                     then pure (previousX, previousY, uniqueCount)+                     else do+                       denseWriteFreshDefaultPoint defaultedMutable uniqueCount nextX nextY+                       MUV.unsafeWrite order uniqueCount (fromIntegral uniqueCount)+                       pure (nextX, nextY, uniqueCount + 1)+            )+            (x, y, 1)+            (MUV.unsafeSlice 1 (inputCount - 1) order)+        unique <$ denseCommitFreshDefaultPoints defaultedMutable unique++  sumGeometryVertices+    :: forall s+     . DenseMutableDcel s () () () ()+    -> MUV.MVector s Word32+    -> ST s (Double, Double)+  sumGeometryVertices dense vertices =+    MUV.foldM'+      (\(!sumX, !sumY) raw -> do+         x <- denseReadPointX dense (fromIntegral raw)+         y <- denseReadPointY dense (fromIntegral raw)+         pure (sumX + x, sumY + y)+      )+      (0, 0)+      vertices++-- | Canonical construction from separate geometry and annotation sources.+-- The coordinate vector remains the only geometry in ingress; payloads never+-- acquire a fabricated 'HasPosition' instance merely to reach the loader.+delaunayFromCoordinates+  :: forall vertex directed undirected face+   . ElementDefaults directed undirected face+  -> V.Vector (Point)+  -> V.Vector vertex+  -> DuplicatePayloadPolicy vertex+  -> Either BuildError (BuildResult 'Unconstrained vertex directed undirected face)+delaunayFromCoordinates defaults coordinates payloads duplicatePolicy+  | coordinateCount /= payloadCount =+      Left (CoordinatePayloadCountMismatch coordinateCount payloadCount)+  | otherwise =+      buildDelaunayFromSource+        defaults+        coordinateCount+        (RawPossiblyDuplicate (coordinates V.!) duplicatePolicy)+        (payloads V.!)+ where+  !coordinateCount = V.length coordinates+  !payloadCount = V.length payloads++-- | Construction from an already-admitted exact site section. The caller+-- supplies Map-ascending, distinct 'QueryPoint's; this skips only raw+-- admission, duplicate classification, and unique-site recentering. It still+-- derives the radial execution order and runs the one circle sweep.+delaunayFromAdmittedDistinctAscending+  :: forall vertex directed undirected face+   . ElementDefaults directed undirected face+  -> V.Vector (QueryPoint, vertex)+  -> Either BuildError (BuildResult 'Unconstrained vertex directed undirected face)+delaunayFromAdmittedDistinctAscending defaults input =+  buildDelaunayFromSource+    defaults+    (V.length input)+    (AdmittedDistinctAscending (\index -> fst (input V.! index)))+    (\index -> snd (input V.! index))+{-# INLINE delaunayFromAdmittedDistinctAscending #-}++buildDelaunayFromSource+  :: forall vertex directed undirected face+   . ElementDefaults directed undirected face+  -> Int+  -> BulkInput vertex+  -> (Int -> vertex)+  -> Either BuildError (BuildResult 'Unconstrained vertex directed undirected face)+buildDelaunayFromSource defaults inputCount input payloadAtInput = do+  ensureCapacity inputCount+  runST $ do+    inputArena <- MUV.new inputCount+    admitted <- initializeInputArena inputArena+    case admitted of+      Left failure -> pure (Left failure)+      Right (inputSumX, inputSumY) -> do+        mutable <- newMutableDcel defaults (planarDcelCapacity inputCount)+        operation <- newOperationState (halfEdgeCapacity mutable)+        (mapping, unique, orderedArena) <-+          case input of+            RawPossiblyDuplicate pointAtInput duplicatePolicy ->+              prepareRawInput+                inputArena+                mutable+                pointAtInput+                duplicatePolicy+                inputSumX+                inputSumY+            AdmittedDistinctAscending _ ->+              prepareAdmittedDistinctInput inputArena mutable inputSumX inputSumY+        setCounter operation CounterInputPoints inputCount+        setCounter operation CounterUniquePoints unique+        setCounter operation CounterDuplicatePoints (inputCount - unique)+        inserted <-+          if unique == 0+            then pure (Right 0)+            else 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+                        }+                  )+ where+  -- Raw sources validate and canonicalize at this one ingress. Admitted+  -- sources merely project their retained QueryPoint evidence into the sweep+  -- arena; neither route reconstructs coordinate identity downstream.+  initializeInputArena+    :: forall s+     . MUV.MVector s (Double, Double, Double, Word32)+    -> ST s (Either BuildError (Double, Double))+  initializeInputArena arena =+    case input of+      RawPossiblyDuplicate pointAtInput _ ->+        initializeRawInputArena arena pointAtInput 0 0 0+      AdmittedDistinctAscending pointAtInput ->+        initializeAdmittedInputArena arena pointAtInput++  initializeRawInputArena+    :: forall s+     . MUV.MVector s (Double, Double, Double, Word32)+    -> (Int -> Point)+    -> Int+    -> Double+    -> Double+    -> ST s (Either BuildError (Double, Double))+  initializeRawInputArena arena pointAtInput !index !sumX !sumY+    | index >= inputCount = pure (Right (sumX, sumY))+    | otherwise =+        case validatePoint (Just index) (pointAtInput index) of+          Left failure -> pure (Left failure)+          Right admitted ->+            case queryPointValue admitted of+              Point x y -> do+                MUV.unsafeWrite arena index (0, x, y, fromIntegral index)+                initializeRawInputArena+                  arena+                  pointAtInput+                  (index + 1)+                  (sumX + x)+                  (sumY + y)++  initializeAdmittedInputArena+    :: forall s+     . MUV.MVector s (Double, Double, Double, Word32)+    -> (Int -> QueryPoint)+    -> ST s (Either BuildError (Double, Double))+  initializeAdmittedInputArena arena pointAtInput =+    Right+      <$> MUV.ifoldM'+        (\(!sumX, !sumY) index _ ->+           case queryPointValue (pointAtInput index) of+             Point x y -> do+               MUV.unsafeWrite arena index (0, x, y, fromIntegral index)+               pure (sumX + x, sumY + y)+        )+        (0, 0)+        arena++  prepareRawInput+    :: forall s+     . MUV.MVector s (Double, Double, Double, Word32)+    -> MutableDcel s vertex directed undirected face+    -> (Int -> Point)+    -> DuplicatePayloadPolicy vertex+    -> Double+    -> Double+    -> ST s (MutablePrimArray s Word32, Int, RadiallyOrderedArena s)+  prepareRawInput inputArena mutable pointAtInput duplicatePolicy inputSumX inputSumY = do+    let !inputScale = if inputCount == 0 then 0 else recip (fromIntegral inputCount)+        !inputCenterX = inputSumX * inputScale+        !inputCenterY = inputSumY * inputScale+    assignRadialDistances inputArena inputCenterX inputCenterY 0+    orderedInputArena <- radiallyOrderArena inputArena+    mapping <- newPrimArray inputCount+    unique <- classifySortedInputs mapping inputArena+    (sumX, sumY) <-+      appendClassifiedInputs mutable mapping pointAtInput duplicatePolicy 0 0 0+    orderedArena <-+      if unique == inputCount+        then pure orderedInputArena+        else do+          let !scale = recip (fromIntegral unique)+              !uniqueArena = MUV.unsafeSlice 0 unique inputArena+          rewriteCompactedArena+            mapping+            (sumX * scale)+            (sumY * scale)+            uniqueArena+            0+          radiallyOrderArena uniqueArena+    pure (mapping, unique, orderedArena)++  prepareAdmittedDistinctInput+    :: forall s+     . MUV.MVector s (Double, Double, Double, Word32)+    -> MutableDcel s vertex directed undirected face+    -> Double+    -> Double+    -> ST s (MutablePrimArray s Word32, Int, RadiallyOrderedArena s)+  prepareAdmittedDistinctInput inputArena mutable inputSumX inputSumY = do+    mapping <- newPrimArray inputCount+    appendAdmittedDistinctInputs mutable mapping inputArena+    let !inputScale = if inputCount == 0 then 0 else recip (fromIntegral inputCount)+        !inputCenterX = inputSumX * inputScale+        !inputCenterY = inputSumY * inputScale+    assignRadialDistances inputArena inputCenterX inputCenterY 0+    orderedArena <- radiallyOrderArena inputArena+    pure (mapping, inputCount, orderedArena)++  -- An admitted SiteSet is already distinct. Sequential append therefore+  -- transports the identity class map directly into authoritative vertex ids,+  -- without reopening duplicate policy or recentering a compacted arena.+  appendAdmittedDistinctInputs+    :: forall s+     . MutableDcel s vertex directed undirected face+    -> MutablePrimArray s Word32+    -> MUV.MVector s (Double, Double, Double, Word32)+    -> ST s ()+  appendAdmittedDistinctInputs mutable mapping arena =+    MUV.imapM_+      (\index (_, x, y, _) -> do+         vertex <- appendVertexCoordinates mutable x y (payloadAtInput index)+         writePrimArray mapping index (fromIntegral vertex)+         MUV.unsafeWrite arena index (0, x, y, fromIntegral vertex)+      )+      arena++  -- The all-input centre seeds the first radial ordering. Duplicate+  -- equivalence classes become adjacent in that total order; after descent,+  -- the compacted section is recentered over unique sites and ordered once+  -- more only when required.+  assignRadialDistances+    :: forall s+     . MUV.MVector s (Double, Double, Double, Word32)+    -> Double+    -> Double+    -> Int+    -> ST s ()+  assignRadialDistances arena centerX centerY !index+    | index >= inputCount = pure ()+    | otherwise = do+        (_, x, y, inputOrdinal) <- MUV.unsafeRead arena index+        let !deltaX = centerX - x+            !deltaY = centerY - y+        MUV.unsafeWrite arena index (deltaX * deltaX + deltaY * deltaY, x, y, inputOrdinal)+        assignRadialDistances arena centerX centerY (index + 1)++  -- Classify the sorted local sections by exact canonical position. The+  -- mapping first names each class by its earliest input slot. Compacting the+  -- unique radial representatives in place cannot overwrite an unread slot.+  classifySortedInputs+    :: forall s+     . MutablePrimArray s Word32+    -> MUV.MVector s (Double, Double, Double, Word32)+    -> ST s Int+  classifySortedInputs mapping arena+    | inputCount == 0 = pure 0+    | otherwise = do+        first@(_, firstX, firstY, firstInput) <- MUV.unsafeRead arena 0+        writePrimArray mapping (fromIntegral firstInput) firstInput+        classifyFrom first firstX firstY firstInput 1 1+   where+    classifyFrom+      :: (Double, Double, Double, Word32)+      -> Double+      -> Double+      -> Word32+      -> Int+      -> Int+      -> ST s Int+    classifyFrom !_ !previousX !previousY !classInput !readIndex !uniqueCount+      | readIndex >= inputCount = pure uniqueCount+      | otherwise = do+          record@(_, x, y, inputOrdinal) <- MUV.unsafeRead arena readIndex+          if x == previousX && y == previousY+            then do+              writePrimArray mapping (fromIntegral inputOrdinal) classInput+              classifyFrom record previousX previousY classInput (readIndex + 1) uniqueCount+            else do+              writePrimArray mapping (fromIntegral inputOrdinal) inputOrdinal+              if uniqueCount == readIndex+                then pure ()+                else MUV.unsafeWrite arena uniqueCount record+              classifyFrom record x y inputOrdinal (readIndex + 1) (uniqueCount + 1)++  -- Materialize in original order, preserving the established handle+  -- assignment and duplicate-payload law. The equivalence mapping for a+  -- duplicate always points backward to an already materialized class owner.+  appendClassifiedInputs+    :: forall s+     . MutableDcel s vertex directed undirected face+    -> MutablePrimArray s Word32+    -> (Int -> Point)+    -> DuplicatePayloadPolicy vertex+    -> Int+    -> Double+    -> Double+    -> ST s (Double, Double)+  appendClassifiedInputs mutable mapping pointAtInput duplicatePolicy !index !sumX !sumY+    | index >= inputCount = pure (sumX, sumY)+    | otherwise = do+        classInput <- readPrimArray mapping index+        let !vertexData = payloadAtInput index+        if fromIntegral classInput == index+          then case pointAtInput index of+            Point x y -> do+              let !canonicalX = canonicalCoordinate x+                  !canonicalY = canonicalCoordinate y+              vertex <- appendVertexCoordinates mutable canonicalX canonicalY vertexData+              writePrimArray mapping index (fromIntegral vertex)+              appendClassifiedInputs+                mutable+                mapping+                pointAtInput+                duplicatePolicy+                (index + 1)+                (sumX + canonicalX)+                (sumY + canonicalY)+          else do+            resident <- fromIntegral <$> readPrimArray mapping (fromIntegral classInput)+            writePrimArray mapping index (fromIntegral resident)+            case duplicatePolicy of+              KeepFirstPayload -> pure ()+              CombineDuplicatePayload combine -> do+                residentData <- vertexDataAt mutable resident+                writeVertexData mutable resident (combine residentData vertexData)+            appendClassifiedInputs+              mutable+              mapping+              pointAtInput+              duplicatePolicy+              (index + 1)+              sumX+              sumY++  -- Once duplicates have shortened the vertex arena, translate each compacted+  -- representative from its input-class name to its authoritative vertex and+  -- restate its radial key around the exact unique-site centre.+  rewriteCompactedArena+    :: forall s+     . MutablePrimArray s Word32+    -> Double+    -> Double+    -> MUV.MVector s (Double, Double, Double, Word32)+    -> Int+    -> ST s ()+  rewriteCompactedArena mapping centerX centerY arena !index+    | index >= MUV.length arena = pure ()+    | otherwise = do+        (_, x, y, classInput) <- MUV.unsafeRead arena index+        vertex <- readPrimArray mapping (fromIntegral classInput)+        let !deltaX = centerX - x+            !deltaY = centerY - y+        MUV.unsafeWrite arena index (deltaX * deltaX + deltaY * deltaY, x, y, vertex)+        rewriteCompactedArena mapping centerX centerY arena (index + 1)++-- | Insert or replace a vertex payload. A payload at an existing position is+-- overwritten without changing topology.+--+-- The published mesh is independent of the one passed in. A singleton below+-- ten thousand resident sites copies densely; larger bases publish through+-- copy-on-write pages. A caller inserting a sequence wants one+-- 'Moonlight.Triangulation.Session.withSession' over+-- 'Moonlight.Triangulation.Session.insertVertex' instead — see 'insertAt'.+insert+  :: HasPosition vertex+  => Triangulation mode vertex directed undirected face+  -> vertex+  -> Either BuildError (InsertionResult mode vertex directed undirected face)+insert triangulation vertexData = insertAt triangulation (position vertexData) vertexData++-- | Insert at a stated point. 'insert' is this with the point read out of the+-- payload, which is what a caller holding only a payload wants; a caller that+-- computed the point — a constraint split, a Steiner refinement — wants to say+-- so rather than build a payload and hope the round trip through 'HasPosition'+-- returns what it started with.+--+-- This is one shaped transaction over a single insertion. Replacing a fold of+-- it with one session is sound because the two agree on every mesh and differ+-- only in how many intermediate meshes they publish. A fold publishes @k@+-- meshes and pays a thaw for each, so it runs in Θ(n·k); the session pays one+-- thaw and runs in O(k·log n) expected.+insertAt+  :: Triangulation mode vertex directed undirected face+  -> Point+  -> vertex+  -> Either BuildError (InsertionResult mode vertex directed undirected face)+insertAt triangulation rawPoint vertexData = do+  queryPoint <- validatePoint Nothing rawPoint+  case locatePointWithHint triangulation Nothing queryPoint of+    (OnVertex resident, walked) ->+      Right (replaceResidentPayload triangulation resident walked vertexData)+    (located, walked) -> do+      let transactionShape =+            if numVertices triangulation < 10_000+              then DenseTransaction+              else LocalTransaction+      ((vertex, disposition), frozen, stats) <-+        runTransaction+          id+          transactionShape+          triangulation+          1+          (\mutable operation -> do+             addCounter operation CounterInputPoints 1+             inserted <-+               case located of+                 -- A frozen degenerate-line location points at an arbitrary visible+                 -- segment, while the line extension interpreter requires a terminal+                 -- edge. The frozen section carries no terminal witness, so retain+                 -- the existing mutable line locator for this one non-lawful case.+                 OutsideConvexHull (Just _)+                   | numInnerFaces triangulation == 0 ->+                       insertVertexAtPoint @'ProbeOff mutable operation Nothing (queryPointValue queryPoint) vertexData+                 _ -> do+                   capacityOutcome <- ensurePointCapacity mutable 1+                   case capacityOutcome of+                     Left failure -> pure (Left failure)+                     Right () -> do+                       vertex <- appendVertex mutable (queryPointValue queryPoint) vertexData+                       let thawedLocation =+                             case located of+                               EmptyTriangulation -> MutableEmpty+                               OnEdge (DirectedEdgeId raw) -> MutableOnEdge (fromIntegral raw)+                               InFace (FaceId raw) -> MutableInFace (fromIntegral raw)+                               -- The frozen locator emits no edge only for a+                               -- singleton mesh. Its mutable interpreter ignores+                               -- this sentinel while constructing the second vertex.+                               OutsideConvexHull Nothing -> MutableOutsideHull 0+                               OutsideConvexHull (Just (DirectedEdgeId raw)) -> MutableOutsideHull (fromIntegral raw)+                       ((vertex, Inserted) <$) <$> insertExistingVertexAtLocation @'ProbeOff mutable operation vertex thawedLocation+             case inserted of+               Left failure -> pure (Left failure)+               Right (vertex, disposition) -> do+                 case disposition of+                   Inserted -> addCounter operation CounterUniquePoints 1+                   AlreadyPresent -> do+                     writeVertexData mutable vertex vertexData+                     addCounter operation CounterExistingPoints 1+                     addCounter operation CounterDuplicatePoints 1+                 pure (Right (vertex, disposition))+          )+      pure+        InsertionResult+          { insertionTriangulation = frozen+          , insertionVertex = VertexId (fromIntegral vertex)+          , insertionDisposition = disposition+          , insertionStats = withFrozenLocationStats walked stats+          }++-- | Publish a payload replacement without opening a transaction.+--+-- A position already resident changes exactly one thing: the payload slot the+-- vertex already occupies. No coordinate, no half-edge, no constraint flag and+-- no face record differs, so the five unboxed planes are the ones the argument+-- already holds rather than copies taken out of it — which is what a thaw costs+-- and what this exists to refuse. They are immutable values; nothing reached+-- from here is a mutable buffer, and 'boxedUpdate' materializes a fresh page+-- for the one it rewrites, leaving the argument's own directory intact.+--+-- The location counters are the frozen walk's, not a thawed walk's. They+-- describe the walk that actually ran.+replaceResidentPayload+  :: Triangulation mode vertex directed undirected face+  -> VertexId+  -> LocationStats+  -> vertex+  -> InsertionResult mode vertex directed undirected face+replaceResidentPayload triangulation resident@(VertexId raw) walked vertexData =+  InsertionResult+    { insertionTriangulation =+        triangulation+          { triVertexData =+              boxedUpdate (fromIntegral raw) vertexData (triVertexData triangulation)+          }+    , insertionVertex = resident+    , insertionDisposition = AlreadyPresent+    , insertionStats =+        withFrozenLocationStats+          walked+          emptyBuildStats+            { statInputPoints = 1+            , statExistingPoints = 1+            , statDuplicatePoints = 1+            }+    }++-- | Add the frozen locator's observation to the local topology interpreter's+-- operation-owned counters. Direct frozen-site insertion contributes no mutable+-- walk; the degenerate fallback contributes its real mutable walk rather than+-- having it erased from the published result.+withFrozenLocationStats :: LocationStats -> BuildStats -> BuildStats+withFrozenLocationStats walked stats =+  stats+    { statLocationWalkSteps = locationWalkSteps walked + statLocationWalkSteps stats+    , statLocationMaxWalk = max (locationWalkSteps walked) (statLocationMaxWalk stats)+    , statLocationFallbacks = (if locationUsedFallback walked then 1 else 0) + statLocationFallbacks stats+    }++-- | Apply a batch in one page transaction. Duplicate positions are processed+-- in input order, so their last payload wins exactly as repeated 'insert'+-- calls would, while topology is inserted only once per new position.+insertMany+  :: forall mode vertex directed undirected face+   . HasPosition vertex+  => Triangulation mode vertex directed undirected face+  -> V.Vector vertex+  -> Either BuildError (BuildResult mode vertex directed undirected face)+insertMany triangulation input = do+  validateVertices input+  (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+     . MutableDcel s vertex directed undirected face+    -> OperationState s+    -> MutablePointIndex s+    -> MutablePrimArray s Word32+    -> MUV.MVector s Word32+    -> Int+    -> Double+    -> Double+    -> Int+    -> ST s (Either BuildError (Double, Double, Int))+  fill mutable operation table mapping freshBuffer !index !sumX !sumY !freshCount+    | index >= V.length input = pure (Right (sumX, sumY, freshCount))+    | otherwise = do+        let !vertexData = input V.! index+        claimed <- claimPosition mutable table (position vertexData) vertexData+        case claimed of+          Left failure -> pure (Left failure)+          Right (FreshPosition vertex canonicalX canonicalY) -> do+            writePrimArray mapping index (fromIntegral vertex)+            MUV.unsafeWrite freshBuffer freshCount (fromIntegral vertex)+            fill+              mutable+              operation+              table+              mapping+              freshBuffer+              (index + 1)+              (sumX + canonicalX)+              (sumY + canonicalY)+              (freshCount + 1)+          Right (ResidentPosition vertex) -> do+            writePrimArray mapping index (fromIntegral vertex)+            writeVertexData mutable vertex vertexData+            fill mutable operation table mapping freshBuffer (index + 1) sumX sumY freshCount++-- | One packed radial record per swept vertex — the derived sort fields and+-- the vertex handle, nothing else — filled straight from the coordinate+-- arenas and consumed in place by the sweep. The squared distance is stated+-- against the ingress-accumulated centre, in the widened comparison format.+fillRadialArena+  :: MutableDcel s vertex directed undirected face+  -> Double+  -> Double+  -> (Int -> ST s Word32)+  -> Int+  -> ST s (MUV.MVector s (Double, Double, Double, Word32))+fillRadialArena mutable centerX centerY lookupId count = do+  arena <- MUV.new count+  forM_ [0 .. count - 1] $ \index -> do+    raw <- lookupId index+    x <- readPointX mutable (fromIntegral raw)+    y <- readPointY mutable (fromIntegral raw)+    let !wideX = x+        !wideY = y+        !deltaX = centerX - wideX+        !deltaY = centerY - wideY+    MUV.unsafeWrite arena index (deltaX * deltaX + deltaY * deltaY, wideX, wideY, raw)+  pure arena++-- | Claim a position for the vertex the arena would append next, or answer the+-- vertex already holding it. The claim is written before the append, so the two+-- must stay adjacent: nothing may consume a vertex slot in between.+claimPosition+  :: MutableDcel s vertex directed undirected face+  -> MutablePointIndex s+  -> Point+  -> vertex+  -> ST s (Either BuildError PositionClaim)+claimPosition mutable table rawPoint vertexData =+  case rawPoint of+    Point x y -> do+      let !canonicalX = canonicalCoordinate x+          !canonicalY = canonicalCoordinate y+      slot <- nextVertexSlot mutable+      let !candidate = nextVertexSlotIndex slot+      owner <-+        resolveMutablePoint+          table+          (readPointX mutable)+          (readPointY mutable)+          canonicalX+          canonicalY+          candidate+      case owner of+        Left failure -> pure (Left failure)+        Right (Just existing) -> pure (Right (ResidentPosition existing))+        Right Nothing -> do+          vertex <- appendVertexCoordinatesAtSlot mutable slot canonicalX canonicalY vertexData+          pure (Right (FreshPosition vertex canonicalX canonicalY))+-- | Enter the positions a batch inherits from the triangulation it extends, so+-- that an input repeating one of them maps to the vertex already there.+seedPointTable+  :: MutableDcel s vertex directed undirected face+  -> MutablePointIndex s+  -> ST s (Either BuildError ())+seedPointTable mutable table = do+  existing <- pointCount mutable+  seedMutablePointIndex+    table+    existing+    (readPointX mutable)+    (readPointY mutable)++validateVertices :: HasPosition vertex => V.Vector vertex -> Either BuildError ()+validateVertices vertices =+  V.iforM_ vertices (\index vertexData -> validatePoint (Just index) (position vertexData))
src-build/Moonlight/Triangulation/Internal/Cdt/Admission.hs view
@@ -11,7 +11,7 @@  import qualified Data.IntSet as IntSet import qualified Moonlight.Triangulation.Dcel as Dcel-import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.HandleDefs import Moonlight.Triangulation.Internal.Representation import Moonlight.Triangulation.Internal.Types import Moonlight.Triangulation.Math
src-build/Moonlight/Triangulation/Internal/Cdt/Batch.hs view
@@ -15,7 +15,7 @@ import Control.Monad.ST (ST) import Data.Either (isRight) import qualified Data.Vector as V-import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.HandleDefs import Moonlight.Triangulation.Internal.Cdt.Combinators (foldWhileM) import Moonlight.Triangulation.Internal.Cdt.Query (validateEndpoints) import Moonlight.Triangulation.Internal.Cdt.Recovery (applyMutableConstraint)
src-build/Moonlight/Triangulation/Internal/Cdt/Build.hs view
@@ -14,7 +14,7 @@ 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.HandleDefs import Moonlight.Triangulation.Internal.Cdt.Batch (recoverConstraints) import Moonlight.Triangulation.Internal.Cdt.Types import Moonlight.Triangulation.Internal.Representation
src-build/Moonlight/Triangulation/Internal/Cdt/Combinators.hs view
@@ -11,7 +11,7 @@   , directedInt   ) where -import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.HandleDefs   ( DirectedEdgeId (..)   , VertexId (..)   )
src-build/Moonlight/Triangulation/Internal/Cdt/Corridor.hs view
@@ -20,7 +20,7 @@  import Control.Monad.ST (ST) import Data.Bits (xor)-import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.HandleDefs import Moonlight.Triangulation.IntersectionIterator (Intersection (..)) import Moonlight.Triangulation.Internal.Cdt.Combinators   ( directedInt
src-build/Moonlight/Triangulation/Internal/Cdt/Corridor/Trace.hs view
@@ -17,7 +17,7 @@  import Control.Monad.ST (ST) import Data.Bits (xor)-import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.HandleDefs import Moonlight.Triangulation.IntersectionIterator import Moonlight.Triangulation.Internal.Cdt.Combinators   ( directedInt
src-build/Moonlight/Triangulation/Internal/Cdt/Query.hs view
@@ -21,7 +21,7 @@ import Data.Bits (xor) import qualified Data.IntSet as IntSet import qualified Moonlight.Triangulation.Dcel as Dcel-import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.HandleDefs import Moonlight.Triangulation.IntersectionIterator import Moonlight.Triangulation.Internal.Cdt.Admission   ( ConstraintAdmission (..)
src-build/Moonlight/Triangulation/Internal/Cdt/Recovery.hs view
@@ -18,7 +18,7 @@ import Data.Bits (xor) import Data.Either (isRight) import Data.Foldable (traverse_)-import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.HandleDefs import Moonlight.Triangulation.Internal.Cdt.Combinators   ( foldWhileM   , vertexInt
src-build/Moonlight/Triangulation/Internal/Cdt/Region.hs view
@@ -10,7 +10,7 @@ import qualified Data.IntSet as IntSet import qualified Moonlight.Triangulation.Dcel as Dcel import Moonlight.Triangulation.FloodFillIterator (facesAtEvenBarrierDepth)-import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.HandleDefs import Moonlight.Triangulation.Handles.Iterators.FixedIterators (innerFaces) import Moonlight.Triangulation.Internal.Representation import Moonlight.Triangulation.Internal.Types
src-build/Moonlight/Triangulation/Internal/Cdt/Segment.hs view
@@ -17,7 +17,7 @@ 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.HandleDefs import Moonlight.Triangulation.Internal.Cdt.Admission   ( ConstraintAdmission (..)   , constraintAdmission
src-build/Moonlight/Triangulation/Internal/Cdt/Site.hs view
@@ -9,7 +9,7 @@   ) where  import Control.Monad.ST (ST)-import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.HandleDefs import Moonlight.Triangulation.Insertion (insertVertexAtPoint) import Moonlight.Triangulation.Internal.Mutable import Moonlight.Triangulation.Internal.OperationState
src-build/Moonlight/Triangulation/Internal/Cdt/Split.hs view
@@ -14,7 +14,7 @@ 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.HandleDefs import Moonlight.Triangulation.Internal.Cdt.Combinators   ( asConstraintStep   , bindMutable
src-build/Moonlight/Triangulation/Internal/Cdt/Types.hs view
@@ -37,7 +37,7 @@ import Control.DeepSeq (NFData) import Data.List.NonEmpty (NonEmpty) import qualified Data.Vector as V-import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.HandleDefs   ( DirectedEdgeId   , FaceId   , UndirectedEdgeId
src-build/Moonlight/Triangulation/Internal/Cdt/Union.hs view
@@ -27,7 +27,7 @@ import qualified Data.Set as Set import qualified Data.Vector as V import qualified Moonlight.Triangulation.Dcel as Dcel-import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.HandleDefs import Moonlight.Triangulation.Internal.Canonical (canonicalize) import Moonlight.Triangulation.Insertion (insertAdmittedPointCombining) import Moonlight.Triangulation.Refinement (validateRefinementParameters)
src-build/Moonlight/Triangulation/Internal/CircleSweep.hs view
@@ -20,7 +20,7 @@ import qualified Data.Vector.Unboxed.Mutable as MUV import Data.Word (Word32, Word64) import GHC.Float (castDoubleToWord64)-import Moonlight.Triangulation.Handles.HandleDefs (DirectedEdgeId (..))+import Moonlight.Triangulation.Internal.HandleDefs (DirectedEdgeId (..)) import Moonlight.Triangulation.Insertion (insertExistingVertex) import Moonlight.Triangulation.Internal.DcelOperations.Hull   ( ReservedSweepCells
src-build/Moonlight/Triangulation/Internal/Excision.hs view
@@ -16,7 +16,7 @@   ( legalizeCavityFanScratch   , legalizeEdges   )-import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.HandleDefs import Moonlight.Triangulation.Internal.Mutable import Moonlight.Triangulation.Internal.OperationState   ( Counter (..)
src-build/Moonlight/Triangulation/Internal/Join.hs view
@@ -31,7 +31,7 @@ import Moonlight.Triangulation.JoinSemilattice   ( JoinSemilattice (joinAnnotations)   )-import Moonlight.Triangulation.Session+import Moonlight.Triangulation.Internal.Session   ( insertAdmittedVertexAtCombining   , withLocalSession   )@@ -43,7 +43,7 @@ -- the larger operand's vertex handles and untouched pages survive. Comparable -- operands may merge along a separating seam or rebuild from their combined -- site set. Every schedule returns valid topology;--- 'Moonlight.Triangulation.Dcel.canonicalize' is the+-- 'Moonlight.Triangulation.Canonical.canonicalize' is the -- separate physical observation when construction-independent numbering is -- required. --
src-build/Moonlight/Triangulation/Internal/Join/Seam.hs view
@@ -44,8 +44,8 @@   , vertexOutEdge   , vertexData   )-import Moonlight.Triangulation.FloodFillIterator (floodFillFacesWithRejectedEdges)-import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.FloodFillIterator (floodFillFacesWithRejectedEdges)+import Moonlight.Triangulation.Internal.HandleDefs   ( DirectedEdgeId (..)   , FaceId (..)   , UndirectedEdgeId (..)
src-build/Moonlight/Triangulation/Internal/Join/SiteSet.hs view
@@ -31,9 +31,9 @@ import qualified Data.Vector as V import qualified Data.Vector.Unboxed as U import qualified Data.Vector.Unboxed.Mutable as MUV-import Moonlight.Triangulation.BulkLoad (delaunayFromAdmittedDistinctAscending)+import Moonlight.Triangulation.Internal.BulkLoad (delaunayFromAdmittedDistinctAscending) import Moonlight.Triangulation.Dcel (numVertices, vertexData, vertexPoint)-import Moonlight.Triangulation.Handles.HandleDefs (VertexId (..))+import Moonlight.Triangulation.Internal.HandleDefs (VertexId (..)) import Moonlight.Triangulation.Internal.Canonical (canonicalizeKnownAscending) import Moonlight.Triangulation.Handles.Iterators.FixedIterators   ( foldVertices'
src-build/Moonlight/Triangulation/Internal/Location.hs view
@@ -22,7 +22,7 @@   , addCounter   , maxCounter   )-import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.HandleDefs   ( FaceId (..)   , UndirectedEdgeId (..)   )
src-build/Moonlight/Triangulation/Internal/Refinement.hs view
@@ -27,7 +27,7 @@ import Data.STRef (STRef, modifySTRef', newSTRef, readSTRef, writeSTRef) import qualified Data.Vector.Unboxed.Mutable as MUV import Data.Word (Word32)-import Moonlight.Triangulation.Handles.HandleDefs (FaceId (..), UndirectedEdgeId (..))+import Moonlight.Triangulation.Internal.HandleDefs (FaceId (..), UndirectedEdgeId (..)) import Moonlight.Triangulation.Internal.DcelOperations.Legalize (legalizeEdges) import Moonlight.Triangulation.Internal.DcelOperations.Subdivide (insertOnEdge) import Moonlight.Triangulation.Internal.DcelOperations.Twin (reverseIndex)
+ src-build/Moonlight/Triangulation/Internal/Session.hs view
@@ -0,0 +1,377 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingStrategies #-}++-- | One owned editing transaction over a triangulation: thaw once, edit, publish once.+module Moonlight.Triangulation.Internal.Session+  ( RemovalOutcome (..)+  , Session+  , withSession+  , withLocalSession+  , insertVertex+  , insertVertexAt+  , insertVertexAtNear+  , insertAdmittedVertexAtCombining+  , removeAt+  , removeAtNear+  , removeManyAt+  , removeManyAtNear+  , excise+  , refuse+  ) where++import Control.DeepSeq (NFData)+import Control.Monad.ST (ST)+import Data.Bits (xor)+import qualified Data.Vector as V+import Moonlight.Triangulation.Internal.HandleDefs+import Moonlight.Triangulation.Insertion+  ( insertAdmittedPointCombining+  , insertPointCombining+  )+import Moonlight.Triangulation.Internal.Excision (removeMutable)+import Moonlight.Triangulation.Internal.Location (MutableLocation (..), locateMutable)+import Moonlight.Triangulation.Internal.Mutable+import Moonlight.Triangulation.Internal.OperationState (OperationState)+import Moonlight.Triangulation.Internal.Paged (TransactionShape (..))+import Moonlight.Triangulation.Math (canonicalPoint)+import Moonlight.Triangulation.Internal.Representation+import Moonlight.Triangulation.Internal.Transaction (runTransaction)+import Moonlight.Triangulation.Internal.Types+import GHC.Generics (Generic)++-- | What a single removal produced: the removed position and payload, plus the+-- slot and position changed by swap compaction when another vertex moved.+data RemovalOutcome vertex = RemovalOutcome+  { removalOutcomePoint :: !(Point)+  , removalOutcomeData :: !vertex+  , removalOutcomeSwap :: !(Maybe (VertexId, Point))+  }+  deriving stock (Eq, Show, Generic)+  deriving anyclass (NFData)++-- | An edit sequence against one thawed mesh.+--+-- The two published entry points before this one — an insertion session and a+-- removal session — each handed the caller a function and could not compose,+-- so a caller who wanted to remove a hundred vertices and insert fifty thawed+-- twice and paid the O(n) publication a session exists to delete. They are the+-- same transaction; only the verb differed. This is that transaction, and the+-- verbs are its primitives.+--+-- The public session algebra and the private exact-site single-insertion+-- interpreter cross the same hidden transaction boundary. Sessions provide+-- composition; the private interpreter reaches that boundary only with+-- evidence from the immediately preceding frozen read. A fold of persistent+-- verbs and a session over the same verbs therefore differ only in which+-- intermediate meshes they publish.+--+-- Refusal short-circuits: the first 'BuildError' abandons the rest of the+-- sequence, and 'withSession' freezes nothing. A partly edited arena cannot+-- reach a caller as a published triangulation.+newtype Session s vertex directed undirected face a = Session+  { stepSession+      :: MutableDcel s vertex directed undirected face+      -> OperationState s+      -> ST s (Either BuildError a)+  }++instance Functor (Session s vertex directed undirected face) where+  fmap f (Session step) = Session $ \mesh operation -> fmap (fmap f) (step mesh operation)+  {-# INLINE fmap #-}++instance Applicative (Session s vertex directed undirected face) where+  pure a = Session $ \_ _ -> pure (Right a)+  {-# INLINE pure #-}+  Session left <*> Session right = Session $ \mesh operation -> do+    outcome <- left mesh operation+    case outcome of+      Left refusal -> pure (Left refusal)+      Right f -> fmap (fmap f) (right mesh operation)+  {-# INLINE (<*>) #-}++instance Monad (Session s vertex directed undirected face) where+  Session step >>= f = Session $ \mesh operation -> do+    outcome <- step mesh operation+    case outcome of+      Left refusal -> pure (Left refusal)+      Right a -> stepSession (f a) mesh operation+  {-# INLINE (>>=) #-}++-- | Abandon the transaction. Nothing is published.+refuse :: BuildError -> Session s vertex directed undirected face a+refuse failure = Session $ \_ _ -> pure (Left failure)+{-# INLINE refuse #-}++-- | Insert a vertex, answering the handle it was given. A point already+-- present keeps its handle and takes the new payload.+insertVertex+  :: HasPosition vertex+  => vertex+  -> Session s vertex directed undirected face VertexId+insertVertex payload = Session $ \mesh operation ->+  fmap+    (fmap (VertexId . fromIntegral . fst))+    (insertPointCombining (\_ replacement -> replacement) Nothing mesh operation (position payload) payload)++-- | Insert at a stated point, answering the handle and whether a site was+-- created. 'insertVertex' is this with the point read out of the payload; a+-- caller that computed the point — a constraint split, a Steiner refinement —+-- states it rather than round-tripping through 'HasPosition'.+insertVertexAt+  :: Point+  -> vertex+  -> Session s vertex directed undirected face (VertexId, InsertionDisposition)+insertVertexAt point payload = Session $ \mesh operation ->+  fmap+    (fmap (\(vertex, disposition) -> (VertexId (fromIntegral vertex), disposition)))+    (insertPointCombining (\_ replacement -> replacement) Nothing mesh operation point payload)++-- | 'insertVertexAt' with the walk seeded at a face the caller vouches for --+-- typically the face a locate on the just-published value settled on, which+-- is exact on the mesh this transaction thawed. The seed is a hint, not an+-- authority: an out-of-range face degrades to the unhinted descent, and the+-- walk corrects.+insertVertexAtNear+  :: FaceId+  -> Point+  -> vertex+  -> Session s vertex directed undirected face (VertexId, InsertionDisposition)+insertVertexAtNear (FaceId rawSeed) point payload = Session $ \mesh operation ->+  fmap+    (fmap (\(vertex, disposition) -> (VertexId (fromIntegral vertex), disposition)))+    (insertPointCombining+       (\_ replacement -> replacement)+       (Just (fromIntegral rawSeed))+       mesh+       operation+       point+       payload+    )++-- | Insert an admitted site while combining an occupied annotation. This is+-- package-internal descent from a boundary that already owns coordinate+-- admission; only location and annotation gluing remain here.+insertAdmittedVertexAtCombining+  :: (vertex -> vertex -> vertex)+  -> QueryPoint+  -> vertex+  -> Session s vertex directed undirected face (VertexId, InsertionDisposition)+insertAdmittedVertexAtCombining combine queryPoint payload = Session $ \mesh operation ->+  fmap+    (fmap (\(vertex, disposition) -> (VertexId (fromIntegral vertex), disposition)))+    (insertAdmittedPointCombining combine Nothing mesh operation queryPoint payload)++-- | Remove the vertex standing at a point, answering 'Nothing' when no vertex+-- stands there.+--+-- Keyed by position rather than by handle because removal swap-compacts the+-- arenas: every outstanding t'VertexId' may relocate, and over a sequence of+-- removals a handle-keyed verb would force the caller to thread every+-- relocation by hand. A position is invariant under compaction. The relocation+-- is still reported, in the 'RemovalOutcome', for callers holding handles.+-- The question is an identity question -- which vertex stands at this exact+-- position. A session that has committed to identity work ('removeManyAt',+-- 'excise') answers it through the identity index in O(1); one that has not+-- answers it with a single walk, because a published index is a lazy rebuild+-- and forcing a whole-mesh build to answer one question is the wrong trade.+removeAt+  :: Point+  -> Session s vertex directed undirected face (Maybe (RemovalOutcome vertex))+removeAt point = Session $ \mesh operation -> do+  indexed <- identityIndexActive mesh+  if indexed+    then removeIndexed mesh operation point+    else walkAndRemove mesh operation Nothing point++removeIndexed+  :: MutableDcel s vertex directed undirected face+  -> OperationState s+  -> Point+  -> ST s (Either BuildError (Maybe (RemovalOutcome vertex)))+removeIndexed mesh operation point = do+  located <- lookupPointVertex mesh point+  case located of+    Just vertex -> fmap (fmap Just) (removeMutableOutcome mesh operation vertex)+    Nothing -> pure (Right Nothing)++-- | Remove the vertex standing at a point, starting from a caller-supplied+-- near vertex -- a locate hint from an external structure such as the+-- Delaunay hierarchy. The guess is a hint, not an authority: a slot renamed+-- by swap-compaction or out of range degrades to a walk hinted by the+-- guess's incident face, and the walk corrects.+removeAtNear+  :: VertexId+  -> Point+  -> Session s vertex directed undirected face (Maybe (RemovalOutcome vertex))+removeAtNear (VertexId rawGuess) point = Session $ \mesh operation -> do+  indexed <- identityIndexActive mesh+  let !guess = fromIntegral rawGuess+      !query = canonicalPoint point+  if indexed+    then removeIndexed mesh operation query+    else do+      vertices <- pointCount mesh+      if guess < 0 || guess >= vertices+        then walkAndRemove mesh operation Nothing query+        else do+          stored <- pointAt mesh guess+          if stored == query+            then fmap (fmap Just) (removeMutableOutcome mesh operation guess)+            else do+              outgoing <- readVertexOut mesh guess+              let interiorFace edge fallback = do+                    face <- readFace mesh edge+                    if face > 0 then pure (Just face) else fallback+              hint <-+                if outgoing < 0+                  then pure Nothing+                  else interiorFace outgoing (interiorFace (outgoing `xor` 1) (pure Nothing))+              walkAndRemove mesh operation hint query++walkAndRemove+  :: MutableDcel s vertex directed undirected face+  -> OperationState s+  -> Maybe Int+  -> Point+  -> ST s (Either BuildError (Maybe (RemovalOutcome vertex)))+walkAndRemove mesh operation hint query = do+  located <- locateMutable mesh operation hint query+  case located of+    Left obstruction -> pure (Left obstruction)+    Right (MutableOnVertex vertex) ->+      fmap (fmap Just) (removeMutableOutcome mesh operation vertex)+    Right _ -> pure (Right Nothing)++-- | Remove the vertex standing at each point, answering per point in order.+-- One session-level decision buys the locate strategy: few removals walk the+-- mesh, each a ~O(sqrt n) descent, while many seed the existing mutable+-- open-addressed identity section once from coordinate authority and answer+-- each question by hash. The guard squares the crossover to avoid a root.+removeManyAt+  :: V.Vector (Point)+  -> Session s vertex directed undirected face (V.Vector (Maybe (RemovalOutcome vertex)))+removeManyAt points =+  withBatchIdentityForLoad (V.length points) (V.mapM removeAt points)++-- | Install the mutable identity section only around a dense removal program.+-- It is a local section over the coordinate arenas, not a new published cache:+-- on every result path it is discarded and freeze glues a lazy @PointIndex@+-- back from those arenas. Sparse loads retain the existing locate walk.+withBatchIdentityForLoad+  :: Int+  -> Session s vertex directed undirected face result+  -> Session s vertex directed undirected face result+withBatchIdentityForLoad count (Session action) = Session $ \mesh operation -> do+  vertices <- pointCount mesh+  if count * count > 10 * vertices+    then do+      activated <- activateBatchPointIndex mesh+      case activated of+        Left failure -> pure (Left failure)+        Right () -> do+          outcome <- action mesh operation+          discardBatchPointIndex mesh+          pure outcome+    else action mesh operation++identityCommitted :: Session s vertex directed undirected face Bool+identityCommitted = Session $ \mesh _ -> fmap Right (identityIndexActive mesh)+{-# INLINE identityCommitted #-}++-- | 'removeManyAt' with a near vertex per point where the caller has one — a+-- hierarchy sample, a previous answer. The batch commits to its load first,+-- so the identity-index crossover decides the regime once: a dense batch buys+-- its local mutable table and never examines a guess, a sparse one walks from+-- its guesses.+removeManyAtNear+  :: V.Vector (Maybe VertexId)+  -> V.Vector (Point)+  -> Session s vertex directed undirected face (V.Vector (Maybe (RemovalOutcome vertex)))+removeManyAtNear guesses points =+  withBatchIdentityForLoad (V.length points) $ do+    indexed <- identityCommitted+    if indexed+      then V.mapM removeAt points+      else+        V.zipWithM+          (\guess point -> maybe (removeAt point) (\vertex -> removeAtNear vertex point) guess)+          guesses+          points++-- | Remove a stated vertex. Sound only while the handle still denotes what the+-- caller means: the first removal in a sequence compacts the arenas, so a+-- handle taken before it may name a different vertex after. @removeAt@ is the+-- verb for a sequence; this is the verb for a handle the caller has just been+-- given and has not yet let a removal run underneath.+excise+  :: VertexId+  -> Session s vertex directed undirected face (RemovalOutcome vertex)+excise requested@(VertexId raw) = Session $ \mesh operation -> do+  vertices <- pointCount mesh+  if toInteger raw >= toInteger vertices+    then pure (Left (RemovalVertexOutOfRange requested vertices))+    else do+      activatePointIndex mesh+      removeMutableOutcome mesh operation (fromIntegral raw)++removeMutableOutcome+  :: MutableDcel s vertex directed undirected face+  -> OperationState s+  -> Int+  -> ST s (Either BuildError (RemovalOutcome vertex))+removeMutableOutcome mesh operation vertex =+  fmap (fmap removalOutcomeOf) (removeMutable mesh operation vertex)++removalOutcomeOf+  :: (Point, vertex, Maybe (Int, Point))+  -> RemovalOutcome vertex+removalOutcomeOf (point, payload, swapped) =+  RemovalOutcome+    { removalOutcomePoint = point+    , removalOutcomeData = payload+    , removalOutcomeSwap =+        (\(slot, standing) -> (VertexId (fromIntegral slot), standing)) <$> swapped+    }++-- | Run an edit sequence: thaw once, edit, freeze once, and publish the+-- counters the whole transaction charged.+--+-- The reservation is taken for the peak vertex count, so it is the insertion+-- count that sizes it; removals only shrink the mesh and a mixed sequence+-- cannot exceed the peak an insert-only sequence of the same count reaches.+--+-- The session cannot escape its callback: the state token is universally+-- quantified, so the mesh it addresses is dead by the time the frozen+-- triangulation is returned.+withSession+  :: forall mode vertex directed undirected face result+   . Triangulation mode vertex directed undirected face+  -> Int+  -> (forall s. Session s vertex directed undirected face result)+  -> Either BuildError (result, Triangulation mode vertex directed undirected face, BuildStats)+withSession triangulation additional session =+  runTransaction+    id+    DenseTransaction+    triangulation+    additional+    (\mesh operation -> stepSession session mesh operation)++-- | The local-edit transaction: copy-on-write pages, publication proportional+-- to what the edit dirtied. This is the section for singleton persistent+-- verbs, whose one edit cannot amortize a dense copy of the whole mesh.+withLocalSession+  :: forall mode vertex directed undirected face result+   . Triangulation mode vertex directed undirected face+  -> Int+  -> (forall s. Session s vertex directed undirected face result)+  -> Either BuildError (result, Triangulation mode vertex directed undirected face, BuildStats)+withLocalSession triangulation additional session =+  runTransaction+    id+    LocalTransaction+    triangulation+    additional+    (\mesh operation -> stepSession session mesh operation)
src-build/Moonlight/Triangulation/Refinement.hs view
@@ -32,7 +32,7 @@   , vertexPoint   ) import Moonlight.Triangulation.FloodFillIterator (facesAtEvenBarrierDepth)-import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.HandleDefs   ( FaceId (..)   , UndirectedEdgeId (..)   , asUndirected@@ -47,10 +47,12 @@ import Moonlight.Triangulation.Internal.Refinement import Moonlight.Triangulation.Internal.Representation (Triangulation (..)) import Moonlight.Triangulation.Internal.Transaction (runTransactionWithPublication)+import Moonlight.Triangulation.Internal.Validation+  ( validateTopologyClosureWithStats+  ) import Moonlight.Triangulation.Types import Moonlight.Triangulation.Validation   ( validateTopology-  , validateTopologyClosureWithStats   )  data RefinementExecution mode vertex directed undirected face = RefinementExecution
src-build/Moonlight/Triangulation/Removal.hs view
@@ -11,12 +11,12 @@   ) where  import Control.DeepSeq (NFData)-import Moonlight.Triangulation.Handles.HandleDefs (VertexId (..))+import Moonlight.Triangulation.Internal.HandleDefs (VertexId (..)) import Moonlight.Triangulation.Internal.PointIndex (lookupPointIndex) import Moonlight.Triangulation.Internal.Representation (Triangulation (..)) import Moonlight.Triangulation.Internal.Types (BuildError, BuildStats, Point, queryPointValue) import Moonlight.Triangulation.Math (validatePoint)-import Moonlight.Triangulation.Session (RemovalOutcome (..), excise, withLocalSession)+import Moonlight.Triangulation.Internal.Session (RemovalOutcome (..), excise, withLocalSession) import GHC.Generics (Generic)  -- | One removal and its publication: the frozen triangulation, the outcome
src-build/Moonlight/Triangulation/Session.hs view
@@ -1,377 +1,16 @@-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE DerivingStrategies #-}---- | One owned editing transaction over a triangulation: thaw once, edit, publish once.+-- | One sealed editing transaction: thaw once, compose edits, publish once. module Moonlight.Triangulation.Session   ( RemovalOutcome (..)   , Session   , withSession-  , withLocalSession   , insertVertex   , insertVertexAt   , insertVertexAtNear-  , insertAdmittedVertexAtCombining   , removeAt   , removeAtNear   , removeManyAt   , removeManyAtNear-  , excise   , refuse   ) where -import Control.DeepSeq (NFData)-import Control.Monad.ST (ST)-import Data.Bits (xor)-import qualified Data.Vector as V-import Moonlight.Triangulation.Handles.HandleDefs-import Moonlight.Triangulation.Insertion-  ( insertAdmittedPointCombining-  , insertPointCombining-  )-import Moonlight.Triangulation.Internal.Excision (removeMutable)-import Moonlight.Triangulation.Internal.Location (MutableLocation (..), locateMutable)-import Moonlight.Triangulation.Internal.Mutable-import Moonlight.Triangulation.Internal.OperationState (OperationState)-import Moonlight.Triangulation.Internal.Paged (TransactionShape (..))-import Moonlight.Triangulation.Math (canonicalPoint)-import Moonlight.Triangulation.Internal.Representation-import Moonlight.Triangulation.Internal.Transaction (runTransaction)-import Moonlight.Triangulation.Internal.Types-import GHC.Generics (Generic)---- | What a single removal produced: the removed position and payload, plus the--- slot and position changed by swap compaction when another vertex moved.-data RemovalOutcome vertex = RemovalOutcome-  { removalOutcomePoint :: !(Point)-  , removalOutcomeData :: !vertex-  , removalOutcomeSwap :: !(Maybe (VertexId, Point))-  }-  deriving stock (Eq, Show, Generic)-  deriving anyclass (NFData)---- | An edit sequence against one thawed mesh.------ The two published entry points before this one — an insertion session and a--- removal session — each handed the caller a function and could not compose,--- so a caller who wanted to remove a hundred vertices and insert fifty thawed--- twice and paid the O(n) publication a session exists to delete. They are the--- same transaction; only the verb differed. This is that transaction, and the--- verbs are its primitives.------ The public session algebra and the private exact-site single-insertion--- interpreter cross the same hidden transaction boundary. Sessions provide--- composition; the private interpreter reaches that boundary only with--- evidence from the immediately preceding frozen read. A fold of persistent--- verbs and a session over the same verbs therefore differ only in which--- intermediate meshes they publish.------ Refusal short-circuits: the first 'BuildError' abandons the rest of the--- sequence, and 'withSession' freezes nothing. A partly edited arena cannot--- reach a caller as a published triangulation.-newtype Session s vertex directed undirected face a = Session-  { stepSession-      :: MutableDcel s vertex directed undirected face-      -> OperationState s-      -> ST s (Either BuildError a)-  }--instance Functor (Session s vertex directed undirected face) where-  fmap f (Session step) = Session $ \mesh operation -> fmap (fmap f) (step mesh operation)-  {-# INLINE fmap #-}--instance Applicative (Session s vertex directed undirected face) where-  pure a = Session $ \_ _ -> pure (Right a)-  {-# INLINE pure #-}-  Session left <*> Session right = Session $ \mesh operation -> do-    outcome <- left mesh operation-    case outcome of-      Left refusal -> pure (Left refusal)-      Right f -> fmap (fmap f) (right mesh operation)-  {-# INLINE (<*>) #-}--instance Monad (Session s vertex directed undirected face) where-  Session step >>= f = Session $ \mesh operation -> do-    outcome <- step mesh operation-    case outcome of-      Left refusal -> pure (Left refusal)-      Right a -> stepSession (f a) mesh operation-  {-# INLINE (>>=) #-}---- | Abandon the transaction. Nothing is published.-refuse :: BuildError -> Session s vertex directed undirected face a-refuse failure = Session $ \_ _ -> pure (Left failure)-{-# INLINE refuse #-}---- | Insert a vertex, answering the handle it was given. A point already--- present keeps its handle and takes the new payload.-insertVertex-  :: HasPosition vertex-  => vertex-  -> Session s vertex directed undirected face VertexId-insertVertex payload = Session $ \mesh operation ->-  fmap-    (fmap (VertexId . fromIntegral . fst))-    (insertPointCombining (\_ replacement -> replacement) Nothing mesh operation (position payload) payload)---- | Insert at a stated point, answering the handle and whether a site was--- created. 'insertVertex' is this with the point read out of the payload; a--- caller that computed the point — a constraint split, a Steiner refinement —--- states it rather than round-tripping through 'HasPosition'.-insertVertexAt-  :: Point-  -> vertex-  -> Session s vertex directed undirected face (VertexId, InsertionDisposition)-insertVertexAt point payload = Session $ \mesh operation ->-  fmap-    (fmap (\(vertex, disposition) -> (VertexId (fromIntegral vertex), disposition)))-    (insertPointCombining (\_ replacement -> replacement) Nothing mesh operation point payload)---- | 'insertVertexAt' with the walk seeded at a face the caller vouches for ----- typically the face a locate on the just-published value settled on, which--- is exact on the mesh this transaction thawed. The seed is a hint, not an--- authority: an out-of-range face degrades to the unhinted descent, and the--- walk corrects.-insertVertexAtNear-  :: FaceId-  -> Point-  -> vertex-  -> Session s vertex directed undirected face (VertexId, InsertionDisposition)-insertVertexAtNear (FaceId rawSeed) point payload = Session $ \mesh operation ->-  fmap-    (fmap (\(vertex, disposition) -> (VertexId (fromIntegral vertex), disposition)))-    (insertPointCombining-       (\_ replacement -> replacement)-       (Just (fromIntegral rawSeed))-       mesh-       operation-       point-       payload-    )---- | Insert an admitted site while combining an occupied annotation. This is--- package-internal descent from a boundary that already owns coordinate--- admission; only location and annotation gluing remain here.-insertAdmittedVertexAtCombining-  :: (vertex -> vertex -> vertex)-  -> QueryPoint-  -> vertex-  -> Session s vertex directed undirected face (VertexId, InsertionDisposition)-insertAdmittedVertexAtCombining combine queryPoint payload = Session $ \mesh operation ->-  fmap-    (fmap (\(vertex, disposition) -> (VertexId (fromIntegral vertex), disposition)))-    (insertAdmittedPointCombining combine Nothing mesh operation queryPoint payload)---- | Remove the vertex standing at a point, answering 'Nothing' when no vertex--- stands there.------ Keyed by position rather than by handle because removal swap-compacts the--- arenas: every outstanding t'VertexId' may relocate, and over a sequence of--- removals a handle-keyed verb would force the caller to thread every--- relocation by hand. A position is invariant under compaction. The relocation--- is still reported, in the 'RemovalOutcome', for callers holding handles.--- The question is an identity question -- which vertex stands at this exact--- position. A session that has committed to identity work ('removeManyAt',--- 'excise') answers it through the identity index in O(1); one that has not--- answers it with a single walk, because a published index is a lazy rebuild--- and forcing a whole-mesh build to answer one question is the wrong trade.-removeAt-  :: Point-  -> Session s vertex directed undirected face (Maybe (RemovalOutcome vertex))-removeAt point = Session $ \mesh operation -> do-  indexed <- identityIndexActive mesh-  if indexed-    then removeIndexed mesh operation point-    else walkAndRemove mesh operation Nothing point--removeIndexed-  :: MutableDcel s vertex directed undirected face-  -> OperationState s-  -> Point-  -> ST s (Either BuildError (Maybe (RemovalOutcome vertex)))-removeIndexed mesh operation point = do-  located <- lookupPointVertex mesh point-  case located of-    Just vertex -> fmap (fmap Just) (removeMutableOutcome mesh operation vertex)-    Nothing -> pure (Right Nothing)---- | Remove the vertex standing at a point, starting from a caller-supplied--- near vertex -- a locate hint from an external structure such as the--- Delaunay hierarchy. The guess is a hint, not an authority: a slot renamed--- by swap-compaction or out of range degrades to a walk hinted by the--- guess's incident face, and the walk corrects.-removeAtNear-  :: VertexId-  -> Point-  -> Session s vertex directed undirected face (Maybe (RemovalOutcome vertex))-removeAtNear (VertexId rawGuess) point = Session $ \mesh operation -> do-  indexed <- identityIndexActive mesh-  let !guess = fromIntegral rawGuess-      !query = canonicalPoint point-  if indexed-    then removeIndexed mesh operation query-    else do-      vertices <- pointCount mesh-      if guess < 0 || guess >= vertices-        then walkAndRemove mesh operation Nothing query-        else do-          stored <- pointAt mesh guess-          if stored == query-            then fmap (fmap Just) (removeMutableOutcome mesh operation guess)-            else do-              outgoing <- readVertexOut mesh guess-              let interiorFace edge fallback = do-                    face <- readFace mesh edge-                    if face > 0 then pure (Just face) else fallback-              hint <--                if outgoing < 0-                  then pure Nothing-                  else interiorFace outgoing (interiorFace (outgoing `xor` 1) (pure Nothing))-              walkAndRemove mesh operation hint query--walkAndRemove-  :: MutableDcel s vertex directed undirected face-  -> OperationState s-  -> Maybe Int-  -> Point-  -> ST s (Either BuildError (Maybe (RemovalOutcome vertex)))-walkAndRemove mesh operation hint query = do-  located <- locateMutable mesh operation hint query-  case located of-    Left obstruction -> pure (Left obstruction)-    Right (MutableOnVertex vertex) ->-      fmap (fmap Just) (removeMutableOutcome mesh operation vertex)-    Right _ -> pure (Right Nothing)---- | Remove the vertex standing at each point, answering per point in order.--- One session-level decision buys the locate strategy: few removals walk the--- mesh, each a ~O(sqrt n) descent, while many seed the existing mutable--- open-addressed identity section once from coordinate authority and answer--- each question by hash. The guard squares the crossover to avoid a root.-removeManyAt-  :: V.Vector (Point)-  -> Session s vertex directed undirected face (V.Vector (Maybe (RemovalOutcome vertex)))-removeManyAt points =-  withBatchIdentityForLoad (V.length points) (V.mapM removeAt points)---- | Install the mutable identity section only around a dense removal program.--- It is a local section over the coordinate arenas, not a new published cache:--- on every result path it is discarded and freeze glues a lazy @PointIndex@--- back from those arenas. Sparse loads retain the existing locate walk.-withBatchIdentityForLoad-  :: Int-  -> Session s vertex directed undirected face result-  -> Session s vertex directed undirected face result-withBatchIdentityForLoad count (Session action) = Session $ \mesh operation -> do-  vertices <- pointCount mesh-  if count * count > 10 * vertices-    then do-      activated <- activateBatchPointIndex mesh-      case activated of-        Left failure -> pure (Left failure)-        Right () -> do-          outcome <- action mesh operation-          discardBatchPointIndex mesh-          pure outcome-    else action mesh operation--identityCommitted :: Session s vertex directed undirected face Bool-identityCommitted = Session $ \mesh _ -> fmap Right (identityIndexActive mesh)-{-# INLINE identityCommitted #-}---- | 'removeManyAt' with a near vertex per point where the caller has one — a--- hierarchy sample, a previous answer. The batch commits to its load first,--- so the identity-index crossover decides the regime once: a dense batch buys--- its local mutable table and never examines a guess, a sparse one walks from--- its guesses.-removeManyAtNear-  :: V.Vector (Maybe VertexId)-  -> V.Vector (Point)-  -> Session s vertex directed undirected face (V.Vector (Maybe (RemovalOutcome vertex)))-removeManyAtNear guesses points =-  withBatchIdentityForLoad (V.length points) $ do-    indexed <- identityCommitted-    if indexed-      then V.mapM removeAt points-      else-        V.zipWithM-          (\guess point -> maybe (removeAt point) (\vertex -> removeAtNear vertex point) guess)-          guesses-          points---- | Remove a stated vertex. Sound only while the handle still denotes what the--- caller means: the first removal in a sequence compacts the arenas, so a--- handle taken before it may name a different vertex after. @removeAt@ is the--- verb for a sequence; this is the verb for a handle the caller has just been--- given and has not yet let a removal run underneath.-excise-  :: VertexId-  -> Session s vertex directed undirected face (RemovalOutcome vertex)-excise requested@(VertexId raw) = Session $ \mesh operation -> do-  vertices <- pointCount mesh-  if toInteger raw >= toInteger vertices-    then pure (Left (RemovalVertexOutOfRange requested vertices))-    else do-      activatePointIndex mesh-      removeMutableOutcome mesh operation (fromIntegral raw)--removeMutableOutcome-  :: MutableDcel s vertex directed undirected face-  -> OperationState s-  -> Int-  -> ST s (Either BuildError (RemovalOutcome vertex))-removeMutableOutcome mesh operation vertex =-  fmap (fmap removalOutcomeOf) (removeMutable mesh operation vertex)--removalOutcomeOf-  :: (Point, vertex, Maybe (Int, Point))-  -> RemovalOutcome vertex-removalOutcomeOf (point, payload, swapped) =-  RemovalOutcome-    { removalOutcomePoint = point-    , removalOutcomeData = payload-    , removalOutcomeSwap =-        (\(slot, standing) -> (VertexId (fromIntegral slot), standing)) <$> swapped-    }---- | Run an edit sequence: thaw once, edit, freeze once, and publish the--- counters the whole transaction charged.------ The reservation is taken for the peak vertex count, so it is the insertion--- count that sizes it; removals only shrink the mesh and a mixed sequence--- cannot exceed the peak an insert-only sequence of the same count reaches.------ The session cannot escape its callback: the state token is universally--- quantified, so the mesh it addresses is dead by the time the frozen--- triangulation is returned.-withSession-  :: forall mode vertex directed undirected face result-   . Triangulation mode vertex directed undirected face-  -> Int-  -> (forall s. Session s vertex directed undirected face result)-  -> Either BuildError (result, Triangulation mode vertex directed undirected face, BuildStats)-withSession triangulation additional session =-  runTransaction-    id-    DenseTransaction-    triangulation-    additional-    (\mesh operation -> stepSession session mesh operation)---- | The local-edit transaction: copy-on-write pages, publication proportional--- to what the edit dirtied. This is the section for singleton persistent--- verbs, whose one edit cannot amortize a dense copy of the whole mesh.-withLocalSession-  :: forall mode vertex directed undirected face result-   . Triangulation mode vertex directed undirected face-  -> Int-  -> (forall s. Session s vertex directed undirected face result)-  -> Either BuildError (result, Triangulation mode vertex directed undirected face, BuildStats)-withLocalSession triangulation additional session =-  runTransaction-    id-    LocalTransaction-    triangulation-    additional-    (\mesh operation -> stepSession session mesh operation)+import Moonlight.Triangulation.Internal.Session
src-build/Moonlight/Triangulation/SetAlgebra.hs view
@@ -40,7 +40,7 @@   , unitElementDefaults   ) import Moonlight.Triangulation.JoinSemilattice (JoinSemilattice)-import Moonlight.Triangulation.Session+import Moonlight.Triangulation.Internal.Session   ( Session   , insertVertexAt   , refuse@@ -61,7 +61,7 @@ {-# INLINE siteRelation #-}  -- | A valid Delaunay representative of both site sets. Use--- 'Moonlight.Triangulation.Dcel.canonicalize'+-- 'Moonlight.Triangulation.Canonical.canonicalize' -- when construction-independent dense numbering is required. union   :: JoinSemilattice annotation
src-cell-complex/Moonlight/Triangulation/CellComplex.hs view
@@ -60,7 +60,7 @@   , alphaVertexBirth   ) import Moonlight.Triangulation.Dcel qualified as Dcel-import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.HandleDefs   ( DirectedEdgeId,     FaceId (..),     UndirectedEdgeId (..),
src-dcel/Moonlight/Triangulation/Alpha.hs view
@@ -39,7 +39,7 @@   , exactCellSet   ) import Moonlight.Triangulation.Dcel qualified as Dcel-import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.HandleDefs   ( FaceId (..)   , UndirectedEdgeId (..)   , VertexId (..)
+ src-dcel/Moonlight/Triangulation/Canonical.hs view
@@ -0,0 +1,6 @@+-- | Construction-independent resident numbering for explicit global comparison.+module Moonlight.Triangulation.Canonical+  ( canonicalize+  ) where++import Moonlight.Triangulation.Internal.Canonical (canonicalize)
src-dcel/Moonlight/Triangulation/Dcel.hs view
@@ -58,7 +58,7 @@ import GHC.Exts (build) import Moonlight.Triangulation.Internal.BoxedPaged (boxedUnsafeIndex, boxedUpdate) import Moonlight.Triangulation.Internal.Paged (pagedLength, pagedUnsafeIndex)-import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.HandleDefs import Moonlight.Triangulation.Internal.PackedIndex (unpackOptionalIndex) import Moonlight.Triangulation.Internal.Representation import Moonlight.Triangulation.Internal.Types
src-dcel/Moonlight/Triangulation/FloodFillIterator.hs view
@@ -1,9 +1,4 @@-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE DerivingStrategies #-}-{-# LANGUAGE FlexibleInstances #-}---- | Shape queries and face flood fills over immutable triangulations.+-- | Shape queries, face descent, and boundary gluing over an immutable mesh. module Moonlight.Triangulation.FloodFillIterator   ( DistanceMetric (..)   , CircleMetric@@ -19,7 +14,6 @@   , edgesInRectangle   , verticesInRectangle   , floodFillFaces-  , floodFillFacesWithRejectedEdges   , outerFaceFloodFill   , facesAtEvenBarrierDepth   , FaceComponent@@ -41,699 +35,4 @@   , componentBoundaryForFaces   ) where -import Control.DeepSeq (NFData)-import Data.Bifunctor (first)-import qualified Data.IntMap.Strict as IntMap-import qualified Data.IntSet as IntSet-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-  , traceOrientedBoundaryCircuits-  )-import Moonlight.Triangulation.Math-import Moonlight.Triangulation.PointLocation-import Moonlight.Triangulation.Types---- | One non-empty connected set of equally labelled bounded face indices.-newtype FaceComponent = FaceComponent IntSet.IntSet-  deriving stock (Eq, Show, Generic)-  deriving anyclass (NFData)---- | Component faces in ascending DCEL order.-faceComponentFaces :: FaceComponent -> [FaceId]-faceComponentFaces (FaceComponent faces) =-  fmap (FaceId . fromIntegral) (IntSet.toAscList faces)---- | 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-  -- | Exact resident DCEL boundary chain. Consecutive vertices, including-  -- the closing pair, are endpoints of one boundary half-edge.-  , boundaryLoopResidentVertices :: !(NonEmpty VertexId)-  -- | Collinearity-reduced polygon observation of the same resident chain.-  , boundaryLoopVertices :: !(NonEmpty VertexId)-  }-  deriving stock (Eq, Show, Generic)-  deriving anyclass (NFData)---- | The authoritative boundary of one face component.-data RegionBoundary = RegionBoundary-  { regionBoundaryOuterLoop :: !BoundaryLoop-  , regionBoundaryHoleLoops :: ![BoundaryLoop]-  }-  deriving stock (Eq, Show, Generic)-  deriving anyclass (NFData)---- | 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-  | BoundaryCycleDidNotClose !DirectedEdgeId !DirectedEdgeId-  | BoundaryLoopDegenerate ![VertexId]-  | BoundaryOuterLoopCardinality !Int-  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---- | A query shape that can admit points, test edges, and supply a location--- seed.-class DistanceMetric metric where-  metricContainsPoint :: metric -> Point -> Bool-  metricIntersectsEdge :: metric -> Point -> Point -> Bool-  metricStartPoint :: metric -> QueryPoint---- | An admitted center and squared radius.-data CircleMetric = CircleMetric !QueryPoint !RadiusSquared-  deriving stock (Eq, Ord, Show)---- | Typed refusal for an invalid circle query.-data CircleMetricError-  = InvalidCircleCenter !PointValidationError-  | InvalidCircleRadius !RadiusSquaredError-  deriving stock (Eq, Ord, Show)---- | Admitted lower corner, upper corner, and center of an axis-aligned box.-data RectangleMetric = RectangleMetric !(QueryPoint) !(QueryPoint) !(QueryPoint)-  deriving stock (Eq, Ord, Show)---- | Typed refusal for an invalid rectangle query.-data RectangleMetricError-  = InvalidRectangleLower !PointValidationError-  | InvalidRectangleUpper !PointValidationError-  | InvalidRectangleCenter !PointValidationError-  deriving stock (Eq, Ord, Show)---- | A circle metric, or why the radius is unusable.-circleMetric :: Point -> Double -> Either CircleMetricError CircleMetric-circleMetric center radiusSquared =-  CircleMetric-    <$> first InvalidCircleCenter (mkQueryPoint center)-    <*> first InvalidCircleRadius (mkRadiusSquared radiusSquared)---- | An axis-aligned rectangle metric, or why the corners are unusable.-rectangleMetric :: Point -> Point -> Either RectangleMetricError RectangleMetric-rectangleMetric lower@(Point lowerX lowerY) upper@(Point upperX upperY) = do-  queryLower <- either (Left . InvalidRectangleLower) Right (mkQueryPoint lower)-  queryUpper <- either (Left . InvalidRectangleUpper) Right (mkQueryPoint upper)-  queryCenter <--    either-      (Left . InvalidRectangleCenter)-      Right-      (mkQueryPoint (Point ((lowerX + upperX) * 0.5) ((lowerY + upperY) * 0.5)))-  Right (RectangleMetric queryLower queryUpper queryCenter)--instance DistanceMetric CircleMetric where-  metricContainsPoint (CircleMetric center radius) point =-    squaredDistanceWide (queryPointValue center) point <= radiusSquaredValue radius-  metricIntersectsEdge (CircleMetric center radius) from to =-    segmentDistanceSquaredWide from to (queryPointValue center) <= radiusSquaredValue radius-  metricStartPoint (CircleMetric center _) = center--instance DistanceMetric RectangleMetric where-  metricContainsPoint (RectangleMetric lower upper _) (Point x y) =-    lowerX <= upperX && lowerY <= upperY && x >= lowerX && x <= upperX && y >= lowerY && y <= upperY-   where-    Point lowerX lowerY = queryPointValue lower-    Point upperX upperY = queryPointValue upper-  metricIntersectsEdge rectangle from to =-    metricContainsPoint rectangle from-      || metricContainsPoint rectangle to-      || segmentRectangleIntersection rectangle from to-  metricStartPoint (RectangleMetric _ _ center) = center---- | Edges meeting a circle.-edgesInCircle :: Triangulation mode vertex directed undirected face -> Point -> Double -> Either CircleMetricError [UndirectedEdgeId]-edgesInCircle triangulation center radiusSquared =-  edgesInShape triangulation <$> circleMetric center radiusSquared---- | Vertices inside a circle.-verticesInCircle :: Triangulation mode vertex directed undirected face -> Point -> Double -> Either CircleMetricError [VertexId]-verticesInCircle triangulation center radiusSquared =-  verticesInShape triangulation <$> circleMetric center radiusSquared---- | Edges meeting an axis-aligned rectangle.-edgesInRectangle :: Triangulation mode vertex directed undirected face -> Point -> Point -> Either RectangleMetricError [UndirectedEdgeId]-edgesInRectangle triangulation lower upper = edgesInShape triangulation <$> rectangleMetric lower upper---- | Vertices inside an axis-aligned rectangle.-verticesInRectangle :: Triangulation mode vertex directed undirected face -> Point -> Point -> Either RectangleMetricError [VertexId]-verticesInRectangle triangulation lower upper = verticesInShape triangulation <$> rectangleMetric lower upper---- | Edges meeting any metric shape.-edgesInShape :: DistanceMetric metric => Triangulation mode vertex directed undirected face -> metric -> [UndirectedEdgeId]-edgesInShape triangulation metric-  | numVertices triangulation <= 1 = []-  | numInnerFaces triangulation == 0 =-      [edge | edge <- undirectedEdges triangulation, edgeInside edge]-  | otherwise =-      let starts = shapeStartFaces triangulation metric-          (_, accepted, _) = floodFillFacesWithEdges triangulation starts edgeInside-       in map (UndirectedEdgeId . fromIntegral) (IntSet.toAscList accepted)- where-  edgeInside edge =-    let (fromVertex, toVertex) = undirectedEndpoints triangulation edge-     in metricIntersectsEdge metric (vertexPoint triangulation fromVertex) (vertexPoint triangulation toVertex)---- | Vertices inside any metric shape.-verticesInShape :: DistanceMetric metric => Triangulation mode vertex directed undirected face -> metric -> [VertexId]-verticesInShape triangulation metric =-  [ vertex-  | vertex <- candidateVertices-  , metricContainsPoint metric (vertexPoint triangulation vertex)-  ]- where-  edges = edgesInShape triangulation metric-  set = List.foldl' addEndpoints IntSet.empty edges-  addEndpoints acc edge =-    let (VertexId from, VertexId to) = undirectedEndpoints triangulation edge-     in IntSet.insert (fromIntegral from) (IntSet.insert (fromIntegral to) acc)-  candidateVertices-    | numVertices triangulation == 1 = [VertexId 0]-    | otherwise = map (VertexId . fromIntegral) (IntSet.toAscList set)---- | Reach inner faces from the supplied seeds by crossing only admitted edges.-floodFillFaces-  :: Triangulation mode vertex directed undirected face -> [FaceId]-  -> (UndirectedEdgeId -> Bool)-  -> [FaceId]-floodFillFaces triangulation starts canCross =-  let (faces, _, _) = floodFillFacesWithEdges triangulation starts canCross-   in faces---- | The reached section and the edge pairs whose crossing was rejected while--- descending it. This package-internal observation retains the boundary--- witness instead of requiring a later face-wide rediscovery.-floodFillFacesWithRejectedEdges-  :: Triangulation mode vertex directed undirected face -> [FaceId]-  -> (UndirectedEdgeId -> Bool)-  -> ([FaceId], [UndirectedEdgeId])-floodFillFacesWithRejectedEdges triangulation starts canCross =-  let (faces, _, rejected) = floodFillFacesWithEdges triangulation starts canCross-   in (faces, fmap (UndirectedEdgeId . fromIntegral) (IntSet.toAscList rejected))--floodFillFacesWithEdges-  :: Triangulation mode vertex directed undirected face -> [FaceId]-  -> (UndirectedEdgeId -> Bool)-  -> ([FaceId], IntSet.IntSet, IntSet.IntSet)-floodFillFacesWithEdges triangulation starts canCross =-  let (faces, accepted, rejected) = go initialStack initialVisited IntSet.empty IntSet.empty []-   in (reverse faces, accepted, rejected)- where-  valid face@(FaceId value) = face /= outerFace && fromIntegral value < numFaces triangulation-  (initialStack, initialVisited) = List.foldl' enqueueStart ([], IntSet.empty) starts--  enqueueStart state face-    | valid face = enqueue face state-    | otherwise = state--  go [] _ accepted rejected result = (result, accepted, rejected)-  go (face : stack) visited accepted rejected result =-    let (stack', visited', accepted', rejected') =-          foldFaceDirectedEdges'-            triangulation-            face-            expand-            (stack, visited, accepted, rejected)-     in go stack' visited' accepted' rejected' (face : result)--  expand (stack, visited, accepted, rejected) edge =-    let undirected@(UndirectedEdgeId raw) = asUndirected edge-        edgeIndex = fromIntegral raw-        adjacent = incidentFace triangulation (reverseEdge edge)-        edgeAdmission-          | IntSet.member edgeIndex accepted = (True, accepted, rejected)-          | IntSet.member edgeIndex rejected = (False, accepted, rejected)-          | canCross undirected = (True, IntSet.insert edgeIndex accepted, rejected)-          | otherwise = (False, accepted, IntSet.insert edgeIndex rejected)-        (crosses, accepted', rejected') = edgeAdmission-        (stack', visited') =-          if crosses && valid adjacent-            then enqueue adjacent (stack, visited)-            else (stack, visited)-     in (stack', visited', accepted', rejected')--  enqueue face@(FaceId value) (stack, visited)-    | IntSet.member index visited = (stack, visited)-    | otherwise = (face : stack, IntSet.insert index visited)-   where-    index = fromIntegral value---- | Connected components of equally labelled bounded faces. Labels are--- evaluated once; the component carrier is the same 'IntSet' used by descent.-faceComponents-  :: Eq label-  => Triangulation mode vertex directed undirected face-  -> (FaceId -> label)-  -> [(label, FaceComponent)]-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)--  descend remaining =-    case IntSet.minView remaining of-      Nothing -> Nothing-      Just (seedIndex, unseeded) ->-        case labelAt seedIndex of-          Nothing -> descend unseeded-          Just componentLabel ->-            let unvisited =-                  collectComponent componentLabel (Seq.singleton seedIndex) unseeded-                componentFaces = IntSet.difference remaining unvisited-             in Just ((componentLabel, FaceComponent componentFaces), unvisited)--  collectComponent componentLabel queued unvisited =-    case Seq.viewl queued of-      Seq.EmptyL -> unvisited-      faceIndex Seq.:< remainingQueue ->-        let face = FaceId (fromIntegral faceIndex)-            (expandedQueue, remainingUnvisited) =-              foldFaceDirectedEdges'-                triangulation-                face-                (admitAdjacent componentLabel)-                (remainingQueue, unvisited)-         in collectComponent-              componentLabel-              expandedQueue-              remainingUnvisited--  admitAdjacent componentLabel (queued, unvisited) edge =-    if-      canCross (asUndirected edge)-        && adjacent /= outerFace-        && IntSet.member adjacentIndex unvisited-        && labelAt adjacentIndex == Just componentLabel-      then-        ( queued Seq.|> adjacentIndex-        , IntSet.delete adjacentIndex unvisited-        )-      else (queued, unvisited)-   where-    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;--- callers may group equal labels only after this descent has succeeded.-labelledRegionBoundaries-  :: Eq label-  => Triangulation mode vertex directed undirected face-  -> (FaceId -> label)-  -> Either BoundaryObstruction [(label, RegionBoundary)]-labelledRegionBoundaries triangulation labelFace =-  traverse-    (\(label, component) -> (label,) <$> componentBoundary triangulation component)-    (faceComponents triangulation labelFace)---- | 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 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---- | Project exactly the supplied face section into one boundary. This is the--- selected-section descent used by local extension: it does not label or--- flood the complement, and therefore its work is proportional to the faces--- named by the caller plus their constant-degree boundary edges.-componentBoundaryForFaces-  :: Triangulation mode vertex directed undirected face-  -> Set.Set FaceId-  -> Either BoundaryObstruction RegionBoundary-componentBoundaryForFaces triangulation selectedFaces = do-  component <- selectedFaceComponent triangulation selectedFaces-  componentBoundary triangulation component--selectedFaceComponent-  :: Triangulation mode vertex directed undirected face-  -> Set.Set FaceId-  -> Either BoundaryObstruction FaceComponent-selectedFaceComponent triangulation selectedFaces =-  case IntSet.lookupMin indices of-    Just 0 -> Left (BoundaryComponentFaceOutOfRange (FaceId 0) (numFaces triangulation))-    _ ->-      case IntSet.lookupGE (numFaces triangulation) indices of-        Just invalid ->-          Left-            ( BoundaryComponentFaceOutOfRange-                (FaceId (fromIntegral invalid))-                (numFaces triangulation)-            )-        Nothing -> Right (FaceComponent indices)- where-  indices = IntSet.fromList (fmap faceIndex (Set.toAscList selectedFaces))-  faceIndex (FaceId raw) = fromIntegral raw--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-        ( BoundaryComponentFaceOutOfRange-            (FaceId (fromIntegral invalid))-            (numFaces triangulation)-        )-    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-        RegionBoundary-          { regionBoundaryOuterLoop = outerLoop-          , regionBoundaryHoleLoops = holeLoops-          }-    _ -> Left (BoundaryOuterLoopCardinality (length outerLoops))- where-  (outerLoops, holeLoops) =-    partition-      ((== BoundaryCounterClockwise) . boundaryLoopOrientation)-      loops--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 [BoundaryLoop]-traceBoundaryLoops triangulation outgoingByVertex = descend []- where-  descend loops unvisited =-    case IntSet.minView unvisited of-      Nothing -> Right (reverse loops)-      Just (rawStart, _) -> do-        let start = DirectedEdgeId (fromIntegral rawStart)-        (vertices, remaining) <- traceCycle start start unvisited []-        loop <- simplifyBoundaryLoop triangulation vertices-        descend (loop : loops) remaining--  traceCycle start current unvisited reversedVertices =-    let DirectedEdgeId rawCurrent = current-        remaining = IntSet.delete (fromIntegral rawCurrent) unvisited-        accumulated = origin triangulation current : reversedVertices-        VertexId rawTarget = destination triangulation current-     in case IntMap.lookup (fromIntegral rawTarget) outgoingByVertex of-          Just successor-            | successor == start -> Right (reverse accumulated, remaining)-            | let DirectedEdgeId rawSuccessor = successor-            , IntSet.member (fromIntegral rawSuccessor) remaining ->-                traceCycle start successor remaining accumulated-            | otherwise -> Left (BoundaryCycleDidNotClose start successor)-          Nothing -> Left (BoundaryCycleDidNotClose start current)--simplifyBoundaryLoop-  :: Triangulation mode vertex directed undirected face-  -> [VertexId]-  -> Either BoundaryObstruction BoundaryLoop-simplifyBoundaryLoop triangulation vertices = do-  (windingOrder, simplifiedVertices) <--    simplifyBoundaryCycle-      BoundaryLoopDegenerate-      redundant-      winding-      key-      vertices-  residentVertices <--    maybe-      (Left (BoundaryLoopDegenerate vertices))-      Right-      (NonEmpty.nonEmpty vertices)-  case windingOrder of-    GT -> Right (BoundaryLoop BoundaryCounterClockwise residentVertices simplifiedVertices)-    LT -> Right (BoundaryLoop BoundaryClockwise residentVertices simplifiedVertices)-    EQ -> Left (BoundaryLoopDegenerate (NonEmpty.toList simplifiedVertices))- where-  point vertex = vertexPoint triangulation vertex-  redundant previousVertex current nextVertex =-    orient2d (point previousVertex) (point current) (point nextVertex) == EQ-      && onClosedSegment (point previousVertex) (point nextVertex) (point current)-  winding previousVertex current nextVertex =-    orient2d (point previousVertex) (point current) (point nextVertex)-  key vertex = (point vertex, vertex)---- | Inner faces separated from the outer face by an even minimum number of--- barriers. A 0–1 BFS floods freely within one depth before crossing a barrier,--- so a free-ended barrier can be walked around at depth zero while nested--- closed barriers alternate outside and inside.-facesAtEvenBarrierDepth-  :: Triangulation mode vertex directed undirected face-  -> (UndirectedEdgeId -> Bool)-  -> [FaceId]-facesAtEvenBarrierDepth triangulation isBarrier =-  concat (evenLayers (barrierDepthLayers triangulation isBarrier))- where-  evenLayers :: [[FaceId]] -> [[FaceId]]-  evenLayers (outsideLayer : _insideLayer : deeper) =-    outsideLayer : evenLayers deeper-  evenLayers shallow = shallow--barrierDepthLayers-  :: Triangulation mode vertex directed undirected face-  -> (UndirectedEdgeId -> Bool)-  -> [[FaceId]]-barrierDepthLayers triangulation isBarrier =-  map (filter (/= outerFace)) (layers IntSet.empty [outerFace])- where-  known (FaceId value) = fromIntegral value < numFaces triangulation-  key :: FaceId -> Int-  key (FaceId value) = fromIntegral value--  layers visited frontier = case flood visited [] frontier of-    ([], _) -> []-    (layer, visited') -> layer : layers visited' (concatMap (neighbours isBarrier) layer)--  flood visited acc [] = (reverse acc, visited)-  flood visited acc (face : rest)-    | not (known face) || IntSet.member (key face) visited = flood visited acc rest-    | otherwise =-        flood-          (IntSet.insert (key face) visited)-          (face : acc)-          (neighbours (not . isBarrier) face <> rest)--  neighbours admit face =-    [ incidentFace triangulation (reverseEdge edge)-    | edge <- faceDirectedEdges triangulation face-    , admit (asUndirected edge)-    ]---- | Faces reachable from the outer face without crossing a barrier edge.-outerFaceFloodFill :: Triangulation mode vertex directed undirected face -> (UndirectedEdgeId -> Bool) -> [FaceId]-outerFaceFloodFill triangulation canCross = floodFillFaces triangulation starts canCross- where-  starts =-    [ face-    | outerEdge <- faceDirectedEdges triangulation outerFace-    , let edge = asUndirected outerEdge-    , canCross edge-    , let face = incidentFace triangulation (reverseEdge outerEdge)-    , face /= outerFace-    ]---- | The faces a shape's start point lands in.-shapeStartFaces :: DistanceMetric metric => Triangulation mode vertex directed undirected face -> metric -> [FaceId]-shapeStartFaces triangulation metric =-  case locatePoint triangulation (metricStartPoint metric) of-    InFace face -> [face]-    OnEdge edge -> filter (/= outerFace) [incidentFace triangulation edge, incidentFace triangulation (reverseEdge edge)]-    OnVertex vertex ->-      intSetToFaces-        (List.foldl' (\set edge -> let FaceId value = incidentFace triangulation edge in if value == 0 then set else IntSet.insert (fromIntegral value) set) IntSet.empty (vertexOutgoingEdges triangulation vertex))-    OutsideConvexHull _ ->-      [ incidentFace triangulation (reverseEdge edge)-      | edge <- faceDirectedEdges triangulation outerFace-      , let from = vertexPoint triangulation (origin triangulation edge)-      , let to = vertexPoint triangulation (destination triangulation edge)-      , metricIntersectsEdge metric from to-      , incidentFace triangulation (reverseEdge edge) /= outerFace-      ]-    EmptyTriangulation -> []- where-  intSetToFaces = map (FaceId . fromIntegral) . IntSet.toAscList--segmentRectangleIntersection :: RectangleMetric -> Point -> Point -> Bool-segmentRectangleIntersection (RectangleMetric lowerQuery upperQuery _) from to-  | lx > ux || ly > uy = False-  | lower == upper = onClosedSegment from to lower-  | otherwise = any (uncurry (segmentsIntersect from to)) boundaries- where-  lower@(Point lx ly) = queryPointValue lowerQuery-  upper@(Point ux uy) = queryPointValue upperQuery-  boundaries =-    [ (Point lx ly, Point lx uy)-    , (Point lx uy, Point ux uy)-    , (Point ux uy, Point ux ly)-    , (Point ux ly, Point lx ly)-    ]+import Moonlight.Triangulation.Internal.FloodFillIterator
src-dcel/Moonlight/Triangulation/Handles/Dynamic.hs view
@@ -65,7 +65,7 @@   ) where  import Moonlight.Triangulation.Dcel qualified as Dcel-import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.HandleDefs import Moonlight.Triangulation.LineSideInfo (LineSideInfo) import Moonlight.Triangulation.Math qualified as Math import Moonlight.Triangulation.Types
src-dcel/Moonlight/Triangulation/Handles/HandleDefs.hs view
@@ -1,12 +1,17 @@-{-# LANGUAGE GeneralizedNewtypeDeriving #-}---- | The identifier family and the arithmetic on it: a directed edge's twin is--- its index complement, so orientation is a bit rather than a lookup.+-- | Opaque resident identifiers and lawful orientation operations.+--+-- Identifiers can only be obtained from a triangulation. Their numeric+-- projections support serialization and diagnostics without allowing a caller+-- to manufacture a handle that belongs to no mesh. module Moonlight.Triangulation.Handles.HandleDefs-  ( VertexId (..)-  , FaceId (..)-  , DirectedEdgeId (..)-  , UndirectedEdgeId (..)+  ( VertexId+  , unVertexId+  , FaceId+  , unFaceId+  , DirectedEdgeId+  , unDirectedEdgeId+  , UndirectedEdgeId+  , unUndirectedEdgeId   , reverseEdge   , asUndirected   , normalizedDirected@@ -15,56 +20,4 @@   , isNormalized   ) where -import Control.DeepSeq (NFData)-import Data.Bits (shiftL, shiftR, xor, (.&.))-import Data.Word (Word32)---- | Index of a vertex in the immutable DCEL.-newtype VertexId = VertexId { unVertexId :: Word32 }-  deriving stock (Show)-  deriving newtype (Eq, Ord, NFData)---- | Index of a face in the immutable DCEL; zero denotes the outer face.-newtype FaceId = FaceId { unFaceId :: Word32 }-  deriving stock (Show)-  deriving newtype (Eq, Ord, NFData)---- | Oriented half-edge index. Twin orientations differ only in the low bit.-newtype DirectedEdgeId = DirectedEdgeId { unDirectedEdgeId :: Word32 }-  deriving stock (Show)-  deriving newtype (Eq, Ord, NFData)---- | Index of a twin pair, with orientation forgotten.-newtype UndirectedEdgeId = UndirectedEdgeId { unUndirectedEdgeId :: Word32 }-  deriving stock (Show)-  deriving newtype (Eq, Ord, NFData)---- | Select the opposite orientation of the same undirected edge.-reverseEdge :: DirectedEdgeId -> DirectedEdgeId-reverseEdge (DirectedEdgeId edge) = DirectedEdgeId (edge `xor` 1)-{-# INLINE reverseEdge #-}---- | Forget a directed edge's orientation.-asUndirected :: DirectedEdgeId -> UndirectedEdgeId-asUndirected (DirectedEdgeId edge) = UndirectedEdgeId (edge `shiftR` 1)-{-# INLINE asUndirected #-}---- | Select the even-indexed orientation of an undirected edge.-normalizedDirected :: UndirectedEdgeId -> DirectedEdgeId-normalizedDirected (UndirectedEdgeId edge) = DirectedEdgeId (edge `shiftL` 1)-{-# INLINE normalizedDirected #-}---- | Select the odd-indexed orientation of an undirected edge.-reversedDirected :: UndirectedEdgeId -> DirectedEdgeId-reversedDirected edge = reverseEdge (normalizedDirected edge)-{-# INLINE reversedDirected #-}---- | Both orientations, normalized first and reversed second.-directedPair :: UndirectedEdgeId -> (DirectedEdgeId, DirectedEdgeId)-directedPair edge = (normalizedDirected edge, reversedDirected edge)-{-# INLINE directedPair #-}---- | Whether a directed edge is the normalized orientation of its pair.-isNormalized :: DirectedEdgeId -> Bool-isNormalized (DirectedEdgeId edge) = edge .&. 1 == 0-{-# INLINE isNormalized #-}+import Moonlight.Triangulation.Internal.HandleDefs
src-dcel/Moonlight/Triangulation/Handles/Iterators/FixedIterators.hs view
@@ -16,7 +16,7 @@   ) where  import Moonlight.Triangulation.Dcel-import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.HandleDefs import Moonlight.Triangulation.Types  -- | Every vertex identifier, ascending.
src-dcel/Moonlight/Triangulation/Handles/Iterators/HullIterator.hs view
@@ -7,7 +7,7 @@   ) where  import Moonlight.Triangulation.Dcel-import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.HandleDefs import Moonlight.Triangulation.Types  -- | The convex hull as directed edges, in order.
src-dcel/Moonlight/Triangulation/Internal/Canonical.hs view
@@ -15,7 +15,7 @@ import qualified Data.Vector.Unboxed as U import qualified Data.Vector.Unboxed.Mutable as MUV import Moonlight.Triangulation.Dcel (numFaces, numUndirectedEdges, numVertices, vertexData)-import Moonlight.Triangulation.Handles.HandleDefs (VertexId (..))+import Moonlight.Triangulation.Internal.HandleDefs (VertexId (..)) import Moonlight.Triangulation.Internal.Mutable import Moonlight.Triangulation.Internal.Paged (Paged, pagedUnsafeIndex) import Moonlight.Triangulation.Internal.Representation (Triangulation (..))
src-dcel/Moonlight/Triangulation/Internal/CellSet.hs view
@@ -37,7 +37,7 @@   , vertexPoint   ) import Moonlight.Triangulation.Exact (ExactPoint, exactPointFromPoint)-import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.HandleDefs   ( FaceId (..)   , UndirectedEdgeId (..)   , VertexId (..)
src-dcel/Moonlight/Triangulation/Internal/DcelOperations/Chain.hs view
@@ -18,7 +18,7 @@ import Control.Monad (forM_, when) import Control.Monad.ST (ST) import Data.STRef (writeSTRef)-import Moonlight.Triangulation.Handles.HandleDefs (VertexId (..))+import Moonlight.Triangulation.Internal.HandleDefs (VertexId (..)) import Moonlight.Triangulation.Internal.DcelOperations.Legalize (legalizeScratch) import Moonlight.Triangulation.Internal.DcelOperations.Twin (reverseIndex) import Moonlight.Triangulation.Internal.Mutable
src-dcel/Moonlight/Triangulation/Internal/DcelOperations/FlipRewrite.hs view
@@ -13,7 +13,7 @@  import Control.Monad (unless) import Control.Monad.ST (ST)-import Moonlight.Triangulation.Handles.HandleDefs (UndirectedEdgeId (..))+import Moonlight.Triangulation.Internal.HandleDefs (UndirectedEdgeId (..)) import Moonlight.Triangulation.Internal.DcelOperations.Twin (reverseIndex) import Moonlight.Triangulation.Internal.Mutable   ( MutableDcel
src-dcel/Moonlight/Triangulation/Internal/DcelOperations/Hull.hs view
@@ -24,7 +24,7 @@ import Control.Monad (forM_, when) import Control.Monad.ST (ST) import Data.STRef (writeSTRef)-import Moonlight.Triangulation.Handles.HandleDefs (DirectedEdgeId (..), FaceId (..))+import Moonlight.Triangulation.Internal.HandleDefs (DirectedEdgeId (..), FaceId (..)) import Moonlight.Triangulation.Internal.DcelOperations.CandidateArena   ( seedGenericPairInArena   )
src-dcel/Moonlight/Triangulation/Internal/DcelOperations/Subdivide.hs view
@@ -13,7 +13,7 @@  import Control.Monad (unless, void, when) import Control.Monad.ST (ST)-import Moonlight.Triangulation.Handles.HandleDefs (UndirectedEdgeId (..))+import Moonlight.Triangulation.Internal.HandleDefs (UndirectedEdgeId (..)) import Moonlight.Triangulation.Internal.DcelOperations.Legalize (legalizeScratch) import Moonlight.Triangulation.Internal.DcelOperations.Twin (reverseIndex) import Moonlight.Triangulation.Internal.Mutable
+ src-dcel/Moonlight/Triangulation/Internal/FloodFillIterator.hs view
@@ -0,0 +1,739 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE FlexibleInstances #-}++-- | Shape queries and face flood fills over immutable triangulations.+module Moonlight.Triangulation.Internal.FloodFillIterator+  ( DistanceMetric (..)+  , CircleMetric+  , CircleMetricError (..)+  , RectangleMetric+  , RectangleMetricError (..)+  , circleMetric+  , rectangleMetric+  , edgesInShape+  , verticesInShape+  , edgesInCircle+  , verticesInCircle+  , edgesInRectangle+  , verticesInRectangle+  , floodFillFaces+  , floodFillFacesWithRejectedEdges+  , outerFaceFloodFill+  , facesAtEvenBarrierDepth+  , FaceComponent+  , faceComponentFaces+  , BoundaryOrientation (..)+  , BoundaryLoop+  , boundaryLoopOrientation+  , boundaryLoopResidentVertices+  , boundaryLoopVertices+  , RegionBoundary+  , regionBoundaryOuterLoop+  , regionBoundaryHoleLoops+  , BoundaryObstruction (..)+  , faceComponents+  , faceComponentsBy+  , labelledRegionBoundaries+  , componentBoundaryLoops+  , componentBoundary+  , componentBoundaryForFaces+  ) where++import Control.DeepSeq (NFData)+import Data.Bifunctor (first)+import qualified Data.IntMap.Strict as IntMap+import qualified Data.IntSet as IntSet+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.Internal.HandleDefs+import Moonlight.Triangulation.Handles.Iterators.FixedIterators (undirectedEdges)+import Moonlight.Triangulation.Internal.BoundaryCycle+  ( simplifyBoundaryCycle+  , traceOrientedBoundaryCircuits+  )+import Moonlight.Triangulation.Math+import Moonlight.Triangulation.PointLocation+import Moonlight.Triangulation.Types++-- | One non-empty connected set of equally labelled bounded face indices.+newtype FaceComponent = FaceComponent IntSet.IntSet+  deriving stock (Eq, Show, Generic)+  deriving anyclass (NFData)++-- | Component faces in ascending DCEL order.+faceComponentFaces :: FaceComponent -> [FaceId]+faceComponentFaces (FaceComponent faces) =+  fmap (FaceId . fromIntegral) (IntSet.toAscList faces)++-- | 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+  -- | Exact resident DCEL boundary chain. Consecutive vertices, including+  -- the closing pair, are endpoints of one boundary half-edge.+  , boundaryLoopResidentVertices :: !(NonEmpty VertexId)+  -- | Collinearity-reduced polygon observation of the same resident chain.+  , boundaryLoopVertices :: !(NonEmpty VertexId)+  }+  deriving stock (Eq, Show, Generic)+  deriving anyclass (NFData)++-- | The authoritative boundary of one face component.+data RegionBoundary = RegionBoundary+  { regionBoundaryOuterLoop :: !BoundaryLoop+  , regionBoundaryHoleLoops :: ![BoundaryLoop]+  }+  deriving stock (Eq, Show, Generic)+  deriving anyclass (NFData)++-- | 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+  | BoundaryCycleDidNotClose !DirectedEdgeId !DirectedEdgeId+  | BoundaryLoopDegenerate ![VertexId]+  | BoundaryOuterLoopCardinality !Int+  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++-- | A query shape that can admit points, test edges, and supply a location+-- seed.+class DistanceMetric metric where+  metricContainsPoint :: metric -> Point -> Bool+  metricIntersectsEdge :: metric -> Point -> Point -> Bool+  metricStartPoint :: metric -> QueryPoint++-- | An admitted center and squared radius.+data CircleMetric = CircleMetric !QueryPoint !RadiusSquared+  deriving stock (Eq, Ord, Show)++-- | Typed refusal for an invalid circle query.+data CircleMetricError+  = InvalidCircleCenter !PointValidationError+  | InvalidCircleRadius !RadiusSquaredError+  deriving stock (Eq, Ord, Show)++-- | Admitted lower corner, upper corner, and center of an axis-aligned box.+data RectangleMetric = RectangleMetric !(QueryPoint) !(QueryPoint) !(QueryPoint)+  deriving stock (Eq, Ord, Show)++-- | Typed refusal for an invalid rectangle query.+data RectangleMetricError+  = InvalidRectangleLower !PointValidationError+  | InvalidRectangleUpper !PointValidationError+  | InvalidRectangleCenter !PointValidationError+  deriving stock (Eq, Ord, Show)++-- | A circle metric, or why the radius is unusable.+circleMetric :: Point -> Double -> Either CircleMetricError CircleMetric+circleMetric center radiusSquared =+  CircleMetric+    <$> first InvalidCircleCenter (mkQueryPoint center)+    <*> first InvalidCircleRadius (mkRadiusSquared radiusSquared)++-- | An axis-aligned rectangle metric, or why the corners are unusable.+rectangleMetric :: Point -> Point -> Either RectangleMetricError RectangleMetric+rectangleMetric lower@(Point lowerX lowerY) upper@(Point upperX upperY) = do+  queryLower <- either (Left . InvalidRectangleLower) Right (mkQueryPoint lower)+  queryUpper <- either (Left . InvalidRectangleUpper) Right (mkQueryPoint upper)+  queryCenter <-+    either+      (Left . InvalidRectangleCenter)+      Right+      (mkQueryPoint (Point ((lowerX + upperX) * 0.5) ((lowerY + upperY) * 0.5)))+  Right (RectangleMetric queryLower queryUpper queryCenter)++instance DistanceMetric CircleMetric where+  metricContainsPoint (CircleMetric center radius) point =+    squaredDistanceWide (queryPointValue center) point <= radiusSquaredValue radius+  metricIntersectsEdge (CircleMetric center radius) from to =+    segmentDistanceSquaredWide from to (queryPointValue center) <= radiusSquaredValue radius+  metricStartPoint (CircleMetric center _) = center++instance DistanceMetric RectangleMetric where+  metricContainsPoint (RectangleMetric lower upper _) (Point x y) =+    lowerX <= upperX && lowerY <= upperY && x >= lowerX && x <= upperX && y >= lowerY && y <= upperY+   where+    Point lowerX lowerY = queryPointValue lower+    Point upperX upperY = queryPointValue upper+  metricIntersectsEdge rectangle from to =+    metricContainsPoint rectangle from+      || metricContainsPoint rectangle to+      || segmentRectangleIntersection rectangle from to+  metricStartPoint (RectangleMetric _ _ center) = center++-- | Edges meeting a circle.+edgesInCircle :: Triangulation mode vertex directed undirected face -> Point -> Double -> Either CircleMetricError [UndirectedEdgeId]+edgesInCircle triangulation center radiusSquared =+  edgesInShape triangulation <$> circleMetric center radiusSquared++-- | Vertices inside a circle.+verticesInCircle :: Triangulation mode vertex directed undirected face -> Point -> Double -> Either CircleMetricError [VertexId]+verticesInCircle triangulation center radiusSquared =+  verticesInShape triangulation <$> circleMetric center radiusSquared++-- | Edges meeting an axis-aligned rectangle.+edgesInRectangle :: Triangulation mode vertex directed undirected face -> Point -> Point -> Either RectangleMetricError [UndirectedEdgeId]+edgesInRectangle triangulation lower upper = edgesInShape triangulation <$> rectangleMetric lower upper++-- | Vertices inside an axis-aligned rectangle.+verticesInRectangle :: Triangulation mode vertex directed undirected face -> Point -> Point -> Either RectangleMetricError [VertexId]+verticesInRectangle triangulation lower upper = verticesInShape triangulation <$> rectangleMetric lower upper++-- | Edges meeting any metric shape.+edgesInShape :: DistanceMetric metric => Triangulation mode vertex directed undirected face -> metric -> [UndirectedEdgeId]+edgesInShape triangulation metric+  | numVertices triangulation <= 1 = []+  | numInnerFaces triangulation == 0 =+      [edge | edge <- undirectedEdges triangulation, edgeInside edge]+  | otherwise =+      let starts = shapeStartFaces triangulation metric+          (_, accepted, _) = floodFillFacesWithEdges triangulation starts edgeInside+       in map (UndirectedEdgeId . fromIntegral) (IntSet.toAscList accepted)+ where+  edgeInside edge =+    let (fromVertex, toVertex) = undirectedEndpoints triangulation edge+     in metricIntersectsEdge metric (vertexPoint triangulation fromVertex) (vertexPoint triangulation toVertex)++-- | Vertices inside any metric shape.+verticesInShape :: DistanceMetric metric => Triangulation mode vertex directed undirected face -> metric -> [VertexId]+verticesInShape triangulation metric =+  [ vertex+  | vertex <- candidateVertices+  , metricContainsPoint metric (vertexPoint triangulation vertex)+  ]+ where+  edges = edgesInShape triangulation metric+  set = List.foldl' addEndpoints IntSet.empty edges+  addEndpoints acc edge =+    let (VertexId from, VertexId to) = undirectedEndpoints triangulation edge+     in IntSet.insert (fromIntegral from) (IntSet.insert (fromIntegral to) acc)+  candidateVertices+    | numVertices triangulation == 1 = [VertexId 0]+    | otherwise = map (VertexId . fromIntegral) (IntSet.toAscList set)++-- | Reach inner faces from the supplied seeds by crossing only admitted edges.+floodFillFaces+  :: Triangulation mode vertex directed undirected face -> [FaceId]+  -> (UndirectedEdgeId -> Bool)+  -> [FaceId]+floodFillFaces triangulation starts canCross =+  let (faces, _, _) = floodFillFacesWithEdges triangulation starts canCross+   in faces++-- | The reached section and the edge pairs whose crossing was rejected while+-- descending it. This package-internal observation retains the boundary+-- witness instead of requiring a later face-wide rediscovery.+floodFillFacesWithRejectedEdges+  :: Triangulation mode vertex directed undirected face -> [FaceId]+  -> (UndirectedEdgeId -> Bool)+  -> ([FaceId], [UndirectedEdgeId])+floodFillFacesWithRejectedEdges triangulation starts canCross =+  let (faces, _, rejected) = floodFillFacesWithEdges triangulation starts canCross+   in (faces, fmap (UndirectedEdgeId . fromIntegral) (IntSet.toAscList rejected))++floodFillFacesWithEdges+  :: Triangulation mode vertex directed undirected face -> [FaceId]+  -> (UndirectedEdgeId -> Bool)+  -> ([FaceId], IntSet.IntSet, IntSet.IntSet)+floodFillFacesWithEdges triangulation starts canCross =+  let (faces, accepted, rejected) = go initialStack initialVisited IntSet.empty IntSet.empty []+   in (reverse faces, accepted, rejected)+ where+  valid face@(FaceId value) = face /= outerFace && fromIntegral value < numFaces triangulation+  (initialStack, initialVisited) = List.foldl' enqueueStart ([], IntSet.empty) starts++  enqueueStart state face+    | valid face = enqueue face state+    | otherwise = state++  go [] _ accepted rejected result = (result, accepted, rejected)+  go (face : stack) visited accepted rejected result =+    let (stack', visited', accepted', rejected') =+          foldFaceDirectedEdges'+            triangulation+            face+            expand+            (stack, visited, accepted, rejected)+     in go stack' visited' accepted' rejected' (face : result)++  expand (stack, visited, accepted, rejected) edge =+    let undirected@(UndirectedEdgeId raw) = asUndirected edge+        edgeIndex = fromIntegral raw+        adjacent = incidentFace triangulation (reverseEdge edge)+        edgeAdmission+          | IntSet.member edgeIndex accepted = (True, accepted, rejected)+          | IntSet.member edgeIndex rejected = (False, accepted, rejected)+          | canCross undirected = (True, IntSet.insert edgeIndex accepted, rejected)+          | otherwise = (False, accepted, IntSet.insert edgeIndex rejected)+        (crosses, accepted', rejected') = edgeAdmission+        (stack', visited') =+          if crosses && valid adjacent+            then enqueue adjacent (stack, visited)+            else (stack, visited)+     in (stack', visited', accepted', rejected')++  enqueue face@(FaceId value) (stack, visited)+    | IntSet.member index visited = (stack, visited)+    | otherwise = (face : stack, IntSet.insert index visited)+   where+    index = fromIntegral value++-- | Connected components of equally labelled bounded faces. Labels are+-- evaluated once; the component carrier is the same 'IntSet' used by descent.+faceComponents+  :: Eq label+  => Triangulation mode vertex directed undirected face+  -> (FaceId -> label)+  -> [(label, FaceComponent)]+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)++  descend remaining =+    case IntSet.minView remaining of+      Nothing -> Nothing+      Just (seedIndex, unseeded) ->+        case labelAt seedIndex of+          Nothing -> descend unseeded+          Just componentLabel ->+            let unvisited =+                  collectComponent componentLabel (Seq.singleton seedIndex) unseeded+                componentFaces = IntSet.difference remaining unvisited+             in Just ((componentLabel, FaceComponent componentFaces), unvisited)++  collectComponent componentLabel queued unvisited =+    case Seq.viewl queued of+      Seq.EmptyL -> unvisited+      faceIndex Seq.:< remainingQueue ->+        let face = FaceId (fromIntegral faceIndex)+            (expandedQueue, remainingUnvisited) =+              foldFaceDirectedEdges'+                triangulation+                face+                (admitAdjacent componentLabel)+                (remainingQueue, unvisited)+         in collectComponent+              componentLabel+              expandedQueue+              remainingUnvisited++  admitAdjacent componentLabel (queued, unvisited) edge =+    if+      canCross (asUndirected edge)+        && adjacent /= outerFace+        && IntSet.member adjacentIndex unvisited+        && labelAt adjacentIndex == Just componentLabel+      then+        ( queued Seq.|> adjacentIndex+        , IntSet.delete adjacentIndex unvisited+        )+      else (queued, unvisited)+   where+    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;+-- callers may group equal labels only after this descent has succeeded.+labelledRegionBoundaries+  :: Eq label+  => Triangulation mode vertex directed undirected face+  -> (FaceId -> label)+  -> Either BoundaryObstruction [(label, RegionBoundary)]+labelledRegionBoundaries triangulation labelFace =+  traverse+    (\(label, component) -> (label,) <$> componentBoundary triangulation component)+    (faceComponents triangulation labelFace)++-- | 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 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++-- | Project exactly the supplied face section into one boundary. This is the+-- selected-section descent used by local extension: it does not label or+-- flood the complement, and therefore its work is proportional to the faces+-- named by the caller plus their constant-degree boundary edges.+componentBoundaryForFaces+  :: Triangulation mode vertex directed undirected face+  -> Set.Set FaceId+  -> Either BoundaryObstruction RegionBoundary+componentBoundaryForFaces triangulation selectedFaces = do+  component <- selectedFaceComponent triangulation selectedFaces+  componentBoundary triangulation component++selectedFaceComponent+  :: Triangulation mode vertex directed undirected face+  -> Set.Set FaceId+  -> Either BoundaryObstruction FaceComponent+selectedFaceComponent triangulation selectedFaces =+  case IntSet.lookupMin indices of+    Just 0 -> Left (BoundaryComponentFaceOutOfRange (FaceId 0) (numFaces triangulation))+    _ ->+      case IntSet.lookupGE (numFaces triangulation) indices of+        Just invalid ->+          Left+            ( BoundaryComponentFaceOutOfRange+                (FaceId (fromIntegral invalid))+                (numFaces triangulation)+            )+        Nothing -> Right (FaceComponent indices)+ where+  indices = IntSet.fromList (fmap faceIndex (Set.toAscList selectedFaces))+  faceIndex (FaceId raw) = fromIntegral raw++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+        ( BoundaryComponentFaceOutOfRange+            (FaceId (fromIntegral invalid))+            (numFaces triangulation)+        )+    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+        RegionBoundary+          { regionBoundaryOuterLoop = outerLoop+          , regionBoundaryHoleLoops = holeLoops+          }+    _ -> Left (BoundaryOuterLoopCardinality (length outerLoops))+ where+  (outerLoops, holeLoops) =+    partition+      ((== BoundaryCounterClockwise) . boundaryLoopOrientation)+      loops++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 [BoundaryLoop]+traceBoundaryLoops triangulation outgoingByVertex = descend []+ where+  descend loops unvisited =+    case IntSet.minView unvisited of+      Nothing -> Right (reverse loops)+      Just (rawStart, _) -> do+        let start = DirectedEdgeId (fromIntegral rawStart)+        (vertices, remaining) <- traceCycle start start unvisited []+        loop <- simplifyBoundaryLoop triangulation vertices+        descend (loop : loops) remaining++  traceCycle start current unvisited reversedVertices =+    let DirectedEdgeId rawCurrent = current+        remaining = IntSet.delete (fromIntegral rawCurrent) unvisited+        accumulated = origin triangulation current : reversedVertices+        VertexId rawTarget = destination triangulation current+     in case IntMap.lookup (fromIntegral rawTarget) outgoingByVertex of+          Just successor+            | successor == start -> Right (reverse accumulated, remaining)+            | let DirectedEdgeId rawSuccessor = successor+            , IntSet.member (fromIntegral rawSuccessor) remaining ->+                traceCycle start successor remaining accumulated+            | otherwise -> Left (BoundaryCycleDidNotClose start successor)+          Nothing -> Left (BoundaryCycleDidNotClose start current)++simplifyBoundaryLoop+  :: Triangulation mode vertex directed undirected face+  -> [VertexId]+  -> Either BoundaryObstruction BoundaryLoop+simplifyBoundaryLoop triangulation vertices = do+  (windingOrder, simplifiedVertices) <-+    simplifyBoundaryCycle+      BoundaryLoopDegenerate+      redundant+      winding+      key+      vertices+  residentVertices <-+    maybe+      (Left (BoundaryLoopDegenerate vertices))+      Right+      (NonEmpty.nonEmpty vertices)+  case windingOrder of+    GT -> Right (BoundaryLoop BoundaryCounterClockwise residentVertices simplifiedVertices)+    LT -> Right (BoundaryLoop BoundaryClockwise residentVertices simplifiedVertices)+    EQ -> Left (BoundaryLoopDegenerate (NonEmpty.toList simplifiedVertices))+ where+  point vertex = vertexPoint triangulation vertex+  redundant previousVertex current nextVertex =+    orient2d (point previousVertex) (point current) (point nextVertex) == EQ+      && onClosedSegment (point previousVertex) (point nextVertex) (point current)+  winding previousVertex current nextVertex =+    orient2d (point previousVertex) (point current) (point nextVertex)+  key vertex = (point vertex, vertex)++-- | Inner faces separated from the outer face by an even minimum number of+-- barriers. A 0–1 BFS floods freely within one depth before crossing a barrier,+-- so a free-ended barrier can be walked around at depth zero while nested+-- closed barriers alternate outside and inside.+facesAtEvenBarrierDepth+  :: Triangulation mode vertex directed undirected face+  -> (UndirectedEdgeId -> Bool)+  -> [FaceId]+facesAtEvenBarrierDepth triangulation isBarrier =+  concat (evenLayers (barrierDepthLayers triangulation isBarrier))+ where+  evenLayers :: [[FaceId]] -> [[FaceId]]+  evenLayers (outsideLayer : _insideLayer : deeper) =+    outsideLayer : evenLayers deeper+  evenLayers shallow = shallow++barrierDepthLayers+  :: Triangulation mode vertex directed undirected face+  -> (UndirectedEdgeId -> Bool)+  -> [[FaceId]]+barrierDepthLayers triangulation isBarrier =+  map (filter (/= outerFace)) (layers IntSet.empty [outerFace])+ where+  known (FaceId value) = fromIntegral value < numFaces triangulation+  key :: FaceId -> Int+  key (FaceId value) = fromIntegral value++  layers visited frontier = case flood visited [] frontier of+    ([], _) -> []+    (layer, visited') -> layer : layers visited' (concatMap (neighbours isBarrier) layer)++  flood visited acc [] = (reverse acc, visited)+  flood visited acc (face : rest)+    | not (known face) || IntSet.member (key face) visited = flood visited acc rest+    | otherwise =+        flood+          (IntSet.insert (key face) visited)+          (face : acc)+          (neighbours (not . isBarrier) face <> rest)++  neighbours admit face =+    [ incidentFace triangulation (reverseEdge edge)+    | edge <- faceDirectedEdges triangulation face+    , admit (asUndirected edge)+    ]++-- | Faces reachable from the outer face without crossing a barrier edge.+outerFaceFloodFill :: Triangulation mode vertex directed undirected face -> (UndirectedEdgeId -> Bool) -> [FaceId]+outerFaceFloodFill triangulation canCross = floodFillFaces triangulation starts canCross+ where+  starts =+    [ face+    | outerEdge <- faceDirectedEdges triangulation outerFace+    , let edge = asUndirected outerEdge+    , canCross edge+    , let face = incidentFace triangulation (reverseEdge outerEdge)+    , face /= outerFace+    ]++-- | The faces a shape's start point lands in.+shapeStartFaces :: DistanceMetric metric => Triangulation mode vertex directed undirected face -> metric -> [FaceId]+shapeStartFaces triangulation metric =+  case locatePoint triangulation (metricStartPoint metric) of+    InFace face -> [face]+    OnEdge edge -> filter (/= outerFace) [incidentFace triangulation edge, incidentFace triangulation (reverseEdge edge)]+    OnVertex vertex ->+      intSetToFaces+        (List.foldl' (\set edge -> let FaceId value = incidentFace triangulation edge in if value == 0 then set else IntSet.insert (fromIntegral value) set) IntSet.empty (vertexOutgoingEdges triangulation vertex))+    OutsideConvexHull _ ->+      [ incidentFace triangulation (reverseEdge edge)+      | edge <- faceDirectedEdges triangulation outerFace+      , let from = vertexPoint triangulation (origin triangulation edge)+      , let to = vertexPoint triangulation (destination triangulation edge)+      , metricIntersectsEdge metric from to+      , incidentFace triangulation (reverseEdge edge) /= outerFace+      ]+    EmptyTriangulation -> []+ where+  intSetToFaces = map (FaceId . fromIntegral) . IntSet.toAscList++segmentRectangleIntersection :: RectangleMetric -> Point -> Point -> Bool+segmentRectangleIntersection (RectangleMetric lowerQuery upperQuery _) from to+  | lx > ux || ly > uy = False+  | lower == upper = onClosedSegment from to lower+  | otherwise = any (uncurry (segmentsIntersect from to)) boundaries+ where+  lower@(Point lx ly) = queryPointValue lowerQuery+  upper@(Point ux uy) = queryPointValue upperQuery+  boundaries =+    [ (Point lx ly, Point lx uy)+    , (Point lx uy, Point ux uy)+    , (Point ux uy, Point ux ly)+    , (Point ux ly, Point lx ly)+    ]
+ src-dcel/Moonlight/Triangulation/Internal/HandleDefs.hs view
@@ -0,0 +1,70 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++-- | The identifier family and the arithmetic on it: a directed edge's twin is+-- its index complement, so orientation is a bit rather than a lookup.+module Moonlight.Triangulation.Internal.HandleDefs+  ( VertexId (..)+  , FaceId (..)+  , DirectedEdgeId (..)+  , UndirectedEdgeId (..)+  , reverseEdge+  , asUndirected+  , normalizedDirected+  , reversedDirected+  , directedPair+  , isNormalized+  ) where++import Control.DeepSeq (NFData)+import Data.Bits (shiftL, shiftR, xor, (.&.))+import Data.Word (Word32)++-- | Index of a vertex in the immutable DCEL.+newtype VertexId = VertexId { unVertexId :: Word32 }+  deriving stock (Show)+  deriving newtype (Eq, Ord, NFData)++-- | Index of a face in the immutable DCEL; zero denotes the outer face.+newtype FaceId = FaceId { unFaceId :: Word32 }+  deriving stock (Show)+  deriving newtype (Eq, Ord, NFData)++-- | Oriented half-edge index. Twin orientations differ only in the low bit.+newtype DirectedEdgeId = DirectedEdgeId { unDirectedEdgeId :: Word32 }+  deriving stock (Show)+  deriving newtype (Eq, Ord, NFData)++-- | Index of a twin pair, with orientation forgotten.+newtype UndirectedEdgeId = UndirectedEdgeId { unUndirectedEdgeId :: Word32 }+  deriving stock (Show)+  deriving newtype (Eq, Ord, NFData)++-- | Select the opposite orientation of the same undirected edge.+reverseEdge :: DirectedEdgeId -> DirectedEdgeId+reverseEdge (DirectedEdgeId edge) = DirectedEdgeId (edge `xor` 1)+{-# INLINE reverseEdge #-}++-- | Forget a directed edge's orientation.+asUndirected :: DirectedEdgeId -> UndirectedEdgeId+asUndirected (DirectedEdgeId edge) = UndirectedEdgeId (edge `shiftR` 1)+{-# INLINE asUndirected #-}++-- | Select the even-indexed orientation of an undirected edge.+normalizedDirected :: UndirectedEdgeId -> DirectedEdgeId+normalizedDirected (UndirectedEdgeId edge) = DirectedEdgeId (edge `shiftL` 1)+{-# INLINE normalizedDirected #-}++-- | Select the odd-indexed orientation of an undirected edge.+reversedDirected :: UndirectedEdgeId -> DirectedEdgeId+reversedDirected edge = reverseEdge (normalizedDirected edge)+{-# INLINE reversedDirected #-}++-- | Both orientations, normalized first and reversed second.+directedPair :: UndirectedEdgeId -> (DirectedEdgeId, DirectedEdgeId)+directedPair edge = (normalizedDirected edge, reversedDirected edge)+{-# INLINE directedPair #-}++-- | Whether a directed edge is the normalized orientation of its pair.+isNormalized :: DirectedEdgeId -> Bool+isNormalized (DirectedEdgeId edge) = edge .&. 1 == 0+{-# INLINE isNormalized #-}
src-dcel/Moonlight/Triangulation/Internal/Mutable.hs view
@@ -121,7 +121,7 @@   , writeSTRef   ) import Data.Word (Word8, Word32)-import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.HandleDefs   ( DirectedEdgeId (..)   , FaceId (..)   , UndirectedEdgeId (..)
src-dcel/Moonlight/Triangulation/Internal/Region/Publication.hs view
@@ -31,7 +31,7 @@   , regionBoundaryHoleLoops   , regionBoundaryOuterLoop   )-import Moonlight.Triangulation.Handles.HandleDefs (FaceId, VertexId)+import Moonlight.Triangulation.Internal.HandleDefs (FaceId, VertexId) import Moonlight.Triangulation.Internal.BoundaryCycle   ( rotateCycleLeast   , simplifyBoundaryCycle
src-dcel/Moonlight/Triangulation/Internal/Region/Types.hs view
@@ -25,7 +25,7 @@   , SegmentRelation   ) import Moonlight.Triangulation.FloodFillIterator (BoundaryObstruction)-import Moonlight.Triangulation.Handles.HandleDefs (FaceId, VertexId)+import Moonlight.Triangulation.Internal.HandleDefs (FaceId, VertexId) import Moonlight.Triangulation.Internal.ExactSegmentEvents   ( ExactSegmentEventObstruction   )
src-dcel/Moonlight/Triangulation/Internal/Representation.hs view
@@ -49,7 +49,7 @@ import Data.Traversable (foldMapDefault) import qualified Data.Vector as V import Data.Word (Word8, Word32)-import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.HandleDefs   ( FaceId (..)   , UndirectedEdgeId (..)   , VertexId (..)
src-dcel/Moonlight/Triangulation/Internal/Types.hs view
@@ -48,7 +48,7 @@   ) where  import Control.DeepSeq (NFData)-import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.HandleDefs   ( DirectedEdgeId   , FaceId   , UndirectedEdgeId
+ src-dcel/Moonlight/Triangulation/Internal/Validation.hs view
@@ -0,0 +1,528 @@+{-# LANGUAGE BangPatterns #-}++-- | Discharge: the invariants the constructors guarantee, checkable on a value+-- built by any route.+module Moonlight.Triangulation.Internal.Validation+  ( validateTopology+  , ValidationClosureStats (..)+  , validateTopologyClosure+  , validateTopologyClosureWithStats+  , validateDelaunay+  , validateTriangulation+  , triangulationIsValid+  , faceArea+  , faceMinimumAngleDegrees+  ) where++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.Internal.HandleDefs+import Moonlight.Triangulation.Handles.Iterators.FixedIterators (allFaces, directedEdges, undirectedEdges, vertices)+import Moonlight.Triangulation.Internal.PackedIndex (noIndex)+import Moonlight.Triangulation.Math+import Moonlight.Triangulation.Internal.Representation+import Moonlight.Triangulation.Internal.Types++-- | Every structural invariant violated, not the first.+validateTopology :: Triangulation mode vertex directed undirected face -> [InvariantViolation]+validateTopology triangulation =+  structuralViolations ++ orientationViolations+ where+  verticesCount = numVertices triangulation+  halfCount = numDirectedEdges triangulation+  edgeCount = numUndirectedEdges triangulation+  facesCount = numFaces triangulation++  -- Geometry descends only after the finite DCEL has glued structurally.+  -- Reading triangle coordinates through malformed links would turn a typed+  -- validation failure into an indexing crash.+  structuralViolations =+    cardinalityViolations+      ++ rangeViolations+      ++ edgeViolations+      ++ faceViolations+      ++ vertexViolations+      ++ eulerViolations++  orientationViolations+    | not (null structuralViolations) = []+    | otherwise =+        [ InnerFaceNotCounterClockwise face+        | face <- allFaces triangulation+        , face /= outerFace+        , Just (first, second, third) <- [innerFaceVertices triangulation face]+        , orient2d+            (vertexPoint triangulation first)+            (vertexPoint triangulation second)+            (vertexPoint triangulation third)+            /= GT+        ]++  cardinalityViolations =+    [ CoordinatePlaneLengthMismatch pointXCount pointYCount+    | pointXCount /= pointYCount+    ]+      ++ [VertexOutgoingLengthMismatch vertexOutCount verticesCount | vertexOutCount /= verticesCount]+      ++ [VertexPayloadLengthMismatch vertexPayloadCount verticesCount | vertexPayloadCount /= verticesCount]+      ++ [TopologyArenaLengthMismatch topologyLength (4 * halfCount) | not halfArraysEqual]+      ++ [DirectedPayloadLengthMismatch directedPayloadCount halfCount | directedPayloadCount /= halfCount]+      ++ [UndirectedPayloadLengthMismatch undirectedPayloadCount edgeCount | undirectedPayloadCount /= edgeCount]+      ++ [DirectedEdgeCountOdd halfCount | odd halfCount]+      ++ [ConstraintLengthMismatch constraintLength edgeCount | constraintLength /= edgeCount]+      ++ [ NonCanonicalConstraintFlag (UndirectedEdgeId (fromIntegral index)) flag+         | index <- [0 .. pagedLength (triConstraint triangulation) - 1]+         , let flag = pagedUnsafeIndex (triConstraint triangulation) index+         , flag /= 0 && flag /= 1+         ]+      ++ [CachedConstraintCountMismatch (triConstraintCount triangulation) actualConstraintCount | triConstraintCount triangulation /= actualConstraintCount]+      ++ [ CachedConstraintIndexMismatch+         | constraintLength == edgeCount+         , triConstraintEdges triangulation /= indexedConstraintEdges+         ]+      ++ [MissingOuterFace | facesCount == 0]+      ++ [FacePayloadLengthMismatch facePayloadCount facesCount | facePayloadCount /= facesCount]++  pointXCount = pagedLength (triPointX triangulation)+  pointYCount = pagedLength (triPointY triangulation)+  vertexOutCount = pagedLength (triVertexOut triangulation)+  vertexPayloadCount = boxedPagedLength (triVertexData triangulation)+  topologyLength = pagedLength (triHalfTopology triangulation)+  directedPayloadCount = boxedPagedLength (triDirectedData triangulation)+  undirectedPayloadCount = boxedPagedLength (triUndirectedData triangulation)+  constraintLength = pagedLength (triConstraint triangulation)+  facePayloadCount = boxedPagedLength (triFaceData triangulation)+  actualConstraintCount = pagedFoldl' (\count flag -> if flag == 1 then count + 1 else count) 0 (triConstraint triangulation)+  halfArraysEqual = topologyLength == 4 * halfCount++  indexedConstraintEdges =+    IntSet.fromAscList+      [ index+      | index <- [0 .. edgeCount - 1]+      , pagedUnsafeIndex (triConstraint triangulation) index == 1+      ]++  rangeViolations =+    [ EdgeOriginOutOfRange (DirectedEdgeId (fromIntegral index)) (VertexId value) verticesCount+    | index <- [0 .. halfCount - 1]+    , let value = pagedUnsafeIndex (triHalfTopology triangulation) (4 * index)+    , fromIntegral value >= verticesCount+    ]+      ++ [ EdgeNextOutOfRange (DirectedEdgeId (fromIntegral index)) (DirectedEdgeId value) halfCount+         | index <- [0 .. halfCount - 1]+         , let value = pagedUnsafeIndex (triHalfTopology triangulation) (4 * index + 1)+         , fromIntegral value >= halfCount+         ]+      ++ [ EdgePreviousOutOfRange (DirectedEdgeId (fromIntegral index)) (DirectedEdgeId value) halfCount+         | index <- [0 .. halfCount - 1]+         , let value = pagedUnsafeIndex (triHalfTopology triangulation) (4 * index + 2)+         , fromIntegral value >= halfCount+         ]+      ++ [ EdgeFaceOutOfRange (DirectedEdgeId (fromIntegral index)) (FaceId value) facesCount+         | index <- [0 .. halfCount - 1]+         , let value = pagedUnsafeIndex (triHalfTopology triangulation) (4 * index + 3)+         , fromIntegral value >= facesCount+         ]+      ++ [ VertexOutgoingOutOfRange (VertexId (fromIntegral index)) (DirectedEdgeId value) halfCount+         | index <- [0 .. pagedLength (triVertexOut triangulation) - 1]+         , let value = pagedUnsafeIndex (triVertexOut triangulation) index+         , value /= noIndex+         , fromIntegral value >= halfCount+         ]+      ++ [ FaceAdjacentOutOfRange (FaceId (fromIntegral index)) (DirectedEdgeId value) halfCount+         | index <- [0 .. facesCount - 1]+         , let value = pagedUnsafeIndex (triFaceEdge triangulation) index+         , value /= noIndex+         , fromIntegral value >= halfCount+         ]++  edgeViolations+    | not halfArraysEqual || odd halfCount || not (null rangeViolations) = []+    | otherwise = concatMap validateEdge (directedEdges triangulation)++  validateEdge edge@(DirectedEdgeId raw) =+    let index = fromIntegral raw+        nextEdge = next triangulation edge+        previousEdge = previous triangulation edge+        twinEdge = reverseEdge edge+        local =+          [ EdgeNextPreviousMismatch edge nextEdge+          | validEdge nextEdge && previous triangulation nextEdge /= edge+          ]+            ++ [ EdgePreviousNextMismatch edge previousEdge+               | validEdge previousEdge && next triangulation previousEdge /= edge+               ]+            ++ [EdgeDoubleReversalMismatch edge | reverseEdge twinEdge /= edge]+            ++ [EdgeSelfLinkedNext edge | nextEdge == edge && halfCount > 2]+            ++ [EdgeSelfLinkedPrevious edge | previousEdge == edge && halfCount > 2]+        innerCycle =+          if incidentFace triangulation edge /= outerFace && validEdge nextEdge && validEdge previousEdge+            then+              [ InnerFaceNotTriangularAtEdge edge+              | next triangulation (next triangulation nextEdge) /= edge+              ]+            else []+     in if index < halfCount then local ++ innerCycle else []++  validEdge (DirectedEdgeId value) = fromIntegral value < halfCount++  faceViolations+    | not (null rangeViolations) = []+    | otherwise = concatMap validateFace (allFaces triangulation)+  validateFace face@(FaceId _) =+    case adjacentEdge triangulation face of+      Nothing+        | face == outerFace && halfCount == 0 -> []+        | otherwise -> [FaceMissingAdjacentEdge face]+      Just edge ->+        [ FaceRepresentativeMismatch face edge representedFace+        | let representedFace = incidentFace triangulation edge+        , representedFace /= face+        ]+          ++ [ InnerFaceVertexCardinalityMismatch face (length faceVertexIds) (length (nub faceVertexIds))+             | face /= outerFace+             , let faceVertexIds = faceVertices triangulation face+             , length faceVertexIds /= 3 || length (nub faceVertexIds) /= 3+             ]++  vertexViolations+    | vertexOutCount /= verticesCount || not (null rangeViolations) = []+    | otherwise = concatMap validateVertex (vertices triangulation)+  validateVertex vertex = case vertexOutEdge triangulation vertex of+    Nothing+      | verticesCount <= 1 -> []+      | otherwise -> [ConnectedVertexMissingOutgoing vertex]+    Just edge ->+      [ VertexOutgoingOriginMismatch vertex edge actualOrigin+      | let actualOrigin = origin triangulation edge+      , actualOrigin /= vertex+      ]++  eulerViolations+    | not (null cardinalityViolations) || verticesCount < 2 = []+    | numInnerFaces triangulation == 0 =+        [ CollinearEdgeCountMismatch (verticesCount - 1) edgeCount+        | edgeCount /= verticesCount - 1+        ]+    | otherwise =+        [ EulerCharacteristicMismatch eulerCharacteristic+        | eulerCharacteristic /= 2+        ]+   where+    eulerCharacteristic = verticesCount - edgeCount + facesCount++-- | Apply the same DCEL edge, face, vertex, and orientation laws as+-- 'validateTopology', but only to a certified local closure.  The caller+-- supplies the admitted inner faces and the interface pairs; the collar faces+-- on the other side of those pairs are included here so a protected source+-- cannot be changed behind the local transaction.  Cardinality and Euler+-- observations remain global scalar invariants and are deliberately not+-- rebuilt from the resident mesh.+validateTopologyClosure+  :: IntSet.IntSet+  -> IntSet.IntSet+  -> Triangulation mode vertex directed undirected face+  -> [InvariantViolation]+validateTopologyClosure admittedFaces interfacePairs triangulation =+  snd (validateTopologyClosureWithStats admittedFaces interfacePairs triangulation)++validateTopologyClosureWithStats+  :: IntSet.IntSet+  -> IntSet.IntSet+  -> Triangulation mode vertex directed undirected face+  -> (ValidationClosureStats, [InvariantViolation])+validateTopologyClosureWithStats admittedFaces interfacePairs triangulation =+  ( ValidationClosureStats+      { validationClosureFaces = IntSet.size selectedFaces+      , validationClosureDirectedEdges = IntSet.size selectedEdges+      , validationClosureVertices = IntSet.size selectedVertices+      , validationClosureInterfacePairs = IntSet.size interfacePairs+      , validationClosureConstraintPairs = IntSet.size selectedPairs+      }+  , rangeViolations ++ edgeViolations ++ faceViolations ++ vertexViolations ++ orientationViolations ++ constraintViolations+  )+ where+  verticesCount = numVertices triangulation+  halfCount = numDirectedEdges triangulation+  facesCount = numFaces triangulation++  collarFaces =+    IntSet.fromList+      [ rawFace+      | rawPair <- IntSet.toAscList interfacePairs+      , rawPair >= 0+      , rawPair < numUndirectedEdges triangulation+      , let edge = UndirectedEdgeId (fromIntegral rawPair)+      , let (forward, backward) = directedPair edge+      , rawFace <- fmap faceRaw [incidentFace triangulation forward, incidentFace triangulation backward]+      , rawFace > 0+      ]+  selectedFaces = IntSet.union admittedFaces collarFaces++  selectedFaceEdges =+    IntSet.fromList+      [ rawEdge+      | rawFace <- IntSet.toAscList selectedFaces+      , edge <- faceEdgesBounded (FaceId (fromIntegral rawFace))+      , rawEdge <- [edgeRaw edge, edgeRaw (reverseEdge edge)]+      ]+  selectedInterfaceEdges =+    IntSet.fromList+      [ rawEdge+      | rawPair <- IntSet.toAscList interfacePairs+      , rawPair >= 0+      , rawPair <= (maxBound - 1) `quot` 2+      , rawEdge <- [2 * rawPair, 2 * rawPair + 1]+      ]+  selectedEdges = IntSet.union selectedFaceEdges selectedInterfaceEdges+  selectedVertices =+    IntSet.fromList+      [ rawVertex+      | rawEdge <- IntSet.toAscList selectedEdges+      , rawEdge >= 0+      , rawEdge < halfCount+      , edgeRaw (reverseEdge (DirectedEdgeId (fromIntegral rawEdge))) < halfCount+      , rawVertex <-+          [ vertexRaw (origin triangulation (DirectedEdgeId (fromIntegral rawEdge)))+          , vertexRaw (destination triangulation (DirectedEdgeId (fromIntegral rawEdge)))+          ]+      , rawVertex < verticesCount+      ]++  rangeViolations =+    [ EdgeOriginOutOfRange edge vertex verticesCount+    | rawEdge <- IntSet.toAscList selectedEdges+    , rawEdge >= 0+    , let edge = DirectedEdgeId (fromIntegral rawEdge)+    , rawEdge < halfCount+    , let vertex = origin triangulation edge+    , vertexRaw vertex >= verticesCount+    ]+      ++ [ EdgeNextOutOfRange edge nextEdge halfCount+         | rawEdge <- IntSet.toAscList selectedEdges+         , rawEdge >= 0+         , rawEdge < halfCount+         , let edge = DirectedEdgeId (fromIntegral rawEdge)+         , let nextEdge = next triangulation edge+         , edgeRaw nextEdge >= halfCount+         ]+      ++ [ EdgePreviousOutOfRange edge previousEdge halfCount+         | rawEdge <- IntSet.toAscList selectedEdges+         , rawEdge >= 0+         , rawEdge < halfCount+         , let edge = DirectedEdgeId (fromIntegral rawEdge)+         , let previousEdge = previous triangulation edge+         , edgeRaw previousEdge >= halfCount+         ]+      ++ [ EdgeFaceOutOfRange edge face facesCount+         | rawEdge <- IntSet.toAscList selectedEdges+         , rawEdge >= 0+         , rawEdge < halfCount+         , let edge = DirectedEdgeId (fromIntegral rawEdge)+         , let face = incidentFace triangulation edge+         , faceRaw face >= facesCount+         ]+      ++ [ FaceAdjacentOutOfRange face edge halfCount+         | rawFace <- IntSet.toAscList selectedFaces+         , rawFace > 0+         , rawFace < facesCount+         , let face = FaceId (fromIntegral rawFace)+         , Just edge <- [adjacentEdge triangulation face]+         , edgeRaw edge >= halfCount+         ]++  edgeViolations =+    concatMap validateEdge (IntSet.toAscList selectedEdges)++  validateEdge rawEdge+    | rawEdge < 0 || rawEdge >= halfCount = []+    | otherwise =+        let edge = DirectedEdgeId (fromIntegral rawEdge)+            nextEdge = next triangulation edge+            previousEdge = previous triangulation edge+            twinEdge = reverseEdge edge+            nextValid = edgeRaw nextEdge < halfCount+            previousValid = edgeRaw previousEdge < halfCount+            nextNextValid = nextValid && edgeRaw (next triangulation nextEdge) < halfCount+            innerCycle =+              if incidentFace triangulation edge /= outerFace && nextValid && previousValid && nextNextValid+                then [InnerFaceNotTriangularAtEdge edge | next triangulation (next triangulation nextEdge) /= edge]+                else []+         in [ EdgeNextPreviousMismatch edge nextEdge+            | nextValid && previous triangulation nextEdge /= edge+            ]+              ++ [ EdgePreviousNextMismatch edge previousEdge+                 | previousValid && next triangulation previousEdge /= edge+                 ]+              ++ [EdgeDoubleReversalMismatch edge | reverseEdge twinEdge /= edge]+              ++ [EdgeSelfLinkedNext edge | nextEdge == edge && halfCount > 2]+              ++ [EdgeSelfLinkedPrevious edge | previousEdge == edge && halfCount > 2]+              ++ innerCycle++  faceViolations = concatMap validateFace (IntSet.toAscList selectedFaces)++  validateFace rawFace+    | rawFace <= 0 || rawFace >= facesCount = []+    | otherwise =+      let face = FaceId (fromIntegral rawFace)+       in case adjacentEdge triangulation face of+          Nothing -> [FaceMissingAdjacentEdge face]+          Just edge+            | edgeRaw edge >= halfCount -> []+            | otherwise ->+                let representedFace = incidentFace triangulation edge+                    (faceEdges, faceVertices') = triangleEdgesAndVertices face+                 in [FaceRepresentativeMismatch face edge representedFace | representedFace /= face]+                      ++ [ InnerFaceVertexCardinalityMismatch face (length faceVertices') (length (nub faceVertices'))+                         | rawFace > 0+                         , length faceVertices' /= 3 || length (nub faceVertices') /= 3+                         ]+                      ++ [InnerFaceNotTriangularAtEdge edge+                         | rawFace > 0+                         , not (triangleClosed faceEdges)+                         ]++  triangleEdgesAndVertices face =+    let edges = faceEdgesBounded face+        vertices' = fmap (vertexRaw . origin triangulation) edges+     in (edges, vertices')++  triangleClosed edges =+    case edges of+      [first, _, third] -> next triangulation third == first+      _ -> False++  vertexViolations = concatMap validateVertex (IntSet.toAscList selectedVertices)++  validateVertex rawVertex =+    let vertex = VertexId (fromIntegral rawVertex)+     in case vertexOutEdge triangulation vertex of+          Nothing+            | verticesCount > 1 -> [ConnectedVertexMissingOutgoing vertex]+            | otherwise -> []+          Just edge+            | edgeRaw edge >= halfCount ->+                [VertexOutgoingOutOfRange vertex edge halfCount]+            | otherwise ->+                [ VertexOutgoingOriginMismatch vertex edge actualOrigin+                | let actualOrigin = origin triangulation edge+                , actualOrigin /= vertex+                ]++  orientationViolations =+    [ InnerFaceNotCounterClockwise face+    | rawFace <- IntSet.toAscList selectedFaces+    , rawFace > 0+    , let face = FaceId (fromIntegral rawFace)+    , let (_, vertices') = triangleEdgesAndVertices face+    , all (< verticesCount) vertices'+    , [first, second, third] <- [fmap (VertexId . fromIntegral) vertices']+    , orient2d (vertexPoint triangulation first) (vertexPoint triangulation second) (vertexPoint triangulation third) /= GT+    ]++  constraintViolations =+    [ NonCanonicalConstraintFlag edge flag+    | rawPair <- IntSet.toAscList selectedPairs+    , rawPair >= 0+    , let edge = UndirectedEdgeId (fromIntegral rawPair)+    , rawPair < numUndirectedEdges triangulation+    , let flag = pagedUnsafeIndex (triConstraint triangulation) rawPair+    , flag /= 0 && flag /= 1+    ]+      ++ [ CachedConstraintIndexMismatch+         | rawPair <- IntSet.toAscList selectedPairs+         , rawPair >= 0+         , rawPair < numUndirectedEdges triangulation+         , let flag = pagedUnsafeIndex (triConstraint triangulation) rawPair+         , (flag == 1) /= IntSet.member rawPair (triConstraintEdges triangulation)+         ]++  selectedPairs = IntSet.fromList [rawEdge `quot` 2 | rawEdge <- IntSet.toAscList selectedEdges]++  faceEdgesBounded face =+    case face of+      FaceId raw+        | toInteger raw <= 0 || toInteger raw >= toInteger facesCount -> []+      _ -> adjacentEdges+   where+    adjacentEdges = case adjacentEdge triangulation face of+      Nothing -> []+      Just start+        | edgeRaw start >= halfCount -> []+        | otherwise ->+            let second = next triangulation start+             in if edgeRaw second >= halfCount+                  then [start]+                  else+                    let third = next triangulation second+                     in if edgeRaw third >= halfCount+                          then [start, second]+                          else [start, second, third]++  edgeRaw (DirectedEdgeId raw) = fromIntegral raw+  vertexRaw (VertexId raw) = fromIntegral raw+  faceRaw (FaceId raw) = fromIntegral raw++-- | Every edge whose circumcircle is not empty.+validateDelaunay :: Triangulation mode vertex directed undirected face -> [InvariantViolation]+validateDelaunay triangulation = concatMap validateEdge (undirectedEdges triangulation)+ where+  validateEdge edge+    | isConstraintEdge triangulation edge = []+    | isBoundaryEdge triangulation edge = []+    | otherwise =+        let directed = normalizedDirected edge+            twin = reverseEdge directed+         in case (innerFaceDirectedEdges triangulation (incidentFace triangulation directed), innerFaceDirectedEdges triangulation (incidentFace triangulation twin)) of+              (Just _, Just _) ->+                let a = vertexPoint triangulation (origin triangulation directed)+                    b = vertexPoint triangulation (destination triangulation directed)+                    c = vertexPoint triangulation (origin triangulation (previous triangulation directed))+                    d = vertexPoint triangulation (origin triangulation (previous triangulation twin))+                    convex = orient2d c d b == GT && orient2d d c a == GT+                    circle = inCircle a b c d+                    illegal = convex && (circle == GT || (circle == EQ && orderedPair c d < orderedPair a b))+                 in [LocallyIllegalDelaunayEdge edge | illegal]+              _ -> [DelaunayIncidentFaceNotTriangular edge]++-- | Topology first; the Delaunay property only if the topology holds.+validateTriangulation+  :: Triangulation mode vertex directed undirected face+  -> [InvariantViolation]+validateTriangulation triangulation =+  let topology = validateTopology triangulation+   in if null topology+        then validateDelaunay triangulation+        else topology++-- | Whether 'validateTriangulation' is empty.+triangulationIsValid+  :: Triangulation mode vertex directed undirected face+  -> Bool+triangulationIsValid = null . validateTriangulation++-- | Signed area, or 'Nothing' where the face is not a triangle.+faceArea :: Triangulation mode vertex directed undirected face -> FaceId -> Maybe Double+faceArea triangulation face = do+  (v0, v1, v2) <- innerFaceVertices triangulation face+  pure (triangleArea (vertexPoint triangulation v0) (vertexPoint triangulation v1) (vertexPoint triangulation v2))++-- | Smallest interior angle, in degrees.+faceMinimumAngleDegrees :: Triangulation mode vertex directed undirected face -> FaceId -> Maybe Double+faceMinimumAngleDegrees triangulation face = do+  (v0, v1, v2) <- innerFaceVertices triangulation face+  let p0 = vertexPoint triangulation v0+      p1 = vertexPoint triangulation v1+      p2 = vertexPoint triangulation v2+      a = sqrt (squaredDistance p1 p2)+      b = sqrt (squaredDistance p2 p0)+      c = sqrt (squaredDistance p0 p1)+  if min a (min b c) <= 0+    then Nothing+    else Just (minimum [angle b c a, angle c a b, angle a b c])+ where+  angle left right opposite = acos (clamp ((left * left + right * right - opposite * opposite) / (2 * left * right))) * 180 / pi+  clamp = max (-1) . min 1
src-dcel/Moonlight/Triangulation/IntersectionIterator.hs view
@@ -16,7 +16,7 @@ import Data.Ord (comparing) import Data.Void (Void, absurd) import Moonlight.Triangulation.Dcel-import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.HandleDefs import Moonlight.Triangulation.Handles.Iterators.FixedIterators (undirectedEdges, vertices) import Moonlight.Triangulation.Math import Moonlight.Triangulation.PointLocation
src-dcel/Moonlight/Triangulation/PointLocation.hs view
@@ -9,7 +9,7 @@  import Data.List (find) import Moonlight.Triangulation.Dcel-import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.HandleDefs import Moonlight.Triangulation.Handles.Iterators.FixedIterators import Moonlight.Triangulation.Internal.FaceProbe import Moonlight.Triangulation.Math
src-dcel/Moonlight/Triangulation/Validation.hs view
@@ -1,12 +1,6 @@-{-# LANGUAGE BangPatterns #-}---- | Discharge: the invariants the constructors guarantee, checkable on a value--- built by any route.+-- | Whole-value DCEL validation and face-quality observations. module Moonlight.Triangulation.Validation   ( validateTopology-  , ValidationClosureStats (..)-  , validateTopologyClosure-  , validateTopologyClosureWithStats   , validateDelaunay   , validateTriangulation   , triangulationIsValid@@ -14,515 +8,4 @@   , faceMinimumAngleDegrees   ) where -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-import Moonlight.Triangulation.Handles.Iterators.FixedIterators (allFaces, directedEdges, undirectedEdges, vertices)-import Moonlight.Triangulation.Internal.PackedIndex (noIndex)-import Moonlight.Triangulation.Math-import Moonlight.Triangulation.Internal.Representation-import Moonlight.Triangulation.Internal.Types---- | Every structural invariant violated, not the first.-validateTopology :: Triangulation mode vertex directed undirected face -> [InvariantViolation]-validateTopology triangulation =-  structuralViolations ++ orientationViolations- where-  verticesCount = numVertices triangulation-  halfCount = numDirectedEdges triangulation-  edgeCount = numUndirectedEdges triangulation-  facesCount = numFaces triangulation--  -- Geometry descends only after the finite DCEL has glued structurally.-  -- Reading triangle coordinates through malformed links would turn a typed-  -- validation failure into an indexing crash.-  structuralViolations =-    cardinalityViolations-      ++ rangeViolations-      ++ edgeViolations-      ++ faceViolations-      ++ vertexViolations-      ++ eulerViolations--  orientationViolations-    | not (null structuralViolations) = []-    | otherwise =-        [ InnerFaceNotCounterClockwise face-        | face <- allFaces triangulation-        , face /= outerFace-        , Just (first, second, third) <- [innerFaceVertices triangulation face]-        , orient2d-            (vertexPoint triangulation first)-            (vertexPoint triangulation second)-            (vertexPoint triangulation third)-            /= GT-        ]--  cardinalityViolations =-    [ CoordinatePlaneLengthMismatch pointXCount pointYCount-    | pointXCount /= pointYCount-    ]-      ++ [VertexOutgoingLengthMismatch vertexOutCount verticesCount | vertexOutCount /= verticesCount]-      ++ [VertexPayloadLengthMismatch vertexPayloadCount verticesCount | vertexPayloadCount /= verticesCount]-      ++ [TopologyArenaLengthMismatch topologyLength (4 * halfCount) | not halfArraysEqual]-      ++ [DirectedPayloadLengthMismatch directedPayloadCount halfCount | directedPayloadCount /= halfCount]-      ++ [UndirectedPayloadLengthMismatch undirectedPayloadCount edgeCount | undirectedPayloadCount /= edgeCount]-      ++ [DirectedEdgeCountOdd halfCount | odd halfCount]-      ++ [ConstraintLengthMismatch constraintLength edgeCount | constraintLength /= edgeCount]-      ++ [ NonCanonicalConstraintFlag (UndirectedEdgeId (fromIntegral index)) flag-         | index <- [0 .. pagedLength (triConstraint triangulation) - 1]-         , let flag = pagedUnsafeIndex (triConstraint triangulation) index-         , flag /= 0 && flag /= 1-         ]-      ++ [CachedConstraintCountMismatch (triConstraintCount triangulation) actualConstraintCount | triConstraintCount triangulation /= actualConstraintCount]-      ++ [ CachedConstraintIndexMismatch-         | constraintLength == edgeCount-         , triConstraintEdges triangulation /= indexedConstraintEdges-         ]-      ++ [MissingOuterFace | facesCount == 0]-      ++ [FacePayloadLengthMismatch facePayloadCount facesCount | facePayloadCount /= facesCount]--  pointXCount = pagedLength (triPointX triangulation)-  pointYCount = pagedLength (triPointY triangulation)-  vertexOutCount = pagedLength (triVertexOut triangulation)-  vertexPayloadCount = boxedPagedLength (triVertexData triangulation)-  topologyLength = pagedLength (triHalfTopology triangulation)-  directedPayloadCount = boxedPagedLength (triDirectedData triangulation)-  undirectedPayloadCount = boxedPagedLength (triUndirectedData triangulation)-  constraintLength = pagedLength (triConstraint triangulation)-  facePayloadCount = boxedPagedLength (triFaceData triangulation)-  actualConstraintCount = pagedFoldl' (\count flag -> if flag == 1 then count + 1 else count) 0 (triConstraint triangulation)-  halfArraysEqual = topologyLength == 4 * halfCount--  indexedConstraintEdges =-    IntSet.fromAscList-      [ index-      | index <- [0 .. edgeCount - 1]-      , pagedUnsafeIndex (triConstraint triangulation) index == 1-      ]--  rangeViolations =-    [ EdgeOriginOutOfRange (DirectedEdgeId (fromIntegral index)) (VertexId value) verticesCount-    | index <- [0 .. halfCount - 1]-    , let value = pagedUnsafeIndex (triHalfTopology triangulation) (4 * index)-    , fromIntegral value >= verticesCount-    ]-      ++ [ EdgeNextOutOfRange (DirectedEdgeId (fromIntegral index)) (DirectedEdgeId value) halfCount-         | index <- [0 .. halfCount - 1]-         , let value = pagedUnsafeIndex (triHalfTopology triangulation) (4 * index + 1)-         , fromIntegral value >= halfCount-         ]-      ++ [ EdgePreviousOutOfRange (DirectedEdgeId (fromIntegral index)) (DirectedEdgeId value) halfCount-         | index <- [0 .. halfCount - 1]-         , let value = pagedUnsafeIndex (triHalfTopology triangulation) (4 * index + 2)-         , fromIntegral value >= halfCount-         ]-      ++ [ EdgeFaceOutOfRange (DirectedEdgeId (fromIntegral index)) (FaceId value) facesCount-         | index <- [0 .. halfCount - 1]-         , let value = pagedUnsafeIndex (triHalfTopology triangulation) (4 * index + 3)-         , fromIntegral value >= facesCount-         ]-      ++ [ VertexOutgoingOutOfRange (VertexId (fromIntegral index)) (DirectedEdgeId value) halfCount-         | index <- [0 .. pagedLength (triVertexOut triangulation) - 1]-         , let value = pagedUnsafeIndex (triVertexOut triangulation) index-         , value /= noIndex-         , fromIntegral value >= halfCount-         ]-      ++ [ FaceAdjacentOutOfRange (FaceId (fromIntegral index)) (DirectedEdgeId value) halfCount-         | index <- [0 .. facesCount - 1]-         , let value = pagedUnsafeIndex (triFaceEdge triangulation) index-         , value /= noIndex-         , fromIntegral value >= halfCount-         ]--  edgeViolations-    | not halfArraysEqual || odd halfCount || not (null rangeViolations) = []-    | otherwise = concatMap validateEdge (directedEdges triangulation)--  validateEdge edge@(DirectedEdgeId raw) =-    let index = fromIntegral raw-        nextEdge = next triangulation edge-        previousEdge = previous triangulation edge-        twinEdge = reverseEdge edge-        local =-          [ EdgeNextPreviousMismatch edge nextEdge-          | validEdge nextEdge && previous triangulation nextEdge /= edge-          ]-            ++ [ EdgePreviousNextMismatch edge previousEdge-               | validEdge previousEdge && next triangulation previousEdge /= edge-               ]-            ++ [EdgeDoubleReversalMismatch edge | reverseEdge twinEdge /= edge]-            ++ [EdgeSelfLinkedNext edge | nextEdge == edge && halfCount > 2]-            ++ [EdgeSelfLinkedPrevious edge | previousEdge == edge && halfCount > 2]-        innerCycle =-          if incidentFace triangulation edge /= outerFace && validEdge nextEdge && validEdge previousEdge-            then-              [ InnerFaceNotTriangularAtEdge edge-              | next triangulation (next triangulation nextEdge) /= edge-              ]-            else []-     in if index < halfCount then local ++ innerCycle else []--  validEdge (DirectedEdgeId value) = fromIntegral value < halfCount--  faceViolations-    | not (null rangeViolations) = []-    | otherwise = concatMap validateFace (allFaces triangulation)-  validateFace face@(FaceId _) =-    case adjacentEdge triangulation face of-      Nothing-        | face == outerFace && halfCount == 0 -> []-        | otherwise -> [FaceMissingAdjacentEdge face]-      Just edge ->-        [ FaceRepresentativeMismatch face edge representedFace-        | let representedFace = incidentFace triangulation edge-        , representedFace /= face-        ]-          ++ [ InnerFaceVertexCardinalityMismatch face (length faceVertexIds) (length (nub faceVertexIds))-             | face /= outerFace-             , let faceVertexIds = faceVertices triangulation face-             , length faceVertexIds /= 3 || length (nub faceVertexIds) /= 3-             ]--  vertexViolations-    | vertexOutCount /= verticesCount || not (null rangeViolations) = []-    | otherwise = concatMap validateVertex (vertices triangulation)-  validateVertex vertex = case vertexOutEdge triangulation vertex of-    Nothing-      | verticesCount <= 1 -> []-      | otherwise -> [ConnectedVertexMissingOutgoing vertex]-    Just edge ->-      [ VertexOutgoingOriginMismatch vertex edge actualOrigin-      | let actualOrigin = origin triangulation edge-      , actualOrigin /= vertex-      ]--  eulerViolations-    | not (null cardinalityViolations) || verticesCount < 2 = []-    | numInnerFaces triangulation == 0 =-        [ CollinearEdgeCountMismatch (verticesCount - 1) edgeCount-        | edgeCount /= verticesCount - 1-        ]-    | otherwise =-        [ EulerCharacteristicMismatch eulerCharacteristic-        | eulerCharacteristic /= 2-        ]-   where-    eulerCharacteristic = verticesCount - edgeCount + facesCount---- | Apply the same DCEL edge, face, vertex, and orientation laws as--- 'validateTopology', but only to a certified local closure.  The caller--- supplies the admitted inner faces and the interface pairs; the collar faces--- on the other side of those pairs are included here so a protected source--- cannot be changed behind the local transaction.  Cardinality and Euler--- observations remain global scalar invariants and are deliberately not--- rebuilt from the resident mesh.-validateTopologyClosure-  :: IntSet.IntSet-  -> IntSet.IntSet-  -> Triangulation mode vertex directed undirected face-  -> [InvariantViolation]-validateTopologyClosure admittedFaces interfacePairs triangulation =-  snd (validateTopologyClosureWithStats admittedFaces interfacePairs triangulation)--validateTopologyClosureWithStats-  :: IntSet.IntSet-  -> IntSet.IntSet-  -> Triangulation mode vertex directed undirected face-  -> (ValidationClosureStats, [InvariantViolation])-validateTopologyClosureWithStats admittedFaces interfacePairs triangulation =-  ( ValidationClosureStats-      { validationClosureFaces = IntSet.size selectedFaces-      , validationClosureDirectedEdges = IntSet.size selectedEdges-      , validationClosureVertices = IntSet.size selectedVertices-      , validationClosureInterfacePairs = IntSet.size interfacePairs-      , validationClosureConstraintPairs = IntSet.size selectedPairs-      }-  , rangeViolations ++ edgeViolations ++ faceViolations ++ vertexViolations ++ orientationViolations ++ constraintViolations-  )- where-  verticesCount = numVertices triangulation-  halfCount = numDirectedEdges triangulation-  facesCount = numFaces triangulation--  collarFaces =-    IntSet.fromList-      [ rawFace-      | rawPair <- IntSet.toAscList interfacePairs-      , rawPair >= 0-      , rawPair < numUndirectedEdges triangulation-      , let edge = UndirectedEdgeId (fromIntegral rawPair)-      , let (forward, backward) = directedPair edge-      , rawFace <- fmap faceRaw [incidentFace triangulation forward, incidentFace triangulation backward]-      , rawFace > 0-      ]-  selectedFaces = IntSet.union admittedFaces collarFaces--  selectedFaceEdges =-    IntSet.fromList-      [ rawEdge-      | rawFace <- IntSet.toAscList selectedFaces-      , edge <- faceEdgesBounded (FaceId (fromIntegral rawFace))-      , rawEdge <- [edgeRaw edge, edgeRaw (reverseEdge edge)]-      ]-  selectedInterfaceEdges =-    IntSet.fromList-      [ rawEdge-      | rawPair <- IntSet.toAscList interfacePairs-      , rawPair >= 0-      , rawPair <= (maxBound - 1) `quot` 2-      , rawEdge <- [2 * rawPair, 2 * rawPair + 1]-      ]-  selectedEdges = IntSet.union selectedFaceEdges selectedInterfaceEdges-  selectedVertices =-    IntSet.fromList-      [ rawVertex-      | rawEdge <- IntSet.toAscList selectedEdges-      , rawEdge >= 0-      , rawEdge < halfCount-      , edgeRaw (reverseEdge (DirectedEdgeId (fromIntegral rawEdge))) < halfCount-      , rawVertex <--          [ vertexRaw (origin triangulation (DirectedEdgeId (fromIntegral rawEdge)))-          , vertexRaw (destination triangulation (DirectedEdgeId (fromIntegral rawEdge)))-          ]-      , rawVertex < verticesCount-      ]--  rangeViolations =-    [ EdgeOriginOutOfRange edge vertex verticesCount-    | rawEdge <- IntSet.toAscList selectedEdges-    , rawEdge >= 0-    , let edge = DirectedEdgeId (fromIntegral rawEdge)-    , rawEdge < halfCount-    , let vertex = origin triangulation edge-    , vertexRaw vertex >= verticesCount-    ]-      ++ [ EdgeNextOutOfRange edge nextEdge halfCount-         | rawEdge <- IntSet.toAscList selectedEdges-         , rawEdge >= 0-         , rawEdge < halfCount-         , let edge = DirectedEdgeId (fromIntegral rawEdge)-         , let nextEdge = next triangulation edge-         , edgeRaw nextEdge >= halfCount-         ]-      ++ [ EdgePreviousOutOfRange edge previousEdge halfCount-         | rawEdge <- IntSet.toAscList selectedEdges-         , rawEdge >= 0-         , rawEdge < halfCount-         , let edge = DirectedEdgeId (fromIntegral rawEdge)-         , let previousEdge = previous triangulation edge-         , edgeRaw previousEdge >= halfCount-         ]-      ++ [ EdgeFaceOutOfRange edge face facesCount-         | rawEdge <- IntSet.toAscList selectedEdges-         , rawEdge >= 0-         , rawEdge < halfCount-         , let edge = DirectedEdgeId (fromIntegral rawEdge)-         , let face = incidentFace triangulation edge-         , faceRaw face >= facesCount-         ]-      ++ [ FaceAdjacentOutOfRange face edge halfCount-         | rawFace <- IntSet.toAscList selectedFaces-         , rawFace > 0-         , rawFace < facesCount-         , let face = FaceId (fromIntegral rawFace)-         , Just edge <- [adjacentEdge triangulation face]-         , edgeRaw edge >= halfCount-         ]--  edgeViolations =-    concatMap validateEdge (IntSet.toAscList selectedEdges)--  validateEdge rawEdge-    | rawEdge < 0 || rawEdge >= halfCount = []-    | otherwise =-        let edge = DirectedEdgeId (fromIntegral rawEdge)-            nextEdge = next triangulation edge-            previousEdge = previous triangulation edge-            twinEdge = reverseEdge edge-            nextValid = edgeRaw nextEdge < halfCount-            previousValid = edgeRaw previousEdge < halfCount-            nextNextValid = nextValid && edgeRaw (next triangulation nextEdge) < halfCount-            innerCycle =-              if incidentFace triangulation edge /= outerFace && nextValid && previousValid && nextNextValid-                then [InnerFaceNotTriangularAtEdge edge | next triangulation (next triangulation nextEdge) /= edge]-                else []-         in [ EdgeNextPreviousMismatch edge nextEdge-            | nextValid && previous triangulation nextEdge /= edge-            ]-              ++ [ EdgePreviousNextMismatch edge previousEdge-                 | previousValid && next triangulation previousEdge /= edge-                 ]-              ++ [EdgeDoubleReversalMismatch edge | reverseEdge twinEdge /= edge]-              ++ [EdgeSelfLinkedNext edge | nextEdge == edge && halfCount > 2]-              ++ [EdgeSelfLinkedPrevious edge | previousEdge == edge && halfCount > 2]-              ++ innerCycle--  faceViolations = concatMap validateFace (IntSet.toAscList selectedFaces)--  validateFace rawFace-    | rawFace <= 0 || rawFace >= facesCount = []-    | otherwise =-      let face = FaceId (fromIntegral rawFace)-       in case adjacentEdge triangulation face of-          Nothing -> [FaceMissingAdjacentEdge face]-          Just edge-            | edgeRaw edge >= halfCount -> []-            | otherwise ->-                let representedFace = incidentFace triangulation edge-                    (faceEdges, faceVertices') = triangleEdgesAndVertices face-                 in [FaceRepresentativeMismatch face edge representedFace | representedFace /= face]-                      ++ [ InnerFaceVertexCardinalityMismatch face (length faceVertices') (length (nub faceVertices'))-                         | rawFace > 0-                         , length faceVertices' /= 3 || length (nub faceVertices') /= 3-                         ]-                      ++ [InnerFaceNotTriangularAtEdge edge-                         | rawFace > 0-                         , not (triangleClosed faceEdges)-                         ]--  triangleEdgesAndVertices face =-    let edges = faceEdgesBounded face-        vertices' = fmap (vertexRaw . origin triangulation) edges-     in (edges, vertices')--  triangleClosed edges =-    case edges of-      [first, _, third] -> next triangulation third == first-      _ -> False--  vertexViolations = concatMap validateVertex (IntSet.toAscList selectedVertices)--  validateVertex rawVertex =-    let vertex = VertexId (fromIntegral rawVertex)-     in case vertexOutEdge triangulation vertex of-          Nothing-            | verticesCount > 1 -> [ConnectedVertexMissingOutgoing vertex]-            | otherwise -> []-          Just edge-            | edgeRaw edge >= halfCount ->-                [VertexOutgoingOutOfRange vertex edge halfCount]-            | otherwise ->-                [ VertexOutgoingOriginMismatch vertex edge actualOrigin-                | let actualOrigin = origin triangulation edge-                , actualOrigin /= vertex-                ]--  orientationViolations =-    [ InnerFaceNotCounterClockwise face-    | rawFace <- IntSet.toAscList selectedFaces-    , rawFace > 0-    , let face = FaceId (fromIntegral rawFace)-    , let (_, vertices') = triangleEdgesAndVertices face-    , all (< verticesCount) vertices'-    , [first, second, third] <- [fmap (VertexId . fromIntegral) vertices']-    , orient2d (vertexPoint triangulation first) (vertexPoint triangulation second) (vertexPoint triangulation third) /= GT-    ]--  constraintViolations =-    [ NonCanonicalConstraintFlag edge flag-    | rawPair <- IntSet.toAscList selectedPairs-    , rawPair >= 0-    , let edge = UndirectedEdgeId (fromIntegral rawPair)-    , rawPair < numUndirectedEdges triangulation-    , let flag = pagedUnsafeIndex (triConstraint triangulation) rawPair-    , flag /= 0 && flag /= 1-    ]-      ++ [ CachedConstraintIndexMismatch-         | rawPair <- IntSet.toAscList selectedPairs-         , rawPair >= 0-         , rawPair < numUndirectedEdges triangulation-         , let flag = pagedUnsafeIndex (triConstraint triangulation) rawPair-         , (flag == 1) /= IntSet.member rawPair (triConstraintEdges triangulation)-         ]--  selectedPairs = IntSet.fromList [rawEdge `quot` 2 | rawEdge <- IntSet.toAscList selectedEdges]--  faceEdgesBounded face =-    case face of-      FaceId raw-        | toInteger raw <= 0 || toInteger raw >= toInteger facesCount -> []-      _ -> adjacentEdges-   where-    adjacentEdges = case adjacentEdge triangulation face of-      Nothing -> []-      Just start-        | edgeRaw start >= halfCount -> []-        | otherwise ->-            let second = next triangulation start-             in if edgeRaw second >= halfCount-                  then [start]-                  else-                    let third = next triangulation second-                     in if edgeRaw third >= halfCount-                          then [start, second]-                          else [start, second, third]--  edgeRaw (DirectedEdgeId raw) = fromIntegral raw-  vertexRaw (VertexId raw) = fromIntegral raw-  faceRaw (FaceId raw) = fromIntegral raw---- | Every edge whose circumcircle is not empty.-validateDelaunay :: Triangulation mode vertex directed undirected face -> [InvariantViolation]-validateDelaunay triangulation = concatMap validateEdge (undirectedEdges triangulation)- where-  validateEdge edge-    | isConstraintEdge triangulation edge = []-    | isBoundaryEdge triangulation edge = []-    | otherwise =-        let directed = normalizedDirected edge-            twin = reverseEdge directed-         in case (innerFaceDirectedEdges triangulation (incidentFace triangulation directed), innerFaceDirectedEdges triangulation (incidentFace triangulation twin)) of-              (Just _, Just _) ->-                let a = vertexPoint triangulation (origin triangulation directed)-                    b = vertexPoint triangulation (destination triangulation directed)-                    c = vertexPoint triangulation (origin triangulation (previous triangulation directed))-                    d = vertexPoint triangulation (origin triangulation (previous triangulation twin))-                    convex = orient2d c d b == GT && orient2d d c a == GT-                    circle = inCircle a b c d-                    illegal = convex && (circle == GT || (circle == EQ && orderedPair c d < orderedPair a b))-                 in [LocallyIllegalDelaunayEdge edge | illegal]-              _ -> [DelaunayIncidentFaceNotTriangular edge]---- | Topology first; the Delaunay property only if the topology holds.-validateTriangulation-  :: Triangulation mode vertex directed undirected face-  -> [InvariantViolation]-validateTriangulation triangulation =-  let topology = validateTopology triangulation-   in if null topology-        then validateDelaunay triangulation-        else topology---- | Whether 'validateTriangulation' is empty.-triangulationIsValid-  :: Triangulation mode vertex directed undirected face-  -> Bool-triangulationIsValid = null . validateTriangulation---- | Signed area, or 'Nothing' where the face is not a triangle.-faceArea :: Triangulation mode vertex directed undirected face -> FaceId -> Maybe Double-faceArea triangulation face = do-  (v0, v1, v2) <- innerFaceVertices triangulation face-  pure (triangleArea (vertexPoint triangulation v0) (vertexPoint triangulation v1) (vertexPoint triangulation v2))---- | Smallest interior angle, in degrees.-faceMinimumAngleDegrees :: Triangulation mode vertex directed undirected face -> FaceId -> Maybe Double-faceMinimumAngleDegrees triangulation face = do-  (v0, v1, v2) <- innerFaceVertices triangulation face-  let p0 = vertexPoint triangulation v0-      p1 = vertexPoint triangulation v1-      p2 = vertexPoint triangulation v2-      a = sqrt (squaredDistance p1 p2)-      b = sqrt (squaredDistance p2 p0)-      c = sqrt (squaredDistance p0 p1)-  if min a (min b c) <= 0-    then Nothing-    else Just (minimum [angle b c a, angle c a b, angle a b c])- where-  angle left right opposite = acos (clamp ((left * left + right * right - opposite * opposite) / (2 * left * right))) * 180 / pi-  clamp = max (-1) . min 1+import Moonlight.Triangulation.Internal.Validation
src-dcel/Moonlight/Triangulation/Valuation.hs view
@@ -62,7 +62,7 @@   , exactSegment   , exactSegmentEndpoints   )-import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.HandleDefs   ( FaceId (..)   , UndirectedEdgeId (..)   , VertexId (..)
src-dual/Moonlight/Triangulation/Interpolation.hs view
@@ -50,7 +50,7 @@   , vertexOutEdge   , vertexPoint   )-import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.HandleDefs   ( DirectedEdgeId (..)   , asUndirected   , FaceId (..)
src-dual/Moonlight/Triangulation/Voronoi.hs view
@@ -33,7 +33,7 @@  import Control.DeepSeq (NFData) import Moonlight.Triangulation.Dcel-import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.HandleDefs import Moonlight.Triangulation.Handles.Iterators.FixedIterators (directedEdges, undirectedEdges, vertices) import Moonlight.Triangulation.Math import Moonlight.Triangulation.Types
src-public/Moonlight/Triangulation.hs view
@@ -30,12 +30,12 @@   , ElementDefaults (..)   , unitElementDefaults   , JoinSemilattice (..)+  , Coordinate2 (..)+  , mapCoordinate2     -- | An identifier is admitted by the triangulation that issued it. The     -- observations below index without a second bounds check, so the-    -- constructors are withheld here and the projections are not: an-    -- identifier can be read, compared and carried, and can only be obtained-    -- from a mesh. "Moonlight.Triangulation.Handles.HandleDefs" exports the-    -- constructors for a caller who is willing to own that obligation.+    -- constructors are withheld and the projections are not: an identifier+    -- can be read, compared and carried, and can only be obtained from a mesh.   , VertexId   , unVertexId   , FaceId@@ -51,7 +51,10 @@   , directedPair   , isNormalized -    -- * Generation — @delaunay@; canonical observation factors through the site set+    -- * Generation and editing — persistent verbs publish one new value;+    -- sessions compose many edits behind one sealed thaw/freeze boundary+  , empty+  , clear   , delaunay   , delaunayGeometry   , DuplicatePayloadPolicy (..)@@ -59,6 +62,32 @@   , BuildResult   , buildTriangulation   , buildInputVertices+  , buildStats+  , BuildStats (..)+  , emptyBuildStats+  , InsertionDisposition (..)+  , InsertionResult+  , insertionTriangulation+  , insertionVertex+  , insertionDisposition+  , insertionStats+  , insert+  , insertAt+  , insertMany+  , RemovalOutcome (..)+  , RemovalResult (..)+  , removeVertex+  , locateAndRemove+  , Session+  , withSession+  , insertVertex+  , insertVertexAt+  , insertVertexAtNear+  , removeAt+  , removeAtNear+  , removeManyAt+  , removeManyAtNear+  , refuse   , BuildError (..)   , PublicationStats (..)   , ValidationClosureStats (..)@@ -68,9 +97,28 @@     -- * The annotation functor — payloads annotate geometry, never author it;     -- the mesh is the same mesh before and after   , mapVertices+  , mapDirectedEdges+  , mapUndirectedEdges+  , mapFaces+  , imapUndirectedEdges+  , imapFaces+  , PayloadTraversal+  , vertexPayloads+  , directedPayloads+  , undirectedPayloads+  , facePayloads+  , overPayloads+  , foldPayloads+  , payloadList   , geometryOnlyPublication   , vertexData+  , directedEdgeData+  , undirectedEdgeData+  , faceData   , setVertexData+  , setDirectedEdgeData+  , setUndirectedEdgeData+  , setFaceData      -- * Finite-set algebra and its normal form — support order is observable,     -- intersection can combine heterogeneous annotations, and each@@ -89,6 +137,7 @@     -- (sites, segments), defined exactly on realizable segment sets and     -- naming the corridor that blocked it where it is not   , constrainedDelaunay+  , constrainedDelaunayMaximal   , fromDelaunay   , constraintEdges   , isConstraintEdge@@ -109,6 +158,12 @@   , ConstrainedExtensionResult   , constrainedExtensionConstraintBatch   , constrainedExtensionBuildStats+  , ConstraintRecoveryResult (..)+  , ConstraintResult+  , ConstraintOutcome (..)+  , ConstraintBatchStats (..)+  , ConstraintSplitBatchResult+  , CdtBuildResult (..)   , ConstrainedSeamSide (..)   , ConstrainedSeamFaceEvidence   , constrainedSeamSourceFace@@ -131,6 +186,19 @@   , unionConstrained   , joinSeparatedConstrained   , extendConstrainedWith+  , constraintStorageBytes+  , existsConstraint+  , canAddConstraint+  , intersectsConstraint+  , getConflictingEdgesBetweenPoints+  , getConflictingEdgesBetweenVertices+  , recoverConstraints+  , addConstraintEdge+  , addConstraintEdges+  , addConstraintAndSplit+  , addConstraintsAndSplit+  , removeConstraintEdge+  , outerRegionFaces      -- * Refinement — budget-bounded, composed after any operation above rather     -- than configured into it; 'refinementComplete' reports sufficiency, not@@ -146,6 +214,7 @@   , refinementDomainResult   , refinementDomainReceipt   , refinedTriangulation+  , refinementStats   , refinementVisitedJoinFaces   , refinementVisitedProtectedFaces   , refinementCreatedFaces@@ -167,31 +236,95 @@     -- interpolation and barrier parity, none of which build a second mesh   , numVertices   , vertices+  , foldVertices'   , vertexPoint   , vertexPoints   , numFaces+  , numInnerFaces+  , allFaces   , innerFaces+  , foldAllFaces'+  , foldInnerFaces'   , outerFace+  , adjacentEdge   , faceDirectedEdges   , faceVertices+  , innerFaceDirectedEdges+  , innerFaceVertices   , innerFaceDirectedEdgeTriples   , innerFaceVertexTriples+  , foldFaceDirectedEdges'+  , vertexOutEdge   , vertexOutgoingEdges+  , foldVertexOutgoingEdges'+  , numDirectedEdges+  , directedEdges+  , foldDirectedEdges'   , numUndirectedEdges   , undirectedEdges+  , foldUndirectedEdges'+  , hullEdges+  , hullVertices   , undirectedEndpoints   , origin   , destination+  , next+  , previous+  , clockwise+  , counterClockwise   , incidentFace+  , isOuterDirectedEdge   , isBoundaryEdge+  , numConstraints+  , locatePoint+  , locatePointWithHint+  , Location (..)   , nearestNeighbor   , NearestStats (..)   , interpolateNearest   , interpolateBarycentric   , LocationHint (..)+  , LocationStats (..)+  , emptyLocationStats+  , Intersection (..)+  , lineIntersections+  , lineIntersectionsBetweenVertices+  , foldCorridorBetweenPoints+  , foldCorridorBetweenVertices+  , conflictingEdges+  , segmentIntersectsNonCollinear+  , DistanceMetric (..)+  , CircleMetric+  , CircleMetricError (..)+  , RectangleMetric+  , RectangleMetricError (..)+  , circleMetric+  , rectangleMetric+  , edgesInShape+  , verticesInShape+  , edgesInCircle+  , verticesInCircle+  , edgesInRectangle+  , verticesInRectangle   , floodFillFaces+  , outerFaceFloodFill   , facesAtEvenBarrierDepth +    -- ** Robust planar predicates and constructions+  , orient2d+  , sideQuery+  , inCircle+  , segmentRelation+  , segmentsProperlyCross+  , segmentsIntersect+  , squaredDistance+  , distance+  , midpoint+  , centroid+  , triangleArea+  , circumcenter+  , barycentricCoordinates+     -- ** Face regions and alpha filtration   , FaceComponent   , faceComponentFaces@@ -205,6 +338,8 @@   , regionBoundaryHoleLoops   , BoundaryObstruction (..)   , faceComponents+  , faceComponentsBy+  , labelledRegionBoundaries   , componentBoundaryLoops   , componentBoundary   , componentBoundaryForFaces@@ -239,6 +374,13 @@   , ExactPoint   , exactPoint   , exactPointCoordinates+  , exactPointCross+  , ExactVector (..)+  , exactVectorFromPoints+  , addExactVectors+  , exactCross+  , compareExactVectorAngle+  , translateExactPoint   , ExactSegment   , ExactGeometryError (..)   , exactSegment@@ -249,9 +391,11 @@   , exactOrient2d   , exactOnClosedSegment   , SegmentRelation (..)+  , allSegmentRelations   , exactSegmentRelation   , ExactIntersectionError (..)   , exactLineIntersection+  , exactSupportingLineIntersection   , ExactLoop   , exactLoop   , exactLoopPoints@@ -340,15 +484,25 @@      -- * Discharge — the invariants the constructors guarantee, checkable on a     -- value built by any route; every violation is a value carrying its witness+  , validateTopology+  , validateDelaunay   , validateTriangulation+  , triangulationIsValid+  , faceArea+  , faceMinimumAngleDegrees   , InvariantViolation (..)   ) where  import Moonlight.Triangulation.BulkLoad   ( DuplicatePayloadPolicy (..)+  , clear   , delaunay   , delaunayFromCoordinates   , delaunayGeometry+  , empty+  , insert+  , insertAt+  , insertMany   ) import Moonlight.Triangulation.CellSet   ( CellSelectionError (..)@@ -363,22 +517,44 @@   , foldExactCellVertices   ) import Moonlight.Triangulation.Dcel-  ( destination+  ( adjacentEdge+  , clockwise+  , counterClockwise+  , destination+  , directedEdgeData+  , faceData   , faceDirectedEdges   , faceVertices+  , foldFaceDirectedEdges'+  , foldVertexOutgoingEdges'+  , imapFaces+  , imapUndirectedEdges+  , innerFaceDirectedEdges   , innerFaceDirectedEdgeTriples+  , innerFaceVertices   , innerFaceVertexTriples   , incidentFace   , isBoundaryEdge   , isConstraintEdge+  , isOuterDirectedEdge+  , next+  , numConstraints+  , numDirectedEdges   , numFaces+  , numInnerFaces   , numUndirectedEdges   , numVertices   , origin   , outerFace+  , previous+  , setDirectedEdgeData+  , setFaceData+  , setUndirectedEdgeData   , setVertexData+  , undirectedEdgeData   , undirectedEndpoints   , vertexData+  , vertexOutEdge   , vertexOutgoingEdges   , vertexPoint   , vertexPoints@@ -405,79 +581,130 @@   ( BoundaryLoop   , BoundaryObstruction (..)   , BoundaryOrientation (..)+  , CircleMetric+  , CircleMetricError (..)+  , DistanceMetric (..)   , FaceComponent+  , RectangleMetric+  , RectangleMetricError (..)   , RegionBoundary   , boundaryLoopOrientation   , boundaryLoopResidentVertices   , boundaryLoopVertices+  , circleMetric   , componentBoundary   , componentBoundaryLoops   , componentBoundaryForFaces+  , edgesInCircle+  , edgesInRectangle+  , edgesInShape   , faceComponentFaces   , faceComponents+  , faceComponentsBy   , facesAtEvenBarrierDepth   , floodFillFaces+  , labelledRegionBoundaries+  , outerFaceFloodFill+  , rectangleMetric   , regionBoundaryHoleLoops   , regionBoundaryOuterLoop+  , verticesInCircle+  , verticesInRectangle+  , verticesInShape   ) import Moonlight.Triangulation.Exact   ( ExactGeometryError (..)   , ExactIntersectionError (..)   , ExactPoint   , ExactSegment+  , ExactVector (..)   , SegmentRelation (..)+  , addExactVectors+  , allSegmentRelations+  , compareExactVectorAngle+  , exactCross   , exactLineIntersection   , exactOnClosedSegment   , exactOrient2d   , exactPoint   , exactPointCoordinates+  , exactPointCross   , exactPointFromPoint   , exactPointFromQueryPoint   , exactPointToEmbeddingCandidate   , exactSegment   , exactSegmentEndpoints   , exactSegmentRelation+  , exactSupportingLineIntersection+  , exactVectorFromPoints+  , translateExactPoint   ) import Moonlight.Triangulation.Handles.HandleDefs-  ( DirectedEdgeId (..)-  , FaceId (..)-  , UndirectedEdgeId (..)-  , VertexId (..)+  ( DirectedEdgeId+  , FaceId+  , UndirectedEdgeId+  , VertexId   , asUndirected   , directedPair   , isNormalized   , normalizedDirected   , reverseEdge   , reversedDirected+  , unDirectedEdgeId+  , unFaceId+  , unUndirectedEdgeId+  , unVertexId   ) import Moonlight.Triangulation.Handles.Iterators.FixedIterators-  ( innerFaces+  ( allFaces+  , directedEdges+  , foldAllFaces'+  , foldDirectedEdges'+  , foldInnerFaces'+  , foldUndirectedEdges'+  , foldVertices'+  , innerFaces   , undirectedEdges   , vertices   )-import Moonlight.Triangulation.Internal.Canonical (canonicalize)-import Moonlight.Triangulation.Cdt (boundedRegionFaces)-import Moonlight.Triangulation.Internal.Cdt.Build-  ( constrainedDelaunay-  , fromDelaunay+import Moonlight.Triangulation.Handles.Iterators.HullIterator+  ( hullEdges+  , hullVertices   )-import Moonlight.Triangulation.Internal.Cdt.Query (constraintEdges)-import Moonlight.Triangulation.Internal.Cdt.Types+import Moonlight.Triangulation.Canonical (canonicalize)+import Moonlight.Triangulation.Cdt   ( CanonicalSegment+  , CdtBuildResult (..)   , CdtError (..)-  , ConstrainedUnionError (..)   , ConstrainedExtensionResult-  , ConstrainedSeamSide (..)   , ConstrainedSeamFaceEvidence   , ConstrainedSeamResult+  , ConstrainedSeamSide (..)+  , ConstrainedUnionError (..)+  , ConstraintBatchStats (..)   , ConstraintBatchResult   , ConstraintConflict+  , ConstraintOutcome (..)+  , ConstraintRecoveryResult (..)+  , ConstraintResult+  , ConstraintSplitBatchResult   , CorridorObstruction (..)+  , addConstraintAndSplit+  , addConstraintEdge+  , addConstraintEdges+  , addConstraintsAndSplit+  , boundedRegionFaces+  , canAddConstraint+  , constrainedDelaunay+  , constrainedDelaunayMaximal   , constrainedExtensionBuildStats   , constrainedExtensionConstraintBatch+  , constraintEdges   , constraintBatchOutcomes   , constraintBatchStats   , constraintBatchTriangulation+  , constraintSegments+  , constraintStorageBytes   , constrainedSeamBuildStats   , constrainedSeamConstraintStats   , constrainedSeamCachedFrontierPointReads@@ -494,13 +721,18 @@   , constrainedSeamTargetFace   , conflictFirstSegment   , conflictSecondSegment-  , segmentEnd-  , segmentStart-  )-import Moonlight.Triangulation.Internal.Cdt.Union-  ( constraintSegments+  , existsConstraint   , extendConstrainedWith+  , fromDelaunay+  , getConflictingEdgesBetweenPoints+  , getConflictingEdgesBetweenVertices+  , intersectsConstraint   , joinSeparatedConstrained+  , outerRegionFaces+  , recoverConstraints+  , removeConstraintEdge+  , segmentEnd+  , segmentStart   , unionConstrained   , unionConstrainedWith   )@@ -512,43 +744,36 @@   , exactRationalDenominator   , exactRationalNumerator   )-import Moonlight.Triangulation.Internal.Representation-  ( BuildResult-  , ConstrainedDelaunayTriangulation-  , DelaunayTriangulation-  , RefinementDomainResult-  , RefinementReceipt-  , RefinementResult-  , Triangulation-  , buildInputVertices-  , buildTriangulation-  , geometryOnlyPublication-  , refinementPublicationStats-  , refinementValidationClosureStats-  , refinementDomainReceipt-  , refinementDomainResult-  , refinedTriangulation-  , refinementInterfaceBoundaryReads-  , refinementAttemptedBoundaryCrossings-  , refinementAddedVertices-  , refinementComplete-  , refinementCreatedFaces-  , refinementFinalPermittedFaces-  , refinementFinalInterfaceIncidence-  , refinementExcludedFaces-  , refinementRemovedEdges-  , refinementTouchedEdges-  , refinementVisitedJoinFaces-  , refinementVisitedProtectedFaces+import Moonlight.Triangulation.Internal.Representation (geometryOnlyPublication)+import Moonlight.Triangulation.Interop+  ( Coordinate2 (..)+  , mapCoordinate2   )-import Moonlight.Triangulation.Internal.Types+import Moonlight.Triangulation.IntersectionIterator+  ( Intersection (..)+  , conflictingEdges+  , foldCorridorBetweenPoints+  , foldCorridorBetweenVertices+  , lineIntersections+  , lineIntersectionsBetweenVertices+  , segmentIntersectsNonCollinear+  )+import Moonlight.Triangulation.Types   ( BuildError (..)+  , BuildResult+  , BuildStats (..)+  , ConstrainedDelaunayTriangulation   , ConstraintMode (..)   , CoordinateError (..)+  , DelaunayTriangulation   , ElementDefaults (..)   , HasPosition (..)+  , InsertionDisposition (..)+  , InsertionResult   , InvariantViolation (..)+  , Location (..)   , LocationHint (..)+  , LocationStats (..)   , NearestStats (..)   , NonFiniteValue (..)   , Point (..)@@ -556,13 +781,44 @@   , QueryPoint   , RadiusSquared   , RadiusSquaredError (..)+  , RefinementDomainResult   , RefinementParameters (..)-  , ValidationClosureStats (..)+  , RefinementReceipt+  , RefinementResult   , SiteRelation (..)+  , Triangulation+  , ValidationClosureStats (..)+  , buildInputVertices+  , buildStats+  , buildTriangulation   , defaultRefinementParameters+  , emptyBuildStats+  , emptyLocationStats+  , insertionDisposition+  , insertionStats+  , insertionTriangulation+  , insertionVertex   , mkRadiusSquared   , queryPointValue   , radiusSquaredValue+  , refinedTriangulation+  , refinementAddedVertices+  , refinementAttemptedBoundaryCrossings+  , refinementComplete+  , refinementCreatedFaces+  , refinementDomainReceipt+  , refinementDomainResult+  , refinementExcludedFaces+  , refinementFinalInterfaceIncidence+  , refinementFinalPermittedFaces+  , refinementInterfaceBoundaryReads+  , refinementPublicationStats+  , refinementRemovedEdges+  , refinementStats+  , refinementTouchedEdges+  , refinementValidationClosureStats+  , refinementVisitedJoinFaces+  , refinementVisitedProtectedFaces   , unitElementDefaults   ) import Moonlight.Triangulation.Interpolation@@ -587,7 +843,22 @@   , polygonOffset   , structuringElement   )-import Moonlight.Triangulation.Math (mkQueryPoint)+import Moonlight.Triangulation.Math+  ( barycentricCoordinates+  , centroid+  , circumcenter+  , distance+  , inCircle+  , midpoint+  , mkQueryPoint+  , orient2d+  , segmentRelation+  , segmentsIntersect+  , segmentsProperlyCross+  , sideQuery+  , squaredDistance+  , triangleArea+  ) import Moonlight.Triangulation.Overlay   ( OverlayError (..)   , OverlayReceipt (..)@@ -606,7 +877,30 @@   , overlayRegularizedDifference   , overlaySelectedRegion   )-import Moonlight.Triangulation.Payload (mapVertices)+import Moonlight.Triangulation.Payload+  ( PayloadTraversal+  , directedPayloads+  , facePayloads+  , foldPayloads+  , mapDirectedEdges+  , mapFaces+  , mapUndirectedEdges+  , mapVertices+  , overPayloads+  , payloadList+  , undirectedPayloads+  , vertexPayloads+  )+import Moonlight.Triangulation.PointLocation+  ( locatePoint+  , locatePointWithHint+  )+import Moonlight.Triangulation.Removal+  ( RemovalOutcome (..)+  , RemovalResult (..)+  , locateAndRemove+  , removeVertex+  ) import Moonlight.Triangulation.JoinSemilattice (JoinSemilattice (..)) import Moonlight.Triangulation.Refinement   ( radiusEdgeRatioForAngle@@ -647,7 +941,26 @@   , union   , unions   )-import Moonlight.Triangulation.Validation (validateTriangulation)+import Moonlight.Triangulation.Session+  ( Session+  , insertVertex+  , insertVertexAt+  , insertVertexAtNear+  , refuse+  , removeAt+  , removeAtNear+  , removeManyAt+  , removeManyAtNear+  , withSession+  )+import Moonlight.Triangulation.Validation+  ( faceArea+  , faceMinimumAngleDegrees+  , triangulationIsValid+  , validateDelaunay+  , validateTopology+  , validateTriangulation+  ) import Moonlight.Triangulation.Valuation   ( CertifiedInterval (..)   , EulerCharacteristic
src-public/Moonlight/Triangulation/HintGenerator.hs view
@@ -32,7 +32,7 @@ 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.Internal.HandleDefs (UndirectedEdgeId (..), VertexId (..)) import Moonlight.Triangulation.Interpolation (nearestNeighbor) import Moonlight.Triangulation.Math (canonicalPoint, validatePoint) import Moonlight.Triangulation.Removal (RemovalOutcome, removalTriangulation, removeVertex)
src-public/Moonlight/Triangulation/Internal/Minkowski/Types.hs view
@@ -18,7 +18,7 @@   , ExactIntersectionError   , ExactPoint   )-import Moonlight.Triangulation.Handles.HandleDefs (FaceId)+import Moonlight.Triangulation.Internal.HandleDefs (FaceId) import Moonlight.Triangulation.Internal.ExactRational (ExactArithmeticError) import Moonlight.Triangulation.Internal.Overlay.Types   ( OverlayCellId
src-public/Moonlight/Triangulation/Internal/Overlay/Resident.hs view
@@ -49,7 +49,7 @@   , faceComponentFaces   , faceComponentsBy   )-import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.HandleDefs   ( DirectedEdgeId   , FaceId   , UndirectedEdgeId
src-public/Moonlight/Triangulation/Internal/Overlay/Types.hs view
@@ -41,7 +41,7 @@ import Moonlight.Triangulation.Exact   ( ExactPoint   )-import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.HandleDefs   ( FaceId   , UndirectedEdgeId   , VertexId
src-public/Moonlight/Triangulation/Minkowski.hs view
@@ -43,7 +43,7 @@   ( ExactPoint   , exactPointCoordinates   )-import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.HandleDefs   ( FaceId   ) import Moonlight.Triangulation.Handles.Iterators.FixedIterators (innerFaces)
src-public/Moonlight/Triangulation/Overlay.hs view
@@ -58,7 +58,7 @@ import Moonlight.Triangulation.CellSet (ExactCellSet) import qualified Moonlight.Triangulation.Dcel as Dcel import Moonlight.Triangulation.Dcel (vertexData)-import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.HandleDefs   ( UndirectedEdgeId   , VertexId   )
src-serialize/Moonlight/Triangulation/Serialization.hs view
@@ -57,7 +57,7 @@ import Moonlight.Triangulation.Internal.Paged (fromLocalVector, fromVector, toVector) import Moonlight.Triangulation.Internal.PackedIndex (indexLimit) import Moonlight.Triangulation.Internal.PointIndex (buildPointIndex)-import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.HandleDefs import Moonlight.Triangulation.Internal.Representation import Moonlight.Triangulation.Internal.Types import Moonlight.Triangulation.Math (canonicalCoordinate, mkQueryPoint)
test/cell-complex/Main.hs view
@@ -68,7 +68,7 @@   ( undirectedEndpoints   , vertexPoint   )-import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.HandleDefs   ( UndirectedEdgeId   ) import Moonlight.Triangulation.Handles.Iterators.FixedIterators
test/examples/Main.hs view
@@ -4,6 +4,9 @@ import Moonlight.Triangulation.Example.ConstrainedRegion import Moonlight.Triangulation.Example.PlanarRegion import Moonlight.Triangulation.Example.SetAlgebra+import Moonlight.Triangulation.Handles.Dynamic ()+import Moonlight.Triangulation.Session ()+import Moonlight.Triangulation.Voronoi () import System.Exit (die)  main :: IO ()
test/native/Moonlight/Triangulation/NativeSpec.hs view
@@ -38,11 +38,13 @@ import Moonlight.Triangulation.Dcel import qualified Moonlight.Triangulation.Dcel as Dcel import Moonlight.Triangulation.FloodFillIterator-import Moonlight.Triangulation.Handles.HandleDefs+import Moonlight.Triangulation.Internal.HandleDefs import Moonlight.Triangulation.Handles.Iterators.FixedIterators import Moonlight.Triangulation.HintGenerator import Moonlight.Triangulation.Interpolation import Moonlight.Triangulation.IntersectionIterator+import Moonlight.Triangulation.Internal.Session (excise)+import Moonlight.Triangulation.Internal.Validation (validateTopologyClosure) import Moonlight.Triangulation.Math import Moonlight.Triangulation.Payload import Moonlight.Triangulation.FilteredPredicateOptimizationSpec
+ test/public-components/Main.hs view
@@ -0,0 +1,112 @@+module Main (main) where++import Data.Bifunctor (first)+import qualified Data.Vector as Vector+import Moonlight.Triangulation.BulkLoad (delaunayGeometry)+import Moonlight.Triangulation.Dcel (numVertices)+import Moonlight.Triangulation.FloodFillIterator+  ( RectangleMetricError+  , verticesInRectangle+  )+import Moonlight.Triangulation.Handles.Iterators.DynamicIterators+  ( vertexHandles+  )+import Moonlight.Triangulation.PointLocation (locatePoint)+import Moonlight.Triangulation.Session+  ( insertVertexAt+  , removeAt+  , withSession+  )+import Moonlight.Triangulation.Types+  ( BuildError+  , InsertionDisposition (Inserted)+  , Location (OnVertex)+  , Point (..)+  , PointValidationError+  )+import Moonlight.Triangulation.Math (mkQueryPoint)+import Moonlight.Triangulation.Validation (validateTriangulation)+import System.Exit (die)++data PublicComponentFailure+  = PublicBuildFailure !BuildError+  | PublicQueryFailure !PointValidationError+  | PublicRectangleFailure !RectangleMetricError+  deriving stock (Show)++data PublicComponentSummary = PublicComponentSummary+  { residentVertices :: !Int+  , owningVertexHandles :: !Int+  , rectangleVertices :: !Int+  , locatedResidentVertex :: !Bool+  , sessionInsertedVertex :: !Bool+  , sessionRemovedVertex :: !Bool+  , finalMeshValid :: !Bool+  }+  deriving stock (Eq, Show)++main :: IO ()+main =+  either+    (die . ("public component workflow refused: " <>) . show)+    assertExpected+    publicComponentWorkflow++publicComponentWorkflow :: Either PublicComponentFailure PublicComponentSummary+publicComponentWorkflow = do+  square <-+    first PublicBuildFailure+      ( delaunayGeometry+          ( Vector.fromList+              [ Point (-1) (-1)+              , Point 1 (-1)+              , Point 1 1+              , Point (-1) 1+              ]+          )+      )+  ((disposition, removed), restored, _) <-+    first PublicBuildFailure+      ( withSession square 1 $ do+          (_, inserted) <- insertVertexAt (Point 0 0) ()+          removal <- removeAt (Point 0 0)+          pure (inserted, removal)+      )+  query <- first PublicQueryFailure (mkQueryPoint (Point (-1) (-1)))+  inside <-+    first PublicRectangleFailure+      (verticesInRectangle restored (Point (-2) (-2)) (Point 2 2))+  pure+    PublicComponentSummary+      { residentVertices = numVertices restored+      , owningVertexHandles = length (vertexHandles restored)+      , rectangleVertices = length inside+      , locatedResidentVertex = case locatePoint restored query of+          OnVertex _ -> True+          _ -> False+      , sessionInsertedVertex = disposition == Inserted+      , sessionRemovedVertex = maybe False (const True) removed+      , finalMeshValid = null (validateTriangulation restored)+      }++assertExpected :: PublicComponentSummary -> IO ()+assertExpected actual+  | actual == expected = putStrLn "public components: ok"+  | otherwise =+      die+        ( "public component summary mismatch; expected "+            <> show expected+            <> ", got "+            <> show actual+        )+ where+  expected =+    PublicComponentSummary+      { residentVertices = 4+      , owningVertexHandles = 4+      , rectangleVertices = 4+      , locatedResidentVertex = True+      , sessionInsertedVertex = True+      , sessionRemovedVertex = True+      , finalMeshValid = True+      }
test/serialization/Moonlight/Triangulation/SerializationSpec.hs view
@@ -18,7 +18,7 @@ import Data.Word (Word16, Word64) import GHC.Generics (Generic) import Moonlight.Triangulation-import Moonlight.Triangulation.Handles.HandleDefs (DirectedEdgeId (..), FaceId (..))+import Moonlight.Triangulation.Internal.HandleDefs (DirectedEdgeId (..), FaceId (..)) import Moonlight.Triangulation.Serialization import Moonlight.Triangulation.Types (KnownConstraintMode) import Support (assertEqual, assertValid, requireRight)