Commit 032550a3 authored by Simon Marlow's avatar Simon Marlow

add Galois' Ray Tracer

parent dc3e92e1
-- Copyright (c) 2000 Galois Connections, Inc.
-- All rights reserved. This software is distributed as
-- free software under the license in the file "LICENSE",
-- which is included in the distribution.
module CSG(module Construct,
module Geometry,
module Intersections,
module Interval,
module Misc) where
import Construct
import Geometry
import Intersections
import Interval
import Misc
-- Copyright (c) 2000 Galois Connections, Inc.
-- All rights reserved. This software is distributed as
-- free software under the license in the file "LICENSE",
-- which is included in the distribution.
module Construct
( Surface (..)
, Face (..)
, CSG (..)
, Texture
, Transform
, union, intersect, difference
, plane, sphere, cube, cylinder, cone
, transform
, translate, translateX, translateY, translateZ
, scale, scaleX, scaleY, scaleZ, uscale
, rotateX, rotateY, rotateZ
, eye, translateEye
, rotateEyeX, rotateEyeY, rotateEyeZ
) where
import Geometry
-- In each case, we model the surface by a point and a pair of tangent vectors.
-- This gives us enough information to determine the surface
-- normal at that point, which is all that is required by the current
-- illumination model. We can't just save the surface normal because
-- that isn't preserved by transformations.
data Surface
= Planar Point Vector Vector
| Spherical Point Vector Vector
| Cylindrical Point Vector Vector
| Conic Point Vector Vector
deriving Show
data Face
= PlaneFace
| SphereFace
| CubeFront
| CubeBack
| CubeLeft
| CubeRight
| CubeTop
| CubeBottom
| CylinderSide
| CylinderTop
| CylinderBottom
| ConeSide
| ConeBase
deriving Show
data CSG a
= Plane a
| Sphere a
| Cylinder a
| Cube a
| Cone a
| Transform Matrix Matrix (CSG a)
| Union (CSG a) (CSG a)
| Intersect (CSG a) (CSG a)
| Difference (CSG a) (CSG a)
| Box Box (CSG a)
deriving (Show)
-- the data returned for determining surface texture
-- the Face tells which face of a primitive this is
-- the Point is the point of intersection in object coordinates
-- the a is application-specific texture information
type Texture a = (Face, Point, a)
union, intersect, difference :: CSG a -> CSG a -> CSG a
union p@(Box b1 _) q@(Box b2 _) = Box (mergeBox b1 b2) (Union p q)
union p q = Union p q
-- rather pessimistic
intersect p@(Box b1 _) q@(Box b2 _) = Box (mergeBox b1 b2) (Intersect p q)
intersect p q = Intersect p q
difference (Box b1 p) q = Box b1 (Difference p q)
-- no need to box again inside
-- difference p@(Box b1 _) q = Box b1 (Difference p q)
difference p q = Difference p q
mkBox b p = Box b p
plane, sphere, cube, cylinder, cone :: a -> CSG a
plane = Plane
sphere s =
mkBox (B (-1 - epsilon) (1 + epsilon)
(-1 - epsilon) (1 + epsilon)
(-1 - epsilon) (1 + epsilon)) (Sphere s)
cone s =
mkBox (B (-1 - epsilon) (1 + epsilon)
( - epsilon) (1 + epsilon)
(-1 - epsilon) (1 + epsilon)) (Cone s)
cube s =
mkBox (B (- epsilon) (1 + epsilon)
(- epsilon) (1 + epsilon)
(- epsilon) (1 + epsilon)) (Cube s)
cylinder s =
mkBox (B (-1 - epsilon) (1 + epsilon)
( - epsilon) (1 + epsilon)
(-1 - epsilon) (1 + epsilon)) (Cylinder s)
----------------------------
-- Object transformations
----------------------------
type Transform = (Matrix, Matrix)
transform :: Transform -> CSG a -> CSG a
transform (m, m') (Transform mp mp' p) = Transform (multMM m mp) (multMM mp' m') p
transform mm' (Union p q) = Union (transform mm' p) (transform mm' q)
transform mm' (Intersect p q) = Intersect (transform mm' p) (transform mm' q)
transform mm' (Difference p q) = Difference (transform mm' p) (transform mm' q)
transform mm'@(m,_) (Box box p) = Box (transformBox m box) (transform mm' p)
transform (m, m') prim = Transform m m' prim
translate :: Coords -> CSG a -> CSG a
translateX, translateY, translateZ :: Double -> CSG a -> CSG a
translate xyz = transform $ transM xyz
translateX x = translate (x, 0, 0)
translateY y = translate (0, y, 0)
translateZ z = translate (0, 0, z)
scale :: Coords -> CSG a -> CSG a
scaleX, scaleY, scaleZ, uscale :: Double -> CSG a -> CSG a
scale xyz = transform $ scaleM xyz
scaleX x = scale (x, 1, 1)
scaleY y = scale (1, y, 1)
scaleZ z = scale (1, 1, z)
uscale u = scale (u,u,u)
rotateX, rotateY, rotateZ :: Radian -> CSG a -> CSG a
rotateX a = transform $ rotxM a
rotateY a = transform $ rotyM a
rotateZ a = transform $ rotzM a
unit = matrix
( ( 1.0, 0.0, 0.0, 0.0 ),
( 0.0, 1.0, 0.0, 0.0 ),
( 0.0, 0.0, 1.0, 0.0 ),
( 0.0, 0.0, 0.0, 1.0 ) )
transM (x, y, z)
= ( matrix
( ( 1, 0, 0, x ),
( 0, 1, 0, y ),
( 0, 0, 1, z ),
( 0, 0, 0, 1 ) ),
matrix
( ( 1, 0, 0, -x ),
( 0, 1, 0, -y ),
( 0, 0, 1, -z ),
( 0, 0, 0, 1 ) ) )
scaleM (x, y, z)
= ( matrix
( ( x', 0, 0, 0 ),
( 0, y', 0, 0 ),
( 0, 0, z', 0 ),
( 0, 0, 0, 1 ) ),
matrix
( ( 1/x', 0, 0, 0 ),
( 0, 1/y', 0, 0 ),
( 0, 0, 1/z', 0 ),
( 0, 0, 0, 1 ) ) )
where x' = nonZero x
y' = nonZero y
z' = nonZero z
rotxM t
= ( matrix
( ( 1, 0, 0, 0 ),
( 0, cos t, -sin t, 0 ),
( 0, sin t, cos t, 0 ),
( 0, 0, 0, 1 ) ),
matrix
( ( 1, 0, 0, 0 ),
( 0, cos t, sin t, 0 ),
( 0, -sin t, cos t, 0 ),
( 0, 0, 0, 1 ) ) )
rotyM t
= ( matrix
( ( cos t, 0, sin t, 0 ),
( 0, 1, 0, 0 ),
( -sin t, 0, cos t, 0 ),
( 0, 0, 0, 1 ) ),
matrix
( ( cos t, 0, -sin t, 0 ),
( 0, 1, 0, 0 ),
( sin t, 0, cos t, 0 ),
( 0, 0, 0, 1 ) ) )
rotzM t
= ( matrix
( ( cos t, -sin t, 0, 0 ),
( sin t, cos t, 0, 0 ),
( 0, 0, 1, 0 ),
( 0, 0, 0, 1 ) ),
matrix
( ( cos t, sin t, 0, 0 ),
( -sin t, cos t, 0, 0 ),
( 0, 0, 1, 0 ),
( 0, 0, 0, 1 ) ) )
-------------------
-- Eye transformations
-- These are used to specify placement of the eye.
-- `eye' starts out at (0, 0, -1).
-- These are implemented as inverse transforms of the model.
-------------------
eye :: Transform
translateEye :: Coords -> Transform -> Transform
rotateEyeX, rotateEyeY, rotateEyeZ :: Radian -> Transform -> Transform
eye = (unit, unit)
translateEye xyz (eye1, eye2)
= (multMM m1 eye1, multMM eye2 m2)
where (m1, m2) = transM xyz
rotateEyeX t (eye1, eye2)
= (multMM m1 eye1, multMM eye2 m2)
where (m1, m2) = rotxM t
rotateEyeY t (eye1, eye2)
= (multMM m1 eye1, multMM eye2 m2)
where (m1, m2) = rotyM t
rotateEyeZ t (eye1, eye2)
= (multMM m1 eye1, multMM eye2 m2)
where (m1, m2) = rotzM t
-------------------
-- Bounding boxes
-------------------
mergeBox (B x11 x12 y11 y12 z11 z12) (B x21 x22 y21 y22 z21 z22) =
B (x11 `min` x21) (x12 `max` x22)
(y11 `min` y21) (y12 `max` y22)
(z11 `min` z21) (z12 `max` z22)
transformBox t (B x1 x2 y1 y2 z1 z2)
= (B (foldr1 min (map xCoord pts'))
(foldr1 max (map xCoord pts'))
(foldr1 min (map yCoord pts'))
(foldr1 max (map yCoord pts'))
(foldr1 min (map zCoord pts'))
(foldr1 max (map zCoord pts')))
where pts' = map (multMP t) pts
pts = [point x1 y1 z1,
point x1 y1 z2,
point x1 y2 z1,
point x1 y2 z2,
point x2 y1 z1,
point x2 y1 z2,
point x2 y2 z1,
point x2 y2 z2]
This diff is collapsed.
This diff is collapsed.
-- Copyright (c) 2000 Galois Connections, Inc.
-- All rights reserved. This software is distributed as
-- free software under the license in the file "LICENSE",
-- which is included in the distribution.
module Geometry
( Coords
, Ray
, Point -- abstract
, Vector -- abstract
, Matrix -- abstract
, Color -- abstract
, Box(..)
, Radian
, matrix
, coord
, color
, uncolor
, xCoord , yCoord , zCoord
, xComponent , yComponent , zComponent
, point
, vector
, nearV
, point_to_vector
, vector_to_point
, dot
, cross
, tangents
, addVV
, addPV
, subVV
, negV
, subPP
, norm
, normalize
, dist2
, sq
, distFrom0Sq
, distFrom0
, multSV
, multMM
, transposeM
, multMV
, multMP
, multMQ
, multMR
, white
, black
, addCC
, subCC
, sumCC
, multCC
, multSC
, nearC
, offsetToPoint
, epsilon
, inf
, nonZero
, eqEps
, near
, clampf
) where
import List
type Coords = (Double,Double,Double)
type Ray = (Point,Vector) -- origin of ray, and unit vector giving direction
data Point = P !Double !Double !Double -- implicit extra arg of 1
deriving (Show)
data Vector = V !Double !Double !Double -- implicit extra arg of 0
deriving (Show, Eq)
data Matrix = M !Quad !Quad !Quad !Quad
deriving (Show)
data Color = C !Double !Double !Double
deriving (Show, Eq)
data Box = B !Double !Double !Double !Double !Double !Double
deriving (Show)
data Quad = Q !Double !Double !Double !Double
deriving (Show)
type Radian = Double
type Tup4 a = (a,a,a,a)
--{-# INLINE matrix #-}
matrix :: Tup4 (Tup4 Double) -> Matrix
matrix ((m11, m12, m13, m14),
(m21, m22, m23, m24),
(m31, m32, m33, m34),
(m41, m42, m43, m44))
= M (Q m11 m12 m13 m14)
(Q m21 m22 m23 m24)
(Q m31 m32 m33 m34)
(Q m41 m42 m43 m44)
coord x y z = (x, y, z)
color r g b = C r g b
uncolor (C r g b) = (r,g,b)
{-# INLINE xCoord #-}
xCoord (P x y z) = x
{-# INLINE yCoord #-}
yCoord (P x y z) = y
{-# INLINE zCoord #-}
zCoord (P x y z) = z
{-# INLINE xComponent #-}
xComponent (V x y z) = x
{-# INLINE yComponent #-}
yComponent (V x y z) = y
{-# INLINE zComponent #-}
zComponent (V x y z) = z
point :: Double -> Double -> Double -> Point
point x y z = P x y z
vector :: Double -> Double -> Double -> Vector
vector x y z = V x y z
nearV :: Vector -> Vector -> Bool
nearV (V a b c) (V d e f) = a `near` d && b `near` e && c `near` f
point_to_vector :: Point -> Vector
point_to_vector (P x y z) = V x y z
vector_to_point :: Vector -> Point
vector_to_point (V x y z) = P x y z
{-# INLINE vector_to_quad #-}
vector_to_quad :: Vector -> Quad
vector_to_quad (V x y z) = Q x y z 0
{-# INLINE point_to_quad #-}
point_to_quad :: Point -> Quad
point_to_quad (P x y z) = Q x y z 1
{-# INLINE quad_to_point #-}
quad_to_point :: Quad -> Point
quad_to_point (Q x y z _) = P x y z
{-# INLINE quad_to_vector #-}
quad_to_vector :: Quad -> Vector
quad_to_vector (Q x y z _) = V x y z
--{-# INLINE dot #-}
dot :: Vector -> Vector -> Double
dot (V x1 y1 z1) (V x2 y2 z2) = x1 * x2 + y1 * y2 + z1 * z2
cross :: Vector -> Vector -> Vector
cross (V x1 y1 z1) (V x2 y2 z2)
= V (y1 * z2 - z1 * y2) (z1 * x2 - x1 * z2) (x1 * y2 - y1 * x2)
-- assumption: the input vector is a normal
tangents :: Vector -> (Vector, Vector)
tangents v@(V x y z)
= (v1, v `cross` v1)
where v1 | x == 0 = normalize (vector 0 z (-y))
| otherwise = normalize (vector (-y) x 0)
{-# INLINE dot4 #-}
dot4 :: Quad -> Quad -> Double
dot4 (Q x1 y1 z1 w1) (Q x2 y2 z2 w2) = x1 * x2 + y1 * y2 + z1 * z2 + w1 * w2
addVV :: Vector -> Vector -> Vector
addVV (V x1 y1 z1) (V x2 y2 z2)
= V (x1 + x2) (y1 + y2) (z1 + z2)
addPV :: Point -> Vector -> Point
addPV (P x1 y1 z1) (V x2 y2 z2)
= P (x1 + x2) (y1 + y2) (z1 + z2)
subVV :: Vector -> Vector -> Vector
subVV (V x1 y1 z1) (V x2 y2 z2)
= V (x1 - x2) (y1 - y2) (z1 - z2)
negV :: Vector -> Vector
negV (V x1 y1 z1)
= V (-x1) (-y1) (-z1)
subPP :: Point -> Point -> Vector
subPP (P x1 y1 z1) (P x2 y2 z2)
= V (x1 - x2) (y1 - y2) (z1 - z2)
--{-# INLINE norm #-}
norm :: Vector -> Double
norm (V x y z) = sqrt (sq x + sq y + sq z)
--{-# INLINE normalize #-}
-- normalize a vector to a unit vector
normalize :: Vector -> Vector
normalize v@(V x y z)
| norm /= 0 = multSV (1/norm) v
| otherwise = error "normalize empty!"
where norm = sqrt (sq x + sq y + sq z)
-- This does computes the distance *squared*
dist2 :: Point -> Point -> Double
dist2 us vs = sq x + sq y + sq z
where
(V x y z) = subPP us vs
{-# INLINE sq #-}
sq :: Double -> Double
sq d = d * d
{-# INLINE distFrom0Sq #-}
distFrom0Sq :: Point -> Double -- Distance of point from origin.
distFrom0Sq (P x y z) = sq x + sq y + sq z
{-# INLINE distFrom0 #-}
distFrom0 :: Point -> Double -- Distance of point from origin.
distFrom0 p = sqrt (distFrom0Sq p)
--{-# INLINE multSV #-}
multSV :: Double -> Vector -> Vector
multSV k (V x y z) = V (k*x) (k*y) (k*z)
--{-# INLINE multMM #-}
multMM :: Matrix -> Matrix -> Matrix
multMM m1@(M q1 q2 q3 q4) m2
= M (multMQ m2' q1)
(multMQ m2' q2)
(multMQ m2' q3)
(multMQ m2' q4)
where
m2' = transposeM m2
{-# INLINE transposeM #-}
transposeM :: Matrix -> Matrix
transposeM (M (Q e11 e12 e13 e14)
(Q e21 e22 e23 e24)
(Q e31 e32 e33 e34)
(Q e41 e42 e43 e44)) = (M (Q e11 e21 e31 e41)
(Q e12 e22 e32 e42)
(Q e13 e23 e33 e43)
(Q e14 e24 e34 e44))
--multMM m1 m2 = [map (dot4 row) (transpose m2) | row <- m1]
--{-# INLINE multMV #-}
multMV :: Matrix -> Vector -> Vector
multMV m v = quad_to_vector (multMQ m (vector_to_quad v))
--{-# INLINE multMP #-}
multMP :: Matrix -> Point -> Point
multMP m p = quad_to_point (multMQ m (point_to_quad p))
-- mat vec = map (dot4 vec) mat
{-# INLINE multMQ #-}
multMQ :: Matrix -> Quad -> Quad
multMQ (M q1 q2 q3 q4) q
= Q (dot4 q q1)
(dot4 q q2)
(dot4 q q3)
(dot4 q q4)
{-# INLINE multMR #-}
multMR :: Matrix -> Ray -> Ray
multMR m (r, v) = (multMP m r, multMV m v)
white :: Color
white = C 1 1 1
black :: Color
black = C 0 0 0
addCC :: Color -> Color -> Color
addCC (C a b c) (C d e f) = C (a+d) (b+e) (c+f)
subCC :: Color -> Color -> Color
subCC (C a b c) (C d e f) = C (a-d) (b-e) (c-f)
sumCC :: [Color] -> Color
sumCC = foldr addCC black
multCC :: Color -> Color -> Color
multCC (C a b c) (C d e f) = C (a*d) (b*e) (c*f)
multSC :: Double -> Color -> Color
multSC k (C a b c) = C (a*k) (b*k) (c*k)
nearC :: Color -> Color -> Bool
nearC (C a b c) (C d e f) = a `near` d && b `near` e && c `near` f
offsetToPoint :: Ray -> Double -> Point
offsetToPoint (r,v) i = r `addPV` (i `multSV` v)
--
epsilon, inf :: Double -- aproximate zero and infinity
epsilon = 1.0e-10
inf = 1.0e20
nonZero :: Double -> Double -- Use before a division. It makes definitions
nonZero x | x > epsilon = x -- more complete and I bet the errors that get
| x < -epsilon = x -- introduced will be undetectable if epsilon
| otherwise = epsilon -- is small enough
eqEps x y = abs (x-y) < epsilon
near = eqEps
clampf :: Double -> Double
clampf p | p < 0 = 0
| p > 1 = 1
| True = p
-- Copyright (c) 2000 Galois Connections, Inc.
-- All rights reserved. This software is distributed as
-- free software under the license in the file "LICENSE",
-- which is included in the distribution.
-- Modified to use stdout (for testing)
{-# LANGUAGE BangPatterns #-}
module Illumination
( Object
, Light (..)
, light, pointlight, spotlight
, render
) where
import Control.Parallel
import Array
import Char(chr)
import Maybe
import Geometry
import CSG
import Surface
import Misc
type Object = CSG (SurfaceFn Color Double)
data Cxt = Cxt {ambient::Color, lights::[Light], object::Object, depth::Int}
deriving Show
render :: (Matrix,Matrix) -> Color -> [Light] -> Object -> Int ->
Radian -> Int -> Int -> String -> IO ()
render (m,m') amb ls obj dep fov wid ht file
= do { debugging
; putStrLn (showBitmap' wid ht (lazyParList 100 (map (\x -> seqList x `pseq` x) pixels)))
}
where
debugging = return ()
{-
do { putStrLn (show cxt)
; putStrLn (show (width, delta, aspect, left, top))
}
-}
obj' = transform (m',m) obj
ls' = [ transformLight m' l | l <- ls ]
pixelA = listArray ((1,1), (ht,wid))
[ illumination cxt (start,pixel i j)
| j <- take ht [0.5..]
, i <- take wid [0.5..] ]
antiA = pixelA //
[ (ix, superSample ix (pixelA ! ix))
| j <- [2 .. ht - 1], i <- [2 .. wid - 1]
, let ix = (j, i)
, contrast ix pixelA ]
pixels = [ [ illumination cxt (start,pixel i j) | i<- take wid [0.5..] ]
| j <- take ht [0.5..]
]
cxt = Cxt {ambient=amb, lights=ls', object=obj', depth=dep}
start = point 0 0 (-1)
width = 2 * tan (fov/2)
delta = width / fromIntegral wid
aspect = fromIntegral ht / fromIntegral wid
left = - width / 2
top = - left * aspect
pixel i j = vector (left + i*delta) (top - j*delta) 1
superSample (y, x) col = avg $ col:
[ illumination cxt (start, pixel (fromIntegral x - 0.5 + xd) (fromIntegral y - 0.5 + yd))
| (xd, yd) <- [(-0.333, 0.0), (0.333, 0.0), (0.0, -0.333), (0.0, 0.333)]
]
parListN :: Int -> [a] -> [a]
parListN 0 xs = xs
parListN !n [] = []
parListN !n (x:xs) = x `par` parListN (n-1) xs
-- like parListN, but starts the sparks in reverse order
parListN1 :: Int -> [a] -> [a] -> [a]
parListN1 0 xs ys = parList ys `pseq` xs
parListN1 !n [] ys = parList ys `pseq` []
parListN1 !n (x:xs) ys = parListN1 (n-1) xs (x:ys)
seqList :: [a] -> ()
seqList [] = ()
seqList (x:xs) = x `pseq` seqList xs
parList :: [a] -> ()
parList [] = ()
parList (x:xs) = x `par` parList xs