reimplement particle-system using packed structs
authorAndy Wingo <wingo@pobox.com>
Fri, 15 Feb 2013 14:57:08 +0000 (15:57 +0100)
committerAndy Wingo <wingo@pobox.com>
Fri, 15 Feb 2013 14:57:08 +0000 (15:57 +0100)
* examples/particle-system.scm: Reimplement in terms of packed structs.

examples/particle-system.scm

index c5e1f67..a3a69d5 100644 (file)
              (figl gl)
              (figl glu)
              (ice-9 match)
-             (ice-9 format))
+             (ice-9 format)
+             (figl contrib packed-struct))
 
-;; FIXME: need a packed-struct / array abstraction.
-(define floats-per-particle 6) ;; Position and velocity.
-(define *n-particles* 0)
-(define *particles* #f32())
+(define-packed-struct zquad
+  (r float)
+  (g float)
+  (b float)
+
+  (x- float)
+  (y- float)
+  (x+ float)
+  (y+ float)
+
+  (z float))
+
+(define-packed-struct position
+  (x float)
+  (y float)
+  (z float)
+  (vx float)
+  (vy float)
+  (vz float))
+
+(define *zquads* (make-packed-array zquad 0))
+(define *particles* (make-packed-array position 0))
 
 (define (draw-particles)
-  (define (call-with-particle-position i proc)
-    (let ((offset (* i floats-per-particle)))
-      (proc (f32vector-ref *particles* (+ offset 0))
-            (f32vector-ref *particles* (+ offset 1))
-            (f32vector-ref *particles* (+ offset 2)))))
-
-  (gl-color 0.8 0.8 1.0)
-  ;; foo
-  (gl-begin (begin-mode triangles)
-    (let lp ((i 0))
-      (when (< i *n-particles*)
-        (call-with-particle-position
-         i
-         (lambda (x y z)
-           (gl-vertex (- x 0.5) (- y 0.5) z)
-           (gl-vertex (+ x 0.5) (- y 0.5) z)
-           (gl-vertex x (+ y 0.5) z)))
-         (lp (1+ i))))))
+  (gl-begin (begin-mode quads)
+    (unpack-each/serial *zquads* zquad
+                        (lambda (n r g b x- y- x+ y+ z)
+                          (gl-color r g b)
+                          (gl-vertex x- y- z)
+                          (gl-vertex x+ y- z)
+                          (gl-vertex x+ y+ z)
+                          (gl-vertex x- y+ z)))))
+
+(define (update-quads)
+  (unpack-each
+   *particles*
+   position
+   (lambda (n x y z vx vy vz)
+     (let ((r (/ (abs vx) 5))
+           (g (/ (abs vy) 5))
+           (b (/ (abs vz) 5))
+           (x- (- x 0.5))
+           (y- (- y 0.5))
+           (x+ (+ x 0.5))
+           (y+ (+ y 0.5)))
+       (pack *zquads* n zquad
+             r g b x- y- x+ y+ z)))))
 
 (define (update-particles dt)
-  (define (update-particle i updater)
-    (let ((offset (* i floats-per-particle)))
-      (call-with-values (lambda ()
-                          (updater (f32vector-ref *particles* (+ offset 0))
-                                   (f32vector-ref *particles* (+ offset 1))
-                                   (f32vector-ref *particles* (+ offset 2))
-                                   (f32vector-ref *particles* (+ offset 3))
-                                   (f32vector-ref *particles* (+ offset 4))
-                                   (f32vector-ref *particles* (+ offset 5))))
-        (lambda (x y z vx vy vz)
-          (f32vector-set! *particles* (+ offset 0) x)
-          (f32vector-set! *particles* (+ offset 1) y)
-          (f32vector-set! *particles* (+ offset 2) z)
-          (f32vector-set! *particles* (+ offset 3) vx)
-          (f32vector-set! *particles* (+ offset 4) vy)
-          (f32vector-set! *particles* (+ offset 5) vz)))))
-
   (let ((half-dt-squared (* 0.5 dt dt)))
-    (let lp ((i 0))
-      (when (< i *n-particles*)
-        (update-particle
-         i
-         (lambda (x y z vx vy vz)
-           (let* ((distance-squared (+ (* x x) (* y y) (* z z)))
-                  (distance (sqrt distance-squared))
-                  (F (/ -200 distance-squared))
-                  (ax (* F (/ x distance)))
-                  (ay (* F (/ y distance)))
-                  (az (* F (/ z distance))))
-             (values (+ x (* vx dt) (* ax half-dt-squared))
-                     (+ y (* vy dt) (* ay half-dt-squared))
-                     (+ z (* vz dt) (* az half-dt-squared))
-                     (+ vx (* ax dt))
-                     (+ vy (* ay dt))
-                     (+ vz (* az dt))))))
-        (lp (1+ i))))))
+    (repack-each
+     *particles*
+     position
+     (lambda (n x y z vx vy vz)
+       (let* ((distance-squared (+ (* x x) (* y y) (* z z)))
+              (distance (sqrt distance-squared))
+              (F (/ -200 distance-squared))
+              (ax (* F (/ x distance)))
+              (ay (* F (/ y distance)))
+              (az (* F (/ z distance))))
+         (values (+ x (* vx dt) (* ax half-dt-squared))
+                 (+ y (* vy dt) (* ay half-dt-squared))
+                 (+ z (* vz dt) (* az half-dt-squared))
+                 (+ vx (* ax dt))
+                 (+ vy (* ay dt))
+                 (+ vz (* az dt))))))))
 
 (define (prepare-particles n)
-  (define (ensure-capacity! n)
-    (define (next-power-of-two n)
-      (let lp ((pot 1))
-        (if (<= n pot)
-            pot
-            (lp (* pot 2)))))
-    (let ((capacity (next-power-of-two n)))
-      (when (< (f32vector-length *particles*) (* capacity floats-per-particle))
-        (set! *particles*
-              (make-f32vector (* capacity floats-per-particle) 0)))))
-  
-  (define (prepare-particle offset)
-    ;; Position.
-    (f32vector-set! *particles* (+ offset 0) (* (random:normal) 30))
-    (f32vector-set! *particles* (+ offset 1) (* (random:normal) 30))
-    (f32vector-set! *particles* (+ offset 2) (* (random:normal) 30))
-
-    ;; Velocity.
-    (f32vector-set! *particles* (+ offset 3) (* (random:normal) 2))
-    (f32vector-set! *particles* (+ offset 4) (* (random:normal) 2))
-    (f32vector-set! *particles* (+ offset 5) (* (random:normal) 2)))
-
-  (ensure-capacity! n)
-  (set! *n-particles* n)
-
-  (let lp ((i 0))
-    (when (< i *n-particles*)
-      (prepare-particle (* i floats-per-particle))
-      (lp (1+ i)))))
+  (set! *particles* (make-packed-array position n))
+  (set! *zquads* (make-packed-array zquad n))
+  (pack-each
+   *particles*
+   position
+   (lambda (n)
+     (values
+      ;; Position.
+      (* (random:normal) 30)
+      (* (random:normal) 30)
+      (* (random:normal) 30)
+
+      ;; Velocity.
+      (* (random:normal) 2)
+      (* (random:normal) 2)
+      (* (random:normal) 2))))
+  (update-quads))
 
 (define main-window #f)
 
 
   (draw-particles)
 
-  ;; With double-buffering, swap-buffers will wait for the frame rate.
+  ;; With double-buffering, swap-buffers will wait for the frame to be shown,
+  ;; which limits this program to the frame rate.
   (swap-buffers))
 
 (define (on-reshape width height)
       ((#\esc #\etx #\q)
        (format #t "~s pressed; quitting.\n" c)
        (exit))
+      ((#\+)
+       ;; The rotations are a hack to re-orient so that a translation in the Z
+       ;; axis will move us towards the origin.
+       (gl-rotate -10 1 0 0)
+       (gl-translate 0 0 10)
+       (gl-rotate 10 1 0 0))
+      ((#\-)
+       (gl-rotate -10 1 0 0)
+       (gl-translate 0 0 -10)
+       (gl-rotate 10 1 0 0))
       ((#\space)
        (set! running? (not running?))
        (set-idle-callback (and running? (lambda () (on-idle)))))
 
 (define (on-idle)
   (set-gl-matrix-mode (matrix-mode modelview))
-  ;; rotate the camera 0.1 degree per frame; at 60 fps that's 60 seconds per
-  ;; full rotation
-  #;
-  (gl-rotate 0.1 0 1 0)
+  ;; Rotate the camera 0.05 degree per frame about the Z axis.  At 60 fps that's
+  ;; 120 seconds per full rotation.  Rotating about the Z axis keeps all the
+  ;; quads facing our way.
+  (gl-rotate 0.05 0 0 1)
   (update-particles 0.016)
+  (update-quads)
   (post-redisplay main-window))
 
 (define (register-glut-callbacks)
 (define (main args)
   (let ((args
          (initialize-glut args
-                          #:window-size '(320 . 200)
+                          #:window-size '(640 . 480)
                           #:display-mode (display-mode rgba alpha double
                                                        depth))))
     (set! main-window (make-window "particle-system"))
     (set-gl-shade-model (shading-model smooth))
 
     ;; Resetting the modelview matrix mode leaves the camera at the origin,
-    ;; oriented to look down the Z axis.  Move the camera away 100 units so that
-    ;; it can see the origin, and rotate it into the first octant so that it can
-    ;; see all the axes.
+    ;; oriented to look down the Z axis.  Rotate up a bit so that we can see the
+    ;; Z axis.
     (set-gl-matrix-mode (matrix-mode modelview))
     (gl-load-identity)
     (gl-translate 0 0 -100)
-    (gl-rotate 30 1 0 0)
-    (gl-rotate -15 0 1 0)
+    (gl-rotate 10 1 0 0)
+
+    (gl-enable (enable-cap depth-test))
 
     (set! *random-state* (random-state-from-platform))
     (prepare-particles (match args