packages feed

waterfall-cad-0.2.0.0: src/Waterfall/BoundingBox/Oriented.hs

module Waterfall.BoundingBox.Oriented
( OrientedBoundingBox
, obbCenter
, obbSideX
, obbSideY
, obbSideZ
, orientedBoundingBox
, obbToSolid
) where

import Linear (V3 (..), normalize, (^*))
import Waterfall.Internal.Solid (Solid (..), acquireSolid, solidFromAcquire)
import Waterfall.Internal.Finalizers (toAcquire, unsafeFromAcquire)
import Waterfall.Internal.FromOpenCascade (gpXYZToV3)
import Foreign.Ptr (Ptr)
import Waterfall.Solids (volume, box)
import Waterfall.Transforms (translate)
import qualified OpenCascade.Bnd.OBB as OBB
import OpenCascade.Bnd.OBB (OBB)
import OpenCascade.GP.Types (XYZ)
import qualified OpenCascade.GP as GP
import qualified OpenCascade.GP.Ax3 as Ax3
import qualified OpenCascade.GP.Trsf as Trsf
import qualified OpenCascade.BRepBndLib as BRepBndLib
import qualified OpenCascade.BRepBuilderAPI.Transform  as BRepBuilderAPI.Transform
import Data.Acquire (Acquire)
import Control.Monad.IO.Class (liftIO)
import Control.Monad ((<=<))

-- | An OrientedBoundingBox may be a tighter fit for a Shape than an axis aligned bounding box would be
-- 
data OrientedBoundingBox = OrientedBoundingBox { rawOBB :: Ptr OBB }

-- | Compute an OrientedBoundingBox for a solid
orientedBoundingBox :: Solid -> Maybe OrientedBoundingBox
orientedBoundingBox s = 
    if volume s == 0
        then Nothing 
        else Just . OrientedBoundingBox . unsafeFromAcquire $ do
            obb <- OBB.new
            solid <- acquireSolid s
            liftIO $ BRepBndLib.addOBB solid obb True True True
            return obb

queryOBB :: (Ptr OBB -> Acquire (V3 Double)) -> OrientedBoundingBox -> V3 Double
queryOBB f = unsafeFromAcquire . ( f <=< toAcquire . rawOBB)

-- | The center point of an `OrientedBoundingBox`
obbCenter :: OrientedBoundingBox -> V3 Double
obbCenter = queryOBB (liftIO . gpXYZToV3 <=< OBB.center)

getSide :: (Ptr OBB -> Acquire (Ptr XYZ)) -> (Ptr OBB -> IO Double) -> OrientedBoundingBox -> V3 Double
getSide fxyz fLength = queryOBB $ \obb -> do
    side <- liftIO . gpXYZToV3 =<< fxyz obb
    len <- liftIO $ fLength obb
    return $ normalize side ^* len

-- | The "X" side of the oriented bounding box.
--
-- This is measured from the center to one Face.
-- So the length of this vector is _half_ of the side length of the bounding box.
obbSideX :: OrientedBoundingBox -> V3 Double 
obbSideX = getSide OBB.xDirection OBB.xHSize
    
-- | The "Y" side of the oriented bounding box.
--
-- This is measured from the center to one face.
-- So the length of this vector is _half_ of the side length of the bounding box.
obbSideY :: OrientedBoundingBox -> V3 Double 
obbSideY = getSide OBB.yDirection OBB.yHSize
    
-- | the "Z" side of the oriented bounding box
--
-- This is measured from the center to one face.
-- So the length of this vector is _half_ of the side length of the bounding box.
obbSideZ :: OrientedBoundingBox -> V3 Double
obbSideZ =  getSide OBB.zDirection OBB.zHSize

-- | Reify an `OrientedBoundingBox` as a `Solid`
obbToSolid :: OrientedBoundingBox -> Solid
obbToSolid obb = solidFromAcquire $ do
    obb' <- toAcquire . rawOBB $ obb
    x <- liftIO . OBB.xHSize $ obb'
    y <- liftIO . OBB.yHSize $ obb'
    z <- liftIO . OBB.zHSize $ obb'
    let halfBox = V3 x y z
    unpositioned <- acquireSolid . translate (negate halfBox) $ box (V3 x y z ^* 2)
    position <- OBB.position obb'
    o <- Ax3.fromAx2 =<< GP.xoy
    trsf <- Trsf.new
    liftIO $ Trsf.setDisplacement trsf o position
    BRepBuilderAPI.Transform.transform unpositioned trsf True