diff --git a/crates/benilla-app/src/pipe_warm/menagerie.rs b/crates/benilla-app/src/pipe_warm/menagerie.rs index 079ee8ed..c4f45c50 100644 --- a/crates/benilla-app/src/pipe_warm/menagerie.rs +++ b/crates/benilla-app/src/pipe_warm/menagerie.rs @@ -278,6 +278,7 @@ pub(super) fn spawn_menagerie( // doodad's trunk batch is Opaque, its canopy Blend, and the builder's untextured fallback is // Opaque + back-cull with the fade still armed — each its own key (0958's sweep; 0938 warmed // Mask only). + let mut clutter_mats: Vec> = Vec::new(); for two_sided in [false, true] { for blend in [ModelBlend::Opaque, ModelBlend::AlphaTest, ModelBlend::Blend] { let plain = model_material( @@ -316,6 +317,8 @@ pub(super) fn spawn_menagerie( let mut m = m.clone(); m.extension.clutter_fade = Vec4::new(52.5, 70.0, 0.0, 1.0); let clutter = materials.add(m); + // MONKEY (fix-wind): also on the clutter mesh layout (UV_1 = wind height/phase). + clutter_mats.push(clutter.clone()); mats.push(clutter); } } @@ -496,6 +499,19 @@ pub(super) fn spawn_menagerie( let posuv = meshes.add(warm_pos_uv_mesh()); let liquid_mesh = meshes.add(warm_liquid_mesh(false)); let liquid_color_mesh = meshes.add(warm_liquid_mesh(true)); + // MONKEY (fix-wind): clutter meshes carry POS + NORMAL + UV_0 + UV_1 (wind) + COLOR, a layout + // (and `VERTEX_UVS_B`) the model quads lack; the liquid colour quad has exactly that set. + for mat in &clutter_mats { + spawn_lane_rig( + commands, + cam, + None, + &liquid_color_mesh, + None, + mat.clone(), + &mut count, + ); + } // Celestial discs + glares (`sun::setup` quads: position+normal+UV). for mat in lane_handles(&mut lanes.celestial) { spawn_lane_rig( diff --git a/crates/benilla-world/src/clutter.rs b/crates/benilla-world/src/clutter.rs index da9cd94d..b43113a8 100644 --- a/crates/benilla-world/src/clutter.rs +++ b/crates/benilla-world/src/clutter.rs @@ -322,17 +322,19 @@ fn build_chunk_clutter( if subs.is_empty() { continue; } + // MONKEY (wind): source M2 Z is up. Normalize the actual tuft geometry rather than + // trusting texture V (WXL's 3.3.5 detail doodads use inverse V, but vanilla assets are + // not guaranteed to). UV_1 stays free here and carries height + per-tuft phase. + // MONKEY (fix-wind): one span over ALL of the model's submeshes, so a trunk + canopy pair + // bends as one body instead of tearing apart at the seam. + let (min_z, max_z) = subs + .iter() + .flat_map(|sub| sub.positions.iter()) + .fold((f32::INFINITY, f32::NEG_INFINITY), |(lo, hi), p| { + (lo.min(p[2]), hi.max(p[2])) + }); + let height_span = (max_z - min_z).max(1.0e-4); for sub in subs.iter() { - // MONKEY (wind): source M2 Z is up. Normalize the actual tuft geometry rather than - // trusting texture V (WXL's 3.3.5 detail doodads use inverse V, but vanilla assets are - // not guaranteed to). UV_1 stays free here and carries height + per-tuft phase. - let (min_z, max_z) = sub - .positions - .iter() - .fold((f32::INFINITY, f32::NEG_INFINITY), |(lo, hi), p| { - (lo.min(p[2]), hi.max(p[2])) - }); - let height_span = (max_z - min_z).max(1.0e-4); let vcount = sub.positions.len() * placements.len(); let mut positions = Vec::with_capacity(vcount); let mut uvs = Vec::with_capacity(vcount);