diff --git a/changes/collide-centre.md b/changes/collide-centre.md new file mode 100644 index 00000000..61cda14a --- /dev/null +++ b/changes/collide-centre.md @@ -0,0 +1,16 @@ +bump: patch +type: fix +**`col_resolve` no longer throws a body across the map when it starts dead centre on a +collider.** The degenerate branch — a point exactly on a circle's axis, where there is +no direction to push it — chose a unit normal `(1, 0)` and then divided it by the +clamped `d = 0.001` anyway, along with the real normals. The push came out a thousand +times too large: a body standing exactly on a 0.5 m trunk was moved about 800 m instead +of the 0.85 m that clears it. + +Off-centre the arithmetic was correct, and off-centre is how anything arrives at a +trunk while walking, so it never showed up in play — only a teleport, a spawn or a +world generator placing something on an existing collider could land on the axis. + +`(ex, ez) / d` is always a unit vector for `d > 0`, because `d` is its own length: there +was never anything to clamp. The normal is now built once, explicitly, and the clamp is +gone. diff --git a/packages/ludic.render3d/collide.ludic b/packages/ludic.render3d/collide.ludic index fe0e087a..bb88ca17 100644 --- a/packages/ludic.render3d/collide.ludic +++ b/packages/ludic.render3d/collide.ludic @@ -72,12 +72,23 @@ function col_resolve(px: int, pz: int, pr: int) -> bool { let d2 = f_add(f_mul(ex, ex), f_mul(ez, ez)) let rr = f_add(col_r[i], pr) if f_ls(d2, f_mul(rr, rr)) { - var d = f_sqrt(d2) - var nx = ex; var nz = ez - if f_ls(d, fl(0.001)) { d = fl(0.001); nx = F_ONE; nz = F_ZERO } + let d = f_sqrt(d2) + # The UNIT normal out of this circle. (ex, ez) / d is always unit for d > 0, + # because d is its own length - there is nothing to clamp and nothing that can + # grow. Exactly at the centre there is no direction to be had, so any one will + # do and +x is as good as another. + # + # This used to clamp d to 0.001 and then divide by it, having ALREADY set the + # degenerate normal to (1, 0): a unit vector divided by a thousandth, so the + # push came out a thousand times too big. A body standing dead centre on a + # 0.5 m trunk was thrown roughly 800 m across the map instead of 0.85 m clear + # of it. Off-centre - which is how anything actually arrives at a trunk - the + # arithmetic was right, so it never showed up in play. + var ux = F_ONE; var uz = F_ZERO + if f_gt(d, F_ZERO) { ux = f_div(ex, d); uz = f_div(ez, d) } let push = f_sub(rr, d) - x = f_add(x, f_mul(f_div(nx, d), push)) - z = f_add(z, f_mul(f_div(nz, d), push)) + x = f_add(x, f_mul(ux, push)) + z = f_add(z, f_mul(uz, push)) moved = true } }