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)
import Graphics.Implicit.Definitions (ℝ, Fastℕ, ℝ2, ℝ3, (⋅), Obj3)
import Codec.Picture (Pixel8)
import Control.Monad (guard)
import Control.Arrow ((***))
import Data.VectorSpace (Scalar, magnitude, (^+^), (*^), normalized, (^-^), InnerSpace)
import Data.Cross (cross3)
default (Fastℕ, ℝ)
data Camera = Camera ℝ3 ℝ3 ℝ3 ℝ
deriving Show
data Ray = Ray ℝ3 ℝ3
deriving Show
data Scene = Scene Obj3 Color [Light] Color
data Light = Light ℝ3 ℝ
deriving Show
data Color = Color Pixel8 Pixel8 Pixel8 Pixel8
vectorDistance :: ℝ3 -> ℝ3 -> Scalar ℝ3
vectorDistance a b = magnitude (b-a)
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 :: [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')
cameraRay :: Camera -> ℝ2 -> Ray
cameraRay (Camera p vx vy f) (x,y) =
let
v = vx `cross3` vy
p' = p ^+^ f*^v ^+^ x*^vx ^+^ y*^vy
n = normalized (p' ^-^ p)
in
Ray p' n
rayFromTo :: ℝ3 -> ℝ3 -> Ray
rayFromTo p1 p2 = Ray p1 (normalized $ p2 ^-^ p1)
rayBounds :: Ray -> (ℝ3, ℝ3) -> ℝ2
rayBounds ray box =
let
Ray (cPx, cPy, cPz) (cVx, cVy, cVz) = ray
((x1,y1,z1),(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
(lower, upper)
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 (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 (a, b) obj =
let
(aval, bval) = (obj a, obj b)
in if bval < aval
then refine' 10 (a, b) (aval, bval) obj
else refine' 10 (b, a) (aval, bval) obj
refine' :: Fastℕ -> ℝ2 -> ℝ2 -> (ℝ -> ℝ) -> ℝ
refine' 0 (a, _) _ _ = a
refine' n (a, b) (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) (a, mid) (aval, midval) obj
else refine' (pred n) (mid, b) (midval, bval) obj
intersects :: Ray -> ((ℝ, ℝ), ℝ) -> ℝ -> Obj3 -> Bool
intersects a b c d = case intersection a b c d of
Nothing -> False
Just _ -> True
traceRay :: Ray -> ℝ -> (ℝ3, ℝ3) -> Scene -> Color
traceRay ray@(Ray cameraP cameraV) step box (Scene obj objColor lights defaultColor) =
let
(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' = normalized 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 = (dirDeriv (1,0,0), dirDeriv (0,1,0), dirDeriv (0,0,1))
normal = normalized deriv
unitV = normalized v'
proj :: InnerSpace v => v -> v -> v
proj a' b' = (a'⋅b')*^b'
dist = vectorDistance p lightPos
illumination = max 0 (normal ⋅ unitV) * lightIntensity * (25 /dist)
rV =
let
normalComponent = proj v' normal
parComponent = v' - normalComponent
in
normalComponent - parComponent
pure $ illumination*(3 + 0.3*abs(rV ⋅ cameraV)*abs(rV ⋅ cameraV))
)
Nothing -> defaultColor