├── 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 |
--------------------------------------------------------------------------------