From 153dc4d57e2cbec2865083508c916b76ad32ad19 Mon Sep 17 00:00:00 2001 From: Haoyu Qiu Date: Thu, 28 Apr 2022 17:00:23 +0800 Subject: [PATCH] Backport the new Tween system as SceneTreeTween Co-authored-by: Tomasz Chabora --- COPYRIGHT.txt | 12 +- doc/classes/CallbackTweener.xml | 27 + doc/classes/IntervalTweener.xml | 16 + doc/classes/MethodTweener.xml | 37 ++ doc/classes/Node.xml | 9 + doc/classes/PropertyTweener.xml | 68 ++ doc/classes/SceneTree.xml | 12 + doc/classes/SceneTreeTween.xml | 324 ++++++++++ doc/classes/Tween.xml | 1 + doc/classes/Tweener.xml | 22 + scene/animation/SCsub | 14 - scene/animation/easing_equations.h | 405 ++++++++++++ scene/animation/scene_tree_tween.cpp | 927 +++++++++++++++++++++++++++ scene/animation/scene_tree_tween.h | 253 ++++++++ scene/animation/tween.cpp | 34 +- scene/animation/tween.h | 3 +- scene/main/node.cpp | 10 + scene/main/node.h | 4 + scene/main/scene_tree.cpp | 51 ++ scene/main/scene_tree.h | 5 + scene/register_scene_types.cpp | 7 + scene/scene_string_names.cpp | 2 + scene/scene_string_names.h | 2 + thirdparty/README.md | 4 - thirdparty/misc/easing_equations.cpp | 323 ---------- 25 files changed, 2221 insertions(+), 351 deletions(-) create mode 100644 doc/classes/CallbackTweener.xml create mode 100644 doc/classes/IntervalTweener.xml create mode 100644 doc/classes/MethodTweener.xml create mode 100644 doc/classes/PropertyTweener.xml create mode 100644 doc/classes/SceneTreeTween.xml create mode 100644 doc/classes/Tweener.xml create mode 100644 scene/animation/easing_equations.h create mode 100644 scene/animation/scene_tree_tween.cpp create mode 100644 scene/animation/scene_tree_tween.h delete mode 100644 thirdparty/misc/easing_equations.cpp diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt index fc974e36c747..6af008bce8ed 100644 --- a/COPYRIGHT.txt +++ b/COPYRIGHT.txt @@ -96,6 +96,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 @@ -273,11 +280,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 000000000000..ab17f73c1033 --- /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 000000000000..fadc00f05626 --- /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 000000000000..d6d5493b48a6 --- /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 b70d6ca20af9..7e77f37a3dae 100644 --- a/doc/classes/Node.xml +++ b/doc/classes/Node.xml @@ -147,6 +147,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 000000000000..09416000c247 --- /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 [Tween] that contains this Tweener. + + + + + + + Sets the type of used transition from [enum Tween.TransitionType]. If not set, the default transition is used from the [Tween] that contains this Tweener. + + + + + + diff --git a/doc/classes/SceneTree.xml b/doc/classes/SceneTree.xml index 56d139155dd4..c78beab7e706 100644 --- a/doc/classes/SceneTree.xml +++ b/doc/classes/SceneTree.xml @@ -73,6 +73,12 @@ The timer will be automatically freed after its time elapses. + + + + Creates and returns a new [SceneTreeTween]. + + @@ -104,6 +110,12 @@ Returns a list of all nodes assigned to the given group. + + + + 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 000000000000..3706d9f726d2 --- /dev/null +++ b/doc/classes/SceneTreeTween.xml @@ -0,0 +1,324 @@ + + + + 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 [SceneTreeTween] animation is composed of a sequence of [Tweener]s, which by default are executed one after another. You can create a sequence by appending [Tweener]s to the [SceneTreeTween]. Animating something with a [Tweener] is called tweening. Example tweening sequence looks like this: + [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 and finally the [method Node.queue_free] is called to remove the sprite. See methods [method tween_property], [method tween_interval], [method tween_callback] and [method tween_method] for more usage information. + 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 different transition type in the above example, you can do: + [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 this example the [SceneTreeTween] is bound and have set a default transition: + [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 set of objects: + [codeblock] + var tween = create_tween() + for sprite in get_children(): + tween.tween_property(sprite, "position", Vector2(), 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 an [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 was 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 given [code]delta[/code] value, in seconds. Mostly useful when the [SceneTreeTween] is paused, for controlling it manually. Can also be used to end the [SceneTreeTween] animation immediately, by using [code]delta[/code] longer than the whole duration. + Returns [code]true[/code] if the [SceneTreeTween] still has [Tweener]s that haven't finished. + [b]Note:[/b] The [SceneTreeTween] will become invalid after finished, but you can call [method stop] after the step, to keep it and reset. + + + + + + Returns the total time in seconds the [SceneTreeTween] has been animating (i.e. 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]). [SceneTreeTween] might become invalid when it has finished tweening or was killed, also when created with [code]Tween.new()[/code]. Invalid [SceneTreeTween] can't have [Tweener]s appended, because it can't animate them. 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 it is either killed by [method kill] or by freeing bound node, 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. 0-duration looped animations (e.g. single [CallbackTweener] with no delay or [PropertyTweener] with invalid node) are equivalent to infinite [code]while[/code] loops and will freeze your game. 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 for 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 a value at the time the tweening of the [PropertyTweener] start. 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 a full loop is complete (see [method set_loops]), providing the loop index. This signal is not emitted after 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 parallelly. + + + + + + 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/Tween.xml b/doc/classes/Tween.xml index ae4906484385..76cce947b719 100644 --- a/doc/classes/Tween.xml +++ b/doc/classes/Tween.xml @@ -18,6 +18,7 @@ Many of the methods accept [code]trans_type[/code] and [code]ease_type[/code]. The first accepts an [enum TransitionType] constant, and refers to the way the timing of the animation is handled (see [url=https://easings.net/]easings.net[/url] for some examples). The second accepts an [enum EaseType] constant, and controls where the [code]trans_type[/code] is applied to the interpolation (in the beginning, the end, or both). If you don't know which transition and easing to pick, you can try different [enum TransitionType] constants with [constant EASE_IN_OUT], and use the one that looks best. [url=https://raw.githubusercontent.com/godotengine/godot-docs/master/img/tween_cheatsheet.png]Tween easing and transition types cheatsheet[/url] [b]Note:[/b] Tween methods will return [code]false[/code] if the requested operation cannot be completed. + [b]Note:[/b] For an alternative method of tweening, that doesn't require using nodes, see [SceneTreeTween]. diff --git a/doc/classes/Tweener.xml b/doc/classes/Tweener.xml new file mode 100644 index 000000000000..45bb9bd99313 --- /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 cc33a5af84fd..ede3fab756de 100644 --- a/scene/animation/SCsub +++ b/scene/animation/SCsub @@ -2,23 +2,9 @@ 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 - # Godot source files 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 000000000000..6d246c7a9396 --- /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 diff --git a/scene/animation/scene_tree_tween.cpp b/scene/animation/scene_tree_tween.cpp new file mode 100644 index 000000000000..d746e1904f84 --- /dev/null +++ b/scene/animation/scene_tree_tween.cpp @@ -0,0 +1,927 @@ +/*************************************************************************/ +/* 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; + + while (rem_delta > 0 && running) { + float step_delta = rem_delta; + step_active = false; + +#ifdef DEBUG_ENABLED + float prev_delta = rem_delta; +#endif + + 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(); + } + } else { + start_tweeners(); + } + } + +#ifdef DEBUG_ENABLED + if (Math::is_equal_approx(rem_delta, prev_delta) && running && loops <= 0) { + ERR_FAIL_V_MSG(false, "Infinite loop detected. Check set_loops() description for more info."); + } +#endif + } + + 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->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 000000000000..373f22fa1a73 --- /dev/null +++ b/scene/animation/scene_tree_tween.h @@ -0,0 +1,253 @@ +/*************************************************************************/ +/* 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; + + 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 diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp index cdd10b6e16a8..b911d6bfe56c 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 23da0ef3f578..aabd5f333a01 100644 --- a/scene/animation/tween.h +++ b/scene/animation/tween.h @@ -130,7 +130,6 @@ class Tween : public Node { typedef real_t (*interpolater)(real_t t, real_t b, real_t c, real_t d); static interpolater interpolaters[TRANS_COUNT][EASE_COUNT]; - real_t _run_equation(TransitionType p_trans_type, EaseType p_ease_type, real_t t, real_t b, real_t c, real_t d); Variant &_get_delta_val(InterpolateData &p_data); Variant _get_initial_val(const InterpolateData &p_data) const; Variant _get_final_val(const InterpolateData &p_data) const; @@ -152,6 +151,8 @@ class Tween : public Node { 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 462f0011c192..2e0a261ed72d 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" @@ -1810,6 +1811,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); @@ -2873,6 +2882,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 3609ce7d8b47..3faef603189e 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -41,6 +41,8 @@ class Viewport; class SceneState; +class SceneTreeTween; + class Node : public Object { GDCLASS(Node, Object); OBJ_CATEGORY("Nodes"); @@ -327,6 +329,8 @@ class Node : public Object { 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 0948fc6cc768..a0c365cf062b 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -40,6 +40,7 @@ #include "core/project_settings.h" #include "main/input_default.h" #include "node.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" @@ -552,6 +553,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--; @@ -644,6 +648,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(); @@ -684,6 +690,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(); @@ -1787,6 +1819,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); } @@ -1897,6 +1946,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 1184df0a5bdd..c97800068604 100644 --- a/scene/main/scene_tree.h +++ b/scene/main/scene_tree.h @@ -41,6 +41,7 @@ class PackedScene; class Node; +class SceneTreeTween; class Spatial; class Viewport; class Material; @@ -188,6 +189,7 @@ class SceneTree : public MainLoop { //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/// @@ -208,6 +210,7 @@ class SceneTree : public MainLoop { 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); @@ -387,6 +390,8 @@ class SceneTree : public MainLoop { 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); diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 19729ed64cf8..5016b72157d8 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -78,6 +78,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" @@ -392,6 +393,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 d19a6df5daeb..6fd20073f555 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 978ad3da935e..05868b5ba58a 100644 --- a/scene/scene_string_names.h +++ b/scene/scene_string_names.h @@ -91,6 +91,8 @@ class SceneStringNames { 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 289d67080c28..1d2775265c3f 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -342,10 +342,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 52b31d1763a7..000000000000 --- 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); -}