Merge pull request #86715 from TokageItLab/revive-capture

Add `AnimationMixer::capture()` and `AnimationPlayer::play_with_capture()` as substitute of update mode capture
This commit is contained in:
Rémi Verschelde 2024-02-12 13:33:39 +01:00
commit 9b189d24fe
No known key found for this signature in database
GPG Key ID: C3336907360768E1
7 changed files with 222 additions and 5 deletions

View File

@ -651,7 +651,7 @@
Update at the keyframes.
</constant>
<constant name="UPDATE_CAPTURE" value="2" 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.
Same as [constant UPDATE_CONTINUOUS] but works as a flag to capture the value of the current object and perform interpolation in some methods. See also [method AnimationMixer.capture] and [method AnimationPlayer.play_with_capture].
</constant>
<constant name="LOOP_NONE" value="0" enum="LoopMode">
At both ends of the animation, the animation will stop playing.

View File

@ -36,6 +36,18 @@
Manually advance the animations by the specified time (in seconds).
</description>
</method>
<method name="capture">
<return type="void" />
<param index="0" name="name" type="StringName" />
<param index="1" name="duration" type="float" />
<param index="2" name="trans_type" type="int" enum="Tween.TransitionType" default="0" />
<param index="3" name="ease_type" type="int" enum="Tween.EaseType" default="0" />
<description>
If the animation track specified by [param name] has an option [constant Animation.UPDATE_CAPTURE], stores current values of the objects indicated by the track path as a cache. If there is already a captured cache, the old cache is discarded.
After this it will interpolate with current animation blending result during the playback process for the time specified by [param duration], working like a crossfade.
You can specify [param trans_type] as the curve for the interpolation. For better results, it may be appropriate to specify [constant Tween.TRANS_LINEAR] for cases where the first key of the track begins with a non-zero value or where the key value does not change, and [constant Tween.TRANS_QUAD] for cases where the key value changes linearly.
</description>
</method>
<method name="clear_caches">
<return type="void" />
<description>

View File

@ -110,6 +110,26 @@
This method is a shorthand for [method play] with [code]custom_speed = -1.0[/code] and [code]from_end = true[/code], so see its description for more information.
</description>
</method>
<method name="play_with_capture">
<return type="void" />
<param index="0" name="name" type="StringName" />
<param index="1" name="duration" type="float" default="-1.0" />
<param index="2" name="custom_blend" type="float" default="-1" />
<param index="3" name="custom_speed" type="float" default="1.0" />
<param index="4" name="from_end" type="bool" default="false" />
<param index="5" name="trans_type" type="int" enum="Tween.TransitionType" default="0" />
<param index="6" name="ease_type" type="int" enum="Tween.EaseType" default="0" />
<description>
See [method AnimationMixer.capture]. It is almost the same as the following:
[codeblock]
capture(name, duration, trans_type, ease_type)
play(name, custom_blend, custom_speed, from_end)
[/codeblock]
If name is blank, it specifies [member assigned_animation].
If [param duration] is a negative value, the duration is set to the interval between the current position and the first key, when [param from_end] is [code]true[/code], uses the interval between the current position and the last key instead.
[b]Note:[/b] The [param duration] takes [member speed_scale] into account, but [param custom_speed] does not, because the capture cache is interpolated with the blend result and the result may contain multiple animations.
</description>
</method>
<method name="queue">
<return type="void" />
<param index="0" name="name" type="StringName" />
@ -125,7 +145,7 @@
<param index="2" name="update_only" type="bool" default="false" />
<description>
Seeks the animation to the [param seconds] point in time (in seconds). If [param update] is [code]true[/code], the animation updates too, otherwise it updates at process time. Events between the current frame and [param seconds] are skipped.
If [param update_only] is true, the method / audio / animation playback tracks will not be processed.
If [param update_only] is [code]true[/code], the method / audio / animation playback tracks will not be processed.
[b]Note:[/b] Seeking to the end of the animation doesn't emit [signal AnimationMixer.animation_finished]. If you want to skip animation and emit the signal, use [method AnimationMixer.advance].
</description>
</method>

View File

@ -552,6 +552,7 @@ void AnimationMixer::_clear_caches() {
}
track_cache.clear();
cache_valid = false;
capture_cache.clear();
emit_signal(SNAME("caches_cleared"));
}
@ -915,6 +916,7 @@ bool AnimationMixer::_update_caches() {
void AnimationMixer::_process_animation(double p_delta, bool p_update_only) {
_blend_init();
if (_blend_pre_process(p_delta, track_count, track_map)) {
_blend_capture(p_delta);
_blend_calc_total_weight();
_blend_process(p_delta, p_update_only);
_blend_apply();
@ -1013,6 +1015,43 @@ void AnimationMixer::_blend_post_process() {
//
}
void AnimationMixer::_blend_capture(double p_delta) {
blend_capture(p_delta);
}
void AnimationMixer::blend_capture(double p_delta) {
if (capture_cache.animation.is_null()) {
return;
}
capture_cache.remain -= p_delta * capture_cache.step;
if (capture_cache.remain <= 0.0) {
capture_cache.clear();
return;
}
real_t weight = Tween::run_equation(capture_cache.trans_type, capture_cache.ease_type, capture_cache.remain, 0.0, 1.0, 1.0);
// Blend with other animations.
real_t inv = 1.0 - weight;
for (AnimationInstance &ai : animation_instances) {
ai.playback_info.weight *= inv;
}
// Build capture animation instance.
AnimationData ad;
ad.animation = capture_cache.animation;
PlaybackInfo pi;
pi.weight = weight;
AnimationInstance ai;
ai.animation_data = ad;
ai.playback_info = pi;
animation_instances.push_back(ai);
}
void AnimationMixer::_blend_calc_total_weight() {
for (const AnimationInstance &ai : animation_instances) {
Ref<Animation> a = ai.animation_data.animation;
@ -1848,6 +1887,10 @@ Vector3 AnimationMixer::get_root_motion_scale_accumulator() const {
return root_motion_scale_accumulator;
}
/* -------------------------------------------- */
/* -- Reset on save --------------------------- */
/* -------------------------------------------- */
void AnimationMixer::set_reset_on_save_enabled(bool p_enabled) {
reset_on_save = p_enabled;
}
@ -2011,6 +2054,50 @@ Ref<AnimatedValuesBackup> AnimationMixer::apply_reset(bool p_user_initiated) {
}
#endif // TOOLS_ENABLED
/* -------------------------------------------- */
/* -- Capture feature ------------------------- */
/* -------------------------------------------- */
void AnimationMixer::capture(const StringName &p_name, double p_duration, Tween::TransitionType p_trans_type, Tween::EaseType p_ease_type) {
ERR_FAIL_COND(!active);
ERR_FAIL_COND(!has_animation(p_name));
ERR_FAIL_COND(Math::is_zero_approx(p_duration));
Ref<Animation> reference_animation = get_animation(p_name);
if (!cache_valid) {
_update_caches(); // Need to retrieve object id.
}
capture_cache.remain = 1.0;
capture_cache.step = 1.0 / p_duration;
capture_cache.trans_type = p_trans_type;
capture_cache.ease_type = p_ease_type;
capture_cache.animation.instantiate();
bool is_valid = false;
for (int i = 0; i < reference_animation->get_track_count(); i++) {
if (!reference_animation->track_is_enabled(i)) {
continue;
}
if (reference_animation->track_get_type(i) == Animation::TYPE_VALUE && reference_animation->value_track_get_update_mode(i) == Animation::UPDATE_CAPTURE) {
TrackCacheValue *t = static_cast<TrackCacheValue *>(track_cache[reference_animation->track_get_type_hash(i)]);
Object *t_obj = ObjectDB::get_instance(t->object_id);
if (t_obj) {
Variant value = t_obj->get_indexed(t->subpath);
int inserted_idx = capture_cache.animation->add_track(Animation::TYPE_VALUE);
capture_cache.animation->track_set_path(inserted_idx, reference_animation->track_get_path(i));
capture_cache.animation->track_insert_key(inserted_idx, 0, value);
capture_cache.animation->value_track_set_update_mode(inserted_idx, Animation::UPDATE_CONTINUOUS);
capture_cache.animation->track_set_interpolation_type(inserted_idx, Animation::INTERPOLATION_LINEAR);
is_valid = true;
}
}
}
if (!is_valid) {
capture_cache.clear();
}
}
/* -------------------------------------------- */
/* -- General functions ----------------------- */
/* -------------------------------------------- */
@ -2118,9 +2205,14 @@ void AnimationMixer::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "active"), "set_active", "is_active");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deterministic"), "set_deterministic", "is_deterministic");
/* ---- Reset on save ---- */
ClassDB::bind_method(D_METHOD("set_reset_on_save_enabled", "enabled"), &AnimationMixer::set_reset_on_save_enabled);
ClassDB::bind_method(D_METHOD("is_reset_on_save_enabled"), &AnimationMixer::is_reset_on_save_enabled);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "reset_on_save", PROPERTY_HINT_NONE, ""), "set_reset_on_save_enabled", "is_reset_on_save_enabled");
/* ---- Capture feature ---- */
ClassDB::bind_method(D_METHOD("capture", "name", "duration", "trans_type", "ease_type"), &AnimationMixer::capture, DEFVAL(Tween::TRANS_LINEAR), DEFVAL(Tween::EASE_IN));
ADD_SIGNAL(MethodInfo("mixer_updated")); // For updating dummy player.
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_node"), "set_root_node", "get_root_node");

View File

@ -31,6 +31,7 @@
#ifndef ANIMATION_MIXER_H
#define ANIMATION_MIXER_H
#include "scene/animation/tween.h"
#include "scene/main/node.h"
#include "scene/resources/animation.h"
#include "scene/resources/animation_library.h"
@ -334,12 +335,34 @@ protected:
void _blend_init();
virtual bool _blend_pre_process(double p_delta, int p_track_count, const HashMap<NodePath, int> &p_track_map);
virtual void _blend_capture(double p_delta);
void _blend_calc_total_weight(); // For undeterministic blending.
void _blend_process(double p_delta, bool p_update_only = false);
void _blend_apply();
virtual void _blend_post_process();
void _call_object(ObjectID p_object_id, const StringName &p_method, const Vector<Variant> &p_params, bool p_deferred);
/* ---- Capture feature ---- */
struct CaptureCache {
Ref<Animation> animation;
double remain = 0.0;
double step = 0.0;
Tween::TransitionType trans_type = Tween::TRANS_LINEAR;
Tween::EaseType ease_type = Tween::EASE_IN;
void clear() {
animation.unref();
remain = 0.0;
step = 0.0;
}
CaptureCache() {}
~CaptureCache() {
clear();
}
} capture_cache;
void blend_capture(double p_delta); // To blend capture track with all other animations.
#ifndef DISABLE_DEPRECATED
virtual Variant _post_process_key_value_bind_compat_86687(const Ref<Animation> &p_anim, int p_track, Variant p_value, Object *p_object, int p_object_idx = -1);
@ -400,9 +423,12 @@ public:
virtual void advance(double p_time);
virtual void clear_caches(); ///< must be called by hand if an animation was modified after added
/* ---- Capture feature ---- */
void capture(const StringName &p_name, double p_duration, Tween::TransitionType p_trans_type = Tween::TRANS_LINEAR, Tween::EaseType p_ease_type = Tween::EASE_IN);
/* ---- Reset on save ---- */
void set_reset_on_save_enabled(bool p_enabled);
bool is_reset_on_save_enabled() const;
bool can_apply_reset() const;
void _build_backup_track_cache();
Ref<AnimatedValuesBackup> make_backup();

View File

@ -310,6 +310,10 @@ bool AnimationPlayer::_blend_pre_process(double p_delta, int p_track_count, cons
return true;
}
void AnimationPlayer::_blend_capture(double p_delta) {
blend_capture(p_delta * Math::abs(speed_scale));
}
void AnimationPlayer::_blend_post_process() {
if (end_reached) {
// If the method track changes current animation, the animation is not finished.
@ -366,13 +370,73 @@ void AnimationPlayer::play_backwards(const StringName &p_name, double p_custom_b
play(p_name, p_custom_blend, -1, true);
}
void AnimationPlayer::play_with_capture(const StringName &p_name, double p_duration, double p_custom_blend, float p_custom_scale, bool p_from_end, Tween::TransitionType p_trans_type, Tween::EaseType p_ease_type) {
StringName name = p_name;
if (name == StringName()) {
name = playback.assigned;
}
if (signbit(p_duration)) {
double max_dur = 0;
Ref<Animation> anim = get_animation(name);
if (anim.is_valid()) {
double current_pos = playback.current.pos;
if (playback.assigned != name) {
current_pos = p_from_end ? anim->get_length() : 0;
}
for (int i = 0; i < anim->get_track_count(); i++) {
if (anim->track_get_type(i) != Animation::TYPE_VALUE) {
continue;
}
if (anim->value_track_get_update_mode(i) != Animation::UPDATE_CAPTURE) {
continue;
}
if (anim->track_get_key_count(i) == 0) {
continue;
}
max_dur = MAX(max_dur, p_from_end ? current_pos - anim->track_get_key_time(i, anim->track_get_key_count(i) - 1) : anim->track_get_key_time(i, 0) - current_pos);
}
}
p_duration = max_dur;
}
capture(name, p_duration, p_trans_type, p_ease_type);
play(name, p_custom_blend, p_custom_scale, p_from_end);
}
void AnimationPlayer::play(const StringName &p_name, double p_custom_blend, float p_custom_scale, bool p_from_end) {
StringName name = p_name;
if (String(name) == "") {
if (name == StringName()) {
name = playback.assigned;
}
#ifdef TOOLS_ENABLED
if (!Engine::get_singleton()->is_editor_hint()) {
bool warn_enabled = false;
if (capture_cache.animation.is_null()) {
Ref<Animation> anim = get_animation(name);
if (anim.is_valid()) {
for (int i = 0; i < anim->get_track_count(); i++) {
if (anim->track_get_type(i) != Animation::TYPE_VALUE) {
continue;
}
if (anim->value_track_get_update_mode(i) != Animation::UPDATE_CAPTURE) {
continue;
}
if (anim->track_get_key_count(i) == 0) {
continue;
}
warn_enabled = true;
}
}
}
if (warn_enabled) {
WARN_PRINT_ONCE_ED("Capture track found. If you want to interpolate animation with captured frame, you can use play_with_capture() instead of play().");
}
}
#endif
ERR_FAIL_COND_MSG(!animation_set.has(name), vformat("Animation not found: %s.", name));
Playback &c = playback;
@ -417,7 +481,7 @@ void AnimationPlayer::play(const StringName &p_name, double p_custom_blend, floa
}
if (get_current_animation() != p_name) {
_clear_caches();
_clear_playing_caches();
}
c.current.from = &animation_set[name];
@ -751,6 +815,7 @@ void AnimationPlayer::_bind_methods() {
ClassDB::bind_method(D_METHOD("play", "name", "custom_blend", "custom_speed", "from_end"), &AnimationPlayer::play, DEFVAL(""), DEFVAL(-1), DEFVAL(1.0), DEFVAL(false));
ClassDB::bind_method(D_METHOD("play_backwards", "name", "custom_blend"), &AnimationPlayer::play_backwards, DEFVAL(""), DEFVAL(-1));
ClassDB::bind_method(D_METHOD("play_with_capture", "name", "duration", "custom_blend", "custom_speed", "from_end", "trans_type", "ease_type"), &AnimationPlayer::play_with_capture, DEFVAL(-1.0), DEFVAL(-1), DEFVAL(1.0), DEFVAL(false), DEFVAL(Tween::TRANS_LINEAR), DEFVAL(Tween::EASE_IN));
ClassDB::bind_method(D_METHOD("pause"), &AnimationPlayer::pause);
ClassDB::bind_method(D_METHOD("stop", "keep_state"), &AnimationPlayer::stop, DEFVAL(false));
ClassDB::bind_method(D_METHOD("is_playing"), &AnimationPlayer::is_playing);

View File

@ -128,6 +128,7 @@ protected:
// Make animation instances.
virtual bool _blend_pre_process(double p_delta, int p_track_count, const HashMap<NodePath, int> &p_track_map) override;
virtual void _blend_capture(double p_delta) override;
virtual void _blend_post_process() override;
virtual void _animation_removed(const StringName &p_name, const StringName &p_library) override;
@ -157,6 +158,7 @@ public:
void play(const StringName &p_name = StringName(), double p_custom_blend = -1, float p_custom_scale = 1.0, bool p_from_end = false);
void play_backwards(const StringName &p_name = StringName(), double p_custom_blend = -1);
void play_with_capture(const StringName &p_name, double p_duration = -1.0, double p_custom_blend = -1, float p_custom_scale = 1.0, bool p_from_end = false, Tween::TransitionType p_trans_type = Tween::TRANS_LINEAR, Tween::EaseType p_ease_type = Tween::EASE_IN);
void queue(const StringName &p_name);
Vector<String> get_queue();
void clear_queue();