Added basic connection validation and connection removal to BlendTreeResources.
parent
d95bc9fb9c
commit
91e226945c
|
@ -368,6 +368,14 @@ bool BlendTreeResource::ConnectSockets(
|
||||||
const std::string& source_socket_name,
|
const std::string& source_socket_name,
|
||||||
const AnimNodeResource* target_node,
|
const AnimNodeResource* target_node,
|
||||||
const std::string& target_socket_name) {
|
const std::string& target_socket_name) {
|
||||||
|
if (!IsConnectionValid(
|
||||||
|
source_node,
|
||||||
|
source_socket_name,
|
||||||
|
target_node,
|
||||||
|
target_socket_name)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
size_t source_node_index = GetNodeIndex(source_node);
|
size_t source_node_index = GetNodeIndex(source_node);
|
||||||
size_t target_node_index = GetNodeIndex(target_node);
|
size_t target_node_index = GetNodeIndex(target_node);
|
||||||
|
|
||||||
|
@ -427,12 +435,163 @@ bool BlendTreeResource::ConnectSockets(
|
||||||
|
|
||||||
m_node_input_connection_indices[target_node_index].emplace_back(
|
m_node_input_connection_indices[target_node_index].emplace_back(
|
||||||
m_connections.size() - 1);
|
m_connections.size() - 1);
|
||||||
m_node_output_connection_indices[source_node_index].emplace_back(
|
|
||||||
m_connections.size() - 1);
|
UpdateTreeTopologyInfo();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool BlendTreeResource::DisconnectSockets(
|
||||||
|
const AnimNodeResource* source_node,
|
||||||
|
const std::string& source_socket_name,
|
||||||
|
const AnimNodeResource* target_node,
|
||||||
|
const std::string& target_socket_name) {
|
||||||
|
int source_node_index = GetNodeIndex(source_node);
|
||||||
|
int target_node_index = GetNodeIndex(target_node);
|
||||||
|
|
||||||
|
if (source_node_index < 0 || target_node_index < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int connection_index = -1;
|
||||||
|
for (size_t i = 0, n = m_connections.size(); i < n; i++) {
|
||||||
|
if (m_connections[i]
|
||||||
|
== BlendTreeConnectionResource{
|
||||||
|
source_node_index,
|
||||||
|
source_socket_name,
|
||||||
|
target_node_index,
|
||||||
|
target_socket_name}) {
|
||||||
|
connection_index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connection_index == -1) {
|
||||||
|
std::cerr << "Error: cannot disconnect sockets as connection is not found!"
|
||||||
|
<< std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove connection
|
||||||
|
m_connections.erase(m_connections.begin() + connection_index);
|
||||||
|
|
||||||
|
// remove the input connection of the target node
|
||||||
|
std::vector<size_t>& target_input_connections =
|
||||||
|
m_node_input_connection_indices[target_node_index];
|
||||||
|
std::vector<size_t>::iterator end_iterator = std::remove(
|
||||||
|
target_input_connections.begin(),
|
||||||
|
target_input_connections.end(),
|
||||||
|
connection_index);
|
||||||
|
target_input_connections.erase(end_iterator);
|
||||||
|
|
||||||
|
// Decrement all node input connection indices that are after the connection
|
||||||
|
// we have removed above.
|
||||||
|
for (size_t node_index = 0, n = m_nodes.size(); node_index < n;
|
||||||
|
node_index++) {
|
||||||
|
std::vector<size_t>& node_input_connections =
|
||||||
|
m_node_input_connection_indices[node_index];
|
||||||
|
for (size_t& node_connection_index : node_input_connections) {
|
||||||
|
if (node_connection_index > connection_index) {
|
||||||
|
node_connection_index--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateTreeTopologyInfo();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BlendTreeResource::IsConnectionValid(
|
||||||
|
const AnimNodeResource* source_node,
|
||||||
|
const std::string& source_socket_name,
|
||||||
|
const AnimNodeResource* target_node,
|
||||||
|
const std::string& target_socket_name) const {
|
||||||
|
// Check for loops
|
||||||
|
size_t source_node_index = GetNodeIndex(source_node);
|
||||||
|
size_t target_node_index = GetNodeIndex(target_node);
|
||||||
|
|
||||||
|
if (target_node_index == source_node_index) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::find(
|
||||||
|
m_node_inputs_subtree[source_node_index].cbegin(),
|
||||||
|
m_node_inputs_subtree[source_node_index].cend(),
|
||||||
|
target_node_index)
|
||||||
|
!= m_node_inputs_subtree[source_node_index].end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BlendTreeResource::UpdateTreeTopologyInfo() {
|
||||||
|
// TODO: Updating eval order and subtrees may get slow with many nodes. An
|
||||||
|
// iterative approach would scale better. But let's leave that optimization
|
||||||
|
// for a later time.
|
||||||
|
|
||||||
|
UpdateNodeEvalOrder();
|
||||||
|
UpdateNodeSubtrees();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BlendTreeResource::UpdateNodeEvalOrderRecursive(const size_t node_index) {
|
||||||
|
const std::vector<size_t>& node_input_connection_indices =
|
||||||
|
m_node_input_connection_indices[node_index];
|
||||||
|
|
||||||
|
for (size_t i = 0, n = node_input_connection_indices.size(); i < n; i++) {
|
||||||
|
const BlendTreeConnectionResource& connection_resource =
|
||||||
|
m_connections[node_input_connection_indices[i]];
|
||||||
|
|
||||||
|
if (connection_resource.source_node_index == 1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateNodeEvalOrderRecursive(connection_resource.source_node_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node_index != 0 && node_index != 1) {
|
||||||
|
// In case we have multiple output connections from the node we here
|
||||||
|
// ensure that use the node evaluation that is the furthest away from
|
||||||
|
// the output.
|
||||||
|
std::vector<size_t>::iterator find_iter = std::find(
|
||||||
|
m_node_eval_order.begin(),
|
||||||
|
m_node_eval_order.end(),
|
||||||
|
node_index);
|
||||||
|
|
||||||
|
if (find_iter != m_node_eval_order.end()) {
|
||||||
|
m_node_eval_order.erase(find_iter);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_node_eval_order.push_back(node_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BlendTreeResource::UpdateNodeSubtrees() {
|
||||||
|
for (size_t eval_index = 0, num_eval_nodes = m_node_eval_order.size();
|
||||||
|
eval_index < num_eval_nodes;
|
||||||
|
eval_index++) {
|
||||||
|
size_t node_index = m_node_eval_order[eval_index];
|
||||||
|
m_node_inputs_subtree[node_index].clear();
|
||||||
|
|
||||||
|
const std::vector<size_t>& node_input_connection_indices =
|
||||||
|
m_node_input_connection_indices[node_index];
|
||||||
|
|
||||||
|
for (size_t i = 0, n = node_input_connection_indices.size(); i < n; i++) {
|
||||||
|
const BlendTreeConnectionResource& connection_resource =
|
||||||
|
m_connections[node_input_connection_indices[i]];
|
||||||
|
|
||||||
|
m_node_inputs_subtree[node_index].emplace_back(
|
||||||
|
connection_resource.source_node_index);
|
||||||
|
|
||||||
|
m_node_inputs_subtree[node_index].insert(
|
||||||
|
m_node_inputs_subtree[node_index].end(),
|
||||||
|
m_node_inputs_subtree[connection_resource.source_node_index].cbegin(),
|
||||||
|
m_node_inputs_subtree[connection_resource.source_node_index].cend());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool AnimGraphResource::LoadFromFile(const char* filename) {
|
bool AnimGraphResource::LoadFromFile(const char* filename) {
|
||||||
std::ifstream input_file;
|
std::ifstream input_file;
|
||||||
input_file.open(filename);
|
input_file.open(filename);
|
||||||
|
|
|
@ -25,15 +25,24 @@ static inline AnimNodeResource* AnimNodeResourceFactory(
|
||||||
const std::string& node_type_name);
|
const std::string& node_type_name);
|
||||||
|
|
||||||
struct BlendTreeConnectionResource {
|
struct BlendTreeConnectionResource {
|
||||||
size_t source_node_index = -1;
|
int source_node_index = -1;
|
||||||
std::string source_socket_name;
|
std::string source_socket_name;
|
||||||
size_t target_node_index = -1;
|
int target_node_index = -1;
|
||||||
std::string target_socket_name;
|
std::string target_socket_name;
|
||||||
|
|
||||||
|
bool operator==(const BlendTreeConnectionResource& other) const {
|
||||||
|
return (
|
||||||
|
source_node_index == other.source_node_index
|
||||||
|
&& target_node_index == other.target_node_index
|
||||||
|
&& source_socket_name == other.source_socket_name
|
||||||
|
|
||||||
|
&& target_socket_name == other.target_socket_name);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct BlendTreeResource {
|
struct BlendTreeResource {
|
||||||
std::vector<std::vector<size_t> > m_node_input_connection_indices;
|
std::vector<std::vector<size_t> > m_node_input_connection_indices;
|
||||||
std::vector<std::vector<size_t> > m_node_output_connection_indices;
|
std::vector<std::vector<size_t> > m_node_inputs_subtree;
|
||||||
|
|
||||||
~BlendTreeResource() { CleanupNodes(); }
|
~BlendTreeResource() { CleanupNodes(); }
|
||||||
|
|
||||||
|
@ -43,7 +52,7 @@ struct BlendTreeResource {
|
||||||
m_connections.clear();
|
m_connections.clear();
|
||||||
|
|
||||||
m_node_input_connection_indices.clear();
|
m_node_input_connection_indices.clear();
|
||||||
m_node_output_connection_indices.clear();
|
m_node_inputs_subtree.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CleanupNodes() {
|
void CleanupNodes() {
|
||||||
|
@ -73,20 +82,22 @@ struct BlendTreeResource {
|
||||||
return m_nodes[1];
|
return m_nodes[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t GetNodeIndex(const AnimNodeResource* node_resource) const {
|
int GetNodeIndex(const AnimNodeResource* node_resource) const {
|
||||||
for (size_t i = 0, n = m_nodes.size(); i < n; i++) {
|
for (size_t i = 0, n = m_nodes.size(); i < n; i++) {
|
||||||
if (m_nodes[i] == node_resource) {
|
if (m_nodes[i] == node_resource) {
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::cerr << "Error: could not find node index for node resource "
|
||||||
|
<< node_resource << std::endl;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[maybe_unused]] size_t AddNode(AnimNodeResource* node_resource) {
|
[[maybe_unused]] size_t AddNode(AnimNodeResource* node_resource) {
|
||||||
m_nodes.push_back(node_resource);
|
m_nodes.push_back(node_resource);
|
||||||
m_node_input_connection_indices.emplace_back();
|
m_node_input_connection_indices.emplace_back();
|
||||||
m_node_output_connection_indices.emplace_back();
|
m_node_inputs_subtree.emplace_back();
|
||||||
return m_nodes.size() - 1;
|
return m_nodes.size() - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,6 +134,25 @@ struct BlendTreeResource {
|
||||||
const AnimNodeResource* target_node,
|
const AnimNodeResource* target_node,
|
||||||
const std::string& target_socket_name);
|
const std::string& target_socket_name);
|
||||||
|
|
||||||
|
bool DisconnectSockets(
|
||||||
|
const AnimNodeResource* source_node,
|
||||||
|
const std::string& source_socket_name,
|
||||||
|
const AnimNodeResource* target_node,
|
||||||
|
const std::string& target_socket_name);
|
||||||
|
|
||||||
|
bool IsConnectionValid(
|
||||||
|
const AnimNodeResource* source_node,
|
||||||
|
const std::string& source_socket_name,
|
||||||
|
const AnimNodeResource* target_node,
|
||||||
|
const std::string& target_socket_name) const;
|
||||||
|
|
||||||
|
bool IsSocketConnected(
|
||||||
|
const AnimNodeResource* source_node,
|
||||||
|
const std::string& socket_name) {
|
||||||
|
assert(false && "Not yet implemented");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<Socket*> GetConstantNodeInputs(
|
std::vector<Socket*> GetConstantNodeInputs(
|
||||||
std::vector<NodeDescriptorBase*>& instance_node_descriptors) const {
|
std::vector<NodeDescriptorBase*>& instance_node_descriptors) const {
|
||||||
std::vector<Socket*> result;
|
std::vector<Socket*> result;
|
||||||
|
@ -147,13 +177,6 @@ struct BlendTreeResource {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsSocketConnected(
|
|
||||||
const AnimNodeResource* source_node,
|
|
||||||
const std::string& socket_name) {
|
|
||||||
assert(false && "Not yet implemented");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t GetNodeIndexForOutputSocket(const std::string& socket_name) const {
|
size_t GetNodeIndexForOutputSocket(const std::string& socket_name) const {
|
||||||
for (size_t i = 0; i < m_connections.size(); i++) {
|
for (size_t i = 0; i < m_connections.size(); i++) {
|
||||||
const BlendTreeConnectionResource& connection = m_connections[i];
|
const BlendTreeConnectionResource& connection = m_connections[i];
|
||||||
|
@ -184,9 +207,23 @@ struct BlendTreeResource {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UpdateTreeTopologyInfo();
|
||||||
|
|
||||||
|
[[nodiscard]] const std::vector<size_t>& GetNodeEvalOrder() const {
|
||||||
|
return m_node_eval_order;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void UpdateNodeEvalOrder() {
|
||||||
|
m_node_eval_order.clear();
|
||||||
|
UpdateNodeEvalOrderRecursive(0);
|
||||||
|
}
|
||||||
|
void UpdateNodeEvalOrderRecursive(size_t node_index);
|
||||||
|
void UpdateNodeSubtrees();
|
||||||
|
|
||||||
std::vector<AnimNodeResource*> m_nodes;
|
std::vector<AnimNodeResource*> m_nodes;
|
||||||
std::vector<BlendTreeConnectionResource> m_connections;
|
std::vector<BlendTreeConnectionResource> m_connections;
|
||||||
|
std::vector<size_t> m_node_eval_order;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct StateMachineTransitionResources {
|
struct StateMachineTransitionResources {
|
||||||
|
|
|
@ -623,6 +623,97 @@ TEST_CASE("AnimSamplerSpeedScaleGraph", "[AnimGraphResource]") {
|
||||||
*dynamic_cast<SpeedScaleNode*>(blend_tree.m_nodes[speed_scale_node_index])
|
*dynamic_cast<SpeedScaleNode*>(blend_tree.m_nodes[speed_scale_node_index])
|
||||||
->i_speed_scale,
|
->i_speed_scale,
|
||||||
Catch::Matchers::WithinAbs(speed_scale_value, 0.1));
|
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(
|
TEST_CASE_METHOD(
|
||||||
|
@ -1219,7 +1310,8 @@ TEST_CASE_METHOD(
|
||||||
blend_tree.StartUpdateTick();
|
blend_tree.StartUpdateTick();
|
||||||
blend_tree.MarkActiveInputs(std::vector<AnimGraphConnection>());
|
blend_tree.MarkActiveInputs(std::vector<AnimGraphConnection>());
|
||||||
THEN(
|
THEN(
|
||||||
"parent AnimSampler is active and embedded AnimSampler is inactive") {
|
"parent AnimSampler is active and embedded AnimSampler is "
|
||||||
|
"inactive") {
|
||||||
CHECK(walk_node->m_state == AnimNodeEvalState::Activated);
|
CHECK(walk_node->m_state == AnimNodeEvalState::Activated);
|
||||||
CHECK(embedded_run_node->m_state == AnimNodeEvalState::Deactivated);
|
CHECK(embedded_run_node->m_state == AnimNodeEvalState::Deactivated);
|
||||||
}
|
}
|
||||||
|
@ -1230,7 +1322,8 @@ TEST_CASE_METHOD(
|
||||||
blend_tree.StartUpdateTick();
|
blend_tree.StartUpdateTick();
|
||||||
blend_tree.MarkActiveInputs(std::vector<AnimGraphConnection>());
|
blend_tree.MarkActiveInputs(std::vector<AnimGraphConnection>());
|
||||||
THEN(
|
THEN(
|
||||||
"parent AnimSampler is inactive and embedded AnimSampler is active") {
|
"parent AnimSampler is inactive and embedded AnimSampler is "
|
||||||
|
"active") {
|
||||||
CHECK(walk_node->m_state == AnimNodeEvalState::Deactivated);
|
CHECK(walk_node->m_state == AnimNodeEvalState::Deactivated);
|
||||||
CHECK(embedded_run_node->m_state == AnimNodeEvalState::Activated);
|
CHECK(embedded_run_node->m_state == AnimNodeEvalState::Activated);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue