;;-*-Lisp-*- (in-package goal) (bundles "GAME.CGO") (require "engine/collide/collide-cache.gc") (require "engine/common-obs/process-drawable.gc") ;; DECOMP BEGINS ;; Each selected skeleton joint becomes an independently moving fragment. The tuning storage is ;; overlaid because fountain launches use component-wise velocity ranges, while radial launches use ;; the same 32 bytes as a focal point plus horizontal and vertical speed ranges. (deftype joint-exploder-tuning (structure) ((explosion uint64) (duration time-frame) (gravity float) (rot-speed float) (fountain-rand-transv-lo vector :inline) (fountain-rand-transv-hi vector :inline) (away-from-focal-pt vector :inline :overlay-at fountain-rand-transv-lo) (away-from-rand-transv-xz-lo float :overlay-at (-> fountain-rand-transv-hi x)) (away-from-rand-transv-xz-hi float :overlay-at (-> fountain-rand-transv-hi y)) (away-from-rand-transv-y-lo float :overlay-at (-> fountain-rand-transv-hi z)) (away-from-rand-transv-y-hi float :overlay-at (-> fountain-rand-transv-hi w))) (:methods (new (symbol type int) _type_))) (deftype joint-exploder-static-joint-params (structure) ((joint-index int16) (parent-joint-index int16))) (deftype joint-exploder-static-params (basic) ((joints (array joint-exploder-static-joint-params)))) (deftype joint-exploder-joint (structure) ((next int16) (prev int16) (joint-index int16) (rspeed float) (mat matrix :inline) (rmat matrix :inline) (transv vector :inline) (prev-pos vector :inline))) (deftype joint-exploder-joints (basic) ((num-joints int32) (joint joint-exploder-joint :inline :dynamic :offset 16)) (:methods (new (symbol type joint-exploder-static-params) _type_))) (deftype joint-exploder-list (structure) ((head int32) (pre-moved? symbol) (bbox-valid? symbol) (bbox bounding-box :inline))) ;; Lists 1 through 4 are spatial buckets for background collision. List 1 starts with every ;; fragment, and oversized bounds split at their midpoint into any empty bucket in that range. ;; When all four are occupied, the upper half spills into list 0: it still moves, draws, and ;; contributes to the overall bounds, but it is not subdivided or tested against the background. (deftype joint-exploder (process-drawable) ((parent-override (pointer process-drawable) :overlay-at parent) (die-if-below-y float) (die-if-beyond-xz-dist-sqrd float) (joints joint-exploder-joints) (static-params joint-exploder-static-params) (anim art-joint-anim) (scale-vector vector :inline) (tuning joint-exploder-tuning :inline) (lists joint-exploder-list 5 :inline)) (:methods (add-joint! (_type_ joint-exploder-list int) int) (grow-bbox! (_type_ joint-exploder-list joint-exploder-joint) none) (collide-joints! (_type_ joint-exploder-list) symbol) (init-joints! (_type_) symbol) (kill-joint! (_type_ joint-exploder-list int) int) (integrate-joints! (_type_ joint-exploder-list) symbol) (remove-joint! (_type_ joint-exploder-list int) int) (split-list! (_type_ joint-exploder-list int) joint-exploder-list) (subdivide-list! (_type_ joint-exploder-list) none)) (:states joint-exploder-shatter)) (defmethod asize-of ((this joint-exploder-joints)) "Return the dynamic allocation size: the sixteen-byte header plus 176 bytes per joint." (the-as int (+ (-> this type size) (* 176 (-> this num-joints))))) (defmethod new joint-exploder-joints ((allocation symbol) (type-to-make type) (static-params joint-exploder-static-params)) "Allocate the dynamic joint array sized from static-params." (let* ((joint-count (-> static-params joints length)) (result (object-new allocation type-to-make (the-as int (+ (-> type-to-make size) (* 176 joint-count)))))) (set! (-> result num-joints) joint-count) result)) (defun joint-exploder-joint-callback ((exploder joint-exploder)) "Compose each fragment's spin and simulated base transform into its skeleton joint, then apply the current disappearance scale to every skeleton transform." (let* ((skeleton-nodes (-> exploder node-list)) (joint-array (-> exploder joints)) (i (-> joint-array num-joints))) (while (nonzero? i) (+! i -1) (let* ((joint-data (-> joint-array joint i)) (bone-transform (-> skeleton-nodes data (-> joint-data joint-index) bone transform))) (matrix*! bone-transform (-> joint-data rmat) (-> joint-data mat)))) (let ((exploder-scale (-> exploder scale-vector))) (set! i (-> skeleton-nodes length)) (while (nonzero? i) (+! i -1) (let ((scaled-transform (-> skeleton-nodes data i bone transform))) (scale-matrix! scaled-transform exploder-scale scaled-transform))))) 0 (none)) (defmethod kill-joint! ((this joint-exploder) (joint-list joint-exploder-list) (joint-index int)) "Remove joint-index from joint-list, zero its transform basis, and place it at the exploder root so its fragment collapses out of view. Return the following list index." (let ((next-index (remove-joint! this joint-list joint-index))) (let* ((joint-array (-> this joints)) (joint-data (-> joint-array joint joint-index))) (set! (-> joint-data mat vector 0 quad) (the-as uint128 0)) (set! (-> joint-data mat vector 1 quad) (the-as uint128 0)) (set! (-> joint-data mat vector 2 quad) (the-as uint128 0)) (set! (-> joint-data mat vector 3 quad) (-> this root trans quad))) next-index)) (defmethod remove-joint! ((this joint-exploder) (joint-list joint-exploder-list) (joint-index int)) "Unlink joint-index from joint-list's doubly linked index chain and return its former next index. Invalidate the bounds when the list becomes empty." (let* ((joint-array (-> this joints)) (joint-data (-> joint-array joint joint-index)) (previous-index (-> joint-data prev)) (next-index (-> joint-data next))) (cond ((>= previous-index 0) (set! (-> joint-array joint previous-index next) next-index) (if (>= next-index 0) (set! (-> (the-as joint-exploder-joint (-> joint-array joint next-index)) prev) previous-index))) (else (set! (-> joint-list head) next-index) (cond ((>= next-index 0) (let ((next-joint-data (-> joint-array joint next-index))) (set! (-> next-joint-data prev) -1))) (else (set! (-> joint-list bbox-valid?) #f))))) next-index)) (defmethod add-joint! ((this joint-exploder) (joint-list joint-exploder-list) (joint-index int)) "Insert joint-index at the head of joint-list's doubly linked index chain." (let* ((joint-array (-> this joints)) (joint-data (-> joint-array joint joint-index)) (old-head-index (-> joint-list head))) (set! (-> joint-list head) joint-index) (set! (-> joint-data prev) -1) (set! (-> joint-data next) old-head-index) (when (>= old-head-index 0) (set! (-> (the-as joint-exploder-joint (-> joint-array joint old-head-index)) prev) joint-index) joint-index))) (defmethod grow-bbox! ((this joint-exploder) (joint-list joint-exploder-list) (joint-data joint-exploder-joint)) "Expand joint-list's bounds to include joint-data's current and previous positions, so the box encloses its complete swept segment for this frame." (let ((joint-position (-> joint-data mat vector 3))) (cond ((-> joint-list bbox-valid?) (add-point! (-> joint-list bbox) (the-as vector3s joint-position))) (else (set! (-> joint-list bbox-valid?) #t) (vector-copy! (-> joint-list bbox min) joint-position) (vector-copy! (-> joint-list bbox max) joint-position)))) (add-point! (-> joint-list bbox) (the-as vector3s (-> joint-data prev-pos))) (none)) (defmethod split-list! ((this joint-exploder) (joint-list joint-exploder-list) (split-axis int)) "Try to acquire an empty collision bucket from lists 1 through 4, split joint-list at its bounding-box midpoint on axis, and move members on the upper side into that bucket. When all four are occupied, spill into non-colliding list 0 and return false to stop recursion." (local-vars (current-joint joint-exploder-joint) (split-position float) (next-index int)) (let ((available-list (the-as joint-exploder-list #f))) (let ((list-index 1)) (until (= list-index 5) (let ((candidate-list (-> this lists list-index))) (when (< (-> candidate-list head) 0) (set! available-list candidate-list) (goto cfg-6))) (+! list-index 1))) (label cfg-6) (let ((destination-list (the-as object available-list))) (cond ((the-as joint-exploder-list destination-list) (set! (-> (the-as joint-exploder-list destination-list) pre-moved?) #t) (set! (-> (the-as joint-exploder-list destination-list) bbox-valid?) #f)) (else (set! destination-list (-> this lists)))) (set! (-> joint-list bbox-valid?) #f) (let ((joint-array (-> this joints)) (current-index (-> joint-list head))) (cond ((zero? split-axis) (set! split-position (* 0.5 (+ (-> joint-list bbox min x) (-> joint-list bbox max x)))) (while (>= current-index 0) (set! current-joint (-> joint-array joint current-index)) (cond ((>= (-> current-joint mat vector 3 x) split-position) (set! next-index (remove-joint! this joint-list current-index)) (add-joint! this (the-as joint-exploder-list destination-list) current-index) (set! current-index next-index) (grow-bbox! this (the-as joint-exploder-list destination-list) current-joint)) (else (grow-bbox! this joint-list current-joint) (set! current-index (-> current-joint next)))))) ((= split-axis 1) (set! split-position (* 0.5 (+ (-> joint-list bbox min y) (-> joint-list bbox max y)))) (while (>= current-index 0) (set! current-joint (-> joint-array joint current-index)) (cond ((>= (-> current-joint mat vector 3 y) split-position) (set! next-index (remove-joint! this joint-list current-index)) (add-joint! this (the-as joint-exploder-list destination-list) current-index) (set! current-index next-index) (grow-bbox! this (the-as joint-exploder-list destination-list) current-joint)) (else (grow-bbox! this joint-list current-joint) (set! current-index (-> current-joint next)))))) ((= split-axis 2) (set! split-position (* 0.5 (+ (-> joint-list bbox min z) (-> joint-list bbox max z)))) (while (>= current-index 0) (set! current-joint (-> joint-array joint current-index)) (cond ((>= (-> current-joint mat vector 3 z) split-position) (set! next-index (remove-joint! this joint-list current-index)) (add-joint! this (the-as joint-exploder-list destination-list) current-index) (set! current-index next-index) (grow-bbox! this (the-as joint-exploder-list destination-list) current-joint)) (else (grow-bbox! this joint-list current-joint) (set! current-index (-> current-joint next))))))))) available-list)) (defmethod subdivide-list! ((this joint-exploder) (joint-list joint-exploder-list)) "Recursively split valid nonempty lists whose X, Y, or Z extent exceeds five metres, prioritizing axes in that order. The small boxes limit the background collision-cache work." (when (and (-> joint-list bbox-valid?) (>= (-> joint-list head) 0)) (cond ((< 20480.0 (- (-> joint-list bbox max x) (-> joint-list bbox min x))) (let ((x-split-list (split-list! this joint-list 0))) (if x-split-list (subdivide-list! this x-split-list))) (subdivide-list! this joint-list)) ((< 20480.0 (- (-> joint-list bbox max y) (-> joint-list bbox min y))) (let ((y-split-list (split-list! this joint-list 1))) (if y-split-list (subdivide-list! this y-split-list))) (subdivide-list! this joint-list)) ((< 20480.0 (- (-> joint-list bbox max z) (-> joint-list bbox min z))) (let ((z-split-list (split-list! this joint-list 2))) (if z-split-list (subdivide-list! this z-split-list))) (subdivide-list! this joint-list)))) (none)) (defmethod integrate-joints! ((this joint-exploder) (joint-list joint-exploder-list)) "Advance every member of joint-list by one frame of gravity, linear velocity, and local-Z spin. Kill fragments outside the vertical or radial limits and rebuild swept bounds for the survivors." (set! (-> joint-list bbox-valid?) #f) (set! (-> joint-list pre-moved?) #t) (let ((joint-array (-> this joints)) (gravity-step (* (-> this tuning gravity) (seconds-per-frame))) (current-index (-> joint-list head))) (while (>= current-index 0) (let* ((joint-data (-> joint-array joint current-index)) (position (-> joint-data mat vector 3))) (set! (-> joint-data prev-pos quad) (-> position quad)) (+! (-> joint-data transv y) gravity-step) (vector-v+! position position (-> joint-data transv)) (let ((cos-step 0.99) (sin-step (* (-> joint-data rspeed) (seconds-per-frame))) (r00 (-> joint-data rmat vector 0 x)) (r01 (-> joint-data rmat vector 0 y)) (r10 (-> joint-data rmat vector 1 x)) (r11 (-> joint-data rmat vector 1 y))) (set! (-> joint-data rmat vector 0 x) (- (* r00 cos-step) (* r01 sin-step))) (set! (-> joint-data rmat vector 0 y) (+ (* r00 sin-step) (* r01 cos-step))) (set! (-> joint-data rmat vector 1 x) (- (* r10 cos-step) (* r11 sin-step))) (set! (-> joint-data rmat vector 1 y) (+ (* r10 sin-step) (* r11 cos-step)))) (cond ((or (< (-> position y) (-> this die-if-below-y)) (< (-> this die-if-beyond-xz-dist-sqrd) (vector-vector-xz-distance position (-> this root trans)))) (set! current-index (kill-joint! this joint-list current-index))) (else (grow-bbox! this joint-list joint-data) (set! current-index (-> joint-data next))))))) #f) (defmethod collide-joints! ((this joint-exploder) (joint-list joint-exploder-list)) "Fill the shared background collision cache for joint-list, sweep a 0.01-metre sphere along each joint's frame motion, and bounce hits with 75 percent horizontal and 70 percent vertical speed retention while halving spin." (fill-using-bounding-box *collide-cache* (-> joint-list bbox) (collide-kind background) this (new 'static 'pat-surface :noentity #x1)) (let ((joint-array (-> this joints)) (current-index (-> joint-list head))) (while (>= current-index 0) (let ((joint-data (-> joint-array joint current-index))) (let ((position (-> joint-data mat vector 3)) (frame-motion (new 'stack-no-clear 'vector)) (probe-result (new 'stack-no-clear 'collide-tri-result))) (vector-! frame-motion position (-> joint-data prev-pos)) (when (>= (probe-using-line-sphere *collide-cache* (-> joint-data prev-pos) frame-motion 40.96 (collide-kind background) probe-result (new 'static 'pat-surface :noentity #x1)) 0.0) (set! (-> position quad) (-> probe-result intersect quad)) (let ((horizontal-speed (vector-xz-length (-> joint-data transv)))) (vector-reflect! (-> joint-data transv) (-> joint-data transv) (-> probe-result normal)) (let ((vertical-speed (-> joint-data transv y))) (set! (-> joint-data transv y) 0.0) (vector-normalize! (-> joint-data transv) (* 0.75 horizontal-speed)) (set! (-> joint-data transv y) (* 0.7 vertical-speed)))) (+! (-> position y) (* 40.96 (-> probe-result normal y))) (set! (-> position w) 1.0) (set! (-> joint-data rspeed) (/ (-> joint-data rspeed) 2)))) (set! current-index (-> joint-data next))))) #f) (defstate joint-exploder-shatter (joint-exploder) :enter (behavior () (set-time! (-> self state-time))) :trans (behavior () ;; Shrink Y to zero after three quarters of the lifetime, then finish collapsing X/Z. Lists ;; are marked as they move because subdivision can transfer joints into a later list. (local-vars (joint-list joint-exploder-list)) (let* ((elapsed-frames (the float (- (current-time) (-> self state-time)))) (horizontal-scale (- 1.0 (/ elapsed-frames (the float (-> self tuning duration))))) (vertical-scale (- 1.0 (/ elapsed-frames (* 0.75 (the float (-> self tuning duration))))))) (if (< vertical-scale 0.0) (set! vertical-scale 0.0)) (set-vector! (-> self scale-vector) horizontal-scale vertical-scale horizontal-scale 1.0)) (let ((i 0)) (while (< i 5) (set! (-> self lists i pre-moved?) #f) (+! i 1)) (set! i 0) (while (< i 5) (set! joint-list (-> self lists i)) (when (>= (-> joint-list head) 0) (when (not (-> joint-list pre-moved?)) (integrate-joints! self joint-list) (if (nonzero? i) (subdivide-list! self joint-list)))) (+! i 1)) (let ((combined-bounds (new 'stack-no-clear 'bounding-box))) (let ((root-position (-> self root trans))) (vector-copy! (-> combined-bounds min) root-position) (vector-copy! (-> combined-bounds max) root-position)) (set! i 0) (while (< i 5) (set! joint-list (-> self lists i)) (if (-> joint-list bbox-valid?) (add-box! combined-bounds (-> joint-list bbox))) (if (nonzero? i) (collide-joints! self joint-list)) (+! i 1)) (let ((draw-bounds (-> self draw bounds))) (set-vector! draw-bounds (* 0.5 (+ (-> combined-bounds min x) (-> combined-bounds max x))) (* 0.5 (+ (-> combined-bounds min y) (-> combined-bounds max y))) (* 0.5 (+ (-> combined-bounds min z) (-> combined-bounds max z))) 1.0) (let ((bound-radius (+ 16384.0 (vector-vector-distance draw-bounds (-> combined-bounds max))))) (vector-! draw-bounds draw-bounds (-> self root trans)) (set! (-> draw-bounds w) bound-radius))))) 0) :code (behavior () (set-time! (-> self state-time)) (until (time-elapsed? (-> self state-time) (-> self tuning duration)) (suspend) (ja :num! (loop!)))) :post ja-post) (defmethod init-joints! ((this joint-exploder)) "Build each fragment's base transform, spin matrix, linked-list indices, and launch velocity from the static joint table and selected explosion style, then seed the main list and bounds." (let ((joint-array (-> this joints)) (i 0)) (while (< i (-> joint-array num-joints)) (let ((static-joint (-> this static-params joints i)) (joint-data (-> joint-array joint i))) (let ((parent-index (-> static-joint parent-joint-index))) (set! (-> joint-data prev) (+ i -1)) (set! (-> joint-data next) (+ i 1)) (set! (-> joint-data joint-index) (-> static-joint joint-index)) (set! (-> joint-data rspeed) (-> this tuning rot-speed)) (cond ((>= parent-index 0) (if (zero? parent-index) (set! parent-index (-> static-joint joint-index))) (matrix-copy! (-> joint-data mat) (-> this parent-override 0 node-list data parent-index bone transform)) (matrix-identity! (-> joint-data rmat))) (else (matrix-copy! (-> joint-data mat) (-> this node-list data (-> static-joint joint-index) bone transform)) (matrix-identity! (-> joint-data rmat))))) (case (-> this tuning explosion) ((1) (vector-! (-> joint-data transv) (-> joint-data mat vector 3) (-> this tuning fountain-rand-transv-lo)) (vector-normalize! (-> joint-data transv) (rand-vu-float-range (-> this tuning fountain-rand-transv-hi x) (-> this tuning fountain-rand-transv-hi y))) (+! (-> joint-data transv y) (rand-vu-float-range (-> this tuning fountain-rand-transv-hi z) (-> this tuning fountain-rand-transv-hi w))) (set! (-> joint-data transv w) 1.0)) (else (let ((velocity-low (-> this tuning fountain-rand-transv-lo)) (velocity-high (-> this tuning fountain-rand-transv-hi))) (set-vector! (-> joint-data transv) (rand-vu-float-range (-> velocity-low x) (-> velocity-high x)) (rand-vu-float-range (-> velocity-low y) (-> velocity-high y)) (rand-vu-float-range (-> velocity-low z) (-> velocity-high z)) 1.0))))) (+! i 1)) (when (nonzero? (-> joint-array num-joints)) (let ((last-joint (-> joint-array joint (+ (-> joint-array num-joints) -1)))) (set! (-> last-joint next) -1)) (let ((main-list (-> this lists 1))) (set! (-> main-list head) 0) (let ((main-bounds (-> main-list bbox))) (let ((first-position (-> joint-array joint 0 mat vector 3))) (set! (-> main-bounds min quad) (-> first-position quad)) (set! (-> main-bounds max quad) (-> first-position quad))) (set! i 0) (while (< i (-> joint-array num-joints)) (add-point! main-bounds (the-as vector3s (-> joint-array joint i mat vector 3))) (+! i 1)))) #f))) (defmethod relocate ((this joint-exploder) (offset int)) "Relocate the separately allocated dynamic joint array, then relocate the drawable." (if (nonzero? (-> this joints)) (&+! (-> this joints) offset)) (the-as joint-exploder ((method-of-type process-drawable relocate) this offset))) (defbehavior joint-exploder-init-by-other joint-exploder ((exploder-sg skeleton-group) (animation-index int) (tuning joint-exploder-tuning) (static-params joint-exploder-static-params)) "Spawn fragments from parent using exploder-sg and animation-index. Copy tuning, allocate the static joint selection, inherit the parent's root transform, pose the animation, initialize the fragments, install the post-bind callback, and enter the shatter state." (set! (-> self static-params) static-params) (set! (-> self die-if-beyond-xz-dist-sqrd) 10485760000.0) (mem-copy! (the-as pointer (-> self tuning)) (the-as pointer tuning) 64) (set! (-> self joints) (new 'process 'joint-exploder-joints static-params)) (dotimes (i 5) (let ((joint-list (-> self lists i))) (set! (-> joint-list head) -1) (set! (-> joint-list bbox-valid?) #f) (set! (-> joint-list pre-moved?) #f))) (logior! (-> self mask) (process-mask enemy)) (set! (-> self root) (new 'process 'trsqv)) (vector-copy! (-> self root trans) (-> self parent-override 0 root trans)) (quaternion-copy! (-> self root quat) (-> self parent-override 0 root quat)) (vector-copy! (-> self root scale) (-> self parent-override 0 root scale)) (initialize-skeleton self exploder-sg '()) (logior! (-> self skel status) (janim-status inited)) (set! (-> self anim) (the-as art-joint-anim (-> self draw art-group data animation-index))) (ja-channel-set! 1) (ja :group! (-> self anim) :num! min) (ja-post) (init-joints! self) (set! (-> self die-if-below-y) (+ -102400.0 (-> self root trans y))) (set! (-> self skel postbind-function) joint-exploder-joint-callback) (go joint-exploder-shatter) (none)) (defmethod new joint-exploder-tuning ((allocation symbol) (type-to-make type) (explosion-style int)) "Allocate default tuning for explosion-style. Style 0 uses component-wise random fountain velocity; style 1 launches away from a focal point with randomized horizontal and vertical speeds." (let ((parent-new (method-of-type structure new)) (requested-type type-to-make)) (-> type-to-make size) (let ((result (the-as joint-exploder-tuning (parent-new allocation requested-type)))) (set! (-> result explosion) (the-as uint explosion-style)) (set! (-> result duration) (seconds 2)) (set! (-> result gravity) -286720.0) (set! (-> result rot-speed) 8.4) (cond ((zero? explosion-style) (set-vector! (-> result fountain-rand-transv-lo) -81920.0 20480.0 -81920.0 1.0) (set-vector! (-> result fountain-rand-transv-hi) 81920.0 61440.0 81920.0 1.0)) ((= explosion-style 1) (vector-reset! (-> result fountain-rand-transv-lo)) (set! (-> result fountain-rand-transv-hi x) 49152.0) (set! (-> result fountain-rand-transv-hi y) 163840.0) (set! (-> result fountain-rand-transv-hi z) 20480.0) (set! (-> result fountain-rand-transv-hi w) 61440.0))) result)))