packages feed

moonlight-triangulation-0.1.0.0: src-build/Moonlight/Triangulation/Insertion.hs

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

module Moonlight.Triangulation.Insertion
  ( insertExistingVertex
  , insertExistingVertexWithHint
  , insertPointCombining
  , insertVertexAtPoint
  , insertExistingVertexAtLocation
  ) where

import Control.Monad.ST (ST)
import Moonlight.Triangulation.Handles.HandleDefs (VertexId (..))
import Moonlight.Triangulation.Internal.DcelOperations
import Moonlight.Triangulation.Internal.Location
import Moonlight.Triangulation.Internal.Mutable
import Moonlight.Triangulation.Internal.OperationState
  ( Counter (..)
  , OperationState
  , addCounter
  )
import Moonlight.Triangulation.Internal.Probe (KnownProbe, Probe (..))
import Moonlight.Triangulation.Math
import Moonlight.Triangulation.Types

-- | The shared exact-site insertion interpreter. Geometry decides whether a
-- site is new; callers choose only the annotation law for an occupied site.
-- Keeping the validation, location, placement, and counters here prevents
-- sessions and constrained extension from drifting into two insertion
-- semantics merely because they own different enclosing transactions.
insertPointCombining
  :: (vertex -> vertex -> vertex)
  -> Maybe Int
  -> MutableDcel s vertex directed undirected face
  -> OperationState s
  -> Point
  -> vertex
  -> ST s (Either BuildError (Int, InsertionDisposition))
insertPointCombining combine seed mutable operation point payload =
  case validatePoint Nothing point of
    Left failure -> pure (Left failure)
    Right _ -> do
      addCounter operation CounterInputPoints 1
      outcome <- insertVertexAtPoint @'ProbeOff mutable operation seed point payload
      case outcome of
        Left failure -> pure (Left failure)
        Right resolved@(vertex, disposition) -> do
          case disposition of
            AlreadyPresent -> do
              resident <- vertexDataAt mutable vertex
              writeVertexData mutable vertex (combine resident payload)
              addCounter operation CounterExistingPoints 1
              addCounter operation CounterDuplicatePoints 1
            Inserted -> addCounter operation CounterUniquePoints 1
          pure (Right resolved)
{-# INLINE insertPointCombining #-}

insertExistingVertex
  :: forall p s vertex directed undirected face
   . KnownProbe p
  => MutableDcel s vertex directed undirected face
  -> OperationState s
  -> Int
  -> ST s (Either BuildError ())
insertExistingVertex mutable operation = insertExistingVertexWithHint @p mutable operation Nothing

-- | Insert a materialized vertex while beginning point location from a face
-- already known to be geometrically adjacent to the request. The hint changes
-- only the amount of walking; the located site remains authoritative.
insertExistingVertexWithHint
  :: forall p s vertex directed undirected face
   . KnownProbe p
  => MutableDcel s vertex directed undirected face
  -> OperationState s
  -> Maybe Int
  -> Int
  -> ST s (Either BuildError ())
insertExistingVertexWithHint mutable operation hint vertex = do
  query <- pointAt mutable vertex
  located <- locateMutable mutable operation hint query
  case located of
    Left obstruction -> pure (Left obstruction)
    Right site -> insertExistingVertexAtLocation @p mutable operation vertex site

-- | Locate before materializing a point, so duplicate detection remains a
-- topological fact rather than a resident coordinate index.
insertVertexAtPoint
  :: forall p s vertex directed undirected face
   . KnownProbe p
  => MutableDcel s vertex directed undirected face
  -> OperationState s
  -> Maybe Int
  -> Point
  -> vertex
  -> ST s (Either BuildError (Int, InsertionDisposition))
insertVertexAtPoint mutable operation hint point vertexData = do
  located <- locateMutable mutable operation hint point
  case located of
    Left obstruction -> pure (Left obstruction)
    Right (MutableOnVertex existing) -> pure (Right (existing, AlreadyPresent))
    Right site -> do
      capacity <- ensurePointCapacity mutable 1
      case capacity of
        Left obstruction -> pure (Left obstruction)
        Right () -> do
          vertex <- appendVertex mutable point vertexData
          inserted <- insertExistingVertexAtLocation @p mutable operation vertex site
          pure ((vertex, Inserted) <$ inserted)

-- | Interpret a point-location result without locating the same point again.
-- Callers may hold this witness only while no topology mutation intervenes.
--
-- The site decides which counts are consulted, and the vertex's own point is
-- read only by the two strata that compare against it — the degenerate line and
-- the failure report. The area strata already stand on a located site and would
-- otherwise rebuild a point the locate stage was handed.
insertExistingVertexAtLocation
  :: forall p s vertex directed undirected face
   . KnownProbe p
  => MutableDcel s vertex directed undirected face
  -> OperationState s
  -> Int
  -> MutableLocation
  -> ST s (Either BuildError ())
insertExistingVertexAtLocation mutable operation vertex located =
  case located of
    MutableOnVertex existing ->
      pure
        ( Left
            ( FreshInsertionMatchedExistingVertex
                (VertexId (fromIntegral vertex))
                (VertexId (fromIntegral existing))
            )
        )
    MutableEmpty -> do
      connected <- connectedCount mutable
      if connected == 0
        then setupFirstVertex mutable vertex >> pure (Right ())
        else locationFailed
    MutableOnEdge edge -> do
      faces <- faceCount mutable
      if faces <= 1
        then splitLineEdge mutable operation edge vertex
        else insertOnEdge @p mutable operation edge vertex
    MutableInFace face -> do
      faces <- faceCount mutable
      if faces <= 1
        then locationFailed
        else insertIntoFace @p mutable operation face vertex
    MutableOutsideHull edge -> do
      connected <- connectedCount mutable
      if connected == 1
        then setupSecondVertex mutable vertex
        else do
          faces <- faceCount mutable
          if faces <= 1
            then extendDegenerateLine edge
            else insertOutsideHull @p mutable operation edge vertex
 where
  extendDegenerateLine edge = do
    from <- edgeOriginPoint mutable edge
    to <- edgeOriginPoint mutable (edge `xorInt` 1)
    query <- pointAt mutable vertex
    if orient2d from to query == EQ
      then do
        endpoint <- readOrigin mutable edge
        extendLine mutable operation endpoint vertex
      else lineToArea @p mutable operation vertex

  locationFailed = do
    query <- pointAt mutable vertex
    pure (Left (PointLocationFailed query))

xorInt :: Int -> Int -> Int
xorInt value 1 = if even value then value + 1 else value - 1
xorInt value _ = value
{-# INLINE xorInt #-}