mirror of
https://github.com/godotengine/godot.git
synced 2024-11-10 14:12:51 +00:00
implement ping-pong loop in animation
Co-authored-by: Chaosus <chaosus89@gmail.com>
This commit is contained in:
parent
e8c89b2b91
commit
372ba76663
@ -291,6 +291,19 @@ public:
|
||||
return is_zero_approx(range) ? min : value - (range * Math::floor((value - min) / range));
|
||||
}
|
||||
|
||||
static _ALWAYS_INLINE_ float fract(float value) {
|
||||
return value - floor(value);
|
||||
}
|
||||
static _ALWAYS_INLINE_ double fract(double value) {
|
||||
return value - floor(value);
|
||||
}
|
||||
static _ALWAYS_INLINE_ float pingpong(float value, float length) {
|
||||
return (length != 0.0f) ? abs(fract((value - length) / (length * 2.0f)) * length * 2.0f - length) : 0.0f;
|
||||
}
|
||||
static _ALWAYS_INLINE_ double pingpong(double value, double length) {
|
||||
return (length != 0.0) ? abs(fract((value - length) / (length * 2.0)) * length * 2.0 - length) : 0.0;
|
||||
}
|
||||
|
||||
// double only, as these functions are mainly used by the editor and not performance-critical,
|
||||
static double ease(double p_x, double p_c);
|
||||
static int step_decimals(double p_step);
|
||||
|
@ -275,6 +275,10 @@ struct VariantUtilityFunctions {
|
||||
return Math::wrapf(value, min, max);
|
||||
}
|
||||
|
||||
static inline double pingpong(double value, double length) {
|
||||
return Math::pingpong(value, length);
|
||||
}
|
||||
|
||||
static inline Variant max(const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
|
||||
if (p_argcount < 2) {
|
||||
r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
|
||||
@ -1226,6 +1230,7 @@ void Variant::_register_variant_utility_functions() {
|
||||
FUNCBINDR(clampf, sarray("value", "min", "max"), Variant::UTILITY_FUNC_TYPE_MATH);
|
||||
|
||||
FUNCBINDR(nearest_po2, sarray("value"), Variant::UTILITY_FUNC_TYPE_MATH);
|
||||
FUNCBINDR(pingpong, sarray("value", "length"), Variant::UTILITY_FUNC_TYPE_MATH);
|
||||
|
||||
// Random
|
||||
|
||||
|
@ -525,6 +525,26 @@
|
||||
[b]Warning:[/b] Due to the way it is implemented, this function returns [code]0[/code] rather than [code]1[/code] for non-positive values of [code]value[/code] (in reality, 1 is the smallest integer power of 2).
|
||||
</description>
|
||||
</method>
|
||||
<method name="pingpong">
|
||||
<return type="float" />
|
||||
<argument index="0" name="value" type="float" />
|
||||
<argument index="1" name="length" type="float" />
|
||||
<description>
|
||||
Returns the [code]value[/code] wrapped between [code]0[/code] and the [code]length[/code]. If the limit is reached, the next value the function returned is decreased to the [code]0[/code] side or increased to the [code]length[/code] side (like a triangle wave). If [code]length[/code] is less than zero, it becomes positive.
|
||||
[codeblock]
|
||||
pingpong(-3.0, 3.0) # Returns 3
|
||||
pingpong(-2.0, 3.0) # Returns 2
|
||||
pingpong(-1.0, 3.0) # Returns 1
|
||||
pingpong(0.0, 3.0) # Returns 0
|
||||
pingpong(1.0, 3.0) # Returns 1
|
||||
pingpong(2.0, 3.0) # Returns 2
|
||||
pingpong(3.0, 3.0) # Returns 3
|
||||
pingpong(4.0, 3.0) # Returns 2
|
||||
pingpong(5.0, 3.0) # Returns 1
|
||||
pingpong(6.0, 3.0) # Returns 0
|
||||
[/codeblock]
|
||||
</description>
|
||||
</method>
|
||||
<method name="posmod">
|
||||
<return type="int" />
|
||||
<argument index="0" name="x" type="int" />
|
||||
|
@ -481,6 +481,7 @@
|
||||
<return type="Array" />
|
||||
<argument index="0" name="track_idx" type="int" />
|
||||
<argument index="1" name="time_sec" type="float" />
|
||||
<argument index="2" name="is_backward" type="bool" default="false" />
|
||||
<description>
|
||||
Returns the interpolated value of a transform track at a given time (in seconds). An array consisting of 3 elements: position ([Vector3]), rotation ([Quaternion]) and scale ([Vector3]).
|
||||
</description>
|
||||
@ -523,8 +524,8 @@
|
||||
The total length of the animation (in seconds).
|
||||
[b]Note:[/b] Length is not delimited by the last key, as this one may be before or after the end to ensure correct interpolation and looping.
|
||||
</member>
|
||||
<member name="loop" type="bool" setter="set_loop" getter="has_loop" default="false">
|
||||
A flag indicating that the animation must loop. This is used for correct interpolation of animation cycles, and for hinting the player that it must restart the animation.
|
||||
<member name="loop_mode" type="int" setter="set_loop_mode" getter="get_loop_mode" enum="Animation.LoopMode" default="0">
|
||||
Determines the behavior of both ends of the animation timeline during animation playback. This is used for correct interpolation of animation cycles, and for hinting the player that it must restart the animation.
|
||||
</member>
|
||||
<member name="step" type="float" setter="set_step" getter="get_step" default="0.1">
|
||||
The animation step value.
|
||||
@ -577,5 +578,14 @@
|
||||
<constant name="UPDATE_CAPTURE" value="3" enum="UpdateMode">
|
||||
Same as linear interpolation, but also interpolates from the current value (i.e. dynamically at runtime) if the first key isn't at 0 seconds.
|
||||
</constant>
|
||||
<constant name="LOOP_NONE" value="0" enum="LoopMode">
|
||||
At both ends of the animation, the animation will stop playing.
|
||||
</constant>
|
||||
<constant name="LOOP_LINEAR" value="1" enum="LoopMode">
|
||||
At both ends of the animation, the animation will be repeated without changing the playback direction.
|
||||
</constant>
|
||||
<constant name="LOOP_PINGPONG" value="2" enum="LoopMode">
|
||||
Repeats playback and reverse playback at both ends of the animation.
|
||||
</constant>
|
||||
</constants>
|
||||
</class>
|
||||
|
@ -73,6 +73,7 @@
|
||||
<argument index="2" name="delta" type="float" />
|
||||
<argument index="3" name="seeked" type="bool" />
|
||||
<argument index="4" name="blend" type="float" />
|
||||
<argument index="5" name="pingponged" type="int" default="0" />
|
||||
<description>
|
||||
Blend an animation by [code]blend[/code] amount (name must be valid in the linked [AnimationPlayer]). A [code]time[/code] and [code]delta[/code] may be passed, as well as whether [code]seek[/code] happened.
|
||||
</description>
|
||||
|
@ -15,5 +15,14 @@
|
||||
<member name="animation" type="StringName" setter="set_animation" getter="get_animation" default="&""">
|
||||
Animation to use as an output. It is one of the animations provided by [member AnimationTree.anim_player].
|
||||
</member>
|
||||
<member name="play_mode" type="int" setter="set_play_mode" getter="get_play_mode" enum="AnimationNodeAnimation.PlayMode" default="0">
|
||||
Determines the playback direction of the animation.
|
||||
</member>
|
||||
</members>
|
||||
<constants>
|
||||
<constant name="PLAY_MODE_FORWARD" value="0" enum="PlayMode">
|
||||
</constant>
|
||||
<constant name="PLAY_MODE_BACKWARD" value="1" enum="PlayMode">
|
||||
</constant>
|
||||
</constants>
|
||||
</class>
|
||||
|
@ -61,7 +61,7 @@
|
||||
<constant name="LOOP_FORWARD" value="1" enum="LoopMode">
|
||||
Audio loops the data between [member loop_begin] and [member loop_end], playing forward only.
|
||||
</constant>
|
||||
<constant name="LOOP_PING_PONG" value="2" enum="LoopMode">
|
||||
<constant name="LOOP_PINGPONG" value="2" enum="LoopMode">
|
||||
Audio loops the data between [member loop_begin] and [member loop_end], playing back and forth.
|
||||
</constant>
|
||||
<constant name="LOOP_BACKWARD" value="3" enum="LoopMode">
|
||||
|
@ -1323,8 +1323,20 @@ void AnimationTimelineEdit::_anim_length_changed(double p_new_len) {
|
||||
|
||||
void AnimationTimelineEdit::_anim_loop_pressed() {
|
||||
undo_redo->create_action(TTR("Change Animation Loop"));
|
||||
undo_redo->add_do_method(animation.ptr(), "set_loop", loop->is_pressed());
|
||||
undo_redo->add_undo_method(animation.ptr(), "set_loop", animation->has_loop());
|
||||
switch (animation->get_loop_mode()) {
|
||||
case Animation::LoopMode::LOOP_NONE: {
|
||||
undo_redo->add_do_method(animation.ptr(), "set_loop_mode", Animation::LoopMode::LOOP_LINEAR);
|
||||
} break;
|
||||
case Animation::LoopMode::LOOP_LINEAR: {
|
||||
undo_redo->add_do_method(animation.ptr(), "set_loop_mode", Animation::LoopMode::LOOP_PINGPONG);
|
||||
} break;
|
||||
case Animation::LoopMode::LOOP_PINGPONG: {
|
||||
undo_redo->add_do_method(animation.ptr(), "set_loop_mode", Animation::LoopMode::LOOP_NONE);
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
undo_redo->add_undo_method(animation.ptr(), "set_loop_mode", animation->get_loop_mode());
|
||||
undo_redo->commit_action();
|
||||
}
|
||||
|
||||
@ -1607,7 +1619,24 @@ void AnimationTimelineEdit::update_values() {
|
||||
length->set_tooltip(TTR("Animation length (seconds)"));
|
||||
time_icon->set_tooltip(TTR("Animation length (seconds)"));
|
||||
}
|
||||
loop->set_pressed(animation->has_loop());
|
||||
|
||||
switch (animation->get_loop_mode()) {
|
||||
case Animation::LoopMode::LOOP_NONE: {
|
||||
loop->set_icon(get_theme_icon("Loop", "EditorIcons"));
|
||||
loop->set_pressed(false);
|
||||
} break;
|
||||
case Animation::LoopMode::LOOP_LINEAR: {
|
||||
loop->set_icon(get_theme_icon("Loop", "EditorIcons"));
|
||||
loop->set_pressed(true);
|
||||
} break;
|
||||
case Animation::LoopMode::LOOP_PINGPONG: {
|
||||
loop->set_icon(get_theme_icon("PingPongLoop", "EditorIcons"));
|
||||
loop->set_pressed(true);
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
editing = false;
|
||||
}
|
||||
|
||||
@ -2050,25 +2079,25 @@ void AnimationTrackEdit::_notification(int p_what) {
|
||||
|
||||
Ref<Texture2D> icon = wrap_icon[loop_wrap ? 1 : 0];
|
||||
|
||||
loop_mode_rect.position.x = ofs;
|
||||
loop_mode_rect.position.y = int(get_size().height - icon->get_height()) / 2;
|
||||
loop_mode_rect.size = icon->get_size();
|
||||
loop_wrap_rect.position.x = ofs;
|
||||
loop_wrap_rect.position.y = int(get_size().height - icon->get_height()) / 2;
|
||||
loop_wrap_rect.size = icon->get_size();
|
||||
|
||||
if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_TRANSFORM3D) {
|
||||
draw_texture(icon, loop_mode_rect.position);
|
||||
draw_texture(icon, loop_wrap_rect.position);
|
||||
}
|
||||
|
||||
loop_mode_rect.position.y = 0;
|
||||
loop_mode_rect.size.y = get_size().height;
|
||||
loop_wrap_rect.position.y = 0;
|
||||
loop_wrap_rect.size.y = get_size().height;
|
||||
|
||||
ofs += icon->get_width() + hsep;
|
||||
loop_mode_rect.size.x += hsep;
|
||||
loop_wrap_rect.size.x += hsep;
|
||||
|
||||
if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_TRANSFORM3D) {
|
||||
draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2));
|
||||
loop_mode_rect.size.x += down_icon->get_width();
|
||||
loop_wrap_rect.size.x += down_icon->get_width();
|
||||
} else {
|
||||
loop_mode_rect = Rect2();
|
||||
loop_wrap_rect = Rect2();
|
||||
}
|
||||
|
||||
ofs += down_icon->get_width();
|
||||
@ -2415,7 +2444,7 @@ String AnimationTrackEdit::get_tooltip(const Point2 &p_pos) const {
|
||||
return TTR("Interpolation Mode");
|
||||
}
|
||||
|
||||
if (loop_mode_rect.has_point(p_pos)) {
|
||||
if (loop_wrap_rect.has_point(p_pos)) {
|
||||
return TTR("Loop Wrap Mode (Interpolate end with beginning on loop)");
|
||||
}
|
||||
|
||||
@ -2614,7 +2643,7 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
|
||||
accept_event();
|
||||
}
|
||||
|
||||
if (loop_mode_rect.has_point(pos)) {
|
||||
if (loop_wrap_rect.has_point(pos)) {
|
||||
if (!menu) {
|
||||
menu = memnew(PopupMenu);
|
||||
add_child(menu);
|
||||
@ -2625,7 +2654,7 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
|
||||
menu->add_icon_item(get_theme_icon(SNAME("InterpWrapLoop"), SNAME("EditorIcons")), TTR("Wrap Loop Interp"), MENU_LOOP_WRAP);
|
||||
menu->set_as_minsize();
|
||||
|
||||
Vector2 popup_pos = get_screen_position() + loop_mode_rect.position + Vector2(0, loop_mode_rect.size.height);
|
||||
Vector2 popup_pos = get_screen_position() + loop_wrap_rect.position + Vector2(0, loop_wrap_rect.size.height);
|
||||
menu->set_position(popup_pos);
|
||||
menu->popup();
|
||||
accept_event();
|
||||
|
@ -159,7 +159,7 @@ class AnimationTrackEdit : public Control {
|
||||
|
||||
Rect2 update_mode_rect;
|
||||
Rect2 interp_mode_rect;
|
||||
Rect2 loop_mode_rect;
|
||||
Rect2 loop_wrap_rect;
|
||||
Rect2 remove_rect;
|
||||
Rect2 bezier_edit_rect;
|
||||
|
||||
@ -466,6 +466,7 @@ class AnimationTrackEditor : public VBoxContainer {
|
||||
Animation::TrackType track_type = Animation::TrackType::TYPE_ANIMATION;
|
||||
Animation::InterpolationType interp_type = Animation::InterpolationType::INTERPOLATION_CUBIC;
|
||||
Animation::UpdateMode update_mode = Animation::UpdateMode::UPDATE_CAPTURE;
|
||||
Animation::LoopMode loop_mode = Animation::LoopMode::LOOP_LINEAR;
|
||||
bool loop_wrap = false;
|
||||
bool enabled = false;
|
||||
|
||||
|
1
editor/icons/PingPongLoop.svg
Normal file
1
editor/icons/PingPongLoop.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg enable-background="new 0 0 16 16" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill="#e0e0e0" fill-opacity=".9961"><path d="m10 7h-4v-2l-4 3 4 3v-2h4v2l4-3-4-3z"/><path d="m0 1v14h2v-7-7z"/><path d="m14 1v7 7h2v-14z"/></g></svg>
|
After Width: | Height: | Size: 270 B |
@ -317,10 +317,10 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, Map<Ref<I
|
||||
|
||||
String animname = E;
|
||||
const int loop_string_count = 3;
|
||||
static const char *loop_strings[loop_string_count] = { "loops", "loop", "cycle" };
|
||||
static const char *loop_strings[loop_string_count] = { "loop_mode", "loop", "cycle" };
|
||||
for (int i = 0; i < loop_string_count; i++) {
|
||||
if (_teststr(animname, loop_strings[i])) {
|
||||
anim->set_loop(true);
|
||||
anim->set_loop_mode(Animation::LoopMode::LOOP_LINEAR);
|
||||
animname = _fixstr(animname, loop_strings[i]);
|
||||
ap->rename_animation(E, animname);
|
||||
}
|
||||
@ -732,7 +732,7 @@ Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, Map<Ref<
|
||||
String name = node_settings["clip_" + itos(i + 1) + "/name"];
|
||||
int from_frame = node_settings["clip_" + itos(i + 1) + "/start_frame"];
|
||||
int end_frame = node_settings["clip_" + itos(i + 1) + "/end_frame"];
|
||||
bool loop = node_settings["clip_" + itos(i + 1) + "/loops"];
|
||||
Animation::LoopMode loop_mode = static_cast<Animation::LoopMode>((int)node_settings["clip_" + itos(i + 1) + "/loop_mode"]);
|
||||
bool save_to_file = node_settings["clip_" + itos(i + 1) + "/save_to_file/enabled"];
|
||||
bool save_to_path = node_settings["clip_" + itos(i + 1) + "/save_to_file/path"];
|
||||
bool save_to_file_keep_custom = node_settings["clip_" + itos(i + 1) + "/save_to_file/keep_custom_tracks"];
|
||||
@ -740,7 +740,7 @@ Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, Map<Ref<
|
||||
animation_clips.push_back(name);
|
||||
animation_clips.push_back(from_frame / p_animation_fps);
|
||||
animation_clips.push_back(end_frame / p_animation_fps);
|
||||
animation_clips.push_back(loop);
|
||||
animation_clips.push_back(loop_mode);
|
||||
animation_clips.push_back(save_to_file);
|
||||
animation_clips.push_back(save_to_path);
|
||||
animation_clips.push_back(save_to_file_keep_custom);
|
||||
@ -767,7 +767,7 @@ Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, Map<Ref<
|
||||
}
|
||||
}
|
||||
|
||||
anim->set_loop(anim_settings["settings/loops"]);
|
||||
anim->set_loop_mode(static_cast<Animation::LoopMode>((int)anim_settings["settings/loop_mode"]));
|
||||
bool save = anim_settings["save_to_file/enabled"];
|
||||
String path = anim_settings["save_to_file/path"];
|
||||
bool keep_custom = anim_settings["save_to_file/keep_custom_tracks"];
|
||||
@ -799,7 +799,7 @@ Ref<Animation> ResourceImporterScene::_save_animation_to_file(Ref<Animation> ani
|
||||
old_anim->copy_track(i, anim);
|
||||
}
|
||||
}
|
||||
anim->set_loop(old_anim->has_loop());
|
||||
anim->set_loop_mode(old_anim->get_loop_mode());
|
||||
}
|
||||
}
|
||||
|
||||
@ -827,7 +827,7 @@ void ResourceImporterScene::_create_clips(AnimationPlayer *anim, const Array &p_
|
||||
String name = p_clips[i];
|
||||
float from = p_clips[i + 1];
|
||||
float to = p_clips[i + 2];
|
||||
bool loop = p_clips[i + 3];
|
||||
Animation::LoopMode loop_mode = static_cast<Animation::LoopMode>((int)p_clips[i + 3]);
|
||||
bool save_to_file = p_clips[i + 4];
|
||||
String save_to_path = p_clips[i + 5];
|
||||
bool keep_current = p_clips[i + 6];
|
||||
@ -915,7 +915,7 @@ void ResourceImporterScene::_create_clips(AnimationPlayer *anim, const Array &p_
|
||||
}
|
||||
}
|
||||
|
||||
new_anim->set_loop(loop);
|
||||
new_anim->set_loop_mode(loop_mode);
|
||||
new_anim->set_length(to - from);
|
||||
anim->add_animation(name, new_anim);
|
||||
|
||||
@ -989,7 +989,7 @@ void ResourceImporterScene::get_internal_import_options(InternalImportCategory p
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "use_external/path", PROPERTY_HINT_FILE, "*.material,*.res,*.tres"), ""));
|
||||
} break;
|
||||
case INTERNAL_IMPORT_CATEGORY_ANIMATION: {
|
||||
r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::BOOL, "settings/loops"), false));
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "settings/loop_mode"), 0));
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "save_to_file/enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false));
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "save_to_file/path", PROPERTY_HINT_SAVE_FILE, "*.res,*.tres"), ""));
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "save_to_file/keep_custom_tracks"), ""));
|
||||
@ -1006,7 +1006,7 @@ void ResourceImporterScene::get_internal_import_options(InternalImportCategory p
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "slice_" + itos(i + 1) + "/name"), ""));
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "slice_" + itos(i + 1) + "/start_frame"), 0));
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "slice_" + itos(i + 1) + "/end_frame"), 0));
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "slice_" + itos(i + 1) + "/loops"), false));
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "slice_" + itos(i + 1) + "/loop_mode"), 0));
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "slice_" + itos(i + 1) + "/save_to_file/enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false));
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "slice_" + itos(i + 1) + "/save_to_file/path", PROPERTY_HINT_SAVE_FILE, ".res,*.tres"), ""));
|
||||
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "slice_" + itos(i + 1) + "/save_to_file/keep_custom_tracks"), false));
|
||||
|
@ -272,7 +272,7 @@ Error ResourceImporterWAV::import(const String &p_source_file, const String &p_s
|
||||
if (loop_type == 0x00) {
|
||||
loop = AudioStreamSample::LOOP_FORWARD;
|
||||
} else if (loop_type == 0x01) {
|
||||
loop = AudioStreamSample::LOOP_PING_PONG;
|
||||
loop = AudioStreamSample::LOOP_PINGPONG;
|
||||
} else if (loop_type == 0x02) {
|
||||
loop = AudioStreamSample::LOOP_BACKWARD;
|
||||
}
|
||||
|
@ -41,6 +41,7 @@
|
||||
#include "editor/plugins/canvas_item_editor_plugin.h" // For onion skinning.
|
||||
#include "editor/plugins/node_3d_editor_plugin.h" // For onion skinning.
|
||||
#include "scene/main/window.h"
|
||||
#include "scene/resources/animation.h"
|
||||
#include "servers/rendering_server.h"
|
||||
|
||||
void AnimationPlayerEditor::_node_removed(Node *p_node) {
|
||||
@ -72,7 +73,7 @@ void AnimationPlayerEditor::_notification(int p_what) {
|
||||
if (player->has_animation(animname)) {
|
||||
Ref<Animation> anim = player->get_animation(animname);
|
||||
if (!anim.is_null()) {
|
||||
frame->set_max(anim->get_length());
|
||||
frame->set_max((double)anim->get_length());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -289,7 +290,7 @@ void AnimationPlayerEditor::_animation_selected(int p_which) {
|
||||
track_editor->set_root(root);
|
||||
}
|
||||
}
|
||||
frame->set_max(anim->get_length());
|
||||
frame->set_max((double)anim->get_length());
|
||||
|
||||
} else {
|
||||
track_editor->set_animation(Ref<Animation>());
|
||||
@ -1014,7 +1015,7 @@ void AnimationPlayerEditor::_seek_value_changed(float p_value, bool p_set, bool
|
||||
Ref<Animation> anim;
|
||||
anim = player->get_animation(current);
|
||||
|
||||
float pos = CLAMP(anim->get_length() * (p_value / frame->get_max()), 0, anim->get_length());
|
||||
float pos = CLAMP((double)anim->get_length() * (p_value / frame->get_max()), 0, (double)anim->get_length());
|
||||
if (track_editor->is_snap_enabled()) {
|
||||
pos = Math::snapped(pos, _get_editor_step());
|
||||
}
|
||||
@ -1424,7 +1425,7 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() {
|
||||
|
||||
float pos = cpos + step_off * anim->get_step();
|
||||
|
||||
bool valid = anim->has_loop() || (pos >= 0 && pos <= anim->get_length());
|
||||
bool valid = anim->get_loop_mode() != Animation::LoopMode::LOOP_NONE || (pos >= 0 && pos <= anim->get_length());
|
||||
onion.captures_valid.write[cidx] = valid;
|
||||
if (valid) {
|
||||
player->seek(pos, true);
|
||||
|
@ -5815,7 +5815,7 @@ void GLTFDocument::_import_animation(Ref<GLTFState> state, AnimationPlayer *ap,
|
||||
animation->set_name(name);
|
||||
|
||||
if (anim->get_loop()) {
|
||||
animation->set_loop(true);
|
||||
animation->set_loop_mode(Animation::LOOP_LINEAR);
|
||||
}
|
||||
|
||||
float length = 0.0;
|
||||
|
@ -693,5 +693,23 @@ namespace Godot
|
||||
}
|
||||
return min + ((((value - min) % range) + range) % range);
|
||||
}
|
||||
|
||||
private static real_t Fract(real_t value)
|
||||
{
|
||||
return value - (real_t)Math.Floor(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the [code]value[/code] wrapped between [code]0[/code] and the [code]length[/code].
|
||||
/// If the limit is reached, the next value the function returned is decreased to the [code]0[/code] side or increased to the [code]length[/code] side (like a triangle wave).
|
||||
/// If [code]length[/code] is less than zero, it becomes positive.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to pingpong.</param>
|
||||
/// <param name="length">The maximum value of the function.</param>
|
||||
/// <returns>The ping-ponged value.</returns>
|
||||
public static real_t PingPong(real_t value, real_t length)
|
||||
{
|
||||
return (length != 0.0) ? Math.Abs(Mathf.Fract((value - length) / (length * 2.0)) * length * 2.0 - length) : 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -140,73 +140,76 @@
|
||||
</constant>
|
||||
<constant name="MATH_WRAPF" value="42" enum="BuiltinFunc">
|
||||
</constant>
|
||||
<constant name="LOGIC_MAX" value="43" enum="BuiltinFunc">
|
||||
<constant name="MATH_PINGPONG" value="43" enum="BuiltinFunc">
|
||||
Return the [code]value[/code] wrapped between [code]0[/code] and the [code]length[/code]. If the limit is reached, the next value the function returned is decreased to the [code]0[/code] side or increased to the [code]length[/code] side (like a triangle wave). If [code]length[/code] is less than zero, it becomes positive.
|
||||
</constant>
|
||||
<constant name="LOGIC_MAX" value="44" enum="BuiltinFunc">
|
||||
Return the greater of the two numbers, also known as their maximum.
|
||||
</constant>
|
||||
<constant name="LOGIC_MIN" value="44" enum="BuiltinFunc">
|
||||
<constant name="LOGIC_MIN" value="45" enum="BuiltinFunc">
|
||||
Return the lesser of the two numbers, also known as their minimum.
|
||||
</constant>
|
||||
<constant name="LOGIC_CLAMP" value="45" enum="BuiltinFunc">
|
||||
<constant name="LOGIC_CLAMP" value="46" enum="BuiltinFunc">
|
||||
Return the input clamped inside the given range, ensuring the result is never outside it. Equivalent to [code]min(max(input, range_low), range_high)[/code].
|
||||
</constant>
|
||||
<constant name="LOGIC_NEAREST_PO2" value="46" enum="BuiltinFunc">
|
||||
<constant name="LOGIC_NEAREST_PO2" value="47" enum="BuiltinFunc">
|
||||
Return the nearest power of 2 to the input.
|
||||
</constant>
|
||||
<constant name="OBJ_WEAKREF" value="47" enum="BuiltinFunc">
|
||||
<constant name="OBJ_WEAKREF" value="48" enum="BuiltinFunc">
|
||||
Create a [WeakRef] from the input.
|
||||
</constant>
|
||||
<constant name="TYPE_CONVERT" value="48" enum="BuiltinFunc">
|
||||
<constant name="TYPE_CONVERT" value="49" enum="BuiltinFunc">
|
||||
Convert between types.
|
||||
</constant>
|
||||
<constant name="TYPE_OF" value="49" enum="BuiltinFunc">
|
||||
<constant name="TYPE_OF" value="50" enum="BuiltinFunc">
|
||||
Return the type of the input as an integer. Check [enum Variant.Type] for the integers that might be returned.
|
||||
</constant>
|
||||
<constant name="TYPE_EXISTS" value="50" enum="BuiltinFunc">
|
||||
<constant name="TYPE_EXISTS" value="51" enum="BuiltinFunc">
|
||||
Checks if a type is registered in the [ClassDB].
|
||||
</constant>
|
||||
<constant name="TEXT_CHAR" value="51" enum="BuiltinFunc">
|
||||
<constant name="TEXT_CHAR" value="52" enum="BuiltinFunc">
|
||||
Return a character with the given ascii value.
|
||||
</constant>
|
||||
<constant name="TEXT_STR" value="52" enum="BuiltinFunc">
|
||||
<constant name="TEXT_STR" value="53" enum="BuiltinFunc">
|
||||
Convert the input to a string.
|
||||
</constant>
|
||||
<constant name="TEXT_PRINT" value="53" enum="BuiltinFunc">
|
||||
<constant name="TEXT_PRINT" value="54" enum="BuiltinFunc">
|
||||
Print the given string to the output window.
|
||||
</constant>
|
||||
<constant name="TEXT_PRINTERR" value="54" enum="BuiltinFunc">
|
||||
<constant name="TEXT_PRINTERR" value="55" enum="BuiltinFunc">
|
||||
Print the given string to the standard error output.
|
||||
</constant>
|
||||
<constant name="TEXT_PRINTRAW" value="55" enum="BuiltinFunc">
|
||||
<constant name="TEXT_PRINTRAW" value="56" enum="BuiltinFunc">
|
||||
Print the given string to the standard output, without adding a newline.
|
||||
</constant>
|
||||
<constant name="TEXT_PRINT_VERBOSE" value="56" enum="BuiltinFunc">
|
||||
<constant name="TEXT_PRINT_VERBOSE" value="57" enum="BuiltinFunc">
|
||||
</constant>
|
||||
<constant name="VAR_TO_STR" value="57" enum="BuiltinFunc">
|
||||
<constant name="VAR_TO_STR" value="58" enum="BuiltinFunc">
|
||||
Serialize a [Variant] to a string.
|
||||
</constant>
|
||||
<constant name="STR_TO_VAR" value="58" enum="BuiltinFunc">
|
||||
<constant name="STR_TO_VAR" value="59" enum="BuiltinFunc">
|
||||
Deserialize a [Variant] from a string serialized using [constant VAR_TO_STR].
|
||||
</constant>
|
||||
<constant name="VAR_TO_BYTES" value="59" enum="BuiltinFunc">
|
||||
<constant name="VAR_TO_BYTES" value="60" enum="BuiltinFunc">
|
||||
Serialize a [Variant] to a [PackedByteArray].
|
||||
</constant>
|
||||
<constant name="BYTES_TO_VAR" value="60" enum="BuiltinFunc">
|
||||
<constant name="BYTES_TO_VAR" value="61" enum="BuiltinFunc">
|
||||
Deserialize a [Variant] from a [PackedByteArray] serialized using [constant VAR_TO_BYTES].
|
||||
</constant>
|
||||
<constant name="MATH_SMOOTHSTEP" value="61" enum="BuiltinFunc">
|
||||
<constant name="MATH_SMOOTHSTEP" value="62" enum="BuiltinFunc">
|
||||
Return a number smoothly interpolated between the first two inputs, based on the third input. Similar to [constant MATH_LERP], but interpolates faster at the beginning and slower at the end. Using Hermite interpolation formula:
|
||||
[codeblock]
|
||||
var t = clamp((weight - from) / (to - from), 0.0, 1.0)
|
||||
return t * t * (3.0 - 2.0 * t)
|
||||
[/codeblock]
|
||||
</constant>
|
||||
<constant name="MATH_POSMOD" value="62" enum="BuiltinFunc">
|
||||
<constant name="MATH_POSMOD" value="63" enum="BuiltinFunc">
|
||||
</constant>
|
||||
<constant name="MATH_LERP_ANGLE" value="63" enum="BuiltinFunc">
|
||||
<constant name="MATH_LERP_ANGLE" value="64" enum="BuiltinFunc">
|
||||
</constant>
|
||||
<constant name="TEXT_ORD" value="64" enum="BuiltinFunc">
|
||||
<constant name="TEXT_ORD" value="65" enum="BuiltinFunc">
|
||||
</constant>
|
||||
<constant name="FUNC_MAX" value="65" enum="BuiltinFunc">
|
||||
<constant name="FUNC_MAX" value="66" enum="BuiltinFunc">
|
||||
Represents the size of the [enum BuiltinFunc] enum.
|
||||
</constant>
|
||||
</constants>
|
||||
|
@ -81,6 +81,7 @@ const char *VisualScriptBuiltinFunc::func_name[VisualScriptBuiltinFunc::FUNC_MAX
|
||||
"db2linear",
|
||||
"wrapi",
|
||||
"wrapf",
|
||||
"pingpong",
|
||||
"max",
|
||||
"min",
|
||||
"clamp",
|
||||
@ -190,6 +191,7 @@ int VisualScriptBuiltinFunc::get_func_argument_count(BuiltinFunc p_func) {
|
||||
case MATH_FMOD:
|
||||
case MATH_FPOSMOD:
|
||||
case MATH_POSMOD:
|
||||
case MATH_PINGPONG:
|
||||
case MATH_POW:
|
||||
case MATH_EASE:
|
||||
case MATH_SNAPPED:
|
||||
@ -381,6 +383,13 @@ PropertyInfo VisualScriptBuiltinFunc::get_input_value_port_info(int p_idx) const
|
||||
case MATH_DB2LINEAR: {
|
||||
return PropertyInfo(Variant::FLOAT, "db");
|
||||
} break;
|
||||
case MATH_PINGPONG: {
|
||||
if (p_idx == 0) {
|
||||
return PropertyInfo(Variant::FLOAT, "value");
|
||||
} else {
|
||||
return PropertyInfo(Variant::FLOAT, "length");
|
||||
}
|
||||
} break;
|
||||
case MATH_WRAP: {
|
||||
if (p_idx == 0) {
|
||||
return PropertyInfo(Variant::INT, "value");
|
||||
@ -537,6 +546,7 @@ PropertyInfo VisualScriptBuiltinFunc::get_output_value_port_info(int p_idx) cons
|
||||
case MATH_RAD2DEG:
|
||||
case MATH_LINEAR2DB:
|
||||
case MATH_WRAPF:
|
||||
case MATH_PINGPONG:
|
||||
case MATH_DB2LINEAR: {
|
||||
t = Variant::FLOAT;
|
||||
} break;
|
||||
@ -859,6 +869,11 @@ void VisualScriptBuiltinFunc::exec_func(BuiltinFunc p_func, const Variant **p_in
|
||||
VALIDATE_ARG_NUM(0);
|
||||
*r_return = Math::db2linear((double)*p_inputs[0]);
|
||||
} break;
|
||||
case VisualScriptBuiltinFunc::MATH_PINGPONG: {
|
||||
VALIDATE_ARG_NUM(0);
|
||||
VALIDATE_ARG_NUM(1);
|
||||
*r_return = Math::pingpong((double)*p_inputs[0], (double)*p_inputs[1]);
|
||||
} break;
|
||||
case VisualScriptBuiltinFunc::MATH_WRAP: {
|
||||
VALIDATE_ARG_NUM(0);
|
||||
VALIDATE_ARG_NUM(1);
|
||||
@ -1206,6 +1221,7 @@ void VisualScriptBuiltinFunc::_bind_methods() {
|
||||
BIND_ENUM_CONSTANT(MATH_DB2LINEAR);
|
||||
BIND_ENUM_CONSTANT(MATH_WRAP);
|
||||
BIND_ENUM_CONSTANT(MATH_WRAPF);
|
||||
BIND_ENUM_CONSTANT(MATH_PINGPONG);
|
||||
BIND_ENUM_CONSTANT(LOGIC_MAX);
|
||||
BIND_ENUM_CONSTANT(LOGIC_MIN);
|
||||
BIND_ENUM_CONSTANT(LOGIC_CLAMP);
|
||||
@ -1296,6 +1312,7 @@ void register_visual_script_builtin_func_node() {
|
||||
VisualScriptLanguage::singleton->add_register_func("functions/built_in/db2linear", create_builtin_func_node<VisualScriptBuiltinFunc::MATH_DB2LINEAR>);
|
||||
VisualScriptLanguage::singleton->add_register_func("functions/built_in/wrapi", create_builtin_func_node<VisualScriptBuiltinFunc::MATH_WRAP>);
|
||||
VisualScriptLanguage::singleton->add_register_func("functions/built_in/wrapf", create_builtin_func_node<VisualScriptBuiltinFunc::MATH_WRAPF>);
|
||||
VisualScriptLanguage::singleton->add_register_func("functions/built_in/pingpong", create_builtin_func_node<VisualScriptBuiltinFunc::MATH_PINGPONG>);
|
||||
|
||||
VisualScriptLanguage::singleton->add_register_func("functions/built_in/max", create_builtin_func_node<VisualScriptBuiltinFunc::LOGIC_MAX>);
|
||||
VisualScriptLanguage::singleton->add_register_func("functions/built_in/min", create_builtin_func_node<VisualScriptBuiltinFunc::LOGIC_MIN>);
|
||||
|
@ -81,6 +81,7 @@ public:
|
||||
MATH_DB2LINEAR,
|
||||
MATH_WRAP,
|
||||
MATH_WRAPF,
|
||||
MATH_PINGPONG,
|
||||
LOGIC_MAX,
|
||||
LOGIC_MIN,
|
||||
LOGIC_CLAMP,
|
||||
|
@ -30,6 +30,7 @@
|
||||
|
||||
#include "animation_blend_space_2d.h"
|
||||
|
||||
#include "animation_blend_tree.h"
|
||||
#include "core/math/geometry_2d.h"
|
||||
|
||||
void AnimationNodeBlendSpace2D::get_parameter_list(List<PropertyInfo> *r_list) const {
|
||||
@ -531,6 +532,12 @@ double AnimationNodeBlendSpace2D::process(double p_time, bool p_seek) {
|
||||
if (new_closest != closest && new_closest != -1) {
|
||||
float from = 0.0;
|
||||
if (blend_mode == BLEND_MODE_DISCRETE_CARRY && closest != -1) {
|
||||
//for ping-pong loop
|
||||
Ref<AnimationNodeAnimation> na_c = static_cast<Ref<AnimationNodeAnimation>>(blend_points[closest].node);
|
||||
Ref<AnimationNodeAnimation> na_n = static_cast<Ref<AnimationNodeAnimation>>(blend_points[new_closest].node);
|
||||
if (!na_c.is_null() && !na_n.is_null()) {
|
||||
na_n->set_backward(na_c->is_backward());
|
||||
}
|
||||
//see how much animation remains
|
||||
from = length_internal - blend_node(blend_points[closest].name, blend_points[closest].node, p_time, false, 0.0, FILTER_IGNORE, false);
|
||||
}
|
||||
|
@ -30,6 +30,7 @@
|
||||
|
||||
#include "animation_blend_tree.h"
|
||||
|
||||
#include "scene/resources/animation.h"
|
||||
#include "scene/scene_string_names.h"
|
||||
|
||||
void AnimationNodeAnimation::set_animation(const StringName &p_name) {
|
||||
@ -83,30 +84,55 @@ double AnimationNodeAnimation::process(double p_time, bool p_seek) {
|
||||
}
|
||||
|
||||
Ref<Animation> anim = ap->get_animation(animation);
|
||||
|
||||
double step;
|
||||
double anim_size = (double)anim->get_length();
|
||||
double step = 0.0;
|
||||
double prev_time = time;
|
||||
int pingponged = 0;
|
||||
bool current_backward = signbit(p_time);
|
||||
|
||||
if (p_seek) {
|
||||
step = p_time - time;
|
||||
time = p_time;
|
||||
step = 0;
|
||||
} else {
|
||||
time = MAX(0, time + p_time);
|
||||
step = p_time;
|
||||
}
|
||||
|
||||
double anim_size = anim->get_length();
|
||||
|
||||
if (anim->has_loop()) {
|
||||
if (anim_size) {
|
||||
time = Math::fposmod(time, anim_size);
|
||||
p_time *= backward ? -1.0 : 1.0;
|
||||
if (!(time == anim_size && !current_backward) && !(time == 0 && current_backward)) {
|
||||
time = time + p_time;
|
||||
step = p_time;
|
||||
}
|
||||
|
||||
} else if (time > anim_size) {
|
||||
time = anim_size;
|
||||
}
|
||||
|
||||
blend_animation(animation, time, step, p_seek, 1.0);
|
||||
if (anim->get_loop_mode() == Animation::LoopMode::LOOP_PINGPONG) {
|
||||
if (anim_size) {
|
||||
if ((int)Math::floor(abs(time - prev_time) / anim_size) % 2 == 0) {
|
||||
if (prev_time > 0 && time <= 0) {
|
||||
backward = !backward;
|
||||
pingponged = -1;
|
||||
}
|
||||
if (prev_time < anim_size && time >= anim_size) {
|
||||
backward = !backward;
|
||||
pingponged = 1;
|
||||
}
|
||||
}
|
||||
time = Math::pingpong(time, anim_size);
|
||||
}
|
||||
} else {
|
||||
if (anim->get_loop_mode() == Animation::LoopMode::LOOP_LINEAR) {
|
||||
if (anim_size) {
|
||||
time = Math::fposmod(time, anim_size);
|
||||
}
|
||||
} else if (time < 0) {
|
||||
time = 0;
|
||||
} else if (time > anim_size) {
|
||||
time = anim_size;
|
||||
}
|
||||
backward = false;
|
||||
}
|
||||
|
||||
if (play_mode == PLAY_MODE_FORWARD) {
|
||||
blend_animation(animation, time, step, p_seek, 1.0, pingponged);
|
||||
} else {
|
||||
blend_animation(animation, anim_size - time, -step, p_seek, 1.0, pingponged);
|
||||
}
|
||||
set_parameter(this->time, time);
|
||||
|
||||
return anim_size - time;
|
||||
@ -116,11 +142,34 @@ String AnimationNodeAnimation::get_caption() const {
|
||||
return "Animation";
|
||||
}
|
||||
|
||||
void AnimationNodeAnimation::set_play_mode(PlayMode p_play_mode) {
|
||||
play_mode = p_play_mode;
|
||||
}
|
||||
|
||||
AnimationNodeAnimation::PlayMode AnimationNodeAnimation::get_play_mode() const {
|
||||
return play_mode;
|
||||
}
|
||||
|
||||
void AnimationNodeAnimation::set_backward(bool p_backward) {
|
||||
backward = p_backward;
|
||||
}
|
||||
|
||||
bool AnimationNodeAnimation::is_backward() const {
|
||||
return backward;
|
||||
}
|
||||
|
||||
void AnimationNodeAnimation::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_animation", "name"), &AnimationNodeAnimation::set_animation);
|
||||
ClassDB::bind_method(D_METHOD("get_animation"), &AnimationNodeAnimation::get_animation);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_play_mode", "mode"), &AnimationNodeAnimation::set_play_mode);
|
||||
ClassDB::bind_method(D_METHOD("get_play_mode"), &AnimationNodeAnimation::get_play_mode);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "animation"), "set_animation", "get_animation");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "play_mode", PROPERTY_HINT_ENUM, "Forward,Backward"), "set_play_mode", "get_play_mode");
|
||||
|
||||
BIND_ENUM_CONSTANT(PLAY_MODE_FORWARD);
|
||||
BIND_ENUM_CONSTANT(PLAY_MODE_BACKWARD);
|
||||
}
|
||||
|
||||
AnimationNodeAnimation::AnimationNodeAnimation() {
|
||||
@ -533,7 +582,7 @@ AnimationNodeBlend3::AnimationNodeBlend3() {
|
||||
/////////////////////////////////
|
||||
|
||||
void AnimationNodeTimeScale::get_parameter_list(List<PropertyInfo> *r_list) const {
|
||||
r_list->push_back(PropertyInfo(Variant::FLOAT, scale, PROPERTY_HINT_RANGE, "0,32,0.01,or_greater"));
|
||||
r_list->push_back(PropertyInfo(Variant::FLOAT, scale, PROPERTY_HINT_RANGE, "-32,32,0.01,or_lesser,or_greater"));
|
||||
}
|
||||
|
||||
Variant AnimationNodeTimeScale::get_parameter_default_value(const StringName &p_parameter) const {
|
||||
|
@ -42,12 +42,12 @@ class AnimationNodeAnimation : public AnimationRootNode {
|
||||
uint64_t last_version = 0;
|
||||
bool skip = false;
|
||||
|
||||
protected:
|
||||
void _validate_property(PropertyInfo &property) const override;
|
||||
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
enum PlayMode {
|
||||
PLAY_MODE_FORWARD,
|
||||
PLAY_MODE_BACKWARD
|
||||
};
|
||||
|
||||
void get_parameter_list(List<PropertyInfo> *r_list) const override;
|
||||
|
||||
static Vector<String> (*get_editable_animation_list)();
|
||||
@ -58,9 +58,25 @@ public:
|
||||
void set_animation(const StringName &p_name);
|
||||
StringName get_animation() const;
|
||||
|
||||
void set_play_mode(PlayMode p_play_mode);
|
||||
PlayMode get_play_mode() const;
|
||||
|
||||
void set_backward(bool p_backward);
|
||||
bool is_backward() const;
|
||||
|
||||
AnimationNodeAnimation();
|
||||
|
||||
protected:
|
||||
void _validate_property(PropertyInfo &property) const override;
|
||||
static void _bind_methods();
|
||||
|
||||
private:
|
||||
PlayMode play_mode = PLAY_MODE_FORWARD;
|
||||
bool backward = false;
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(AnimationNodeAnimation::PlayMode)
|
||||
|
||||
class AnimationNodeOneShot : public AnimationNode {
|
||||
GDCLASS(AnimationNodeOneShot, AnimationNode);
|
||||
|
||||
|
@ -340,12 +340,13 @@ void AnimationPlayer::_ensure_node_caches(AnimationData *p_anim, Node *p_root_ov
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double p_time, double p_delta, float p_interp, bool p_is_current, bool p_seeked, bool p_started) {
|
||||
void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double p_time, double p_delta, float p_interp, bool p_is_current, bool p_seeked, bool p_started, int p_pingponged) {
|
||||
_ensure_node_caches(p_anim);
|
||||
ERR_FAIL_COND(p_anim->node_cache.size() != p_anim->animation->get_track_count());
|
||||
|
||||
Animation *a = p_anim->animation.operator->();
|
||||
bool can_call = is_inside_tree() && !Engine::get_singleton()->is_editor_hint();
|
||||
bool backward = signbit(p_delta);
|
||||
|
||||
for (int i = 0; i < a->get_track_count(); i++) {
|
||||
// If an animation changes this animation (or it animates itself)
|
||||
@ -425,8 +426,8 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
|
||||
continue; //eeh not worth it
|
||||
}
|
||||
|
||||
float first_key_time = a->track_get_key_time(i, 0);
|
||||
float transition = 1.0;
|
||||
double first_key_time = a->track_get_key_time(i, 0);
|
||||
double transition = 1.0;
|
||||
int first_key = 0;
|
||||
|
||||
if (first_key_time == 0.0) {
|
||||
@ -434,13 +435,13 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
|
||||
if (key_count == 1) {
|
||||
continue; //with one key we can't do anything
|
||||
}
|
||||
transition = a->track_get_key_transition(i, 0);
|
||||
transition = (double)a->track_get_key_transition(i, 0);
|
||||
first_key_time = a->track_get_key_time(i, 1);
|
||||
first_key = 1;
|
||||
}
|
||||
|
||||
if (p_time < first_key_time) {
|
||||
float c = Math::ease(p_time / first_key_time, transition);
|
||||
double c = Math::ease(p_time / first_key_time, transition);
|
||||
Variant first_value = a->track_get_key_value(i, first_key);
|
||||
Variant interp_value;
|
||||
Variant::interpolate(pa->capture, first_value, c, interp_value);
|
||||
@ -482,7 +483,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
|
||||
|
||||
} else if (p_is_current && p_delta != 0) {
|
||||
List<int> indices;
|
||||
a->value_track_get_key_indices(i, p_time, p_delta, &indices);
|
||||
a->value_track_get_key_indices(i, p_time, p_delta, &indices, p_pingponged);
|
||||
|
||||
for (int &F : indices) {
|
||||
Variant value = a->track_get_key_value(i, F);
|
||||
@ -541,7 +542,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
|
||||
|
||||
List<int> indices;
|
||||
|
||||
a->method_track_get_key_indices(i, p_time, p_delta, &indices);
|
||||
a->method_track_get_key_indices(i, p_time, p_delta, &indices, p_pingponged);
|
||||
|
||||
for (int &E : indices) {
|
||||
StringName method = a->method_track_get_name(i, E);
|
||||
@ -596,7 +597,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
|
||||
|
||||
TrackNodeCache::BezierAnim *ba = &E->get();
|
||||
|
||||
float bezier = a->bezier_track_interpolate(i, p_time);
|
||||
real_t bezier = a->bezier_track_interpolate(i, p_time);
|
||||
if (ba->accum_pass != accum_pass) {
|
||||
ERR_CONTINUE(cache_update_bezier_size >= NODE_CACHE_UPDATE_MAX);
|
||||
cache_update_bezier[cache_update_bezier_size++] = ba;
|
||||
@ -657,7 +658,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
|
||||
} else {
|
||||
//find stuff to play
|
||||
List<int> to_play;
|
||||
a->track_get_key_indices_in_range(i, p_time, p_delta, &to_play);
|
||||
a->track_get_key_indices_in_range(i, p_time, p_delta, &to_play, p_pingponged);
|
||||
if (to_play.size()) {
|
||||
int idx = to_play.back()->get();
|
||||
|
||||
@ -685,12 +686,14 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
|
||||
nc->audio_start = p_time;
|
||||
}
|
||||
} else if (nc->audio_playing) {
|
||||
bool loop = a->has_loop();
|
||||
bool loop = a->get_loop_mode() != Animation::LoopMode::LOOP_NONE;
|
||||
|
||||
bool stop = false;
|
||||
|
||||
if (!loop && p_time < nc->audio_start) {
|
||||
stop = true;
|
||||
if (!loop) {
|
||||
if ((p_time < nc->audio_start && !backward) || (p_time > nc->audio_start && backward)) {
|
||||
stop = true;
|
||||
}
|
||||
} else if (nc->audio_len > 0) {
|
||||
float len = nc->audio_start > p_time ? (a->get_length() - nc->audio_start) + p_time : p_time - nc->audio_start;
|
||||
|
||||
@ -731,12 +734,23 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
|
||||
|
||||
Ref<Animation> anim = player->get_animation(anim_name);
|
||||
|
||||
double at_anim_pos;
|
||||
double at_anim_pos = 0.0;
|
||||
|
||||
if (anim->has_loop()) {
|
||||
at_anim_pos = Math::fposmod(p_time - pos, (double)anim->get_length()); //seek to loop
|
||||
} else {
|
||||
at_anim_pos = MIN((double)anim->get_length(), p_time - pos); //seek to end
|
||||
switch (anim->get_loop_mode()) {
|
||||
case Animation::LoopMode::LOOP_NONE: {
|
||||
at_anim_pos = MIN((double)anim->get_length(), p_time - pos); //seek to end
|
||||
} break;
|
||||
|
||||
case Animation::LoopMode::LOOP_LINEAR: {
|
||||
at_anim_pos = Math::fposmod(p_time - pos, (double)anim->get_length()); //seek to loop
|
||||
} break;
|
||||
|
||||
case Animation::LoopMode::LOOP_PINGPONG: {
|
||||
at_anim_pos = Math::pingpong(p_time - pos, (double)anim->get_length());
|
||||
} break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (player->is_playing() || p_seeked) {
|
||||
@ -751,7 +765,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
|
||||
} else {
|
||||
//find stuff to play
|
||||
List<int> to_play;
|
||||
a->track_get_key_indices_in_range(i, p_time, p_delta, &to_play);
|
||||
a->track_get_key_indices_in_range(i, p_time, p_delta, &to_play, p_pingponged);
|
||||
if (to_play.size()) {
|
||||
int idx = to_play.back()->get();
|
||||
|
||||
@ -781,46 +795,73 @@ void AnimationPlayer::_animation_process_data(PlaybackData &cd, double p_delta,
|
||||
double next_pos = cd.pos + delta;
|
||||
|
||||
real_t len = cd.from->animation->get_length();
|
||||
bool loop = cd.from->animation->has_loop();
|
||||
int pingponged = 0;
|
||||
|
||||
if (!loop) {
|
||||
if (next_pos < 0) {
|
||||
next_pos = 0;
|
||||
} else if (next_pos > len) {
|
||||
next_pos = len;
|
||||
}
|
||||
|
||||
bool backwards = signbit(delta); // Negative zero means playing backwards too
|
||||
delta = next_pos - cd.pos; // Fix delta (after determination of backwards because negative zero is lost here)
|
||||
|
||||
if (&cd == &playback.current) {
|
||||
if (!backwards && cd.pos <= len && next_pos == len) {
|
||||
//playback finished
|
||||
end_reached = true;
|
||||
end_notify = cd.pos < len; // Notify only if not already at the end
|
||||
switch (cd.from->animation->get_loop_mode()) {
|
||||
case Animation::LoopMode::LOOP_NONE: {
|
||||
if (next_pos < 0) {
|
||||
next_pos = 0;
|
||||
} else if (next_pos > len) {
|
||||
next_pos = len;
|
||||
}
|
||||
|
||||
if (backwards && cd.pos >= 0 && next_pos == 0) {
|
||||
//playback finished
|
||||
end_reached = true;
|
||||
end_notify = cd.pos > 0; // Notify only if not already at the beginning
|
||||
}
|
||||
}
|
||||
bool backwards = signbit(delta); // Negative zero means playing backwards too
|
||||
delta = next_pos - cd.pos; // Fix delta (after determination of backwards because negative zero is lost here)
|
||||
|
||||
} else {
|
||||
double looped_next_pos = Math::fposmod(next_pos, (double)len);
|
||||
if (looped_next_pos == 0 && next_pos != 0) {
|
||||
// Loop multiples of the length to it, rather than 0
|
||||
// so state at time=length is previewable in the editor
|
||||
next_pos = len;
|
||||
} else {
|
||||
next_pos = looped_next_pos;
|
||||
}
|
||||
if (&cd == &playback.current) {
|
||||
if (!backwards && cd.pos <= len && next_pos == len) {
|
||||
//playback finished
|
||||
end_reached = true;
|
||||
end_notify = cd.pos < len; // Notify only if not already at the end
|
||||
}
|
||||
|
||||
if (backwards && cd.pos >= 0 && next_pos == 0) {
|
||||
//playback finished
|
||||
end_reached = true;
|
||||
end_notify = cd.pos > 0; // Notify only if not already at the beginning
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
case Animation::LoopMode::LOOP_LINEAR: {
|
||||
double looped_next_pos = Math::fposmod(next_pos, (double)len);
|
||||
if (looped_next_pos == 0 && next_pos != 0) {
|
||||
// Loop multiples of the length to it, rather than 0
|
||||
// so state at time=length is previewable in the editor
|
||||
next_pos = len;
|
||||
} else {
|
||||
next_pos = looped_next_pos;
|
||||
}
|
||||
} break;
|
||||
|
||||
case Animation::LoopMode::LOOP_PINGPONG: {
|
||||
if ((int)Math::floor(abs(next_pos - cd.pos) / len) % 2 == 0) {
|
||||
if (next_pos < 0 && cd.pos >= 0) {
|
||||
cd.speed_scale *= -1.0;
|
||||
pingponged = -1;
|
||||
}
|
||||
if (next_pos > len && cd.pos <= len) {
|
||||
cd.speed_scale *= -1.0;
|
||||
pingponged = 1;
|
||||
}
|
||||
}
|
||||
double looped_next_pos = Math::pingpong(next_pos, (double)len);
|
||||
if (looped_next_pos == 0 && next_pos != 0) {
|
||||
// Loop multiples of the length to it, rather than 0
|
||||
// so state at time=length is previewable in the editor
|
||||
next_pos = len;
|
||||
} else {
|
||||
next_pos = looped_next_pos;
|
||||
}
|
||||
} break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
cd.pos = next_pos;
|
||||
|
||||
_animation_process_animation(cd.from, cd.pos, delta, p_blend, &cd == &playback.current, p_seeked, p_started);
|
||||
_animation_process_animation(cd.from, cd.pos, delta, p_blend, &cd == &playback.current, p_seeked, p_started, pingponged);
|
||||
}
|
||||
|
||||
void AnimationPlayer::_animation_process2(double p_delta, bool p_started) {
|
||||
|
@ -215,7 +215,7 @@ private:
|
||||
|
||||
NodePath root;
|
||||
|
||||
void _animation_process_animation(AnimationData *p_anim, double p_time, double p_delta, float p_interp, bool p_is_current = true, bool p_seeked = false, bool p_started = false);
|
||||
void _animation_process_animation(AnimationData *p_anim, double p_time, double p_delta, float p_interp, bool p_is_current = true, bool p_seeked = false, bool p_started = false, int p_pingponged = 0);
|
||||
|
||||
void _ensure_node_caches(AnimationData *p_anim, Node *p_root_override = nullptr);
|
||||
void _animation_process_data(PlaybackData &cd, double p_delta, float p_blend, bool p_seeked, bool p_started);
|
||||
|
@ -32,6 +32,7 @@
|
||||
|
||||
#include "animation_blend_tree.h"
|
||||
#include "core/config/engine.h"
|
||||
#include "scene/resources/animation.h"
|
||||
#include "scene/scene_string_names.h"
|
||||
#include "servers/audio/audio_stream.h"
|
||||
|
||||
@ -87,7 +88,7 @@ void AnimationNode::get_child_nodes(List<ChildNode> *r_child_nodes) {
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationNode::blend_animation(const StringName &p_animation, real_t p_time, real_t p_delta, bool p_seeked, real_t p_blend) {
|
||||
void AnimationNode::blend_animation(const StringName &p_animation, real_t p_time, real_t p_delta, bool p_seeked, real_t p_blend, int p_pingponged) {
|
||||
ERR_FAIL_COND(!state);
|
||||
ERR_FAIL_COND(!state->player->has_animation(p_animation));
|
||||
|
||||
@ -113,6 +114,7 @@ void AnimationNode::blend_animation(const StringName &p_animation, real_t p_time
|
||||
anim_state.time = p_time;
|
||||
anim_state.animation = animation;
|
||||
anim_state.seeked = p_seeked;
|
||||
anim_state.pingponged = p_pingponged;
|
||||
|
||||
state->animation_states.push_back(anim_state);
|
||||
}
|
||||
@ -418,7 +420,7 @@ void AnimationNode::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("_set_filters", "filters"), &AnimationNode::_set_filters);
|
||||
ClassDB::bind_method(D_METHOD("_get_filters"), &AnimationNode::_get_filters);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("blend_animation", "animation", "time", "delta", "seeked", "blend"), &AnimationNode::blend_animation);
|
||||
ClassDB::bind_method(D_METHOD("blend_animation", "animation", "time", "delta", "seeked", "blend", "pingponged"), &AnimationNode::blend_animation, DEFVAL(0));
|
||||
ClassDB::bind_method(D_METHOD("blend_node", "name", "node", "time", "seek", "blend", "filter", "optimize"), &AnimationNode::blend_node, DEFVAL(FILTER_IGNORE), DEFVAL(true));
|
||||
ClassDB::bind_method(D_METHOD("blend_input", "input_index", "time", "seek", "blend", "filter", "optimize"), &AnimationNode::blend_input, DEFVAL(FILTER_IGNORE), DEFVAL(true));
|
||||
|
||||
@ -824,6 +826,8 @@ void AnimationTree::_process_graph(real_t p_delta) {
|
||||
double delta = as.delta;
|
||||
real_t weight = as.blend;
|
||||
bool seeked = as.seeked;
|
||||
int pingponged = as.pingponged;
|
||||
bool backward = signbit(delta);
|
||||
|
||||
for (int i = 0; i < a->get_track_count(); i++) {
|
||||
NodePath path = a->track_get_path(i);
|
||||
@ -862,12 +866,38 @@ void AnimationTree::_process_graph(real_t p_delta) {
|
||||
t->scale = Vector3(1, 1, 1);
|
||||
}
|
||||
|
||||
real_t prev_time = time - delta;
|
||||
if (prev_time < 0) {
|
||||
if (!a->has_loop()) {
|
||||
prev_time = 0;
|
||||
} else {
|
||||
prev_time = a->get_length() + prev_time;
|
||||
double prev_time = time - delta;
|
||||
if (!backward) {
|
||||
if (prev_time < 0) {
|
||||
switch (a->get_loop_mode()) {
|
||||
case Animation::LoopMode::LOOP_NONE: {
|
||||
prev_time = 0;
|
||||
} break;
|
||||
case Animation::LoopMode::LOOP_LINEAR: {
|
||||
prev_time = Math::fposmod(prev_time, (double)a->get_length());
|
||||
} break;
|
||||
case Animation::LoopMode::LOOP_PINGPONG: {
|
||||
prev_time = Math::pingpong(prev_time, (double)a->get_length());
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (prev_time > a->get_length()) {
|
||||
switch (a->get_loop_mode()) {
|
||||
case Animation::LoopMode::LOOP_NONE: {
|
||||
prev_time = (double)a->get_length();
|
||||
} break;
|
||||
case Animation::LoopMode::LOOP_LINEAR: {
|
||||
prev_time = Math::fposmod(prev_time, (double)a->get_length());
|
||||
} break;
|
||||
case Animation::LoopMode::LOOP_PINGPONG: {
|
||||
prev_time = Math::pingpong(prev_time, (double)a->get_length());
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -875,20 +905,38 @@ void AnimationTree::_process_graph(real_t p_delta) {
|
||||
Quaternion rot[2];
|
||||
Vector3 scale[2];
|
||||
|
||||
if (prev_time > time) {
|
||||
Error err = a->transform_track_interpolate(i, prev_time, &loc[0], &rot[0], &scale[0]);
|
||||
if (err != OK) {
|
||||
continue;
|
||||
if (!backward) {
|
||||
if (prev_time > time) {
|
||||
Error err = a->transform_track_interpolate(i, prev_time, &loc[0], &rot[0], &scale[0]);
|
||||
if (err != OK) {
|
||||
continue;
|
||||
}
|
||||
|
||||
a->transform_track_interpolate(i, (double)a->get_length(), &loc[1], &rot[1], &scale[1]);
|
||||
|
||||
t->loc += (loc[1] - loc[0]) * blend;
|
||||
t->scale += (scale[1] - scale[0]) * blend;
|
||||
Quaternion q = Quaternion().slerp(rot[0].normalized().inverse() * rot[1].normalized(), blend).normalized();
|
||||
t->rot = (t->rot * q).normalized();
|
||||
|
||||
prev_time = 0;
|
||||
}
|
||||
} else {
|
||||
if (prev_time < time) {
|
||||
Error err = a->transform_track_interpolate(i, prev_time, &loc[0], &rot[0], &scale[0]);
|
||||
if (err != OK) {
|
||||
continue;
|
||||
}
|
||||
|
||||
a->transform_track_interpolate(i, a->get_length(), &loc[1], &rot[1], &scale[1]);
|
||||
a->transform_track_interpolate(i, 0, &loc[1], &rot[1], &scale[1]);
|
||||
|
||||
t->loc += (loc[1] - loc[0]) * blend;
|
||||
t->scale += (scale[1] - scale[0]) * blend;
|
||||
Quaternion q = Quaternion().slerp(rot[0].normalized().inverse() * rot[1].normalized(), blend).normalized();
|
||||
t->rot = (t->rot * q).normalized();
|
||||
t->loc += (loc[1] - loc[0]) * blend;
|
||||
t->scale += (scale[1] - scale[0]) * blend;
|
||||
Quaternion q = Quaternion().slerp(rot[0].normalized().inverse() * rot[1].normalized(), blend).normalized();
|
||||
t->rot = (t->rot * q).normalized();
|
||||
|
||||
prev_time = 0;
|
||||
prev_time = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Error err = a->transform_track_interpolate(i, prev_time, &loc[0], &rot[0], &scale[0]);
|
||||
@ -903,8 +951,7 @@ void AnimationTree::_process_graph(real_t p_delta) {
|
||||
Quaternion q = Quaternion().slerp(rot[0].normalized().inverse() * rot[1].normalized(), blend).normalized();
|
||||
t->rot = (t->rot * q).normalized();
|
||||
|
||||
prev_time = 0;
|
||||
|
||||
prev_time = !backward ? 0 : (double)a->get_length();
|
||||
} else {
|
||||
Vector3 loc;
|
||||
Quaternion rot;
|
||||
@ -960,7 +1007,7 @@ void AnimationTree::_process_graph(real_t p_delta) {
|
||||
|
||||
} else {
|
||||
List<int> indices;
|
||||
a->value_track_get_key_indices(i, time, delta, &indices);
|
||||
a->value_track_get_key_indices(i, time, delta, &indices, pingponged);
|
||||
|
||||
for (int &F : indices) {
|
||||
Variant value = a->track_get_key_value(i, F);
|
||||
@ -977,7 +1024,7 @@ void AnimationTree::_process_graph(real_t p_delta) {
|
||||
|
||||
List<int> indices;
|
||||
|
||||
a->method_track_get_key_indices(i, time, delta, &indices);
|
||||
a->method_track_get_key_indices(i, time, delta, &indices, pingponged);
|
||||
|
||||
for (int &F : indices) {
|
||||
StringName method = a->method_track_get_name(i, F);
|
||||
@ -1060,7 +1107,7 @@ void AnimationTree::_process_graph(real_t p_delta) {
|
||||
} else {
|
||||
//find stuff to play
|
||||
List<int> to_play;
|
||||
a->track_get_key_indices_in_range(i, time, delta, &to_play);
|
||||
a->track_get_key_indices_in_range(i, time, delta, &to_play, pingponged);
|
||||
if (to_play.size()) {
|
||||
int idx = to_play.back()->get();
|
||||
|
||||
@ -1088,12 +1135,20 @@ void AnimationTree::_process_graph(real_t p_delta) {
|
||||
t->start = time;
|
||||
}
|
||||
} else if (t->playing) {
|
||||
bool loop = a->has_loop();
|
||||
bool loop = a->get_loop_mode() != Animation::LoopMode::LOOP_NONE;
|
||||
|
||||
bool stop = false;
|
||||
|
||||
if (!loop && time < t->start) {
|
||||
stop = true;
|
||||
if (!loop) {
|
||||
if (delta > 0) {
|
||||
if (time < t->start) {
|
||||
stop = true;
|
||||
}
|
||||
} else if (delta < 0) {
|
||||
if (time > t->start) {
|
||||
stop = true;
|
||||
}
|
||||
}
|
||||
} else if (t->len > 0) {
|
||||
real_t len = t->start > time ? (a->get_length() - t->start) + time : time - t->start;
|
||||
|
||||
@ -1127,7 +1182,7 @@ void AnimationTree::_process_graph(real_t p_delta) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (delta == 0 || seeked) {
|
||||
if (seeked) {
|
||||
//seek
|
||||
int idx = a->track_find_key(i, time);
|
||||
if (idx < 0) {
|
||||
@ -1143,12 +1198,20 @@ void AnimationTree::_process_graph(real_t p_delta) {
|
||||
|
||||
Ref<Animation> anim = player2->get_animation(anim_name);
|
||||
|
||||
real_t at_anim_pos;
|
||||
real_t at_anim_pos = 0.0;
|
||||
|
||||
if (anim->has_loop()) {
|
||||
at_anim_pos = Math::fposmod(time - pos, (double)anim->get_length()); //seek to loop
|
||||
} else {
|
||||
at_anim_pos = MAX(anim->get_length(), time - pos); //seek to end
|
||||
switch (anim->get_loop_mode()) {
|
||||
case Animation::LoopMode::LOOP_NONE: {
|
||||
at_anim_pos = MAX((double)anim->get_length(), time - pos); //seek to end
|
||||
} break;
|
||||
case Animation::LoopMode::LOOP_LINEAR: {
|
||||
at_anim_pos = Math::fposmod(time - pos, (double)anim->get_length()); //seek to loop
|
||||
} break;
|
||||
case Animation::LoopMode::LOOP_PINGPONG: {
|
||||
at_anim_pos = Math::pingpong(time - pos, (double)a->get_length());
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (player2->is_playing() || seeked) {
|
||||
@ -1163,7 +1226,7 @@ void AnimationTree::_process_graph(real_t p_delta) {
|
||||
} else {
|
||||
//find stuff to play
|
||||
List<int> to_play;
|
||||
a->track_get_key_indices_in_range(i, time, delta, &to_play);
|
||||
a->track_get_key_indices_in_range(i, time, delta, &to_play, pingponged);
|
||||
if (to_play.size()) {
|
||||
int idx = to_play.back()->get();
|
||||
|
||||
|
@ -68,6 +68,7 @@ public:
|
||||
const Vector<real_t> *track_blends = nullptr;
|
||||
real_t blend = 0.0;
|
||||
bool seeked = false;
|
||||
int pingponged = 0;
|
||||
};
|
||||
|
||||
struct State {
|
||||
@ -101,9 +102,10 @@ public:
|
||||
real_t _blend_node(const StringName &p_subpath, const Vector<StringName> &p_connections, AnimationNode *p_new_parent, Ref<AnimationNode> p_node, real_t p_time, bool p_seek, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true, real_t *r_max = nullptr);
|
||||
|
||||
protected:
|
||||
void blend_animation(const StringName &p_animation, real_t p_time, real_t p_delta, bool p_seeked, real_t p_blend);
|
||||
void blend_animation(const StringName &p_animation, real_t p_time, real_t p_delta, bool p_seeked, real_t p_blend, int p_pingponged = 0);
|
||||
real_t blend_node(const StringName &p_sub_path, Ref<AnimationNode> p_node, real_t p_time, bool p_seek, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true);
|
||||
real_t blend_input(int p_input, real_t p_time, bool p_seek, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true);
|
||||
|
||||
void make_invalid(const String &p_reason);
|
||||
|
||||
static void _bind_methods();
|
||||
|
@ -309,8 +309,8 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const {
|
||||
|
||||
if (name == "length") {
|
||||
r_ret = length;
|
||||
} else if (name == "loop") {
|
||||
r_ret = loop;
|
||||
} else if (name == "loop_mode") {
|
||||
r_ret = loop_mode;
|
||||
} else if (name == "step") {
|
||||
r_ret = step;
|
||||
} else if (name.begins_with("tracks/")) {
|
||||
@ -1413,7 +1413,7 @@ void Animation::track_set_key_transition(int p_track, int p_key_idx, real_t p_tr
|
||||
}
|
||||
|
||||
template <class K>
|
||||
int Animation::_find(const Vector<K> &p_keys, double p_time) const {
|
||||
int Animation::_find(const Vector<K> &p_keys, double p_time, bool p_backward) const {
|
||||
int len = p_keys.size();
|
||||
if (len == 0) {
|
||||
return -2;
|
||||
@ -1443,8 +1443,14 @@ int Animation::_find(const Vector<K> &p_keys, double p_time) const {
|
||||
}
|
||||
}
|
||||
|
||||
if (keys[middle].time > p_time) {
|
||||
middle--;
|
||||
if (!p_backward) {
|
||||
if (keys[middle].time > p_time) {
|
||||
middle--;
|
||||
}
|
||||
} else {
|
||||
if (keys[middle].time < p_time) {
|
||||
middle++;
|
||||
}
|
||||
}
|
||||
|
||||
return middle;
|
||||
@ -1585,7 +1591,7 @@ real_t Animation::_cubic_interpolate(const real_t &p_pre_a, const real_t &p_a, c
|
||||
}
|
||||
|
||||
template <class T>
|
||||
T Animation::_interpolate(const Vector<TKey<T>> &p_keys, double p_time, InterpolationType p_interp, bool p_loop_wrap, bool *p_ok) const {
|
||||
T Animation::_interpolate(const Vector<TKey<T>> &p_keys, double p_time, InterpolationType p_interp, bool p_loop_wrap, bool *p_ok, bool p_backward) const {
|
||||
int len = _find(p_keys, length) + 1; // try to find last key (there may be more past the end)
|
||||
|
||||
if (len <= 0) {
|
||||
@ -1603,7 +1609,7 @@ T Animation::_interpolate(const Vector<TKey<T>> &p_keys, double p_time, Interpol
|
||||
return p_keys[0].value;
|
||||
}
|
||||
|
||||
int idx = _find(p_keys, p_time);
|
||||
int idx = _find(p_keys, p_time, p_backward);
|
||||
|
||||
ERR_FAIL_COND_V(idx == -2, T());
|
||||
|
||||
@ -1612,24 +1618,42 @@ T Animation::_interpolate(const Vector<TKey<T>> &p_keys, double p_time, Interpol
|
||||
real_t c = 0.0;
|
||||
// prepare for all cases of interpolation
|
||||
|
||||
if (loop && p_loop_wrap) {
|
||||
if ((loop_mode == LOOP_LINEAR || loop_mode == LOOP_PINGPONG) && p_loop_wrap) {
|
||||
// loop
|
||||
if (idx >= 0) {
|
||||
if ((idx + 1) < len) {
|
||||
next = idx + 1;
|
||||
real_t delta = p_keys[next].time - p_keys[idx].time;
|
||||
real_t from = p_time - p_keys[idx].time;
|
||||
if (!p_backward) {
|
||||
// no backward
|
||||
if (idx >= 0) {
|
||||
if (idx < len - 1) {
|
||||
next = idx + 1;
|
||||
real_t delta = p_keys[next].time - p_keys[idx].time;
|
||||
real_t from = p_time - p_keys[idx].time;
|
||||
|
||||
if (Math::is_zero_approx(delta)) {
|
||||
c = 0;
|
||||
if (Math::is_zero_approx(delta)) {
|
||||
c = 0;
|
||||
} else {
|
||||
c = from / delta;
|
||||
}
|
||||
} else {
|
||||
c = from / delta;
|
||||
}
|
||||
next = 0;
|
||||
real_t delta = (length - p_keys[idx].time) + p_keys[next].time;
|
||||
real_t from = p_time - p_keys[idx].time;
|
||||
|
||||
if (Math::is_zero_approx(delta)) {
|
||||
c = 0;
|
||||
} else {
|
||||
c = from / delta;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// on loop, behind first key
|
||||
idx = len - 1;
|
||||
next = 0;
|
||||
real_t delta = (length - p_keys[idx].time) + p_keys[next].time;
|
||||
real_t from = p_time - p_keys[idx].time;
|
||||
real_t endtime = (length - p_keys[idx].time);
|
||||
if (endtime < 0) { // may be keys past the end
|
||||
endtime = 0;
|
||||
}
|
||||
real_t delta = endtime + p_keys[next].time;
|
||||
real_t from = endtime + p_time;
|
||||
|
||||
if (Math::is_zero_approx(delta)) {
|
||||
c = 0;
|
||||
@ -1637,49 +1661,81 @@ T Animation::_interpolate(const Vector<TKey<T>> &p_keys, double p_time, Interpol
|
||||
c = from / delta;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// on loop, behind first key
|
||||
idx = len - 1;
|
||||
next = 0;
|
||||
real_t endtime = (length - p_keys[idx].time);
|
||||
if (endtime < 0) { // may be keys past the end
|
||||
endtime = 0;
|
||||
}
|
||||
real_t delta = endtime + p_keys[next].time;
|
||||
real_t from = endtime + p_time;
|
||||
// backward
|
||||
if (idx <= len - 1) {
|
||||
if (idx > 0) {
|
||||
next = idx - 1;
|
||||
real_t delta = (length - p_keys[next].time) - (length - p_keys[idx].time);
|
||||
real_t from = (length - p_time) - (length - p_keys[idx].time);
|
||||
|
||||
if (Math::is_zero_approx(delta)) {
|
||||
c = 0;
|
||||
if (Math::is_zero_approx(delta))
|
||||
c = 0;
|
||||
else
|
||||
c = from / delta;
|
||||
} else {
|
||||
next = len - 1;
|
||||
real_t delta = p_keys[idx].time + (length - p_keys[next].time);
|
||||
real_t from = (length - p_time) - (length - p_keys[idx].time);
|
||||
|
||||
if (Math::is_zero_approx(delta))
|
||||
c = 0;
|
||||
else
|
||||
c = from / delta;
|
||||
}
|
||||
} else {
|
||||
c = from / delta;
|
||||
// on loop, in front of last key
|
||||
idx = 0;
|
||||
next = len - 1;
|
||||
real_t endtime = p_keys[idx].time;
|
||||
if (endtime > length) // may be keys past the end
|
||||
endtime = length;
|
||||
real_t delta = p_keys[next].time - endtime;
|
||||
real_t from = p_time - endtime;
|
||||
|
||||
if (Math::is_zero_approx(delta))
|
||||
c = 0;
|
||||
else
|
||||
c = from / delta;
|
||||
}
|
||||
}
|
||||
|
||||
} else { // no loop
|
||||
if (!p_backward) {
|
||||
if (idx >= 0) {
|
||||
if (idx < len - 1) {
|
||||
next = idx + 1;
|
||||
real_t delta = p_keys[next].time - p_keys[idx].time;
|
||||
real_t from = p_time - p_keys[idx].time;
|
||||
|
||||
if (idx >= 0) {
|
||||
if ((idx + 1) < len) {
|
||||
next = idx + 1;
|
||||
real_t delta = p_keys[next].time - p_keys[idx].time;
|
||||
real_t from = p_time - p_keys[idx].time;
|
||||
|
||||
if (Math::is_zero_approx(delta)) {
|
||||
c = 0;
|
||||
if (Math::is_zero_approx(delta)) {
|
||||
c = 0;
|
||||
} else {
|
||||
c = from / delta;
|
||||
}
|
||||
} else {
|
||||
c = from / delta;
|
||||
next = idx;
|
||||
}
|
||||
|
||||
} else {
|
||||
next = idx;
|
||||
}
|
||||
|
||||
} else {
|
||||
// only allow extending first key to anim start if looping
|
||||
if (loop) {
|
||||
idx = next = 0;
|
||||
}
|
||||
} else {
|
||||
if (idx <= len - 1) {
|
||||
if (idx > 0) {
|
||||
next = idx - 1;
|
||||
real_t delta = (length - p_keys[next].time) - (length - p_keys[idx].time);
|
||||
real_t from = (length - p_time) - (length - p_keys[idx].time);
|
||||
|
||||
if (Math::is_zero_approx(delta)) {
|
||||
c = 0;
|
||||
} else {
|
||||
c = from / delta;
|
||||
}
|
||||
|
||||
} else {
|
||||
next = idx;
|
||||
}
|
||||
} else {
|
||||
result = false;
|
||||
idx = next = len - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1729,7 +1785,7 @@ T Animation::_interpolate(const Vector<TKey<T>> &p_keys, double p_time, Interpol
|
||||
// do a barrel roll
|
||||
}
|
||||
|
||||
Error Animation::transform_track_interpolate(int p_track, double p_time, Vector3 *r_loc, Quaternion *r_rot, Vector3 *r_scale) const {
|
||||
Error Animation::transform_track_interpolate(int p_track, double p_time, Vector3 *r_loc, Quaternion *r_rot, Vector3 *r_scale, bool p_backward) const {
|
||||
ERR_FAIL_INDEX_V(p_track, tracks.size(), ERR_INVALID_PARAMETER);
|
||||
Track *t = tracks[p_track];
|
||||
ERR_FAIL_COND_V(t->type != TYPE_TRANSFORM3D, ERR_INVALID_PARAMETER);
|
||||
@ -1738,7 +1794,7 @@ Error Animation::transform_track_interpolate(int p_track, double p_time, Vector3
|
||||
|
||||
bool ok = false;
|
||||
|
||||
TransformKey tk = _interpolate(tt->transforms, p_time, tt->interpolation, tt->loop_wrap, &ok);
|
||||
TransformKey tk = _interpolate(tt->transforms, p_time, tt->interpolation, tt->loop_wrap, &ok, p_backward);
|
||||
|
||||
if (!ok) {
|
||||
return ERR_UNAVAILABLE;
|
||||
@ -1813,7 +1869,7 @@ void Animation::_value_track_get_key_indices_in_range(const ValueTrack *vt, doub
|
||||
}
|
||||
}
|
||||
|
||||
void Animation::value_track_get_key_indices(int p_track, double p_time, double p_delta, List<int> *p_indices) const {
|
||||
void Animation::value_track_get_key_indices(int p_track, double p_time, double p_delta, List<int> *p_indices, int p_pingponged) const {
|
||||
ERR_FAIL_INDEX(p_track, tracks.size());
|
||||
Track *t = tracks[p_track];
|
||||
ERR_FAIL_COND(t->type != TYPE_VALUE);
|
||||
@ -1827,30 +1883,50 @@ void Animation::value_track_get_key_indices(int p_track, double p_time, double p
|
||||
SWAP(from_time, to_time);
|
||||
}
|
||||
|
||||
if (loop) {
|
||||
from_time = Math::fposmod(from_time, length);
|
||||
to_time = Math::fposmod(to_time, length);
|
||||
switch (loop_mode) {
|
||||
case LOOP_NONE: {
|
||||
if (from_time < 0) {
|
||||
from_time = 0;
|
||||
}
|
||||
if (from_time > length) {
|
||||
from_time = length;
|
||||
}
|
||||
|
||||
if (from_time > to_time) {
|
||||
// handle loop by splitting
|
||||
_value_track_get_key_indices_in_range(vt, from_time, length, p_indices);
|
||||
_value_track_get_key_indices_in_range(vt, 0, to_time, p_indices);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (from_time < 0) {
|
||||
from_time = 0;
|
||||
}
|
||||
if (from_time > length) {
|
||||
from_time = length;
|
||||
}
|
||||
if (to_time < 0) {
|
||||
to_time = 0;
|
||||
}
|
||||
if (to_time > length) {
|
||||
to_time = length;
|
||||
}
|
||||
} break;
|
||||
case LOOP_LINEAR: {
|
||||
from_time = Math::fposmod(from_time, length);
|
||||
to_time = Math::fposmod(to_time, length);
|
||||
|
||||
if (to_time < 0) {
|
||||
to_time = 0;
|
||||
}
|
||||
if (to_time > length) {
|
||||
to_time = length;
|
||||
}
|
||||
if (from_time > to_time) {
|
||||
// handle loop by splitting
|
||||
_value_track_get_key_indices_in_range(vt, from_time, length, p_indices);
|
||||
_value_track_get_key_indices_in_range(vt, 0, to_time, p_indices);
|
||||
return;
|
||||
}
|
||||
} break;
|
||||
case LOOP_PINGPONG: {
|
||||
from_time = Math::pingpong(from_time, length);
|
||||
to_time = Math::pingpong(to_time, length);
|
||||
|
||||
if (p_pingponged == -1) {
|
||||
// handle loop by splitting
|
||||
_value_track_get_key_indices_in_range(vt, 0, from_time, p_indices);
|
||||
_value_track_get_key_indices_in_range(vt, 0, to_time, p_indices);
|
||||
return;
|
||||
}
|
||||
if (p_pingponged == 1) {
|
||||
// handle loop by splitting
|
||||
_value_track_get_key_indices_in_range(vt, from_time, length, p_indices);
|
||||
_value_track_get_key_indices_in_range(vt, to_time, length, p_indices);
|
||||
return;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
|
||||
_value_track_get_key_indices_in_range(vt, from_time, to_time, p_indices);
|
||||
@ -1909,7 +1985,7 @@ void Animation::_track_get_key_indices_in_range(const Vector<T> &p_array, double
|
||||
}
|
||||
}
|
||||
|
||||
void Animation::track_get_key_indices_in_range(int p_track, double p_time, double p_delta, List<int> *p_indices) const {
|
||||
void Animation::track_get_key_indices_in_range(int p_track, double p_time, double p_delta, List<int> *p_indices, int p_pingponged) const {
|
||||
ERR_FAIL_INDEX(p_track, tracks.size());
|
||||
const Track *t = tracks[p_track];
|
||||
|
||||
@ -1920,104 +1996,176 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl
|
||||
SWAP(from_time, to_time);
|
||||
}
|
||||
|
||||
if (loop) {
|
||||
if (from_time > length || from_time < 0) {
|
||||
from_time = Math::fposmod(from_time, length);
|
||||
}
|
||||
|
||||
if (to_time > length || to_time < 0) {
|
||||
to_time = Math::fposmod(to_time, length);
|
||||
}
|
||||
|
||||
if (from_time > to_time) {
|
||||
// handle loop by splitting
|
||||
|
||||
switch (t->type) {
|
||||
case TYPE_TRANSFORM3D: {
|
||||
const TransformTrack *tt = static_cast<const TransformTrack *>(t);
|
||||
_track_get_key_indices_in_range(tt->transforms, from_time, length, p_indices);
|
||||
_track_get_key_indices_in_range(tt->transforms, 0, to_time, p_indices);
|
||||
|
||||
} break;
|
||||
case TYPE_VALUE: {
|
||||
const ValueTrack *vt = static_cast<const ValueTrack *>(t);
|
||||
_track_get_key_indices_in_range(vt->values, from_time, length, p_indices);
|
||||
_track_get_key_indices_in_range(vt->values, 0, to_time, p_indices);
|
||||
|
||||
} break;
|
||||
case TYPE_METHOD: {
|
||||
const MethodTrack *mt = static_cast<const MethodTrack *>(t);
|
||||
_track_get_key_indices_in_range(mt->methods, from_time, length, p_indices);
|
||||
_track_get_key_indices_in_range(mt->methods, 0, to_time, p_indices);
|
||||
|
||||
} break;
|
||||
case TYPE_BEZIER: {
|
||||
const BezierTrack *bz = static_cast<const BezierTrack *>(t);
|
||||
_track_get_key_indices_in_range(bz->values, from_time, length, p_indices);
|
||||
_track_get_key_indices_in_range(bz->values, 0, to_time, p_indices);
|
||||
|
||||
} break;
|
||||
case TYPE_AUDIO: {
|
||||
const AudioTrack *ad = static_cast<const AudioTrack *>(t);
|
||||
_track_get_key_indices_in_range(ad->values, from_time, length, p_indices);
|
||||
_track_get_key_indices_in_range(ad->values, 0, to_time, p_indices);
|
||||
|
||||
} break;
|
||||
case TYPE_ANIMATION: {
|
||||
const AnimationTrack *an = static_cast<const AnimationTrack *>(t);
|
||||
_track_get_key_indices_in_range(an->values, from_time, length, p_indices);
|
||||
_track_get_key_indices_in_range(an->values, 0, to_time, p_indices);
|
||||
|
||||
} break;
|
||||
switch (loop_mode) {
|
||||
case LOOP_NONE: {
|
||||
if (from_time < 0) {
|
||||
from_time = 0;
|
||||
}
|
||||
if (from_time > length) {
|
||||
from_time = length;
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (from_time < 0) {
|
||||
from_time = 0;
|
||||
}
|
||||
if (from_time > length) {
|
||||
from_time = length;
|
||||
}
|
||||
|
||||
if (to_time < 0) {
|
||||
to_time = 0;
|
||||
}
|
||||
if (to_time > length) {
|
||||
to_time = length;
|
||||
}
|
||||
if (to_time < 0) {
|
||||
to_time = 0;
|
||||
}
|
||||
if (to_time > length) {
|
||||
to_time = length;
|
||||
}
|
||||
} break;
|
||||
case LOOP_LINEAR: {
|
||||
if (from_time > length || from_time < 0) {
|
||||
from_time = Math::fposmod(from_time, length);
|
||||
}
|
||||
if (to_time > length || to_time < 0) {
|
||||
to_time = Math::fposmod(to_time, length);
|
||||
}
|
||||
|
||||
if (from_time > to_time) {
|
||||
// handle loop by splitting
|
||||
switch (t->type) {
|
||||
case TYPE_TRANSFORM3D: {
|
||||
const TransformTrack *tt = static_cast<const TransformTrack *>(t);
|
||||
_track_get_key_indices_in_range(tt->transforms, from_time, length, p_indices);
|
||||
_track_get_key_indices_in_range(tt->transforms, 0, to_time, p_indices);
|
||||
} break;
|
||||
case TYPE_VALUE: {
|
||||
const ValueTrack *vt = static_cast<const ValueTrack *>(t);
|
||||
_track_get_key_indices_in_range(vt->values, from_time, length, p_indices);
|
||||
_track_get_key_indices_in_range(vt->values, 0, to_time, p_indices);
|
||||
} break;
|
||||
case TYPE_METHOD: {
|
||||
const MethodTrack *mt = static_cast<const MethodTrack *>(t);
|
||||
_track_get_key_indices_in_range(mt->methods, from_time, length, p_indices);
|
||||
_track_get_key_indices_in_range(mt->methods, 0, to_time, p_indices);
|
||||
} break;
|
||||
case TYPE_BEZIER: {
|
||||
const BezierTrack *bz = static_cast<const BezierTrack *>(t);
|
||||
_track_get_key_indices_in_range(bz->values, from_time, length, p_indices);
|
||||
_track_get_key_indices_in_range(bz->values, 0, to_time, p_indices);
|
||||
} break;
|
||||
case TYPE_AUDIO: {
|
||||
const AudioTrack *ad = static_cast<const AudioTrack *>(t);
|
||||
_track_get_key_indices_in_range(ad->values, from_time, length, p_indices);
|
||||
_track_get_key_indices_in_range(ad->values, 0, to_time, p_indices);
|
||||
} break;
|
||||
case TYPE_ANIMATION: {
|
||||
const AnimationTrack *an = static_cast<const AnimationTrack *>(t);
|
||||
_track_get_key_indices_in_range(an->values, from_time, length, p_indices);
|
||||
_track_get_key_indices_in_range(an->values, 0, to_time, p_indices);
|
||||
} break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
} break;
|
||||
case LOOP_PINGPONG: {
|
||||
if (from_time > length || from_time < 0) {
|
||||
from_time = Math::pingpong(from_time, length);
|
||||
}
|
||||
if (to_time > length || to_time < 0) {
|
||||
to_time = Math::pingpong(to_time, length);
|
||||
}
|
||||
|
||||
if ((int)Math::floor(abs(p_delta) / length) % 2 == 0) {
|
||||
if (p_pingponged == -1) {
|
||||
// handle loop by splitting
|
||||
switch (t->type) {
|
||||
case TYPE_TRANSFORM3D: {
|
||||
const TransformTrack *tt = static_cast<const TransformTrack *>(t);
|
||||
_track_get_key_indices_in_range(tt->transforms, 0, from_time, p_indices);
|
||||
_track_get_key_indices_in_range(tt->transforms, 0, to_time, p_indices);
|
||||
} break;
|
||||
case TYPE_VALUE: {
|
||||
const ValueTrack *vt = static_cast<const ValueTrack *>(t);
|
||||
_track_get_key_indices_in_range(vt->values, 0, from_time, p_indices);
|
||||
_track_get_key_indices_in_range(vt->values, 0, to_time, p_indices);
|
||||
} break;
|
||||
case TYPE_METHOD: {
|
||||
const MethodTrack *mt = static_cast<const MethodTrack *>(t);
|
||||
_track_get_key_indices_in_range(mt->methods, 0, from_time, p_indices);
|
||||
_track_get_key_indices_in_range(mt->methods, 0, to_time, p_indices);
|
||||
} break;
|
||||
case TYPE_BEZIER: {
|
||||
const BezierTrack *bz = static_cast<const BezierTrack *>(t);
|
||||
_track_get_key_indices_in_range(bz->values, 0, from_time, p_indices);
|
||||
_track_get_key_indices_in_range(bz->values, 0, to_time, p_indices);
|
||||
} break;
|
||||
case TYPE_AUDIO: {
|
||||
const AudioTrack *ad = static_cast<const AudioTrack *>(t);
|
||||
_track_get_key_indices_in_range(ad->values, 0, from_time, p_indices);
|
||||
_track_get_key_indices_in_range(ad->values, 0, to_time, p_indices);
|
||||
} break;
|
||||
case TYPE_ANIMATION: {
|
||||
const AnimationTrack *an = static_cast<const AnimationTrack *>(t);
|
||||
_track_get_key_indices_in_range(an->values, 0, from_time, p_indices);
|
||||
_track_get_key_indices_in_range(an->values, 0, to_time, p_indices);
|
||||
} break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (p_pingponged == 1) {
|
||||
// handle loop by splitting
|
||||
switch (t->type) {
|
||||
case TYPE_TRANSFORM3D: {
|
||||
const TransformTrack *tt = static_cast<const TransformTrack *>(t);
|
||||
_track_get_key_indices_in_range(tt->transforms, from_time, length, p_indices);
|
||||
_track_get_key_indices_in_range(tt->transforms, to_time, length, p_indices);
|
||||
} break;
|
||||
case TYPE_VALUE: {
|
||||
const ValueTrack *vt = static_cast<const ValueTrack *>(t);
|
||||
_track_get_key_indices_in_range(vt->values, from_time, length, p_indices);
|
||||
_track_get_key_indices_in_range(vt->values, to_time, length, p_indices);
|
||||
} break;
|
||||
case TYPE_METHOD: {
|
||||
const MethodTrack *mt = static_cast<const MethodTrack *>(t);
|
||||
_track_get_key_indices_in_range(mt->methods, from_time, length, p_indices);
|
||||
_track_get_key_indices_in_range(mt->methods, to_time, length, p_indices);
|
||||
} break;
|
||||
case TYPE_BEZIER: {
|
||||
const BezierTrack *bz = static_cast<const BezierTrack *>(t);
|
||||
_track_get_key_indices_in_range(bz->values, from_time, length, p_indices);
|
||||
_track_get_key_indices_in_range(bz->values, to_time, length, p_indices);
|
||||
} break;
|
||||
case TYPE_AUDIO: {
|
||||
const AudioTrack *ad = static_cast<const AudioTrack *>(t);
|
||||
_track_get_key_indices_in_range(ad->values, from_time, length, p_indices);
|
||||
_track_get_key_indices_in_range(ad->values, to_time, length, p_indices);
|
||||
} break;
|
||||
case TYPE_ANIMATION: {
|
||||
const AnimationTrack *an = static_cast<const AnimationTrack *>(t);
|
||||
_track_get_key_indices_in_range(an->values, from_time, length, p_indices);
|
||||
_track_get_key_indices_in_range(an->values, to_time, length, p_indices);
|
||||
} break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
}
|
||||
|
||||
switch (t->type) {
|
||||
case TYPE_TRANSFORM3D: {
|
||||
const TransformTrack *tt = static_cast<const TransformTrack *>(t);
|
||||
_track_get_key_indices_in_range(tt->transforms, from_time, to_time, p_indices);
|
||||
|
||||
} break;
|
||||
case TYPE_VALUE: {
|
||||
const ValueTrack *vt = static_cast<const ValueTrack *>(t);
|
||||
_track_get_key_indices_in_range(vt->values, from_time, to_time, p_indices);
|
||||
|
||||
} break;
|
||||
case TYPE_METHOD: {
|
||||
const MethodTrack *mt = static_cast<const MethodTrack *>(t);
|
||||
_track_get_key_indices_in_range(mt->methods, from_time, to_time, p_indices);
|
||||
|
||||
} break;
|
||||
case TYPE_BEZIER: {
|
||||
const BezierTrack *bz = static_cast<const BezierTrack *>(t);
|
||||
_track_get_key_indices_in_range(bz->values, from_time, to_time, p_indices);
|
||||
|
||||
} break;
|
||||
case TYPE_AUDIO: {
|
||||
const AudioTrack *ad = static_cast<const AudioTrack *>(t);
|
||||
_track_get_key_indices_in_range(ad->values, from_time, to_time, p_indices);
|
||||
|
||||
} break;
|
||||
case TYPE_ANIMATION: {
|
||||
const AnimationTrack *an = static_cast<const AnimationTrack *>(t);
|
||||
_track_get_key_indices_in_range(an->values, from_time, to_time, p_indices);
|
||||
|
||||
} break;
|
||||
}
|
||||
}
|
||||
@ -2055,7 +2203,7 @@ void Animation::_method_track_get_key_indices_in_range(const MethodTrack *mt, do
|
||||
}
|
||||
}
|
||||
|
||||
void Animation::method_track_get_key_indices(int p_track, double p_time, double p_delta, List<int> *p_indices) const {
|
||||
void Animation::method_track_get_key_indices(int p_track, double p_time, double p_delta, List<int> *p_indices, int p_pingponged) const {
|
||||
ERR_FAIL_INDEX(p_track, tracks.size());
|
||||
Track *t = tracks[p_track];
|
||||
ERR_FAIL_COND(t->type != TYPE_METHOD);
|
||||
@ -2069,35 +2217,58 @@ void Animation::method_track_get_key_indices(int p_track, double p_time, double
|
||||
SWAP(from_time, to_time);
|
||||
}
|
||||
|
||||
if (loop) {
|
||||
if (from_time > length || from_time < 0) {
|
||||
from_time = Math::fposmod(from_time, length);
|
||||
}
|
||||
switch (loop_mode) {
|
||||
case LOOP_NONE: {
|
||||
if (from_time < 0) {
|
||||
from_time = 0;
|
||||
}
|
||||
if (from_time > length) {
|
||||
from_time = length;
|
||||
}
|
||||
|
||||
if (to_time > length || to_time < 0) {
|
||||
to_time = Math::fposmod(to_time, length);
|
||||
}
|
||||
if (to_time < 0) {
|
||||
to_time = 0;
|
||||
}
|
||||
if (to_time > length) {
|
||||
to_time = length;
|
||||
}
|
||||
} break;
|
||||
case LOOP_LINEAR: {
|
||||
if (from_time > length || from_time < 0) {
|
||||
from_time = Math::fposmod(from_time, length);
|
||||
}
|
||||
if (to_time > length || to_time < 0) {
|
||||
to_time = Math::fposmod(to_time, length);
|
||||
}
|
||||
|
||||
if (from_time > to_time) {
|
||||
// handle loop by splitting
|
||||
_method_track_get_key_indices_in_range(mt, from_time, length, p_indices);
|
||||
_method_track_get_key_indices_in_range(mt, 0, to_time, p_indices);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (from_time < 0) {
|
||||
from_time = 0;
|
||||
}
|
||||
if (from_time > length) {
|
||||
from_time = length;
|
||||
}
|
||||
if (from_time > to_time) {
|
||||
// handle loop by splitting
|
||||
_method_track_get_key_indices_in_range(mt, from_time, length, p_indices);
|
||||
_method_track_get_key_indices_in_range(mt, 0, to_time, p_indices);
|
||||
return;
|
||||
}
|
||||
} break;
|
||||
case LOOP_PINGPONG: {
|
||||
if (from_time > length || from_time < 0) {
|
||||
from_time = Math::pingpong(from_time, length);
|
||||
}
|
||||
if (to_time > length || to_time < 0) {
|
||||
to_time = Math::pingpong(to_time, length);
|
||||
}
|
||||
|
||||
if (to_time < 0) {
|
||||
to_time = 0;
|
||||
}
|
||||
if (to_time > length) {
|
||||
to_time = length;
|
||||
}
|
||||
if (p_pingponged == -1) {
|
||||
_method_track_get_key_indices_in_range(mt, 0, from_time, p_indices);
|
||||
_method_track_get_key_indices_in_range(mt, 0, to_time, p_indices);
|
||||
return;
|
||||
}
|
||||
if (p_pingponged == 1) {
|
||||
_method_track_get_key_indices_in_range(mt, from_time, length, p_indices);
|
||||
_method_track_get_key_indices_in_range(mt, to_time, length, p_indices);
|
||||
return;
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
_method_track_get_key_indices_in_range(mt, from_time, to_time, p_indices);
|
||||
@ -2483,13 +2654,13 @@ real_t Animation::get_length() const {
|
||||
return length;
|
||||
}
|
||||
|
||||
void Animation::set_loop(bool p_enabled) {
|
||||
loop = p_enabled;
|
||||
void Animation::set_loop_mode(Animation::LoopMode p_loop_mode) {
|
||||
loop_mode = p_loop_mode;
|
||||
emit_changed();
|
||||
}
|
||||
|
||||
bool Animation::has_loop() const {
|
||||
return loop;
|
||||
Animation::LoopMode Animation::get_loop_mode() const {
|
||||
return loop_mode;
|
||||
}
|
||||
|
||||
void Animation::track_set_imported(int p_track, bool p_imported) {
|
||||
@ -2628,7 +2799,7 @@ void Animation::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("track_set_interpolation_loop_wrap", "track_idx", "interpolation"), &Animation::track_set_interpolation_loop_wrap);
|
||||
ClassDB::bind_method(D_METHOD("track_get_interpolation_loop_wrap", "track_idx"), &Animation::track_get_interpolation_loop_wrap);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("transform_track_interpolate", "track_idx", "time_sec"), &Animation::_transform_track_interpolate);
|
||||
ClassDB::bind_method(D_METHOD("transform_track_interpolate", "track_idx", "time_sec", "is_backward"), &Animation::_transform_track_interpolate, DEFVAL(false));
|
||||
ClassDB::bind_method(D_METHOD("value_track_set_update_mode", "track_idx", "mode"), &Animation::value_track_set_update_mode);
|
||||
ClassDB::bind_method(D_METHOD("value_track_get_update_mode", "track_idx"), &Animation::value_track_get_update_mode);
|
||||
|
||||
@ -2666,8 +2837,8 @@ void Animation::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_length", "time_sec"), &Animation::set_length);
|
||||
ClassDB::bind_method(D_METHOD("get_length"), &Animation::get_length);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_loop", "enabled"), &Animation::set_loop);
|
||||
ClassDB::bind_method(D_METHOD("has_loop"), &Animation::has_loop);
|
||||
ClassDB::bind_method(D_METHOD("set_loop_mode", "loop_mode"), &Animation::set_loop_mode);
|
||||
ClassDB::bind_method(D_METHOD("get_loop_mode"), &Animation::get_loop_mode);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_step", "size_sec"), &Animation::set_step);
|
||||
ClassDB::bind_method(D_METHOD("get_step"), &Animation::get_step);
|
||||
@ -2676,7 +2847,7 @@ void Animation::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("copy_track", "track_idx", "to_animation"), &Animation::copy_track);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "length", PROPERTY_HINT_RANGE, "0.001,99999,0.001"), "set_length", "get_length");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "loop"), "set_loop", "has_loop");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_mode"), "set_loop_mode", "get_loop_mode");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "step", PROPERTY_HINT_RANGE, "0,4096,0.001"), "set_step", "get_step");
|
||||
|
||||
ADD_SIGNAL(MethodInfo("tracks_changed"));
|
||||
@ -2696,6 +2867,10 @@ void Animation::_bind_methods() {
|
||||
BIND_ENUM_CONSTANT(UPDATE_DISCRETE);
|
||||
BIND_ENUM_CONSTANT(UPDATE_TRIGGER);
|
||||
BIND_ENUM_CONSTANT(UPDATE_CAPTURE);
|
||||
|
||||
BIND_ENUM_CONSTANT(LOOP_NONE);
|
||||
BIND_ENUM_CONSTANT(LOOP_LINEAR);
|
||||
BIND_ENUM_CONSTANT(LOOP_PINGPONG);
|
||||
}
|
||||
|
||||
void Animation::clear() {
|
||||
@ -2703,7 +2878,7 @@ void Animation::clear() {
|
||||
memdelete(tracks[i]);
|
||||
}
|
||||
tracks.clear();
|
||||
loop = false;
|
||||
loop_mode = LOOP_NONE;
|
||||
length = 1;
|
||||
emit_changed();
|
||||
emit_signal(SceneStringNames::get_singleton()->tracks_changed);
|
||||
|
@ -60,7 +60,12 @@ public:
|
||||
UPDATE_DISCRETE,
|
||||
UPDATE_TRIGGER,
|
||||
UPDATE_CAPTURE,
|
||||
};
|
||||
|
||||
enum LoopMode {
|
||||
LOOP_NONE,
|
||||
LOOP_LINEAR,
|
||||
LOOP_PINGPONG,
|
||||
};
|
||||
|
||||
private:
|
||||
@ -184,7 +189,8 @@ private:
|
||||
int _insert(double p_time, T &p_keys, const V &p_value);
|
||||
|
||||
template <class K>
|
||||
inline int _find(const Vector<K> &p_keys, double p_time) const;
|
||||
|
||||
inline int _find(const Vector<K> &p_keys, double p_time, bool p_backward = false) const;
|
||||
|
||||
_FORCE_INLINE_ Animation::TransformKey _interpolate(const Animation::TransformKey &p_a, const Animation::TransformKey &p_b, real_t p_c) const;
|
||||
|
||||
@ -200,7 +206,7 @@ private:
|
||||
_FORCE_INLINE_ real_t _cubic_interpolate(const real_t &p_pre_a, const real_t &p_a, const real_t &p_b, const real_t &p_post_b, real_t p_c) const;
|
||||
|
||||
template <class T>
|
||||
_FORCE_INLINE_ T _interpolate(const Vector<TKey<T>> &p_keys, double p_time, InterpolationType p_interp, bool p_loop_wrap, bool *p_ok) const;
|
||||
_FORCE_INLINE_ T _interpolate(const Vector<TKey<T>> &p_keys, double p_time, InterpolationType p_interp, bool p_loop_wrap, bool *p_ok, bool p_backward = false) const;
|
||||
|
||||
template <class T>
|
||||
_FORCE_INLINE_ void _track_get_key_indices_in_range(const Vector<T> &p_array, double from_time, double to_time, List<int> *p_indices) const;
|
||||
@ -210,15 +216,16 @@ private:
|
||||
|
||||
double length = 1.0;
|
||||
real_t step = 0.1;
|
||||
bool loop = false;
|
||||
LoopMode loop_mode = LOOP_NONE;
|
||||
int pingponged = 0;
|
||||
|
||||
// bind helpers
|
||||
private:
|
||||
Array _transform_track_interpolate(int p_track, double p_time) const {
|
||||
Array _transform_track_interpolate(int p_track, double p_time, bool p_backward = false) const {
|
||||
Vector3 loc;
|
||||
Quaternion rot;
|
||||
Vector3 scale;
|
||||
transform_track_interpolate(p_track, p_time, &loc, &rot, &scale);
|
||||
transform_track_interpolate(p_track, p_time, &loc, &rot, &scale, p_backward);
|
||||
Array ret;
|
||||
ret.push_back(loc);
|
||||
ret.push_back(rot);
|
||||
@ -324,26 +331,26 @@ public:
|
||||
void track_set_interpolation_loop_wrap(int p_track, bool p_enable);
|
||||
bool track_get_interpolation_loop_wrap(int p_track) const;
|
||||
|
||||
Error transform_track_interpolate(int p_track, double p_time, Vector3 *r_loc, Quaternion *r_rot, Vector3 *r_scale) const;
|
||||
Error transform_track_interpolate(int p_track, double p_time, Vector3 *r_loc, Quaternion *r_rot, Vector3 *r_scale, bool p_backward = false) const;
|
||||
|
||||
Variant value_track_interpolate(int p_track, double p_time) const;
|
||||
void value_track_get_key_indices(int p_track, double p_time, double p_delta, List<int> *p_indices) const;
|
||||
void value_track_get_key_indices(int p_track, double p_time, double p_delta, List<int> *p_indices, int p_pingponged = 0) const;
|
||||
void value_track_set_update_mode(int p_track, UpdateMode p_mode);
|
||||
UpdateMode value_track_get_update_mode(int p_track) const;
|
||||
|
||||
void method_track_get_key_indices(int p_track, double p_time, double p_delta, List<int> *p_indices) const;
|
||||
void method_track_get_key_indices(int p_track, double p_time, double p_delta, List<int> *p_indices, int p_pingponged = 0) const;
|
||||
Vector<Variant> method_track_get_params(int p_track, int p_key_idx) const;
|
||||
StringName method_track_get_name(int p_track, int p_key_idx) const;
|
||||
|
||||
void copy_track(int p_track, Ref<Animation> p_to_animation);
|
||||
|
||||
void track_get_key_indices_in_range(int p_track, double p_time, double p_delta, List<int> *p_indices) const;
|
||||
void track_get_key_indices_in_range(int p_track, double p_time, double p_delta, List<int> *p_indices, int p_pingponged = 0) const;
|
||||
|
||||
void set_length(real_t p_length);
|
||||
real_t get_length() const;
|
||||
|
||||
void set_loop(bool p_enabled);
|
||||
bool has_loop() const;
|
||||
void set_loop_mode(LoopMode p_loop_mode);
|
||||
LoopMode get_loop_mode() const;
|
||||
|
||||
void set_step(real_t p_step);
|
||||
real_t get_step() const;
|
||||
@ -359,5 +366,6 @@ public:
|
||||
VARIANT_ENUM_CAST(Animation::TrackType);
|
||||
VARIANT_ENUM_CAST(Animation::InterpolationType);
|
||||
VARIANT_ENUM_CAST(Animation::UpdateMode);
|
||||
VARIANT_ENUM_CAST(Animation::LoopMode);
|
||||
|
||||
#endif
|
||||
|
@ -299,7 +299,7 @@ int AudioStreamPlaybackSample::mix(AudioFrame *p_buffer, float p_rate_scale, int
|
||||
|
||||
if (loop_format != AudioStreamSample::LOOP_DISABLED && offset < loop_begin_fp) {
|
||||
/* loopstart reached */
|
||||
if (loop_format == AudioStreamSample::LOOP_PING_PONG) {
|
||||
if (loop_format == AudioStreamSample::LOOP_PINGPONG) {
|
||||
/* bounce ping pong */
|
||||
offset = loop_begin_fp + (loop_begin_fp - offset);
|
||||
increment = -increment;
|
||||
@ -320,7 +320,7 @@ int AudioStreamPlaybackSample::mix(AudioFrame *p_buffer, float p_rate_scale, int
|
||||
if (loop_format != AudioStreamSample::LOOP_DISABLED && offset >= loop_end_fp) {
|
||||
/* loopend reached */
|
||||
|
||||
if (loop_format == AudioStreamSample::LOOP_PING_PONG) {
|
||||
if (loop_format == AudioStreamSample::LOOP_PINGPONG) {
|
||||
/* bounce ping pong */
|
||||
offset = loop_end_fp - (offset - loop_end_fp);
|
||||
increment = -increment;
|
||||
@ -650,7 +650,7 @@ void AudioStreamSample::_bind_methods() {
|
||||
|
||||
BIND_ENUM_CONSTANT(LOOP_DISABLED);
|
||||
BIND_ENUM_CONSTANT(LOOP_FORWARD);
|
||||
BIND_ENUM_CONSTANT(LOOP_PING_PONG);
|
||||
BIND_ENUM_CONSTANT(LOOP_PINGPONG);
|
||||
BIND_ENUM_CONSTANT(LOOP_BACKWARD);
|
||||
}
|
||||
|
||||
|
@ -92,7 +92,7 @@ public:
|
||||
enum LoopMode {
|
||||
LOOP_DISABLED,
|
||||
LOOP_FORWARD,
|
||||
LOOP_PING_PONG,
|
||||
LOOP_PINGPONG,
|
||||
LOOP_BACKWARD
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user