   This code has been created by   aka  - 20 may 2000 
  *   and uses NeHe tutorials as a starting point ( window initialization , 
  *   texture loading , GL initialization and code for keypresses ) - very good 
  *   tutorials ,  . If anyone is interested about the presented algorithm 
  *   please e - mail me at  
  * 
  *   Code Commmenting And Clean Up By   ( NeHe ) 
  *   NeHe Productions     ...       
  * 
  *   The full tutorial associated with this file is available here : 
  *    
 
 *  and uses NeHe tutorials as a starting point (window initialization,
 *  texture loading, GL initialization and code for keypresses) - very good
 *  tutorials, Jeff. If anyone is interested about the presented algorithm
 *  please e-mail me at 
 *
 *  Code Commmenting And Clean Up By Jeff Molofee ( NeHe )
 *  NeHe Productions    ...     
 *
 *  The full tutorial associated with this file is available here:
 *  
 *)
  Ported from C to OCaml by  

open GL                                               (* Module For The OpenGL Library *)
open Glu                                              (* Module For The GLu Library *)
open Glut                                             (* Module For The GLUT Library *)

(* Light Parameters *)
let lightAmb = (0.7, 0.7, 0.7, 1.0)                   (* Ambient Light *)
let lightDif = (1.0, 1.0, 1.0, 1.0)                   (* Diffuse Light *)
let lightPos = (4.0, 4.0, 6.0, 1.0)                   (* Light Position *)


let xrot      = ref  0.0                              (* X Rotation *)
let yrot      = ref  0.0                              (* Y Rotation *)
let xrotspeed = ref  0.0                              (* X Rotation Speed *)
let yrotspeed = ref  0.0                              (* Y Rotation Speed *)
let zoom      = ref(-7.0)                             (* Depth Into The Screen *)
let height    = ref  2.0                              (* Height Of Ball From Floor *)

let ( += ) a b = a := !a +. b
let ( -= ) a b = a := !a -. b


  Resize And Initialize The GL Window
  let height =
  Prevent A Divide By Zero By
  Making Height Equal One
    else height
  in
  glViewport 0 0 width height;                        (* Reset The Current Viewport *)

  glMatrixMode GL_PROJECTION;                         (* Select The Projection Matrix *)
  glLoadIdentity();                                   (* Reset The Projection Matrix *)

  (* Calculate The Aspect Ratio Of The Window *)
  gluPerspective 45.0 (float width /. float height) 0.1 100.0;

  glMatrixMode GL_MODELVIEW;                          (* Select The Modelview Matrix *)
  glLoadIdentity();                                   (* Reset The Modelview Matrix *)
;;


let loadGLTextures() =                                (* Load Bitmaps And Convert To Textures *)
  (* Load Texture *)
  let load_image filename =
    let image_data, sizeX, sizeY, _, _ = Png_loader.load_img (Filename filename) in
    (sizeX, sizeY, image_data)
  in
  let files = [|
    "Data/lesson26/Envwall.png";                      (* The Floor Texture *)
  the Light Texture
    "Data/lesson26/Envroll.png";                      (* the Wall Texture *)
  |] in
  let img_textures = Array.map load_image files in

  let textures = glGenTextures 3 in                   (* Create 3 Textures *)

  Array.iteri (fun i (sizeX, sizeY, image_data) ->
      glBindTexture BindTex.GL_TEXTURE_2D textures.(i);
      glTexImage2D TexTarget.GL_TEXTURE_2D 0 InternalFormat.GL_RGB sizeX sizeY GL_RGB GL_UNSIGNED_BYTE image_data;
      glTexParameter TexParam.GL_TEXTURE_2D (TexParam.GL_TEXTURE_MAG_FILTER Mag.GL_LINEAR);
      glTexParameter TexParam.GL_TEXTURE_2D (TexParam.GL_TEXTURE_MIN_FILTER Min.GL_LINEAR);
  ) img_textures;

  (textures)
;;


let initGL() =                                        (* All Setup For OpenGL Goes Here *)
  If Loading The Textures Failed
  Enable Smooth Shading
  glClearColor 0.2 0.5 1.0 1.0;                       (* Background *)
  glClearDepth 1.0;                                   (* Depth Buffer Setup *)
  Clear The Stencil Buffer To 0
  glEnable GL_DEPTH_TEST;                             (* Enables Depth Testing *)
  The Type Of Depth Testing To Do
  glHint GL_PERSPECTIVE_CORRECTION_HINT  GL_NICEST;   (* Really Nice Perspective Calculations *)
  Enable 2D Texture Mapping

  Set The Ambient Lighting For Light0
  Set The Diffuse Lighting For Light0
  glLight (GL_LIGHT 0) (Light.GL_POSITION lightPos);  (* Set The Position For Light0 *)

  glEnable GL_LIGHT0;                                 (* Enable Light 0 *)
  glEnable GL_LIGHTING;                               (* Enable Lighting *)

  let q = gluNewQuadric() in                          (* Create A New Quadratic For Drawing A Sphere *)
  gluQuadricNormals q GLU_SMOOTH;                     (* Generate Smooth Normals For The Quad *)
  gluQuadricTexture q true;                           (* Enable Texture Coords For The Quad *)

  glTexGen GL_S GL_TEXTURE_GEN_MODE GL_SPHERE_MAP;    (* Set Up Sphere Mapping *)
  glTexGen GL_T GL_TEXTURE_GEN_MODE GL_SPHERE_MAP;    (* Set Up Sphere Mapping *)

  (texture, q)
;;


let drawObject ~q ~texture =                          (* Draw Our Ball *)
  glColor3 1.0 1.0 1.0;                               (* Set Color To White *)
  Select Texture 2 ( 1 )
  gluSphere q 0.35 32 16;                             (* Draw First Sphere *)

  Select Texture 3 ( 2 )
  Set Color To White With 40 % Alpha
  glEnable GL_BLEND;                                  (* Enable Blending *)
  Set Blending Mode To Mix Based On SRC Alpha
  glEnable GL_TEXTURE_GEN_S;                          (* Enable Sphere Mapping *)
  glEnable GL_TEXTURE_GEN_T;                          (* Enable Sphere Mapping *)

  gluSphere q 0.35 32 16;                             (* Draw Another Sphere Using New Texture *)
                                                      (* Textures Will Mix Creating A MultiTexture Effect (Reflection) *)
  glDisable GL_TEXTURE_GEN_S;                         (* Disable Sphere Mapping *)
  glDisable GL_TEXTURE_GEN_T;                         (* Disable Sphere Mapping *)
  glDisable GL_BLEND;                                 (* Disable Blending *)
;;


let drawFloor ~texture =                              (* Draws The Floor *)
  glBindTexture BindTex.GL_TEXTURE_2D texture.(0);    (* Select Texture 1 (0) *)
  glBegin GL_QUADS;                                   (* Begin Drawing A Quad *)
    glNormal3 0.0 1.0 0.0;                            (* Normal Pointing Up *)
      glTexCoord2 0.0 1.0;                            (* Bottom Left Of Texture *)
      glVertex3 (-2.0) 0.0 2.0;                       (* Bottom Left Corner Of Floor *)

      glTexCoord2 0.0 0.0;                            (* Top Left Of Texture *)
      glVertex3 (-2.0) 0.0 (-2.0);                    (* Top Left Corner Of Floor *)

      glTexCoord2 1.0 0.0;                            (* Top Right Of Texture *)
      glVertex3 2.0 0.0 (-2.0);                       (* Top Right Corner Of Floor *)

  Bottom Right Of Texture
  Bottom Right Corner Of Floor
  glEnd();                                            (* Done Drawing The Quad *)
;;


let display ~q ~texture () =                          (* Draw Everything *)
  Clear Screen , Depth Buffer & Stencil Buffer
  glClear [GL_COLOR_BUFFER_BIT; GL_DEPTH_BUFFER_BIT; GL_STENCIL_BUFFER_BIT];

  (* Clip Plane Equations *)
  let eqr = [| 0.0; -1.0; 0.0; 0.0 |] in              (* Plane Equation To Use For The Reflected Objects *)

  glLoadIdentity();                                   (* Reset The Modelview Matrix *)
  glTranslate 0.0 (-0.6) !zoom;                       (* Zoom And Raise Camera Above The Floor (Up 0.6 Units) *)
  glColorMask false false false false;                (* Set Color Mask *)
  Enable Stencil Buffer For " marking " The Floor
  glStencilFunc GL_ALWAYS 1 1;                        (* Always Passes, 1 Bit Plane, 1 As Mask *)
  We Set The Stencil Buffer To 1 Where We Draw Any Polygon
                                                      (* Keep If Test Fails, Keep If Test Passes But Buffer Test Fails *)
                                                      (* Replace If Test Passes *)
  glDisable GL_DEPTH_TEST;                            (* Disable Depth Testing *)
  drawFloor ~texture;                                 (* Draw The Floor (Draws To The Stencil Buffer) *)
                                                      (* We Only Want To Mark It In The Stencil Buffer *)
  glEnable GL_DEPTH_TEST;                             (* Enable Depth Testing *)
  glColorMask true true true true;                    (* Set Color Mask to true, true, true, true *)
  We Draw Only Where The Stencil Is 1
  ( I.E. Where The Floor Was Drawn )
  glStencilOp GL_KEEP  GL_KEEP  GL_KEEP;              (* Don't Change The Stencil Buffer *)
  glEnable GL_CLIP_PLANE0;                            (* Enable Clip Plane For Removing Artifacts *)
  ( When The Object Crosses The Floor )
  glClipPlane Plane.GL_CLIP_PLANE0 eqr;               (* Equation For Reflected Objects *)
  glPushMatrix();                                     (* Push The Matrix Onto The Stack *)
  Mirror Y Axis
  Set Up Light0
    glTranslate 0.0 !height 0.0;                      (* Position The Object *)
  Rotate Local Coordinate System On X Axis
  Rotate Local Coordinate System On Y Axis
    drawObject ~q ~texture;                           (* Draw The Sphere (Reflection) *)
  Pop The Matrix Off The Stack
  glDisable GL_CLIP_PLANE0;                           (* Disable Clip Plane For Drawing The Floor *)
  glDisable GL_STENCIL_TEST;                          (* We Don't Need The Stencil Buffer Any More (Disable) *)
  Set Up Light0 Position
  Enable Blending ( Otherwise The Reflected Object Wo nt Show )
  glDisable GL_LIGHTING;                              (* Since We Use Blending, We Disable Lighting *)
  Set Color To White With 80 % Alpha
  glBlendFunc Sfactor.GL_SRC_ALPHA
  Blending Based On Source Alpha And 1 Minus Dest Alpha
  Draw The Floor To The Screen
  glEnable GL_LIGHTING;                               (* Enable Lighting *)
  glDisable GL_BLEND;                                 (* Disable Blending *)
  glTranslate 0.0 !height 0.0;                        (* Position The Ball At Proper Height *)
  Rotate On The X Axis
  Rotate On The Y Axis
  drawObject ~q ~texture;                             (* Draw The Ball *)
  Update X Rotation Angle By 
  Update Y Rotation Angle By yrotspeed
  glFlush();                                          (* Flush The GL Pipeline *)

  glutSwapBuffers();
;;


let key ~key ~x ~y =
  begin match key with
  | '\027' | 'q' ->
      exit(0);

  | 'A' ->
      zoom += 0.05;

  | 'Z' ->
      zoom -= 0.05;
  | _ -> ()
  end;
  glutPostRedisplay();
;;


let specialKey ~key ~x ~y =
  begin match key with
  | GLUT_KEY_UP ->
      xrotspeed += 0.08;
  | GLUT_KEY_DOWN ->
      xrotspeed -= 0.08;
  | GLUT_KEY_LEFT ->
      yrotspeed -= 0.08;
  | GLUT_KEY_RIGHT ->
      yrotspeed += 0.08;
  | GLUT_KEY_PAGE_UP ->
      height += 0.03;
  | GLUT_KEY_PAGE_DOWN ->
      height -= 0.03;
  | _ -> ()
  end;
  glutPostRedisplay();
;;


(* main *)
let () =
  let _ = glutInit Sys.argv in
  glutInitWindowPosition 0 0;
  glutInitWindowSize 640 480;
  glutInitDisplayMode [GLUT_RGB; GLUT_DOUBLE; GLUT_DEPTH; GLUT_STENCIL];
  let _ = glutCreateWindow Sys.argv.(0) in
  let texture, q = initGL() in
  glutReshapeFunc ~reshape;
  glutKeyboardFunc (key);
  glutSpecialFunc (specialKey);
  glutDisplayFunc (display ~q ~texture);
  glutMainLoop();
;;


 Module For The OpenGL Library 
 Module For The GLu Library 
 Module For The GLUT Library 
 Light Parameters 
 Ambient Light 
 Diffuse Light 
 Light Position 
 X Rotation 
 Y Rotation 
 X Rotation Speed 
 Y Rotation Speed 
 Depth Into The Screen 
 Height Of Ball From Floor 
 Reset The Current Viewport 
 Select The Projection Matrix 
 Reset The Projection Matrix 
 Calculate The Aspect Ratio Of The Window 
 Select The Modelview Matrix 
 Reset The Modelview Matrix 
 Load Bitmaps And Convert To Textures 
 Load Texture 
 The Floor Texture 
 the Wall Texture 
 Create 3 Textures 
 All Setup For OpenGL Goes Here 
 Background 
 Depth Buffer Setup 
 Enables Depth Testing 
 Really Nice Perspective Calculations 
 Set The Position For Light0 
 Enable Light 0 
 Enable Lighting 
 Create A New Quadratic For Drawing A Sphere 
 Generate Smooth Normals For The Quad 
 Enable Texture Coords For The Quad 
 Set Up Sphere Mapping 
 Set Up Sphere Mapping 
 Draw Our Ball 
 Set Color To White 
 Draw First Sphere 
 Enable Blending 
 Enable Sphere Mapping 
 Enable Sphere Mapping 
 Draw Another Sphere Using New Texture 
 Textures Will Mix Creating A MultiTexture Effect (Reflection) 
 Disable Sphere Mapping 
 Disable Sphere Mapping 
 Disable Blending 
 Draws The Floor 
 Select Texture 1 (0) 
 Begin Drawing A Quad 
 Normal Pointing Up 
 Bottom Left Of Texture 
 Bottom Left Corner Of Floor 
 Top Left Of Texture 
 Top Left Corner Of Floor 
 Top Right Of Texture 
 Top Right Corner Of Floor 
 Done Drawing The Quad 
 Draw Everything 
 Clip Plane Equations 
 Plane Equation To Use For The Reflected Objects 
 Reset The Modelview Matrix 
 Zoom And Raise Camera Above The Floor (Up 0.6 Units) 
 Set Color Mask 
 Always Passes, 1 Bit Plane, 1 As Mask 
 Keep If Test Fails, Keep If Test Passes But Buffer Test Fails 
 Replace If Test Passes 
 Disable Depth Testing 
 Draw The Floor (Draws To The Stencil Buffer) 
 We Only Want To Mark It In The Stencil Buffer 
 Enable Depth Testing 
 Set Color Mask to true, true, true, true 
 Don't Change The Stencil Buffer 
 Enable Clip Plane For Removing Artifacts 
 Equation For Reflected Objects 
 Push The Matrix Onto The Stack 
 Position The Object 
 Draw The Sphere (Reflection) 
 Disable Clip Plane For Drawing The Floor 
 We Don't Need The Stencil Buffer Any More (Disable) 
 Since We Use Blending, We Disable Lighting 
 Enable Lighting 
 Disable Blending 
 Position The Ball At Proper Height 
 Draw The Ball 
 Flush The GL Pipeline 
 main 
   This code has been created by   aka  - 20 may 2000 
  *   and uses NeHe tutorials as a starting point ( window initialization , 
  *   texture loading , GL initialization and code for keypresses ) - very good 
  *   tutorials ,  . If anyone is interested about the presented algorithm 
  *   please e - mail me at  
  * 
  *   Code Commmenting And Clean Up By   ( NeHe ) 
  *   NeHe Productions     ...       
  * 
  *   The full tutorial associated with this file is available here : 
  *    
 
 *  and uses NeHe tutorials as a starting point (window initialization,
 *  texture loading, GL initialization and code for keypresses) - very good
 *  tutorials, Jeff. If anyone is interested about the presented algorithm
 *  please e-mail me at 
 *
 *  Code Commmenting And Clean Up By Jeff Molofee ( NeHe )
 *  NeHe Productions    ...     
 *
 *  The full tutorial associated with this file is available here:
 *  
 *)
  Ported from C to OCaml by  





let ( += ) a b = a := !a +. b
let ( -= ) a b = a := !a -. b


  Resize And Initialize The GL Window
  let height =
  Prevent A Divide By Zero By
  Making Height Equal One
    else height
  in


  gluPerspective 45.0 (float width /. float height) 0.1 100.0;

;;


  let load_image filename =
    let image_data, sizeX, sizeY, _, _ = Png_loader.load_img (Filename filename) in
    (sizeX, sizeY, image_data)
  in
  let files = [|
  the Light Texture
  |] in
  let img_textures = Array.map load_image files in


  Array.iteri (fun i (sizeX, sizeY, image_data) ->
      glBindTexture BindTex.GL_TEXTURE_2D textures.(i);
      glTexImage2D TexTarget.GL_TEXTURE_2D 0 InternalFormat.GL_RGB sizeX sizeY GL_RGB GL_UNSIGNED_BYTE image_data;
      glTexParameter TexParam.GL_TEXTURE_2D (TexParam.GL_TEXTURE_MAG_FILTER Mag.GL_LINEAR);
      glTexParameter TexParam.GL_TEXTURE_2D (TexParam.GL_TEXTURE_MIN_FILTER Min.GL_LINEAR);
  ) img_textures;

  (textures)
;;


  If Loading The Textures Failed
  Enable Smooth Shading
  Clear The Stencil Buffer To 0
  The Type Of Depth Testing To Do
  Enable 2D Texture Mapping

  Set The Ambient Lighting For Light0
  Set The Diffuse Lighting For Light0




  (texture, q)
;;


  Select Texture 2 ( 1 )

  Select Texture 3 ( 2 )
  Set Color To White With 40 % Alpha
  Set Blending Mode To Mix Based On SRC Alpha

;;





  Bottom Right Of Texture
  Bottom Right Corner Of Floor
;;


  Clear Screen , Depth Buffer & Stencil Buffer
  glClear [GL_COLOR_BUFFER_BIT; GL_DEPTH_BUFFER_BIT; GL_STENCIL_BUFFER_BIT];


  Enable Stencil Buffer For " marking " The Floor
  We Set The Stencil Buffer To 1 Where We Draw Any Polygon
  We Draw Only Where The Stencil Is 1
  ( I.E. Where The Floor Was Drawn )
  ( When The Object Crosses The Floor )
  Mirror Y Axis
  Set Up Light0
  Rotate Local Coordinate System On X Axis
  Rotate Local Coordinate System On Y Axis
  Pop The Matrix Off The Stack
  Set Up Light0 Position
  Enable Blending ( Otherwise The Reflected Object Wo nt Show )
  Set Color To White With 80 % Alpha
  glBlendFunc Sfactor.GL_SRC_ALPHA
  Blending Based On Source Alpha And 1 Minus Dest Alpha
  Draw The Floor To The Screen
  Rotate On The X Axis
  Rotate On The Y Axis
  Update X Rotation Angle By 
  Update Y Rotation Angle By yrotspeed

  glutSwapBuffers();
;;


let key ~key ~x ~y =
  begin match key with
  | '\027' | 'q' ->
      exit(0);

  | 'A' ->
      zoom += 0.05;

  | 'Z' ->
      zoom -= 0.05;
  | _ -> ()
  end;
  glutPostRedisplay();
;;


let specialKey ~key ~x ~y =
  begin match key with
  | GLUT_KEY_UP ->
      xrotspeed += 0.08;
  | GLUT_KEY_DOWN ->
      xrotspeed -= 0.08;
  | GLUT_KEY_LEFT ->
      yrotspeed -= 0.08;
  | GLUT_KEY_RIGHT ->
      yrotspeed += 0.08;
  | GLUT_KEY_PAGE_UP ->
      height += 0.03;
  | GLUT_KEY_PAGE_DOWN ->
      height -= 0.03;
  | _ -> ()
  end;
  glutPostRedisplay();
;;


let () =
  let _ = glutInit Sys.argv in
  glutInitWindowPosition 0 0;
  glutInitWindowSize 640 480;
  glutInitDisplayMode [GLUT_RGB; GLUT_DOUBLE; GLUT_DEPTH; GLUT_STENCIL];
  let _ = glutCreateWindow Sys.argv.(0) in
  let texture, q = initGL() in
  glutReshapeFunc ~reshape;
  glutKeyboardFunc (key);
  glutSpecialFunc (specialKey);
  glutDisplayFunc (display ~q ~texture);
  glutMainLoop();
;;

