Compare commits
No commits in common. "ecf3b0fef2c37e2e696b50d9644c17e774b40e8c" and "603df6c37707f1bce77a44158042ebed9d346a11" have entirely different histories.
ecf3b0fef2
...
603df6c377
@ -1,9 +1,9 @@
|
||||
# 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
|
||||
very flexible animation syncing mechanism that allows smooth transitions between related motions (e.g. walking, running,
|
||||
limping , ...). This is done by using SyncTracks as described by Bobby Anguelov
|
||||
here: https://www.youtube.com/watch?v=Jkv0pbp0ckQ&t=7998s.
|
||||
**Status**: This is a very much work in progress repository. Very rough drafts of the design and API can be found in the
|
||||
doc folder.
|
||||
|
||||
Blendalot is an experimental animation system for Godot that is currently in development.
|
||||
|
||||
Stay tuned for more...
|
||||
|
||||
|
||||
@ -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"]
|
||||
@ -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"]
|
||||
17
demo/main.gd
17
demo/main.gd
@ -1,26 +1,19 @@
|
||||
extends Node3D
|
||||
|
||||
@onready var mixamo_amy_walk_limp: Node3D = %MixamoAmyWalkLimp
|
||||
@onready var mixamo_amy_walk_limp_synced: Node3D = %MixamoAmyWalkLimpSynced
|
||||
@onready var mixamo_amy_walk_run: Node3D = %MixamoAmyWalkRun
|
||||
@onready var mixamo_amy_walk_run_synced: Node3D = %MixamoAmyWalkRunSynced
|
||||
|
||||
@onready var synced_animation_graph: SyncedAnimationGraph = %SyncedAnimationGraph
|
||||
@onready var animation_tree: AnimationTree = %AnimationTree
|
||||
@onready var blend_weight_slider: HSlider = %BlendWeightSlider
|
||||
@onready var blend_weight_label: Label = %BlendWeightLabel
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
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.
|
||||
func _process(delta: float) -> void:
|
||||
pass
|
||||
|
||||
func _on_blend_weight_slider_value_changed(value: float) -> void:
|
||||
mixamo_amy_walk_limp.get_node("AnimationTree").set("parameters/Blend2/blend_amount", value)
|
||||
mixamo_amy_walk_limp_synced.get_node("SyncedAnimationGraph").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)
|
||||
|
||||
animation_tree.set("parameters/Blend2/blend_amount", value)
|
||||
synced_animation_graph.set("parameters/AnimationBlend2Node/blend_amount", value)
|
||||
blend_weight_label.text = str(value)
|
||||
|
||||
114
demo/main.tscn
114
demo/main.tscn
@ -3,14 +3,7 @@
|
||||
[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="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://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
|
||||
[ext_resource type="SyncedBlendTree" uid="uid://bijslmj4wd7ap" path="res://synced_blend_tree_node_limping.tres" id="4_1bvp3"]
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_h2yge"]
|
||||
albedo_color = Color(0.427493, 0.42749307, 0.42749307, 1)
|
||||
@ -32,6 +25,24 @@ sky = SubResource("Sky_1bvp3")
|
||||
tonemap_mode = 2
|
||||
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"]
|
||||
script = ExtResource("1_1bvp3")
|
||||
|
||||
@ -50,7 +61,6 @@ grow_horizontal = 2
|
||||
grow_vertical = 0
|
||||
size_flags_horizontal = 4
|
||||
size_flags_vertical = 8
|
||||
theme = SubResource("Theme_272bh")
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="UI/MarginContainer"]
|
||||
layout_mode = 2
|
||||
@ -70,36 +80,8 @@ step = 0.001
|
||||
|
||||
[node name="BlendWeightLabel" type="Label" parent="UI/MarginContainer/HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(80, 0)
|
||||
layout_mode = 2
|
||||
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="."]
|
||||
|
||||
@ -115,62 +97,36 @@ shadow_enabled = true
|
||||
environment = SubResource("Environment_lquwl")
|
||||
|
||||
[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)
|
||||
fov = 36.8
|
||||
transform = Transform3D(1, 0, 0, 0, 0.9897887, 0.14254257, 0, -0.14254257, 0.9897887, 0, 0.89188766, 1.4517534)
|
||||
|
||||
[node name="Characters" type="Node3D" parent="."]
|
||||
|
||||
[node name="MixamoAmyWalkLimp" parent="Characters" instance=ExtResource("1_0xm2m")]
|
||||
unique_name_in_owner = true
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.4, 0, 0)
|
||||
[node name="MixamoAmy" parent="Characters" instance=ExtResource("1_0xm2m")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.5, 0, 0)
|
||||
|
||||
[node name="AnimationTree" type="AnimationTree" parent="Characters/MixamoAmyWalkLimp"]
|
||||
tree_root = ExtResource("3_272bh")
|
||||
[node name="AnimationTree" type="AnimationTree" parent="Characters/MixamoAmy"]
|
||||
unique_name_in_owner = true
|
||||
root_node = NodePath("%AnimationTree/..")
|
||||
tree_root = SubResource("AnimationNodeBlendTree_7mycd")
|
||||
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")]
|
||||
unique_name_in_owner = true
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.6, 0, 0)
|
||||
[node name="MixamoAmySynced" parent="Characters" instance=ExtResource("1_0xm2m")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.5, 0, 0)
|
||||
|
||||
[node name="AnimationTree" type="AnimationTree" parent="Characters/MixamoAmyWalkRun"]
|
||||
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"]
|
||||
[node name="AnimationPlayer2" type="AnimationPlayer" parent="Characters/MixamoAmySynced"]
|
||||
libraries = {
|
||||
&"animation_library": ExtResource("3_1bvp3")
|
||||
}
|
||||
|
||||
[node name="SyncedAnimationGraph" type="SyncedAnimationGraph" parent="Characters/MixamoAmyWalkLimpSynced"]
|
||||
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")]
|
||||
[node name="SyncedAnimationGraph" type="SyncedAnimationGraph" parent="Characters/MixamoAmySynced"]
|
||||
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")
|
||||
tree_root = ExtResource("5_7mycd")
|
||||
tree_root = ExtResource("4_1bvp3")
|
||||
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"]
|
||||
|
||||
[editable path="Characters/MixamoAmyWalkLimp"]
|
||||
[editable path="Characters/MixamoAmyWalkRun"]
|
||||
[editable path="Characters/MixamoAmyWalkLimpSynced"]
|
||||
[editable path="Characters/MixamoAmyWalkRunSynced"]
|
||||
[editable path="Characters/MixamoAmy"]
|
||||
[editable path="Characters/MixamoAmySynced"]
|
||||
|
||||
@ -15,11 +15,6 @@ run/main_scene="uid://svj53e2xoio"
|
||||
config/features=PackedStringArray("4.5", "Forward Plus")
|
||||
config/icon="res://icon.svg"
|
||||
|
||||
[display]
|
||||
|
||||
window/size/viewport_width=1900
|
||||
window/size/viewport_height=1024
|
||||
|
||||
[dotnet]
|
||||
|
||||
project/assembly_name="Synced Blend Tree Test"
|
||||
|
||||
@ -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"]
|
||||
animation = &"animation_library/TestAnimationB"
|
||||
animation = &"animation_library/Run-InPlace"
|
||||
|
||||
[sub_resource type="AnimationSamplerNode" id="AnimationSamplerNode_sntl5"]
|
||||
animation = &"animation_library/TestAnimationA"
|
||||
animation = &"animation_library/Walk-InPlace"
|
||||
|
||||
[sub_resource type="AnimationBlend2Node" id="AnimationBlend2Node_n4m28"]
|
||||
sync = false
|
||||
blend_amount = 0.5
|
||||
sync = false
|
||||
|
||||
[resource]
|
||||
nodes/Blend2/node = SubResource("AnimationBlend2Node_n4m28")
|
||||
|
||||
@ -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"]
|
||||
@ -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"]
|
||||
@ -3,7 +3,7 @@
|
||||
#include "core/object/class_db.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) {
|
||||
return;
|
||||
}
|
||||
@ -14,7 +14,7 @@ void initialize_blendalot_animgraph_module(ModuleInitializationLevel p_level) {
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
#include "modules/register_module_types.h"
|
||||
|
||||
void initialize_blendalot_animgraph_module(ModuleInitializationLevel p_level);
|
||||
void uninitialize_blendalot_animgraph_module(ModuleInitializationLevel p_level);
|
||||
void initialize_synced_blend_tree_module(ModuleInitializationLevel p_level);
|
||||
void uninitialize_synced_blend_tree_module(ModuleInitializationLevel p_level);
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
#include "synced_animation_graph.h"
|
||||
|
||||
#include "core/os/time.h"
|
||||
#include "core/profiling/profiling.h"
|
||||
#include "scene/3d/skeleton_3d.h"
|
||||
#include "scene/animation/animation_player.h"
|
||||
|
||||
@ -142,8 +141,6 @@ void SyncedAnimationGraph::_tree_changed() {
|
||||
}
|
||||
|
||||
void SyncedAnimationGraph::_notification(int p_what) {
|
||||
GodotProfileZone("SyncedAnimationGraph::_notification");
|
||||
|
||||
switch (p_what) {
|
||||
case Node::NOTIFICATION_READY: {
|
||||
_setup_evaluation_context();
|
||||
@ -295,32 +292,31 @@ void SyncedAnimationGraph::_process_graph(double p_delta, bool p_update_only) {
|
||||
return;
|
||||
}
|
||||
|
||||
GodotProfileZone("SyncedAnimationGraph::_process_graph");
|
||||
|
||||
_update_properties();
|
||||
|
||||
AnimationData *graph_output = graph_context.animation_data_allocator.allocate();
|
||||
root_animation_node->activate_inputs(Vector<Ref<SyncedAnimationNode>>());
|
||||
root_animation_node->calculate_sync_track(Vector<Ref<SyncedAnimationNode>>());
|
||||
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);
|
||||
|
||||
graph_context.animation_data_allocator.free(graph_output);
|
||||
_apply_animation_data(graph_output);
|
||||
}
|
||||
|
||||
void SyncedAnimationGraph::_apply_animation_data(const AnimationData &output_data) const {
|
||||
GodotProfileZone("SyncedAnimationGraph::_apply_animation_data");
|
||||
|
||||
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);
|
||||
for (const KeyValue<Animation::TypeHash, AnimationData::TrackValue *> &K : output_data.track_values) {
|
||||
const AnimationData::TrackValue *track_value = K.value;
|
||||
switch (track_value->type) {
|
||||
case AnimationData::TrackType::TYPE_POSITION_3D:
|
||||
case AnimationData::TrackType::TYPE_ROTATION_3D: {
|
||||
const AnimationData::TransformTrackValue *transform_track_value = static_cast<const AnimationData::TransformTrackValue *>(track_value);
|
||||
|
||||
if (transform_track_value->bone_idx != -1) {
|
||||
int bone_idx = -1;
|
||||
NodePath path = transform_track_value->track->path;
|
||||
|
||||
if (path.get_subname_count() == 1) {
|
||||
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);
|
||||
}
|
||||
@ -331,6 +327,7 @@ void SyncedAnimationGraph::_apply_animation_data(const AnimationData &output_dat
|
||||
} else {
|
||||
assert(false && "Not yet implemented!");
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
#include "scene/animation/animation_player.h"
|
||||
#include "synced_animation_node.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
class Skeleton3D;
|
||||
|
||||
class SyncedAnimationGraph : public Node {
|
||||
@ -14,6 +16,7 @@ private:
|
||||
NodePath skeleton_path;
|
||||
|
||||
GraphEvaluationContext graph_context = {};
|
||||
AnimationData graph_output;
|
||||
|
||||
mutable List<PropertyInfo> properties;
|
||||
mutable AHashMap<StringName, Pair<Ref<SyncedAnimationNode>, StringName>> parameter_to_node_parameter_map;
|
||||
|
||||
@ -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) {
|
||||
GodotProfileZone("AnimationData::sample_from_animation");
|
||||
|
||||
const LocalVector<Animation::Track *> tracks = animation->get_tracks();
|
||||
const Vector<Animation::Track *> tracks = animation->get_tracks();
|
||||
Animation::Track *const *tracks_ptr = tracks.ptr();
|
||||
|
||||
int count = tracks.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
TrackValue *track_value = nullptr;
|
||||
const Animation::Track *animation_track = tracks_ptr[i];
|
||||
const NodePath &track_node_path = animation_track->path;
|
||||
if (!animation_track->enabled) {
|
||||
continue;
|
||||
}
|
||||
@ -151,9 +151,18 @@ void AnimationData::sample_from_animation(const Ref<Animation> &animation, const
|
||||
switch (ttype) {
|
||||
case Animation::TYPE_POSITION_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) {
|
||||
bone_idx = skeleton_3d->find_bone(track_node_path.get_subname(0));
|
||||
if (bone_idx != -1) {
|
||||
transform_track_value->bone_idx = bone_idx;
|
||||
switch (ttype) {
|
||||
case Animation::TYPE_POSITION_3D: {
|
||||
animation->try_position_track_interpolate(i, p_time, &transform_track_value->loc);
|
||||
@ -170,10 +179,13 @@ void AnimationData::sample_from_animation(const Ref<Animation> &animation, const
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// TODO
|
||||
assert(false && !"Not yet implemented");
|
||||
}
|
||||
|
||||
track_value = transform_track_value;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
@ -182,61 +194,10 @@ void AnimationData::sample_from_animation(const Ref<Animation> &animation, const
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
track_value->track = tracks_ptr[i];
|
||||
set_value(animation_track->thash, track_value);
|
||||
}
|
||||
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) {
|
||||
@ -248,8 +209,6 @@ bool AnimationSamplerNode::initialize(GraphEvaluationContext &context) {
|
||||
return false;
|
||||
}
|
||||
|
||||
context.animation_data_allocator.register_track_values(animation, context.skeleton_3d);
|
||||
|
||||
node_time_info.loop_mode = animation->get_loop_mode();
|
||||
|
||||
// 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) {
|
||||
GodotProfileZone("AnimationSamplerNode::evaluate");
|
||||
|
||||
assert(inputs.size() == 0);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
output.clear();
|
||||
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) {
|
||||
GodotProfileZone("AnimationBlend2Node::evaluate");
|
||||
|
||||
output = std::move(*inputs[0]);
|
||||
output = *inputs[0];
|
||||
output.blend(*inputs[1], blend_weight);
|
||||
}
|
||||
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
#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 "core/io/resource.h"
|
||||
#include "scene/3d/skeleton_3d.h"
|
||||
#include "sync_track.h"
|
||||
|
||||
#include <cassert>
|
||||
@ -13,11 +12,7 @@
|
||||
* @class AnimationData
|
||||
* Represents data that is transported via animation connections in the SyncedAnimationGraph.
|
||||
*
|
||||
* In general AnimationData objects should be obtained using the AnimationDataAllocator.
|
||||
*
|
||||
* 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.
|
||||
* Essentially, it is a hash map for all Animation::Track values that can are sampled from an Animation.
|
||||
*/
|
||||
struct AnimationData {
|
||||
enum TrackType : uint8_t {
|
||||
@ -33,6 +28,7 @@ struct AnimationData {
|
||||
};
|
||||
|
||||
struct TrackValue {
|
||||
Animation::Track *track = nullptr;
|
||||
TrackType type = TYPE_ANIMATION;
|
||||
|
||||
virtual ~TrackValue() = default;
|
||||
@ -94,51 +90,70 @@ struct AnimationData {
|
||||
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;
|
||||
}
|
||||
|
||||
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() {
|
||||
_clear_values();
|
||||
}
|
||||
AnimationData(const AnimationData &other) {
|
||||
value_buffer_offset = other.value_buffer_offset;
|
||||
buffer = other.buffer;
|
||||
for (const KeyValue<Animation::TypeHash, TrackValue *> &K : other.track_values) {
|
||||
track_values.insert(K.key, K.value->clone());
|
||||
}
|
||||
}
|
||||
AnimationData(AnimationData &&other) noexcept :
|
||||
value_buffer_offset(std::exchange(other.value_buffer_offset, AHashMap<Animation::TypeHash, size_t, HashHasher>())),
|
||||
buffer(std::exchange(other.buffer, LocalVector<uint8_t>())) {
|
||||
track_values(std::exchange(other.track_values, AHashMap<Animation::TypeHash, TrackValue *, HashHasher>())) {
|
||||
}
|
||||
AnimationData &operator=(const AnimationData &other) {
|
||||
AnimationData temp(other);
|
||||
std::swap(value_buffer_offset, temp.value_buffer_offset);
|
||||
std::swap(buffer, temp.buffer);
|
||||
std::swap(track_values, temp.track_values);
|
||||
return *this;
|
||||
}
|
||||
AnimationData &operator=(AnimationData &&other) noexcept {
|
||||
std::swap(value_buffer_offset, other.value_buffer_offset);
|
||||
std::swap(buffer, other.buffer);
|
||||
std::swap(track_values, other.track_values);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void allocate_track_value(const Animation::Track *animation_track, const Skeleton3D *skeleton_3d);
|
||||
void allocate_track_values(const Ref<Animation> &animation, const Skeleton3D *skeleton_3d);
|
||||
|
||||
template <typename TrackValueType>
|
||||
TrackValueType *get_value(const Animation::TypeHash &thash) {
|
||||
return reinterpret_cast<TrackValueType *>(&buffer[value_buffer_offset[thash]]);
|
||||
void
|
||||
set_value(const Animation::TypeHash &thash, TrackValue *value) {
|
||||
if (!track_values.has(thash)) {
|
||||
track_values.insert(thash, value);
|
||||
} else {
|
||||
track_values[thash] = value;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename TrackValueType>
|
||||
const TrackValueType *get_value(const Animation::TypeHash &thash) const {
|
||||
return reinterpret_cast<const TrackValueType *>(&buffer[value_buffer_offset[thash]]);
|
||||
void clear() {
|
||||
_clear_values();
|
||||
}
|
||||
|
||||
bool has_same_tracks(const AnimationData &other) const {
|
||||
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);
|
||||
}
|
||||
|
||||
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)) {
|
||||
valid_track_hashes.remove(entry);
|
||||
} else {
|
||||
@ -150,16 +165,14 @@ struct AnimationData {
|
||||
}
|
||||
|
||||
void blend(const AnimationData &to_data, const float lambda) {
|
||||
GodotProfileZone("AnimationData::blend");
|
||||
|
||||
if (!has_same_tracks(to_data)) {
|
||||
print_error("Cannot blend AnimationData: tracks do not match.");
|
||||
return;
|
||||
}
|
||||
|
||||
for (const KeyValue<Animation::TypeHash, size_t> &K : value_buffer_offset) {
|
||||
TrackValue *track_value = get_value<TrackValue>(K.key);
|
||||
const TrackValue *other_track_value = to_data.get_value<TrackValue>(K.key);
|
||||
for (const KeyValue<Animation::TypeHash, TrackValue *> &K : track_values) {
|
||||
TrackValue *track_value = K.value;
|
||||
TrackValue *other_track_value = to_data.track_values[K.key];
|
||||
|
||||
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);
|
||||
|
||||
AHashMap<Animation::TypeHash, size_t, HashHasher> value_buffer_offset;
|
||||
LocalVector<uint8_t> buffer;
|
||||
};
|
||||
AHashMap<Animation::TypeHash, TrackValue *, HashHasher> track_values; // Animation::Track to TrackValue
|
||||
|
||||
/**
|
||||
* @class AnimationDataAllocator
|
||||
*
|
||||
* Allows reusing of already allocated AnimationData objects. Stores the default values for all
|
||||
* 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();
|
||||
protected:
|
||||
void _clear_values() {
|
||||
for (KeyValue<Animation::TypeHash, TrackValue *> &K : track_values) {
|
||||
memdelete(K.value);
|
||||
}
|
||||
}
|
||||
|
||||
/// @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 {
|
||||
AnimationPlayer *animation_player = nullptr;
|
||||
Skeleton3D *skeleton_3d = nullptr;
|
||||
AnimationDataAllocator animation_data_allocator;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -271,14 +240,14 @@ public:
|
||||
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.
|
||||
for (const Ref<SyncedAnimationNode> &node : input_nodes) {
|
||||
node->active = true;
|
||||
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.
|
||||
if (input_nodes.size() > 0) {
|
||||
node_time_info.sync_track = input_nodes[0]->node_time_info.sync_track;
|
||||
@ -368,16 +337,16 @@ public:
|
||||
|
||||
return result;
|
||||
}
|
||||
void activate_inputs(const Vector<Ref<SyncedAnimationNode>> &input_nodes) override {
|
||||
input_nodes[0]->active = true;
|
||||
input_nodes[1]->active = true;
|
||||
void activate_inputs(Vector<Ref<SyncedAnimationNode>> input_nodes) override {
|
||||
for (const Ref<SyncedAnimationNode> &node : input_nodes) {
|
||||
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.
|
||||
input_nodes[0]->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;
|
||||
node->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) {
|
||||
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);
|
||||
@ -533,7 +502,7 @@ struct BlendTreeGraph {
|
||||
LocalVector<int> sorted_node_indices = get_sorted_node_indices();
|
||||
|
||||
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++) {
|
||||
int node_index = sorted_node_indices[i];
|
||||
sorted_nodes.push_back(nodes[node_index]);
|
||||
@ -744,9 +713,7 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
void activate_inputs(const Vector<Ref<SyncedAnimationNode>> &input_nodes) override {
|
||||
GodotProfileZone("SyncedBlendTree::activate_inputs");
|
||||
|
||||
void activate_inputs(Vector<Ref<SyncedAnimationNode>> input_nodes) override {
|
||||
tree_graph.nodes[0]->active = true;
|
||||
for (int i = 0; i < tree_graph.nodes.size(); 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 {
|
||||
GodotProfileZone("SyncedBlendTree::calculate_sync_track");
|
||||
void calculate_sync_track(Vector<Ref<SyncedAnimationNode>> input_nodes) override {
|
||||
for (int i = tree_graph.nodes.size() - 1; i > 0; i--) {
|
||||
const Ref<SyncedAnimationNode> &node = tree_graph.nodes[i];
|
||||
|
||||
@ -776,8 +742,6 @@ public:
|
||||
}
|
||||
|
||||
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.position += p_delta;
|
||||
|
||||
@ -799,8 +763,6 @@ public:
|
||||
}
|
||||
|
||||
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--) {
|
||||
const Ref<SyncedAnimationNode> &node = tree_graph.nodes[i];
|
||||
|
||||
@ -820,14 +782,14 @@ public:
|
||||
if (i == 1) {
|
||||
node_runtime_data.output_data = &output_data;
|
||||
} 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);
|
||||
|
||||
// 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) {
|
||||
context.animation_data_allocator.free(_node_runtime_data[child_index].output_data);
|
||||
memfree(_node_runtime_data[child_index].output_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -222,25 +222,20 @@ TEST_CASE("[SyncedAnimationGraph] Test BlendTree construction") {
|
||||
|
||||
TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph] Test AnimationData blending") {
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
AnimationData data_blended = data_t0;
|
||||
data_blended.blend(data_t1, 0.5);
|
||||
|
||||
REQUIRE(data_blended.has_same_tracks(data_t0_5));
|
||||
for (const KeyValue<Animation::TypeHash, size_t> &K : data_blended.value_buffer_offset) {
|
||||
AnimationData::TrackValue *blended_value = data_blended.get_value<AnimationData::TrackValue>(K.key);
|
||||
AnimationData::TrackValue *data_t0_5_value = data_t0_5.get_value<AnimationData::TrackValue>(K.key);
|
||||
CHECK(*blended_value == *data_t0_5_value);
|
||||
for (const KeyValue<Animation::TypeHash, AnimationData::TrackValue *> &K : data_blended.track_values) {
|
||||
CHECK(K.value->operator==(*data_t0_5.track_values.find(K.key)->value));
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
REQUIRE(data_blended.has_same_tracks(data_t0_5));
|
||||
for (const KeyValue<Animation::TypeHash, size_t> &K : data_blended.value_buffer_offset) {
|
||||
AnimationData::TrackValue *blended_value = data_blended.get_value<AnimationData::TrackValue>(K.key);
|
||||
AnimationData::TrackValue *data_t0_5_value = data_t0_5.get_value<AnimationData::TrackValue>(K.key);
|
||||
CHECK(*blended_value != *data_t0_5_value);
|
||||
for (const KeyValue<Animation::TypeHash, AnimationData::TrackValue *> &K : data_blended.track_values) {
|
||||
CHECK(K.value->operator!=(*data_t0_5.track_values.find(K.key)->value));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user