1
0
Fork 0
mirror of https://github.com/luanti-org/luanti.git synced 2025-09-15 18:57:08 +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

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