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-20 02:39:14 +01:00
parent d10abba38c
commit 503d7cf081
13 changed files with 434 additions and 498 deletions

View file

@ -16,17 +16,32 @@ namespace scene
class IBoneSceneNode : public ISceneNode
{
public:
IBoneSceneNode(ISceneNode *parent, ISceneManager *mgr, s32 id = -1) :
ISceneNode(parent, mgr, id) {}
IBoneSceneNode(ISceneNode *parent, ISceneManager *mgr,
s32 id = -1, u32 boneIndex = 0,
const std::optional<std::string> &boneName = std::nullopt)
:
ISceneNode(parent, mgr, id),
BoneIndex(boneIndex)
{
setName(boneName);
}
//! Get the index of the bone
virtual u32 getBoneIndex() const = 0;
//! Returns the index of the bone
u32 getBoneIndex() const
{
return BoneIndex;
}
//! Get the axis aligned bounding box of this node
const core::aabbox3d<f32> &getBoundingBox() const override = 0;
//! returns the axis aligned bounding box of this node
const core::aabbox3d<f32> &getBoundingBox() const override
{
return Box;
}
//! Returns the relative transformation of the scene node.
// virtual core::matrix4 getRelativeTransformation() const = 0;
const u32 BoneIndex;
// Bogus box; bone scene nodes are not rendered anyways.
static constexpr core::aabbox3d<f32> Box = {{0, 0, 0}};
//! The render method.
/** Does nothing as bones are not visible. */

View file

@ -8,14 +8,17 @@
#include "ISceneManager.h"
#include "SMeshBuffer.h"
#include "SSkinMeshBuffer.h"
#include "aabbox3d.h"
#include "irrMath.h"
#include "matrix4.h"
#include "quaternion.h"
#include "vector3d.h"
#include "Transform.h"
#include <optional>
#include <string>
#include <variant>
#include <vector>
namespace irr
{
@ -40,9 +43,8 @@ public:
//! constructor
SkinnedMesh(SourceFormat src_format) :
EndFrame(0.f), FramesPerSecond(25.f),
LastAnimatedFrame(-1), SkinnedLastFrame(false),
HasAnimation(false), PreparedForSkinning(false),
AnimateNormals(true), HardwareSkinning(false),
AnimateNormals(true),
SrcFormat(src_format)
{
SkinningBuffers = &LocalBuffers;
@ -67,14 +69,16 @@ public:
The actual speed is set in the scene node the mesh is instantiated in.*/
void setAnimationSpeed(f32 fps) override;
//! returns the animated mesh for the given frame
IMesh *getMesh(f32) override;
//! **Must not be called**.
//! TODO refactor Irrlicht so that we need not implement this.
IMesh *getMesh(f32) override { assert(false); };
//! Animates joints based on frame input
void animateMesh(f32 frame);
//! Turns the given array of local matrices into an array of global matrices
//! by multiplying with respective parent matrices.
void calculateGlobalMatrices(std::vector<core::matrix4> &matrices);
//! Performs a software skin on this mesh based of joint positions
void skinMesh();
//! Performs a software skin on this mesh based on the given joint matrices
void skinMesh(const std::vector<core::matrix4> &animated_transforms);
//! returns amount of mesh buffers.
u32 getMeshBufferCount() const override;
@ -143,10 +147,6 @@ public:
return !HasAnimation;
}
//! Allows to enable hardware skinning.
/* This feature is not implemented in Irrlicht yet */
bool setHardwareSkinning(bool on);
//! Refreshes vertex data cached in joints such as positions and normals
void refreshJointCache();
@ -155,16 +155,9 @@ public:
void updateBoundingBox();
//! Recovers the joints from the mesh
void recoverJointsFromMesh(std::vector<IBoneSceneNode *> &jointChildSceneNodes);
//! Transfers the joint data to the mesh
void transferJointsToMesh(const std::vector<IBoneSceneNode *> &jointChildSceneNodes);
//! Creates an array of joints from this mesh as children of node
void addJoints(std::vector<IBoneSceneNode *> &jointChildSceneNodes,
IAnimatedMeshSceneNode *node,
ISceneManager *smgr);
std::vector<IBoneSceneNode *> addJoints(
IAnimatedMeshSceneNode *node, ISceneManager *smgr);
//! A vertex weight
struct SWeight
@ -291,15 +284,14 @@ public:
});
}
void updateTransform(f32 frame,
core::vector3df &t, core::quaternion &r, core::vector3df &s) const
void updateTransform(f32 frame, core::Transform &transform) const
{
if (auto pos = position.get(frame))
t = *pos;
transform.translation = *pos;
if (auto rot = rotation.get(frame))
r = *rot;
transform.rotation = *rot;
if (auto scl = scale.get(frame))
s = *scl;
transform.scale = *scl;
}
void cleanup() {
@ -317,55 +309,24 @@ public:
//! The name of this joint
std::optional<std::string> Name;
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{};
using VariantTransform = std::variant<core::Transform, core::matrix4>;
VariantTransform transform{core::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) {
VariantTransform animate(f32 frame) const {
if (keys.empty())
return;
auto &transform = getAnimatableTransform();
keys.updateTransform(frame,
transform.translation,
transform.rotation,
transform.scale);
}
return transform;
core::matrix4 buildLocalMatrix() const {
if (std::holds_alternative<core::matrix4>(transform))
return std::get<core::matrix4>(transform);
return std::get<Transform>(transform).buildMatrix();
if (std::holds_alternative<core::matrix4>(transform)) {
// TODO raise a warning: Attempt to animate a static joint.
return transform;
}
auto trs = std::get<core::Transform>(transform);
keys.updateTransform(frame, trs);
return {trs};
}
//! List of child joints
@ -382,33 +343,35 @@ public:
//! Unnecessary for loaders, will be overwritten on finalize
core::matrix4 GlobalMatrix; // loaders may still choose to set this (temporarily) to calculate absolute vertex data.
core::matrix4 GlobalAnimatedMatrix;
core::matrix4 LocalAnimatedMatrix;
// The .x and .gltf formats pre-calculate this
std::optional<core::matrix4> GlobalInversedMatrix;
private:
//! Internal members used by SkinnedMesh
friend class SkinnedMesh;
// TODO friends?
u16 JointID; // TODO refactor away: pointers -> IDs
std::optional<u16> ParentJointID;
};
//! Animates joints based on frame input
std::vector<SJoint::VariantTransform> animateMesh(f32 frame);
//! TODO
core::aabbox3df calculateBoundingBox(
const std::vector<SJoint::VariantTransform> &transforms);
const std::vector<SJoint *> &getAllJoints() const {
return AllJoints;
}
protected:
void checkForAnimation();
bool checkForAnimation() const;
void topoSortJoints();
void prepareForSkinning();
void normalizeWeights();
void buildAllLocalAnimatedMatrices();
void buildAllGlobalAnimatedMatrices(SJoint *Joint = 0, SJoint *ParentJoint = 0);
void calculateGlobalMatrices(SJoint *Joint, SJoint *ParentJoint);
void skinJoint(SJoint *Joint, SJoint *ParentJoint);
void calculateTangents(core::vector3df &normal,
core::vector3df &tangent, core::vector3df &binormal,
const core::vector3df &vt1, const core::vector3df &vt2, const core::vector3df &vt3,
@ -420,8 +383,8 @@ protected:
//! Mapping from meshbuffer number to bindable texture slot
std::vector<u32> TextureSlots;
//! Joints, topologically sorted (parents come before their children).
std::vector<SJoint *> AllJoints;
std::vector<SJoint *> RootJoints;
// bool can't be used here because std::vector<bool>
// doesn't allow taking a reference to individual elements.
@ -432,15 +395,17 @@ protected:
f32 EndFrame;
f32 FramesPerSecond;
f32 LastAnimatedFrame;
bool SkinnedLastFrame;
bool HasAnimation;
bool PreparedForSkinning;
bool AnimateNormals;
<<<<<<< HEAD
bool HardwareSkinning;
SourceFormat SrcFormat;
||||||| parent of 44f1cd940 (progress)
bool HardwareSkinning;
=======
>>>>>>> 44f1cd940 (progress)
};
// Interface for mesh loaders

44
irr/include/Transform.h Normal file
View file

@ -0,0 +1,44 @@
#pragma once
#include "irrMath.h"
#include <matrix4.h>
#include <variant>
#include <vector3d.h>
#include <quaternion.h>
namespace irr
{
namespace core
{
struct Transform {
vector3df translation;
quaternion rotation;
vector3df scale{1};
// Tries to decompose the matrix, if there is one.
static Transform decompose(const core::matrix4 &mat)
{
auto scale = mat.getScale();
return {
mat.getTranslation(),
quaternion(mat.getRotationDegrees(scale) * DEGTORAD),
scale,
};
}
matrix4 buildMatrix() const
{
matrix4 T;
T.setTranslation(translation);
matrix4 R;
// TODO this is sussy. probably shouldn't be doing this.
rotation.getMatrix_transposed(R);
matrix4 S;
S.setScale(scale);
return T * R * S;
}
};
} // end namespace core
} // end namespace irr

View file

@ -3,9 +3,13 @@
// For conditions of distribution and use, see copyright notice in irrlicht.h
#include "CAnimatedMeshSceneNode.h"
#include "CBoneSceneNode.h"
#include "IVideoDriver.h"
#include "ISceneManager.h"
#include "MatrixBoneSceneNode.h"
#include "S3DVertex.h"
#include "Transform.h"
#include "matrix4.h"
#include "os.h"
#include "SkinnedMesh.h"
#include "IDummyTransformationSceneNode.h"
@ -17,6 +21,7 @@
#include "IFileSystem.h"
#include "quaternion.h"
#include <algorithm>
#include <cstddef>
namespace irr
{
@ -30,7 +35,7 @@ CAnimatedMeshSceneNode::CAnimatedMeshSceneNode(IAnimatedMesh *mesh,
const core::vector3df &rotation,
const core::vector3df &scale) :
IAnimatedMeshSceneNode(parent, mgr, id, position, rotation, scale),
Mesh(0),
Mesh(nullptr),
StartFrame(0), EndFrame(0), FramesPerSecond(0.025f),
CurrentFrameNr(0.f), LastTimeMs(0),
TransitionTime(0), Transiting(0.f), TransitingBlend(0.f),
@ -143,25 +148,27 @@ void CAnimatedMeshSceneNode::OnRegisterSceneNode()
IMesh *CAnimatedMeshSceneNode::getMeshForCurrentFrame()
{
if (Mesh->getMeshType() != EAMT_SKINNED) {
if (Mesh->getMeshType() != EAMT_SKINNED)
return Mesh->getMesh(getFrameNr());
} else {
// As multiple scene nodes may be sharing the same skinned mesh, we have to
// re-animate it every frame to ensure that this node gets the mesh that it needs.
SkinnedMesh *skinnedMesh = static_cast<SkinnedMesh *>(Mesh);
// As multiple scene nodes may be sharing the same skinned mesh, we have to
// re-animate it every frame to ensure that this node gets the mesh that it needs.
skinnedMesh->transferJointsToMesh(JointChildSceneNodes);
auto *skinnedMesh = static_cast<SkinnedMesh *>(Mesh);
// Update the skinned mesh for the current joint transforms.
skinnedMesh->skinMesh();
std::vector<core::matrix4> matrices;
matrices.reserve(JointChildSceneNodes.size());
for (auto *node : JointChildSceneNodes)
matrices.push_back(node->getRelativeTransformation());
skinnedMesh->calculateGlobalMatrices(matrices);
skinnedMesh->updateBoundingBox();
skinnedMesh->skinMesh(matrices);
Box = skinnedMesh->getBoundingBox();
skinnedMesh->updateBoundingBox();
return skinnedMesh;
}
Box = skinnedMesh->getBoundingBox();
return skinnedMesh;
}
//! OnAnimate() is called just before rendering the whole scene.
@ -268,13 +275,13 @@ void CAnimatedMeshSceneNode::render()
if (Mesh->getMeshType() == EAMT_SKINNED) {
// draw skeleton
for (auto *joint : ((SkinnedMesh *)Mesh)->getAllJoints()) {
/*for (auto *joint : ((SkinnedMesh *)Mesh)->getAllJoints()) {
for (const auto *childJoint : joint->Children) {
driver->draw3DLine(joint->GlobalAnimatedMatrix.getTranslation(),
childJoint->GlobalAnimatedMatrix.getTranslation(),
video::SColor(255, 51, 66, 255));
}
}
}*/
}
}
@ -526,6 +533,42 @@ void CAnimatedMeshSceneNode::setRenderFromIdentity(bool enable)
RenderFromIdentity = enable;
}
void CAnimatedMeshSceneNode::addJoints()
{
const auto &joints = static_cast<SkinnedMesh*>(Mesh)->getAllJoints();
JointChildSceneNodes.clear();
JointChildSceneNodes.reserve(joints.size());
for (size_t i = 0; i < joints.size(); ++i) {
const auto *joint = joints[i];
ISceneNode *parent = this;
if (joint->ParentJointID)
parent = JointChildSceneNodes.at(*joint->ParentJointID); // exists because of topo. order
assert(parent);
if (const auto *matrix = std::get_if<core::matrix4>(&joint->transform)) {
JointChildSceneNodes.push_back(new MatrixBoneSceneNode(
parent, SceneManager, 0, i, joint->Name, *matrix));
} else {
JointChildSceneNodes.push_back(new CBoneSceneNode(
parent, SceneManager, 0, i, joint->Name,
std::get<core::Transform>(joint->transform)));
}
}
}
void CAnimatedMeshSceneNode::updateJointSceneNodes(
const std::vector<SkinnedMesh::SJoint::VariantTransform> &transforms)
{
for (size_t i = 0; i < transforms.size(); ++i) {
const auto &transform = transforms[i];
IBoneSceneNode *node = JointChildSceneNodes[i];
if (const auto *trs = std::get_if<core::Transform>(&transform)) {
dynamic_cast<CBoneSceneNode*>(node)->setTransform(*trs);
} else {
assert(dynamic_cast<MatrixBoneSceneNode*>(node));
}
}
}
//! updates the joint positions of this mesh
void CAnimatedMeshSceneNode::animateJoints()
{
@ -536,8 +579,7 @@ void CAnimatedMeshSceneNode::animateJoints()
SkinnedMesh *skinnedMesh = static_cast<SkinnedMesh *>(Mesh);
skinnedMesh->animateMesh(getFrameNr());
skinnedMesh->recoverJointsFromMesh(JointChildSceneNodes);
updateJointSceneNodes(skinnedMesh->animateMesh(getFrameNr()));
//-----------------------------------------
// Transition
@ -593,11 +635,7 @@ void CAnimatedMeshSceneNode::checkJoints()
if (!JointsUsed) {
for (u32 i = 0; i < JointChildSceneNodes.size(); ++i)
removeChild(JointChildSceneNodes[i]);
JointChildSceneNodes.clear();
// Create joints for SkinnedMesh
((SkinnedMesh *)Mesh)->addJoints(JointChildSceneNodes, this, SceneManager);
((SkinnedMesh *)Mesh)->recoverJointsFromMesh(JointChildSceneNodes);
addJoints();
JointsUsed = true;
}

View file

@ -7,6 +7,7 @@
#include "IAnimatedMeshSceneNode.h"
#include "IAnimatedMesh.h"
#include "SkinnedMesh.h"
#include "matrix4.h"
namespace irr
@ -123,9 +124,13 @@ public:
//! you must call animateJoints(), or the mesh will not animate
void setTransitionTime(f32 Time) override;
void updateJointSceneNodes(const std::vector<SkinnedMesh::SJoint::VariantTransform> &transforms);
//! updates the joint positions of this mesh
void animateJoints() override;
void addJoints();
//! render mesh ignoring its transformation. Used with ragdolls. (culling is unaffected)
void setRenderFromIdentity(bool On) override;

View file

@ -143,7 +143,7 @@ bool CB3DMeshFileLoader::readChunkNODE(SkinnedMesh::SJoint *inJoint)
os::Printer::log(logStr.c_str(), joint->Name.value_or("").c_str(), ELL_DEBUG);
#endif
SkinnedMesh::SJoint::Transform transform;
core::Transform transform;
{
f32 t[3], s[3], r[4];
@ -159,9 +159,9 @@ bool CB3DMeshFileLoader::readChunkNODE(SkinnedMesh::SJoint *inJoint)
}
if (inJoint)
joint->GlobalMatrix = inJoint->GlobalMatrix * joint->buildLocalMatrix();
joint->GlobalMatrix = inJoint->GlobalMatrix * transform.buildMatrix();
else
joint->GlobalMatrix = joint->buildLocalMatrix();
joint->GlobalMatrix = transform.buildMatrix();
while (B3dStack.getLast().startposition + B3dStack.getLast().length > B3DFile->getPos()) // this chunk repeats
{

View file

@ -7,6 +7,7 @@
// Used with SkinnedMesh and IAnimatedMeshSceneNode, for boned meshes
#include "IBoneSceneNode.h"
#include "Transform.h"
#include <optional>
@ -21,29 +22,26 @@ public:
//! constructor
CBoneSceneNode(ISceneNode *parent, ISceneManager *mgr,
s32 id = -1, u32 boneIndex = 0,
const std::optional<std::string> &boneName = std::nullopt) :
IBoneSceneNode(parent, mgr, id),
BoneIndex(boneIndex)
const std::optional<std::string> &boneName = std::nullopt,
const core::Transform &transform = {}) :
IBoneSceneNode(parent, mgr, id, boneIndex, boneName)
{
setName(boneName);
setTransform(transform);
}
//! Returns the index of the bone
u32 getBoneIndex() const override
void setTransform(const core::Transform &transform)
{
return BoneIndex;
setPosition(transform.translation);
{
core::vector3df euler;
auto rot = transform.rotation;
// Invert to be consistent with setRotationDegrees
rot.makeInverse();
rot.toEuler(euler);
setRotation(euler * core::RADTODEG);
}
setScale(transform.scale);
}
//! returns the axis aligned bounding box of this node
const core::aabbox3d<f32> &getBoundingBox() const override
{
return Box;
}
const u32 BoneIndex;
// Bogus box; bone scene nodes are not rendered anyways.
static constexpr core::aabbox3d<f32> Box = {{0, 0, 0}};
};
} // end namespace scene

View file

@ -551,7 +551,7 @@ static core::matrix4 loadTransform(const tiniergltf::Node::TRS &trs, SkinnedMesh
const auto &t = trs.translation;
const auto &r = trs.rotation;
const auto &s = trs.scale;
SkinnedMesh::SJoint::Transform transform{
core::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]),

View file

@ -4,6 +4,7 @@
#include "CXMeshFileLoader.h"
#include "SkinnedMesh.h"
#include "Transform.h"
#include "os.h"
#include "fast_atof.h"
@ -552,9 +553,16 @@ bool CXMeshFileLoader::parseDataObjectFrame(SkinnedMesh::SJoint *Parent)
if (!parseDataObjectFrame(joint))
return false;
} else if (objectName == "FrameTransformMatrix") {
joint->transform = core::matrix4();
if (!parseDataObjectTransformationMatrix(std::get<core::matrix4>(joint->transform)))
core::matrix4 matrix;
if (!parseDataObjectTransformationMatrix(matrix))
return false;
auto transform = core::Transform::decompose(matrix);
// Try to decompose. If the recomposed matrix equals the old one with a liberal tolerance, use that.
if (transform.buildMatrix().equals(matrix, 1e-5)) {
joint->transform = transform;
} else {
joint->transform = matrix;
}
} else if (objectName == "Mesh") {
/*
frame.Meshes.push_back(SXMesh());

View file

@ -0,0 +1,56 @@
// Copyright (C) 2025 Joe Mama
// SPDX-License-Identifier: LGPL-2.1-or-later
#pragma once
#include "IBoneSceneNode.h"
#include "matrix4.h"
#include "vector3d.h"
#include <iostream>
#include <optional>
// We must represent some transforms differently:
// Bones can have static non-TRS matrix transforms,
// for example shearing (can not be decomposed at all)
// or negatively scaling an axis (can not be decomposed uniquely).
// Hence well-defined animation is not possible for such nodes
// (and in fact glTF even guarantees that they will never be animated).
namespace irr
{
namespace scene
{
class MatrixBoneSceneNode : public IBoneSceneNode
{
public:
//! constructor
MatrixBoneSceneNode(ISceneNode *parent, ISceneManager *mgr,
s32 id = -1, u32 boneIndex = 0,
const std::optional<std::string> &boneName = std::nullopt,
const core::matrix4 &matrix = core::IdentityMatrix) :
IBoneSceneNode(parent, mgr, id, boneIndex, boneName),
matrix(matrix)
{}
const core::matrix4 matrix;
// Matrix nodes should not be fake decomposed.
const core::vector3df &getPosition() const override { assert(false); }
const core::vector3df &getRotation() const override { assert(false); }
const core::vector3df &getScale() const override { assert(false); }
// This node should be static.
void setPosition(const core::vector3df &pos) override { assert(false); }
void setRotation(const core::vector3df &euler_deg) override { assert(false); }
void setScale(const core::vector3df &scale) override { assert(false); }
core::matrix4 getRelativeTransformation() const override
{
return matrix;
}
};
} // end namespace scene
} // end namespace irr

View file

@ -6,11 +6,15 @@
#include "IBoneSceneNode.h"
#include "CBoneSceneNode.h"
#include "IAnimatedMeshSceneNode.h"
#include "MatrixBoneSceneNode.h"
#include "SSkinMeshBuffer.h"
#include "Transform.h"
#include "irrMath.h"
#include "matrix4.h"
#include "os.h"
#include "vector3d.h"
#include <cassert>
#include <cstddef>
#include <variant>
#include <vector>
#include <cassert>
@ -52,125 +56,55 @@ void SkinnedMesh::setAnimationSpeed(f32 fps)
FramesPerSecond = fps;
}
//! returns the animated mesh based
IMesh *SkinnedMesh::getMesh(f32 frame)
// Keyframe Animation
using VariantTransform = SkinnedMesh::SJoint::VariantTransform;
std::vector<VariantTransform> SkinnedMesh::animateMesh(f32 frame)
{
// animate(frame,startFrameLoop, endFrameLoop);
if (frame == -1)
return this;
animateMesh(frame);
skinMesh();
return this;
}
//--------------------------------------------------------------------------
// Keyframe Animation
//--------------------------------------------------------------------------
//! Animates joints based on frame input
void SkinnedMesh::animateMesh(f32 frame)
{
if (!HasAnimation || LastAnimatedFrame == frame)
return;
LastAnimatedFrame = frame;
SkinnedLastFrame = false;
assert(HasAnimation);
std::vector<VariantTransform> result;
result.reserve(AllJoints.size());
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
// one render (to play two animations at the same time) LocalAnimatedMatrix only needs to be built once.
// a call to buildAllLocalAnimatedMatrices is needed before skinning the mesh, and before the user gets the joints to move
//----------------
// Temp!
buildAllLocalAnimatedMatrices();
//-----------------
updateBoundingBox();
result.push_back(joint->animate(frame));
return result;
}
void SkinnedMesh::buildAllLocalAnimatedMatrices()
// Software Skinning
void SkinnedMesh::skinMesh(const std::vector<core::matrix4> &global_matrices)
{
for (auto *joint : AllJoints) {
// Could be faster:
joint->LocalAnimatedMatrix = joint->buildLocalMatrix();
}
SkinnedLastFrame = false;
}
void SkinnedMesh::buildAllGlobalAnimatedMatrices(SJoint *joint, SJoint *parentJoint)
{
if (!joint) {
for (auto *rootJoint : RootJoints)
buildAllGlobalAnimatedMatrices(rootJoint, 0);
return;
} else {
// Find global matrix...
if (!parentJoint)
joint->GlobalAnimatedMatrix = joint->LocalAnimatedMatrix;
else
joint->GlobalAnimatedMatrix = parentJoint->GlobalAnimatedMatrix * joint->LocalAnimatedMatrix;
}
for (auto *childJoint : joint->Children)
buildAllGlobalAnimatedMatrices(childJoint, joint);
}
//--------------------------------------------------------------------------
// Software Skinning
//--------------------------------------------------------------------------
//! Preforms a software skin on this mesh based of joint positions
void SkinnedMesh::skinMesh()
{
if (!HasAnimation || SkinnedLastFrame)
if (!HasAnimation)
return;
//----------------
// This is marked as "Temp!". A shiny dubloon to whomever can tell me why.
buildAllGlobalAnimatedMatrices();
//-----------------
SkinnedLastFrame = true;
if (!HardwareSkinning) {
// rigid animation
for (auto *joint : AllJoints) {
for (u32 attachedMeshIdx : joint->AttachedMeshes) {
SSkinMeshBuffer *Buffer = (*SkinningBuffers)[attachedMeshIdx];
Buffer->Transformation = joint->GlobalAnimatedMatrix;
}
// rigid animation
for (size_t i = 0; i < AllJoints.size(); ++i) {
auto *joint = AllJoints[i];
for (u32 attachedMeshIdx : joint->AttachedMeshes) {
SSkinMeshBuffer *Buffer = (*SkinningBuffers)[attachedMeshIdx];
Buffer->Transformation = global_matrices[i];
}
// clear skinning helper array
for (std::vector<char> &buf : Vertices_Moved)
std::fill(buf.begin(), buf.end(), false);
// skin starting with the root joints
for (auto *rootJoint : RootJoints)
skinJoint(rootJoint, 0);
for (auto *buffer : *SkinningBuffers)
buffer->setDirty(EBT_VERTEX);
}
updateBoundingBox();
}
void SkinnedMesh::skinJoint(SJoint *joint, SJoint *parentJoint)
{
if (joint->Weights.size()) {
// Find this joints pull on vertices...
// clear skinning helper array
for (std::vector<char> &buf : Vertices_Moved)
std::fill(buf.begin(), buf.end(), false);
// skin starting with the root joints
for (size_t i = 0; i < AllJoints.size(); ++i) {
auto *joint = AllJoints[i];
if (joint->Weights.empty())
continue;
// Find this joints pull on vertices
// Note: It is assumed that the global inversed matrix has been calculated at this point.
core::matrix4 jointVertexPull = joint->GlobalAnimatedMatrix * joint->GlobalInversedMatrix.value();
core::matrix4 jointVertexPull = global_matrices[i] * joint->GlobalInversedMatrix.value();
core::vector3df thisVertexMove, thisNormalMove;
auto &buffersUsed = *SkinningBuffers;
// Skin Vertices Positions and Normals...
// Skin Vertices, Positions and Normals
for (const auto &weight : joint->Weights) {
// Pull this vertex...
jointVertexPull.transformVect(thisVertexMove, weight.StaticPos);
@ -202,9 +136,8 @@ void SkinnedMesh::skinJoint(SJoint *joint, SJoint *parentJoint)
}
}
// Skin all children
for (auto *childJoint : joint->Children)
skinJoint(childJoint, joint);
for (auto *buffer : *SkinningBuffers)
buffer->setDirty(EBT_VERTEX);
}
//! Gets joint count.
@ -256,7 +189,7 @@ IMeshBuffer *SkinnedMesh::getMeshBuffer(const video::SMaterial &material) const
if (LocalBuffers[i]->getMaterial() == material)
return LocalBuffers[i];
}
return 0;
return nullptr;
}
u32 SkinnedMesh::getTextureSlot(u32 meshbufNr) const
@ -283,29 +216,6 @@ void SkinnedMesh::setDirty(E_BUFFER_TYPE buffer)
LocalBuffers[i]->setDirty(buffer);
}
//! (This feature is not implemented in irrlicht yet)
bool SkinnedMesh::setHardwareSkinning(bool on)
{
if (HardwareSkinning != on) {
if (on) {
// set mesh to static pose...
for (auto *joint : AllJoints) {
for (const auto &weight : joint->Weights) {
const u16 buffer_id = weight.buffer_id;
const u32 vertex_id = weight.vertex_id;
LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos = weight.StaticPos;
LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal = weight.StaticNormal;
LocalBuffers[buffer_id]->boundingBoxNeedsRecalculated();
}
}
}
HardwareSkinning = on;
}
return HardwareSkinning;
}
void SkinnedMesh::refreshJointCache()
{
// copy cache from the mesh...
@ -330,114 +240,125 @@ void SkinnedMesh::resetAnimation()
LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal = weight.StaticNormal;
}
}
SkinnedLastFrame = false;
LastAnimatedFrame = -1;
}
void SkinnedMesh::calculateGlobalMatrices(SJoint *joint, SJoint *parentJoint)
//! Turns the given array of local matrices into an array of global matrices
//! by multiplying with respective parent matrices.
void SkinnedMesh::calculateGlobalMatrices(std::vector<core::matrix4> &matrices)
{
if (!joint && parentJoint) // bit of protection from endless loops
return;
// Go through the root bones
if (!joint) {
for (auto *rootJoint : RootJoints)
calculateGlobalMatrices(rootJoint, nullptr);
return;
// Note that the joints are topologically sorted.
for (u16 i = 0; i < AllJoints.size(); ++i) {
if (auto parent_id = AllJoints[i]->ParentJointID) {
matrices[i] = matrices[*parent_id] * matrices[i];
}
}
const auto local_matrix = joint->buildLocalMatrix();
if (!parentJoint)
joint->GlobalMatrix = local_matrix;
else
joint->GlobalMatrix = parentJoint->GlobalMatrix * local_matrix;
joint->LocalAnimatedMatrix = local_matrix;
joint->GlobalAnimatedMatrix = joint->GlobalMatrix;
if (!joint->GlobalInversedMatrix.has_value()) { // might be pre calculated
joint->GlobalInversedMatrix = joint->GlobalMatrix;
joint->GlobalInversedMatrix->makeInverse(); // slow
}
for (auto *childJoint : joint->Children)
calculateGlobalMatrices(childJoint, joint);
SkinnedLastFrame = false;
}
void SkinnedMesh::checkForAnimation()
bool SkinnedMesh::checkForAnimation() const
{
// Check for animation...
HasAnimation = false;
for (auto *joint : AllJoints) {
if (!joint->keys.empty()) {
HasAnimation = true;
break;
return true;
}
}
// meshes with weights, are still counted as animated for ragdolls, etc
if (!HasAnimation) {
for (auto *joint : AllJoints) {
if (joint->Weights.size()) {
HasAnimation = true;
break;
// meshes with weights are animatable
for (auto *joint : AllJoints) {
if (!joint->Weights.empty()) {
return true;
}
}
return false;
}
void SkinnedMesh::prepareForSkinning()
{
HasAnimation = checkForAnimation();
if (!HasAnimation || PreparedForSkinning)
return;
PreparedForSkinning = true;
EndFrame = 0.0f;
for (const auto *joint : AllJoints) {
EndFrame = std::max(EndFrame, joint->keys.getEndFrame());
}
for (auto *joint : AllJoints) {
for (auto &weight : joint->Weights) {
const u16 buffer_id = weight.buffer_id;
const u32 vertex_id = weight.vertex_id;
// check for invalid ids
if (buffer_id >= LocalBuffers.size()) {
os::Printer::log("Skinned Mesh: Weight buffer id too large", ELL_WARNING);
weight.buffer_id = weight.vertex_id = 0;
} else if (vertex_id >= LocalBuffers[buffer_id]->getVertexCount()) {
os::Printer::log("Skinned Mesh: Weight vertex id too large", ELL_WARNING);
weight.buffer_id = weight.vertex_id = 0;
}
}
}
if (HasAnimation) {
EndFrame = 0.0f;
for (const auto *joint : AllJoints) {
EndFrame = std::max(EndFrame, joint->keys.getEndFrame());
for (u32 i = 0; i < Vertices_Moved.size(); ++i)
for (u32 j = 0; j < Vertices_Moved[i].size(); ++j)
Vertices_Moved[i][j] = false;
// For skinning: cache weight values for speed
for (auto *joint : AllJoints) {
for (auto &weight : joint->Weights) {
const u16 buffer_id = weight.buffer_id;
const u32 vertex_id = weight.vertex_id;
weight.Moved = &Vertices_Moved[buffer_id][vertex_id];
weight.StaticPos = LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos;
weight.StaticNormal = LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal;
// weight._Pos=&Buffers[buffer_id]->getVertex(vertex_id)->Pos;
}
}
if (HasAnimation && !PreparedForSkinning) {
PreparedForSkinning = true;
normalizeWeights();
// check for bugs:
for (auto *joint : AllJoints) {
for (auto &weight : joint->Weights) {
const u16 buffer_id = weight.buffer_id;
const u32 vertex_id = weight.vertex_id;
// check for invalid ids
if (buffer_id >= LocalBuffers.size()) {
os::Printer::log("Skinned Mesh: Weight buffer id too large", ELL_WARNING);
weight.buffer_id = weight.vertex_id = 0;
} else if (vertex_id >= LocalBuffers[buffer_id]->getVertexCount()) {
os::Printer::log("Skinned Mesh: Weight vertex id too large", ELL_WARNING);
weight.buffer_id = weight.vertex_id = 0;
}
}
}
// An array used in skinning
for (u32 i = 0; i < Vertices_Moved.size(); ++i)
for (u32 j = 0; j < Vertices_Moved[i].size(); ++j)
Vertices_Moved[i][j] = false;
// For skinning: cache weight values for speed
for (auto *joint : AllJoints) {
for (auto &weight : joint->Weights) {
const u16 buffer_id = weight.buffer_id;
const u32 vertex_id = weight.vertex_id;
weight.Moved = &Vertices_Moved[buffer_id][vertex_id];
weight.StaticPos = LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos;
weight.StaticNormal = LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal;
// weight._Pos=&Buffers[buffer_id]->getVertex(vertex_id)->Pos;
}
}
// normalize weights
normalizeWeights();
for (auto *joint : AllJoints) {
joint->keys.cleanup();
}
SkinnedLastFrame = false;
}
void SkinnedMesh::topoSortJoints()
{
size_t n = AllJoints.size();
std::vector<u16> permutation; // new id -> old id
std::vector<std::vector<u16>> children(AllJoints.size());
for (u16 i = 0; i < n; ++i) {
if (auto parentId = AllJoints[i]->ParentJointID)
children[*parentId].push_back(i);
else
permutation.push_back(i);
}
// Levelorder
for (u16 i = 0; i < n; ++i) {
permutation.insert(permutation.end(),
children[i].begin(), children[i].end());
}
// old id -> new id
std::vector<u16> inverse_permutation(n);
for (u16 i = 0; i < n; ++i)
inverse_permutation[permutation[i]] = i;
std::vector<SJoint *> joints(n);
for (u16 i = 0; i < n; ++i) {
joints[i] = AllJoints[permutation[i]];
joints[i]->JointID = i;
if (auto parentId = joints[i]->ParentJointID)
joints[i]->ParentJointID = inverse_permutation[*parentId];
}
AllJoints = joints;
}
//! called by loader after populating with mesh and bone data
@ -445,60 +366,40 @@ SkinnedMesh *SkinnedMeshBuilder::finalize()
{
os::Printer::log("Skinned Mesh - finalize", ELL_DEBUG);
// Make sure we recalc the next frame
LastAnimatedFrame = -1;
SkinnedLastFrame = false;
topoSortJoints();
// calculate bounding box
// Calculate bounding box
for (auto *buffer : LocalBuffers) {
buffer->recalculateBoundingBox();
}
if (AllJoints.size() || RootJoints.size()) {
// populate AllJoints or RootJoints, depending on which is empty
if (RootJoints.empty()) {
for (auto *joint : AllJoints) {
bool foundParent = false;
for (const auto *parentJoint : AllJoints) {
for (const auto *childJoint : parentJoint->Children) {
if (childJoint == joint)
foundParent = true;
}
}
if (!foundParent)
RootJoints.push_back(joint);
}
} else {
AllJoints = RootJoints;
}
}
// Set array sizes...
// Set array sizes
for (u32 i = 0; i < LocalBuffers.size(); ++i) {
Vertices_Moved.emplace_back(LocalBuffers[i]->getVertexCount());
}
checkForAnimation();
prepareForSkinning();
if (HasAnimation) {
for (auto *joint : AllJoints) {
joint->keys.cleanup();
}
}
// Needed for animation and skinning...
calculateGlobalMatrices(0, 0);
// rigid animation for non animated meshes
std::vector<core::matrix4> matrices;
matrices.reserve(AllJoints.size());
// TODO populate with local matrices
for (auto *joint : AllJoints) {
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());
}
calculateGlobalMatrices(matrices);
for (size_t i = 0; i < AllJoints.size(); ++i) {
auto *joint = AllJoints[i];
joint->GlobalInversedMatrix = matrices[i];
joint->GlobalInversedMatrix->makeInverse();
// rigid animation for non animated meshes
for (u32 attachedMeshIdx : joint->AttachedMeshes) {
SSkinMeshBuffer *Buffer = (*SkinningBuffers)[attachedMeshIdx];
Buffer->Transformation = joint->GlobalAnimatedMatrix;
Buffer->Transformation = matrices[i];
}
}
@ -554,14 +455,11 @@ void SkinnedMeshBuilder::addMeshBuffer(SSkinMeshBuffer *meshbuf)
SkinnedMesh::SJoint *SkinnedMeshBuilder::addJoint(SJoint *parent)
{
SJoint *joint = new SJoint;
if (parent)
joint->ParentJointID = parent->JointID;
joint->JointID = AllJoints.size();
AllJoints.push_back(joint);
if (!parent) {
// Add root joints to array in finalize()
} else {
// Set parent (Be careful of the mesh loader also setting the parent)
parent->Children.push_back(joint);
}
return joint;
}
@ -631,88 +529,6 @@ void SkinnedMesh::normalizeWeights()
}
}
void SkinnedMesh::recoverJointsFromMesh(std::vector<IBoneSceneNode *> &jointChildSceneNodes)
{
for (u32 i = 0; i < AllJoints.size(); ++i) {
IBoneSceneNode *node = jointChildSceneNodes[i];
SJoint *joint = AllJoints[i];
if (std::holds_alternative<SJoint::Transform>(joint->transform)) {
auto transform = std::get<SJoint::Transform>(joint->transform);
node->setPosition(transform.translation);
{
core::vector3df euler;
auto rot = transform.rotation;
// Invert to be consistent with setRotationDegrees
rot.makeInverse();
rot.toEuler(euler);
node->setRotation(euler * core::RADTODEG);
}
node->setScale(transform.scale);
} else {
node->setPosition(joint->LocalAnimatedMatrix.getTranslation());
node->setRotation(joint->LocalAnimatedMatrix.getRotationDegrees());
node->setScale(joint->LocalAnimatedMatrix.getScale());
}
// node->updateAbsolutePosition(); // WTF
}
}
void SkinnedMesh::transferJointsToMesh(const std::vector<IBoneSceneNode *> &jointChildSceneNodes)
{
for (u32 i = 0; i < AllJoints.size(); ++i) {
const IBoneSceneNode *const node = jointChildSceneNodes[i];
SJoint *joint = AllJoints[i];
if (std::holds_alternative<SJoint::Transform>(joint->transform)) {
joint->LocalAnimatedMatrix = core::matrix4();
joint->LocalAnimatedMatrix.setRotationDegrees(node->getRotation());
joint->LocalAnimatedMatrix.setTranslation(node->getPosition());
joint->LocalAnimatedMatrix *= core::matrix4().setScale(node->getScale());
}
}
// Make sure we recalc the next frame
LastAnimatedFrame = -1;
SkinnedLastFrame = false;
}
void SkinnedMesh::addJoints(std::vector<IBoneSceneNode *> &jointChildSceneNodes,
IAnimatedMeshSceneNode *node, ISceneManager *smgr)
{
// Create new joints
for (u32 i = 0; i < AllJoints.size(); ++i) {
jointChildSceneNodes.push_back(new CBoneSceneNode(0, smgr, 0, i, AllJoints[i]->Name));
}
// Match up parents
for (u32 i = 0; i < jointChildSceneNodes.size(); ++i) {
const SJoint *const joint = AllJoints[i]; // should be fine
s32 parentID = -1;
for (u32 j = 0; (parentID == -1) && (j < AllJoints.size()); ++j) {
if (i != j) {
const SJoint *const parentTest = AllJoints[j];
for (u32 n = 0; n < parentTest->Children.size(); ++n) {
if (parentTest->Children[n] == joint) {
parentID = j;
break;
}
}
}
}
IBoneSceneNode *bone = jointChildSceneNodes[i];
if (parentID != -1)
bone->setParent(jointChildSceneNodes[parentID]);
else
bone->setParent(node);
bone->drop();
}
SkinnedLastFrame = false;
}
void SkinnedMesh::convertMeshToTangents()
{
// now calculate tangents

View file

@ -178,15 +178,6 @@ static void setBillboardTextureMatrix(scene::IBillboardSceneNode *bill,
matrix.setTextureScale(txs, tys);
}
// Evaluate transform chain recursively; irrlicht does not do this for us
static void updatePositionRecursive(scene::ISceneNode *node)
{
scene::ISceneNode *parent = node->getParent();
if (parent)
updatePositionRecursive(parent);
node->updateAbsolutePosition();
}
static bool logOnce(const std::ostringstream &from, std::ostream &log_to)
{
thread_local std::vector<u64> logged;

View file

@ -414,14 +414,14 @@ SECTION("simple skin")
SECTION("transformations are correct")
{
{
const auto &transform = std::get<SkinnedMesh::SJoint::Transform>(parent->transform);
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<SkinnedMesh::SJoint::Transform>(child->transform);
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());