packages feed

moonlight-triangulation-1.4.0.2: docs/examples/Moonlight/Triangulation/Example/PlanarRegion.hs

module Moonlight.Triangulation.Example.PlanarRegion
  ( ExactFractionSummary (..)
  , CellValuationSummary (..)
  , PlanarExampleError (..)
  , PlanarExampleSummary (..)
  , planarExample
  ) where

import Data.Bifunctor (first)
import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.Map.Strict as Map
import Moonlight.Triangulation

data ExactFractionSummary = ExactFractionSummary
  { fractionNumerator :: !Integer
  , fractionDenominator :: !Integer
  }
  deriving stock (Eq, Show)

data CellValuationSummary = CellValuationSummary
  { cellEulerCharacteristic :: !Int
  , cellExactArea :: !ExactFractionSummary
  }
  deriving stock (Eq, Show)

data PlanarExampleSummary = PlanarExampleSummary
  { booleanValuations :: ![CellValuationSummary]
  , morphologyAreas :: ![ExactFractionSummary]
  }
  deriving stock (Eq, Show)

data PlanarExampleError
  = PlanarRegionFailed !RegionValidationError
  | PlanarOverlayFailed !(OverlayError Bool Bool)
  | PlanarSelectionFailed !OverlaySelectionError
  | PlanarValuationFailed !ValuationError
  | PlanarMinkowskiFailed !MinkowskiError
  deriving stock (Eq, Show)

-- | Compute exact Boolean valuations and morphology through the public facade.
planarExample :: Either PlanarExampleError PlanarExampleSummary
planarExample = do
  left <- rectangleRegion 0 0 4 4
  right <- rectangleRegion 2 1 6 3
  leftLayer <- insideLayer left
  rightLayer <- insideLayer right
  overlay <- first PlanarOverlayFailed (overlayLayers leftLayer rightLayer)
  unionCells <- first PlanarSelectionFailed (overlayClosedUnion id id overlay)
  intersectionCells <-
    first PlanarSelectionFailed (overlayClosedIntersection id id overlay)
  differenceCells <-
    first PlanarSelectionFailed (overlayRegularizedDifference id id overlay)
  cellSummaries <-
    traverse
      cellValuationSummary
      [unionCells, intersectionCells, differenceCells]

  kernelLoop <- rectangleLoop (-1) (-1) 1 1
  kernelPolygon <-
    first PlanarMinkowskiFailed (convexPolygon (exactLoopPoints kernelLoop))
  kernel <- first PlanarMinkowskiFailed (structuringElement kernelPolygon)
  (sumRegion, _) <- first PlanarMinkowskiFailed (minkowskiSum left right)
  (offsetRegion, _) <- first PlanarMinkowskiFailed (polygonOffset kernel left)
  (insetRegion, _) <- first PlanarMinkowskiFailed (polygonInset kernel left)
  (openedRegion, _) <- first PlanarMinkowskiFailed (openWith kernel left)
  (closedRegion, _) <- first PlanarMinkowskiFailed (closeWith kernel left)
  areaSummaries <-
    traverse
      regionAreaSummary
      [sumRegion, offsetRegion, insetRegion, openedRegion, closedRegion]

  pure
    PlanarExampleSummary
      { booleanValuations = cellSummaries
      , morphologyAreas = areaSummaries
      }

rectangleLoop
  :: Integer
  -> Integer
  -> Integer
  -> Integer
  -> Either PlanarExampleError ExactLoop
rectangleLoop minimumX minimumY maximumX maximumY =
  first PlanarRegionFailed
    ( exactLoop
        ( exactPoint (fromInteger minimumX) (fromInteger minimumY)
            :| [ exactPoint (fromInteger maximumX) (fromInteger minimumY)
               , exactPoint (fromInteger maximumX) (fromInteger maximumY)
               , exactPoint (fromInteger minimumX) (fromInteger maximumY)
               ]
        )
    )

rectangleRegion
  :: Integer
  -> Integer
  -> Integer
  -> Integer
  -> Either PlanarExampleError PlanarRegion
rectangleRegion minimumX minimumY maximumX maximumY = do
  outer <- rectangleLoop minimumX minimumY maximumX maximumY
  component <- first PlanarRegionFailed (polygonComponent outer [])
  first PlanarRegionFailed (planarRegion [component])

insideLayer :: PlanarRegion -> Either PlanarExampleError (PlanarLayer Bool)
insideLayer =
  first PlanarRegionFailed
    . planarLayer False
    . Map.singleton True

cellValuationSummary
  :: ExactCellSet
  -> Either PlanarExampleError CellValuationSummary
cellValuationSummary cells = do
  valuations <- first PlanarValuationFailed (cellValuations cells)
  pure
    CellValuationSummary
      { cellEulerCharacteristic =
          eulerCharacteristicValue (valuationEuler valuations)
      , cellExactArea = exactAreaSummary valuations
      }

regionAreaSummary
  :: PlanarRegion
  -> Either PlanarExampleError ExactFractionSummary
regionAreaSummary region =
  exactAreaSummary
    <$> first PlanarValuationFailed (regionValuations region)

exactAreaSummary :: PlanarValuations -> ExactFractionSummary
exactAreaSummary valuations =
  let area = exactAreaValue (valuationArea valuations)
   in ExactFractionSummary
        { fractionNumerator = exactRationalNumerator area
        , fractionDenominator = exactRationalDenominator area
        }