openvibers/breakroomsimulator Commits
Bug58: airborne collision is full-height — no more jumping through thin prop tops
Changes
@@@ -2152,9 +2152,12 @@ function start(token, user) {my.wishVel = my.wishVel || { x: 0, z: 0 };my.wishVel.x = my.vel.x; my.wishVel.z = my.vel.z;W.feetY = my.y; // surface-probing colliders judge "climbable" from foot height++ // airborne = full-height collision (tiny .12 clearance): you must truly++ // CLEAR an obstacle to cross it — no more slipping through thin tops++ const stepH = my.onGround ? .5 : .12;[nx, nz] = resolveCollisions(nx, nz, .34);-- [nx, nz] = phys.resolvePlayer(nx, nz, my.y, .34, pgState.held); // props shove people-- [nx, nz] = phys.clipMove(my.x, my.z, nx, nz, my.y); // frozen props: walk into them, just stop++ [nx, nz] = phys.resolvePlayer(nx, nz, my.y, .34, pgState.held, stepH); // props shove people++ [nx, nz] = phys.clipMove(my.x, my.z, nx, nz, my.y, .34, stepH); // frozen props: walk into them, just stopif (dt > 0) { my.vel.x = (nx - my.x) / dt; my.vel.z = (nz - my.z) / dt; } // wall clipmy.x = nx; my.z = nz;const hsp = Math.hypot(my.vel.x, my.vel.z);@@@ -336,7 +336,11 @@ export function initPhysics(scene) {const bdConj = new CANNON.Quaternion();const bdLocal = new CANNON.Vec3();const bdClosest = new CANNON.Vec3();-- function blockDepth(x, z, feetY, r) {++ // stepH: how far below a surface top your feet may sit while still passing.++ // Grounded it's the step height; AIRBORNE it shrinks to ~.1 (Bug58: the .5++ // mantle window let jumps drift through a thin wall's top edge — now your++ // feet must genuinely clear an obstacle to cross it, like Source).++ function blockDepth(x, z, feetY, r, stepH = STEP) {let depth = 0;for (const e of props.values()) {if (!e.frozen) continue;@@@ -346,7 +350,7 @@ export function initPhysics(scene) {if (x < bb.lowerBound.x - r || x > bb.upperBound.x + r ||z < bb.lowerBound.z - r || z > bb.upperBound.z + r) continue;if (bb.lowerBound.y > feetY + 1.7) continue; // frozen shelf overhead-- if (bb.upperBound.y - feetY <= STEP) continue; // low enough to step on++ if (bb.upperBound.y - feetY <= stepH) continue; // low enough to step onconst shape = b.shapes[0];if (shape?.halfExtents) {// closest point on the oriented box to the player axis, sampled at a@@@ -363,7 +367,7 @@ export function initPhysics(scene) {Math.max(-he.z, Math.min(he.z, bdLocal.z)));b.quaternion.vmult(bdClosest, bdClosest);const wy = bdClosest.y + b.position.y - feetY;-- if (wy <= STEP || wy > 1.7) continue; // touching only a climbable/overhead part++ if (wy <= stepH || wy > 1.7) continue; // touching only a climbable/overhead partconst hd = Math.hypot(bdClosest.x + b.position.x - x, bdClosest.z + b.position.z - z);if (r - hd > depth) depth = r - hd;}@@@ -376,28 +380,28 @@ export function initPhysics(scene) {}return depth;}-- function clipMove(px, pz, nx, nz, feetY, r = .34) {++ function clipMove(px, pz, nx, nz, feetY, r = .34, stepH = STEP) {if (nx === px && nz === pz) return [nx, nz];-- const d0 = blockDepth(px, pz, feetY, r);++ const d0 = blockDepth(px, pz, feetY, r, stepH);if (d0 >= r - 1e-3) return [nx, nz]; // fully embedded (broken state): free escapeif (d0 > 0) { // overlapping a face: only moves that get you OUT-- const dn = blockDepth(nx, nz, feetY, r);++ const dn = blockDepth(nx, nz, feetY, r, stepH);return dn < d0 - 1e-4 ? [nx, nz] : [px, pz];}let x = nx;-- if (blockDepth(x, pz, feetY, r) > 0) x = px; // clip X++ if (blockDepth(x, pz, feetY, r, stepH) > 0) x = px; // clip Xlet z = nz;-- if (blockDepth(x, z, feetY, r) > 0) z = pz; // clip Z (with the clipped X)++ if (blockDepth(x, z, feetY, r, stepH) > 0) z = pz; // clip Z (with the clipped X)return [x, z];}// players are solid: shove the walking capsule out of dynamic props so a// physgunned crate PUSHES people instead of clipping through them-- function resolvePlayer(px, pz, py, r, exclude = null) {++ function resolvePlayer(px, pz, py, r, exclude = null, stepH = STEP) {for (const e of props.values()) {if (e === exclude || e.frozen) continue;const bb = e.body.aabb;-- if (bb.upperBound.y - py <= STEP) continue; // low enough to step on++ if (bb.upperBound.y - py <= stepH) continue; // low enough to step onif (bb.lowerBound.y > py + 1.6) continue; // floating overheadconst nx = Math.max(bb.lowerBound.x, Math.min(px, bb.upperBound.x));const nz = Math.max(bb.lowerBound.z, Math.min(pz, bb.upperBound.z));
@@ -2152,9 +2152,12 @@ function start(token, user) {
@@ -2152,9 +2152,12 @@ function start(token, user) {
my.wishVel = my.wishVel || { x: 0, z: 0 };
my.wishVel = my.wishVel || { x: 0, z: 0 };
my.wishVel.x = my.vel.x; my.wishVel.z = my.vel.z;
my.wishVel.x = my.vel.x; my.wishVel.z = my.vel.z;
W.feetY = my.y; // surface-probing colliders judge "climbable" from foot height
W.feetY = my.y; // surface-probing colliders judge "climbable" from foot height
+ // airborne = full-height collision (tiny .12 clearance): you must truly
+ // CLEAR an obstacle to cross it — no more slipping through thin tops
+ const stepH = my.onGround ? .5 : .12;
[nx, nz] = resolveCollisions(nx, nz, .34);
[nx, nz] = resolveCollisions(nx, nz, .34);
- [nx, nz] = phys.resolvePlayer(nx, nz, my.y, .34, pgState.held); // props shove people
- [nx, nz] = phys.clipMove(my.x, my.z, nx, nz, my.y); // frozen props: walk into them, just stop
+ [nx, nz] = phys.resolvePlayer(nx, nz, my.y, .34, pgState.held, stepH); // props shove people
+ [nx, nz] = phys.clipMove(my.x, my.z, nx, nz, my.y, .34, stepH); // frozen props: walk into them, just stop
if (dt > 0) { my.vel.x = (nx - my.x) / dt; my.vel.z = (nz - my.z) / dt; } // wall clip
if (dt > 0) { my.vel.x = (nx - my.x) / dt; my.vel.z = (nz - my.z) / dt; } // wall clip
my.x = nx; my.z = nz;
my.x = nx; my.z = nz;
const hsp = Math.hypot(my.vel.x, my.vel.z);
const hsp = Math.hypot(my.vel.x, my.vel.z);
@@ -336,7 +336,11 @@ export function initPhysics(scene) {
@@ -336,7 +336,11 @@ export function initPhysics(scene) {
const bdConj = new CANNON.Quaternion();
const bdConj = new CANNON.Quaternion();
const bdLocal = new CANNON.Vec3();
const bdLocal = new CANNON.Vec3();
const bdClosest = new CANNON.Vec3();
const bdClosest = new CANNON.Vec3();
- function blockDepth(x, z, feetY, r) {
+ // stepH: how far below a surface top your feet may sit while still passing.
+ // Grounded it's the step height; AIRBORNE it shrinks to ~.1 (Bug58: the .5
+ // mantle window let jumps drift through a thin wall's top edge — now your
+ // feet must genuinely clear an obstacle to cross it, like Source).
+ function blockDepth(x, z, feetY, r, stepH = STEP) {
let depth = 0;
let depth = 0;
for (const e of props.values()) {
for (const e of props.values()) {
if (!e.frozen) continue;
if (!e.frozen) continue;
@@ -346,7 +350,7 @@ export function initPhysics(scene) {
@@ -346,7 +350,7 @@ export function initPhysics(scene) {
if (x < bb.lowerBound.x - r || x > bb.upperBound.x + r ||
if (x < bb.lowerBound.x - r || x > bb.upperBound.x + r ||
z < bb.lowerBound.z - r || z > bb.upperBound.z + r) continue;
z < bb.lowerBound.z - r || z > bb.upperBound.z + r) continue;
if (bb.lowerBound.y > feetY + 1.7) continue; // frozen shelf overhead
if (bb.lowerBound.y > feetY + 1.7) continue; // frozen shelf overhead
- if (bb.upperBound.y - feetY <= STEP) continue; // low enough to step on
+ if (bb.upperBound.y - feetY <= stepH) continue; // low enough to step on
const shape = b.shapes[0];
const shape = b.shapes[0];
if (shape?.halfExtents) {
if (shape?.halfExtents) {
// closest point on the oriented box to the player axis, sampled at a
// closest point on the oriented box to the player axis, sampled at a
@@ -363,7 +367,7 @@ export function initPhysics(scene) {
@@ -363,7 +367,7 @@ export function initPhysics(scene) {
Math.max(-he.z, Math.min(he.z, bdLocal.z)));
Math.max(-he.z, Math.min(he.z, bdLocal.z)));
b.quaternion.vmult(bdClosest, bdClosest);
b.quaternion.vmult(bdClosest, bdClosest);
const wy = bdClosest.y + b.position.y - feetY;
const wy = bdClosest.y + b.position.y - feetY;
- if (wy <= STEP || wy > 1.7) continue; // touching only a climbable/overhead part
+ if (wy <= stepH || wy > 1.7) continue; // touching only a climbable/overhead part
const hd = Math.hypot(bdClosest.x + b.position.x - x, bdClosest.z + b.position.z - z);
const hd = Math.hypot(bdClosest.x + b.position.x - x, bdClosest.z + b.position.z - z);
if (r - hd > depth) depth = r - hd;
if (r - hd > depth) depth = r - hd;
}
}
@@ -376,28 +380,28 @@ export function initPhysics(scene) {
@@ -376,28 +380,28 @@ export function initPhysics(scene) {
}
}
return depth;
return depth;
}
}
- function clipMove(px, pz, nx, nz, feetY, r = .34) {
+ function clipMove(px, pz, nx, nz, feetY, r = .34, stepH = STEP) {
if (nx === px && nz === pz) return [nx, nz];
if (nx === px && nz === pz) return [nx, nz];
- const d0 = blockDepth(px, pz, feetY, r);
+ const d0 = blockDepth(px, pz, feetY, r, stepH);
if (d0 >= r - 1e-3) return [nx, nz]; // fully embedded (broken state): free escape
if (d0 >= r - 1e-3) return [nx, nz]; // fully embedded (broken state): free escape
if (d0 > 0) { // overlapping a face: only moves that get you OUT
if (d0 > 0) { // overlapping a face: only moves that get you OUT
- const dn = blockDepth(nx, nz, feetY, r);
+ const dn = blockDepth(nx, nz, feetY, r, stepH);
return dn < d0 - 1e-4 ? [nx, nz] : [px, pz];
return dn < d0 - 1e-4 ? [nx, nz] : [px, pz];
}
}
let x = nx;
let x = nx;
- if (blockDepth(x, pz, feetY, r) > 0) x = px; // clip X
+ if (blockDepth(x, pz, feetY, r, stepH) > 0) x = px; // clip X
let z = nz;
let z = nz;
- if (blockDepth(x, z, feetY, r) > 0) z = pz; // clip Z (with the clipped X)
+ if (blockDepth(x, z, feetY, r, stepH) > 0) z = pz; // clip Z (with the clipped X)
return [x, z];
return [x, z];
}
}
// players are solid: shove the walking capsule out of dynamic props so a
// players are solid: shove the walking capsule out of dynamic props so a
// physgunned crate PUSHES people instead of clipping through them
// physgunned crate PUSHES people instead of clipping through them
- function resolvePlayer(px, pz, py, r, exclude = null) {
+ function resolvePlayer(px, pz, py, r, exclude = null, stepH = STEP) {
for (const e of props.values()) {
for (const e of props.values()) {
if (e === exclude || e.frozen) continue;
if (e === exclude || e.frozen) continue;
const bb = e.body.aabb;
const bb = e.body.aabb;
- if (bb.upperBound.y - py <= STEP) continue; // low enough to step on
+ if (bb.upperBound.y - py <= stepH) continue; // low enough to step on
if (bb.lowerBound.y > py + 1.6) continue; // floating overhead
if (bb.lowerBound.y > py + 1.6) continue; // floating overhead
const nx = Math.max(bb.lowerBound.x, Math.min(px, bb.upperBound.x));
const nx = Math.max(bb.lowerBound.x, Math.min(px, bb.upperBound.x));
const nz = Math.max(bb.lowerBound.z, Math.min(pz, bb.upperBound.z));
const nz = Math.max(bb.lowerBound.z, Math.min(pz, bb.upperBound.z));