packages feed

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

{-# LANGUAGE NamedFieldPuns #-}

-- | Ruppert refinement: Steiner insertion composed after a built mesh, never a
-- second kind of mesh. Parameters are reached through checked verbs rather than
-- a raw record, so an unrealizable quality bar is a refusal.
module Moonlight.Triangulation.Refinement
  ( refine
  , refineWithinDomain
  , validateRefinementParameters
  , withMinimumAngle
  , radiusEdgeRatioForAngle
  ) where

import Control.Monad.ST (ST, runST)
import Data.Foldable (traverse_)
import qualified Data.IntSet as IntSet
import Data.List (sort)
import qualified Data.Map.Strict as Map
import Data.Maybe (fromMaybe)
import qualified Data.Set as Set
import qualified Data.Vector as V
import Moonlight.Triangulation.Dcel
  ( faceVertices
  , incidentFace
  , isConstraintEdge
  , numFaces
  , numUndirectedEdges
  , numVertices
  , undirectedEndpoints
  , vertexPoint
  )
import Moonlight.Triangulation.FloodFillIterator (facesAtEvenBarrierDepth)
import Moonlight.Triangulation.Handles
  ( FaceId (..)
  , UndirectedEdgeId (..)
  , directedPair
  )
import Moonlight.Triangulation.Internal.Mutable
import Moonlight.Triangulation.Internal.Capacity (ensureCapacity)
import Moonlight.Triangulation.Internal.OperationState (freezeBuildStats, newOperationState)
import Moonlight.Triangulation.Internal.Refinement
import Moonlight.Triangulation.Types
import Moonlight.Triangulation.Validation (validateTopology)

data RefinementExecution mode vertex directed undirected face = RefinementExecution
  { refinementExecutionResult :: !(RefinementResult mode vertex directed undirected face)
  , refinementExecutionVisitedFaces :: ![Int]
  , refinementExecutionInterfaceBoundaryReads :: {-# UNPACK #-} !Int
  , refinementExecutionBoundaryCrossingAttempts :: {-# UNPACK #-} !Int
  }

-- | The radius-edge ratio admitting a minimum angle, in degrees.
radiusEdgeRatioForAngle :: Double -> Either BuildError (Maybe Double)
radiusEdgeRatioForAngle degrees =
  case classifyNonFinite degrees of
    Just nonFinite ->
      Left (RefinementMinimumAngleNotFinite nonFinite)
    Nothing
      | degrees < 0 || degrees > 60 ->
          Left (RefinementMinimumAngleOutOfRange degrees)
      | degrees == 0 -> Right Nothing
      | otherwise ->
          let ratio = 0.5 / sin (degrees * pi / 180)
           in case classifyNonFinite ratio of
                Just nonFinite ->
                  Left (RefinementMinimumAngleDerivedRatioNotFinite nonFinite)
                Nothing -> Right (Just ratio)

-- | Set the minimum angle, in degrees.
withMinimumAngle :: Double -> RefinementParameters -> Either BuildError (RefinementParameters)
withMinimumAngle degrees parameters = do
  ratio <- radiusEdgeRatioForAngle degrees
  pure parameters{refineMaxRadiusEdgeRatio = ratio}

-- | Refine an unconstrained or constrained triangulation in the same finite
-- DCEL. The constructor supplies application payloads for Steiner vertices;
-- payloads annotate the requested geometric point and cannot reauthor it.
refine
  :: (Point -> vertex)
  -> RefinementParameters
  -> Triangulation mode vertex directed undirected face
  -> Either BuildError (RefinementResult mode vertex directed undirected face)
refine makeVertex parameters =
  fmap refinementExecutionResult
    . refineWithInitialSeed RefineEveryFace Nothing makeVertex parameters

-- | Prove that a set of active faces is separated from every protected inner
-- face by exactly the supplied immutable edge section. The witness is tied to
-- the input arena cardinalities and is revalidated before interpretation.
mkRefinementDomain
  :: Set.Set FaceId
  -> Set.Set UndirectedEdgeId
  -> Triangulation mode vertex directed undirected face
  -> Either BuildError RefinementDomain
mkRefinementDomain permittedFaces interfaceEdges triangulation = do
  permitted <- validateSeedFaces triangulation permittedFaces
  interface <- IntSet.fromList <$> traverse validateInterfaceEdge (Set.toAscList interfaceEdges)
  let expected = expectedInterface permitted
  case IntSet.lookupMin (expected IntSet.\\ interface) of
    Just pair -> Left (RefinementDomainInterfaceMissing (UndirectedEdgeId (fromIntegral pair)))
    Nothing ->
      case IntSet.lookupMin (interface IntSet.\\ expected) of
        Just pair -> Left (RefinementDomainInterfaceExtraneous (UndirectedEdgeId (fromIntegral pair)))
        Nothing ->
          Right
            RefinementDomain
              { refinementDomainPermittedFaces = permitted
              , refinementDomainInterfacePairs = interface
              , refinementDomainInputFaces =
                  Map.fromAscList
                    [ (face, faceSignature triangulation face)
                    | raw <- [1 .. numFaces triangulation - 1]
                    , let face = FaceId (fromIntegral raw)
                    ]
              , refinementDomainInputFaceCount = numFaces triangulation
              , refinementDomainInputEdgeCount = numUndirectedEdges triangulation
              }
 where
  totalEdges = numUndirectedEdges triangulation
  validateInterfaceEdge edge@(UndirectedEdgeId raw)
    | toInteger raw >= toInteger totalEdges =
        Left (RefinementDomainInterfaceEdgeNotActive edge totalEdges)
    | otherwise = Right (fromIntegral raw)

  expectedInterface permitted =
    IntSet.fromList
      [ pair
      | pair <- [0 .. totalEdges - 1]
      , let edge = UndirectedEdgeId (fromIntegral pair)
            (forward, backward) = directedPair edge
            FaceId forwardFace = incidentFace triangulation forward
            FaceId backwardFace = incidentFace triangulation backward
            forwardInner = forwardFace /= 0
            backwardInner = backwardFace /= 0
            forwardPermitted = IntSet.member (fromIntegral forwardFace) permitted
            backwardPermitted = IntSet.member (fromIntegral backwardFace) permitted
      , forwardInner && backwardInner && forwardPermitted /= backwardPermitted
      ]

-- | Refine exactly one checked local section. Interface edges are installed as
-- transaction-local legalization barriers and removed before publication;
-- every protected face signature is then compared with the input witness.
refineWithinDomain
  :: (Point -> vertex)
  -> RefinementParameters
  -> Set.Set FaceId
  -> Set.Set UndirectedEdgeId
  -> Triangulation mode vertex directed undirected face
  -> Either BuildError (RefinementDomainResult mode vertex directed undirected face)
refineWithinDomain makeVertex parameters permittedFaces interfaceEdges triangulation = do
  validateDomainParameters parameters
  domain <- mkRefinementDomain permittedFaces interfaceEdges triangulation
  execution <-
    refineWithInitialSeed
      (RefineSeededFaces (refinementDomainPermittedFaces domain))
      (Just domain)
      makeVertex
      parameters
      triangulation
  let result = refinementExecutionResult execution
      receipt =
        buildRefinementReceipt
          domain
          triangulation
          (refinedTriangulation result)
          (refinementExecutionVisitedFaces execution)
          (refinementExecutionInterfaceBoundaryReads execution)
          (refinementExecutionBoundaryCrossingAttempts execution)
  validateProtectedFaces domain triangulation (refinedTriangulation result)
  case V.toList (refinementVisitedProtectedFaces receipt) of
    protected : _ -> Left (RefinementDomainWouldRewriteProtectedFace protected)
    [] ->
      Right
        RefinementDomainResult
          { refinementDomainResult = result
          , refinementDomainReceipt = receipt
          }

validateDomainParameters :: RefinementParameters -> Either BuildError ()
validateDomainParameters parameters
  | not (refinePreserveConvexHull parameters) =
      Left RefinementDomainRequiresConvexHullPreservation
  | not (refineKeepConstraintEdges parameters) =
      Left RefinementDomainRequiresConstraintPreservation
  | refineExcludeOuterFaces parameters =
      Left RefinementDomainForbidsOuterFaceExclusion
  | otherwise = Right ()

refineWithInitialSeed
  :: RefinementInitialSeed
  -> Maybe RefinementDomain
  -> (Point -> vertex)
  -> RefinementParameters
  -> Triangulation mode vertex directed undirected face
  -> Either BuildError (RefinementExecution mode vertex directed undirected face)
refineWithInitialSeed initialSeed domain makeVertex parameters triangulation = do
  validateRefinementParameters parameters
  case validateTopology triangulation of
    violation : _ -> Left (RefinementInputTopologyInvalid violation)
    [] -> pure ()
  let originalCount = numVertices triangulation
      budget = max 0 (fromMaybe (10 * max 1 originalCount) (refineMaxAdditionalVertices parameters))
      maximumVerticesInteger = toInteger originalCount + toInteger budget
      initialExcludedFaces =
        if refineExcludeOuterFaces parameters
          then
            IntSet.fromList
              [ fromIntegral raw
              | FaceId raw <-
                  facesAtEvenBarrierDepth
                    triangulation
                    (isConstraintEdge triangulation)
              ]
          else IntSet.empty
  let maximumVertices =
        if maximumVerticesInteger > toInteger (maxBound :: Int)
          then maxBound
          else fromInteger maximumVerticesInteger
  ensureCapacity maximumVertices
  runST $ do
      mutable <-
        thawTriangulationDense
          maximumVertices
          triangulation
      operation <- newOperationState (halfEdgeCapacity mutable)
      installInterfaceBarriers mutable domain
      outcome <-
        refineMutable
          makeVertex
          mutable
          operation
          parameters
          originalCount
          initialExcludedFaces
          initialSeed
          domain
      removeInterfaceBarriers mutable triangulation domain
      case outcome of
        Left failure -> pure (Left failure)
        Right (complete, added, excluded, visited, interfaceBoundaryReads, boundaryCrossingAttempts) -> do
          frozenOutcome <- freezeTriangulation mutable
          case frozenOutcome of
            Left obstruction -> pure (Left obstruction)
            Right frozen -> do
              stats <- freezeBuildStats operation
              pure
                ( Right
                    RefinementExecution
                      { refinementExecutionResult =
                          RefinementResult
                            { refinedTriangulation = frozen
                            , refinementStats = stats
                            , refinementAddedVertices = added
                            , refinementComplete = complete
                            , refinementExcludedFaces = V.fromList (map (FaceId . fromIntegral) excluded)
                            }
                      , refinementExecutionVisitedFaces = visited
                      , refinementExecutionInterfaceBoundaryReads = interfaceBoundaryReads
                      , refinementExecutionBoundaryCrossingAttempts = boundaryCrossingAttempts
                      }
                )

installInterfaceBarriers
  :: MutableDcel s vertex directed undirected face
  -> Maybe RefinementDomain
  -> ST s ()
installInterfaceBarriers mutable =
  traverse_
    (\pair -> setConstraint mutable (2 * pair) >> pure ())
    . maybe [] (IntSet.toAscList . refinementDomainInterfacePairs)

removeInterfaceBarriers
  :: MutableDcel s vertex directed undirected face
  -> Triangulation mode vertex directed undirected face
  -> Maybe RefinementDomain
  -> ST s ()
removeInterfaceBarriers mutable input =
  traverse_
    (\pair ->
       let edge = UndirectedEdgeId (fromIntegral pair)
        in if isConstraintEdge input edge
             then pure ()
             else clearConstraint mutable (2 * pair) >> pure ()
    )
    . maybe [] (IntSet.toAscList . refinementDomainInterfacePairs)

validateProtectedFaces
  :: RefinementDomain
  -> Triangulation mode vertex directed undirected face
  -> Triangulation mode vertex directed undirected face
  -> Either BuildError ()
validateProtectedFaces domain before after =
  traverse_ validateProtected protectedFaces
 where
  protectedFaces =
    [ FaceId (fromIntegral face)
    | face <- [1 .. refinementDomainInputFaceCount domain - 1]
    , IntSet.notMember face (refinementDomainPermittedFaces domain)
    ]
  validateProtected face
    | Map.lookup face (refinementDomainInputFaces domain)
        == Just (faceSignature before face)
        && Map.lookup face (refinementDomainInputFaces domain)
          == Just (faceSignature after face) = Right ()
    | otherwise = Left (RefinementDomainProtectedFaceChanged face)

faceSignature
  :: Triangulation mode vertex directed undirected face
  -> FaceId
  -> [Point]
faceSignature triangulation =
  sort . fmap (vertexPoint triangulation) . faceVertices triangulation

type EdgeSignature = (Point, Point, [FaceId])

edgeSignature
  :: Triangulation mode vertex directed undirected face
  -> UndirectedEdgeId
  -> EdgeSignature
edgeSignature triangulation edge =
  (min fromPoint toPoint, max fromPoint toPoint, sort [forwardFace, backwardFace])
 where
  (fromVertex, toVertex) = undirectedEndpoints triangulation edge
  fromPoint = vertexPoint triangulation fromVertex
  toPoint = vertexPoint triangulation toVertex
  (forward, backward) = directedPair edge
  forwardFace = incidentFace triangulation forward
  backwardFace = incidentFace triangulation backward

buildRefinementReceipt
  :: RefinementDomain
  -> Triangulation mode vertex directed undirected face
  -> Triangulation mode vertex directed undirected face
  -> [Int]
  -> Int
  -> Int
  -> RefinementReceipt
buildRefinementReceipt domain before after visited interfaceBoundaryReads boundaryCrossingAttempts =
  RefinementReceipt
    { refinementVisitedJoinFaces = V.fromList (fmap toFace visitedJoin)
    , refinementVisitedProtectedFaces = V.fromList (fmap toFace visitedProtected)
    , refinementCreatedFaces =
        V.fromList
          [ targetFace
          | face <- [1 .. numFaces after - 1]
          , let targetFace = toFace face
          , face >= numFaces before
              || faceSignature before targetFace /= faceSignature after targetFace
          ]
    , refinementTouchedEdges = V.fromList touched
    , refinementRemovedEdges = V.fromList removed
    , refinementInterfaceBoundaryReads = interfaceBoundaryReads
    , refinementAttemptedBoundaryCrossings = boundaryCrossingAttempts
    }
 where
  permitted = refinementDomainPermittedFaces domain
  (visitedProtected, visitedJoin) =
    foldr
      (\face (protected, join) ->
         if IntSet.member face permitted || face >= refinementDomainInputFaceCount domain
           then (protected, face : join)
           else (face : protected, join)
      )
      ([], [])
      visited
  toFace = FaceId . fromIntegral
  beforeEdges = numUndirectedEdges before
  afterEdges = numUndirectedEdges after
  touched =
    [ edge
    | raw <- [0 .. afterEdges - 1]
    , let edge = UndirectedEdgeId (fromIntegral raw)
    , raw >= beforeEdges || edgeSignature before edge /= edgeSignature after edge
    ]
  removed =
    [ edge
    | raw <- [0 .. beforeEdges - 1]
    , let edge = UndirectedEdgeId (fromIntegral raw)
    , raw >= afterEdges || edgeSignature before edge /= edgeSignature after edge
    ]

-- | Refuse outer or absent faces rather than silently treating an invalid
-- topology witness as an empty repair. Face zero is the outer face and has no
-- active refinement equation.
validateSeedFaces
  :: Triangulation mode vertex directed undirected face
  -> Set.Set FaceId
  -> Either BuildError IntSet.IntSet
validateSeedFaces triangulation requested =
  IntSet.fromList <$> traverse validateFace (Set.toAscList requested)
 where
  totalFaces = numFaces triangulation

  validateFace face@(FaceId raw)
    | raw == 0 || toInteger raw >= toInteger totalFaces =
        Left (RefinementSeedFaceNotActive face totalFaces)
    | otherwise = Right (fromIntegral raw)

-- | Refuse parameters no mesh can satisfy.
validateRefinementParameters :: RefinementParameters -> Either BuildError ()
validateRefinementParameters RefinementParameters{refineMaxAdditionalVertices, refineMinArea, refineMaxArea, refineMaxRadiusEdgeRatio} = do
  case refineMaxAdditionalVertices of
    Just value
      | value < 0 ->
          Left (RefinementMaximumAdditionalVerticesNegative value)
    _ -> Right ()
  validateOptionalRefinementParameter
    RefinementMinimumAreaNotFinite
    RefinementMinimumAreaNegative
    (>= 0)
    refineMinArea
  validateOptionalRefinementParameter
    RefinementMaximumAreaNotFinite
    RefinementMaximumAreaNotPositive
    (> 0)
    refineMaxArea
  validateOptionalRefinementParameter
    RefinementMaximumRadiusEdgeRatioNotFinite
    RefinementMaximumRadiusEdgeRatioNotPositive
    (> 0)
    refineMaxRadiusEdgeRatio
  case (refineMinArea, refineMaxArea) of
    (Just minimumArea, Just maximumArea)
      | minimumArea > maximumArea ->
          Left
            ( RefinementMinimumAreaExceedsMaximum
                minimumArea
                maximumArea
            )
    _ -> Right ()

validateOptionalRefinementParameter
  :: (NonFiniteValue -> BuildError)
  -> (Double -> BuildError)
  -> (Double -> Bool)
  -> Maybe Double
  -> Either BuildError ()
validateOptionalRefinementParameter nonFinite outsideRange predicate value =
  case value of
    Nothing -> Right ()
    Just number ->
      case classifyNonFinite number of
        Just nonFiniteValue ->
          Left (nonFinite nonFiniteValue)
        Nothing
          | predicate number -> Right ()
          | otherwise ->
              Left (outsideRange number)