moonlight-triangulation-0.1.0.0: src-build/Moonlight/Triangulation/Internal/Location.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE NamedFieldPuns #-}
module Moonlight.Triangulation.Internal.Location
( MutableLocation (..)
, locateMutable
, locateLineMutable
, visibleOuterEdge
) where
import Control.Monad.ST (ST)
import Data.Bits (xor, (.&.))
import Data.STRef (readSTRef, writeSTRef)
import Moonlight.Triangulation.Internal.FaceProbe
import Moonlight.Triangulation.Internal.Mutable
import Moonlight.Triangulation.Internal.OperationState
( Counter (..)
, OperationState
, addCounter
, maxCounter
)
import Moonlight.Triangulation.Math
import Moonlight.Triangulation.Types
data MutableLocation
= MutableEmpty
| MutableOnVertex {-# UNPACK #-} !Int
| MutableOnEdge {-# UNPACK #-} !Int
| MutableInFace {-# UNPACK #-} !Int
| MutableOutsideHull {-# UNPACK #-} !Int
deriving stock (Eq, Ord, Show)
reverseIndex :: Int -> Int
reverseIndex edge = edge `xor` 1
{-# INLINE reverseIndex #-}
locateMutable
:: MutableDcel s vertex directed undirected face
-> OperationState s
-> Maybe Int
-> Point
-> ST s (Either BuildError MutableLocation)
locateMutable mutable operation hint rawQuery = do
connected <- connectedCount mutable
faces <- faceCount mutable
let !query = canonicalPoint rawQuery
case connected of
0 -> pure (Right MutableEmpty)
_ | faces <= 1 -> Right <$> locateLineMutable mutable query
-- A hint is the caller vouching for adjacency, so the exact walk starts
-- there directly; only an unhinted locate buys the vertex descent.
_ | Just face <- hint, face > 0 && face < faces -> walk mutable operation query face
_ -> do
start <- chooseStartFace mutable hint
anchor <- readFaceEdge mutable start
if anchor < 0
then walk mutable operation query start
else do
origin <- readOrigin mutable anchor
nearest <- descendToNearest mutable operation query origin
face <- incidentInnerFace mutable nearest start
walk mutable operation query face
-- | Greedy first-improvement descent through vertex neighbours: hop to the
-- first neighbour strictly closer to the query until none improves. Distance
-- is a heuristic only — exact containment belongs to 'walk' — so the widened
-- comparison can lengthen the path but never move the located answer.
--
-- Strictly decreasing distance bounds the hops and nothing else: no vertex is
-- entered twice, but the star rotation terminates only if @reverse . previous@
-- closes an orbit, which is a property of the links rather than of the
-- geometry. Disjoint stars over distinct entered vertices spend at most one
-- step per outgoing half-edge, so a budget past 'directedEdgeCount' cannot be
-- exhausted while the links are well formed. Exhausting it means they are not,
-- and yields the vertex in hand rather than an error — a truncated descent can
-- only lengthen 'walk', never move what it finds.
descendToNearest
:: MutableDcel s vertex directed undirected face
-> OperationState s
-> Point
-> Int
-> ST s Int
descendToNearest mutable operation query start = do
halfEdges <- directedEdgeCount mutable
point <- pointAt mutable start
settle (halfEdges + 1) start (squaredDistanceWide query point)
where
settle !budget !vertex !best = do
first <- readVertexOut mutable vertex
if first < 0
then pure vertex
else rotate budget vertex best first first
rotate !budget !vertex !best !first !edge
| budget <= 0 = pure vertex
| otherwise = do
neighbour <- readOrigin mutable (reverseIndex edge)
point <- pointAt mutable neighbour
let !candidate = squaredDistanceWide query point
if candidate < best
then do
addCounter operation CounterLocationWalkSteps 1
settle (budget - 1) neighbour candidate
else do
previousEdge <- readPrevious mutable edge
let !outgoing = reverseIndex previousEdge
if outgoing == first then pure vertex else rotate (budget - 1) vertex best first outgoing
-- | An inner face incident to the vertex, so the exact walk starts adjacent
-- to where the descent settled; the caller's face stands in when the star
-- offers none.
incidentInnerFace
:: MutableDcel s vertex directed undirected face
-> Int
-> Int
-> ST s Int
incidentInnerFace mutable vertex fallback = do
first <- readVertexOut mutable vertex
if first < 0 then pure fallback else rotate first first
where
rotate !first !edge = do
face <- readFace mutable edge
if face > 0
then pure face
else do
previousEdge <- readPrevious mutable edge
let !outgoing = reverseIndex previousEdge
if outgoing == first then pure fallback else rotate first outgoing
locateLineMutable :: MutableDcel s vertex directed undirected face -> Point -> ST s MutableLocation
locateLineMutable mutable rawQuery = do
let !query = canonicalPoint rawQuery
vertices <- pointCount mutable
exactVertex <- findVertex query vertices 0
case exactVertex of
Just vertex -> pure (MutableOnVertex vertex)
Nothing -> do
halfEdges <- directedEdgeCount mutable
if halfEdges == 0
then pure (MutableOutsideHull 0)
else do
onEdge <- findSegment query halfEdges 0
case onEdge of
Just edge -> pure (MutableOnEdge edge)
Nothing -> do
firstConnected <- findConnected vertices 0
firstOut <- readVertexOut mutable firstConnected
let edge = if firstOut < 0 then 0 else firstOut
from <- edgeOriginPoint mutable edge
to <- edgeOriginPoint mutable (reverseIndex edge)
case orient2d from to query of
GT -> pure (MutableOutsideHull (orientOuter edge))
LT -> pure (MutableOutsideHull (orientOuter (reverseIndex edge)))
EQ -> MutableOutsideHull <$> nearestTerminalEdge query halfEdges edge
where
findVertex !query !limit !index
| index >= limit = pure Nothing
| otherwise = do
connected <- isConnected mutable index
if not connected
then findVertex query limit (index + 1)
else do
point <- pointAt mutable index
if point == query then pure (Just index) else findVertex query limit (index + 1)
findSegment !query !limit !edge
| edge >= limit = pure Nothing
| otherwise = do
let normalized = edge .&. complementOne
from <- edgeOriginPoint mutable normalized
to <- edgeOriginPoint mutable (reverseIndex normalized)
if onClosedSegment from to query
then pure (Just normalized)
else findSegment query limit (normalized + 2)
findConnected !limit !index
| index >= limit = pure 0
| otherwise = do
connected <- isConnected mutable index
if connected then pure index else findConnected limit (index + 1)
orientOuter :: Int -> Int
orientOuter edge = edge
-- A degenerate chain's outer cycle doubles back exactly at its two terminal
-- vertices, so @next e == reverse e@ characterises the edge entering a
-- terminal. Collinear extension must attach to the terminal nearest the
-- query, which for three or more vertices is not an endpoint of any single
-- arbitrary edge.
nearestTerminalEdge !query !limit !fallback = go 0 Nothing
where
go !edge !best
| edge >= limit = pure (maybe fallback fst best)
| otherwise = do
edgeNext <- readNext mutable edge
if edgeNext /= reverseIndex edge
then go (edge + 1) best
else do
let !terminal = reverseIndex edge
point <- edgeOriginPoint mutable terminal
let !candidate = squaredDistanceWide query point
case best of
Just (_, closest) | closest <= candidate -> go (edge + 1) best
_ -> go (edge + 1) (Just (terminal, candidate))
complementOne = -2
walk
:: MutableDcel s vertex directed undirected face
-> OperationState s
-> Point
-> Int
-> ST s (Either BuildError MutableLocation)
walk mutable operation query initialFace = do
faces <- faceCount mutable
halfEdges <- directedEdgeCount mutable
let !budget = max 8 (faces + halfEdges `quot` 2 + 4)
go budget 0 initialFace
where
go !remaining !steps !face
| remaining <= 0 = do
addCounter operation CounterLocationFallbacks 1
maxCounter operation CounterLocationMaxWalk steps
pure (Left (LocationWalkExhausted query steps))
| face <= 0 = Right . MutableOutsideHull <$> visibleOuterEdge mutable query
| otherwise = do
addCounter operation CounterLocationWalkSteps 1
let !nextSteps = steps + 1
probe <- probeFace mutable face query
case probe of
MutableInFace found -> do
writeSTRef (mdLastFace mutable) found
maxCounter operation CounterLocationMaxWalk nextSteps
pure (Right probe)
MutableOnVertex _ -> do
writeSTRef (mdLastFace mutable) face
maxCounter operation CounterLocationMaxWalk nextSteps
pure (Right probe)
MutableOnEdge _ -> do
writeSTRef (mdLastFace mutable) face
maxCounter operation CounterLocationMaxWalk nextSteps
pure (Right probe)
MutableOutsideHull edge -> do
adjacent <- readFace mutable edge
if adjacent == 0
then do
maxCounter operation CounterLocationMaxWalk nextSteps
pure (Right (MutableOutsideHull edge))
else go (remaining - 1) nextSteps adjacent
MutableEmpty -> pure (Left (PointLocationFailed query))
probeFace :: MutableDcel s vertex directed undirected face -> Int -> Point -> ST s MutableLocation
-- 'faceEdges' walks the @next@ chain, so the cycle already states each
-- boundary's destination: @destination e0 = origin e1@. Reading the twin's
-- origin instead asks the store for what the face has already said, at twice
-- the endpoint loads and twice the coordinate loads per probe. Three origins
-- and three points settle all three boundaries. Vertex and edge hits still
-- return on the first boundary that reports one, and a later crossing still
-- displaces an earlier one.
probeFace mutable face query = do
(e0, e1, e2) <- faceEdges mutable face
a <- readOrigin mutable e0
b <- readOrigin mutable e1
c <- readOrigin mutable e2
pa <- pointAt mutable a
pb <- pointAt mutable b
pc <- pointAt mutable c
let !first = probeBoundary reverseIndex query e0 a pa b pb
case first of
BoundaryOnVertex vertex -> pure (MutableOnVertex vertex)
BoundaryOnEdge boundary -> pure (MutableOnEdge boundary)
_ -> do
let !second = probeBoundary reverseIndex query e1 b pb c pc
case second of
BoundaryOnVertex vertex -> pure (MutableOnVertex vertex)
BoundaryOnEdge boundary -> pure (MutableOnEdge boundary)
_ -> do
let !third = probeBoundary reverseIndex query e2 c pc a pa
case third of
BoundaryOnVertex vertex -> pure (MutableOnVertex vertex)
BoundaryOnEdge boundary -> pure (MutableOnEdge boundary)
_ -> pure $ case keep third (keep second (keep first Nothing)) of
Nothing -> MutableInFace face
Just boundary -> MutableOutsideHull boundary
where
keep :: BoundaryProbe boundary probeVertex -> Maybe boundary -> Maybe boundary
keep (BoundaryCrossing boundary) _ = Just boundary
keep _ held = held
chooseStartFace :: MutableDcel s vertex directed undirected face -> Maybe Int -> ST s Int
chooseStartFace mutable hint = do
faces <- faceCount mutable
cached <- readSTRef (mdLastFace mutable)
let candidate = case hint of
Just face | face > 0 && face < faces -> face
_ | cached > 0 && cached < faces -> cached
_ -> 1
pure candidate
visibleOuterEdge :: MutableDcel s vertex directed undirected face -> Point -> ST s Int
visibleOuterEdge mutable query = do
start <- readFaceEdge mutable 0
halfEdges <- directedEdgeCount mutable
if start < 0 || halfEdges == 0
then pure 0
else go (halfEdges + 1) start start Nothing
where
go !remaining !start !edge !fallback
| remaining <= 0 = pure (maybe start id fallback)
| otherwise = do
from <- edgeOriginPoint mutable edge
to <- edgeOriginPoint mutable (reverseIndex edge)
let !side = orient2d from to query
!fallback' = if side /= LT then Just edge else fallback
if side == GT
then pure edge
else do
edgeNext <- readNext mutable edge
if edgeNext == start then pure (maybe edge id fallback') else go (remaining - 1) start edgeNext fallback'