Files
jak-project/goal_src/jak1/engine/math/math.gc
water111 637990314b wip: better stack var support (#4222)
Closes #736

---------

Co-authored-by: Hat Kid <6624576+Hat-Kid@users.noreply.github.com>
2026-04-19 00:14:44 +02:00

241 lines
7.9 KiB
Common Lisp

;;-*-Lisp-*-
(in-package goal)
(bundles "ENGINE.CGO" "GAME.CGO")
(require "kernel/gcommon.gc")
;; various math helpers
(defmacro square (x)
`(* ,x ,x))
;; DECOMP BEGINS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; float utility
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun truncate ((x float))
"Truncate a floating point number to an integer value.
Positive values round down and negative values round up."
(declare (inline))
(the float (the int x)))
(defun integral? ((x float))
"Is a float an exact integer?"
(= (truncate x) x))
(defun fractional-part ((x float))
"Get the fractional part of a float"
(- x (truncate x)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; bitfield types
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(deftype rgba (uint32)
((r uint8 :offset 0)
(g uint8 :offset 8)
(b uint8 :offset 16)
(a uint8 :offset 24)))
(defmacro static-rgba (r g b a)
"make a new static rgba"
`(new 'static 'rgba :r ,r :g ,g :b ,b :a ,a))
(defmacro static-rgba-uint (col)
"make a new static rgba"
`(the-as rgba ,col))
;; TODO: fields
(deftype xyzw (uint128) ())
;; TODO: fields
(deftype xyzwh (uint128) ())
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; utility functions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun log2 ((x int))
"Straight out of Bit Twiddling Hacks graphics.stanford.edu.
This website is old enough that they possibly used this back
in 1999."
(- (sar (the-as int (the float x)) 23) 127))
(defun seek ((x float) (target float) (diff float))
"Move x toward target by at most diff, with floats"
(let ((err (- target x)))
(cond
((>= diff (fabs err))
;; can get there in one go
target)
((>= err 0)
;; increase
(+ x diff))
(else (- x diff)))))
(defun lerp ((minimum float) (maximum float) (amount float))
"Interpolate between minimum and maximum. The output is not clamped."
(+ minimum (* amount (- maximum minimum))))
(defun lerp-scale ((min-out float) (max-out float) (in float) (min-in float) (max-in float))
"Interpolate from [min-in, max-in] to [min-out, max-out].
If the output is out of range, it will be clamped.
This is not a great implementation."
(let ((scale (fmax 0.0 (fmin 1.0 (/ (- in min-in) (- max-in min-in))))))
(+ (* (- 1.0 scale) min-out) (* scale max-out))))
(defun lerp-clamp ((minimum float) (maximum float) (amount float))
"Interpolate between minimum and maximum, but saturate the amount to [0, 1]"
(cond
((>= 0.0 amount) minimum)
((>= amount 1.0) maximum)
(else
;; the implementation in lerp uses fewer operations...
(+ (* (- 1.0 amount) minimum) (* amount maximum)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; utility macros
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmacro seek! (place target rate)
"Macro to use seek in-place. place is the base, and where the result is stored."
`(set! ,place (seek ,place ,target ,rate)))
(defmacro seekl! (place target rate)
"Macro to use seekl in-place. place is the base, and where the result is stored."
`(set! ,place (seekl ,place ,target ,rate)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; integer utility
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun seekl ((x int) (target int) (diff int))
"Move x toward a target by at most diff, with integers"
(let ((err (- target x))) (if (< (abs err) diff) (return-from #f target)) (if (>= err 0) (+ x diff) (- x diff))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; random vu hardware
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; in the PS2 there is a R register for generating random numbers
;; it is a "32-bit" register, but the upper bits are fixed so it always
;; represents a float in (1, 2).
;; we don't have this register on x86, so we add a special global: *_vu-reg-R_*
(define *_vu-reg-R_* 0)
(defun rand-vu-init ((seed float))
"Initialize the VU0 random generator"
;; (.ctc2.i R arg0)
;; (.cfc2.i v0 R)
(set! *_vu-reg-R_* (logior #x3F800000 (logand (the-as int seed) #x007FFFFF)))
(the-as float *_vu-reg-R_*))
;; this is _almost_ sqrt(2) = 1.414
(rand-vu-init 1.418091058731079)
;; rand-vu
(defun rand-vu ()
"Get a random number in [0, 1) and advance the random generator."
;; (.vrget.xyzw vf1) - get current random
(let ((current-random *_vu-reg-R_*))
;; here they update the random generate with some junk
;; for now, we don't do this in OpenGOAL.
;; (.vsqrty Q vf1)
;; (.vaddq.x vf2 vf0 Q) ;; you're not allowed to do this!
;; (.vrxorw vf2)
;; and advance
;; (.vrnext.xyzw vf1)
(let ((x (logand 1 (shr current-random 4)))
(y (logand 1 (shr current-random 22))))
(set! current-random (shl current-random 1))
(set! current-random (logxor current-random (logxor x y)))
(logxor! current-random (pc-rand))
(set! *_vu-reg-R_* (logior #x3f800000 (logand current-random #x7fffff)))))
;; (.vsubw.xyzw vf1 vf1 vf0)
;; (.qmfc2.i v0 vf1)
(- (the-as float *_vu-reg-R_*) 1.0))
;; rand-vu-nostep
(defun rand-vu-nostep ()
"Get the number currently in the random generator.
This will be equal to the last call of (rand-vu)
This will not update the random generator"
(- (the-as float *_vu-reg-R_*) 1.0))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; random vu utilities
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun rand-vu-float-range ((minimum float) (maximum float))
"Get a random number in the given range.
TODO: is this inclusive? I think it's [min, max)"
(+ minimum (* (rand-vu) (- maximum minimum))))
(defun rand-vu-percent? ((prob float))
"Get a boolean that's true with the given probability."
(>= prob (rand-vu)))
(defun rand-vu-int-range ((first int) (second int))
"Get an integer the given range. Inclusive of both?
It looks like they actually did this right??"
(local-vars (float-in-range float))
;; increment the larger of the range, so it is inclusive
;; (we should have (max - min) + 1 buckets)
(if (< first second) (set! second (+ second 1)) (set! first (+ first 1)))
;; get a float in the range
(set! float-in-range (rand-vu-float-range (the float first) (the float second)))
;; negatives round up and positives round down.
;; but we want all to round consistently, so we subtract one from negatives.
(when (< float-in-range 0)
(set! float-in-range (+ -1.0 float-in-range)))
;; and back to integer.
(the int float-in-range))
(defun rand-vu-int-count ((maximum int))
"Get an integer in [0, maximum)"
(the int (* (rand-vu) (the float maximum))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; terrible random integer generator
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(deftype random-generator (basic)
((seed uint32)))
(define *random-generator* (new 'global 'random-generator))
;; I wonder who wrote this code.
(set! (-> *random-generator* seed) #x666edd1e)
(defmacro sext32-64 (x)
"Sign extend a 32-bit value to 64-bits"
`(sar (shl ,x 32) 32))
(defun rand-uint31-gen ((gen random-generator))
"Generate a supposedly random integer.
Note, this might not quite be right.
But the highest bit is always zero, like it says
and it looks kinda random to me."
(let* ((sd (-> gen seed))
;; addiu v1, r0, 16807
;; mult3 v0, v1, a1
(prod (imul64 16807 sd))
;; mfhi v1
(hi (shr prod 32)) ;; sign extend this?
(lo (sar (shl prod 32) 32))
;; daddu v1, v1, v1
(v1 (+ hi hi))
;; srl a1, v0, 31
(a1 (logand #xffffffff (shr lo 31)))
;; or v1, v1, a1
;; daddu v0, v0 v1
(result (+ lo (logior v1 a1))))
(set! result (shr (logand #xffffffff (shl result 1)) 1))
(set! (-> gen seed) result)
(the uint result)))
(defmacro rand-float-gen (&key (gen *random-generator*))
"Generate a float from [0, 1)"
`(+ -1.0 (the-as float (logior #x3f800000 (/ (rand-uint31-gen ,gen) 256)))))