MONKEY (fix-wind): visible foliage wind + Foliage Wind Strength slider
The owner's "wind does not work" was the motion being too small to see (WXL's 0.06 yd grass tips on a 6 s wave, 0.1 yd tree crowns). Retuned at the 9 yd/s base speed: - grass: tips about +-0.2-0.3 yd, 2.5 s main wave (182 cycles/wrap), 1.1 s cross flutter (410); 55 yd gust bands roll downwind (68), swelling the sway to 1.2x and lulling it to 0.55x; a bent tip dips (no stretched blades). Parting around units unchanged. - trees/bushes: 4 s crown sway (114), 5.5 s side wave (82), 0.8 s leaf flutter (569); crown weight grows with height (0.43x at 1.5 yd, 1x at 6 yd, 1.3x cap), so canopies sway about +-0.3 yd while bushes nod and bases stay planted. - new cvar foliageWindStrength 0.25..3 (default 1, not on the preset ladder): Advanced Graphics slider under Foliage Wind (EN+RU label/tooltip), greyed while Foliage Wind is Off; carried in sway_strength, so the parting ignores it; a storm adds up to +60 % sway. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
2776a5c1b2
commit
3e59cc126c
+1
-1
@@ -31,7 +31,7 @@ behind each constant.
|
||||
| Sky quality | Enhanced: the five Light.dbc sky stops through a smooth monotone curve in linear light (no bands at the rings), a soft sun glow tinted by sun and fog colour (fades at night and under cloud), a procedural star field with twinkle and a faint Milky Way over the stock `Stars.m2`. High adds domain-warped cloud detail and sun-lit clouds (self-shadow, silver lining; technique from WarcraftXL, see `THIRD-PARTY.md`). Classic is the reference sky unchanged | Advanced Graphics → Sky Quality, cvar `skyQuality` 0 Classic / 1 Enhanced / 2 High, env `WOW_SKY_QUALITY` |
|
||||
| Modern fog | MONKEY (fog): radial distance fog with a gradual exponential curve, a daylight sun lobe, and an end colour shared by the world, WDL hull, sky horizon and volumetric haze. Each zone keeps its authored fog end up to the reference view-distance limit; optional `LightFogBand.dbc` rows provide height, sun and end-fog controls. Interior WMO fog stays classic | Advanced Graphics → Modern Fog, cvar `fogModel` 0 Classic (default) / 1 Modern (High = 1), env `WOW_FOGMODEL=0\|1`; `fog_hook.wgsl`, `lighting/fog_model.rs`, `light/fog_band.rs` |
|
||||
| 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; MONKEY (rainshelter): a camera-centred rain-occlusion map (128² cells of 0.75 yd, filled by vertical collision rays) keeps ground, walls and water under roofs, porches and bridges dry, with a ~1.5 yd drip margin and a soft edge; tree canopies without colliders do not shelter | 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`, `weather/shelter.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` (dev build + `WOW_CAPTURE` only); 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 |
|
||||
| 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), plus the slider Foliage Wind Strength, cvar `foliageWindStrength` 0.25..3 (default 1, not on the preset ladder; a gain on the sway, not the parting; a storm adds up to +60 %). Tuning at 1 (fix-wind): grass tips about ±0.2-0.3 yd on a 2.5 s wave with 1.1 s cross flutter, gust bands 55 yd wide rolling downwind; tree crowns about ±0.3 yd (lever-arm weight grows with height to 1.3x at 8.5 yd) on a 4 s sway, 5.5 s side wave, 0.8 s leaf flutter; bent tips dip slightly. capture override `WOW_FOLIAGE_WIND` (dev build + `WOW_CAPTURE` only); 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; on alpha-tested foliage (a dense window of two-sided depth spikes) the blur widens its depth tolerance and the AO drops 40 %, which removes the leaf grain while planes, silhouettes and contact creases keep full AO (`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..5` diagnostic views (factor, protection, distance, raw occlusion, foliage mask), `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` |
|
||||
| City window daylight | MONKEY (fix-daylight/integration): a district WMO's oversized window batch (e.g. the Cathedral of Light's stained-glass walls) is split into window-sized daylight apertures, so the nave takes daylight pane by pane. Applies to WMOs loaded after a change | Advanced Graphics → City Window Daylight, cvar `daylightWindowSplit` 0/1 (default 1, the merged behaviour); `benilla-world/src/lighting/daylight.rs` (`set_window_split`), bridge `benilla-app/src/dynamic_interior.rs` |
|
||||
|
||||
@@ -543,6 +543,7 @@
|
||||
foliageWind = "Foliage Wind",
|
||||
windGrass = "Grass",
|
||||
windTrees = "Grass + Trees",
|
||||
foliageWindStrength = "Foliage Wind Strength", -- MONKEY (fix-wind)
|
||||
-- MONKEY (ao): localised contact-shadow row.
|
||||
ambientOcclusion = "Ambient Occlusion",
|
||||
-- MONKEY (lampfog)
|
||||
@@ -707,6 +708,11 @@
|
||||
"Grass bends in two travelling wind waves and parts around nearby characters. "
|
||||
.. "Grass + Trees also gives classified tree and bush foliage a gentle sway.\n\n"
|
||||
.. "Off makes no vertex displacement and restores the unchanged image. Tree shadows stay static.",
|
||||
-- MONKEY (fix-wind)
|
||||
FOLIAGE_WIND_STRENGTH =
|
||||
"How far grass and foliage swing in the wind. 100% is the standard sway; lower "
|
||||
.. "is calmer, higher is a blustery day. Storms swing everything harder on top. "
|
||||
.. "Grass still parts around characters the same way. Needs Foliage Wind on.",
|
||||
-- MONKEY (ao): what the contact shadows touch and what they spare.
|
||||
AMBIENT_OCCLUSION =
|
||||
"Soft contact shadows where surfaces meet: under props and furniture, in the corners "
|
||||
@@ -782,6 +788,7 @@
|
||||
foliageWind = "Ветер листвы",
|
||||
windGrass = "Трава",
|
||||
windTrees = "Трава + деревья",
|
||||
foliageWindStrength = "Сила ветра листвы", -- MONKEY (fix-wind)
|
||||
-- MONKEY (ao): Russian contact-shadow row.
|
||||
ambientOcclusion = "Затенение окружения",
|
||||
-- MONKEY (lampfog)
|
||||
@@ -956,6 +963,11 @@
|
||||
"Трава гнётся двумя бегущими волнами ветра и расступается вокруг персонажей. "
|
||||
.. "Трава + деревья также добавляет мягкое качание листвы деревьев и кустов.\n\n"
|
||||
.. "Выключено не смещает вершины и восстанавливает исходный вид. Тени деревьев остаются неподвижными.",
|
||||
-- MONKEY (fix-wind)
|
||||
FOLIAGE_WIND_STRENGTH =
|
||||
"Насколько сильно трава и листва качаются на ветру. 100% — обычное качание; меньше — "
|
||||
.. "спокойнее, больше — ветреный день. В грозу всё качается ещё сильнее. "
|
||||
.. "Трава по-прежнему расступается вокруг персонажей. Работает, если включён ветер листвы.",
|
||||
-- MONKEY (ao): Russian contact-shadow explanation.
|
||||
AMBIENT_OCCLUSION =
|
||||
"Мягкие контактные тени там, где поверхности соприкасаются: под предметами и мебелью, "
|
||||
@@ -1049,6 +1061,8 @@
|
||||
"RowRainSurfaces",
|
||||
-- MONKEY (wind): appended for conflict-free lane merges.
|
||||
"RowFoliageWind",
|
||||
-- MONKEY (fix-wind): the sway strength, right under its tier.
|
||||
"RowFoliageWindStrength",
|
||||
-- MONKEY (ao): appended so the other rows keep their indices.
|
||||
"RowAmbientOcclusion",
|
||||
-- MONKEY (lampfog)
|
||||
@@ -1574,6 +1588,9 @@
|
||||
-- MONKEY (daylight: terrain torch casters): the ground only casts for outdoor fires.
|
||||
OptionsRow_SetEnabled(getglobal(base .. "RowTorchTerrainShadows"),
|
||||
GetCVar("exteriorShadows") ~= "0")
|
||||
-- MONKEY (fix-wind): the strength only matters while some foliage sways.
|
||||
OptionsRow_SetEnabled(getglobal(base .. "RowFoliageWindStrength"),
|
||||
GetCVar("foliageWind") ~= "0")
|
||||
return
|
||||
end
|
||||
if key == "Controls" then
|
||||
@@ -3260,12 +3277,25 @@
|
||||
})
|
||||
</OnLoad></Scripts>
|
||||
</Frame>
|
||||
<!-- MONKEY (ao): numeric tiers through the existing localised dropdown. -->
|
||||
<Frame name="$parentRowAmbientOcclusion" inherits="OptionsDropdownRowTemplate">
|
||||
<!-- MONKEY (fix-wind): the sway gain, 0.25..3 (100% = the shipped tuning). -->
|
||||
<Frame name="$parentRowFoliageWindStrength" inherits="OptionsSliderRowTemplate">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeTo="$parentRowFoliageWind" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
|
||||
<Anchor point="TOPRIGHT" relativeTo="$parentRowFoliageWind" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Scripts><OnLoad>
|
||||
OptionsRow_OnLoad(self, "foliageWindStrength",
|
||||
BENILLA_ADVGFX.foliageWindStrength,
|
||||
"BENILLA_TOOLTIP_FOLIAGE_WIND_STRENGTH")
|
||||
OptionsSliderRow_Setup(self, 0.25, 3, 0.25)
|
||||
</OnLoad></Scripts>
|
||||
</Frame>
|
||||
<!-- MONKEY (ao): numeric tiers through the existing localised dropdown. -->
|
||||
<Frame name="$parentRowAmbientOcclusion" inherits="OptionsDropdownRowTemplate">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeTo="$parentRowFoliageWindStrength" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
|
||||
<Anchor point="TOPRIGHT" relativeTo="$parentRowFoliageWindStrength" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Scripts><OnLoad>
|
||||
OptionsRow_OnLoad(self, "ambientOcclusion", BENILLA_ADVGFX.ambientOcclusion,
|
||||
"BENILLA_TOOLTIP_AMBIENT_OCCLUSION")
|
||||
|
||||
@@ -964,6 +964,12 @@ pub(crate) const REGISTERED: &[Registered] = &[
|
||||
"2",
|
||||
"benilla's own: foliage wind, 0 Off / 1 Grass / 2 Grass + trees",
|
||||
),
|
||||
// MONKEY (fix-wind): the sway strength slider under Foliage Wind; not on the preset ladder.
|
||||
ours(
|
||||
"foliageWindStrength",
|
||||
"1",
|
||||
"benilla's own: foliage wind strength, a gain on the sway, 0.25..3 (1 = the shipped tuning)",
|
||||
),
|
||||
// MONKEY (fog): the distance-fog model. 0 = the 1.12 linear fog (byte-identical), 1 = Modern
|
||||
// (exponential, sun/horizon colour, end-fog shift, fog end decoupled from farclip past 777).
|
||||
// Default 0; the Graphics preset's High sets 1.
|
||||
@@ -4420,6 +4426,7 @@ mod tests {
|
||||
app.init_resource::<benilla_world::lighting::FogModelSetting>();
|
||||
// MONKEY (wind)
|
||||
app.init_resource::<benilla_world::wind::FoliageWind>();
|
||||
app.init_resource::<benilla_world::wind::FoliageWindStrength>(); // MONKEY (fix-wind)
|
||||
app.init_resource::<benilla_world::weather::RainSurfaces>(); // MONKEY (wet)
|
||||
app.add_observer(crate::monkey_gfx::on_cvar);
|
||||
},
|
||||
|
||||
@@ -6,12 +6,13 @@
|
||||
//! |---|---|---|---|---|
|
||||
//! | `skyDither` | 0 Off / 1 On | 0 | 1 | `benilla_world::ffx_glow::SkyDither` |
|
||||
//! | `foliageWind` | 0 Off / 1 Grass / 2 Grass + trees | 2 | 2 | `benilla_world::wind::FoliageWind` |
|
||||
//! | `foliageWindStrength` | 0.25..3 sway gain | 1 | not governed | `benilla_world::wind::FoliageWindStrength` |
|
||||
//! | `fogModel` | 0 Classic / 1 Modern | 0 | 1 | `benilla_world::lighting::FogModelSetting` |
|
||||
//! | `rainSurfaces` | 0 Off / 1 On | 1 | 1 | `benilla_world::weather::RainSurfaces` |
|
||||
|
||||
use benilla_world::ffx_glow::SkyDither;
|
||||
use benilla_world::lighting::FogModelSetting;
|
||||
use benilla_world::wind::FoliageWind;
|
||||
use benilla_world::wind::{FoliageWind, FoliageWindStrength};
|
||||
use bevy::prelude::*;
|
||||
use benilla_world::weather::RainSurfaces; // MONKEY (wet)
|
||||
|
||||
@@ -20,6 +21,7 @@ pub(crate) fn on_cvar(
|
||||
ev: On<crate::cvars::CvarChanged>,
|
||||
mut dither: ResMut<SkyDither>,
|
||||
mut foliage_wind: ResMut<FoliageWind>,
|
||||
mut wind_strength: ResMut<FoliageWindStrength>, // MONKEY (fix-wind)
|
||||
// MONKEY (fog)
|
||||
mut fog_model: ResMut<FogModelSetting>,
|
||||
mut rain: ResMut<RainSurfaces>, // MONKEY (wet)
|
||||
@@ -41,6 +43,13 @@ pub(crate) fn on_cvar(
|
||||
foliage_wind.0 = want;
|
||||
}
|
||||
}
|
||||
// MONKEY (fix-wind): the sway gain, live, clamped to the slider's 0.25..3.
|
||||
"foliagewindstrength" => {
|
||||
let want = FoliageWindStrength::from_cvar(ev.num());
|
||||
if *wind_strength != want {
|
||||
*wind_strength = want;
|
||||
}
|
||||
}
|
||||
// MONKEY (fog): `WOW_FOGMODEL` still wins for the session (captures).
|
||||
"fogmodel" => fog_model.set_from_cvar(ev.num()),
|
||||
// MONKEY (wet): wet surfaces + rain ripples.
|
||||
@@ -74,6 +83,7 @@ impl Plugin for MonkeyGfxPlugin {
|
||||
.insert_resource(FoliageWind(
|
||||
foliage_wind_pin().unwrap_or(FoliageWind::REGISTERED),
|
||||
))
|
||||
.init_resource::<FoliageWindStrength>() // MONKEY (fix-wind)
|
||||
.init_resource::<FogModelSetting>()
|
||||
.init_resource::<RainSurfaces>() // MONKEY (wet)
|
||||
.add_observer(on_cvar);
|
||||
|
||||
@@ -21,6 +21,14 @@ fn wind_base_dir(frame: MonkeyFrame) -> vec2<f32> {
|
||||
return vec2<f32>(cos(frame.wind_a.z), sin(frame.wind_a.z));
|
||||
}
|
||||
|
||||
// MONKEY (fix-wind): gusts rolling across a field: a 55 yd band travelling downwind (68 cycles
|
||||
// per wrap, about 8 yd/s at the base speed) that swells the sway to 1.2x and lulls it to 0.55x.
|
||||
fn wind_gust_roll(frame: MonkeyFrame, xz: vec2<f32>) -> f32 {
|
||||
let band = sin(dot(wind_base_dir(frame), xz) * (WIND_TAU / 55.0)
|
||||
- wind_travel_phase(frame.wind_b.x, 68.0));
|
||||
return mix(0.55, 1.2, 0.5 + 0.5 * band);
|
||||
}
|
||||
|
||||
fn wind_cross_dir(dir: vec2<f32>) -> vec2<f32> {
|
||||
// WarcraftXL's secondary wave is 35 degrees off the primary.
|
||||
return vec2<f32>(
|
||||
@@ -51,19 +59,20 @@ fn grass_offset(
|
||||
let base_secondary = wind_cross_dir(base_primary);
|
||||
let wave_primary = WIND_TAU / 18.0;
|
||||
let wave_secondary = WIND_TAU / 6.5;
|
||||
// WarcraftXL's k * 0.33 * speed (primary) and k * 0.33 * 1.37 * speed (secondary), as whole
|
||||
// cycles per wrap: 75 and 285.
|
||||
// MONKEY (fix-wind): visible tuning. At the 9 yd/s base speed the primary wave runs a 2.5 s
|
||||
// period (182 cycles per wrap) and the cross flutter 1.1 s (410); both speed up with gusts.
|
||||
let phase_primary = wave_primary * dot(base_primary, world.xz)
|
||||
- wind_travel_phase(frame.wind_b.x, 75.0) + tuft_phase;
|
||||
- wind_travel_phase(frame.wind_b.x, 182.0) + tuft_phase;
|
||||
let phase_secondary = wave_secondary * dot(base_secondary, world.xz)
|
||||
- wind_travel_phase(frame.wind_b.x, 285.0) + tuft_phase * 1.7;
|
||||
- wind_travel_phase(frame.wind_b.x, 410.0) + tuft_phase * 1.7;
|
||||
let gust = 1.0 + 0.5 * (frame.wind_a.w - 0.5) * 2.0;
|
||||
let variance = mix(0.7, 1.3, fract(sin(tuft_phase * 12.9898) * 43758.5453));
|
||||
let distance_fade = 1.0 / (1.0 + distance(camera, world) * 0.015);
|
||||
var xz = (
|
||||
dir_primary * 0.060 * (sin(phase_primary) + 0.35)
|
||||
+ dir_secondary * 0.020 * sin(phase_secondary)
|
||||
) * gust * variance * weight * distance_fade * frame.wind_b.y * frame.wind_b.z;
|
||||
dir_primary * 0.18 * (sin(phase_primary) + 0.35)
|
||||
+ dir_secondary * 0.06 * sin(phase_secondary)
|
||||
) * gust * variance * wind_gust_roll(frame, world.xz) * weight * distance_fade
|
||||
* frame.wind_b.y * frame.wind_b.z;
|
||||
|
||||
let count = min(u32(frame.misc.x), 8u);
|
||||
for (var i = 0u; i < 8u; i += 1u) {
|
||||
@@ -77,7 +86,8 @@ fn grass_offset(
|
||||
xz += radial * 0.50 * edge * cone * weight * frame.wind_b.z;
|
||||
}
|
||||
}
|
||||
return vec3<f32>(xz.x, 0.0, xz.y);
|
||||
// MONKEY (fix-wind): a bent tip dips, so a blade reads as bending rather than stretching.
|
||||
return vec3<f32>(xz.x, -min(0.6 * dot(xz, xz), 0.25), xz.y);
|
||||
}
|
||||
|
||||
// MONKEY (wind): our tree/bush design (WarcraftXL has no tree wind). Only leaf-card batches call
|
||||
@@ -95,26 +105,29 @@ fn tree_offset(
|
||||
// A one-yard bush still needs a crown; tree leaf cards generally begin above this range and
|
||||
// therefore reach full weight, while the first 0.15 yd remains a planted base.
|
||||
let crown = smoothstep(0.15, 1.5, h);
|
||||
let weight = crown * crown;
|
||||
// MONKEY (fix-wind): a taller crown swings further (lever arm): 0.43x at 1.5 yd, 1x at 6 yd,
|
||||
// capped at 1.3x from 8.5 yd, so a bush nods while a canopy sways.
|
||||
let weight = crown * crown * clamp(0.25 + h * 0.12, 0.25, 1.3);
|
||||
let dir = normalize(frame.wind_a.xy + vec2<f32>(1.0e-6, 0.0));
|
||||
let cross = vec2<f32>(-dir.y, dir.x);
|
||||
let base = wind_base_dir(frame);
|
||||
let base_cross = vec2<f32>(-base.y, base.x);
|
||||
let phase = fract(sin(dot(anchor.xz, vec2<f32>(0.173, 0.317))) * 43758.5453) * WIND_TAU;
|
||||
// 0.08 rad/yd sway, 0.61x side wave and a 2.3 rad/s-at-base-speed flutter, as whole cycles
|
||||
// per wrap of travel: 52, 32 and 167.
|
||||
// MONKEY (fix-wind): visible tuning. At the 9 yd/s base speed: a 4 s crown sway (114 cycles
|
||||
// per wrap), a 5.5 s side wave (82) and a 0.8 s leaf flutter (569).
|
||||
let slow = sin(dot(base, world.xz) * (WIND_TAU / 34.0)
|
||||
- wind_travel_phase(frame.wind_b.x, 52.0) + phase);
|
||||
- wind_travel_phase(frame.wind_b.x, 114.0) + phase);
|
||||
let side = sin(dot(base_cross, world.xz) * (WIND_TAU / 21.0)
|
||||
- wind_travel_phase(frame.wind_b.x, 32.0) + phase * 1.7);
|
||||
let flutter = sin(wind_travel_phase(frame.wind_b.x, 167.0)
|
||||
- wind_travel_phase(frame.wind_b.x, 82.0) + phase * 1.7);
|
||||
let flutter = sin(wind_travel_phase(frame.wind_b.x, 569.0)
|
||||
+ dot(world.xz, vec2<f32>(1.7, 2.1)) + phase * 2.0);
|
||||
let gust = 0.75 + frame.wind_a.w * 0.5;
|
||||
let fade = 1.0 / (1.0 + distance(camera, world) * 0.006);
|
||||
let xz = (
|
||||
dir * (0.045 + slow * 0.055)
|
||||
+ cross * side * 0.025
|
||||
+ dir * flutter * 0.012
|
||||
) * weight * gust * fade * frame.wind_b.y * frame.wind_b.w;
|
||||
return vec3<f32>(xz.x, 0.0, xz.y);
|
||||
dir * (0.08 + slow * 0.20)
|
||||
+ cross * side * 0.08
|
||||
+ dir * flutter * 0.03
|
||||
) * weight * gust * mix(0.75, 1.05, (wind_gust_roll(frame, anchor.xz) - 0.55) / 0.65)
|
||||
* fade * frame.wind_b.y * frame.wind_b.w;
|
||||
return vec3<f32>(xz.x, -0.15 * dot(xz, xz), xz.y);
|
||||
}
|
||||
|
||||
@@ -91,6 +91,37 @@ impl FoliageWind {
|
||||
}
|
||||
}
|
||||
|
||||
/// MONKEY (fix-wind): the player's foliage sway strength (`foliageWindStrength`), a plain gain on
|
||||
/// the sway amplitude: 0.25..3, 1 = the shipped tuning. Parting around units ignores it.
|
||||
#[derive(Resource, Clone, Copy, Debug, PartialEq)]
|
||||
pub struct FoliageWindStrength(pub f32);
|
||||
|
||||
impl FoliageWindStrength {
|
||||
pub const MIN: f32 = 0.25;
|
||||
pub const MAX: f32 = 3.0;
|
||||
/// The registered default.
|
||||
pub const REGISTERED: f32 = 1.0;
|
||||
|
||||
/// A CVar value into the slider's range (non-finite reads as the default).
|
||||
pub fn from_cvar(v: f32) -> Self {
|
||||
Self(if v.is_finite() {
|
||||
v.clamp(Self::MIN, Self::MAX)
|
||||
} else {
|
||||
Self::REGISTERED
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FoliageWindStrength {
|
||||
fn default() -> Self {
|
||||
Self(Self::REGISTERED)
|
||||
}
|
||||
}
|
||||
|
||||
/// MONKEY (fix-wind): a storm swings the foliage harder, up to +60 % at a full storm, on top of the
|
||||
/// faster travel the storm's wind speed already gives.
|
||||
pub const STORM_SWAY_GAIN: f32 = 0.6;
|
||||
|
||||
impl Default for FoliageWind {
|
||||
fn default() -> Self {
|
||||
// The resource exists before the CVar host. Its ordinary default therefore has to equal
|
||||
@@ -186,6 +217,7 @@ fn update_wind(
|
||||
time: Res<Time>,
|
||||
weather: Res<WeatherState>,
|
||||
quality: Res<FoliageWind>,
|
||||
strength: Option<Res<FoliageWindStrength>>,
|
||||
mut wind: ResMut<WindField>,
|
||||
mut frame: ResMut<MonkeyFrame>,
|
||||
mut clock: Local<WindClock>,
|
||||
@@ -221,7 +253,16 @@ fn update_wind(
|
||||
frame.wind_base_heading = wind.profile.heading_deg.to_radians();
|
||||
frame.wind_gust = wind.sample.gust;
|
||||
frame.wind_travel = clock.travel as f32;
|
||||
frame.sway_strength = if quality.0 > 0 { 1.0 } else { 0.0 };
|
||||
// MONKEY (fix-wind): `sway_strength` carries the player's gain and the storm swing; the tier
|
||||
// flags stay 0/1 (the grass parting reads only the grass flag, so it keeps its size).
|
||||
let gain = strength.map_or(FoliageWindStrength::REGISTERED, |s| {
|
||||
FoliageWindStrength::from_cvar(s.0).0
|
||||
});
|
||||
frame.sway_strength = if quality.0 > 0 {
|
||||
gain * (1.0 + STORM_SWAY_GAIN * storm)
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
frame.grass_strength = if quality.0 >= 1 { 1.0 } else { 0.0 };
|
||||
frame.tree_strength = if quality.0 >= 2 { 1.0 } else { 0.0 };
|
||||
}
|
||||
@@ -291,6 +332,7 @@ impl Plugin for WindPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.init_resource::<WindField>()
|
||||
.init_resource::<FoliageWind>()
|
||||
.init_resource::<FoliageWindStrength>() // MONKEY (fix-wind)
|
||||
// MONKEY (integration): one ordered writer run of `MonkeyFrame`'s wind rows, before
|
||||
// the lighting resolve (the fog model writes the same resource there).
|
||||
.add_systems(
|
||||
@@ -367,12 +409,28 @@ mod tests {
|
||||
travel.push(rows[5][0]);
|
||||
}
|
||||
assert!(travel[0] < travel[1] && travel[1] < travel[2], "{travel:?}");
|
||||
// The player's strength scales the sway only; a storm swings it harder still.
|
||||
world.insert_resource(FoliageWindStrength::from_cvar(2.0));
|
||||
world.run_system_once(update_wind).unwrap();
|
||||
assert_eq!(world.resource::<MonkeyFrame>().pack(0.5, 0.0)[5][1..], [2.0, 1.0, 1.0]);
|
||||
world.resource_mut::<WeatherState>().sky_density = 1.0;
|
||||
world.run_system_once(update_wind).unwrap();
|
||||
let sway = world.resource::<MonkeyFrame>().sway_strength;
|
||||
assert!((sway - 2.0 * (1.0 + STORM_SWAY_GAIN)).abs() < 1.0e-6, "{sway}");
|
||||
world.insert_resource(FoliageWind(0));
|
||||
world.run_system_once(update_wind).unwrap();
|
||||
let rows = world.resource::<MonkeyFrame>().pack(0.5, 0.0);
|
||||
assert_eq!(rows[5][1..], [0.0, 0.0, 0.0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strength_clamps_to_the_slider_range() {
|
||||
assert_eq!(FoliageWindStrength::from_cvar(0.0).0, 0.25);
|
||||
assert_eq!(FoliageWindStrength::from_cvar(9.0).0, 3.0);
|
||||
assert_eq!(FoliageWindStrength::from_cvar(f32::NAN).0, 1.0);
|
||||
assert_eq!(FoliageWindStrength::default().0, 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn travel_rate_stays_bounded_late_in_a_session() {
|
||||
// MONKEY (fix-wind): the published travel advances at the wind speed, never speed x age.
|
||||
|
||||
Reference in New Issue
Block a user