Compare commits
No commits in common. "c6f37c8ef14121682bef9dc973a5a43f5c60ab48" and "3bb0725e3e0aec00b3c2eb2c898357697386ca47" have entirely different histories.
c6f37c8ef1
...
3bb0725e3e
Binary file not shown.
19
demo/main.gd
19
demo/main.gd
@ -1,19 +0,0 @@
|
|||||||
extends Node3D
|
|
||||||
|
|
||||||
@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.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:
|
|
||||||
animation_tree.set("parameters/Blend2/blend_amount", value)
|
|
||||||
synced_animation_graph.set("parameters/AnimationBlend2Node/blend_amount", value)
|
|
||||||
blend_weight_label.text = str(value)
|
|
||||||
@ -1 +0,0 @@
|
|||||||
uid://bjvgqujpqumj7
|
|
||||||
144
demo/main.tscn
144
demo/main.tscn
@ -1,132 +1,34 @@
|
|||||||
[gd_scene load_steps=14 format=3 uid="uid://svj53e2xoio"]
|
[gd_scene load_steps=5 format=3 uid="uid://svj53e2xoio"]
|
||||||
|
|
||||||
[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="AnimationNodeBlendTree" uid="uid://dbkgln7hoxxc8" path="res://embedded_statemachine.tres" id="2_h2yge"]
|
||||||
[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_h2yge"]
|
||||||
[ext_resource type="SyncedBlendTree" uid="uid://bijslmj4wd7ap" path="res://synced_blend_tree_node_limping.tres" id="4_1bvp3"]
|
[ext_resource type="SyncedBlendTree" uid="uid://cjeho6848x43q" path="res://synced_blend_tree_node.tres" id="4_1bvp3"]
|
||||||
|
|
||||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_h2yge"]
|
[node name="Node3D" type="Node3D"]
|
||||||
albedo_color = Color(0.427493, 0.42749307, 0.42749307, 1)
|
|
||||||
|
|
||||||
[sub_resource type="PlaneMesh" id="PlaneMesh_h2yge"]
|
[node name="MixamoAmy" parent="." instance=ExtResource("1_0xm2m")]
|
||||||
material = SubResource("StandardMaterial3D_h2yge")
|
|
||||||
size = Vector2(20, 20)
|
|
||||||
|
|
||||||
[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_h2yge"]
|
[node name="AnimationTree" type="AnimationTree" parent="."]
|
||||||
sky_horizon_color = Color(0.66224277, 0.6717428, 0.6867428, 1)
|
active = false
|
||||||
ground_horizon_color = Color(0.66224277, 0.6717428, 0.6867428, 1)
|
root_node = NodePath("../MixamoAmy")
|
||||||
|
tree_root = ExtResource("2_h2yge")
|
||||||
|
anim_player = NodePath("../MixamoAmy/AnimationPlayer")
|
||||||
|
parameters/Blend2/blend_amount = 0.44
|
||||||
|
"parameters/Embedded StateMachine/conditions/is_healthy" = false
|
||||||
|
"parameters/Embedded StateMachine/conditions/is_limping" = false
|
||||||
|
|
||||||
[sub_resource type="Sky" id="Sky_1bvp3"]
|
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
|
||||||
sky_material = SubResource("ProceduralSkyMaterial_h2yge")
|
active = false
|
||||||
|
root_node = NodePath("../MixamoAmy")
|
||||||
[sub_resource type="Environment" id="Environment_lquwl"]
|
|
||||||
background_mode = 2
|
|
||||||
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")
|
|
||||||
|
|
||||||
[node name="UI" type="CanvasLayer" parent="."]
|
|
||||||
|
|
||||||
[node name="MarginContainer" type="MarginContainer" parent="UI"]
|
|
||||||
anchors_preset = 7
|
|
||||||
anchor_left = 0.5
|
|
||||||
anchor_top = 1.0
|
|
||||||
anchor_right = 0.5
|
|
||||||
anchor_bottom = 1.0
|
|
||||||
offset_left = -153.5
|
|
||||||
offset_top = -40.0
|
|
||||||
offset_right = 153.5
|
|
||||||
grow_horizontal = 2
|
|
||||||
grow_vertical = 0
|
|
||||||
size_flags_horizontal = 4
|
|
||||||
size_flags_vertical = 8
|
|
||||||
|
|
||||||
[node name="HBoxContainer" type="HBoxContainer" parent="UI/MarginContainer"]
|
|
||||||
layout_mode = 2
|
|
||||||
theme_override_constants/separation = 14
|
|
||||||
|
|
||||||
[node name="Label" type="Label" parent="UI/MarginContainer/HBoxContainer"]
|
|
||||||
layout_mode = 2
|
|
||||||
text = "Blend Weight"
|
|
||||||
|
|
||||||
[node name="BlendWeightSlider" type="HSlider" parent="UI/MarginContainer/HBoxContainer"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
custom_minimum_size = Vector2(200, 0)
|
|
||||||
layout_mode = 2
|
|
||||||
size_flags_vertical = 4
|
|
||||||
max_value = 1.0
|
|
||||||
step = 0.001
|
|
||||||
|
|
||||||
[node name="BlendWeightLabel" type="Label" parent="UI/MarginContainer/HBoxContainer"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
text = "0.0"
|
|
||||||
|
|
||||||
[node name="Level" type="Node3D" parent="."]
|
|
||||||
|
|
||||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="Level"]
|
|
||||||
mesh = SubResource("PlaneMesh_h2yge")
|
|
||||||
skeleton = NodePath("../..")
|
|
||||||
|
|
||||||
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="Level"]
|
|
||||||
transform = Transform3D(1, 0, 0, 0, 0.14081486, 0.99003595, 0, -0.99003595, 0.14081486, 0, 2.0531263, 4.9494514)
|
|
||||||
shadow_enabled = true
|
|
||||||
|
|
||||||
[node name="WorldEnvironment" type="WorldEnvironment" parent="Level"]
|
|
||||||
environment = SubResource("Environment_lquwl")
|
|
||||||
|
|
||||||
[node name="Camera3D" type="Camera3D" parent="Level"]
|
|
||||||
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="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/MixamoAmy"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
root_node = NodePath("%AnimationTree/..")
|
|
||||||
tree_root = SubResource("AnimationNodeBlendTree_7mycd")
|
|
||||||
anim_player = NodePath("../AnimationPlayer")
|
|
||||||
parameters/Blend2/blend_amount = 0.5
|
|
||||||
|
|
||||||
[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="AnimationPlayer2" type="AnimationPlayer" parent="Characters/MixamoAmySynced"]
|
|
||||||
libraries = {
|
libraries = {
|
||||||
&"animation_library": ExtResource("3_1bvp3")
|
&"animation_library": ExtResource("3_h2yge")
|
||||||
}
|
}
|
||||||
|
|
||||||
[node name="SyncedAnimationGraph" type="SyncedAnimationGraph" parent="Characters/MixamoAmySynced"]
|
[node name="SyncedAnimationGraph" type="SyncedAnimationGraph" parent="."]
|
||||||
unique_name_in_owner = true
|
animation_player = NodePath("../AnimationPlayer")
|
||||||
animation_player = NodePath("../AnimationPlayer2")
|
|
||||||
tree_root = ExtResource("4_1bvp3")
|
tree_root = ExtResource("4_1bvp3")
|
||||||
skeleton = NodePath("../Armature/Skeleton3D")
|
skeleton = NodePath("../MixamoAmy/Armature/Skeleton3D")
|
||||||
parameters/AnimationBlend2Node/blend_amount = 0.5
|
parameters/AnimationBlend2Node/blend_amount = 0.24
|
||||||
|
|
||||||
[connection signal="value_changed" from="UI/MarginContainer/HBoxContainer/BlendWeightSlider" to="." method="_on_blend_weight_slider_value_changed"]
|
[editable path="MixamoAmy"]
|
||||||
|
|
||||||
[editable path="Characters/MixamoAmy"]
|
|
||||||
[editable path="Characters/MixamoAmySynced"]
|
|
||||||
|
|||||||
@ -11,7 +11,6 @@ config_version=5
|
|||||||
[application]
|
[application]
|
||||||
|
|
||||||
config/name="Synced Blend Tree Test"
|
config/name="Synced Blend Tree Test"
|
||||||
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"
|
||||||
|
|
||||||
|
|||||||
@ -1,18 +0,0 @@
|
|||||||
[gd_resource type="SyncedBlendTree" load_steps=4 format=3 uid="uid://de41u8rkjnjyk"]
|
|
||||||
|
|
||||||
[sub_resource type="AnimationSamplerNode" id="AnimationSamplerNode_bvt3d"]
|
|
||||||
animation = &"animation_library/Run-InPlace"
|
|
||||||
|
|
||||||
[sub_resource type="AnimationSamplerNode" id="AnimationSamplerNode_sntl5"]
|
|
||||||
animation = &"animation_library/Walk-InPlace"
|
|
||||||
|
|
||||||
[sub_resource type="AnimationBlend2Node" id="AnimationBlend2Node_n4m28"]
|
|
||||||
|
|
||||||
[resource]
|
|
||||||
nodes/Blend2/node = SubResource("AnimationBlend2Node_n4m28")
|
|
||||||
nodes/Blend2/position = Vector2(0, 0)
|
|
||||||
"nodes/AnimationSamplerNode 1/node" = SubResource("AnimationSamplerNode_bvt3d")
|
|
||||||
"nodes/AnimationSamplerNode 1/position" = Vector2(0, 0)
|
|
||||||
nodes/AnimationSamplerNode/node = SubResource("AnimationSamplerNode_sntl5")
|
|
||||||
nodes/AnimationSamplerNode/position = Vector2(0, 0)
|
|
||||||
node_connections = [&"Blend2", 0, &"AnimationSamplerNode", &"Blend2", 1, &"AnimationSamplerNode 1", &"Output", 0, &"Blend2"]
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
[gd_resource type="SyncedBlendTree" load_steps=4 format=3 uid="uid://bijslmj4wd7ap"]
|
|
||||||
|
|
||||||
[sub_resource type="AnimationBlend2Node" id="AnimationBlend2Node_bvt3d"]
|
|
||||||
blend_amount = 0.5
|
|
||||||
|
|
||||||
[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"]
|
|
||||||
19
demo/walk_limp_blend_tree.tres
Normal file
19
demo/walk_limp_blend_tree.tres
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
[gd_resource type="AnimationNodeBlendTree" load_steps=4 format=3 uid="uid://c7o0gt3li5p4g"]
|
||||||
|
|
||||||
|
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_fpiwu"]
|
||||||
|
animation = &"Limping-InPlace"
|
||||||
|
|
||||||
|
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_4sffn"]
|
||||||
|
animation = &"Walk-InPlace"
|
||||||
|
|
||||||
|
[sub_resource type="AnimationNodeBlend2" id="AnimationNodeBlend2_w6plo"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
nodes/output/position = Vector2(860, 160)
|
||||||
|
nodes/Animation/node = SubResource("AnimationNodeAnimation_4sffn")
|
||||||
|
nodes/Animation/position = Vector2(280, 100)
|
||||||
|
"nodes/Animation 2/node" = SubResource("AnimationNodeAnimation_fpiwu")
|
||||||
|
"nodes/Animation 2/position" = Vector2(280, 300)
|
||||||
|
nodes/Blend2/node = SubResource("AnimationNodeBlend2_w6plo")
|
||||||
|
nodes/Blend2/position = Vector2(640, 160)
|
||||||
|
node_connections = [&"output", 0, &"Blend2", &"Blend2", 0, &"Animation", &"Blend2", 1, &"Animation 2"]
|
||||||
25
sync_track.h
25
sync_track.h
@ -7,18 +7,17 @@
|
|||||||
|
|
||||||
/** @class SyncTrack used for synced animation blending.
|
/** @class SyncTrack used for synced animation blending.
|
||||||
*
|
*
|
||||||
* A SyncTrack consists of multiple sync intervals that are adjacent to each other.
|
* A SyncTrack consists of multiple SyncInterval that are adjacent to each other.
|
||||||
*
|
*
|
||||||
* Important definitions:
|
* Important definitions:
|
||||||
*
|
*
|
||||||
* - Absolute Time: time within an animation duration in seconds.
|
* - Absolute Time: time within an animation duration in seconds.
|
||||||
* - Ratio: time relative to the SyncTrack duration, e.g. 0.5 corresponds to 50% of the duration.
|
* - Ratio: time relative to the animations duration, e.g. 0.5 corresponds to 50% of the duration.
|
||||||
* - SyncTime is a floating point value where the integer parts defines the sync interval and the
|
* - SyncTime is a floating point value where the integer parts defines the SyncInterval and the
|
||||||
* fractional part the fraction within the interval. I.e. a SyncTime of 5.332 means it is ~33%
|
* fractional part the fraction within the interval. I.e. a SyncTime of 5.332 means it is ~33%
|
||||||
* through interval 5.
|
* through interval 5.
|
||||||
*
|
*
|
||||||
* A sync interval is defined by a ratio of the starting point and the ratio of the interval's
|
* A SyncInterval is defined by a ratio of the starting point and the ratio of the interval's duration.
|
||||||
* duration. Blended SyncTracks always have their first interval start at t = 0.0s.
|
|
||||||
*/
|
*/
|
||||||
struct SyncTrack {
|
struct SyncTrack {
|
||||||
static constexpr int cSyncTrackMaxIntervals = 8;
|
static constexpr int cSyncTrackMaxIntervals = 8;
|
||||||
@ -120,10 +119,6 @@ struct SyncTrack {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Creates a blended SyncTrack from two input SyncTracks
|
|
||||||
*
|
|
||||||
* \note the first interval will always start at sync time 0, i.e. interval_start_ratio[0] = 0.0.
|
|
||||||
*/
|
|
||||||
static SyncTrack
|
static SyncTrack
|
||||||
blend(float weight, const SyncTrack &track_A, const SyncTrack &track_B) {
|
blend(float weight, const SyncTrack &track_A, const SyncTrack &track_B) {
|
||||||
assert(track_A.num_intervals == track_B.num_intervals);
|
assert(track_A.num_intervals == track_B.num_intervals);
|
||||||
@ -134,7 +129,17 @@ struct SyncTrack {
|
|||||||
result.duration =
|
result.duration =
|
||||||
(1.0f - weight) * track_A.duration + weight * track_B.duration;
|
(1.0f - weight) * track_A.duration + weight * track_B.duration;
|
||||||
|
|
||||||
result.interval_start_ratio[0] = 0.f;
|
float interval_0_offset =
|
||||||
|
track_B.interval_start_ratio[0] - track_A.interval_start_ratio[0];
|
||||||
|
if (interval_0_offset > 0.5f) {
|
||||||
|
interval_0_offset = -fmodf(1.f - interval_0_offset, 1.0f);
|
||||||
|
} else if (interval_0_offset < -0.5) {
|
||||||
|
interval_0_offset = fmodf(1.f + interval_0_offset, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.interval_start_ratio[0] = fmodf(
|
||||||
|
1.0 + (1.0f - weight) * track_A.interval_start_ratio[0] + weight * (track_A.interval_start_ratio[0] + interval_0_offset),
|
||||||
|
1.0f);
|
||||||
|
|
||||||
for (int i = 0; i < result.num_intervals; i++) {
|
for (int i = 0; i < result.num_intervals; i++) {
|
||||||
float interval_duration_A = track_A.interval_duration_ratio[i];
|
float interval_duration_A = track_A.interval_duration_ratio[i];
|
||||||
|
|||||||
@ -201,63 +201,43 @@ void AnimationData::sample_from_animation(const Ref<Animation> &animation, const
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool AnimationSamplerNode::initialize(GraphEvaluationContext &context) {
|
bool AnimationSamplerNode::initialize(GraphEvaluationContext &context) {
|
||||||
SyncedAnimationNode::initialize(context);
|
|
||||||
|
|
||||||
animation = context.animation_player->get_animation(animation_name);
|
animation = context.animation_player->get_animation(animation_name);
|
||||||
if (!animation.is_valid()) {
|
if (!animation.is_valid()) {
|
||||||
print_error(vformat("Cannot initialize node %s: animation '%s' not found in animation player.", name, animation_name));
|
print_error(vformat("Cannot initialize node %s: animation '%s' not found in animation player.", name, animation_name));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
node_time_info.loop_mode = animation->get_loop_mode();
|
if (animation_name == "animation_library/TestAnimationA") {
|
||||||
|
// Corresponds to the walking animation
|
||||||
// Initialize Sync Track from marker
|
node_time_info.sync_track = SyncTrack::create_from_markers(animation->get_length(), { 0.8117, 0.314 });
|
||||||
LocalVector<float> sync_markers;
|
print_line(vformat("Using hardcoded sync track for animation %s.", animation_name));
|
||||||
int marker_index = 0;
|
} else if (animation_name == "animation_library/TestAnimationB") {
|
||||||
StringName marker_name = itos(marker_index);
|
// Corresponds to the running animation
|
||||||
while (animation->has_marker(marker_name)) {
|
node_time_info.sync_track = SyncTrack::create_from_markers(animation->get_length(), { 0.6256, 0.2721 });
|
||||||
sync_markers.push_back(animation->get_marker_time(marker_name));
|
print_line(vformat("Using hardcoded sync track for animation %s.", animation_name));
|
||||||
marker_index++;
|
} else if (animation_name == "animation_library/TestAnimationC") {
|
||||||
marker_name = itos(marker_index);
|
// Corresponds to the limping animation
|
||||||
|
node_time_info.sync_track = SyncTrack::create_from_markers(animation->get_length(), { 0.0674, 1.1047 });
|
||||||
|
print_line(vformat("Using hardcoded sync track for animation %s.", animation_name));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sync_markers.size() > 0) {
|
node_time_info.length = animation->get_length();
|
||||||
node_time_info.sync_track = SyncTrack::create_from_markers(animation->get_length(), sync_markers);
|
node_time_info.loop_mode = Animation::LOOP_LINEAR;
|
||||||
} else {
|
|
||||||
node_time_info.sync_track = SyncTrack::create_from_markers(animation->get_length(), { 0 });
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnimationSamplerNode::update_time(double p_time) {
|
|
||||||
SyncedAnimationNode::update_time(p_time);
|
|
||||||
|
|
||||||
if (node_time_info.is_synced) {
|
|
||||||
// Any potential looping has already been performed in the sync-controlling node.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node_time_info.loop_mode != Animation::LOOP_NONE) {
|
|
||||||
if (node_time_info.loop_mode == Animation::LOOP_LINEAR) {
|
|
||||||
if (!Math::is_zero_approx(animation->get_length())) {
|
|
||||||
node_time_info.position = Math::fposmod(node_time_info.position, static_cast<double>(animation->get_length()));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
assert(false && !"Ping-pong looping not yet supported");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AnimationSamplerNode::evaluate(GraphEvaluationContext &context, const LocalVector<AnimationData *> &inputs, AnimationData &output) {
|
void AnimationSamplerNode::evaluate(GraphEvaluationContext &context, const LocalVector<AnimationData *> &inputs, AnimationData &output) {
|
||||||
assert(inputs.size() == 0);
|
assert(inputs.size() == 0);
|
||||||
|
|
||||||
|
double sample_time = node_time_info.position;
|
||||||
|
|
||||||
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();
|
sample_time = node_time_info.sync_track.calc_ratio_from_sync_time(node_time_info.sync_position) * animation->get_length();
|
||||||
}
|
}
|
||||||
|
|
||||||
output.clear();
|
output.clear();
|
||||||
output.sample_from_animation(animation, context.skeleton_3d, node_time_info.position);
|
output.sample_from_animation(animation, context.skeleton_3d, sample_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnimationSamplerNode::set_animation(const StringName &p_name) {
|
void AnimationSamplerNode::set_animation(const StringName &p_name) {
|
||||||
@ -296,7 +276,7 @@ void AnimationBlend2Node::_bind_methods() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void AnimationBlend2Node::get_parameter_list(List<PropertyInfo> *p_list) const {
|
void AnimationBlend2Node::get_parameter_list(List<PropertyInfo> *p_list) const {
|
||||||
p_list->push_back(PropertyInfo(Variant::FLOAT, blend_weight_pname, PROPERTY_HINT_RANGE, "0,1,0.01,or_less,or_greater"));
|
p_list->push_back(PropertyInfo(Variant::FLOAT, blend_amount, PROPERTY_HINT_RANGE, "0,1,0.01,or_less,or_greater"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnimationBlend2Node::set_parameter(const StringName &p_name, const Variant &p_value) {
|
void AnimationBlend2Node::set_parameter(const StringName &p_name, const Variant &p_value) {
|
||||||
@ -310,7 +290,7 @@ Variant AnimationBlend2Node::get_parameter(const StringName &p_name) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Variant AnimationBlend2Node::get_parameter_default_value(const StringName &p_parameter) const {
|
Variant AnimationBlend2Node::get_parameter_default_value(const StringName &p_parameter) const {
|
||||||
if (p_parameter == blend_weight_pname) {
|
if (p_parameter == blend_amount) {
|
||||||
return blend_weight;
|
return blend_weight;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -318,34 +298,23 @@ Variant AnimationBlend2Node::get_parameter_default_value(const StringName &p_par
|
|||||||
}
|
}
|
||||||
|
|
||||||
void AnimationBlend2Node::_get_property_list(List<PropertyInfo> *p_list) const {
|
void AnimationBlend2Node::_get_property_list(List<PropertyInfo> *p_list) const {
|
||||||
p_list->push_back(PropertyInfo(Variant::FLOAT, blend_weight_pname, PROPERTY_HINT_RANGE, "0,1,0.01,or_less,or_greater"));
|
p_list->push_back(PropertyInfo(Variant::FLOAT, blend_amount, PROPERTY_HINT_RANGE, "0,1,0.01,or_less,or_greater"));
|
||||||
p_list->push_back(PropertyInfo(Variant::BOOL, sync_pname));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AnimationBlend2Node::_get(const StringName &p_name, Variant &r_value) const {
|
bool AnimationBlend2Node::_get(const StringName &p_name, Variant &r_value) const {
|
||||||
if (p_name == blend_weight_pname) {
|
if (p_name == blend_amount) {
|
||||||
r_value = blend_weight;
|
r_value = blend_weight;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p_name == sync_pname) {
|
|
||||||
r_value = sync;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AnimationBlend2Node::_set(const StringName &p_name, const Variant &p_value) {
|
bool AnimationBlend2Node::_set(const StringName &p_name, const Variant &p_value) {
|
||||||
if (p_name == blend_weight_pname) {
|
if (p_name == blend_amount) {
|
||||||
blend_weight = p_value;
|
blend_weight = p_value;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p_name == sync_pname) {
|
|
||||||
sync = p_value;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -220,12 +220,14 @@ protected:
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
struct NodeTimeInfo {
|
struct NodeTimeInfo {
|
||||||
double delta = 0.0;
|
double length = 0.0;
|
||||||
double position = 0.0;
|
double position = 0.0;
|
||||||
double sync_position = 0.0;
|
double sync_position = 0.0;
|
||||||
|
double delta = 0.0;
|
||||||
|
double sync_delta = 0.0;
|
||||||
bool is_synced = false;
|
bool is_synced = false;
|
||||||
|
|
||||||
Animation::LoopMode loop_mode = Animation::LOOP_NONE;
|
Animation::LoopMode loop_mode = Animation::LOOP_LINEAR;
|
||||||
SyncTrack sync_track;
|
SyncTrack sync_track;
|
||||||
};
|
};
|
||||||
NodeTimeInfo node_time_info;
|
NodeTimeInfo node_time_info;
|
||||||
@ -235,10 +237,7 @@ public:
|
|||||||
Vector2 position;
|
Vector2 position;
|
||||||
|
|
||||||
virtual ~SyncedAnimationNode() override = default;
|
virtual ~SyncedAnimationNode() override = default;
|
||||||
virtual bool initialize(GraphEvaluationContext &context) {
|
virtual bool initialize(GraphEvaluationContext &context) { return true; }
|
||||||
node_time_info = {};
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void activate_inputs(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.
|
||||||
@ -251,7 +250,6 @@ public:
|
|||||||
// 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;
|
||||||
node_time_info.loop_mode = input_nodes[0]->node_time_info.loop_mode;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
virtual void update_time(double p_time) {
|
virtual void update_time(double p_time) {
|
||||||
@ -260,6 +258,25 @@ public:
|
|||||||
} else {
|
} else {
|
||||||
node_time_info.delta = p_time;
|
node_time_info.delta = p_time;
|
||||||
node_time_info.position += p_time;
|
node_time_info.position += p_time;
|
||||||
|
if (node_time_info.position > node_time_info.length) {
|
||||||
|
switch (node_time_info.loop_mode) {
|
||||||
|
case Animation::LOOP_NONE: {
|
||||||
|
node_time_info.position = node_time_info.length;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Animation::LOOP_LINEAR: {
|
||||||
|
assert(node_time_info.length > 0.0);
|
||||||
|
while (node_time_info.position > node_time_info.length) {
|
||||||
|
node_time_info.position -= node_time_info.length;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Animation::LOOP_PINGPONG: {
|
||||||
|
assert(false && !"Not yet implemented.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
virtual void evaluate(GraphEvaluationContext &context, const LocalVector<AnimationData *> &input_datas, AnimationData &output_data) {
|
virtual void evaluate(GraphEvaluationContext &context, const LocalVector<AnimationData *> &input_datas, AnimationData &output_data) {
|
||||||
@ -300,7 +317,6 @@ private:
|
|||||||
Ref<Animation> animation;
|
Ref<Animation> animation;
|
||||||
|
|
||||||
bool initialize(GraphEvaluationContext &context) override;
|
bool initialize(GraphEvaluationContext &context) override;
|
||||||
void update_time(double p_time) override;
|
|
||||||
void evaluate(GraphEvaluationContext &context, const LocalVector<AnimationData *> &inputs, AnimationData &output) override;
|
void evaluate(GraphEvaluationContext &context, const LocalVector<AnimationData *> &inputs, AnimationData &output) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@ -320,6 +336,7 @@ class AnimationBlend2Node : public SyncedAnimationNode {
|
|||||||
GDCLASS(AnimationBlend2Node, SyncedAnimationNode);
|
GDCLASS(AnimationBlend2Node, SyncedAnimationNode);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
StringName blend_amount = PNAME("blend_amount");
|
||||||
float blend_weight = 0.0f;
|
float blend_weight = 0.0f;
|
||||||
bool sync = true;
|
bool sync = true;
|
||||||
|
|
||||||
@ -327,17 +344,6 @@ public:
|
|||||||
inputs.push_back("Input0");
|
inputs.push_back("Input0");
|
||||||
inputs.push_back("Input1");
|
inputs.push_back("Input1");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool initialize(GraphEvaluationContext &context) override {
|
|
||||||
bool result = SyncedAnimationNode::initialize(context);
|
|
||||||
|
|
||||||
if (sync) {
|
|
||||||
// TODO: do we always want looping in this case or do we traverse the graph to check what's reasonable?
|
|
||||||
node_time_info.loop_mode = Animation::LOOP_LINEAR;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
void activate_inputs(Vector<Ref<SyncedAnimationNode>> input_nodes) override {
|
void activate_inputs(Vector<Ref<SyncedAnimationNode>> input_nodes) override {
|
||||||
for (const Ref<SyncedAnimationNode> &node : input_nodes) {
|
for (const Ref<SyncedAnimationNode> &node : input_nodes) {
|
||||||
node->active = true;
|
node->active = true;
|
||||||
@ -346,28 +352,17 @@ public:
|
|||||||
node->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(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);
|
|
||||||
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);
|
||||||
|
node_time_info.length = node_time_info.sync_track.duration;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void update_time(double p_delta) override {
|
void update_time(double p_delta) override {
|
||||||
SyncedAnimationNode::update_time(p_delta);
|
SyncedAnimationNode::update_time(p_delta);
|
||||||
|
|
||||||
if (sync && !node_time_info.is_synced) {
|
if (sync && !node_time_info.is_synced) {
|
||||||
if (node_time_info.loop_mode != Animation::LOOP_NONE) {
|
|
||||||
if (node_time_info.loop_mode == Animation::LOOP_LINEAR) {
|
|
||||||
if (!Math::is_zero_approx(node_time_info.sync_track.duration)) {
|
|
||||||
node_time_info.position = Math::fposmod(static_cast<float>(node_time_info.position), node_time_info.sync_track.duration);
|
|
||||||
node_time_info.sync_position = node_time_info.sync_track.calc_sync_from_abs_time(node_time_info.position);
|
node_time_info.sync_position = node_time_info.sync_track.calc_sync_from_abs_time(node_time_info.position);
|
||||||
} else {
|
|
||||||
assert(false && !"Loop mode ping-pong not yet supported");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void evaluate(GraphEvaluationContext &context, const LocalVector<AnimationData *> &inputs, AnimationData &output) override;
|
void evaluate(GraphEvaluationContext &context, const LocalVector<AnimationData *> &inputs, AnimationData &output) override;
|
||||||
@ -386,10 +381,6 @@ protected:
|
|||||||
void _get_property_list(List<PropertyInfo> *p_list) const;
|
void _get_property_list(List<PropertyInfo> *p_list) const;
|
||||||
bool _get(const StringName &p_name, Variant &r_value) const;
|
bool _get(const StringName &p_name, Variant &r_value) const;
|
||||||
bool _set(const StringName &p_name, const Variant &p_value);
|
bool _set(const StringName &p_name, const Variant &p_value);
|
||||||
|
|
||||||
private:
|
|
||||||
StringName blend_weight_pname = PNAME("blend_amount");
|
|
||||||
StringName sync_pname = PNAME("sync");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct BlendTreeConnection {
|
struct BlendTreeConnection {
|
||||||
@ -672,14 +663,6 @@ public:
|
|||||||
return tree_graph.find_node_index_by_name(name);
|
return tree_graph.find_node_index_by_name(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ref<SyncedAnimationNode> get_node(int node_index) {
|
|
||||||
if (node_index < 0 || node_index > tree_graph.nodes.size()) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return tree_graph.nodes[node_index];
|
|
||||||
}
|
|
||||||
|
|
||||||
void add_node(const Ref<SyncedAnimationNode> &node) {
|
void add_node(const Ref<SyncedAnimationNode> &node) {
|
||||||
if (tree_initialized) {
|
if (tree_initialized) {
|
||||||
print_error("Cannot add node to BlendTree: BlendTree already initialized.");
|
print_error("Cannot add node to BlendTree: BlendTree already initialized.");
|
||||||
|
|||||||
@ -66,20 +66,16 @@ TEST_CASE("[SyncedAnimationGraph][SyncTrack] Basic") {
|
|||||||
WHEN("Blending two synctracks with weight 0.") {
|
WHEN("Blending two synctracks with weight 0.") {
|
||||||
SyncTrack blended = SyncTrack::blend(0.f, track_a, track_b);
|
SyncTrack blended = SyncTrack::blend(0.f, track_a, track_b);
|
||||||
|
|
||||||
blended.duration = track_a.duration;
|
THEN("Result must equal track_A") {
|
||||||
blended.interval_start_ratio[0] = 0.0;
|
REQUIRE(track_a == blended);
|
||||||
for (int i = 0; i < track_a.num_intervals; i++) {
|
|
||||||
CHECK(blended.interval_duration_ratio[i] == track_a.interval_duration_ratio[i]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WHEN("Blending two synctracks with weight 1.") {
|
WHEN("Blending two synctracks with weight 1.") {
|
||||||
SyncTrack blended = SyncTrack::blend(1.f, track_a, track_b);
|
SyncTrack blended = SyncTrack::blend(1.f, track_a, track_b);
|
||||||
|
|
||||||
blended.duration = track_b.duration;
|
THEN("Result must equal track_B") {
|
||||||
blended.interval_start_ratio[0] = 0.0;
|
REQUIRE(track_b == blended);
|
||||||
for (int i = 0; i < track_b.num_intervals; i++) {
|
|
||||||
CHECK(blended.interval_duration_ratio[i] == track_b.interval_duration_ratio[i]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -157,20 +153,16 @@ TEST_CASE("[SyncedAnimationGraph][SyncTrack] Sync Track blending") {
|
|||||||
WHEN("Blending two synctracks with weight 0.") {
|
WHEN("Blending two synctracks with weight 0.") {
|
||||||
SyncTrack blended = SyncTrack::blend(0.f, track_a, track_b);
|
SyncTrack blended = SyncTrack::blend(0.f, track_a, track_b);
|
||||||
|
|
||||||
blended.duration = track_a.duration;
|
THEN("Result must equal track_A") {
|
||||||
blended.interval_start_ratio[0] = 0.0;
|
REQUIRE(track_a == blended);
|
||||||
for (int i = 0; i < track_a.num_intervals; i++) {
|
|
||||||
CHECK(blended.interval_duration_ratio[i] == track_a.interval_duration_ratio[i]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WHEN("Blending two synctracks with weight 1.") {
|
WHEN("Blending two synctracks with weight 1.") {
|
||||||
SyncTrack blended = SyncTrack::blend(1.f, track_a, track_b);
|
SyncTrack blended = SyncTrack::blend(1.f, track_a, track_b);
|
||||||
|
|
||||||
blended.duration = track_b.duration;
|
THEN("Result must equal track_B") {
|
||||||
blended.interval_start_ratio[0] = 0.0;
|
REQUIRE(track_b == blended);
|
||||||
for (int i = 0; i < track_b.num_intervals; i++) {
|
|
||||||
CHECK(blended.interval_duration_ratio[i] == track_b.interval_duration_ratio[i]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,7 +173,7 @@ TEST_CASE("[SyncedAnimationGraph][SyncTrack] Sync Track blending") {
|
|||||||
REQUIRE(
|
REQUIRE(
|
||||||
blended.duration == (1.0f - weight) * track_a.duration + weight * track_b.duration);
|
blended.duration == (1.0f - weight) * track_a.duration + weight * track_b.duration);
|
||||||
REQUIRE(
|
REQUIRE(
|
||||||
blended.interval_start_ratio[0] == 0.0);
|
blended.interval_start_ratio[0] == fmodf((1.0f - weight) * (track_a.interval_start_ratio[0] + 1.0f) + weight * (track_b.interval_start_ratio[0]), 1.0f));
|
||||||
REQUIRE(
|
REQUIRE(
|
||||||
blended.interval_duration_ratio[1] == (1.0f - weight) * (track_a.interval_duration_ratio[1]) + weight * (track_b.interval_duration_ratio[1]));
|
blended.interval_duration_ratio[1] == (1.0f - weight) * (track_a.interval_duration_ratio[1]) + weight * (track_b.interval_duration_ratio[1]));
|
||||||
REQUIRE(
|
REQUIRE(
|
||||||
@ -195,7 +187,7 @@ TEST_CASE("[SyncedAnimationGraph][SyncTrack] Sync Track blending") {
|
|||||||
REQUIRE(
|
REQUIRE(
|
||||||
blended.duration == (1.0f - weight) * track_b.duration + weight * track_a.duration);
|
blended.duration == (1.0f - weight) * track_b.duration + weight * track_a.duration);
|
||||||
REQUIRE(
|
REQUIRE(
|
||||||
blended.interval_start_ratio[0] == 0.0);
|
blended.interval_start_ratio[0] == fmodf((1.0f - weight) * (track_b.interval_start_ratio[0]) + weight * (track_a.interval_start_ratio[0] + 1.0f), 1.0f));
|
||||||
REQUIRE(
|
REQUIRE(
|
||||||
blended.interval_duration_ratio[1] == (1.0f - weight) * (track_b.interval_duration_ratio[1]) + weight * (track_a.interval_duration_ratio[1]));
|
blended.interval_duration_ratio[1] == (1.0f - weight) * (track_b.interval_duration_ratio[1]) + weight * (track_a.interval_duration_ratio[1]));
|
||||||
REQUIRE(
|
REQUIRE(
|
||||||
|
|||||||
@ -15,23 +15,10 @@ struct SyncedAnimationGraphFixture {
|
|||||||
|
|
||||||
Ref<Animation> test_animation_a;
|
Ref<Animation> test_animation_a;
|
||||||
Ref<Animation> test_animation_b;
|
Ref<Animation> test_animation_b;
|
||||||
Ref<Animation> test_animation_sync_a;
|
|
||||||
Ref<Animation> test_animation_sync_b;
|
|
||||||
|
|
||||||
Ref<AnimationLibrary> animation_library;
|
Ref<AnimationLibrary> animation_library;
|
||||||
|
|
||||||
SyncedAnimationGraph *synced_animation_graph;
|
SyncedAnimationGraph *synced_animation_graph;
|
||||||
SyncedAnimationGraphFixture() {
|
SyncedAnimationGraphFixture() {
|
||||||
SyncedAnimationGraph *scene_animation_graph = dynamic_cast<SyncedAnimationGraph *>(SceneTree::get_singleton()->get_root()->find_child("SyncedAnimationGraphFixtureTestNode", true, false));
|
|
||||||
|
|
||||||
if (scene_animation_graph == nullptr) {
|
|
||||||
setup_test_scene();
|
|
||||||
}
|
|
||||||
|
|
||||||
assign_scene_variables();
|
|
||||||
}
|
|
||||||
|
|
||||||
void setup_test_scene() {
|
|
||||||
character_node = memnew(Node);
|
character_node = memnew(Node);
|
||||||
character_node->set_name("CharacterNode");
|
character_node->set_name("CharacterNode");
|
||||||
SceneTree::get_singleton()->get_root()->add_child(character_node);
|
SceneTree::get_singleton()->get_root()->add_child(character_node);
|
||||||
@ -51,7 +38,6 @@ struct SyncedAnimationGraphFixture {
|
|||||||
SceneTree::get_singleton()->get_root()->add_child(player_node);
|
SceneTree::get_singleton()->get_root()->add_child(player_node);
|
||||||
|
|
||||||
synced_animation_graph = memnew(SyncedAnimationGraph);
|
synced_animation_graph = memnew(SyncedAnimationGraph);
|
||||||
synced_animation_graph->set_name("SyncedAnimationGraphFixtureTestNode");
|
|
||||||
SceneTree::get_singleton()->get_root()->add_child(synced_animation_graph);
|
SceneTree::get_singleton()->get_root()->add_child(synced_animation_graph);
|
||||||
|
|
||||||
synced_animation_graph->set_animation_player(player_node->get_path());
|
synced_animation_graph->set_animation_player(player_node->get_path());
|
||||||
@ -65,7 +51,6 @@ struct SyncedAnimationGraphFixture {
|
|||||||
test_animation_a->track_insert_key(track_index, 0.0, Vector3(0., 0., 0.));
|
test_animation_a->track_insert_key(track_index, 0.0, Vector3(0., 0., 0.));
|
||||||
test_animation_a->track_insert_key(track_index, 1.0, Vector3(1., 2., 3.));
|
test_animation_a->track_insert_key(track_index, 1.0, Vector3(1., 2., 3.));
|
||||||
test_animation_a->track_set_path(track_index, NodePath(vformat("%s:%s", skeleton_node->get_path().get_concatenated_names(), "Hips")));
|
test_animation_a->track_set_path(track_index, NodePath(vformat("%s:%s", skeleton_node->get_path().get_concatenated_names(), "Hips")));
|
||||||
test_animation_a->set_loop_mode(Animation::LOOP_LINEAR);
|
|
||||||
|
|
||||||
animation_library.instantiate();
|
animation_library.instantiate();
|
||||||
animation_library->add_animation("TestAnimationA", test_animation_a);
|
animation_library->add_animation("TestAnimationA", test_animation_a);
|
||||||
@ -76,67 +61,11 @@ struct SyncedAnimationGraphFixture {
|
|||||||
test_animation_b->track_insert_key(track_index, 0.0, Vector3(0., 0., 0.));
|
test_animation_b->track_insert_key(track_index, 0.0, Vector3(0., 0., 0.));
|
||||||
test_animation_b->track_insert_key(track_index, 1.0, Vector3(2., 4., 6.));
|
test_animation_b->track_insert_key(track_index, 1.0, Vector3(2., 4., 6.));
|
||||||
test_animation_b->track_set_path(track_index, NodePath(vformat("%s:%s", skeleton_node->get_path().get_concatenated_names(), "Hips")));
|
test_animation_b->track_set_path(track_index, NodePath(vformat("%s:%s", skeleton_node->get_path().get_concatenated_names(), "Hips")));
|
||||||
test_animation_b->set_loop_mode(Animation::LOOP_LINEAR);
|
|
||||||
|
|
||||||
animation_library->add_animation("TestAnimationB", test_animation_b);
|
animation_library->add_animation("TestAnimationB", test_animation_b);
|
||||||
|
|
||||||
test_animation_sync_a = memnew(Animation);
|
|
||||||
track_index = test_animation_sync_a->add_track(Animation::TYPE_POSITION_3D);
|
|
||||||
CHECK(track_index == 0);
|
|
||||||
test_animation_sync_a->track_insert_key(track_index, 0.0, Vector3(0., 0., 0.));
|
|
||||||
test_animation_sync_a->track_insert_key(track_index, 0.4, Vector3(1., 2., 3.));
|
|
||||||
test_animation_sync_a->set_length(2.0);
|
|
||||||
test_animation_sync_a->track_set_path(track_index, NodePath(vformat("%s:%s", skeleton_node->get_path().get_concatenated_names(), "Hips")));
|
|
||||||
test_animation_sync_a->add_marker("0", 0.0);
|
|
||||||
test_animation_sync_a->add_marker("1", 0.4);
|
|
||||||
test_animation_sync_a->track_set_interpolation_type(track_index, Animation::INTERPOLATION_LINEAR);
|
|
||||||
test_animation_sync_a->set_loop_mode(Animation::LOOP_LINEAR);
|
|
||||||
|
|
||||||
animation_library->add_animation("TestAnimationSyncA", test_animation_sync_a);
|
|
||||||
|
|
||||||
test_animation_sync_b = memnew(Animation);
|
|
||||||
track_index = test_animation_sync_b->add_track(Animation::TYPE_POSITION_3D);
|
|
||||||
CHECK(track_index == 0);
|
|
||||||
test_animation_sync_b->track_insert_key(track_index, 0.1, Vector3(2., 4., 6.));
|
|
||||||
test_animation_sync_b->track_insert_key(track_index, 0.2, Vector3(0., 0., 0.));
|
|
||||||
test_animation_sync_b->set_length(1.0);
|
|
||||||
test_animation_sync_b->track_set_path(track_index, NodePath(vformat("%s:%s", skeleton_node->get_path().get_concatenated_names(), "Hips")));
|
|
||||||
test_animation_sync_b->add_marker("1", 0.1);
|
|
||||||
test_animation_sync_b->add_marker("0", 0.2);
|
|
||||||
test_animation_sync_b->track_set_interpolation_type(track_index, Animation::INTERPOLATION_LINEAR);
|
|
||||||
test_animation_sync_b->set_loop_mode(Animation::LOOP_LINEAR);
|
|
||||||
|
|
||||||
animation_library->add_animation("TestAnimationSyncB", test_animation_sync_b);
|
|
||||||
|
|
||||||
player_node->add_animation_library("animation_library", animation_library);
|
player_node->add_animation_library("animation_library", animation_library);
|
||||||
}
|
}
|
||||||
|
|
||||||
void assign_scene_variables() {
|
|
||||||
synced_animation_graph = dynamic_cast<SyncedAnimationGraph *>(SceneTree::get_singleton()->get_root()->find_child("SyncedAnimationGraphFixtureTestNode", true, false));
|
|
||||||
REQUIRE(synced_animation_graph);
|
|
||||||
character_node = (SceneTree::get_singleton()->get_root()->find_child("CharacterNode", true, false));
|
|
||||||
REQUIRE(character_node != nullptr);
|
|
||||||
skeleton_node = dynamic_cast<Skeleton3D *>((SceneTree::get_singleton()->get_root()->find_child("Skeleton", true, false)));
|
|
||||||
REQUIRE(skeleton_node != nullptr);
|
|
||||||
player_node = dynamic_cast<AnimationPlayer *>((SceneTree::get_singleton()->get_root()->find_child("AnimationPlayer", true, false)));
|
|
||||||
REQUIRE(player_node != nullptr);
|
|
||||||
|
|
||||||
skeleton_node->reset_bone_poses();
|
|
||||||
hip_bone_index = skeleton_node->find_bone("Hips");
|
|
||||||
REQUIRE(hip_bone_index > -1);
|
|
||||||
|
|
||||||
animation_library = player_node->get_animation_library("animation_library");
|
|
||||||
REQUIRE(animation_library.is_valid());
|
|
||||||
|
|
||||||
test_animation_a = animation_library->get_animation("TestAnimationA");
|
|
||||||
REQUIRE(test_animation_a.is_valid());
|
|
||||||
test_animation_b = animation_library->get_animation("TestAnimationB");
|
|
||||||
REQUIRE(test_animation_b.is_valid());
|
|
||||||
test_animation_sync_a = animation_library->get_animation("TestAnimationSyncA");
|
|
||||||
REQUIRE(test_animation_sync_a.is_valid());
|
|
||||||
test_animation_sync_b = animation_library->get_animation("TestAnimationSyncB");
|
|
||||||
REQUIRE(test_animation_sync_b.is_valid());
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace TestSyncedAnimationGraph {
|
namespace TestSyncedAnimationGraph {
|
||||||
@ -248,7 +177,7 @@ TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph] SyncedAnimationGraph evaluation with an AnimationSampler as root node") {
|
TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph] SyncedAnimationGraph with an AnimationSampler as root node") {
|
||||||
Ref<AnimationSamplerNode> animation_sampler_node;
|
Ref<AnimationSamplerNode> animation_sampler_node;
|
||||||
animation_sampler_node.instantiate();
|
animation_sampler_node.instantiate();
|
||||||
animation_sampler_node->animation_name = "animation_library/TestAnimationA";
|
animation_sampler_node->animation_name = "animation_library/TestAnimationA";
|
||||||
@ -270,7 +199,7 @@ TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph
|
|||||||
CHECK(hip_bone_position.z == doctest::Approx(0.03));
|
CHECK(hip_bone_position.z == doctest::Approx(0.03));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph][BlendTree] BlendTree evaluation with a AnimationSamplerNode connected to the output") {
|
TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph][BlendTree] BlendTree with a AnimationSamplerNode connected to the output") {
|
||||||
Ref<SyncedBlendTree> synced_blend_tree_node;
|
Ref<SyncedBlendTree> synced_blend_tree_node;
|
||||||
synced_blend_tree_node.instantiate();
|
synced_blend_tree_node.instantiate();
|
||||||
|
|
||||||
@ -300,7 +229,7 @@ TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph
|
|||||||
CHECK(hip_bone_position.z == doctest::Approx(0.03));
|
CHECK(hip_bone_position.z == doctest::Approx(0.03));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph][BlendTree][Blend2Node] BlendTree evaluation with a Blend2Node connected to the output") {
|
TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph][BlendTree][Blend2Node] BlendTree with a Blend2Node connected to the output") {
|
||||||
Ref<SyncedBlendTree> synced_blend_tree_node;
|
Ref<SyncedBlendTree> synced_blend_tree_node;
|
||||||
synced_blend_tree_node.instantiate();
|
synced_blend_tree_node.instantiate();
|
||||||
|
|
||||||
@ -321,9 +250,7 @@ TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph
|
|||||||
// Blend2
|
// Blend2
|
||||||
Ref<AnimationBlend2Node> blend2_node;
|
Ref<AnimationBlend2Node> blend2_node;
|
||||||
blend2_node.instantiate();
|
blend2_node.instantiate();
|
||||||
blend2_node->name = "Blend2";
|
|
||||||
blend2_node->blend_weight = 0.5;
|
blend2_node->blend_weight = 0.5;
|
||||||
blend2_node->sync = false;
|
|
||||||
|
|
||||||
synced_blend_tree_node->add_node(blend2_node);
|
synced_blend_tree_node->add_node(blend2_node);
|
||||||
|
|
||||||
@ -344,7 +271,6 @@ TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph
|
|||||||
|
|
||||||
synced_animation_graph->set_root_animation_node(synced_blend_tree_node);
|
synced_animation_graph->set_root_animation_node(synced_blend_tree_node);
|
||||||
|
|
||||||
SUBCASE("Perform default evaluation") {
|
|
||||||
Vector3 hip_bone_position = skeleton_node->get_bone_global_pose(hip_bone_index).origin;
|
Vector3 hip_bone_position = skeleton_node->get_bone_global_pose(hip_bone_index).origin;
|
||||||
|
|
||||||
CHECK(hip_bone_position.x == doctest::Approx(0.0));
|
CHECK(hip_bone_position.x == doctest::Approx(0.0));
|
||||||
@ -358,68 +284,7 @@ TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph
|
|||||||
CHECK(hip_bone_position.x == doctest::Approx(0.75));
|
CHECK(hip_bone_position.x == doctest::Approx(0.75));
|
||||||
CHECK(hip_bone_position.y == doctest::Approx(1.5));
|
CHECK(hip_bone_position.y == doctest::Approx(1.5));
|
||||||
CHECK(hip_bone_position.z == doctest::Approx(2.25));
|
CHECK(hip_bone_position.z == doctest::Approx(2.25));
|
||||||
}
|
|
||||||
|
|
||||||
SUBCASE("Evaluate tree such that animations get looped") {
|
|
||||||
Vector3 hip_bone_position = skeleton_node->get_bone_global_pose(hip_bone_index).origin;
|
|
||||||
|
|
||||||
CHECK(hip_bone_position.x == doctest::Approx(0.0));
|
|
||||||
CHECK(hip_bone_position.y == doctest::Approx(0.0));
|
|
||||||
CHECK(hip_bone_position.z == doctest::Approx(0.0));
|
|
||||||
|
|
||||||
SceneTree::get_singleton()->process(1.2);
|
|
||||||
|
|
||||||
hip_bone_position = skeleton_node->get_bone_global_pose(hip_bone_index).origin;
|
|
||||||
|
|
||||||
CHECK(hip_bone_position.x == doctest::Approx(0.3));
|
|
||||||
CHECK(hip_bone_position.y == doctest::Approx(0.6));
|
|
||||||
CHECK(hip_bone_position.z == doctest::Approx(0.9));
|
|
||||||
}
|
|
||||||
|
|
||||||
SUBCASE("Evaluate synced blend") {
|
|
||||||
animation_sampler_node_a->animation_name = "animation_library/TestAnimationSyncA";
|
|
||||||
animation_sampler_node_b->animation_name = "animation_library/TestAnimationSyncB";
|
|
||||||
blend2_node->sync = true;
|
|
||||||
synced_blend_tree_node->initialize(synced_animation_graph->get_context());
|
|
||||||
|
|
||||||
REQUIRE(synced_animation_graph->get_root_animation_node().ptr() == synced_blend_tree_node.ptr());
|
|
||||||
|
|
||||||
// By blending both animations we get a SyncTrack of duration 1.5s with the following
|
|
||||||
// intervals:
|
|
||||||
// 0: 0.825s
|
|
||||||
// 1: 0.675s
|
|
||||||
// By updating by 0s we get to the start of interval 0, an update by 0.65s to the start of interval 1, and
|
|
||||||
// another update by 0.85 to the start again to interval 0.
|
|
||||||
SceneTree::get_singleton()->process(0.);
|
|
||||||
|
|
||||||
Vector3 hip_bone_position = skeleton_node->get_bone_global_pose(hip_bone_index).origin;
|
|
||||||
|
|
||||||
CHECK(hip_bone_position.x == doctest::Approx(0.0));
|
|
||||||
CHECK(hip_bone_position.y == doctest::Approx(0.0));
|
|
||||||
CHECK(hip_bone_position.z == doctest::Approx(0.0));
|
|
||||||
|
|
||||||
// By updating again by 0.825s we get loop to the start of the 0th interval where
|
|
||||||
// both TrackValues are zero.
|
|
||||||
SceneTree::get_singleton()->process(0.825);
|
|
||||||
|
|
||||||
hip_bone_position = skeleton_node->get_bone_global_pose(hip_bone_index).origin;
|
|
||||||
|
|
||||||
CHECK(hip_bone_position.x == doctest::Approx(1.5));
|
|
||||||
CHECK(hip_bone_position.y == doctest::Approx(3.0));
|
|
||||||
CHECK(hip_bone_position.z == doctest::Approx(4.5));
|
|
||||||
|
|
||||||
// By updating again by 0.675s we loop to the start of the 0th interval where both
|
|
||||||
// TrackValues are zero.
|
|
||||||
SceneTree::get_singleton()->process(0.675);
|
|
||||||
|
|
||||||
hip_bone_position = skeleton_node->get_bone_global_pose(hip_bone_index).origin;
|
|
||||||
|
|
||||||
CHECK(hip_bone_position.x == doctest::Approx(0.0));
|
|
||||||
CHECK(hip_bone_position.y == doctest::Approx(0.0));
|
|
||||||
CHECK(hip_bone_position.z == doctest::Approx(0.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
SUBCASE("Save, load and evaluate the SyncedBlendTree") {
|
|
||||||
// Test saving and loading of the blend tree to a resource
|
// Test saving and loading of the blend tree to a resource
|
||||||
ResourceSaver::save(synced_blend_tree_node, "synced_blend_tree_node.tres");
|
ResourceSaver::save(synced_blend_tree_node, "synced_blend_tree_node.tres");
|
||||||
|
|
||||||
@ -429,23 +294,38 @@ TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph
|
|||||||
Ref<SyncedBlendTree> loaded_synced_blend_tree = ResourceLoader::load("synced_blend_tree_node.tres");
|
Ref<SyncedBlendTree> loaded_synced_blend_tree = ResourceLoader::load("synced_blend_tree_node.tres");
|
||||||
REQUIRE(loaded_synced_blend_tree.is_valid());
|
REQUIRE(loaded_synced_blend_tree.is_valid());
|
||||||
|
|
||||||
Ref<AnimationBlend2Node> loaded_blend2_node = loaded_synced_blend_tree->get_node(loaded_synced_blend_tree->find_node_index_by_name("Blend2"));
|
|
||||||
REQUIRE(loaded_blend2_node.is_valid());
|
|
||||||
CHECK(loaded_blend2_node->sync == false);
|
|
||||||
CHECK(loaded_blend2_node->blend_weight == blend2_node->blend_weight);
|
|
||||||
|
|
||||||
loaded_synced_blend_tree->initialize(synced_animation_graph->get_context());
|
loaded_synced_blend_tree->initialize(synced_animation_graph->get_context());
|
||||||
synced_animation_graph->set_root_animation_node(loaded_synced_blend_tree);
|
synced_animation_graph->set_root_animation_node(loaded_synced_blend_tree);
|
||||||
|
|
||||||
// Re-evaluate using a different time. All animation samplers will start again from 0.
|
// Re-evaluate using a different time. All animation samplers will start again from 0.
|
||||||
SceneTree::get_singleton()->process(0.2);
|
SceneTree::get_singleton()->process(0.2);
|
||||||
|
|
||||||
Vector3 hip_bone_position = skeleton_node->get_bone_global_pose(hip_bone_index).origin;
|
hip_bone_position = skeleton_node->get_bone_global_pose(hip_bone_index).origin;
|
||||||
|
|
||||||
CHECK(hip_bone_position.x == doctest::Approx(0.3));
|
CHECK(hip_bone_position.x == doctest::Approx(0.3));
|
||||||
CHECK(hip_bone_position.y == doctest::Approx(0.6));
|
CHECK(hip_bone_position.y == doctest::Approx(0.6));
|
||||||
CHECK(hip_bone_position.z == doctest::Approx(0.9));
|
CHECK(hip_bone_position.z == doctest::Approx(0.9));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph][BlendTree][Blend2Node] Serialize AnimationTree" * doctest::skip(true)) {
|
||||||
|
AnimationTree *animation_tree = memnew(AnimationTree);
|
||||||
|
|
||||||
|
character_node->add_child(animation_tree);
|
||||||
|
animation_tree->set_animation_player(player_node->get_path());
|
||||||
|
animation_tree->set_root_node(character_node->get_path());
|
||||||
|
Ref<AnimationNodeAnimation> animation_node_animation;
|
||||||
|
animation_node_animation.instantiate();
|
||||||
|
animation_node_animation->set_animation("TestAnimationA");
|
||||||
|
|
||||||
|
Ref<AnimationNodeBlendTree> animation_node_blend_tree;
|
||||||
|
animation_node_blend_tree.instantiate();
|
||||||
|
animation_node_blend_tree->add_node("SamplerTestAnimationA", animation_node_animation, Vector2(0, 0));
|
||||||
|
animation_node_blend_tree->connect_node("output", 0, "SamplerTestAnimationA");
|
||||||
|
animation_node_blend_tree->setup_local_to_scene();
|
||||||
|
|
||||||
|
animation_tree->set_root_animation_node(animation_node_blend_tree);
|
||||||
|
|
||||||
|
ResourceSaver::save(animation_node_blend_tree, "animation_tree.tres");
|
||||||
}
|
}
|
||||||
|
|
||||||
} //namespace TestSyncedAnimationGraph
|
} //namespace TestSyncedAnimationGraph
|
||||||
Loading…
x
Reference in New Issue
Block a user