wh-engine/wh-engine/actor.lisp

362 lines
12 KiB
Common Lisp

;;;; wh-engine/actor.lisp
(in-package wh-engine)
(defclass actor ()
((id :documentation "This actor's unique ID."
:reader id
:type fixnum
:initarg :id
:initform (make-id))
(name :documentation "This actor's human-readable name."
:accessor name
:type string
:initarg :name
:initform "")
(scene :documentation "The scene containing this actor."
:reader scene
:type pointer
:initform nil)
(tags :documentation "This actor's tags."
:reader tags
:type (proper-list symbol)
:initarg :tags
:initform '(:default))
(active-p :documentation "Whether or not this actor should be processed."
:reader active-p
:type boolean
:initform t)
(blocked-p :documentation "Whether or not this actor has a deactivated ancestor."
:type boolean
:initform nil)
(parent :documentation "This actor's parent."
:reader parent
:type pointer
:initarg :parent
:initform nil)
(children :documentation "The actors this actor is a parent of."
:reader children
:type (proper-list pointer)
:initform nil)
(components :documentation "The components attached to this actor."
:reader components
:type (proper-list component)
:initform nil)
(destroyed-p :documentation "If true, this actor will be unloaded."
:reader destroyed-p
:type boolean
:initform nil)
;; Transform data
(location :documentation "The actor's location relative to its parent."
:reader location
:type vec2
:initarg :location
:initform (vec2 0 0))
(z-layer :documentation "The actor's Z layer."
:accessor z-layer
:type fixnum
:initarg :z-layer
:initform 0)
(rotation :documentation "The actor's rotation relative to its parent."
:reader rotation
:type single-float
:initarg :rotation
:initform 0.0)
(scale :documentation "The actor's scale relative to its parent."
:reader scale
:type vec2
:initarg :scale
:initform (vec2 1 1))
(matrix :documentation "Local-to-parent-space transformation matrix."
:reader matrix
:type mat3
:initform (meye 3)))
(:documentation "Base class for entities in the game."))
(defmethod make-load-form ((this actor) &optional environment)
(make-load-form-saving-slots this :environment environment))
(defmethod scene ((this actor))
"The scene containing this actor."
(deref-sus-pointer [this :slot scene]))
(defmethod parent ((this actor))
"This actor's parent."
(deref-sus-pointer [this :slot parent]))
(defmethod tree-active-p ((this actor))
"Whether or not this actor and all its parents are active."
(and [this active-p] (not [this :slot blocked-p])))
(defmethod print-object ((this actor) stream)
(print-unreadable-object (this stream :type t :identity t)
(prin1 [this :slot id] stream)
(princ " ")
(prin1 [this :slot name] stream)))
(defmethod get-component ((this actor) component-class)
"Get a component of the specified class attached to this object."
(find-if (lambda (component) (typep component component-class))
[this components]))
(defmethod add-component ((this actor) component)
"Add a component to this object."
(let ((component-class (class-of component)))
(when [this (get-component component-class)]
(error "~S already has a component of class ~S" this component-class))
(push component [this :slot components])
[component (attach this)])
component)
(defmethod add-child ((this actor) child)
"Add a child to this object."
(when [child parent]
(error "~S is already a child of ~S" child [child parent]))
(push (make-weak-pointer child) [this :slot children])
(setf [child :slot parent] (make-weak-pointer this))
[child (parent-changed)]
child)
(defmethod remove-child ((this actor) child)
"Remove a child from this object."
(unless (eq [child parent] this)
(error "~S is not a child of ~S" child this))
(setf [this :slot children] (delete child [this :slot children] :key #'deref-sus-pointer :count 1))
(setf [child :slot parent] nil)
[child (parent-changed)]
child)
(defmethod recompute-blocked-p ((this actor))
"Determine if any ancestors of this actor are deactivated."
(setf [this :slot blocked-p]
(when [this parent] (or (not [this parent active-p]) [this parent :slot blocked-p]))))
(defmethod has-tag ((this actor) tag)
"Check if this object has the specified tag."
(find tag [this tags]))
(defmethod add-tag ((this actor) tag)
"Add a tag to this object."
(pushnew tag [this :slot tags]))
(defmethod remove-tag ((this actor) tag)
"Remove a tag from this object."
(setf [this :slot tags] (remove tag [this :slot tags])))
(defmethod parent-deactivated ((this actor) parent)
"Called when the actor's parent is deactivated."
[this (recompute-blocked-p)]
(loop for component in [this components]
do [component (parent-deactivated parent)])
(loop for child-ptr in [this children]
for child = (weak-pointer-value child-ptr)
do [child (parent-deactivated parent)]))
(defmethod parent-activated ((this actor) parent)
"Called when the actor's parent is activated."
[this (recompute-blocked-p)]
(when [this tree-active-p]
(loop for child-ptr in [this children]
for child = (weak-pointer-value child-ptr)
do [child (parent-activated parent)])
(loop for component in [this components]
do [component (parent-activated parent)])))
(defmethod parent-changed ((this actor))
"Called when the actor's parent is changed."
[this (recompute-blocked-p)]
(loop for component in [this components]
do [component (parent-changed)]))
(defmethod deactivate ((this actor))
"Deactivate this object."
(setf [this :slot active-p] nil)
(loop for component in [this components]
do [component (parent-deactivated this)])
(loop for child-ptr in [this children]
for child = (weak-pointer-value child-ptr)
do [child (parent-deactivated this)]))
(defmethod activate ((this actor))
"Activate this object."
(setf [this :slot active-p] t)
(loop for child-ptr in [this children]
for child = (weak-pointer-value child-ptr)
do [child (parent-activated this)])
(loop for component in [this components]
do [component (parent-activated this)]))
(defmethod resume ((this actor))
"Initialize or restore this actor's state."
;; Restore self
(when (typep [this :slot scene] 'id-ref)
;; relink to scene
(let ((scene (get-scene (id-ref-scene [this :slot scene]))))
(setf [this :slot scene] nil)
[scene (add-actor this)]))
(when (typep [this :slot parent] 'id-ref)
;; relink to parent
(let ((parent [this scene (get-actor (id-ref-actor [this :slot parent]))]))
(setf [this :slot parent] nil)
[parent (add-child this)]))
(loop for entry on [this :slot children]
when (typep (car entry) 'id-ref)
do (rplaca entry (pointerize (car entry))))
;; Restore components
(loop for component in [this components]
do [component (resume)])
;; Restore children
(loop for child-ptr in [this children]
for child = (weak-pointer-value child-ptr)
do [child (resume)]))
(defmethod suspend ((this actor))
"Prepare this actor for serialization."
;; Suspend children
(loop for child-ptr in [this children]
for child = (weak-pointer-value child-ptr)
do [child (suspend)])
;; Suspend components
(loop for component in [this components]
do [component (suspend)])
;; Suspend self
(loop for child-cell on [this :slot children]
when (typep (car child-cell) 'weak-pointer)
do (rplaca child-cell (referize (car child-cell))))
(referize-setf [this :slot scene])
(referize-setf [this :slot parent]))
(defmethod update ((this actor))
"Update this actor's components."
(loop for component in [this components]
do (when [component active-p]
(unless [component started-p] [component (start)])
[component (update)]))
; (loop for child in [this children]
; do (when [child active-p]
; [child (.update)]))
)
(defmethod destroy ((this actor))
"Mark this object for unloading."
(unless [this destroyed-p]
; Cleanup on aisle 5!
(loop for component in [this components]
when [component active-p]
do [component (destroy)])
; Remove from parent
(when [this parent]
[this parent (remove-child this)])
(loop for child-ptr in [this children]
for child = (deref-sus-pointer child-ptr)
do [child (destroy)])
(when [this scene]
[this scene (remove-actor this)]))
(setf [this :slot destroyed-p] t))
;; Transform
(defmethod initialize-instance :after ((this actor) &key)
[this (recompute-matrix)])
(defmethod recompute-matrix ((this actor))
"Recompute the local-to-parent-space matrix."
(let ((rs (sin [this rotation]))
(rc (cos [this rotation]))
(sx (vx2 [this scale]))
(sy (vy2 [this scale]))
(tx (vx2 [this location]))
(ty (vy2 [this location])))
(with-fast-matref (m [this :slot matrix] 3)
(setf (m 0 0) (* sx rc)
(m 0 1) (* sy (- rs))
(m 0 2) tx
(m 1 0) (* sx rs)
(m 1 1) (* sy rc)
(m 1 2) ty)
)))
(defmethod (setf location) (new-val (this actor))
"The actor's location relative to its parent."
(declare (type vec2 new-val))
(setf (vx2 [this :slot location]) (vx2 new-val)
(vy2 [this :slot location]) (vy2 new-val))
[this (recompute-matrix)])
(defmethod (setf rotation) (new-val (this actor))
"The actor's rotation relative to its parent."
(declare (type single-float new-val))
(setf [this :slot rotation] new-val)
[this (recompute-matrix)])
(defmethod (setf scale) (new-val (this actor))
"The actor's scale relative to its parent."
(declare (type vec2 new-val))
(setf (vx2 [this :slot scale]) (vx2 new-val)
(vy2 [this :slot scale]) (vy2 new-val))
[this (recompute-matrix)])
(defmethod world-matrix ((this actor))
"The local-to-world-space transformation matrix for this actor."
(if [this parent]
(m* [this matrix] [this parent world-matrix])
[this matrix]))
(defmethod local-matrix ((this actor))
"The world-to-local-space transformation matrix for this actor."
(minv [this world-matrix]))
(defmethod world-location ((this actor))
"The world-space location of this actor."
(vxy (m* [this world-matrix] (vec3 0 0 1))))
(defmethod transform-point ((this actor) point)
"Transform point from local space to parent space."
(declare (type vec2 point))
(vxy (m* [this matrix] (vxy1 point))))
(defmethod world-point ((this actor) point)
"Transform point from local space to world space."
(declare (type vec2 point))
(vxy (m* [this world-matrix] (vxy1 point))))
(defmethod local-point ((this actor) point)
"Transform point from world space to local space."
(declare (type vec2 point))
(vxy (m* [this local-matrix] (vxy1 point))))
(defmethod transform-svector ((this actor) vector)
"Transform vector from local space to parent space."
(declare (type vec2 vector))
(vxy (m* [this matrix] (vxy_ vector))))
(defmethod transform-vector ((this actor) vector)
"Transform vector from local space to parent space, without changing its length."
(declare (type vec2 vector))
(nvscale [this (transform-svector vector)] (vlength vector)))
(defmethod translate-by ((this actor) vector)
"Translate this actor by the given vector in parent space."
(declare (type vec2 vector))
(setf [this location] (v+ [this location] vector)))
(defmethod rotate-by ((this actor) angle)
"Rotate this actor by the given angle."
(declare (type single-float angle))
(setf [this rotation] (+ [this rotation] angle)))
(defmethod scale-by ((this actor) factor)
"Scale this actor by the given factor (either a scalar or a vector)."
(declare (type (or single-float vec2) factor))
(setf [this scale] (v* [this scale] factor)))