mirror of
https://github.com/Laupetin/OpenAssetTools.git
synced 2026-05-12 21:31:43 +00:00
Merge pull request #752 from Laupetin/fix/relink-dumped-xmodels
fix: xmodels with more than one root bone are not linkable
This commit is contained in:
@@ -12,10 +12,10 @@
|
|||||||
#include <deque>
|
#include <deque>
|
||||||
#include <exception>
|
#include <exception>
|
||||||
#include <format>
|
#include <format>
|
||||||
#include <iostream>
|
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
#include <numbers>
|
#include <numbers>
|
||||||
|
#include <numeric>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
using namespace gltf;
|
using namespace gltf;
|
||||||
@@ -316,7 +316,7 @@ namespace
|
|||||||
|| !colorAccessor->GetFloatVec4(vertexIndex, vertex.color) || !uvAccessor->GetFloatVec2(vertexIndex, vertex.uv)
|
|| !colorAccessor->GetFloatVec4(vertexIndex, vertex.color) || !uvAccessor->GetFloatVec2(vertexIndex, vertex.uv)
|
||||||
|| !jointsAccessor->GetUnsignedVec4(vertexIndex, joints) || !weightsAccessor->GetFloatVec4(vertexIndex, weights))
|
|| !jointsAccessor->GetUnsignedVec4(vertexIndex, joints) || !weightsAccessor->GetFloatVec4(vertexIndex, weights))
|
||||||
{
|
{
|
||||||
return false;
|
throw GltfLoadException("Failed to load vertex data from accessors");
|
||||||
}
|
}
|
||||||
|
|
||||||
RhcToLhcCoordinates(vertex.coordinates);
|
RhcToLhcCoordinates(vertex.coordinates);
|
||||||
@@ -330,7 +330,16 @@ namespace
|
|||||||
if (std::abs(weights[i]) < std::numeric_limits<float>::epsilon())
|
if (std::abs(weights[i]) < std::numeric_limits<float>::epsilon())
|
||||||
continue;
|
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++;
|
vertexWeights.weightCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -406,13 +415,12 @@ namespace
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::optional<unsigned> GetRootNodeForSkin(const JsonRoot& jRoot, const JsonSkin& skin)
|
static std::vector<unsigned> GetRootNodesForSkin(const JsonRoot& jRoot, const JsonSkin& skin)
|
||||||
{
|
{
|
||||||
if (!jRoot.nodes || skin.joints.empty())
|
if (!jRoot.nodes || skin.joints.empty())
|
||||||
return std::nullopt;
|
return {};
|
||||||
|
|
||||||
const auto jointCount = skin.joints.size();
|
const auto jointCount = skin.joints.size();
|
||||||
auto rootCount = jointCount;
|
|
||||||
std::vector<bool> isRoot(jointCount, true);
|
std::vector<bool> isRoot(jointCount, true);
|
||||||
|
|
||||||
for (const auto joint : skin.joints)
|
for (const auto joint : skin.joints)
|
||||||
@@ -432,26 +440,23 @@ namespace
|
|||||||
if (isRoot[foundChildJointIndex])
|
if (isRoot[foundChildJointIndex])
|
||||||
{
|
{
|
||||||
isRoot[foundChildJointIndex] = false;
|
isRoot[foundChildJointIndex] = false;
|
||||||
rootCount--;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rootCount != 1)
|
std::vector<unsigned> result;
|
||||||
throw GltfLoadException("Skins must have exactly one common root node");
|
|
||||||
|
|
||||||
for (auto index = 0u; index < jointCount; index++)
|
for (auto index = 0u; index < jointCount; index++)
|
||||||
{
|
{
|
||||||
if (isRoot[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({
|
const auto matrix = Eigen::Matrix4f({
|
||||||
{(*node.matrix)[0], (*node.matrix)[4], (*node.matrix)[8], (*node.matrix)[12]},
|
{(*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();
|
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)
|
if (node.translation)
|
||||||
{
|
{
|
||||||
@@ -612,13 +617,63 @@ namespace
|
|||||||
{
|
{
|
||||||
if (!ConvertJoint(
|
if (!ConvertJoint(
|
||||||
jRoot, skin, common, skinBoneOffset, childIndex, commonBoneOffset, globalTranslationEigenRhc, globalRotationEigenRhc, bone.scale))
|
jRoot, skin, common, skinBoneOffset, childIndex, commonBoneOffset, globalTranslationEigenRhc, globalRotationEigenRhc, bone.scale))
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
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<unsigned> 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<XModelBone> 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)
|
bool ConvertSkin(const JsonRoot& jRoot, const JsonSkin& skin, XModelCommon& common)
|
||||||
{
|
{
|
||||||
if (skin.joints.empty())
|
if (skin.joints.empty())
|
||||||
@@ -626,18 +681,27 @@ namespace
|
|||||||
if (!jRoot.nodes)
|
if (!jRoot.nodes)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
const auto rootNode = GetRootNodeForSkin(jRoot, skin).value_or(skin.joints[0]);
|
auto rootNodes = GetRootNodesForSkin(jRoot, skin);
|
||||||
const auto skinBoneOffset = static_cast<unsigned>(common.m_bones.size());
|
if (rootNodes.empty())
|
||||||
common.m_bones.resize(skinBoneOffset + skin.joints.size());
|
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);
|
const Eigen::Quaternionf defaultRotation(1.0f, 0.0f, 0.0f, 0.0f);
|
||||||
constexpr float defaultScale[3]{1.0f, 1.0f, 1.0f};
|
constexpr float defaultScale[3]{1.0f, 1.0f, 1.0f};
|
||||||
|
|
||||||
if (!ConvertJoint(jRoot, skin, common, skinBoneOffset, rootNode, std::nullopt, defaultTranslation, defaultRotation, defaultScale))
|
for (const auto rootNode : rootNodes)
|
||||||
return false;
|
{
|
||||||
|
if (!ConvertJoint(jRoot, skin, common, 0, rootNode, std::nullopt, defaultTranslation, defaultRotation, defaultScale))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReorderBonesForXModels(common);
|
||||||
common.CalculateBoneLocalsFromGlobals();
|
common.CalculateBoneLocalsFromGlobals();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -832,7 +896,15 @@ namespace
|
|||||||
std::vector<std::unique_ptr<BufferView>> m_buffer_views;
|
std::vector<std::unique_ptr<BufferView>> m_buffer_views;
|
||||||
std::vector<std::unique_ptr<Buffer>> m_buffers;
|
std::vector<std::unique_ptr<Buffer>> 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;
|
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<unsigned> m_gltf_to_common_joint_index_lookup;
|
||||||
};
|
};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
|||||||
@@ -323,8 +323,12 @@ namespace
|
|||||||
{
|
{
|
||||||
xmodel.parentList = m_memory.Alloc<unsigned char>(xmodel.numBones - xmodel.numRootBones);
|
xmodel.parentList = m_memory.Alloc<unsigned char>(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<float>((xmodel.numBones - xmodel.numRootBones) * 4u);
|
xmodel.trans = m_memory.Alloc<float>((xmodel.numBones - xmodel.numRootBones) * 4u);
|
||||||
|
#else
|
||||||
|
xmodel.trans = m_memory.Alloc<float>((xmodel.numBones - xmodel.numRootBones) * 3u);
|
||||||
|
#endif
|
||||||
xmodel.quats = m_memory.Alloc<XModelQuat>(xmodel.numBones - xmodel.numRootBones);
|
xmodel.quats = m_memory.Alloc<XModelQuat>(xmodel.numBones - xmodel.numRootBones);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -373,7 +377,11 @@ namespace
|
|||||||
// Viewhands seem to have nulled trans for some reason?
|
// Viewhands seem to have nulled trans for some reason?
|
||||||
if (jXModel.type.value_or(JsonXModelType::RIGID) == JsonXModelType::VIEWHANDS)
|
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));
|
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;
|
return true;
|
||||||
|
|||||||
@@ -187,10 +187,10 @@ namespace
|
|||||||
|
|
||||||
const auto meshCount = xmodel.m_objects.size();
|
const auto meshCount = xmodel.m_objects.size();
|
||||||
for (auto meshIndex = 0u; meshIndex < meshCount; meshIndex++)
|
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())
|
for (auto rootBoneIndex = 0u; rootBoneIndex < m_root_bone_count; rootBoneIndex++)
|
||||||
rootNode.children->push_back(m_first_bone_node);
|
rootNode.children->emplace_back(m_first_bone_node + rootBoneIndex);
|
||||||
|
|
||||||
m_root_node = static_cast<unsigned>(gltf.nodes->size());
|
m_root_node = static_cast<unsigned>(gltf.nodes->size());
|
||||||
gltf.nodes->emplace_back(std::move(rootNode));
|
gltf.nodes->emplace_back(std::move(rootNode));
|
||||||
@@ -303,6 +303,7 @@ namespace
|
|||||||
|
|
||||||
const auto boneCount = common.m_bones.size();
|
const auto boneCount = common.m_bones.size();
|
||||||
m_first_bone_node = static_cast<unsigned>(gltf.nodes->size());
|
m_first_bone_node = static_cast<unsigned>(gltf.nodes->size());
|
||||||
|
m_root_bone_count = 0;
|
||||||
for (auto boneIndex = 0u; boneIndex < boneCount; boneIndex++)
|
for (auto boneIndex = 0u; boneIndex < boneCount; boneIndex++)
|
||||||
{
|
{
|
||||||
JsonNode boneNode;
|
JsonNode boneNode;
|
||||||
@@ -335,6 +336,11 @@ namespace
|
|||||||
translation = inverseParentRotation * translation;
|
translation = inverseParentRotation * translation;
|
||||||
rotation = inverseParentRotation * rotation;
|
rotation = inverseParentRotation * rotation;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
assert(m_root_bone_count == boneIndex);
|
||||||
|
m_root_bone_count++;
|
||||||
|
}
|
||||||
rotation.normalize();
|
rotation.normalize();
|
||||||
|
|
||||||
boneNode.name = bone.name;
|
boneNode.name = bone.name;
|
||||||
@@ -745,6 +751,7 @@ namespace
|
|||||||
unsigned m_first_mesh_node = 0u;
|
unsigned m_first_mesh_node = 0u;
|
||||||
unsigned m_root_node = 0u;
|
unsigned m_root_node = 0u;
|
||||||
unsigned m_first_bone_node = 0u;
|
unsigned m_first_bone_node = 0u;
|
||||||
|
unsigned m_root_bone_count = 0u;
|
||||||
unsigned m_position_accessor = 0u;
|
unsigned m_position_accessor = 0u;
|
||||||
unsigned m_normal_accessor = 0u;
|
unsigned m_normal_accessor = 0u;
|
||||||
unsigned m_color_accessor = 0u;
|
unsigned m_color_accessor = 0u;
|
||||||
|
|||||||
Reference in New Issue
Block a user