├── demo ├── .gitignore ├── icon.png ├── node_2d.gd ├── node_2d.tscn ├── project.godot └── icon.png.import ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ └── bug_report.yml └── workflows │ └── builds.yml ├── .gitattributes ├── .gitmodules ├── bin ├── Threen.svg ├── threen.gdextension └── Threen.svg.import ├── .gitignore ├── SConstruct ├── README.md ├── LICENSE.md ├── src ├── register_types.h ├── register_types.cpp ├── threen.h ├── easing_equations.h └── threen.cpp └── doc_classes └── Threen.xml /demo/.gitignore: -------------------------------------------------------------------------------- 1 | /bin 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | -------------------------------------------------------------------------------- /demo/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akien-mga/threen/HEAD/demo/icon.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Normalize EOL for all files that Git considers text files. 2 | * text=auto eol=lf 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "godot-cpp"] 2 | path = godot-cpp 3 | url = https://github.com/godotengine/godot-cpp 4 | -------------------------------------------------------------------------------- /bin/Threen.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Godot 4+ specific ignores 2 | .godot/ 3 | game/bin/summator/* 4 | .sconsign*.dblite 5 | 6 | # Binaries and generated 7 | *.o 8 | *.os 9 | *.so 10 | *.obj 11 | *.bc 12 | *.pyc 13 | *.dblite 14 | *.pdb 15 | *.lib 16 | *.config 17 | *.creator 18 | *.creator.user 19 | *.files 20 | *.includes 21 | *.idb 22 | *.exp 23 | *.gen.* 24 | 25 | # Other stuff 26 | *.log 27 | -------------------------------------------------------------------------------- /demo/node_2d.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | func _ready(): 4 | var threen = Threen.new() 5 | add_child(threen) 6 | var threen_callable = func(): 7 | threen.interpolate_property($Icon, "position", 8 | Vector2(0, 0), Vector2(500, 500), 2, 9 | Threen.TRANS_LINEAR, Threen.EASE_IN_OUT) 10 | threen.start() 11 | threen.tween_all_completed.connect(threen_callable) 12 | threen_callable.call() 13 | -------------------------------------------------------------------------------- /demo/node_2d.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=3 uid="uid://bu6al12g2qcjr"] 2 | 3 | [ext_resource type="Texture2D" uid="uid://cx1mscuegq1lo" path="res://icon.png" id="1_ey71a"] 4 | [ext_resource type="Script" path="res://node_2d.gd" id="1_l522l"] 5 | 6 | [node name="Node2D" type="Node2D"] 7 | script = ExtResource("1_l522l") 8 | 9 | [node name="Icon" type="Sprite2D" parent="."] 10 | position = Vector2(112, 131) 11 | texture = ExtResource("1_ey71a") 12 | -------------------------------------------------------------------------------- /demo/project.godot: -------------------------------------------------------------------------------- 1 | ; Engine configuration file. 2 | ; It's best edited using the editor UI and not directly, 3 | ; since the parameters that go here are not all obvious. 4 | ; 5 | ; Format: 6 | ; [section] ; section goes between [] 7 | ; param=value ; assign values to parameters 8 | 9 | config_version=5 10 | 11 | [application] 12 | 13 | config/name="GDExtension Threen Demo" 14 | run/main_scene="res://node_2d.tscn" 15 | config/features=PackedStringArray("4.3") 16 | config/icon="res://icon.png" 17 | -------------------------------------------------------------------------------- /bin/threen.gdextension: -------------------------------------------------------------------------------- 1 | [configuration] 2 | 3 | entry_symbol = "threen_library_init" 4 | compatibility_minimum = "4.1" 5 | 6 | [icons] 7 | 8 | Threen = "res://bin/Threen.svg" 9 | 10 | [libraries] 11 | 12 | linux.x86_64.debug = "res://bin/libthreen.linux.template_debug.x86_64.so" 13 | linux.x86_64.release = "res://bin/libthreen.linux.template_release.x86_64.so" 14 | linux.debug.arm64 = "res://bin/libthreen.linux.template_debug.arm64.so" 15 | linux.release.arm64 = "res://bin/libthreen.linux.template_release.arm64.so" 16 | windows.x86_64.debug = "res://bin/libthreen.windows.template_debug.x86_64.dll" 17 | windows.x86_64.release = "res://bin/libthreen.windows.template_release.x86_64.dll" 18 | macos.debug = "res://bin/libthreen.macos.template_debug.framework" 19 | macos.release = "res://bin/libthreen.macos.template_release.framework" 20 | -------------------------------------------------------------------------------- /demo/icon.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://cx1mscuegq1lo" 6 | path="res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://icon.png" 14 | dest_files=["res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /SConstruct: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | libname = "libthreen" 4 | 5 | env = SConscript("godot-cpp/SConstruct") 6 | 7 | env.Append(CPPPATH=["src/"]) 8 | sources = Glob("src/*.cpp") 9 | 10 | if env["target"] in ["editor", "template_debug"]: 11 | try: 12 | doc_data = env.GodotCPPDocData("src/gen/doc_data.gen.cpp", source=Glob("doc_classes/*.xml")) 13 | sources.append(doc_data) 14 | except AttributeError: 15 | print("Not including class reference as we're targeting a pre-4.3 baseline.") 16 | 17 | if env["platform"] == "macos": 18 | platlibname = "{}.{}.{}".format(libname, env["platform"], env["target"]) 19 | library = env.SharedLibrary( 20 | "bin/{}.framework/{}".format(platlibname, platlibname), 21 | source=sources, 22 | ) 23 | else: 24 | library = env.SharedLibrary( 25 | "bin/{}{}{}".format(libname, env["suffix"], env["SHLIBSUFFIX"]), 26 | source=sources, 27 | ) 28 | 29 | Default(library) 30 | -------------------------------------------------------------------------------- /bin/Threen.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://dsk3tvoo4ooj6" 6 | path="res://.godot/imported/Threen.svg-93a9b1dcd3e634afdd64a494c0aa9265.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://bin/Threen.svg" 14 | dest_files=["res://.godot/imported/Threen.svg-93a9b1dcd3e634afdd64a494c0aa9265.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Threen - Godot 3 Tween for Godot 4 2 | 3 | `Threen` is a forward port of Godot 3's `Tween` node to Godot 4, provided as a 4 | GDExtension library. 5 | 6 | ## Why? 7 | 8 | Godot 4 already has a different and better `Tween` class, which corresponds to 9 | what was backported to Godot 3.5+ as `SceneTreeTween`. 10 | 11 | But some users have complex Godot 3 projects using the old `Tween` API 12 | extensively, and would benefit from keeping access to it when porting their 13 | projects to Godot 4. 14 | 15 | Therefore `Threen` is provided as a 1:1 forward port Godot 3's `Tween`. You only 16 | need to add the GDExtension to your project ported from Godot 3, and replace 17 | uses of the `Tween` node in your scenes and scripts with `Threen`. The rest of 18 | the API stays unchanged, i.e. it's still `tween_completed`, etc. 19 | 20 | ## How to use 21 | 22 | - Compile the library with `scons`. 23 | - Copy the `bin` folder to the `demo` folder to try it out with the premade 24 | project. 25 | 26 | ## License 27 | 28 | This library was copied from the Godot 3.x codebase (as of 3.5.2-stable) and is 29 | therefore distributed under the same MIT license as Godot itself. 30 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). 2 | Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Report a bug in the Threen extension 3 | body: 4 | 5 | - type: markdown 6 | attributes: 7 | value: | 8 | - Write a descriptive issue title above. 9 | - Search [open](https://github.com/akien-mga/threen/issues) and [closed](https://github.com/akien-mga/threen/issues?q=is%3Aissue+is%3Aclosed) issues to ensure it has not already been reported. 10 | 11 | - type: input 12 | attributes: 13 | label: Godot version 14 | description: > 15 | Specify the Git commit hash of your Godot build. 16 | placeholder: v4.0.stable.official [92bee43ad] 17 | validations: 18 | required: true 19 | 20 | - type: input 21 | attributes: 22 | label: godot-cpp version 23 | description: > 24 | Specify the Git commit hash of the godot-cpp submodule in your project. You can run `git status` inside the folder to check it. 25 | placeholder: v4.0.stable.official [9d1c396c5] 26 | validations: 27 | required: true 28 | 29 | - type: input 30 | attributes: 31 | label: System information 32 | description: | 33 | Specify the OS version. 34 | placeholder: Windows 10 35 | validations: 36 | required: true 37 | 38 | - type: textarea 39 | attributes: 40 | label: Issue description 41 | description: | 42 | Describe your issue briefly. What doesn't work, and how do you expect it to work instead? 43 | You can include images or videos with drag and drop, and format code blocks or logs with ``` tags. 44 | validations: 45 | required: true 46 | -------------------------------------------------------------------------------- /src/register_types.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************/ 2 | /* register_types.cpp */ 3 | /**************************************************************************/ 4 | /* This file is part of: */ 5 | /* GODOT ENGINE */ 6 | /* https://godotengine.org */ 7 | /**************************************************************************/ 8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ 9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ 10 | /* */ 11 | /* Permission is hereby granted, free of charge, to any person obtaining */ 12 | /* a copy of this software and associated documentation files (the */ 13 | /* "Software"), to deal in the Software without restriction, including */ 14 | /* without limitation the rights to use, copy, modify, merge, publish, */ 15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ 16 | /* permit persons to whom the Software is furnished to do so, subject to */ 17 | /* the following conditions: */ 18 | /* */ 19 | /* The above copyright notice and this permission notice shall be */ 20 | /* included in all copies or substantial portions of the Software. */ 21 | /* */ 22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ 23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ 24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ 25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ 26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ 27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ 28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ 29 | /**************************************************************************/ 30 | 31 | #ifndef THREEN_REGISTER_TYPES_H 32 | #define THREEN_REGISTER_TYPES_H 33 | 34 | void initialize_threen_types(); 35 | void uninitialize_threen_types(); 36 | 37 | #endif // THREEN_REGISTER_TYPES_H 38 | -------------------------------------------------------------------------------- /src/register_types.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************/ 2 | /* register_types.cpp */ 3 | /**************************************************************************/ 4 | /* This file is part of: */ 5 | /* GODOT ENGINE */ 6 | /* https://godotengine.org */ 7 | /**************************************************************************/ 8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ 9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ 10 | /* */ 11 | /* Permission is hereby granted, free of charge, to any person obtaining */ 12 | /* a copy of this software and associated documentation files (the */ 13 | /* "Software"), to deal in the Software without restriction, including */ 14 | /* without limitation the rights to use, copy, modify, merge, publish, */ 15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ 16 | /* permit persons to whom the Software is furnished to do so, subject to */ 17 | /* the following conditions: */ 18 | /* */ 19 | /* The above copyright notice and this permission notice shall be */ 20 | /* included in all copies or substantial portions of the Software. */ 21 | /* */ 22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ 23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ 24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ 25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ 26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ 27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ 28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ 29 | /**************************************************************************/ 30 | 31 | #include "register_types.h" 32 | 33 | #include "threen.h" 34 | 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | using namespace godot; 41 | 42 | void initialize_threen_types(ModuleInitializationLevel p_level) { 43 | if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { 44 | return; 45 | } 46 | ClassDB::register_class(); 47 | } 48 | 49 | void uninitialize_threen_types(ModuleInitializationLevel p_level) { 50 | if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { 51 | return; 52 | } 53 | } 54 | 55 | extern "C" { 56 | // Initialization. 57 | GDExtensionBool GDE_EXPORT threen_library_init(GDExtensionInterfaceGetProcAddress p_get_proc_address, const GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization) { 58 | GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization); 59 | 60 | init_obj.register_initializer(initialize_threen_types); 61 | init_obj.register_terminator(uninitialize_threen_types); 62 | init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE); 63 | 64 | return init_obj.init(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /.github/workflows/builds.yml: -------------------------------------------------------------------------------- 1 | name: Builds 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | LIBNAME: threen 7 | 8 | concurrency: 9 | group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}}-macos 10 | cancel-in-progress: true 11 | 12 | jobs: 13 | build: 14 | runs-on: ${{matrix.os}} 15 | name: ${{matrix.name}} 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | include: 20 | - identifier: windows-debug 21 | os: windows-latest 22 | name: 🏁 Windows Debug 23 | target: template_debug 24 | platform: windows 25 | arch: x86_64 26 | - identifier: windows-release 27 | os: windows-latest 28 | name: 🏁 Windows Release 29 | target: template_release 30 | platform: windows 31 | arch: x86_64 32 | - identifier: macos-debug 33 | os: macos-latest 34 | name: 🍎 macOS (universal) Debug 35 | target: template_debug 36 | platform: macos 37 | arch: universal 38 | - identifier: macos-release 39 | os: macos-latest 40 | name: 🍎 macOS (universal) Release 41 | target: template_release 42 | platform: macos 43 | arch: universal 44 | - identifier: linux-debug 45 | os: ubuntu-latest 46 | name: 🐧 Linux Debug 47 | runner: ubuntu-20.04 48 | target: template_debug 49 | platform: linux 50 | arch: x86_64 51 | - identifier: linux-release 52 | os: ubuntu-latest 53 | name: 🐧 Linux Release 54 | runner: ubuntu-20.04 55 | target: template_release 56 | platform: linux 57 | arch: x86_64 58 | 59 | steps: 60 | - name: Checkout project 61 | uses: actions/checkout@v4 62 | with: 63 | submodules: recursive 64 | 65 | - name: Set up Python 66 | uses: actions/setup-python@v5 67 | with: 68 | python-version: '3.x' 69 | 70 | - name: Set up SCons 71 | shell: bash 72 | run: | 73 | python -c "import sys; print(sys.version)" 74 | python -m pip install scons 75 | scons --version 76 | 77 | - name: Linux dependencies 78 | if: ${{ matrix.platform == 'linux' }} 79 | run: | 80 | sudo apt-get update -qq 81 | sudo apt-get install -qqq build-essential pkg-config 82 | 83 | - name: Setup MinGW for Windows/MinGW build 84 | if: ${{ matrix.platform == 'windows' }} 85 | uses: egor-tensin/setup-mingw@v2 86 | with: 87 | version: 12.2.0 88 | 89 | - name: Compile godot-cpp 90 | shell: sh 91 | run: | 92 | scons target='${{ matrix.target }}' platform='${{ matrix.platform }}' arch='${{ matrix.arch }}' 93 | working-directory: godot-cpp 94 | 95 | - name: Compile Extension 96 | shell: sh 97 | run: | 98 | scons target='${{ matrix.target }}' platform='${{ matrix.platform }}' arch='${{ matrix.arch }}' 99 | 100 | - name: Delete compilation files 101 | if: ${{ matrix.platform == 'windows' }} 102 | run: | 103 | Remove-Item bin/* -Include *.exp,*.lib,*.pdb -Force 104 | 105 | - name: Upload artifact 106 | uses: actions/upload-artifact@v4 107 | with: 108 | name: '${{ env.LIBNAME }}.${{ matrix.platform }}.${{ matrix.target }}.${{ matrix.arch }}' 109 | path: 'bin/' 110 | 111 | - name: Archive Release 112 | if: success() && github.event_name == 'push' && startsWith(github.ref, 'refs/tags') 113 | uses: thedoctor0/zip-release@0.7.6 114 | with: 115 | type: 'zip' 116 | filename: '${{ env.LIBNAME }}.${{ matrix.platform }}.${{ matrix.target }}.${{ matrix.arch }}.zip' 117 | path: 'bin/' 118 | 119 | - name: Create and upload asset 120 | if: success() && github.event_name == 'push' && startsWith(github.ref, 'refs/tags') 121 | uses: ncipollo/release-action@v1 122 | with: 123 | allowUpdates: true 124 | artifacts: "${{ env.LIBNAME }}.${{ matrix.platform }}.${{ matrix.target }}.${{ matrix.arch }}.zip" 125 | omitNameDuringUpdate: true 126 | omitBodyDuringUpdate: true 127 | token: ${{ secrets.GITHUB_TOKEN }} 128 | -------------------------------------------------------------------------------- /src/threen.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************/ 2 | /* threen.h */ 3 | /**************************************************************************/ 4 | /* This file is part of: */ 5 | /* GODOT ENGINE */ 6 | /* https://godotengine.org */ 7 | /**************************************************************************/ 8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ 9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ 10 | /* */ 11 | /* Permission is hereby granted, free of charge, to any person obtaining */ 12 | /* a copy of this software and associated documentation files (the */ 13 | /* "Software"), to deal in the Software without restriction, including */ 14 | /* without limitation the rights to use, copy, modify, merge, publish, */ 15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ 16 | /* permit persons to whom the Software is furnished to do so, subject to */ 17 | /* the following conditions: */ 18 | /* */ 19 | /* The above copyright notice and this permission notice shall be */ 20 | /* included in all copies or substantial portions of the Software. */ 21 | /* */ 22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ 23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ 24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ 25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ 26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ 27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ 28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ 29 | /**************************************************************************/ 30 | 31 | #ifndef THREEN_H 32 | #define THREEN_H 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | using namespace godot; 40 | 41 | // Helper defines ported from 3.x. 42 | #define VARIANT_ARG_MAX 8 43 | #define VARIANT_ARG_DECLARE const Variant &p_arg1, const Variant &p_arg2, const Variant &p_arg3, const Variant &p_arg4, const Variant &p_arg5, const Variant &p_arg6, const Variant &p_arg7, const Variant &p_arg8 44 | 45 | class Threen : public Node { 46 | GDCLASS(Threen, Node); 47 | 48 | public: 49 | enum TweenProcessMode { 50 | TWEEN_PROCESS_PHYSICS, 51 | TWEEN_PROCESS_IDLE, 52 | }; 53 | 54 | enum TransitionType { 55 | TRANS_LINEAR, 56 | TRANS_SINE, 57 | TRANS_QUINT, 58 | TRANS_QUART, 59 | TRANS_QUAD, 60 | TRANS_EXPO, 61 | TRANS_ELASTIC, 62 | TRANS_CUBIC, 63 | TRANS_CIRC, 64 | TRANS_BOUNCE, 65 | TRANS_BACK, 66 | 67 | TRANS_COUNT, 68 | }; 69 | 70 | enum EaseType { 71 | EASE_IN, 72 | EASE_OUT, 73 | EASE_IN_OUT, 74 | EASE_OUT_IN, 75 | 76 | EASE_COUNT, 77 | }; 78 | 79 | private: 80 | enum InterpolateType { 81 | 82 | INTER_PROPERTY, 83 | INTER_METHOD, 84 | FOLLOW_PROPERTY, 85 | FOLLOW_METHOD, 86 | TARGETING_PROPERTY, 87 | TARGETING_METHOD, 88 | INTER_CALLBACK, 89 | }; 90 | 91 | struct InterpolateData { 92 | bool active; 93 | InterpolateType type; 94 | bool finish; 95 | bool call_deferred; 96 | real_t elapsed; 97 | ObjectID id; 98 | Vector key; 99 | StringName concatenated_key; 100 | Variant initial_val; 101 | Variant delta_val; 102 | Variant final_val; 103 | ObjectID target_id; 104 | Vector target_key; 105 | real_t duration; 106 | TransitionType trans_type; 107 | EaseType ease_type; 108 | real_t delay; 109 | int args; 110 | Variant arg[VARIANT_ARG_MAX]; 111 | int uid; 112 | InterpolateData() { 113 | active = false; 114 | finish = false; 115 | call_deferred = false; 116 | uid = 0; 117 | } 118 | }; 119 | 120 | String autoplay; 121 | TweenProcessMode tween_process_mode; 122 | bool repeat; 123 | float speed_scale; 124 | mutable int pending_update; 125 | int uid; 126 | bool was_stopped = false; 127 | List interpolates; 128 | 129 | struct PendingCommand { 130 | StringName key; 131 | int args; 132 | Variant arg[10]; 133 | }; 134 | List pending_commands; 135 | 136 | void _add_pending_command(StringName p_key, const Variant &p_arg1 = Variant(), const Variant &p_arg2 = Variant(), const Variant &p_arg3 = Variant(), const Variant &p_arg4 = Variant(), const Variant &p_arg5 = Variant(), const Variant &p_arg6 = Variant(), const Variant &p_arg7 = Variant(), const Variant &p_arg8 = Variant(), const Variant &p_arg9 = Variant(), const Variant &p_arg10 = Variant()); 137 | void _process_pending_commands(); 138 | 139 | typedef real_t (*interpolater)(real_t t, real_t b, real_t c, real_t d); 140 | static interpolater interpolaters[TRANS_COUNT][EASE_COUNT]; 141 | 142 | Variant &_get_delta_val(InterpolateData &p_data); 143 | Variant _get_initial_val(const InterpolateData &p_data) const; 144 | Variant _get_final_val(const InterpolateData &p_data) const; 145 | Variant _run_equation(InterpolateData &p_data); 146 | bool _calc_delta_val(const Variant &p_initial_val, const Variant &p_final_val, Variant &p_delta_val); 147 | bool _apply_tween_value(InterpolateData &p_data, Variant &value); 148 | 149 | void _tween_process(float p_delta); 150 | void _remove_by_uid(int uid); 151 | void _push_interpolate_data(InterpolateData &p_data); 152 | bool _build_interpolation(InterpolateType p_interpolation_type, Object *p_object, NodePath *p_property, StringName *p_method, Variant p_initial_val, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay); 153 | 154 | protected: 155 | bool _set(const StringName &p_name, const Variant &p_value); 156 | bool _get(const StringName &p_name, Variant &r_ret) const; 157 | void _get_property_list(List *p_list) const; 158 | void _notification(int p_what); 159 | 160 | static void _bind_methods(); 161 | 162 | public: 163 | static real_t run_equation(Threen::TransitionType p_trans_type, Threen::EaseType p_ease_type, real_t p_time, real_t p_initial, real_t p_delta, real_t p_duration); 164 | 165 | bool is_active() const; 166 | void set_active(bool p_active); 167 | 168 | bool is_repeat() const; 169 | void set_repeat(bool p_repeat); 170 | 171 | void set_tween_process_mode(TweenProcessMode p_mode); 172 | TweenProcessMode get_tween_process_mode() const; 173 | 174 | void set_speed_scale(float p_speed); 175 | float get_speed_scale() const; 176 | 177 | bool start(); 178 | bool reset(Object *p_object, StringName p_key); 179 | bool reset_all(); 180 | bool stop(Object *p_object, StringName p_key); 181 | bool stop_all(); 182 | bool resume(Object *p_object, StringName p_key); 183 | bool resume_all(); 184 | bool remove(Object *p_object, StringName p_key); 185 | bool remove_all(); 186 | 187 | bool seek(real_t p_time); 188 | real_t tell() const; 189 | real_t get_runtime() const; 190 | 191 | bool interpolate_property(Object *p_object, NodePath p_property, Variant p_initial_val, Variant p_final_val, real_t p_duration, TransitionType p_trans_type = TRANS_LINEAR, EaseType p_ease_type = EASE_IN_OUT, real_t p_delay = 0); 192 | bool interpolate_method(Object *p_object, StringName p_method, Variant p_initial_val, Variant p_final_val, real_t p_duration, TransitionType p_trans_type = TRANS_LINEAR, EaseType p_ease_type = EASE_IN_OUT, real_t p_delay = 0); 193 | bool interpolate_callback(Object *p_object, real_t p_duration, String p_callback, VARIANT_ARG_DECLARE); 194 | bool interpolate_deferred_callback(Object *p_object, real_t p_duration, String p_callback, VARIANT_ARG_DECLARE); 195 | bool follow_property(Object *p_object, NodePath p_property, Variant p_initial_val, Object *p_target, NodePath p_target_property, real_t p_duration, TransitionType p_trans_type = TRANS_LINEAR, EaseType p_ease_type = EASE_IN_OUT, real_t p_delay = 0); 196 | bool follow_method(Object *p_object, StringName p_method, Variant p_initial_val, Object *p_target, StringName p_target_method, real_t p_duration, TransitionType p_trans_type = TRANS_LINEAR, EaseType p_ease_type = EASE_IN_OUT, real_t p_delay = 0); 197 | bool targeting_property(Object *p_object, NodePath p_property, Object *p_initial, NodePath p_initial_property, Variant p_final_val, real_t p_duration, TransitionType p_trans_type = TRANS_LINEAR, EaseType p_ease_type = EASE_IN_OUT, real_t p_delay = 0); 198 | bool targeting_method(Object *p_object, StringName p_method, Object *p_initial, StringName p_initial_method, Variant p_final_val, real_t p_duration, TransitionType p_trans_type = TRANS_LINEAR, EaseType p_ease_type = EASE_IN_OUT, real_t p_delay = 0); 199 | 200 | Threen(); 201 | ~Threen(); 202 | }; 203 | 204 | VARIANT_ENUM_CAST(Threen::TweenProcessMode); 205 | VARIANT_ENUM_CAST(Threen::TransitionType); 206 | VARIANT_ENUM_CAST(Threen::EaseType); 207 | 208 | #endif // THREEN_H 209 | -------------------------------------------------------------------------------- /src/easing_equations.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************/ 2 | /* easing_equations.h */ 3 | /**************************************************************************/ 4 | /* This file is part of: */ 5 | /* GODOT ENGINE */ 6 | /* https://godotengine.org */ 7 | /**************************************************************************/ 8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ 9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ 10 | /* */ 11 | /* Permission is hereby granted, free of charge, to any person obtaining */ 12 | /* a copy of this software and associated documentation files (the */ 13 | /* "Software"), to deal in the Software without restriction, including */ 14 | /* without limitation the rights to use, copy, modify, merge, publish, */ 15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ 16 | /* permit persons to whom the Software is furnished to do so, subject to */ 17 | /* the following conditions: */ 18 | /* */ 19 | /* The above copyright notice and this permission notice shall be */ 20 | /* included in all copies or substantial portions of the Software. */ 21 | /* */ 22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ 23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ 24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ 25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ 26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ 27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ 28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ 29 | /**************************************************************************/ 30 | 31 | /* 32 | * Derived from Robert Penner's easing equations: http://robertpenner.com/easing/ 33 | * 34 | * Copyright (c) 2001 Robert Penner 35 | * 36 | * Permission is hereby granted, free of charge, to any person obtaining a copy 37 | * of this software and associated documentation files (the "Software"), to deal 38 | * in the Software without restriction, including without limitation the rights 39 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 40 | * copies of the Software, and to permit persons to whom the Software is 41 | * furnished to do so, subject to the following conditions: 42 | * 43 | * The above copyright notice and this permission notice shall be included in all 44 | * copies or substantial portions of the Software. 45 | * 46 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 47 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 48 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 49 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 50 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 51 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 52 | * SOFTWARE. 53 | */ 54 | 55 | #ifndef EASING_EQUATIONS_H 56 | #define EASING_EQUATIONS_H 57 | 58 | namespace linear { 59 | static real_t in(real_t t, real_t b, real_t c, real_t d) { 60 | return c * t / d + b; 61 | } 62 | }; // namespace linear 63 | 64 | namespace sine { 65 | static real_t in(real_t t, real_t b, real_t c, real_t d) { 66 | return -c * cos(t / d * (Math_PI / 2)) + c + b; 67 | } 68 | 69 | static real_t out(real_t t, real_t b, real_t c, real_t d) { 70 | return c * sin(t / d * (Math_PI / 2)) + b; 71 | } 72 | 73 | static real_t in_out(real_t t, real_t b, real_t c, real_t d) { 74 | return -c / 2 * (cos(Math_PI * t / d) - 1) + b; 75 | } 76 | 77 | static real_t out_in(real_t t, real_t b, real_t c, real_t d) { 78 | if (t < d / 2) { 79 | return out(t * 2, b, c / 2, d); 80 | } 81 | return in(t * 2 - d, b + c / 2, c / 2, d); 82 | } 83 | }; // namespace sine 84 | 85 | namespace quint { 86 | static real_t in(real_t t, real_t b, real_t c, real_t d) { 87 | return c * pow(t / d, 5) + b; 88 | } 89 | 90 | static real_t out(real_t t, real_t b, real_t c, real_t d) { 91 | return c * (pow(t / d - 1, 5) + 1) + b; 92 | } 93 | 94 | static real_t in_out(real_t t, real_t b, real_t c, real_t d) { 95 | t = t / d * 2; 96 | 97 | if (t < 1) { 98 | return c / 2 * pow(t, 5) + b; 99 | } 100 | return c / 2 * (pow(t - 2, 5) + 2) + b; 101 | } 102 | 103 | static real_t out_in(real_t t, real_t b, real_t c, real_t d) { 104 | if (t < d / 2) { 105 | return out(t * 2, b, c / 2, d); 106 | } 107 | return in(t * 2 - d, b + c / 2, c / 2, d); 108 | } 109 | }; // namespace quint 110 | 111 | namespace quart { 112 | static real_t in(real_t t, real_t b, real_t c, real_t d) { 113 | return c * pow(t / d, 4) + b; 114 | } 115 | 116 | static real_t out(real_t t, real_t b, real_t c, real_t d) { 117 | return -c * (pow(t / d - 1, 4) - 1) + b; 118 | } 119 | 120 | static real_t in_out(real_t t, real_t b, real_t c, real_t d) { 121 | t = t / d * 2; 122 | 123 | if (t < 1) { 124 | return c / 2 * pow(t, 4) + b; 125 | } 126 | return -c / 2 * (pow(t - 2, 4) - 2) + b; 127 | } 128 | 129 | static real_t out_in(real_t t, real_t b, real_t c, real_t d) { 130 | if (t < d / 2) { 131 | return out(t * 2, b, c / 2, d); 132 | } 133 | return in(t * 2 - d, b + c / 2, c / 2, d); 134 | } 135 | }; // namespace quart 136 | 137 | namespace quad { 138 | static real_t in(real_t t, real_t b, real_t c, real_t d) { 139 | return c * pow(t / d, 2) + b; 140 | } 141 | 142 | static real_t out(real_t t, real_t b, real_t c, real_t d) { 143 | t /= d; 144 | return -c * t * (t - 2) + b; 145 | } 146 | 147 | static real_t in_out(real_t t, real_t b, real_t c, real_t d) { 148 | t = t / d * 2; 149 | 150 | if (t < 1) { 151 | return c / 2 * pow(t, 2) + b; 152 | } 153 | return -c / 2 * ((t - 1) * (t - 3) - 1) + b; 154 | } 155 | 156 | static real_t out_in(real_t t, real_t b, real_t c, real_t d) { 157 | if (t < d / 2) { 158 | return out(t * 2, b, c / 2, d); 159 | } 160 | return in(t * 2 - d, b + c / 2, c / 2, d); 161 | } 162 | }; // namespace quad 163 | 164 | namespace expo { 165 | static real_t in(real_t t, real_t b, real_t c, real_t d) { 166 | if (t == 0) { 167 | return b; 168 | } 169 | return c * pow(2, 10 * (t / d - 1)) + b - c * 0.001; 170 | } 171 | 172 | static real_t out(real_t t, real_t b, real_t c, real_t d) { 173 | if (t == d) { 174 | return b + c; 175 | } 176 | return c * 1.001 * (-pow(2, -10 * t / d) + 1) + b; 177 | } 178 | 179 | static real_t in_out(real_t t, real_t b, real_t c, real_t d) { 180 | if (t == 0) { 181 | return b; 182 | } 183 | 184 | if (t == d) { 185 | return b + c; 186 | } 187 | 188 | t = t / d * 2; 189 | 190 | if (t < 1) { 191 | return c / 2 * pow(2, 10 * (t - 1)) + b - c * 0.0005; 192 | } 193 | return c / 2 * 1.0005 * (-pow(2, -10 * (t - 1)) + 2) + b; 194 | } 195 | 196 | static real_t out_in(real_t t, real_t b, real_t c, real_t d) { 197 | if (t < d / 2) { 198 | return out(t * 2, b, c / 2, d); 199 | } 200 | return in(t * 2 - d, b + c / 2, c / 2, d); 201 | } 202 | }; // namespace expo 203 | 204 | namespace elastic { 205 | static real_t in(real_t t, real_t b, real_t c, real_t d) { 206 | if (t == 0) { 207 | return b; 208 | } 209 | 210 | t /= d; 211 | if (t == 1) { 212 | return b + c; 213 | } 214 | 215 | t -= 1; 216 | float p = d * 0.3f; 217 | float a = c * pow(2, 10 * t); 218 | float s = p / 4; 219 | 220 | return -(a * sin((t * d - s) * (2 * Math_PI) / p)) + b; 221 | } 222 | 223 | static real_t out(real_t t, real_t b, real_t c, real_t d) { 224 | if (t == 0) { 225 | return b; 226 | } 227 | 228 | t /= d; 229 | if (t == 1) { 230 | return b + c; 231 | } 232 | 233 | float p = d * 0.3f; 234 | float s = p / 4; 235 | 236 | return (c * pow(2, -10 * t) * sin((t * d - s) * (2 * Math_PI) / p) + c + b); 237 | } 238 | 239 | static real_t in_out(real_t t, real_t b, real_t c, real_t d) { 240 | if (t == 0) { 241 | return b; 242 | } 243 | 244 | if ((t /= d / 2) == 2) { 245 | return b + c; 246 | } 247 | 248 | float p = d * (0.3f * 1.5f); 249 | float a = c; 250 | float s = p / 4; 251 | 252 | if (t < 1) { 253 | t -= 1; 254 | a *= pow(2, 10 * t); 255 | return -0.5f * (a * sin((t * d - s) * (2 * Math_PI) / p)) + b; 256 | } 257 | 258 | t -= 1; 259 | a *= pow(2, -10 * t); 260 | return a * sin((t * d - s) * (2 * Math_PI) / p) * 0.5f + c + b; 261 | } 262 | 263 | static real_t out_in(real_t t, real_t b, real_t c, real_t d) { 264 | if (t < d / 2) { 265 | return out(t * 2, b, c / 2, d); 266 | } 267 | return in(t * 2 - d, b + c / 2, c / 2, d); 268 | } 269 | }; // namespace elastic 270 | 271 | namespace cubic { 272 | static real_t in(real_t t, real_t b, real_t c, real_t d) { 273 | t /= d; 274 | return c * t * t * t + b; 275 | } 276 | 277 | static real_t out(real_t t, real_t b, real_t c, real_t d) { 278 | t = t / d - 1; 279 | return c * (t * t * t + 1) + b; 280 | } 281 | 282 | static real_t in_out(real_t t, real_t b, real_t c, real_t d) { 283 | t /= d / 2; 284 | if (t < 1) { 285 | return c / 2 * t * t * t + b; 286 | } 287 | 288 | t -= 2; 289 | return c / 2 * (t * t * t + 2) + b; 290 | } 291 | 292 | static real_t out_in(real_t t, real_t b, real_t c, real_t d) { 293 | if (t < d / 2) { 294 | return out(t * 2, b, c / 2, d); 295 | } 296 | return in(t * 2 - d, b + c / 2, c / 2, d); 297 | } 298 | }; // namespace cubic 299 | 300 | namespace circ { 301 | static real_t in(real_t t, real_t b, real_t c, real_t d) { 302 | t /= d; 303 | return -c * (sqrt(1 - t * t) - 1) + b; 304 | } 305 | 306 | static real_t out(real_t t, real_t b, real_t c, real_t d) { 307 | t = t / d - 1; 308 | return c * sqrt(1 - t * t) + b; 309 | } 310 | 311 | static real_t in_out(real_t t, real_t b, real_t c, real_t d) { 312 | t /= d / 2; 313 | if (t < 1) { 314 | return -c / 2 * (sqrt(1 - t * t) - 1) + b; 315 | } 316 | 317 | t -= 2; 318 | return c / 2 * (sqrt(1 - t * t) + 1) + b; 319 | } 320 | 321 | static real_t out_in(real_t t, real_t b, real_t c, real_t d) { 322 | if (t < d / 2) { 323 | return out(t * 2, b, c / 2, d); 324 | } 325 | return in(t * 2 - d, b + c / 2, c / 2, d); 326 | } 327 | }; // namespace circ 328 | 329 | namespace bounce { 330 | static real_t out(real_t t, real_t b, real_t c, real_t d) { 331 | t /= d; 332 | 333 | if (t < (1 / 2.75f)) { 334 | return c * (7.5625f * t * t) + b; 335 | } 336 | 337 | if (t < (2 / 2.75f)) { 338 | t -= 1.5f / 2.75f; 339 | return c * (7.5625f * t * t + 0.75f) + b; 340 | } 341 | 342 | if (t < (2.5 / 2.75)) { 343 | t -= 2.25f / 2.75f; 344 | return c * (7.5625f * t * t + 0.9375f) + b; 345 | } 346 | 347 | t -= 2.625f / 2.75f; 348 | return c * (7.5625f * t * t + 0.984375f) + b; 349 | } 350 | 351 | static real_t in(real_t t, real_t b, real_t c, real_t d) { 352 | return c - out(d - t, 0, c, d) + b; 353 | } 354 | 355 | static real_t in_out(real_t t, real_t b, real_t c, real_t d) { 356 | if (t < d / 2) { 357 | return in(t * 2, b, c / 2, d); 358 | } 359 | return out(t * 2 - d, b + c / 2, c / 2, d); 360 | } 361 | 362 | static real_t out_in(real_t t, real_t b, real_t c, real_t d) { 363 | if (t < d / 2) { 364 | return out(t * 2, b, c / 2, d); 365 | } 366 | return in(t * 2 - d, b + c / 2, c / 2, d); 367 | } 368 | }; // namespace bounce 369 | 370 | namespace back { 371 | static real_t in(real_t t, real_t b, real_t c, real_t d) { 372 | float s = 1.70158f; 373 | t /= d; 374 | 375 | return c * t * t * ((s + 1) * t - s) + b; 376 | } 377 | 378 | static real_t out(real_t t, real_t b, real_t c, real_t d) { 379 | float s = 1.70158f; 380 | t = t / d - 1; 381 | 382 | return c * (t * t * ((s + 1) * t + s) + 1) + b; 383 | } 384 | 385 | static real_t in_out(real_t t, real_t b, real_t c, real_t d) { 386 | float s = 1.70158f * 1.525f; 387 | t /= d / 2; 388 | 389 | if (t < 1) { 390 | return c / 2 * (t * t * ((s + 1) * t - s)) + b; 391 | } 392 | 393 | t -= 2; 394 | return c / 2 * (t * t * ((s + 1) * t + s) + 2) + b; 395 | } 396 | 397 | static real_t out_in(real_t t, real_t b, real_t c, real_t d) { 398 | if (t < d / 2) { 399 | return out(t * 2, b, c / 2, d); 400 | } 401 | return in(t * 2 - d, b + c / 2, c / 2, d); 402 | } 403 | }; // namespace back 404 | 405 | #endif // EASING_EQUATIONS_H 406 | -------------------------------------------------------------------------------- /doc_classes/Threen.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Smoothly animates a node's properties over time. Forward-port of Godot 3's Tween to Godot 4. 5 | 6 | 7 | Threens are useful for animations requiring a numerical property to be interpolated over a range of values. The name [i]tween[/i] comes from [i]in-betweening[/i], an animation technique where you specify [i]keyframes[/i] and the computer interpolates the frames that appear between them. 8 | [Threen] is more suited than [AnimationPlayer] for animations where you don't know the final values in advance. For example, interpolating a dynamically-chosen camera zoom value is best done with a [Threen] node; it would be difficult to do the same thing with an [AnimationPlayer] node. 9 | Here is a brief usage example that makes a 2D node move smoothly between two positions: 10 | [codeblock] 11 | var tween = get_node("Threen") 12 | tween.interpolate_property($Node2D, "position", 13 | Vector2(0, 0), Vector2(100, 100), 1, 14 | Threen.TRANS_LINEAR, Threen.EASE_IN_OUT) 15 | tween.start() 16 | [/codeblock] 17 | Many methods require a property name, such as [code]"position"[/code] above. You can find the correct property name by hovering over the property in the Inspector. You can also provide the components of a property directly by using [code]"property:component"[/code] (e.g. [code]position:x[/code]), where it would only apply to that particular component. 18 | Many of the methods accept [code]trans_type[/code] and [code]ease_type[/code]. The first accepts an [enum TransitionType] constant, and refers to the way the timing of the animation is handled (see [url=https://easings.net/]easings.net[/url] for some examples). The second accepts an [enum EaseType] constant, and controls where the [code]trans_type[/code] is applied to the interpolation (in the beginning, the end, or both). If you don't know which transition and easing to pick, you can try different [enum TransitionType] constants with [constant EASE_IN_OUT], and use the one that looks best. 19 | [url=https://raw.githubusercontent.com/godotengine/godot-docs/3.6/img/tween_cheatsheet.png]Tween easing and transition types cheatsheet[/url] 20 | [b]Note:[/b] Threen methods will return [code]false[/code] if the requested operation cannot be completed. 21 | [b]Note:[/b] For an alternative method of tweening, that doesn't require using nodes, see [Tween]. 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | Follows [code]method[/code] of [code]object[/code] and applies the returned value on [code]target_method[/code] of [code]target[/code], beginning from [code]initial_val[/code] for [code]duration[/code] seconds, [code]delay[/code] later. Methods are called with consecutive values. 39 | Use [enum TransitionType] for [code]trans_type[/code] and [enum EaseType] for [code]ease_type[/code] parameters. These values control the timing and direction of the interpolation. See the class description for more information. 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | Follows [code]property[/code] of [code]object[/code] and applies it on [code]target_property[/code] of [code]target[/code], beginning from [code]initial_val[/code] for [code]duration[/code] seconds, [code]delay[/code] seconds later. 55 | Use [enum TransitionType] for [code]trans_type[/code] and [enum EaseType] for [code]ease_type[/code] parameters. These values control the timing and direction of the interpolation. See the class description for more information. 56 | 57 | 58 | 59 | 60 | 61 | Returns the total time needed for all tweens to end. If you have two tweens, one lasting 10 seconds and the other 20 seconds, it would return 20 seconds, as by that time all tweens would have finished. 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | Calls [code]callback[/code] of [code]object[/code] after [code]duration[/code]. [code]arg1[/code]-[code]arg5[/code] are arguments to be passed to the callback. 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | Calls [code]callback[/code] of [code]object[/code] after [code]duration[/code] on the main thread (similar to [method Object.call_deferred]). [code]arg1[/code]-[code]arg5[/code] are arguments to be passed to the callback. 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | Animates [code]method[/code] of [code]object[/code] from [code]initial_val[/code] to [code]final_val[/code] for [code]duration[/code] seconds, [code]delay[/code] seconds later. Methods are called with consecutive values. 110 | Use [enum TransitionType] for [code]trans_type[/code] and [enum EaseType] for [code]ease_type[/code] parameters. These values control the timing and direction of the interpolation. See the class description for more information. 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | Animates [code]property[/code] of [code]object[/code] from [code]initial_val[/code] to [code]final_val[/code] for [code]duration[/code] seconds, [code]delay[/code] seconds later. Setting the initial value to [code]null[/code] uses the current value of the property. 125 | Use [enum TransitionType] for [code]trans_type[/code] and [enum EaseType] for [code]ease_type[/code] parameters. These values control the timing and direction of the interpolation. See the class description for more information. 126 | 127 | 128 | 129 | 130 | 131 | Returns [code]true[/code] if any tweens are currently running. 132 | [b]Note:[/b] This method doesn't consider tweens that have ended. 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | Stops animation and removes a tween, given its object and property/method pair. By default, all tweens are removed, unless [code]key[/code] is specified. 141 | 142 | 143 | 144 | 145 | 146 | Stops animation and removes all tweens. 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | Resets a tween to its initial value (the one given, not the one before the tween), given its object and property/method pair. By default, all tweens are reset, unless [code]key[/code] is specified. 155 | 156 | 157 | 158 | 159 | 160 | Resets all tweens to their initial values (the ones given, not those before the tween). 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | Continues animating a stopped tween, given its object and property/method pair. By default, all tweens are resumed, unless [code]key[/code] is specified. 169 | 170 | 171 | 172 | 173 | 174 | Continues animating all stopped tweens. 175 | 176 | 177 | 178 | 179 | 180 | 181 | Sets the interpolation to the given [code]time[/code] in seconds. 182 | 183 | 184 | 185 | 186 | 187 | 188 | Activates/deactivates the tween. See also [method stop_all] and [method resume_all]. 189 | 190 | 191 | 192 | 193 | 194 | Starts the tween. You can define animations both before and after this. 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | Stops a tween, given its object and property/method pair. By default, all tweens are stopped, unless [code]key[/code] is specified. 203 | 204 | 205 | 206 | 207 | 208 | Stops animating all tweens. 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | Animates [code]method[/code] of [code]object[/code] from the value returned by [code]initial_method[/code] to [code]final_val[/code] for [code]duration[/code] seconds, [code]delay[/code] seconds later. Methods are animated by calling them with consecutive values. 224 | Use [enum TransitionType] for [code]trans_type[/code] and [enum EaseType] for [code]ease_type[/code] parameters. These values control the timing and direction of the interpolation. See the class description for more information. 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | Animates [code]property[/code] of [code]object[/code] from the current value of the [code]initial_val[/code] property of [code]initial[/code] to [code]final_val[/code] for [code]duration[/code] seconds, [code]delay[/code] seconds later. 240 | Use [enum TransitionType] for [code]trans_type[/code] and [enum EaseType] for [code]ease_type[/code] parameters. These values control the timing and direction of the interpolation. See the class description for more information. 241 | 242 | 243 | 244 | 245 | 246 | Returns the current time of the tween. 247 | 248 | 249 | 250 | 251 | 252 | The tween's animation process thread. See [enum TweenProcessMode]. 253 | 254 | 255 | The tween's speed multiplier. For example, set it to [code]1.0[/code] for normal speed, [code]2.0[/code] for two times normal speed, or [code]0.5[/code] for half of the normal speed. A value of [code]0[/code] pauses the animation, but see also [method set_active] or [method stop_all] for this. 256 | 257 | 258 | If [code]true[/code], the tween loops. 259 | 260 | 261 | 262 | 263 | 264 | Emitted when all processes in a tween end. 265 | 266 | 267 | 268 | 269 | 270 | 271 | Emitted when a tween ends. 272 | 273 | 274 | 275 | 276 | 277 | 278 | Emitted when a tween starts. 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | Emitted at each step of the animation. 288 | 289 | 290 | 291 | 292 | 293 | The tween updates with the [code]_physics_process[/code] callback. 294 | 295 | 296 | The tween updates with the [code]_process[/code] callback. 297 | 298 | 299 | The animation is interpolated linearly. 300 | 301 | 302 | The animation is interpolated using a sine function. 303 | 304 | 305 | The animation is interpolated with a quintic (to the power of 5) function. 306 | 307 | 308 | The animation is interpolated with a quartic (to the power of 4) function. 309 | 310 | 311 | The animation is interpolated with a quadratic (to the power of 2) function. 312 | 313 | 314 | The animation is interpolated with an exponential (to the power of x) function. 315 | 316 | 317 | The animation is interpolated with elasticity, wiggling around the edges. 318 | 319 | 320 | The animation is interpolated with a cubic (to the power of 3) function. 321 | 322 | 323 | The animation is interpolated with a function using square roots. 324 | 325 | 326 | The animation is interpolated by bouncing at the end. 327 | 328 | 329 | The animation is interpolated backing out at ends. 330 | 331 | 332 | The interpolation starts slowly and speeds up towards the end. 333 | 334 | 335 | The interpolation starts quickly and slows down towards the end. 336 | 337 | 338 | A combination of [constant EASE_IN] and [constant EASE_OUT]. The interpolation is slowest at both ends. 339 | 340 | 341 | A combination of [constant EASE_IN] and [constant EASE_OUT]. The interpolation is fastest at both ends. 342 | 343 | 344 | 345 | -------------------------------------------------------------------------------- /src/threen.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************/ 2 | /* threen.cpp */ 3 | /**************************************************************************/ 4 | /* This file is part of: */ 5 | /* GODOT ENGINE */ 6 | /* https://godotengine.org */ 7 | /**************************************************************************/ 8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ 9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ 10 | /* */ 11 | /* Permission is hereby granted, free of charge, to any person obtaining */ 12 | /* a copy of this software and associated documentation files (the */ 13 | /* "Software"), to deal in the Software without restriction, including */ 14 | /* without limitation the rights to use, copy, modify, merge, publish, */ 15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ 16 | /* permit persons to whom the Software is furnished to do so, subject to */ 17 | /* the following conditions: */ 18 | /* */ 19 | /* The above copyright notice and this permission notice shall be */ 20 | /* included in all copies or substantial portions of the Software. */ 21 | /* */ 22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ 23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ 24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ 25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ 26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ 27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ 28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ 29 | /**************************************************************************/ 30 | 31 | #include "threen.h" 32 | 33 | #include "easing_equations.h" 34 | 35 | #include 36 | 37 | using namespace godot; 38 | 39 | // Helpers to handle the difference between core Object::get_indexed and the bindings version, 40 | // and in this class we only care about subnames. 41 | static NodePath nodepath_from_subnames(const Vector &p_subnames) { 42 | String spath; 43 | for (int i = 0; i < p_subnames.size(); i++) { 44 | spath += ":" + String(p_subnames[i]); 45 | } 46 | return NodePath(spath); 47 | } 48 | 49 | static Vector nodepath_get_subnames(const NodePath &p_nodepath) { 50 | PackedStringArray sv = p_nodepath.get_concatenated_subnames().split(":"); 51 | Vector snv; 52 | for (int i = 0; i < sv.size(); i++) { 53 | snv.push_back(StringName(sv[i])); 54 | } 55 | return snv; 56 | } 57 | 58 | static NodePath nodepath_subnames_only(const NodePath &p_nodepath) { 59 | return nodepath_from_subnames(nodepath_get_subnames(p_nodepath)); 60 | } 61 | 62 | Threen::interpolater Threen::interpolaters[Threen::TRANS_COUNT][Threen::EASE_COUNT] = { 63 | { &linear::in, &linear::in, &linear::in, &linear::in }, // Linear is the same for each easing. 64 | { &sine::in, &sine::out, &sine::in_out, &sine::out_in }, 65 | { &quint::in, &quint::out, &quint::in_out, &quint::out_in }, 66 | { &quart::in, &quart::out, &quart::in_out, &quart::out_in }, 67 | { &quad::in, &quad::out, &quad::in_out, &quad::out_in }, 68 | { &expo::in, &expo::out, &expo::in_out, &expo::out_in }, 69 | { &elastic::in, &elastic::out, &elastic::in_out, &elastic::out_in }, 70 | { &cubic::in, &cubic::out, &cubic::in_out, &cubic::out_in }, 71 | { &circ::in, &circ::out, &circ::in_out, &circ::out_in }, 72 | { &bounce::in, &bounce::out, &bounce::in_out, &bounce::out_in }, 73 | { &back::in, &back::out, &back::in_out, &back::out_in }, 74 | }; 75 | 76 | real_t Threen::run_equation(Threen::TransitionType p_trans_type, Threen::EaseType p_ease_type, real_t p_time, real_t p_initial, real_t p_delta, real_t p_duration) { 77 | if (p_duration == 0) { 78 | // Special case to avoid dividing by 0 in equations. 79 | return p_initial + p_delta; 80 | } 81 | 82 | interpolater func = interpolaters[p_trans_type][p_ease_type]; 83 | ERR_FAIL_NULL_V(func, p_initial); 84 | return func(p_time, p_initial, p_delta, p_duration); 85 | } 86 | 87 | void Threen::_add_pending_command(StringName p_key, const Variant &p_arg1, const Variant &p_arg2, const Variant &p_arg3, const Variant &p_arg4, const Variant &p_arg5, const Variant &p_arg6, const Variant &p_arg7, const Variant &p_arg8, const Variant &p_arg9, const Variant &p_arg10) { 88 | // Add a new pending command and reference it 89 | pending_commands.push_back(PendingCommand()); 90 | PendingCommand &cmd = pending_commands.back()->get(); 91 | 92 | // Update the command with the target key 93 | cmd.key = p_key; 94 | 95 | // Determine command argument count 96 | int &count = cmd.args; 97 | if (p_arg10.get_type() != Variant::NIL) { 98 | count = 10; 99 | } else if (p_arg9.get_type() != Variant::NIL) { 100 | count = 9; 101 | } else if (p_arg8.get_type() != Variant::NIL) { 102 | count = 8; 103 | } else if (p_arg7.get_type() != Variant::NIL) { 104 | count = 7; 105 | } else if (p_arg6.get_type() != Variant::NIL) { 106 | count = 6; 107 | } else if (p_arg5.get_type() != Variant::NIL) { 108 | count = 5; 109 | } else if (p_arg4.get_type() != Variant::NIL) { 110 | count = 4; 111 | } else if (p_arg3.get_type() != Variant::NIL) { 112 | count = 3; 113 | } else if (p_arg2.get_type() != Variant::NIL) { 114 | count = 2; 115 | } else if (p_arg1.get_type() != Variant::NIL) { 116 | count = 1; 117 | } else { 118 | count = 0; 119 | } 120 | 121 | // Add the specified arguments to the command 122 | if (count > 0) { 123 | cmd.arg[0] = p_arg1; 124 | } 125 | if (count > 1) { 126 | cmd.arg[1] = p_arg2; 127 | } 128 | if (count > 2) { 129 | cmd.arg[2] = p_arg3; 130 | } 131 | if (count > 3) { 132 | cmd.arg[3] = p_arg4; 133 | } 134 | if (count > 4) { 135 | cmd.arg[4] = p_arg5; 136 | } 137 | if (count > 5) { 138 | cmd.arg[5] = p_arg6; 139 | } 140 | if (count > 6) { 141 | cmd.arg[6] = p_arg7; 142 | } 143 | if (count > 7) { 144 | cmd.arg[7] = p_arg8; 145 | } 146 | if (count > 8) { 147 | cmd.arg[8] = p_arg9; 148 | } 149 | if (count > 9) { 150 | cmd.arg[9] = p_arg10; 151 | } 152 | } 153 | 154 | void Threen::_process_pending_commands() { 155 | // For each pending command... 156 | for (List::Element *E = pending_commands.front(); E; E = E->next()) { 157 | // Get the command 158 | PendingCommand &cmd = E->get(); 159 | 160 | // FIXME: Ugly workaround as we don't have callp exposed in GDExtension. 161 | // All the hardcoded vararg logic in this class could be simplified. 162 | 163 | // Execute the command (and retrieve any errors) 164 | switch (cmd.args) { 165 | case 0: 166 | this->call(cmd.key); 167 | break; 168 | case 1: 169 | this->call(cmd.key, cmd.arg[0]); 170 | break; 171 | case 2: 172 | this->call(cmd.key, cmd.arg[0], cmd.arg[1]); 173 | break; 174 | case 3: 175 | this->call(cmd.key, cmd.arg[0], cmd.arg[1], cmd.arg[2]); 176 | break; 177 | case 4: 178 | this->call(cmd.key, cmd.arg[0], cmd.arg[1], cmd.arg[2], cmd.arg[3]); 179 | break; 180 | case 5: 181 | this->call(cmd.key, cmd.arg[0], cmd.arg[1], cmd.arg[2], cmd.arg[3], cmd.arg[4]); 182 | break; 183 | case 6: 184 | this->call(cmd.key, cmd.arg[0], cmd.arg[1], cmd.arg[2], cmd.arg[3], cmd.arg[4], cmd.arg[5]); 185 | break; 186 | case 7: 187 | this->call(cmd.key, cmd.arg[0], cmd.arg[1], cmd.arg[2], cmd.arg[3], cmd.arg[4], cmd.arg[5], cmd.arg[6]); 188 | break; 189 | case 8: 190 | this->call(cmd.key, cmd.arg[0], cmd.arg[1], cmd.arg[2], cmd.arg[3], cmd.arg[4], cmd.arg[5], cmd.arg[6], cmd.arg[7]); 191 | break; 192 | case 9: 193 | this->call(cmd.key, cmd.arg[0], cmd.arg[1], cmd.arg[2], cmd.arg[3], cmd.arg[4], cmd.arg[5], cmd.arg[6], cmd.arg[7], cmd.arg[8]); 194 | break; 195 | case 10: 196 | this->call(cmd.key, cmd.arg[0], cmd.arg[1], cmd.arg[2], cmd.arg[3], cmd.arg[4], cmd.arg[5], cmd.arg[6], cmd.arg[7], cmd.arg[8], cmd.arg[9]); 197 | break; 198 | } 199 | } 200 | 201 | // Clear the pending commands 202 | pending_commands.clear(); 203 | } 204 | 205 | bool Threen::_set(const StringName &p_name, const Variant &p_value) { 206 | // Set the correct attribute based on the given name 207 | String name = p_name; 208 | if (name == "playback/speed" || name == "speed") { // Backwards compatibility 209 | set_speed_scale(p_value); 210 | return true; 211 | 212 | } else if (name == "playback/active") { 213 | set_active(p_value); 214 | return true; 215 | 216 | } else if (name == "playback/repeat") { 217 | set_repeat(p_value); 218 | return true; 219 | } 220 | return false; 221 | } 222 | 223 | bool Threen::_get(const StringName &p_name, Variant &r_ret) const { 224 | // Get the correct attribute based on the given name 225 | String name = p_name; 226 | if (name == "playback/speed") { // Backwards compatibility 227 | r_ret = speed_scale; 228 | return true; 229 | 230 | } else if (name == "playback/active") { 231 | r_ret = is_active(); 232 | return true; 233 | 234 | } else if (name == "playback/repeat") { 235 | r_ret = is_repeat(); 236 | return true; 237 | } 238 | return false; 239 | } 240 | 241 | void Threen::_get_property_list(List *p_list) const { 242 | // Add the property info for the Threen object 243 | p_list->push_back(PropertyInfo(Variant::BOOL, "playback/active", PROPERTY_HINT_NONE, "")); 244 | p_list->push_back(PropertyInfo(Variant::BOOL, "playback/repeat", PROPERTY_HINT_NONE, "")); 245 | p_list->push_back(PropertyInfo(Variant::FLOAT, "playback/speed", PROPERTY_HINT_RANGE, "-64,64,0.01")); 246 | } 247 | 248 | void Threen::_notification(int p_what) { 249 | // What notification did we receive? 250 | switch (p_what) { 251 | case NOTIFICATION_ENTER_TREE: { 252 | // Are we not already active? 253 | if (!is_active()) { 254 | // Make sure that a previous process state was not saved 255 | // Only process if "processing" is set 256 | set_physics_process_internal(false); 257 | set_process_internal(false); 258 | } 259 | } break; 260 | 261 | case NOTIFICATION_READY: { 262 | // Do nothing 263 | } break; 264 | 265 | case NOTIFICATION_INTERNAL_PROCESS: { 266 | // Are we processing during physics time? 267 | if (tween_process_mode == TWEEN_PROCESS_PHYSICS) { 268 | // Do nothing since we aren't aligned with physics when we should be 269 | break; 270 | } 271 | 272 | // Should we update? 273 | if (is_active()) { 274 | // Update the tweens 275 | _tween_process(get_process_delta_time()); 276 | } 277 | } break; 278 | 279 | case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { 280 | // Are we processing during 'regular' time? 281 | if (tween_process_mode == TWEEN_PROCESS_IDLE) { 282 | // Do nothing since we would only process during idle time 283 | break; 284 | } 285 | 286 | // Should we update? 287 | if (is_active()) { 288 | // Update the tweens 289 | _tween_process(get_physics_process_delta_time()); 290 | } 291 | } break; 292 | 293 | case NOTIFICATION_EXIT_TREE: { 294 | // We've left the tree. Stop all tweens 295 | stop_all(); 296 | } break; 297 | } 298 | } 299 | 300 | void Threen::_bind_methods() { 301 | // Bind getters and setters 302 | ClassDB::bind_method(D_METHOD("is_active"), &Threen::is_active); 303 | ClassDB::bind_method(D_METHOD("set_active", "active"), &Threen::set_active); 304 | 305 | ClassDB::bind_method(D_METHOD("is_repeat"), &Threen::is_repeat); 306 | ClassDB::bind_method(D_METHOD("set_repeat", "repeat"), &Threen::set_repeat); 307 | 308 | ClassDB::bind_method(D_METHOD("set_speed_scale", "speed"), &Threen::set_speed_scale); 309 | ClassDB::bind_method(D_METHOD("get_speed_scale"), &Threen::get_speed_scale); 310 | 311 | ClassDB::bind_method(D_METHOD("set_tween_process_mode", "mode"), &Threen::set_tween_process_mode); 312 | ClassDB::bind_method(D_METHOD("get_tween_process_mode"), &Threen::get_tween_process_mode); 313 | 314 | // Bind the various Threen control methods 315 | ClassDB::bind_method(D_METHOD("start"), &Threen::start); 316 | ClassDB::bind_method(D_METHOD("reset", "object", "key"), &Threen::reset, DEFVAL("")); 317 | ClassDB::bind_method(D_METHOD("reset_all"), &Threen::reset_all); 318 | ClassDB::bind_method(D_METHOD("stop", "object", "key"), &Threen::stop, DEFVAL("")); 319 | ClassDB::bind_method(D_METHOD("stop_all"), &Threen::stop_all); 320 | ClassDB::bind_method(D_METHOD("resume", "object", "key"), &Threen::resume, DEFVAL("")); 321 | ClassDB::bind_method(D_METHOD("resume_all"), &Threen::resume_all); 322 | ClassDB::bind_method(D_METHOD("remove", "object", "key"), &Threen::remove, DEFVAL("")); 323 | ClassDB::bind_method(D_METHOD("_remove_by_uid", "uid"), &Threen::_remove_by_uid); 324 | ClassDB::bind_method(D_METHOD("remove_all"), &Threen::remove_all); 325 | ClassDB::bind_method(D_METHOD("seek", "time"), &Threen::seek); 326 | ClassDB::bind_method(D_METHOD("tell"), &Threen::tell); 327 | ClassDB::bind_method(D_METHOD("get_runtime"), &Threen::get_runtime); 328 | 329 | // Bind interpolation and follow methods 330 | ClassDB::bind_method(D_METHOD("interpolate_property", "object", "property", "initial_val", "final_val", "duration", "trans_type", "ease_type", "delay"), &Threen::interpolate_property, DEFVAL(TRANS_LINEAR), DEFVAL(EASE_IN_OUT), DEFVAL(0)); 331 | ClassDB::bind_method(D_METHOD("interpolate_method", "object", "method", "initial_val", "final_val", "duration", "trans_type", "ease_type", "delay"), &Threen::interpolate_method, DEFVAL(TRANS_LINEAR), DEFVAL(EASE_IN_OUT), DEFVAL(0)); 332 | ClassDB::bind_method(D_METHOD("interpolate_callback", "object", "duration", "callback", "arg1", "arg2", "arg3", "arg4", "arg5", "arg6", "arg7", "arg8"), &Threen::interpolate_callback, DEFVAL(Variant()), DEFVAL(Variant()), DEFVAL(Variant()), DEFVAL(Variant()), DEFVAL(Variant()), DEFVAL(Variant()), DEFVAL(Variant()), DEFVAL(Variant())); 333 | ClassDB::bind_method(D_METHOD("interpolate_deferred_callback", "object", "duration", "callback", "arg1", "arg2", "arg3", "arg4", "arg5", "arg6", "arg7", "arg8"), &Threen::interpolate_deferred_callback, DEFVAL(Variant()), DEFVAL(Variant()), DEFVAL(Variant()), DEFVAL(Variant()), DEFVAL(Variant()), DEFVAL(Variant()), DEFVAL(Variant()), DEFVAL(Variant())); 334 | ClassDB::bind_method(D_METHOD("follow_property", "object", "property", "initial_val", "target", "target_property", "duration", "trans_type", "ease_type", "delay"), &Threen::follow_property, DEFVAL(TRANS_LINEAR), DEFVAL(EASE_IN_OUT), DEFVAL(0)); 335 | ClassDB::bind_method(D_METHOD("follow_method", "object", "method", "initial_val", "target", "target_method", "duration", "trans_type", "ease_type", "delay"), &Threen::follow_method, DEFVAL(TRANS_LINEAR), DEFVAL(EASE_IN_OUT), DEFVAL(0)); 336 | ClassDB::bind_method(D_METHOD("targeting_property", "object", "property", "initial", "initial_val", "final_val", "duration", "trans_type", "ease_type", "delay"), &Threen::targeting_property, DEFVAL(TRANS_LINEAR), DEFVAL(EASE_IN_OUT), DEFVAL(0)); 337 | ClassDB::bind_method(D_METHOD("targeting_method", "object", "method", "initial", "initial_method", "final_val", "duration", "trans_type", "ease_type", "delay"), &Threen::targeting_method, DEFVAL(TRANS_LINEAR), DEFVAL(EASE_IN_OUT), DEFVAL(0)); 338 | 339 | // Add the Threen signals 340 | ADD_SIGNAL(MethodInfo("tween_started", PropertyInfo(Variant::OBJECT, "object"), PropertyInfo(Variant::NODE_PATH, "key"))); 341 | ADD_SIGNAL(MethodInfo("tween_step", PropertyInfo(Variant::OBJECT, "object"), PropertyInfo(Variant::NODE_PATH, "key"), PropertyInfo(Variant::FLOAT, "elapsed"), PropertyInfo(Variant::OBJECT, "value"))); 342 | ADD_SIGNAL(MethodInfo("tween_completed", PropertyInfo(Variant::OBJECT, "object"), PropertyInfo(Variant::NODE_PATH, "key"))); 343 | ADD_SIGNAL(MethodInfo("tween_all_completed")); 344 | 345 | // Add the properties and tie them to the getters and setters 346 | ADD_PROPERTY(PropertyInfo(Variant::BOOL, "repeat"), "set_repeat", "is_repeat"); 347 | ADD_PROPERTY(PropertyInfo(Variant::INT, "playback_process_mode", PROPERTY_HINT_ENUM, "Physics,Idle"), "set_tween_process_mode", "get_tween_process_mode"); 348 | ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "playback_speed", PROPERTY_HINT_RANGE, "-64,64,0.01"), "set_speed_scale", "get_speed_scale"); 349 | 350 | // Bind Idle vs Physics process 351 | BIND_ENUM_CONSTANT(TWEEN_PROCESS_PHYSICS); 352 | BIND_ENUM_CONSTANT(TWEEN_PROCESS_IDLE); 353 | 354 | // Bind the Transition type constants 355 | BIND_ENUM_CONSTANT(TRANS_LINEAR); 356 | BIND_ENUM_CONSTANT(TRANS_SINE); 357 | BIND_ENUM_CONSTANT(TRANS_QUINT); 358 | BIND_ENUM_CONSTANT(TRANS_QUART); 359 | BIND_ENUM_CONSTANT(TRANS_QUAD); 360 | BIND_ENUM_CONSTANT(TRANS_EXPO); 361 | BIND_ENUM_CONSTANT(TRANS_ELASTIC); 362 | BIND_ENUM_CONSTANT(TRANS_CUBIC); 363 | BIND_ENUM_CONSTANT(TRANS_CIRC); 364 | BIND_ENUM_CONSTANT(TRANS_BOUNCE); 365 | BIND_ENUM_CONSTANT(TRANS_BACK); 366 | 367 | // Bind the easing constants 368 | BIND_ENUM_CONSTANT(EASE_IN); 369 | BIND_ENUM_CONSTANT(EASE_OUT); 370 | BIND_ENUM_CONSTANT(EASE_IN_OUT); 371 | BIND_ENUM_CONSTANT(EASE_OUT_IN); 372 | } 373 | 374 | Variant Threen::_get_initial_val(const InterpolateData &p_data) const { 375 | // What type of data are we interpolating? 376 | switch (p_data.type) { 377 | case INTER_PROPERTY: 378 | case INTER_METHOD: 379 | case FOLLOW_PROPERTY: 380 | case FOLLOW_METHOD: 381 | // Simply use the given initial value 382 | return p_data.initial_val; 383 | 384 | case TARGETING_PROPERTY: 385 | case TARGETING_METHOD: { 386 | // Get the object that is being targeted 387 | Object *object = ObjectDB::get_instance(p_data.target_id); 388 | ERR_FAIL_COND_V(object == nullptr, p_data.initial_val); 389 | 390 | // Are we targeting a property or a method? 391 | Variant initial_val; 392 | if (p_data.type == TARGETING_PROPERTY) { 393 | // Get the property from the target object 394 | initial_val = object->get_indexed(nodepath_from_subnames(p_data.target_key)); 395 | } else { 396 | // Call the method and get the initial value from it 397 | initial_val = object->call(p_data.target_key[0]); 398 | //ERR_FAIL_COND_V(error.error != GDEXTENSION_CALL_OK, p_data.initial_val); 399 | } 400 | return initial_val; 401 | } 402 | 403 | case INTER_CALLBACK: 404 | // Callback does not have a special initial value 405 | break; 406 | } 407 | // If we've made it here, just return the delta value as the initial value 408 | return p_data.delta_val; 409 | } 410 | 411 | Variant Threen::_get_final_val(const InterpolateData &p_data) const { 412 | switch (p_data.type) { 413 | case FOLLOW_PROPERTY: 414 | case FOLLOW_METHOD: { 415 | // Get the object that is being followed 416 | Object *target = ObjectDB::get_instance(p_data.target_id); 417 | ERR_FAIL_COND_V(target == nullptr, p_data.initial_val); 418 | 419 | // We want to figure out the final value 420 | Variant final_val; 421 | if (p_data.type == FOLLOW_PROPERTY) { 422 | // Read the property as-is 423 | final_val = target->get_indexed(nodepath_from_subnames(p_data.target_key)); 424 | } else { 425 | // We're looking at a method. Call the method on the target object 426 | final_val = target->call(p_data.target_key[0]); 427 | //ERR_FAIL_COND_V(error.error != GDEXTENSION_CALL_OK, p_data.initial_val); 428 | } 429 | 430 | // If we're looking at an INT value, instead convert it to a REAL 431 | // This is better for interpolation 432 | if (final_val.get_type() == Variant::INT) { 433 | final_val = final_val.operator real_t(); 434 | } 435 | 436 | return final_val; 437 | } 438 | default: { 439 | // If we're not following a final value/method, use the final value from the data 440 | return p_data.final_val; 441 | } 442 | } 443 | } 444 | 445 | Variant &Threen::_get_delta_val(InterpolateData &p_data) { 446 | // What kind of data are we interpolating? 447 | switch (p_data.type) { 448 | case INTER_PROPERTY: 449 | case INTER_METHOD: 450 | // Simply return the given delta value 451 | return p_data.delta_val; 452 | 453 | case FOLLOW_PROPERTY: 454 | case FOLLOW_METHOD: { 455 | // We're following an object, so grab that instance 456 | Object *target = ObjectDB::get_instance(p_data.target_id); 457 | ERR_FAIL_COND_V(target == nullptr, p_data.initial_val); 458 | 459 | // We want to figure out the final value 460 | Variant final_val; 461 | if (p_data.type == FOLLOW_PROPERTY) { 462 | // Read the property as-is 463 | final_val = target->get_indexed(nodepath_from_subnames(p_data.target_key)); 464 | } else { 465 | // We're looking at a method. Call the method on the target object 466 | final_val = target->call(p_data.target_key[0]); 467 | //ERR_FAIL_COND_V(error.error != GDEXTENSION_CALL_OK, p_data.initial_val); 468 | } 469 | 470 | // If we're looking at an INT value, instead convert it to a REAL 471 | // This is better for interpolation 472 | if (final_val.get_type() == Variant::INT) { 473 | final_val = final_val.operator real_t(); 474 | } 475 | 476 | // Calculate the delta based on the initial value and the final value 477 | _calc_delta_val(p_data.initial_val, final_val, p_data.delta_val); 478 | return p_data.delta_val; 479 | } 480 | 481 | case TARGETING_PROPERTY: 482 | case TARGETING_METHOD: { 483 | // Grab the initial value from the data to calculate delta 484 | Variant initial_val = _get_initial_val(p_data); 485 | 486 | // If we're looking at an INT value, instead convert it to a REAL 487 | // This is better for interpolation 488 | if (initial_val.get_type() == Variant::INT) { 489 | initial_val = initial_val.operator real_t(); 490 | } 491 | 492 | // Calculate the delta based on the initial value and the final value 493 | _calc_delta_val(initial_val, p_data.final_val, p_data.delta_val); 494 | return p_data.delta_val; 495 | } 496 | 497 | case INTER_CALLBACK: 498 | // Callbacks have no special delta 499 | break; 500 | } 501 | // If we've made it here, use the initial value as the delta 502 | return p_data.initial_val; 503 | } 504 | 505 | Variant Threen::_run_equation(InterpolateData &p_data) { 506 | // Get the initial and delta values from the data 507 | Variant initial_val = _get_initial_val(p_data); 508 | Variant &delta_val = _get_delta_val(p_data); 509 | Variant result; 510 | 511 | #define APPLY_EQUATION(element) \ 512 | r.element = run_equation(p_data.trans_type, p_data.ease_type, p_data.elapsed - p_data.delay, i.element, d.element, p_data.duration); 513 | 514 | // What type of data are we interpolating? 515 | switch (initial_val.get_type()) { 516 | case Variant::BOOL: 517 | // Run the boolean specific equation (checking if it is at least 0.5) 518 | result = (run_equation(p_data.trans_type, p_data.ease_type, p_data.elapsed - p_data.delay, initial_val, delta_val, p_data.duration)) >= 0.5; 519 | break; 520 | 521 | case Variant::INT: 522 | // Run the integer specific equation 523 | result = (int)run_equation(p_data.trans_type, p_data.ease_type, p_data.elapsed - p_data.delay, (int)initial_val, (int)delta_val, p_data.duration); 524 | break; 525 | 526 | case Variant::FLOAT: 527 | // Run the REAL specific equation 528 | result = run_equation(p_data.trans_type, p_data.ease_type, p_data.elapsed - p_data.delay, (real_t)initial_val, (real_t)delta_val, p_data.duration); 529 | break; 530 | 531 | case Variant::VECTOR2: { 532 | // Get vectors for initial and delta values 533 | Vector2 i = initial_val; 534 | Vector2 d = delta_val; 535 | Vector2 r; 536 | 537 | // Execute the equation and mutate the r vector 538 | // This uses the custom APPLY_EQUATION macro defined above 539 | APPLY_EQUATION(x); 540 | APPLY_EQUATION(y); 541 | result = r; 542 | } break; 543 | 544 | case Variant::RECT2: { 545 | // Get the Rect2 for initial and delta value 546 | Rect2 i = initial_val; 547 | Rect2 d = delta_val; 548 | Rect2 r; 549 | 550 | // Execute the equation for the position and size of Rect2 551 | APPLY_EQUATION(position.x); 552 | APPLY_EQUATION(position.y); 553 | APPLY_EQUATION(size.x); 554 | APPLY_EQUATION(size.y); 555 | result = r; 556 | } break; 557 | 558 | case Variant::VECTOR3: { 559 | // Get vectors for initial and delta values 560 | Vector3 i = initial_val; 561 | Vector3 d = delta_val; 562 | Vector3 r; 563 | 564 | // Execute the equation and mutate the r vector 565 | // This uses the custom APPLY_EQUATION macro defined above 566 | APPLY_EQUATION(x); 567 | APPLY_EQUATION(y); 568 | APPLY_EQUATION(z); 569 | result = r; 570 | } break; 571 | 572 | case Variant::TRANSFORM2D: { 573 | // Get the transforms for initial and delta values 574 | Transform2D i = initial_val; 575 | Transform2D d = delta_val; 576 | Transform2D r; 577 | 578 | // Execute the equation on the transforms and mutate the r transform 579 | // This uses the custom APPLY_EQUATION macro defined above 580 | APPLY_EQUATION(columns[0][0]); 581 | APPLY_EQUATION(columns[0][1]); 582 | APPLY_EQUATION(columns[1][0]); 583 | APPLY_EQUATION(columns[1][1]); 584 | APPLY_EQUATION(columns[2][0]); 585 | APPLY_EQUATION(columns[2][1]); 586 | result = r; 587 | } break; 588 | 589 | case Variant::QUATERNION: { 590 | // Get the quaternian for the initial and delta values 591 | Quaternion i = initial_val; 592 | Quaternion d = delta_val; 593 | Quaternion r; 594 | 595 | // Execute the equation on the quaternian values and mutate the r quaternian 596 | // This uses the custom APPLY_EQUATION macro defined above 597 | APPLY_EQUATION(x); 598 | APPLY_EQUATION(y); 599 | APPLY_EQUATION(z); 600 | APPLY_EQUATION(w); 601 | result = r; 602 | } break; 603 | 604 | case Variant::AABB: { 605 | // Get the AABB's for the initial and delta values 606 | AABB i = initial_val; 607 | AABB d = delta_val; 608 | AABB r; 609 | 610 | // Execute the equation for the position and size of the AABB's and mutate the r AABB 611 | // This uses the custom APPLY_EQUATION macro defined above 612 | APPLY_EQUATION(position.x); 613 | APPLY_EQUATION(position.y); 614 | APPLY_EQUATION(position.z); 615 | APPLY_EQUATION(size.x); 616 | APPLY_EQUATION(size.y); 617 | APPLY_EQUATION(size.z); 618 | result = r; 619 | } break; 620 | 621 | case Variant::BASIS: { 622 | // Get the basis for initial and delta values 623 | Basis i = initial_val; 624 | Basis d = delta_val; 625 | Basis r; 626 | 627 | // Execute the equation on all the basis and mutate the r basis 628 | // This uses the custom APPLY_EQUATION macro defined above 629 | APPLY_EQUATION(rows[0][0]); 630 | APPLY_EQUATION(rows[0][1]); 631 | APPLY_EQUATION(rows[0][2]); 632 | APPLY_EQUATION(rows[1][0]); 633 | APPLY_EQUATION(rows[1][1]); 634 | APPLY_EQUATION(rows[1][2]); 635 | APPLY_EQUATION(rows[2][0]); 636 | APPLY_EQUATION(rows[2][1]); 637 | APPLY_EQUATION(rows[2][2]); 638 | result = r; 639 | } break; 640 | 641 | case Variant::TRANSFORM3D: { 642 | // Get the transforms for the initial and delta values 643 | Transform3D i = initial_val; 644 | Transform3D d = delta_val; 645 | Transform3D r; 646 | 647 | // Execute the equation for each of the transforms and their origin and mutate the r transform 648 | // This uses the custom APPLY_EQUATION macro defined above 649 | APPLY_EQUATION(basis.rows[0][0]); 650 | APPLY_EQUATION(basis.rows[0][1]); 651 | APPLY_EQUATION(basis.rows[0][2]); 652 | APPLY_EQUATION(basis.rows[1][0]); 653 | APPLY_EQUATION(basis.rows[1][1]); 654 | APPLY_EQUATION(basis.rows[1][2]); 655 | APPLY_EQUATION(basis.rows[2][0]); 656 | APPLY_EQUATION(basis.rows[2][1]); 657 | APPLY_EQUATION(basis.rows[2][2]); 658 | APPLY_EQUATION(origin.x); 659 | APPLY_EQUATION(origin.y); 660 | APPLY_EQUATION(origin.z); 661 | result = r; 662 | } break; 663 | 664 | case Variant::COLOR: { 665 | // Get the Color for initial and delta value 666 | Color i = initial_val; 667 | Color d = delta_val; 668 | Color r; 669 | 670 | // Apply the equation on the Color RGBA, and mutate the r color 671 | // This uses the custom APPLY_EQUATION macro defined above 672 | APPLY_EQUATION(r); 673 | APPLY_EQUATION(g); 674 | APPLY_EQUATION(b); 675 | APPLY_EQUATION(a); 676 | result = r; 677 | } break; 678 | 679 | default: { 680 | // If unknown, just return the initial value 681 | result = initial_val; 682 | } break; 683 | }; 684 | #undef APPLY_EQUATION 685 | // Return the result that was computed 686 | return result; 687 | } 688 | 689 | bool Threen::_apply_tween_value(InterpolateData &p_data, Variant &value) { 690 | // Get the object we want to apply the new value to 691 | Object *object = ObjectDB::get_instance(p_data.id); 692 | ERR_FAIL_COND_V(object == nullptr, false); 693 | 694 | // What kind of data are we mutating? 695 | switch (p_data.type) { 696 | case INTER_PROPERTY: 697 | case FOLLOW_PROPERTY: 698 | case TARGETING_PROPERTY: { 699 | // Simply set the property on the object 700 | object->set_indexed(nodepath_from_subnames(p_data.key), value); 701 | return true; 702 | } 703 | 704 | case INTER_METHOD: 705 | case FOLLOW_METHOD: 706 | case TARGETING_METHOD: { 707 | // We want to call the method on the target object 708 | 709 | // Do we have a non-nil value passed in? 710 | if (value.get_type() != Variant::NIL) { 711 | // Pass it as an argument to the function call 712 | object->call(p_data.key[0], value); 713 | } else { 714 | // Don't pass any argument 715 | object->call(p_data.key[0]); 716 | } 717 | 718 | // Did we get an error from the function call? 719 | //return error.error == GDEXTENSION_CALL_OK; 720 | return true; 721 | } 722 | 723 | case INTER_CALLBACK: 724 | // Nothing to apply for a callback 725 | break; 726 | }; 727 | // No issues found! 728 | return true; 729 | } 730 | 731 | void Threen::_tween_process(float p_delta) { 732 | // Process all of the pending commands 733 | _process_pending_commands(); 734 | 735 | // If the scale is 0, make no progress on the tweens 736 | if (speed_scale == 0) { 737 | return; 738 | } 739 | 740 | // Update the delta and whether we are pending an update 741 | p_delta *= speed_scale; 742 | pending_update++; 743 | 744 | // Are we repeating the interpolations? 745 | if (repeat) { 746 | // For each interpolation... 747 | bool repeats_finished = true; 748 | for (List::Element *E = interpolates.front(); E; E = E->next()) { 749 | // Get the data from it 750 | InterpolateData &data = E->get(); 751 | 752 | // Is not finished? 753 | if (!data.finish) { 754 | // We aren't finished yet, no need to check the rest 755 | repeats_finished = false; 756 | break; 757 | } 758 | } 759 | 760 | // If we are all finished, we can reset all of the tweens 761 | if (repeats_finished) { 762 | reset_all(); 763 | } 764 | } 765 | 766 | // Are all of the tweens complete? 767 | bool all_finished = true; 768 | 769 | // For each tween we wish to interpolate... 770 | for (List::Element *E = interpolates.front(); E; E = E->next()) { 771 | // Get the data from it 772 | InterpolateData &data = E->get(); 773 | 774 | // Track if we hit one that isn't finished yet 775 | all_finished = all_finished && data.finish; 776 | 777 | // Is the data not active or already finished? No need to go any further 778 | if (!data.active || data.finish) { 779 | continue; 780 | } 781 | 782 | // Get the target object for this interpolation 783 | Object *object = ObjectDB::get_instance(data.id); 784 | if (object == nullptr) { 785 | continue; 786 | } 787 | 788 | // Are we still delaying this tween? 789 | bool prev_delaying = data.elapsed <= data.delay; 790 | data.elapsed += p_delta; 791 | if (data.elapsed < data.delay) { 792 | continue; 793 | } else if (prev_delaying) { 794 | // We can apply the tween's value to the data and emit that the tween has started 795 | _apply_tween_value(data, data.initial_val); 796 | emit_signal("tween_started", object, nodepath_from_subnames(data.key)); 797 | } 798 | 799 | // Are we at the end of the tween? 800 | if (data.elapsed > (data.delay + data.duration)) { 801 | // Set the elapsed time to the end and mark this one as finished 802 | data.elapsed = data.delay + data.duration; 803 | data.finish = true; 804 | } 805 | 806 | // Are we interpolating a callback? 807 | if (data.type == INTER_CALLBACK) { 808 | // Is the tween completed? 809 | if (data.finish) { 810 | static_assert(VARIANT_ARG_MAX == 8, "This code needs to be updated if VARIANT_ARG_MAX != 8"); 811 | 812 | // Are we calling this callback deferred or immediately? 813 | if (data.call_deferred) { 814 | // Run the deferred function callback, applying the correct number of arguments 815 | switch (data.args) { 816 | case 0: 817 | object->call_deferred(data.key[0]); 818 | break; 819 | case 1: 820 | object->call_deferred(data.key[0], data.arg[0]); 821 | break; 822 | case 2: 823 | object->call_deferred(data.key[0], data.arg[0], data.arg[1]); 824 | break; 825 | case 3: 826 | object->call_deferred(data.key[0], data.arg[0], data.arg[1], data.arg[2]); 827 | break; 828 | case 4: 829 | object->call_deferred(data.key[0], data.arg[0], data.arg[1], data.arg[2], data.arg[3]); 830 | break; 831 | case 5: 832 | object->call_deferred(data.key[0], data.arg[0], data.arg[1], data.arg[2], data.arg[3], data.arg[4]); 833 | break; 834 | case 6: 835 | object->call_deferred(data.key[0], data.arg[0], data.arg[1], data.arg[2], data.arg[3], data.arg[4], data.arg[5]); 836 | break; 837 | case 7: 838 | object->call_deferred(data.key[0], data.arg[0], data.arg[1], data.arg[2], data.arg[3], data.arg[4], data.arg[5], data.arg[6]); 839 | break; 840 | case 8: 841 | object->call_deferred(data.key[0], data.arg[0], data.arg[1], data.arg[2], data.arg[3], data.arg[4], data.arg[5], data.arg[6], data.arg[7]); 842 | break; 843 | } 844 | } else { 845 | // Call the function directly with the arguments 846 | switch (data.args) { 847 | case 0: 848 | object->call(data.key[0]); 849 | break; 850 | case 1: 851 | object->call(data.key[0], data.arg[0]); 852 | break; 853 | case 2: 854 | object->call(data.key[0], data.arg[0], data.arg[1]); 855 | break; 856 | case 3: 857 | object->call(data.key[0], data.arg[0], data.arg[1], data.arg[2]); 858 | break; 859 | case 4: 860 | object->call(data.key[0], data.arg[0], data.arg[1], data.arg[2], data.arg[3]); 861 | break; 862 | case 5: 863 | object->call(data.key[0], data.arg[0], data.arg[1], data.arg[2], data.arg[3], data.arg[4]); 864 | break; 865 | case 6: 866 | object->call(data.key[0], data.arg[0], data.arg[1], data.arg[2], data.arg[3], data.arg[4], data.arg[5]); 867 | break; 868 | case 7: 869 | object->call(data.key[0], data.arg[0], data.arg[1], data.arg[2], data.arg[3], data.arg[4], data.arg[5], data.arg[6]); 870 | break; 871 | case 8: 872 | object->call(data.key[0], data.arg[0], data.arg[1], data.arg[2], data.arg[3], data.arg[4], data.arg[5], data.arg[6], data.arg[7]); 873 | break; 874 | } 875 | } 876 | } 877 | } else { 878 | // We can apply the value directly 879 | Variant result = _run_equation(data); 880 | _apply_tween_value(data, result); 881 | 882 | // Emit that the tween has taken a step 883 | emit_signal("tween_step", object, nodepath_from_subnames(data.key), data.elapsed, result); 884 | } 885 | 886 | // Is the tween now finished? 887 | if (data.finish) { 888 | // Set it to the final value directly 889 | Variant final_val = _get_final_val(data); 890 | _apply_tween_value(data, final_val); 891 | 892 | // Emit the signal 893 | emit_signal("tween_completed", object, nodepath_from_subnames(data.key)); 894 | 895 | // If we are not repeating the tween, remove it 896 | if (!repeat) { 897 | call_deferred("_remove_by_uid", data.uid); 898 | } 899 | } else if (!repeat) { 900 | // Check whether all tweens are finished 901 | all_finished = all_finished && data.finish; 902 | } 903 | } 904 | // One less update left to go 905 | pending_update--; 906 | 907 | // If all tweens are completed, we no longer need to be active 908 | if (all_finished) { 909 | set_active(false); 910 | emit_signal("tween_all_completed"); 911 | } 912 | } 913 | 914 | void Threen::set_tween_process_mode(TweenProcessMode p_mode) { 915 | tween_process_mode = p_mode; 916 | } 917 | 918 | Threen::TweenProcessMode Threen::get_tween_process_mode() const { 919 | return tween_process_mode; 920 | } 921 | 922 | bool Threen::is_active() const { 923 | return is_processing_internal() || is_physics_processing_internal(); 924 | } 925 | 926 | void Threen::set_active(bool p_active) { 927 | // Do nothing if it's the same active mode that we currently are 928 | if (is_active() == p_active) { 929 | return; 930 | } 931 | 932 | // Depending on physics or idle, set processing 933 | switch (tween_process_mode) { 934 | case TWEEN_PROCESS_IDLE: 935 | set_process_internal(p_active); 936 | break; 937 | case TWEEN_PROCESS_PHYSICS: 938 | set_physics_process_internal(p_active); 939 | break; 940 | } 941 | } 942 | 943 | bool Threen::is_repeat() const { 944 | return repeat; 945 | } 946 | 947 | void Threen::set_repeat(bool p_repeat) { 948 | repeat = p_repeat; 949 | } 950 | 951 | void Threen::set_speed_scale(float p_speed) { 952 | speed_scale = p_speed; 953 | } 954 | 955 | float Threen::get_speed_scale() const { 956 | return speed_scale; 957 | } 958 | 959 | bool Threen::start() { 960 | ERR_FAIL_COND_V_MSG(!is_inside_tree(), false, "Threen was not added to the SceneTree!"); 961 | 962 | // Are there any pending updates? 963 | if (pending_update != 0) { 964 | // Start the tweens after deferring 965 | call_deferred("start"); 966 | return true; 967 | } 968 | 969 | pending_update++; 970 | for (List::Element *E = interpolates.front(); E; E = E->next()) { 971 | InterpolateData &data = E->get(); 972 | data.active = true; 973 | } 974 | pending_update--; 975 | 976 | // We want to be activated 977 | set_active(true); 978 | 979 | // Don't resume from current position if stop_all() function has been used 980 | if (was_stopped) { 981 | seek(0); 982 | } 983 | was_stopped = false; 984 | 985 | return true; 986 | } 987 | 988 | bool Threen::reset(Object *p_object, StringName p_key) { 989 | // Find all interpolations that use the same object and target string 990 | pending_update++; 991 | for (List::Element *E = interpolates.front(); E; E = E->next()) { 992 | // Get the target object 993 | InterpolateData &data = E->get(); 994 | Object *object = ObjectDB::get_instance(data.id); 995 | if (object == nullptr) { 996 | continue; 997 | } 998 | 999 | // Do we have the correct object and key? 1000 | if (object == p_object && (data.concatenated_key == p_key || p_key == StringName())) { 1001 | // Reset the tween to the initial state 1002 | data.elapsed = 0; 1003 | data.finish = false; 1004 | 1005 | // Also apply the initial state if there isn't a delay 1006 | if (data.delay == 0) { 1007 | _apply_tween_value(data, data.initial_val); 1008 | } 1009 | } 1010 | } 1011 | pending_update--; 1012 | return true; 1013 | } 1014 | 1015 | bool Threen::reset_all() { 1016 | // Go through all interpolations 1017 | pending_update++; 1018 | for (List::Element *E = interpolates.front(); E; E = E->next()) { 1019 | // Get the target data and set it back to the initial state 1020 | InterpolateData &data = E->get(); 1021 | data.elapsed = 0; 1022 | data.finish = false; 1023 | 1024 | // If there isn't a delay, apply the value to the object 1025 | if (data.delay == 0) { 1026 | _apply_tween_value(data, data.initial_val); 1027 | } 1028 | } 1029 | pending_update--; 1030 | return true; 1031 | } 1032 | 1033 | bool Threen::stop(Object *p_object, StringName p_key) { 1034 | // Find the tween that has the given target object and string key 1035 | pending_update++; 1036 | for (List::Element *E = interpolates.front(); E; E = E->next()) { 1037 | // Get the object the tween is targeting 1038 | InterpolateData &data = E->get(); 1039 | Object *object = ObjectDB::get_instance(data.id); 1040 | if (object == nullptr) { 1041 | continue; 1042 | } 1043 | 1044 | // Is this the correct object and does it have the given key? 1045 | if (object == p_object && (data.concatenated_key == p_key || p_key == StringName())) { 1046 | // Disable the tween 1047 | data.active = false; 1048 | } 1049 | } 1050 | pending_update--; 1051 | return true; 1052 | } 1053 | 1054 | bool Threen::stop_all() { 1055 | // We no longer need to be active since all tweens have been stopped 1056 | set_active(false); 1057 | was_stopped = true; 1058 | 1059 | // For each interpolation... 1060 | pending_update++; 1061 | for (List::Element *E = interpolates.front(); E; E = E->next()) { 1062 | // Simply set it inactive 1063 | InterpolateData &data = E->get(); 1064 | data.active = false; 1065 | } 1066 | pending_update--; 1067 | return true; 1068 | } 1069 | 1070 | bool Threen::resume(Object *p_object, StringName p_key) { 1071 | // We need to be activated 1072 | // TODO: What if no tween is found?? 1073 | set_active(true); 1074 | 1075 | // Find the tween that uses the given target object and string key 1076 | pending_update++; 1077 | for (List::Element *E = interpolates.front(); E; E = E->next()) { 1078 | // Grab the object 1079 | InterpolateData &data = E->get(); 1080 | Object *object = ObjectDB::get_instance(data.id); 1081 | if (object == nullptr) { 1082 | continue; 1083 | } 1084 | 1085 | // If the object and string key match, activate it 1086 | if (object == p_object && (data.concatenated_key == p_key || p_key == StringName())) { 1087 | data.active = true; 1088 | } 1089 | } 1090 | pending_update--; 1091 | return true; 1092 | } 1093 | 1094 | bool Threen::resume_all() { 1095 | // Set ourselves active so we can process tweens 1096 | // TODO: What if there are no tweens? We get set to active for no reason! 1097 | set_active(true); 1098 | 1099 | // For each interpolation... 1100 | pending_update++; 1101 | for (List::Element *E = interpolates.front(); E; E = E->next()) { 1102 | // Simply grab it and set it to active 1103 | InterpolateData &data = E->get(); 1104 | data.active = true; 1105 | } 1106 | pending_update--; 1107 | return true; 1108 | } 1109 | 1110 | bool Threen::remove(Object *p_object, StringName p_key) { 1111 | // If we are still updating, call this function again later 1112 | if (pending_update != 0) { 1113 | call_deferred("remove", p_object, p_key); 1114 | return true; 1115 | } 1116 | 1117 | // For each interpolation... 1118 | List::Element *> for_removal; 1119 | for (List::Element *E = interpolates.front(); E; E = E->next()) { 1120 | // Get the target object 1121 | InterpolateData &data = E->get(); 1122 | Object *object = ObjectDB::get_instance(data.id); 1123 | if (object == nullptr) { 1124 | continue; 1125 | } 1126 | 1127 | // If the target object and string key match, queue it for removal 1128 | if (object == p_object && (data.concatenated_key == p_key || p_key == StringName())) { 1129 | for_removal.push_back(E); 1130 | } 1131 | } 1132 | 1133 | // For each interpolation we wish to remove... 1134 | for (List::Element *>::Element *E = for_removal.front(); E; E = E->next()) { 1135 | // Erase it 1136 | interpolates.erase(E->get()); 1137 | } 1138 | return true; 1139 | } 1140 | 1141 | void Threen::_remove_by_uid(int uid) { 1142 | // If we are still updating, call this function again later 1143 | if (pending_update != 0) { 1144 | call_deferred("_remove_by_uid", uid); 1145 | return; 1146 | } 1147 | 1148 | // Find the interpolation that matches the given UID 1149 | for (List::Element *E = interpolates.front(); E; E = E->next()) { 1150 | if (uid == E->get().uid) { 1151 | // It matches, erase it and stop looking 1152 | E->erase(); 1153 | break; 1154 | } 1155 | } 1156 | } 1157 | 1158 | void Threen::_push_interpolate_data(InterpolateData &p_data) { 1159 | pending_update++; 1160 | 1161 | // Add the new interpolation 1162 | p_data.uid = ++uid; 1163 | interpolates.push_back(p_data); 1164 | 1165 | pending_update--; 1166 | } 1167 | 1168 | bool Threen::remove_all() { 1169 | // If we are still updating, call this function again later 1170 | if (pending_update != 0) { 1171 | call_deferred("remove_all"); 1172 | return true; 1173 | } 1174 | // We no longer need to be active 1175 | set_active(false); 1176 | 1177 | // Clear out all interpolations and reset the uid 1178 | interpolates.clear(); 1179 | uid = 0; 1180 | 1181 | return true; 1182 | } 1183 | 1184 | bool Threen::seek(real_t p_time) { 1185 | // Go through each interpolation... 1186 | pending_update++; 1187 | for (List::Element *E = interpolates.front(); E; E = E->next()) { 1188 | // Get the target data 1189 | InterpolateData &data = E->get(); 1190 | 1191 | // Update the elapsed data to be set to the target time 1192 | data.elapsed = p_time; 1193 | 1194 | // Are we at the end? 1195 | if (data.elapsed < data.delay) { 1196 | // There is still time left to go 1197 | data.finish = false; 1198 | continue; 1199 | } else if (data.elapsed >= (data.delay + data.duration)) { 1200 | // We are past the end of it, set the elapsed time to the end and mark as finished 1201 | data.elapsed = (data.delay + data.duration); 1202 | data.finish = true; 1203 | } else { 1204 | // We are not finished with this interpolation yet 1205 | data.finish = false; 1206 | } 1207 | 1208 | // If we are a callback, do nothing special 1209 | if (data.type == INTER_CALLBACK) { 1210 | continue; 1211 | } 1212 | 1213 | // Run the equation on the data and apply the value 1214 | Variant result = _run_equation(data); 1215 | _apply_tween_value(data, result); 1216 | } 1217 | pending_update--; 1218 | return true; 1219 | } 1220 | 1221 | real_t Threen::tell() const { 1222 | // We want to grab the position of the furthest along tween 1223 | pending_update++; 1224 | real_t pos = 0; 1225 | 1226 | // For each interpolation... 1227 | for (const List::Element *E = interpolates.front(); E; E = E->next()) { 1228 | // Get the data and figure out if it's position is further along than the previous ones 1229 | const InterpolateData &data = E->get(); 1230 | if (data.elapsed > pos) { 1231 | // Save it if so 1232 | pos = data.elapsed; 1233 | } 1234 | } 1235 | pending_update--; 1236 | return pos; 1237 | } 1238 | 1239 | real_t Threen::get_runtime() const { 1240 | // If the tween isn't moving, it'll last forever 1241 | if (speed_scale == 0) { 1242 | return INFINITY; 1243 | } 1244 | 1245 | pending_update++; 1246 | 1247 | // For each interpolation... 1248 | real_t runtime = 0; 1249 | for (const List::Element *E = interpolates.front(); E; E = E->next()) { 1250 | // Get the tween data and see if it's runtime is greater than the previous tweens 1251 | const InterpolateData &data = E->get(); 1252 | real_t t = data.delay + data.duration; 1253 | if (t > runtime) { 1254 | // This is the longest running tween 1255 | runtime = t; 1256 | } 1257 | } 1258 | pending_update--; 1259 | 1260 | // Adjust the runtime for the current speed scale 1261 | return runtime / speed_scale; 1262 | } 1263 | 1264 | bool Threen::_calc_delta_val(const Variant &p_initial_val, const Variant &p_final_val, Variant &p_delta_val) { 1265 | // Get the initial, final, and delta values 1266 | const Variant &initial_val = p_initial_val; 1267 | const Variant &final_val = p_final_val; 1268 | Variant &delta_val = p_delta_val; 1269 | 1270 | // What kind of data are we interpolating? 1271 | switch (initial_val.get_type()) { 1272 | case Variant::BOOL: 1273 | // We'll treat booleans just like integers 1274 | case Variant::INT: 1275 | // Compute the integer delta 1276 | delta_val = (int)final_val - (int)initial_val; 1277 | break; 1278 | 1279 | case Variant::FLOAT: 1280 | // Convert to REAL and find the delta 1281 | delta_val = (real_t)final_val - (real_t)initial_val; 1282 | break; 1283 | 1284 | case Variant::VECTOR2: 1285 | // Convert to Vectors and find the delta 1286 | delta_val = final_val.operator Vector2() - initial_val.operator Vector2(); 1287 | break; 1288 | 1289 | case Variant::RECT2: { 1290 | // Build a new Rect2 and use the new position and sizes to make a delta 1291 | Rect2 i = initial_val; 1292 | Rect2 f = final_val; 1293 | delta_val = Rect2(f.position - i.position, f.size - i.size); 1294 | } break; 1295 | 1296 | case Variant::VECTOR3: 1297 | // Convert to Vectors and find the delta 1298 | delta_val = final_val.operator Vector3() - initial_val.operator Vector3(); 1299 | break; 1300 | 1301 | case Variant::TRANSFORM2D: { 1302 | // Build a new transform which is the difference between the initial and final values 1303 | Transform2D i = initial_val; 1304 | Transform2D f = final_val; 1305 | Transform2D d = Transform2D(); 1306 | d[0][0] = f.columns[0][0] - i.columns[0][0]; 1307 | d[0][1] = f.columns[0][1] - i.columns[0][1]; 1308 | d[1][0] = f.columns[1][0] - i.columns[1][0]; 1309 | d[1][1] = f.columns[1][1] - i.columns[1][1]; 1310 | d[2][0] = f.columns[2][0] - i.columns[2][0]; 1311 | d[2][1] = f.columns[2][1] - i.columns[2][1]; 1312 | delta_val = d; 1313 | } break; 1314 | 1315 | case Variant::QUATERNION: 1316 | // Convert to quaternianls and find the delta 1317 | delta_val = final_val.operator Quaternion() - initial_val.operator Quaternion(); 1318 | break; 1319 | 1320 | case Variant::AABB: { 1321 | // Build a new AABB and use the new position and sizes to make a delta 1322 | AABB i = initial_val; 1323 | AABB f = final_val; 1324 | delta_val = AABB(f.position - i.position, f.size - i.size); 1325 | } break; 1326 | 1327 | case Variant::BASIS: { 1328 | // Build a new basis which is the delta between the initial and final values 1329 | Basis i = initial_val; 1330 | Basis f = final_val; 1331 | delta_val = Basis(f.rows[0][0] - i.rows[0][0], 1332 | f.rows[0][1] - i.rows[0][1], 1333 | f.rows[0][2] - i.rows[0][2], 1334 | f.rows[1][0] - i.rows[1][0], 1335 | f.rows[1][1] - i.rows[1][1], 1336 | f.rows[1][2] - i.rows[1][2], 1337 | f.rows[2][0] - i.rows[2][0], 1338 | f.rows[2][1] - i.rows[2][1], 1339 | f.rows[2][2] - i.rows[2][2]); 1340 | } break; 1341 | 1342 | case Variant::TRANSFORM3D: { 1343 | // Build a new transform which is the difference between the initial and final values 1344 | Transform3D i = initial_val; 1345 | Transform3D f = final_val; 1346 | Transform3D d; 1347 | d.set(f.basis.rows[0][0] - i.basis.rows[0][0], 1348 | f.basis.rows[0][1] - i.basis.rows[0][1], 1349 | f.basis.rows[0][2] - i.basis.rows[0][2], 1350 | f.basis.rows[1][0] - i.basis.rows[1][0], 1351 | f.basis.rows[1][1] - i.basis.rows[1][1], 1352 | f.basis.rows[1][2] - i.basis.rows[1][2], 1353 | f.basis.rows[2][0] - i.basis.rows[2][0], 1354 | f.basis.rows[2][1] - i.basis.rows[2][1], 1355 | f.basis.rows[2][2] - i.basis.rows[2][2], 1356 | f.origin.x - i.origin.x, 1357 | f.origin.y - i.origin.y, 1358 | f.origin.z - i.origin.z); 1359 | 1360 | delta_val = d; 1361 | } break; 1362 | 1363 | case Variant::COLOR: { 1364 | // Make a new color which is the difference between each the color's RGBA attributes 1365 | Color i = initial_val; 1366 | Color f = final_val; 1367 | delta_val = Color(f.r - i.r, f.g - i.g, f.b - i.b, f.a - i.a); 1368 | } break; 1369 | 1370 | default: { 1371 | static Variant::Type supported_types[] = { 1372 | Variant::BOOL, 1373 | Variant::INT, 1374 | Variant::FLOAT, 1375 | Variant::VECTOR2, 1376 | Variant::RECT2, 1377 | Variant::VECTOR3, 1378 | Variant::TRANSFORM2D, 1379 | Variant::QUATERNION, 1380 | Variant::AABB, 1381 | Variant::BASIS, 1382 | Variant::TRANSFORM3D, 1383 | Variant::COLOR, 1384 | }; 1385 | 1386 | int length = *(&supported_types + 1) - supported_types; 1387 | String error_msg = "Invalid parameter type. Supported types are: "; 1388 | for (int i = 0; i < length; i++) { 1389 | if (i != 0) { 1390 | error_msg += ", "; 1391 | } 1392 | error_msg += Variant::get_type_name(supported_types[i]); 1393 | } 1394 | error_msg += "."; 1395 | ERR_PRINT(error_msg); 1396 | return false; 1397 | } 1398 | }; 1399 | return true; 1400 | } 1401 | 1402 | bool Threen::_build_interpolation(InterpolateType p_interpolation_type, Object *p_object, NodePath *p_property, StringName *p_method, Variant p_initial_val, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) { 1403 | // TODO: Add initialization+implementation for remaining interpolation types 1404 | // TODO: Fix this method's organization to take advantage of the type 1405 | 1406 | // Make a new interpolation data 1407 | InterpolateData data; 1408 | data.active = true; 1409 | data.type = p_interpolation_type; 1410 | data.finish = false; 1411 | data.elapsed = 0; 1412 | 1413 | // Validate and apply interpolation data 1414 | 1415 | // Give it the object 1416 | ERR_FAIL_COND_V_MSG(p_object == nullptr, false, "Invalid object provided to Threen."); 1417 | data.id = p_object->get_instance_id(); 1418 | 1419 | // Validate the initial and final values 1420 | ERR_FAIL_COND_V_MSG(p_initial_val.get_type() != p_final_val.get_type(), false, "Initial value type '" + Variant::get_type_name(p_initial_val.get_type()) + "' does not match final value type '" + Variant::get_type_name(p_final_val.get_type()) + "'."); 1421 | data.initial_val = p_initial_val; 1422 | data.final_val = p_final_val; 1423 | 1424 | // Check the Duration 1425 | ERR_FAIL_COND_V_MSG(p_duration < 0, false, "Only non-negative duration values allowed in Threens."); 1426 | data.duration = p_duration; 1427 | 1428 | // Threen Delay 1429 | ERR_FAIL_COND_V_MSG(p_delay < 0, false, "Only non-negative delay values allowed in Threens."); 1430 | data.delay = p_delay; 1431 | 1432 | // Transition type 1433 | ERR_FAIL_COND_V_MSG(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false, "Invalid transition type provided to Threen."); 1434 | data.trans_type = p_trans_type; 1435 | 1436 | // Easing type 1437 | ERR_FAIL_COND_V_MSG(p_ease_type < 0 || p_ease_type >= EASE_COUNT, false, "Invalid easing type provided to Threen."); 1438 | data.ease_type = p_ease_type; 1439 | 1440 | // Is the property defined? 1441 | if (p_property) { 1442 | /* FIXME: Can't validate as GDExtension's `get_indexed` and `get` don't expose `r_valid`. 1443 | // Check that the object actually contains the given property 1444 | bool prop_valid = false; 1445 | p_object->get_indexed(p_property->get_subnames(), &prop_valid); 1446 | ERR_FAIL_COND_V_MSG(!prop_valid, false, "Threen target object has no property named: " + p_property->get_concatenated_subnames() + "."); 1447 | */ 1448 | 1449 | data.key = nodepath_get_subnames(*p_property); 1450 | data.concatenated_key = p_property->get_concatenated_subnames(); 1451 | } 1452 | 1453 | // Is the method defined? 1454 | if (p_method) { 1455 | // Does the object even have the requested method? 1456 | ERR_FAIL_COND_V_MSG(!p_object->has_method(*p_method), false, "Threen target object has no method named: " + *p_method + "."); 1457 | 1458 | data.key.push_back(*p_method); 1459 | data.concatenated_key = *p_method; 1460 | } 1461 | 1462 | // Is there not a valid delta? 1463 | if (!_calc_delta_val(data.initial_val, data.final_val, data.delta_val)) { 1464 | return false; 1465 | } 1466 | 1467 | // Add this interpolation to the total 1468 | _push_interpolate_data(data); 1469 | return true; 1470 | } 1471 | 1472 | bool Threen::interpolate_property(Object *p_object, NodePath p_property, Variant p_initial_val, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) { 1473 | // If we are busy updating, call this function again later 1474 | if (pending_update != 0) { 1475 | _add_pending_command("interpolate_property", p_object, p_property, p_initial_val, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay); 1476 | return true; 1477 | } 1478 | 1479 | // Check that the target object is valid 1480 | ERR_FAIL_COND_V_MSG(p_object == nullptr, false, vformat("The Threen \"%s\"'s target node is `null`. Is the node reference correct?", get_name())); 1481 | 1482 | // Get the property from the node path 1483 | p_property = p_property.get_as_property_path(); 1484 | 1485 | // If no initial value given, grab the initial value from the object 1486 | // TODO: Is this documented? This is very useful and removes a lot of clutter from tweens! 1487 | if (p_initial_val.get_type() == Variant::NIL) { 1488 | p_initial_val = p_object->get_indexed(nodepath_subnames_only(p_property)); 1489 | } 1490 | 1491 | // Convert any integers into REALs as they are better for interpolation 1492 | if (p_initial_val.get_type() == Variant::INT) { 1493 | p_initial_val = p_initial_val.operator real_t(); 1494 | } 1495 | if (p_final_val.get_type() == Variant::INT) { 1496 | p_final_val = p_final_val.operator real_t(); 1497 | } 1498 | 1499 | // Build the interpolation data 1500 | bool result = _build_interpolation(INTER_PROPERTY, p_object, &p_property, nullptr, p_initial_val, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay); 1501 | return result; 1502 | } 1503 | 1504 | bool Threen::interpolate_method(Object *p_object, StringName p_method, Variant p_initial_val, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) { 1505 | // If we are busy updating, call this function again later 1506 | if (pending_update != 0) { 1507 | _add_pending_command("interpolate_method", p_object, p_method, p_initial_val, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay); 1508 | return true; 1509 | } 1510 | 1511 | // Check that the target object is valid 1512 | ERR_FAIL_COND_V_MSG(p_object == nullptr, false, vformat("The Threen \"%s\"'s target node is `null`. Is the node reference correct?", get_name())); 1513 | 1514 | // Convert any integers into REALs as they are better for interpolation 1515 | if (p_initial_val.get_type() == Variant::INT) { 1516 | p_initial_val = p_initial_val.operator real_t(); 1517 | } 1518 | if (p_final_val.get_type() == Variant::INT) { 1519 | p_final_val = p_final_val.operator real_t(); 1520 | } 1521 | 1522 | // Build the interpolation data 1523 | bool result = _build_interpolation(INTER_METHOD, p_object, nullptr, &p_method, p_initial_val, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay); 1524 | return result; 1525 | } 1526 | 1527 | bool Threen::interpolate_callback(Object *p_object, real_t p_duration, String p_callback, VARIANT_ARG_DECLARE) { 1528 | // If we are already updating, call this function again later 1529 | if (pending_update != 0) { 1530 | _add_pending_command("interpolate_callback", p_object, p_duration, p_callback, p_arg1, p_arg2, p_arg3, p_arg4, p_arg5); 1531 | return true; 1532 | } 1533 | 1534 | // Check that the target object is valid 1535 | ERR_FAIL_COND_V(p_object == nullptr, false); 1536 | 1537 | // Duration cannot be negative 1538 | ERR_FAIL_COND_V(p_duration < 0, false); 1539 | 1540 | // Check whether the object even has the callback 1541 | ERR_FAIL_COND_V_MSG(!p_object->has_method(p_callback), false, "Object has no callback named: " + p_callback + "."); 1542 | 1543 | // Build a new InterpolationData 1544 | InterpolateData data; 1545 | data.active = true; 1546 | data.type = INTER_CALLBACK; 1547 | data.finish = false; 1548 | data.call_deferred = false; 1549 | data.elapsed = 0; 1550 | 1551 | // Give the data it's configuration 1552 | data.id = p_object->get_instance_id(); 1553 | data.key.push_back(p_callback); 1554 | data.concatenated_key = p_callback; 1555 | data.duration = p_duration; 1556 | data.delay = 0; 1557 | 1558 | // Add arguments to the interpolation 1559 | int args = 0; 1560 | if (p_arg5.get_type() != Variant::NIL) { 1561 | args = 5; 1562 | } else if (p_arg4.get_type() != Variant::NIL) { 1563 | args = 4; 1564 | } else if (p_arg3.get_type() != Variant::NIL) { 1565 | args = 3; 1566 | } else if (p_arg2.get_type() != Variant::NIL) { 1567 | args = 2; 1568 | } else if (p_arg1.get_type() != Variant::NIL) { 1569 | args = 1; 1570 | } else { 1571 | args = 0; 1572 | } 1573 | 1574 | data.args = args; 1575 | data.arg[0] = p_arg1; 1576 | data.arg[1] = p_arg2; 1577 | data.arg[2] = p_arg3; 1578 | data.arg[3] = p_arg4; 1579 | data.arg[4] = p_arg5; 1580 | 1581 | // Add the new interpolation 1582 | _push_interpolate_data(data); 1583 | return true; 1584 | } 1585 | 1586 | bool Threen::interpolate_deferred_callback(Object *p_object, real_t p_duration, String p_callback, VARIANT_ARG_DECLARE) { 1587 | // If we are already updating, call this function again later 1588 | if (pending_update != 0) { 1589 | _add_pending_command("interpolate_deferred_callback", p_object, p_duration, p_callback, p_arg1, p_arg2, p_arg3, p_arg4, p_arg5); 1590 | return true; 1591 | } 1592 | 1593 | // Check that the target object is valid 1594 | ERR_FAIL_COND_V(p_object == nullptr, false); 1595 | 1596 | // No negative durations allowed 1597 | ERR_FAIL_COND_V(p_duration < 0, false); 1598 | 1599 | // Confirm the callback exists on the object 1600 | ERR_FAIL_COND_V_MSG(!p_object->has_method(p_callback), false, "Object has no callback named: " + p_callback + "."); 1601 | 1602 | // Create a new InterpolateData for the callback 1603 | InterpolateData data; 1604 | data.active = true; 1605 | data.type = INTER_CALLBACK; 1606 | data.finish = false; 1607 | data.call_deferred = true; 1608 | data.elapsed = 0; 1609 | 1610 | // Give the data it's configuration 1611 | data.id = p_object->get_instance_id(); 1612 | data.key.push_back(p_callback); 1613 | data.concatenated_key = p_callback; 1614 | data.duration = p_duration; 1615 | data.delay = 0; 1616 | 1617 | // Collect arguments for the callback 1618 | static_assert(VARIANT_ARG_MAX == 8, "This code needs to be updated if VARIANT_ARG_MAX != 8"); 1619 | int args = 0; 1620 | if (p_arg8.get_type() != Variant::NIL) { 1621 | args = 8; 1622 | } else if (p_arg7.get_type() != Variant::NIL) { 1623 | args = 7; 1624 | } else if (p_arg6.get_type() != Variant::NIL) { 1625 | args = 6; 1626 | } else if (p_arg5.get_type() != Variant::NIL) { 1627 | args = 5; 1628 | } else if (p_arg4.get_type() != Variant::NIL) { 1629 | args = 4; 1630 | } else if (p_arg3.get_type() != Variant::NIL) { 1631 | args = 3; 1632 | } else if (p_arg2.get_type() != Variant::NIL) { 1633 | args = 2; 1634 | } else if (p_arg1.get_type() != Variant::NIL) { 1635 | args = 1; 1636 | } else { 1637 | args = 0; 1638 | } 1639 | 1640 | data.args = args; 1641 | data.arg[0] = p_arg1; 1642 | data.arg[1] = p_arg2; 1643 | data.arg[2] = p_arg3; 1644 | data.arg[3] = p_arg4; 1645 | data.arg[4] = p_arg5; 1646 | data.arg[5] = p_arg6; 1647 | data.arg[6] = p_arg7; 1648 | data.arg[7] = p_arg8; 1649 | 1650 | // Add the new interpolation 1651 | _push_interpolate_data(data); 1652 | return true; 1653 | } 1654 | 1655 | bool Threen::follow_property(Object *p_object, NodePath p_property, Variant p_initial_val, Object *p_target, NodePath p_target_property, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) { 1656 | // If we are already updating, call this function again later 1657 | if (pending_update != 0) { 1658 | _add_pending_command("follow_property", p_object, p_property, p_initial_val, p_target, p_target_property, p_duration, p_trans_type, p_ease_type, p_delay); 1659 | return true; 1660 | } 1661 | 1662 | // Confirm the source and target objects are valid 1663 | ERR_FAIL_NULL_V(p_object, false); 1664 | ERR_FAIL_NULL_V(p_target, false); 1665 | 1666 | // Get the two properties from their paths 1667 | p_property = p_property.get_as_property_path(); 1668 | p_target_property = p_target_property.get_as_property_path(); 1669 | 1670 | // If no initial value is given, grab it from the source object 1671 | // TODO: Is this documented? It's really helpful for decluttering tweens 1672 | if (p_initial_val.get_type() == Variant::NIL) { 1673 | p_initial_val = p_object->get_indexed(nodepath_subnames_only(p_property)); 1674 | } 1675 | 1676 | // Convert initial INT values to REAL as they are better for interpolation 1677 | if (p_initial_val.get_type() == Variant::INT) { 1678 | p_initial_val = p_initial_val.operator real_t(); 1679 | } 1680 | 1681 | // No negative durations 1682 | ERR_FAIL_COND_V(p_duration < 0, false); 1683 | 1684 | // Ensure transition and easing types are valid 1685 | ERR_FAIL_COND_V(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false); 1686 | ERR_FAIL_COND_V(p_ease_type < 0 || p_ease_type >= EASE_COUNT, false); 1687 | 1688 | // No negative delays 1689 | ERR_FAIL_COND_V(p_delay < 0, false); 1690 | 1691 | /* FIXME: Can't validate as GDExtension's `get_indexed` and `get` don't expose `r_valid`. 1692 | // Confirm the source and target objects have the desired properties 1693 | bool prop_valid = false; 1694 | p_object->get_indexed(p_property.get_subnames(), &prop_valid); 1695 | ERR_FAIL_COND_V(!prop_valid, false); 1696 | 1697 | bool target_prop_valid = false; 1698 | Variant target_val = p_target->get_indexed(p_target_property.get_subnames(), &target_prop_valid); 1699 | ERR_FAIL_COND_V(!target_prop_valid, false); 1700 | */ 1701 | Variant target_val = p_target->get_indexed(nodepath_subnames_only(p_target_property)); 1702 | 1703 | // Convert target INT to REAL since it is better for interpolation 1704 | if (target_val.get_type() == Variant::INT) { 1705 | target_val = target_val.operator real_t(); 1706 | } 1707 | 1708 | // Verify that the target value and initial value are the same type 1709 | ERR_FAIL_COND_V(target_val.get_type() != p_initial_val.get_type(), false); 1710 | 1711 | // Create a new InterpolateData 1712 | InterpolateData data; 1713 | data.active = true; 1714 | data.type = FOLLOW_PROPERTY; 1715 | data.finish = false; 1716 | data.elapsed = 0; 1717 | 1718 | // Give the InterpolateData it's configuration 1719 | data.id = p_object->get_instance_id(); 1720 | data.key = nodepath_get_subnames(p_property); 1721 | data.concatenated_key = p_property.get_concatenated_subnames(); 1722 | data.initial_val = p_initial_val; 1723 | data.target_id = p_target->get_instance_id(); 1724 | data.target_key = nodepath_get_subnames(p_target_property); 1725 | data.duration = p_duration; 1726 | data.trans_type = p_trans_type; 1727 | data.ease_type = p_ease_type; 1728 | data.delay = p_delay; 1729 | 1730 | // Add the interpolation 1731 | _push_interpolate_data(data); 1732 | return true; 1733 | } 1734 | 1735 | bool Threen::follow_method(Object *p_object, StringName p_method, Variant p_initial_val, Object *p_target, StringName p_target_method, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) { 1736 | // If we are currently updating, call this function again later 1737 | if (pending_update != 0) { 1738 | _add_pending_command("follow_method", p_object, p_method, p_initial_val, p_target, p_target_method, p_duration, p_trans_type, p_ease_type, p_delay); 1739 | return true; 1740 | } 1741 | // Convert initial INT values to REAL as they are better for interpolation 1742 | if (p_initial_val.get_type() == Variant::INT) { 1743 | p_initial_val = p_initial_val.operator real_t(); 1744 | } 1745 | 1746 | // Verify the source and target objects are valid 1747 | ERR_FAIL_COND_V(p_object == nullptr, false); 1748 | ERR_FAIL_COND_V(p_target == nullptr, false); 1749 | 1750 | // No negative durations 1751 | ERR_FAIL_COND_V(p_duration < 0, false); 1752 | 1753 | // Ensure that the transition and ease types are valid 1754 | ERR_FAIL_COND_V(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false); 1755 | ERR_FAIL_COND_V(p_ease_type < 0 || p_ease_type >= EASE_COUNT, false); 1756 | 1757 | // No negative delays 1758 | ERR_FAIL_COND_V(p_delay < 0, false); 1759 | 1760 | // Confirm both objects have the target methods 1761 | ERR_FAIL_COND_V_MSG(!p_object->has_method(p_method), false, "Object has no method named: " + p_method + "."); 1762 | ERR_FAIL_COND_V_MSG(!p_target->has_method(p_target_method), false, "Target has no method named: " + p_target_method + "."); 1763 | 1764 | // Call the method to get the target value 1765 | Variant target_val = p_target->call(p_target_method); 1766 | //ERR_FAIL_COND_V(error.error != GDEXTENSION_CALL_OK, false); 1767 | 1768 | // Convert target INT values to REAL as they are better for interpolation 1769 | if (target_val.get_type() == Variant::INT) { 1770 | target_val = target_val.operator real_t(); 1771 | } 1772 | ERR_FAIL_COND_V(target_val.get_type() != p_initial_val.get_type(), false); 1773 | 1774 | // Make the new InterpolateData for the method follow 1775 | InterpolateData data; 1776 | data.active = true; 1777 | data.type = FOLLOW_METHOD; 1778 | data.finish = false; 1779 | data.elapsed = 0; 1780 | 1781 | // Give the data it's configuration 1782 | data.id = p_object->get_instance_id(); 1783 | data.key.push_back(p_method); 1784 | data.concatenated_key = p_method; 1785 | data.initial_val = p_initial_val; 1786 | data.target_id = p_target->get_instance_id(); 1787 | data.target_key.push_back(p_target_method); 1788 | data.duration = p_duration; 1789 | data.trans_type = p_trans_type; 1790 | data.ease_type = p_ease_type; 1791 | data.delay = p_delay; 1792 | 1793 | // Add the new interpolation 1794 | _push_interpolate_data(data); 1795 | return true; 1796 | } 1797 | 1798 | bool Threen::targeting_property(Object *p_object, NodePath p_property, Object *p_initial, NodePath p_initial_property, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) { 1799 | // If we are currently updating, call this function again later 1800 | if (pending_update != 0) { 1801 | _add_pending_command("targeting_property", p_object, p_property, p_initial, p_initial_property, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay); 1802 | return true; 1803 | } 1804 | // Grab the target property and the target property 1805 | p_property = p_property.get_as_property_path(); 1806 | p_initial_property = p_initial_property.get_as_property_path(); 1807 | 1808 | // Convert the initial INT values to REAL as they are better for Interpolation 1809 | if (p_final_val.get_type() == Variant::INT) { 1810 | p_final_val = p_final_val.operator real_t(); 1811 | } 1812 | 1813 | // Verify both objects are valid 1814 | ERR_FAIL_COND_V(p_object == nullptr, false); 1815 | ERR_FAIL_COND_V(p_initial == nullptr, false); 1816 | 1817 | // No negative durations 1818 | ERR_FAIL_COND_V(p_duration < 0, false); 1819 | 1820 | // Ensure transition and easing types are valid 1821 | ERR_FAIL_COND_V(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false); 1822 | ERR_FAIL_COND_V(p_ease_type < 0 || p_ease_type >= EASE_COUNT, false); 1823 | 1824 | // No negative delays 1825 | ERR_FAIL_COND_V(p_delay < 0, false); 1826 | 1827 | /* FIXME: Can't validate as GDExtension's `get_indexed` and `get` don't expose `r_valid`. 1828 | // Ensure the initial and target properties exist on their objects 1829 | bool prop_valid = false; 1830 | p_object->get_indexed(p_property.get_subnames(), &prop_valid); 1831 | ERR_FAIL_COND_V(!prop_valid, false); 1832 | 1833 | bool initial_prop_valid = false; 1834 | Variant initial_val = p_initial->get_indexed(p_initial_property.get_subnames(), &initial_prop_valid); 1835 | ERR_FAIL_COND_V(!initial_prop_valid, false); 1836 | */ 1837 | Variant initial_val = p_initial->get_indexed(nodepath_subnames_only(p_initial_property)); 1838 | 1839 | // Convert the initial INT value to REAL as it is better for interpolation 1840 | if (initial_val.get_type() == Variant::INT) { 1841 | initial_val = initial_val.operator real_t(); 1842 | } 1843 | ERR_FAIL_COND_V(initial_val.get_type() != p_final_val.get_type(), false); 1844 | 1845 | // Build the InterpolateData object 1846 | InterpolateData data; 1847 | data.active = true; 1848 | data.type = TARGETING_PROPERTY; 1849 | data.finish = false; 1850 | data.elapsed = 0; 1851 | 1852 | // Give the data it's configuration 1853 | data.id = p_object->get_instance_id(); 1854 | data.key = nodepath_get_subnames(p_property); 1855 | data.concatenated_key = p_property.get_concatenated_subnames(); 1856 | data.target_id = p_initial->get_instance_id(); 1857 | data.target_key = nodepath_get_subnames(p_initial_property); 1858 | data.initial_val = initial_val; 1859 | data.final_val = p_final_val; 1860 | data.duration = p_duration; 1861 | data.trans_type = p_trans_type; 1862 | data.ease_type = p_ease_type; 1863 | data.delay = p_delay; 1864 | 1865 | // Ensure there is a valid delta 1866 | if (!_calc_delta_val(data.initial_val, data.final_val, data.delta_val)) { 1867 | return false; 1868 | } 1869 | 1870 | // Add the interpolation 1871 | _push_interpolate_data(data); 1872 | return true; 1873 | } 1874 | 1875 | bool Threen::targeting_method(Object *p_object, StringName p_method, Object *p_initial, StringName p_initial_method, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) { 1876 | // If we are currently updating, call this function again later 1877 | if (pending_update != 0) { 1878 | _add_pending_command("targeting_method", p_object, p_method, p_initial, p_initial_method, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay); 1879 | return true; 1880 | } 1881 | 1882 | // Convert final INT values to REAL as they are better for interpolation 1883 | if (p_final_val.get_type() == Variant::INT) { 1884 | p_final_val = p_final_val.operator real_t(); 1885 | } 1886 | 1887 | // Make sure the given objects are valid 1888 | ERR_FAIL_COND_V(p_object == nullptr, false); 1889 | ERR_FAIL_COND_V(p_initial == nullptr, false); 1890 | 1891 | // No negative durations 1892 | ERR_FAIL_COND_V(p_duration < 0, false); 1893 | 1894 | // Ensure transition and easing types are valid 1895 | ERR_FAIL_COND_V(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false); 1896 | ERR_FAIL_COND_V(p_ease_type < 0 || p_ease_type >= EASE_COUNT, false); 1897 | 1898 | // No negative delays 1899 | ERR_FAIL_COND_V(p_delay < 0, false); 1900 | 1901 | // Make sure both objects have the given method 1902 | ERR_FAIL_COND_V_MSG(!p_object->has_method(p_method), false, "Object has no method named: " + p_method + "."); 1903 | ERR_FAIL_COND_V_MSG(!p_initial->has_method(p_initial_method), false, "Initial Object has no method named: " + p_initial_method + "."); 1904 | 1905 | // Call the method to get the initial value 1906 | Variant initial_val = p_initial->call(p_initial_method); 1907 | //ERR_FAIL_COND_V(error.error != GDEXTENSION_CALL_OK, false); 1908 | 1909 | // Convert initial INT values to REAL as they aer better for interpolation 1910 | if (initial_val.get_type() == Variant::INT) { 1911 | initial_val = initial_val.operator real_t(); 1912 | } 1913 | ERR_FAIL_COND_V(initial_val.get_type() != p_final_val.get_type(), false); 1914 | 1915 | // Build the new InterpolateData object 1916 | InterpolateData data; 1917 | data.active = true; 1918 | data.type = TARGETING_METHOD; 1919 | data.finish = false; 1920 | data.elapsed = 0; 1921 | 1922 | // Configure the data 1923 | data.id = p_object->get_instance_id(); 1924 | data.key.push_back(p_method); 1925 | data.concatenated_key = p_method; 1926 | data.target_id = p_initial->get_instance_id(); 1927 | data.target_key.push_back(p_initial_method); 1928 | data.initial_val = initial_val; 1929 | data.final_val = p_final_val; 1930 | data.duration = p_duration; 1931 | data.trans_type = p_trans_type; 1932 | data.ease_type = p_ease_type; 1933 | data.delay = p_delay; 1934 | 1935 | // Ensure there is a valid delta 1936 | if (!_calc_delta_val(data.initial_val, data.final_val, data.delta_val)) { 1937 | return false; 1938 | } 1939 | 1940 | // Add the interpolation 1941 | _push_interpolate_data(data); 1942 | return true; 1943 | } 1944 | 1945 | Threen::Threen() { 1946 | // Initialize tween attributes 1947 | tween_process_mode = TWEEN_PROCESS_IDLE; 1948 | repeat = false; 1949 | speed_scale = 1; 1950 | pending_update = 0; 1951 | uid = 0; 1952 | } 1953 | 1954 | Threen::~Threen() { 1955 | } 1956 | --------------------------------------------------------------------------------