Add root_motion_local option to AnimationMixer

This commit is contained in:
Silc Lizard (Tokage) Renew 2024-11-18 22:16:59 +09:00
parent 5efd124ca1
commit 755bcf4737
4 changed files with 137 additions and 47 deletions

View File

@ -112,13 +112,13 @@
The most basic example is applying position to [CharacterBody3D]:
[codeblocks]
[gdscript]
var current_rotation: Quaternion
var current_rotation
func _process(delta):
if Input.is_action_just_pressed("animate"):
current_rotation = get_quaternion()
state_machine.travel("Animate")
var velocity: Vector3 = current_rotation * animation_tree.get_root_motion_position() / delta
var velocity = current_rotation * animation_tree.get_root_motion_position() / delta
set_velocity(velocity)
move_and_slide()
[/gdscript]
@ -130,7 +130,20 @@
if Input.is_action_just_pressed("animate"):
state_machine.travel("Animate")
set_quaternion(get_quaternion() * animation_tree.get_root_motion_rotation())
var velocity: Vector3 = (animation_tree.get_root_motion_rotation_accumulator().inverse() * get_quaternion()) * animation_tree.get_root_motion_position() / delta
var velocity = (animation_tree.get_root_motion_rotation_accumulator().inverse() * get_quaternion()) * animation_tree.get_root_motion_position() / delta
set_velocity(velocity)
move_and_slide()
[/gdscript]
[/codeblocks]
If [member root_motion_local] is [code]true[/code], return the pre-multiplied translation value with the inverted rotation.
In this case, the code can be written as follows:
[codeblocks]
[gdscript]
func _process(delta):
if Input.is_action_just_pressed("animate"):
state_machine.travel("Animate")
set_quaternion(get_quaternion() * animation_tree.get_root_motion_rotation())
var velocity = get_quaternion() * animation_tree.get_root_motion_position() / delta
set_velocity(velocity)
move_and_slide()
[/gdscript]
@ -145,13 +158,13 @@
For example, if an animation with only one key [code]Vector3(0, 0, 0)[/code] is played in the previous frame and then an animation with only one key [code]Vector3(1, 0, 1)[/code] is played in the next frame, the difference can be calculated as follows:
[codeblocks]
[gdscript]
var prev_root_motion_position_accumulator: Vector3
var prev_root_motion_position_accumulator
func _process(delta):
if Input.is_action_just_pressed("animate"):
state_machine.travel("Animate")
var current_root_motion_position_accumulator: Vector3 = animation_tree.get_root_motion_position_accumulator()
var difference: Vector3 = current_root_motion_position_accumulator - prev_root_motion_position_accumulator
var current_root_motion_position_accumulator = animation_tree.get_root_motion_position_accumulator()
var difference = current_root_motion_position_accumulator - prev_root_motion_position_accumulator
prev_root_motion_position_accumulator = current_root_motion_position_accumulator
transform.origin += difference
[/gdscript]
@ -185,13 +198,13 @@
For example, if an animation with only one key [code]Quaternion(0, 0, 0, 1)[/code] is played in the previous frame and then an animation with only one key [code]Quaternion(0, 0.707, 0, 0.707)[/code] is played in the next frame, the difference can be calculated as follows:
[codeblocks]
[gdscript]
var prev_root_motion_rotation_accumulator: Quaternion
var prev_root_motion_rotation_accumulator
func _process(delta):
if Input.is_action_just_pressed("animate"):
state_machine.travel("Animate")
var current_root_motion_rotation_accumulator: Quaternion = animation_tree.get_root_motion_rotation_accumulator()
var difference: Quaternion = prev_root_motion_rotation_accumulator.inverse() * current_root_motion_rotation_accumulator
var current_root_motion_rotation_accumulator = animation_tree.get_root_motion_rotation_accumulator()
var difference = prev_root_motion_rotation_accumulator.inverse() * current_root_motion_rotation_accumulator
prev_root_motion_rotation_accumulator = current_root_motion_rotation_accumulator
transform.basis *= Basis(difference)
[/gdscript]
@ -208,8 +221,8 @@
The most basic example is applying scale to [CharacterBody3D]:
[codeblocks]
[gdscript]
var current_scale: Vector3 = Vector3(1, 1, 1)
var scale_accum: Vector3 = Vector3(1, 1, 1)
var current_scale = Vector3(1, 1, 1)
var scale_accum = Vector3(1, 1, 1)
func _process(delta):
if Input.is_action_just_pressed("animate"):
@ -229,13 +242,13 @@
For example, if an animation with only one key [code]Vector3(1, 1, 1)[/code] is played in the previous frame and then an animation with only one key [code]Vector3(2, 2, 2)[/code] is played in the next frame, the difference can be calculated as follows:
[codeblocks]
[gdscript]
var prev_root_motion_scale_accumulator: Vector3
var prev_root_motion_scale_accumulator
func _process(delta):
if Input.is_action_just_pressed("animate"):
state_machine.travel("Animate")
var current_root_motion_scale_accumulator: Vector3 = animation_tree.get_root_motion_scale_accumulator()
var difference: Vector3 = current_root_motion_scale_accumulator - prev_root_motion_scale_accumulator
var current_root_motion_scale_accumulator = animation_tree.get_root_motion_scale_accumulator()
var difference = current_root_motion_scale_accumulator - prev_root_motion_scale_accumulator
prev_root_motion_scale_accumulator = current_root_motion_scale_accumulator
transform.basis = transform.basis.scaled(difference)
[/gdscript]
@ -304,6 +317,9 @@
This is used by the editor. If set to [code]true[/code], the scene will be saved with the effects of the reset animation (the animation with the key [code]"RESET"[/code]) applied as if it had been seeked to time 0, with the editor keeping the values that the scene had before saving.
This makes it more convenient to preview and edit animations in the editor, as changes to the scene will not be saved as long as they are set in the reset animation.
</member>
<member name="root_motion_local" type="bool" setter="set_root_motion_local" getter="is_root_motion_local">
If [code]true[/code], [method get_root_motion_position] value is extracted as a local translation value before blending. In other words, it is treated like the translation is done after the rotation.
</member>
<member name="root_motion_track" type="NodePath" setter="set_root_motion_track" getter="get_root_motion_track" default="NodePath(&quot;&quot;)">
The path to the Animation track used for root motion. Paths must be valid scene-tree paths to a node, and must be specified starting from the parent node of the node that will reproduce the animation. The [member root_motion_track] uses the same format as [method Animation.track_set_path], but note that a bone must be specified.
If the track has type [constant Animation.TYPE_POSITION_3D], [constant Animation.TYPE_ROTATION_3D], or [constant Animation.TYPE_SCALE_3D] the transformation will be canceled visually, and the animation will appear to stay in place. See also [method get_root_motion_position], [method get_root_motion_rotation], [method get_root_motion_scale], and [RootMotionView].

View File

@ -127,6 +127,9 @@ void AnimationMixer::_validate_property(PropertyInfo &p_property) const {
p_property.usage |= PROPERTY_USAGE_READ_ONLY;
}
#endif // TOOLS_ENABLED
if (root_motion_track.is_empty() && p_property.name == "root_motion_local") {
p_property.usage = PROPERTY_USAGE_NONE;
}
}
/* -------------------------------------------- */
@ -1200,6 +1203,10 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
}
TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track);
if (track->root_motion && calc_root) {
int rot_track = -1;
if (root_motion_local) {
rot_track = a->find_track(a->track_get_path(i), Animation::TYPE_ROTATION_3D);
}
double prev_time = time - delta;
if (!backward) {
if (Animation::is_less_approx(prev_time, start)) {
@ -1234,41 +1241,92 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
}
}
}
Vector3 loc[2];
if (!backward) {
if (Animation::is_greater_approx(prev_time, time)) {
Error err = a->try_position_track_interpolate(i, prev_time, &loc[0]);
if (err != OK) {
continue;
if (rot_track >= 0) {
Vector3 loc[2];
Quaternion rot;
if (!backward) {
if (Animation::is_greater_approx(prev_time, time)) {
Error err = a->try_position_track_interpolate(i, prev_time, &loc[0]);
if (err != OK) {
continue;
}
loc[0] = post_process_key_value(a, i, loc[0], t->object_id, t->bone_idx);
a->try_position_track_interpolate(i, end, &loc[1]);
loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx);
a->try_rotation_track_interpolate(rot_track, end, &rot);
rot = post_process_key_value(a, rot_track, rot, t->object_id, t->bone_idx);
root_motion_cache.loc += rot.xform_inv(loc[1] - loc[0]) * blend;
prev_time = start;
}
} else {
if (Animation::is_less_approx(prev_time, time)) {
Error err = a->try_position_track_interpolate(i, prev_time, &loc[0]);
if (err != OK) {
continue;
}
loc[0] = post_process_key_value(a, i, loc[0], t->object_id, t->bone_idx);
a->try_position_track_interpolate(i, start, &loc[1]);
loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx);
a->try_rotation_track_interpolate(rot_track, start, &rot);
rot = post_process_key_value(a, rot_track, rot, t->object_id, t->bone_idx);
root_motion_cache.loc += rot.xform_inv(loc[1] - loc[0]) * blend;
prev_time = end;
}
loc[0] = post_process_key_value(a, i, loc[0], t->object_id, t->bone_idx);
a->try_position_track_interpolate(i, end, &loc[1]);
loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx);
root_motion_cache.loc += (loc[1] - loc[0]) * blend;
prev_time = start;
}
Error err = a->try_position_track_interpolate(i, prev_time, &loc[0]);
if (err != OK) {
continue;
}
loc[0] = post_process_key_value(a, i, loc[0], t->object_id, t->bone_idx);
a->try_position_track_interpolate(i, time, &loc[1]);
loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx);
a->try_rotation_track_interpolate(rot_track, time, &rot);
rot = post_process_key_value(a, rot_track, rot, t->object_id, t->bone_idx);
root_motion_cache.loc += rot.xform_inv(loc[1] - loc[0]) * blend;
prev_time = !backward ? start : end;
} else {
if (Animation::is_less_approx(prev_time, time)) {
Error err = a->try_position_track_interpolate(i, prev_time, &loc[0]);
if (err != OK) {
continue;
Vector3 loc[2];
if (!backward) {
if (Animation::is_greater_approx(prev_time, time)) {
Error err = a->try_position_track_interpolate(i, prev_time, &loc[0]);
if (err != OK) {
continue;
}
loc[0] = post_process_key_value(a, i, loc[0], t->object_id, t->bone_idx);
a->try_position_track_interpolate(i, end, &loc[1]);
loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx);
root_motion_cache.loc += (loc[1] - loc[0]) * blend;
prev_time = start;
}
} else {
if (Animation::is_less_approx(prev_time, time)) {
Error err = a->try_position_track_interpolate(i, prev_time, &loc[0]);
if (err != OK) {
continue;
}
loc[0] = post_process_key_value(a, i, loc[0], t->object_id, t->bone_idx);
a->try_position_track_interpolate(i, start, &loc[1]);
loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx);
root_motion_cache.loc += (loc[1] - loc[0]) * blend;
prev_time = end;
}
loc[0] = post_process_key_value(a, i, loc[0], t->object_id, t->bone_idx);
a->try_position_track_interpolate(i, start, &loc[1]);
loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx);
root_motion_cache.loc += (loc[1] - loc[0]) * blend;
prev_time = end;
}
Error err = a->try_position_track_interpolate(i, prev_time, &loc[0]);
if (err != OK) {
continue;
}
loc[0] = post_process_key_value(a, i, loc[0], t->object_id, t->bone_idx);
a->try_position_track_interpolate(i, time, &loc[1]);
loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx);
root_motion_cache.loc += (loc[1] - loc[0]) * blend;
prev_time = !backward ? start : end;
}
Error err = a->try_position_track_interpolate(i, prev_time, &loc[0]);
if (err != OK) {
continue;
}
loc[0] = post_process_key_value(a, i, loc[0], t->object_id, t->bone_idx);
a->try_position_track_interpolate(i, time, &loc[1]);
loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx);
root_motion_cache.loc += (loc[1] - loc[0]) * blend;
prev_time = !backward ? start : end;
}
{
Vector3 loc;
@ -1343,6 +1401,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
}
rot[0] = post_process_key_value(a, i, rot[0], t->object_id, t->bone_idx);
a->try_rotation_track_interpolate(i, start, &rot[1]);
rot[1] = post_process_key_value(a, i, rot[1], t->object_id, t->bone_idx);
root_motion_cache.rot = (root_motion_cache.rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized();
prev_time = end;
}
@ -1418,8 +1477,8 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) {
}
scale[0] = post_process_key_value(a, i, scale[0], t->object_id, t->bone_idx);
a->try_scale_track_interpolate(i, end, &scale[1]);
root_motion_cache.scale += (scale[1] - scale[0]) * blend;
scale[1] = post_process_key_value(a, i, scale[1], t->object_id, t->bone_idx);
root_motion_cache.scale += (scale[1] - scale[0]) * blend;
prev_time = start;
}
} else {
@ -1990,12 +2049,21 @@ void AnimationMixer::clear_caches() {
void AnimationMixer::set_root_motion_track(const NodePath &p_track) {
root_motion_track = p_track;
notify_property_list_changed();
}
NodePath AnimationMixer::get_root_motion_track() const {
return root_motion_track;
}
void AnimationMixer::set_root_motion_local(bool p_enabled) {
root_motion_local = p_enabled;
}
bool AnimationMixer::is_root_motion_local() const {
return root_motion_local;
}
Vector3 AnimationMixer::get_root_motion_position() const {
return root_motion_position;
}
@ -2341,6 +2409,8 @@ void AnimationMixer::_bind_methods() {
/* ---- Root motion accumulator for Skeleton3D ---- */
ClassDB::bind_method(D_METHOD("set_root_motion_track", "path"), &AnimationMixer::set_root_motion_track);
ClassDB::bind_method(D_METHOD("get_root_motion_track"), &AnimationMixer::get_root_motion_track);
ClassDB::bind_method(D_METHOD("set_root_motion_local", "enabled"), &AnimationMixer::set_root_motion_local);
ClassDB::bind_method(D_METHOD("is_root_motion_local"), &AnimationMixer::is_root_motion_local);
ClassDB::bind_method(D_METHOD("get_root_motion_position"), &AnimationMixer::get_root_motion_position);
ClassDB::bind_method(D_METHOD("get_root_motion_rotation"), &AnimationMixer::get_root_motion_rotation);
@ -2368,6 +2438,7 @@ void AnimationMixer::_bind_methods() {
ADD_GROUP("Root Motion", "root_motion_");
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_motion_track"), "set_root_motion_track", "get_root_motion_track");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "root_motion_local"), "set_root_motion_local", "is_root_motion_local");
ADD_GROUP("Audio", "audio_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "audio_max_polyphony", PROPERTY_HINT_RANGE, "1,127,1"), "set_audio_max_polyphony", "get_audio_max_polyphony");

View File

@ -334,6 +334,7 @@ protected:
/* ---- Root motion accumulator for Skeleton3D ---- */
NodePath root_motion_track;
bool root_motion_local = false;
Vector3 root_motion_position = Vector3(0, 0, 0);
Quaternion root_motion_rotation = Quaternion(0, 0, 0, 1);
Vector3 root_motion_scale = Vector3(0, 0, 0);
@ -446,6 +447,9 @@ public:
void set_root_motion_track(const NodePath &p_track);
NodePath get_root_motion_track() const;
void set_root_motion_local(bool p_enabled);
bool is_root_motion_local() const;
Vector3 get_root_motion_position() const;
Quaternion get_root_motion_rotation() const;
Vector3 get_root_motion_scale() const;

View File

@ -94,7 +94,6 @@ void RootMotionView::_notification(int p_what) {
if (has_node(path)) {
Node *node = get_node(path);
AnimationMixer *mixer = Object::cast_to<AnimationMixer>(node);
if (mixer && mixer->is_active() && mixer->get_root_motion_track() != NodePath()) {
if (is_processing_internal() && mixer->get_callback_mode_process() == AnimationMixer::ANIMATION_CALLBACK_MODE_PROCESS_PHYSICS) {
@ -106,12 +105,12 @@ void RootMotionView::_notification(int p_what) {
set_process_internal(true);
set_physics_process_internal(false);
}
transform.origin = mixer->get_root_motion_position();
transform.basis = mixer->get_root_motion_rotation(); // Scale is meaningless.
diff = mixer->get_root_motion_rotation_accumulator();
diff = mixer->is_root_motion_local() ? Quaternion() : mixer->get_root_motion_rotation_accumulator();
}
}
if (!first && transform == Transform3D()) {
return;
}