packages feed

raytrace-0.1.0.0: test/Main.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE PartialTypeSignatures #-}
{-# LANGUAGE ViewPatterns #-}
module Main where

import Graphics.Ray

import Linear (V3(V3), (*^), normalize, norm, (!*!))
import System.Random (StdGen, newStdGen, randomR, random, mkStdGen)
import Control.Monad.State (State, runState, state)
import Control.Monad (forM, replicateM)
import Control.Applicative (liftA2)
import Data.Functor.Identity (Identity)

sky :: Ray -> Color
sky (Ray _ (normalize -> V3 _ y _)) = 
  let a = 0.5 * (y + 1) in
  (1 - a) *^ V3 1 1 1 + a *^ V3 0.5 0.7 1

metalTest :: IO ()
metalTest = let
  materialGround = lambertian (constantTexture (V3 0.8 0.8 0.0))
  materialCenter = lambertian (constantTexture (V3 0.1 0.2 0.5))
  materialLeft = dielectric 1.5
  materialBubble = dielectric (1 / 1.5)
  materialRight = metal 1.0 (constantTexture (V3 0.8 0.6 0.2))

  world = group 
    [ materialGround <$ sphere (V3 0 (-100.5) (-1)) 100
    , materialCenter <$ sphere (V3 0 0 (-1.2)) 0.5
    , materialLeft <$ sphere (V3 (-1) 0 (-1)) 0.5 
    , materialBubble <$ sphere (V3 (-1) 0 (-1)) 0.4
    , materialRight <$ sphere (V3 1 0 (-1)) 0.5
    ]

  settings = defaultCameraSettings 
    { cs_aspectRatio = 16/9
    , cs_imageWidth = 400
    , cs_samplesPerPixel = 100
    , cs_maxRecursionDepth = 50
    , cs_background = sky
    , cs_center = V3 (-2) 2 1
    , cs_lookAt = V3 0 0 (-1)
    , cs_vfov = degrees 20
    , cs_defocusAngle = degrees 10
    , cs_focusDist = 3.4
    }

  in do
  seed <- newStdGen
  writeImageSqrt "test_image.png" $ raytrace settings world seed

noiseTest :: IO ()
noiseTest = let
  groundMaterial = lambertian (noiseTexture 2 2.0 (V3 10 0 0) 0 1)
  ballMaterial = lambertian (marbleTexture (V3 0 0 1) 4 0)

  world = group 
    [ groundMaterial <$ sphere (V3 0 (-1000) 0) 1000
    , ballMaterial <$ sphere (V3 0 2 0) 2
    ]
  
  settings = defaultCameraSettings
    { cs_aspectRatio = 16 / 9
    , cs_imageWidth = 400
    , cs_samplesPerPixel = 100
    , cs_maxRecursionDepth = 50
    , cs_background = sky
    , cs_vfov = degrees 20
    , cs_center = V3 13 2 3
    , cs_lookAt = V3 0 0 0
    }
    
  in do
  seed <- newStdGen
  writeImageSqrt "noise_test.png" $ raytrace settings world seed

quadTest :: IO ()
quadTest = let
  red = lambertian (constantTexture (V3 1.0 0.2 0.2))
  green = lambertian (constantTexture (V3 0.2 1.0 0.2))
  blue = lambertian (constantTexture (V3 0.2 0.2 1.0))
  orange = lambertian (constantTexture (V3 1.0 0.5 0.0))
  teal = lambertian (constantTexture (V3 0.2 0.8 0.8))

  world = group
    [ red <$ parallelogram (V3 (-3) (-2) 5) (V3 0 0 (-4)) (V3 0 4 0) 
    , green <$ parallelogram (V3 (-2) (-2) 0) (V3 4 0 0) (V3 0 4 0)
    , blue <$ parallelogram (V3 3 (-2) 1) (V3 0 0 4) (V3 0 4 0)
    , orange <$ parallelogram (V3 (-2) 3 1) (V3 4 0 0) (V3 0 0 4)
    , teal <$ parallelogram (V3 (-2) (-3) 5) (V3 4 0 0) (V3 0 0 (-4))
    ]
  
  settings = defaultCameraSettings
    { cs_aspectRatio = 1
    , cs_imageWidth = 400
    , cs_samplesPerPixel = 100
    , cs_maxRecursionDepth = 50
    , cs_background = sky
    , cs_vfov = degrees 80
    , cs_center = V3 0 0 9
    , cs_lookAt = V3 0 0 0
    }

  in do
  seed <- newStdGen
  writeImageSqrt "test_image.png" $ raytrace settings world seed

cuboidTest :: IO ()
cuboidTest = do
  globe <- readImage "images/earthmap.jpg"
  let globeMaterial = lambertian (imageTexture globe)
  let object = globeMaterial <$ cuboid (fromCorners (-V3 1 2 0.5) (V3 1 2 0.5))
  let world = transform (translate (V3 0 0 (-3)) !*! rotateX (degrees 60)) object
  let settings = defaultCameraSettings { cs_imageWidth = 300 }
  writeImage "test_image.png" . raytrace settings world =<< newStdGen

sphereUVTest :: IO ()
sphereUVTest = do
  globe <- readImage "images/earthmap.jpg"
  let globeMaterial = lambertian (imageTexture globe)
  let world = globeMaterial <$ group [ sphere (V3 0 0 (-2)) 0.4, sphere (V3 0 0 (-1)) 0.4 ]
  let settings = defaultCameraSettings { cs_imageWidth = 1, cs_samplesPerPixel = 1, cs_vfov = 0.0001}
  writeImage "test_image.png" (raytrace settings world (mkStdGen 12))

demo1 :: IO ()
demo1 = let
  materialGround = lambertian (constantTexture (V3 0.5 0.5 0.5))
  materialGlass = dielectric 1.5
  materialDiffuse = lambertian (constantTexture (V3 0.4 0.2 0.1))
  materialMirror = mirror (constantTexture (V3 0.7 0.6 0.5))

  bigSpheres =
    [ materialGround <$ sphere (V3 0 (-1000) 0) 1000
    , materialGlass <$ sphere (V3 0 1 0) 1
    , materialDiffuse <$ sphere (V3 (-4) 1 0) 1
    , materialMirror <$ sphere (V3 4 1 0) 1
    ]

  genWorld :: State StdGen (Geometry Identity Material)
  genWorld = do
    fmap (bvhTree . autoTree . (bigSpheres ++) . concat) $ forM (liftA2 (,) [-11..10] [-11..10]) $ \(a, b) -> do
      offsetX <- state (randomR (0, 0.9))
      offsetZ <- state (randomR (0, 0.9))
      let center = V3 (a + offsetX) 0.2 (b + offsetZ)

      if norm (center - V3 4 0.2 0) <= 0.9 then pure [] else do
        chooseMat <- state random
        mat <- 
          if (chooseMat :: Double) < 0.8 then do
            color <- liftA2 (*) (state random) (state random)
            pure (lambertian (constantTexture color))
          else if chooseMat < 0.95 then do
            fuzz <- state (randomR (0, 0.5))
            color <- state (randomR (0.5, 1))
            pure (metal fuzz (constantTexture color))
          else pure materialGlass
        pure [ mat <$ sphere center 0.2 ]
  
  settings = defaultCameraSettings
    { cs_aspectRatio = 16 / 9
    , cs_imageWidth = 1200
    , cs_samplesPerPixel = 500
    , cs_maxRecursionDepth = 50
    , cs_vfov = degrees 20
    , cs_center = V3 13 2 3
    , cs_lookAt = V3 0 0 0
    , cs_defocusAngle = degrees 0.6
    , cs_focusDist = 10
    , cs_background = sky
    }

  in do
  seed <- newStdGen
  let (world, seed') = runState genWorld seed
  writeImageSqrt "test_image.png" $ raytrace settings world seed'

cornellBox :: Int -> Int -> IO ()
cornellBox samplesPerPixel maxRecurionDepth = let
  red = lambertian (constantTexture (V3 0.65 0.05 0.05))
  white = lambertian (constantTexture (V3 0.73 0.73 0.73))
  green = lambertian (constantTexture (V3 0.12 0.45 0.15))
  light = lightSource (constantTexture (V3 15 15 15))

  world = group
    [ green <$ parallelogram (V3 555 0 0) (V3 0 555 0) (V3 0 0 555)
    , red <$ parallelogram (V3 0 0 0) (V3 0 555 0) (V3 0 0 555)
    , light <$ parallelogram (V3 343 554 332) (V3 (-130) 0 0) (V3 0 0 (-105))
    , white <$ parallelogram (V3 0 0 0) (V3 555 0 0) (V3 0 0 555)
    , white <$ parallelogram (V3 555 555 555) (V3 (-555) 0 0) (V3 0 0 (-555))
    , white <$ parallelogram (V3 0 0 555) (V3 555 0 0) (V3 0 555 0)
    , transform (translate (V3 265 0 295) !*! rotateY (degrees 15)) $ white <$ cuboid (fromCorners (V3 0 0 0) (V3 165 330 165))
    , transform (translate (V3 130 0 65) !*! rotateY (degrees (-18))) $ white <$ cuboid (fromCorners (V3 0 0 0) (V3 165 165 165))
    ]

  settings = defaultCameraSettings
    { cs_aspectRatio = 1.0
    , cs_imageWidth = 600
    , cs_samplesPerPixel = samplesPerPixel
    , cs_maxRecursionDepth = maxRecurionDepth
    , cs_background = const (V3 0 0 0)
    , cs_vfov = degrees 40
    , cs_center = V3 278 278 (-800)
    , cs_lookAt = V3 278 278 0
    }

  in writeImageSqrt "cornell_box.png" . raytrace settings world =<< newStdGen

cornellSmoke :: IO ()
cornellSmoke = let
  red = lambertian (constantTexture (V3 0.65 0.05 0.05))
  white = lambertian (constantTexture (V3 0.73 0.73 0.73))
  green = lambertian (constantTexture (V3 0.12 0.45 0.15))
  light = lightSource (constantTexture (V3 7 7 7))

  surfaces = group
    [ green <$ parallelogram (V3 555 0 0) (V3 0 555 0) (V3 0 0 555)
    , red <$ parallelogram (V3 0 0 0) (V3 0 555 0) (V3 0 0 555)
    , light <$ parallelogram (V3 113 554 127) (V3 330 0 0) (V3 0 0 305)
    , white <$ parallelogram (V3 0 0 0) (V3 555 0 0) (V3 0 0 555)
    , white <$ parallelogram (V3 555 555 555) (V3 (-555) 0 0) (V3 0 0 (-555))
    , white <$ parallelogram (V3 0 0 555) (V3 555 0 0) (V3 0 555 0)
    ]
  
  cube1 = transform (translate (V3 265 0 295) !*! rotateY (degrees 15)) $ cuboid (fromCorners (V3 0 0 0) (V3 165 330 165))
  cube2 = transform (translate (V3 130 0 65) !*! rotateY (degrees (-18))) $ cuboid (fromCorners (V3 0 0 0) (V3 165 165 165))
  
  world = group
    [ pureGeometry surfaces
    , pitchBlack <$ constantMedium 0.01 cube1
    , isotropic (constantTexture 1) <$ constantMedium 0.01 cube2
    ]

  settings = defaultCameraSettings
    { cs_aspectRatio = 1.0
    , cs_imageWidth = 600
    , cs_samplesPerPixel = 200
    , cs_maxRecursionDepth = 50
    , cs_background = const (V3 0 0 0)
    , cs_vfov = degrees 40
    , cs_center = V3 278 278 (-800)
    , cs_lookAt = V3 278 278 0
    }

  in writeImageSqrt "cornell_smoke.png" . raytrace settings world =<< newStdGen

demo2 :: FilePath -> Int -> Int -> Int -> IO ()
demo2 path imageWidth samplesPerPixel maxRecursionDepth = let
  ground = lambertian (constantTexture (V3 0.48 0.83 0.53))
  white = lambertian (constantTexture (V3 0.73 0.73 0.73))

  generateBoxes :: State StdGen (Geometry Identity Material)
  generateBoxes = 
    fmap ((ground <$) . bvhTree . autoTree) $ 
    forM (liftA2 (,) [0..19] [0..19]) $ \(i, j) -> do
      let x0 = -1000 + i * 100
      let z0 = -1000 + j * 100
      let x1 = x0 + 100
      let z1 = z0 + 100
      let y0 = 0
      y1 <- state (randomR (1, 101))
      pure (cuboid (fromCorners (V3 x0 y0 z0) (V3 x1 y1 z1)))
  
  generateBalls :: State StdGen (Geometry Identity Material)
  generateBalls =
    fmap ((white <$) . transform (translate (V3 (-100) 270 395) !*! rotateY (degrees 15)) . bvhTree . autoTree) $
    replicateM 1000 $ do
      p <- state (randomR (0, 165)) 
      pure (sphere p 10)
  
  boundary = sphere (V3 360 150 145) 70 
  
  largeObjects earth =
    [ lightSource (constantTexture (V3 7 7 7)) <$ parallelogram (V3 123 554 147) (V3 300 0 0) (V3 0 0 265)
    , lambertian (constantTexture (V3 0.7 0.3 0.1)) <$ sphere (V3 415 400 200) 50
    , dielectric 1.5 <$ sphere (V3 260 150 45) 50
    , dielectric 1.5 <$ boundary
    , metal 1.0 (constantTexture (V3 0.8 0.8 0.9)) <$ sphere (V3 0 150 145) 50
    , lambertian (imageTexture earth) <$ transform (translate (V3 400 0 400) !*! rotateY (pi/2)) (sphere (V3 0 200 0) 100)
    , lambertian (marbleTexture (V3 0 0 0.05) 4 0) <$ sphere (V3 220 280 300) 80
    ]
  
  generateWorld earth = do
    boxes <- generateBoxes
    balls <- generateBalls
    pure $ group 
      [ pureGeometry (group (boxes : balls : largeObjects earth))
      , isotropic (constantTexture 1) <$ constantMedium 0.0001 (sphere (V3 0 0 0) 5000)
      , isotropic (constantTexture (V3 0.2 0.4 0.9)) <$ constantMedium 0.2 boundary
      ]
  
  settings = defaultCameraSettings
    { cs_center = V3 478 278 (-600)
    , cs_lookAt = V3 278 278 0
    , cs_vfov = degrees 40
    , cs_aspectRatio = 1.0
    , cs_imageWidth = imageWidth
    , cs_samplesPerPixel = samplesPerPixel
    , cs_maxRecursionDepth = maxRecursionDepth
    , cs_background = const 0
    }

  in do
    earth <- readImage "images/earthmap.jpg"
    seed <- newStdGen
    let (world, seed') = runState (generateWorld earth) seed
    writeImageSqrt path (raytrace settings world seed')

-- This should take less than 110 seconds
cornellTest :: IO ()
cornellTest = cornellBox 200 50

-- This should take less than 70 seconds
demoTest :: IO ()
demoTest = demo2 "test_image.png" 400 250 4

main :: IO ()
main = noiseTest