]> git.lizzy.rs Git - minetest.git/commitdiff
Ability to define and use LUA wield animations
authorEmmanuel <emmanuel@chthonicsoftware.com>
Mon, 7 Sep 2020 17:59:13 +0000 (11:59 -0600)
committerElias Fleckenstein <eliasfleckenstein@web.de>
Fri, 5 Mar 2021 08:45:58 +0000 (09:45 +0100)
14 files changed:
CMakeSettings.json [new file with mode: 0644]
src/CMakeLists.txt
src/client/camera.cpp
src/client/camera.h
src/itemdef.cpp
src/itemdef.h
src/script/common/c_content.cpp
src/script/common/c_content.h
src/script/lua_api/l_item.cpp
src/script/lua_api/l_item.h
src/splinesequence.cpp [new file with mode: 0644]
src/splinesequence.h [new file with mode: 0644]
src/wieldanimation.cpp [new file with mode: 0644]
src/wieldanimation.h [new file with mode: 0644]

diff --git a/CMakeSettings.json b/CMakeSettings.json
new file mode 100644 (file)
index 0000000..b56c79e
--- /dev/null
@@ -0,0 +1,17 @@
+{
+  "configurations": [
+    {
+      "name": "x64-Debug",
+      "generator": "Ninja",
+      "configurationType": "Debug",
+      "inheritEnvironments": [ "msvc_x64_x64" ],
+      "buildRoot": "${projectDir}\\out\\build\\${name}",
+      "installRoot": "${projectDir}\\out\\install\\${name}",
+      "cmakeCommandArgs": "",
+      "buildCommandArgs": "",
+      "ctestCommandArgs": "",
+      "variables": [],
+      "cmakeToolchain": "D:/Code Projects/vcpkg/scripts/buildsystems/vcpkg.cmake"
+    }
+  ]
+}
\ No newline at end of file
index 7bcf8d6c73f84aabe155c33804aef191a65c5148..805c5cd2ece80d6c691cb1a21dd202c9c3955284 100644 (file)
@@ -423,6 +423,7 @@ set(common_SRCS
        serverenvironment.cpp
        serverlist.cpp
        settings.cpp
+       splinesequence.cpp
        staticobject.cpp
        terminal_chat_console.cpp
        texture_override.cpp
@@ -432,6 +433,7 @@ set(common_SRCS
        version.cpp
        voxel.cpp
        voxelalgorithms.cpp
+       wieldanimation.cpp
        hud.cpp
        ${common_network_SRCS}
        ${JTHREAD_SRCS}
index 350b685e19b073b2ac740061184ac795b67612a0..f3aa4bafeed87cb691a505924e453571b71fae57 100644 (file)
@@ -36,6 +36,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "constants.h"
 #include "fontengine.h"
 #include "script/scripting_client.h"
+#include "wieldanimation.h"
 
 #define CAMERA_OFFSET_STEP 200
 #define WIELDMESH_OFFSET_X 55.0f
@@ -173,8 +174,12 @@ void Camera::step(f32 dtime)
        bool was_under_zero = m_wield_change_timer < 0;
        m_wield_change_timer = MYMIN(m_wield_change_timer + dtime, 0.125);
 
-       if (m_wield_change_timer >= 0 && was_under_zero)
+if (m_wield_change_timer >= 0 && was_under_zero) {
                m_wieldnode->setItem(m_wield_item_next, m_client);
+               m_wield_animation = m_wield_item_next
+                       .getDefinition(m_client->getItemDefManager())
+                       .wield_animation;
+}
 
        if (m_view_bobbing_state != 0)
        {
@@ -217,15 +222,15 @@ void Camera::step(f32 dtime)
        }
 
        if (m_digging_button != -1) {
-               f32 offset = dtime * 3.5f;
                float m_digging_anim_was = m_digging_anim;
-               m_digging_anim += offset;
-               if (m_digging_anim >= 1)
+               m_digging_anim += dtime;
+               const WieldAnimation &wield_anim = WieldAnimation::getNamed(m_wield_animation);
+               if (m_digging_anim >= wield_anim.getDuration())
                {
                        m_digging_anim = 0;
                        m_digging_button = -1;
                }
-               float lim = 0.15;
+               float lim = 0.05f;
                if(m_digging_anim_was < lim && m_digging_anim >= lim)
                {
                        if (m_digging_button == 0) {
@@ -553,12 +558,16 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_r
        if (m_arm_inertia)
                addArmInertia(player->getYaw());
 
+       const WieldAnimation &wield_anim = WieldAnimation::getNamed(m_wield_animation);
+
        // Position the wielded item
        //v3f wield_position = v3f(45, -35, 65);
        v3f wield_position = v3f(m_wieldmesh_offset.X, m_wieldmesh_offset.Y, 65);
        //v3f wield_rotation = v3f(-100, 120, -100);
        v3f wield_rotation = v3f(-100, 120, -100);
+       core::quaternion wield_rotation_q =     core::quaternion(wield_rotation * core::DEGTORAD);
        wield_position.Y += fabs(m_wield_change_timer)*320 - 40;
+#if 0
        if(m_digging_anim < 0.05 || m_digging_anim > 0.5)
        {
                f32 frac = 1.0;
@@ -575,24 +584,55 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_r
                //wield_rotation.X -= frac * 15.0 * pow(ratiothing2, 1.4f);
                //wield_rotation.Z += frac * 15.0 * pow(ratiothing2, 1.0f);
        }
+#endif
        if (m_digging_button != -1)
        {
                f32 digfrac = m_digging_anim;
-               wield_position.X -= 50 * sin(pow(digfrac, 0.8f) * M_PI);
-               wield_position.Y += 24 * sin(digfrac * 1.8 * M_PI);
-               wield_position.Z += 25 * 0.5;
+               v3f anim_position = wield_anim.getTranslationAt(digfrac);
+               wield_position.X += anim_position.X;
+               wield_position.Y += anim_position.Y;
+               wield_position.Z += anim_position.Z;
 
                // Euler angles are PURE EVIL, so why not use quaternions?
-               core::quaternion quat_begin(wield_rotation * core::DEGTORAD);
-               core::quaternion quat_end(v3f(80, 30, 100) * core::DEGTORAD);
-               core::quaternion quat_slerp;
-               quat_slerp.slerp(quat_begin, quat_end, sin(digfrac * M_PI));
-               quat_slerp.toEuler(wield_rotation);
+               core::quaternion quat_rot = wield_anim.getRotationAt(digfrac);
+               // apply rotation to starting rotation
+               wield_rotation_q *= quat_rot;
+               // convert back to euler angles
+               wield_rotation_q.toEuler(wield_rotation);
                wield_rotation *= core::RADTODEG;
        } else {
                f32 bobfrac = my_modf(m_view_bobbing_anim);
+               // std::cout << "Third block, frac = " << bobfrac << std::endl;
                wield_position.X -= sin(bobfrac*M_PI*2.0) * 3.0;
                wield_position.Y += sin(my_modf(bobfrac*2.0)*M_PI) * 3.0;
+#if 0
+               // to help with finding rotations, change the '0' above
+               // to '1' and recompile, then fly around.
+
+               wield_position.X += -50;
+               wield_position.Y += 20;
+               wield_position.Z += 0;
+
+               float pitch = wrapDegrees_180(player_position.X);
+               float yaw   = wrapDegrees_180(player_position.Y);
+               float roll  = wrapDegrees_180(player_position.Z);
+               //wield_rotation_q *= core::quaternion(
+               //      (pitch) * core::DEGTORAD,
+               //      (roll) * core::DEGTORAD,
+               //      (yaw) * core::DEGTORAD
+               //);
+               wield_rotation_q *= core::quaternion(pitch * core::DEGTORAD, 0, 0);
+               wield_rotation_q *= core::quaternion(0, yaw * core::DEGTORAD, 0);
+               wield_rotation_q *= core::quaternion(0, 0, roll * core::DEGTORAD);
+               dstream << "Wield rotation " << pitch << ", " << yaw << ", " << roll << std::endl;
+               // -90, 15, -60
+               // convert back to euler angles
+               wield_rotation_q.toEuler(wield_rotation);
+               wield_rotation *= core::RADTODEG;
+#endif
+       }
+       if (!wield_position.equals(v3f(55, -35, 65))) {
+               // std::cout << m_digging_anim << ": " << wield_position.X-55 << ", " << wield_position.Y+35 << ", " << wield_position.Z-65 << std::endl;
        }
        m_wieldnode->setPosition(wield_position);
        m_wieldnode->setRotation(wield_rotation);
index 6fd8d9aa7666a00c35089c30948556260e6fad1e..df156f06d2933775589c76103eeeca693b29f661 100644 (file)
@@ -247,12 +247,14 @@ class Camera
        // Fall view bobbing
        f32 m_view_bobbing_fall = 0.0f;
 
-       // Digging animation frame (0 <= m_digging_anim < 1)
+       // Digging animation frame, in seconds
+       // (0 <= m_digging_anim < m_wield_animation.getDuration())
        f32 m_digging_anim = 0.0f;
        // If -1, no digging animation
        // If 0, left-click digging animation
        // If 1, right-click digging animation
        s32 m_digging_button = -1;
+       std::string m_wield_animation = "";
 
        // Animation when changing wielded item
        f32 m_wield_change_timer = 0.125f;
index 5fb1e4c470019df5a9921977d107da0973c22c6a..9a8ef89c8b7abdad79fddd30bc79b63379c2dd41 100644 (file)
@@ -67,6 +67,7 @@ ItemDefinition& ItemDefinition::operator=(const ItemDefinition &def)
        inventory_overlay = def.inventory_overlay;
        wield_image = def.wield_image;
        wield_overlay = def.wield_overlay;
+       wield_animation = def.wield_animation;
        wield_scale = def.wield_scale;
        stack_max = def.stack_max;
        usable = def.usable;
@@ -108,6 +109,7 @@ void ItemDefinition::reset()
        inventory_overlay = "";
        wield_image = "";
        wield_overlay = "";
+       wield_animation = "";
        palette_image = "";
        color = video::SColor(0xFFFFFFFF);
        wield_scale = v3f(1.0, 1.0, 1.0);
@@ -164,6 +166,7 @@ void ItemDefinition::serialize(std::ostream &os, u16 protocol_version) const
        writeARGB8(os, color);
        os << serializeString16(inventory_overlay);
        os << serializeString16(wield_overlay);
+       os << serializeString(wield_animation);
 
        os << serializeString16(short_description);
 }
@@ -219,7 +222,9 @@ void ItemDefinition::deSerialize(std::istream &is)
        // block to not need to increase the version.
        try {
                short_description = deSerializeString16(is);
-       } catch(SerializationError &e) {};
+               wield_animation = deSerializeString(is);
+       } catch (SerializationError &e) {
+       }
 }
 
 
index ebf0d35273b5cbce57b7586393748db4d3d91e4f..10b4e906995b785d86a688d751762cda8c4aeee2 100644 (file)
@@ -27,6 +27,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "itemgroup.h"
 #include "sound.h"
 #include "texture_override.h" // TextureOverride
+#include <vector>
+#include "wieldanimation.h"
 class IGameDef;
 class Client;
 struct ToolCapabilities;
@@ -63,11 +65,13 @@ struct ItemDefinition
        */
        std::string inventory_image; // Optional for nodes, mandatory for tools/craftitems
        std::string inventory_overlay; // Overlay of inventory_image.
+       std::string wield_animation;   // Named wield animation
        std::string wield_image; // If empty, inventory_image or mesh (only nodes) is used
        std::string wield_overlay; // Overlay of wield_image.
        std::string palette_image; // If specified, the item will be colorized based on this
        video::SColor color; // The fallback color of the node.
        v3f wield_scale;
+       WieldAnimation animation;
 
        /*
                Item stack and interaction properties
index 6995f6b610c8062d23d7b24859a399b2d4d341a5..90787e13d23f3b071ae9cdb4f19d344bb0683c78 100644 (file)
@@ -34,6 +34,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "noise.h"
 #include "server/player_sao.h"
 #include "util/pointedthing.h"
+#include <utility>
+#include <algorithm>
 #include "debug.h" // For FATAL_ERROR
 #include <json/json.h>
 
@@ -61,6 +63,7 @@ void read_item_definition(lua_State* L, int index,
        getstringfield(L, index, "inventory_overlay", def.inventory_overlay);
        getstringfield(L, index, "wield_image", def.wield_image);
        getstringfield(L, index, "wield_overlay", def.wield_overlay);
+       getstringfield(L, index, "wield_animation", def.wield_animation);
        getstringfield(L, index, "palette", def.palette_image);
 
        // Read item color.
@@ -120,7 +123,113 @@ void read_item_definition(lua_State* L, int index,
        getstringfield(L, index, "node_placement_prediction",
                        def.node_placement_prediction);
 }
+/******************************************************************************/
+void read_wield_animation(lua_State *L, int index)
+{
 
+       if (index < 0)
+               index = lua_gettop(L) + 1 + index;
+       std::string name;
+       getstringfield(L, index, "name", name);
+       if (WieldAnimation::repository.size() == 0)
+               WieldAnimation::fillRepository();
+       if (WieldAnimation::repository.find(name) != WieldAnimation::repository.end()) {
+               WieldAnimation::repository.erase(name);
+       }
+       WieldAnimation &anim = WieldAnimation::repository[name];
+       anim.name = name;
+       float totalDuration;
+       getfloatfield(L, index, "duration", totalDuration);
+       lua_getfield(L, index, "translation");
+       if (lua_istable(L, -1)) {
+               int table_translation = lua_gettop(L);
+               lua_pushnil(L);
+               lua_getfield(L, table_translation, "nodes");
+               if (lua_istable(L, -1)) {
+                       int table_nodes = lua_gettop(L);
+                       lua_pushnil(L);
+                       std::vector<std::pair<int, v3f>> nodes;
+                       while (lua_next(L, table_nodes) != 0) {
+                               int i = luaL_checkinteger(L, -2);
+                               v3f node = check_v3f(L, -1);
+                               std::pair<int, v3f> unorderedNode(i, node);
+                               nodes.push_back(unorderedNode);
+                               lua_pop(L, 1);
+                       }
+                       sort(nodes.begin(), nodes.end());
+                       for (std::size_t i = 0; i < nodes.size(); i++) {
+                               anim.m_translationspline.addNode(nodes[i].second);
+                       }
+               }
+               lua_pop(L, 1);
+               lua_getfield(L, table_translation, "indices");
+               if (lua_istable(L, -1)) {
+                       int table_indices = lua_gettop(L);
+                       lua_pushnil(L);
+                       while (lua_next(L, table_indices) != 0) {
+                               if (lua_istable(L, -1)) {
+                                       float duration;
+                                       u32 offset = 0;
+                                       u32 length = 0;
+                                       getfloatfield(L, -1, "duration", duration);
+                                       getintfield(L, -1, "offset", offset);
+                                       getintfield(L, -1, "length", length);
+                                       anim.m_translationspline.addIndex(duration, offset, length);
+                               }
+                               lua_pop(L, 1);
+                       }
+               }
+               lua_pop(L, 1);
+       }
+       lua_pop(L, 1);
+
+       lua_getfield(L, index, "rotation");
+       if (lua_istable(L, -1)) {
+               int table_rotation = lua_gettop(L);
+               lua_pushnil(L);
+               lua_getfield(L, table_rotation, "nodes");
+               if (lua_istable(L, -1)) {
+                       int table_nodes = lua_gettop(L);
+                       lua_pushnil(L);
+                       std::vector<std::pair<int, v3f>> nodes;
+                       while (lua_next(L, table_nodes) != 0) {
+                               int i = luaL_checkinteger(L, -2);
+                               v3f node = check_v3f(L, -1);
+                               std::pair<int, v3f> unorderedNode(i, node);
+                               nodes.push_back(unorderedNode);
+                               lua_pop(L, 1);
+                       }
+                       sort(nodes.begin(), nodes.end());
+                       for (std::size_t i = 0; i < nodes.size(); i++) {
+                               anim.m_rotationspline.addNode(
+                                               WieldAnimation::quatFromAngles(
+                                                               nodes[i].second.X,
+                                                               nodes[i].second.Y,
+                                                               nodes[i].second.Z));
+                       }
+               }
+               lua_getfield(L, table_rotation, "indices");
+               if (lua_istable(L, -1)) {
+                       int table_indices = lua_gettop(L);
+                       lua_pushnil(L);
+                       while (lua_next(L, table_indices) != 0) {
+                               if (lua_istable(L, -1)) {
+                                       float duration;
+                                       u32 offset = 0;
+                                       u32 length = 0;
+                                       getfloatfield(L, -1, "duration", duration);
+                                       getintfield(L, -1, "offset", offset);
+                                       getintfield(L,-1, "length", length);
+                                       anim.m_rotationspline.addIndex(duration, offset, length);
+                               }
+                               lua_pop(L, 1);
+                       }
+               }
+               lua_pop(L, 1);
+       }
+       anim.setDuration(totalDuration);
+       lua_pop(L, 1);
+}
 /******************************************************************************/
 void push_item_definition(lua_State *L, const ItemDefinition &i)
 {
index 29d5763558bd62e75ae4f4b30ed2100c06a22002..2733be663aa8f8232b15fb345f1d2d7d1ba3dcde 100644 (file)
@@ -33,7 +33,6 @@ extern "C" {
 
 #include <iostream>
 #include <vector>
-
 #include "irrlichttypes_bloated.h"
 #include "util/string.h"
 #include "itemgroup.h"
@@ -48,6 +47,7 @@ class NodeDefManager;
 struct PointedThing;
 struct ItemStack;
 struct ItemDefinition;
+
 struct ToolCapabilities;
 struct ObjectProperties;
 struct SimpleSoundSpec;
@@ -109,6 +109,7 @@ void               push_item_definition      (lua_State *L,
                                               const ItemDefinition &i);
 void               push_item_definition_full (lua_State *L,
                                               const ItemDefinition &i);
+void read_wield_animation(lua_State *L, int index);
 
 void               read_object_properties    (lua_State *L, int index,
                                               ServerActiveObject *sao,
index 9e0da4034cb30322966f7febd77458152076cb32..a765c96df0c84ade4ad93319e5cc1a3344e53e4b 100644 (file)
@@ -665,8 +665,19 @@ int ModApiItemMod::l_get_name_from_content_id(lua_State *L)
        return 1; /* number of results */
 }
 
+int ModApiItemMod::l_register_wield_animation(lua_State *L)
+{
+       NO_MAP_LOCK_REQUIRED;
+       luaL_checktype(L, 1, LUA_TTABLE);
+       int table = 1;
+
+       read_wield_animation(L, table);
+       return 0; /* number of results */
+}
+
 void ModApiItemMod::Initialize(lua_State *L, int top)
 {
+       API_FCT(register_wield_animation);
        API_FCT(register_item_raw);
        API_FCT(unregister_item_raw);
        API_FCT(register_alias_raw);
index 16878c101df34a1d598b43d83203ab3d6fd3436b..771f1f504a7c3142aef868ce13fb7ae0105f515b 100644 (file)
@@ -152,6 +152,7 @@ class ModApiItemMod : public ModApiBase {
        static int l_register_alias_raw(lua_State *L);
        static int l_get_content_id(lua_State *L);
        static int l_get_name_from_content_id(lua_State *L);
+       static int l_register_wield_animation(lua_State *L);
 public:
        static void Initialize(lua_State *L, int top);
 };
diff --git a/src/splinesequence.cpp b/src/splinesequence.cpp
new file mode 100644 (file)
index 0000000..e61c7e8
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+Minetest SplineSequence
+Copyright (C) 2016-2018 Ben Deutsch <ben@bendeutsch.de>
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "splinesequence.h"
+#include "irrlichttypes_extrabloated.h"
+
+SplineSequence<core::quaternion> quatSplineSequence;
+
+static void fill_it()
+{
+       core::quaternion a_quat;
+       quatSplineSequence.addNode(a_quat);
+       quatSplineSequence.addIndex(0.0, 0, 0);
+       quatSplineSequence.normalizeDurations();
+}
+
+// some specializations
+
+template <>
+core::quaternion SplineSequence<core::quaternion>::_interpolate(
+               core::quaternion &bottom, core::quaternion &top, float alpha) const
+{
+       core::quaternion result;
+       // slerp or lerp? We're dealing with splines anyway...
+       result.slerp(bottom, top, alpha);
+       return result;
+}
diff --git a/src/splinesequence.h b/src/splinesequence.h
new file mode 100644 (file)
index 0000000..e1163e1
--- /dev/null
@@ -0,0 +1,204 @@
+/*
+Minetest SplineSequence
+Copyright (C) 2016-2018 Ben Deutsch <ben@bendeutsch.de>
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+#include <vector>
+#include <iostream>
+#include <quaternion.h>
+
+struct SplineIndex
+{
+       float duration;
+       unsigned int offset;
+       unsigned int length;
+};
+
+template <typename T> class SplineSequence
+{
+private:
+       std::vector<T> nodes;
+       std::vector<SplineIndex> indices;
+       float m_totalDuration;
+
+public:
+       SplineSequence();
+       ~SplineSequence();
+
+       std::vector<T> getNodes();
+       std::vector<SplineIndex> getIndices();
+       SplineSequence<T> &addNode(T node);
+       SplineSequence<T> &addIndex(
+                       float duration, unsigned int offset, unsigned int length);
+       // after this, all durations are non-negative, and sum to 1.0
+       // or passed number
+       SplineSequence<T> &normalizeDurations(float total = 1.0f);
+
+       inline float getTotalDuration() { return m_totalDuration; };
+
+       void interpolate(T &result, float alpha) const;
+
+       T _interpolate(T &bottom, T &top, float alpha) const;
+};
+
+template <typename T> SplineSequence<T>::SplineSequence() : m_totalDuration(0.0f)
+{
+       // noop
+}
+
+template <typename T> SplineSequence<T>::~SplineSequence()
+{
+       // noop
+}
+
+template <typename T> inline std::vector<T> SplineSequence<T>::getNodes()
+{
+       return this->nodes;
+}
+
+template <typename T>
+inline std::vector<SplineIndex> SplineSequence<T>::getIndices()
+{
+       return this->indices;
+}
+
+template <typename T> SplineSequence<T> &SplineSequence<T>::addNode(T node)
+{
+       nodes.push_back(node);
+       return *this;
+}
+
+template <typename T>
+SplineSequence<T> &SplineSequence<T>::addIndex(
+               float duration, unsigned int offset, unsigned int length)
+{
+       // TODO: in-place constructor for structs?
+       // TODO: sanity checks for durations
+       SplineIndex index;
+       index.duration = duration;
+       index.offset = offset;
+       index.length = length;
+       indices.push_back(index);
+       m_totalDuration += duration;
+       return *this;
+}
+
+template <typename T>
+SplineSequence<T> &SplineSequence<T>::normalizeDurations(float target)
+{
+       float sum = 0.0;
+       for (std::vector<SplineIndex>::iterator i = indices.begin(); i != indices.end();
+                       i++) {
+               float d = i->duration;
+               if (d < 0.0)
+                       d = 0.0; // TODO: sanity check in addIndex
+               sum += d;
+       }
+
+       if (sum == 0.0) {
+               // TODO: throw exception or similar
+       }
+       float adjust = target / sum;
+
+       for (std::vector<SplineIndex>::iterator i = indices.begin(); i != indices.end();
+                       i++) {
+               float d = i->duration;
+               if (d < 0.0)
+                       d = 0.0;
+               d = d * adjust;
+               i->duration = d;
+       }
+       m_totalDuration = target;
+       return *this;
+}
+
+template <typename T> void SplineSequence<T>::interpolate(T &result, float alpha) const
+{
+
+       // find the index
+       std::vector<SplineIndex>::const_iterator index = indices.begin();
+       while (index != indices.end() && index->duration <= alpha) {
+               alpha -= index->duration;
+               index++;
+       }
+       if (index == indices.end()) {
+               // search unsuccessful?
+               return;
+       }
+       // std::cout << "Found index " << index->offset << ", " << index->length <<
+       // std::endl;
+       /*
+       0.3 0.3 0.4 , 0.0 -> 1st, 0.0 rem -> 0.0
+       0.3 0.3 0.4 , 0.5 -> 2nd, 0.2 rem -> 0.66
+       */
+       alpha = alpha / index->duration;
+       // std::cout << "Remaining alpha " << alpha << std::endl;
+
+       typename std::vector<T>::const_iterator start = nodes.begin();
+       start += index->offset;
+       typename std::vector<T>::const_iterator end = start;
+       end += index->length;
+       // std::cout << "Start: " << start->X << " " << start->Y << " " << start->Z <<
+       // std::endl; std::cout << "End:   " << end->X << " " << end->Y << " " << end->Z <<
+       // std::endl; std::cout << "Ends: " << *start << " " << *end << std::endl;
+       // these are both inclusive, but vector's range constructor
+       // excludes the end -> advance by one
+       end++;
+
+       std::vector<T> workspace(start, end);
+       for (unsigned int degree = index->length; degree > 0; degree--) {
+               for (unsigned int i = 0; i < degree; i++) {
+                       // std::cout << "Interpolating alpha " << alpha << ", degree " <<
+                       // degree << ", step " << i << std::endl; std::cout << "Before " <<
+                       // workspace[i] << " onto " << workspace[i+1] << std::endl;
+                       workspace[i] = _interpolate(
+                                       workspace[i], workspace[i + 1], alpha);
+                       //_interpolate(workspace[i], alpha, index->length);
+                       // workspace[i] = (1.0-alpha) * workspace[i] + alpha *
+                       // workspace[i+1]; std::cout << "After " << workspace[i] << " onto
+                       // " << workspace[i+1] << std::endl;
+               }
+       }
+       result = workspace[0];
+
+       // SplineIndex index;
+       // for(
+       //      std::vector<SplineIndex>::const_iterator i = indices.begin();
+       //      i != indices.end(); i++
+       //) {
+       //      if (i->duration >= alpha) {
+       //              index = *i;
+       //              alpha -= i->duration;
+       //      }
+       //}
+       // if (index->duration <= 0.0) {
+       //      return;
+       //}
+}
+
+template <typename T>
+T SplineSequence<T>::_interpolate(T &bottom, T &top, float alpha) const
+{
+       return (1.0 - alpha) * bottom + alpha * top;
+}
+
+// declare specializations
+
+// quaternions have a special interpolation
+template <>
+core::quaternion SplineSequence<core::quaternion>::_interpolate(
+               core::quaternion &bottom, core::quaternion &top, float alpha) const;
\ No newline at end of file
diff --git a/src/wieldanimation.cpp b/src/wieldanimation.cpp
new file mode 100644 (file)
index 0000000..0225884
--- /dev/null
@@ -0,0 +1,211 @@
+/*
+Minetest WieldAnimation
+Copyright (C) 2018 Ben Deutsch <ben@bendeutsch.de>
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+
+#include "wieldanimation.h"
+
+v3f WieldAnimation::getTranslationAt(float time) const
+{
+       v3f translation;
+       m_translationspline.interpolate(translation, time);
+       return translation;
+}
+
+core::quaternion WieldAnimation::getRotationAt(float time) const
+{
+       core::quaternion rotation;
+       m_rotationspline.interpolate(rotation, time);
+       return rotation;
+}
+
+float WieldAnimation::getDuration() const
+{
+       return m_duration;
+}
+
+void WieldAnimation::setDuration(float duration)
+{
+       m_duration = duration;
+       m_translationspline.normalizeDurations(duration);
+       m_rotationspline.normalizeDurations(duration);
+}
+
+core::quaternion WieldAnimation::quatFromAngles(float pitch, float yaw, float roll)
+{
+       // the order of angles is important:
+       core::quaternion res;
+       res *= core::quaternion(pitch * core::DEGTORAD, 0, 0);
+       res *= core::quaternion(0, yaw * core::DEGTORAD, 0);
+       res *= core::quaternion(0, 0, roll * core::DEGTORAD);
+       return res;
+}
+
+const WieldAnimation &WieldAnimation::getNamed(const std::string &name)
+{
+       if (repository.size() == 0)
+               fillRepository();
+
+       if (repository.find(name) == repository.end())
+               return repository["punch"];
+
+       return repository[name];
+}
+
+std::unordered_map<std::string, WieldAnimation> WieldAnimation::repository;
+
+void WieldAnimation::fillRepository()
+{
+       // default: "punch"
+       WieldAnimation &punch = repository["punch"];
+       punch.m_translationspline.addNode(v3f(0, 0, 0))
+                       .addNode(v3f(-70, 50, 0))
+                       .addNode(v3f(-70, -50, 0))
+                       .addNode(v3f(0, 0, 0));
+       punch.m_translationspline.addIndex(1.0, 0, 3);
+
+       punch.m_rotationspline.addNode(quatFromAngles(0.0f, 0.0f, 0.0f))
+                       .addNode(quatFromAngles(0.0f, 0.0f, 90.0f))
+                       .addNode(quatFromAngles(0.0f, 0.0f, 0.0f));
+       punch.m_rotationspline.addIndex(1.0, 0, 2);
+       punch.setDuration(0.3f);
+
+       WieldAnimation &dig = repository["dig"];
+       dig.m_translationspline.addNode(v3f(0, 0, 0))
+                       .addNode(v3f(-70, -50, 0))
+                       .addNode(v3f(-70, 50, 0))
+                       .addNode(v3f(0, 0, 0));
+       dig.m_translationspline.addIndex(1.0, 0, 3);
+
+       dig.m_rotationspline.addNode(quatFromAngles(0.0f, 0.0f, 0.0f))
+                       .addNode(quatFromAngles(0.0f, 0.0f, 135.0f))
+                       .addNode(quatFromAngles(0.0f, 0.0f, 135.0f))
+                       .addNode(quatFromAngles(0.0f, 0.0f, 0.0f))
+                       .addNode(quatFromAngles(0.0f, 0.0f, -80.0f))
+                       .addNode(quatFromAngles(0.0f, 0.0f, 0.0f));
+       dig.m_rotationspline.addIndex(1.0, 0, 2).addIndex(1.0, 2, 3);
+       dig.setDuration(0.3f);
+
+       // eat (without chewing)
+       WieldAnimation &eat = repository["eat"];
+       eat.m_translationspline.addNode(v3f(0, 0, 0))
+                       .addNode(v3f(-35, 20, 0))
+                       .addNode(v3f(-55, 10, 0))
+                       .addNode(v3f(-55, 10, 0))
+                       .addNode(v3f(-55, 15, 0))
+                       .addNode(v3f(-55, 10, 0))
+                       .addNode(v3f(-55, 15, 0))
+                       .addNode(v3f(-55, 10, 0))
+                       .addNode(v3f(-30, 0, 0))
+                       .addNode(v3f(0, 0, 0))
+                       .addNode(v3f(0, 0, 0));
+       eat.m_translationspline.addIndex(1.0, 0, 3)
+                       .addIndex(0.5, 3, 1)
+                       .addIndex(0.5, 4, 1)
+                       .addIndex(0.5, 5, 1)
+                       .addIndex(0.5, 6, 1)
+                       .addIndex(1.0, 7, 3);
+
+       eat.m_rotationspline.addNode(quatFromAngles(0.0f, 0.0f, 0.0f))
+                       .addNode(quatFromAngles(-90.0f, 20.0f, -80.0f))
+                       .addNode(quatFromAngles(0.0f, 0.0f, 0.0f));
+       eat.m_rotationspline.addIndex(1.0, 0, 1).addIndex(2.0, 1, 0).addIndex(1.0, 1, 1);
+       eat.setDuration(1.0f);
+
+       // "poke"
+       WieldAnimation &poke = repository["poke"];
+       poke.m_translationspline.addNode(v3f(0, 0, 0))
+                       .addNode(v3f(0, -10, 0))
+                       .addNode(v3f(-50, 20, 50))
+                       .addNode(v3f(0, -10, 0))
+                       .addNode(v3f(0, 0, 0));
+       poke.m_translationspline.addIndex(1.0, 0, 2).addIndex(1.0, 2, 2);
+
+       poke.m_rotationspline.addNode(quatFromAngles(0.0f, 0.0f, 0.0f))
+                       .addNode(quatFromAngles(0.0f, 0.0f, 90.0f))
+                       .addNode(quatFromAngles(0.0f, 0.0f, 90.0f))
+                       .addNode(quatFromAngles(0.0f, 0.0f, 90.0f))
+                       .addNode(quatFromAngles(0.0f, 0.0f, 0.0f));
+       poke.m_rotationspline.addIndex(1.0, 0, 2).addIndex(1.0, 2, 2);
+       poke.setDuration(0.5f);
+
+}
+
+void WieldAnimation::serialize(std::ostream &os, u16 protocol_version)
+{
+       os << serializeString(name);
+       writeF32(os, m_duration);
+       std::vector<v3f> nodes = m_translationspline.getNodes();
+       writeU16(os, nodes.size());
+       for (const auto &node : nodes) {
+               writeV3F32(os, node);
+       }
+       std::vector<SplineIndex> indices = m_translationspline.getIndices();
+       writeU16(os, indices.size());
+       for (const auto &index : indices) {
+               writeF32(os, index.duration);
+               writeU16(os, index.offset);
+               writeU16(os, index.length);
+       }
+       std::vector<core::quaternion> rnodes = m_rotationspline.getNodes();
+       writeU16(os, rnodes.size());
+       for (const auto &node : rnodes) {
+               v3f out = v3f(node.X, node.Y, node.Z);
+               writeV3F32(os, out);
+       }
+       std::vector<SplineIndex> rindices = m_rotationspline.getIndices();
+       writeU16(os, rindices.size());
+       for (const auto &index : rindices) {
+               writeF32(os, index.duration);
+               writeU16(os, index.offset);
+               writeU16(os, index.length);
+       }
+}
+
+WieldAnimation WieldAnimation::deSerialize(std::istream &is)
+{
+       std::string name = deSerializeString(is);
+       WieldAnimation &anim = repository[name];
+       anim.name = name;
+       m_duration = readF32(is);
+       u32 translationNodes_size = readU32(is);
+       for (u32 i = 0; i < translationNodes_size; i++) {
+               v3f node = readV3F32(is);
+               anim.m_translationspline.addNode(node);
+       }
+
+       u32 translationIndex_size = readU32(is);
+       for (u32 i = 0; i < translationIndex_size; i++) {
+               float duration = readF32(is);
+               u32 offset = readU16(is);
+               u32 length = readU16(is);
+               anim.m_translationspline.addIndex(duration, offset, length);
+       }
+
+       u32 rotationNodes_size = readU32(is);
+       for (u32 i = 0; i < rotationNodes_size; i++) {
+               v3f node = readV3F32(is);
+               anim.m_rotationspline.addNode(quatFromAngles(node.X,node.Y, node.Z));
+       }
+       u32 rotationIndex_size = readU32(is);
+       for (u32 i = 0; i < rotationIndex_size; i++) {
+               float duration = readF32(is);
+               u32 offset = readU16(is);
+               u32 length = readU16(is);
+               anim.m_rotationspline.addIndex(duration, offset, length);
+       }
+       anim.setDuration(m_duration);
+       return anim;
+}
diff --git a/src/wieldanimation.h b/src/wieldanimation.h
new file mode 100644 (file)
index 0000000..774ee9a
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+Minetest WieldAnimation
+Copyright (C) 2018 Ben Deutsch <ben@bendeutsch.de>
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+#include <iostream>
+#include "util/serialize.h"
+#include "splinesequence.h"
+#include <unordered_map>
+
+class WieldAnimation
+{
+public:
+       std::string name;
+       v3f getTranslationAt(float time) const;
+       core::quaternion getRotationAt(float time) const;
+       float getDuration() const;
+       // call this *after* filling the splines
+       void setDuration(float duration);
+       static core::quaternion quatFromAngles(float pitch, float yaw, float roll);
+
+       static const WieldAnimation &getNamed(const std::string &name);
+
+       SplineSequence<v3f> m_translationspline;
+       SplineSequence<core::quaternion> m_rotationspline;
+       float m_duration;
+
+       static std::unordered_map<std::string, WieldAnimation> repository;
+       static void fillRepository();
+       WieldAnimation deSerialize(std::istream &is);
+       void serialize(std::ostream &os, u16 protocol_version);
+};
\ No newline at end of file