THE BLOCKER WAS UPSTREAM OF THE RENDERER: zero of the 4,442 models had a
texture on disk. download_assets.sh extracted only .glb and deleted the zip,
reasoning "GLB is self-contained" — false for Kenney, where every material
points at an external Textures/colormap.png shared across the pack. PNGs are
extracted alongside now, and the resume check REQUIRES a texture, because a
pack of GLBs with no atlas renders white, which is worse than missing.
(The atlases are tiny: 212 textures, 42 KB.)
Static path (model.rs) reuses skin.rs's container/JSON/accessor code rather
than growing a second parser. A static mesh is a skinned one minus joints,
plus one difference: a prop never animates, so each node's world transform is
BAKED into its vertices at load and the model becomes one buffer. Dropping
that bake is exactly how a prop silently renders at the origin, so there is a
test for it. All 4,442 models parse: 1.31M triangles total, 294 average —
comfortably Quest-sized.
Kenney ships TWO conventions, and the second only turned up by looking at a
failure: most packs UV-map into colormap.png, but nature-kit and friends carry
no texture at all and colour each primitive with a material baseColorFactor.
Rather than branch, that factor is baked into the packed vertex's colour lane
and multiplied in the shader (albedo * v_tint) — atlas models carry white,
untextured models get a white 1x1. One shader, both conventions. A model that
DECLARES an atlas but cannot find it stays a hard error; that case really is
broken.
Batching sorts instances by model so equal geometry+texture land adjacent and
accumulate into one draw item: the demo runs 36 instances in 5 draw items,
9,887 triangles. Copies of a prop are free; cost is per distinct model.
The demo picks props BY DESCRIPTION through the asset index (find("pine
tree")), not by hardcoded paths, so it exercises the same path a generated
game takes — and it walks the ranked hits taking the first that loads, so a
pack with a missing atlas yields to the next candidate instead of leaving a
hole. Pillar ring and cone removed; they read as a test harness.
Honest read of the captures: before, coloured cylinders and spheres on a slab
— unmistakably a tech demo. After, a woodland treeline at mixed scale and
species, a suburban house with windows and a teal roof, wooden fences,
textured crates, correctly lit and shadowed. Still imperfect: "boulder"
resolves to nature-kit/cliff_blockCave_rock, a cave-mouth block that reads as
a small building scattered about — a SEARCH-QUALITY gap for the alias owner,
not a render bug.
Washed-out look diagnosed (not fixed, out of scope): it is FOG, not the bake
or the textures. SkyConfig::default()'s density mixes every surface toward the
pale horizon (0.75,0.87,0.96) over a 34-unit camera distance — the far
treeline desaturates toward sky colour while near crates keep their brown. Fix
is either a lower default density or making fog colour follow the sun's
horizon tint so it reads as haze rather than a grey wash.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
32 lines
1.4 KiB
Rust
32 lines
1.4 KiB
Rust
// Parse real Kenney GLBs and report what came out.
|
|
use makepad_game_render::model::StaticModel;
|
|
fn main() {
|
|
let root = std::path::Path::new("apps/arcade/resources/models/kenney");
|
|
let mut total = 0usize;
|
|
let mut ok = 0usize;
|
|
let mut tris = 0usize;
|
|
let mut fails: Vec<String> = Vec::new();
|
|
for pack in std::fs::read_dir(root).unwrap().flatten() {
|
|
let p = pack.path();
|
|
if !p.is_dir() { continue; }
|
|
for f in std::fs::read_dir(&p).unwrap().flatten() {
|
|
let fp = f.path();
|
|
if fp.extension().and_then(|e| e.to_str()) != Some("glb") { continue; }
|
|
total += 1;
|
|
match StaticModel::parse_glb(&std::fs::read(&fp).unwrap()) {
|
|
Ok(m) => {
|
|
ok += 1;
|
|
tris += m.triangle_count();
|
|
if ok <= 5 {
|
|
println!("{:40} v={:5} t={:5} h={:.2} tex={:?}",
|
|
fp.file_name().unwrap().to_string_lossy(),
|
|
m.vertex_count(), m.triangle_count(), m.height(), m.texture_uri);
|
|
}
|
|
}
|
|
Err(e) => if fails.len() < 5 { fails.push(format!("{}: {e}", fp.display())) },
|
|
}
|
|
}
|
|
}
|
|
println!("\nparsed {ok}/{total}, {tris} triangles total, avg {} tris/model", tris / ok.max(1));
|
|
for f in &fails { println!("FAIL {f}"); }
|
|
}
|