packages feed

moonlight-planar-1.1.0.0: test/curve/Moonlight/Planar/RegionAdmissionSpec.hs

-- | Pin the polygon admission semantics consumed by curve-region lowering.
module Moonlight.Planar.RegionAdmissionSpec (tests) where

import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.Map.Strict as Map
import Moonlight.Planar.Region
  ( RegionPointLocation (..)
  , RegionValidationError (..)
  , exactLoop
  , planarLayer
  , planarRegion
  , planarRegionComponents
  , polygonComponent
  , regionPointLocation
  )
import Support (assertEqual, integerPoint, rectangleComponent, rectangleLoop, requireRight)

tests :: IO ()
tests = sequence_
  [ testBatchRefusalOrder
  , testBoundaryContactAndHoles
  , putStrLn "region admission: ok"
  ]

testBatchRefusalOrder :: IO ()
testBatchRefusalOrder = do
  first <- rectangleComponent 20 0 24 4
  second <- rectangleComponent 21 1 25 5
  third <- rectangleComponent 0 0 4 4
  fourth <- rectangleComponent 1 1 5 5
  assertEqual "component refusal uses input order rather than x sweep order"
    (Left (RegionComponentInteriorOverlap 0 1))
    (planarRegion [first, second, third, fourth])
  regions <- traverse (requireRight "singleton region" . planarRegion . pure)
    [first, second, third, fourth]
  assertEqual "layer refusal uses label order rather than x sweep order"
    (Left (RegionLayerInteriorOverlap 0 1))
    (planarLayer (-1 :: Int) (Map.fromList (zip [0 ..] regions)))
  outer <- rectangleLoop 0 0 8 8
  outsideHole <- requireRight "clockwise outside hole" (exactLoop
    (integerPoint 20 0 :| [integerPoint 20 2, integerPoint 22 2, integerPoint 22 0]))
  wrongWindingHole <- rectangleLoop 2 2 3 3
  assertEqual "all hole windings precede containment admission"
    (Left (RegionHoleLoopWinding 1 GT))
    (polygonComponent outer [outsideHole, wrongWindingHole])
  assertEqual "disjoint bounds still report outside hole location"
    (Left (RegionHoleLocation 0 RegionExterior))
    (polygonComponent outer [outsideHole])

testBoundaryContactAndHoles :: IO ()
testBoundaryContactAndHoles = do
  left <- rectangleComponent 0 0 2 2
  right <- rectangleComponent 2 0 4 2
  touching <- requireRight "oppositely directed shared boundary" (planarRegion [left, right])
  assertEqual "boundary-only contact retains both components"
    2 (length (planarRegionComponents touching))
  assertEqual "shared edge remains boundary" RegionOnBoundary
    (regionPointLocation touching (integerPoint 2 1))
  assertEqual "identically directed duplicate cycles overlap"
    (Left (RegionComponentInteriorOverlap 0 1)) (planarRegion [left, left])
  outer <- rectangleLoop 0 0 10 10
  hole <- requireRight "clockwise central hole" (exactLoop
    (integerPoint 2 2 :| [integerPoint 2 8, integerPoint 8 8, integerPoint 8 2]))
  annulus <- requireRight "annulus" (polygonComponent outer [hole])
  island <- rectangleComponent 4 4 6 6
  nested <- requireRight "island within another component's hole" (planarRegion [annulus, island])
  assertEqual "annulus interior" RegionInterior (regionPointLocation nested (integerPoint 1 1))
  assertEqual "unfilled hole" RegionExterior (regionPointLocation nested (integerPoint 3 3))
  assertEqual "separate island interior" RegionInterior (regionPointLocation nested (integerPoint 5 5))
  assertEqual "hole boundary" RegionOnBoundary (regionPointLocation nested (integerPoint 2 5))