content: {-# LANGUAGE DeriveAnyClass    #-}
  {-# LANGUAGE DeriveGeneric     #-}
  {-# LANGUAGE NoImplicitPrelude #-}
  {-# LANGUAGE PatternSynonyms   #-}
  
  module Scene.Light
    where
  
  -- friends
  import Common.Type
  import Ray.Intersect
  import Scene.Object
  
  -- frenemies
  import Data.Array.Accelerate                                    as A
  import Data.Array.Accelerate.Data.Colour.Names
  import Data.Array.Accelerate.Data.Colour.RGB
  import Data.Array.Accelerate.Linear.Metric
  import Data.Array.Accelerate.Linear.Vector
  
  
  -- | An omnidirectional point light source, whose intensity drops off with
  --   distance from the source.
  --
  data Light = Light_ Position Colour
    deriving (Show, Generic, Elt)
  
  type Lights = Array DIM1 Light
  
  pattern Light :: Exp Position -> Exp Colour -> Exp Light
  pattern Light { lightPos, lightColor } = Pattern (lightPos, lightColor)
  {-# COMPLETE Light #-}
  
  
  -- | Compute the direct lighting contribution of all lights acting on a point on
  -- a surface at a given normal direction.
  --
  --    normal
  --         ^           x light
  --         |        .
  --         |     . objects?
  --         |  .
  --   ______x______ surface
  --       point
  --
  applyLights
      :: Acc Objects
      -> Acc Lights
      -> Exp Position
      -> Exp Direction
      -> Exp Colour
  applyLights objects lights point normal
    = sfoldl (\c l -> c + applyLight objects point normal l) (constant black) (constant Z) lights
  
  
  -- | Compute the direct lighting at a particular point for a single light
  --
  applyLight
      :: Acc Objects              -- possible occluding objects, used for shadows
      -> Exp Position             -- point which is being lit
      -> Exp Direction            -- surface normal at this point
      -> Exp Light                -- does this light contribute colour to this point?
      -> Exp Colour
  applyLight objects point normal light
    = let
          (spheres, planes)       = unlift objects
  
          -- What is the direction and distance from the light source to the point
          -- on the surface?
          --
          lp_p                    = lightPos light - point
          dist                    = norm lp_p
          dir                     = (1.0 / dist) *^ lp_p
  
          -- Calculate the magnitude of the reflected light, if there are no
          -- occluding objects between the light and the surface point.
          --
          mag                     = (normal `dot` dir) / (dist * dist)
          RGB r g b               = unlift (lightColor light)
          refl                    = lift $ RGB (r * mag) (g * mag) (b * mag)
      in
      checkRay distanceToSphere spheres point dir dist || checkRay distanceToPlane planes point dir dist
        ? ( constant black, refl )
  
  