packages feed

crocodile-0.1.1: app/src/Main.hs

-- Main module of raytracer

import Data.Bits
import Data.ByteString hiding (map)
import System.Console.GetOpt
import System.Environment
import RayTrace
import Colour
import SceneGraph
import KDTree
import CornellBox
import GHC.Conc (numCapabilities)
import Codec.BMP
import PhotonMap
import RenderContext
import Light
import ToneMap
import Control.Arrow

-- Command line option support
data Option
    = ShowIntermediate -- -i
    | PhotonMap -- -p
    | DirectPhotonMapVisualisation -- -v
    | DistributedRayTracing -- d
    | IrradianceCaching -- c
      deriving (Eq, Ord, Enum, Show, Bounded)

options :: [OptDescr Option]
options = [
    Option ['i'] [] (NoArg ShowIntermediate) "Show intermediates",
    Option ['p'] [] (NoArg PhotonMap) "Photon map",
    Option ['v'] [] (NoArg DirectPhotonMapVisualisation) "Direct photon map visualisation",
    Option ['d'] [] (NoArg DistributedRayTracing) "Distributed ray tracing",
    Option ['c'] [] (NoArg IrradianceCaching) "Irradiance caching"
    ]

parsedOptions :: [String] -> [Option]
parsedOptions argv = case getOpt Permute options argv of
        (args,_,[]) -> args
        (_,_,_) -> []

-- Some hardcoded values, at present
renderWidth :: Int -> Int
renderWidth mipLevel = 1280 `shiftR` mipLevel

renderHeight :: Int -> Int
renderHeight mipLevel = 720 `shiftR` mipLevel

-- This returns a list of colours of pixels
renderImage :: Int -> RenderContext -> Maybe PhotonMap -> [Colour]
renderImage mipLevel renderSettings photonMap = finalImage
    where
      rawImageOutput = rayTraceImage renderSettings cornellBoxCamera (renderWidth mipLevel) (renderHeight mipLevel) photonMap
      exposedImage = exposeImage imageAverageLogLuminance rawImageOutput 4
      toneMappedImage = toneMapImage toneMapHejlBurgessDawson exposedImage
      finalImage = map (clamp . invGammaCorrect) toneMappedImage

-- In the interest of rapid developer feedback, this functions writes a progressively-increasing image
-- So, we get quick feedback on the intermediate results, but will still ultimately get the final image
-- Note this does no re-use, so it'll be slower overall
writeRaytracedImage :: [Int] -> Maybe PhotonMap -> RenderContext -> IO ()
writeRaytracedImage [] photonMap renderSettings = do
  let imageData = renderImage 0 renderSettings photonMap
  let rgba = Data.ByteString.pack (convertColoursToPixels imageData)
  let bmp = packRGBA32ToBMP (renderWidth 0) (renderHeight 0) rgba
  Prelude.putStrLn "Performing final render"
  writeBMP "test.bmp" bmp
writeRaytracedImage (mipLevel:mipLevels) photonMap renderSettings = do
  let imageData = renderImage mipLevel renderSettings photonMap
  let rgba = Data.ByteString.pack (convertColoursToPixels imageData)
  let bmp = packRGBA32ToBMP (renderWidth mipLevel) (renderHeight mipLevel) rgba
  let filename = "test-intermediate-" ++ show mipLevel ++ ".bmp"
  Prelude.putStrLn filename
  writeBMP filename bmp
  writeRaytracedImage mipLevels photonMap renderSettings

-- Strip off the photon map flag from a light
notInPhotonMap :: Light -> Light
notInPhotonMap (PointLight (CommonLightData colour' _) position' range') = PointLight (CommonLightData colour' False) position' range'
notInPhotonMap (AmbientLight (CommonLightData colour' _)) = AmbientLight (CommonLightData colour' False)
notInPhotonMap (QuadLight (CommonLightData colour' _) position' range' deltaU' deltaV') = QuadLight (CommonLightData colour' False) position' range' deltaU' deltaV'

-- Main function
main :: IO ()
main = do 
  args <- getArgs
  let opts = parsedOptions args

  let renderSettings = RenderContext 
                       numDistributedSamples 
                       (buildSceneGraph cornellBox generateSceneGraphUsingKDTree) 
                       cornellBoxLights 
                       maxRayDepth 
                       reflectionDistance 
                       refractionDistance 
                       (PhotonMapContext photonGatherDistance maxGatherPhotons coneFilterConstant directPhotonMapVisualisation) 
                       rayOriginDistribution'
                       depthOfFieldFocalDistance'
                       renderMode'
                       enableIrradianceCache
       where
         -- Ray trace constants
         numDistributedSamples = if DistributedRayTracing `Prelude.elem` opts 
                                 then 64 
                                 else 1
         maxRayDepth = 5
         reflectionDistance = 1000
         refractionDistance = 1000
         -- Photon constants
         photonGatherDistance = 100
         maxGatherPhotons = 200
         coneFilterConstant = 2
         -- Depth of field constants
         rayOriginDistribution' = 0.5
         depthOfFieldFocalDistance' = 400
         renderMode'
             | PhotonMap `Prelude.elem` opts = PhotonMapper
             | otherwise = RayTrace
         directPhotonMapVisualisation = DirectPhotonMapVisualisation `Prelude.elem` opts
         enableIrradianceCache = IrradianceCaching `Prelude.elem` opts

  -- Display hardware capabilities
  Prelude.putStrLn $ "Running on " ++ show numCapabilities ++ " cores"

  -- Create a photon map, if necessary
  let doPhotonMapping = PhotonMap `Prelude.elem` opts
  let photonMapMessage = if doPhotonMapping 
                         then if DirectPhotonMapVisualisation `Prelude.elem` opts
                              then "Directly visualising photon map"
                              else "Creating photon map..." 
                         else "Photon mapping disabled"
  Prelude.putStrLn photonMapMessage
  let thousand = 1000
  let numPhotons = 200 * thousand
  let (photonMap, lights')
          | doPhotonMapping = Control.Arrow.first Just $ 
                              buildPhotonMap (sceneGraph renderSettings) cornellBoxLights numPhotons
          | otherwise = (Nothing, map notInPhotonMap (lights renderSettings))

  -- Display message about irradiance cache
  Prelude.putStrLn (if useIrradianceCache renderSettings then "Irradiance caching enabled" else "Irrradiance caching disabled")

  -- Render the image
  let renderSettings' = renderSettings { lights = lights' }
  let maxMipLevel = 8
  let intermediateMipLevels = if ShowIntermediate `Prelude.elem` opts
                              then Prelude.reverse [1..maxMipLevel]
                              else []
  Prelude.putStrLn "Rendering image..."
  writeRaytracedImage intermediateMipLevels photonMap renderSettings'