mirror of
https://github.com/luanti-org/luanti.git
synced 2025-11-24 18:15:26 +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
|
||||
@@ -514,10 +514,6 @@ void AnimatedMeshSceneNode::setMesh(IAnimatedMesh *mesh)
|
||||
JointsUsed = false;
|
||||
checkJoints();
|
||||
}
|
||||
|
||||
// get start and begin time
|
||||
setAnimationSpeed(Mesh->getAnimationSpeed()); // NOTE: This had been commented out (but not removed!) in r3526. Which caused meshloader-values for speed to be ignored unless users specified explicitly. Missing a test-case where this could go wrong so I put the code back in.
|
||||
setFrameLoop(0, Mesh->getMaxFrameNumber());
|
||||
}
|
||||
|
||||
//! updates the absolute position based on the relative and the parents position
|
||||
|
||||
@@ -25,8 +25,11 @@ namespace scene
|
||||
|
||||
//! Constructor
|
||||
CB3DMeshFileLoader::CB3DMeshFileLoader(scene::ISceneManager *smgr) :
|
||||
AnimatedMesh(0), B3DFile(0), VerticesStart(0), NormalsInFile(false),
|
||||
HasVertexColors(false), ShowWarning(true)
|
||||
B3DFile(nullptr),
|
||||
VerticesStart(0),
|
||||
NormalsInFile(false),
|
||||
HasVertexColors(false),
|
||||
ShowWarning(true)
|
||||
{}
|
||||
|
||||
//! returns true if the file maybe is able to be loaded by this class
|
||||
@@ -43,21 +46,17 @@ bool CB3DMeshFileLoader::isALoadableFileExtension(const io::path &filename) cons
|
||||
IAnimatedMesh *CB3DMeshFileLoader::createMesh(io::IReadFile *file)
|
||||
{
|
||||
if (!file)
|
||||
return 0;
|
||||
return nullptr;
|
||||
|
||||
B3DFile = file;
|
||||
AnimatedMesh = new scene::SkinnedMeshBuilder(SkinnedMesh::SourceFormat::B3D);
|
||||
AnimatedMesh = scene::SkinnedMeshBuilder(SkinnedMesh::SourceFormat::B3D);
|
||||
ShowWarning = true; // If true a warning is issued if too many textures are used
|
||||
VerticesStart = 0;
|
||||
|
||||
if (load()) {
|
||||
return AnimatedMesh->finalize();
|
||||
} else {
|
||||
AnimatedMesh->drop();
|
||||
AnimatedMesh = 0;
|
||||
}
|
||||
if (!load())
|
||||
return nullptr;
|
||||
|
||||
return AnimatedMesh;
|
||||
return std::move(AnimatedMesh).finalize();
|
||||
}
|
||||
|
||||
bool CB3DMeshFileLoader::load()
|
||||
@@ -130,7 +129,7 @@ bool CB3DMeshFileLoader::load()
|
||||
|
||||
bool CB3DMeshFileLoader::readChunkNODE(SkinnedMesh::SJoint *inJoint)
|
||||
{
|
||||
SkinnedMesh::SJoint *joint = AnimatedMesh->addJoint(inJoint);
|
||||
SkinnedMesh::SJoint *joint = AnimatedMesh.addJoint(inJoint);
|
||||
joint->Name = readString();
|
||||
|
||||
#ifdef _B3D_READER_DEBUG
|
||||
@@ -233,7 +232,7 @@ bool CB3DMeshFileLoader::readChunkMESH(SkinnedMesh::SJoint *inJoint)
|
||||
if (!readChunkVRTS(inJoint))
|
||||
return false;
|
||||
} else if (strncmp(B3dStack.getLast().name, "TRIS", 4) == 0) {
|
||||
scene::SSkinMeshBuffer *meshBuffer = AnimatedMesh->addMeshBuffer();
|
||||
scene::SSkinMeshBuffer *meshBuffer = AnimatedMesh.addMeshBuffer();
|
||||
|
||||
if (brushID == -1) { /* ok */
|
||||
} else if (brushID < 0 || (u32)brushID >= Materials.size()) {
|
||||
@@ -243,7 +242,7 @@ bool CB3DMeshFileLoader::readChunkMESH(SkinnedMesh::SJoint *inJoint)
|
||||
meshBuffer->Material = Materials[brushID].Material;
|
||||
}
|
||||
|
||||
if (readChunkTRIS(meshBuffer, AnimatedMesh->getMeshBufferCount() - 1, VerticesStart) == false)
|
||||
if (readChunkTRIS(meshBuffer, AnimatedMesh.getMeshBufferCount() - 1, VerticesStart) == false)
|
||||
return false;
|
||||
|
||||
if (!NormalsInFile) {
|
||||
@@ -541,11 +540,10 @@ bool CB3DMeshFileLoader::readChunkBONE(SkinnedMesh::SJoint *inJoint)
|
||||
if (AnimatedVertices_VertexID[globalVertexID] == -1) {
|
||||
os::Printer::log("B3dMeshLoader: Weight has bad vertex id (no link to meshbuffer index found)");
|
||||
} else if (strength > 0) {
|
||||
SkinnedMesh::SWeight *weight = AnimatedMesh->addWeight(inJoint);
|
||||
weight->strength = strength;
|
||||
// Find the meshbuffer and Vertex index from the Global Vertex ID:
|
||||
weight->vertex_id = AnimatedVertices_VertexID[globalVertexID];
|
||||
weight->buffer_id = AnimatedVertices_BufferID[globalVertexID];
|
||||
AnimatedMesh.addWeight(inJoint,
|
||||
AnimatedVertices_BufferID[globalVertexID],
|
||||
AnimatedVertices_VertexID[globalVertexID],
|
||||
strength);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -591,15 +589,15 @@ bool CB3DMeshFileLoader::readChunkKEYS(SkinnedMesh::SJoint *inJoint)
|
||||
f32 data[4];
|
||||
if (flags & 1) {
|
||||
readFloats(data, 3);
|
||||
AnimatedMesh->addPositionKey(inJoint, frame - 1, {data[0], data[1], data[2]});
|
||||
AnimatedMesh.addPositionKey(inJoint, frame - 1, {data[0], data[1], data[2]});
|
||||
}
|
||||
if (flags & 2) {
|
||||
readFloats(data, 3);
|
||||
AnimatedMesh->addScaleKey(inJoint, frame - 1, {data[0], data[1], data[2]});
|
||||
AnimatedMesh.addScaleKey(inJoint, frame - 1, {data[0], data[1], data[2]});
|
||||
}
|
||||
if (flags & 4) {
|
||||
readFloats(data, 4);
|
||||
AnimatedMesh->addRotationKey(inJoint, frame - 1, core::quaternion(data[1], data[2], data[3], data[0]));
|
||||
AnimatedMesh.addRotationKey(inJoint, frame - 1, core::quaternion(data[1], data[2], data[3], data[0]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -617,15 +615,13 @@ bool CB3DMeshFileLoader::readChunkANIM()
|
||||
os::Printer::log(logStr.c_str(), ELL_DEBUG);
|
||||
#endif
|
||||
|
||||
s32 animFlags; // not stored\used
|
||||
s32 animFrames; // not stored\used
|
||||
f32 animFPS; // not stored\used
|
||||
s32 animFlags; // not stored/used
|
||||
s32 animFrames; // not stored/used
|
||||
f32 animFPS; // not stored/used
|
||||
|
||||
B3DFile->read(&animFlags, sizeof(s32));
|
||||
B3DFile->read(&animFrames, sizeof(s32));
|
||||
readFloats(&animFPS, 1);
|
||||
if (animFPS > 0.f)
|
||||
AnimatedMesh->setAnimationSpeed(animFPS);
|
||||
os::Printer::log("FPS", io::path((double)animFPS), ELL_DEBUG);
|
||||
|
||||
#ifdef __BIG_ENDIAN__
|
||||
|
||||
@@ -60,7 +60,7 @@ private:
|
||||
|
||||
core::array<video::S3DVertex2TCoords> BaseVertices;
|
||||
|
||||
SkinnedMeshBuilder *AnimatedMesh;
|
||||
SkinnedMeshBuilder AnimatedMesh;
|
||||
io::IReadFile *B3DFile;
|
||||
|
||||
// B3Ds have Vertex ID's local within the mesh I don't want this
|
||||
|
||||
@@ -345,15 +345,14 @@ IAnimatedMesh* SelfType::createMesh(io::IReadFile* file)
|
||||
const char *filename = file->getFileName().c_str();
|
||||
try {
|
||||
tiniergltf::GlTF model = parseGLTF(file);
|
||||
irr_ptr<SkinnedMeshBuilder> mesh(new SkinnedMeshBuilder(
|
||||
SkinnedMesh::SourceFormat::GLTF));
|
||||
MeshExtractor extractor(std::move(model), mesh.get());
|
||||
SkinnedMeshBuilder mesh(SkinnedMesh::SourceFormat::GLTF);
|
||||
MeshExtractor extractor(std::move(model));
|
||||
try {
|
||||
extractor.load();
|
||||
auto *res = extractor.load();
|
||||
for (const auto &warning : extractor.getWarnings()) {
|
||||
os::Printer::log(filename, warning.c_str(), ELL_WARNING);
|
||||
}
|
||||
return mesh.release()->finalize();
|
||||
return res;
|
||||
} catch (const std::runtime_error &e) {
|
||||
os::Printer::log("error converting gltf to irrlicht mesh", e.what(), ELL_ERROR);
|
||||
}
|
||||
@@ -423,15 +422,14 @@ void SelfType::MeshExtractor::addPrimitive(
|
||||
}
|
||||
|
||||
auto *meshbuf = new SSkinMeshBuffer(std::move(*vertices), std::move(indices));
|
||||
m_irr_model->addMeshBuffer(meshbuf);
|
||||
const auto meshbufNr = m_irr_model->getMeshBufferCount() - 1;
|
||||
const auto meshbufNr = m_irr_model.addMeshBuffer(meshbuf);
|
||||
|
||||
if (primitive.material.has_value()) {
|
||||
const auto &material = m_gltf_model.materials->at(*primitive.material);
|
||||
if (material.pbrMetallicRoughness.has_value()) {
|
||||
const auto &texture = material.pbrMetallicRoughness->baseColorTexture;
|
||||
if (texture.has_value()) {
|
||||
m_irr_model->setTextureSlot(meshbufNr, static_cast<u32>(texture->index));
|
||||
m_irr_model.setTextureSlot(meshbufNr, static_cast<u32>(texture->index));
|
||||
const auto samplerIdx = m_gltf_model.textures->at(texture->index).sampler;
|
||||
if (samplerIdx.has_value()) {
|
||||
auto &sampler = m_gltf_model.samplers->at(*samplerIdx);
|
||||
@@ -501,10 +499,8 @@ void SelfType::MeshExtractor::addPrimitive(
|
||||
if (strength <= 0)
|
||||
continue; // note: also ignores negative weights
|
||||
|
||||
SkinnedMesh::SWeight *weight = m_irr_model->addWeight(m_loaded_nodes.at(skin.joints.at(jointIdx)));
|
||||
weight->buffer_id = meshbufNr;
|
||||
weight->vertex_id = v;
|
||||
weight->strength = strength;
|
||||
m_irr_model.addWeight(m_loaded_nodes.at(skin.joints.at(jointIdx)),
|
||||
meshbufNr, v, strength);
|
||||
}
|
||||
}
|
||||
if (negative_weights)
|
||||
@@ -571,7 +567,7 @@ void SelfType::MeshExtractor::loadNode(
|
||||
SkinnedMesh::SJoint *parent)
|
||||
{
|
||||
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);
|
||||
joint->GlobalMatrix = parent ? parent->GlobalMatrix * transform : transform;
|
||||
if (node.name.has_value()) {
|
||||
@@ -695,7 +691,7 @@ void SelfType::MeshExtractor::loadAnimation(const std::size_t animIdx)
|
||||
}
|
||||
}
|
||||
|
||||
void SelfType::MeshExtractor::load()
|
||||
SkinnedMesh *SelfType::MeshExtractor::load()
|
||||
{
|
||||
if (m_gltf_model.extensionsRequired)
|
||||
throw std::runtime_error("model requires extensions, but we support none");
|
||||
@@ -723,8 +719,8 @@ void SelfType::MeshExtractor::load()
|
||||
warn("multiple animations are not supported");
|
||||
|
||||
loadAnimation(0);
|
||||
m_irr_model->setAnimationSpeed(1);
|
||||
}
|
||||
return std::move(m_irr_model).finalize();
|
||||
} catch (const std::out_of_range &e) {
|
||||
throw std::runtime_error(e.what());
|
||||
} catch (const std::bad_optional_access &e) {
|
||||
|
||||
@@ -96,9 +96,10 @@ private:
|
||||
class MeshExtractor
|
||||
{
|
||||
public:
|
||||
MeshExtractor(tiniergltf::GlTF &&model,
|
||||
SkinnedMeshBuilder *mesh) noexcept
|
||||
: m_gltf_model(std::move(model)), m_irr_model(mesh) {};
|
||||
MeshExtractor(tiniergltf::GlTF &&model) noexcept
|
||||
: m_gltf_model(std::move(model))
|
||||
, m_irr_model(SkinnedMesh::SourceFormat::GLTF)
|
||||
{}
|
||||
|
||||
/* Gets indices for the given mesh/primitive.
|
||||
*
|
||||
@@ -114,14 +115,14 @@ private:
|
||||
|
||||
std::size_t getPrimitiveCount(const std::size_t meshIdx) const;
|
||||
|
||||
void load();
|
||||
SkinnedMesh *load();
|
||||
const std::unordered_set<std::string> &getWarnings() {
|
||||
return warnings;
|
||||
}
|
||||
|
||||
private:
|
||||
const tiniergltf::GlTF m_gltf_model;
|
||||
SkinnedMeshBuilder *m_irr_model;
|
||||
SkinnedMeshBuilder m_irr_model;
|
||||
|
||||
std::vector<std::function<void()>> m_mesh_loaders;
|
||||
std::vector<SkinnedMesh::SJoint *> m_loaded_nodes;
|
||||
|
||||
@@ -273,6 +273,7 @@ set(IRRMESHLOADER
|
||||
add_library(IRRMESHOBJ OBJECT
|
||||
CMeshSceneNode.h
|
||||
|
||||
WeightBuffer.cpp
|
||||
SkinnedMesh.cpp
|
||||
CMeshSceneNode.cpp
|
||||
AnimatedMeshSceneNode.cpp
|
||||
|
||||
@@ -110,7 +110,7 @@ void CMeshManipulator::recalculateNormals(scene::IMesh *mesh, bool smooth, bool
|
||||
|
||||
if (mesh->getMeshType() == EAMT_SKINNED) {
|
||||
auto *smesh = (SkinnedMesh *)mesh;
|
||||
smesh->refreshJointCache();
|
||||
smesh->updateStaticPose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
#include "CXMeshFileLoader.h"
|
||||
#include "SkinnedMesh.h"
|
||||
#include "Transform.h"
|
||||
#include "os.h"
|
||||
|
||||
#include "fast_atof.h"
|
||||
@@ -16,7 +15,6 @@
|
||||
#ifdef _DEBUG
|
||||
#define _XREADER_DEBUG
|
||||
#endif
|
||||
// #define BETTER_MESHBUFFER_SPLITTING_FOR_X
|
||||
|
||||
#define SET_ERR_AND_RETURN() \
|
||||
do { \
|
||||
@@ -29,8 +27,17 @@ namespace scene
|
||||
|
||||
//! Constructor
|
||||
CXMeshFileLoader::CXMeshFileLoader(scene::ISceneManager *smgr) :
|
||||
AnimatedMesh(0), Buffer(0), P(0), End(0), BinaryNumCount(0), Line(0), ErrorState(false),
|
||||
CurFrame(0), MajorVersion(0), MinorVersion(0), BinaryFormat(false), FloatSize(0)
|
||||
Buffer(nullptr),
|
||||
P(nullptr),
|
||||
End(nullptr),
|
||||
BinaryNumCount(0),
|
||||
Line(0),
|
||||
ErrorState(false),
|
||||
CurFrame(nullptr),
|
||||
MajorVersion(0),
|
||||
MinorVersion(0),
|
||||
BinaryFormat(false),
|
||||
FloatSize(0)
|
||||
{}
|
||||
|
||||
//! returns true if the file maybe is able to be loaded by this class
|
||||
@@ -47,20 +54,17 @@ bool CXMeshFileLoader::isALoadableFileExtension(const io::path &filename) const
|
||||
IAnimatedMesh *CXMeshFileLoader::createMesh(io::IReadFile *file)
|
||||
{
|
||||
if (!file)
|
||||
return 0;
|
||||
return nullptr;
|
||||
|
||||
#ifdef _XREADER_DEBUG
|
||||
u32 time = os::Timer::getRealTime();
|
||||
#endif
|
||||
|
||||
AnimatedMesh = new SkinnedMeshBuilder(SkinnedMesh::SourceFormat::X);
|
||||
AnimatedMesh = SkinnedMeshBuilder(SkinnedMesh::SourceFormat::X);
|
||||
|
||||
SkinnedMesh *res = nullptr;
|
||||
if (load(file)) {
|
||||
res = AnimatedMesh->finalize();
|
||||
} else {
|
||||
AnimatedMesh->drop();
|
||||
AnimatedMesh = 0;
|
||||
res = std::move(AnimatedMesh).finalize();
|
||||
}
|
||||
#ifdef _XREADER_DEBUG
|
||||
time = os::Timer::getRealTime() - time;
|
||||
@@ -71,7 +75,7 @@ IAnimatedMesh *CXMeshFileLoader::createMesh(io::IReadFile *file)
|
||||
tmpString += "ms";
|
||||
os::Printer::log(tmpString.c_str());
|
||||
#endif
|
||||
// Clear up
|
||||
// Clean up
|
||||
|
||||
MajorVersion = 0;
|
||||
MinorVersion = 0;
|
||||
@@ -111,17 +115,15 @@ bool CXMeshFileLoader::load(io::IReadFile *file)
|
||||
u32 i;
|
||||
|
||||
mesh->Buffers.reallocate(mesh->Materials.size());
|
||||
#ifndef BETTER_MESHBUFFER_SPLITTING_FOR_X
|
||||
const u32 bufferOffset = AnimatedMesh->getMeshBufferCount();
|
||||
#endif
|
||||
const u32 bufferOffset = AnimatedMesh.getMeshBufferCount();
|
||||
for (i = 0; i < mesh->Materials.size(); ++i) {
|
||||
mesh->Buffers.push_back(AnimatedMesh->addMeshBuffer());
|
||||
mesh->Buffers.push_back(AnimatedMesh.addMeshBuffer());
|
||||
mesh->Buffers.getLast()->Material = mesh->Materials[i];
|
||||
|
||||
if (!mesh->HasSkinning) {
|
||||
// Set up rigid animation
|
||||
if (mesh->AttachedJointID != -1) {
|
||||
AnimatedMesh->getAllJoints()[mesh->AttachedJointID]->AttachedMeshes.push_back(AnimatedMesh->getMeshBufferCount() - 1);
|
||||
AnimatedMesh.getJoints()[mesh->AttachedJointID]->AttachedMeshes.push_back(AnimatedMesh.getMeshBufferCount() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,192 +142,103 @@ bool CXMeshFileLoader::load(io::IReadFile *file)
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef BETTER_MESHBUFFER_SPLITTING_FOR_X
|
||||
{
|
||||
// the same vertex can be used in many different meshbuffers, but it's slow to work out
|
||||
core::array<u32> verticesLinkIndex;
|
||||
core::array<s16> verticesLinkBuffer;
|
||||
verticesLinkBuffer.set_used(mesh->Vertices.size());
|
||||
|
||||
core::array<core::array<u32>> verticesLinkIndex;
|
||||
verticesLinkIndex.reallocate(mesh->Vertices.size());
|
||||
core::array<core::array<u16>> verticesLinkBuffer;
|
||||
verticesLinkBuffer.reallocate(mesh->Vertices.size());
|
||||
// init with 0
|
||||
for (i = 0; i < mesh->Vertices.size(); ++i) {
|
||||
// watch out for vertices which are not part of the mesh
|
||||
// they will keep the -1 and can lead to out-of-bounds access
|
||||
verticesLinkBuffer[i] = -1;
|
||||
}
|
||||
|
||||
for (i = 0; i < mesh->Vertices.size(); ++i) {
|
||||
verticesLinkIndex.push_back(core::array<u32>());
|
||||
verticesLinkBuffer.push_back(core::array<u16>());
|
||||
}
|
||||
|
||||
for (i = 0; i < mesh->FaceMaterialIndices.size(); ++i) {
|
||||
for (u32 id = i * 3 + 0; id <= i * 3 + 2; ++id) {
|
||||
core::array<u16> &Array = verticesLinkBuffer[mesh->Indices[id]];
|
||||
bool found = false;
|
||||
|
||||
for (u32 j = 0; j < Array.size(); ++j) {
|
||||
if (Array[j] == mesh->FaceMaterialIndices[i]) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
bool warned = false;
|
||||
// store meshbuffer number per vertex
|
||||
for (i = 0; i < mesh->FaceMaterialIndices.size(); ++i) {
|
||||
for (u32 id = i * 3 + 0; id <= i * 3 + 2; ++id) {
|
||||
if ((verticesLinkBuffer[mesh->Indices[id]] != -1) && (verticesLinkBuffer[mesh->Indices[id]] != (s16)mesh->FaceMaterialIndices[i])) {
|
||||
if (!warned) {
|
||||
os::Printer::log("X loader", "Duplicated vertex, animation might be corrupted.", ELL_WARNING);
|
||||
warned = true;
|
||||
}
|
||||
|
||||
if (!found)
|
||||
Array.push_back(mesh->FaceMaterialIndices[i]);
|
||||
const u32 tmp = mesh->Vertices.size();
|
||||
mesh->Vertices.push_back(mesh->Vertices[mesh->Indices[id]]);
|
||||
mesh->Indices[id] = tmp;
|
||||
verticesLinkBuffer.set_used(mesh->Vertices.size());
|
||||
}
|
||||
verticesLinkBuffer[mesh->Indices[id]] = mesh->FaceMaterialIndices[i];
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < verticesLinkBuffer.size(); ++i) {
|
||||
if (!verticesLinkBuffer[i].size())
|
||||
verticesLinkBuffer[i].push_back(0);
|
||||
}
|
||||
|
||||
if (mesh->FaceMaterialIndices.size() != 0) {
|
||||
// store vertices in buffers and remember relation in verticesLinkIndex
|
||||
u32 *vCountArray = new u32[mesh->Buffers.size()];
|
||||
memset(vCountArray, 0, mesh->Buffers.size() * sizeof(u32));
|
||||
// count vertices in each buffer and reallocate
|
||||
for (i = 0; i < mesh->Vertices.size(); ++i) {
|
||||
core::array<u16> &Array = verticesLinkBuffer[i];
|
||||
verticesLinkIndex[i].reallocate(Array.size());
|
||||
for (u32 j = 0; j < Array.size(); ++j) {
|
||||
scene::SSkinMeshBuffer *buffer = mesh->Buffers[Array[j]];
|
||||
verticesLinkIndex[i].push_back(buffer->Vertices_Standard.size());
|
||||
buffer->Vertices_Standard.push_back(mesh->Vertices[i]);
|
||||
if (verticesLinkBuffer[i] != -1)
|
||||
++vCountArray[verticesLinkBuffer[i]];
|
||||
}
|
||||
if (mesh->TCoords2.size()) {
|
||||
for (i = 0; i != mesh->Buffers.size(); ++i) {
|
||||
mesh->Buffers[i]->Vertices_2TCoords->Data.reserve(vCountArray[i]);
|
||||
mesh->Buffers[i]->VertexType = video::EVT_2TCOORDS;
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i != mesh->Buffers.size(); ++i)
|
||||
mesh->Buffers[i]->Vertices_Standard->Data.reserve(vCountArray[i]);
|
||||
}
|
||||
|
||||
verticesLinkIndex.set_used(mesh->Vertices.size());
|
||||
// actually store vertices
|
||||
for (i = 0; i < mesh->Vertices.size(); ++i) {
|
||||
// if a vertex is missing for some reason, just skip it
|
||||
if (verticesLinkBuffer[i] == -1)
|
||||
continue;
|
||||
scene::SSkinMeshBuffer *buffer = mesh->Buffers[verticesLinkBuffer[i]];
|
||||
|
||||
if (mesh->TCoords2.size()) {
|
||||
verticesLinkIndex[i] = buffer->Vertices_2TCoords->getCount();
|
||||
buffer->Vertices_2TCoords->Data.emplace_back(mesh->Vertices[i]);
|
||||
// We have a problem with correct tcoord2 handling here
|
||||
// crash fixed for now by checking the values
|
||||
buffer->Vertices_2TCoords->Data.back().TCoords2 = (i < mesh->TCoords2.size()) ? mesh->TCoords2[i] : mesh->Vertices[i].TCoords;
|
||||
} else {
|
||||
verticesLinkIndex[i] = buffer->Vertices_Standard->getCount();
|
||||
buffer->Vertices_Standard->Data.push_back(mesh->Vertices[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// count indices per buffer and reallocate
|
||||
memset(vCountArray, 0, mesh->Buffers.size() * sizeof(u32));
|
||||
for (i = 0; i < mesh->FaceMaterialIndices.size(); ++i)
|
||||
++vCountArray[mesh->FaceMaterialIndices[i]];
|
||||
for (i = 0; i != mesh->Buffers.size(); ++i)
|
||||
mesh->Buffers[i]->Indices->Data.reserve(vCountArray[i]);
|
||||
delete[] vCountArray;
|
||||
// create indices per buffer
|
||||
for (i = 0; i < mesh->FaceMaterialIndices.size(); ++i) {
|
||||
scene::SSkinMeshBuffer *buffer = mesh->Buffers[mesh->FaceMaterialIndices[i]];
|
||||
|
||||
for (u32 id = i * 3 + 0; id <= i * 3 + 2; ++id) {
|
||||
core::array<u16> &Array = verticesLinkBuffer[mesh->Indices[id]];
|
||||
|
||||
for (u32 j = 0; j < Array.size(); ++j) {
|
||||
if (Array[j] == mesh->FaceMaterialIndices[i])
|
||||
buffer->Indices.push_back(verticesLinkIndex[mesh->Indices[id]][j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (u32 j = 0; j < mesh->WeightJoint.size(); ++j) {
|
||||
SkinnedMesh::SJoint *joint = AnimatedMesh->getAllJoints()[mesh->WeightJoint[j]];
|
||||
SkinnedMesh::SWeight &weight = joint->Weights[mesh->WeightNum[j]];
|
||||
|
||||
u32 id = weight.vertex_id;
|
||||
|
||||
if (id >= verticesLinkIndex.size()) {
|
||||
os::Printer::log("X loader: Weight id out of range", ELL_WARNING);
|
||||
id = 0;
|
||||
weight.strength = 0.f;
|
||||
}
|
||||
|
||||
if (verticesLinkBuffer[id].size() == 1) {
|
||||
weight.vertex_id = verticesLinkIndex[id][0];
|
||||
weight.buffer_id = verticesLinkBuffer[id][0];
|
||||
} else if (verticesLinkBuffer[id].size() != 0) {
|
||||
for (u32 k = 1; k < verticesLinkBuffer[id].size(); ++k) {
|
||||
SkinnedMesh::SWeight *WeightClone = AnimatedMesh->addWeight(joint);
|
||||
WeightClone->strength = weight.strength;
|
||||
WeightClone->vertex_id = verticesLinkIndex[id][k];
|
||||
WeightClone->buffer_id = verticesLinkBuffer[id][k];
|
||||
}
|
||||
for (u32 id = i * 3 + 0; id != i * 3 + 3; ++id) {
|
||||
buffer->Indices->Data.push_back(verticesLinkIndex[mesh->Indices[id]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
{
|
||||
core::array<u32> verticesLinkIndex;
|
||||
core::array<s16> verticesLinkBuffer;
|
||||
verticesLinkBuffer.set_used(mesh->Vertices.size());
|
||||
|
||||
// init with 0
|
||||
for (i = 0; i < mesh->Vertices.size(); ++i) {
|
||||
// watch out for vertices which are not part of the mesh
|
||||
// they will keep the -1 and can lead to out-of-bounds access
|
||||
verticesLinkBuffer[i] = -1;
|
||||
for (const auto &weight : mesh->Weights) {
|
||||
u32 id = weight.global_vertex_id;
|
||||
|
||||
if (id >= verticesLinkIndex.size()) {
|
||||
os::Printer::log("X loader: Weight id out of range", ELL_WARNING);
|
||||
continue;
|
||||
}
|
||||
|
||||
bool warned = false;
|
||||
// store meshbuffer number per vertex
|
||||
for (i = 0; i < mesh->FaceMaterialIndices.size(); ++i) {
|
||||
for (u32 id = i * 3 + 0; id <= i * 3 + 2; ++id) {
|
||||
if ((verticesLinkBuffer[mesh->Indices[id]] != -1) && (verticesLinkBuffer[mesh->Indices[id]] != (s16)mesh->FaceMaterialIndices[i])) {
|
||||
if (!warned) {
|
||||
os::Printer::log("X loader", "Duplicated vertex, animation might be corrupted.", ELL_WARNING);
|
||||
warned = true;
|
||||
}
|
||||
const u32 tmp = mesh->Vertices.size();
|
||||
mesh->Vertices.push_back(mesh->Vertices[mesh->Indices[id]]);
|
||||
mesh->Indices[id] = tmp;
|
||||
verticesLinkBuffer.set_used(mesh->Vertices.size());
|
||||
}
|
||||
verticesLinkBuffer[mesh->Indices[id]] = mesh->FaceMaterialIndices[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (mesh->FaceMaterialIndices.size() != 0) {
|
||||
// store vertices in buffers and remember relation in verticesLinkIndex
|
||||
u32 *vCountArray = new u32[mesh->Buffers.size()];
|
||||
memset(vCountArray, 0, mesh->Buffers.size() * sizeof(u32));
|
||||
// count vertices in each buffer and reallocate
|
||||
for (i = 0; i < mesh->Vertices.size(); ++i) {
|
||||
if (verticesLinkBuffer[i] != -1)
|
||||
++vCountArray[verticesLinkBuffer[i]];
|
||||
}
|
||||
if (mesh->TCoords2.size()) {
|
||||
for (i = 0; i != mesh->Buffers.size(); ++i) {
|
||||
mesh->Buffers[i]->Vertices_2TCoords->Data.reserve(vCountArray[i]);
|
||||
mesh->Buffers[i]->VertexType = video::EVT_2TCOORDS;
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i != mesh->Buffers.size(); ++i)
|
||||
mesh->Buffers[i]->Vertices_Standard->Data.reserve(vCountArray[i]);
|
||||
}
|
||||
|
||||
verticesLinkIndex.set_used(mesh->Vertices.size());
|
||||
// actually store vertices
|
||||
for (i = 0; i < mesh->Vertices.size(); ++i) {
|
||||
// if a vertex is missing for some reason, just skip it
|
||||
if (verticesLinkBuffer[i] == -1)
|
||||
continue;
|
||||
scene::SSkinMeshBuffer *buffer = mesh->Buffers[verticesLinkBuffer[i]];
|
||||
|
||||
if (mesh->TCoords2.size()) {
|
||||
verticesLinkIndex[i] = buffer->Vertices_2TCoords->getCount();
|
||||
buffer->Vertices_2TCoords->Data.emplace_back(mesh->Vertices[i]);
|
||||
// We have a problem with correct tcoord2 handling here
|
||||
// crash fixed for now by checking the values
|
||||
buffer->Vertices_2TCoords->Data.back().TCoords2 = (i < mesh->TCoords2.size()) ? mesh->TCoords2[i] : mesh->Vertices[i].TCoords;
|
||||
} else {
|
||||
verticesLinkIndex[i] = buffer->Vertices_Standard->getCount();
|
||||
buffer->Vertices_Standard->Data.push_back(mesh->Vertices[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// count indices per buffer and reallocate
|
||||
memset(vCountArray, 0, mesh->Buffers.size() * sizeof(u32));
|
||||
for (i = 0; i < mesh->FaceMaterialIndices.size(); ++i)
|
||||
++vCountArray[mesh->FaceMaterialIndices[i]];
|
||||
for (i = 0; i != mesh->Buffers.size(); ++i)
|
||||
mesh->Buffers[i]->Indices->Data.reserve(vCountArray[i]);
|
||||
delete[] vCountArray;
|
||||
// create indices per buffer
|
||||
for (i = 0; i < mesh->FaceMaterialIndices.size(); ++i) {
|
||||
scene::SSkinMeshBuffer *buffer = mesh->Buffers[mesh->FaceMaterialIndices[i]];
|
||||
for (u32 id = i * 3 + 0; id != i * 3 + 3; ++id) {
|
||||
buffer->Indices->Data.push_back(verticesLinkIndex[mesh->Indices[id]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (u32 j = 0; j < mesh->WeightJoint.size(); ++j) {
|
||||
SkinnedMesh::SWeight &weight = (AnimatedMesh->getAllJoints()[mesh->WeightJoint[j]]->Weights[mesh->WeightNum[j]]);
|
||||
|
||||
u32 id = weight.vertex_id;
|
||||
|
||||
if (id >= verticesLinkIndex.size()) {
|
||||
os::Printer::log("X loader: Weight id out of range", ELL_WARNING);
|
||||
id = 0;
|
||||
weight.strength = 0.f;
|
||||
}
|
||||
|
||||
weight.vertex_id = verticesLinkIndex[id];
|
||||
weight.buffer_id = verticesLinkBuffer[id] + bufferOffset;
|
||||
}
|
||||
u16 buf_id = verticesLinkBuffer[id] + bufferOffset;
|
||||
u32 vert_id = verticesLinkIndex[id];
|
||||
auto *joint = AnimatedMesh.getJoints()[weight.joint_id];
|
||||
AnimatedMesh.addWeight(joint, buf_id, vert_id, weight.strength);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -508,10 +421,10 @@ bool CXMeshFileLoader::parseDataObjectFrame(SkinnedMesh::SJoint *Parent)
|
||||
SkinnedMesh::SJoint *joint = 0;
|
||||
|
||||
if (name.size()) {
|
||||
auto n = AnimatedMesh->getJointNumber(name.c_str());
|
||||
auto n = AnimatedMesh.getJointNumber(name.c_str());
|
||||
if (n.has_value()) {
|
||||
JointID = *n;
|
||||
joint = AnimatedMesh->getAllJoints()[JointID];
|
||||
joint = AnimatedMesh.getJoints()[JointID];
|
||||
joint->setParent(Parent);
|
||||
}
|
||||
}
|
||||
@@ -520,9 +433,9 @@ bool CXMeshFileLoader::parseDataObjectFrame(SkinnedMesh::SJoint *Parent)
|
||||
#ifdef _XREADER_DEBUG
|
||||
os::Printer::log("creating joint ", name.c_str(), ELL_DEBUG);
|
||||
#endif
|
||||
joint = AnimatedMesh->addJoint(Parent);
|
||||
joint = AnimatedMesh.addJoint(Parent);
|
||||
joint->Name = name.c_str();
|
||||
JointID = AnimatedMesh->getAllJoints().size() - 1;
|
||||
JointID = AnimatedMesh.getJoints().size() - 1;
|
||||
} else {
|
||||
#ifdef _XREADER_DEBUG
|
||||
os::Printer::log("using joint ", name.c_str(), ELL_DEBUG);
|
||||
@@ -940,46 +853,34 @@ bool CXMeshFileLoader::parseDataObjectSkinWeights(SXMesh &mesh)
|
||||
|
||||
mesh.HasSkinning = true;
|
||||
|
||||
auto n = AnimatedMesh->getJointNumber(TransformNodeName.c_str());
|
||||
SkinnedMesh::SJoint *joint = n.has_value() ? AnimatedMesh->getAllJoints()[*n] : nullptr;
|
||||
auto joint_id = AnimatedMesh.getJointNumber(TransformNodeName.c_str());
|
||||
SkinnedMesh::SJoint *joint = joint_id.has_value() ? AnimatedMesh.getJoints()[*joint_id] : nullptr;
|
||||
|
||||
if (!joint) {
|
||||
#ifdef _XREADER_DEBUG
|
||||
os::Printer::log("creating joint for skinning ", TransformNodeName.c_str(), ELL_DEBUG);
|
||||
#endif
|
||||
n = AnimatedMesh->getAllJoints().size();
|
||||
joint = AnimatedMesh->addJoint(0);
|
||||
joint = AnimatedMesh.addJoint(nullptr);
|
||||
joint->Name = TransformNodeName.c_str();
|
||||
joint_id = joint->JointID;
|
||||
}
|
||||
|
||||
// read vertex weights
|
||||
|
||||
const u32 nWeights = readInt();
|
||||
|
||||
// read vertex indices
|
||||
u32 i;
|
||||
mesh.Weights.reserve(mesh.Weights.size() + nWeights);
|
||||
|
||||
const u32 jointStart = joint->Weights.size();
|
||||
joint->Weights.reserve(jointStart + nWeights);
|
||||
std::vector<u32> vertex_ids;
|
||||
vertex_ids.reserve(nWeights);
|
||||
for (u32 i = 0; i < nWeights; ++i)
|
||||
vertex_ids.push_back(readInt());
|
||||
|
||||
mesh.WeightJoint.reallocate(mesh.WeightJoint.size() + nWeights);
|
||||
mesh.WeightNum.reallocate(mesh.WeightNum.size() + nWeights);
|
||||
|
||||
for (i = 0; i < nWeights; ++i) {
|
||||
mesh.WeightJoint.push_back(*n);
|
||||
mesh.WeightNum.push_back(joint->Weights.size()); // id of weight
|
||||
|
||||
// Note: This adds a weight to joint->Weights
|
||||
SkinnedMesh::SWeight *weight = AnimatedMesh->addWeight(joint);
|
||||
|
||||
weight->buffer_id = 0;
|
||||
weight->vertex_id = readInt();
|
||||
for (u32 i = 0; i < nWeights; ++i) {
|
||||
f32 strength = readFloat();
|
||||
mesh.Weights.emplace_back(SXMesh::Weight{
|
||||
(u16) *joint_id, vertex_ids[i], strength});
|
||||
}
|
||||
|
||||
// read vertex weights
|
||||
|
||||
for (i = jointStart; i < jointStart + nWeights; ++i)
|
||||
joint->Weights[i].strength = readFloat();
|
||||
|
||||
// read matrix offset
|
||||
|
||||
// transforms the mesh vertices to the space of the bone
|
||||
@@ -1320,7 +1221,7 @@ bool CXMeshFileLoader::parseDataObjectAnimationTicksPerSecond()
|
||||
SET_ERR_AND_RETURN();
|
||||
}
|
||||
|
||||
const u32 ticks = readInt();
|
||||
static_cast<void>(readInt());
|
||||
|
||||
if (!checkForOneFollowingSemicolons()) {
|
||||
os::Printer::log("No closing semicolon in AnimationTicksPerSecond in x file", ELL_WARNING);
|
||||
@@ -1334,8 +1235,6 @@ bool CXMeshFileLoader::parseDataObjectAnimationTicksPerSecond()
|
||||
SET_ERR_AND_RETURN();
|
||||
}
|
||||
|
||||
AnimatedMesh->setAnimationSpeed(static_cast<f32>(ticks));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1393,16 +1292,16 @@ bool CXMeshFileLoader::parseDataObjectAnimation()
|
||||
#ifdef _XREADER_DEBUG
|
||||
os::Printer::log("frame name", FrameName.c_str(), ELL_DEBUG);
|
||||
#endif
|
||||
auto n = AnimatedMesh->getJointNumber(FrameName.c_str());
|
||||
auto joint_id = AnimatedMesh.getJointNumber(FrameName.c_str());
|
||||
|
||||
SkinnedMesh::SJoint *joint;
|
||||
if (n.has_value()) {
|
||||
joint = AnimatedMesh->getAllJoints()[*n];
|
||||
if (joint_id.has_value()) {
|
||||
joint = AnimatedMesh.getJoints()[*joint_id];
|
||||
} else {
|
||||
#ifdef _XREADER_DEBUG
|
||||
os::Printer::log("creating joint for animation ", FrameName.c_str(), ELL_DEBUG);
|
||||
#endif
|
||||
joint = AnimatedMesh->addJoint(0);
|
||||
joint = AnimatedMesh.addJoint();
|
||||
joint->Name = FrameName.c_str();
|
||||
}
|
||||
|
||||
@@ -1472,7 +1371,7 @@ bool CXMeshFileLoader::parseDataObjectAnimationKey(SkinnedMesh::SJoint *joint)
|
||||
|
||||
core::quaternion rotation(X, Y, Z, W);
|
||||
rotation.normalize();
|
||||
AnimatedMesh->addRotationKey(joint, time, rotation);
|
||||
AnimatedMesh.addRotationKey(joint, time, rotation);
|
||||
} break;
|
||||
case 1: // scale
|
||||
case 2: // position
|
||||
@@ -1495,9 +1394,9 @@ bool CXMeshFileLoader::parseDataObjectAnimationKey(SkinnedMesh::SJoint *joint)
|
||||
}
|
||||
|
||||
if (keyType == 2) {
|
||||
AnimatedMesh->addPositionKey(joint, time, vector);
|
||||
AnimatedMesh.addPositionKey(joint, time, vector);
|
||||
} else {
|
||||
AnimatedMesh->addScaleKey(joint, time, vector);
|
||||
AnimatedMesh.addScaleKey(joint, time, vector);
|
||||
}
|
||||
} break;
|
||||
case 3:
|
||||
@@ -1522,8 +1421,8 @@ bool CXMeshFileLoader::parseDataObjectAnimationKey(SkinnedMesh::SJoint *joint)
|
||||
os::Printer::log("Line", core::stringc(Line).c_str(), ELL_WARNING);
|
||||
}
|
||||
|
||||
AnimatedMesh->addRotationKey(joint, time, core::quaternion(mat.getTransposed()));
|
||||
AnimatedMesh->addPositionKey(joint, time, mat.getTranslation());
|
||||
AnimatedMesh.addRotationKey(joint, time, core::quaternion(mat.getTransposed()));
|
||||
AnimatedMesh.addPositionKey(joint, time, mat.getTranslation());
|
||||
|
||||
/*
|
||||
core::vector3df scale=mat.getScale();
|
||||
|
||||
@@ -63,8 +63,12 @@ public:
|
||||
|
||||
core::array<video::SMaterial> Materials; // material array
|
||||
|
||||
core::array<u32> WeightJoint;
|
||||
core::array<u32> WeightNum;
|
||||
struct Weight {
|
||||
u16 joint_id;
|
||||
u32 global_vertex_id;
|
||||
f32 strength;
|
||||
};
|
||||
std::vector<Weight> Weights;
|
||||
|
||||
s32 AttachedJointID;
|
||||
|
||||
@@ -152,7 +156,7 @@ private:
|
||||
bool readRGB(video::SColor &color);
|
||||
bool readRGBA(video::SColor &color);
|
||||
|
||||
SkinnedMeshBuilder *AnimatedMesh;
|
||||
SkinnedMeshBuilder AnimatedMesh;
|
||||
|
||||
c8 *Buffer;
|
||||
const c8 *P;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
#include <cassert>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace scene
|
||||
{
|
||||
@@ -35,21 +36,6 @@ f32 SkinnedMesh::getMaxFrameNumber() const
|
||||
return EndFrame;
|
||||
}
|
||||
|
||||
//! 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 SkinnedMesh::getAnimationSpeed() const
|
||||
{
|
||||
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 SkinnedMesh::setAnimationSpeed(f32 fps)
|
||||
{
|
||||
FramesPerSecond = fps;
|
||||
}
|
||||
|
||||
// Keyframe Animation
|
||||
|
||||
|
||||
@@ -102,56 +88,19 @@ void SkinnedMesh::skinMesh(const std::vector<core::matrix4> &global_matrices)
|
||||
}
|
||||
}
|
||||
|
||||
// clear skinning helper array
|
||||
for (std::vector<char> &buf : Vertices_Moved)
|
||||
std::fill(buf.begin(), buf.end(), false);
|
||||
|
||||
// skin starting with the root joints
|
||||
for (size_t i = 0; i < AllJoints.size(); ++i) {
|
||||
// Premultiply with global inversed matrices, if present
|
||||
// (which they should be for joints with weights)
|
||||
std::vector<core::matrix4> joint_transforms = global_matrices;
|
||||
for (u16 i = 0; i < AllJoints.size(); ++i) {
|
||||
auto *joint = AllJoints[i];
|
||||
if (joint->Weights.empty())
|
||||
continue;
|
||||
|
||||
// Find this joints pull on vertices
|
||||
// Note: It is assumed that the global inversed matrix has been calculated at this point.
|
||||
core::matrix4 jointVertexPull = global_matrices[i] * joint->GlobalInversedMatrix.value();
|
||||
|
||||
core::vector3df thisVertexMove, thisNormalMove;
|
||||
|
||||
auto &buffersUsed = *SkinningBuffers;
|
||||
|
||||
// Skin Vertices, Positions and Normals
|
||||
for (const auto &weight : joint->Weights) {
|
||||
// Pull this vertex...
|
||||
jointVertexPull.transformVect(thisVertexMove, weight.StaticPos);
|
||||
|
||||
if (AnimateNormals) {
|
||||
thisNormalMove = jointVertexPull.rotateAndScaleVect(weight.StaticNormal);
|
||||
thisNormalMove.normalize(); // must renormalize after potentially scaling
|
||||
}
|
||||
|
||||
if (!(*(weight.Moved))) {
|
||||
*(weight.Moved) = true;
|
||||
|
||||
buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Pos = thisVertexMove * weight.strength;
|
||||
|
||||
if (AnimateNormals)
|
||||
buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Normal = thisNormalMove * weight.strength;
|
||||
|
||||
//*(weight._Pos) = thisVertexMove * weight.strength;
|
||||
} else {
|
||||
buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Pos += thisVertexMove * weight.strength;
|
||||
|
||||
if (AnimateNormals)
|
||||
buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Normal += thisNormalMove * weight.strength;
|
||||
|
||||
//*(weight._Pos) += thisVertexMove * weight.strength;
|
||||
}
|
||||
}
|
||||
if (joint->GlobalInversedMatrix)
|
||||
joint_transforms[i] = joint_transforms[i] * (*joint->GlobalInversedMatrix);
|
||||
}
|
||||
|
||||
for (auto *buffer : *SkinningBuffers)
|
||||
buffer->setDirty(EBT_VERTEX);
|
||||
for (auto *buffer : *SkinningBuffers) {
|
||||
if (buffer->Weights)
|
||||
buffer->Weights->skin(buffer->getVertexBuffer(), joint_transforms);
|
||||
}
|
||||
}
|
||||
|
||||
//! Gets joint count.
|
||||
@@ -211,10 +160,6 @@ u32 SkinnedMesh::getTextureSlot(u32 meshbufNr) const
|
||||
return TextureSlots.at(meshbufNr);
|
||||
}
|
||||
|
||||
void SkinnedMesh::setTextureSlot(u32 meshbufNr, u32 textureSlot) {
|
||||
TextureSlots.at(meshbufNr) = textureSlot;
|
||||
}
|
||||
|
||||
//! set the hardware mapping hint, for driver
|
||||
void SkinnedMesh::setHardwareMappingHint(E_HARDWARE_MAPPING newMappingHint,
|
||||
E_BUFFER_TYPE buffer)
|
||||
@@ -230,29 +175,20 @@ void SkinnedMesh::setDirty(E_BUFFER_TYPE buffer)
|
||||
LocalBuffers[i]->setDirty(buffer);
|
||||
}
|
||||
|
||||
void SkinnedMesh::refreshJointCache()
|
||||
void SkinnedMesh::updateStaticPose()
|
||||
{
|
||||
// copy cache from the mesh...
|
||||
for (auto *joint : AllJoints) {
|
||||
for (auto &weight : joint->Weights) {
|
||||
const u16 buffer_id = weight.buffer_id;
|
||||
const u32 vertex_id = weight.vertex_id;
|
||||
weight.StaticPos = LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos;
|
||||
weight.StaticNormal = LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal;
|
||||
}
|
||||
for (auto *buf : LocalBuffers) {
|
||||
if (buf->Weights)
|
||||
buf->Weights->updateStaticPose(buf->getVertexBuffer());
|
||||
}
|
||||
}
|
||||
|
||||
void SkinnedMesh::resetAnimation()
|
||||
{
|
||||
// copy from the cache to the mesh...
|
||||
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;
|
||||
}
|
||||
for (auto *buf : LocalBuffers) {
|
||||
if (buf->Weights)
|
||||
buf->Weights->resetToStatic(buf->getVertexBuffer());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,8 +213,8 @@ bool SkinnedMesh::checkForAnimation() const
|
||||
}
|
||||
|
||||
// meshes with weights are animatable
|
||||
for (auto *joint : AllJoints) {
|
||||
if (!joint->Weights.empty()) {
|
||||
for (auto *buf : LocalBuffers) {
|
||||
if (buf->Weights) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -299,40 +235,6 @@ void SkinnedMesh::prepareForSkinning()
|
||||
EndFrame = std::max(EndFrame, joint->keys.getEndFrame());
|
||||
}
|
||||
|
||||
for (auto *joint : AllJoints) {
|
||||
for (auto &weight : joint->Weights) {
|
||||
const u16 buffer_id = weight.buffer_id;
|
||||
const u32 vertex_id = weight.vertex_id;
|
||||
|
||||
// check for invalid ids
|
||||
if (buffer_id >= LocalBuffers.size()) {
|
||||
os::Printer::log("Skinned Mesh: Weight buffer id too large", ELL_WARNING);
|
||||
weight.buffer_id = weight.vertex_id = 0;
|
||||
} else if (vertex_id >= LocalBuffers[buffer_id]->getVertexCount()) {
|
||||
os::Printer::log("Skinned Mesh: Weight vertex id too large", ELL_WARNING);
|
||||
weight.buffer_id = weight.vertex_id = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < Vertices_Moved.size(); ++i)
|
||||
for (u32 j = 0; j < Vertices_Moved[i].size(); ++j)
|
||||
Vertices_Moved[i][j] = false;
|
||||
|
||||
// For skinning: cache weight values for speed
|
||||
for (auto *joint : AllJoints) {
|
||||
for (auto &weight : joint->Weights) {
|
||||
const u16 buffer_id = weight.buffer_id;
|
||||
const u32 vertex_id = weight.vertex_id;
|
||||
|
||||
weight.Moved = &Vertices_Moved[buffer_id][vertex_id];
|
||||
weight.StaticPos = LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos;
|
||||
weight.StaticNormal = LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal;
|
||||
}
|
||||
}
|
||||
|
||||
normalizeWeights();
|
||||
|
||||
for (auto *joint : AllJoints) {
|
||||
joint->keys.cleanup();
|
||||
}
|
||||
@@ -340,29 +242,27 @@ void SkinnedMesh::prepareForSkinning()
|
||||
|
||||
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;
|
||||
std::vector<bool> animated;
|
||||
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;
|
||||
}
|
||||
auto *buf = LocalBuffers[mb];
|
||||
animated.clear();
|
||||
animated.resize(buf->getVertexCount(), false);
|
||||
if (buf->Weights) {
|
||||
for (u32 vert_id : buf->Weights->animated_vertices.value()) {
|
||||
animated[vert_id] = true;
|
||||
}
|
||||
}
|
||||
for (u32 v = 0; v < buf->getVertexCount(); v++) {
|
||||
if (animated[v])
|
||||
continue;
|
||||
|
||||
auto pos = getMeshBuffer(mb)->getVertexBuffer()->getPosition(v);
|
||||
if (!first) {
|
||||
StaticPartsBox.addInternalPoint(pos);
|
||||
} else {
|
||||
StaticPartsBox.reset(pos);
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -370,21 +270,33 @@ void SkinnedMesh::calculateStaticBoundingBox()
|
||||
|
||||
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;
|
||||
std::vector<std::optional<core::aabbox3df>> joint_boxes(AllJoints.size());
|
||||
for (auto *buf : LocalBuffers) {
|
||||
const auto &weights = buf->Weights;
|
||||
if (!weights)
|
||||
continue;
|
||||
for (u32 vert_id : weights->animated_vertices.value()) {
|
||||
const auto pos = buf->getVertex(vert_id)->Pos;
|
||||
for (u16 j = 0; j < WeightBuffer::MAX_WEIGHTS_PER_VERTEX; j++) {
|
||||
const u16 joint_id = weights->getJointIds(vert_id)[j];
|
||||
const SJoint *joint = AllJoints[joint_id];
|
||||
const f32 weight = weights->getWeights(vert_id)[j];
|
||||
if (core::equals(weight, 0.0f))
|
||||
continue;
|
||||
auto trans_pos = pos;
|
||||
joint->GlobalInversedMatrix.value().transformVect(trans_pos);
|
||||
if (joint_boxes[joint_id]) {
|
||||
joint_boxes[joint_id]->addInternalPoint(trans_pos);
|
||||
} else {
|
||||
joint_boxes[joint_id] = core::aabbox3df{trans_pos};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (u16 joint_id = 0; joint_id < AllJoints.size(); joint_id++) {
|
||||
auto *joint = AllJoints[joint_id];
|
||||
joint->LocalBoundingBox = joint_boxes[joint_id].value_or(core::aabbox3df{{0,0,0}});
|
||||
}
|
||||
}
|
||||
|
||||
void SkinnedMesh::calculateBufferBoundingBoxes()
|
||||
@@ -402,15 +314,16 @@ void SkinnedMesh::recalculateBaseBoundingBoxes() {
|
||||
calculateBufferBoundingBoxes();
|
||||
}
|
||||
|
||||
void SkinnedMesh::topoSortJoints()
|
||||
void SkinnedMeshBuilder::topoSortJoints()
|
||||
{
|
||||
size_t n = AllJoints.size();
|
||||
auto &joints = getJoints();
|
||||
const size_t n = joints.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)
|
||||
if (auto parentId = joints[i]->ParentJointID)
|
||||
children[*parentId].push_back(i);
|
||||
else
|
||||
new_to_old_id.push_back(i);
|
||||
@@ -427,76 +340,93 @@ void SkinnedMesh::topoSortJoints()
|
||||
for (u16 i = 0; i < n; ++i)
|
||||
old_to_new_id[new_to_old_id[i]] = i;
|
||||
|
||||
std::vector<SJoint *> joints(n);
|
||||
std::vector<SJoint *> sorted_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];
|
||||
auto *joint = joints[new_to_old_id[i]];
|
||||
if (auto parentId = joint->ParentJointID)
|
||||
joint->ParentJointID = old_to_new_id[*parentId];
|
||||
sorted_joints[i] = joint;
|
||||
joint->JointID = i;
|
||||
}
|
||||
AllJoints = std::move(joints);
|
||||
|
||||
// Verify that the topological ordering is correct
|
||||
for (u16 i = 0; i < n; ++i) {
|
||||
if (auto pjid = AllJoints[i]->ParentJointID)
|
||||
if (auto pjid = sorted_joints[i]->ParentJointID)
|
||||
assert(*pjid < i);
|
||||
}
|
||||
getJoints() = std::move(sorted_joints);
|
||||
|
||||
for (auto &weight : weights) {
|
||||
weight.joint_id = old_to_new_id[weight.joint_id];
|
||||
}
|
||||
}
|
||||
|
||||
//! called by loader after populating with mesh and bone data
|
||||
SkinnedMesh *SkinnedMeshBuilder::finalize()
|
||||
SkinnedMesh *SkinnedMeshBuilder::finalize() &&
|
||||
{
|
||||
os::Printer::log("Skinned Mesh - finalize", ELL_DEBUG);
|
||||
|
||||
// Topologically sort the joints such that parents come before their children.
|
||||
// From this point on, transformations can be calculated in linear order.
|
||||
// (see e.g. SkinnedMesh::calculateGlobalMatrices)
|
||||
topoSortJoints();
|
||||
|
||||
// Set array sizes
|
||||
for (u32 i = 0; i < LocalBuffers.size(); ++i) {
|
||||
Vertices_Moved.emplace_back(LocalBuffers[i]->getVertexCount());
|
||||
}
|
||||
|
||||
prepareForSkinning();
|
||||
mesh->prepareForSkinning();
|
||||
|
||||
std::vector<core::matrix4> matrices;
|
||||
matrices.reserve(AllJoints.size());
|
||||
for (auto *joint : AllJoints) {
|
||||
matrices.reserve(getJoints().size());
|
||||
for (auto *joint : getJoints()) {
|
||||
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);
|
||||
mesh->calculateGlobalMatrices(matrices);
|
||||
|
||||
for (size_t i = 0; i < AllJoints.size(); ++i) {
|
||||
auto *joint = AllJoints[i];
|
||||
for (size_t i = 0; i < getJoints().size(); ++i) {
|
||||
auto *joint = getJoints()[i];
|
||||
if (!joint->GlobalInversedMatrix) {
|
||||
joint->GlobalInversedMatrix = matrices[i];
|
||||
joint->GlobalInversedMatrix->makeInverse();
|
||||
}
|
||||
// rigid animation for non animated meshes
|
||||
for (u32 attachedMeshIdx : joint->AttachedMeshes) {
|
||||
SSkinMeshBuffer *Buffer = (*SkinningBuffers)[attachedMeshIdx];
|
||||
SSkinMeshBuffer *Buffer = (*mesh->SkinningBuffers)[attachedMeshIdx];
|
||||
Buffer->Transformation = matrices[i];
|
||||
}
|
||||
}
|
||||
|
||||
recalculateBaseBoundingBoxes();
|
||||
StaticPoseBox = calculateBoundingBox(matrices);
|
||||
for (const auto &weight : weights) {
|
||||
auto *buf = mesh->LocalBuffers.at(weight.buffer_id);
|
||||
if (!buf->Weights)
|
||||
buf->Weights = WeightBuffer(buf->getVertexCount());
|
||||
buf->Weights->addWeight(weight.vertex_id, weight.joint_id, weight.strength);
|
||||
}
|
||||
|
||||
return this;
|
||||
for (auto *buffer : mesh->LocalBuffers) {
|
||||
if (buffer->Weights)
|
||||
buffer->Weights->finalize();
|
||||
}
|
||||
mesh->updateStaticPose();
|
||||
|
||||
mesh->recalculateBaseBoundingBoxes();
|
||||
mesh->StaticPoseBox = mesh->calculateBoundingBox(matrices);
|
||||
|
||||
return mesh.release();
|
||||
}
|
||||
|
||||
scene::SSkinMeshBuffer *SkinnedMeshBuilder::addMeshBuffer()
|
||||
{
|
||||
scene::SSkinMeshBuffer *buffer = new scene::SSkinMeshBuffer();
|
||||
TextureSlots.push_back(LocalBuffers.size());
|
||||
LocalBuffers.push_back(buffer);
|
||||
mesh->TextureSlots.push_back(mesh->LocalBuffers.size());
|
||||
mesh->LocalBuffers.push_back(buffer);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void SkinnedMeshBuilder::addMeshBuffer(SSkinMeshBuffer *meshbuf)
|
||||
u32 SkinnedMeshBuilder::addMeshBuffer(SSkinMeshBuffer *meshbuf)
|
||||
{
|
||||
TextureSlots.push_back(LocalBuffers.size());
|
||||
LocalBuffers.push_back(meshbuf);
|
||||
mesh->TextureSlots.push_back(mesh->LocalBuffers.size());
|
||||
mesh->LocalBuffers.push_back(meshbuf);
|
||||
return mesh->getMeshBufferCount() - 1;
|
||||
}
|
||||
|
||||
SkinnedMesh::SJoint *SkinnedMeshBuilder::addJoint(SJoint *parent)
|
||||
@@ -504,8 +434,8 @@ SkinnedMesh::SJoint *SkinnedMeshBuilder::addJoint(SJoint *parent)
|
||||
SJoint *joint = new SJoint;
|
||||
joint->setParent(parent);
|
||||
|
||||
joint->JointID = AllJoints.size();
|
||||
AllJoints.push_back(joint);
|
||||
joint->JointID = getJoints().size();
|
||||
getJoints().push_back(joint);
|
||||
|
||||
return joint;
|
||||
}
|
||||
@@ -528,51 +458,12 @@ void SkinnedMeshBuilder::addRotationKey(SJoint *joint, f32 frame, core::quaterni
|
||||
joint->keys.rotation.pushBack(frame, rot);
|
||||
}
|
||||
|
||||
SkinnedMesh::SWeight *SkinnedMeshBuilder::addWeight(SJoint *joint)
|
||||
void SkinnedMeshBuilder::addWeight(SJoint *joint, u16 buf_id, u32 vert_id, f32 strength)
|
||||
{
|
||||
if (!joint)
|
||||
return nullptr;
|
||||
|
||||
joint->Weights.emplace_back();
|
||||
return &joint->Weights.back();
|
||||
}
|
||||
|
||||
void SkinnedMesh::normalizeWeights()
|
||||
{
|
||||
// note: unsure if weights ids are going to be used.
|
||||
|
||||
// Normalise the weights on bones....
|
||||
|
||||
std::vector<std::vector<f32>> verticesTotalWeight;
|
||||
|
||||
verticesTotalWeight.reserve(LocalBuffers.size());
|
||||
for (u32 i = 0; i < LocalBuffers.size(); ++i) {
|
||||
verticesTotalWeight.emplace_back(LocalBuffers[i]->getVertexCount());
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < verticesTotalWeight.size(); ++i)
|
||||
for (u32 j = 0; j < verticesTotalWeight[i].size(); ++j)
|
||||
verticesTotalWeight[i][j] = 0;
|
||||
|
||||
for (auto *joint : AllJoints) {
|
||||
auto &weights = joint->Weights;
|
||||
|
||||
weights.erase(std::remove_if(weights.begin(), weights.end(), [](const auto &weight) {
|
||||
return weight.strength <= 0;
|
||||
}), weights.end());
|
||||
|
||||
for (const auto &weight : weights) {
|
||||
verticesTotalWeight[weight.buffer_id][weight.vertex_id] += weight.strength;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto *joint : AllJoints) {
|
||||
for (auto &weight : joint->Weights) {
|
||||
const f32 total = verticesTotalWeight[weight.buffer_id][weight.vertex_id];
|
||||
if (total != 0 && total != 1)
|
||||
weight.strength /= total;
|
||||
}
|
||||
}
|
||||
assert(joint);
|
||||
if (strength <= 0.0f)
|
||||
return;
|
||||
weights.emplace_back(Weight{joint->JointID, buf_id, vert_id, strength});
|
||||
}
|
||||
|
||||
void SkinnedMesh::convertMeshToTangents()
|
||||
|
||||
124
irr/src/WeightBuffer.cpp
Normal file
124
irr/src/WeightBuffer.cpp
Normal file
@@ -0,0 +1,124 @@
|
||||
// Copyright (C) 2025 Lars Müller
|
||||
// For conditions of distribution and use, see copyright notice in irrlicht.h
|
||||
|
||||
#include "WeightBuffer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
|
||||
namespace scene {
|
||||
|
||||
void WeightBuffer::VertexWeights::addWeight(u16 joint_id, f32 weight)
|
||||
{
|
||||
assert(weight >= 0.0f);
|
||||
auto min_weight = std::min_element(weights.begin(), weights.end());
|
||||
if (*min_weight > weight)
|
||||
return;
|
||||
|
||||
*min_weight = weight;
|
||||
joint_ids[std::distance(weights.begin(), min_weight)] = joint_id;
|
||||
}
|
||||
|
||||
void WeightBuffer::addWeight(u32 vertex_id, u16 joint_id, f32 weight)
|
||||
{
|
||||
weights.at(vertex_id).addWeight(joint_id, weight);
|
||||
}
|
||||
|
||||
void WeightBuffer::VertexWeights::skinVertex(core::vector3df &pos, core::vector3df &normal,
|
||||
const std::vector<core::matrix4> &joint_transforms) const
|
||||
{
|
||||
f32 total_weight = 0.0f;
|
||||
core::vector3df skinned_pos;
|
||||
core::vector3df skinned_normal;
|
||||
for (u16 i = 0; i < MAX_WEIGHTS_PER_VERTEX; ++i) {
|
||||
const u16 joint_id = joint_ids[i];
|
||||
const f32 weight = weights[i];
|
||||
if (core::equals(weight, 0.0f))
|
||||
continue;
|
||||
|
||||
const auto &transform = joint_transforms[joint_id];
|
||||
core::vector3df transformed_pos = pos;
|
||||
transform.transformVect(transformed_pos);
|
||||
skinned_pos += weight * transformed_pos;
|
||||
skinned_normal += weight * transform.rotateAndScaleVect(normal);
|
||||
total_weight += weight;
|
||||
}
|
||||
if (core::equals(total_weight, 0.0f))
|
||||
return;
|
||||
|
||||
pos = skinned_pos;
|
||||
// Need to renormalize normal after potentially scaling
|
||||
normal = skinned_normal.normalize();
|
||||
}
|
||||
|
||||
void WeightBuffer::skinVertex(u32 vertex_id, core::vector3df &pos, core::vector3df &normal,
|
||||
const std::vector<core::matrix4> &joint_transforms) const
|
||||
{
|
||||
return weights[vertex_id].skinVertex(pos, normal, joint_transforms);
|
||||
}
|
||||
|
||||
void WeightBuffer::skin(IVertexBuffer *dst,
|
||||
const std::vector<core::matrix4> &joint_transforms) const
|
||||
{
|
||||
assert(animated_vertices.has_value());
|
||||
for (u32 i = 0; i < animated_vertices->size(); ++i) {
|
||||
|
||||
u32 vertex_id = (*animated_vertices)[i];
|
||||
auto pos = static_positions[i];
|
||||
auto normal = static_normals[i];
|
||||
skinVertex(vertex_id, pos, normal, joint_transforms);
|
||||
dst->getPosition(vertex_id) = pos;
|
||||
dst->getNormal(vertex_id) = normal;
|
||||
}
|
||||
if (!animated_vertices->empty())
|
||||
dst->setDirty();
|
||||
}
|
||||
|
||||
void WeightBuffer::finalize()
|
||||
{
|
||||
// Normalizes weights so that they sum to 1.0 per vertex,
|
||||
// stores which vertices are animated.
|
||||
assert(!animated_vertices.has_value());
|
||||
animated_vertices.emplace();
|
||||
for (u32 i = 0; i < size(); ++i) {
|
||||
auto &weights_i = weights[i].weights;
|
||||
f32 total_weight = std::accumulate(weights_i.begin(), weights_i.end(), 0.0f);
|
||||
if (core::equals(total_weight, 0.0f)) {
|
||||
std::fill(weights_i.begin(), weights_i.end(), 0.0f);
|
||||
continue;
|
||||
}
|
||||
animated_vertices->emplace_back(i);
|
||||
if (core::equals(total_weight, 1.0f))
|
||||
continue;
|
||||
for (auto &strength : weights_i)
|
||||
strength /= total_weight;
|
||||
}
|
||||
animated_vertices->shrink_to_fit();
|
||||
}
|
||||
|
||||
void WeightBuffer::updateStaticPose(const IVertexBuffer *vbuf)
|
||||
{
|
||||
if (!static_normals)
|
||||
static_normals = std::make_unique<core::vector3df[]>(animated_vertices->size());
|
||||
if (!static_positions)
|
||||
static_positions = std::make_unique<core::vector3df[]>(animated_vertices->size());
|
||||
for (size_t idx = 0; idx < animated_vertices->size(); ++idx) {
|
||||
u32 vertex_id = (*animated_vertices)[idx];
|
||||
static_positions[idx] = vbuf->getPosition(vertex_id);
|
||||
static_normals[idx] = vbuf->getNormal(vertex_id);
|
||||
}
|
||||
}
|
||||
|
||||
void WeightBuffer::resetToStatic(IVertexBuffer *vbuf) const
|
||||
{
|
||||
assert(animated_vertices.has_value());
|
||||
for (size_t idx = 0; idx < animated_vertices->size(); ++idx) {
|
||||
u32 vertex_id = (*animated_vertices)[idx];
|
||||
vbuf->getPosition(vertex_id) = static_positions[idx];
|
||||
vbuf->getNormal(vertex_id) = static_normals[idx];
|
||||
}
|
||||
if (!animated_vertices->empty())
|
||||
vbuf->setDirty();
|
||||
}
|
||||
|
||||
} // end namespace scene
|
||||
@@ -1,4 +1,4 @@
|
||||
// Minetest
|
||||
// Luanti
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
#include "content/subgames.h"
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "IReadFile.h"
|
||||
#include "ISceneManager.h"
|
||||
#include "SkinnedMesh.h"
|
||||
#include "SSkinMeshBuffer.h"
|
||||
#include "irrlicht.h"
|
||||
|
||||
#include "catch.h"
|
||||
@@ -394,7 +395,7 @@ SECTION("simple skin")
|
||||
const auto joints = csm->getAllJoints();
|
||||
REQUIRE(joints.size() == 3);
|
||||
|
||||
const auto findJoint = [&](const std::function<bool(const SkinnedMesh::SJoint*)> &predicate) {
|
||||
const auto find_joint = [&](const std::function<bool(const SkinnedMesh::SJoint*)> &predicate) {
|
||||
for (const auto *joint : joints) {
|
||||
if (predicate(joint)) {
|
||||
return joint;
|
||||
@@ -404,7 +405,7 @@ SECTION("simple skin")
|
||||
};
|
||||
|
||||
// Check the node hierarchy
|
||||
const auto child = findJoint([&](auto *joint) {
|
||||
const auto child = find_joint([&](auto *joint) {
|
||||
return !!joint->ParentJointID;
|
||||
});
|
||||
const auto *parent = joints.at(*child->ParentJointID);
|
||||
@@ -430,46 +431,51 @@ SECTION("simple skin")
|
||||
}
|
||||
}
|
||||
|
||||
const auto weights = [&](const SkinnedMesh::SJoint *joint) {
|
||||
// Find the mesh buffer and search the weights
|
||||
const auto *buf = dynamic_cast<scene::SSkinMeshBuffer *>(mesh->getMeshBuffer(0));
|
||||
REQUIRE(buf);
|
||||
REQUIRE(buf->Weights.has_value());
|
||||
std::vector<f32> weights(buf->getVertexCount(), 0.0f);
|
||||
for (size_t i = 0; i < buf->getVertexCount(); ++i) {
|
||||
const auto &joint_ids = buf->Weights->getJointIds(i);
|
||||
const auto it = std::find(joint_ids.begin(), joint_ids.end(), joint->JointID);
|
||||
if (it == joint_ids.end())
|
||||
continue;
|
||||
weights[i] = buf->Weights->getWeights(i)[std::distance(joint_ids.begin(), it)];
|
||||
}
|
||||
return weights;
|
||||
};
|
||||
|
||||
SECTION("weights are correct")
|
||||
{
|
||||
const auto weights = [&](const SkinnedMesh::SJoint *joint) {
|
||||
std::unordered_map<u32, f32> weights;
|
||||
for (std::size_t i = 0; i < joint->Weights.size(); ++i) {
|
||||
const auto weight = joint->Weights[i];
|
||||
REQUIRE(weight.buffer_id == 0);
|
||||
weights[weight.vertex_id] = weight.strength;
|
||||
}
|
||||
return weights;
|
||||
};
|
||||
const auto parentWeights = weights(parent);
|
||||
const auto childWeights = weights(child);
|
||||
const auto parent_weights = weights(parent);
|
||||
const auto child_weights = weights(child);
|
||||
|
||||
const auto checkWeights = [&](u32 index, f32 parentWeight, f32 childWeight) {
|
||||
const auto getWeight = [](auto weights, auto index) {
|
||||
const auto it = weights.find(index);
|
||||
return it == weights.end() ? 0.0f : it->second;
|
||||
};
|
||||
CHECK(getWeight(parentWeights, index) == parentWeight);
|
||||
CHECK(getWeight(childWeights, index) == childWeight);
|
||||
const auto check_weights = [&](u32 vert_idx, f32 parent_weight, f32 child_weight) {
|
||||
CHECK(parent_weights[vert_idx] == parent_weight);
|
||||
CHECK(child_weights[vert_idx] == child_weight);
|
||||
};
|
||||
checkWeights(0, 1.00, 0.00);
|
||||
checkWeights(1, 1.00, 0.00);
|
||||
checkWeights(2, 0.75, 0.25);
|
||||
checkWeights(3, 0.75, 0.25);
|
||||
checkWeights(4, 0.50, 0.50);
|
||||
checkWeights(5, 0.50, 0.50);
|
||||
checkWeights(6, 0.25, 0.75);
|
||||
checkWeights(7, 0.25, 0.75);
|
||||
checkWeights(8, 0.00, 1.00);
|
||||
checkWeights(9, 0.00, 1.00);
|
||||
check_weights(0, 1.00, 0.00);
|
||||
check_weights(1, 1.00, 0.00);
|
||||
check_weights(2, 0.75, 0.25);
|
||||
check_weights(3, 0.75, 0.25);
|
||||
check_weights(4, 0.50, 0.50);
|
||||
check_weights(5, 0.50, 0.50);
|
||||
check_weights(6, 0.25, 0.75);
|
||||
check_weights(7, 0.25, 0.75);
|
||||
check_weights(8, 0.00, 1.00);
|
||||
check_weights(9, 0.00, 1.00);
|
||||
}
|
||||
|
||||
SECTION("there should be a third node not involved in skinning")
|
||||
{
|
||||
const auto other = findJoint([&](auto joint) {
|
||||
const auto other = find_joint([&](auto joint) {
|
||||
return joint != child && joint != parent;
|
||||
});
|
||||
CHECK(other->Weights.empty());
|
||||
const auto other_weights = weights(other);
|
||||
CHECK(std::all_of(other_weights.begin(), other_weights.end(),
|
||||
[](f32 weight) { return weight == 0.0f; }));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user