content: 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
  