Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix and clean up skeletal animation in Irrlicht #15722

Open
wants to merge 28 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions doc/lua_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,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).

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)
to check whether a model is a valid glTF file.

Expand Down
8 changes: 7 additions & 1 deletion games/devtest/mods/testentities/models/LICENSE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,10 @@ Jordach (CC BY-SA 3.0):

Zeg9 (CC BY-SA 3.0):
testentities_lava_flan.x
testentities_lava_flan.png
testentities_lava_flan.png

"Cool Guy":

Irrlicht authors (refer to irr/LICENSE):
testentities_cool_guy.x
testentities_cool_guy.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions games/devtest/mods/testentities/visuals.lua
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,19 @@ core.register_entity("testentities:lava_flan", {
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

-- An entity for testing animated and yaw-modulated sprites
Expand Down
22 changes: 4 additions & 18 deletions irr/include/IAnimatedMesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ namespace scene
//! Interface for an animated mesh.
/** 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 might want to use irr::scene::SAnimatedMesh, irr::scene::SMesh,
irr::scene::SMeshBuffer etc. */
You might want to use irr::scene::SMesh, irr::scene::SMeshBuffer etc.
*/
class IAnimatedMesh : public IMesh
{
public:
Expand All @@ -34,22 +34,8 @@ class IAnimatedMesh : public IMesh
scene node the mesh is instantiated in.*/
virtual void setAnimationSpeed(f32 fps) = 0;

//! Returns the IMesh interface for a frame.
/** \param frame: Frame number, >= 0, <= getMaxFrameNumber()
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;
}
//! Returns the type of the animated mesh. Useful for safe downcasts.
E_ANIMATED_MESH_TYPE getMeshType() const = 0;
};

} // end namespace scene
Expand Down
47 changes: 7 additions & 40 deletions irr/include/IAnimatedMeshSceneNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,35 +12,8 @@ namespace irr
{
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;

//! 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.
class IAnimatedMeshSceneNode : public ISceneNode
{
Expand Down Expand Up @@ -120,11 +93,10 @@ class IAnimatedMeshSceneNode : public ISceneNode
/** When true the animations are played looped */
virtual bool getLoopMode() const = 0;

//! Sets a callback interface which will be called if an animation playback has ended.
/** Set this to 0 to disable the callback again.
Please note that this will only be called when in non looped
mode, see IAnimatedMeshSceneNode::setLoopMode(). */
virtual void setAnimationEndCallback(IAnimationEndCallBack *callback = 0) = 0;
//! Will be called right after the joints have been animated,
//! but before the transforms have been propagated recursively to children.
virtual void setOnAnimateCallback(
const std::function<void(f32 dtime)> &cb) = 0;

//! 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
Expand All @@ -139,20 +111,15 @@ class IAnimatedMeshSceneNode : public ISceneNode
virtual void setMesh(IAnimatedMesh *mesh) = 0;

//! Returns the current mesh
virtual IAnimatedMesh *getMesh(void) = 0;

//! Set how the joints should be updated on render
virtual void setJointMode(E_JOINT_UPDATE_ON_RENDER mode) = 0;
virtual IAnimatedMesh *getMesh() = 0;

//! Sets the transition time in seconds
/** Note: This needs to enable joints, and setJointmode set to
EJUOR_CONTROL. You must call animateJoints(), or the mesh will
not animate. */
/** Note: You must call animateJoints(), or the mesh will not animate. */
virtual void setTransitionTime(f32 Time) = 0;

//! animates the joints in the mesh based on the current frame.
/** Also takes in to account transitions. */
virtual void animateJoints(bool CalculateAbsolutePositions = true) = 0;
virtual void animateJoints() = 0;

//! render mesh ignoring its transformation.
/** Culling is unaffected. */
Expand Down
96 changes: 26 additions & 70 deletions irr/include/IBoneSceneNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,85 +11,41 @@ namespace irr
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.
/** Used with SkinnedMesh and IAnimatedMeshSceneNode. */
class IBoneSceneNode : public ISceneNode
{
public:
IBoneSceneNode(ISceneNode *parent, ISceneManager *mgr, s32 id = -1) :
ISceneNode(parent, mgr, id), positionHint(-1), scaleHint(-1), rotationHint(-1) {}

//! Get the index of the bone
virtual u32 getBoneIndex() const = 0;

//! Sets the animation mode of the bone.
/** \return True if successful. (Unused) */
virtual bool setAnimationMode(E_BONE_ANIMATION_MODE mode) = 0;

//! Gets the current animation mode of the bone
virtual E_BONE_ANIMATION_MODE getAnimationMode() const = 0;

//! Get the axis aligned bounding box of this node
const core::aabbox3d<f32> &getBoundingBox() const override = 0;

//! Returns the relative transformation of the scene node.
// virtual core::matrix4 getRelativeTransformation() const = 0;

//! The animation method.
void OnAnimate(u32 timeMs) override = 0;
IBoneSceneNode(ISceneNode *parent, ISceneManager *mgr,
s32 id = -1, u32 boneIndex = 0,
const std::optional<std::string> &boneName = std::nullopt)
:
ISceneNode(parent, mgr, id),
BoneIndex(boneIndex)
{
setName(boneName);
}

//! Returns the index of the bone
u32 getBoneIndex() const
{
return BoneIndex;
}

//! returns the axis aligned bounding box of this node
const core::aabbox3d<f32> &getBoundingBox() const override
{
return Box;
}

const u32 BoneIndex;

// Bogus box; bone scene nodes are not rendered anyways.
static constexpr core::aabbox3d<f32> Box = {{0, 0, 0}};

//! The render method.
/** Does nothing as bones are not visible. */
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
Expand Down
36 changes: 1 addition & 35 deletions irr/include/IMesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,38 +20,6 @@ enum E_ANIMATED_MESH_TYPE
//! Unknown animated mesh type.
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
EAMT_SKINNED,

Expand Down Expand Up @@ -119,9 +87,7 @@ class IMesh : public virtual IReferenceCounted
virtual void setDirty(E_BUFFER_TYPE buffer = EBT_VERTEX_AND_INDEX) = 0;

//! Returns the type of the meshes.
/** This is useful for making a safe downcast. For example,
if getMeshType() returns EAMT_MD2 it's safe to cast the
IMesh to IAnimatedMeshMD2.
/** This is useful for making a safe downcast.
Note: It's no longer just about animated meshes, that name has just historical reasons.
\returns Type of the mesh */
virtual E_ANIMATED_MESH_TYPE getMeshType() const
Expand Down
20 changes: 0 additions & 20 deletions irr/include/IMeshManipulator.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,26 +66,6 @@ class IMeshManipulator : public virtual IReferenceCounted
IReferenceCounted::drop() for more information. */
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
/** \param func A functor defining the mesh manipulation.
\param buffer The Meshbuffer to apply the manipulator to.
Expand Down
2 changes: 1 addition & 1 deletion irr/include/IMeshSceneNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class IMeshSceneNode : public ISceneNode

//! Get the currently defined mesh for display.
/** \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.
/** In this way it is possible to change the materials of a mesh
Expand Down
14 changes: 5 additions & 9 deletions irr/include/ISceneNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,16 +93,12 @@ class ISceneNode : virtual public IReferenceCounted
\param timeMs Current time in milliseconds. */
virtual void OnAnimate(u32 timeMs)
{
if (IsVisible) {
// update absolute position
updateAbsolutePosition();

// perform the post render process on all children
if (!IsVisible && Children.empty())
return;

ISceneNodeList::iterator it = Children.begin();
for (; it != Children.end(); ++it)
(*it)->OnAnimate(timeMs);
}
updateAbsolutePosition();
for (auto *child : Children)
child->OnAnimate(timeMs);
}

//! Renders the node.
Expand Down
Loading
Loading