packages feed

moonlight-triangulation-1.4.0.4: src-public/Moonlight/Triangulation/Internal/RegularTriangulation.hs

{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}

-- | Exact upper-hull descent for affine generators. The public power owner
-- supplies admitted, distinct slopes; this module returns only topology and
-- exact dual geometry.
module Moonlight.Triangulation.Internal.RegularTriangulation
  ( ExactPowerGenerator (..)
  , DistinctSlopeGenerators (..)
  , RegularGeneratorDisposition (..)
  , GeneratorRegularFace (..)
  , GeneratorDualGeometry (..)
  , GeneratorRegularEdge (..)
  , GeneratorRegularTopology (..)
  , GeneratorRegularReceipt (..)
  , RegularTopologyError (..)
  , regularGeneratorTopology
  , exactGeneratorAxis
  ) where

import Control.DeepSeq (NFData)
import Data.Bifunctor (first)
import qualified Data.Foldable as Foldable
import qualified Data.IntMap.Strict as IntMap
import qualified Data.IntSet as IntSet
import qualified Data.List as List
import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.List.NonEmpty as NonEmpty
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Data.Maybe (mapMaybe)
import Data.Set (Set)
import qualified Data.Set as Set
import GHC.Generics (Generic)
import Moonlight.Triangulation.Exact
  ( ExactAffineLine
  , ExactGeometryError
  , ExactHalfPlaneError
  , ExactIntersectionError
  , ExactPoint
  , ExactRay
  , ExactSegment
  , ExactVector (..)
  , exactAffineLine
  , exactAffineLineCoefficients
  , exactAffineLineIntersection
  , exactPoint
  , exactPointCoordinates
  , exactRay
  , exactSegment
  )
import Moonlight.Triangulation.Internal.ExactRational
  ( ExactRational )
import Moonlight.Triangulation.Internal.BoundaryCycle
  ( consecutivePairs )
import Moonlight.Triangulation.Internal.Minkowski.Convex
  ( convexHullPolygon )
import Moonlight.Triangulation.Minkowski
  ( MinkowskiError
  , convexPolygonPoints
  )

-- | Exact affine score @constant + xCoefficient*x + yCoefficient*y@.
data ExactPowerGenerator label = ExactPowerGenerator
  { exactPowerGeneratorLabel :: !label
  , exactPowerGeneratorXCoefficient :: !ExactRational
  , exactPowerGeneratorYCoefficient :: !ExactRational
  , exactPowerGeneratorConstant :: !ExactRational
  }
  deriving stock (Eq, Ord, Show, Generic)
  deriving anyclass (NFData)

-- | Package-internal witness that coincident affine slopes were resolved once.
newtype DistinctSlopeGenerators label =
  DistinctSlopeGenerators (NonEmpty (ExactPowerGenerator label))

data RegularGeneratorDisposition
  = RegularGeneratorVisible
  | RegularGeneratorLowerDimensional
  | RegularGeneratorHidden
  deriving stock (Eq, Ord, Show, Generic)
  deriving anyclass (NFData)

data GeneratorRegularFace label = GeneratorRegularFace
  { generatorRegularFaceFirst :: !label
  , generatorRegularFaceSecond :: !label
  , generatorRegularFaceThird :: !label
  , generatorRegularFaceDualPoint :: !ExactPoint
  }
  deriving stock (Eq, Ord, Show, Generic)
  deriving anyclass (NFData)

data GeneratorDualGeometry
  = GeneratorDualSegment !ExactSegment
  | GeneratorDualRay !ExactRay
  | GeneratorDualLine !ExactAffineLine
  | GeneratorDualCollapsed !ExactPoint
  deriving stock (Eq, Ord, Show, Generic)
  deriving anyclass (NFData)

data GeneratorRegularEdge label = GeneratorRegularEdge
  { generatorRegularEdgeFirst :: !(ExactPowerGenerator label)
  , generatorRegularEdgeSecond :: !(ExactPowerGenerator label)
  , generatorRegularEdgeDual :: !GeneratorDualGeometry
  }
  deriving stock (Eq, Ord, Show, Generic)
  deriving anyclass (NFData)

data GeneratorRegularTopology label = GeneratorRegularTopology
  { generatorRegularDispositions :: !(NonEmpty (ExactPowerGenerator label, RegularGeneratorDisposition))
  , generatorRegularFaces :: ![GeneratorRegularFace label]
  , generatorRegularEdges :: ![GeneratorRegularEdge label]
  , generatorRegularReceipt :: !GeneratorRegularReceipt
  }
  deriving stock (Eq, Show, Generic)
  deriving anyclass (NFData)

data GeneratorRegularReceipt = GeneratorRegularReceipt
  { generatorRegularInputSites :: !Int
  , generatorRegularVisibleSites :: !Int
  , generatorRegularLowerDimensionalSites :: !Int
  , generatorRegularHiddenSites :: !Int
  , generatorRegularFaceCount :: !Int
  , generatorRegularEdgeCount :: !Int
  , generatorRegularPeakHullFaces :: !Int
  }
  deriving stock (Eq, Ord, Show, Generic)
  deriving anyclass (NFData)

data RegularTopologyError label
  = RegularTopologySlopeHullInvalid !MinkowskiError
  | RegularTopologySlopeVertexMissing !ExactPoint
  | RegularTopologyAxisInvalid !label !label !ExactHalfPlaneError
  | RegularTopologyDualVertexInvalid !label !label !label !ExactIntersectionError
  | RegularTopologyDualSegmentInvalid !label !label !ExactGeometryError
  | RegularTopologyDualRayInvalid !label !label !ExactGeometryError
  | RegularTopologyFlatBoundary !label !label !label
  | RegularTopologyNonManifoldEdge !label !label !Int
  deriving stock (Eq, Show, Generic)
  deriving anyclass (NFData)

-- | Construct exact regular topology from distinct affine slopes. Full-rank
-- input uses one conflict graph; collinear slopes use the one-dimensional
-- upper chain; coplanar lifted input reuses the exact planar hull owner.
regularGeneratorTopology
  :: Ord label
  => DistinctSlopeGenerators label
  -> Either (RegularTopologyError label) (GeneratorRegularTopology label)
regularGeneratorTopology (DistinctSlopeGenerators generators) =
  let points = admittedLiftedPoints generators
   in case points of
        singleton :| [] ->
          pure (topologyFromParts points (Set.singleton singleton) Set.empty [] [] 0)
        firstPoint :| secondPoint : remaining ->
          case List.find (not . slopeCollinear firstPoint secondPoint) remaining of
            Nothing -> collinearSlopeTopology points firstPoint secondPoint
            Just thirdPoint ->
              case List.find (not . liftedCoplanar firstPoint secondPoint thirdPoint) remaining of
                Nothing -> coplanarLiftedTopology points
                Just fourthPoint -> fullRankTopology points firstPoint secondPoint thirdPoint fourthPoint

-- | The equality line of two distinct-slope affine generators.
exactGeneratorAxis
  :: ExactPowerGenerator label
  -> ExactPowerGenerator label
  -> Either ExactHalfPlaneError ExactAffineLine
exactGeneratorAxis firstGenerator secondGenerator =
  exactAffineLine
    ( exactPowerGeneratorXCoefficient firstGenerator
        - exactPowerGeneratorXCoefficient secondGenerator
    )
    ( exactPowerGeneratorYCoefficient firstGenerator
        - exactPowerGeneratorYCoefficient secondGenerator
    )
    ( exactPowerGeneratorConstant firstGenerator
        - exactPowerGeneratorConstant secondGenerator
    )

data LiftedPoint label = LiftedPoint
  { liftedPointId :: !Int
  , liftedPointGenerator :: !(ExactPowerGenerator label)
  }

instance Eq (LiftedPoint label) where
  left == right = liftedPointId left == liftedPointId right

instance Ord (LiftedPoint label) where
  compare left right = compare (liftedPointId left) (liftedPointId right)

admittedLiftedPoints
  :: Ord label
  => NonEmpty (ExactPowerGenerator label)
  -> NonEmpty (LiftedPoint label)
admittedLiftedPoints generators =
  let ordered = NonEmpty.sortWith exactPowerGeneratorLabel generators
   in NonEmpty.zipWith
        (\identifier generator -> LiftedPoint identifier generator)
        (0 :| [1 ..])
        ordered

liftedCoordinates
  :: LiftedPoint label
  -> (ExactRational, ExactRational, ExactRational)
liftedCoordinates point =
  let generator = liftedPointGenerator point
   in ( exactPowerGeneratorXCoefficient generator
      , exactPowerGeneratorYCoefficient generator
      , exactPowerGeneratorConstant generator
      )

slopePoint :: LiftedPoint label -> ExactPoint
slopePoint point =
  let (coordinateX, coordinateY, _) = liftedCoordinates point
   in exactPoint coordinateX coordinateY

slopeCollinear
  :: LiftedPoint label
  -> LiftedPoint label
  -> LiftedPoint label
  -> Bool
slopeCollinear firstPoint secondPoint thirdPoint =
  slopeOrientation firstPoint secondPoint thirdPoint == 0

slopeOrientation
  :: LiftedPoint label
  -> LiftedPoint label
  -> LiftedPoint label
  -> ExactRational
slopeOrientation firstPoint secondPoint thirdPoint =
  let (firstX, firstY, _) = liftedCoordinates firstPoint
      (secondX, secondY, _) = liftedCoordinates secondPoint
      (thirdX, thirdY, _) = liftedCoordinates thirdPoint
   in (secondX - firstX) * (thirdY - firstY)
        - (secondY - firstY) * (thirdX - firstX)

liftedCoplanar
  :: LiftedPoint label
  -> LiftedPoint label
  -> LiftedPoint label
  -> LiftedPoint label
  -> Bool
liftedCoplanar firstPoint secondPoint thirdPoint fourthPoint =
  liftedVolume firstPoint secondPoint thirdPoint fourthPoint == 0

liftedVolume
  :: LiftedPoint label
  -> LiftedPoint label
  -> LiftedPoint label
  -> LiftedPoint label
  -> ExactRational
liftedVolume firstPoint secondPoint thirdPoint fourthPoint =
  let (firstX, firstY, firstZ) = liftedCoordinates firstPoint
      (secondX, secondY, secondZ) = liftedCoordinates secondPoint
      (thirdX, thirdY, thirdZ) = liftedCoordinates thirdPoint
      (fourthX, fourthY, fourthZ) = liftedCoordinates fourthPoint
      secondX' = secondX - firstX
      secondY' = secondY - firstY
      secondZ' = secondZ - firstZ
      thirdX' = thirdX - firstX
      thirdY' = thirdY - firstY
      thirdZ' = thirdZ - firstZ
      fourthX' = fourthX - firstX
      fourthY' = fourthY - firstY
      fourthZ' = fourthZ - firstZ
   in secondX' * (thirdY' * fourthZ' - thirdZ' * fourthY')
        - secondY' * (thirdX' * fourthZ' - thirdZ' * fourthX')
        + secondZ' * (thirdX' * fourthY' - thirdY' * fourthX')

data HullFace label = HullFace
  { hullFaceId :: !Int
  , hullFaceFirst :: !(LiftedPoint label)
  , hullFaceSecond :: !(LiftedPoint label)
  , hullFaceThird :: !(LiftedPoint label)
  , hullFaceOutside :: !(Set (LiftedPoint label))
  , hullFaceCoplanar :: !(Set (LiftedPoint label))
  }

type HullEdge = (Int, Int)

data HullState label = HullState
  { hullFaces :: !(IntMap.IntMap (HullFace label))
  , hullEdgeFaces :: !(Map HullEdge IntSet.IntSet)
  , hullConflictFaces :: !IntSet.IntSet
  , hullNextFaceId :: !Int
  , hullPeakFaceCount :: !Int
  }

fullRankTopology
  :: NonEmpty (LiftedPoint label)
  -> LiftedPoint label
  -> LiftedPoint label
  -> LiftedPoint label
  -> LiftedPoint label
  -> Either (RegularTopologyError label) (GeneratorRegularTopology label)
fullRankTopology points firstPoint secondPoint thirdPoint fourthPoint = do
  let interior = liftedInteriorSum firstPoint secondPoint thirdPoint fourthPoint
      simplex = Set.fromList [firstPoint, secondPoint, thirdPoint, fourthPoint]
      initialFaces =
        zipWith
          (orientedHullFace interior)
          [0 ..]
          [ (firstPoint, secondPoint, thirdPoint)
          , (firstPoint, fourthPoint, secondPoint)
          , (firstPoint, thirdPoint, fourthPoint)
          , (secondPoint, fourthPoint, thirdPoint)
          ]
      initialState =
        List.foldl'
          insertHullFace
          (HullState IntMap.empty Map.empty IntSet.empty 4 4)
          initialFaces
      seededState =
        Foldable.foldl'
          (flip assignPointToHull)
          initialState
          (filter (`Set.notMember` simplex) (NonEmpty.toList points))
      finalState = descendHull interior seededState
      upperFaces = filter ((> 0) . hullFaceNormalZ) (IntMap.elems (hullFaces finalState))
  (canonicalFaces, visible, lowerDimensional) <- canonicalUpperFaces upperFaces
  (faces, edges) <- topologyFromHullFaces canonicalFaces
  pure
    ( topologyFromParts
        points
        visible
        lowerDimensional
        faces
        edges
        (hullPeakFaceCount finalState)
    )

liftedInteriorSum
  :: LiftedPoint label
  -> LiftedPoint label
  -> LiftedPoint label
  -> LiftedPoint label
  -> (ExactRational, ExactRational, ExactRational)
liftedInteriorSum firstPoint secondPoint thirdPoint fourthPoint =
  let (firstX, firstY, firstZ) = liftedCoordinates firstPoint
      (secondX, secondY, secondZ) = liftedCoordinates secondPoint
      (thirdX, thirdY, thirdZ) = liftedCoordinates thirdPoint
      (fourthX, fourthY, fourthZ) = liftedCoordinates fourthPoint
   in ( firstX + secondX + thirdX + fourthX
      , firstY + secondY + thirdY + fourthY
      , firstZ + secondZ + thirdZ + fourthZ
      )

orientedHullFace
  :: (ExactRational, ExactRational, ExactRational)
  -> Int
  -> (LiftedPoint label, LiftedPoint label, LiftedPoint label)
  -> HullFace label
orientedHullFace interior identifier (firstPoint, secondPoint, thirdPoint) =
  let provisional = HullFace identifier firstPoint secondPoint thirdPoint Set.empty Set.empty
   in if hullFaceVolumeAtInterior interior provisional < 0
        then provisional
        else HullFace identifier firstPoint thirdPoint secondPoint Set.empty Set.empty

hullFaceVolumeAtInterior
  :: (ExactRational, ExactRational, ExactRational)
  -> HullFace label
  -> ExactRational
hullFaceVolumeAtInterior (sumX, sumY, sumZ) face =
  let (firstX, firstY, firstZ) = liftedCoordinates (hullFaceFirst face)
      (secondX, secondY, secondZ) = liftedCoordinates (hullFaceSecond face)
      (thirdX, thirdY, thirdZ) = liftedCoordinates (hullFaceThird face)
      secondX' = secondX - firstX
      secondY' = secondY - firstY
      secondZ' = secondZ - firstZ
      thirdX' = thirdX - firstX
      thirdY' = thirdY - firstY
      thirdZ' = thirdZ - firstZ
      interiorX' = sumX - 4 * firstX
      interiorY' = sumY - 4 * firstY
      interiorZ' = sumZ - 4 * firstZ
   in secondX' * (thirdY' * interiorZ' - thirdZ' * interiorY')
        - secondY' * (thirdX' * interiorZ' - thirdZ' * interiorX')
        + secondZ' * (thirdX' * interiorY' - thirdY' * interiorX')

liftedVolumeAtFace :: HullFace label -> LiftedPoint label -> ExactRational
liftedVolumeAtFace face =
  liftedVolume
    (hullFaceFirst face)
    (hullFaceSecond face)
    (hullFaceThird face)

hullFaceNormalZ :: HullFace label -> ExactRational
hullFaceNormalZ face =
  slopeOrientation
    (hullFaceFirst face)
    (hullFaceSecond face)
    (hullFaceThird face)

hullFacePoints :: HullFace label -> [LiftedPoint label]
hullFacePoints face =
  [hullFaceFirst face, hullFaceSecond face, hullFaceThird face]

hullFaceDirectedEdges
  :: HullFace label
  -> [(LiftedPoint label, LiftedPoint label)]
hullFaceDirectedEdges face =
  [ (hullFaceFirst face, hullFaceSecond face)
  , (hullFaceSecond face, hullFaceThird face)
  , (hullFaceThird face, hullFaceFirst face)
  ]

hullEdgeKey :: LiftedPoint label -> LiftedPoint label -> HullEdge
hullEdgeKey firstPoint secondPoint =
  let firstId = liftedPointId firstPoint
      secondId = liftedPointId secondPoint
   in if firstId <= secondId then (firstId, secondId) else (secondId, firstId)

insertHullFace :: HullState label -> HullFace label -> HullState label
insertHullFace state face =
  state
    { hullFaces = IntMap.insert (hullFaceId face) face (hullFaces state)
    , hullEdgeFaces =
        List.foldl'
          (\incidence (fromPoint, toPoint) ->
             Map.insertWith
               IntSet.union
               (hullEdgeKey fromPoint toPoint)
               (IntSet.singleton (hullFaceId face))
               incidence)
          (hullEdgeFaces state)
          (hullFaceDirectedEdges face)
    , hullPeakFaceCount = max (hullPeakFaceCount state) (IntMap.size (hullFaces state) + 1)
    }

assignPointToHull :: LiftedPoint label -> HullState label -> HullState label
assignPointToHull point state =
  attachSelectedPoint point state
    (selectHullFace point (IntMap.elems (hullFaces state)))

data HullFaceSelection label
  = HullFaceInterior
  | HullFaceCoplanar !(HullFace label)
  | HullFaceVisible !(HullFace label) !ExactRational

selectHullFace
  :: LiftedPoint label
  -> [HullFace label]
  -> HullFaceSelection label
selectHullFace point = List.foldl' choose HullFaceInterior
 where
  choose selected face =
    let volume = liftedVolumeAtFace face point
     in case compare volume 0 of
          LT -> selected
          EQ ->
            case selected of
              HullFaceInterior -> HullFaceCoplanar face
              _ -> selected
          GT ->
            case selected of
              HullFaceVisible current currentVolume
                | volume < currentVolume
                    || (volume == currentVolume && hullFaceId current < hullFaceId face) ->
                    selected
              _ -> HullFaceVisible face volume

attachSelectedPoint
  :: LiftedPoint label
  -> HullState label
  -> HullFaceSelection label
  -> HullState label
attachSelectedPoint point state selection =
  case selection of
    HullFaceInterior -> state
    HullFaceCoplanar face ->
      state
        { hullFaces =
            IntMap.adjust
              (\selected -> selected{hullFaceCoplanar = Set.insert point (hullFaceCoplanar selected)})
              (hullFaceId face)
              (hullFaces state)
        }
    HullFaceVisible face _ ->
      state
        { hullFaces =
            IntMap.adjust
              (\selected -> selected{hullFaceOutside = Set.insert point (hullFaceOutside selected)})
              (hullFaceId face)
              (hullFaces state)
        , hullConflictFaces = IntSet.insert (hullFaceId face) (hullConflictFaces state)
        }

descendHull
  :: (ExactRational, ExactRational, ExactRational)
  -> HullState label
  -> HullState label
descendHull interior state =
  case nextHullExpansion state of
    Nothing -> state
    Just (seedFace, apex) ->
      let visible = visibleHullPatch state apex (IntSet.singleton (hullFaceId seedFace)) [hullFaceId seedFace]
          visibleFaces =
            mapMaybe (`IntMap.lookup` hullFaces state) (IntSet.toAscList visible)
          horizon = hullHorizon state visible visibleFaces
          candidates =
            Set.difference
              ( Set.delete apex
                  ( Set.unions
                      [ Set.unions
                          [ hullFaceOutside face
                          , hullFaceCoplanar face
                          , Set.fromList (hullFacePoints face)
                          ]
                      | face <- visibleFaces
                      ]
                  )
              )
              ( Set.fromList
                  [ point
                  | (firstPoint, secondPoint) <- Map.elems horizon
                  , point <- [firstPoint, secondPoint]
                  ]
              )
          withoutVisible = removeHullFaces visibleFaces state
          (withHorizon, newFaceIds) =
            Map.foldl'
              (insertHorizonFace interior apex)
              (withoutVisible, [])
              horizon
          redistributed =
            Foldable.foldl'
              (assignPointToFaces newFaceIds)
              withHorizon
              candidates
       in descendHull interior redistributed

nextHullExpansion
  :: HullState label
  -> Maybe (HullFace label, LiftedPoint label)
nextHullExpansion state = do
  identifier <- IntSet.lookupMin (hullConflictFaces state)
  face <- IntMap.lookup identifier (hullFaces state)
  apex <- farthestOutsidePoint face
  pure (face, apex)

farthestOutsidePoint :: HullFace label -> Maybe (LiftedPoint label)
farthestOutsidePoint face =
  case Set.minView (hullFaceOutside face) of
    Nothing -> Nothing
    Just (initial, remaining) ->
      Just
        ( fst
            ( Foldable.foldl'
                (\(selected, selectedVolume) candidate ->
                   let candidateVolume = liftedVolumeAtFace face candidate
                    in if candidateVolume > selectedVolume
                         then (candidate, candidateVolume)
                         else (selected, selectedVolume))
                (initial, liftedVolumeAtFace face initial)
                remaining
            )
        )

visibleHullPatch
  :: HullState label
  -> LiftedPoint label
  -> IntSet.IntSet
  -> [Int]
  -> IntSet.IntSet
visibleHullPatch _ _ visited [] = visited
visibleHullPatch state apex visited (identifier : remaining) =
  case IntMap.lookup identifier (hullFaces state) of
    Nothing -> visibleHullPatch state apex visited remaining
    Just face ->
      let neighbours =
            IntSet.unions
              [ Map.findWithDefault IntSet.empty (hullEdgeKey fromPoint toPoint) (hullEdgeFaces state)
              | (fromPoint, toPoint) <- hullFaceDirectedEdges face
              ]
          newlyVisible =
            IntSet.filter
              (\candidateId ->
                 IntSet.notMember candidateId visited
                   && maybe
                     False
                     ((> 0) . (`liftedVolumeAtFace` apex))
                     (IntMap.lookup candidateId (hullFaces state)))
              neighbours
       in visibleHullPatch
            state
            apex
            (IntSet.union visited newlyVisible)
            (IntSet.toList newlyVisible <> remaining)

hullHorizon
  :: HullState label
  -> IntSet.IntSet
  -> [HullFace label]
  -> Map HullEdge (LiftedPoint label, LiftedPoint label)
hullHorizon state visible visibleFaces =
  Map.fromList
    [ (hullEdgeKey fromPoint toPoint, (fromPoint, toPoint))
    | face <- visibleFaces
    , (fromPoint, toPoint) <- hullFaceDirectedEdges face
    , not
        ( IntSet.null
            ( IntSet.difference
                (Map.findWithDefault IntSet.empty (hullEdgeKey fromPoint toPoint) (hullEdgeFaces state))
                visible
            )
        )
    ]

removeHullFaces :: [HullFace label] -> HullState label -> HullState label
removeHullFaces removed state = List.foldl' removeHullFace state removed

removeHullFace :: HullState label -> HullFace label -> HullState label
removeHullFace state face =
  state
    { hullFaces = IntMap.delete identifier (hullFaces state)
    , hullEdgeFaces =
        List.foldl'
          (removeHullFaceFromEdge identifier)
          (hullEdgeFaces state)
          (hullFaceDirectedEdges face)
    , hullConflictFaces = IntSet.delete identifier (hullConflictFaces state)
    }
 where
  identifier = hullFaceId face

removeHullFaceFromEdge
  :: Int
  -> Map HullEdge IntSet.IntSet
  -> (LiftedPoint label, LiftedPoint label)
  -> Map HullEdge IntSet.IntSet
removeHullFaceFromEdge identifier incidence (fromPoint, toPoint) =
  Map.update
    (\incident ->
       let retained = IntSet.delete identifier incident
        in if IntSet.null retained then Nothing else Just retained)
    (hullEdgeKey fromPoint toPoint)
    incidence

insertHorizonFace
  :: (ExactRational, ExactRational, ExactRational)
  -> LiftedPoint label
  -> (HullState label, [Int])
  -> (LiftedPoint label, LiftedPoint label)
  -> (HullState label, [Int])
insertHorizonFace interior apex (state, identifiers) (firstPoint, secondPoint) =
  let identifier = hullNextFaceId state
      face = orientedHullFace interior identifier (firstPoint, secondPoint, apex)
      nextState =
        (insertHullFace state face)
          { hullNextFaceId = identifier + 1 }
   in (nextState, identifier : identifiers)

assignPointToFaces
  :: [Int]
  -> HullState label
  -> LiftedPoint label
  -> HullState label
assignPointToFaces identifiers state point =
  attachSelectedPoint point state
    (selectHullFace point (mapMaybe (`IntMap.lookup` hullFaces state) identifiers))

-- | Glue the triangular implementation facets on each exact supporting plane,
-- then recover only that plane's extreme slope vertices. This removes
-- simplex/horizon diagonals from the visibility decision while retaining a
-- deterministic triangulation for incidence and collapsed duals.
canonicalUpperFaces
  :: [HullFace label]
  -> Either
      (RegularTopologyError label)
      ( [(HullFace label, ExactPoint)]
      , Set (LiftedPoint label)
      , Set (LiftedPoint label)
      )
canonicalUpperFaces faces = do
  associations <- traverse upperFaceAssociation faces
  sections <-
    traverse canonicalUpperFaceGroup
      ( Map.elems
          ( List.foldl'
              (\groups (planeKey, dualFace) ->
                 Map.insertWith (<>) planeKey (dualFace :| []) groups)
              Map.empty
              associations
          )
      )
  let (sectionFaces, visible, lowerDimensional) = mconcat sections
      canonicalFaces =
        zipWith
          (\identifier (face, dualPoint) ->
             (face{hullFaceId = identifier}, dualPoint))
          [0 ..]
          sectionFaces
  pure (canonicalFaces, visible, lowerDimensional)

canonicalUpperFaceGroup
  :: NonEmpty (HullFace label, ExactPoint)
  -> Either
      (RegularTopologyError label)
      ( [(HullFace label, ExactPoint)]
      , Set (LiftedPoint label)
      , Set (LiftedPoint label)
      )
canonicalUpperFaceGroup ((face, dualPoint) :| [])
  | Set.null (hullFaceCoplanar face) =
      Right ([(face, dualPoint)], Set.fromList (hullFacePoints face), Set.empty)
canonicalUpperFaceGroup ((firstFace, dualPoint) :| remainingDualFaces) = do
  let groupFaces = firstFace : fmap fst remainingDualFaces
      groupPoints =
        Set.unions
          [ Set.union
              (Set.fromList (hullFacePoints face))
              (hullFaceCoplanar face)
          | face <- groupFaces
          ]
      initialPoint = hullFaceFirst firstFace
      points = initialPoint :| Set.toAscList (Set.delete initialPoint groupPoints)
      pointsBySlope =
        Map.fromList [(slopePoint point, point) | point <- NonEmpty.toList points]
  hull <-
    first RegularTopologySlopeHullInvalid
      (convexHullPolygon (fmap slopePoint points))
  visiblePoints <-
    traverse
      (\point ->
         maybe
           (Left (RegularTopologySlopeVertexMissing point))
           Right
           (Map.lookup point pointsBySlope))
      (NonEmpty.toList (convexPolygonPoints hull))
  let visible = Set.fromList visiblePoints
  pure
    ( fmap (\fanFace -> (fanFace, dualPoint)) (coplanarFanFaces visiblePoints)
    , visible
    , Set.difference groupPoints visible
    )

hullFacePlaneKey
  :: HullFace label
  -> ExactPoint
  -> (ExactRational, ExactRational, ExactRational)
hullFacePlaneKey face dualPoint =
  let (dualX, dualY) = exactPointCoordinates dualPoint
      generator = liftedPointGenerator (hullFaceFirst face)
      support =
        exactPowerGeneratorXCoefficient generator * dualX
          + exactPowerGeneratorYCoefficient generator * dualY
          + exactPowerGeneratorConstant generator
   in (dualX, dualY, support)

upperFaceAssociation
  :: HullFace label
  -> Either
      (RegularTopologyError label)
      ( (ExactRational, ExactRational, ExactRational)
      , (HullFace label, ExactPoint)
      )
upperFaceAssociation face = do
  (_, dualPoint) <- hullFaceDual face
  pure (hullFacePlaneKey face dualPoint, (face, dualPoint))

coplanarLiftedTopology
  :: NonEmpty (LiftedPoint label)
  -> Either (RegularTopologyError label) (GeneratorRegularTopology label)
coplanarLiftedTopology points = do
  hull <-
    first RegularTopologySlopeHullInvalid
      (convexHullPolygon (fmap slopePoint points))
  let pointsBySlope = Map.fromList [(slopePoint point, point) | point <- NonEmpty.toList points]
  hullPoints <-
    traverse
      (\point -> maybe (Left (RegularTopologySlopeVertexMissing point)) Right (Map.lookup point pointsBySlope))
      (NonEmpty.toList (convexPolygonPoints hull))
  let visible = Set.fromList hullPoints
      lowerDimensional = Set.difference (Set.fromList (NonEmpty.toList points)) visible
      hullFaces' = coplanarFanFaces hullPoints
  dualFaces <- coplanarFanDualFaces hullFaces'
  (faces, edges) <- topologyFromHullFaces dualFaces
  pure
    ( topologyFromParts
        points
        visible
        lowerDimensional
        faces
        edges
        (length hullFaces')
    )

coplanarFanDualFaces
  :: [HullFace label]
  -> Either
      (RegularTopologyError label)
      [(HullFace label, ExactPoint)]
coplanarFanDualFaces [] = Right []
coplanarFanDualFaces faces@(firstFace : _) = do
  (_, dualPoint) <- hullFaceDual firstFace
  pure (fmap (\face -> (face, dualPoint)) faces)

coplanarFanFaces :: [LiftedPoint label] -> [HullFace label]
coplanarFanFaces (firstPoint : secondPoint : thirdPoint : remaining) =
  zipWith
    (\identifier (leftPoint, rightPoint) ->
       HullFace identifier firstPoint leftPoint rightPoint Set.empty Set.empty)
    [0 ..]
    (consecutivePairs (secondPoint : thirdPoint : remaining))
coplanarFanFaces _ = []

collinearSlopeTopology
  :: NonEmpty (LiftedPoint label)
  -> LiftedPoint label
  -> LiftedPoint label
  -> Either (RegularTopologyError label) (GeneratorRegularTopology label)
collinearSlopeTopology points firstPoint secondPoint = do
  let direction = slopeDifference firstPoint secondPoint
      ordered = List.sortOn (slopeParameter direction) (NonEmpty.toList points)
      visibleList = upperSlopeChain direction ordered
      visible = Set.fromList visibleList
      lowerDimensional =
        Set.difference
          (upperChainPointSet direction visibleList ordered)
          visible
  edges <- traverse fullLineEdge (consecutivePairs visibleList)
  pure (topologyFromParts points visible lowerDimensional [] edges 0)

slopeDifference
  :: LiftedPoint label
  -> LiftedPoint label
  -> (ExactRational, ExactRational)
slopeDifference firstPoint secondPoint =
  let (firstX, firstY, _) = liftedCoordinates firstPoint
      (secondX, secondY, _) = liftedCoordinates secondPoint
   in (secondX - firstX, secondY - firstY)

slopeParameter
  :: (ExactRational, ExactRational)
  -> LiftedPoint label
  -> ExactRational
slopeParameter (directionX, directionY) point =
  let (coordinateX, coordinateY, _) = liftedCoordinates point
   in directionX * coordinateX + directionY * coordinateY

upperSlopeChain
  :: (ExactRational, ExactRational)
  -> [LiftedPoint label]
  -> [LiftedPoint label]
upperSlopeChain direction = reverse . List.foldl' insertUpper []
 where
  insertUpper
    :: [LiftedPoint label]
    -> LiftedPoint label
    -> [LiftedPoint label]
  insertUpper (secondPoint : firstPoint : remaining) candidate
    | slopeChainTurn direction firstPoint secondPoint candidate >= 0 =
        insertUpper (firstPoint : remaining) candidate
  insertUpper retained candidate = candidate : retained

slopeChainTurn
  :: (ExactRational, ExactRational)
  -> LiftedPoint label
  -> LiftedPoint label
  -> LiftedPoint label
  -> ExactRational
slopeChainTurn direction firstPoint secondPoint thirdPoint =
  let firstT = slopeParameter direction firstPoint
      secondT = slopeParameter direction secondPoint
      thirdT = slopeParameter direction thirdPoint
      (_, _, firstConstant) = liftedCoordinates firstPoint
      (_, _, secondConstant) = liftedCoordinates secondPoint
      (_, _, thirdConstant) = liftedCoordinates thirdPoint
   in (secondT - firstT) * (thirdConstant - firstConstant)
        - (secondConstant - firstConstant) * (thirdT - firstT)

upperChainPointSet
  :: (ExactRational, ExactRational)
  -> [LiftedPoint label]
  -> [LiftedPoint label]
  -> Set (LiftedPoint label)
upperChainPointSet direction chain =
  snd
    . List.foldl'
        classifyPoint
        (consecutivePairs chain, Set.empty)
 where
  classifyPoint
    :: ([(LiftedPoint label, LiftedPoint label)], Set (LiftedPoint label))
    -> LiftedPoint label
    -> ([(LiftedPoint label, LiftedPoint label)], Set (LiftedPoint label))
  classifyPoint (remainingEdges, members) point =
    let pointParameter = slopeParameter direction point
        candidateEdges =
          List.dropWhile
            ((< pointParameter) . slopeParameter direction . snd)
            remainingEdges
        retainedMembers =
          case candidateEdges of
            (firstPoint, secondPoint) : _
              | slopeParameter direction firstPoint <= pointParameter
                  && slopeChainTurn direction firstPoint secondPoint point == 0 ->
                  Set.insert point members
            _ -> members
     in (candidateEdges, retainedMembers)

fullLineEdge
  :: (LiftedPoint label, LiftedPoint label)
  -> Either (RegularTopologyError label) (GeneratorRegularEdge label)
fullLineEdge (firstPoint, secondPoint) = do
  axis <- generatorAxis firstPoint secondPoint
  pure (regularEdge firstPoint secondPoint (GeneratorDualLine axis))

topologyFromHullFaces
  :: [(HullFace label, ExactPoint)]
  -> Either
      (RegularTopologyError label)
      ([GeneratorRegularFace label], [GeneratorRegularEdge label])
topologyFromHullFaces dualFaces = do
  let dualByFace = IntMap.fromList [(hullFaceId face, dual) | (face, dual) <- dualFaces]
      edgeIncidence =
        List.foldl'
          (\incidence (face, _) ->
             List.foldl'
               (\current (firstPoint, secondPoint, thirdPoint) ->
                  Map.insertWith
                    (<>)
                    (hullEdgeKey firstPoint secondPoint)
                    ((face, firstPoint, secondPoint, thirdPoint) :| [])
                    current)
               incidence
               (faceEdgesWithOpposite face))
          Map.empty
          dualFaces
  edges <- traverse (dualEdge dualByFace) (Map.elems edgeIncidence)
  pure
    ( [ GeneratorRegularFace
          (pointLabel (hullFaceFirst face))
          (pointLabel (hullFaceSecond face))
          (pointLabel (hullFaceThird face))
          dual
      | (face, dual) <- dualFaces
      ]
    , edges
    )

hullFaceDual
  :: HullFace label
  -> Either (RegularTopologyError label) (HullFace label, ExactPoint)
hullFaceDual face = do
  let firstPoint = hullFaceFirst face
      secondPoint = hullFaceSecond face
      thirdPoint = hullFaceThird face
  firstAxis <- generatorAxis firstPoint secondPoint
  secondAxis <- generatorAxis firstPoint thirdPoint
  dual <-
    first
      ( RegularTopologyDualVertexInvalid
          (pointLabel firstPoint)
          (pointLabel secondPoint)
          (pointLabel thirdPoint)
      )
      (exactAffineLineIntersection firstAxis secondAxis)
  pure (face, dual)

faceEdgesWithOpposite
  :: HullFace label
  -> [(LiftedPoint label, LiftedPoint label, LiftedPoint label)]
faceEdgesWithOpposite face =
  [ (hullFaceFirst face, hullFaceSecond face, hullFaceThird face)
  , (hullFaceSecond face, hullFaceThird face, hullFaceFirst face)
  , (hullFaceThird face, hullFaceFirst face, hullFaceSecond face)
  ]

dualEdge
  :: IntMap.IntMap ExactPoint
  -> NonEmpty (HullFace label, LiftedPoint label, LiftedPoint label, LiftedPoint label)
  -> Either (RegularTopologyError label) (GeneratorRegularEdge label)
dualEdge dualByFace incidences =
  case incidences of
    (face, firstPoint, secondPoint, thirdPoint) :| [] -> do
      dual <-
        maybe
          (Left (RegularTopologyFlatBoundary (pointLabel firstPoint) (pointLabel secondPoint) (pointLabel thirdPoint)))
          Right
          (IntMap.lookup (hullFaceId face) dualByFace)
      ray <- boundaryDualRay dual firstPoint secondPoint thirdPoint
      pure (regularEdge firstPoint secondPoint (GeneratorDualRay ray))
    (firstFace, firstPoint, secondPoint, _) :| [(secondFace, _, _, _)] -> do
        firstDual <- requiredDual firstPoint secondPoint firstFace
        secondDual <- requiredDual firstPoint secondPoint secondFace
        if firstDual == secondDual
          then pure (regularEdge firstPoint secondPoint (GeneratorDualCollapsed firstDual))
          else do
            segment <-
              first
                (RegularTopologyDualSegmentInvalid (pointLabel firstPoint) (pointLabel secondPoint))
                (exactSegment firstDual secondDual)
            pure (regularEdge firstPoint secondPoint (GeneratorDualSegment segment))
    (_, firstPoint, secondPoint, _) :| _ ->
      Left
        ( RegularTopologyNonManifoldEdge
            (pointLabel firstPoint)
            (pointLabel secondPoint)
            (NonEmpty.length incidences)
        )
 where
  requiredDual
    :: LiftedPoint label
    -> LiftedPoint label
    -> HullFace label
    -> Either (RegularTopologyError label) ExactPoint
  requiredDual firstPoint secondPoint face =
    maybe
      (Left (RegularTopologyFlatBoundary (pointLabel firstPoint) (pointLabel secondPoint) (pointLabel (hullFaceThird face))))
      Right
      (IntMap.lookup (hullFaceId face) dualByFace)

boundaryDualRay
  :: ExactPoint
  -> LiftedPoint label
  -> LiftedPoint label
  -> LiftedPoint label
  -> Either (RegularTopologyError label) ExactRay
boundaryDualRay originPoint firstPoint secondPoint thirdPoint = do
  axis <- generatorAxis firstPoint secondPoint
  let (axisX, axisY, _) = exactAffineLineCoefficients axis
      candidate = ExactVector axisY (negate axisX)
      firstGenerator = liftedPointGenerator firstPoint
      thirdGenerator = liftedPointGenerator thirdPoint
      advantageX =
        exactPowerGeneratorXCoefficient firstGenerator
          - exactPowerGeneratorXCoefficient thirdGenerator
      advantageY =
        exactPowerGeneratorYCoefficient firstGenerator
          - exactPowerGeneratorYCoefficient thirdGenerator
      ExactVector candidateX candidateY = candidate
      derivative = advantageX * candidateX + advantageY * candidateY
      direction =
        if derivative > 0
          then candidate
          else ExactVector (negate candidateX) (negate candidateY)
  if derivative == 0
    then
      Left
        ( RegularTopologyFlatBoundary
            (pointLabel firstPoint)
            (pointLabel secondPoint)
            (pointLabel thirdPoint)
        )
    else
      first
        (RegularTopologyDualRayInvalid (pointLabel firstPoint) (pointLabel secondPoint))
        (exactRay originPoint direction)

generatorAxis
  :: LiftedPoint label
  -> LiftedPoint label
  -> Either (RegularTopologyError label) ExactAffineLine
generatorAxis firstPoint secondPoint =
  first
    (RegularTopologyAxisInvalid (pointLabel firstPoint) (pointLabel secondPoint))
    (exactGeneratorAxis (liftedPointGenerator firstPoint) (liftedPointGenerator secondPoint))

regularEdge
  :: LiftedPoint label
  -> LiftedPoint label
  -> GeneratorDualGeometry
  -> GeneratorRegularEdge label
regularEdge firstPoint secondPoint dual =
  if liftedPointId firstPoint <= liftedPointId secondPoint
    then GeneratorRegularEdge (liftedPointGenerator firstPoint) (liftedPointGenerator secondPoint) dual
    else GeneratorRegularEdge (liftedPointGenerator secondPoint) (liftedPointGenerator firstPoint) dual

pointLabel :: LiftedPoint label -> label
pointLabel = exactPowerGeneratorLabel . liftedPointGenerator

topologyFromParts
  :: NonEmpty (LiftedPoint label)
  -> Set (LiftedPoint label)
  -> Set (LiftedPoint label)
  -> [GeneratorRegularFace label]
  -> [GeneratorRegularEdge label]
  -> Int
  -> GeneratorRegularTopology label
topologyFromParts points visible lowerDimensional faces edges peakFaces =
  let dispositions =
        fmap
          (\point ->
             ( liftedPointGenerator point
             , if Set.member point visible
                 then RegularGeneratorVisible
                 else
                   if Set.member point lowerDimensional
                     then RegularGeneratorLowerDimensional
                     else RegularGeneratorHidden
             ))
          points
      visibleCount = Set.size visible
      lowerCount = Set.size lowerDimensional
      inputCount = NonEmpty.length points
   in GeneratorRegularTopology
        { generatorRegularDispositions = dispositions
        , generatorRegularFaces = faces
        , generatorRegularEdges = edges
        , generatorRegularReceipt =
            GeneratorRegularReceipt
              { generatorRegularInputSites = inputCount
              , generatorRegularVisibleSites = visibleCount
              , generatorRegularLowerDimensionalSites = lowerCount
              , generatorRegularHiddenSites = inputCount - visibleCount - lowerCount
              , generatorRegularFaceCount = length faces
              , generatorRegularEdgeCount = length edges
              , generatorRegularPeakHullFaces = peakFaces
              }
        }