moonlight-triangulation-1.0.1.0: src-ffi/Moonlight/Triangulation/Foreign/ABI.hs
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RecordWildCards #-}
module Moonlight.Triangulation.Foreign.ABI
( CObstruction (..)
, delaunayF64
, meshInsertManyF64
, meshUnion
, meshIntersection
, meshDifference
, meshSymmetricDifference
, meshVertexCount
, meshTriangleCount
, meshCopyVerticesF64
, meshCopyTrianglesU32
, meshFree
) where
import Control.Exception (SomeException, displayException, try)
import Control.Monad (void)
import Data.Foldable (traverse_)
import Data.Word (Word32, Word64)
import Foreign.C.String (peekCString, withCStringLen)
import Foreign.C.Types (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 (validatePoint)
import qualified Moonlight.Triangulation.Session as Session
type GeometryMesh = T.DelaunayTriangulation ()
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 (undefined :: 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
fillBytes messagePointer 0 256
withCStringLen obstructionMessage $ \(source, lengthInBytes) ->
copyBytes messagePointer source (min 255 lengthInBytes)
data AbiFailure = AbiFailure !CUInt !CObstruction
statusOk, statusNullPointer, statusCountOverflow, statusBufferTooSmall, statusBuildObstruction, statusRuntimeFailure :: CUInt
statusOk = 0
statusNullPointer = 1
statusCountOverflow = 2
statusBufferTooSmall = 3
statusBuildObstruction = 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}
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)
)
prepareMeshOutput :: Ptr (Ptr ()) -> IO (Either AbiFailure ())
prepareMeshOutput pointer =
case requirePointer "result" pointer of
Left failure -> pure (Left failure)
Right () -> poke pointer nullPtr >> pure (Right ())
publishMesh :: Ptr (Ptr ()) -> GeometryMesh -> IO ()
publishMesh output mesh = do
stable <- newStablePtr mesh
poke output (castStablePtrToPtr stable)
produceMesh :: Ptr (Ptr ()) -> IO (Either AbiFailure (Either T.BuildError GeometryMesh)) -> IO (Either AbiFailure ())
produceMesh output obtain = do
prepared <- prepareMeshOutput output
case prepared of
Left failure -> pure (Left failure)
Right () -> do
outcome <- obtain
case outcome of
Left failure -> pure (Left failure)
Right (Left obstruction) ->
pure (Left (AbiFailure statusBuildObstruction (buildErrorObstruction obstruction)))
Right (Right mesh) -> publishMesh output mesh >> pure (Right ())
delaunayF64 :: Ptr CDouble -> CSize -> Ptr (Ptr ()) -> Ptr CObstruction -> IO CUInt
delaunayF64 coordinates rawCount output obstructionPointer =
runBoundary obstructionPointer $ produceMesh output $ do
case checkedCount 2 rawCount of
Left failure -> pure (Left failure)
Right count -> fmap (fmap T.delaunayGeometry) (readPoints coordinates count)
meshInsertManyF64 :: Ptr () -> Ptr CDouble -> CSize -> Ptr (Ptr ()) -> Ptr CObstruction -> IO CUInt
meshInsertManyF64 meshPointer coordinates rawCount output obstructionPointer =
runBoundary obstructionPointer $ produceMesh output $ do
case (requirePointer "mesh" meshPointer, checkedCount 2 rawCount) of
(Left failure, _) -> pure (Left failure)
(_, Left failure) -> pure (Left failure)
(Right (), Right count) -> do
pointsOutcome <- readPoints coordinates count
case pointsOutcome of
Left failure -> pure (Left failure)
Right points -> do
mesh <- dereferenceMesh meshPointer
pure (Right (insertGeometryBatch mesh points))
insertGeometryBatch :: GeometryMesh -> V.Vector T.Point -> Either T.BuildError GeometryMesh
insertGeometryBatch mesh points = do
normalized <-
V.imapM
(\index point -> T.queryPointValue <$> validatePoint (Just index) point)
points
(_, revised, _) <-
Session.withSession
mesh
(V.length normalized)
(traverse_ (\point -> void (Session.insertVertexAt point ())) normalized)
pure revised
meshUnion, meshIntersection, meshDifference, meshSymmetricDifference :: Ptr () -> Ptr () -> Ptr (Ptr ()) -> Ptr CObstruction -> IO CUInt
meshUnion = binaryMeshOperation T.union
meshIntersection = binaryMeshOperation T.intersection
meshDifference = binaryMeshOperation T.difference
meshSymmetricDifference = binaryMeshOperation T.symmetricDifference
binaryMeshOperation :: (GeometryMesh -> GeometryMesh -> Either T.BuildError GeometryMesh) -> Ptr () -> Ptr () -> Ptr (Ptr ()) -> Ptr CObstruction -> IO CUInt
binaryMeshOperation operation leftPointer rightPointer output obstructionPointer =
runBoundary obstructionPointer $ produceMesh output $ do
case (requirePointer "left mesh" leftPointer, requirePointer "right mesh" rightPointer) of
(Left failure, _) -> pure (Left failure)
(_, Left failure) -> pure (Left failure)
(Right (), Right ()) -> do
left <- dereferenceMesh leftPointer
right <- dereferenceMesh rightPointer
pure (Right (operation left right))
meshVertexCount, meshTriangleCount :: Ptr () -> Ptr CSize -> Ptr CObstruction -> IO CUInt
meshVertexCount = meshCount T.numVertices
meshTriangleCount = meshCount (V.length . T.innerFaceVertexTriples)
meshCount :: (GeometryMesh -> Int) -> Ptr () -> Ptr CSize -> Ptr CObstruction -> IO CUInt
meshCount observe meshPointer output obstructionPointer =
runBoundary obstructionPointer $
case (requirePointer "mesh" meshPointer, requirePointer "count" output) of
(Left failure, _) -> pure (Left failure)
(_, Left failure) -> pure (Left failure)
(Right (), Right ()) -> do
mesh <- dereferenceMesh meshPointer
poke output (fromIntegral (observe mesh))
pure (Right ())
meshCopyVerticesF64 :: Ptr () -> Ptr CDouble -> CSize -> Ptr CSize -> Ptr CObstruction -> IO CUInt
meshCopyVerticesF64 =
copyMeshProjection
"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 () -> Ptr Word32 -> CSize -> Ptr CSize -> Ptr CObstruction -> IO CUInt
meshCopyTrianglesU32 =
copyMeshProjection
"triangles_written"
"triangles"
T.innerFaceVertexTriples
( \output index (first, second, third) -> do
pokeElemOff output (index * 3) (T.unVertexId first)
pokeElemOff output (index * 3 + 1) (T.unVertexId second)
pokeElemOff output (index * 3 + 2) (T.unVertexId third)
)
copyMeshProjection
:: String -> String -> (GeometryMesh -> V.Vector item) -> (Ptr element -> Int -> item -> IO ())
-> Ptr () -> Ptr element -> CSize -> Ptr CSize -> Ptr CObstruction -> IO CUInt
{-# INLINE copyMeshProjection #-}
copyMeshProjection writtenLabel outputLabel project writeItem meshPointer output rawCapacity written obstructionPointer =
runBoundary obstructionPointer $
case (requirePointer "mesh" meshPointer, requirePointer writtenLabel written, checkedCount 1 rawCapacity) of
(Left failure, _, _) -> pure (Left failure)
(_, Left failure, _) -> pure (Left failure)
(_, _, Left failure) -> pure (Left failure)
(Right (), Right (), Right capacity) -> do
mesh <- dereferenceMesh meshPointer
let items = project mesh
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 ()
dereferenceMesh :: Ptr () -> IO GeometryMesh
dereferenceMesh = deRefStablePtr . (castPtrToStablePtr :: Ptr () -> StablePtr GeometryMesh)
meshFree :: Ptr () -> IO ()
meshFree pointer
| pointer == nullPtr = pure ()
| otherwise = freeStablePtr ((castPtrToStablePtr pointer) :: StablePtr GeometryMesh)
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 first second -> indices 5 (T.unVertexId first) (T.unVertexId second)
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 first second steps ->
(indices 10 (T.unDirectedEdgeId first) (T.unDirectedEdgeId second))
{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 first second steps ->
(indices 24 (T.unDirectedEdgeId first) (T.unDirectedEdgeId second))
{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
)
{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 first second =
(codeOnly code)
{ obstructionFirstIndex = fromIntegral first
, obstructionSecondIndex = fromIntegral second
}
firstValue :: Word32 -> Double -> CObstruction
firstValue code value = (codeOnly code) {obstructionFirstValue = value}
values :: Word32 -> Double -> Double -> CObstruction
values code first second =
(codeOnly code)
{ obstructionFirstValue = first
, obstructionSecondValue = second
}
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