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