fix(GltfWriter): emit 32-bit glTF indices for models with >65535 vertices#901
Open
vladtrc wants to merge 1 commit into
Open
fix(GltfWriter): emit 32-bit glTF indices for models with >65535 vertices#901vladtrc wants to merge 1 commit into
vladtrc wants to merge 1 commit into
Conversation
XModelFace::vertexIndex is 32-bit and every glTF primitive shares one global vertex buffer, but the writer hardcoded UNSIGNED_SHORT face indices and the matching stride. Any index >= 65536 was truncated modulo 65536, silently corrupting the geometry of models with more than 65535 vertices. Select UNSIGNED_INT (and the matching stride/buffer size) when the model has more than 65535 vertices; keep the UNSIGNED_SHORT path otherwise. Add a GltfWriter regression test covering the >65535-vertex case.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
GltfWriteralways writes face indices asunsigned shortand hardcodes the index accessorcomponentTypetoUNSIGNED_SHORT, while the source fieldXModelFace::vertexIndexis 32-bitunsigned. All primitives of a model share a single global vertex buffer (positionAccessor.count == xmodel.m_vertices.size()), so for any XModel whose vertex buffer exceeds 65535 vertices every index>= 65536is silently truncated modulo 65536. The exported glTF then references the wrong vertices and the mesh is corrupted, with no error emitted.Affected sites in
GltfWriter.cpp: the indexbufferViewbyteLength, the index accessorcomponentType, thestatic_cast<unsigned short>index write inFillBufferData, andGetExpectedBufferSize.Why it's real
XModelFace::vertexIndexisunsigned(32-bit); the writer narrows it tounsigned short.static_cast<unsigned short>(N) == N mod 65536, so a face referencing vertex 70000 references vertex 4464 instead — deterministic.componentType 5125 (UNSIGNED_INT)for indices; standard, no extension required.UNSIGNED_SHORT, so ~170k vertices are unaddressable. Individual large XModels in the later supported titles hit the same ceiling through the stockxmodelglTF dumper.Fix
Select
unsigned int/UNSIGNED_INTand the matching stride/buffer size whenm_vertices.size() > 0xFFFF, keep the existingunsigned shortpath otherwise.LhcToRhcIndicesis templated so the winding conversion stays a single source of truth for both index widths.Test
Adds
GltfWriterTest.cpp: builds a 70000-vertexXModelCommonwith a face referencing vertex 69999, runs the writer through a mockgltf::Output, and asserts the index accessor isUNSIGNED_INT (5125)and the high index survives in the buffer unwrapped. Verified against the current tree: the test fails on the unpatched writer (componentTypeis5123) and passes with the fix; the fullObjWritingTestssuite stays green.Disclosure
This bug was found, and this change prepared, with heavy use of AI agents while building an independent IW4 asset reader. The analysis, the byte-level reasoning and the vertex counts above are real and were verified against actual game data and the current source — nothing is fabricated. Happy to adjust to your preferred style or add/adapt the regression test.