Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AnimationMixer::capture() and AnimationPlayer::play_with_capture() as substitute of update mode capture #86715

Merged
merged 1 commit into from
Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/classes/Animation.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
12 changes: 12 additions & 0 deletions doc/classes/AnimationMixer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
22 changes: 21 additions & 1 deletion doc/classes/AnimationPlayer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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" />
Expand All @@ -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>
Expand Down
92 changes: 92 additions & 0 deletions scene/animation/animation_mixer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,7 @@ void AnimationMixer::_clear_caches() {
}
track_cache.clear();
cache_valid = false;
capture_cache.clear();

emit_signal(SNAME("caches_cleared"));
}
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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 ----------------------- */
/* -------------------------------------------- */
Expand Down Expand Up @@ -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");
Expand Down
28 changes: 27 additions & 1 deletion scene/animation/animation_mixer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -334,12 +335,34 @@ class AnimationMixer : public Node {

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);

Expand Down Expand Up @@ -400,9 +423,12 @@ class AnimationMixer : public Node {
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();
Expand Down
69 changes: 67 additions & 2 deletions scene/animation/animation_player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions scene/animation/animation_player.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ class AnimationPlayer : public AnimationMixer {

// 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;
Expand Down Expand Up @@ -157,6 +158,7 @@ class AnimationPlayer : public AnimationMixer {

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();
Expand Down
Loading