
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.
1358 lines
46 KiB
C++
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;
|
|
} |