diff --git a/public/js/rw/dff.js b/public/js/rw/dff.js index 26c3861..1192c0e 100644 --- a/public/js/rw/dff.js +++ b/public/js/rw/dff.js @@ -158,8 +158,8 @@ function parseGeometry(chunk) { // One draw group per material, so each group can get its own texture. const order = [...Array(numTriangles).keys()].sort((x, y) => triMaterial[x] - triMaterial[y]); - const indices = new Uint32Array(numTriangles * 3); - const groups = []; + let indices = new Uint32Array(numTriangles * 3); + let groups = []; let current = null; order.forEach((tri, slot) => { indices[slot * 3] = tris[tri * 3]; @@ -173,6 +173,19 @@ function parseGeometry(chunk) { current.count += 3; }); + // Many GTA:SA exporters leave the per-triangle material index at zero and put + // the real split in the BINMESH extension instead. The struct then yields a + // single group and the whole model renders with material 0's texture, so + // prefer BINMESH whenever it describes more submeshes than the struct did. + const binMesh = extension && extension.find(RW.BINMESH); + if (binMesh) { + const split = parseBinMesh(binMesh, numVertices); + if (split && split.groups.length > groups.length) { + indices = split.indices; + groups = split.groups; + } + } + return { numVertices, positions, @@ -187,6 +200,45 @@ function parseGeometry(chunk) { }; } +// The BINMESH extension holds the render-ready index lists, one submesh per +// material. flags bit 0 means the indices are tristrips rather than triangle +// lists; degenerate triangles are the standard way strips stitch together, so +// they are dropped rather than emitted as zero area faces. +function parseBinMesh(chunk, numVertices) { + const r = chunk.reader(); + if (r.remaining < 12) return null; + const strip = r.u32() & 1; + const numMeshes = r.u32(); + r.u32(); // total index count, recomputed below + const out = []; + const groups = []; + for (let i = 0; i < numMeshes; i++) { + if (r.remaining < 8) return null; + const count = r.u32(); + const material = r.u32(); + if (r.remaining < count * 4) return null; + const list = new Uint32Array(count); + for (let k = 0; k < count; k++) list[k] = r.u32(); + + const start = out.length; + if (strip) { + for (let k = 0; k + 2 < count; k++) { + const a = list[k], b = list[k + (k & 1 ? 2 : 1)], c = list[k + (k & 1 ? 1 : 2)]; + if (a === b || b === c || a === c) continue; // strip stitch + out.push(a, b, c); + } + } else { + for (let k = 0; k + 2 < count; k += 3) out.push(list[k], list[k + 1], list[k + 2]); + } + if (out.length > start) groups.push({ start, count: out.length - start, material }); + } + if (!groups.length) return null; + for (const index of out) { + if (index >= numVertices) return null; // not indices we can trust + } + return { indices: Uint32Array.from(out), groups }; +} + export function parseDFF(buffer) { const view = new DataView(buffer); let clump = new Chunk(view, 0);