jak-project/goal_src/jak1/engine/math/math.gc
Tyler Wilding c162c66118
g/j1: Cleanup all main issues in the formatter and format all of goal_src/jak1 (#3535)
This PR does two main things:
1. Work through the main low-hanging fruit issues in the formatter
keeping it from feeling mature and usable
2. Iterate and prove that point by formatting all of the Jak 1 code
base. **This has removed around 100K lines in total.**
- The decompiler will now format it's results for jak 1 to keep things
from drifting back to where they were. This is controlled by a new
config flag `format_code`.

How am I confident this hasn't broken anything?:
- I compiled the entire project and stored it's `out/jak1/obj` files
separately
- I then recompiled the project after formatting and wrote a script that
md5's each file and compares it (`compare-compilation-outputs.py`
- The results (eventually) were the same:

![Screenshot 2024-05-25
132900](https://github.com/open-goal/jak-project/assets/13153231/015e6f20-8d19-49b7-9951-97fa88ddc6c2)
> This proves that the only difference before and after is non-critical
whitespace for all code/macros that is actually in use.

I'm still aware of improvements that could be made to the formatter, as
well as general optimization of it's performance. But in general these
are for rare or non-critical situations in my opinion and I'll work
through them before doing Jak 2. The vast majority looks great and is
working properly at this point. Those known issues are the following if
you are curious:

![image](https://github.com/open-goal/jak-project/assets/13153231/0edfaba1-6d36-40f5-ab23-0642209867c4)
2024-06-05 22:17:31 -04:00

238 lines
7.8 KiB
Common Lisp

;;-*-Lisp-*-
(in-package goal)
(bundles "ENGINE.CGO" "GAME.CGO")
(require "kernel/gcommon.gc")
;; various math helpers
;; 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)))))