AnimTestbed/tests/AnimGraphResourceTests.cc
Martin Felis 40f631c51a Fixed memory leak by introducing virtual node descriptors.
AnimNodeResources do not reference an actual node anymore. However, we still need descriptors to check whether connections are valid.

For this we have VirtualNodeDescriptors for which all sockets point to nullptr.
2025-02-16 14:22:13 +01:00

1358 lines
46 KiB
C++

//
// Created by martin on 04.02.22.
//
#include "AnimGraph/AnimGraphBlendTree.h"
#include "AnimGraph/AnimGraphEditor.h"
#include "AnimGraph/AnimGraphNodes.h"
#include "AnimGraph/AnimGraphResource.h"
#include "catch.hpp"
#include "ozz/base/io/archive.h"
#include "ozz/base/io/stream.h"
#include "ozz/base/log.h"
class SimpleAnimSamplerGraphResource {
protected:
AnimGraphResource graph_resource;
BlendTreeResource* blend_tree_resource = nullptr;
size_t walk_node_index = -1;
AnimNodeResource* walk_node = nullptr;
public:
SimpleAnimSamplerGraphResource() {
graph_resource.m_name = "AnimSamplerBlendTree";
graph_resource.m_node_type_name = "BlendTree";
graph_resource.m_graph_type_name = "BlendTree";
blend_tree_resource = &graph_resource.m_blend_tree_resource;
blend_tree_resource->InitGraphConnectors();
// Prepare graph inputs and outputs
walk_node_index =
blend_tree_resource->AddNode(AnimNodeResourceFactory("AnimSampler"));
walk_node = blend_tree_resource->GetNode(walk_node_index);
walk_node->m_name = "WalkAnim";
walk_node->m_virtual_socket_accessor->SetPropertyValue(
"Filename",
std::string("media/Walking-loop.ozz"));
AnimNodeResource* graph_node = blend_tree_resource->GetGraphOutputNode();
graph_node->m_virtual_socket_accessor->RegisterInput<AnimData>(
"GraphOutput",
nullptr);
blend_tree_resource->ConnectSockets(
walk_node,
"Output",
blend_tree_resource->GetGraphOutputNode(),
"GraphOutput");
}
};
class Blend2GraphResource {
protected:
AnimGraphResource graph_resource;
BlendTreeResource* blend_tree_resource = nullptr;
size_t walk_node_index = -1;
size_t run_node_index = -1;
size_t blend_node_index = -1;
AnimNodeResource* walk_node = nullptr;
AnimNodeResource* run_node = nullptr;
AnimNodeResource* blend_node = nullptr;
public:
Blend2GraphResource() {
graph_resource.m_name = "WalkRunBlendGraph";
graph_resource.m_node_type_name = "BlendTree";
graph_resource.m_graph_type_name = "BlendTree";
blend_tree_resource = &graph_resource.m_blend_tree_resource;
blend_tree_resource->InitGraphConnectors();
// Prepare graph inputs and outputs
walk_node_index =
blend_tree_resource->AddNode(AnimNodeResourceFactory("AnimSampler"));
run_node_index =
blend_tree_resource->AddNode(AnimNodeResourceFactory("AnimSampler"));
blend_node_index =
blend_tree_resource->AddNode(AnimNodeResourceFactory("Blend2"));
walk_node = blend_tree_resource->GetNode(walk_node_index);
walk_node->m_name = "WalkAnim";
walk_node->m_virtual_socket_accessor->SetPropertyValue(
"Filename",
std::string("media/Walking-loop.ozz"));
run_node = blend_tree_resource->GetNode(run_node_index);
run_node->m_virtual_socket_accessor->SetPropertyValue(
"Filename",
std::string("media/Running0-loop.ozz"));
run_node->m_name = "RunAnim";
blend_node = blend_tree_resource->GetNode(blend_node_index);
blend_node->m_name = "BlendWalkRun";
AnimNodeResource* graph_node = blend_tree_resource->GetGraphOutputNode();
graph_node->m_virtual_socket_accessor->RegisterInput<AnimData>(
"GraphOutput",
nullptr);
REQUIRE(graph_node->m_virtual_socket_accessor->m_inputs.size() == 1);
REQUIRE(
blend_node->m_virtual_socket_accessor->GetInputIndex("Input0") == 0);
REQUIRE(
blend_node->m_virtual_socket_accessor->GetInputIndex("Input1") == 1);
blend_node->m_virtual_socket_accessor->SetInputValue("Weight", 0.123f);
blend_tree_resource
->ConnectSockets(walk_node, "Output", blend_node, "Input0");
blend_tree_resource
->ConnectSockets(run_node, "Output", blend_node, "Input1");
blend_tree_resource->ConnectSockets(
blend_node,
"Output",
blend_tree_resource->GetGraphOutputNode(),
"GraphOutput");
}
};
//
// Used for tests with a BlendTree within a BlendTree
//
// +-----------Parent BlendTree-------------+
// | |
// | +-----Embd Btree---+ |
// | AnmSmpl--+-\ | |
// | | \-SpdScale------+--Out |
// | |------------------+ |
// | |
// +----------------------------------------+
//
class EmbeddedBlendTreeGraphResource {
protected:
AnimGraphResource parent_graph_resource;
BlendTreeResource* parent_blend_tree_resource = nullptr;
AnimGraphResource* embedded_graph = nullptr;
BlendTreeResource* embedded_blend_tree_resource = nullptr;
size_t walk_node_index = -1;
AnimNodeResource* walk_node_resource = nullptr;
size_t embedded_blend_tree_node_index = -1;
size_t embedded_speed_scale_index = -1;
public:
EmbeddedBlendTreeGraphResource() {
parent_graph_resource.m_name = "ParentBlendTree";
parent_graph_resource.m_graph_type_name = "BlendTree";
parent_graph_resource.m_node_type_name = "BlendTree";
parent_blend_tree_resource = &parent_graph_resource.m_blend_tree_resource;
parent_blend_tree_resource->Reset();
parent_blend_tree_resource->InitGraphConnectors();
// Setup parent outputs
AnimNodeResource* parent_blend_tree_outputs =
parent_blend_tree_resource->GetGraphOutputNode();
parent_blend_tree_outputs->m_virtual_socket_accessor
->RegisterInput<AnimData>("Output", nullptr);
// Parent AnimSampler
walk_node_index = parent_blend_tree_resource->AddNode(
AnimNodeResourceFactory("AnimSampler"));
walk_node_resource = parent_blend_tree_resource->GetNode(walk_node_index);
walk_node_resource->m_name = "WalkAnim";
walk_node_resource->m_virtual_socket_accessor->SetPropertyValue(
"Filename",
std::string("media/Walking-loop.ozz"));
//
// Embedded Tree
//
embedded_blend_tree_node_index = parent_blend_tree_resource->AddNode(
AnimNodeResourceFactory("BlendTree"));
embedded_graph = dynamic_cast<AnimGraphResource*>(
parent_blend_tree_resource->GetNode(embedded_blend_tree_node_index));
embedded_graph->m_name = "EmbeddedBlendTree";
embedded_graph->m_node_type_name = "BlendTree";
embedded_graph->m_graph_type_name = "BlendTree";
embedded_blend_tree_resource = &embedded_graph->m_blend_tree_resource;
// Embedded: outputs
AnimNodeResource* embedded_outputs =
embedded_blend_tree_resource->GetGraphOutputNode();
embedded_outputs->m_virtual_socket_accessor->RegisterInput<AnimData>(
"AnimOutput",
nullptr);
// Embedded: inputs
AnimNodeResource* embedded_inputs =
embedded_blend_tree_resource->GetGraphInputNode();
embedded_inputs->m_virtual_socket_accessor->RegisterOutput<AnimData>(
"AnimInput",
nullptr);
// Embedded: SpeedScale node
embedded_speed_scale_index = embedded_blend_tree_resource->AddNode(
AnimNodeResourceFactory("SpeedScale"));
AnimNodeResource* embedded_speed_scale_resource =
embedded_blend_tree_resource->GetNode(embedded_speed_scale_index);
embedded_speed_scale_resource->m_virtual_socket_accessor->SetInputValue(
"SpeedScale",
0.1f);
// Embedded: setup connections
embedded_blend_tree_resource->ConnectSockets(
embedded_inputs,
"AnimInput",
embedded_speed_scale_resource,
"Input");
embedded_blend_tree_resource->ConnectSockets(
embedded_speed_scale_resource,
"Output",
embedded_outputs,
"AnimOutput");
// Parent: setup connections
REQUIRE(parent_blend_tree_resource->ConnectSockets(
walk_node_resource,
"Output",
embedded_graph,
"AnimInput"));
REQUIRE(parent_blend_tree_resource->ConnectSockets(
embedded_graph,
"AnimOutput",
parent_blend_tree_outputs,
"Output"));
}
};
//
// Used for tests with a BlendTree within a BlendTree
//
// +-----------Parent BlendTree-------------+
// | |
// | +-----Embd Btree---+ |
// | AnmSmpl--+-------\ | |
// | | Blend2---+--Out
// | | AnmS-/ | |
// | |------------------+ |
// | |
// +----------------------------------------+
//
class EmbeddedTreeBlend2GraphResource {
protected:
AnimGraphResource parent_graph_resource;
BlendTreeResource* parent_blend_tree_resource = nullptr;
AnimGraphResource* embedded_graph = nullptr;
BlendTreeResource* embedded_blend_tree_resource = nullptr;
size_t walk_node_index = -1;
AnimNodeResource* walk_node_resource = nullptr;
size_t embedded_blend_tree_node_index = -1;
size_t embedded_blend2_node_index = -1;
size_t embedded_run_node_index = -1;
AnimNodeResource* embedded_blend2_node_resource = nullptr;
AnimNodeResource* embedded_run_node_resource = nullptr;
public:
EmbeddedTreeBlend2GraphResource() {
parent_graph_resource.m_name = "ParentBlendTree";
parent_graph_resource.m_graph_type_name = "BlendTree";
parent_graph_resource.m_node_type_name = "BlendTree";
parent_blend_tree_resource = &parent_graph_resource.m_blend_tree_resource;
parent_blend_tree_resource->Reset();
parent_blend_tree_resource->InitGraphConnectors();
// Setup parent outputs
AnimNodeResource* parent_blend_tree_outputs =
parent_blend_tree_resource->GetGraphOutputNode();
parent_blend_tree_outputs->m_virtual_socket_accessor
->RegisterInput<AnimData>("Output", nullptr);
// Setup parent inputs
AnimNodeResource* parent_blend_tree_inputs =
parent_blend_tree_resource->GetGraphInputNode();
parent_blend_tree_inputs->m_virtual_socket_accessor->RegisterOutput<float>(
"EmbeddedBlend2Weight",
nullptr);
// Parent AnimSampler
walk_node_index = parent_blend_tree_resource->AddNode(
AnimNodeResourceFactory("AnimSampler"));
walk_node_resource = parent_blend_tree_resource->GetNode(walk_node_index);
walk_node_resource->m_name = "WalkAnim";
walk_node_resource->m_virtual_socket_accessor->SetPropertyValue(
"Filename",
std::string("media/Walking-loop.ozz"));
//
// Embedded Tree
//
embedded_blend_tree_node_index = parent_blend_tree_resource->AddNode(
AnimNodeResourceFactory("BlendTree"));
embedded_graph = dynamic_cast<AnimGraphResource*>(
parent_blend_tree_resource->GetNode(embedded_blend_tree_node_index));
embedded_graph->m_name = "EmbeddedTreeBlend2GraphResource";
embedded_graph->m_node_type_name = "BlendTree";
embedded_graph->m_graph_type_name = "BlendTree";
embedded_blend_tree_resource = &embedded_graph->m_blend_tree_resource;
// Embedded: outputs
embedded_graph->RegisterBlendTreeOutputSocket<AnimData>("AnimOutput");
// Embedded: inputs
embedded_graph->RegisterBlendTreeInputSocket<AnimData>("AnimInput");
embedded_graph->RegisterBlendTreeInputSocket<float>("BlendWeight");
// Embedded nodes
embedded_blend2_node_index = embedded_blend_tree_resource->AddNode(
AnimNodeResourceFactory("Blend2"));
embedded_run_node_index = embedded_blend_tree_resource->AddNode(
AnimNodeResourceFactory("AnimSampler"));
// Configure node resources
embedded_blend2_node_resource =
embedded_blend_tree_resource->GetNode(embedded_blend2_node_index);
embedded_blend2_node_resource->m_virtual_socket_accessor->SetInputValue(
"Weight",
0.1f);
embedded_run_node_resource =
embedded_blend_tree_resource->GetNode(embedded_run_node_index);
embedded_run_node_resource->m_name = "RunAnim";
embedded_run_node_resource->m_virtual_socket_accessor->SetPropertyValue(
"Filename",
std::string("media/RunningSlow-loop.ozz"));
// Embedded: setup connections
REQUIRE(embedded_blend_tree_resource->ConnectSockets(
embedded_blend_tree_resource->GetGraphInputNode(),
"AnimInput",
embedded_blend2_node_resource,
"Input0"));
REQUIRE(embedded_blend_tree_resource->ConnectSockets(
embedded_run_node_resource,
"Output",
embedded_blend2_node_resource,
"Input1"));
REQUIRE(embedded_blend_tree_resource->ConnectSockets(
embedded_blend2_node_resource,
"Output",
embedded_blend_tree_resource->GetGraphOutputNode(),
"AnimOutput"));
REQUIRE(embedded_blend_tree_resource->ConnectSockets(
embedded_blend_tree_resource->GetGraphInputNode(),
"BlendWeight",
embedded_blend2_node_resource,
"Weight"));
// Parent: setup connections
REQUIRE(parent_blend_tree_resource->ConnectSockets(
walk_node_resource,
"Output",
embedded_graph,
"AnimInput"));
REQUIRE(parent_blend_tree_resource->ConnectSockets(
embedded_graph,
"AnimOutput",
parent_blend_tree_outputs,
"Output"));
REQUIRE(parent_blend_tree_resource->ConnectSockets(
parent_blend_tree_inputs,
"EmbeddedBlend2Weight",
embedded_graph,
"BlendWeight"));
}
};
bool load_skeleton(ozz::animation::Skeleton& skeleton, const char* filename) {
assert(filename);
ozz::io::File file(filename, "rb");
if (!file.opened()) {
ozz::log::Err() << "Failed to open skeleton file " << filename << "."
<< std::endl;
return false;
}
ozz::io::IArchive archive(&file);
if (!archive.TestTag<ozz::animation::Skeleton>()) {
ozz::log::Err() << "Failed to load skeleton instance from file " << filename
<< "." << std::endl;
return false;
}
// Once the tag is validated, reading cannot fail.
archive >> skeleton;
return true;
}
void CheckBlendTreeResourcesEqual(
const BlendTreeResource* blend_tree_resource_reference,
const BlendTreeResource* blend_tree_resource_rhs) {
REQUIRE(
blend_tree_resource_reference->GetNumNodes()
== blend_tree_resource_rhs->GetNumNodes());
for (size_t i = 0; i < blend_tree_resource_reference->GetNumNodes(); i++) {
const AnimNodeResource* node = blend_tree_resource_reference->GetNode(i);
const AnimNodeResource* node_loaded = blend_tree_resource_rhs->GetNode(i);
REQUIRE(node->m_name == node_loaded->m_name);
REQUIRE(node->m_node_type_name == node_loaded->m_node_type_name);
}
REQUIRE(
blend_tree_resource_reference->GetNumConnections()
== blend_tree_resource_rhs->GetNumConnections());
for (size_t i = 0; i < blend_tree_resource_reference->GetNumConnections();
i++) {
const BlendTreeConnectionResource* connection =
blend_tree_resource_reference->GetConnection(i);
const BlendTreeConnectionResource* connection_loaded =
blend_tree_resource_rhs->GetConnection(i);
REQUIRE(
connection->source_node_index == connection_loaded->source_node_index);
REQUIRE(
connection->source_socket_name
== connection_loaded->source_socket_name);
REQUIRE(
connection->target_node_index == connection_loaded->target_node_index);
REQUIRE(
connection->target_socket_name
== connection_loaded->target_socket_name);
}
}
void CheckAnimGraphResourceEqual(
const AnimGraphResource& graph_resource_reference,
const AnimGraphResource& graph_resource_rhs) {
REQUIRE(
graph_resource_reference.m_graph_type_name
== graph_resource_rhs.m_graph_type_name);
REQUIRE(graph_resource_reference.m_name == graph_resource_rhs.m_name);
REQUIRE(graph_resource_reference.m_graph_type_name == "BlendTree");
const BlendTreeResource* blend_tree_resource_reference =
&graph_resource_reference.m_blend_tree_resource;
const BlendTreeResource* blend_tree_resource_rhs =
&graph_resource_rhs.m_blend_tree_resource;
CheckBlendTreeResourcesEqual(
blend_tree_resource_reference,
blend_tree_resource_rhs);
}
TEST_CASE("InputAttributeConversion", "[AnimGraphResource]") {
int node_id = 3321;
int input_index = 221;
int output_index = 125;
int parsed_node_id;
int parsed_input_index;
int parsed_output_index;
int attribute_id = GenerateInputAttributeId(node_id, input_index);
SplitInputAttributeId(attribute_id, &parsed_node_id, &parsed_input_index);
CHECK(node_id == parsed_node_id);
CHECK(input_index == parsed_input_index);
attribute_id = GenerateOutputAttributeId(node_id, output_index);
SplitOutputAttributeId(attribute_id, &parsed_node_id, &parsed_output_index);
CHECK(node_id == parsed_node_id);
CHECK(output_index == parsed_output_index);
}
TEST_CASE_METHOD(
SimpleAnimSamplerGraphResource,
"SimpleAnimSamplerGraphResource saving and loading results in same "
"resource",
"[SimpleAnimSamplerGraphResource]") {
graph_resource.SaveToFile("TestGraphAnimSamplerBlendTree.json");
AnimGraphResource graph_resource_loaded;
graph_resource_loaded.LoadFromFile("TestGraphAnimSamplerBlendTree.json");
CheckAnimGraphResourceEqual(graph_resource, graph_resource_loaded);
}
TEST_CASE_METHOD(
SimpleAnimSamplerGraphResource,
"SimpleAnimSamplerGraphResource emulated evaluation",
"[SimpleAnimSamplerGraphResource]") {
AnimGraphBlendTree anim_graph_blend_tree;
graph_resource.CreateBlendTreeInstance(anim_graph_blend_tree);
AnimGraphContext graph_context;
ozz::animation::Skeleton skeleton;
REQUIRE(load_skeleton(skeleton, "media/skeleton.ozz"));
graph_context.m_skeleton = &skeleton;
REQUIRE(anim_graph_blend_tree.Init(graph_context));
REQUIRE(anim_graph_blend_tree.m_nodes.size() == 3);
REQUIRE(
anim_graph_blend_tree.m_nodes[0]->m_node_type_name == "BlendTreeSockets");
REQUIRE(
anim_graph_blend_tree.m_nodes[1]->m_node_type_name == "BlendTreeSockets");
REQUIRE(anim_graph_blend_tree.m_nodes[2]->m_node_type_name == "AnimSampler");
// connections within the graph
AnimSamplerNode* anim_sampler_walk =
dynamic_cast<AnimSamplerNode*>(anim_graph_blend_tree.m_nodes[2]);
BlendTreeSocketNode* graph_output_node =
dynamic_cast<BlendTreeSocketNode*>(anim_graph_blend_tree.m_nodes[0]);
// check node input dependencies
size_t anim_sampler_index =
anim_graph_blend_tree.GetAnimNodeIndex(anim_sampler_walk);
REQUIRE(
anim_graph_blend_tree.m_node_output_connections[anim_sampler_index].size()
== 1);
CHECK(
anim_graph_blend_tree.m_node_output_connections[anim_sampler_index][0]
.m_target_node
== graph_output_node);
// Ensure animation sampler nodes use the correct files
REQUIRE(anim_sampler_walk->m_filename == "media/Walking-loop.ozz");
REQUIRE(anim_sampler_walk->m_animation != nullptr);
// Ensure that outputs are properly propagated.
AnimData output;
output.m_local_matrices.resize(skeleton.num_soa_joints());
anim_graph_blend_tree.SetOutput("GraphOutput", &output);
REQUIRE(anim_sampler_walk->o_output == &output);
WHEN("Emulating Graph Evaluation") {
CHECK(anim_graph_blend_tree.m_anim_data_allocator.size() == 0);
anim_sampler_walk->Evaluate(graph_context);
}
graph_context.freeAnimations();
}
//
// Checks that node const inputs are properly set.
//
TEST_CASE("AnimSamplerSpeedScaleGraph", "[AnimGraphResource]") {
AnimGraphResource graph_resource;
graph_resource.m_name = "AnimSamplerSpeedScaleGraph";
graph_resource.m_graph_type_name = "BlendTree";
BlendTreeResource& blend_tree_resource = graph_resource.m_blend_tree_resource;
blend_tree_resource.Reset();
blend_tree_resource.InitGraphConnectors();
// Prepare graph inputs and outputs
size_t walk_node_index =
blend_tree_resource.AddNode(AnimNodeResourceFactory("AnimSampler"));
size_t speed_scale_node_index =
blend_tree_resource.AddNode(AnimNodeResourceFactory("SpeedScale"));
AnimNodeResource* walk_node = blend_tree_resource.GetNode(walk_node_index);
walk_node->m_name = "WalkAnim";
walk_node->m_virtual_socket_accessor->SetPropertyValue(
"Filename",
std::string("media/Walking-loop.ozz"));
AnimNodeResource* speed_scale_node =
blend_tree_resource.GetNode(speed_scale_node_index);
speed_scale_node->m_name = "SpeedScale";
float speed_scale_value = 1.35f;
speed_scale_node->m_virtual_socket_accessor->SetInputValue(
"SpeedScale",
speed_scale_value);
AnimNodeResource* graph_node = blend_tree_resource.GetGraphOutputNode();
graph_node->m_virtual_socket_accessor->RegisterInput<AnimData>(
"GraphOutput",
nullptr);
blend_tree_resource
.ConnectSockets(walk_node, "Output", speed_scale_node, "Input");
blend_tree_resource.ConnectSockets(
speed_scale_node,
"Output",
blend_tree_resource.GetGraphOutputNode(),
"GraphOutput");
graph_resource.SaveToFile(
"TestGraphAnimSamplerSpeedScaleGraph.animgraph.json");
AnimGraphResource graph_resource_loaded;
graph_resource_loaded.LoadFromFile(
"TestGraphAnimSamplerSpeedScaleGraph.animgraph.json");
BlendTreeResource& blend_tree_resource_loaded =
graph_resource_loaded.m_blend_tree_resource;
Socket* speed_scale_resource_loaded_input =
blend_tree_resource_loaded.GetNode(speed_scale_node_index)
->m_virtual_socket_accessor->GetInputSocket("SpeedScale");
REQUIRE(speed_scale_resource_loaded_input != nullptr);
REQUIRE_THAT(
speed_scale_resource_loaded_input->m_value.float_value,
Catch::Matchers::WithinAbs(speed_scale_value, 0.1));
AnimGraphBlendTree blend_tree;
graph_resource_loaded.CreateBlendTreeInstance(blend_tree);
REQUIRE_THAT(
*dynamic_cast<SpeedScaleNode*>(blend_tree.m_nodes[speed_scale_node_index])
->i_speed_scale,
Catch::Matchers::WithinAbs(speed_scale_value, 0.1));
WHEN("Checking node eval order and node subtrees") {
const std::vector<size_t>& eval_order =
graph_resource_loaded.m_blend_tree_resource.GetNodeEvalOrder();
THEN("Walk node gets evaluated before speed scale node") {
CHECK(eval_order.size() == 2);
CHECK(eval_order[0] == walk_node_index);
CHECK(eval_order[1] == speed_scale_node_index);
}
THEN("Subtree of the speed scale node contains only the walk node") {
CHECK(
graph_resource_loaded.m_blend_tree_resource
.m_node_inputs_subtree[speed_scale_node_index]
.size()
== 1);
CHECK(
graph_resource_loaded.m_blend_tree_resource
.m_node_inputs_subtree[speed_scale_node_index][0]
== walk_node_index);
}
}
}
//
// Checks that connections additions and removals are properly validated.
//
TEST_CASE_METHOD(
Blend2GraphResource,
"Connectivity Tests",
"[AnimGraphResource][Blend2GraphResource]") {
INFO("Removing Blend2 -> Output Connection")
CHECK(
blend_tree_resource->DisconnectSockets(
blend_node,
"Output",
blend_tree_resource->GetGraphOutputNode(),
"GraphOutput")
== true);
CHECK(blend_tree_resource->GetNodeEvalOrder().empty());
INFO("Adding speed scale node");
size_t speed_scale_node_index =
blend_tree_resource->AddNode(AnimNodeResourceFactory("SpeedScale"));
AnimNodeResource* speed_scale_node_resource =
blend_tree_resource->GetNode(speed_scale_node_index);
INFO("Connecting speed scale node");
CHECK(
blend_tree_resource->ConnectSockets(
speed_scale_node_resource,
"Output",
blend_tree_resource->GetGraphOutputNode(),
"GraphOutput")
== true);
const std::vector<size_t>& tree_eval_order =
blend_tree_resource->GetNodeEvalOrder();
CHECK(tree_eval_order.size() == 1);
CHECK(tree_eval_order[0] == speed_scale_node_index);
CHECK(
blend_tree_resource->ConnectSockets(
blend_node,
"Output",
speed_scale_node_resource,
"Input")
== true);
CHECK(tree_eval_order.size() == 4);
CHECK(tree_eval_order[3] == speed_scale_node_index);
CHECK(tree_eval_order[2] == blend_node_index);
CHECK(tree_eval_order[1] == run_node_index);
CHECK(tree_eval_order[0] == walk_node_index);
INFO("Creating loop");
CHECK(blend_tree_resource->DisconnectSockets(
speed_scale_node_resource,
"Output",
blend_tree_resource->GetGraphOutputNode(),
"GraphOutput"));
CHECK(blend_tree_resource
->DisconnectSockets(walk_node, "Output", blend_node, "Input0"));
CHECK(
blend_tree_resource->IsConnectionValid(
speed_scale_node_resource,
"Output",
blend_node,
"Input0")
== false);
}
TEST_CASE_METHOD(
Blend2GraphResource,
"Blend2GraphResource saving and loading results in same resource",
"[Blend2GraphResource]") {
graph_resource.SaveToFile("TestGraphBlend2Graph.animgraph.json");
AnimGraphResource graph_resource_loaded;
graph_resource_loaded.LoadFromFile("TestGraphBlend2Graph.animgraph.json");
CheckAnimGraphResourceEqual(graph_resource, graph_resource_loaded);
BlendTreeResource* blend_tree_resource_loaded =
&graph_resource_loaded.m_blend_tree_resource;
// Check that the constant weight of the Blend2 node was properly applied when
// loading the resource.
const NodeDescriptor<Blend2Node>* blend2_node_descriptor_loaded =
dynamic_cast<NodeDescriptor<Blend2Node>*>(
blend_tree_resource_loaded->GetNode(blend_node_index)
->m_virtual_socket_accessor);
REQUIRE_THAT(
blend_node->m_virtual_socket_accessor->GetInputValue<float>("Weight"),
Catch::Matchers::WithinAbs(
blend2_node_descriptor_loaded->GetInputValue<float>("Weight"),
0.01));
}
TEST_CASE_METHOD(
Blend2GraphResource,
"Blend2GraphResource graph unsynced evaluation",
"[Blend2GraphResource]") {
AnimGraphBlendTree blend_tree_graph;
graph_resource.CreateBlendTreeInstance(blend_tree_graph);
AnimGraphContext graph_context;
ozz::animation::Skeleton skeleton;
REQUIRE(load_skeleton(skeleton, "media/skeleton.ozz"));
graph_context.m_skeleton = &skeleton;
REQUIRE(blend_tree_graph.Init(graph_context));
REQUIRE(blend_tree_graph.m_nodes.size() == 5);
REQUIRE(blend_tree_graph.m_nodes[0]->m_node_type_name == "BlendTreeSockets");
REQUIRE(blend_tree_graph.m_nodes[1]->m_node_type_name == "BlendTreeSockets");
REQUIRE(blend_tree_graph.m_nodes[2]->m_node_type_name == "AnimSampler");
REQUIRE(blend_tree_graph.m_nodes[3]->m_node_type_name == "AnimSampler");
REQUIRE(blend_tree_graph.m_nodes[4]->m_node_type_name == "Blend2");
// connections within the graph
AnimSamplerNode* anim_sampler_walk =
dynamic_cast<AnimSamplerNode*>(blend_tree_graph.m_nodes[2]);
AnimSamplerNode* anim_sampler_run =
dynamic_cast<AnimSamplerNode*>(blend_tree_graph.m_nodes[3]);
Blend2Node* blend2_instance =
dynamic_cast<Blend2Node*>(blend_tree_graph.m_nodes[4]);
// check node input dependencies
size_t anim_sampler_index0 =
blend_tree_graph.GetAnimNodeIndex(anim_sampler_walk);
size_t anim_sampler_index1 =
blend_tree_graph.GetAnimNodeIndex(anim_sampler_run);
size_t blend_index = blend_tree_graph.GetAnimNodeIndex(blend2_instance);
REQUIRE(blend_tree_graph.m_node_input_connections[blend_index].size() == 2);
CHECK(
blend_tree_graph.m_node_input_connections[blend_index][0].m_source_node
== anim_sampler_walk);
CHECK(
blend_tree_graph.m_node_input_connections[blend_index][1].m_source_node
== anim_sampler_run);
REQUIRE(
blend_tree_graph.m_node_output_connections[anim_sampler_index0].size()
== 1);
CHECK(
blend_tree_graph.m_node_output_connections[anim_sampler_index0][0]
.m_target_node
== blend2_instance);
REQUIRE(
blend_tree_graph.m_node_output_connections[anim_sampler_index1].size()
== 1);
CHECK(
blend_tree_graph.m_node_output_connections[anim_sampler_index1][0]
.m_target_node
== blend2_instance);
// Ensure animation sampler nodes use the correct files
REQUIRE(anim_sampler_walk->m_filename == "media/Walking-loop.ozz");
REQUIRE(anim_sampler_walk->m_animation != nullptr);
REQUIRE(anim_sampler_run->m_filename == "media/Running0-loop.ozz");
REQUIRE(anim_sampler_run->m_animation != nullptr);
WHEN("Emulating Graph Evaluation") {
CHECK(blend_tree_graph.m_anim_data_allocator.size() == 0);
CHECK(blend2_instance->i_input0 == anim_sampler_walk->o_output);
CHECK(blend2_instance->i_input1 == anim_sampler_run->o_output);
AnimData* graph_output = static_cast<AnimData*>(
blend_tree_graph.GetOutputPtr<AnimData>("GraphOutput"));
CHECK(
graph_output->m_local_matrices.size()
== graph_context.m_skeleton->num_soa_joints());
CHECK(blend2_instance->o_output == graph_output);
}
graph_context.freeAnimations();
}
//
// Graph:
// Outputs:
// - GraphFloatOutput (float)
// - GraphVec3Output (Vec3)
//
// Inputs:
// - GraphFloatInput (float)
//
// Nodes:
// - MathFloatToVec3Node
//
// Connectivity:
// GraphFloatInput -+----------------------> GraphFloatOutput
// \-> MathFloatToVec3 --> GraphVec3Output
//
//
TEST_CASE("ResourceSaveLoadMathGraphInputs", "[AnimGraphResource]") {
AnimGraphResource graph_resource_origin;
graph_resource_origin.m_name = "TestInputOutputGraph";
graph_resource_origin.m_graph_type_name = "BlendTree";
BlendTreeResource& blend_tree_resource =
graph_resource_origin.m_blend_tree_resource;
blend_tree_resource.Reset();
blend_tree_resource.InitGraphConnectors();
// Prepare graph inputs and outputs
size_t float_to_vec3_node_index = blend_tree_resource.AddNode(
AnimNodeResourceFactory("MathFloatToVec3Node"));
AnimNodeResource* graph_output_node =
blend_tree_resource.GetGraphOutputNode();
graph_output_node->m_virtual_socket_accessor->RegisterInput<float>(
"GraphFloatOutput",
nullptr);
graph_output_node->m_virtual_socket_accessor->RegisterInput<Vec3>(
"GraphVec3Output",
nullptr);
AnimNodeResource* graph_input_node_resource =
blend_tree_resource.GetGraphInputNode();
graph_input_node_resource->m_virtual_socket_accessor->RegisterOutput<float>(
"GraphFloatInput",
nullptr);
// Prepare graph inputs and outputs
AnimNodeResource* float_to_vec3_node_resource =
blend_tree_resource.GetNode(float_to_vec3_node_index);
REQUIRE(blend_tree_resource.ConnectSockets(
graph_input_node_resource,
"GraphFloatInput",
graph_output_node,
"GraphFloatOutput"));
REQUIRE(blend_tree_resource.ConnectSockets(
graph_input_node_resource,
"GraphFloatInput",
float_to_vec3_node_resource,
"Input0"));
REQUIRE(blend_tree_resource.ConnectSockets(
graph_input_node_resource,
"GraphFloatInput",
float_to_vec3_node_resource,
"Input1"));
REQUIRE(blend_tree_resource.ConnectSockets(
graph_input_node_resource,
"GraphFloatInput",
float_to_vec3_node_resource,
"Input2"));
REQUIRE(blend_tree_resource.ConnectSockets(
float_to_vec3_node_resource,
"Output",
graph_output_node,
"GraphVec3Output"));
WHEN("Saving and loading graph resource") {
const char* filename = "TestGraphResourceSaveLoadGraphInputs.json";
graph_resource_origin.SaveToFile(filename);
AnimGraphResource graph_resource_loaded;
graph_resource_loaded.LoadFromFile(filename);
BlendTreeResource& graph_blend_tree_loaded =
graph_resource_loaded.m_blend_tree_resource;
const AnimNodeResource* graph_loaded_output_node =
graph_blend_tree_loaded.GetGraphOutputNode();
const AnimNodeResource* graph_loaded_input_node =
graph_blend_tree_loaded.GetGraphInputNode();
THEN("Graph inputs and outputs must be in loaded resource as well.") {
REQUIRE(
graph_output_node->m_virtual_socket_accessor->m_inputs.size()
== graph_loaded_output_node->m_virtual_socket_accessor->m_inputs
.size());
REQUIRE(
graph_input_node_resource->m_virtual_socket_accessor->m_outputs.size()
== graph_loaded_input_node->m_virtual_socket_accessor->m_outputs
.size());
REQUIRE(
graph_loaded_input_node->m_virtual_socket_accessor->GetOutputSocket(
"GraphFloatInput")
!= nullptr);
REQUIRE(
graph_loaded_output_node->m_virtual_socket_accessor->GetInputSocket(
"GraphFloatOutput")
!= nullptr);
REQUIRE(
graph_loaded_output_node->m_virtual_socket_accessor->GetInputSocket(
"GraphVec3Output")
!= nullptr);
WHEN("Instantiating an AnimGraph") {
AnimGraphBlendTree blend_tree_node;
graph_resource_loaded.CreateBlendTreeInstance(blend_tree_node);
float graph_float_input = 123.456f;
blend_tree_node.SetInput("GraphFloatInput", &graph_float_input);
MathFloatToVec3Node* float_to_vec3_node =
dynamic_cast<MathFloatToVec3Node*>(
blend_tree_node.m_nodes[float_to_vec3_node_index]);
CHECK(float_to_vec3_node->i_input0 == &graph_float_input);
CHECK(float_to_vec3_node->i_input1 == &graph_float_input);
CHECK(float_to_vec3_node->i_input2 == &graph_float_input);
AND_WHEN("Evaluating Graph") {
AnimGraphContext context;
context.m_graph = nullptr;
blend_tree_node.Init(context);
// GraphFloatOutput is directly connected to GraphFloatInput therefore
// we need to get the pointer here.
float* graph_float_ptr =
blend_tree_node.GetOutputPtr<float>("GraphFloatOutput");
Vec3 graph_vec3_output;
blend_tree_node.SetOutput("GraphVec3Output", &graph_vec3_output);
blend_tree_node.UpdateTime(0.f, 0.f);
blend_tree_node.Evaluate(context);
THEN("output vector components equal the graph input values") {
CHECK(graph_float_ptr == &graph_float_input);
CHECK(graph_vec3_output.v[0] == graph_float_input);
CHECK(graph_vec3_output.v[1] == graph_float_input);
CHECK(graph_vec3_output.v[2] == graph_float_input);
}
context.freeAnimations();
}
}
}
}
}
//
// Graph:
// Input: float GraphFloatInputSingle
// Output: float GraphFloat0Output
// float GraphFloat1Output
// float GraphFloat1Output
// Nodes: 2x Add Node
// Connections:
// - Direct GraphFloatInputSingle to GraphFQloat0Output
// - Remaining wires such that
// GraphFloat1Output -> GraphFLoatInputSingle * 2
// GraphFloat1Output -> GraphFLoatInputSingle * 3
//
TEST_CASE("SimpleMathEvaluations", "[AnimGraphResource]") {
AnimGraphResource graph_resource_origin;
graph_resource_origin.m_name = "TestSimpleMathGraph";
graph_resource_origin.m_graph_type_name = "BlendTree";
BlendTreeResource& blend_tree_resource =
graph_resource_origin.m_blend_tree_resource;
blend_tree_resource.Reset();
blend_tree_resource.InitGraphConnectors();
// Prepare graph inputs and outputs
size_t math_add0_node_index =
blend_tree_resource.AddNode(AnimNodeResourceFactory("MathAddNode"));
size_t math_add1_node_index =
blend_tree_resource.AddNode(AnimNodeResourceFactory("MathAddNode"));
AnimNodeResource* graph_output_node =
blend_tree_resource.GetGraphOutputNode();
graph_output_node->m_virtual_socket_accessor->RegisterInput<float>(
"GraphFloat0Output",
nullptr);
graph_output_node->m_virtual_socket_accessor->RegisterInput<float>(
"GraphFloat1Output",
nullptr);
graph_output_node->m_virtual_socket_accessor->RegisterInput<float>(
"GraphFloat2Output",
nullptr);
AnimNodeResource* graph_input_node = blend_tree_resource.GetGraphInputNode();
graph_input_node->m_virtual_socket_accessor->RegisterOutput<float>(
"GraphFloatInput",
nullptr);
// Prepare graph inputs and outputs
AnimNodeResource* math_add0_node =
blend_tree_resource.GetNode(math_add0_node_index);
AnimNodeResource* math_add1_node =
blend_tree_resource.GetNode(math_add1_node_index);
// direct output
REQUIRE(blend_tree_resource.ConnectSockets(
graph_input_node,
"GraphFloatInput",
graph_output_node,
"GraphFloat0Output"));
// add0 node
REQUIRE(blend_tree_resource.ConnectSockets(
graph_input_node,
"GraphFloatInput",
math_add0_node,
"Input0"));
REQUIRE(blend_tree_resource.ConnectSockets(
graph_input_node,
"GraphFloatInput",
math_add0_node,
"Input1"));
REQUIRE(blend_tree_resource.ConnectSockets(
math_add0_node,
"Output",
graph_output_node,
"GraphFloat1Output"));
// add1 node
REQUIRE(blend_tree_resource.ConnectSockets(
math_add0_node,
"Output",
math_add1_node,
"Input0"));
REQUIRE(blend_tree_resource.ConnectSockets(
graph_input_node,
"GraphFloatInput",
math_add1_node,
"Input1"));
REQUIRE(blend_tree_resource.ConnectSockets(
math_add1_node,
"Output",
graph_output_node,
"GraphFloat2Output"));
WHEN("Saving and loading graph resource") {
const char* filename = "TestGraphResourceSaveLoadGraphInputs.json";
graph_resource_origin.SaveToFile(filename);
AnimGraphResource graph_resource_loaded;
graph_resource_loaded.LoadFromFile(filename);
WHEN("Instantiating an AnimGraph") {
AnimGraphBlendTree blend_tree;
graph_resource_loaded.CreateBlendTreeInstance(blend_tree);
float graph_float_input = 123.456f;
blend_tree.SetInput("GraphFloatInput", &graph_float_input);
AND_WHEN("Evaluating Graph") {
AnimGraphContext context;
context.m_graph = nullptr;
float float1_output = -1.f;
float float2_output = -1.f;
// float0 output is directly connected to the graph input, therefore
// we have to get a ptr to the input data here.
float* float0_output_ptr =
blend_tree.GetOutputPtr<float>("GraphFloat0Output");
blend_tree.SetOutput("GraphFloat1Output", &float1_output);
blend_tree.SetOutput("GraphFloat2Output", &float2_output);
blend_tree.UpdateTime(0.f, 0.f);
blend_tree.Evaluate(context);
THEN("output vector components equal the graph input vaulues") {
CHECK(float0_output_ptr == &graph_float_input);
CHECK(float1_output == Approx(graph_float_input * 2.f));
REQUIRE_THAT(
float2_output,
Catch::Matchers::WithinAbs(graph_float_input * 3.f, 10));
}
context.freeAnimations();
}
}
}
}
//
// Tests whether BlendTree within a BlendTree can be saved and loaded
//
// +-----------Parent BlendTree-------------+
// | |
// | +-----Embd Btree---+ |
// | AnmSmpl--+-\ | |
// | | \-SpdScale------+--Out |
// | |------------------+ |
// | |
// +----------------------------------------+
//
TEST_CASE_METHOD(
EmbeddedBlendTreeGraphResource,
"EmbeddedBlendTreeGraphResource saving and loading results in same "
"resource",
"[EmbeddedBlendTreeGraphResource]") {
parent_graph_resource.SaveToFile("TestGraphEmbeddedBlendTree.json");
AnimGraphResource parent_graph_resource_loaded;
parent_graph_resource_loaded.LoadFromFile("TestGraphEmbeddedBlendTree.json");
// Check the loaded parent graph
CheckAnimGraphResourceEqual(
parent_graph_resource,
parent_graph_resource_loaded);
const BlendTreeResource& parent_blend_tree_resource_loaded =
parent_graph_resource_loaded.m_blend_tree_resource;
// Check the loaded embedded graph
REQUIRE(
parent_blend_tree_resource_loaded.GetNode(3)->m_node_type_name
== "BlendTree");
const AnimGraphResource* embedded_graph_loaded =
dynamic_cast<const AnimGraphResource*>(
parent_blend_tree_resource_loaded.GetNode(3));
const BlendTreeResource* embedded_blend_tree_resource_loaded =
&embedded_graph_loaded->m_blend_tree_resource;
CheckBlendTreeResourcesEqual(
embedded_blend_tree_resource,
embedded_blend_tree_resource_loaded);
}
TEST_CASE_METHOD(
EmbeddedBlendTreeGraphResource,
"EmbeddedBlendTreeGraphResource instantiation",
"[EmbeddedBlendTreeGraphResource]") {
AnimGraphBlendTree blend_tree;
parent_graph_resource.CreateBlendTreeInstance(blend_tree);
AnimGraphContext graph_context;
ozz::animation::Skeleton skeleton;
REQUIRE(load_skeleton(skeleton, "media/skeleton.ozz"));
graph_context.m_skeleton = &skeleton;
blend_tree.Init(graph_context);
const AnimSamplerNode* walk_node =
dynamic_cast<AnimSamplerNode*>(blend_tree.m_nodes[walk_node_index]);
const AnimGraphBlendTree* embedded_blend_tree_node =
dynamic_cast<AnimGraphBlendTree*>(
blend_tree.m_nodes[embedded_blend_tree_node_index]);
const SpeedScaleNode* speed_scale_node = dynamic_cast<SpeedScaleNode*>(
embedded_blend_tree_node->m_nodes[embedded_speed_scale_index]);
blend_tree.StartUpdateTick();
blend_tree.MarkActiveInputs(std::vector<AnimGraphConnection>());
REQUIRE(embedded_blend_tree_node->m_state == AnimNodeEvalState::Activated);
REQUIRE(speed_scale_node->m_state == AnimNodeEvalState::Activated);
REQUIRE(walk_node->m_state == AnimNodeEvalState::Activated);
float time_last = 0.f;
float dt = 0.1f;
blend_tree.UpdateTime(time_last, time_last + dt);
CHECK(embedded_blend_tree_node->m_state == AnimNodeEvalState::TimeUpdated);
CHECK(speed_scale_node->m_state == AnimNodeEvalState::TimeUpdated);
CHECK(walk_node->m_state == AnimNodeEvalState::TimeUpdated);
CHECK_THAT(
walk_node->m_time_last,
Catch::Matchers::WithinAbs(time_last, 0.001));
CHECK_THAT(
walk_node->m_time_now,
Catch::Matchers::WithinAbs(
time_last + dt * (*speed_scale_node->i_speed_scale),
0.001));
blend_tree.Evaluate(graph_context);
WHEN("Updating the time a second time") {
// Perform another update
time_last = time_last + dt;
dt = 0.3f;
blend_tree.StartUpdateTick();
blend_tree.MarkActiveInputs(std::vector<AnimGraphConnection>());
blend_tree.UpdateTime(time_last, time_last + dt);
CHECK(embedded_blend_tree_node->m_state == AnimNodeEvalState::TimeUpdated);
CHECK(speed_scale_node->m_state == AnimNodeEvalState::TimeUpdated);
CHECK(walk_node->m_state == AnimNodeEvalState::TimeUpdated);
CHECK_THAT(
walk_node->m_time_last,
Catch::Matchers::WithinAbs(
time_last * (*speed_scale_node->i_speed_scale),
0.001));
CHECK_THAT(
walk_node->m_time_now,
Catch::Matchers::WithinAbs(
(time_last + dt) * (*speed_scale_node->i_speed_scale),
0.001));
}
graph_context.freeAnimations();
}
// Here we check two things:
// 1. That nodes of the parent get properly activated by an embedded node.
// 2. That output sockets of nodes in the parent graph can properly be
// connected to inputs of an embedded node.
TEST_CASE_METHOD(
EmbeddedTreeBlend2GraphResource,
"EmbeddedTreeBlend2GraphResource instantiation",
"[EmbeddedTreeBlend2GraphResource]") {
AnimGraphBlendTree blend_tree;
parent_graph_resource.CreateBlendTreeInstance(blend_tree);
AnimGraphContext graph_context;
ozz::animation::Skeleton skeleton;
REQUIRE(load_skeleton(skeleton, "media/skeleton.ozz"));
graph_context.m_skeleton = &skeleton;
blend_tree.Init(graph_context);
const AnimSamplerNode* walk_node =
dynamic_cast<AnimSamplerNode*>(blend_tree.m_nodes[walk_node_index]);
AnimGraphBlendTree* embedded_blend_tree_node =
dynamic_cast<AnimGraphBlendTree*>(
blend_tree.m_nodes[embedded_blend_tree_node_index]);
const Blend2Node* embedded_blend2_node = dynamic_cast<Blend2Node*>(
embedded_blend_tree_node->m_nodes[embedded_blend2_node_index]);
const AnimSamplerNode* embedded_run_node = dynamic_cast<AnimSamplerNode*>(
embedded_blend_tree_node->m_nodes[embedded_run_node_index]);
REQUIRE(walk_node != nullptr);
REQUIRE(embedded_blend2_node != nullptr);
REQUIRE(embedded_run_node != nullptr);
WHEN("Setting blend2 weight within EmbeddedBlendTree") {
float embedded_blend2_weight = 0.2f;
embedded_blend_tree_node->SetInput<float>(
"BlendWeight",
&embedded_blend2_weight);
CHECK_THAT(
*embedded_blend2_node->i_blend_weight,
Catch::Matchers::WithinAbs(embedded_blend2_weight, 0.001));
}
WHEN("Setting blend2 weight on the parent BlendTree") {
float parent_blend2_weight = 0.4f;
blend_tree.SetInput<float>("EmbeddedBlend2Weight", &parent_blend2_weight);
THEN(
"the embedded blend2 weights points to the address specified in the "
"parent tree.") {
CHECK(embedded_blend2_node->i_blend_weight == &parent_blend2_weight);
}
WHEN("Setting the blend weight to 0 and marking inputs as active") {
parent_blend2_weight = 0.f;
blend_tree.StartUpdateTick();
blend_tree.MarkActiveInputs(std::vector<AnimGraphConnection>());
THEN(
"parent AnimSampler is active and embedded AnimSampler is "
"inactive") {
CHECK(walk_node->m_state == AnimNodeEvalState::Activated);
CHECK(embedded_run_node->m_state == AnimNodeEvalState::Deactivated);
}
}
WHEN("Setting the blend weight to 1 and marking inputs as active") {
parent_blend2_weight = 1.f;
blend_tree.StartUpdateTick();
blend_tree.MarkActiveInputs(std::vector<AnimGraphConnection>());
THEN(
"parent AnimSampler is inactive and embedded AnimSampler is "
"active") {
CHECK(walk_node->m_state == AnimNodeEvalState::Deactivated);
CHECK(embedded_run_node->m_state == AnimNodeEvalState::Activated);
}
}
WHEN("Setting the blend weight to 0.3 and marking inputs as active") {
parent_blend2_weight = 0.3f;
blend_tree.StartUpdateTick();
blend_tree.MarkActiveInputs(std::vector<AnimGraphConnection>());
THEN("both parent AnimSampler and embedded AnimSampler are active") {
CHECK(walk_node->m_state == AnimNodeEvalState::Activated);
CHECK(embedded_run_node->m_state == AnimNodeEvalState::Activated);
}
}
}
graph_context.freeAnimations();
}
TEST_CASE(
"Register AnimGraphResource Blendtree Sockets",
"[AnimGraphResource]") {
AnimNodeResource* blend_tree_anim_node_resource =
AnimNodeResourceFactory("BlendTree");
AnimGraphResource* blend_tree_graph_resource =
dynamic_cast<AnimGraphResource*>(blend_tree_anim_node_resource);
Socket socket;
socket.m_name = "FloatSocket";
socket.m_type = SocketType::SocketTypeFloat;
socket.m_reference.ptr = nullptr;
CHECK(blend_tree_graph_resource->RegisterBlendTreeInputSocket(socket));
CHECK(!blend_tree_graph_resource->RegisterBlendTreeInputSocket(socket));
CHECK(blend_tree_graph_resource->RegisterBlendTreeOutputSocket(socket));
CHECK(!blend_tree_graph_resource->RegisterBlendTreeOutputSocket(socket));
delete blend_tree_anim_node_resource;
}