anim lod: a far on-screen rig parks, so a city crowd costs no per-bone work

`animLodDistance` (0..=300 yd, default 60, `0` off) adds a distance leg to the animation-LOD
gate: a rig past it parks its pose evaluation even when it is in frame and in a PVS-visible
room, joining the frustum and room legs. The gate reads the world camera's `GlobalTransform`
for the position. `0` restores the shipped frustum-and-room-only behaviour.
This commit is contained in:
chairmanpow
2026-10-03 17:41:40 +01:00
parent 55040a63ea
commit d9c820ea27
3 changed files with 83 additions and 14 deletions
+59 -14
View File
@@ -1,6 +1,6 @@
//! The animation-LOD gate: a rig off the frustum, or in a room the portal PVS does not reach,
//! parks its pose evaluation ([`AnimParked`]) while its clocks run on; every [`RigPose`] is
//! covered, GameObject rigs included. The reference elects every scene object each frame
//! The animation-LOD gate: a rig off the frustum, past `animLodDistance` yards, or in a room the
//! portal PVS does not reach, parks its pose evaluation ([`AnimParked`]) while its clocks run on;
//! every [`RigPose`] is covered, GameObject rigs included. The reference elects every scene object each frame
//! (`0x683dd0`): a pass-2 unit is neither drawn, animated nor event-ticked, except a
//! `MORE_AUDIBLE` creature, re-linked for the tick (`0x607da0`). So a parked rig fires no event
//! keys unless flagged, and wakes to the pose its absolute clock dictates (`0x714260` samples the
@@ -38,14 +38,16 @@ const RADIUS_SCALE: f32 = 2.0;
const RADIUS_PAD: f32 = 4.0;
const FALLBACK_RADIUS: f32 = 6.0;
/// Park or wake each rig by its padded frustum sphere and its room, before [`AnimationSystems`] so
/// a wake samples the same frame. The body we drive and its mount never park: the camera rides its
/// attachment 17 and it animates faded out in first person. `WOW_NO_ANIM_LOD=1` never parks and
/// `WOW_NO_ROOM_LOD=1` drops the room leg.
/// Park or wake each rig by its padded frustum sphere, its distance from the camera and its room,
/// before [`AnimationSystems`] so a wake samples the same frame. The body we drive and its mount
/// never park: the camera rides its attachment 17 and it animates faded out in first person.
/// `WOW_NO_ANIM_LOD=1` never parks, `WOW_NO_ROOM_LOD=1` drops the room leg and `animLodDistance 0`
/// drops the distance leg.
#[allow(clippy::type_complexity)] // one Bevy system's full input set
pub(super) fn gate_rig_animation(
time: Res<Time>,
cam: Query<&Frustum, With<WorldCamera>>,
video: Res<crate::video::VideoConfig>,
cam: Query<(&Frustum, &GlobalTransform), With<WorldCamera>>,
rigs: Query<
(
Entity,
@@ -83,9 +85,12 @@ pub(super) fn gate_rig_animation(
// the PVS rejects, even under `WOW_NO_ROOM_LOD`.
let cost_on = benilla_world::rig_palette::rig_cost_enabled();
let (mut n_rigs, mut n_parked, mut room_out) = (0u32, 0u32, 0u32);
let Ok(frustum) = cam.single() else {
let Ok((frustum, cam_tf)) = cam.single() else {
return;
};
let cam_pos = cam_tf.translation();
let max_dist = video.anim_lod_distance;
let max_dist2 = max_dist * max_dist;
let now = time.elapsed_secs();
for (entity, tf, radius, parked, is_self, mount, room) in &rigs {
let exempt =
@@ -101,15 +106,19 @@ pub(super) fn gate_rig_animation(
},
false,
);
// The room leg: only a rig the frustum leg kept can be room-parked.
let room_in = !sphere_in || (no_room && !cost_on) || {
// The distance leg: on-screen but past `animLodDistance` parks too. `0` disables it.
let dist_in =
max_dist <= 0.0 || tf.translation().distance_squared(cam_pos) <= max_dist2;
let keep = sphere_in && dist_in;
// The room leg: only a rig the frustum + distance legs kept can be room-parked.
let room_in = !keep || (no_room && !cost_on) || {
let ok = room_pvs_visible(room, &instances, &wmos);
if !ok {
room_out += 1;
}
ok || no_room
};
sphere_in && room_in
keep && room_in
};
if cost_on {
n_rigs += 1;
@@ -268,8 +277,11 @@ mod tests {
/// at +Z are behind it.
fn spawn_camera(app: &mut App) {
let clip_from_world = Mat4::perspective_rh(1.2, 1.0, 0.1, 1000.0);
app.world_mut()
.spawn((WorldCamera, Frustum::from_clip_from_world(&clip_from_world)));
app.world_mut().spawn((
WorldCamera,
Frustum::from_clip_from_world(&clip_from_world),
GlobalTransform::default(),
));
}
fn app() -> App {
@@ -281,6 +293,9 @@ mod tests {
));
app.init_asset::<WmoModel>();
app.add_message::<AnimSoundEvent>();
// The gate reads `animLodDistance`; the default (60 yd) leaves the frustum tests' rigs
// (20 and 50 yd out) inside, so only the frustum/room legs decide them.
app.insert_resource(crate::video::VideoConfig::default());
// Empty: every parked rig reads unflagged.
app.init_resource::<crate::names::NameCache>();
// The live order: the gate, the pose evaluation, then the event scan.
@@ -563,6 +578,36 @@ mod tests {
assert_eq!(frozen, bone(&app, joint), "the parked GO rig's bones hold");
}
/// An in-frame rig past `animLodDistance` parks on the distance leg, and wakes when it comes
/// inside — the on-screen-but-far crowd a city needs parked.
#[test]
fn a_far_in_frame_rig_parks_on_the_distance_leg() {
let mut app = app();
spawn_camera(&mut app);
app.world_mut()
.resource_mut::<crate::video::VideoConfig>()
.anim_lod_distance = 30.0;
let (far, joint) = spawn_rig(&mut app, -Vec3::Z * 50.0); // in frame, 50 yd out
advance(&mut app, PARK_AFTER_SECS + 0.2, 4);
assert!(
app.world().entity(far).contains::<AnimParked>(),
"in-frame but past animLodDistance ⇒ parked"
);
let frozen = bone(&app, joint);
advance(&mut app, DUR * 0.35, 2);
assert_eq!(frozen, bone(&app, joint), "the distance-parked bone holds");
// Inside the distance: wake instantly and sample again.
app.world_mut()
.entity_mut(far)
.insert(GlobalTransform::from_translation(-Vec3::Z * 20.0));
app.update();
assert!(
!app.world().entity(far).contains::<AnimParked>(),
"inside animLodDistance ⇒ instant wake"
);
}
#[test]
fn the_self_avatar_is_exempt() {
let mut app = app();
+5
View File
@@ -685,6 +685,11 @@ pub(crate) const REGISTERED: &[Registered] = &[
"1",
"benilla's own: multiplier on the shadow caster-collection reach, 0.25..2 (1 = unchanged)",
),
ours(
"animLodDistance",
"60",
"benilla's own: park a rig's pose evaluation past this many yards, 0..300 (0 = frustum/room only)",
),
// MONKEY (moon shadows): how dark a MOON-shadowed fragment is allowed to get at night — the
// dial over the night lane's own shadow term (`benilla_world::lighting::MoonShadowStrength`,
// bridged by `dynamic_interior` beside the spell gain). `0` is the FAITHFUL null: every
+19
View File
@@ -68,6 +68,11 @@ pub(crate) enum DisplayMode {
/// The windowed size a fresh config gets.
pub(crate) const DEFAULT_WINDOWED: UVec2 = UVec2::new(1600, 900);
/// `animLodDistance`: yards past which a rig's pose evaluation parks. 60 is past the range a
/// city-crowd member reads as more than a speck; `0` disables the distance leg.
pub(crate) const ANIM_LOD_DISTANCE_DEFAULT: f32 = 60.0;
pub(crate) const ANIM_LOD_DISTANCE_RANGE: std::ops::RangeInclusive<f32> = 0.0..=300.0;
/// `gxWindow`'s value to the mode, by the reference's 0/1 parse (int, `!= 0`); shared by
/// [`crate::cvars`]'s arm and the boot read.
pub(crate) fn display_from_flag(v: f32) -> DisplayMode {
@@ -199,6 +204,12 @@ pub(crate) struct VideoConfig {
/// proxies. Trimming it is the direct dial on caster POPULATION — the input to both lanes'
/// per-rebuild cost — at the risk of a tall caster's shadow popping in as you approach.
pub(crate) shadow_caster_reach: f32,
/// MONKEY (anim LOD): park a rig's pose evaluation past this distance in yards
/// (`animLodDistance`, 0..=300, default 60). Off-frustum rigs and dark-room rigs already park
/// (`creature_anim::lod`); this adds the on-screen-but-far leg, so a city crowd past the
/// horizon of interest costs no per-bone evaluation, no palette rebuild and no transform
/// propagation. `0` disables the distance leg (frustum + room only — the shipped behaviour).
pub(crate) anim_lod_distance: f32,
/// MONKEY (moon shadows): how dark a MOON-shadowed fragment is allowed to get at night
/// (`moonShadowStrength`, 0..1, default 0.35 = the fragment keeps 65 % of the night
/// directional term). Bridged to [`benilla_world::lighting::MoonShadowStrength`] by
@@ -359,6 +370,7 @@ impl Default for VideoConfig {
character_shadow_rate: crate::shadow_core::DEFAULT_SHADOW_RATE,
world_shadow_rate: crate::shadow_core::DEFAULT_SHADOW_RATE,
shadow_caster_reach: 1.0,
anim_lod_distance: ANIM_LOD_DISTANCE_DEFAULT,
// MONKEY (moon shadows): 0.35 — a hint of a silhouette, not a daylight-hard shadow.
moon_shadow_strength: 0.35,
// The cvar defaults are the source of truth at load; these only stand in until then.
@@ -535,6 +547,13 @@ pub(crate) fn on_cvar(
*crate::shadow_core::CASTER_REACH_RANGE.end(),
)
}
// `0` is meaningful: the distance leg off, frustum + room only (the shipped behaviour).
"animloddistance" => {
cfg.anim_lod_distance = v.clamp(
*ANIM_LOD_DISTANCE_RANGE.start(),
*ANIM_LOD_DISTANCE_RANGE.end(),
)
}
// MONKEY (moon shadows): the night lane's shadow darkness. 0 IS meaningful (the lane off,
// bit-identical to the pre-feature night), so it floors at 0 rather than at a
// minimum-useful value; 1 is a daylight-hard silhouette by moonlight.