MONKEY (fix-wind): one grass bend span per model; warm the clutter UV_1 layout

Review W4: the bend height was normalised per submesh, so a trunk + canopy
detail doodad tore apart at the seam; the span is now taken over all of the
model's submeshes. Review W5: clutter meshes carry UV_1 (VERTEX_UVS_B), so the
six clutter pipelines are also warmed on the POS+NORMAL+UV0+UV1+COLOR quad.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
pkuzic
2026-09-25 17:02:12 +01:00
co-authored by Claude Opus 5.5
parent cf7b401599
commit bc7dc6509b
2 changed files with 28 additions and 10 deletions
@@ -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<Handle<WowModelMaterial>> = 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(
+12 -10
View File
@@ -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);