moonlight-planar-1.1.0.0: src-core/Moonlight/Planar/Internal/Point.hs
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
-- | Coordinate values and finite-query admission, independent of mesh storage.
-- Trusted mesh owners may construct QueryPoint from already-admitted planes.
module Moonlight.Planar.Internal.Point where
import Control.DeepSeq (NFData (..))
import Foreign.Ptr (castPtr)
import Foreign.Storable (Storable (..), peekElemOff, pokeElemOff)
import GHC.Generics (Generic)
import Moonlight.Planar.Scalar
( CoordinateError, canonicalScalarZero, minimumAllowedCoordinate, validateCoordinate )
-- | Cartesian binary64 point.
data Point = Point
{ pointX :: !Double
, pointY :: !Double
}
deriving stock (Eq, Ord, Show, Generic)
deriving anyclass (NFData)
-- | A coordinate pair admitted to the exact-predicate domain and normalized
-- at its construction boundary. Query algorithms consume this phase rather
-- than each inventing a fallback for invalid floating-point input.
newtype QueryPoint = QueryPoint Point
deriving stock (Eq, Ord, Show)
instance NFData QueryPoint where
rnf (QueryPoint point) = rnf point
{-# INLINE rnf #-}
-- | Read the admitted coordinate without exposing a record-update route around
-- finite-coordinate admission and canonical normalization.
queryPointValue :: QueryPoint -> Point
queryPointValue (QueryPoint point) = point
{-# INLINE queryPointValue #-}
-- | Coordinate axis and reason that kept a point outside the query domain.
data PointValidationError
= InvalidPointX !CoordinateError
| InvalidPointY !CoordinateError
deriving stock (Eq, Ord, Show, Generic)
deriving anyclass (NFData)
instance Storable (Point) where
sizeOf _ = 2 * sizeOf (0 :: Double)
alignment _ = alignment (0 :: Double)
peek pointer = do
x <- peekElemOff (castPtr pointer) 0
y <- peekElemOff (castPtr pointer) 1
pure (Point x y)
poke pointer (Point x y) = do
pokeElemOff (castPtr pointer) 0 x
pokeElemOff (castPtr pointer) 1 y
-- | Extract a vertex's position once, at the construction boundary.
class HasPosition vertex where
position :: vertex -> Point
instance HasPosition (Point) where
position = id
-- | Admit and normalize a finite point for read-only geometric queries.
mkQueryPoint :: Point -> Either PointValidationError (QueryPoint)
mkQueryPoint point@(Point x y) = do
maybe (Right ()) (Left . InvalidPointX) (validateCoordinate x)
maybe (Right ()) (Left . InvalidPointY) (validateCoordinate y)
Right (QueryPoint (canonicalPoint point))
-- | Round coordinates below the robust-predicate input floor toward zero.
-- The operation never changes a coordinate already accepted by
-- 'validateCoordinate'.
mitigateUnderflow :: Point -> Point
mitigateUnderflow (Point x y) = Point (mitigate x) (mitigate y)
where
mitigate :: Double -> Double
mitigate value
| value /= 0 && abs value < minimumAllowedCoordinate = 0
| otherwise = value
-- | Canonicalize both coordinate components for point identity.
canonicalPoint :: Point -> Point
canonicalPoint (Point x y) = Point (canonicalScalarZero x) (canonicalScalarZero y)
{-# INLINE canonicalPoint #-}