1
0
Fork 0
mirror of https://github.com/luanti-org/luanti.git synced 2025-06-27 16:36:03 +00:00
This commit is contained in:
Lars Mueller 2025-01-16 17:13:59 +01:00
parent 769c472ceb
commit abc2e54c07
16 changed files with 137 additions and 224 deletions

View file

@ -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. */

View file

@ -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

View file

@ -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.

View file

@ -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 <optional>
#include <string>
#include <variant>
namespace irr
{
@ -314,8 +317,56 @@ public:
//! The name of this joint
std::optional<std::string> 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<core::matrix4, Transform> transform = Transform{};
Transform &getAnimatableTransform() {
if (std::holds_alternative<Transform>(transform))
return std::get<Transform>(transform);
const auto &mat = std::get<core::matrix4>(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>(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<core::matrix4>(transform))
return std::get<core::matrix4>(transform);
return std::get<Transform>(transform).buildMatrix();
}
//! List of child joints
std::vector<SJoint *> 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<core::matrix4> GlobalInversedMatrix;
private:

View file

@ -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
}
}
}
}
}

View file

@ -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;

View file

@ -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
{

View file

@ -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 <optional>
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

View file

@ -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.

View file

@ -539,34 +539,25 @@ static core::matrix4 loadTransform(const tiniergltf::Node::Matrix &m, SkinnedMes
mat[i] = static_cast<f32>(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<std::variant<tiniergltf::Node::Matrix, tiniergltf::Node::TRS>> 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();
}

View file

@ -320,7 +320,6 @@ set(IRRMESHLOADER
add_library(IRRMESHOBJ OBJECT
SkinnedMesh.cpp
CBoneSceneNode.cpp
CMeshSceneNode.cpp
CAnimatedMeshSceneNode.cpp
${IRRMESHLOADER}

View file

@ -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<core::matrix4>(joint->transform)))
return false;
// joint->LocalAnimatedMatrix
// joint->LocalAnimatedMatrix.makeInverse();
// joint->LocalMatrix=tmp*joint->LocalAnimatedMatrix;
} else if (objectName == "Mesh") {
/*
frame.Meshes.push_back(SXMesh());

View file

@ -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 <variant>
#include <vector>
#include <cassert>
@ -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<IBoneSceneNode *> &jointChil
node->setRotation(joint->LocalAnimatedMatrix.getRotationDegrees());
node->setScale(joint->LocalAnimatedMatrix.getScale());
node->updateAbsolutePosition();
node->updateAbsolutePosition(); // WTF
}
}

View file

@ -916,12 +916,7 @@ struct Node {
std::optional<std::size_t> skin;
std::optional<std::vector<double>> 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")) {

View file

@ -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());

View file

@ -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<SkinnedMesh::SJoint::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<SkinnedMesh::SJoint::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")