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 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..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);