[Shootout-list] Ray tracer

Jon Harrop jon@ffconsultancy.com
Thu, 28 Apr 2005 12:32:05 +0100


--Boundary-00=_2mMcCfbj7Z9UnU8
Content-Type: text/plain;
  charset="iso-8859-1"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

On Thursday 28 April 2005 11:38, Sebastien Loisel wrote:
> > I've boiled my program down to 94 LOC but managed to keep the
> > hierarchical spherical bounding volumes, so it can still render a
> > sphere-flake containing 600,000 spheres in under 8.5 seconds on my cheapy
> > laptop.
>
> This program is very good. To please everyone perhaps we can try to
> simplify it a smidgen further. If use only translations to create a
> sphere flakes (as opposed to translations+rotations), do you suppose
> that would still be an interesting test? And then, would we need the
> matrix infrastructure?

Thanks for the tip. Now its only 62 LOC:

let delta = sqrt epsilon_float and pi = 4. *. atan 1.
let ( *| ) s r = [| s *. r.(0); s *. r.(1); s *. r.(2)|]
let ( +| ) a b = [| a.(0) +. b.(0); a.(1) +. b.(1); a.(2) +. b.(2)|]
let ( -| ) a b = [| a.(0) -. b.(0); a.(1) -. b.(1); a.(2) -. b.(2)|]
let dot a b = a.(0) *. b.(0) +. a.(1) *. b.(1) +. a.(2) *. b.(2)
let unitise r = (1. /. sqrt (dot r r)) *| r
type sphere = { center: float array; radius: float }
type ray = { orig: float array; dir: float array }
type obj = Sphere of sphere * float | Group of sphere * obj list
let ray_sphere ray sphere =
  let v = sphere.center -| ray.orig in
  let b = dot v ray.dir in
  let disc = b *. b -. dot v v +. sphere.radius *. sphere.radius in
  if disc < 0. then infinity else
    let disc = sqrt disc in
    (fun t2 -> if t2 < 0. then infinity else
       ((fun t1 -> if t1 > 0. then t1 else t2) (b -. disc))) (b +. disc)
let intersect ray scene =
  let rec of_scene ((l, _, _) as first) = function
      Sphere (s, material) ->
 let l' = ray_sphere ray s in
 if l' >= l then first else (* No nearer *)
   let normal = unitise (ray.orig +| l' *| ray.dir -| s.center) in
   l', normal, material (* Replace with nearer intersection *)
    | Group (bound, scenes) ->
 let l' = ray_sphere ray bound in (* Cull if possible *)
 if l' >= l then first else List.fold_left of_scene first scenes in
  of_scene (infinity, [|0.; 0.; 0.|], 0.) scene
let rec ray_trace weight light ray scene = match intersect ray scene with
    lambda, n, color ->
      if lambda = infinity then 0. else
 let o = ray.orig +| lambda *| ray.dir +| delta *| n in
 (match intersect { orig = o; dir = [|0.; 0.; 0.|] -| light } scene with
    l, _, _ when l = infinity -> max 0. (-. dot n light)
  | _ -> 0.) *. color
let () =
  let level = match Sys.argv with [| _; l |] -> int_of_string l | _ -> 6 in
  let light = unitise [|-1.; -3.; 2.|] in
  let scene =
    let rec aux level r (x, y, z) =
      let sphere = { center = [|x;y;z|]; radius = r } in
      let obj = Sphere (sphere, 1.) in
      if level = 1 then obj else begin
 let aux l (x', y', z') = aux (level-1) (0.5 *. r)
   (x -. x', y +. y', z +. z') :: l in
 let objs = let r' = r /. sqrt 3. in List.fold_left aux [obj]
   [-.r', r', -.r'; r', r', -.r'; -.r', r', r'; r', r', r'] in
 Group ({sphere with radius = 2. *. r}, objs)
      end in
    aux level 1. (0., 0., 0.) in
  let w, h = 768, 768 in
  Printf.printf "P2\n%d %d\n256\n" w h;
  for y = h - 1 downto 0 do
    for x = 0 to w - 1 do
      let ray =
 let x, y = float x -. 0.5 *. float w, float y -. float h in
 {orig = [|0.;2.;-5.|]; dir = unitise [|x; y; float (max w h)|] } in
      Printf.printf "%d " (int_of_float (256. *.ray_trace 1. light ray 
scene));
    done;
    Printf.printf "\n";
  done;

Unless I've failed to use OCaml's built-in ray_trace function, I don't think 
it's going to get a lot smaller... ;-)

-- 
Dr Jon D Harrop, Flying Frog Consultancy Ltd.
Objective CAML for Scientists
http://www.ffconsultancy.com/products/ocaml_for_scientists

--Boundary-00=_2mMcCfbj7Z9UnU8
Content-Type: text/x-csrc;
  charset="iso-8859-1";
  name="ray8.ml"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
	filename="ray8.ml"

let delta = sqrt epsilon_float and pi = 4. *. atan 1.
let ( *| ) s r = [| s *. r.(0); s *. r.(1); s *. r.(2)|]
let ( +| ) a b = [| a.(0) +. b.(0); a.(1) +. b.(1); a.(2) +. b.(2)|]
let ( -| ) a b = [| a.(0) -. b.(0); a.(1) -. b.(1); a.(2) -. b.(2)|]
let dot a b = a.(0) *. b.(0) +. a.(1) *. b.(1) +. a.(2) *. b.(2)
let unitise r = (1. /. sqrt (dot r r)) *| r
type sphere = { center: float array; radius: float }
type ray = { orig: float array; dir: float array }
type obj = Sphere of sphere * float | Group of sphere * obj list
let ray_sphere ray sphere =
  let v = sphere.center -| ray.orig in
  let b = dot v ray.dir in
  let disc = b *. b -. dot v v +. sphere.radius *. sphere.radius in
  if disc < 0. then infinity else
    let disc = sqrt disc in
    (fun t2 -> if t2 < 0. then infinity else
       ((fun t1 -> if t1 > 0. then t1 else t2) (b -. disc))) (b +. disc)
let intersect ray scene =
  let rec of_scene ((l, _, _) as first) = function
      Sphere (s, material) ->
	let l' = ray_sphere ray s in
	if l' >= l then first else (* No nearer *)
	  let normal = unitise (ray.orig +| l' *| ray.dir -| s.center) in
	  l', normal, material (* Replace with nearer intersection *)
    | Group (bound, scenes) ->
	let l' = ray_sphere ray bound in (* Cull if possible *)
	if l' >= l then first else List.fold_left of_scene first scenes in
  of_scene (infinity, [|0.; 0.; 0.|], 0.) scene
let rec ray_trace weight light ray scene = match intersect ray scene with
    lambda, n, color ->
      if lambda = infinity then 0. else
	let o = ray.orig +| lambda *| ray.dir +| delta *| n in
	(match intersect { orig = o; dir = [|0.; 0.; 0.|] -| light } scene with
	   l, _, _ when l = infinity -> max 0. (-. dot n light)
	 | _ -> 0.) *. color
let () =
  let level = match Sys.argv with [| _; l |] -> int_of_string l | _ -> 6 in
  let light = unitise [|-1.; -3.; 2.|] in
  let scene =
    let rec aux level r (x, y, z) =
      let sphere = { center = [|x;y;z|]; radius = r } in
      let obj = Sphere (sphere, 1.) in
      if level = 1 then obj else begin
	let aux l (x', y', z') = aux (level-1) (0.5 *. r)
	  (x -. x', y +. y', z +. z') :: l in
	let objs = let r' = r /. sqrt 3. in List.fold_left aux [obj]
	  [-.r', r', -.r'; r', r', -.r'; -.r', r', r'; r', r', r'] in
	Group ({sphere with radius = 2. *. r}, objs)
      end in
    aux level 1. (0., 0., 0.) in
  let w, h = 768, 768 in
  Printf.printf "P2\n%d %d\n256\n" w h;
  for y = h - 1 downto 0 do
    for x = 0 to w - 1 do
      let ray =
	let x, y = float x -. 0.5 *. float w, float y -. float h in
	{orig = [|0.;2.;-5.|]; dir = unitise [|x; y; float (max w h)|] } in
      Printf.printf "%d " (int_of_float (256. *.ray_trace 1. light ray scene));
    done;
    Printf.printf "\n";
  done;

--Boundary-00=_2mMcCfbj7Z9UnU8--