Added a lablgl demo.
authorRichard W.M. Jones <rjones@redhat.com>
Sun, 23 Nov 2008 20:28:30 +0000 (20:28 +0000)
committerRichard W.M. Jones <rjones@redhat.com>
Sun, 23 Nov 2008 20:28:30 +0000 (20:28 +0000)
.hgignore
ocaml-lablgl/Makefile [new file with mode: 0644]
ocaml-lablgl/balls.ml [new file with mode: 0644]

index 74b413c..e85c993 100644 (file)
--- a/.hgignore
+++ b/.hgignore
@@ -1,10 +1,11 @@
 syntax: glob
 
 *~
-*.exe
 *.cmi
 *.cmx
+*.exe
 *.o
+*.opt
 *.src.rpm
 
 SDL/SDL-1.2.13.tar.gz
diff --git a/ocaml-lablgl/Makefile b/ocaml-lablgl/Makefile
new file mode 100644 (file)
index 0000000..41b84f4
--- /dev/null
@@ -0,0 +1,15 @@
+# Test some binaries which use ocaml-lablgl.  This builds both the
+# Linux native and Windows binaries, so you can compare them.
+#
+# Note that the demos themselves are non-free, written by Jon Harrop
+# and available from his website here:
+# http://www.ffconsultancy.com/ocaml/
+
+all: balls.opt balls.exe
+
+%.opt: %.ml
+       ocamlfind ocamlopt -I +lablGL unix.cmxa lablglut.cmxa lablgl.cmxa $< -o $@
+
+%.exe: %.ml
+       OCAMLFIND_CONF=/etc/i686-pc-mingw32-ocamlfind.conf \
+       ocamlfind ocamlopt -I +lablGL lablgl.cmxa $< -o $@
diff --git a/ocaml-lablgl/balls.ml b/ocaml-lablgl/balls.ml
new file mode 100644 (file)
index 0000000..f99dbe0
--- /dev/null
@@ -0,0 +1,382 @@
+(* Source of this demo:
+ * http://www.ffconsultancy.com/ocaml/index.html
+ * Non-free, not for redistribution in Fedora.
+ *)
+
+open Printf
+
+let pi = 4. *. atan 1.
+
+module Array = struct
+  include Array
+
+  let for_all p a =
+    fold_left (fun b x -> p x && b) true a
+end
+
+type vec2 = { x: float; y: float }
+
+let vec2 x y = {x=x; y=y}
+
+let zero = vec2 0. 0.
+
+let of_list = function
+    [x; y] -> vec2 x y
+  | _ -> invalid_arg "Vec2.of_list"
+
+let ( +| ) a b = {x = a.x +. b.x; y = a.y +. b.y}
+let ( -| ) a b = {x = a.x -. b.x; y = a.y -. b.y}
+let ( ~| ) a = {x = -. a.x; y = -. a.y}
+let ( *| ) s r = {x = s *. r.x; y = s *. r.y}
+
+let normal a = { x = a.y; y = -. a.x }
+let dot a b = a.x *. b.x +. a.y *. b.y
+let length2 r = dot r r
+let length r = sqrt(length2 r)
+let unitise r = 1. /. length r *| r
+
+(* Get the time since the program started. *)
+let time =
+  let t = Unix.gettimeofday() in
+  fun () -> Unix.gettimeofday() -. t
+
+let width = ref 1 and height = ref 1
+
+(* Reshape the viewport and store the width and height. *)
+let reshape ~w ~h =
+  GlDraw.viewport ~x:0 ~y:0 ~w ~h;
+  width := max 1 w;
+  height := max 1 h
+
+(* Pass a single vertex to the OpenGL. *)
+let vertex {x=x; y=y} =
+  GlDraw.vertex ~x ~y ()
+
+let translate r =
+  GlMat.translate ~x:r.x ~y:r.y ()
+
+let rotate angle =
+  let angle = angle *. 180. /. pi in
+  GlMat.rotate ~angle ~z:1. ()
+
+let scale s =
+  GlMat.scale ~x:s ~y:s ()
+
+let protect f g =
+  try
+    f();
+    g()
+  with e ->
+    g();
+    raise e
+
+let mat f =
+  GlMat.push();
+  protect f GlMat.pop
+
+let render prim f =
+  GlDraw.begins prim;
+  protect f GlDraw.ends
+
+let circle x = vec2 (sin x) (cos x)
+
+(* Memoize OpenGL calls in a display list. *)
+let gl_memoize f =
+  let dl = ref None in
+  fun () -> match !dl with
+  | Some dl -> GlList.call dl
+  | None ->
+      let dl' = GlList.create `compile in
+      f ();
+      GlList.ends ();
+      dl := Some dl'
+
+let rec iter f l u = if l<u then (f l; iter f (l+1) u)
+
+(* Render a ball at the origin. *)
+let render_ball =
+  gl_memoize
+    (fun () ->
+       let n = 36 in
+       let aux i = circle (2. *. pi *. float i /. float n) in
+       render `triangle_fan
+        (fun () ->
+           vertex zero;
+           iter (fun i -> vertex (aux i)) 0 (n/4+1));
+       render `triangle_fan
+        (fun () ->
+           vertex zero;
+           iter (fun i -> vertex (aux i)) (n/2) (3*n/4+1));
+       render `triangle_strip
+        (fun () -> iter (fun i ->
+                           vertex (0.9 *| aux i);
+                           vertex (aux i)) 0 (n+1)))
+
+type circle = { center: vec2; radius: float }
+
+type ball = { circle: circle;
+             velocity: vec2;
+             angle: float;
+             angular_velocity: float }
+
+let make_ball ?(v=vec2 0.1 0.) ?(r=0.05) x y =
+  { circle = { center = vec2 x y; radius = r };
+    velocity = v;
+    angle = 0.;
+    angular_velocity = 0. }
+
+type surface =
+    Line of vec2 * vec2
+  | Circle of vec2 * float
+
+let surfaces = ref [Line (vec2 0. 0., vec2 0. 1.);
+                   Line (vec2 0. 1., vec2 1. 1.);
+                   Line (vec2 1. 1., vec2 1. 0.);
+                   Line (vec2 1. 0.2, vec2 0. 0.)]
+
+(* Split balls. *)
+let balls =
+  ref [|{(make_ball 0.5 0.9) with velocity = vec2 0. (-1.)};
+       {(make_ball 0.45 0.05) with velocity = vec2 0. 0.};
+       {(make_ball 0.55 0.05) with velocity = vec2 0. 0.}|]
+let surfaces = ref [Line (vec2 0. 0., vec2 0. 1.);
+                   Line (vec2 0. 1., vec2 1. 1.);
+                   Line (vec2 1. 1., vec2 1. 0.);
+                   Line (vec2 1. 0., vec2 0. 0.)]
+let g = vec2 0. 0.1
+
+let n_balls = try min 100 (max 0 (int_of_string(Sys.argv.(1)))) with _ -> 3
+
+(* Funnel *)
+let balls =
+  let n = 10 in
+  ref (Array.init n_balls
+        (fun x -> make_ball ~v:(vec2 0. 0.) ~r:0.02
+           (0.2 +. 0.03 *. float ((x/n) mod 2) +. 0.06 *. float (x mod n))
+           (0.9 -. 0.04 *. float (x/n))))
+let surfaces =
+  let n = 128 in
+  let aux i =
+    let r = circle (2. *. pi *. float (i + n/4) /. float n) in
+    vec2 (0.5 +. 0.5 *. r.x) (1. +. r.y) in
+  ref (Array.to_list (Array.init n (fun i -> Line (aux (i-1), aux i))))
+let surfaces = ref (Circle (vec2 0.37 0.3, 0.11) ::
+                     Circle (vec2 0.63 0.3, 0.11) ::
+                     !surfaces)
+let g = vec2 0. 0.5
+
+let display_ball ball =
+  mat (fun () ->
+        translate ball.circle.center;
+        rotate ball.angle;
+        scale ball.circle.radius;
+        render_ball())
+
+let display_balls () =
+  Array.iter display_ball !balls
+
+let display_surface = function
+    Line (v1, v2) ->
+      GlDraw.begins `lines;
+      List.iter vertex [v1; v2];
+      GlDraw.ends ()
+  | Circle (c, r) ->
+      GlMat.push();
+      mat (fun () ->
+            translate c;
+            scale r;
+            let n = 360 in
+            GlDraw.begins `line_loop;
+            for i=0 to n-1 do
+              vertex (circle (2. *. pi *. float i /. float n))
+            done;
+            GlDraw.ends ())
+
+let display_surfaces =
+  gl_memoize (fun () -> List.iter display_surface !surfaces)
+
+let display () =
+  GlClear.clear [ `color; `depth ];
+  Gl.enable `depth_test;
+  GlFunc.depth_func `lequal;
+
+  (* Initialise a orthogonal projection of the 2D scene. *)
+  GlMat.mode `projection;
+  GlMat.load_identity ();
+  GlMat.ortho ~x:(0., 1.) ~y:(0., 1.) ~z:(0., 1.);
+  GlMat.mode `modelview;
+  GlMat.load_identity ();
+
+  GlDraw.color (1., 1., 1.) ~alpha:1.;
+
+  display_balls();
+  display_surfaces();
+
+  Gl.finish ();
+
+  Unix.sleep 0;
+
+  Glut.swapBuffers ()
+
+let clamp l u (x : float) =
+  if x<l then l else if x>u then u else x
+
+let circle_circle c1 r1 c2 r2 =
+  let s = c2 -| c1 in
+  c1 +| 0.5 *. (length s +. r1 -. r2) *| unitise s
+
+let circle_circle c1 r1 c2 r2 =
+  let s = c2 -| c1 in
+  let l = length s in
+  let s = (1. /. l) *| s in
+  c1 +| 0.5 *. (l +. r1 -. r2) *| s
+
+(* Find the point of impact between a circle and a surface. *)
+let point_of_impact circle = function
+    Line (p1, p2) ->
+      (* Find the closest approach of the finite line v1 -> v2 to the point
+        r. *)
+      let p21 = p2 -| p1 in
+      let x = clamp 0. 1. (dot (circle.center -| p1) p21 /. length2 p21) in
+      p1 +| x *| p21
+  | Circle (c2, r2) -> circle_circle circle.center circle.radius c2 r2
+
+let circle_circle_intersects c1 c2 =
+  let p = circle_circle c1.center c1.radius c2.center c2.radius in
+  length(c1.center -| p) < c1.radius
+
+let rec update_ball dt (ball, impacts as accu) surface =
+  let p = point_of_impact ball.circle surface in
+
+  let d = ball.circle.center -| p in
+
+  if length d > ball.circle.radius then accu else begin
+    (* Local basis through center and perpendicular. *)
+    let a = unitise d in
+    let b = normal a in
+    
+    (* Resolve the velocity at the point of impact into components through the
+       center and perpendicular. *)
+    let dva = dot ball.velocity a in
+    let dvb = dot ball.velocity b in
+    if dva >= 0. then accu else
+      { ball with
+         velocity = ball.velocity +| 2. *. abs_float dva *| a -|
+             0. *. ball.angular_velocity *. ball.circle.radius *| b;
+         angular_velocity = -. dvb /. ball.circle.radius }, p::impacts
+  end
+
+(* Bisect the time slice until there is only one impact. *)
+let rec update t t' balls =
+  let dt = t' -. t in
+
+  (* Ball-ball impacts. *)
+  let balls', impacts =
+    let balls = Array.copy balls in
+    let impacts = Array.map (fun _ -> []) balls in
+    let n = Array.length balls in
+
+    for i=0 to n-1 do
+      (* Ball-surface impacts *)
+      (fun (b, is) ->
+        balls.(i) <- b;
+        impacts.(i) <- is)
+       (List.fold_left (update_ball dt) (balls.(i), impacts.(i)) !surfaces);
+
+      (* Ball-ball impacts. *)
+      if i>0 then
+       let b1 = balls.(i) in
+       for j=0 to i-1 do
+         let b2 = balls.(j) in
+         let c1 = b1.circle and c2 = b2.circle in
+         let p = circle_circle c1.center c1.radius c2.center c2.radius in
+         if length(c1.center -| p) < c1.radius then begin
+           let u1 = b1.velocity and u2 = b2.velocity in
+           let da1 = b1.angular_velocity and da2 = b2.angular_velocity in
+           let r1 = c1.radius and r2 = c2.radius in
+
+           (* Find the velocity difference to the center-of-momentum frame. *)
+           let com = 0.5 *| (u1 +| u2) in
+
+           (* Move to COM frame. *)
+           let u1 = u1 -| com and u2 = u2 -| com in
+           let u = unitise (c2.center -| c1.center) in
+           let v = normal u in
+           let impulse = u2 -| u1 +| (da1 *. r1 -. da2 *. r2) *| v in
+           let impulse_u = dot u impulse *| u in
+           let impulse_v = dot v impulse in
+           let v1 = u1 +| impulse_u and v2 = u2 -| impulse_u in
+           let da1' = da1 +. impulse_v *. r1 in
+           let da2' = da2 -. impulse_v *. r2 in
+
+           (* Move from COM frame. *)
+           let v1 = v1 +| com and v2 = v2 +| com in
+
+           balls.(i) <- { balls.(i) with
+                            velocity = v1;
+                            angular_velocity = da1' };
+           balls.(j) <- { balls.(j) with
+                            velocity = v2;
+                            angular_velocity = da2' };
+           impacts.(i) <- p :: impacts.(i);
+           impacts.(j) <- p :: impacts.(j);
+         end
+       done;
+    done;
+
+    for i=0 to n-1 do
+      let b = balls.(i) in
+      let r = b.circle.center and dr = b.velocity in
+      let a = b.angle and da = b.angular_velocity in
+      let rec aux dr = function
+         [] ->
+            { balls.(i) with
+                circle = { balls.(i).circle with center = r +| dt *| dr };
+                angle = mod_float (a +. dt *. da) (2. *. pi);
+                velocity = dr -| (t' -. t) *| g }
+       | p::t ->
+           (* Make sure the velocity is not pointing towards the impact
+              point. *)
+           let d = unitise(r -| p) in
+           aux (dr -| min 0. (dot dr d) *| d) t in
+      balls.(i) <- aux dr impacts.(i);
+      match impacts.(i) with
+       [] | [_] -> ()
+      | _ ->
+         balls.(i) <- { balls.(i) with angular_velocity = 0. }
+    done;
+    balls, impacts in
+
+  (* Bisect if there was at least one impact and the time slice was large
+     enough. *)
+  let aux = function [] | [_] -> true | _ -> false in
+  if dt<0.01 && (Array.for_all aux impacts || dt < 1e-3) then balls' else
+    let t2 = (t +. t') *. 0.5 in
+    update t2 t' (update t t2 balls)
+
+let old_time = ref 0.
+
+let idle () =
+  let time' = time() in
+  balls := update !old_time time' !balls;
+  old_time := time';
+  Glut.postRedisplay ()
+
+let () =
+  ignore (Glut.init Sys.argv);
+  Glut.initDisplayMode ~alpha:true ~double_buffer:true ~depth:true ();
+  Glut.initWindowSize ~w:1024 ~h:1024;
+  ignore (Glut.createWindow ~title:"Bouncing balls");
+  GlClear.color (0., 0., 0.) ~alpha:0.;
+
+  Gl.enable `blend;
+  List.iter Gl.enable [`line_smooth; `polygon_smooth];
+  List.iter (fun x -> GlMisc.hint x `nicest) [`line_smooth; `polygon_smooth];
+  GlDraw.line_width 4.;
+  GlFunc.blend_func ~src:`src_alpha ~dst:`one;
+
+  Glut.reshapeFunc ~cb:reshape;
+  Glut.displayFunc ~cb:display;
+  Glut.idleFunc ~cb:(Some idle);
+  Glut.keyboardFunc ~cb:(fun ~key ~x ~y -> if key=27 then exit 0);
+  Glut.mainLoop ()