1
0
Fork 0
mirror of https://github.com/luanti-org/luanti.git synced 2025-06-27 16:36:03 +00:00
This commit is contained in:
Lars Müller 2025-05-31 10:56:52 -05:00 committed by GitHub
commit dd8069cfa8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 657 additions and 39 deletions

View file

@ -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/

View file

@ -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 = {

Binary file not shown.

View 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"
}
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View file

@ -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;

View file

@ -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;

View file

@ -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);
}

View file

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

View file

@ -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];
}
}