1 ;;; -*- mode: lisp; syntax: common-lisp; package: existenz-client; coding: utf-8-unix; -*-
3 (in-package #:existenz-client)
6 ;;; - bridge between blender and engine, in addition to inotify-watching.
7 ;;; this will be best possible with some ready to run rpc framework,
8 ;; i.e. xmlrpc.server.SimpleXMLRPCServer
9 ;;; source files if no bridge is available
10 ;;; - interaction with windows messaging, i.e. enable powermenu
11 ;;; - config.lisp loading a la emacs
12 ;;; - ensure conversion to CL-TUPLES::FAST-FLOAT on system boundaries,
13 ;;; also add normalization and test for divergence on low optimization
14 ;;; levels to *every* operation
15 ;;; - be sure about the coordinate systems, i.e. 2d+gui/3d
16 ;;; - how to do networking (iolib? or some other plain library?), sound
18 ;;; - serialization? cl-store or something else?
19 ;;; - FINALIZE as per its description, i.e. register closures to directly
20 ;;; deallocate the nested resources. since this may occur in any thread,
21 ;;; unloading of textures needs to be pushed to the main/opengl thread -
22 ;;; the same goes for other values (openal?) similarly. it may be
23 ;;; possible to still retain often used resources in a separate cache,
24 ;;; but that's just icing then. better make a state-chart for all this
25 ;;; behaviour, i.e. when is a resource (successfully) loaded, what
26 ;;; happens at what stage
27 ;;; - replace missing resources with placeholders, which may still be
28 ;;; corrected, i.e. by saving a file to the wanted name, or correcting
30 ;;; - resource lookup by what? filesystem like? how are different
31 ;;; authors sufficiently discriminated against?
33 ;;; future future ideas
34 ;;; - bytecode vm for scripting? targeted to mathematics, i.e. full
35 ;;; regular tower + vectors + matrixes + quaternions, which then may be
38 ;;; every object in the world is in the scene tree and in the spatial
39 ;;; trees. if an object is removed, it is removed from all trees. if
40 ;;; an object moves, it has to be reinserted in some of the spatial trees,
41 ;;; also if it changes its scale, rotation and furthermore its actual
44 ;; nice, but doesn't work with eldoc, so it's kinda useless
46 (eval-when (:compile-toplevel :load-toplevel :execute)
47 (defmacro package-calls (package &body body)
48 (setf package (find-package package))
50 ,.(mapcar (lambda (form)
55 (setf found (find-symbol
58 `(,found ,.(cdr form))
62 (defmacro gl (&body body)
63 `(package-calls #.(find-package '#:gl) ,@body)))
65 (defvar *window-width*)
66 (defvar *window-height*)
67 (defvar *window-fullscreen*)
69 (push '(window-resolution . (640 480)) *options*)
70 (push '(window-fullscreen . (NIL)) *options*)
72 (push '(window-resolution *window-width* *window-height*) *options-mappings*)
73 (push '(window-fullscreen *window-fullscreen*) *options-mappings*)
75 ;;; message passing to the client main thread
80 (defun send-to-client (object)
82 ((not (boundp '*client*))
83 (warn "*CLIENT* is unbound"))
85 (warn "*CLIENT* has no value"))
86 ((not (thread-alive-p *client*))
87 (warn "*CLIENT* isn't running"))
89 (prog1 T (enqueue *messages* object)))))
91 (defmacro run-in-client (&body body)
92 `(send-to-client (lambda () ,@body)))
94 (defun handle-message (message)
97 (handler-case (funcall message)
99 (warn "ERROR occured while executing ~A: ~A" message error))))))
101 (defun handle-messages (locked-deque)
102 (let ((messages (dequeue-all locked-deque)))
103 (mapc #'handle-message messages)))
107 (defvar *resources* (make-hash-table :test 'eq))
109 (defgeneric unload-resource (resource)
110 (:documentation "Explicitely deallocate the RESOURCE and disable the
111 finalizer (see MAKE-RESOURCE-FINALIZER)."))
112 (defgeneric make-resource-finalizer (resource)
113 (:documentation "Returns a FUNCTION, which takes care of cleaning up
114 resources in the correct threads when the RESOURCE is gc'ed. Note the
115 rules for FINALIZE apply, i.e. the closure may not reference the RESOURCE
116 object, or it won't ever be collected."))
117 (defgeneric load-resource (resource))
118 (defgeneric resource-loaded-p (resource)
119 (:documentation "A resource may fail to load, but by using placeholder
120 values, it can still be used. Of course, this only works for some objects,
123 (defun map-resources (function)
125 (lambda (name resource)
126 (declare (ignore name))
127 (funcall function resource))
130 (defun load-all-resources (&optional force)
133 (when (or (not (resource-loaded-p resource)) force)
134 (load-resource resource)))))
136 (defun unload-all-resources ()
137 (map-resources #'unload-resource))
139 (defmacro with-all-resources (&body body)
143 (unload-all-resources)))
145 (defun get-resource (name)
146 (gethash name *resources*))
148 (defun %delete-resource (name)
149 (awhen (get-resource name)
151 (remhash name *resources*)))
153 (defun %add-resource (name new)
154 (symbol-macrolet ((resource (gethash name *resources*)))
155 (let ((old resource))
157 (unload-resource old)
159 (setf resource new))))
161 (defun add-resource (name new)
162 "Adds the NEW resource. For the moment all resources are loaded in the
164 ;; TODO: wait for client to execute it, then return the object
165 (unless (run-in-client (%add-resource name new))
166 (setf (gethash name *resources*) new))
169 (defun delete-resource (name)
170 (unless (run-in-client (%delete-resource name))
171 (remhash name *resources*)))
173 (define-condition undefined-resource (error)
174 ((resource-name :initarg :resource-name :reader resource-name))
176 (lambda (condition stream)
177 (format stream "Undefined RESOURCE ~A requested."
178 (resource-name condition)))))
180 (defun resource (name)
181 (let ((resource (get-resource name)))
183 (error 'undefined-resource :resource-name name))
186 (defclass resource ()
187 ((name :type symbol :initarg :name)))
189 (defmethod unload-resource :around (resource)
190 (when (resource-loaded-p resource)
193 (defmethod load-resource :before (resource)
194 (when (resource-loaded-p resource)
195 (unload-resource resource)))
197 (defmethod load-resource :after (resource)
198 (format-log "loaded resource ~A" resource))
200 (defmethod unload-resource :after (resource)
201 (format-log "unloaded resource ~A" resource))
203 (defun ensure-resource (resource)
204 (unless (resource-loaded-p resource)
205 (load-resource resource)))
207 (defmethod print-object ((resource resource) stream)
209 (prin1 (make-load-form resource) stream)
210 (print-unreadable-object (resource stream :type T :identity T)
211 (with-slots (name) resource
212 (format stream "~A ~A"
213 name (if (resource-loaded-p resource)
214 'loaded 'unloaded))))))
216 (defclass pathname-mixin ()
217 ((pathname :initarg :pathname)))
222 (deftype font-mode ()
223 `(member :textured :polygon))
226 (defvar *default-font-mode* :textured)
229 (defclass font (pathname-mixin resource)
230 ((ftgl-object :initform NIL)
231 (mode :initarg :mode)))
234 (defun make-font (name pathname &key (mode *default-font-mode*))
235 (check-type mode font-mode)
236 (make-instance 'font :name name :pathname pathname :mode mode))
239 (defmethod unload-resource ((font font))
240 (with-slots (ftgl-object) font
241 (let ((object ftgl-object))
242 (setf ftgl-object NIL)
243 (cancel-finalization object)
244 (ftgl:destroy-font object))))
247 (defmethod load-resource ((font font))
248 (with-slots (pathname ftgl-object mode) font
249 (let ((object (funcall (case mode
250 (:textured #'ftgl:create-texture-font)
251 (:polygon #'ftgl:create-polygon-font))
253 (finalize object #'ftgl:destroy-font)
254 (setf ftgl-object object)
255 (ftgl:set-font-face-size object 36 90)
256 (ftgl:set-font-char-map object :unicode))))
259 (defmethod resource-loaded-p ((font font))
260 (and (slot-value font 'ftgl-object) T))
263 (defmacro add-font (name pathname &key (mode NIL modep))
266 (make-font ',name ,pathname ,@(when modep (list :mode mode)))))
269 (defmethod make-load-form ((font font) &optional environment)
270 (declare (ignore environment))
271 (with-slots (name pathname mode) font
272 `(add-font ,name ,pathname ,mode)))
276 (defclass image (pathname-mixin resource)
277 ((sdl-object :initform NIL)))
279 (defun make-image (name pathname)
280 (make-instance 'image :name name :pathname pathname))
282 ;; TODO: um, is the free necessary?
283 (defmethod unload-resource ((image image))
284 (with-slots (sdl-object) image
285 (sdl:free (shiftf sdl-object NIL))))
287 ;; TODO: and then, is this necessary? specific thread?
288 (defmethod make-resource-finalizer ((image image))
289 (with-slots (sdl-object) image
291 (sdl:free sdl-object))))
293 (defmethod load-resource ((image image))
294 (with-slots (pathname sdl-object) image
295 (setf sdl-object (sdl:load-image pathname))))
297 (defmethod resource-loaded-p ((image image))
298 (and (slot-value image 'sdl-object) T))
300 (defmacro add-image (name pathname)
301 `(add-resource ',name (make-image ',name ,pathname)))
303 (defmethod make-load-form ((image image) &optional environment)
304 (declare (ignore environment))
305 (with-slots (name pathname) image
306 `(add-image ,name ,pathname)))
310 ;;; multiple shaders are linked into a program, but they can already be
311 ;;; compiled beforehand
313 ;; (defclass shader (pathname-mixin resource)
314 ;; ((shader-object :initform NIL)))
316 ;; (defun make-shader (name pathname)
317 ;; (make-instance 'shader :name name :pathname pathname))
319 ;; (defmethod unload-resource ((shader shader))
320 ;; (with-slots (shader-object
322 ;;; reloading resources on change
326 ;;; distinction between stripped geometry (allocated in not gc'ed memory)
327 ;;; and lisp-managed geometry
329 (defun make-static-geometry (geometry)
333 ((dimensions :initarg :dimensions)))
335 (defgeneric render-object (object))
337 (defvar *debug-matrix*)
339 (defmethod render-object ((cube cube))
340 (let* ((dimensions (slot-value cube 'dimensions))
341 (w (aref dimensions 0))
342 (d (aref dimensions 1))
343 (h (aref dimensions 2))
344 ;; TODO: make static macros for this using some permutation
347 (debug-display-matrix :modelview)
355 (defun window-resolution ()
356 (values *window-width* *window-height*))
358 (defun window-fullscreen ()
361 (defun set-window-resolution (width height)
362 (setf *window-width* width
363 *window-height* height))
365 (defun reset-opengl ()
366 (multiple-value-bind (width height)
368 (gl:viewport 0 0 width height)
369 (gl:matrix-mode :projection)
371 (gl:matrix-mode :modelview)
373 (gl:clear-color 0.2 0.2 0.2 0)))
378 (apply-camera-transformation *camera*))
381 (multiple-value-bind (width height)
383 (gl:matrix-mode :projection)
385 (gl:ortho 0 width 0 height -1 1)
386 (gl:matrix-mode :modelview)))
388 (defun bbox-size (bbox)
390 (+ (fourth bbox) (max 0 (first bbox)))
391 (+ (fifth bbox) (max 0 (second bbox)))))
393 (defun rectangle (x1 y1 x2 y2)
394 (vector x1 y1 x2 y2))
396 (defmacro with-rectangle ((rect &optional (x1 'x1) (y1 'y1) (x2 'x2) (y2 'y2)) &body body)
397 "Destructures the rectangle RECT into the components named by the given
398 symbols X1 to Y2 for the duration of BODY."
399 (let ((rect-sym (gensym))
401 `(let* ((,rect-sym ,rect)
402 (,x1 (,ref ,rect-sym 0))
403 (,y1 (,ref ,rect-sym 1))
404 (,x2 (,ref ,rect-sym 2))
405 (,y2 (,ref ,rect-sym 3)))
409 (defun render-aligned-text (font string size rect &key (vertical :left) (horizontal :top) (resolution 75))
410 (let ((ftgl (slot-value font 'ftgl-object)))
411 (ftgl:set-font-face-size ftgl size resolution)
412 (multiple-value-bind (text-width text-height)
413 (bbox-size (ftgl:get-font-bbox ftgl string))
414 (setf text-width (fceiling text-width)
415 text-height (fceiling text-height))
416 (gl:with-pushed-matrix
417 (with-rectangle (rect)
418 (multiple-value-bind (xtrans ytrans)
419 (values (ecase vertical
421 (:center (/ (- (- x2 x1) text-width) 2))
422 (:right (- x2 text-width)))
424 (:top (- y2 text-height))
425 (:center (/ (- (- y2 y1) text-height) 2))
427 (gl:translate xtrans ytrans 0)))
428 (ftgl:render-font ftgl string :all))
429 (values text-width text-height))))
432 (defun render-aligned-matrix (font matrix size rect &key (vertical :left) (horizontal :top) (resolution 75))
433 (flet ((format-line (matrix offset)
434 (format NIL "~8,3F ~8,3F ~8,3F ~8,3F"
435 (aref matrix (+ offset 0))
436 (aref matrix (+ offset 1))
437 (aref matrix (+ offset 2))
438 (aref matrix (+ offset 3)))))
439 (let* ((lines (iterate
440 (for i from 0 to 15 by 4)
441 (collect (format-line matrix i))))
442 (ftgl (slot-value font 'ftgl-object)))
443 (ftgl:set-font-face-size ftgl size resolution)
444 (let* ((sizes (mapcar (lambda (line) (multiple-value-list (bbox-size (ftgl:get-font-bbox ftgl line)))) lines))
445 (bbox (reduce (lambda (size1 size2) (list (max (first size1) (first size2))
446 (+ (second size1) (second size2))))
448 (gl:with-pushed-matrix
449 (with-rectangle (rect)
450 (multiple-value-bind (xtrans ytrans)
451 (destructuring-bind (text-width text-height) bbox
452 (incf text-height (* 3 3)) ; three gaps, three pixels
453 (values (ecase vertical
455 (:center (/ (- (- x2 x1) text-width) 2))
456 (:right (- x2 text-width)))
458 (:top (- y2 text-height))
459 (:center (/ (- (- y2 y1) text-height) 2))
461 (gl:translate xtrans ytrans 0)
465 (ftgl:render-font ftgl line :all)
466 (gl:translate 0 (+ 3 (second size)) 0)))))))))
468 (defmacro with-all-pushed-matrixes (&body body)
471 (gl:matrix-mode :texture)
473 (gl:matrix-mode :projection)
475 (gl:matrix-mode :modelview)
478 (gl:matrix-mode :texture)
480 (gl:matrix-mode :projection)
482 (gl:matrix-mode :modelview)
485 (define-condition abort-client () ())
486 (define-condition restart-inner-client () ())
490 (sdl:with-init (sdl:sdl-init-video sdl:sdl-init-joystick)
492 (restart-case (inner-client)
493 (restart-inner-client ()
494 :report "Restart the INNER-CLIENT while keeping the SDL context alive."
495 (format-log "restarting inner client")))))
497 :report "Abort CLIENT thread.")))
499 (defvar *scene-tree*)
501 (defun run-client (&optional (client #'client))
503 ((and (boundp '*client*) (thread-alive-p *client*))
504 (warn "Client is already running."))
506 (let* ((locked-deque (or (and (boundp '*messages*) *messages*)
507 (make-locked-deque "EXISTENZ-MESSAGES")))
508 ;; TODO: maybe include *STANDARD-IO-BINDINGS*?
509 (bindings `((*messages* . ,locked-deque)
510 (*package* . #.(find-package '#:existenz-client))
511 (*standard-output* . ,*standard-output*)
512 ;; TODO: include all the other private variables, i.e. *SCENE-TREE*
515 (setf *messages* locked-deque
516 *client* (make-thread client :name "EXISTENZ-CLIENT"
517 :initial-bindings bindings))))))
519 (defun abort-client ()
520 (run-in-client (invoke-restart 'abort-client))
521 (join-thread *client*))
523 (defun restart-client (&optional (client #'client))
527 (defun restart-inner-client ()
528 (run-in-client (invoke-restart 'restart-inner-client)))
530 (defun reset-identity ()
532 (gl:matrix-mode :modelview)
537 (add-font arial-polygon #P"/usr/share/fonts/corefonts/arial.ttf")
538 (add-font proggy-polygon #P"/usr/share/fonts/ProggyTinySZ.ttf")
539 (add-image silkscreen-texture #P"~/src/existenz/data/silkscreen.bmp"))
542 (defun run-client (&optional (host +ipv6-loopback+) (port 4000))
543 (with-event-base (event-base)
544 (let ((client (make-socket :ipv6 T
550 (send-to client #(1 0 0 3))
552 (when (wait-until-fd-ready (fd-of client) :input)
553 (multiple-value-bind (buffer length host port)
554 (receive-from client :size 100)
555 (format T "received ~A bytes from ~A:~A~%"
559 ;;; scene node / entity protocol
561 ;;; individual scene nodes build a logical tree of related objects
562 ;;; one or more spatial trees are independent of them and build orthogonal
563 ;;; hierarchies, which allows ...
564 (defgeneric parent (object)
565 (:documentation "Retrieves the PARENT node of the OBJECT. This may be
566 used to traverse the scene tree."))
567 (defgeneric (setf parent) (new-value object)
568 (:documentation "Sets the PARENT node of the OBJECT to NEW-VALUE."))
570 (defgeneric children (object)
571 (:method ((object T)))
572 (:documentation "Retrieves the LIST of CHILDREN of the OBJECT. This may
573 be used to traverse the scene tree."))
574 (defgeneric (setf children) (new-value object)
575 (:documentation "Sets the CHILDREN of the OBJECT to NEW-VALUE."))
577 (defgeneric transformation (object)
578 (:method ((object T)))
579 (:documentation "Retrieves the TRANSFORMATION for OBJECT. If it has
580 none, returns NIL instead (default)."))
582 (defgeneric bounding-box (object)
583 (:documentation "Retrieves the axis aligned bounding box around around
584 the OBJECT. This one is relative to the object. See
585 ABSOLUTE-BOUNDING-BOX for the box transformed into absolute world
587 (defgeneric absolute-bounding-box (object)
588 (:documentation "Retrieves the axis aligned bounding box in world
589 coordinates for OBJECT. See BOUNDING-BOX to get the relative one
592 (defgeneric animators (object)
593 (:method ((object T)))
594 (:documentation "Retrieves the LIST of ANIMATORS of the OBJECT."))
595 (defgeneric (setf animators) (new-value object)
596 (:documentation "Sets the ANIMATORS of the OBJECT to NEW-VALUE."))
598 (defvar *delta-real-elapsed* 0
599 "Measures real milliseconds since the last UPDATE call.")
601 (defgeneric update (object)
602 (:method ((object T)))
603 (:documentation "Updates an OBJECT when some real time has elapsed.
604 Does nothing by default. The elapsed real time is measured in
605 milliseconds and stored in the global variable *DELTA-REAL-ELAPSED*."))
607 (defgeneric animate (animator object)
608 (:method ((animator T) (object T))))
610 (defgeneric render (object)
611 (:method ((object T)))
612 (:documentation "Renders an OBJECT to the screen. Since objects may be
613 sorted by some external criteria, iteration over child nodes is done
614 externally, thus by default child nodes aren't rendered and in fact
617 (defclass scene-node-mixin ()
627 :initarg :transformation
628 :accessor transformation
629 :initform (make-transformation))
635 ;;; scene node animators
637 ;; TODO: this should be angle + velocity, or pure quaternion angle, when
638 ;; we figure out how to scale it
639 (defclass animator-rotation ()
643 :initform (new-angle-axis))))
645 ;; TODO: so how does one actually do quaternion scaling? i.e. scaling
646 ;; below one rotation or applying multiple rotations in one step?
647 (defmethod animate ((animator animator-rotation) object)
648 (let ((transformation (transformation object)))
650 (let ((rotation (relative-rotation transformation)))
653 (quaternion-normalize*
655 (with-angle-axis (rotation animator)
657 (angle-axis-quaternion*
658 (angle-axis-values* x y z (* a 0.001 *delta-real-elapsed*))))
661 (setf (relative-dirty-p transformation) T))))
665 ;;; distinction between stripped geometry (allocated in not gc'ed memory)
666 ;;; and lisp-managed geometry
668 (defclass cube (scene-node-mixin)
671 (defun make-cube (&optional (width 1.0) (height 1.0) (depth 1.0))
672 (let ((transformation (make-transformation)))
673 (setf (relative-scale transformation)
674 (make-vector3d width height depth))
675 (make-instance 'cube :transformation transformation)))
677 (defun render-unit-cube ()
678 (let ((points #(0 0 0 ; 0
686 (indexes #(0 1 2 3 ; front
692 (gl:with-primitives :quads
693 (map NIL (lambda (index)
694 (let ((x (aref points (+ (* index 3) 0)))
695 (y (aref points (+ (* index 3) 1)))
696 (z (aref points (+ (* index 3) 2))))
698 ;; align cube at 0,0,0
699 (gl:vertex (1- (* 2 x)) (1- (* 2 y)) (1- (* 2 z)))))
702 (defvar *debug-matrix*)
704 (defun debug-display-matrix (mode)
705 ;; FIXME: welp, if this is only for debugging okay, but otherwise write
706 ;; a wrapper, which returns row-major matrixes instead
713 (:modelview :modelview-matrix)
714 (:projection :projection-matrix)
715 (:texture :texture-matrix))))))))
717 (defmethod render ((cube cube))
724 ;;; three structures, logical (scene tree), spatial and render graph
726 (defun add-scene-node (scene-node)
727 (push scene-node *scene-tree*))
729 (defgeneric relative-transformation (object))
731 (defmethod relative-transformation (object)
732 (relative-transformation (transformation object)))
734 (defun translate (object x y z)
735 (let* ((transformation (transformation object))
736 (relative (relative-position transformation)))
741 (vector3d-values* x y z)))
742 (setf (relative-dirty-p transformation) T)))
744 ;; TODO: this in all possible permutations and as def-tuple-op
745 (defun euler-xyz-to-quaternion (x y z)
746 (let* ((sr (sin (/ x 2)))
756 (quaternion-normalize*
758 (- (* sr cpcy) (* cr spsy))
759 (+ (* cr spcy) (* sr cpsy))
760 (- (* cr cpsy) (* sr spcy))
761 (+ (* cr cpcy) (* sr spsy))))))
763 (defun rotate (object x y z)
764 (let* ((transformation (transformation object))
765 (relative (relative-rotation transformation)))
769 (quaternion* relative)
770 (euler-xyz-to-quaternion x y z)))
771 (setf (relative-dirty-p transformation) T)))
773 (defun scale (object x y z)
774 (let* ((transformation (transformation object))
775 (relative (relative-scale transformation)))
780 (vector3d-values* x y z)))
781 (setf (relative-dirty-p transformation) T)))
783 (defgeneric absolute-transformation (object))
785 (defmethod absolute-transformation (object)
786 (absolute-transformation (transformation object)))
788 (defun identity-matrix44 ()
789 (make-matrix44* (identity-matrix44*)))
791 (defvar *identity-matrix44* (identity-matrix44))
793 (defun ones-vector3d ()
794 (make-vector3d 1.0 1.0 1.0))
796 (def-tuple-op zero-quaternion*
799 (quaternion-values* 0.0 0.0 0.0 1.0)))
801 (defun zero-quaternion ()
805 (def-tuple-op unit-quaternion-x*
806 ((angle cl-tuples::fast-float))
808 (let ((angle/2 (/ angle 2)))
809 (quaternion-normalize* (quaternion-values* (sin angle/2) 0.0 0.0 (cos angle/2))))))
811 (def-tuple-op unit-quaternion-y*
812 ((angle cl-tuples::fast-float))
814 (let ((angle/2 (/ angle 2)))
815 (quaternion-normalize* (quaternion-values* 0.0 (sin angle/2) 0.0 (cos angle/2))))))
817 (def-tuple-op unit-quaternion-z*
818 ((angle cl-tuples::fast-float))
820 (let ((angle/2 (/ angle 2)))
821 (quaternion-normalize* (quaternion-values* 0.0 0.0 (sin angle/2) (cos angle/2))))))
823 (defclass transformation ()
825 :initarg :relative-position
826 :accessor relative-position
827 :initform (new-vector3d))
829 :initarg :relative-rotation
830 :accessor relative-rotation
831 :initform (zero-quaternion))
833 :initarg :relative-scale
834 :accessor relative-scale
835 :initform (ones-vector3d))
837 :initarg :relative-dirty-p
838 :accessor relative-dirty-p
840 (relative-transformation
841 :initarg :relative-transformation
842 :accessor relative-transformation
843 :initform (new-matrix44))
844 (absolute-transformation
845 :initarg :absolute-transformation
846 :accessor absolute-transformation
847 :initform (new-matrix44)))
848 (:documentation "Contains information about the position, rotation and
849 scale relative to some other scene node (usually the parent) or the
850 coordinate origin. Also contains a scratch slot ABSOLUTE-TRANSFORMATION,
851 which is used destructively to calculate it."))
853 (defmethod (setf relative-position) :after (new-value (transformation transformation))
854 (setf (relative-dirty-p transformation) T))
856 (defmethod (setf relative-rotation) :after (new-value (transformation transformation))
857 (setf (relative-dirty-p transformation) T))
859 (defmethod (setf relative-scale) :after (new-value (transformation transformation))
860 (setf (relative-dirty-p transformation) T))
862 (defun calculate-relative-transformation (position rotation scale relative-transformation)
864 relative-transformation
866 (with-vector3d scale (sx sy sz)
867 (scaling-matrix44* sx sy sz))
870 (quaternion-matrix33*
871 (quaternion* rotation)))
872 (with-vector3d position (tx ty tz)
873 (translation-matrix44* tx ty tz))))))
875 (defmethod relative-transformation :before ((transformation transformation))
876 (when (relative-dirty-p transformation)
877 (calculate-relative-transformation
878 (relative-position transformation)
879 (relative-rotation transformation)
880 (relative-scale transformation)
881 (slot-value transformation 'relative-transformation))
882 (setf (relative-dirty-p transformation) NIL)))
884 (defun make-transformation ()
885 (make-instance 'transformation))
889 (defclass camera (scene-node-mixin)
890 ((inverse-absolute-matrix
891 :initarg :inverse-absolute-matrix
892 :accessor inverse-absolute-matrix
893 :initform (new-matrix44)
894 :documentation "Scratch slot to calculate the inverse transformation.")
900 :initarg :clipping-planes
901 :accessor clipping-planes
902 :initform (cons 1 1024))))
904 (defclass fps-camera (camera)
906 :initarg :relative-rotation
907 :accessor relative-rotation
908 :initform (new-vector3d))))
910 (defgeneric apply-camera-rotation (camera x y z))
912 (defmethod apply-camera-rotation ((camera camera) rotate-x rotate-y rotate-z)
913 (symbol-macrolet ((r (relative-rotation (transformation *camera*)))
914 (r* (quaternion* r)))
920 (quaternion-transform-vector3d* (vector3d-values* 0.0 0.0 -1.0) r*)
922 (angle-axis-quaternion*
923 (angle-axis-values* x y z rotate-z)))
926 (quaternion-transform-vector3d* (vector3d-values* 0.0 1.0 0.0) r*)
928 (angle-axis-quaternion*
929 (angle-axis-values* x y z rotate-y)))
931 (quaternion-transform-vector3d* (vector3d-values* 1.0 0.0 0.0) r*)
933 (angle-axis-quaternion*
934 (angle-axis-values* x y z rotate-x)))))
937 (defconstant +2-pi-fast+
938 (coerce (* pi 2) 'cl-tuples::fast-float))
940 (defconstant +-2-pi-fast+
941 (coerce (- (* pi 2)) 'cl-tuples::fast-float))
943 (defconstant +pi/2-fast+
944 (coerce (/ pi 2) 'cl-tuples::fast-float))
946 (defconstant +-pi/2-fast+
947 (coerce (- (/ pi 2)) 'cl-tuples::fast-float))
949 (defmethod apply-camera-rotation ((camera fps-camera) rotate-x rotate-y rotate-z)
950 (symbol-macrolet ((r (relative-rotation (transformation *camera*)))
951 (r* (quaternion* r)))
953 (relative-rotation *camera*)
957 (relative-rotation *camera*)
962 (let ((y (+ old-y rotate-y)))
966 ((< y 0) (+ +2-pi-fast+ y))
967 ((> y +2-pi-fast+) (+ +-2-pi-fast+ y))
969 (let ((z (+ old-z rotate-z)))
973 ((< z 0) (+ +2-pi-fast+ z))
974 ((> z +2-pi-fast+) (+ +-2-pi-fast+ z))
978 ;; (format-log "~F ~F ~F" new-x new-y new-z)
982 (euler-xzy-to-quaternion new-x new-y new-z))
985 (unit-quaternion-y* new-y)
987 (unit-quaternion-z* new-z)
988 (unit-quaternion-x* new-x)))))))))
990 (defmethod render ((camera camera))
991 "Render a placeholder for the invisible CAMERA when debugging.")
993 (defun make-camera ()
994 (make-instance 'camera))
996 (defun make-fps-camera ()
997 (make-instance 'fps-camera))
999 ;; http://nehe.gamedev.net/article/replacement_for_gluperspective/21002/
1000 (defun perspective (fov aspect near far)
1001 (let* ((fh (* (tan (* (/ fov 360) pi)) near))
1003 (gl:frustum (- fw) fw (- fh) fh near far)))
1005 (defun setup-camera (camera)
1006 "Initial setup which affects the OpenGL PROJECTION matrix."
1007 (multiple-value-bind (width height)
1009 (let ((planes (clipping-planes camera)))
1010 (gl:matrix-mode :projection)
1012 (perspective (fov camera) (/ width height)
1013 (car planes) (cdr planes))
1014 (gl:matrix-mode :modelview))))
1016 (defun calculate-inverse-absolute-matrix (absolute-transformation
1017 inverse-absolute-matrix)
1018 ;; (format-log "det ~F" (matrix44-determinant* (matrix44* absolute-transformation)))
1020 inverse-absolute-matrix
1022 (matrix44* absolute-transformation))))
1024 (defun apply-camera-transformation (camera)
1025 ;; same as GLU:LOOK-AT
1026 (let ((inverse-absolute-matrix (inverse-absolute-matrix camera)))
1027 (calculate-inverse-absolute-matrix
1028 (absolute-transformation camera)
1029 inverse-absolute-matrix)
1030 ;; (cl-tuples::print-matrix44* (matrix44* (absolute-transformation camera)))
1031 ;; i.e. column-major mode
1032 (gl:load-transpose-matrix inverse-absolute-matrix)))
1034 (defun calculate-absolute-transformation (absolute relative parent)
1038 (matrix44* relative)
1039 (matrix44* parent))))
1041 (defun update-absolute-transformation (object parent-transformation)
1042 (let ((absolute (absolute-transformation object)))
1043 (calculate-absolute-transformation
1045 (relative-transformation object)
1046 parent-transformation)
1051 (defun reset-window ()
1052 ;; FIXME: check these options ...
1053 (multiple-value-call #'sdl:window
1055 :position (list 0 0)
1056 :fullscreen (window-fullscreen)
1062 :opengl-attributes '((:sdl-gl-doublebuffer 1))
1063 :title-caption "Lisp Game"
1064 :icon-caption "Lisp Game")
1065 (sdl:initialise-default-font)
1066 (sdl:show-cursor NIL))
1069 (break "~A ~A ~A, ~A ~A, ~A ~A ~A, ~A ~A"
1070 (sdl-base:surf-w (sdl:fp sdl-object))
1071 (sdl-base:surf-h (sdl:fp sdl-object))
1072 (sdl-base:pixel-format (sdl:fp sdl-object))
1073 (cffi:foreign-slot-value (sdl-base:pixel-format (sdl:fp sdl-object))
1074 'sdl-cffi::sdl-pixel-format 'sdl-cffi::bytesperpixel)
1075 (cffi:foreign-slot-value (sdl-base:pixel-format (sdl:fp sdl-object))
1076 'sdl-cffi::sdl-pixel-format 'sdl-cffi::bitsperpixel)
1077 (cffi:foreign-slot-value (sdl-base:pixel-format (sdl:fp sdl-object))
1078 'sdl-cffi::sdl-pixel-format 'sdl-cffi::Rmask)
1079 (cffi:foreign-slot-value (sdl-base:pixel-format (sdl:fp sdl-object))
1080 'sdl-cffi::sdl-pixel-format 'sdl-cffi::Gmask)
1081 (cffi:foreign-slot-value (sdl-base:pixel-format (sdl:fp sdl-object))
1082 'sdl-cffi::sdl-pixel-format 'sdl-cffi::Bmask)
1083 (sdl:with-pixel (pixels (sdl:fp sdl-object))
1084 (multiple-value-list (sdl:read-pixel pixels 0 0)))
1085 (sdl:with-pixel (pixels (sdl:fp sdl-object))
1086 (multiple-value-list (sdl:read-pixel pixels 2 130))))
1088 ;; (defvar *joystick*)
1091 (defun render-debug-joystick ()
1092 (gl:with-primitive :points
1093 (flet ((color (state)
1095 (gl:color 0.0 0.0 0.0)
1096 (gl:color 1.0 1.0 1.0))))
1097 ;; FIXME: idiotic functions here, it's constant anyway
1098 (color (sdl-cffi::sdl-joystick-get-button *joystick* 0))
1101 (color (sdl-cffi::sdl-joystick-get-button *joystick* 1))
1104 (color (sdl-cffi::sdl-joystick-get-button *joystick* 2))
1107 (color (sdl-cffi::sdl-joystick-get-button *joystick* 3))
1110 (color (sdl-cffi::sdl-joystick-get-button *joystick* 4))
1113 (color (sdl-cffi::sdl-joystick-get-button *joystick* 5))
1116 (color (sdl-cffi::sdl-joystick-get-button *joystick* 6))
1119 (color (sdl-cffi::sdl-joystick-get-button *joystick* 7))
1122 (color (sdl-cffi::sdl-joystick-get-button *joystick* 8))
1125 (color (sdl-cffi::sdl-joystick-get-button *joystick* 9))
1129 (color (logand 8 (sdl-cffi::sdl-joystick-get-hat *joystick* 0)))
1132 (color (logand 1 (sdl-cffi::sdl-joystick-get-hat *joystick* 0)))
1135 (color (logand 2 (sdl-cffi::sdl-joystick-get-hat *joystick* 0)))
1138 (color (logand 4 (sdl-cffi::sdl-joystick-get-hat *joystick* 0)))
1143 ;; TODO: show edges, double clicks
1144 (defun render-debug-mouse (&optional (button (sdl::get-mouse-button)))
1145 (gl:with-primitive :points
1146 (flet ((color (mask)
1147 (if (eql 0 (logand button mask))
1148 (gl:color 0.0 0.0 0.0)
1149 (gl:color 1.0 1.0 1.0))))
1150 ;; FIXME: idiotic functions here, it's constant anyway
1151 (color #.(sdl-cffi::sdl-button-lmask))
1153 (color #.(sdl-cffi::sdl-button-rmask))
1156 (color #.(sdl-cffi::sdl-button-mmask))
1158 (color #.(sdl-cffi::sdl-button-wumask))
1160 (color #.(sdl-cffi::sdl-button-wdmask))
1163 (color #.(sdl-cffi::sdl-button-x1mask))
1165 (color #.(sdl-cffi::sdl-button-x2mask))
1168 (defvar *fix-texture* T)
1171 (defun render-debug-overlay ()
1172 (gl:polygon-mode :front-and-back :fill)
1173 (gl:hint :line-smooth-hint :fastest)
1174 (multiple-value-bind (width height)
1176 (gl:color 1 0.5 0.5)
1177 (let ((proggy (resource 'proggy-polygon))
1179 (rect (rectangle 0 0 width height)))
1180 (multiple-value-bind (render-width render-height)
1181 (render-aligned-text
1182 proggy (format NIL "FPS ~3D" (sdl:frame-rate)) size
1185 (declare (ignore render-width))
1186 (decf (aref rect 3) (+ render-height 3)))
1187 (when *debug-matrix*
1188 (render-aligned-matrix
1189 proggy *debug-matrix* size
1191 :vertical :right)))))
1193 (defun render-debug-overlay ()
1194 (multiple-value-bind (width height)
1196 (gl:with-pushed-matrix
1197 (gl:translate (- width 100) (- height 100) 0)
1198 (render-debug-mouse)
1199 ;; (gl:translate 0 50 0)
1200 ;; (render-debug-joystick)
1202 (let ((silkscreen-texture (resource 'silkscreen-texture))
1203 (texture (first (gl:gen-textures 1))))
1204 (ensure-resource silkscreen-texture)
1205 (gl:enable :blend :texture-2d)
1206 (let ((sdl-object (slot-value silkscreen-texture 'sdl-object)))
1207 ;; FIXME: effing hell, scaling to 255 is necessary
1209 (sdl:with-pixel (pixels (sdl:fp sdl-object))
1211 (for x from 0 below (sdl:width sdl-object))
1213 (for y from 0 below (sdl:height sdl-object))
1214 (when (eql (sdl:read-pixel pixels x y) 0)
1215 (sdl:write-pixel pixels x y 255))
1216 (when (eql (sdl:read-pixel pixels x y) 1)
1217 (sdl:write-pixel pixels x y 0)))))
1218 (setf *fix-texture* NIL))
1220 (gl:bind-texture :texture-2d texture)
1221 (gl:tex-parameter :texture-2d :texture-min-filter :nearest)
1222 (gl:tex-parameter :texture-2d :texture-mag-filter :nearest)
1224 (gl:tex-image-2d :texture-2d 0 :intensity
1225 (sdl:width sdl-object)
1226 (sdl:height sdl-object)
1229 (sdl:with-pixel (pixels (sdl:fp sdl-object))
1230 (sdl:pixel-data pixels)))
1231 (gl:generate-mipmap-ext :texture-2d))
1232 (gl:blend-func :one :one-minus-src-alpha)
1233 (gl:color 1.0 1.0 1.0 1.0)
1234 (let ((fps (format NIL "~D FPS" (floor (sdl:average-fps)))))
1235 (gl:translate (- width (* (length fps) 8)) (- height 8) 0)
1236 (gl:matrix-mode :texture)
1238 ;; this fixes the inverted loading from bmp format
1239 (gl:scale 1.0 -1.0 1.0)
1240 (gl:matrix-mode :modelview)
1241 (gl:with-primitive :quads
1244 (for character in-string fps)
1245 (for index = (- (char-code character) 32))
1246 (for (values y x) = (floor index 16))
1247 (setf x (/ x 16) y (- 1 (/ y 16)))
1248 (gl:tex-coord x (- y 1/16))
1249 (gl:vertex (* i 8) 0.0)
1251 (gl:tex-coord (+ x 1/16) (- y 1/16))
1252 (gl:vertex (* (1+ i) 8) 0.0)
1254 (gl:tex-coord (+ x 1/16) y)
1255 (gl:vertex (* (1+ i) 8) 8)
1258 (gl:vertex (* i 8) 8))))
1259 (gl:bind-texture :texture-2d 0)
1260 (gl:delete-textures (list texture))
1261 (gl:blend-func :one :zero)
1262 (gl:disable :blend :texture-2d))))
1264 (defun render-coordinate-arrows ()
1265 (gl:with-primitive :lines
1277 (defun render-camera-arrows ()
1278 (gl:with-pushed-matrix
1280 (quaternion-transform-vector3d*
1281 (vector3d-values* 1.0 0.0 0.0)
1282 (quaternion* (relative-rotation (transformation *camera*))))
1284 (gl:with-primitive :lines
1289 (quaternion-transform-vector3d*
1290 (vector3d-values* 0.0 1.0 0.0)
1291 (quaternion* (relative-rotation (transformation *camera*))))
1293 (gl:with-primitive :lines
1298 (quaternion-transform-vector3d*
1299 (vector3d-values* 0.0 0.0 -1.0)
1300 (quaternion* (relative-rotation (transformation *camera*))))
1302 (gl:with-primitive :lines
1305 (gl:vertex x y z)))))
1307 (defun update-scene-tree (scene-tree)
1308 (setf *delta-real-elapsed* (sdl::delta-ticks sdl:*default-fpsmanager*))
1309 (labels ((aux (list parent-transformation)
1310 (dolist (object list)
1312 (dolist (animator (animators object))
1313 (animate animator object))
1314 (aux (children object)
1315 (update-absolute-transformation
1317 parent-transformation)))))
1318 (aux scene-tree *identity-matrix44*)))
1322 (defun render-scene-tree (scene-tree)
1323 (labels ((aux (list)
1324 (dolist (object list)
1325 (let ((transformation (transformation object)))
1326 (when transformation
1328 ;; transpose, because opengl uses column-major mode for
1330 ;; TODO: also, have some c-level buffer here instead of
1331 ;; allocating all other the place
1332 (gl:mult-transpose-matrix
1335 (relative-transformation transformation)))))
1336 ;; TODO: maybe this UNWIND-PROTECT can be omitted, i.e.
1337 ;; "there'll be no errors", or if there are, a trashed
1338 ;; OpenGL state is the least of our problems?
1342 (aux (children object)))
1343 (when transformation
1344 (gl:pop-matrix)))))))
1347 (defun render-frame ()
1348 ;; TODO: assert the correct matrix mode here and reset if necessary
1349 (gl:clear :color-buffer :depth-buffer)
1350 ;; TODO: protect us from clobbering the setup? look at the matrix
1351 ;; stack and reset if necessary
1352 (with-all-pushed-matrixes
1354 (gl:enable :depth-test)
1355 (gl:with-pushed-matrix
1357 ;; TODO: render on "inside" the gui layer, i.e. in front of the
1358 ;; scene and rotate it to match the view orientation, like in
1360 (render-coordinate-arrows))
1361 ;; (render-camera-arrows)
1362 (render-scene-tree *scene-tree*))
1363 (with-all-pushed-matrixes
1365 (gl:with-pushed-matrix
1366 (render-debug-overlay)))
1367 (sdl:update-display))
1369 ;; (gl:shader-source fragment-shader-object (list fragment-shader))
1370 ;; (gl:compile-shader fragment-shader-object)
1371 ;; (format T "~A~%" (gl:get-shader-info-log fragment-shader-object))
1372 ;; (gl:attach-shader program-object fragment-shader-object)
1374 ;; (gl:shader-source vertex-shader-object (list vertex-shader))
1375 ;; (gl:compile-shader vertex-shader-object)
1376 ;; (format T "~A~%" (gl:get-shader-info-log vertex-shader-object))
1377 ;; (gl:attach-shader program-object vertex-shader-object)
1379 ;; (gl:link-program program-object)
1380 ;; (format T "~A~%" (gl:get-program-info-log program-object))
1381 ;; (gl:use-program program-object)
1383 ;; (fragment-shader (read-file-into-string "../data/shaders/green.glsl"))
1384 ;; (fragment-shader-object (gl:create-shader :fragment-shader))
1385 ;; (vertex-shader (read-file-into-string "../data/shaders/scale.glsl"))
1386 ;; (vertex-shader-object (gl:create-shader :vertex-shader))
1387 ;; (program-object (gl:create-program))
1389 ;; rotate around global z
1390 ;; which is more fps like
1392 (unit-quaternion-y* rotate-y)
1395 (defun apply-camera-rotation (rotate-x rotate-y rotate-z)
1397 (relative-rotation *camera*)
1401 (vector3d-values* old-x old-y old-z)
1402 (vector3d-values* rotate-x rotate-y (- rotate-z)))
1405 (relative-rotation *camera*)
1406 (vector3d-values* new-x new-y new-z)))))
1408 (defun inner-client ()
1412 (setup-camera (setf *camera* (make-fps-camera)))
1413 (add-scene-node *camera*)
1414 (setf (relative-position (transformation *camera*)) (make-vector3d 10.0 0.0 0.0))
1415 (setf (sdl:frame-rate) 30)
1417 (load-all-resources)
1418 (sdl:enable-unicode)
1419 (let ((rotate-x 0.0)
1425 (capture-mouse-p T))
1427 (:active-event (:gain gain)
1428 ;; FIXME: this needs better handling of the actual state, not just gain
1432 (setf capture-mouse-p NIL))
1434 (sdl:show-cursor NIL)
1435 (setf capture-mouse-p T))))
1437 (:video-expose-event () (render-frame))
1438 (:key-down-event (:key key :unicode unicode)
1439 (declare (ignore unicode))
1441 (:sdl-key-a (setf translate-x -10.0))
1442 (:sdl-key-d (setf translate-x 10.0))
1443 (:sdl-key-w (setf translate-z -10.0))
1444 (:sdl-key-s (setf translate-z 10.0))
1445 (:sdl-key-space (setf translate-y 10.0))
1446 (:sdl-key-lctrl (setf translate-y -10.0))
1447 (:sdl-key-q (setf rotate-z (to-radian -0.5)))
1448 (:sdl-key-e (setf rotate-z (to-radian 0.5)))
1450 (setf *window-fullscreen* (not *window-fullscreen*))
1453 (setup-camera *camera*))
1455 (invoke-restart 'abort-client))))
1456 (:key-up-event (:key key :unicode unicode)
1457 (declare (ignore unicode))
1459 (:sdl-key-a (setf translate-x 0.0))
1460 (:sdl-key-d (setf translate-x 0.0))
1461 (:sdl-key-w (setf translate-z 0.0))
1462 (:sdl-key-s (setf translate-z 0.0))
1463 (:sdl-key-q (setf rotate-z 0.0))
1464 (:sdl-key-e (setf rotate-z 0.0))
1465 (:sdl-key-space (setf translate-y 0.0))
1466 (:sdl-key-lctrl (setf translate-y 0.0))))
1467 (:mouse-button-down-event ())
1468 (:mouse-button-up-event ())
1469 (:joy-button-down-event ())
1470 (:mouse-motion-event (:state state :x x :y y :x-rel xrel :y-rel yrel)
1471 (declare (ignore state x y))
1472 (setf rotate-y (* xrel (to-radian 0.5)))
1473 (setf rotate-x (* yrel (to-radian 0.5)))
1474 (when capture-mouse-p
1475 (multiple-value-bind (width height)
1477 (sdl-cffi::sdl-warp-mouse (floor width 2) (floor height 2)))))
1479 (handle-messages *messages*)
1480 (update-scene-tree *scene-tree*)
1483 ;; TODO: limit rotation around x
1484 (apply-fps-camera-rotation rotate-x rotate-y rotate-z)
1485 ;; FIXME: vector3d-x expands into wrong code
1486 (setf (relative-position (transformation *camera*))
1489 (vector3d* (relative-position (transformation *camera*)))
1490 (quaternion-transform-vector3d*
1492 (vector3d-values* translate-x translate-y translate-z)
1493 (* 0.001 *delta-real-elapsed*))
1494 (quaternion* (relative-rotation (transformation *camera*)))))))
1495 ;; rotate-z left out, because it's controlled by keyboard
1496 (setf rotate-x 0.0 rotate-y 0.0 ;; rotate-z 0.0
1500 :report "Continue with the next frame.")))))))
1502 ;; seems like the direct calculation has some signs inverted
1505 (setf x (coerce x 'cl-tuples::fast-float)
1506 y (coerce y 'cl-tuples::fast-float)
1507 z (coerce z 'cl-tuples::fast-float))
1508 (let* ((sr (sin (/ x 2)))
1521 (quaternion-normalize*
1523 (- (* sr cpcy) (* cr spsy))
1524 (+ (* cr spcy) (* sr cpsy))
1525 (- (* cr cpsy) (* sr spcy))
1526 (+ (* cr cpcy) (* sr spsy)))))
1529 (quaternion-normalize*
1530 (quaternion-product*
1531 (quaternion-product*
1532 (unit-quaternion-x* z)
1533 (unit-quaternion-y* x))
1534 (unit-quaternion-z* y)))))))
1536 ;; no, this doesn't work at all
1539 (quaternion-normalize*
1540 (quaternion-product*
1541 (quaternion* (euler-xyz-to-quaternion rotate-x rotate-y rotate-z))
1542 (quaternion* (relative-rotation (transformation *camera*))))))