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 f6a8f9bdf..591b875cb 100644 --- a/games/devtest/mods/gltf/init.lua +++ b/games/devtest/mods/gltf/init.lua @@ -30,6 +30,41 @@ 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: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 = { 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 000000000..219d2ac52 Binary files /dev/null and b/games/devtest/mods/gltf/models/gltf_morph_animated.glb differ 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 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 000000000..47314c683 Binary files /dev/null and b/games/devtest/mods/gltf/models/gltf_morph_static.glb differ diff --git a/games/devtest/mods/gltf/models/gltf_morph_static.jpg b/games/devtest/mods/gltf/models/gltf_morph_static.jpg new file mode 100644 index 000000000..7f63873f9 Binary files /dev/null and b/games/devtest/mods/gltf/models/gltf_morph_static.jpg differ diff --git a/irr/include/SSkinMeshBuffer.h b/irr/include/SSkinMeshBuffer.h index 8b2e26882..c4f7c8ed5 100644 --- a/irr/include/SSkinMeshBuffer.h +++ b/irr/include/SSkinMeshBuffer.h @@ -7,14 +7,169 @@ #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 +#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 +372,30 @@ public: } //! Call this after changing the positions of any vertex. - void boundingBoxNeedsRecalculated(void) { BoundingBoxNeedsRecalculated = true; } + void boundingBoxNeedsRecalculated() { BoundingBoxNeedsRecalculated = true; } + + void setMorph(const std::optional> &weights) + { + resetMorph(); + addMorph(weights.value_or(DefaultWeights)); + } + + void addMorph(const std::vector &weights) + { + assert(weights.size() == MorphTargets.size()); + 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 +404,11 @@ public: core::matrix4 Transformation; + // 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 a527db76e..e878bd1b0 100644 --- a/irr/include/SkinnedMesh.h +++ b/irr/include/SkinnedMesh.h @@ -11,6 +11,7 @@ #include "quaternion.h" #include "vector3d.h" +#include #include #include @@ -179,8 +180,6 @@ public: //! Internal members used by SkinnedMesh friend class SkinnedMesh; char *Moved; - core::vector3df StaticPos; - core::vector3df StaticNormal; }; template @@ -265,26 +264,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(), }); } @@ -303,6 +374,7 @@ public: position.cleanup(); rotation.cleanup(); scale.cleanup(); + weights.cleanup(); } }; @@ -320,6 +392,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; @@ -373,6 +448,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 3f2096f40..ec3837ec6 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() \ @@ -401,6 +412,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) { @@ -428,6 +440,65 @@ void SelfType::MeshExtractor::addPrimitive( m_irr_model->addMeshBuffer(meshbuf); 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()); + 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)); + meshbuf->DefaultWeights = weights; + meshbuf->addMorph(weights); + } + } + if (primitive.material.has_value()) { const auto &material = m_gltf_model.materials->at(*primitive.material); if (material.pbrMetallicRoughness.has_value()) { @@ -453,6 +524,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." @@ -526,8 +602,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); } }); } @@ -638,6 +715,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); @@ -661,8 +750,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); @@ -697,8 +787,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; + } } } } @@ -798,7 +904,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; @@ -820,6 +926,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); 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]; } }