Răsfoiți Sursa

Refactor process of animation to retrive keys more exactly

Silc Renew 2 ani în urmă
părinte
comite
1fc3833617

+ 9 - 0
doc/classes/Animation.xml

@@ -616,5 +616,14 @@
 		<constant name="LOOP_PINGPONG" value="2" enum="LoopMode">
 			Repeats playback and reverse playback at both ends of the animation.
 		</constant>
+		<constant name="LOOPED_FLAG_NONE" value="0" enum="LoopedFlag">
+			This flag indicates that the animation proceeds without any looping.
+		</constant>
+		<constant name="LOOPED_FLAG_END" value="1" enum="LoopedFlag">
+			This flag indicates that the animation has reached the end of the animation and just after loop processed.
+		</constant>
+		<constant name="LOOPED_FLAG_START" value="2" enum="LoopedFlag">
+			This flag indicates that the animation has reached the start of the animation and just after loop processed.
+		</constant>
 	</constants>
 </class>

+ 2 - 1
doc/classes/AnimationNode.xml

@@ -75,9 +75,10 @@
 			<param index="3" name="seeked" type="bool" />
 			<param index="4" name="is_external_seeking" type="bool" />
 			<param index="5" name="blend" type="float" />
-			<param index="6" name="pingponged" type="int" default="0" />
+			<param index="6" name="looped_flag" type="int" enum="Animation.LoopedFlag" default="0" />
 			<description>
 				Blend an animation by [param blend] amount (name must be valid in the linked [AnimationPlayer]). A [param time] and [param delta] may be passed, as well as whether [param seeked] happened.
+				A [param looped_flag] is used by internal processing immediately after the loop. See also [enum Animation.LoopedFlag].
 			</description>
 		</method>
 		<method name="blend_input">

+ 27 - 27
scene/animation/animation_blend_tree.cpp

@@ -87,36 +87,38 @@ double AnimationNodeAnimation::process(double p_time, bool p_seek, bool p_is_ext
 	double anim_size = (double)anim->get_length();
 	double step = 0.0;
 	double prev_time = cur_time;
-	int pingponged = 0;
-	bool current_backward = signbit(p_time);
+	Animation::LoopedFlag looped_flag = Animation::LOOPED_FLAG_NONE;
+	bool node_backward = play_mode == PLAY_MODE_BACKWARD;
 
 	if (p_seek) {
 		step = p_time - cur_time;
 		cur_time = p_time;
 	} else {
 		p_time *= backward ? -1.0 : 1.0;
-		if (!(cur_time == anim_size && !current_backward) && !(cur_time == 0 && current_backward)) {
-			cur_time = cur_time + p_time;
-			step = p_time;
-		}
+		cur_time = cur_time + p_time;
+		step = p_time;
 	}
 
 	if (anim->get_loop_mode() == Animation::LOOP_PINGPONG) {
 		if (!Math::is_zero_approx(anim_size)) {
-			if ((int)Math::floor(abs(cur_time - prev_time) / anim_size) % 2 == 0) {
-				if (prev_time >= 0 && cur_time < 0) {
-					backward = !backward;
-					pingponged = -1;
-				}
-				if (prev_time <= anim_size && cur_time > anim_size) {
-					backward = !backward;
-					pingponged = 1;
-				}
+			if (prev_time >= 0 && cur_time < 0) {
+				backward = !backward;
+				looped_flag = node_backward ? Animation::LOOPED_FLAG_END : Animation::LOOPED_FLAG_START;
+			}
+			if (prev_time <= anim_size && cur_time > anim_size) {
+				backward = !backward;
+				looped_flag = node_backward ? Animation::LOOPED_FLAG_START : Animation::LOOPED_FLAG_END;
 			}
 			cur_time = Math::pingpong(cur_time, anim_size);
 		}
 	} else if (anim->get_loop_mode() == Animation::LOOP_LINEAR) {
 		if (!Math::is_zero_approx(anim_size)) {
+			if (prev_time >= 0 && cur_time < 0) {
+				looped_flag = node_backward ? Animation::LOOPED_FLAG_END : Animation::LOOPED_FLAG_START;
+			}
+			if (prev_time <= anim_size && cur_time > anim_size) {
+				looped_flag = node_backward ? Animation::LOOPED_FLAG_START : Animation::LOOPED_FLAG_END;
+			}
 			cur_time = Math::fposmod(cur_time, anim_size);
 		}
 		backward = false;
@@ -145,9 +147,9 @@ double AnimationNodeAnimation::process(double p_time, bool p_seek, bool p_is_ext
 	}
 
 	if (play_mode == PLAY_MODE_FORWARD) {
-		blend_animation(animation, cur_time, step, p_seek, p_is_external_seeking, 1.0, pingponged);
+		blend_animation(animation, cur_time, step, p_seek, p_is_external_seeking, 1.0, looped_flag);
 	} else {
-		blend_animation(animation, anim_size - cur_time, -step, p_seek, p_is_external_seeking, 1.0, pingponged);
+		blend_animation(animation, anim_size - cur_time, -step, p_seek, p_is_external_seeking, 1.0, looped_flag);
 	}
 	set_parameter(time, cur_time);
 
@@ -309,9 +311,7 @@ double AnimationNodeOneShot::process(double p_time, bool p_seek, bool p_is_exter
 			set_parameter(time_to_restart, cur_time_to_restart);
 		}
 
-		if (!cur_active) {
-			return blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync);
-		}
+		return blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync);
 	}
 
 	bool os_seek = p_seek;
@@ -349,10 +349,9 @@ double AnimationNodeOneShot::process(double p_time, bool p_seek, bool p_is_exter
 	if (mix == MIX_MODE_ADD) {
 		main_rem = blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync);
 	} else {
-		main_rem = blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0 - blend, FILTER_BLEND, sync);
+		main_rem = blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0 - blend, FILTER_BLEND, sync); // Unlike below, processing this edge is a corner case.
 	}
-
-	double os_rem = blend_input(1, os_seek ? cur_time : p_time, os_seek, p_is_external_seeking, blend, FILTER_PASS, true);
+	double os_rem = blend_input(1, os_seek ? cur_time : p_time, os_seek, p_is_external_seeking, MAX(CMP_EPSILON, blend), FILTER_PASS, true); // Blend values must be more than CMP_EPSILON to process discrete keys in edge.
 
 	if (do_start) {
 		cur_remaining = os_rem;
@@ -769,17 +768,18 @@ double AnimationNodeTransition::process(double p_time, bool p_seek, bool p_is_ex
 			blend = xfade_curve->sample(blend);
 		}
 
+		// Blend values must be more than CMP_EPSILON to process discrete keys in edge.
 		if (from_start && !p_seek && switched) { //just switched, seek to start of current
-			rem = blend_input(cur_current, 0, true, p_is_external_seeking, 1.0 - blend, FILTER_IGNORE, true);
+			rem = blend_input(cur_current, 0, true, p_is_external_seeking, MAX(CMP_EPSILON, 1.0 - blend), FILTER_IGNORE, true);
 		} else {
-			rem = blend_input(cur_current, p_time, p_seek, p_is_external_seeking, 1.0 - blend, FILTER_IGNORE, true);
+			rem = blend_input(cur_current, p_time, p_seek, p_is_external_seeking, MAX(CMP_EPSILON, 1.0 - blend), FILTER_IGNORE, true);
 		}
 
 		if (p_seek) {
-			blend_input(cur_prev, p_time, true, p_is_external_seeking, blend, FILTER_IGNORE, true);
+			blend_input(cur_prev, p_time, true, p_is_external_seeking, MAX(CMP_EPSILON, blend), FILTER_IGNORE, true);
 			cur_time = p_time;
 		} else {
-			blend_input(cur_prev, p_time, false, p_is_external_seeking, blend, FILTER_IGNORE, true);
+			blend_input(cur_prev, p_time, false, p_is_external_seeking, MAX(CMP_EPSILON, blend), FILTER_IGNORE, true);
 			cur_time += p_time;
 			cur_prev_xfading -= p_time;
 			if (cur_prev_xfading < 0) {

+ 3 - 5
scene/animation/animation_node_state_machine.cpp

@@ -433,10 +433,10 @@ double AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_s
 	if (current_curve.is_valid()) {
 		fade_blend = current_curve->sample(fade_blend);
 	}
-	float rem = p_state_machine->blend_node(current, p_state_machine->states[current].node, p_time, p_seek, p_is_external_seeking, fade_blend, AnimationNode::FILTER_IGNORE, true);
+	float rem = p_state_machine->blend_node(current, p_state_machine->states[current].node, p_time, p_seek, p_is_external_seeking, MAX(CMP_EPSILON, fade_blend), AnimationNode::FILTER_IGNORE, true); // Blend values must be more than CMP_EPSILON to process discrete keys in edge.
 
 	if (fading_from != StringName()) {
-		p_state_machine->blend_node(fading_from, p_state_machine->states[fading_from].node, p_time, p_seek, p_is_external_seeking, 1.0 - fade_blend, AnimationNode::FILTER_IGNORE, true);
+		p_state_machine->blend_node(fading_from, p_state_machine->states[fading_from].node, p_time, p_seek, p_is_external_seeking, MAX(CMP_EPSILON, 1.0 - fade_blend), AnimationNode::FILTER_IGNORE, true); // Blend values must be more than CMP_EPSILON to process discrete keys in edge.
 	}
 
 	//guess playback position
@@ -599,13 +599,11 @@ double AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_s
 
 			current = next;
 
+			len_current = p_state_machine->blend_node(current, p_state_machine->states[current].node, 0, true, p_is_external_seeking, CMP_EPSILON, AnimationNode::FILTER_IGNORE, true); // Process next node's first key in here.
 			if (switch_mode == AnimationNodeStateMachineTransition::SWITCH_MODE_SYNC) {
-				len_current = p_state_machine->blend_node(current, p_state_machine->states[current].node, 0, true, p_is_external_seeking, 0, AnimationNode::FILTER_IGNORE, true);
 				pos_current = MIN(pos_current, len_current);
 				p_state_machine->blend_node(current, p_state_machine->states[current].node, pos_current, true, p_is_external_seeking, 0, AnimationNode::FILTER_IGNORE, true);
-
 			} else {
-				len_current = p_state_machine->blend_node(current, p_state_machine->states[current].node, 0, true, p_is_external_seeking, 0, AnimationNode::FILTER_IGNORE, true);
 				pos_current = 0;
 			}
 

+ 44 - 31
scene/animation/animation_player.cpp

@@ -468,7 +468,7 @@ Variant AnimationPlayer::_post_process_key_value(const Ref<Animation> &p_anim, i
 	return p_value;
 }
 
-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) {
+void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double p_prev_time, double p_time, double p_delta, float p_interp, bool p_is_current, bool p_seeked, bool p_started, Animation::LoopedFlag p_looped_flag) {
 	_ensure_node_caches(p_anim);
 	ERR_FAIL_COND(p_anim->node_cache.size() != p_anim->animation->get_track_count());
 
@@ -683,7 +683,13 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
 
 				} else if (p_is_current && p_delta != 0) {
 					List<int> indices;
-					a->track_get_key_indices_in_range(i, p_time, p_delta, &indices, p_pingponged);
+					if (p_started) {
+						int first_key = a->track_find_key(i, p_prev_time, true);
+						if (first_key >= 0) {
+							indices.push_back(first_key);
+						}
+					}
+					a->track_get_key_indices_in_range(i, p_time, p_delta, &indices, p_looped_flag);
 
 					for (int &F : indices) {
 						Variant value = a->track_get_key_value(i, F);
@@ -742,7 +748,13 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
 				}
 
 				List<int> indices;
-				a->track_get_key_indices_in_range(i, p_time, p_delta, &indices, p_pingponged);
+				if (p_started) {
+					int first_key = a->track_find_key(i, p_prev_time, true);
+					if (first_key >= 0) {
+						indices.push_back(first_key);
+					}
+				}
+				a->track_get_key_indices_in_range(i, p_time, p_delta, &indices, p_looped_flag);
 
 				for (int &E : indices) {
 					StringName method = a->method_track_get_name(i, E);
@@ -832,7 +844,13 @@ 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, p_pingponged);
+					if (p_started) {
+						int first_key = a->track_find_key(i, p_prev_time, true);
+						if (first_key >= 0) {
+							to_play.push_back(first_key);
+						}
+					}
+					a->track_get_key_indices_in_range(i, p_time, p_delta, &to_play, p_looped_flag);
 					if (to_play.size()) {
 						int idx = to_play.back()->get();
 
@@ -939,7 +957,13 @@ 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, p_pingponged);
+					if (p_started) {
+						int first_key = a->track_find_key(i, p_prev_time, true);
+						if (first_key >= 0) {
+							to_play.push_back(first_key);
+						}
+					}
+					a->track_get_key_indices_in_range(i, p_time, p_delta, &to_play, p_looped_flag);
 					if (to_play.size()) {
 						int idx = to_play.back()->get();
 
@@ -969,7 +993,7 @@ void AnimationPlayer::_animation_process_data(PlaybackData &cd, double p_delta,
 	double next_pos = cd.pos + delta;
 
 	real_t len = cd.from->animation->get_length();
-	int pingponged = 0;
+	Animation::LoopedFlag looped_flag = Animation::LOOPED_FLAG_NONE;
 
 	switch (cd.from->animation->get_loop_mode()) {
 		case Animation::LOOP_NONE: {
@@ -998,44 +1022,33 @@ void AnimationPlayer::_animation_process_data(PlaybackData &cd, double p_delta,
 		} break;
 
 		case Animation::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;
+			if (next_pos < 0 && cd.pos >= 0) {
+				looped_flag = Animation::LOOPED_FLAG_START;
 			}
+			if (next_pos > len && cd.pos <= len) {
+				looped_flag = Animation::LOOPED_FLAG_END;
+			}
+			next_pos = Math::fposmod(next_pos, (double)len);
 		} break;
 
 		case Animation::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;
-				}
+			if (next_pos < 0 && cd.pos >= 0) {
+				cd.speed_scale *= -1.0;
+				looped_flag = Animation::LOOPED_FLAG_START;
 			}
-			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;
+			if (next_pos > len && cd.pos <= len) {
+				cd.speed_scale *= -1.0;
+				looped_flag = Animation::LOOPED_FLAG_END;
 			}
+			next_pos = Math::pingpong(next_pos, (double)len);
 		} break;
 
 		default:
 			break;
 	}
 
+	_animation_process_animation(cd.from, cd.pos, next_pos, delta, p_blend, &cd == &playback.current, p_seeked, p_started, looped_flag);
 	cd.pos = next_pos;
-
-	_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) {

+ 1 - 1
scene/animation/animation_player.h

@@ -268,7 +268,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, int p_pingponged = 0);
+	void _animation_process_animation(AnimationData *p_anim, double p_prev_time, double p_time, double p_delta, float p_interp, bool p_is_current = true, bool p_seeked = false, bool p_started = false, Animation::LoopedFlag p_looped_flag = Animation::LOOPED_FLAG_NONE);
 
 	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);

+ 8 - 8
scene/animation/animation_tree.cpp

@@ -86,7 +86,7 @@ void AnimationNode::get_child_nodes(List<ChildNode> *r_child_nodes) {
 	}
 }
 
-void AnimationNode::blend_animation(const StringName &p_animation, double p_time, double p_delta, bool p_seeked, bool p_is_external_seeking, real_t p_blend, int p_pingponged) {
+void AnimationNode::blend_animation(const StringName &p_animation, double p_time, double p_delta, bool p_seeked, bool p_is_external_seeking, real_t p_blend, Animation::LoopedFlag p_looped_flag) {
 	ERR_FAIL_COND(!state);
 	ERR_FAIL_COND(!state->player->has_animation(p_animation));
 
@@ -112,7 +112,7 @@ void AnimationNode::blend_animation(const StringName &p_animation, double p_time
 	anim_state.time = p_time;
 	anim_state.animation = animation;
 	anim_state.seeked = p_seeked;
-	anim_state.pingponged = p_pingponged;
+	anim_state.looped_flag = p_looped_flag;
 	anim_state.is_external_seeking = p_is_external_seeking;
 
 	state->animation_states.push_back(anim_state);
@@ -413,7 +413,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", "is_external_seeking", "blend", "pingponged"), &AnimationNode::blend_animation, DEFVAL(0));
+	ClassDB::bind_method(D_METHOD("blend_animation", "animation", "time", "delta", "seeked", "is_external_seeking", "blend", "looped_flag"), &AnimationNode::blend_animation, DEFVAL(Animation::LOOPED_FLAG_NONE));
 	ClassDB::bind_method(D_METHOD("blend_node", "name", "node", "time", "seek", "is_external_seeking", "blend", "filter", "sync"), &AnimationNode::blend_node, DEFVAL(FILTER_IGNORE), DEFVAL(true));
 	ClassDB::bind_method(D_METHOD("blend_input", "input_index", "time", "seek", "is_external_seeking", "blend", "filter", "sync"), &AnimationNode::blend_input, DEFVAL(FILTER_IGNORE), DEFVAL(true));
 
@@ -1019,7 +1019,7 @@ void AnimationTree::_process_graph(double p_delta) {
 			double delta = as.delta;
 			real_t weight = as.blend;
 			bool seeked = as.seeked;
-			int pingponged = as.pingponged;
+			Animation::LoopedFlag looped_flag = as.looped_flag;
 			bool is_external_seeking = as.is_external_seeking;
 #ifndef _3D_DISABLED
 			bool backward = signbit(delta); // This flag is required only for the root motion since it calculates the difference between the previous and current frames.
@@ -1391,7 +1391,7 @@ void AnimationTree::_process_graph(double p_delta) {
 								t->object->set_indexed(t->subpath, value);
 							} else {
 								List<int> indices;
-								a->track_get_key_indices_in_range(i, time, delta, &indices, pingponged);
+								a->track_get_key_indices_in_range(i, time, delta, &indices, looped_flag);
 								for (int &F : indices) {
 									Variant value = a->track_get_key_value(i, F);
 									value = _post_process_key_value(a, i, value, t->object);
@@ -1416,7 +1416,7 @@ void AnimationTree::_process_graph(double p_delta) {
 							}
 						} else {
 							List<int> indices;
-							a->track_get_key_indices_in_range(i, time, delta, &indices, pingponged);
+							a->track_get_key_indices_in_range(i, time, delta, &indices, looped_flag);
 							for (int &F : indices) {
 								StringName method = a->method_track_get_name(i, F);
 								Vector<Variant> params = a->method_track_get_params(i, F);
@@ -1479,7 +1479,7 @@ void AnimationTree::_process_graph(double p_delta) {
 						} else {
 							//find stuff to play
 							List<int> to_play;
-							a->track_get_key_indices_in_range(i, time, delta, &to_play, pingponged);
+							a->track_get_key_indices_in_range(i, time, delta, &to_play, looped_flag);
 							if (to_play.size()) {
 								int idx = to_play.back()->get();
 
@@ -1594,7 +1594,7 @@ void AnimationTree::_process_graph(double p_delta) {
 						} else {
 							//find stuff to play
 							List<int> to_play;
-							a->track_get_key_indices_in_range(i, time, delta, &to_play, pingponged);
+							a->track_get_key_indices_in_range(i, time, delta, &to_play, looped_flag);
 							if (to_play.size()) {
 								int idx = to_play.back()->get();
 

+ 2 - 2
scene/animation/animation_tree.h

@@ -69,7 +69,7 @@ public:
 		real_t blend = 0.0;
 		bool seeked = false;
 		bool is_external_seeking = false;
-		int pingponged = 0;
+		Animation::LoopedFlag looped_flag = Animation::LOOPED_FLAG_NONE;
 	};
 
 	struct State {
@@ -102,7 +102,7 @@ public:
 	double _blend_node(const StringName &p_subpath, const Vector<StringName> &p_connections, AnimationNode *p_new_parent, Ref<AnimationNode> p_node, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, real_t *r_max = nullptr);
 
 protected:
-	void blend_animation(const StringName &p_animation, double p_time, double p_delta, bool p_seeked, bool p_is_external_seeking, real_t p_blend, int p_pingponged = 0);
+	void blend_animation(const StringName &p_animation, double p_time, double p_delta, bool p_seeked, bool p_is_external_seeking, real_t p_blend, Animation::LoopedFlag p_looped_flag = Animation::LOOPED_FLAG_NONE);
 	double blend_node(const StringName &p_sub_path, Ref<AnimationNode> p_node, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true);
 	double blend_input(int p_input, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true);
 

+ 289 - 197
scene/resources/animation.cpp

@@ -2726,40 +2726,60 @@ Animation::UpdateMode Animation::value_track_get_update_mode(int p_track) const
 }
 
 template <class T>
-void Animation::_track_get_key_indices_in_range(const Vector<T> &p_array, double from_time, double to_time, List<int> *p_indices) const {
-	if (to_time == length) {
-		to_time = length + CMP_EPSILON; //include a little more if at the end
+void Animation::_track_get_key_indices_in_range(const Vector<T> &p_array, double from_time, double to_time, List<int> *p_indices, bool p_is_backward) const {
+	int len = p_array.size();
+	if (len == 0) {
+		return;
 	}
 
-	int to = _find(p_array, to_time);
-
-	// can't really send the events == time, will be sent in the next frame.
-	// if event>=len then it will probably never be requested by the anim player.
-
-	if (to >= 0 && p_array[to].time >= to_time) {
-		to--;
-	}
+	int from = 0;
+	int to = len - 1;
 
-	if (to < 0) {
-		return; // not bother
+	if (!p_is_backward) {
+		while (p_array[from].time < from_time || Math::is_equal_approx(p_array[from].time, from_time)) {
+			from++;
+			if (to < from) {
+				return;
+			}
+		}
+		while (p_array[to].time > to_time && !Math::is_equal_approx(p_array[to].time, to_time)) {
+			to--;
+			if (to < from) {
+				return;
+			}
+		}
+	} else {
+		while (p_array[from].time < from_time && !Math::is_equal_approx(p_array[from].time, from_time)) {
+			from++;
+			if (to < from) {
+				return;
+			}
+		}
+		while (p_array[to].time > to_time || Math::is_equal_approx(p_array[to].time, to_time)) {
+			to--;
+			if (to < from) {
+				return;
+			}
+		}
 	}
 
-	int from = _find(p_array, from_time);
-
-	// position in the right first event.+
-	if (from < 0 || p_array[from].time < from_time) {
-		from++;
+	if (from == to) {
+		p_indices->push_back(from);
+		return;
 	}
 
-	int max = p_array.size();
-
-	for (int i = from; i <= to; i++) {
-		ERR_CONTINUE(i < 0 || i >= max); // shouldn't happen
-		p_indices->push_back(i);
+	if (!p_is_backward) {
+		for (int i = from; i <= to; i++) {
+			p_indices->push_back(i);
+		}
+	} else {
+		for (int i = to; i >= to; i--) {
+			p_indices->push_back(i);
+		}
 	}
 }
 
-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 {
+void Animation::track_get_key_indices_in_range(int p_track, double p_time, double p_delta, List<int> *p_indices, Animation::LoopedFlag p_looped_flag) const {
 	ERR_FAIL_INDEX(p_track, tracks.size());
 
 	if (p_delta == 0) {
@@ -2771,7 +2791,9 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl
 	double from_time = p_time - p_delta;
 	double to_time = p_time;
 
+	bool is_backward = false;
 	if (from_time > to_time) {
+		is_backward = true;
 		SWAP(from_time, to_time);
 	}
 
@@ -2800,7 +2822,10 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl
 			}
 
 			if (from_time > to_time) {
-				// handle loop by splitting
+				// Handle loop by splitting.
+				double anim_end = length + CMP_EPSILON;
+				double anim_start = -CMP_EPSILON;
+
 				switch (t->type) {
 					case TYPE_POSITION_3D: {
 						const PositionTrack *tt = static_cast<const PositionTrack *>(t);
@@ -2808,8 +2833,13 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl
 							_get_compressed_key_indices_in_range<3>(tt->compressed_track, from_time, length, p_indices);
 							_get_compressed_key_indices_in_range<3>(tt->compressed_track, 0, to_time, p_indices);
 						} else {
-							_track_get_key_indices_in_range(tt->positions, from_time, length, p_indices);
-							_track_get_key_indices_in_range(tt->positions, 0, to_time, p_indices);
+							if (!is_backward) {
+								_track_get_key_indices_in_range(tt->positions, from_time, anim_end, p_indices, is_backward);
+								_track_get_key_indices_in_range(tt->positions, anim_start, to_time, p_indices, is_backward);
+							} else {
+								_track_get_key_indices_in_range(tt->positions, anim_start, to_time, p_indices, is_backward);
+								_track_get_key_indices_in_range(tt->positions, from_time, anim_end, p_indices, is_backward);
+							}
 						}
 					} break;
 					case TYPE_ROTATION_3D: {
@@ -2818,8 +2848,13 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl
 							_get_compressed_key_indices_in_range<3>(rt->compressed_track, from_time, length, p_indices);
 							_get_compressed_key_indices_in_range<3>(rt->compressed_track, 0, to_time, p_indices);
 						} else {
-							_track_get_key_indices_in_range(rt->rotations, from_time, length, p_indices);
-							_track_get_key_indices_in_range(rt->rotations, 0, to_time, p_indices);
+							if (!is_backward) {
+								_track_get_key_indices_in_range(rt->rotations, from_time, anim_end, p_indices, is_backward);
+								_track_get_key_indices_in_range(rt->rotations, anim_start, to_time, p_indices, is_backward);
+							} else {
+								_track_get_key_indices_in_range(rt->rotations, anim_start, to_time, p_indices, is_backward);
+								_track_get_key_indices_in_range(rt->rotations, from_time, anim_end, p_indices, is_backward);
+							}
 						}
 					} break;
 					case TYPE_SCALE_3D: {
@@ -2828,8 +2863,13 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl
 							_get_compressed_key_indices_in_range<3>(st->compressed_track, from_time, length, p_indices);
 							_get_compressed_key_indices_in_range<3>(st->compressed_track, 0, to_time, p_indices);
 						} else {
-							_track_get_key_indices_in_range(st->scales, from_time, length, p_indices);
-							_track_get_key_indices_in_range(st->scales, 0, to_time, p_indices);
+							if (!is_backward) {
+								_track_get_key_indices_in_range(st->scales, from_time, anim_end, p_indices, is_backward);
+								_track_get_key_indices_in_range(st->scales, anim_start, to_time, p_indices, is_backward);
+							} else {
+								_track_get_key_indices_in_range(st->scales, anim_start, to_time, p_indices, is_backward);
+								_track_get_key_indices_in_range(st->scales, from_time, anim_end, p_indices, is_backward);
+							}
 						}
 					} break;
 					case TYPE_BLEND_SHAPE: {
@@ -2838,38 +2878,83 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl
 							_get_compressed_key_indices_in_range<1>(bst->compressed_track, from_time, length, p_indices);
 							_get_compressed_key_indices_in_range<1>(bst->compressed_track, 0, to_time, p_indices);
 						} else {
-							_track_get_key_indices_in_range(bst->blend_shapes, from_time, length, p_indices);
-							_track_get_key_indices_in_range(bst->blend_shapes, 0, to_time, p_indices);
+							if (!is_backward) {
+								_track_get_key_indices_in_range(bst->blend_shapes, from_time, anim_end, p_indices, is_backward);
+								_track_get_key_indices_in_range(bst->blend_shapes, anim_start, to_time, p_indices, is_backward);
+							} else {
+								_track_get_key_indices_in_range(bst->blend_shapes, anim_start, to_time, p_indices, is_backward);
+								_track_get_key_indices_in_range(bst->blend_shapes, from_time, anim_end, p_indices, is_backward);
+							}
 						}
 					} 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);
+						if (!is_backward) {
+							_track_get_key_indices_in_range(vt->values, from_time, anim_end, p_indices, is_backward);
+							_track_get_key_indices_in_range(vt->values, anim_start, to_time, p_indices, is_backward);
+						} else {
+							_track_get_key_indices_in_range(vt->values, anim_start, to_time, p_indices, is_backward);
+							_track_get_key_indices_in_range(vt->values, from_time, anim_end, p_indices, is_backward);
+						}
 					} 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);
+						if (!is_backward) {
+							_track_get_key_indices_in_range(mt->methods, from_time, anim_end, p_indices, is_backward);
+							_track_get_key_indices_in_range(mt->methods, anim_start, to_time, p_indices, is_backward);
+						} else {
+							_track_get_key_indices_in_range(mt->methods, anim_start, to_time, p_indices, is_backward);
+							_track_get_key_indices_in_range(mt->methods, from_time, anim_end, p_indices, is_backward);
+						}
 					} 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);
+						if (!is_backward) {
+							_track_get_key_indices_in_range(bz->values, from_time, anim_end, p_indices, is_backward);
+							_track_get_key_indices_in_range(bz->values, anim_start, to_time, p_indices, is_backward);
+						} else {
+							_track_get_key_indices_in_range(bz->values, anim_start, to_time, p_indices, is_backward);
+							_track_get_key_indices_in_range(bz->values, from_time, anim_end, p_indices, is_backward);
+						}
 					} 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);
+						if (!is_backward) {
+							_track_get_key_indices_in_range(ad->values, from_time, anim_end, p_indices, is_backward);
+							_track_get_key_indices_in_range(ad->values, anim_start, to_time, p_indices, is_backward);
+						} else {
+							_track_get_key_indices_in_range(ad->values, anim_start, to_time, p_indices, is_backward);
+							_track_get_key_indices_in_range(ad->values, from_time, anim_end, p_indices, is_backward);
+						}
 					} 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);
+						if (!is_backward) {
+							_track_get_key_indices_in_range(an->values, from_time, anim_end, p_indices, is_backward);
+							_track_get_key_indices_in_range(an->values, anim_start, to_time, p_indices, is_backward);
+						} else {
+							_track_get_key_indices_in_range(an->values, anim_start, to_time, p_indices, is_backward);
+							_track_get_key_indices_in_range(an->values, from_time, anim_end, p_indices, is_backward);
+						}
 					} break;
 				}
 				return;
 			}
+
+			// Not from_time > to_time but most recent of looping...
+			if (p_looped_flag != Animation::LOOPED_FLAG_NONE) {
+				if (!is_backward && Math::is_equal_approx(from_time, 0)) {
+					int edge = track_find_key(p_track, 0, true);
+					if (edge >= 0) {
+						p_indices->push_back(edge);
+					}
+				} else if (is_backward && Math::is_equal_approx(to_time, length)) {
+					int edge = track_find_key(p_track, length, true);
+					if (edge >= 0) {
+						p_indices->push_back(edge);
+					}
+				}
+			}
 		} break;
 		case LOOP_PINGPONG: {
 			if (from_time > length || from_time < 0) {
@@ -2879,162 +2964,164 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl
 				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
-					to_time = MAX(CMP_EPSILON, to_time); // To avoid overlapping keys at the turnaround point, one of the point will needs to be shifted slightly.
-					switch (t->type) {
-						case TYPE_POSITION_3D: {
-							const PositionTrack *tt = static_cast<const PositionTrack *>(t);
-							if (tt->compressed_track >= 0) {
-								_get_compressed_key_indices_in_range<3>(tt->compressed_track, 0, from_time, p_indices);
-								_get_compressed_key_indices_in_range<3>(tt->compressed_track, CMP_EPSILON, to_time, p_indices);
-							} else {
-								_track_get_key_indices_in_range(tt->positions, 0, from_time, p_indices);
-								_track_get_key_indices_in_range(tt->positions, CMP_EPSILON, to_time, p_indices);
-							}
-						} break;
-						case TYPE_ROTATION_3D: {
-							const RotationTrack *rt = static_cast<const RotationTrack *>(t);
-							if (rt->compressed_track >= 0) {
-								_get_compressed_key_indices_in_range<3>(rt->compressed_track, 0, from_time, p_indices);
-								_get_compressed_key_indices_in_range<3>(rt->compressed_track, CMP_EPSILON, to_time, p_indices);
-							} else {
-								_track_get_key_indices_in_range(rt->rotations, 0, from_time, p_indices);
-								_track_get_key_indices_in_range(rt->rotations, CMP_EPSILON, to_time, p_indices);
-							}
-						} break;
-						case TYPE_SCALE_3D: {
-							const ScaleTrack *st = static_cast<const ScaleTrack *>(t);
-							if (st->compressed_track >= 0) {
-								_get_compressed_key_indices_in_range<3>(st->compressed_track, 0, from_time, p_indices);
-								_get_compressed_key_indices_in_range<3>(st->compressed_track, CMP_EPSILON, to_time, p_indices);
-							} else {
-								_track_get_key_indices_in_range(st->scales, 0, from_time, p_indices);
-								_track_get_key_indices_in_range(st->scales, CMP_EPSILON, to_time, p_indices);
-							}
-						} break;
-						case TYPE_BLEND_SHAPE: {
-							const BlendShapeTrack *bst = static_cast<const BlendShapeTrack *>(t);
-							if (bst->compressed_track >= 0) {
-								_get_compressed_key_indices_in_range<1>(bst->compressed_track, 0, from_time, p_indices);
-								_get_compressed_key_indices_in_range<1>(bst->compressed_track, CMP_EPSILON, to_time, p_indices);
-							} else {
-								_track_get_key_indices_in_range(bst->blend_shapes, 0, from_time, p_indices);
-								_track_get_key_indices_in_range(bst->blend_shapes, CMP_EPSILON, 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, CMP_EPSILON, 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, CMP_EPSILON, 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, CMP_EPSILON, 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, CMP_EPSILON, 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, CMP_EPSILON, to_time, p_indices);
-						} break;
-					}
-					return;
+			if (p_looped_flag == Animation::LOOPED_FLAG_START) {
+				// Handle loop by splitting.
+				switch (t->type) {
+					case TYPE_POSITION_3D: {
+						const PositionTrack *tt = static_cast<const PositionTrack *>(t);
+						if (tt->compressed_track >= 0) {
+							_get_compressed_key_indices_in_range<3>(tt->compressed_track, 0, from_time, p_indices);
+							_get_compressed_key_indices_in_range<3>(tt->compressed_track, CMP_EPSILON, to_time, p_indices);
+						} else {
+							_track_get_key_indices_in_range(tt->positions, 0, from_time, p_indices, true);
+							_track_get_key_indices_in_range(tt->positions, 0, to_time, p_indices, false);
+						}
+					} break;
+					case TYPE_ROTATION_3D: {
+						const RotationTrack *rt = static_cast<const RotationTrack *>(t);
+						if (rt->compressed_track >= 0) {
+							_get_compressed_key_indices_in_range<3>(rt->compressed_track, 0, from_time, p_indices);
+							_get_compressed_key_indices_in_range<3>(rt->compressed_track, CMP_EPSILON, to_time, p_indices);
+						} else {
+							_track_get_key_indices_in_range(rt->rotations, 0, from_time, p_indices, true);
+							_track_get_key_indices_in_range(rt->rotations, 0, to_time, p_indices, false);
+						}
+					} break;
+					case TYPE_SCALE_3D: {
+						const ScaleTrack *st = static_cast<const ScaleTrack *>(t);
+						if (st->compressed_track >= 0) {
+							_get_compressed_key_indices_in_range<3>(st->compressed_track, 0, from_time, p_indices);
+							_get_compressed_key_indices_in_range<3>(st->compressed_track, 0, to_time, p_indices);
+						} else {
+							_track_get_key_indices_in_range(st->scales, 0, from_time, p_indices, true);
+							_track_get_key_indices_in_range(st->scales, 0, to_time, p_indices, false);
+						}
+					} break;
+					case TYPE_BLEND_SHAPE: {
+						const BlendShapeTrack *bst = static_cast<const BlendShapeTrack *>(t);
+						if (bst->compressed_track >= 0) {
+							_get_compressed_key_indices_in_range<1>(bst->compressed_track, 0, from_time, p_indices);
+							_get_compressed_key_indices_in_range<1>(bst->compressed_track, 0, to_time, p_indices);
+						} else {
+							_track_get_key_indices_in_range(bst->blend_shapes, 0, from_time, p_indices, true);
+							_track_get_key_indices_in_range(bst->blend_shapes, 0, to_time, p_indices, false);
+						}
+					} 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, true);
+						_track_get_key_indices_in_range(vt->values, 0, to_time, p_indices, false);
+					} 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, true);
+						_track_get_key_indices_in_range(mt->methods, 0, to_time, p_indices, false);
+					} 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, true);
+						_track_get_key_indices_in_range(bz->values, 0, to_time, p_indices, false);
+					} 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, true);
+						_track_get_key_indices_in_range(ad->values, 0, to_time, p_indices, false);
+					} 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, true);
+						_track_get_key_indices_in_range(an->values, 0, to_time, p_indices, false);
+					} break;
 				}
-				if (p_pingponged == 1) {
-					// handle loop by splitting
-					to_time = MIN(length - CMP_EPSILON, to_time);
-					switch (t->type) {
-						case TYPE_POSITION_3D: {
-							const PositionTrack *tt = static_cast<const PositionTrack *>(t);
-							if (tt->compressed_track >= 0) {
-								_get_compressed_key_indices_in_range<3>(tt->compressed_track, from_time, length, p_indices);
-								_get_compressed_key_indices_in_range<3>(tt->compressed_track, to_time, length - CMP_EPSILON, p_indices);
-							} else {
-								_track_get_key_indices_in_range(tt->positions, from_time, length, p_indices);
-								_track_get_key_indices_in_range(tt->positions, to_time, length - CMP_EPSILON, p_indices);
-							}
-						} break;
-						case TYPE_ROTATION_3D: {
-							const RotationTrack *rt = static_cast<const RotationTrack *>(t);
-							if (rt->compressed_track >= 0) {
-								_get_compressed_key_indices_in_range<3>(rt->compressed_track, from_time, length, p_indices);
-								_get_compressed_key_indices_in_range<3>(rt->compressed_track, to_time, length, p_indices);
-							} else {
-								_track_get_key_indices_in_range(rt->rotations, from_time, length, p_indices);
-								_track_get_key_indices_in_range(rt->rotations, to_time, length - CMP_EPSILON, p_indices);
-							}
-						} break;
-						case TYPE_SCALE_3D: {
-							const ScaleTrack *st = static_cast<const ScaleTrack *>(t);
-							if (st->compressed_track >= 0) {
-								_get_compressed_key_indices_in_range<3>(st->compressed_track, from_time, length, p_indices);
-								_get_compressed_key_indices_in_range<3>(st->compressed_track, to_time, length, p_indices);
-							} else {
-								_track_get_key_indices_in_range(st->scales, from_time, length, p_indices);
-								_track_get_key_indices_in_range(st->scales, to_time, length - CMP_EPSILON, p_indices);
-							}
-						} break;
-						case TYPE_BLEND_SHAPE: {
-							const BlendShapeTrack *bst = static_cast<const BlendShapeTrack *>(t);
-							if (bst->compressed_track >= 0) {
-								_get_compressed_key_indices_in_range<1>(bst->compressed_track, from_time, length, p_indices);
-								_get_compressed_key_indices_in_range<1>(bst->compressed_track, to_time, length - CMP_EPSILON, p_indices);
-							} else {
-								_track_get_key_indices_in_range(bst->blend_shapes, from_time, length, p_indices);
-								_track_get_key_indices_in_range(bst->blend_shapes, to_time, length - CMP_EPSILON, 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 - CMP_EPSILON, 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 - CMP_EPSILON, 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 - CMP_EPSILON, 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 - CMP_EPSILON, 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 - CMP_EPSILON, p_indices);
-						} break;
-					}
-					return;
+				return;
+			}
+			if (p_looped_flag == Animation::LOOPED_FLAG_END) {
+				// Handle loop by splitting.
+				switch (t->type) {
+					case TYPE_POSITION_3D: {
+						const PositionTrack *tt = static_cast<const PositionTrack *>(t);
+						if (tt->compressed_track >= 0) {
+							_get_compressed_key_indices_in_range<3>(tt->compressed_track, from_time, length, p_indices);
+							_get_compressed_key_indices_in_range<3>(tt->compressed_track, to_time, length, p_indices);
+						} else {
+							_track_get_key_indices_in_range(tt->positions, from_time, length, p_indices, false);
+							_track_get_key_indices_in_range(tt->positions, to_time, length, p_indices, true);
+						}
+					} break;
+					case TYPE_ROTATION_3D: {
+						const RotationTrack *rt = static_cast<const RotationTrack *>(t);
+						if (rt->compressed_track >= 0) {
+							_get_compressed_key_indices_in_range<3>(rt->compressed_track, from_time, length, p_indices);
+							_get_compressed_key_indices_in_range<3>(rt->compressed_track, to_time, length, p_indices);
+						} else {
+							_track_get_key_indices_in_range(rt->rotations, from_time, length, p_indices, false);
+							_track_get_key_indices_in_range(rt->rotations, to_time, length, p_indices, true);
+						}
+					} break;
+					case TYPE_SCALE_3D: {
+						const ScaleTrack *st = static_cast<const ScaleTrack *>(t);
+						if (st->compressed_track >= 0) {
+							_get_compressed_key_indices_in_range<3>(st->compressed_track, from_time, length, p_indices);
+							_get_compressed_key_indices_in_range<3>(st->compressed_track, to_time, length, p_indices);
+						} else {
+							_track_get_key_indices_in_range(st->scales, from_time, length, p_indices, false);
+							_track_get_key_indices_in_range(st->scales, to_time, length, p_indices, true);
+						}
+					} break;
+					case TYPE_BLEND_SHAPE: {
+						const BlendShapeTrack *bst = static_cast<const BlendShapeTrack *>(t);
+						if (bst->compressed_track >= 0) {
+							_get_compressed_key_indices_in_range<1>(bst->compressed_track, from_time, length, p_indices);
+							_get_compressed_key_indices_in_range<1>(bst->compressed_track, to_time, length - CMP_EPSILON, p_indices);
+						} else {
+							_track_get_key_indices_in_range(bst->blend_shapes, from_time, length, p_indices, false);
+							_track_get_key_indices_in_range(bst->blend_shapes, to_time, length, p_indices, true);
+						}
+					} 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, false);
+						_track_get_key_indices_in_range(vt->values, to_time, length, p_indices, true);
+					} 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, false);
+						_track_get_key_indices_in_range(mt->methods, to_time, length, p_indices, true);
+					} 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, false);
+						_track_get_key_indices_in_range(bz->values, to_time, length, p_indices, true);
+					} 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, false);
+						_track_get_key_indices_in_range(ad->values, to_time, length, p_indices, true);
+					} 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, false);
+						_track_get_key_indices_in_range(an->values, to_time, length, p_indices, true);
+					} break;
 				}
+				return;
+			}
+
+			// The edge will be pingponged in the next frame and processed there, so let's ignore it now...
+			if (!is_backward && Math::is_equal_approx(to_time, length)) {
+				to_time = length - CMP_EPSILON;
+			} else if (is_backward && Math::is_equal_approx(from_time, 0)) {
+				from_time = CMP_EPSILON;
 			}
 		} break;
 	}
-
 	switch (t->type) {
 		case TYPE_POSITION_3D: {
 			const PositionTrack *tt = static_cast<const PositionTrack *>(t);
 			if (tt->compressed_track >= 0) {
 				_get_compressed_key_indices_in_range<3>(tt->compressed_track, from_time, to_time - from_time, p_indices);
 			} else {
-				_track_get_key_indices_in_range(tt->positions, from_time, to_time, p_indices);
+				_track_get_key_indices_in_range(tt->positions, from_time, to_time, p_indices, is_backward);
 			}
 		} break;
 		case TYPE_ROTATION_3D: {
@@ -3042,7 +3129,7 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl
 			if (rt->compressed_track >= 0) {
 				_get_compressed_key_indices_in_range<3>(rt->compressed_track, from_time, to_time - from_time, p_indices);
 			} else {
-				_track_get_key_indices_in_range(rt->rotations, from_time, to_time, p_indices);
+				_track_get_key_indices_in_range(rt->rotations, from_time, to_time, p_indices, is_backward);
 			}
 		} break;
 		case TYPE_SCALE_3D: {
@@ -3050,7 +3137,7 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl
 			if (st->compressed_track >= 0) {
 				_get_compressed_key_indices_in_range<3>(st->compressed_track, from_time, to_time - from_time, p_indices);
 			} else {
-				_track_get_key_indices_in_range(st->scales, from_time, to_time, p_indices);
+				_track_get_key_indices_in_range(st->scales, from_time, to_time, p_indices, is_backward);
 			}
 		} break;
 		case TYPE_BLEND_SHAPE: {
@@ -3058,28 +3145,28 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl
 			if (bst->compressed_track >= 0) {
 				_get_compressed_key_indices_in_range<1>(bst->compressed_track, from_time, to_time - from_time, p_indices);
 			} else {
-				_track_get_key_indices_in_range(bst->blend_shapes, from_time, to_time, p_indices);
+				_track_get_key_indices_in_range(bst->blend_shapes, from_time, to_time, p_indices, is_backward);
 			}
 		} 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);
+			_track_get_key_indices_in_range(vt->values, from_time, to_time, p_indices, is_backward);
 		} 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);
+			_track_get_key_indices_in_range(mt->methods, from_time, to_time, p_indices, is_backward);
 		} 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);
+			_track_get_key_indices_in_range(bz->values, from_time, to_time, p_indices, is_backward);
 		} 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);
+			_track_get_key_indices_in_range(ad->values, from_time, to_time, p_indices, is_backward);
 		} 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);
+			_track_get_key_indices_in_range(an->values, from_time, to_time, p_indices, is_backward);
 		} break;
 	}
 }
@@ -3815,6 +3902,10 @@ void Animation::_bind_methods() {
 	BIND_ENUM_CONSTANT(LOOP_NONE);
 	BIND_ENUM_CONSTANT(LOOP_LINEAR);
 	BIND_ENUM_CONSTANT(LOOP_PINGPONG);
+
+	BIND_ENUM_CONSTANT(LOOPED_FLAG_NONE);
+	BIND_ENUM_CONSTANT(LOOPED_FLAG_END);
+	BIND_ENUM_CONSTANT(LOOPED_FLAG_START);
 }
 
 void Animation::clear() {
@@ -5798,7 +5889,8 @@ Variant Animation::interpolate_variant(const Variant &a, const Variant &b, float
 	}
 }
 
-Animation::Animation() {}
+Animation::Animation() {
+}
 
 Animation::~Animation() {
 	for (int i = 0; i < tracks.size(); i++) {

+ 9 - 3
scene/resources/animation.h

@@ -74,6 +74,12 @@ public:
 		LOOP_PINGPONG,
 	};
 
+	enum LoopedFlag {
+		LOOPED_FLAG_NONE,
+		LOOPED_FLAG_END,
+		LOOPED_FLAG_START,
+	};
+
 #ifdef TOOLS_ENABLED
 	enum HandleMode {
 		HANDLE_MODE_FREE,
@@ -250,12 +256,11 @@ private:
 	_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;
+	_FORCE_INLINE_ void _track_get_key_indices_in_range(const Vector<T> &p_array, double from_time, double to_time, List<int> *p_indices, bool p_is_backward) const;
 
 	double length = 1.0;
 	real_t step = 0.1;
 	LoopMode loop_mode = LOOP_NONE;
-	int pingponged = 0;
 
 	/* Animation compression page format (version 1):
 	 *
@@ -454,7 +459,7 @@ public:
 
 	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, int p_pingponged = 0) const;
+	void track_get_key_indices_in_range(int p_track, double p_time, double p_delta, List<int> *p_indices, Animation::LoopedFlag p_looped_flag = Animation::LOOPED_FLAG_NONE) const;
 
 	void set_length(real_t p_length);
 	real_t get_length() const;
@@ -484,6 +489,7 @@ VARIANT_ENUM_CAST(Animation::TrackType);
 VARIANT_ENUM_CAST(Animation::InterpolationType);
 VARIANT_ENUM_CAST(Animation::UpdateMode);
 VARIANT_ENUM_CAST(Animation::LoopMode);
+VARIANT_ENUM_CAST(Animation::LoopedFlag);
 #ifdef TOOLS_ENABLED
 VARIANT_ENUM_CAST(Animation::HandleMode);
 VARIANT_ENUM_CAST(Animation::HandleSetMode);