From 7331156650292f95dad8eb67be8e7d111fb8aecc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20M=C3=BCller?= <34514239+appgurueu@users.noreply.github.com> Date: Wed, 29 Oct 2025 10:25:50 +0100 Subject: [PATCH] Fix interpolating to identity bone overrides (#16609) The previous code immediately dropped identity overrides, even if there still was an interpolation to be done. Also a little bit of cleanup, and setting an appropriate identity default for the scale property when interpolating. For modders: As a workaround, you can add a tiny offset so that overrides aren't identity overrides. --- games/devtest/mods/testentities/visuals.lua | 2 +- src/activeobject.h | 38 ++++++++++++-------- src/client/content_cao.cpp | 40 ++++++++++----------- src/script/lua_api/l_object.cpp | 4 +-- src/server/unit_sao.cpp | 6 ++-- 5 files changed, 49 insertions(+), 41 deletions(-) diff --git a/games/devtest/mods/testentities/visuals.lua b/games/devtest/mods/testentities/visuals.lua index fe611edd24..7e759bd5bf 100644 --- a/games/devtest/mods/testentities/visuals.lua +++ b/games/devtest/mods/testentities/visuals.lua @@ -95,7 +95,7 @@ core.register_entity("testentities:sam", { self.object:set_bone_override("Head", { scale = { vec = vector.new(s, s, s), - absolute = true, + absolute = false, interpolation = self._head_anim_duration, }, }) diff --git a/src/activeobject.h b/src/activeobject.h index 28a7f0eeb4..c76ae950ff 100644 --- a/src/activeobject.h +++ b/src/activeobject.h @@ -66,12 +66,12 @@ struct BoneOverride v3f previous; v3f vector; bool absolute = false; - f32 interp_timer = 0; + f32 interp_duration = 0.0f; } position; v3f getPosition(v3f anim_pos) const { - f32 progress = dtime_passed / position.interp_timer; - if (progress > 1.0f || position.interp_timer == 0.0f) + f32 progress = dtime_passed / position.interp_duration; + if (progress > 1.0f || position.interp_duration == 0.0f) progress = 1.0f; return position.vector.getInterpolated(position.previous, progress) + (position.absolute ? v3f() : anim_pos); @@ -85,14 +85,14 @@ struct BoneOverride // so that we can return them in the appropriate getters v3f next_radians; bool absolute = false; - f32 interp_timer = 0; + f32 interp_duration = 0.0f; } rotation; v3f getRotationEulerDeg(v3f anim_rot_euler) const { core::quaternion rot; - f32 progress = dtime_passed / rotation.interp_timer; - if (progress > 1.0f || rotation.interp_timer == 0.0f) + f32 progress = dtime_passed / rotation.interp_duration; + if (progress > 1.0f || rotation.interp_duration == 0.0f) progress = 1.0f; rot.slerp(rotation.previous, rotation.next, progress); if (!rotation.absolute) { @@ -107,27 +107,35 @@ struct BoneOverride struct ScaleProperty { - v3f previous; - v3f vector{1, 1, 1}; + v3f previous = v3f(1.0f); + v3f vector = v3f(1.0f); bool absolute = false; - f32 interp_timer = 0; + f32 interp_duration = 0.0f; } scale; v3f getScale(v3f anim_scale) const { - f32 progress = dtime_passed / scale.interp_timer; - if (progress > 1.0f || scale.interp_timer == 0.0f) + f32 progress = dtime_passed / scale.interp_duration; + if (progress > 1.0f || scale.interp_duration == 0.0f) progress = 1.0f; return scale.vector.getInterpolated(scale.previous, progress) - * (scale.absolute ? v3f(1) : anim_scale); + * (scale.absolute ? v3f(1.0f) : anim_scale); } - f32 dtime_passed = 0; + f32 dtime_passed = 0.0f; + + bool finishedInterpolation() const + { + return dtime_passed >= std::max(std::max( + position.interp_duration, rotation.interp_duration), + scale.interp_duration); + } bool isIdentity() const { - return !position.absolute && position.vector == v3f() + return finishedInterpolation() + && !position.absolute && position.vector == v3f() && !rotation.absolute && rotation.next == core::quaternion() - && !scale.absolute && scale.vector == v3f(1); + && !scale.absolute && scale.vector == v3f(1.0f); } }; diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index a3633fb278..3cbd2b1495 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -702,17 +702,21 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr) }); m_animated_meshnode->setOnAnimateCallback([&](f32 dtime) { - for (auto &it : m_bone_override) { - auto* bone = m_animated_meshnode->getJointNode(it.first.c_str()); - if (!bone) - continue; - - BoneOverride &props = it.second; + for (auto it = m_bone_override.begin(); it != m_bone_override.end();) { + BoneOverride &props = it->second; props.dtime_passed += dtime; - bone->setPosition(props.getPosition(bone->getPosition())); - bone->setRotation(props.getRotationEulerDeg(bone->getRotation())); - bone->setScale(props.getScale(bone->getScale())); + if (props.isIdentity()) { + it = m_bone_override.erase(it); + continue; + } + + if (auto *bone = m_animated_meshnode->getJointNode(it->first.c_str())) { + bone->setPosition(props.getPosition(bone->getPosition())); + bone->setRotation(props.getRotationEulerDeg(bone->getRotation())); + bone->setScale(props.getScale(bone->getScale())); + } + ++it; } }); } else @@ -1689,9 +1693,9 @@ void GenericCAO::processMessage(const std::string &data) props.scale.previous = props.scale.vector; } else { // Disable interpolation - props.position.interp_timer = 0.0f; - props.rotation.interp_timer = 0.0f; - props.scale.interp_timer = 0.0f; + props.position.interp_duration = 0.0f; + props.rotation.interp_duration = 0.0f; + props.scale.interp_duration = 0.0f; } // Read new values props.position.vector = readV3F32(is); @@ -1703,19 +1707,15 @@ void GenericCAO::processMessage(const std::string &data) props.position.absolute = true; props.rotation.absolute = true; } else { - props.position.interp_timer = readF32(is); - props.rotation.interp_timer = readF32(is); - props.scale.interp_timer = readF32(is); + props.position.interp_duration = readF32(is); + props.rotation.interp_duration = readF32(is); + props.scale.interp_duration = readF32(is); u8 absoluteFlag = readU8(is); props.position.absolute = (absoluteFlag & 1) > 0; props.rotation.absolute = (absoluteFlag & 2) > 0; props.scale.absolute = (absoluteFlag & 4) > 0; } - if (props.isIdentity()) { - m_bone_override.erase(bone); - } else { - m_bone_override[bone] = props; - } + m_bone_override[bone] = props; } else if (cmd == AO_CMD_ATTACH_TO) { u16 parent_id = readS16(is); std::string bone = deSerializeString16(is); diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index eb9ee42bbe..a407f5407a 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -668,7 +668,7 @@ int ObjectRef::l_set_bone_override(lua_State *L) lua_getfield(L, -1, "interpolation"); if (lua_isnumber(L, -1)) - prop.interp_timer = lua_tonumber(L, -1); + prop.interp_duration = lua_tonumber(L, -1); lua_pop(L, 1); }; @@ -718,7 +718,7 @@ static void push_bone_override(lua_State *L, const BoneOverride &props) lua_newtable(L); push_v3f(L, vec); lua_setfield(L, -2, "vec"); - lua_pushnumber(L, prop.interp_timer); + lua_pushnumber(L, prop.interp_duration); lua_setfield(L, -2, "interpolation"); lua_pushboolean(L, prop.absolute); lua_setfield(L, -2, "absolute"); diff --git a/src/server/unit_sao.cpp b/src/server/unit_sao.cpp index 35c92063d9..a663ed29e4 100644 --- a/src/server/unit_sao.cpp +++ b/src/server/unit_sao.cpp @@ -312,9 +312,9 @@ std::string UnitSAO::generateUpdateBoneOverrideCommand( props.rotation.next.toEuler(euler_rot); writeV3F32(os, euler_rot * core::RADTODEG); writeV3F32(os, props.scale.vector); - writeF32(os, props.position.interp_timer); - writeF32(os, props.rotation.interp_timer); - writeF32(os, props.scale.interp_timer); + writeF32(os, props.position.interp_duration); + writeF32(os, props.rotation.interp_duration); + writeF32(os, props.scale.interp_duration); writeU8(os, (props.position.absolute & 1) << 0 | (props.rotation.absolute & 1) << 1 | (props.scale.absolute & 1) << 2);