packages feed

accelerate-examples-0.15.0.0: examples/ray/Ray/Trace.hs

{-# LANGUAGE ScopedTypeVariables #-}

module Ray.Trace
  where

-- friends
import Vec3
import Scene.Object
import Scene.Light
import Ray.Intersect

-- frenemies
import Data.Array.Accelerate                                    as A
import Graphics.Gloss.Accelerate.Data.Color.RGB
import Graphics.Gloss.Accelerate.Data.Point

-- standard library
import Prelude                                                  as P


-- | Generate all of the rays that will be cast from the given eye position to
--   cover the entire field of view.
--
castViewRays
    :: Int                              -- width of the display
    -> Int                              -- height
    -> Int                              -- field of view
    -> Exp Position                     -- eye position
    -> Acc (Array DIM2 Direction)       -- all rays originating from the eye position
castViewRays sizeX sizeY fov eyePos
  = let
        sizeX'          = P.fromIntegral sizeX
        sizeY'          = P.fromIntegral sizeY
        aspect          = sizeX' / sizeY'
        fov'            = P.fromIntegral fov
        fovX            = fov' * aspect
        fovY            = fov'
    in
    A.generate (constant (Z :. sizeY :. sizeX))
               (\ix -> let (x, y) = xyOfPoint $ pointOfIndex sizeX sizeY ix
                       in  normalise $ makeVec3 (x * fovX) ((-y) * fovY) 0 - eyePos)


-- | Cast a single ray into the scene
--
traceRay
    :: Int                              -- ^ Maximum reflection count
    -> Acc Objects                      -- ^ Objects in the scene
    -> Acc Lights                       -- ^ Direct lighting in the scene
    -> Exp Color                        -- ^ Ambient light in the scene
    -> Exp Position                     -- ^ Origin of the ray
    -> Exp Direction                    -- ^ Direction of the ray
    -> Exp Color
traceRay limit objects lights ambient = go limit
  where
    (spheres, planes)   = unlift objects

    dummySphere         = constant (Sphere (XYZ 0 0 0) 0           (RGB 0 0 0) 0)
    dummyPlane          = constant (Plane  (XYZ 0 0 0) (XYZ 0 0 1) (RGB 0 0 0) 0)

    -- Stop once there are too many reflections, in case we've found two
    -- parallel mirrors.
    --
    go 0 _ _
      = black

    go bounces orig dir
      = let
            -- See which objects the ray intersects. Since we have no sum
            -- types, we need to do this separately for each object type,
            -- and determine the closest separately.
            --
            (hit_s, dist_s, s)  = unlift $ castRay distanceToSphere dummySphere spheres orig dir
            (hit_p, dist_p, p)  = unlift $ castRay distanceToPlane  dummyPlane  planes  orig dir
        in
        A.not (hit_s ||* hit_p) ?
          -- ray didn't intersect any objects
        ( black

          -- ray hit an object
        , let
              -- Determine the intersection point, and surface properties that
              -- will contribute to the colour
              next_s      = hitSphere     s dist_s orig dir
              next_p      = hitPlaneCheck p dist_p orig dir

              (point, normal, color, shine)
                          = unlift (dist_s <* dist_p ? ( next_s, next_p ))

              -- result angle of ray after reflection
              newdir      = dir - (2.0 * (normal `dot` dir)) .* normal

              -- determine the direct lighting at this point
              direct      = applyLights objects lights point normal

              -- see if the ray hits anything else
              refl        = go (bounces - 1) point newdir

              -- total lighting is the direct lighting plus ambient
              lighting    = direct + ambient

              -- total incoming light is direct lighting plus reflections
              light_in    = scaleColour shine         refl
                          + scaleColour (1.0 - shine) lighting

              -- outgoing light is incoming light modified by surface color.
              -- We also need to clip it in case the sum of all incoming
              -- lights is too bright to display.
              light_out   = clampColor (light_in * color)
          in
          light_out
        )


scaleColour :: Exp Float -> Exp Color -> Exp Color
scaleColour s c
  = let (r,g,b) = rgbOfColor c
    in  rawColor (r * s) (g * s) (b * s)

hitSphere :: Exp Sphere -> Exp Float -> Exp Position -> Exp Direction -> Exp (Position, Direction, Color, Float)
hitSphere sph dist orig dir
  = let
        point   = orig + dist .* dir
        normal  = sphereNormal sph point
        color   = sphereColor sph
        shine   = sphereShine sph
    in
    lift (point, normal, color, shine)

hitPlane :: Exp Plane -> Exp Float -> Exp Position -> Exp Direction -> Exp (Position, Direction, Color, Float)
hitPlane pln dist orig dir
  = let
        point   = orig + dist .* dir
        normal  = planeNormal pln
        color   = planeColor pln
        shine   = planeShine pln
    in
    lift (point, normal, color, shine)

hitPlaneCheck :: Exp PlaneCheck -> Exp Float -> Exp Position -> Exp Direction -> Exp (Position, Direction, Color, Float)
hitPlaneCheck pln dist orig dir
  = let
        point   = orig + dist .* dir
        normal  = planeCheckNormal pln
        color   = checkers point
        shine   = planeCheckShine pln
    in
    lift (point, normal, color, shine)