b9e8e689f5ca756c8d430a1bda96e7c3669042f3
[clnl] / src / main / interface.lisp
1 (in-package #:clnl-interface)
2
3 (defvar *turtle-lists* nil)
4 (defvar *patch-list* nil)
5
6 (defvar *glut-window-opened* nil)
7 (defvar *dimensions* nil) ; This is a useful placeholder for other view properties
8 (defvar *window-width* 1024)
9 (defvar *window-height* 768)
10
11 (defvar *default-shapes* nil)
12
13 (defvar *textbox* nil)
14 (defvar *inputbox* nil)
15
16 (defvar *widgets* nil) ; this is going to be pairs to save the original definition
17
18 ; This is the thread that does the work of querying the currently running system to update
19 ; the interface state.  We keep it seperate from the main system display thread for two reasons:
20 ; 1) It should run even if there's no active display
21 ; 2) We want it to run slower than the current display thread
22 (defvar *interface-thread* nil)
23
24 (defgeneric update-widget (type def widget extra-info))
25 (defmethod update-widget (type def widget extra-info))
26
27 (defmethod update-widget ((type (eql :button)) button-def button idx)
28  (when (getf button-def :forever)
29   (clnl-gltk:toggle button (clnl-model:forever-button-on (getf button-def :display) idx))))
30
31 (defun update-interface ()
32  (mapcar
33   (lambda (widget) (apply #'update-widget widget))
34   *widgets*))
35
36 (defun boot-interface-thread ()
37  (when (not *interface-thread*)
38   (setf *interface-thread*
39    (sb-thread:make-thread (lambda () (loop (update-interface) (sleep .1))) :name "Interface Thread"))))
40
41 ; For now, shapes can live in here
42 ; header is
43 ; * name <like default>
44 ; * rotatable (equal to "true" if yes)
45 ;
46 ; then after, the elements are like so:
47 ;
48 ; filled == filled in (always for now, ha)
49 ; marked == use the turtle color instead of a color
50 ; polygon -> Polygon <color> <filled> <marked> <alternating x y coords>
51 ; circle -> Circle <color> <filled> <marked> <left> <top> <diameter> ; here, the left and top are NOT the center
52 ; rectangle -> Rectangle <color> <filled> <marked> <left> <top> <right> <bottom>
53 ;
54 ; then ends with an empty string
55
56 (defun parse-circle (sections)
57  (list :circle
58   :color (parse-integer (car sections))
59   :filled (string= (nth 1 sections) "true")
60   :marked (string= (nth 2 sections) "true")
61   :left (parse-integer (nth 3 sections))
62   :top (parse-integer (nth 4 sections))
63   :diameter (parse-integer (nth 5 sections))))
64
65 (defun parse-rectangle (sections)
66  (list
67   :rectangle
68   :color (parse-integer (car sections))
69   :filled (string= (nth 1 sections) "true")
70   :marked (string= (nth 2 sections) "true")
71   :left (parse-integer (nth 3 sections))
72   :top (parse-integer (nth 4 sections))
73   :right (parse-integer (nth 5 sections))
74   :bottom (parse-integer (nth 6 sections))))
75
76 (defun parse-polygon (sections)
77  (labels
78   ((parse-points (sections)
79     (when sections
80      (cons
81       (list (- 300 (parse-integer (car sections))) (parse-integer (cadr sections)))
82       (parse-points (cddr sections))))))
83   (list
84    :polygon
85    :color (parse-integer (car sections))
86    :filled (string= (nth 1 sections) "true")
87    :marked (string= (nth 2 sections) "true")
88    :coords (parse-points (nthcdr 3 sections)))))
89
90 (defun parse-shape (str)
91  (labels
92   ((parse-element (line)
93     (let
94      ((sections (cl-ppcre:split " " line)))
95      (cond
96       ((string= (car sections) "Circle") (parse-circle (cdr sections)))
97       ((string= (car sections) "Rectangle") (parse-rectangle (cdr sections)))
98       ((string= (car sections) "Polygon") (parse-polygon (cdr sections))))))
99    (parse-elements ()
100     (let
101      ((line (read-line str nil)))
102      (when (and line (string/= line ""))
103       (cons
104        (parse-element line)
105        (parse-elements))))))
106   (let
107    ((next-line (read-line str nil)))
108    (when next-line
109     (list
110      :name next-line
111      :rotatable (string= "true" (read-line str))
112      :rgb (read-line str) ; this is ignored for now, I think
113      :elements (parse-elements))))))
114
115 ; Clipping ears algorithm.  This can be slow due to the fact that it will only be run once.
116 (defun triangulate (points &optional (ccw :unknown))
117  (labels
118   ((tri-is-ccw (x y z)
119     (< 0 (- (* (- (car y) (car x)) (- (cadr z) (cadr x))) (* (- (car z) (car x)) (- (cadr y) (cadr x))))))
120    (tri-is-concave (x y z) (if (tri-is-ccw x y z) (not ccw) ccw))
121    (poly-is-ccw (points &optional cur-tri)
122     (cond
123      ((not cur-tri)
124       (poly-is-ccw (append points (list (car points))) (list (car (last points)) (car points) (cadr points))))
125      ((eql (length points) 2)
126       (apply #'tri-is-ccw cur-tri))
127      ((or
128        (< (car (cadr points)) (car (cadr cur-tri)))
129        (and
130         (= (car (cadr points)) (car (cadr cur-tri)))
131         (< (cadr (cadr points)) (cadr (cadr cur-tri)))))
132       (poly-is-ccw (cdr points) (subseq points 0 3)))
133      (t (poly-is-ccw (cdr points) cur-tri))))
134    (point-in-tri (x y z p)
135     ; Barycentric system test
136     (let*
137      ((denom (+ (* (- (cadr y) (cadr z)) (- (car x) (car z))) (* (- (car z) (car y)) (- (cadr x) (cadr z)))))
138       (a (/ (+ (* (- (cadr y) (cadr z)) (- (car p) (car z))) (* (- (car z) (car y)) (- (cadr p) (cadr z)))) denom))
139       (b (/ (+ (* (- (cadr z) (cadr x)) (- (car p) (car z))) (* (- (car x) (car z)) (- (cadr p) (cadr z)))) denom))
140       (c (- 1 a b)))
141      (and (<= 0 a 1) (<= 0 b 1) (<= 0 c 1))))
142    (no-points-in-tri (tri points)
143     (every (lambda (point) (not (point-in-tri (car tri) (cadr tri) (caddr tri) point))) points))
144    (tri-is-actually-line (x y z)
145     (zerop (+ (* (- (cadr y) (cadr z)) (- (car x) (car z))) (* (- (car z) (car y)) (- (cadr x) (cadr z)))))))
146   (cond
147    ((not (find :end points)) (triangulate (append points (list :end)) ccw))
148    ((< (length points) 4) (error "Must have at least 3 points..."))
149    ((= (length points) 4) (list (remove :end points)))
150    ((eql ccw :unknown) (triangulate points (poly-is-ccw (remove :end points))))
151    ((eql :end (car points)) (error "This polygon may not be triangulateable"))
152    (t
153     (let*
154      ((endless (remove :end points))
155       (tri (subseq endless 0 3)))
156      (cond
157       ((apply #'tri-is-actually-line tri)
158        (triangulate (cons (car endless) (cddr endless)) ccw))
159       ((apply #'tri-is-concave tri)
160        (triangulate (append (cdr points) (list (car points))) ccw))
161       ((no-points-in-tri tri (nthcdr 3 endless))
162        (cons tri (triangulate (cons (car endless) (cddr endless)) ccw)))
163       (t (triangulate (append (cdr points) (list (car points))) ccw))))))))
164
165 (defun element->gl-list (shape)
166  (progn
167   (when (not (getf (cdr shape) :marked))
168    (gl:push-attrib :all-attrib-bits)
169    (gl:color
170     (/ (ash (ldb (byte 24 0) (getf (cdr shape) :color)) -16) 255)
171     (/ (ash (ldb (byte 16 0) (getf (cdr shape) :color)) -8) 255)
172     (/ (ldb (byte 8 0) (getf (cdr shape) :color)) 255)))
173   (gl:begin :triangles)
174   (case (car shape)
175    (:polygon
176     (mapcar
177      (lambda (point) (gl:vertex (car point) (cadr point) 0))
178      (apply #'append (triangulate (getf (cdr shape) :coords)))))
179    (:rectangle
180     (mapcar
181      (lambda (point) (gl:vertex (car point) (cadr point) 0))
182      (apply #'append
183       (triangulate
184        (list
185         (list (- 300 (getf (cdr shape) :left)) (getf (cdr shape) :top))
186         (list (- 300 (getf (cdr shape) :right)) (getf (cdr shape) :top))
187         (list (- 300 (getf (cdr shape) :right)) (getf (cdr shape) :bottom))
188         (list (- 300 (getf (cdr shape) :left)) (getf (cdr shape) :bottom)))))))
189    (:circle
190     (mapcar
191      (lambda (point) (gl:vertex (car point) (cadr point) 0))
192      (apply #'append
193       (triangulate
194        (loop
195         :repeat 360
196         :with c := (strictmath:cos (strictmath:to-radians 1))
197         :with s := (strictmath:sin (strictmath:to-radians 1))
198         :with r := (/ (getf (cdr shape) :diameter) 2)
199         :with left := (getf (cdr shape) :left)
200         :with top := (getf (cdr shape) :top)
201         :for n := 0 :then x
202         :for x := r :then (- (* c x) (* s y))
203         :for y := 0 :then (+ (* s n) (* c y))
204         :collect (list (- 300 (+ (+ x left) r)) (+ (+ y top) r))))))))
205   (gl:end)
206   (when (not (getf (cdr shape) :marked))
207    (gl:pop-attrib))))
208
209 (defun parse-shapes (str)
210  (let
211   ((shape (parse-shape str)))
212   (when shape (cons shape (parse-shapes str)))))
213
214 (defun default-shapes ()
215  (with-open-file (str "resources/defaultshapes") (parse-shapes str)))
216
217 (eval-when (:load-toplevel)
218  (when (probe-file "resources/defaultshapes")
219   (setf *default-shapes* (default-shapes))))
220
221 (defvar *colors*
222  '((140 140 140) ; gray       (5)
223    (215 48 39) ; red       (15)
224    (241 105 19) ; orange    (25)
225    (156 109 70) ; brown     (35)
226    (237 237 47) ; yellow    (45)
227    (87 176 58) ; green     (55)
228    (42 209 57) ; lime      (65)
229    (27 158 119) ; turquoise (75)
230    (82 196 196) ; cyan      (85)
231    (43 140 190) ; sky       (95)
232    (50 92 168) ; blue     (105)
233    (123 78 163) ; violet   (115)
234    (166 25 105) ; magenta  (125)
235    (224 126 149) ; pink     (135)
236    (0 0 0) ; black
237    (255 255 255))) ; white
238
239 (defun nl-color->rgb (color)
240  (let*
241   ((step (+ (/ (- (mod (floor (* color 10)) 100) 50) 50.48) 0.012)))
242   (mapcar
243    (lambda (x) (/ (+ x (floor (* (if (< step 0d0) x (- 255 x)) step))) 255))
244    (nth (floor color 10) *colors*))))
245
246 (defun render-scene ()
247  (gl:matrix-mode :projection)
248  (gl:with-pushed-matrix
249   (gl:load-identity)
250   (gl:ortho
251    (floor (* (- (getf *dimensions* :xmin) 0.5) (patch-size)))
252    (floor (* (+ (getf *dimensions* :xmax) 0.5) (patch-size)))
253    (floor (* (- (getf *dimensions* :ymin) 0.5) (patch-size)))
254    (floor (* (+ (getf *dimensions* :ymax) 0.5) (patch-size)))
255    0 5000)
256   (gl:matrix-mode :modelview)
257   (gl:with-pushed-matrix
258    (gl:load-identity)
259    (destructuring-bind (turtles patches) (clnl-nvm:current-state)
260     (mapcar
261      (lambda (patch)
262       (let
263        ((color (nl-color->rgb (getf patch :color))))
264        (gl:color (car color) (cadr color) (caddr color)))
265       (gl:with-pushed-matrix
266        (gl:translate (* (getf patch :xcor) (patch-size)) (* (getf patch :ycor) (patch-size)) 0)
267        (gl:translate (floor (* -.5d0 (patch-size))) (floor (* -.5d0 (patch-size))) 0)
268        (gl:scale (patch-size) (patch-size) 1)
269        (gl:call-list *patch-list*)))
270      patches)
271     (mapcar
272      (lambda (turtle)
273       (let
274        ((color (nl-color->rgb (getf turtle :color))))
275        (gl:color (car color) (cadr color) (caddr color)))
276       (mapcar
277        (lambda (x-modification y-modification)
278         (gl:with-pushed-matrix
279          (gl:translate (* (getf turtle :xcor) (patch-size)) (* (getf turtle :ycor) (patch-size)) 0)
280          (gl:translate x-modification y-modification 0)
281          (let
282           ((turtle-list (find (getf turtle :shape) *turtle-lists* :test #'string= :key #'car)))
283           (when turtle-list
284            (when (second turtle-list)
285             (gl:rotate (getf turtle :heading) 0 0 -1))
286            (gl:scale (patch-size) (patch-size) 1)
287            (gl:scale (getf turtle :size) (getf turtle :size) 1)
288            (gl:call-list (third turtle-list))))))
289        (list 0 (1- (world-width-in-pixels)) (- (1- (world-width-in-pixels))) 0 0)
290        (list 0 0 0 (1- (world-height-in-pixels)) (- (1- (world-height-in-pixels))))))
291      turtles)))
292   (gl:matrix-mode :projection))
293  (gl:flush))
294
295 (defun render-widgets ()
296  (clnl-gltk:render *textbox*)
297  (clnl-gltk:render *inputbox*)
298  (mapcar #'clnl-gltk:render (mapcar #'third *widgets*)))
299
300 (defun render ()
301  (gl:clear :color-buffer-bit :depth-buffer-bit)
302  (let
303   ((width (world-width-in-pixels))
304    (height (world-height-in-pixels)))
305   (gl:viewport 0 0 *window-width* *window-height*)
306
307   (gl:matrix-mode :projection)
308   (let*
309    ((left (getf *dimensions* :left))
310     (top (getf *dimensions* :top))
311     (view-x1 left)
312     (view-x2 (+ view-x1 width))
313     (view-y1 (- *window-height* height top))
314     (view-y2 (- *window-height* top)))
315
316    (gl:with-pushed-matrix
317     (gl:load-identity)
318     (gl:ortho 0 *window-width* 0 *window-height* 0 5000)
319     (render-widgets)
320
321     (gl:color 1 1 1)
322     (gl:begin :lines)
323     (gl:vertex view-x1 view-y1)
324     (gl:vertex view-x1 (+ view-y2 1))
325
326     (gl:vertex view-x1 (+ view-y2 1))
327     (gl:vertex (+ view-x2 1) (+ view-y2 1))
328
329     (gl:vertex (+ view-x2 1) (+ view-y2 1))
330     (gl:vertex (+ view-x2 1) (- view-y1 1))
331
332     (gl:vertex (+ view-x2 1) view-y1)
333     (gl:vertex (- view-x1 1) view-y1)
334     (gl:end))
335
336    (gl:viewport view-x1 view-y1 width height)
337    (render-scene))))
338
339 (defun display ()
340  (render)
341  (cl-glut:swap-buffers))
342
343 (defun idle ()
344  (cl-glut:post-redisplay))
345
346 (defun close-func ()
347  ;(glut:leave-main-loop)
348  (sb-ext:exit :abort t))
349
350 (defun reshape (width height)
351  (when (and (/= 0 width) (/= 0 height))
352   (setf *window-width* width)
353   (setf *window-height* height)
354   (let
355    ((box-width (truncate (- width 12) clnl-gltk:*font-width*)))
356    (clnl-gltk:resize *textbox* box-width 12)
357    (clnl-gltk:resize *inputbox* box-width 1))
358   (mapcar
359    (lambda (widget)
360     (clnl-gltk:reposition (third widget)
361      (getf (second widget) :left)
362      (- *window-height* (getf (second widget) :height) (getf (second widget) :top))))
363    *widgets*)))
364
365 (defun execute (str)
366  (setf
367   (clnl-gltk:textbox-text *textbox*)
368   (format nil "> ~A~%~%~A" str
369    (handler-case
370     (with-output-to-string (*standard-output*)
371      (clnl:run-commands str))
372     (error (e) (format nil "Ok, something went wrong: ~A~%Try :help" e))))))
373
374 (defun key-pressed (key x y)
375  (declare (ignore x y))
376  (if (eql key 13)
377   (progn
378    (execute (clnl-gltk:value *inputbox*))
379    (clnl-gltk:clear *inputbox*))
380   (clnl-gltk:key-pressed *inputbox* key)))
381
382 (defun mouse (button state x y)
383  (declare (ignore button))
384  (mapcar
385   (lambda (w)
386    (when (eql state :down) (clnl-gltk:mousedown w x (- *window-height* y)))
387    (when (eql state :up) (clnl-gltk:mouseup w x (- *window-height* y))))
388   (mapcar #'third *widgets*)))
389
390 (defun motion (x y)
391  (mapcar
392   (lambda (w) (clnl-gltk:mousemove w x (- *window-height* y)))
393   (mapcar #'third *widgets*)))
394
395 (cffi:defcallback display :void () (display))
396 (cffi:defcallback idle :void () (idle))
397 (cffi:defcallback close-func :void () (close-func))
398 (cffi:defcallback reshape :void ((width :int) (height :int)) (reshape width height))
399 (cffi:defcallback key-pressed :void ((key :uchar) (x :int) (y :int)) (key-pressed key x y))
400 (cffi:defcallback special-key-pressed :void ((key glut:special-keys) (x :int) (y :int)) (key-pressed key x y))
401 (cffi:defcallback mouse :void ((button cl-glut:mouse-button) (state cl-glut:mouse-button-state) (x :int) (y :int))
402  (mouse button state x y))
403
404 (cffi:defcallback motion :void ((x :int) (y :int)) (motion x y))
405
406 (defun set-turtle-lists ()
407  (setf
408   *turtle-lists*
409   (mapcar
410    (lambda (shape)
411     (let
412      ((turtle-list
413        (list
414         (getf shape :name)
415         (getf shape :rotatable)
416         (gl:gen-lists 1))))
417      (gl:with-new-list ((third turtle-list) :compile)
418       (gl:rotate 180d0 0d0 0d0 -1d0)
419       (gl:scale (/ 1d0 300d0) (/ 1d0 300d0) 1)
420       (gl:translate -150d0 -150d0 -0.0d0)
421       (mapcar #'element->gl-list (getf shape :elements)))
422      turtle-list))
423    (or *default-shapes* (default-shapes)))))
424
425 (defun set-patch-list ()
426  (setf *patch-list* (gl:gen-lists 1))
427  (gl:with-new-list (*patch-list* :compile)
428   (gl:begin :polygon)
429   (gl:vertex 0 0 0)
430   (gl:vertex 0 1 0)
431   (gl:vertex 1 1 0)
432   (gl:vertex 1 0 0)
433   (gl:end)))
434
435 (defvar *initial-banner*
436  "
437      / \\
438     /   \\     Welcome to CLNL version ~A!
439    /     \\
440   /_______\\
441
442 CLNL is an experiment at creating an alternate implementation of NetLogo.
443
444 You can enter in various netlogo commands below, or use :q to quit the program.
445
446 See http://github.com/frankduncan/clnl for more information about CLNL and to
447 keep apprised of any updates that may happen.")
448
449 (defun button-defs->buttons (button-defs)
450  (let
451   ((known-button-names nil))
452   (mapcar
453    (lambda (button-def)
454     (let*
455      ((idx (length (remove (getf button-def :display) known-button-names :test-not #'equal)))
456       (toggle-button nil)
457       (button
458        (clnl-gltk:button
459         (getf button-def :left)
460         (- *window-height* (getf button-def :height) (getf button-def :top))
461         (getf button-def :width)
462         (getf button-def :height)
463         (getf button-def :display)
464         (lambda ()
465          (when toggle-button (funcall toggle-button))
466          (execute
467           (format nil ":button \"~A\"~A"
468            (getf button-def :display)
469            (if (zerop idx) "" (format nil " ~A" idx)))))
470         :forever (getf button-def :forever))))
471      (push (getf button-def :display) known-button-names)
472      (when (getf button-def :forever) (setf toggle-button (lambda () (clnl-gltk:toggle button))))
473      (list :button button-def button idx)))
474    button-defs)))
475
476 (defun initialize (&key dims view buttons)
477  "INITIALIZE &key DIMS VIEW BUTTONS => RESULT
478
479   DIMS: (:xmin XMIN :xmax XMAX :ymin YMIN :ymax YMAX :patch-size PATCH-SIZE)
480   VIEW: (:left LEFT :top TOP)
481   BUTTONS: BUTTON-DEF*
482   BUTTON-DEF: (:left LEFT :top TOP :height HEIGHT :width WIDTH :forever FOREVER :display DISPLAY)
483
484 ARGUMENTS AND VALUES:
485
486   RESULT: undefined
487   XMIN: An integer representing the minimum patch coord in X
488   XMAX: An integer representing the maximum patch coord in X
489   YMIN: An integer representing the minimum patch coord in Y
490   YMAX: An integer representing the maximum patch coord in Y
491   PATCH-SIZE: A double representing the size of the patches in pixels
492   LEFT: An integer representing the left position
493   TOP: An integer representing the top position
494   HEIGHT: An integer representing height
495   WIDTH: An integer representing width
496   FOREVER: A boolean representing the forever status
497   DISPLAY: A string representing display name
498
499 DESCRIPTION:
500
501   This is where the initialization of the interface that sits behind
502   the interface lives.  From here, one can go into headless or running
503   mode, but for certain things this interface will still need to act,
504   and also allows for bringing up and taking down of visual elements."
505  (boot-interface-thread)
506  (setf *dimensions* (append dims view))
507  (setf *widgets*
508   (button-defs->buttons buttons)))
509
510 (defun run ()
511  "RUN => RESULT
512
513 ARGUMENTS AND VALUES:
514
515   RESULT: undefined, should never get here
516
517 DESCRIPTION:
518
519   RUN runs the view in an external window.
520
521   This should be run inside another thread as it starts the glut main-loop.
522   Closing this window will then cause the entire program to terminate."
523  ; I do this because I don't know who or what in the many layers
524  ; is causing the floating point errors, but I definitely don't
525  ; want to investigate until simply ignoring them becomes a problem.
526  (sb-int:with-float-traps-masked (:invalid :inexact :divide-by-zero :overflow :underflow)
527   (cl-glut:init)
528   (cl-glut:init-window-size *window-width* *window-height*)
529   (cl-glut:init-display-mode :double :rgba)
530   (cl-glut:create-window "CLNL Test Window")
531   (setf *glut-window-opened* t)
532   (gl:clear-color 0 0 0 1)
533   (cl-glut:display-func (cffi:get-callback 'display))
534   (glut:reshape-func (cffi:callback reshape))
535   (cl-glut:idle-func (cffi:get-callback 'idle))
536   (cl-glut:close-func (cffi:get-callback 'close-func))
537   (cl-glut:keyboard-func (cffi:get-callback 'key-pressed))
538   (cl-glut:special-func (cffi:get-callback 'special-key-pressed))
539   (cl-glut:motion-func (cffi:get-callback 'motion)) ; while mouse is down
540   (cl-glut:passive-motion-func (cffi:get-callback 'motion)) ; while mouse is up
541   (cl-glut:mouse-func (cffi:get-callback 'mouse)) ; state is up/down, button is button
542   (gl:depth-func :lequal)
543   (gl:blend-func :src-alpha :one-minus-src-alpha)
544   (gl:enable :blend)
545   (set-turtle-lists)
546   (set-patch-list)
547   (clnl-gltk:setup)
548   (setf *textbox*
549    (clnl-gltk:textbox
550     5 (+ clnl-gltk:*font-height* 14)
551     10 12
552     (format nil *initial-banner* (asdf:component-version (asdf:find-system :clnl)))))
553   (setf *inputbox* (clnl-gltk:inputbox 5 5 10))
554   (cl-glut:main-loop)))
555
556 (defun patch-size () (getf *dimensions* :patch-size))
557
558 (defun world-width-in-pixels ()
559  (floor (* (patch-size) (1+ (- (getf *dimensions* :xmax) (getf *dimensions* :xmin))))))
560
561 (defun world-height-in-pixels ()
562  (floor (* (patch-size) (1+ (- (getf *dimensions* :ymax) (getf *dimensions* :ymin))))))
563
564 (defun export-view ()
565  "EXPORT-VIEW => IMAGE-DATA
566
567 ARGUMENTS AND VALUES:
568
569   IMAGE-DATA: A vector, pixel data as returned by opengls readPixels
570
571 DESCRIPTION:
572
573   EXPORT-VIEW returns the current view in raw data of RGBA pixels.
574
575   Each pixel is made up of 4 bytes of data, which an be walked over.  The number
576   of pixels is the current width x height.  Converting to some other image format
577   is a matter of pulling that information out and putting it into whatever format
578   you like.
579
580   This requires opengl to run, but can be used with xvfb in a headless mode."
581  (sb-int:with-float-traps-masked (:invalid)
582   (when (not *glut-window-opened*)
583    (cl-glut:init)
584    (cl-glut:init-window-size 1 1)
585    (cl-glut:create-window "CLNL Test Window")
586    (gl:clear-color 0 0 0 1)
587    (set-turtle-lists)
588    (set-patch-list)
589    (setf *glut-window-opened* t))
590   (let
591    ((fbo (first (gl:gen-framebuffers 1)))
592     (render-buf (first (gl:gen-renderbuffers 1)))
593     (width (world-width-in-pixels))
594     (height (world-height-in-pixels)))
595    (gl:bind-framebuffer :framebuffer fbo)
596    (gl:bind-renderbuffer :renderbuffer render-buf)
597    (gl:renderbuffer-storage :renderbuffer :rgba8 width height)
598    (gl:framebuffer-renderbuffer :draw-framebuffer :color-attachment0 :renderbuffer render-buf)
599    (gl:viewport 0 0 width height)
600    (render-scene)
601    (gl:read-pixels 0 0 width height :rgba :unsigned-byte))))