packages feed

moonlight-triangulation-0.1.0.0: src-dcel/Moonlight/Triangulation/Internal/DcelOperations/Hull.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}

-- | Growth outside the hull: visible ranges, turn closure, and convexity repair.
module Moonlight.Triangulation.Internal.DcelOperations.Hull
  ( insertOutsideHull
  , insertOutsideHullBetween
  , closeOuterTurn
  , fixHullConvexity
  ) where

import Control.Monad (forM_)
import Control.Monad.ST (ST)
import Data.STRef (writeSTRef)
import Moonlight.Triangulation.Handles.HandleDefs (DirectedEdgeId (..), FaceId (..))
import Moonlight.Triangulation.Internal.DcelOperations.CandidateArena
  ( noStarVertex
  , seedGenericEdges
  )
import Moonlight.Triangulation.Internal.DcelOperations.FlipRule (LegalizationLaw (..))
import Moonlight.Triangulation.Internal.DcelOperations.Legalize (legalizeScratch)
import Moonlight.Triangulation.Internal.DcelOperations.Normalize (drainLegalization)
import Moonlight.Triangulation.Internal.DcelOperations.Twin (reverseIndex)
import Moonlight.Triangulation.Internal.Mutable
  ( MutableDcel (..)
  , addEdge
  , addEdgeBlock
  , addFaceBlock
  , directedEdgeCount
  , ensureCellCapacity
  , linkEdges
  , markConnected
  , pointAt
  , readFace
  , readFaceEdge
  , readNext
  , readOrigin
  , readPointX
  , readPointY
  , readPrevious
  , setCycle3
  , writeFace
  , writeFaceEdge
  , writeOrigin
  , writeVertexOut
  )
import Moonlight.Triangulation.Internal.OperationState
  ( Counter (..)
  , OperationState
  , addCounter
  , readScratch
  , writeScratch
  )
import Moonlight.Triangulation.Internal.Probe (KnownProbe)
import Moonlight.Triangulation.Types (BuildError (..), Point (..))
import Moonlight.Triangulation.Scalar (orient2dCoordinates)

insertOutsideHull :: forall p s vertex directed undirected face. KnownProbe p => MutableDcel s vertex directed undirected face -> OperationState s -> Int -> Int -> ST s (Either BuildError ())
insertOutsideHull mutable operation start vertex = do
  query <- pointAt mutable vertex
  visibleStart <- visibleOuter mutable start query
  if not visibleStart
    then pure (Left (HullStartNotVisible (DirectedEdgeId (fromIntegral start))))
    else do
      halfEdges <- directedEdgeCount mutable
      left <- expandPrevious halfEdges start start query
      right <- expandNext halfEdges start left query
      collected <- collectOuterChain mutable operation left right
      case collected of
        Left obstruction -> pure (Left obstruction)
        Right chainCount -> do
          inserted <- insertOutsideHullCollected @p mutable operation vertex chainCount
          case inserted of
            Left obstruction -> pure (Left obstruction)
            Right _ -> do
              -- The regular insertion path owns its own hull-insertion count; the sweep
              -- path through 'insertOutsideHullBetween' counts its own instead.
              addCounter operation CounterHullInsertions 1
              pure (Right ())
 where
  expandPrevious !bound !stopAt !current !query
    | bound <= 0 = pure current
    | otherwise = do
        candidate <- readPrevious mutable current
        if candidate == stopAt
          then pure current
          else do
            visible <- visibleOuter mutable candidate query
            if visible then expandPrevious (bound - 1) stopAt candidate query else pure current

  expandNext !bound !current !left !query
    | bound <= 0 = pure current
    | otherwise = do
        candidate <- readNext mutable current
        if candidate == left
          then pure current
          else do
            visible <- visibleOuter mutable candidate query
            if visible then expandNext (bound - 1) candidate left query else pure current

-- | Insert a vertex outside an explicitly selected contiguous outer-face range.
-- The range, rather than a convexity policy, is the primitive: regular
-- insertion supplies the complete visible range, while circle sweep may supply
-- one edge and defer the remaining hull turns. Topology mutation has one owner.
insertOutsideHullBetween
  :: forall p s vertex directed undirected face
   . KnownProbe p
  => MutableDcel s vertex directed undirected face
  -> OperationState s
  -> Int
  -> Int
  -> Int
  -> ST s (Either BuildError (Int, Int))
insertOutsideHullBetween mutable operation left right vertex = do
  collected <- collectOuterChain mutable operation left right
  case collected of
    Left obstruction -> pure (Left obstruction)
    Right chainCount -> insertOutsideHullCollected @p mutable operation vertex chainCount

collectOuterChain
  :: MutableDcel s vertex directed undirected face
  -> OperationState s
  -> Int
  -> Int
  -> ST s (Either BuildError Int)
collectOuterChain mutable operation left right = do
  halfEdges <- directedEdgeCount mutable
  go (halfEdges + 1) left 0
 where
  go !remaining !current !count
    | remaining <= 0 =
        pure
          ( Left
              ( OuterRangeDidNotTerminate
                  (DirectedEdgeId (fromIntegral left))
                  (DirectedEdgeId (fromIntegral right))
                  count
              )
          )
    | otherwise = do
        incident <- readFace mutable current
        if incident /= 0
          then
            pure
              ( Left
                  ( OuterRangeContainsInnerEdge
                      (DirectedEdgeId (fromIntegral current))
                      (FaceId (fromIntegral incident))
                  )
              )
          else do
            writeScratch operation count current
            if current == right
              then pure (Right (count + 1))
              else readNext mutable current >>= \following -> go (remaining - 1) following (count + 1)

insertOutsideHullCollected
  :: forall p s vertex directed undirected face
   . KnownProbe p
  => MutableDcel s vertex directed undirected face
  -> OperationState s
  -> Int
  -> Int
  -> ST s (Either BuildError (Int, Int))
insertOutsideHullCollected mutable@MutableDcel{mdLastFace} operation vertex chainCount = do
  capacity <- ensureCellCapacity mutable (chainCount + 1) chainCount
  case capacity of
    Left obstruction -> pure (Left obstruction)
    Right () -> Right <$> insertOutsideHullWithCapacity
 where
  insertOutsideHullWithCapacity = do
    left <- readScratch operation 0
    right <- readScratch operation (chainCount - 1)
    oldPrevious <- readPrevious mutable left
    oldNext <- readNext mutable right
    edgeBase <- addEdgeBlock mutable (chainCount + 1)
    faceBase <- addFaceBlock mutable chainCount
    forM_ [0 .. chainCount] $ \index -> do
      chainVertex <-
        if index == 0
          then readScratch operation 0 >>= readOrigin mutable
          else readScratch operation (index - 1) >>= readOrigin mutable . reverseIndex
      let !forward = edgeBase + 2 * index
          !backward = forward + 1
      writeOrigin mutable forward chainVertex
      writeOrigin mutable backward vertex
    forM_ [0 .. chainCount - 1] $ \index -> do
      outerEdge <- readScratch operation index
      let !face = faceBase + index
          !nextSpoke = edgeBase + 2 * (index + 1)
          !previousSpoke = edgeBase + 2 * index + 1
      setCycle3 mutable face outerEdge nextSpoke previousSpoke
    let !firstOuterSpoke = edgeBase
        !lastOuterSpoke = edgeBase + 2 * chainCount + 1
    writeFace mutable firstOuterSpoke 0
    writeFace mutable lastOuterSpoke 0
    linkEdges mutable oldPrevious firstOuterSpoke
    linkEdges mutable firstOuterSpoke lastOuterSpoke
    linkEdges mutable lastOuterSpoke oldNext
    writeFaceEdge mutable 0 firstOuterSpoke
    forM_ [0 .. chainCount - 1] $ \index -> do
      chainEdge <- readScratch operation index
      chainVertex <- readOrigin mutable chainEdge
      writeVertexOut mutable chainVertex chainEdge
    lastChain <- readScratch operation (chainCount - 1)
    lastVertex <- readOrigin mutable (reverseIndex lastChain)
    writeVertexOut mutable lastVertex (edgeBase + 2 * chainCount)
    markConnected mutable vertex lastOuterSpoke
    writeSTRef mdLastFace faceBase
    legalizeScratch @p mutable operation vertex chainCount
    pure (firstOuterSpoke, lastOuterSpoke)

-- | Replace two consecutive outer edges @a->b, b->c@ by @a->c@ and
-- materialize the triangle they bound. The returned edge is the new outer
-- diagonal. This is the sole turn-closing primitive used both by deferred
-- circle sweep and the final Graham repair. Topology mutation only: the
-- caller owns the legalization epoch, and seeds the two closed edges itself
-- once the replacement's links are in place.
closeOuterTurn
  :: MutableDcel s vertex directed undirected face
  -> Int
  -> ST s (Either BuildError Int)
closeOuterTurn mutable first = do
  capacity <- ensureCellCapacity mutable 1 1
  case capacity of
    Left obstruction -> pure (Left obstruction)
    Right () -> do
      second <- readNext mutable first
      oldPrevious <- readPrevious mutable first
      oldNext <- readNext mutable second
      from <- readOrigin mutable first
      to <- readOrigin mutable (reverseIndex second)
      (outer, inner) <- addEdge mutable from to
      newFace <- addFaceBlock mutable 1
      writeFace mutable outer 0
      setCycle3 mutable newFace first second inner
      linkEdges mutable oldPrevious outer
      linkEdges mutable outer oldNext
      writeFaceEdge mutable 0 outer
      writeVertexOut mutable from outer
      writeVertexOut mutable to inner
      pure (Right outer)
{-# INLINE closeOuterTurn #-}

-- | Close every remaining left turn of the star-shaped sweep hull in one
-- Graham-style pass. Each closure strictly decreases the outer-edge count, so
-- the pass is linear in the visited hull plus the local Delaunay legalization
-- work it causes. Turn closures are seeded into one shared epoch as they
-- happen and drained once at the end: closure never deletes an edge and never
-- touches the outer cycle's legality, so which turns close does not depend on
-- when the interior is repaired. The pass counts its own closures and returns
-- the drain's tallies; nothing is reported behind its back.
fixHullConvexity
  :: forall p s vertex directed undirected face
   . KnownProbe p
  => MutableDcel s vertex directed undirected face
  -> OperationState s
  -> ST s (Either BuildError (Int, Int, Int))
fixHullConvexity mutable operation = do
  start <- readFaceEdge mutable 0
  if start < 0
    then pure (Right (0, 0, 0))
    else do
      walked <- walk start start 0 0 0 0
      case walked of
        Left obstruction -> pure (Left obstruction)
        Right (top, closures) -> do
          (flips, maxDepth) <- drainLegalization @p mutable operation top noStarVertex ValidMesh
          pure (Right (closures, flips, maxDepth))
 where
  walk !start !current !stackSize !steps !top !closures = do
    halfEdges <- directedEdgeCount mutable
    if steps > halfEdges + 2
      then
        pure
          ( Left
              ( OuterCycleDidNotTerminate
                  (DirectedEdgeId (fromIntegral start))
                  (DirectedEdgeId (fromIntegral current))
                  steps
              )
          )
      else do
        following <- readNext mutable current
        writeScratch operation stackSize current
        reduction <- reduce (stackSize + 1) top closures
        case reduction of
          Left obstruction -> pure (Left obstruction)
          Right (reduced, nextTop, nextClosures) -> do
            finished <-
              if reduced < 2
                then pure False
                else (== following) <$> readScratch operation 1
            if finished
              then pure (Right (nextTop, nextClosures))
              else walk start following reduced (steps + 1) nextTop nextClosures

  reduce !count !top !closures
    | count < 2 = pure (Right (count, top, closures))
    | otherwise = do
        first <- readScratch operation (count - 2)
        second <- readScratch operation (count - 1)
        fromVertex <- readOrigin mutable first
        middleVertex <- readOrigin mutable (reverseIndex first)
        targetVertex <- readOrigin mutable (reverseIndex second)
        fromX <- readPointX mutable fromVertex
        fromY <- readPointY mutable fromVertex
        middleX <- readPointX mutable middleVertex
        middleY <- readPointY mutable middleVertex
        targetX <- readPointX mutable targetVertex
        targetY <- readPointY mutable targetVertex
        if orient2dCoordinates fromX fromY middleX middleY targetX targetY == GT
          then do
            closed <- closeOuterTurn mutable first
            case closed of
              Left obstruction -> pure (Left obstruction)
              Right replacement -> do
                writeScratch operation (count - 2) replacement
                nextTop <- seedGenericEdges operation top [first, second]
                reduce (count - 1) nextTop (closures + 1)
          else pure (Right (count, top, closures))

visibleOuter :: MutableDcel s vertex directed undirected face -> Int -> Point -> ST s Bool
visibleOuter mutable edge query = do
  fromVertex <- readOrigin mutable edge
  toVertex <- readOrigin mutable (reverseIndex edge)
  case query of
    Point queryX queryY -> do
      fromX <- readPointX mutable fromVertex
      fromY <- readPointY mutable fromVertex
      toX <- readPointX mutable toVertex
      toY <- readPointY mutable toVertex
      pure (orient2dCoordinates fromX fromY toX toY queryX queryY == GT)
{-# INLINE visibleOuter #-}