packages feed

crocodile-0.1: app/src/IrradianceCache.hs

-- The irradiance cache
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE MagicHash #-}

module IrradianceCache (IrradianceCache, query, initialiseCache) where

import Vector
import Colour
import BoundingBox
import Octree
import SceneGraph

-- Irradiance gradient using a central-differencing approach
data IrradianceGradient = CentralDifferenceGradient {-# UNPACK #-} !(Colour, Colour, Colour)

-- Direction of normal, colour, radius
data CacheSample = CacheSample {-# UNPACK #-} !(Normal, Colour, Double)

type IrradianceCache = OctTree CacheSample

-- Pretty printer for cache samples
instance Show CacheSample where
    show (CacheSample (dir, col, r)) = "\tDirection: " ++ show dir ++ "\n\tColour: " ++ show col ++ "\n\tRadius: " ++ show r ++ "\n"

-- This gives an initial empty cache that will later be populated
initialiseCache :: SceneGraph -> IrradianceCache
initialiseCache sceneGraph = OctTreeNode slightlyEnlargedBox $ map OctTreeDummy (generateOctreeBoxList slightlyEnlargedBox)
    where
      -- Create the initial irradiance cache tree. This is a box a little larger than the world so that we fit any points offset along the normal etc
      slightlyEnlargedBox = growBoundingBox (finiteBox sceneGraph) 10

-- Quantify the error if we use a given sample to shade a point
-- The bigger the number, the better the estimate
errorWeight :: (Position, Direction) -> (Position, CacheSample) -> Double
{-# SPECIALIZE INLINE errorWeight :: (Position, Direction) -> (Position, CacheSample) -> Double #-}
errorWeight (pos', dir') (pos, CacheSample (dir, _, r)) = 1 / ((pos `distance` pos') / r + sqrt (1 + (dir `sdot3` dir')))

-- This slightly convoluted version is written to be tail recursive. I effectively have to maintain a software stack of the
-- nodes remaining to be traversed
findSamples :: (Position, Direction) -> [IrradianceCache] -> [(Vector, CacheSample, Double)] -> [(Vector, CacheSample, Double)]
findSamples posDir@(!pos, _) (OctTreeNode !box nodeChildren : xs) !acc
    | box `contains` pos = findSamples posDir (nodeChildren ++ xs) acc
    | otherwise = findSamples posDir xs acc
findSamples posDir@(pos, _) (OctTreeLeaf _ (samplePos, sample) : xs) !acc
    | (pos `distanceSq` samplePos) <= sampleR * sampleR && weight > minimumWeight = findSamples posDir xs ((samplePos, sample, weight) : acc)
    | otherwise = findSamples posDir xs acc
    where
      !weight = errorWeight posDir (samplePos, sample)
      (CacheSample (_, _, !sampleR)) = sample
      minimumWeight = 0.4 -- This is approximately the lower bound of the weight at the radius of the sample
findSamples posDir (OctTreeDummy _ : xs) !acc = findSamples posDir xs acc
findSamples _ [] !acc = acc

-- Sum together a list of samples and error weights
sumSamples :: [(Vector, CacheSample, Double)] -> Colour
sumSamples !samples = colourSum Colour.</> weightSum
    where
      sumSamples' !(!colAcc, !weightAcc) ((_, CacheSample (_, !col, _), !weight):xs) = sumSamples' (colAcc + col Colour.<*> weight, weightAcc + weight) xs
      sumSamples' !(!colAcc, !weightAcc) [] = (colAcc, weightAcc)
      !(!colourSum, !weightSum) = sumSamples' (colBlack, 0) samples

-- Query the irradiance given a point
-- Supplied function supplies the irradiance colour at a surface location along with the radius it is valid for
query :: IrradianceCache -> SurfaceLocation -> (SurfaceLocation -> (Colour, Double)) -> (Colour, IrradianceCache)
query irrCache !posTanSpace f = case findSamples (position, normal) [irrCache] [] of
                                  -- Insert a new cache sample
                                  [] -> let (!colour, !r) = f posTanSpace 
                                            !sample = CacheSample (normal, colour, r)
                                        in (colour, Octree.insert (fst posTanSpace) sample irrCache)
                                  -- Re-use existing cache samples
                                  list -> (sumSamples list, irrCache)
    where
      !position = fst posTanSpace
      !tanSpace = snd posTanSpace
      !normal = tsNormal tanSpace