diff --git a/src/ObjLoading/XModel/Gltf/GltfLoader.cpp b/src/ObjLoading/XModel/Gltf/GltfLoader.cpp index 0adba623..79a1122d 100644 --- a/src/ObjLoading/XModel/Gltf/GltfLoader.cpp +++ b/src/ObjLoading/XModel/Gltf/GltfLoader.cpp @@ -12,10 +12,10 @@ #include #include #include -#include #include #include #include +#include #include using namespace gltf; @@ -316,7 +316,7 @@ namespace || !colorAccessor->GetFloatVec4(vertexIndex, vertex.color) || !uvAccessor->GetFloatVec2(vertexIndex, vertex.uv) || !jointsAccessor->GetUnsignedVec4(vertexIndex, joints) || !weightsAccessor->GetFloatVec4(vertexIndex, weights)) { - return false; + throw GltfLoadException("Failed to load vertex data from accessors"); } RhcToLhcCoordinates(vertex.coordinates); @@ -330,7 +330,16 @@ namespace if (std::abs(weights[i]) < std::numeric_limits::epsilon()) continue; - common.m_bone_weight_data.weights.emplace_back(joints[i], weights[i]); + assert(joints[i] < m_gltf_to_common_joint_index_lookup.size()); + if (joints[i] >= m_gltf_to_common_joint_index_lookup.size()) + { + throw GltfLoadException(std::format( + "Vertex weight referenced joint {} (there are only {} joints in skin)", joints[i], m_gltf_to_common_joint_index_lookup.size())); + } + + const auto xmodelBoneIndex = m_gltf_to_common_joint_index_lookup[joints[i]]; + + common.m_bone_weight_data.weights.emplace_back(xmodelBoneIndex, weights[i]); vertexWeights.weightCount++; } @@ -406,13 +415,12 @@ namespace return true; } - static std::optional GetRootNodeForSkin(const JsonRoot& jRoot, const JsonSkin& skin) + static std::vector GetRootNodesForSkin(const JsonRoot& jRoot, const JsonSkin& skin) { if (!jRoot.nodes || skin.joints.empty()) - return std::nullopt; + return {}; const auto jointCount = skin.joints.size(); - auto rootCount = jointCount; std::vector isRoot(jointCount, true); for (const auto joint : skin.joints) @@ -432,26 +440,23 @@ namespace if (isRoot[foundChildJointIndex]) { isRoot[foundChildJointIndex] = false; - rootCount--; } } } } } - if (rootCount != 1) - throw GltfLoadException("Skins must have exactly one common root node"); - + std::vector result; for (auto index = 0u; index < jointCount; index++) { if (isRoot[index]) - return skin.joints[index]; + result.emplace_back(skin.joints[index]); } - return std::nullopt; + return std::move(result); } - void ApplyNodeMatrixTRS(const JsonNode& node, float (&localOffsetRhc)[3], float (&localRotationRhc)[4], float (&scaleRhc)[3]) + void ApplyNodeMatrixTRS(const JsonNode& node, float (&localOffsetRhc)[3], float (&localRotationRhc)[4], float (&scaleRhc)[3]) const { const auto matrix = Eigen::Matrix4f({ {(*node.matrix)[0], (*node.matrix)[4], (*node.matrix)[8], (*node.matrix)[12]}, @@ -492,7 +497,7 @@ namespace scaleRhc[2] = matrix.block<3, 1>(0, 2).norm(); } - void ApplyNodeSeparateTRS(const JsonNode& node, float (&localOffsetRhc)[3], float (&localRotationRhc)[4], float (&scaleRhc)[3]) + void ApplyNodeSeparateTRS(const JsonNode& node, float (&localOffsetRhc)[3], float (&localRotationRhc)[4], float (&scaleRhc)[3]) const { if (node.translation) { @@ -612,13 +617,63 @@ namespace { if (!ConvertJoint( jRoot, skin, common, skinBoneOffset, childIndex, commonBoneOffset, globalTranslationEigenRhc, globalRotationEigenRhc, bone.scale)) + { return false; + } } } return true; } + void ReorderBonesForXModels(XModelCommon& common) + { + const auto boneCount = common.m_bones.size(); + m_gltf_to_common_joint_index_lookup.resize(boneCount); + auto reorderedBoneIndex = 0u; + + std::deque parentIndicesToTraverse; + for (auto boneIndex = 0u; boneIndex < boneCount; ++boneIndex) + { + const auto& bone = common.m_bones[boneIndex]; + if (!bone.parentIndex.has_value()) + { + parentIndicesToTraverse.emplace_back(boneIndex); + m_gltf_to_common_joint_index_lookup[boneIndex] = reorderedBoneIndex++; + } + } + + while (!parentIndicesToTraverse.empty()) + { + const auto parentIndex = parentIndicesToTraverse.front(); + parentIndicesToTraverse.pop_front(); + + for (auto boneIndex = 0u; boneIndex < boneCount; ++boneIndex) + { + const auto& bone = common.m_bones[boneIndex]; + if (bone.parentIndex.has_value() && *bone.parentIndex == parentIndex) + { + parentIndicesToTraverse.emplace_back(boneIndex); + m_gltf_to_common_joint_index_lookup[boneIndex] = reorderedBoneIndex++; + } + } + } + + assert(reorderedBoneIndex == boneCount); + + std::vector reorderedBones(boneCount); + for (size_t boneIndex = 0; boneIndex < boneCount; ++boneIndex) + { + auto& reorderedBone = reorderedBones[m_gltf_to_common_joint_index_lookup[boneIndex]]; + reorderedBone = std::move(common.m_bones[boneIndex]); + + if (reorderedBone.parentIndex.has_value()) + reorderedBone.parentIndex = m_gltf_to_common_joint_index_lookup[*reorderedBone.parentIndex]; + } + + common.m_bones = std::move(reorderedBones); + } + bool ConvertSkin(const JsonRoot& jRoot, const JsonSkin& skin, XModelCommon& common) { if (skin.joints.empty()) @@ -626,18 +681,27 @@ namespace if (!jRoot.nodes) return false; - const auto rootNode = GetRootNodeForSkin(jRoot, skin).value_or(skin.joints[0]); - const auto skinBoneOffset = static_cast(common.m_bones.size()); - common.m_bones.resize(skinBoneOffset + skin.joints.size()); + auto rootNodes = GetRootNodesForSkin(jRoot, skin); + if (rootNodes.empty()) + rootNodes.emplace_back(skin.joints[0]); - const Eigen::Vector3f defaultTranslation(0.0f, 0.0f, 0.0f); + // Only one skin per GLTF allowed, more would require more complex mapping and reordering + assert(common.m_bones.empty()); + common.m_bones.resize(skin.joints.size()); + + constexpr Eigen::Vector3f defaultTranslation(0.0f, 0.0f, 0.0f); const Eigen::Quaternionf defaultRotation(1.0f, 0.0f, 0.0f, 0.0f); constexpr float defaultScale[3]{1.0f, 1.0f, 1.0f}; - if (!ConvertJoint(jRoot, skin, common, skinBoneOffset, rootNode, std::nullopt, defaultTranslation, defaultRotation, defaultScale)) - return false; + for (const auto rootNode : rootNodes) + { + if (!ConvertJoint(jRoot, skin, common, 0, rootNode, std::nullopt, defaultTranslation, defaultRotation, defaultScale)) + return false; + } + ReorderBonesForXModels(common); common.CalculateBoneLocalsFromGlobals(); + return true; } @@ -832,7 +896,15 @@ namespace std::vector> m_buffer_views; std::vector> m_buffers; + // Old gltf support for OAT used bad formulas to calculate right-handed coordinate system rotations + // To make the fixed code be backwards compatible, old behaviour can be restored with this setting. bool m_bad_rotation_formulas; + + // We may need to reorder bones to account for the constraints of xmodels: + // Root bones have the lowest indices and the index of the parent of each bone must be lower than its own. + // The index in this vector is the joint index of the gltf. + // The value is the index in the common xmodel. + std::vector m_gltf_to_common_joint_index_lookup; }; } // namespace diff --git a/src/ObjLoading/XModel/LoaderXModel.cpp.template b/src/ObjLoading/XModel/LoaderXModel.cpp.template index b23fbd2b..018ada36 100644 --- a/src/ObjLoading/XModel/LoaderXModel.cpp.template +++ b/src/ObjLoading/XModel/LoaderXModel.cpp.template @@ -323,8 +323,12 @@ namespace { xmodel.parentList = m_memory.Alloc(xmodel.numBones - xmodel.numRootBones); - // For some reason Treyarch games allocate for a vec4 here. it is treated as a vec3 though? +#if defined(FEATURE_IW3) || defined(FEATURE_T5) || defined(FEATURE_T6) + // For some reason some games allocate for a vec4 here. it is treated as a vec3 though? xmodel.trans = m_memory.Alloc((xmodel.numBones - xmodel.numRootBones) * 4u); +#else + xmodel.trans = m_memory.Alloc((xmodel.numBones - xmodel.numRootBones) * 3u); +#endif xmodel.quats = m_memory.Alloc(xmodel.numBones - xmodel.numRootBones); } else @@ -373,7 +377,11 @@ namespace // Viewhands seem to have nulled trans for some reason? if (jXModel.type.value_or(JsonXModelType::RIGID) == JsonXModelType::VIEWHANDS) { +#if defined(FEATURE_IW3) || defined(FEATURE_T5) || defined(FEATURE_T6) memset(xmodel.trans, 0, sizeof(float) * 4 * (xmodel.numBones - xmodel.numRootBones)); +#else + memset(xmodel.trans, 0, sizeof(float) * 3 * (xmodel.numBones - xmodel.numRootBones)); +#endif } return true; diff --git a/src/ObjWriting/XModel/Gltf/GltfWriter.cpp b/src/ObjWriting/XModel/Gltf/GltfWriter.cpp index ab81bbc1..ecac06eb 100644 --- a/src/ObjWriting/XModel/Gltf/GltfWriter.cpp +++ b/src/ObjWriting/XModel/Gltf/GltfWriter.cpp @@ -187,10 +187,10 @@ namespace const auto meshCount = xmodel.m_objects.size(); for (auto meshIndex = 0u; meshIndex < meshCount; meshIndex++) - rootNode.children->push_back(m_first_mesh_node + meshIndex); + rootNode.children->emplace_back(m_first_mesh_node + meshIndex); - if (!xmodel.m_bones.empty()) - rootNode.children->push_back(m_first_bone_node); + for (auto rootBoneIndex = 0u; rootBoneIndex < m_root_bone_count; rootBoneIndex++) + rootNode.children->emplace_back(m_first_bone_node + rootBoneIndex); m_root_node = static_cast(gltf.nodes->size()); gltf.nodes->emplace_back(std::move(rootNode)); @@ -303,6 +303,7 @@ namespace const auto boneCount = common.m_bones.size(); m_first_bone_node = static_cast(gltf.nodes->size()); + m_root_bone_count = 0; for (auto boneIndex = 0u; boneIndex < boneCount; boneIndex++) { JsonNode boneNode; @@ -335,6 +336,11 @@ namespace translation = inverseParentRotation * translation; rotation = inverseParentRotation * rotation; } + else + { + assert(m_root_bone_count == boneIndex); + m_root_bone_count++; + } rotation.normalize(); boneNode.name = bone.name; @@ -745,6 +751,7 @@ namespace unsigned m_first_mesh_node = 0u; unsigned m_root_node = 0u; unsigned m_first_bone_node = 0u; + unsigned m_root_bone_count = 0u; unsigned m_position_accessor = 0u; unsigned m_normal_accessor = 0u; unsigned m_color_accessor = 0u;