mirror of
https://github.com/open-goal/jak-project
synced 2026-07-02 20:47:07 -04:00
c162c66118
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:  > 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: 
396 lines
20 KiB
Common Lisp
396 lines
20 KiB
Common Lisp
;;-*-Lisp-*-
|
|
(in-package goal)
|
|
(bundles "ENGINE.CGO" "GAME.CGO")
|
|
(require "engine/collide/collide-touch-h.gc")
|
|
(require "kernel/gstate.gc")
|
|
(require "engine/target/target-h.gc")
|
|
(require "engine/collide/collide-shape-h.gc")
|
|
(require "engine/collide/collide-mesh-h.gc")
|
|
|
|
;; DECOMP BEGINS
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;
|
|
;; Touching List
|
|
;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
;; The idea behind the touching list is to determine which things were touched during collision resolution, then
|
|
;; send events to processes based on this.
|
|
|
|
;; In the end, we want to track pairs of collide-shape-prims, and possibly triangles (if one of the prims has triangles).
|
|
;; The logic for this is more confusing than you might expect because they don't count every single intersection that
|
|
;; occurs in every single iteration of the collision solve.
|
|
|
|
;;;;;;;;;;;;;;;;;;;;
|
|
;; Entry Pool
|
|
;;;;;;;;;;;;;;;;;;;;
|
|
|
|
;; there's a global shared pool of entries that you can alloc and free from.
|
|
|
|
(defmethod get-free-node-count ((this touching-prims-entry-pool))
|
|
"Get the number of nodes that are not in use."
|
|
(let ((v0-0 0))
|
|
(let ((v1-0 (-> this head))) (while v1-0 (+! v0-0 1) (set! v1-0 (-> v1-0 next)) (nop!) (nop!) (nop!)))
|
|
v0-0))
|
|
|
|
(defmethod alloc-node ((this touching-prims-entry-pool))
|
|
"Allocate a node. Will return #f if there are none left."
|
|
(let ((gp-0 (-> this head)))
|
|
(cond
|
|
(gp-0
|
|
(let ((v1-0 (-> gp-0 next))) (set! (-> this head) v1-0) (if v1-0 (set! (-> v1-0 prev) #f)))
|
|
(set! (-> gp-0 allocated?) #t)
|
|
(set! (-> gp-0 next) #f)
|
|
(set! (-> gp-0 prev) #f))
|
|
(else (format 0 "ERROR: touching-prims-entry-pool::alloc-node() failed!~%")))
|
|
gp-0))
|
|
|
|
(defmethod free-node ((this touching-prims-entry-pool) (arg0 touching-prims-entry))
|
|
"Free a node allocated with alloc-node"
|
|
(when (-> arg0 allocated?)
|
|
(set! (-> arg0 allocated?) #f)
|
|
(let ((v1-1 (-> this head)))
|
|
(set! (-> arg0 next) v1-1)
|
|
(set! (-> arg0 prev) #f)
|
|
(set! (-> this head) arg0)
|
|
(if v1-1 (set! (-> v1-1 prev) arg0)))))
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Shapes Entry
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
;; a single shape entry represents a pair of shapes that collide.
|
|
;; There can be multiple colliding primitives.
|
|
|
|
(defmethod free-touching-prims-list ((this touching-shapes-entry))
|
|
"Return all nodes used by this touching-shapes-entry to the touching-prims-entry-pool"
|
|
(when (-> this cshape1)
|
|
(set! (-> this cshape1) #f)
|
|
(let ((gp-0 (-> this head)))
|
|
(when gp-0
|
|
(set! (-> this head) #f)
|
|
(let ((s5-0 *touching-prims-entry-pool*))
|
|
(while gp-0
|
|
(let ((a1-0 gp-0)) (set! gp-0 (-> a1-0 next)) (free-node s5-0 a1-0))))
|
|
#f))))
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Touching List
|
|
;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
;; A touching list is a list up to TOUCHING_LIST_LENGTH pairs of colliding collide-shapes.
|
|
|
|
(defmethod free-all-prim-nodes ((this touching-list))
|
|
"Free all prim nodes used by all touching shapes in this touching-list."
|
|
(let ((s5-0 (the-as touching-shapes-entry (-> this touching-shapes))))
|
|
(countdown (s4-0 (-> this num-touching-shapes))
|
|
(free-touching-prims-list s5-0)
|
|
(&+! s5-0 16)))
|
|
(set! (-> this num-touching-shapes) 0)
|
|
(set! (-> this resolve-u) 0)
|
|
0
|
|
(none))
|
|
|
|
(defmethod get-shapes-entry ((this touching-list) (arg0 collide-shape) (arg1 collide-shape))
|
|
"Get a touching-shapes-entry for the two shapes. If one exists, it will be returned. Otherwise a new one will be made."
|
|
(let ((v0-0 (the-as touching-shapes-entry (-> this touching-shapes)))) ;; the candidate
|
|
(let ((v1-0 (the-as touching-shapes-entry #f))) ;; a good one
|
|
;; loop over all touching shapes
|
|
(countdown (a3-0 (-> this num-touching-shapes))
|
|
(let ((t0-0 (-> v0-0 cshape1)))
|
|
(set! v1-0
|
|
(cond
|
|
(t0-0
|
|
;; in use. If it's match, return it (allows a,b or b,a to match a,b)
|
|
(if (or (and (= t0-0 arg0) (= (-> v0-0 cshape2) arg1)) (and (= t0-0 arg1) (= (-> v0-0 cshape2) arg0))) (return v0-0))
|
|
;; otherwise bad
|
|
v1-0)
|
|
(else
|
|
;; not in use. remember it's free and keep looking.
|
|
v0-0))))
|
|
(&+! v0-0 16))
|
|
;; done looping.
|
|
(cond
|
|
(v1-0 ;; did we find an unused slot? if so return it
|
|
(set! v0-0 v1-0))
|
|
(else
|
|
;; need to add a new one
|
|
(when (>= (-> this num-touching-shapes) TOUCHING_LIST_LENGTH)
|
|
;; but there's no room!
|
|
(format 0 "ERROR: touching-list::get-shapes-entry() failed!~%")
|
|
(return (the-as touching-shapes-entry #f)))
|
|
;; enough room, increase the size
|
|
(+! (-> this num-touching-shapes) 1))))
|
|
;; if we're doing a new one, set it up.
|
|
(set! (-> v0-0 cshape1) arg0)
|
|
(set! (-> v0-0 cshape2) arg1)
|
|
(set! (-> v0-0 head) #f)
|
|
(set! (-> v0-0 resolve-u) 1)
|
|
(set! (-> this resolve-u) 1)
|
|
(the-as touching-shapes-entry v0-0)))
|
|
|
|
(deftype add-prims-touching-work (structure)
|
|
((tri1 collide-tri-result)
|
|
(tri2 collide-tri-result)))
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Touching List Update
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
;; Note: this code relies a lot on the details of the iterative collision solver they made.
|
|
;; Basically, the solver works like this:
|
|
|
|
;; "What happens if I move forward 1.0 timesteps?"
|
|
;; "You'd hit object A after 0.6 of a step, and object B after 0.3 of a step" (touching list has A and B now)
|
|
;; "OK. I will move forward 0.3 then. I will remove A from the touching list because I only went 0.3 of a step, and that's not enough to reach A."
|
|
;; ~Move forward 0.3, and handle the collision reaction from hitting B~
|
|
;; "I moved forward 0.3, so I now have 0.7 left to move (but potentially in a different direction because I bounced off of B)"
|
|
;; "What happens if I move forward 0.7 timesteps?"
|
|
;; .. repeat ..
|
|
|
|
(defmethod add-touching-prims ((this touching-list) (arg0 collide-shape-prim) (arg1 collide-shape-prim) (arg2 float) (arg3 collide-tri-result) (arg4 collide-tri-result))
|
|
"Tell the touching list that if we end taking a step of at least arg2, the prims arg0/arg1 will collide.
|
|
However, you don't have to know for sure if you're going to take this big of a step yet.
|
|
You can provide triangles if you want, but you don't have to.
|
|
The logic for calling this twice for the same prims in between calls to update-from-step-size is a little weird
|
|
so I suspect this never happens (and it's probably cheaper to avoid this duplication in the actual prim collision code)."
|
|
;; I don't know why they made this type, but I'm guessing it's to avoid the compiler spilling to the stack
|
|
(let ((gp-0 (new 'stack-no-clear 'add-prims-touching-work)))
|
|
(set! (-> gp-0 tri1) arg3)
|
|
(set! (-> gp-0 tri2) arg4)
|
|
;; first, grab the entry for the collide-shapes involved.
|
|
(let ((s2-0 (get-shapes-entry this (-> arg0 cshape) (-> arg1 cshape))))
|
|
(when s2-0
|
|
;; if we ask for a,b, that function might give us b,a. Detect that, and swap our collide shape prims.
|
|
(when (= (-> s2-0 cshape1) (-> arg1 cshape))
|
|
(let ((v1-4 arg0)) (set! arg0 arg1) (set! arg1 v1-4)))
|
|
(let ((s0-0 (-> s2-0 head)))
|
|
;; loop over all the entries in the shapes
|
|
(while s0-0
|
|
;; and only look at the ones that match these prims
|
|
(when (and (= (-> s0-0 prim1 cprim) arg0) (= (-> s0-0 prim2 cprim) arg1))
|
|
;; if we already have an entry for this one, only update if this one is closer.
|
|
(when (< arg2 (-> s0-0 u))
|
|
;; this value is unused.
|
|
(-> s0-0 u)
|
|
(let ((v1-12 (-> s0-0 prim1))
|
|
(a1-2 (-> gp-0 tri1)))
|
|
(cond
|
|
(a1-2
|
|
;; we have a tri, copy it
|
|
(set! (-> v1-12 has-tri?) #t)
|
|
(mem-copy! (the-as pointer (-> v1-12 tri)) (the-as pointer a1-2) 84))
|
|
(else
|
|
;; no tri
|
|
(set! (-> v1-12 has-tri?) #f))))
|
|
;; same for the other tri.
|
|
(let ((v1-15 (-> s0-0 prim2))
|
|
(a1-3 (-> gp-0 tri2)))
|
|
(cond
|
|
(a1-3 (set! (-> v1-15 has-tri?) #t) (mem-copy! (the-as pointer (-> v1-15 tri)) (the-as pointer a1-3) 84))
|
|
(else (set! (-> v1-15 has-tri?) #f)))))
|
|
;; after we found the matching node for these prims, we're done!
|
|
(return 0))
|
|
(set! s0-0 (-> s0-0 next))))
|
|
;; nope, didn't find an entry, so make a new one.
|
|
(let ((s0-1 (alloc-node *touching-prims-entry-pool*)))
|
|
;; allocate a new node, link it.
|
|
(when s0-1
|
|
(let ((v1-22 (-> s2-0 head)))
|
|
(set! (-> s0-1 next) v1-22)
|
|
(set! (-> s0-1 prev) #f)
|
|
(set! (-> s2-0 head) s0-1)
|
|
(if v1-22 (set! (-> v1-22 prev) s0-1)))
|
|
;; and set it up.
|
|
(set! (-> s0-1 u) arg2)
|
|
(when (>= arg2 0.0)
|
|
;; if we're >0, we have to move (which we haven't done yet) to actually collide.
|
|
;; flag us as resolve-u (meaning our u is from the resolve function, which looks at what _would_
|
|
;; happen if we did it all at once.)
|
|
;; if we're <0, we started in collision (possible, if we didnt' converge on the last frame)
|
|
;; we should still add, but we don't need to bother with the "did we go far enough" checks.
|
|
(set! (-> s2-0 resolve-u) 1)
|
|
(set! (-> this resolve-u) 1))
|
|
(let ((v1-26 (-> s0-1 prim1))
|
|
(a1-4 (-> gp-0 tri1)))
|
|
(set! (-> v1-26 cprim) arg0)
|
|
(cond
|
|
(a1-4 (set! (-> v1-26 has-tri?) #t) (mem-copy! (the-as pointer (-> v1-26 tri)) (the-as pointer a1-4) 84))
|
|
(else (set! (-> v1-26 has-tri?) #f))))
|
|
(let ((v1-29 (-> s0-1 prim2))
|
|
(a1-5 (-> gp-0 tri2)))
|
|
(set! (-> v1-29 cprim) arg1)
|
|
(cond
|
|
(a1-5 (set! (-> v1-29 has-tri?) #t) (mem-copy! (the-as pointer (-> v1-29 tri)) (the-as pointer a1-5) 84))
|
|
(else (set! (-> v1-29 has-tri?) #f)))))))))
|
|
0
|
|
(none))
|
|
|
|
(defmethod update-from-step-size ((this touching-list) (arg0 float))
|
|
"Given that we actually will take a step size of arg0, remove things we won't actually hit."
|
|
;; only if we have some un-updated potential collision
|
|
(when (nonzero? (-> this resolve-u))
|
|
;; remember we did it
|
|
(set! (-> this resolve-u) 0)
|
|
;; loop through touching-shape-entries
|
|
(let ((s5-0 (the-as touching-shapes-entry (-> this touching-shapes))))
|
|
(countdown (s4-0 (-> this num-touching-shapes))
|
|
;; only when the entry isn't done yet
|
|
(when (nonzero? (-> s5-0 resolve-u))
|
|
;; remember we did it
|
|
(set! (-> s5-0 resolve-u) 0)
|
|
;; we can have empty entries, so check cshape1
|
|
(when (-> s5-0 cshape1)
|
|
;; loop over nodes (each node is a pair of prims)
|
|
(let ((s3-0 (-> s5-0 head)))
|
|
(while s3-0
|
|
(let ((f0-0 (-> s3-0 u)))
|
|
(set! s3-0
|
|
(cond
|
|
((>= f0-0 0.0)
|
|
;; we'd have to take a step to hit this one!
|
|
(cond
|
|
((>= arg0 f0-0)
|
|
;; our step is big enough!
|
|
;; we hit it. To prevent this from being possibly removed later on
|
|
;; we set the u to -1.0 to indicate that we're massively intersecting.
|
|
;; this will no longer be eligible for removal because we actually hit it.
|
|
(set! (-> s3-0 u) -1.0)
|
|
(set! s3-0 (-> s3-0 next)))
|
|
(else
|
|
;; we would have needed to move more than arg0 to hit this one.
|
|
;; at least for now, remove by splicing out of the list and freeing.
|
|
(let ((a1-1 s3-0)) ;; this
|
|
(let ((v1-7 (-> s3-0 next))) ;; next
|
|
(let ((a0-1 (-> s3-0 prev))) ;; prev
|
|
(if a0-1 (set! (-> a0-1 next) v1-7) (set! (-> s5-0 head) v1-7))
|
|
(if v1-7 (set! (-> v1-7 prev) a0-1)))
|
|
(set! s3-0 v1-7))
|
|
(free-node *touching-prims-entry-pool* a1-1))))
|
|
s3-0)
|
|
(else
|
|
;; not touching, advance to the next.
|
|
(-> s3-0 next)))))))
|
|
;; if we removed everything from this, mark it as dead (the allocation function will reuse it now)
|
|
(if (not (-> s5-0 head)) (set! (-> s5-0 cshape1) #f))))
|
|
(&+! s5-0 16))))
|
|
0
|
|
(none))
|
|
|
|
(defmethod send-events-for-touching-shapes ((this touching-list))
|
|
"Send all events for touching shapes.
|
|
Note that the order of event sending is basically random.
|
|
(this could explain lava walks's unreliable behavior)"
|
|
;; og:preserve-this
|
|
;; PC port note : there is a fatal bug here where the processes are NOT SAFE to use without a handle.
|
|
;; this can lead to crashes if for example target hits 2 things at once, and one of those things kills the other.
|
|
;; e.g.: target hits eco1 and eco2. eco1 gets processed first and kills eco2 as a result. eco2 is now dead, but
|
|
;; the touching list will run its logic on it regardless.
|
|
;; this is fixed here by creating a really large list of 2 handles per collide shape and storing all collided
|
|
;; process handles there. (process->handle) is then safe to use.
|
|
; (* 2 TOUCHING_LIST_LENGTH) -> 64
|
|
(let ((handles (new 'stack-no-clear 'array 'handle 64)))
|
|
(let ((entry (the-as touching-shapes-entry (-> this touching-shapes))))
|
|
(countdown (i (-> this num-touching-shapes))
|
|
(let ((c1 (-> entry cshape1)))
|
|
(when c1
|
|
(let ((c2 (-> entry cshape2)))
|
|
;; not quite sure why, but we make it look like cshape1 (s4) is target always.
|
|
;; I guess this makes it so the target/enemy events are always sent in the same order.
|
|
(when (= (-> c2 process type) target)
|
|
(swap! c1 c2))
|
|
(set! (-> handles (+ 0 (* 2 i))) (process->handle (-> c1 process)))
|
|
(set! (-> handles (+ 1 (* 2 i))) (process->handle (-> c2 process))))))
|
|
(set! entry (-> (the-as (inline-array touching-shapes-entry) entry) 1))))
|
|
(let ((entry (the-as touching-shapes-entry (-> this touching-shapes))))
|
|
(countdown (i (-> this num-touching-shapes))
|
|
(let ((c1 (-> entry cshape1)))
|
|
(when c1
|
|
(let ((c2 (-> entry cshape2)))
|
|
;; not quite sure why, but we make it look like cshape1 (s4) is target always.
|
|
;; I guess this makes it so the target/enemy events are always sent in the same order.
|
|
(when (= (-> c2 process type) target)
|
|
(swap! c1 c2))
|
|
;; send events!
|
|
(let ((c1-proc (handle->process (-> handles (+ 0 (* 2 i)))))
|
|
(c2-proc (handle->process (-> handles (+ 1 (* 2 i))))))
|
|
(let ((v1-4 (-> c1 event-self))) (if v1-4 (send-event c1-proc v1-4 :from c2-proc entry)))
|
|
(let ((v1-5 (-> c1 event-other))) (if v1-5 (send-event c2-proc v1-5 :from c1-proc entry)))
|
|
(let ((v1-6 (-> c2 event-self))) (if v1-6 (send-event c2-proc v1-6 :from c1-proc entry)))
|
|
(let ((v1-7 (-> c2 event-other))) (if v1-7 (send-event c1-proc v1-7 :from c2-proc entry)))))))
|
|
(set! entry (-> (the-as (inline-array touching-shapes-entry) entry) 1)))))
|
|
0
|
|
(none))
|
|
|
|
(defmethod prims-touching? ((this touching-shapes-entry) (arg0 collide-shape-moving) (arg1 uint))
|
|
"In a pair of collide shapes, is a prim from the given collide shape with the given prim-id mask touching the other shape?"
|
|
(cond
|
|
((= (-> this cshape1) arg0)
|
|
(let ((v1-1 (-> this head)))
|
|
(while v1-1
|
|
(if (logtest? (-> v1-1 prim1 cprim prim-id) arg1) (return v1-1))
|
|
(set! v1-1 (-> v1-1 next)))))
|
|
((= (-> this cshape2) arg0)
|
|
(let ((v1-4 (-> this head)))
|
|
(while v1-4
|
|
(if (logtest? (-> v1-4 prim2 cprim prim-id) arg1) (return v1-4))
|
|
(set! v1-4 (-> v1-4 next)))))
|
|
(else (format 0 "ERROR: touching-shapes-entry::prims-touching? : Bogus cshape value!~%")))
|
|
(the-as touching-prims-entry #f))
|
|
|
|
(defmethod prims-touching-action? ((this touching-shapes-entry) (arg0 collide-shape) (arg1 collide-action) (arg2 collide-action))
|
|
"In a pair of collide shapes, find a pair of colliding prims where the prim from the given collide shape has at least one of the actions in arg1
|
|
and none of the actions in arg2."
|
|
(cond
|
|
((= (-> this cshape1) arg0)
|
|
(let ((v1-1 (-> this head)))
|
|
(while v1-1
|
|
(let ((a0-1 (-> v1-1 prim1 cprim)))
|
|
(if (and (logtest? arg1 (-> a0-1 prim-core action)) (not (logtest? arg2 (-> a0-1 prim-core action)))) (return v1-1)))
|
|
(set! v1-1 (-> v1-1 next)))))
|
|
((= (-> this cshape2) arg0)
|
|
(let ((v1-4 (-> this head)))
|
|
(while v1-4
|
|
(let ((a0-5 (-> v1-4 prim2 cprim)))
|
|
(if (and (logtest? arg1 (-> a0-5 prim-core action)) (not (logtest? arg2 (-> a0-5 prim-core action)))) (return v1-4)))
|
|
(set! v1-4 (-> v1-4 next)))))
|
|
(else (format 0 "ERROR: touching-shapes-entry::prims-touching-action? : Bogus cshape value!~%")))
|
|
(the-as touching-prims-entry #f))
|
|
|
|
(defmethod get-touched-shape ((this touching-shapes-entry) (arg0 collide-shape))
|
|
"Get the other shape in a pair of shapes."
|
|
(cond
|
|
((= (-> this cshape1) arg0) (return (-> this cshape2)))
|
|
((= (-> this cshape2) arg0) (return (-> this cshape1))))
|
|
(the-as collide-shape #f))
|
|
|
|
(defmethod get-touched-prim ((this touching-prims-entry) (arg0 trsqv) (arg1 touching-shapes-entry))
|
|
"Get the primitive belonging to the collide shape that is touching."
|
|
(cond
|
|
((= (-> arg1 cshape1) arg0) (return (-> this prim1 cprim)))
|
|
((= (-> arg1 cshape2) arg0) (return (-> this prim2 cprim))))
|
|
(the-as collide-shape-prim #f))
|
|
|
|
(defmethod get-touched-tri ((this touching-prims-entry) (arg0 collide-shape) (arg1 touching-shapes-entry))
|
|
"Get the triangle belonging to the the collide shape that is touching (if it has one, otherwise #f)"
|
|
(let ((v0-0 (the-as collide-tri-result #f)))
|
|
(cond
|
|
((= (-> arg1 cshape1) arg0) (let ((v1-2 (-> this prim1))) (if (-> v1-2 has-tri?) (set! v0-0 (-> v1-2 tri)))))
|
|
((= (-> arg1 cshape2) arg0) (let ((v1-5 (-> this prim2))) (if (-> v1-5 has-tri?) (set! v0-0 (-> v1-5 tri))))))
|
|
v0-0))
|
|
|
|
(defmethod get-middle-of-bsphere-overlap ((this touching-prims-entry) (arg0 vector))
|
|
"This is a bit weird...
|
|
But assuming the the bounding spheres overlap, draw a line between their centers, consider the line segment
|
|
that is inside of both spheres, and get the midpoint of that."
|
|
(let* ((s4-0 (-> this prim1 cprim))
|
|
(s3-0 (-> this prim2 cprim))
|
|
;; compute the offset between the prim cores.
|
|
(gp-1 (vector-! (new 'stack-no-clear 'vector) (the-as vector (-> s3-0 prim-core)) (the-as vector (-> s4-0 prim-core)))))
|
|
;; subtract off the two radius. this is now the offset between the "closest" points (and is negative)
|
|
(let ((f1-2 (- (- (vector-length gp-1) (-> s3-0 prim-core world-sphere w)) (-> s4-0 prim-core world-sphere w))))
|
|
;; this offset is the radius, minus half the overlap distance
|
|
(vector-normalize! gp-1 (+ (-> s4-0 prim-core world-sphere w) (* 0.5 f1-2))))
|
|
;; so add it to s4's origin to get to the halfway point
|
|
(vector+! arg0 gp-1 (the-as vector (-> s4-0 prim-core))))
|
|
arg0)
|