moonlight-triangulation-1.3.0.0: src-ffi/Moonlight/Triangulation/Foreign/ABI.hs
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeFamilies #-}
module Moonlight.Triangulation.Foreign.ABI
( CObstruction (..)
, CMinkowskiReceipt (..)
, CMesh
, CRegion
, CStructuringElement
, delaunayF64
, meshInsertManyF64
, -- | Exported for measurement. @meshInsertManyF64@ is a pointer boundary and
-- cannot be timed against a Haskell arm without dragging marshalling into
-- one side only; this is the same route with the pointers already resolved.
insertGeometryBatch
, meshSiteUnion
, meshSiteIntersection
, meshSiteDifference
, meshSiteSymmetricDifference
, meshVertexCount
, meshTriangleCount
, meshCopyVerticesF64
, meshCopyTrianglesU32
, meshFree
, regionCreateF64
, regionCounts
, regionCopyF64
, regionUnion
, regionIntersection
, regionDifference
, regionSymmetricDifference
, regionLocatePointF64
, regionMeasure
, regionFree
, structuringElementCreateF64
, structuringElementFree
, regionMinkowskiSum
, regionOffset
, regionInset
, regionOpen
, regionClose
) where
import Control.Exception (SomeException, displayException, try)
import Control.Monad (void)
import Data.Bifunctor (first)
import Data.Int (Int64)
import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.List.NonEmpty as NonEmpty
import qualified Data.Map.Strict as Map
import Data.Word (Word32, Word64)
import Foreign.C.String (peekCString, withCStringLen)
import Foreign.C.Types (CChar, CDouble (..), CSize (..), CUInt (..))
import Foreign.Marshal.Utils (copyBytes, fillBytes)
import Foreign.Ptr (Ptr, castPtr, nullPtr, plusPtr)
import Foreign.StablePtr
( StablePtr
, castPtrToStablePtr
, castStablePtrToPtr
, deRefStablePtr
, freeStablePtr
, newStablePtr
)
import Foreign.Storable (Storable (..))
import qualified Data.Vector as V
import qualified Moonlight.Triangulation as T
import Moonlight.Triangulation.Math (canonicalPoint, validatePoint)
import qualified Moonlight.Triangulation.Session as Session
type GeometryMesh = T.DelaunayTriangulation ()
data CMesh
data CRegion
data CStructuringElement
type family HandleValue carrier where
HandleValue CMesh = GeometryMesh
HandleValue CRegion = T.PlanarRegion
HandleValue CStructuringElement = T.StructuringElement
data CObstruction = CObstruction
{ obstructionCode :: !Word32
, obstructionCoordinateError :: !Word32
, obstructionInputIndex :: !Word64
, obstructionFirstIndex :: !Word64
, obstructionSecondIndex :: !Word64
, obstructionFirstValue :: !Double
, obstructionSecondValue :: !Double
, obstructionPointX :: !Double
, obstructionPointY :: !Double
, obstructionMessage :: !String
}
deriving stock (Eq, Show)
instance Storable CObstruction where
sizeOf _ = 320
alignment _ = alignment (0 :: Word64)
peek pointer = do
obstructionCode <- peekByteOff pointer 0
obstructionCoordinateError <- peekByteOff pointer 4
obstructionInputIndex <- peekByteOff pointer 8
obstructionFirstIndex <- peekByteOff pointer 16
obstructionSecondIndex <- peekByteOff pointer 24
obstructionFirstValue <- peekByteOff pointer 32
obstructionSecondValue <- peekByteOff pointer 40
obstructionPointX <- peekByteOff pointer 48
obstructionPointY <- peekByteOff pointer 56
obstructionMessage <- peekCString (castPtr pointer `plusPtr` 64)
pure CObstruction {..}
poke pointer CObstruction {..} = do
pokeByteOff pointer 0 obstructionCode
pokeByteOff pointer 4 obstructionCoordinateError
pokeByteOff pointer 8 obstructionInputIndex
pokeByteOff pointer 16 obstructionFirstIndex
pokeByteOff pointer 24 obstructionSecondIndex
pokeByteOff pointer 32 obstructionFirstValue
pokeByteOff pointer 40 obstructionSecondValue
pokeByteOff pointer 48 obstructionPointX
pokeByteOff pointer 56 obstructionPointY
let messagePointer = castPtr pointer `plusPtr` 64 :: Ptr CChar
fillBytes messagePointer 0 256
withCStringLen obstructionMessage $ \(source, lengthInBytes) ->
copyBytes messagePointer source (min 255 lengthInBytes)
-- | Fixed-width projection of the existing Haskell morphology receipt.
data CMinkowskiReceipt = CMinkowskiReceipt
{ receiptOperation :: !Word32
, receiptInputComponents :: !Word64
, receiptConvexPieces :: !Word64
, receiptGeneratedPieces :: !Word64
, receiptGeneratedConvolutionEdges :: !Word64
, receiptOverlayPasses :: !Word64
, receiptExactCrossings :: !Word64
, receiptOutputCells :: !Word64
, receiptExactCoordinateBitGrowth :: !Word64
}
deriving stock (Eq, Show)
instance Storable CMinkowskiReceipt where
sizeOf _ = 72
alignment _ = alignment (0 :: Word64)
peek pointer = do
receiptOperation <- peekByteOff pointer 0
receiptInputComponents <- peekByteOff pointer 8
receiptConvexPieces <- peekByteOff pointer 16
receiptGeneratedPieces <- peekByteOff pointer 24
receiptGeneratedConvolutionEdges <- peekByteOff pointer 32
receiptOverlayPasses <- peekByteOff pointer 40
receiptExactCrossings <- peekByteOff pointer 48
receiptOutputCells <- peekByteOff pointer 56
receiptExactCoordinateBitGrowth <- peekByteOff pointer 64
pure CMinkowskiReceipt {..}
poke pointer CMinkowskiReceipt {..} = do
pokeByteOff pointer 0 receiptOperation
pokeByteOff pointer 4 (0 :: Word32)
pokeByteOff pointer 8 receiptInputComponents
pokeByteOff pointer 16 receiptConvexPieces
pokeByteOff pointer 24 receiptGeneratedPieces
pokeByteOff pointer 32 receiptGeneratedConvolutionEdges
pokeByteOff pointer 40 receiptOverlayPasses
pokeByteOff pointer 48 receiptExactCrossings
pokeByteOff pointer 56 receiptOutputCells
pokeByteOff pointer 64 receiptExactCoordinateBitGrowth
data AbiFailure = AbiFailure !CUInt !CObstruction
statusOk, statusNullPointer, statusCountOverflow, statusBufferTooSmall, statusGeometryObstruction, statusRuntimeFailure :: CUInt
statusOk = 0
statusNullPointer = 1
statusCountOverflow = 2
statusBufferTooSmall = 3
statusGeometryObstruction = 4
statusRuntimeFailure = 5
emptyObstruction :: CObstruction
emptyObstruction =
CObstruction
{ obstructionCode = 0
, obstructionCoordinateError = 0
, obstructionInputIndex = maxBound
, obstructionFirstIndex = 0
, obstructionSecondIndex = 0
, obstructionFirstValue = 0
, obstructionSecondValue = 0
, obstructionPointX = 0
, obstructionPointY = 0
, obstructionMessage = ""
}
apiFailure :: CUInt -> Word32 -> String -> AbiFailure
apiFailure status code message =
AbiFailure status emptyObstruction {obstructionCode = code, obstructionMessage = message}
geometryFailure :: Show obstruction => Word32 -> obstruction -> AbiFailure
geometryFailure code obstruction =
apiFailure statusGeometryObstruction code (show obstruction)
nullPointerFailure :: String -> AbiFailure
nullPointerFailure label = apiFailure statusNullPointer 100 (label <> " must not be null")
countOverflowFailure :: Word64 -> AbiFailure
countOverflowFailure count =
AbiFailure statusCountOverflow emptyObstruction
{ obstructionCode = 101
, obstructionFirstIndex = count
, obstructionMessage = "count exceeds the host Int range"
}
bufferTooSmallFailure :: Int -> Int -> AbiFailure
bufferTooSmallFailure required capacity =
AbiFailure statusBufferTooSmall emptyObstruction
{ obstructionCode = 102
, obstructionFirstIndex = fromIntegral required
, obstructionSecondIndex = fromIntegral capacity
, obstructionMessage = "output buffer is smaller than the required element count"
}
runtimeFailure :: SomeException -> AbiFailure
runtimeFailure = apiFailure statusRuntimeFailure 103 . displayException
runBoundary :: Ptr CObstruction -> IO (Either AbiFailure ()) -> IO CUInt
runBoundary obstructionPointer action = do
writeObstruction obstructionPointer emptyObstruction
outcome <- try action :: IO (Either SomeException (Either AbiFailure ()))
case outcome of
Left exception -> finishFailure obstructionPointer (runtimeFailure exception)
Right (Left failure) -> finishFailure obstructionPointer failure
Right (Right ()) -> pure statusOk
finishFailure :: Ptr CObstruction -> AbiFailure -> IO CUInt
finishFailure obstructionPointer (AbiFailure status obstruction) = do
writeObstruction obstructionPointer obstruction
pure status
writeObstruction :: Ptr CObstruction -> CObstruction -> IO ()
writeObstruction pointer obstruction
| pointer == nullPtr = pure ()
| otherwise = poke pointer obstruction
requirePointer :: String -> Ptr value -> Either AbiFailure ()
requirePointer label pointer
| pointer == nullPtr = Left (nullPointerFailure label)
| otherwise = Right ()
checkedCount :: Int -> CSize -> Either AbiFailure Int
checkedCount elementsPerItem rawCount
| toInteger rawCount * toInteger elementsPerItem > toInteger (maxBound :: Int) =
Left (countOverflowFailure (fromIntegral rawCount))
| otherwise = Right (fromIntegral rawCount)
readPoints :: Ptr CDouble -> Int -> IO (Either AbiFailure (V.Vector T.Point))
readPoints pointer count
| count == 0 = pure (Right V.empty)
| pointer == nullPtr = pure (Left (nullPointerFailure "coordinates"))
| otherwise =
Right
<$> V.generateM
count
( \index -> do
CDouble x <- peekElemOff pointer (index * 2)
CDouble y <- peekElemOff pointer (index * 2 + 1)
pure (T.Point x y)
)
prepareHandleOutput :: Ptr (Ptr carrier) -> IO (Either AbiFailure ())
prepareHandleOutput pointer =
case requirePointer "result" pointer of
Left failure -> pure (Left failure)
Right () -> poke pointer nullPtr >> pure (Right ())
publishHandle :: Ptr (Ptr carrier) -> HandleValue carrier -> IO ()
publishHandle output value = do
stable <- newStablePtr value
poke output (castPtr (castStablePtrToPtr stable))
produceHandle
:: Ptr (Ptr carrier)
-> IO (Either AbiFailure (HandleValue carrier))
-> IO (Either AbiFailure ())
produceHandle output obtain = do
prepared <- prepareHandleOutput output
case prepared of
Left failure -> pure (Left failure)
Right () -> do
outcome <- obtain
case outcome of
Left failure -> pure (Left failure)
Right value -> publishHandle output value >> pure (Right ())
dereferenceHandle :: Ptr carrier -> IO (HandleValue carrier)
dereferenceHandle pointer = deRefStablePtr (castPtrToStablePtr (castPtr pointer))
freeHandle :: Ptr carrier -> IO ()
freeHandle pointer
| pointer == nullPtr = pure ()
| otherwise = freeStablePtr (castPtrToStablePtr (castPtr pointer) :: StablePtr ())
buildFailure :: T.BuildError -> AbiFailure
buildFailure obstruction =
AbiFailure statusGeometryObstruction (buildErrorObstruction obstruction)
delaunayF64 :: Ptr CDouble -> CSize -> Ptr (Ptr CMesh) -> Ptr CObstruction -> IO CUInt
delaunayF64 coordinates rawCount output obstructionPointer =
runBoundary obstructionPointer $ produceHandle output $ do
case checkedCount 2 rawCount of
Left failure -> pure (Left failure)
Right count -> do
points <- readPoints coordinates count
pure (points >>= first buildFailure . T.delaunayGeometry)
meshInsertManyF64 :: Ptr CMesh -> Ptr CDouble -> CSize -> Ptr (Ptr CMesh) -> Ptr CObstruction -> IO CUInt
meshInsertManyF64 meshPointer coordinates rawCount output obstructionPointer =
runBoundary obstructionPointer $ produceHandle output $ do
case requirePointer "mesh" meshPointer >> checkedCount 2 rawCount of
Left failure -> pure (Left failure)
Right count -> do
points <- readPoints coordinates count
case points of
Left failure -> pure (Left failure)
Right admitted -> do
mesh <- dereferenceHandle meshPointer
pure (first buildFailure (insertGeometryBatch mesh admitted))
-- | Admission stays where it was — every point is validated before any point
-- is inserted, so a malformed input at the end still refuses the whole batch —
-- but it no longer materializes a second vector to carry the canonical
-- coordinates. @imapM@ over 'Either' cannot fill in place, so the discarded
-- form is the one that costs nothing; canonicalization is exactly what
-- @queryPointValue . validatePoint@ returned, applied where the point is used.
insertGeometryBatch :: GeometryMesh -> V.Vector T.Point -> Either T.BuildError GeometryMesh
insertGeometryBatch mesh points = do
V.imapM_ (\index point -> () <$ validatePoint (Just index) point) points
(_, revised, _) <-
Session.withSession
mesh
(V.length points)
(V.mapM_ (\point -> void (Session.insertVertexAt (canonicalPoint point) ())) points)
pure revised
meshSiteUnion, meshSiteIntersection, meshSiteDifference, meshSiteSymmetricDifference :: Ptr CMesh -> Ptr CMesh -> Ptr (Ptr CMesh) -> Ptr CObstruction -> IO CUInt
meshSiteUnion = binaryMeshOperation T.union
meshSiteIntersection = binaryMeshOperation T.intersection
meshSiteDifference = binaryMeshOperation T.difference
meshSiteSymmetricDifference = binaryMeshOperation T.symmetricDifference
binaryMeshOperation
:: (GeometryMesh -> GeometryMesh -> Either T.BuildError GeometryMesh)
-> Ptr CMesh
-> Ptr CMesh
-> Ptr (Ptr CMesh)
-> Ptr CObstruction
-> IO CUInt
binaryMeshOperation operation leftPointer rightPointer output obstructionPointer =
runBoundary obstructionPointer $ produceHandle output $ do
case requirePointer "left mesh" leftPointer >> requirePointer "right mesh" rightPointer of
Left failure -> pure (Left failure)
Right () -> do
left <- dereferenceHandle leftPointer
right <- dereferenceHandle rightPointer
pure (first buildFailure (operation left right))
meshVertexCount, meshTriangleCount :: Ptr CMesh -> Ptr CSize -> Ptr CObstruction -> IO CUInt
meshVertexCount = handleCount "mesh" T.numVertices
meshTriangleCount = handleCount "mesh" (V.length . T.innerFaceVertexTriples)
handleCount
:: String
-> (HandleValue carrier -> Int)
-> Ptr carrier
-> Ptr CSize
-> Ptr CObstruction
-> IO CUInt
handleCount handleLabel observe handlePointer output obstructionPointer =
runBoundary obstructionPointer $
case requirePointer handleLabel handlePointer >> requirePointer "count" output of
Left failure -> pure (Left failure)
Right () -> do
value <- dereferenceHandle handlePointer
poke output (fromIntegral (observe value))
pure (Right ())
meshCopyVerticesF64 :: Ptr CMesh -> Ptr CDouble -> CSize -> Ptr CSize -> Ptr CObstruction -> IO CUInt
meshCopyVerticesF64 =
copyHandleProjection
"mesh"
"points_written"
"coordinates"
T.vertexPoints
( \output index (T.Point x y) -> do
pokeElemOff output (index * 2) (CDouble x)
pokeElemOff output (index * 2 + 1) (CDouble y)
)
meshCopyTrianglesU32 :: Ptr CMesh -> Ptr Word32 -> CSize -> Ptr CSize -> Ptr CObstruction -> IO CUInt
meshCopyTrianglesU32 =
copyHandleProjection
"mesh"
"triangles_written"
"triangles"
T.innerFaceVertexTriples
( \output index (firstVertex, secondVertex, thirdVertex) -> do
pokeElemOff output (index * 3) (T.unVertexId firstVertex)
pokeElemOff output (index * 3 + 1) (T.unVertexId secondVertex)
pokeElemOff output (index * 3 + 2) (T.unVertexId thirdVertex)
)
copyHandleProjection
:: String
-> String
-> String
-> (HandleValue carrier -> V.Vector item)
-> (Ptr element -> Int -> item -> IO ())
-> Ptr carrier
-> Ptr element
-> CSize
-> Ptr CSize
-> Ptr CObstruction
-> IO CUInt
{-# INLINE copyHandleProjection #-}
copyHandleProjection handleLabel writtenLabel outputLabel project writeItem handlePointer output rawCapacity written obstructionPointer =
runBoundary obstructionPointer $
case requirePointer handleLabel handlePointer >> requirePointer writtenLabel written >> checkedCount 1 rawCapacity of
Left failure -> pure (Left failure)
Right capacity -> do
value <- dereferenceHandle handlePointer
let items = project value
required = V.length items
poke written (fromIntegral required)
case requireOutputCapacity outputLabel output required capacity of
Left failure -> pure (Left failure)
Right () -> V.imapM_ (writeItem output) items >> pure (Right ())
requireOutputCapacity :: String -> Ptr value -> Int -> Int -> Either AbiFailure ()
requireOutputCapacity label output required capacity
| capacity < required = Left (bufferTooSmallFailure required capacity)
| required > 0 = requirePointer label output
| otherwise = Right ()
meshFree :: Ptr CMesh -> IO ()
meshFree = freeHandle
data RegionCountKind
= LoopPointCounts
| ComponentLoopCounts
deriving stock (Eq, Show)
data RegionLayoutError
= RegionGroupEmpty !RegionCountKind !Int
| RegionCountTotalMismatch !RegionCountKind !Integer !Integer
| RegionStructuringElementEmpty
deriving stock (Eq, Show)
regionLayoutFailure :: RegionLayoutError -> AbiFailure
regionLayoutFailure layoutError =
AbiFailure statusGeometryObstruction (layoutObstruction layoutError)
layoutObstruction :: RegionLayoutError -> CObstruction
layoutObstruction layoutError =
(case layoutError of
RegionCountTotalMismatch _ actual expected ->
base
{ obstructionFirstIndex = fromIntegral actual
, obstructionSecondIndex = fromIntegral expected
}
RegionGroupEmpty _ index -> base {obstructionInputIndex = fromIntegral index}
RegionStructuringElementEmpty -> base
)
{obstructionMessage = show layoutError}
where
base = emptyObstruction {obstructionCode = 200}
regionValidationFailure :: T.RegionValidationError -> AbiFailure
regionValidationFailure = geometryFailure 201
overlayFailure :: T.OverlayError Bool Bool -> AbiFailure
overlayFailure = geometryFailure 202
regionPublicationFailure :: T.RegionPublicationError -> AbiFailure
regionPublicationFailure = geometryFailure 203
valuationFailure :: T.ValuationError -> AbiFailure
valuationFailure = geometryFailure 204
minkowskiFailure :: T.MinkowskiError -> AbiFailure
minkowskiFailure = geometryFailure 205
pointInputFailure :: Int -> T.Point -> T.PointValidationError -> AbiFailure
pointInputFailure index (T.Point x y) pointError =
AbiFailure statusGeometryObstruction emptyObstruction
{ obstructionCode = 1
, obstructionCoordinateError = coordinateErrorCode reason
, obstructionInputIndex = fromIntegral index
, obstructionFirstValue = invalidValue
, obstructionPointX = x
, obstructionPointY = y
, obstructionMessage = show pointError
}
where
(invalidValue, reason) =
case pointError of
T.InvalidPointX coordinateError -> (x, coordinateError)
T.InvalidPointY coordinateError -> (y, coordinateError)
projectionFailure :: Int -> T.PointValidationError -> AbiFailure
projectionFailure index pointError =
AbiFailure statusGeometryObstruction emptyObstruction
{ obstructionCode = 206
, obstructionCoordinateError =
coordinateErrorCode
(case pointError of
T.InvalidPointX coordinateError -> coordinateError
T.InvalidPointY coordinateError -> coordinateError)
, obstructionInputIndex = fromIntegral index
, obstructionMessage = show pointError
}
readCounts
:: String
-> Ptr CSize
-> Int
-> IO (Either AbiFailure (V.Vector Int))
readCounts _ _ 0 = pure (Right V.empty)
readCounts label pointer count
| pointer == nullPtr = pure (Left (nullPointerFailure label))
| otherwise = do
rawCounts <- V.generateM count (peekElemOff pointer)
pure (V.mapM (checkedCount 1) rawCounts)
validateCounts
:: RegionCountKind
-> Int
-> V.Vector Int
-> Either RegionLayoutError ()
validateCounts kind expectedTotal counts =
case V.findIndex (== 0) counts of
Just index -> Left (RegionGroupEmpty kind index)
Nothing
| observedTotal /= toInteger expectedTotal ->
Left (RegionCountTotalMismatch kind observedTotal (toInteger expectedTotal))
| otherwise -> Right ()
where
observedTotal = V.foldl' (\total count -> total + toInteger count) 0 counts
regionCreateF64
:: Ptr CDouble
-> CSize
-> Ptr CSize
-> CSize
-> Ptr CSize
-> CSize
-> Ptr (Ptr CRegion)
-> Ptr CObstruction
-> IO CUInt
regionCreateF64 coordinates rawPointCount loopPointCounts rawLoopCount componentLoopCounts rawComponentCount output obstructionPointer =
runBoundary obstructionPointer $
produceHandle output $
readRegionF64
coordinates
rawPointCount
loopPointCounts
rawLoopCount
componentLoopCounts
rawComponentCount
readRegionF64
:: Ptr CDouble
-> CSize
-> Ptr CSize
-> CSize
-> Ptr CSize
-> CSize
-> IO (Either AbiFailure T.PlanarRegion)
readRegionF64 coordinates rawPointCount loopPointCounts rawLoopCount componentLoopCounts rawComponentCount =
case checkedRegionInputCounts rawPointCount rawLoopCount rawComponentCount of
Left failure -> pure (Left failure)
Right (pointCount, loopCount, componentCount) -> do
points <- readPoints coordinates pointCount
loopCounts <- readCounts "loop_point_counts" loopPointCounts loopCount
componentCounts <- readCounts "component_loop_counts" componentLoopCounts componentCount
pure $ do
admittedPoints <- points
admittedLoopCounts <- loopCounts
admittedComponentCounts <- componentCounts
first regionLayoutFailure (validateCounts LoopPointCounts pointCount admittedLoopCounts)
first regionLayoutFailure (validateCounts ComponentLoopCounts loopCount admittedComponentCounts)
buildRegion admittedPoints admittedLoopCounts admittedComponentCounts
checkedRegionInputCounts
:: CSize
-> CSize
-> CSize
-> Either AbiFailure (Int, Int, Int)
checkedRegionInputCounts rawPointCount rawLoopCount rawComponentCount = do
pointCount <- checkedCount 2 rawPointCount
loopCount <- checkedCount 1 rawLoopCount
componentCount <- checkedCount 1 rawComponentCount
pure (pointCount, loopCount, componentCount)
buildRegion
:: V.Vector T.Point
-> V.Vector Int
-> V.Vector Int
-> Either AbiFailure T.PlanarRegion
buildRegion points loopCounts componentCounts = do
exactPoints <-
V.imapM
(\index point -> first (pointInputFailure index point) (T.exactPointFromPoint point))
points
loops <-
V.imapM
(buildLoop exactPoints)
(adjacentOffsets loopCounts)
components <-
V.imapM
(buildComponent loops)
(adjacentOffsets componentCounts)
first regionValidationFailure (T.planarRegion (V.toList components))
adjacentOffsets :: V.Vector Int -> V.Vector (Int, Int)
adjacentOffsets counts =
let offsets = V.scanl' (+) 0 counts
in V.zip offsets (V.drop 1 offsets)
buildLoop
:: V.Vector T.ExactPoint
-> Int
-> (Int, Int)
-> Either AbiFailure T.ExactLoop
buildLoop points loopIndex (start, end) =
case NonEmpty.nonEmpty (V.toList (V.slice start (end - start) points)) of
Nothing -> Left (regionLayoutFailure (RegionGroupEmpty LoopPointCounts loopIndex))
Just submitted -> first regionValidationFailure (T.exactLoop submitted)
buildComponent
:: V.Vector T.ExactLoop
-> Int
-> (Int, Int)
-> Either AbiFailure T.PolygonComponent
buildComponent loops componentIndex (start, end) =
case NonEmpty.nonEmpty (V.toList (V.slice start (end - start) loops)) of
Nothing -> Left (regionLayoutFailure (RegionGroupEmpty ComponentLoopCounts componentIndex))
Just (outer :| holes) -> first regionValidationFailure (T.polygonComponent outer holes)
regionShape
:: T.PlanarRegion
-> ([T.PolygonComponent], [[T.ExactLoop]], [T.ExactLoop])
regionShape region =
let components = T.planarRegionComponents region
componentLoops component = T.polygonOuterLoop component : T.polygonHoleLoops component
loopsByComponent = map componentLoops components
in (components, loopsByComponent, concat loopsByComponent)
regionCounts
:: Ptr CRegion
-> Ptr CSize
-> Ptr CSize
-> Ptr CSize
-> Ptr CObstruction
-> IO CUInt
regionCounts regionPointer componentCountOutput loopCountOutput pointCountOutput obstructionPointer =
runBoundary obstructionPointer $
case
requirePointer "region" regionPointer
>> requirePointer "component_count" componentCountOutput
>> requirePointer "loop_count" loopCountOutput
>> requirePointer "point_count" pointCountOutput
of
Left failure -> pure (Left failure)
Right () -> do
region <- dereferenceHandle regionPointer
let (components, _, loops) = regionShape region
pointCount = sum (map (NonEmpty.length . T.exactLoopPoints) loops)
poke componentCountOutput (fromIntegral (length components))
poke loopCountOutput (fromIntegral (length loops))
poke pointCountOutput (fromIntegral pointCount)
pure (Right ())
data RegionProjection = RegionProjection
{ projectionPoints :: !(V.Vector T.Point)
, projectionLoopPointOffsets :: !(V.Vector CSize)
, projectionComponentLoopOffsets :: !(V.Vector CSize)
}
regionProjection :: T.PlanarRegion -> Either AbiFailure RegionProjection
regionProjection region = do
let (_, loopsByComponent, loops) = regionShape region
exactPoints = concatMap (NonEmpty.toList . T.exactLoopPoints) loops
loopPointOffsets = scanl (+) 0 (map (NonEmpty.length . T.exactLoopPoints) loops)
componentLoopOffsets = scanl (+) 0 (map length loopsByComponent)
projectedPoints <-
V.fromList
<$> traverse
(uncurry projectPoint)
(zip [0 ..] exactPoints)
pure
RegionProjection
{ projectionPoints = projectedPoints
, projectionLoopPointOffsets = V.fromList (map fromIntegral loopPointOffsets)
, projectionComponentLoopOffsets = V.fromList (map fromIntegral componentLoopOffsets)
}
where
projectPoint index point =
first (projectionFailure index) (T.exactPointToEmbeddingCandidate point)
regionCopyF64
:: Ptr CRegion
-> Ptr CDouble
-> CSize
-> Ptr CSize
-> CSize
-> Ptr CSize
-> CSize
-> Ptr CObstruction
-> IO CUInt
regionCopyF64 regionPointer coordinates rawPointCapacity loopPointOffsets rawLoopOffsetCapacity componentLoopOffsets rawComponentOffsetCapacity obstructionPointer =
runBoundary obstructionPointer $
case
( (,,)
<$> (requirePointer "region" regionPointer >> checkedCount 2 rawPointCapacity)
<*> checkedCount 1 rawLoopOffsetCapacity
<*> checkedCount 1 rawComponentOffsetCapacity
)
of
Left failure -> pure (Left failure)
Right (pointCapacity, loopOffsetCapacity, componentOffsetCapacity) -> do
region <- dereferenceHandle regionPointer
case regionProjection region of
Left failure -> pure (Left failure)
Right RegionProjection {..} ->
case
requireOutputCapacity "coordinates" coordinates (V.length projectionPoints) pointCapacity
>> requireOutputCapacity "loop_point_offsets" loopPointOffsets (V.length projectionLoopPointOffsets) loopOffsetCapacity
>> requireOutputCapacity "component_loop_offsets" componentLoopOffsets (V.length projectionComponentLoopOffsets) componentOffsetCapacity
of
Left failure -> pure (Left failure)
Right () -> do
V.imapM_
(\index (T.Point x y) -> do
pokeElemOff coordinates (index * 2) (CDouble x)
pokeElemOff coordinates (index * 2 + 1) (CDouble y))
projectionPoints
V.imapM_ (pokeElemOff loopPointOffsets) projectionLoopPointOffsets
V.imapM_ (pokeElemOff componentLoopOffsets) projectionComponentLoopOffsets
pure (Right ())
regionUnion, regionIntersection, regionDifference, regionSymmetricDifference :: Ptr CRegion -> Ptr CRegion -> Ptr (Ptr CRegion) -> Ptr CObstruction -> IO CUInt
regionUnion = binaryRegionOperation (\(left, right) -> left || right)
regionIntersection = binaryRegionOperation (\(left, right) -> left && right)
regionDifference = binaryRegionOperation (\(left, right) -> left && not right)
regionSymmetricDifference = binaryRegionOperation (uncurry (/=))
binaryRegionOperation
:: ((Bool, Bool) -> Bool)
-> Ptr CRegion
-> Ptr CRegion
-> Ptr (Ptr CRegion)
-> Ptr CObstruction
-> IO CUInt
binaryRegionOperation selected leftPointer rightPointer output obstructionPointer =
runBoundary obstructionPointer $ produceHandle output $ do
case requirePointer "left region" leftPointer >> requirePointer "right region" rightPointer of
Left failure -> pure (Left failure)
Right () -> do
left <- dereferenceHandle leftPointer
right <- dereferenceHandle rightPointer
pure (exactRegionBoolean selected left right)
exactRegionBoolean
:: ((Bool, Bool) -> Bool)
-> T.PlanarRegion
-> T.PlanarRegion
-> Either AbiFailure T.PlanarRegion
exactRegionBoolean selected left right = do
leftLayer <- first regionValidationFailure (T.planarLayer False (Map.singleton True left))
rightLayer <- first regionValidationFailure (T.planarLayer False (Map.singleton True right))
overlay <- first overlayFailure (T.overlayLayers leftLayer rightLayer)
first regionPublicationFailure (T.overlaySelectedRegion selected overlay)
regionLocatePointF64
:: Ptr CRegion
-> CDouble
-> CDouble
-> Ptr CUInt
-> Ptr CObstruction
-> IO CUInt
regionLocatePointF64 regionPointer (CDouble x) (CDouble y) output obstructionPointer =
runBoundary obstructionPointer $
case
requirePointer "region" regionPointer
>> requirePointer "location" output
>> first (pointInputFailure 0 (T.Point x y)) (T.exactPointFromPoint (T.Point x y))
of
Left failure -> pure (Left failure)
Right query -> do
region <- dereferenceHandle regionPointer
poke output (regionLocationCode (T.regionPointLocation region query))
pure (Right ())
regionLocationCode :: T.RegionPointLocation -> CUInt
regionLocationCode location =
case location of
T.RegionExterior -> 0
T.RegionOnBoundary -> 1
T.RegionInterior -> 2
regionMeasure
:: Ptr CRegion
-> Ptr Int64
-> Ptr CChar
-> CSize
-> Ptr CSize
-> Ptr CDouble
-> Ptr CDouble
-> Ptr CObstruction
-> IO CUInt
regionMeasure regionPointer eulerOutput areaRatioOutput rawAreaCapacity areaBytesWritten perimeterLowerOutput perimeterUpperOutput obstructionPointer =
runBoundary obstructionPointer $
case
requirePointer "region" regionPointer
>> requirePointer "euler_characteristic" eulerOutput
>> requirePointer "area_bytes_written" areaBytesWritten
>> requirePointer "perimeter_lower" perimeterLowerOutput
>> requirePointer "perimeter_upper" perimeterUpperOutput
>> checkedCount 1 rawAreaCapacity
of
Left failure -> pure (Left failure)
Right areaCapacity -> do
region <- dereferenceHandle regionPointer
case regionMeasurements region of
Left failure -> pure (Left failure)
Right (valuations, perimeter) -> do
let area = T.exactAreaValue (T.valuationArea valuations)
areaText =
show (T.exactRationalNumerator area)
<> "/"
<> show (T.exactRationalDenominator area)
bounds = T.exactLengthBounds perimeter
poke eulerOutput (fromIntegral (T.eulerCharacteristicValue (T.valuationEuler valuations)))
poke perimeterLowerOutput (CDouble (T.intervalLower bounds))
poke perimeterUpperOutput (CDouble (T.intervalUpper bounds))
copyCStringOutput areaText areaRatioOutput areaCapacity areaBytesWritten
regionMeasurements
:: T.PlanarRegion
-> Either AbiFailure (T.PlanarValuations, T.ExactLengthMeasurement)
regionMeasurements region = do
valuations <- first valuationFailure (T.regionValuations region)
perimeter <- first valuationFailure (T.planarValuationsPerimeter valuations)
pure (valuations, perimeter)
copyCStringOutput
:: String
-> Ptr CChar
-> Int
-> Ptr CSize
-> IO (Either AbiFailure ())
copyCStringOutput value output capacity bytesWritten =
withCStringLen value $ \(source, byteCount) -> do
poke bytesWritten (fromIntegral byteCount)
case requireOutputCapacity "area_ratio_utf8" output (byteCount + 1) capacity of
Left failure -> pure (Left failure)
Right () -> do
copyBytes output source byteCount
pokeElemOff output byteCount 0
pure (Right ())
regionFree :: Ptr CRegion -> IO ()
regionFree = freeHandle
structuringElementCreateF64
:: Ptr CDouble
-> CSize
-> Ptr (Ptr CStructuringElement)
-> Ptr CObstruction
-> IO CUInt
structuringElementCreateF64 coordinates rawPointCount output obstructionPointer =
runBoundary obstructionPointer $ produceHandle output $ do
case checkedCount 2 rawPointCount of
Left failure -> pure (Left failure)
Right pointCount -> do
points <- readPoints coordinates pointCount
pure (points >>= buildStructuringElement)
buildStructuringElement
:: V.Vector T.Point
-> Either AbiFailure T.StructuringElement
buildStructuringElement points = do
exactPoints <-
V.imapM
(\index point -> first (pointInputFailure index point) (T.exactPointFromPoint point))
points
submitted <-
maybe
(Left (regionLayoutFailure RegionStructuringElementEmpty))
Right
(NonEmpty.nonEmpty (V.toList exactPoints))
polygon <- first minkowskiFailure (T.convexPolygon submitted)
first minkowskiFailure (T.structuringElement polygon)
structuringElementFree :: Ptr CStructuringElement -> IO ()
structuringElementFree = freeHandle
regionMinkowskiSum
:: Ptr CRegion
-> Ptr CRegion
-> Ptr (Ptr CRegion)
-> Ptr CMinkowskiReceipt
-> Ptr CObstruction
-> IO CUInt
regionMinkowskiSum leftPointer rightPointer output receiptOutput obstructionPointer =
runBoundary obstructionPointer $
produceRegionWithReceipt output receiptOutput $ do
case requirePointer "left region" leftPointer >> requirePointer "right region" rightPointer of
Left failure -> pure (Left failure)
Right () -> do
left <- dereferenceHandle leftPointer
right <- dereferenceHandle rightPointer
pure (first minkowskiFailure (T.minkowskiSum left right))
regionOffset, regionInset, regionOpen, regionClose :: Ptr CStructuringElement -> Ptr CRegion -> Ptr (Ptr CRegion) -> Ptr CMinkowskiReceipt -> Ptr CObstruction -> IO CUInt
regionOffset = structuringElementOperation T.polygonOffset
regionInset = structuringElementOperation T.polygonInset
regionOpen = structuringElementOperation T.openWith
regionClose = structuringElementOperation T.closeWith
structuringElementOperation
:: (T.StructuringElement -> T.PlanarRegion -> Either T.MinkowskiError (T.PlanarRegion, T.MinkowskiReceipt))
-> Ptr CStructuringElement
-> Ptr CRegion
-> Ptr (Ptr CRegion)
-> Ptr CMinkowskiReceipt
-> Ptr CObstruction
-> IO CUInt
structuringElementOperation operation elementPointer regionPointer output receiptOutput obstructionPointer =
runBoundary obstructionPointer $
produceRegionWithReceipt output receiptOutput $ do
case requirePointer "structuring element" elementPointer >> requirePointer "region" regionPointer of
Left failure -> pure (Left failure)
Right () -> do
element <- dereferenceHandle elementPointer
region <- dereferenceHandle regionPointer
pure (first minkowskiFailure (operation element region))
produceRegionWithReceipt
:: Ptr (Ptr CRegion)
-> Ptr CMinkowskiReceipt
-> IO (Either AbiFailure (T.PlanarRegion, T.MinkowskiReceipt))
-> IO (Either AbiFailure ())
produceRegionWithReceipt output receiptOutput obtain = do
prepared <- prepareHandleOutput output
case (prepared, requirePointer "receipt" receiptOutput) of
(Left failure, _) -> pure (Left failure)
(_, Left failure) -> pure (Left failure)
(Right (), Right ()) -> do
outcome <- obtain
case outcome of
Left failure -> pure (Left failure)
Right (region, receipt) -> do
poke receiptOutput (minkowskiReceiptProjection receipt)
publishHandle output region
pure (Right ())
minkowskiReceiptProjection :: T.MinkowskiReceipt -> CMinkowskiReceipt
minkowskiReceiptProjection receipt =
CMinkowskiReceipt
{ receiptOperation = minkowskiOperationCode (T.minkowskiOperation receipt)
, receiptInputComponents = fromIntegral (T.minkowskiInputComponents receipt)
, receiptConvexPieces = fromIntegral (T.minkowskiConvexPieces receipt)
, receiptGeneratedPieces = fromIntegral (T.minkowskiGeneratedPieces receipt)
, receiptGeneratedConvolutionEdges = fromIntegral (T.minkowskiGeneratedConvolutionEdges receipt)
, receiptOverlayPasses = fromIntegral (T.minkowskiOverlayPasses receipt)
, receiptExactCrossings = fromIntegral (T.minkowskiExactCrossings receipt)
, receiptOutputCells = fromIntegral (T.minkowskiOutputCells receipt)
, receiptExactCoordinateBitGrowth = fromIntegral (T.minkowskiExactCoordinateBitGrowth receipt)
}
minkowskiOperationCode :: T.MinkowskiOperation -> Word32
minkowskiOperationCode operation =
case operation of
T.MinkowskiAddition -> 0
T.MinkowskiErosion -> 1
T.MinkowskiOpening -> 2
T.MinkowskiClosing -> 3
buildErrorObstruction :: T.BuildError -> CObstruction
buildErrorObstruction failure =
(case failure of
T.InvalidCoordinate inputIndex value reason ->
emptyObstruction
{ obstructionCode = 1
, obstructionCoordinateError = coordinateErrorCode reason
, obstructionInputIndex = maybe maxBound fromIntegral inputIndex
, obstructionFirstValue = value
}
T.PointLocationFailed (T.Point x y) -> pointObstruction 2 x y
T.LocationWalkExhausted (T.Point x y) steps ->
(pointObstruction 3 x y) {obstructionFirstIndex = fromIntegral steps}
T.RefinementInputTopologyInvalid _ -> codeOnly 4
T.FreshInsertionMatchedExistingVertex firstVertex secondVertex -> indices 5 (T.unVertexId firstVertex) (T.unVertexId secondVertex)
T.DegenerateLineEndpointMissingOutgoing vertex -> firstIndex 6 (T.unVertexId vertex)
T.DegenerateLineEndpointTurnMissing index -> firstIndex 7 index
T.DegenerateLineConnectedVertexMissing index -> firstIndex 8 index
T.HullStartNotVisible edge -> firstIndex 9 (T.unDirectedEdgeId edge)
T.OuterRangeDidNotTerminate firstEdge secondEdge steps ->
(indices 10 (T.unDirectedEdgeId firstEdge) (T.unDirectedEdgeId secondEdge))
{obstructionFirstValue = fromIntegral steps}
T.OuterRangeContainsInnerEdge edge face -> indices 11 (T.unDirectedEdgeId edge) (T.unFaceId face)
T.ConstrainedEdgeFlipRefused edge -> firstIndex 12 (T.unUndirectedEdgeId edge)
T.RemovalVertexOutOfRange vertex count -> indices 13 (T.unVertexId vertex) count
T.RemovalEdgeOutOfRange edge count -> indices 14 (T.unUndirectedEdgeId edge) count
T.RemovalFaceOutOfRange face count -> indices 15 (T.unFaceId face) count
T.RemovalFaceCycleDidNotTerminate face edge steps ->
(indices 16 (T.unFaceId face) (T.unDirectedEdgeId edge))
{obstructionFirstValue = fromIntegral steps}
T.RemovalEmptyTriangulation vertex -> firstIndex 17 (T.unVertexId vertex)
T.RemovalTwoPointDegreeMismatch vertex degree -> indices 18 (T.unVertexId vertex) degree
T.RemovalCollinearDegreeMismatch vertex degree -> indices 19 (T.unVertexId vertex) degree
T.RemovalBorderTooShort count -> firstIndex 20 count
T.RemovalBorderArityMismatch count -> firstIndex 21 count
T.RemovalOutgoingCycleDidNotTerminate vertex edge steps ->
(indices 22 (T.unVertexId vertex) (T.unDirectedEdgeId edge))
{obstructionFirstValue = fromIntegral steps}
T.CircleSweepHullEmpty -> codeOnly 23
T.OuterCycleDidNotTerminate firstEdge secondEdge steps ->
(indices 24 (T.unDirectedEdgeId firstEdge) (T.unDirectedEdgeId secondEdge))
{obstructionFirstValue = fromIntegral steps}
T.HierarchyLevelPopulationMismatch level expected observed ->
(indices 25 expected observed) {obstructionFirstValue = fromIntegral level}
T.HierarchyInsertionHandleMismatch expected observed -> indices 26 (T.unVertexId expected) (T.unVertexId observed)
T.PointIndexCapacityExhausted count -> firstIndex 27 count
T.RefinementMinimumAngleNotFinite value -> nonFinite 28 value
T.RefinementMinimumAngleOutOfRange value -> firstValue 29 value
T.RefinementMinimumAngleDerivedRatioNotFinite value -> nonFinite 30 value
T.RefinementMaximumAdditionalVerticesNegative value -> firstValue 31 (fromIntegral value)
T.RefinementMinimumAreaNotFinite value -> nonFinite 32 value
T.RefinementMinimumAreaNegative value -> firstValue 33 value
T.RefinementMaximumAreaNotFinite value -> nonFinite 34 value
T.RefinementMaximumAreaNotPositive value -> firstValue 35 value
T.RefinementMaximumRadiusEdgeRatioNotFinite value -> nonFinite 36 value
T.RefinementMaximumRadiusEdgeRatioNotPositive value -> firstValue 37 value
T.RefinementMinimumAreaExceedsMaximum minimumArea maximumArea -> values 38 minimumArea maximumArea
T.RefinementSeedFaceNotActive face count -> indices 39 (T.unFaceId face) count
T.RefinementDomainInterfaceEdgeNotActive edge count -> indices 40 (T.unUndirectedEdgeId edge) count
T.RefinementDomainInterfaceMissing edge -> firstIndex 41 (T.unUndirectedEdgeId edge)
T.RefinementDomainInterfaceExtraneous edge -> firstIndex 42 (T.unUndirectedEdgeId edge)
T.RefinementDomainTopologyChanged -> codeOnly 43
T.RefinementDomainRequiresConvexHullPreservation -> codeOnly 44
T.RefinementDomainRequiresConstraintPreservation -> codeOnly 45
T.RefinementDomainForbidsOuterFaceExclusion -> codeOnly 46
T.RefinementDomainWouldCrossInterface edge face -> indices 47 (T.unUndirectedEdgeId edge) (T.unFaceId face)
T.RefinementDomainWouldRewriteProtectedFace face -> firstIndex 48 (T.unFaceId face)
T.RefinementDomainProtectedFaceChanged face -> firstIndex 49 (T.unFaceId face)
T.CapacityExceeded count -> firstIndex 50 count
T.HalfEdgeCapacityExceeded requested capacity -> indices 51 requested capacity
T.FaceCapacityExceeded requested capacity -> indices 52 requested capacity
T.PayloadStorageFailure _ -> codeOnly 53
T.CoordinatePayloadCountMismatch coordinates payloads -> indices 54 coordinates payloads
T.CircleSweepRequiresDenseStorage -> codeOnly 55
)
{obstructionMessage = show failure}
where
codeOnly :: Word32 -> CObstruction
codeOnly code = emptyObstruction {obstructionCode = code}
firstIndex :: Integral index => Word32 -> index -> CObstruction
firstIndex code index = (codeOnly code) {obstructionFirstIndex = fromIntegral index}
indices :: (Integral first, Integral second) => Word32 -> first -> second -> CObstruction
indices code firstIndexValue secondIndexValue =
(codeOnly code)
{ obstructionFirstIndex = fromIntegral firstIndexValue
, obstructionSecondIndex = fromIntegral secondIndexValue
}
firstValue :: Word32 -> Double -> CObstruction
firstValue code value = (codeOnly code) {obstructionFirstValue = value}
values :: Word32 -> Double -> Double -> CObstruction
values code firstCoordinateValue secondCoordinateValue =
(codeOnly code)
{ obstructionFirstValue = firstCoordinateValue
, obstructionSecondValue = secondCoordinateValue
}
pointObstruction :: Word32 -> Double -> Double -> CObstruction
pointObstruction code x y =
(codeOnly code)
{ obstructionPointX = x
, obstructionPointY = y
}
nonFinite :: Word32 -> T.NonFiniteValue -> CObstruction
nonFinite code value = firstIndex code (nonFiniteCode value)
coordinateErrorCode :: T.CoordinateError -> Word32
coordinateErrorCode reason =
case reason of
T.CoordinateNaN -> 1
T.CoordinateInfinite -> 2
T.CoordinateTooSmall -> 3
T.CoordinateTooLarge -> 4
nonFiniteCode :: T.NonFiniteValue -> Word32
nonFiniteCode value =
case value of
T.ValueNaN -> 1
T.ValuePositiveInfinity -> 2
T.ValueNegativeInfinity -> 3