Shapes - polygon, circle, rectangle
[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
9 ; For now, shapes can live in here
10 ; header is
11 ; * name <like default>
12 ; * rotatable (equal to "true" if yes)
13 ;
14 ; then after, the elements are like so:
15 ;
16 ; filled == filled in (always for now, ha)
17 ; marked == use the turtle color instead of a color
18 ; polygon -> Polygon <color> <filled> <marked> <alternating x y coords>
19 ; circle -> Circle <color> <filled> <marked> <left> <top> <diameter> ; here, the left and top are NOT the center
20 ; rectangle -> Rectangle <color> <filled> <marked> <left> <top> <right> <bottom>
21 ;
22 ; then ends with an empty string
23
24 (defun parse-circle (sections)
25  (list :circle
26   :color (parse-integer (car sections))
27   :filled (string= (nth 1 sections) "true")
28   :marked (string= (nth 2 sections) "true")
29   :left (parse-integer (nth 3 sections))
30   :top (parse-integer (nth 4 sections))
31   :diameter (parse-integer (nth 5 sections))))
32
33 (defun parse-rectangle (sections)
34  (list
35   :rectangle
36   :color (parse-integer (car sections))
37   :filled (string= (nth 1 sections) "true")
38   :marked (string= (nth 2 sections) "true")
39   :left (parse-integer (nth 3 sections))
40   :top (parse-integer (nth 4 sections))
41   :right (parse-integer (nth 5 sections))
42   :bottom (parse-integer (nth 6 sections))))
43
44 (defun parse-polygon (sections)
45  (labels
46   ((parse-points (sections)
47     (when sections
48      (cons
49       (list (parse-integer (car sections)) (parse-integer (cadr sections)))
50       (parse-points (cddr sections))))))
51   (list
52    :polygon
53    :color (parse-integer (car sections))
54    :filled (string= (nth 1 sections) "true")
55    :marked (string= (nth 2 sections) "true")
56    :coords (parse-points (nthcdr 3 sections)))))
57
58 (defun parse-shape (str)
59  (labels
60   ((parse-element (line)
61     (let
62      ((sections (cl-ppcre:split " " line)))
63      (cond
64       ((string= (car sections) "Circle") (parse-circle (cdr sections)))
65       ((string= (car sections) "Rectangle") (parse-rectangle (cdr sections)))
66       ((string= (car sections) "Polygon") (parse-polygon (cdr sections))))))
67    (parse-elements ()
68     (let
69      ((line (read-line str nil)))
70      (when (and line (string/= line ""))
71       (cons
72        (parse-element line)
73        (parse-elements))))))
74   (let
75    ((next-line (read-line str nil)))
76    (when next-line
77     (list
78      :name next-line
79      :rotatable (string= "true" (read-line str))
80      :rgb (read-line str) ; this is ignored for now, I think
81      :elements (parse-elements))))))
82
83 ; Clipping ears algorithm.  This can be slow due to the fact that it will only be run once.
84 (defun triangulate (points &optional (ccw :unknown))
85  (labels
86   ((tri-is-ccw (x y z)
87     (< 0 (- (* (- (car y) (car x)) (- (cadr z) (cadr x))) (* (- (car z) (car x)) (- (cadr y) (cadr x))))))
88    (tri-is-concave (x y z) (if (tri-is-ccw x y z) (not ccw) ccw))
89    (poly-is-ccw (points &optional cur-tri)
90     (cond
91      ((not cur-tri)
92       (poly-is-ccw (append points (list (car points))) (list (car (last points)) (car points) (cadr points))))
93      ((eql (length points) 2)
94       (apply #'tri-is-ccw cur-tri))
95      ((or
96        (< (car (cadr points)) (car (cadr cur-tri)))
97        (and
98         (= (car (cadr points)) (car (cadr cur-tri)))
99         (< (cadr (cadr points)) (cadr (cadr cur-tri)))))
100       (poly-is-ccw (cdr points) (subseq points 0 3)))
101      (t (poly-is-ccw (cdr points) cur-tri))))
102    (point-in-tri (x y z p)
103     ; Barycentric system test
104     (let*
105      ((denom (+ (* (- (cadr y) (cadr z)) (- (car x) (car z))) (* (- (car z) (car y)) (- (cadr x) (cadr z)))))
106       (a (/ (+ (* (- (cadr y) (cadr z)) (- (car p) (car z))) (* (- (car z) (car y)) (- (cadr p) (cadr z)))) denom))
107       (b (/ (+ (* (- (cadr z) (cadr x)) (- (car p) (car z))) (* (- (car x) (car z)) (- (cadr p) (cadr z)))) denom))
108       (c (- 1 a b)))
109      (and (<= 0 a 1) (<= 0 b 1) (<= 0 c 1))))
110    (no-points-in-tri (tri points)
111     (every (lambda (point) (not (point-in-tri (car tri) (cadr tri) (caddr tri) point))) points))
112    (tri-is-actually-line (x y z)
113     (zerop (+ (* (- (cadr y) (cadr z)) (- (car x) (car z))) (* (- (car z) (car y)) (- (cadr x) (cadr z)))))))
114   (cond
115    ((not (find :end points)) (triangulate (append points (list :end)) ccw))
116    ((< (length points) 4) (error "Must have at least 3 points..."))
117    ((= (length points) 4) (list (remove :end points)))
118    ((eql ccw :unknown) (triangulate points (poly-is-ccw (remove :end points))))
119    ((eql :end (car points)) (error "This polygon may not be triangulateable"))
120    (t
121     (let*
122      ((endless (remove :end points))
123       (tri (subseq endless 0 3)))
124      (cond
125       ((apply #'tri-is-actually-line tri)
126        (triangulate (cons (car endless) (cddr endless)) ccw))
127       ((apply #'tri-is-concave tri)
128        (triangulate (append (cdr points) (list (car points))) ccw))
129       ((no-points-in-tri tri (nthcdr 3 endless))
130        (cons tri (triangulate (cons (car endless) (cddr endless)) ccw)))
131       (t (triangulate (append (cdr points) (list (car points))) ccw))))))))
132
133 (defun element->gl-list (shape)
134  (case (car shape)
135   (:polygon
136    (progn
137     (gl:begin :triangles)
138     (mapcar
139      (lambda (point) (gl:vertex (car point) (cadr point) 0))
140      (apply #'append (triangulate (getf (cdr shape) :coords))))
141     (gl:end)))
142   (:rectangle
143    (progn
144     (gl:begin :triangles)
145     (mapcar
146      (lambda (point) (gl:vertex (car point) (cadr point) 0))
147      (apply #'append
148       (triangulate
149        (list
150         (list (getf (cdr shape) :left) (getf (cdr shape) :top))
151         (list (getf (cdr shape) :right) (getf (cdr shape) :top))
152         (list (getf (cdr shape) :right) (getf (cdr shape) :bottom))
153         (list (getf (cdr shape) :left) (getf (cdr shape) :bottom))))))
154     (gl:end)))
155   (:circle
156    (progn
157     (gl:begin :triangles)
158     (mapcar
159      (lambda (point) (gl:vertex (car point) (cadr point) 0))
160      (apply #'append
161       (triangulate
162        (loop
163         :repeat 360
164         :with c := (strictmath:cos (strictmath:to-radians 1))
165         :with s := (strictmath:sin (strictmath:to-radians 1))
166         :with r := (/ (getf (cdr shape) :diameter) 2)
167         :with left := (getf (cdr shape) :left)
168         :with top := (getf (cdr shape) :top)
169         :for n := 0 :then x
170         :for x := r :then (- (* c x) (* s y))
171         :for y := 0 :then (+ (* s n) (* c y))
172         :collect (list (+ (+ x left) r) (+ (+ y top) r))))))
173     (gl:end)))))
174
175 (defun parse-shapes (str)
176  (let
177   ((shape (parse-shape str)))
178   (when shape (cons shape (parse-shapes str)))))
179
180 (defun default-shapes ()
181  (with-open-file (str "resources/defaultshapes") (parse-shapes str)))
182
183 (defvar *colors*
184  '((140 140 140) ; gray       (5)
185    (215 48 39) ; red       (15)
186    (241 105 19) ; orange    (25)
187    (156 109 70) ; brown     (35)
188    (237 237 47) ; yellow    (45)
189    (87 176 58) ; green     (55)
190    (42 209 57) ; lime      (65)
191    (27 158 119) ; turquoise (75)
192    (82 196 196) ; cyan      (85)
193    (43 140 190) ; sky       (95)
194    (50 92 168) ; blue     (105)
195    (123 78 163) ; violet   (115)
196    (166 25 105) ; magenta  (125)
197    (224 126 149) ; pink     (135)
198    (0 0 0) ; black
199    (255 255 255))) ; white
200
201 (defun nl-color->rgb (color)
202  (let*
203   ((step (+ (/ (- (mod (floor (* color 10)) 100) 50) 50.48) 0.012)))
204   (mapcar
205    (lambda (x) (/ (+ x (floor (* (if (< step 0d0) x (- 255 x)) step))) 255))
206    (nth (floor color 10) *colors*))))
207
208 (defun render-scene ()
209  (gl:clear :color-buffer-bit :depth-buffer-bit)
210  (gl:matrix-mode :projection)
211  (gl:load-identity)
212  (gl:ortho
213   (floor (* (- (getf *dimensions* :xmin) 0.5) (patch-size)))
214   (floor (* (+ (getf *dimensions* :xmax) 0.5) (patch-size)))
215   (floor (* (- (getf *dimensions* :ymin) 0.5) (patch-size)))
216   (floor (* (+ (getf *dimensions* :ymax) 0.5) (patch-size)))
217   0 5000)
218  (gl:matrix-mode :modelview)
219  (gl:load-identity)
220  (destructuring-bind (turtles patches) (clnl-nvm:current-state)
221   (mapcar
222    (lambda (patch)
223     (let
224      ((color (nl-color->rgb (getf patch :color))))
225      (gl:color (car color) (cadr color) (caddr color)))
226     (gl:with-pushed-matrix
227      (gl:translate (* (getf patch :xcor) (patch-size)) (* (getf patch :ycor) (patch-size)) 0)
228      (gl:translate (floor (* -.5d0 (patch-size))) (floor (* -.5d0 (patch-size))) 0)
229      (gl:scale (patch-size) (patch-size) 1)
230      (gl:call-list *patch-list*)))
231    patches)
232   (mapcar
233    (lambda (turtle)
234     (let
235      ((color (nl-color->rgb (getf turtle :color))))
236      (gl:color (car color) (cadr color) (caddr color)))
237     (mapcar
238      (lambda (x-modification y-modification)
239       (gl:with-pushed-matrix
240        (gl:translate (* (getf turtle :xcor) (patch-size)) (* (getf turtle :ycor) (patch-size)) 0)
241        (gl:translate x-modification y-modification 0)
242        (let
243         ((turtle-list (find (getf turtle :shape) *turtle-lists* :test #'string= :key #'car)))
244         (when turtle-list
245          (when (second turtle-list)
246           (gl:rotate (getf turtle :heading) 0 0 -1))
247          (gl:scale (patch-size) (patch-size) 1)
248          (gl:scale (getf turtle :size) (getf turtle :size) 1)
249          (gl:call-list (third turtle-list))))))
250      (list 0 (1- (world-width-in-pixels)) (- (1- (world-width-in-pixels))) 0 0)
251      (list 0 0 0 (1- (world-height-in-pixels)) (- (1- (world-height-in-pixels))))))
252    turtles))
253  (gl:flush))
254
255 (defun display ()
256  (render-scene)
257  (cl-glut:swap-buffers))
258
259 (defun idle ()
260  (cl-glut:post-redisplay))
261
262 (defun close-func ()
263  (sb-ext:exit :abort t))
264
265 (defun reshape (width height)
266  (when (and (/= 0 width) (/= 0 height))
267   (gl:viewport 0 0 width height)))
268
269 (cffi:defcallback display :void () (display))
270 (cffi:defcallback idle :void () (idle))
271 (cffi:defcallback close-func :void () (close-func))
272 (cffi:defcallback reshape :void ((width :int) (height :int)) (reshape width height))
273
274 (defun set-turtle-lists ()
275  (setf
276   *turtle-lists*
277   (mapcar
278    (lambda (shape)
279     (let
280      ((turtle-list
281        (list
282         (getf shape :name)
283         (getf shape :rotatable)
284         (gl:gen-lists 1))))
285      (gl:with-new-list ((third turtle-list) :compile)
286       (gl:rotate 180d0 0d0 0d0 -1d0)
287       (gl:scale (/ 1d0 300d0) (/ 1d0 300d0) 1)
288       (gl:translate -150d0 -150d0 -0.0d0)
289       (mapcar #'element->gl-list (getf shape :elements)))
290      turtle-list))
291    (default-shapes))))
292
293 (defun set-patch-list ()
294  (setf *patch-list* (gl:gen-lists 1))
295  (gl:with-new-list (*patch-list* :compile)
296   (gl:begin :polygon)
297   (gl:vertex 0 0 0)
298   (gl:vertex 0 1 0)
299   (gl:vertex 1 1 0)
300   (gl:vertex 1 0 0)
301   (gl:end)))
302
303 (defun initialize (&key dims)
304  "INITIALIZE &key DIMS => RESULT
305
306   DIMS: (:xmin XMIN :xmax XMAX :ymin YMIN :ymax YMAX :patch-size PATCH-SIZE)
307
308 ARGUMENTS AND VALUES:
309
310   RESULT: undefined
311   XMIN: An integer representing the minimum patch coord in X
312   XMAX: An integer representing the maximum patch coord in X
313   YMIN: An integer representing the minimum patch coord in Y
314   YMAX: An integer representing the maximum patch coord in Y
315   PATCH-SIZE: A double representing the size of the patches in pixels
316
317 DESCRIPTION:
318
319   This is where the initialization of the interface that sits behind
320   the interface lives.  From here, one can go into headless or running
321   mode, but for certain things this interface will still need to act,
322   and also allows for bringing up and taking down of visual elements."
323  (setf *dimensions* dims)
324  (when *glut-window-opened*
325   (cl-glut:reshape-window (world-width-in-pixels) (world-height-in-pixels))))
326
327 (defun run ()
328  "RUN => RESULT
329
330 ARGUMENTS AND VALUES:
331
332   RESULT: undefined, should never get here
333
334 DESCRIPTION:
335
336   RUN runs the view in an external window.
337
338   This should be run inside another thread as it starts the glut main-loop.
339   Closing this window will then cause the entire program to terminate."
340  ; I do this because I don't know who or what in the many layers
341  ; is causing the floating point errors, but I definitely don't
342  ; want to investigate until simply ignoring them becomes a problem.
343  (sb-int:with-float-traps-masked (:invalid :inexact :divide-by-zero :overflow :underflow)
344   (cl-glut:init)
345   (cl-glut:init-window-size
346    (world-width-in-pixels)
347    (world-height-in-pixels))
348   (cl-glut:init-display-mode :double :rgba)
349   (cl-glut:create-window "CLNL Test Window")
350   (setf *glut-window-opened* t)
351   (gl:clear-color 0 0 0 1)
352   (cl-glut:display-func (cffi:get-callback 'display))
353   (glut:reshape-func (cffi:callback reshape))
354   (cl-glut:idle-func (cffi:get-callback 'idle))
355   (cl-glut:close-func (cffi:get-callback 'close-func))
356   (set-turtle-lists)
357   (set-patch-list)
358   (cl-glut:main-loop)))
359
360 (defun patch-size () (getf *dimensions* :patch-size))
361
362 (defun world-width-in-pixels ()
363  (floor (* (patch-size) (1+ (- (getf *dimensions* :xmax) (getf *dimensions* :xmin))))))
364
365 (defun world-height-in-pixels ()
366  (floor (* (patch-size) (1+ (- (getf *dimensions* :ymax) (getf *dimensions* :ymin))))))
367
368 (defun export-view ()
369  "EXPORT-VIEW => IMAGE-DATA
370
371 ARGUMENTS AND VALUES:
372
373   IMAGE-DATA: A vector, pixel data as returned by opengls readPixels
374
375 DESCRIPTION:
376
377   EXPORT-VIEW returns the current view in raw data of RGBA pixels.
378
379   Each pixel is made up of 4 bytes of data, which an be walked over.  The number
380   of pixels is the current width x height.  Converting to some other image format
381   is a matter of pulling that information out and putting it into whatever format
382   you like.
383
384   This requires opengl to run, but can be used with xvfb in a headless mode."
385  (sb-int:with-float-traps-masked (:invalid)
386   (when (not *glut-window-opened*)
387    (cl-glut:init)
388    (cl-glut:init-window-size 1 1)
389    (cl-glut:create-window "CLNL Test Window")
390    (gl:clear-color 0 0 0 1)
391    (set-turtle-lists)
392    (set-patch-list)
393    (setf *glut-window-opened* t))
394   (let
395    ((fbo (first (gl:gen-framebuffers 1)))
396     (render-buf (first (gl:gen-renderbuffers 1)))
397    ;(width
398    ; (floor (* (patch-size) (1+ (-
399    ;                             (getf *dimensions* :ymax)
400    ;                             (getf *dimensions* :ymin))))))
401    ;(height
402    ; (floor (* (patch-size) (1+ (- (getf *world-dims* :xmax) (getf *world-dims* :xmin))))))
403    ; (floor (* (patch-size) (1+ (-
404    ;                            (getf *dimensions* :xmax)
405    ;                            (getf *dimensions* :xmin)))))
406     (width (world-width-in-pixels))  ; Hard coded for now, yay v1 (if you see this comment in a year, please cry for me)
407     (height (world-height-in-pixels)))
408    (gl:bind-framebuffer :framebuffer fbo)
409    (gl:bind-renderbuffer :renderbuffer render-buf)
410    (gl:renderbuffer-storage :renderbuffer :rgba8 width height)
411    (gl:framebuffer-renderbuffer :draw-framebuffer :color-attachment0 :renderbuffer render-buf)
412    (gl:viewport 0 0 width height)
413    (render-scene)
414    (gl:read-pixels 0 0 width height :rgba :unsigned-byte))))