gloss-examples-1.12.0.0: raster/Ray/Object.hs
{-# LANGUAGE BangPatterns #-}
module Object
( Color
, Object(..)
, translateObject
, castRay
, castRay_continuation
, checkRay
, surfaceNormal
, colorOfObject
, shineOfObject)
where
import Vec3
type Color = Vec3
-- | An object in the world
data Object
= Sphere
{ spherePos :: !Vec3
, sphereRadius :: !Float
, sphereColor :: !Color
, sphereShine :: !Float }
| Plane
{ planePos :: !Vec3
, planeNormal :: !Vec3
, planeColor :: !Color
, planeShine :: !Float }
| PlaneCheck
{ planeCheckPos :: !Vec3
, planeCheckNormal :: !Vec3
, planeCheckShine :: !Float }
deriving (Eq, Show)
translateObject :: Vec3 -> Object -> Object
translateObject v obj
= case obj of
Sphere{} -> obj { spherePos = spherePos obj + v }
Plane{} -> obj { planePos = planePos obj + v }
PlaneCheck{} -> obj { planeCheckPos = planeCheckPos obj + v }
{-# INLINE translateObject #-}
-- | Find the nearest point of intersection for a ray
castRay :: [Object] -- check for intersections on all these objects
-> Vec3 -- ray origin
-> Vec3 -- ray direction
-> Maybe
( Object -- object of first intersected
, Vec3) -- position of intersection, on surface of object
castRay !objs !orig !dir
= go0 objs
where -- We haven't hit any objects yet.
go0 [] = Nothing
go0 (obj:rest)
= case distanceToObject obj orig dir of
Nothing -> go0 rest
Just dist -> go1 rest obj dist
-- We hit an object before, and we're testing others
-- to see if they're closer.
go1 [] !objClose !dist
= Just (objClose, orig + dir `mulsV3` dist)
go1 (obj:rest) !objClose !dist
= case distanceToObject obj orig dir of
Nothing -> go1 rest objClose dist
Just dist'
| dist' < dist -> go1 rest obj dist'
| otherwise -> go1 rest objClose dist
{-# INLINE castRay #-}
-- | Like castRay, but take continuations for the Nothing and Just branches to
-- eliminate intermediate unboxings.
castRay_continuation
:: [Object] -- check for intersections on all these objects
-> Vec3 -- ray origin
-> Vec3 -- ray direction
-> a -- continuation when no intersection
-> (Object -> Vec3 -> a) -- continuation with intersection
-> a
castRay_continuation !objs !orig !dir contNone contJust
= go0 objs
where -- We haven't hit any objects yet.
go0 [] = contNone
go0 (obj:rest)
= case distanceToObject obj orig dir of
Nothing -> go0 rest
Just dist -> go1 rest obj dist
-- We hit an object before, and we're testing others
-- to see if they're closer.
go1 [] !objClose !dist
= contJust objClose (orig + dir `mulsV3` dist)
go1 (obj:rest) !objClose !dist
= case distanceToObject obj orig dir of
Nothing -> go1 rest objClose dist
Just dist'
| dist' < dist -> go1 rest obj dist'
| otherwise -> go1 rest objClose dist
{-# INLINE castRay_continuation #-}
-- | Simplified version of `castRay` that only checks whether there is some
-- object closer than a given mimimum distance.
checkRay :: [Object] -- ^ Check for intersection on all these objects.
-> Vec3 -- ^ Ray origin.
-> Vec3 -- ^ Ray direction.
-> Float -- ^ Minimum distance.
-> Bool
checkRay !objs !orig !dir !dist
= go0 objs
where go0 [] = False
go0 (obj:rest)
= case distanceToObject obj orig dir of
Nothing -> go0 rest
Just dist'
| dist' < dist -> True
| otherwise -> go0 rest
{-# INLINE checkRay #-}
-- | Compute the distance to the surface of this shape
distanceToObject
:: Object -- ^ Towards this object.
-> Vec3 -- ^ Start from this point.
-> Vec3 -- ^ Along this ray.
-> Maybe Float -- ^ Distance to intersection, if there is one.
distanceToObject !obj !orig !dir
= case obj of
Sphere pos radius _ _
-> let !p = orig + dir `mulsV3` ((pos - orig) `dotV3` dir)
!d_cp = magnitudeV3 (p - pos)
in if d_cp >= radius then Nothing
else if (p - orig) `dotV3` dir <= 0.0 then Nothing
else Just $ magnitudeV3 (p - orig) - sqrt (radius * radius - d_cp * d_cp)
Plane pos normal _ _
-> if dotV3 dir normal >= 0.0
then Nothing
else Just (((pos - orig) `dotV3` normal) / (dir `dotV3` normal))
PlaneCheck pos normal _
-> if dotV3 dir normal >= 0.0
then Nothing
else Just (((pos - orig) `dotV3` normal) / (dir `dotV3` normal))
{-# INLINE distanceToObject #-}
-- | Compute the surface normal of the shape at this point
surfaceNormal
:: Object
-> Vec3 -- ^ A point on the surface of the shape.
-> Vec3
surfaceNormal obj point
= case obj of
Sphere pos _ _ _ -> normaliseV3 (point - pos)
Plane _ normal _ _ -> normal
PlaneCheck _ normal _ -> normal
{-# INLINE surfaceNormal #-}
-- | Get the color of an object at the given point.
colorOfObject :: Object -> Vec3 -> Color
colorOfObject obj point
= case obj of
Sphere _ _ c _ -> c
Plane _ _ c _ -> c
PlaneCheck{} -> checkers point
{-# INLINE colorOfObject #-}
-- | Get the shine of an object at the given point.
shineOfObject :: Object -> Vec3 -> Float
shineOfObject obj _point
= case obj of
Sphere _ _ _ s -> s
Plane _ _ _ s -> s
PlaneCheck _ _ s -> s
{-# INLINE shineOfObject #-}
-- | A checkerboard pattern along the x/z coords
checkers :: Vec3 -> Vec3
checkers (Vec3 x _ z)
| ((truncate (z / 100.0) :: Int)`mod` 2 == 0)
`xor` ((truncate (x / 100.0) :: Int) `mod` 2 == 0)
`xor` (x < 0.0)
`xor` (z < 0.0)
= Vec3 1.0 1.0 1.0
| otherwise
= Vec3 0.4 0.4 0.4
xor :: Bool -> Bool -> Bool
xor x1 x2
= case (x1, x2) of
(False, False) -> False
(False, True) -> True
(True, False) -> True
(True, True) -> False
{-# INLINE xor #-}