mirror of
https://github.com/luanti-org/luanti.git
synced 2025-11-28 11:45:29 +01:00
Refactor skinned mesh weight data structure (#16655)
This commit is contained in:
@@ -19,19 +19,6 @@ public:
|
||||
//! Gets the maximum frame number, 0 if the mesh is static.
|
||||
virtual f32 getMaxFrameNumber() const = 0;
|
||||
|
||||
//! Gets the animation speed of the animated mesh.
|
||||
/** \return The number of frames per second to play the
|
||||
animation with by default. If the amount is 0,
|
||||
it is a static, non animated mesh. */
|
||||
virtual f32 getAnimationSpeed() const = 0;
|
||||
|
||||
//! Sets the animation speed of the animated mesh.
|
||||
/** \param fps Number of frames per second to play the
|
||||
animation with by default. If the amount is 0,
|
||||
it is not animated. The actual speed is set in the
|
||||
scene node the mesh is instantiated in.*/
|
||||
virtual void setAnimationSpeed(f32 fps) = 0;
|
||||
|
||||
//! Returns the type of the animated mesh. Useful for safe downcasts.
|
||||
E_ANIMATED_MESH_TYPE getMeshType() const = 0;
|
||||
};
|
||||
|
||||
@@ -138,8 +138,6 @@ struct SMesh final : public IAnimatedMesh
|
||||
// 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; }
|
||||
};
|
||||
|
||||
|
||||
@@ -7,7 +7,10 @@
|
||||
#include "IMeshBuffer.h"
|
||||
#include "CVertexBuffer.h"
|
||||
#include "CIndexBuffer.h"
|
||||
#include "WeightBuffer.h"
|
||||
#include "IVertexBuffer.h"
|
||||
#include "S3DVertex.h"
|
||||
#include "vector3d.h"
|
||||
#include <cassert>
|
||||
|
||||
namespace scene
|
||||
@@ -222,6 +225,8 @@ public:
|
||||
SVertexBuffer *Vertices_Standard;
|
||||
SIndexBuffer *Indices;
|
||||
|
||||
std::optional<WeightBuffer> Weights;
|
||||
|
||||
core::matrix4 Transformation;
|
||||
|
||||
video::SMaterial Material;
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "aabbox3d.h"
|
||||
#include "irrMath.h"
|
||||
#include "irrTypes.h"
|
||||
#include "irr_ptr.h"
|
||||
#include "matrix4.h"
|
||||
#include "quaternion.h"
|
||||
#include "vector3d.h"
|
||||
@@ -43,7 +44,6 @@ public:
|
||||
SkinnedMesh(SourceFormat src_format) :
|
||||
EndFrame(0.f), FramesPerSecond(25.f),
|
||||
HasAnimation(false), PreparedForSkinning(false),
|
||||
AnimateNormals(true),
|
||||
SrcFormat(src_format)
|
||||
{
|
||||
SkinningBuffers = &LocalBuffers;
|
||||
@@ -59,15 +59,6 @@ public:
|
||||
//! If the duration is 0, it is a static (=non animated) mesh.
|
||||
f32 getMaxFrameNumber() const override;
|
||||
|
||||
//! 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;
|
||||
|
||||
//! 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;
|
||||
|
||||
//! Turns the given array of local matrices into an array of global matrices
|
||||
//! by multiplying with respective parent matrices.
|
||||
void calculateGlobalMatrices(std::vector<core::matrix4> &matrices) const;
|
||||
@@ -89,8 +80,6 @@ public:
|
||||
|
||||
u32 getTextureSlot(u32 meshbufNr) const override;
|
||||
|
||||
void setTextureSlot(u32 meshbufNr, u32 textureSlot);
|
||||
|
||||
//! Returns bounding box of the mesh *in static pose*.
|
||||
const core::aabbox3d<f32> &getBoundingBox() const override {
|
||||
// TODO ideally we shouldn't be forced to implement this
|
||||
@@ -126,14 +115,6 @@ public:
|
||||
\return Number of the joint or std::nullopt if not found. */
|
||||
std::optional<u32> getJointNumber(const std::string &name) const;
|
||||
|
||||
//! Update Normals when Animating
|
||||
/** \param on If false don't animate, which is faster.
|
||||
Else update normals, which allows for proper lighting of
|
||||
animated meshes (default). */
|
||||
void updateNormalsWhenAnimating(bool on) {
|
||||
AnimateNormals = on;
|
||||
}
|
||||
|
||||
//! converts the vertex type of all meshbuffers to tangents.
|
||||
/** E.g. used for bump mapping. */
|
||||
void convertMeshToTangents();
|
||||
@@ -143,8 +124,8 @@ public:
|
||||
return !HasAnimation;
|
||||
}
|
||||
|
||||
//! Refreshes vertex data cached in joints such as positions and normals
|
||||
void refreshJointCache();
|
||||
//! Back up static pose after local buffers have been modified directly
|
||||
void updateStaticPose();
|
||||
|
||||
//! Moves the mesh into static position.
|
||||
void resetAnimation();
|
||||
@@ -153,26 +134,6 @@ public:
|
||||
std::vector<IBoneSceneNode *> addJoints(
|
||||
AnimatedMeshSceneNode *node, ISceneManager *smgr);
|
||||
|
||||
//! A vertex weight
|
||||
struct SWeight
|
||||
{
|
||||
//! Index of the mesh buffer
|
||||
u16 buffer_id; // I doubt 32bits is needed
|
||||
|
||||
//! Index of the vertex
|
||||
u32 vertex_id; // Store global ID here
|
||||
|
||||
//! Weight Strength/Percentage (0-1)
|
||||
f32 strength;
|
||||
|
||||
private:
|
||||
//! Internal members used by SkinnedMesh
|
||||
friend class SkinnedMesh;
|
||||
char *Moved;
|
||||
core::vector3df StaticPos;
|
||||
core::vector3df StaticNormal;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct Channel {
|
||||
struct Frame {
|
||||
@@ -329,13 +290,11 @@ public:
|
||||
|
||||
//! List of attached meshes
|
||||
std::vector<u32> AttachedMeshes;
|
||||
// TODO ^ should turn this into optional meshbuffer parent field?
|
||||
|
||||
// Animation keyframes for translation, rotation, scale
|
||||
Keys keys;
|
||||
|
||||
//! Skin weights
|
||||
std::vector<SWeight> Weights;
|
||||
|
||||
//! Bounding box of all affected vertices, in local space
|
||||
core::aabbox3df LocalBoundingBox{{0, 0, 0}};
|
||||
|
||||
@@ -369,34 +328,29 @@ public:
|
||||
protected:
|
||||
bool checkForAnimation() const;
|
||||
|
||||
void topoSortJoints();
|
||||
|
||||
void prepareForSkinning();
|
||||
|
||||
void calculateStaticBoundingBox();
|
||||
void calculateJointBoundingBoxes();
|
||||
void calculateBufferBoundingBoxes();
|
||||
|
||||
void normalizeWeights();
|
||||
|
||||
void calculateTangents(core::vector3df &normal,
|
||||
core::vector3df &tangent, core::vector3df &binormal,
|
||||
const core::vector3df &vt1, const core::vector3df &vt2, const core::vector3df &vt3,
|
||||
const core::vector2df &tc1, const core::vector2df &tc2, const core::vector2df &tc3);
|
||||
|
||||
friend class SkinnedMeshBuilder;
|
||||
|
||||
std::vector<SSkinMeshBuffer *> *SkinningBuffers; // Meshbuffer to skin, default is to skin localBuffers
|
||||
|
||||
std::vector<SSkinMeshBuffer *> LocalBuffers;
|
||||
|
||||
//! Mapping from meshbuffer number to bindable texture slot
|
||||
std::vector<u32> TextureSlots;
|
||||
|
||||
//! Joints, topologically sorted (parents come before their children).
|
||||
std::vector<SJoint *> AllJoints;
|
||||
|
||||
// bool can't be used here because std::vector<bool>
|
||||
// doesn't allow taking a reference to individual elements.
|
||||
std::vector<std::vector<char>> Vertices_Moved;
|
||||
|
||||
//! Bounding box of just the static parts of the mesh
|
||||
core::aabbox3df StaticPartsBox{{0, 0, 0}};
|
||||
|
||||
@@ -408,40 +362,76 @@ protected:
|
||||
|
||||
bool HasAnimation;
|
||||
bool PreparedForSkinning;
|
||||
bool AnimateNormals;
|
||||
|
||||
SourceFormat SrcFormat;
|
||||
};
|
||||
|
||||
// Interface for mesh loaders
|
||||
class SkinnedMeshBuilder : public SkinnedMesh {
|
||||
class SkinnedMeshBuilder {
|
||||
using SJoint = SkinnedMesh::SJoint;
|
||||
|
||||
public:
|
||||
SkinnedMeshBuilder(SourceFormat src_format) : SkinnedMesh(src_format) {}
|
||||
|
||||
// HACK the .x and .b3d loader do not separate the "loader" class from an "extractor" class
|
||||
// used and destroyed in a specific loading process (contrast with the .gltf mesh loader).
|
||||
// This means we need an empty skinned mesh builder.
|
||||
SkinnedMeshBuilder() {}
|
||||
|
||||
SkinnedMeshBuilder(SkinnedMesh::SourceFormat src_format)
|
||||
: mesh(new SkinnedMesh(src_format))
|
||||
{}
|
||||
|
||||
//! loaders should call this after populating the mesh
|
||||
// returns *this, so do not try to drop the mesh builder instance
|
||||
SkinnedMesh *finalize();
|
||||
SkinnedMesh *finalize() &&;
|
||||
|
||||
//! alternative method for adding joints
|
||||
std::vector<SJoint *> &getAllJoints() {
|
||||
return AllJoints;
|
||||
}
|
||||
std::vector<SJoint *> &getJoints() { return mesh->AllJoints; }
|
||||
|
||||
//! Adds a new meshbuffer to the mesh, access it as last one
|
||||
SSkinMeshBuffer *addMeshBuffer();
|
||||
|
||||
//! Adds a new meshbuffer to the mesh, access it as last one
|
||||
void addMeshBuffer(SSkinMeshBuffer *meshbuf);
|
||||
//! Adds a new meshbuffer to the mesh, returns ID
|
||||
u32 addMeshBuffer(SSkinMeshBuffer *meshbuf);
|
||||
|
||||
u32 getMeshBufferCount() { return mesh->getMeshBufferCount(); }
|
||||
|
||||
void setTextureSlot(u32 meshbufNr, u32 textureSlot)
|
||||
{
|
||||
mesh->TextureSlots.at(meshbufNr) = textureSlot;
|
||||
}
|
||||
|
||||
//! Adds a new joint to the mesh, access it as last one
|
||||
SJoint *addJoint(SJoint *parent = nullptr);
|
||||
|
||||
std::optional<u32> getJointNumber(const std::string &name) const
|
||||
{
|
||||
return mesh->getJointNumber(name);
|
||||
}
|
||||
|
||||
void addPositionKey(SJoint *joint, f32 frame, core::vector3df pos);
|
||||
void addRotationKey(SJoint *joint, f32 frame, core::quaternion rotation);
|
||||
void addScaleKey(SJoint *joint, f32 frame, core::vector3df scale);
|
||||
|
||||
//! Adds a new weight to the mesh, access it as last one
|
||||
SWeight *addWeight(SJoint *joint);
|
||||
//! Adds a new weight to the mesh
|
||||
void addWeight(SJoint *joint, u16 buf, u32 vert_id, f32 strength);
|
||||
|
||||
private:
|
||||
|
||||
void topoSortJoints();
|
||||
|
||||
//! The mesh that is being built
|
||||
irr_ptr<SkinnedMesh> mesh;
|
||||
|
||||
struct Weight {
|
||||
u16 joint_id;
|
||||
u16 buffer_id;
|
||||
u32 vertex_id;
|
||||
f32 strength;
|
||||
};
|
||||
|
||||
//! Weights to be added once all mesh buffers have been loaded
|
||||
std::vector<Weight> weights;
|
||||
|
||||
};
|
||||
|
||||
} // end namespace scene
|
||||
|
||||
67
irr/include/WeightBuffer.h
Normal file
67
irr/include/WeightBuffer.h
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright (C) 2025 Lars Müller
|
||||
// For conditions of distribution and use, see copyright notice in irrlicht.h
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "vector3d.h"
|
||||
#include "matrix4.h"
|
||||
#include "IVertexBuffer.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
namespace scene
|
||||
{
|
||||
|
||||
struct WeightBuffer
|
||||
{
|
||||
constexpr static u16 MAX_WEIGHTS_PER_VERTEX = 4;
|
||||
// ID-weight pairs for a joint
|
||||
struct VertexWeights {
|
||||
std::array<u16, MAX_WEIGHTS_PER_VERTEX> joint_ids = {};
|
||||
std::array<f32, MAX_WEIGHTS_PER_VERTEX> weights = {};
|
||||
void addWeight(u16 joint_id, f32 weight);
|
||||
/// Transform given position and normal with these weights
|
||||
void skinVertex(core::vector3df &pos, core::vector3df &normal,
|
||||
const std::vector<core::matrix4> &joint_transforms) const;
|
||||
};
|
||||
std::vector<VertexWeights> weights;
|
||||
|
||||
std::optional<std::vector<u32>> animated_vertices;
|
||||
|
||||
// A bit of a hack for now: Store static positions here so we can use them for skinning.
|
||||
// Ideally we might want a design where we do not mutate the original vertex buffer at all.
|
||||
std::unique_ptr<core::vector3df[]> static_positions;
|
||||
std::unique_ptr<core::vector3df[]> static_normals;
|
||||
|
||||
WeightBuffer(size_t n_verts) : weights(n_verts) {}
|
||||
|
||||
const std::array<u16, MAX_WEIGHTS_PER_VERTEX> &getJointIds(u32 vertex_id) const
|
||||
{ return weights[vertex_id].joint_ids; }
|
||||
|
||||
const std::array<f32, MAX_WEIGHTS_PER_VERTEX> &getWeights(u32 vertex_id) const
|
||||
{ return weights[vertex_id].weights; }
|
||||
|
||||
size_t size() const
|
||||
{ return weights.size(); }
|
||||
|
||||
void addWeight(u32 vertex_id, u16 joint_id, f32 weight);
|
||||
|
||||
/// Transform position and normal using the weights of the given vertex
|
||||
void skinVertex(u32 vertex_id, core::vector3df &pos, core::vector3df &normal,
|
||||
const std::vector<core::matrix4> &joint_transforms) const;
|
||||
|
||||
/// @note src and dst can be the same buffer
|
||||
void skin(IVertexBuffer *dst,
|
||||
const std::vector<core::matrix4> &joint_transforms) const;
|
||||
|
||||
/// Prepares this buffer for use in skinning.
|
||||
void finalize();
|
||||
|
||||
void updateStaticPose(const IVertexBuffer *vbuf);
|
||||
|
||||
void resetToStatic(IVertexBuffer *vbuf) const;
|
||||
};
|
||||
|
||||
} // end namespace scene
|
||||
Reference in New Issue
Block a user