diff --git a/irr/include/IAnimatedMeshSceneNode.h b/irr/include/IAnimatedMeshSceneNode.h index a043f6fa2..2400c354f 100644 --- a/irr/include/IAnimatedMeshSceneNode.h +++ b/irr/include/IAnimatedMeshSceneNode.h @@ -135,7 +135,7 @@ public: //! animates the joints in the mesh based on the current frame. /** Also takes in to account transitions. */ - virtual void animateJoints(bool CalculateAbsolutePositions = true) = 0; + virtual void animateJoints() = 0; //! render mesh ignoring its transformation. /** Culling is unaffected. */ diff --git a/irr/include/IBoneSceneNode.h b/irr/include/IBoneSceneNode.h index 8f4fb8234..bd8efaa89 100644 --- a/irr/include/IBoneSceneNode.h +++ b/irr/include/IBoneSceneNode.h @@ -28,15 +28,9 @@ public: //! Returns the relative transformation of the scene node. // virtual core::matrix4 getRelativeTransformation() const = 0; - //! The animation method. - void OnAnimate(u32 timeMs) override = 0; - //! The render method. /** Does nothing as bones are not visible. */ void render() override {} - - //! Updates the absolute position based on the relative and the parents position - virtual void updateAbsolutePositionOfAllChildren() = 0; }; } // end namespace scene diff --git a/irr/include/ISceneNode.h b/irr/include/ISceneNode.h index f91fd6499..573715861 100644 --- a/irr/include/ISceneNode.h +++ b/irr/include/ISceneNode.h @@ -94,16 +94,12 @@ public: \param timeMs Current time in milliseconds. */ virtual void OnAnimate(u32 timeMs) { - if (IsVisible) { - // update absolute position - updateAbsolutePosition(); + if (!IsVisible && Children.empty()) + return; - // perform the post render process on all children - - ISceneNodeList::iterator it = Children.begin(); - for (; it != Children.end(); ++it) - (*it)->OnAnimate(timeMs); - } + updateAbsolutePosition(); + for (auto *child : Children) + child->OnAnimate(timeMs); } //! Renders the node. diff --git a/irr/include/SkinnedMesh.h b/irr/include/SkinnedMesh.h index 56496763b..5c125999a 100644 --- a/irr/include/SkinnedMesh.h +++ b/irr/include/SkinnedMesh.h @@ -8,11 +8,14 @@ #include "ISceneManager.h" #include "SMeshBuffer.h" #include "SSkinMeshBuffer.h" +#include "irrMath.h" +#include "matrix4.h" #include "quaternion.h" #include "vector3d.h" #include #include +#include namespace irr { @@ -314,8 +317,56 @@ public: //! The name of this joint std::optional Name; - //! Local matrix of this joint - core::matrix4 LocalMatrix; + struct Transform { + core::vector3df translation; + core::quaternion rotation; + core::vector3df scale{1}; + + core::matrix4 buildMatrix() const { + core::matrix4 T; + T.setTranslation(translation); + core::matrix4 R; + rotation.getMatrix_transposed(R); + core::matrix4 S; + S.setScale(scale); + return T * R * S; + } + }; + + //! Local transformation to be set by loaders. Mutated by animation. + //! If a matrix is used, this joint **must not** be animated, + //! because then the unique decomposition into translation, rotation and scale need not exist! + std::variant transform = Transform{}; + + Transform &getAnimatableTransform() { + if (std::holds_alternative(transform)) + return std::get(transform); + const auto &mat = std::get(transform); + Transform trs; + trs.translation = mat.getTranslation(); + trs.scale = mat.getScale(); + trs.rotation = core::quaternion( + mat.getRotationDegrees(trs.scale) * core::DEGTORAD); + transform = trs; + // TODO raise a warning if the recomposed matrix does not equal the decomposed. + return std::get(transform); + } + + void animate(f32 frame) { + if (keys.empty()) + return; + auto &transform = getAnimatableTransform(); + keys.updateTransform(frame, + transform.translation, + transform.rotation, + transform.scale); + } + + core::matrix4 buildLocalMatrix() const { + if (std::holds_alternative(transform)) + return std::get(transform); + return std::get(transform).buildMatrix(); + } //! List of child joints std::vector Children; @@ -334,11 +385,6 @@ public: core::matrix4 GlobalAnimatedMatrix; core::matrix4 LocalAnimatedMatrix; - //! These should be set by loaders. - core::vector3df Animatedposition; - core::vector3df Animatedscale; - core::quaternion Animatedrotation; - // The .x and .gltf formats pre-calculate this std::optional GlobalInversedMatrix; private: diff --git a/irr/src/CAnimatedMeshSceneNode.cpp b/irr/src/CAnimatedMeshSceneNode.cpp index cca7c0f92..c1f8afcfe 100644 --- a/irr/src/CAnimatedMeshSceneNode.cpp +++ b/irr/src/CAnimatedMeshSceneNode.cpp @@ -170,7 +170,11 @@ IMesh *CAnimatedMeshSceneNode::getMeshForCurrentFrame() // Update the skinned mesh for the current joint transforms. skinnedMesh->skinMesh(); - skinnedMesh->updateBoundingBox(); + skinnedMesh->updateBoundingBox(); + + Box = skinnedMesh->getBoundingBox(); + + setAutomaticCulling(EAC_OFF); return skinnedMesh; } @@ -187,6 +191,10 @@ void CAnimatedMeshSceneNode::OnAnimate(u32 timeMs) buildFrameNr(timeMs - LastTimeMs); LastTimeMs = timeMs; + // This needs to be done on animate, which is called recursively *before* + // anything is rendered so that the transformations of children are up to date + animateJoints(); + IAnimatedMeshSceneNode::OnAnimate(timeMs); } @@ -204,15 +212,8 @@ void CAnimatedMeshSceneNode::render() ++PassCount; scene::IMesh *m = getMeshForCurrentFrame(); - - if (m) { - Box = m->getBoundingBox(); - } else { -#ifdef _DEBUG - os::Printer::log("Animated Mesh returned no mesh to render.", ELL_WARNING); -#endif - return; - } + _IRR_DEBUG_BREAK_IF(!m); + Box = m->getBoundingBox(); // HACK driver->setTransform(video::ETS_WORLD, AbsoluteTransformation); @@ -237,6 +238,7 @@ void CAnimatedMeshSceneNode::render() driver->setTransform(video::ETS_WORLD, AbsoluteTransformation); // for debug purposes only: + // DebugDataVisible = ~0; if (DebugDataVisible && PassCount == 1) { video::SMaterial debug_mat; debug_mat.AntiAliasing = video::EAAM_OFF; @@ -559,7 +561,7 @@ void CAnimatedMeshSceneNode::setRenderFromIdentity(bool enable) } //! updates the joint positions of this mesh -void CAnimatedMeshSceneNode::animateJoints(bool CalculateAbsolutePositions) +void CAnimatedMeshSceneNode::animateJoints() { if (Mesh && Mesh->getMeshType() == EAMT_SKINNED) { checkJoints(); @@ -614,15 +616,6 @@ void CAnimatedMeshSceneNode::animateJoints(bool CalculateAbsolutePositions) // TransitingBlend)); } } - - if (CalculateAbsolutePositions) { - //---slow--- - for (u32 n = 0; n < JointChildSceneNodes.size(); ++n) { - if (JointChildSceneNodes[n]->getParent() == this) { - JointChildSceneNodes[n]->updateAbsolutePositionOfAllChildren(); // temp, should be an option - } - } - } } } diff --git a/irr/src/CAnimatedMeshSceneNode.h b/irr/src/CAnimatedMeshSceneNode.h index 9b1d7d4bd..b8e5fd245 100644 --- a/irr/src/CAnimatedMeshSceneNode.h +++ b/irr/src/CAnimatedMeshSceneNode.h @@ -117,12 +117,12 @@ public: //! updates the absolute position based on the relative and the parents position void updateAbsolutePosition() override; - //! Sets the transition time in seconds (note: This needs to enable joints, and setJointmode maybe set to 2) + //! Sets the transition time in seconds (note: This needs to enable joints) //! you must call animateJoints(), or the mesh will not animate void setTransitionTime(f32 Time) override; //! updates the joint positions of this mesh - void animateJoints(bool CalculateAbsolutePositions = true) override; + void animateJoints() override; //! render mesh ignoring its transformation. Used with ragdolls. (culling is unaffected) void setRenderFromIdentity(bool On) override; diff --git a/irr/src/CB3DMeshFileLoader.cpp b/irr/src/CB3DMeshFileLoader.cpp index 51342f451..f3cd2f179 100644 --- a/irr/src/CB3DMeshFileLoader.cpp +++ b/irr/src/CB3DMeshFileLoader.cpp @@ -143,31 +143,25 @@ bool CB3DMeshFileLoader::readChunkNODE(SkinnedMesh::SJoint *inJoint) os::Printer::log(logStr.c_str(), joint->Name.value_or("").c_str(), ELL_DEBUG); #endif - f32 position[3], scale[3], rotation[4]; + SkinnedMesh::SJoint::Transform transform; + { + f32 t[3], s[3], r[4]; - readFloats(position, 3); - readFloats(scale, 3); - readFloats(rotation, 4); + readFloats(t, 3); + readFloats(s, 3); + readFloats(r, 4); - joint->Animatedposition = core::vector3df(position[0], position[1], position[2]); - joint->Animatedscale = core::vector3df(scale[0], scale[1], scale[2]); - joint->Animatedrotation = core::quaternion(rotation[1], rotation[2], rotation[3], rotation[0]); - - // Build LocalMatrix: - - core::matrix4 positionMatrix; - positionMatrix.setTranslation(joint->Animatedposition); - core::matrix4 scaleMatrix; - scaleMatrix.setScale(joint->Animatedscale); - core::matrix4 rotationMatrix; - joint->Animatedrotation.getMatrix_transposed(rotationMatrix); - - joint->LocalMatrix = positionMatrix * rotationMatrix * scaleMatrix; + joint->transform = transform = { + {t[0], t[1], t[2]}, + {r[1], r[2], r[3], r[0]}, + {s[0], s[1], s[2]}, + }; + } if (inJoint) - joint->GlobalMatrix = inJoint->GlobalMatrix * joint->LocalMatrix; + joint->GlobalMatrix = inJoint->GlobalMatrix * joint->buildLocalMatrix(); else - joint->GlobalMatrix = joint->LocalMatrix; + joint->GlobalMatrix = joint->buildLocalMatrix(); while (B3dStack.getLast().startposition + B3dStack.getLast().length > B3DFile->getPos()) // this chunk repeats { diff --git a/irr/src/CBoneSceneNode.cpp b/irr/src/CBoneSceneNode.cpp deleted file mode 100644 index fcb5086ee..000000000 --- a/irr/src/CBoneSceneNode.cpp +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (C) 2002-2012 Nikolaus Gebhardt -// This file is part of the "Irrlicht Engine". -// For conditions of distribution and use, see copyright notice in irrlicht.h - -#include "CBoneSceneNode.h" - -#include - -namespace irr -{ -namespace scene -{ - -void CBoneSceneNode::OnAnimate(u32 timeMs) -{ - if (IsVisible) { - // update absolute position - // updateAbsolutePosition(); - - // perform the post render process on all children - ISceneNodeList::iterator it = Children.begin(); - for (; it != Children.end(); ++it) - (*it)->OnAnimate(timeMs); - } -} - -void CBoneSceneNode::helper_updateAbsolutePositionOfAllChildren(ISceneNode *Node) -{ - Node->updateAbsolutePosition(); - - ISceneNodeList::const_iterator it = Node->getChildren().begin(); - for (; it != Node->getChildren().end(); ++it) { - helper_updateAbsolutePositionOfAllChildren((*it)); - } -} - -void CBoneSceneNode::updateAbsolutePositionOfAllChildren() -{ - helper_updateAbsolutePositionOfAllChildren(this); -} - -} // namespace scene -} // namespace irr diff --git a/irr/src/CBoneSceneNode.h b/irr/src/CBoneSceneNode.h index 9612f8115..6dd4e2440 100644 --- a/irr/src/CBoneSceneNode.h +++ b/irr/src/CBoneSceneNode.h @@ -40,13 +40,6 @@ public: return Box; } - void OnAnimate(u32 timeMs) override; - - void updateAbsolutePositionOfAllChildren() override; - -private: - void helper_updateAbsolutePositionOfAllChildren(ISceneNode *Node); - const u32 BoneIndex; // Bogus box; bone scene nodes are not rendered anyways. diff --git a/irr/src/CGLTFMeshFileLoader.cpp b/irr/src/CGLTFMeshFileLoader.cpp index 3f2096f40..794b5af94 100644 --- a/irr/src/CGLTFMeshFileLoader.cpp +++ b/irr/src/CGLTFMeshFileLoader.cpp @@ -539,34 +539,25 @@ static core::matrix4 loadTransform(const tiniergltf::Node::Matrix &m, SkinnedMes mat[i] = static_cast(m[i]); mat = convertHandedness(mat); - // Decompose the matrix into translation, scale, and rotation. - joint->Animatedposition = mat.getTranslation(); - - auto scale = mat.getScale(); - joint->Animatedscale = scale; - joint->Animatedrotation = mat.getRotationRadians(scale); - // Invert the rotation because it is applied using `getMatrix_transposed`, - // which again inverts. - joint->Animatedrotation.makeInverse(); - + // Note: "When a node is targeted for animation [...], + // only TRS properties MAY be present; matrix MUST NOT be present." + // Thus we MUST NOT do any decomposition, which in general need not exist. + joint->transform = mat; return mat; } static core::matrix4 loadTransform(const tiniergltf::Node::TRS &trs, SkinnedMesh::SJoint *joint) { - const auto &trans = trs.translation; - const auto &rot = trs.rotation; - const auto &scale = trs.scale; - core::matrix4 transMat; - joint->Animatedposition = convertHandedness(core::vector3df(trans[0], trans[1], trans[2])); - transMat.setTranslation(joint->Animatedposition); - core::matrix4 rotMat; - joint->Animatedrotation = convertHandedness(core::quaternion(rot[0], rot[1], rot[2], rot[3])); - core::quaternion(joint->Animatedrotation).getMatrix_transposed(rotMat); - joint->Animatedscale = core::vector3df(scale[0], scale[1], scale[2]); - core::matrix4 scaleMat; - scaleMat.setScale(joint->Animatedscale); - return transMat * rotMat * scaleMat; + const auto &t = trs.translation; + const auto &r = trs.rotation; + const auto &s = trs.scale; + SkinnedMesh::SJoint::Transform transform{ + convertHandedness(core::vector3df(t[0], t[1], t[2])), + convertHandedness(core::quaternion(r[0], r[1], r[2], r[3])), + core::vector3df(s[0], s[1], s[2]), + }; + joint->transform = transform; + return transform.buildMatrix(); } static core::matrix4 loadTransform(std::optional> transform, @@ -584,8 +575,7 @@ void SelfType::MeshExtractor::loadNode( const auto &node = m_gltf_model.nodes->at(nodeIdx); auto *joint = m_irr_model->addJoint(parent); const core::matrix4 transform = loadTransform(node.transform, joint); - joint->LocalMatrix = transform; - joint->GlobalMatrix = parent ? parent->GlobalMatrix * joint->LocalMatrix : joint->LocalMatrix; + joint->GlobalMatrix = parent ? parent->GlobalMatrix * transform : transform; if (node.name.has_value()) { joint->Name = node.name->c_str(); } diff --git a/irr/src/CMakeLists.txt b/irr/src/CMakeLists.txt index 83da89c11..c752d7d31 100644 --- a/irr/src/CMakeLists.txt +++ b/irr/src/CMakeLists.txt @@ -320,7 +320,6 @@ set(IRRMESHLOADER add_library(IRRMESHOBJ OBJECT SkinnedMesh.cpp - CBoneSceneNode.cpp CMeshSceneNode.cpp CAnimatedMeshSceneNode.cpp ${IRRMESHLOADER} diff --git a/irr/src/CXMeshFileLoader.cpp b/irr/src/CXMeshFileLoader.cpp index d93502d6b..295e14feb 100644 --- a/irr/src/CXMeshFileLoader.cpp +++ b/irr/src/CXMeshFileLoader.cpp @@ -552,12 +552,9 @@ bool CXMeshFileLoader::parseDataObjectFrame(SkinnedMesh::SJoint *Parent) if (!parseDataObjectFrame(joint)) return false; } else if (objectName == "FrameTransformMatrix") { - if (!parseDataObjectTransformationMatrix(joint->LocalMatrix)) + joint->transform = core::matrix4(); + if (!parseDataObjectTransformationMatrix(std::get(joint->transform))) return false; - - // joint->LocalAnimatedMatrix - // joint->LocalAnimatedMatrix.makeInverse(); - // joint->LocalMatrix=tmp*joint->LocalAnimatedMatrix; } else if (objectName == "Mesh") { /* frame.Meshes.push_back(SXMesh()); diff --git a/irr/src/SkinnedMesh.cpp b/irr/src/SkinnedMesh.cpp index 30501cd45..10fa3082e 100644 --- a/irr/src/SkinnedMesh.cpp +++ b/irr/src/SkinnedMesh.cpp @@ -7,7 +7,11 @@ #include "CBoneSceneNode.h" #include "IAnimatedMeshSceneNode.h" #include "SSkinMeshBuffer.h" +#include "irrMath.h" +#include "matrix4.h" #include "os.h" +#include "vector3d.h" +#include #include #include @@ -73,13 +77,8 @@ void SkinnedMesh::animateMesh(f32 frame) LastAnimatedFrame = frame; SkinnedLastFrame = false; - for (auto *joint : AllJoints) { - // The joints can be animated here with no input from their parents - joint->keys.updateTransform(frame, - joint->Animatedposition, - joint->Animatedrotation, - joint->Animatedscale); - } + for (auto *joint : AllJoints) + joint->animate(frame); // Note: // LocalAnimatedMatrix needs to be built at some point, but this function may be called lots of times for @@ -98,56 +97,7 @@ void SkinnedMesh::buildAllLocalAnimatedMatrices() { for (auto *joint : AllJoints) { // Could be faster: - - if (!joint->keys.empty()) { - // IRR_TEST_BROKEN_QUATERNION_USE: TODO - switched to getMatrix_transposed instead of getMatrix for downward compatibility. - // Not tested so far if this was correct or wrong before quaternion fix! - // Note that using getMatrix_transposed inverts the rotation. - joint->Animatedrotation.getMatrix_transposed(joint->LocalAnimatedMatrix); - - // --- joint->LocalAnimatedMatrix *= joint->Animatedrotation.getMatrix() --- - f32 *m1 = joint->LocalAnimatedMatrix.pointer(); - core::vector3df &Pos = joint->Animatedposition; - m1[0] += Pos.X * m1[3]; - m1[1] += Pos.Y * m1[3]; - m1[2] += Pos.Z * m1[3]; - m1[4] += Pos.X * m1[7]; - m1[5] += Pos.Y * m1[7]; - m1[6] += Pos.Z * m1[7]; - m1[8] += Pos.X * m1[11]; - m1[9] += Pos.Y * m1[11]; - m1[10] += Pos.Z * m1[11]; - m1[12] += Pos.X * m1[15]; - m1[13] += Pos.Y * m1[15]; - m1[14] += Pos.Z * m1[15]; - // ----------------------------------- - - if (!joint->keys.scale.empty()) { - /* - core::matrix4 scaleMatrix; - scaleMatrix.setScale(joint->Animatedscale); - joint->LocalAnimatedMatrix *= scaleMatrix; - */ - - // -------- joint->LocalAnimatedMatrix *= scaleMatrix ----------------- - core::matrix4 &mat = joint->LocalAnimatedMatrix; - mat[0] *= joint->Animatedscale.X; - mat[1] *= joint->Animatedscale.X; - mat[2] *= joint->Animatedscale.X; - mat[3] *= joint->Animatedscale.X; - mat[4] *= joint->Animatedscale.Y; - mat[5] *= joint->Animatedscale.Y; - mat[6] *= joint->Animatedscale.Y; - mat[7] *= joint->Animatedscale.Y; - mat[8] *= joint->Animatedscale.Z; - mat[9] *= joint->Animatedscale.Z; - mat[10] *= joint->Animatedscale.Z; - mat[11] *= joint->Animatedscale.Z; - // ----------------------------------- - } - } else { - joint->LocalAnimatedMatrix = joint->LocalMatrix; - } + joint->LocalAnimatedMatrix = joint->buildLocalMatrix(); } SkinnedLastFrame = false; } @@ -396,12 +346,13 @@ void SkinnedMesh::calculateGlobalMatrices(SJoint *joint, SJoint *parentJoint) return; } + const auto local_matrix = joint->buildLocalMatrix(); if (!parentJoint) - joint->GlobalMatrix = joint->LocalMatrix; + joint->GlobalMatrix = local_matrix; else - joint->GlobalMatrix = parentJoint->GlobalMatrix * joint->LocalMatrix; + joint->GlobalMatrix = parentJoint->GlobalMatrix * local_matrix; - joint->LocalAnimatedMatrix = joint->LocalMatrix; + joint->LocalAnimatedMatrix = local_matrix; joint->GlobalAnimatedMatrix = joint->GlobalMatrix; if (!joint->GlobalInversedMatrix.has_value()) { // might be pre calculated @@ -689,7 +640,7 @@ void SkinnedMesh::recoverJointsFromMesh(std::vector &jointChil node->setRotation(joint->LocalAnimatedMatrix.getRotationDegrees()); node->setScale(joint->LocalAnimatedMatrix.getScale()); - node->updateAbsolutePosition(); + node->updateAbsolutePosition(); // WTF } } diff --git a/lib/tiniergltf/tiniergltf.hpp b/lib/tiniergltf/tiniergltf.hpp index 06e2f5356..81ac10ec8 100644 --- a/lib/tiniergltf/tiniergltf.hpp +++ b/lib/tiniergltf/tiniergltf.hpp @@ -916,12 +916,7 @@ struct Node { std::optional skin; std::optional> weights; Node(const Json::Value &o) - : transform(Matrix { - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1 - }) + : transform(TRS{}) { check(o.isObject()); if (o.isMember("camera")) { diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index e2535d55b..16aac49a7 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -1401,6 +1401,8 @@ void GenericCAO::updateBones(f32 dtime) if (m_bone_override.empty()) return; + // FIXME these need to be applied at a different point in time + // in order to be relative to the animated bone positions of the current frame for (auto &it : m_bone_override) { std::string bone_name = it.first; scene::IBoneSceneNode* bone = m_animated_meshnode->getJointNode(bone_name.c_str()); diff --git a/src/unittest/test_irr_gltf_mesh_loader.cpp b/src/unittest/test_irr_gltf_mesh_loader.cpp index f689838db..391a9fd29 100644 --- a/src/unittest/test_irr_gltf_mesh_loader.cpp +++ b/src/unittest/test_irr_gltf_mesh_loader.cpp @@ -413,17 +413,23 @@ SECTION("simple skin") 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(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(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")