Compare commits

..

No commits in common. "ecf3b0fef2c37e2e696b50d9644c17e774b40e8c" and "603df6c37707f1bce77a44158042ebed9d346a11" have entirely different histories.

16 changed files with 182 additions and 402 deletions

View File

@ -1,9 +1,9 @@
# Blendalot - A Magical Animation System for Godot # Blendalot - A Magical Animation System for Godot
Blendalot is an experimental animation system for Godot that is currently in development. One of it's core features is a **Status**: This is a very much work in progress repository. Very rough drafts of the design and API can be found in the
very flexible animation syncing mechanism that allows smooth transitions between related motions (e.g. walking, running, doc folder.
limping , ...). This is done by using SyncTracks as described by Bobby Anguelov
here: https://www.youtube.com/watch?v=Jkv0pbp0ckQ&t=7998s. Blendalot is an experimental animation system for Godot that is currently in development.
Stay tuned for more... Stay tuned for more...

View File

@ -1,19 +0,0 @@
[gd_resource type="AnimationNodeBlendTree" load_steps=4 format=3 uid="uid://dqy0dgwsm8t46"]
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_h2yge"]
animation = &"Limping-InPlace"
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_1bvp3"]
animation = &"Walk-InPlace"
[sub_resource type="AnimationNodeBlend2" id="AnimationNodeBlend2_lquwl"]
[resource]
nodes/output/position = Vector2(540, 140)
nodes/Animation/node = SubResource("AnimationNodeAnimation_1bvp3")
nodes/Animation/position = Vector2(120, 80)
"nodes/Animation 2/node" = SubResource("AnimationNodeAnimation_h2yge")
"nodes/Animation 2/position" = Vector2(80, 320)
nodes/Blend2/node = SubResource("AnimationNodeBlend2_lquwl")
nodes/Blend2/position = Vector2(360, 180)
node_connections = [&"output", 0, &"Blend2", &"Blend2", 0, &"Animation", &"Blend2", 1, &"Animation 2"]

View File

@ -1,19 +0,0 @@
[gd_resource type="AnimationNodeBlendTree" load_steps=4 format=3 uid="uid://vsf71o82lkld"]
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_h2yge"]
animation = &"Run-InPlace"
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_1bvp3"]
animation = &"Walk-InPlace"
[sub_resource type="AnimationNodeBlend2" id="AnimationNodeBlend2_lquwl"]
[resource]
nodes/output/position = Vector2(540, 140)
nodes/Animation/node = SubResource("AnimationNodeAnimation_1bvp3")
nodes/Animation/position = Vector2(120, 80)
"nodes/Animation 2/node" = SubResource("AnimationNodeAnimation_h2yge")
"nodes/Animation 2/position" = Vector2(80, 320)
nodes/Blend2/node = SubResource("AnimationNodeBlend2_lquwl")
nodes/Blend2/position = Vector2(360, 180)
node_connections = [&"output", 0, &"Blend2", &"Blend2", 0, &"Animation", &"Blend2", 1, &"Animation 2"]

View File

@ -1,26 +1,19 @@
extends Node3D extends Node3D
@onready var mixamo_amy_walk_limp: Node3D = %MixamoAmyWalkLimp @onready var synced_animation_graph: SyncedAnimationGraph = %SyncedAnimationGraph
@onready var mixamo_amy_walk_limp_synced: Node3D = %MixamoAmyWalkLimpSynced @onready var animation_tree: AnimationTree = %AnimationTree
@onready var mixamo_amy_walk_run: Node3D = %MixamoAmyWalkRun
@onready var mixamo_amy_walk_run_synced: Node3D = %MixamoAmyWalkRunSynced
@onready var blend_weight_slider: HSlider = %BlendWeightSlider @onready var blend_weight_slider: HSlider = %BlendWeightSlider
@onready var blend_weight_label: Label = %BlendWeightLabel @onready var blend_weight_label: Label = %BlendWeightLabel
# Called when the node enters the scene tree for the first time. # Called when the node enters the scene tree for the first time.
func _ready() -> void: func _ready() -> void:
blend_weight_slider.value = 0.5 blend_weight_slider.value = 0.0
# Called every frame. 'delta' is the elapsed time since the previous frame. # Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void: func _process(delta: float) -> void:
pass pass
func _on_blend_weight_slider_value_changed(value: float) -> void: func _on_blend_weight_slider_value_changed(value: float) -> void:
mixamo_amy_walk_limp.get_node("AnimationTree").set("parameters/Blend2/blend_amount", value) animation_tree.set("parameters/Blend2/blend_amount", value)
mixamo_amy_walk_limp_synced.get_node("SyncedAnimationGraph").set("parameters/AnimationBlend2Node/blend_amount", value) synced_animation_graph.set("parameters/AnimationBlend2Node/blend_amount", value)
mixamo_amy_walk_run.get_node("AnimationTree").set("parameters/Blend2/blend_amount", value)
mixamo_amy_walk_run_synced.get_node("SyncedAnimationGraph").set("parameters/AnimationBlend2Node/blend_amount", value)
blend_weight_label.text = str(value) blend_weight_label.text = str(value)

View File

@ -3,14 +3,7 @@
[ext_resource type="PackedScene" uid="uid://d1xcqdqr1qeu6" path="res://assets/MixamoAmy.glb" id="1_0xm2m"] [ext_resource type="PackedScene" uid="uid://d1xcqdqr1qeu6" path="res://assets/MixamoAmy.glb" id="1_0xm2m"]
[ext_resource type="Script" uid="uid://bjvgqujpqumj7" path="res://main.gd" id="1_1bvp3"] [ext_resource type="Script" uid="uid://bjvgqujpqumj7" path="res://main.gd" id="1_1bvp3"]
[ext_resource type="AnimationLibrary" uid="uid://dwubn740aqx51" path="res://animation_library.res" id="3_1bvp3"] [ext_resource type="AnimationLibrary" uid="uid://dwubn740aqx51" path="res://animation_library.res" id="3_1bvp3"]
[ext_resource type="AnimationNodeBlendTree" uid="uid://dqy0dgwsm8t46" path="res://animation_tree_walk_limp.tres" id="3_272bh"] [ext_resource type="SyncedBlendTree" uid="uid://bijslmj4wd7ap" path="res://synced_blend_tree_node_limping.tres" id="4_1bvp3"]
[ext_resource type="SyncedBlendTree" uid="uid://2qfwr1xkiw0s" path="res://synced_blend_tree_walk_limp.tres" id="4_lquwl"]
[ext_resource type="SyncedBlendTree" uid="uid://qsk64ax2o47f" path="res://synced_blend_tree_walk_run.tres" id="5_7mycd"]
[ext_resource type="AnimationNodeBlendTree" uid="uid://vsf71o82lkld" path="res://animation_tree_walk_run.tres" id="6_5vw27"]
[sub_resource type="Theme" id="Theme_272bh"]
default_font_size = 30
Label/fonts/DefaultFont = null
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_h2yge"] [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_h2yge"]
albedo_color = Color(0.427493, 0.42749307, 0.42749307, 1) albedo_color = Color(0.427493, 0.42749307, 0.42749307, 1)
@ -32,6 +25,24 @@ sky = SubResource("Sky_1bvp3")
tonemap_mode = 2 tonemap_mode = 2
glow_enabled = true glow_enabled = true
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_h2yge"]
animation = &"Limping-InPlace"
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_1bvp3"]
animation = &"Walk-InPlace"
[sub_resource type="AnimationNodeBlend2" id="AnimationNodeBlend2_lquwl"]
[sub_resource type="AnimationNodeBlendTree" id="AnimationNodeBlendTree_7mycd"]
nodes/output/position = Vector2(540, 140)
nodes/Animation/node = SubResource("AnimationNodeAnimation_1bvp3")
nodes/Animation/position = Vector2(120, 80)
"nodes/Animation 2/node" = SubResource("AnimationNodeAnimation_h2yge")
"nodes/Animation 2/position" = Vector2(80, 320)
nodes/Blend2/node = SubResource("AnimationNodeBlend2_lquwl")
nodes/Blend2/position = Vector2(360, 180)
node_connections = [&"output", 0, &"Blend2", &"Blend2", 0, &"Animation", &"Blend2", 1, &"Animation 2"]
[node name="Main" type="Node3D"] [node name="Main" type="Node3D"]
script = ExtResource("1_1bvp3") script = ExtResource("1_1bvp3")
@ -50,7 +61,6 @@ grow_horizontal = 2
grow_vertical = 0 grow_vertical = 0
size_flags_horizontal = 4 size_flags_horizontal = 4
size_flags_vertical = 8 size_flags_vertical = 8
theme = SubResource("Theme_272bh")
[node name="HBoxContainer" type="HBoxContainer" parent="UI/MarginContainer"] [node name="HBoxContainer" type="HBoxContainer" parent="UI/MarginContainer"]
layout_mode = 2 layout_mode = 2
@ -70,36 +80,8 @@ step = 0.001
[node name="BlendWeightLabel" type="Label" parent="UI/MarginContainer/HBoxContainer"] [node name="BlendWeightLabel" type="Label" parent="UI/MarginContainer/HBoxContainer"]
unique_name_in_owner = true unique_name_in_owner = true
custom_minimum_size = Vector2(80, 0)
layout_mode = 2 layout_mode = 2
text = "0.0" text = "0.0"
horizontal_alignment = 2
[node name="MarginContainer2" type="MarginContainer" parent="UI"]
anchors_preset = 10
anchor_right = 1.0
offset_bottom = 23.0
grow_horizontal = 2
theme_override_constants/margin_top = 80
[node name="HBoxContainer2" type="HBoxContainer" parent="UI/MarginContainer2"]
layout_mode = 2
size_flags_vertical = 0
theme_override_constants/separation = 400
alignment = 1
[node name="Label" type="Label" parent="UI/MarginContainer2/HBoxContainer2"]
layout_mode = 2
theme_override_font_sizes/font_size = 32
text = "Unsynced"
[node name="EmptyLabel" type="Label" parent="UI/MarginContainer2/HBoxContainer2"]
layout_mode = 2
[node name="Label3" type="Label" parent="UI/MarginContainer2/HBoxContainer2"]
layout_mode = 2
theme_override_font_sizes/font_size = 32
text = "Synced"
[node name="Level" type="Node3D" parent="."] [node name="Level" type="Node3D" parent="."]
@ -115,62 +97,36 @@ shadow_enabled = true
environment = SubResource("Environment_lquwl") environment = SubResource("Environment_lquwl")
[node name="Camera3D" type="Camera3D" parent="Level"] [node name="Camera3D" type="Camera3D" parent="Level"]
transform = Transform3D(1, 0, 0, 0, 0.95413065, 0.29939055, 0, -0.29939055, 0.95413065, 0, 1.649, 3.197) transform = Transform3D(1, 0, 0, 0, 0.9897887, 0.14254257, 0, -0.14254257, 0.9897887, 0, 0.89188766, 1.4517534)
fov = 36.8
[node name="Characters" type="Node3D" parent="."] [node name="Characters" type="Node3D" parent="."]
[node name="MixamoAmyWalkLimp" parent="Characters" instance=ExtResource("1_0xm2m")] [node name="MixamoAmy" parent="Characters" instance=ExtResource("1_0xm2m")]
unique_name_in_owner = true transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.5, 0, 0)
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.4, 0, 0)
[node name="AnimationTree" type="AnimationTree" parent="Characters/MixamoAmyWalkLimp"] [node name="AnimationTree" type="AnimationTree" parent="Characters/MixamoAmy"]
tree_root = ExtResource("3_272bh") unique_name_in_owner = true
root_node = NodePath("%AnimationTree/..")
tree_root = SubResource("AnimationNodeBlendTree_7mycd")
anim_player = NodePath("../AnimationPlayer") anim_player = NodePath("../AnimationPlayer")
parameters/Blend2/blend_amount = 0.0 parameters/Blend2/blend_amount = 0.5
[node name="MixamoAmyWalkRun" parent="Characters" instance=ExtResource("1_0xm2m")] [node name="MixamoAmySynced" parent="Characters" instance=ExtResource("1_0xm2m")]
unique_name_in_owner = true transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.5, 0, 0)
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.6, 0, 0)
[node name="AnimationTree" type="AnimationTree" parent="Characters/MixamoAmyWalkRun"] [node name="AnimationPlayer2" type="AnimationPlayer" parent="Characters/MixamoAmySynced"]
tree_root = ExtResource("6_5vw27")
anim_player = NodePath("../AnimationPlayer")
parameters/Blend2/blend_amount = 0.0
[node name="MixamoAmyWalkLimpSynced" parent="Characters" instance=ExtResource("1_0xm2m")]
unique_name_in_owner = true
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.6, 0, 0)
[node name="AnimationPlayer2" type="AnimationPlayer" parent="Characters/MixamoAmyWalkLimpSynced"]
libraries = { libraries = {
&"animation_library": ExtResource("3_1bvp3") &"animation_library": ExtResource("3_1bvp3")
} }
[node name="SyncedAnimationGraph" type="SyncedAnimationGraph" parent="Characters/MixamoAmyWalkLimpSynced"] [node name="SyncedAnimationGraph" type="SyncedAnimationGraph" parent="Characters/MixamoAmySynced"]
animation_player = NodePath("../AnimationPlayer2")
tree_root = ExtResource("4_lquwl")
skeleton = NodePath("../Armature/Skeleton3D")
parameters/AnimationBlend2Node/blend_amount = 0.0
[node name="MixamoAmyWalkRunSynced" parent="Characters" instance=ExtResource("1_0xm2m")]
unique_name_in_owner = true unique_name_in_owner = true
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.4, 0, 0)
[node name="AnimationPlayer2" type="AnimationPlayer" parent="Characters/MixamoAmyWalkRunSynced"]
libraries = {
&"animation_library": ExtResource("3_1bvp3")
}
[node name="SyncedAnimationGraph" type="SyncedAnimationGraph" parent="Characters/MixamoAmyWalkRunSynced"]
animation_player = NodePath("../AnimationPlayer2") animation_player = NodePath("../AnimationPlayer2")
tree_root = ExtResource("5_7mycd") tree_root = ExtResource("4_1bvp3")
skeleton = NodePath("../Armature/Skeleton3D") skeleton = NodePath("../Armature/Skeleton3D")
parameters/AnimationBlend2Node/blend_amount = 0.0 parameters/AnimationBlend2Node/blend_amount = 0.5
[connection signal="value_changed" from="UI/MarginContainer/HBoxContainer/BlendWeightSlider" to="." method="_on_blend_weight_slider_value_changed"] [connection signal="value_changed" from="UI/MarginContainer/HBoxContainer/BlendWeightSlider" to="." method="_on_blend_weight_slider_value_changed"]
[editable path="Characters/MixamoAmyWalkLimp"] [editable path="Characters/MixamoAmy"]
[editable path="Characters/MixamoAmyWalkRun"] [editable path="Characters/MixamoAmySynced"]
[editable path="Characters/MixamoAmyWalkLimpSynced"]
[editable path="Characters/MixamoAmyWalkRunSynced"]

View File

@ -15,11 +15,6 @@ run/main_scene="uid://svj53e2xoio"
config/features=PackedStringArray("4.5", "Forward Plus") config/features=PackedStringArray("4.5", "Forward Plus")
config/icon="res://icon.svg" config/icon="res://icon.svg"
[display]
window/size/viewport_width=1900
window/size/viewport_height=1024
[dotnet] [dotnet]
project/assembly_name="Synced Blend Tree Test" project/assembly_name="Synced Blend Tree Test"

View File

@ -1,15 +1,12 @@
[gd_resource type="SyncedBlendTree" load_steps=4 format=3] [gd_resource type="SyncedBlendTree" load_steps=4 format=3 uid="uid://de41u8rkjnjyk"]
[sub_resource type="AnimationSamplerNode" id="AnimationSamplerNode_bvt3d"] [sub_resource type="AnimationSamplerNode" id="AnimationSamplerNode_bvt3d"]
animation = &"animation_library/TestAnimationB" animation = &"animation_library/Run-InPlace"
[sub_resource type="AnimationSamplerNode" id="AnimationSamplerNode_sntl5"] [sub_resource type="AnimationSamplerNode" id="AnimationSamplerNode_sntl5"]
animation = &"animation_library/TestAnimationA" animation = &"animation_library/Walk-InPlace"
[sub_resource type="AnimationBlend2Node" id="AnimationBlend2Node_n4m28"] [sub_resource type="AnimationBlend2Node" id="AnimationBlend2Node_n4m28"]
sync = false
blend_amount = 0.5
sync = false
[resource] [resource]
nodes/Blend2/node = SubResource("AnimationBlend2Node_n4m28") nodes/Blend2/node = SubResource("AnimationBlend2Node_n4m28")

View File

@ -1,18 +0,0 @@
[gd_resource type="SyncedBlendTree" load_steps=4 format=3 uid="uid://2qfwr1xkiw0s"]
[sub_resource type="AnimationBlend2Node" id="AnimationBlend2Node_bvt3d"]
[sub_resource type="AnimationSamplerNode" id="AnimationSamplerNode_sntl5"]
animation = &"animation_library/Limping-InPlace"
[sub_resource type="AnimationSamplerNode" id="AnimationSamplerNode_n4m28"]
animation = &"animation_library/Walk-InPlace"
[resource]
nodes/AnimationBlend2Node/node = SubResource("AnimationBlend2Node_bvt3d")
nodes/AnimationBlend2Node/position = Vector2(0, 0)
"nodes/AnimationSamplerNode 1/node" = SubResource("AnimationSamplerNode_sntl5")
"nodes/AnimationSamplerNode 1/position" = Vector2(0, 0)
nodes/AnimationSamplerNode/node = SubResource("AnimationSamplerNode_n4m28")
nodes/AnimationSamplerNode/position = Vector2(0, 0)
node_connections = [&"AnimationBlend2Node", 0, &"AnimationSamplerNode", &"AnimationBlend2Node", 1, &"AnimationSamplerNode 1", &"Output", 0, &"AnimationBlend2Node"]

View File

@ -1,18 +0,0 @@
[gd_resource type="SyncedBlendTree" load_steps=4 format=3 uid="uid://qsk64ax2o47f"]
[sub_resource type="AnimationBlend2Node" id="AnimationBlend2Node_bvt3d"]
[sub_resource type="AnimationSamplerNode" id="AnimationSamplerNode_sntl5"]
animation = &"animation_library/Run-InPlace"
[sub_resource type="AnimationSamplerNode" id="AnimationSamplerNode_n4m28"]
animation = &"animation_library/Walk-InPlace"
[resource]
nodes/AnimationBlend2Node/node = SubResource("AnimationBlend2Node_bvt3d")
nodes/AnimationBlend2Node/position = Vector2(0, 0)
"nodes/AnimationSamplerNode 1/node" = SubResource("AnimationSamplerNode_sntl5")
"nodes/AnimationSamplerNode 1/position" = Vector2(0, 0)
nodes/AnimationSamplerNode/node = SubResource("AnimationSamplerNode_n4m28")
nodes/AnimationSamplerNode/position = Vector2(0, 0)
node_connections = [&"AnimationBlend2Node", 0, &"AnimationSamplerNode", &"AnimationBlend2Node", 1, &"AnimationSamplerNode 1", &"Output", 0, &"AnimationBlend2Node"]

View File

@ -3,7 +3,7 @@
#include "core/object/class_db.h" #include "core/object/class_db.h"
#include "synced_animation_graph.h" #include "synced_animation_graph.h"
void initialize_blendalot_animgraph_module(ModuleInitializationLevel p_level) { void initialize_synced_blend_tree_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return; return;
} }
@ -14,7 +14,7 @@ void initialize_blendalot_animgraph_module(ModuleInitializationLevel p_level) {
ClassDB::register_class<AnimationBlend2Node>(); ClassDB::register_class<AnimationBlend2Node>();
} }
void uninitialize_blendalot_animgraph_module(ModuleInitializationLevel p_level) { void uninitialize_synced_blend_tree_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return; return;
} }

View File

@ -1,4 +1,4 @@
#include "modules/register_module_types.h" #include "modules/register_module_types.h"
void initialize_blendalot_animgraph_module(ModuleInitializationLevel p_level); void initialize_synced_blend_tree_module(ModuleInitializationLevel p_level);
void uninitialize_blendalot_animgraph_module(ModuleInitializationLevel p_level); void uninitialize_synced_blend_tree_module(ModuleInitializationLevel p_level);

View File

@ -1,7 +1,6 @@
#include "synced_animation_graph.h" #include "synced_animation_graph.h"
#include "core/os/time.h" #include "core/os/time.h"
#include "core/profiling/profiling.h"
#include "scene/3d/skeleton_3d.h" #include "scene/3d/skeleton_3d.h"
#include "scene/animation/animation_player.h" #include "scene/animation/animation_player.h"
@ -142,8 +141,6 @@ void SyncedAnimationGraph::_tree_changed() {
} }
void SyncedAnimationGraph::_notification(int p_what) { void SyncedAnimationGraph::_notification(int p_what) {
GodotProfileZone("SyncedAnimationGraph::_notification");
switch (p_what) { switch (p_what) {
case Node::NOTIFICATION_READY: { case Node::NOTIFICATION_READY: {
_setup_evaluation_context(); _setup_evaluation_context();
@ -295,41 +292,41 @@ void SyncedAnimationGraph::_process_graph(double p_delta, bool p_update_only) {
return; return;
} }
GodotProfileZone("SyncedAnimationGraph::_process_graph");
_update_properties(); _update_properties();
AnimationData *graph_output = graph_context.animation_data_allocator.allocate();
root_animation_node->activate_inputs(Vector<Ref<SyncedAnimationNode>>()); root_animation_node->activate_inputs(Vector<Ref<SyncedAnimationNode>>());
root_animation_node->calculate_sync_track(Vector<Ref<SyncedAnimationNode>>()); root_animation_node->calculate_sync_track(Vector<Ref<SyncedAnimationNode>>());
root_animation_node->update_time(p_delta); root_animation_node->update_time(p_delta);
root_animation_node->evaluate(graph_context, LocalVector<AnimationData *>(), *graph_output); root_animation_node->evaluate(graph_context, LocalVector<AnimationData *>(), graph_output);
_apply_animation_data(*graph_output); _apply_animation_data(graph_output);
graph_context.animation_data_allocator.free(graph_output);
} }
void SyncedAnimationGraph::_apply_animation_data(const AnimationData &output_data) const { void SyncedAnimationGraph::_apply_animation_data(const AnimationData &output_data) const {
GodotProfileZone("SyncedAnimationGraph::_apply_animation_data"); for (const KeyValue<Animation::TypeHash, AnimationData::TrackValue *> &K : output_data.track_values) {
const AnimationData::TrackValue *track_value = K.value;
for (const KeyValue<Animation::TypeHash, size_t> &K : output_data.value_buffer_offset) {
const AnimationData::TrackValue *track_value = output_data.get_value<AnimationData::TrackValue>(K.key);
switch (track_value->type) { switch (track_value->type) {
case AnimationData::TrackType::TYPE_POSITION_3D: case AnimationData::TrackType::TYPE_POSITION_3D:
case AnimationData::TrackType::TYPE_ROTATION_3D: { case AnimationData::TrackType::TYPE_ROTATION_3D: {
const AnimationData::TransformTrackValue *transform_track_value = static_cast<const AnimationData::TransformTrackValue *>(track_value); const AnimationData::TransformTrackValue *transform_track_value = static_cast<const AnimationData::TransformTrackValue *>(track_value);
if (transform_track_value->bone_idx != -1) { int bone_idx = -1;
if (transform_track_value->loc_used) { NodePath path = transform_track_value->track->path;
graph_context.skeleton_3d->set_bone_pose_position(transform_track_value->bone_idx, transform_track_value->loc);
}
if (transform_track_value->rot_used) { if (path.get_subname_count() == 1) {
graph_context.skeleton_3d->set_bone_pose_rotation(transform_track_value->bone_idx, transform_track_value->rot); bone_idx = graph_context.skeleton_3d->find_bone(path.get_subname(0));
if (bone_idx != -1) {
if (transform_track_value->loc_used) {
graph_context.skeleton_3d->set_bone_pose_position(transform_track_value->bone_idx, transform_track_value->loc);
}
if (transform_track_value->rot_used) {
graph_context.skeleton_3d->set_bone_pose_rotation(transform_track_value->bone_idx, transform_track_value->rot);
}
} else {
assert(false && "Not yet implemented!");
} }
} else {
assert(false && "Not yet implemented!");
} }
break; break;
@ -340,6 +337,8 @@ void SyncedAnimationGraph::_apply_animation_data(const AnimationData &output_dat
} }
} }
} }
graph_context.skeleton_3d->force_update_all_bone_transforms();
} }
void SyncedAnimationGraph::_set_process(bool p_process, bool p_force) { void SyncedAnimationGraph::_set_process(bool p_process, bool p_force) {

View File

@ -3,6 +3,8 @@
#include "scene/animation/animation_player.h" #include "scene/animation/animation_player.h"
#include "synced_animation_node.h" #include "synced_animation_node.h"
#include <cassert>
class Skeleton3D; class Skeleton3D;
class SyncedAnimationGraph : public Node { class SyncedAnimationGraph : public Node {
@ -14,6 +16,7 @@ private:
NodePath skeleton_path; NodePath skeleton_path;
GraphEvaluationContext graph_context = {}; GraphEvaluationContext graph_context = {};
AnimationData graph_output;
mutable List<PropertyInfo> properties; mutable List<PropertyInfo> properties;
mutable AHashMap<StringName, Pair<Ref<SyncedAnimationNode>, StringName>> parameter_to_node_parameter_map; mutable AHashMap<StringName, Pair<Ref<SyncedAnimationNode>, StringName>> parameter_to_node_parameter_map;

View File

@ -135,14 +135,14 @@ bool SyncedBlendTree::_set(const StringName &p_name, const Variant &p_value) {
} }
void AnimationData::sample_from_animation(const Ref<Animation> &animation, const Skeleton3D *skeleton_3d, double p_time) { void AnimationData::sample_from_animation(const Ref<Animation> &animation, const Skeleton3D *skeleton_3d, double p_time) {
GodotProfileZone("AnimationData::sample_from_animation"); const Vector<Animation::Track *> tracks = animation->get_tracks();
const LocalVector<Animation::Track *> tracks = animation->get_tracks();
Animation::Track *const *tracks_ptr = tracks.ptr(); Animation::Track *const *tracks_ptr = tracks.ptr();
int count = tracks.size(); int count = tracks.size();
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
TrackValue *track_value = nullptr;
const Animation::Track *animation_track = tracks_ptr[i]; const Animation::Track *animation_track = tracks_ptr[i];
const NodePath &track_node_path = animation_track->path;
if (!animation_track->enabled) { if (!animation_track->enabled) {
continue; continue;
} }
@ -151,29 +151,41 @@ void AnimationData::sample_from_animation(const Ref<Animation> &animation, const
switch (ttype) { switch (ttype) {
case Animation::TYPE_POSITION_3D: case Animation::TYPE_POSITION_3D:
case Animation::TYPE_ROTATION_3D: { case Animation::TYPE_ROTATION_3D: {
TransformTrackValue *transform_track_value = get_value<TransformTrackValue>(animation_track->thash); TransformTrackValue *transform_track_value = nullptr;
if (track_values.has(animation_track->thash)) {
transform_track_value = static_cast<TransformTrackValue *>(track_values[animation_track->thash]);
} else {
transform_track_value = memnew(AnimationData::TransformTrackValue);
}
int bone_idx = -1;
if (transform_track_value->bone_idx != -1) { if (track_node_path.get_subname_count() == 1) {
switch (ttype) { bone_idx = skeleton_3d->find_bone(track_node_path.get_subname(0));
case Animation::TYPE_POSITION_3D: { if (bone_idx != -1) {
animation->try_position_track_interpolate(i, p_time, &transform_track_value->loc); transform_track_value->bone_idx = bone_idx;
transform_track_value->loc_used = true; switch (ttype) {
break; case Animation::TYPE_POSITION_3D: {
} animation->try_position_track_interpolate(i, p_time, &transform_track_value->loc);
case Animation::TYPE_ROTATION_3D: { transform_track_value->loc_used = true;
animation->try_rotation_track_interpolate(i, p_time, &transform_track_value->rot); break;
transform_track_value->rot_used = true; }
break; case Animation::TYPE_ROTATION_3D: {
} animation->try_rotation_track_interpolate(i, p_time, &transform_track_value->rot);
default: { transform_track_value->rot_used = true;
assert(false && !"Not yet implemented"); break;
break; }
default: {
assert(false && !"Not yet implemented");
break;
}
} }
} }
} else { } else {
// TODO // TODO
assert(false && !"Not yet implemented"); assert(false && !"Not yet implemented");
} }
track_value = transform_track_value;
break; break;
} }
default: { default: {
@ -182,63 +194,12 @@ void AnimationData::sample_from_animation(const Ref<Animation> &animation, const
break; break;
} }
} }
track_value->track = tracks_ptr[i];
set_value(animation_track->thash, track_value);
} }
} }
void AnimationData::allocate_track_value(const Animation::Track *animation_track, const Skeleton3D *skeleton_3d) {
switch (animation_track->type) {
case Animation::TrackType::TYPE_ROTATION_3D:
case Animation::TrackType::TYPE_POSITION_3D: {
size_t value_offset = 0;
AnimationData::TransformTrackValue *transform_track_value = nullptr;
if (value_buffer_offset.has(animation_track->thash)) {
value_offset = value_buffer_offset[animation_track->thash];
transform_track_value = reinterpret_cast<AnimationData::TransformTrackValue *>(&buffer[value_offset]);
} else {
value_offset = buffer.size();
value_buffer_offset.insert(animation_track->thash, buffer.size());
buffer.resize(buffer.size() + sizeof(AnimationData::TransformTrackValue));
transform_track_value = new (reinterpret_cast<AnimationData::TransformTrackValue *>(&buffer[value_offset])) AnimationData::TransformTrackValue();
}
assert(transform_track_value != nullptr);
if (animation_track->path.get_subname_count() == 1) {
transform_track_value->bone_idx = skeleton_3d->find_bone(animation_track->path.get_subname(0));
}
if (animation_track->type == Animation::TrackType::TYPE_POSITION_3D) {
transform_track_value->loc_used = true;
} else if (animation_track->type == Animation::TrackType::TYPE_ROTATION_3D) {
transform_track_value->rot_used = true;
}
break;
}
default:
break;
}
}
void AnimationData::allocate_track_values(const Ref<Animation> &animation, const Skeleton3D *skeleton_3d) {
GodotProfileZone("AnimationData::allocate_track_values");
const LocalVector<Animation::Track *> tracks = animation->get_tracks();
Animation::Track *const *tracks_ptr = tracks.ptr();
int count = tracks.size();
for (int i = 0; i < count; i++) {
const Animation::Track *animation_track = tracks_ptr[i];
if (!animation_track->enabled) {
continue;
}
allocate_track_value(animation_track, skeleton_3d);
}
}
void AnimationDataAllocator::register_track_values(const Ref<Animation> &animation, const Skeleton3D *skeleton_3d) {
default_data.allocate_track_values(animation, skeleton_3d);
}
bool AnimationSamplerNode::initialize(GraphEvaluationContext &context) { bool AnimationSamplerNode::initialize(GraphEvaluationContext &context) {
SyncedAnimationNode::initialize(context); SyncedAnimationNode::initialize(context);
@ -248,8 +209,6 @@ bool AnimationSamplerNode::initialize(GraphEvaluationContext &context) {
return false; return false;
} }
context.animation_data_allocator.register_track_values(animation, context.skeleton_3d);
node_time_info.loop_mode = animation->get_loop_mode(); node_time_info.loop_mode = animation->get_loop_mode();
// Initialize Sync Track from marker // Initialize Sync Track from marker
@ -291,14 +250,13 @@ void AnimationSamplerNode::update_time(double p_time) {
} }
void AnimationSamplerNode::evaluate(GraphEvaluationContext &context, const LocalVector<AnimationData *> &inputs, AnimationData &output) { void AnimationSamplerNode::evaluate(GraphEvaluationContext &context, const LocalVector<AnimationData *> &inputs, AnimationData &output) {
GodotProfileZone("AnimationSamplerNode::evaluate");
assert(inputs.size() == 0); assert(inputs.size() == 0);
if (node_time_info.is_synced) { if (node_time_info.is_synced) {
node_time_info.position = node_time_info.sync_track.calc_ratio_from_sync_time(node_time_info.sync_position) * animation->get_length(); node_time_info.position = node_time_info.sync_track.calc_ratio_from_sync_time(node_time_info.sync_position) * animation->get_length();
} }
output.clear();
output.sample_from_animation(animation, context.skeleton_3d, node_time_info.position); output.sample_from_animation(animation, context.skeleton_3d, node_time_info.position);
} }
@ -318,9 +276,7 @@ void AnimationSamplerNode::_bind_methods() {
} }
void AnimationBlend2Node::evaluate(GraphEvaluationContext &context, const LocalVector<AnimationData *> &inputs, AnimationData &output) { void AnimationBlend2Node::evaluate(GraphEvaluationContext &context, const LocalVector<AnimationData *> &inputs, AnimationData &output) {
GodotProfileZone("AnimationBlend2Node::evaluate"); output = *inputs[0];
output = std::move(*inputs[0]);
output.blend(*inputs[1], blend_weight); output.blend(*inputs[1], blend_weight);
} }

View File

@ -1,10 +1,9 @@
#pragma once #pragma once
#include "core/io/resource.h"
#include "core/profiling/profiling.h"
#include "scene/3d/skeleton_3d.h"
#include "scene/animation/animation_player.h" #include "scene/animation/animation_player.h"
#include "core/io/resource.h"
#include "scene/3d/skeleton_3d.h"
#include "sync_track.h" #include "sync_track.h"
#include <cassert> #include <cassert>
@ -13,11 +12,7 @@
* @class AnimationData * @class AnimationData
* Represents data that is transported via animation connections in the SyncedAnimationGraph. * Represents data that is transported via animation connections in the SyncedAnimationGraph.
* *
* In general AnimationData objects should be obtained using the AnimationDataAllocator. * Essentially, it is a hash map for all Animation::Track values that can are sampled from an Animation.
*
* The class consists of a buffer containing the data and a hashmap that resolves the
* Animation::TypeHash of an Animation::Track to the corresponding AnimationData::TrackValue
* block within the buffer.
*/ */
struct AnimationData { struct AnimationData {
enum TrackType : uint8_t { enum TrackType : uint8_t {
@ -33,6 +28,7 @@ struct AnimationData {
}; };
struct TrackValue { struct TrackValue {
Animation::Track *track = nullptr;
TrackType type = TYPE_ANIMATION; TrackType type = TYPE_ANIMATION;
virtual ~TrackValue() = default; virtual ~TrackValue() = default;
@ -94,51 +90,70 @@ struct AnimationData {
const TransformTrackValue *other_value_casted = &static_cast<const TransformTrackValue &>(other_value); const TransformTrackValue *other_value_casted = &static_cast<const TransformTrackValue &>(other_value);
return bone_idx == other_value_casted->bone_idx && loc == other_value_casted->loc && rot == other_value_casted->rot && scale == other_value_casted->scale; return bone_idx == other_value_casted->bone_idx && loc == other_value_casted->loc && rot == other_value_casted->rot && scale == other_value_casted->scale;
} }
TrackValue *clone() const override {
TransformTrackValue *result = memnew(TransformTrackValue);
result->track = track;
result->bone_idx = bone_idx;
result->loc_used = loc_used;
result->rot_used = rot_used;
result->scale_used = scale_used;
result->init_loc = init_loc;
result->init_rot = init_rot;
result->init_scale = init_scale;
result->loc = loc;
result->rot = rot;
result->scale = scale;
return result;
}
}; };
AnimationData() = default; AnimationData() = default;
~AnimationData() = default; ~AnimationData() {
_clear_values();
}
AnimationData(const AnimationData &other) { AnimationData(const AnimationData &other) {
value_buffer_offset = other.value_buffer_offset; for (const KeyValue<Animation::TypeHash, TrackValue *> &K : other.track_values) {
buffer = other.buffer; track_values.insert(K.key, K.value->clone());
}
} }
AnimationData(AnimationData &&other) noexcept : AnimationData(AnimationData &&other) noexcept :
value_buffer_offset(std::exchange(other.value_buffer_offset, AHashMap<Animation::TypeHash, size_t, HashHasher>())), track_values(std::exchange(other.track_values, AHashMap<Animation::TypeHash, TrackValue *, HashHasher>())) {
buffer(std::exchange(other.buffer, LocalVector<uint8_t>())) {
} }
AnimationData &operator=(const AnimationData &other) { AnimationData &operator=(const AnimationData &other) {
AnimationData temp(other); AnimationData temp(other);
std::swap(value_buffer_offset, temp.value_buffer_offset); std::swap(track_values, temp.track_values);
std::swap(buffer, temp.buffer);
return *this; return *this;
} }
AnimationData &operator=(AnimationData &&other) noexcept { AnimationData &operator=(AnimationData &&other) noexcept {
std::swap(value_buffer_offset, other.value_buffer_offset); std::swap(track_values, other.track_values);
std::swap(buffer, other.buffer);
return *this; return *this;
} }
void allocate_track_value(const Animation::Track *animation_track, const Skeleton3D *skeleton_3d); void
void allocate_track_values(const Ref<Animation> &animation, const Skeleton3D *skeleton_3d); set_value(const Animation::TypeHash &thash, TrackValue *value) {
if (!track_values.has(thash)) {
template <typename TrackValueType> track_values.insert(thash, value);
TrackValueType *get_value(const Animation::TypeHash &thash) { } else {
return reinterpret_cast<TrackValueType *>(&buffer[value_buffer_offset[thash]]); track_values[thash] = value;
}
} }
template <typename TrackValueType> void clear() {
const TrackValueType *get_value(const Animation::TypeHash &thash) const { _clear_values();
return reinterpret_cast<const TrackValueType *>(&buffer[value_buffer_offset[thash]]);
} }
bool has_same_tracks(const AnimationData &other) const { bool has_same_tracks(const AnimationData &other) const {
HashSet<Animation::TypeHash> valid_track_hashes; HashSet<Animation::TypeHash> valid_track_hashes;
for (const KeyValue<Animation::TypeHash, size_t> &K : value_buffer_offset) { for (const KeyValue<Animation::TypeHash, TrackValue *> &K : track_values) {
valid_track_hashes.insert(K.key); valid_track_hashes.insert(K.key);
} }
for (const KeyValue<Animation::TypeHash, size_t> &K : other.value_buffer_offset) { for (const KeyValue<Animation::TypeHash, TrackValue *> &K : other.track_values) {
if (HashSet<Animation::TypeHash>::Iterator entry = valid_track_hashes.find(K.key)) { if (HashSet<Animation::TypeHash>::Iterator entry = valid_track_hashes.find(K.key)) {
valid_track_hashes.remove(entry); valid_track_hashes.remove(entry);
} else { } else {
@ -150,16 +165,14 @@ struct AnimationData {
} }
void blend(const AnimationData &to_data, const float lambda) { void blend(const AnimationData &to_data, const float lambda) {
GodotProfileZone("AnimationData::blend");
if (!has_same_tracks(to_data)) { if (!has_same_tracks(to_data)) {
print_error("Cannot blend AnimationData: tracks do not match."); print_error("Cannot blend AnimationData: tracks do not match.");
return; return;
} }
for (const KeyValue<Animation::TypeHash, size_t> &K : value_buffer_offset) { for (const KeyValue<Animation::TypeHash, TrackValue *> &K : track_values) {
TrackValue *track_value = get_value<TrackValue>(K.key); TrackValue *track_value = K.value;
const TrackValue *other_track_value = to_data.get_value<TrackValue>(K.key); TrackValue *other_track_value = to_data.track_values[K.key];
track_value->blend(*other_track_value, lambda); track_value->blend(*other_track_value, lambda);
} }
@ -167,63 +180,19 @@ struct AnimationData {
void sample_from_animation(const Ref<Animation> &animation, const Skeleton3D *skeleton_3d, double p_time); void sample_from_animation(const Ref<Animation> &animation, const Skeleton3D *skeleton_3d, double p_time);
AHashMap<Animation::TypeHash, size_t, HashHasher> value_buffer_offset; AHashMap<Animation::TypeHash, TrackValue *, HashHasher> track_values; // Animation::Track to TrackValue
LocalVector<uint8_t> buffer;
};
/** protected:
* @class AnimationDataAllocator void _clear_values() {
* for (KeyValue<Animation::TypeHash, TrackValue *> &K : track_values) {
* Allows reusing of already allocated AnimationData objects. Stores the default values for all memdelete(K.value);
* tracks. An allocated AnimationData object always has a resetted state where all TrackValues
* have the default value.
*
* During SyncedAnimationGraph initialization all nodes that generate values for AnimationData
* must register their tracks in the AnimationDataAllocator to ensure all allocated AnimationData
* have corresponding tracks.
*/
class AnimationDataAllocator {
AnimationData default_data;
List<AnimationData *> allocated_data;
public:
~AnimationDataAllocator() {
while (!allocated_data.is_empty()) {
memfree(allocated_data.front()->get());
allocated_data.pop_front();
} }
} }
/// @brief Registers all animation track values for the default_data value.
void register_track_values(const Ref<Animation> &animation, const Skeleton3D *skeleton_3d);
AnimationData *allocate() {
GodotProfileZone("AnimationDataAllocator::allocate_template");
if (!allocated_data.is_empty()) {
GodotProfileZone("AnimationDataAllocator::allocate_from_list");
AnimationData *result = allocated_data.front()->get();
allocated_data.pop_front();
// We copy the whole block as the assignment operator copies entries element wise.
memcpy(result->buffer.ptr(), default_data.buffer.ptr(), default_data.buffer.size());
return result;
}
AnimationData *result = memnew(AnimationData);
*result = default_data;
return result;
}
void free(AnimationData *data) {
allocated_data.push_front(data);
}
}; };
struct GraphEvaluationContext { struct GraphEvaluationContext {
AnimationPlayer *animation_player = nullptr; AnimationPlayer *animation_player = nullptr;
Skeleton3D *skeleton_3d = nullptr; Skeleton3D *skeleton_3d = nullptr;
AnimationDataAllocator animation_data_allocator;
}; };
/** /**
@ -271,14 +240,14 @@ public:
return true; return true;
} }
virtual void activate_inputs(const Vector<Ref<SyncedAnimationNode>> &input_nodes) { virtual void activate_inputs(Vector<Ref<SyncedAnimationNode>> input_nodes) {
// By default, all inputs nodes are activated. // By default, all inputs nodes are activated.
for (const Ref<SyncedAnimationNode> &node : input_nodes) { for (const Ref<SyncedAnimationNode> &node : input_nodes) {
node->active = true; node->active = true;
node->node_time_info.is_synced = node_time_info.is_synced; node->node_time_info.is_synced = node_time_info.is_synced;
} }
} }
virtual void calculate_sync_track(const Vector<Ref<SyncedAnimationNode>> &input_nodes) { virtual void calculate_sync_track(Vector<Ref<SyncedAnimationNode>> input_nodes) {
// By default, use the SyncTrack of the first input. // By default, use the SyncTrack of the first input.
if (input_nodes.size() > 0) { if (input_nodes.size() > 0) {
node_time_info.sync_track = input_nodes[0]->node_time_info.sync_track; node_time_info.sync_track = input_nodes[0]->node_time_info.sync_track;
@ -368,16 +337,16 @@ public:
return result; return result;
} }
void activate_inputs(const Vector<Ref<SyncedAnimationNode>> &input_nodes) override { void activate_inputs(Vector<Ref<SyncedAnimationNode>> input_nodes) override {
input_nodes[0]->active = true; for (const Ref<SyncedAnimationNode> &node : input_nodes) {
input_nodes[1]->active = true; node->active = true;
// If this Blend2 node is already synced then inputs are also synced. Otherwise, inputs are only set to synced if synced blending is active in this node. // If this Blend2 node is already synced then inputs are also synced. Otherwise, inputs are only set to synced if synced blending is active in this node.
input_nodes[0]->node_time_info.is_synced = node_time_info.is_synced || sync; node->node_time_info.is_synced = node_time_info.is_synced || sync;
input_nodes[1]->node_time_info.is_synced = node_time_info.is_synced || sync; }
} }
void calculate_sync_track(const Vector<Ref<SyncedAnimationNode>> &input_nodes) override { void calculate_sync_track(Vector<Ref<SyncedAnimationNode>> input_nodes) override {
if (node_time_info.is_synced || sync) { if (node_time_info.is_synced || sync) {
assert(input_nodes[0]->node_time_info.loop_mode == input_nodes[1]->node_time_info.loop_mode); assert(input_nodes[0]->node_time_info.loop_mode == input_nodes[1]->node_time_info.loop_mode);
node_time_info.sync_track = SyncTrack::blend(blend_weight, input_nodes[0]->node_time_info.sync_track, input_nodes[1]->node_time_info.sync_track); node_time_info.sync_track = SyncTrack::blend(blend_weight, input_nodes[0]->node_time_info.sync_track, input_nodes[1]->node_time_info.sync_track);
@ -533,7 +502,7 @@ struct BlendTreeGraph {
LocalVector<int> sorted_node_indices = get_sorted_node_indices(); LocalVector<int> sorted_node_indices = get_sorted_node_indices();
Vector<Ref<SyncedAnimationNode>> sorted_nodes; Vector<Ref<SyncedAnimationNode>> sorted_nodes;
LocalVector<NodeConnectionInfo> old_node_connection_info = node_connection_info; Vector<NodeConnectionInfo> old_node_connection_info = node_connection_info;
for (unsigned int i = 0; i < sorted_node_indices.size(); i++) { for (unsigned int i = 0; i < sorted_node_indices.size(); i++) {
int node_index = sorted_node_indices[i]; int node_index = sorted_node_indices[i];
sorted_nodes.push_back(nodes[node_index]); sorted_nodes.push_back(nodes[node_index]);
@ -744,9 +713,7 @@ public:
return true; return true;
} }
void activate_inputs(const Vector<Ref<SyncedAnimationNode>> &input_nodes) override { void activate_inputs(Vector<Ref<SyncedAnimationNode>> input_nodes) override {
GodotProfileZone("SyncedBlendTree::activate_inputs");
tree_graph.nodes[0]->active = true; tree_graph.nodes[0]->active = true;
for (int i = 0; i < tree_graph.nodes.size(); i++) { for (int i = 0; i < tree_graph.nodes.size(); i++) {
const Ref<SyncedAnimationNode> &node = tree_graph.nodes[i]; const Ref<SyncedAnimationNode> &node = tree_graph.nodes[i];
@ -760,8 +727,7 @@ public:
} }
} }
void calculate_sync_track(const Vector<Ref<SyncedAnimationNode>> &input_nodes) override { void calculate_sync_track(Vector<Ref<SyncedAnimationNode>> input_nodes) override {
GodotProfileZone("SyncedBlendTree::calculate_sync_track");
for (int i = tree_graph.nodes.size() - 1; i > 0; i--) { for (int i = tree_graph.nodes.size() - 1; i > 0; i--) {
const Ref<SyncedAnimationNode> &node = tree_graph.nodes[i]; const Ref<SyncedAnimationNode> &node = tree_graph.nodes[i];
@ -776,8 +742,6 @@ public:
} }
void update_time(double p_delta) override { void update_time(double p_delta) override {
GodotProfileZone("SyncedBlendTree::update_time");
tree_graph.nodes[0]->node_time_info.delta = p_delta; tree_graph.nodes[0]->node_time_info.delta = p_delta;
tree_graph.nodes[0]->node_time_info.position += p_delta; tree_graph.nodes[0]->node_time_info.position += p_delta;
@ -799,8 +763,6 @@ public:
} }
void evaluate(GraphEvaluationContext &context, const LocalVector<AnimationData *> &input_datas, AnimationData &output_data) override { void evaluate(GraphEvaluationContext &context, const LocalVector<AnimationData *> &input_datas, AnimationData &output_data) override {
ZoneScopedN("SyncedBlendTree::evaluate");
for (int i = tree_graph.nodes.size() - 1; i > 0; i--) { for (int i = tree_graph.nodes.size() - 1; i > 0; i--) {
const Ref<SyncedAnimationNode> &node = tree_graph.nodes[i]; const Ref<SyncedAnimationNode> &node = tree_graph.nodes[i];
@ -820,14 +782,14 @@ public:
if (i == 1) { if (i == 1) {
node_runtime_data.output_data = &output_data; node_runtime_data.output_data = &output_data;
} else { } else {
node_runtime_data.output_data = context.animation_data_allocator.allocate(); node_runtime_data.output_data = memnew(AnimationData);
} }
node->evaluate(context, node_runtime_data.input_data, *node_runtime_data.output_data); node->evaluate(context, node_runtime_data.input_data, *node_runtime_data.output_data);
// All inputs have been consumed and can now be freed. // All inputs have been consumed and can now be freed.
for (const int child_index : tree_graph.node_connection_info[i].connected_child_node_index_at_port) { for (const int child_index : tree_graph.node_connection_info[i].connected_child_node_index_at_port) {
context.animation_data_allocator.free(_node_runtime_data[child_index].output_data); memfree(_node_runtime_data[child_index].output_data);
} }
} }
} }

View File

@ -222,25 +222,20 @@ TEST_CASE("[SyncedAnimationGraph] Test BlendTree construction") {
TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph] Test AnimationData blending") { TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph] Test AnimationData blending") {
AnimationData data_t0; AnimationData data_t0;
data_t0.allocate_track_values(test_animation_a, skeleton_node);
data_t0.sample_from_animation(test_animation_a, skeleton_node, 0.0); data_t0.sample_from_animation(test_animation_a, skeleton_node, 0.0);
AnimationData data_t1; AnimationData data_t1;
data_t1.allocate_track_values(test_animation_a, skeleton_node);
data_t1.sample_from_animation(test_animation_a, skeleton_node, 1.0); data_t1.sample_from_animation(test_animation_a, skeleton_node, 1.0);
AnimationData data_t0_5; AnimationData data_t0_5;
data_t0_5.allocate_track_values(test_animation_a, skeleton_node);
data_t0_5.sample_from_animation(test_animation_a, skeleton_node, 0.5); data_t0_5.sample_from_animation(test_animation_a, skeleton_node, 0.5);
AnimationData data_blended = data_t0; AnimationData data_blended = data_t0;
data_blended.blend(data_t1, 0.5); data_blended.blend(data_t1, 0.5);
REQUIRE(data_blended.has_same_tracks(data_t0_5)); REQUIRE(data_blended.has_same_tracks(data_t0_5));
for (const KeyValue<Animation::TypeHash, size_t> &K : data_blended.value_buffer_offset) { for (const KeyValue<Animation::TypeHash, AnimationData::TrackValue *> &K : data_blended.track_values) {
AnimationData::TrackValue *blended_value = data_blended.get_value<AnimationData::TrackValue>(K.key); CHECK(K.value->operator==(*data_t0_5.track_values.find(K.key)->value));
AnimationData::TrackValue *data_t0_5_value = data_t0_5.get_value<AnimationData::TrackValue>(K.key);
CHECK(*blended_value == *data_t0_5_value);
} }
// And also check that values do not match // And also check that values do not match
@ -248,10 +243,8 @@ TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph
data_blended.blend(data_t1, 0.3); data_blended.blend(data_t1, 0.3);
REQUIRE(data_blended.has_same_tracks(data_t0_5)); REQUIRE(data_blended.has_same_tracks(data_t0_5));
for (const KeyValue<Animation::TypeHash, size_t> &K : data_blended.value_buffer_offset) { for (const KeyValue<Animation::TypeHash, AnimationData::TrackValue *> &K : data_blended.track_values) {
AnimationData::TrackValue *blended_value = data_blended.get_value<AnimationData::TrackValue>(K.key); CHECK(K.value->operator!=(*data_t0_5.track_values.find(K.key)->value));
AnimationData::TrackValue *data_t0_5_value = data_t0_5.get_value<AnimationData::TrackValue>(K.key);
CHECK(*blended_value != *data_t0_5_value);
} }
} }