packages feed

moonlight-triangulation-1.0.0.0: src-dual/Moonlight/Triangulation/Voronoi.hs

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}

-- | The Voronoi dual, addressed by its own handle family: every cell, edge and
-- vertex is a view of the Delaunay mesh rather than a second structure.
module Moonlight.Triangulation.Voronoi
  ( VoronoiFaceId (..)
  , DirectedVoronoiEdgeId (..)
  , UndirectedVoronoiEdgeId (..)
  , VoronoiVertexId (..)
  , VoronoiEdgeGeometry (..)
  , voronoiFaces
  , directedVoronoiEdges
  , undirectedVoronoiEdges
  , asDelaunayDirectedEdge
  , asDelaunayUndirectedEdge
  , asDirectedVoronoiEdge
  , asUndirectedVoronoiEdge
  , reverseVoronoiEdge
  , voronoiNext
  , voronoiPrevious
  , voronoiFrom
  , voronoiTo
  , voronoiIncidentFace
  , voronoiFaceSite
  , voronoiFaceAdjacentEdges
  , voronoiVertexPosition
  , voronoiVertexOutgoingEdges
  , voronoiDirectionVector
  , voronoiEdgeGeometry
  , faceCircumcenter
  ) where

import Control.DeepSeq (NFData)
import Moonlight.Triangulation.Dcel
import Moonlight.Triangulation.Handles.HandleDefs
import Moonlight.Triangulation.Handles.Iterators.FixedIterators (directedEdges, undirectedEdges, vertices)
import Moonlight.Triangulation.Math
import Moonlight.Triangulation.Types

-- | A Voronoi face, identified by its primal site.
--
-- The dual uses the exact same fixed-index space as the primal DCEL. These
-- newtypes add semantic separation without allocating or owning any dual mesh.
newtype VoronoiFaceId = VoronoiFaceId { unVoronoiFaceId :: VertexId }
  deriving stock (Show)
  deriving newtype (Eq, Ord, NFData)

-- | An oriented Voronoi edge, identified by its primal directed edge.
newtype DirectedVoronoiEdgeId = DirectedVoronoiEdgeId { unDirectedVoronoiEdgeId :: DirectedEdgeId }
  deriving stock (Show)
  deriving newtype (Eq, Ord, NFData)

-- | An unoriented Voronoi edge, identified by its primal undirected edge.
newtype UndirectedVoronoiEdgeId = UndirectedVoronoiEdgeId { unUndirectedVoronoiEdgeId :: UndirectedEdgeId }
  deriving stock (Show)
  deriving newtype (Eq, Ord, NFData)

-- | A finite dual vertex or an ideal endpoint of an unbounded dual edge.
data VoronoiVertexId
  = InnerVoronoiVertex !FaceId
  | OuterVoronoiVertex !DirectedVoronoiEdgeId
  deriving stock (Eq, Ord, Show)

-- | Finite segment, half-infinite ray, or infinite line in the dual.
data VoronoiEdgeGeometry
  = VoronoiSegment !(Point) !(Point)
  | VoronoiRay !(Point) !(Point)
  | VoronoiLine !(Point) !(Point)
  deriving stock (Eq, Ord, Show)

-- | Voronoi faces in primal vertex-handle order.
voronoiFaces :: Triangulation mode vertex directed undirected face -> [VoronoiFaceId]
voronoiFaces triangulation = map VoronoiFaceId (vertices triangulation)
{-# INLINE voronoiFaces #-}

-- | Directed Voronoi edges in primal directed-edge order.
--
-- Every primal directed edge is one directed edge in the dual. Boundary
-- handles naturally represent half-infinite edges through OuterVoronoiVertex.
directedVoronoiEdges :: Triangulation mode vertex directed undirected face -> [DirectedVoronoiEdgeId]
directedVoronoiEdges triangulation = map DirectedVoronoiEdgeId (directedEdges triangulation)
{-# INLINE directedVoronoiEdges #-}

-- | Undirected Voronoi edges in primal undirected-edge order.
undirectedVoronoiEdges :: Triangulation mode vertex directed undirected face -> [UndirectedVoronoiEdgeId]
undirectedVoronoiEdges triangulation = map UndirectedVoronoiEdgeId (undirectedEdges triangulation)
{-# INLINE undirectedVoronoiEdges #-}

-- | Recover the primal directed edge.
asDelaunayDirectedEdge :: DirectedVoronoiEdgeId -> DirectedEdgeId
asDelaunayDirectedEdge = unDirectedVoronoiEdgeId
{-# INLINE asDelaunayDirectedEdge #-}

-- | Recover the primal undirected edge.
asDelaunayUndirectedEdge :: UndirectedVoronoiEdgeId -> UndirectedEdgeId
asDelaunayUndirectedEdge = unUndirectedVoronoiEdgeId
{-# INLINE asDelaunayUndirectedEdge #-}

-- | View a primal directed edge in the dual handle family.
asDirectedVoronoiEdge :: DirectedEdgeId -> DirectedVoronoiEdgeId
asDirectedVoronoiEdge = DirectedVoronoiEdgeId
{-# INLINE asDirectedVoronoiEdge #-}

-- | View a primal undirected edge in the dual handle family.
asUndirectedVoronoiEdge :: UndirectedEdgeId -> UndirectedVoronoiEdgeId
asUndirectedVoronoiEdge = UndirectedVoronoiEdgeId
{-# INLINE asUndirectedVoronoiEdge #-}

-- | Reverse a directed Voronoi edge.
reverseVoronoiEdge :: DirectedVoronoiEdgeId -> DirectedVoronoiEdgeId
reverseVoronoiEdge (DirectedVoronoiEdgeId edge) = DirectedVoronoiEdgeId (reverseEdge edge)
{-# INLINE reverseVoronoiEdge #-}

-- | Next dual edge around the incident Voronoi face.
--
-- Dual next/previous rotate around the primal origin site.
voronoiNext
  :: Triangulation mode vertex directed undirected face
  -> DirectedVoronoiEdgeId
  -> DirectedVoronoiEdgeId
voronoiNext triangulation (DirectedVoronoiEdgeId edge) =
  DirectedVoronoiEdgeId (counterClockwise triangulation edge)
{-# INLINE voronoiNext #-}

-- | Previous dual edge around the incident Voronoi face.
voronoiPrevious
  :: Triangulation mode vertex directed undirected face
  -> DirectedVoronoiEdgeId
  -> DirectedVoronoiEdgeId
voronoiPrevious triangulation (DirectedVoronoiEdgeId edge) =
  DirectedVoronoiEdgeId (clockwise triangulation edge)
{-# INLINE voronoiPrevious #-}

-- | Origin dual vertex, including ideal endpoints on the hull.
voronoiFrom
  :: Triangulation mode vertex directed undirected face
  -> DirectedVoronoiEdgeId
  -> VoronoiVertexId
voronoiFrom triangulation edge@(DirectedVoronoiEdgeId primal)
  | face == outerFace = OuterVoronoiVertex edge
  | otherwise = InnerVoronoiVertex face
 where
  face = incidentFace triangulation primal
{-# INLINE voronoiFrom #-}

-- | Destination dual vertex, including ideal endpoints on the hull.
voronoiTo
  :: Triangulation mode vertex directed undirected face
  -> DirectedVoronoiEdgeId
  -> VoronoiVertexId
voronoiTo triangulation = voronoiFrom triangulation . reverseVoronoiEdge
{-# INLINE voronoiTo #-}

-- | Voronoi face to the left of a directed dual edge.
voronoiIncidentFace
  :: Triangulation mode vertex directed undirected face
  -> DirectedVoronoiEdgeId
  -> VoronoiFaceId
voronoiIncidentFace triangulation (DirectedVoronoiEdgeId edge) =
  VoronoiFaceId (origin triangulation edge)
{-# INLINE voronoiIncidentFace #-}

-- | Primal site represented by a Voronoi face.
voronoiFaceSite :: VoronoiFaceId -> VertexId
voronoiFaceSite = unVoronoiFaceId
{-# INLINE voronoiFaceSite #-}

-- | Directed boundary of a Voronoi face.
voronoiFaceAdjacentEdges
  :: Triangulation mode vertex directed undirected face
  -> VoronoiFaceId
  -> [DirectedVoronoiEdgeId]
voronoiFaceAdjacentEdges triangulation (VoronoiFaceId site) =
  map DirectedVoronoiEdgeId (vertexOutgoingEdges triangulation site)
{-# INLINE voronoiFaceAdjacentEdges #-}

-- | Position of a finite dual vertex; ideal hull endpoints have no position.
--
-- A caller reaches this through 'voronoiFrom' or 'voronoiTo', which build the
-- endpoint sum immediately before it is taken apart again. Only an unfolding at
-- the consumer lets the two meet, so the constructor never reaches the heap.
voronoiVertexPosition
  :: Triangulation mode vertex directed undirected face
  -> VoronoiVertexId
  -> Maybe (Point)
voronoiVertexPosition triangulation vertex = case vertex of
  InnerVoronoiVertex face -> faceCircumcenter triangulation face
  OuterVoronoiVertex _ -> Nothing
{-# INLINE voronoiVertexPosition #-}

-- | Directed dual edges leaving a finite dual vertex.
voronoiVertexOutgoingEdges
  :: Triangulation mode vertex directed undirected face
  -> VoronoiVertexId
  -> Maybe [DirectedVoronoiEdgeId]
voronoiVertexOutgoingEdges triangulation vertex = case vertex of
  OuterVoronoiVertex _ -> Nothing
  InnerVoronoiVertex face ->
    map DirectedVoronoiEdgeId . faceDirectedEdges triangulation <$> nonOuter face
 where
  nonOuter face
    | face == outerFace = Nothing
    | otherwise = Just face
{-# INLINE voronoiVertexOutgoingEdges #-}

-- | Unit direction of the dual edge orthogonal to a primal edge.
voronoiDirectionVector
  :: Triangulation mode vertex directed undirected face
  -> DirectedVoronoiEdgeId
  -> Point
voronoiDirectionVector triangulation (DirectedVoronoiEdgeId edge) =
  case vertexPoint triangulation (origin triangulation edge) of
    Point ax ay -> case vertexPoint triangulation (destination triangulation edge) of
      Point bx by -> Point (ay - by) (bx - ax)

-- | Circumcenter of a bounded primal face.
--
-- The circumcentre stands on the absolute vertex positions, which is a
-- different value from the query-relative one the Sibson pipeline caches: that
-- one rescales the differences it was handed, so translating the inputs moves
-- the rounding and the two do not differ by the translation. The interpolation
-- workspace's plane therefore cannot serve this function, and the repetition
-- here is across calls rather than within one — a sweep recomputes each face
-- once per incident dual edge, while a single call touches two distinct faces.
-- That threefold repetition is what a dual which is a view rather than a
-- structure costs, and the referent pays it identically, so no cache is owed.
faceCircumcenter
  :: Triangulation mode vertex directed undirected face
  -> FaceId
  -> Maybe (Point)
faceCircumcenter triangulation face
  | face == outerFace = Nothing
  | otherwise = case adjacentEdge triangulation face of
      Nothing -> Nothing
      Just e0 ->
        let !e1 = next triangulation e0
            !e2 = next triangulation e1
         in if next triangulation e2 /= e0
              then Nothing
              else
                circumcenter
                  (vertexPoint triangulation (origin triangulation e0))
                  (vertexPoint triangulation (origin triangulation e1))
                  (vertexPoint triangulation (origin triangulation e2))
{-# INLINE faceCircumcenter #-}

-- | Geometric realization of one dual edge.
--
-- The endpoint classification 'voronoiFrom' and 'voronoiTo' publish is two face
-- reads and two comparisons; taken through those observations it is also two
-- sum values built and immediately scrutinized. The geometry reads the faces
-- itself so that the classification stays in registers.
voronoiEdgeGeometry
  :: Triangulation mode vertex directed undirected face
  -> DirectedVoronoiEdgeId
  -> Maybe (VoronoiEdgeGeometry)
voronoiEdgeGeometry triangulation edge@(DirectedVoronoiEdgeId primal)
  | innerFrom, innerTo =
      VoronoiSegment <$> faceCircumcenter triangulation fromFace <*> faceCircumcenter triangulation toFace
  | innerFrom = do
      start <- faceCircumcenter triangulation fromFace
      pure (VoronoiRay start (normalize (voronoiDirectionVector triangulation edge)))
  | innerTo = do
      end <- faceCircumcenter triangulation toFace
      pure (VoronoiRay end (normalize (negatePoint (voronoiDirectionVector triangulation edge))))
  | otherwise =
      let !center = midpoint
            (vertexPoint triangulation (origin triangulation primal))
            (vertexPoint triangulation (destination triangulation primal))
       in Just (VoronoiLine center (normalize (voronoiDirectionVector triangulation edge)))
 where
  !fromFace = incidentFace triangulation primal
  !toFace = incidentFace triangulation (reverseEdge primal)
  !innerFrom = fromFace /= outerFace
  !innerTo = toFace /= outerFace

normalize :: Point -> Point
normalize (Point x y)
  | scale == 0 = Point 0 0
  | otherwise =
      let !scaledX = x / scale
          !scaledY = y / scale
          !length' = sqrt (scaledX * scaledX + scaledY * scaledY)
       in Point (scaledX / length') (scaledY / length')
 where
  !scale = max (abs x) (abs y)

negatePoint :: Point -> Point
negatePoint (Point x y) = Point (-x) (-y)

{-# INLINE normalize #-}
{-# INLINE negatePoint #-}