packages feed

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

-- | Resident-boundary observations shared by region and alpha laws.
module Moonlight.Planar.BoundaryFixtures
  ( requireLabelledComponent
  , requireComponentBoundary
  , assertLoopWinding
  , assertBoundaryShape
  ) where

import Data.Foldable ( traverse_ )
import Data.List.NonEmpty ( NonEmpty(..) )
import Moonlight.Planar.Dcel ( vertexPoint )
import Moonlight.Planar.FloodFillIterator ( componentBoundary, faceComponents,
  BoundaryLoop(boundaryLoopVertices, boundaryLoopOrientation),
  BoundaryOrientation(BoundaryClockwise, BoundaryCounterClockwise), FaceComponent,
  RegionBoundary(..) )
import Moonlight.Planar.Internal.HandleDefs ( FaceId )
import Moonlight.Planar.Point (Point(Point))
import Moonlight.Planar.Types (Triangulation)
import Support ( assertEqual, requireRight )


requireLabelledComponent
  :: (Eq label, Show label)
  => String
  -> label
  -> [(label, FaceComponent)]
  -> IO FaceComponent
requireLabelledComponent label expected components =
  case [component | (actual, component) <- components, actual == expected] of
    [component] -> pure component
    matches ->
      fail
        ( label
            <> ": expected one component for label "
            <> show expected
            <> ", got "
            <> show (length matches)
        )

requireComponentBoundary
  :: (Eq label, Show label)
  => String
  -> label
  -> Triangulation mode vertex directed undirected face
  -> (FaceId -> label)
  -> IO RegionBoundary
requireComponentBoundary label expected triangulation labelFace = do
  component <-
    requireLabelledComponent
      (label <> " component")
      expected
      (faceComponents triangulation labelFace)
  requireRight (label <> " boundary") (componentBoundary triangulation component)

assertLoopWinding
  :: String
  -> Ordering
  -> Triangulation mode vertex directed undirected face
  -> BoundaryLoop
  -> IO ()
assertLoopWinding label expected triangulation loop =
  let first :| remaining = fmap (vertexPoint triangulation) (boundaryLoopVertices loop)
      points = first : remaining
      twiceArea =
        sum
          ( zipWith
              (\(Point ax ay) (Point bx by) -> ax * by - ay * bx)
              points
              (remaining <> [first])
          )
   in assertEqual label expected (compare twiceArea 0)

assertBoundaryShape
  :: String
  -> Triangulation mode vertex directed undirected face
  -> Int
  -> [Int]
  -> RegionBoundary
  -> IO ()
assertBoundaryShape label triangulation outerVertexCount holeVertexCounts boundary = do
  assertEqual
    (label <> " outer orientation")
    BoundaryCounterClockwise
    (boundaryLoopOrientation (regionBoundaryOuterLoop boundary))
  assertEqual (label <> " outer vertex count") outerVertexCount
    (length (boundaryLoopVertices (regionBoundaryOuterLoop boundary)))
  assertLoopWinding (label <> " outer winding") GT triangulation
    (regionBoundaryOuterLoop boundary)
  let holes = regionBoundaryHoleLoops boundary
  assertEqual (label <> " hole count") (length holeVertexCounts) (length holes)
  traverse_
    (\(vertexCount, hole) -> do
       assertEqual
         (label <> " hole orientation")
         BoundaryClockwise
         (boundaryLoopOrientation hole)
       assertEqual (label <> " hole vertex count") vertexCount
         (length (boundaryLoopVertices hole))
       assertLoopWinding (label <> " hole winding") LT triangulation hole)
    (zip holeVertexCounts holes)