Commit | Line | Data |
---|---|---|
c64c0f81 AW |
1 | #!/usr/bin/env guile |
2 | !# | |
3 | ||
62c85b51 DH |
4 | ;;; figl |
5 | ;;; Copyright (C) 2013 Andy Wingo <wingo@pobox.com> | |
6 | ;;; | |
7 | ;;; Figl is free software: you can redistribute it and/or modify it | |
8 | ;;; under the terms of the GNU Lesser General Public License as | |
9 | ;;; published by the Free Software Foundation, either version 3 of the | |
10 | ;;; License, or (at your option) any later version. | |
11 | ;;; | |
12 | ;;; Figl is distributed in the hope that it will be useful, but WITHOUT | |
13 | ;;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY | |
14 | ;;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General | |
15 | ;;; Public License for more details. | |
16 | ;;; | |
17 | ;;; You should have received a copy of the GNU Lesser General Public | |
18 | ;;; License along with this program. If not, see | |
19 | ;;; <http://www.gnu.org/licenses/>. | |
20 | ||
c64c0f81 AW |
21 | (use-modules (figl glut) |
22 | (figl gl) | |
23 | (figl glu) | |
24 | (ice-9 match) | |
25 | (ice-9 format) | |
26 | (figl contrib packed-struct)) | |
27 | ||
28 | (define-packed-struct color-vertex | |
29 | (x float) | |
30 | (y float) | |
31 | (z float) | |
32 | ||
33 | (r float) | |
34 | (g float) | |
35 | (b float)) | |
36 | ||
37 | (define-packed-struct particle | |
38 | (x float) | |
39 | (y float) | |
40 | (z float) | |
41 | (vx float) | |
42 | (vy float) | |
43 | (vz float)) | |
44 | ||
45 | (define *vertices* (make-packed-array color-vertex 0)) | |
46 | (define *particles* (make-packed-array particle 0)) | |
47 | ||
48 | ;; Vertex buffer object. | |
49 | (define *vbo* 0) | |
50 | ||
51 | (define (draw-particles) | |
52 | (gl-bind-buffer (version-1-5 array-buffer) *vbo*) | |
53 | ||
54 | (update-gl-buffer-data (version-1-5 array-buffer) | |
55 | *vertices*) | |
56 | ||
57 | (gl-enable-client-state (enable-cap vertex-array)) | |
58 | (gl-enable-client-state (enable-cap color-array)) | |
59 | (set-gl-vertex-array (vertex-pointer-type float) | |
60 | #f | |
61 | #:stride (packed-struct-size color-vertex) | |
62 | #:offset (packed-struct-offset color-vertex x)) | |
63 | (set-gl-color-array (color-pointer-type float) | |
64 | #f | |
65 | #:stride (packed-struct-size color-vertex) | |
66 | #:offset (packed-struct-offset color-vertex r)) | |
54ead4dd | 67 | (gl-draw-arrays (primitive-type quads) 0 |
c64c0f81 AW |
68 | (packed-array-length *vertices* color-vertex)) |
69 | (gl-disable-client-state (enable-cap color-array)) | |
70 | (gl-disable-client-state (enable-cap vertex-array)) | |
71 | ||
72 | (gl-bind-buffer (version-1-5 array-buffer) 0)) | |
73 | ||
74 | (define (update-quads) | |
75 | (unpack-each | |
76 | *particles* | |
77 | particle | |
78 | (lambda (n x y z vx vy vz) | |
79 | (let ((r (/ (abs vx) 5)) | |
80 | (g (/ (abs vy) 5)) | |
81 | (b (/ (abs vz) 5)) | |
82 | (x- (- x 0.5)) | |
83 | (y- (- y 0.5)) | |
84 | (x+ (+ x 0.5)) | |
85 | (y+ (+ y 0.5)) | |
86 | (base (* n 4))) | |
87 | (pack *vertices* base color-vertex | |
88 | x- y- z | |
89 | r g b) | |
90 | (pack *vertices* (+ base 1) color-vertex | |
91 | x+ y- z | |
92 | r g b) | |
93 | (pack *vertices* (+ base 2) color-vertex | |
94 | x+ y+ z | |
95 | r g b) | |
96 | (pack *vertices* (+ base 3) color-vertex | |
97 | x- y+ z | |
98 | r g b))))) | |
99 | ||
100 | (define (update-particles dt) | |
101 | (let ((half-dt-squared (* 0.5 dt dt))) | |
102 | (repack-each | |
103 | *particles* | |
104 | particle | |
105 | (lambda (n x y z vx vy vz) | |
106 | (let* ((distance-squared (+ (* x x) (* y y) (* z z))) | |
107 | (distance (sqrt distance-squared)) | |
108 | (F (/ -200 distance-squared)) | |
109 | (ax (* F (/ x distance))) | |
110 | (ay (* F (/ y distance))) | |
111 | (az (* F (/ z distance)))) | |
112 | (values (+ x (* vx dt) (* ax half-dt-squared)) | |
113 | (+ y (* vy dt) (* ay half-dt-squared)) | |
114 | (+ z (* vz dt) (* az half-dt-squared)) | |
115 | (+ vx (* ax dt)) | |
116 | (+ vy (* ay dt)) | |
117 | (+ vz (* az dt)))))))) | |
118 | ||
119 | (define (prepare-particles n) | |
120 | (set! *particles* (make-packed-array particle n)) | |
121 | (set! *vertices* (make-packed-array color-vertex (* n 4))) | |
122 | ||
123 | (pack-each | |
124 | *particles* | |
125 | particle | |
126 | (lambda (n) | |
127 | (values | |
128 | ;; Position. | |
129 | (* (random:normal) 30) | |
130 | (* (random:normal) 30) | |
131 | (* (random:normal) 30) | |
132 | ||
133 | ;; Velocity. | |
134 | (* (random:normal) 2) | |
135 | (* (random:normal) 2) | |
136 | (* (random:normal) 2)))) | |
137 | ||
138 | (update-quads) | |
139 | ||
140 | (gl-delete-buffer *vbo*) ; no-op if 0 | |
141 | (set! *vbo* (gl-generate-buffer)) | |
142 | ||
143 | (gl-bind-buffer (version-1-5 array-buffer) *vbo*) | |
144 | (set-gl-buffer-data (version-1-5 array-buffer) | |
145 | *vertices* | |
146 | (version-1-5 stream-draw)) | |
147 | (gl-bind-buffer (version-1-5 array-buffer) 0)) | |
148 | ||
149 | (define main-window #f) | |
150 | ||
151 | (define (make-fps-accumulator period) | |
152 | (let* ((frame-count 0) | |
153 | (last-fps-time (get-internal-real-time)) | |
154 | (last-fps-run-time (get-internal-run-time)) | |
155 | (last-frame-time (get-internal-real-time)) | |
156 | (max-frame-time (get-internal-real-time)) | |
157 | (last-frame-count 0) | |
158 | (jiffies-per-sec (exact->inexact internal-time-units-per-second)) | |
159 | (jiffies-per-ms (/ jiffies-per-sec 1000))) | |
160 | (lambda () | |
161 | (let ((now (get-internal-real-time))) | |
162 | (set! frame-count (1+ frame-count)) | |
163 | (when (> (- now last-frame-time) max-frame-time) | |
164 | (set! max-frame-time (- now last-frame-time))) | |
165 | (set! last-frame-time now) | |
166 | (when (> (- now last-fps-time) period) | |
167 | (let* ((run (get-internal-run-time)) | |
168 | (frames (- frame-count last-frame-count)) | |
169 | (secs (/ (- now last-fps-time) jiffies-per-sec)) | |
170 | (fps (/ frames secs)) | |
171 | (ms-per-frame (/ (* secs 1000) frames)) | |
172 | (max-ms-per-frame (/ max-frame-time jiffies-per-ms)) | |
173 | (cpu (* 100.0 (/ (- run last-fps-run-time) | |
174 | (- now last-fps-time))))) | |
175 | (format | |
176 | (current-error-port) | |
177 | ";;; ~a frames in ~,2f sec = ~,2f fps; ~,2f ms/frame, ~,2f ms max; ~,2f% CPU\n" | |
178 | frames secs fps ms-per-frame max-ms-per-frame cpu) | |
179 | (set! last-frame-count frame-count) | |
180 | (set! max-frame-time 0) | |
181 | (set! last-fps-time now) | |
182 | (set! last-fps-run-time run))))))) | |
183 | ||
184 | (define accumulate-fps! | |
185 | (make-fps-accumulator (* 2 internal-time-units-per-second))) | |
186 | ||
187 | (define (draw-axis scale) | |
188 | ;; Could disable lighting and depth test. | |
54ead4dd | 189 | (gl-begin (primitive-type lines) |
c64c0f81 AW |
190 | (gl-color 1 0 0) |
191 | (gl-vertex 0 0 0) | |
192 | (gl-vertex scale 0 0) | |
193 | ||
194 | (gl-color 0 1 0) | |
195 | (gl-vertex 0 0 0) | |
196 | (gl-vertex 0 scale 0) | |
197 | ||
198 | (gl-color 0 0 1) | |
199 | (gl-vertex 0 0 0) | |
200 | (gl-vertex 0 0 scale))) | |
201 | ||
202 | (define (on-display) | |
203 | (accumulate-fps!) | |
204 | (gl-clear (clear-buffer-mask color-buffer depth-buffer)) | |
205 | ||
206 | (draw-axis 10) | |
207 | ||
208 | (draw-particles) | |
209 | ||
210 | ;; With double-buffering, swap-buffers will wait for the frame to be shown, | |
211 | ;; which limits this program to the frame rate. | |
212 | (swap-buffers)) | |
213 | ||
214 | (define (on-reshape width height) | |
215 | (pk 'reshape! width height) | |
216 | (gl-viewport 0 0 width height) | |
217 | ||
218 | ;; Projection matrix. | |
219 | (set-gl-matrix-mode (matrix-mode projection)) | |
220 | (gl-load-identity) | |
221 | (glu-perspective 60 (/ width height) 0.1 1000)) | |
222 | ||
223 | (define full-screen? #f) | |
224 | (define running? #t) | |
225 | ||
226 | (define (on-keyboard keycode x y) | |
227 | (let ((c (integer->char keycode))) | |
228 | (case c | |
229 | ((#\f) | |
230 | (set! full-screen? (not full-screen?)) | |
231 | (full-screen main-window full-screen?)) | |
232 | ((#\esc #\etx #\q) | |
233 | (format #t "~s pressed; quitting.\n" c) | |
234 | (exit)) | |
235 | ((#\+) | |
236 | ;; The rotations are a hack to re-orient so that a translation in the Z | |
237 | ;; axis will move us towards the origin. | |
238 | (gl-rotate -10 1 0 0) | |
239 | (gl-translate 0 0 10) | |
240 | (gl-rotate 10 1 0 0)) | |
241 | ((#\-) | |
242 | (gl-rotate -10 1 0 0) | |
243 | (gl-translate 0 0 -10) | |
244 | (gl-rotate 10 1 0 0)) | |
245 | ((#\space) | |
246 | (set! running? (not running?)) | |
247 | (set-idle-callback (and running? (lambda () (on-idle))))) | |
248 | (else | |
249 | (pk 'keyboard c x y))))) | |
250 | ||
251 | (define (on-special keycode x y) | |
252 | (pk 'special keycode x y)) | |
253 | ||
254 | (define (on-mouse button state x y) | |
255 | (pk 'mouse button state x y)) | |
256 | ||
257 | (define (on-motion x y) | |
258 | (pk 'motion x y)) | |
259 | ||
260 | (define (on-visibility visible?) | |
261 | (pk 'visible visible?)) | |
262 | ||
263 | (define (on-idle) | |
264 | (set-gl-matrix-mode (matrix-mode modelview)) | |
265 | ;; Rotate the camera 0.05 degree per frame about the Z axis. At 60 fps that's | |
266 | ;; 120 seconds per full rotation. Rotating about the Z axis keeps all the | |
267 | ;; quads facing our way. | |
268 | (gl-rotate 0.05 0 0 1) | |
269 | (update-particles 0.016) | |
270 | (update-quads) | |
271 | (post-redisplay main-window)) | |
272 | ||
273 | (define (register-glut-callbacks) | |
274 | ;; The trampolines allow the handlers to be overridden at runtime by | |
275 | ;; an attached Guile REPL client. | |
276 | (set-display-callback (lambda () (on-display))) | |
277 | (set-reshape-callback (lambda (w h) (on-reshape w h))) | |
278 | (set-keyboard-callback (lambda (k x y) (on-keyboard k x y))) | |
279 | (set-special-callback (lambda (k x y) (on-special k x y))) | |
280 | (set-mouse-callback (lambda (b s x y) (on-mouse b s x y))) | |
281 | (set-motion-callback (lambda (x y) (on-motion x y))) | |
282 | (set-visibility-callback (lambda (visible?) (on-visibility visible?))) | |
283 | (set-idle-callback (lambda () (on-idle)))) | |
284 | ||
285 | (define (main args) | |
286 | (let ((args | |
287 | (initialize-glut args | |
288 | #:window-size '(640 . 480) | |
289 | #:display-mode (display-mode rgba alpha double | |
290 | depth)))) | |
291 | (set! main-window (make-window "particle-system")) | |
292 | (register-glut-callbacks) | |
293 | (set-gl-clear-color 0 0 0 1) | |
294 | (set-gl-clear-depth 1) | |
295 | #; | |
296 | (set-gl-shade-model (shading-model smooth)) | |
297 | ||
298 | ;; Resetting the modelview matrix mode leaves the camera at the origin, | |
299 | ;; oriented to look down the Z axis. Rotate up a bit so that we can see the | |
300 | ;; Z axis. | |
301 | (set-gl-matrix-mode (matrix-mode modelview)) | |
302 | (gl-load-identity) | |
303 | (gl-translate 0 0 -100) | |
304 | (gl-rotate 10 1 0 0) | |
305 | ||
306 | (gl-enable (enable-cap depth-test)) | |
307 | ||
308 | (set! *random-state* (random-state-from-platform)) | |
309 | (prepare-particles (match args | |
310 | ((_) 1000) | |
311 | ((_ n) (string->number n)))) | |
312 | ||
313 | (glut-main-loop))) | |
314 | ||
315 | (when (batch-mode?) | |
316 | (exit (main (program-arguments)))) |