Part of #41: the comments of `benilla-assets`, Rust and WGSL, rewritten to the rule in `docs/METHOD.md`: what the code does and the reference fact behind it, in a line. 3,872 comment lines become 1,568. - The citations that were only a private record number go first, mechanically, each edit shown to be a pure removal. - The rewrite corrects the comments that stated wrong facts: the alpha-key cutoff now cites the reference's per-blend-mode table (`0x85ad20`), the texture-filter note names the subsystems that ignore the global setting, a test doc said an exhausted variation roll returns the last variation (it returns the first), and about twenty more found on the way (renamed or deleted items, a shader flag the doc misread, docs sitting on the wrong function). "Deviation:" now marks only a choice with a reason; an unbuilt behaviour says so plainly. - Verified: every file strips to identical code, and `cargo fmt --check` is clean. A second reader compared every file old against new, restored the facts the rewrite had dropped, and checked each new citation against the reference. String literals are untouched; they are #92. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
173 lines
6.7 KiB
Rust
173 lines
6.7 KiB
Rust
//! Every walking-collidable WMO face under a world pin, with its height, its authored normal
|
||
//! against a fall, and whether the reference blocks on it.
|
||
//! `cargo run -p benilla-assets --example face_facing_at -- <map> <x> <y> <z> [wmo-substring]`
|
||
//! e.g. `face_facing_at Kalimdor -8137.9 -4897.2 2.0 caverns`.
|
||
//!
|
||
//! The reference's movement collision is one-sided: `0x671cc0` emits each face's plane at the file
|
||
//! winding and `0x632700` processes it only if `n·dir <= -1e-5`, so a face blocks a fall only if
|
||
//! its authored normal points up. Faces take the app's collider transform, `wow_to_bevy` then the
|
||
//! placement `Transform`; both are proper rotations, so the winding and the sign of `n·dir`
|
||
//! survive. Output is Blizzard data: never commit it.
|
||
|
||
use benilla_assets::coords::{placement_rotation, wow_to_bevy};
|
||
use bevy::prelude::*;
|
||
|
||
/// The reference's facing gate: `[0x80c5c4] = 0xb727c5ac`. A face is processed iff `n·dir <= EPS`.
|
||
const FACING_EPS: f32 = f32::from_bits(0xb727_c5ac);
|
||
|
||
fn main() -> anyhow::Result<()> {
|
||
let mut args = std::env::args().skip(1);
|
||
let usage = || anyhow::anyhow!("usage: face_facing_at <map> <x> <y> <z> [wmo-substring]");
|
||
let map = args.next().ok_or_else(usage)?;
|
||
let x: f32 = args.next().ok_or_else(usage)?.parse()?;
|
||
let y: f32 = args.next().ok_or_else(usage)?.parse()?;
|
||
let z: f32 = args.next().ok_or_else(usage)?.parse()?;
|
||
let filter = args.next().unwrap_or_default().to_lowercase();
|
||
|
||
let data = benilla_formats::wow_data().expect("no WoW install found (set $WOW_DATA)");
|
||
let mut chain = benilla_formats::open_chain(&data)?;
|
||
let tiles = benilla_formats::MapTiles::load(&mut chain, &map)?;
|
||
let pin = wow_to_bevy([x, y, z]);
|
||
// Falling: WoW −Z, which `wow_to_bevy` carries to Bevy −Y.
|
||
let dir = Vec3::NEG_Y;
|
||
println!("{map}: pin ({x}, {y}, {z}) — faces under a straight-down approach\n");
|
||
|
||
// Every placement in the 3x3 tile neighbourhood, deduped by uniqueId (a building spans tiles).
|
||
let mut seen = std::collections::HashSet::new();
|
||
let mut placements: Vec<(String, Transform, u32)> = Vec::new();
|
||
for (tx, ty) in tiles.existing_in_radius(x, y, 1) {
|
||
let tile = benilla_formats::load_tile_mesh(&mut chain, &map, tx, ty)?;
|
||
for w in &tile.wmos {
|
||
if !filter.is_empty() && !w.model.to_lowercase().contains(&filter) {
|
||
continue;
|
||
}
|
||
if seen.insert(w.unique_id) {
|
||
placements.push((
|
||
w.model.clone(),
|
||
Transform {
|
||
translation: wow_to_bevy(w.position),
|
||
rotation: placement_rotation(w.rotation),
|
||
scale: Vec3::ONE,
|
||
},
|
||
w.unique_id,
|
||
));
|
||
}
|
||
}
|
||
}
|
||
|
||
let mut hits: Vec<Hit> = Vec::new();
|
||
for (model, transform, uid) in &placements {
|
||
let stem = model.to_ascii_lowercase().replace('\\', "/");
|
||
let stem = stem.strip_suffix(".wmo").unwrap_or(&stem).to_string();
|
||
// Group files by index, `<stem>_NNN.wmo` as the loader names them, tolerating a few misses.
|
||
let (mut gi, mut misses) = (0u32, 0u32);
|
||
while misses < 4 {
|
||
let Ok(gbytes) = chain.read_file(&format!("{stem}_{gi:03}.wmo")) else {
|
||
misses += 1;
|
||
gi += 1;
|
||
continue;
|
||
};
|
||
misses = 0;
|
||
// The walking gather, DETAIL (`0x04`) skipped, as the WMO loader makes it.
|
||
let (mut pos, mut idx) = (Vec::new(), Vec::new());
|
||
benilla_formats::accumulate_wmo_group_collision(&gbytes, &mut pos, &mut idx);
|
||
let verts: Vec<Vec3> = pos
|
||
.iter()
|
||
.map(|p| transform.transform_point(wow_to_bevy(*p)))
|
||
.collect();
|
||
for t in idx.as_chunks::<3>().0 {
|
||
let (Some(&a), Some(&b), Some(&c)) = (
|
||
verts.get(t[0] as usize),
|
||
verts.get(t[1] as usize),
|
||
verts.get(t[2] as usize),
|
||
) else {
|
||
continue;
|
||
};
|
||
let Some(height) = plane_height_under(pin, [a, b, c]) else {
|
||
continue;
|
||
};
|
||
let n = (b - a).cross(c - a);
|
||
if n.length_squared() < 1e-18 {
|
||
continue;
|
||
}
|
||
hits.push(Hit {
|
||
height,
|
||
dot: n.normalize().dot(dir),
|
||
group: gi,
|
||
uid: *uid,
|
||
model: model.clone(),
|
||
});
|
||
}
|
||
gi += 1;
|
||
}
|
||
}
|
||
|
||
if hits.is_empty() {
|
||
println!("no walking-collidable WMO face has this pin in its footprint");
|
||
return Ok(());
|
||
}
|
||
hits.sort_by(|p, q| {
|
||
(p.height - pin.y)
|
||
.abs()
|
||
.total_cmp(&(q.height - pin.y).abs())
|
||
});
|
||
println!(
|
||
"{:>9} {:>9} {:>9} {:>8} {:>4} wmo",
|
||
"worldZ", "Δ pin", "n·dir", "ref", "grp"
|
||
);
|
||
let (mut blocks, mut passes) = (0usize, 0usize);
|
||
for h in &hits {
|
||
// The pin is Bevy-space; its Y is WoW Z.
|
||
let reference_blocks = h.dot <= FACING_EPS;
|
||
if reference_blocks {
|
||
blocks += 1;
|
||
} else {
|
||
passes += 1;
|
||
}
|
||
println!(
|
||
"{:>9.2} {:>9.2} {:>9.4} {:>8} {:>4} #{} {}",
|
||
h.height,
|
||
h.height - pin.y,
|
||
h.dot,
|
||
if reference_blocks { "BLOCKS" } else { "PASSES" },
|
||
h.group,
|
||
h.uid,
|
||
h.model,
|
||
);
|
||
}
|
||
println!(
|
||
"\n{} face(s) under the pin: reference blocks on {blocks}, falls through {passes}. \
|
||
We block on all {} (parry trimesh is two-sided).",
|
||
hits.len(),
|
||
hits.len()
|
||
);
|
||
Ok(())
|
||
}
|
||
|
||
struct Hit {
|
||
/// World height (Bevy +Y = WoW +Z) where the pin's vertical line meets this face's plane.
|
||
height: f32,
|
||
/// The authored winding normal dotted with the fall direction, the reference's gate input.
|
||
dot: f32,
|
||
group: u32,
|
||
uid: u32,
|
||
model: String,
|
||
}
|
||
|
||
/// Where the vertical through `pin` meets the triangle's plane, if the pin is inside its XZ
|
||
/// projection.
|
||
fn plane_height_under(pin: Vec3, tri: [Vec3; 3]) -> Option<f32> {
|
||
let p = Vec2::new(pin.x, pin.z);
|
||
let [a, b, c] = tri.map(|v| Vec2::new(v.x, v.z));
|
||
let area = (b - a).perp_dot(c - a);
|
||
if area.abs() < 1e-9 {
|
||
return None; // edge-on to the vertical: no footprint to stand in
|
||
}
|
||
let (u, v) = (
|
||
(b - p).perp_dot(c - p) / area,
|
||
(c - p).perp_dot(a - p) / area,
|
||
);
|
||
let w = 1.0 - u - v;
|
||
(u >= 0.0 && v >= 0.0 && w >= 0.0).then(|| u * tri[0].y + v * tri[1].y + w * tri[2].y)
|
||
}
|