MONKEY (gfx): keep named rock doodads and ground clutter rigid in wind
Reject rock, stone, boulder, pebble, gravel and rubble model filenames from retained foliage. Give matching ground-detail models zero bend height so neither grass wind nor player parting moves them. Limit the name veto to the filename to preserve vegetation in stone-named zones. Verified on Windows: two foliage classifier tests and one rigid-model test pass; benilla-app lib tests have 4313 passed, 0 failed, 10 ignored (no-install run); play build passes. Elwynn wind captures at 0 and 3 seconds preserve coloured foliage motion. The tester's specific small-rock assets still need an in-game retest. Co-Authored-By: GPT-6 Astra (Codex)
This commit is contained in:
co-authored by
GPT-6 Astra (Codex)
parent
fbeb0f02f6
commit
f5547a63bd
@@ -334,6 +334,9 @@ fn build_chunk_clutter(
|
||||
(lo.min(p[2]), hi.max(p[2]))
|
||||
});
|
||||
let height_span = (max_z - min_z).max(1.0e-4);
|
||||
// MONKEY (wind): the detail table includes stones as well as grass. Zero bend weight
|
||||
// keeps their geometry fixed under both wind and the player-parting field.
|
||||
let bends = !crate::wind::rigid_model(model_path);
|
||||
for sub in subs.iter() {
|
||||
let vcount = sub.positions.len() * placements.len();
|
||||
let mut positions = Vec::with_capacity(vcount);
|
||||
@@ -359,7 +362,12 @@ fn build_chunk_clutter(
|
||||
for (i, p) in sub.positions.iter().enumerate() {
|
||||
positions.push((rot * (wow_to_bevy(*p) * inst_scale) + origin).to_array());
|
||||
uvs.push(sub.uvs[i]);
|
||||
wind.push([((p[2] - min_z) / height_span).clamp(0.0, 1.0), phase]);
|
||||
let bend_height = if bends {
|
||||
((p[2] - min_z) / height_span).clamp(0.0, 1.0)
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
wind.push([bend_height, phase]);
|
||||
normals.push(sp.ground_normal);
|
||||
colors.push(tint);
|
||||
}
|
||||
|
||||
@@ -700,11 +700,11 @@ impl StaticGx {
|
||||
}
|
||||
}
|
||||
|
||||
/// MONKEY (wind): conservative leaf classification. Alpha test excludes trunks/rocks even when a
|
||||
/// whole model path says tree; the name terms cover vanilla's tree/bush/plant families. Animated
|
||||
/// MONKEY (wind): conservative leaf classification. Alpha test alone does not exclude rock cards;
|
||||
/// rigid model names veto plant-family directories. Animated
|
||||
/// doodads never reach this function because `assemble.rs` excludes them before `StaticGx::divert`.
|
||||
pub(crate) fn foliage_wind_batch(path: &str, blend: ModelBlend, wmo_geometry: bool) -> bool {
|
||||
if wmo_geometry || blend != ModelBlend::AlphaTest {
|
||||
if wmo_geometry || blend != ModelBlend::AlphaTest || crate::wind::rigid_model(path) {
|
||||
return false;
|
||||
}
|
||||
let p = path.to_ascii_lowercase();
|
||||
@@ -920,6 +920,23 @@ mod tests {
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn foliage_classifier_vetoes_rigid_cards_in_plant_families() {
|
||||
for path in [
|
||||
"World\\Generic\\PassiveDoodads\\Bush\\BushRock01.m2",
|
||||
"World\\Azeroth\\Elwynn\\PassiveDoodads\\Trees\\Stone01.m2",
|
||||
"World/Generic/Foliage/Boulder01.m2",
|
||||
"World/Generic/Plants/Pebble01.m2",
|
||||
"World/Generic/Foliage/Gravel01.m2",
|
||||
"World/Generic/Foliage/Rubble01.m2",
|
||||
] {
|
||||
assert!(
|
||||
!foliage_wind_batch(path, ModelBlend::AlphaTest, false),
|
||||
"{path}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_fader_divert_registers_its_placement_seed() {
|
||||
let mut gx = StaticGx::default();
|
||||
|
||||
@@ -15,6 +15,19 @@ const TAU: f64 = std::f64::consts::TAU;
|
||||
const RATIOS: [f64; 3] = [1.0, 0.437, 0.1913];
|
||||
const WEIGHTS: [f64; 3] = [0.60, 0.28, 0.12];
|
||||
|
||||
/// MONKEY (wind): rigid detail models can have alpha-tested cards and live in plant directories.
|
||||
/// Inspect the filename so a zone such as Stonetalon does not disable all its vegetation.
|
||||
pub(crate) fn rigid_model(path: &str) -> bool {
|
||||
let name = path
|
||||
.rsplit(['/', '\\'])
|
||||
.next()
|
||||
.unwrap_or(path)
|
||||
.to_ascii_lowercase();
|
||||
["rock", "stone", "boulder", "pebble", "gravel", "rubble"]
|
||||
.iter()
|
||||
.any(|term| name.contains(term))
|
||||
}
|
||||
|
||||
/// The WarcraftXL wind profile. Units are yards, seconds and degrees.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct WindProfile {
|
||||
@@ -352,6 +365,22 @@ impl Plugin for WindPlugin {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn rigid_details_ignore_case_and_plant_directory_names() {
|
||||
for path in [
|
||||
"World/Plants/BushRock01.m2",
|
||||
"World\\Trees\\STONE01.mdx",
|
||||
"Boulder02.m2",
|
||||
"Pebble01.m2",
|
||||
"Gravel01.m2",
|
||||
"Rubble01.m2",
|
||||
] {
|
||||
assert!(rigid_model(path), "{path}");
|
||||
}
|
||||
assert!(!rigid_model("World/Stonetalon/Trees/Pine01.m2"));
|
||||
assert!(!rigid_model("World/RockyZone/Grass01.m2"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn defaults_are_the_warcraft_xl_profile() {
|
||||
let p = WindProfile::default();
|
||||
|
||||
Reference in New Issue
Block a user