packages feed

moonlight-triangulation 0.1.0.0 → 1.0.0.0

raw patch · 42 files changed

+429/−599 lines, 42 files

Files

CHANGELOG.md view
@@ -6,7 +6,23 @@ The serialization format carries its own version tag, independent of the package version; any change to it is recorded here explicitly. +## 1.0.0.0++* Specialize the public geometry surface to binary64 and remove the ornamental+  scalar parameter from points, queries, triangulations, sessions, hierarchy+  hints, interpolation workspaces, and result types.+* Add dense coordinate and inner-face vertex projections to the existing DCEL+  surface.+* Add exact support relations and finite-set operations over triangulations,+  with local publication for sparse differences, intersections, and symmetric+  differences.+* Schedule singleton insertion and constrained extension between dense and+  locality-preserving transactions from measured workload evidence.+* Resolve dense coordinate-removal batches through one mutable identity index,+  retaining geometric descent for sparse removals.+* Strengthen constrained extension, refinement, serialization, algebra, and+  hostile-boundary validation.+ ## 0.1.0.0 -* Initial release with an honest binary64 geometry surface, dense DCEL-  projections, coverage-guided boundary targets, and PVP-bounded dependencies.+* Initial release.
README.md view
@@ -77,15 +77,13 @@ | `refineWithinDomain` | Dense publication. The local transaction candidate preserved semantics but did not improve wall time, so it was removed. |  On the retained one-million-site / five-thousand-site witnesses, raw-`difference` fell from 4.997 s to 0.169 s. Near-full `intersection` now takes-0.116 s from a cold persistent-index context and 0.100 s after that cache has-already been forced, down from 3.475 s before indexed descent. A-five-thousand-site `symmetricDifference` result fell from 4.022 s / 2.19 GB-allocated to 0.121 s / 244 MB. Exact `siteRelation` now takes 0.083 s cold and-0.115 s after forcing the persistent cache, down from 3.417 s before indexed-descent. Singleton insertion remains 5.326 ms from 48.037 ms. These are raw-publication measurements; paying for `canonicalize` afterward is deliberately-reported separately rather than smuggled into the local-operation claim.+`difference` takes 0.169 s. Near-full `intersection` takes 0.116 s and 0.100 s+across the cold and pre-forced contexts. A five-thousand-site+`symmetricDifference` result takes 0.121 s and allocates 244 MB. Exact+`siteRelation` takes 0.083 s and 0.115 s across those contexts. Singleton+insertion takes 5.326 ms. These are raw publication measurements;+`canonicalize` is measured separately when construction-independent numbering+is required.  ```haskell union :: JoinSemilattice annotation
− docs/ARCHITECTURE.md
@@ -1,170 +0,0 @@-# moonlight-triangulation architecture laws--`moonlight-triangulation.cabal` is the authoritative component, module, and-dependency graph. Duplicating that inventory here only manufactures a stale-second owner. This document records the laws the manifest cannot express.--## Geometry has one owner--The coordinate pages `triPointX` and `triPointY` own vertex geometry. Vertex-payloads are independent annotations: `setVertexData` and `mapVertices` may-change them without moving a vertex. `triPointIndex` is a derived lookup over-the coordinate pages.--Construction may read a position from an incoming payload through-`HasPosition`; no operation may later reconstruct geometry from that payload.-The versioned serialization boundary therefore persists coordinate pages and-payloads independently, then derives only the point index on decode.--## Topology is finite and mutation is sealed--The half-edge DCEL is a structure of arrays. Twin half-edges are arithmetic-complements (`e xor 1`), and every inner face has a three-edge cycle. Persistent-operations thaw into `MutableDcel` in `ST`, perform a bounded local rewrite, and-freeze the result; mutable references never escape.--Validation descends in two stages. Structural cardinality, range, link, face,-vertex, point-index, and Euler obligations must glue first. Geometric-orientation and Delaunay checks run only after that structural section is-valid, because reading geometry through malformed links would turn a reported-obstruction into an indexing crash.--## Constrained regions descend by barrier parity--For a constrained triangulation, a face's region is determined by the minimum-number of constrained edges crossed from the outer face. Even depths are-outside: depth zero is the exterior, depth one the first bounded domain, depth-two a hole, and so on. A dangling constraint can be walked around at depth-zero.--`FloodFillIterator.facesAtEvenBarrierDepth` owns this derived classification.-CDT queries and refinement import it; neither maintains a second definition of-"outside."--## Component cuts justify dependencies--Sublibraries are proof boundaries, not size buckets. In particular,-`serialize` alone owns the `binary` and `bytestring` dependencies. Modules named-`Internal` may be exposed only where Cabal sublibraries must cross them; that-exposure does not make them a supported public API.--## The surface states a theory--`Moonlight.Triangulation` is one export list read as the statement of a theory,-in eight sections: the object, generation, the annotation functor, the-finite-set algebra and its normal form, the constraint layer, refinement,-observations, discharge. A name reaches the surface because a law mentions it.--The headline law is a homomorphism from finite site sets to canonical-observations of triangulations,--    publish (A ∪ B) = canonicalize (union (publish A) (publish B))--where `publish` sends a site set through `delaunay` and then through-`canonicalize . mapVertices (const ())`. A raw build or local union may retain-the schedule that produced it, so structural `Eq` compares resident-representations while the finite-set laws compare their canonical,-payload-free images. `canonicalize` is that explicit physical normal form, and-`mapVertices (const ())` is the arrow onto the carrier used by the-geometry-only laws. Annotated `union` and `unions` glue equal-site payloads-through `JoinSemilattice` without allowing payloads to author geometry.--The exact support order is observed by `siteRelation`: equality, either proper-subset direction, disjointness, or a positive partial-overlap count. Restriction-descends through the same private site-set owner. `intersectionWith` combines-the left and right annotations only where both supports contain the coordinate;-`difference` preserves the left annotation; `symmetricDifference` preserves-the annotation of whichever exclusive support contains the coordinate. All-three publish through the same canonical rebuild boundary as the geometry-only-operations.--The generator is named `delaunay` because the equation is false of-triangulations in general and true of Delaunay triangulations. The name has to-make the law true.--The topology arena is finite, so union is not total. `union` and `unions`-therefore return `Either BuildError`; arena exhaustion is a typed obstruction,-not a lawful `Semigroup` instance with a trapdoor beneath it.--The constrained layer is not a total semilattice and does not pretend to be-one. Its-extension is the pair lattice of site sets and segment sets under componentwise-union, on which `constrainedDelaunay` is a partial homomorphism restricted to-the realizable sublattice: partiality kills `Semigroup`, and greedy-totalization kills associativity. `unionConstrained` is therefore surfaced as-an explicit operation returning `ConstrainedUnionError`, never as a typeclass-instance.--## Tiers govern names at their export site--Four tiers, and a name's tier is where it is exported rather than where it is-defined — a surface name may be defined below without lowering its tier.--The facade is the surface, and a subtraction there is a major version. The-sublibrary components are a partition: no manifest outside this package names-one, the in-repo spade referent excepted, since it ships with the package it-measures. Non-`Internal` sublibrary modules are the machine room, semi-stable-and reached by consumers who accept that. `Internal` modules promise nothing.--## Results are values, not histories--`BuildResult` and `RefinementResult` are abstract, reached through named-projections; `buildStats` and the rest of the telemetry stay below the wall.-Neither derives `Eq` or `Show`, because a derived instance observes through a-facade that hides — equality would consult counters the surface does not admit-exist.--## One concept has one owner--`isConstraintEdge` is owned by `Dcel`, polymorphic in the constraint mode;-`Cdt` does not restate it, and no alias survives. `innerFaceVertices` remains a-genuine collision — `Dcel`'s takes raw identifiers and answers `Maybe`, while-`Handles.Dynamic`'s takes and answers handles. Neither is surfaced, so the-collision stays in the machine room and callers name the module they mean.--## Vocabulary precedes representation--`Internal.Types` holds the vocabulary the surface names, none of which mentions-the stored mesh. `Internal.Representation` holds the structure of arrays, the-payload traversals, and the records that carry a built mesh beside its-telemetry. Representation imports vocabulary; the reverse import does not-exist. A type whose definition mentions the `Triangulation` record belongs to-Representation, which is why the result records live there.--## Mechanical acceptance--The native suite checks geometry and topology laws without depending on-`serialize`; the serialization suite checks the binary boundary separately. The-algebra suite is three slices with three dependency sets: fixtures, the laws-stated against the facade alone, and the agreement between the seam schedule-and the reference rebuild, which names an internal schedule and travels with-it. The parallel suite owns tournament behavior. The separate coherence test-imports every slice against the union of their dependencies at `-O0`; it detects-module and instance collisions but does not execute the behavioral suites a-second time.-The package's `warnings-as-errors` flag and `weeder.toml` turn compiler warnings-and unreachable internal declarations into refusals. The dedicated GitHub-workflow selects all library components from Cabal's generated plan rather than-restating build directories by hand.--## Published documentation is assembled, not generated--Cabal writes a Haddock interface for every sublibrary and hands none of them to-the siblings. The facade re-exports names it therefore cannot resolve, and-renders them as text. Cabal also writes one documentation tarball per-component, each under the same name, so the components overwrite one another-and only the last to run survives.--Hackage adds conditions of its own. A module on the package page links to its-documentation only when that page sits at the documentation root, so Cabal's-per-sublibrary subdirectories leave every module listed and unreachable. A-tarball is refused outright for a colon in any filename — which is how Cabal-names the sublibrary Hoogle databases — and for the extended attributes a macOS-`tar` attaches unbidden.--None of this is a defect in the component cuts. The repair changes no module,-no export, and no dependency; it is entirely downstream of the manifest, which-is why the manifest cannot express it. `release/hackage-docs.sh` owns it:-regenerate the facade against every sibling interface, flatten the module pages-to one root, cut a portable tarball. A release that skips the script publishes-a surface of unlinked names.
− fuzz/README.md
@@ -1,25 +0,0 @@-# Coverage-guided fuzzing--The fuzz executables are byte-oriented boundaries for an external coverage-guided engine. They are excluded from ordinary builds and add no dependency to any library component.--```bash-cabal build -ffuzz-targets \-  exe:moonlight-triangulation-fuzz-decode \-  exe:moonlight-triangulation-fuzz-constraints \-  exe:moonlight-triangulation-fuzz-refinement-```--Each executable accepts one corpus path in `@@` form or reads the input from standard input. On Linux, AFL++ can guide an uninstrumented GHC executable through QEMU mode:--```bash-afl-fuzz -Q -i fuzz/corpus/decode -o fuzz/artifacts/decode -- \-  "$(cabal list-bin -ffuzz-targets exe:moonlight-triangulation-fuzz-decode)" @@--afl-fuzz -Q -i fuzz/corpus/constraints -o fuzz/artifacts/constraints -- \-  "$(cabal list-bin -ffuzz-targets exe:moonlight-triangulation-fuzz-constraints)" @@--afl-fuzz -Q -i fuzz/corpus/refinement -o fuzz/artifacts/refinement -- \-  "$(cabal list-bin -ffuzz-targets exe:moonlight-triangulation-fuzz-refinement)" @@-```--Decoder refusals, unrealizable constraint batches, invalid refinement parameters, and rejected domains are expected typed outcomes. A target fails only when an admitted result violates the DCEL laws or serialization round-trip.
− fuzz/constraints/Main.hs
@@ -1,27 +0,0 @@-module Main (main) where--import qualified Data.ByteString as BS-import Data.List.NonEmpty (NonEmpty (..))-import qualified Data.Vector as V-import Moonlight.Triangulation-import Moonlight.Triangulation.Cdt-import Moonlight.Triangulation.Fuzz.Boundary (runFuzzTarget)-import Moonlight.Triangulation.Fuzz.Input (decodeConstraints, decodePoints)--newtype ConstraintFuzzFailure = ConstraintInvariantFailure (NonEmpty InvariantViolation)-  deriving stock (Show)--main :: IO ()-main = runFuzzTarget fuzzConstraintRecovery--fuzzConstraintRecovery :: BS.ByteString -> Either ConstraintFuzzFailure ()-fuzzConstraintRecovery bytes =-  case constrainedDelaunayMaximal unitElementDefaults points constraints of-    Left _ -> Right ()-    Right result ->-      case validateTriangulation (cdtBuildTriangulation result) of-        violation : violations -> Left (ConstraintInvariantFailure (violation :| violations))-        [] -> Right ()- where-  points = decodePoints bytes-  constraints = decodeConstraints bytes (V.length points)
− fuzz/corpus/constraints/seed.bin

binary file changed (12 → absent bytes)

− fuzz/corpus/decode/seed.bin

binary file changed (482 → absent bytes)

− fuzz/corpus/refinement/seed.bin

binary file changed (14 → absent bytes)

− fuzz/decode/Main.hs
@@ -1,49 +0,0 @@-{-# LANGUAGE DataKinds #-}--module Main (main) where--import qualified Data.ByteString as BS-import qualified Data.ByteString.Lazy as BL-import Data.List.NonEmpty (NonEmpty (..))-import Moonlight.Triangulation-import Moonlight.Triangulation.Fuzz.Boundary (runFuzzTarget)-import Moonlight.Triangulation.Serialization-import Moonlight.Triangulation.Types (KnownConstraintMode)--data DecodeFuzzFailure-  = DecodeInvariantFailure !(NonEmpty InvariantViolation)-  | DecodeRoundTripFailure-  deriving stock (Show)--main :: IO ()-main = runFuzzTarget fuzzDecodeTriangulation--fuzzDecodeTriangulation :: BS.ByteString -> Either DecodeFuzzFailure ()-fuzzDecodeTriangulation bytes =-  checkUnconstrained (decodeTriangulation lazyBytes)-    *> checkConstrained (decodeTriangulation lazyBytes)- where-  lazyBytes = BL.fromStrict bytes--checkUnconstrained-  :: Either SerializationError (Triangulation 'Unconstrained () () () ())-  -> Either DecodeFuzzFailure ()-checkUnconstrained = either (const (Right ())) checkDecoded--checkConstrained-  :: Either SerializationError (Triangulation 'Constrained () () () ())-  -> Either DecodeFuzzFailure ()-checkConstrained = either (const (Right ())) checkDecoded--checkDecoded-  :: KnownConstraintMode mode-  => Triangulation mode () () () ()-  -> Either DecodeFuzzFailure ()-checkDecoded triangulation =-  case validateTriangulation triangulation of-    violation : violations -> Left (DecodeInvariantFailure (violation :| violations))-    [] ->-      case decodeTriangulation (encodeTriangulation triangulation) of-        Right decoded-          | decoded == triangulation -> Right ()-        _ -> Left DecodeRoundTripFailure
− fuzz/refinement/Main.hs
@@ -1,117 +0,0 @@-module Main (main) where--import qualified Data.ByteString as BS-import qualified Data.Set as Set-import Data.List.NonEmpty (NonEmpty (..))-import qualified Data.Vector as V-import Moonlight.Triangulation-import Moonlight.Triangulation.Cdt-import Moonlight.Triangulation.Fuzz.Boundary (runFuzzTarget)-import Moonlight.Triangulation.Fuzz.Input-  ( decodeConstraints-  , decodePoints-  , decodeRefinementParameters-  , inputByte-  )-import Moonlight.Triangulation.Handles.HandleDefs-  ( FaceId (..)-  , UndirectedEdgeId (..)-  )--data RefinementFuzzFailure-  = RefinementInvariantFailure !(NonEmpty InvariantViolation)-  | RefinementFullDomainInvariantFailure !(NonEmpty InvariantViolation)-  | RefinementDomainInvariantFailure !(NonEmpty InvariantViolation)-  deriving stock (Show)--main :: IO ()-main = runFuzzTarget fuzzRefinementAdmission--fuzzRefinementAdmission :: BS.ByteString -> Either RefinementFuzzFailure ()-fuzzRefinementAdmission bytes =-  case constrainedDelaunayMaximal unitElementDefaults points constraints of-    Left _ -> Right ()-    Right buildResult ->-      let triangulation = cdtBuildTriangulation buildResult-       in checkOrdinary triangulation-            *> checkFullDomain triangulation-            *> checkHostileDomain triangulation- where-  points = decodePoints bytes-  constraints = decodeConstraints bytes (V.length points)-  parameters = decodeRefinementParameters bytes-  checkOrdinary triangulation =-    case validateRefinementParameters parameters of-      Left _ -> Right ()-      Right () ->-        either-          (const (Right ()))-          (checkResult RefinementInvariantFailure)-          (refine id parameters triangulation)-  checkFullDomain triangulation =-    either-      (const (Right ()))-      (checkResult RefinementFullDomainInvariantFailure . refinementDomainResult)-      ( refineWithinDomain-          id-          domainParameters-          (allInnerFaces triangulation)-          Set.empty-          triangulation-      )-  checkHostileDomain triangulation =-    either-      (const (Right ()))-      (checkResult RefinementDomainInvariantFailure . refinementDomainResult)-      ( refineWithinDomain-          id-          domainParameters-          (faceSelection triangulation)-          (edgeSelection triangulation)-          triangulation-      )-  allInnerFaces-    :: Triangulation mode vertex directed undirected face-    -> Set.Set FaceId-  allInnerFaces triangulation =-    Set.fromDistinctAscList-      (FaceId . fromIntegral <$> [1 .. numFaces triangulation - 1])-  domainParameters =-    defaultRefinementParameters-      { refineMaxAdditionalVertices = Just (fromIntegral (inputByte bytes 7) `mod` 9)-      , refineMaxArea = Just (fromIntegral (inputByte bytes 8) / 8 + 1 / 8)-      , refinePreserveConvexHull = True-      , refineKeepConstraintEdges = True-      , refineExcludeOuterFaces = False-      }-  faceSelection triangulation =-    Set.fromList-      ( V.toList-          ( V.generate-              (min 24 (fromIntegral (inputByte bytes 9)))-              (FaceId . fromIntegral . selectedFace triangulation)-          )-      )-  selectedFace triangulation index-    | inputByte bytes (index + 10) `mod` 8 == 0 = numFaces triangulation + index-    | otherwise = fromIntegral (inputByte bytes (index + 10)) `mod` max 1 (numFaces triangulation)-  edgeSelection triangulation =-    Set.fromList-      ( V.toList-          ( V.generate-              (min 24 (fromIntegral (inputByte bytes 34)))-              (UndirectedEdgeId . fromIntegral . selectedEdge triangulation)-          )-      )-  selectedEdge triangulation index-    | inputByte bytes (index + 35) `mod` 8 == 0 = numUndirectedEdges triangulation + index-    | otherwise = fromIntegral (inputByte bytes (index + 35)) `mod` max 1 (numUndirectedEdges triangulation)--checkResult-  :: (NonEmpty InvariantViolation -> RefinementFuzzFailure)-  -> RefinementResult mode vertex directed undirected face-  -> Either RefinementFuzzFailure ()-checkResult failure result =-  case validateTriangulation (refinedTriangulation result) of-    violation : violations -> Left (failure (violation :| violations))-    [] -> Right ()
− fuzz/support/Moonlight/Triangulation/Fuzz/Boundary.hs
@@ -1,20 +0,0 @@-{-# LANGUAGE LambdaCase #-}--module Moonlight.Triangulation.Fuzz.Boundary-  ( runFuzzTarget-  ) where--import qualified Data.ByteString as BS-import System.Environment (getArgs)-import System.Exit (die)--runFuzzTarget :: Show failure => (BS.ByteString -> Either failure ()) -> IO ()-runFuzzTarget target =-  readInput >>= either (die . show) (const (pure ())) . target--readInput :: IO BS.ByteString-readInput =-  getArgs >>= \case-    [] -> BS.getContents-    [path] -> BS.readFile path-    _ -> die "expected zero arguments for stdin or one input path"
− fuzz/support/Moonlight/Triangulation/Fuzz/Input.hs
@@ -1,65 +0,0 @@-module Moonlight.Triangulation.Fuzz.Input-  ( decodeConstraints-  , decodePoints-  , decodeRefinementParameters-  , inputByte-  ) where--import qualified Data.ByteString as BS-import qualified Data.Vector as V-import Data.Word (Word8)-import Moonlight.Triangulation--decodePoints :: BS.ByteString -> V.Vector Point-decodePoints bytes =-  V.generate pointCount $ \index ->-    Point-      (coordinate (inputByte bytes (2 * index)) + fromIntegral (index `mod` 3) / 1024)-      (coordinate (inputByte bytes (2 * index + 1)) + fromIntegral ((index * index) `mod` 5) / 1024)- where-  pointCount = max 3 (min 64 ((BS.length bytes + 1) `quot` 2))-  coordinate :: Word8 -> Double-  coordinate value = (fromIntegral value - 127.5) / 4--decodeConstraints :: BS.ByteString -> Int -> V.Vector (Int, Int)-decodeConstraints bytes pointCount =-  V.generate constraintCount $ \index ->-    (endpoint (inputByte bytes (2 * index)), endpoint (inputByte bytes (2 * index + 1)))- where-  constraintCount = max 1 (min 96 ((BS.length bytes + 1) `quot` 2))-  endpoint value-    | value `mod` 8 == 0 = pointCount + fromIntegral (value `mod` 5)-    | otherwise = fromIntegral value `mod` pointCount--decodeRefinementParameters :: BS.ByteString -> RefinementParameters-decodeRefinementParameters bytes =-  defaultRefinementParameters-    { refineMaxAdditionalVertices = budget (inputByte bytes 0)-    , refineMinArea = metric (inputByte bytes 1)-    , refineMaxArea = metric (inputByte bytes 2)-    , refineMaxRadiusEdgeRatio = metric (inputByte bytes 3)-    , refinePreserveConvexHull = odd (inputByte bytes 4)-    , refineKeepConstraintEdges = odd (inputByte bytes 5)-    , refineExcludeOuterFaces = odd (inputByte bytes 6)-    }- where-  budget :: Word8 -> Maybe Int-  budget value =-    case value `mod` 5 of-      0 -> Nothing-      1 -> Just (-1)-      _ -> Just (fromIntegral value `mod` 17)-  metric :: Word8 -> Maybe Double-  metric value =-    case value `mod` 8 of-      0 -> Nothing-      1 -> Just (-1)-      2 -> Just 0-      3 -> Just (0 / 0)-      4 -> Just (1 / 0)-      _ -> Just (fromIntegral value / 8)--inputByte :: BS.ByteString -> Int -> Word8-inputByte bytes index-  | BS.null bytes = fromIntegral index-  | otherwise = BS.index bytes (index `mod` BS.length bytes)
moonlight-triangulation.cabal view
@@ -1,6 +1,6 @@ cabal-version:       3.4 name:                moonlight-triangulation-version:             0.1.0.0+version:             1.0.0.0 synopsis:            Delaunay triangulations as a lawful finite-set algebra. description:         Delaunay and constrained Delaunay triangulation as a lawful                      finite-set algebra: a mesh is a value of its site set, so@@ -32,13 +32,8 @@ extra-doc-files:   README.md   CHANGELOG.md-  docs/ARCHITECTURE.md extra-source-files:   weeder.toml-  fuzz/README.md-  fuzz/corpus/constraints/seed.bin-  fuzz/corpus/decode/seed.bin-  fuzz/corpus/refinement/seed.bin  source-repository head   type:     git@@ -50,11 +45,6 @@   default: False   manual: True -flag fuzz-targets-  description: Build the external coverage-guided fuzz entrypoints.-  default: False-  manual: True- common shared-properties   default-language: GHC2024   ghc-options:@@ -269,45 +259,6 @@     , moonlight-triangulation:build     , moonlight-triangulation:dual   ghc-options: -fexpose-all-unfoldings--common triangulation-fuzz-properties-  import: shared-properties-  hs-source-dirs: fuzz/support-  other-modules:-    Moonlight.Triangulation.Fuzz.Boundary-    Moonlight.Triangulation.Fuzz.Input-  build-depends:-    base >= 4.20 && < 5-    , bytestring >= 0.12 && < 0.13-    , containers >= 0.8 && < 0.9-    , vector >= 0.13 && < 0.14-    , moonlight-triangulation-    , moonlight-triangulation:build-    , moonlight-triangulation:dcel--executable moonlight-triangulation-fuzz-decode-  import: triangulation-fuzz-properties-  if !flag(fuzz-targets)-    buildable: False-  main-is: Main.hs-  hs-source-dirs: fuzz/decode-  build-depends:-    binary >= 0.8 && < 0.9-    , moonlight-triangulation:serialize--executable moonlight-triangulation-fuzz-constraints-  import: triangulation-fuzz-properties-  if !flag(fuzz-targets)-    buildable: False-  main-is: Main.hs-  hs-source-dirs: fuzz/constraints--executable moonlight-triangulation-fuzz-refinement-  import: triangulation-fuzz-properties-  if !flag(fuzz-targets)-    buildable: False-  main-is: Main.hs-  hs-source-dirs: fuzz/refinement  -- ── test slices ────────────────────────────────────────────────────────────── 
src-build/Moonlight/Triangulation/Internal/Cdt/Query.hs view
@@ -43,11 +43,13 @@     . triConstraintEdges {-# INLINE constraintEdges #-} +-- | Bytes occupied by the dense constraint marker plane. constraintStorageBytes   :: Triangulation 'Constrained vertex directed undirected face   -> Integer constraintStorageBytes = toInteger . pagedLength . triConstraint +-- | Whether a marked edge directly joins two admitted vertices. existsConstraint   :: Triangulation 'Constrained vertex directed undirected face   -> VertexId@@ -58,6 +60,7 @@     Just edge -> Dcel.isConstraintEdge triangulation (asUndirected edge)     Nothing -> False +-- | Whether a segment can be admitted without crossing a resident constraint. canAddConstraint   :: Triangulation 'Constrained vertex directed undirected face   -> VertexId@@ -71,6 +74,7 @@       ConstraintBlocked _ -> False       ConstraintAdmitted -> True +-- | Whether a query segment properly crosses a resident constraint. intersectsConstraint   :: Triangulation 'Constrained vertex directed undirected face   -> QueryPoint@@ -87,6 +91,7 @@     | Dcel.isConstraintEdge triangulation (asUndirected edge) = Left ()   firstBlocking _ _ = Right () +-- | Resident constrained edges properly crossed by a query segment. getConflictingEdgesBetweenPoints   :: Triangulation 'Constrained vertex directed undirected face   -> QueryPoint@@ -98,6 +103,7 @@   , Dcel.isConstraintEdge triangulation (asUndirected edge)   ] +-- | Resident constrained edges crossed between two admitted vertices. getConflictingEdgesBetweenVertices   :: Triangulation 'Constrained vertex directed undirected face   -> VertexId@@ -111,12 +117,14 @@       , Dcel.isConstraintEdge triangulation (asUndirected edge)       ] +-- | Find an oriented edge joining two admitted vertices. findDirectedEdge :: Triangulation 'Constrained vertex directed undirected face -> VertexId -> VertexId -> Maybe DirectedEdgeId findDirectedEdge triangulation from to =   case filter ((== to) . Dcel.destination triangulation) (Dcel.vertexOutgoingEdges triangulation from) of     edge : _ -> Just edge     [] -> Nothing +-- | Find an oriented edge in a thawed triangulation. findMutableEdge :: MutableDcel s vertex directed undirected face -> Int -> Int -> ST s (Maybe Int) findMutableEdge mutable from to = do   start <- readVertexOut mutable from@@ -136,11 +144,13 @@             previousEdge <- readPrevious mutable edge             go (remaining - 1) start (previousEdge `xor` 1) True +-- | Validate that both constraint endpoints belong to the mesh. validateEndpoints :: Triangulation 'Constrained vertex directed undirected face -> VertexId -> VertexId -> Either (CdtError) () validateEndpoints triangulation from to   | not (validVertex triangulation from) = Left (InvalidConstraintVertex from)   | not (validVertex triangulation to) = Left (InvalidConstraintVertex to)   | otherwise = Right () +-- | Whether a vertex handle is admitted by the triangulation. validVertex :: Triangulation 'Constrained vertex directed undirected face -> VertexId -> Bool validVertex triangulation (VertexId vertex) = fromIntegral vertex < Dcel.numVertices triangulation
src-build/Moonlight/Triangulation/Internal/Cdt/Region.hs view
@@ -15,6 +15,7 @@ import Moonlight.Triangulation.Internal.Representation import Moonlight.Triangulation.Internal.Types +-- | Inner faces at even minimum constraint-crossing depth from the outer face. outerRegionFaces   :: Triangulation 'Constrained vertex directed undirected face   -> [FaceId]@@ -26,6 +27,7 @@ outerRegionFaces triangulation =   facesAtEvenBarrierDepth triangulation (Dcel.isConstraintEdge triangulation) +-- | Inner faces enclosed at odd constraint-crossing depth. boundedRegionFaces   :: Triangulation 'Constrained vertex directed undirected face   -> [FaceId]
src-build/Moonlight/Triangulation/Internal/Cdt/Segment.hs view
@@ -161,6 +161,8 @@           , constraintAddedEdges = added           } +-- | Insert a polyline's vertices and recover each adjacent segment as a+-- constraint, optionally closing the final segment back to the first. addConstraintEdges   :: HasPosition vertex   => Triangulation 'Constrained vertex directed undirected face@@ -201,6 +203,7 @@   let !mapping = buildInputVertices result   pure (buildTriangulation result, V.generate (sizeofPrimArray mapping) (VertexId . indexPrimArray mapping)) +-- | Retire one constraint edge and restore local Delaunay legality. removeConstraintEdge   :: Triangulation 'Constrained vertex directed undirected face   -> UndirectedEdgeId
src-build/Moonlight/Triangulation/Internal/Cdt/Types.hs view
@@ -150,10 +150,15 @@ -- of local face rotation and handle numbering. data ConstrainedSeamFaceEvidence = ConstrainedSeamFaceEvidence   { constrainedSeamSourceFace :: !FaceId+    -- ^ Face handle in the source operand.   , constrainedSeamTargetFace :: !FaceId+    -- ^ Corresponding face handle in the published result.   , constrainedSeamFaceFirstPoint :: !(Point)+    -- ^ Least vertex position under canonical point order.   , constrainedSeamFaceSecondPoint :: !(Point)+    -- ^ Middle vertex position under canonical point order.   , constrainedSeamFaceThirdPoint :: !(Point)+    -- ^ Greatest vertex position under canonical point order.   }   deriving stock (Eq, Show, Generic)   deriving anyclass (NFData)@@ -163,7 +168,9 @@ -- 'Just' records the corridor outcome when recovery was required. data ConstrainedSeamConstraintEvidence = ConstrainedSeamConstraintEvidence   { constrainedSeamConstraintSegment :: !(CanonicalSegment)+    -- ^ Canonical source segment preserved by the publication.   , constrainedSeamConstraintRecovery :: !(Maybe ConstraintOutcome)+    -- ^ Recovery work when copying alone did not preserve the segment.   }   deriving stock (Eq, Show, Generic)   deriving anyclass (NFData)@@ -174,17 +181,25 @@ data ConstrainedSeamResult vertex = ConstrainedSeamResult   { constrainedSeamResultTriangulation       :: !(Triangulation 'Constrained vertex () () ())+    -- ^ Published constrained union.   , constrainedSeamLeftFaceEvidence       :: !(V.Vector (ConstrainedSeamFaceEvidence))+    -- ^ Transport evidence for every active left-source face.   , constrainedSeamRightFaceEvidence       :: !(V.Vector (ConstrainedSeamFaceEvidence))+    -- ^ Transport evidence for every active right-source face.   , constrainedSeamNewFaces :: !(V.Vector FaceId)+    -- ^ Faces created by the seam rather than transported from an operand.   , constrainedSeamLeftConstraintEvidence       :: !(V.Vector (ConstrainedSeamConstraintEvidence))+    -- ^ Preservation evidence for left-source constraints.   , constrainedSeamRightConstraintEvidence       :: !(V.Vector (ConstrainedSeamConstraintEvidence))+    -- ^ Preservation evidence for right-source constraints.   , constrainedSeamConstraintStats :: !ConstraintBatchStats+    -- ^ Constraint recovery work performed by the seam.   , constrainedSeamBuildStats :: !BuildStats+    -- ^ Topology work performed by the seam.   }   deriving stock (Generic)   deriving anyclass (NFData)@@ -201,10 +216,14 @@   deriving stock (Eq, Show, Generic)   deriving anyclass (NFData) +-- | Atomic result of admitting one constraint segment. data ConstraintResult vertex directed undirected face = ConstraintResult   { constraintTriangulation :: !(Triangulation 'Constrained vertex directed undirected face)+    -- ^ Published constrained triangulation.   , constraintPath :: !(V.Vector DirectedEdgeId)+    -- ^ Directed-edge path representing the segment at publication time.   , constraintAddedEdges :: {-# UNPACK #-} !Int+    -- ^ Edges created while recovering the path.   }   deriving stock (Generic)   deriving anyclass (NFData)@@ -216,6 +235,7 @@   (Show vertex, Show directed, Show undirected, Show face)   => Show (ConstraintResult vertex directed undirected face) +-- | Per-request outcome in a constraint batch. data ConstraintOutcome   = ConstraintAccepted       !(V.Vector DirectedEdgeId)@@ -225,21 +245,32 @@   deriving stock (Eq, Show, Generic)   deriving anyclass (NFData) +-- | Aggregate work and disposition counts for a constraint batch. data ConstraintBatchStats = ConstraintBatchStats   { constraintBatchRequests :: {-# UNPACK #-} !Int+    -- ^ Requests interpreted.   , constraintBatchAccepted :: {-# UNPACK #-} !Int+    -- ^ Requests admitted.   , constraintBatchRejected :: {-# UNPACK #-} !Int+    -- ^ Requests refused by an existing constraint.   , constraintBatchCorridors :: {-# UNPACK #-} !Int+    -- ^ Recovery corridors opened.   , constraintBatchReusedFaces :: {-# UNPACK #-} !Int+    -- ^ Existing faces retained while recovering corridors.   , constraintBatchCrossedEdges :: {-# UNPACK #-} !Int+    -- ^ Edges crossed while tracing corridors.   }   deriving stock (Eq, Show, Read, Generic)   deriving anyclass (NFData) +-- | Atomic publication of one constraint batch and its receipts. data ConstraintBatchResult vertex directed undirected face = ConstraintBatchResult   { constraintBatchTriangulation :: !(Triangulation 'Constrained vertex directed undirected face)+    -- ^ Published constrained triangulation.   , constraintBatchOutcomes :: !(V.Vector ConstraintOutcome)+    -- ^ Outcomes in request order.   , constraintBatchStats :: !ConstraintBatchStats+    -- ^ Aggregate work performed by the batch.   }   deriving stock (Generic)   deriving anyclass (NFData)@@ -258,9 +289,13 @@ -- this immutable result, not a hidden continuation of this transaction. data ConstrainedExtensionResult vertex directed undirected face = ConstrainedExtensionResult   { constrainedExtensionTriangulation :: !(Triangulation 'Constrained vertex directed undirected face)+    -- ^ Published extension.   , constrainedExtensionConstraintOutcomes :: !(V.Vector ConstraintOutcome)+    -- ^ Outcomes for incoming constraints in request order.   , constrainedExtensionConstraintStats :: !ConstraintBatchStats+    -- ^ Constraint work performed by the extension.   , constrainedExtensionBuildStats :: !BuildStats+    -- ^ Topology work performed by the extension.   }   deriving stock (Generic)   deriving anyclass (NFData)@@ -278,8 +313,11 @@ -- singleton path already carries. data ConstraintSplitBatchResult vertex directed undirected face = ConstraintSplitBatchResult   { splitBatchTriangulation :: !(Triangulation 'Constrained vertex directed undirected face)+    -- ^ Published constrained triangulation.   , splitBatchPaths :: !(V.Vector (V.Vector DirectedEdgeId))+    -- ^ Recovered paths in request order.   , splitBatchAddedEdges :: {-# UNPACK #-} !Int+    -- ^ Edges created across all requested divisions.   }   deriving stock (Generic)   deriving anyclass (NFData)@@ -346,11 +384,16 @@   , accumulatedProgramRequest :: !ConstraintRequestAccumulator   } +-- | Result of maximal constrained construction, including rejected requests. data CdtBuildResult vertex directed undirected face = CdtBuildResult   { cdtBuildTriangulation :: !(Triangulation 'Constrained vertex directed undirected face)+    -- ^ Published constrained triangulation.   , cdtBuildInputVertices :: !(PrimArray Word32)+    -- ^ Input-to-vertex handle projection.   , cdtBuildStats :: !BuildStats+    -- ^ Topology work performed by construction.   , cdtRejectedConstraints :: !(V.Vector (Int, Int))+    -- ^ Input-index pairs that could not be admitted.   }   deriving stock (Generic)   deriving anyclass (NFData)
src-build/Moonlight/Triangulation/Internal/Excision.hs view
@@ -1,16 +1,11 @@ {-# LANGUAGE BangPatterns #-}-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE DerivingStrategies #-}  -- | Excision of a vertex from a thawed mesh: the removal kernel, stated over -- the mutable arena and publishing nothing. module Moonlight.Triangulation.Internal.Excision-  ( RemovalOutcome (..)-  , removeMutable+  ( removeMutable   ) where -import Control.DeepSeq (NFData) import Control.Monad (forM, when) import Control.Monad.ST (ST) import Data.Bits (xor)@@ -28,69 +23,29 @@   ) import Moonlight.Triangulation.Internal.Types import Moonlight.Triangulation.Math (orient2d)-import GHC.Generics (Generic) --- | What a single removal produced: the position and payload of the removed--- vertex, and what swap compaction did — the slot it freed together with the--- position now standing in it, when it moved anything. All fixed handles are--- invalidated by removal, so the swap report is the only continuity the mesh--- offers a caller still holding one.------ Slot and position travel together because they are one fact. A caller told--- only the slot has to go back to the mesh to learn what landed there, and a--- caller that reconstructs the position from its own records reconstructs it--- from something the mesh never stored: the arena holds @canonicalPoint@ of--- what it was given, so a signed zero would differ in the last bit and every--- distance tie decided against it would answer a different vertex.------ It is not a location hint. A hint names a face; this names an arena slot,--- and the vertex that landed in it is the arena's last, which stands wherever--- it stands. The locator re-anchors on its own cached face, which--- 'Moonlight.Triangulation.Internal.Mutable.swapRemoveFace' follows through--- compaction.-data RemovalOutcome vertex = RemovalOutcome-  { removalOutcomePoint :: !(Point)-  , removalOutcomeData :: !vertex-  , removalOutcomeSwap :: !(Maybe (VertexId, Point))-  }-  deriving stock (Eq, Show, Generic)-  deriving anyclass (NFData)- -- | The proved outgoing section of one ordinary removal. Its edges live in the -- operation scratch arena in counter-clockwise order; the record carries only -- the section's extent and the first outer-face incidence, if any. data RemovalStar = RemovalStar {-# UNPACK #-} !Int !(Maybe Int) --- | The swap-compaction primitive reports raw arena indices; the public--- concept carries the named handle. One conversion, one owner.-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-    }- removeMutable   :: MutableDcel s vertex directed undirected face   -> OperationState s   -> Int-  -> ST s (Either BuildError (RemovalOutcome vertex))+  -> ST s (Either BuildError (Point, vertex, Maybe (Int, Point))) removeMutable mutable operation vertex = do   faces <- faceCount mutable   if faces <= 1-    then fmap removalOutcomeOf <$> removeDegenerate mutable vertex+    then removeDegenerate mutable vertex     else do       collected <- collectRemovalStar mutable operation vertex       case collected of         Left obstruction -> pure (Left obstruction)         Right (RemovalStar degree outerOutgoing) -> do           case outerOutgoing of-            Nothing -> fmap removalOutcomeOf <$> removeInterior mutable operation vertex degree-            Just hullEdge -> fmap removalOutcomeOf <$> removeHull mutable operation vertex hullEdge+            Nothing -> removeInterior mutable operation vertex degree+            Just hullEdge -> removeHull mutable operation vertex hullEdge  removeDegenerate   :: MutableDcel s vertex directed undirected face
src-build/Moonlight/Triangulation/Internal/Join.hs view
@@ -42,7 +42,8 @@ -- Skewed operands descend through the existing local copy-on-write session, so -- 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; 'canonicalize' is the+-- site set. Every schedule returns valid topology;+-- 'Moonlight.Triangulation.Dcel.canonicalize' is the -- separate physical observation when construction-independent numbering is -- required. --
src-build/Moonlight/Triangulation/Internal/Join/Seam.hs view
@@ -75,7 +75,8 @@     Nothing -> Nothing  -- | Execute a proved seam schedule. Numbering follows the schedule;--- 'canonicalize' remains the explicit construction-independent observation.+-- 'Moonlight.Triangulation.Dcel.canonicalize' remains the explicit+-- construction-independent observation. executeSeam   :: forall vertex    . SeamPlan@@ -350,7 +351,7 @@       _ <- addFaceBlock mutable (leftFaces + rightFaces - 2)       copySource mutable left 0 0 0       copySource mutable right leftVertices leftDirected (leftFaces - 1)-      traverse_ (uncurry (copySourceConstraints mutable)) constraintSections+      traverse_ (uncurry (protectSourceEdges mutable)) constraintSections       base <- spliceLowerTangent mutable leftDirected lowerLeft lowerRight       operation <- newOperationState (halfEdgeCapacity mutable)       stitched <-@@ -364,6 +365,7 @@       case stitched of         Left obstruction -> pure (Left obstruction)         Right () -> do+          traverse_ (uncurry (restoreSourceConstraints mutable)) constraintSections           statistics <- freezeBuildStats operation           fmap (\triangulation -> (triangulation, statistics))             <$> freezeTriangulation mutable@@ -513,6 +515,32 @@            (directedEdgeOffset + 2 * fromIntegral edge)     )     (constraintEdges source)++-- Source faces are immutable sections of a separated join.  Protect every+-- copied source edge while the seam is legalized, then restore the original+-- constraint plane before publication.  The temporary flags prevent the+-- corridor legalization from escaping the overlap and flipping a solved+-- source face.+protectSourceEdges+  :: MutableDcel s vertex () () ()+  -> Int+  -> Triangulation 'Constrained vertex () () ()+  -> ST s ()+protectSourceEdges mutable directedEdgeOffset source =+  forRange 0 (numDirectedEdges source `quot` 2) $ \edge -> do+    _ <- setConstraint mutable (directedEdgeOffset + 2 * edge)+    pure ()++restoreSourceConstraints+  :: MutableDcel s vertex () () ()+  -> Int+  -> Triangulation 'Constrained vertex () () ()+  -> ST s ()+restoreSourceConstraints mutable directedEdgeOffset source = do+  forRange 0 (numDirectedEdges source `quot` 2) $ \edge -> do+    _ <- clearConstraint mutable (directedEdgeOffset + 2 * edge)+    pure ()+  copySourceConstraints mutable directedEdgeOffset source  spliceLowerTangent   :: MutableDcel s vertex () () ()
src-build/Moonlight/Triangulation/Removal.hs view
@@ -12,12 +12,11 @@  import Control.DeepSeq (NFData) import Moonlight.Triangulation.Handles.HandleDefs (VertexId (..))-import Moonlight.Triangulation.Internal.Excision (RemovalOutcome (..)) 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 (excise, withLocalSession)+import Moonlight.Triangulation.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,8 +1,12 @@ {-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingStrategies #-}  -- | One owned editing transaction over a triangulation: thaw once, edit, publish once. module Moonlight.Triangulation.Session-  ( Session+  ( RemovalOutcome (..)+  , Session   , withSession   , withLocalSession   , insertVertex@@ -17,12 +21,13 @@   , 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 (insertPointCombining)-import Moonlight.Triangulation.Internal.Excision (RemovalOutcome, removeMutable)+import Moonlight.Triangulation.Internal.Excision (removeMutable) import Moonlight.Triangulation.Internal.Location (MutableLocation (..), locateMutable) import Moonlight.Triangulation.Internal.Mutable import Moonlight.Triangulation.Internal.OperationState (OperationState)@@ -31,7 +36,18 @@ 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@@ -174,7 +190,7 @@ removeIndexed mesh operation point = do   located <- lookupPointVertex mesh point   case located of-    Just vertex -> fmap (fmap Just) (removeMutable mesh operation vertex)+    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@@ -199,7 +215,7 @@         else do           stored <- pointAt mesh guess           if stored == query-            then fmap (fmap Just) (removeMutable mesh operation guess)+            then fmap (fmap Just) (removeMutableOutcome mesh operation guess)             else do               outgoing <- readVertexOut mesh guess               let interiorFace edge fallback = do@@ -222,7 +238,7 @@   case located of     Left obstruction -> pure (Left obstruction)     Right (MutableOnVertex vertex) ->-      fmap (fmap Just) (removeMutable mesh operation vertex)+      fmap (fmap Just) (removeMutableOutcome mesh operation vertex)     Right _ -> pure (Right Nothing)  -- | Remove the vertex standing at each point, answering per point in order.@@ -295,7 +311,26 @@     then pure (Left (RemovalVertexOutOfRange requested vertices))     else do       activatePointIndex mesh-      removeMutable mesh operation (fromIntegral raw)+      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.
src-build/Moonlight/Triangulation/SetAlgebra.hs view
@@ -60,7 +60,8 @@   siteRelationFromTriangulations left right {-# INLINE siteRelation #-} --- | A valid Delaunay representative of both site sets. Use 'canonicalize'+-- | A valid Delaunay representative of both site sets. Use+-- 'Moonlight.Triangulation.Dcel.canonicalize' -- when construction-independent dense numbering is required. union   :: JoinSemilattice annotation
src-core/Moonlight/Triangulation/Scalar.hs view
@@ -29,19 +29,26 @@   | otherwise = value {-# INLINE canonicalScalarZero #-} +-- | Machine format of the coordinate scalar. data BinaryFormat = BinaryFormat   { formatRadix :: !Integer+    -- ^ Numeric base of the significand.   , formatMantissaDigits :: !Int+    -- ^ Number of base-'formatRadix' digits in the significand.   , formatExponentRange :: !(Int, Int)+    -- ^ Inclusive minimum and exclusive maximum exponent bounds.   }   deriving stock (Eq, Show) +-- | Stable name of the coordinate scalar. scalarName :: String scalarName = "binary64" +-- | Bytes occupied by one coordinate component. scalarByteSize :: Int scalarByteSize = 8 +-- | Runtime-confirmed binary format of 'Double'. scalarBinaryFormat :: BinaryFormat scalarBinaryFormat =   BinaryFormat@@ -50,23 +57,29 @@     , formatExponentRange = floatRange (0 :: Double)     } +-- | Difference between one and the next representable value above one. scalarEpsilon :: Double scalarEpsilon = 2.220446049250313e-16 +-- | Maximum relative rounding error of one binary64 operation. scalarUnitRoundoff :: Double scalarUnitRoundoff = 1.1102230246251565e-16 +-- | Error coefficient for the filtered orientation predicate. scalarCcwErrorBound :: Double scalarCcwErrorBound = 3.3306690738754716e-16 +-- | Error coefficient for the filtered in-circle predicate. scalarInCircleErrorBound :: Double scalarInCircleErrorBound = 1.1102230246251577e-15 +-- | Exact orientation ordering of three binary64 coordinate pairs. orient2dCoordinates   :: Double -> Double -> Double -> Double -> Double -> Double   -> Ordering orient2dCoordinates = filteredOrient2dDouble +-- | Exact in-circle ordering of four binary64 coordinate pairs. inCircleCoordinates   :: Double -> Double -> Double -> Double   -> Double -> Double -> Double -> Double
src-dcel/Moonlight/Triangulation/Dcel.hs view
@@ -70,6 +70,7 @@ numVertices = pagedLength . triPointX {-# INLINE numVertices #-} +-- | Number of oriented half-edges in the mesh. numDirectedEdges :: Triangulation mode vertex directed undirected face -> Int numDirectedEdges = (`quot` 4) . pagedLength . triHalfTopology {-# INLINE numDirectedEdges #-}@@ -84,6 +85,7 @@ numFaces = pagedLength . triFaceEdge {-# INLINE numFaces #-} +-- | Number of bounded triangular faces. numInnerFaces :: Triangulation mode vertex directed undirected face -> Int numInnerFaces triangulation = max 0 (numFaces triangulation - 1) {-# INLINE numInnerFaces #-}@@ -95,6 +97,7 @@    in Point (pagedUnsafeIndex (triPointX triangulation) index) (pagedUnsafeIndex (triPointY triangulation) index) {-# INLINE vertexPoint #-} +-- | Dense vertex positions in handle order. vertexPoints :: Triangulation mode vertex directed undirected face -> V.Vector Point vertexPoints triangulation =   V.generate (numVertices triangulation) (vertexPoint triangulation . VertexId . fromIntegral)@@ -106,16 +109,19 @@   boxedUnsafeIndex (triVertexData triangulation) (fromIntegral vertex) {-# INLINE vertexData #-} +-- | Annotation carried by an admitted directed edge. directedEdgeData :: Triangulation mode vertex directed undirected face -> DirectedEdgeId -> directed directedEdgeData triangulation (DirectedEdgeId edge) =   boxedUnsafeIndex (triDirectedData triangulation) (fromIntegral edge) {-# INLINE directedEdgeData #-} +-- | Annotation carried by an admitted undirected edge. undirectedEdgeData :: Triangulation mode vertex directed undirected face -> UndirectedEdgeId -> undirected undirectedEdgeData triangulation (UndirectedEdgeId edge) =   boxedUnsafeIndex (triUndirectedData triangulation) (fromIntegral edge) {-# INLINE undirectedEdgeData #-} +-- | Annotation carried by an admitted face. faceData :: Triangulation mode vertex directed undirected face -> FaceId -> face faceData triangulation (FaceId face) =   boxedUnsafeIndex (triFaceData triangulation) (fromIntegral face)@@ -130,6 +136,7 @@ setVertexData triangulation (VertexId vertex) payload =   triangulation{triVertexData = boxedUpdate (fromIntegral vertex) payload (triVertexData triangulation)} +-- | Replace one directed-edge annotation without changing geometry or topology. setDirectedEdgeData   :: Triangulation mode vertex directed undirected face   -> DirectedEdgeId@@ -138,6 +145,7 @@ setDirectedEdgeData triangulation (DirectedEdgeId edge) payload =   triangulation{triDirectedData = boxedUpdate (fromIntegral edge) payload (triDirectedData triangulation)} +-- | Replace one undirected-edge annotation without changing geometry or topology. setUndirectedEdgeData   :: Triangulation mode vertex directed undirected face   -> UndirectedEdgeId@@ -146,6 +154,7 @@ setUndirectedEdgeData triangulation (UndirectedEdgeId edge) payload =   triangulation{triUndirectedData = boxedUpdate (fromIntegral edge) payload (triUndirectedData triangulation)} +-- | Replace one face annotation without changing geometry or topology. setFaceData   :: Triangulation mode vertex directed undirected face   -> FaceId@@ -154,11 +163,13 @@ setFaceData triangulation (FaceId face) payload =   triangulation{triFaceData = boxedUpdate (fromIntegral face) payload (triFaceData triangulation)} +-- | One outgoing directed edge, if the vertex is connected. vertexOutEdge :: Triangulation mode vertex directed undirected face -> VertexId -> Maybe DirectedEdgeId vertexOutEdge triangulation (VertexId vertex) =   DirectedEdgeId . fromIntegral <$> unpackOptionalIndex (pagedUnsafeIndex (triVertexOut triangulation) (fromIntegral vertex)) {-# INLINE vertexOutEdge #-} +-- | One boundary edge of a face, if the face has a boundary. adjacentEdge :: Triangulation mode vertex directed undirected face -> FaceId -> Maybe DirectedEdgeId adjacentEdge triangulation (FaceId face) =   DirectedEdgeId . fromIntegral <$> unpackOptionalIndex (pagedUnsafeIndex (triFaceEdge triangulation) (fromIntegral face))@@ -175,11 +186,13 @@ destination triangulation = origin triangulation . reverseEdge {-# INLINE destination #-} +-- | Next directed edge around the incident face. next :: Triangulation mode vertex directed undirected face -> DirectedEdgeId -> DirectedEdgeId next triangulation (DirectedEdgeId edge) =   DirectedEdgeId (pagedUnsafeIndex (triHalfTopology triangulation) (4 * fromIntegral edge + 1)) {-# INLINE next #-} +-- | Previous directed edge around the incident face. previous :: Triangulation mode vertex directed undirected face -> DirectedEdgeId -> DirectedEdgeId previous triangulation (DirectedEdgeId edge) =   DirectedEdgeId (pagedUnsafeIndex (triHalfTopology triangulation) (4 * fromIntegral edge + 2))@@ -191,6 +204,7 @@   FaceId (pagedUnsafeIndex (triHalfTopology triangulation) (4 * fromIntegral edge + 3)) {-# INLINE incidentFace #-} +-- | Whether the directed edge is incident to the unbounded face. isOuterDirectedEdge :: Triangulation mode vertex directed undirected face -> DirectedEdgeId -> Bool isOuterDirectedEdge triangulation edge = incidentFace triangulation edge == outerFace {-# INLINE isOuterDirectedEdge #-}@@ -208,6 +222,7 @@   pagedUnsafeIndex (triConstraint triangulation) (fromIntegral edge) /= (0 :: Word8) {-# INLINE isConstraintEdge #-} +-- | Number of constrained undirected edges. numConstraints :: Triangulation mode vertex directed undirected face -> Int numConstraints = triConstraintCount {-# INLINE numConstraints #-}@@ -232,6 +247,7 @@ faceVertices triangulation = map (origin triangulation) . faceDirectedEdges triangulation {-# INLINE faceVertices #-} +-- | The three directed edges of a bounded triangular face. innerFaceDirectedEdges :: Triangulation mode vertex directed undirected face -> FaceId -> Maybe (DirectedEdgeId, DirectedEdgeId, DirectedEdgeId) innerFaceDirectedEdges triangulation face   | face == outerFace = Nothing@@ -244,12 +260,14 @@         else Nothing {-# INLINE innerFaceDirectedEdges #-} +-- | The three vertices of a bounded triangular face. innerFaceVertices :: Triangulation mode vertex directed undirected face -> FaceId -> Maybe (VertexId, VertexId, VertexId) innerFaceVertices triangulation face = do   (e0, e1, e2) <- innerFaceDirectedEdges triangulation face   pure (origin triangulation e0, origin triangulation e1, origin triangulation e2) {-# INLINE innerFaceVertices #-} +-- | Dense bounded-face vertex triples in face-handle order. innerFaceVertexTriples   :: Triangulation mode vertex directed undirected face   -> V.Vector (VertexId, VertexId, VertexId)@@ -275,14 +293,17 @@     Just start -> circularWalk triangulation start (counterClockwise triangulation) {-# INLINE vertexOutgoingEdges #-} +-- | Previous directed edge around its origin vertex. clockwise :: Triangulation mode vertex directed undirected face -> DirectedEdgeId -> DirectedEdgeId clockwise triangulation edge = next triangulation (reverseEdge edge) {-# INLINE clockwise #-} +-- | Next directed edge around its origin vertex. counterClockwise :: Triangulation mode vertex directed undirected face -> DirectedEdgeId -> DirectedEdgeId counterClockwise triangulation edge = reverseEdge (previous triangulation edge) {-# INLINE counterClockwise #-} +-- | Strict fold over the directed edges around a face. foldFaceDirectedEdges'   :: Triangulation mode vertex directed undirected face -> FaceId   -> (a -> DirectedEdgeId -> a)@@ -294,6 +315,7 @@     Just start -> circularFold triangulation start (next triangulation) step initial {-# INLINE foldFaceDirectedEdges' #-} +-- | Strict fold over the directed edges originating at a vertex. foldVertexOutgoingEdges'   :: Triangulation mode vertex directed undirected face -> VertexId   -> (a -> DirectedEdgeId -> a)@@ -306,6 +328,7 @@ {-# INLINE foldVertexOutgoingEdges' #-}  -- Constraint bytes are intentionally reported separately by the CDT layer.+-- | Bytes occupied by vertex, half-edge, and face topology indices. topologyIndexBytes :: Triangulation mode vertex directed undirected face -> Integer topologyIndexBytes triangulation =   4 * toInteger@@ -314,6 +337,7 @@         + pagedLength (triFaceEdge triangulation)     ) +-- | Bytes occupied by authoritative coordinates and topology indices. geometryTopologyBytes :: Triangulation mode vertex directed undirected face -> Integer geometryTopologyBytes triangulation =   2 * toInteger scalarByteSize * toInteger (numVertices triangulation)
src-dcel/Moonlight/Triangulation/FloodFillIterator.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE FlexibleInstances #-} +-- | Shape queries and face flood fills over immutable triangulations. module Moonlight.Triangulation.FloodFillIterator   ( DistanceMetric (..)   , CircleMetric@@ -26,23 +27,30 @@ import Moonlight.Triangulation.Math import Moonlight.Triangulation.PointLocation import Moonlight.Triangulation.Types++-- | 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) !Double   deriving stock (Eq, Ord, Show) +-- | Typed refusal for an invalid circle query. data CircleMetricError   = InvalidCircleCenter !PointValidationError   | NonFiniteRadiusSquared !NonFiniteValue   | NegativeRadiusSquared !Double   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@@ -140,6 +148,7 @@     | 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)
src-dcel/Moonlight/Triangulation/Handles/Dynamic.hs view
@@ -5,6 +5,7 @@ {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE RoleAnnotations #-} +-- | Owning handles that prevent identifiers from crossing mesh boundaries. module Moonlight.Triangulation.Handles.Dynamic   ( InnerTag   , PossiblyOuterTag@@ -76,30 +77,37 @@ data PossiblyOuterTag  type role FixedFaceHandle nominal+-- | Fixed face identifier refined by whether it may denote the outer face. newtype FixedFaceHandle tag = FixedFaceHandle { unFixedFaceHandle :: FaceId }   deriving stock (Show)   deriving newtype (Eq, Ord) +-- | Forget the proof that a fixed face is bounded. asPossiblyOuter :: FixedFaceHandle InnerTag -> FixedFaceHandle PossiblyOuterTag asPossiblyOuter (FixedFaceHandle face) = FixedFaceHandle face {-# INLINE asPossiblyOuter #-} +-- | Recover the unrefined face identifier. fixedFaceId :: FixedFaceHandle tag -> FaceId fixedFaceId (FixedFaceHandle face) = face {-# INLINE fixedFaceId #-} +-- | Admitted vertex paired with its owning triangulation. data VertexHandle mode vertex directed undirected face = VertexHandle   !(Triangulation mode vertex directed undirected face)   !VertexId +-- | Admitted directed edge paired with its owning triangulation. data DirectedEdgeHandle mode vertex directed undirected face = DirectedEdgeHandle   !(Triangulation mode vertex directed undirected face)   !DirectedEdgeId +-- | Admitted undirected edge paired with its owning triangulation. data UndirectedEdgeHandle mode vertex directed undirected face = UndirectedEdgeHandle   !(Triangulation mode vertex directed undirected face)   !UndirectedEdgeId +-- | Admitted face paired with its owning triangulation and outer-face proof. data FaceHandle tag mode vertex directed undirected face = FaceHandle   !(Triangulation mode vertex directed undirected face)   !(FixedFaceHandle tag)@@ -116,6 +124,7 @@ instance Show (FaceHandle tag mode vertex directed undirected face) where   showsPrec precedence = showsPrec precedence . fixFace +-- | Admit a vertex identifier into a triangulation. vertexHandle   :: Triangulation mode vertex directed undirected face   -> VertexId@@ -124,6 +133,7 @@   | fromIntegral raw < Dcel.numVertices triangulation = Just (VertexHandle triangulation vertex)   | otherwise = Nothing +-- | Admit a directed-edge identifier into a triangulation. directedEdgeHandle   :: Triangulation mode vertex directed undirected face   -> DirectedEdgeId@@ -132,6 +142,7 @@   | fromIntegral raw < Dcel.numDirectedEdges triangulation = Just (DirectedEdgeHandle triangulation edge)   | otherwise = Nothing +-- | Admit an undirected-edge identifier into a triangulation. undirectedEdgeHandle   :: Triangulation mode vertex directed undirected face   -> UndirectedEdgeId@@ -140,6 +151,7 @@   | fromIntegral raw < Dcel.numUndirectedEdges triangulation = Just (UndirectedEdgeHandle triangulation edge)   | otherwise = Nothing +-- | Admit a face identifier that may denote the outer face. faceHandle   :: Triangulation mode vertex directed undirected face   -> FaceId@@ -148,6 +160,7 @@   | fromIntegral raw < Dcel.numFaces triangulation = Just (FaceHandle triangulation (FixedFaceHandle face))   | otherwise = Nothing +-- | Admit a face identifier while proving that it is bounded. innerFaceHandle   :: Triangulation mode vertex directed undirected face   -> FaceId@@ -158,103 +171,126 @@       FaceHandle _ (FixedFaceHandle valid) <- faceHandle triangulation face       pure (FaceHandle triangulation (FixedFaceHandle valid)) +-- | Owning handle to the unique unbounded face. outerFaceHandle   :: Triangulation mode vertex directed undirected face   -> FaceHandle PossiblyOuterTag mode vertex directed undirected face outerFaceHandle triangulation = FaceHandle triangulation (FixedFaceHandle Dcel.outerFace) +-- | Forget ownership and recover the vertex identifier. fixVertex :: VertexHandle mode vertex directed undirected face -> VertexId fixVertex (VertexHandle _ vertex) = vertex {-# INLINE fixVertex #-} +-- | Forget ownership and recover the directed-edge identifier. fixDirectedEdge :: DirectedEdgeHandle mode vertex directed undirected face -> DirectedEdgeId fixDirectedEdge (DirectedEdgeHandle _ edge) = edge {-# INLINE fixDirectedEdge #-} +-- | Forget ownership and recover the undirected-edge identifier. fixUndirectedEdge :: UndirectedEdgeHandle mode vertex directed undirected face -> UndirectedEdgeId fixUndirectedEdge (UndirectedEdgeHandle _ edge) = edge {-# INLINE fixUndirectedEdge #-} +-- | Forget ownership while retaining the outer-face refinement. fixFace :: FaceHandle tag mode vertex directed undirected face -> FixedFaceHandle tag fixFace (FaceHandle _ face) = face {-# INLINE fixFace #-} +-- | Vertex annotation through an owning handle. vertexHandleData :: VertexHandle mode vertex directed undirected face -> vertex vertexHandleData (VertexHandle triangulation vertex) = Dcel.vertexData triangulation vertex {-# INLINE vertexHandleData #-} +-- | Authoritative vertex position through an owning handle. vertexHandlePosition   :: VertexHandle mode vertex directed undirected face   -> Point vertexHandlePosition (VertexHandle triangulation vertex) = (Dcel.vertexPoint triangulation vertex) {-# INLINE vertexHandlePosition #-} +-- | One outgoing edge of a connected vertex. vertexHandleOutEdge   :: VertexHandle mode vertex directed undirected face   -> Maybe (DirectedEdgeHandle mode vertex directed undirected face) vertexHandleOutEdge (VertexHandle triangulation vertex) = DirectedEdgeHandle triangulation <$> Dcel.vertexOutEdge triangulation vertex +-- | Directed edges leaving a vertex in ring order. vertexHandleOutEdges   :: VertexHandle mode vertex directed undirected face   -> [DirectedEdgeHandle mode vertex directed undirected face] vertexHandleOutEdges (VertexHandle triangulation vertex) = map (DirectedEdgeHandle triangulation) (Dcel.vertexOutgoingEdges triangulation vertex) +-- | Directed-edge annotation through an owning handle. directedEdgeDataH :: DirectedEdgeHandle mode vertex directed undirected face -> directed directedEdgeDataH (DirectedEdgeHandle triangulation edge) = Dcel.directedEdgeData triangulation edge {-# INLINE directedEdgeDataH #-} +-- | Origin vertex of an owning directed edge. directedEdgeFrom :: DirectedEdgeHandle mode vertex directed undirected face -> VertexHandle mode vertex directed undirected face directedEdgeFrom (DirectedEdgeHandle triangulation edge) = VertexHandle triangulation (Dcel.origin triangulation edge) {-# INLINE directedEdgeFrom #-} +-- | Destination vertex of an owning directed edge. directedEdgeTo :: DirectedEdgeHandle mode vertex directed undirected face -> VertexHandle mode vertex directed undirected face directedEdgeTo (DirectedEdgeHandle triangulation edge) = VertexHandle triangulation (Dcel.destination triangulation edge) {-# INLINE directedEdgeTo #-} +-- | Origin and destination of an owning directed edge. directedEdgeVertices   :: DirectedEdgeHandle mode vertex directed undirected face   -> (VertexHandle mode vertex directed undirected face, VertexHandle mode vertex directed undirected face) directedEdgeVertices edge = (directedEdgeFrom edge, directedEdgeTo edge) +-- | Origin and destination positions of an owning directed edge. directedEdgePositions   :: DirectedEdgeHandle mode vertex directed undirected face   -> (Point, Point) directedEdgePositions edge = (vertexHandlePosition (directedEdgeFrom edge), vertexHandlePosition (directedEdgeTo edge)) {-# INLINE directedEdgePositions #-} +-- | Reverse an owning directed edge. directedEdgeReverse :: DirectedEdgeHandle mode vertex directed undirected face -> DirectedEdgeHandle mode vertex directed undirected face directedEdgeReverse (DirectedEdgeHandle triangulation edge) = DirectedEdgeHandle triangulation (reverseEdge edge) {-# INLINE directedEdgeReverse #-} +-- | Next owning edge around the incident face. directedEdgeNext :: DirectedEdgeHandle mode vertex directed undirected face -> DirectedEdgeHandle mode vertex directed undirected face directedEdgeNext (DirectedEdgeHandle triangulation edge) = DirectedEdgeHandle triangulation (Dcel.next triangulation edge) {-# INLINE directedEdgeNext #-} +-- | Previous owning edge around the incident face. directedEdgePrevious :: DirectedEdgeHandle mode vertex directed undirected face -> DirectedEdgeHandle mode vertex directed undirected face directedEdgePrevious (DirectedEdgeHandle triangulation edge) = DirectedEdgeHandle triangulation (Dcel.previous triangulation edge) {-# INLINE directedEdgePrevious #-} +-- | Previous owning edge around its origin vertex. directedEdgeClockwise :: DirectedEdgeHandle mode vertex directed undirected face -> DirectedEdgeHandle mode vertex directed undirected face directedEdgeClockwise (DirectedEdgeHandle triangulation edge) = DirectedEdgeHandle triangulation (Dcel.clockwise triangulation edge) {-# INLINE directedEdgeClockwise #-} +-- | Next owning edge around its origin vertex. directedEdgeCounterClockwise :: DirectedEdgeHandle mode vertex directed undirected face -> DirectedEdgeHandle mode vertex directed undirected face directedEdgeCounterClockwise (DirectedEdgeHandle triangulation edge) = DirectedEdgeHandle triangulation (Dcel.counterClockwise triangulation edge) {-# INLINE directedEdgeCounterClockwise #-} +-- | Owning handle to the incident face. directedEdgeFace   :: DirectedEdgeHandle mode vertex directed undirected face   -> FaceHandle PossiblyOuterTag mode vertex directed undirected face directedEdgeFace (DirectedEdgeHandle triangulation edge) = FaceHandle triangulation (FixedFaceHandle (Dcel.incidentFace triangulation edge)) +-- | Forget the orientation of an owning edge. directedEdgeAsUndirected   :: DirectedEdgeHandle mode vertex directed undirected face   -> UndirectedEdgeHandle mode vertex directed undirected face directedEdgeAsUndirected (DirectedEdgeHandle triangulation edge) = UndirectedEdgeHandle triangulation (asUndirected edge) +-- | Whether the owning edge is incident to the outer face. directedEdgeIsOuter :: DirectedEdgeHandle mode vertex directed undirected face -> Bool directedEdgeIsOuter = faceIsOuter . directedEdgeFace +-- | Exact side of the owning edge's oriented line. directedEdgeSideQuery   :: DirectedEdgeHandle mode vertex directed undirected face   -> Point@@ -263,28 +299,34 @@   let (from, to) = directedEdgePositions edge    in Math.sideQuery from to query +-- | Undirected-edge annotation through an owning handle. undirectedEdgeDataH :: UndirectedEdgeHandle mode vertex directed undirected face -> undirected undirectedEdgeDataH (UndirectedEdgeHandle triangulation edge) = Dcel.undirectedEdgeData triangulation edge {-# INLINE undirectedEdgeDataH #-} +-- | Normalized directed orientation of an owning undirected edge. undirectedEdgeAsDirected   :: UndirectedEdgeHandle mode vertex directed undirected face   -> DirectedEdgeHandle mode vertex directed undirected face undirectedEdgeAsDirected (UndirectedEdgeHandle triangulation edge) = DirectedEdgeHandle triangulation (normalizedDirected edge) +-- | Endpoints of an owning undirected edge. undirectedEdgeVertices   :: UndirectedEdgeHandle mode vertex directed undirected face   -> (VertexHandle mode vertex directed undirected face, VertexHandle mode vertex directed undirected face) undirectedEdgeVertices = directedEdgeVertices . undirectedEdgeAsDirected +-- | Face annotation through an owning handle. faceDataH :: FaceHandle tag mode vertex directed undirected face -> face faceDataH (FaceHandle triangulation (FixedFaceHandle face)) = Dcel.faceData triangulation face {-# INLINE faceDataH #-} +-- | Whether the owning face is the unique unbounded face. faceIsOuter :: FaceHandle tag mode vertex directed undirected face -> Bool faceIsOuter (FaceHandle _ (FixedFaceHandle face)) = face == Dcel.outerFace {-# INLINE faceIsOuter #-} +-- | Refine an owning face handle by excluding the outer face. faceAsInner   :: FaceHandle PossiblyOuterTag mode vertex directed undirected face   -> Maybe (FaceHandle InnerTag mode vertex directed undirected face)@@ -292,16 +334,19 @@   | faceIsOuter handle = Nothing   | otherwise = Just (FaceHandle triangulation (FixedFaceHandle face)) +-- | One owning edge on the face boundary. faceAdjacentEdge   :: FaceHandle tag mode vertex directed undirected face   -> Maybe (DirectedEdgeHandle mode vertex directed undirected face) faceAdjacentEdge (FaceHandle triangulation (FixedFaceHandle face)) = DirectedEdgeHandle triangulation <$> Dcel.adjacentEdge triangulation face +-- | Owning directed boundary of a face. faceAdjacentEdges   :: FaceHandle tag mode vertex directed undirected face   -> [DirectedEdgeHandle mode vertex directed undirected face] faceAdjacentEdges (FaceHandle triangulation (FixedFaceHandle face)) = map (DirectedEdgeHandle triangulation) (Dcel.faceDirectedEdges triangulation face) +-- | Three owning vertices of a bounded face. innerFaceVertices   :: FaceHandle InnerTag mode vertex directed undirected face   -> Maybe@@ -313,6 +358,7 @@   (\(a, b, c) -> (VertexHandle triangulation a, VertexHandle triangulation b, VertexHandle triangulation c))     <$> Dcel.innerFaceVertices triangulation face +-- | Circumcenter of an owning bounded face. innerFaceCircumcenter   :: FaceHandle InnerTag mode vertex directed undirected face   -> Maybe (Point)@@ -320,6 +366,7 @@   (a, b, c) <- innerFaceVertices face   Math.circumcenter (vertexHandlePosition a) (vertexHandlePosition b) (vertexHandlePosition c) +-- | Vertex opposite an owning directed edge in its bounded incident face. directedEdgeOppositeVertex   :: DirectedEdgeHandle mode vertex directed undirected face   -> Maybe (VertexHandle mode vertex directed undirected face)@@ -328,12 +375,14 @@   | otherwise =       Just (VertexHandle triangulation (Dcel.destination triangulation (Dcel.next triangulation edge))) +-- | Position opposite an owning directed edge in its bounded incident face. directedEdgeOppositePosition   :: DirectedEdgeHandle mode vertex directed undirected face   -> Maybe (Point) directedEdgeOppositePosition = fmap vertexHandlePosition . directedEdgeOppositeVertex {-# INLINE directedEdgeOppositePosition #-} +-- | Projection parameter of a point onto an owning directed edge's line. directedEdgeProjectionFactor   :: DirectedEdgeHandle mode vertex directed undirected face   -> Point@@ -342,6 +391,7 @@   let (from, to) = directedEdgePositions edge    in Math.projectionFactor from to query +-- | Nearest point on the closed owning directed edge. directedEdgeNearestPoint   :: DirectedEdgeHandle mode vertex directed undirected face   -> Point@@ -351,18 +401,21 @@       factor = max 0 (min 1 (Math.projectionFactor from to query))    in Point (ax + factor * (bx - ax)) (ay + factor * (by - ay)) +-- | Whether an owning undirected edge is constrained. undirectedEdgeIsConstraint   :: UndirectedEdgeHandle mode vertex directed undirected face   -> Bool undirectedEdgeIsConstraint (UndirectedEdgeHandle triangulation edge) =   Dcel.isConstraintEdge triangulation edge +-- | Whether an owning undirected edge touches the outer face. undirectedEdgeIsBoundary   :: UndirectedEdgeHandle mode vertex directed undirected face   -> Bool undirectedEdgeIsBoundary (UndirectedEdgeHandle triangulation edge) =   Dcel.isBoundaryEdge triangulation edge +-- | Positions of the three vertices of an owning bounded face. innerFacePositions   :: FaceHandle InnerTag mode vertex directed undirected face   -> Maybe (Point, Point, Point)@@ -371,6 +424,7 @@   pure (vertexHandlePosition a, vertexHandlePosition b, vertexHandlePosition c) {-# INLINE innerFacePositions #-} +-- | Barycentric coordinates in an owning bounded face. innerFaceBarycentric   :: FaceHandle InnerTag mode vertex directed undirected face   -> Point
src-dcel/Moonlight/Triangulation/Handles/Iterators/CircularIterator.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE BangPatterns #-} +-- | Bounded traversal of a cyclic successor relation. module Moonlight.Triangulation.Handles.Iterators.CircularIterator   ( circularList   , foldCircular'@@ -24,6 +25,7 @@     ) {-# INLINE circularList #-} +-- | Strictly fold a bounded cycle in visit order. foldCircular' :: Eq a => Int -> (a -> a) -> a -> (b -> a -> b) -> b -> b foldCircular' limit advance start step = go limit start False  where
src-dcel/Moonlight/Triangulation/Handles/Iterators/DynamicIterators.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE BangPatterns #-} {-# LANGUAGE DataKinds #-} +-- | Whole-mesh traversal through topology-bound dynamic handles. module Moonlight.Triangulation.Handles.Iterators.DynamicIterators   ( vertexHandles   , directedEdgeHandles@@ -31,42 +32,49 @@ vertexHandles triangulation =   mapValid (vertexHandle triangulation) (Fixed.vertices triangulation) +-- | Every directed-edge handle in fixed-index order. directedEdgeHandles   :: Triangulation mode vertex directed undirected face   -> [DirectedEdgeHandle mode vertex directed undirected face] directedEdgeHandles triangulation =   mapValid (directedEdgeHandle triangulation) (Fixed.directedEdges triangulation) +-- | Every undirected-edge handle in fixed-index order. undirectedEdgeHandles   :: Triangulation mode vertex directed undirected face   -> [UndirectedEdgeHandle mode vertex directed undirected face] undirectedEdgeHandles triangulation =   mapValid (undirectedEdgeHandle triangulation) (Fixed.undirectedEdges triangulation) +-- | Every face handle, including the outer face. allFaceHandles   :: Triangulation mode vertex directed undirected face   -> [FaceHandle PossiblyOuterTag mode vertex directed undirected face] allFaceHandles triangulation =   mapValid (faceHandle triangulation) (Fixed.allFaces triangulation) +-- | Every inner-face handle. innerFaceHandles   :: Triangulation mode vertex directed undirected face   -> [FaceHandle InnerTag mode vertex directed undirected face] innerFaceHandles triangulation =   mapValid (innerFaceHandle triangulation) (Fixed.innerFaces triangulation) +-- | Hull directed-edge handles in boundary order. hullEdgeHandles   :: Triangulation mode vertex directed undirected face   -> [DirectedEdgeHandle mode vertex directed undirected face] hullEdgeHandles triangulation =   mapValid (directedEdgeHandle triangulation) (Hull.hullEdges triangulation) +-- | Hull vertex handles in boundary order. hullVertexHandles   :: Triangulation mode vertex directed undirected face   -> [VertexHandle mode vertex directed undirected face] hullVertexHandles triangulation =   map directedEdgeFrom (hullEdgeHandles triangulation) +-- | Strict fold over every vertex handle. foldVertexHandles'   :: Triangulation mode vertex directed undirected face   -> (accumulator -> VertexHandle mode vertex directed undirected face -> accumulator)@@ -75,6 +83,7 @@ foldVertexHandles' triangulation step =   Fixed.foldVertices' triangulation (applyValid (vertexHandle triangulation) step) +-- | Strict fold over every directed-edge handle. foldDirectedEdgeHandles'   :: Triangulation mode vertex directed undirected face   -> (accumulator -> DirectedEdgeHandle mode vertex directed undirected face -> accumulator)@@ -83,6 +92,7 @@ foldDirectedEdgeHandles' triangulation step =   Fixed.foldDirectedEdges' triangulation (applyValid (directedEdgeHandle triangulation) step) +-- | Strict fold over every undirected-edge handle. foldUndirectedEdgeHandles'   :: Triangulation mode vertex directed undirected face   -> (accumulator -> UndirectedEdgeHandle mode vertex directed undirected face -> accumulator)@@ -91,6 +101,7 @@ foldUndirectedEdgeHandles' triangulation step =   Fixed.foldUndirectedEdges' triangulation (applyValid (undirectedEdgeHandle triangulation) step) +-- | Strict fold over every face handle, including the outer face. foldAllFaceHandles'   :: Triangulation mode vertex directed undirected face   -> (accumulator -> FaceHandle PossiblyOuterTag mode vertex directed undirected face -> accumulator)@@ -99,6 +110,7 @@ foldAllFaceHandles' triangulation step =   Fixed.foldAllFaces' triangulation (applyValid (faceHandle triangulation) step) +-- | Strict fold over every inner-face handle. foldInnerFaceHandles'   :: Triangulation mode vertex directed undirected face   -> (accumulator -> FaceHandle InnerTag mode vertex directed undirected face -> accumulator)@@ -107,6 +119,7 @@ foldInnerFaceHandles' triangulation step =   Fixed.foldInnerFaces' triangulation (applyValid (innerFaceHandle triangulation) step) +-- | Strict fold over hull directed-edge handles. foldHullEdgeHandles'   :: Triangulation mode vertex directed undirected face   -> (accumulator -> DirectedEdgeHandle mode vertex directed undirected face -> accumulator)@@ -115,6 +128,7 @@ foldHullEdgeHandles' triangulation step =   Hull.foldHullEdges' triangulation (applyValid (directedEdgeHandle triangulation) step) +-- | Strict fold over hull vertex handles. foldHullVertexHandles'   :: Triangulation mode vertex directed undirected face   -> (accumulator -> VertexHandle mode vertex directed undirected face -> accumulator)
src-dcel/Moonlight/Triangulation/Handles/Iterators/FixedIterators.hs view
@@ -53,18 +53,22 @@ foldVertices' triangulation step = foldRange (numVertices triangulation) (VertexId . fromIntegral) step {-# INLINE foldVertices' #-} +-- | Strict fold over every directed-edge identifier. foldDirectedEdges' :: Triangulation mode vertex directed undirected face -> (a -> DirectedEdgeId -> a) -> a -> a foldDirectedEdges' triangulation step = foldRange (numDirectedEdges triangulation) (DirectedEdgeId . fromIntegral) step {-# INLINE foldDirectedEdges' #-} +-- | Strict fold over every undirected-edge identifier. foldUndirectedEdges' :: Triangulation mode vertex directed undirected face -> (a -> UndirectedEdgeId -> a) -> a -> a foldUndirectedEdges' triangulation step = foldRange (numUndirectedEdges triangulation) (UndirectedEdgeId . fromIntegral) step {-# INLINE foldUndirectedEdges' #-} +-- | Strict fold over every face, including the outer face. foldAllFaces' :: Triangulation mode vertex directed undirected face -> (a -> FaceId -> a) -> a -> a foldAllFaces' triangulation step = foldRange (numFaces triangulation) (FaceId . fromIntegral) step {-# INLINE foldAllFaces' #-} +-- | Strict fold over every inner face. foldInnerFaces' :: Triangulation mode vertex directed undirected face -> (a -> FaceId -> a) -> a -> a foldInnerFaces' triangulation step initial = go 1 initial  where
src-dcel/Moonlight/Triangulation/Internal/Representation.hs view
@@ -256,6 +256,7 @@  where   defaults = triElementDefaults triangulation +-- | Map every undirected-edge annotation and its future-element default. mapUndirectedEdges   :: (undirected -> undirected')   -> Triangulation mode vertex directed undirected face@@ -268,6 +269,7 @@  where   defaults = triElementDefaults triangulation +-- | Map every face annotation and its future-element default. mapFaces   :: (face -> face')   -> Triangulation mode vertex directed undirected face@@ -311,6 +313,7 @@   deriving stock (Generic)   deriving anyclass (NFData) +-- | Published insertion result, selected vertex, disposition, and work receipt. data InsertionResult mode vertex directed undirected face = InsertionResult   { insertionTriangulation :: !(Triangulation mode vertex directed undirected face)   , insertionVertex :: !VertexId
src-dcel/Moonlight/Triangulation/Internal/Types.hs view
@@ -60,6 +60,7 @@   deriving stock (Eq, Ord, Show, Generic)   deriving anyclass (NFData) +-- | Recover a type-level constraint mode as a value. class KnownConstraintMode (mode :: ConstraintMode) where   constraintModeValue :: proxy mode -> ConstraintMode @@ -143,6 +144,7 @@ unitElementDefaults :: ElementDefaults () () () unitElementDefaults = ElementDefaults () () () +-- | Construction, location, legalization, and refinement work counters. data BuildStats = BuildStats   { statInputPoints :: {-# UNPACK #-} !Int   , statUniquePoints :: {-# UNPACK #-} !Int@@ -170,6 +172,7 @@   deriving stock (Eq, Show, Read, Generic)   deriving anyclass (NFData) +-- | The additive identity for construction telemetry. emptyBuildStats :: BuildStats emptyBuildStats =   BuildStats@@ -197,6 +200,7 @@     , statSweepSkippedPoints = 0     } +-- | Whether an insertion published a new site or selected an existing one. data InsertionDisposition = Inserted | AlreadyPresent   deriving stock (Eq, Ord, Show, Generic)   deriving anyclass (NFData)@@ -218,6 +222,7 @@   deriving stock (Eq, Ord, Show, Generic)   deriving anyclass (NFData) +-- | Classify NaN and signed infinity, leaving finite values unclassified. classifyNonFinite :: Double -> Maybe NonFiniteValue classifyNonFinite value   | isNaN value = Just ValueNaN@@ -300,6 +305,7 @@   deriving stock (Eq, Show, Generic)   deriving anyclass (NFData) +-- | Exact position of a query relative to the finite triangulation. data Location   = EmptyTriangulation   | OnVertex !VertexId@@ -316,6 +322,7 @@   deriving stock (Eq, Ord, Show, Generic)   deriving anyclass (NFData) +-- | Work performed by point-location descent. data LocationStats = LocationStats   { locationWalkSteps :: {-# UNPACK #-} !Int   , locationUsedFallback :: !Bool@@ -323,6 +330,7 @@   deriving stock (Eq, Ord, Show, Generic)   deriving anyclass (NFData) +-- | Location telemetry for a query that required no descent. emptyLocationStats :: LocationStats emptyLocationStats = LocationStats 0 False 
src-dcel/Moonlight/Triangulation/Interop.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE FlexibleInstances #-} +-- | Boundary conversions between Moonlight points and common coordinate pairs. module Moonlight.Triangulation.Interop   ( Coordinate2 (..)   , mapCoordinate2@@ -27,6 +28,7 @@   toPoint (x :+ y) = Point x y   fromPoint (Point x y) = x :+ y +-- | Transform a coordinate through the canonical 'Point' representation. mapCoordinate2   :: (Coordinate2 input, Coordinate2 output)   => (Point -> Point)
src-dcel/Moonlight/Triangulation/IntersectionIterator.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE BangPatterns #-} +-- | Ordered traversal of the mesh features met by a line segment. module Moonlight.Triangulation.IntersectionIterator   ( Intersection (..)   , lineIntersections
src-dcel/Moonlight/Triangulation/Math.hs view
@@ -3,6 +3,7 @@ {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DerivingStrategies #-} +-- | Robust planar predicates and derived Euclidean constructions. module Moonlight.Triangulation.Math   ( orient2d   , sideQuery@@ -76,9 +77,11 @@   deriving stock (Bounded, Enum, Eq, Ord, Show, Generic)   deriving anyclass (NFData) +-- | Every segment relation in constructor order. allSegmentRelations :: [SegmentRelation] allSegmentRelations = [minBound .. maxBound] +-- | Exact relation between two closed segments. segmentRelation   :: Point   -> Point@@ -114,6 +117,7 @@   opposite left right =     (left == LT && right == GT) || (left == GT && right == LT) +-- | Whether two closed segments share any point. segmentsIntersect   :: Point   -> Point@@ -140,6 +144,7 @@   opposite GT LT = True   opposite _ _ = False +-- | Classify a coordinate outside the exact-predicate input domain. validateCoordinate :: Double -> Maybe CoordinateError validateCoordinate value   | isNaN value = Just CoordinateNaN@@ -155,6 +160,7 @@   maybe (Right ()) (Left . InvalidPointY) (validateCoordinate y)   Right (QueryPoint (canonicalPoint point)) +-- | Validate a construction point while retaining its optional input slot. validatePoint :: Maybe Int -> Point -> Either BuildError (QueryPoint) validatePoint slot point@(Point x y) =   case mkQueryPoint point of@@ -173,6 +179,7 @@     | value /= 0 && abs value < minimumAllowedCoordinate = 0     | otherwise = value +-- | Canonicalize both coordinate components for point identity. canonicalPoint :: Point -> Point canonicalPoint (Point x y) = Point (canonicalCoordinate x) (canonicalCoordinate y) {-# INLINE canonicalPoint #-}@@ -190,24 +197,29 @@ -- them per call. @value - value == 0@ is the same predicate in pure Double -- arithmetic: finite values subtract to zero, while NaN and the infinities -- subtract to NaN, which never compares equal.+-- | Whether a scalar is neither infinite nor NaN. isFinite :: Double -> Bool isFinite value = value - value == 0 {-# INLINE isFinite #-} +-- | Fast approximate signed orientation determinant. orientDetApprox :: Point -> Point -> Point -> Double orientDetApprox (Point ax ay) (Point bx by) (Point cx cy) =   (ax - cx) * (by - cy) - (ay - cy) * (bx - cx) {-# INLINE orientDetApprox #-} +-- | Exact orientation ordering of three points. orient2d :: Point -> Point -> Point -> Ordering orient2d (Point ax ay) (Point bx by) (Point cx cy) =   orient2dCoordinates ax ay bx by cx cy {-# INLINE orient2d #-} +-- | Exact side of an oriented line. sideQuery :: Point -> Point -> Point -> LineSideInfo sideQuery a b point = fromOrdering (orient2d a b point) {-# INLINE sideQuery #-} +-- | Fast approximate oriented in-circle determinant. inCircleDetApprox   :: Point -> Point -> Point -> Point -> Double inCircleDetApprox@@ -243,6 +255,7 @@     inCircleCoordinates ax ay bx by cx cy dx dy {-# INLINE inCircle #-} +-- | Whether a point lies on a closed segment. onClosedSegment :: Point -> Point -> Point -> Bool onClosedSegment a@(Point ax ay) b@(Point bx by) query@(Point qx qy) =   orient2d a b query == EQ@@ -252,6 +265,7 @@     && qy <= max ay by {-# INLINE onClosedSegment #-} +-- | Squared Euclidean distance. squaredDistance :: Point -> Point -> Double squaredDistance (Point ax ay) (Point bx by) =   let !dx = ax - bx@@ -264,6 +278,7 @@ squaredDistanceWide = squaredDistance {-# INLINE squaredDistanceWide #-} +-- | Squared distance from a point to a closed segment. segmentDistanceSquared   :: Point -> Point -> Point -> Double segmentDistanceSquared from@(Point ax ay) to@(Point bx by) point@(Point px py)@@ -285,23 +300,28 @@ segmentDistanceSquaredWide = segmentDistanceSquared {-# INLINE segmentDistanceSquaredWide #-} +-- | Euclidean distance. distance :: Point -> Point -> Double distance left right = sqrt (squaredDistance left right) {-# INLINE distance #-} +-- | Midpoint of two points. midpoint :: Point -> Point -> Point midpoint (Point ax ay) (Point bx by) = Point (0.5 * ax + 0.5 * bx) (0.5 * ay + 0.5 * by) {-# INLINE midpoint #-} +-- | Centroid of three points. centroid :: Point -> Point -> Point -> Point centroid (Point ax ay) (Point bx by) (Point cx cy) =   Point (ax + (bx - ax) / 3 + (cx - ax) / 3) (ay + (by - ay) / 3 + (cy - ay) / 3) {-# INLINE centroid #-} +-- | Unsigned area of a triangle. triangleArea :: Point -> Point -> Point -> Double triangleArea a b c = 0.5 * abs (orientDetApprox a b c) {-# INLINE triangleArea #-} +-- | Circumradius divided by shortest edge length. triangleRadiusEdgeRatio   :: Point -> Point -> Point -> Maybe Double triangleRadiusEdgeRatio p0 p1 p2@@ -357,6 +377,7 @@ -- 1e183, five orders below the Double ceiling; the four divisions it cost -- are the circumcentre's hot-path price. Identical points answer through the -- denominator, which is exactly zero exactly when they are collinear.+-- | Circumcenter of a nondegenerate triangle. circumcenter   :: Point -> Point -> Point -> Maybe (Point) circumcenter (Point ax ay) (Point bx by) (Point cx cy)@@ -376,6 +397,7 @@   !resultX = ax + offsetX   !resultY = ay + offsetY +-- | Barycentric coordinates of a point in a nondegenerate triangle. barycentricCoordinates   :: Point -> Point -> Point -> Point   -> Maybe (Double, Double, Double)@@ -428,6 +450,7 @@   !dotSum = abs left + abs right {-# INLINE inDiametralCircle #-} +-- | Projection parameter of a point onto an oriented segment line. projectionFactor :: Point -> Point -> Point -> Double projectionFactor (Point ax ay) (Point bx by) (Point qx qy)   | lengthSquared == 0 = 0
src-dcel/Moonlight/Triangulation/PointLocation.hs view
@@ -15,9 +15,12 @@ import Moonlight.Triangulation.Math import Moonlight.Triangulation.Types +-- | Locate an admitted point without an initial topology hint. locatePoint :: Triangulation mode vertex directed undirected face -> QueryPoint -> Location locatePoint triangulation query = fst (locatePointWithHint triangulation Nothing query) +-- | Locate an admitted point from an optional starting cell and report the+-- descent work. locatePointWithHint :: Triangulation mode vertex directed undirected face -> Maybe LocationHint -> QueryPoint -> (Location, LocationStats) locatePointWithHint triangulation hint queryPoint   | numVertices triangulation == 0 = (EmptyTriangulation, emptyLocationStats)@@ -124,4 +127,3 @@             [] -> FaceId 1       | otherwise -> FaceId 1     _ -> FaceId 1-
src-dual/Moonlight/Triangulation/HintGenerator.hs view
@@ -6,6 +6,7 @@ {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE NamedFieldPuns #-} +-- | Reusable point-location hints and their topology-preserving maintenance. module Moonlight.Triangulation.HintGenerator   ( LastUsedHint   , emptyLastUsedHint@@ -39,16 +40,20 @@ import Moonlight.Triangulation.Types import GHC.Generics (Generic) +-- | Most recently admitted vertex, suitable as the next descent seed. newtype LastUsedHint = LastUsedHint (Maybe VertexId)   deriving stock (Show)   deriving newtype (Eq, Ord) +-- | A last-used hint with no remembered vertex. emptyLastUsedHint :: LastUsedHint emptyLastUsedHint = LastUsedHint Nothing +-- | Project a remembered vertex into a location hint. lastUsedHint :: LastUsedHint -> Maybe LocationHint lastUsedHint (LastUsedHint vertex) = VertexHint <$> vertex +-- | Replace the remembered vertex. rememberVertex :: VertexId -> LastUsedHint -> LastUsedHint rememberVertex vertex _ = LastUsedHint (Just vertex) @@ -70,7 +75,9 @@ -- base this hierarchy describes, and is rebuilt for rather than patched. data HierarchyHint = HierarchyHint   { hierarchyBranchFactor :: {-# UNPACK #-} !Int+    -- ^ Sampling stride between adjacent hierarchy levels.   , hierarchyBaseCount :: {-# UNPACK #-} !Int+    -- ^ Cardinality of the base mesh described by the hierarchy.   , hierarchyLevels :: !(V.Vector (Triangulation 'Unconstrained (Point) () () ()))   }   deriving stock (Generic)@@ -127,12 +134,15 @@             to = vertexPoint triangulation toVertex       ] +-- | Default sampling stride between hierarchy levels. defaultHierarchyBranchFactor :: Int defaultHierarchyBranchFactor = 16 +-- | Number of stored sparse levels. hierarchyLevelCount :: HierarchyHint -> Int hierarchyLevelCount = V.length . hierarchyLevels +-- | Total vertices retained across every sparse level. hierarchyVertexCount :: HierarchyHint -> Int hierarchyVertexCount =   V.foldl' (\total level -> total + numVertices level) 0 . hierarchyLevels
src-dual/Moonlight/Triangulation/Internal/InterpolationWorkspace.hs view
@@ -51,6 +51,7 @@   , nnWeightValue :: !(MUV.MVector state Double)   } +-- | Allocate reusable interpolation storage sized to one triangulation. newNaturalNeighborWorkspace   :: PrimMonad m   => Triangulation mode vertex directed undirected face@@ -124,6 +125,7 @@       pure 1     else writeMutVar reference next >> pure next +-- | Bytes owned by the reusable numeric and index planes. workspaceBytes   :: NaturalNeighborWorkspace state mode vertex directed undirected face   -> Integer
src-dual/Moonlight/Triangulation/Interpolation.hs view
@@ -89,10 +89,14 @@   deriving stock (Eq, Show, Generic)   deriving anyclass (NFData) +-- | Work performed by one natural-neighbor query. data InterpolationStats = InterpolationStats   { interpolationCavityFaces :: {-# UNPACK #-} !Int+    -- ^ Faces in the query insertion cavity.   , interpolationNaturalNeighbors :: {-# UNPACK #-} !Int+    -- ^ Sites contributing nonzero Sibson weight.   , interpolationFaceTests :: {-# UNPACK #-} !Int+    -- ^ Faces tested while discovering the cavity.     -- | 'True' exactly when the Sibson pipeline declined and the returned     -- weights are the barycentric coordinates of the located face instead.     -- A silent degradation from Sibson to barycentric is a defect that hides;@@ -102,10 +106,14 @@   deriving stock (Eq, Ord, Show, Generic)   deriving anyclass (NFData) +-- | Weights and receipts produced by one natural-neighbor query. data NaturalNeighborResult = NaturalNeighborResult   { naturalNeighborValues :: !(V.Vector (VertexId, Double))+    -- ^ Nonzero weights keyed by source vertex.   , naturalNeighborLocationStats :: !LocationStats+    -- ^ Work performed while locating the query.   , naturalNeighborStats :: !InterpolationStats+    -- ^ Work performed while constructing the Sibson coordinates.   }   deriving stock (Eq, Show, Generic)   deriving anyclass (NFData)@@ -192,6 +200,7 @@           then (candidate, candidateDistance, tests + 1)           else (best, bestDistance, tests + 1) +-- | Barycentric weights at a validated query, plus point-location work. barycentricWeights   :: Triangulation mode vertex directed undirected face   -> Maybe LocationHint@@ -706,6 +715,7 @@     TwoWeights a wa b wb -> Just (wa * sample a + wb * sample b)     ThreeWeights a wa b wb c wc -> Just (wa * sample a + wb * sample b + wc * sample c) +-- | Interpolate from reusable Sibson weights. interpolateNaturalNeighbor   :: (VertexId -> Double)   -> NaturalNeighborWorkspace s mode vertex directed undirected face@@ -722,6 +732,7 @@       query   pure (if interpolationNaturalNeighbors stats == 0 then Nothing else Just total, stats) +-- | Estimate one vertex gradient from its Delaunay neighbors. estimateGradient   :: (VertexId -> Double)   -> Triangulation mode vertex directed undirected face@@ -771,6 +782,7 @@           then (sumX + normalX, sumY + normalY, sumZ + normalZ)           else (sumX, sumY, sumZ) +-- | Estimate gradients for every vertex in handle order. estimateGradients   :: (VertexId -> Double)   -> Triangulation mode vertex directed undirected face@@ -779,6 +791,7 @@   V.generate (numVertices triangulation) $ \index ->     estimateGradient sample triangulation (VertexId (fromIntegral index)) +-- | Natural-neighbor interpolation with nodal-gradient correction. interpolateNaturalNeighborGradient   :: (VertexId -> Double)   -> (VertexId -> (Double, Double))
src-dual/Moonlight/Triangulation/Voronoi.hs view
@@ -38,65 +38,82 @@ import Moonlight.Triangulation.Math import Moonlight.Triangulation.Types +-- | A Voronoi face, identified by its primal site.+-- -- The dual uses the exact same fixed-index space as the primal DCEL. These -- newtypes add semantic separation without allocating or owning any dual mesh. newtype VoronoiFaceId = VoronoiFaceId { unVoronoiFaceId :: VertexId }   deriving stock (Show)   deriving newtype (Eq, Ord, NFData) +-- | An oriented Voronoi edge, identified by its primal directed edge. newtype DirectedVoronoiEdgeId = DirectedVoronoiEdgeId { unDirectedVoronoiEdgeId :: DirectedEdgeId }   deriving stock (Show)   deriving newtype (Eq, Ord, NFData) +-- | An unoriented Voronoi edge, identified by its primal undirected edge. newtype UndirectedVoronoiEdgeId = UndirectedVoronoiEdgeId { unUndirectedVoronoiEdgeId :: UndirectedEdgeId }   deriving stock (Show)   deriving newtype (Eq, Ord, NFData) +-- | A finite dual vertex or an ideal endpoint of an unbounded dual edge. data VoronoiVertexId   = InnerVoronoiVertex !FaceId   | OuterVoronoiVertex !DirectedVoronoiEdgeId   deriving stock (Eq, Ord, Show) +-- | Finite segment, half-infinite ray, or infinite line in the dual. data VoronoiEdgeGeometry   = VoronoiSegment !(Point) !(Point)   | VoronoiRay !(Point) !(Point)   | VoronoiLine !(Point) !(Point)   deriving stock (Eq, Ord, Show) +-- | Voronoi faces in primal vertex-handle order. voronoiFaces :: Triangulation mode vertex directed undirected face -> [VoronoiFaceId] voronoiFaces triangulation = map VoronoiFaceId (vertices triangulation) {-# INLINE voronoiFaces #-} +-- | Directed Voronoi edges in primal directed-edge order.+-- -- Every primal directed edge is one directed edge in the dual. Boundary -- handles naturally represent half-infinite edges through OuterVoronoiVertex. directedVoronoiEdges :: Triangulation mode vertex directed undirected face -> [DirectedVoronoiEdgeId] directedVoronoiEdges triangulation = map DirectedVoronoiEdgeId (directedEdges triangulation) {-# INLINE directedVoronoiEdges #-} +-- | Undirected Voronoi edges in primal undirected-edge order. undirectedVoronoiEdges :: Triangulation mode vertex directed undirected face -> [UndirectedVoronoiEdgeId] undirectedVoronoiEdges triangulation = map UndirectedVoronoiEdgeId (undirectedEdges triangulation) {-# INLINE undirectedVoronoiEdges #-} +-- | Recover the primal directed edge. asDelaunayDirectedEdge :: DirectedVoronoiEdgeId -> DirectedEdgeId asDelaunayDirectedEdge = unDirectedVoronoiEdgeId {-# INLINE asDelaunayDirectedEdge #-} +-- | Recover the primal undirected edge. asDelaunayUndirectedEdge :: UndirectedVoronoiEdgeId -> UndirectedEdgeId asDelaunayUndirectedEdge = unUndirectedVoronoiEdgeId {-# INLINE asDelaunayUndirectedEdge #-} +-- | View a primal directed edge in the dual handle family. asDirectedVoronoiEdge :: DirectedEdgeId -> DirectedVoronoiEdgeId asDirectedVoronoiEdge = DirectedVoronoiEdgeId {-# INLINE asDirectedVoronoiEdge #-} +-- | View a primal undirected edge in the dual handle family. asUndirectedVoronoiEdge :: UndirectedEdgeId -> UndirectedVoronoiEdgeId asUndirectedVoronoiEdge = UndirectedVoronoiEdgeId {-# INLINE asUndirectedVoronoiEdge #-} +-- | Reverse a directed Voronoi edge. reverseVoronoiEdge :: DirectedVoronoiEdgeId -> DirectedVoronoiEdgeId reverseVoronoiEdge (DirectedVoronoiEdgeId edge) = DirectedVoronoiEdgeId (reverseEdge edge) {-# INLINE reverseVoronoiEdge #-} +-- | Next dual edge around the incident Voronoi face.+-- -- Dual next/previous rotate around the primal origin site. voronoiNext   :: Triangulation mode vertex directed undirected face@@ -106,6 +123,7 @@   DirectedVoronoiEdgeId (counterClockwise triangulation edge) {-# INLINE voronoiNext #-} +-- | Previous dual edge around the incident Voronoi face. voronoiPrevious   :: Triangulation mode vertex directed undirected face   -> DirectedVoronoiEdgeId@@ -114,6 +132,7 @@   DirectedVoronoiEdgeId (clockwise triangulation edge) {-# INLINE voronoiPrevious #-} +-- | Origin dual vertex, including ideal endpoints on the hull. voronoiFrom   :: Triangulation mode vertex directed undirected face   -> DirectedVoronoiEdgeId@@ -125,6 +144,7 @@   face = incidentFace triangulation primal {-# INLINE voronoiFrom #-} +-- | Destination dual vertex, including ideal endpoints on the hull. voronoiTo   :: Triangulation mode vertex directed undirected face   -> DirectedVoronoiEdgeId@@ -132,6 +152,7 @@ voronoiTo triangulation = voronoiFrom triangulation . reverseVoronoiEdge {-# INLINE voronoiTo #-} +-- | Voronoi face to the left of a directed dual edge. voronoiIncidentFace   :: Triangulation mode vertex directed undirected face   -> DirectedVoronoiEdgeId@@ -140,10 +161,12 @@   VoronoiFaceId (origin triangulation edge) {-# INLINE voronoiIncidentFace #-} +-- | Primal site represented by a Voronoi face. voronoiFaceSite :: VoronoiFaceId -> VertexId voronoiFaceSite = unVoronoiFaceId {-# INLINE voronoiFaceSite #-} +-- | Directed boundary of a Voronoi face. voronoiFaceAdjacentEdges   :: Triangulation mode vertex directed undirected face   -> VoronoiFaceId@@ -152,6 +175,8 @@   map DirectedVoronoiEdgeId (vertexOutgoingEdges triangulation site) {-# INLINE voronoiFaceAdjacentEdges #-} +-- | Position of a finite dual vertex; ideal hull endpoints have no position.+-- -- A caller reaches this through 'voronoiFrom' or 'voronoiTo', which build the -- endpoint sum immediately before it is taken apart again. Only an unfolding at -- the consumer lets the two meet, so the constructor never reaches the heap.@@ -164,6 +189,7 @@   OuterVoronoiVertex _ -> Nothing {-# INLINE voronoiVertexPosition #-} +-- | Directed dual edges leaving a finite dual vertex. voronoiVertexOutgoingEdges   :: Triangulation mode vertex directed undirected face   -> VoronoiVertexId@@ -178,6 +204,7 @@     | otherwise = Just face {-# INLINE voronoiVertexOutgoingEdges #-} +-- | Unit direction of the dual edge orthogonal to a primal edge. voronoiDirectionVector   :: Triangulation mode vertex directed undirected face   -> DirectedVoronoiEdgeId@@ -187,6 +214,8 @@     Point ax ay -> case vertexPoint triangulation (destination triangulation edge) of       Point bx by -> Point (ay - by) (bx - ax) +-- | Circumcenter of a bounded primal face.+-- -- The circumcentre stands on the absolute vertex positions, which is a -- different value from the query-relative one the Sibson pipeline caches: that -- one rescales the differences it was handed, so translating the inputs moves@@ -216,6 +245,8 @@                   (vertexPoint triangulation (origin triangulation e2)) {-# INLINE faceCircumcenter #-} +-- | Geometric realization of one dual edge.+-- -- The endpoint classification 'voronoiFrom' and 'voronoiTo' publish is two face -- reads and two comparisons; taken through those observations it is also two -- sum values built and immediately scrutinized. The geometry reads the faces
src-dual/Moonlight/Triangulation/Voronoi/Handles.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE BangPatterns #-} {-# LANGUAGE DataKinds #-} +-- | Zero-copy Voronoi views that retain the owning triangulation. module Moonlight.Triangulation.Voronoi.Handles   ( VoronoiFaceHandle   , DirectedVoronoiEdgeHandle@@ -46,12 +47,15 @@ newtype VoronoiFaceHandle mode vertex directed undirected face =   VoronoiFaceHandle (VertexHandle mode vertex directed undirected face) +-- | An owning handle to a directed dual edge. newtype DirectedVoronoiEdgeHandle mode vertex directed undirected face =   DirectedVoronoiEdgeHandle (DirectedEdgeHandle mode vertex directed undirected face) +-- | An owning handle to an undirected dual edge. newtype UndirectedVoronoiEdgeHandle mode vertex directed undirected face =   UndirectedVoronoiEdgeHandle (UndirectedEdgeHandle mode vertex directed undirected face) +-- | An owning handle to a finite or ideal dual vertex. data VoronoiVertexHandle mode vertex directed undirected face   = InnerVoronoiVertexHandle       !(FaceHandle InnerTag mode vertex directed undirected face)@@ -70,6 +74,7 @@ instance Show (VoronoiVertexHandle mode vertex directed undirected face) where   showsPrec precedence = showsPrec precedence . fixVoronoiVertex +-- | Admit a fixed Voronoi face identifier into a triangulation. voronoiFaceHandle   :: Triangulation mode vertex directed undirected face   -> VoronoiFaceId@@ -77,11 +82,13 @@ voronoiFaceHandle triangulation (VoronoiFaceId site) =   VoronoiFaceHandle <$> vertexHandle triangulation site +-- | View an admitted primal vertex as its Voronoi face. vertexAsVoronoiFaceH   :: VertexHandle mode vertex directed undirected face   -> VoronoiFaceHandle mode vertex directed undirected face vertexAsVoronoiFaceH = VoronoiFaceHandle +-- | Admit a fixed directed Voronoi edge identifier into a triangulation. directedVoronoiEdgeHandle   :: Triangulation mode vertex directed undirected face   -> DirectedVoronoiEdgeId@@ -89,11 +96,13 @@ directedVoronoiEdgeHandle triangulation (DirectedVoronoiEdgeId edge) =   DirectedVoronoiEdgeHandle <$> directedEdgeHandle triangulation edge +-- | View an admitted primal directed edge in the dual handle family. directedEdgeAsVoronoiH   :: DirectedEdgeHandle mode vertex directed undirected face   -> DirectedVoronoiEdgeHandle mode vertex directed undirected face directedEdgeAsVoronoiH = DirectedVoronoiEdgeHandle +-- | Admit a fixed undirected Voronoi edge identifier into a triangulation. undirectedVoronoiEdgeHandle   :: Triangulation mode vertex directed undirected face   -> UndirectedVoronoiEdgeId@@ -101,20 +110,24 @@ undirectedVoronoiEdgeHandle triangulation (UndirectedVoronoiEdgeId edge) =   UndirectedVoronoiEdgeHandle <$> undirectedEdgeHandle triangulation edge +-- | View an admitted primal undirected edge in the dual handle family. undirectedEdgeAsVoronoiH   :: UndirectedEdgeHandle mode vertex directed undirected face   -> UndirectedVoronoiEdgeHandle mode vertex directed undirected face undirectedEdgeAsVoronoiH = UndirectedVoronoiEdgeHandle +-- | View an admitted bounded primal face as a finite dual vertex. innerFaceAsVoronoiVertexH   :: FaceHandle InnerTag mode vertex directed undirected face   -> VoronoiVertexHandle mode vertex directed undirected face innerFaceAsVoronoiVertexH = InnerVoronoiVertexHandle +-- | Forget ownership and recover a fixed Voronoi face identifier. fixVoronoiFace :: VoronoiFaceHandle mode vertex directed undirected face -> VoronoiFaceId fixVoronoiFace (VoronoiFaceHandle site) = VoronoiFaceId (fixVertex site) {-# INLINE fixVoronoiFace #-} +-- | Forget ownership and recover a fixed directed dual edge identifier. fixDirectedVoronoiEdge   :: DirectedVoronoiEdgeHandle mode vertex directed undirected face   -> DirectedVoronoiEdgeId@@ -122,6 +135,7 @@   DirectedVoronoiEdgeId (fixDirectedEdge edge) {-# INLINE fixDirectedVoronoiEdge #-} +-- | Forget ownership and recover a fixed undirected dual edge identifier. fixUndirectedVoronoiEdge   :: UndirectedVoronoiEdgeHandle mode vertex directed undirected face   -> UndirectedVoronoiEdgeId@@ -129,6 +143,7 @@   UndirectedVoronoiEdgeId (fixUndirectedEdge edge) {-# INLINE fixUndirectedVoronoiEdge #-} +-- | Forget ownership and recover a fixed dual vertex identifier. fixVoronoiVertex   :: VoronoiVertexHandle mode vertex directed undirected face   -> VoronoiVertexId@@ -138,24 +153,28 @@   OuterVoronoiVertex (fixDirectedVoronoiEdge edge) {-# INLINE fixVoronoiVertex #-} +-- | Reverse an admitted directed dual edge. voronoiEdgeReverseH   :: DirectedVoronoiEdgeHandle mode vertex directed undirected face   -> DirectedVoronoiEdgeHandle mode vertex directed undirected face voronoiEdgeReverseH (DirectedVoronoiEdgeHandle edge) =   DirectedVoronoiEdgeHandle (directedEdgeReverse edge) +-- | Next admitted dual edge around its Voronoi face. voronoiEdgeNextH   :: DirectedVoronoiEdgeHandle mode vertex directed undirected face   -> DirectedVoronoiEdgeHandle mode vertex directed undirected face voronoiEdgeNextH (DirectedVoronoiEdgeHandle edge) =   DirectedVoronoiEdgeHandle (directedEdgeCounterClockwise edge) +-- | Previous admitted dual edge around its Voronoi face. voronoiEdgePreviousH   :: DirectedVoronoiEdgeHandle mode vertex directed undirected face   -> DirectedVoronoiEdgeHandle mode vertex directed undirected face voronoiEdgePreviousH (DirectedVoronoiEdgeHandle edge) =   DirectedVoronoiEdgeHandle (directedEdgeClockwise edge) +-- | Origin finite or ideal vertex of an admitted dual edge. voronoiEdgeFromH   :: DirectedVoronoiEdgeHandle mode vertex directed undirected face   -> VoronoiVertexHandle mode vertex directed undirected face@@ -164,45 +183,53 @@     Just inner -> InnerVoronoiVertexHandle inner     Nothing -> OuterVoronoiVertexHandle edge +-- | Destination finite or ideal vertex of an admitted dual edge. voronoiEdgeToH   :: DirectedVoronoiEdgeHandle mode vertex directed undirected face   -> VoronoiVertexHandle mode vertex directed undirected face voronoiEdgeToH = voronoiEdgeFromH . voronoiEdgeReverseH +-- | Voronoi face to the left of an admitted directed dual edge. voronoiEdgeFaceH   :: DirectedVoronoiEdgeHandle mode vertex directed undirected face   -> VoronoiFaceHandle mode vertex directed undirected face voronoiEdgeFaceH (DirectedVoronoiEdgeHandle edge) =   VoronoiFaceHandle (directedEdgeFrom edge) +-- | Forget orientation while retaining ownership. voronoiEdgeAsUndirectedH   :: DirectedVoronoiEdgeHandle mode vertex directed undirected face   -> UndirectedVoronoiEdgeHandle mode vertex directed undirected face voronoiEdgeAsUndirectedH (DirectedVoronoiEdgeHandle edge) =   UndirectedVoronoiEdgeHandle (directedEdgeAsUndirected edge) +-- | Recover the owning primal directed-edge handle. voronoiEdgeAsDelaunayH   :: DirectedVoronoiEdgeHandle mode vertex directed undirected face   -> DirectedEdgeHandle mode vertex directed undirected face voronoiEdgeAsDelaunayH (DirectedVoronoiEdgeHandle edge) = edge +-- | Recover the owning primal site handle. voronoiFaceSiteH   :: VoronoiFaceHandle mode vertex directed undirected face   -> VertexHandle mode vertex directed undirected face voronoiFaceSiteH (VoronoiFaceHandle site) = site +-- | Admitted directed boundary of a Voronoi face. voronoiFaceAdjacentEdgesH   :: VoronoiFaceHandle mode vertex directed undirected face   -> [DirectedVoronoiEdgeHandle mode vertex directed undirected face] voronoiFaceAdjacentEdgesH (VoronoiFaceHandle site) =   map DirectedVoronoiEdgeHandle (vertexHandleOutEdges site) +-- | Position of a finite admitted dual vertex. voronoiVertexPositionH   :: VoronoiVertexHandle mode vertex directed undirected face   -> Maybe (Point) voronoiVertexPositionH (InnerVoronoiVertexHandle face) = innerFaceCircumcenter face voronoiVertexPositionH (OuterVoronoiVertexHandle _) = Nothing +-- | Directed dual edges leaving a finite admitted dual vertex. voronoiVertexOutEdgesH   :: VoronoiVertexHandle mode vertex directed undirected face   -> Maybe [DirectedVoronoiEdgeHandle mode vertex directed undirected face]@@ -210,18 +237,21 @@   Just (map DirectedVoronoiEdgeHandle (faceAdjacentEdges face)) voronoiVertexOutEdgesH (OuterVoronoiVertexHandle _) = Nothing +-- | Recover the primal bounded face underlying a finite dual vertex. voronoiVertexAsDelaunayFaceH   :: VoronoiVertexHandle mode vertex directed undirected face   -> Maybe (FaceHandle InnerTag mode vertex directed undirected face) voronoiVertexAsDelaunayFaceH (InnerVoronoiVertexHandle face) = Just face voronoiVertexAsDelaunayFaceH (OuterVoronoiVertexHandle _) = Nothing +-- | Recover the primal hull edge underlying an ideal dual vertex. voronoiVertexAsOuterEdgeH   :: VoronoiVertexHandle mode vertex directed undirected face   -> Maybe (DirectedVoronoiEdgeHandle mode vertex directed undirected face) voronoiVertexAsOuterEdgeH (InnerVoronoiVertexHandle _) = Nothing voronoiVertexAsOuterEdgeH (OuterVoronoiVertexHandle edge) = Just edge +-- | Unit direction of the dual edge orthogonal to the primal edge. voronoiEdgeDirectionH   :: DirectedVoronoiEdgeHandle mode vertex directed undirected face   -> Point@@ -229,6 +259,7 @@   let (Point ax ay, Point bx by) = directedEdgePositions primal    in Point (ay - by) (bx - ax) +-- | Geometric realization of an admitted dual edge. voronoiEdgeGeometryH   :: DirectedVoronoiEdgeHandle mode vertex directed undirected face   -> Maybe (VoronoiEdgeGeometry)@@ -265,4 +296,3 @@ -- reaches its arithmetic through 'innerFaceCircumcenter', whose own worker -- publishes no unfolding, so it stops at that call until the dcel layer says -- otherwise.-
src-public/Moonlight/Triangulation.hs view
@@ -1,4 +1,20 @@--- | The equational surface: a triangulation is a value of its site set.+-- | The public surface for Delaunay triangulations as values of finite+-- coordinate supports. Its exports are grouped by object, generation,+-- annotations, finite-set algebra, constraints, refinement, observations and+-- validation.+--+-- Geometry-only set laws are observed after erasing vertex annotations with+-- @mapVertices (const ())@ and applying 'canonicalize'. Successful construction+-- may retain schedule-dependent resident numbering, so structural 'Eq' compares+-- stored representations rather than silently normalizing them. Annotations may+-- change independently through 'mapVertices' and 'setVertexData'; they never+-- determine geometry.+--+-- The algebra is deliberately partial. Finite arena exhaustion is returned as+-- 'BuildError', while unrealizable constrained compositions return+-- 'ConstrainedUnionError'. These typed obstructions are why the surface exposes+-- explicit operations rather than total 'Semigroup' instances. Refinement then+-- composes over any admitted result without introducing a second mesh type. module Moonlight.Triangulation   ( -- * The object — a triangulation is a value of its site set     Triangulation