mirror of
https://github.com/luanti-org/luanti.git
synced 2025-08-26 18:21:04 +00:00
Fix and clean up skeletal animation (#15722)
* Fix attachments lagging behind their parents (#14818) * Fix animation blending (#14817) * Bring back cool guy as another .x smoke test * Add .x mesh loader unittest * Do bounding box & matrix calculation at proper point in time * Remove obsolete `SAnimatedMesh`
This commit is contained in:
parent
0bb87eb1ff
commit
fde6384a09
40 changed files with 856 additions and 1388 deletions
|
@ -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 "S3DVertex.h"
|
||||
#include "Transform.h"
|
||||
#include "irrTypes.h"
|
||||
#include "matrix4.h"
|
||||
#include "os.h"
|
||||
#include "SkinnedMesh.h"
|
||||
#include "IDummyTransformationSceneNode.h"
|
||||
|
@ -17,6 +21,9 @@
|
|||
#include "IFileSystem.h"
|
||||
#include "quaternion.h"
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <optional>
|
||||
#include <cassert>
|
||||
|
||||
namespace irr
|
||||
{
|
||||
|
@ -30,13 +37,13 @@ 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),
|
||||
JointMode(EJUOR_NONE), JointsUsed(false),
|
||||
JointsUsed(false),
|
||||
Looping(true), ReadOnlyMaterials(false), RenderFromIdentity(false),
|
||||
LoopCallBack(0), PassCount(0)
|
||||
PassCount(0)
|
||||
{
|
||||
setMesh(mesh);
|
||||
}
|
||||
|
@ -44,8 +51,6 @@ CAnimatedMeshSceneNode::CAnimatedMeshSceneNode(IAnimatedMesh *mesh,
|
|||
//! destructor
|
||||
CAnimatedMeshSceneNode::~CAnimatedMeshSceneNode()
|
||||
{
|
||||
if (LoopCallBack)
|
||||
LoopCallBack->drop();
|
||||
if (Mesh)
|
||||
Mesh->drop();
|
||||
}
|
||||
|
@ -87,8 +92,7 @@ void CAnimatedMeshSceneNode::buildFrameNr(u32 timeMs)
|
|||
if (FramesPerSecond > 0.f) { // forwards...
|
||||
if (CurrentFrameNr > EndFrame)
|
||||
CurrentFrameNr = StartFrame + fmodf(CurrentFrameNr - StartFrame, EndFrame - StartFrame);
|
||||
} else // backwards...
|
||||
{
|
||||
} else { // backwards...
|
||||
if (CurrentFrameNr < StartFrame)
|
||||
CurrentFrameNr = EndFrame - fmodf(EndFrame - CurrentFrameNr, EndFrame - StartFrame);
|
||||
}
|
||||
|
@ -97,18 +101,9 @@ void CAnimatedMeshSceneNode::buildFrameNr(u32 timeMs)
|
|||
|
||||
CurrentFrameNr += timeMs * FramesPerSecond;
|
||||
if (FramesPerSecond > 0.f) { // forwards...
|
||||
if (CurrentFrameNr > EndFrame) {
|
||||
CurrentFrameNr = EndFrame;
|
||||
if (LoopCallBack)
|
||||
LoopCallBack->OnAnimationEnd(this);
|
||||
}
|
||||
} else // backwards...
|
||||
{
|
||||
if (CurrentFrameNr < StartFrame) {
|
||||
CurrentFrameNr = StartFrame;
|
||||
if (LoopCallBack)
|
||||
LoopCallBack->OnAnimationEnd(this);
|
||||
}
|
||||
CurrentFrameNr = std::min(CurrentFrameNr, EndFrame);
|
||||
} else { // backwards...
|
||||
CurrentFrameNr = std::max(CurrentFrameNr, StartFrame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -156,38 +151,18 @@ void CAnimatedMeshSceneNode::OnRegisterSceneNode()
|
|||
IMesh *CAnimatedMeshSceneNode::getMeshForCurrentFrame()
|
||||
{
|
||||
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);
|
||||
|
||||
if (JointMode == EJUOR_CONTROL) // write to mesh
|
||||
skinnedMesh->transferJointsToMesh(JointChildSceneNodes);
|
||||
else
|
||||
skinnedMesh->animateMesh(getFrameNr());
|
||||
|
||||
// Update the skinned mesh for the current joint transforms.
|
||||
skinnedMesh->skinMesh();
|
||||
|
||||
if (JointMode == EJUOR_READ) { // read from mesh
|
||||
skinnedMesh->recoverJointsFromMesh(JointChildSceneNodes);
|
||||
|
||||
//---slow---
|
||||
for (u32 n = 0; n < JointChildSceneNodes.size(); ++n)
|
||||
if (JointChildSceneNodes[n]->getParent() == this) {
|
||||
JointChildSceneNodes[n]->updateAbsolutePositionOfAllChildren(); // temp, should be an option
|
||||
}
|
||||
}
|
||||
|
||||
if (JointMode == EJUOR_CONTROL) {
|
||||
// For meshes other than EJUOR_CONTROL, this is done by calling animateMesh()
|
||||
skinnedMesh->updateBoundingBox();
|
||||
}
|
||||
|
||||
return skinnedMesh;
|
||||
return 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.
|
||||
|
||||
auto *skinnedMesh = static_cast<SkinnedMesh *>(Mesh);
|
||||
|
||||
// Matrices have already been calculated in OnAnimate
|
||||
skinnedMesh->skinMesh(PerJoint.GlobalMatrices);
|
||||
|
||||
return skinnedMesh;
|
||||
}
|
||||
|
||||
//! OnAnimate() is called just before rendering the whole scene.
|
||||
|
@ -201,7 +176,28 @@ 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();
|
||||
|
||||
// Copy old transforms *before* bone overrides have been applied.
|
||||
// TODO if there are no bone overrides or no animation blending, this is unnecessary.
|
||||
copyOldTransforms();
|
||||
|
||||
if (OnAnimateCallback)
|
||||
OnAnimateCallback(timeMs / 1000.0f);
|
||||
|
||||
IAnimatedMeshSceneNode::OnAnimate(timeMs);
|
||||
|
||||
if (auto *skinnedMesh = dynamic_cast<SkinnedMesh*>(Mesh)) {
|
||||
for (u16 i = 0; i < PerJoint.SceneNodes.size(); ++i)
|
||||
PerJoint.GlobalMatrices[i] = PerJoint.SceneNodes[i]->getRelativeTransformation();
|
||||
assert(PerJoint.GlobalMatrices.size() == skinnedMesh->getJointCount());
|
||||
skinnedMesh->calculateGlobalMatrices(PerJoint.GlobalMatrices);
|
||||
Box = skinnedMesh->calculateBoundingBox(PerJoint.GlobalMatrices);
|
||||
} else {
|
||||
Box = Mesh->getBoundingBox();
|
||||
}
|
||||
}
|
||||
|
||||
//! renders the node.
|
||||
|
@ -218,15 +214,7 @@ 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;
|
||||
}
|
||||
assert(m);
|
||||
|
||||
driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
|
||||
|
||||
|
@ -294,11 +282,12 @@ void CAnimatedMeshSceneNode::render()
|
|||
if (DebugDataVisible & scene::EDS_SKELETON) {
|
||||
if (Mesh->getMeshType() == EAMT_SKINNED) {
|
||||
// draw skeleton
|
||||
|
||||
for (auto *joint : ((SkinnedMesh *)Mesh)->getAllJoints()) {
|
||||
for (const auto *childJoint : joint->Children) {
|
||||
driver->draw3DLine(joint->GlobalAnimatedMatrix.getTranslation(),
|
||||
childJoint->GlobalAnimatedMatrix.getTranslation(),
|
||||
const auto &joints = (static_cast<SkinnedMesh *>(Mesh))->getAllJoints();
|
||||
for (u16 i = 0; i < PerJoint.GlobalMatrices.size(); ++i) {
|
||||
const auto translation = PerJoint.GlobalMatrices[i].getTranslation();
|
||||
if (auto pjid = joints[i]->ParentJointID) {
|
||||
const auto parent_translation = PerJoint.GlobalMatrices[*pjid].getTranslation();
|
||||
driver->draw3DLine(parent_translation, translation,
|
||||
video::SColor(255, 51, 66, 255));
|
||||
}
|
||||
}
|
||||
|
@ -407,12 +396,12 @@ IBoneSceneNode *CAnimatedMeshSceneNode::getJointNode(const c8 *jointName)
|
|||
return 0;
|
||||
}
|
||||
|
||||
if (JointChildSceneNodes.size() <= *number) {
|
||||
if (PerJoint.SceneNodes.size() <= *number) {
|
||||
os::Printer::log("Joint was found in mesh, but is not loaded into node", jointName, ELL_WARNING);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return JointChildSceneNodes[*number];
|
||||
return PerJoint.SceneNodes[*number];
|
||||
}
|
||||
|
||||
//! Returns a pointer to a child node, which has the same transformation as
|
||||
|
@ -426,12 +415,12 @@ IBoneSceneNode *CAnimatedMeshSceneNode::getJointNode(u32 jointID)
|
|||
|
||||
checkJoints();
|
||||
|
||||
if (JointChildSceneNodes.size() <= jointID) {
|
||||
if (PerJoint.SceneNodes.size() <= jointID) {
|
||||
os::Printer::log("Joint not loaded into node", ELL_WARNING);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return JointChildSceneNodes[jointID];
|
||||
return PerJoint.SceneNodes[jointID];
|
||||
}
|
||||
|
||||
//! Gets joint count.
|
||||
|
@ -452,9 +441,9 @@ bool CAnimatedMeshSceneNode::removeChild(ISceneNode *child)
|
|||
{
|
||||
if (ISceneNode::removeChild(child)) {
|
||||
if (JointsUsed) { // stop weird bugs caused while changing parents as the joints are being created
|
||||
for (u32 i = 0; i < JointChildSceneNodes.size(); ++i) {
|
||||
if (JointChildSceneNodes[i] == child) {
|
||||
JointChildSceneNodes[i] = 0; // remove link to child
|
||||
for (u32 i = 0; i < PerJoint.SceneNodes.size(); ++i) {
|
||||
if (PerJoint.SceneNodes[i] == child) {
|
||||
PerJoint.SceneNodes[i] = 0; // remove link to child
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -478,22 +467,6 @@ bool CAnimatedMeshSceneNode::getLoopMode() const
|
|||
return Looping;
|
||||
}
|
||||
|
||||
//! Sets a callback interface which will be called if an animation
|
||||
//! playback has ended. Set this to 0 to disable the callback again.
|
||||
void CAnimatedMeshSceneNode::setAnimationEndCallback(IAnimationEndCallBack *callback)
|
||||
{
|
||||
if (callback == LoopCallBack)
|
||||
return;
|
||||
|
||||
if (LoopCallBack)
|
||||
LoopCallBack->drop();
|
||||
|
||||
LoopCallBack = callback;
|
||||
|
||||
if (LoopCallBack)
|
||||
LoopCallBack->grab();
|
||||
}
|
||||
|
||||
//! Sets if the scene node should not copy the materials of the mesh but use them in a read only style.
|
||||
void CAnimatedMeshSceneNode::setReadOnlyMaterials(bool readonly)
|
||||
{
|
||||
|
@ -525,18 +498,15 @@ void CAnimatedMeshSceneNode::setMesh(IAnimatedMesh *mesh)
|
|||
// get materials and bounding box
|
||||
Box = Mesh->getBoundingBox();
|
||||
|
||||
IMesh *m = Mesh->getMesh(0);
|
||||
if (m) {
|
||||
Materials.clear();
|
||||
Materials.reallocate(m->getMeshBufferCount());
|
||||
Materials.clear();
|
||||
Materials.reallocate(Mesh->getMeshBufferCount());
|
||||
|
||||
for (u32 i = 0; i < m->getMeshBufferCount(); ++i) {
|
||||
IMeshBuffer *mb = m->getMeshBuffer(i);
|
||||
if (mb)
|
||||
Materials.push_back(mb->getMaterial());
|
||||
else
|
||||
Materials.push_back(video::SMaterial());
|
||||
}
|
||||
for (u32 i = 0; i < Mesh->getMeshBufferCount(); ++i) {
|
||||
IMeshBuffer *mb = Mesh->getMeshBuffer(i);
|
||||
if (mb)
|
||||
Materials.push_back(mb->getMaterial());
|
||||
else
|
||||
Materials.push_back(video::SMaterial());
|
||||
}
|
||||
|
||||
// clean up joint nodes
|
||||
|
@ -556,14 +526,7 @@ void CAnimatedMeshSceneNode::updateAbsolutePosition()
|
|||
IAnimatedMeshSceneNode::updateAbsolutePosition();
|
||||
}
|
||||
|
||||
//! Set the joint update mode (0-unused, 1-get joints only, 2-set joints only, 3-move and set)
|
||||
void CAnimatedMeshSceneNode::setJointMode(E_JOINT_UPDATE_ON_RENDER mode)
|
||||
{
|
||||
checkJoints();
|
||||
JointMode = mode;
|
||||
}
|
||||
|
||||
//! 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 CAnimatedMeshSceneNode::setTransitionTime(f32 time)
|
||||
{
|
||||
|
@ -571,10 +534,6 @@ void CAnimatedMeshSceneNode::setTransitionTime(f32 time)
|
|||
if (TransitionTime == ttime)
|
||||
return;
|
||||
TransitionTime = ttime;
|
||||
if (ttime != 0)
|
||||
setJointMode(EJUOR_CONTROL);
|
||||
else
|
||||
setJointMode(EJUOR_NONE);
|
||||
}
|
||||
|
||||
//! render mesh ignoring its transformation. Used with ragdolls. (culling is unaffected)
|
||||
|
@ -583,120 +542,104 @@ void CAnimatedMeshSceneNode::setRenderFromIdentity(bool enable)
|
|||
RenderFromIdentity = enable;
|
||||
}
|
||||
|
||||
//! updates the joint positions of this mesh
|
||||
void CAnimatedMeshSceneNode::animateJoints(bool CalculateAbsolutePositions)
|
||||
void CAnimatedMeshSceneNode::addJoints()
|
||||
{
|
||||
if (Mesh && Mesh->getMeshType() == EAMT_SKINNED) {
|
||||
checkJoints();
|
||||
const f32 frame = getFrameNr(); // old?
|
||||
const auto &joints = static_cast<SkinnedMesh*>(Mesh)->getAllJoints();
|
||||
PerJoint.setN(joints.size());
|
||||
PerJoint.SceneNodes.clear();
|
||||
PerJoint.SceneNodes.reserve(joints.size());
|
||||
for (size_t i = 0; i < joints.size(); ++i) {
|
||||
const auto *joint = joints[i];
|
||||
ISceneNode *parent = this;
|
||||
if (joint->ParentJointID)
|
||||
parent = PerJoint.SceneNodes.at(*joint->ParentJointID); // exists because of topo. order
|
||||
assert(parent);
|
||||
const auto *matrix = std::get_if<core::matrix4>(&joint->transform);
|
||||
PerJoint.SceneNodes.push_back(new CBoneSceneNode(
|
||||
parent, SceneManager, 0, i, joint->Name,
|
||||
matrix ? core::Transform{} : std::get<core::Transform>(joint->transform),
|
||||
matrix ? *matrix : std::optional<core::matrix4>{}));
|
||||
}
|
||||
}
|
||||
|
||||
SkinnedMesh *skinnedMesh = static_cast<SkinnedMesh *>(Mesh);
|
||||
|
||||
skinnedMesh->animateMesh(frame);
|
||||
skinnedMesh->recoverJointsFromMesh(JointChildSceneNodes);
|
||||
|
||||
//-----------------------------------------
|
||||
// Transition
|
||||
//-----------------------------------------
|
||||
|
||||
if (Transiting != 0.f) {
|
||||
// Init additional matrices
|
||||
if (PretransitingSave.size() < JointChildSceneNodes.size()) {
|
||||
for (u32 n = PretransitingSave.size(); n < JointChildSceneNodes.size(); ++n)
|
||||
PretransitingSave.push_back(core::matrix4());
|
||||
}
|
||||
|
||||
for (u32 n = 0; n < JointChildSceneNodes.size(); ++n) {
|
||||
//------Position------
|
||||
|
||||
JointChildSceneNodes[n]->setPosition(
|
||||
core::lerp(
|
||||
PretransitingSave[n].getTranslation(),
|
||||
JointChildSceneNodes[n]->getPosition(),
|
||||
TransitingBlend));
|
||||
|
||||
//------Rotation------
|
||||
|
||||
// Code is slow, needs to be fixed up
|
||||
|
||||
const core::quaternion RotationStart(PretransitingSave[n].getRotationRadians());
|
||||
const core::quaternion RotationEnd(JointChildSceneNodes[n]->getRotation() * core::DEGTORAD);
|
||||
|
||||
core::quaternion QRotation;
|
||||
QRotation.slerp(RotationStart, RotationEnd, TransitingBlend);
|
||||
|
||||
core::vector3df tmpVector;
|
||||
QRotation.toEuler(tmpVector);
|
||||
tmpVector *= core::RADTODEG; // convert from radians back to degrees
|
||||
JointChildSceneNodes[n]->setRotation(tmpVector);
|
||||
|
||||
//------Scale------
|
||||
|
||||
// JointChildSceneNodes[n]->setScale(
|
||||
// core::lerp(
|
||||
// PretransitingSave[n].getScale(),
|
||||
// JointChildSceneNodes[n]->getScale(),
|
||||
// TransitingBlend));
|
||||
}
|
||||
void CAnimatedMeshSceneNode::updateJointSceneNodes(
|
||||
const std::vector<SkinnedMesh::SJoint::VariantTransform> &transforms)
|
||||
{
|
||||
for (size_t i = 0; i < transforms.size(); ++i) {
|
||||
const auto &transform = transforms[i];
|
||||
auto *node = static_cast<CBoneSceneNode*>(PerJoint.SceneNodes[i]);
|
||||
if (const auto *trs = std::get_if<core::Transform>(&transform)) {
|
||||
node->setTransform(*trs);
|
||||
// .x lets animations override matrix transforms entirely.
|
||||
node->Matrix = std::nullopt;
|
||||
} else {
|
||||
node->Matrix = std::get<core::matrix4>(transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (CalculateAbsolutePositions) {
|
||||
//---slow---
|
||||
for (u32 n = 0; n < JointChildSceneNodes.size(); ++n) {
|
||||
if (JointChildSceneNodes[n]->getParent() == this) {
|
||||
JointChildSceneNodes[n]->updateAbsolutePositionOfAllChildren(); // temp, should be an option
|
||||
}
|
||||
//! updates the joint positions of this mesh
|
||||
void CAnimatedMeshSceneNode::animateJoints()
|
||||
{
|
||||
if (!Mesh || Mesh->getMeshType() != EAMT_SKINNED)
|
||||
return;
|
||||
|
||||
checkJoints();
|
||||
|
||||
SkinnedMesh *skinnedMesh = static_cast<SkinnedMesh *>(Mesh);
|
||||
if (!skinnedMesh->isStatic())
|
||||
updateJointSceneNodes(skinnedMesh->animateMesh(getFrameNr()));
|
||||
|
||||
//-----------------------------------------
|
||||
// Transition
|
||||
//-----------------------------------------
|
||||
|
||||
if (Transiting != 0.f) {
|
||||
for (u32 i = 0; i < PerJoint.SceneNodes.size(); ++i) {
|
||||
if (PerJoint.PreTransSaves[i]) {
|
||||
PerJoint.SceneNodes[i]->setTransform(PerJoint.PreTransSaves[i]->interpolate(
|
||||
PerJoint.SceneNodes[i]->getTransform(), TransitingBlend));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
*/
|
||||
void CAnimatedMeshSceneNode::checkJoints()
|
||||
{
|
||||
if (!Mesh || Mesh->getMeshType() != EAMT_SKINNED)
|
||||
return;
|
||||
|
||||
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);
|
||||
for (u32 i = 0; i < PerJoint.SceneNodes.size(); ++i)
|
||||
removeChild(PerJoint.SceneNodes[i]);
|
||||
addJoints();
|
||||
|
||||
JointsUsed = true;
|
||||
JointMode = EJUOR_READ;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
*/
|
||||
void CAnimatedMeshSceneNode::copyOldTransforms()
|
||||
{
|
||||
for (u32 i = 0; i < PerJoint.SceneNodes.size(); ++i) {
|
||||
if (!PerJoint.SceneNodes[i]->Matrix) {
|
||||
PerJoint.PreTransSaves[i] = PerJoint.SceneNodes[i]->getTransform();
|
||||
} else {
|
||||
PerJoint.PreTransSaves[i] = std::nullopt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CAnimatedMeshSceneNode::beginTransition()
|
||||
{
|
||||
if (!JointsUsed)
|
||||
return;
|
||||
|
||||
if (TransitionTime != 0) {
|
||||
// Check the array is big enough
|
||||
if (PretransitingSave.size() < JointChildSceneNodes.size()) {
|
||||
for (u32 n = PretransitingSave.size(); n < JointChildSceneNodes.size(); ++n)
|
||||
PretransitingSave.push_back(core::matrix4());
|
||||
}
|
||||
|
||||
// Copy the position of joints
|
||||
for (u32 n = 0; n < JointChildSceneNodes.size(); ++n)
|
||||
PretransitingSave[n] = JointChildSceneNodes[n]->getRelativeTransformation();
|
||||
|
||||
Transiting = core::reciprocal((f32)TransitionTime);
|
||||
}
|
||||
TransitingBlend = 0.f;
|
||||
}
|
||||
|
||||
/*!
|
||||
*/
|
||||
ISceneNode *CAnimatedMeshSceneNode::clone(ISceneNode *newParent, ISceneManager *newManager)
|
||||
{
|
||||
if (!newParent)
|
||||
|
@ -722,19 +665,15 @@ ISceneNode *CAnimatedMeshSceneNode::clone(ISceneNode *newParent, ISceneManager *
|
|||
newNode->EndFrame = EndFrame;
|
||||
newNode->FramesPerSecond = FramesPerSecond;
|
||||
newNode->CurrentFrameNr = CurrentFrameNr;
|
||||
newNode->JointMode = JointMode;
|
||||
newNode->JointsUsed = JointsUsed;
|
||||
newNode->TransitionTime = TransitionTime;
|
||||
newNode->Transiting = Transiting;
|
||||
newNode->TransitingBlend = TransitingBlend;
|
||||
newNode->Looping = Looping;
|
||||
newNode->ReadOnlyMaterials = ReadOnlyMaterials;
|
||||
newNode->LoopCallBack = LoopCallBack;
|
||||
if (newNode->LoopCallBack)
|
||||
newNode->LoopCallBack->grab();
|
||||
newNode->PassCount = PassCount;
|
||||
newNode->JointChildSceneNodes = JointChildSceneNodes;
|
||||
newNode->PretransitingSave = PretransitingSave;
|
||||
newNode->PerJoint.SceneNodes = PerJoint.SceneNodes;
|
||||
newNode->PerJoint.PreTransSaves = PerJoint.PreTransSaves;
|
||||
newNode->RenderFromIdentity = RenderFromIdentity;
|
||||
|
||||
return newNode;
|
||||
|
|
|
@ -4,9 +4,12 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "CBoneSceneNode.h"
|
||||
#include "IAnimatedMeshSceneNode.h"
|
||||
#include "IAnimatedMesh.h"
|
||||
|
||||
#include "SkinnedMesh.h"
|
||||
#include "Transform.h"
|
||||
#include "matrix4.h"
|
||||
|
||||
namespace irr
|
||||
|
@ -54,9 +57,11 @@ public:
|
|||
//! returns the current loop mode
|
||||
bool getLoopMode() const override;
|
||||
|
||||
//! Sets a callback interface which will be called if an animation
|
||||
//! playback has ended. Set this to 0 to disable the callback again.
|
||||
void setAnimationEndCallback(IAnimationEndCallBack *callback = 0) override;
|
||||
void setOnAnimateCallback(
|
||||
const std::function<void(f32 dtime)> &cb) override
|
||||
{
|
||||
OnAnimateCallback = cb;
|
||||
}
|
||||
|
||||
//! sets the speed with which the animation is played
|
||||
//! NOTE: setMesh will also change this value and set it to the default speed of the mesh
|
||||
|
@ -117,15 +122,16 @@ public:
|
|||
//! updates the absolute position based on the relative and the parents position
|
||||
void updateAbsolutePosition() override;
|
||||
|
||||
//! Set the joint update mode (0-unused, 1-get joints only, 2-set joints only, 3-move and set)
|
||||
void setJointMode(E_JOINT_UPDATE_ON_RENDER mode) 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;
|
||||
|
||||
void updateJointSceneNodes(const std::vector<SkinnedMesh::SJoint::VariantTransform> &transforms);
|
||||
|
||||
//! updates the joint positions of this mesh
|
||||
void animateJoints(bool CalculateAbsolutePositions = true) override;
|
||||
void animateJoints() override;
|
||||
|
||||
void addJoints();
|
||||
|
||||
//! render mesh ignoring its transformation. Used with ragdolls. (culling is unaffected)
|
||||
void setRenderFromIdentity(bool On) override;
|
||||
|
@ -142,6 +148,7 @@ private:
|
|||
|
||||
void buildFrameNr(u32 timeMs);
|
||||
void checkJoints();
|
||||
void copyOldTransforms();
|
||||
void beginTransition();
|
||||
|
||||
core::array<video::SMaterial> Materials;
|
||||
|
@ -158,19 +165,30 @@ private:
|
|||
f32 Transiting; // is mesh transiting (plus cache of TransitionTime)
|
||||
f32 TransitingBlend; // 0-1, calculated on buildFrameNr
|
||||
|
||||
// 0-unused, 1-get joints only, 2-set joints only, 3-move and set
|
||||
E_JOINT_UPDATE_ON_RENDER JointMode;
|
||||
bool JointsUsed;
|
||||
|
||||
bool Looping;
|
||||
bool ReadOnlyMaterials;
|
||||
bool RenderFromIdentity;
|
||||
|
||||
IAnimationEndCallBack *LoopCallBack;
|
||||
s32 PassCount;
|
||||
std::function<void(f32)> OnAnimateCallback;
|
||||
|
||||
std::vector<IBoneSceneNode *> JointChildSceneNodes;
|
||||
core::array<core::matrix4> PretransitingSave;
|
||||
struct PerJointData {
|
||||
std::vector<CBoneSceneNode *> SceneNodes;
|
||||
std::vector<core::matrix4> GlobalMatrices;
|
||||
std::vector<std::optional<core::Transform>> PreTransSaves;
|
||||
void setN(u16 n) {
|
||||
SceneNodes.clear();
|
||||
SceneNodes.resize(n);
|
||||
GlobalMatrices.clear();
|
||||
GlobalMatrices.resize(n);
|
||||
PreTransSaves.clear();
|
||||
PreTransSaves.resize(n);
|
||||
}
|
||||
};
|
||||
|
||||
PerJointData PerJoint;
|
||||
};
|
||||
|
||||
} // end namespace scene
|
||||
|
|
|
@ -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];
|
||||
core::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 * transform.buildMatrix();
|
||||
else
|
||||
joint->GlobalMatrix = joint->LocalMatrix;
|
||||
joint->GlobalMatrix = transform.buildMatrix();
|
||||
|
||||
while (B3dStack.getLast().startposition + B3dStack.getLast().length > B3DFile->getPos()) // this chunk repeats
|
||||
{
|
||||
|
|
|
@ -1,86 +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
|
||||
{
|
||||
|
||||
//! constructor
|
||||
CBoneSceneNode::CBoneSceneNode(ISceneNode *parent, ISceneManager *mgr, s32 id,
|
||||
u32 boneIndex, const std::optional<std::string> &boneName) :
|
||||
IBoneSceneNode(parent, mgr, id),
|
||||
BoneIndex(boneIndex),
|
||||
AnimationMode(EBAM_AUTOMATIC), SkinningSpace(EBSS_LOCAL)
|
||||
{
|
||||
setName(boneName);
|
||||
}
|
||||
|
||||
//! Returns the index of the bone
|
||||
u32 CBoneSceneNode::getBoneIndex() const
|
||||
{
|
||||
return BoneIndex;
|
||||
}
|
||||
|
||||
//! Sets the animation mode of the bone. Returns true if successful.
|
||||
bool CBoneSceneNode::setAnimationMode(E_BONE_ANIMATION_MODE mode)
|
||||
{
|
||||
AnimationMode = mode;
|
||||
return true;
|
||||
}
|
||||
|
||||
//! Gets the current animation mode of the bone
|
||||
E_BONE_ANIMATION_MODE CBoneSceneNode::getAnimationMode() const
|
||||
{
|
||||
return AnimationMode;
|
||||
}
|
||||
|
||||
//! returns the axis aligned bounding box of this node
|
||||
const core::aabbox3d<f32> &CBoneSceneNode::getBoundingBox() const
|
||||
{
|
||||
return Box;
|
||||
}
|
||||
|
||||
/*
|
||||
//! Returns the relative transformation of the scene node.
|
||||
core::matrix4 CBoneSceneNode::getRelativeTransformation() const
|
||||
{
|
||||
return core::matrix4(); // RelativeTransformation;
|
||||
}
|
||||
*/
|
||||
|
||||
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
|
|
@ -7,6 +7,8 @@
|
|||
// Used with SkinnedMesh and IAnimatedMeshSceneNode, for boned meshes
|
||||
|
||||
#include "IBoneSceneNode.h"
|
||||
#include "Transform.h"
|
||||
#include "matrix4.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
|
@ -21,49 +23,48 @@ public:
|
|||
//! constructor
|
||||
CBoneSceneNode(ISceneNode *parent, ISceneManager *mgr,
|
||||
s32 id = -1, u32 boneIndex = 0,
|
||||
const std::optional<std::string> &boneName = std::nullopt);
|
||||
|
||||
//! Returns the index of the bone
|
||||
u32 getBoneIndex() const override;
|
||||
|
||||
//! Sets the animation mode of the bone. Returns true if successful.
|
||||
bool setAnimationMode(E_BONE_ANIMATION_MODE mode) override;
|
||||
|
||||
//! Gets the current animation mode of the bone
|
||||
E_BONE_ANIMATION_MODE getAnimationMode() const override;
|
||||
|
||||
//! returns the axis aligned bounding box of this node
|
||||
const core::aabbox3d<f32> &getBoundingBox() const override;
|
||||
|
||||
/*
|
||||
//! Returns the relative transformation of the scene node.
|
||||
//core::matrix4 getRelativeTransformation() const override;
|
||||
*/
|
||||
|
||||
void OnAnimate(u32 timeMs) override;
|
||||
|
||||
void updateAbsolutePositionOfAllChildren() override;
|
||||
|
||||
//! How the relative transformation of the bone is used
|
||||
void setSkinningSpace(E_BONE_SKINNING_SPACE space) override
|
||||
const std::optional<std::string> &boneName = std::nullopt,
|
||||
const core::Transform &transform = {},
|
||||
const std::optional<core::matrix4> &matrix = std::nullopt) :
|
||||
IBoneSceneNode(parent, mgr, id, boneIndex, boneName),
|
||||
Matrix(matrix)
|
||||
{
|
||||
SkinningSpace = space;
|
||||
setTransform(transform);
|
||||
}
|
||||
|
||||
E_BONE_SKINNING_SPACE getSkinningSpace() const override
|
||||
void setTransform(const core::Transform &transform)
|
||||
{
|
||||
return SkinningSpace;
|
||||
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);
|
||||
}
|
||||
|
||||
private:
|
||||
void helper_updateAbsolutePositionOfAllChildren(ISceneNode *Node);
|
||||
core::Transform getTransform() const
|
||||
{
|
||||
return {
|
||||
getPosition(),
|
||||
core::quaternion(getRotation() * core::DEGTORAD).makeInverse(),
|
||||
getScale()
|
||||
};
|
||||
}
|
||||
|
||||
u32 BoneIndex;
|
||||
core::matrix4 getRelativeTransformation() const override
|
||||
{
|
||||
if (Matrix)
|
||||
return *Matrix;
|
||||
return IBoneSceneNode::getRelativeTransformation();
|
||||
}
|
||||
|
||||
core::aabbox3d<f32> Box{-1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f};
|
||||
|
||||
E_BONE_ANIMATION_MODE AnimationMode;
|
||||
E_BONE_SKINNING_SPACE SkinningSpace;
|
||||
//! Some file formats alternatively let bones specify a transformation matrix.
|
||||
//! If this is set, it overrides the TRS properties.
|
||||
std::optional<core::matrix4> Matrix;
|
||||
};
|
||||
|
||||
} // end namespace scene
|
||||
|
|
|
@ -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;
|
||||
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]),
|
||||
};
|
||||
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();
|
||||
}
|
||||
|
@ -642,7 +632,6 @@ void SelfType::MeshExtractor::loadAnimation(const std::size_t animIdx)
|
|||
{
|
||||
const auto &anim = m_gltf_model.animations->at(animIdx);
|
||||
for (const auto &channel : anim.channels) {
|
||||
|
||||
const auto &sampler = anim.samplers.at(channel.sampler);
|
||||
|
||||
bool interpolate = ([&]() {
|
||||
|
@ -663,6 +652,11 @@ void SelfType::MeshExtractor::loadAnimation(const std::size_t animIdx)
|
|||
throw std::runtime_error("no animated node");
|
||||
|
||||
auto *joint = m_loaded_nodes.at(*channel.target.node);
|
||||
if (std::holds_alternative<core::matrix4>(joint->transform)) {
|
||||
warn("nodes using matrix transforms must not be animated");
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (channel.target.path) {
|
||||
case tiniergltf::AnimationChannelTarget::Path::TRANSLATION: {
|
||||
const auto outputAccessor = Accessor<core::vector3df>::make(m_gltf_model, sampler.output);
|
||||
|
|
|
@ -320,7 +320,6 @@ set(IRRMESHLOADER
|
|||
|
||||
add_library(IRRMESHOBJ OBJECT
|
||||
SkinnedMesh.cpp
|
||||
CBoneSceneNode.cpp
|
||||
CMeshSceneNode.cpp
|
||||
CAnimatedMeshSceneNode.cpp
|
||||
${IRRMESHLOADER}
|
||||
|
|
|
@ -35,7 +35,7 @@ void CMeshCache::removeMesh(const IMesh *const mesh)
|
|||
if (!mesh)
|
||||
return;
|
||||
for (u32 i = 0; i < Meshes.size(); ++i) {
|
||||
if (Meshes[i].Mesh == mesh || (Meshes[i].Mesh && Meshes[i].Mesh->getMesh(0) == mesh)) {
|
||||
if (Meshes[i].Mesh == mesh) {
|
||||
Meshes[i].Mesh->drop();
|
||||
Meshes.erase(i);
|
||||
return;
|
||||
|
@ -53,7 +53,7 @@ u32 CMeshCache::getMeshCount() const
|
|||
s32 CMeshCache::getMeshIndex(const IMesh *const mesh) const
|
||||
{
|
||||
for (u32 i = 0; i < Meshes.size(); ++i) {
|
||||
if (Meshes[i].Mesh == mesh || (Meshes[i].Mesh && Meshes[i].Mesh->getMesh(0) == mesh))
|
||||
if (Meshes[i].Mesh == mesh)
|
||||
return (s32)i;
|
||||
}
|
||||
|
||||
|
@ -93,7 +93,7 @@ const io::SNamedPath &CMeshCache::getMeshName(const IMesh *const mesh) const
|
|||
return emptyNamedPath;
|
||||
|
||||
for (u32 i = 0; i < Meshes.size(); ++i) {
|
||||
if (Meshes[i].Mesh == mesh || (Meshes[i].Mesh && Meshes[i].Mesh->getMesh(0) == mesh))
|
||||
if (Meshes[i].Mesh == mesh)
|
||||
return Meshes[i].NamedPath;
|
||||
}
|
||||
|
||||
|
@ -115,7 +115,7 @@ bool CMeshCache::renameMesh(u32 index, const io::path &name)
|
|||
bool CMeshCache::renameMesh(const IMesh *const mesh, const io::path &name)
|
||||
{
|
||||
for (u32 i = 0; i < Meshes.size(); ++i) {
|
||||
if (Meshes[i].Mesh == mesh || (Meshes[i].Mesh && Meshes[i].Mesh->getMesh(0) == mesh)) {
|
||||
if (Meshes[i].Mesh == mesh) {
|
||||
Meshes[i].NamedPath.setPath(name);
|
||||
Meshes.sort();
|
||||
return true;
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
#include "SkinnedMesh.h"
|
||||
#include "SMesh.h"
|
||||
#include "CMeshBuffer.h"
|
||||
#include "SAnimatedMesh.h"
|
||||
#include "os.h"
|
||||
|
||||
#include <cassert>
|
||||
|
@ -178,34 +177,5 @@ SMesh *CMeshManipulator::createMeshCopy(scene::IMesh *mesh) const
|
|||
return clone;
|
||||
}
|
||||
|
||||
//! Returns amount of polygons in mesh.
|
||||
s32 CMeshManipulator::getPolyCount(scene::IMesh *mesh) const
|
||||
{
|
||||
if (!mesh)
|
||||
return 0;
|
||||
|
||||
s32 trianglecount = 0;
|
||||
|
||||
for (u32 g = 0; g < mesh->getMeshBufferCount(); ++g)
|
||||
trianglecount += mesh->getMeshBuffer(g)->getIndexCount() / 3;
|
||||
|
||||
return trianglecount;
|
||||
}
|
||||
|
||||
//! Returns amount of polygons in mesh.
|
||||
s32 CMeshManipulator::getPolyCount(scene::IAnimatedMesh *mesh) const
|
||||
{
|
||||
if (mesh && mesh->getMaxFrameNumber() != 0)
|
||||
return getPolyCount(mesh->getMesh(0));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
//! create a new AnimatedMesh and adds the mesh to it
|
||||
IAnimatedMesh *CMeshManipulator::createAnimatedMesh(scene::IMesh *mesh, scene::E_ANIMATED_MESH_TYPE type) const
|
||||
{
|
||||
return new SAnimatedMesh(mesh, type);
|
||||
}
|
||||
|
||||
} // end namespace scene
|
||||
} // end namespace irr
|
||||
|
|
|
@ -31,15 +31,6 @@ public:
|
|||
|
||||
//! Clones a static IMesh into a modifiable SMesh.
|
||||
SMesh *createMeshCopy(scene::IMesh *mesh) const override;
|
||||
|
||||
//! Returns amount of polygons in mesh.
|
||||
s32 getPolyCount(scene::IMesh *mesh) const override;
|
||||
|
||||
//! Returns amount of polygons in mesh.
|
||||
s32 getPolyCount(scene::IAnimatedMesh *mesh) const override;
|
||||
|
||||
//! create a new AnimatedMesh and adds the mesh to it
|
||||
IAnimatedMesh *createAnimatedMesh(scene::IMesh *mesh, scene::E_ANIMATED_MESH_TYPE type) const override;
|
||||
};
|
||||
|
||||
} // end namespace scene
|
||||
|
|
|
@ -1238,7 +1238,7 @@ void CNullDriver::addOcclusionQuery(scene::ISceneNode *node, const scene::IMesh
|
|||
else if (node->getType() == scene::ESNT_MESH)
|
||||
mesh = static_cast<scene::IMeshSceneNode *>(node)->getMesh();
|
||||
else
|
||||
mesh = static_cast<scene::IAnimatedMeshSceneNode *>(node)->getMesh()->getMesh(0);
|
||||
mesh = static_cast<scene::IAnimatedMeshSceneNode *>(node)->getMesh();
|
||||
if (!mesh)
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
#include "IVideoDriver.h"
|
||||
#include "SMesh.h"
|
||||
#include "SMeshBuffer.h"
|
||||
#include "SAnimatedMesh.h"
|
||||
#include "IReadFile.h"
|
||||
#include "fast_atof.h"
|
||||
#include "coreutil.h"
|
||||
|
@ -272,23 +271,19 @@ IAnimatedMesh *COBJMeshFileLoader::createMesh(io::IReadFile *file)
|
|||
}
|
||||
}
|
||||
|
||||
// Create the Animated mesh if there's anything in the mesh
|
||||
SAnimatedMesh *animMesh = 0;
|
||||
if (0 != mesh->getMeshBufferCount()) {
|
||||
mesh->recalculateBoundingBox();
|
||||
animMesh = new SAnimatedMesh();
|
||||
animMesh->Type = EAMT_OBJ;
|
||||
animMesh->addMesh(mesh);
|
||||
animMesh->recalculateBoundingBox();
|
||||
}
|
||||
|
||||
// Clean up the allocate obj file contents
|
||||
delete[] buf;
|
||||
// more cleaning up
|
||||
cleanUp();
|
||||
mesh->drop();
|
||||
|
||||
return animMesh;
|
||||
// Nothing in the mesh
|
||||
if (mesh->getMeshBufferCount() == 0) {
|
||||
mesh->drop();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
mesh->recalculateBoundingBox();
|
||||
return mesh;
|
||||
}
|
||||
|
||||
//! Read RGB color
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
#include "CSceneManager.h"
|
||||
#include "IVideoDriver.h"
|
||||
#include "IFileSystem.h"
|
||||
#include "SAnimatedMesh.h"
|
||||
#include "CMeshCache.h"
|
||||
#include "IGUIEnvironment.h"
|
||||
#include "IMaterialRenderer.h"
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include "CXMeshFileLoader.h"
|
||||
#include "SkinnedMesh.h"
|
||||
#include "Transform.h"
|
||||
#include "os.h"
|
||||
|
||||
#include "fast_atof.h"
|
||||
|
@ -513,6 +514,7 @@ bool CXMeshFileLoader::parseDataObjectFrame(SkinnedMesh::SJoint *Parent)
|
|||
if (n.has_value()) {
|
||||
JointID = *n;
|
||||
joint = AnimatedMesh->getAllJoints()[JointID];
|
||||
joint->setParent(Parent);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -527,8 +529,6 @@ bool CXMeshFileLoader::parseDataObjectFrame(SkinnedMesh::SJoint *Parent)
|
|||
#ifdef _XREADER_DEBUG
|
||||
os::Printer::log("using joint ", name.c_str(), ELL_DEBUG);
|
||||
#endif
|
||||
if (Parent)
|
||||
Parent->Children.push_back(joint);
|
||||
}
|
||||
|
||||
// Now inside a frame.
|
||||
|
@ -552,12 +552,10 @@ bool CXMeshFileLoader::parseDataObjectFrame(SkinnedMesh::SJoint *Parent)
|
|||
if (!parseDataObjectFrame(joint))
|
||||
return false;
|
||||
} else if (objectName == "FrameTransformMatrix") {
|
||||
if (!parseDataObjectTransformationMatrix(joint->LocalMatrix))
|
||||
core::matrix4 matrix;
|
||||
if (!parseDataObjectTransformationMatrix(matrix))
|
||||
return false;
|
||||
|
||||
// joint->LocalAnimatedMatrix
|
||||
// joint->LocalAnimatedMatrix.makeInverse();
|
||||
// joint->LocalMatrix=tmp*joint->LocalAnimatedMatrix;
|
||||
joint->transform = matrix;
|
||||
} else if (objectName == "Mesh") {
|
||||
/*
|
||||
frame.Meshes.push_back(SXMesh());
|
||||
|
|
|
@ -7,7 +7,15 @@
|
|||
#include "CBoneSceneNode.h"
|
||||
#include "IAnimatedMeshSceneNode.h"
|
||||
#include "SSkinMeshBuffer.h"
|
||||
#include "Transform.h"
|
||||
#include "aabbox3d.h"
|
||||
#include "irrMath.h"
|
||||
#include "matrix4.h"
|
||||
#include "os.h"
|
||||
#include "vector3d.h"
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
#include <cassert>
|
||||
|
||||
|
@ -48,183 +56,77 @@ void SkinnedMesh::setAnimationSpeed(f32 fps)
|
|||
FramesPerSecond = fps;
|
||||
}
|
||||
|
||||
//! returns the animated mesh based
|
||||
IMesh *SkinnedMesh::getMesh(f32 frame)
|
||||
{
|
||||
// animate(frame,startFrameLoop, endFrameLoop);
|
||||
if (frame == -1)
|
||||
return this;
|
||||
// Keyframe Animation
|
||||
|
||||
animateMesh(frame);
|
||||
skinMesh();
|
||||
return this;
|
||||
|
||||
using VariantTransform = SkinnedMesh::SJoint::VariantTransform;
|
||||
std::vector<VariantTransform> SkinnedMesh::animateMesh(f32 frame)
|
||||
{
|
||||
assert(HasAnimation);
|
||||
std::vector<VariantTransform> result;
|
||||
result.reserve(AllJoints.size());
|
||||
for (auto *joint : AllJoints)
|
||||
result.push_back(joint->animate(frame));
|
||||
return result;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Keyframe Animation
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
//! Animates joints based on frame input
|
||||
void SkinnedMesh::animateMesh(f32 frame)
|
||||
core::aabbox3df SkinnedMesh::calculateBoundingBox(
|
||||
const std::vector<core::matrix4> &global_transforms)
|
||||
{
|
||||
if (!HasAnimation || LastAnimatedFrame == frame)
|
||||
return;
|
||||
|
||||
LastAnimatedFrame = frame;
|
||||
SkinnedLastFrame = false;
|
||||
|
||||
for (auto *joint : AllJoints) {
|
||||
// The joints can be animated here with no input from their
|
||||
// parents, but for setAnimationMode extra checks are needed
|
||||
// to their parents
|
||||
joint->keys.updateTransform(frame,
|
||||
joint->Animatedposition,
|
||||
joint->Animatedrotation,
|
||||
joint->Animatedscale);
|
||||
assert(global_transforms.size() == AllJoints.size());
|
||||
core::aabbox3df result = StaticPartsBox;
|
||||
// skeletal animation
|
||||
for (u16 i = 0; i < AllJoints.size(); ++i) {
|
||||
auto box = AllJoints[i]->LocalBoundingBox;
|
||||
global_transforms[i].transformBoxEx(box);
|
||||
result.addInternalBox(box);
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
void SkinnedMesh::buildAllLocalAnimatedMatrices()
|
||||
{
|
||||
for (auto *joint : AllJoints) {
|
||||
// Could be faster:
|
||||
|
||||
if (!joint->keys.empty()) {
|
||||
joint->GlobalSkinningSpace = false;
|
||||
|
||||
// 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;
|
||||
// rigid animation
|
||||
for (u16 i = 0; i < AllJoints.size(); ++i) {
|
||||
for (u32 j : AllJoints[i]->AttachedMeshes) {
|
||||
auto box = (*SkinningBuffers)[j]->BoundingBox;
|
||||
global_transforms[i].transformBoxEx(box);
|
||||
result.addInternalBox(box);
|
||||
}
|
||||
}
|
||||
SkinnedLastFrame = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
void SkinnedMesh::buildAllGlobalAnimatedMatrices(SJoint *joint, SJoint *parentJoint)
|
||||
// Software Skinning
|
||||
|
||||
void SkinnedMesh::skinMesh(const std::vector<core::matrix4> &global_matrices)
|
||||
{
|
||||
if (!joint) {
|
||||
for (auto *rootJoint : RootJoints)
|
||||
buildAllGlobalAnimatedMatrices(rootJoint, 0);
|
||||
return;
|
||||
} else {
|
||||
// Find global matrix...
|
||||
if (!parentJoint || joint->GlobalSkinningSpace)
|
||||
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);
|
||||
|
@ -251,14 +153,11 @@ void SkinnedMesh::skinJoint(SJoint *joint, SJoint *parentJoint)
|
|||
|
||||
//*(weight._Pos) += thisVertexMove * weight.strength;
|
||||
}
|
||||
|
||||
buffersUsed[weight.buffer_id]->boundingBoxNeedsRecalculated();
|
||||
}
|
||||
}
|
||||
|
||||
// Skin all children
|
||||
for (auto *childJoint : joint->Children)
|
||||
skinJoint(childJoint, joint);
|
||||
for (auto *buffer : *SkinningBuffers)
|
||||
buffer->setDirty(EBT_VERTEX);
|
||||
}
|
||||
|
||||
//! Gets joint count.
|
||||
|
@ -310,7 +209,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
|
||||
|
@ -337,29 +236,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...
|
||||
|
@ -384,113 +260,192 @@ 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) const
|
||||
{
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
if (!parentJoint)
|
||||
joint->GlobalMatrix = joint->LocalMatrix;
|
||||
else
|
||||
joint->GlobalMatrix = parentJoint->GlobalMatrix * joint->LocalMatrix;
|
||||
|
||||
joint->LocalAnimatedMatrix = joint->LocalMatrix;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
for (auto *joint : AllJoints) {
|
||||
joint->keys.cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
void SkinnedMesh::calculateStaticBoundingBox()
|
||||
{
|
||||
std::vector<std::vector<bool>> animated(getMeshBufferCount());
|
||||
for (u32 mb = 0; mb < getMeshBufferCount(); mb++)
|
||||
animated[mb] = std::vector<bool>(getMeshBuffer(mb)->getVertexCount());
|
||||
|
||||
for (auto *joint : AllJoints) {
|
||||
for (auto &weight : joint->Weights) {
|
||||
const u16 buffer_id = weight.buffer_id;
|
||||
const u32 vertex_id = weight.vertex_id;
|
||||
animated[buffer_id][vertex_id] = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool first = true;
|
||||
for (u16 mb = 0; mb < getMeshBufferCount(); mb++) {
|
||||
for (u32 v = 0; v < getMeshBuffer(mb)->getVertexCount(); v++) {
|
||||
if (!animated[mb][v]) {
|
||||
auto pos = getMeshBuffer(mb)->getVertexBuffer()->getPosition(v);
|
||||
if (!first) {
|
||||
StaticPartsBox.addInternalPoint(pos);
|
||||
} else {
|
||||
StaticPartsBox.reset(pos);
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
void SkinnedMesh::calculateJointBoundingBoxes()
|
||||
{
|
||||
for (auto *joint : AllJoints) {
|
||||
bool first = true;
|
||||
for (auto &weight : joint->Weights) {
|
||||
if (weight.strength < 1e-6)
|
||||
continue;
|
||||
auto pos = weight.StaticPos;
|
||||
joint->GlobalInversedMatrix.value().transformVect(pos);
|
||||
if (!first) {
|
||||
joint->LocalBoundingBox.addInternalPoint(pos);
|
||||
} else {
|
||||
joint->LocalBoundingBox.reset(pos);
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
|
||||
// normalize weights
|
||||
normalizeWeights();
|
||||
}
|
||||
SkinnedLastFrame = false;
|
||||
}
|
||||
|
||||
void SkinnedMesh::calculateBufferBoundingBoxes()
|
||||
{
|
||||
for (u32 j = 0; j < LocalBuffers.size(); ++j) {
|
||||
// If we use skeletal animation, this will just be a bounding box of the static pose;
|
||||
// if we use rigid animation, this will correctly transform the points first.
|
||||
LocalBuffers[j]->recalculateBoundingBox();
|
||||
}
|
||||
}
|
||||
|
||||
void SkinnedMesh::recalculateBaseBoundingBoxes() {
|
||||
calculateStaticBoundingBox();
|
||||
calculateJointBoundingBoxes();
|
||||
calculateBufferBoundingBoxes();
|
||||
}
|
||||
|
||||
void SkinnedMesh::topoSortJoints()
|
||||
{
|
||||
size_t n = AllJoints.size();
|
||||
|
||||
std::vector<u16> new_to_old_id;
|
||||
|
||||
std::vector<std::vector<u16>> children(n);
|
||||
for (u16 i = 0; i < n; ++i) {
|
||||
if (auto parentId = AllJoints[i]->ParentJointID)
|
||||
children[*parentId].push_back(i);
|
||||
else
|
||||
new_to_old_id.push_back(i);
|
||||
}
|
||||
|
||||
// Levelorder
|
||||
for (u16 i = 0; i < n; ++i) {
|
||||
new_to_old_id.insert(new_to_old_id.end(),
|
||||
children[new_to_old_id[i]].begin(),
|
||||
children[new_to_old_id[i]].end());
|
||||
}
|
||||
|
||||
std::vector<u16> old_to_new_id(n);
|
||||
for (u16 i = 0; i < n; ++i)
|
||||
old_to_new_id[new_to_old_id[i]] = i;
|
||||
|
||||
std::vector<SJoint *> joints(n);
|
||||
for (u16 i = 0; i < n; ++i) {
|
||||
joints[i] = AllJoints[new_to_old_id[i]];
|
||||
joints[i]->JointID = i;
|
||||
if (auto parentId = joints[i]->ParentJointID)
|
||||
joints[i]->ParentJointID = old_to_new_id[*parentId];
|
||||
}
|
||||
AllJoints = std::move(joints);
|
||||
|
||||
for (u16 i = 0; i < n; ++i) {
|
||||
if (auto pjid = AllJoints[i]->ParentJointID)
|
||||
assert(*pjid < i);
|
||||
}
|
||||
}
|
||||
|
||||
//! called by loader after populating with mesh and bone data
|
||||
|
@ -498,98 +453,44 @@ SkinnedMesh *SkinnedMeshBuilder::finalize()
|
|||
{
|
||||
os::Printer::log("Skinned Mesh - finalize", ELL_DEBUG);
|
||||
|
||||
// Make sure we recalc the next frame
|
||||
LastAnimatedFrame = -1;
|
||||
SkinnedLastFrame = false;
|
||||
|
||||
// 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...
|
||||
topoSortJoints();
|
||||
|
||||
// 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());
|
||||
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];
|
||||
if (!joint->GlobalInversedMatrix) {
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
// calculate bounding box
|
||||
if (LocalBuffers.empty())
|
||||
BoundingBox.reset(0, 0, 0);
|
||||
else {
|
||||
irr::core::aabbox3df bb(LocalBuffers[0]->BoundingBox);
|
||||
LocalBuffers[0]->Transformation.transformBoxEx(bb);
|
||||
BoundingBox.reset(bb);
|
||||
|
||||
for (u32 j = 1; j < LocalBuffers.size(); ++j) {
|
||||
bb = LocalBuffers[j]->BoundingBox;
|
||||
LocalBuffers[j]->Transformation.transformBoxEx(bb);
|
||||
|
||||
BoundingBox.addInternalBox(bb);
|
||||
}
|
||||
}
|
||||
recalculateBaseBoundingBoxes();
|
||||
StaticPoseBox = calculateBoundingBox(matrices);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
void SkinnedMesh::updateBoundingBox()
|
||||
{
|
||||
if (!SkinningBuffers)
|
||||
return;
|
||||
|
||||
BoundingBox.reset(0, 0, 0);
|
||||
|
||||
for (auto *buffer : *SkinningBuffers) {
|
||||
buffer->recalculateBoundingBox();
|
||||
core::aabbox3df bb = buffer->BoundingBox;
|
||||
buffer->Transformation.transformBoxEx(bb);
|
||||
|
||||
BoundingBox.addInternalBox(bb);
|
||||
}
|
||||
}
|
||||
|
||||
scene::SSkinMeshBuffer *SkinnedMeshBuilder::addMeshBuffer()
|
||||
{
|
||||
scene::SSkinMeshBuffer *buffer = new scene::SSkinMeshBuffer();
|
||||
|
@ -607,14 +508,10 @@ void SkinnedMeshBuilder::addMeshBuffer(SSkinMeshBuffer *meshbuf)
|
|||
SkinnedMesh::SJoint *SkinnedMeshBuilder::addJoint(SJoint *parent)
|
||||
{
|
||||
SJoint *joint = new SJoint;
|
||||
joint->setParent(parent);
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -684,73 +581,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];
|
||||
node->setPosition(joint->LocalAnimatedMatrix.getTranslation());
|
||||
node->setRotation(joint->LocalAnimatedMatrix.getRotationDegrees());
|
||||
node->setScale(joint->LocalAnimatedMatrix.getScale());
|
||||
|
||||
node->updateAbsolutePosition();
|
||||
}
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
joint->LocalAnimatedMatrix.setRotationDegrees(node->getRotation());
|
||||
joint->LocalAnimatedMatrix.setTranslation(node->getPosition());
|
||||
joint->LocalAnimatedMatrix *= core::matrix4().setScale(node->getScale());
|
||||
|
||||
joint->GlobalSkinningSpace = (node->getSkinningSpace() == EBSS_GLOBAL);
|
||||
}
|
||||
// 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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue