MONKEY (integration): merge gfx/lampfog
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -33,6 +33,7 @@ behind each constant.
|
||||
| Rain on surfaces | MONKEY (wet): rain gradually darkens and saturates exposed terrain and models, adds puddles and a sky/sun sheen, and produces rings on exterior Enhanced water. Wetness rises over roughly 90 seconds and dries over roughly four minutes; phase 1 has no rain-occlusion map, so shelter is classified only by interior/unlit state and surface facing | Advanced Graphics → Wet Surfaces in Rain, cvar `rainSurfaces` 0/1 (default and High 1), env `WOW_RAIN_SURFACES`, `WOW_WETNESS`, `WOW_WET_T`; `weather/wetness.rs`, `wet_hook.wgsl` |
|
||||
| Foliage wind | one weather-fed gust/veer field drives grass and classified tree/bush foliage; grass also parts around the player and nearby units | Advanced Graphics → Foliage Wind, cvar `foliageWind` 0 Off / 1 Grass / 2 Grass + Trees (High = 2), capture override `WOW_FOLIAGE_WIND`; field and benders in `benilla-world/src/wind/`. The wave phase is the CPU-integrated travel (speed integrated over time, wrapped seamlessly), and the waves' spatial term uses the fixed profile heading; the veer turns only the bend. Tree exile copies sway through material marker bit 14 (`FOLIAGE_WIND_MARKER`). Capture offset `WOW_CAPTURE_WIND_T` (s). Known: shadow casters (world-shadow proxies, `torch_depth.wgsl`) do not apply the sway, so leaves sample a static shadow; a future world-camera DepthPrepass must call the same offsets |
|
||||
| Ambient occlusion | soft contact shadows under props, in wall-floor corners and around feet; depth-only SSAO at half resolution (depth-reconstructed normals, 6/12 spiral taps, 4x4 depth-aware blur, bilateral upsample) multiplied into the opaque scene after the opaque pass and before the water copy, so water, particles and UI are untouched; fades out 45-90 yd (Low) / 60-120 yd (High), spares sky and bright pixels (`benilla-app/src/ssao.rs`, `benilla-app/src/shaders/ssao.wgsl`) | Advanced Graphics → Ambient Occlusion (Off/Low/High; default Off, High preset 2), cvar `ambientOcclusion`, env `WOW_AO=0\|1\|2`, `WOW_AO_DEBUG=1..4` diagnostic views (factor, protection, distance, raw occlusion), `WOW_AO_GAIN`/`_RADIUS`/`_BIAS`/`_STRENGTH` tuning |
|
||||
| Lamp fog | MONKEY (lampfog): the fog fullscreen pass selects the nearest resolved exterior and interior point-table sources and analytically integrates their softened inverse-square light over the camera-to-scene-depth ray. A near-field haze floor makes lamps visible inside the ordinary volumetric haze's 10 yd clear zone; it is zero by day, stronger in rain/fog, and deliberately unshadowed. The table colour already includes intensity, flame flicker and `fireLightGain`. Halo output uses destination headroom and stays below 1.0, so a later bloom pass cannot blow it out | Advanced Graphics → Lamp Fog, cvar `lampFog` 0 Off (default, unchanged) / 1 Low (16 lamps) / 2 High (32 lamps; Graphics preset High = 2), env `WOW_LAMPFOG=0\|1\|2`; `benilla-app/src/volumetric_fog.rs` |
|
||||
| Night and interior level | global dimming of the night sky term and of interior ambient | `nightGain`, `interiorGain`, `interiorBakeFloor` |
|
||||
|
||||
Players reach all of it from **Options -> Advanced Graphics** (a Lighting Quality preset Off / Low / Medium / High plus the individual rows; Off is the original client look). The dev build has a panel for all of it: **Ctrl+Shift+D → Lighting & shadows**, with Dim / Default /
|
||||
@@ -98,6 +99,9 @@ is its own module, documented in `WATER.md`.
|
||||
- World lights use upstream's `WorldPointLight`, never Bevy's `PointLight`.
|
||||
- Foliage wind is vertex-only. Tree and leaf shadow meshes stay static; the small mismatch is the
|
||||
accepted first-stage cost/complexity tradeoff.
|
||||
- MONKEY (lampfog): `ResolvedPointLights` is a CPU-only read view of the live, truncated point table;
|
||||
it does not add a row to the shared GPU buffer. Lamp selection is capped at 100 yd and ranks both
|
||||
exterior and interior rows together before the 16/32 nearest limit is applied.
|
||||
- WGSL only fails at pipeline creation, so a shader edit is verified by running a world scene, not
|
||||
by `cargo check`.
|
||||
- Exterior point lights are selected per draw unit (12 slots, ranked against the chunk's box); the
|
||||
|
||||
@@ -538,6 +538,8 @@
|
||||
windTrees = "Grass + Trees",
|
||||
-- MONKEY (ao): localised contact-shadow row.
|
||||
ambientOcclusion = "Ambient Occlusion",
|
||||
-- MONKEY (lampfog)
|
||||
lampFog = "Lamp Fog",
|
||||
yards = "%d yd",
|
||||
tips = {
|
||||
LIGHTING_QUALITY =
|
||||
@@ -677,6 +679,12 @@
|
||||
.. "High is smoother and reaches a little further.\n\n"
|
||||
.. "Fades out with distance and spares the sky, water, lit windows and flames. "
|
||||
.. "Off leaves the image unchanged.",
|
||||
-- MONKEY (lampfog)
|
||||
LAMP_FOG =
|
||||
"Makes nearby lamps and fires glow through the air after dark. Low uses the "
|
||||
.. "nearest 16 lights; High uses 32. Rain and fog strengthen the halos. "
|
||||
.. "The effect is unshadowed, but stops at visible scene depth.\n\n"
|
||||
.. "Off is unchanged, and daylight is unchanged at every setting.",
|
||||
},
|
||||
},
|
||||
ruRU = {
|
||||
@@ -728,6 +736,8 @@
|
||||
windTrees = "Трава + деревья",
|
||||
-- MONKEY (ao): Russian contact-shadow row.
|
||||
ambientOcclusion = "Затенение окружения",
|
||||
-- MONKEY (lampfog)
|
||||
lampFog = "Свечение фонарей в тумане",
|
||||
yards = "%d ярд.",
|
||||
tips = {
|
||||
LIGHTING_QUALITY =
|
||||
@@ -879,6 +889,12 @@
|
||||
.. "«Высокое» — более плавное и захватывает чуть больше.\n\n"
|
||||
.. "Ослабевает с расстоянием и не затрагивает небо, воду, освещённые окна и пламя. "
|
||||
.. "«Выкл» оставляет изображение без изменений.",
|
||||
-- MONKEY (lampfog)
|
||||
LAMP_FOG =
|
||||
"После заката ближайшие фонари и огни светятся сквозь воздух. Низкое "
|
||||
.. "качество использует 16 ближайших источников, высокое — 32. Дождь и туман усиливают ореолы. "
|
||||
.. "Эффект не рассчитывает тени, но обрывается на видимой глубине.\n\n"
|
||||
.. "Выключено — без изменений; днём изображение не меняется при любом значении.",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -953,6 +969,8 @@
|
||||
"RowFoliageWind",
|
||||
-- MONKEY (ao): appended so the other rows keep their indices.
|
||||
"RowAmbientOcclusion",
|
||||
-- MONKEY (lampfog)
|
||||
"RowLampFog",
|
||||
},
|
||||
-- Player Guild Names is a child of Player Names, as in 1.12 (l.702-708).
|
||||
Nameplates = {
|
||||
@@ -3118,6 +3136,22 @@
|
||||
})
|
||||
</OnLoad></Scripts>
|
||||
</Frame>
|
||||
<!-- MONKEY (lampfog): night point-light airlight tiers. -->
|
||||
<Frame name="$parentRowLampFog" inherits="OptionsDropdownRowTemplate">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeTo="$parentRowAmbientOcclusion" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
|
||||
<Anchor point="TOPRIGHT" relativeTo="$parentRowAmbientOcclusion" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Scripts><OnLoad>
|
||||
OptionsRow_OnLoad(self, "lampFog", BENILLA_ADVGFX.lampFog,
|
||||
"BENILLA_TOOLTIP_LAMP_FOG")
|
||||
OptionsDropdownRow_Setup(self, {
|
||||
{ text = BENILLA_ADVGFX.fogOff, value = "0" },
|
||||
{ text = BENILLA_ADVGFX.qLow, value = "1" },
|
||||
{ text = BENILLA_ADVGFX.qHigh, value = "2" },
|
||||
})
|
||||
</OnLoad></Scripts>
|
||||
</Frame>
|
||||
</Frames>
|
||||
<!-- The poll (see AdvGfx_OnUpdate): this is the one page whose
|
||||
settings move from outside it — the Lighting Quality row
|
||||
|
||||
@@ -1665,6 +1665,44 @@ pub(super) const ON_DEMAND: &[Scenario] = &[
|
||||
eye: [-9495.2, 60.8, 59.0], look: [-9500.00, 56.00, 56.9], minute: 720,
|
||||
ui: Some(UiFixture::Subject { kind: SubjectKind::Creature, at: SUBJECT_SUN }),
|
||||
},
|
||||
// MONKEY (lampfog): lampFog 0/2 A/B set. Kept at the END so parallel lane tables merge cleanly.
|
||||
// Goldshire square: the Lion's Pride fixtures and outdoor lamps share one night frame.
|
||||
Scenario {
|
||||
name: "lampfog-goldshire-night",
|
||||
map: Some(MAP_AZEROTH),
|
||||
eye: [-9460.0, 70.0, 58.0],
|
||||
look: [-9450.0, 20.0, 61.0],
|
||||
minute: 0,
|
||||
ui: None,
|
||||
},
|
||||
// Stormwind Trade District from the established city-scale camera, after dark.
|
||||
Scenario {
|
||||
name: "lampfog-stormwind-night",
|
||||
map: Some(MAP_AZEROTH),
|
||||
eye: [-8833.38, 628.63, 96.0],
|
||||
look: [-8809.1, 672.3, 94.0],
|
||||
minute: 0,
|
||||
ui: None,
|
||||
},
|
||||
// Goldshire's east road, looking through the known exterior lamp at (-9477, 53, 60). This is
|
||||
// deliberately not the fence instrument: that camera looks down at an unlit verge at night.
|
||||
Scenario {
|
||||
name: "lampfog-elwynn-road-night",
|
||||
map: Some(MAP_AZEROTH),
|
||||
eye: [-9505.0, 36.0, 61.5],
|
||||
look: [-9455.0, 75.0, 59.0],
|
||||
minute: 0,
|
||||
ui: None,
|
||||
},
|
||||
// Exact Goldshire framing at noon: lampFog 0 and 2 must be pixel-identical.
|
||||
Scenario {
|
||||
name: "lampfog-goldshire-day",
|
||||
map: Some(MAP_AZEROTH),
|
||||
eye: [-9460.0, 70.0, 58.0],
|
||||
look: [-9450.0, 20.0, 61.0],
|
||||
minute: 720,
|
||||
ui: None,
|
||||
},
|
||||
];
|
||||
|
||||
/// MONKEY (p0 baseline): the Elwynn programme view, above the canopy east of Goldshire looking
|
||||
|
||||
@@ -921,6 +921,12 @@ pub(crate) const REGISTERED: &[Registered] = &[
|
||||
),
|
||||
// MONKEY (volumetric fog): saved live tier; capture override stays session-only.
|
||||
ours("volumetricFog", "1", "benilla's own: near-field volumetric fog, 0 Off / 1 Low / 2 High"),
|
||||
// MONKEY (lampfog): opt-in point-light halos; High graphics preset value is 2.
|
||||
ours(
|
||||
"lampFog",
|
||||
"0",
|
||||
"benilla's own: lamps scattering through night fog, 0 Off / 1 Low (16 lamps) / 2 High (32 lamps)",
|
||||
),
|
||||
// MONKEY (p0 skyDither): the FFXGlow combine's deband dither (was env WOW_DITHER only).
|
||||
// Default 0 = the reference look; the Graphics preset's High sets 1.
|
||||
ours(
|
||||
@@ -2976,7 +2982,7 @@ mod tests {
|
||||
assert_eq!(d["gxVSync"] != 0.0, VideoConfig::default().vsync);
|
||||
// ── MONKEY (lighting): the weld for the dynamic light + shadow system's whole row set ──
|
||||
//
|
||||
// **All 32, as one census, with the count asserted.** Every one of them lands on
|
||||
// **All 34, as one census, with the count asserted.** Every one of them lands on
|
||||
// `VideoConfig` and is read from there per frame (`shadow_core::update_shadows`,
|
||||
// `dynamic_interior::bridge`, `torch_shadow`), so a registered default that drifts from
|
||||
// the struct's literal is a setting that reads one way in `config.toml` and renders
|
||||
@@ -2986,10 +2992,12 @@ mod tests {
|
||||
let shadows = VideoConfig::default();
|
||||
let flag = |b: bool| if b { 1.0 } else { 0.0 };
|
||||
// MONKEY (volumetric fog): include the atmospheric tier in this fixed-size default table.
|
||||
let lighting: [(&str, f32); 37] = [
|
||||
let lighting: [(&str, f32); 38] = [
|
||||
("waterQuality", shadows.water_quality as f32),
|
||||
// MONKEY (volumetric fog): weld registry and renderer defaults.
|
||||
("volumetricFog", shadows.volumetric_fog as f32),
|
||||
// MONKEY (lampfog): opt-in point-light fog.
|
||||
("lampFog", shadows.lamp_fog as f32),
|
||||
// MONKEY (post): weld registry and renderer defaults.
|
||||
("bloom", shadows.bloom as f32),
|
||||
("sunShafts", flag(shadows.sun_shafts)),
|
||||
@@ -3048,11 +3056,11 @@ mod tests {
|
||||
assert_eq!(d[name], want, "{name}: registered default left the knob");
|
||||
}
|
||||
// …and the census is the VideoConfig-backed row set. A name here that nothing registers
|
||||
// would weld against a row the client does not have; the length is the other half — 37
|
||||
// VideoConfig rows, 37 welds. MONKEY (wind): `foliageWind` is a world resource bridge.
|
||||
// would weld against a row the client does not have; the length is the other half — 38
|
||||
// VideoConfig rows, 38 welds (MONKEY lampfog: + lampFog). MONKEY (wind): `foliageWind` is a world resource bridge.
|
||||
let welded: std::collections::BTreeSet<&str> = lighting.iter().map(|(n, _)| *n).collect();
|
||||
// MONKEY (volumetric fog): the atmospheric tier joins the default-consumer weld.
|
||||
assert_eq!(welded.len(), 37, "the lighting lane welds 37 distinct rows");
|
||||
assert_eq!(welded.len(), 38, "the lighting lane welds 38 distinct rows");
|
||||
for name in &welded {
|
||||
assert!(
|
||||
REGISTERED.iter().any(|r| r.name == *name),
|
||||
|
||||
@@ -2091,6 +2091,67 @@ fn ambient_occlusion_dropdown_is_localised_and_live() {
|
||||
}
|
||||
}
|
||||
|
||||
// MONKEY (lampfog): the end-of-page quality row uses the same numeric tier contract as the pass.
|
||||
#[test]
|
||||
fn lamp_fog_dropdown_is_localised_and_live() {
|
||||
let xml = include_str!("../../assets/ui/OptionsFrame.xml");
|
||||
let strings = &xml[xml.find("BENILLA_ADVGFX_STRINGS = {").unwrap()
|
||||
..xml.find("OPTIONS_PAGE_ROWS = {").unwrap()];
|
||||
let row = xml.split("<Frame name=\"$parentRowLampFog\"").nth(1).unwrap();
|
||||
let on_load = row.split("<OnLoad>").nth(1).unwrap().split("</OnLoad>").next().unwrap();
|
||||
for (locale, title, labels) in [
|
||||
("enUS", "Lamp Fog", ["Off", "Low", "High"]),
|
||||
(
|
||||
"ruRU",
|
||||
"Свечение фонарей в тумане",
|
||||
["Выкл", "Низкое", "Высокое"],
|
||||
),
|
||||
] {
|
||||
let mut s = audio_harness();
|
||||
s.run(&format!("function GetLocale() return '{locale}' end"))
|
||||
.unwrap();
|
||||
s.run(strings).unwrap();
|
||||
s.run(
|
||||
r#"
|
||||
self = {}
|
||||
function OptionsRow_OnLoad(row, cvar, title, tip)
|
||||
row.cvar, row.title, row.tip = cvar, title, tip
|
||||
end
|
||||
function OptionsDropdownRow_Setup(row, choices) row.choices = choices end
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
s.run(on_load).unwrap();
|
||||
assert_eq!(s.eval::<String>("return self.title").unwrap(), title);
|
||||
assert!(s
|
||||
.eval::<bool>(
|
||||
"return getglobal(self.tip) == BENILLA_ADVGFX.tips.LAMP_FOG and string.len(getglobal(self.tip)) > 80",
|
||||
)
|
||||
.unwrap());
|
||||
// `lampFog` registers at Off, so seed away from the first choice before asserting that
|
||||
// every selection produces the expected live write (the CVar layer suppresses no-ops).
|
||||
s.run("SetCVar(self.cvar, '2')").unwrap();
|
||||
let _ = s.take_cvar_changes();
|
||||
for (tier, label) in labels.iter().enumerate() {
|
||||
assert_eq!(
|
||||
s.eval::<String>(&format!("return self.choices[{}].text", tier + 1))
|
||||
.unwrap(),
|
||||
*label
|
||||
);
|
||||
let _ = s.take_cvar_changes();
|
||||
s.run(&format!(
|
||||
"SetCVar(self.cvar, self.choices[{}].value)",
|
||||
tier + 1
|
||||
))
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
s.take_cvar_changes(),
|
||||
vec![("lampFog".to_string(), tier.to_string())]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn water_quality_writes_numeric_tiers_with_localised_labels() {
|
||||
benilla_formats::wow_data_or_skip!();
|
||||
@@ -2103,8 +2164,8 @@ fn water_quality_writes_numeric_tiers_with_localised_labels() {
|
||||
let mut s = harness_on(s);
|
||||
s.run("ShowUIPanel(BenillaOptionsFrame) BenillaOptionsFrameCategoryListRowAdvancedGraphics:Click()").unwrap();
|
||||
// MONKEY (volumetric fog): account for the atmosphere row after water.
|
||||
// MONKEY (integration): all programme rows (sky, post, dither, fog, wet, wind, ao) counted.
|
||||
assert_eq!(s.eval::<usize>("return table.getn(OPTIONS_PAGE_ROWS.AdvancedGraphics)").unwrap(), 28);
|
||||
// MONKEY (integration): all programme rows (sky, post, dither, fog, wet, wind, ao, lamp fog) counted.
|
||||
assert_eq!(s.eval::<usize>("return table.getn(OPTIONS_PAGE_ROWS.AdvancedGraphics)").unwrap(), 29);
|
||||
assert_eq!(s.eval::<String>("return OPTIONS_PAGE_ROWS.AdvancedGraphics[2]").unwrap(), "RowWaterQuality");
|
||||
assert_eq!(s.eval::<String>("return OPTIONS_PAGE_ROWS.AdvancedGraphics[21]").unwrap(), "RowLavaGlow");
|
||||
assert_eq!(s.eval::<String>(&format!("return {ADVGFX}RowWaterQualityDropdownText:GetText()")).unwrap(), labels[1]);
|
||||
@@ -3041,6 +3102,8 @@ fn every_row_tooltip_key_resolves_in_the_real_global_strings() {
|
||||
("BENILLA_TOOLTIP_SKY_QUALITY", "AdvancedGraphicsRowSkyQuality"),
|
||||
// MONKEY (ao): the contact-shadow row's translated tooltip.
|
||||
("BENILLA_TOOLTIP_AMBIENT_OCCLUSION", "AdvancedGraphicsRowAmbientOcclusion"),
|
||||
// MONKEY (lampfog): the quality row owns a translated tooltip too.
|
||||
("BENILLA_TOOLTIP_LAMP_FOG", "AdvancedGraphicsRowLampFog"),
|
||||
("BENILLA_TOOLTIP_WATER_QUALITY", "AdvancedGraphicsRowWaterQuality"),
|
||||
("BENILLA_TOOLTIP_LAVA_GLOW", "AdvancedGraphicsRowLavaGlow"),
|
||||
("BENILLA_TOOLTIP_RENDER_SCALE", "GraphicsRowRenderScale"),
|
||||
|
||||
@@ -232,6 +232,8 @@ pub(crate) struct VideoConfig {
|
||||
pub(crate) sky_quality: u8,
|
||||
// MONKEY (ao): screen-space contact shadows: 0 Off, 1 Low, 2 High.
|
||||
pub(crate) ambient_occlusion: u8,
|
||||
// MONKEY (lampfog): point-light fog tier: 0 Off, 1 nearest 16, 2 nearest 32.
|
||||
pub(crate) lamp_fog: u8,
|
||||
/// Brightness of lava lighting its surroundings, 0..4; 0 disables the glow.
|
||||
/// Published to `benilla_world::lighting::LavaLightGain` by `dynamic_interior::bridge`.
|
||||
pub(crate) lava_light_gain: f32,
|
||||
@@ -497,6 +499,8 @@ impl Default for VideoConfig {
|
||||
sky_quality: 0,
|
||||
// MONKEY (ao): opt-in; the future Graphics preset sets High = 2.
|
||||
ambient_occlusion: 0,
|
||||
// MONKEY (lampfog): opt-in; zero is exactly the pre-lane render.
|
||||
lamp_fog: 0,
|
||||
lava_light_gain: 1.0,
|
||||
fire_flicker: 1.0,
|
||||
display: if windowed_env() {
|
||||
@@ -557,7 +561,8 @@ pub(crate) fn on_cvar(
|
||||
// Display mode (1627) — the reference's own polarity: `1` is WINDOWED (the row is
|
||||
// "Windowed Mode"). `apply_window_mode` pushes it to the window when this moves.
|
||||
"gxwindow" => cfg.display = display_from_flag(v),
|
||||
// ── MONKEY (lighting): the dynamic light + shadow system's 33 rows ────────────────────
|
||||
// ── MONKEY (lighting): the dynamic light + shadow system's 34 rows ────────────────────
|
||||
// MONKEY (lampfog): lampFog is one of these live VideoConfig rows too.
|
||||
// They live in THIS observer, and not in one of their own beside `shadow_core` /
|
||||
// `dynamic_interior`, because of the law the arm above states: *each arm writes only its
|
||||
// own resource*. Every one of these knobs IS a field of [`VideoConfig`] — the lanes read
|
||||
@@ -573,7 +578,7 @@ pub(crate) fn on_cvar(
|
||||
//
|
||||
// Clamps are each row's own, stated beside it, exactly as for the reference rows above;
|
||||
// the `ours(...)` entries in `cvars::REGISTERED` carry the matching defaults, and
|
||||
// MONKEY (volumetric fog): the atmospheric tier brings the defaults weld to 33 pairs.
|
||||
// MONKEY (lampfog): the two atmospheric tiers bring the defaults weld to 34 pairs.
|
||||
"waterquality" => cfg.water_quality = v.clamp(0.0, 2.0) as u8,
|
||||
// MONKEY (volumetric fog): constrain UI/console writes to supported tiers.
|
||||
"volumetricfog" => cfg.volumetric_fog = v.clamp(0.0, 2.0) as u8,
|
||||
@@ -586,6 +591,8 @@ pub(crate) fn on_cvar(
|
||||
"skyquality" => cfg.sky_quality = v.clamp(0.0, 2.0) as u8,
|
||||
// MONKEY (ao): constrain UI/console writes to supported tiers.
|
||||
"ambientocclusion" => cfg.ambient_occlusion = v.clamp(0.0, 2.0) as u8,
|
||||
// MONKEY (lampfog): 0 Off / 1 nearest 16 / 2 nearest 32.
|
||||
"lampfog" => cfg.lamp_fog = v.clamp(0.0, 2.0) as u8,
|
||||
"lavalightgain" => cfg.lava_light_gain = v.clamp(0.0, 4.0),
|
||||
"worldshadows" => cfg.world_shadows = ev.flag(),
|
||||
"charactershadows" => cfg.character_shadows = ev.flag(),
|
||||
|
||||
@@ -8,25 +8,27 @@ use crate::{
|
||||
world_shadow::WorldLane,
|
||||
};
|
||||
use benilla_world::{
|
||||
lighting::{WorldTime, WowLighting},
|
||||
lighting::{ResolvedPointLight, ResolvedPointLights, WorldTime, WowLighting},
|
||||
static_gx::StaticGx,
|
||||
view::WorldCamera,
|
||||
weather::WeatherState,
|
||||
wmo_portal::CameraInteriorClaim,
|
||||
};
|
||||
use bevy::{
|
||||
camera::primitives::{Frustum, Sphere as CullSphere},
|
||||
core_pipeline::{
|
||||
core_3d::graph::{Core3d, Node3d},
|
||||
FullscreenShader,
|
||||
core_3d::graph::{Core3d, Node3d},
|
||||
},
|
||||
ecs::query::QueryItem,
|
||||
light::VolumetricLight,
|
||||
pbr::{
|
||||
GpuLights, LightMeta, ViewLightsUniformOffset, ViewShadowBindings, MAX_CASCADES_PER_LIGHT,
|
||||
MAX_DIRECTIONAL_LIGHTS,
|
||||
GpuLights, LightMeta, MAX_CASCADES_PER_LIGHT, MAX_DIRECTIONAL_LIGHTS,
|
||||
ViewLightsUniformOffset, ViewShadowBindings,
|
||||
},
|
||||
prelude::*,
|
||||
render::{
|
||||
Render, RenderApp, RenderStartup, RenderSystems,
|
||||
diagnostic::RecordDiagnostics,
|
||||
extract_component::{ExtractComponent, ExtractComponentPlugin},
|
||||
render_graph::{
|
||||
@@ -35,14 +37,38 @@ use bevy::{
|
||||
render_resource::{binding_types::*, *},
|
||||
renderer::{RenderContext, RenderDevice, RenderQueue},
|
||||
view::{Msaa, ViewDepthTexture, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms},
|
||||
Render, RenderApp, RenderStartup, RenderSystems,
|
||||
},
|
||||
shader::ShaderDefVal,
|
||||
};
|
||||
|
||||
pub(crate) struct VolumetricFogPlugin;
|
||||
#[derive(Resource)]
|
||||
struct FogOverride(Option<u8>, f32);
|
||||
struct FogOverride {
|
||||
fog: Option<u8>,
|
||||
lamp: Option<u8>,
|
||||
shaft_gain: f32,
|
||||
}
|
||||
|
||||
const MAX_FOG_LAMPS: usize = 32;
|
||||
const LOW_FOG_LAMPS: usize = 16;
|
||||
const FOG_LAMP_RADIUS: f32 = 100.0;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct FogLamps {
|
||||
positions: [Vec4; MAX_FOG_LAMPS],
|
||||
colors: [Vec4; MAX_FOG_LAMPS],
|
||||
count: usize,
|
||||
}
|
||||
|
||||
impl Default for FogLamps {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
positions: [Vec4::ZERO; MAX_FOG_LAMPS],
|
||||
colors: [Vec4::ZERO; MAX_FOG_LAMPS],
|
||||
count: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Clone, Copy, ExtractComponent, ShaderType)]
|
||||
struct FogView {
|
||||
@@ -57,6 +83,11 @@ struct FogView {
|
||||
mf_fog1: Vec4,
|
||||
mf_fog2: Vec4,
|
||||
mf_fog3: Vec4,
|
||||
// MONKEY (lampfog): count + strength, followed by 32 resolved point-table rows. Colour already
|
||||
// includes intensity, flame flicker and (for synthesised fire) fireLightGain.
|
||||
lamp_meta: Vec4,
|
||||
lamp_positions: [Vec4; MAX_FOG_LAMPS],
|
||||
lamp_colors: [Vec4; MAX_FOG_LAMPS],
|
||||
}
|
||||
|
||||
impl Plugin for VolumetricFogPlugin {
|
||||
@@ -65,6 +96,11 @@ impl Plugin for VolumetricFogPlugin {
|
||||
.ok()
|
||||
.and_then(|v| v.parse::<u8>().ok())
|
||||
.filter(|v| *v <= 2);
|
||||
// MONKEY (lampfog): session-only capture override; the persistent setting is `lampFog`.
|
||||
let lamp = std::env::var("WOW_LAMPFOG")
|
||||
.ok()
|
||||
.and_then(|v| v.parse::<u8>().ok())
|
||||
.filter(|v| *v <= 2);
|
||||
// Dev capture instrument: isolate shafts without changing surface shadows or haze.
|
||||
let shaft_gain = if cfg!(feature = "dev") && std::env::var_os("WOW_CAPTURE").is_some() {
|
||||
std::env::var("WOW_VOLFOG_SHAFT_GAIN")
|
||||
@@ -76,10 +112,14 @@ impl Plugin for VolumetricFogPlugin {
|
||||
} else {
|
||||
2.0
|
||||
};
|
||||
app.insert_resource(FogOverride(value, shaft_gain))
|
||||
.add_plugins(ExtractComponentPlugin::<FogView>::default())
|
||||
.add_systems(Last, refresh_streamed_shadows.before(ShadowSet::Lanes))
|
||||
.add_systems(Last, update_fog.after(ShadowSet::Lanes));
|
||||
app.insert_resource(FogOverride {
|
||||
fog: value,
|
||||
lamp,
|
||||
shaft_gain,
|
||||
})
|
||||
.add_plugins(ExtractComponentPlugin::<FogView>::default())
|
||||
.add_systems(Last, refresh_streamed_shadows.before(ShadowSet::Lanes))
|
||||
.add_systems(Last, update_fog.after(ShadowSet::Lanes));
|
||||
// Keep headless policy tests independent of the renderer.
|
||||
if !app.is_plugin_added::<AssetPlugin>() {
|
||||
return;
|
||||
@@ -117,7 +157,7 @@ fn refresh_streamed_shadows(
|
||||
mut lane: ResMut<WorldLane>,
|
||||
mut seen: Local<Option<u64>>,
|
||||
) {
|
||||
if override_value.0.unwrap_or(video.volumetric_fog) == 0 || !video.world_shadows {
|
||||
if override_value.fog.unwrap_or(video.volumetric_fog) == 0 || !video.world_shadows {
|
||||
*seen = None;
|
||||
return;
|
||||
}
|
||||
@@ -135,6 +175,71 @@ fn density(minute: f32, weather: f32, indoors: bool) -> f32 {
|
||||
0.004 * (1.0 + dawn * 1.5 + weather.clamp(0.0, 1.0) * 2.0) * if indoors { 0.18 } else { 1.0 }
|
||||
}
|
||||
|
||||
// MONKEY (lampfog): independent near-field haze floor. Full daylight is exactly zero; weather
|
||||
// only strengthens an already-night-time halo, and interiors keep a restrained local version.
|
||||
fn lamp_haze_strength(sun_height: f32, weather: f32, indoors: bool) -> f32 {
|
||||
let daylight = ((sun_height + 0.02) / 0.15).clamp(0.0, 1.0);
|
||||
(1.0 - daylight) * (0.8 + 1.2 * weather.clamp(0.0, 1.0)) * if indoors { 0.55 } else { 1.0 }
|
||||
}
|
||||
|
||||
// MONKEY (lampfog): pick from the exact CPU mirror of the packed point table. A sphere test keeps
|
||||
// off-screen fixtures whose halo can still cross the view. Both exterior and interior rows are
|
||||
// eligible: a street camera can see facade/room fixtures, and scene depth—not a camera-lane guess—
|
||||
// cuts the view segment. This pass is deliberately unshadowed, so the remaining leakage is the
|
||||
// documented tradeoff until point-light cube shadowing exists.
|
||||
fn select_fog_lamps(
|
||||
points: &[ResolvedPointLight],
|
||||
camera_position: Vec3,
|
||||
frustum: Option<&Frustum>,
|
||||
limit: usize,
|
||||
) -> FogLamps {
|
||||
let mut candidates: Vec<(f32, ResolvedPointLight)> = points
|
||||
.iter()
|
||||
.copied()
|
||||
.filter_map(|point| {
|
||||
let point_is_indoors = point.lane > 0.5;
|
||||
if point.color.max_element() <= 0.0001 {
|
||||
return None;
|
||||
}
|
||||
let distance_squared = point.position.distance_squared(camera_position);
|
||||
if distance_squared > FOG_LAMP_RADIUS * FOG_LAMP_RADIUS {
|
||||
return None;
|
||||
}
|
||||
let reach = if point_is_indoors {
|
||||
point.lane
|
||||
} else {
|
||||
point.range
|
||||
}
|
||||
.clamp(1.0, 48.0);
|
||||
let visible = frustum.is_none_or(|frustum| {
|
||||
frustum.intersects_sphere(
|
||||
&CullSphere {
|
||||
center: point.position.into(),
|
||||
radius: reach,
|
||||
},
|
||||
false,
|
||||
)
|
||||
});
|
||||
visible.then_some((distance_squared, point))
|
||||
})
|
||||
.collect();
|
||||
candidates.sort_by(|left, right| left.0.total_cmp(&right.0));
|
||||
|
||||
let mut selected = FogLamps::default();
|
||||
for (_, point) in candidates.into_iter().take(limit.min(MAX_FOG_LAMPS)) {
|
||||
let reach = if point.lane > 0.5 {
|
||||
point.lane
|
||||
} else {
|
||||
point.range
|
||||
}
|
||||
.clamp(1.0, 48.0);
|
||||
selected.positions[selected.count] = point.position.extend(reach);
|
||||
selected.colors[selected.count] = point.color.extend(0.0);
|
||||
selected.count += 1;
|
||||
}
|
||||
selected
|
||||
}
|
||||
|
||||
fn update_fog(
|
||||
mut commands: Commands,
|
||||
video: Res<VideoConfig>,
|
||||
@@ -143,23 +248,36 @@ fn update_fog(
|
||||
clock: Res<WorldTime>,
|
||||
weather: Res<WeatherState>,
|
||||
interior: Res<CameraInteriorClaim>,
|
||||
// MONKEY (lampfog): the resolved point-table view published by global_light's packer.
|
||||
point_lights: Res<ResolvedPointLights>,
|
||||
// MONKEY (fog): the Modern fog rows (Option: absent in unit tests).
|
||||
monkey: Option<Res<benilla_world::lighting::MonkeyFrame>>,
|
||||
mut cameras: Query<(Entity, &Camera, &mut Camera3d), With<WorldCamera>>,
|
||||
mut cameras: Query<
|
||||
(
|
||||
Entity,
|
||||
&Camera,
|
||||
&mut Camera3d,
|
||||
Option<&GlobalTransform>,
|
||||
Option<&Frustum>,
|
||||
),
|
||||
With<WorldCamera>,
|
||||
>,
|
||||
suns: Query<(Entity, &DirectionalLight), With<ShadowSun>>,
|
||||
) {
|
||||
let tier = override_value.0.unwrap_or(video.volumetric_fog).min(2);
|
||||
let enabled = tier != 0 && cameras.iter().any(|(_, c, _)| c.is_active);
|
||||
let tier = override_value.fog.unwrap_or(video.volumetric_fog).min(2);
|
||||
let lamp_tier = override_value.lamp.unwrap_or(video.lamp_fog).min(2);
|
||||
let enabled = (tier != 0 || lamp_tier != 0)
|
||||
&& cameras.iter().any(|(_, camera, _, _, _)| camera.is_active);
|
||||
for (entity, sun) in &suns {
|
||||
// Only a tag for selecting this light in our shader. No Bevy VolumetricFog
|
||||
// camera/volume is created, and the rig's lux and surface lighting stay intact.
|
||||
if enabled && sun.shadows_enabled {
|
||||
if tier != 0 && enabled && sun.shadows_enabled {
|
||||
commands.entity(entity).insert(VolumetricLight);
|
||||
} else {
|
||||
commands.entity(entity).remove::<VolumetricLight>();
|
||||
}
|
||||
}
|
||||
for (entity, camera, mut camera3d) in &mut cameras {
|
||||
for (entity, camera, mut camera3d, camera_transform, frustum) in &mut cameras {
|
||||
if !enabled || !camera.is_active {
|
||||
commands.entity(entity).remove::<FogView>();
|
||||
continue;
|
||||
@@ -167,6 +285,31 @@ fn update_fog(
|
||||
camera3d.depth_texture_usages.0 |= TextureUsages::TEXTURE_BINDING.bits();
|
||||
let sun = lighting.celestial_dir();
|
||||
let daylight = ((sun.y + 0.02) / 0.15).clamp(0.0, 1.0);
|
||||
let weather_amount = weather
|
||||
.sky_density
|
||||
.max(weather.effect_density)
|
||||
.clamp(0.0, 1.0);
|
||||
// Exact zero by day is both the intended look and the A/B invariant. At night a constant
|
||||
// floor makes nearby halos survive the zone haze's ten-yard dead zone; rain/fog raises it.
|
||||
let lamp_strength = if lamp_tier == 0 {
|
||||
0.0
|
||||
} else {
|
||||
lamp_haze_strength(sun.y, weather_amount, interior.0.is_some())
|
||||
};
|
||||
let lamps = if lamp_strength > 0.0 {
|
||||
select_fog_lamps(
|
||||
point_lights.as_slice(),
|
||||
camera_transform.map_or(Vec3::ZERO, GlobalTransform::translation),
|
||||
frustum,
|
||||
if lamp_tier == 1 {
|
||||
LOW_FOG_LAMPS
|
||||
} else {
|
||||
MAX_FOG_LAMPS
|
||||
},
|
||||
)
|
||||
} else {
|
||||
FogLamps::default()
|
||||
};
|
||||
let fog = Vec3::from_array(lighting.fog_color);
|
||||
// MONKEY (fog): rows 1-3 as packed for the light buffer.
|
||||
let rows = monkey.as_ref().map_or([[0.0; 4]; 16], |m| m.pack(0.0, 0.0));
|
||||
@@ -174,16 +317,26 @@ fn update_fog(
|
||||
mf_fog1: Vec4::from_array(rows[1]),
|
||||
mf_fog2: Vec4::from_array(rows[2]),
|
||||
mf_fog3: Vec4::from_array(rows[3]),
|
||||
lamp_meta: Vec4::new(lamps.count as f32, lamp_strength, 0.0, 0.0),
|
||||
lamp_positions: lamps.positions,
|
||||
lamp_colors: lamps.colors,
|
||||
color_density: fog.extend(
|
||||
density(
|
||||
clock.minute_f,
|
||||
weather.sky_density.max(weather.effect_density),
|
||||
interior.0.is_some(),
|
||||
) * if tier == 2 { 1.6 } else { 1.0 },
|
||||
density(clock.minute_f, weather_amount, interior.0.is_some())
|
||||
* if tier == 0 {
|
||||
0.0
|
||||
} else if tier == 2 {
|
||||
1.6
|
||||
} else {
|
||||
1.0
|
||||
},
|
||||
),
|
||||
sun_strength: Vec3::from_array(lighting.diffuse)
|
||||
.lerp(Vec3::ONE, 0.5)
|
||||
.extend(override_value.1 * daylight),
|
||||
.extend(if tier == 0 {
|
||||
0.0
|
||||
} else {
|
||||
override_value.shaft_gain * daylight
|
||||
}),
|
||||
direction_steps: sun.extend(if tier == 1 { 24.0 } else { 64.0 }),
|
||||
});
|
||||
}
|
||||
@@ -398,6 +551,10 @@ struct Fog {
|
||||
color_density: vec4<f32>, sun_strength: vec4<f32>, direction_steps: vec4<f32>,
|
||||
// MONKEY (fog): MonkeyFrame fog1..fog3.
|
||||
mf_fog1: vec4<f32>, mf_fog2: vec4<f32>, mf_fog3: vec4<f32>,
|
||||
// MONKEY (lampfog): x=count, y=night/weather strength; packed point-table rows follow.
|
||||
lamp_meta: vec4<f32>,
|
||||
lamp_positions: array<vec4<f32>, 32>,
|
||||
lamp_colors: array<vec4<f32>, 32>,
|
||||
}
|
||||
@group(0) @binding(6) var<uniform> fog: Fog;
|
||||
|
||||
@@ -425,6 +582,56 @@ fn visibility(light_index: u32, p: vec3<f32>) -> f32 {
|
||||
// Unknown outside caster coverage is not evidence of direct sunlight.
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// MONKEY (lampfog): a mild Henyey-Greenstein lobe. The inverse-square term is integrated exactly;
|
||||
// evaluating this slow angular term at the closest admitted point keeps the closed form intact.
|
||||
fn lamp_phase(cosine: f32) -> f32 {
|
||||
let anisotropy = 0.2;
|
||||
let denom = 1.0 + anisotropy * anisotropy - 2.0 * anisotropy * cosine;
|
||||
return 0.07957747 * (1.0 - anisotropy * anisotropy) / (denom * sqrt(denom));
|
||||
}
|
||||
|
||||
// MONKEY (lampfog): analytic line integral of a softened inverse-square source over the visible
|
||||
// camera-to-depth segment. It is intentionally UNSHADOWED; scene depth cuts the segment, but no
|
||||
// point-light cube map is sampled. Range is a smooth screen-space gate around the exact sphere
|
||||
// intersection. The exponential soft cap plus destination headroom keeps halos below 1.0 so a
|
||||
// later bloom pass cannot turn a stacked row of lamps into white slabs.
|
||||
fn lamp_halos(ray: vec3<f32>, distance: f32) -> vec3<f32> {
|
||||
var energy = vec3<f32>(0.0);
|
||||
let lamp_count = u32(fog.lamp_meta.x);
|
||||
for (var lamp_index = 0u; lamp_index < 32u; lamp_index += 1u) {
|
||||
if (lamp_index >= lamp_count) { break; }
|
||||
let position_range = fog.lamp_positions[lamp_index];
|
||||
let to_lamp = position_range.xyz - view.world_position;
|
||||
let projection = dot(to_lamp, ray);
|
||||
let perpendicular = to_lamp - ray * projection;
|
||||
let perpendicular_sq = dot(perpendicular, perpendicular);
|
||||
let range_sq = position_range.w * position_range.w;
|
||||
if (perpendicular_sq >= range_sq) { continue; }
|
||||
|
||||
let half_span = sqrt(max(range_sq - perpendicular_sq, 0.0));
|
||||
let segment_start = max(0.0, projection - half_span);
|
||||
let segment_end = min(distance, projection + half_span);
|
||||
if (segment_end <= segment_start) { continue; }
|
||||
|
||||
// Integral dt / (core^2 + perpendicular^2 + (t - projection)^2).
|
||||
let core_radius = 1.25;
|
||||
let height = sqrt(perpendicular_sq + core_radius * core_radius);
|
||||
let integral = (atan((segment_end - projection) / height)
|
||||
- atan((segment_start - projection) / height)) / height;
|
||||
let closest = clamp(projection, segment_start, segment_end);
|
||||
let sample_to_lamp = view.world_position + ray * closest - position_range.xyz;
|
||||
let light_path = sample_to_lamp / max(length(sample_to_lamp), 0.0001);
|
||||
let phase = lamp_phase(dot(light_path, -ray));
|
||||
let edge = 1.0 - smoothstep(position_range.w * 0.72, position_range.w,
|
||||
sqrt(perpendicular_sq));
|
||||
energy += max(fog.lamp_colors[lamp_index].rgb, vec3<f32>(0.0))
|
||||
* (integral * phase * edge);
|
||||
}
|
||||
let capped = min(energy * fog.lamp_meta.y * 0.9, vec3<f32>(0.75));
|
||||
return vec3<f32>(1.0) - exp(-capped);
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fragment(@builtin(position) pixel: vec4<f32>) -> @location(0) vec4<f32> {
|
||||
let xy = vec2<i32>(pixel.xy);
|
||||
@@ -434,45 +641,52 @@ fn fragment(@builtin(position) pixel: vec4<f32>) -> @location(0) vec4<f32> {
|
||||
let q = view.world_from_clip * vec4(uv * vec2(2.0, -2.0) + vec2(-1.0, 1.0), max(z, 0.000001), 1.0);
|
||||
let delta = q.xyz / q.w - view.world_position;
|
||||
let distance = min(length(delta), 150.0);
|
||||
if (distance <= 10.0) { return source; }
|
||||
if (distance <= 10.0 && fog.lamp_meta.x < 0.5) { return source; }
|
||||
let ray = normalize(delta);
|
||||
let optical_depth = fog.color_density.w * fog_path(distance);
|
||||
var opacity = 1.0 - exp(-optical_depth);
|
||||
var haze_rgb = fog.color_density.rgb;
|
||||
if (fog_hook::fog_is_modern(fog.mf_fog3)) {
|
||||
// MONKEY (fog): the sky (depth 0, clamped to 150 yd above) already carries its own
|
||||
// horizon fog, so it takes no distance haze; the haze colour is the shared fog colour.
|
||||
if (z <= 0.0) { opacity = 0.0; }
|
||||
haze_rgb = fog_hook::fog_modern_colour(fog.color_density.rgb, ray, length(delta),
|
||||
fog.mf_fog1, fog.mf_fog2, fog.mf_fog3);
|
||||
}
|
||||
let luma = vec3(0.2126, 0.7152, 0.0722);
|
||||
var haze = mix(source.rgb, haze_rgb, opacity);
|
||||
// A darker zone colour may change hue, never the pixel's luminance. Add
|
||||
// neutral headroom proportionally so saturated channels cannot clip dark.
|
||||
let missing = max(0.0, dot(source.rgb - haze, luma));
|
||||
let headroom = max(vec3(0.0), vec3(1.0) - haze);
|
||||
haze += headroom * clamp(missing / max(dot(headroom, luma), 0.00001), 0.0, 1.0);
|
||||
if (fog.sun_strength.w > 0.0) {
|
||||
let g = 0.65;
|
||||
let cosine = dot(ray, fog.direction_steps.xyz);
|
||||
let denom = 1.0 + g*g - 2.0*g*cosine;
|
||||
let phase = 0.07957747 * (1.0-g*g) / (denom * sqrt(denom));
|
||||
let count = u32(fog.direction_steps.w);
|
||||
let step_size = (distance - 10.0) / f32(count);
|
||||
var scatter = 0.0;
|
||||
for (var light_index = 0u; light_index < lights.n_directional_lights; light_index += 1u) {
|
||||
if ((lights.directional_lights[light_index].flags & 3u) != 3u) { continue; }
|
||||
for (var step = 0u; step < count; step += 1u) {
|
||||
// Fixed midpoint samples: no temporal noise in this gamma lane.
|
||||
let t = 10.0 + (f32(step) + 0.5) * step_size;
|
||||
let density = fog.color_density.w * smoothstep(10.0, 30.0, t);
|
||||
scatter += visibility(light_index, view.world_position + ray*t)
|
||||
* exp(-fog.color_density.w * fog_path(t)) * density * step_size;
|
||||
}
|
||||
var haze = source.rgb;
|
||||
if (distance > 10.0) {
|
||||
let optical_depth = fog.color_density.w * fog_path(distance);
|
||||
var opacity = 1.0 - exp(-optical_depth);
|
||||
var haze_rgb = fog.color_density.rgb;
|
||||
if (fog_hook::fog_is_modern(fog.mf_fog3)) {
|
||||
// MONKEY (fog): the sky (depth 0, clamped to 150 yd above) already carries its own
|
||||
// horizon fog, so it takes no distance haze; the haze colour is the shared fog colour.
|
||||
if (z <= 0.0) { opacity = 0.0; }
|
||||
haze_rgb = fog_hook::fog_modern_colour(fog.color_density.rgb, ray, length(delta),
|
||||
fog.mf_fog1, fog.mf_fog2, fog.mf_fog3);
|
||||
}
|
||||
let shafts = fog.sun_strength.rgb * fog.sun_strength.w * phase * scatter;
|
||||
haze += max(vec3(0.0), vec3(1.0) - haze) * (vec3(1.0) - exp(-shafts));
|
||||
haze = mix(source.rgb, haze_rgb, opacity);
|
||||
// A darker zone colour may change hue, never the pixel's luminance. Add
|
||||
// neutral headroom proportionally so saturated channels cannot clip dark.
|
||||
let missing = max(0.0, dot(source.rgb - haze, luma));
|
||||
let headroom = max(vec3(0.0), vec3(1.0) - haze);
|
||||
haze += headroom * clamp(missing / max(dot(headroom, luma), 0.00001), 0.0, 1.0);
|
||||
if (fog.sun_strength.w > 0.0) {
|
||||
let g = 0.65;
|
||||
let cosine = dot(ray, fog.direction_steps.xyz);
|
||||
let denom = 1.0 + g*g - 2.0*g*cosine;
|
||||
let phase = 0.07957747 * (1.0-g*g) / (denom * sqrt(denom));
|
||||
let count = u32(fog.direction_steps.w);
|
||||
let step_size = (distance - 10.0) / f32(count);
|
||||
var scatter = 0.0;
|
||||
for (var light_index = 0u; light_index < lights.n_directional_lights; light_index += 1u) {
|
||||
if ((lights.directional_lights[light_index].flags & 3u) != 3u) { continue; }
|
||||
for (var step = 0u; step < count; step += 1u) {
|
||||
// Fixed midpoint samples: no temporal noise in this gamma lane.
|
||||
let t = 10.0 + (f32(step) + 0.5) * step_size;
|
||||
let density = fog.color_density.w * smoothstep(10.0, 30.0, t);
|
||||
scatter += visibility(light_index, view.world_position + ray*t)
|
||||
* exp(-fog.color_density.w * fog_path(t)) * density * step_size;
|
||||
}
|
||||
}
|
||||
let shafts = fog.sun_strength.rgb * fog.sun_strength.w * phase * scatter;
|
||||
haze += max(vec3(0.0), vec3(1.0) - haze) * (vec3(1.0) - exp(-shafts));
|
||||
}
|
||||
}
|
||||
if (fog.lamp_meta.x > 0.5) {
|
||||
let halo = lamp_halos(ray, distance);
|
||||
haze += max(vec3(0.0), vec3(1.0) - haze) * halo;
|
||||
}
|
||||
return vec4(haze, source.a);
|
||||
}
|
||||
@@ -487,12 +701,16 @@ mod tests {
|
||||
app.init_resource::<VideoConfig>()
|
||||
.init_resource::<StaticGx>()
|
||||
.init_resource::<WorldLane>()
|
||||
.insert_resource(FogOverride(Some(0), 2.0))
|
||||
.insert_resource(FogOverride {
|
||||
fog: Some(0),
|
||||
lamp: None,
|
||||
shaft_gain: 2.0,
|
||||
})
|
||||
.add_systems(Update, refresh_streamed_shadows);
|
||||
app.world_mut().clear_trackers();
|
||||
app.world_mut().run_schedule(Update);
|
||||
assert!(!app.world().resource_ref::<WorldLane>().is_changed());
|
||||
app.world_mut().resource_mut::<FogOverride>().0 = Some(1);
|
||||
app.world_mut().resource_mut::<FogOverride>().fog = Some(1);
|
||||
app.world_mut().run_schedule(Update);
|
||||
assert!(app.world().resource_ref::<WorldLane>().is_changed());
|
||||
app.world_mut().clear_trackers();
|
||||
@@ -509,6 +727,45 @@ mod tests {
|
||||
assert!(density(390.0, 1.0, true) < noon);
|
||||
}
|
||||
#[test]
|
||||
fn lamp_haze_is_exactly_off_by_day_and_weather_only_strengthens_night() {
|
||||
assert_eq!(lamp_haze_strength(1.0, 1.0, false), 0.0);
|
||||
let clear = lamp_haze_strength(-1.0, 0.0, false);
|
||||
assert!(lamp_haze_strength(-1.0, 1.0, false) > clear * 2.0);
|
||||
assert!(lamp_haze_strength(-1.0, 0.0, true) < clear);
|
||||
}
|
||||
#[test]
|
||||
fn lamp_picker_keeps_the_nearest_across_both_lanes_and_tier_limit() {
|
||||
let mut points: Vec<_> = (1..=40)
|
||||
.rev()
|
||||
.map(|distance| ResolvedPointLight {
|
||||
position: Vec3::X * distance as f32,
|
||||
range: 48.0,
|
||||
color: Vec3::new(0.8, 0.4, 0.1),
|
||||
lane: 0.0,
|
||||
})
|
||||
.collect();
|
||||
// Interior and exterior table rows compete by distance; black rows never consume a slot.
|
||||
points.push(ResolvedPointLight {
|
||||
position: Vec3::X * 0.25,
|
||||
range: 48.0,
|
||||
color: Vec3::ONE,
|
||||
lane: 8.0,
|
||||
});
|
||||
points.push(ResolvedPointLight {
|
||||
position: Vec3::X * 0.5,
|
||||
range: 48.0,
|
||||
color: Vec3::ZERO,
|
||||
lane: 0.0,
|
||||
});
|
||||
let low = select_fog_lamps(&points, Vec3::ZERO, None, LOW_FOG_LAMPS);
|
||||
assert_eq!(low.count, LOW_FOG_LAMPS);
|
||||
assert_eq!(low.positions[0], Vec4::new(0.25, 0.0, 0.0, 8.0));
|
||||
assert_eq!(low.positions[1].x, 1.0);
|
||||
assert_eq!(low.positions[LOW_FOG_LAMPS - 1].x, 15.0);
|
||||
assert_eq!(low.colors[0].truncate(), Vec3::ONE);
|
||||
assert_eq!(low.colors[1].truncate(), Vec3::new(0.8, 0.4, 0.1));
|
||||
}
|
||||
#[test]
|
||||
fn live_tiers_shadow_loss_inactive_camera_and_off() {
|
||||
let mut app = App::new();
|
||||
app.init_resource::<VideoConfig>()
|
||||
@@ -516,7 +773,12 @@ mod tests {
|
||||
.init_resource::<WorldTime>()
|
||||
.init_resource::<WeatherState>()
|
||||
.init_resource::<CameraInteriorClaim>()
|
||||
.insert_resource(FogOverride(None, 2.0))
|
||||
.init_resource::<ResolvedPointLights>()
|
||||
.insert_resource(FogOverride {
|
||||
fog: None,
|
||||
lamp: None,
|
||||
shaft_gain: 2.0,
|
||||
})
|
||||
.add_systems(Update, update_fog);
|
||||
let camera = app
|
||||
.world_mut()
|
||||
@@ -563,10 +825,10 @@ mod tests {
|
||||
app.update();
|
||||
assert!(app.world().get::<FogView>(camera).is_none());
|
||||
app.world_mut().get_mut::<Camera>(camera).unwrap().is_active = true;
|
||||
app.world_mut().resource_mut::<FogOverride>().0 = Some(0);
|
||||
app.world_mut().resource_mut::<FogOverride>().fog = Some(0);
|
||||
app.update();
|
||||
assert!(app.world().get::<FogView>(camera).is_none());
|
||||
app.world_mut().resource_mut::<FogOverride>().0 = None;
|
||||
app.world_mut().resource_mut::<FogOverride>().fog = None;
|
||||
app.update();
|
||||
assert!(app.world().get::<FogView>(camera).is_some());
|
||||
}
|
||||
|
||||
@@ -614,6 +614,29 @@ pub struct WorldPointLight {
|
||||
pub range: f32,
|
||||
}
|
||||
|
||||
/// MONKEY (lampfog): one live entry exactly as [`build_light_data`] resolved it for the shared
|
||||
/// point table. Colour is already `colour x intensity`, with the source's live gain and flame
|
||||
/// flicker folded in; position is Bevy world space and `lane` keeps the table's exterior/interior
|
||||
/// discriminator (an interior lane's value is also its effective reach).
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub struct ResolvedPointLight {
|
||||
pub position: Vec3,
|
||||
pub range: f32,
|
||||
pub color: Vec3,
|
||||
pub lane: f32,
|
||||
}
|
||||
|
||||
/// MONKEY (lampfog): read-only CPU view of the shared point table for post-process consumers.
|
||||
/// The GPU blob stays private and its pinned layout does not grow; readers get only the live rows.
|
||||
#[derive(Resource, Default)]
|
||||
pub struct ResolvedPointLights(Vec<ResolvedPointLight>);
|
||||
|
||||
impl ResolvedPointLights {
|
||||
pub fn as_slice(&self) -> &[ResolvedPointLight] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// The packed light for this frame, extracted for [`upload_light`].
|
||||
#[derive(Resource, Clone, Copy, ExtractResource)]
|
||||
struct WowLightData(LightStd430);
|
||||
@@ -636,6 +659,8 @@ pub struct SharedLightBuffer(pub Buffer);
|
||||
/// Registers the light pack, the probe publish, their extracts and the render-world uploads.
|
||||
pub(super) fn register(app: &mut App) {
|
||||
app.init_resource::<WowLightData>()
|
||||
// MONKEY (lampfog): CPU mirror of only the live point rows; not extracted or GPU-bound.
|
||||
.init_resource::<ResolvedPointLights>()
|
||||
// MONKEY (room gate): the per-fixture room claims, packed beside the point table.
|
||||
.init_resource::<RoomClaimTable>()
|
||||
.init_resource::<WorldShadowActive>()
|
||||
@@ -1308,6 +1333,8 @@ fn build_light_data(
|
||||
// The per-frame portal PVS, for the room term below ([`LightRooms`]).
|
||||
portals: Query<&crate::wmo_portal::WmoPortalInstance>,
|
||||
mut data: ResMut<WowLightData>,
|
||||
// MONKEY (lampfog): publish the already-resolved live rows without exposing the GPU blob.
|
||||
mut resolved_points: ResMut<ResolvedPointLights>,
|
||||
// MONKEY (room gate): packed in the same walk as the point table, so the two can never
|
||||
// disagree about which entry is which.
|
||||
mut claims: ResMut<RoomClaimTable>,
|
||||
@@ -1595,6 +1622,20 @@ fn build_light_data(
|
||||
fresh.points[2 * i + 1] = [rgb[0], rgb[1], rgb[2], *lane];
|
||||
claim.write(&mut claims.0[i * ROOM_CLAIM_STRIDE..][..ROOM_CLAIM_STRIDE]);
|
||||
}
|
||||
// MONKEY (lampfog): same order and values as the rows above. This is intentionally updated
|
||||
// after the 255-entry truncate, so a CPU post effect can never see a light the shaders cannot.
|
||||
let next_points: Vec<_> = pts
|
||||
.iter()
|
||||
.map(|(_, p, range, rgb, _, lane, _, _)| ResolvedPointLight {
|
||||
position: *p,
|
||||
range: *range,
|
||||
color: Vec3::from_array(*rgb),
|
||||
lane: *lane,
|
||||
})
|
||||
.collect();
|
||||
if resolved_points.0 != next_points {
|
||||
resolved_points.0 = next_points;
|
||||
}
|
||||
// Entries past the count are stale in the point table by design (the count row guards every
|
||||
// reader) — but the claim table is read at the SAME index, so a stale head there would gate a
|
||||
// live light with a dead building's identity. Clear the tail instead of trusting the count.
|
||||
@@ -2061,6 +2102,7 @@ mod tests {
|
||||
.init_resource::<crate::dev_state::DebugState>()
|
||||
.init_resource::<crate::view::ViewDistance>()
|
||||
.init_resource::<WowLightData>()
|
||||
.init_resource::<ResolvedPointLights>()
|
||||
// MONKEY (room gate): the packer writes the claim table in the same walk.
|
||||
.init_resource::<RoomClaimTable>()
|
||||
.init_resource::<Time>()
|
||||
@@ -2293,6 +2335,12 @@ mod tests {
|
||||
(synthetic[0] - 1.0).abs() < 1e-4,
|
||||
"the synthesised light takes the gain: {synthetic:?}"
|
||||
);
|
||||
// MONKEY (lampfog): the CPU accessor is the live table, not a second light calculation.
|
||||
let resolved = app.world().resource::<ResolvedPointLights>();
|
||||
assert_eq!(resolved.as_slice().len(), 2);
|
||||
assert_eq!(resolved.as_slice()[0].position, Vec3::X);
|
||||
assert!((resolved.as_slice()[0].color.x - authored[0]).abs() < 1e-4);
|
||||
assert!((resolved.as_slice()[1].color.x - synthetic[0]).abs() < 1e-4);
|
||||
|
||||
// Zero is the kill switch: the invented light commits black, the authored one is unmoved.
|
||||
app.world_mut().insert_resource(FireLightGain(0.0));
|
||||
@@ -2300,6 +2348,11 @@ mod tests {
|
||||
let data = app.world().resource::<WowLightData>().0;
|
||||
assert_eq!(data.points[3], [0.0, 0.0, 0.0, 0.0], "gain 0 = lane off");
|
||||
assert!((data.points[1][0] - 2.0).abs() < 1e-4, "authored unaffected");
|
||||
assert_eq!(
|
||||
app.world().resource::<ResolvedPointLights>().as_slice()[1].color,
|
||||
Vec3::ZERO,
|
||||
"the accessor carries the same live fire-gain fold"
|
||||
);
|
||||
}
|
||||
|
||||
/// GOLDEN — MONKEY (spellLightGain): a SPELL light takes `spellLightGain` **instead of**
|
||||
|
||||
@@ -42,7 +42,7 @@ pub use flicker::{flame_kind_for, flicker_seed, FlameFlicker, FlameKind, Flicker
|
||||
pub use global_light::{
|
||||
interior_reach, m2_light_reach, new_shared_light_buffer, DynamicInteriors, FireLightGain,
|
||||
ClaimFade, LightLane, LightLitRooms, LightReach, LightRooms, RoomClaimTable,
|
||||
SharedLightBuffer,
|
||||
ResolvedPointLight, ResolvedPointLights, SharedLightBuffer,
|
||||
ShadowDistance, ShadowFilterGaussian, ShadowProxyLight, SyntheticFireLight,
|
||||
WorldShadowActive,
|
||||
};
|
||||
|
||||
+48
-28
@@ -101,17 +101,17 @@
|
||||
|
||||
## Modules (top-level, per crate)
|
||||
|
||||
- **benilla-adt** (lib.rs): combined_alpha
|
||||
- **benilla-app** (lib.rs): addon_harness area area_poi area_trigger asset_churn aura_visual bindings blob_shadow bowstring camera_shake capture char_create char_select chat_bubble chr_classes cinematic combat_log combat_text console crash creature_anim cursor cvars death debug_panel dev doodad_events entities fishing_line footprints game_plugins game_tip glue glue_strings go_anim go_templates hover_log items loading_screen local_state login minimap name_persist nameplates names net npc_text opaque2d pending_item_ops perf pipe_warm player poi_marker portrait preflight probe_shield query_cache quest_markers raid_marks ranged_flex realm_select realmlist run_mode screen_fade screenshot shaders shutdown smart_rect sound spell swing_refusal target test_support text_filter text_reshape textinput transport tutorial ui_action ui_auction ui_aura ui_bank ui_battlefield ui_battlefield_positions ui_battlefield_score ui_bind_confirm ui_binder ui_cast ui_char ui_chat ui_craft ui_dialog_verbs ui_dressup ui_duel ui_follow ui_gamma ui_gm_ticket ui_gossip ui_guild ui_hide ui_honor ui_inspect ui_instance ui_item_text ui_items ui_layout ui_logout ui_loot ui_loot_roll ui_macro ui_mail ui_merchant ui_mirror ui_models ui_net ui_party ui_pass ui_pet ui_pet_book ui_pet_doll ui_pet_stats ui_petition ui_quest ui_quest_log ui_quest_share ui_reputation ui_saved ui_script ui_session ui_shapeshift ui_social ui_spellbook ui_stable ui_summon ui_tabard ui_talent ui_talent_wipe ui_taxi ui_text ui_tooltip ui_trade ui_tradeskill ui_trainer ui_unit ui_world_map video vplates weapon_trail world_backdrop world_state world_state_ui
|
||||
- **benilla-assets** (lib.rs): adt anim_rng blp column_grid coords gpu_blp m2 materials minimap_grid model spatial_cache terrain tex_filter trace wdt wmo world_assets
|
||||
- **benilla-formats** (lib.rs): anim_data area_poi area_sound area_table area_trigger auction_house bank_bag_slot_prices camera_shakes chain characters chat_channels chr_classes cinematics creature_families creature_sound creature_types creatures dbc death_thud durability elevators emit_timing emote_text emotes environmental_damage exhaustion factions footsteps game_tips gameobjects garble gm_ticket_category ground_effects install item_random_properties item_sounds item_visuals itembagfamily itemclass items itemsets itemsubclass languages light liquid loading_screen lock lock_type macro_icons maps material minimap_translate models npc_greeting page_text_material particles pet_stats quest_headers quest_info race_pvp_team race_sound ribbons server_messages sheathe skill_lines sound_entries sound_provider sound_water spell_focus spell_mechanic spell_visual spells stable_slot_prices stationery talents taxi taxi_nodes taxi_path terrain text_filter_lists tga transport_period transports unit_blood value_track vocal_ui_sounds wdl weapon_impact weapon_swing wmo_area world_map_area world_map_continent world_map_overlay world_state_ui zone_map
|
||||
- **benilla-m2** (lib.rs): error model skin tests track
|
||||
- **benilla-mpq** (lib.rs): crypto
|
||||
- **benilla-protocol** (lib.rs): auth events guid messages wire world
|
||||
- **benilla-srp** (lib.rs): vanilla_header
|
||||
- **benilla-ui** (lib.rs): bindings_xml civil framexml justify layout loader markup messages order script source strings toc widget
|
||||
- **benilla-visual** (lib.rs): relight
|
||||
- **benilla-world** (lib.rs): art_scope assets bgwin billboard boot build_id clouds clutter collision decal dev_state doodad_anim entity_shade exterior_cull ffx_glow final_pass frame_pace ground_fx instance_tint interact interior lighting liquid log_ring mac_quit map_proj mat_anim_table mesh_tag model_fade model_forms model_render modkeys particles ribbons ride_frame rig_anim rig_palette rig_rider schedule shaders sky sky_order skybox static_gx static_merge straddle sun surface terrain_stream thread_qos view vis_chain water_fx wdl weather wmo_portal world_census world_map world_plugins world_point world_unit worldview zfill
|
||||
- **benilla-adt** (lib.rs): combined_alpha
|
||||
- **benilla-app** (lib.rs): addon_harness area area_poi area_trigger asset_churn aura_visual bindings blob_shadow bowstring camera_shake capture char_create char_select character_shadow chat_bubble chr_classes cinematic combat_log combat_text console crash creature_anim cursor cvars death debug_panel dev doodad_events dynamic_interior entities fishing_line footprints game_plugins game_tip glue glue_strings go_anim go_templates hover_log items loading_screen local_state login minimap monkey_gfx name_persist nameplates names net npc_text opaque2d pending_item_ops perf pipe_warm player poi_marker portrait preflight probe_shield query_cache quest_markers raid_marks ranged_flex realm_select realmlist run_mode screen_fade screenshot shaders shadow_core shutdown smart_rect sound spell swing_refusal target test_support text_filter text_reshape textinput torch_shadow transport tutorial ui_action ui_auction ui_aura ui_bank ui_battlefield ui_battlefield_positions ui_battlefield_score ui_bind_confirm ui_binder ui_cast ui_char ui_chat ui_craft ui_dialog_verbs ui_dressup ui_duel ui_follow ui_gamma ui_gm_ticket ui_gossip ui_guild ui_hide ui_honor ui_inspect ui_instance ui_item_text ui_items ui_layout ui_logout ui_loot ui_loot_roll ui_macro ui_mail ui_merchant ui_mirror ui_models ui_net ui_party ui_pass ui_pet ui_pet_book ui_pet_doll ui_pet_stats ui_petition ui_quest ui_quest_log ui_quest_share ui_reputation ui_saved ui_script ui_session ui_shapeshift ui_social ui_spellbook ui_stable ui_summon ui_tabard ui_talent ui_talent_wipe ui_taxi ui_text ui_tooltip ui_trade ui_tradeskill ui_trainer ui_unit ui_world_map video volumetric_fog vplates weapon_trail world_backdrop world_shadow world_state world_state_ui
|
||||
- **benilla-assets** (lib.rs): adt anim_rng blp column_grid coords gpu_blp m2 materials minimap_grid model spatial_cache terrain tex_filter trace water_depth wdt wmo world_assets
|
||||
- **benilla-formats** (lib.rs): anim_data area_poi area_sound area_table area_trigger auction_house bank_bag_slot_prices camera_shakes chain characters chat_channels chr_classes cinematics creature_families creature_sound creature_types creatures dbc death_thud durability elevators emit_timing emote_text emotes environmental_damage exhaustion factions fire_light footsteps game_tips gameobjects garble gm_ticket_category ground_effects install item_random_properties item_sounds item_visuals itembagfamily itemclass items itemsets itemsubclass languages light liquid loading_screen lock lock_type macro_icons maps material minimap_translate models npc_greeting page_text_material particles pet_stats quest_headers quest_info race_pvp_team race_sound ribbons room_claim server_messages sheathe skill_lines sound_entries sound_provider sound_water spell_focus spell_mechanic spell_visual spells stable_slot_prices stationery talents taxi taxi_nodes taxi_path terrain text_filter_lists tga transport_period transports unit_blood value_track vocal_ui_sounds wdl weapon_impact weapon_swing wmo_area world_map_area world_map_continent world_map_overlay world_state_ui zone_map
|
||||
- **benilla-m2** (lib.rs): error model skin tests track
|
||||
- **benilla-mpq** (lib.rs): crypto
|
||||
- **benilla-protocol** (lib.rs): auth events guid messages wire world
|
||||
- **benilla-srp** (lib.rs): vanilla_header
|
||||
- **benilla-ui** (lib.rs): bindings_xml civil framexml justify layout loader markup messages order script source strings toc widget
|
||||
- **benilla-visual** (lib.rs): relight
|
||||
- **benilla-world** (lib.rs): art_scope assets bgwin billboard boot build_id clouds clutter collision decal dev_state doodad_anim entity_shade exterior_cull ffx_glow final_pass frame_pace ground_fx instance_tint interact interior lighting liquid log_ring mac_quit map_proj mat_anim_table mesh_tag model_fade model_forms model_render modkeys particles ribbons ride_frame rig_anim rig_palette rig_rider schedule shaders sky sky_order skybox static_gx static_merge straddle sun surface terrain_stream thread_qos view vis_chain water_fx wdl weather wmo_portal world_census world_map world_plugins world_point world_unit worldview zfill
|
||||
|
||||
## CLI binaries
|
||||
|
||||
@@ -123,22 +123,29 @@
|
||||
|
||||
## WGSL shaders (`crates/*/src/shaders/`, embedded)
|
||||
|
||||
- `embedded://benilla_app/shaders/shadow_caster.wgsl`
|
||||
- `embedded://benilla_app/shaders/shadow_caster_cutout_prepass.wgsl`
|
||||
- `embedded://benilla_app/shaders/ui_add.wgsl`
|
||||
- `embedded://benilla_app/shaders/ui_gamma.wgsl`
|
||||
- `embedded://benilla_app/shaders/ui_node_gamma.wgsl`
|
||||
- `embedded://benilla_app/shaders/ui_quad.wgsl`
|
||||
- `embedded://benilla_app/shaders/ui_slice_gamma.wgsl`
|
||||
- `embedded://benilla_assets/shaders/enhanced_water.wgsl`
|
||||
- `embedded://benilla_assets/shaders/fog_hook.wgsl`
|
||||
- `embedded://benilla_assets/shaders/liquid.wgsl`
|
||||
- `embedded://benilla_assets/shaders/monkey_frame.wgsl`
|
||||
- `embedded://benilla_assets/shaders/shadow_hook.wgsl`
|
||||
- `embedded://benilla_assets/shaders/terrain.wgsl`
|
||||
- `embedded://benilla_assets/shaders/wdl.wgsl`
|
||||
- `embedded://benilla_assets/shaders/wow_model.wgsl`
|
||||
- `embedded://benilla_world/shaders/celestial.wgsl`
|
||||
- `embedded://benilla_world/shaders/cloud.wgsl`
|
||||
- `embedded://benilla_world/shaders/ffx_glow.wgsl`
|
||||
- `embedded://benilla_world/shaders/sky_vertex.wgsl`
|
||||
- `embedded://benilla_world/shaders/sky.wgsl`
|
||||
- `embedded://benilla_world/shaders/sky_vertex.wgsl`
|
||||
- `embedded://benilla_world/shaders/star.wgsl`
|
||||
- `embedded://benilla_world/shaders/static_gx.wgsl`
|
||||
- `embedded://benilla_world/shaders/torch_depth.wgsl`
|
||||
- `embedded://benilla_world/shaders/wow_effect.wgsl`
|
||||
|
||||
## Instruments — `$WOW_*` switches (env-var read sites)
|
||||
@@ -152,6 +159,7 @@
|
||||
- `WOW_ADDON_PROBE_TRACE` — benilla-protocol/examples/addon_chat_probe.rs
|
||||
- `WOW_ALLOW_ACCOUNT` — benilla-app/src/login/mod.rs, benilla-app/src/run_mode.rs
|
||||
- `WOW_AMBIGUITY_DUMP` — benilla-app/src/game_plugins.rs
|
||||
- `WOW_AMBIGUITY_DUMP_POST` — benilla-app/src/game_plugins.rs
|
||||
- `WOW_ANIM_COST` — benilla-app/src/creature_anim/driver.rs
|
||||
- `WOW_ANIM_PARK_ALL` — benilla-app/src/creature_anim/lod.rs
|
||||
- `WOW_ANISO` — benilla-app/src/cvars.rs, benilla-assets/src/tex_filter.rs
|
||||
@@ -167,6 +175,7 @@
|
||||
- `WOW_AUDIT_WMO` — benilla-world/src/wmo_portal/audit/mod.rs
|
||||
- `WOW_BARE_DOODAD_COLLIDERS` — benilla-world/src/terrain_stream/collider.rs
|
||||
- `WOW_BG` — benilla-world/src/bgwin.rs
|
||||
- `WOW_BLEED` — benilla-world/src/lighting/daylight.rs
|
||||
- `WOW_BLOB_VIS` — benilla-world/src/terrain_stream/merge.rs
|
||||
- `WOW_BLOB_VIS_UID` — benilla-world/src/terrain_stream/merge.rs
|
||||
- `WOW_BOOTH_DUMP` — benilla-app/src/portrait/test_bake.rs
|
||||
@@ -174,13 +183,14 @@
|
||||
- `WOW_BUBBLE_TRACE` — benilla-app/src/chat_bubble.rs
|
||||
- `WOW_CAM_CHANGED` — benilla-app/src/perf/mod.rs
|
||||
- `WOW_CAM_DUMP` — benilla-app/src/player/camera.rs
|
||||
- `WOW_CAPTURE` — benilla-app/src/capture/mod.rs, benilla-app/src/capture/scenarios.rs, benilla-app/src/char_select/addons.rs, benilla-app/src/cvars.rs, benilla-app/src/lib.rs, benilla-app/src/local_state.rs, benilla-app/src/login/mod.rs, benilla-app/src/name_persist.rs, benilla-app/src/net.rs, benilla-app/src/player/camera_saved.rs, benilla-app/src/run_mode.rs, benilla-app/src/sound/mod.rs, benilla-app/src/ui_chat/settings.rs, benilla-app/src/ui_chat/tests.rs, benilla-app/src/ui_macro/tests.rs, benilla-app/src/ui_saved.rs, benilla-app/src/ui_script/addons.rs, benilla-app/src/ui_script/mod.rs, benilla-app/src/ui_script/world_entry_tests.rs, benilla-app/src/video.rs, benilla-world/src/bgwin.rs, benilla-world/src/clouds/mod.rs, benilla-world/src/dev_state.rs
|
||||
- `WOW_CAPTURE` — benilla-app/src/capture/mod.rs, benilla-app/src/capture/scenarios.rs, benilla-app/src/char_select/addons.rs, benilla-app/src/cvars.rs, benilla-app/src/lib.rs, benilla-app/src/local_state.rs, benilla-app/src/login/mod.rs, benilla-app/src/name_persist.rs, benilla-app/src/net.rs, benilla-app/src/player/camera_saved.rs, benilla-app/src/run_mode.rs, benilla-app/src/sound/mod.rs, benilla-app/src/ui_chat/settings.rs, benilla-app/src/ui_chat/tests.rs, benilla-app/src/ui_macro/tests.rs, benilla-app/src/ui_saved.rs, benilla-app/src/ui_script/addons.rs, benilla-app/src/ui_script/mod.rs, benilla-app/src/ui_script/world_entry_tests.rs, benilla-app/src/video.rs, benilla-app/src/volumetric_fog.rs, benilla-world/src/bgwin.rs, benilla-world/src/clouds/mod.rs, benilla-world/src/dev_state.rs, benilla-world/src/liquid/surface.rs
|
||||
- `WOW_CAPTURE_AGE` — benilla-app/src/capture/mod.rs
|
||||
- `WOW_CAPTURE_DEADLINE` — benilla-app/src/capture/mod.rs
|
||||
- `WOW_CAPTURE_OUT` — benilla-app/src/capture/mod.rs
|
||||
- `WOW_CAPTURE_REHOVER` — benilla-app/src/ui_script/mod.rs
|
||||
- `WOW_CAPTURE_STABLE` — benilla-app/src/capture/mod.rs
|
||||
- `WOW_CAPTURE_UI` — benilla-app/src/capture/scenarios.rs, benilla-app/src/ui_script/mod.rs, benilla-app/src/video.rs
|
||||
- `WOW_CAPTURE_WATER_T` — benilla-world/src/liquid/surface.rs, benilla-world/src/liquid/waves.rs
|
||||
- `WOW_CAST_TRACE` — benilla-app/src/net.rs
|
||||
- `WOW_CHAR` — benilla-app/src/capture/probe_mail.rs, benilla-app/src/char_select/mod.rs, benilla-app/src/cvars.rs, benilla-app/src/login/mod.rs, benilla-app/src/net.rs, benilla-app/src/net/io.rs, benilla-app/src/realm_select/smoke.rs, benilla-app/src/run_mode.rs
|
||||
- `WOW_CHARCREATE_DIALS` — benilla-app/src/char_create/mod.rs
|
||||
@@ -209,10 +219,12 @@
|
||||
- `WOW_CRASH_INJECT` — benilla-app/src/perf/crash_inject.rs
|
||||
- `WOW_CREATE_SPLINE` — benilla-app/src/net/motion/spline.rs
|
||||
- `WOW_CREATE_TEST` — benilla-app/src/portrait/glue_booth.rs, benilla-world/src/bgwin.rs
|
||||
- `WOW_CULL_TRACE` — benilla-world/src/exterior_cull.rs
|
||||
- `WOW_CULLDUMP` — benilla-world/src/wmo_portal/mod.rs
|
||||
- `WOW_CULL_TRACE` — benilla-world/src/exterior_cull.rs
|
||||
- `WOW_CURSOR_TRACE` — benilla-app/src/cursor.rs
|
||||
- `WOW_DATA` — benilla-app/src/local_state.rs, benilla-formats/src/install.rs, benilla-formats/tests/wow_data_env.rs, benilla-formats/tests/wow_data_none.rs
|
||||
- `WOW_DAYLIGHT` — benilla-world/src/lighting/daylight.rs
|
||||
- `WOW_DAYLIGHT_SEAM_EPS` — benilla-world/src/lighting/daylight.rs
|
||||
- `WOW_DEMO_BOTTOM_BARS` — benilla-app/src/ui_script/mod.rs
|
||||
- `WOW_DEPTH` — benilla-app/src/capture/depth_probe.rs, benilla-app/src/dev.rs, benilla-world/src/bgwin.rs
|
||||
- `WOW_DEPTH_AFTER` — benilla-app/src/capture/depth_probe.rs
|
||||
@@ -234,6 +246,7 @@
|
||||
- `WOW_FEED_GATE_CHECK` — benilla-app/src/ui_script/gate.rs, benilla-world/src/bgwin.rs
|
||||
- `WOW_FEED_GATE_TRACE` — benilla-app/src/ui_script/gate.rs, benilla-world/src/bgwin.rs
|
||||
- `WOW_FIXED_DT` — benilla-world/src/frame_pace.rs
|
||||
- `WOW_FOGMODEL` — benilla-world/src/lighting/fog_model.rs
|
||||
- `WOW_FOG_DUMP` — benilla-world/src/lighting/resolve.rs, benilla-world/src/liquid/query.rs
|
||||
- `WOW_FOLLOW_TRACE` — benilla-app/src/player/follow.rs
|
||||
- `WOW_FORCE_SUB` — benilla-world/src/liquid/mod.rs
|
||||
@@ -284,6 +297,7 @@
|
||||
- `WOW_IO_BUFFER` — benilla-app/src/sound/mixer.rs
|
||||
- `WOW_JITTER` — benilla-app/src/capture/probes/jitter.rs, benilla-app/src/dev.rs
|
||||
- `WOW_JITTER_RAW` — benilla-app/src/capture/probes/jitter.rs
|
||||
- `WOW_LAMPFOG` — benilla-app/src/volumetric_fog.rs
|
||||
- `WOW_LAYOUT_DERIVE_TRACE` — benilla-ui/src/script/model.rs
|
||||
- `WOW_LAYOUT_PROF` — benilla-ui/src/script/layout.rs
|
||||
- `WOW_LAYOUT_TOUCH_TRACE` — benilla-ui/src/script/model.rs
|
||||
@@ -310,10 +324,10 @@
|
||||
- `WOW_MATANIM_DUTY` — benilla-world/src/doodad_anim/mat_anim.rs
|
||||
- `WOW_MATANIM_LIVE` — benilla-world/src/doodad_anim/mat_anim.rs
|
||||
- `WOW_MATANIM_PROBE` — benilla-world/src/doodad_anim/mat_anim.rs
|
||||
- `WOW_MERGE_CENSUS` — benilla-world/src/static_merge.rs
|
||||
- `WOW_MERGE_FADERS` — benilla-world/src/terrain_stream/merge.rs
|
||||
- `WOW_MERGED_FADE` — benilla-assets/src/materials.rs
|
||||
- `WOW_MERGED_SLOT` — benilla-assets/src/materials.rs
|
||||
- `WOW_MERGE_CENSUS` — benilla-world/src/static_merge.rs
|
||||
- `WOW_MERGE_FADERS` — benilla-world/src/terrain_stream/merge.rs
|
||||
- `WOW_MESH_EVENTS` — benilla-app/src/perf/mod.rs
|
||||
- `WOW_MESH_HOLDERS` — benilla-app/src/perf/mod.rs
|
||||
- `WOW_MESH_TOUCH` — benilla-app/src/perf/mod.rs
|
||||
@@ -333,6 +347,10 @@
|
||||
- `WOW_NAME_DIST` — benilla-app/src/capture/mod.rs
|
||||
- `WOW_NAME_EL` — benilla-app/src/capture/mod.rs
|
||||
- `WOW_NAME_H` — benilla-app/src/capture/mod.rs
|
||||
- `WOW_NODE_PROBE` — benilla-app/src/capture/probes/world_census.rs, benilla-app/src/dev.rs, benilla-world/src/bgwin.rs
|
||||
- `WOW_NOPORTALCULL` — benilla-world/src/dev_state.rs
|
||||
- `WOW_NOSOUND` — benilla-app/src/sound/mod.rs
|
||||
- `WOW_NOVSYNC` — benilla-app/src/video.rs, benilla-world/src/worldview.rs
|
||||
- `WOW_NO_ALPHATEST` — benilla-world/src/model_render.rs
|
||||
- `WOW_NO_ANIM_LOD` — benilla-app/src/creature_anim/lod.rs, benilla-app/src/lib.rs
|
||||
- `WOW_NO_BC` — benilla-assets/src/gpu_blp.rs
|
||||
@@ -352,18 +370,14 @@
|
||||
- `WOW_NO_TILE_CELLS` — benilla-world/src/terrain_stream/furnish.rs
|
||||
- `WOW_NO_TILE_DROP` — benilla-world/src/terrain_stream.rs
|
||||
- `WOW_NO_TWIST` — benilla-app/src/creature_anim/twist.rs
|
||||
- `WOW_NODE_PROBE` — benilla-app/src/capture/probes/world_census.rs, benilla-app/src/dev.rs, benilla-world/src/bgwin.rs
|
||||
- `WOW_NOPORTALCULL` — benilla-world/src/dev_state.rs
|
||||
- `WOW_NOSOUND` — benilla-app/src/sound/mod.rs
|
||||
- `WOW_NOVSYNC` — benilla-app/src/video.rs, benilla-world/src/worldview.rs
|
||||
- `WOW_OCEAN_DEPTH_DIV` — benilla-formats/src/liquid.rs
|
||||
- `WOW_PANEL` — benilla-world/src/dev_state.rs
|
||||
- `WOW_PART_CHURN` — benilla-app/src/perf/mod.rs
|
||||
- `WOW_PARTICLE_CENSUS` — benilla-app/src/capture/probes/particle_census.rs, benilla-app/src/dev.rs, benilla-world/src/bgwin.rs
|
||||
- `WOW_PARTICLE_DEPTHDUMP` — benilla-world/src/particles/depthdump.rs
|
||||
- `WOW_PARTICLE_DEPTHDUMP_BONES` — benilla-world/src/particles/depthdump.rs
|
||||
- `WOW_PARTICLE_FLAT` — benilla-world/src/particles/render.rs
|
||||
- `WOW_PARTICLE_NODEPTH` — benilla-world/src/particles/render.rs
|
||||
- `WOW_PART_CHURN` — benilla-app/src/perf/mod.rs
|
||||
- `WOW_PASS` — benilla-app/src/login/mod.rs, benilla-app/src/net.rs, benilla-app/src/run_mode.rs
|
||||
- `WOW_PERF_HUD` — benilla-app/src/perf/hud.rs
|
||||
- `WOW_PHASE` — benilla-app/src/capture/phase_probe.rs, benilla-app/src/dev.rs, benilla-world/src/bgwin.rs
|
||||
@@ -387,15 +401,15 @@
|
||||
- `WOW_PIN_UID` — benilla-world/src/wmo_portal/audit/pin.rs
|
||||
- `WOW_PIN_WMO` — benilla-world/src/wmo_portal/audit/pin.rs
|
||||
- `WOW_PIPE_TRACE` — benilla-app/src/pipe_warm/mod.rs
|
||||
- `WOW_POINTS_DUMP` — benilla-world/src/lighting/global_light.rs
|
||||
- `WOW_POINTS_DUMP` — benilla-world/src/lighting/daylight.rs, benilla-world/src/lighting/global_light.rs, benilla-world/src/lighting/lava_light.rs
|
||||
- `WOW_PORTRAIT_TEST` — benilla-app/src/portrait/mod.rs, benilla-app/src/portrait/test_bake.rs, benilla-world/src/bgwin.rs
|
||||
- `WOW_PORTRAIT_TEST_SKIN` — benilla-app/src/portrait/test_bake.rs
|
||||
- `WOW_PROBE` — benilla-app/src/dev.rs, benilla-app/src/lib.rs, benilla-world/src/bgwin.rs
|
||||
- `WOW_PROBE_AUCTION` — benilla-app/src/dev.rs
|
||||
- `WOW_PROBE_BANK` — benilla-app/src/dev.rs
|
||||
- `WOW_PROBE_BG` — benilla-app/src/capture/probe_bg.rs, benilla-app/src/dev.rs
|
||||
- `WOW_PROBE_BG_SAMPLES` — benilla-app/src/capture/probe_bg.rs
|
||||
- `WOW_PROBE_BGQUEUE` — benilla-app/src/dev.rs
|
||||
- `WOW_PROBE_BG_SAMPLES` — benilla-app/src/capture/probe_bg.rs
|
||||
- `WOW_PROBE_BINDER` — benilla-app/src/dev.rs
|
||||
- `WOW_PROBE_BOOK` — benilla-app/src/dev.rs
|
||||
- `WOW_PROBE_CAM` — benilla-app/src/capture/probe_cam.rs
|
||||
@@ -445,8 +459,8 @@
|
||||
- `WOW_REMOTE_IDLE_GATE` — benilla-app/src/net/motion/remote.rs
|
||||
- `WOW_REMOTE_SNAP` — benilla-app/src/net/motion/remote.rs
|
||||
- `WOW_RENDER_SCALE` — benilla-app/src/cvars.rs, benilla-app/src/world_backdrop.rs
|
||||
- `WOW_RES_CENSUS` — benilla-app/src/perf/census.rs
|
||||
- `WOW_RESIZE` — benilla-app/src/capture/mod.rs
|
||||
- `WOW_RES_CENSUS` — benilla-app/src/perf/census.rs
|
||||
- `WOW_REVEAL` — benilla-app/src/capture/probes/reveal.rs, benilla-app/src/dev.rs
|
||||
- `WOW_RIG` — benilla-app/src/capture/probe_rig.rs, benilla-app/src/dev.rs, benilla-app/src/login/mod.rs, benilla-app/src/net.rs, benilla-app/src/probe_shield.rs, benilla-app/src/realm_select/smoke.rs, benilla-app/src/run_mode.rs, benilla-world/src/bgwin.rs
|
||||
- `WOW_RIG_CENSUS` — benilla-world/src/rig_palette.rs
|
||||
@@ -455,10 +469,12 @@
|
||||
- `WOW_ROW_BLOAT` — benilla-app/src/perf/census.rs, benilla-app/src/perf/mod.rs
|
||||
- `WOW_SCHED_CENSUS` — benilla-app/src/dev.rs, benilla-world/src/bgwin.rs
|
||||
- `WOW_SHADE_CENSUS` — benilla-world/src/entity_shade.rs
|
||||
- `WOW_SHADOW_TRACE` — benilla-app/src/shadow_core.rs
|
||||
- `WOW_SHOT_REQUIRE` — benilla-app/src/capture/live_shot.rs
|
||||
- `WOW_SHOT_REQUIRE_DIST` — benilla-app/src/capture/live_shot.rs
|
||||
- `WOW_SKY_DEPTH` — benilla-assets/src/materials.rs
|
||||
- `WOW_SOUND_PROBE` — benilla-app/src/sound/probe.rs
|
||||
- `WOW_SPELL_LIGHT` — benilla-app/src/entities/spell_fx/lifecycle.rs
|
||||
- `WOW_STALL` — benilla-app/src/capture/probes/stall.rs, benilla-app/src/dev.rs
|
||||
- `WOW_STALL_INJECT` — benilla-app/src/perf/stall.rs
|
||||
- `WOW_STALL_SAMPLE` — benilla-app/src/perf/stall.rs
|
||||
@@ -468,8 +484,8 @@
|
||||
- `WOW_STATIC_GX_WMO` — benilla-world/src/static_gx/mod.rs
|
||||
- `WOW_STATIC_MERGE` — benilla-world/src/terrain_stream/merge.rs
|
||||
- `WOW_STREAM_TRACE` — benilla-app/src/perf/mod.rs
|
||||
- `WOW_TAB_TRACE` — benilla-app/src/target/scan.rs
|
||||
- `WOW_TABHOVER` — benilla-app/src/capture/fixtures.rs
|
||||
- `WOW_TAB_TRACE` — benilla-app/src/target/scan.rs
|
||||
- `WOW_TEARDOWN_INJECT` — benilla-app/src/perf/stall.rs
|
||||
- `WOW_TEXT_PROBE` — benilla-app/src/ui_script/extract/text.rs
|
||||
- `WOW_THREADS` — benilla-world/src/boot.rs
|
||||
@@ -477,9 +493,11 @@
|
||||
- `WOW_TILE_TRACE` — benilla-app/src/ui_models/mod.rs
|
||||
- `WOW_TILE_UNLOAD` — benilla-world/src/assets/mod.rs
|
||||
- `WOW_TINT_PROBE` — benilla-app/src/aura_visual.rs
|
||||
- `WOW_TORCH_TRACE` — benilla-app/src/torch_shadow.rs, benilla-world/src/static_gx/torch_depth.rs
|
||||
- `WOW_TRAIL_CENSUS` — benilla-app/src/capture/probes/trail_census.rs, benilla-app/src/dev.rs
|
||||
- `WOW_TRILINEAR` — benilla-app/src/cvars.rs, benilla-assets/src/tex_filter.rs
|
||||
- `WOW_TWIST_GAP` — benilla-app/src/player/body_pose.rs
|
||||
- `WOW_UISCRIPT_DUMP` — benilla-app/tests/ui_script_wall.rs
|
||||
- `WOW_UI_COST` — benilla-app/src/ui_script/extract/mod.rs
|
||||
- `WOW_UI_DIFF` — benilla-app/src/ui_pass.rs, benilla-app/src/ui_script/extract/mod.rs
|
||||
- `WOW_UI_GATE` — benilla-app/src/ui_script/extract/mod.rs
|
||||
@@ -488,20 +506,22 @@
|
||||
- `WOW_UI_PROBE` — benilla-app/src/ui_pass.rs
|
||||
- `WOW_UI_SCALE` — benilla-app/src/cvars.rs, benilla-app/src/ui_script/mod.rs
|
||||
- `WOW_UI_SPLICE_VERIFY` — benilla-app/src/ui_script/extract/mod.rs
|
||||
- `WOW_UISCRIPT_DUMP` — benilla-app/tests/ui_script_wall.rs
|
||||
- `WOW_UNATTENDED` — benilla-app/src/login/mod.rs, benilla-app/src/net.rs, benilla-app/src/run_mode.rs, benilla-world/src/bgwin.rs
|
||||
- `WOW_UNIT_VISUALS` — benilla-app/src/capture/probes/visual_census.rs, benilla-app/src/dev.rs
|
||||
- `WOW_UNIT_VISUALS_RADIUS` — benilla-app/src/capture/probes/visual_census.rs
|
||||
- `WOW_UPLOAD_BUDGET` — benilla-world/src/world_plugins.rs
|
||||
- `WOW_USER` — benilla-app/src/login/mod.rs, benilla-app/src/net.rs, benilla-app/src/run_mode.rs
|
||||
- `WOW_VIS_DUMP` — benilla-app/src/capture/probes/live_fps.rs
|
||||
- `WOW_VIS_TRACE` — benilla-world/src/model_render/visibility.rs
|
||||
- `WOW_VISTA_AT` — benilla-app/src/capture/mod.rs
|
||||
- `WOW_VISTA_FACE` — benilla-app/src/capture/mod.rs
|
||||
- `WOW_VISTA_MIN` — benilla-app/src/capture/mod.rs
|
||||
- `WOW_VISTA_PITCH` — benilla-app/src/capture/mod.rs
|
||||
- `WOW_VIS_DUMP` — benilla-app/src/capture/probes/live_fps.rs
|
||||
- `WOW_VIS_TRACE` — benilla-world/src/model_render/visibility.rs
|
||||
- `WOW_VOLFOG` — benilla-app/src/volumetric_fog.rs
|
||||
- `WOW_VOLFOG_SHAFT_GAIN` — benilla-app/src/volumetric_fog.rs
|
||||
- `WOW_VPLATE_TRACE` — benilla-app/src/vplates.rs
|
||||
- `WOW_WARM_SLICE` — benilla-app/src/pipe_warm/mod.rs
|
||||
- `WOW_WATER` — benilla-world/src/liquid/scene_depth.rs
|
||||
- `WOW_WATER_CLIP` — benilla-assets/src/materials.rs
|
||||
- `WOW_WAVE_DUMP` — benilla-world/src/ffx_glow.rs
|
||||
- `WOW_WEATHER` — benilla-world/src/weather/mod.rs
|
||||
|
||||
Reference in New Issue
Block a user