From 75a7e5ff20dbc3f3b583d31b4a1ff0b6a1459d49 Mon Sep 17 00:00:00 2001
From: andy <unknown>
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 +
 .../programs/galois_raytrace/Construct.hs     | 265 ++++++++++++
 ghc/tests/programs/galois_raytrace/Data.hs    | 407 ++++++++++++++++++
 ghc/tests/programs/galois_raytrace/Eval.hs    | 357 +++++++++++++++
 .../programs/galois_raytrace/Geometry.hs      | 314 ++++++++++++++
 .../programs/galois_raytrace/Illumination.hs  | 212 +++++++++
 .../programs/galois_raytrace/Intersections.hs | 404 +++++++++++++++++
 .../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 +++
 .../programs/galois_raytrace/Primitives.hs    |  24 ++
 .../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 000000000000..ba37a17b25e7
--- /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 000000000000..90dbc60f9e51
--- /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 000000000000..1f716ea5e0cb
--- /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> :: Closure"
+showStkEle (VArray arr)   
+     = "<array (" ++  show (succ (snd (bounds arr))) ++ " elements)> :: Array"
+showStkEle (VPoint x y z) = "(" ++ show x 
+                         ++ "," ++ show y
+                         ++ "," ++ show z
+                         ++ ") :: Point"
+showStkEle (VObject {})   = "<Object> :: Object"
+showStkEle (VLight {})    = "<Light> :: 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,  "<anything>")
+  , ( TyAbsObj, "<abs>")
+  ]
+
+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 000000000000..9d00cd9b67bd
--- /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 000000000000..673c7d4812af
--- /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 000000000000..9242cbf7a416
--- /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 000000000000..c7fe003eb338
--- /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 000000000000..a4d313f66e7b
--- /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 000000000000..4ef9fe3e95a9
--- /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 000000000000..f181efd9171f
--- /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 000000000000..1368b31f3f6c
--- /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 000000000000..10b9f9b2f8e6
--- /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 "<stdin>" 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 000000000000..11d20f0df2cd
--- /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 000000000000..2f2165405506
--- /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 000000000000..cb15388e2c70
--- /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 000000000000..832f0fcae270
--- /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 000000000000..5029d57620ef
--- /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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
Ts)%8yBxVKdl*Byc@Du+5pBqoI

literal 0
HcmV?d00001

-- 
GitLab