moonlight-triangulation-1.5.0.0: src-public/Moonlight/Triangulation/Internal/PowerDiagram/Projection.hs
{-# LANGUAGE BangPatterns #-}
-- | Bounded clipping and planar publication of exact regular sections.
module Moonlight.Triangulation.Internal.PowerDiagram.Projection
( boundedPowerDiagram
, boundedPowerDiagramFromRegular
, powerCellDisposition
, powerCellDispositions
, powerDiagramPlanarLayer
, upperEnvelope
) where
import Data.Bifunctor (first)
import qualified Data.Foldable as Foldable
import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.List.NonEmpty as NonEmpty
import qualified Data.Map.Strict as Map
import Data.Map.Strict (Map)
import qualified Data.Vector as Vector
import Moonlight.Triangulation.Exact
( ExactClipDisposition (..)
, ExactClipReceipt (..)
, ExactAffineLine
, ExactClosedHalfPlane
, ExactRetainedPolygon
, exactClipRetainedPolygon
, exactClosedHalfPlane
, exactRetainedPolygon
, oppositeExactAffineLine
)
import Moonlight.Triangulation.Internal.PowerDiagram.Generator
( ExactPowerGenerator (..)
, GeneratorRegularEdge (..)
, GeneratorRegularReceipt (..)
, GeneratorRegularTopology (..)
, RegularGeneratorDisposition (..)
, exactGeneratorAxis
)
import Moonlight.Triangulation.Internal.PowerDiagram.Model
import Moonlight.Triangulation.Internal.PowerDiagram.Section
import Moonlight.Triangulation.Internal.Minkowski.Convex
( convexHullPolygon
, convexPolygonComponent
, convexPolygonFromRetained
)
import Moonlight.Triangulation.Internal.Overlay.Types
( OverlayCell (..)
, OverlayCellGeometry (..)
, OverlayResult (..)
)
import Moonlight.Triangulation.Internal.Region.Publication
( planarLayerFromAdmittedComponents
)
import Moonlight.Triangulation.Minkowski
( ConvexPolygon
, convexPolygonPoints
)
import Moonlight.Triangulation.Overlay
( overlayLayers )
import Moonlight.Triangulation.Region
( PlanarLayer
, PolygonComponent
, exactLoopPoints
, polygonOuterLoop
)
-- | Construct and clip a normalized regular subdivision to one convex domain.
boundedPowerDiagram
:: Ord label
=> ConvexPolygon
-> NonEmpty (PowerSite label)
-> Either (PowerDiagramError label) (BoundedPowerDiagram label, PowerDiagramReceipt)
boundedPowerDiagram domain submitted = do
sortedSites <- validateAndSortSites submitted
let preparedGenerators = fmap prepareExactPowerGenerator sortedSites
generators = fmap fst preparedGenerators
siteInputBits =
Foldable.foldl' (\bits preparation -> max bits (snd preparation)) 0 preparedGenerators
(dispositions, clipReceipt, regularReceipt, maximumCellConstraints) <-
exactGeneratorDispositionsWith (<>) mempty domain generators
let receipt =
aggregateReceipt
domain
dispositions
siteInputBits
clipReceipt
regularReceipt
maximumCellConstraints
pure (BoundedPowerDiagram dispositions, receipt)
-- | Clip an already normalized regular value without rebuilding its lifted
-- hull. The empty regular value yields an empty labelled diagram.
boundedPowerDiagramFromRegular
:: Ord label
=> ConvexPolygon
-> RegularTriangulation label
-> Either (PowerDiagramError label) (BoundedPowerDiagram label, PowerDiagramReceipt)
boundedPowerDiagramFromRegular domain triangulation =
case storedRegularSection triangulation of
Nothing ->
Right
( BoundedPowerDiagram Map.empty
, aggregateReceipt
domain
Map.empty
0
mempty
emptyGeneratorRegularReceipt
0
)
Just section -> do
(dispositions, clipReceipt, generatorReceipt, maximumCellConstraints) <-
exactRegularSectionDispositionsWith (<>) mempty domain section
let receipt =
aggregateReceipt
domain
dispositions
(maximumPowerSiteInputBits (storedRegularSites triangulation))
clipReceipt
generatorReceipt
maximumCellConstraints
pure (BoundedPowerDiagram dispositions, receipt)
-- | Full-dimensional labelled argmax regions for affine forms
-- @c0 + cx*x + cy*y@ inside an admitted polygonal window. The result is a
-- planar projection: lower-dimensional and empty winners intentionally have
-- no region. Use 'regularTriangulation' on corresponding weighted sites when
-- those dispositions or exact unbounded duals are required. Identical forms
-- choose the least label, independent of map construction order.
upperEnvelope
:: Ord label
=> PolygonComponent
-> Map label AffineForm
-> Either (UpperEnvelopeError label) (PlanarLayer (Maybe label))
upperEnvelope window forms = do
generators <- affineFormGenerators forms
domain <-
first UpperEnvelopeWindowHullFailed
(convexHullPolygon (exactLoopPoints (polygonOuterLoop window)))
(dispositions, _, _, _) <-
first UpperEnvelopePowerConstructionFailed
(exactGeneratorDispositionsWith discardClipReceipt () domain generators)
let envelopeLayer = affineDispositionLayer dispositions
if convexPolygonComponent domain == window
then Right envelopeLayer
else restrictEnvelopeToWindow window envelopeLayer
-- | Look up the exhaustive bounded-cell disposition for one submitted label.
powerCellDisposition
:: Ord label
=> label
-> BoundedPowerDiagram label
-> Maybe (PowerCellDisposition label)
powerCellDisposition label (BoundedPowerDiagram dispositions) =
Map.lookup label dispositions
-- | Enumerate every submitted label and bounded-cell disposition in order.
powerCellDispositions
:: BoundedPowerDiagram label
-> [(label, PowerCellDisposition label)]
powerCellDispositions (BoundedPowerDiagram dispositions) = Map.toAscList dispositions
-- | Publish the full-dimensional cell layer with a canonical exterior.
powerDiagramPlanarLayer
:: Ord label
=> BoundedPowerDiagram label
-> PlanarLayer (Maybe label)
powerDiagramPlanarLayer (BoundedPowerDiagram dispositions) =
affineDispositionLayer dispositions
affineDispositionLayer
:: Ord label
=> Map label (PowerCellDisposition label)
-> PlanarLayer (Maybe label)
affineDispositionLayer = publishedPowerLayer Nothing Just
publishedPowerLayer
:: Ord publishedLabel
=> publishedLabel
-> (label -> publishedLabel)
-> Map label (PowerCellDisposition label)
-> PlanarLayer publishedLabel
publishedPowerLayer outside publishLabel dispositions =
planarLayerFromAdmittedComponents
outside
[ (publishLabel label, convexPolygonComponent polygon)
| (label, PublishedPowerCell polygon) <- Map.toAscList dispositions
]
restrictEnvelopeToWindow
:: Ord label
=> PolygonComponent
-> PlanarLayer (Maybe label)
-> Either (UpperEnvelopeError label) (PlanarLayer (Maybe label))
restrictEnvelopeToWindow window envelopeLayer = do
let windowLayer = planarLayerFromAdmittedComponents False [(True, window)]
clipped <-
first UpperEnvelopeWindowOverlayFailed
(overlayLayers envelopeLayer windowLayer)
pure
( planarLayerFromAdmittedComponents
Nothing
[ (Just label, component)
| cell <- Vector.toList (overlayResultCells clipped)
, overlayCellRight cell
, Just label <- [overlayCellLeft cell]
, BoundedOverlayCell component <- [overlayCellGeometry cell]
]
)
affineFormGenerators
:: Map label AffineForm
-> Either (UpperEnvelopeError label) (NonEmpty (ExactPowerGenerator label))
affineFormGenerators forms =
case Map.minViewWithKey forms of
Nothing -> Left UpperEnvelopeEmptyForms
Just ((firstLabel, firstForm), remaining) ->
Right
( affineFormGenerator firstLabel firstForm
:| fmap (uncurry affineFormGenerator) (Map.toAscList remaining)
)
affineFormGenerator
:: label
-> AffineForm
-> ExactPowerGenerator label
affineFormGenerator label form =
ExactPowerGenerator
{ exactPowerGeneratorLabel = label
, exactPowerGeneratorXCoefficient = affineFormXCoefficient form
, exactPowerGeneratorYCoefficient = affineFormYCoefficient form
, exactPowerGeneratorConstant = affineFormConstant form
}
exactGeneratorDispositionsWith
:: Ord label
=> (summary -> ExactClipReceipt -> summary)
-> summary
-> ConvexPolygon
-> NonEmpty (ExactPowerGenerator label)
-> Either
(PowerDiagramError label)
( Map label (PowerCellDisposition label)
, summary
, GeneratorRegularReceipt
, Int
)
exactGeneratorDispositionsWith summarizeReceipt initialSummary domain generators = do
section <- first PowerRegularTopologyFailed (resolvedGeneratorSection generators)
exactResolvedGeneratorDispositionsWith
summarizeReceipt
initialSummary
domain
section
exactResolvedGeneratorDispositionsWith
:: Ord label
=> (summary -> ExactClipReceipt -> summary)
-> summary
-> ConvexPolygon
-> ResolvedGeneratorSection label
-> Either
(PowerDiagramError label)
( Map label (PowerCellDisposition label)
, summary
, GeneratorRegularReceipt
, Int
)
exactResolvedGeneratorDispositionsWith summarizeReceipt initialSummary domain section = do
retainedDomain <-
first PowerDomainInvalid (exactRetainedPolygon (convexPolygonPoints domain))
let topology = resolvedRegularTopology section
constraints <- prepareRegularConstraintSection topology
clipped <-
traverse
(clipPowerCell retainedDomain section constraints)
(generatorRegularDispositions topology)
let (dispositions, summary, maximumCellConstraints) =
summarizeClippedPowerCells
summarizeReceipt
initialSummary
(resolvedCoincidentDispositions section)
(NonEmpty.toList clipped)
pure
( dispositions
, summary
, generatorRegularReceipt topology
, maximumCellConstraints
)
exactRegularSectionDispositionsWith
:: Ord label
=> (summary -> ExactClipReceipt -> summary)
-> summary
-> ConvexPolygon
-> RegularSection label
-> Either
(PowerDiagramError label)
( Map label (PowerCellDisposition label)
, summary
, GeneratorRegularReceipt
, Int
)
exactRegularSectionDispositionsWith summarizeReceipt initialSummary domain section = do
retainedDomain <-
first PowerDomainInvalid (exactRetainedPolygon (convexPolygonPoints domain))
constraints <- preparePublishedRegularConstraints section
representativeDispositions <-
traverse
(\(label, generator) -> do
disposition <-
maybe
(Left (PowerRegularDispositionMissing label))
Right
(Map.lookup label (sectionRegularDispositions section))
clipPublishedPowerCell
retainedDomain
section
constraints
generator
disposition)
(Map.toAscList (sectionGenerators section))
let (dispositions, summary, maximumCellConstraints) =
summarizeClippedPowerCells
summarizeReceipt
initialSummary
(sectionCoincidentDispositions section)
representativeDispositions
pure
( dispositions
, summary
, sectionRegularReceipt section
, maximumCellConstraints
)
summarizeClippedPowerCells
:: Ord label
=> (summary -> ExactClipReceipt -> summary)
-> summary
-> Map label (CoincidentGeneratorDisposition label)
-> [(label, PowerCellDisposition label, ExactClipReceipt, Int)]
-> (Map label (PowerCellDisposition label), summary, Int)
summarizeClippedPowerCells summarizeReceipt initialSummary coincident clipped =
let (publishedAssociations, summary, maximumCellConstraints) =
Foldable.foldl'
(\(associations, accumulatedSummary, peak) (label, disposition, cellReceipt, cellAxes) ->
let !combinedSummary = summarizeReceipt accumulatedSummary cellReceipt
in ( (label, disposition) : associations
, combinedSummary
, max peak cellAxes
))
([], initialSummary, 0)
clipped
in ( fmap coincidentPowerDisposition coincident
<> Map.fromList publishedAssociations
, summary
, maximumCellConstraints
)
preparePublishedRegularConstraints
:: Ord label
=> RegularSection label
-> Either (PowerDiagramError label) (Map label [ExactClosedHalfPlane])
preparePublishedRegularConstraints section =
Foldable.foldlM prepare Map.empty (Map.elems (sectionRegularEdges section))
where
prepare constraints edgeSection = do
let edge = sectionRegularEdge edgeSection
(firstLabel, secondLabel) = regularEdgeLabels edge
firstGenerator <- requireSectionGenerator section firstLabel
secondGenerator <- requireSectionGenerator section secondLabel
insertRegularConstraintEdge constraints firstGenerator secondGenerator
clipPublishedPowerCell
:: Ord label
=> ExactRetainedPolygon
-> RegularSection label
-> Map label [ExactClosedHalfPlane]
-> ExactPowerGenerator label
-> RegularSiteDisposition label
-> Either
(PowerDiagramError label)
(label, PowerCellDisposition label, ExactClipReceipt, Int)
clipPublishedPowerCell retainedDomain section constraints ownerGenerator disposition =
let ownerLabel = exactPowerGeneratorLabel ownerGenerator
competitors =
filter
((/= ownerLabel) . exactPowerGeneratorLabel)
(Map.elems (sectionGenerators section))
clipAs =
clipRegularGeneratorCell
retainedDomain
ownerGenerator
(Map.findWithDefault [] ownerLabel constraints)
competitors
in case disposition of
RegularSiteHidden -> clipAs RegularGeneratorHidden
RegularSiteVisible -> clipAs RegularGeneratorVisible
RegularSiteLowerDimensional -> clipAs RegularGeneratorLowerDimensional
RegularSiteCoincidentEquivalentTo _ ->
Left (PowerRegularDispositionMissing ownerLabel)
RegularSiteCoincidentDominatedBy _ ->
Left (PowerRegularDispositionMissing ownerLabel)
requireSectionGenerator
:: Ord label
=> RegularSection label
-> label
-> Either (PowerDiagramError label) (ExactPowerGenerator label)
requireSectionGenerator section label =
maybe
(Left (PowerRegularGeneratorMissing label))
Right
(Map.lookup label (sectionGenerators section))
discardClipReceipt :: () -> ExactClipReceipt -> ()
discardClipReceipt _ _ = ()
prepareRadicalAxis
:: ExactPowerGenerator label
-> ExactPowerGenerator label
-> Either (PowerDiagramError label) ExactAffineLine
prepareRadicalAxis firstGenerator secondGenerator =
first
( PowerBisectorInvalid
(exactPowerGeneratorLabel firstGenerator)
(exactPowerGeneratorLabel secondGenerator)
)
(exactGeneratorAxis firstGenerator secondGenerator)
prepareRegularConstraintSection
:: Ord label
=> GeneratorRegularTopology label
-> Either (PowerDiagramError label) (Map label [ExactClosedHalfPlane])
prepareRegularConstraintSection =
Foldable.foldlM prepareRegularConstraintEdge Map.empty . generatorRegularEdges
prepareRegularConstraintEdge
:: Ord label
=> Map label [ExactClosedHalfPlane]
-> GeneratorRegularEdge label
-> Either (PowerDiagramError label) (Map label [ExactClosedHalfPlane])
prepareRegularConstraintEdge constraints edge = do
let firstGenerator = generatorRegularEdgeFirst edge
secondGenerator = generatorRegularEdgeSecond edge
insertRegularConstraintEdge constraints firstGenerator secondGenerator
insertRegularConstraintEdge
:: Ord label
=> Map label [ExactClosedHalfPlane]
-> ExactPowerGenerator label
-> ExactPowerGenerator label
-> Either (PowerDiagramError label) (Map label [ExactClosedHalfPlane])
insertRegularConstraintEdge constraints firstGenerator secondGenerator = do
let firstLabel = exactPowerGeneratorLabel firstGenerator
secondLabel = exactPowerGeneratorLabel secondGenerator
axis <- prepareRadicalAxis firstGenerator secondGenerator
pure
( Map.insertWith (<>) secondLabel [exactClosedHalfPlane (oppositeExactAffineLine axis)]
(Map.insertWith (<>) firstLabel [exactClosedHalfPlane axis] constraints)
)
clipPowerCell
:: Ord label
=> ExactRetainedPolygon
-> ResolvedGeneratorSection label
-> Map label [ExactClosedHalfPlane]
-> (ExactPowerGenerator label, RegularGeneratorDisposition)
-> Either
(PowerDiagramError label)
(label, PowerCellDisposition label, ExactClipReceipt, Int)
clipPowerCell retainedDomain section constraints (ownerGenerator, disposition) =
let ownerLabel = exactPowerGeneratorLabel ownerGenerator
topology = resolvedRegularTopology section
competitors =
[ generator
| (generator, _) <-
NonEmpty.toList (generatorRegularDispositions topology)
, exactPowerGeneratorLabel generator /= ownerLabel
]
in clipRegularGeneratorCell
retainedDomain
ownerGenerator
(Map.findWithDefault [] ownerLabel constraints)
competitors
disposition
clipRegularGeneratorCell
:: ExactRetainedPolygon
-> ExactPowerGenerator label
-> [ExactClosedHalfPlane]
-> [ExactPowerGenerator label]
-> RegularGeneratorDisposition
-> Either
(PowerDiagramError label)
(label, PowerCellDisposition label, ExactClipReceipt, Int)
clipRegularGeneratorCell retainedDomain ownerGenerator activeConstraints competitors disposition =
case disposition of
RegularGeneratorHidden ->
pure (exactPowerGeneratorLabel ownerGenerator, EmptyPowerCell, mempty, 0)
RegularGeneratorVisible ->
finishPowerCell retainedDomain ownerGenerator activeConstraints
RegularGeneratorLowerDimensional -> do
halfPlanes <- traverse (preparedDirectHalfPlane ownerGenerator) competitors
finishPowerCell retainedDomain ownerGenerator halfPlanes
preparedDirectHalfPlane
:: ExactPowerGenerator label
-> ExactPowerGenerator label
-> Either (PowerDiagramError label) ExactClosedHalfPlane
preparedDirectHalfPlane owner competitor =
exactClosedHalfPlane <$> prepareRadicalAxis owner competitor
finishPowerCell
:: ExactRetainedPolygon
-> ExactPowerGenerator label
-> [ExactClosedHalfPlane]
-> Either
(PowerDiagramError label)
(label, PowerCellDisposition label, ExactClipReceipt, Int)
finishPowerCell retainedDomain ownerGenerator halfPlanes = do
(exactDisposition, receipt) <-
first (PowerCellClipFailed (exactPowerGeneratorLabel ownerGenerator))
(exactClipRetainedPolygon retainedDomain halfPlanes)
pure
( exactPowerGeneratorLabel ownerGenerator
, case exactDisposition of
ExactClipFullDimensional retained ->
PublishedPowerCell (convexPolygonFromRetained retained)
ExactClipLowerDimensional points -> LowerDimensionalPowerCell points
ExactClipEmpty -> EmptyPowerCell
, receipt
, length halfPlanes
)
aggregateReceipt
:: ConvexPolygon
-> Map label (PowerCellDisposition label)
-> Int
-> ExactClipReceipt
-> GeneratorRegularReceipt
-> Int
-> PowerDiagramReceipt
aggregateReceipt domain dispositions siteInputBits clipReceipt regularReceipt maximumCellConstraints =
let inputBits = max siteInputBits (exactClipInputCoordinateBits clipReceipt)
peakBits = exactClipPeakIntermediateCoordinateBits clipReceipt
finalBits = exactClipFinalCoordinateBits clipReceipt
dispositionCounts = countPowerDispositions dispositions
in PowerDiagramReceipt
{ powerDiagramDomainVertices = NonEmpty.length (convexPolygonPoints domain)
, powerDiagramSubmittedSiteConstraints = exactClipSubmittedHalfPlanes clipReceipt
, powerDiagramActiveBoundaries = exactClipActiveBoundaries clipReceipt
, powerDiagramBoundaryCompatibilityChecks = exactClipBoundaryCompatibilityChecks clipReceipt
, powerDiagramExactIntersections = exactClipExactIntersections clipReceipt
, powerDiagramPublishedCells = countedPublishedCells dispositionCounts
, powerDiagramLowerDimensionalCells = countedLowerDimensionalCells dispositionCounts
, powerDiagramEmptyCells = countedEmptyCells dispositionCounts
, powerDiagramCoincidentEquivalentCells = countedCoincidentEquivalentCells dispositionCounts
, powerDiagramCoincidentDominatedCells = countedCoincidentDominatedCells dispositionCounts
, powerDiagramRegularFaces = generatorRegularFaceCount regularReceipt
, powerDiagramRegularEdges = generatorRegularEdgeCount regularReceipt
, powerDiagramOracleCells = generatorRegularLowerDimensionalSites regularReceipt
, powerDiagramMaximumCellConstraints = maximumCellConstraints
, powerDiagramMaximumInputBits = inputBits
, powerDiagramMaximumAffineCoefficientBits = exactClipMaximumAffineCoefficientBits clipReceipt
, powerDiagramPeakIntermediateCoordinateBits = peakBits
, powerDiagramFinalCoordinateBits = finalBits
, powerDiagramFinalDenominatorBits = exactClipFinalDenominatorBits clipReceipt
}
data PowerDispositionCounts = PowerDispositionCounts
{ countedPublishedCells :: !Int
, countedLowerDimensionalCells :: !Int
, countedEmptyCells :: !Int
, countedCoincidentEquivalentCells :: !Int
, countedCoincidentDominatedCells :: !Int
}
countPowerDispositions
:: Map label (PowerCellDisposition label)
-> PowerDispositionCounts
countPowerDispositions =
Map.foldl'
(\counts disposition -> case disposition of
PublishedPowerCell _ ->
counts {countedPublishedCells = countedPublishedCells counts + 1}
LowerDimensionalPowerCell _ ->
counts {countedLowerDimensionalCells = countedLowerDimensionalCells counts + 1}
EmptyPowerCell ->
counts {countedEmptyCells = countedEmptyCells counts + 1}
CoincidentEquivalentTo _ ->
counts {countedCoincidentEquivalentCells = countedCoincidentEquivalentCells counts + 1}
CoincidentDominatedBy _ ->
counts {countedCoincidentDominatedCells = countedCoincidentDominatedCells counts + 1})
PowerDispositionCounts
{ countedPublishedCells = 0
, countedLowerDimensionalCells = 0
, countedEmptyCells = 0
, countedCoincidentEquivalentCells = 0
, countedCoincidentDominatedCells = 0
}