Initial graph editor and graph instantiation from file.
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
//
|
||||
// Created by martin on 11.02.22.
|
||||
//
|
||||
|
||||
#include "AnimGraphEditor.h"
|
||||
|
||||
#include "imnodes.h"
|
||||
|
||||
#include "AnimGraphResource.h"
|
||||
|
||||
static AnimGraphResource gGraphResource;
|
||||
|
||||
constexpr int NodeInputAttributeFlag = 2 << 16;
|
||||
constexpr int NodeOutputAttributeFlag = 2 << 17;
|
||||
|
||||
|
||||
void AnimGraphEditorUpdate() {
|
||||
ImGui::BeginMenuBar();
|
||||
if (ImGui::Button("Save")) {
|
||||
gGraphResource.saveToFile("editor_graph.json");
|
||||
}
|
||||
if (ImGui::Button("Load")) {
|
||||
gGraphResource.loadFromFile("editor_graph.json");
|
||||
|
||||
for (size_t i = 0, n = gGraphResource.m_nodes.size(); i < n; i++) {
|
||||
const AnimNodeResource& node_resource = gGraphResource.m_nodes[i];
|
||||
ImNodes::SetNodeGridSpacePos(i, ImVec2(node_resource.m_position[0], node_resource.m_position[1]));
|
||||
}
|
||||
}
|
||||
if (ImGui::Button("Clear")) {
|
||||
gGraphResource.clear();
|
||||
}
|
||||
char graph_name_buffer[256];
|
||||
memset (graph_name_buffer, 0, sizeof(graph_name_buffer));
|
||||
strncpy (graph_name_buffer, gGraphResource.m_name.c_str(), sizeof(graph_name_buffer));
|
||||
if (ImGui::InputText("Name", graph_name_buffer, sizeof(graph_name_buffer))) {
|
||||
gGraphResource.m_name = graph_name_buffer;
|
||||
}
|
||||
|
||||
ImGui::EndMenuBar();
|
||||
|
||||
ImNodes::BeginNodeEditor();
|
||||
|
||||
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) && ImNodes::IsEditorHovered()
|
||||
&& ImGui::IsMouseReleased(ImGuiMouseButton_Right)) {
|
||||
AnimNodeResource node_resource = AnimNodeResourceFactory("Blend2");
|
||||
size_t node_id = gGraphResource.m_nodes.size();
|
||||
ImNodes::SetNodeScreenSpacePos(node_id, ImGui::GetMousePos());
|
||||
gGraphResource.m_nodes.push_back(node_resource);
|
||||
}
|
||||
|
||||
for (size_t i = 0, n = gGraphResource.m_nodes.size(); i < n; i++) {
|
||||
AnimNodeResource& node_resource = gGraphResource.m_nodes[i];
|
||||
|
||||
ImNodes::BeginNode(i);
|
||||
|
||||
// Header
|
||||
ImNodes::BeginNodeTitleBar();
|
||||
ImGui::TextUnformatted(node_resource.m_type_name.c_str());
|
||||
ImNodes::EndNodeTitleBar();
|
||||
|
||||
// Inputs
|
||||
const std::vector<Socket>& node_inputs =node_resource.m_socket_accessor->m_inputs;
|
||||
for (size_t j = 0, ni = node_inputs.size(); j < ni; j++) {
|
||||
const Socket& socket = node_inputs[j];
|
||||
ImNodes::BeginInputAttribute(GenerateInputAttributeId(i, j));
|
||||
ImGui::Text(socket.m_name.c_str());
|
||||
ImNodes::EndInputAttribute();
|
||||
}
|
||||
|
||||
// Outputs
|
||||
const std::vector<Socket>& node_outputs =node_resource.m_socket_accessor->m_outputs;
|
||||
for (size_t j = 0, ni = node_outputs.size(); j < ni; j++) {
|
||||
const Socket& socket = node_outputs[j];
|
||||
ImNodes::BeginOutputAttribute(GenerateOutputAttributeId(i, j));
|
||||
ImGui::Text(socket.m_name.c_str());
|
||||
ImNodes::EndInputAttribute();
|
||||
}
|
||||
|
||||
// Save state in node resource
|
||||
ImVec2 node_pos = ImNodes::GetNodeGridSpacePos(i);
|
||||
node_resource.m_position[0] = node_pos[0];
|
||||
node_resource.m_position[1] = node_pos[1];
|
||||
ImNodes::EndNode();
|
||||
}
|
||||
|
||||
for (size_t i = 0, n = gGraphResource.m_connections.size(); i < n; i++) {
|
||||
const AnimGraphConnection& connection = gGraphResource.m_connections[i];
|
||||
int start_attr, end_attr;
|
||||
start_attr = GenerateOutputAttributeId(connection.m_source_node_index, connection.m_source_socket_index);
|
||||
end_attr = GenerateInputAttributeId(connection.m_target_node_index, connection.m_target_socket_index);
|
||||
ImNodes::Link(i, start_attr, end_attr);
|
||||
}
|
||||
|
||||
ImNodes::EndNodeEditor();
|
||||
|
||||
int start_attr, end_attr;
|
||||
if (ImNodes::IsLinkCreated(&start_attr, &end_attr)) {
|
||||
int node_start_id;
|
||||
int node_start_output_index;
|
||||
SplitOutputAttributeId(start_attr, &node_start_id, &node_start_output_index);
|
||||
|
||||
int node_end_id;
|
||||
int node_end_input_index;
|
||||
SplitInputAttributeId(end_attr, &node_end_id, &node_end_input_index);
|
||||
|
||||
std::cout << "Link created: " << node_start_id << ", " << node_start_output_index << " -> "
|
||||
<< node_end_id << ", " << node_end_input_index << std::endl;
|
||||
|
||||
AnimGraphConnection connection;
|
||||
connection.m_source_node_index = node_start_id;
|
||||
connection.m_source_socket_index = node_start_output_index;
|
||||
|
||||
connection.m_target_node_index = node_end_id;
|
||||
connection.m_target_socket_index = node_end_input_index;
|
||||
gGraphResource.m_connections.push_back(connection);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
//
|
||||
// Created by martin on 11.02.22.
|
||||
//
|
||||
|
||||
#ifndef ANIMTESTBED_ANIMGRAPHEDITOR_H
|
||||
#define ANIMTESTBED_ANIMGRAPHEDITOR_H
|
||||
|
||||
void AnimGraphEditorUpdate();
|
||||
|
||||
#endif //ANIMTESTBED_ANIMGRAPHEDITOR_H
|
||||
@@ -0,0 +1,161 @@
|
||||
//
|
||||
// Created by martin on 04.02.22.
|
||||
//
|
||||
|
||||
#include <fstream>
|
||||
#include "AnimGraphResource.h"
|
||||
|
||||
#include "3rdparty/json/json.hpp"
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
std::string sPinTypeToStr (SocketType pin_type) {
|
||||
std::string result = "unknown";
|
||||
switch (pin_type) {
|
||||
case SocketType::SocketTypeFloat: result = "Float"; break;
|
||||
case SocketType::SocketTypeAnimation: result = "AnimationData"; break;
|
||||
default: result = "Unknown";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
json sSocketToJson(const Socket& pin) {
|
||||
json result;
|
||||
result["name"] = pin.m_name;
|
||||
result["type"] = sPinTypeToStr(pin.m_type);
|
||||
return result;
|
||||
}
|
||||
|
||||
json sAnimGraphNodeToJson(const AnimNodeResource& node) {
|
||||
json result;
|
||||
|
||||
result["name"] = node.m_name;
|
||||
result["type"] = "AnimNodeResource";
|
||||
result["node_type"] = node.m_type_name;
|
||||
|
||||
for (size_t j = 0; j < 2; j++) {
|
||||
result["position"][j] = node.m_position[j];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
AnimNodeResource sAnimGraphNodeFromJson(const json& json_node) {
|
||||
AnimNodeResource result;
|
||||
|
||||
result.m_name = json_node["name"];
|
||||
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);
|
||||
result.m_socket_accessor = AnimNodeAccessorFactory(result.m_type_name, result.m_anim_node);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
json sAnimGraphConnectionToJson(const AnimGraphConnection& connection) {
|
||||
json result;
|
||||
|
||||
result["type"] = "AnimGraphConnection";
|
||||
|
||||
result["source_node_index"] = connection.m_source_node_index;
|
||||
result["source_socket_index"] = connection.m_source_socket_index;
|
||||
|
||||
result["target_node_index"] = connection.m_target_node_index;
|
||||
result["target_socket_index"] = connection.m_target_socket_index;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
AnimGraphConnection sAnimGraphConnectionFromJson(const json& json_node) {
|
||||
AnimGraphConnection connection;
|
||||
|
||||
connection.m_source_node_index = json_node["source_node_index"];
|
||||
connection.m_source_socket_index = json_node["source_socket_index"];
|
||||
|
||||
connection.m_target_node_index = json_node["target_node_index"];
|
||||
connection.m_target_socket_index = json_node["target_socket_index"];
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
|
||||
void AnimGraphResource::clear() {
|
||||
m_name = "";
|
||||
m_nodes.clear();
|
||||
m_connections.clear();
|
||||
}
|
||||
|
||||
|
||||
bool AnimGraphResource::saveToFile (const char* filename) const {
|
||||
json result;
|
||||
|
||||
result["name"] = m_name;
|
||||
result["type"] = "AnimGraphResource";
|
||||
|
||||
for (size_t i = 0; i < m_nodes.size(); i++) {
|
||||
const AnimNodeResource& node = m_nodes[i];
|
||||
result["nodes"][i] = sAnimGraphNodeToJson(node);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < m_connections.size(); i++) {
|
||||
const AnimGraphConnection& connection = m_connections[i];
|
||||
result["connections"][i] = sAnimGraphConnectionToJson(connection);
|
||||
}
|
||||
|
||||
std::ofstream output_file;
|
||||
output_file.open (filename);
|
||||
output_file << to_string(result) << std::endl;
|
||||
output_file.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AnimGraphResource::loadFromFile (const char* filename) {
|
||||
std::ifstream input_file;
|
||||
input_file.open(filename);
|
||||
std::stringstream buffer;
|
||||
buffer << input_file.rdbuf();
|
||||
|
||||
json json_data = json::parse(buffer.str(), nullptr, false);
|
||||
if (json_data.is_discarded()) {
|
||||
std::cerr << "Error parsing json of file '" << filename << "'." << std::endl;
|
||||
}
|
||||
|
||||
if (json_data["type"] != "AnimGraphResource") {
|
||||
std::cerr << "Invalid json object. Expected type 'AnimGraphResource' but got '" << json_data["type"] << "'." << std::endl;
|
||||
}
|
||||
|
||||
clear();
|
||||
|
||||
m_name = json_data["name"];
|
||||
for (size_t i = 0; i < json_data["nodes"].size(); 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);
|
||||
m_nodes.push_back(node);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < json_data["connections"].size(); i++) {
|
||||
const json& json_connection = json_data["connections"][i];
|
||||
if (json_connection["type"] != "AnimGraphConnection") {
|
||||
std::cerr << "Invalid json object. Expected type 'AnimGraphConnection' but got '" << json_connection["type"] << "'." << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
AnimGraphConnection connection = sAnimGraphConnectionFromJson(json_connection);
|
||||
m_connections.push_back(connection);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,556 @@
|
||||
//
|
||||
// Created by martin on 04.02.22.
|
||||
//
|
||||
|
||||
#ifndef ANIMTESTBED_ANIMGRAPHRESOURCE_H
|
||||
#define ANIMTESTBED_ANIMGRAPHRESOURCE_H
|
||||
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "SyncTrack.h"
|
||||
|
||||
enum class SocketType {
|
||||
SocketTypeUndefined,
|
||||
SocketTypeBool,
|
||||
SocketTypeAnimation,
|
||||
SocketTypeFloat,
|
||||
SocketTypeVec3,
|
||||
SocketTypeQuat,
|
||||
SocketTypeString
|
||||
};
|
||||
|
||||
struct AnimData {
|
||||
float m_bone_transforms[16];
|
||||
};
|
||||
|
||||
typedef float Vec3[3];
|
||||
typedef float Quat[4];
|
||||
|
||||
inline int GenerateInputAttributeId(int node_id, int input_index) {
|
||||
return ((input_index + 1) << 14) + node_id;
|
||||
}
|
||||
|
||||
inline void
|
||||
SplitInputAttributeId(int attribute_id, int* node_id, int* input_index) {
|
||||
*node_id = attribute_id & ((1 << 14) - 1);
|
||||
*input_index = (attribute_id >> 14) - 1;
|
||||
}
|
||||
|
||||
inline int GenerateOutputAttributeId(int node_id, int output_index) {
|
||||
return ((output_index + 1) << 23) + node_id;
|
||||
}
|
||||
|
||||
inline void
|
||||
SplitOutputAttributeId(int attribute_id, int* node_id, int* output_index) {
|
||||
*node_id = attribute_id & ((1 << 14) - 1);
|
||||
*output_index = (attribute_id >> 23) - 1;
|
||||
}
|
||||
|
||||
struct AnimNodeResource;
|
||||
|
||||
struct Socket {
|
||||
std::string m_name;
|
||||
SocketType m_type;
|
||||
union Value {
|
||||
bool* flag;
|
||||
AnimData* anim_data;
|
||||
float* real;
|
||||
Vec3* vec3;
|
||||
Quat* quat;
|
||||
std::string* str;
|
||||
bool** flag_ptr;
|
||||
AnimData** anim_data_ptr;
|
||||
float** real_ptr;
|
||||
float** vec3_ptr;
|
||||
float** quat_ptr;
|
||||
std::string** str_ptr;
|
||||
};
|
||||
Value m_value;
|
||||
};
|
||||
|
||||
struct AnimNode;
|
||||
struct NodeSocketAccessorBase;
|
||||
|
||||
struct AnimNodeResource {
|
||||
std::string m_name;
|
||||
std::string m_type_name;
|
||||
AnimNode* m_anim_node = nullptr;
|
||||
NodeSocketAccessorBase* m_socket_accessor = nullptr;
|
||||
|
||||
float m_position[2] = {0.f, 0.f};
|
||||
};
|
||||
|
||||
struct AnimNode {
|
||||
virtual ~AnimNode(){};
|
||||
|
||||
std::string m_name;
|
||||
std::string m_node_type_name;
|
||||
bool m_is_time_synced;
|
||||
float m_time_now;
|
||||
float m_time_last;
|
||||
SyncTrack m_sync_track;
|
||||
};
|
||||
|
||||
struct NodeSocketAccessorBase {
|
||||
NodeSocketAccessorBase(AnimNode* node) {}
|
||||
virtual ~NodeSocketAccessorBase() {}
|
||||
|
||||
Socket* FindSocket(std::vector<Socket>& sockets, const std::string& name) {
|
||||
Socket* result = nullptr;
|
||||
for (size_t i = 0, n = sockets.size(); i < n; i++) {
|
||||
if (sockets[i].m_name == name) {
|
||||
result = &sockets[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
SocketType GetSocketType(
|
||||
const std::vector<Socket> sockets,
|
||||
const std::string& name) {
|
||||
Socket* socket = FindSocket(m_properties, name);
|
||||
if (socket == nullptr) {
|
||||
return SocketType::SocketTypeUndefined;
|
||||
}
|
||||
return socket->m_type;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool RegisterSocket(
|
||||
std::vector<Socket>& sockets,
|
||||
const std::string& name,
|
||||
T* value_ptr) {
|
||||
Socket* socket = FindSocket(sockets, name);
|
||||
if (socket != nullptr) {
|
||||
std::cerr << "Socket " << name << " already registered." << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
sockets.push_back(Socket());
|
||||
socket = &sockets[sockets.size() - 1];
|
||||
socket->m_name = name;
|
||||
|
||||
if constexpr (std::is_same<T, float>::value) {
|
||||
socket->m_value.real = value_ptr;
|
||||
socket->m_type = SocketType::SocketTypeFloat;
|
||||
} else if constexpr (std::is_same<T, bool>::value) {
|
||||
socket->m_value.flag = value_ptr;
|
||||
socket->m_type = SocketType::SocketTypeBool;
|
||||
} else if constexpr (std::is_same<T, Vec3>::value) {
|
||||
socket->m_value.vec3 = value_ptr;
|
||||
socket->m_type = SocketType::SocketTypeVec3;
|
||||
} else if constexpr (std::is_same<T, Quat>::value) {
|
||||
socket->m_value.quat = value_ptr;
|
||||
socket->m_type = SocketType::SocketTypeQuat;
|
||||
} else if constexpr (std::is_same<T, AnimData>::value) {
|
||||
socket->m_value.anim_data = value_ptr;
|
||||
socket->m_type = SocketType::SocketTypeAnimation;
|
||||
} else if constexpr (std::is_same<T, std::string>::value) {
|
||||
socket->m_value.str = value_ptr;
|
||||
socket->m_type = SocketType::SocketTypeString;
|
||||
} else if constexpr (std::is_same<T, float*>::value) {
|
||||
socket->m_value.real_ptr = value_ptr;
|
||||
socket->m_type = SocketType::SocketTypeFloat;
|
||||
} else if constexpr (std::is_same<T, bool*>::value) {
|
||||
socket->m_value.flag_ptr = value_ptr;
|
||||
socket->m_type = SocketType::SocketTypeBool;
|
||||
} else if constexpr (std::is_same<T, Vec3*>::value) {
|
||||
socket->m_value.vec3_ptr = value_ptr;
|
||||
socket->m_type = SocketType::SocketTypeVec3;
|
||||
} else if constexpr (std::is_same<T, Quat*>::value) {
|
||||
socket->m_value.quat_ptr = value_ptr;
|
||||
socket->m_type = SocketType::SocketTypeQuat;
|
||||
} else if constexpr (std::is_same<T, AnimData*>::value) {
|
||||
socket->m_value.anim_data_ptr = value_ptr;
|
||||
socket->m_type = SocketType::SocketTypeAnimation;
|
||||
} else if constexpr (std::is_same<T, std::string*>::value) {
|
||||
socket->m_value.str_ptr = value_ptr;
|
||||
socket->m_type = SocketType::SocketTypeString;
|
||||
} else {
|
||||
std::cerr << "Cannot register socket, invalid type." << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool SetSocketValue(
|
||||
std::vector<Socket>& sockets,
|
||||
const std::string& name,
|
||||
T value) {
|
||||
Socket* socket = FindSocket(sockets, name);
|
||||
if (socket == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if constexpr (std::is_same<T, float>::value) {
|
||||
(*socket->m_value.real) = value;
|
||||
} else if constexpr (std::is_same<T, bool>::value) {
|
||||
(*socket->m_value.flag) = value;
|
||||
} else if constexpr (std::is_same<T, Vec3>::value) {
|
||||
(*socket->m_value.vec3) = value;
|
||||
} else if constexpr (std::is_same<T, Quat>::value) {
|
||||
(*socket->m_value.vec3) = value;
|
||||
} else if constexpr (std::is_same<T, AnimData>::value) {
|
||||
(*socket->m_value.vec3) = value;
|
||||
} else if constexpr (std::is_same<T, std::string>::value) {
|
||||
(*socket->m_value.str) = value;
|
||||
} else {
|
||||
std::cerr << "Invalid type!" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool SetSocketRef(
|
||||
std::vector<Socket>& sockets,
|
||||
const std::string& name,
|
||||
T* value) {
|
||||
Socket* socket = FindSocket(sockets, name);
|
||||
if (socket == nullptr) {
|
||||
return false;
|
||||
} else if constexpr (std::is_same<T, float>::value) {
|
||||
(*socket->m_value.real_ptr) = value;
|
||||
} else if constexpr (std::is_same<T, bool>::value) {
|
||||
(*socket->m_value.flag_ptr) = value;
|
||||
} else if constexpr (std::is_same<T, Vec3>::value) {
|
||||
(*socket->m_value.vec3_ptr) = value;
|
||||
} else if constexpr (std::is_same<T, Quat>::value) {
|
||||
(*socket->m_value.quat_ptr) = value;
|
||||
} else if constexpr (std::is_same<T, AnimData>::value) {
|
||||
(*socket->m_value.anim_data_ptr) = value;
|
||||
} else if constexpr (std::is_same<T, std::string>::value) {
|
||||
(*socket->m_value.str_ptr) = value;
|
||||
} else {
|
||||
std::cerr << "Invalid type!" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* GetSocketValue(
|
||||
std::vector<Socket>& sockets,
|
||||
const std::string& name,
|
||||
T* default_value) {
|
||||
Socket* socket = FindSocket(sockets, name);
|
||||
if (socket == nullptr) {
|
||||
return default_value;
|
||||
}
|
||||
|
||||
if constexpr (std::is_same<T, float>::value) {
|
||||
return socket->m_value.real;
|
||||
} else if constexpr (std::is_same<T, Vec3>::value) {
|
||||
return socket->m_value.vec3;
|
||||
} else if constexpr (std::is_same<T, bool>::value) {
|
||||
return socket->m_value.flag;
|
||||
} else if constexpr (std::is_same<T, Quat>::value) {
|
||||
return socket->m_value.vec3;
|
||||
} else if constexpr (std::is_same<T, AnimData>::value) {
|
||||
return socket->m_value.anim_data;
|
||||
} else if constexpr (std::is_same<T, std::string>::value) {
|
||||
return socket->m_value.str;
|
||||
}
|
||||
|
||||
std::cerr << "Invalid type!" << std::endl;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool RegisterProperty(const std::string& name, T* value) {
|
||||
return RegisterSocket(m_properties, name, value);
|
||||
}
|
||||
template <typename T>
|
||||
bool SetProperty(const std::string& name, T value) {
|
||||
return SetSocketValue(m_properties, name, value);
|
||||
}
|
||||
template <typename T>
|
||||
T GetProperty(const std::string& name, T value) {
|
||||
return GetSocketValue(m_properties, name, value);
|
||||
}
|
||||
SocketType GetPropertyType(const std::string& name) {
|
||||
return GetSocketType(m_properties, name);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool RegisterInput(const std::string& name, T* value) {
|
||||
return RegisterSocket(m_inputs, name, value);
|
||||
}
|
||||
template <typename T>
|
||||
bool SetInput(const std::string& name, T value) {
|
||||
return SetSocketValue(m_inputs, name, value);
|
||||
}
|
||||
template <typename T>
|
||||
T* GetInput(const std::string& name, T* value) {
|
||||
return GetSocketValue(m_inputs, name, value);
|
||||
}
|
||||
SocketType GetInputType(const std::string& name) {
|
||||
return GetSocketType(m_inputs, name);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool RegisterOutput(const std::string& name, T** value) {
|
||||
return RegisterSocket(m_outputs, name, value);
|
||||
}
|
||||
template <typename T>
|
||||
bool SetOutputRef(const std::string& name, T* value) {
|
||||
return SetSocketRef(m_outputs, name, value);
|
||||
}
|
||||
SocketType GetOutputType(const std::string& name) {
|
||||
return GetSocketType(m_outputs, name);
|
||||
}
|
||||
|
||||
std::vector<Socket> m_properties;
|
||||
std::vector<Socket> m_inputs;
|
||||
std::vector<Socket> m_outputs;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct NodeSocketAccessor : public NodeSocketAccessorBase {
|
||||
virtual ~NodeSocketAccessor() {}
|
||||
};
|
||||
|
||||
struct NodeRegistry {
|
||||
AnimNode* createNode(const std::string& node_type);
|
||||
NodeSocketAccessorBase* createNodeAccessor(
|
||||
const std::string& node_type,
|
||||
AnimNode* node);
|
||||
};
|
||||
|
||||
struct Blend2Node : public AnimNode {
|
||||
AnimData m_input0;
|
||||
AnimData m_input1;
|
||||
AnimData* m_output = nullptr;
|
||||
float m_blend_weight = 0.f;
|
||||
bool m_sync_blend = false;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct NodeSocketAccessor<Blend2Node> : public NodeSocketAccessorBase {
|
||||
NodeSocketAccessor(AnimNode* node_) : NodeSocketAccessorBase(node_) {
|
||||
Blend2Node* node = dynamic_cast<Blend2Node*>(node_);
|
||||
RegisterInput("Input0", &node->m_input0);
|
||||
RegisterInput("Input1", &node->m_input1);
|
||||
RegisterInput("Weight", &node->m_blend_weight);
|
||||
|
||||
RegisterOutput("Output", &node->m_output);
|
||||
|
||||
RegisterProperty("Sync", &node->m_sync_blend);
|
||||
}
|
||||
};
|
||||
|
||||
struct SpeedScaleNode : public AnimNode {
|
||||
AnimData m_input;
|
||||
AnimData* m_output = nullptr;
|
||||
float m_speed_scale = 0.f;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct NodeSocketAccessor<SpeedScaleNode> : public NodeSocketAccessorBase {
|
||||
NodeSocketAccessor(AnimNode* node_) : NodeSocketAccessorBase(node_) {
|
||||
SpeedScaleNode* node = dynamic_cast<SpeedScaleNode*>(node_);
|
||||
RegisterInput("SpeedScale", &node->m_speed_scale);
|
||||
RegisterInput("Input", &node->m_input);
|
||||
|
||||
RegisterOutput("Output", &node->m_output);
|
||||
}
|
||||
};
|
||||
|
||||
struct AnimSamplerNode : public AnimNode {
|
||||
AnimData* m_output = nullptr;
|
||||
std::string m_filename;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct NodeSocketAccessor<AnimSamplerNode> : public NodeSocketAccessorBase {
|
||||
NodeSocketAccessor(AnimNode* node_) : NodeSocketAccessorBase(node_) {
|
||||
AnimSamplerNode* node = dynamic_cast<AnimSamplerNode*>(node_);
|
||||
RegisterOutput("Output", &node->m_output);
|
||||
|
||||
RegisterProperty("Filename", &node->m_filename);
|
||||
}
|
||||
};
|
||||
|
||||
struct AnimGraphConnection {
|
||||
int m_source_node_index;
|
||||
int m_source_socket_index;
|
||||
int m_target_node_index;
|
||||
int m_target_socket_index;
|
||||
};
|
||||
|
||||
struct AnimGraphResource {
|
||||
std::string m_name;
|
||||
std::vector<AnimNodeResource> m_nodes;
|
||||
std::vector<AnimGraphConnection> m_connections;
|
||||
std::vector<Socket> m_inputs;
|
||||
std::vector<Socket> m_outputs;
|
||||
|
||||
void clear();
|
||||
bool saveToFile(const char* filename) const;
|
||||
bool loadFromFile(const char* filename);
|
||||
};
|
||||
|
||||
static inline AnimNode* AnimNodeFactory(const std::string& name) {
|
||||
if (name == "Blend2") {
|
||||
return new Blend2Node;
|
||||
} else if (name == "SpeedScale") {
|
||||
return new SpeedScaleNode;
|
||||
} else if (name == "AnimSampler") {
|
||||
return new AnimSamplerNode;
|
||||
}
|
||||
|
||||
std::cerr << "Invalid node type: " << name << std::endl;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static inline NodeSocketAccessorBase* AnimNodeAccessorFactory(
|
||||
const std::string& node_type_name,
|
||||
AnimNode* node) {
|
||||
if (node_type_name == "Blend2") {
|
||||
return new NodeSocketAccessor<Blend2Node>(node);
|
||||
} else if (node_type_name == "SpeedScale") {
|
||||
return new NodeSocketAccessor<SpeedScaleNode>(node);
|
||||
} else if (node_type_name == "AnimSampler") {
|
||||
return new NodeSocketAccessor<AnimSamplerNode>(node);
|
||||
} else {
|
||||
std::cerr << "Invalid node type name " << node_type_name << "."
|
||||
<< std::endl;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static inline AnimNodeResource AnimNodeResourceFactory(
|
||||
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 =
|
||||
AnimNodeAccessorFactory(node_type_name, result.m_anim_node);
|
||||
return result;
|
||||
}
|
||||
|
||||
struct AnimGraph {
|
||||
void UpdateTime(float dt);
|
||||
void Evaluate();
|
||||
|
||||
AnimData m_local_transforms;
|
||||
std::vector<AnimNode*> m_nodes;
|
||||
std::vector<std::vector<AnimNode*> > m_node_inputs;
|
||||
|
||||
AnimNode* getAnimNode(const char* name) {
|
||||
for (size_t i = 0; i < m_nodes.size(); i++) {
|
||||
if (m_nodes[i]->m_name == name) {
|
||||
return m_nodes[i];
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t getAnimNodeIndex(AnimNode* node) {
|
||||
for (size_t i = 0, n = m_nodes.size(); i < n; i++) {
|
||||
if (m_nodes[i] == node) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
std::cerr << "Could not find node " << node << "." << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static AnimGraph createFromResource(const AnimGraphResource& resource) {
|
||||
AnimGraph result;
|
||||
for (int i = 0; i < resource.m_nodes.size(); i++) {
|
||||
const AnimNodeResource& node_resource = resource.m_nodes[i];
|
||||
AnimNode* node = AnimNodeFactory(node_resource.m_type_name.c_str());
|
||||
node->m_name = node_resource.m_name;
|
||||
node->m_node_type_name = node_resource.m_type_name;
|
||||
result.m_nodes.push_back(node);
|
||||
result.m_node_inputs.push_back(std::vector<AnimNode*>());
|
||||
}
|
||||
|
||||
for (int i = 0; i < resource.m_connections.size(); i++) {
|
||||
const AnimGraphConnection& connection = resource.m_connections[i];
|
||||
std::string source_node_type = "";
|
||||
std::string target_node_type = "";
|
||||
std::string source_node_name = "";
|
||||
std::string target_node_name = "";
|
||||
AnimNode* source_node = nullptr;
|
||||
AnimNode* target_node = nullptr;
|
||||
NodeSocketAccessorBase* source_node_accessor = nullptr;
|
||||
NodeSocketAccessorBase* target_node_accessor = nullptr;
|
||||
SocketType source_type;
|
||||
SocketType target_type;
|
||||
|
||||
if (connection.m_source_node_index >= 0) {
|
||||
source_node = result.m_nodes[connection.m_source_node_index];
|
||||
source_node_name = source_node->m_name;
|
||||
source_node_type = source_node->m_node_type_name;
|
||||
source_node_accessor =
|
||||
AnimNodeAccessorFactory(source_node_type, source_node);
|
||||
}
|
||||
|
||||
if (connection.m_target_node_index >= 0) {
|
||||
target_node = result.m_nodes[connection.m_target_node_index];
|
||||
target_node_name = target_node->m_name;
|
||||
target_node_type = target_node->m_node_type_name;
|
||||
target_node_accessor =
|
||||
AnimNodeAccessorFactory(target_node_type, target_node);
|
||||
}
|
||||
|
||||
assert(source_node != nullptr);
|
||||
assert(target_node != nullptr);
|
||||
|
||||
if (connection.m_source_socket_index
|
||||
> source_node_accessor->m_outputs.size()) {
|
||||
std::cerr << "Invalid source socket index "
|
||||
<< connection.m_source_socket_index << ". Only "
|
||||
<< source_node_accessor->m_outputs.size()
|
||||
<< " output sockets found." << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (connection.m_target_socket_index
|
||||
> target_node_accessor->m_inputs.size()) {
|
||||
std::cerr << "Invalid source socket index "
|
||||
<< connection.m_target_socket_index << ". Only "
|
||||
<< source_node_accessor->m_inputs.size()
|
||||
<< " input sockets found." << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (source_node_accessor->m_outputs[connection.m_source_socket_index].m_type
|
||||
!= target_node_accessor->m_inputs[connection.m_target_socket_index].m_type) {
|
||||
std::cerr << "Invalid connection connecting nodes '" << source_node_name
|
||||
<< "' and '" << target_node_name << "'." << std::endl;
|
||||
return result;
|
||||
}
|
||||
|
||||
// TODO! How to wire up the different types?
|
||||
// std::string socket_name = connection.m_target_socket_name;
|
||||
// AnimData* input_ref =
|
||||
// target_node_accessor->GetInput<AnimData>(socket_name, nullptr);
|
||||
// source_node_accessor->SetOutputRef(
|
||||
// connection.m_source_socket_name,
|
||||
// input_ref);
|
||||
|
||||
size_t target_node_index = result.getAnimNodeIndex(target_node);
|
||||
|
||||
result.m_node_inputs[target_node_index].push_back(source_node);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
#endif //ANIMTESTBED_ANIMGRAPHRESOURCE_H
|
||||
+61
-27
@@ -5,6 +5,7 @@
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
#include "imgui.h"
|
||||
#include "imnodes.h"
|
||||
#define SOKOL_IMPL
|
||||
#define SOKOL_GLCORE33
|
||||
#include "sokol_gfx.h"
|
||||
@@ -16,6 +17,7 @@
|
||||
|
||||
#include "Camera.h"
|
||||
#include "SkinnedMesh.h"
|
||||
#include "AnimGraphEditor.h"
|
||||
#include "GLFW/glfw3.h"
|
||||
|
||||
const int Width = 1024;
|
||||
@@ -108,6 +110,15 @@ enum class ControlMode {
|
||||
|
||||
ControlMode gControlMode = ControlMode::ControlModeNone;
|
||||
|
||||
enum class AppMode {
|
||||
AnimRuntime = 0,
|
||||
GraphEditor = 1,
|
||||
Debug = 2
|
||||
};
|
||||
const char* AppModeNames[] = {"Runtime", "Graph Editor", "Debug"};
|
||||
|
||||
AppMode gAppMode = AppMode::GraphEditor;
|
||||
|
||||
// io buffers for skeleton and animation data files, we know the max file size upfront
|
||||
static uint8_t skel_data_buffer[4 * 1024];
|
||||
static uint8_t anim_data_buffer[32 * 1024];
|
||||
@@ -262,6 +273,9 @@ int main() {
|
||||
io.KeyMap[ImGuiKey_Y] = GLFW_KEY_Y;
|
||||
io.KeyMap[ImGuiKey_Z] = GLFW_KEY_Z;
|
||||
|
||||
// ImNodes
|
||||
ImNodes::CreateContext();
|
||||
|
||||
// dynamic vertex- and index-buffers for imgui-generated geometry
|
||||
sg_buffer_desc vbuf_desc = {};
|
||||
vbuf_desc.usage = SG_USAGE_STREAM;
|
||||
@@ -356,14 +370,6 @@ int main() {
|
||||
io.DeltaTime = (float)stm_sec(stm_laptime(&last_time));
|
||||
ImGui::NewFrame();
|
||||
|
||||
ImGui::Begin("Camera");
|
||||
ImGui::SliderFloat3("pos", state.camera.pos, -100.f, 100.f);
|
||||
ImGui::SliderFloat("near", &state.camera.near, 0.001f, 10.f);
|
||||
ImGui::SliderFloat("far", &state.camera.far, 1.0f, 10000.f);
|
||||
ImGui::SliderFloat("heading", &state.camera.heading, -180.0f, 180.f);
|
||||
ImGui::SliderFloat("pitch", &state.camera.pitch, -179.0f, 179.f);
|
||||
ImGui::End();
|
||||
|
||||
// handle input
|
||||
handle_mouse (w, &gGuiInputState);
|
||||
|
||||
@@ -427,29 +433,59 @@ int main() {
|
||||
|
||||
if (ImGui::BeginMainMenuBar()) {
|
||||
ImGui::Text("AnimTestbed");
|
||||
int mode_current = static_cast<int>(gAppMode);
|
||||
if (ImGui::Combo("Mode", &mode_current, AppModeNames, sizeof(AppModeNames) / sizeof(char*))) {
|
||||
switch (mode_current) {
|
||||
case 0: gAppMode = AppMode::AnimRuntime; break;
|
||||
case 1: gAppMode = AppMode::GraphEditor; break;
|
||||
case 2: gAppMode = AppMode::Debug; break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
ImGui::Checkbox("ImGui Demo", &show_imgui_demo_window);
|
||||
ImGui::EndMainMenuBar();
|
||||
}
|
||||
|
||||
if (gAppMode == AppMode::AnimRuntime) {
|
||||
ImGui::Begin("Camera");
|
||||
ImGui::SliderFloat3("pos", state.camera.pos, -100.f, 100.f);
|
||||
ImGui::SliderFloat("near", &state.camera.near, 0.001f, 10.f);
|
||||
ImGui::SliderFloat("far", &state.camera.far, 1.0f, 10000.f);
|
||||
ImGui::SliderFloat("heading", &state.camera.heading, -180.0f, 180.f);
|
||||
ImGui::SliderFloat("pitch", &state.camera.pitch, -179.0f, 179.f);
|
||||
ImGui::End();
|
||||
|
||||
draw_grid();
|
||||
ImGui::SetNextWindowPos(ImVec2 (600, 60), ImGuiCond_FirstUseEver);
|
||||
ImGui::SetNextWindowSize(ImVec2 (300, 400), ImGuiCond_FirstUseEver);
|
||||
skinned_mesh.DrawDebugUi();
|
||||
animation_controller.DrawDebugUi();
|
||||
ImGui::SetNextWindowPos(ImVec2(600, 60), ImGuiCond_FirstUseEver);
|
||||
ImGui::SetNextWindowSize(ImVec2(300, 400), ImGuiCond_FirstUseEver);
|
||||
|
||||
if (!skinned_mesh.m_sync_track_override) {
|
||||
animation_controller.UpdateTime(state.time.frame);
|
||||
animation_controller.Evaluate();
|
||||
draw_grid();
|
||||
skinned_mesh.DrawDebugUi();
|
||||
animation_controller.DrawDebugUi();
|
||||
|
||||
if (!skinned_mesh.m_sync_track_override) {
|
||||
animation_controller.UpdateTime(state.time.frame);
|
||||
animation_controller.Evaluate();
|
||||
}
|
||||
skinned_mesh.CalcModelMatrices();
|
||||
|
||||
sgl_defaults();
|
||||
sgl_matrix_mode_projection();
|
||||
sgl_load_matrix((const float*)&state.camera.mtxProj);
|
||||
sgl_matrix_mode_modelview();
|
||||
sgl_load_matrix((const float*)&state.camera.mtxView);
|
||||
RenderSkinnedMesh(skinned_mesh);
|
||||
} else if (gAppMode == AppMode::GraphEditor) {
|
||||
ImGui::SetNextWindowPos(ImVec2(20, 20), ImGuiCond_FirstUseEver);
|
||||
ImGui::SetNextWindowSize(ImVec2(500, 400), ImGuiCond_FirstUseEver);
|
||||
|
||||
ImGui::Begin("Graph Editor", nullptr, ImGuiWindowFlags_MenuBar);
|
||||
AnimGraphEditorUpdate();
|
||||
ImGui::End();
|
||||
} else if (gAppMode == AppMode::Debug) {
|
||||
ImGui::SetNextWindowPos(ImVec2(460, 20), ImGuiCond_FirstUseEver);
|
||||
ImGui::Begin("Debug");
|
||||
ImGui::End();
|
||||
}
|
||||
skinned_mesh.CalcModelMatrices();
|
||||
|
||||
sgl_defaults();
|
||||
sgl_matrix_mode_projection();
|
||||
sgl_load_matrix((const float*)&state.camera.mtxProj);
|
||||
sgl_matrix_mode_modelview();
|
||||
sgl_load_matrix((const float*)&state.camera.mtxView);
|
||||
RenderSkinnedMesh(skinned_mesh);
|
||||
|
||||
// 3. Show the ImGui test window. Most of the sample code is in ImGui::ShowDemoWindow()
|
||||
if (show_imgui_demo_window) {
|
||||
@@ -457,7 +493,6 @@ int main() {
|
||||
ImGui::ShowDemoWindow();
|
||||
}
|
||||
|
||||
|
||||
// the sokol_gfx draw pass
|
||||
sg_begin_default_pass(&pass_action, cur_width, cur_height);
|
||||
sgl_draw();
|
||||
@@ -469,9 +504,8 @@ int main() {
|
||||
glfwPollEvents();
|
||||
}
|
||||
|
||||
// state.ozz = nullptr;
|
||||
|
||||
/* cleanup */
|
||||
ImNodes::DestroyContext();
|
||||
ImGui::DestroyContext();
|
||||
sgl_shutdown();
|
||||
sg_shutdown();
|
||||
|
||||
Reference in New Issue
Block a user