From 4e33746634a5cc40d7aebda2f93b1c9f408ffe6e Mon Sep 17 00:00:00 2001 From: water111 <48171810+water111@users.noreply.github.com> Date: Sun, 22 Sep 2024 12:41:38 -0400 Subject: [PATCH] [jak3] Fix bomb bot bombs disappearing (#3674) This is basically applying the same fix as `vector<-cspace!`, which we've been doing since jak 1. They often make bugs where they use bones before they are properly initialized. On PS2, it's relatively harmless - it results in stuff going to the origin for 1 frame (where it collides with nothing, since the collide cache was filled somewhere else), then going back to normal. On PC, using these uninitialized bones results in NaNs. This is because `0 * (1 / w)` where `w = 0` done in the `update-transforms` is `NaN` on PC, but 0 on PS2. These NaNs propagate to the velocity, and you get stuck with everything being NaN. ![image](https://github.com/user-attachments/assets/f575fed5-4543-4f72-b7d1-c5c8be8036f8) Co-authored-by: water111 --- goal_src/jak3/engine/collide/collide-cache.gc | 103 ++++++++++++++++++ goal_src/jak3/engine/collide/collide-shape.gc | 18 ++- 2 files changed, 118 insertions(+), 3 deletions(-) diff --git a/goal_src/jak3/engine/collide/collide-cache.gc b/goal_src/jak3/engine/collide/collide-cache.gc index 8031a5afe66..f97d1755858 100644 --- a/goal_src/jak3/engine/collide/collide-cache.gc +++ b/goal_src/jak3/engine/collide/collide-cache.gc @@ -866,6 +866,109 @@ (defmethod-mips2c "(method 10 collide-cache-prim)" 10 collide-cache-prim) +;; this function is a cleaned up version of the MIPS2C'd (method 10 collide-cache-prim) +;; as far as I know, it works correctly, but I'm too scared to use it as the default +;; in case there's a subtle bug. +#| +(defmethod resolve-moving-sphere-sphere ((this collide-cache-prim) + (result collide-query) + (other sphere) + (move-dist vector) + (best-dist float) + (action collide-action)) + "Update the `result` collide query with the result of moving `other` by `move-dist` + If best-dist is positive, it indicates a fraction of move-dist where we know there is already a collision. + So, collisions after this point should be ignored. + Additionally, the fraction along move-dist to have collision is returned, or a negative number if no collision is possible. + Note that a collision that occurs in move-dist, but after best-dist will return a negative number." + + ;; treat the incoming sphere as the "moving sphere", do collision detection: + (let* ((sphere-collide-pt (new 'stack-no-clear 'vector)) + (u (moving-sphere-sphere-intersect + other ;; the incoming sphere's position/radius + move-dist ;; the delta position of incoming sphere, if no collision happens + (-> this world-sphere) ;; our position/radius + sphere-collide-pt ;; resulting position of sphere collision + )) + ) + + ;; if we are moving away from collision, there is no collision to report + (when (< u 0.0) + (return u) + ) + + ;; if a previous prim has already found a closer collision, ignore this one. + (when (>= best-dist 0.0) ;; only if we've found something. + (when (<= best-dist u) + (return -100000000.0) + ) + ) + + ;; vector pointing from our center to the intersect point + ;; if the incoming sphere moves this way, it will move it out of collision. + (let* ((escape-dir (vector-! (new 'stack-no-clear 'vector) sphere-collide-pt (-> this world-sphere))) + ) + + ;; next, we'll reject collision if we collide with a solid, but our velocity is moving us out of collision + (when (and (logtest? action (collide-action solid)) + (>= (vector-dot escape-dir move-dist) 0.0) + ) + (return -100000000.0) + ) + + ;; we'll now accept this as the real collision! + (set! (-> result best-my-tri intersect quad) (-> sphere-collide-pt quad)) + (let ((p (the collide-shape-prim-sphere (-> this prim)))) + (set! (-> result best-my-tri pat) (-> p pat)) + (set! (-> result best-my-tri collide-ptr) p) + ) + + (vector-normalize! escape-dir 1.0) + (vector-copy! (-> result best-my-tri normal) escape-dir) + + ;; we need to fake a triangle for the collision result. + ;; to do this, we need to find a vector that's perpendicular to our normal + (let ((triangle-rot (new 'stack-no-clear 'matrix))) + (vector-copy! (-> triangle-rot vector 0) escape-dir) + (cond + ((and (= (-> escape-dir x) 0.0) (= (-> escape-dir y) 0.0)) + ;; unlikely case that escape is exactly along z. If this happens, pick unit x. + (set-vector! (-> triangle-rot vector 1) (-> escape-dir z) 0.0 0.0 1.0) + ) + (else + ;; normal case that there's some x/y component + (set-vector! (-> triangle-rot vector 1) (- 0.0 (-> escape-dir y)) (-> escape-dir x) 0.0 1.0) + (vector-normalize! (-> triangle-rot vector 1) 1.0) + ) + ) + + ;; pick final axis + (vector-cross! (-> triangle-rot vector 2) + (-> triangle-rot vector 0) + (-> triangle-rot vector 1)) + ;; position + (set! (-> triangle-rot quad 3) + (-> sphere-collide-pt quad)) + + ;; transform + (vector-matrix*! (-> result best-my-tri vertex 0) + (new 'static 'vector :y 4096.0 :w 1.0) + triangle-rot) + (vector-matrix*! (-> result best-my-tri vertex 1) + (new 'static 'vector :y -4096.0 :z 4096.0 :w 1.0) + triangle-rot) + (vector-matrix*! (-> result best-my-tri vertex 2) + (new 'static 'vector :y -4096.0 :z -4096.0 :w 1.0) + triangle-rot) + + + ) + u + ) + ) + ) +|# + (defmethod fill-and-probe-using-spheres ((this collide-cache) (arg0 collide-query)) (fill-using-spheres this arg0) (probe-using-spheres this arg0) diff --git a/goal_src/jak3/engine/collide/collide-shape.gc b/goal_src/jak3/engine/collide/collide-shape.gc index b0a17b3c538..91279c960c1 100644 --- a/goal_src/jak3/engine/collide/collide-shape.gc +++ b/goal_src/jak3/engine/collide/collide-shape.gc @@ -2116,10 +2116,22 @@ (.lvf vf3 (&-> a1-4 uvec quad)) (.add.mul.x.vf acc vf2 vf1 acc) (.lvf vf4 (&-> a1-4 fvec quad)) + + (.add.mul.y.vf acc vf3 vf1 acc) + (.add.mul.z.vf vf1 vf4 vf1 acc :mask #b111) + + ;; this check is added in the PC port. It's added for the same reason as the modification + ;; to vector<-cspace!. On the first frame of a process-drawable being alive, the bones will often be zero + ;; on PS2, this would have a potentially harmless result of setting this bone to 0, putting the collision geometry at 0. + ;; this would usually result in no collision, and no change in velocities + ;; in the next frame, the position would be computed correctly. + ;; on PC, this bone becomes NaN, which propagates to setting the velocity to NaN, which results in the + ;; entire collision system having everything as NaN all the time. + (if (!= (-> a1-4 trans w) 0.0) + (.mul.vf vf1 vf1 Q :mask #b111) ) - (.add.mul.y.vf acc vf3 vf1 acc) - (.add.mul.z.vf vf1 vf4 vf1 acc :mask #b111) - (.mul.vf vf1 vf1 Q :mask #b111) + ) + (.svf (&-> s5-0 prim-core world-sphere quad) vf1) (.mov a1-5 vf1) )