From 0bbab054794ae5e329137b9a61caefabc237a0ea Mon Sep 17 00:00:00 2001 From: JohnM <234386938+treuenten@users.noreply.github.com> Date: Tue, 13 Oct 2026 20:40:53 +1000 Subject: [PATCH] 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 --- .../render_anim/src/occupancy/dyn_ent_phys.rs | 66 ++++++++++++++++--- 1 file changed, 56 insertions(+), 10 deletions(-) diff --git a/crates/render_anim/src/occupancy/dyn_ent_phys.rs b/crates/render_anim/src/occupancy/dyn_ent_phys.rs index 82d095f..f082f70 100644 --- a/crates/render_anim/src/occupancy/dyn_ent_phys.rs +++ b/crates/render_anim/src/occupancy/dyn_ent_phys.rs @@ -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;