packages feed

moonlight-planar-1.1.0.0: test/native/Moonlight/Planar/OverlayPublicationSpec.hs

-- | Grouped polygon publication, boundary walks, and retained source vertices.
module Moonlight.Planar.OverlayPublicationSpec (tests) where

import Data.List.NonEmpty ( NonEmpty(..) )
import Moonlight.Planar.Internal.Incidence ( incidenceEdgeCount, incidenceFaceCount,
  incidenceVertexCount )
import Moonlight.Planar.Internal.Overlay.Types ( overlayResultIncidence )
import Moonlight.Planar.Overlay ( OverlayVertex(overlayExactPoint), overlayArrangementVertices,
  overlayCells, overlayClosedUnion, overlayLayers, overlayPlanarLayer )
import Moonlight.Planar.OverlayAssertions ( assertOverlayIntegrity, unboundedLoopCount )
import Moonlight.Planar.OverlayFixtures ( singletonLayer )
import Moonlight.Planar.Region ( exactLoop, polygonComponent, PolygonComponent, planarLayerRegions,
  planarLayer, planarRegion, planarRegionComponents )
import Moonlight.Planar.Valuation ( cellValuations, eulerCharacteristicValue, exactAreaValue,
  valuationArea, valuationEuler )
import Support ( assertEqual, integerPoint, rectangleComponent, requireRight, rectangleRegion,
  annulusRegion )
import qualified Data.Map.Strict as Map
import qualified Data.Set as Set

tests :: IO ()
tests =
  sequence_
    [ testOutsidePairRemainsImplicit
    , testDisconnectedGroupedPublication
    , testMultiwayPointBoundaryCycles
    , testExactBoundaryWithoutDiagonals
    , testCancelledSeamsRetainIsolatedVertex
    ]

testOutsidePairRemainsImplicit :: IO ()
testOutsidePairRemainsImplicit = do
  region <- annulusRegion (0, 0, 4, 4) (1, 1, 3, 3)
  left <-
    requireRight
      "annulus layer"
      (planarLayer "outside" (Map.singleton "annulus" region))
  right <- requireRight "annulus empty layer" (planarLayer "void" Map.empty)
  result <- requireRight "annulus overlay" (overlayLayers left right)
  published <- requireRight "publish annulus exact arrangement" (overlayPlanarLayer result)
  assertEqual
    "bounded cavities remain represented by the implicit outside pair"
    Nothing
    ( Map.lookup
        ("outside", "void")
        (planarLayerRegions published)
    )

testDisconnectedGroupedPublication :: IO ()
testDisconnectedGroupedPublication = do
  first <- rectangleComponent 0 0 1 1
  second <- rectangleComponent 3 0 4 1
  region <- requireRight "two-island region" (planarRegion [first, second])
  left <-
    requireRight
      "two-island layer"
      (planarLayer "outside" (Map.singleton "island" region))
  right <- requireRight "empty right layer" (planarLayer "void" Map.empty)
  result <- requireRight "two-island overlay" (overlayLayers left right)
  assertEqual "two islands give two unbounded boundary cycles" (Just 2) (unboundedLoopCount result)
  published <- requireRight "publish disconnected exact arrangement" (overlayPlanarLayer result)
  let components =
        maybe
          []
          planarRegionComponents
          (Map.lookup ("island", "void") (planarLayerRegions published))
  assertEqual "equal labels group after component descent" 2 (length components)
  reversedRegion <- requireRight "reversed two-island region" (planarRegion [second, first])
  reversedLeft <-
    requireRight
      "reversed two-island layer"
      (planarLayer "outside" (Map.singleton "island" reversedRegion))
  reversedResult <- requireRight "reversed two-island overlay" (overlayLayers reversedLeft right)
  assertEqual
    "component construction order cannot perturb stable cells"
    (overlayCells result)
    (overlayCells reversedResult)

testMultiwayPointBoundaryCycles :: IO ()
testMultiwayPointBoundaryCycles = do
  components <-
    traverse
      triangleComponent
      [ ((0, 0), (2, 0), (1, 1))
      , ((0, 0), (-1, 1), (-2, 0))
      , ((0, 0), (-1, -1), (1, -1))
      ]
  region <- requireRight "three point-touching components" (planarRegion components)
  left <-
    requireRight
      "three point-touching layer"
      (planarLayer "outside" (Map.singleton "inside" region))
  right <- requireRight "empty multiway right layer" (planarLayer "void" Map.empty)
  result <- requireRight "three-way point-touching overlay" (overlayLayers left right)
  assertOverlayIntegrity "three-way point-touching" result
  assertEqual
    "multiway point contact retains one unsplit outside boundary walk"
    (Just 1)
    (unboundedLoopCount result)

testExactBoundaryWithoutDiagonals :: IO ()
testExactBoundaryWithoutDiagonals = do
  left <- rectangleRegion 0 0 2 2 >>= singletonLayer "outside" "inside"
  right <- requireRight "empty exact-boundary layer" (planarLayer "void" Map.empty)
  result <- requireRight "exact boundary-only arrangement" (overlayLayers left right)
  assertOverlayIntegrity "exact boundary-only arrangement" result
  let incidence = overlayResultIncidence result
  assertEqual
    "a square arrangement has no invented diagonal or triangular face"
    (4, 4, 2)
    (incidenceVertexCount incidence, incidenceEdgeCount incidence, incidenceFaceCount incidence)

testCancelledSeamsRetainIsolatedVertex :: IO ()
testCancelledSeamsRetainIsolatedVertex = do
  components <- traverse triangleComponent
    [ ((0, 0), (2, 0), (1, 1))
    , ((2, 0), (2, 2), (1, 1))
    , ((2, 2), (0, 2), (1, 1))
    , ((0, 2), (0, 0), (1, 1))
    ]
  region <- requireRight "four-triangle source fan" (planarRegion components)
  left <- requireRight "same-label source fan" (planarLayer "outside" (Map.singleton "inside" region))
  right <- requireRight "empty fan counterpart" (planarLayer "void" Map.empty)
  result <- requireRight "cancelled source fan seams" (overlayLayers left right)
  assertOverlayIntegrity "cancelled source fan" result
  let incidence = overlayResultIncidence result
  assertEqual "seam cancellation keeps the isolated center provenance vertex"
    (5, 4, 2)
    (incidenceVertexCount incidence, incidenceEdgeCount incidence, incidenceFaceCount incidence)
  assertEqual "source center survives without a manufactured edge" True
    (Set.member (integerPoint 1 1) (Set.fromList (fmap (overlayExactPoint . snd) (overlayArrangementVertices result))))
  cells <- requireRight "closed disk with isolated source vertex" (overlayClosedUnion (== "inside") (const False) result)
  values <- requireRight "isolated-vertex face Euler contribution" (cellValuations cells)
  assertEqual "punctured open face plus its selected isolated point is a disk"
    (1, 4) (eulerCharacteristicValue (valuationEuler values), exactAreaValue (valuationArea values))

triangleComponent
  :: ((Integer, Integer), (Integer, Integer), (Integer, Integer))
  -> IO PolygonComponent
triangleComponent (firstPoint, secondPoint, thirdPoint) = do
  loop <-
    requireRight
      "overlay triangle loop"
      ( exactLoop
          ( uncurry integerPoint firstPoint
              :| [uncurry integerPoint secondPoint, uncurry integerPoint thirdPoint]
          )
      )
  requireRight "overlay triangle component" (polygonComponent loop [])