1
0
Fork 0
mirror of https://github.com/luanti-org/luanti.git synced 2025-07-22 17:18:39 +00:00

Fix and clean up skeletal animation (#15722)

* Fix attachments lagging behind their parents (#14818)
* Fix animation blending (#14817)
* Bring back cool guy as another .x smoke test
* Add .x mesh loader unittest
* Do bounding box & matrix calculation at proper point in time
* Remove obsolete `SAnimatedMesh`
This commit is contained in:
Lars Müller 2025-06-01 23:21:35 +02:00 committed by GitHub
parent 0bb87eb1ff
commit fde6384a09
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
40 changed files with 856 additions and 1388 deletions

View file

@ -59,6 +59,8 @@ set (UNITTEST_CLIENT_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/test_eventmanager.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_gameui.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_irr_gltf_mesh_loader.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_irr_x_mesh_loader.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_irr_matrix4.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_mesh_compare.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_keycode.cpp
PARENT_SCOPE)

View file

@ -394,36 +394,40 @@ SECTION("simple skin")
const auto joints = csm->getAllJoints();
REQUIRE(joints.size() == 3);
const auto findJoint = [&](const std::function<bool(SkinnedMesh::SJoint*)> &predicate) {
for (std::size_t i = 0; i < joints.size(); ++i) {
if (predicate(joints[i])) {
return joints[i];
const auto findJoint = [&](const std::function<bool(const SkinnedMesh::SJoint*)> &predicate) {
for (const auto *joint : joints) {
if (predicate(joint)) {
return joint;
}
}
throw std::runtime_error("joint not found");
};
// Check the node hierarchy
const auto parent = findJoint([](auto joint) {
return !joint->Children.empty();
const auto child = findJoint([&](auto *joint) {
return !!joint->ParentJointID;
});
REQUIRE(parent->Children.size() == 1);
const auto child = parent->Children[0];
REQUIRE(child != parent);
const auto *parent = joints.at(*child->ParentJointID);
SECTION("transformations are correct")
{
CHECK(parent->Animatedposition == v3f(0, 0, 0));
CHECK(parent->Animatedrotation == irr::core::quaternion());
CHECK(parent->Animatedscale == v3f(1, 1, 1));
CHECK(parent->GlobalInversedMatrix == irr::core::matrix4());
const v3f childTranslation(0, 1, 0);
CHECK(child->Animatedposition == childTranslation);
CHECK(child->Animatedrotation == irr::core::quaternion());
CHECK(child->Animatedscale == v3f(1, 1, 1));
irr::core::matrix4 inverseBindMatrix;
inverseBindMatrix.setTranslation(-childTranslation);
CHECK(child->GlobalInversedMatrix == inverseBindMatrix);
{
const auto &transform = std::get<core::Transform>(parent->transform);
CHECK(transform.translation == v3f(0, 0, 0));
CHECK(transform.rotation == irr::core::quaternion());
CHECK(transform.scale == v3f(1, 1, 1));
CHECK(parent->GlobalInversedMatrix == irr::core::matrix4());
}
{
const auto &transform = std::get<core::Transform>(child->transform);
const v3f translation(0, 1, 0);
CHECK(transform.translation == translation);
CHECK(transform.rotation == irr::core::quaternion());
CHECK(transform.scale == v3f(1, 1, 1));
irr::core::matrix4 inverseBindMatrix;
inverseBindMatrix.setTranslation(-translation);
CHECK(child->GlobalInversedMatrix == inverseBindMatrix);
}
}
SECTION("weights are correct")

View file

@ -0,0 +1,111 @@
// Luanti
// SPDX-License-Identifier: LGPL-2.1-or-later
#include "catch_amalgamated.hpp"
#include "content/subgames.h"
#include "filesys.h"
#include "irrlichttypes.h"
#include "irr_ptr.h"
#include "EDriverTypes.h"
#include "IFileSystem.h"
#include "IReadFile.h"
#include "ISceneManager.h"
#include "SkinnedMesh.h"
#include "irrlicht.h"
#include "catch.h"
#include "matrix4.h"
TEST_CASE("x") {
const auto gamespec = findSubgame("devtest");
if (!gamespec.isValid())
SKIP();
irr::SIrrlichtCreationParameters p;
p.DriverType = video::EDT_NULL;
auto *driver = irr::createDeviceEx(p);
REQUIRE(driver);
auto *smgr = driver->getSceneManager();
const auto loadMesh = [&] (const io::path& filepath) {
irr_ptr<io::IReadFile> file(driver->getFileSystem()->createAndOpenFile(filepath));
REQUIRE(file);
return smgr->getMesh(file.get());
};
const static auto model_stem = gamespec.gamemods_path +
DIR_DELIM + "testentities" + DIR_DELIM + "models" + DIR_DELIM + "testentities_";
SECTION("cool guy") {
const auto *mesh = dynamic_cast<irr::scene::SkinnedMesh*>(loadMesh(model_stem + "cool_guy.x"));
REQUIRE(mesh);
REQUIRE(mesh->getMeshBufferCount() == 1);
auto getJointId = [&](auto name) {
return mesh->getJointNumber(name).value();
};
const auto root = getJointId("Root");
const auto armature = getJointId("Armature");
const auto armature_body = getJointId("Armature_body");
const auto armature_arm_r = getJointId("Armature_arm_r");
std::vector<core::matrix4> matrices;
matrices.reserve(mesh->getJointCount());
for (auto *joint : mesh->getAllJoints()) {
if (const auto *matrix = std::get_if<core::matrix4>(&joint->transform))
matrices.push_back(*matrix);
else
matrices.push_back(std::get<core::Transform>(joint->transform).buildMatrix());
}
auto local_matrices = matrices;
mesh->calculateGlobalMatrices(matrices);
SECTION("joints are topologically sorted") {
REQUIRE(root < armature);
REQUIRE(armature < armature_body);
REQUIRE(armature_body < armature_arm_r);
}
SECTION("parents are correct") {
const auto get_parent = [&](auto id) {
return mesh->getAllJoints()[id]->ParentJointID;
};
REQUIRE(!get_parent(root));
REQUIRE(get_parent(armature).value() == root);
REQUIRE(get_parent(armature_body).value() == armature);
REQUIRE(get_parent(armature_arm_r).value() == armature_body);
}
SECTION("local matrices are correct") {
REQUIRE(local_matrices[root].equals(core::IdentityMatrix));
REQUIRE(local_matrices[armature].equals(core::IdentityMatrix));
REQUIRE(local_matrices[armature_body] == core::matrix4(
-1,0,0,0,
0,0,1,0,
0,1,0,0,
0,2.571201,0,1
));
REQUIRE(local_matrices[armature_arm_r] == core::matrix4(
-0.047733,0.997488,-0.05233,0,
0.901521,0.020464,-0.432251,0,
-0.430095,-0.067809,-0.900233,
0,-0.545315,0,1,1
));
}
SECTION("global matrices are correct") {
REQUIRE(matrices[armature_body] == local_matrices[armature_body]);
REQUIRE(matrices[armature_arm_r] ==
matrices[armature_body] * local_matrices[armature_arm_r]);
}
}
driver->closeDevice();
driver->drop();
}