openvibers/breakroomsimulator Commits
Bug55: ceiling collision — heads bonk on overhead geometry instead of clipping in
Changes
@@@ -2170,7 +2170,7 @@ function start(token, user) {: hsp > 5.2 ? 'run' : hsp > .5 ? 'walk' : 'idle';// gravity — against the walkable surface under our feet (props, ramps,// car roofs), not just the world floor-- const grd = phys.groundAt(my.x, my.z, my.y);++ const grd = phys.groundAt(my.x, my.z, my.y, !my.onGround);const gY = grd.y;if (my.onGround && gY > my.y && gY - my.y <= .5) { my.y = gY; my.vy = 0; } // step/ramp upmy.vy -= 16 * dt;@@@ -2182,6 +2182,15 @@ function start(token, user) {}my.y = gY; my.vy = 0; my.onGround = true;} else if (my.y > gY + .03) my.onGround = false;++ // ceiling: heads bonk on overhead geometry instead of clipping into it++ {++ const ceilY = phys.ceilingAt(my.x, my.z, my.y);++ const headH = my.crouch ? 1.25 : 1.72;++ if (ceilY != null && my.y + headH > ceilY) {++ my.y = Math.max(gY, ceilY - headH);++ if (my.vy > 0) { my.vy = 0; beep(200, .04, 'square', .05); } // *donk*++ }++ }// standing on a moving prop/car = moving platform: ride it (source-style)if (my.onGround && grd.e && !grd.e.frozen) {const bv = grd.e.body.velocity;@@@ -270,7 +270,7 @@ export function initPhysics(scene) {// highest walkable surface under (x,z) reachable from foot height py.// Returns {y, e}: e is the entity you'd be standing on (null = world floor),// so the walker can ride moving props like source-engine platforms.-- function groundAt(x, z, py) {++ function groundAt(x, z, py, airborne = false) {let g = 0, ge = null;const consider = (body, e) => {body.updateAABB();@@@ -279,12 +279,47 @@ export function initPhysics(scene) {z < bb.lowerBound.z - .05 || z > bb.upperBound.z + .05) return;if (bb.lowerBound.y > py + STEP + 1.2) return; // floating way overheadconst h = surfaceYAt(body, x, z);-- if (h != null && h <= py + STEP && h > g) { g = h; ge = e; }++ if (h == null || h > py + STEP || h <= g) return;++ // approached from BELOW while airborne? if a downward-facing surface++ // sits well above our feet, we're UNDER the piece — its top is not our++ // ground (jumping beneath a thin slab used to warp you through onto it).++ // Grounded step-ups skip this: the ceiling clamp already makes the++ // grounded warp geometrically impossible, and thin ledges must step.++ if (airborne && h - py > .3) {++ wRay.from.set(x, py + .03, z);++ wRay.to.set(x, h - .02, z);++ wRes.reset();++ wRay.intersectBody(body, wRes);++ if (wRes.hasHit && wRes.hitPointWorld.y - py > .28) return;++ }++ g = h; ge = e;};for (const e of props.values()) consider(e.body, e);for (const e of cars.values()) consider(e.body, e);return { y: g, e: ge };}++ // lowest overhead surface (an underside) above the feet at (x,z), or null —++ // source-style head collision: jumps BONK instead of clipping into geometry++ function ceilingAt(x, z, feetY) {++ let c = null;++ const consider = (e) => {++ const b = e.body;++ if (!b.collisionResponse) return; // fading door++ b.updateAABB();++ const bb = b.aabb;++ if (x < bb.lowerBound.x - .05 || x > bb.upperBound.x + .05 ||++ z < bb.lowerBound.z - .05 || z > bb.upperBound.z + .05) return;++ if (bb.upperBound.y <= feetY + .25 || bb.lowerBound.y > feetY + 2.5) return;++ wRay.from.set(x, feetY + .25, z);++ wRay.to.set(x, feetY + 2.5, z);++ wRes.reset();++ wRay.intersectBody(b, wRes);++ if (wRes.hasHit && (c == null || wRes.hitPointWorld.y < c)) c = wRes.hitPointWorld.y;++ };++ for (const e of props.values()) consider(e);++ for (const e of cars.values()) consider(e);++ return c;++ }// landing on a physics prop presses it down — the source-engine thudfunction stomp(e, vy) {if (!e || e.frozen || e.isCar) return;@@@ -793,7 +828,7 @@ export function initPhysics(scene) {return {world, props, cars, add, remove, claim, applyState, applyCarState, sendState, sendCarState,drive, step, smack, raycast, yawBody, rotateBody, setQuatAnchored, grabLocal, anchorWorld,-- addStatic, removeStatic, setFrozen, addCar, groundAt, resolvePlayer, clipMove, stomp, PHYS_KINDS,++ addStatic, removeStatic, setFrozen, addCar, groundAt, ceilingAt, resolvePlayer, clipMove, stomp, PHYS_KINDS,setMyId(id) { myId = id; },};}
@@ -2170,7 +2170,7 @@ function start(token, user) {
@@ -2170,7 +2170,7 @@ function start(token, user) {
: hsp > 5.2 ? 'run' : hsp > .5 ? 'walk' : 'idle';
: hsp > 5.2 ? 'run' : hsp > .5 ? 'walk' : 'idle';
// gravity — against the walkable surface under our feet (props, ramps,
// gravity — against the walkable surface under our feet (props, ramps,
// car roofs), not just the world floor
// car roofs), not just the world floor
- const grd = phys.groundAt(my.x, my.z, my.y);
+ const grd = phys.groundAt(my.x, my.z, my.y, !my.onGround);
const gY = grd.y;
const gY = grd.y;
if (my.onGround && gY > my.y && gY - my.y <= .5) { my.y = gY; my.vy = 0; } // step/ramp up
if (my.onGround && gY > my.y && gY - my.y <= .5) { my.y = gY; my.vy = 0; } // step/ramp up
my.vy -= 16 * dt;
my.vy -= 16 * dt;
@@ -2182,6 +2182,15 @@ function start(token, user) {
@@ -2182,6 +2182,15 @@ function start(token, user) {
}
}
my.y = gY; my.vy = 0; my.onGround = true;
my.y = gY; my.vy = 0; my.onGround = true;
} else if (my.y > gY + .03) my.onGround = false;
} else if (my.y > gY + .03) my.onGround = false;
+ // ceiling: heads bonk on overhead geometry instead of clipping into it
+ {
+ const ceilY = phys.ceilingAt(my.x, my.z, my.y);
+ const headH = my.crouch ? 1.25 : 1.72;
+ if (ceilY != null && my.y + headH > ceilY) {
+ my.y = Math.max(gY, ceilY - headH);
+ if (my.vy > 0) { my.vy = 0; beep(200, .04, 'square', .05); } // *donk*
+ }
+ }
// standing on a moving prop/car = moving platform: ride it (source-style)
// standing on a moving prop/car = moving platform: ride it (source-style)
if (my.onGround && grd.e && !grd.e.frozen) {
if (my.onGround && grd.e && !grd.e.frozen) {
const bv = grd.e.body.velocity;
const bv = grd.e.body.velocity;
@@ -270,7 +270,7 @@ export function initPhysics(scene) {
@@ -270,7 +270,7 @@ export function initPhysics(scene) {
// highest walkable surface under (x,z) reachable from foot height py.
// highest walkable surface under (x,z) reachable from foot height py.
// Returns {y, e}: e is the entity you'd be standing on (null = world floor),
// Returns {y, e}: e is the entity you'd be standing on (null = world floor),
// so the walker can ride moving props like source-engine platforms.
// so the walker can ride moving props like source-engine platforms.
- function groundAt(x, z, py) {
+ function groundAt(x, z, py, airborne = false) {
let g = 0, ge = null;
let g = 0, ge = null;
const consider = (body, e) => {
const consider = (body, e) => {
body.updateAABB();
body.updateAABB();
@@ -279,12 +279,47 @@ export function initPhysics(scene) {
@@ -279,12 +279,47 @@ export function initPhysics(scene) {
z < bb.lowerBound.z - .05 || z > bb.upperBound.z + .05) return;
z < bb.lowerBound.z - .05 || z > bb.upperBound.z + .05) return;
if (bb.lowerBound.y > py + STEP + 1.2) return; // floating way overhead
if (bb.lowerBound.y > py + STEP + 1.2) return; // floating way overhead
const h = surfaceYAt(body, x, z);
const h = surfaceYAt(body, x, z);
- if (h != null && h <= py + STEP && h > g) { g = h; ge = e; }
+ if (h == null || h > py + STEP || h <= g) return;
+ // approached from BELOW while airborne? if a downward-facing surface
+ // sits well above our feet, we're UNDER the piece — its top is not our
+ // ground (jumping beneath a thin slab used to warp you through onto it).
+ // Grounded step-ups skip this: the ceiling clamp already makes the
+ // grounded warp geometrically impossible, and thin ledges must step.
+ if (airborne && h - py > .3) {
+ wRay.from.set(x, py + .03, z);
+ wRay.to.set(x, h - .02, z);
+ wRes.reset();
+ wRay.intersectBody(body, wRes);
+ if (wRes.hasHit && wRes.hitPointWorld.y - py > .28) return;
+ }
+ g = h; ge = e;
};
};
for (const e of props.values()) consider(e.body, e);
for (const e of props.values()) consider(e.body, e);
for (const e of cars.values()) consider(e.body, e);
for (const e of cars.values()) consider(e.body, e);
return { y: g, e: ge };
return { y: g, e: ge };
}
}
+ // lowest overhead surface (an underside) above the feet at (x,z), or null —
+ // source-style head collision: jumps BONK instead of clipping into geometry
+ function ceilingAt(x, z, feetY) {
+ let c = null;
+ const consider = (e) => {
+ const b = e.body;
+ if (!b.collisionResponse) return; // fading door
+ b.updateAABB();
+ const bb = b.aabb;
+ if (x < bb.lowerBound.x - .05 || x > bb.upperBound.x + .05 ||
+ z < bb.lowerBound.z - .05 || z > bb.upperBound.z + .05) return;
+ if (bb.upperBound.y <= feetY + .25 || bb.lowerBound.y > feetY + 2.5) return;
+ wRay.from.set(x, feetY + .25, z);
+ wRay.to.set(x, feetY + 2.5, z);
+ wRes.reset();
+ wRay.intersectBody(b, wRes);
+ if (wRes.hasHit && (c == null || wRes.hitPointWorld.y < c)) c = wRes.hitPointWorld.y;
+ };
+ for (const e of props.values()) consider(e);
+ for (const e of cars.values()) consider(e);
+ return c;
+ }
// landing on a physics prop presses it down — the source-engine thud
// landing on a physics prop presses it down — the source-engine thud
function stomp(e, vy) {
function stomp(e, vy) {
if (!e || e.frozen || e.isCar) return;
if (!e || e.frozen || e.isCar) return;
@@ -793,7 +828,7 @@ export function initPhysics(scene) {
@@ -793,7 +828,7 @@ export function initPhysics(scene) {
return {
return {
world, props, cars, add, remove, claim, applyState, applyCarState, sendState, sendCarState,
world, props, cars, add, remove, claim, applyState, applyCarState, sendState, sendCarState,
drive, step, smack, raycast, yawBody, rotateBody, setQuatAnchored, grabLocal, anchorWorld,
drive, step, smack, raycast, yawBody, rotateBody, setQuatAnchored, grabLocal, anchorWorld,
- addStatic, removeStatic, setFrozen, addCar, groundAt, resolvePlayer, clipMove, stomp, PHYS_KINDS,
+ addStatic, removeStatic, setFrozen, addCar, groundAt, ceilingAt, resolvePlayer, clipMove, stomp, PHYS_KINDS,
setMyId(id) { myId = id; },
setMyId(id) { myId = id; },
};
};
}
}