From 9938b02eee92dba1140f0fabd717a02e1999c7f7 Mon Sep 17 00:00:00 2001 From: Lars Mueller Date: Tue, 29 Apr 2025 01:14:12 +0200 Subject: [PATCH 1/3] Rudimentary static morph support go brrr --- games/devtest/mods/gltf/init.lua | 9 + .../mods/gltf/models/gltf_morph_static.glb | Bin 0 -> 53656 bytes .../mods/gltf/models/gltf_morph_static.jpg | Bin 0 -> 49535 bytes irr/include/SSkinMeshBuffer.h | 178 +++++++++++++++++- irr/src/CGLTFMeshFileLoader.cpp | 79 +++++++- irr/src/CGLTFMeshFileLoader.h | 5 +- 6 files changed, 266 insertions(+), 5 deletions(-) create mode 100644 games/devtest/mods/gltf/models/gltf_morph_static.glb create mode 100644 games/devtest/mods/gltf/models/gltf_morph_static.jpg diff --git a/games/devtest/mods/gltf/init.lua b/games/devtest/mods/gltf/init.lua index f6a8f9bdf..bcf18c327 100644 --- a/games/devtest/mods/gltf/init.lua +++ b/games/devtest/mods/gltf/init.lua @@ -30,6 +30,15 @@ end register_entity("snow_man", {"gltf_snow_man.png"}) register_entity("spider", {"gltf_spider.png"}) +core.register_entity("gltf:morph", { + initial_properties = { + visual = "mesh", + mesh = "gltf_morph_static.glb", + textures = {"gltf_morph_static.jpg"}, + backface_culling = false, + visual_size = vector.new(5, 5, 5), + }, +}) core.register_entity("gltf:spider_animated", { initial_properties = { diff --git a/games/devtest/mods/gltf/models/gltf_morph_static.glb b/games/devtest/mods/gltf/models/gltf_morph_static.glb new file mode 100644 index 0000000000000000000000000000000000000000..47314c68348ffa50c1b9d70914e642a0c614d545 GIT binary patch literal 53656 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 H5(()JX%;CUjesH@N+TVDQc}|0Qc8(} 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 VTFlm1nD;WG1*o10kor3L{{c~=dOZLD literal 0 HcmV?d00001 diff --git a/irr/include/SSkinMeshBuffer.h b/irr/include/SSkinMeshBuffer.h index 8b2e26882..085a18851 100644 --- a/irr/include/SSkinMeshBuffer.h +++ b/irr/include/SSkinMeshBuffer.h @@ -7,14 +7,168 @@ #include "IMeshBuffer.h" #include "CVertexBuffer.h" #include "CIndexBuffer.h" +#include "IVertexBuffer.h" #include "S3DVertex.h" +#include "irrTypes.h" +#include "vector3d.h" #include +#include +#include +#include namespace irr { namespace scene { +struct MorphTargetDelta +{ + std::vector positions; + std::vector normals; + std::vector texcoords; + + void add(IVertexBuffer *buf, f32 weight) + { + size_t n = buf->getCount(); + void *data = buf->getData(); + switch (buf->getType()) { + case video::EVT_TANGENTS: + add(data, n, weight); + case video::EVT_2TCOORDS: + add(data, n, weight); + case video::EVT_STANDARD: + default: + add(data, n, weight); + } + buf->setDirty(); + } + + void set(IVertexBuffer *buf) + { + size_t n = buf->getCount(); + void *data = buf->getData(); + switch (buf->getType()) { + case video::EVT_TANGENTS: + set(data, n); + case video::EVT_2TCOORDS: + set(data, n); + case video::EVT_STANDARD: + default: + set(data, n); + } + buf->setDirty(); + } + + struct Flags { + bool positions = false; + bool normals = false; + bool texcoords = false; + }; + void get(IVertexBuffer *buf, Flags flags) + { + size_t n = buf->getCount(); + void *data = buf->getData(); + switch (buf->getType()) { + case video::EVT_TANGENTS: + get(data, n, flags); + case video::EVT_2TCOORDS: + get(data, n, flags); + case video::EVT_STANDARD: + default: + get(data, n, flags); + } + } + +private: + + template + void add(void *vertex_data, size_t n, f32 weight) + { + auto *vertices = static_cast(vertex_data); + if (!positions.empty()) { + add(&VertexType::Pos, + vertices, positions.data(), n, weight); + } + if (!normals.empty()) { + add(&VertexType::Normal, + vertices, normals.data(), n, weight); + } + if (!texcoords.empty()) { + add(&VertexType::TCoords, + vertices, texcoords.data(), n, weight); + } + } + + template + static void add(AttributeType VertexType::* attribute, VertexType *vertex_data, + AttributeType *deltas, size_t n, f32 weight) + { + auto *vertices = static_cast(vertex_data); + for (size_t i = 0; i < n; ++i) { + vertices[i].*attribute += weight * deltas[i]; + } + } + + template + void set(void *vertex_data, size_t n) + { + auto *vertices = static_cast(vertex_data); + if (!positions.empty()) { + set(&VertexType::Pos, + vertices, positions.data(), n); + } + if (!normals.empty()) { + set(&VertexType::Normal, + vertices, normals.data(), n); + } + if (!texcoords.empty()) { + set(&VertexType::TCoords, + vertices, texcoords.data(), n); + } + } + + template + void get(void *vertex_data, size_t n, Flags flags) + { + auto *vertices = static_cast(vertex_data); + if (flags.positions) { + positions.reserve(n); + get(&VertexType::Pos, + vertices, positions, n); + } + if (flags.normals) { + normals.reserve(n); + get(&VertexType::Normal, + vertices, normals, n); + } + if (flags.texcoords) { + texcoords.reserve(n); + get(&VertexType::TCoords, + vertices, texcoords, n); + } + } + + template + static void set(AttributeType VertexType::* attribute, VertexType *vertex_data, + AttributeType *deltas, size_t n) + { + auto *vertices = static_cast(vertex_data); + for (size_t i = 0; i < n; ++i) { + vertices[i].*attribute = deltas[i]; + } + } + + template + static void get(AttributeType VertexType::* attribute, VertexType *vertex_data, + std::vector &deltas, size_t n) + { + auto *vertices = static_cast(vertex_data); + for (size_t i = 0; i < n; ++i) { + deltas.push_back(vertices[i].*attribute); + } + } +}; + //! A mesh buffer able to choose between S3DVertex2TCoords, S3DVertex and S3DVertexTangents at runtime struct SSkinMeshBuffer final : public IMeshBuffer { @@ -217,7 +371,25 @@ public: } //! Call this after changing the positions of any vertex. - void boundingBoxNeedsRecalculated(void) { BoundingBoxNeedsRecalculated = true; } + void boundingBoxNeedsRecalculated() { BoundingBoxNeedsRecalculated = true; } + + void morph(const std::vector &weights) + { + assert(weights.size() == MorphTargets.size()); + resetMorph(); + for (size_t i = 0; i < weights.size(); ++i) { + MorphTargets[i].add(getVertexBuffer(), weights[i]); + if (!MorphTargets[i].positions.empty()) + boundingBoxNeedsRecalculated(); + } + } + + void resetMorph() + { + MorphStaticPose.set(getVertexBuffer()); + if (!MorphStaticPose.positions.empty()) + boundingBoxNeedsRecalculated(); + } SVertexBufferTangents *Vertices_Tangents; SVertexBufferLightMap *Vertices_2TCoords; @@ -226,6 +398,10 @@ public: core::matrix4 Transformation; + // TODO consolidate with Static(Pos|Normal) in weights + MorphTargetDelta MorphStaticPose; + std::vector MorphTargets; + video::SMaterial Material; video::E_VERTEX_TYPE VertexType; diff --git a/irr/src/CGLTFMeshFileLoader.cpp b/irr/src/CGLTFMeshFileLoader.cpp index f70f6692b..91d655693 100644 --- a/irr/src/CGLTFMeshFileLoader.cpp +++ b/irr/src/CGLTFMeshFileLoader.cpp @@ -4,6 +4,7 @@ #include "CGLTFMeshFileLoader.h" #include "SMaterialLayer.h" +#include "SSkinMeshBuffer.h" #include "coreutil.h" #include "SkinnedMesh.h" #include "IAnimatedMesh.h" @@ -177,6 +178,16 @@ SelfType::Accessor::make(const tiniergltf::GlTF &model, std::size_t accessorI return base; } +template +std::vector SelfType::Accessor::toVector() const +{ + std::vector vec; + vec.reserve(getCount()); + for (size_t i = 0; i < getCount(); ++i) + vec.push_back(get(i)); + return vec; +} + #define ACCESSOR_TYPES(T, U, V) \ template <> \ constexpr tiniergltf::Accessor::Type SelfType::Accessor::getType() \ @@ -400,6 +411,7 @@ static video::E_TEXTURE_CLAMP convertTextureWrap(const Wrap wrap) { void SelfType::MeshExtractor::addPrimitive( const tiniergltf::MeshPrimitive &primitive, + const std::optional> &morphWeights, const std::optional skinIdx, SkinnedMesh::SJoint *parent) { @@ -427,6 +439,64 @@ void SelfType::MeshExtractor::addPrimitive( m_irr_model->addMeshBuffer(meshbuf); const auto meshbufNr = m_irr_model->getMeshBufferCount() - 1; + if (auto morphTargets = primitive.targets) { + MorphTargetDelta::Flags used; + std::vector deltas; + deltas.reserve(morphTargets->size()); + for (const auto &morphTarget : *morphTargets) { + MorphTargetDelta delta; + if (morphTarget.position) { + used.positions = true; + delta.positions = Accessor::make( + m_gltf_model, *morphTarget.position).toVector(); + if (delta.positions.size() != n_vertices) + throw std::runtime_error("wrong number of morph target positions"); + } + if (morphTarget.normal) { + used.normals = true; + delta.normals = Accessor::make( + m_gltf_model, *morphTarget.normal).toVector(); + if (delta.normals.size() != n_vertices) + throw std::runtime_error("wrong number of morph target normals"); + } + if (morphTarget.texcoord) { + used.texcoords = true; + const size_t accessorIdx = morphTarget.texcoord->at(0); + const auto componentType = m_gltf_model.accessors->at(accessorIdx).componentType; + if (componentType == tiniergltf::Accessor::ComponentType::FLOAT) { + // If floats are used, they need not be normalized: Wrapping may take effect. + const auto accessor = Accessor>::make(m_gltf_model, accessorIdx); + delta.texcoords.reserve(accessor.getCount()); + for (std::size_t i = 0; i < accessor.getCount(); ++i) { + delta.texcoords.emplace_back(accessor.get(i)); + } + } else { + const auto accessor = createNormalizedValuesAccessor<2>(m_gltf_model, accessorIdx); + const auto count = std::visit([](auto &&a) { return a.getCount(); }, accessor); + delta.texcoords.reserve(count); + for (std::size_t i = 0; i < count; ++i) { + delta.texcoords.emplace_back(getNormalizedValues(accessor, i)); + } + } + if (delta.texcoords.size() != n_vertices) + throw std::runtime_error("wrong number of morph target texcoords"); + } + deltas.emplace_back(std::move(delta)); + } + // Save all morphed attributes in unmorphed pose + meshbuf->MorphStaticPose.get(meshbuf->getVertexBuffer(), used); + meshbuf->MorphTargets = std::move(deltas); + // Apply default weights + if (morphWeights) { + std::vector weights; + weights.reserve(morphWeights->size()); + for (f64 weight : *morphWeights) + weights.push_back(static_cast(weight)); + // TODO does some unnecessary work - resets what we just read + meshbuf->morph(weights); + } + } + if (primitive.material.has_value()) { const auto &material = m_gltf_model.materials->at(*primitive.material); if (material.pbrMetallicRoughness.has_value()) { @@ -525,8 +595,9 @@ void SelfType::MeshExtractor::deferAddMesh( { m_mesh_loaders.emplace_back([=] { for (std::size_t pi = 0; pi < getPrimitiveCount(meshIdx); ++pi) { - const auto &primitive = m_gltf_model.meshes->at(meshIdx).primitives.at(pi); - addPrimitive(primitive, skinIdx, parent); + const auto &mesh = m_gltf_model.meshes->at(meshIdx); + const auto &primitive = mesh.primitives.at(pi); + addPrimitive(primitive, mesh.weights, skinIdx, parent); } }); } @@ -797,7 +868,7 @@ std::optional> SelfType::MeshExtractor::getIndices( * Create a vector of video::S3DVertex (model data) from a mesh & primitive index. */ std::optional> SelfType::MeshExtractor::getVertices( - const tiniergltf::MeshPrimitive &primitive) const + const tiniergltf::MeshPrimitive &primitive) { const auto &attributes = primitive.attributes; const auto positionAccessorIdx = attributes.position; @@ -819,6 +890,8 @@ std::optional> SelfType::MeshExtractor::getVertice const auto &texcoords = attributes.texcoord; if (texcoords.has_value()) { + if (texcoords->size() > 1) + warn("Multiple texture coordinate sets are not supported yet"); const auto tCoordAccessorIdx = texcoords->at(0); copyTCoords(tCoordAccessorIdx, vertices); } diff --git a/irr/src/CGLTFMeshFileLoader.h b/irr/src/CGLTFMeshFileLoader.h index a4eac8baa..2fb5b1f7f 100644 --- a/irr/src/CGLTFMeshFileLoader.h +++ b/irr/src/CGLTFMeshFileLoader.h @@ -65,6 +65,8 @@ private: std::size_t getCount() const { return count; } T get(std::size_t i) const; + std::vector toVector() const; + private: Accessor(const char *ptr, std::size_t byteStride, std::size_t count) : source(BufferSource{ptr, byteStride}), count(count) {} @@ -111,7 +113,7 @@ private: const tiniergltf::MeshPrimitive &primitive) const; std::optional> getVertices( - const tiniergltf::MeshPrimitive &primitive) const; + const tiniergltf::MeshPrimitive &primitive); std::size_t getMeshCount() const; @@ -144,6 +146,7 @@ private: std::vector& vertices) const; void addPrimitive(const tiniergltf::MeshPrimitive &primitive, + const std::optional> &morphWeights, const std::optional skinIdx, SkinnedMesh::SJoint *parent); From 5a09102770c9d710557279961a352fd3ceada2be Mon Sep 17 00:00:00 2001 From: Lars Mueller Date: Wed, 30 Apr 2025 20:14:34 +0200 Subject: [PATCH 2/3] glTF morph animation proof of concept Some things to look at: - Normal recalculation for vertex morphing - Bounding boxes (esp. after rebasing this upon the other PR) --- irr/include/SSkinMeshBuffer.h | 11 ++++- irr/include/SkinnedMesh.h | 86 +++++++++++++++++++++++++++++++-- irr/src/CGLTFMeshFileLoader.cpp | 45 +++++++++++++++-- irr/src/SkinnedMesh.cpp | 70 ++++++++++++++++----------- 4 files changed, 174 insertions(+), 38 deletions(-) diff --git a/irr/include/SSkinMeshBuffer.h b/irr/include/SSkinMeshBuffer.h index 085a18851..c4f7c8ed5 100644 --- a/irr/include/SSkinMeshBuffer.h +++ b/irr/include/SSkinMeshBuffer.h @@ -14,6 +14,7 @@ #include #include #include +#include #include namespace irr @@ -373,10 +374,15 @@ public: //! Call this after changing the positions of any vertex. void boundingBoxNeedsRecalculated() { BoundingBoxNeedsRecalculated = true; } - void morph(const std::vector &weights) + void setMorph(const std::optional> &weights) + { + resetMorph(); + addMorph(weights.value_or(DefaultWeights)); + } + + void addMorph(const std::vector &weights) { assert(weights.size() == MorphTargets.size()); - resetMorph(); for (size_t i = 0; i < weights.size(); ++i) { MorphTargets[i].add(getVertexBuffer(), weights[i]); if (!MorphTargets[i].positions.empty()) @@ -401,6 +407,7 @@ public: // TODO consolidate with Static(Pos|Normal) in weights MorphTargetDelta MorphStaticPose; std::vector MorphTargets; + std::vector DefaultWeights; video::SMaterial Material; video::E_VERTEX_TYPE VertexType; diff --git a/irr/include/SkinnedMesh.h b/irr/include/SkinnedMesh.h index c9ea99365..8d238d6ce 100644 --- a/irr/include/SkinnedMesh.h +++ b/irr/include/SkinnedMesh.h @@ -11,6 +11,7 @@ #include "quaternion.h" #include "vector3d.h" +#include #include #include @@ -166,8 +167,6 @@ public: //! Internal members used by SkinnedMesh friend class SkinnedMesh; char *Moved; - core::vector3df StaticPos; - core::vector3df StaticNormal; }; template @@ -252,26 +251,98 @@ public: } }; + struct WeightChannel + { + std::vector timestamps; + std::vector weights; + bool interpolate = true; + size_t n_weights; + + bool empty() const { + return timestamps.empty(); + } + + f32 getEndFrame() const { + return timestamps.empty() ? 0 : timestamps.back(); + } + + void reserve(size_t n) { + timestamps.reserve(n); + weights.reserve(n); + } + + void cleanup() { + timestamps.shrink_to_fit(); + weights.shrink_to_fit(); + } + + static core::vector3df interpolateValue(core::vector3df from, core::vector3df to, f32 time) { + // Note: `from` and `to` are swapped here compared to quaternion slerp + return to.getInterpolated(from, time); + } + + std::optional> get(f32 time) const { + if (empty()) + return std::nullopt; + + const size_t i = std::distance(timestamps.begin(), + std::lower_bound(timestamps.begin(), timestamps.end(), time)); + if (i == 0) + return getWeightsVec(0); + if (i == timestamps.size()) + return getWeightsVec(i - 1); + + auto res = getWeightsVec(i - 1); + if (!interpolate) + return res; + + f32 prev_time = timestamps[i - 1]; + f32 next_time = timestamps[i]; + f32 progress = (time - prev_time) / (next_time - prev_time); + for (size_t j = 0; j < n_weights; ++j) { + res[j] = progress * *(getWeights(i) + j) + (1.0f - progress) * res[j]; + } + return res; + } + +private: + std::vector::const_iterator getWeights(size_t i) const + { + return weights.begin() + i * n_weights; + } + + std::vector getWeightsVec(size_t i) const + { + std::vector res; + res.insert(res.begin(), getWeights(i), getWeights(i + 1)); + return res; + } + }; + struct Keys { Channel position; Channel rotation; Channel scale; + WeightChannel weights; bool empty() const { - return position.empty() && rotation.empty() && scale.empty(); + return position.empty() && rotation.empty() && scale.empty() && weights.empty(); } void append(const Keys &other) { position.append(other.position); rotation.append(other.rotation); scale.append(other.scale); + // This is only used by .x, which has no morph animation + assert(other.weights.empty()); } f32 getEndFrame() const { return std::max({ position.getEndFrame(), rotation.getEndFrame(), - scale.getEndFrame() + scale.getEndFrame(), + weights.getEndFrame(), }); } @@ -290,6 +361,7 @@ public: position.cleanup(); rotation.cleanup(); scale.cleanup(); + weights.cleanup(); } }; @@ -307,6 +379,9 @@ public: //! List of child joints std::vector Children; + //! List of meshes which may be affected by morph animations targeting this joint + std::vector MorphedMeshes; + //! List of attached meshes std::vector AttachedMeshes; @@ -360,6 +435,9 @@ protected: std::vector *SkinningBuffers; // Meshbuffer to skin, default is to skin localBuffers std::vector LocalBuffers; + //! Buffers involved in skinning. + //! These have a MorphStaticPose which can be used to reset positions & normals. + std::vector SkinnedBuffers; //! Mapping from meshbuffer number to bindable texture slot std::vector TextureSlots; diff --git a/irr/src/CGLTFMeshFileLoader.cpp b/irr/src/CGLTFMeshFileLoader.cpp index 91d655693..ce2a44178 100644 --- a/irr/src/CGLTFMeshFileLoader.cpp +++ b/irr/src/CGLTFMeshFileLoader.cpp @@ -440,6 +440,7 @@ void SelfType::MeshExtractor::addPrimitive( const auto meshbufNr = m_irr_model->getMeshBufferCount() - 1; if (auto morphTargets = primitive.targets) { + parent->MorphedMeshes.push_back(meshbufNr); MorphTargetDelta::Flags used; std::vector deltas; deltas.reserve(morphTargets->size()); @@ -492,8 +493,8 @@ void SelfType::MeshExtractor::addPrimitive( weights.reserve(morphWeights->size()); for (f64 weight : *morphWeights) weights.push_back(static_cast(weight)); - // TODO does some unnecessary work - resets what we just read - meshbuf->morph(weights); + meshbuf->DefaultWeights = weights; + meshbuf->addMorph(weights); } } @@ -522,6 +523,11 @@ void SelfType::MeshExtractor::addPrimitive( return; } + if (!primitive.targets) { + // We use morphing to reset to static pose. + parent->MorphedMeshes.push_back(meshbufNr); + } + // Otherwise: "Only the joint transforms are applied to the skinned mesh; // the transform of the skinned mesh node MUST be ignored." @@ -708,6 +714,18 @@ void SelfType::MeshExtractor::loadSkins() } } +static size_t countMorphTargets(const tiniergltf::GlTF &gltfModel, size_t nodeIdx) +{ + const auto &node = gltfModel.nodes->at(nodeIdx); + const auto &mesh = gltfModel.meshes->at(node.mesh.value()); + size_t morph_targets = 0; + for (const auto &primitive : mesh.primitives) { + if (primitive.targets) + morph_targets = std::max(morph_targets, primitive.targets->size()); + } + return morph_targets; +} + void SelfType::MeshExtractor::loadAnimation(const std::size_t animIdx) { const auto &anim = m_gltf_model.animations->at(animIdx); @@ -731,8 +749,9 @@ void SelfType::MeshExtractor::loadAnimation(const std::size_t animIdx) if (!channel.target.node.has_value()) throw std::runtime_error("no animated node"); + size_t targetNodeIdx = *channel.target.node; - auto *joint = m_loaded_nodes.at(*channel.target.node); + auto *joint = m_loaded_nodes.at(targetNodeIdx); switch (channel.target.path) { case tiniergltf::AnimationChannelTarget::Path::TRANSLATION: { const auto outputAccessor = Accessor::make(m_gltf_model, sampler.output); @@ -767,8 +786,24 @@ void SelfType::MeshExtractor::loadAnimation(const std::size_t animIdx) } break; } - case tiniergltf::AnimationChannelTarget::Path::WEIGHTS: - throw std::runtime_error("no support for morph animations"); + case tiniergltf::AnimationChannelTarget::Path::WEIGHTS: { + // FIXME support normalized bytes and stuff + const auto outputAccessor = Accessor::make(m_gltf_model, sampler.output); + const size_t count = outputAccessor.getCount(); + auto &channel = joint->keys.weights; + channel.n_weights = countMorphTargets(m_gltf_model, targetNodeIdx); + if (channel.n_weights == 0) + throw std::runtime_error("missing morph targets for morph animation"); + if (count % channel.n_weights != 0) + throw std::runtime_error("wrong number of values in morph weight accessor"); + for (size_t i = 0; i < count / channel.n_weights; ++i) { + channel.timestamps.push_back(inputAccessor.get(i)); + for (size_t j = 0; j < channel.n_weights; ++j) { + channel.weights.push_back(outputAccessor.get(i * channel.n_weights + j)); + } + } + break; + } } } } diff --git a/irr/src/SkinnedMesh.cpp b/irr/src/SkinnedMesh.cpp index 938a50e17..b3ee5ad5d 100644 --- a/irr/src/SkinnedMesh.cpp +++ b/irr/src/SkinnedMesh.cpp @@ -8,6 +8,8 @@ #include "IAnimatedMeshSceneNode.h" #include "SSkinMeshBuffer.h" #include "os.h" +#include +#include #include #include @@ -73,6 +75,9 @@ void SkinnedMesh::animateMesh(f32 frame) LastAnimatedFrame = frame; SkinnedLastFrame = false; + // TODO do something if there are too many mesh buffers + std::bitset<256> morphed_buffers; + for (auto *joint : AllJoints) { // The joints can be animated here with no input from their // parents, but for setAnimationMode extra checks are needed @@ -81,6 +86,17 @@ void SkinnedMesh::animateMesh(f32 frame) joint->Animatedposition, joint->Animatedrotation, joint->Animatedscale); + auto weights = joint->keys.weights.get(frame); + for (size_t meshbufIdx : joint->MorphedMeshes) { + LocalBuffers[meshbufIdx]->setMorph(weights); + morphed_buffers.set(meshbufIdx); + } + } + + for (size_t meshbufIdx : SkinnedBuffers) { + if (!morphed_buffers.test(meshbufIdx)) { + LocalBuffers[meshbufIdx]->setMorph(std::nullopt); + } } // Note: @@ -226,11 +242,13 @@ void SkinnedMesh::skinJoint(SJoint *joint, SJoint *parentJoint) // Skin Vertices Positions and Normals... for (const auto &weight : joint->Weights) { + auto *buf = buffersUsed[weight.buffer_id]; // Pull this vertex... - jointVertexPull.transformVect(thisVertexMove, weight.StaticPos); + auto *vertex = buf->getVertex(weight.vertex_id); + jointVertexPull.transformVect(thisVertexMove, vertex->Pos); if (AnimateNormals) { - thisNormalMove = jointVertexPull.rotateAndScaleVect(weight.StaticNormal); + thisNormalMove = jointVertexPull.rotateAndScaleVect(vertex->Normal); thisNormalMove.normalize(); // must renormalize after potentially scaling } @@ -345,12 +363,8 @@ bool SkinnedMesh::setHardwareSkinning(bool on) // set mesh to static pose... for (auto *joint : AllJoints) { - for (const auto &weight : joint->Weights) { - const u16 buffer_id = weight.buffer_id; - const u32 vertex_id = weight.vertex_id; - LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos = weight.StaticPos; - LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal = weight.StaticNormal; - LocalBuffers[buffer_id]->boundingBoxNeedsRecalculated(); + for (auto buf_id : joint->MorphedMeshes) { + LocalBuffers[buf_id]->setMorph(std::nullopt); } } } @@ -360,15 +374,14 @@ bool SkinnedMesh::setHardwareSkinning(bool on) return HardwareSkinning; } +// TODO the only usage of this only needs to refresh normals void SkinnedMesh::refreshJointCache() { // 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_id : joint->MorphedMeshes) { + auto *buf = LocalBuffers[buf_id]; + buf->MorphStaticPose.get(buf->getVertexBuffer(), {true, true, false}); } } } @@ -377,11 +390,8 @@ 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_id : joint->MorphedMeshes) { + LocalBuffers[buf_id]->setMorph(std::nullopt); } } SkinnedLastFrame = false; @@ -449,6 +459,8 @@ void SkinnedMesh::checkForAnimation() if (HasAnimation && !PreparedForSkinning) { PreparedForSkinning = true; + std::unordered_set bufs; + // check for bugs: for (auto *joint : AllJoints) { for (auto &weight : joint->Weights) { @@ -463,6 +475,8 @@ void SkinnedMesh::checkForAnimation() os::Printer::log("Skinned Mesh: Weight vertex id too large", ELL_WARNING); weight.buffer_id = weight.vertex_id = 0; } + + bufs.insert(weight.buffer_id); } } @@ -472,18 +486,20 @@ void SkinnedMesh::checkForAnimation() for (u32 j = 0; j < Vertices_Moved[i].size(); ++j) Vertices_Moved[i][j] = false; - // For skinning: cache weight values for speed + for (auto id : bufs) { + SkinnedBuffers.emplace_back(id); + auto &msp = LocalBuffers[id]->MorphStaticPose; + // Make sure we have static pose data for positions & normals + msp.get(LocalBuffers[id]->getVertexBuffer(), { + msp.positions.empty(), + msp.normals.empty(), + false, + }); + } 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; - - // weight._Pos=&Buffers[buffer_id]->getVertex(vertex_id)->Pos; + weight.Moved = &Vertices_Moved[weight.buffer_id][weight.vertex_id]; } } From 38a704dd1c0a106ac3fa515e0062ce320053b9ae Mon Sep 17 00:00:00 2001 From: Lars Mueller Date: Wed, 30 Apr 2025 20:17:13 +0200 Subject: [PATCH 3/3] Add gltf morph anim tests --- games/devtest/mods/gltf/LICENSE.md | 3 + games/devtest/mods/gltf/init.lua | 26 +++ .../mods/gltf/models/gltf_morph_animated.glb | Bin 0 -> 6752 bytes .../gltf/models/gltf_morph_animated2.gltf | 192 ++++++++++++++++++ 4 files changed, 221 insertions(+) create mode 100644 games/devtest/mods/gltf/models/gltf_morph_animated.glb create mode 100644 games/devtest/mods/gltf/models/gltf_morph_animated2.gltf diff --git a/games/devtest/mods/gltf/LICENSE.md b/games/devtest/mods/gltf/LICENSE.md index 6c3828a4a..9c6b1185c 100644 --- a/games/devtest/mods/gltf/LICENSE.md +++ b/games/devtest/mods/gltf/LICENSE.md @@ -12,3 +12,6 @@ The glTF test models (and corresponding textures) in this mod are all licensed f * Minimal triangle, triangle without indices (`gltf_minimal_triangle.gltf`, `gltf_triangle_without_indices.gltf`) * From [the glTF sample model collection](https://github.com/KhronosGroup/glTF-Sample-Models) * Licensed under CC0 / public domain + + +Morph stuff is from https://github.com/KhronosGroup/glTF-Sample-Assets/ \ No newline at end of file diff --git a/games/devtest/mods/gltf/init.lua b/games/devtest/mods/gltf/init.lua index bcf18c327..591b875cb 100644 --- a/games/devtest/mods/gltf/init.lua +++ b/games/devtest/mods/gltf/init.lua @@ -40,6 +40,32 @@ core.register_entity("gltf:morph", { }, }) +core.register_entity("gltf:morph_animated", { + initial_properties = { + visual = "mesh", + mesh = "gltf_morph_animated.glb", + textures = {}, + backface_culling = false, + visual_size = vector.new(100, 100, 100), + }, + on_activate = function(self) + self.object:set_animation({x = 0, y = 5}, 1) + end +}) + +core.register_entity("gltf:morph_animated2", { + initial_properties = { + visual = "mesh", + mesh = "gltf_morph_animated2.gltf", + textures = {}, + backface_culling = false, + visual_size = vector.new(1, 1, 1), + }, + on_activate = function(self) + self.object:set_animation({x = 0, y = 5}, 1) + end +}) + core.register_entity("gltf:spider_animated", { initial_properties = { visual = "mesh", diff --git a/games/devtest/mods/gltf/models/gltf_morph_animated.glb b/games/devtest/mods/gltf/models/gltf_morph_animated.glb new file mode 100644 index 0000000000000000000000000000000000000000..219d2ac524060e34f13d1253729f563cf76aca97 GIT binary patch literal 6752 zcmeHM3s6+o89oXsAmJfKtH#1ypJ7(m-Mhf@*z+F*MdP|e9;T5JU6#ABX4%Ev#SK9O z6ELf7GL4Tab?j6pv2kteG-_=JteI(QGIsOOF=C3;q}EJIfHZYPr>5DSd+uGlV!Fa) zI@8X0XLi5;ob&(x`Jd<9g&KR=((wqPbu$t2MI-cBX|bt^Gg~Zz+wF9@xtxk7uByRm z6n}f0~y0fIysT89Oc~m*l9e%9#ILvi4 zWSpm#Za7jI4c%Bmt8k9_q|fHH8N5giJ49)5EYF48J7cVShf~JNVe~bk;$&rJM~z*$ zCoEZGHElTOEG*gjY_*=xV0jN8m~Y-*Ki2KTI+OiP+E^8__Tg#hMWa)Ti9PX)U&HZ|mcEgArnyEsw!&YbZ*qjdO%&^p&9S*@R z4cz9sdb{9ar-#SvsuAc((Zo5N)wCC=KlNr$Eo};~V5_P1xVdIgoh%~B+Z^=`R7CXM z=I{uvdZ(RjMTLyZO-pE)RGhQHBQa@2aXAU48yd_mT2i8YZnv~;jo=VmW{;Da!`0B! zFQv@sw7XNRPFG5~!{%wEx;6?fH}!-vXw%d*1&R|;c_K37s~SCmQE=3VE@<`oJ54>j zT5Pqlt?4S&q+sXux-jgSS@g71vWM56ok^oJdUlOgH^Q3m>NOc+ZH_^|Uc3rM&#%>J z#^kP6ryi5P+RTyB&(ekozBX%QI5X7Ya?4Zwce|@mYcj>UlP*uWY!u1YX=(Q9dY#~| z6{O{Qm#xm`v2B!xW{<~Zt7`Cw!zRvDT(UCX$cm~g-&C~3R7M*@#XVkJy1Z<8v56KR zty7z$+D30~E{C2ov>OGN&1`2PQrAh_gf!7(=Z-W@*09S{I_Rs0=wX%FEfhHIPS;Yig|#QW zw=>neUYn((dhew)(oH9;3JHn#9p?(^y)#Rl3Y`ioLy-~f7f&I{E-@1E( z2fBBbI!mL@%o1M-BvLOs>b98ajg?bD-!3Sl;#*WKUG@3wTokHTI$ia(!x3>?=qbcD zW0Yh-$P_gVY;#IVN>>6xikCIga1Sc!(b)WL_`i+pXl#4H zGrIm7kH*nZMIiC-gcH#O6p1ELj6^Xg8bzVW6r)fadI-g$c#5%T3Q9x?D2ZYMnuZ=m zQ_*yaQ(3sT`JxQld_Kc_fdIuQe8iuM-whb?7lRH==kZTEI_R7qcXf8s`2hZNZ!cXN z#8en4KTJtXgnb<|U@phO>;6>8~`6`%9zRPih6wW18*7>B(w~!q2r;>L9D$+NYMq(2+B#r0EH zJ9EjUUIY1O5Xd9kVuA|`$vW#&@|98KK<%h@mh{ZM-RN3E8;k$ z7ta=COFzJ{f8H&I!C<;UoR1!On&JH8dl({gilOGfHHNjnj1p@D1%t^92VT`P>}o5K z=3D9*9yry;aPp#^3?tA%hNgwTVt8`Tn+zk#2MoWO@-ai}_`gf@wEo0>E_^+S;r5|; zh7&%X&QNtcnc<(eQ!5y@UtGlyq3)AKhlM5uw`Is6R6wxMUG`IKgc^Nw$3c?AOWWA@TLJ4^r{KKT_V0xqozrLynjK8hIzbGQG>XXg*SZX$O_+P|3 zVzh9%Zy%m|GZii-`f&`J3;n^97-!6ew%vcgEr+H-#If_Z!;%2+>9625n_|E?>2v%t zngCb6zJck#CSWia#H&q1_`^$)uuR{NFJFy@?z5lZT{GihXi7J3-#8Wa%no4Pmown# z%{TF*ZOM>&^aSo(I1iA?kI_e|ko?!5;xAuR!?VphaY242L@w~*=Kfq*+tY|2-IfoR zx-9r?LJ?HNn(*H?8R6_zJ$~rzRS;7-7oYlUHJ}{{xV5JOW@g_uT;975_(k1@H8TZ> z(VZ}~JZS^_mfeQ)#dToVQE#|BR1e+FxrRB*Jd`iiu*l$r?)Wo#Ti$Pkp}y9c*tJg>N*&yLs)oTW@TJq1qR6j9o22e?Rc_wU^~CUC!kmImff)-0zobBGM?;O1fM_ z6W2<$Ws_^pAlIT*uF<#U+AWZ4x+O`fb^Ttc2cDOEZS82h4sQK2c_OC ZIU)6A|2Q@4#eR*{!~Aurx2X@u{|)ch8?XQX literal 0 HcmV?d00001 diff --git a/games/devtest/mods/gltf/models/gltf_morph_animated2.gltf b/games/devtest/mods/gltf/models/gltf_morph_animated2.gltf new file mode 100644 index 000000000..3b6efd8d0 --- /dev/null +++ b/games/devtest/mods/gltf/models/gltf_morph_animated2.gltf @@ -0,0 +1,192 @@ +{ + "scene" : 0, + "scenes":[ + { + "nodes":[ + 0 + ] + } + ], + "nodes":[ + { + "mesh":0 + } + ], + "meshes":[ + { + "primitives":[ + { + "attributes":{ + "POSITION":1 + }, + "targets":[ + { + "POSITION":2 + }, + { + "POSITION":3 + } + ], + "indices":0 + } + ], + "weights":[ + 0.5, + 0.5 + ] + } + ], + + "animations":[ + { + "samplers":[ + { + "input":4, + "interpolation":"LINEAR", + "output":5 + } + ], + "channels":[ + { + "sampler":0, + "target":{ + "node":0, + "path":"weights" + } + } + ] + } + ], + + "buffers":[ + { + "uri":"data:application/gltf-buffer;base64,AAABAAIAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAA/AAAAPwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIC/AACAPwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA/AACAPwAAAAA=", + "byteLength":116 + }, + { + "uri":"data:application/gltf-buffer;base64,AAAAAAAAgD8AAABAAABAQAAAgEAAAAAAAAAAAAAAAAAAAIA/AACAPwAAgD8AAIA/AAAAAAAAAAAAAAAA", + "byteLength":60 + } + ], + "bufferViews":[ + { + "buffer":0, + "byteOffset":0, + "byteLength":6, + "target":34963 + }, + { + "buffer":0, + "byteOffset":8, + "byteLength":108, + "byteStride":12, + "target":34962 + }, + { + "buffer":1, + "byteOffset":0, + "byteLength":20 + }, + { + "buffer":1, + "byteOffset":20, + "byteLength":40 + } + ], + "accessors":[ + { + "bufferView":0, + "byteOffset":0, + "componentType":5123, + "count":3, + "type":"SCALAR", + "max":[ + 2 + ], + "min":[ + 0 + ] + }, + { + "bufferView":1, + "byteOffset":0, + "componentType":5126, + "count":3, + "type":"VEC3", + "max":[ + 1.0, + 0.5, + 0.0 + ], + "min":[ + 0.0, + 0.0, + 0.0 + ] + }, + { + "bufferView":1, + "byteOffset":36, + "componentType":5126, + "count":3, + "type":"VEC3", + "max":[ + 0.0, + 1.0, + 0.0 + ], + "min":[ + -1.0, + 0.0, + 0.0 + ] + }, + { + "bufferView":1, + "byteOffset":72, + "componentType":5126, + "count":3, + "type":"VEC3", + "max":[ + 1.0, + 1.0, + 0.0 + ], + "min":[ + 0.0, + 0.0, + 0.0 + ] + }, + { + "bufferView":2, + "byteOffset":0, + "componentType":5126, + "count":5, + "type":"SCALAR", + "max":[ + 4.0 + ], + "min":[ + 0.0 + ] + }, + { + "bufferView":3, + "byteOffset":0, + "componentType":5126, + "count":10, + "type":"SCALAR", + "max":[ + 1.0 + ], + "min":[ + 0.0 + ] + } + ], + + "asset":{ + "version":"2.0" + } +} \ No newline at end of file