(* Tiny Objective Caml Raytracer        *)
(* (C) 2008 by Tomasz bla Fortuna       *)
(* License: GPLv3+                      *)

open Printf

let fabs a = if a < 0.0 then -. a else a

(** Our main hero **)
module Vect = 
  struct
    type t = {
      x : float;
      y : float; 
      z : float;
    }

    let zero = {
      x = 0.0;
      y = 0.0;
      z = 0.0;
    }

    let print v =
      printf "%5.7f %5.7f %5.7f" v.x v.y v.z

    let create x y z = {
      x = x;
      y = y;
      z = z;
    }

    let get v = 
      (v.x, v.y, v.z)

    let add a b = {
      x = a.x +. b.x;
      y = a.y +. b.y;
      z = a.z +. b.z;
    }

    let sub a b = {
      x = a.x -. b.x;
      y = a.y -. b.y;
      z = a.z -. b.z;
    }

    let dot a b =
      (a.x *. b.x) +. (a.y *. b.y) +. (a.z *. b.z)

    let cross a b = {
      x = a.y *. b.z -. a.z *. b.y;
      y = a.z *. b.x -. a.x *. b.z;
      z = a.x *. b.y -. a.y *. b.x;
    }

    let mul f v = {
      x = v.x *. f;
      y = v.y *. f;
      z = v.z *. f;
    }

    let length2 a =
      a.x *. a.x +. a.y *. a.y +. a.z *. a.z

    let length a = 
      sqrt (length2 a)
      
    let normalize a = 
      let len = length a in {
          x = a.x /. len;
          y = a.y /. len;
          z = a.z /. len;
        }
  end

(** Module creating/operating on rays **)
module Ray =
  struct
    type t = {
      start : Vect.t; (* Start vector     *)
      dir : Vect.t;   (* Direction vector *)
    }

    let print r =
      printf "Ray: start: (";
      Vect.print r.start;
      printf "), dir: (";
      Vect.print r.dir;
      printf ")\n%!"

    let get_start r =
      r.start

    let get_dir r =
      r.dir

    let point_of_t ~ray ~t =
      Vect.add ray.start (Vect.mul t ray.dir)

    let create ~start ~dir () = {
      start = start;
      dir = dir;
     }
      
    let gen_reflected_ray ~ray ~normal ~collision_point =
      let a = 2.0 *. (Vect.dot normal ray.dir) in
      let dir = Vect.sub ray.dir (Vect.mul a normal) in
        create ~start:collision_point ~dir:(Vect.normalize dir) ()

    let gen_refracted_ray ~ray ~normal ~collision_point ~cur_n ~new_n =
      let c1 = -. Vect.dot normal ray.dir in
      let n = cur_n /. new_n in
      let c2 = sqrt (1.0 -. n*.n *. (1.0 -. c1 *. c1)) in
      let c3 = n *. c1 -. c2 in
      let dir =
        Vect.add
          (Vect.mul n ray.dir)
          (Vect.mul c3 normal)
      in
        create ~start:collision_point ~dir ()
          
    let ray_of_points ~source ~destination () =
      let dir = Vect.sub destination source in
        create ~start:source ~dir:(Vect.normalize dir) ()
      
  end

(** Color **)
module Color = 
  struct
    type t = {
      r : float;
      g : float;
      b : float;
    }

    let crop a =
      if a > 1.0 then 1.0 else if a < 0.0 then 0.0 else a

    let create r g b = {
      r = crop r;
      g = crop g;
      b = crop b;
    }
    let print c = 
      printf "r=%5.2f g=%5.2f b=%5.2f\n%!" c.r c.g c.b

    let red = create 1.0 0.0 0.0
    let green = create 0.0 1.0 0.0
    let blue = create 0.0 0.0 1.0
    let white = create 1.0 1.0 1.0
    let black = create 0.0 0.0 0.0
    let grey = create 0.5 0.5 0.5
    let yellow = create 1.0 1.0 0.0

    let get_r c = c.r
    let get_g c = c.b
    let get_b c = c.g

    let int_r c = int_of_float (c.r *. 255.0)
    let int_g c = int_of_float (c.g *. 255.0)
    let int_b c = int_of_float (c.b *. 255.0)

    let graph_of_color c = 
      Graphics.rgb (int_r c) (int_g c) (int_b c)

    let combine a b = {
      r = a.r *. b.r;
      g = a.g *. b.g;
      b = a.b *. b.b;
    }

    let add lst =
      let add a b = {
        r = a.r +. b.r;
        g = a.g +. b.g;
        b = a.b +. b.b;
      } in
      let summed = List.fold_left add black lst in
        {
          r = crop summed.r;
          g = crop summed.g;
          b = crop summed.b;
        }

    let mul a c = {
      r = a *. c.r;
      g = a *. c.g;
      b = a *. c.b;
    }

    let pow c a = {
      r = c.r ** a;
      g = c.g ** a;
      b = c.b ** a;      
    }

    let intensity c = 
      (c.r +. c.g +. c.b) /. 3.0

    let equal a b =
      if a.r = b.r && a.g = b.g && a.b = b.b then
        true
      else
        false

    let is_zero c = 
      if c.r = 0.0 && c.g = 0.0 && c.b = 0.0 then
        true
      else
        false

    let average lst =
      let length = float_of_int (List.length lst) in
      let add a b = {
        r = a.r +. b.r;
        g = a.g +. b.g;
        b = a.b +. b.b;
      } in
      let summed = List.fold_left add black lst in
        {
          r = summed.r /. length;
          g = summed.g /. length;
          b = summed.b /. length;
        }
  end

(* Texture is a function returning color given a x,y coordinates *)
module Texture =
  struct 
    type t = float -> float -> Color.t
    let create ~pattern ?(sizex=1.0) ?(sizey=1.0) () = 
      match pattern with
        | `PLAIN c ->
            (* Plain color, returns it's parameter *)
            (fun x y -> c)
        | `NOISE ->
            (* Randomly generated color *)
            (fun x y ->
               let r = Random.float 1.0
               and g = Random.float 1.0
               and b = Random.float 1.0 in
                 Color.create r g b)
        | `XGRADIENT (a, b) ->
            (* Gradient along X parameter *)
            (fun x y ->
               let x = mod_float x sizex in
               let a_coeff = (sizex -. x) /. sizex in
               let b_coeff = 1.0 -. a_coeff in 
                 Color.add [Color.mul a_coeff a; Color.mul b_coeff b]
            )
        | `CHECKED (a, c) ->
            (fun x y ->
               let halfx = sizex /. 2.0
               and halfy = sizey /. 2.0 in

               let x = if x<0.0 then x-.halfx else x
               and y = if y<0.0 then y-.halfy else y in

               let x = abs_float (mod_float x sizex)
               and y = abs_float (mod_float y sizey) in
                 if (x < halfx && y < halfy) || (x > halfx && y > halfy) then
                   a
                 else
                   c
            )

    let color c = create ~pattern:(`PLAIN c) ()
    let create_plain r g b = color (Color.create r g b)
    let black = color Color.black
    let white = color Color.white

    let red = color Color.red
    let green = color Color.green
    let blue = color Color.blue
  end

module Material =
  struct
    type t = {
      transparency : float;
      reflective : float;
      n : float;
      diffuse : Texture.t;  (* Color filter for... general color. ;p *)
      mirror : Texture.t;   (* Color filter for reflected rays *)
      specular : Texture.t; (* Color filter for light rays *)
      shininess : float
    }

    let create
        ?(shininess=12.0)
        ?(diffuse=Texture.black)
        ?(specular=Texture.create_plain 0.7 0.7 0.7)
        ?(mirror=Texture.create_plain 0.2 0.2 0.2)
        ?(n=1.0)
        ?(transparency=0.0)
        ?(reflective=0.0) () =
      {
        diffuse = diffuse;
        specular = specular;
        mirror = mirror;

        transparency = transparency;
        reflective = reflective;
        n = n;
        shininess = shininess;
      }

    let get_property ~material ~property =
      match property with 
        | `N -> material.n
        | `TRANSPARENCY -> material.transparency
        | `REFLECTIVE -> material.reflective
        | `SHININESS -> material.shininess

    let get_texture ~material ~texture =
      match texture with
        | `DIFFUSE -> material.diffuse
        | `SPECULAR -> material.specular
        | `MIRROR -> material.mirror

    let get_color ~material ~texture ~x ~y =
      (get_texture ~material ~texture) x y 
    
  end

(** Camera type **)
module Camera = 
  struct
    type t = {
      loc : Vect.t; (* Eyepoint location *)
      dir : Vect.t; (* Viewing direction *)
      top : Vect.t; (* Normalized vector pointing to the 'camera top' *)
      fov : float; (* Camera horizontal field of view *)
      ratio : float; (* Aspect ratio *)
    }
    let pi = 4.0 *. atan(1.0)

    let print c =
      printf "Camera:\n";
      printf "\tloc: ("; Vect.print c.loc; printf ")\n";
      printf "\tdir: ("; Vect.print c.dir; printf ")\n";
      printf "\ttop: ("; Vect.print c.top; printf ")\n";
      printf "\tfov: %5.5f (%5.5f deg)\n" c.fov (c.fov /. pi *. 360.0);
      printf "\tratio: %5.2f\n%!" c.ratio


    let fov_of_degree d =
      d /. 360.0 *. 2.0  *. pi

    let get_loc c = c.loc
    let get_dir c = c.dir
    let get_top c = c.top
    let get_fov c = c.fov
    let get_ratio c = c.ratio

    let create
        ?(loc=Vect.create 0. 0. (-. 1.))
        ?(dir=Vect.create 0. 0. 1.)
        ?(top=Vect.create 0. 1. 0.)
        ?(fov=fov_of_degree 45.0)
        ?(ratio=4.0/.3.0)
        ?(auto_top=false)
        () =
      let dir' = Vect.normalize dir in
      let top' = 
        if auto_top then
          (* top.x = dir.x, top.z = dir.z
             top.y calculated from constraint: top dot dir = 0 *)
          let (dx, dy, dz) = Vect.get dir in
          let ty = -. (dz ** 2.0 +. dx ** 2.0) /. dy in
            Vect.create dx ty dz
        else
          top
      in {
          loc = loc;
          dir = dir';
          top = Vect.normalize top';
          fov = fov;
          ratio = ratio;
        }

    (* Generates camera-function which generates rays for specified x,y *)
    let gen_ray_of_xy ~cnt ~xres ~yres ~camera =
      let cam_left = Vect.normalize (Vect.cross camera.top camera.dir) in
      let xwidth = tan (camera.fov /. 2.0) in (* Screen size *)
      let ywidth = xwidth /. camera.ratio in
      let x_dist = xwidth /. float_of_int xres in (* Pixel distance on screen *)
      let y_dist = ywidth /. float_of_int yres in
      let x_vect = Vect.mul x_dist cam_left
      and y_vect = Vect.mul y_dist camera.top
      in
      let ray_of_xy x y =
        let x_times = float_of_int (x - (xres / 2))
        and y_times = float_of_int (y - (yres / 2)) in
        let base = Vect.add (Vect.mul x_times x_vect) (Vect.mul y_times y_vect)
        in
          incr cnt;
          Ray.create ~dir:(Vect.add base camera.dir) ~start:camera.loc ();
      in
        ray_of_xy
  end

module Scene =
  struct
    let pi = 4.0 *. atan(1.0) 
    type plane = { 
      normal : Vect.t;
      distance : float;  (* Distance from (0,0) along normal *)
      p_material : Material.t;
    }

    type sphere = {
      center : Vect.t;
      radius : float;
      s_material : Material.t;      
    }

    type light = {
      light_pos : Vect.t;
      light_color : Color.t;
    }

    type obj = 
      | Plane of plane
      | Sphere of sphere
      | NoObject

    let objects = ref []
    let lights = ref []

    (* Getters *)
    let get_object_material obj =
      match obj with
        | Sphere s -> s.s_material
        | Plane p -> p.p_material
        | NoObject -> failwith "Wrong object specified"

    let get_object_coordinates obj v =
      (* Get object x, y coordinates *)
      match obj with
        | Plane p ->
            let (x, _, z) = Vect.get v in
            (x, z) (* FIXME *)
        | Sphere s -> 
            let w = Vect.sub v s.center in
            let (x,y,z) = Vect.get w
            and r = s.radius in
            let v = acos (y /. r) /. pi in
            let helper = acos (x /. (r *. sin (pi *. v) )) in
            let u = 
              if y > 0.0 then
                helper /. 2. /. pi 
              else
                (pi +. helper) /. 2. /. pi
            in
              (v, u)
        | NoObject -> failwith "Wrong object specified"

    let get_object_color ~obj ~texture ~point =
      let (x, y) = get_object_coordinates obj point in
      let material = get_object_material obj in
      let c = Material.get_color ~material ~texture ~x ~y
      in
        c

    let get_light_position l =
      l.light_pos

    let get_light_color l = 
      l.light_color

    let get_normal obj point =
      match obj with
        | Sphere s ->
            Vect.normalize (Vect.sub point s.center)
        | Plane p ->
            p.normal
        | _ -> failwith "Wrong object specified"

    (* Adders *)
    let add_sphere ~location ~radius ~material =
      let sphere = {
        center = location;
        radius = radius;
        s_material = material;
      } in
        objects := (Sphere sphere) :: (!objects)

    let add_plane ~distance ~normal ~material =
      let plane = {
        normal = normal;
        distance = distance;
        p_material = material;
      } in
        objects := (Plane plane) :: (!objects)

    let add_light ~location ~color =
      let light = {
        light_pos = location;
        light_color = color;
      } in
        lights := light :: (!lights)

    let add_area_light ~lights ~radius ~location ~color =
      for i = 0 to lights do
        let ra = radius *. 2.0 in
        let rb = radius in

        let v =
          Vect.create (rb -. Random.float ra)
            (rb -. Random.float ra)
            (rb -. Random.float ra)
        in
          add_light ~location:(Vect.add location v) ~color
      done

    let lights_fold f init =
      List.fold_left f init !lights

    let rec find_collision ~ray =
      let ray_start = Ray.get_start ray
      and ray_dir = Ray.get_dir ray
      and (@) = Vect.dot in

      let collide collision obj =
        let best_t, best_obj, opacity = collision in
        match obj with
          | Plane p ->
              (* N[A, B, C] - plane normals
               * P[x, y, z] point of collision 
               * Rs - Ray start vector; Rd - Ray direction vector
               * 
               * N . P + D = 0  ;   Rs + t * Rd = P
               * N . (Rs + t*Rd) + D = 0
               * N.Rs + t*(N . Rd) + D = 0
               * 
               *   N . Rs + D
               * - ---------- = t
               *     N . Rd
               * 
               *)
              let t =
                (-. (p.normal @ ray_start) +. p.distance) /.
                  (p.normal @ ray_dir)
              in
                if t > 0.0000001 && t < best_t then (
                  let opacity' = 1.0 -.  
                    (Material.get_property
                       ~material:(get_object_material obj)
                       ~property:`TRANSPARENCY) in
                  (t, obj, opacity' +. opacity)
                ) else (
                  (best_t, best_obj, opacity)
                )
          | Sphere s ->
              (* R - radius
               * C[a,b,c] - sphere center
               * P[x,y,z] - sphere point
               * 
               * |P - C| = R
               * Rs + t*Rd = P
               * | Rs+t*Rd - C | = R
               *)
              let minimal_distance = 0.00001 in
              let v = Vect.sub ray_start s.center in
              let v' = Vect.length2 v 
              and d' = Vect.length2 ray_dir
              and r' = s.radius *. s.radius in
              let denom = 2.0 *. d'
              and a = 2.0 *. (v @ ray_dir) in
              let delta = (a *. a) -. (2.0 *. denom) *. (v' -. r') in
                if delta > 0.0 then (
                  let b = sqrt delta in
                  let first = ((-. a) -. b) /. denom 
                  and second = ((-. a) +. b) /. denom in
                  let choose dist =
                    if dist < best_t then (
                      let opacity' = 1.0 -.  
                        (Material.get_property
                           ~material:(get_object_material obj)
                           ~property:`TRANSPARENCY) in
                      (dist, Sphere s, opacity +. opacity')
                    ) else (
                      (best_t, best_obj, opacity)
                    )
                  in
                    if first > minimal_distance then (
                      if second > minimal_distance then (
                        let closer = if first < second then first else second in
                          choose closer
                      ) else (
                        choose first
                      )
                    ) else (
                      if second > minimal_distance then (
                        choose second
                      ) else (
                        (best_t, best_obj, opacity)
                      )
                    );

                ) else 
                  (best_t, best_obj, opacity)
                
          | _ -> (best_t, best_obj, opacity)
      in
        List.fold_left collide (infinity, NoObject, 0.0) !objects
  end
    
module Graph =
  struct
    let start x y =
      (* Open graphics *)
      Graphics.open_graph "";
      Graphics.resize_window x y

    let put_pixel x y color =
      let c = Color.graph_of_color color in
        Graphics.set_color c;
        Graphics.plot x y

    let stop () = 
      Graphics.close_graph ()  

    let wait () =
      ignore (input_char stdin)
  end

module Render =
  struct
    let n_vacuum = 1.0
    let n_air = 1.0002926
    let n_water = 1.333
    let n_diamond = 2.419
    let n_amber = 1.55
    let n_salt = 1.544
    let n_ice = 1.31
    let n_glass = 1.60

    (* Stats *)
    let main_rays = ref 0
    and reflect_rays = ref 0
    and refract_rays = ref 0
    and shadow_rays = ref 0

    (* Arguments:
       ambient - Ambient color
       refract_stack - Stack of refract indexes used for leaving objects
       depth - Current depth used to stop recursion at some level
       ray - currently traced ray
    *)
    let rec tracer ~background ~ambient ~refract_stack ~depth ~ray = 
      let t, obj, _ = Scene.find_collision ~ray in
        if t < infinity then ( 
          (* We've got a collision *)  
          let collision_point = Ray.point_of_t ~ray ~t in
          let normal = Scene.get_normal obj collision_point in
          
          (* Read color info at collision point *)
            
          let helper = Scene.get_object_color ~obj ~point:collision_point in
          let diffuse = helper ~texture:`DIFFUSE
          and specular = helper ~texture:`SPECULAR
          and mirror = helper ~texture:`MIRROR in
           

          (* Read other material properties *)
          let get_prop prop =
            Material.get_property
              ~material:(Scene.get_object_material obj)
              ~property:prop
          in
          let shininess = get_prop `SHININESS
          and transparency = get_prop `TRANSPARENCY
          and obj_n = get_prop `N in

          (* Create reflect ray as it's angle is 
             needed for shadow calculations *)
          let reflect_ray =
            Ray.gen_reflected_ray
              ~ray
              ~collision_point
              ~normal
          in
            
          (* Function to be 'left folded' over lights list
             it checks shadow rays for diffuse + specular lightening *)
          let light_check (diffuse, spec) light =
            let light_pos = Scene.get_light_position light
            and light_color = Scene.get_light_color light in
            let light_ray =
              Ray.ray_of_points
                ~source:collision_point
                ~destination:light_pos ()
            in
              incr  shadow_rays;
              (* Is anything in a way? *)
              (* FIXME: 
                 In this place we should add additional light filter so the light 
                 would get coloured when passing through transparent object
                 It would have to be done in the Scene collision function...
              *)
              let distance, obj, opacity =
                Scene.find_collision ~ray:light_ray in
              let light_visibility = 
                let crop a = if a < 0.0 then 0.0 else a in
                if distance = infinity then
                  1.0 
                else
                  crop (0.6 -. opacity)
              in
              let light_dir = Ray.get_dir light_ray
              and reflect_dir = Ray.get_dir reflect_ray in
              let diffuse_coeff = 
                light_visibility *. Vect.dot normal light_dir
              and specular_coeff =
                light_visibility *. Vect.dot reflect_dir light_dir in
              let specular_coeff = 
                if specular_coeff > 0.0 then
                  specular_coeff ** shininess
                else 0.0
              in
                (* Take in account computations for previous lights
                   and light colour *)
              let diffuse' =
                Color.add [
                  diffuse;
                  Color.mul diffuse_coeff light_color
                ] in
              let spec' =
                Color.add [
                  spec;
                  Color.mul specular_coeff light_color
                ] in
                (diffuse', spec');
          in
            
          (* fold this function and calculate diffuse and specular light intensity *)
          let diffuse_color, specular_color  =
            Scene.lights_fold light_check (Color.black, Color.black)
              
          and refract_color =
            if transparency = 0.0 || depth = 0 then (
              Color.black
            ) else (
              let refract_stack' = Stack.copy refract_stack in
              let cur_n = Stack.top refract_stack' in
              let dot_product = Vect.dot ray.Ray.dir normal in 
              let leaving = if dot_product > 0.0 then true else false in
                (* Accoring to leaving set choose new 'n' and normal *)
              let normal =
                if leaving then
                  Vect.sub Vect.zero normal
                else
                  normal
              in
              let new_n =
                if leaving = true then (
                  Stack.pop refract_stack'
                ) else (
                  Stack.push obj_n refract_stack';
                  obj_n
                )
              in
              let refract_ray  =
                Ray.gen_refracted_ray
                  ~ray
                  ~collision_point
                  ~normal
                  ~cur_n
                  ~new_n
              in
                incr refract_rays;
                let result =
                  tracer
                    ~ambient
                    ~background
                    ~refract_stack:refract_stack'
                    ~depth:(depth - 1)
                    ~ray:refract_ray
                in
                let color =
                  match result with
                    | `BACKGROUND -> background (* Color.black*)
                    | `COLOR c -> c
                in
                  Color.mul transparency color 
            )
          and reflect_color = 
            if depth = 0 || Color.is_zero mirror then
              Color.black
            else (
              incr reflect_rays;
              let result = 
                tracer
                  ~ambient
                  ~background
                  ~refract_stack:(Stack.copy refract_stack) 
                  ~depth:(depth - 1)
                  ~ray:reflect_ray
              in 
                match result with
                  | `BACKGROUND -> (*Color.black *) background
                  | `COLOR c -> c
            )
          in
          let one = Color.combine (Color.add [ambient; diffuse_color]) diffuse
          and two = Color.combine reflect_color mirror
          and three = Color.combine specular_color specular
          and four = refract_color in
          let result = Color.add [one; two; three; four] in
            `COLOR result
        ) else (
          `BACKGROUND
        )

    let ray_iter
        ~antialiasing
        ~background
        ~put_pixel
        ~xres ~yres
        ~camera
        ~tracer = 
      let trace_for_color ray =
        match tracer ~ray with
          | `BACKGROUND -> background
          | `COLOR c -> c
      in 
        if antialiasing then (
          let ray_of_xy =
            Camera.gen_ray_of_xy
              ~cnt:main_rays
              ~xres:(xres * 5)
              ~yres:(yres * 5) ~camera
          in
            for y = 0 to yres do
              for x = 0 to xres do
                let curx, cury = x * 5 + 2, y * 5 + 2 in
                let rays = [
                  ray_of_xy (curx-1) (cury-1);
                  ray_of_xy (curx+1) (cury+1);
                  ray_of_xy (curx-1) (cury+1);
                  ray_of_xy (curx+1) (cury-1);
                ] in
                let colors = List.map trace_for_color rays in
                  put_pixel x y (Color.average colors)
              done;
            done
        ) else (
          let ray_of_xy =
            Camera.gen_ray_of_xy
              ~cnt:main_rays
              ~xres ~yres
              ~camera
          in
            for y = yres downto 0 do
              for x = 0 to xres do
                let ray = ray_of_xy x y in
                let color = trace_for_color ray in
                  put_pixel x y color
              done;
            done
        )
    ;;

    let render
        ?(n_atmosphere=n_air)
        ?(antialiasing=true)
        ?(background=Color.black)
        ?(ambient=Color.black)
        ~xres ~yres ~camera () =
      
      printf "*** Rendering scene ***\n";
      let time1 = Unix.time () in
      Camera.print camera;
      
      Graph.start xres yres;
      let refract_stack = Stack.create() in
        Stack.push n_atmosphere refract_stack;
        
        ray_iter
          ~antialiasing
          ~background
          ~xres ~yres
          ~camera 
          ~put_pixel:Graph.put_pixel
          ~tracer:(tracer ~background ~ambient ~refract_stack ~depth:8); 
        
        printf "(%d main rays) (%d reflect) (%d refract) (%d shadow rays) "
          !main_rays !reflect_rays !refract_rays !shadow_rays;
        printf "traced = %d in %5.2f seconds\n%!"
          (!main_rays + !reflect_rays + !refract_rays + !shadow_rays)
          (Unix.time () -. time1);

        Graph.wait ();
        Graph.stop ();
  end
    

type renderer =
    ?background:Color.t
    -> ?ambient:Color.t
    -> camera:Camera.t
    -> unit -> unit

let render_scene1 (renderer:renderer) =
  (* Textures *)
  let c_checked1 =
    Texture.create
      ~pattern:(`CHECKED (Color.black, Color.white))
      ~sizex:1.0 ~sizey:1.0 ()

  and c_checked2 =
    Texture.create
      ~pattern:(`CHECKED (Color.green, Color.yellow))
      ~sizex:0.1 ~sizey:0.1 ()

  and c_gradient =
    Texture.create
      ~pattern:(`XGRADIENT (Color.red, Color.yellow)) ()
  in

  (* Materials *)
  let m_floor =
    Material.create
      ~specular:Texture.black
      ~mirror:Texture.black
      ~diffuse:c_checked1 ()

  and m_gradient =
    Material.create
      ~diffuse:c_gradient () 

  and m_blue_checked =
    Material.create
      ~diffuse:c_checked2 () 

  in 
    (* Objects *)
    Scene.add_plane 
      ~distance:(-.1.0)
      ~normal:(Vect.create 0. 1. 0.)
      ~material:m_floor;

    Scene.add_sphere
      ~location:(Vect.create 0.5 0. (8.))
      ~radius:1.0
      ~material:m_blue_checked;

    Scene.add_sphere
      ~location:(Vect.create (-.0.7) (-.0.5) (7.)) 
      ~radius:0.5
      ~material:m_gradient;

    Scene.add_area_light 
      ~location:(Vect.create 3. 12. 0.) 
      ~radius:2.
      ~color:(Color.create 0.03 0.03 0.03) ~lights:20; 

    let camera = Camera.create ()
    and ambient = Color.create 0.1 0.1 0.1
    and background = Color.create 0.5 0.5 0.5 in
      renderer ~ambient ~background ~camera ()
;;

let render_scene2 (renderer : renderer) =
  (* Textures *)
  let tex_checked =
    Texture.create
      ~pattern:(`CHECKED
                  (
                    Color.create 0.0 0.0 0.3 ,
                    Color.create 0.9 0.9 0.9
                  )
               )
      ~sizex:0.70
      ~sizey:0.70 ()
  
  and tex_checked2 =
    Texture.create
      ~pattern:(`CHECKED (Color.green, Color.yellow))
      ~sizex:0.1
      ~sizey:0.1 ()
  in

  (* Materials *)
  let m_floor =
    Material.create
      ~specular:Texture.black
      ~mirror:Texture.black
      ~diffuse:tex_checked ()

  and m_big_ball =
    Material.create
      ~diffuse:tex_checked2
      ~mirror:(Texture.create_plain 0.2 0.2 0.2) () 

  and m_glass =
    Material.create 
      ~diffuse:(Texture.create_plain 0.1 0.1 0.1)
      ~specular:(Texture.create_plain 0.5 0.5 0.5)
      ~mirror:(Texture.create_plain 0.08 0.08 0.08)
      ~shininess:5.0
      ~n:(Render.n_glass)
      ~transparency:0.9 ()
  in 
    (* Objects *)
    Scene.add_plane
      ~distance:(-.1.0)
      ~normal:(Vect.create 0. 1. 0.) 
      ~material:m_floor;

    Scene.add_sphere 
      ~location:(Vect.create 0.5 0. (10.))
      ~radius:1.0 
      ~material:m_big_ball;

    Scene.add_sphere 
      ~location:(Vect.create (-.0.7) (-.0.5) (7.))
      ~radius:0.5
      ~material:m_glass;

    Scene.add_sphere 
      ~location:(Vect.create (0.7) (-.0.5) (8.))
      ~radius:0.9
      ~material:m_glass;

    Scene.add_area_light 
      ~location:(Vect.create 3. 12. 0.)
      ~radius:2. 
      ~color:(Color.create 0.03 0.03 0.03)
      ~lights:20; 

    let camera = 
      Camera.create
        ~loc:(Vect.create (-.2.8) 3.0 (1.2))
        ~dir:(Vect.create 0.4 (-.0.45) 1.)
        ~auto_top:true
        ~fov:(Camera.fov_of_degree 60.0)
        ~ratio:(4.0/.3.0)
        ()
    and ambient = Color.create 0.1 0.1 0.1
    and background = Color.create 0.7 0.7 1.0 in
      renderer ~ambient ~background ~camera ()
;;

let render_scene_with_diamond (renderer : renderer) =
  (* Textures *)
  let tex_checked =
    Texture.create
      ~pattern:(`CHECKED
                  (Color.create 0.0 0.0 0.3 ,
                   Color.create 0.9 0.9 0.9)
               )
      ~sizex:0.70
      ~sizey:0.70 ()
  
  and tex_checked2 =
    Texture.create
      ~pattern:(`CHECKED (Color.green, Color.yellow))
      ~sizex:0.1
      ~sizey:0.1 ()
  in

  (* Materials *)
  let m_floor =
    Material.create
      ~specular:Texture.black
      ~mirror:Texture.black
      ~diffuse:tex_checked ()

  and m_big_ball =
    Material.create
      ~diffuse:tex_checked2
      ~mirror:(Texture.create_plain 0.0 0.0 0.0) () 

  and m_glass =
    Material.create 
      ~diffuse:(Texture.black)
      ~specular:(Texture.create_plain 0.0 0.0 0.0)
      ~mirror:(Texture.create_plain 0.00 0.00 0.00)
      ~shininess:5.0
      ~n:(Render.n_glass)
      ~transparency:1.0 ()

  and m_diamond =
    Material.create 
      ~diffuse:(Texture.black)
      ~specular:(Texture.create_plain 0.0 0.0 0.0)
      ~mirror:(Texture.create_plain 0.5 0.5 0.5)
      ~shininess:5.0
      ~n:(Render.n_diamond)
      ~transparency:0.9 ()

  in 
    (* Objects *)
    Scene.add_plane 
      ~distance:(-.1.0) 
      ~normal:(Vect.create 0. 1. 0.)
      ~material:m_floor;

    Scene.add_sphere
      ~location:(Vect.create 0.0 0. 6.)
      ~radius:1.0
      ~material:m_glass;

    Scene.add_sphere 
      ~location:(Vect.create 0.0 0.0 6.0) 
      ~radius:0.75
      ~material:m_diamond;

    Scene.add_area_light
      ~location:(Vect.create 3. 12. 0.)
      ~radius:2. 
      ~color:(Color.create 0.03 0.03 0.03) 
      ~lights:20; 

    let camera = Camera.create ()
    and ambient = Color.create 0.1 0.1 0.1
    and background = Color.create 0.7 0.7 1.0 in
      renderer ~ambient ~background ~camera ()
;;


let render_scene_billiards (renderer : renderer) =
  (* Textures *)
  let tex_checked =
    Texture.create
      ~pattern:(`CHECKED
                  (Color.create 0.0 0.0 0.3 ,
                   Color.create 0.9 0.9 0.9)
               )
      ~sizex:0.70
      ~sizey:0.70 ()
  
  and tex_checked2 =
    Texture.create
      ~pattern:(`CHECKED (Color.green, Color.yellow))
      ~sizex:0.1
      ~sizey:0.1 ()
  in

  (* Materials *)
  let m_floor =
    Material.create
      ~specular:Texture.black
      ~mirror:Texture.black
      ~diffuse:tex_checked ()

  and m_redball =
    Material.create
      ~diffuse:tex_checked2
      ~mirror:(Texture.black) 
      ~transparency:0.0 ()

  and m_glass =
    Material.create 
      ~diffuse:(Texture.create_plain 0.1 0.1 0.1)
      ~specular:(Texture.create_plain 0.5 0.5 0.5)
      ~mirror:(Texture.create_plain 0.08 0.08 0.08)
      ~shininess:5.0
      ~n:(Render.n_glass)
      ~transparency:0.0 ()
  in 
    
  let rand_texture () =
    let color () = 
      match Random.int 10  with
        | 0 -> Color.white
        | 1 -> Color.black
        | 2 -> Color.green
        | 3 -> Color.red
        | 4 -> Color.blue
        | 5 -> Color.create 1.0 1.0 0.0
        | 6 -> Color.create 0.0 1.0 1.0
        | 7 -> Color.create 1.0 0.0 1.0
        | 8 -> Color.create 0.5 0.5 0.5
        | _ ->
            Color.create
              (Random.float 1.0)
              (Random.float 1.0)
              (Random.float 1.0)
    in
      match Random.int 2 with
        | 0 -> 
            Texture.create ~pattern:(`PLAIN (color ())) ()
        | _ -> 
            Texture.create
              ~pattern:(`CHECKED (color (), color ()))
              ~sizex:0.1
              ~sizey:0.1 ()
  in

  let create_spheres st_x st_z x_cnt z_cnt y radius =
    let left = st_x -. (float_of_int x_cnt) *. radius 
    and top = st_z -. (float_of_int z_cnt) *. radius
    in
      for x = 0 to x_cnt-1 do
        for z = 0 to z_cnt-1 do 
          let material = 
            Material.create
              ~diffuse:(rand_texture ())
              ~mirror:(Texture.create_plain 0.0 0.0 0.0)
              ~transparency:0.8
              ~n:Render.n_glass ()
          in
          let center_x = left +. (float_of_int x) *. radius *. 2.0 
          and center_z = top +. (float_of_int z) *. radius *. 2.0
          in
            Scene.add_sphere
              ~location:(Vect.create center_x y center_z)
              ~radius:(radius -. 0.01)
              ~material
        done
      done
  in

    (* Objects *)
    Scene.add_plane 
      ~distance:(-.0.3)
      ~normal:(Vect.create 0. 1. 0.) 
      ~material:m_floor;

    create_spheres 0.0 4.0 6 6 0.0 0.3;
    create_spheres 0.0 4.0 5 5 0.6 0.3;
    create_spheres 0.0 4.0 4 4 1.2 0.3;
    create_spheres 0.0 4.0 3 3 1.8 0.3;
    create_spheres 0.0 4.0 2 2 2.4 0.3;
    create_spheres 0.0 4.0 1 1 3.0 0.3;

    Scene.add_light 
      ~location:(Vect.create (-.5.) 5. 0.)
      ~color:(Color.create 0.3 0.3 0.3);
    Scene.add_area_light 
      ~location:(Vect.create 3. 12. 0.)
      ~radius:2.
      ~color:(Color.create 0.03 0.03 0.03) 
      ~lights:20; 

    let camera = 
      Camera.create
        ~loc:(Vect.create (-.3.0) 7.8 (-5.0))
        ~dir:(Vect.create 0.4 (-.0.86) 1.)
        ~auto_top:true
        ~fov:(Camera.fov_of_degree 60.0)
        ~ratio:(4.0/.3.0)
        ()
    and ambient = Color.create 0.1 0.1 0.1
    and background = Color.create 0.7 0.7 1.0 in
      renderer ~ambient ~background ~camera ()

;;


let _ = 
  let renderer = 
    Render.render
      ~n_atmosphere:Render.n_air
      ~antialiasing:true
      ~xres:640
      ~yres:480
  in
    render_scene_billiards renderer;
;;