From b087e2554f6091cb222ae1462ebb927b19b6414b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20M=C3=BCller?= <34514239+appgurueu@users.noreply.github.com> Date: Tue, 24 Dec 2024 15:25:07 +0100 Subject: [PATCH] Add glTF STEP interpolation support (#15525) --- doc/lua_api.md | 2 +- games/devtest/mods/gltf/init.lua | 14 ++++++++++ .../gltf/models/gltf_simple_skin_step.gltf | 1 + irr/src/CGLTFMeshFileLoader.cpp | 27 ++++++++++++++----- 4 files changed, 37 insertions(+), 7 deletions(-) create mode 100644 games/devtest/mods/gltf/models/gltf_simple_skin_step.gltf diff --git a/doc/lua_api.md b/doc/lua_api.md index 91d8f314a..f46c9eb28 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -318,7 +318,7 @@ Many glTF features are not supported *yet*, including: * Animations * Only a single animation is supported, use frame ranges within this animation. - * Only linear interpolation is supported. + * `CUBICSPLINE` interpolation is not supported. * Cameras * Materials * Only base color textures are supported diff --git a/games/devtest/mods/gltf/init.lua b/games/devtest/mods/gltf/init.lua index fd4d13bee..f6a8f9bdf 100644 --- a/games/devtest/mods/gltf/init.lua +++ b/games/devtest/mods/gltf/init.lua @@ -55,6 +55,20 @@ core.register_entity("gltf:simple_skin", { end }) +core.register_entity("gltf:simple_skin_step", { + initial_properties = { + infotext = "Simple skin, but using STEP interpolation", + visual = "mesh", + visual_size = vector.new(5, 5, 5), + mesh = "gltf_simple_skin_step.gltf", + textures = {}, + backface_culling = false + }, + on_activate = function(self) + self.object:set_animation({x = 0, y = 5.5}, 1) + end +}) + -- The claws rendering incorrectly from one side is expected behavior: -- They use an unsupported double-sided material. core.register_entity("gltf:frog", { diff --git a/games/devtest/mods/gltf/models/gltf_simple_skin_step.gltf b/games/devtest/mods/gltf/models/gltf_simple_skin_step.gltf new file mode 100644 index 000000000..f26c0c8b1 --- /dev/null +++ b/games/devtest/mods/gltf/models/gltf_simple_skin_step.gltf @@ -0,0 +1 @@ +{"scene":0,"scenes":[{"nodes":[0,1]}],"nodes":[{"skin":0,"mesh":0},{"children":[2]},{"translation":[0.0,1.0,0.0],"rotation":[0.0,0.0,0.0,1.0]}],"meshes":[{"primitives":[{"attributes":{"POSITION":1,"JOINTS_0":2,"WEIGHTS_0":3},"indices":0}]}],"skins":[{"inverseBindMatrices":4,"joints":[1,2]}],"animations":[{"channels":[{"sampler":0,"target":{"node":2,"path":"rotation"}}],"samplers":[{"input":5,"interpolation":"STEP","output":6}]}],"buffers":[{"uri":"data:application/gltf-buffer;base64,AAABAAMAAAADAAIAAgADAAUAAgAFAAQABAAFAAcABAAHAAYABgAHAAkABgAJAAgAAAAAvwAAAAAAAAAAAAAAPwAAAAAAAAAAAAAAvwAAAD8AAAAAAAAAPwAAAD8AAAAAAAAAvwAAgD8AAAAAAAAAPwAAgD8AAAAAAAAAvwAAwD8AAAAAAAAAPwAAwD8AAAAAAAAAvwAAAEAAAAAAAAAAPwAAAEAAAAAA","byteLength":168},{"uri":"data:application/gltf-buffer;base64,AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAABAPwAAgD4AAAAAAAAAAAAAQD8AAIA+AAAAAAAAAAAAAAA/AAAAPwAAAAAAAAAAAAAAPwAAAD8AAAAAAAAAAAAAgD4AAEA/AAAAAAAAAAAAAIA+AABAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAA=","byteLength":320},{"uri":"data:application/gltf-buffer;base64,AACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAAAAAACAPwAAgD8AAAAAAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAgD8=","byteLength":128},{"uri":"data:application/gltf-buffer;base64,AAAAAAAAAD8AAIA/AADAPwAAAEAAACBAAABAQAAAYEAAAIBAAACQQAAAoEAAALBAAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAkxjEPkSLbD8AAAAAAAAAAPT9ND/0/TQ/AAAAAAAAAAD0/TQ/9P00PwAAAAAAAAAAkxjEPkSLbD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAkxjEvkSLbD8AAAAAAAAAAPT9NL/0/TQ/AAAAAAAAAAD0/TS/9P00PwAAAAAAAAAAkxjEvkSLbD8AAAAAAAAAAAAAAAAAAIA/","byteLength":240}],"bufferViews":[{"buffer":0,"byteLength":48,"target":34963},{"buffer":0,"byteOffset":48,"byteLength":120,"target":34962},{"buffer":1,"byteLength":320,"byteStride":16},{"buffer":2,"byteLength":128},{"buffer":3,"byteLength":240}],"accessors":[{"bufferView":0,"componentType":5123,"count":24,"type":"SCALAR"},{"bufferView":1,"componentType":5126,"count":10,"type":"VEC3","max":[0.5,2.0,0.0],"min":[-0.5,0.0,0.0]},{"bufferView":2,"componentType":5123,"count":10,"type":"VEC4"},{"bufferView":2,"byteOffset":160,"componentType":5126,"count":10,"type":"VEC4"},{"bufferView":3,"componentType":5126,"count":2,"type":"MAT4"},{"bufferView":4,"componentType":5126,"count":12,"type":"SCALAR","max":[5.5],"min":[0.0]},{"bufferView":4,"byteOffset":48,"componentType":5126,"count":12,"type":"VEC4","max":[0.0,0.0,0.707,1.0],"min":[0.0,0.0,-0.707,0.707]}],"asset":{"version":"2.0"}} diff --git a/irr/src/CGLTFMeshFileLoader.cpp b/irr/src/CGLTFMeshFileLoader.cpp index 79c68355b..4e653fbf8 100644 --- a/irr/src/CGLTFMeshFileLoader.cpp +++ b/irr/src/CGLTFMeshFileLoader.cpp @@ -677,8 +677,17 @@ void SelfType::MeshExtractor::loadAnimation(const std::size_t animIdx) for (const auto &channel : anim.channels) { const auto &sampler = anim.samplers.at(channel.sampler); - if (sampler.interpolation != tiniergltf::AnimationSampler::Interpolation::LINEAR) - throw std::runtime_error("unsupported interpolation, only linear interpolation is supported"); + + bool interpolate = ([&]() { + switch (sampler.interpolation) { + case tiniergltf::AnimationSampler::Interpolation::STEP: + return false; + case tiniergltf::AnimationSampler::Interpolation::LINEAR: + return true; + default: + throw std::runtime_error("Only STEP and LINEAR keyframe interpolation are supported"); + } + })(); const auto inputAccessor = Accessor::make(m_gltf_model, sampler.input); const auto n_frames = inputAccessor.getCount(); @@ -686,32 +695,38 @@ void SelfType::MeshExtractor::loadAnimation(const std::size_t animIdx) if (!channel.target.node.has_value()) throw std::runtime_error("no animated node"); - const auto &joint = m_loaded_nodes.at(*channel.target.node); + auto *joint = m_loaded_nodes.at(*channel.target.node); switch (channel.target.path) { case tiniergltf::AnimationChannelTarget::Path::TRANSLATION: { const auto outputAccessor = Accessor::make(m_gltf_model, sampler.output); + auto &channel = joint->keys.position; + channel.interpolate = interpolate; for (std::size_t i = 0; i < n_frames; ++i) { f32 frame = inputAccessor.get(i); core::vector3df position = outputAccessor.get(i); - m_irr_model->addPositionKey(joint, frame, convertHandedness(position)); + channel.pushBack(frame, convertHandedness(position)); } break; } case tiniergltf::AnimationChannelTarget::Path::ROTATION: { const auto outputAccessor = Accessor::make(m_gltf_model, sampler.output); + auto &channel = joint->keys.rotation; + channel.interpolate = interpolate; for (std::size_t i = 0; i < n_frames; ++i) { f32 frame = inputAccessor.get(i); core::quaternion rotation = outputAccessor.get(i); - m_irr_model->addRotationKey(joint, frame, convertHandedness(rotation)); + channel.pushBack(frame, convertHandedness(rotation)); } break; } case tiniergltf::AnimationChannelTarget::Path::SCALE: { const auto outputAccessor = Accessor::make(m_gltf_model, sampler.output); + auto &channel = joint->keys.scale; + channel.interpolate = interpolate; for (std::size_t i = 0; i < n_frames; ++i) { f32 frame = inputAccessor.get(i); core::vector3df scale = outputAccessor.get(i); - m_irr_model->addScaleKey(joint, frame, scale); + channel.pushBack(frame, scale); } break; }