├── LICENSE ├── README.md ├── SCsub ├── config.py ├── register_types.cpp ├── register_types.h ├── smooth.cpp ├── smooth.h ├── smooth_2d.cpp ├── smooth_2d.h ├── smooth_body.inl └── smooth_header.inl /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 lawnjelly 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # godot-smooth 2 | Fixed timestep interpolation helper nodes for Godot, 3D and 2D 3 | 4 | This a module for Godot engine (3.2 master, 19 July 2019 or later) giving two new node types, Smooth and Smooth2d. 5 | 6 | https://www.youtube.com/watch?v=lWhHBAcH4sM 7 | 8 | **Installation** 9 | To install you will need to compile Godot from source: 10 | 11 | http://docs.godotengine.org/en/3.0/development/compiling/index.html 12 | 13 | To install the module create a new folder called 'smooth' in the modules folder of the engine, then clone / copy godot-smooth files into this folder. Compile the engine as usual, and the module will be automatically added. 14 | 15 | 16 | **Instructions** 17 | This is an example, your exact use will vary depending on your needs. 18 | 19 | 1. Lower the physics tick rate to 10 ticks per second (Project Settings/Physics/Common/Physics FPS) 20 | 2. Create a root node for the scene, either Spatial or Node2D. 21 | 3. Add a RigidBody (2d or 3d) as a child of the root node, with a MeshInstance to display something as a child of the RigidBody. 22 | 4. Run the scene such that the RigidBody drops according to gravity, moving in a jerky fashion 10 times per second. 23 | 5. Add a Smooth node into the scene as a second child of the root node, either Smooth, or Smooth2D depending on whether you are creating a 3d or 2d scene. 24 | 6. If you click the Inspector options for the Smooth node, you can set the target (for the Smooth node to follow) to be the RigidBody. 25 | 7. Now drag the MeshInstance so that instead of being a child of the RigidBody directly, it is a child of the SmoothNode. 26 | 8. If you now run the scene, you should now see the mesh smoothly following the RigidBody, instead of moving in a jerky fashion. 27 | 28 | **Notes** 29 | You can switch on and off interpolation for different components. You can change the input and output coordinate system of the smoothing node. In most cases you would use the default local space. 30 | 31 | For the most part this is all that needs to be done, the target (whether it be a RigidBody, KinematicBody, or simple Spatial or Node2D) can be moved as usual. The one exception is in the case where you are placing an object for example at the start of a level. To prevent it interpolating from the previous position, you should call the 'teleport' function on the Smoothing node, after setting the position of the target node. 32 | -------------------------------------------------------------------------------- /SCsub: -------------------------------------------------------------------------------- 1 | # SCsub 2 | Import('env') 3 | 4 | sources = [ 5 | "register_types.cpp", 6 | "smooth.cpp", 7 | "smooth_2d.cpp" 8 | ] 9 | 10 | module_env = env.Clone() 11 | #module_env.Append(CXXFLAGS=['-O2', '-std=c++11']) 12 | 13 | if ARGUMENTS.get('smooth_shared', 'no') == 'yes': 14 | # Shared lib compilation 15 | module_env.Append(CXXFLAGS='-fPIC') 16 | module_env['LIBS'] = [] 17 | shared_lib = module_env.SharedLibrary(target='#bin/smooth', source=sources) 18 | shared_lib_shim = shared_lib[0].name.rsplit('.', 1)[0] 19 | env.Append(LIBS=[shared_lib_shim]) 20 | env.Append(LIBPATH=['#bin']) 21 | else: 22 | # Static compilation 23 | module_env.add_source_files(env.modules_sources, sources) 24 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | # config.py 2 | 3 | def can_build(env, platform): 4 | return True 5 | 6 | def configure(env): 7 | pass 8 | -------------------------------------------------------------------------------- /register_types.cpp: -------------------------------------------------------------------------------- 1 | /* register_types.cpp */ 2 | 3 | #include "register_types.h" 4 | 5 | #include "core/class_db.h" 6 | #include "smooth.h" 7 | #include "smooth_2d.h" 8 | 9 | void register_smooth_types() { 10 | 11 | ClassDB::register_class(); 12 | ClassDB::register_class(); 13 | } 14 | 15 | void unregister_smooth_types() { 16 | //nothing to do here 17 | } 18 | -------------------------------------------------------------------------------- /register_types.h: -------------------------------------------------------------------------------- 1 | /* register_types.h */ 2 | 3 | void register_smooth_types(); 4 | void unregister_smooth_types(); 5 | /* yes, the word in the middle must be the same as the module folder name */ 6 | -------------------------------------------------------------------------------- /smooth.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Lawnjelly 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | #include "smooth.h" 22 | #include "core/engine.h" 23 | 24 | #define SMOOTHCLASS Smooth 25 | #define SMOOTHNODE Spatial 26 | #include "smooth_body.inl" 27 | 28 | // finish the bind with custom stuff 29 | BIND_ENUM_CONSTANT(METHOD_SLERP); 30 | BIND_ENUM_CONSTANT(METHOD_LERP); 31 | ClassDB::bind_method(D_METHOD("set_method", "method"), &SMOOTHCLASS::set_method); 32 | ClassDB::bind_method(D_METHOD("get_method"), &SMOOTHCLASS::get_method); 33 | 34 | ADD_GROUP("Misc", ""); 35 | ADD_PROPERTY(PropertyInfo(Variant::INT, "method", PROPERTY_HINT_ENUM, "Slerp,Lerp"), "set_method", "get_method"); 36 | } 37 | 38 | #undef SMOOTHCLASS 39 | #undef SMOOTHNODE 40 | 41 | Smooth::Smooth() { 42 | m_Flags = 0; 43 | SetFlags(SF_ENABLED | SF_TRANSLATE | SF_ROTATE | SF_LERP); 44 | set_process_priority(100); 45 | Engine::get_singleton()->set_physics_jitter_fix(0.0); 46 | } 47 | 48 | void Smooth::set_method(eMethod p_method) { 49 | ChangeFlags(SF_LERP, p_method == METHOD_LERP); 50 | } 51 | 52 | Smooth::eMethod Smooth::get_method() const { 53 | if (TestFlags(SF_LERP)) 54 | return METHOD_LERP; 55 | 56 | return METHOD_SLERP; 57 | } 58 | 59 | void Smooth::teleport() { 60 | Spatial *pTarget = GetTarget(); 61 | if (!pTarget) 62 | return; 63 | 64 | // refresh all components during teleport 65 | int temp_flags = m_Flags; 66 | SetFlags(SF_TRANSLATE | SF_ROTATE | SF_SCALE | SF_LERP); 67 | 68 | RefreshTransform(pTarget); 69 | 70 | // set previous equal to current 71 | m_Prev = m_Curr; 72 | m_ptTranslateDiff.zero(); 73 | 74 | // call frame upate to make sure all components of the node are set 75 | FrameUpdate(); 76 | 77 | // restore flags 78 | m_Flags = temp_flags; 79 | } 80 | 81 | void Smooth::RefreshTransform(Spatial *pTarget) { 82 | ClearFlags(SF_DIRTY); 83 | 84 | // keep the data flowing... 85 | // translate.. 86 | m_Prev.m_Transform.origin = m_Curr.m_Transform.origin; 87 | 88 | // global or local? 89 | bool bGlobal = TestFlags(SF_GLOBAL_IN); 90 | 91 | // new transform for this tick 92 | Transform trans; 93 | if (bGlobal) 94 | trans = pTarget->get_global_transform(); 95 | else 96 | trans = pTarget->get_transform(); 97 | 98 | if (TestFlags(SF_TRANSLATE)) { 99 | m_Curr.m_Transform.origin = trans.origin; 100 | m_ptTranslateDiff = m_Curr.m_Transform.origin - m_Prev.m_Transform.origin; 101 | } 102 | 103 | // lerp? keep the basis 104 | if (TestFlags(SF_LERP)) { 105 | m_Prev.m_Transform.basis = m_Curr.m_Transform.basis; 106 | m_Curr.m_Transform.basis = trans.basis; 107 | } else { 108 | if (TestFlags(SF_ROTATE)) { 109 | m_Prev.m_qtRotate = m_Curr.m_qtRotate; 110 | m_Curr.m_qtRotate = trans.basis.get_rotation_quat(); 111 | } 112 | 113 | if (TestFlags(SF_SCALE)) { 114 | m_Prev.m_ptScale = m_Curr.m_ptScale; 115 | m_Curr.m_ptScale = trans.basis.get_scale(); 116 | } 117 | 118 | } // if not lerp 119 | } 120 | 121 | void Smooth::FrameUpdate() { 122 | Spatial *pTarget = GetTarget(); 123 | if (!pTarget) 124 | return; 125 | 126 | if (TestFlags(SF_DIRTY)) 127 | RefreshTransform(pTarget); 128 | 129 | // interpolation fraction 130 | float f = Engine::get_singleton()->get_physics_interpolation_fraction(); 131 | 132 | Vector3 ptNew = m_Prev.m_Transform.origin + (m_ptTranslateDiff * f); 133 | 134 | // global or local? 135 | bool bGlobal = TestFlags(SF_GLOBAL_OUT); 136 | 137 | // simplified, only using translate .. useful for e.g. moving platforms that don't rotate 138 | // NOTE THIS IMPLIES LOCAL as global flag not set... 139 | if (m_Flags == (SF_ENABLED | SF_TRANSLATE)) { 140 | set_translation(ptNew); 141 | return; 142 | } 143 | 144 | // send as a transform, the whole kabunga 145 | Transform trans; 146 | trans.origin = ptNew; 147 | 148 | // lerping 149 | if (TestFlags(SF_LERP)) { 150 | //trans.basis = m_Prev.m_Basis.slerp(m_Curr.m_Basis, f); 151 | LerpBasis(m_Prev.m_Transform.basis, m_Curr.m_Transform.basis, trans.basis, f); 152 | } else { 153 | // slerping 154 | Quat qtRot = m_Prev.m_qtRotate.slerp(m_Curr.m_qtRotate, f); 155 | trans.basis.set_quat(qtRot); 156 | 157 | if (TestFlags(SF_SCALE)) { 158 | Vector3 ptScale = ((m_Curr.m_ptScale - m_Prev.m_ptScale) * f) + m_Prev.m_ptScale; 159 | trans.basis.scale(ptScale); 160 | } 161 | } 162 | 163 | if (bGlobal) 164 | set_global_transform(trans); 165 | else 166 | set_transform(trans); 167 | } 168 | 169 | // Directly lerping the basis vectors is cheaper than doing a slerp and dealing with scale properly 170 | // Could look bad with large changes between ticks, but with reasonable tick rate usually looks acceptable. 171 | void Smooth::LerpBasis(const Basis &from, const Basis &to, Basis &res, float f) const { 172 | res = from; 173 | 174 | for (int n = 0; n < 3; n++) 175 | res.elements[n] = from.elements[n].linear_interpolate(to.elements[n], f); 176 | } 177 | 178 | //bool Smooth::FindVisibility() const 179 | //{ 180 | // const Spatial *s = this; 181 | 182 | // int count = 0; 183 | // while (s) { 184 | 185 | // if (!s->data.visible) 186 | // { 187 | // print_line(itos(count++) + " hidden"); 188 | // return false; 189 | // } 190 | // else 191 | // { 192 | // print_line(itos(count++) + " visible"); 193 | // } 194 | // s = s->data.parent; 195 | // } 196 | 197 | // return true; 198 | //} 199 | -------------------------------------------------------------------------------- /smooth.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Lawnjelly 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | /* smooth.h */ 22 | #ifndef SMOOTH_H 23 | #define SMOOTH_H 24 | 25 | /** 26 | @author lawnjelly 27 | */ 28 | 29 | #include "scene/3d/spatial.h" 30 | 31 | // Smooth node allows fixed timestep interpolation without having to write any code. 32 | // It requires a proxy node (which is moved on physics tick), e.g. a rigid body or manually moved spatial.. 33 | // and instead of having MeshInstance as a child of this, you add Smooth node to another part of the scene graph, 34 | // make the MeshInstance a child of the smooth node, then choose the proxy as the target for the smooth node. 35 | 36 | // Note that in the special case of manually moving the proxy to a completely new location, you should call 37 | // 'teleport' on the smooth node after setting the proxy node transform. This will ensure that the current AND 38 | // previous transform records are reset, so it moves instantaneously. 39 | 40 | class Smooth : public Spatial { 41 | GDCLASS(Smooth, Spatial); 42 | 43 | // custom 44 | private: 45 | class STransform { 46 | public: 47 | Transform m_Transform; 48 | Quat m_qtRotate; 49 | Vector3 m_ptScale; 50 | }; 51 | 52 | Vector3 m_ptTranslateDiff; 53 | 54 | public: 55 | enum eMethod { 56 | METHOD_SLERP, 57 | METHOD_LERP, 58 | }; 59 | 60 | #define SMOOTHNODE Spatial 61 | #include "smooth_header.inl" 62 | #undef SMOOTHNODE 63 | 64 | // specific 65 | public: 66 | Smooth(); 67 | 68 | void set_method(eMethod p_method); 69 | eMethod get_method() const; 70 | 71 | private: 72 | void LerpBasis(const Basis &from, const Basis &to, Basis &res, float f) const; 73 | }; 74 | 75 | VARIANT_ENUM_CAST(Smooth::eMode); 76 | VARIANT_ENUM_CAST(Smooth::eMethod); 77 | 78 | #endif 79 | -------------------------------------------------------------------------------- /smooth_2d.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Lawnjelly 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | #include "smooth_2d.h" 22 | #include "core/engine.h" 23 | 24 | #define SMOOTHCLASS Smooth2D 25 | #define SMOOTHNODE Node2D 26 | #include "smooth_body.inl" 27 | 28 | // finish the bind with custom stuff 29 | } 30 | 31 | #undef SMOOTHCLASS 32 | #undef SMOOTHNODE 33 | 34 | //////////////////////////////// 35 | 36 | Smooth2D::Smooth2D() { 37 | m_Flags = 0; 38 | SetFlags(SF_ENABLED | SF_TRANSLATE | SF_ROTATE | SF_SCALE); 39 | set_process_priority(100); 40 | Engine::get_singleton()->set_physics_jitter_fix(0.0); 41 | } 42 | 43 | float Smooth2D::LerpAngle(float from, float to, float weight) const { 44 | return from + ShortAngleDist(from, to) * weight; 45 | } 46 | 47 | float Smooth2D::ShortAngleDist(float from, float to) const { 48 | float max_angle = (3.14159265358979323846264 * 2.0); 49 | float difference = fmod(to - from, max_angle); 50 | return fmod(2.0f * difference, max_angle) - difference; 51 | } 52 | 53 | void Smooth2D::RefreshTransform(Node2D *pTarget) { 54 | //print_line("RefreshTransform"); 55 | ClearFlags(SF_DIRTY); 56 | 57 | // keep the data flowing... 58 | m_Prev = m_Curr; 59 | 60 | // global or local? 61 | bool bGlobal = TestFlags(SF_GLOBAL_IN); 62 | 63 | if (bGlobal) { 64 | // new transform for this tick 65 | if (TestFlags(SF_TRANSLATE)) { 66 | m_Curr.pos = pTarget->get_global_position(); 67 | m_ptTranslateDiff = m_Curr.pos - m_Prev.pos; 68 | } 69 | 70 | if (TestFlags(SF_ROTATE)) 71 | m_Curr.angle = pTarget->get_global_rotation(); 72 | 73 | if (TestFlags(SF_SCALE)) 74 | m_Curr.scale = pTarget->get_global_scale(); 75 | } else { 76 | // new transform for this tick 77 | if (TestFlags(SF_TRANSLATE)) { 78 | m_Curr.pos = pTarget->get_position(); 79 | m_ptTranslateDiff = m_Curr.pos - m_Prev.pos; 80 | } 81 | 82 | if (TestFlags(SF_ROTATE)) 83 | m_Curr.angle = pTarget->get_rotation(); 84 | 85 | if (TestFlags(SF_SCALE)) 86 | m_Curr.scale = pTarget->get_scale(); 87 | } 88 | } 89 | 90 | void Smooth2D::FrameUpdate() { 91 | //print_line("FrameUpdate"); 92 | 93 | Node2D *pTarget = GetTarget(); 94 | if (!pTarget) 95 | return; 96 | 97 | if (TestFlags(SF_DIRTY)) 98 | RefreshTransform(pTarget); 99 | 100 | // interpolation fraction 101 | float f = Engine::get_singleton()->get_physics_interpolation_fraction(); 102 | 103 | // global or local? 104 | bool bGlobal = TestFlags(SF_GLOBAL_OUT); 105 | 106 | if (bGlobal) { 107 | if (TestFlags(SF_TRANSLATE)) { 108 | Point2 ptNew = m_Prev.pos + (m_ptTranslateDiff * f); 109 | set_global_position(ptNew); 110 | } 111 | 112 | if (TestFlags(SF_ROTATE)) { 113 | // need angular interp 114 | float r = LerpAngle(m_Prev.angle, m_Curr.angle, f); 115 | set_global_rotation(r); 116 | } 117 | 118 | if (TestFlags(SF_SCALE)) { 119 | Size2 s = m_Prev.scale + ((m_Curr.scale - m_Prev.scale) * f); 120 | set_global_scale(s); 121 | } 122 | } else { 123 | if (TestFlags(SF_TRANSLATE)) { 124 | Point2 ptNew = m_Prev.pos + (m_ptTranslateDiff * f); 125 | set_position(ptNew); 126 | } 127 | 128 | if (TestFlags(SF_ROTATE)) { 129 | // need angular interp 130 | float r = LerpAngle(m_Prev.angle, m_Curr.angle, f); 131 | set_rotation(r); 132 | } 133 | 134 | if (TestFlags(SF_SCALE)) { 135 | Size2 s = m_Prev.scale + ((m_Curr.scale - m_Prev.scale) * f); 136 | set_scale(s); 137 | } 138 | } 139 | } 140 | 141 | void Smooth2D::teleport() { 142 | Node2D *pTarget = GetTarget(); 143 | if (!pTarget) 144 | return; 145 | 146 | // refresh all components during teleport 147 | int temp_flags = m_Flags; 148 | SetFlags(SF_TRANSLATE | SF_ROTATE | SF_SCALE | SF_LERP); 149 | 150 | RefreshTransform(pTarget); 151 | 152 | // set previous equal to current 153 | m_Prev = m_Curr; 154 | m_ptTranslateDiff.x = 0.0f; 155 | m_ptTranslateDiff.y = 0.0f; 156 | 157 | // call frame upate to make sure all components of the node are set 158 | FrameUpdate(); 159 | 160 | // restore flags 161 | m_Flags = temp_flags; 162 | } 163 | 164 | //bool Smooth2D::FindVisibility() const 165 | //{ 166 | // return is_visible_in_tree(); 167 | //} 168 | -------------------------------------------------------------------------------- /smooth_2d.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Lawnjelly 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | #ifndef SMOOTH2D_H 22 | #define SMOOTH2D_H 23 | 24 | /** 25 | @author lawnjelly 26 | */ 27 | 28 | #include "scene/2d/node_2d.h" 29 | 30 | // Smooth node allows fixed timestep interpolation without having to write any code. 31 | // It requires a proxy node (which is moved on physics tick), e.g. a rigid body or manually moved node2d.. 32 | // and instead of having MeshInstance as a child of this, you add Smooth node to another part of the scene graph, 33 | // make the MeshInstance a child of the smooth node, then choose the proxy as the target for the smooth node. 34 | 35 | // Note that in the special case of manually moving the proxy to a completely new location, you should call 36 | // 'teleport' on the smooth node after setting the proxy node transform. This will ensure that the current AND 37 | // previous transform records are reset, so it moves instantaneously. 38 | 39 | class Smooth2D : public Node2D { 40 | GDCLASS(Smooth2D, Node2D); 41 | 42 | // custom 43 | class STransform { 44 | public: 45 | Point2 pos; 46 | float angle; 47 | Size2 scale; 48 | }; 49 | 50 | Point2 m_ptTranslateDiff; 51 | 52 | #define SMOOTHNODE Node2D 53 | #include "smooth_header.inl" 54 | #undef SMOOTHNODE 55 | 56 | // specific 57 | public: 58 | Smooth2D(); 59 | 60 | private: 61 | float LerpAngle(float from, float to, float weight) const; 62 | float ShortAngleDist(float from, float to) const; 63 | }; 64 | 65 | VARIANT_ENUM_CAST(Smooth2D::eMode); 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /smooth_body.inl: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Lawnjelly 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | // where smoothclass is defined as the class 22 | // and smoothnode is defined as the node 23 | 24 | //#define SMOOTHVERBOSE 25 | 26 | #define SMOOTH_STRINGIFY(x) #x 27 | #define SMOOTH_TOSTRING(x) SMOOTH_STRINGIFY(x) 28 | 29 | void SMOOTHCLASS::SetProcessing() 30 | { 31 | if (Engine::get_singleton()->is_editor_hint()) 32 | return; 33 | 34 | bool bEnable = TestFlags(SF_ENABLED); 35 | if (TestFlags(SF_INVISIBLE)) 36 | bEnable = false; 37 | 38 | set_process_internal(bEnable); 39 | set_physics_process_internal(bEnable); 40 | } 41 | 42 | 43 | void SMOOTHCLASS::set_enabled(bool p_enabled) 44 | { 45 | if (TestFlags(SF_ENABLED) == p_enabled) 46 | return; 47 | 48 | ChangeFlags(SF_ENABLED, p_enabled); 49 | 50 | SetProcessing (); 51 | } 52 | 53 | bool SMOOTHCLASS::is_enabled() const 54 | { 55 | return TestFlags(SF_ENABLED); 56 | } 57 | 58 | void SMOOTHCLASS::set_interpolate_translation(bool bTranslate) 59 | { 60 | ChangeFlags(SF_TRANSLATE, bTranslate); 61 | } 62 | 63 | bool SMOOTHCLASS::get_interpolate_translation() const 64 | { 65 | return TestFlags(SF_TRANSLATE); 66 | } 67 | 68 | 69 | void SMOOTHCLASS::set_interpolate_rotation(bool bRotate) 70 | { 71 | ChangeFlags(SF_ROTATE, bRotate); 72 | } 73 | 74 | bool SMOOTHCLASS::get_interpolate_rotation() const 75 | { 76 | return TestFlags(SF_ROTATE); 77 | } 78 | 79 | void SMOOTHCLASS::set_interpolate_scale(bool bScale) 80 | { 81 | ChangeFlags(SF_SCALE, bScale); 82 | } 83 | 84 | bool SMOOTHCLASS::get_interpolate_scale() const 85 | { 86 | return TestFlags(SF_SCALE); 87 | } 88 | 89 | 90 | void SMOOTHCLASS::set_input_mode(eMode p_mode) 91 | { 92 | if (p_mode == MODE_GLOBAL) 93 | SetFlags(SF_GLOBAL_IN); 94 | else 95 | ClearFlags(SF_GLOBAL_IN); 96 | } 97 | 98 | SMOOTHCLASS::eMode SMOOTHCLASS::get_input_mode() const 99 | { 100 | if (TestFlags(SF_GLOBAL_IN)) 101 | return MODE_GLOBAL; 102 | 103 | return MODE_LOCAL; 104 | } 105 | 106 | void SMOOTHCLASS::set_output_mode(eMode p_mode) 107 | { 108 | if (p_mode == MODE_GLOBAL) 109 | SetFlags(SF_GLOBAL_OUT); 110 | else 111 | ClearFlags(SF_GLOBAL_OUT); 112 | } 113 | 114 | SMOOTHCLASS::eMode SMOOTHCLASS::get_output_mode() const 115 | { 116 | if (TestFlags(SF_GLOBAL_OUT)) 117 | return MODE_GLOBAL; 118 | 119 | return MODE_LOCAL; 120 | } 121 | 122 | 123 | void SMOOTHCLASS::RemoveTarget() 124 | { 125 | NodePath pathnull; 126 | set_target_path(pathnull); 127 | } 128 | 129 | void SMOOTHCLASS::smooth_print_line(Variant sz) 130 | { 131 | #ifdef SMOOTHVERBOSE 132 | print_line(sz); 133 | #endif 134 | } 135 | 136 | 137 | void SMOOTHCLASS::set_target(const Object *p_target) 138 | { 139 | // handle null 140 | if (p_target == NULL) 141 | { 142 | smooth_print_line("SCRIPT set_Target NULL"); 143 | RemoveTarget(); 144 | return; 145 | } 146 | smooth_print_line("SCRIPT set_Target"); 147 | 148 | // is it a SMOOTHNODE? 149 | const SMOOTHNODE * pSMOOTHNODE = Object::cast_to(p_target); 150 | 151 | if (!pSMOOTHNODE) 152 | { 153 | WARN_PRINT("Target must be a " SMOOTH_TOSTRING(SMOOTHNODE) "."); 154 | RemoveTarget(); 155 | return; 156 | } 157 | smooth_print_line("Target successfully cast to " SMOOTH_TOSTRING(SMOOTHNODE) "."); 158 | 159 | NodePath path = get_path_to(pSMOOTHNODE); 160 | set_target_path(path); 161 | } 162 | 163 | void SMOOTHCLASS::_set_target(const Object *p_target) 164 | { 165 | // m_refTarget.set_obj(pTarget); 166 | if (p_target) 167 | smooth_print_line("\t_set_Target"); 168 | else 169 | smooth_print_line("\t_set_Target NULL"); 170 | 171 | m_ID_target = 0; 172 | 173 | if (p_target) 174 | { 175 | // check is SMOOTHNODE 176 | const SMOOTHNODE *pSMOOTHNODE = Object::cast_to(p_target); 177 | 178 | if (pSMOOTHNODE) 179 | { 180 | m_ID_target = pSMOOTHNODE->get_instance_id(); 181 | smooth_print_line("\t\tTarget was SMOOTHNODE ID " + itos(m_ID_target)); 182 | } 183 | else 184 | { 185 | // should never get here? 186 | WARN_PRINT(SMOOTH_TOSTRING(SMOOTHCLASS) " target must be a " SMOOTH_TOSTRING(SMOOTHNODE) "."); 187 | } 188 | } 189 | 190 | } 191 | 192 | void SMOOTHCLASS::ResolveTargetPath() 193 | { 194 | smooth_print_line("resolve_Target_path " + m_path_target); 195 | 196 | if (has_node(m_path_target)) 197 | { 198 | smooth_print_line("has_node"); 199 | SMOOTHNODE * pNode = Object::cast_to(get_node(m_path_target)); 200 | if (pNode) 201 | { 202 | smooth_print_line("node_was " SMOOTH_TOSTRING(SMOOTHNODE)); 203 | _set_target(pNode); 204 | return; 205 | } 206 | else 207 | { 208 | WARN_PRINT(SMOOTH_TOSTRING(SMOOTHCLASS) " target must be a " SMOOTH_TOSTRING(SMOOTHNODE) "."); 209 | } 210 | } 211 | 212 | // default to setting to null 213 | _set_target(NULL); 214 | } 215 | 216 | void SMOOTHCLASS::set_target_path(const NodePath &p_path) 217 | { 218 | smooth_print_line("set_Target_path " + p_path); 219 | m_path_target = p_path; 220 | ResolveTargetPath(); 221 | } 222 | 223 | NodePath SMOOTHCLASS::get_target_path() const 224 | { 225 | return m_path_target; 226 | } 227 | 228 | SMOOTHNODE * SMOOTHCLASS::GetTarget() const 229 | { 230 | if (m_ID_target == 0) 231 | return 0; 232 | 233 | Object *obj = ObjectDB::get_instance(m_ID_target); 234 | return (SMOOTHNODE *) obj; 235 | } 236 | 237 | void SMOOTHCLASS::FixedUpdate() 238 | { 239 | // has more than one physics tick occurred before a frame? if so 240 | // refresh the transform to the last physics tick. In most cases this will not occur, 241 | // except with tick rates higher than frame rate 242 | if (TestFlags(SF_DIRTY)) 243 | { 244 | SMOOTHNODE * pTarget = GetTarget(); 245 | if (pTarget) 246 | { 247 | RefreshTransform(pTarget); 248 | } 249 | } 250 | 251 | // refresh the transform on the next frame 252 | // because the Target transform may not be updated until AFTER the fixed update! 253 | // (this gets around an order of operations issue) 254 | SetFlags(SF_DIRTY); 255 | } 256 | 257 | 258 | 259 | void SMOOTHCLASS::_notification(int p_what) { 260 | 261 | switch (p_what) { 262 | case NOTIFICATION_ENTER_TREE: { 263 | bool bVisible = is_visible_in_tree(); 264 | ChangeFlags(SF_INVISIBLE, bVisible == false); 265 | SetProcessing(); 266 | 267 | // we can't translate string name of Target to a node until we are in the tree 268 | ResolveTargetPath(); 269 | } break; 270 | case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { 271 | FixedUpdate(); 272 | } break; 273 | case NOTIFICATION_INTERNAL_PROCESS: { 274 | FrameUpdate(); 275 | } break; 276 | case NOTIFICATION_VISIBILITY_CHANGED: { 277 | bool bVisible = is_visible_in_tree(); 278 | ChangeFlags(SF_INVISIBLE, bVisible == false); 279 | SetProcessing(); 280 | // if (bVisible) 281 | // print_line("now visible"); 282 | // else 283 | // print_line("now hidden"); 284 | } break; 285 | } 286 | } 287 | 288 | 289 | 290 | void SMOOTHCLASS::_bind_methods() { 291 | 292 | BIND_ENUM_CONSTANT(MODE_LOCAL); 293 | BIND_ENUM_CONSTANT(MODE_GLOBAL); 294 | 295 | 296 | ClassDB::bind_method(D_METHOD("teleport"), &SMOOTHCLASS::teleport); 297 | 298 | ClassDB::bind_method(D_METHOD("set_enabled"), &SMOOTHCLASS::set_enabled); 299 | ClassDB::bind_method(D_METHOD("is_enabled"), &SMOOTHCLASS::is_enabled); 300 | ClassDB::bind_method(D_METHOD("set_smooth_translate"), &SMOOTHCLASS::set_interpolate_translation); 301 | ClassDB::bind_method(D_METHOD("get_smooth_translate"), &SMOOTHCLASS::get_interpolate_translation); 302 | ClassDB::bind_method(D_METHOD("set_smooth_rotate"), &SMOOTHCLASS::set_interpolate_rotation); 303 | ClassDB::bind_method(D_METHOD("get_smooth_rotate"), &SMOOTHCLASS::get_interpolate_rotation); 304 | ClassDB::bind_method(D_METHOD("set_smooth_scale"), &SMOOTHCLASS::set_interpolate_scale); 305 | ClassDB::bind_method(D_METHOD("get_smooth_scale"), &SMOOTHCLASS::get_interpolate_scale); 306 | 307 | ClassDB::bind_method(D_METHOD("set_input_mode", "mode"), &SMOOTHCLASS::set_input_mode); 308 | ClassDB::bind_method(D_METHOD("get_input_mode"), &SMOOTHCLASS::get_input_mode); 309 | ClassDB::bind_method(D_METHOD("set_output_mode", "mode"), &SMOOTHCLASS::set_output_mode); 310 | ClassDB::bind_method(D_METHOD("get_output_mode"), &SMOOTHCLASS::get_output_mode); 311 | 312 | ClassDB::bind_method(D_METHOD("set_target", "target"), &SMOOTHCLASS::set_target); 313 | ClassDB::bind_method(D_METHOD("set_target_path", "path"), &SMOOTHCLASS::set_target_path); 314 | ClassDB::bind_method(D_METHOD("get_target_path"), &SMOOTHCLASS::get_target_path); 315 | 316 | 317 | ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled"); 318 | 319 | ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target"), "set_target_path", "get_target_path"); 320 | 321 | 322 | ADD_GROUP("Components", ""); 323 | ADD_PROPERTY(PropertyInfo(Variant::BOOL, "smooth_translate"), "set_smooth_translate", "get_smooth_translate"); 324 | ADD_PROPERTY(PropertyInfo(Variant::BOOL, "smooth_rotate"), "set_smooth_rotate", "get_smooth_rotate"); 325 | ADD_PROPERTY(PropertyInfo(Variant::BOOL, "smooth_scale"), "set_smooth_scale", "get_smooth_scale"); 326 | ADD_GROUP("Coords", ""); 327 | ADD_PROPERTY(PropertyInfo(Variant::INT, "input", PROPERTY_HINT_ENUM, "Local,Global"), "set_input_mode", "get_input_mode"); 328 | ADD_PROPERTY(PropertyInfo(Variant::INT, "output", PROPERTY_HINT_ENUM, "Local,Global"), "set_output_mode", "get_output_mode"); 329 | // ADD_PROPERTY(PropertyInfo(Variant::BOOL, "lerp"), "set_lerp", "get_lerp"); 330 | //} 331 | #undef SMOOTH_STRINGIFY 332 | #undef SMOOTH_TOSTRING 333 | -------------------------------------------------------------------------------- /smooth_header.inl: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Lawnjelly 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | // where smoothclass is defined as the class 22 | // and smoothnode is defined as the node 23 | 24 | public: 25 | enum eMode 26 | { 27 | MODE_LOCAL, 28 | MODE_GLOBAL, 29 | }; 30 | private: 31 | enum eSmoothFlags 32 | { 33 | SF_ENABLED = 1, 34 | SF_DIRTY = 2, 35 | SF_TRANSLATE = 4, 36 | SF_ROTATE = 8, 37 | SF_SCALE = 16, 38 | SF_GLOBAL_IN = 32, 39 | SF_GLOBAL_OUT = 64, 40 | SF_LERP = 128, 41 | SF_INVISIBLE = 256, 42 | }; 43 | 44 | 45 | STransform m_Curr; 46 | STransform m_Prev; 47 | 48 | 49 | // defined by eSmoothFlags 50 | int m_Flags; 51 | // eMode m_Mode; 52 | 53 | //WeakRef m_refTarget; 54 | ObjectID m_ID_target; 55 | NodePath m_path_target; 56 | 57 | protected: 58 | static void _bind_methods(); 59 | void _notification(int p_what); 60 | 61 | public: 62 | void set_input_mode(eMode p_mode); 63 | eMode get_input_mode() const; 64 | void set_output_mode(eMode p_mode); 65 | eMode get_output_mode() const; 66 | 67 | void set_enabled(bool p_enabled); 68 | bool is_enabled() const; 69 | 70 | void set_interpolate_translation(bool bTranslate); 71 | bool get_interpolate_translation() const; 72 | 73 | void set_interpolate_rotation(bool bRotate); 74 | bool get_interpolate_rotation() const; 75 | 76 | void set_interpolate_scale(bool bScale); 77 | bool get_interpolate_scale() const; 78 | 79 | 80 | void set_target(const Object *p_target); 81 | void set_target_path(const NodePath &p_path); 82 | NodePath get_target_path() const; 83 | 84 | void teleport(); 85 | 86 | 87 | private: 88 | void _set_target(const Object *p_target); 89 | void RemoveTarget(); 90 | void FixedUpdate(); 91 | void FrameUpdate(); 92 | void RefreshTransform(SMOOTHNODE * pTarget); 93 | SMOOTHNODE * GetTarget() const; 94 | void SetProcessing(); 95 | 96 | void ChangeFlags(int f, bool bSet) {if (bSet) {SetFlags(f);} else {ClearFlags(f);}} 97 | void SetFlags(int f) {m_Flags |= f;} 98 | void ClearFlags(int f) {m_Flags &= ~f;} 99 | bool TestFlags(int f) const {return (m_Flags & f) == f;} 100 | 101 | void ResolveTargetPath(); 102 | //bool FindVisibility() const; 103 | void smooth_print_line(Variant sz); 104 | --------------------------------------------------------------------------------