diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt
index 903fa8c7e..bb8141be6 100644
--- a/COPYRIGHT.txt
+++ b/COPYRIGHT.txt
@@ -97,6 +97,13 @@ Copyright: 1997-2017, Sam Lantinga
2014-2022, Godot Engine contributors.
License: Expat and Zlib
+Files: ./scene/animation/easing_equations.h
+Comment: Robert Penner's Easing Functions
+Copyright: 2001, Robert Penner
+ 2007-2022 Juan Linietsky, Ariel Manzur.
+ 2014-2022 Godot Engine contributors.
+License: Expat
+
Files: ./servers/physics/collision_solver_sat.cpp
Comment: Open Dynamics Engine
Copyright: 2001-2003, Russell L. Smith, Alen Ladavac, Nguyen Binh
@@ -248,11 +255,6 @@ Comment: Clipper
Copyright: 2010-2017, Angus Johnson
License: BSL-1.0
-Files: ./thirdparty/misc/easing_equations.cpp
-Comment: Robert Penner's Easing Functions
-Copyright: 2001, Robert Penner
-License: BSD-3-clause
-
Files: ./thirdparty/misc/fastlz.c
./thirdparty/misc/fastlz.h
Comment: FastLZ
diff --git a/doc/classes/CallbackTweener.xml b/doc/classes/CallbackTweener.xml
new file mode 100644
index 000000000..ab17f73c1
--- /dev/null
+++ b/doc/classes/CallbackTweener.xml
@@ -0,0 +1,27 @@
+
+
+
+ Calls the specified method after optional delay.
+
+
+ [CallbackTweener] is used to call a method in a tweening sequence. See [method SceneTreeTween.tween_callback] for more usage information.
+ [b]Note:[/b] [method SceneTreeTween.tween_callback] is the only correct way to create [CallbackTweener]. Any [CallbackTweener] created manually will not function correctly.
+
+
+
+
+
+
+
+
+ Makes the callback call delayed by given time in seconds. Example:
+ [codeblock]
+ var tween = get_tree().create_tween()
+ tween.tween_callback(queue_free).set_delay(2) #this will call queue_free() after 2 seconds
+ [/codeblock]
+
+
+
+
+
+
diff --git a/doc/classes/IntervalTweener.xml b/doc/classes/IntervalTweener.xml
new file mode 100644
index 000000000..fadc00f05
--- /dev/null
+++ b/doc/classes/IntervalTweener.xml
@@ -0,0 +1,16 @@
+
+
+
+ Creates an idle interval in a [SceneTreeTween] animation.
+
+
+ [IntervalTweener] is used to make delays in a tweening sequence. See [method SceneTreeTween.tween_interval] for more usage information.
+ [b]Note:[/b] [method SceneTreeTween.tween_interval] is the only correct way to create [IntervalTweener]. Any [IntervalTweener] created manually will not function correctly.
+
+
+
+
+
+
+
+
diff --git a/doc/classes/MethodTweener.xml b/doc/classes/MethodTweener.xml
new file mode 100644
index 000000000..d6d5493b4
--- /dev/null
+++ b/doc/classes/MethodTweener.xml
@@ -0,0 +1,37 @@
+
+
+
+ Interpolates an abstract value and supplies it to a method called over time.
+
+
+ [MethodTweener] is similar to a combination of [CallbackTweener] and [PropertyTweener]. It calls a method providing an interpolated value as a parameter. See [method SceneTreeTween.tween_method] for more usage information.
+ [b]Note:[/b] [method SceneTreeTween.tween_method] is the only correct way to create [MethodTweener]. Any [MethodTweener] created manually will not function correctly.
+
+
+
+
+
+
+
+
+ Sets the time in seconds after which the [MethodTweener] will start interpolating. By default there's no delay.
+
+
+
+
+
+
+ Sets the type of used easing from [enum Tween.EaseType]. If not set, the default easing is used from the [SceneTreeTween] that contains this Tweener.
+
+
+
+
+
+
+ Sets the type of used transition from [enum Tween.TransitionType]. If not set, the default transition is used from the [SceneTreeTween] that contains this Tweener.
+
+
+
+
+
+
diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml
index a093efa0d..09e9b6196 100644
--- a/doc/classes/Node.xml
+++ b/doc/classes/Node.xml
@@ -144,6 +144,15 @@
Returns [code]true[/code] if the node can process while the scene tree is paused (see [member pause_mode]). Always returns [code]true[/code] if the scene tree is not paused, and [code]false[/code] if the node is not in the tree.
+
+
+
+ Creates a new [SceneTreeTween] and binds it to this node. This is equivalent of doing:
+ [codeblock]
+ get_tree().create_tween().bind_node(self)
+ [/codeblock]
+
+
diff --git a/doc/classes/PropertyTweener.xml b/doc/classes/PropertyTweener.xml
new file mode 100644
index 000000000..3cde8cd18
--- /dev/null
+++ b/doc/classes/PropertyTweener.xml
@@ -0,0 +1,68 @@
+
+
+
+ Interpolates an [Object]'s property over time.
+
+
+ [PropertyTweener] is used to interpolate a property in an object. See [method SceneTreeTween.tween_property] for more usage information.
+ [b]Note:[/b] [method SceneTreeTween.tween_property] is the only correct way to create [PropertyTweener]. Any [PropertyTweener] created manually will not function correctly.
+
+
+
+
+
+
+
+ When called, the final value will be used as a relative value instead. Example:
+ [codeblock]
+ var tween = get_tree().create_tween()
+ tween.tween_property(self, "position", Vector2.RIGHT * 100, 1).as_relative() #the node will move by 100 pixels to the right
+ [/codeblock]
+
+
+
+
+
+
+ Sets a custom initial value to the [PropertyTweener]. Example:
+ [codeblock]
+ var tween = get_tree().create_tween()
+ tween.tween_property(self, "position", Vector2(200, 100), 1).from(Vector2(100, 100) #this will move the node from position (100, 100) to (200, 100)
+ [/codeblock]
+
+
+
+
+
+ Makes the [PropertyTweener] use the current property value (i.e. at the time of creating this [PropertyTweener]) as a starting point. This is equivalent of using [method from] with the current value. These two calls will do the same:
+ [codeblock]
+ tween.tween_property(self, "position", Vector2(200, 100), 1).from(position)
+ tween.tween_property(self, "position", Vector2(200, 100), 1).from_current()
+ [/codeblock]
+
+
+
+
+
+
+ Sets the time in seconds after which the [PropertyTweener] will start interpolating. By default there's no delay.
+
+
+
+
+
+
+ Sets the type of used easing from [enum Tween.EaseType]. If not set, the default easing is used from the [SceneTreeTween] that contains this Tweener.
+
+
+
+
+
+
+ Sets the type of used transition from [enum Tween.TransitionType]. If not set, the default transition is used from the [SceneTreeTween] that contains this Tweener.
+
+
+
+
+
+
diff --git a/doc/classes/SceneTree.xml b/doc/classes/SceneTree.xml
index 23306218c..9412e0165 100644
--- a/doc/classes/SceneTree.xml
+++ b/doc/classes/SceneTree.xml
@@ -62,6 +62,12 @@
The timer will be automatically freed after its time elapses.
+
+
+
+ Creates and returns a new [SceneTreeTween].
+
+
@@ -92,6 +98,12 @@
+
+
+
+ Returns an array of currently existing [SceneTreeTween]s in the [SceneTree] (both running and paused).
+
+
diff --git a/doc/classes/SceneTreeTween.xml b/doc/classes/SceneTreeTween.xml
new file mode 100644
index 000000000..ff353ae6d
--- /dev/null
+++ b/doc/classes/SceneTreeTween.xml
@@ -0,0 +1,326 @@
+
+
+
+ Lightweight object used for general-purpose animation via script, using [Tweener]s.
+
+
+ [SceneTreeTween] is a tween managed by the scene tree. As opposed to [Tween], it does not require the instantiation of a node.
+ [SceneTreeTween]s are more light-weight than [AnimationPlayer], so they are very much suited for simple animations or general tasks that don't require visual tweaking provided by the editor. They can be used in a fire-and-forget manner for some logic that normally would be done by code. You can e.g. make something shoot periodically by using a looped [CallbackTweener] with a delay.
+ A [SceneTreeTween] can be created by using either [method SceneTree.create_tween] or [method Node.create_tween]. [SceneTreeTween]s created manually (i.e. by using [code]Tween.new()[/code]) are invalid. They can't be used for tweening values, but you can do manual interpolation with [method interpolate_value].
+ A tween animation is created by adding [Tweener]s to the [SceneTreeTween] object, using [method tween_property], [method tween_interval], [method tween_callback] or [method tween_method]:
+ [codeblock]
+ var tween = get_tree().create_tween()
+ tween.tween_property($Sprite, "modulate", Color.red, 1)
+ tween.tween_property($Sprite, "scale", Vector2(), 1)
+ tween.tween_callback($Sprite, "queue_free")
+ [/codeblock]
+ This sequence will make the [code]$Sprite[/code] node turn red, then shrink, before finally calling [method Node.queue_free] to free the sprite. [Tweener]s are executed one after another by default. This behavior can be changed using [method parallel] and [method set_parallel].
+ When a [Tweener] is created with one of the [code]tween_*[/code] methods, a chained method call can be used to tweak the properties of this [Tweener]. For example, if you want to set a different transition type in the above example, you can use [method set_trans]:
+ [codeblock]
+ var tween = get_tree().create_tween()
+ tween.tween_property($Sprite, "modulate", Color.red, 1).set_trans(Tween.TRANS_SINE)
+ tween.tween_property($Sprite, "scale", Vector2(), 1).set_trans(Tween.TRANS_BOUNCE)
+ tween.tween_callback($Sprite, "queue_free")
+ [/codeblock]
+ Most of the [SceneTreeTween] methods can be chained this way too. In the following example the [SceneTreeTween] is bound to the running script's node and a default transition is set for its [Tweener]s:
+ [codeblock]
+ var tween = get_tree().create_tween().bind_node(self).set_trans(Tween.TRANS_ELASTIC)
+ tween.tween_property($Sprite, "modulate", Color.red, 1)
+ tween.tween_property($Sprite, "scale", Vector2(), 1)
+ tween.tween_callback($Sprite, "queue_free")
+ [/codeblock]
+ Another interesting use for [SceneTreeTween]s is animating arbitrary sets of objects:
+ [codeblock]
+ var tween = create_tween()
+ for sprite in get_children():
+ tween.tween_property(sprite, "position", Vector2(0, 0), 1)
+ [/codeblock]
+ In the example above, all children of a node are moved one after another to position (0, 0).
+ Some [Tweener]s use transitions and eases. The first accepts a [enum Tween.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 Tween.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 Tween.TransitionType] constants with [constant Tween.EASE_IN_OUT], and use the one that looks best.
+ [url=https://raw.githubusercontent.com/godotengine/godot-docs/master/img/tween_cheatsheet.png]Tween easing and transition types cheatsheet[/url]
+ [b]Note:[/b] All [SceneTreeTween]s will automatically start by default. To prevent a [SceneTreeTween] from autostarting, you can call [method stop] immediately after it is created.
+
+
+
+
+
+
+
+
+ Binds this [SceneTreeTween] with the given [code]node[/code]. [SceneTreeTween]s are processed directly by the [SceneTree], so they run independently of the animated nodes. When you bind a [Node] with the [SceneTreeTween], the [SceneTreeTween] will halt the animation when the object is not inside tree and the [SceneTreeTween] will be automatically killed when the bound object is freed. Also [constant TWEEN_PAUSE_BOUND] will make the pausing behavior dependent on the bound node.
+ For a shorter way to create and bind a [SceneTreeTween], you can use [method Node.create_tween].
+
+
+
+
+
+ Used to chain two [Tweener]s after [method set_parallel] is called with [code]true[/code].
+ [codeblock]
+ var tween = create_tween().set_parallel(true)
+ tween.tween_property(...)
+ tween.tween_property(...) # Will run parallelly with above.
+ tween.chain().tween_property(...) # Will run after two above are finished.
+ [/codeblock]
+
+
+
+
+
+
+ Processes the [SceneTreeTween] by the given [code]delta[/code] value, in seconds. This is mostly useful for manual control when the [SceneTreeTween] is paused. It can also be used to end the [SceneTreeTween] animation immediately, by setting [code]delta[/code] longer than the whole duration of the [SceneTreeTween] animation.
+ Returns [code]true[/code] if the [SceneTreeTween] still has [Tweener]s that haven't finished.
+ [b]Note:[/b] The [SceneTreeTween] will become invalid in the next processing frame after its animation finishes. Calling [method stop] after performing [method custom_step] instead keeps and resets the [SceneTreeTween].
+
+
+
+
+
+ Returns the total time in seconds the [SceneTreeTween] has been animating (i.e. the time since it started, not counting pauses etc.). The time is affected by [method set_speed_scale], and [method stop] will reset it to [code]0[/code].
+ [b]Note:[/b] As it results from accumulating frame deltas, the time returned after the [SceneTreeTween] has finished animating will be slightly greater than the actual [SceneTreeTween] duration.
+
+
+
+
+
+
+
+
+
+
+
+ This method can be used for manual interpolation of a value, when you don't want [SceneTreeTween] to do animating for you. It's similar to [method @GDScript.lerp], but with support for custom transition and easing.
+ [code]initial_value[/code] is the starting value of the interpolation.
+ [code]delta_value[/code] is the change of the value in the interpolation, i.e. it's equal to [code]final_value - initial_value[/code].
+ [code]elapsed_time[/code] is the time in seconds that passed after the interpolation started and it's used to control the position of the interpolation. E.g. when it's equal to half of the [code]duration[/code], the interpolated value will be halfway between initial and final values. This value can also be greater than [code]duration[/code] or lower than 0, which will extrapolate the value.
+ [code]duration[/code] is the total time of the interpolation.
+ [b]Note:[/b] If [code]duration[/code] is equal to [code]0[/code], the method will always return the final value, regardless of [code]elapsed_time[/code] provided.
+
+
+
+
+
+ Returns whether the [SceneTreeTween] is currently running, i.e. it wasn't paused and it's not finished.
+
+
+
+
+
+ Returns whether the [SceneTreeTween] is valid. A valid [SceneTreeTween] is a [SceneTreeTween] contained by the scene tree (i.e. the array from [method SceneTree.get_processed_tweens] will contain this [SceneTreeTween]). A [SceneTreeTween] might become invalid when it has finished tweening, is killed, or when created with [code]SceneTreeTween.new()[/code]. Invalid [SceneTreeTween]s can't have [Tweener]s appended. You can however still use [method interpolate_value].
+
+
+
+
+
+ Aborts all tweening operations and invalidates the [SceneTreeTween].
+
+
+
+
+
+ Makes the next [Tweener] run parallelly to the previous one. Example:
+ [codeblock]
+ var tween = create_tween()
+ tween.tween_property(...)
+ tween.parallel().tween_property(...)
+ tween.parallel().tween_property(...)
+ [/codeblock]
+ All [Tweener]s in the example will run at the same time.
+ You can make the [SceneTreeTween] parallel by default by using [method set_parallel].
+
+
+
+
+
+ Pauses the tweening. The animation can be resumed by using [method play].
+
+
+
+
+
+ Resumes a paused or stopped [SceneTreeTween].
+
+
+
+
+
+
+ Sets the default ease type for [PropertyTweener]s and [MethodTweener]s animated by this [SceneTreeTween].
+
+
+
+
+
+
+ Sets the number of times the tweening sequence will be repeated, i.e. [code]set_loops(2)[/code] will run the animation twice.
+ Calling this method without arguments will make the [SceneTreeTween] run infinitely, until either it is killed with [method kill], the [SceneTreeTween]'s bound node is freed, or all the animated objects have been freed (which makes further animation impossible).
+ [b]Warning:[/b] Make sure to always add some duration/delay when using infinite loops. To prevent the game freezing, 0-duration looped animations (e.g. a single [CallbackTweener] with no delay) are stopped after a small number of loops, which may produce unexpected results. If a [SceneTreeTween]'s lifetime depends on some node, always use [method bind_node].
+
+
+
+
+
+
+ If [code]parallel[/code] is [code]true[/code], the [Tweener]s appended after this method will by default run simultaneously, as opposed to sequentially.
+
+
+
+
+
+
+ Determines the behavior of the [SceneTreeTween] when the [SceneTree] is paused. Check [enum TweenPauseMode] for options.
+ Default value is [constant TWEEN_PAUSE_BOUND].
+
+
+
+
+
+
+ Determines whether the [SceneTreeTween] should run during idle frame (see [method Node._process]) or physics frame (see [method Node._physics_process].
+ Default value is [constant Tween.TWEEN_PROCESS_IDLE].
+
+
+
+
+
+
+ Scales the speed of tweening. This affects all [Tweener]s and their delays.
+
+
+
+
+
+
+ Sets the default transition type for [PropertyTweener]s and [MethodTweener]s animated by this [SceneTreeTween].
+
+
+
+
+
+ Stops the tweening and resets the [SceneTreeTween] to its initial state. This will not remove any appended [Tweener]s.
+
+
+
+
+
+
+
+
+ Creates and appends a [CallbackTweener]. This method can be used to call an arbitrary method in any object. Use [code]binds[/code] to bind additional arguments for the call.
+ Example: object that keeps shooting every 1 second.
+ [codeblock]
+ var tween = get_tree().create_tween().set_loops()
+ tween.tween_callback(self, "shoot").set_delay(1)
+ [/codeblock]
+ Example: turning a sprite red and then blue, with 2 second delay.
+ [codeblock]
+ var tween = get_tree().create_tween()
+ tween.tween_callback($Sprite, "set_modulate", [Color.red]).set_delay(2)
+ tween.tween_callback($Sprite, "set_modulate", [Color.blue]).set_delay(2)
+ [/codeblock]
+
+
+
+
+
+
+ Creates and appends an [IntervalTweener]. This method can be used to create delays in the tween animation, as an alternative to using the delay in other [Tweener]s, or when there's no animation (in which case the [SceneTreeTween] acts as a timer). [code]time[/code] is the length of the interval, in seconds.
+ Example: creating an interval in code execution.
+ [codeblock]
+ # ... some code
+ yield(create_tween().tween_interval(2), "finished")
+ # ... more code
+ [/codeblock]
+ Example: creating an object that moves back and forth and jumps every few seconds.
+ [codeblock]
+ var tween = create_tween().set_loops()
+ tween.tween_property($Sprite, "position:x", 200.0, 1).as_relative()
+ tween.tween_callback(self, "jump")
+ tween.tween_interval(2)
+ tween.tween_property($Sprite, "position:x", -200.0, 1).as_relative()
+ tween.tween_callback(self, "jump")
+ tween.tween_interval(2)
+ [/codeblock]
+
+
+
+
+
+
+
+
+
+
+
+ Creates and appends a [MethodTweener]. This method is similar to a combination of [method tween_callback] and [method tween_property]. It calls a method over time with a tweened value provided as an argument. The value is tweened between [code]from[/code] and [code]to[/code] over the time specified by [code]duration[/code], in seconds. Use [code]binds[/code] to bind additional arguments for the call. You can use [method MethodTweener.set_ease] and [method MethodTweener.set_trans] to tweak the easing and transition of the value or [method MethodTweener.set_delay] to delay the tweening.
+ Example: making a 3D object look from one point to another point.
+ [codeblock]
+ var tween = create_tween()
+ tween.tween_method(self, "look_at", Vector3(-1, 0, -1), Vector3(1, 0, -1), 1, [Vector3.UP]) # The look_at() method takes up vector as second argument.
+ [/codeblock]
+ Example: setting a text of a [Label], using an intermediate method and after a delay.
+ [codeblock]
+ func _ready():
+ var tween = create_tween()
+ tween.tween_method(self, "set_label_text", 0, 10, 1).set_delay(1)
+
+ func set_label_text(value: int):
+ $Label.text = "Counting " + str(value)
+ [/codeblock]
+
+
+
+
+
+
+
+
+
+ Creates and appends a [PropertyTweener]. This method tweens a [code]property[/code] of an [code]object[/code] between an initial value and [code]final_val[/code] in a span of time equal to [code]duration[/code], in seconds. The initial value by default is the property's value at the time the tweening of the [PropertyTweener] starts. For example:
+ [codeblock]
+ var tween = create_tween()
+ tween.tween_property($Sprite, "position", Vector2(100, 200), 1)
+ tween.tween_property($Sprite, "position", Vector2(200, 300), 1)
+ [/codeblock]
+ will move the sprite to position (100, 200) and then to (200, 300). If you use [method PropertyTweener.from] or [method PropertyTweener.from_current], the starting position will be overwritten by the given value instead. See other methods in [PropertyTweener] to see how the tweening can be tweaked further.
+ [b]Note:[/b] 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] (eg. [code]position:x[/code]), where it would only apply to that particular component.
+ Example: moving object twice from the same position, with different transition types.
+ [codeblock]
+ var tween = create_tween()
+ tween.tween_property($Sprite, "position", Vector2.RIGHT * 300, 1).as_relative().set_trans(Tween.TRANS_SINE)
+ tween.tween_property($Sprite, "position", Vector2.RIGHT * 300, 1).as_relative().from_current().set_trans(Tween.TRANS_EXPO)
+ [/codeblock]
+
+
+
+
+
+
+ Emitted when the [SceneTreeTween] has finished all tweening. Never emitted when the [SceneTreeTween] is set to infinite looping (see [method set_loops]).
+ [b]Note:[/b] The [SceneTreeTween] is removed (invalidated) in the next processing frame after this signal is emitted. Calling [method stop] inside the signal callback will prevent the [SceneTreeTween] from being removed.
+
+
+
+
+
+ Emitted when a full loop is complete (see [method set_loops]), providing the loop index. This signal is not emitted after the final loop, use [signal finished] instead for this case.
+
+
+
+
+
+ Emitted when one step of the [SceneTreeTween] is complete, providing the step index. One step is either a single [Tweener] or a group of [Tweener]s running in parallel.
+
+
+
+
+
+ If the [SceneTreeTween] has a bound node, it will process when that node can process (see [member Node.pause_mode]). Otherwise it's the same as [constant TWEEN_PAUSE_STOP].
+
+
+ If [SceneTree] is paused, the [SceneTreeTween] will also pause.
+
+
+ The [SceneTreeTween] will process regardless of whether [SceneTree] is paused.
+
+
+
diff --git a/doc/classes/Tweener.xml b/doc/classes/Tweener.xml
new file mode 100644
index 000000000..45bb9bd99
--- /dev/null
+++ b/doc/classes/Tweener.xml
@@ -0,0 +1,22 @@
+
+
+
+ Abstract class for all Tweeners used by [SceneTreeTween].
+
+
+ Tweeners are objects that perform a specific animating task, e.g. interpolating a property or calling a method at a given time. A [Tweener] can't be created manually, you need to use a dedicated method from [SceneTreeTween].
+
+
+
+
+
+
+
+
+ Emitted when the [Tweener] has just finished its job.
+
+
+
+
+
+
diff --git a/scene/animation/SCsub b/scene/animation/SCsub
index e45df70c3..f867b9193 100644
--- a/scene/animation/SCsub
+++ b/scene/animation/SCsub
@@ -2,17 +2,6 @@
Import("env")
-# Thirdparty code
-
-thirdparty_obj = []
-
-thirdparty_sources = "#thirdparty/misc/easing_equations.cpp"
-
-env_thirdparty = env.Clone()
-env_thirdparty.disable_warnings()
-env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources)
-env.scene_sources += thirdparty_obj
-
# Pandemonium source files
scene_obj = []
@@ -20,5 +9,3 @@ scene_obj = []
env.add_source_files(scene_obj, "*.cpp")
env.scene_sources += scene_obj
-# Needed to force rebuilding the scene files when the thirdparty code is updated.
-env.Depends(scene_obj, thirdparty_obj)
diff --git a/scene/animation/easing_equations.h b/scene/animation/easing_equations.h
new file mode 100644
index 000000000..094829e40
--- /dev/null
+++ b/scene/animation/easing_equations.h
@@ -0,0 +1,405 @@
+/*************************************************************************/
+/* easing_equations.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+/*
+ * Derived from Robert Penner's easing equations: http://robertpenner.com/easing/
+ *
+ * Copyright (c) 2001 Robert Penner
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef EASING_EQUATIONS_H
+#define EASING_EQUATIONS_H
+
+namespace linear {
+static real_t in(real_t t, real_t b, real_t c, real_t d) {
+ return c * t / d + b;
+}
+}; // namespace linear
+
+namespace sine {
+static real_t in(real_t t, real_t b, real_t c, real_t d) {
+ return -c * cos(t / d * (Math_PI / 2)) + c + b;
+}
+
+static real_t out(real_t t, real_t b, real_t c, real_t d) {
+ return c * sin(t / d * (Math_PI / 2)) + b;
+}
+
+static real_t in_out(real_t t, real_t b, real_t c, real_t d) {
+ return -c / 2 * (cos(Math_PI * t / d) - 1) + b;
+}
+
+static real_t out_in(real_t t, real_t b, real_t c, real_t d) {
+ if (t < d / 2) {
+ return out(t * 2, b, c / 2, d);
+ }
+ return in(t * 2 - d, b + c / 2, c / 2, d);
+}
+}; // namespace sine
+
+namespace quint {
+static real_t in(real_t t, real_t b, real_t c, real_t d) {
+ return c * pow(t / d, 5) + b;
+}
+
+static real_t out(real_t t, real_t b, real_t c, real_t d) {
+ return c * (pow(t / d - 1, 5) + 1) + b;
+}
+
+static real_t in_out(real_t t, real_t b, real_t c, real_t d) {
+ t = t / d * 2;
+
+ if (t < 1) {
+ return c / 2 * pow(t, 5) + b;
+ }
+ return c / 2 * (pow(t - 2, 5) + 2) + b;
+}
+
+static real_t out_in(real_t t, real_t b, real_t c, real_t d) {
+ if (t < d / 2) {
+ return out(t * 2, b, c / 2, d);
+ }
+ return in(t * 2 - d, b + c / 2, c / 2, d);
+}
+}; // namespace quint
+
+namespace quart {
+static real_t in(real_t t, real_t b, real_t c, real_t d) {
+ return c * pow(t / d, 4) + b;
+}
+
+static real_t out(real_t t, real_t b, real_t c, real_t d) {
+ return -c * (pow(t / d - 1, 4) - 1) + b;
+}
+
+static real_t in_out(real_t t, real_t b, real_t c, real_t d) {
+ t = t / d * 2;
+
+ if (t < 1) {
+ return c / 2 * pow(t, 4) + b;
+ }
+ return -c / 2 * (pow(t - 2, 4) - 2) + b;
+}
+
+static real_t out_in(real_t t, real_t b, real_t c, real_t d) {
+ if (t < d / 2) {
+ return out(t * 2, b, c / 2, d);
+ }
+ return in(t * 2 - d, b + c / 2, c / 2, d);
+}
+}; // namespace quart
+
+namespace quad {
+static real_t in(real_t t, real_t b, real_t c, real_t d) {
+ return c * pow(t / d, 2) + b;
+}
+
+static real_t out(real_t t, real_t b, real_t c, real_t d) {
+ t /= d;
+ return -c * t * (t - 2) + b;
+}
+
+static real_t in_out(real_t t, real_t b, real_t c, real_t d) {
+ t = t / d * 2;
+
+ if (t < 1) {
+ return c / 2 * pow(t, 2) + b;
+ }
+ return -c / 2 * ((t - 1) * (t - 3) - 1) + b;
+}
+
+static real_t out_in(real_t t, real_t b, real_t c, real_t d) {
+ if (t < d / 2) {
+ return out(t * 2, b, c / 2, d);
+ }
+ return in(t * 2 - d, b + c / 2, c / 2, d);
+}
+}; // namespace quad
+
+namespace expo {
+static real_t in(real_t t, real_t b, real_t c, real_t d) {
+ if (t == 0) {
+ return b;
+ }
+ return c * pow(2, 10 * (t / d - 1)) + b - c * 0.001;
+}
+
+static real_t out(real_t t, real_t b, real_t c, real_t d) {
+ if (t == d) {
+ return b + c;
+ }
+ return c * 1.001 * (-pow(2, -10 * t / d) + 1) + b;
+}
+
+static real_t in_out(real_t t, real_t b, real_t c, real_t d) {
+ if (t == 0) {
+ return b;
+ }
+
+ if (t == d) {
+ return b + c;
+ }
+
+ t = t / d * 2;
+
+ if (t < 1) {
+ return c / 2 * pow(2, 10 * (t - 1)) + b - c * 0.0005;
+ }
+ return c / 2 * 1.0005 * (-pow(2, -10 * (t - 1)) + 2) + b;
+}
+
+static real_t out_in(real_t t, real_t b, real_t c, real_t d) {
+ if (t < d / 2) {
+ return out(t * 2, b, c / 2, d);
+ }
+ return in(t * 2 - d, b + c / 2, c / 2, d);
+}
+}; // namespace expo
+
+namespace elastic {
+static real_t in(real_t t, real_t b, real_t c, real_t d) {
+ if (t == 0) {
+ return b;
+ }
+
+ t /= d;
+ if (t == 1) {
+ return b + c;
+ }
+
+ t -= 1;
+ float p = d * 0.3f;
+ float a = c * pow(2, 10 * t);
+ float s = p / 4;
+
+ return -(a * sin((t * d - s) * (2 * Math_PI) / p)) + b;
+}
+
+static real_t out(real_t t, real_t b, real_t c, real_t d) {
+ if (t == 0) {
+ return b;
+ }
+
+ t /= d;
+ if (t == 1) {
+ return b + c;
+ }
+
+ float p = d * 0.3f;
+ float s = p / 4;
+
+ return (c * pow(2, -10 * t) * sin((t * d - s) * (2 * Math_PI) / p) + c + b);
+}
+
+static real_t in_out(real_t t, real_t b, real_t c, real_t d) {
+ if (t == 0) {
+ return b;
+ }
+
+ if ((t /= d / 2) == 2) {
+ return b + c;
+ }
+
+ float p = d * (0.3f * 1.5f);
+ float a = c;
+ float s = p / 4;
+
+ if (t < 1) {
+ t -= 1;
+ a *= pow(2, 10 * t);
+ return -0.5f * (a * sin((t * d - s) * (2 * Math_PI) / p)) + b;
+ }
+
+ t -= 1;
+ a *= pow(2, -10 * t);
+ return a * sin((t * d - s) * (2 * Math_PI) / p) * 0.5f + c + b;
+}
+
+static real_t out_in(real_t t, real_t b, real_t c, real_t d) {
+ if (t < d / 2) {
+ return out(t * 2, b, c / 2, d);
+ }
+ return in(t * 2 - d, b + c / 2, c / 2, d);
+}
+}; // namespace elastic
+
+namespace cubic {
+static real_t in(real_t t, real_t b, real_t c, real_t d) {
+ t /= d;
+ return c * t * t * t + b;
+}
+
+static real_t out(real_t t, real_t b, real_t c, real_t d) {
+ t = t / d - 1;
+ return c * (t * t * t + 1) + b;
+}
+
+static real_t in_out(real_t t, real_t b, real_t c, real_t d) {
+ t /= d / 2;
+ if (t < 1) {
+ return c / 2 * t * t * t + b;
+ }
+
+ t -= 2;
+ return c / 2 * (t * t * t + 2) + b;
+}
+
+static real_t out_in(real_t t, real_t b, real_t c, real_t d) {
+ if (t < d / 2) {
+ return out(t * 2, b, c / 2, d);
+ }
+ return in(t * 2 - d, b + c / 2, c / 2, d);
+}
+}; // namespace cubic
+
+namespace circ {
+static real_t in(real_t t, real_t b, real_t c, real_t d) {
+ t /= d;
+ return -c * (sqrt(1 - t * t) - 1) + b;
+}
+
+static real_t out(real_t t, real_t b, real_t c, real_t d) {
+ t = t / d - 1;
+ return c * sqrt(1 - t * t) + b;
+}
+
+static real_t in_out(real_t t, real_t b, real_t c, real_t d) {
+ t /= d / 2;
+ if (t < 1) {
+ return -c / 2 * (sqrt(1 - t * t) - 1) + b;
+ }
+
+ t -= 2;
+ return c / 2 * (sqrt(1 - t * t) + 1) + b;
+}
+
+static real_t out_in(real_t t, real_t b, real_t c, real_t d) {
+ if (t < d / 2) {
+ return out(t * 2, b, c / 2, d);
+ }
+ return in(t * 2 - d, b + c / 2, c / 2, d);
+}
+}; // namespace circ
+
+namespace bounce {
+static real_t out(real_t t, real_t b, real_t c, real_t d) {
+ t /= d;
+
+ if (t < (1 / 2.75f)) {
+ return c * (7.5625f * t * t) + b;
+ }
+
+ if (t < (2 / 2.75f)) {
+ t -= 1.5f / 2.75f;
+ return c * (7.5625f * t * t + 0.75f) + b;
+ }
+
+ if (t < (2.5 / 2.75)) {
+ t -= 2.25f / 2.75f;
+ return c * (7.5625f * t * t + 0.9375f) + b;
+ }
+
+ t -= 2.625f / 2.75f;
+ return c * (7.5625f * t * t + 0.984375f) + b;
+}
+
+static real_t in(real_t t, real_t b, real_t c, real_t d) {
+ return c - out(d - t, 0, c, d) + b;
+}
+
+static real_t in_out(real_t t, real_t b, real_t c, real_t d) {
+ if (t < d / 2) {
+ return in(t * 2, b, c / 2, d);
+ }
+ return out(t * 2 - d, b + c / 2, c / 2, d);
+}
+
+static real_t out_in(real_t t, real_t b, real_t c, real_t d) {
+ if (t < d / 2) {
+ return out(t * 2, b, c / 2, d);
+ }
+ return in(t * 2 - d, b + c / 2, c / 2, d);
+}
+}; // namespace bounce
+
+namespace back {
+static real_t in(real_t t, real_t b, real_t c, real_t d) {
+ float s = 1.70158f;
+ t /= d;
+
+ return c * t * t * ((s + 1) * t - s) + b;
+}
+
+static real_t out(real_t t, real_t b, real_t c, real_t d) {
+ float s = 1.70158f;
+ t = t / d - 1;
+
+ return c * (t * t * ((s + 1) * t + s) + 1) + b;
+}
+
+static real_t in_out(real_t t, real_t b, real_t c, real_t d) {
+ float s = 1.70158f * 1.525f;
+ t /= d / 2;
+
+ if (t < 1) {
+ return c / 2 * (t * t * ((s + 1) * t - s)) + b;
+ }
+
+ t -= 2;
+ return c / 2 * (t * t * ((s + 1) * t + s) + 2) + b;
+}
+
+static real_t out_in(real_t t, real_t b, real_t c, real_t d) {
+ if (t < d / 2) {
+ return out(t * 2, b, c / 2, d);
+ }
+ return in(t * 2 - d, b + c / 2, c / 2, d);
+}
+}; // namespace back
+
+#endif // EASING_EQUATIONS_H
diff --git a/scene/animation/scene_tree_tween.cpp b/scene/animation/scene_tree_tween.cpp
new file mode 100644
index 000000000..d15e3299b
--- /dev/null
+++ b/scene/animation/scene_tree_tween.cpp
@@ -0,0 +1,932 @@
+/*************************************************************************/
+/* scene_tree_tween.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "scene_tree_tween.h"
+
+#include "core/method_bind_ext.gen.inc"
+#include "scene/animation/tween.h"
+#include "scene/main/node.h"
+#include "scene/scene_string_names.h"
+
+void Tweener::set_tween(Ref p_tween) {
+ tween = p_tween;
+}
+
+void Tweener::clear_tween() {
+ tween.unref();
+}
+
+void Tweener::_bind_methods() {
+ ADD_SIGNAL(MethodInfo("finished"));
+}
+
+void SceneTreeTween::start_tweeners() {
+ if (tweeners.empty()) {
+ dead = true;
+ ERR_FAIL_MSG("SceneTreeTween without commands, aborting");
+ }
+
+ List[> &step = tweeners.write[current_step];
+ for (int i = 0; i < step.size(); i++) {
+ Ref &tweener = step[i];
+ tweener->start();
+ }
+}
+
+Ref SceneTreeTween::tween_property(Object *p_target, NodePath p_property, Variant p_to, float p_duration) {
+ ERR_FAIL_NULL_V(p_target, nullptr);
+ ERR_FAIL_COND_V_MSG(!valid, nullptr, "SceneTreeTween invalid. Either finished or created outside scene tree.");
+ ERR_FAIL_COND_V_MSG(started, nullptr, "Can't append to a SceneTreeTween that has started. Use stop() first.");
+
+#ifdef DEBUG_ENABLED
+ Variant::Type property_type = p_target->get_indexed(p_property.get_as_property_path().get_subnames()).get_type();
+ ERR_FAIL_COND_V_MSG(property_type != p_to.get_type(), Ref(), "Type mismatch between property and final value: " + Variant::get_type_name(property_type) + " and " + Variant::get_type_name(p_to.get_type()));
+#endif
+
+ Ref tweener = memnew(PropertyTweener(p_target, p_property, p_to, p_duration));
+ append(tweener);
+ return tweener;
+}
+
+Ref SceneTreeTween::tween_interval(float p_time) {
+ ERR_FAIL_COND_V_MSG(!valid, nullptr, "SceneTreeTween invalid. Either finished or created outside scene tree.");
+ ERR_FAIL_COND_V_MSG(started, nullptr, "Can't append to a SceneTreeTween that has started. Use stop() first.");
+
+ Ref tweener = memnew(IntervalTweener(p_time));
+ append(tweener);
+ return tweener;
+}
+
+Ref SceneTreeTween::tween_callback(Object *p_target, StringName p_method, const Vector &p_binds) {
+ ERR_FAIL_NULL_V(p_target, nullptr);
+ ERR_FAIL_COND_V_MSG(!valid, nullptr, "SceneTreeTween invalid. Either finished or created outside scene tree.");
+ ERR_FAIL_COND_V_MSG(started, nullptr, "Can't append to a SceneTreeTween that has started. Use stop() first.");
+
+ Ref tweener = memnew(CallbackTweener(p_target, p_method, p_binds));
+ append(tweener);
+ return tweener;
+}
+
+Ref SceneTreeTween::tween_method(Object *p_target, StringName p_method, Variant p_from, Variant p_to, float p_duration, const Vector &p_binds) {
+ ERR_FAIL_NULL_V(p_target, nullptr);
+ ERR_FAIL_COND_V_MSG(!valid, nullptr, "SceneTreeTween invalid. Either finished or created outside scene tree.");
+ ERR_FAIL_COND_V_MSG(started, nullptr, "Can't append to a SceneTreeTween that has started. Use stop() first.");
+
+ Ref tweener = memnew(MethodTweener(p_target, p_method, p_from, p_to, p_duration, p_binds));
+ append(tweener);
+ return tweener;
+}
+
+void SceneTreeTween::append(Ref p_tweener) {
+ p_tweener->set_tween(this);
+
+ if (parallel_enabled) {
+ current_step = MAX(current_step, 0);
+ } else {
+ current_step++;
+ }
+ parallel_enabled = default_parallel;
+
+ tweeners.resize(current_step + 1);
+ tweeners.write[current_step].push_back(p_tweener);
+}
+
+void SceneTreeTween::stop() {
+ started = false;
+ running = false;
+ dead = false;
+ total_time = 0;
+}
+
+void SceneTreeTween::pause() {
+ running = false;
+}
+
+void SceneTreeTween::play() {
+ ERR_FAIL_COND_MSG(!valid, "SceneTreeTween invalid. Either finished or created outside scene tree.");
+ ERR_FAIL_COND_MSG(dead, "Can't play finished SceneTreeTween, use stop() first to reset its state.");
+ running = true;
+}
+
+void SceneTreeTween::kill() {
+ running = false; // For the sake of is_running().
+ dead = true;
+}
+
+bool SceneTreeTween::is_running() const {
+ return running;
+}
+
+bool SceneTreeTween::is_valid() const {
+ return valid;
+}
+
+void SceneTreeTween::clear() {
+ valid = false;
+
+ for (int i = 0; i < tweeners.size(); i++) {
+ List][> &step = tweeners.write[i];
+ for (int j = 0; j < step.size(); j++) {
+ Ref &tweener = step[j];
+ tweener->clear_tween();
+ }
+ }
+ tweeners.clear();
+}
+
+Ref SceneTreeTween::bind_node(Node *p_node) {
+ ERR_FAIL_NULL_V(p_node, this);
+
+ bound_node = p_node->get_instance_id();
+ is_bound = true;
+ return this;
+}
+
+Ref SceneTreeTween::set_process_mode(Tween::TweenProcessMode p_mode) {
+ process_mode = p_mode;
+ return this;
+}
+
+Ref SceneTreeTween::set_pause_mode(TweenPauseMode p_mode) {
+ pause_mode = p_mode;
+ return this;
+}
+
+Ref SceneTreeTween::set_parallel(bool p_parallel) {
+ default_parallel = p_parallel;
+ parallel_enabled = p_parallel;
+ return this;
+}
+
+Ref SceneTreeTween::set_loops(int p_loops) {
+ loops = p_loops;
+ return this;
+}
+
+Ref SceneTreeTween::set_speed_scale(float p_speed) {
+ speed_scale = p_speed;
+ return this;
+}
+
+Ref SceneTreeTween::set_trans(Tween::TransitionType p_trans) {
+ default_transition = p_trans;
+ return this;
+}
+
+Ref SceneTreeTween::set_ease(Tween::EaseType p_ease) {
+ default_ease = p_ease;
+ return this;
+}
+
+Tween::TweenProcessMode SceneTreeTween::get_process_mode() const {
+ return process_mode;
+}
+
+SceneTreeTween::TweenPauseMode SceneTreeTween::get_pause_mode() const {
+ return pause_mode;
+}
+
+Tween::TransitionType SceneTreeTween::get_trans() const {
+ return default_transition;
+}
+
+Tween::EaseType SceneTreeTween::get_ease() const {
+ return default_ease;
+}
+
+Ref SceneTreeTween::parallel() {
+ parallel_enabled = true;
+ return this;
+}
+
+Ref SceneTreeTween::chain() {
+ parallel_enabled = false;
+ return this;
+}
+
+bool SceneTreeTween::custom_step(float p_delta) {
+ bool r = running;
+ running = true;
+ bool ret = step(p_delta);
+ running = running && r; // Running might turn false when SceneTreeTween finished;
+ return ret;
+}
+
+bool SceneTreeTween::step(float p_delta) {
+ if (dead) {
+ return false;
+ }
+
+ if (!running) {
+ return true;
+ }
+
+ if (is_bound) {
+ Node *bound_node = get_bound_node();
+ if (bound_node) {
+ if (!bound_node->is_inside_tree()) {
+ return true;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ if (!started) {
+ ERR_FAIL_COND_V_MSG(tweeners.empty(), false, "SceneTreeTween started, but has no Tweeners.");
+ current_step = 0;
+ loops_done = 0;
+ total_time = 0;
+ start_tweeners();
+ started = true;
+ }
+
+ float rem_delta = p_delta * speed_scale;
+ bool step_active = false;
+ total_time += rem_delta;
+
+#ifdef DEBUG_ENABLED
+ float initial_delta = rem_delta;
+ bool potential_infinite = false;
+#endif
+
+ while (rem_delta > 0 && running) {
+ float step_delta = rem_delta;
+ step_active = false;
+
+ List][> &step = tweeners.write[current_step];
+ for (int i = 0; i < step.size(); i++) {
+ Ref &tweener = step[i];
+
+ // Modified inside Tweener.step().
+ float temp_delta = rem_delta;
+ // Turns to true if any Tweener returns true (i.e. is still not finished).
+ step_active = tweener->step(temp_delta) || step_active;
+ step_delta = MIN(temp_delta, step_delta);
+ }
+
+ rem_delta = step_delta;
+
+ if (!step_active) {
+ emit_signal(SceneStringNames::get_singleton()->step_finished, current_step);
+ current_step++;
+
+ if (current_step == tweeners.size()) {
+ loops_done++;
+ if (loops_done == loops) {
+ running = false;
+ dead = true;
+ emit_signal(SceneStringNames::get_singleton()->finished);
+ } else {
+ emit_signal(SceneStringNames::get_singleton()->loop_finished, loops_done);
+ current_step = 0;
+ start_tweeners();
+#ifdef DEBUG_ENABLED
+ if (loops <= 0 && Math::is_equal_approx(rem_delta, initial_delta)) {
+ if (!potential_infinite) {
+ potential_infinite = true;
+ } else {
+ // Looped twice without using any time, this is 100% certain infinite loop.
+ ERR_FAIL_V_MSG(false, "Infinite loop detected. Check set_loops() description for more info.");
+ }
+ }
+#endif
+ }
+ } else {
+ start_tweeners();
+ }
+ }
+ }
+
+ return true;
+}
+
+bool SceneTreeTween::can_process(bool p_tree_paused) const {
+ if (is_bound && pause_mode == TWEEN_PAUSE_BOUND) {
+ Node *bound_node = get_bound_node();
+ if (bound_node) {
+ return bound_node->is_inside_tree() && bound_node->can_process();
+ }
+ }
+
+ return !p_tree_paused || pause_mode == TWEEN_PAUSE_PROCESS;
+}
+
+Node *SceneTreeTween::get_bound_node() const {
+ if (is_bound) {
+ return Object::cast_to(ObjectDB::get_instance(bound_node));
+ } else {
+ return nullptr;
+ }
+}
+
+float SceneTreeTween::get_total_time() const {
+ return total_time;
+}
+
+Variant SceneTreeTween::interpolate_variant(Variant p_initial_val, Variant p_delta_val, float p_time, float p_duration, Tween::TransitionType p_trans, Tween::EaseType p_ease) const {
+ ERR_FAIL_INDEX_V(p_trans, Tween::TRANS_COUNT, Variant());
+ ERR_FAIL_INDEX_V(p_ease, Tween::EASE_COUNT, Variant());
+
+// Helper macro to run equation on sub-elements of the value (e.g. x and y of Vector2).
+#define APPLY_EQUATION(element) \
+ r.element = Tween::run_equation(p_trans, p_ease, p_time, i.element, d.element, p_duration);
+
+ switch (p_initial_val.get_type()) {
+ case Variant::BOOL: {
+ return (Tween::run_equation(p_trans, p_ease, p_time, p_initial_val, p_delta_val, p_duration)) >= 0.5;
+ }
+
+ case Variant::INT: {
+ return (int)Tween::run_equation(p_trans, p_ease, p_time, (int)p_initial_val, (int)p_delta_val, p_duration);
+ }
+
+ case Variant::REAL: {
+ return Tween::run_equation(p_trans, p_ease, p_time, (real_t)p_initial_val, (real_t)p_delta_val, p_duration);
+ }
+
+ case Variant::VECTOR2: {
+ Vector2 i = p_initial_val;
+ Vector2 d = p_delta_val;
+ Vector2 r;
+
+ APPLY_EQUATION(x);
+ APPLY_EQUATION(y);
+ return r;
+ }
+
+ case Variant::RECT2: {
+ Rect2 i = p_initial_val;
+ Rect2 d = p_delta_val;
+ Rect2 r;
+
+ APPLY_EQUATION(position.x);
+ APPLY_EQUATION(position.y);
+ APPLY_EQUATION(size.x);
+ APPLY_EQUATION(size.y);
+ return r;
+ }
+
+ case Variant::VECTOR3: {
+ Vector3 i = p_initial_val;
+ Vector3 d = p_delta_val;
+ Vector3 r;
+
+ APPLY_EQUATION(x);
+ APPLY_EQUATION(y);
+ APPLY_EQUATION(z);
+ return r;
+ }
+
+ case Variant::TRANSFORM2D: {
+ Transform2D i = p_initial_val;
+ Transform2D d = p_delta_val;
+ Transform2D r;
+
+ APPLY_EQUATION(elements[0][0]);
+ APPLY_EQUATION(elements[0][1]);
+ APPLY_EQUATION(elements[1][0]);
+ APPLY_EQUATION(elements[1][1]);
+ APPLY_EQUATION(elements[2][0]);
+ APPLY_EQUATION(elements[2][1]);
+ return r;
+ }
+
+ case Variant::QUAT: {
+ Quat i = p_initial_val;
+ Quat d = p_delta_val;
+ Quat r;
+
+ APPLY_EQUATION(x);
+ APPLY_EQUATION(y);
+ APPLY_EQUATION(z);
+ APPLY_EQUATION(w);
+ return r;
+ }
+
+ case Variant::AABB: {
+ AABB i = p_initial_val;
+ AABB d = p_delta_val;
+ AABB r;
+
+ APPLY_EQUATION(position.x);
+ APPLY_EQUATION(position.y);
+ APPLY_EQUATION(position.z);
+ APPLY_EQUATION(size.x);
+ APPLY_EQUATION(size.y);
+ APPLY_EQUATION(size.z);
+ return r;
+ }
+
+ case Variant::BASIS: {
+ Basis i = p_initial_val;
+ Basis d = p_delta_val;
+ Basis r;
+
+ APPLY_EQUATION(elements[0][0]);
+ APPLY_EQUATION(elements[0][1]);
+ APPLY_EQUATION(elements[0][2]);
+ APPLY_EQUATION(elements[1][0]);
+ APPLY_EQUATION(elements[1][1]);
+ APPLY_EQUATION(elements[1][2]);
+ APPLY_EQUATION(elements[2][0]);
+ APPLY_EQUATION(elements[2][1]);
+ APPLY_EQUATION(elements[2][2]);
+ return r;
+ }
+
+ case Variant::TRANSFORM: {
+ Transform i = p_initial_val;
+ Transform d = p_delta_val;
+ Transform r;
+
+ APPLY_EQUATION(basis.elements[0][0]);
+ APPLY_EQUATION(basis.elements[0][1]);
+ APPLY_EQUATION(basis.elements[0][2]);
+ APPLY_EQUATION(basis.elements[1][0]);
+ APPLY_EQUATION(basis.elements[1][1]);
+ APPLY_EQUATION(basis.elements[1][2]);
+ APPLY_EQUATION(basis.elements[2][0]);
+ APPLY_EQUATION(basis.elements[2][1]);
+ APPLY_EQUATION(basis.elements[2][2]);
+ APPLY_EQUATION(origin.x);
+ APPLY_EQUATION(origin.y);
+ APPLY_EQUATION(origin.z);
+ return r;
+ }
+
+ case Variant::COLOR: {
+ Color i = p_initial_val;
+ Color d = p_delta_val;
+ Color r;
+
+ APPLY_EQUATION(r);
+ APPLY_EQUATION(g);
+ APPLY_EQUATION(b);
+ APPLY_EQUATION(a);
+ return r;
+ }
+
+ default: {
+ return p_initial_val;
+ }
+ };
+#undef APPLY_EQUATION
+}
+
+Variant SceneTreeTween::calculate_delta_value(Variant p_intial_val, Variant p_final_val) {
+ ERR_FAIL_COND_V_MSG(p_intial_val.get_type() != p_final_val.get_type(), p_intial_val, "Type mismatch between initial and final value: " + Variant::get_type_name(p_intial_val.get_type()) + " and " + Variant::get_type_name(p_final_val.get_type()));
+
+ switch (p_intial_val.get_type()) {
+ case Variant::BOOL: {
+ return (int)p_final_val - (int)p_intial_val;
+ }
+
+ case Variant::RECT2: {
+ Rect2 i = p_intial_val;
+ Rect2 f = p_final_val;
+ return Rect2(f.position - i.position, f.size - i.size);
+ }
+
+ case Variant::TRANSFORM2D: {
+ Transform2D i = p_intial_val;
+ Transform2D f = p_final_val;
+ return Transform2D(f.elements[0][0] - i.elements[0][0],
+ f.elements[0][1] - i.elements[0][1],
+ f.elements[1][0] - i.elements[1][0],
+ f.elements[1][1] - i.elements[1][1],
+ f.elements[2][0] - i.elements[2][0],
+ f.elements[2][1] - i.elements[2][1]);
+ }
+
+ case Variant::AABB: {
+ AABB i = p_intial_val;
+ AABB f = p_final_val;
+ return AABB(f.position - i.position, f.size - i.size);
+ }
+
+ case Variant::BASIS: {
+ Basis i = p_intial_val;
+ Basis f = p_final_val;
+ return Basis(f.elements[0][0] - i.elements[0][0],
+ f.elements[0][1] - i.elements[0][1],
+ f.elements[0][2] - i.elements[0][2],
+ f.elements[1][0] - i.elements[1][0],
+ f.elements[1][1] - i.elements[1][1],
+ f.elements[1][2] - i.elements[1][2],
+ f.elements[2][0] - i.elements[2][0],
+ f.elements[2][1] - i.elements[2][1],
+ f.elements[2][2] - i.elements[2][2]);
+ }
+
+ case Variant::TRANSFORM: {
+ Transform i = p_intial_val;
+ Transform f = p_final_val;
+ return Transform(f.basis.elements[0][0] - i.basis.elements[0][0],
+ f.basis.elements[0][1] - i.basis.elements[0][1],
+ f.basis.elements[0][2] - i.basis.elements[0][2],
+ f.basis.elements[1][0] - i.basis.elements[1][0],
+ f.basis.elements[1][1] - i.basis.elements[1][1],
+ f.basis.elements[1][2] - i.basis.elements[1][2],
+ f.basis.elements[2][0] - i.basis.elements[2][0],
+ f.basis.elements[2][1] - i.basis.elements[2][1],
+ f.basis.elements[2][2] - i.basis.elements[2][2],
+ f.origin.x - i.origin.x,
+ f.origin.y - i.origin.y,
+ f.origin.z - i.origin.z);
+ }
+
+ default: {
+ return Variant::evaluate(Variant::OP_SUBTRACT, p_final_val, p_intial_val);
+ }
+ };
+}
+
+void SceneTreeTween::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("tween_property", "object", "property", "final_val", "duration"), &SceneTreeTween::tween_property);
+ ClassDB::bind_method(D_METHOD("tween_interval", "time"), &SceneTreeTween::tween_interval);
+ ClassDB::bind_method(D_METHOD("tween_callback", "object", "method", "binds"), &SceneTreeTween::tween_callback, DEFVAL(Array()));
+ ClassDB::bind_method(D_METHOD("tween_method", "object", "method", "from", "to", "duration", "binds"), &SceneTreeTween::tween_method, DEFVAL(Array()));
+
+ ClassDB::bind_method(D_METHOD("custom_step", "delta"), &SceneTreeTween::custom_step);
+ ClassDB::bind_method(D_METHOD("stop"), &SceneTreeTween::stop);
+ ClassDB::bind_method(D_METHOD("pause"), &SceneTreeTween::pause);
+ ClassDB::bind_method(D_METHOD("play"), &SceneTreeTween::play);
+ ClassDB::bind_method(D_METHOD("kill"), &SceneTreeTween::kill);
+ ClassDB::bind_method(D_METHOD("get_total_elapsed_time"), &SceneTreeTween::get_total_time);
+
+ ClassDB::bind_method(D_METHOD("is_running"), &SceneTreeTween::is_running);
+ ClassDB::bind_method(D_METHOD("is_valid"), &SceneTreeTween::is_valid);
+ ClassDB::bind_method(D_METHOD("bind_node", "node"), &SceneTreeTween::bind_node);
+ ClassDB::bind_method(D_METHOD("set_process_mode", "mode"), &SceneTreeTween::set_process_mode);
+ ClassDB::bind_method(D_METHOD("set_pause_mode", "mode"), &SceneTreeTween::set_pause_mode);
+
+ ClassDB::bind_method(D_METHOD("set_parallel", "parallel"), &SceneTreeTween::set_parallel, DEFVAL(true));
+ ClassDB::bind_method(D_METHOD("set_loops", "loops"), &SceneTreeTween::set_loops, DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("set_speed_scale", "speed"), &SceneTreeTween::set_speed_scale);
+ ClassDB::bind_method(D_METHOD("set_trans", "trans"), &SceneTreeTween::set_trans);
+ ClassDB::bind_method(D_METHOD("set_ease", "ease"), &SceneTreeTween::set_ease);
+
+ ClassDB::bind_method(D_METHOD("parallel"), &SceneTreeTween::parallel);
+ ClassDB::bind_method(D_METHOD("chain"), &SceneTreeTween::chain);
+
+ ClassDB::bind_method(D_METHOD("interpolate_value", "initial_value", "delta_value", "elapsed_time", "duration", "trans_type", "ease_type"), &SceneTreeTween::interpolate_variant);
+
+ ADD_SIGNAL(MethodInfo("step_finished", PropertyInfo(Variant::INT, "idx")));
+ ADD_SIGNAL(MethodInfo("loop_finished", PropertyInfo(Variant::INT, "loop_count")));
+ ADD_SIGNAL(MethodInfo("finished"));
+
+ BIND_ENUM_CONSTANT(TWEEN_PAUSE_BOUND);
+ BIND_ENUM_CONSTANT(TWEEN_PAUSE_STOP);
+ BIND_ENUM_CONSTANT(TWEEN_PAUSE_PROCESS);
+}
+
+SceneTreeTween::SceneTreeTween(bool p_valid) {
+ valid = p_valid;
+}
+
+Ref PropertyTweener::from(Variant p_value) {
+ initial_val = p_value;
+ do_continue = false;
+ return this;
+}
+
+Ref PropertyTweener::from_current() {
+ do_continue = false;
+ return this;
+}
+
+Ref PropertyTweener::as_relative() {
+ relative = true;
+ return this;
+}
+
+Ref PropertyTweener::set_trans(Tween::TransitionType p_trans) {
+ trans_type = p_trans;
+ return this;
+}
+
+Ref PropertyTweener::set_ease(Tween::EaseType p_ease) {
+ ease_type = p_ease;
+ return this;
+}
+
+Ref PropertyTweener::set_delay(float p_delay) {
+ delay = p_delay;
+ return this;
+}
+
+void PropertyTweener::start() {
+ elapsed_time = 0;
+ finished = false;
+
+ Object *target_instance = ObjectDB::get_instance(target);
+ if (!target_instance) {
+ WARN_PRINT("Target object freed before starting, aborting Tweener.");
+ return;
+ }
+
+ if (do_continue) {
+ initial_val = target_instance->get_indexed(property);
+ }
+
+ if (relative) {
+ final_val = Variant::evaluate(Variant::Operator::OP_ADD, initial_val, base_final_val);
+ }
+
+ delta_val = tween->calculate_delta_value(initial_val, final_val);
+}
+
+bool PropertyTweener::step(float &r_delta) {
+ if (finished) {
+ // This is needed in case there's a parallel Tweener with longer duration.
+ return false;
+ }
+
+ Object *target_instance = ObjectDB::get_instance(target);
+ if (!target_instance) {
+ return false;
+ }
+ elapsed_time += r_delta;
+
+ if (elapsed_time < delay) {
+ r_delta = 0;
+ return true;
+ }
+
+ float time = MIN(elapsed_time - delay, duration);
+ if (time < duration) {
+ target_instance->set_indexed(property, tween->interpolate_variant(initial_val, delta_val, time, duration, trans_type, ease_type));
+ r_delta = 0;
+ return true;
+ } else {
+ target_instance->set_indexed(property, final_val);
+ finished = true;
+ r_delta = elapsed_time - delay - duration;
+ emit_signal(SceneStringNames::get_singleton()->finished);
+ return false;
+ }
+}
+
+void PropertyTweener::set_tween(Ref p_tween) {
+ tween = p_tween;
+ if (trans_type == Tween::TRANS_COUNT) {
+ trans_type = tween->get_trans();
+ }
+ if (ease_type == Tween::EASE_COUNT) {
+ ease_type = tween->get_ease();
+ }
+}
+
+void PropertyTweener::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("from", "value"), &PropertyTweener::from);
+ ClassDB::bind_method(D_METHOD("from_current"), &PropertyTweener::from_current);
+ ClassDB::bind_method(D_METHOD("as_relative"), &PropertyTweener::as_relative);
+ ClassDB::bind_method(D_METHOD("set_trans", "trans"), &PropertyTweener::set_trans);
+ ClassDB::bind_method(D_METHOD("set_ease", "ease"), &PropertyTweener::set_ease);
+ ClassDB::bind_method(D_METHOD("set_delay", "delay"), &PropertyTweener::set_delay);
+}
+
+PropertyTweener::PropertyTweener(Object *p_target, NodePath p_property, Variant p_to, float p_duration) {
+ target = p_target->get_instance_id();
+ property = p_property.get_as_property_path().get_subnames();
+ initial_val = p_target->get_indexed(property);
+ base_final_val = p_to;
+ final_val = base_final_val;
+ duration = p_duration;
+}
+
+PropertyTweener::PropertyTweener() {
+ ERR_FAIL_MSG("Can't create empty PropertyTweener. Use get_tree().tween_property() or tween_property() instead.");
+}
+
+void IntervalTweener::start() {
+ elapsed_time = 0;
+ finished = false;
+}
+
+bool IntervalTweener::step(float &r_delta) {
+ if (finished) {
+ return false;
+ }
+
+ elapsed_time += r_delta;
+
+ if (elapsed_time < duration) {
+ r_delta = 0;
+ return true;
+ } else {
+ finished = true;
+ r_delta = elapsed_time - duration;
+ emit_signal(SceneStringNames::get_singleton()->finished);
+ return false;
+ }
+}
+
+IntervalTweener::IntervalTweener(float p_time) {
+ duration = p_time;
+}
+
+IntervalTweener::IntervalTweener() {
+ ERR_FAIL_MSG("Can't create empty IntervalTweener. Use get_tree().tween_property() or tween_property() instead.");
+}
+
+Ref CallbackTweener::set_delay(float p_delay) {
+ delay = p_delay;
+ return this;
+}
+
+void CallbackTweener::start() {
+ elapsed_time = 0;
+ finished = false;
+}
+
+bool CallbackTweener::step(float &r_delta) {
+ if (finished) {
+ return false;
+ }
+
+ Object *target_instance = ObjectDB::get_instance(target);
+ if (!target_instance) {
+ return false;
+ }
+
+ elapsed_time += r_delta;
+ if (elapsed_time >= delay) {
+ Vector bind_mem;
+
+ if (binds.size()) {
+ bind_mem.resize(binds.size());
+
+ for (int i = 0; i < binds.size(); i++) {
+ bind_mem.write[i] = &binds[i];
+ }
+ }
+
+ const Variant **args = (const Variant **)bind_mem.ptr();
+ int argc = bind_mem.size();
+
+ Variant::CallError ce;
+ target_instance->call(method, args, argc, ce);
+ if (ce.error != Variant::CallError::CALL_OK) {
+ ERR_FAIL_V_MSG(false, "Error calling method from CallbackTweener: " + Variant::get_call_error_text(target_instance, method, args, argc, ce));
+ }
+
+ finished = true;
+ r_delta = elapsed_time - delay;
+ emit_signal(SceneStringNames::get_singleton()->finished);
+ return false;
+ }
+
+ r_delta = 0;
+ return true;
+}
+
+void CallbackTweener::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_delay", "delay"), &CallbackTweener::set_delay);
+}
+
+CallbackTweener::CallbackTweener(Object *p_target, StringName p_method, const Vector &p_binds) {
+ target = p_target->get_instance_id();
+ method = p_method;
+ binds = p_binds;
+}
+
+CallbackTweener::CallbackTweener() {
+ ERR_FAIL_MSG("Can't create empty CallbackTweener. Use get_tree().tween_callback() instead.");
+}
+
+Ref MethodTweener::set_delay(float p_delay) {
+ delay = p_delay;
+ return this;
+}
+
+Ref MethodTweener::set_trans(Tween::TransitionType p_trans) {
+ trans_type = p_trans;
+ return this;
+}
+
+Ref MethodTweener::set_ease(Tween::EaseType p_ease) {
+ ease_type = p_ease;
+ return this;
+}
+
+void MethodTweener::start() {
+ elapsed_time = 0;
+ finished = false;
+}
+
+bool MethodTweener::step(float &r_delta) {
+ if (finished) {
+ return false;
+ }
+
+ Object *target_instance = ObjectDB::get_instance(target);
+ if (!target_instance) {
+ return false;
+ }
+
+ elapsed_time += r_delta;
+
+ if (elapsed_time < delay) {
+ r_delta = 0;
+ return true;
+ }
+
+ Variant current_val;
+ float time = MIN(elapsed_time - delay, duration);
+ if (time < duration) {
+ current_val = tween->interpolate_variant(initial_val, delta_val, time, duration, trans_type, ease_type);
+ } else {
+ current_val = final_val;
+ }
+
+ Vector bind_mem;
+
+ if (binds.empty()) {
+ bind_mem.push_back(¤t_val);
+ } else {
+ bind_mem.resize(1 + binds.size());
+
+ bind_mem.write[0] = ¤t_val;
+ for (int i = 0; i < binds.size(); i++) {
+ bind_mem.write[1 + i] = &binds[i];
+ }
+ }
+
+ const Variant **args = (const Variant **)bind_mem.ptr();
+ int argc = bind_mem.size();
+
+ Variant::CallError ce;
+ target_instance->call(method, args, argc, ce);
+ if (ce.error != Variant::CallError::CALL_OK) {
+ ERR_FAIL_V_MSG(false, "Error calling method from MethodTweener: " + Variant::get_call_error_text(target_instance, method, args, argc, ce));
+ }
+
+ if (time < duration) {
+ r_delta = 0;
+ return true;
+ } else {
+ finished = true;
+ r_delta = elapsed_time - delay - duration;
+ emit_signal(SceneStringNames::get_singleton()->finished);
+ return false;
+ }
+}
+
+void MethodTweener::set_tween(Ref p_tween) {
+ tween = p_tween;
+ if (trans_type == Tween::TRANS_COUNT) {
+ trans_type = tween->get_trans();
+ }
+ if (ease_type == Tween::EASE_COUNT) {
+ ease_type = tween->get_ease();
+ }
+}
+
+void MethodTweener::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_delay", "delay"), &MethodTweener::set_delay);
+ ClassDB::bind_method(D_METHOD("set_trans", "trans"), &MethodTweener::set_trans);
+ ClassDB::bind_method(D_METHOD("set_ease", "ease"), &MethodTweener::set_ease);
+}
+
+MethodTweener::MethodTweener(Object *p_target, StringName p_method, Variant p_from, Variant p_to, float p_duration, const Vector &p_binds) {
+ target = p_target->get_instance_id();
+ method = p_method;
+ binds = p_binds;
+ initial_val = p_from;
+ delta_val = tween->calculate_delta_value(p_from, p_to);
+ final_val = p_to;
+ duration = p_duration;
+}
+
+MethodTweener::MethodTweener() {
+ ERR_FAIL_MSG("Can't create empty MethodTweener. Use get_tree().tween_method() instead.");
+}
diff --git a/scene/animation/scene_tree_tween.h b/scene/animation/scene_tree_tween.h
new file mode 100644
index 000000000..cf57d7b65
--- /dev/null
+++ b/scene/animation/scene_tree_tween.h
@@ -0,0 +1,256 @@
+/*************************************************************************/
+/* scene_tree_tween.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef SCENE_TREE_TWEEN_H
+#define SCENE_TREE_TWEEN_H
+
+#include "core/reference.h"
+#include "scene/animation/tween.h"
+
+class SceneTreeTween;
+
+class Tweener : public Reference {
+ GDCLASS(Tweener, Reference);
+
+public:
+ virtual void set_tween(Ref p_tween);
+ virtual void start() = 0;
+ virtual bool step(float &r_delta) = 0;
+ void clear_tween();
+
+protected:
+ static void _bind_methods();
+ Ref tween;
+ float elapsed_time = 0;
+ bool finished = false;
+};
+
+class PropertyTweener;
+class IntervalTweener;
+class CallbackTweener;
+class MethodTweener;
+
+class SceneTreeTween : public Reference {
+ GDCLASS(SceneTreeTween, Reference);
+
+public:
+ enum TweenPauseMode {
+ TWEEN_PAUSE_BOUND,
+ TWEEN_PAUSE_STOP,
+ TWEEN_PAUSE_PROCESS,
+ };
+
+private:
+ Tween::TweenProcessMode process_mode = Tween::TWEEN_PROCESS_IDLE;
+ TweenPauseMode pause_mode = TweenPauseMode::TWEEN_PAUSE_BOUND;
+ Tween::TransitionType default_transition = Tween::TRANS_LINEAR;
+ Tween::EaseType default_ease = Tween::EASE_IN_OUT;
+ ObjectID bound_node;
+
+ Vector]>> tweeners;
+ float total_time = 0;
+ int current_step = -1;
+ int loops = 1;
+ int loops_done = 0;
+ float speed_scale = 1;
+
+ bool is_bound = false;
+ bool started = false;
+ bool running = true;
+ bool dead = false;
+ bool valid = false;
+ bool default_parallel = false;
+ bool parallel_enabled = false;
+#ifdef DEBUG_ENABLED
+ bool is_infinite = false;
+#endif
+
+ void start_tweeners();
+
+protected:
+ static void _bind_methods();
+
+public:
+ Ref tween_property(Object *p_target, NodePath p_property, Variant p_to, float p_duration);
+ Ref tween_interval(float p_time);
+ Ref tween_callback(Object *p_target, StringName p_method, const Vector &p_binds = Vector());
+ Ref tween_method(Object *p_target, StringName p_method, Variant p_from, Variant p_to, float p_duration, const Vector &p_binds = Vector());
+ void append(Ref p_tweener);
+
+ bool custom_step(float p_delta);
+ void stop();
+ void pause();
+ void play();
+ void kill();
+
+ bool is_running() const;
+ bool is_valid() const;
+ void clear();
+
+ Tween::TweenProcessMode get_process_mode() const;
+ TweenPauseMode get_pause_mode() const;
+ Tween::TransitionType get_trans() const;
+ Tween::EaseType get_ease() const;
+
+ Ref bind_node(Node *p_node);
+ Ref set_process_mode(Tween::TweenProcessMode p_mode);
+ Ref set_pause_mode(TweenPauseMode p_mode);
+ Ref set_parallel(bool p_parallel);
+ Ref set_loops(int p_loops);
+ Ref set_speed_scale(float p_speed);
+ Ref set_trans(Tween::TransitionType p_trans);
+ Ref set_ease(Tween::EaseType p_ease);
+
+ Ref parallel();
+ Ref chain();
+
+ Variant interpolate_variant(Variant p_initial_val, Variant p_delta_val, float p_time, float p_duration, Tween::TransitionType p_trans, Tween::EaseType p_ease) const;
+ Variant calculate_delta_value(Variant p_intial_val, Variant p_final_val);
+
+ bool step(float p_delta);
+ bool can_process(bool p_tree_paused) const;
+ Node *get_bound_node() const;
+ float get_total_time() const;
+
+ SceneTreeTween() = default;
+ SceneTreeTween(bool p_valid);
+};
+
+VARIANT_ENUM_CAST(SceneTreeTween::TweenPauseMode);
+
+class PropertyTweener : public Tweener {
+ GDCLASS(PropertyTweener, Tweener);
+
+public:
+ Ref from(Variant p_value);
+ Ref from_current();
+ Ref as_relative();
+ Ref set_trans(Tween::TransitionType p_trans);
+ Ref set_ease(Tween::EaseType p_ease);
+ Ref set_delay(float p_delay);
+
+ virtual void set_tween(Ref p_tween);
+ virtual void start();
+ virtual bool step(float &r_delta);
+
+ PropertyTweener(Object *p_target, NodePath p_property, Variant p_to, float p_duration);
+ PropertyTweener();
+
+protected:
+ static void _bind_methods();
+
+private:
+ ObjectID target;
+ Vector property;
+ Variant initial_val;
+ Variant base_final_val;
+ Variant final_val;
+ Variant delta_val;
+
+ float duration = 0;
+ Tween::TransitionType trans_type = Tween::TRANS_COUNT; // This is set inside set_tween();
+ Tween::EaseType ease_type = Tween::EASE_COUNT;
+
+ float delay = 0;
+ bool do_continue = true;
+ bool relative = false;
+};
+
+class IntervalTweener : public Tweener {
+ GDCLASS(IntervalTweener, Tweener);
+
+public:
+ virtual void start();
+ virtual bool step(float &r_delta);
+
+ IntervalTweener(float p_time);
+ IntervalTweener();
+
+private:
+ float duration = 0;
+};
+
+class CallbackTweener : public Tweener {
+ GDCLASS(CallbackTweener, Tweener);
+
+public:
+ Ref set_delay(float p_delay);
+
+ virtual void start();
+ virtual bool step(float &r_delta);
+
+ CallbackTweener(Object *p_target, StringName p_method, const Vector &p_binds);
+ CallbackTweener();
+
+protected:
+ static void _bind_methods();
+
+private:
+ ObjectID target;
+ StringName method;
+ Vector binds;
+ int args = 0;
+ float delay = 0;
+};
+
+class MethodTweener : public Tweener {
+ GDCLASS(MethodTweener, Tweener);
+
+public:
+ Ref set_trans(Tween::TransitionType p_trans);
+ Ref set_ease(Tween::EaseType p_ease);
+ Ref set_delay(float p_delay);
+
+ virtual void set_tween(Ref p_tween);
+ virtual void start();
+ virtual bool step(float &r_delta);
+
+ MethodTweener(Object *p_target, StringName p_method, Variant p_from, Variant p_to, float p_duration, const Vector &p_binds);
+ MethodTweener();
+
+protected:
+ static void _bind_methods();
+
+private:
+ float duration = 0;
+ float delay = 0;
+ Tween::TransitionType trans_type = Tween::TRANS_COUNT;
+ Tween::EaseType ease_type = Tween::EASE_COUNT;
+
+ Ref tween;
+ Variant initial_val;
+ Variant delta_val;
+ Variant final_val;
+ ObjectID target;
+ StringName method;
+ Vector binds;
+};
+
+#endif // SCENE_TREE_TWEEN_H
diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp
index cdd10b6e1..b911d6bfe 100644
--- a/scene/animation/tween.cpp
+++ b/scene/animation/tween.cpp
@@ -31,6 +31,32 @@
#include "tween.h"
#include "core/method_bind_ext.gen.inc"
+#include "scene/animation/easing_equations.h"
+
+Tween::interpolater Tween::interpolaters[Tween::TRANS_COUNT][Tween::EASE_COUNT] = {
+ { &linear::in, &linear::in, &linear::in, &linear::in }, // Linear is the same for each easing.
+ { &sine::in, &sine::out, &sine::in_out, &sine::out_in },
+ { &quint::in, &quint::out, &quint::in_out, &quint::out_in },
+ { &quart::in, &quart::out, &quart::in_out, &quart::out_in },
+ { &quad::in, &quad::out, &quad::in_out, &quad::out_in },
+ { &expo::in, &expo::out, &expo::in_out, &expo::out_in },
+ { &elastic::in, &elastic::out, &elastic::in_out, &elastic::out_in },
+ { &cubic::in, &cubic::out, &cubic::in_out, &cubic::out_in },
+ { &circ::in, &circ::out, &circ::in_out, &circ::out_in },
+ { &bounce::in, &bounce::out, &bounce::in_out, &bounce::out_in },
+ { &back::in, &back::out, &back::in_out, &back::out_in },
+};
+
+real_t Tween::run_equation(Tween::TransitionType p_trans_type, Tween::EaseType p_ease_type, real_t p_time, real_t p_initial, real_t p_delta, real_t p_duration) {
+ if (p_duration == 0) {
+ // Special case to avoid dividing by 0 in equations.
+ return p_initial + p_delta;
+ }
+
+ interpolater func = interpolaters[p_trans_type][p_ease_type];
+ ERR_FAIL_NULL_V(func, p_initial);
+ return func(p_time, p_initial, p_delta, p_duration);
+}
void Tween::_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) {
// Add a new pending command and reference it
@@ -444,23 +470,23 @@ Variant Tween::_run_equation(InterpolateData &p_data) {
Variant result;
#define APPLY_EQUATION(element) \
- 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);
+ 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);
// What type of data are we interpolating?
switch (initial_val.get_type()) {
case Variant::BOOL:
// Run the boolean specific equation (checking if it is at least 0.5)
- 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;
+ 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;
break;
case Variant::INT:
// Run the integer specific equation
- 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);
+ 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);
break;
case Variant::REAL:
// Run the REAL specific equation
- 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);
+ 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);
break;
case Variant::VECTOR2: {
diff --git a/scene/animation/tween.h b/scene/animation/tween.h
index dc2809644..38a0bc3e2 100644
--- a/scene/animation/tween.h
+++ b/scene/animation/tween.h
@@ -151,6 +151,8 @@ protected:
static void _bind_methods();
public:
+ static real_t run_equation(Tween::TransitionType p_trans_type, Tween::EaseType p_ease_type, real_t p_time, real_t p_initial, real_t p_delta, real_t p_duration);
+
bool is_active() const;
void set_active(bool p_active);
diff --git a/scene/main/node.cpp b/scene/main/node.cpp
index 50d4de708..901d67e37 100644
--- a/scene/main/node.cpp
+++ b/scene/main/node.cpp
@@ -35,6 +35,7 @@
#include "core/message_queue.h"
#include "core/print_string.h"
#include "instance_placeholder.h"
+#include "scene/animation/scene_tree_tween.h"
#include "scene/resources/packed_scene.h"
#include "scene/scene_string_names.h"
#include "viewport.h"
@@ -1894,6 +1895,14 @@ void Node::_propagate_replace_owner(Node *p_owner, Node *p_by_owner) {
int Node::get_index() const {
return data.pos;
}
+
+Ref Node::create_tween() {
+ ERR_FAIL_COND_V_MSG(!data.tree, nullptr, "Can't create SceneTreeTween when not inside scene tree.");
+ Ref tween = get_tree()->create_tween();
+ tween->bind_node(this);
+ return tween;
+}
+
void Node::remove_and_skip() {
ERR_FAIL_COND(!data.parent);
@@ -2957,6 +2966,7 @@ void Node::_bind_methods() {
ClassDB::bind_method(D_METHOD("reset_physics_interpolation"), &Node::reset_physics_interpolation);
ClassDB::bind_method(D_METHOD("get_tree"), &Node::get_tree);
+ ClassDB::bind_method(D_METHOD("create_tween"), &Node::create_tween);
ClassDB::bind_method(D_METHOD("duplicate", "flags"), &Node::duplicate, DEFVAL(DUPLICATE_USE_INSTANCING | DUPLICATE_SIGNALS | DUPLICATE_GROUPS | DUPLICATE_SCRIPTS));
ClassDB::bind_method(D_METHOD("replace_by", "node", "keep_data"), &Node::replace_by, DEFVAL(false));
diff --git a/scene/main/node.h b/scene/main/node.h
index 6b27f7c0e..6a8264dde 100644
--- a/scene/main/node.h
+++ b/scene/main/node.h
@@ -38,6 +38,7 @@
class Viewport;
class SceneState;
+class SceneTreeTween;
class Node : public Object {
GDCLASS(Node, Object);
@@ -343,6 +344,8 @@ public:
void remove_and_skip();
int get_index() const;
+ Ref create_tween();
+
void print_tree();
void print_tree_pretty();
diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp
index 119979146..62008ca25 100644
--- a/scene/main/scene_tree.cpp
+++ b/scene/main/scene_tree.cpp
@@ -41,6 +41,7 @@
#include "main/input_default.h"
#include "node.h"
#include "scene/3d/spatial.h"
+#include "scene/animation/scene_tree_tween.h"
#include "scene/debugger/script_debugger_remote.h"
#include "scene/resources/dynamic_font.h"
#include "scene/resources/material.h"
@@ -565,6 +566,9 @@ bool SceneTree::iteration(float p_time) {
_notify_group_pause("physics_process", Node::NOTIFICATION_PHYSICS_PROCESS);
_flush_ugc();
MessageQueue::get_singleton()->flush(); //small little hack
+
+ process_tweens(p_time, true);
+
flush_transform_notifications();
call_group_flags(GROUP_CALL_REALTIME, "_viewports", "update_worlds");
root_lock--;
@@ -657,6 +661,8 @@ bool SceneTree::idle(float p_time) {
E = N;
}
+ process_tweens(p_time, false);
+
flush_transform_notifications(); //additional transforms after timers update
_call_idle_callbacks();
@@ -700,6 +706,32 @@ bool SceneTree::idle(float p_time) {
return _quit;
}
+void SceneTree::process_tweens(float p_delta, bool p_physics) {
+ // This methods works similarly to how SceneTreeTimers are handled.
+ List[>::Element *L = tweens.back();
+
+ for (List][>::Element *E = tweens.front(); E;) {
+ List][>::Element *N = E->next();
+ // Don't process if paused or process mode doesn't match.
+ if (!E->get()->can_process(pause) || (p_physics == (E->get()->get_process_mode() == Tween::TWEEN_PROCESS_IDLE))) {
+ if (E == L) {
+ break;
+ }
+ E = N;
+ continue;
+ }
+
+ if (!E->get()->step(p_delta)) {
+ E->get()->clear();
+ tweens.erase(E);
+ }
+ if (E == L) {
+ break;
+ }
+ E = N;
+ }
+}
+
void SceneTree::finish() {
_flush_delete_queue();
@@ -1803,6 +1835,23 @@ Ref SceneTree::create_timer(float p_delay_sec, bool p_process_pa
return stt;
}
+Ref SceneTree::create_tween() {
+ Ref tween = memnew(SceneTreeTween(true));
+ tweens.push_back(tween);
+ return tween;
+}
+
+Array SceneTree::get_processed_tweens() {
+ Array ret;
+ ret.resize(tweens.size());
+
+ for (int i = 0; i < tweens.size(); i++) {
+ ret[i] = tweens[i];
+ }
+
+ return ret;
+}
+
void SceneTree::_network_peer_connected(int p_id) {
emit_signal("network_peer_connected", p_id);
}
@@ -1913,6 +1962,8 @@ void SceneTree::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_input_handled"), &SceneTree::is_input_handled);
ClassDB::bind_method(D_METHOD("create_timer", "time_sec", "pause_mode_process"), &SceneTree::create_timer, DEFVAL(true));
+ ClassDB::bind_method(D_METHOD("create_tween"), &SceneTree::create_tween);
+ ClassDB::bind_method(D_METHOD("get_processed_tweens"), &SceneTree::get_processed_tweens);
ClassDB::bind_method(D_METHOD("get_node_count"), &SceneTree::get_node_count);
ClassDB::bind_method(D_METHOD("get_frame"), &SceneTree::get_frame);
diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h
index c8230cc88..bfe44e670 100644
--- a/scene/main/scene_tree.h
+++ b/scene/main/scene_tree.h
@@ -37,6 +37,7 @@
class PackedScene;
class Node;
+class SceneTreeTween;
class Spatial;
class Viewport;
class Material;
@@ -185,6 +186,7 @@ private:
//void _call_group(uint32_t p_call_flags,const StringName& p_group,const StringName& p_function,const Variant& p_arg1,const Variant& p_arg2);
List][> timers;
+ List][> tweens;
///network///
@@ -205,6 +207,7 @@ private:
void node_added(Node *p_node);
void node_removed(Node *p_node);
void node_renamed(Node *p_node);
+ void process_tweens(float p_delta, bool p_physics_frame);
Group *add_to_group(const StringName &p_group, Node *p_node);
void remove_from_group(const StringName &p_group, Node *p_node);
@@ -283,7 +286,9 @@ public:
GROUP_CALL_MULTILEVEL = 8,
};
- _FORCE_INLINE_ Viewport *get_root() const { return root; }
+ _FORCE_INLINE_ Viewport *get_root() const {
+ return root;
+ }
void call_group_flags(uint32_t p_call_flags, const StringName &p_group, const StringName &p_function, VARIANT_ARG_LIST);
void notify_group_flags(uint32_t p_call_flags, const StringName &p_group, int p_notification);
@@ -311,13 +316,19 @@ public:
void set_input_as_handled();
bool is_input_handled();
- _FORCE_INLINE_ float get_physics_process_time() const { return physics_process_time; }
- _FORCE_INLINE_ float get_idle_process_time() const { return idle_process_time; }
+ _FORCE_INLINE_ float get_physics_process_time() const {
+ return physics_process_time;
+ }
+ _FORCE_INLINE_ float get_idle_process_time() const {
+ return idle_process_time;
+ }
#ifdef TOOLS_ENABLED
bool is_node_being_edited(const Node *p_node) const;
#else
- bool is_node_being_edited(const Node *p_node) const { return false; }
+ bool is_node_being_edited(const Node *p_node) const {
+ return false;
+ }
#endif
void set_pause(bool p_enabled);
@@ -331,10 +342,14 @@ public:
bool is_debugging_navigation_hint() const;
#else
void set_debug_collisions_hint(bool p_enabled) {}
- bool is_debugging_collisions_hint() const { return false; }
+ bool is_debugging_collisions_hint() const {
+ return false;
+ }
void set_debug_navigation_hint(bool p_enabled) {}
- bool is_debugging_navigation_hint() const { return false; }
+ bool is_debugging_navigation_hint() const {
+ return false;
+ }
#endif
void set_debug_collisions_color(const Color &p_color);
@@ -354,7 +369,9 @@ public:
Ref get_debug_collision_material();
Ref get_debug_contact_mesh();
- int get_collision_debug_contact_count() { return collision_debug_contacts; }
+ int get_collision_debug_contact_count() {
+ return collision_debug_contacts;
+ }
int64_t get_frame() const;
int64_t get_event_count() const;
@@ -384,11 +401,15 @@ public:
Error reload_current_scene();
Ref create_timer(float p_delay_sec, bool p_process_pause = true);
+ Ref create_tween();
+ Array get_processed_tweens();
//used by Main::start, don't use otherwise
void add_current_scene(Node *p_current);
- static SceneTree *get_singleton() { return singleton; }
+ static SceneTree *get_singleton() {
+ return singleton;
+ }
void drop_files(const Vector &p_files, int p_from_screen = 0);
void global_menu_action(const Variant &p_id, const Variant &p_meta);
diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp
index 403bd1ca9..9b4512aee 100644
--- a/scene/register_scene_types.cpp
+++ b/scene/register_scene_types.cpp
@@ -77,6 +77,7 @@
#include "scene/animation/animation_tree.h"
#include "scene/animation/animation_tree_player.h"
#include "scene/animation/root_motion_view.h"
+#include "scene/animation/scene_tree_tween.h"
#include "scene/animation/tween.h"
#include "scene/audio/audio_stream_player.h"
#include "scene/gui/aspect_ratio_container.h"
@@ -387,6 +388,12 @@ void register_scene_types() {
ClassDB::register_class();
ClassDB::register_class();
ClassDB::register_class();
+ ClassDB::register_class();
+ ClassDB::register_virtual_class();
+ ClassDB::register_class();
+ ClassDB::register_class();
+ ClassDB::register_class();
+ ClassDB::register_class();
ClassDB::register_class();
ClassDB::register_class();
diff --git a/scene/scene_string_names.cpp b/scene/scene_string_names.cpp
index ec716a7eb..932710427 100644
--- a/scene/scene_string_names.cpp
+++ b/scene/scene_string_names.cpp
@@ -59,6 +59,8 @@ SceneStringNames::SceneStringNames() {
sleeping_state_changed = StaticCString::create("sleeping_state_changed");
finished = StaticCString::create("finished");
+ loop_finished = StaticCString::create("loop_finished");
+ step_finished = StaticCString::create("step_finished");
emission_finished = StaticCString::create("emission_finished");
animation_finished = StaticCString::create("animation_finished");
animation_changed = StaticCString::create("animation_changed");
diff --git a/scene/scene_string_names.h b/scene/scene_string_names.h
index a4968d487..f003858cd 100644
--- a/scene/scene_string_names.h
+++ b/scene/scene_string_names.h
@@ -90,6 +90,8 @@ public:
StringName sort_children;
StringName finished;
+ StringName loop_finished;
+ StringName step_finished;
StringName emission_finished;
StringName animation_finished;
StringName animation_changed;
diff --git a/thirdparty/README.md b/thirdparty/README.md
index a018795d9..31125985a 100644
--- a/thirdparty/README.md
+++ b/thirdparty/README.md
@@ -279,10 +279,6 @@ Collection of single-file libraries used in Godot components.
* Upstream: https://sourceforge.net/projects/polyclipping
* Version: 6.4.2 (2017) + Godot changes (added optional exceptions handling)
* License: BSL-1.0
-- `easing_equations.cpp`
- * Upstream: http://robertpenner.com/easing/ via https://github.com/jesusgollonet/ofpennereasing (modified to fit Godot types)
- * Version: git (af72c147c3a74e7e872aa28c7e2abfcced04fdce, 2008) + Godot types and style changes
- * License: BSD-3-Clause
- `fastlz.{c,h}`
* Upstream: https://github.com/ariya/FastLZ
* Version: 0.5.0 (4f20f54d46f5a6dd4fae4def134933369b7602d2, 2020)
diff --git a/thirdparty/misc/easing_equations.cpp b/thirdparty/misc/easing_equations.cpp
deleted file mode 100644
index 52b31d176..000000000
--- a/thirdparty/misc/easing_equations.cpp
+++ /dev/null
@@ -1,323 +0,0 @@
-/**
- * Adapted from Penner Easing equations' C++ port.
- * Source: https://github.com/jesusgollonet/ofpennereasing
- * License: BSD-3-clause
- */
-
-#include "scene/animation/tween.h"
-
-const real_t pi = 3.1415926535898;
-
-///////////////////////////////////////////////////////////////////////////
-// linear
-///////////////////////////////////////////////////////////////////////////
-namespace linear {
-static real_t in(real_t t, real_t b, real_t c, real_t d) {
- return c * t / d + b;
-}
-
-static real_t out(real_t t, real_t b, real_t c, real_t d) {
- return c * t / d + b;
-}
-
-static real_t in_out(real_t t, real_t b, real_t c, real_t d) {
- return c * t / d + b;
-}
-
-static real_t out_in(real_t t, real_t b, real_t c, real_t d) {
- return c * t / d + b;
-}
-}; // namespace linear
-///////////////////////////////////////////////////////////////////////////
-// sine
-///////////////////////////////////////////////////////////////////////////
-namespace sine {
-static real_t in(real_t t, real_t b, real_t c, real_t d) {
- return -c * cos(t / d * (pi / 2)) + c + b;
-}
-
-static real_t out(real_t t, real_t b, real_t c, real_t d) {
- return c * sin(t / d * (pi / 2)) + b;
-}
-
-static real_t in_out(real_t t, real_t b, real_t c, real_t d) {
- return -c / 2 * (cos(pi * t / d) - 1) + b;
-}
-
-static real_t out_in(real_t t, real_t b, real_t c, real_t d) {
- return (t < d / 2) ? out(t * 2, b, c / 2, d) : in((t * 2) - d, b + c / 2, c / 2, d);
-}
-}; // namespace sine
-///////////////////////////////////////////////////////////////////////////
-// quint
-///////////////////////////////////////////////////////////////////////////
-namespace quint {
-static real_t in(real_t t, real_t b, real_t c, real_t d) {
- return c * pow(t / d, 5) + b;
-}
-
-static real_t out(real_t t, real_t b, real_t c, real_t d) {
- return c * (pow(t / d - 1, 5) + 1) + b;
-}
-
-static real_t in_out(real_t t, real_t b, real_t c, real_t d) {
- t = t / d * 2;
- if (t < 1) return c / 2 * pow(t, 5) + b;
- return c / 2 * (pow(t - 2, 5) + 2) + b;
-}
-
-static real_t out_in(real_t t, real_t b, real_t c, real_t d) {
- return (t < d / 2) ? out(t * 2, b, c / 2, d) : in((t * 2) - d, b + c / 2, c / 2, d);
-}
-}; // namespace quint
-///////////////////////////////////////////////////////////////////////////
-// quart
-///////////////////////////////////////////////////////////////////////////
-namespace quart {
-static real_t in(real_t t, real_t b, real_t c, real_t d) {
- return c * pow(t / d, 4) + b;
-}
-
-static real_t out(real_t t, real_t b, real_t c, real_t d) {
- return -c * (pow(t / d - 1, 4) - 1) + b;
-}
-
-static real_t in_out(real_t t, real_t b, real_t c, real_t d) {
- t = t / d * 2;
- if (t < 1) return c / 2 * pow(t, 4) + b;
- return -c / 2 * (pow(t - 2, 4) - 2) + b;
-}
-
-static real_t out_in(real_t t, real_t b, real_t c, real_t d) {
- return (t < d / 2) ? out(t * 2, b, c / 2, d) : in((t * 2) - d, b + c / 2, c / 2, d);
-}
-}; // namespace quart
-///////////////////////////////////////////////////////////////////////////
-// quad
-///////////////////////////////////////////////////////////////////////////
-namespace quad {
-static real_t in(real_t t, real_t b, real_t c, real_t d) {
- return c * pow(t / d, 2) + b;
-}
-
-static real_t out(real_t t, real_t b, real_t c, real_t d) {
- t = t / d;
- return -c * t * (t - 2) + b;
-}
-
-static real_t in_out(real_t t, real_t b, real_t c, real_t d) {
- t = t / d * 2;
- if (t < 1) return c / 2 * pow(t, 2) + b;
- return -c / 2 * ((t - 1) * (t - 3) - 1) + b;
-}
-
-static real_t out_in(real_t t, real_t b, real_t c, real_t d) {
- return (t < d / 2) ? out(t * 2, b, c / 2, d) : in((t * 2) - d, b + c / 2, c / 2, d);
-}
-}; // namespace quad
-///////////////////////////////////////////////////////////////////////////
-// expo
-///////////////////////////////////////////////////////////////////////////
-namespace expo {
-static real_t in(real_t t, real_t b, real_t c, real_t d) {
- if (t == 0) return b;
- return c * pow(2, 10 * (t / d - 1)) + b - c * 0.001;
-}
-
-static real_t out(real_t t, real_t b, real_t c, real_t d) {
- if (t == d) return b + c;
- return c * 1.001 * (-pow(2, -10 * t / d) + 1) + b;
-}
-
-static real_t in_out(real_t t, real_t b, real_t c, real_t d) {
- if (t == 0) return b;
- if (t == d) return b + c;
- t = t / d * 2;
- if (t < 1) return c / 2 * pow(2, 10 * (t - 1)) + b - c * 0.0005;
- return c / 2 * 1.0005 * (-pow(2, -10 * (t - 1)) + 2) + b;
-}
-
-static real_t out_in(real_t t, real_t b, real_t c, real_t d) {
- return (t < d / 2) ? out(t * 2, b, c / 2, d) : in((t * 2) - d, b + c / 2, c / 2, d);
-}
-}; // namespace expo
-///////////////////////////////////////////////////////////////////////////
-// elastic
-///////////////////////////////////////////////////////////////////////////
-namespace elastic {
-static real_t in(real_t t, real_t b, real_t c, real_t d) {
- if (t == 0) return b;
- if ((t /= d) == 1) return b + c;
- float p = d * 0.3f;
- float a = c;
- float s = p / 4;
- float postFix = a * pow(2, 10 * (t -= 1)); // this is a fix, again, with post-increment operators
- return -(postFix * sin((t * d - s) * (2 * pi) / p)) + b;
-}
-
-static real_t out(real_t t, real_t b, real_t c, real_t d) {
- if (t == 0) return b;
- if ((t /= d) == 1) return b + c;
- float p = d * 0.3f;
- float a = c;
- float s = p / 4;
- return (a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p) + c + b);
-}
-
-static real_t in_out(real_t t, real_t b, real_t c, real_t d) {
- if (t == 0) return b;
- if ((t /= d / 2) == 2) return b + c;
- float p = d * (0.3f * 1.5f);
- float a = c;
- float s = p / 4;
-
- if (t < 1) {
- float postFix = a * pow(2, 10 * (t -= 1)); // postIncrement is evil
- return -0.5f * (postFix * sin((t * d - s) * (2 * pi) / p)) + b;
- }
- float postFix = a * pow(2, -10 * (t -= 1)); // postIncrement is evil
- return postFix * sin((t * d - s) * (2 * pi) / p) * 0.5f + c + b;
-}
-
-static real_t out_in(real_t t, real_t b, real_t c, real_t d) {
- return (t < d / 2) ? out(t * 2, b, c / 2, d) : in((t * 2) - d, b + c / 2, c / 2, d);
-}
-}; // namespace elastic
-///////////////////////////////////////////////////////////////////////////
-// cubic
-///////////////////////////////////////////////////////////////////////////
-namespace cubic {
-static real_t in(real_t t, real_t b, real_t c, real_t d) {
- t /= d;
- return c * t * t * t + b;
-}
-
-static real_t out(real_t t, real_t b, real_t c, real_t d) {
- t = t / d - 1;
- return c * (t * t * t + 1) + b;
-}
-
-static real_t in_out(real_t t, real_t b, real_t c, real_t d) {
- t /= d / 2;
- if (t < 1) return c / 2 * t * t * t + b;
- t -= 2;
- return c / 2 * (t * t * t + 2) + b;
-}
-
-static real_t out_in(real_t t, real_t b, real_t c, real_t d) {
- return (t < d / 2) ? out(t * 2, b, c / 2, d) : in((t * 2) - d, b + c / 2, c / 2, d);
-}
-}; // namespace cubic
-///////////////////////////////////////////////////////////////////////////
-// circ
-///////////////////////////////////////////////////////////////////////////
-namespace circ {
-static real_t in(real_t t, real_t b, real_t c, real_t d) {
- t /= d;
- return -c * (sqrt(1 - t * t) - 1) + b;
-}
-
-static real_t out(real_t t, real_t b, real_t c, real_t d) {
- t = t / d - 1;
- return c * sqrt(1 - t * t) + b;
-}
-
-static real_t in_out(real_t t, real_t b, real_t c, real_t d) {
- t /= d / 2;
- if (t < 1) {
- return -c / 2 * (sqrt(1 - t * t) - 1) + b;
- }
- t -= 2;
- return c / 2 * (sqrt(1 - t * t) + 1) + b;
-}
-
-static real_t out_in(real_t t, real_t b, real_t c, real_t d) {
- return (t < d / 2) ? out(t * 2, b, c / 2, d) : in((t * 2) - d, b + c / 2, c / 2, d);
-}
-}; // namespace circ
-///////////////////////////////////////////////////////////////////////////
-// bounce
-///////////////////////////////////////////////////////////////////////////
-namespace bounce {
-static real_t out(real_t t, real_t b, real_t c, real_t d);
-
-static real_t in(real_t t, real_t b, real_t c, real_t d) {
- return c - out(d - t, 0, c, d) + b;
-}
-
-static real_t out(real_t t, real_t b, real_t c, real_t d) {
- if ((t /= d) < (1 / 2.75f)) {
- return c * (7.5625f * t * t) + b;
- } else if (t < (2 / 2.75f)) {
- float postFix = t -= (1.5f / 2.75f);
- return c * (7.5625f * (postFix)*t + .75f) + b;
- } else if (t < (2.5 / 2.75)) {
- float postFix = t -= (2.25f / 2.75f);
- return c * (7.5625f * (postFix)*t + .9375f) + b;
- } else {
- float postFix = t -= (2.625f / 2.75f);
- return c * (7.5625f * (postFix)*t + .984375f) + b;
- }
-}
-
-static real_t in_out(real_t t, real_t b, real_t c, real_t d) {
- return (t < d / 2) ? in(t * 2, b, c / 2, d) : out((t * 2) - d, b + c / 2, c / 2, d);
-}
-
-static real_t out_in(real_t t, real_t b, real_t c, real_t d) {
- return (t < d / 2) ? out(t * 2, b, c / 2, d) : in((t * 2) - d, b + c / 2, c / 2, d);
-}
-}; // namespace bounce
-///////////////////////////////////////////////////////////////////////////
-// back
-///////////////////////////////////////////////////////////////////////////
-namespace back {
-static real_t in(real_t t, real_t b, real_t c, real_t d) {
- float s = 1.70158f;
- float postFix = t /= d;
- return c * (postFix)*t * ((s + 1) * t - s) + b;
-}
-
-static real_t out(real_t t, real_t b, real_t c, real_t d) {
- float s = 1.70158f;
- t = t / d - 1;
- return c * (t * t * ((s + 1) * t + s) + 1) + b;
-}
-
-static real_t in_out(real_t t, real_t b, real_t c, real_t d) {
- float s = 1.70158f * 1.525f;
- t /= d / 2;
- if (t < 1) return c / 2 * (t * t * ((s + 1) * t - s)) + b;
- t -= 2;
- return c / 2 * (t * t * ((s + 1) * t + s) + 2) + b;
-}
-
-static real_t out_in(real_t t, real_t b, real_t c, real_t d) {
- return (t < d / 2) ? out(t * 2, b, c / 2, d) : in((t * 2) - d, b + c / 2, c / 2, d);
-}
-}; // namespace back
-
-Tween::interpolater Tween::interpolaters[Tween::TRANS_COUNT][Tween::EASE_COUNT] = {
- { &linear::in, &linear::out, &linear::in_out, &linear::out_in },
- { &sine::in, &sine::out, &sine::in_out, &sine::out_in },
- { &quint::in, &quint::out, &quint::in_out, &quint::out_in },
- { &quart::in, &quart::out, &quart::in_out, &quart::out_in },
- { &quad::in, &quad::out, &quad::in_out, &quad::out_in },
- { &expo::in, &expo::out, &expo::in_out, &expo::out_in },
- { &elastic::in, &elastic::out, &elastic::in_out, &elastic::out_in },
- { &cubic::in, &cubic::out, &cubic::in_out, &cubic::out_in },
- { &circ::in, &circ::out, &circ::in_out, &circ::out_in },
- { &bounce::in, &bounce::out, &bounce::in_out, &bounce::out_in },
- { &back::in, &back::out, &back::in_out, &back::out_in },
-};
-
-real_t Tween::_run_equation(TransitionType p_trans_type, EaseType p_ease_type, real_t t, real_t b, real_t c, real_t d) {
- if (d == 0) {
- // Special case to avoid dividing by 0 in equations.
- return b + c;
- }
-
- interpolater cb = interpolaters[p_trans_type][p_ease_type];
- ERR_FAIL_COND_V(cb == NULL, b);
- return cb(t, b, c, d);
-}
]