implicit-0.4.0.0: Graphics/Implicit/Export/RayTrace.hs
{- ORMOLU_DISABLE -}
-- Implicit CAD. Copyright (C) 2011, Christopher Olah (chris@colah.ca)
-- Copyright 2016, Julia Longtin (julial@turinglace.com)
-- Released under the GNU AGPLV3+, see LICENSE
module Graphics.Implicit.Export.RayTrace( Color(Color), average, Camera(Camera), Light(Light), Scene(Scene), traceRay, cameraRay) where
import Prelude(Show, RealFrac, Maybe(Just, Nothing), Bool(False, True), (-), (.), ($), (*), (/), min, fromInteger, max, round, fromIntegral, unzip, fmap, length, sum, maximum, minimum, (>), (+), (<), (==), pred, flip, not, abs, floor, toRational, otherwise, pure)
-- Our number system, and the definition of a 3D object.
import Graphics.Implicit.Definitions (ℝ, Fastℕ, ℝ2, ℝ3, Obj3)
import Codec.Picture (Pixel8)
import Control.Monad (guard)
import Control.Arrow ((***))
import Linear
( V3(V3), cross, Metric(dot, norm), V2(V2), normalize, (*^) )
default (Fastℕ, ℝ)
-- Definitions
data Camera = Camera ℝ3 ℝ3 ℝ3 ℝ
deriving Show
-- | A ray. A point, and a normal pointing in the direction the ray is going.
data Ray = Ray ℝ3 ℝ3
deriving Show
data Scene = Scene Obj3 Color [Light] Color
-- | A light source. source point, and intensity.
data Light = Light ℝ3 ℝ
deriving Show
-- | A colour. Red Green Blue and Alpha components.
data Color = Color Pixel8 Pixel8 Pixel8 Pixel8
-- Math
-- | The distance traveled by a line segment from the first point to the second point.
vectorDistance :: ℝ3 -> ℝ3 -> ℝ
vectorDistance a b = norm (b-a)
-- | Multiply a colour by an intensity.
colorMult :: Pixel8 -> Color -> Color
s `colorMult` (Color a b c d) = Color (s `mult` a) (s `mult` b) (s `mult` c) d
where
bound :: RealFrac a => a -> a
bound = max 0 . min 255
mult :: Pixel8 -> Pixel8 -> Pixel8
mult x y = round . bound . toRational $ x * y
-- | Average a set of colours.
average :: [Color] -> Color
average l =
let
((rs, gs), (bs, as)) = (unzip *** unzip) . unzip $ fmap
(\(Color r g b a) -> ((fromIntegral r, fromIntegral g), (fromIntegral b, fromIntegral a)))
l :: (([ℝ], [ℝ]), ([ℝ], [ℝ]))
n :: ℝ
n = fromIntegral $ length l
(r', g', b', a') = (sum rs/n, sum gs/n, sum bs/n, sum as/n)
in Color
(fromInteger . round $ r') (fromInteger . round $ g') (fromInteger . round $ b') (fromInteger . round $ a')
-- Ray Utilities
cameraRay :: Camera -> ℝ2 -> Ray
cameraRay (Camera p vx vy f) (V2 x y) =
let
v = vx `cross` vy
p' = p + f*^v + x*^vx + y*^vy
n = normalize (p' - p)
in
Ray p' n
-- | Create a ray from two points.
rayFromTo :: ℝ3 -> ℝ3 -> Ray
rayFromTo p1 p2 = Ray p1 (normalize $ p2 - p1)
rayBounds :: Ray -> (ℝ3, ℝ3) -> ℝ2
rayBounds ray box =
let
Ray (V3 cPx cPy cPz) (V3 cVx cVy cVz) = ray
(V3 x1 y1 z1, V3 x2 y2 z2) = box
xbounds = [(x1 - cPx)/cVx, (x2-cPx)/cVx]
ybounds = [(y1-cPy)/cVy, (y2-cPy)/cVy]
zbounds = [(z1-cPz)/cVz, (z2-cPz)/cVz]
lower = maximum [minimum xbounds, minimum ybounds, minimum zbounds]
upper = minimum [maximum xbounds, maximum ybounds, maximum zbounds]
in
V2 lower upper
-- Intersection
-- FIXME: magic numbers.
intersection :: Ray -> ((ℝ,ℝ), ℝ) -> ℝ -> Obj3 -> Maybe ℝ3
intersection r@(Ray p v) ((a, aval),b) res obj =
let
step | aval/4 > res = res
| aval/2 > res = res/2
| otherwise = res/10
a' = a + step
a'val = obj (p + a'*^v)
in if a'val < 0
then
let a'' = refine (V2 a a') (\s -> obj (p + s*^v))
in Just (p + a''*^v)
else if a' < b
then intersection r ((a',a'val), b) res obj
else Nothing
refine :: ℝ2 -> (ℝ -> ℝ) -> ℝ
refine (V2 a b) obj =
let
(aval, bval) = (obj a, obj b)
in if bval < aval
then refine' 10 (V2 a b) (V2 aval bval) obj
else refine' 10 (V2 b a) (V2 aval bval) obj
refine' :: Fastℕ -> ℝ2 -> ℝ2 -> (ℝ -> ℝ) -> ℝ
refine' 0 (V2 a _) _ _ = a
refine' n (V2 a b) (V2 aval bval) obj =
let
mid = (a+b)/2
midval = obj mid
in
if midval == 0
then mid
else if midval < 0
then refine' (pred n) (V2 a mid) (V2 aval midval) obj
else refine' (pred n) (V2 mid b) (V2 midval bval) obj
intersects :: Ray -> ((ℝ, ℝ), ℝ) -> ℝ -> Obj3 -> Bool
intersects a b c d = case intersection a b c d of
Nothing -> False
Just _ -> True
-- Trace
-- FIXME: magic numbers.
traceRay :: Ray -> ℝ -> (ℝ3, ℝ3) -> Scene -> Color
traceRay ray@(Ray cameraP cameraV) step box (Scene obj objColor lights defaultColor) =
let
(V2 a b) = rayBounds ray box
in case intersection ray ((a, obj (cameraP + a*^cameraV)), b) step obj of
Just p -> flip colorMult objColor $ floor (sum $ 0.2 : do
Light lightPos lightIntensity <- lights
let
ray'@(Ray _ v) = rayFromTo p lightPos
v' = normalize v
guard . not $ intersects ray' ((0, obj p),20) step obj
let
pval = obj p
dirDeriv :: ℝ3 -> ℝ
dirDeriv v'' = (obj (p + step*^v'') - pval)/step
deriv = V3 (dirDeriv (V3 1 0 0)) (dirDeriv (V3 0 1 0)) (dirDeriv (V3 0 0 1))
normal = normalize deriv
unitV = normalize v'
-- proj :: InnerSpace v => v -> v -> v
proj a' b' = (a' `dot` b')*^b'
dist = vectorDistance p lightPos
illumination = max 0 (normal `dot` unitV) * lightIntensity * (25 /dist)
rV =
let
normalComponent = proj v' normal
parComponent = v' - normalComponent
in
normalComponent - parComponent
pure $ illumination*(3 + 0.3*abs(rV `dot` cameraV)*abs(rV `dot` cameraV))
)
Nothing -> defaultColor