From 28eca48a61645159f542d08fcd1322b0d435d37d Mon Sep 17 00:00:00 2001 From: Martin Felis Date: Mon, 1 Apr 2024 17:59:25 +0200 Subject: [PATCH] Added a test that checks socket propagation into an embedded BlendTree. --- src/AnimGraph/AnimGraphBlendTree.h | 4 +- src/AnimGraph/AnimGraphResource.cc | 23 ++- tests/AnimGraphResourceTests.cc | 252 ++++++++++++++++++++++++++++- 3 files changed, 271 insertions(+), 8 deletions(-) diff --git a/src/AnimGraph/AnimGraphBlendTree.h b/src/AnimGraph/AnimGraphBlendTree.h index a964d66..62c97ec 100644 --- a/src/AnimGraph/AnimGraphBlendTree.h +++ b/src/AnimGraph/AnimGraphBlendTree.h @@ -24,8 +24,8 @@ struct AnimGraphBlendTree : public AnimNode { char* m_connection_data_storage = nullptr; char* m_const_node_inputs = nullptr; - std::vector& getGraphOutputs() { return m_node_descriptor->m_inputs; } - std::vector& getGraphInputs() { return m_node_descriptor->m_outputs; } + std::vector& GetGraphOutputs() { return m_node_descriptor->m_inputs; } + std::vector& GetGraphInputs() { return m_node_descriptor->m_outputs; } AnimDataAllocator m_anim_data_allocator; diff --git a/src/AnimGraph/AnimGraphResource.cc b/src/AnimGraph/AnimGraphResource.cc index c0651d7..b55eac7 100644 --- a/src/AnimGraph/AnimGraphResource.cc +++ b/src/AnimGraph/AnimGraphResource.cc @@ -397,8 +397,17 @@ bool BlendTreeResource::ConnectSockets( source_socket_name.c_str()); } - if (source_socket == nullptr || target_socket == nullptr) { - std::cerr << "Cannot connect nodes: could not find sockets." << std::endl; + if (source_socket == nullptr) { + std::cerr << "Cannot connect nodes: could not find source socket '" + << source_socket_name << "'." << std::endl; + } + + if (target_socket == nullptr) { + std::cerr << "Cannot connect nodes: could not find target socket '" + << target_socket_name << "'." << std::endl; + } + + if (target_socket == nullptr || source_socket == nullptr) { return false; } @@ -539,7 +548,7 @@ void AnimGraphResource::PrepareBlendTreeIOData( // graph inputs // int input_block_size = 0; - std::vector& graph_inputs = instance.getGraphInputs(); + std::vector& graph_inputs = instance.GetGraphInputs(); for (int i = 0; i < graph_inputs.size(); i++) { input_block_size += sizeof(void*); } @@ -562,7 +571,7 @@ void AnimGraphResource::PrepareBlendTreeIOData( // graph outputs // int output_block_size = 0; - std::vector& graph_outputs = instance.getGraphOutputs(); + std::vector& graph_outputs = instance.GetGraphOutputs(); for (int i = 0; i < graph_outputs.size(); i++) { output_block_size += sizeof(void*); } @@ -697,6 +706,12 @@ void AnimGraphResource::CreateBlendTreeConnectionInstances( == target_socket->m_name) { embedded_target_connection.m_source_node = source_node; embedded_target_connection.m_crosses_hierarchy = true; + + // In addition: make sure we expose the embedded socket to the + // connection in the parent blend tree. That way + // parent_tree.SetValue<>() correctly propagates the value to the + // embedded node socket. + target_socket = &embedded_target_connection.m_target_socket; } } } diff --git a/tests/AnimGraphResourceTests.cc b/tests/AnimGraphResourceTests.cc index 63c9bb3..020b2d7 100644 --- a/tests/AnimGraphResourceTests.cc +++ b/tests/AnimGraphResourceTests.cc @@ -146,7 +146,6 @@ class EmbeddedBlendTreeGraphResource { public: EmbeddedBlendTreeGraphResource() { - const char* parent_graph_filename = "TestGraphEmbeddedBlendTree.json"; parent_graph_resource.m_name = "ParentBlendTree"; parent_graph_resource.m_graph_type_name = "BlendTree"; parent_graph_resource.m_node_type_name = "BlendTree"; @@ -238,6 +237,167 @@ class EmbeddedBlendTreeGraphResource { } }; +// +// 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_socket_accessor->RegisterInput( + "Output", + nullptr); + + // Setup parent inputs + AnimNodeResource* parent_blend_tree_inputs = + parent_blend_tree_resource->GetGraphInputNode(); + parent_blend_tree_inputs->m_socket_accessor->RegisterOutput( + "EmbeddedBlend2Weight", + nullptr); + + // Parent AnimSampler + parent_blend_tree_resource->m_nodes.push_back( + AnimNodeResourceFactory("AnimSampler")); + walk_node_index = parent_blend_tree_resource->m_nodes.size() - 1; + + walk_node_resource = parent_blend_tree_resource->m_nodes[walk_node_index]; + walk_node_resource->m_name = "WalkAnim"; + walk_node_resource->m_socket_accessor->SetPropertyValue( + "Filename", + std::string("media/Walking-loop.ozz")); + + // + // Embedded Tree + // + parent_blend_tree_resource->m_nodes.push_back( + AnimNodeResourceFactory("BlendTree")); + embedded_blend_tree_node_index = + parent_blend_tree_resource->m_nodes.size() - 1; + embedded_graph = dynamic_cast( + parent_blend_tree_resource->m_nodes.back()); + 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 + AnimNodeResource* embedded_outputs = + embedded_blend_tree_resource->GetGraphOutputNode(); + embedded_outputs->m_socket_accessor->RegisterInput( + "AnimOutput", + nullptr); + + // Embedded: inputs + AnimNodeResource* embedded_inputs = + embedded_blend_tree_resource->GetGraphInputNode(); + embedded_inputs->m_socket_accessor->RegisterOutput( + "AnimInput", + nullptr); + embedded_inputs->m_socket_accessor->RegisterOutput( + "BlendWeight", + nullptr); + + // Embedded nodes + embedded_blend_tree_resource->m_nodes.push_back( + AnimNodeResourceFactory("Blend2")); + embedded_blend2_node_index = + embedded_blend_tree_resource->m_nodes.size() - 1; + + embedded_blend_tree_resource->m_nodes.push_back( + AnimNodeResourceFactory("AnimSampler")); + embedded_run_node_index = embedded_blend_tree_resource->m_nodes.size() - 1; + + // Configure node resources + AnimNodeResource* embedded_blend2_node_resource = + embedded_blend_tree_resource->m_nodes[embedded_blend2_node_index]; + embedded_blend2_node_resource->m_socket_accessor->SetInputValue( + "Weight", + 0.1f); + + embedded_run_node_resource = + embedded_blend_tree_resource->m_nodes[embedded_run_node_index]; + embedded_run_node_resource->m_name = "RunAnim"; + embedded_run_node_resource->m_socket_accessor->SetPropertyValue( + "Filename", + std::string("media/RunningSlow-loop.ozz")); + + // Embedded: setup connections + REQUIRE(embedded_blend_tree_resource->ConnectSockets( + embedded_inputs, + "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_inputs, + "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"); @@ -1055,7 +1215,7 @@ TEST_CASE_METHOD( dynamic_cast( blend_tree.m_nodes[embedded_blend_tree_node_index]); const SpeedScaleNode* speed_scale_node = dynamic_cast( - embedded_blend_tree_node->m_nodes[walk_node_index]); + embedded_blend_tree_node->m_nodes[embedded_speed_scale_index]); blend_tree.StartUpdateTick(); blend_tree.MarkActiveInputs(std::vector()); @@ -1104,5 +1264,93 @@ TEST_CASE_METHOD( 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(blend_tree.m_nodes[walk_node_index]); + AnimGraphBlendTree* embedded_blend_tree_node = + dynamic_cast( + blend_tree.m_nodes[embedded_blend_tree_node_index]); + const Blend2Node* embedded_blend2_node = dynamic_cast( + embedded_blend_tree_node->m_nodes[embedded_blend2_node_index]); + const AnimSamplerNode* embedded_run_node = dynamic_cast( + 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( + "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("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()); + 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()); + 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()); + 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(); } \ No newline at end of file