moonlight-triangulation-1.4.0.1: src-build/Moonlight/Triangulation/Internal/Join/Seam.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- | Linear seam construction for separated Delaunay triangulations. Admission
-- returns an opaque proof carrying the exact source order and tangents; the
-- executor therefore has no untyped precondition and owns no fallback.
module Moonlight.Triangulation.Internal.Join.Seam
( SeamPlan
, planSeam
, executeSeam
, ConstrainedSeamExecution
, seamExecutionTriangulation
, seamExecutionBuildStats
, seamExecutionPublicationStats
, seamExecutionCachedFrontierPointReads
, seamExecutionLeftFaceCount
, seamExecutionRightFaceEvidence
, seamExecutionJoinFaces
, executeConstrainedSeam
) where
import Control.Applicative ((<|>))
import Control.Monad.ST (ST, runST)
import Data.Bits (finiteBitSize, xor)
import Data.Foldable (traverse_)
import qualified Data.IntSet as IntSet
import qualified Data.List as List
import qualified Data.Sequence as Seq
import qualified Data.Vector as V
import Moonlight.Triangulation.Dcel
( adjacentEdge
, faceDirectedEdges
, faceVertices
, incidentFace
, isConstraintEdge
, numDirectedEdges
, numFaces
, numInnerFaces
, numVertices
, origin
, previous
, vertexOutEdge
, vertexData
)
import Moonlight.Triangulation.FloodFillIterator (floodFillFaces)
import Moonlight.Triangulation.Handles.HandleDefs
( DirectedEdgeId (..)
, FaceId (..)
, UndirectedEdgeId (..)
, VertexId (..)
, asUndirected
, directedPair
)
import Moonlight.Triangulation.Internal.DcelOperations.Hull (closeOuterTurn)
import Moonlight.Triangulation.Internal.DcelOperations.Legalize
( legalizeEdges
, legalizeEdgesPinned
)
import Moonlight.Triangulation.Internal.DcelOperations.Subdivide
( splitBoundaryEdgeWithLegalization
)
import Moonlight.Triangulation.Internal.DcelOperations.FlipRule (illegalDiagonal)
import Moonlight.Triangulation.Internal.Cdt.Combinators (bindMutable)
import Moonlight.Triangulation.Internal.Cdt.Query (constraintEdges)
import Moonlight.Triangulation.Internal.Mutable
import Moonlight.Triangulation.Internal.OperationState
( Counter (CounterSteinerPoints)
, OperationState
, addCounter
, freezeBuildStats
, newOperationState
)
import Moonlight.Triangulation.Internal.Paged
( PublicationStats
, TransactionShape (LocalTransaction)
, pagedUnsafeIndex
)
import Moonlight.Triangulation.Internal.Representation
( SeamFrontierIndex (..)
, Triangulation (..)
, seamQuarterTurn
, summarizeSeamChart
)
import Moonlight.Triangulation.Internal.Cdt.Types
( ConstrainedSeamFaceEvidence (..)
, ConstrainedSeamSide (..)
)
import Moonlight.Triangulation.Internal.Types
( BuildError (..)
, BuildStats
, ConstraintMode (Constrained, Unconstrained)
, InvariantViolation (..)
, Point (..)
, QueryPoint (queryPointValue)
, RefinementParameters (..)
, unitElementDefaults
)
import Moonlight.Triangulation.Scalar (inCircleCoordinates, orient2dCoordinates)
import Moonlight.Triangulation.Math (distance, midpoint, validatePoint)
import Moonlight.Triangulation.Internal.Transaction (runTransactionWithPublication)
-- | Proof that a particular pair can be copied and stitched by the seam
-- kernel. Constructors stay private so an unseparated pair cannot be handed to
-- the executor by accident.
data SeamPlan
= SeamLeftBeforeRight !SeamTangents
| SeamRightBeforeLeft !SeamTangents
-- | Coordinate chart used by the separated seam. The second chart is the
-- orientation-preserving quarter turn @(u,v) = (y,-x)@; exact predicates and
-- the zipper therefore remain unchanged while north/south admission reuses
-- the same tangent interpreter.
data SeamAxis
= SeamAxisX
| SeamAxisY
deriving stock (Eq, Show)
planSeam
:: Triangulation mode vertex () () ()
-> Triangulation mode' vertex () () ()
-> Maybe SeamPlan
planSeam left right = do
leftFrontier <- triSeamFrontier left
rightFrontier <- triSeamFrontier right
planAxis SeamAxisX leftFrontier rightFrontier
<|> planAxis SeamAxisY leftFrontier rightFrontier
where
planAxis axis leftFrontier rightFrontier =
case separatedOrder axis left right leftFrontier rightFrontier of
Just LeftBeforeRight ->
SeamLeftBeforeRight
<$> seamTangents axis left leftFrontier right rightFrontier
Just RightBeforeLeft ->
SeamRightBeforeLeft
<$> seamTangents axis right rightFrontier left leftFrontier
Nothing -> Nothing
-- | Execute a proved seam schedule. Numbering follows the schedule;
-- 'Moonlight.Triangulation.Dcel.canonicalize' remains the explicit
-- construction-independent observation.
executeSeam
:: forall vertex
. SeamPlan
-> Triangulation 'Unconstrained vertex () () ()
-> Triangulation 'Unconstrained vertex () () ()
-> Either BuildError (Triangulation 'Unconstrained vertex () () ())
executeSeam plan left right =
case plan of
SeamLeftBeforeRight tangents ->
fmap fst (mergeSeparated left right tangents)
SeamRightBeforeLeft tangents ->
fmap fst (mergeSeparated right left tangents)
-- | Result of the constrained seam kernel. Constraint flags are copied before
-- legalization, so source contour edges are immutable barriers while the
-- zipper constructs only the missing corridor.
data ConstrainedSeamExecution vertex = ConstrainedSeamExecution
{ seamExecutionTriangulation
:: !(Triangulation 'Constrained vertex () () ())
, seamExecutionBuildStats :: !BuildStats
, seamExecutionPublicationStats :: !PublicationStats
, seamExecutionCachedFrontierPointReads :: {-# UNPACK #-} !Int
, seamExecutionLeftFaceCount :: {-# UNPACK #-} !Int
, seamExecutionRightFaceEvidence :: !(V.Vector ConstrainedSeamFaceEvidence)
, seamExecutionJoinFaces :: !(V.Vector FaceId)
}
-- | Execute a proved seam while transporting both source constraint planes.
-- This is distinct from promoting the unconstrained result afterward: source
-- hull constraints must already be visible to seam legalization or the
-- legalization schedule could erase a solved source face before recovery had
-- a chance to mark it.
executeConstrainedSeam
:: forall vertex
. (ConstrainedSeamSide -> FaceId -> Bool)
-> RefinementParameters
-> vertex
-> SeamPlan
-> Triangulation 'Constrained vertex () () ()
-> Triangulation 'Constrained vertex () () ()
-> Either BuildError (ConstrainedSeamExecution vertex)
executeConstrainedSeam sourceFacePreserved parameters vertexDefault plan left right = do
let (order, tangents) =
case plan of
SeamLeftBeforeRight selectedTangents ->
(GeometricBaseLeft, selectedTangents)
SeamRightBeforeLeft selectedTangents ->
(GeometricBaseRight, selectedTangents)
(triangulation, statistics, publicationStats, joinFaces) <-
executeResidentConstrained
targetFaceProtected
parameters
vertexDefault
order
left
right
tangents
rightEvidence <-
seamFaceEvidenceForSource
right
triangulation
incomingFaceOffset
rightEvidenceFaces
pure
ConstrainedSeamExecution
{ seamExecutionTriangulation = triangulation
, seamExecutionBuildStats = statistics
, seamExecutionPublicationStats = publicationStats
, seamExecutionCachedFrontierPointReads =
seamFrontierPointReads tangents
+ case refineMaxEdgeLength parameters of
Nothing -> 0
Just _ -> 4
, seamExecutionLeftFaceCount = numInnerFaces left
, seamExecutionRightFaceEvidence = rightEvidence
, seamExecutionJoinFaces = joinFaces
}
where
!residentFaceLimit = numFaces left
!incomingFaceOffset = residentFaceLimit - 1
!incomingFaceLimit = incomingFaceOffset + numFaces right
targetFaceProtected rawFace
| rawFace <= 0 = False
| rawFace < residentFaceLimit =
sourceFacePreserved SeamResident (FaceId (fromIntegral rawFace))
| rawFace < incomingFaceLimit =
sourceFacePreserved
SeamIncoming
(FaceId (fromIntegral (rawFace - incomingFaceOffset)))
| otherwise = False
rightEvidenceFaces =
filter
(sourceFacePreserved SeamIncoming)
(fmap (FaceId . fromIntegral) [1 .. numFaces right - 1])
-- | Descend exactly the unprotected face component touched by the zipper.
-- This is the authoritative J section: it contains both newly allocated seam
-- faces and any source exterior filler lawfully retriangulated into them.
exactJoinFaces
:: Triangulation 'Constrained vertex () () ()
-> (Int -> Bool)
-> V.Vector FaceId
-> V.Vector FaceId
exactJoinFaces triangulation targetFaceProtected seeds =
V.fromList
( floodFillFaces
triangulation
(V.toList seeds)
canCross
)
where
canCross edge =
not (isConstraintEdge triangulation edge)
&& not
( any
targetFaceIsProtected
(incidentInnerFaces triangulation edge)
)
targetFaceIsProtected (FaceId raw) =
targetFaceProtected (fromIntegral raw)
-- | Certify only the J/selected-face overlap after local descent. A selected
-- source face is immutable; therefore an unconstrained overlap edge that still
-- requires a Delaunay flip is a genuine incompatibility, never permission to
-- rewrite the solved section.
certifyJoinBoundary
:: Triangulation 'Constrained vertex () () ()
-> (Int -> Bool)
-> V.Vector FaceId
-> Either BuildError ()
certifyJoinBoundary triangulation targetFaceProtected joinFaces =
traverse_ certifyPair (IntSet.toAscList candidatePairs)
where
joinFaceSet =
IntSet.fromList
[ fromIntegral raw
| FaceId raw <- V.toList joinFaces
]
candidatePairs =
List.foldl'
(\pairs faceHandle ->
List.foldl'
(\acc edge ->
let UndirectedEdgeId raw = asUndirected edge
in IntSet.insert (fromIntegral raw) acc
)
pairs
(faceDirectedEdges triangulation faceHandle)
)
IntSet.empty
(V.toList joinFaces)
certifyPair rawPair =
let edge = UndirectedEdgeId (fromIntegral rawPair)
in if not (selectedJoinOverlap edge)
|| isConstraintEdge triangulation edge
|| not (immutableEdgeRequiresFlip triangulation edge)
then Right ()
else Left (SeamSourceEdgeRequiresFlip edge)
selectedJoinOverlap edge =
case directedPair edge of
(forward, backward) ->
let forwardFace = incidentFace triangulation forward
backwardFace = incidentFace triangulation backward
in (joinFace forwardFace && protectedFace backwardFace)
|| (joinFace backwardFace && protectedFace forwardFace)
joinFace (FaceId raw) = IntSet.member (fromIntegral raw) joinFaceSet
protectedFace (FaceId raw) = targetFaceProtected (fromIntegral raw)
incidentInnerFaces
:: Triangulation mode vertex directed undirected face
-> UndirectedEdgeId
-> [FaceId]
incidentInnerFaces triangulation edge =
filter (/= FaceId 0)
[ incidentFace triangulation forward
, incidentFace triangulation backward
]
where
(forward, backward) = directedPair edge
immutableEdgeRequiresFlip
:: Triangulation mode vertex directed undirected face
-> UndirectedEdgeId
-> Bool
immutableEdgeRequiresFlip triangulation edge =
leftFace /= FaceId 0
&& rightFace /= FaceId 0
&& orient2dCoordinates cx cy dx dy bx by == GT
&& orient2dCoordinates dx dy cx cy ax ay == GT
&& illegalDiagonal ax ay bx by cx cy dx dy
where
(forward, backward) = directedPair edge
leftFace = incidentFace triangulation forward
rightFace = incidentFace triangulation backward
forwardPrevious = previous triangulation forward
backwardPrevious = previous triangulation backward
Point ax ay = vertexPointAt triangulation (origin triangulation forward)
Point bx by = vertexPointAt triangulation (origin triangulation backward)
Point cx cy = vertexPointAt triangulation (origin triangulation forwardPrevious)
Point dx dy = vertexPointAt triangulation (origin triangulation backwardPrevious)
vertexPointAt
:: Triangulation mode vertex directed undirected face
-> VertexId
-> Point
vertexPointAt triangulation (VertexId raw) =
Point
(triPointX triangulation `pagedUnsafeIndex` fromIntegral raw)
(triPointY triangulation `pagedUnsafeIndex` fromIntegral raw)
data GeometricBaseOrder
= GeometricBaseLeft
| GeometricBaseRight
deriving stock (Eq, Show)
-- | Finite local work required to turn the two synthetic perimeter bridges
-- into edge-bounded constrained paths. The witness is derived while the
-- tangent endpoints still identify J exactly; after publication those edges
-- are ordinary constraints and must not be rediscovered by a global scan.
data SeamBridgeSubdivision = SeamBridgeSubdivision
{ seamLowerBridgeDepth :: {-# UNPACK #-} !Int
, seamUpperBridgeDepth :: {-# UNPACK #-} !Int
, seamBridgeAddedVertices :: {-# UNPACK #-} !Int
}
planSeamBridgeSubdivision
:: RefinementParameters
-> Point
-> Point
-> Point
-> Point
-> Either BuildError SeamBridgeSubdivision
planSeamBridgeSubdivision parameters lowerLeft lowerRight upperLeft upperRight =
case refineMaxEdgeLength parameters of
Nothing -> Right (SeamBridgeSubdivision 0 0 0)
Just maximumLength -> do
let !lowerDepth = requiredBridgeSubdivisionDepth maximumLength lowerLeft lowerRight
!upperDepth = requiredBridgeSubdivisionDepth maximumLength upperLeft upperRight
!required =
subdivisionVertexCount lowerDepth
+ subdivisionVertexCount upperDepth
if required > toInteger (maxBound :: Int)
then Left (CapacityExceeded maxBound)
else do
let !requiredVertices = fromInteger required
available <-
case (requiredVertices, refineMaxAdditionalVertices parameters) of
(0, Nothing) -> Right 0
(_, Nothing) -> Left RefinementDomainRequiresFiniteVertexBudget
(_, Just budget) -> Right budget
if requiredVertices <= available
then
Right
SeamBridgeSubdivision
{ seamLowerBridgeDepth = lowerDepth
, seamUpperBridgeDepth = upperDepth
, seamBridgeAddedVertices = requiredVertices
}
else
Left
( RefinementSeamBridgeBudgetExceeded
requiredVertices
available
)
where
subdivisionVertexCount :: Int -> Integer
subdivisionVertexCount depth = (2 :: Integer) ^ depth - 1
requiredBridgeSubdivisionDepth :: Double -> Point -> Point -> Int
requiredBridgeSubdivisionDepth maximumLength from to =
descend 0 (distance from to)
where
descend !depth !currentLength
| currentLength <= maximumLength = depth
| depth >= finiteBitSize (0 :: Int) = depth
| otherwise = descend (depth + 1) (0.5 * currentLength)
vertexPointForOuterEdge
:: Triangulation mode vertex directed undirected face
-> Int
-> Point
vertexPointForOuterEdge triangulation edge =
vertexPointAt
triangulation
(VertexId (fromIntegral (topologyAt triangulation (4 * edge))))
subdivideSeamBridge
:: forall s vertex directed undirected face
. (Int -> Bool)
-> vertex
-> Int
-> MutableDcel s vertex directed undirected face
-> OperationState s
-> Int
-> ST s (Either BuildError (Seq.Seq Int))
subdivideSeamBridge targetFaceProtected vertexDefault = descend
where
descend
:: Int
-> MutableDcel s vertex directed undirected face
-> OperationState s
-> Int
-> ST s (Either BuildError (Seq.Seq Int))
descend depth mutable operation outerEdge
| depth <= 0 = pure (Right (Seq.singleton outerEdge))
| otherwise = do
fromVertex <- readOrigin mutable outerEdge
toVertex <- readOrigin mutable (outerEdge `xor` 1)
fromPoint <- pointAt mutable fromVertex
toPoint <- pointAt mutable toVertex
case validatePoint Nothing (midpoint fromPoint toPoint) of
Left obstruction -> pure (Left obstruction)
Right admitted ->
let !splitPoint = queryPointValue admitted
in if splitPoint == fromPoint || splitPoint == toPoint
then
pure
( Left
( RefinementSeamBridgeMidpointCollapsed
(UndirectedEdgeId (fromIntegral (outerEdge `quot` 2)))
)
)
else do
vertex <- appendVertex mutable splitPoint vertexDefault
splitBoundaryEdgeWithLegalization
(\target targetOperation _ firstCandidate secondCandidate ->
legalizeEdgesPinned
targetFaceProtected
target
targetOperation
[firstCandidate, secondCandidate]
)
mutable
operation
outerEdge
vertex
`bindMutable` \(firstOuter, secondOuter) -> do
addCounter operation CounterSteinerPoints 1
descend (depth - 1) mutable operation firstOuter
`bindMutable` \firstChain ->
fmap (fmap (firstChain Seq.><))
(descend (depth - 1) mutable operation secondOuter)
executeResidentConstrained
:: forall vertex
. (Int -> Bool)
-> RefinementParameters
-> vertex
-> GeometricBaseOrder
-> Triangulation 'Constrained vertex () () ()
-> Triangulation 'Constrained vertex () () ()
-> SeamTangents
-> Either BuildError
( Triangulation 'Constrained vertex () () ()
, BuildStats
, PublicationStats
, V.Vector FaceId
)
executeResidentConstrained targetFaceProtected parameters vertexDefault order base extension tangents = do
let !baseDirected = numDirectedEdges base
!extensionDirected = numDirectedEdges extension
!baseVertices = numVertices base
(leftSource, rightSource, leftVertexOffset, rightVertexOffset, leftEdgeOffset, rightEdgeOffset) =
case order of
GeometricBaseLeft ->
(base, extension, 0, baseVertices, 0, baseDirected)
GeometricBaseRight ->
(extension, base, baseVertices, 0, baseDirected, 0)
SeamTangents
{ seamLowerLeft = SeamTangent _ lowerLeft
, seamLowerRight = SeamTangent _ lowerRight
, seamUpperLeft = SeamTangent _ upperLeft
, seamUpperRight = SeamTangent _ upperRight
} = tangents
leftFrontier <- maybe (Left SeamFrontierUnavailable) Right (triSeamFrontier leftSource)
rightFrontier <- maybe (Left SeamFrontierUnavailable) Right (triSeamFrontier rightSource)
bridgePlan <-
planSeamBridgeSubdivision
parameters
(vertexPointForOuterEdge leftSource lowerLeft)
(vertexPointForOuterEdge rightSource lowerRight)
(vertexPointForOuterEdge leftSource upperLeft)
(vertexPointForOuterEdge rightSource upperRight)
let !extensionVertices = numVertices extension
!bridgeVertices = seamBridgeAddedVertices bridgePlan
transactionAdditional <-
if bridgeVertices > maxBound - extensionVertices
then Left (CapacityExceeded maxBound)
else Right (extensionVertices + bridgeVertices)
((newFaces, progress, upperBridgeChain, lowerBridgeChain), triangulation, statistics, publicationStats) <-
runTransactionWithPublication
id
LocalTransaction
base
transactionAdditional
(\mutable operation -> do
appendSourceVertices mutable extension
_ <- addEdgeBlock mutable (extensionDirected `quot` 2)
_ <- addFaceBlock mutable (numFaces extension - 1)
copySource
mutable
extension
baseVertices
baseDirected
(numFaces base - 1)
copySourceConstraints mutable baseDirected extension
seamBase <-
spliceLowerTangent
mutable
rightEdgeOffset
(leftEdgeOffset + lowerLeft)
lowerRight
-- The two synthetic hull bridges are the certified perimeter of the
-- published world. Once a later extension consumes either bridge it
-- becomes Γ, so fixing it now preserves the prior solve without a
-- retrospective source rewrite.
_ <- setConstraint mutable seamBase
stitchSeamPinned
targetFaceProtected
mutable
operation
(Seq.length (seamFrontierEdges leftFrontier))
(seamTangentIndex (seamLowerLeft tangents))
(Seq.length (seamFrontierEdges rightFrontier))
(seamTangentIndex (seamLowerRight tangents))
seamBase
(leftVertexOffset + topologyAt leftSource (4 * upperLeft))
(rightVertexOffset + topologyAt rightSource (4 * upperRight))
[]
[]
`bindMutable` \(upperBridge, newFaces, progress) -> do
_ <- setConstraint mutable upperBridge
subdivideSeamBridge
targetFaceProtected
vertexDefault
(seamLowerBridgeDepth bridgePlan)
mutable
operation
(seamBase `xor` 1)
`bindMutable` \lowerBridgeChain ->
subdivideSeamBridge
targetFaceProtected
vertexDefault
(seamUpperBridgeDepth bridgePlan)
mutable
operation
upperBridge
`bindMutable` \upperBridgeChain ->
case Seq.lookup 0 upperBridgeChain of
Nothing -> pure (Left SeamFrontierUnavailable)
Just frontierStart -> do
writeFaceEdge mutable 0 frontierStart
pure
( Right
(newFaces, progress, upperBridgeChain, lowerBridgeChain)
)
)
let newFaceSeeds = V.fromList (fmap (FaceId . fromIntegral) newFaces)
joinFaces = exactJoinFaces triangulation targetFaceProtected newFaceSeeds
certifyJoinBoundary triangulation targetFaceProtected joinFaces
frontier <-
combineSeamFrontier
triangulation
leftFrontier
rightFrontier
leftEdgeOffset
rightEdgeOffset
upperBridgeChain
lowerBridgeChain
progress
let published :: Triangulation 'Constrained vertex () () ()
published = triangulation{triSeamFrontier = Just frontier}
pure
( published
, statistics
, publicationStats
, joinFaces
)
seamFaceEvidenceForSource
:: Triangulation 'Constrained vertex () () ()
-> Triangulation 'Constrained vertex () () ()
-> Int
-> [FaceId]
-> Either BuildError (V.Vector ConstrainedSeamFaceEvidence)
seamFaceEvidenceForSource source target targetOffset sourceFaces =
V.fromList <$> traverse evidenceFor sourceFaces
where
evidenceFor sourceFace = do
sourcePoints <- facePoints source sourceFace
let targetFace = FaceId (unFaceId sourceFace + fromIntegral targetOffset)
targetPoints <- facePoints target targetFace
if sourcePoints == targetPoints
then
case sourcePoints of
[first, second, third] ->
Right
ConstrainedSeamFaceEvidence
{ constrainedSeamSourceFace = sourceFace
, constrainedSeamTargetFace = targetFace
, constrainedSeamFaceFirstPoint = first
, constrainedSeamFaceSecondPoint = second
, constrainedSeamFaceThirdPoint = third
}
points -> Left (faceCardinalityFailure source sourceFace points)
else Left (SeamProtectedSourceFaceChanged sourceFace)
facePoints triangulation faceHandle =
case List.sort (fmap (vertexPointAt triangulation) (faceVertices triangulation faceHandle)) of
[first, second, third] -> Right [first, second, third]
points -> Left (faceCardinalityFailure triangulation faceHandle points)
faceCardinalityFailure triangulation faceHandle _points =
case faceDirectedEdges triangulation faceHandle of
edge : _ -> RefinementInputTopologyInvalid (InnerFaceNotTriangularAtEdge edge)
[] -> RefinementInputTopologyInvalid (FaceMissingAdjacentEdge faceHandle)
data SeparatedOrder
= LeftBeforeRight
| RightBeforeLeft
separatedOrder
:: SeamAxis
-> Triangulation mode vertex directed undirected face
-> Triangulation mode' vertex' directed' undirected' face'
-> SeamFrontierIndex
-> SeamFrontierIndex
-> Maybe SeparatedOrder
separatedOrder axis left right leftFrontier rightFrontier
| numInnerFaces left <= 0 || numInnerFaces right <= 0 = Nothing
| chartMaximum axis leftFrontier < chartMinimum axis rightFrontier =
Just LeftBeforeRight
| chartMaximum axis rightFrontier < chartMinimum axis leftFrontier =
Just RightBeforeLeft
| otherwise = Nothing
chartMinimum :: SeamAxis -> SeamFrontierIndex -> Double
chartMinimum axis frontier =
case axis of
SeamAxisX -> seamFrontierXMinimum frontier
SeamAxisY -> seamFrontierYMinimum frontier
chartMaximum :: SeamAxis -> SeamFrontierIndex -> Double
chartMaximum axis frontier =
case axis of
SeamAxisX -> seamFrontierXMaximum frontier
SeamAxisY -> seamFrontierYMaximum frontier
-- The opposite-sign branch cannot overflow in its sum. The same-sign branch
-- cannot overflow in its difference.
data SeamTangent = SeamTangent
{ seamTangentIndex :: {-# UNPACK #-} !Int
, seamTangentEdge :: {-# UNPACK #-} !Int
}
data SeamTangents = SeamTangents
{ seamLowerLeft :: !SeamTangent
, seamLowerRight :: !SeamTangent
, seamUpperLeft :: !SeamTangent
, seamUpperRight :: !SeamTangent
, seamFrontierPointReads :: {-# UNPACK #-} !Int
}
seamTangents
:: SeamAxis
-> Triangulation mode vertex directed undirected face
-> SeamFrontierIndex
-> Triangulation mode' vertex' directed' undirected' face'
-> SeamFrontierIndex
-> Maybe SeamTangents
seamTangents axis left leftFrontier right rightFrontier = do
(lowerLeft, lowerRight, lowerReads) <- lowerTangent axis left leftFrontier right rightFrontier
(upperLeft, upperRight, upperReads) <- upperTangent axis left leftFrontier right rightFrontier
pure
SeamTangents
{ seamLowerLeft = lowerLeft
, seamLowerRight = lowerRight
, seamUpperLeft = upperLeft
, seamUpperRight = upperRight
, seamFrontierPointReads = lowerReads + upperReads
}
lowerTangent
:: SeamAxis
-> Triangulation mode vertex directed undirected face
-> SeamFrontierIndex
-> Triangulation mode' vertex' directed' undirected' face'
-> SeamFrontierIndex
-> Maybe (SeamTangent, SeamTangent, Int)
-- The walk carries both endpoints' coordinates: a step replaces exactly one
-- endpoint, and the replacement is the neighbour whose coordinates the step's
-- own test already read.
lowerTangent axis left leftFrontier right rightFrontier = do
(leftStart, leftReads) <- frontierPointAtCount axis left leftFrontier leftStartIndex
(rightStart, rightReads) <- frontierPointAtCount axis right rightFrontier rightStartIndex
walk leftStartIndex leftStart rightStartIndex rightStart (leftReads + rightReads)
where
leftStartIndex :: Int
rightStartIndex :: Int
leftSize :: Int
rightSize :: Int
walk
:: Int
-> (Double, Double)
-> Int
-> (Double, Double)
-> Int
-> Maybe (SeamTangent, SeamTangent, Int)
leftStartIndex = chartLowerRightmost axis leftFrontier
rightStartIndex = chartLowerLeftmost axis rightFrontier
leftSize = Seq.length (seamFrontierEdges leftFrontier)
rightSize = Seq.length (seamFrontierEdges rightFrontier)
walk !leftIndex (!leftX, !leftY) !rightIndex (!rightX, !rightY) !reads = do
nextLeftIndex <- pure (nextIndex leftSize leftIndex)
previousRightIndex <- pure (previousIndex rightSize rightIndex)
(nextLeftX, nextLeftY) <- frontierPointAt axis left leftFrontier nextLeftIndex
(previousRightX, previousRightY) <- frontierPointAt axis right rightFrontier previousRightIndex
let !nextReads = reads + 2
!leftBelow =
orient2dCoordinates leftX leftY rightX rightY nextLeftX nextLeftY == LT
!rightBelow =
orient2dCoordinates leftX leftY rightX rightY previousRightX previousRightY == LT
if leftBelow
then walk nextLeftIndex (nextLeftX, nextLeftY) rightIndex (rightX, rightY) nextReads
else
if rightBelow
then walk leftIndex (leftX, leftY) previousRightIndex (previousRightX, previousRightY) nextReads
else do
leftEdge <- frontierEdgeAt leftFrontier leftIndex
rightEdge <- frontierEdgeAt rightFrontier rightIndex
pure
( SeamTangent leftIndex leftEdge
, SeamTangent rightIndex rightEdge
, nextReads
)
upperTangent
:: SeamAxis
-> Triangulation mode vertex directed undirected face
-> SeamFrontierIndex
-> Triangulation mode' vertex' directed' undirected' face'
-> SeamFrontierIndex
-> Maybe (SeamTangent, SeamTangent, Int)
upperTangent axis left leftFrontier right rightFrontier = do
(leftStart, leftReads) <- frontierPointAtCount axis left leftFrontier leftStartIndex
(rightStart, rightReads) <- frontierPointAtCount axis right rightFrontier rightStartIndex
walk leftStartIndex leftStart rightStartIndex rightStart (leftReads + rightReads)
where
leftStartIndex :: Int
rightStartIndex :: Int
leftSize :: Int
rightSize :: Int
walk
:: Int
-> (Double, Double)
-> Int
-> (Double, Double)
-> Int
-> Maybe (SeamTangent, SeamTangent, Int)
leftStartIndex = chartUpperRightmost axis leftFrontier
rightStartIndex = chartUpperLeftmost axis rightFrontier
leftSize = Seq.length (seamFrontierEdges leftFrontier)
rightSize = Seq.length (seamFrontierEdges rightFrontier)
walk !leftIndex (!leftX, !leftY) !rightIndex (!rightX, !rightY) !reads = do
previousLeftIndex <- pure (previousIndex leftSize leftIndex)
nextRightIndex <- pure (nextIndex rightSize rightIndex)
(previousLeftX, previousLeftY) <- frontierPointAt axis left leftFrontier previousLeftIndex
(nextRightX, nextRightY) <- frontierPointAt axis right rightFrontier nextRightIndex
let !nextReads = reads + 2
!leftAbove =
orient2dCoordinates leftX leftY rightX rightY previousLeftX previousLeftY == GT
!rightAbove =
orient2dCoordinates leftX leftY rightX rightY nextRightX nextRightY == GT
if leftAbove
then walk previousLeftIndex (previousLeftX, previousLeftY) rightIndex (rightX, rightY) nextReads
else
if rightAbove
then walk leftIndex (leftX, leftY) nextRightIndex (nextRightX, nextRightY) nextReads
else do
leftEdge <- frontierEdgeAt leftFrontier leftIndex
rightEdge <- frontierEdgeAt rightFrontier rightIndex
pure
( SeamTangent leftIndex leftEdge
, SeamTangent rightIndex rightEdge
, nextReads
)
frontierEdgeAt :: SeamFrontierIndex -> Int -> Maybe Int
frontierEdgeAt frontier index = Seq.lookup index (seamFrontierEdges frontier)
chartLowerRightmost :: SeamAxis -> SeamFrontierIndex -> Int
chartLowerRightmost axis frontier =
case axis of
SeamAxisX -> seamFrontierLowerRightmost frontier
SeamAxisY -> seamFrontierYLowerRightmost frontier
chartLowerLeftmost :: SeamAxis -> SeamFrontierIndex -> Int
chartLowerLeftmost axis frontier =
case axis of
SeamAxisX -> seamFrontierLowerLeftmost frontier
SeamAxisY -> seamFrontierYLowerLeftmost frontier
chartUpperRightmost :: SeamAxis -> SeamFrontierIndex -> Int
chartUpperRightmost axis frontier =
case axis of
SeamAxisX -> seamFrontierUpperRightmost frontier
SeamAxisY -> seamFrontierYUpperRightmost frontier
chartUpperLeftmost :: SeamAxis -> SeamFrontierIndex -> Int
chartUpperLeftmost axis frontier =
case axis of
SeamAxisX -> seamFrontierUpperLeftmost frontier
SeamAxisY -> seamFrontierYUpperLeftmost frontier
frontierPointAt
:: SeamAxis
-> Triangulation mode vertex directed undirected face
-> SeamFrontierIndex
-> Int
-> Maybe (Double, Double)
frontierPointAt axis triangulation frontier index = do
edge <- frontierEdgeAt frontier index
let !vertex = topologyAt triangulation (4 * edge)
pure (chartPoint axis (physicalPoint triangulation vertex))
{-# INLINE frontierPointAt #-}
chartPoint :: SeamAxis -> (Double, Double) -> (Double, Double)
chartPoint axis (x, y) =
case axis of
SeamAxisX -> (x, y)
SeamAxisY -> seamQuarterTurn (x, y)
physicalPoint
:: Triangulation mode vertex directed undirected face
-> Int
-> (Double, Double)
physicalPoint triangulation vertex =
( triPointX triangulation `pagedUnsafeIndex` vertex
, triPointY triangulation `pagedUnsafeIndex` vertex
)
frontierPointAtCount
:: SeamAxis
-> Triangulation mode vertex directed undirected face
-> SeamFrontierIndex
-> Int
-> Maybe ((Double, Double), Int)
frontierPointAtCount axis triangulation frontier index = do
point <- frontierPointAt axis triangulation frontier index
pure (point, 1)
nextIndex :: Int -> Int -> Int
nextIndex size index
| index + 1 == size = 0
| otherwise = index + 1
{-# INLINE nextIndex #-}
previousIndex :: Int -> Int -> Int
previousIndex size index
| index == 0 = size - 1
| otherwise = index - 1
{-# INLINE previousIndex #-}
mergeSeparated
:: forall outputMode leftMode rightMode vertex
. Triangulation leftMode vertex () () ()
-> Triangulation rightMode vertex () () ()
-> SeamTangents
-> Either
BuildError
(Triangulation outputMode vertex () () (), BuildStats)
mergeSeparated
left
right
SeamTangents
{ seamLowerLeft = SeamTangent _ lowerLeft
, seamLowerRight = SeamTangent _ lowerRight
, seamUpperLeft = SeamTangent _ upperLeft
, seamUpperRight = SeamTangent _ upperRight
} = runST $ do
mutable <- newMutableDcel unitElementDefaults (planarDcelCapacity totalVertices)
pointCapacityOutcome <- ensurePointCapacity mutable totalVertices
cellCapacityOutcome <-
ensureCellCapacity
mutable
((leftDirected + rightDirected) `quot` 2 + 1)
(leftFaces + rightFaces - 2)
case (pointCapacityOutcome, cellCapacityOutcome) of
(Left obstruction, _) -> pure (Left obstruction)
(_, Left obstruction) -> pure (Left obstruction)
(Right (), Right ()) -> do
appendSourceVertices mutable left
appendSourceVertices mutable right
_ <- addEdgeBlock mutable ((leftDirected + rightDirected) `quot` 2)
_ <- addFaceBlock mutable (leftFaces + rightFaces - 2)
copySource mutable left 0 0 0
copySource mutable right leftVertices leftDirected (leftFaces - 1)
base <- spliceLowerTangent mutable leftDirected lowerLeft lowerRight
operation <- newOperationState (halfEdgeCapacity mutable)
stitched <-
stitchSeam
mutable
operation
base
(topologyAt left (4 * upperLeft))
(leftVertices + topologyAt right (4 * upperRight))
[]
[]
case stitched of
Left obstruction -> pure (Left obstruction)
Right _ -> do
statistics <- freezeBuildStats operation
fmap (\triangulation -> (triangulation, statistics))
<$> freezeTriangulation mutable
where
!leftVertices = numVertices left
!rightVertices = numVertices right
!totalVertices = leftVertices + rightVertices
!leftDirected = numDirectedEdges left
!rightDirected = numDirectedEdges right
!leftFaces = numFaces left
!rightFaces = numFaces right
stitchSeam
:: MutableDcel s vertex directed undirected face
-> OperationState s
-> Int
-> Int
-> Int
-> [Int]
-> [Int]
-> ST s (Either BuildError (Int, [Int]))
stitchSeam mutable operation base upperLeft upperRight seeds newFaces =
fmap (fmap (\(finalBase, faces, _) -> (finalBase, faces)))
( stitchSeamWith
(\target ops edges -> legalizeEdges target ops edges >> pure (Right ()))
(SeamProgress 0 0 0 0 0 0)
mutable
operation
base
upperLeft
upperRight
seeds
newFaces
)
stitchSeamPinned
:: (Int -> Bool)
-> MutableDcel s vertex directed undirected face
-> OperationState s
-> Int
-> Int
-> Int
-> Int
-> Int
-> Int
-> Int
-> [Int]
-> [Int]
-> ST s (Either BuildError (Int, [Int], SeamProgress))
stitchSeamPinned targetFaceProtected mutable operation leftSize leftStart rightSize rightStart base upperLeft upperRight seeds newFaces =
stitchSeamWith
(\target ops edges -> legalizeEdgesPinned targetFaceProtected target ops edges >> pure (Right ()))
(SeamProgress leftSize leftStart (previousIndex leftSize leftStart) rightSize rightStart rightStart)
mutable
operation
base
upperLeft
upperRight
seeds
newFaces
data SeamProgress = SeamProgress
{ seamProgressLeftSize :: {-# UNPACK #-} !Int
, seamProgressLeftStart :: {-# UNPACK #-} !Int
, seamProgressLeftCursor :: {-# UNPACK #-} !Int
, seamProgressRightSize :: {-# UNPACK #-} !Int
, seamProgressRightStart :: {-# UNPACK #-} !Int
, seamProgressRightCursor :: {-# UNPACK #-} !Int
}
stitchSeamWith
:: (MutableDcel s vertex directed undirected face -> OperationState s -> [Int] -> ST s (Either BuildError ()))
-> SeamProgress
-> MutableDcel s vertex directed undirected face
-> OperationState s
-> Int
-> Int
-> Int
-> [Int]
-> [Int]
-> ST s (Either BuildError (Int, [Int], SeamProgress))
stitchSeamWith legalize progress mutable operation base upperLeft upperRight seeds newFaces = do
leftVertex <- readOrigin mutable base
rightVertex <- readOrigin mutable (base `xor` 1)
if leftVertex == upperLeft && rightVertex == upperRight
then do
legalized <- legalize mutable operation seeds
pure (fmap (const (base, newFaces, progress)) legalized)
else do
leftEdge <- readPrevious mutable base
rightEdge <- readNext mutable base
nextLeft <- readOrigin mutable leftEdge
nextRight <- readOrigin mutable (rightEdge `xor` 1)
leftTurn <- vertexOrientation mutable nextLeft leftVertex rightVertex
rightTurn <- vertexOrientation mutable leftVertex rightVertex nextRight
chooseRight <-
if rightVertex == upperRight
then pure False
else
if leftVertex == upperLeft
then pure True
else
case (leftTurn == GT, rightTurn == GT) of
(True, True) ->
(== GT) <$> vertexInCircle mutable nextLeft leftVertex rightVertex nextRight
(False, True) -> pure True
_ -> pure False
if chooseRight
then do
closed <- closeOuterTurn mutable base
case closed of
Left obstruction -> pure (Left obstruction)
Right nextBase ->
do
face <- readFace mutable (nextBase `xor` 1)
stitchSeamWith
legalize
(advanceRight progress)
mutable
operation
nextBase
upperLeft
upperRight
(base : rightEdge : seeds)
(face : newFaces)
else do
closed <- closeOuterTurn mutable leftEdge
case closed of
Left obstruction -> pure (Left obstruction)
Right nextBase ->
do
face <- readFace mutable (nextBase `xor` 1)
stitchSeamWith
legalize
(advanceLeft progress)
mutable
operation
nextBase
upperLeft
upperRight
(leftEdge : base : seeds)
(face : newFaces)
advanceLeft :: SeamProgress -> SeamProgress
advanceLeft progress@SeamProgress{seamProgressLeftSize, seamProgressLeftCursor} =
progress
{ seamProgressLeftCursor =
previousIndex seamProgressLeftSize seamProgressLeftCursor
}
advanceRight :: SeamProgress -> SeamProgress
advanceRight progress@SeamProgress{seamProgressRightSize, seamProgressRightCursor} =
progress
{ seamProgressRightCursor =
nextIndex seamProgressRightSize seamProgressRightCursor
}
combineSeamFrontier
:: Triangulation mode vertex directed undirected face
-> SeamFrontierIndex
-> SeamFrontierIndex
-> Int
-> Int
-> Seq.Seq Int
-> Seq.Seq Int
-> SeamProgress
-> Either BuildError SeamFrontierIndex
combineSeamFrontier triangulation leftFrontier rightFrontier leftEdgeOffset rightEdgeOffset upperBridge lowerBridge progress
| seamProgressLeftSize progress /= Seq.length (seamFrontierEdges leftFrontier) = Left SeamFrontierUnavailable
| seamProgressRightSize progress /= Seq.length (seamFrontierEdges rightFrontier) = Left SeamFrontierUnavailable
| otherwise = do
leftEdges <- translatedEdges leftFrontier leftEdgeOffset
rightEdges <- translatedEdges rightFrontier rightEdgeOffset
rightResidual <-
cycleSlice
rightEdges
(seamProgressRightSize progress)
(seamProgressRightCursor progress)
(previousIndex
(seamProgressRightSize progress)
(seamProgressRightStart progress))
leftResidual <-
cycleSlice
leftEdges
(seamProgressLeftSize progress)
(seamProgressLeftStart progress)
(seamProgressLeftCursor progress)
let !outputEdges =
upperBridge
Seq.>< rightResidual
Seq.>< lowerBridge
Seq.>< leftResidual
!rightOffset = Seq.length upperBridge
!lowerOffset = rightOffset + Seq.length rightResidual
!leftOffset = lowerOffset + Seq.length lowerBridge
!rightEnd = previousIndex (seamProgressRightSize progress) progressRightStart
!leftEnd = seamProgressLeftCursor progress
!candidatePositions =
IntSet.toAscList
( IntSet.fromList
( residualBoundaryPositions 0 upperBridge
++ residualBoundaryPositions rightOffset rightResidual
++ residualBoundaryPositions lowerOffset lowerBridge
++ residualBoundaryPositions leftOffset leftResidual
++ sourceAnchorPositions
rightOffset
(seamProgressRightCursor progress)
rightEnd
(seamProgressRightSize progress)
rightFrontier
++ sourceAnchorPositions
leftOffset
progressLeftStart
leftEnd
(seamProgressLeftSize progress)
leftFrontier
)
)
(minimumX, maximumX, lowerRight, lowerLeft, upperRight, upperLeft) <-
summarizeOutputChart id candidatePositions outputEdges
(minimumY, maximumY, yLowerRight, yLowerLeft, yUpperRight, yUpperLeft) <-
summarizeOutputChart seamQuarterTurn candidatePositions outputEdges
pure
SeamFrontierIndex
{ seamFrontierEdges = outputEdges
, seamFrontierXMinimum = minimumX
, seamFrontierXMaximum = maximumX
, seamFrontierLowerRightmost = lowerRight
, seamFrontierLowerLeftmost = lowerLeft
, seamFrontierUpperRightmost = upperRight
, seamFrontierUpperLeftmost = upperLeft
, seamFrontierYMinimum = minimumY
, seamFrontierYMaximum = maximumY
, seamFrontierYLowerRightmost = yLowerRight
, seamFrontierYLowerLeftmost = yLowerLeft
, seamFrontierYUpperRightmost = yUpperRight
, seamFrontierYUpperLeftmost = yUpperLeft
}
where
progressRightStart = seamProgressRightStart progress
progressLeftStart = seamProgressLeftStart progress
translatedEdges
:: SeamFrontierIndex
-> Int
-> Either BuildError (Seq.Seq Int)
translatedEdges frontier offset
| offset == 0 = Right (seamFrontierEdges frontier)
| otherwise = Right (fmap (+ offset) (seamFrontierEdges frontier))
cycleSlice
:: Seq.Seq Int
-> Int
-> Int
-> Int
-> Either BuildError (Seq.Seq Int)
cycleSlice edges size start end
| size <= 0 || start < 0 || end < 0 || start >= size || end >= size = Left SeamFrontierUnavailable
| start <= end = Right (Seq.take (end - start + 1) (Seq.drop start edges))
| otherwise =
Right
( Seq.drop start edges
Seq.>< Seq.take (end + 1) edges
)
residualBoundaryPositions :: Int -> Seq.Seq Int -> [Int]
residualBoundaryPositions offset residual =
case Seq.length residual of
0 -> []
lengthOfResidual -> [offset, offset + lengthOfResidual - 1]
sourceAnchorPositions
:: Int
-> Int
-> Int
-> Int
-> SeamFrontierIndex
-> [Int]
sourceAnchorPositions offset start end size frontier =
[ offset + position
| anchor <- sourceAnchors frontier
, Right position <- [intervalPosition size anchor start end]
]
sourceAnchors :: SeamFrontierIndex -> [Int]
sourceAnchors frontier =
[ seamFrontierLowerRightmost frontier
, seamFrontierLowerLeftmost frontier
, seamFrontierUpperRightmost frontier
, seamFrontierUpperLeftmost frontier
, seamFrontierYLowerRightmost frontier
, seamFrontierYLowerLeftmost frontier
, seamFrontierYUpperRightmost frontier
, seamFrontierYUpperLeftmost frontier
]
summarizeOutputChart
:: ((Double, Double) -> (Double, Double))
-> [Int]
-> Seq.Seq Int
-> Either BuildError (Double, Double, Int, Int, Int, Int)
summarizeOutputChart transform candidatePositions outputEdges = do
candidates <-
traverse
(\index -> do
edge <- maybe (Left SeamFrontierUnavailable) Right (Seq.lookup index outputEdges)
let !vertex = topologyAt triangulation (4 * edge)
pure (index, physicalPoint triangulation vertex))
candidatePositions
maybe (Left SeamFrontierUnavailable) Right
(summarizeSeamChart transform candidates)
intervalPosition :: Int -> Int -> Int -> Int -> Either BuildError Int
intervalPosition size target start end
| target < 0 || target >= size = Left SeamFrontierUnavailable
| start <= end =
if target >= start && target <= end
then Right (target - start)
else Left SeamFrontierUnavailable
| target >= start = Right (target - start)
| target <= end = Right (size - start + target)
| otherwise = Left SeamFrontierUnavailable
vertexOrientation
:: MutableDcel s vertex directed undirected face
-> Int
-> Int
-> Int
-> ST s Ordering
vertexOrientation mutable a b c = do
ax <- readPointX mutable a
ay <- readPointY mutable a
bx <- readPointX mutable b
by <- readPointY mutable b
cx <- readPointX mutable c
cy <- readPointY mutable c
pure $! orient2dCoordinates ax ay bx by cx cy
{-# INLINE vertexOrientation #-}
vertexInCircle
:: MutableDcel s vertex directed undirected face
-> Int
-> Int
-> Int
-> Int
-> ST s Ordering
vertexInCircle mutable a b c d = do
ax <- readPointX mutable a
ay <- readPointY mutable a
bx <- readPointX mutable b
by <- readPointY mutable b
cx <- readPointX mutable c
cy <- readPointY mutable c
dx <- readPointX mutable d
dy <- readPointY mutable d
pure $! inCircleCoordinates ax ay bx by cx cy dx dy
{-# INLINE vertexInCircle #-}
appendSourceVertices
:: MutableDcel s vertex () () ()
-> Triangulation mode vertex () () ()
-> ST s ()
appendSourceVertices mutable source =
forRange 0 (numVertices source) $ \vertex -> do
_ <-
appendVertexCoordinates
mutable
(triPointX source `pagedUnsafeIndex` vertex)
(triPointY source `pagedUnsafeIndex` vertex)
(vertexData source (VertexId (fromIntegral vertex)))
pure ()
copySource
:: MutableDcel s vertex () () ()
-> Triangulation mode vertex () () ()
-> Int
-> Int
-> Int
-> ST s ()
copySource mutable source vertexOffset edgeOffset faceOffset = do
forRange 0 (numDirectedEdges source) $ \edge -> do
let !target = edgeOffset + edge
!sourceBase = 4 * edge
!sourceFace = topologyAt source (sourceBase + 3)
!targetFace = if sourceFace == 0 then 0 else faceOffset + sourceFace
writeOrigin mutable target (vertexOffset + topologyAt source sourceBase)
writeNext mutable target (edgeOffset + topologyAt source (sourceBase + 1))
writePrevious mutable target (edgeOffset + topologyAt source (sourceBase + 2))
writeFace mutable target targetFace
forRange 0 (numVertices source) $ \vertex ->
case vertexOutEdge source (VertexId (fromIntegral vertex)) of
Nothing -> markConnected mutable (vertexOffset + vertex) (-1)
Just (DirectedEdgeId edge) ->
markConnected mutable (vertexOffset + vertex) (edgeOffset + fromIntegral edge)
forRange 1 (numFaces source) $ \face ->
case adjacentEdge source (FaceId (fromIntegral face)) of
Nothing -> writeFaceEdge mutable (faceOffset + face) (-1)
Just (DirectedEdgeId edge) ->
writeFaceEdge mutable (faceOffset + face) (edgeOffset + fromIntegral edge)
copySourceConstraints
:: MutableDcel s vertex () () ()
-> Int
-> Triangulation 'Constrained vertex () () ()
-> ST s ()
copySourceConstraints mutable directedEdgeOffset source =
traverse_
(\(UndirectedEdgeId edge) ->
()
<$ setConstraint
mutable
(directedEdgeOffset + 2 * fromIntegral edge)
)
(constraintEdges source)
spliceLowerTangent
:: MutableDcel s vertex () () ()
-> Int
-> Int
-> Int
-> ST s Int
spliceLowerTangent mutable rightEdgeOffset leftOuter rightOuterSource = do
let !rightOuter = rightEdgeOffset + rightOuterSource
leftVertex <- readOrigin mutable leftOuter
rightVertex <- readOrigin mutable rightOuter
leftPrevious <- readPrevious mutable leftOuter
rightPrevious <- readPrevious mutable rightOuter
(forward, backward) <- addEdge mutable leftVertex rightVertex
writeFace mutable forward 0
writeFace mutable backward 0
linkEdges mutable leftPrevious forward
linkEdges mutable forward rightOuter
linkEdges mutable rightPrevious backward
linkEdges mutable backward leftOuter
writeFaceEdge mutable 0 forward
writeVertexOut mutable leftVertex forward
writeVertexOut mutable rightVertex backward
pure forward
{-# INLINE spliceLowerTangent #-}
topologyAt
:: Triangulation mode vertex directed undirected face
-> Int
-> Int
topologyAt triangulation slot =
fromIntegral (triHalfTopology triangulation `pagedUnsafeIndex` slot)
{-# INLINE topologyAt #-}
forRange :: Monad m => Int -> Int -> (Int -> m ()) -> m ()
forRange from to action = go from
where
go !index
| index >= to = pure ()
| otherwise = action index >> go (index + 1)
{-# INLINE forRange #-}