virtualx-engine/scene/animation/tween.cpp
Rémi Verschelde 1426cd3b3a
One Copyright Update to rule them all
As many open source projects have started doing it, we're removing the
current year from the copyright notice, so that we don't need to bump
it every year.

It seems like only the first year of publication is technically
relevant for copyright notices, and even that seems to be something
that many companies stopped listing altogether (in a version controlled
codebase, the commits are a much better source of date of publication
than a hardcoded copyright statement).

We also now list Godot Engine contributors first as we're collectively
the current maintainers of the project, and we clarify that the
"exclusive" copyright of the co-founders covers the timespan before
opensourcing (their further contributions are included as part of Godot
Engine contributors).

Also fixed "cf." Frenchism - it's meant as "refer to / see".

Backported from #70885.
2023-01-10 15:26:54 +01:00

1895 lines
62 KiB
C++

/**************************************************************************/
/* tween.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* 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 "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
pending_commands.push_back(PendingCommand());
PendingCommand &cmd = pending_commands.back()->get();
// Update the command with the target key
cmd.key = p_key;
// Determine command argument count
int &count = cmd.args;
if (p_arg10.get_type() != Variant::NIL) {
count = 10;
} else if (p_arg9.get_type() != Variant::NIL) {
count = 9;
} else if (p_arg8.get_type() != Variant::NIL) {
count = 8;
} else if (p_arg7.get_type() != Variant::NIL) {
count = 7;
} else if (p_arg6.get_type() != Variant::NIL) {
count = 6;
} else if (p_arg5.get_type() != Variant::NIL) {
count = 5;
} else if (p_arg4.get_type() != Variant::NIL) {
count = 4;
} else if (p_arg3.get_type() != Variant::NIL) {
count = 3;
} else if (p_arg2.get_type() != Variant::NIL) {
count = 2;
} else if (p_arg1.get_type() != Variant::NIL) {
count = 1;
} else {
count = 0;
}
// Add the specified arguments to the command
if (count > 0) {
cmd.arg[0] = p_arg1;
}
if (count > 1) {
cmd.arg[1] = p_arg2;
}
if (count > 2) {
cmd.arg[2] = p_arg3;
}
if (count > 3) {
cmd.arg[3] = p_arg4;
}
if (count > 4) {
cmd.arg[4] = p_arg5;
}
if (count > 5) {
cmd.arg[5] = p_arg6;
}
if (count > 6) {
cmd.arg[6] = p_arg7;
}
if (count > 7) {
cmd.arg[7] = p_arg8;
}
if (count > 8) {
cmd.arg[8] = p_arg9;
}
if (count > 9) {
cmd.arg[9] = p_arg10;
}
}
void Tween::_process_pending_commands() {
// For each pending command...
for (List<PendingCommand>::Element *E = pending_commands.front(); E; E = E->next()) {
// Get the command
PendingCommand &cmd = E->get();
Variant::CallError err;
// Grab all of the arguments for the command
Variant *arg[10] = {
&cmd.arg[0],
&cmd.arg[1],
&cmd.arg[2],
&cmd.arg[3],
&cmd.arg[4],
&cmd.arg[5],
&cmd.arg[6],
&cmd.arg[7],
&cmd.arg[8],
&cmd.arg[9],
};
// Execute the command (and retrieve any errors)
this->call(cmd.key, (const Variant **)arg, cmd.args, err);
}
// Clear the pending commands
pending_commands.clear();
}
bool Tween::_set(const StringName &p_name, const Variant &p_value) {
// Set the correct attribute based on the given name
String name = p_name;
if (name == "playback/speed" || name == "speed") { // Backwards compatibility
set_speed_scale(p_value);
return true;
} else if (name == "playback/active") {
set_active(p_value);
return true;
} else if (name == "playback/repeat") {
set_repeat(p_value);
return true;
}
return false;
}
bool Tween::_get(const StringName &p_name, Variant &r_ret) const {
// Get the correct attribute based on the given name
String name = p_name;
if (name == "playback/speed") { // Backwards compatibility
r_ret = speed_scale;
return true;
} else if (name == "playback/active") {
r_ret = is_active();
return true;
} else if (name == "playback/repeat") {
r_ret = is_repeat();
return true;
}
return false;
}
void Tween::_get_property_list(List<PropertyInfo> *p_list) const {
// Add the property info for the Tween object
p_list->push_back(PropertyInfo(Variant::BOOL, "playback/active", PROPERTY_HINT_NONE, ""));
p_list->push_back(PropertyInfo(Variant::BOOL, "playback/repeat", PROPERTY_HINT_NONE, ""));
p_list->push_back(PropertyInfo(Variant::REAL, "playback/speed", PROPERTY_HINT_RANGE, "-64,64,0.01"));
}
void Tween::_notification(int p_what) {
// What notification did we receive?
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
// Are we not already active?
if (!is_active()) {
// Make sure that a previous process state was not saved
// Only process if "processing" is set
set_physics_process_internal(false);
set_process_internal(false);
}
} break;
case NOTIFICATION_READY: {
// Do nothing
} break;
case NOTIFICATION_INTERNAL_PROCESS: {
// Are we processing during physics time?
if (tween_process_mode == TWEEN_PROCESS_PHYSICS) {
// Do nothing since we aren't aligned with physics when we should be
break;
}
// Should we update?
if (is_active()) {
// Update the tweens
_tween_process(get_process_delta_time());
}
} break;
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
// Are we processing during 'regular' time?
if (tween_process_mode == TWEEN_PROCESS_IDLE) {
// Do nothing since we would only process during idle time
break;
}
// Should we update?
if (is_active()) {
// Update the tweens
_tween_process(get_physics_process_delta_time());
}
} break;
case NOTIFICATION_EXIT_TREE: {
// We've left the tree. Stop all tweens
stop_all();
} break;
}
}
void Tween::_bind_methods() {
// Bind getters and setters
ClassDB::bind_method(D_METHOD("is_active"), &Tween::is_active);
ClassDB::bind_method(D_METHOD("set_active", "active"), &Tween::set_active);
ClassDB::bind_method(D_METHOD("is_repeat"), &Tween::is_repeat);
ClassDB::bind_method(D_METHOD("set_repeat", "repeat"), &Tween::set_repeat);
ClassDB::bind_method(D_METHOD("set_speed_scale", "speed"), &Tween::set_speed_scale);
ClassDB::bind_method(D_METHOD("get_speed_scale"), &Tween::get_speed_scale);
ClassDB::bind_method(D_METHOD("set_tween_process_mode", "mode"), &Tween::set_tween_process_mode);
ClassDB::bind_method(D_METHOD("get_tween_process_mode"), &Tween::get_tween_process_mode);
// Bind the various Tween control methods
ClassDB::bind_method(D_METHOD("start"), &Tween::start);
ClassDB::bind_method(D_METHOD("reset", "object", "key"), &Tween::reset, DEFVAL(""));
ClassDB::bind_method(D_METHOD("reset_all"), &Tween::reset_all);
ClassDB::bind_method(D_METHOD("stop", "object", "key"), &Tween::stop, DEFVAL(""));
ClassDB::bind_method(D_METHOD("stop_all"), &Tween::stop_all);
ClassDB::bind_method(D_METHOD("resume", "object", "key"), &Tween::resume, DEFVAL(""));
ClassDB::bind_method(D_METHOD("resume_all"), &Tween::resume_all);
ClassDB::bind_method(D_METHOD("remove", "object", "key"), &Tween::remove, DEFVAL(""));
ClassDB::bind_method(D_METHOD("_remove_by_uid", "uid"), &Tween::_remove_by_uid);
ClassDB::bind_method(D_METHOD("remove_all"), &Tween::remove_all);
ClassDB::bind_method(D_METHOD("seek", "time"), &Tween::seek);
ClassDB::bind_method(D_METHOD("tell"), &Tween::tell);
ClassDB::bind_method(D_METHOD("get_runtime"), &Tween::get_runtime);
// Bind interpolation and follow methods
ClassDB::bind_method(D_METHOD("interpolate_property", "object", "property", "initial_val", "final_val", "duration", "trans_type", "ease_type", "delay"), &Tween::interpolate_property, DEFVAL(TRANS_LINEAR), DEFVAL(EASE_IN_OUT), DEFVAL(0));
ClassDB::bind_method(D_METHOD("interpolate_method", "object", "method", "initial_val", "final_val", "duration", "trans_type", "ease_type", "delay"), &Tween::interpolate_method, DEFVAL(TRANS_LINEAR), DEFVAL(EASE_IN_OUT), DEFVAL(0));
ClassDB::bind_method(D_METHOD("interpolate_callback", "object", "duration", "callback", "arg1", "arg2", "arg3", "arg4", "arg5", "arg6", "arg7", "arg8"), &Tween::interpolate_callback, DEFVAL(Variant()), DEFVAL(Variant()), DEFVAL(Variant()), DEFVAL(Variant()), DEFVAL(Variant()), DEFVAL(Variant()), DEFVAL(Variant()), DEFVAL(Variant()));
ClassDB::bind_method(D_METHOD("interpolate_deferred_callback", "object", "duration", "callback", "arg1", "arg2", "arg3", "arg4", "arg5", "arg6", "arg7", "arg8"), &Tween::interpolate_deferred_callback, DEFVAL(Variant()), DEFVAL(Variant()), DEFVAL(Variant()), DEFVAL(Variant()), DEFVAL(Variant()), DEFVAL(Variant()), DEFVAL(Variant()), DEFVAL(Variant()));
ClassDB::bind_method(D_METHOD("follow_property", "object", "property", "initial_val", "target", "target_property", "duration", "trans_type", "ease_type", "delay"), &Tween::follow_property, DEFVAL(TRANS_LINEAR), DEFVAL(EASE_IN_OUT), DEFVAL(0));
ClassDB::bind_method(D_METHOD("follow_method", "object", "method", "initial_val", "target", "target_method", "duration", "trans_type", "ease_type", "delay"), &Tween::follow_method, DEFVAL(TRANS_LINEAR), DEFVAL(EASE_IN_OUT), DEFVAL(0));
ClassDB::bind_method(D_METHOD("targeting_property", "object", "property", "initial", "initial_val", "final_val", "duration", "trans_type", "ease_type", "delay"), &Tween::targeting_property, DEFVAL(TRANS_LINEAR), DEFVAL(EASE_IN_OUT), DEFVAL(0));
ClassDB::bind_method(D_METHOD("targeting_method", "object", "method", "initial", "initial_method", "final_val", "duration", "trans_type", "ease_type", "delay"), &Tween::targeting_method, DEFVAL(TRANS_LINEAR), DEFVAL(EASE_IN_OUT), DEFVAL(0));
// Add the Tween signals
ADD_SIGNAL(MethodInfo("tween_started", PropertyInfo(Variant::OBJECT, "object"), PropertyInfo(Variant::NODE_PATH, "key")));
ADD_SIGNAL(MethodInfo("tween_step", PropertyInfo(Variant::OBJECT, "object"), PropertyInfo(Variant::NODE_PATH, "key"), PropertyInfo(Variant::REAL, "elapsed"), PropertyInfo(Variant::OBJECT, "value")));
ADD_SIGNAL(MethodInfo("tween_completed", PropertyInfo(Variant::OBJECT, "object"), PropertyInfo(Variant::NODE_PATH, "key")));
ADD_SIGNAL(MethodInfo("tween_all_completed"));
// Add the properties and tie them to the getters and setters
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "repeat"), "set_repeat", "is_repeat");
ADD_PROPERTY(PropertyInfo(Variant::INT, "playback_process_mode", PROPERTY_HINT_ENUM, "Physics,Idle"), "set_tween_process_mode", "get_tween_process_mode");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "playback_speed", PROPERTY_HINT_RANGE, "-64,64,0.01"), "set_speed_scale", "get_speed_scale");
// Bind Idle vs Physics process
BIND_ENUM_CONSTANT(TWEEN_PROCESS_PHYSICS);
BIND_ENUM_CONSTANT(TWEEN_PROCESS_IDLE);
// Bind the Transition type constants
BIND_ENUM_CONSTANT(TRANS_LINEAR);
BIND_ENUM_CONSTANT(TRANS_SINE);
BIND_ENUM_CONSTANT(TRANS_QUINT);
BIND_ENUM_CONSTANT(TRANS_QUART);
BIND_ENUM_CONSTANT(TRANS_QUAD);
BIND_ENUM_CONSTANT(TRANS_EXPO);
BIND_ENUM_CONSTANT(TRANS_ELASTIC);
BIND_ENUM_CONSTANT(TRANS_CUBIC);
BIND_ENUM_CONSTANT(TRANS_CIRC);
BIND_ENUM_CONSTANT(TRANS_BOUNCE);
BIND_ENUM_CONSTANT(TRANS_BACK);
// Bind the easing constants
BIND_ENUM_CONSTANT(EASE_IN);
BIND_ENUM_CONSTANT(EASE_OUT);
BIND_ENUM_CONSTANT(EASE_IN_OUT);
BIND_ENUM_CONSTANT(EASE_OUT_IN);
}
Variant Tween::_get_initial_val(const InterpolateData &p_data) const {
// What type of data are we interpolating?
switch (p_data.type) {
case INTER_PROPERTY:
case INTER_METHOD:
case FOLLOW_PROPERTY:
case FOLLOW_METHOD:
// Simply use the given initial value
return p_data.initial_val;
case TARGETING_PROPERTY:
case TARGETING_METHOD: {
// Get the object that is being targeted
Object *object = ObjectDB::get_instance(p_data.target_id);
ERR_FAIL_COND_V(object == nullptr, p_data.initial_val);
// Are we targeting a property or a method?
Variant initial_val;
if (p_data.type == TARGETING_PROPERTY) {
// Get the property from the target object
bool valid = false;
initial_val = object->get_indexed(p_data.target_key, &valid);
ERR_FAIL_COND_V(!valid, p_data.initial_val);
} else {
// Call the method and get the initial value from it
Variant::CallError error;
initial_val = object->call(p_data.target_key[0], nullptr, 0, error);
ERR_FAIL_COND_V(error.error != Variant::CallError::CALL_OK, p_data.initial_val);
}
return initial_val;
}
case INTER_CALLBACK:
// Callback does not have a special initial value
break;
}
// If we've made it here, just return the delta value as the initial value
return p_data.delta_val;
}
Variant Tween::_get_final_val(const InterpolateData &p_data) const {
switch (p_data.type) {
case FOLLOW_PROPERTY:
case FOLLOW_METHOD: {
// Get the object that is being followed
Object *target = ObjectDB::get_instance(p_data.target_id);
ERR_FAIL_COND_V(target == nullptr, p_data.initial_val);
// We want to figure out the final value
Variant final_val;
if (p_data.type == FOLLOW_PROPERTY) {
// Read the property as-is
bool valid = false;
final_val = target->get_indexed(p_data.target_key, &valid);
ERR_FAIL_COND_V(!valid, p_data.initial_val);
} else {
// We're looking at a method. Call the method on the target object
Variant::CallError error;
final_val = target->call(p_data.target_key[0], nullptr, 0, error);
ERR_FAIL_COND_V(error.error != Variant::CallError::CALL_OK, p_data.initial_val);
}
// If we're looking at an INT value, instead convert it to a REAL
// This is better for interpolation
if (final_val.get_type() == Variant::INT) {
final_val = final_val.operator real_t();
}
return final_val;
}
default: {
// If we're not following a final value/method, use the final value from the data
return p_data.final_val;
}
}
}
Variant &Tween::_get_delta_val(InterpolateData &p_data) {
// What kind of data are we interpolating?
switch (p_data.type) {
case INTER_PROPERTY:
case INTER_METHOD:
// Simply return the given delta value
return p_data.delta_val;
case FOLLOW_PROPERTY:
case FOLLOW_METHOD: {
// We're following an object, so grab that instance
Object *target = ObjectDB::get_instance(p_data.target_id);
ERR_FAIL_COND_V(target == nullptr, p_data.initial_val);
// We want to figure out the final value
Variant final_val;
if (p_data.type == FOLLOW_PROPERTY) {
// Read the property as-is
bool valid = false;
final_val = target->get_indexed(p_data.target_key, &valid);
ERR_FAIL_COND_V(!valid, p_data.initial_val);
} else {
// We're looking at a method. Call the method on the target object
Variant::CallError error;
final_val = target->call(p_data.target_key[0], nullptr, 0, error);
ERR_FAIL_COND_V(error.error != Variant::CallError::CALL_OK, p_data.initial_val);
}
// If we're looking at an INT value, instead convert it to a REAL
// This is better for interpolation
if (final_val.get_type() == Variant::INT) {
final_val = final_val.operator real_t();
}
// Calculate the delta based on the initial value and the final value
_calc_delta_val(p_data.initial_val, final_val, p_data.delta_val);
return p_data.delta_val;
}
case TARGETING_PROPERTY:
case TARGETING_METHOD: {
// Grab the initial value from the data to calculate delta
Variant initial_val = _get_initial_val(p_data);
// If we're looking at an INT value, instead convert it to a REAL
// This is better for interpolation
if (initial_val.get_type() == Variant::INT) {
initial_val = initial_val.operator real_t();
}
// Calculate the delta based on the initial value and the final value
_calc_delta_val(initial_val, p_data.final_val, p_data.delta_val);
return p_data.delta_val;
}
case INTER_CALLBACK:
// Callbacks have no special delta
break;
}
// If we've made it here, use the initial value as the delta
return p_data.initial_val;
}
Variant Tween::_run_equation(InterpolateData &p_data) {
// Get the initial and delta values from the data
Variant initial_val = _get_initial_val(p_data);
Variant &delta_val = _get_delta_val(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);
// 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;
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);
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);
break;
case Variant::VECTOR2: {
// Get vectors for initial and delta values
Vector2 i = initial_val;
Vector2 d = delta_val;
Vector2 r;
// Execute the equation and mutate the r vector
// This uses the custom APPLY_EQUATION macro defined above
APPLY_EQUATION(x);
APPLY_EQUATION(y);
result = r;
} break;
case Variant::RECT2: {
// Get the Rect2 for initial and delta value
Rect2 i = initial_val;
Rect2 d = delta_val;
Rect2 r;
// Execute the equation for the position and size of Rect2
APPLY_EQUATION(position.x);
APPLY_EQUATION(position.y);
APPLY_EQUATION(size.x);
APPLY_EQUATION(size.y);
result = r;
} break;
case Variant::VECTOR3: {
// Get vectors for initial and delta values
Vector3 i = initial_val;
Vector3 d = delta_val;
Vector3 r;
// Execute the equation and mutate the r vector
// This uses the custom APPLY_EQUATION macro defined above
APPLY_EQUATION(x);
APPLY_EQUATION(y);
APPLY_EQUATION(z);
result = r;
} break;
case Variant::TRANSFORM2D: {
// Get the transforms for initial and delta values
Transform2D i = initial_val;
Transform2D d = delta_val;
Transform2D r;
// Execute the equation on the transforms and mutate the r transform
// This uses the custom APPLY_EQUATION macro defined above
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]);
result = r;
} break;
case Variant::QUAT: {
// Get the quaternian for the initial and delta values
Quat i = initial_val;
Quat d = delta_val;
Quat r;
// Execute the equation on the quaternian values and mutate the r quaternian
// This uses the custom APPLY_EQUATION macro defined above
APPLY_EQUATION(x);
APPLY_EQUATION(y);
APPLY_EQUATION(z);
APPLY_EQUATION(w);
result = r;
} break;
case Variant::AABB: {
// Get the AABB's for the initial and delta values
AABB i = initial_val;
AABB d = delta_val;
AABB r;
// Execute the equation for the position and size of the AABB's and mutate the r AABB
// This uses the custom APPLY_EQUATION macro defined above
APPLY_EQUATION(position.x);
APPLY_EQUATION(position.y);
APPLY_EQUATION(position.z);
APPLY_EQUATION(size.x);
APPLY_EQUATION(size.y);
APPLY_EQUATION(size.z);
result = r;
} break;
case Variant::BASIS: {
// Get the basis for initial and delta values
Basis i = initial_val;
Basis d = delta_val;
Basis r;
// Execute the equation on all the basis and mutate the r basis
// This uses the custom APPLY_EQUATION macro defined above
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]);
result = r;
} break;
case Variant::TRANSFORM: {
// Get the transforms for the initial and delta values
Transform i = initial_val;
Transform d = delta_val;
Transform r;
// Execute the equation for each of the transforms and their origin and mutate the r transform
// This uses the custom APPLY_EQUATION macro defined above
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);
result = r;
} break;
case Variant::COLOR: {
// Get the Color for initial and delta value
Color i = initial_val;
Color d = delta_val;
Color r;
// Apply the equation on the Color RGBA, and mutate the r color
// This uses the custom APPLY_EQUATION macro defined above
APPLY_EQUATION(r);
APPLY_EQUATION(g);
APPLY_EQUATION(b);
APPLY_EQUATION(a);
result = r;
} break;
default: {
// If unknown, just return the initial value
result = initial_val;
} break;
};
#undef APPLY_EQUATION
// Return the result that was computed
return result;
}
bool Tween::_apply_tween_value(InterpolateData &p_data, Variant &value) {
// Get the object we want to apply the new value to
Object *object = ObjectDB::get_instance(p_data.id);
ERR_FAIL_COND_V(object == nullptr, false);
// What kind of data are we mutating?
switch (p_data.type) {
case INTER_PROPERTY:
case FOLLOW_PROPERTY:
case TARGETING_PROPERTY: {
// Simply set the property on the object
bool valid = false;
object->set_indexed(p_data.key, value, &valid);
return valid;
}
case INTER_METHOD:
case FOLLOW_METHOD:
case TARGETING_METHOD: {
// We want to call the method on the target object
Variant::CallError error;
// Do we have a non-nil value passed in?
if (value.get_type() != Variant::NIL) {
// Pass it as an argument to the function call
Variant *arg[1] = { &value };
object->call(p_data.key[0], (const Variant **)arg, 1, error);
} else {
// Don't pass any argument
object->call(p_data.key[0], nullptr, 0, error);
}
// Did we get an error from the function call?
return error.error == Variant::CallError::CALL_OK;
}
case INTER_CALLBACK:
// Nothing to apply for a callback
break;
};
// No issues found!
return true;
}
void Tween::_tween_process(float p_delta) {
// Process all of the pending commands
_process_pending_commands();
// If the scale is 0, make no progress on the tweens
if (speed_scale == 0) {
return;
}
// Update the delta and whether we are pending an update
p_delta *= speed_scale;
pending_update++;
// Are we repeating the interpolations?
if (repeat) {
// For each interpolation...
bool repeats_finished = true;
for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) {
// Get the data from it
InterpolateData &data = E->get();
// Is not finished?
if (!data.finish) {
// We aren't finished yet, no need to check the rest
repeats_finished = false;
break;
}
}
// If we are all finished, we can reset all of the tweens
if (repeats_finished) {
reset_all();
}
}
// Are all of the tweens complete?
bool all_finished = true;
// For each tween we wish to interpolate...
for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) {
// Get the data from it
InterpolateData &data = E->get();
// Track if we hit one that isn't finished yet
all_finished = all_finished && data.finish;
// Is the data not active or already finished? No need to go any further
if (!data.active || data.finish) {
continue;
}
// Get the target object for this interpolation
Object *object = ObjectDB::get_instance(data.id);
if (object == nullptr) {
continue;
}
// Are we still delaying this tween?
bool prev_delaying = data.elapsed <= data.delay;
data.elapsed += p_delta;
if (data.elapsed < data.delay) {
continue;
} else if (prev_delaying) {
// We can apply the tween's value to the data and emit that the tween has started
_apply_tween_value(data, data.initial_val);
emit_signal("tween_started", object, NodePath(Vector<StringName>(), data.key, false));
}
// Are we at the end of the tween?
if (data.elapsed > (data.delay + data.duration)) {
// Set the elapsed time to the end and mark this one as finished
data.elapsed = data.delay + data.duration;
data.finish = true;
}
// Are we interpolating a callback?
if (data.type == INTER_CALLBACK) {
// Is the tween completed?
if (data.finish) {
static_assert(VARIANT_ARG_MAX == 8, "This code needs to be updated if VARIANT_ARG_MAX != 8");
// Are we calling this callback deferred or immediately?
if (data.call_deferred) {
// Run the deferred function callback, applying the correct number of arguments
switch (data.args) {
case 0:
object->call_deferred(data.key[0]);
break;
case 1:
object->call_deferred(data.key[0], data.arg[0]);
break;
case 2:
object->call_deferred(data.key[0], data.arg[0], data.arg[1]);
break;
case 3:
object->call_deferred(data.key[0], data.arg[0], data.arg[1], data.arg[2]);
break;
case 4:
object->call_deferred(data.key[0], data.arg[0], data.arg[1], data.arg[2], data.arg[3]);
break;
case 5:
object->call_deferred(data.key[0], data.arg[0], data.arg[1], data.arg[2], data.arg[3], data.arg[4]);
break;
case 6:
object->call_deferred(data.key[0], data.arg[0], data.arg[1], data.arg[2], data.arg[3], data.arg[4], data.arg[5]);
break;
case 7:
object->call_deferred(data.key[0], data.arg[0], data.arg[1], data.arg[2], data.arg[3], data.arg[4], data.arg[5], data.arg[6]);
break;
case 8:
object->call_deferred(data.key[0], data.arg[0], data.arg[1], data.arg[2], data.arg[3], data.arg[4], data.arg[5], data.arg[6], data.arg[7]);
break;
}
} else {
// Call the function directly with the arguments
Variant::CallError error;
Variant *arg[VARIANT_ARG_MAX] = {
&data.arg[0],
&data.arg[1],
&data.arg[2],
&data.arg[3],
&data.arg[4],
&data.arg[5],
&data.arg[6],
&data.arg[7],
};
object->call(data.key[0], (const Variant **)arg, data.args, error);
}
}
} else {
// We can apply the value directly
Variant result = _run_equation(data);
_apply_tween_value(data, result);
// Emit that the tween has taken a step
emit_signal("tween_step", object, NodePath(Vector<StringName>(), data.key, false), data.elapsed, result);
}
// Is the tween now finished?
if (data.finish) {
// Set it to the final value directly
Variant final_val = _get_final_val(data);
_apply_tween_value(data, final_val);
// Emit the signal
emit_signal("tween_completed", object, NodePath(Vector<StringName>(), data.key, false));
// If we are not repeating the tween, remove it
if (!repeat) {
call_deferred("_remove_by_uid", data.uid);
}
} else if (!repeat) {
// Check whether all tweens are finished
all_finished = all_finished && data.finish;
}
}
// One less update left to go
pending_update--;
// If all tweens are completed, we no longer need to be active
if (all_finished) {
set_active(false);
emit_signal("tween_all_completed");
}
}
void Tween::set_tween_process_mode(TweenProcessMode p_mode) {
tween_process_mode = p_mode;
}
Tween::TweenProcessMode Tween::get_tween_process_mode() const {
return tween_process_mode;
}
bool Tween::is_active() const {
return is_processing_internal() || is_physics_processing_internal();
}
void Tween::set_active(bool p_active) {
// Do nothing if it's the same active mode that we currently are
if (is_active() == p_active) {
return;
}
// Depending on physics or idle, set processing
switch (tween_process_mode) {
case TWEEN_PROCESS_IDLE:
set_process_internal(p_active);
break;
case TWEEN_PROCESS_PHYSICS:
set_physics_process_internal(p_active);
break;
}
}
bool Tween::is_repeat() const {
return repeat;
}
void Tween::set_repeat(bool p_repeat) {
repeat = p_repeat;
}
void Tween::set_speed_scale(float p_speed) {
speed_scale = p_speed;
}
float Tween::get_speed_scale() const {
return speed_scale;
}
bool Tween::start() {
ERR_FAIL_COND_V_MSG(!is_inside_tree(), false, "Tween was not added to the SceneTree!");
// Are there any pending updates?
if (pending_update != 0) {
// Start the tweens after deferring
call_deferred("start");
return true;
}
pending_update++;
for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) {
InterpolateData &data = E->get();
data.active = true;
}
pending_update--;
// We want to be activated
set_active(true);
// Don't resume from current position if stop_all() function has been used
if (was_stopped) {
seek(0);
}
was_stopped = false;
return true;
}
bool Tween::reset(Object *p_object, StringName p_key) {
// Find all interpolations that use the same object and target string
pending_update++;
for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) {
// Get the target object
InterpolateData &data = E->get();
Object *object = ObjectDB::get_instance(data.id);
if (object == nullptr) {
continue;
}
// Do we have the correct object and key?
if (object == p_object && (data.concatenated_key == p_key || p_key == "")) {
// Reset the tween to the initial state
data.elapsed = 0;
data.finish = false;
// Also apply the initial state if there isn't a delay
if (data.delay == 0) {
_apply_tween_value(data, data.initial_val);
}
}
}
pending_update--;
return true;
}
bool Tween::reset_all() {
// Go through all interpolations
pending_update++;
for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) {
// Get the target data and set it back to the initial state
InterpolateData &data = E->get();
data.elapsed = 0;
data.finish = false;
// If there isn't a delay, apply the value to the object
if (data.delay == 0) {
_apply_tween_value(data, data.initial_val);
}
}
pending_update--;
return true;
}
bool Tween::stop(Object *p_object, StringName p_key) {
// Find the tween that has the given target object and string key
pending_update++;
for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) {
// Get the object the tween is targeting
InterpolateData &data = E->get();
Object *object = ObjectDB::get_instance(data.id);
if (object == nullptr) {
continue;
}
// Is this the correct object and does it have the given key?
if (object == p_object && (data.concatenated_key == p_key || p_key == "")) {
// Disable the tween
data.active = false;
}
}
pending_update--;
return true;
}
bool Tween::stop_all() {
// We no longer need to be active since all tweens have been stopped
set_active(false);
was_stopped = true;
// For each interpolation...
pending_update++;
for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) {
// Simply set it inactive
InterpolateData &data = E->get();
data.active = false;
}
pending_update--;
return true;
}
bool Tween::resume(Object *p_object, StringName p_key) {
// We need to be activated
// TODO: What if no tween is found??
set_active(true);
// Find the tween that uses the given target object and string key
pending_update++;
for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) {
// Grab the object
InterpolateData &data = E->get();
Object *object = ObjectDB::get_instance(data.id);
if (object == nullptr) {
continue;
}
// If the object and string key match, activate it
if (object == p_object && (data.concatenated_key == p_key || p_key == "")) {
data.active = true;
}
}
pending_update--;
return true;
}
bool Tween::resume_all() {
// Set ourselves active so we can process tweens
// TODO: What if there are no tweens? We get set to active for no reason!
set_active(true);
// For each interpolation...
pending_update++;
for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) {
// Simply grab it and set it to active
InterpolateData &data = E->get();
data.active = true;
}
pending_update--;
return true;
}
bool Tween::remove(Object *p_object, StringName p_key) {
// If we are still updating, call this function again later
if (pending_update != 0) {
call_deferred("remove", p_object, p_key);
return true;
}
// For each interpolation...
List<List<InterpolateData>::Element *> for_removal;
for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) {
// Get the target object
InterpolateData &data = E->get();
Object *object = ObjectDB::get_instance(data.id);
if (object == nullptr) {
continue;
}
// If the target object and string key match, queue it for removal
if (object == p_object && (data.concatenated_key == p_key || p_key == "")) {
for_removal.push_back(E);
}
}
// For each interpolation we wish to remove...
for (List<List<InterpolateData>::Element *>::Element *E = for_removal.front(); E; E = E->next()) {
// Erase it
interpolates.erase(E->get());
}
return true;
}
void Tween::_remove_by_uid(int uid) {
// If we are still updating, call this function again later
if (pending_update != 0) {
call_deferred("_remove_by_uid", uid);
return;
}
// Find the interpolation that matches the given UID
for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) {
if (uid == E->get().uid) {
// It matches, erase it and stop looking
E->erase();
break;
}
}
}
void Tween::_push_interpolate_data(InterpolateData &p_data) {
pending_update++;
// Add the new interpolation
p_data.uid = ++uid;
interpolates.push_back(p_data);
pending_update--;
}
bool Tween::remove_all() {
// If we are still updating, call this function again later
if (pending_update != 0) {
call_deferred("remove_all");
return true;
}
// We no longer need to be active
set_active(false);
// Clear out all interpolations and reset the uid
interpolates.clear();
uid = 0;
return true;
}
bool Tween::seek(real_t p_time) {
// Go through each interpolation...
pending_update++;
for (List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) {
// Get the target data
InterpolateData &data = E->get();
// Update the elapsed data to be set to the target time
data.elapsed = p_time;
// Are we at the end?
if (data.elapsed < data.delay) {
// There is still time left to go
data.finish = false;
continue;
} else if (data.elapsed >= (data.delay + data.duration)) {
// We are past the end of it, set the elapsed time to the end and mark as finished
data.elapsed = (data.delay + data.duration);
data.finish = true;
} else {
// We are not finished with this interpolation yet
data.finish = false;
}
// If we are a callback, do nothing special
if (data.type == INTER_CALLBACK) {
continue;
}
// Run the equation on the data and apply the value
Variant result = _run_equation(data);
_apply_tween_value(data, result);
}
pending_update--;
return true;
}
real_t Tween::tell() const {
// We want to grab the position of the furthest along tween
pending_update++;
real_t pos = 0;
// For each interpolation...
for (const List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) {
// Get the data and figure out if it's position is further along than the previous ones
const InterpolateData &data = E->get();
if (data.elapsed > pos) {
// Save it if so
pos = data.elapsed;
}
}
pending_update--;
return pos;
}
real_t Tween::get_runtime() const {
// If the tween isn't moving, it'll last forever
if (speed_scale == 0) {
return INFINITY;
}
pending_update++;
// For each interpolation...
real_t runtime = 0;
for (const List<InterpolateData>::Element *E = interpolates.front(); E; E = E->next()) {
// Get the tween data and see if it's runtime is greater than the previous tweens
const InterpolateData &data = E->get();
real_t t = data.delay + data.duration;
if (t > runtime) {
// This is the longest running tween
runtime = t;
}
}
pending_update--;
// Adjust the runtime for the current speed scale
return runtime / speed_scale;
}
bool Tween::_calc_delta_val(const Variant &p_initial_val, const Variant &p_final_val, Variant &p_delta_val) {
// Get the initial, final, and delta values
const Variant &initial_val = p_initial_val;
const Variant &final_val = p_final_val;
Variant &delta_val = p_delta_val;
// What kind of data are we interpolating?
switch (initial_val.get_type()) {
case Variant::BOOL:
// We'll treat booleans just like integers
case Variant::INT:
// Compute the integer delta
delta_val = (int)final_val - (int)initial_val;
break;
case Variant::REAL:
// Convert to REAL and find the delta
delta_val = (real_t)final_val - (real_t)initial_val;
break;
case Variant::VECTOR2:
// Convert to Vectors and find the delta
delta_val = final_val.operator Vector2() - initial_val.operator Vector2();
break;
case Variant::RECT2: {
// Build a new Rect2 and use the new position and sizes to make a delta
Rect2 i = initial_val;
Rect2 f = final_val;
delta_val = Rect2(f.position - i.position, f.size - i.size);
} break;
case Variant::VECTOR3:
// Convert to Vectors and find the delta
delta_val = final_val.operator Vector3() - initial_val.operator Vector3();
break;
case Variant::TRANSFORM2D: {
// Build a new transform which is the difference between the initial and final values
Transform2D i = initial_val;
Transform2D f = final_val;
Transform2D d = Transform2D();
d[0][0] = f.elements[0][0] - i.elements[0][0];
d[0][1] = f.elements[0][1] - i.elements[0][1];
d[1][0] = f.elements[1][0] - i.elements[1][0];
d[1][1] = f.elements[1][1] - i.elements[1][1];
d[2][0] = f.elements[2][0] - i.elements[2][0];
d[2][1] = f.elements[2][1] - i.elements[2][1];
delta_val = d;
} break;
case Variant::QUAT:
// Convert to quaternianls and find the delta
delta_val = final_val.operator Quat() - initial_val.operator Quat();
break;
case Variant::AABB: {
// Build a new AABB and use the new position and sizes to make a delta
AABB i = initial_val;
AABB f = final_val;
delta_val = AABB(f.position - i.position, f.size - i.size);
} break;
case Variant::BASIS: {
// Build a new basis which is the delta between the initial and final values
Basis i = initial_val;
Basis f = final_val;
delta_val = 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]);
} break;
case Variant::TRANSFORM: {
// Build a new transform which is the difference between the initial and final values
Transform i = initial_val;
Transform f = final_val;
Transform d;
d.set(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);
delta_val = d;
} break;
case Variant::COLOR: {
// Make a new color which is the difference between each the color's RGBA attributes
Color i = initial_val;
Color f = final_val;
delta_val = Color(f.r - i.r, f.g - i.g, f.b - i.b, f.a - i.a);
} break;
default: {
static Variant::Type supported_types[] = {
Variant::BOOL,
Variant::INT,
Variant::REAL,
Variant::VECTOR2,
Variant::RECT2,
Variant::VECTOR3,
Variant::TRANSFORM2D,
Variant::QUAT,
Variant::AABB,
Variant::BASIS,
Variant::TRANSFORM,
Variant::COLOR,
};
int length = *(&supported_types + 1) - supported_types;
String error_msg = "Invalid parameter type. Supported types are: ";
for (int i = 0; i < length; i++) {
if (i != 0) {
error_msg += ", ";
}
error_msg += Variant::get_type_name(supported_types[i]);
}
error_msg += ".";
ERR_PRINT(error_msg);
return false;
}
};
return true;
}
bool Tween::_build_interpolation(InterpolateType p_interpolation_type, Object *p_object, NodePath *p_property, StringName *p_method, Variant p_initial_val, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) {
// TODO: Add initialization+implementation for remaining interpolation types
// TODO: Fix this method's organization to take advantage of the type
// Make a new interpolation data
InterpolateData data;
data.active = true;
data.type = p_interpolation_type;
data.finish = false;
data.elapsed = 0;
// Validate and apply interpolation data
// Give it the object
ERR_FAIL_COND_V_MSG(p_object == nullptr, false, "Invalid object provided to Tween.");
data.id = p_object->get_instance_id();
// Validate the initial and final values
ERR_FAIL_COND_V_MSG(p_initial_val.get_type() != p_final_val.get_type(), false, "Initial value type '" + Variant::get_type_name(p_initial_val.get_type()) + "' does not match final value type '" + Variant::get_type_name(p_final_val.get_type()) + "'.");
data.initial_val = p_initial_val;
data.final_val = p_final_val;
// Check the Duration
ERR_FAIL_COND_V_MSG(p_duration < 0, false, "Only non-negative duration values allowed in Tweens.");
data.duration = p_duration;
// Tween Delay
ERR_FAIL_COND_V_MSG(p_delay < 0, false, "Only non-negative delay values allowed in Tweens.");
data.delay = p_delay;
// Transition type
ERR_FAIL_COND_V_MSG(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false, "Invalid transition type provided to Tween.");
data.trans_type = p_trans_type;
// Easing type
ERR_FAIL_COND_V_MSG(p_ease_type < 0 || p_ease_type >= EASE_COUNT, false, "Invalid easing type provided to Tween.");
data.ease_type = p_ease_type;
// Is the property defined?
if (p_property) {
// Check that the object actually contains the given property
bool prop_valid = false;
p_object->get_indexed(p_property->get_subnames(), &prop_valid);
ERR_FAIL_COND_V_MSG(!prop_valid, false, "Tween target object has no property named: " + p_property->get_concatenated_subnames() + ".");
data.key = p_property->get_subnames();
data.concatenated_key = p_property->get_concatenated_subnames();
}
// Is the method defined?
if (p_method) {
// Does the object even have the requested method?
ERR_FAIL_COND_V_MSG(!p_object->has_method(*p_method), false, "Tween target object has no method named: " + *p_method + ".");
data.key.push_back(*p_method);
data.concatenated_key = *p_method;
}
// Is there not a valid delta?
if (!_calc_delta_val(data.initial_val, data.final_val, data.delta_val)) {
return false;
}
// Add this interpolation to the total
_push_interpolate_data(data);
return true;
}
bool Tween::interpolate_property(Object *p_object, NodePath p_property, Variant p_initial_val, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) {
// If we are busy updating, call this function again later
if (pending_update != 0) {
_add_pending_command("interpolate_property", p_object, p_property, p_initial_val, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay);
return true;
}
// Check that the target object is valid
ERR_FAIL_COND_V_MSG(p_object == nullptr, false, vformat("The Tween \"%s\"'s target node is `null`. Is the node reference correct?", get_name()));
// Get the property from the node path
p_property = p_property.get_as_property_path();
// If no initial value given, grab the initial value from the object
// TODO: Is this documented? This is very useful and removes a lot of clutter from tweens!
if (p_initial_val.get_type() == Variant::NIL) {
p_initial_val = p_object->get_indexed(p_property.get_subnames());
}
// Convert any integers into REALs as they are better for interpolation
if (p_initial_val.get_type() == Variant::INT) {
p_initial_val = p_initial_val.operator real_t();
}
if (p_final_val.get_type() == Variant::INT) {
p_final_val = p_final_val.operator real_t();
}
// Build the interpolation data
bool result = _build_interpolation(INTER_PROPERTY, p_object, &p_property, nullptr, p_initial_val, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay);
return result;
}
bool Tween::interpolate_method(Object *p_object, StringName p_method, Variant p_initial_val, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) {
// If we are busy updating, call this function again later
if (pending_update != 0) {
_add_pending_command("interpolate_method", p_object, p_method, p_initial_val, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay);
return true;
}
// Check that the target object is valid
ERR_FAIL_COND_V_MSG(p_object == nullptr, false, vformat("The Tween \"%s\"'s target node is `null`. Is the node reference correct?", get_name()));
// Convert any integers into REALs as they are better for interpolation
if (p_initial_val.get_type() == Variant::INT) {
p_initial_val = p_initial_val.operator real_t();
}
if (p_final_val.get_type() == Variant::INT) {
p_final_val = p_final_val.operator real_t();
}
// Build the interpolation data
bool result = _build_interpolation(INTER_METHOD, p_object, nullptr, &p_method, p_initial_val, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay);
return result;
}
bool Tween::interpolate_callback(Object *p_object, real_t p_duration, String p_callback, VARIANT_ARG_DECLARE) {
// If we are already updating, call this function again later
if (pending_update != 0) {
_add_pending_command("interpolate_callback", p_object, p_duration, p_callback, p_arg1, p_arg2, p_arg3, p_arg4, p_arg5);
return true;
}
// Check that the target object is valid
ERR_FAIL_COND_V(p_object == nullptr, false);
// Duration cannot be negative
ERR_FAIL_COND_V(p_duration < 0, false);
// Check whether the object even has the callback
ERR_FAIL_COND_V_MSG(!p_object->has_method(p_callback), false, "Object has no callback named: " + p_callback + ".");
// Build a new InterpolationData
InterpolateData data;
data.active = true;
data.type = INTER_CALLBACK;
data.finish = false;
data.call_deferred = false;
data.elapsed = 0;
// Give the data it's configuration
data.id = p_object->get_instance_id();
data.key.push_back(p_callback);
data.concatenated_key = p_callback;
data.duration = p_duration;
data.delay = 0;
// Add arguments to the interpolation
int args = 0;
if (p_arg5.get_type() != Variant::NIL) {
args = 5;
} else if (p_arg4.get_type() != Variant::NIL) {
args = 4;
} else if (p_arg3.get_type() != Variant::NIL) {
args = 3;
} else if (p_arg2.get_type() != Variant::NIL) {
args = 2;
} else if (p_arg1.get_type() != Variant::NIL) {
args = 1;
} else {
args = 0;
}
data.args = args;
data.arg[0] = p_arg1;
data.arg[1] = p_arg2;
data.arg[2] = p_arg3;
data.arg[3] = p_arg4;
data.arg[4] = p_arg5;
// Add the new interpolation
_push_interpolate_data(data);
return true;
}
bool Tween::interpolate_deferred_callback(Object *p_object, real_t p_duration, String p_callback, VARIANT_ARG_DECLARE) {
// If we are already updating, call this function again later
if (pending_update != 0) {
_add_pending_command("interpolate_deferred_callback", p_object, p_duration, p_callback, p_arg1, p_arg2, p_arg3, p_arg4, p_arg5);
return true;
}
// Check that the target object is valid
ERR_FAIL_COND_V(p_object == nullptr, false);
// No negative durations allowed
ERR_FAIL_COND_V(p_duration < 0, false);
// Confirm the callback exists on the object
ERR_FAIL_COND_V_MSG(!p_object->has_method(p_callback), false, "Object has no callback named: " + p_callback + ".");
// Create a new InterpolateData for the callback
InterpolateData data;
data.active = true;
data.type = INTER_CALLBACK;
data.finish = false;
data.call_deferred = true;
data.elapsed = 0;
// Give the data it's configuration
data.id = p_object->get_instance_id();
data.key.push_back(p_callback);
data.concatenated_key = p_callback;
data.duration = p_duration;
data.delay = 0;
// Collect arguments for the callback
static_assert(VARIANT_ARG_MAX == 8, "This code needs to be updated if VARIANT_ARG_MAX != 8");
int args = 0;
if (p_arg8.get_type() != Variant::NIL) {
args = 8;
} else if (p_arg7.get_type() != Variant::NIL) {
args = 7;
} else if (p_arg6.get_type() != Variant::NIL) {
args = 6;
} else if (p_arg5.get_type() != Variant::NIL) {
args = 5;
} else if (p_arg4.get_type() != Variant::NIL) {
args = 4;
} else if (p_arg3.get_type() != Variant::NIL) {
args = 3;
} else if (p_arg2.get_type() != Variant::NIL) {
args = 2;
} else if (p_arg1.get_type() != Variant::NIL) {
args = 1;
} else {
args = 0;
}
data.args = args;
data.arg[0] = p_arg1;
data.arg[1] = p_arg2;
data.arg[2] = p_arg3;
data.arg[3] = p_arg4;
data.arg[4] = p_arg5;
data.arg[5] = p_arg6;
data.arg[6] = p_arg7;
data.arg[7] = p_arg8;
// Add the new interpolation
_push_interpolate_data(data);
return true;
}
bool Tween::follow_property(Object *p_object, NodePath p_property, Variant p_initial_val, Object *p_target, NodePath p_target_property, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) {
// If we are already updating, call this function again later
if (pending_update != 0) {
_add_pending_command("follow_property", p_object, p_property, p_initial_val, p_target, p_target_property, p_duration, p_trans_type, p_ease_type, p_delay);
return true;
}
// Confirm the source and target objects are valid
ERR_FAIL_NULL_V(p_object, false);
ERR_FAIL_NULL_V(p_target, false);
// Get the two properties from their paths
p_property = p_property.get_as_property_path();
p_target_property = p_target_property.get_as_property_path();
// If no initial value is given, grab it from the source object
// TODO: Is this documented? It's really helpful for decluttering tweens
if (p_initial_val.get_type() == Variant::NIL) {
p_initial_val = p_object->get_indexed(p_property.get_subnames());
}
// Convert initial INT values to REAL as they are better for interpolation
if (p_initial_val.get_type() == Variant::INT) {
p_initial_val = p_initial_val.operator real_t();
}
// No negative durations
ERR_FAIL_COND_V(p_duration < 0, false);
// Ensure transition and easing types are valid
ERR_FAIL_COND_V(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false);
ERR_FAIL_COND_V(p_ease_type < 0 || p_ease_type >= EASE_COUNT, false);
// No negative delays
ERR_FAIL_COND_V(p_delay < 0, false);
// Confirm the source and target objects have the desired properties
bool prop_valid = false;
p_object->get_indexed(p_property.get_subnames(), &prop_valid);
ERR_FAIL_COND_V(!prop_valid, false);
bool target_prop_valid = false;
Variant target_val = p_target->get_indexed(p_target_property.get_subnames(), &target_prop_valid);
ERR_FAIL_COND_V(!target_prop_valid, false);
// Convert target INT to REAL since it is better for interpolation
if (target_val.get_type() == Variant::INT) {
target_val = target_val.operator real_t();
}
// Verify that the target value and initial value are the same type
ERR_FAIL_COND_V(target_val.get_type() != p_initial_val.get_type(), false);
// Create a new InterpolateData
InterpolateData data;
data.active = true;
data.type = FOLLOW_PROPERTY;
data.finish = false;
data.elapsed = 0;
// Give the InterpolateData it's configuration
data.id = p_object->get_instance_id();
data.key = p_property.get_subnames();
data.concatenated_key = p_property.get_concatenated_subnames();
data.initial_val = p_initial_val;
data.target_id = p_target->get_instance_id();
data.target_key = p_target_property.get_subnames();
data.duration = p_duration;
data.trans_type = p_trans_type;
data.ease_type = p_ease_type;
data.delay = p_delay;
// Add the interpolation
_push_interpolate_data(data);
return true;
}
bool Tween::follow_method(Object *p_object, StringName p_method, Variant p_initial_val, Object *p_target, StringName p_target_method, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) {
// If we are currently updating, call this function again later
if (pending_update != 0) {
_add_pending_command("follow_method", p_object, p_method, p_initial_val, p_target, p_target_method, p_duration, p_trans_type, p_ease_type, p_delay);
return true;
}
// Convert initial INT values to REAL as they are better for interpolation
if (p_initial_val.get_type() == Variant::INT) {
p_initial_val = p_initial_val.operator real_t();
}
// Verify the source and target objects are valid
ERR_FAIL_COND_V(p_object == nullptr, false);
ERR_FAIL_COND_V(p_target == nullptr, false);
// No negative durations
ERR_FAIL_COND_V(p_duration < 0, false);
// Ensure that the transition and ease types are valid
ERR_FAIL_COND_V(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false);
ERR_FAIL_COND_V(p_ease_type < 0 || p_ease_type >= EASE_COUNT, false);
// No negative delays
ERR_FAIL_COND_V(p_delay < 0, false);
// Confirm both objects have the target methods
ERR_FAIL_COND_V_MSG(!p_object->has_method(p_method), false, "Object has no method named: " + p_method + ".");
ERR_FAIL_COND_V_MSG(!p_target->has_method(p_target_method), false, "Target has no method named: " + p_target_method + ".");
// Call the method to get the target value
Variant::CallError error;
Variant target_val = p_target->call(p_target_method, nullptr, 0, error);
ERR_FAIL_COND_V(error.error != Variant::CallError::CALL_OK, false);
// Convert target INT values to REAL as they are better for interpolation
if (target_val.get_type() == Variant::INT) {
target_val = target_val.operator real_t();
}
ERR_FAIL_COND_V(target_val.get_type() != p_initial_val.get_type(), false);
// Make the new InterpolateData for the method follow
InterpolateData data;
data.active = true;
data.type = FOLLOW_METHOD;
data.finish = false;
data.elapsed = 0;
// Give the data it's configuration
data.id = p_object->get_instance_id();
data.key.push_back(p_method);
data.concatenated_key = p_method;
data.initial_val = p_initial_val;
data.target_id = p_target->get_instance_id();
data.target_key.push_back(p_target_method);
data.duration = p_duration;
data.trans_type = p_trans_type;
data.ease_type = p_ease_type;
data.delay = p_delay;
// Add the new interpolation
_push_interpolate_data(data);
return true;
}
bool Tween::targeting_property(Object *p_object, NodePath p_property, Object *p_initial, NodePath p_initial_property, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) {
// If we are currently updating, call this function again later
if (pending_update != 0) {
_add_pending_command("targeting_property", p_object, p_property, p_initial, p_initial_property, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay);
return true;
}
// Grab the target property and the target property
p_property = p_property.get_as_property_path();
p_initial_property = p_initial_property.get_as_property_path();
// Convert the initial INT values to REAL as they are better for Interpolation
if (p_final_val.get_type() == Variant::INT) {
p_final_val = p_final_val.operator real_t();
}
// Verify both objects are valid
ERR_FAIL_COND_V(p_object == nullptr, false);
ERR_FAIL_COND_V(p_initial == nullptr, false);
// No negative durations
ERR_FAIL_COND_V(p_duration < 0, false);
// Ensure transition and easing types are valid
ERR_FAIL_COND_V(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false);
ERR_FAIL_COND_V(p_ease_type < 0 || p_ease_type >= EASE_COUNT, false);
// No negative delays
ERR_FAIL_COND_V(p_delay < 0, false);
// Ensure the initial and target properties exist on their objects
bool prop_valid = false;
p_object->get_indexed(p_property.get_subnames(), &prop_valid);
ERR_FAIL_COND_V(!prop_valid, false);
bool initial_prop_valid = false;
Variant initial_val = p_initial->get_indexed(p_initial_property.get_subnames(), &initial_prop_valid);
ERR_FAIL_COND_V(!initial_prop_valid, false);
// Convert the initial INT value to REAL as it is better for interpolation
if (initial_val.get_type() == Variant::INT) {
initial_val = initial_val.operator real_t();
}
ERR_FAIL_COND_V(initial_val.get_type() != p_final_val.get_type(), false);
// Build the InterpolateData object
InterpolateData data;
data.active = true;
data.type = TARGETING_PROPERTY;
data.finish = false;
data.elapsed = 0;
// Give the data it's configuration
data.id = p_object->get_instance_id();
data.key = p_property.get_subnames();
data.concatenated_key = p_property.get_concatenated_subnames();
data.target_id = p_initial->get_instance_id();
data.target_key = p_initial_property.get_subnames();
data.initial_val = initial_val;
data.final_val = p_final_val;
data.duration = p_duration;
data.trans_type = p_trans_type;
data.ease_type = p_ease_type;
data.delay = p_delay;
// Ensure there is a valid delta
if (!_calc_delta_val(data.initial_val, data.final_val, data.delta_val)) {
return false;
}
// Add the interpolation
_push_interpolate_data(data);
return true;
}
bool Tween::targeting_method(Object *p_object, StringName p_method, Object *p_initial, StringName p_initial_method, Variant p_final_val, real_t p_duration, TransitionType p_trans_type, EaseType p_ease_type, real_t p_delay) {
// If we are currently updating, call this function again later
if (pending_update != 0) {
_add_pending_command("targeting_method", p_object, p_method, p_initial, p_initial_method, p_final_val, p_duration, p_trans_type, p_ease_type, p_delay);
return true;
}
// Convert final INT values to REAL as they are better for interpolation
if (p_final_val.get_type() == Variant::INT) {
p_final_val = p_final_val.operator real_t();
}
// Make sure the given objects are valid
ERR_FAIL_COND_V(p_object == nullptr, false);
ERR_FAIL_COND_V(p_initial == nullptr, false);
// No negative durations
ERR_FAIL_COND_V(p_duration < 0, false);
// Ensure transition and easing types are valid
ERR_FAIL_COND_V(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false);
ERR_FAIL_COND_V(p_ease_type < 0 || p_ease_type >= EASE_COUNT, false);
// No negative delays
ERR_FAIL_COND_V(p_delay < 0, false);
// Make sure both objects have the given method
ERR_FAIL_COND_V_MSG(!p_object->has_method(p_method), false, "Object has no method named: " + p_method + ".");
ERR_FAIL_COND_V_MSG(!p_initial->has_method(p_initial_method), false, "Initial Object has no method named: " + p_initial_method + ".");
// Call the method to get the initial value
Variant::CallError error;
Variant initial_val = p_initial->call(p_initial_method, nullptr, 0, error);
ERR_FAIL_COND_V(error.error != Variant::CallError::CALL_OK, false);
// Convert initial INT values to REAL as they aer better for interpolation
if (initial_val.get_type() == Variant::INT) {
initial_val = initial_val.operator real_t();
}
ERR_FAIL_COND_V(initial_val.get_type() != p_final_val.get_type(), false);
// Build the new InterpolateData object
InterpolateData data;
data.active = true;
data.type = TARGETING_METHOD;
data.finish = false;
data.elapsed = 0;
// Configure the data
data.id = p_object->get_instance_id();
data.key.push_back(p_method);
data.concatenated_key = p_method;
data.target_id = p_initial->get_instance_id();
data.target_key.push_back(p_initial_method);
data.initial_val = initial_val;
data.final_val = p_final_val;
data.duration = p_duration;
data.trans_type = p_trans_type;
data.ease_type = p_ease_type;
data.delay = p_delay;
// Ensure there is a valid delta
if (!_calc_delta_val(data.initial_val, data.final_val, data.delta_val)) {
return false;
}
// Add the interpolation
_push_interpolate_data(data);
return true;
}
Tween::Tween() {
// Initialize tween attributes
tween_process_mode = TWEEN_PROCESS_IDLE;
repeat = false;
speed_scale = 1;
pending_update = 0;
uid = 0;
}
Tween::~Tween() {
}