From 75a7e5ff20dbc3f3b583d31b4a1ff0b6a1459d49 Mon Sep 17 00:00:00 2001 From: andy Date: Tue, 3 Oct 2000 18:25:29 +0000 Subject: [PATCH] [project @ 2000-10-03 18:25:28 by andy] Addding the Galois Connections ray tracer as an example for GHC. --- ghc/tests/programs/galois_raytrace/CSG.hs | 16 + ghc/tests/programs/galois_raytrace/Construct.hs | 265 +++++++++++++ ghc/tests/programs/galois_raytrace/Data.hs | 407 ++++++++++++++++++++ ghc/tests/programs/galois_raytrace/Eval.hs | 357 +++++++++++++++++ ghc/tests/programs/galois_raytrace/Geometry.hs | 314 +++++++++++++++ ghc/tests/programs/galois_raytrace/Illumination.hs | 212 ++++++++++ .../programs/galois_raytrace/Intersections.hs | 404 +++++++++++++++++++ ghc/tests/programs/galois_raytrace/Interval.hs | 121 ++++++ ghc/tests/programs/galois_raytrace/Main.hs | 17 + ghc/tests/programs/galois_raytrace/Makefile | 9 + ghc/tests/programs/galois_raytrace/Misc.hs | 11 + ghc/tests/programs/galois_raytrace/Parse.hs | 137 +++++++ ghc/tests/programs/galois_raytrace/Pixmap.hs | 64 +++ ghc/tests/programs/galois_raytrace/Primitives.hs | 24 ++ ghc/tests/programs/galois_raytrace/RayTrace.hs | 9 + ghc/tests/programs/galois_raytrace/Surface.hs | 115 ++++++ ghc/tests/programs/galois_raytrace/galois.gml | 147 +++++++ .../galois_raytrace/galois_raytrace.stdout | Bin 0 -> 180024 bytes 18 files changed, 2629 insertions(+) create mode 100644 ghc/tests/programs/galois_raytrace/CSG.hs create mode 100644 ghc/tests/programs/galois_raytrace/Construct.hs create mode 100644 ghc/tests/programs/galois_raytrace/Data.hs create mode 100644 ghc/tests/programs/galois_raytrace/Eval.hs create mode 100644 ghc/tests/programs/galois_raytrace/Geometry.hs create mode 100644 ghc/tests/programs/galois_raytrace/Illumination.hs create mode 100644 ghc/tests/programs/galois_raytrace/Intersections.hs create mode 100644 ghc/tests/programs/galois_raytrace/Interval.hs create mode 100644 ghc/tests/programs/galois_raytrace/Main.hs create mode 100644 ghc/tests/programs/galois_raytrace/Makefile create mode 100644 ghc/tests/programs/galois_raytrace/Misc.hs create mode 100644 ghc/tests/programs/galois_raytrace/Parse.hs create mode 100644 ghc/tests/programs/galois_raytrace/Pixmap.hs create mode 100644 ghc/tests/programs/galois_raytrace/Primitives.hs create mode 100644 ghc/tests/programs/galois_raytrace/RayTrace.hs create mode 100644 ghc/tests/programs/galois_raytrace/Surface.hs create mode 100644 ghc/tests/programs/galois_raytrace/galois.gml create mode 100644 ghc/tests/programs/galois_raytrace/galois_raytrace.stdout diff --git a/ghc/tests/programs/galois_raytrace/CSG.hs b/ghc/tests/programs/galois_raytrace/CSG.hs new file mode 100644 index 0000000..ba37a17 --- /dev/null +++ b/ghc/tests/programs/galois_raytrace/CSG.hs @@ -0,0 +1,16 @@ +-- 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 diff --git a/ghc/tests/programs/galois_raytrace/Construct.hs b/ghc/tests/programs/galois_raytrace/Construct.hs new file mode 100644 index 0000000..90dbc60 --- /dev/null +++ b/ghc/tests/programs/galois_raytrace/Construct.hs @@ -0,0 +1,265 @@ +-- 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] diff --git a/ghc/tests/programs/galois_raytrace/Data.hs b/ghc/tests/programs/galois_raytrace/Data.hs new file mode 100644 index 0000000..1f716ea --- /dev/null +++ b/ghc/tests/programs/galois_raytrace/Data.hs @@ -0,0 +1,407 @@ +-- 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 Data where + +import Array +import IOExts + +import CSG +import Geometry +import Illumination +import Primitives +import Surface + +-- Now the parsed (expresssion) language + +type Name = String + +type Code = [GMLToken] + +data GMLToken + -- All these can occur in parsed code + = TOp GMLOp + | TId Name + | TBind Name + | TBool Bool + | TInt Int + | TReal Double + | TString String + | TBody Code + | TArray Code + | TApply + | TIf + -- These can occur in optimized/transformed code + -- NONE (yet!) + + +instance Show GMLToken where + showsPrec p (TOp op) = shows op + showsPrec p (TId id) = showString id + showsPrec p (TBind id) = showString ('/' : id) + showsPrec p (TBool bool) = shows bool + showsPrec p (TInt i) = shows i + showsPrec p (TReal d) = shows d + showsPrec p (TString s) = shows s + showsPrec p (TBody code) = shows code + showsPrec p (TArray code) = showString "[ " + . foldr (\ a b -> a . showChar ' ' . b) id (map shows code) + . showString "]" + showsPrec p (TApply) = showString "apply" + showsPrec p (TIf) = showString "if" + + showList code = showString "{ " + . foldr (\ a b -> a . showChar ' ' . b) id (map shows code) + . showString "}" + + +-- Now the value language, used inside the interpreter + +type Stack = [GMLValue] + +data GMLValue + = VBool !Bool + | VInt !Int + | VReal !Double + | VString String + | VClosure Env Code + | VArray (Array Int GMLValue) -- FIXME: Haskell array + -- uses the interpreter version of point + | VPoint { xPoint :: !Double + , yPoint :: !Double + , zPoint :: !Double + } + -- these are abstract to the interpreter + | VObject Object + | VLight Light + -- This is an abstract object, used by the abstract interpreter + | VAbsObj AbsObj + + +-- There are only *3* basic abstract values, +-- and the combinators also. + +data AbsObj + = AbsFACE + | AbsU + | AbsV + deriving (Show) + +instance Show GMLValue where + showsPrec p value = showString (showStkEle value) + +showStkEle :: GMLValue -> String +showStkEle (VBool b) = show b ++ " :: Bool" +showStkEle (VInt i) = show i ++ " :: Int" +showStkEle (VReal r) = show r ++ " :: Real" +showStkEle (VString s) = show s ++ " :: String" +showStkEle (VClosure {}) = " :: Closure" +showStkEle (VArray arr) + = " :: Array" +showStkEle (VPoint x y z) = "(" ++ show x + ++ "," ++ show y + ++ "," ++ show z + ++ ") :: Point" +showStkEle (VObject {}) = " :: Object" +showStkEle (VLight {}) = " :: Object" +showStkEle (VAbsObj vobs) = "{{ " ++ show vobs ++ "}} :: AbsObj" + +-- An abstract environment + +newtype Env = Env [(Name, GMLValue)] deriving Show + +emptyEnv :: Env +emptyEnv = Env [] + +extendEnv :: Env -> Name -> GMLValue -> Env +extendEnv (Env e) n v = Env ((n, v):e) + +lookupEnv :: Env -> Name -> Maybe GMLValue +lookupEnv (Env e) n = lookup n e + +-- All primitive operators +-- +-- There is no Op_apply, Op_false, Op_true and Op_if +-- (because they appear explcitly in the rules). + +data GMLOp + = Op_acos + | Op_addi + | Op_addf + | Op_asin + | Op_clampf + | Op_cone + | Op_cos + | Op_cube + | Op_cylinder + | Op_difference + | Op_divi + | Op_divf + | Op_eqi + | Op_eqf + | Op_floor + | Op_frac + | Op_get + | Op_getx + | Op_gety + | Op_getz + | Op_intersect + | Op_length + | Op_lessi + | Op_lessf + | Op_light + | Op_modi + | Op_muli + | Op_mulf + | Op_negi + | Op_negf + | Op_plane + | Op_point + | Op_pointlight + | Op_real + | Op_render + | Op_rotatex + | Op_rotatey + | Op_rotatez + | Op_scale + | Op_sin + | Op_sphere + | Op_spotlight + | Op_sqrt + | Op_subi + | Op_subf + | Op_trace -- non standard, for debugging GML programs + | Op_translate + | Op_union + | Op_uscale + deriving (Eq,Ord,Ix,Bounded) + +instance Show GMLOp where + showsPrec _ op = showString (opNameTable ! op) + + +------------------------------------------------------------------------------ + +-- And how we use the op codes (there names, there interface) + +-- These keywords include, "apply", "if", "true" and "false", +-- they are not parsed as operators, but are +-- captured by the parser as a special case. + +keyWords :: [String] +keyWords = [ kwd | (kwd,_,_) <- opcodes ] + +-- Lookup has to look from the start (or else...) +opTable :: [(Name,GMLToken)] +opTable = [ (kwd,op) | (kwd,op,_) <- opcodes ] + +opNameTable :: Array GMLOp Name +opNameTable = array (minBound,maxBound) + [ (op,name) | (name,TOp op,_) <- opcodes ] + +undef = error "undefined function" +image = error "undefined function: talk to image group" + +-- typically, its best to have *one* opcode table, +-- so that mis-alignments do not happen. + +opcodes :: [(String,GMLToken,PrimOp)] +opcodes = + [ ("apply", TApply, error "incorrect use of apply") + , ("if", TIf, error "incorrect use of if") + , ("false", TBool False, error "incorrect use of false") + , ("true", TBool True, error "incorrect use of true") + ] ++ map (\ (a,b,c) -> (a,TOp b,c)) + -- These are just invocation, any coersions need to occur between here + -- and before arriving at the application code (like deg -> rad). + [ ("acos", Op_acos, Real_Real (rad2deg . acos)) + , ("addi", Op_addi, Int_Int_Int (+)) + , ("addf", Op_addf, Real_Real_Real (+)) + , ("asin", Op_asin, Real_Real (rad2deg . asin)) + , ("clampf", Op_clampf, Real_Real clampf) + , ("cone", Op_cone, Surface_Obj cone) + , ("cos", Op_cos, Real_Real (cos . deg2rad)) + , ("cube", Op_cube, Surface_Obj cube) + , ("cylinder", Op_cylinder, Surface_Obj cylinder) + , ("difference", Op_difference, Obj_Obj_Obj difference) + , ("divi", Op_divi, Int_Int_Int (ourQuot)) + , ("divf", Op_divf, Real_Real_Real (/)) + , ("eqi", Op_eqi, Int_Int_Bool (==)) + , ("eqf", Op_eqf, Real_Real_Bool (==)) + , ("floor", Op_floor, Real_Int floor) + , ("frac", Op_frac, Real_Real (snd . properFraction)) + , ("get", Op_get, Arr_Int_Value ixGet) + , ("getx", Op_getx, Point_Real (\ x y z -> x)) + , ("gety", Op_gety, Point_Real (\ x y z -> y)) + , ("getz", Op_getz, Point_Real (\ x y z -> z)) + , ("intersect", Op_intersect, Obj_Obj_Obj intersect) + , ("length", Op_length, Arr_Int (succ . snd . bounds)) + , ("lessi", Op_lessi, Int_Int_Bool (<)) + , ("lessf", Op_lessf, Real_Real_Bool (<)) + , ("light", Op_light, Point_Color_Light light) + , ("modi", Op_modi, Int_Int_Int (ourRem)) + , ("muli", Op_muli, Int_Int_Int (*)) + , ("mulf", Op_mulf, Real_Real_Real (*)) + , ("negi", Op_negi, Int_Int negate) + , ("negf", Op_negf, Real_Real negate) + , ("plane", Op_plane, Surface_Obj plane) + , ("point", Op_point, Real_Real_Real_Point VPoint) + , ("pointlight", Op_pointlight, Point_Color_Light pointlight) + , ("real", Op_real, Int_Real fromIntegral) + , ("render", Op_render, Render $ render eye) + , ("rotatex", Op_rotatex, Obj_Real_Obj (\ o d -> rotateX (deg2rad d) o)) + , ("rotatey", Op_rotatey, Obj_Real_Obj (\ o d -> rotateY (deg2rad d) o)) + , ("rotatez", Op_rotatez, Obj_Real_Obj (\ o d -> rotateZ (deg2rad d) o)) + , ("scale", Op_scale, Obj_Real_Real_Real_Obj (\ o x y z -> scale (x,y,z) o)) + , ("sin", Op_sin, Real_Real (sin . deg2rad)) + , ("sphere", Op_sphere, Surface_Obj sphere') -- see comment at end of file + , ("spotlight", Op_spotlight, Point_Point_Color_Real_Real_Light mySpotlight) + , ("sqrt", Op_sqrt, Real_Real ourSqrt) + , ("subi", Op_subi, Int_Int_Int (-)) + , ("subf", Op_subf, Real_Real_Real (-)) + , ("trace", Op_trace, Value_String_Value mytrace) + , ("translate", Op_translate, Obj_Real_Real_Real_Obj (\ o x y z -> translate (x,y,z) o)) + , ("union", Op_union, Obj_Obj_Obj union) + , ("uscale", Op_uscale, Obj_Real_Obj (\ o r -> uscale r o)) + ] + +-- This enumerate all possible ways of calling the fixed primitives + +-- The datatype captures the type at the *interp* level, +-- the type of the functional is mirrored on this (using Haskell types). + +data PrimOp + + -- 1 argument + = Int_Int (Int -> Int) + | Real_Real (Double -> Double) + | Point_Real (Double -> Double -> Double -> Double) + | Surface_Obj (SurfaceFn Color Double -> Object) + | Real_Int (Double -> Int) + | Int_Real (Int -> Double) + | Arr_Int (Array Int GMLValue -> Int) + + -- 2 arguments + | Int_Int_Int (Int -> Int -> Int) + | Int_Int_Bool (Int -> Int -> Bool) + | Real_Real_Real (Double -> Double -> Double) + | Real_Real_Bool (Double -> Double -> Bool) + | Arr_Int_Value (Array Int GMLValue -> Int -> GMLValue) + + -- Many arguments, typically image mangling + + | Obj_Obj_Obj (Object -> Object -> Object) + | Point_Color_Light (Coords -> Color -> Light) + | Real_Real_Real_Point (Double -> Double -> Double -> GMLValue) + | Obj_Real_Obj (Object -> Double -> Object) + | Obj_Real_Real_Real_Obj (Object -> Double -> Double -> Double -> Object) + | Value_String_Value (GMLValue -> String -> GMLValue) + + | Point_Point_Color_Real_Real_Light + (Coords -> Coords -> Color -> Radian -> Radian -> Light) + -- And finally render + | Render (Color -> [Light] -> Object -> Int -> Double -> Int -> Int -> String -> IO ()) + +data Type + = TyBool + | TyInt + | TyReal + | TyString + | TyCode + | TyArray + | TyPoint + | TyObject + | TyLight + | TyAlpha + | TyAbsObj + deriving (Eq,Ord,Ix,Bounded) + +typeTable = + [ ( TyBool, "Bool") + , ( TyInt, "Int") + , ( TyReal, "Real") + , ( TyString, "String") + , ( TyCode, "Code") + , ( TyArray, "Array") + , ( TyPoint, "Point") + , ( TyObject, "Object") + , ( TyLight, "Light") + , ( TyAlpha, "") + , ( TyAbsObj, "") + ] + +typeNames = array (minBound,maxBound) typeTable + +instance Show Type where + showsPrec _ op = showString (typeNames ! op) + +getPrimOpType :: PrimOp -> [Type] +getPrimOpType (Int_Int _) = [TyInt] +getPrimOpType (Real_Real _) = [TyReal] +getPrimOpType (Point_Real _) = [TyPoint] +getPrimOpType (Surface_Obj _) = [TyCode] +getPrimOpType (Real_Int _) = [TyReal] +getPrimOpType (Int_Real _) = [TyInt] +getPrimOpType (Arr_Int _) = [TyArray] +getPrimOpType (Int_Int_Int _) = [TyInt,TyInt] +getPrimOpType (Int_Int_Bool _) = [TyInt,TyInt] +getPrimOpType (Real_Real_Real _) = [TyReal,TyReal] +getPrimOpType (Real_Real_Bool _) = [TyReal,TyReal] +getPrimOpType (Arr_Int_Value _) = [TyArray,TyInt] +getPrimOpType (Obj_Obj_Obj _) = [TyObject,TyObject] +getPrimOpType (Point_Color_Light _) = [TyPoint,TyPoint] +getPrimOpType (Real_Real_Real_Point _) = [TyReal,TyReal,TyReal] +getPrimOpType (Obj_Real_Obj _) = [TyObject,TyReal] +getPrimOpType (Obj_Real_Real_Real_Obj _) = [TyObject,TyReal,TyReal,TyReal] +getPrimOpType (Value_String_Value _) = [TyAlpha,TyString] +getPrimOpType (Point_Point_Color_Real_Real_Light _) + = [TyPoint,TyPoint,TyPoint,TyReal,TyReal] +getPrimOpType (Render _) = [TyPoint, + TyLight, + TyObject, + TyInt, + TyReal, + TyReal, + TyReal, + TyString] + + +-- Some primitives with better error message + +mytrace v s = trace (s ++" : "++ show v ++ "\n") v + + +ixGet :: Array Int GMLValue -> Int -> GMLValue +ixGet arr i + | inRange (bounds arr) i = arr ! i + | otherwise = error ("failed access with index value " + ++ show i + ++ " (should be between 0 and " + ++ show (snd (bounds arr)) ++ ")") + +ourQuot :: Int -> Int -> Int +ourQuot _ 0 = error "attempt to use divi to divide by 0" +ourQuot a b = a `quot` b + +ourRem :: Int -> Int -> Int +ourRem _ 0 = error "attempt to use remi to divide by 0" +ourRem a b = a `rem` b + +ourSqrt :: Double -> Double +ourSqrt n | n < 0 = error "attempt to use sqrt on a negative number" + | otherwise = sqrt n + + +mySpotlight p1 p2 col cutoff exp = spotlight p1 p2 col (deg2rad cutoff) exp + +-- The problem specification gets the mapping for spheres backwards +-- (it maps the image from right to left). +-- We've fixed that in the raytracing library so that it goes from left +-- to right, but to keep the GML front compatible with the problem +-- statement, we reverse it here. + +sphere' :: SurfaceFn Color Double -> CSG (SurfaceFn Color Double) +sphere' (SFun f) = sphere (SFun (\i u v -> f i (1 - u) v)) +sphere' s = sphere s diff --git a/ghc/tests/programs/galois_raytrace/Eval.hs b/ghc/tests/programs/galois_raytrace/Eval.hs new file mode 100644 index 0000000..9d00cd9 --- /dev/null +++ b/ghc/tests/programs/galois_raytrace/Eval.hs @@ -0,0 +1,357 @@ +-- 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 Eval where + +import Array + +import IOExts + +import Geometry +import CSG +import Surface +import Data +import Parse (rayParse, rayParseF) + +class Monad m => MonadEval m where + doOp :: PrimOp -> GMLOp -> Stack -> m Stack + tick :: m () + err :: String -> m a + + tick = return () + +newtype Pure a = Pure a deriving Show + +instance Monad Pure where + Pure x >>= k = k x + return = Pure + fail s = error s + +instance MonadEval Pure where + doOp = doPureOp + err s = error s + +instance MonadEval IO where + doOp prim op stk = do { -- putStrLn ("Calling " ++ show op + -- ++ " << " ++ show stk ++ " >>") + doAllOp prim op stk + } + err s = error s + +data State + = State { env :: Env + , stack :: Stack + , code :: Code + } deriving Show + +callback :: Env -> Code -> Stack -> Stack +callback env code stk + = case eval (State { env = env, stack = stk, code = code}) of + Pure stk -> stk + +{-# SPECIALIZE eval :: State -> Pure Stack #-} +{-# SPECIALIZE eval :: State -> IO Stack #-} + +eval :: MonadEval m => State -> m Stack +eval st = + do { () <- return () -- $ unsafePerformIO (print st) -- Functional debugger + ; if moreCode st then + do { tick -- tick first, so as to catch loops on new eval. + ; st' <- step st + ; eval st' + } + else return (stack st) + } + +moreCode :: State -> Bool +moreCode (State {code = []}) = False +moreCode _ = True + +-- Step has a precondition that there *is* code to run +{-# SPECIALIZE step :: State -> Pure State #-} +{-# SPECIALIZE step :: State -> IO State #-} +step :: MonadEval m => State -> m State + +-- Rule 1: Pushing BaseValues +step st@(State{ stack = stack, code = (TBool b):cs }) + = return (st { stack = (VBool b):stack, code = cs }) +step st@(State{ stack = stack, code = (TInt i):cs }) + = return (st { stack = (VInt i):stack, code = cs }) +step st@(State{ stack = stack, code = (TReal r):cs }) + = return (st { stack = (VReal r):stack, code = cs }) +step st@(State{ stack = stack, code = (TString s):cs }) + = return (st { stack = (VString s):stack, code = cs }) + +-- Rule 2: Name binding +step st@(State{ env = env, stack = (v:stack), code = (TBind id):cs }) = + return (State { env = extendEnv env id v, stack = stack, code = cs }) +step st@(State{ env = env, stack = [], code = (TBind id):cs }) = + err "Attempt to bind the top of an empty stack" + +-- Rule 3: Name lookup +step st@(State{ env = env, stack = stack, code = (TId id):cs }) = + case (lookupEnv env id) of + Just v -> return (st { stack = v:stack, code = cs }) + Nothing -> err ("Cannot find value for identifier: " ++ id) + +-- Rule 4: Closure creation +step st@(State{ env = env, stack = stack, code = (TBody body):cs }) = + return (st { stack = (VClosure env body):stack, code = cs }) + +-- Rule 5: Application +step st@(State{ env = env, stack = (VClosure env' code'):stack, code = TApply:cs }) = + do { stk <- eval (State {env = env', stack = stack, code = code'}) + ; return (st { stack = stk, code = cs }) + } +step st@(State{ env = env, stack = [], code = TApply:cs }) = + err "Application with an empty stack" +step st@(State{ env = env, stack = _:_, code = TApply:cs }) = + err "Application of a non-closure" + +-- Rule 6: Arrays +step st@(State{ env = env, stack = stack, code = TArray code':cs }) = + do { stk <- eval (State {env = env, stack = [], code = code'}) + ; let last = length stk-1 + ; let arr = array (0,last) (zip [last,last-1..] stk) + ; return (st { stack = (VArray arr):stack, code = cs }) + } + +-- Rule 7 & 8: If statement +step st@(State{ env = env, stack = (VClosure e2 c2):(VClosure e1 c1):(VBool True):stack, code = TIf:cs }) = + do { stk <- eval (State {env = e1, stack = stack, code = c1}) + ; return (st { stack = stk, code = cs }) + } +step st@(State{ env = env, stack = (VClosure e2 c2):(VClosure e1 c1):(VBool False):stack, code = TIf:cs }) = + do { stk <- eval (State {env = e2, stack = stack, code = c2}) + ; return (st { stack = stk, code = cs }) + } +step st@(State{ env = env, stack = _, code = TIf:cs }) = + err "Incorrect use of if (bad and/or inappropriate values on the stack)" + +-- Rule 9: Operators +step st@(State{ env = env, stack = stack, code = (TOp op):cs }) = + do { stk <- doOp (opFnTable ! op) op stack + ; return (st { stack = stk, code = cs }) + } + +-- Rule Opps +step _ = err "Tripped on sidewalk while stepping." + + +-------------------------------------------------------------------------- +-- Operator code + +opFnTable :: Array GMLOp PrimOp +opFnTable = array (minBound,maxBound) + [ (op,prim) | (_,TOp op,prim) <- opcodes ] + + + + +doPureOp :: (MonadEval m) => PrimOp -> GMLOp -> Stack -> m Stack +doPureOp _ Op_render _ = + err ("\nAttempting to call render from inside a purely functional callback.") +doPureOp primOp op stk = doPrimOp primOp op stk -- call the purely functional operators + +{-# SPECIALIZE doPrimOp :: PrimOp -> GMLOp -> Stack -> Pure Stack #-} +{-# SPECIALIZE doPrimOp :: PrimOp -> GMLOp -> Stack -> IO Stack #-} +{-# SPECIALIZE doPrimOp :: PrimOp -> GMLOp -> Stack -> Abs Stack #-} + +doPrimOp :: (MonadEval m) => PrimOp -> GMLOp -> Stack -> m Stack + +-- 1 argument. + +doPrimOp (Int_Int fn) _ (VInt i1:stk) + = return ((VInt (fn i1)) : stk) +doPrimOp (Real_Real fn) _ (VReal r1:stk) + = return ((VReal (fn r1)) : stk) +doPrimOp (Point_Real fn) _ (VPoint x y z:stk) + = return ((VReal (fn x y z)) : stk) + +-- This is where the callbacks happen from... +doPrimOp (Surface_Obj fn) _ (VClosure env code:stk) + = case absapply env code [VAbsObj AbsFACE,VAbsObj AbsU,VAbsObj AbsV] of + Just [VReal r3,VReal r2,VReal r1,VPoint c1 c2 c3] -> + let + res = prop (color c1 c2 c3) r1 r2 r3 + in + return ((VObject (fn (SConst res))) : stk) + _ -> return ((VObject (fn (SFun call))) : stk) + where + -- The most general case + call i r1 r2 = + case callback env code [VReal r2,VReal r1,VInt i] of + [VReal r3,VReal r2,VReal r1,VPoint c1 c2 c3] + -> prop (color c1 c2 c3) r1 r2 r3 + stk -> error ("callback failed: incorrectly typed return arguments" + ++ show stk) + +doPrimOp (Real_Int fn) _ (VReal r1:stk) + = return ((VInt (fn r1)) : stk) +doPrimOp (Int_Real fn) _ (VInt r1:stk) + = return ((VReal (fn r1)) : stk) +doPrimOp (Arr_Int fn) _ (VArray arr:stk) + = return ((VInt (fn arr)) : stk) + +-- 2 arguments. + +doPrimOp (Int_Int_Int fn) _ (VInt i2:VInt i1:stk) + = return ((VInt (fn i1 i2)) : stk) +doPrimOp (Int_Int_Bool fn) _ (VInt i2:VInt i1:stk) + = return ((VBool (fn i1 i2)) : stk) +doPrimOp (Real_Real_Real fn) _ (VReal r2:VReal r1:stk) + = return ((VReal (fn r1 r2)) : stk) +doPrimOp (Real_Real_Bool fn) _ (VReal r2:VReal r1:stk) + = return ((VBool (fn r1 r2)) : stk) +doPrimOp (Arr_Int_Value fn) _ (VInt i:VArray arr:stk) + = return ((fn arr i) : stk) + + + -- Many arguments, typically image mangling + +doPrimOp (Obj_Obj_Obj fn) _ (VObject o2:VObject o1:stk) + = return ((VObject (fn o1 o2)) : stk) +doPrimOp (Point_Color_Light fn) _ (VPoint r g b:VPoint x y z : stk) + = return (VLight (fn (x,y,z) (color r g b)) : stk) +doPrimOp (Point_Point_Color_Real_Real_Light fn) _ + (VReal r2:VReal r1:VPoint r g b:VPoint x2 y2 z2:VPoint x1 y1 z1 : stk) + = return (VLight (fn (x1,y1,z1) (x2,y2,z2) (color r g b) r1 r2) : stk) +doPrimOp (Real_Real_Real_Point fn) _ (VReal r3:VReal r2:VReal r1:stk) + = return ((fn r1 r2 r3) : stk) +doPrimOp (Obj_Real_Obj fn) _ (VReal r:VObject o:stk) + = return (VObject (fn o r) : stk) +doPrimOp (Obj_Real_Real_Real_Obj fn) _ (VReal r3:VReal r2:VReal r1:VObject o:stk) + = return (VObject (fn o r1 r2 r3) : stk) + +-- This one is our testing harness +doPrimOp (Value_String_Value fn) _ (VString s:o:stk) + = res `seq` return (res : stk) + where + res = fn o s + +doPrimOp primOp op args + = err ("\n\ntype error when attempting to execute builtin primitive \"" ++ + show op ++ "\"\n\n| " ++ + show op ++ " takes " ++ show (length types) ++ " argument" ++ s + ++ " with" ++ the ++ " type" ++ s ++ "\n|\n|" ++ + " " ++ unwords [ show ty | ty <- types ] ++ "\n|\n|" ++ + " currently, the relevent argument" ++ s ++ " on the stack " ++ + are ++ "\n|\n| " ++ + unwords [ "(" ++ show arg ++ ")" + | arg <- reverse (take (length types) args) ] ++ "\n|\n| " + ++ " (top of stack is on the right hand side)\n\n") + where + len = length types + s = (if len /= 1 then "s" else "") + are = (if len /= 1 then "are" else "is") + the = (if len /= 1 then "" else " the") + types = getPrimOpType primOp + + +-- Render is somewhat funny, becauase it can only get called at top level. +-- All other operations are purely functional. + +doAllOp :: PrimOp -> GMLOp -> Stack -> IO Stack +doAllOp (Render render) Op_render + (VString str:VInt ht:VInt wid:VReal fov + :VInt dep:VObject obj:VArray arr + :VPoint r g b : stk) + = do { render (color r g b) lights obj dep (fov * (pi / 180.0)) wid ht str + ; return stk + } + where + lights = [ light | (VLight light) <- elems arr ] + +doAllOp primOp op stk = doPrimOp primOp op stk -- call the purely functional operators + +------------------------------------------------------------------------------ +{- + - Abstract evaluation. + - + - The idea is you check for constant code that + - (1) does not look at its arguments + - (2) gives a fixed result + - + - We run for 100 steps. + - + -} + +absapply :: Env -> Code -> Stack -> Maybe Stack +absapply env code stk = + case runAbs (eval (State env stk code)) 100 of + AbsState stk _ -> Just stk + AbsFail m -> Nothing + +newtype Abs a = Abs { runAbs :: Int -> AbsState a } +data AbsState a = AbsState a !Int + | AbsFail String + +instance Monad Abs where + (Abs fn) >>= k = Abs (\ s -> case fn s of + AbsState r s' -> runAbs (k r) s' + AbsFail m -> AbsFail m) + return x = Abs (\ n -> AbsState x n) + fail s = Abs (\ n -> AbsFail s) + +instance MonadEval Abs where + doOp = doAbsOp + err = fail + tick = Abs (\ n -> if n <= 0 + then AbsFail "run out of time" + else AbsState () (n-1)) + +doAbsOp :: PrimOp -> GMLOp -> Stack -> Abs Stack +doAbsOp _ Op_point (VReal r3:VReal r2:VReal r1:stk) + = return ((VPoint r1 r2 r3) : stk) + -- here, you could have an (AbsPoint :: AbsObj) which you put on the + -- stack, with any object in the three fields. +doAbsOp _ op _ = err ("operator not understood (" ++ show op ++ ")") + +------------------------------------------------------------------------------ +-- Driver + +mainEval :: Code -> IO () +mainEval prog = do { stk <- eval (State emptyEnv [] prog) + ; return () + } +{- + * Oops, one of the example actually has something + * on the stack at the end. + * Oh well... + ; if null stk + then return () + else do { putStrLn done + ; print stk + } +-} + +done = "Items still on stack at (successfull) termination of program" + +------------------------------------------------------------------------------ +-- testing + +test :: String -> Pure Stack +test is = eval (State emptyEnv [] (rayParse is)) + +testF :: String -> IO Stack +testF is = do prog <- rayParseF is + eval (State emptyEnv [] prog) + +testA :: String -> Either String (Stack,Int) +testA is = case runAbs (eval (State emptyEnv + [VAbsObj AbsFACE,VAbsObj AbsU,VAbsObj AbsV] + (rayParse is))) 100 of + AbsState a n -> Right (a,n) + AbsFail m -> Left m + +abstest1 = "1.0 0.0 0.0 point /red { /v /u /face red 1.0 0.0 1.0 } apply" + +-- should be [3:: Int] +et1 = test "1 /x { x } /f 2 /x f apply x addi" + + + + + diff --git a/ghc/tests/programs/galois_raytrace/Geometry.hs b/ghc/tests/programs/galois_raytrace/Geometry.hs new file mode 100644 index 0000000..673c7d4 --- /dev/null +++ b/ghc/tests/programs/galois_raytrace/Geometry.hs @@ -0,0 +1,314 @@ +-- 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 diff --git a/ghc/tests/programs/galois_raytrace/Illumination.hs b/ghc/tests/programs/galois_raytrace/Illumination.hs new file mode 100644 index 0000000..9242cbf --- /dev/null +++ b/ghc/tests/programs/galois_raytrace/Illumination.hs @@ -0,0 +1,212 @@ +-- 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) + +module Illumination + ( Object + , Light (..) + , light, pointlight, spotlight + , render + ) where + +import Array +import Char(chr) +import IOExts +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 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)] + ] + +avg cs = divN (fromIntegral (length cs)) (uncolor (sumCC cs)) + where divN n (r,g,b) = color (r / n) (g / n) (b / n) + +contrast :: (Int, Int) -> Array (Int, Int) Color -> Bool +contrast (x, y) arr = any diffMax [ subCC cur (arr ! (x + xd, y + yd)) + | xd <- [-1, 1], yd <- [-1, 1] + ] + where cur = arr ! (x, y) + diffMax col = (abs r) > 0.25 || (abs g) > 0.2 || (abs b) > 0.4 + where + (r,g,b) = uncolor col + + +illumination :: Cxt -> Ray -> Color +illumination cxt (r,v) + | depth cxt <= 0 = black + | otherwise = case castRay (r,v) (object cxt) of + Nothing -> black + Just info -> illum (cxt{depth=(depth cxt)-1}) info v + +illum :: Cxt -> (Point,Vector,Properties Color Double) -> Vector -> Color +illum cxt (pos,normV,(col,kd,ks,n)) v + = ambTerm `addCC` difTerm `addCC` spcTerm `addCC` recTerm + where + visibleLights = unobscured pos (object cxt) (lights cxt) normV + d = depth cxt + amb = ambient cxt + newV = subVV v (multSV (2 * dot normV v) normV) + + ambTerm = multSC kd (multCC amb col) + difTerm = multSC kd (sumCC [multSC (dot normV lj) (multCC intensity col) + |(loc,intensity) <- visibleLights, + let lj = normalize ({- pos `subVV` -} loc)]) + -- ZZ might want to avoid the phong, when you can... + spcTerm = multSC ks (sumCC [multSC ((dot normV hj) ** n ) (multCC intensity col) + |(loc,intensity) <- visibleLights, + -- ZZ note this is specific to the light at infinity + let lj = {- pos `subVV` -} normalize loc, + let hj = normalize (lj `subVV` normalize v)]) + recTerm = if recCoeff `nearC` black then black else multCC recCoeff recRay + recCoeff = multSC ks col + recRay = illumination cxt (pos,newV) + +showBitmapA :: Int -> Int -> Array (Int, Int) Color -> String +showBitmapA wid ht arr + = header ++ concatMap scaleColor (elems arr) + where + scaleColor col = [scalePixel r, scalePixel g, scalePixel b] + where (r,g,b) = uncolor col + header = "P6\n#Galois\n" ++ show wid ++ " " ++ show ht ++ "\n255\n" + +showBitmap :: Int -> Int ->[[Color]] -> String +showBitmap wid ht pss +-- type of assert | length pss == ht && all (\ ps -> length ps == wid) pss + = header ++ concat [[scalePixel r,scalePixel g,scalePixel b] + | ps <- pss, (r,g,b) <- map uncolor ps] + where + header = "P6\n#Galois\n" ++ show wid ++ " " ++ show ht ++ "\n255\n" +showBitmap _ _ _ = error "incorrect length of bitmap string" + +scalePixel :: Double -> Char +scalePixel p = chr (floor (clampf p * 255)) + + +-- Lights + +data Light = Light Vector Color + | PointLight Point Color + | SpotLight Point Point Color Radian Double + deriving Show + +light :: Coords -> Color -> Light +light (x,y,z) color = + Light (normalize (vector (-x) (-y) (-z))) color +pointlight (x,y,z) color = + PointLight (point x y z) color +spotlight (x,y,z) (p,q,r) col cutoff exp = + SpotLight (point x y z) (point p q r) col cutoff exp + +transformLight m (Light v c) = Light (multMV m v) c +transformLight m (PointLight p c) = PointLight (multMP m p) c +transformLight m (SpotLight p q c r d) = SpotLight (multMP m p) (multMP m q) c r d + +unobscured :: Point -> Object -> [Light] -> Vector -> [(Vector,Color)] +unobscured pos obj lights normV = catMaybes (map (unobscure pos obj normV) lights) + +unobscure :: Point -> Object -> Vector -> Light -> Maybe (Vector,Color) +unobscure pos obj normV (Light vec color) + -- ZZ probably want to make this faster + | vec `dot` normV < 0 = Nothing + | intersects (pos `addPV` (0.0001 `multSV` vec),vec) obj = Nothing + | otherwise = Just (vec,color) +unobscure pos obj normV (PointLight pp color) + | vec `dot` normV < 0 = Nothing + | intersectWithin (pos `addPV` (0.0001 `multSV` (normalize vec)), vec) obj = Nothing + | otherwise = Just (vec,is) + where vec = pp `subPP` pos + is = attenuate vec color +unobscure org obj normV (SpotLight pos at color cutoff exp) + | vec `dot` normV < 0 = Nothing + | intersectWithin (org `addPV` (0.0001 `multSV` (normalize vec)), vec) obj = Nothing + | angle > cutoff = Nothing + | otherwise = Just (vec, is) + where vec = pos `subPP` org + vec' = pos `subPP` at + angle = acos (normalize vec `dot` (normalize vec')) + + asp = normalize (at `subPP` pos) + qsp = normalize (org `subPP` pos) + is = attenuate vec (((asp `dot` qsp) ** exp) `multSC` color) + +attenuate :: Vector -> Color -> Color +attenuate vec color = (100 / (99 + sq (norm vec))) `multSC` color + +-- + +castRay ray p + = case intersectRayWithObject ray p of + (True, _, _) -> Nothing -- eye is inside + (False, [], _) -> Nothing -- eye is inside + (False, (0, b, _) : _, _) -> Nothing -- eye is inside + (False, (i, False, _) : _, _) -> Nothing -- eye is inside + (False, (t, b, (s, p0)) : _, _) -> + let (v, prop) = surface s p0 in + Just (offsetToPoint ray t, v, prop) + +intersects ray p + = case intersectRayWithObject ray p of + (True, _, _) -> False + (False, [], _) -> False + (False, (0, b, _) : _, _) -> False + (False, (i, False, _) : _, _) -> False + (False, (i, b, _) : _, _) -> True + +intersectWithin :: Ray -> Object -> Bool +intersectWithin ray p + = case intersectRayWithObject ray p of + (True, _, _) -> False -- eye is inside + (False, [], _) -> False -- eye is inside + (False, (0, b, _) : _, _) -> False -- eye is inside + (False, (i, False, _) : _, _) -> False -- eye is inside + (False, (t, b, _) : _, _) -> t < 1.0 diff --git a/ghc/tests/programs/galois_raytrace/Intersections.hs b/ghc/tests/programs/galois_raytrace/Intersections.hs new file mode 100644 index 0000000..c7fe003 --- /dev/null +++ b/ghc/tests/programs/galois_raytrace/Intersections.hs @@ -0,0 +1,404 @@ +-- 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 Intersections + ( intersectRayWithObject, + quadratic + ) where + +import Maybe(isJust) + +import Construct +import Geometry +import Interval +import Misc + +-- This is factored into two bits. The main function `intersections' +-- intersects a line with an object. +-- The wrapper call `intersectRayWithObject' coerces this to an intersection +-- with a ray by clamping the result to start at 0. + +intersectRayWithObject ray p + = clampIntervals is + where is = intersections ray p + +clampIntervals (True, [], True) = (False, [(0, True, undefined)], True) +clampIntervals empty@(False, [], False) = empty +clampIntervals (True, is@((i, False, p) : is'), isOpen) + | i `near` 0 || i < 0 + = clampIntervals (False, is', isOpen) + | otherwise + = (False, (0, True, undefined) : is, isOpen) +clampIntervals ivals@(False, is@((i, True, p) : is'), isOpen) + | i `near` 0 || i < 0 + -- can unify this with first case... + = clampIntervals (True, is', isOpen) + | otherwise + = ivals + +intersections ray (Union p q) + = unionIntervals is js + where is = intersections ray p + js = intersections ray q + +intersections ray (Intersect p q) + = intersectIntervals is js + where is = intersections ray p + js = intersections ray q + +intersections ray (Difference p q) + = differenceIntervals is (negateSurfaces js) + where is = intersections ray p + js = intersections ray q + +intersections ray (Transform m m' p) + = mapI (xform m) is + where is = intersections (m' `multMR` ray) p + xform m (i, b, (s, p0)) = (i, b, (transformSurface m s, p0)) + +intersections ray (Box box p) + | intersectWithBox ray box = intersections ray p + | otherwise = emptyIList + +intersections ray p@(Plane s) + = intersectPlane ray s + +intersections ray p@(Sphere s) + = intersectSphere ray s + +intersections ray p@(Cube s) + = intersectCube ray s + +intersections ray p@(Cylinder s) + = intersectCylinder ray s + +intersections ray p@(Cone s) + = intersectCone ray s + +negateSurfaces :: IList (Surface, Texture a) -> IList (Surface, Texture a) +negateSurfaces = mapI negSurf + where negSurf (i, b, (s,t)) = (i, b, (negateSurface s, t)) + +negateSurface (Planar p0 v0 v1) + = Planar p0 v1 v0 +negateSurface (Spherical p0 v0 v1) + = Spherical p0 v1 v0 +negateSurface (Cylindrical p0 v0 v1) + = Cylindrical p0 v1 v0 +negateSurface (Conic p0 v0 v1) + = Conic p0 v1 v0 + +transformSurface m (Planar p0 v0 v1) + = Planar p0' v0' v1' + where p0' = multMP m p0 + v0' = multMV m v0 + v1' = multMV m v1 + +transformSurface m (Spherical p0 v0 v1) + = Spherical p0' v0' v1' + where p0' = multMP m p0 + v0' = multMV m v0 + v1' = multMV m v1 + +-- ditto as above +transformSurface m (Cylindrical p0 v0 v1) + = Cylindrical p0' v0' v1' + where p0' = multMP m p0 + v0' = multMV m v0 + v1' = multMV m v1 + +transformSurface m (Conic p0 v0 v1) + = Conic p0' v0' v1' + where p0' = multMP m p0 + v0' = multMV m v0 + v1' = multMV m v1 + +-------------------------------- +-- Plane +-------------------------------- + +intersectPlane :: Ray -> a -> IList (Surface, Texture a) +intersectPlane ray texture = intersectXZPlane PlaneFace ray 0.0 texture + +intersectXZPlane :: Face -> Ray -> Double -> a -> IList (Surface, Texture a) +intersectXZPlane n (r,v) yoffset texture + | b `near` 0 + = -- the ray is parallel to the plane - it's either all in, or all out + if y `near` yoffset || y < yoffset then openIList else emptyIList + + -- The line intersects the plane. Find t such that + -- (x + at, y + bt, z + ct) intersects the X-Z plane. + -- t may be negative (the ray starts within the halfspace), + -- but we'll catch that later when we clamp the intervals + + | b < 0 -- the ray is pointing downwards + = (False, [mkEntry (t0, (Planar p0 v0 v1, (n, p0, texture)))], True) + + | otherwise -- the ray is pointing upwards + = (True, [mkExit (t0, (Planar p0 v0 v1, (n, p0, texture)))], False) + + where t0 = (yoffset-y) / b + x0 = x + a * t0 + z0 = z + c * t0 + p0 = point x0 0 z0 + v0 = vector 0 0 1 + v1 = vector 1 0 0 + + x = xCoord r + y = yCoord r + z = zCoord r + a = xComponent v + b = yComponent v + c = zComponent v + + +-------------------------------- +-- Sphere +-------------------------------- + +intersectSphere :: Ray -> a -> IList (Surface, Texture a) +intersectSphere ray@(r, v) texture + = -- Find t such that (x + ta, y + tb, z + tc) intersects the + -- unit sphere, that is, such that: + -- (x + ta)^2 + (y + tb)^2 + (z + tc)^2 = 1 + -- This is a quadratic equation in t: + -- t^2(a^2 + b^2 + c^2) + 2t(xa + yb + zc) + (x^2 + y^2 + z^2 - 1) = 0 + let c1 = sq a + sq b + sq c + c2 = 2 * (x * a + y * b + z * c) + c3 = sq x + sq y + sq z - 1 + in + case quadratic c1 c2 c3 of + Nothing -> emptyIList + Just (t1, t2) -> entryexit (g t1) (g t2) + where x = xCoord r + y = yCoord r + z = zCoord r + a = xComponent v + b = yComponent v + c = zComponent v + g t = (t, (Spherical origin v1 v2, (SphereFace, p0, texture))) + where origin = point 0 0 0 + x0 = x + t * a + y0 = y + t * b + z0 = z + t * c + p0 = point x0 y0 z0 + v0 = vector x0 y0 z0 + (v1, v2) = tangents v0 + + +-------------------------------- +-- Cube +-------------------------------- + +intersectCube :: Ray -> a -> IList (Surface, Texture a) +intersectCube ray@(r, v) texture + = -- The set of t such that (x + at, y + bt, z + ct) lies within + -- the unit cube satisfies: + -- 0 <= x + at <= 1, 0 <= y + bt <= 1, 0 <= z + ct <= 1 + -- The minimum and maximum such values of t give us the two + -- intersection points. + case intersectSlabIval (intersectCubeSlab face2 face3 x a) + (intersectSlabIval (intersectCubeSlab face5 face4 y b) + (intersectCubeSlab face0 face1 z c)) of + Nothing -> emptyIList + Just (t1, t2) -> entryexit (g t1) (g t2) + where g ((n, v0, v1), t) + = (t, (Planar p0 v0 v1, (n, p0, texture))) + where p0 = offsetToPoint ray t + face0 = (CubeFront, vectorY, vectorX) + face1 = (CubeBack, vectorX, vectorY) + face2 = (CubeLeft, vectorZ, vectorY) + face3 = (CubeRight, vectorY, vectorZ) + face4 = (CubeTop, vectorZ, vectorX) + face5 = (CubeBottom, vectorX, vectorZ) + vectorX = vector 1 0 0 + vectorY = vector 0 1 0 + vectorZ = vector 0 0 1 + x = xCoord r + y = yCoord r + z = zCoord r + a = xComponent v + b = yComponent v + c = zComponent v + +intersectCubeSlab n m w d + | d `near` 0 = if (0 <= w) && (w <= 1) + then Just ((n, -inf), (m, inf)) else Nothing + | d > 0 = Just ((n, (-w)/d), (m, (1-w)/d)) + | otherwise = Just ((m, (1-w)/d), (n, (-w)/d)) + +intersectSlabIval Nothing Nothing = Nothing +intersectSlabIval Nothing (Just i) = Nothing +intersectSlabIval (Just i) Nothing = Nothing +intersectSlabIval (Just (nu1@(n1, u1), mv1@(m1, v1))) + (Just (nu2@(n2, u2), mv2@(m2, v2))) + = checkInterval (nu, mv) + where nu = if u1 < u2 then nu2 else nu1 + mv = if v1 < v2 then mv1 else mv2 + checkInterval numv@(nu@(_, u), (m, v)) + -- rounding error may force us to push v out a bit + | u `near` v = Just (nu, (m, u + epsilon)) + | u < v = Just numv + | otherwise = Nothing + + +-------------------------------- +-- Cylinder +-------------------------------- + +intersectCylinder :: Ray -> a -> IList (Surface, Texture a) +intersectCylinder ray texture + = isectSide `intersectIntervals` isectTop `intersectIntervals` isectBottom + where isectSide = intersectCylSide ray texture + isectTop = intersectXZPlane CylinderTop ray 1.0 texture + isectBottom = complementIntervals $ negateSurfaces $ + intersectXZPlane CylinderBottom ray 0.0 texture + +intersectCylSide (r, v) texture + = -- The ray (x + ta, y + tb, z + tc) intersects the sides of the + -- cylinder if: + -- (x + ta)^2 + (z + tc)^2 = 1 and 0 <= y + tb <= 1. + if (sq a + sq c) `near` 0 + then -- The ray is parallel to the Y-axis, and does not intersect + -- the cylinder sides. It's either all in, or all out + if (sqxy `near` 1.0 || sqxy < 1.0) then openIList else emptyIList + else -- Find values of t that solve the quadratic equation + -- (a^2 + c^2)t^2 + 2(ax + cz)t + x^2 + z^2 - 1 = 0 + let c1 = sq a + sq c + c2 = 2 * (x * a + z * c) + c3 = sq x + sq z - 1 + in + case quadratic c1 c2 c3 of + Nothing -> emptyIList + Just (t1, t2) -> entryexit (g t1) (g t2) + + where sqxy = sq x + sq y + g t = (t, (Cylindrical origin v1 v2, (CylinderSide, p0, texture))) + where origin = point 0 0 0 + x0 = x + t * a + y0 = y + t * b + z0 = z + t * c + p0 = point x0 y0 z0 + v0 = vector x0 0 z0 + (v1, v2) = tangents v0 + + x = xCoord r + y = yCoord r + z = zCoord r + a = xComponent v + b = yComponent v + c = zComponent v + + +------------------- +-- Cone +------------------- + +intersectCone :: Ray -> a -> IList (Surface, Texture a) +intersectCone ray texture + = isectSide `intersectIntervals` isectTop `intersectIntervals` isectBottom + where isectSide = intersectConeSide ray texture + isectTop = intersectXZPlane ConeBase ray 1.0 texture + isectBottom = complementIntervals $ negateSurfaces $ + intersectXZPlane ConeBase ray 0.0 texture + +intersectConeSide (r, v) texture + = -- Find the points where the ray intersects the cond side. At any points of + -- intersection, we must have: + -- (x + ta)^2 + (z + tc)^2 = (y + tb)^2 + -- which is the following quadratic equation: + -- t^2(a^2-b^2+c^2) + 2t(xa-yb+cz) + (x^2-y^2+z^2) = 0 + let c1 = sq a - sq b + sq c + c2 = 2 * (x * a - y * b + c * z) + c3 = sq x - sq y + sq z + in case quadratic c1 c2 c3 of + Nothing -> emptyIList + Just (t1, t2) -> + -- If either intersection strikes the middle, then the other + -- can only be off by rounding error, so we make a tangent + -- strike using the "good" value. + -- If the intersections straddle the origin, then it's + -- an exit/entry pair, otherwise it's an entry/exit pair. + let y1 = y + t1 * b + y2 = y + t2 * b + in if y1 `near` 0 then entryexit (g t1) (g t1) + else if y2 `near` 0 then entryexit (g t2) (g t2) + else if (y1 < 0) `xor` (y2 < 0) then exitentry (g t1) (g t2) + else entryexit (g t1) (g t2) + + where g t = (t, (Conic origin v1 v2, (ConeSide, p0, texture))) + where origin = point 0 0 0 + x0 = x + t * a + y0 = y + t * b + z0 = z + t * c + p0 = point x0 y0 z0 + v0 = normalize $ vector x0 (-y0) z0 + (v1, v2) = tangents v0 + + x = xCoord r + y = yCoord r + z = zCoord r + a = xComponent v + b = yComponent v + c = zComponent v + + -- beyond me why this isn't defined in the prelude... + xor False b = b + xor True b = not b + + +------------------- +-- Solving quadratics +------------------- + +quadratic :: Double -> Double -> Double -> Maybe (Double, Double) +quadratic a b c = + -- Solve the equation ax^2 + bx + c = 0 by using the quadratic formula. + let d = sq b - 4 * a * c + d' = if d `near` 0 then 0 else d + in if d' < 0 + then Nothing -- There are no real roots. + else + if a > 0 then Just (((-b) - sqrt d') / (2 * a), + ((-b) + sqrt d') / (2 * a)) + else Just (((-b) + sqrt d') / (2 * a), + ((-b) - sqrt d') / (2 * a)) + +------------------- +-- Bounding boxes +------------------- + +data MaybeInterval = Interval !Double !Double + | NoInterval + +isInterval (Interval _ _) = True +isInterval _ = False + +intersectWithBox :: Ray -> Box -> Bool +intersectWithBox (r, v) (B x1 x2 y1 y2 z1 z2) + = isInterval interval + where x_interval = intersectRayWithSlab (xCoord r) (xComponent v) (x1, x2) + y_interval = intersectRayWithSlab (yCoord r) (yComponent v) (y1, y2) + z_interval = intersectRayWithSlab (zCoord r) (zComponent v) (z1, z2) + interval = intersectInterval x_interval + (intersectInterval y_interval z_interval) + +intersectInterval :: MaybeInterval -> MaybeInterval -> MaybeInterval +intersectInterval NoInterval _ = NoInterval +intersectInterval _ NoInterval = NoInterval +intersectInterval (Interval a b) (Interval c d) + | b < c || d < a = NoInterval + | otherwise = Interval (a `max` c) (b `min` d) + +{-# INLINE intersectRayWithSlab #-} +intersectRayWithSlab :: Double -> Double -> (Double,Double) -> MaybeInterval +intersectRayWithSlab xCoord alpha (x1, x2) + | alpha == 0 = if xCoord < x1 || xCoord > x2 then NoInterval else infInterval + | alpha > 0 = Interval a b + | otherwise = Interval b a + where a = (x1 - xCoord) / alpha + b = (x2 - xCoord) / alpha + +infInterval = Interval (-inf) inf diff --git a/ghc/tests/programs/galois_raytrace/Interval.hs b/ghc/tests/programs/galois_raytrace/Interval.hs new file mode 100644 index 0000000..a4d313f --- /dev/null +++ b/ghc/tests/programs/galois_raytrace/Interval.hs @@ -0,0 +1,121 @@ +-- 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 Interval + ( IList + , Intersection + , emptyIList, openIList + , mkEntry, mkExit + , entryexit, exitentry + , mapI + , unionIntervals, intersectIntervals, differenceIntervals + , complementIntervals + ) where + +import Geometry + +-- The result of a ray trace is represented as a list of surface +-- intersections. Each intersection is a point along the ray with +-- a flag indicating whether this intersection is an entry or an +-- exit from the solid. Each intersection also carries unspecified +-- surface data for use by the illumination model. + +-- Just the list of intersections isn't enough, however. An empty +-- list can denote either a trace that is always within the solid +-- or never in the solid. To dissambiguate, an extra flag is kept +-- that indicates whether we are starting inside or outside of the +-- solid. As a convenience, we also keep an additional flag that +-- indicates whether the last intersection ends inside or outside. + +type IList a = (Bool, [Intersection a], Bool) +type Intersection a = (Double, Bool, a) + +emptyIList = (False, [], False) +openIList = (True, [], True) + +mapI f (b1, is, b2) = (b1, map f is, b2) + +isEntry (_, entry, _) = entry +isExit (_, entry, _) = not entry + +mkEntry (t, a) = (t, True, a) +mkExit (t, a) = (t, False, a) + +entryexit w1 w2 = (False, [mkEntry w1, mkExit w2], False) +exitentry w1 w2 = (True, [mkExit w1, mkEntry w2], True) +arrange w1@(t1, _) w2@(t2, _) | t1 < t2 = entryexit w1 w2 + | otherwise = entryexit w2 w1 + + +cmpI :: Intersection a -> Intersection a -> Ordering +cmpI (i, _, _) (j, _, _) + | i `near` j = EQ + | i < j = LT + | otherwise = GT + +bad (b1, [], b2) = b1 /= b2 +bad (b1, is, b2) = bad' b1 is || b2 /= b3 + where (_, b3, _) = last is + +bad' b [] = False +bad' b ((_, c, _) : is) = b == c || bad' c is + +unionIntervals :: IList a -> IList a -> IList a +unionIntervals (isStartOpen, is, isEndOpen) (jsStartOpen, js, jsEndOpen) + = (isStartOpen || jsStartOpen, uniIntervals is js, isEndOpen || jsEndOpen) + where uniIntervals is [] | jsEndOpen = [] + | otherwise = is + uniIntervals [] js | isEndOpen = [] + | otherwise = js + uniIntervals is@(i : is') js@(j : js') + = case cmpI i j of + EQ -> if isEntry i == isEntry j then i : uniIntervals is' js' + else uniIntervals is' js' + LT -> if isEntry j then i : uniIntervals is' js + else uniIntervals is' js + GT -> if isEntry i then j : uniIntervals is js' + else uniIntervals is js' + +intersectIntervals :: IList a -> IList a -> IList a +intersectIntervals is js + = complementIntervals (unionIntervals is' js') + where is' = complementIntervals is + js' = complementIntervals js + +differenceIntervals :: IList a -> IList a -> IList a +differenceIntervals is js + = complementIntervals (unionIntervals is' js) + where is' = complementIntervals is + +complementIntervals :: IList a -> IList a +complementIntervals (o1, is, o2) + = (not o1, [ (i, not isentry, a) | (i, isentry, a) <- is ], not o2) + +-- tests... + +{- +mkIn, mkOut :: Double -> Intersection a +mkIn x = (x, True, undefined) +mkOut x = (x, False, undefined) + +i1 = (False, [ mkIn 2, mkOut 7 ], False) +i1' = (True, [ mkOut 2, mkIn 7 ], True) +i2 = (False, [ mkIn 1, mkOut 3, mkIn 4, mkOut 5, mkIn 6, mkOut 8 ], False) + +t1 = unionIntervals i1 i2 +t2 = intersectIntervals i1 i2 +t3 = intersectIntervals i2 i1 +t4 = complementIntervals i1 +t5 = intersectIntervals i2 i1' +t6 = differenceIntervals i2 i1 +t7 = differenceIntervals i2 i2 + +sh (o1,is,o2) = + do if o1 then putStr "..." else return () + putStr $ foldr1 (++) (map si is) + if o2 then putStr "..." else return () +si (i, True, _, _) = "<" ++ show i +si (i, False, _, _) = " " ++ show i ++ ">" +-} diff --git a/ghc/tests/programs/galois_raytrace/Main.hs b/ghc/tests/programs/galois_raytrace/Main.hs new file mode 100644 index 0000000..4ef9fe3 --- /dev/null +++ b/ghc/tests/programs/galois_raytrace/Main.hs @@ -0,0 +1,17 @@ +-- 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 read sample input directly from a file. + +module Main where + +import System + +import Parse +import Eval + +main = do { str <- readFile "galois.gml" + ; mainEval (rayParse str) + } diff --git a/ghc/tests/programs/galois_raytrace/Makefile b/ghc/tests/programs/galois_raytrace/Makefile new file mode 100644 index 0000000..f181efd --- /dev/null +++ b/ghc/tests/programs/galois_raytrace/Makefile @@ -0,0 +1,9 @@ +TOP = .. +include $(TOP)/mk/boilerplate.mk + +SRC_HC_OPTS += -package text -package lang + +all :: runtest + +include $(TOP)/mk/target.mk + diff --git a/ghc/tests/programs/galois_raytrace/Misc.hs b/ghc/tests/programs/galois_raytrace/Misc.hs new file mode 100644 index 0000000..1368b31 --- /dev/null +++ b/ghc/tests/programs/galois_raytrace/Misc.hs @@ -0,0 +1,11 @@ +-- 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 Misc where + +import IOExts + +debug s v = trace (s ++" : "++ show v ++ "\n") v +-- debug s v = v diff --git a/ghc/tests/programs/galois_raytrace/Parse.hs b/ghc/tests/programs/galois_raytrace/Parse.hs new file mode 100644 index 0000000..10b9f9b --- /dev/null +++ b/ghc/tests/programs/galois_raytrace/Parse.hs @@ -0,0 +1,137 @@ +-- 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 Parse where + +import Char +import Parsec hiding (token) + +import Data + + +program :: Parser Code +program = + do { whiteSpace + ; ts <- tokenList + ; eof + ; return ts + } + +tokenList :: Parser Code +tokenList = many token "list of tokens" + +token :: Parser GMLToken +token = + do { ts <- braces tokenList ; return (TBody ts) } + <|> do { ts <- brackets tokenList ; return (TArray ts) } + <|> (do { s <- gmlString ; return (TString s) } "string") + <|> (do { t <- pident False ; return t } "identifier") + <|> (do { char '/' -- No whitespace after slash + ; t <- pident True ; return t } "binding identifier") + <|> (do { n <- number ; return n } "number") + +pident :: Bool -> Parser GMLToken +pident rebind = + do { id <- ident + ; case (lookup id opTable) of + Nothing -> if rebind then return (TBind id) else return (TId id) + Just t -> if rebind then error ("Attempted rebinding of identifier " ++ id) else return t + } + +ident :: Parser String +ident = lexeme $ + do { l <- letter + ; ls <- many (satisfy (\x -> isAlphaNum x || x == '-' || x == '_')) + ; return (l:ls) + } + +gmlString :: Parser String +gmlString = lexeme $ between (char '"') (char '"') (many (satisfy (\x -> isPrint x && x /= '"'))) + +-- Tests for numbers +-- Hugs breaks on big exponents (> ~40) +test_number = "1234 -1234 1 -0 0" ++ + " 1234.5678 -1234.5678 1234.5678e12 1234.5678e-12 -1234.5678e-12" ++ + " -1234.5678e12 -1234.5678E-12 -1234.5678E12" ++ + " 1234e11 1234E33 -1234e33 1234e-33" ++ + " 123e 123.4e 123ee 123.4ee 123E 123.4E 123EE 123.4EE" + + +-- Always int or real +number :: Parser GMLToken +number = lexeme $ + do { s <- optSign + ; n <- decimal + ; do { string "." + ; m <- decimal + ; e <- option "" exponent' + ; return (TReal (read (s ++ n ++ "." ++ m ++ e))) -- FIXME: Handle error conditions + } + <|> do { e <- exponent' + ; return (TReal (read (s ++ n ++ ".0" ++ e))) + } + <|> do { return (TInt (read (s ++ n))) } + } + +exponent' :: Parser String +exponent' = try $ + do { e <- oneOf "eE" + ; s <- optSign + ; n <- decimal + ; return (e:s ++ n) + } + +decimal = many1 digit + +optSign :: Parser String +optSign = option "" (string "-") + + +------------------------------------------------------ +-- Library for tokenizing. + +braces p = between (symbol "{") (symbol "}") p +brackets p = between (symbol "[") (symbol "]") p + +symbol name = lexeme (string name) + +lexeme p = do{ x <- p; whiteSpace; return x } + +whiteSpace = skipMany (simpleSpace <|> oneLineComment "") + where simpleSpace = skipMany1 (oneOf " \t\n\r\v") + oneLineComment = + do{ string "%" + ; skipMany (noneOf "\n\r\v") + ; return () + } + + +------------------------------------------------------------------------------ + +rayParse :: String -> Code +rayParse is = case (parse program "" is) of + Left err -> error (show err) + Right x -> x + +rayParseF :: String -> IO Code +rayParseF file = + do { r <- parseFromFile program file + ; case r of + Left err -> error (show err) + Right x -> return x + } + +run :: String -> IO () +run is = case (parse program "" is) of + Left err -> print err + Right x -> print x + +runF :: IO () +runF = + do { r <- parseFromFile program "simple.gml" + ; case r of + Left err -> print err + Right x -> print x + } diff --git a/ghc/tests/programs/galois_raytrace/Pixmap.hs b/ghc/tests/programs/galois_raytrace/Pixmap.hs new file mode 100644 index 0000000..11d20f0 --- /dev/null +++ b/ghc/tests/programs/galois_raytrace/Pixmap.hs @@ -0,0 +1,64 @@ +-- 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 Pixmap where + +import Char +import IO hiding (try) +import Parsec + +readPPM f + = do h <- openFile f ReadMode + s <- hGetContents h + case (parse parsePPM f s) of + Left err -> error (show err) + Right x -> return x + +writePPM f ppm + = do h <- openFile f WriteMode + let s = showPPM (length (head ppm)) (length ppm) ppm + hPutStr h s + +-- parsing + +parsePPM + = do string "P6" + whiteSpace + width <- number + whiteSpace + height <- number + whiteSpace + colormax <- number + whiteSpace + cs <- getInput + return (chop width (chopColors cs)) + +chopColors [] = [] +chopColors (a:b:c:ds) = (ord a, ord b, ord c) : chopColors ds + +chop n [] = [] +chop n xs = h : chop n t + where (h, t) = splitAt n xs + +number + = do ds <- many1 digit + return (read ds :: Int) + +whiteSpace + = skipMany (simpleSpace <|> oneLineComment "") + where simpleSpace = skipMany1 (oneOf " \t\n\r\v") + oneLineComment = + do char '#' + skipMany (noneOf "\n\r\v") + return () + +-- printing + +showPPM :: Int -> Int -> [[(Int,Int,Int)]] -> String +showPPM wid ht pss + = header ++ concat [[chr r, chr g, chr b] | ps <- pss, (r, g, b) <-ps] + where + header = "P6\n#Galois\n" ++ show wid ++ " " ++ show ht ++ "\n255\n" +showPPM _ _ _ = error "incorrect length of bitmap string" diff --git a/ghc/tests/programs/galois_raytrace/Primitives.hs b/ghc/tests/programs/galois_raytrace/Primitives.hs new file mode 100644 index 0000000..2f21654 --- /dev/null +++ b/ghc/tests/programs/galois_raytrace/Primitives.hs @@ -0,0 +1,24 @@ +-- 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 Primitives where + +rad2deg :: Double -> Double +rad2deg r = r * 180 / pi + +deg2rad :: Double -> Double +deg2rad d = d * pi / 180 + +addi :: Int -> Int -> Int +addi = (+) + +addf :: Double -> Double -> Double +addf = (+) + +acosD :: Double -> Double +acosD x = acos x * 180 / pi + +asinD :: Double -> Double +asinD x = asin x * 180 / pi diff --git a/ghc/tests/programs/galois_raytrace/RayTrace.hs b/ghc/tests/programs/galois_raytrace/RayTrace.hs new file mode 100644 index 0000000..cb15388 --- /dev/null +++ b/ghc/tests/programs/galois_raytrace/RayTrace.hs @@ -0,0 +1,9 @@ +-- 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 RayTrace(module Illumination, module Surface) where + +import Illumination +import Surface diff --git a/ghc/tests/programs/galois_raytrace/Surface.hs b/ghc/tests/programs/galois_raytrace/Surface.hs new file mode 100644 index 0000000..832f0fc --- /dev/null +++ b/ghc/tests/programs/galois_raytrace/Surface.hs @@ -0,0 +1,115 @@ +-- 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 Surface + ( SurfaceFn (..) + , Properties + , sfun, sconst + , prop + , matte, shiny + , chgColor + , surface + ) where + +import Geometry +import CSG +import Misc + +-- the surface gets passed face then u then v. +data SurfaceFn c v = SFun (Int -> Double -> Double -> Properties c v) + | SConst (Properties c v) + +sfun :: (Int -> Double -> Double -> Properties c v) -> SurfaceFn c v +sfun = SFun +sconst :: Properties c v -> SurfaceFn c v +sconst = SConst + +type Properties c v = (c, v, v, v) + +prop c d s p = (c, d, s, p) + +matte = (white, 1.0, 0.0, 1.0) +shiny = (white, 0.0, 1.0, 1.0) + +chgColor :: c -> Properties d v -> Properties c v +chgColor c (_, d, s, p) = (c, d, s, p) + +instance (Show c, Show v) => Show (SurfaceFn c v) where + show (SFun _) = "Surface function" + -- show (SConst p) = "Surface constant: " ++ show p + show (SConst p) = "Surface constant" + +evalSurface :: SurfaceFn Color Double -> Int -> Double -> Double -> Properties Color Double +evalSurface (SConst p) = \_ _ _ -> p +evalSurface (SFun f) = f + +-- calculate surface properties, given the type of +-- surface, and intersection point in object coordinates + +-- surface :: Surface SurfaceFn -> (Int, Point) -> (Vector, Properties) + +surface (Planar _ v0 v1) (n, p0, fn) + = (norm, evalSurface fn n' u v) + where norm = normalize $ cross v0 v1 + (n', u, v) = planarUV n p0 + +surface (Spherical _ v0 v1) (_, p0, fn) + = (norm, evalSurface fn 0 u v) + where x = xCoord p0 + y = yCoord p0 + z = zCoord p0 + k = sqrt (1 - sq y) + theta = adjustRadian (atan2 (x / k) (z / k)) + -- correct so that the image grows left-to-right + -- instead of right-to-left + u = 1.0 - clampf (theta / (2 * pi)) + v = clampf ((y + 1) / 2) + norm = normalize $ cross v0 v1 + +-- ZZ ignore the (incorrect) surface model, and estimate the normal +-- from the intersection in object space +surface (Cylindrical _ v0 v1) (_, p0, fn) + = (norm, evalSurface fn 0 u v) + where x = xCoord p0 + y = yCoord p0 + z = zCoord p0 + u = clampf $ adjustRadian (atan2 x z) / (2 * pi) + v = y + norm = normalize $ cross v0 v1 + +-- ZZ ignore the (incorrect) surface model, and estimate the normal +-- from the intersection in object space +surface (Conic _ v0 v1) (_, p0, fn) + = (norm, evalSurface fn 0 u v) + where x = xCoord p0 + y = yCoord p0 + z = zCoord p0 + u = clampf $ adjustRadian (atan2 (x / y) (z / y)) / (2 * pi) + v = y + norm = normalize $ cross v0 v1 + +planarUV face p0 + = case face of + PlaneFace -> (0, x, z) + + CubeFront -> (0, x, y) + CubeBack -> (1, x, y) + CubeLeft -> (2, z, y) + CubeRight -> (3, z, y) + CubeTop -> (4, x, z) + CubeBottom -> (5, x, z) + + CylinderTop -> (1, (x + 1) / 2, (z + 1) / 2) + CylinderBottom -> (2, (x + 1) / 2, (z + 1) / 2) + + ConeBase -> (1, (x + 1) / 2, (z + 1) / 2) + where x = xCoord p0 + y = yCoord p0 + z = zCoord p0 + +-- misc + +adjustRadian :: Radian -> Radian +adjustRadian r = if r > 0 then r else r + 2 * pi diff --git a/ghc/tests/programs/galois_raytrace/galois.gml b/ghc/tests/programs/galois_raytrace/galois.gml new file mode 100644 index 0000000..5029d57 --- /dev/null +++ b/ghc/tests/programs/galois_raytrace/galois.gml @@ -0,0 +1,147 @@ + +[ [97 95 73 50 89 97 99 99 99 99 99 99 99 99 99 98 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99 98 99 99 99 99 99 98 99 99 98 97 99 99 99 99 99 99 99 99 99 99 97 97 96 96 96 96 96 96 99 99 99] + [88 96 66 53 52 86 99 99 99 99 99 99 99 99 99 99 99 99 99 98 99 99 99 99 99 99 99 99 99 99 98 96 98 99 99 99 99 99 99 99 99 97 98 99 99 99 99 99 99 99 99 99 96 96 96 98 97 96 96 96 97 97 96] + [89 92 79 50 54 45 91 98 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99 98 98 99 98 96 98 98 98 98 97 98 99 99 99 99 99 99 99 99 99 99 99 99 99 97 96 96 97 99 99 96 96 98 98 97 97] + [88 91 96 81 40 35 39 91 95 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99 98 97 97 98 99 98 99 99 96 95 95 95 96 97 99 99 98 97 99 99 97 98 99 99 98 96 96 96 94 96 98 99 96 96 96 97 95 96] + [83 92 91 48 54 33 62 64 98 99 99 99 99 99 99 99 97 98 99 99 99 99 99 99 99 98 98 99 99 99 99 99 99 98 97 97 98 99 97 99 99 99 99 99 99 99 99 99 99 99 96 97 98 96 96 97 99 98 96 95 96 96 96] + [91 93 64 78 94 75 57 50 81 97 99 99 99 99 99 98 94 96 99 99 99 99 99 99 99 97 98 99 99 99 98 98 99 98 99 98 99 99 98 99 99 99 99 99 99 99 99 99 99 99 97 99 98 96 91 96 97 98 98 96 96 96 99] + [95 63 85 94 84 95 72 61 44 84 96 98 99 99 99 99 98 98 98 99 99 99 99 99 99 97 96 98 99 99 97 96 98 99 99 97 98 99 98 97 99 99 99 99 99 99 99 99 99 99 97 99 99 96 93 98 96 97 96 96 96 96 95] + [63 80 88 96 96 88 90 52 64 52 95 98 99 99 99 99 99 99 97 98 99 99 99 97 97 99 98 97 97 97 99 96 98 99 99 97 98 99 99 98 98 98 99 99 99 99 99 99 99 99 98 98 99 96 96 96 94 97 98 99 96 92 95] + [92 84 90 92 91 88 89 75 50 58 64 96 99 99 99 99 99 99 99 99 99 99 99 98 99 99 98 99 99 98 99 98 99 99 99 99 99 99 99 99 98 98 99 99 99 96 99 98 99 99 99 97 96 97 96 96 92 96 99 98 95 94 95] + [91 80 85 85 92 96 93 87 81 49 66 88 99 99 99 99 99 99 99 99 99 99 99 98 99 99 96 98 99 99 99 99 99 98 99 99 99 98 99 99 99 99 99 99 99 98 99 98 99 99 98 97 98 96 96 96 93 96 99 98 96 97 97] + [70 90 96 96 95 95 97 93 60 73 64 67 93 97 99 99 99 99 99 99 99 99 99 98 97 97 98 99 99 99 99 99 98 94 97 98 99 98 99 99 99 99 99 99 99 99 98 98 98 97 98 99 99 96 96 96 96 96 99 97 96 96 95] + [93 93 97 97 94 88 85 89 90 57 72 43 82 97 99 99 99 99 99 99 99 99 99 99 98 96 97 99 99 99 99 99 99 96 96 96 98 99 99 98 99 97 98 99 99 98 95 81 88 84 98 98 95 96 96 95 96 96 98 95 94 94 92] + [87 96 91 94 96 97 98 94 75 66 76 60 67 83 99 99 99 99 99 99 99 99 99 99 99 98 98 99 99 99 99 99 99 97 97 97 95 96 97 95 96 76 70 66 73 83 92 60 88 58 88 95 95 95 96 94 95 96 98 96 97 97 97] + [90 96 86 84 89 85 93 92 96 96 94 84 56 85 98 98 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99 98 99 97 56 40 57 71 69 66 78 73 84 55 34 39 39 41 44 46 31 61 90 98 97 92 94 98 99 97 98 98 98] + [93 91 94 89 66 81 86 94 89 87 97 82 84 65 84 82 87 89 97 96 97 99 99 99 99 99 99 99 99 99 99 97 96 40 4 15 9 9 6 2 7 14 23 9 8 8 10 3 3 8 13 12 17 42 90 93 86 93 96 97 96 96 96] + [85 82 82 76 91 90 86 83 84 86 54 37 26 49 31 20 13 43 40 55 80 95 98 98 99 99 99 99 99 98 99 97 66 10 3 4 12 19 4 4 7 18 7 4 11 11 6 11 24 11 12 8 18 9 30 85 93 90 98 99 96 93 96] + [77 68 78 66 72 66 79 92 73 57 57 73 69 39 73 68 70 57 22 15 17 21 50 83 93 97 98 99 99 97 94 66 12 0 0 0 3 4 6 21 7 3 5 2 3 14 17 7 4 6 28 39 39 24 20 53 95 80 98 99 98 96 97] + [69 75 86 82 89 86 64 51 66 93 93 80 90 92 89 87 92 94 68 34 24 13 17 6 20 55 91 98 98 95 66 10 18 4 3 3 1 3 12 10 9 11 17 3 15 14 17 23 15 26 25 25 21 43 21 10 65 62 97 98 99 98 96] + [95 94 94 68 45 48 41 69 84 72 80 94 78 78 94 96 88 93 95 94 83 67 35 13 28 38 15 40 87 80 17 7 4 7 6 9 37 20 19 19 22 9 5 7 11 13 26 14 29 27 41 35 44 19 20 16 43 70 96 96 97 99 99] + [93 96 88 54 57 57 75 74 70 79 94 96 94 97 97 88 88 96 83 97 97 72 65 75 46 21 17 9 28 29 19 22 4 9 8 5 24 34 17 19 7 8 25 16 9 5 8 12 15 32 54 43 30 14 18 11 45 62 96 96 96 97 98] + [97 91 56 59 44 70 63 78 88 82 73 82 96 97 78 94 94 96 99 99 98 91 54 21 4 4 3 11 14 35 14 17 7 26 36 21 11 35 42 24 3 9 17 13 9 20 9 21 20 45 28 35 42 19 38 14 36 67 98 98 97 97 96] + [80 42 56 39 41 71 74 62 54 57 62 68 96 87 92 95 93 97 98 98 67 21 7 4 5 19 22 25 10 8 24 7 6 26 42 43 38 42 32 13 36 60 81 88 67 62 37 63 28 15 26 44 32 30 25 38 62 70 73 78 87 95 97] + [33 60 39 44 80 73 40 59 58 55 70 71 90 91 86 97 98 98 92 43 8 9 2 6 42 65 50 45 27 27 24 9 13 37 66 66 88 80 60 72 92 90 96 98 94 84 43 43 18 31 34 35 19 53 28 46 31 23 49 56 40 78 96] + [74 32 63 76 67 52 85 93 81 74 85 83 65 80 88 96 98 68 30 4 2 2 2 24 82 56 40 25 44 44 30 52 70 83 87 91 90 73 69 51 86 97 99 99 97 92 44 39 28 26 26 22 49 23 36 67 37 17 23 27 39 54 91] + [44 62 77 88 81 94 95 94 55 78 57 42 77 79 90 92 39 7 3 9 1 4 19 68 57 39 49 54 62 53 74 92 88 97 99 99 90 64 82 53 84 99 99 99 97 99 87 76 52 39 48 37 35 26 51 32 8 6 19 6 42 78 84] + [61 82 82 81 96 90 94 66 64 77 65 81 96 97 81 20 2 5 9 10 21 23 61 67 55 48 53 61 48 87 97 96 98 98 98 96 69 50 85 60 65 98 99 99 98 97 94 62 45 50 57 54 21 11 16 11 6 11 21 6 26 71 75] + [93 77 71 97 90 93 51 85 65 77 94 96 94 77 15 1 0 0 8 9 8 53 73 61 64 59 59 53 87 98 98 98 96 94 98 75 37 34 84 51 37 97 98 99 99 97 57 5 5 4 5 4 7 3 18 4 6 15 9 4 8 64 76] + [96 82 93 79 95 82 81 79 85 96 91 74 44 11 12 9 2 0 5 5 24 69 55 63 84 82 75 78 92 98 99 97 95 91 89 33 38 41 70 55 30 94 98 99 98 90 11 8 4 6 3 12 41 2 3 1 6 6 6 6 16 30 58] + [89 66 92 84 96 98 97 92 96 75 45 29 9 38 6 10 3 10 1 8 69 67 64 70 85 87 93 94 97 99 99 99 94 94 70 44 23 60 78 55 28 98 99 99 99 78 6 9 16 15 5 2 8 4 7 1 18 13 18 16 4 23 66] + [48 79 85 82 90 97 96 95 64 20 25 26 27 12 5 8 6 7 6 42 74 64 49 80 94 96 98 99 99 99 99 98 94 96 71 50 7 55 92 31 49 98 99 99 98 62 7 10 13 8 12 11 9 3 5 4 16 9 11 9 13 44 68] + [93 72 87 92 96 98 95 62 38 9 23 64 5 6 12 32 7 9 15 70 69 54 75 97 96 97 99 99 99 99 99 98 98 96 71 79 62 66 69 20 41 98 99 99 98 63 9 29 38 33 28 17 17 40 10 9 18 10 22 10 8 39 74] + [93 69 84 88 95 93 72 41 17 31 69 54 35 6 6 26 18 36 39 68 77 82 95 98 98 97 99 98 99 99 99 99 99 97 91 89 92 63 31 11 44 98 99 99 98 77 19 8 13 22 30 28 35 14 18 21 15 7 37 46 23 53 91] + [77 85 92 96 88 65 24 23 58 63 44 35 24 17 9 24 18 21 43 54 87 95 97 98 98 97 99 97 96 98 99 99 99 99 97 89 64 47 42 16 68 99 99 99 99 94 31 17 37 25 20 51 13 7 8 7 6 34 40 39 65 63 94] + [58 83 97 92 90 42 34 66 76 38 42 35 22 12 10 51 8 33 54 80 98 96 94 90 56 88 94 95 70 68 98 98 98 97 96 94 89 66 30 48 97 99 99 99 98 98 61 13 24 10 20 19 18 6 15 10 11 25 43 27 63 53 92] + [87 71 66 73 79 65 69 44 74 47 44 38 18 15 24 29 9 38 67 93 98 96 79 53 46 96 95 88 23 61 87 95 95 95 87 79 56 35 46 89 99 99 99 99 99 98 67 23 13 10 21 49 31 21 26 16 17 36 46 40 46 67 96] + [96 80 47 69 60 77 59 22 29 74 61 50 56 41 34 27 12 48 74 96 99 97 56 40 23 95 96 63 39 77 85 82 77 75 69 68 71 77 90 97 99 99 99 99 99 98 66 29 44 17 7 23 23 18 24 27 16 33 67 18 30 87 96] + [97 96 92 70 45 52 21 22 30 18 38 74 82 25 24 21 12 35 74 96 99 93 31 57 33 95 94 35 17 53 50 48 77 89 93 96 96 98 95 97 99 99 99 99 99 99 72 7 16 28 22 31 14 10 20 23 16 13 39 25 54 95 92] + [90 64 52 70 92 83 73 49 25 8 36 40 44 25 39 28 42 26 72 96 98 96 28 41 43 95 88 18 8 75 92 89 91 95 98 93 75 61 94 97 99 99 99 99 99 98 82 13 12 20 34 25 9 23 73 55 45 19 21 15 93 95 96] + [22 38 82 96 93 95 93 81 89 51 45 46 26 36 28 24 36 15 31 94 98 95 55 44 30 80 93 34 19 87 88 89 83 94 94 79 30 16 33 90 97 99 99 99 98 98 74 26 9 32 38 21 14 54 74 41 46 46 18 29 91 92 96] + [79 92 98 98 98 95 86 82 85 91 94 81 47 28 13 16 47 7 6 69 93 95 92 70 40 75 95 83 44 42 43 83 94 96 81 61 26 26 7 32 85 98 99 99 98 95 62 38 8 13 20 30 23 62 42 31 3 11 11 59 94 97 97] + [96 96 97 98 98 85 82 94 93 88 89 96 94 92 59 40 19 12 3 10 37 84 90 94 79 60 93 67 75 65 68 94 97 96 66 40 9 11 48 4 35 93 98 99 97 62 50 36 4 3 15 53 33 63 28 6 2 15 56 72 97 97 95] + [98 94 97 95 86 94 91 89 90 95 95 96 95 93 98 88 75 58 34 29 7 21 54 83 84 89 75 79 85 94 94 95 98 82 43 25 6 21 60 24 15 89 97 99 70 66 47 48 2 0 3 22 21 7 4 1 6 55 83 96 98 95 93] + [93 96 96 91 60 65 73 87 93 96 95 91 91 88 94 97 97 97 90 76 74 25 2 19 37 59 72 71 83 91 87 90 92 95 67 47 69 37 51 45 3 76 98 98 46 83 71 21 3 4 2 19 13 2 0 7 60 89 97 99 98 92 90] + [90 88 92 95 97 95 95 93 87 88 82 86 95 93 92 91 97 96 49 73 58 58 24 6 10 58 77 49 56 84 82 89 87 94 87 46 71 37 47 67 9 54 98 98 56 50 58 13 14 18 0 10 20 4 2 31 96 96 99 99 99 96 95] + [78 55 84 95 97 89 73 71 89 92 91 89 90 90 83 82 93 93 68 54 41 27 24 49 85 96 97 89 72 70 48 70 76 91 95 92 43 25 40 87 27 69 97 96 75 33 13 12 10 5 2 14 6 3 15 86 99 97 99 99 97 96 97] + [43 61 66 70 93 79 86 94 89 84 80 84 90 96 90 92 94 92 81 37 34 70 94 96 99 98 99 98 96 95 85 77 57 62 63 59 55 50 52 77 33 63 91 71 18 6 4 5 4 4 4 12 4 15 77 98 99 99 99 98 95 95 96] + [77 82 75 56 62 67 86 85 84 89 92 90 80 77 52 61 56 59 70 89 98 98 99 99 99 99 99 99 99 98 98 98 92 92 91 69 61 47 28 22 4 9 14 7 4 3 7 11 4 9 4 4 22 79 97 96 97 99 98 96 93 95 96] + [97 96 93 97 94 86 69 64 73 59 71 71 71 64 87 94 95 96 99 99 99 99 99 99 99 97 99 99 99 99 99 99 98 96 96 96 96 95 96 93 77 35 11 4 10 2 7 6 2 1 18 59 97 97 99 97 97 99 99 97 95 95 95] + [96 97 98 94 97 95 89 75 65 85 89 93 97 98 99 98 98 99 99 99 99 99 99 97 97 99 98 99 99 97 96 96 95 94 95 97 96 96 96 97 98 93 75 45 19 4 2 4 18 49 90 98 99 99 99 95 96 98 96 96 96 95 96] + [96 98 99 97 96 95 78 59 78 97 98 98 99 99 99 99 99 99 99 99 99 99 99 98 98 99 97 97 98 96 92 91 92 95 96 98 98 95 96 97 99 98 96 97 93 82 80 87 98 97 98 99 99 98 97 96 96 98 96 96 96 97 96] + [95 84 92 97 95 94 50 80 95 98 99 99 99 99 99 97 96 99 98 97 98 99 99 97 97 99 97 97 96 96 90 82 89 92 97 94 94 94 93 96 99 99 98 99 97 99 98 99 99 97 97 97 98 98 96 97 96 96 96 96 96 98 96] + [97 90 92 98 92 52 39 93 98 99 99 99 99 99 98 95 95 98 98 96 97 99 99 97 97 99 99 98 98 95 93 89 87 92 94 88 90 90 88 94 96 97 98 98 97 99 99 99 99 97 97 96 95 96 95 96 96 97 98 98 99 98 96] + [96 94 90 95 69 43 75 97 99 99 99 99 99 99 97 96 94 94 95 96 99 99 97 94 95 98 99 97 95 95 98 95 87 93 89 89 87 88 93 94 90 94 96 96 98 98 99 98 99 98 96 97 97 95 93 95 96 98 99 99 99 98 99] + [97 81 85 68 51 53 96 97 99 99 99 99 99 99 99 97 94 95 95 96 96 98 98 97 95 94 95 95 84 90 96 95 93 95 94 88 79 88 92 97 95 95 95 96 98 99 97 97 99 99 99 98 95 94 96 98 97 99 98 98 98 99 99] + [81 86 70 56 71 72 97 99 99 99 99 99 99 99 99 99 97 98 97 96 96 96 98 98 95 94 96 96 92 94 92 94 95 94 93 91 91 93 95 97 92 87 93 95 98 99 95 95 99 98 98 98 94 88 95 95 96 98 96 97 97 96 98] + [67 62 30 43 90 73 95 98 98 96 98 98 97 98 95 98 95 95 96 95 98 94 95 96 94 95 96 95 95 96 90 94 96 96 96 92 91 92 94 96 97 91 91 95 94 97 95 93 98 96 96 96 97 95 96 93 95 96 96 96 97 95 96] + [53 35 46 37 71 72 94 97 99 99 98 97 98 98 95 94 96 97 96 96 98 87 92 93 96 95 95 92 93 96 95 93 93 89 94 90 87 89 92 90 95 96 97 90 90 96 95 94 97 97 96 96 96 97 97 94 95 96 96 97 96 97 96] + [74 88 56 35 91 90 88 94 97 97 95 95 97 98 95 94 96 97 96 96 98 96 95 95 92 88 91 92 92 94 93 94 90 86 88 95 94 93 93 92 97 96 96 94 95 96 96 96 98 99 98 99 98 98 98 97 95 97 98 98 96 94 93] + [92 93 63 65 92 97 93 95 99 96 89 98 99 98 96 98 96 93 93 95 97 94 91 94 97 94 95 96 93 88 85 90 92 86 90 97 96 96 96 93 94 97 95 96 94 96 99 96 96 96 96 98 98 96 95 95 94 97 96 93 92 95 93] + [82 94 95 81 92 95 92 97 99 98 94 93 92 97 94 97 97 98 94 90 97 95 92 94 94 90 91 96 92 88 94 94 89 83 90 97 96 96 95 96 97 95 94 97 96 96 97 94 93 93 96 98 94 96 96 92 89 90 94 94 94 92 88] + [78 85 76 94 97 95 96 97 99 99 98 96 97 97 96 97 97 95 95 96 98 96 96 96 88 86 93 96 94 93 89 88 88 90 90 94 94 97 97 96 96 97 98 98 98 96 92 87 96 96 96 96 96 94 92 93 88 88 93 95 94 90 87] + [83 88 91 94 97 97 99 98 98 98 98 99 99 98 99 98 99 97 98 97 98 96 96 95 96 94 96 95 95 91 85 90 90 93 94 94 92 94 95 96 98 97 98 98 97 97 97 96 96 96 95 96 94 95 95 94 93 93 94 95 93 88 91] + [95 90 94 94 98 96 98 99 96 98 97 97 98 98 99 99 96 95 97 97 99 95 96 98 93 96 96 96 93 95 89 93 93 95 96 96 97 97 97 97 99 98 97 96 98 99 99 99 98 93 93 96 96 96 95 94 91 92 90 93 94 96 96] +] /galois + +{ /v /u /face % bind parameters + { % toIntCoord : float -> int + 63.0 mulf floor /i % i = floor(3.0*i) + i 63 eqi { 62 } { i } if % return max(2, i) + } /toIntCoord + galois u toIntCoord apply get % val = texture[u][v] + v toIntCoord apply get + real 100.0 divf /gal + gal gal gal point % b/w galois + 1.0 % kd = 1.0 + 0.0 % ks = 0.0 + 1.0 % n = 1.0 +} /galoisface + + +galoisface cube +-0.5 -0.5 -0.5 translate % center +2.5 uscale % make it bigger +-25.0 rotatex -25.0 rotatey % rotate +0.0 -1.0 7.0 translate % move to final position + +%galoisface cylinder +%-0.5 -0.5 -0.5 translate % center +%1.5 uscale % make it bigger +%0.0 rotatex 90.0 rotatey % rotate +%0.0 0.0 5.0 translate % move to final position + +%galoisface sphere +%-0.5 -0.5 -0.5 translate % center +%1.5 uscale % make it bigger +%-25.0 rotatex 25.0 rotatey % rotate +%-3.0 0.0 5.0 translate % move to final position + +%union union % join the 3 together + +{ /v /u /face + v 5.0 divf /v + u 5.0 divf /u + v floor 2 modi 0 eqi + { 1.0 } + { 0.8 } + if /r + u floor 2 modi 0 eqi + { 1.0 } + { 0.8 } + if /g + v frac /v + u frac /u + v 0.0 lessf { v 1.0 addf } { v } if /v + u 0.0 lessf { u 1.0 addf } { u } if /u + { % toIntCoord : float -> int + 63.0 mulf floor /i % i = floor(3.0*i) + i 63 eqi { 62 } { i } if % return max(2, i) + } /toIntCoord + galois u toIntCoord apply get % val = texture[u][v] + v toIntCoord apply get + real 100.0 divf /gal + r gal mulf g gal mulf gal point % b/w galois + 0.0 % kd = 1.0 + 1.0 % ks = 0.0 + 1.0 % n = 1.0 +} plane /p + +p 0.0 -3.0 0.0 translate % plane at Y = -3 + +union + +/scene + % directional light +1.0 -1.0 0.0 point % direction +1.0 1.0 1.0 point light /l % directional light + +1.0 0.5 0.0 point % ambient light +[ l ] % lights +scene % scene to render +3 % tracing depth +90.0 % field of view +300 200 +"galois.ppm" % output file +render + diff --git a/ghc/tests/programs/galois_raytrace/galois_raytrace.stdout b/ghc/tests/programs/galois_raytrace/galois_raytrace.stdout new file mode 100644 index 0000000000000000000000000000000000000000..6cbbcb5457719bd39b4b8af033c0357da214e0ef GIT binary patch literal 180024 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