mirror of
https://github.com/luanti-org/luanti.git
synced 2025-06-27 16:36:03 +00:00
Merge 38a704dd1c
into 535d757563
This commit is contained in:
commit
dd8069cfa8
11 changed files with 657 additions and 39 deletions
|
@ -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
|
||||
|
||||
<!-- TODO detail -->
|
||||
Morph stuff is from https://github.com/KhronosGroup/glTF-Sample-Assets/
|
|
@ -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 = {
|
||||
|
|
BIN
games/devtest/mods/gltf/models/gltf_morph_animated.glb
Normal file
BIN
games/devtest/mods/gltf/models/gltf_morph_animated.glb
Normal file
Binary file not shown.
192
games/devtest/mods/gltf/models/gltf_morph_animated2.gltf
Normal file
192
games/devtest/mods/gltf/models/gltf_morph_animated2.gltf
Normal file
|
@ -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"
|
||||
}
|
||||
}
|
BIN
games/devtest/mods/gltf/models/gltf_morph_static.glb
Normal file
BIN
games/devtest/mods/gltf/models/gltf_morph_static.glb
Normal file
Binary file not shown.
BIN
games/devtest/mods/gltf/models/gltf_morph_static.jpg
Normal file
BIN
games/devtest/mods/gltf/models/gltf_morph_static.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 48 KiB |
|
@ -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 <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
namespace irr
|
||||
{
|
||||
namespace scene
|
||||
{
|
||||
|
||||
struct MorphTargetDelta
|
||||
{
|
||||
std::vector<core::vector3df> positions;
|
||||
std::vector<core::vector3df> normals;
|
||||
std::vector<core::vector2df> texcoords;
|
||||
|
||||
void add(IVertexBuffer *buf, f32 weight)
|
||||
{
|
||||
size_t n = buf->getCount();
|
||||
void *data = buf->getData();
|
||||
switch (buf->getType()) {
|
||||
case video::EVT_TANGENTS:
|
||||
add<video::S3DVertexTangents>(data, n, weight);
|
||||
case video::EVT_2TCOORDS:
|
||||
add<video::S3DVertex2TCoords>(data, n, weight);
|
||||
case video::EVT_STANDARD:
|
||||
default:
|
||||
add<video::S3DVertex>(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<video::S3DVertexTangents>(data, n);
|
||||
case video::EVT_2TCOORDS:
|
||||
set<video::S3DVertex2TCoords>(data, n);
|
||||
case video::EVT_STANDARD:
|
||||
default:
|
||||
set<video::S3DVertex>(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<video::S3DVertexTangents>(data, n, flags);
|
||||
case video::EVT_2TCOORDS:
|
||||
get<video::S3DVertex2TCoords>(data, n, flags);
|
||||
case video::EVT_STANDARD:
|
||||
default:
|
||||
get<video::S3DVertex>(data, n, flags);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
template<class VertexType>
|
||||
void add(void *vertex_data, size_t n, f32 weight)
|
||||
{
|
||||
auto *vertices = static_cast<VertexType *>(vertex_data);
|
||||
if (!positions.empty()) {
|
||||
add<VertexType, core::vector3df>(&VertexType::Pos,
|
||||
vertices, positions.data(), n, weight);
|
||||
}
|
||||
if (!normals.empty()) {
|
||||
add<VertexType, core::vector3df>(&VertexType::Normal,
|
||||
vertices, normals.data(), n, weight);
|
||||
}
|
||||
if (!texcoords.empty()) {
|
||||
add<VertexType, core::vector2df>(&VertexType::TCoords,
|
||||
vertices, texcoords.data(), n, weight);
|
||||
}
|
||||
}
|
||||
|
||||
template<class VertexType, class AttributeType>
|
||||
static void add(AttributeType VertexType::* attribute, VertexType *vertex_data,
|
||||
AttributeType *deltas, size_t n, f32 weight)
|
||||
{
|
||||
auto *vertices = static_cast<VertexType *>(vertex_data);
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
vertices[i].*attribute += weight * deltas[i];
|
||||
}
|
||||
}
|
||||
|
||||
template<class VertexType>
|
||||
void set(void *vertex_data, size_t n)
|
||||
{
|
||||
auto *vertices = static_cast<VertexType *>(vertex_data);
|
||||
if (!positions.empty()) {
|
||||
set<VertexType, core::vector3df>(&VertexType::Pos,
|
||||
vertices, positions.data(), n);
|
||||
}
|
||||
if (!normals.empty()) {
|
||||
set<VertexType, core::vector3df>(&VertexType::Normal,
|
||||
vertices, normals.data(), n);
|
||||
}
|
||||
if (!texcoords.empty()) {
|
||||
set<VertexType, core::vector2df>(&VertexType::TCoords,
|
||||
vertices, texcoords.data(), n);
|
||||
}
|
||||
}
|
||||
|
||||
template<class VertexType>
|
||||
void get(void *vertex_data, size_t n, Flags flags)
|
||||
{
|
||||
auto *vertices = static_cast<VertexType *>(vertex_data);
|
||||
if (flags.positions) {
|
||||
positions.reserve(n);
|
||||
get<VertexType, core::vector3df>(&VertexType::Pos,
|
||||
vertices, positions, n);
|
||||
}
|
||||
if (flags.normals) {
|
||||
normals.reserve(n);
|
||||
get<VertexType, core::vector3df>(&VertexType::Normal,
|
||||
vertices, normals, n);
|
||||
}
|
||||
if (flags.texcoords) {
|
||||
texcoords.reserve(n);
|
||||
get<VertexType, core::vector2df>(&VertexType::TCoords,
|
||||
vertices, texcoords, n);
|
||||
}
|
||||
}
|
||||
|
||||
template<class VertexType, class AttributeType>
|
||||
static void set(AttributeType VertexType::* attribute, VertexType *vertex_data,
|
||||
AttributeType *deltas, size_t n)
|
||||
{
|
||||
auto *vertices = static_cast<VertexType *>(vertex_data);
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
vertices[i].*attribute = deltas[i];
|
||||
}
|
||||
}
|
||||
|
||||
template<class VertexType, class AttributeType>
|
||||
static void get(AttributeType VertexType::* attribute, VertexType *vertex_data,
|
||||
std::vector<AttributeType> &deltas, size_t n)
|
||||
{
|
||||
auto *vertices = static_cast<VertexType *>(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<std::vector<f32>> &weights)
|
||||
{
|
||||
resetMorph();
|
||||
addMorph(weights.value_or(DefaultWeights));
|
||||
}
|
||||
|
||||
void addMorph(const std::vector<f32> &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<MorphTargetDelta> MorphTargets;
|
||||
std::vector<f32> DefaultWeights;
|
||||
|
||||
video::SMaterial Material;
|
||||
video::E_VERTEX_TYPE VertexType;
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "quaternion.h"
|
||||
#include "vector3d.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
|
@ -179,8 +180,6 @@ public:
|
|||
//! Internal members used by SkinnedMesh
|
||||
friend class SkinnedMesh;
|
||||
char *Moved;
|
||||
core::vector3df StaticPos;
|
||||
core::vector3df StaticNormal;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
|
@ -265,26 +264,98 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
struct WeightChannel
|
||||
{
|
||||
std::vector<f32> timestamps;
|
||||
std::vector<f32> 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<std::vector<f32>> 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<f32>::const_iterator getWeights(size_t i) const
|
||||
{
|
||||
return weights.begin() + i * n_weights;
|
||||
}
|
||||
|
||||
std::vector<f32> getWeightsVec(size_t i) const
|
||||
{
|
||||
std::vector<f32> res;
|
||||
res.insert(res.begin(), getWeights(i), getWeights(i + 1));
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
struct Keys {
|
||||
Channel<core::vector3df> position;
|
||||
Channel<core::quaternion> rotation;
|
||||
Channel<core::vector3df> 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<SJoint *> Children;
|
||||
|
||||
//! List of meshes which may be affected by morph animations targeting this joint
|
||||
std::vector<size_t> MorphedMeshes;
|
||||
|
||||
//! List of attached meshes
|
||||
std::vector<u32> AttachedMeshes;
|
||||
|
||||
|
@ -373,6 +448,9 @@ protected:
|
|||
std::vector<SSkinMeshBuffer *> *SkinningBuffers; // Meshbuffer to skin, default is to skin localBuffers
|
||||
|
||||
std::vector<SSkinMeshBuffer *> LocalBuffers;
|
||||
//! Buffers involved in skinning.
|
||||
//! These have a MorphStaticPose which can be used to reset positions & normals.
|
||||
std::vector<u32> SkinnedBuffers;
|
||||
//! Mapping from meshbuffer number to bindable texture slot
|
||||
std::vector<u32> TextureSlots;
|
||||
|
||||
|
|
|
@ -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<T>::make(const tiniergltf::GlTF &model, std::size_t accessorI
|
|||
return base;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
std::vector<T> SelfType::Accessor<T>::toVector() const
|
||||
{
|
||||
std::vector<T> 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<T>::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<std::vector<f64>> &morphWeights,
|
||||
const std::optional<std::size_t> 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<MorphTargetDelta> deltas;
|
||||
deltas.reserve(morphTargets->size());
|
||||
for (const auto &morphTarget : *morphTargets) {
|
||||
MorphTargetDelta delta;
|
||||
if (morphTarget.position) {
|
||||
used.positions = true;
|
||||
delta.positions = Accessor<core::vector3df>::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<core::vector3df>::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<std::array<f32, 2>>::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<f32> weights;
|
||||
weights.reserve(morphWeights->size());
|
||||
for (f64 weight : *morphWeights)
|
||||
weights.push_back(static_cast<f32>(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<core::vector3df>::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<f32>::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<std::vector<u16>> SelfType::MeshExtractor::getIndices(
|
|||
* Create a vector of video::S3DVertex (model data) from a mesh & primitive index.
|
||||
*/
|
||||
std::optional<std::vector<video::S3DVertex>> 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<std::vector<video::S3DVertex>> 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);
|
||||
}
|
||||
|
|
|
@ -65,6 +65,8 @@ private:
|
|||
std::size_t getCount() const { return count; }
|
||||
T get(std::size_t i) const;
|
||||
|
||||
std::vector<T> 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<std::vector<video::S3DVertex>> getVertices(
|
||||
const tiniergltf::MeshPrimitive &primitive) const;
|
||||
const tiniergltf::MeshPrimitive &primitive);
|
||||
|
||||
std::size_t getMeshCount() const;
|
||||
|
||||
|
@ -144,6 +146,7 @@ private:
|
|||
std::vector<video::S3DVertex>& vertices) const;
|
||||
|
||||
void addPrimitive(const tiniergltf::MeshPrimitive &primitive,
|
||||
const std::optional<std::vector<f64>> &morphWeights,
|
||||
const std::optional<std::size_t> skinIdx,
|
||||
SkinnedMesh::SJoint *parent);
|
||||
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
#include "IAnimatedMeshSceneNode.h"
|
||||
#include "SSkinMeshBuffer.h"
|
||||
#include "os.h"
|
||||
#include <bitset>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
#include <cassert>
|
||||
|
||||
|
@ -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<u16> 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];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue