From 04ad88330feb0c1076466abc7a049f130c8ed029 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Fri, 3 Apr 2026 22:22:51 -0700 Subject: [PATCH] fix(rendering): remap M2 vertex bone indices through bone lookup table M2 vertex bone indices are indices into boneLookupTable, not direct bone array indices. Without remapping, vertices weighted to higher bone indices (cloak, cape, hair) get the wrong bone transform, causing vertices to project wildly outward from the character. --- src/rendering/character_renderer.cpp | 12 +++++++++++- src/rendering/m2_renderer.cpp | 12 ++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/rendering/character_renderer.cpp b/src/rendering/character_renderer.cpp index 12765800..cccde825 100644 --- a/src/rendering/character_renderer.cpp +++ b/src/rendering/character_renderer.cpp @@ -1486,7 +1486,17 @@ void CharacterRenderer::setupModelBuffers(M2ModelGPU& gpuModel) { auto& dst = gpuVerts[i]; dst.position = src.position; std::memcpy(dst.boneWeights, src.boneWeights, 4); - std::memcpy(dst.boneIndices, src.boneIndices, 4); + // Remap bone indices through the bone lookup table. + // M2 vertex bone indices are lookup table indices, not direct bone indices. + for (int j = 0; j < 4; j++) { + uint8_t idx = src.boneIndices[j]; + if (idx < model.boneLookupTable.size()) { + dst.boneIndices[j] = static_cast( + std::min(model.boneLookupTable[idx], 255)); + } else { + dst.boneIndices[j] = 0; + } + } dst.normal = src.normal; dst.texCoords = src.texCoords[0]; // Use first UV set dst.tangent = glm::vec4(1.0f, 0.0f, 0.0f, 1.0f); // default diff --git a/src/rendering/m2_renderer.cpp b/src/rendering/m2_renderer.cpp index d87a6844..921ccad7 100644 --- a/src/rendering/m2_renderer.cpp +++ b/src/rendering/m2_renderer.cpp @@ -1158,10 +1158,14 @@ bool M2Renderer::loadModel(const pipeline::M2Model& model, uint32_t modelId) { vertexData.push_back(w1); vertexData.push_back(w2); vertexData.push_back(w3); - vertexData.push_back(static_cast(std::min(v.boneIndices[0], uint8_t(127)))); - vertexData.push_back(static_cast(std::min(v.boneIndices[1], uint8_t(127)))); - vertexData.push_back(static_cast(std::min(v.boneIndices[2], uint8_t(127)))); - vertexData.push_back(static_cast(std::min(v.boneIndices[3], uint8_t(127)))); + // Remap bone indices through the bone lookup table. + // M2 vertex bone indices are lookup table indices, not direct bone indices. + for (int j = 0; j < 4; j++) { + uint8_t idx = v.boneIndices[j]; + uint16_t actual = (idx < model.boneLookupTable.size()) + ? model.boneLookupTable[idx] : 0; + vertexData.push_back(static_cast(std::min(actual, uint16_t(127)))); + } } // Upload vertex buffer to GPU