Files
benilla-octo/crates/benilla-formats/examples/wmo_coplanar.rs
T
SamandClaude Fable 5 6b980b9efb the player build: one binary that carries its content
- a player build ships beside the dev one — dev chords and probe surfaces go dark, and a gate enforces what a player can still reach, by medium not by module
- the binary carries its own shaders and its shipped UI, finds the WoW folder by itself, and keeps its local state in one benilla-config folder
- the glue screens get a capture shutter, and the engine stops depending on the protocol crate

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 18:18:02 +07:00

209 lines
8.0 KiB
Rust

//! Which batches of a WMO are **coplanar with each other**, and by how much:
//! `cargo run -p benilla-formats --example wmo_coplanar -- <wmo-path-or-substring> [gap_yd]`
//! e.g. `wmo_coplanar orctower 0.05`.
//!
//! The falsifier for a z-fighting report. Two surfaces that swap which one is in front do it for one
//! of two reasons, and they take different fixes: a **true tie** (the author put both faces on the
//! same plane, so the depth test has nothing to decide with and the winner is whoever drew last) or
//! **depth precision** (a real but tiny separation that quantises away). The distinction is a
//! property of the *file*, not of the frame — so read it here rather than inferring it from pixels.
//!
//! Prints, for every pair of batches whose geometry overlaps in space, the smallest plane gap between
//! near-parallel faces that actually overlap. A `0.000` gap is a tie; anything else is a separation
//! to compare against the depth buffer's resolution at that range.
//!
//! Output is Blizzard data — pipe it to the scratchpad, never into the repo.
use benilla_formats::RenderSubmesh;
/// Faces this close to parallel are treated as the same plane orientation (≈2.6°).
const PARALLEL_DOT: f32 = 0.999;
/// One triangle reduced to its plane plus the box it occupies — everything the gap test needs.
struct Face {
normal: [f32; 3],
/// Plane offset: `normal · vertex`.
d: f32,
min: [f32; 3],
max: [f32; 3],
/// The triangle's own corners, kept so a batch can be compared against **itself**: two faces of
/// one flat quad share an edge and are trivially coplanar, which is not a defect. Real overlapping
/// sheets share no vertex.
verts: [[f32; 3]; 3],
}
fn faces(sub: &RenderSubmesh) -> Vec<Face> {
sub.indices
.chunks_exact(3)
.filter_map(|t| {
let p: Vec<[f32; 3]> = t.iter().map(|&i| sub.positions[i as usize]).collect();
let (u, v) = (sub3(p[1], p[0]), sub3(p[2], p[0]));
let n = [
u[1] * v[2] - u[2] * v[1],
u[2] * v[0] - u[0] * v[2],
u[0] * v[1] - u[1] * v[0],
];
let len = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt();
if len < 1e-9 {
return None; // degenerate triangle: no plane to speak of
}
let normal = [n[0] / len, n[1] / len, n[2] / len];
let mut min = p[0];
let mut max = p[0];
for q in &p[1..] {
for c in 0..3 {
min[c] = min[c].min(q[c]);
max[c] = max[c].max(q[c]);
}
}
Some(Face {
d: dot(normal, p[0]),
normal,
min,
max,
verts: [p[0], p[1], p[2]],
})
})
.collect()
}
fn sub3(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
[a[0] - b[0], a[1] - b[1], a[2] - b[2]]
}
fn dot(a: [f32; 3], b: [f32; 3]) -> f32 {
a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
}
/// Do the two boxes overlap, allowing `slack` of separation on every axis?
fn boxes_touch(a: &Face, b: &Face, slack: f32) -> bool {
(0..3).all(|c| a.min[c] - slack <= b.max[c] && b.min[c] - slack <= a.max[c])
}
/// Do the triangles share a corner? Two halves of a quad do; two stacked sheets do not.
fn shares_a_vertex(a: &Face, b: &Face) -> bool {
a.verts.iter().any(|p| {
b.verts
.iter()
.any(|q| (0..3).all(|c| (p[c] - q[c]).abs() < 1e-5))
})
}
/// The smallest plane gap between near-parallel, spatially-overlapping faces of `a` and `b`, or
/// `None` if no such pair sits within `gap`. When `a` and `b` are the same batch, edge-sharing
/// neighbours are skipped — they are how a flat surface is built, not a defect.
fn closest_coplanar(a: &[Face], b: &[Face], gap: f32, same_batch: bool) -> Option<f32> {
let mut best: Option<f32> = None;
for (i, fa) in a.iter().enumerate() {
// Within one batch every unordered pair is visited once, and a face never fights itself.
let rest = if same_batch { &b[i + 1..] } else { b };
for fb in rest {
if !boxes_touch(fa, fb, gap) || (same_batch && shares_a_vertex(fa, fb)) {
continue;
}
let d = dot(fa.normal, fb.normal);
if d.abs() < PARALLEL_DOT {
continue;
}
// Flip B's plane to A's orientation so the offsets are comparable.
let db = if d < 0.0 { -fb.d } else { fb.d };
let g = (fa.d - db).abs();
if g <= gap && best.is_none_or(|x| g < x) {
best = Some(g);
}
}
}
best
}
fn main() -> anyhow::Result<()> {
let mut args = std::env::args().skip(1);
let pat = args
.next()
.ok_or_else(|| anyhow::anyhow!("usage: wmo_coplanar <wmo-path-or-substring> [gap_yd]"))?;
// Report pairs whose faces sit within this gap. The default is generous — a real z-fight needs a
// gap far under a centimetre, but seeing the near misses tells you where the margin actually is.
let gap: f32 = args.next().map_or(Ok(0.05), |g| g.parse())?;
let data = benilla_formats::wow_data().expect("no WoW install found (set $WOW_DATA)");
let mut chain = benilla_formats::open_chain(&data)?;
let path = if pat.to_lowercase().ends_with(".wmo") {
pat.clone()
} else {
let lower = pat.to_lowercase();
chain
.list()?
.into_iter()
.map(|e| e.name)
.find(|n| {
let l = n.to_lowercase();
l.ends_with(".wmo") && l.contains(&lower) && !l.contains("_00")
})
.ok_or_else(|| anyhow::anyhow!("no root .wmo matching {pat:?}"))?
};
println!("{path}\n");
let subs = benilla_formats::load_wmo(&mut chain, &path)?;
println!(
"=== {} batches (spawn order = depth-bias order) ===",
subs.len()
);
for (i, s) in subs.iter().enumerate() {
let n = s.positions.len();
let (mut min, mut max) = ([f32::MAX; 3], [f32::MIN; 3]);
for p in &s.positions {
for c in 0..3 {
min[c] = min[c].min(p[c]);
max[c] = max[c].max(p[c]);
}
}
println!(
"[{i:3}] bias {:3} {:5} tri {:?}{}{} span ({:.1},{:.1},{:.1})..({:.1},{:.1},{:.1}) {}",
i + 1,
s.indices.len() / 3,
s.blend,
if s.interior { " INT" } else { "" },
if s.emissive { " UNLIT" } else { "" },
min[0], min[1], min[2], max[0], max[1], max[2],
s.texture.as_deref().unwrap_or("(none)"),
);
let _ = n;
}
let all: Vec<Vec<Face>> = subs.iter().map(faces).collect();
// Facing is ignored throughout: two coplanar faces z-fight whether or not their normals agree.
println!("\n=== batches coplanar WITH THEMSELVES (gap <= {gap}) ===");
println!("(no per-batch depth bias can separate these — the pipeline draws them as one call)");
let mut self_found = 0;
for (i, f) in all.iter().enumerate() {
if let Some(g) = closest_coplanar(f, f, gap, true) {
self_found += 1;
println!(
"[{i:3}] gap {g:.5} yd {}",
subs[i].texture.as_deref().unwrap_or("(none)")
);
}
}
if self_found == 0 {
println!("(none)");
}
println!("\n=== coplanar batch pairs (gap <= {gap}) ===");
let mut found = 0;
for a in 0..all.len() {
for b in (a + 1)..all.len() {
if let Some(g) = closest_coplanar(&all[a], &all[b], gap, false) {
found += 1;
println!(
"[{a:3}] x [{b:3}] gap {g:.5} yd {} | {}",
subs[a].texture.as_deref().unwrap_or("(none)"),
subs[b].texture.as_deref().unwrap_or("(none)"),
);
}
}
}
if found == 0 {
println!("(none — no two batches share a plane within {gap} yd)");
}
Ok(())
}