Let shot desk props move from where they are placed, and stand on desks

A placed prop often starts inside the world: a monitor's stand sits a
hair inside the desk, and its unrotated model bounds reach into the
cubicle wall behind it. integrate_body stops a body that starts inside,
so such a prop never moved however often it was shot, such as the
back-office com_widescreen_monitor on mp_terminal. The physics also
collided only with solid, while a static-model desk stands in the
clipmap as item clip, so a keyboard or phone knocked off its spot
dropped through the desk to the floor.

On its first wake a body now takes the nearest clear pose (small lifts
up to 2 units, then a footprint narrowed to 75% or 50%), colliding with
solid and item clip where it can and with solid alone where it can't,
and keeps that mask. A prop with no clear pose stays where it was
placed and settles, as 61b7c43 made it. Props still only slide; they
don't tip over.

Measured on our build with a replay of the map data: of the 1007
mp_terminal dynents with physics, 78 started stuck and this frees 38 of
them, and it freezes none that moved before. On an iPhone build on
mp_terminal, the back-office keyboards, phones and monitors moved when
shot and stayed on their desks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
JohnM
2026-10-13 20:40:53 +10:00
parent f46d5aadcd
commit 0bbab05479
@@ -17,6 +17,10 @@ const MSEC_STEP: i32 = 17;
const MAX_ITERS: i32 = 2;
const CONTENTS_SOLID: u32 = 1;
const CONTENTS_ITEMCLIP: u32 = 0x400;
const WAKE_MASKS: [u32; 2] = [CONTENTS_SOLID | CONTENTS_ITEMCLIP, CONTENTS_SOLID];
const WAKE_LIFTS: [f32; 6] = [0.0, CONTACT_SLOP, 0.25, 0.5, 1.0, 2.0];
const WAKE_FOOTPRINTS: [f32; 3] = [1.0, 0.75, 0.5];
const SLEEP_SPEED: f32 = 4.0;
const GROUND_NZ: f32 = 0.7;
const CONTACT_SLOP: f32 = 0.125;
@@ -56,6 +60,7 @@ struct DynEntPhysBody {
maxs: [f32; 3],
bounce: f32,
friction: f32,
mask: u32,
on_ground: bool,
asleep_ms: i32,
}
@@ -99,6 +104,7 @@ pub fn step_phys_world0(
}
let (mins, maxs) = xmodel_phys_hull(catalog, &inst.current_model);
let add_vel = impulse.impulse / preset.mass;
let phys_clip = clip.0.as_deref();
world
.bodies
.entry(entity)
@@ -106,15 +112,22 @@ pub fn step_phys_world0(
body.vel += add_vel;
body.asleep_ms = 0;
})
.or_insert_with(|| DynEntPhysBody {
origin: transform.translation,
vel: add_vel,
mins,
maxs,
bounce: preset.bounce,
friction: preset.friction,
on_ground: false,
asleep_ms: 0,
.or_insert_with(|| {
let pose = phys_clip
.and_then(|clip| clear_wake_pose(clip, transform.translation, mins, maxs));
let (origin, mins, maxs, mask) =
pose.unwrap_or((transform.translation, mins, maxs, CONTENTS_SOLID));
DynEntPhysBody {
origin,
vel: add_vel,
mins,
maxs,
bounce: preset.bounce,
friction: preset.friction,
mask,
on_ground: false,
asleep_ms: 0,
}
});
}
@@ -185,6 +198,39 @@ pub fn step_phys_world0(
}
}
fn clear_wake_pose(
clip: &ClipCollision,
origin: Vec3,
mins: [f32; 3],
maxs: [f32; 3],
) -> Option<(Vec3, [f32; 3], [f32; 3], u32)> {
for mask in WAKE_MASKS {
for scale in WAKE_FOOTPRINTS {
let (mins, maxs) = narrowed_footprint(mins, maxs, scale);
for lift in WAKE_LIFTS {
let at = (origin + Vec3::Z * lift).to_array();
let probe = clip.sweep_box(at, at, mins, maxs, mask);
if !probe.startsolid && !probe.allsolid {
return Some((Vec3::from_array(at), mins, maxs, mask));
}
}
}
}
None
}
fn narrowed_footprint(mins: [f32; 3], maxs: [f32; 3], scale: f32) -> ([f32; 3], [f32; 3]) {
let mut lo = mins;
let mut hi = maxs;
for axis in 0..2 {
let mid = (mins[axis] + maxs[axis]) * 0.5;
let half = (maxs[axis] - mins[axis]) * 0.5 * scale;
lo[axis] = mid - half;
hi[axis] = mid + half;
}
(lo, hi)
}
fn integrate_free(body: &mut DynEntPhysBody, dt: f32) {
body.vel.z += GRAVITY_Z * dt;
body.origin += body.vel * dt;
@@ -200,7 +246,7 @@ fn integrate_body(body: &mut DynEntPhysBody, clip: &ClipCollision, dt: f32) {
end.to_array(),
body.mins,
body.maxs,
CONTENTS_SOLID,
body.mask,
);
if hit.allsolid || hit.startsolid {
body.vel = Vec3::ZERO;