mirror of
https://github.com/luanti-org/luanti.git
synced 2025-06-27 16:36:03 +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
|
@ -315,6 +315,9 @@ due to their space savings.
|
||||||
|
|
||||||
Bone weights should be normalized, e.g. using ["normalize all" in Blender](https://docs.blender.org/manual/en/4.2/grease_pencil/modes/weight_paint/weights_menu.html#normalize-all).
|
Bone weights should be normalized, e.g. using ["normalize all" in Blender](https://docs.blender.org/manual/en/4.2/grease_pencil/modes/weight_paint/weights_menu.html#normalize-all).
|
||||||
|
|
||||||
|
Note that nodes using matrix transforms must not be animated.
|
||||||
|
This also extends to bone overrides, which must not be applied to them.
|
||||||
|
|
||||||
You can use the [Khronos glTF validator](https://github.com/KhronosGroup/glTF-Validator)
|
You can use the [Khronos glTF validator](https://github.com/KhronosGroup/glTF-Validator)
|
||||||
to check whether a model is a valid glTF file.
|
to check whether a model is a valid glTF file.
|
||||||
|
|
||||||
|
|
|
@ -13,3 +13,9 @@ Jordach (CC BY-SA 3.0):
|
||||||
Zeg9 (CC BY-SA 3.0):
|
Zeg9 (CC BY-SA 3.0):
|
||||||
testentities_lava_flan.x
|
testentities_lava_flan.x
|
||||||
testentities_lava_flan.png
|
testentities_lava_flan.png
|
||||||
|
|
||||||
|
"Cool Guy":
|
||||||
|
|
||||||
|
hecks (refer to irr/LICENSE):
|
||||||
|
testentities_cool_guy.x
|
||||||
|
testentities_cool_guy.png
|
||||||
|
|
BIN
games/devtest/mods/testentities/models/testentities_cool_guy.png
Normal file
BIN
games/devtest/mods/testentities/models/testentities_cool_guy.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.6 KiB |
2
games/devtest/mods/testentities/models/testentities_cool_guy.x
Executable file
2
games/devtest/mods/testentities/models/testentities_cool_guy.x
Executable file
File diff suppressed because one or more lines are too long
|
@ -102,6 +102,19 @@ core.register_entity("testentities:lava_flan", {
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
core.register_entity("testentities:cool_guy", {
|
||||||
|
initial_properties = {
|
||||||
|
visual = "mesh",
|
||||||
|
mesh = "testentities_cool_guy.x",
|
||||||
|
textures = {
|
||||||
|
"testentities_cool_guy.png"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
on_activate = function(self)
|
||||||
|
self.object:set_animation({x = 0, y = 29}, 30, 0, true)
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
-- Advanced visual tests
|
-- Advanced visual tests
|
||||||
|
|
||||||
-- An entity for testing animated and yaw-modulated sprites
|
-- An entity for testing animated and yaw-modulated sprites
|
||||||
|
|
|
@ -13,8 +13,8 @@ namespace scene
|
||||||
//! Interface for an animated mesh.
|
//! Interface for an animated mesh.
|
||||||
/** There are already simple implementations of this interface available so
|
/** There are already simple implementations of this interface available so
|
||||||
you don't have to implement this interface on your own if you need to:
|
you don't have to implement this interface on your own if you need to:
|
||||||
You might want to use irr::scene::SAnimatedMesh, irr::scene::SMesh,
|
You might want to use irr::scene::SMesh, irr::scene::SMeshBuffer etc.
|
||||||
irr::scene::SMeshBuffer etc. */
|
*/
|
||||||
class IAnimatedMesh : public IMesh
|
class IAnimatedMesh : public IMesh
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -34,22 +34,8 @@ public:
|
||||||
scene node the mesh is instantiated in.*/
|
scene node the mesh is instantiated in.*/
|
||||||
virtual void setAnimationSpeed(f32 fps) = 0;
|
virtual void setAnimationSpeed(f32 fps) = 0;
|
||||||
|
|
||||||
//! Returns the IMesh interface for a frame.
|
//! Returns the type of the animated mesh. Useful for safe downcasts.
|
||||||
/** \param frame: Frame number, >= 0, <= getMaxFrameNumber()
|
E_ANIMATED_MESH_TYPE getMeshType() const = 0;
|
||||||
Linear interpolation is used if this is between two frames.
|
|
||||||
\return Returns the animated mesh for the given frame */
|
|
||||||
virtual IMesh *getMesh(f32 frame) = 0;
|
|
||||||
|
|
||||||
//! Returns the type of the animated mesh.
|
|
||||||
/** In most cases it is not necessary to use this method.
|
|
||||||
This is useful for making a safe downcast. For example,
|
|
||||||
if getMeshType() returns EAMT_MD2 it's safe to cast the
|
|
||||||
IAnimatedMesh to IAnimatedMeshMD2.
|
|
||||||
\returns Type of the mesh. */
|
|
||||||
E_ANIMATED_MESH_TYPE getMeshType() const override
|
|
||||||
{
|
|
||||||
return EAMT_UNKNOWN;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // end namespace scene
|
} // end namespace scene
|
||||||
|
|
|
@ -12,35 +12,8 @@ namespace irr
|
||||||
{
|
{
|
||||||
namespace scene
|
namespace scene
|
||||||
{
|
{
|
||||||
enum E_JOINT_UPDATE_ON_RENDER
|
|
||||||
{
|
|
||||||
//! do nothing
|
|
||||||
EJUOR_NONE = 0,
|
|
||||||
|
|
||||||
//! get joints positions from the mesh (for attached nodes, etc)
|
|
||||||
EJUOR_READ,
|
|
||||||
|
|
||||||
//! control joint positions in the mesh (eg. ragdolls, or set the animation from animateJoints() )
|
|
||||||
EJUOR_CONTROL
|
|
||||||
};
|
|
||||||
|
|
||||||
class IAnimatedMeshSceneNode;
|
class IAnimatedMeshSceneNode;
|
||||||
|
|
||||||
//! Callback interface for catching events of ended animations.
|
|
||||||
/** Implement this interface and use
|
|
||||||
IAnimatedMeshSceneNode::setAnimationEndCallback to be able to
|
|
||||||
be notified if an animation playback has ended.
|
|
||||||
**/
|
|
||||||
class IAnimationEndCallBack : public virtual IReferenceCounted
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
//! Will be called when the animation playback has ended.
|
|
||||||
/** See IAnimatedMeshSceneNode::setAnimationEndCallback for
|
|
||||||
more information.
|
|
||||||
\param node: Node of which the animation has ended. */
|
|
||||||
virtual void OnAnimationEnd(IAnimatedMeshSceneNode *node) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
//! Scene node capable of displaying an animated mesh.
|
//! Scene node capable of displaying an animated mesh.
|
||||||
class IAnimatedMeshSceneNode : public ISceneNode
|
class IAnimatedMeshSceneNode : public ISceneNode
|
||||||
{
|
{
|
||||||
|
@ -120,11 +93,10 @@ public:
|
||||||
/** When true the animations are played looped */
|
/** When true the animations are played looped */
|
||||||
virtual bool getLoopMode() const = 0;
|
virtual bool getLoopMode() const = 0;
|
||||||
|
|
||||||
//! Sets a callback interface which will be called if an animation playback has ended.
|
//! Will be called right after the joints have been animated,
|
||||||
/** Set this to 0 to disable the callback again.
|
//! but before the transforms have been propagated recursively to children.
|
||||||
Please note that this will only be called when in non looped
|
virtual void setOnAnimateCallback(
|
||||||
mode, see IAnimatedMeshSceneNode::setLoopMode(). */
|
const std::function<void(f32 dtime)> &cb) = 0;
|
||||||
virtual void setAnimationEndCallback(IAnimationEndCallBack *callback = 0) = 0;
|
|
||||||
|
|
||||||
//! Sets if the scene node should not copy the materials of the mesh but use them in a read only style.
|
//! Sets if the scene node should not copy the materials of the mesh but use them in a read only style.
|
||||||
/** In this way it is possible to change the materials a mesh
|
/** In this way it is possible to change the materials a mesh
|
||||||
|
@ -139,20 +111,15 @@ public:
|
||||||
virtual void setMesh(IAnimatedMesh *mesh) = 0;
|
virtual void setMesh(IAnimatedMesh *mesh) = 0;
|
||||||
|
|
||||||
//! Returns the current mesh
|
//! Returns the current mesh
|
||||||
virtual IAnimatedMesh *getMesh(void) = 0;
|
virtual IAnimatedMesh *getMesh() = 0;
|
||||||
|
|
||||||
//! Set how the joints should be updated on render
|
|
||||||
virtual void setJointMode(E_JOINT_UPDATE_ON_RENDER mode) = 0;
|
|
||||||
|
|
||||||
//! Sets the transition time in seconds
|
//! Sets the transition time in seconds
|
||||||
/** Note: This needs to enable joints, and setJointmode set to
|
/** Note: You must call animateJoints(), or the mesh will not animate. */
|
||||||
EJUOR_CONTROL. You must call animateJoints(), or the mesh will
|
|
||||||
not animate. */
|
|
||||||
virtual void setTransitionTime(f32 Time) = 0;
|
virtual void setTransitionTime(f32 Time) = 0;
|
||||||
|
|
||||||
//! animates the joints in the mesh based on the current frame.
|
//! animates the joints in the mesh based on the current frame.
|
||||||
/** Also takes in to account transitions. */
|
/** Also takes in to account transitions. */
|
||||||
virtual void animateJoints(bool CalculateAbsolutePositions = true) = 0;
|
virtual void animateJoints() = 0;
|
||||||
|
|
||||||
//! render mesh ignoring its transformation.
|
//! render mesh ignoring its transformation.
|
||||||
/** Culling is unaffected. */
|
/** Culling is unaffected. */
|
||||||
|
|
|
@ -11,85 +11,41 @@ namespace irr
|
||||||
namespace scene
|
namespace scene
|
||||||
{
|
{
|
||||||
|
|
||||||
//! Enumeration for different bone animation modes
|
|
||||||
enum E_BONE_ANIMATION_MODE
|
|
||||||
{
|
|
||||||
//! The bone is usually animated, unless it's parent is not animated
|
|
||||||
EBAM_AUTOMATIC = 0,
|
|
||||||
|
|
||||||
//! The bone is animated by the skin, if it's parent is not animated then animation will resume from this bone onward
|
|
||||||
EBAM_ANIMATED,
|
|
||||||
|
|
||||||
//! The bone is not animated by the skin
|
|
||||||
EBAM_UNANIMATED,
|
|
||||||
|
|
||||||
//! Not an animation mode, just here to count the available modes
|
|
||||||
EBAM_COUNT
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
enum E_BONE_SKINNING_SPACE
|
|
||||||
{
|
|
||||||
//! local skinning, standard
|
|
||||||
EBSS_LOCAL = 0,
|
|
||||||
|
|
||||||
//! global skinning
|
|
||||||
EBSS_GLOBAL,
|
|
||||||
|
|
||||||
EBSS_COUNT
|
|
||||||
};
|
|
||||||
|
|
||||||
//! Names for bone animation modes
|
|
||||||
const c8 *const BoneAnimationModeNames[] = {
|
|
||||||
"automatic",
|
|
||||||
"animated",
|
|
||||||
"unanimated",
|
|
||||||
0,
|
|
||||||
};
|
|
||||||
|
|
||||||
//! Interface for bones used for skeletal animation.
|
//! Interface for bones used for skeletal animation.
|
||||||
/** Used with SkinnedMesh and IAnimatedMeshSceneNode. */
|
/** Used with SkinnedMesh and IAnimatedMeshSceneNode. */
|
||||||
class IBoneSceneNode : public ISceneNode
|
class IBoneSceneNode : public ISceneNode
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
IBoneSceneNode(ISceneNode *parent, ISceneManager *mgr, s32 id = -1) :
|
IBoneSceneNode(ISceneNode *parent, ISceneManager *mgr,
|
||||||
ISceneNode(parent, mgr, id), positionHint(-1), scaleHint(-1), rotationHint(-1) {}
|
s32 id = -1, u32 boneIndex = 0,
|
||||||
|
const std::optional<std::string> &boneName = std::nullopt)
|
||||||
|
:
|
||||||
|
ISceneNode(parent, mgr, id),
|
||||||
|
BoneIndex(boneIndex)
|
||||||
|
{
|
||||||
|
setName(boneName);
|
||||||
|
}
|
||||||
|
|
||||||
//! Get the index of the bone
|
//! Returns the index of the bone
|
||||||
virtual u32 getBoneIndex() const = 0;
|
u32 getBoneIndex() const
|
||||||
|
{
|
||||||
|
return BoneIndex;
|
||||||
|
}
|
||||||
|
|
||||||
//! Sets the animation mode of the bone.
|
//! returns the axis aligned bounding box of this node
|
||||||
/** \return True if successful. (Unused) */
|
const core::aabbox3d<f32> &getBoundingBox() const override
|
||||||
virtual bool setAnimationMode(E_BONE_ANIMATION_MODE mode) = 0;
|
{
|
||||||
|
return Box;
|
||||||
|
}
|
||||||
|
|
||||||
//! Gets the current animation mode of the bone
|
const u32 BoneIndex;
|
||||||
virtual E_BONE_ANIMATION_MODE getAnimationMode() const = 0;
|
|
||||||
|
|
||||||
//! Get the axis aligned bounding box of this node
|
// Bogus box; bone scene nodes are not rendered anyways.
|
||||||
const core::aabbox3d<f32> &getBoundingBox() const override = 0;
|
static constexpr core::aabbox3d<f32> Box = {{0, 0, 0}};
|
||||||
|
|
||||||
//! Returns the relative transformation of the scene node.
|
|
||||||
// virtual core::matrix4 getRelativeTransformation() const = 0;
|
|
||||||
|
|
||||||
//! The animation method.
|
|
||||||
void OnAnimate(u32 timeMs) override = 0;
|
|
||||||
|
|
||||||
//! The render method.
|
//! The render method.
|
||||||
/** Does nothing as bones are not visible. */
|
/** Does nothing as bones are not visible. */
|
||||||
void render() override {}
|
void render() override {}
|
||||||
|
|
||||||
//! How the relative transformation of the bone is used
|
|
||||||
virtual void setSkinningSpace(E_BONE_SKINNING_SPACE space) = 0;
|
|
||||||
|
|
||||||
//! How the relative transformation of the bone is used
|
|
||||||
virtual E_BONE_SKINNING_SPACE getSkinningSpace() const = 0;
|
|
||||||
|
|
||||||
//! Updates the absolute position based on the relative and the parents position
|
|
||||||
virtual void updateAbsolutePositionOfAllChildren() = 0;
|
|
||||||
|
|
||||||
s32 positionHint;
|
|
||||||
s32 scaleHint;
|
|
||||||
s32 rotationHint;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // end namespace scene
|
} // end namespace scene
|
||||||
|
|
|
@ -20,38 +20,6 @@ enum E_ANIMATED_MESH_TYPE
|
||||||
//! Unknown animated mesh type.
|
//! Unknown animated mesh type.
|
||||||
EAMT_UNKNOWN = 0,
|
EAMT_UNKNOWN = 0,
|
||||||
|
|
||||||
//! Quake 2 MD2 model file
|
|
||||||
EAMT_MD2,
|
|
||||||
|
|
||||||
//! Quake 3 MD3 model file
|
|
||||||
EAMT_MD3,
|
|
||||||
|
|
||||||
//! Maya .obj static model
|
|
||||||
EAMT_OBJ,
|
|
||||||
|
|
||||||
//! Quake 3 .bsp static Map
|
|
||||||
EAMT_BSP,
|
|
||||||
|
|
||||||
//! 3D Studio .3ds file
|
|
||||||
EAMT_3DS,
|
|
||||||
|
|
||||||
//! My3D Mesh, the file format by Zhuck Dimitry
|
|
||||||
EAMT_MY3D,
|
|
||||||
|
|
||||||
//! Pulsar LMTools .lmts file. This Irrlicht loader was written by Jonas Petersen
|
|
||||||
EAMT_LMTS,
|
|
||||||
|
|
||||||
//! Cartography Shop .csm file. This loader was created by Saurav Mohapatra.
|
|
||||||
EAMT_CSM,
|
|
||||||
|
|
||||||
//! .oct file for Paul Nette's FSRad or from Murphy McCauley's Blender .oct exporter.
|
|
||||||
/** The oct file format contains 3D geometry and lightmaps and
|
|
||||||
can be loaded directly by Irrlicht */
|
|
||||||
EAMT_OCT,
|
|
||||||
|
|
||||||
//! Halflife MDL model file
|
|
||||||
EAMT_MDL_HALFLIFE,
|
|
||||||
|
|
||||||
//! generic skinned mesh
|
//! generic skinned mesh
|
||||||
EAMT_SKINNED,
|
EAMT_SKINNED,
|
||||||
|
|
||||||
|
@ -119,9 +87,7 @@ public:
|
||||||
virtual void setDirty(E_BUFFER_TYPE buffer = EBT_VERTEX_AND_INDEX) = 0;
|
virtual void setDirty(E_BUFFER_TYPE buffer = EBT_VERTEX_AND_INDEX) = 0;
|
||||||
|
|
||||||
//! Returns the type of the meshes.
|
//! Returns the type of the meshes.
|
||||||
/** This is useful for making a safe downcast. For example,
|
/** This is useful for making a safe downcast.
|
||||||
if getMeshType() returns EAMT_MD2 it's safe to cast the
|
|
||||||
IMesh to IAnimatedMeshMD2.
|
|
||||||
Note: It's no longer just about animated meshes, that name has just historical reasons.
|
Note: It's no longer just about animated meshes, that name has just historical reasons.
|
||||||
\returns Type of the mesh */
|
\returns Type of the mesh */
|
||||||
virtual E_ANIMATED_MESH_TYPE getMeshType() const
|
virtual E_ANIMATED_MESH_TYPE getMeshType() const
|
||||||
|
|
|
@ -66,26 +66,6 @@ public:
|
||||||
IReferenceCounted::drop() for more information. */
|
IReferenceCounted::drop() for more information. */
|
||||||
virtual SMesh *createMeshCopy(IMesh *mesh) const = 0;
|
virtual SMesh *createMeshCopy(IMesh *mesh) const = 0;
|
||||||
|
|
||||||
//! Get amount of polygons in mesh.
|
|
||||||
/** \param mesh Input mesh
|
|
||||||
\return Number of polygons in mesh. */
|
|
||||||
virtual s32 getPolyCount(IMesh *mesh) const = 0;
|
|
||||||
|
|
||||||
//! Get amount of polygons in mesh.
|
|
||||||
/** \param mesh Input mesh
|
|
||||||
\return Number of polygons in mesh. */
|
|
||||||
virtual s32 getPolyCount(IAnimatedMesh *mesh) const = 0;
|
|
||||||
|
|
||||||
//! Create a new AnimatedMesh and adds the mesh to it
|
|
||||||
/** \param mesh Input mesh
|
|
||||||
\param type The type of the animated mesh to create.
|
|
||||||
\return Newly created animated mesh with mesh as its only
|
|
||||||
content. When you don't need the animated mesh anymore, you
|
|
||||||
should call IAnimatedMesh::drop(). See
|
|
||||||
IReferenceCounted::drop() for more information. */
|
|
||||||
virtual IAnimatedMesh *createAnimatedMesh(IMesh *mesh,
|
|
||||||
scene::E_ANIMATED_MESH_TYPE type = scene::EAMT_UNKNOWN) const = 0;
|
|
||||||
|
|
||||||
//! Apply a manipulator on the Meshbuffer
|
//! Apply a manipulator on the Meshbuffer
|
||||||
/** \param func A functor defining the mesh manipulation.
|
/** \param func A functor defining the mesh manipulation.
|
||||||
\param buffer The Meshbuffer to apply the manipulator to.
|
\param buffer The Meshbuffer to apply the manipulator to.
|
||||||
|
|
|
@ -32,7 +32,7 @@ public:
|
||||||
|
|
||||||
//! Get the currently defined mesh for display.
|
//! Get the currently defined mesh for display.
|
||||||
/** \return Pointer to mesh which is displayed by this node. */
|
/** \return Pointer to mesh which is displayed by this node. */
|
||||||
virtual IMesh *getMesh(void) = 0;
|
virtual IMesh *getMesh() = 0;
|
||||||
|
|
||||||
//! Sets if the scene node should not copy the materials of the mesh but use them directly.
|
//! Sets if the scene node should not copy the materials of the mesh but use them directly.
|
||||||
/** In this way it is possible to change the materials of a mesh
|
/** In this way it is possible to change the materials of a mesh
|
||||||
|
|
|
@ -94,16 +94,12 @@ public:
|
||||||
\param timeMs Current time in milliseconds. */
|
\param timeMs Current time in milliseconds. */
|
||||||
virtual void OnAnimate(u32 timeMs)
|
virtual void OnAnimate(u32 timeMs)
|
||||||
{
|
{
|
||||||
if (IsVisible) {
|
if (!IsVisible && Children.empty())
|
||||||
// update absolute position
|
return;
|
||||||
|
|
||||||
updateAbsolutePosition();
|
updateAbsolutePosition();
|
||||||
|
for (auto *child : Children)
|
||||||
// perform the post render process on all children
|
child->OnAnimate(timeMs);
|
||||||
|
|
||||||
ISceneNodeList::iterator it = Children.begin();
|
|
||||||
for (; it != Children.end(); ++it)
|
|
||||||
(*it)->OnAnimate(timeMs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//! Renders the node.
|
//! Renders the node.
|
||||||
|
|
|
@ -1,167 +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
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
#include "IAnimatedMesh.h"
|
|
||||||
#include "IMesh.h"
|
|
||||||
#include "aabbox3d.h"
|
|
||||||
|
|
||||||
namespace irr
|
|
||||||
{
|
|
||||||
namespace scene
|
|
||||||
{
|
|
||||||
|
|
||||||
//! Simple implementation of the IAnimatedMesh interface.
|
|
||||||
struct SAnimatedMesh final : public IAnimatedMesh
|
|
||||||
{
|
|
||||||
//! constructor
|
|
||||||
SAnimatedMesh(scene::IMesh *mesh = 0, scene::E_ANIMATED_MESH_TYPE type = scene::EAMT_UNKNOWN) :
|
|
||||||
IAnimatedMesh(), FramesPerSecond(25.f), Type(type)
|
|
||||||
{
|
|
||||||
addMesh(mesh);
|
|
||||||
recalculateBoundingBox();
|
|
||||||
}
|
|
||||||
|
|
||||||
//! destructor
|
|
||||||
virtual ~SAnimatedMesh()
|
|
||||||
{
|
|
||||||
// drop meshes
|
|
||||||
for (auto *mesh : Meshes)
|
|
||||||
mesh->drop();
|
|
||||||
}
|
|
||||||
|
|
||||||
f32 getMaxFrameNumber() const override
|
|
||||||
{
|
|
||||||
return static_cast<f32>(Meshes.size() - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
//! 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. */
|
|
||||||
f32 getAnimationSpeed() const override
|
|
||||||
{
|
|
||||||
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.*/
|
|
||||||
void setAnimationSpeed(f32 fps) override
|
|
||||||
{
|
|
||||||
FramesPerSecond = fps;
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Returns the IMesh interface for a frame.
|
|
||||||
/** \param frame: Frame number as zero based index.
|
|
||||||
\return The animated mesh based for the given frame */
|
|
||||||
IMesh *getMesh(f32 frame) override
|
|
||||||
{
|
|
||||||
if (Meshes.empty())
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
return Meshes[static_cast<s32>(frame)];
|
|
||||||
}
|
|
||||||
|
|
||||||
//! adds a Mesh
|
|
||||||
void addMesh(IMesh *mesh)
|
|
||||||
{
|
|
||||||
if (mesh) {
|
|
||||||
mesh->grab();
|
|
||||||
Meshes.push_back(mesh);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Returns an axis aligned bounding box of the mesh.
|
|
||||||
/** \return A bounding box of this mesh is returned. */
|
|
||||||
const core::aabbox3d<f32> &getBoundingBox() const override
|
|
||||||
{
|
|
||||||
return Box;
|
|
||||||
}
|
|
||||||
|
|
||||||
//! set user axis aligned bounding box
|
|
||||||
void setBoundingBox(const core::aabbox3df &box) override
|
|
||||||
{
|
|
||||||
Box = box;
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Recalculates the bounding box.
|
|
||||||
void recalculateBoundingBox()
|
|
||||||
{
|
|
||||||
Box.reset(0, 0, 0);
|
|
||||||
|
|
||||||
if (Meshes.empty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
Box = Meshes[0]->getBoundingBox();
|
|
||||||
|
|
||||||
for (u32 i = 1; i < Meshes.size(); ++i)
|
|
||||||
Box.addInternalBox(Meshes[i]->getBoundingBox());
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Returns the type of the animated mesh.
|
|
||||||
E_ANIMATED_MESH_TYPE getMeshType() const override
|
|
||||||
{
|
|
||||||
return Type;
|
|
||||||
}
|
|
||||||
|
|
||||||
//! returns amount of mesh buffers.
|
|
||||||
u32 getMeshBufferCount() const override
|
|
||||||
{
|
|
||||||
if (Meshes.empty())
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return Meshes[0]->getMeshBufferCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
//! returns pointer to a mesh buffer
|
|
||||||
IMeshBuffer *getMeshBuffer(u32 nr) const override
|
|
||||||
{
|
|
||||||
if (Meshes.empty())
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return Meshes[0]->getMeshBuffer(nr);
|
|
||||||
}
|
|
||||||
|
|
||||||
//! Returns pointer to a mesh buffer which fits a material
|
|
||||||
/** \param material: material to search for
|
|
||||||
\return Returns the pointer to the mesh buffer or
|
|
||||||
NULL if there is no such mesh buffer. */
|
|
||||||
IMeshBuffer *getMeshBuffer(const video::SMaterial &material) const override
|
|
||||||
{
|
|
||||||
if (Meshes.empty())
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return Meshes[0]->getMeshBuffer(material);
|
|
||||||
}
|
|
||||||
|
|
||||||
//! set the hardware mapping hint, for driver
|
|
||||||
void setHardwareMappingHint(E_HARDWARE_MAPPING newMappingHint, E_BUFFER_TYPE buffer = EBT_VERTEX_AND_INDEX) override
|
|
||||||
{
|
|
||||||
for (u32 i = 0; i < Meshes.size(); ++i)
|
|
||||||
Meshes[i]->setHardwareMappingHint(newMappingHint, buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
//! flags the meshbuffer as changed, reloads hardware buffers
|
|
||||||
void setDirty(E_BUFFER_TYPE buffer = EBT_VERTEX_AND_INDEX) override
|
|
||||||
{
|
|
||||||
for (u32 i = 0; i < Meshes.size(); ++i)
|
|
||||||
Meshes[i]->setDirty(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
//! All meshes defining the animated mesh
|
|
||||||
std::vector<IMesh *> Meshes;
|
|
||||||
|
|
||||||
//! The bounding box of this mesh
|
|
||||||
core::aabbox3d<f32> Box{{0.0f, 0.0f, 0.0f}};
|
|
||||||
|
|
||||||
//! Default animation speed of this mesh.
|
|
||||||
f32 FramesPerSecond;
|
|
||||||
|
|
||||||
//! The type of the mesh.
|
|
||||||
E_ANIMATED_MESH_TYPE Type;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // end namespace scene
|
|
||||||
} // end namespace irr
|
|
|
@ -5,7 +5,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "IMesh.h"
|
#include "IAnimatedMesh.h"
|
||||||
#include "IMeshBuffer.h"
|
#include "IMeshBuffer.h"
|
||||||
#include "aabbox3d.h"
|
#include "aabbox3d.h"
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ namespace irr
|
||||||
namespace scene
|
namespace scene
|
||||||
{
|
{
|
||||||
//! Simple implementation of the IMesh interface.
|
//! Simple implementation of the IMesh interface.
|
||||||
struct SMesh final : public IMesh
|
struct SMesh final : public IAnimatedMesh
|
||||||
{
|
{
|
||||||
//! constructor
|
//! constructor
|
||||||
SMesh() {}
|
SMesh() {}
|
||||||
|
@ -134,6 +134,15 @@ struct SMesh final : public IMesh
|
||||||
|
|
||||||
//! The bounding box of this mesh
|
//! The bounding box of this mesh
|
||||||
core::aabbox3d<f32> BoundingBox{{0, 0, 0}};
|
core::aabbox3d<f32> BoundingBox{{0, 0, 0}};
|
||||||
|
|
||||||
|
// Implement animated mesh interface as a static mesh.
|
||||||
|
// Slightly hacky: Eventually should be consolidated with SSkinnedMesh,
|
||||||
|
// with all the animation-related parts behind an optional.
|
||||||
|
|
||||||
|
virtual f32 getMaxFrameNumber() const override { return 0.0f; }
|
||||||
|
virtual f32 getAnimationSpeed() const override { return 0.0f; }
|
||||||
|
virtual void setAnimationSpeed(f32 fps) override {}
|
||||||
|
E_ANIMATED_MESH_TYPE getMeshType() const override { return EAMT_STATIC; }
|
||||||
};
|
};
|
||||||
|
|
||||||
} // end namespace scene
|
} // end namespace scene
|
||||||
|
|
|
@ -8,11 +8,18 @@
|
||||||
#include "ISceneManager.h"
|
#include "ISceneManager.h"
|
||||||
#include "SMeshBuffer.h"
|
#include "SMeshBuffer.h"
|
||||||
#include "SSkinMeshBuffer.h"
|
#include "SSkinMeshBuffer.h"
|
||||||
|
#include "aabbox3d.h"
|
||||||
|
#include "irrMath.h"
|
||||||
|
#include "irrTypes.h"
|
||||||
|
#include "matrix4.h"
|
||||||
#include "quaternion.h"
|
#include "quaternion.h"
|
||||||
#include "vector3d.h"
|
#include "vector3d.h"
|
||||||
|
#include "Transform.h"
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <variant>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace irr
|
namespace irr
|
||||||
{
|
{
|
||||||
|
@ -37,9 +44,8 @@ public:
|
||||||
//! constructor
|
//! constructor
|
||||||
SkinnedMesh(SourceFormat src_format) :
|
SkinnedMesh(SourceFormat src_format) :
|
||||||
EndFrame(0.f), FramesPerSecond(25.f),
|
EndFrame(0.f), FramesPerSecond(25.f),
|
||||||
LastAnimatedFrame(-1), SkinnedLastFrame(false),
|
|
||||||
HasAnimation(false), PreparedForSkinning(false),
|
HasAnimation(false), PreparedForSkinning(false),
|
||||||
AnimateNormals(true), HardwareSkinning(false),
|
AnimateNormals(true),
|
||||||
SrcFormat(src_format)
|
SrcFormat(src_format)
|
||||||
{
|
{
|
||||||
SkinningBuffers = &LocalBuffers;
|
SkinningBuffers = &LocalBuffers;
|
||||||
|
@ -64,14 +70,12 @@ public:
|
||||||
The actual speed is set in the scene node the mesh is instantiated in.*/
|
The actual speed is set in the scene node the mesh is instantiated in.*/
|
||||||
void setAnimationSpeed(f32 fps) override;
|
void setAnimationSpeed(f32 fps) override;
|
||||||
|
|
||||||
//! returns the animated mesh for the given frame
|
//! Turns the given array of local matrices into an array of global matrices
|
||||||
IMesh *getMesh(f32) override;
|
//! by multiplying with respective parent matrices.
|
||||||
|
void calculateGlobalMatrices(std::vector<core::matrix4> &matrices) const;
|
||||||
|
|
||||||
//! Animates joints based on frame input
|
//! Performs a software skin on this mesh based on the given joint matrices
|
||||||
void animateMesh(f32 frame);
|
void skinMesh(const std::vector<core::matrix4> &animated_transforms);
|
||||||
|
|
||||||
//! Performs a software skin on this mesh based of joint positions
|
|
||||||
void skinMesh();
|
|
||||||
|
|
||||||
//! returns amount of mesh buffers.
|
//! returns amount of mesh buffers.
|
||||||
u32 getMeshBufferCount() const override;
|
u32 getMeshBufferCount() const override;
|
||||||
|
@ -89,14 +93,15 @@ public:
|
||||||
|
|
||||||
void setTextureSlot(u32 meshbufNr, u32 textureSlot);
|
void setTextureSlot(u32 meshbufNr, u32 textureSlot);
|
||||||
|
|
||||||
//! returns an axis aligned bounding box
|
//! Returns bounding box of the mesh *in static pose*.
|
||||||
const core::aabbox3d<f32> &getBoundingBox() const override {
|
const core::aabbox3d<f32> &getBoundingBox() const override {
|
||||||
return BoundingBox;
|
// TODO ideally we shouldn't be forced to implement this
|
||||||
|
return StaticPoseBox;
|
||||||
}
|
}
|
||||||
|
|
||||||
//! set user axis aligned bounding box
|
//! Set bounding box of the mesh *in static pose*.
|
||||||
void setBoundingBox(const core::aabbox3df &box) override {
|
void setBoundingBox(const core::aabbox3df &box) override {
|
||||||
BoundingBox = box;
|
StaticPoseBox = box;
|
||||||
}
|
}
|
||||||
|
|
||||||
//! set the hardware mapping hint, for driver
|
//! set the hardware mapping hint, for driver
|
||||||
|
@ -140,28 +145,15 @@ public:
|
||||||
return !HasAnimation;
|
return !HasAnimation;
|
||||||
}
|
}
|
||||||
|
|
||||||
//! Allows to enable hardware skinning.
|
|
||||||
/* This feature is not implemented in Irrlicht yet */
|
|
||||||
bool setHardwareSkinning(bool on);
|
|
||||||
|
|
||||||
//! Refreshes vertex data cached in joints such as positions and normals
|
//! Refreshes vertex data cached in joints such as positions and normals
|
||||||
void refreshJointCache();
|
void refreshJointCache();
|
||||||
|
|
||||||
//! Moves the mesh into static position.
|
//! Moves the mesh into static position.
|
||||||
void resetAnimation();
|
void resetAnimation();
|
||||||
|
|
||||||
void updateBoundingBox();
|
|
||||||
|
|
||||||
//! Recovers the joints from the mesh
|
|
||||||
void recoverJointsFromMesh(std::vector<IBoneSceneNode *> &jointChildSceneNodes);
|
|
||||||
|
|
||||||
//! Transfers the joint data to the mesh
|
|
||||||
void transferJointsToMesh(const std::vector<IBoneSceneNode *> &jointChildSceneNodes);
|
|
||||||
|
|
||||||
//! Creates an array of joints from this mesh as children of node
|
//! Creates an array of joints from this mesh as children of node
|
||||||
void addJoints(std::vector<IBoneSceneNode *> &jointChildSceneNodes,
|
std::vector<IBoneSceneNode *> addJoints(
|
||||||
IAnimatedMeshSceneNode *node,
|
IAnimatedMeshSceneNode *node, ISceneManager *smgr);
|
||||||
ISceneManager *smgr);
|
|
||||||
|
|
||||||
//! A vertex weight
|
//! A vertex weight
|
||||||
struct SWeight
|
struct SWeight
|
||||||
|
@ -236,7 +228,7 @@ public:
|
||||||
|
|
||||||
static core::quaternion interpolateValue(core::quaternion from, core::quaternion to, f32 time) {
|
static core::quaternion interpolateValue(core::quaternion from, core::quaternion to, f32 time) {
|
||||||
core::quaternion result;
|
core::quaternion result;
|
||||||
result.slerp(from, to, time, 0.001f);
|
result.slerp(from, to, time);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -288,15 +280,14 @@ public:
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateTransform(f32 frame,
|
void updateTransform(f32 frame, core::Transform &transform) const
|
||||||
core::vector3df &t, core::quaternion &r, core::vector3df &s) const
|
|
||||||
{
|
{
|
||||||
if (auto pos = position.get(frame))
|
if (auto pos = position.get(frame))
|
||||||
t = *pos;
|
transform.translation = *pos;
|
||||||
if (auto rot = rotation.get(frame))
|
if (auto rot = rotation.get(frame))
|
||||||
r = *rot;
|
transform.rotation = *rot;
|
||||||
if (auto scl = scale.get(frame))
|
if (auto scl = scale.get(frame))
|
||||||
s = *scl;
|
transform.scale = *scl;
|
||||||
}
|
}
|
||||||
|
|
||||||
void cleanup() {
|
void cleanup() {
|
||||||
|
@ -309,16 +300,34 @@ public:
|
||||||
//! Joints
|
//! Joints
|
||||||
struct SJoint
|
struct SJoint
|
||||||
{
|
{
|
||||||
SJoint() : GlobalSkinningSpace(false) {}
|
SJoint() {}
|
||||||
|
|
||||||
//! The name of this joint
|
//! The name of this joint
|
||||||
std::optional<std::string> Name;
|
std::optional<std::string> Name;
|
||||||
|
|
||||||
//! Local matrix of this joint
|
//! Local transformation to be set by loaders. Mutated by animation.
|
||||||
core::matrix4 LocalMatrix;
|
using VariantTransform = std::variant<core::Transform, core::matrix4>;
|
||||||
|
VariantTransform transform{core::Transform{}};
|
||||||
|
|
||||||
|
VariantTransform animate(f32 frame) const {
|
||||||
|
if (keys.empty())
|
||||||
|
return transform;
|
||||||
|
|
||||||
|
if (std::holds_alternative<core::matrix4>(transform)) {
|
||||||
|
// .x lets animations override matrix transforms entirely,
|
||||||
|
// which is what we implement here.
|
||||||
|
// .gltf does not allow animation of nodes using matrix transforms.
|
||||||
|
// Note that a decomposition into a TRS transform need not exist!
|
||||||
|
core::Transform trs;
|
||||||
|
keys.updateTransform(frame, trs);
|
||||||
|
return {trs};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto trs = std::get<core::Transform>(transform);
|
||||||
|
keys.updateTransform(frame, trs);
|
||||||
|
return {trs};
|
||||||
|
}
|
||||||
|
|
||||||
//! List of child joints
|
|
||||||
std::vector<SJoint *> Children;
|
|
||||||
|
|
||||||
//! List of attached meshes
|
//! List of attached meshes
|
||||||
std::vector<u32> AttachedMeshes;
|
std::vector<u32> AttachedMeshes;
|
||||||
|
@ -329,42 +338,49 @@ public:
|
||||||
//! Skin weights
|
//! Skin weights
|
||||||
std::vector<SWeight> Weights;
|
std::vector<SWeight> Weights;
|
||||||
|
|
||||||
|
//! Bounding box of all affected vertices, in local space
|
||||||
|
core::aabbox3df LocalBoundingBox{{0, 0, 0}};
|
||||||
|
|
||||||
//! Unnecessary for loaders, will be overwritten on finalize
|
//! Unnecessary for loaders, will be overwritten on finalize
|
||||||
core::matrix4 GlobalMatrix; // loaders may still choose to set this (temporarily) to calculate absolute vertex data.
|
core::matrix4 GlobalMatrix; // loaders may still choose to set this (temporarily) to calculate absolute vertex data.
|
||||||
core::matrix4 GlobalAnimatedMatrix;
|
|
||||||
core::matrix4 LocalAnimatedMatrix;
|
|
||||||
|
|
||||||
//! These should be set by loaders.
|
|
||||||
core::vector3df Animatedposition;
|
|
||||||
core::vector3df Animatedscale;
|
|
||||||
core::quaternion Animatedrotation;
|
|
||||||
|
|
||||||
// The .x and .gltf formats pre-calculate this
|
// The .x and .gltf formats pre-calculate this
|
||||||
std::optional<core::matrix4> GlobalInversedMatrix;
|
std::optional<core::matrix4> GlobalInversedMatrix;
|
||||||
private:
|
|
||||||
//! Internal members used by SkinnedMesh
|
|
||||||
friend class SkinnedMesh;
|
|
||||||
|
|
||||||
bool GlobalSkinningSpace;
|
void setParent(SJoint *parent) {
|
||||||
|
ParentJointID = parent ? parent->JointID : std::optional<u16>{};
|
||||||
|
}
|
||||||
|
|
||||||
|
u16 JointID; // TODO refactor away: pointers -> IDs (problem: .x loader abuses SJoint)
|
||||||
|
std::optional<u16> ParentJointID;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//! Animates joints based on frame input
|
||||||
|
std::vector<SJoint::VariantTransform> animateMesh(f32 frame);
|
||||||
|
|
||||||
|
//! Calculates a bounding box given an animation in the form of global joint transforms.
|
||||||
|
core::aabbox3df calculateBoundingBox(
|
||||||
|
const std::vector<core::matrix4> &global_transforms);
|
||||||
|
|
||||||
|
void recalculateBaseBoundingBoxes();
|
||||||
|
|
||||||
const std::vector<SJoint *> &getAllJoints() const {
|
const std::vector<SJoint *> &getAllJoints() const {
|
||||||
return AllJoints;
|
return AllJoints;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void checkForAnimation();
|
bool checkForAnimation() const;
|
||||||
|
|
||||||
|
void topoSortJoints();
|
||||||
|
|
||||||
|
void prepareForSkinning();
|
||||||
|
|
||||||
|
void calculateStaticBoundingBox();
|
||||||
|
void calculateJointBoundingBoxes();
|
||||||
|
void calculateBufferBoundingBoxes();
|
||||||
|
|
||||||
void normalizeWeights();
|
void normalizeWeights();
|
||||||
|
|
||||||
void buildAllLocalAnimatedMatrices();
|
|
||||||
|
|
||||||
void buildAllGlobalAnimatedMatrices(SJoint *Joint = 0, SJoint *ParentJoint = 0);
|
|
||||||
|
|
||||||
void calculateGlobalMatrices(SJoint *Joint, SJoint *ParentJoint);
|
|
||||||
|
|
||||||
void skinJoint(SJoint *Joint, SJoint *ParentJoint);
|
|
||||||
|
|
||||||
void calculateTangents(core::vector3df &normal,
|
void calculateTangents(core::vector3df &normal,
|
||||||
core::vector3df &tangent, core::vector3df &binormal,
|
core::vector3df &tangent, core::vector3df &binormal,
|
||||||
const core::vector3df &vt1, const core::vector3df &vt2, const core::vector3df &vt3,
|
const core::vector3df &vt1, const core::vector3df &vt2, const core::vector3df &vt3,
|
||||||
|
@ -376,25 +392,25 @@ protected:
|
||||||
//! Mapping from meshbuffer number to bindable texture slot
|
//! Mapping from meshbuffer number to bindable texture slot
|
||||||
std::vector<u32> TextureSlots;
|
std::vector<u32> TextureSlots;
|
||||||
|
|
||||||
|
//! Joints, topologically sorted (parents come before their children).
|
||||||
std::vector<SJoint *> AllJoints;
|
std::vector<SJoint *> AllJoints;
|
||||||
std::vector<SJoint *> RootJoints;
|
|
||||||
|
|
||||||
// bool can't be used here because std::vector<bool>
|
// bool can't be used here because std::vector<bool>
|
||||||
// doesn't allow taking a reference to individual elements.
|
// doesn't allow taking a reference to individual elements.
|
||||||
std::vector<std::vector<char>> Vertices_Moved;
|
std::vector<std::vector<char>> Vertices_Moved;
|
||||||
|
|
||||||
core::aabbox3d<f32> BoundingBox{{0, 0, 0}};
|
//! Bounding box of just the static parts of the mesh
|
||||||
|
core::aabbox3df StaticPartsBox{{0, 0, 0}};
|
||||||
|
|
||||||
|
//! Bounding box of the mesh in static pose
|
||||||
|
core::aabbox3df StaticPoseBox{{0, 0, 0}};
|
||||||
|
|
||||||
f32 EndFrame;
|
f32 EndFrame;
|
||||||
f32 FramesPerSecond;
|
f32 FramesPerSecond;
|
||||||
|
|
||||||
f32 LastAnimatedFrame;
|
|
||||||
bool SkinnedLastFrame;
|
|
||||||
|
|
||||||
bool HasAnimation;
|
bool HasAnimation;
|
||||||
bool PreparedForSkinning;
|
bool PreparedForSkinning;
|
||||||
bool AnimateNormals;
|
bool AnimateNormals;
|
||||||
bool HardwareSkinning;
|
|
||||||
|
|
||||||
SourceFormat SrcFormat;
|
SourceFormat SrcFormat;
|
||||||
};
|
};
|
||||||
|
|
42
irr/include/Transform.h
Normal file
42
irr/include/Transform.h
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "irrMath.h"
|
||||||
|
#include <matrix4.h>
|
||||||
|
#include <vector3d.h>
|
||||||
|
#include <quaternion.h>
|
||||||
|
|
||||||
|
namespace irr
|
||||||
|
{
|
||||||
|
namespace core
|
||||||
|
{
|
||||||
|
|
||||||
|
struct Transform {
|
||||||
|
vector3df translation;
|
||||||
|
quaternion rotation;
|
||||||
|
vector3df scale{1};
|
||||||
|
|
||||||
|
Transform interpolate(Transform to, f32 time) const
|
||||||
|
{
|
||||||
|
core::quaternion interpolated_rotation;
|
||||||
|
interpolated_rotation.slerp(rotation, to.rotation, time);
|
||||||
|
return {
|
||||||
|
to.translation.getInterpolated(translation, time),
|
||||||
|
interpolated_rotation,
|
||||||
|
to.scale.getInterpolated(scale, time),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
matrix4 buildMatrix() const
|
||||||
|
{
|
||||||
|
matrix4 T;
|
||||||
|
T.setTranslation(translation);
|
||||||
|
matrix4 R;
|
||||||
|
rotation.getMatrix_transposed(R);
|
||||||
|
matrix4 S;
|
||||||
|
S.setScale(scale);
|
||||||
|
return T * R * S;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // end namespace core
|
||||||
|
} // end namespace irr
|
|
@ -180,7 +180,7 @@ public:
|
||||||
linear interpolation.
|
linear interpolation.
|
||||||
*/
|
*/
|
||||||
quaternion &slerp(quaternion q1, quaternion q2,
|
quaternion &slerp(quaternion q1, quaternion q2,
|
||||||
f32 time, f32 threshold = .05f);
|
f32 time, f32 threshold = .001f);
|
||||||
|
|
||||||
//! Set this quaternion to represent a rotation from angle and axis.
|
//! Set this quaternion to represent a rotation from angle and axis.
|
||||||
/** Axis must be unit length.
|
/** Axis must be unit length.
|
||||||
|
|
|
@ -3,9 +3,13 @@
|
||||||
// For conditions of distribution and use, see copyright notice in irrlicht.h
|
// For conditions of distribution and use, see copyright notice in irrlicht.h
|
||||||
|
|
||||||
#include "CAnimatedMeshSceneNode.h"
|
#include "CAnimatedMeshSceneNode.h"
|
||||||
|
#include "CBoneSceneNode.h"
|
||||||
#include "IVideoDriver.h"
|
#include "IVideoDriver.h"
|
||||||
#include "ISceneManager.h"
|
#include "ISceneManager.h"
|
||||||
#include "S3DVertex.h"
|
#include "S3DVertex.h"
|
||||||
|
#include "Transform.h"
|
||||||
|
#include "irrTypes.h"
|
||||||
|
#include "matrix4.h"
|
||||||
#include "os.h"
|
#include "os.h"
|
||||||
#include "SkinnedMesh.h"
|
#include "SkinnedMesh.h"
|
||||||
#include "IDummyTransformationSceneNode.h"
|
#include "IDummyTransformationSceneNode.h"
|
||||||
|
@ -17,6 +21,9 @@
|
||||||
#include "IFileSystem.h"
|
#include "IFileSystem.h"
|
||||||
#include "quaternion.h"
|
#include "quaternion.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <optional>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
namespace irr
|
namespace irr
|
||||||
{
|
{
|
||||||
|
@ -30,13 +37,13 @@ CAnimatedMeshSceneNode::CAnimatedMeshSceneNode(IAnimatedMesh *mesh,
|
||||||
const core::vector3df &rotation,
|
const core::vector3df &rotation,
|
||||||
const core::vector3df &scale) :
|
const core::vector3df &scale) :
|
||||||
IAnimatedMeshSceneNode(parent, mgr, id, position, rotation, scale),
|
IAnimatedMeshSceneNode(parent, mgr, id, position, rotation, scale),
|
||||||
Mesh(0),
|
Mesh(nullptr),
|
||||||
StartFrame(0), EndFrame(0), FramesPerSecond(0.025f),
|
StartFrame(0), EndFrame(0), FramesPerSecond(0.025f),
|
||||||
CurrentFrameNr(0.f), LastTimeMs(0),
|
CurrentFrameNr(0.f), LastTimeMs(0),
|
||||||
TransitionTime(0), Transiting(0.f), TransitingBlend(0.f),
|
TransitionTime(0), Transiting(0.f), TransitingBlend(0.f),
|
||||||
JointMode(EJUOR_NONE), JointsUsed(false),
|
JointsUsed(false),
|
||||||
Looping(true), ReadOnlyMaterials(false), RenderFromIdentity(false),
|
Looping(true), ReadOnlyMaterials(false), RenderFromIdentity(false),
|
||||||
LoopCallBack(0), PassCount(0)
|
PassCount(0)
|
||||||
{
|
{
|
||||||
setMesh(mesh);
|
setMesh(mesh);
|
||||||
}
|
}
|
||||||
|
@ -44,8 +51,6 @@ CAnimatedMeshSceneNode::CAnimatedMeshSceneNode(IAnimatedMesh *mesh,
|
||||||
//! destructor
|
//! destructor
|
||||||
CAnimatedMeshSceneNode::~CAnimatedMeshSceneNode()
|
CAnimatedMeshSceneNode::~CAnimatedMeshSceneNode()
|
||||||
{
|
{
|
||||||
if (LoopCallBack)
|
|
||||||
LoopCallBack->drop();
|
|
||||||
if (Mesh)
|
if (Mesh)
|
||||||
Mesh->drop();
|
Mesh->drop();
|
||||||
}
|
}
|
||||||
|
@ -87,8 +92,7 @@ void CAnimatedMeshSceneNode::buildFrameNr(u32 timeMs)
|
||||||
if (FramesPerSecond > 0.f) { // forwards...
|
if (FramesPerSecond > 0.f) { // forwards...
|
||||||
if (CurrentFrameNr > EndFrame)
|
if (CurrentFrameNr > EndFrame)
|
||||||
CurrentFrameNr = StartFrame + fmodf(CurrentFrameNr - StartFrame, EndFrame - StartFrame);
|
CurrentFrameNr = StartFrame + fmodf(CurrentFrameNr - StartFrame, EndFrame - StartFrame);
|
||||||
} else // backwards...
|
} else { // backwards...
|
||||||
{
|
|
||||||
if (CurrentFrameNr < StartFrame)
|
if (CurrentFrameNr < StartFrame)
|
||||||
CurrentFrameNr = EndFrame - fmodf(EndFrame - CurrentFrameNr, EndFrame - StartFrame);
|
CurrentFrameNr = EndFrame - fmodf(EndFrame - CurrentFrameNr, EndFrame - StartFrame);
|
||||||
}
|
}
|
||||||
|
@ -97,18 +101,9 @@ void CAnimatedMeshSceneNode::buildFrameNr(u32 timeMs)
|
||||||
|
|
||||||
CurrentFrameNr += timeMs * FramesPerSecond;
|
CurrentFrameNr += timeMs * FramesPerSecond;
|
||||||
if (FramesPerSecond > 0.f) { // forwards...
|
if (FramesPerSecond > 0.f) { // forwards...
|
||||||
if (CurrentFrameNr > EndFrame) {
|
CurrentFrameNr = std::min(CurrentFrameNr, EndFrame);
|
||||||
CurrentFrameNr = EndFrame;
|
} else { // backwards...
|
||||||
if (LoopCallBack)
|
CurrentFrameNr = std::max(CurrentFrameNr, StartFrame);
|
||||||
LoopCallBack->OnAnimationEnd(this);
|
|
||||||
}
|
|
||||||
} else // backwards...
|
|
||||||
{
|
|
||||||
if (CurrentFrameNr < StartFrame) {
|
|
||||||
CurrentFrameNr = StartFrame;
|
|
||||||
if (LoopCallBack)
|
|
||||||
LoopCallBack->OnAnimationEnd(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -156,38 +151,18 @@ void CAnimatedMeshSceneNode::OnRegisterSceneNode()
|
||||||
IMesh *CAnimatedMeshSceneNode::getMeshForCurrentFrame()
|
IMesh *CAnimatedMeshSceneNode::getMeshForCurrentFrame()
|
||||||
{
|
{
|
||||||
if (Mesh->getMeshType() != EAMT_SKINNED) {
|
if (Mesh->getMeshType() != EAMT_SKINNED) {
|
||||||
return Mesh->getMesh(getFrameNr());
|
return Mesh;
|
||||||
} else {
|
}
|
||||||
|
|
||||||
// As multiple scene nodes may be sharing the same skinned mesh, we have to
|
// 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.
|
// re-animate it every frame to ensure that this node gets the mesh that it needs.
|
||||||
|
|
||||||
SkinnedMesh *skinnedMesh = static_cast<SkinnedMesh *>(Mesh);
|
auto *skinnedMesh = static_cast<SkinnedMesh *>(Mesh);
|
||||||
|
|
||||||
if (JointMode == EJUOR_CONTROL) // write to mesh
|
// Matrices have already been calculated in OnAnimate
|
||||||
skinnedMesh->transferJointsToMesh(JointChildSceneNodes);
|
skinnedMesh->skinMesh(PerJoint.GlobalMatrices);
|
||||||
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 skinnedMesh;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//! OnAnimate() is called just before rendering the whole scene.
|
//! OnAnimate() is called just before rendering the whole scene.
|
||||||
|
@ -201,7 +176,28 @@ void CAnimatedMeshSceneNode::OnAnimate(u32 timeMs)
|
||||||
buildFrameNr(timeMs - LastTimeMs);
|
buildFrameNr(timeMs - LastTimeMs);
|
||||||
LastTimeMs = timeMs;
|
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);
|
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.
|
//! renders the node.
|
||||||
|
@ -218,15 +214,7 @@ void CAnimatedMeshSceneNode::render()
|
||||||
++PassCount;
|
++PassCount;
|
||||||
|
|
||||||
scene::IMesh *m = getMeshForCurrentFrame();
|
scene::IMesh *m = getMeshForCurrentFrame();
|
||||||
|
assert(m);
|
||||||
if (m) {
|
|
||||||
Box = m->getBoundingBox();
|
|
||||||
} else {
|
|
||||||
#ifdef _DEBUG
|
|
||||||
os::Printer::log("Animated Mesh returned no mesh to render.", ELL_WARNING);
|
|
||||||
#endif
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
|
driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
|
||||||
|
|
||||||
|
@ -294,11 +282,12 @@ void CAnimatedMeshSceneNode::render()
|
||||||
if (DebugDataVisible & scene::EDS_SKELETON) {
|
if (DebugDataVisible & scene::EDS_SKELETON) {
|
||||||
if (Mesh->getMeshType() == EAMT_SKINNED) {
|
if (Mesh->getMeshType() == EAMT_SKINNED) {
|
||||||
// draw skeleton
|
// draw skeleton
|
||||||
|
const auto &joints = (static_cast<SkinnedMesh *>(Mesh))->getAllJoints();
|
||||||
for (auto *joint : ((SkinnedMesh *)Mesh)->getAllJoints()) {
|
for (u16 i = 0; i < PerJoint.GlobalMatrices.size(); ++i) {
|
||||||
for (const auto *childJoint : joint->Children) {
|
const auto translation = PerJoint.GlobalMatrices[i].getTranslation();
|
||||||
driver->draw3DLine(joint->GlobalAnimatedMatrix.getTranslation(),
|
if (auto pjid = joints[i]->ParentJointID) {
|
||||||
childJoint->GlobalAnimatedMatrix.getTranslation(),
|
const auto parent_translation = PerJoint.GlobalMatrices[*pjid].getTranslation();
|
||||||
|
driver->draw3DLine(parent_translation, translation,
|
||||||
video::SColor(255, 51, 66, 255));
|
video::SColor(255, 51, 66, 255));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -407,12 +396,12 @@ IBoneSceneNode *CAnimatedMeshSceneNode::getJointNode(const c8 *jointName)
|
||||||
return 0;
|
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);
|
os::Printer::log("Joint was found in mesh, but is not loaded into node", jointName, ELL_WARNING);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return JointChildSceneNodes[*number];
|
return PerJoint.SceneNodes[*number];
|
||||||
}
|
}
|
||||||
|
|
||||||
//! Returns a pointer to a child node, which has the same transformation as
|
//! Returns a pointer to a child node, which has the same transformation as
|
||||||
|
@ -426,12 +415,12 @@ IBoneSceneNode *CAnimatedMeshSceneNode::getJointNode(u32 jointID)
|
||||||
|
|
||||||
checkJoints();
|
checkJoints();
|
||||||
|
|
||||||
if (JointChildSceneNodes.size() <= jointID) {
|
if (PerJoint.SceneNodes.size() <= jointID) {
|
||||||
os::Printer::log("Joint not loaded into node", ELL_WARNING);
|
os::Printer::log("Joint not loaded into node", ELL_WARNING);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return JointChildSceneNodes[jointID];
|
return PerJoint.SceneNodes[jointID];
|
||||||
}
|
}
|
||||||
|
|
||||||
//! Gets joint count.
|
//! Gets joint count.
|
||||||
|
@ -452,9 +441,9 @@ bool CAnimatedMeshSceneNode::removeChild(ISceneNode *child)
|
||||||
{
|
{
|
||||||
if (ISceneNode::removeChild(child)) {
|
if (ISceneNode::removeChild(child)) {
|
||||||
if (JointsUsed) { // stop weird bugs caused while changing parents as the joints are being created
|
if (JointsUsed) { // stop weird bugs caused while changing parents as the joints are being created
|
||||||
for (u32 i = 0; i < JointChildSceneNodes.size(); ++i) {
|
for (u32 i = 0; i < PerJoint.SceneNodes.size(); ++i) {
|
||||||
if (JointChildSceneNodes[i] == child) {
|
if (PerJoint.SceneNodes[i] == child) {
|
||||||
JointChildSceneNodes[i] = 0; // remove link to child
|
PerJoint.SceneNodes[i] = 0; // remove link to child
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -478,22 +467,6 @@ bool CAnimatedMeshSceneNode::getLoopMode() const
|
||||||
return Looping;
|
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.
|
//! 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)
|
void CAnimatedMeshSceneNode::setReadOnlyMaterials(bool readonly)
|
||||||
{
|
{
|
||||||
|
@ -525,19 +498,16 @@ void CAnimatedMeshSceneNode::setMesh(IAnimatedMesh *mesh)
|
||||||
// get materials and bounding box
|
// get materials and bounding box
|
||||||
Box = Mesh->getBoundingBox();
|
Box = Mesh->getBoundingBox();
|
||||||
|
|
||||||
IMesh *m = Mesh->getMesh(0);
|
|
||||||
if (m) {
|
|
||||||
Materials.clear();
|
Materials.clear();
|
||||||
Materials.reallocate(m->getMeshBufferCount());
|
Materials.reallocate(Mesh->getMeshBufferCount());
|
||||||
|
|
||||||
for (u32 i = 0; i < m->getMeshBufferCount(); ++i) {
|
for (u32 i = 0; i < Mesh->getMeshBufferCount(); ++i) {
|
||||||
IMeshBuffer *mb = m->getMeshBuffer(i);
|
IMeshBuffer *mb = Mesh->getMeshBuffer(i);
|
||||||
if (mb)
|
if (mb)
|
||||||
Materials.push_back(mb->getMaterial());
|
Materials.push_back(mb->getMaterial());
|
||||||
else
|
else
|
||||||
Materials.push_back(video::SMaterial());
|
Materials.push_back(video::SMaterial());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// clean up joint nodes
|
// clean up joint nodes
|
||||||
if (JointsUsed) {
|
if (JointsUsed) {
|
||||||
|
@ -556,14 +526,7 @@ void CAnimatedMeshSceneNode::updateAbsolutePosition()
|
||||||
IAnimatedMeshSceneNode::updateAbsolutePosition();
|
IAnimatedMeshSceneNode::updateAbsolutePosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
//! Set the joint update mode (0-unused, 1-get joints only, 2-set joints only, 3-move and set)
|
//! Sets the transition time in seconds (note: This needs to enable joints)
|
||||||
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)
|
|
||||||
//! you must call animateJoints(), or the mesh will not animate
|
//! you must call animateJoints(), or the mesh will not animate
|
||||||
void CAnimatedMeshSceneNode::setTransitionTime(f32 time)
|
void CAnimatedMeshSceneNode::setTransitionTime(f32 time)
|
||||||
{
|
{
|
||||||
|
@ -571,10 +534,6 @@ void CAnimatedMeshSceneNode::setTransitionTime(f32 time)
|
||||||
if (TransitionTime == ttime)
|
if (TransitionTime == ttime)
|
||||||
return;
|
return;
|
||||||
TransitionTime = ttime;
|
TransitionTime = ttime;
|
||||||
if (ttime != 0)
|
|
||||||
setJointMode(EJUOR_CONTROL);
|
|
||||||
else
|
|
||||||
setJointMode(EJUOR_NONE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//! render mesh ignoring its transformation. Used with ragdolls. (culling is unaffected)
|
//! render mesh ignoring its transformation. Used with ragdolls. (culling is unaffected)
|
||||||
|
@ -583,120 +542,104 @@ void CAnimatedMeshSceneNode::setRenderFromIdentity(bool enable)
|
||||||
RenderFromIdentity = enable;
|
RenderFromIdentity = enable;
|
||||||
}
|
}
|
||||||
|
|
||||||
//! updates the joint positions of this mesh
|
void CAnimatedMeshSceneNode::addJoints()
|
||||||
void CAnimatedMeshSceneNode::animateJoints(bool CalculateAbsolutePositions)
|
|
||||||
{
|
{
|
||||||
if (Mesh && Mesh->getMeshType() == EAMT_SKINNED) {
|
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>{}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//! updates the joint positions of this mesh
|
||||||
|
void CAnimatedMeshSceneNode::animateJoints()
|
||||||
|
{
|
||||||
|
if (!Mesh || Mesh->getMeshType() != EAMT_SKINNED)
|
||||||
|
return;
|
||||||
|
|
||||||
checkJoints();
|
checkJoints();
|
||||||
const f32 frame = getFrameNr(); // old?
|
|
||||||
|
|
||||||
SkinnedMesh *skinnedMesh = static_cast<SkinnedMesh *>(Mesh);
|
SkinnedMesh *skinnedMesh = static_cast<SkinnedMesh *>(Mesh);
|
||||||
|
if (!skinnedMesh->isStatic())
|
||||||
skinnedMesh->animateMesh(frame);
|
updateJointSceneNodes(skinnedMesh->animateMesh(getFrameNr()));
|
||||||
skinnedMesh->recoverJointsFromMesh(JointChildSceneNodes);
|
|
||||||
|
|
||||||
//-----------------------------------------
|
//-----------------------------------------
|
||||||
// Transition
|
// Transition
|
||||||
//-----------------------------------------
|
//-----------------------------------------
|
||||||
|
|
||||||
if (Transiting != 0.f) {
|
if (Transiting != 0.f) {
|
||||||
// Init additional matrices
|
for (u32 i = 0; i < PerJoint.SceneNodes.size(); ++i) {
|
||||||
if (PretransitingSave.size() < JointChildSceneNodes.size()) {
|
if (PerJoint.PreTransSaves[i]) {
|
||||||
for (u32 n = PretransitingSave.size(); n < JointChildSceneNodes.size(); ++n)
|
PerJoint.SceneNodes[i]->setTransform(PerJoint.PreTransSaves[i]->interpolate(
|
||||||
PretransitingSave.push_back(core::matrix4());
|
PerJoint.SceneNodes[i]->getTransform(), TransitingBlend));
|
||||||
}
|
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CalculateAbsolutePositions) {
|
|
||||||
//---slow---
|
|
||||||
for (u32 n = 0; n < JointChildSceneNodes.size(); ++n) {
|
|
||||||
if (JointChildSceneNodes[n]->getParent() == this) {
|
|
||||||
JointChildSceneNodes[n]->updateAbsolutePositionOfAllChildren(); // temp, should be an option
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
|
||||||
*/
|
|
||||||
void CAnimatedMeshSceneNode::checkJoints()
|
void CAnimatedMeshSceneNode::checkJoints()
|
||||||
{
|
{
|
||||||
if (!Mesh || Mesh->getMeshType() != EAMT_SKINNED)
|
if (!Mesh || Mesh->getMeshType() != EAMT_SKINNED)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!JointsUsed) {
|
if (!JointsUsed) {
|
||||||
for (u32 i = 0; i < JointChildSceneNodes.size(); ++i)
|
for (u32 i = 0; i < PerJoint.SceneNodes.size(); ++i)
|
||||||
removeChild(JointChildSceneNodes[i]);
|
removeChild(PerJoint.SceneNodes[i]);
|
||||||
JointChildSceneNodes.clear();
|
addJoints();
|
||||||
|
|
||||||
// Create joints for SkinnedMesh
|
|
||||||
((SkinnedMesh *)Mesh)->addJoints(JointChildSceneNodes, this, SceneManager);
|
|
||||||
((SkinnedMesh *)Mesh)->recoverJointsFromMesh(JointChildSceneNodes);
|
|
||||||
|
|
||||||
JointsUsed = true;
|
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()
|
void CAnimatedMeshSceneNode::beginTransition()
|
||||||
{
|
{
|
||||||
if (!JointsUsed)
|
if (!JointsUsed)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (TransitionTime != 0) {
|
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);
|
Transiting = core::reciprocal((f32)TransitionTime);
|
||||||
}
|
}
|
||||||
TransitingBlend = 0.f;
|
TransitingBlend = 0.f;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
|
||||||
*/
|
|
||||||
ISceneNode *CAnimatedMeshSceneNode::clone(ISceneNode *newParent, ISceneManager *newManager)
|
ISceneNode *CAnimatedMeshSceneNode::clone(ISceneNode *newParent, ISceneManager *newManager)
|
||||||
{
|
{
|
||||||
if (!newParent)
|
if (!newParent)
|
||||||
|
@ -722,19 +665,15 @@ ISceneNode *CAnimatedMeshSceneNode::clone(ISceneNode *newParent, ISceneManager *
|
||||||
newNode->EndFrame = EndFrame;
|
newNode->EndFrame = EndFrame;
|
||||||
newNode->FramesPerSecond = FramesPerSecond;
|
newNode->FramesPerSecond = FramesPerSecond;
|
||||||
newNode->CurrentFrameNr = CurrentFrameNr;
|
newNode->CurrentFrameNr = CurrentFrameNr;
|
||||||
newNode->JointMode = JointMode;
|
|
||||||
newNode->JointsUsed = JointsUsed;
|
newNode->JointsUsed = JointsUsed;
|
||||||
newNode->TransitionTime = TransitionTime;
|
newNode->TransitionTime = TransitionTime;
|
||||||
newNode->Transiting = Transiting;
|
newNode->Transiting = Transiting;
|
||||||
newNode->TransitingBlend = TransitingBlend;
|
newNode->TransitingBlend = TransitingBlend;
|
||||||
newNode->Looping = Looping;
|
newNode->Looping = Looping;
|
||||||
newNode->ReadOnlyMaterials = ReadOnlyMaterials;
|
newNode->ReadOnlyMaterials = ReadOnlyMaterials;
|
||||||
newNode->LoopCallBack = LoopCallBack;
|
|
||||||
if (newNode->LoopCallBack)
|
|
||||||
newNode->LoopCallBack->grab();
|
|
||||||
newNode->PassCount = PassCount;
|
newNode->PassCount = PassCount;
|
||||||
newNode->JointChildSceneNodes = JointChildSceneNodes;
|
newNode->PerJoint.SceneNodes = PerJoint.SceneNodes;
|
||||||
newNode->PretransitingSave = PretransitingSave;
|
newNode->PerJoint.PreTransSaves = PerJoint.PreTransSaves;
|
||||||
newNode->RenderFromIdentity = RenderFromIdentity;
|
newNode->RenderFromIdentity = RenderFromIdentity;
|
||||||
|
|
||||||
return newNode;
|
return newNode;
|
||||||
|
|
|
@ -4,9 +4,12 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "CBoneSceneNode.h"
|
||||||
#include "IAnimatedMeshSceneNode.h"
|
#include "IAnimatedMeshSceneNode.h"
|
||||||
#include "IAnimatedMesh.h"
|
#include "IAnimatedMesh.h"
|
||||||
|
|
||||||
|
#include "SkinnedMesh.h"
|
||||||
|
#include "Transform.h"
|
||||||
#include "matrix4.h"
|
#include "matrix4.h"
|
||||||
|
|
||||||
namespace irr
|
namespace irr
|
||||||
|
@ -54,9 +57,11 @@ public:
|
||||||
//! returns the current loop mode
|
//! returns the current loop mode
|
||||||
bool getLoopMode() const override;
|
bool getLoopMode() const override;
|
||||||
|
|
||||||
//! Sets a callback interface which will be called if an animation
|
void setOnAnimateCallback(
|
||||||
//! playback has ended. Set this to 0 to disable the callback again.
|
const std::function<void(f32 dtime)> &cb) override
|
||||||
void setAnimationEndCallback(IAnimationEndCallBack *callback = 0) override;
|
{
|
||||||
|
OnAnimateCallback = cb;
|
||||||
|
}
|
||||||
|
|
||||||
//! sets the speed with which the animation is played
|
//! 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
|
//! 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
|
//! updates the absolute position based on the relative and the parents position
|
||||||
void updateAbsolutePosition() override;
|
void updateAbsolutePosition() override;
|
||||||
|
|
||||||
//! Set the joint update mode (0-unused, 1-get joints only, 2-set joints only, 3-move and set)
|
//! Sets the transition time in seconds (note: This needs to enable joints)
|
||||||
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)
|
|
||||||
//! you must call animateJoints(), or the mesh will not animate
|
//! you must call animateJoints(), or the mesh will not animate
|
||||||
void setTransitionTime(f32 Time) override;
|
void setTransitionTime(f32 Time) override;
|
||||||
|
|
||||||
|
void updateJointSceneNodes(const std::vector<SkinnedMesh::SJoint::VariantTransform> &transforms);
|
||||||
|
|
||||||
//! updates the joint positions of this mesh
|
//! 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)
|
//! render mesh ignoring its transformation. Used with ragdolls. (culling is unaffected)
|
||||||
void setRenderFromIdentity(bool On) override;
|
void setRenderFromIdentity(bool On) override;
|
||||||
|
@ -142,6 +148,7 @@ private:
|
||||||
|
|
||||||
void buildFrameNr(u32 timeMs);
|
void buildFrameNr(u32 timeMs);
|
||||||
void checkJoints();
|
void checkJoints();
|
||||||
|
void copyOldTransforms();
|
||||||
void beginTransition();
|
void beginTransition();
|
||||||
|
|
||||||
core::array<video::SMaterial> Materials;
|
core::array<video::SMaterial> Materials;
|
||||||
|
@ -158,19 +165,30 @@ private:
|
||||||
f32 Transiting; // is mesh transiting (plus cache of TransitionTime)
|
f32 Transiting; // is mesh transiting (plus cache of TransitionTime)
|
||||||
f32 TransitingBlend; // 0-1, calculated on buildFrameNr
|
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 JointsUsed;
|
||||||
|
|
||||||
bool Looping;
|
bool Looping;
|
||||||
bool ReadOnlyMaterials;
|
bool ReadOnlyMaterials;
|
||||||
bool RenderFromIdentity;
|
bool RenderFromIdentity;
|
||||||
|
|
||||||
IAnimationEndCallBack *LoopCallBack;
|
|
||||||
s32 PassCount;
|
s32 PassCount;
|
||||||
|
std::function<void(f32)> OnAnimateCallback;
|
||||||
|
|
||||||
std::vector<IBoneSceneNode *> JointChildSceneNodes;
|
struct PerJointData {
|
||||||
core::array<core::matrix4> PretransitingSave;
|
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
|
} // 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);
|
os::Printer::log(logStr.c_str(), joint->Name.value_or("").c_str(), ELL_DEBUG);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
f32 position[3], scale[3], rotation[4];
|
core::Transform transform;
|
||||||
|
{
|
||||||
|
f32 t[3], s[3], r[4];
|
||||||
|
|
||||||
readFloats(position, 3);
|
readFloats(t, 3);
|
||||||
readFloats(scale, 3);
|
readFloats(s, 3);
|
||||||
readFloats(rotation, 4);
|
readFloats(r, 4);
|
||||||
|
|
||||||
joint->Animatedposition = core::vector3df(position[0], position[1], position[2]);
|
joint->transform = transform = {
|
||||||
joint->Animatedscale = core::vector3df(scale[0], scale[1], scale[2]);
|
{t[0], t[1], t[2]},
|
||||||
joint->Animatedrotation = core::quaternion(rotation[1], rotation[2], rotation[3], rotation[0]);
|
{r[1], r[2], r[3], r[0]},
|
||||||
|
{s[0], s[1], s[2]},
|
||||||
// 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;
|
|
||||||
|
|
||||||
if (inJoint)
|
if (inJoint)
|
||||||
joint->GlobalMatrix = inJoint->GlobalMatrix * joint->LocalMatrix;
|
joint->GlobalMatrix = inJoint->GlobalMatrix * transform.buildMatrix();
|
||||||
else
|
else
|
||||||
joint->GlobalMatrix = joint->LocalMatrix;
|
joint->GlobalMatrix = transform.buildMatrix();
|
||||||
|
|
||||||
while (B3dStack.getLast().startposition + B3dStack.getLast().length > B3DFile->getPos()) // this chunk repeats
|
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
|
// Used with SkinnedMesh and IAnimatedMeshSceneNode, for boned meshes
|
||||||
|
|
||||||
#include "IBoneSceneNode.h"
|
#include "IBoneSceneNode.h"
|
||||||
|
#include "Transform.h"
|
||||||
|
#include "matrix4.h"
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
|
@ -21,49 +23,48 @@ public:
|
||||||
//! constructor
|
//! constructor
|
||||||
CBoneSceneNode(ISceneNode *parent, ISceneManager *mgr,
|
CBoneSceneNode(ISceneNode *parent, ISceneManager *mgr,
|
||||||
s32 id = -1, u32 boneIndex = 0,
|
s32 id = -1, u32 boneIndex = 0,
|
||||||
const std::optional<std::string> &boneName = std::nullopt);
|
const std::optional<std::string> &boneName = std::nullopt,
|
||||||
|
const core::Transform &transform = {},
|
||||||
//! Returns the index of the bone
|
const std::optional<core::matrix4> &matrix = std::nullopt) :
|
||||||
u32 getBoneIndex() const override;
|
IBoneSceneNode(parent, mgr, id, boneIndex, boneName),
|
||||||
|
Matrix(matrix)
|
||||||
//! 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
|
|
||||||
{
|
{
|
||||||
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:
|
core::Transform getTransform() const
|
||||||
void helper_updateAbsolutePositionOfAllChildren(ISceneNode *Node);
|
{
|
||||||
|
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};
|
//! Some file formats alternatively let bones specify a transformation matrix.
|
||||||
|
//! If this is set, it overrides the TRS properties.
|
||||||
E_BONE_ANIMATION_MODE AnimationMode;
|
std::optional<core::matrix4> Matrix;
|
||||||
E_BONE_SKINNING_SPACE SkinningSpace;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // end namespace scene
|
} // 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[i] = static_cast<f32>(m[i]);
|
||||||
mat = convertHandedness(mat);
|
mat = convertHandedness(mat);
|
||||||
|
|
||||||
// Decompose the matrix into translation, scale, and rotation.
|
// Note: "When a node is targeted for animation [...],
|
||||||
joint->Animatedposition = mat.getTranslation();
|
// only TRS properties MAY be present; matrix MUST NOT be present."
|
||||||
|
// Thus we MUST NOT do any decomposition, which in general need not exist.
|
||||||
auto scale = mat.getScale();
|
joint->transform = mat;
|
||||||
joint->Animatedscale = scale;
|
|
||||||
joint->Animatedrotation = mat.getRotationRadians(scale);
|
|
||||||
// Invert the rotation because it is applied using `getMatrix_transposed`,
|
|
||||||
// which again inverts.
|
|
||||||
joint->Animatedrotation.makeInverse();
|
|
||||||
|
|
||||||
return mat;
|
return mat;
|
||||||
}
|
}
|
||||||
|
|
||||||
static core::matrix4 loadTransform(const tiniergltf::Node::TRS &trs, SkinnedMesh::SJoint *joint)
|
static core::matrix4 loadTransform(const tiniergltf::Node::TRS &trs, SkinnedMesh::SJoint *joint)
|
||||||
{
|
{
|
||||||
const auto &trans = trs.translation;
|
const auto &t = trs.translation;
|
||||||
const auto &rot = trs.rotation;
|
const auto &r = trs.rotation;
|
||||||
const auto &scale = trs.scale;
|
const auto &s = trs.scale;
|
||||||
core::matrix4 transMat;
|
core::Transform transform{
|
||||||
joint->Animatedposition = convertHandedness(core::vector3df(trans[0], trans[1], trans[2]));
|
convertHandedness(core::vector3df(t[0], t[1], t[2])),
|
||||||
transMat.setTranslation(joint->Animatedposition);
|
convertHandedness(core::quaternion(r[0], r[1], r[2], r[3])),
|
||||||
core::matrix4 rotMat;
|
core::vector3df(s[0], s[1], s[2]),
|
||||||
joint->Animatedrotation = convertHandedness(core::quaternion(rot[0], rot[1], rot[2], rot[3]));
|
};
|
||||||
core::quaternion(joint->Animatedrotation).getMatrix_transposed(rotMat);
|
joint->transform = transform;
|
||||||
joint->Animatedscale = core::vector3df(scale[0], scale[1], scale[2]);
|
return transform.buildMatrix();
|
||||||
core::matrix4 scaleMat;
|
|
||||||
scaleMat.setScale(joint->Animatedscale);
|
|
||||||
return transMat * rotMat * scaleMat;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static core::matrix4 loadTransform(std::optional<std::variant<tiniergltf::Node::Matrix, tiniergltf::Node::TRS>> transform,
|
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);
|
const auto &node = m_gltf_model.nodes->at(nodeIdx);
|
||||||
auto *joint = m_irr_model->addJoint(parent);
|
auto *joint = m_irr_model->addJoint(parent);
|
||||||
const core::matrix4 transform = loadTransform(node.transform, joint);
|
const core::matrix4 transform = loadTransform(node.transform, joint);
|
||||||
joint->LocalMatrix = transform;
|
joint->GlobalMatrix = parent ? parent->GlobalMatrix * transform : transform;
|
||||||
joint->GlobalMatrix = parent ? parent->GlobalMatrix * joint->LocalMatrix : joint->LocalMatrix;
|
|
||||||
if (node.name.has_value()) {
|
if (node.name.has_value()) {
|
||||||
joint->Name = node.name->c_str();
|
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);
|
const auto &anim = m_gltf_model.animations->at(animIdx);
|
||||||
for (const auto &channel : anim.channels) {
|
for (const auto &channel : anim.channels) {
|
||||||
|
|
||||||
const auto &sampler = anim.samplers.at(channel.sampler);
|
const auto &sampler = anim.samplers.at(channel.sampler);
|
||||||
|
|
||||||
bool interpolate = ([&]() {
|
bool interpolate = ([&]() {
|
||||||
|
@ -663,6 +652,11 @@ void SelfType::MeshExtractor::loadAnimation(const std::size_t animIdx)
|
||||||
throw std::runtime_error("no animated node");
|
throw std::runtime_error("no animated node");
|
||||||
|
|
||||||
auto *joint = m_loaded_nodes.at(*channel.target.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) {
|
switch (channel.target.path) {
|
||||||
case tiniergltf::AnimationChannelTarget::Path::TRANSLATION: {
|
case tiniergltf::AnimationChannelTarget::Path::TRANSLATION: {
|
||||||
const auto outputAccessor = Accessor<core::vector3df>::make(m_gltf_model, sampler.output);
|
const auto outputAccessor = Accessor<core::vector3df>::make(m_gltf_model, sampler.output);
|
||||||
|
|
|
@ -320,7 +320,6 @@ set(IRRMESHLOADER
|
||||||
|
|
||||||
add_library(IRRMESHOBJ OBJECT
|
add_library(IRRMESHOBJ OBJECT
|
||||||
SkinnedMesh.cpp
|
SkinnedMesh.cpp
|
||||||
CBoneSceneNode.cpp
|
|
||||||
CMeshSceneNode.cpp
|
CMeshSceneNode.cpp
|
||||||
CAnimatedMeshSceneNode.cpp
|
CAnimatedMeshSceneNode.cpp
|
||||||
${IRRMESHLOADER}
|
${IRRMESHLOADER}
|
||||||
|
|
|
@ -35,7 +35,7 @@ void CMeshCache::removeMesh(const IMesh *const mesh)
|
||||||
if (!mesh)
|
if (!mesh)
|
||||||
return;
|
return;
|
||||||
for (u32 i = 0; i < Meshes.size(); ++i) {
|
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[i].Mesh->drop();
|
||||||
Meshes.erase(i);
|
Meshes.erase(i);
|
||||||
return;
|
return;
|
||||||
|
@ -53,7 +53,7 @@ u32 CMeshCache::getMeshCount() const
|
||||||
s32 CMeshCache::getMeshIndex(const IMesh *const mesh) const
|
s32 CMeshCache::getMeshIndex(const IMesh *const mesh) const
|
||||||
{
|
{
|
||||||
for (u32 i = 0; i < Meshes.size(); ++i) {
|
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;
|
return (s32)i;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ const io::SNamedPath &CMeshCache::getMeshName(const IMesh *const mesh) const
|
||||||
return emptyNamedPath;
|
return emptyNamedPath;
|
||||||
|
|
||||||
for (u32 i = 0; i < Meshes.size(); ++i) {
|
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;
|
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)
|
bool CMeshCache::renameMesh(const IMesh *const mesh, const io::path &name)
|
||||||
{
|
{
|
||||||
for (u32 i = 0; i < Meshes.size(); ++i) {
|
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[i].NamedPath.setPath(name);
|
||||||
Meshes.sort();
|
Meshes.sort();
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
#include "SkinnedMesh.h"
|
#include "SkinnedMesh.h"
|
||||||
#include "SMesh.h"
|
#include "SMesh.h"
|
||||||
#include "CMeshBuffer.h"
|
#include "CMeshBuffer.h"
|
||||||
#include "SAnimatedMesh.h"
|
|
||||||
#include "os.h"
|
#include "os.h"
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
@ -178,34 +177,5 @@ SMesh *CMeshManipulator::createMeshCopy(scene::IMesh *mesh) const
|
||||||
return clone;
|
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 scene
|
||||||
} // end namespace irr
|
} // end namespace irr
|
||||||
|
|
|
@ -31,15 +31,6 @@ public:
|
||||||
|
|
||||||
//! Clones a static IMesh into a modifiable SMesh.
|
//! Clones a static IMesh into a modifiable SMesh.
|
||||||
SMesh *createMeshCopy(scene::IMesh *mesh) const override;
|
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
|
} // end namespace scene
|
||||||
|
|
|
@ -1238,7 +1238,7 @@ void CNullDriver::addOcclusionQuery(scene::ISceneNode *node, const scene::IMesh
|
||||||
else if (node->getType() == scene::ESNT_MESH)
|
else if (node->getType() == scene::ESNT_MESH)
|
||||||
mesh = static_cast<scene::IMeshSceneNode *>(node)->getMesh();
|
mesh = static_cast<scene::IMeshSceneNode *>(node)->getMesh();
|
||||||
else
|
else
|
||||||
mesh = static_cast<scene::IAnimatedMeshSceneNode *>(node)->getMesh()->getMesh(0);
|
mesh = static_cast<scene::IAnimatedMeshSceneNode *>(node)->getMesh();
|
||||||
if (!mesh)
|
if (!mesh)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
#include "IVideoDriver.h"
|
#include "IVideoDriver.h"
|
||||||
#include "SMesh.h"
|
#include "SMesh.h"
|
||||||
#include "SMeshBuffer.h"
|
#include "SMeshBuffer.h"
|
||||||
#include "SAnimatedMesh.h"
|
|
||||||
#include "IReadFile.h"
|
#include "IReadFile.h"
|
||||||
#include "fast_atof.h"
|
#include "fast_atof.h"
|
||||||
#include "coreutil.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
|
// Clean up the allocate obj file contents
|
||||||
delete[] buf;
|
delete[] buf;
|
||||||
// more cleaning up
|
// more cleaning up
|
||||||
cleanUp();
|
cleanUp();
|
||||||
mesh->drop();
|
|
||||||
|
|
||||||
return animMesh;
|
// Nothing in the mesh
|
||||||
|
if (mesh->getMeshBufferCount() == 0) {
|
||||||
|
mesh->drop();
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
mesh->recalculateBoundingBox();
|
||||||
|
return mesh;
|
||||||
}
|
}
|
||||||
|
|
||||||
//! Read RGB color
|
//! Read RGB color
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
#include "CSceneManager.h"
|
#include "CSceneManager.h"
|
||||||
#include "IVideoDriver.h"
|
#include "IVideoDriver.h"
|
||||||
#include "IFileSystem.h"
|
#include "IFileSystem.h"
|
||||||
#include "SAnimatedMesh.h"
|
|
||||||
#include "CMeshCache.h"
|
#include "CMeshCache.h"
|
||||||
#include "IGUIEnvironment.h"
|
#include "IGUIEnvironment.h"
|
||||||
#include "IMaterialRenderer.h"
|
#include "IMaterialRenderer.h"
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
#include "CXMeshFileLoader.h"
|
#include "CXMeshFileLoader.h"
|
||||||
#include "SkinnedMesh.h"
|
#include "SkinnedMesh.h"
|
||||||
|
#include "Transform.h"
|
||||||
#include "os.h"
|
#include "os.h"
|
||||||
|
|
||||||
#include "fast_atof.h"
|
#include "fast_atof.h"
|
||||||
|
@ -513,6 +514,7 @@ bool CXMeshFileLoader::parseDataObjectFrame(SkinnedMesh::SJoint *Parent)
|
||||||
if (n.has_value()) {
|
if (n.has_value()) {
|
||||||
JointID = *n;
|
JointID = *n;
|
||||||
joint = AnimatedMesh->getAllJoints()[JointID];
|
joint = AnimatedMesh->getAllJoints()[JointID];
|
||||||
|
joint->setParent(Parent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -527,8 +529,6 @@ bool CXMeshFileLoader::parseDataObjectFrame(SkinnedMesh::SJoint *Parent)
|
||||||
#ifdef _XREADER_DEBUG
|
#ifdef _XREADER_DEBUG
|
||||||
os::Printer::log("using joint ", name.c_str(), ELL_DEBUG);
|
os::Printer::log("using joint ", name.c_str(), ELL_DEBUG);
|
||||||
#endif
|
#endif
|
||||||
if (Parent)
|
|
||||||
Parent->Children.push_back(joint);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now inside a frame.
|
// Now inside a frame.
|
||||||
|
@ -552,12 +552,10 @@ bool CXMeshFileLoader::parseDataObjectFrame(SkinnedMesh::SJoint *Parent)
|
||||||
if (!parseDataObjectFrame(joint))
|
if (!parseDataObjectFrame(joint))
|
||||||
return false;
|
return false;
|
||||||
} else if (objectName == "FrameTransformMatrix") {
|
} else if (objectName == "FrameTransformMatrix") {
|
||||||
if (!parseDataObjectTransformationMatrix(joint->LocalMatrix))
|
core::matrix4 matrix;
|
||||||
|
if (!parseDataObjectTransformationMatrix(matrix))
|
||||||
return false;
|
return false;
|
||||||
|
joint->transform = matrix;
|
||||||
// joint->LocalAnimatedMatrix
|
|
||||||
// joint->LocalAnimatedMatrix.makeInverse();
|
|
||||||
// joint->LocalMatrix=tmp*joint->LocalAnimatedMatrix;
|
|
||||||
} else if (objectName == "Mesh") {
|
} else if (objectName == "Mesh") {
|
||||||
/*
|
/*
|
||||||
frame.Meshes.push_back(SXMesh());
|
frame.Meshes.push_back(SXMesh());
|
||||||
|
|
|
@ -7,7 +7,15 @@
|
||||||
#include "CBoneSceneNode.h"
|
#include "CBoneSceneNode.h"
|
||||||
#include "IAnimatedMeshSceneNode.h"
|
#include "IAnimatedMeshSceneNode.h"
|
||||||
#include "SSkinMeshBuffer.h"
|
#include "SSkinMeshBuffer.h"
|
||||||
|
#include "Transform.h"
|
||||||
|
#include "aabbox3d.h"
|
||||||
|
#include "irrMath.h"
|
||||||
|
#include "matrix4.h"
|
||||||
#include "os.h"
|
#include "os.h"
|
||||||
|
#include "vector3d.h"
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <variant>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
|
@ -48,154 +56,55 @@ void SkinnedMesh::setAnimationSpeed(f32 fps)
|
||||||
FramesPerSecond = fps;
|
FramesPerSecond = fps;
|
||||||
}
|
}
|
||||||
|
|
||||||
//! returns the animated mesh based
|
|
||||||
IMesh *SkinnedMesh::getMesh(f32 frame)
|
|
||||||
{
|
|
||||||
// animate(frame,startFrameLoop, endFrameLoop);
|
|
||||||
if (frame == -1)
|
|
||||||
return this;
|
|
||||||
|
|
||||||
animateMesh(frame);
|
|
||||||
skinMesh();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
//--------------------------------------------------------------------------
|
|
||||||
// Keyframe Animation
|
// Keyframe Animation
|
||||||
//--------------------------------------------------------------------------
|
|
||||||
|
|
||||||
//! Animates joints based on frame input
|
|
||||||
void SkinnedMesh::animateMesh(f32 frame)
|
using VariantTransform = SkinnedMesh::SJoint::VariantTransform;
|
||||||
|
std::vector<VariantTransform> SkinnedMesh::animateMesh(f32 frame)
|
||||||
{
|
{
|
||||||
if (!HasAnimation || LastAnimatedFrame == frame)
|
assert(HasAnimation);
|
||||||
return;
|
std::vector<VariantTransform> result;
|
||||||
|
result.reserve(AllJoints.size());
|
||||||
LastAnimatedFrame = frame;
|
for (auto *joint : AllJoints)
|
||||||
SkinnedLastFrame = false;
|
result.push_back(joint->animate(frame));
|
||||||
|
return result;
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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()
|
core::aabbox3df SkinnedMesh::calculateBoundingBox(
|
||||||
|
const std::vector<core::matrix4> &global_transforms)
|
||||||
{
|
{
|
||||||
for (auto *joint : AllJoints) {
|
assert(global_transforms.size() == AllJoints.size());
|
||||||
// Could be faster:
|
core::aabbox3df result = StaticPartsBox;
|
||||||
|
// skeletal animation
|
||||||
if (!joint->keys.empty()) {
|
for (u16 i = 0; i < AllJoints.size(); ++i) {
|
||||||
joint->GlobalSkinningSpace = false;
|
auto box = AllJoints[i]->LocalBoundingBox;
|
||||||
|
global_transforms[i].transformBoxEx(box);
|
||||||
// IRR_TEST_BROKEN_QUATERNION_USE: TODO - switched to getMatrix_transposed instead of getMatrix for downward compatibility.
|
result.addInternalBox(box);
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SkinnedLastFrame = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SkinnedMesh::buildAllGlobalAnimatedMatrices(SJoint *joint, SJoint *parentJoint)
|
|
||||||
{
|
|
||||||
if (!joint) {
|
|
||||||
for (auto *rootJoint : RootJoints)
|
|
||||||
buildAllGlobalAnimatedMatrices(rootJoint, 0);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
// Find global matrix...
|
|
||||||
if (!parentJoint || joint->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)
|
|
||||||
return;
|
|
||||||
|
|
||||||
//----------------
|
|
||||||
// This is marked as "Temp!". A shiny dubloon to whomever can tell me why.
|
|
||||||
buildAllGlobalAnimatedMatrices();
|
|
||||||
//-----------------
|
|
||||||
|
|
||||||
SkinnedLastFrame = true;
|
|
||||||
if (!HardwareSkinning) {
|
|
||||||
// rigid animation
|
// rigid animation
|
||||||
for (auto *joint : AllJoints) {
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Software Skinning
|
||||||
|
|
||||||
|
void SkinnedMesh::skinMesh(const std::vector<core::matrix4> &global_matrices)
|
||||||
|
{
|
||||||
|
if (!HasAnimation)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// rigid animation
|
||||||
|
for (size_t i = 0; i < AllJoints.size(); ++i) {
|
||||||
|
auto *joint = AllJoints[i];
|
||||||
for (u32 attachedMeshIdx : joint->AttachedMeshes) {
|
for (u32 attachedMeshIdx : joint->AttachedMeshes) {
|
||||||
SSkinMeshBuffer *Buffer = (*SkinningBuffers)[attachedMeshIdx];
|
SSkinMeshBuffer *Buffer = (*SkinningBuffers)[attachedMeshIdx];
|
||||||
Buffer->Transformation = joint->GlobalAnimatedMatrix;
|
Buffer->Transformation = global_matrices[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,27 +113,20 @@ void SkinnedMesh::skinMesh()
|
||||||
std::fill(buf.begin(), buf.end(), false);
|
std::fill(buf.begin(), buf.end(), false);
|
||||||
|
|
||||||
// skin starting with the root joints
|
// skin starting with the root joints
|
||||||
for (auto *rootJoint : RootJoints)
|
for (size_t i = 0; i < AllJoints.size(); ++i) {
|
||||||
skinJoint(rootJoint, 0);
|
auto *joint = AllJoints[i];
|
||||||
|
if (joint->Weights.empty())
|
||||||
|
continue;
|
||||||
|
|
||||||
for (auto *buffer : *SkinningBuffers)
|
// Find this joints pull on vertices
|
||||||
buffer->setDirty(EBT_VERTEX);
|
|
||||||
}
|
|
||||||
updateBoundingBox();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SkinnedMesh::skinJoint(SJoint *joint, SJoint *parentJoint)
|
|
||||||
{
|
|
||||||
if (joint->Weights.size()) {
|
|
||||||
// Find this joints pull on vertices...
|
|
||||||
// Note: It is assumed that the global inversed matrix has been calculated at this point.
|
// 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;
|
core::vector3df thisVertexMove, thisNormalMove;
|
||||||
|
|
||||||
auto &buffersUsed = *SkinningBuffers;
|
auto &buffersUsed = *SkinningBuffers;
|
||||||
|
|
||||||
// Skin Vertices Positions and Normals...
|
// Skin Vertices, Positions and Normals
|
||||||
for (const auto &weight : joint->Weights) {
|
for (const auto &weight : joint->Weights) {
|
||||||
// Pull this vertex...
|
// Pull this vertex...
|
||||||
jointVertexPull.transformVect(thisVertexMove, weight.StaticPos);
|
jointVertexPull.transformVect(thisVertexMove, weight.StaticPos);
|
||||||
|
@ -251,14 +153,11 @@ void SkinnedMesh::skinJoint(SJoint *joint, SJoint *parentJoint)
|
||||||
|
|
||||||
//*(weight._Pos) += thisVertexMove * weight.strength;
|
//*(weight._Pos) += thisVertexMove * weight.strength;
|
||||||
}
|
}
|
||||||
|
|
||||||
buffersUsed[weight.buffer_id]->boundingBoxNeedsRecalculated();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skin all children
|
for (auto *buffer : *SkinningBuffers)
|
||||||
for (auto *childJoint : joint->Children)
|
buffer->setDirty(EBT_VERTEX);
|
||||||
skinJoint(childJoint, joint);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//! Gets joint count.
|
//! Gets joint count.
|
||||||
|
@ -310,7 +209,7 @@ IMeshBuffer *SkinnedMesh::getMeshBuffer(const video::SMaterial &material) const
|
||||||
if (LocalBuffers[i]->getMaterial() == material)
|
if (LocalBuffers[i]->getMaterial() == material)
|
||||||
return LocalBuffers[i];
|
return LocalBuffers[i];
|
||||||
}
|
}
|
||||||
return 0;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 SkinnedMesh::getTextureSlot(u32 meshbufNr) const
|
u32 SkinnedMesh::getTextureSlot(u32 meshbufNr) const
|
||||||
|
@ -337,29 +236,6 @@ void SkinnedMesh::setDirty(E_BUFFER_TYPE buffer)
|
||||||
LocalBuffers[i]->setDirty(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()
|
void SkinnedMesh::refreshJointCache()
|
||||||
{
|
{
|
||||||
// copy cache from the mesh...
|
// copy cache from the mesh...
|
||||||
|
@ -384,72 +260,51 @@ void SkinnedMesh::resetAnimation()
|
||||||
LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal = weight.StaticNormal;
|
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
|
// Note that the joints are topologically sorted.
|
||||||
return;
|
for (u16 i = 0; i < AllJoints.size(); ++i) {
|
||||||
|
if (auto parent_id = AllJoints[i]->ParentJointID) {
|
||||||
// Go through the root bones
|
matrices[i] = matrices[*parent_id] * matrices[i];
|
||||||
if (!joint) {
|
|
||||||
for (auto *rootJoint : RootJoints)
|
|
||||||
calculateGlobalMatrices(rootJoint, nullptr);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
for (auto *joint : AllJoints) {
|
||||||
if (!joint->keys.empty()) {
|
if (!joint->keys.empty()) {
|
||||||
HasAnimation = true;
|
return true;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// meshes with weights, are still counted as animated for ragdolls, etc
|
// meshes with weights are animatable
|
||||||
if (!HasAnimation) {
|
|
||||||
for (auto *joint : AllJoints) {
|
for (auto *joint : AllJoints) {
|
||||||
if (joint->Weights.size()) {
|
if (!joint->Weights.empty()) {
|
||||||
HasAnimation = true;
|
return true;
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (HasAnimation) {
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SkinnedMesh::prepareForSkinning()
|
||||||
|
{
|
||||||
|
HasAnimation = checkForAnimation();
|
||||||
|
if (!HasAnimation || PreparedForSkinning)
|
||||||
|
return;
|
||||||
|
|
||||||
|
PreparedForSkinning = true;
|
||||||
|
|
||||||
EndFrame = 0.0f;
|
EndFrame = 0.0f;
|
||||||
for (const auto *joint : AllJoints) {
|
for (const auto *joint : AllJoints) {
|
||||||
EndFrame = std::max(EndFrame, joint->keys.getEndFrame());
|
EndFrame = std::max(EndFrame, joint->keys.getEndFrame());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (HasAnimation && !PreparedForSkinning) {
|
|
||||||
PreparedForSkinning = true;
|
|
||||||
|
|
||||||
// check for bugs:
|
|
||||||
for (auto *joint : AllJoints) {
|
for (auto *joint : AllJoints) {
|
||||||
for (auto &weight : joint->Weights) {
|
for (auto &weight : joint->Weights) {
|
||||||
const u16 buffer_id = weight.buffer_id;
|
const u16 buffer_id = weight.buffer_id;
|
||||||
|
@ -466,14 +321,11 @@ void SkinnedMesh::checkForAnimation()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// An array used in skinning
|
|
||||||
|
|
||||||
for (u32 i = 0; i < Vertices_Moved.size(); ++i)
|
for (u32 i = 0; i < Vertices_Moved.size(); ++i)
|
||||||
for (u32 j = 0; j < Vertices_Moved[i].size(); ++j)
|
for (u32 j = 0; j < Vertices_Moved[i].size(); ++j)
|
||||||
Vertices_Moved[i][j] = false;
|
Vertices_Moved[i][j] = false;
|
||||||
|
|
||||||
// For skinning: cache weight values for speed
|
// For skinning: cache weight values for speed
|
||||||
|
|
||||||
for (auto *joint : AllJoints) {
|
for (auto *joint : AllJoints) {
|
||||||
for (auto &weight : joint->Weights) {
|
for (auto &weight : joint->Weights) {
|
||||||
const u16 buffer_id = weight.buffer_id;
|
const u16 buffer_id = weight.buffer_id;
|
||||||
|
@ -482,15 +334,118 @@ void SkinnedMesh::checkForAnimation()
|
||||||
weight.Moved = &Vertices_Moved[buffer_id][vertex_id];
|
weight.Moved = &Vertices_Moved[buffer_id][vertex_id];
|
||||||
weight.StaticPos = LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos;
|
weight.StaticPos = LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos;
|
||||||
weight.StaticNormal = LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal;
|
weight.StaticNormal = LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal;
|
||||||
|
|
||||||
// weight._Pos=&Buffers[buffer_id]->getVertex(vertex_id)->Pos;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// normalize weights
|
|
||||||
normalizeWeights();
|
normalizeWeights();
|
||||||
|
|
||||||
|
for (auto *joint : AllJoints) {
|
||||||
|
joint->keys.cleanup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
SkinnedLastFrame = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//! called by loader after populating with mesh and bone data
|
//! 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);
|
os::Printer::log("Skinned Mesh - finalize", ELL_DEBUG);
|
||||||
|
|
||||||
// Make sure we recalc the next frame
|
topoSortJoints();
|
||||||
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...
|
|
||||||
|
|
||||||
|
// Set array sizes
|
||||||
for (u32 i = 0; i < LocalBuffers.size(); ++i) {
|
for (u32 i = 0; i < LocalBuffers.size(); ++i) {
|
||||||
Vertices_Moved.emplace_back(LocalBuffers[i]->getVertexCount());
|
Vertices_Moved.emplace_back(LocalBuffers[i]->getVertexCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
checkForAnimation();
|
prepareForSkinning();
|
||||||
|
|
||||||
if (HasAnimation) {
|
std::vector<core::matrix4> matrices;
|
||||||
|
matrices.reserve(AllJoints.size());
|
||||||
for (auto *joint : AllJoints) {
|
for (auto *joint : AllJoints) {
|
||||||
joint->keys.cleanup();
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Needed for animation and skinning...
|
|
||||||
|
|
||||||
calculateGlobalMatrices(0, 0);
|
|
||||||
|
|
||||||
// rigid animation for non animated meshes
|
// rigid animation for non animated meshes
|
||||||
for (auto *joint : AllJoints) {
|
|
||||||
for (u32 attachedMeshIdx : joint->AttachedMeshes) {
|
for (u32 attachedMeshIdx : joint->AttachedMeshes) {
|
||||||
SSkinMeshBuffer *Buffer = (*SkinningBuffers)[attachedMeshIdx];
|
SSkinMeshBuffer *Buffer = (*SkinningBuffers)[attachedMeshIdx];
|
||||||
Buffer->Transformation = joint->GlobalAnimatedMatrix;
|
Buffer->Transformation = matrices[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// calculate bounding box
|
recalculateBaseBoundingBoxes();
|
||||||
if (LocalBuffers.empty())
|
StaticPoseBox = calculateBoundingBox(matrices);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
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 *SkinnedMeshBuilder::addMeshBuffer()
|
||||||
{
|
{
|
||||||
scene::SSkinMeshBuffer *buffer = new scene::SSkinMeshBuffer();
|
scene::SSkinMeshBuffer *buffer = new scene::SSkinMeshBuffer();
|
||||||
|
@ -607,14 +508,10 @@ void SkinnedMeshBuilder::addMeshBuffer(SSkinMeshBuffer *meshbuf)
|
||||||
SkinnedMesh::SJoint *SkinnedMeshBuilder::addJoint(SJoint *parent)
|
SkinnedMesh::SJoint *SkinnedMeshBuilder::addJoint(SJoint *parent)
|
||||||
{
|
{
|
||||||
SJoint *joint = new SJoint;
|
SJoint *joint = new SJoint;
|
||||||
|
joint->setParent(parent);
|
||||||
|
|
||||||
|
joint->JointID = AllJoints.size();
|
||||||
AllJoints.push_back(joint);
|
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;
|
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()
|
void SkinnedMesh::convertMeshToTangents()
|
||||||
{
|
{
|
||||||
// now calculate tangents
|
// now calculate tangents
|
||||||
|
|
|
@ -916,12 +916,7 @@ struct Node {
|
||||||
std::optional<std::size_t> skin;
|
std::optional<std::size_t> skin;
|
||||||
std::optional<std::vector<double>> weights;
|
std::optional<std::vector<double>> weights;
|
||||||
Node(const Json::Value &o)
|
Node(const Json::Value &o)
|
||||||
: transform(Matrix {
|
: transform(TRS{})
|
||||||
1, 0, 0, 0,
|
|
||||||
0, 1, 0, 0,
|
|
||||||
0, 0, 1, 0,
|
|
||||||
0, 0, 0, 1
|
|
||||||
})
|
|
||||||
{
|
{
|
||||||
check(o.isObject());
|
check(o.isObject());
|
||||||
if (o.isMember("camera")) {
|
if (o.isMember("camera")) {
|
||||||
|
|
|
@ -178,15 +178,6 @@ static void setBillboardTextureMatrix(scene::IBillboardSceneNode *bill,
|
||||||
matrix.setTextureScale(txs, tys);
|
matrix.setTextureScale(txs, tys);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evaluate transform chain recursively; irrlicht does not do this for us
|
|
||||||
static void updatePositionRecursive(scene::ISceneNode *node)
|
|
||||||
{
|
|
||||||
scene::ISceneNode *parent = node->getParent();
|
|
||||||
if (parent)
|
|
||||||
updatePositionRecursive(parent);
|
|
||||||
node->updateAbsolutePosition();
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool logOnce(const std::ostringstream &from, std::ostream &log_to)
|
static bool logOnce(const std::ostringstream &from, std::ostream &log_to)
|
||||||
{
|
{
|
||||||
thread_local std::vector<u64> logged;
|
thread_local std::vector<u64> logged;
|
||||||
|
@ -682,7 +673,6 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr)
|
||||||
m_animated_meshnode = m_smgr->addAnimatedMeshSceneNode(mesh, m_matrixnode);
|
m_animated_meshnode = m_smgr->addAnimatedMeshSceneNode(mesh, m_matrixnode);
|
||||||
m_animated_meshnode->grab();
|
m_animated_meshnode->grab();
|
||||||
mesh->drop(); // The scene node took hold of it
|
mesh->drop(); // The scene node took hold of it
|
||||||
m_animated_meshnode->animateJoints(); // Needed for some animations
|
|
||||||
m_animated_meshnode->setScale(m_prop.visual_size);
|
m_animated_meshnode->setScale(m_prop.visual_size);
|
||||||
|
|
||||||
// set vertex colors to ensure alpha is set
|
// set vertex colors to ensure alpha is set
|
||||||
|
@ -693,6 +683,21 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr)
|
||||||
m_animated_meshnode->forEachMaterial([this] (auto &mat) {
|
m_animated_meshnode->forEachMaterial([this] (auto &mat) {
|
||||||
mat.BackfaceCulling = m_prop.backface_culling;
|
mat.BackfaceCulling = m_prop.backface_culling;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
m_animated_meshnode->setOnAnimateCallback([&](f32 dtime) {
|
||||||
|
for (auto &it : m_bone_override) {
|
||||||
|
auto* bone = m_animated_meshnode->getJointNode(it.first.c_str());
|
||||||
|
if (!bone)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
BoneOverride &props = it.second;
|
||||||
|
props.dtime_passed += dtime;
|
||||||
|
|
||||||
|
bone->setPosition(props.getPosition(bone->getPosition()));
|
||||||
|
bone->setRotation(props.getRotationEulerDeg(bone->getRotation()));
|
||||||
|
bone->setScale(props.getScale(bone->getScale()));
|
||||||
|
}
|
||||||
|
});
|
||||||
} else
|
} else
|
||||||
errorstream<<"GenericCAO::addToScene(): Could not load mesh "<<m_prop.mesh<<std::endl;
|
errorstream<<"GenericCAO::addToScene(): Could not load mesh "<<m_prop.mesh<<std::endl;
|
||||||
break;
|
break;
|
||||||
|
@ -783,7 +788,6 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr)
|
||||||
updateMarker();
|
updateMarker();
|
||||||
updateNodePos();
|
updateNodePos();
|
||||||
updateAnimation();
|
updateAnimation();
|
||||||
updateBones(.0f);
|
|
||||||
updateAttachments();
|
updateAttachments();
|
||||||
setNodeLight(m_last_light);
|
setNodeLight(m_last_light);
|
||||||
updateMeshCulling();
|
updateMeshCulling();
|
||||||
|
@ -1174,18 +1178,6 @@ void GenericCAO::step(float dtime, ClientEnvironment *env)
|
||||||
rot_translator.val_current = m_rotation;
|
rot_translator.val_current = m_rotation;
|
||||||
updateNodePos();
|
updateNodePos();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_animated_meshnode) {
|
|
||||||
// Everything must be updated; the whole transform
|
|
||||||
// chain as well as the animated mesh node.
|
|
||||||
// Otherwise, bone attachments would be relative to
|
|
||||||
// a position that's one frame old.
|
|
||||||
if (m_matrixnode)
|
|
||||||
updatePositionRecursive(m_matrixnode);
|
|
||||||
m_animated_meshnode->updateAbsolutePosition();
|
|
||||||
m_animated_meshnode->animateJoints();
|
|
||||||
updateBones(dtime);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void setMeshBufferTextureCoords(scene::IMeshBuffer *buf, const v2f *uv, u32 count)
|
static void setMeshBufferTextureCoords(scene::IMeshBuffer *buf, const v2f *uv, u32 count)
|
||||||
|
@ -1394,44 +1386,6 @@ void GenericCAO::updateAnimationSpeed()
|
||||||
m_animated_meshnode->setAnimationSpeed(m_animation_speed);
|
m_animated_meshnode->setAnimationSpeed(m_animation_speed);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GenericCAO::updateBones(f32 dtime)
|
|
||||||
{
|
|
||||||
if (!m_animated_meshnode)
|
|
||||||
return;
|
|
||||||
if (m_bone_override.empty()) {
|
|
||||||
m_animated_meshnode->setJointMode(scene::EJUOR_NONE);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_animated_meshnode->setJointMode(scene::EJUOR_CONTROL); // To write positions to the mesh on render
|
|
||||||
for (auto &it : m_bone_override) {
|
|
||||||
std::string bone_name = it.first;
|
|
||||||
scene::IBoneSceneNode* bone = m_animated_meshnode->getJointNode(bone_name.c_str());
|
|
||||||
if (!bone)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
BoneOverride &props = it.second;
|
|
||||||
props.dtime_passed += dtime;
|
|
||||||
|
|
||||||
bone->setPosition(props.getPosition(bone->getPosition()));
|
|
||||||
bone->setRotation(props.getRotationEulerDeg(bone->getRotation()));
|
|
||||||
bone->setScale(props.getScale(bone->getScale()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// The following is needed for set_bone_pos to propagate to
|
|
||||||
// attached objects correctly.
|
|
||||||
// Irrlicht ought to do this, but doesn't when using EJUOR_CONTROL.
|
|
||||||
for (u32 i = 0; i < m_animated_meshnode->getJointCount(); ++i) {
|
|
||||||
auto bone = m_animated_meshnode->getJointNode(i);
|
|
||||||
// Look for the root bone.
|
|
||||||
if (bone && bone->getParent() == m_animated_meshnode) {
|
|
||||||
// Update entire skeleton.
|
|
||||||
bone->updateAbsolutePositionOfAllChildren();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GenericCAO::updateAttachments()
|
void GenericCAO::updateAttachments()
|
||||||
{
|
{
|
||||||
ClientActiveObject *parent = getParent();
|
ClientActiveObject *parent = getParent();
|
||||||
|
@ -1747,7 +1701,6 @@ void GenericCAO::processMessage(const std::string &data)
|
||||||
} else {
|
} else {
|
||||||
m_bone_override[bone] = props;
|
m_bone_override[bone] = props;
|
||||||
}
|
}
|
||||||
// updateBones(); now called every step
|
|
||||||
} else if (cmd == AO_CMD_ATTACH_TO) {
|
} else if (cmd == AO_CMD_ATTACH_TO) {
|
||||||
u16 parent_id = readS16(is);
|
u16 parent_id = readS16(is);
|
||||||
std::string bone = deSerializeString16(is);
|
std::string bone = deSerializeString16(is);
|
||||||
|
|
|
@ -286,8 +286,6 @@ public:
|
||||||
|
|
||||||
void updateAnimationSpeed();
|
void updateAnimationSpeed();
|
||||||
|
|
||||||
void updateBones(f32 dtime);
|
|
||||||
|
|
||||||
void processMessage(const std::string &data) override;
|
void processMessage(const std::string &data) override;
|
||||||
|
|
||||||
bool directReportPunch(v3f dir, const ItemStack *punchitem,
|
bool directReportPunch(v3f dir, const ItemStack *punchitem,
|
||||||
|
|
|
@ -10,10 +10,9 @@
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <IAnimatedMesh.h>
|
#include <IAnimatedMesh.h>
|
||||||
#include <SAnimatedMesh.h>
|
|
||||||
#include <IAnimatedMeshSceneNode.h>
|
#include <IAnimatedMeshSceneNode.h>
|
||||||
#include "S3DVertex.h"
|
#include "S3DVertex.h"
|
||||||
#include "SMesh.h"
|
#include <SMesh.h>
|
||||||
#include "SMeshBuffer.h"
|
#include "SMeshBuffer.h"
|
||||||
|
|
||||||
inline static void applyShadeFactor(video::SColor& color, float factor)
|
inline static void applyShadeFactor(video::SColor& color, float factor)
|
||||||
|
@ -97,11 +96,8 @@ scene::IAnimatedMesh* createCubeMesh(v3f scale)
|
||||||
mesh->addMeshBuffer(buf);
|
mesh->addMeshBuffer(buf);
|
||||||
buf->drop();
|
buf->drop();
|
||||||
}
|
}
|
||||||
|
scaleMesh(mesh, scale); // also recalculates bounding box
|
||||||
scene::SAnimatedMesh *anim_mesh = new scene::SAnimatedMesh(mesh);
|
return mesh;
|
||||||
mesh->drop();
|
|
||||||
scaleMesh(anim_mesh, scale); // also recalculates bounding box
|
|
||||||
return anim_mesh;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename F>
|
template<typename F>
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
|
|
||||||
#include "nodedef.h"
|
#include "nodedef.h"
|
||||||
|
|
||||||
#include "SAnimatedMesh.h"
|
|
||||||
#include "itemdef.h"
|
#include "itemdef.h"
|
||||||
#if CHECK_CLIENT_BUILD()
|
#if CHECK_CLIENT_BUILD()
|
||||||
#include "client/mesh.h"
|
#include "client/mesh.h"
|
||||||
|
@ -964,13 +963,6 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc
|
||||||
// Note: By freshly reading, we get an unencumbered mesh.
|
// Note: By freshly reading, we get an unencumbered mesh.
|
||||||
if (scene::IMesh *src_mesh = client->getMesh(mesh)) {
|
if (scene::IMesh *src_mesh = client->getMesh(mesh)) {
|
||||||
bool apply_bs = false;
|
bool apply_bs = false;
|
||||||
// For frame-animated meshes, always get the first frame,
|
|
||||||
// which holds a model for which we can eventually get the static pose.
|
|
||||||
while (auto *src_meshes = dynamic_cast<scene::SAnimatedMesh *>(src_mesh)) {
|
|
||||||
src_mesh = src_meshes->getMesh(0.0f);
|
|
||||||
src_mesh->grab();
|
|
||||||
src_meshes->drop();
|
|
||||||
}
|
|
||||||
if (auto *skinned_mesh = dynamic_cast<scene::SkinnedMesh *>(src_mesh)) {
|
if (auto *skinned_mesh = dynamic_cast<scene::SkinnedMesh *>(src_mesh)) {
|
||||||
// Compatibility: Animated meshes, as well as static gltf meshes, are not scaled by BS.
|
// Compatibility: Animated meshes, as well as static gltf meshes, are not scaled by BS.
|
||||||
// See https://github.com/luanti-org/luanti/pull/16112#issuecomment-2881860329
|
// See https://github.com/luanti-org/luanti/pull/16112#issuecomment-2881860329
|
||||||
|
|
|
@ -59,6 +59,8 @@ set (UNITTEST_CLIENT_SRCS
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/test_eventmanager.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/test_eventmanager.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/test_gameui.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/test_gameui.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/test_irr_gltf_mesh_loader.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/test_irr_gltf_mesh_loader.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test_irr_x_mesh_loader.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test_irr_matrix4.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/test_mesh_compare.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/test_mesh_compare.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/test_keycode.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/test_keycode.cpp
|
||||||
PARENT_SCOPE)
|
PARENT_SCOPE)
|
||||||
|
|
|
@ -394,37 +394,41 @@ SECTION("simple skin")
|
||||||
const auto joints = csm->getAllJoints();
|
const auto joints = csm->getAllJoints();
|
||||||
REQUIRE(joints.size() == 3);
|
REQUIRE(joints.size() == 3);
|
||||||
|
|
||||||
const auto findJoint = [&](const std::function<bool(SkinnedMesh::SJoint*)> &predicate) {
|
const auto findJoint = [&](const std::function<bool(const SkinnedMesh::SJoint*)> &predicate) {
|
||||||
for (std::size_t i = 0; i < joints.size(); ++i) {
|
for (const auto *joint : joints) {
|
||||||
if (predicate(joints[i])) {
|
if (predicate(joint)) {
|
||||||
return joints[i];
|
return joint;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw std::runtime_error("joint not found");
|
throw std::runtime_error("joint not found");
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check the node hierarchy
|
// Check the node hierarchy
|
||||||
const auto parent = findJoint([](auto joint) {
|
const auto child = findJoint([&](auto *joint) {
|
||||||
return !joint->Children.empty();
|
return !!joint->ParentJointID;
|
||||||
});
|
});
|
||||||
REQUIRE(parent->Children.size() == 1);
|
const auto *parent = joints.at(*child->ParentJointID);
|
||||||
const auto child = parent->Children[0];
|
|
||||||
REQUIRE(child != parent);
|
|
||||||
|
|
||||||
SECTION("transformations are correct")
|
SECTION("transformations are correct")
|
||||||
{
|
{
|
||||||
CHECK(parent->Animatedposition == v3f(0, 0, 0));
|
{
|
||||||
CHECK(parent->Animatedrotation == irr::core::quaternion());
|
const auto &transform = std::get<core::Transform>(parent->transform);
|
||||||
CHECK(parent->Animatedscale == v3f(1, 1, 1));
|
CHECK(transform.translation == v3f(0, 0, 0));
|
||||||
|
CHECK(transform.rotation == irr::core::quaternion());
|
||||||
|
CHECK(transform.scale == v3f(1, 1, 1));
|
||||||
CHECK(parent->GlobalInversedMatrix == irr::core::matrix4());
|
CHECK(parent->GlobalInversedMatrix == irr::core::matrix4());
|
||||||
const v3f childTranslation(0, 1, 0);
|
}
|
||||||
CHECK(child->Animatedposition == childTranslation);
|
{
|
||||||
CHECK(child->Animatedrotation == irr::core::quaternion());
|
const auto &transform = std::get<core::Transform>(child->transform);
|
||||||
CHECK(child->Animatedscale == v3f(1, 1, 1));
|
const v3f translation(0, 1, 0);
|
||||||
|
CHECK(transform.translation == translation);
|
||||||
|
CHECK(transform.rotation == irr::core::quaternion());
|
||||||
|
CHECK(transform.scale == v3f(1, 1, 1));
|
||||||
irr::core::matrix4 inverseBindMatrix;
|
irr::core::matrix4 inverseBindMatrix;
|
||||||
inverseBindMatrix.setTranslation(-childTranslation);
|
inverseBindMatrix.setTranslation(-translation);
|
||||||
CHECK(child->GlobalInversedMatrix == inverseBindMatrix);
|
CHECK(child->GlobalInversedMatrix == inverseBindMatrix);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("weights are correct")
|
SECTION("weights are correct")
|
||||||
{
|
{
|
||||||
|
|
111
src/unittest/test_irr_x_mesh_loader.cpp
Normal file
111
src/unittest/test_irr_x_mesh_loader.cpp
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
// Luanti
|
||||||
|
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
|
||||||
|
#include "catch_amalgamated.hpp"
|
||||||
|
#include "content/subgames.h"
|
||||||
|
#include "filesys.h"
|
||||||
|
|
||||||
|
#include "irrlichttypes.h"
|
||||||
|
#include "irr_ptr.h"
|
||||||
|
|
||||||
|
#include "EDriverTypes.h"
|
||||||
|
#include "IFileSystem.h"
|
||||||
|
#include "IReadFile.h"
|
||||||
|
#include "ISceneManager.h"
|
||||||
|
#include "SkinnedMesh.h"
|
||||||
|
#include "irrlicht.h"
|
||||||
|
|
||||||
|
#include "catch.h"
|
||||||
|
#include "matrix4.h"
|
||||||
|
|
||||||
|
TEST_CASE("x") {
|
||||||
|
|
||||||
|
const auto gamespec = findSubgame("devtest");
|
||||||
|
|
||||||
|
if (!gamespec.isValid())
|
||||||
|
SKIP();
|
||||||
|
|
||||||
|
irr::SIrrlichtCreationParameters p;
|
||||||
|
p.DriverType = video::EDT_NULL;
|
||||||
|
auto *driver = irr::createDeviceEx(p);
|
||||||
|
|
||||||
|
REQUIRE(driver);
|
||||||
|
|
||||||
|
auto *smgr = driver->getSceneManager();
|
||||||
|
const auto loadMesh = [&] (const io::path& filepath) {
|
||||||
|
irr_ptr<io::IReadFile> file(driver->getFileSystem()->createAndOpenFile(filepath));
|
||||||
|
REQUIRE(file);
|
||||||
|
return smgr->getMesh(file.get());
|
||||||
|
};
|
||||||
|
|
||||||
|
const static auto model_stem = gamespec.gamemods_path +
|
||||||
|
DIR_DELIM + "testentities" + DIR_DELIM + "models" + DIR_DELIM + "testentities_";
|
||||||
|
|
||||||
|
SECTION("cool guy") {
|
||||||
|
const auto *mesh = dynamic_cast<irr::scene::SkinnedMesh*>(loadMesh(model_stem + "cool_guy.x"));
|
||||||
|
REQUIRE(mesh);
|
||||||
|
REQUIRE(mesh->getMeshBufferCount() == 1);
|
||||||
|
|
||||||
|
auto getJointId = [&](auto name) {
|
||||||
|
return mesh->getJointNumber(name).value();
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto root = getJointId("Root");
|
||||||
|
const auto armature = getJointId("Armature");
|
||||||
|
const auto armature_body = getJointId("Armature_body");
|
||||||
|
const auto armature_arm_r = getJointId("Armature_arm_r");
|
||||||
|
|
||||||
|
std::vector<core::matrix4> matrices;
|
||||||
|
matrices.reserve(mesh->getJointCount());
|
||||||
|
for (auto *joint : mesh->getAllJoints()) {
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
auto local_matrices = matrices;
|
||||||
|
mesh->calculateGlobalMatrices(matrices);
|
||||||
|
|
||||||
|
SECTION("joints are topologically sorted") {
|
||||||
|
REQUIRE(root < armature);
|
||||||
|
REQUIRE(armature < armature_body);
|
||||||
|
REQUIRE(armature_body < armature_arm_r);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("parents are correct") {
|
||||||
|
const auto get_parent = [&](auto id) {
|
||||||
|
return mesh->getAllJoints()[id]->ParentJointID;
|
||||||
|
};
|
||||||
|
REQUIRE(!get_parent(root));
|
||||||
|
REQUIRE(get_parent(armature).value() == root);
|
||||||
|
REQUIRE(get_parent(armature_body).value() == armature);
|
||||||
|
REQUIRE(get_parent(armature_arm_r).value() == armature_body);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("local matrices are correct") {
|
||||||
|
REQUIRE(local_matrices[root].equals(core::IdentityMatrix));
|
||||||
|
REQUIRE(local_matrices[armature].equals(core::IdentityMatrix));
|
||||||
|
REQUIRE(local_matrices[armature_body] == core::matrix4(
|
||||||
|
-1,0,0,0,
|
||||||
|
0,0,1,0,
|
||||||
|
0,1,0,0,
|
||||||
|
0,2.571201,0,1
|
||||||
|
));
|
||||||
|
REQUIRE(local_matrices[armature_arm_r] == core::matrix4(
|
||||||
|
-0.047733,0.997488,-0.05233,0,
|
||||||
|
0.901521,0.020464,-0.432251,0,
|
||||||
|
-0.430095,-0.067809,-0.900233,
|
||||||
|
0,-0.545315,0,1,1
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("global matrices are correct") {
|
||||||
|
REQUIRE(matrices[armature_body] == local_matrices[armature_body]);
|
||||||
|
REQUIRE(matrices[armature_arm_r] ==
|
||||||
|
matrices[armature_body] * local_matrices[armature_arm_r]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
driver->closeDevice();
|
||||||
|
driver->drop();
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue