Merge branch 'gfx/1009-shadows' into gfx/1009-upstream
This commit is contained in:
@@ -311,6 +311,55 @@ pub(super) const SUBJECT_INDOOR: [f32; 3] = [-9469.4, 31.9, 57.9];
|
||||
|
||||
/// Every other named viewpoint, capturable by name (`WOW_CAPTURE=<name>`) but not in the sweep.
|
||||
pub(super) const ON_DEMAND: &[Scenario] = &[
|
||||
// MONKEY (wmo surface lights): the portcullis/archery yard and lamp-lined gate ramp.
|
||||
Scenario {
|
||||
name: "stormwind-training-yard-day",
|
||||
map: Some(MAP_AZEROTH),
|
||||
eye: [-8728.0, 341.0, 104.0],
|
||||
look: [-8517.4951, 792.4295, 147.57787],
|
||||
minute: 720,
|
||||
ui: None,
|
||||
},
|
||||
Scenario {
|
||||
name: "stormwind-training-yard-dusk",
|
||||
map: Some(MAP_AZEROTH),
|
||||
eye: [-8728.0, 341.0, 104.0],
|
||||
look: [-8517.4951, 792.4295, 147.57787],
|
||||
minute: 1110,
|
||||
ui: None,
|
||||
},
|
||||
Scenario {
|
||||
name: "stormwind-training-yard-night",
|
||||
map: Some(MAP_AZEROTH),
|
||||
eye: [-8728.0, 341.0, 104.0],
|
||||
look: [-8517.4951, 792.4295, 147.57787],
|
||||
minute: 0,
|
||||
ui: None,
|
||||
},
|
||||
Scenario {
|
||||
name: "stormwind-gate-ramp-day",
|
||||
map: Some(MAP_AZEROTH),
|
||||
eye: [-9095.0, 412.0, 124.0],
|
||||
look: [-8818.3066, 632.09216, -229.55338],
|
||||
minute: 720,
|
||||
ui: None,
|
||||
},
|
||||
Scenario {
|
||||
name: "stormwind-gate-ramp-dusk",
|
||||
map: Some(MAP_AZEROTH),
|
||||
eye: [-9095.0, 412.0, 124.0],
|
||||
look: [-8818.3066, 632.09216, -229.55338],
|
||||
minute: 1110,
|
||||
ui: None,
|
||||
},
|
||||
Scenario {
|
||||
name: "stormwind-gate-ramp-night",
|
||||
map: Some(MAP_AZEROTH),
|
||||
eye: [-9095.0, 412.0, 124.0],
|
||||
look: [-8818.3066, 632.09216, -229.55338],
|
||||
minute: 0,
|
||||
ui: None,
|
||||
},
|
||||
// MONKEY (volumetric fog): UI proof uses the same live page selection as a player.
|
||||
Scenario {
|
||||
name: "ui-options-advanced", map: Some(MAP_AZEROTH),
|
||||
|
||||
@@ -18,7 +18,9 @@ struct MonkeyFrame {
|
||||
wind_a: vec4<f32>, // dir_x, dir_y (unit, world XZ: .x = world x, .y = world z), base_heading (rad, fixed profile heading), gust 0..1
|
||||
wind_b: vec4<f32>, // travel (yd, integrated speed, wrapped at 4096), sway_strength, grass_strength, tree_strength
|
||||
wet_a: vec4<f32>, // rain_rate 0..1, wetness 0..1, ripple_time_s, snow 0..1
|
||||
misc: vec4<f32>, // bender_count, time_of_day 0..1 (packer), night 0..1 (packer), moon shadow confidence 0..1
|
||||
// MONKEY (wmo surface lights): misc.x's integer is the bender count; its fraction is 0.5
|
||||
// when enhanced exterior points are enabled, zero in Classic. Wind truncates to u32.
|
||||
misc: vec4<f32>, // bender_count + exterior gate, time_of_day 0..1, night 0..1, moon shadow confidence
|
||||
benders: array<vec4<f32>, 8>, // world xyz (Bevy space) + radius (yd); the first bender_count are live
|
||||
// GFX (moonlight): unit direction toward the moon (Bevy space) + intensity (gamma; 0 = off).
|
||||
moon: vec4<f32>,
|
||||
|
||||
@@ -1195,19 +1195,12 @@ fn vertex(vertex: WowVertex) -> WowVsOut {
|
||||
// at its INSTANCE origin (wow-re wmo-surface-dynamic-light §6 — the receiving unit's own
|
||||
// position, deliberately not the skinned per-vertex matrix).
|
||||
if (m.model_flags.x > 0.5 && m.model_flags.z < 0.5) {
|
||||
// MONKEY (wmo exterior points): an EXTERIOR-class WMO group (`model_flags.z` clear = MOGP
|
||||
// `& 0x48` set) is a street, a courtyard, a porch — drawn by the exterior law, the same law
|
||||
// the terrain beside it is drawn by, so it takes the exterior point term terrain takes. The
|
||||
// §2 "zero on every WMO surface" finding was measured on abbey INTERIOR rooms and reading
|
||||
// it as a blanket zero is what left Stormwind's Trade District torches lighting nothing but
|
||||
// the NPC beside them. Mirrors `static_gx.wgsl`'s branch (which draws the vast majority of
|
||||
// these batches) — keep the two in step.
|
||||
// MONKEY (outdoor torch shadows): the pick is PUBLISHED so the fragment stage can shadow
|
||||
// this same selection at night without re-ranking. One table walk still, not two -
|
||||
// the sum was always `eval(pick(..))`, it is just no longer inlined.
|
||||
let sel = wmo_exterior_pick(out.world_position.xyz);
|
||||
out.ext_sel = sel;
|
||||
// MONKEY (B035 per-fragment wmo points): summed per FRAGMENT now, as in `static_gx.wgsl`.
|
||||
// MONKEY (wmo surface lights): Classic retains its MCNK vertex pick. Enhanced WMO
|
||||
// picks once at the fragment instead; no per-vertex table walk on that lane.
|
||||
out.ext_sel = EXT_SEL_NONE;
|
||||
if (fract(wow_light.monkey.misc.x) == 0.0) {
|
||||
out.ext_sel = wmo_exterior_pick(out.world_position.xyz);
|
||||
}
|
||||
} else if (m.model_flags.x > 0.5 || m.model_flags.z > 0.5) {
|
||||
out.ext_sel = EXT_SEL_NONE;
|
||||
} else {
|
||||
@@ -1607,16 +1600,20 @@ fn fragment(in: WowVsOut, @builtin(front_facing) is_front: bool) -> WowFragOut {
|
||||
}
|
||||
}
|
||||
let sidn_e = m.sidn.rgb * (wow_light.grade.x * sidn_w);
|
||||
// MONKEY (local-light consistency): all exterior receivers sum their committed lights
|
||||
// per fragment. Sparse prop vertices must not spread the near-field peak across
|
||||
// furniture while the adjacent terrain and WMO evaluate a different pool.
|
||||
// Interior receivers publish EXT_SEL_NONE and use the separate room-light lane.
|
||||
var point_diffuse = point_light_eval(in.ext_sel, in.world_position.xyz, n_lit);
|
||||
// MONKEY (wmo surface lights): a city WMO spans many terrain cells. A flat vertex pick
|
||||
// draws its lamp pool on whole triangles; a snapped cell pick moves the seam to the grid.
|
||||
// Enhanced exterior WMO ranks at the receiving fragment, within the existing 48 yd reach.
|
||||
// Props keep their placement pick, interior receivers their room lane, Classic its old pick.
|
||||
var exterior_selection = in.ext_sel;
|
||||
if (is_wmo && !is_interior && fract(wow_light.monkey.misc.x) > 0.0) {
|
||||
exterior_selection = point_light_pick(in.world_position.xyz, 0.0);
|
||||
}
|
||||
var point_diffuse = point_light_eval(exterior_selection, in.world_position.xyz, n_lit);
|
||||
let ext_night_w = select(0.0, clamp(1.0 - wow_light.fog_params.z, 0.0, 1.0), torch_ext_on());
|
||||
if (ext_night_w > 0.0) {
|
||||
point_diffuse = mix(
|
||||
point_diffuse,
|
||||
point_light_eval_shadowed(in.ext_sel, in.world_position.xyz, n_lit),
|
||||
point_light_eval_shadowed(exterior_selection, in.world_position.xyz, n_lit),
|
||||
ext_night_w,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -756,10 +756,18 @@ fn pack_monkey_frame(
|
||||
frame: Res<super::MonkeyFrame>,
|
||||
clock: Res<super::GameClock>,
|
||||
light: Res<WowLighting>,
|
||||
fire_gain: Res<FireLightGain>,
|
||||
spell_gain: Res<SpellLightGain>,
|
||||
lava_gain: Option<Res<super::LavaLightGain>>,
|
||||
mut data: ResMut<WowLightData>,
|
||||
) {
|
||||
let night = 1.0 - sun_shadow_strength(light.celestial_dir.y);
|
||||
let packed = frame.pack(clock.minute as f32 / 1440.0, night);
|
||||
let mut packed = frame.pack(clock.minute as f32 / 1440.0, night);
|
||||
// MONKEY (wmo surface lights): wind truncates misc.x to u32, leaving its fraction free.
|
||||
// All enhanced point gains zero (Classic) preserves the old pick and frame bits.
|
||||
if fire_gain.0 > 0.0 || spell_gain.0 > 0.0 || lava_gain.is_some_and(|gain| gain.0 > 0.0) {
|
||||
packed[7][0] += 0.5;
|
||||
}
|
||||
if data.0.monkey != packed {
|
||||
data.0.monkey = packed;
|
||||
}
|
||||
@@ -2381,6 +2389,53 @@ mod tests {
|
||||
assert_eq!(gpu.len(), ROOM_CLAIM_STRIDE, "2 head + 6 ids + 6 x 4 fade");
|
||||
}
|
||||
|
||||
/// MONKEY (wmo surface lights): Classic keeps all frame bits, and the enhanced gate must
|
||||
/// never alter the wind shader's integer bender count or any other programme row.
|
||||
#[test]
|
||||
fn the_wmo_surface_gate_preserves_classic_and_wind_counts() {
|
||||
let mut app = App::new();
|
||||
app.init_resource::<super::super::MonkeyFrame>()
|
||||
.init_resource::<super::super::GameClock>()
|
||||
.init_resource::<WowLighting>()
|
||||
.init_resource::<WowLightData>()
|
||||
.insert_resource(FireLightGain(0.0))
|
||||
.insert_resource(SpellLightGain(0.0))
|
||||
.insert_resource(super::super::LavaLightGain(0.0))
|
||||
.add_systems(Update, pack_monkey_frame);
|
||||
for count in 0..=8 {
|
||||
app.world_mut()
|
||||
.resource_mut::<super::super::MonkeyFrame>()
|
||||
.bender_count = count;
|
||||
let frame = app.world().resource::<super::super::MonkeyFrame>();
|
||||
let classic = frame.pack(0.0, 1.0);
|
||||
for gains in [
|
||||
[0.0, 0.0, 0.0],
|
||||
[0.25, 0.0, 0.0],
|
||||
[0.0, 0.25, 0.0],
|
||||
[0.0, 0.0, 0.25],
|
||||
[1.0, 1.0, 1.0],
|
||||
[0.0, 0.0, 0.0],
|
||||
] {
|
||||
app.world_mut().resource_mut::<FireLightGain>().0 = gains[0];
|
||||
app.world_mut().resource_mut::<SpellLightGain>().0 = gains[1];
|
||||
app.world_mut()
|
||||
.resource_mut::<super::super::LavaLightGain>()
|
||||
.0 = gains[2];
|
||||
app.update();
|
||||
let packed = app.world().resource::<WowLightData>().0.monkey;
|
||||
assert_eq!(
|
||||
packed[7][0] as u32, count,
|
||||
"wind count must truncate unchanged"
|
||||
);
|
||||
let mut expected = classic;
|
||||
if gains.iter().any(|gain| *gain > 0.0) {
|
||||
expected[7][0] += 0.5;
|
||||
}
|
||||
assert_eq!(packed, expected, "only the enhanced gate may move");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// GOLDEN — MONKEY (fire GO lights): `fireLightGain` scales SYNTHESISED sources and **only**
|
||||
/// them, at pack time.
|
||||
///
|
||||
|
||||
@@ -1424,18 +1424,12 @@ fn vertex(v: GxVertex) -> GxVsOut {
|
||||
// Gouraud: straight-edged wedges, and a torch mid-triangle lights nothing.
|
||||
out.ext_sel = EXT_SEL_NONE;
|
||||
} else if ((v.word & WORD_WMO) != 0u) {
|
||||
// MONKEY (wmo exterior points): an EXTERIOR-class group (MOGP `& 0x48`) is a street, a
|
||||
// courtyard, a porch — drawn by the exterior law, so it takes the exterior point term the
|
||||
// terrain beside it takes. Its own anchor is the placement origin (one point for all of
|
||||
// Stormwind), so `wmo_exterior_point_sum` re-anchors on the MCNK cell; see its comment.
|
||||
// Selection stays per vertex; light falloff is evaluated per fragment.
|
||||
// MONKEY (outdoor torch shadows): the pick is published so the fragment stage can shadow
|
||||
// this same selection at night without re-ranking (see `EXT_SEL_NONE`). One table walk
|
||||
// still, not two — the sum was always `eval(pick(...))`, it is just no longer inlined.
|
||||
let sel = wmo_exterior_pick(world);
|
||||
out.ext_sel = sel;
|
||||
// MONKEY (B035 per-fragment wmo points): the SUM moved to the fragment stage (see there);
|
||||
// only the pick stays per vertex, so this lane still walks the table once per vertex.
|
||||
// MONKEY (wmo surface lights): Classic retains its MCNK vertex pick. Enhanced WMO
|
||||
// picks once at the fragment instead; no per-vertex table walk on that lane.
|
||||
out.ext_sel = EXT_SEL_NONE;
|
||||
if (fract(wow_light.monkey.misc.x) == 0.0) {
|
||||
out.ext_sel = wmo_exterior_pick(world);
|
||||
}
|
||||
} else {
|
||||
// MONKEY (ext light k12): `box = 0` — an exterior doodad/MODD prop is its own draw unit and
|
||||
// ranks from its baked PLACEMENT origin, exactly as it did before the widening.
|
||||
@@ -1489,15 +1483,20 @@ fn fragment(in: GxVsOut) -> @location(0) vec4<f32> {
|
||||
// Both faces light from the submitted normal (no GL_LIGHT_MODEL_TWO_SIDE in the reference);
|
||||
// this pipeline never negates back faces, so it has no front-face select like wow_model.wgsl.
|
||||
let n_lit = wow_normalize(in.world_normal);
|
||||
// MONKEY (local-light consistency): the same per-fragment falloff for WMO and props.
|
||||
// The vertex still ranks once and publishes its flat selection. Interior receivers
|
||||
// publish EXT_SEL_NONE and keep their room-light lane.
|
||||
var point_lit = point_light_eval(in.ext_sel, in.world_position.xyz, n_lit);
|
||||
// MONKEY (wmo surface lights): a city WMO spans many terrain cells. A flat vertex pick
|
||||
// draws its lamp pool on whole triangles; a snapped cell pick moves the seam to the grid.
|
||||
// Enhanced exterior WMO ranks at the receiving fragment, within the existing 48 yd reach.
|
||||
// Props keep their placement pick, interior receivers their room lane, Classic its old pick.
|
||||
var exterior_selection = in.ext_sel;
|
||||
if ((in.word & WORD_WMO) != 0u && (in.word & WORD_INTERIOR) == 0u && fract(wow_light.monkey.misc.x) > 0.0) {
|
||||
exterior_selection = point_light_pick(in.world_position.xyz, 0.0);
|
||||
}
|
||||
var point_lit = point_light_eval(exterior_selection, in.world_position.xyz, n_lit);
|
||||
let ext_night_w = select(0.0, clamp(1.0 - wow_light.fog_params.z, 0.0, 1.0), torch_ext_on());
|
||||
if (ext_night_w > 0.0) {
|
||||
point_lit = mix(
|
||||
point_lit,
|
||||
point_light_eval_shadowed(in.ext_sel, in.world_position.xyz, n_lit),
|
||||
point_light_eval_shadowed(exterior_selection, in.world_position.xyz, n_lit),
|
||||
ext_night_w,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user