From 0cc47ce12cc9ac3520828c37dcffd088de46eec8 Mon Sep 17 00:00:00 2001 From: Jan Laupetin Date: Sat, 9 Aug 2025 13:36:09 +0100 Subject: [PATCH 1/9] fix: not being able to write xmodel_export with omitted default armature --- src/ObjWriting/XModel/Export/XModelExportWriter.cpp | 12 ++++++------ src/ObjWriting/XModel/XModelDumper.cpp.template | 10 ++++++++-- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/ObjWriting/XModel/Export/XModelExportWriter.cpp b/src/ObjWriting/XModel/Export/XModelExportWriter.cpp index 45f4a021..b8531a6d 100644 --- a/src/ObjWriting/XModel/Export/XModelExportWriter.cpp +++ b/src/ObjWriting/XModel/Export/XModelExportWriter.cpp @@ -18,16 +18,16 @@ protected: auto vertexOffset = 0u; for (const auto& vertex : xmodel.m_vertices) { - XModelVertexBoneWeights weights{0, 0}; + XModelVertexBoneWeights weights{.weightOffset = 0, .weightCount = 0}; if (vertexOffset < xmodel.m_vertex_bone_weights.size()) weights = xmodel.m_vertex_bone_weights[vertexOffset]; - m_vertex_merger.Add(VertexMergerPos{vertex.coordinates[0], - vertex.coordinates[1], - vertex.coordinates[2], - &xmodel.m_bone_weight_data.weights[weights.weightOffset], - weights.weightCount}); + m_vertex_merger.Add(VertexMergerPos{.x = vertex.coordinates[0], + .y = vertex.coordinates[1], + .z = vertex.coordinates[2], + .weights = &xmodel.m_bone_weight_data.weights[weights.weightOffset], + .weightCount = weights.weightCount}); vertexOffset++; } diff --git a/src/ObjWriting/XModel/XModelDumper.cpp.template b/src/ObjWriting/XModel/XModelDumper.cpp.template index c1d75920..a79a4ef2 100644 --- a/src/ObjWriting/XModel/XModelDumper.cpp.template +++ b/src/ObjWriting/XModel/XModelDumper.cpp.template @@ -532,6 +532,12 @@ namespace } } + bool CanOmitDefaultArmature() + { + return ObjWriting::Configuration.ModelOutputFormat != ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_EXPORT + && ObjWriting::Configuration.ModelOutputFormat != ObjWriting::Configuration_t::ModelOutputFormat_e::XMODEL_BIN; + } + void PopulateXModelWriter(XModelCommon& out, const AssetDumpingContext& context, const unsigned lod, const XModel* model) { DistinctMapper materialMapper(model->numsurfs); @@ -542,8 +548,8 @@ namespace AddXModelObjects(out, model, lod, materialMapper); AddXModelVertices(out, model, lod); AddXModelFaces(out, model, lod); - - if (!HasDefaultArmature(model, lod)) + + if (!CanOmitDefaultArmature() || !HasDefaultArmature(model, lod)) { AddXModelBones(out, context, model); AddXModelVertexBoneWeights(out, model, lod); From 98fe3ed6f7d6c81970c53f4bf5007778407d6ce1 Mon Sep 17 00:00:00 2001 From: Jan Laupetin Date: Sat, 9 Aug 2025 13:36:41 +0100 Subject: [PATCH 2/9] fix: bad gltf math on inverse bind matrix in export --- src/ObjWriting/XModel/Gltf/GltfWriter.cpp | 93 ++++++++++++++++++++--- 1 file changed, 81 insertions(+), 12 deletions(-) diff --git a/src/ObjWriting/XModel/Gltf/GltfWriter.cpp b/src/ObjWriting/XModel/Gltf/GltfWriter.cpp index bec9c598..33cc1e71 100644 --- a/src/ObjWriting/XModel/Gltf/GltfWriter.cpp +++ b/src/ObjWriting/XModel/Gltf/GltfWriter.cpp @@ -25,6 +25,66 @@ namespace float uv[2]; }; + void LhcToRhcCoordinates(float (&coords)[3]) + { + const float two[3]{coords[0], coords[1], coords[2]}; + + coords[0] = two[0]; + coords[1] = two[2]; + coords[2] = -two[1]; + } + + void LhcToRhcCoordinates(std::array& coords) + { + float two[3]{coords[0], coords[1], coords[2]}; + LhcToRhcCoordinates(two); + coords[0] = two[0]; + coords[1] = two[1]; + coords[2] = two[2]; + } + + void LhcToRhcQuaternion(float (&quat)[4]) + { + const float two[4]{quat[0], quat[1], quat[2], quat[3]}; + + quat[0] = two[0]; + quat[1] = two[2]; + quat[2] = -two[1]; + quat[3] = two[3]; + } + + void LhcToRhcQuaternion(std::array& quat) + { + float two[4]{quat[0], quat[1], quat[2], quat[3]}; + LhcToRhcQuaternion(two); + quat[0] = two[0]; + quat[1] = two[1]; + quat[2] = two[2]; + quat[3] = two[3]; + } + + void LhcToRhcIndices(unsigned short* indices) + { + const unsigned short two[3]{indices[0], indices[1], indices[2]}; + + indices[0] = two[2]; + indices[1] = two[1]; + indices[2] = two[0]; + } + + void LhcToRhcMatrix(Eigen::Matrix4f& matrix) + { + const Eigen::Matrix4f convertMatrix({ + {1.0, 0.0, 0.0, 0.0}, + {0.0, 0.0, 1.0, 0.0}, + {0.0, -1.0, 0.0, 0.0}, + {0.0, 0.0, 0.0, 1.0} + }); + + const auto result = convertMatrix * matrix; + matrix = result; + } + class GltfWriterImpl final : public gltf::Writer { public: @@ -256,8 +316,12 @@ namespace rotation.normalize(); boneNode.name = bone.name; - boneNode.translation = std::to_array({translation.x(), translation.z(), -translation.y()}); - boneNode.rotation = std::to_array({rotation.x(), rotation.z(), -rotation.y(), rotation.w()}); + + boneNode.translation = std::to_array({translation.x(), translation.y(), translation.z()}); + LhcToRhcCoordinates(*boneNode.translation); + + boneNode.rotation = std::to_array({rotation.x(), rotation.y(), rotation.z(), rotation.w()}); + LhcToRhcQuaternion(*boneNode.rotation); std::vector children; for (auto maybeChildIndex = 0u; maybeChildIndex < boneCount; maybeChildIndex++) @@ -471,8 +535,9 @@ namespace auto* vertex = reinterpret_cast(&bufferData[currentBufferOffset]); vertex->coordinates[0] = commonVertex.coordinates[0]; - vertex->coordinates[1] = commonVertex.coordinates[2]; - vertex->coordinates[2] = -commonVertex.coordinates[1]; + vertex->coordinates[1] = commonVertex.coordinates[1]; + vertex->coordinates[2] = commonVertex.coordinates[2]; + LhcToRhcCoordinates(vertex->coordinates); minPosition[0] = std::min(minPosition[0], vertex->coordinates[0]); minPosition[1] = std::min(minPosition[1], vertex->coordinates[1]); @@ -482,8 +547,9 @@ namespace maxPosition[2] = std::max(maxPosition[2], vertex->coordinates[2]); vertex->normal[0] = commonVertex.normal[0]; - vertex->normal[1] = commonVertex.normal[2]; - vertex->normal[2] = -commonVertex.normal[1]; + vertex->normal[1] = commonVertex.normal[1]; + vertex->normal[2] = commonVertex.normal[2]; + LhcToRhcCoordinates(vertex->normal); vertex->uv[0] = commonVertex.uv[0]; vertex->uv[1] = commonVertex.uv[1]; @@ -531,11 +597,13 @@ namespace auto* inverseBindMatrixData = reinterpret_cast(&bufferData[currentBufferOffset]); for (const auto& bone : xmodel.m_bones) { - const auto translation = Eigen::Translation3f(bone.globalOffset[0], bone.globalOffset[2], -bone.globalOffset[1]); - const auto rotation = Eigen::Quaternionf(bone.globalRotation.w, bone.globalRotation.x, bone.globalRotation.z, -bone.globalRotation.y); + const auto translation = Eigen::Translation3f(bone.globalOffset[0], bone.globalOffset[1], bone.globalOffset[2]); + const auto rotation = Eigen::Quaternionf(bone.globalRotation.w, bone.globalRotation.x, bone.globalRotation.y, bone.globalRotation.z); + const auto bindMatrixTransform = translation * rotation; + auto bindMatrix = bindMatrixTransform.matrix(); - const auto bindMatrix = (translation * rotation); - const auto inverseBindMatrix = bindMatrix.matrix().inverse(); + LhcToRhcMatrix(bindMatrix); + const auto inverseBindMatrix = bindMatrix.inverse(); // GLTF matrix is column major inverseBindMatrixData[0] = inverseBindMatrix(0, 0); @@ -565,9 +633,10 @@ namespace for (const auto& face : object.m_faces) { auto* faceIndices = reinterpret_cast(&bufferData[currentBufferOffset]); - faceIndices[0] = static_cast(face.vertexIndex[2]); + faceIndices[0] = static_cast(face.vertexIndex[0]); faceIndices[1] = static_cast(face.vertexIndex[1]); - faceIndices[2] = static_cast(face.vertexIndex[0]); + faceIndices[2] = static_cast(face.vertexIndex[2]); + LhcToRhcIndices(faceIndices); currentBufferOffset += sizeof(unsigned short) * 3u; } From 84409a975adc903f6d76f9eebeb2eba8f931df59 Mon Sep 17 00:00:00 2001 From: Jan Laupetin Date: Sat, 9 Aug 2025 17:01:08 +0100 Subject: [PATCH 3/9] fix: bad gltf node rotations in export --- src/ObjWriting/XModel/Gltf/GltfWriter.cpp | 62 ++++++++++------------- 1 file changed, 28 insertions(+), 34 deletions(-) diff --git a/src/ObjWriting/XModel/Gltf/GltfWriter.cpp b/src/ObjWriting/XModel/Gltf/GltfWriter.cpp index 33cc1e71..b3576ed0 100644 --- a/src/ObjWriting/XModel/Gltf/GltfWriter.cpp +++ b/src/ObjWriting/XModel/Gltf/GltfWriter.cpp @@ -10,6 +10,7 @@ #include #include +#include using namespace gltf; using namespace nlohmann; @@ -34,33 +35,17 @@ namespace coords[2] = -two[1]; } - void LhcToRhcCoordinates(std::array& coords) - { - float two[3]{coords[0], coords[1], coords[2]}; - LhcToRhcCoordinates(two); - coords[0] = two[0]; - coords[1] = two[1]; - coords[2] = two[2]; - } - void LhcToRhcQuaternion(float (&quat)[4]) { - const float two[4]{quat[0], quat[1], quat[2], quat[3]}; + Eigen::Quaternionf eigenQuat(quat[3], quat[0], quat[1], quat[2]); + const Eigen::Quaternionf eigenRotationQuat(Eigen::AngleAxisf(-std::numbers::pi_v / 2.f, Eigen::Vector3f::UnitX())); - quat[0] = two[0]; - quat[1] = two[2]; - quat[2] = -two[1]; - quat[3] = two[3]; - } + eigenQuat = eigenRotationQuat * eigenQuat; - void LhcToRhcQuaternion(std::array& quat) - { - float two[4]{quat[0], quat[1], quat[2], quat[3]}; - LhcToRhcQuaternion(two); - quat[0] = two[0]; - quat[1] = two[1]; - quat[2] = two[2]; - quat[3] = two[3]; + quat[0] = eigenQuat.x(); + quat[1] = eigenQuat.y(); + quat[2] = eigenQuat.z(); + quat[3] = eigenQuat.w(); } void LhcToRhcIndices(unsigned short* indices) @@ -298,18 +283,30 @@ namespace JsonNode boneNode; const auto& bone = common.m_bones[boneIndex]; - Eigen::Vector3f translation(bone.globalOffset[0], bone.globalOffset[1], bone.globalOffset[2]); - Eigen::Quaternionf rotation(bone.globalRotation.w, bone.globalRotation.x, bone.globalRotation.y, bone.globalRotation.z); + float globalTranslationData[3]{bone.globalOffset[0], bone.globalOffset[1], bone.globalOffset[2]}; + LhcToRhcCoordinates(globalTranslationData); + Eigen::Vector3f translation(globalTranslationData[0], globalTranslationData[1], globalTranslationData[2]); + + float globalRotationData[4]{bone.globalRotation.x, bone.globalRotation.y, bone.globalRotation.z, bone.globalRotation.w}; + LhcToRhcQuaternion(globalRotationData); + Eigen::Quaternionf rotation(globalRotationData[3], globalRotationData[0], globalRotationData[1], globalRotationData[2]); + if (bone.parentIndex) { const auto& parentBone = common.m_bones[*bone.parentIndex]; - const auto inverseParentRotation = - Eigen::Quaternionf(parentBone.globalRotation.w, parentBone.globalRotation.x, parentBone.globalRotation.y, parentBone.globalRotation.z) - .normalized() - .inverse() - .normalized(); - translation -= Eigen::Vector3f(parentBone.globalOffset[0], parentBone.globalOffset[1], parentBone.globalOffset[2]); + float parentGlobalTranslationData[3]{parentBone.globalOffset[0], parentBone.globalOffset[1], parentBone.globalOffset[2]}; + LhcToRhcCoordinates(parentGlobalTranslationData); + const Eigen::Vector3f parentTranslation(parentGlobalTranslationData[0], parentGlobalTranslationData[1], parentGlobalTranslationData[2]); + + float parentGlobalRotationData[4]{ + parentBone.globalRotation.x, parentBone.globalRotation.y, parentBone.globalRotation.z, parentBone.globalRotation.w}; + LhcToRhcQuaternion(parentGlobalRotationData); + const Eigen::Quaternionf parentRotation( + parentGlobalRotationData[3], parentGlobalRotationData[0], parentGlobalRotationData[1], parentGlobalRotationData[2]); + const auto inverseParentRotation = parentRotation.inverse(); + + translation -= parentTranslation; translation = inverseParentRotation * translation; rotation = inverseParentRotation * rotation; } @@ -318,10 +315,7 @@ namespace boneNode.name = bone.name; boneNode.translation = std::to_array({translation.x(), translation.y(), translation.z()}); - LhcToRhcCoordinates(*boneNode.translation); - boneNode.rotation = std::to_array({rotation.x(), rotation.y(), rotation.z(), rotation.w()}); - LhcToRhcQuaternion(*boneNode.rotation); std::vector children; for (auto maybeChildIndex = 0u; maybeChildIndex < boneCount; maybeChildIndex++) From 3a5cfc01d94c926143e6c2a22eae79606593520c Mon Sep 17 00:00:00 2001 From: Jan Laupetin Date: Sun, 10 Aug 2025 13:01:05 +0100 Subject: [PATCH 4/9] fix: make ObjLoader load updated gltf bone data --- src/ObjCommon.lua | 1 + src/ObjCommon/XModel/XModelCommon.cpp | 39 +++++ src/ObjCommon/XModel/XModelCommon.h | 2 + src/ObjLoading/XModel/Gltf/GltfLoader.cpp | 193 ++++++++++++++-------- 4 files changed, 168 insertions(+), 67 deletions(-) diff --git a/src/ObjCommon.lua b/src/ObjCommon.lua index d1e28826..93bf6d44 100644 --- a/src/ObjCommon.lua +++ b/src/ObjCommon.lua @@ -54,4 +54,5 @@ function ObjCommon:project() self:include(includes) Utils:include(includes) + eigen:include(includes) end diff --git a/src/ObjCommon/XModel/XModelCommon.cpp b/src/ObjCommon/XModel/XModelCommon.cpp index f44e7165..083f845c 100644 --- a/src/ObjCommon/XModel/XModelCommon.cpp +++ b/src/ObjCommon/XModel/XModelCommon.cpp @@ -1,5 +1,9 @@ #include "XModelCommon.h" +#pragma warning(push, 0) +#include +#pragma warning(pop) + #include #include #include @@ -48,6 +52,41 @@ void XModelMaterial::ApplyDefaults() phong = -1; } +void XModelCommon::CalculateBoneLocalsFromGlobals() +{ + const auto boneCount = m_bones.size(); + for (auto boneIndex = 0u; boneIndex < boneCount; boneIndex++) + { + auto& bone = m_bones[boneIndex]; + + Eigen::Vector3f translation(bone.globalOffset[0], bone.globalOffset[1], bone.globalOffset[2]); + Eigen::Quaternionf rotation(bone.globalRotation.w, bone.globalRotation.x, bone.globalRotation.y, bone.globalRotation.z); + + if (bone.parentIndex) + { + assert(boneIndex > *bone.parentIndex); + const auto& parentBone = m_bones[*bone.parentIndex]; + + const Eigen::Vector3f parentTranslation(parentBone.globalOffset[0], parentBone.globalOffset[1], parentBone.globalOffset[2]); + const Eigen::Quaternionf parentRotation( + parentBone.globalRotation.w, parentBone.globalRotation.x, parentBone.globalRotation.y, parentBone.globalRotation.z); + const auto inverseParentRotation = parentRotation.inverse(); + + translation -= parentTranslation; + translation = inverseParentRotation * translation; + rotation = inverseParentRotation * rotation; + } + + bone.localOffset[0] = translation.x(); + bone.localOffset[1] = translation.y(); + bone.localOffset[2] = translation.z(); + bone.localRotation.x = rotation.x(); + bone.localRotation.y = rotation.y(); + bone.localRotation.z = rotation.z(); + bone.localRotation.w = rotation.w(); + } +} + bool operator==(const VertexMergerPos& lhs, const VertexMergerPos& rhs) { const auto coordinatesMatch = std::fabs(lhs.x - rhs.x) < std::numeric_limits::epsilon() diff --git a/src/ObjCommon/XModel/XModelCommon.h b/src/ObjCommon/XModel/XModelCommon.h index c5f0843c..46ebf3bb 100644 --- a/src/ObjCommon/XModel/XModelCommon.h +++ b/src/ObjCommon/XModel/XModelCommon.h @@ -111,6 +111,8 @@ struct XModelCommon std::vector m_vertices; std::vector m_vertex_bone_weights; XModelVertexBoneWeightCollection m_bone_weight_data; + + void CalculateBoneLocalsFromGlobals(); }; struct VertexMergerPos diff --git a/src/ObjLoading/XModel/Gltf/GltfLoader.cpp b/src/ObjLoading/XModel/Gltf/GltfLoader.cpp index 8ffe97a7..ab6c6885 100644 --- a/src/ObjLoading/XModel/Gltf/GltfLoader.cpp +++ b/src/ObjLoading/XModel/Gltf/GltfLoader.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include using namespace gltf; @@ -40,6 +41,59 @@ namespace return !(lhs == rhs); } }; + + void RhcToLhcCoordinates(float (&coords)[3]) + { + const float two[3]{coords[0], coords[1], coords[2]}; + + coords[0] = two[0]; + coords[1] = -two[2]; + coords[2] = two[1]; + } + + void RhcToLhcScale(float (&coords)[3]) + { + const float two[3]{coords[0], coords[1], coords[2]}; + + coords[0] = two[0]; + coords[1] = two[2]; + coords[2] = two[1]; + } + + void RhcToLhcQuaternion(XModelQuaternion& quat) + { + Eigen::Quaternionf eigenQuat(quat.w, quat.x, quat.y, quat.z); + const Eigen::Quaternionf eigenRotationQuat(Eigen::AngleAxisf(std::numbers::pi_v / 2.f, Eigen::Vector3f::UnitX())); + + eigenQuat = eigenRotationQuat * eigenQuat; + + quat.x = eigenQuat.x(); + quat.y = eigenQuat.y(); + quat.z = eigenQuat.z(); + quat.w = eigenQuat.w(); + } + + void RhcToLhcIndices(unsigned (&indices)[3]) + { + const unsigned two[3]{indices[0], indices[1], indices[2]}; + + indices[0] = two[2]; + indices[1] = two[1]; + indices[2] = two[0]; + } + + void RhcToLhcMatrix(Eigen::Matrix4f& matrix) + { + const Eigen::Matrix4f convertMatrix({ + {1.0, 0.0, 0.0, 0.0}, + {0.0, 0.0, -1.0, 0.0}, + {0.0, 1.0, 0.0, 0.0}, + {0.0, 0.0, 0.0, 1.0} + }); + + const auto result = convertMatrix * matrix; + matrix = result; + } } // namespace template<> struct std::hash @@ -268,21 +322,15 @@ namespace unsigned joints[4]; float weights[4]; - float coordinates[3]; - float normal[3]; - if (!positionAccessor->GetFloatVec3(vertexIndex, coordinates) || !normalAccessor->GetFloatVec3(vertexIndex, normal) + if (!positionAccessor->GetFloatVec3(vertexIndex, vertex.coordinates) || !normalAccessor->GetFloatVec3(vertexIndex, vertex.normal) || !colorAccessor->GetFloatVec4(vertexIndex, vertex.color) || !uvAccessor->GetFloatVec2(vertexIndex, vertex.uv) || !jointsAccessor->GetUnsignedVec4(vertexIndex, joints) || !weightsAccessor->GetFloatVec4(vertexIndex, weights)) { return false; } - vertex.coordinates[0] = coordinates[0]; - vertex.coordinates[1] = -coordinates[2]; - vertex.coordinates[2] = coordinates[1]; - vertex.normal[0] = normal[0]; - vertex.normal[1] = -normal[2]; - vertex.normal[2] = normal[1]; + RhcToLhcCoordinates(vertex.coordinates); + RhcToLhcCoordinates(vertex.normal); common.m_vertices.emplace_back(vertex); @@ -351,11 +399,12 @@ namespace { return false; } + RhcToLhcIndices(indices); object.m_faces.emplace_back(XModelFace{ - vertexOffset + indices[2], - vertexOffset + indices[1], vertexOffset + indices[0], + vertexOffset + indices[1], + vertexOffset + indices[2], }); } @@ -412,7 +461,7 @@ namespace return std::nullopt; } - static void ApplyNodeMatrixTRS(XModelBone& bone, const JsonNode& node) + static void ApplyNodeMatrixTRS(const JsonNode& node, float (&localOffsetRhc)[3], float (&localRotationRhc)[4], float (&scaleRhc)[3]) { const auto matrix = Eigen::Matrix4f({ {(*node.matrix)[0], (*node.matrix)[4], (*node.matrix)[8], (*node.matrix)[12]}, @@ -423,63 +472,63 @@ namespace Eigen::Affine3f transform(matrix); const auto translation = transform.translation(); - bone.localOffset[0] = translation.x(); - bone.localOffset[1] = -translation.z(); - bone.localOffset[2] = translation.y(); + localOffsetRhc[0] = translation.x(); + localOffsetRhc[1] = translation.y(); + localOffsetRhc[2] = translation.z(); const auto rotation = transform.rotation(); const auto rotationQuat = Eigen::Quaternionf(rotation); - bone.localRotation.x = rotationQuat.x(); - bone.localRotation.y = -rotationQuat.z(); - bone.localRotation.z = rotationQuat.y(); - bone.localRotation.w = rotationQuat.w(); + localRotationRhc[0] = rotationQuat.x(); + localRotationRhc[1] = rotationQuat.y(); + localRotationRhc[2] = rotationQuat.z(); + localRotationRhc[3] = rotationQuat.w(); - bone.scale[0] = matrix.block<3, 1>(0, 0).norm(); - bone.scale[1] = matrix.block<3, 1>(0, 1).norm(); - bone.scale[2] = matrix.block<3, 1>(0, 2).norm(); + scaleRhc[0] = matrix.block<3, 1>(0, 0).norm(); + scaleRhc[1] = matrix.block<3, 1>(0, 1).norm(); + scaleRhc[2] = matrix.block<3, 1>(0, 2).norm(); } - static void ApplyNodeSeparateTRS(XModelBone& bone, const JsonNode& node) + static void ApplyNodeSeparateTRS(const JsonNode& node, float (&localOffsetRhc)[3], float (&localRotationRhc)[4], float (&scaleRhc)[3]) { if (node.translation) { - bone.localOffset[0] = (*node.translation)[0]; - bone.localOffset[1] = -(*node.translation)[2]; - bone.localOffset[2] = (*node.translation)[1]; + localOffsetRhc[0] = (*node.translation)[0]; + localOffsetRhc[1] = (*node.translation)[1]; + localOffsetRhc[2] = (*node.translation)[2]; } else { - bone.localOffset[0] = 0.0f; - bone.localOffset[1] = 0.0f; - bone.localOffset[2] = 0.0f; + localOffsetRhc[0] = 0.0f; + localOffsetRhc[1] = 0.0f; + localOffsetRhc[2] = 0.0f; } if (node.rotation) { - bone.localRotation.x = (*node.rotation)[0]; - bone.localRotation.y = -(*node.rotation)[2]; - bone.localRotation.z = (*node.rotation)[1]; - bone.localRotation.w = (*node.rotation)[3]; + localRotationRhc[0] = (*node.rotation)[0]; + localRotationRhc[1] = (*node.rotation)[1]; + localRotationRhc[2] = (*node.rotation)[2]; + localRotationRhc[3] = (*node.rotation)[3]; } else { - bone.localRotation.x = 0.0f; - bone.localRotation.y = 0.0f; - bone.localRotation.z = 0.0f; - bone.localRotation.w = 1.0f; + localRotationRhc[0] = 0.0f; + localRotationRhc[1] = 0.0f; + localRotationRhc[2] = 0.0f; + localRotationRhc[3] = 1.0f; } if (node.scale) { - bone.scale[0] = (*node.scale)[0]; - bone.scale[1] = (*node.scale)[1]; - bone.scale[2] = (*node.scale)[2]; + scaleRhc[0] = (*node.scale)[0]; + scaleRhc[1] = (*node.scale)[1]; + scaleRhc[2] = (*node.scale)[2]; } else { - bone.scale[0] = 1.0f; - bone.scale[1] = 1.0f; - bone.scale[2] = 1.0f; + scaleRhc[0] = 1.0f; + scaleRhc[1] = 1.0f; + scaleRhc[2] = 1.0f; } } @@ -489,8 +538,8 @@ namespace const unsigned skinBoneOffset, const unsigned nodeIndex, const std::optional parentIndex, - const float (&parentOffset)[3], - const XModelQuaternion& parentRotation, + const Eigen::Vector3f& parentTranslationEigenRhc, + const Eigen::Quaternionf& parentRotationEigenRhc, const float (&parentScale)[3]) { if (!jRoot.nodes || nodeIndex >= jRoot.nodes->size()) @@ -507,36 +556,42 @@ namespace bone.name = node.name.value_or(std::string()); bone.parentIndex = parentIndex; + float localOffsetRhc[3]; + float localRotationRhc[4]; + float localScaleRhc[3]; if (node.matrix) - ApplyNodeMatrixTRS(bone, node); + ApplyNodeMatrixTRS(node, localOffsetRhc, localRotationRhc, localScaleRhc); else - ApplyNodeSeparateTRS(bone, node); + ApplyNodeSeparateTRS(node, localOffsetRhc, localRotationRhc, localScaleRhc); - bone.scale[0] *= parentScale[0]; - bone.scale[1] *= parentScale[1]; - bone.scale[2] *= parentScale[2]; + bone.scale[0] = localScaleRhc[0] * parentScale[0]; + bone.scale[1] = localScaleRhc[1] * parentScale[1]; + bone.scale[2] = localScaleRhc[2] * parentScale[2]; + RhcToLhcScale(bone.scale); - const auto localRotationEigen = Eigen::Quaternionf(bone.localRotation.w, bone.localRotation.x, bone.localRotation.y, bone.localRotation.z); - const auto parentRotationEigen = Eigen::Quaternionf(parentRotation.w, parentRotation.x, parentRotation.y, parentRotation.z); - const auto globalRotationEigen = (parentRotationEigen * localRotationEigen).normalized(); + const Eigen::Vector3f localTranslationEigen(localOffsetRhc[0], localOffsetRhc[1], localOffsetRhc[2]); + const Eigen::Quaternionf localRotationEigen(localRotationRhc[3], localRotationRhc[0], localRotationRhc[1], localRotationRhc[2]); - const Eigen::Vector3f localTranslationEigen(bone.localOffset[0], bone.localOffset[1], bone.localOffset[2]); - const Eigen::Vector3f parentTranslationEigen(parentOffset[0], parentOffset[1], parentOffset[2]); - const auto globalTranslationEigen = (parentRotationEigen * localTranslationEigen) + parentTranslationEigen; - bone.globalOffset[0] = globalTranslationEigen.x(); - bone.globalOffset[1] = globalTranslationEigen.y(); - bone.globalOffset[2] = globalTranslationEigen.z(); + const Eigen::Quaternionf globalRotationEigenRhc((parentRotationEigenRhc * localRotationEigen).normalized()); + const Eigen::Vector3f globalTranslationEigenRhc((parentRotationEigenRhc * localTranslationEigen) + parentTranslationEigenRhc); - bone.globalRotation.x = globalRotationEigen.x(); - bone.globalRotation.y = globalRotationEigen.y(); - bone.globalRotation.z = globalRotationEigen.z(); - bone.globalRotation.w = globalRotationEigen.w(); + bone.globalOffset[0] = globalTranslationEigenRhc.x(); + bone.globalOffset[1] = globalTranslationEigenRhc.y(); + bone.globalOffset[2] = globalTranslationEigenRhc.z(); + RhcToLhcCoordinates(bone.globalOffset); + + bone.globalRotation.x = globalRotationEigenRhc.x(); + bone.globalRotation.y = globalRotationEigenRhc.y(); + bone.globalRotation.z = globalRotationEigenRhc.z(); + bone.globalRotation.w = globalRotationEigenRhc.w(); + RhcToLhcQuaternion(bone.globalRotation); if (node.children) { for (const auto childIndex : *node.children) { - if (!ConvertJoint(jRoot, skin, common, skinBoneOffset, childIndex, commonBoneOffset, bone.globalOffset, bone.globalRotation, bone.scale)) + if (!ConvertJoint( + jRoot, skin, common, skinBoneOffset, childIndex, commonBoneOffset, globalTranslationEigenRhc, globalRotationEigenRhc, bone.scale)) return false; } } @@ -555,11 +610,15 @@ namespace const auto skinBoneOffset = static_cast(common.m_bones.size()); common.m_bones.resize(skinBoneOffset + skin.joints.size()); - constexpr float defaultTranslation[3]{0.0f, 0.0f, 0.0f}; - constexpr XModelQuaternion defaultRotation{.x = 0.0f, .y = 0.0f, .z = 0.0f, .w = 1.0f}; + const 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}; - return ConvertJoint(jRoot, skin, common, skinBoneOffset, rootNode, std::nullopt, defaultTranslation, defaultRotation, defaultScale); + if (!ConvertJoint(jRoot, skin, common, skinBoneOffset, rootNode, std::nullopt, defaultTranslation, defaultRotation, defaultScale)) + return false; + + common.CalculateBoneLocalsFromGlobals(); + return true; } void ConvertObjects(const JsonRoot& jRoot, XModelCommon& common) From 3fb887b5dde0ac42e067f4adbab576780d6a309d Mon Sep 17 00:00:00 2001 From: Jan Laupetin Date: Sun, 10 Aug 2025 13:18:28 +0100 Subject: [PATCH 5/9] fix: compilation --- src/ObjCommon/XModel/XModelCommon.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ObjCommon/XModel/XModelCommon.cpp b/src/ObjCommon/XModel/XModelCommon.cpp index 083f845c..3a7fd151 100644 --- a/src/ObjCommon/XModel/XModelCommon.cpp +++ b/src/ObjCommon/XModel/XModelCommon.cpp @@ -1,9 +1,13 @@ #include "XModelCommon.h" #pragma warning(push, 0) +// clang-format off: Order of includes is important +#include // Eigen uses std::bit_cast without including header themselves... #include +// clang-format on #pragma warning(pop) +#include #include #include #include From 1b6c58d843f6b5f13154f3ca01c75c0b6ee02e7f Mon Sep 17 00:00:00 2001 From: Jan Laupetin Date: Sun, 7 Sep 2025 19:29:32 +0100 Subject: [PATCH 6/9] chore: update minor code smells in GltfLoader --- src/ObjLoading/XModel/Gltf/GltfLoader.cpp | 108 ++++++++++------------ 1 file changed, 49 insertions(+), 59 deletions(-) diff --git a/src/ObjLoading/XModel/Gltf/GltfLoader.cpp b/src/ObjLoading/XModel/Gltf/GltfLoader.cpp index ab6c6885..fdccaf89 100644 --- a/src/ObjLoading/XModel/Gltf/GltfLoader.cpp +++ b/src/ObjLoading/XModel/Gltf/GltfLoader.cpp @@ -23,23 +23,24 @@ namespace { struct AccessorsForVertex { - unsigned positionAccessor; - std::optional normalAccessor; - std::optional colorAccessor; - std::optional uvAccessor; - std::optional jointsAccessor; - std::optional weightsAccessor; - friend bool operator==(const AccessorsForVertex& lhs, const AccessorsForVertex& rhs) { - return lhs.positionAccessor == rhs.positionAccessor && lhs.normalAccessor == rhs.normalAccessor && lhs.colorAccessor == rhs.colorAccessor - && lhs.uvAccessor == rhs.uvAccessor && lhs.jointsAccessor == rhs.jointsAccessor && lhs.weightsAccessor == rhs.weightsAccessor; + return lhs.m_position_accessor == rhs.m_position_accessor && lhs.m_normal_accessor == rhs.m_normal_accessor + && lhs.m_color_accessor == rhs.m_color_accessor && lhs.m_uv_accessor == rhs.m_uv_accessor && lhs.m_joints_accessor == rhs.m_joints_accessor + && lhs.m_weights_accessor == rhs.m_weights_accessor; } friend bool operator!=(const AccessorsForVertex& lhs, const AccessorsForVertex& rhs) { return !(lhs == rhs); } + + unsigned m_position_accessor; + std::optional m_normal_accessor; + std::optional m_color_accessor; + std::optional m_uv_accessor; + std::optional m_joints_accessor; + std::optional m_weights_accessor; }; void RhcToLhcCoordinates(float (&coords)[3]) @@ -81,19 +82,6 @@ namespace indices[1] = two[1]; indices[2] = two[0]; } - - void RhcToLhcMatrix(Eigen::Matrix4f& matrix) - { - const Eigen::Matrix4f convertMatrix({ - {1.0, 0.0, 0.0, 0.0}, - {0.0, 0.0, -1.0, 0.0}, - {0.0, 1.0, 0.0, 0.0}, - {0.0, 0.0, 0.0, 1.0} - }); - - const auto result = convertMatrix * matrix; - matrix = result; - } } // namespace template<> struct std::hash @@ -101,12 +89,12 @@ template<> struct std::hash std::size_t operator()(const AccessorsForVertex& v) const noexcept { std::size_t seed = 0x7E42C0E6; - seed ^= (seed << 6) + (seed >> 2) + 0x47B15429 + static_cast(v.positionAccessor); - seed ^= (seed << 6) + (seed >> 2) + 0x66847B5C + std::hash>()(v.normalAccessor); - seed ^= (seed << 6) + (seed >> 2) + 0x77399D60 + std::hash>()(v.colorAccessor); - seed ^= (seed << 6) + (seed >> 2) + 0x477AF9AB + std::hash>()(v.uvAccessor); - seed ^= (seed << 6) + (seed >> 2) + 0x4421B4D9 + std::hash>()(v.jointsAccessor); - seed ^= (seed << 6) + (seed >> 2) + 0x13C2EBA1 + std::hash>()(v.weightsAccessor); + seed ^= (seed << 6) + (seed >> 2) + 0x47B15429 + static_cast(v.m_position_accessor); + seed ^= (seed << 6) + (seed >> 2) + 0x66847B5C + std::hash>()(v.m_normal_accessor); + seed ^= (seed << 6) + (seed >> 2) + 0x77399D60 + std::hash>()(v.m_color_accessor); + seed ^= (seed << 6) + (seed >> 2) + 0x477AF9AB + std::hash>()(v.m_uv_accessor); + seed ^= (seed << 6) + (seed >> 2) + 0x4421B4D9 + std::hash>()(v.m_joints_accessor); + seed ^= (seed << 6) + (seed >> 2) + 0x13C2EBA1 + std::hash>()(v.m_weights_accessor); return seed; } }; @@ -137,14 +125,14 @@ namespace struct ObjectToLoad { - unsigned meshIndex; - std::optional skinIndex; - ObjectToLoad(const unsigned meshIndex, const std::optional skinIndex) - : meshIndex(meshIndex), - skinIndex(skinIndex) + : m_mesh_index(meshIndex), + m_skin_index(skinIndex) { } + + unsigned m_mesh_index; + std::optional m_skin_index; }; class GltfLoaderImpl final : public Loader @@ -197,7 +185,7 @@ namespace return; std::deque nodeQueue; - std::vector rootNodes = GetRootNodes(jRoot); + const std::vector rootNodes = GetRootNodes(jRoot); for (const auto rootNode : rootNodes) nodeQueue.emplace_back(rootNode); @@ -258,9 +246,9 @@ namespace unsigned CreateVertices(XModelCommon& common, const AccessorsForVertex& accessorsForVertex) { // clang-format off - auto* positionAccessor = GetAccessorForIndex( + const auto* positionAccessor = GetAccessorForIndex( "POSITION", - accessorsForVertex.positionAccessor, + accessorsForVertex.m_position_accessor, {JsonAccessorType::VEC3}, {JsonAccessorComponentType::FLOAT} ).value_or(nullptr); @@ -271,41 +259,41 @@ namespace OnesAccessor onesAccessor(vertexCount); // clang-format off - auto* normalAccessor = GetAccessorForIndex( + const auto* normalAccessor = GetAccessorForIndex( "NORMAL", - accessorsForVertex.normalAccessor, + accessorsForVertex.m_normal_accessor, {JsonAccessorType::VEC3}, {JsonAccessorComponentType::FLOAT} ).value_or(&nullAccessor); VerifyAccessorVertexCount("NORMAL", normalAccessor, vertexCount); - auto* uvAccessor = GetAccessorForIndex( + const auto* uvAccessor = GetAccessorForIndex( "TEXCOORD_0", - accessorsForVertex.uvAccessor, + accessorsForVertex.m_uv_accessor, {JsonAccessorType::VEC2}, {JsonAccessorComponentType::FLOAT, JsonAccessorComponentType::UNSIGNED_BYTE, JsonAccessorComponentType::UNSIGNED_SHORT} ).value_or(&nullAccessor); VerifyAccessorVertexCount("TEXCOORD_0", uvAccessor, vertexCount); - auto* colorAccessor = GetAccessorForIndex( + const auto* colorAccessor = GetAccessorForIndex( "COLOR_0", - accessorsForVertex.colorAccessor, + accessorsForVertex.m_color_accessor, {JsonAccessorType::VEC3, JsonAccessorType::VEC4}, {JsonAccessorComponentType::FLOAT, JsonAccessorComponentType::UNSIGNED_BYTE, JsonAccessorComponentType::UNSIGNED_SHORT} ).value_or(&onesAccessor); VerifyAccessorVertexCount("COLOR_0", colorAccessor, vertexCount); - auto* jointsAccessor = GetAccessorForIndex( + const auto* jointsAccessor = GetAccessorForIndex( "JOINTS_0", - accessorsForVertex.jointsAccessor, + accessorsForVertex.m_joints_accessor, {JsonAccessorType::VEC4}, {JsonAccessorComponentType::UNSIGNED_BYTE, JsonAccessorComponentType::UNSIGNED_SHORT} ).value_or(&nullAccessor); VerifyAccessorVertexCount("JOINTS_0", jointsAccessor, vertexCount); - auto* weightsAccessor = GetAccessorForIndex( + const auto* weightsAccessor = GetAccessorForIndex( "WEIGHTS_0", - accessorsForVertex.weightsAccessor, + accessorsForVertex.m_weights_accessor, {JsonAccessorType::VEC4}, {JsonAccessorComponentType::FLOAT, JsonAccessorComponentType::UNSIGNED_BYTE, JsonAccessorComponentType::UNSIGNED_SHORT} ).value_or(&nullAccessor); @@ -363,13 +351,13 @@ namespace if (!primitives.attributes.POSITION) throw GltfLoadException("Requires primitives attribute POSITION"); - AccessorsForVertex accessorsForVertex{ - .positionAccessor = *primitives.attributes.POSITION, - .normalAccessor = primitives.attributes.NORMAL, - .colorAccessor = primitives.attributes.COLOR_0, - .uvAccessor = primitives.attributes.TEXCOORD_0, - .jointsAccessor = primitives.attributes.JOINTS_0, - .weightsAccessor = primitives.attributes.WEIGHTS_0, + const AccessorsForVertex accessorsForVertex{ + .m_position_accessor = *primitives.attributes.POSITION, + .m_normal_accessor = primitives.attributes.NORMAL, + .m_color_accessor = primitives.attributes.COLOR_0, + .m_uv_accessor = primitives.attributes.TEXCOORD_0, + .m_joints_accessor = primitives.attributes.JOINTS_0, + .m_weights_accessor = primitives.attributes.WEIGHTS_0, }; const auto existingVertices = m_vertex_offset_for_accessors.find(accessorsForVertex); @@ -630,26 +618,26 @@ namespace for (const auto& loadObject : m_load_objects) { - if (loadObject.skinIndex && jRoot.skins) + if (loadObject.m_skin_index && jRoot.skins) { if (alreadyLoadedSkinIndex) { - if (*alreadyLoadedSkinIndex != *loadObject.skinIndex) + if (*alreadyLoadedSkinIndex != *loadObject.m_skin_index) throw GltfLoadException("Only scenes with at most one skin are supported"); // Do not load already loaded skin } else { - const auto& skin = jRoot.skins.value()[*loadObject.skinIndex]; + const auto& skin = jRoot.skins.value()[*loadObject.m_skin_index]; if (!ConvertSkin(jRoot, skin, common)) return; - alreadyLoadedSkinIndex = *loadObject.skinIndex; + alreadyLoadedSkinIndex = *loadObject.m_skin_index; } } - const auto& mesh = jRoot.meshes.value()[loadObject.meshIndex]; + const auto& mesh = jRoot.meshes.value()[loadObject.m_mesh_index]; common.m_objects.reserve(common.m_objects.size() + mesh.primitives.size()); for (const auto& primitives : mesh.primitives) @@ -811,10 +799,12 @@ namespace std::vector> m_accessors; std::vector> m_buffer_views; std::vector> m_buffers; + + bool m_bad_rotation_formulas; }; } // namespace std::unique_ptr Loader::CreateLoader(const Input* input) { - return std::make_unique(input); + return std::make_unique(input, useBadRotationFormulas); } From fed6e2f8454c7d47bb718915d9290875aa78dab4 Mon Sep 17 00:00:00 2001 From: Jan Laupetin Date: Tue, 19 Aug 2025 13:31:46 +0100 Subject: [PATCH 7/9] chore: backwards compatibility for old xmodel exports --- src/ObjLoading/XModel/Gltf/GltfLoader.cpp | 89 +++++++++++++------ src/ObjLoading/XModel/Gltf/GltfLoader.h | 9 +- .../XModel/LoaderXModel.cpp.template | 17 ++-- .../XModel/XModelDumper.cpp.template | 2 +- 4 files changed, 80 insertions(+), 37 deletions(-) diff --git a/src/ObjLoading/XModel/Gltf/GltfLoader.cpp b/src/ObjLoading/XModel/Gltf/GltfLoader.cpp index fdccaf89..add6b39f 100644 --- a/src/ObjLoading/XModel/Gltf/GltfLoader.cpp +++ b/src/ObjLoading/XModel/Gltf/GltfLoader.cpp @@ -138,8 +138,9 @@ namespace class GltfLoaderImpl final : public Loader { public: - explicit GltfLoaderImpl(const Input* input) - : m_input(input) + GltfLoaderImpl(const Input& input, const bool useBadRotationFormulas) + : m_input(input), + m_bad_rotation_formulas(useBadRotationFormulas) { } @@ -449,7 +450,7 @@ namespace return std::nullopt; } - static 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 auto matrix = Eigen::Matrix4f({ {(*node.matrix)[0], (*node.matrix)[4], (*node.matrix)[8], (*node.matrix)[12]}, @@ -460,29 +461,45 @@ namespace Eigen::Affine3f transform(matrix); const auto translation = transform.translation(); + localOffsetRhc[0] = translation.x(); localOffsetRhc[1] = translation.y(); localOffsetRhc[2] = translation.z(); + if (m_bad_rotation_formulas) + RhcToLhcCoordinates(localOffsetRhc); const auto rotation = transform.rotation(); const auto rotationQuat = Eigen::Quaternionf(rotation); - localRotationRhc[0] = rotationQuat.x(); - localRotationRhc[1] = rotationQuat.y(); - localRotationRhc[2] = rotationQuat.z(); - localRotationRhc[3] = rotationQuat.w(); + if (!m_bad_rotation_formulas) + { + localRotationRhc[0] = rotationQuat.x(); + localRotationRhc[1] = rotationQuat.y(); + localRotationRhc[2] = rotationQuat.z(); + localRotationRhc[3] = rotationQuat.w(); + } + else + { + // Backwards compatibility + localRotationRhc[0] = rotationQuat.x(); + localRotationRhc[1] = -rotationQuat.z(); + localRotationRhc[2] = rotationQuat.y(); + localRotationRhc[3] = rotationQuat.w(); + } scaleRhc[0] = matrix.block<3, 1>(0, 0).norm(); scaleRhc[1] = matrix.block<3, 1>(0, 1).norm(); scaleRhc[2] = matrix.block<3, 1>(0, 2).norm(); } - static 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]) { if (node.translation) { localOffsetRhc[0] = (*node.translation)[0]; localOffsetRhc[1] = (*node.translation)[1]; localOffsetRhc[2] = (*node.translation)[2]; + if (m_bad_rotation_formulas) + RhcToLhcCoordinates(localOffsetRhc); } else { @@ -493,10 +510,21 @@ namespace if (node.rotation) { - localRotationRhc[0] = (*node.rotation)[0]; - localRotationRhc[1] = (*node.rotation)[1]; - localRotationRhc[2] = (*node.rotation)[2]; - localRotationRhc[3] = (*node.rotation)[3]; + if (!m_bad_rotation_formulas) + { + localRotationRhc[0] = (*node.rotation)[0]; + localRotationRhc[1] = (*node.rotation)[1]; + localRotationRhc[2] = (*node.rotation)[2]; + localRotationRhc[3] = (*node.rotation)[3]; + } + else + { + // Backwards compatibility + localRotationRhc[0] = (*node.rotation)[0]; + localRotationRhc[1] = -(*node.rotation)[2]; + localRotationRhc[2] = (*node.rotation)[1]; + localRotationRhc[3] = (*node.rotation)[3]; + } } else { @@ -520,15 +548,15 @@ namespace } } - static bool ConvertJoint(const JsonRoot& jRoot, - const JsonSkin& skin, - XModelCommon& common, - const unsigned skinBoneOffset, - const unsigned nodeIndex, - const std::optional parentIndex, - const Eigen::Vector3f& parentTranslationEigenRhc, - const Eigen::Quaternionf& parentRotationEigenRhc, - const float (&parentScale)[3]) + bool ConvertJoint(const JsonRoot& jRoot, + const JsonSkin& skin, + XModelCommon& common, + const unsigned skinBoneOffset, + const unsigned nodeIndex, + const std::optional parentIndex, + const Eigen::Vector3f& parentTranslationEigenRhc, + const Eigen::Quaternionf& parentRotationEigenRhc, + const float (&parentScale)[3]) { if (!jRoot.nodes || nodeIndex >= jRoot.nodes->size()) return false; @@ -555,7 +583,8 @@ namespace bone.scale[0] = localScaleRhc[0] * parentScale[0]; bone.scale[1] = localScaleRhc[1] * parentScale[1]; bone.scale[2] = localScaleRhc[2] * parentScale[2]; - RhcToLhcScale(bone.scale); + if (!m_bad_rotation_formulas) + RhcToLhcScale(bone.scale); const Eigen::Vector3f localTranslationEigen(localOffsetRhc[0], localOffsetRhc[1], localOffsetRhc[2]); const Eigen::Quaternionf localRotationEigen(localRotationRhc[3], localRotationRhc[0], localRotationRhc[1], localRotationRhc[2]); @@ -566,13 +595,15 @@ namespace bone.globalOffset[0] = globalTranslationEigenRhc.x(); bone.globalOffset[1] = globalTranslationEigenRhc.y(); bone.globalOffset[2] = globalTranslationEigenRhc.z(); - RhcToLhcCoordinates(bone.globalOffset); + if (!m_bad_rotation_formulas) + RhcToLhcCoordinates(bone.globalOffset); bone.globalRotation.x = globalRotationEigenRhc.x(); bone.globalRotation.y = globalRotationEigenRhc.y(); bone.globalRotation.z = globalRotationEigenRhc.z(); bone.globalRotation.w = globalRotationEigenRhc.w(); - RhcToLhcQuaternion(bone.globalRotation); + if (!m_bad_rotation_formulas) + RhcToLhcQuaternion(bone.globalRotation); if (node.children) { @@ -587,7 +618,7 @@ namespace return true; } - static bool ConvertSkin(const JsonRoot& jRoot, const JsonSkin& skin, XModelCommon& common) + bool ConvertSkin(const JsonRoot& jRoot, const JsonSkin& skin, XModelCommon& common) { if (skin.joints.empty()) return true; @@ -683,7 +714,7 @@ namespace { const void* embeddedBufferPtr = nullptr; size_t embeddedBufferSize = 0u; - if (!m_input->GetEmbeddedBuffer(embeddedBufferPtr, embeddedBufferSize) || embeddedBufferSize == 0u) + if (!m_input.GetEmbeddedBuffer(embeddedBufferPtr, embeddedBufferSize) || embeddedBufferSize == 0u) throw GltfLoadException("Buffer tried to access embedded data when there is none"); m_buffers.emplace_back(std::make_unique(embeddedBufferPtr, embeddedBufferSize)); @@ -763,7 +794,7 @@ namespace JsonRoot jRoot; try { - jRoot = m_input->GetJson().get(); + jRoot = m_input.GetJson().get(); } catch (const nlohmann::json::exception& e) { @@ -793,7 +824,7 @@ namespace } private: - const Input* m_input; + const Input& m_input; std::vector m_load_objects; std::unordered_map m_vertex_offset_for_accessors; std::vector> m_accessors; @@ -804,7 +835,7 @@ namespace }; } // namespace -std::unique_ptr Loader::CreateLoader(const Input* input) +std::unique_ptr Loader::CreateLoader(const Input& input, bool useBadRotationFormulas) { return std::make_unique(input, useBadRotationFormulas); } diff --git a/src/ObjLoading/XModel/Gltf/GltfLoader.h b/src/ObjLoading/XModel/Gltf/GltfLoader.h index 872394e4..46ae42ad 100644 --- a/src/ObjLoading/XModel/Gltf/GltfLoader.h +++ b/src/ObjLoading/XModel/Gltf/GltfLoader.h @@ -19,6 +19,13 @@ namespace gltf Loader& operator=(const Loader& other) = default; Loader& operator=(Loader&& other) noexcept = default; - static std::unique_ptr CreateLoader(const Input* input); + /** + * \brief Creates a loader capable of loading gltf-like files + * \param input The gltf input + * \param useBadRotationFormulas Old versions used bad formulas for converting into gltf space. Set to \c true to use them for loading to preserve + * backwards compatibility. + * \return + */ + static std::unique_ptr CreateLoader(const Input& input, bool useBadRotationFormulas); }; } // namespace gltf diff --git a/src/ObjLoading/XModel/LoaderXModel.cpp.template b/src/ObjLoading/XModel/LoaderXModel.cpp.template index c47b260e..90ef18ba 100644 --- a/src/ObjLoading/XModel/LoaderXModel.cpp.template +++ b/src/ObjLoading/XModel/LoaderXModel.cpp.template @@ -57,7 +57,8 @@ namespace { public: XModelLoader(MemoryManager& memory, ISearchPath& searchPath, ZoneScriptStrings& scriptStrings) - : m_memory(memory), + : m_gltf_bad_rotation_formulas(false), + m_memory(memory), m_search_path(searchPath), m_script_strings(scriptStrings) { @@ -97,12 +98,14 @@ namespace jRoot.at("_type").get_to(type); jRoot.at("_version").get_to(version); - if (type != "xmodel" || version != 1u) + if (type != "xmodel" || version < 1u || version > 2u) { - std::cerr << std::format("Tried to load xmodel \"{}\" but did not find expected type material of version 1\n", xmodel.name); + std::cerr << std::format("Tried to load xmodel \"{}\" but did not find expected type material of version 1 or 2\n", xmodel.name); return false; } + m_gltf_bad_rotation_formulas = version == 1u; + const auto jXModel = jRoot.get(); return CreateXModelFromJson(jXModel, xmodel, context, registration); } @@ -119,7 +122,7 @@ namespace std::cerr << std::format("Cannot load xmodel \"{}\": {}\n", xmodel.name, message); } - static std::unique_ptr LoadModelByExtension(std::istream& stream, const std::string& extension) + std::unique_ptr LoadModelByExtension(std::istream& stream, const std::string& extension) const { if (extension == ".glb") { @@ -127,7 +130,7 @@ namespace if (!input.ReadGltfData(stream)) return nullptr; - const auto loader = gltf::Loader::CreateLoader(&input); + const auto loader = gltf::Loader::CreateLoader(input, m_gltf_bad_rotation_formulas); return loader->Load(); } @@ -137,7 +140,7 @@ namespace if (!input.ReadGltfData(stream)) return nullptr; - const auto loader = gltf::Loader::CreateLoader(&input); + const auto loader = gltf::Loader::CreateLoader(input, m_gltf_bad_rotation_formulas); return loader->Load(); } @@ -1072,6 +1075,8 @@ namespace return true; } + bool m_gltf_bad_rotation_formulas; + std::vector m_surfaces; std::vector m_materials; diff --git a/src/ObjWriting/XModel/XModelDumper.cpp.template b/src/ObjWriting/XModel/XModelDumper.cpp.template index a79a4ef2..4a569a56 100644 --- a/src/ObjWriting/XModel/XModelDumper.cpp.template +++ b/src/ObjWriting/XModel/XModelDumper.cpp.template @@ -690,7 +690,7 @@ namespace jRoot["$schema"] = "http://openassettools.dev/schema/xmodel.v1.json"; jRoot["_type"] = "xmodel"; - jRoot["_version"] = 1; + jRoot["_version"] = 2; jRoot["_game"] = GAME_LOWER; m_stream << std::setw(4) << jRoot << "\n"; From 27ba80c647d5b8413b14b8177e916ebcccaf24a1 Mon Sep 17 00:00:00 2001 From: Jan Laupetin Date: Sun, 7 Sep 2025 22:19:37 +0100 Subject: [PATCH 8/9] chore: log about deprecated xmodel version 1 backwards compatibility --- src/ObjLoading/XModel/LoaderXModel.cpp.template | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/ObjLoading/XModel/LoaderXModel.cpp.template b/src/ObjLoading/XModel/LoaderXModel.cpp.template index 90ef18ba..aa0db871 100644 --- a/src/ObjLoading/XModel/LoaderXModel.cpp.template +++ b/src/ObjLoading/XModel/LoaderXModel.cpp.template @@ -103,8 +103,12 @@ namespace std::cerr << std::format("Tried to load xmodel \"{}\" but did not find expected type material of version 1 or 2\n", xmodel.name); return false; } - - m_gltf_bad_rotation_formulas = version == 1u; + + if (version == 1u) + { + m_gltf_bad_rotation_formulas = true; + std::cerr << std::format("DEPRECATED: XModel {} is version 1 that made use of bad GLTF bone rotations.\n", xmodel.name); + } const auto jXModel = jRoot.get(); return CreateXModelFromJson(jXModel, xmodel, context, registration); From 49daacfb8b6d7830775d6b26271ca193c28dc420 Mon Sep 17 00:00:00 2001 From: Jan Laupetin Date: Tue, 9 Sep 2025 17:38:49 +0100 Subject: [PATCH 9/9] fix: reset xmodel version configuration on every model instead of only on version 1 --- src/ObjLoading/XModel/LoaderXModel.cpp.template | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ObjLoading/XModel/LoaderXModel.cpp.template b/src/ObjLoading/XModel/LoaderXModel.cpp.template index aa0db871..fce40ef4 100644 --- a/src/ObjLoading/XModel/LoaderXModel.cpp.template +++ b/src/ObjLoading/XModel/LoaderXModel.cpp.template @@ -109,6 +109,10 @@ namespace m_gltf_bad_rotation_formulas = true; std::cerr << std::format("DEPRECATED: XModel {} is version 1 that made use of bad GLTF bone rotations.\n", xmodel.name); } + else + { + m_gltf_bad_rotation_formulas = false; + } const auto jXModel = jRoot.get(); return CreateXModelFromJson(jXModel, xmodel, context, registration);