diff --git a/crates/benilla-app/assets/ui/OptionsFrame.xml b/crates/benilla-app/assets/ui/OptionsFrame.xml
index 71e19e82..2598e806 100644
--- a/crates/benilla-app/assets/ui/OptionsFrame.xml
+++ b/crates/benilla-app/assets/ui/OptionsFrame.xml
@@ -513,6 +513,7 @@
interiorLight = "Dynamic Interior Lighting",
interiorShadows = "Indoor Torch Shadows",
exteriorShadows = "Outdoor Torch Shadows",
+ torchTerrainShadows = "Terrain Blocks Torchlight",
torchSoft = "Torch Shadow Softness",
fireLight = "Fire Light Brightness",
fireFlicker = "Fire Flicker",
@@ -578,6 +579,13 @@
"The same for fires out in the open: campfires, braziers and lampposts "
.. "cast real shadows after dark.\n\n"
.. "It does nothing by day and does not require Dynamic Interior Lighting.",
+ TORCH_TERRAIN_SHADOWS =
+ "Lets the ground itself take part in outdoor torch shadows: a hill or a "
+ .. "bank between a fire and the slope behind it blocks the fire's light.
+
+"
+ .. "Adds the nearby terrain to each outdoor fire's shadow map when it is "
+ .. "rebuilt; the per-frame cost is small. Needs Outdoor Torch Shadows.",
TORCH_SOFT =
"How soft the edge of a torch shadow is where the object meets the "
.. "ground. Low is hard and crisp; high spreads the edge the way a real "
@@ -637,6 +645,7 @@
interiorLight = "Динамическое освещение помещений",
interiorShadows = "Тени факелов в помещениях",
exteriorShadows = "Тени огней под открытым небом",
+ torchTerrainShadows = "Рельеф заслоняет огни",
torchSoft = "Мягкость теней от огня",
fireLight = "Яркость огня",
fireFlicker = "Мерцание огня",
@@ -710,6 +719,14 @@
"То же для огней под открытым небом: костры, жаровни и фонари "
.. "отбрасывают настоящие тени после наступления темноты.\n\n"
.. "Днём не действует и не требует динамического освещения помещений.",
+ TORCH_TERRAIN_SHADOWS =
+ "Земля сама участвует в тенях от огней снаружи: холм или склон между "
+ .. "костром и местностью за ним заслоняет свет огня.
+
+"
+ .. "Добавляет ближний рельеф в карту теней каждого уличного огня при её "
+ .. "перестройке; затраты на кадр невелики. Требует теней огней под "
+ .. "открытым небом.",
TORCH_SOFT =
"Насколько мягок край тени от огня там, где предмет касается пола. "
.. "Низкое значение — резкий край, высокое — размытый, как от "
@@ -798,7 +815,9 @@
"RowLightingQuality", "RowWaterQuality", "RowVolumetricFog",
"RowCharacterShadows", "RowWorldShadows", "RowShadowDistance",
"RowShadowResolution", "RowMoonShadows",
- "RowInteriorLight", "RowInteriorShadows", "RowExteriorShadows", "RowTorchSoft",
+ "RowInteriorLight", "RowInteriorShadows", "RowExteriorShadows",
+ -- MONKEY (daylight: terrain torch casters)
+ "RowTorchTerrainShadows", "RowTorchSoft",
"RowFireLight", "RowFireFlicker", "RowSpellLights", "RowLavaGlow",
"RowNightDarkness", "RowInteriorDarkness",
},
@@ -1315,6 +1334,9 @@
local torch = (interior and GetCVar("interiorShadows") ~= "0")
or GetCVar("exteriorShadows") ~= "0"
OptionsRow_SetEnabled(getglobal(base .. "RowTorchSoft"), torch)
+ -- MONKEY (daylight: terrain torch casters): the ground only casts for outdoor fires.
+ OptionsRow_SetEnabled(getglobal(base .. "RowTorchTerrainShadows"),
+ GetCVar("exteriorShadows") ~= "0")
return
end
if key == "Controls" then
@@ -2721,11 +2743,23 @@
"BENILLA_TOOLTIP_EXTERIOR_SHADOWS")
-
+
+
+
+ OptionsRow_OnLoad(self, "torchTerrainShadows",
+ BENILLA_ADVGFX.torchTerrainShadows,
+ "BENILLA_TOOLTIP_TORCH_TERRAIN_SHADOWS")
+
+
+
+
+
+
+
-- 0.5..3, the CVar's own clamp verbatim: it is a
-- SCALE on the PCF tap radius at a contact, so the
diff --git a/crates/benilla-app/src/cvars.rs b/crates/benilla-app/src/cvars.rs
index 31a7eced..2235a622 100644
--- a/crates/benilla-app/src/cvars.rs
+++ b/crates/benilla-app/src/cvars.rs
@@ -1070,6 +1070,12 @@ pub(crate) const REGISTERED: &[Registered] = &[
"1",
"benilla's own: outdoor fire lights (campfires, braziers, lampposts) cast real shadows at night; no effect by day",
),
+ // MONKEY (daylight: terrain torch casters): the ground as a torch caster.
+ ours(
+ "torchTerrainShadows",
+ "0",
+ "benilla's own: the ground casts into outdoor fire shadows (hills and banks block a fire's light); needs exteriorShadows",
+ ),
// MONKEY (static torch cache): residency and per-frame work have separate live budgets.
ours(
"interiorShadowCasters",
@@ -2898,7 +2904,7 @@ 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); 33] = [
+ let lighting: [(&str, f32); 34] = [
("waterQuality", shadows.water_quality as f32),
// MONKEY (volumetric fog): weld registry and renderer defaults.
("volumetricFog", shadows.volumetric_fog as f32),
@@ -2926,6 +2932,8 @@ mod tests {
// MONKEY (torch shadows): the cube-map lane, indoors and out.
("interiorShadows", flag(shadows.interior_shadows)),
("exteriorShadows", flag(shadows.exterior_shadows)),
+ // MONKEY (daylight: terrain torch casters)
+ ("torchTerrainShadows", flag(shadows.torch_terrain_shadows)),
(
"interiorShadowCasters",
shadows.interior_shadow_casters as f32,
@@ -2957,7 +2965,7 @@ mod tests {
// row the client does not have; the length is the other half — 33 rows, 33 welds.
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(), 33, "the lighting lane welds 33 distinct rows");
+ assert_eq!(welded.len(), 34, "the lighting lane welds 34 distinct rows");
for name in &welded {
assert!(
REGISTERED.iter().any(|r| r.name == *name),
diff --git a/crates/benilla-app/src/torch_shadow.rs b/crates/benilla-app/src/torch_shadow.rs
index 26d8f3d7..6f31663b 100644
--- a/crates/benilla-app/src/torch_shadow.rs
+++ b/crates/benilla-app/src/torch_shadow.rs
@@ -645,6 +645,23 @@ struct EntityCasters<'w, 's> {
/// bundle rather than being a 17th system param because `update_torch_shadows` already sits at
/// Bevy's 16-param ceiling — the same reason `distance` is here.
lighting: Res<'w, WowLighting>,
+ /// MONKEY (daylight: terrain torch casters): the resident ADT tiles, for the ground half of an
+ /// exterior slot's static caster mesh (`torchTerrainShadows`). `Option` so a scene with no
+ /// terrain streamer (the glue, a booth) is simply terrain-less.
+ terrain: Option>,
+ adt_tiles: Option>>,
+}
+
+/// MONKEY (daylight: terrain torch casters): does an EXTERIOR slot's static map include the ground?
+/// The cvar `torchTerrainShadows`, or `WOW_TORCH_TERRAIN=0|1` (read once) for hermetic captures.
+fn terrain_casters_on(video: &VideoConfig) -> bool {
+ static ENV: std::sync::OnceLock