Files
jak-project/goal_src/jak1/engine/util/sync-info.gc
T
2026-07-28 12:00:35 -07:00

404 lines
22 KiB
Common Lisp

;;-*-Lisp-*-
(in-package goal)
(bundles "ENGINE.CGO" "GAME.CGO")
(require "engine/entity/res.gc")
(require "engine/util/smush-control-h.gc")
(require "engine/util/sync-info-h.gc")
;; DECOMP BEGINS
(defmethod setup-params! ((this sync-info) (period uint) (phase float) (ease-out float) (ease-in float))
"Set the period in 300 Hz game-time ticks and the initial phase as a fraction of that period.
The base clock ignores ease-out and ease-in, which are retained for the shared subtype interface."
(set! (-> this period) period)
(let* ((period-float (the float period))
(phase-ticks (* phase period-float)))
;; This is fmod for the phase values used here.
(set! (-> this offset) (- phase-ticks (* (the float (the int (/ phase-ticks period-float))) period-float))))
0
(none))
(defmethod setup-params! ((this sync-info-eased) (period uint) (phase float) (ease-out float) (ease-in float))
"Set the period and initial phase, then build a normalized quadratic-linear-quadratic easing
curve for each leg of the mirrored phase. ease-out is the accelerating fraction at the start and
ease-in is the decelerating fraction at the end; both are clamped, and ease-out is shortened if
they overlap."
(set! (-> this period) period)
(let* ((period-float (the float period))
(phase-ticks (* phase period-float)))
(set! (-> this offset) (- phase-ticks (* (the float (the int (/ phase-ticks period-float))) period-float))))
(if (< ease-out 0.0) (set! ease-out 0.0))
(if (< 1.0 ease-out) (set! ease-out 1.0))
(if (< ease-in 0.001) (set! ease-in 0.001))
(if (< 1.0 ease-in) (set! ease-in 1.0))
(let ((total-easing-phase (+ ease-out ease-in)))
(when (< 1.0 total-easing-phase)
(set! total-easing-phase 1.0)
(set! ease-out (- 1.0 ease-in)))
(let* ((total-normal-phase (- 1.0 total-easing-phase))
(tlo ease-out)
(thi (+ ease-out total-normal-phase))
(ylo (square tlo))
(y-at-thi (+ (* 2.0 tlo (- thi tlo)) ylo))
(m2 (/ tlo (- 1.0 thi)))
(y-end (+ (* (- 1.0 thi) (- 1.0 thi) m2) y-at-thi)))
(set! (-> this tlo) tlo)
(set! (-> this thi) thi)
(set! (-> this ylo) ylo)
(set! (-> this m2) m2)
(set! (-> this yend) y-end)))
0
(none))
(defmethod setup-params! ((this sync-info-paused) (period uint) (phase float) (pause-after-out float) (pause-after-in float))
"Set the period and initial phase plus endpoint pauses measured as fractions of the complete
cycle. pause-after-out holds the value at one and pause-after-in holds it at zero; both are clamped
and pause-after-in is shortened if the pauses would overlap."
(set! (-> this period) period)
(let* ((period-float (the float period))
(phase-ticks (* phase period-float)))
(set! (-> this offset) (- phase-ticks (* (the float (the int (/ phase-ticks period-float))) period-float))))
(cond
((< pause-after-out 0.0) (set! pause-after-out 0.0))
((< 1.0 pause-after-out) (set! pause-after-out 1.0)))
(cond
((< pause-after-in 0.0) (set! pause-after-in 0.0))
;; The two endpoint pauses may not overlap.
((< (- 1.0 pause-after-out) pause-after-in) (set! pause-after-in (- 1.0 pause-after-out))))
(set! (-> this pause-after-in) pause-after-in)
(set! (-> this pause-after-out) pause-after-out)
0
(none))
(defmethod load-params! ((this sync-info) (proc process) (default-period uint) (default-phase float) (default-ease-out float) (default-ease-in float))
"Load the sync resource from proc. Its first two floats are the period in seconds and initial
phase; the period is converted to 300 Hz game-time ticks. Return true when the resource exists,
otherwise install the supplied defaults and return false. The base clock ignores default-ease-out
and default-ease-in."
(local-vars (sync-tag res-tag))
(set! sync-tag (new 'static 'res-tag))
(let ((params (res-lump-data (-> proc entity) 'sync pointer :tag-ptr (& sync-tag))))
(cond
(params
(setup-params! this
(the-as uint (the int (* 300.0 (-> (the-as (pointer float) params) 0))))
(-> (the-as (pointer float) params) 1)
0.15
0.15)
#t)
(else (setup-params! this default-period default-phase 0.15 0.15) #f))))
(defmethod load-params! ((this sync-info-eased) (proc process) (default-period uint) (default-phase float) (default-ease-out float) (default-ease-in float))
"Load the sync resource from proc. Four floats supply period seconds, initial phase,
ease-out, and ease-in; a two-value resource uses the supplied easing defaults. Return true when the
resource exists, otherwise install every supplied default and return false."
(local-vars (sync-tag res-tag))
(set! sync-tag (new 'static 'res-tag))
(let ((params (res-lump-data (-> proc entity) 'sync pointer :tag-ptr (& sync-tag))))
(cond
(params
(if (>= (-> sync-tag elt-count) (the-as uint 4))
(setup-params! this
(the-as uint (the int (* 300.0 (-> (the-as (pointer float) params) 0))))
(-> (the-as (pointer float) params) 1)
(-> (the-as (pointer float) params) 2)
(-> (the-as (pointer float) params) 3))
(setup-params! this
(the-as uint (the int (* 300.0 (-> (the-as (pointer float) params) 0))))
(-> (the-as (pointer float) params) 1)
default-ease-out
default-ease-in))
#t)
(else (setup-params! this default-period default-phase default-ease-out default-ease-in) #f))))
(defmethod load-params! ((this sync-info-paused) (proc process) (default-period uint) (default-phase float) (default-pause-after-out float) (default-pause-after-in float))
"Load the sync resource from proc. Four floats supply period seconds, initial phase,
pause-after-out, and pause-after-in; a two-value resource uses the supplied pause defaults. Return
true when the resource exists, otherwise install every supplied default and return false."
(local-vars (sync-tag res-tag))
(set! sync-tag (new 'static 'res-tag))
(let ((params (res-lump-data (-> proc entity) 'sync pointer :tag-ptr (& sync-tag))))
(cond
(params
(if (>= (-> sync-tag elt-count) (the-as uint 4))
(setup-params! this
(the-as uint (the int (* 300.0 (-> (the-as (pointer float) params) 0))))
(-> (the-as (pointer float) params) 1)
(-> (the-as (pointer float) params) 2)
(-> (the-as (pointer float) params) 3))
(setup-params! this
(the-as uint (the int (* 300.0 (-> (the-as (pointer float) params) 0))))
(-> (the-as (pointer float) params) 1)
default-pause-after-out
default-pause-after-in))
#t)
(else (setup-params! this default-period default-phase default-pause-after-out default-pause-after-in) #f))))
(defmethod get-current-phase-no-mod ((this sync-info))
"Return the base clock's wrapped phase from 0 through 1 without applying a subtype's easing
or endpoint pauses."
(let* ((period (-> this period))
(period-float (the float period))
(current-time (+ (the float (mod (the-as uint (current-time)) period)) (-> this offset))))
(/ (- current-time (* (the float (the int (/ current-time period-float))) period-float)) period-float)))
(defmethod get-phase-offset ((this sync-info))
"Return the stored tick offset as a fraction of the period."
(/ (-> this offset) (the float (-> this period))))
(defmethod sync-now! ((this sync-info) (phase-now float))
"Adjust the stored tick offset so the current normalized phase equals phase-now, then return
the wrapped offset."
(let* ((period (-> this period))
(period-float (the float period))
;; phase-now is normally in [0, 1], so this period-sized wrap leaves it unchanged.
(wrapped-phase-now (- phase-now (* (the float (the int (/ phase-now period-float))) period-float)))
(current-time (+ (the float (mod (the-as uint (current-time)) period)) (-> this offset)))
(current-phase (/ (- current-time (* (the float (the int (/ current-time period-float))) period-float)) period-float))
(new-offset (+ (* (- wrapped-phase-now current-phase) period-float) period-float (-> this offset))))
(set! (-> this offset) (- new-offset (* (the float (the int (/ new-offset period-float))) period-float)))))
(defmethod get-current-phase ((this sync-info))
"Return the current sawtooth phase from 0 through 1."
(let* ((period (-> this period))
(period-float (the float period))
(current-time (+ (the float (mod (the-as uint (current-time)) period)) (-> this offset))))
(/ (- current-time (* (the float (the int (/ current-time period-float))) period-float)) period-float)))
(defmethod get-current-phase ((this sync-info-paused))
"Return a zero-to-one sawtooth that reaches one early, then holds there for the configured
pause-after-out fraction of the cycle. This non-mirrored form does not use pause-after-in."
(let* ((period (-> this period))
(period-float (the float period))
(max-phase 1.0)
(current-time (+ (the float (mod (the-as uint (current-time)) period)) (-> this offset))))
(fmin max-phase
(/ (- current-time (* (the float (the int (/ current-time period-float))) period-float))
(* period-float (- 1.0 (-> this pause-after-out)))))))
(defmethod get-current-value ((this sync-info) (max-value float))
"Return the current sawtooth phase multiplied by max-value."
(let* ((period (-> this period))
(period-float (the float period))
(current-time (+ (the float (mod (the-as uint (current-time)) period)) (-> this offset))))
(* (/ (- current-time (* (the float (the int (/ current-time period-float))) period-float)) period-float) max-value)))
(defmethod get-current-value ((this sync-info-paused) (max-value float))
"Return the paused non-mirrored phase multiplied by max-value."
(* (get-current-phase this) max-value))
(defmethod get-current-phase-with-mirror ((this sync-info))
"Return a triangular phase that moves from zero to one and back to zero during each period."
(let* ((period (-> this period))
(period-float (the float period))
(mirror-scale 2.0)
(current-time (+ (the float (mod (the-as uint (current-time)) period)) (-> this offset)))
(mirrored-phase (* mirror-scale (/ (- current-time (* (the float (the int (/ current-time period-float))) period-float)) period-float))))
(if (>= mirrored-phase 1.0) (set! mirrored-phase (- 2.0 mirrored-phase)))
mirrored-phase))
(defmethod get-current-phase-with-mirror ((this sync-info-eased))
"Return the mirrored zero-to-one-to-zero phase after applying the stored
quadratic-linear-quadratic easing curve independently to each leg."
(let* ((period (-> this period))
(period-float (the float period))
(mirror-scale 2.0)
(current-time (+ (the float (mod (the-as uint (current-time)) period)) (-> this offset)))
(leg-phase (* mirror-scale (/ (- current-time (* (the float (the int (/ current-time period-float))) period-float)) period-float)))
(returning? #f))
(when (>= leg-phase 1.0)
(set! returning? #t)
(set! leg-phase (+ -1.0 leg-phase)))
(let* ((tlo (-> this tlo))
(phase (/ (cond
((< leg-phase tlo)
;; Accelerate quadratically from zero slope.
(square leg-phase))
((< leg-phase (-> this thi))
;; Continue at the tangent reached at tlo.
(+ (* 2.0 tlo (- leg-phase tlo)) (-> this ylo)))
(else
(let ((remaining-phase (- 1.0 leg-phase)))
;; Decelerate quadratically to zero slope at the endpoint.
(- (-> this yend) (* (square remaining-phase) (-> this m2))))))
(-> this yend))))
(if returning? (set! phase (- 1.0 phase)))
phase)))
(defmethod get-current-phase-with-mirror ((this sync-info-paused))
"Return a zero-to-one-to-zero phase whose moving legs are shortened to leave the configured
holds at one and zero."
(let* ((period (-> this period))
(period-float (the float period))
(mirror-scale 2.0)
(current-time (+ (the float (mod (the-as uint (current-time)) period)) (-> this offset)))
(mirrored-phase (* mirror-scale (/ (- current-time (* (the float (the int (/ current-time period-float))) period-float)) period-float)))
(return-duration (- 1.0 (* 2.0 (-> this pause-after-in))))
(outward-duration (- 1.0 (* 2.0 (-> this pause-after-out)))))
(cond
((>= mirrored-phase (+ 1.0 return-duration)) 0.0)
((< 1.0 mirrored-phase) (- 1.0 (/ (+ -1.0 mirrored-phase) return-duration)))
((>= mirrored-phase outward-duration) 1.0)
(else (/ mirrored-phase outward-duration)))))
(defmethod get-current-value-with-mirror ((this sync-info) (max-value float))
"Return a triangular value that moves from zero to max-value and back to zero during each
period."
(let* ((period (-> this period))
(period-float (the float period))
(mirror-scale 2.0)
(current-time (+ (the float (mod (the-as uint (current-time)) period)) (-> this offset)))
(mirrored-phase (* mirror-scale (/ (- current-time (* (the float (the int (/ current-time period-float))) period-float)) period-float))))
(if (>= mirrored-phase 1.0) (set! mirrored-phase (- 2.0 mirrored-phase)))
(* mirrored-phase max-value)))
(defmethod get-current-value-with-mirror ((this sync-info-eased) (max-value float))
"Return the eased mirrored phase multiplied by max-value."
(* (get-current-phase-with-mirror this) max-value))
(defmethod get-current-value-with-mirror ((this sync-info-paused) (max-value float))
"Return the paused mirrored phase multiplied by max-value."
(* (get-current-phase-with-mirror this) max-value))
(defmethod set-params! ((this delayed-rand-float) (min-delay int) (max-delay int) (value-range float))
"Initialize the value to zero. Each later change waits a random number of game-time ticks
between min-delay and max-delay and chooses a value from the symmetric interval whose total width
is value-range."
(set! (-> this min-time) min-delay)
(set! (-> this max-time) max-delay)
(set! (-> this max-val) (/ value-range 2))
(set! (-> this start-time) 0)
(set! (-> this timer) 0)
(set! (-> this value) 0.0)
(-> this value))
(defmethod update! ((this delayed-rand-float))
"Choose a new delay and uniformly distributed value when the current delay expires, then
return the held value."
(when (time-elapsed? (-> this start-time) (-> this timer))
(set-time! (-> this start-time))
(set! (-> this timer) (rand-vu-int-range (-> this min-time) (-> this max-time)))
(set! (-> this value) (rand-vu-float-range (- (-> this max-val)) (-> this max-val))))
(-> this value))
(defmethod set-params! ((this oscillating-float) (initial-value float) (accel float) (max-vel float) (damping float))
"Initialize value and target to initial-value, clear velocity, and set the spring gain,
velocity limit, and retained damping fraction. A damping value of zero prevents movement, while
one applies no damping and permits continued oscillation."
(set! (-> this value) initial-value)
(set! (-> this target) initial-value)
(set! (-> this vel) 0.0)
(set! (-> this max-vel) max-vel)
(set! (-> this damping) damping)
(set! (-> this accel) accel)
(-> this value))
(defmethod update! ((this oscillating-float) (target-offset float))
"Advance the damped spring toward target plus target-offset. Acceleration is scaled by frame
time, velocity is clamped before damping, and the final integration is also frame-time scaled."
(let ((acceleration (* (- (+ (-> this target) target-offset) (-> this value)) (* (-> this accel) (-> *display* time-adjust-ratio)))))
(+! (-> this vel) acceleration))
(set! (-> this vel) (fmin (-> this max-vel) (fmax (- (-> this max-vel)) (-> this vel))))
(set! (-> this vel) (* (-> this vel) (-> this damping)))
(+! (-> this value) (* (-> this vel) (-> *display* time-adjust-ratio)))
(-> this value))
(defmethod set-params! ((this bouncing-float) (initial-value float) (max-value float) (min-value float) (elasticity float) (accel float) (max-vel float) (damping float))
"Initialize a damped spring constrained between min-value and max-value. elasticity controls
the fraction of outward velocity retained, with its direction reversed, when an endpoint is hit;
max-vel clamps the spring velocity before that collision response."
(set-params! (-> this osc) initial-value accel max-vel damping)
(set! (-> this max-value) max-value)
(set! (-> this min-value) min-value)
(set! (-> this elasticity) elasticity)
(set! (-> this state) (bouncing-float-state free))
(-> this osc value))
(defmethod update! ((this bouncing-float) (target-offset float))
"Advance the spring toward target plus target-offset, clamp any endpoint crossing, reflect
outward velocity by elasticity, record which endpoint was hit, and return the resulting value."
(update! (-> this osc) target-offset)
(set! (-> this state) (bouncing-float-state free))
(when (>= (-> this osc value) (-> this max-value))
(set! (-> this osc value) (-> this max-value))
(if (< 0.0 (-> this osc vel)) (set! (-> this osc vel) (* (-> this osc vel) (- (-> this elasticity)))))
(set! (-> this state) (bouncing-float-state at-maximum)))
(when (>= (-> this min-value) (-> this osc value))
(set! (-> this osc value) (-> this min-value))
(if (< (-> this osc vel) 0.0) (set! (-> this osc vel) (* (-> this osc vel) (- (-> this elasticity)))))
(set! (-> this state) (bouncing-float-state at-minimum)))
(-> this osc value))
(defmethod at-min? ((this bouncing-float))
"Return true when the latest update hit the minimum endpoint."
(= (-> this state) (bouncing-float-state at-minimum)))
(defmethod at-max? ((this bouncing-float))
"Return true when the latest update hit the maximum endpoint."
(= (-> this state) (bouncing-float-state at-maximum)))
(defmethod set-params! ((this delayed-rand-vector) (min-delay int) (max-delay int) (xz-range float) (y-range float))
"Initialize the vector to zero. Each later change waits a random number of game-time ticks
between min-delay and max-delay; X and Z use the symmetric xz-range and Y uses y-range."
(set! (-> this min-time) min-delay)
(set! (-> this max-time) max-delay)
(set! (-> this xz-max) (/ xz-range 2))
(set! (-> this y-max) (/ y-range 2))
(set! (-> this start-time) 0)
(set! (-> this timer) 0)
(vector-reset! (-> this value))
(-> this value))
(defmethod update-now! ((this delayed-rand-vector))
"Choose a new delay and uniformly distributed X, Y, and Z components immediately, then return
the stored vector."
(set-time! (-> this start-time))
(set! (-> this timer) (rand-vu-int-range (-> this min-time) (-> this max-time)))
(set! (-> this value x) (rand-vu-float-range (- (-> this xz-max)) (-> this xz-max)))
(set! (-> this value y) (rand-vu-float-range (- (-> this y-max)) (-> this y-max)))
(set! (-> this value z) (rand-vu-float-range (- (-> this xz-max)) (-> this xz-max)))
(-> this value))
(defmethod update-with-delay! ((this delayed-rand-vector))
"Choose a new random vector if the current delay has expired; otherwise retain the previous
value."
(if (time-elapsed? (-> this start-time) (-> this timer)) (update-now! this))
(-> this value))
(defmethod update-with-delay-or-reset! ((this delayed-rand-vector))
"Choose a new random vector if the current delay has expired; otherwise reset the stored
value to zero."
(if (time-elapsed? (-> this start-time) (-> this timer)) (update-now! this) (vector-reset! (-> this value)))
(-> this value))
(defmethod set-params! ((this oscillating-vector) (initial-value vector) (accel float) (max-vel float) (damping float))
"Initialize value and target from initial-value, or reset both when it is false; clear
velocity and set the spring gain, speed limit, and retained damping fraction. A damping value of
zero prevents movement, while one applies no damping and permits continued oscillation."
(cond
(initial-value (vector-copy! (-> this value) initial-value) (vector-copy! (-> this target) initial-value))
(else (vector-reset! (-> this value)) (vector-reset! (-> this target))))
(vector-reset! (-> this vel))
(set! (-> this max-vel) max-vel)
(set! (-> this damping) damping)
(set! (-> this accel) accel)
(-> this value))
(defmethod update! ((this oscillating-vector) (target-offset vector))
"Advance the damped vector spring toward target plus an optional target-offset. Limit the
velocity by magnitude so its direction is preserved, then apply damping and frame-time integration."
(let ((acceleration (new 'stack-no-clear 'vector)))
(cond
(target-offset
(vector+! acceleration (-> this target) target-offset)
(vector-! acceleration acceleration (-> this value)))
(else (vector-! acceleration (-> this target) (-> this value))))
(vector-float*! acceleration acceleration (* (-> this accel) (-> *display* time-adjust-ratio)))
(vector+! (-> this vel) (-> this vel) acceleration)
(let ((speed (vector-length (-> this vel))))
(if (< (-> this max-vel) speed) (vector-float*! (-> this vel) (-> this vel) (/ (-> this max-vel) speed))))
(vector-float*! (-> this vel) (-> this vel) (-> this damping))
(vector-float*! acceleration (-> this vel) (-> *display* time-adjust-ratio))
(vector+! (-> this value) (-> this value) acceleration))
(-> this value))