packages feed

moonlight-linalg-0.1.0.0: src-geometry/Moonlight/LinAlg/Pure/Geometry/AABB.hs

module Moonlight.LinAlg.Pure.Geometry.AABB
  ( AABB,
    mkAabb,
    aabbMin,
    aabbMax,
    aabbDimensions,
    aabbCenter,
    aabbHalfExtent,
    aabbRadius,
    symmetricAabb,
    unionAabb,
    expandAabb,
    intersectAabbMaybe,
    translateAabb,
    transformAabb,
    scaleAabb,
  )
where

import Control.DeepSeq (NFData (..))
import Data.Kind (Type)
import Moonlight.Algebra.Pure.Lattice (JoinSemilattice (..))
import Moonlight.LinAlg.Pure.Geometry.Transform.Affine
  ( AffineTransform,
    affineMaxScale,
    atTranslation,
  )
import Moonlight.LinAlg.Pure.Geometry.Vec3
  ( Vec3 (..),
    addVec3,
    averageVec3,
    mapVec3,
    maxVec3,
    minVec3,
    mulVec3,
    scaleVec3,
    subVec3,
  )
import Prelude (Double, Eq, Maybe (..), Ord, Show, abs, seq, sqrt, (*), (+), (<=), (&&))

type AABB :: Type
data AABB = AABB
  { aabbMin :: {-# UNPACK #-} !Vec3,
    aabbMax :: {-# UNPACK #-} !Vec3
  }
  deriving stock (Eq, Ord, Show)

instance NFData AABB where
  rnf aabbValue = aabbValue `seq` ()

instance JoinSemilattice AABB where
  join leftAabb rightAabb =
    AABB
      { aabbMin = minVec3 (aabbMin leftAabb) (aabbMin rightAabb),
        aabbMax = maxVec3 (aabbMax leftAabb) (aabbMax rightAabb)
      }

mkAabb :: Vec3 -> Vec3 -> Maybe AABB
mkAabb minimumCorner maximumCorner =
  let Vec3 minX minY minZ = minimumCorner
      Vec3 maxX maxY maxZ = maximumCorner
   in if minX <= maxX && minY <= maxY && minZ <= maxZ
        then Just (AABB minimumCorner maximumCorner)
        else Nothing

aabbDimensions :: AABB -> Vec3
aabbDimensions aabbValue =
  subVec3 (aabbMax aabbValue) (aabbMin aabbValue)

aabbCenter :: AABB -> Vec3
aabbCenter aabbValue =
  averageVec3 (aabbMin aabbValue) (aabbMax aabbValue)

aabbHalfExtent :: AABB -> Vec3
aabbHalfExtent aabbValue =
  scaleVec3 0.5 (aabbDimensions aabbValue)

aabbRadius :: AABB -> Double
aabbRadius aabbValue =
  let Vec3 halfX halfY halfZ = aabbHalfExtent aabbValue
   in sqrt (halfX * halfX + halfY * halfY + halfZ * halfZ)

symmetricAabb :: Double -> Double -> Double -> Maybe AABB
symmetricAabb halfX halfY halfZ =
  mkAabb
    (Vec3 (-halfX) (-halfY) (-halfZ))
    (Vec3 halfX halfY halfZ)

unionAabb :: AABB -> AABB -> AABB
unionAabb = join

expandAabb :: Double -> AABB -> Maybe AABB
expandAabb radius aabbValue =
  if 0.0 <= radius
    then
      mkAabb
        (shiftVec3 (-radius) (aabbMin aabbValue))
        (shiftVec3 radius (aabbMax aabbValue))
    else Nothing

intersectAabbMaybe :: AABB -> AABB -> Maybe AABB
intersectAabbMaybe leftAabb rightAabb =
  mkAabb
    (maxVec3 (aabbMin leftAabb) (aabbMin rightAabb))
    (minVec3 (aabbMax leftAabb) (aabbMax rightAabb))

translateAabb :: Vec3 -> AABB -> AABB
translateAabb translationVector aabbValue =
  AABB
    { aabbMin = addVec3 translationVector (aabbMin aabbValue),
      aabbMax = addVec3 translationVector (aabbMax aabbValue)
    }

transformAabb :: AffineTransform -> AABB -> AABB
transformAabb affineTransform aabbValue =
  let radius = aabbRadius aabbValue * affineMaxScale affineTransform
      translatedCenter = addVec3 (atTranslation affineTransform) (aabbCenter aabbValue)
   in AABB
        { aabbMin = shiftVec3 (-radius) translatedCenter,
          aabbMax = shiftVec3 radius translatedCenter
        }

scaleAabb :: Vec3 -> AABB -> AABB
scaleAabb scaleVector aabbValue =
  let center = aabbCenter aabbValue
      halfExtent = aabbHalfExtent aabbValue
      scaledCenter = mulVec3 scaleVector center
      scaledHalfExtent = mulVec3 (mapVec3 abs scaleVector) halfExtent
   in AABB
        { aabbMin = subVec3 scaledCenter scaledHalfExtent,
          aabbMax = addVec3 scaledCenter scaledHalfExtent
        }

shiftVec3 :: Double -> Vec3 -> Vec3
shiftVec3 offset = mapVec3 (+ offset)