Added support of saving and loading of embedded blend tree resources.

RefactorUnifiedBlendTreeStateMachineHandling
Martin Felis 2024-03-24 21:50:22 +01:00
parent 3a7f470acf
commit 116bf7699b
6 changed files with 483 additions and 255 deletions

View File

@ -21,7 +21,7 @@ struct AnimNode;
struct AnimNodeResource { struct AnimNodeResource {
std::string m_name; std::string m_name;
std::string m_type_name; std::string m_node_type_name;
AnimNode* m_anim_node = nullptr; AnimNode* m_anim_node = nullptr;
NodeDescriptorBase* m_socket_accessor = nullptr; NodeDescriptorBase* m_socket_accessor = nullptr;
float m_position[2] = {0.f, 0.f}; float m_position[2] = {0.f, 0.f};

View File

@ -162,7 +162,7 @@ void SkinnedMeshWidget(SkinnedMesh* skinned_mesh) {
void AnimGraphEditorRenderSidebar( void AnimGraphEditorRenderSidebar(
AnimGraphBlendTreeResource& graph_resource, AnimGraphBlendTreeResource& graph_resource,
AnimNodeResource& node_resource) { AnimNodeResource& node_resource) {
ImGui::Text("[%s]", node_resource.m_type_name.c_str()); ImGui::Text("[%s]", node_resource.m_node_type_name.c_str());
char node_name_buffer[256]; char node_name_buffer[256];
memset(node_name_buffer, 0, sizeof(node_name_buffer)); memset(node_name_buffer, 0, sizeof(node_name_buffer));
@ -307,7 +307,7 @@ void AnimGraphEditorUpdate(ax::NodeEditor::EditorContext* context) {
ImVec2(node_resource.m_position[0], node_resource.m_position[1])); ImVec2(node_resource.m_position[0], node_resource.m_position[1]));
} }
ax::NodeEditor::BeginNode(node_id); ax::NodeEditor::BeginNode(node_id);
ImGui::Text("%s", node_resource.m_type_name.c_str()); ImGui::Text("%s", node_resource.m_node_type_name.c_str());
// Inputs // Inputs
std::vector<Socket>& node_inputs = std::vector<Socket>& node_inputs =
@ -485,7 +485,7 @@ void LegacyAnimGraphEditorUpdate() {
} else if (&node_resource == &sGraphGresource.getGraphInputNode()) { } else if (&node_resource == &sGraphGresource.getGraphInputNode()) {
ImGui::TextUnformatted("Graph Inputs"); ImGui::TextUnformatted("Graph Inputs");
} else { } else {
ImGui::TextUnformatted(node_resource.m_type_name.c_str()); ImGui::TextUnformatted(node_resource.m_node_type_name.c_str());
} }
ImNodes::EndNodeTitleBar(); ImNodes::EndNodeTitleBar();

View File

@ -262,6 +262,8 @@ static inline AnimNode* AnimNodeFactory(const std::string& name) {
result = new LockTranslationNode; result = new LockTranslationNode;
} else if (name == "BlendTree") { } else if (name == "BlendTree") {
result = new BlendTreeNode; result = new BlendTreeNode;
} else if (name == "BlendTreeSockets") {
result = new BlendTreeNode;
} else if (name == "MathAddNode") { } else if (name == "MathAddNode") {
result = new MathAddNode; result = new MathAddNode;
} else if (name == "MathFloatToVec3Node") { } else if (name == "MathFloatToVec3Node") {
@ -292,6 +294,8 @@ static inline NodeDescriptorBase* AnimNodeDescriptorFactory(
return CreateNodeDescriptor<LockTranslationNode>(node); return CreateNodeDescriptor<LockTranslationNode>(node);
} else if (node_type_name == "BlendTree") { } else if (node_type_name == "BlendTree") {
return CreateNodeDescriptor<BlendTreeNode>(node); return CreateNodeDescriptor<BlendTreeNode>(node);
} else if (node_type_name == "BlendTreeSockets") {
return CreateNodeDescriptor<BlendTreeNode>(node);
} else if (node_type_name == "MathAddNode") { } else if (node_type_name == "MathAddNode") {
return CreateNodeDescriptor<MathAddNode>(node); return CreateNodeDescriptor<MathAddNode>(node);
} else if (node_type_name == "MathFloatToVec3Node") { } else if (node_type_name == "MathFloatToVec3Node") {

View File

@ -14,6 +14,10 @@
using json = nlohmann::json; using json = nlohmann::json;
// forward declarations
static json sAnimGraphResourceBlendTreeToJson(const AnimGraphResource& anim_graph_resource);
static bool sAnimGraphResourceBlendTreeFromJson(const json& json_data, AnimGraphResource* result_graph_resource);
// //
// Socket <-> json // Socket <-> json
// //
@ -125,20 +129,24 @@ Socket sJsonToSocket(const json& json_data) {
// AnimGraphNode <-> json // AnimGraphNode <-> json
// //
json sAnimGraphNodeToJson( json sAnimGraphNodeToJson(
const AnimNodeResource& node, const AnimNodeResource* node,
size_t node_index, size_t node_index,
const std::vector<BlendTreeConnectionResource>& connections) { const std::vector<BlendTreeConnectionResource>& connections) {
json result; json result;
result["name"] = node.m_name; result["name"] = node->m_name;
result["type"] = "AnimNodeResource"; result["type"] = "AnimNodeResource";
result["node_type"] = node.m_type_name; result["node_type"] = node->m_node_type_name;
for (size_t j = 0; j < 2; j++) { for (size_t j = 0; j < 2; j++) {
result["position"][j] = node.m_position[j]; result["position"][j] = node->m_position[j];
} }
for (const auto& socket : node.m_socket_accessor->m_inputs) { if (node->m_node_type_name == "BlendTree") {
}
for (const auto& socket : node->m_socket_accessor->m_inputs) {
if (socket.m_type == SocketType::SocketTypeAnimation) { if (socket.m_type == SocketType::SocketTypeAnimation) {
continue; continue;
} }
@ -157,28 +165,37 @@ json sAnimGraphNodeToJson(
} }
} }
for (auto& property : node.m_socket_accessor->m_properties) { for (auto& property : node->m_socket_accessor->m_properties) {
result["properties"][property.m_name] = sSocketToJson(property); result["properties"][property.m_name] = sSocketToJson(property);
} }
return result; return result;
} }
AnimNodeResource sAnimGraphNodeFromJson( AnimNodeResource* sAnimGraphNodeFromJson(
const json& json_node, const json& json_node,
size_t node_index) { size_t node_index) {
AnimNodeResource result;
result.m_name = json_node["name"]; std::string node_type = json_node["node_type"];
result.m_type_name = json_node["node_type"];
result.m_position[0] = json_node["position"][0];
result.m_position[1] = json_node["position"][1];
result.m_anim_node = AnimNodeFactory(result.m_type_name); if (node_type == "BlendTree") {
result.m_socket_accessor = AnimGraphResource* result = new AnimGraphResource();
AnimNodeDescriptorFactory(result.m_type_name, result.m_anim_node); sAnimGraphResourceBlendTreeFromJson(json_node, result);
return result;
}
for (auto& property : result.m_socket_accessor->m_properties) { AnimNodeResource* result;
result = new AnimNodeResource();
result->m_name = json_node["name"];
result->m_node_type_name = node_type;
result->m_position[0] = json_node["position"][0];
result->m_position[1] = json_node["position"][1];
result->m_anim_node = AnimNodeFactory(result->m_node_type_name);
result->m_socket_accessor =
AnimNodeDescriptorFactory(result->m_node_type_name, result->m_anim_node);
for (auto& property : result->m_socket_accessor->m_properties) {
property = sJsonToSocket(json_node["properties"][property.m_name]); property = sJsonToSocket(json_node["properties"][property.m_name]);
} }
@ -187,10 +204,10 @@ AnimNodeResource sAnimGraphNodeFromJson(
assert(json_node["inputs"][j].contains("name")); assert(json_node["inputs"][j].contains("name"));
std::string input_name = json_node["inputs"][j]["name"]; std::string input_name = json_node["inputs"][j]["name"];
Socket* input_socket = Socket* input_socket =
result.m_socket_accessor->GetInputSocket(input_name.c_str()); result->m_socket_accessor->GetInputSocket(input_name.c_str());
if (input_socket == nullptr) { if (input_socket == nullptr) {
std::cerr << "Could not find input socket with name " << input_name std::cerr << "Could not find input socket with name " << input_name
<< " for node type " << result.m_type_name << std::endl; << " for node type " << result->m_node_type_name << std::endl;
abort(); abort();
} }
*input_socket = sJsonToSocket(json_node["inputs"][j]); *input_socket = sJsonToSocket(json_node["inputs"][j]);
@ -230,6 +247,160 @@ BlendTreeConnectionResource sAnimGraphConnectionFromJson(
return connection; return connection;
} }
static json sAnimGraphResourceBlendTreeToJson(const AnimGraphResource& anim_graph_resource) {
json result;
result["name"] = anim_graph_resource.m_name;
result["type"] = "AnimNodeResource";
result["node_type"] = "BlendTree";
const BlendTreeResource& blend_tree_resource = anim_graph_resource.m_blend_tree_resource;
for (size_t i = 0; i < blend_tree_resource.m_nodes.size(); i++) {
const AnimNodeResource* node = blend_tree_resource.m_nodes[i];
if (node->m_node_type_name == "BlendTree") {
const AnimGraphResource* graph_resource = dynamic_cast<const AnimGraphResource*>(node);
result["nodes"][i] = sAnimGraphResourceBlendTreeToJson(*graph_resource);
} else {
result["nodes"][i] =
sAnimGraphNodeToJson(node, i, blend_tree_resource.m_connections);
}
}
for (size_t i = 0; i < blend_tree_resource.m_connections.size(); i++) {
const BlendTreeConnectionResource& connection =
blend_tree_resource.m_connections[i];
result["connections"][i] = sAnimGraphConnectionToJson(connection);
}
// Graph inputs and outputs
{
const AnimNodeResource* graph_output_node =
blend_tree_resource.m_nodes[0];
const std::vector<Socket> graph_inputs =
graph_output_node->m_socket_accessor->m_inputs;
for (size_t i = 0; i < graph_inputs.size(); i++) {
result["nodes"][0]["inputs"][i] = sSocketToJson(graph_inputs[i]);
}
const AnimNodeResource* graph_input_node = blend_tree_resource.m_nodes[1];
const std::vector<Socket> graph_outputs =
graph_input_node->m_socket_accessor->m_outputs;
for (size_t i = 0; i < graph_outputs.size(); i++) {
result["nodes"][1]["outputs"][i] = sSocketToJson(graph_outputs[i]);
}
}
return result;
}
static bool sAnimGraphResourceBlendTreeFromJson(const json& json_data, AnimGraphResource* result_graph_resource) {
BlendTreeResource& blend_tree_resource = result_graph_resource->m_blend_tree_resource;
result_graph_resource->m_graph_type_name = "BlendTree";
result_graph_resource->m_node_type_name = "BlendTree";
result_graph_resource->m_name = json_data["name"];
// Load nodes
for (size_t i = 0, n = json_data["nodes"].size(); i < n; i++) {
const json& json_node = json_data["nodes"][i];
if (json_node["type"] != "AnimNodeResource") {
std::cerr
<< "Invalid json object. Expected type 'AnimNodeResource' but got '"
<< json_node["type"] << "'." << std::endl;
return false;
}
AnimNodeResource* node = sAnimGraphNodeFromJson(json_node, i);
blend_tree_resource.m_nodes.push_back(node);
}
// Graph outputs
const json& graph_outputs = json_data["nodes"][0]["inputs"];
for (const auto& graph_output : graph_outputs) {
AnimNodeResource* graph_node = blend_tree_resource.m_nodes[0];
graph_node->m_socket_accessor->m_inputs.push_back(
sJsonToSocket(graph_output));
}
// Graph inputs (optional)
if (json_data["nodes"][1].contains("outputs")) {
const json& graph_inputs = json_data["nodes"][1]["outputs"];
for (const auto& graph_input : graph_inputs) {
AnimNodeResource* graph_node = blend_tree_resource.m_nodes[1];
graph_node->m_socket_accessor->m_outputs.push_back(
sJsonToSocket(graph_input));
}
}
// Load connections
for (const auto& json_connection : json_data["connections"]) {
if (json_connection["type"] != "AnimGraphConnectionResource") {
std::cerr << "Invalid json object. Expected type "
"'AnimGraphConnectionResource' "
"but got '"
<< json_connection["type"] << "'." << std::endl;
return false;
}
BlendTreeConnectionResource connection =
sAnimGraphConnectionFromJson(json_connection);
blend_tree_resource.m_connections.push_back(connection);
}
return true;
}
bool BlendTreeResource::ConnectSockets (
const AnimNodeResource* source_node,
const std::string& source_socket_name,
const AnimNodeResource* target_node,
const std::string& target_socket_name) {
size_t source_node_index = GetNodeIndex(source_node);
size_t target_node_index = GetNodeIndex(target_node);
if (source_node_index >= m_nodes.size()
|| target_node_index >= m_nodes.size()) {
std::cerr << "Cannot connect nodes: could not find nodes." << std::endl;
return false;
}
Socket* source_socket;
Socket* target_socket;
if (target_node->m_node_type_name == "BlendTree") {
const AnimGraphResource* target_graph_resource = dynamic_cast<const AnimGraphResource*>(target_node);
AnimNodeResource* graph_output_node = target_graph_resource->m_blend_tree_resource.GetGraphInputNode();
target_socket = graph_output_node->m_socket_accessor->GetOutputSocket(target_socket_name.c_str());
} else {
target_socket =
target_node->m_socket_accessor->GetInputSocket(target_socket_name.c_str());
}
if (source_node->m_node_type_name == "BlendTree") {
const AnimGraphResource* source_graph_resource = dynamic_cast<const AnimGraphResource*>(source_node);
AnimNodeResource* graph_output_node = source_graph_resource->m_blend_tree_resource.GetGraphOutputNode();
source_socket = graph_output_node->m_socket_accessor->GetInputSocket(source_socket_name.c_str());
} else {
source_socket =
source_node->m_socket_accessor->GetOutputSocket(source_socket_name.c_str());
}
if (source_socket == nullptr || target_socket == nullptr) {
std::cerr << "Cannot connect nodes: could not find sockets." << std::endl;
return false;
}
BlendTreeConnectionResource connection;
connection.source_node_index = source_node_index;
connection.source_socket_name = source_socket_name;
connection.target_node_index = target_node_index;
connection.target_socket_name = target_socket_name;
m_connections.push_back(connection);
return true;
}
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);
@ -253,7 +424,7 @@ bool AnimGraphResource::LoadFromFile(const char* filename) {
} }
if (json_data["node_type"] == "BlendTree") { if (json_data["node_type"] == "BlendTree") {
return LoadBlendTreeResourceFromJson(json_data); return sAnimGraphResourceBlendTreeFromJson(json_data, this);
} else if (json_data["node_type"] == "StateMachine") { } else if (json_data["node_type"] == "StateMachine") {
return LoadStateMachineResourceFromJson(json_data); return LoadStateMachineResourceFromJson(json_data);
} }
@ -265,69 +436,14 @@ bool AnimGraphResource::LoadFromFile(const char* filename) {
return false; return false;
} }
bool AnimGraphResource::LoadBlendTreeResourceFromJson(nlohmann::json const& json_data) {
m_blend_tree_resource.Reset();
m_type = "BlendTree";
m_name = json_data["name"];
// Load nodes
for (size_t i = 0, n = json_data["nodes"].size(); i < n; i++) {
const json& json_node = json_data["nodes"][i];
if (json_node["type"] != "AnimNodeResource") {
std::cerr
<< "Invalid json object. Expected type 'AnimNodeResource' but got '"
<< json_node["type"] << "'." << std::endl;
return false;
}
AnimNodeResource node = sAnimGraphNodeFromJson(json_node, i);
m_blend_tree_resource.m_nodes.push_back(node);
}
// Graph outputs
const json& graph_outputs = json_data["nodes"][0]["inputs"];
for (const auto& graph_output : graph_outputs) {
AnimNodeResource& graph_node = m_blend_tree_resource.m_nodes[0];
graph_node.m_socket_accessor->m_inputs.push_back(
sJsonToSocket(graph_output));
}
// Graph inputs (optional)
if (json_data["nodes"][1].contains("outputs")) {
const json& graph_inputs = json_data["nodes"][1]["outputs"];
for (const auto& graph_input : graph_inputs) {
AnimNodeResource& graph_node = m_blend_tree_resource.m_nodes[1];
graph_node.m_socket_accessor->m_outputs.push_back(
sJsonToSocket(graph_input));
}
}
// Load connections
for (const auto& json_connection : json_data["connections"]) {
if (json_connection["type"] != "AnimGraphConnectionResource") {
std::cerr << "Invalid json object. Expected type "
"'AnimGraphConnectionResource' "
"but got '"
<< json_connection["type"] << "'." << std::endl;
return false;
}
BlendTreeConnectionResource connection =
sAnimGraphConnectionFromJson(json_connection);
m_blend_tree_resource.m_connections.push_back(connection);
}
return true;
}
bool AnimGraphResource::SaveToFile(const char* filename) const { bool AnimGraphResource::SaveToFile(const char* filename) const {
if (m_type == "BlendTree") { if (m_graph_type_name == "BlendTree") {
return SaveBlendTreeResourceToFile(filename); return SaveBlendTreeResourceToFile(filename);
} else if (m_type == "StateMachine") { } else if (m_graph_type_name == "StateMachine") {
return SaveStateMachineResourceToFile(filename); return SaveStateMachineResourceToFile(filename);
} }
std::cerr << "Invalid AnimGraphResource type: " << m_type << "." << std::endl; std::cerr << "Invalid AnimGraphResource type: " << m_graph_type_name << "." << std::endl;
return false; return false;
} }
@ -336,39 +452,8 @@ bool AnimGraphResource::SaveBlendTreeResourceToFile(
const char* filename) const { const char* filename) const {
json result; json result;
result["name"] = m_name; result = sAnimGraphResourceBlendTreeToJson(
result["type"] = "AnimNodeResource"; *this);
result["node_type"] = "BlendTree";
for (size_t i = 0; i < m_blend_tree_resource.m_nodes.size(); i++) {
const AnimNodeResource& node = m_blend_tree_resource.m_nodes[i];
result["nodes"][i] =
sAnimGraphNodeToJson(node, i, m_blend_tree_resource.m_connections);
}
for (size_t i = 0; i < m_blend_tree_resource.m_connections.size(); i++) {
const BlendTreeConnectionResource& connection =
m_blend_tree_resource.m_connections[i];
result["connections"][i] = sAnimGraphConnectionToJson(connection);
}
// Graph inputs and outputs
{
const AnimNodeResource& graph_output_node =
m_blend_tree_resource.m_nodes[0];
const std::vector<Socket> graph_inputs =
graph_output_node.m_socket_accessor->m_inputs;
for (size_t i = 0; i < graph_inputs.size(); i++) {
result["nodes"][0]["inputs"][i] = sSocketToJson(graph_inputs[i]);
}
const AnimNodeResource& graph_input_node = m_blend_tree_resource.m_nodes[1];
const std::vector<Socket> graph_outputs =
graph_input_node.m_socket_accessor->m_outputs;
for (size_t i = 0; i < graph_outputs.size(); i++) {
result["nodes"][1]["outputs"][i] = sSocketToJson(graph_outputs[i]);
}
}
std::ofstream output_file; std::ofstream output_file;
output_file.open(filename); output_file.open(filename);
@ -380,9 +465,9 @@ bool AnimGraphResource::SaveBlendTreeResourceToFile(
void AnimGraphResource::CreateBlendTreeInstance( void AnimGraphResource::CreateBlendTreeInstance(
AnimGraphBlendTree& result) const { AnimGraphBlendTree& result) const {
if (m_type != "BlendTree") { if (m_node_type_name != "BlendTree") {
std::cerr << "Invalid AnimGraphResource. Expected type 'BlendTree' but got '" std::cerr << "Invalid AnimGraphResource. Expected type 'BlendTree' but got '"
<< m_type << "'." << std::endl; << m_graph_type_name << "'." << std::endl;
return; return;
} }
@ -396,11 +481,10 @@ void AnimGraphResource::CreateBlendTreeInstance(
void AnimGraphResource::CreateBlendTreeRuntimeNodeInstances( void AnimGraphResource::CreateBlendTreeRuntimeNodeInstances(
AnimGraphBlendTree& result) const { AnimGraphBlendTree& result) const {
for (int i = 0; i < m_blend_tree_resource.m_nodes.size(); i++) { for (auto node_resource : m_blend_tree_resource.m_nodes) {
const AnimNodeResource& node_resource = m_blend_tree_resource.m_nodes[i]; AnimNode* node = AnimNodeFactory(node_resource->m_node_type_name);
AnimNode* node = AnimNodeFactory(node_resource.m_type_name); node->m_name = node_resource->m_name;
node->m_name = node_resource.m_name; node->m_node_type_name = node_resource->m_node_type_name;
node->m_node_type_name = node_resource.m_type_name;
result.m_nodes.push_back(node); result.m_nodes.push_back(node);
// runtime node connections // runtime node connections
@ -414,9 +498,9 @@ void AnimGraphResource::PrepareBlendTreeIOData(
instance.m_node_descriptor = instance.m_node_descriptor =
AnimNodeDescriptorFactory("BlendTree", instance.m_nodes[0]); AnimNodeDescriptorFactory("BlendTree", instance.m_nodes[0]);
instance.m_node_descriptor->m_outputs = instance.m_node_descriptor->m_outputs =
m_blend_tree_resource.m_nodes[1].m_socket_accessor->m_outputs; m_blend_tree_resource.m_nodes[1]->m_socket_accessor->m_outputs;
instance.m_node_descriptor->m_inputs = instance.m_node_descriptor->m_inputs =
m_blend_tree_resource.m_nodes[0].m_socket_accessor->m_inputs; m_blend_tree_resource.m_nodes[0]->m_socket_accessor->m_inputs;
// //
// graph inputs // graph inputs
@ -466,9 +550,9 @@ void AnimGraphResource::PrepareBlendTreeIOData(
// TODO: instead of every connection, only create data blocks for the source sockets and make sure every source socket gets allocated once. // TODO: instead of every connection, only create data blocks for the source sockets and make sure every source socket gets allocated once.
size_t connection_data_storage_size = 0; size_t connection_data_storage_size = 0;
for (const auto& connection : m_blend_tree_resource.m_connections) { for (const auto& connection : m_blend_tree_resource.m_connections) {
const AnimNodeResource& source_node = const AnimNodeResource* source_node =
m_blend_tree_resource.m_nodes[connection.source_node_index]; m_blend_tree_resource.m_nodes[connection.source_node_index];
Socket* source_socket = source_node.m_socket_accessor->GetOutputSocket( Socket* source_socket = source_node->m_socket_accessor->GetOutputSocket(
connection.source_socket_name.c_str()); connection.source_socket_name.c_str());
connection_data_storage_size += source_socket->m_type_size; connection_data_storage_size += source_socket->m_type_size;
} }
@ -483,7 +567,7 @@ void AnimGraphResource::PrepareBlendTreeIOData(
nullptr); nullptr);
for (int i = 0; i < m_blend_tree_resource.m_nodes.size(); i++) { for (int i = 0; i < m_blend_tree_resource.m_nodes.size(); i++) {
instance_node_descriptors[i] = AnimNodeDescriptorFactory( instance_node_descriptors[i] = AnimNodeDescriptorFactory(
m_blend_tree_resource.m_nodes[i].m_type_name, m_blend_tree_resource.m_nodes[i]->m_node_type_name,
instance.m_nodes[i]); instance.m_nodes[i]);
} }
@ -579,13 +663,13 @@ void AnimGraphResource::PrepareBlendTreeIOData(
void AnimGraphResource::SetRuntimeNodeProperties( void AnimGraphResource::SetRuntimeNodeProperties(
AnimGraphBlendTree& result) const { AnimGraphBlendTree& result) const {
for (int i = 2; i < m_blend_tree_resource.m_nodes.size(); i++) { for (int i = 2; i < m_blend_tree_resource.m_nodes.size(); i++) {
const AnimNodeResource& node_resource = m_blend_tree_resource.m_nodes[i]; const AnimNodeResource* node_resource = m_blend_tree_resource.m_nodes[i];
NodeDescriptorBase* node_instance_accessor = NodeDescriptorBase* node_instance_accessor =
AnimNodeDescriptorFactory(node_resource.m_type_name, result.m_nodes[i]); AnimNodeDescriptorFactory(node_resource->m_node_type_name, result.m_nodes[i]);
std::vector<Socket>& resource_properties = std::vector<Socket>& resource_properties =
node_resource.m_socket_accessor->m_properties; node_resource->m_socket_accessor->m_properties;
for (const auto& property : resource_properties) { for (const auto& property : resource_properties) {
const std::string& name = property.m_name; const std::string& name = property.m_name;

View File

@ -14,22 +14,16 @@ struct AnimGraphBlendTree;
struct AnimGraphStateMachine; struct AnimGraphStateMachine;
struct AnimNodeResource { struct AnimNodeResource {
virtual ~AnimNodeResource() = default;
std::string m_name; std::string m_name;
std::string m_type_name; std::string m_node_type_name;
AnimNode* m_anim_node = nullptr; AnimNode* m_anim_node = nullptr;
NodeDescriptorBase* m_socket_accessor = nullptr; NodeDescriptorBase* m_socket_accessor = nullptr;
float m_position[2] = {0.f, 0.f}; float m_position[2] = {0.f, 0.f};
}; };
static inline AnimNodeResource AnimNodeResourceFactory( static inline AnimNodeResource* AnimNodeResourceFactory(const std::string& node_type_name);
const std::string& node_type_name) {
AnimNodeResource result;
result.m_type_name = node_type_name;
result.m_anim_node = AnimNodeFactory(node_type_name);
result.m_socket_accessor =
AnimNodeDescriptorFactory(node_type_name, result.m_anim_node);
return result;
}
struct BlendTreeConnectionResource { struct BlendTreeConnectionResource {
size_t source_node_index = -1; size_t source_node_index = -1;
@ -39,7 +33,7 @@ struct BlendTreeConnectionResource {
}; };
struct BlendTreeResource { struct BlendTreeResource {
std::vector<AnimNodeResource> m_nodes; std::vector<AnimNodeResource*> m_nodes;
std::vector<BlendTreeConnectionResource> m_connections; std::vector<BlendTreeConnectionResource> m_connections;
~BlendTreeResource() { CleanupNodes(); ~BlendTreeResource() { CleanupNodes();
@ -52,27 +46,28 @@ struct BlendTreeResource {
} }
void CleanupNodes() { void CleanupNodes() {
for (AnimNodeResource& node_resource : m_nodes) { for (AnimNodeResource* node_resource : m_nodes) {
delete node_resource.m_anim_node; delete node_resource->m_anim_node;
delete node_resource.m_socket_accessor; delete node_resource->m_socket_accessor;
delete node_resource;
} }
m_nodes.clear(); m_nodes.clear();
} }
void InitGraphConnectors() { void InitGraphConnectors() {
m_nodes.push_back(AnimNodeResourceFactory("BlendTree")); m_nodes.push_back(AnimNodeResourceFactory("BlendTreeSockets"));
m_nodes[0].m_name = "Outputs"; m_nodes[0]->m_name = "Outputs";
m_nodes.push_back(AnimNodeResourceFactory("BlendTree")); m_nodes.push_back(AnimNodeResourceFactory("BlendTreeSockets"));
m_nodes[1].m_name = "Inputs"; m_nodes[1]->m_name = "Inputs";
} }
AnimNodeResource& GetGraphOutputNode() { return m_nodes[0]; } [[nodiscard]] AnimNodeResource* GetGraphOutputNode() const { return m_nodes[0]; }
AnimNodeResource& GetGraphInputNode() { return m_nodes[1]; } [[nodiscard]] AnimNodeResource* GetGraphInputNode() const { return m_nodes[1]; }
size_t GetNodeIndex(const AnimNodeResource& node_resource) const { size_t 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;
} }
} }
@ -81,38 +76,10 @@ struct BlendTreeResource {
} }
bool ConnectSockets ( bool ConnectSockets (
const AnimNodeResource& source_node, const AnimNodeResource* source_node,
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);
size_t source_node_index = GetNodeIndex(source_node);
size_t target_node_index = GetNodeIndex(target_node);
if (source_node_index >= m_nodes.size()
|| target_node_index >= m_nodes.size()) {
std::cerr << "Cannot connect nodes: could not find nodes." << std::endl;
return false;
}
Socket* source_socket =
source_node.m_socket_accessor->GetOutputSocket(source_socket_name.c_str());
Socket* target_socket =
target_node.m_socket_accessor->GetInputSocket(target_socket_name.c_str());
if (source_socket == nullptr || target_socket == nullptr) {
std::cerr << "Cannot connect nodes: could not find sockets." << std::endl;
return false;
}
BlendTreeConnectionResource connection;
connection.source_node_index = source_node_index;
connection.source_socket_name = source_socket_name;
connection.target_node_index = target_node_index;
connection.target_socket_name = target_socket_name;
m_connections.push_back(connection);
return true;
}
std::vector<Socket*> GetConstantNodeInputs( std::vector<Socket*> GetConstantNodeInputs(
std::vector<NodeDescriptorBase*>& instance_node_descriptors) const { std::vector<NodeDescriptorBase*>& instance_node_descriptors) const {
@ -125,7 +92,7 @@ struct BlendTreeResource {
Socket& input = instance_node_descriptors[i]->m_inputs[j]; Socket& input = instance_node_descriptors[i]->m_inputs[j];
if (*input.m_reference.ptr_ptr == nullptr) { if (*input.m_reference.ptr_ptr == nullptr) {
memcpy(&input.m_value, &m_nodes[i].m_socket_accessor->m_inputs[j].m_value, sizeof(Socket::SocketValue)); memcpy(&input.m_value, &m_nodes[i]->m_socket_accessor->m_inputs[j].m_value, sizeof(Socket::SocketValue));
result.push_back(&input); result.push_back(&input);
} }
} }
@ -147,8 +114,8 @@ struct StateMachineResource {
std::vector<StateMachineTransitionResources> m_transitions; std::vector<StateMachineTransitionResources> m_transitions;
}; };
struct AnimGraphResource { struct AnimGraphResource: AnimNodeResource {
std::string m_type; std::string m_graph_type_name;
std::string m_name; std::string m_name;
BlendTreeResource m_blend_tree_resource; BlendTreeResource m_blend_tree_resource;
@ -163,7 +130,6 @@ struct AnimGraphResource {
private: private:
// BlendTree // BlendTree
bool SaveBlendTreeResourceToFile(const char* filename) const; bool SaveBlendTreeResourceToFile(const char* filename) const;
bool LoadBlendTreeResourceFromJson(nlohmann::json const& json_data);
void CreateBlendTreeRuntimeNodeInstances(AnimGraphBlendTree& result) const; void CreateBlendTreeRuntimeNodeInstances(AnimGraphBlendTree& result) const;
void PrepareBlendTreeIOData(AnimGraphBlendTree& instance) const; void PrepareBlendTreeIOData(AnimGraphBlendTree& instance) const;
void SetRuntimeNodeProperties(AnimGraphBlendTree& result) const; void SetRuntimeNodeProperties(AnimGraphBlendTree& result) const;
@ -172,4 +138,30 @@ struct AnimGraphResource {
bool LoadStateMachineResourceFromJson(nlohmann::json const& json_data); bool LoadStateMachineResourceFromJson(nlohmann::json const& json_data);
}; };
static inline AnimNodeResource* AnimNodeResourceFactory(
const std::string& node_type_name) {
AnimNodeResource* result;
if (node_type_name == "BlendTree") {
AnimGraphResource* blend_tree_resource = new AnimGraphResource();
blend_tree_resource->m_blend_tree_resource.InitGraphConnectors();
result = blend_tree_resource;
} else {
result = new AnimNodeResource();
}
result->m_node_type_name = node_type_name;
if (node_type_name == "BlendTreeSockets") {
result->m_anim_node = AnimNodeFactory("BlendTree");
result->m_socket_accessor = new NodeDescriptorBase();
} else {
result->m_anim_node = AnimNodeFactory(node_type_name);
result->m_socket_accessor =
AnimNodeDescriptorFactory(node_type_name, result->m_anim_node);
}
return result;
}
#endif //ANIMTESTBED_ANIMGRAPHRESOURCE_H #endif //ANIMTESTBED_ANIMGRAPHRESOURCE_H

View File

@ -55,24 +55,23 @@ TEST_CASE("InputAttributeConversion", "[AnimGraphResource]") {
TEST_CASE("AnimSamplerGraph", "[AnimGraphResource]") { TEST_CASE("AnimSamplerGraph", "[AnimGraphResource]") {
AnimGraphResource graph_resource; AnimGraphResource graph_resource;
graph_resource.m_name = "AnimSamplerBlendTree"; graph_resource.m_name = "AnimSamplerBlendTree";
graph_resource.m_type = "BlendTree"; graph_resource.m_graph_type_name = "BlendTree";
BlendTreeResource& blend_tree_resource = graph_resource.m_blend_tree_resource; BlendTreeResource& blend_tree_resource = graph_resource.m_blend_tree_resource;
blend_tree_resource.Reset();
blend_tree_resource.InitGraphConnectors(); blend_tree_resource.InitGraphConnectors();
// Prepare graph inputs and outputs // Prepare graph inputs and outputs
blend_tree_resource.m_nodes.push_back(AnimNodeResourceFactory("AnimSampler")); blend_tree_resource.m_nodes.push_back(AnimNodeResourceFactory("AnimSampler"));
size_t walk_node_index = blend_tree_resource.m_nodes.size() - 1; size_t walk_node_index = blend_tree_resource.m_nodes.size() - 1;
AnimNodeResource& walk_node = blend_tree_resource.m_nodes[walk_node_index]; AnimNodeResource* walk_node = blend_tree_resource.m_nodes[walk_node_index];
walk_node.m_name = "WalkAnim"; walk_node->m_name = "WalkAnim";
walk_node.m_socket_accessor->SetPropertyValue( walk_node->m_socket_accessor->SetPropertyValue(
"Filename", "Filename",
std::string("media/Walking-loop.ozz")); std::string("media/Walking-loop.ozz"));
AnimNodeResource& graph_node = blend_tree_resource.m_nodes[0]; AnimNodeResource* graph_node = blend_tree_resource.m_nodes[0];
graph_node.m_socket_accessor->RegisterInput<AnimData>("GraphOutput", nullptr); graph_node->m_socket_accessor->RegisterInput<AnimData>("GraphOutput", nullptr);
blend_tree_resource.ConnectSockets( blend_tree_resource.ConnectSockets(
walk_node, walk_node,
@ -80,10 +79,10 @@ TEST_CASE("AnimSamplerGraph", "[AnimGraphResource]") {
blend_tree_resource.GetGraphOutputNode(), blend_tree_resource.GetGraphOutputNode(),
"GraphOutput"); "GraphOutput");
graph_resource.SaveToFile("AnimSamplerBlendTree.json"); graph_resource.SaveToFile("TestGraphAnimSamplerBlendTree.json");
AnimGraphResource graph_resource_loaded; AnimGraphResource graph_resource_loaded;
graph_resource_loaded.LoadFromFile("AnimSamplerBlendTree.json"); graph_resource_loaded.LoadFromFile("TestGraphAnimSamplerBlendTree.json");
AnimGraphBlendTree anim_graph_blend_tree; AnimGraphBlendTree anim_graph_blend_tree;
graph_resource_loaded.CreateBlendTreeInstance(anim_graph_blend_tree); graph_resource_loaded.CreateBlendTreeInstance(anim_graph_blend_tree);
@ -96,8 +95,8 @@ TEST_CASE("AnimSamplerGraph", "[AnimGraphResource]") {
REQUIRE(anim_graph_blend_tree.Init(graph_context)); REQUIRE(anim_graph_blend_tree.Init(graph_context));
REQUIRE(anim_graph_blend_tree.m_nodes.size() == 3); REQUIRE(anim_graph_blend_tree.m_nodes.size() == 3);
REQUIRE(anim_graph_blend_tree.m_nodes[0]->m_node_type_name == "BlendTree"); 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 == "BlendTree"); 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"); REQUIRE(anim_graph_blend_tree.m_nodes[2]->m_node_type_name == "AnimSampler");
// connections within the graph // connections within the graph
@ -139,7 +138,7 @@ TEST_CASE("AnimSamplerGraph", "[AnimGraphResource]") {
TEST_CASE("AnimSamplerSpeedScaleGraph", "[AnimGraphResource]") { TEST_CASE("AnimSamplerSpeedScaleGraph", "[AnimGraphResource]") {
AnimGraphResource graph_resource; AnimGraphResource graph_resource;
graph_resource.m_name = "AnimSamplerSpeedScaleGraph"; graph_resource.m_name = "AnimSamplerSpeedScaleGraph";
graph_resource.m_type = "BlendTree"; graph_resource.m_graph_type_name = "BlendTree";
BlendTreeResource& blend_tree_resource = graph_resource.m_blend_tree_resource; BlendTreeResource& blend_tree_resource = graph_resource.m_blend_tree_resource;
blend_tree_resource.Reset(); blend_tree_resource.Reset();
@ -152,22 +151,22 @@ TEST_CASE("AnimSamplerSpeedScaleGraph", "[AnimGraphResource]") {
blend_tree_resource.m_nodes.push_back(AnimNodeResourceFactory("SpeedScale")); blend_tree_resource.m_nodes.push_back(AnimNodeResourceFactory("SpeedScale"));
size_t speed_scale_node_index = blend_tree_resource.m_nodes.size() - 1; size_t speed_scale_node_index = blend_tree_resource.m_nodes.size() - 1;
AnimNodeResource& walk_node = blend_tree_resource.m_nodes[walk_node_index]; AnimNodeResource* walk_node = blend_tree_resource.m_nodes[walk_node_index];
walk_node.m_name = "WalkAnim"; walk_node->m_name = "WalkAnim";
walk_node.m_socket_accessor->SetPropertyValue( walk_node->m_socket_accessor->SetPropertyValue(
"Filename", "Filename",
std::string("media/Walking-loop.ozz")); std::string("media/Walking-loop.ozz"));
AnimNodeResource& speed_scale_node = AnimNodeResource* speed_scale_node =
blend_tree_resource.m_nodes[speed_scale_node_index]; blend_tree_resource.m_nodes[speed_scale_node_index];
speed_scale_node.m_name = "SpeedScale"; speed_scale_node->m_name = "SpeedScale";
float speed_scale_value = 1.35f; float speed_scale_value = 1.35f;
speed_scale_node.m_socket_accessor->SetInputValue( speed_scale_node->m_socket_accessor->SetInputValue(
"SpeedScale", "SpeedScale",
speed_scale_value); speed_scale_value);
AnimNodeResource& graph_node = blend_tree_resource.m_nodes[0]; AnimNodeResource* graph_node = blend_tree_resource.GetGraphOutputNode();
graph_node.m_socket_accessor->RegisterInput<AnimData>("GraphOutput", nullptr); graph_node->m_socket_accessor->RegisterInput<AnimData>("GraphOutput", nullptr);
blend_tree_resource.ConnectSockets(walk_node, "Output", speed_scale_node, "Input"); blend_tree_resource.ConnectSockets(walk_node, "Output", speed_scale_node, "Input");
@ -177,16 +176,15 @@ TEST_CASE("AnimSamplerSpeedScaleGraph", "[AnimGraphResource]") {
blend_tree_resource.GetGraphOutputNode(), blend_tree_resource.GetGraphOutputNode(),
"GraphOutput"); "GraphOutput");
graph_resource.SaveToFile("AnimSamplerSpeedScaleGraph.animgraph.json"); graph_resource.SaveToFile("TestGraphAnimSamplerSpeedScaleGraph.animgraph.json");
AnimGraphResource graph_resource_loaded; AnimGraphResource graph_resource_loaded;
graph_resource_loaded.LoadFromFile( graph_resource_loaded.LoadFromFile(
"AnimSamplerSpeedScaleGraph.animgraph.json"); "TestGraphAnimSamplerSpeedScaleGraph.animgraph.json");
BlendTreeResource& blend_tree_resource_loaded = graph_resource_loaded.m_blend_tree_resource; BlendTreeResource& blend_tree_resource_loaded = graph_resource_loaded.m_blend_tree_resource;
Socket* speed_scale_resource_loaded_input = Socket* speed_scale_resource_loaded_input =
blend_tree_resource_loaded.m_nodes[speed_scale_node_index] blend_tree_resource_loaded.m_nodes[speed_scale_node_index]->m_socket_accessor->GetInputSocket("SpeedScale");
.m_socket_accessor->GetInputSocket("SpeedScale");
REQUIRE(speed_scale_resource_loaded_input != nullptr); REQUIRE(speed_scale_resource_loaded_input != nullptr);
REQUIRE_THAT( REQUIRE_THAT(
@ -203,7 +201,7 @@ TEST_CASE("AnimSamplerSpeedScaleGraph", "[AnimGraphResource]") {
TEST_CASE("Blend2Graph", "[AnimGraphResource]") { TEST_CASE("Blend2Graph", "[AnimGraphResource]") {
AnimGraphResource graph_resource; AnimGraphResource graph_resource;
graph_resource.m_name = "WalkRunBlendGraph"; graph_resource.m_name = "WalkRunBlendGraph";
graph_resource.m_type = "BlendTree"; graph_resource.m_graph_type_name = "BlendTree";
BlendTreeResource& blend_tree_resource = graph_resource.m_blend_tree_resource; BlendTreeResource& blend_tree_resource = graph_resource.m_blend_tree_resource;
blend_tree_resource.Reset(); blend_tree_resource.Reset();
@ -219,25 +217,25 @@ TEST_CASE("Blend2Graph", "[AnimGraphResource]") {
blend_tree_resource.m_nodes.push_back(AnimNodeResourceFactory("Blend2")); blend_tree_resource.m_nodes.push_back(AnimNodeResourceFactory("Blend2"));
size_t blend_node_index = blend_tree_resource.m_nodes.size() - 1; size_t blend_node_index = blend_tree_resource.m_nodes.size() - 1;
AnimNodeResource& walk_node = blend_tree_resource.m_nodes[walk_node_index]; AnimNodeResource* walk_node = blend_tree_resource.m_nodes[walk_node_index];
walk_node.m_name = "WalkAnim"; walk_node->m_name = "WalkAnim";
walk_node.m_socket_accessor->SetPropertyValue( walk_node->m_socket_accessor->SetPropertyValue(
"Filename", "Filename",
std::string("media/Walking-loop.ozz")); std::string("media/Walking-loop.ozz"));
AnimNodeResource& run_node = blend_tree_resource.m_nodes[run_node_index]; AnimNodeResource* run_node = blend_tree_resource.m_nodes[run_node_index];
run_node.m_socket_accessor->SetPropertyValue( run_node->m_socket_accessor->SetPropertyValue(
"Filename", "Filename",
std::string("media/Running0-loop.ozz")); std::string("media/Running0-loop.ozz"));
run_node.m_name = "RunAnim"; run_node->m_name = "RunAnim";
AnimNodeResource& blend_node = blend_tree_resource.m_nodes[blend_node_index]; AnimNodeResource* blend_node = blend_tree_resource.m_nodes[blend_node_index];
blend_node.m_name = "BlendWalkRun"; blend_node->m_name = "BlendWalkRun";
AnimNodeResource& graph_node = blend_tree_resource.m_nodes[0]; AnimNodeResource* graph_node = blend_tree_resource.GetGraphOutputNode();
graph_node.m_socket_accessor->RegisterInput<AnimData>("GraphOutput", nullptr); graph_node->m_socket_accessor->RegisterInput<AnimData>("GraphOutput", nullptr);
REQUIRE(graph_node.m_socket_accessor->m_inputs.size() == 1); REQUIRE(graph_node->m_socket_accessor->m_inputs.size() == 1);
REQUIRE(blend_node.m_socket_accessor->GetInputIndex("Input0") == 0); REQUIRE(blend_node->m_socket_accessor->GetInputIndex("Input0") == 0);
REQUIRE(blend_node.m_socket_accessor->GetInputIndex("Input1") == 1); REQUIRE(blend_node->m_socket_accessor->GetInputIndex("Input1") == 1);
blend_tree_resource.ConnectSockets(walk_node, "Output", blend_node, "Input0"); blend_tree_resource.ConnectSockets(walk_node, "Output", blend_node, "Input0");
blend_tree_resource.ConnectSockets(run_node, "Output", blend_node, "Input1"); blend_tree_resource.ConnectSockets(run_node, "Output", blend_node, "Input1");
@ -247,10 +245,10 @@ TEST_CASE("Blend2Graph", "[AnimGraphResource]") {
blend_tree_resource.GetGraphOutputNode(), blend_tree_resource.GetGraphOutputNode(),
"GraphOutput"); "GraphOutput");
graph_resource.SaveToFile("Blend2Graph.animgraph.json"); graph_resource.SaveToFile("TestGraphBlend2Graph.animgraph.json");
AnimGraphResource graph_resource_loaded; AnimGraphResource graph_resource_loaded;
graph_resource_loaded.LoadFromFile("Blend2Graph.animgraph.json"); graph_resource_loaded.LoadFromFile("TestGraphBlend2Graph.animgraph.json");
AnimGraphBlendTree blend_tree_graph; AnimGraphBlendTree blend_tree_graph;
graph_resource_loaded.CreateBlendTreeInstance(blend_tree_graph); graph_resource_loaded.CreateBlendTreeInstance(blend_tree_graph);
@ -263,8 +261,8 @@ TEST_CASE("Blend2Graph", "[AnimGraphResource]") {
REQUIRE(blend_tree_graph.Init(graph_context)); REQUIRE(blend_tree_graph.Init(graph_context));
REQUIRE(blend_tree_graph.m_nodes.size() == 5); REQUIRE(blend_tree_graph.m_nodes.size() == 5);
REQUIRE(blend_tree_graph.m_nodes[0]->m_node_type_name == "BlendTree"); REQUIRE(blend_tree_graph.m_nodes[0]->m_node_type_name == "BlendTreeSockets");
REQUIRE(blend_tree_graph.m_nodes[1]->m_node_type_name == "BlendTree"); 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[2]->m_node_type_name == "AnimSampler");
REQUIRE(blend_tree_graph.m_nodes[3]->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"); REQUIRE(blend_tree_graph.m_nodes[4]->m_node_type_name == "Blend2");
@ -349,7 +347,7 @@ TEST_CASE("Blend2Graph", "[AnimGraphResource]") {
TEST_CASE("ResourceSaveLoadMathGraphInputs", "[AnimGraphResource]") { TEST_CASE("ResourceSaveLoadMathGraphInputs", "[AnimGraphResource]") {
AnimGraphResource graph_resource_origin; AnimGraphResource graph_resource_origin;
graph_resource_origin.m_name = "TestInputOutputGraph"; graph_resource_origin.m_name = "TestInputOutputGraph";
graph_resource_origin.m_type = "BlendTree"; graph_resource_origin.m_graph_type_name = "BlendTree";
BlendTreeResource& blend_tree_resource = graph_resource_origin.m_blend_tree_resource; BlendTreeResource& blend_tree_resource = graph_resource_origin.m_blend_tree_resource;
blend_tree_resource.Reset(); blend_tree_resource.Reset();
@ -359,23 +357,23 @@ TEST_CASE("ResourceSaveLoadMathGraphInputs", "[AnimGraphResource]") {
blend_tree_resource.m_nodes.push_back(AnimNodeResourceFactory("MathFloatToVec3Node")); blend_tree_resource.m_nodes.push_back(AnimNodeResourceFactory("MathFloatToVec3Node"));
size_t float_to_vec3_node_index = blend_tree_resource.m_nodes.size() - 1; size_t float_to_vec3_node_index = blend_tree_resource.m_nodes.size() - 1;
AnimNodeResource& graph_output_node = AnimNodeResource* graph_output_node =
blend_tree_resource.GetGraphOutputNode(); blend_tree_resource.GetGraphOutputNode();
graph_output_node.m_socket_accessor->RegisterInput<float>( graph_output_node->m_socket_accessor->RegisterInput<float>(
"GraphFloatOutput", "GraphFloatOutput",
nullptr); nullptr);
graph_output_node.m_socket_accessor->RegisterInput<Vec3>( graph_output_node->m_socket_accessor->RegisterInput<Vec3>(
"GraphVec3Output", "GraphVec3Output",
nullptr); nullptr);
AnimNodeResource& graph_input_node = AnimNodeResource* graph_input_node =
blend_tree_resource.GetGraphInputNode(); blend_tree_resource.GetGraphInputNode();
graph_input_node.m_socket_accessor->RegisterOutput<float>( graph_input_node->m_socket_accessor->RegisterOutput<float>(
"GraphFloatInput", "GraphFloatInput",
nullptr); nullptr);
// Prepare graph inputs and outputs // Prepare graph inputs and outputs
AnimNodeResource& float_to_vec3_node = AnimNodeResource* float_to_vec3_node =
blend_tree_resource.m_nodes[float_to_vec3_node_index]; blend_tree_resource.m_nodes[float_to_vec3_node_index];
REQUIRE(blend_tree_resource.ConnectSockets( REQUIRE(blend_tree_resource.ConnectSockets(
@ -407,7 +405,7 @@ TEST_CASE("ResourceSaveLoadMathGraphInputs", "[AnimGraphResource]") {
"GraphVec3Output")); "GraphVec3Output"));
WHEN("Saving and loading graph resource") { WHEN("Saving and loading graph resource") {
const char* filename = "ResourceSaveLoadGraphInputs.json"; const char* filename = "TestGraphResourceSaveLoadGraphInputs.json";
graph_resource_origin.SaveToFile(filename); graph_resource_origin.SaveToFile(filename);
AnimGraphResource graph_resource_loaded; AnimGraphResource graph_resource_loaded;
@ -415,31 +413,31 @@ TEST_CASE("ResourceSaveLoadMathGraphInputs", "[AnimGraphResource]") {
BlendTreeResource& graph_blend_tree_loaded = graph_resource_loaded.m_blend_tree_resource; BlendTreeResource& graph_blend_tree_loaded = graph_resource_loaded.m_blend_tree_resource;
const AnimNodeResource& graph_loaded_output_node = const AnimNodeResource* graph_loaded_output_node =
graph_blend_tree_loaded.m_nodes[0]; graph_blend_tree_loaded.m_nodes[0];
const AnimNodeResource& graph_loaded_input_node = const AnimNodeResource* graph_loaded_input_node =
graph_blend_tree_loaded.m_nodes[1]; graph_blend_tree_loaded.m_nodes[1];
THEN("Graph inputs and outputs must be in loaded resource as well.") { THEN("Graph inputs and outputs must be in loaded resource as well.") {
REQUIRE( REQUIRE(
graph_output_node.m_socket_accessor->m_inputs.size() graph_output_node->m_socket_accessor->m_inputs.size()
== graph_loaded_output_node.m_socket_accessor->m_inputs.size()); == graph_loaded_output_node->m_socket_accessor->m_inputs.size());
REQUIRE( REQUIRE(
graph_input_node.m_socket_accessor->m_outputs.size() graph_input_node->m_socket_accessor->m_outputs.size()
== graph_loaded_input_node.m_socket_accessor->m_outputs.size()); == graph_loaded_input_node->m_socket_accessor->m_outputs.size());
REQUIRE( REQUIRE(
graph_loaded_input_node.m_socket_accessor->GetOutputSocket( graph_loaded_input_node->m_socket_accessor->GetOutputSocket(
"GraphFloatInput") "GraphFloatInput")
!= nullptr); != nullptr);
REQUIRE( REQUIRE(
graph_loaded_output_node.m_socket_accessor->GetInputSocket( graph_loaded_output_node->m_socket_accessor->GetInputSocket(
"GraphFloatOutput") "GraphFloatOutput")
!= nullptr); != nullptr);
REQUIRE( REQUIRE(
graph_loaded_output_node.m_socket_accessor->GetInputSocket( graph_loaded_output_node->m_socket_accessor->GetInputSocket(
"GraphVec3Output") "GraphVec3Output")
!= nullptr); != nullptr);
@ -488,7 +486,7 @@ TEST_CASE("ResourceSaveLoadMathGraphInputs", "[AnimGraphResource]") {
TEST_CASE("SimpleMathEvaluations", "[AnimGraphResource]") { TEST_CASE("SimpleMathEvaluations", "[AnimGraphResource]") {
AnimGraphResource graph_resource_origin; AnimGraphResource graph_resource_origin;
graph_resource_origin.m_name = "TestSimpleMathGraph"; graph_resource_origin.m_name = "TestSimpleMathGraph";
graph_resource_origin.m_type = "BlendTree"; graph_resource_origin.m_graph_type_name = "BlendTree";
BlendTreeResource& blend_tree_resource = graph_resource_origin.m_blend_tree_resource; BlendTreeResource& blend_tree_resource = graph_resource_origin.m_blend_tree_resource;
blend_tree_resource.Reset(); blend_tree_resource.Reset();
@ -501,29 +499,29 @@ TEST_CASE("SimpleMathEvaluations", "[AnimGraphResource]") {
blend_tree_resource.m_nodes.push_back(AnimNodeResourceFactory("MathAddNode")); blend_tree_resource.m_nodes.push_back(AnimNodeResourceFactory("MathAddNode"));
size_t math_add1_node_index = blend_tree_resource.m_nodes.size() - 1; size_t math_add1_node_index = blend_tree_resource.m_nodes.size() - 1;
AnimNodeResource& graph_output_node = AnimNodeResource* graph_output_node =
blend_tree_resource.GetGraphOutputNode(); blend_tree_resource.GetGraphOutputNode();
graph_output_node.m_socket_accessor->RegisterInput<float>( graph_output_node->m_socket_accessor->RegisterInput<float>(
"GraphFloat0Output", "GraphFloat0Output",
nullptr); nullptr);
graph_output_node.m_socket_accessor->RegisterInput<float>( graph_output_node->m_socket_accessor->RegisterInput<float>(
"GraphFloat1Output", "GraphFloat1Output",
nullptr); nullptr);
graph_output_node.m_socket_accessor->RegisterInput<float>( graph_output_node->m_socket_accessor->RegisterInput<float>(
"GraphFloat2Output", "GraphFloat2Output",
nullptr); nullptr);
AnimNodeResource& graph_input_node = AnimNodeResource* graph_input_node =
blend_tree_resource.GetGraphInputNode(); blend_tree_resource.GetGraphInputNode();
graph_input_node.m_socket_accessor->RegisterOutput<float>( graph_input_node->m_socket_accessor->RegisterOutput<float>(
"GraphFloatInput", "GraphFloatInput",
nullptr); nullptr);
// Prepare graph inputs and outputs // Prepare graph inputs and outputs
AnimNodeResource& math_add0_node = AnimNodeResource* math_add0_node =
blend_tree_resource.m_nodes[math_add0_node_index]; blend_tree_resource.m_nodes[math_add0_node_index];
AnimNodeResource& math_add1_node = AnimNodeResource* math_add1_node =
blend_tree_resource.m_nodes[math_add1_node_index]; blend_tree_resource.m_nodes[math_add1_node_index];
// direct output // direct output
@ -572,7 +570,7 @@ TEST_CASE("SimpleMathEvaluations", "[AnimGraphResource]") {
"GraphFloat2Output")); "GraphFloat2Output"));
WHEN("Saving and loading graph resource") { WHEN("Saving and loading graph resource") {
const char* filename = "ResourceSaveLoadGraphInputs.json"; const char* filename = "TestGraphResourceSaveLoadGraphInputs.json";
graph_resource_origin.SaveToFile(filename); graph_resource_origin.SaveToFile(filename);
AnimGraphResource graph_resource_loaded; AnimGraphResource graph_resource_loaded;
@ -619,3 +617,153 @@ TEST_CASE("SimpleMathEvaluations", "[AnimGraphResource]") {
} }
} }
} }
//
// Tests whether BlendTree within a BlendTree can be saved and loaded
//
// +-----------Parent BlendTree-------------+
// | |
// | +-----Embd Btree---+ |
// | AnmSmpl--+-\ | |
// | | \-SpdScale------+--Out |
// | |------------------+ |
// | |
// +----------------------------------------+
//
TEST_CASE("EmbeddedBlendTreeSaveLoad", "[AnimGraphResource]") {
const char* parent_graph_filename = "TestGraphEmbeddedBlendTree.json";
AnimGraphResource parent_graph_resource;
parent_graph_resource.m_name = "ParentBlendTree";
parent_graph_resource.m_graph_type_name = "BlendTree";
parent_graph_resource.m_node_type_name = "BlendTree";
BlendTreeResource& 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<AnimData>("Output", nullptr);
// Parent AnimSampler
parent_blend_tree_resource.m_nodes.push_back(
AnimNodeResourceFactory("AnimSampler"));
AnimNodeResource* walk_node =
parent_blend_tree_resource.m_nodes.back();
walk_node->m_name = "WalkAnim";
walk_node->m_socket_accessor->SetPropertyValue(
"Filename",
std::string("media/Walking-loop.ozz"));
//
// Embedded Tree
//
parent_blend_tree_resource.m_nodes.push_back(
AnimNodeResourceFactory("BlendTree"));
size_t embedded_blend_tree_node_index =
parent_blend_tree_resource.m_nodes.size() - 1;
AnimGraphResource* embedded_graph =
dynamic_cast<AnimGraphResource*>(parent_blend_tree_resource.m_nodes.back());
embedded_graph->m_name = "EmbeddedBlendTree";
embedded_graph->m_node_type_name = "BlendTree";
BlendTreeResource* 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<AnimData>(
"AnimOutput",
nullptr);
// Embedded: inputs
AnimNodeResource* embedded_inputs =
embedded_blend_tree_resource->GetGraphInputNode();
embedded_inputs->m_socket_accessor->RegisterOutput<AnimData>(
"AnimInput",
nullptr);
// Embedded: SpeedScale node
embedded_blend_tree_resource->m_nodes.push_back(
AnimNodeResourceFactory("SpeedScale"));
AnimNodeResource* embedded_speed_scale_resource = embedded_blend_tree_resource->m_nodes.back();
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, "Output", embedded_graph, "AnimInput"));
REQUIRE(parent_blend_tree_resource.ConnectSockets(embedded_graph, "AnimOutput", parent_blend_tree_outputs, "Output"));
parent_graph_resource.SaveToFile(parent_graph_filename);
//
// Load the graph
//
AnimGraphResource parent_graph_resource_loaded;
parent_graph_resource_loaded.LoadFromFile(parent_graph_filename);
//
// Check the loaded parent graph
//
CHECK(parent_graph_resource.m_name == parent_graph_resource_loaded.m_name);
CHECK(parent_graph_resource.m_graph_type_name == parent_graph_resource_loaded.m_graph_type_name);
CHECK(parent_graph_resource.m_node_type_name
== parent_graph_resource_loaded.m_node_type_name);
const BlendTreeResource& parent_blend_tree_resource_loaded = parent_graph_resource_loaded.m_blend_tree_resource;
CHECK(parent_blend_tree_resource.m_nodes.size() == parent_blend_tree_resource_loaded.m_nodes.size());
for (size_t i = 0; i < parent_blend_tree_resource.m_nodes.size(); i++) {
const AnimNodeResource* parent_node = parent_blend_tree_resource.m_nodes[i];
const AnimNodeResource* parent_node_loaded = parent_blend_tree_resource_loaded.m_nodes[i];
CHECK(parent_node->m_name == parent_node_loaded->m_name);
CHECK(parent_node->m_node_type_name == parent_node_loaded->m_node_type_name);
}
CHECK(parent_blend_tree_resource.m_connections.size() == parent_blend_tree_resource_loaded.m_connections.size());
for (size_t i = 0; i < parent_blend_tree_resource.m_connections.size(); i++) {
const BlendTreeConnectionResource& parent_connection = parent_blend_tree_resource.m_connections[i];
const BlendTreeConnectionResource& parent_connection_loaded = parent_blend_tree_resource_loaded.m_connections[i];
CHECK(parent_connection.source_node_index == parent_connection_loaded.source_node_index);
CHECK(parent_connection.source_socket_name == parent_connection_loaded.source_socket_name);
CHECK(parent_connection.target_node_index == parent_connection_loaded.target_node_index);
CHECK(parent_connection.target_socket_name == parent_connection_loaded.target_socket_name);
}
//
// Check the loaded embedded graph
//
REQUIRE(parent_blend_tree_resource_loaded.m_nodes[3]->m_node_type_name == "BlendTree");
const AnimGraphResource* embedded_graph_loaded = dynamic_cast<AnimGraphResource*>(parent_blend_tree_resource_loaded.m_nodes[3]);
const BlendTreeResource& embedded_blend_tree_resource_loaded = embedded_graph_loaded->m_blend_tree_resource;
CHECK(embedded_blend_tree_resource->m_nodes.size() == embedded_blend_tree_resource_loaded.m_nodes.size());
CHECK(embedded_blend_tree_resource->m_connections.size() == embedded_blend_tree_resource_loaded.m_connections.size());
for (size_t i = 0; i < embedded_blend_tree_resource->m_nodes.size(); i++) {
const AnimNodeResource* parent_node = embedded_blend_tree_resource->m_nodes[i];
const AnimNodeResource* parent_node_loaded = embedded_blend_tree_resource_loaded.m_nodes[i];
CHECK(parent_node->m_name == parent_node_loaded->m_name);
CHECK(parent_node->m_node_type_name == parent_node_loaded->m_node_type_name);
}
CHECK(embedded_blend_tree_resource->m_connections.size() == embedded_blend_tree_resource_loaded.m_connections.size());
for (size_t i = 0; i < embedded_blend_tree_resource->m_connections.size(); i++) {
const BlendTreeConnectionResource& embedded_connection = embedded_blend_tree_resource->m_connections[i];
const BlendTreeConnectionResource& embedded_connection_loaded = embedded_blend_tree_resource_loaded.m_connections[i];
CHECK(embedded_connection.source_node_index == embedded_connection_loaded.source_node_index);
CHECK(embedded_connection.source_socket_name == embedded_connection_loaded.source_socket_name);
CHECK(embedded_connection.target_node_index == embedded_connection_loaded.target_node_index);
CHECK(embedded_connection.target_socket_name == embedded_connection_loaded.target_socket_name);
}
}