MONKEY (fix-wind): tree exile sway rides a material marker, not MeshTag bit 18
Bit 18 aliases the top lane-weight bit and bit 12 of the interior probe slot, so doorway units and probe-lit interior props swayed like trees. A classified leaf batch's fade-band exile now gets marked copies of its cutout/blend materials (clutter_fade.z bit 14, FOLIAGE_WIND_MARKER, not a key axis) and wow_model.wgsl sways only those. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
f0526d610b
commit
512c81f43e
@@ -1095,10 +1095,11 @@ fn vertex(vertex: WowVertex) -> WowVsOut {
|
||||
);
|
||||
}
|
||||
#endif
|
||||
// MONKEY (wind): a retained tree inside its fade band renders here. StaticGx marks only its
|
||||
// alpha-tested leaf batches in MeshTag bit 18; use the same anchor/field maths to avoid a pop.
|
||||
let wind_tag = mesh_functions::get_tag(vertex.instance_index);
|
||||
if ((wind_tag & 262144u) != 0u && m.clutter_fade.w <= 0.5) {
|
||||
// MONKEY (wind): a retained tree inside its fade band renders here with the same anchor/field
|
||||
// maths, to avoid a pop. MONKEY (fix-wind): the leaf-batch marker is material bit 14
|
||||
// (`model_render::FOLIAGE_WIND_MARKER`), never a MeshTag bit (bit 18 aliases the lane weight
|
||||
// and the interior probe slot, so doorway units and interior props swayed).
|
||||
if ((u32(m.clutter_fade.z) & 16384u) != 0u && m.clutter_fade.w <= 0.5) {
|
||||
let world = p_cam + view.world_position;
|
||||
p_cam += wind_hook::tree_offset(
|
||||
world, frame_origin, view.world_position, wow_light.monkey
|
||||
|
||||
@@ -90,10 +90,6 @@ const SHADE_SHIFT: u32 = 6;
|
||||
/// Sits in the exterior payload's reserved bits, so [`with_shade`]/[`with_alpha`]/[`with_rig`]
|
||||
/// carry it through and the shader's 8-bit shade decode never sees it.
|
||||
pub(crate) const MATTE_INDOOR_BIT: u32 = 0x0000_4000;
|
||||
/// MONKEY (wind): bit 18 on an exterior, non-Matte static doodad exile marks a classified
|
||||
/// tree/bush leaf batch. It aliases the top lane-weight bit safely: lane weight is read only with
|
||||
/// [`MATTE_INDOOR_BIT`], which these exterior fade twins never carry.
|
||||
const FOLIAGE_WIND_BIT: u32 = 0x0004_0000;
|
||||
/// Bits 15..=18 of the exterior payload (MONKEY, portal lane fade): the **lane weight**, a 4-bit
|
||||
/// crossfade `0` (fully exterior-lit) … [`LANE_MAX`] (fully room-lit) that the shader mixes the
|
||||
/// two lanes by. It rides beside [`MATTE_INDOOR_BIT`] — the bit says "this part is on the room
|
||||
@@ -208,16 +204,6 @@ pub fn alpha_bits(alpha: f32) -> u32 {
|
||||
}
|
||||
}
|
||||
|
||||
/// MONKEY (wind): marks or clears the fade-twin foliage receiver without disturbing its live
|
||||
/// alpha. Only exterior static doodad exiles use this payload convention.
|
||||
pub(crate) fn with_foliage_wind(tag: u32, on: bool) -> u32 {
|
||||
if on {
|
||||
tag | FOLIAGE_WIND_BIT
|
||||
} else {
|
||||
tag & !FOLIAGE_WIND_BIT
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets or clears [`INTERIOR_FOG_BIT`] alone: the probe slot says how a prop is lit, the
|
||||
/// reference's `[0xca7f00]` only which fog triple its batch gets.
|
||||
pub(crate) fn with_interior_fog(tag: u32, on: bool) -> u32 {
|
||||
@@ -366,17 +352,6 @@ mod tests {
|
||||
assert_eq!(shade_of(t, ext()), 10);
|
||||
}
|
||||
|
||||
// MONKEY (wind): the fade-twin marker is orthogonal to the live fade field.
|
||||
#[test]
|
||||
fn foliage_marker_survives_alpha_writes_and_clears_cleanly() {
|
||||
let alpha = alpha_bits(0.5);
|
||||
let marked = with_foliage_wind(alpha, true);
|
||||
assert_eq!(marked & FOLIAGE_WIND_BIT, FOLIAGE_WIND_BIT);
|
||||
assert_eq!(marked & ALPHA_MASK, alpha);
|
||||
assert_eq!(with_alpha(marked, 0.25) & FOLIAGE_WIND_BIT, FOLIAGE_WIND_BIT);
|
||||
assert_eq!(with_foliage_wind(marked, false), alpha);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn probe_bits_compose_with_the_alpha_field() {
|
||||
let t = probe_bits(6660);
|
||||
|
||||
@@ -75,6 +75,9 @@ pub struct MatKey {
|
||||
window: bool,
|
||||
/// A depth-prime twin ([`zfill_material`], the reference's `M2UseZFill` clone at `0x707f7d`).
|
||||
zfill: bool,
|
||||
/// MONKEY (fix-wind): a classified tree/bush leaf batch's exile copy, marker bit 14
|
||||
/// ([`FOLIAGE_WIND_MARKER`]); `wow_model.wgsl` sways only these.
|
||||
foliage_wind: bool,
|
||||
/// A WMO skybox batch ([`crate::skybox`]), with its own pipeline and sort rung.
|
||||
sky_depth: bool,
|
||||
/// The placement owning this material when its animated loop follows the instance's sequence
|
||||
@@ -154,6 +157,137 @@ pub fn model_material(
|
||||
// the sequence its instance is playing (decision 1408): the animated-material registries are
|
||||
// keyed by material, so such a batch cannot share one and be right. See `MatKey::instance`.
|
||||
instance: Option<Entity>,
|
||||
) -> Handle<WowModelMaterial> {
|
||||
model_material_impl(
|
||||
cache,
|
||||
materials,
|
||||
texture,
|
||||
blend,
|
||||
two_sided,
|
||||
is_wmo,
|
||||
is_interior,
|
||||
is_emissive,
|
||||
is_additive,
|
||||
fade_variant,
|
||||
no_depth_write,
|
||||
no_depth_test,
|
||||
fog_policy,
|
||||
env_map,
|
||||
shade,
|
||||
batch_order,
|
||||
uv_anim,
|
||||
rgb_anim,
|
||||
wmo_class,
|
||||
sidn,
|
||||
window,
|
||||
sky_depth,
|
||||
light,
|
||||
torch,
|
||||
instance,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
/// MONKEY (fix-wind): [`model_material`] for a classified tree/bush leaf batch's exile copy: the
|
||||
/// same material plus [`FOLIAGE_WIND_MARKER`], so the entity draw sways like the retained batch.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn foliage_model_material(
|
||||
cache: &mut MaterialCache,
|
||||
materials: &mut Assets<WowModelMaterial>,
|
||||
texture: Option<Handle<Image>>,
|
||||
blend: ModelBlend,
|
||||
two_sided: bool,
|
||||
is_wmo: bool,
|
||||
is_interior: bool,
|
||||
is_emissive: bool,
|
||||
is_additive: bool,
|
||||
fade_variant: bool,
|
||||
no_depth_write: bool,
|
||||
no_depth_test: bool,
|
||||
fog_policy: benilla_formats::FogPolicy,
|
||||
env_map: bool,
|
||||
shade: ShadeSel,
|
||||
batch_order: u16,
|
||||
uv_anim: Option<&std::sync::Arc<benilla_formats::UvAnim>>,
|
||||
rgb_anim: Option<&std::sync::Arc<benilla_formats::RgbAnim>>,
|
||||
wmo_class: Option<WmoBatchClass>,
|
||||
sidn: Option<[u8; 3]>,
|
||||
window: bool,
|
||||
sky_depth: bool,
|
||||
light: &Buffer,
|
||||
// MONKEY (torch shadows Phase 3A): the shared torch depth image + table buffer, cloned into the
|
||||
// material beside `light` (not a key axis — one pair for the whole scene, like the light).
|
||||
torch: &TorchBinds,
|
||||
// The ONE placement this material belongs to, or `None` for the shared batch material every
|
||||
// instance of the model reuses. `Some` only for a batch whose animated UV/tint loop depends on
|
||||
// the sequence its instance is playing (decision 1408): the animated-material registries are
|
||||
// keyed by material, so such a batch cannot share one and be right. See `MatKey::instance`.
|
||||
instance: Option<Entity>,
|
||||
) -> Handle<WowModelMaterial> {
|
||||
model_material_impl(
|
||||
cache,
|
||||
materials,
|
||||
texture,
|
||||
blend,
|
||||
two_sided,
|
||||
is_wmo,
|
||||
is_interior,
|
||||
is_emissive,
|
||||
is_additive,
|
||||
fade_variant,
|
||||
no_depth_write,
|
||||
no_depth_test,
|
||||
fog_policy,
|
||||
env_map,
|
||||
shade,
|
||||
batch_order,
|
||||
uv_anim,
|
||||
rgb_anim,
|
||||
wmo_class,
|
||||
sidn,
|
||||
window,
|
||||
sky_depth,
|
||||
light,
|
||||
torch,
|
||||
instance,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn model_material_impl(
|
||||
cache: &mut MaterialCache,
|
||||
materials: &mut Assets<WowModelMaterial>,
|
||||
texture: Option<Handle<Image>>,
|
||||
blend: ModelBlend,
|
||||
two_sided: bool,
|
||||
is_wmo: bool,
|
||||
is_interior: bool,
|
||||
is_emissive: bool,
|
||||
is_additive: bool,
|
||||
fade_variant: bool,
|
||||
no_depth_write: bool,
|
||||
no_depth_test: bool,
|
||||
fog_policy: benilla_formats::FogPolicy,
|
||||
env_map: bool,
|
||||
shade: ShadeSel,
|
||||
batch_order: u16,
|
||||
uv_anim: Option<&std::sync::Arc<benilla_formats::UvAnim>>,
|
||||
rgb_anim: Option<&std::sync::Arc<benilla_formats::RgbAnim>>,
|
||||
wmo_class: Option<WmoBatchClass>,
|
||||
sidn: Option<[u8; 3]>,
|
||||
window: bool,
|
||||
sky_depth: bool,
|
||||
light: &Buffer,
|
||||
// MONKEY (torch shadows Phase 3A): the shared torch depth image + table buffer, cloned into the
|
||||
// material beside `light` (not a key axis — one pair for the whole scene, like the light).
|
||||
torch: &TorchBinds,
|
||||
// The ONE placement this material belongs to, or `None` for the shared batch material every
|
||||
// instance of the model reuses. `Some` only for a batch whose animated UV/tint loop depends on
|
||||
// the sequence its instance is playing (decision 1408): the animated-material registries are
|
||||
// keyed by material, so such a batch cannot share one and be right. See `MatKey::instance`.
|
||||
instance: Option<Entity>,
|
||||
foliage_wind: bool,
|
||||
) -> Handle<WowModelMaterial> {
|
||||
let key = MatKey {
|
||||
light: light.id(),
|
||||
@@ -177,6 +311,7 @@ pub fn model_material(
|
||||
sidn,
|
||||
window,
|
||||
zfill: false,
|
||||
foliage_wind,
|
||||
sky_depth,
|
||||
instance,
|
||||
};
|
||||
@@ -263,7 +398,8 @@ pub fn model_material(
|
||||
| (u16::from(blend == ModelBlend::Mod2x) << 8)
|
||||
| (u16::from(fade_variant && source_cutout) * TWIN_CUTOUT_MARKER)
|
||||
| (u16::from(env_map) * ENV_MAP_MARKER)
|
||||
| (u16::from(sky_depth) * SKY_DEPTH_MARKER),
|
||||
| (u16::from(sky_depth) * SKY_DEPTH_MARKER)
|
||||
| (u16::from(foliage_wind) * FOLIAGE_WIND_MARKER),
|
||||
),
|
||||
0.0,
|
||||
),
|
||||
@@ -349,6 +485,12 @@ pub(crate) const ENV_MAP_MARKER: u16 = 1 << 12;
|
||||
/// vertex with no raster bias; a `WowModelKey` axis, so every other draw keeps its real depth.
|
||||
pub(crate) const SKY_DEPTH_MARKER: u16 = 1 << 13;
|
||||
|
||||
/// MONKEY (fix-wind): `clutter_fade.z` marker bit 14: a classified tree/bush leaf batch's exile
|
||||
/// copy, the only entity draw `wow_model.wgsl` applies `tree_offset` to. Vertex-only uniform data,
|
||||
/// not a `WowModelKey` axis. (It replaced MeshTag bit 18, which aliases the lane weight and the
|
||||
/// interior probe slot, so doorway units and interior props swayed.)
|
||||
pub(crate) const FOLIAGE_WIND_MARKER: u16 = 1 << 14;
|
||||
|
||||
/// [`BATCH_ORDER_SORT_EPS`] for the WMO skybox lane: at the −6e4 rung the f32 ulp is 0.0039, so
|
||||
/// 1e-3 would round away; this step is 4 ulps. Every skybox batch pair ties, being eye-anchored.
|
||||
pub(crate) const SKYBOX_ORDER_EPS: f32 = 0.015625;
|
||||
@@ -412,6 +554,7 @@ pub fn zfill_material(
|
||||
sidn: None,
|
||||
window: false,
|
||||
zfill: true,
|
||||
foliage_wind: false,
|
||||
// No skybox batch has a twin: the sky writes no depth.
|
||||
sky_depth: false,
|
||||
instance: None,
|
||||
|
||||
@@ -100,11 +100,8 @@ fn spawn_exile(commands: &mut Commands, f: &GxFader, alpha: f32, admitted: bool)
|
||||
},
|
||||
crate::model_render::EntityPathWhy("exile"),
|
||||
crate::interact::PickMesh(b.geometry.clone()),
|
||||
// MONKEY (wind): bit 18 is the fade-twin leaf marker in exterior payload mode.
|
||||
bevy::mesh::MeshTag(crate::mesh_tag::with_foliage_wind(
|
||||
crate::mesh_tag::alpha_bits(alpha),
|
||||
b.foliage_wind,
|
||||
)),
|
||||
// MONKEY (fix-wind): a leaf batch's sway marker rides its material (`FOLIAGE_WIND_MARKER`).
|
||||
bevy::mesh::MeshTag(crate::mesh_tag::alpha_bits(alpha)),
|
||||
crate::model_fade::DoodadFade {
|
||||
radius: f.radius,
|
||||
local_center: f.local_center,
|
||||
|
||||
@@ -215,8 +215,6 @@ struct GxFaderBatch {
|
||||
blend: Handle<benilla_assets::materials::WowModelMaterial>,
|
||||
blend_mode: ModelBlend,
|
||||
geometry: Arc<RenderSubmesh>,
|
||||
/// MONKEY (wind): mirror the retained batch's sway while this placement fades as an entity.
|
||||
foliage_wind: bool,
|
||||
}
|
||||
|
||||
/// A fader's exile state. Steady: drawn retained at fade 1. Exiled: drawn as ordinary entities,
|
||||
@@ -569,7 +567,6 @@ impl StaticGx {
|
||||
blend: seed.blend,
|
||||
blend_mode: b.blend,
|
||||
geometry: b.geometry.clone(),
|
||||
foliage_wind,
|
||||
});
|
||||
if is_new {
|
||||
// A placement's later batches share its sphere: the caches move only on a new one.
|
||||
@@ -706,7 +703,7 @@ 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
|
||||
/// doodads never reach this function because `assemble.rs` excludes them before `StaticGx::divert`.
|
||||
fn foliage_wind_batch(path: &str, blend: ModelBlend, wmo_geometry: bool) -> bool {
|
||||
pub(crate) fn foliage_wind_batch(path: &str, blend: ModelBlend, wmo_geometry: bool) -> bool {
|
||||
if wmo_geometry || blend != ModelBlend::AlphaTest {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -233,6 +233,44 @@ pub fn spawn_model_entities(
|
||||
seq_owner,
|
||||
)
|
||||
};
|
||||
// MONKEY (fix-wind): a classified exterior tree/bush leaf batch gets marked copies of both
|
||||
// materials for its fade-band exile (`FOLIAGE_WIND_MARKER`), so only those entity draws
|
||||
// sway; the plain pair stays on every other path.
|
||||
let wind_seed = (!interior
|
||||
&& !steady_interior_prop
|
||||
&& crate::static_gx::foliage_wind_batch(&object.label, sub.blend, is_wmo))
|
||||
.then(|| {
|
||||
let mut wind_mat = |fade: bool| {
|
||||
crate::model_render::foliage_model_material(
|
||||
mat_cache,
|
||||
materials,
|
||||
sub.texture.clone(),
|
||||
sub.blend,
|
||||
two_sided,
|
||||
is_wmo,
|
||||
interior,
|
||||
sub.emissive,
|
||||
sub.additive,
|
||||
fade,
|
||||
sub.no_depth_write,
|
||||
sub.no_depth_test,
|
||||
sub.fog_policy,
|
||||
sub.env_map,
|
||||
shade,
|
||||
batch_order,
|
||||
sub.uv_anim.as_ref(),
|
||||
sub.rgb_anim.as_ref(),
|
||||
sub.wmo_batch,
|
||||
sub.sidn,
|
||||
sub.window,
|
||||
false,
|
||||
light,
|
||||
torch,
|
||||
seq_owner,
|
||||
)
|
||||
};
|
||||
(wind_mat(false), wind_mat(true))
|
||||
});
|
||||
// One classification for the census tally and the diverts, so the two cannot drift.
|
||||
let class = crate::static_merge::BatchClass {
|
||||
excluded: animated
|
||||
@@ -311,8 +349,13 @@ pub fn spawn_model_entities(
|
||||
local_center,
|
||||
stat_mesh: stat_mesh.clone(),
|
||||
aabb: *stat_aabb,
|
||||
cutout: cutout.clone(),
|
||||
blend: blend.clone(),
|
||||
// MONKEY (fix-wind): the marked pair for a leaf batch.
|
||||
cutout: wind_seed
|
||||
.as_ref()
|
||||
.map_or_else(|| cutout.clone(), |w| w.0.clone()),
|
||||
blend: wind_seed
|
||||
.as_ref()
|
||||
.map_or_else(|| blend.clone(), |w| w.1.clone()),
|
||||
});
|
||||
// A never-fader enters bare, a fader only with its seed.
|
||||
(class.never_fade || fade_seed.is_some())
|
||||
|
||||
Reference in New Issue
Block a user