2024-03-21 20:13:15 +01:00
|
|
|
// 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
|
|
|
|
|
2024-12-06 18:03:44 +01:00
|
|
|
#include "SkinnedMesh.h"
|
|
|
|
#include "IBoneSceneNode.h"
|
2024-03-21 20:13:15 +01:00
|
|
|
#include "CBoneSceneNode.h"
|
|
|
|
#include "IAnimatedMeshSceneNode.h"
|
2024-09-02 07:50:30 -05:00
|
|
|
#include "SSkinMeshBuffer.h"
|
2025-01-16 17:13:59 +01:00
|
|
|
#include "irrMath.h"
|
|
|
|
#include "matrix4.h"
|
2024-03-21 20:13:15 +01:00
|
|
|
#include "os.h"
|
2025-01-16 17:13:59 +01:00
|
|
|
#include "vector3d.h"
|
|
|
|
#include <variant>
|
2024-12-12 15:33:08 +01:00
|
|
|
#include <vector>
|
2025-04-04 01:26:02 +02:00
|
|
|
#include <cassert>
|
2024-03-21 20:13:15 +01:00
|
|
|
|
|
|
|
namespace irr
|
|
|
|
{
|
|
|
|
namespace scene
|
|
|
|
{
|
|
|
|
|
|
|
|
//! destructor
|
2024-12-06 18:03:44 +01:00
|
|
|
SkinnedMesh::~SkinnedMesh()
|
2024-03-21 20:13:15 +01:00
|
|
|
{
|
2024-12-12 15:33:08 +01:00
|
|
|
for (auto *joint : AllJoints)
|
|
|
|
delete joint;
|
2024-03-21 20:13:15 +01:00
|
|
|
|
2024-12-12 15:33:08 +01:00
|
|
|
for (auto *buffer : LocalBuffers) {
|
|
|
|
if (buffer)
|
|
|
|
buffer->drop();
|
2024-03-21 20:13:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-06 18:03:44 +01:00
|
|
|
f32 SkinnedMesh::getMaxFrameNumber() const
|
2024-03-21 20:13:15 +01:00
|
|
|
{
|
2024-09-02 21:11:08 +02:00
|
|
|
return EndFrame;
|
2024-03-21 20:13:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//! Gets the default animation speed of the animated mesh.
|
|
|
|
/** \return Amount of frames per second. If the amount is 0, it is a static, non animated mesh. */
|
2024-12-06 18:03:44 +01:00
|
|
|
f32 SkinnedMesh::getAnimationSpeed() const
|
2024-03-21 20:13:15 +01:00
|
|
|
{
|
|
|
|
return FramesPerSecond;
|
|
|
|
}
|
|
|
|
|
|
|
|
//! Gets the frame count of the animated mesh.
|
|
|
|
/** \param fps Frames per second to play the animation with. If the amount is 0, it is not animated.
|
|
|
|
The actual speed is set in the scene node the mesh is instantiated in.*/
|
2024-12-06 18:03:44 +01:00
|
|
|
void SkinnedMesh::setAnimationSpeed(f32 fps)
|
2024-03-21 20:13:15 +01:00
|
|
|
{
|
|
|
|
FramesPerSecond = fps;
|
|
|
|
}
|
|
|
|
|
2024-09-02 21:11:08 +02:00
|
|
|
//! returns the animated mesh based
|
2024-12-06 18:03:44 +01:00
|
|
|
IMesh *SkinnedMesh::getMesh(f32 frame)
|
2024-03-21 20:13:15 +01:00
|
|
|
{
|
|
|
|
// animate(frame,startFrameLoop, endFrameLoop);
|
|
|
|
if (frame == -1)
|
|
|
|
return this;
|
|
|
|
|
2024-12-12 15:33:08 +01:00
|
|
|
animateMesh(frame);
|
2024-03-21 20:13:15 +01:00
|
|
|
skinMesh();
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
// Keyframe Animation
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
|
2024-12-12 15:33:08 +01:00
|
|
|
//! Animates joints based on frame input
|
|
|
|
void SkinnedMesh::animateMesh(f32 frame)
|
2024-03-21 20:13:15 +01:00
|
|
|
{
|
|
|
|
if (!HasAnimation || LastAnimatedFrame == frame)
|
|
|
|
return;
|
|
|
|
|
|
|
|
LastAnimatedFrame = frame;
|
|
|
|
SkinnedLastFrame = false;
|
|
|
|
|
2025-01-16 17:13:59 +01:00
|
|
|
for (auto *joint : AllJoints)
|
|
|
|
joint->animate(frame);
|
2024-03-21 20:13:15 +01:00
|
|
|
|
|
|
|
// 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();
|
|
|
|
}
|
|
|
|
|
2024-12-06 18:03:44 +01:00
|
|
|
void SkinnedMesh::buildAllLocalAnimatedMatrices()
|
2024-03-21 20:13:15 +01:00
|
|
|
{
|
2024-12-12 15:33:08 +01:00
|
|
|
for (auto *joint : AllJoints) {
|
2024-03-21 20:13:15 +01:00
|
|
|
// Could be faster:
|
2025-01-16 17:13:59 +01:00
|
|
|
joint->LocalAnimatedMatrix = joint->buildLocalMatrix();
|
2024-03-21 20:13:15 +01:00
|
|
|
}
|
|
|
|
SkinnedLastFrame = false;
|
|
|
|
}
|
|
|
|
|
2024-12-06 18:03:44 +01:00
|
|
|
void SkinnedMesh::buildAllGlobalAnimatedMatrices(SJoint *joint, SJoint *parentJoint)
|
2024-03-21 20:13:15 +01:00
|
|
|
{
|
|
|
|
if (!joint) {
|
2024-12-12 15:33:08 +01:00
|
|
|
for (auto *rootJoint : RootJoints)
|
|
|
|
buildAllGlobalAnimatedMatrices(rootJoint, 0);
|
2024-03-21 20:13:15 +01:00
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
// Find global matrix...
|
2025-01-14 18:04:15 +01:00
|
|
|
if (!parentJoint)
|
2024-03-21 20:13:15 +01:00
|
|
|
joint->GlobalAnimatedMatrix = joint->LocalAnimatedMatrix;
|
|
|
|
else
|
|
|
|
joint->GlobalAnimatedMatrix = parentJoint->GlobalAnimatedMatrix * joint->LocalAnimatedMatrix;
|
|
|
|
}
|
|
|
|
|
2024-12-12 15:33:08 +01:00
|
|
|
for (auto *childJoint : joint->Children)
|
|
|
|
buildAllGlobalAnimatedMatrices(childJoint, joint);
|
2024-03-21 20:13:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
// Software Skinning
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
|
|
|
|
//! Preforms a software skin on this mesh based of joint positions
|
2024-12-06 18:03:44 +01:00
|
|
|
void SkinnedMesh::skinMesh()
|
2024-03-21 20:13:15 +01:00
|
|
|
{
|
|
|
|
if (!HasAnimation || SkinnedLastFrame)
|
|
|
|
return;
|
|
|
|
|
|
|
|
//----------------
|
|
|
|
// This is marked as "Temp!". A shiny dubloon to whomever can tell me why.
|
|
|
|
buildAllGlobalAnimatedMatrices();
|
|
|
|
//-----------------
|
|
|
|
|
|
|
|
SkinnedLastFrame = true;
|
|
|
|
if (!HardwareSkinning) {
|
|
|
|
// rigid animation
|
2024-12-12 15:33:08 +01:00
|
|
|
for (auto *joint : AllJoints) {
|
|
|
|
for (u32 attachedMeshIdx : joint->AttachedMeshes) {
|
|
|
|
SSkinMeshBuffer *Buffer = (*SkinningBuffers)[attachedMeshIdx];
|
|
|
|
Buffer->Transformation = joint->GlobalAnimatedMatrix;
|
2024-03-21 20:13:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// clear skinning helper array
|
2024-12-12 15:33:08 +01:00
|
|
|
for (std::vector<char> &buf : Vertices_Moved)
|
|
|
|
std::fill(buf.begin(), buf.end(), false);
|
2024-03-21 20:13:15 +01:00
|
|
|
|
|
|
|
// skin starting with the root joints
|
2024-12-12 15:33:08 +01:00
|
|
|
for (auto *rootJoint : RootJoints)
|
|
|
|
skinJoint(rootJoint, 0);
|
2024-03-21 20:13:15 +01:00
|
|
|
|
2024-12-12 15:33:08 +01:00
|
|
|
for (auto *buffer : *SkinningBuffers)
|
|
|
|
buffer->setDirty(EBT_VERTEX);
|
2024-03-21 20:13:15 +01:00
|
|
|
}
|
|
|
|
updateBoundingBox();
|
|
|
|
}
|
|
|
|
|
2024-12-06 18:03:44 +01:00
|
|
|
void SkinnedMesh::skinJoint(SJoint *joint, SJoint *parentJoint)
|
2024-03-21 20:13:15 +01:00
|
|
|
{
|
|
|
|
if (joint->Weights.size()) {
|
|
|
|
// Find this joints pull on vertices...
|
2024-05-15 01:00:07 +02:00
|
|
|
// Note: It is assumed that the global inversed matrix has been calculated at this point.
|
|
|
|
core::matrix4 jointVertexPull = joint->GlobalAnimatedMatrix * joint->GlobalInversedMatrix.value();
|
2024-03-21 20:13:15 +01:00
|
|
|
|
|
|
|
core::vector3df thisVertexMove, thisNormalMove;
|
|
|
|
|
2024-12-12 15:33:08 +01:00
|
|
|
auto &buffersUsed = *SkinningBuffers;
|
2024-03-21 20:13:15 +01:00
|
|
|
|
|
|
|
// Skin Vertices Positions and Normals...
|
2024-12-12 15:33:08 +01:00
|
|
|
for (const auto &weight : joint->Weights) {
|
2024-03-21 20:13:15 +01:00
|
|
|
// Pull this vertex...
|
|
|
|
jointVertexPull.transformVect(thisVertexMove, weight.StaticPos);
|
|
|
|
|
2024-05-15 01:00:07 +02:00
|
|
|
if (AnimateNormals) {
|
2024-05-14 23:48:36 +02:00
|
|
|
thisNormalMove = jointVertexPull.rotateAndScaleVect(weight.StaticNormal);
|
2024-05-15 01:00:07 +02:00
|
|
|
thisNormalMove.normalize(); // must renormalize after potentially scaling
|
|
|
|
}
|
2024-03-21 20:13:15 +01:00
|
|
|
|
|
|
|
if (!(*(weight.Moved))) {
|
|
|
|
*(weight.Moved) = true;
|
|
|
|
|
|
|
|
buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Pos = thisVertexMove * weight.strength;
|
|
|
|
|
|
|
|
if (AnimateNormals)
|
|
|
|
buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Normal = thisNormalMove * weight.strength;
|
|
|
|
|
|
|
|
//*(weight._Pos) = thisVertexMove * weight.strength;
|
|
|
|
} else {
|
|
|
|
buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Pos += thisVertexMove * weight.strength;
|
|
|
|
|
|
|
|
if (AnimateNormals)
|
|
|
|
buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Normal += thisNormalMove * weight.strength;
|
|
|
|
|
|
|
|
//*(weight._Pos) += thisVertexMove * weight.strength;
|
|
|
|
}
|
|
|
|
|
|
|
|
buffersUsed[weight.buffer_id]->boundingBoxNeedsRecalculated();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Skin all children
|
2024-12-12 15:33:08 +01:00
|
|
|
for (auto *childJoint : joint->Children)
|
|
|
|
skinJoint(childJoint, joint);
|
2024-03-21 20:13:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//! Gets joint count.
|
2024-12-06 18:03:44 +01:00
|
|
|
u32 SkinnedMesh::getJointCount() const
|
2024-03-21 20:13:15 +01:00
|
|
|
{
|
|
|
|
return AllJoints.size();
|
|
|
|
}
|
|
|
|
|
|
|
|
//! Gets the name of a joint.
|
2024-12-06 18:03:44 +01:00
|
|
|
const std::optional<std::string> &SkinnedMesh::getJointName(u32 number) const
|
2024-03-21 20:13:15 +01:00
|
|
|
{
|
|
|
|
if (number >= getJointCount()) {
|
|
|
|
static const std::optional<std::string> nullopt;
|
|
|
|
return nullopt;
|
|
|
|
}
|
|
|
|
return AllJoints[number]->Name;
|
|
|
|
}
|
|
|
|
|
|
|
|
//! Gets a joint number from its name
|
2024-12-06 18:03:44 +01:00
|
|
|
std::optional<u32> SkinnedMesh::getJointNumber(const std::string &name) const
|
2024-03-21 20:13:15 +01:00
|
|
|
{
|
|
|
|
for (u32 i = 0; i < AllJoints.size(); ++i) {
|
|
|
|
if (AllJoints[i]->Name == name)
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
|
|
|
|
//! returns amount of mesh buffers.
|
2024-12-06 18:03:44 +01:00
|
|
|
u32 SkinnedMesh::getMeshBufferCount() const
|
2024-03-21 20:13:15 +01:00
|
|
|
{
|
|
|
|
return LocalBuffers.size();
|
|
|
|
}
|
|
|
|
|
|
|
|
//! returns pointer to a mesh buffer
|
2024-12-06 18:03:44 +01:00
|
|
|
IMeshBuffer *SkinnedMesh::getMeshBuffer(u32 nr) const
|
2024-03-21 20:13:15 +01:00
|
|
|
{
|
|
|
|
if (nr < LocalBuffers.size())
|
|
|
|
return LocalBuffers[nr];
|
|
|
|
else
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
//! Returns pointer to a mesh buffer which fits a material
|
2024-12-06 18:03:44 +01:00
|
|
|
IMeshBuffer *SkinnedMesh::getMeshBuffer(const video::SMaterial &material) const
|
2024-03-21 20:13:15 +01:00
|
|
|
{
|
|
|
|
for (u32 i = 0; i < LocalBuffers.size(); ++i) {
|
|
|
|
if (LocalBuffers[i]->getMaterial() == material)
|
|
|
|
return LocalBuffers[i];
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-12-06 18:03:44 +01:00
|
|
|
u32 SkinnedMesh::getTextureSlot(u32 meshbufNr) const
|
2024-09-02 07:50:30 -05:00
|
|
|
{
|
|
|
|
return TextureSlots.at(meshbufNr);
|
|
|
|
}
|
|
|
|
|
2024-12-06 18:03:44 +01:00
|
|
|
void SkinnedMesh::setTextureSlot(u32 meshbufNr, u32 textureSlot) {
|
2024-09-02 07:50:30 -05:00
|
|
|
TextureSlots.at(meshbufNr) = textureSlot;
|
|
|
|
}
|
|
|
|
|
2024-03-21 20:13:15 +01:00
|
|
|
//! set the hardware mapping hint, for driver
|
2024-12-06 18:03:44 +01:00
|
|
|
void SkinnedMesh::setHardwareMappingHint(E_HARDWARE_MAPPING newMappingHint,
|
2024-03-21 20:13:15 +01:00
|
|
|
E_BUFFER_TYPE buffer)
|
|
|
|
{
|
|
|
|
for (u32 i = 0; i < LocalBuffers.size(); ++i)
|
|
|
|
LocalBuffers[i]->setHardwareMappingHint(newMappingHint, buffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
//! flags the meshbuffer as changed, reloads hardware buffers
|
2024-12-06 18:03:44 +01:00
|
|
|
void SkinnedMesh::setDirty(E_BUFFER_TYPE buffer)
|
2024-03-21 20:13:15 +01:00
|
|
|
{
|
|
|
|
for (u32 i = 0; i < LocalBuffers.size(); ++i)
|
|
|
|
LocalBuffers[i]->setDirty(buffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
//! (This feature is not implemented in irrlicht yet)
|
2024-12-06 18:03:44 +01:00
|
|
|
bool SkinnedMesh::setHardwareSkinning(bool on)
|
2024-03-21 20:13:15 +01:00
|
|
|
{
|
|
|
|
if (HardwareSkinning != on) {
|
|
|
|
if (on) {
|
|
|
|
|
|
|
|
// set mesh to static pose...
|
2024-12-12 15:33:08 +01:00
|
|
|
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;
|
2024-03-21 20:13:15 +01:00
|
|
|
LocalBuffers[buffer_id]->boundingBoxNeedsRecalculated();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
HardwareSkinning = on;
|
|
|
|
}
|
|
|
|
return HardwareSkinning;
|
|
|
|
}
|
|
|
|
|
2024-12-06 18:03:44 +01:00
|
|
|
void SkinnedMesh::refreshJointCache()
|
2024-03-21 20:13:15 +01:00
|
|
|
{
|
|
|
|
// copy cache from the mesh...
|
2024-12-12 15:33:08 +01:00
|
|
|
for (auto *joint : AllJoints) {
|
|
|
|
for (auto &weight : joint->Weights) {
|
|
|
|
const u16 buffer_id = weight.buffer_id;
|
|
|
|
const u32 vertex_id = weight.vertex_id;
|
|
|
|
weight.StaticPos = LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos;
|
|
|
|
weight.StaticNormal = LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal;
|
2024-03-21 20:13:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-06 18:03:44 +01:00
|
|
|
void SkinnedMesh::resetAnimation()
|
2024-03-21 20:13:15 +01:00
|
|
|
{
|
|
|
|
// copy from the cache to the mesh...
|
2024-12-12 15:33:08 +01:00
|
|
|
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;
|
2024-03-21 20:13:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
SkinnedLastFrame = false;
|
|
|
|
LastAnimatedFrame = -1;
|
|
|
|
}
|
|
|
|
|
2024-12-06 18:03:44 +01:00
|
|
|
void SkinnedMesh::calculateGlobalMatrices(SJoint *joint, SJoint *parentJoint)
|
2024-03-21 20:13:15 +01:00
|
|
|
{
|
|
|
|
if (!joint && parentJoint) // bit of protection from endless loops
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Go through the root bones
|
|
|
|
if (!joint) {
|
2024-12-12 15:33:08 +01:00
|
|
|
for (auto *rootJoint : RootJoints)
|
|
|
|
calculateGlobalMatrices(rootJoint, nullptr);
|
2024-03-21 20:13:15 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-01-16 17:13:59 +01:00
|
|
|
const auto local_matrix = joint->buildLocalMatrix();
|
2024-03-21 20:13:15 +01:00
|
|
|
if (!parentJoint)
|
2025-01-16 17:13:59 +01:00
|
|
|
joint->GlobalMatrix = local_matrix;
|
2024-03-21 20:13:15 +01:00
|
|
|
else
|
2025-01-16 17:13:59 +01:00
|
|
|
joint->GlobalMatrix = parentJoint->GlobalMatrix * local_matrix;
|
2024-03-21 20:13:15 +01:00
|
|
|
|
2025-01-16 17:13:59 +01:00
|
|
|
joint->LocalAnimatedMatrix = local_matrix;
|
2024-03-21 20:13:15 +01:00
|
|
|
joint->GlobalAnimatedMatrix = joint->GlobalMatrix;
|
|
|
|
|
2024-05-15 01:00:07 +02:00
|
|
|
if (!joint->GlobalInversedMatrix.has_value()) { // might be pre calculated
|
2024-03-21 20:13:15 +01:00
|
|
|
joint->GlobalInversedMatrix = joint->GlobalMatrix;
|
2024-05-15 01:00:07 +02:00
|
|
|
joint->GlobalInversedMatrix->makeInverse(); // slow
|
2024-03-21 20:13:15 +01:00
|
|
|
}
|
|
|
|
|
2024-12-12 15:33:08 +01:00
|
|
|
for (auto *childJoint : joint->Children)
|
|
|
|
calculateGlobalMatrices(childJoint, joint);
|
2024-03-21 20:13:15 +01:00
|
|
|
SkinnedLastFrame = false;
|
|
|
|
}
|
|
|
|
|
2024-12-06 18:03:44 +01:00
|
|
|
void SkinnedMesh::checkForAnimation()
|
2024-03-21 20:13:15 +01:00
|
|
|
{
|
|
|
|
// Check for animation...
|
|
|
|
HasAnimation = false;
|
2024-12-12 15:33:08 +01:00
|
|
|
for (auto *joint : AllJoints) {
|
|
|
|
if (!joint->keys.empty()) {
|
|
|
|
HasAnimation = true;
|
|
|
|
break;
|
2024-03-21 20:13:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// meshes with weights, are still counted as animated for ragdolls, etc
|
|
|
|
if (!HasAnimation) {
|
2024-12-12 15:33:08 +01:00
|
|
|
for (auto *joint : AllJoints) {
|
|
|
|
if (joint->Weights.size()) {
|
2024-03-21 20:13:15 +01:00
|
|
|
HasAnimation = true;
|
2024-12-12 15:33:08 +01:00
|
|
|
break;
|
|
|
|
}
|
2024-03-21 20:13:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (HasAnimation) {
|
2024-12-12 15:33:08 +01:00
|
|
|
EndFrame = 0.0f;
|
|
|
|
for (const auto *joint : AllJoints) {
|
|
|
|
EndFrame = std::max(EndFrame, joint->keys.getEndFrame());
|
2024-03-21 20:13:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (HasAnimation && !PreparedForSkinning) {
|
|
|
|
PreparedForSkinning = true;
|
|
|
|
|
|
|
|
// check for bugs:
|
2024-12-12 15:33:08 +01:00
|
|
|
for (auto *joint : AllJoints) {
|
|
|
|
for (auto &weight : joint->Weights) {
|
|
|
|
const u16 buffer_id = weight.buffer_id;
|
|
|
|
const u32 vertex_id = weight.vertex_id;
|
2024-03-21 20:13:15 +01:00
|
|
|
|
|
|
|
// check for invalid ids
|
|
|
|
if (buffer_id >= LocalBuffers.size()) {
|
|
|
|
os::Printer::log("Skinned Mesh: Weight buffer id too large", ELL_WARNING);
|
2024-12-12 15:33:08 +01:00
|
|
|
weight.buffer_id = weight.vertex_id = 0;
|
2024-03-21 20:13:15 +01:00
|
|
|
} else if (vertex_id >= LocalBuffers[buffer_id]->getVertexCount()) {
|
|
|
|
os::Printer::log("Skinned Mesh: Weight vertex id too large", ELL_WARNING);
|
2024-12-12 15:33:08 +01:00
|
|
|
weight.buffer_id = weight.vertex_id = 0;
|
2024-03-21 20:13:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// An array used in skinning
|
|
|
|
|
2024-12-12 15:33:08 +01:00
|
|
|
for (u32 i = 0; i < Vertices_Moved.size(); ++i)
|
|
|
|
for (u32 j = 0; j < Vertices_Moved[i].size(); ++j)
|
2024-03-21 20:13:15 +01:00
|
|
|
Vertices_Moved[i][j] = false;
|
|
|
|
|
|
|
|
// For skinning: cache weight values for speed
|
|
|
|
|
2024-12-12 15:33:08 +01:00
|
|
|
for (auto *joint : AllJoints) {
|
|
|
|
for (auto &weight : joint->Weights) {
|
|
|
|
const u16 buffer_id = weight.buffer_id;
|
|
|
|
const u32 vertex_id = weight.vertex_id;
|
2024-03-21 20:13:15 +01:00
|
|
|
|
2024-12-12 15:33:08 +01:00
|
|
|
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;
|
2024-03-21 20:13:15 +01:00
|
|
|
|
2024-12-12 15:33:08 +01:00
|
|
|
// weight._Pos=&Buffers[buffer_id]->getVertex(vertex_id)->Pos;
|
2024-03-21 20:13:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// normalize weights
|
|
|
|
normalizeWeights();
|
|
|
|
}
|
|
|
|
SkinnedLastFrame = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//! called by loader after populating with mesh and bone data
|
2024-12-12 15:33:08 +01:00
|
|
|
SkinnedMesh *SkinnedMeshBuilder::finalize()
|
2024-03-21 20:13:15 +01:00
|
|
|
{
|
|
|
|
os::Printer::log("Skinned Mesh - finalize", ELL_DEBUG);
|
|
|
|
|
|
|
|
// Make sure we recalc the next frame
|
|
|
|
LastAnimatedFrame = -1;
|
|
|
|
SkinnedLastFrame = false;
|
|
|
|
|
|
|
|
// calculate bounding box
|
2024-12-12 15:33:08 +01:00
|
|
|
for (auto *buffer : LocalBuffers) {
|
|
|
|
buffer->recalculateBoundingBox();
|
2024-03-21 20:13:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (AllJoints.size() || RootJoints.size()) {
|
|
|
|
// populate AllJoints or RootJoints, depending on which is empty
|
2024-12-12 15:33:08 +01:00
|
|
|
if (RootJoints.empty()) {
|
2024-03-21 20:13:15 +01:00
|
|
|
|
2024-12-12 15:33:08 +01:00
|
|
|
for (auto *joint : AllJoints) {
|
2024-03-21 20:13:15 +01:00
|
|
|
|
|
|
|
bool foundParent = false;
|
2024-12-12 15:33:08 +01:00
|
|
|
for (const auto *parentJoint : AllJoints) {
|
|
|
|
for (const auto *childJoint : parentJoint->Children) {
|
|
|
|
if (childJoint == joint)
|
2024-03-21 20:13:15 +01:00
|
|
|
foundParent = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!foundParent)
|
2024-12-12 15:33:08 +01:00
|
|
|
RootJoints.push_back(joint);
|
2024-03-21 20:13:15 +01:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
AllJoints = RootJoints;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set array sizes...
|
|
|
|
|
2024-12-12 15:33:08 +01:00
|
|
|
for (u32 i = 0; i < LocalBuffers.size(); ++i) {
|
|
|
|
Vertices_Moved.emplace_back(LocalBuffers[i]->getVertexCount());
|
2024-03-21 20:13:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
checkForAnimation();
|
|
|
|
|
|
|
|
if (HasAnimation) {
|
2024-12-12 15:33:08 +01:00
|
|
|
for (auto *joint : AllJoints) {
|
|
|
|
joint->keys.cleanup();
|
2024-03-21 20:13:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Needed for animation and skinning...
|
|
|
|
|
|
|
|
calculateGlobalMatrices(0, 0);
|
|
|
|
|
|
|
|
// rigid animation for non animated meshes
|
2024-12-12 15:33:08 +01:00
|
|
|
for (auto *joint : AllJoints) {
|
|
|
|
for (u32 attachedMeshIdx : joint->AttachedMeshes) {
|
|
|
|
SSkinMeshBuffer *Buffer = (*SkinningBuffers)[attachedMeshIdx];
|
|
|
|
Buffer->Transformation = joint->GlobalAnimatedMatrix;
|
2024-03-21 20:13:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
}
|
2024-12-12 15:33:08 +01:00
|
|
|
|
|
|
|
return this;
|
2024-03-21 20:13:15 +01:00
|
|
|
}
|
|
|
|
|
2024-12-29 14:36:30 +01:00
|
|
|
void SkinnedMesh::updateBoundingBox()
|
2024-03-21 20:13:15 +01:00
|
|
|
{
|
|
|
|
if (!SkinningBuffers)
|
|
|
|
return;
|
|
|
|
|
|
|
|
BoundingBox.reset(0, 0, 0);
|
|
|
|
|
2024-12-12 15:33:08 +01:00
|
|
|
for (auto *buffer : *SkinningBuffers) {
|
|
|
|
buffer->recalculateBoundingBox();
|
|
|
|
core::aabbox3df bb = buffer->BoundingBox;
|
|
|
|
buffer->Transformation.transformBoxEx(bb);
|
2024-03-21 20:13:15 +01:00
|
|
|
|
2024-12-12 15:33:08 +01:00
|
|
|
BoundingBox.addInternalBox(bb);
|
2024-03-21 20:13:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-12 15:33:08 +01:00
|
|
|
scene::SSkinMeshBuffer *SkinnedMeshBuilder::addMeshBuffer()
|
2024-03-21 20:13:15 +01:00
|
|
|
{
|
|
|
|
scene::SSkinMeshBuffer *buffer = new scene::SSkinMeshBuffer();
|
2024-09-02 07:50:30 -05:00
|
|
|
TextureSlots.push_back(LocalBuffers.size());
|
2024-03-21 20:13:15 +01:00
|
|
|
LocalBuffers.push_back(buffer);
|
|
|
|
return buffer;
|
|
|
|
}
|
|
|
|
|
2024-12-12 15:33:08 +01:00
|
|
|
void SkinnedMeshBuilder::addMeshBuffer(SSkinMeshBuffer *meshbuf)
|
2024-09-02 07:50:30 -05:00
|
|
|
{
|
|
|
|
TextureSlots.push_back(LocalBuffers.size());
|
|
|
|
LocalBuffers.push_back(meshbuf);
|
|
|
|
}
|
|
|
|
|
2024-12-12 15:33:08 +01:00
|
|
|
SkinnedMesh::SJoint *SkinnedMeshBuilder::addJoint(SJoint *parent)
|
2024-03-21 20:13:15 +01:00
|
|
|
{
|
|
|
|
SJoint *joint = new SJoint;
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-12-12 15:33:08 +01:00
|
|
|
void SkinnedMeshBuilder::addPositionKey(SJoint *joint, f32 frame, core::vector3df pos)
|
2024-03-21 20:13:15 +01:00
|
|
|
{
|
2025-04-04 01:26:02 +02:00
|
|
|
assert(joint);
|
2024-12-12 15:33:08 +01:00
|
|
|
joint->keys.position.pushBack(frame, pos);
|
2024-03-21 20:13:15 +01:00
|
|
|
}
|
|
|
|
|
2024-12-12 15:33:08 +01:00
|
|
|
void SkinnedMeshBuilder::addScaleKey(SJoint *joint, f32 frame, core::vector3df scale)
|
2024-03-21 20:13:15 +01:00
|
|
|
{
|
2025-04-04 01:26:02 +02:00
|
|
|
assert(joint);
|
2024-12-12 15:33:08 +01:00
|
|
|
joint->keys.scale.pushBack(frame, scale);
|
2024-03-21 20:13:15 +01:00
|
|
|
}
|
|
|
|
|
2024-12-12 15:33:08 +01:00
|
|
|
void SkinnedMeshBuilder::addRotationKey(SJoint *joint, f32 frame, core::quaternion rot)
|
2024-03-21 20:13:15 +01:00
|
|
|
{
|
2025-04-04 01:26:02 +02:00
|
|
|
assert(joint);
|
2024-12-12 15:33:08 +01:00
|
|
|
joint->keys.rotation.pushBack(frame, rot);
|
2024-03-21 20:13:15 +01:00
|
|
|
}
|
|
|
|
|
2024-12-12 15:33:08 +01:00
|
|
|
SkinnedMesh::SWeight *SkinnedMeshBuilder::addWeight(SJoint *joint)
|
2024-03-21 20:13:15 +01:00
|
|
|
{
|
|
|
|
if (!joint)
|
2024-12-12 15:33:08 +01:00
|
|
|
return nullptr;
|
2024-03-21 20:13:15 +01:00
|
|
|
|
2024-12-12 15:33:08 +01:00
|
|
|
joint->Weights.emplace_back();
|
|
|
|
return &joint->Weights.back();
|
2024-03-21 20:13:15 +01:00
|
|
|
}
|
|
|
|
|
2024-12-06 18:03:44 +01:00
|
|
|
void SkinnedMesh::normalizeWeights()
|
2024-03-21 20:13:15 +01:00
|
|
|
{
|
|
|
|
// note: unsure if weights ids are going to be used.
|
|
|
|
|
|
|
|
// Normalise the weights on bones....
|
|
|
|
|
2024-12-12 15:33:08 +01:00
|
|
|
std::vector<std::vector<f32>> verticesTotalWeight;
|
2024-03-21 20:13:15 +01:00
|
|
|
|
2024-12-12 15:33:08 +01:00
|
|
|
verticesTotalWeight.reserve(LocalBuffers.size());
|
|
|
|
for (u32 i = 0; i < LocalBuffers.size(); ++i) {
|
|
|
|
verticesTotalWeight.emplace_back(LocalBuffers[i]->getVertexCount());
|
2024-03-21 20:13:15 +01:00
|
|
|
}
|
|
|
|
|
2024-12-12 15:33:08 +01:00
|
|
|
for (u32 i = 0; i < verticesTotalWeight.size(); ++i)
|
|
|
|
for (u32 j = 0; j < verticesTotalWeight[i].size(); ++j)
|
2024-03-21 20:13:15 +01:00
|
|
|
verticesTotalWeight[i][j] = 0;
|
|
|
|
|
2024-12-12 15:33:08 +01:00
|
|
|
for (auto *joint : AllJoints) {
|
|
|
|
auto &weights = joint->Weights;
|
|
|
|
|
|
|
|
weights.erase(std::remove_if(weights.begin(), weights.end(), [](const auto &weight) {
|
|
|
|
return weight.strength <= 0;
|
|
|
|
}), weights.end());
|
|
|
|
|
|
|
|
for (const auto &weight : weights) {
|
|
|
|
verticesTotalWeight[weight.buffer_id][weight.vertex_id] += weight.strength;
|
2024-03-21 20:13:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-12 15:33:08 +01:00
|
|
|
for (auto *joint : AllJoints) {
|
|
|
|
for (auto &weight : joint->Weights) {
|
|
|
|
const f32 total = verticesTotalWeight[weight.buffer_id][weight.vertex_id];
|
2024-03-21 20:13:15 +01:00
|
|
|
if (total != 0 && total != 1)
|
2024-12-12 15:33:08 +01:00
|
|
|
weight.strength /= total;
|
2024-03-21 20:13:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-12 15:33:08 +01:00
|
|
|
void SkinnedMesh::recoverJointsFromMesh(std::vector<IBoneSceneNode *> &jointChildSceneNodes)
|
2024-03-21 20:13:15 +01:00
|
|
|
{
|
|
|
|
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());
|
|
|
|
|
2025-01-16 17:13:59 +01:00
|
|
|
node->updateAbsolutePosition(); // WTF
|
2024-03-21 20:13:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-12 15:33:08 +01:00
|
|
|
void SkinnedMesh::transferJointsToMesh(const std::vector<IBoneSceneNode *> &jointChildSceneNodes)
|
2024-03-21 20:13:15 +01:00
|
|
|
{
|
|
|
|
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());
|
|
|
|
}
|
|
|
|
// Make sure we recalc the next frame
|
|
|
|
LastAnimatedFrame = -1;
|
|
|
|
SkinnedLastFrame = false;
|
|
|
|
}
|
|
|
|
|
2024-12-12 15:33:08 +01:00
|
|
|
void SkinnedMesh::addJoints(std::vector<IBoneSceneNode *> &jointChildSceneNodes,
|
2024-03-21 20:13:15 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-12-06 18:03:44 +01:00
|
|
|
void SkinnedMesh::convertMeshToTangents()
|
2024-03-21 20:13:15 +01:00
|
|
|
{
|
|
|
|
// now calculate tangents
|
|
|
|
for (u32 b = 0; b < LocalBuffers.size(); ++b) {
|
|
|
|
if (LocalBuffers[b]) {
|
|
|
|
LocalBuffers[b]->convertToTangents();
|
|
|
|
|
|
|
|
const s32 idxCnt = LocalBuffers[b]->getIndexCount();
|
|
|
|
|
|
|
|
u16 *idx = LocalBuffers[b]->getIndices();
|
|
|
|
video::S3DVertexTangents *v =
|
|
|
|
(video::S3DVertexTangents *)LocalBuffers[b]->getVertices();
|
|
|
|
|
|
|
|
for (s32 i = 0; i < idxCnt; i += 3) {
|
|
|
|
calculateTangents(
|
|
|
|
v[idx[i + 0]].Normal,
|
|
|
|
v[idx[i + 0]].Tangent,
|
|
|
|
v[idx[i + 0]].Binormal,
|
|
|
|
v[idx[i + 0]].Pos,
|
|
|
|
v[idx[i + 1]].Pos,
|
|
|
|
v[idx[i + 2]].Pos,
|
|
|
|
v[idx[i + 0]].TCoords,
|
|
|
|
v[idx[i + 1]].TCoords,
|
|
|
|
v[idx[i + 2]].TCoords);
|
|
|
|
|
|
|
|
calculateTangents(
|
|
|
|
v[idx[i + 1]].Normal,
|
|
|
|
v[idx[i + 1]].Tangent,
|
|
|
|
v[idx[i + 1]].Binormal,
|
|
|
|
v[idx[i + 1]].Pos,
|
|
|
|
v[idx[i + 2]].Pos,
|
|
|
|
v[idx[i + 0]].Pos,
|
|
|
|
v[idx[i + 1]].TCoords,
|
|
|
|
v[idx[i + 2]].TCoords,
|
|
|
|
v[idx[i + 0]].TCoords);
|
|
|
|
|
|
|
|
calculateTangents(
|
|
|
|
v[idx[i + 2]].Normal,
|
|
|
|
v[idx[i + 2]].Tangent,
|
|
|
|
v[idx[i + 2]].Binormal,
|
|
|
|
v[idx[i + 2]].Pos,
|
|
|
|
v[idx[i + 0]].Pos,
|
|
|
|
v[idx[i + 1]].Pos,
|
|
|
|
v[idx[i + 2]].TCoords,
|
|
|
|
v[idx[i + 0]].TCoords,
|
|
|
|
v[idx[i + 1]].TCoords);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-06 18:03:44 +01:00
|
|
|
void SkinnedMesh::calculateTangents(
|
2024-03-21 20:13:15 +01:00
|
|
|
core::vector3df &normal,
|
|
|
|
core::vector3df &tangent,
|
|
|
|
core::vector3df &binormal,
|
|
|
|
const core::vector3df &vt1, const core::vector3df &vt2, const core::vector3df &vt3, // vertices
|
|
|
|
const core::vector2df &tc1, const core::vector2df &tc2, const core::vector2df &tc3) // texture coords
|
|
|
|
{
|
|
|
|
core::vector3df v1 = vt1 - vt2;
|
|
|
|
core::vector3df v2 = vt3 - vt1;
|
|
|
|
normal = v2.crossProduct(v1);
|
|
|
|
normal.normalize();
|
|
|
|
|
|
|
|
// binormal
|
|
|
|
|
|
|
|
f32 deltaX1 = tc1.X - tc2.X;
|
|
|
|
f32 deltaX2 = tc3.X - tc1.X;
|
|
|
|
binormal = (v1 * deltaX2) - (v2 * deltaX1);
|
|
|
|
binormal.normalize();
|
|
|
|
|
|
|
|
// tangent
|
|
|
|
|
|
|
|
f32 deltaY1 = tc1.Y - tc2.Y;
|
|
|
|
f32 deltaY2 = tc3.Y - tc1.Y;
|
|
|
|
tangent = (v1 * deltaY2) - (v2 * deltaY1);
|
|
|
|
tangent.normalize();
|
|
|
|
|
|
|
|
// adjust
|
|
|
|
|
|
|
|
core::vector3df txb = tangent.crossProduct(binormal);
|
|
|
|
if (txb.dotProduct(normal) < 0.0f) {
|
|
|
|
tangent *= -1.0f;
|
|
|
|
binormal *= -1.0f;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} // end namespace scene
|
|
|
|
} // end namespace irr
|