Compare commits

...

7 Commits

Author SHA1 Message Date
Martin Felis
5d8c1e289b Initial graph evaluations, added Float to Vec3 Node, minor editor tweaks. 2022-04-03 21:05:11 +02:00
Martin Felis
08ae84fcb4 New evaluation scheme.
- Animation Data are always referenced via a pointer in nodes.
- Animation Data storage pointers are injected from graph when node is evaluated.
- Node value outputs always stored in node.
- Node value inputs always referenced via pointer. References are created when instantiating the graph.
2022-04-01 13:19:54 +02:00
Martin Felis
abddbea62b Refactoring. 2022-03-25 12:05:56 +01:00
Martin Felis
6c0c0599f8 Removed old animation graph code. 2022-03-25 11:51:37 +01:00
Martin Felis
72a67195e6 Use socket pointers instead of indices for connections. 2022-03-25 11:23:03 +01:00
Martin Felis
84a9ef6f14 Use node pointer to check whether we use graph input/output nodes instead of indices. 2022-03-25 11:03:53 +01:00
Martin Felis
2f646bc5ec Simpler conversion SocketType <-> Strings. 2022-03-25 10:59:12 +01:00
30 changed files with 1688 additions and 2360 deletions

View File

@ -48,20 +48,18 @@ add_library(AnimTestbedCode OBJECT
src/SkinnedMesh.h
src/SyncTrack.cc
src/SyncTrack.h
src/AnimNode.cc
src/AnimNodes/AnimSamplerNode.cc
src/AnimNodes/SpeedScaleNode.cc
src/AnimNodes/BlendSpace1D.cc
src/AnimNodes/BlendNode.cc
src/AnimNodes/LockTranslationNode.cc
src/AnimationController.cc
src/ozzutils.cc
3rdparty/imgui/imgui.cpp
3rdparty/imgui/imgui_draw.cpp
3rdparty/imgui/imgui_widgets.cpp
3rdparty/imgui/misc/cpp/imgui_stdlib.cpp
3rdparty/imnodes/imnodes.cpp
src/AnimGraphResource.cc
src/AnimGraphResource.h src/AnimGraphEditor.cc src/AnimGraphEditor.h)
src/AnimGraph/AnimGraphResource.cc
src/AnimGraph/AnimGraphResource.h
src/AnimGraph/AnimGraphEditor.cc
src/AnimGraph/AnimGraphEditor.h
src/AnimGraph/AnimGraph.cc
src/AnimGraph/AnimGraph.h src/AnimGraph/AnimGraphNodes.cc src/AnimGraph/AnimGraphNodes.h src/AnimGraph/AnimGraphData.cc src/AnimGraph/AnimGraphData.h)
target_include_directories(
AnimTestbedCode
@ -96,7 +94,6 @@ add_executable(runtests)
target_sources(runtests PRIVATE
tests/AnimGraphResourceTests.cc
tests/AnimSampleNodeTests.cc
tests/SyncTrackTests.cc
tests/main.cc
)

270
src/AnimGraph/AnimGraph.cc Normal file
View File

@ -0,0 +1,270 @@
//
// Created by martin on 25.03.22.
//
#include "AnimGraph.h"
#include <cstring>
void AnimGraph::updateOrderedNodes() {
m_eval_ordered_nodes.clear();
updateOrderedNodesRecursive(0);
}
void AnimGraph::updateOrderedNodesRecursive(int node_index) {
AnimNode* node = m_nodes[node_index];
const std::vector<AnimGraphConnection> node_input_connections =
m_node_input_connections[node_index];
for (size_t i = 0, n = node_input_connections.size(); i < n; i++) {
int input_node_index =
getAnimNodeIndex(node_input_connections[i].m_source_node);
if (input_node_index == 1) {
continue;
}
updateOrderedNodesRecursive(input_node_index);
}
if (node_index != 0) {
m_eval_ordered_nodes.push_back(node);
}
}
void AnimGraph::markActiveNodes() {
for (size_t i = 0, n = m_nodes.size(); i < n; i++) {
m_nodes[i]->m_state = AnimNodeEvalState::Deactivated;
}
const std::vector<AnimGraphConnection>& graph_output_inputs =
m_node_input_connections[0];
for (size_t i = 0, n = graph_output_inputs.size(); i < n; i++) {
const AnimGraphConnection& graph_input = graph_output_inputs[i];
AnimNode* node = graph_input.m_source_node;
if (node != nullptr) {
node->m_state = AnimNodeEvalState::Activated;
}
}
for (size_t i = 0, n = m_eval_ordered_nodes.size(); i < n; i++) {
AnimNode* node = m_eval_ordered_nodes[i];
if (checkIsNodeActive(node)) {
int node_index = node->m_index;
node->MarkActiveInputs(m_node_input_connections[node_index]);
// Non-animation data inputs are always active.
for (size_t j = 0, nj = m_node_input_connections[node_index].size();
j < nj;
j++) {
const AnimGraphConnection& input =
m_node_input_connections[node_index][j];
if (input.m_source_node != nullptr
&& input.m_target_socket.m_type
!= SocketType::SocketTypeAnimation) {
input.m_source_node->m_state = AnimNodeEvalState::Activated;
}
}
}
}
}
void AnimGraph::prepareNodeEval(size_t node_index) {
AnimNode* node = m_nodes[node_index];
for (size_t i = 0, n = m_node_output_connections[node_index].size(); i < n;
i++) {
AnimGraphConnection& output_connection =
m_node_output_connections[node_index][i];
if (output_connection.m_source_socket.m_type
!= SocketType::SocketTypeAnimation) {
continue;
}
(*output_connection.m_source_socket.m_value.ptr_ptr) =
m_anim_data_work_buffer.peek();
m_anim_data_work_buffer.pop();
}
for (size_t i = 0, n = m_node_input_connections[node_index].size(); i < n;
i++) {
AnimGraphConnection& input_connection =
m_node_input_connections[node_index][i];
if (input_connection.m_source_socket.m_type
!= SocketType::SocketTypeAnimation) {
continue;
}
(*input_connection.m_target_socket.m_value.ptr_ptr) =
(*input_connection.m_source_socket.m_value.ptr_ptr);
}
}
void AnimGraph::finishNodeEval(size_t node_index) {
AnimNode* node = m_nodes[node_index];
for (size_t i = 0, n = m_node_input_connections[node_index].size(); i < n;
i++) {
AnimGraphConnection& input_connection =
m_node_input_connections[node_index][i];
if (input_connection.m_source_socket.m_type
!= SocketType::SocketTypeAnimation) {
continue;
}
m_anim_data_work_buffer.push(
static_cast<AnimData*>(input_connection.m_source_socket.m_value.ptr));
(*input_connection.m_source_socket.m_value.ptr_ptr) = nullptr;
}
}
void AnimGraph::evalInputNode() {
for (size_t i = 0, n = m_node_output_connections[1].size(); i < n; i++) {
AnimGraphConnection& graph_input_connection =
m_node_output_connections[1][i];
if (graph_input_connection.m_source_socket.m_type
!= SocketType::SocketTypeAnimation) {
memcpy(
*graph_input_connection.m_target_socket.m_value.ptr_ptr,
graph_input_connection.m_source_socket.m_value.ptr,
sizeof(void*));
printf("bla");
} else {
// TODO: how to deal with anim data outputs?
}
}
}
void AnimGraph::evalOutputNode() {
for (size_t i = 0, n = m_node_input_connections[0].size(); i < n; i++) {
AnimGraphConnection& graph_output_connection =
m_node_input_connections[0][i];
if (graph_output_connection.m_source_socket.m_type
!= SocketType::SocketTypeAnimation) {
memcpy(
graph_output_connection.m_target_socket.m_value.ptr,
graph_output_connection.m_source_socket.m_value.ptr,
graph_output_connection.m_target_socket.m_type_size);
} else {
// TODO: how to deal with anim data outputs?
}
}
}
void AnimGraph::evalSyncTracks() {
for (size_t i = m_eval_ordered_nodes.size() - 1; i >= 0; i--) {
AnimNode* node = m_eval_ordered_nodes[i];
int node_index = node->m_index;
if (node->m_state == AnimNodeEvalState::Deactivated) {
continue;
}
node->CalcSyncTrack(m_node_input_connections[node_index]);
}
}
void AnimGraph::updateTime(float dt) {
const std::vector<AnimGraphConnection> graph_output_inputs =
m_node_input_connections[0];
for (size_t i = 0, n = graph_output_inputs.size(); i < n; i++) {
AnimNode* node = graph_output_inputs[i].m_source_node;
if (node != nullptr) {
node->UpdateTime(node->m_time_now, node->m_time_now + dt);
}
}
for (size_t i = m_eval_ordered_nodes.size() - 1; i > 0; --i) {
AnimNode* node = m_eval_ordered_nodes[i];
if (node->m_state != AnimNodeEvalState::TimeUpdated) {
continue;
}
int node_index = node->m_index;
const std::vector<AnimGraphConnection> node_input_connections =
m_node_input_connections[node_index];
float node_time_now = node->m_time_now;
float node_time_last = node->m_time_last;
for (size_t i = 0, n = node_input_connections.size(); i < n; i++) {
AnimNode* input_node = node_input_connections[i].m_source_node;
// Only propagate time updates via animation sockets.
if (input_node != nullptr
&& node_input_connections[i].m_target_socket.m_type
== SocketType::SocketTypeAnimation
&& input_node->m_state == AnimNodeEvalState::Activated) {
input_node->UpdateTime(node_time_last, node_time_now);
}
}
}
}
void AnimGraph::evaluate() {
constexpr int eval_stack_size = 5;
int eval_stack_index = eval_stack_size;
AnimData eval_buffers[eval_stack_size];
AnimData* eval_stack[eval_stack_size];
for (size_t i = 0; i < eval_stack_size; i++) {
eval_stack[i] = &eval_buffers[i];
}
for (int i = 0, n = m_eval_ordered_nodes.size(); i < n; i++) {
AnimNode* node = m_eval_ordered_nodes[i];
if (node->m_state == AnimNodeEvalState::Deactivated) {
continue;
}
prepareNodeEval(node->m_index);
node->Evaluate();
finishNodeEval(node->m_index);
}
}
Socket* AnimGraph::getInputSocket(const std::string& name) {
for (size_t i = 0, n = m_node_output_connections[1].size(); i < n; i++) {
AnimGraphConnection& connection = m_node_output_connections[1][i];
if (connection.m_source_socket.m_name == name) {
return &connection.m_source_socket;
}
}
return nullptr;
}
Socket* AnimGraph::getOutputSocket(const std::string& name) {
for (size_t i = 0, n = m_node_input_connections[0].size(); i < n; i++) {
AnimGraphConnection& connection = m_node_input_connections[0][i];
if (connection.m_target_socket.m_name == name) {
return &connection.m_target_socket;
}
}
return nullptr;
}
const Socket* AnimGraph::getInputSocket(const std::string& name) const {
for (size_t i = 0, n = m_node_output_connections[1].size(); i < n; i++) {
const AnimGraphConnection& connection = m_node_output_connections[1][i];
if (connection.m_source_socket.m_name == name) {
return &connection.m_source_socket;
}
}
return nullptr;
}
const Socket* AnimGraph::getOutputSocket(const std::string& name) const {
for (size_t i = 0, n = m_node_input_connections[0].size(); i < n; i++) {
const AnimGraphConnection& connection = m_node_input_connections[0][i];
if (connection.m_target_socket.m_name == name) {
return &connection.m_target_socket;
}
}
return nullptr;
}

153
src/AnimGraph/AnimGraph.h Normal file
View File

@ -0,0 +1,153 @@
//
// Created by martin on 25.03.22.
//
#ifndef ANIMTESTBED_ANIMGRAPH_H
#define ANIMTESTBED_ANIMGRAPH_H
#include "AnimGraphData.h"
#include "AnimGraphNodes.h"
struct AnimDataWorkBuffer {
std::vector<AnimData> m_eval_anim_data;
std::vector<AnimData*> m_available_data;
AnimDataWorkBuffer(size_t stack_size) {
m_eval_anim_data.resize(stack_size);
m_available_data.resize(stack_size);
for (size_t i = 0; i < stack_size; i++) {
m_available_data[i] = &m_eval_anim_data[i];
}
}
void push(AnimData* anim_data) {
assert (m_available_data.size() < m_eval_anim_data.size());
m_available_data.push_back(anim_data);
}
void pop() {
assert (m_available_data.size() > 0);
m_available_data.pop_back();
}
AnimData* peek() {
return m_available_data.back();
}
};
//
// AnimGraph (Runtime)
//
struct AnimGraph {
AnimData m_local_transforms;
std::vector<AnimNode*> m_nodes;
std::vector<AnimNode*> m_eval_ordered_nodes;
std::vector<std::vector<AnimGraphConnection> > m_node_input_connections;
std::vector<std::vector<AnimGraphConnection> > m_node_output_connections;
NodeSocketAccessorBase* m_socket_accessor;
char* m_input_buffer = nullptr;
char* m_output_buffer = nullptr;
std::vector<Socket>& getGraphOutputs() { return m_socket_accessor->m_inputs; }
std::vector<Socket>& getGraphInputs() { return m_socket_accessor->m_outputs; }
AnimDataWorkBuffer m_anim_data_work_buffer = AnimDataWorkBuffer(5);
~AnimGraph() {
delete[] m_input_buffer;
delete[] m_output_buffer;
for (int i = 0; i < m_nodes.size(); i++) {
delete m_nodes[i];
}
delete m_socket_accessor;
}
void updateOrderedNodes();
void updateOrderedNodesRecursive(int node_index);
void markActiveNodes();
bool checkIsNodeActive(AnimNode* node) {
return node->m_state != AnimNodeEvalState::Deactivated;
}
void evalInputNode();
void prepareNodeEval(size_t node_index);
void finishNodeEval(size_t node_index);
void evalOutputNode();
void evalSyncTracks();
void updateTime(float dt);
void evaluate();
void reset() {
for (size_t i = 0, n = m_nodes.size(); i < n; i++) {
m_nodes[i]->m_time_now = 0.f;
m_nodes[i]->m_time_last = 0.f;
m_nodes[i]->m_state = AnimNodeEvalState::Undefined;
}
}
Socket* getInputSocket(const std::string& name);
Socket* getOutputSocket(const std::string& name);
const Socket* getInputSocket(const std::string& name) const;
const Socket* getOutputSocket(const std::string& name) const;
void* getInputPtr(const std::string& name) const {
const Socket* input_socket = getInputSocket(name);
if (input_socket != nullptr) {
return input_socket->m_value.ptr;
}
return nullptr;
}
int getNodeEvalOrderIndex(const AnimNode* node) {
for (size_t i = 0, n = m_eval_ordered_nodes.size(); i < n; i++) {
if (m_eval_ordered_nodes[i] == node) {
return i;
}
}
return -1;
}
const AnimNode* getAnimNodeForInput (
size_t node_index,
const std::string& input_name) const {
assert(node_index < m_nodes.size());
const std::vector<AnimGraphConnection>& input_connection = m_node_input_connections[node_index];
for (size_t i = 0, n = input_connection.size(); i < n; i++) {
if (input_connection[i].m_target_socket.m_name == input_name) {
return input_connection[i].m_source_node;
}
}
return nullptr;
}
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; i < m_nodes.size(); i++) {
if (m_nodes[i] == node) {
return i;
}
}
return -1;
}
};
#endif //ANIMTESTBED_ANIMGRAPH_H

View File

@ -0,0 +1,5 @@
//
// Created by martin on 25.03.22.
//
#include "AnimGraphData.h"

View File

@ -0,0 +1,248 @@
//
// Created by martin on 25.03.22.
//
#ifndef ANIMTESTBED_ANIMGRAPHDATA_H
#define ANIMTESTBED_ANIMGRAPHDATA_H
#include <string>
#include <vector>
#include <iostream>
#include "SyncTrack.h"
//
// Data types
//
struct AnimData {
float m_bone_transforms[16];
};
typedef float Vec3[3];
typedef float Quat[4];
enum class SocketType {
SocketTypeUndefined = 0,
SocketTypeBool,
SocketTypeAnimation,
SocketTypeFloat,
SocketTypeVec3,
SocketTypeQuat,
SocketTypeString,
SocketTypeLast
};
static const char* SocketTypeNames[] =
{"", "Bool", "Animation", "Float", "Vec3", "Quat", "String"};
enum SocketFlags { SocketFlagAffectsTime = 1 };
struct Socket {
std::string m_name;
SocketType m_type = SocketType::SocketTypeUndefined;
union SocketValue {
void* ptr;
void** ptr_ptr;
};
SocketValue m_value = {nullptr};
int m_flags = 0;
size_t m_type_size = 0;
};
struct NodeSocketAccessorBase {
std::vector<Socket> m_properties;
std::vector<Socket> m_inputs;
std::vector<Socket> m_outputs;
NodeSocketAccessorBase() {}
virtual ~NodeSocketAccessorBase() {}
virtual void UpdateFlags(){};
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;
}
const Socket* FindSocket(
const std::vector<Socket>& sockets,
const std::string& name) const {
const 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) {
const Socket* socket = FindSocket(sockets, name);
if (socket == nullptr) {
return SocketType::SocketTypeUndefined;
}
return socket->m_type;
}
size_t GetSocketIndex(
const std::vector<Socket>& sockets,
const std::string& name) const {
for (size_t i = 0, n = sockets.size(); i < n; i++) {
if (sockets[i].m_name == name) {
return i;
}
}
return -1;
}
template <typename T>
T GetSocketValue(
const std::vector<Socket>& sockets,
const std::string& name,
T default_value) {
const Socket* socket = FindSocket(sockets, name);
if (socket == nullptr) {
return default_value;
}
return *static_cast<T*>(socket->m_value.ptr);
}
template <typename T>
void SetSocketValue(
const std::vector<Socket>& sockets,
const std::string& name,
const T& value) {
const Socket* socket = FindSocket(sockets, name);
if (socket == nullptr) {
std::cerr << "Error: could not set value of socket with name " << name
<< ": no socket found." << std::endl;
return;
}
*static_cast<T*>(socket->m_value.ptr) = value;
}
template <typename T>
bool RegisterSocket(
std::vector<Socket>& sockets,
const std::string& name,
T* value_ptr,
int flags = 0) {
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;
socket->m_type_size = sizeof(T);
socket->m_flags = flags;
if constexpr (std::is_same<T, float>::value) {
socket->m_type = SocketType::SocketTypeFloat;
} else if constexpr (std::is_same<T, bool>::value) {
socket->m_type = SocketType::SocketTypeBool;
} else if constexpr (std::is_same<T, Vec3>::value) {
socket->m_type = SocketType::SocketTypeVec3;
} else if constexpr (std::is_same<T, Quat>::value) {
socket->m_type = SocketType::SocketTypeQuat;
} else if constexpr (std::is_same<T, AnimData>::value) {
socket->m_type = SocketType::SocketTypeAnimation;
} else if constexpr (std::is_same<T, std::string>::value) {
socket->m_type = SocketType::SocketTypeString;
} else if constexpr (std::is_same<T, float*>::value) {
socket->m_type = SocketType::SocketTypeFloat;
} else if constexpr (std::is_same<T, bool*>::value) {
socket->m_type = SocketType::SocketTypeBool;
} else if constexpr (std::is_same<T, Vec3*>::value) {
socket->m_type = SocketType::SocketTypeVec3;
} else if constexpr (std::is_same<T, Quat*>::value) {
socket->m_type = SocketType::SocketTypeQuat;
} else if constexpr (std::is_same<T, AnimData*>::value) {
socket->m_type = SocketType::SocketTypeAnimation;
} else if constexpr (std::is_same<T, std::string*>::value) {
socket->m_type = SocketType::SocketTypeString;
} else {
std::cerr << "Cannot register socket, invalid type." << std::endl;
return false;
}
socket->m_value.ptr = value_ptr;
return true;
}
template <typename T>
bool RegisterProperty(const std::string& name, T* value) {
return RegisterSocket(m_properties, name, value);
}
template <typename T>
void SetProperty(const std::string& name, const T& value) {
SetSocketValue(m_properties, name, value);
}
template <typename T>
T GetProperty(const std::string& name, T default_value) {
return GetSocketValue(m_properties, name, default_value);
}
SocketType GetPropertyType(const std::string& name) {
return GetSocketType(m_properties, name);
}
template <typename T>
bool RegisterInput(const std::string& name, T** value, int flags = 0) {
return RegisterSocket(m_inputs, name, value, flags);
}
template <typename T>
T* GetInput(const std::string& name, T* value) {
return GetSocketValue(m_inputs, name, value);
}
Socket* FindInputSocket(const std::string& name) {
return FindSocket(m_inputs, name);
}
SocketType GetInputType(const std::string& name) {
return GetSocketType(m_inputs, name);
}
size_t GetInputIndex(const std::string& name) {
return GetSocketIndex(m_inputs, name);
}
template <typename T>
bool RegisterOutput(const std::string& name, T* value, int flags = 0) {
return RegisterSocket(m_outputs, name, value, flags);
}
template <typename T>
bool RegisterOutput(const std::string& name, T** value, int flags = 0) {
return RegisterSocket(m_outputs, name, value, flags);
}
SocketType GetOutputType(const std::string& name) {
return GetSocketType(m_outputs, name);
}
Socket* FindOutputSocket(const std::string& name) {
return FindSocket(m_outputs, name);
}
size_t GetOutputIndex(const std::string& name) {
return GetSocketIndex(m_outputs, name);
}
};
template <typename T>
struct NodeSocketAccessor : public NodeSocketAccessorBase {
virtual ~NodeSocketAccessor() {}
};
#endif //ANIMTESTBED_ANIMGRAPHDATA_H

View File

@ -5,9 +5,9 @@
#include "AnimGraphEditor.h"
#include "AnimGraphResource.h"
#include "imgui.h"
#include "imnodes.h"
using namespace AniGraph;
#include "misc/cpp/imgui_stdlib.h"
ImNodesPinShape sGetSocketShapeFromSocketType(const SocketType& socket_type) {
switch (socket_type) {
@ -28,7 +28,42 @@ ImNodesPinShape sGetSocketShapeFromSocketType(const SocketType& socket_type) {
return ImNodesPinShape_Quad;
}
void AnimGraphEditorRenderSidebar(AnimNodeResource& node_resource) {
void NodeSocketEditor(Socket& socket) {
int mode_current = static_cast<int>(socket.m_type);
ImGui::InputText("Name", &socket.m_name);
if (ImGui::Combo(
"Type",
&mode_current,
SocketTypeNames,
sizeof(SocketTypeNames) / sizeof(char*))) {
socket.m_type = static_cast<SocketType>(mode_current);
}
}
void RemoveConnectionsForSocket(
AnimGraphResource& graph_resource,
AnimNodeResource& node_resource,
Socket& socket) {
std::vector<AnimGraphConnectionResource>::iterator iter =
graph_resource.m_connections.begin();
while (iter != graph_resource.m_connections.end()) {
// TODO adjust for refactor
assert(false);
// AnimGraphConnectionResource& connection = *iter;
// if (connection.m_source_node == &node_resource
// && connection.m_source_socket == &socket) {
// iter = graph_resource.m_connections.erase(iter);
// } else {
// iter++;
// }
}
}
void AnimGraphEditorRenderSidebar(
AnimGraphResource& graph_resource,
AnimNodeResource& node_resource) {
ImGui::Text("[%s]", node_resource.m_type_name.c_str());
char node_name_buffer[256];
@ -72,6 +107,48 @@ void AnimGraphEditorRenderSidebar(AnimNodeResource& node_resource) {
}
}
}
if (&node_resource == &graph_resource.getGraphOutputNode()) {
ImGui::Text("Outputs");
// Graph outputs are the inputs of the output node!
std::vector<Socket>& outputs = node_resource.m_socket_accessor->m_inputs;
std::vector<Socket>::iterator iter = outputs.begin();
while (iter != outputs.end()) {
Socket& output = *iter;
ImGui::PushID(&output);
NodeSocketEditor(output);
if (ImGui::Button("X")) {
RemoveConnectionsForSocket(graph_resource, node_resource, output);
iter = outputs.erase(iter);
} else {
iter++;
}
ImGui::PopID();
}
}
if (&node_resource == &graph_resource.getGraphInputNode()) {
ImGui::Text("Inputs");
// Graph inputs are the outputs of the input node!
std::vector<Socket>& inputs = node_resource.m_socket_accessor->m_outputs;
std::vector<Socket>::iterator iter = inputs.begin();
while (iter != inputs.end()) {
Socket& input = *iter;
ImGui::PushID(&input);
NodeSocketEditor(input);
if (ImGui::Button("X")) {
RemoveConnectionsForSocket(graph_resource, node_resource, input);
iter = inputs.erase(iter);
} else {
iter++;
}
ImGui::PopID();
}
}
}
void AnimGraphEditorUpdate() {
@ -140,6 +217,14 @@ void AnimGraphEditorUpdate() {
node_type_name = "SpeedScale";
}
if (ImGui::MenuItem("MathAddNode")) {
node_type_name = "MathAddNode";
}
if (ImGui::MenuItem("MathFloatToVec3Node")) {
node_type_name = "MathFloatToVec3Node";
}
if (node_type_name != "") {
AnimNodeResource node_resource =
AnimNodeResourceFactory(node_type_name);
@ -158,12 +243,13 @@ void AnimGraphEditorUpdate() {
AnimNodeResource& node_resource = graph_resource.m_nodes[i];
ImNodes::BeginNode(i);
ImGui::PushItemWidth(110.0f);
// Header
ImNodes::BeginNodeTitleBar();
if (i == 0) {
if (&node_resource == &graph_resource.getGraphOutputNode()) {
ImGui::TextUnformatted("Graph Outputs");
} else if (i == 1) {
} else if (&node_resource == &graph_resource.getGraphInputNode()) {
ImGui::TextUnformatted("Graph Inputs");
} else {
ImGui::TextUnformatted(node_resource.m_type_name.c_str());
@ -187,6 +273,16 @@ void AnimGraphEditorUpdate() {
socket_color);
ImGui::TextUnformatted(socket.m_name.c_str());
bool socket_connected = graph_resource.isSocketConnected(node_resource, socket.m_name);
if (!socket_connected &&
(socket.m_type == SocketType::SocketTypeFloat)) {
ImGui::SameLine();
float socket_value = 0.f;
ImGui::PushItemWidth(100.0f - ImGui::CalcTextSize(socket.m_name.c_str()).x);
ImGui::DragFloat("##hidelabel", &socket_value, 0.01f);
ImGui::PopItemWidth();
}
ImNodes::PushAttributeFlag(
ImNodesAttributeFlags_EnableLinkDetachWithDragClick);
ImNodes::EndInputAttribute();
@ -239,6 +335,8 @@ void AnimGraphEditorUpdate() {
ImVec2 node_pos = ImNodes::GetNodeGridSpacePos(i);
node_resource.m_position[0] = node_pos[0];
node_resource.m_position[1] = node_pos[1];
ImGui::PopItemWidth();
ImNodes::EndNode();
// Ensure flags such as SocketFlagAffectsTime are properly set.
@ -246,14 +344,27 @@ void AnimGraphEditorUpdate() {
}
for (size_t i = 0, n = graph_resource.m_connections.size(); i < n; i++) {
const AnimGraphConnection& connection = graph_resource.m_connections[i];
const AnimGraphConnectionResource& connection =
graph_resource.m_connections[i];
int start_attr, end_attr;
const AnimNodeResource& source_node =
graph_resource.m_nodes[connection.source_node_index];
int source_socket_index = source_node.m_socket_accessor->GetOutputIndex(
connection.source_socket_name);
const AnimNodeResource& target_node =
graph_resource.m_nodes[connection.target_node_index];
int target_socket_index = target_node.m_socket_accessor->GetInputIndex(
connection.target_socket_name);
start_attr = GenerateOutputAttributeId(
connection.m_source_node_index,
connection.m_source_socket_index);
connection.source_node_index,
source_socket_index);
end_attr = GenerateInputAttributeId(
connection.m_target_node_index,
connection.m_target_socket_index);
connection.target_node_index,
target_socket_index);
ImNodes::Link(i, start_attr, end_attr);
}
@ -273,12 +384,18 @@ void AnimGraphEditorUpdate() {
int node_end_input_index;
SplitInputAttributeId(end_attr, &node_end_id, &node_end_input_index);
AnimGraphConnection connection;
connection.m_source_node_index = node_start_id;
connection.m_source_socket_index = node_start_output_index;
AnimGraphConnectionResource connection;
connection.source_node_index = node_start_id;
const AnimNodeResource& source_node = graph_resource.m_nodes[node_start_id];
connection.source_socket_name =
source_node.m_socket_accessor->m_outputs[node_start_output_index]
.m_name;
connection.target_node_index = node_end_id;
const AnimNodeResource& target_node = graph_resource.m_nodes[node_end_id];
connection.target_socket_name =
target_node.m_socket_accessor->m_inputs[node_end_input_index].m_name;
connection.m_target_node_index = node_end_id;
connection.m_target_socket_index = node_end_input_index;
graph_resource.m_connections.push_back(connection);
}
@ -301,7 +418,7 @@ void AnimGraphEditorUpdate() {
if (selected_nodes[0] < graph_resource.m_nodes.size()) {
AnimNodeResource& selected_node =
graph_resource.m_nodes[selected_nodes[0]];
AnimGraphEditorRenderSidebar(selected_node);
AnimGraphEditorRenderSidebar(graph_resource, selected_node);
}
}

View File

@ -0,0 +1,30 @@
//
// Created by martin on 11.02.22.
//
#ifndef ANIMTESTBED_ANIMGRAPHEDITOR_H
#define ANIMTESTBED_ANIMGRAPHEDITOR_H
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;
}
void AnimGraphEditorUpdate();
#endif //ANIMTESTBED_ANIMGRAPHEDITOR_H

View File

@ -0,0 +1,5 @@
//
// Created by martin on 25.03.22.
//
#include "AnimGraphNodes.h"

View File

@ -0,0 +1,321 @@
//
// Created by martin on 25.03.22.
//
#ifndef ANIMTESTBED_ANIMGRAPHNODES_H
#define ANIMTESTBED_ANIMGRAPHNODES_H
#include <vector>
#include "AnimGraphData.h"
#include "SyncTrack.h"
struct AnimNode;
struct NodeInput {
AnimNode* m_node;
SocketType m_type = SocketType::SocketTypeUndefined;
Socket* m_node_output_socket;
std::string m_input_name;
};
enum class AnimNodeEvalState {
Undefined,
Deactivated,
Activated,
SyncTrackUpdated,
TimeUpdated,
Evaluated
};
struct AnimGraphConnection {
AnimNode* m_source_node = nullptr;
Socket m_source_socket;
AnimNode* m_target_node = nullptr;
Socket m_target_socket;
};
struct AnimNode {
std::string m_name;
std::string m_node_type_name;
float m_time_now = 0.f;
float m_time_last = 0.f;
size_t m_index = -1;
AnimNodeEvalState m_state = AnimNodeEvalState::Undefined;
SyncTrack m_sync_track;
virtual ~AnimNode(){};
virtual void MarkActiveInputs(const std::vector<AnimGraphConnection>& inputs) {
for (size_t i = 0, n = inputs.size(); i < n; i++) {
AnimNode* input_node = inputs[i].m_source_node;
if (input_node != nullptr) {
input_node->m_state = AnimNodeEvalState::Activated;
}
}
}
virtual void CalcSyncTrack(const std::vector<AnimGraphConnection>& inputs) {
for (size_t i = 0, n = inputs.size(); i < n; i++) {
AnimNode* input_node = inputs[i].m_source_node;
if (input_node != nullptr
&& inputs[i].m_source_socket.m_type == SocketType::SocketTypeAnimation
&& input_node->m_state != AnimNodeEvalState::Deactivated) {
m_sync_track = input_node->m_sync_track;
return;
}
}
}
virtual void UpdateTime(float time_last, float time_now) {
m_time_last = time_last;
m_time_now = time_now;
m_state = AnimNodeEvalState::TimeUpdated;
}
virtual void Evaluate(){};
};
//
// BlendTreeNode
//
struct BlendTreeNode : public AnimNode {};
template <>
struct NodeSocketAccessor<BlendTreeNode> : public NodeSocketAccessorBase {
NodeSocketAccessor(AnimNode* node_) {}
};
//
// Blend2Node
//
struct Blend2Node : public AnimNode {
AnimData* i_input0 = nullptr;
AnimData* i_input1 = nullptr;
AnimData* o_output = nullptr;
float* i_blend_weight = nullptr;
bool m_sync_blend = false;
virtual void MarkActiveInputs(const std::vector<AnimGraphConnection>& inputs) override {
for (size_t i = 0, n = inputs.size(); i < n; i++) {
AnimNode* input_node = inputs[i].m_source_node;
if (input_node == nullptr) {
continue;
}
if (inputs[i].m_target_socket.m_name == "Input0" && *i_blend_weight < 0.999) {
input_node->m_state = AnimNodeEvalState::Activated;
continue;
}
if (inputs[i].m_target_socket.m_name == "Input1" && *i_blend_weight > 0.001) {
input_node->m_state = AnimNodeEvalState::Activated;
continue;
}
}
}
virtual void UpdateTime(float dt, std::vector<NodeInput>& inputs) {
if (!m_sync_blend) {
m_time_now = m_time_now + dt;
}
for (size_t i = 0, n = inputs.size(); i < n; i++) {
AnimNode* input_node = inputs[i].m_node;
if (input_node == nullptr) {
continue;
}
if (input_node->m_state != AnimNodeEvalState::Deactivated) {
if (!m_sync_blend) {
input_node->m_time_now = m_time_now;
}
input_node->m_state = AnimNodeEvalState::TimeUpdated;
continue;
}
}
}
};
template <>
struct NodeSocketAccessor<Blend2Node> : public NodeSocketAccessorBase {
NodeSocketAccessor(AnimNode* node_) {
Blend2Node* node = dynamic_cast<Blend2Node*>(node_);
RegisterInput("Input0", &node->i_input0);
RegisterInput("Input1", &node->i_input1);
RegisterInput(
"Weight",
&node->i_blend_weight,
SocketFlags::SocketFlagAffectsTime);
RegisterOutput("Output", &node->o_output);
RegisterProperty("Sync", &node->m_sync_blend);
}
virtual void UpdateFlags() override {
Socket* weight_input_socket = FindSocket(m_inputs, "Weight");
assert(weight_input_socket != nullptr);
if (GetProperty<bool>("Sync", false) == true) {
weight_input_socket->m_flags = SocketFlags::SocketFlagAffectsTime;
} else {
weight_input_socket->m_flags = 0;
}
}
};
//
// SpeedScaleNode
//
struct SpeedScaleNode : public AnimNode {
AnimData* i_input = nullptr;
AnimData* i_output = nullptr;
float* i_speed_scale = nullptr;
virtual void UpdateTime(float time_last, float time_now) {
m_time_last = time_last;
m_time_now = time_last + (time_now - time_last) * (*i_speed_scale);
m_state = AnimNodeEvalState::TimeUpdated;
}
};
template <>
struct NodeSocketAccessor<SpeedScaleNode> : public NodeSocketAccessorBase {
NodeSocketAccessor(AnimNode* node_) {
SpeedScaleNode* node = dynamic_cast<SpeedScaleNode*>(node_);
RegisterInput(
"SpeedScale",
&node->i_speed_scale,
SocketFlags::SocketFlagAffectsTime);
RegisterInput("Input", &node->i_input);
RegisterOutput("Output", &node->i_output);
}
};
//
// AnimSamplerNode
//
struct AnimSamplerNode : public AnimNode {
AnimData* o_output = nullptr;
std::string m_filename;
};
template <>
struct NodeSocketAccessor<AnimSamplerNode> : public NodeSocketAccessorBase {
NodeSocketAccessor(AnimNode* node_) {
AnimSamplerNode* node = dynamic_cast<AnimSamplerNode*>(node_);
RegisterOutput("Output", &node->o_output);
RegisterProperty("Filename", &node->m_filename);
}
};
//
// MathAddNode
//
struct MathAddNode : public AnimNode {
float* i_input0 = nullptr;
float* i_input1 = nullptr;
float o_output = 0.f;
void Evaluate() override {
assert (i_input0 != nullptr);
assert (i_input1 != nullptr);
o_output = *i_input0 + *i_input1;
}
};
template <>
struct NodeSocketAccessor<MathAddNode> : public NodeSocketAccessorBase {
NodeSocketAccessor(AnimNode* node_) {
MathAddNode* node = dynamic_cast<MathAddNode*>(node_);
RegisterInput("Input0", &node->i_input0);
RegisterInput("Input1", &node->i_input1);
RegisterOutput("Output", &node->o_output);
}
};
//
// MathFloatToVec3Node
//
struct MathFloatToVec3Node : public AnimNode {
float* i_input0 = nullptr;
float* i_input1 = nullptr;
float* i_input2 = nullptr;
Vec3 o_output = {0.f, 0.f, 0.f};
void Evaluate() override {
assert (i_input0 != nullptr);
assert (i_input1 != nullptr);
assert (i_input2 != nullptr);
o_output[0] = *i_input0;
o_output[1] = *i_input1;
o_output[2] = *i_input2;
}
};
template <>
struct NodeSocketAccessor<MathFloatToVec3Node> : public NodeSocketAccessorBase {
NodeSocketAccessor(AnimNode* node_) {
MathFloatToVec3Node* node = dynamic_cast<MathFloatToVec3Node*>(node_);
RegisterInput("Input0", &node->i_input0);
RegisterInput("Input1", &node->i_input1);
RegisterInput("Input2", &node->i_input2);
RegisterOutput("Output", &node->o_output);
}
};
static inline AnimNode* AnimNodeFactory(const std::string& name) {
AnimNode* result;
if (name == "Blend2") {
result = new Blend2Node;
} else if (name == "SpeedScale") {
result = new SpeedScaleNode;
} else if (name == "AnimSampler") {
result = new AnimSamplerNode;
} else if (name == "BlendTree") {
result = new BlendTreeNode;
} else if (name == "MathAddNode") {
result = new MathAddNode;
} else if (name == "MathFloatToVec3Node") {
result = new MathFloatToVec3Node;
}
if (result != nullptr) {
result->m_node_type_name = name;
return result;
}
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 if (node_type_name == "BlendTree") {
return new NodeSocketAccessor<BlendTreeNode>(node);
} else if (node_type_name == "MathAddNode") {
return new NodeSocketAccessor<MathAddNode>(node);
} else if (node_type_name == "MathFloatToVec3Node") {
return new NodeSocketAccessor<MathFloatToVec3Node>(node);
} else {
std::cerr << "Invalid node type name " << node_type_name << "."
<< std::endl;
}
return nullptr;
}
#endif //ANIMTESTBED_ANIMGRAPHNODES_H

View File

@ -8,39 +8,18 @@
#include "3rdparty/json/json.hpp"
namespace AniGraph {
using json = nlohmann::json;
//
// Socket <-> json
//
std::string sSocketTypeToStr(SocketType pin_type) {
std::string result = "unknown";
switch (pin_type) {
case SocketType::SocketTypeBool:
result = "Bool";
break;
case SocketType::SocketTypeAnimation:
result = "Animation";
break;
case SocketType::SocketTypeFloat:
result = "Float";
break;
case SocketType::SocketTypeVec3:
result = "Vec3";
break;
case SocketType::SocketTypeQuat:
result = "Quat";
break;
case SocketType::SocketTypeString:
result = "String";
break;
default:
result = "Unknown";
if (pin_type < SocketType::SocketTypeUndefined
|| pin_type >= SocketType::SocketTypeLast) {
return "Unknown";
}
return result;
return SocketTypeNames[static_cast<int>(pin_type)];
}
json sSocketToJson(const Socket& socket) {
@ -190,30 +169,34 @@ AnimNodeResource sAnimGraphNodeFromJson(const json& json_node) {
}
//
// AnimGraphConnection <-> Json
// AnimGraphConnectionResource <-> Json
//
json sAnimGraphConnectionToJson(const AnimGraphConnection& connection) {
json sAnimGraphConnectionToJson(
const AnimGraphResource& graph_resource,
const AnimGraphConnectionResource& connection) {
json result;
result["type"] = "AnimGraphConnection";
result["type"] = "AnimGraphConnectionResource";
result["source_node_index"] = connection.m_source_node_index;
result["source_socket_index"] = connection.m_source_socket_index;
result["source_node_index"] = connection.source_node_index;
result["source_socket_name"] = connection.source_socket_name;
result["target_node_index"] = connection.m_target_node_index;
result["target_socket_index"] = connection.m_target_socket_index;
result["target_node_index"] = connection.target_node_index;
result["target_socket_name"] = connection.target_socket_name;
return result;
}
AnimGraphConnection sAnimGraphConnectionFromJson(const json& json_node) {
AnimGraphConnection connection;
AnimGraphConnectionResource sAnimGraphConnectionFromJson(
const AnimGraphResource& graph_resource,
const json& json_node) {
AnimGraphConnectionResource connection;
connection.m_source_node_index = json_node["source_node_index"];
connection.m_source_socket_index = json_node["source_socket_index"];
connection.source_node_index = json_node["source_node_index"];
connection.source_socket_name = json_node["source_socket_name"];
connection.m_target_node_index = json_node["target_node_index"];
connection.m_target_socket_index = json_node["target_socket_index"];
connection.target_node_index = json_node["target_node_index"];
connection.target_socket_name = json_node["target_socket_name"];
return connection;
}
@ -256,8 +239,8 @@ bool AnimGraphResource::saveToFile(const char* filename) const {
}
for (size_t i = 0; i < m_connections.size(); i++) {
const AnimGraphConnection& connection = m_connections[i];
result["connections"][i] = sAnimGraphConnectionToJson(connection);
const AnimGraphConnectionResource& connection = m_connections[i];
result["connections"][i] = sAnimGraphConnectionToJson(*this, connection);
}
// Graph inputs and outputs
@ -307,6 +290,8 @@ bool AnimGraphResource::loadFromFile(const char* filename) {
clearNodes();
m_name = json_data["name"];
// Load nodes
for (size_t i = 0; i < json_data["nodes"].size(); i++) {
const json& json_node = json_data["nodes"][i];
if (json_node["type"] != "AnimNodeResource") {
@ -320,20 +305,7 @@ bool AnimGraphResource::loadFromFile(const char* filename) {
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);
}
// Setup graph inputs and outputs
const json& graph_outputs = json_data["nodes"][0]["inputs"];
for (size_t i = 0; i < graph_outputs.size(); i++) {
AnimNodeResource& graph_node = m_nodes[0];
@ -348,149 +320,199 @@ bool AnimGraphResource::loadFromFile(const char* filename) {
sJsonToSocket(graph_inputs[i]));
}
// Load connections
for (size_t i = 0; i < json_data["connections"].size(); i++) {
const json& json_connection = json_data["connections"][i];
if (json_connection["type"] != "AnimGraphConnectionResource") {
std::cerr
<< "Invalid json object. Expected type 'AnimGraphConnectionResource' "
"but got '"
<< json_connection["type"] << "'." << std::endl;
return false;
}
AnimGraphConnectionResource connection =
sAnimGraphConnectionFromJson(*this, json_connection);
m_connections.push_back(connection);
}
return true;
}
void AnimGraph::updateOrderedNodes() {
std::vector<int> node_index_stack;
node_index_stack.push_back(0);
AnimGraph AnimGraphResource::createInstance() const {
AnimGraph result;
m_eval_ordered_nodes.clear();
createRuntimeNodeInstances(result);
prepareGraphIOData(result);
connectRuntimeNodes(result);
while (node_index_stack.size() > 0) {
std::vector<NodeInput>& node_inputs =
m_node_inputs[node_index_stack.back()];
node_index_stack.pop_back();
result.updateOrderedNodes();
result.reset();
for (size_t i = 0, n = node_inputs.size(); i < n; i++) {
AnimNode* input_node = node_inputs[i].m_node;
if (input_node == nullptr) {
return result;
}
void AnimGraphResource::createRuntimeNodeInstances(AnimGraph& instance) const {
for (int i = 0; i < m_nodes.size(); i++) {
const AnimNodeResource& node_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;
node->m_index = i;
instance.m_nodes.push_back(node);
// runtime node connections
instance.m_node_input_connections.push_back(
std::vector<AnimGraphConnection>());
instance.m_node_output_connections.push_back(
std::vector<AnimGraphConnection>());
}
}
void AnimGraphResource::prepareGraphIOData(AnimGraph& instance) const {
instance.m_socket_accessor =
AnimNodeAccessorFactory("BlendTree", instance.m_nodes[0]);
instance.m_socket_accessor->m_outputs =
m_nodes[1].m_socket_accessor->m_outputs;
instance.m_socket_accessor->m_inputs = m_nodes[0].m_socket_accessor->m_inputs;
// inputs
int input_block_size = 0;
std::vector<Socket>& graph_inputs = instance.getGraphInputs();
for (int i = 0; i < graph_inputs.size(); i++) {
input_block_size += sizeof(void*);
}
instance.m_input_buffer = new char[input_block_size];
memset(instance.m_input_buffer, 0, input_block_size);
int input_block_offset = 0;
for (int i = 0; i < graph_inputs.size(); i++) {
graph_inputs[i].m_value.ptr =
(void*)&instance.m_input_buffer[input_block_offset];
input_block_offset += sizeof(void*);
}
// outputs
int output_block_size = 0;
std::vector<Socket>& graph_outputs = instance.getGraphOutputs();
for (int i = 0; i < graph_outputs.size(); i++) {
output_block_size += graph_outputs[i].m_type_size;
}
instance.m_output_buffer = new char[output_block_size];
memset(instance.m_output_buffer, 0, output_block_size);
int output_block_offset = 0;
for (int i = 0; i < graph_outputs.size(); i++) {
graph_outputs[i].m_value.ptr =
(void*)&instance.m_output_buffer[output_block_offset];
output_block_offset += graph_outputs[i].m_type_size;
}
}
void AnimGraphResource::connectRuntimeNodes(AnimGraph& instance) const {
for (int i = 0; i < m_connections.size(); i++) {
const AnimGraphConnectionResource& connection = m_connections[i];
std::string source_node_type = "";
std::string target_node_type = "";
AnimNode* source_node = nullptr;
AnimNode* target_node = nullptr;
NodeSocketAccessorBase* source_node_accessor = nullptr;
NodeSocketAccessorBase* target_node_accessor = nullptr;
SocketType source_type;
SocketType target_type;
size_t source_socket_index = -1;
size_t target_socket_index = -1;
if (connection.source_node_index < 0
|| connection.source_node_index >= m_nodes.size()) {
std::cerr << "Could not find source node index." << std::endl;
continue;
}
int input_node_index = input_node->m_index;
bool is_node_processed = false;
for (size_t j = 0, m = m_eval_ordered_nodes.size(); j < m; j++) {
if (m_eval_ordered_nodes[j] == input_node) {
is_node_processed = true;
break;
}
source_node = instance.m_nodes[connection.source_node_index];
source_node_type = source_node->m_node_type_name;
if (connection.source_node_index == 1) {
source_node_accessor = instance.m_socket_accessor;
} else {
source_node_accessor =
AnimNodeAccessorFactory(source_node_type, source_node);
}
if (is_node_processed) {
if (connection.target_node_index < 0
|| connection.target_node_index >= m_nodes.size()) {
std::cerr << "Could not find source node index." << std::endl;
continue;
}
m_eval_ordered_nodes.push_back(input_node);
node_index_stack.push_back(input_node_index);
}
}
target_node = instance.m_nodes[connection.target_node_index];
target_node_type = target_node->m_node_type_name;
if (connection.target_node_index == 0) {
target_node_accessor = instance.m_socket_accessor;
} else {
target_node_accessor =
AnimNodeAccessorFactory(target_node_type, target_node);
}
void AnimGraph::markActiveNodes() {
for (size_t i = 0, n = m_nodes.size(); i < n; i++) {
m_nodes[i]->m_state = AnimNodeEvalState::Deactivated;
}
assert(source_node != nullptr);
assert(target_node != nullptr);
const std::vector<NodeInput> graph_output_inputs = m_node_inputs[0];
for (size_t i = 0, n = graph_output_inputs.size(); i < n; i++) {
const NodeInput& graph_input = graph_output_inputs[i];
AnimNode* node = graph_input.m_node;
if (node != nullptr) {
node->m_state = AnimNodeEvalState::Activated;
}
}
for (size_t i = 0, n = m_eval_ordered_nodes.size(); i < n; i++) {
AnimNode* node = m_eval_ordered_nodes[i];
if (checkIsNodeActive(node)) {
int node_index = node->m_index;
node->MarkActiveInputs(m_node_inputs[node_index]);
// Non-animation data inputs are always active.
for (size_t j = 0, nj = m_node_inputs[node_index].size(); j < nj; j++) {
const NodeInput& input = m_node_inputs[node_index][j];
if (input.m_node != nullptr && input.m_type != SocketType::SocketTypeAnimation) {
input.m_node->m_state = AnimNodeEvalState::Activated;
}
}
}
}
}
void AnimGraph::evalSyncTracks() {
for (size_t i = m_eval_ordered_nodes.size() - 1; i >= 0; i--) {
AnimNode* node = m_eval_ordered_nodes[i];
int node_index = node->m_index;
if (node->m_state == AnimNodeEvalState::Deactivated) {
//
// Map resource node sockets to graph instance node sockets
//
source_socket_index =
source_node_accessor->GetOutputIndex(connection.source_socket_name);
if (source_socket_index == -1) {
std::cerr << "Invalid source socket " << connection.source_socket_name
<< " for node " << source_node->m_name << "." << std::endl;
continue;
}
Socket* source_socket =
&source_node_accessor->m_outputs[source_socket_index];
node->CalcSyncTrack(m_node_inputs[node_index]);
}
}
void AnimGraph::updateTime(float dt) {
const std::vector<NodeInput> graph_output_inputs = m_node_inputs[0];
for (size_t i = 0, n = graph_output_inputs.size(); i < n; i++) {
AnimNode* node = m_eval_ordered_nodes[i];
if (node != nullptr) {
node->UpdateTime(node->m_time_now, node->m_time_now + dt);
}
}
for (size_t i = 0, n = m_eval_ordered_nodes.size(); i < n; i++) {
AnimNode* node = m_eval_ordered_nodes[i];
if (node->m_state != AnimNodeEvalState::TimeUpdated) {
target_socket_index =
target_node_accessor->GetInputIndex(connection.target_socket_name);
if (target_socket_index == -1) {
std::cerr << "Invalid target socket " << connection.target_socket_name
<< " for node " << target_node->m_name << "." << std::endl;
continue;
}
Socket* target_socket =
&target_node_accessor->m_inputs[target_socket_index];
int node_index = node->m_index;
const std::vector<NodeInput> node_inputs =
m_node_inputs[node_index];
float node_time_now = node->m_time_now;
float node_time_last = node->m_time_last;
for (size_t i = 0, n = node_inputs.size(); i < n; i++) {
AnimNode* input_node = node_inputs[i].m_node;
// Only propagate time updates via animation sockets.
if (input_node != nullptr
&& node_inputs[i].m_type == SocketType::SocketTypeAnimation
&& input_node->m_state == AnimNodeEvalState::Activated) {
input_node->UpdateTime(node_time_last, node_time_now);
if (source_socket->m_type != target_socket->m_type) {
std::cerr << "Cannot connect sockets: invalid types!" << std::endl;
}
//
// Wire up outputs to inputs.
//
(*target_socket->m_value.ptr_ptr) = source_socket->m_value.ptr;
// (*source_socket->m_value.ptr_ptr) = target_socket->m_value.ptr;
size_t target_node_index = target_node->m_index;
// Register the runtime connection
AnimGraphConnection runtime_connection = {
source_node,
*source_socket,
target_node,
*target_socket};
std::vector<AnimGraphConnection>& target_input_connections =
instance.m_node_input_connections[target_node_index];
target_input_connections.push_back(runtime_connection);
std::vector<AnimGraphConnection>& source_output_connections =
instance.m_node_output_connections[source_node->m_index];
source_output_connections.push_back(runtime_connection);
if (target_node_accessor != instance.m_socket_accessor) {
delete target_node_accessor;
}
if (source_node_accessor != instance.m_socket_accessor) {
delete source_node_accessor;
}
}
}
void AnimGraph::evaluate() {
for (size_t i = m_eval_ordered_nodes.size() - 1; i >= 0; i--) {
AnimNode* node = m_eval_ordered_nodes[i];
if (node->m_state == AnimNodeEvalState::Deactivated) {
continue;
}
node->Evaluate();
}
}
void* AnimGraph::getOutput(const std::string& name) const {
Socket* socket = m_socket_accessor->FindInputSocket(name);
if (socket == nullptr) {
return nullptr;
}
return socket->m_value.ptr;
}
void* AnimGraph::getInput(const std::string& name) const {
Socket* socket = m_socket_accessor->FindOutputSocket(name);
if (socket == nullptr) {
return nullptr;
}
return *(socket->m_value.ptr_ptr);
}
} // namespace AniGraph

View File

@ -0,0 +1,149 @@
//
// Created by martin on 04.02.22.
//
#ifndef ANIMTESTBED_ANIMGRAPHRESOURCE_H
#define ANIMTESTBED_ANIMGRAPHRESOURCE_H
#include <cstring>
#include <iostream>
#include <map>
#include <string>
#include <type_traits>
#include <vector>
#include "AnimGraph.h"
#include "AnimGraphData.h"
#include "AnimGraphNodes.h"
#include "SyncTrack.h"
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};
};
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;
}
//
// AnimGraphResource
//
struct AnimGraphConnectionResource {
size_t source_node_index = -1;
std::string source_socket_name = "";
size_t target_node_index = -1;
std::string target_socket_name = "";
};
struct AnimGraphResource {
std::string m_name;
std::vector<AnimNodeResource> m_nodes;
std::vector<AnimGraphConnectionResource> m_connections;
~AnimGraphResource() {
for (size_t i = 0, n = m_nodes.size(); i < n; i++) {
delete m_nodes[i].m_anim_node;
delete m_nodes[i].m_socket_accessor;
}
}
AnimGraphResource() { clear(); }
void clear();
void clearNodes();
void initGraphConnectors();
bool saveToFile(const char* filename) const;
bool loadFromFile(const char* filename);
AnimNodeResource& getGraphOutputNode() { return m_nodes[0]; }
AnimNodeResource& getGraphInputNode() { return m_nodes[1]; }
const AnimNodeResource& getGraphOutputNode() const { return m_nodes[0]; }
const AnimNodeResource& getGraphInputNode() const { return m_nodes[1]; }
size_t getNodeIndex(const AnimNodeResource& node_resource) const {
for (size_t i = 0, n = m_nodes.size(); i < n; i++) {
if (&m_nodes[i] == &node_resource) {
return i;
}
}
return -1;
}
size_t addNode(AnimNodeResource node_resource) {
m_nodes.push_back(node_resource);
return m_nodes.size() - 1;
}
bool 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 =
source_node.m_socket_accessor->FindOutputSocket(source_socket_name);
Socket* target_socket =
target_node.m_socket_accessor->FindInputSocket(target_socket_name);
if (source_socket == nullptr || target_socket == nullptr) {
std::cerr << "Cannot connect nodes: could not find sockets." << std::endl;
return false;
}
AnimGraphConnectionResource 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 isSocketConnected(
const AnimNodeResource& node,
const std::string& socket_name) {
int node_index = getNodeIndex(node);
for (size_t i = 0, n = m_connections.size(); i < n; i++) {
const AnimGraphConnectionResource& connection = m_connections[i];
if ((connection.source_node_index == node_index
&& connection.source_socket_name == socket_name)
|| ((connection.target_node_index == node_index)
&& connection.target_socket_name == socket_name)) {
return true;
}
}
return false;
}
AnimGraph createInstance() const;
void createRuntimeNodeInstances(AnimGraph& instance) const;
void prepareGraphIOData(AnimGraph& instance) const;
void connectRuntimeNodes(AnimGraph& instance) const;
};
#endif //ANIMTESTBED_ANIMGRAPHRESOURCE_H

View File

@ -1,10 +0,0 @@
//
// Created by martin on 11.02.22.
//
#ifndef ANIMTESTBED_ANIMGRAPHEDITOR_H
#define ANIMTESTBED_ANIMGRAPHEDITOR_H
void AnimGraphEditorUpdate();
#endif //ANIMTESTBED_ANIMGRAPHEDITOR_H

View File

@ -1,877 +0,0 @@
//
// Created by martin on 04.02.22.
//
#ifndef ANIMTESTBED_ANIMGRAPHRESOURCE_H
#define ANIMTESTBED_ANIMGRAPHRESOURCE_H
#include <cstring>
#include <iostream>
#include <map>
#include <string>
#include <type_traits>
#include <vector>
#include "SyncTrack.h"
namespace AniGraph {
//
// Data types
//
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;
}
enum class SocketType {
SocketTypeUndefined,
SocketTypeBool,
SocketTypeAnimation,
SocketTypeFloat,
SocketTypeVec3,
SocketTypeQuat,
SocketTypeString
};
enum SocketFlags { SocketFlagAffectsTime = 1 };
struct Socket {
std::string m_name;
SocketType m_type = SocketType::SocketTypeUndefined;
union SocketValue {
void* ptr;
void** ptr_ptr;
};
SocketValue m_value = {nullptr};
int m_flags = 0;
size_t m_type_size = 0;
};
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 NodeInput {
AnimNode* m_node;
SocketType m_type = SocketType::SocketTypeUndefined;
std::string m_input_name;
};
enum class AnimNodeEvalState {
Undefined,
Deactivated,
Activated,
SyncTrackUpdated,
TimeUpdated,
Evaluated
};
struct AnimNode {
std::string m_name;
std::string m_node_type_name;
float m_time_now = 0.f;
float m_time_last = 0.f;
size_t m_index = -1;
AnimNodeEvalState m_state = AnimNodeEvalState::Undefined;
SyncTrack m_sync_track;
virtual ~AnimNode(){};
virtual void MarkActiveInputs(const std::vector<NodeInput>& inputs) {
for (size_t i = 0, n = inputs.size(); i < n; i++) {
AnimNode* input_node = inputs[i].m_node;
if (input_node != nullptr) {
input_node->m_state = AnimNodeEvalState::Activated;
}
}
}
virtual void CalcSyncTrack(const std::vector<NodeInput>& inputs) {
for (size_t i = 0, n = inputs.size(); i < n; i++) {
AnimNode* input_node = inputs[i].m_node;
if (input_node != nullptr
&& inputs[i].m_type == SocketType::SocketTypeAnimation
&& input_node->m_state != AnimNodeEvalState::Deactivated) {
m_sync_track = input_node->m_sync_track;
return;
}
}
}
virtual void UpdateTime(float time_last, float time_now) {
m_time_last = time_last;
m_time_now = time_now;
m_state = AnimNodeEvalState::TimeUpdated;
}
virtual void Evaluate() {};
};
struct NodeSocketAccessorBase {
std::vector<Socket> m_properties;
std::vector<Socket> m_inputs;
std::vector<Socket> m_outputs;
NodeSocketAccessorBase() {}
virtual ~NodeSocketAccessorBase() {}
virtual void UpdateFlags(){};
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;
}
const Socket* FindSocket(
const std::vector<Socket>& sockets,
const std::string& name) const {
const 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) {
const Socket* socket = FindSocket(sockets, name);
if (socket == nullptr) {
return SocketType::SocketTypeUndefined;
}
return socket->m_type;
}
size_t GetSocketIndex(
const std::vector<Socket>& sockets,
const std::string& name) const {
for (size_t i = 0, n = sockets.size(); i < n; i++) {
if (sockets[i].m_name == name) {
return i;
}
}
return -1;
}
template <typename T>
T GetSocketValue(
const std::vector<Socket>& sockets,
const std::string& name,
T default_value) {
const Socket* socket = FindSocket(sockets, name);
if (socket == nullptr) {
return default_value;
}
return *static_cast<T*>(socket->m_value.ptr);
}
template <typename T>
void SetSocketValue(
const std::vector<Socket>& sockets,
const std::string& name,
const T& value) {
const Socket* socket = FindSocket(sockets, name);
if (socket == nullptr) {
std::cerr << "Error: could not set value of socket with name " << name
<< ": no socket found." << std::endl;
return;
}
*static_cast<T*>(socket->m_value.ptr) = value;
}
template <typename T>
bool RegisterSocket(
std::vector<Socket>& sockets,
const std::string& name,
T* value_ptr,
int flags = 0) {
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;
socket->m_type_size = sizeof(T);
socket->m_flags = flags;
if constexpr (std::is_same<T, float>::value) {
socket->m_type = SocketType::SocketTypeFloat;
} else if constexpr (std::is_same<T, bool>::value) {
socket->m_type = SocketType::SocketTypeBool;
} else if constexpr (std::is_same<T, Vec3>::value) {
socket->m_type = SocketType::SocketTypeVec3;
} else if constexpr (std::is_same<T, Quat>::value) {
socket->m_type = SocketType::SocketTypeQuat;
} else if constexpr (std::is_same<T, AnimData>::value) {
socket->m_type = SocketType::SocketTypeAnimation;
} else if constexpr (std::is_same<T, std::string>::value) {
socket->m_type = SocketType::SocketTypeString;
} else if constexpr (std::is_same<T, float*>::value) {
socket->m_type = SocketType::SocketTypeFloat;
} else if constexpr (std::is_same<T, bool*>::value) {
socket->m_type = SocketType::SocketTypeBool;
} else if constexpr (std::is_same<T, Vec3*>::value) {
socket->m_type = SocketType::SocketTypeVec3;
} else if constexpr (std::is_same<T, Quat*>::value) {
socket->m_type = SocketType::SocketTypeQuat;
} else if constexpr (std::is_same<T, AnimData*>::value) {
socket->m_type = SocketType::SocketTypeAnimation;
} else if constexpr (std::is_same<T, std::string*>::value) {
socket->m_type = SocketType::SocketTypeString;
} else {
std::cerr << "Cannot register socket, invalid type." << std::endl;
return false;
}
socket->m_value.ptr = value_ptr;
return true;
}
template <typename T>
bool RegisterProperty(const std::string& name, T* value) {
return RegisterSocket(m_properties, name, value);
}
template <typename T>
void SetProperty(const std::string& name, const T& value) {
SetSocketValue(m_properties, name, value);
}
template <typename T>
T GetProperty(const std::string& name, T default_value) {
return GetSocketValue(m_properties, name, default_value);
}
SocketType GetPropertyType(const std::string& name) {
return GetSocketType(m_properties, name);
}
template <typename T>
bool RegisterInput(const std::string& name, T* value, int flags = 0) {
return RegisterSocket(m_inputs, name, value, flags);
}
template <typename T>
T* GetInput(const std::string& name, T* value) {
return GetSocketValue(m_inputs, name, value);
}
Socket* FindInputSocket(const std::string& name) {
return FindSocket(m_inputs, name);
}
SocketType GetInputType(const std::string& name) {
return GetSocketType(m_inputs, name);
}
size_t GetInputIndex(const std::string& name) {
return GetSocketIndex(m_inputs, name);
}
template <typename T>
bool RegisterOutput(const std::string& name, T** value, int flags = 0) {
return RegisterSocket(m_outputs, name, value, flags);
}
SocketType GetOutputType(const std::string& name) {
return GetSocketType(m_outputs, name);
}
Socket* FindOutputSocket(const std::string& name) {
return FindSocket(m_outputs, name);
}
size_t GetOutputIndex(const std::string& name) {
return GetSocketIndex(m_outputs, name);
}
};
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);
};
//
// BlendTreeNode
//
struct BlendTreeNode : public AnimNode {};
template <>
struct NodeSocketAccessor<BlendTreeNode> : public NodeSocketAccessorBase {
NodeSocketAccessor(AnimNode* node_) {}
};
//
// Blend2Node
//
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;
virtual void MarkActiveInputs(const std::vector<NodeInput>& inputs) override {
for (size_t i = 0, n = inputs.size(); i < n; i++) {
AnimNode* input_node = inputs[i].m_node;
if (input_node == nullptr) {
continue;
}
if (inputs[i].m_input_name == "Input0" && m_blend_weight < 0.999) {
input_node->m_state = AnimNodeEvalState::Activated;
continue;
}
if (inputs[i].m_input_name == "Input1" && m_blend_weight > 0.001) {
input_node->m_state = AnimNodeEvalState::Activated;
continue;
}
}
}
virtual void UpdateTime(float dt, std::vector<NodeInput>& inputs) {
if (!m_sync_blend) {
m_time_now = m_time_now + dt;
}
for (size_t i = 0, n = inputs.size(); i < n; i++) {
AnimNode* input_node = inputs[i].m_node;
if (input_node == nullptr) {
continue;
}
if (input_node->m_state != AnimNodeEvalState::Deactivated) {
if (!m_sync_blend) {
input_node->m_time_now = m_time_now;
}
input_node->m_state = AnimNodeEvalState::TimeUpdated;
continue;
}
}
}
};
template <>
struct NodeSocketAccessor<Blend2Node> : public NodeSocketAccessorBase {
NodeSocketAccessor(AnimNode* node_) {
Blend2Node* node = dynamic_cast<Blend2Node*>(node_);
RegisterInput("Input0", &node->m_input0);
RegisterInput("Input1", &node->m_input1);
RegisterInput(
"Weight",
&node->m_blend_weight,
SocketFlags::SocketFlagAffectsTime);
RegisterOutput("Output", &node->m_output);
RegisterProperty("Sync", &node->m_sync_blend);
}
virtual void UpdateFlags() override {
Socket* weight_input_socket = FindSocket(m_inputs, "Weight");
assert(weight_input_socket != nullptr);
if (GetProperty<bool>("Sync", false) == true) {
weight_input_socket->m_flags = SocketFlags::SocketFlagAffectsTime;
} else {
weight_input_socket->m_flags = 0;
}
}
};
//
// SpeedScaleNode
//
struct SpeedScaleNode : public AnimNode {
AnimData m_input;
AnimData* m_output = nullptr;
float m_speed_scale = 0.f;
virtual void UpdateTime(float time_last, float time_now) {
m_time_last = time_last;
m_time_now = time_last + (time_now - time_last) * m_speed_scale;
m_state = AnimNodeEvalState::TimeUpdated;
}
};
template <>
struct NodeSocketAccessor<SpeedScaleNode> : public NodeSocketAccessorBase {
NodeSocketAccessor(AnimNode* node_) {
SpeedScaleNode* node = dynamic_cast<SpeedScaleNode*>(node_);
RegisterInput(
"SpeedScale",
&node->m_speed_scale,
SocketFlags::SocketFlagAffectsTime);
RegisterInput("Input", &node->m_input);
RegisterOutput("Output", &node->m_output);
}
};
//
// AnimSamplerNode
//
struct AnimSamplerNode : public AnimNode {
AnimData* m_output = nullptr;
std::string m_filename;
};
template <>
struct NodeSocketAccessor<AnimSamplerNode> : public NodeSocketAccessorBase {
NodeSocketAccessor(AnimNode* node_) {
AnimSamplerNode* node = dynamic_cast<AnimSamplerNode*>(node_);
RegisterOutput("Output", &node->m_output);
RegisterProperty("Filename", &node->m_filename);
}
};
//
// AnimGraphResource
//
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;
~AnimGraphResource() {
for (size_t i = 0, n = m_nodes.size(); i < n; i++) {
delete m_nodes[i].m_anim_node;
delete m_nodes[i].m_socket_accessor;
}
}
AnimGraphResource() { clear(); }
void clear();
void clearNodes();
void initGraphConnectors();
bool saveToFile(const char* filename) const;
bool loadFromFile(const char* filename);
AnimNodeResource& getGraphOutputNode() { return m_nodes[0]; }
AnimNodeResource& getGraphInputNode() { return m_nodes[1]; }
size_t addNode(AnimNodeResource node_resource) {
m_nodes.push_back(node_resource);
return m_nodes.size() - 1;
}
bool connectSockets(
const AnimNodeResource& source_node,
const std::string& source_socket,
const AnimNodeResource& target_node,
const std::string& target_socket) {
size_t source_index = -1;
size_t target_index = -1;
for (size_t i = 0, n = m_nodes.size(); i < n; i++) {
if (&source_node == &m_nodes[i]) {
source_index = i;
}
if (&target_node == &m_nodes[i]) {
target_index = i;
}
if (source_index < m_nodes.size() && target_index < m_nodes.size()) {
break;
}
}
if (source_index >= m_nodes.size() || target_index >= m_nodes.size()) {
std::cerr << "Cannot connect nodes: could not find nodes." << std::endl;
return false;
}
size_t source_socket_index =
source_node.m_socket_accessor->GetOutputIndex(source_socket);
size_t target_socket_index =
target_node.m_socket_accessor->GetInputIndex(target_socket);
if (source_socket_index >= source_node.m_socket_accessor->m_outputs.size()
|| target_socket_index
>= target_node.m_socket_accessor->m_inputs.size()) {
std::cerr << "Cannot connect nodes: could not find sockets." << std::endl;
return false;
}
AnimGraphConnection connection;
connection.m_source_node_index = source_index;
connection.m_source_socket_index = source_socket_index;
connection.m_target_node_index = target_index;
connection.m_target_socket_index = target_socket_index;
m_connections.push_back(connection);
return true;
}
};
static inline AnimNode* AnimNodeFactory(const std::string& name) {
AnimNode* result;
if (name == "Blend2") {
result = new Blend2Node;
} else if (name == "SpeedScale") {
result = new SpeedScaleNode;
} else if (name == "AnimSampler") {
result = new AnimSamplerNode;
} else if (name == "BlendTree") {
result = new BlendTreeNode;
}
if (result != nullptr) {
result->m_node_type_name = name;
return result;
}
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 if (node_type_name == "BlendTree") {
return new NodeSocketAccessor<BlendTreeNode>(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;
}
//
// AnimGraph (Runtime)
//
struct AnimGraph {
AnimData m_local_transforms;
std::vector<AnimNode*> m_nodes;
std::vector<AnimNode*> m_eval_ordered_nodes;
std::vector<std::vector<NodeInput> > m_node_inputs;
NodeSocketAccessorBase* m_socket_accessor;
char* m_input_buffer = nullptr;
char* m_output_buffer = nullptr;
std::vector<Socket>& getGraphOutputs() { return m_socket_accessor->m_inputs; }
std::vector<Socket>& getGraphInputs() { return m_socket_accessor->m_outputs; }
~AnimGraph() {
delete[] m_input_buffer;
delete[] m_output_buffer;
for (int i = 0; i < m_nodes.size(); i++) {
delete m_nodes[i];
}
delete m_socket_accessor;
}
void updateOrderedNodes();
void markActiveNodes();
bool checkIsNodeActive(AnimNode* node) {
return node->m_state != AnimNodeEvalState::Deactivated;
}
void evalSyncTracks();
void updateTime(float dt);
void evaluate();
void reset() {
for (size_t i = 0, n = m_nodes.size(); i < n; i++) {
m_nodes[i]->m_time_now = 0.f;
m_nodes[i]->m_time_last = 0.f;
m_nodes[i]->m_state = AnimNodeEvalState::Undefined;
}
}
void* getOutput(const std::string& name) const;
void* getInput(const std::string& name) const;
int getNodeEvalOrderIndex(const AnimNode* node) {
for (size_t i = 0, n = m_eval_ordered_nodes.size(); i < n; i++) {
if (m_eval_ordered_nodes[i] == node) {
return i;
}
}
return -1;
}
AnimNode* getAnimNodeForInput(
size_t node_index,
const std::string& input_name) {
assert(node_index < m_nodes.size());
assert(node_index < m_node_inputs.size());
std::vector<NodeInput>& node_inputs = m_node_inputs[node_index];
for (size_t i = 0, n = node_inputs.size(); i < n; i++) {
if (node_inputs[i].m_input_name == input_name) {
return node_inputs[i].m_node;
}
}
return nullptr;
}
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;
}
static AnimGraph createFromResource(const AnimGraphResource& resource) {
AnimGraph result;
// create nodes
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;
node->m_index = i;
result.m_nodes.push_back(node);
assert(node_resource.m_socket_accessor != nullptr);
result.m_node_inputs.push_back(std::vector<NodeInput>());
std::vector<NodeInput>& node_inputs = result.m_node_inputs.back();
for (int j = 0, n = node_resource.m_socket_accessor->m_inputs.size();
j < n;
j++) {
const Socket& input_socket =
node_resource.m_socket_accessor->m_inputs[j];
NodeInput input;
input.m_node = nullptr;
input.m_type = input_socket.m_type;
input.m_input_name = input_socket.m_name;
node_inputs.push_back(input);
}
}
// Prepare graph inputs
result.m_socket_accessor =
AnimNodeAccessorFactory("BlendTree", result.m_nodes[0]);
result.m_socket_accessor->m_outputs =
resource.m_nodes[1].m_socket_accessor->m_outputs;
result.m_socket_accessor->m_inputs =
resource.m_nodes[0].m_socket_accessor->m_inputs;
// inputs
int input_block_size = 0;
std::vector<Socket>& graph_inputs = result.getGraphInputs();
for (int i = 0; i < graph_inputs.size(); i++) {
input_block_size += sizeof(void*);
}
result.m_input_buffer = new char[input_block_size];
memset(result.m_input_buffer, 0, input_block_size);
int input_block_offset = 0;
for (int i = 0; i < graph_inputs.size(); i++) {
if (graph_inputs[i].m_type == SocketType::SocketTypeAnimation) {
}
graph_inputs[i].m_value.ptr =
(void*)&result.m_input_buffer[input_block_offset];
input_block_offset += sizeof(void*);
}
// outputs
int output_block_size = 0;
std::vector<Socket>& graph_outputs = result.getGraphOutputs();
for (int i = 0; i < graph_outputs.size(); i++) {
output_block_size += graph_outputs[i].m_type_size;
}
result.m_output_buffer = new char[output_block_size];
memset(result.m_output_buffer, 0, output_block_size);
int output_block_offset = 0;
for (int i = 0; i < graph_outputs.size(); i++) {
if (graph_outputs[i].m_type == SocketType::SocketTypeAnimation) {
}
graph_outputs[i].m_value.ptr =
(void*)&result.m_output_buffer[output_block_offset];
output_block_offset += graph_outputs[i].m_type_size;
}
// connect the nodes
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;
if (connection.m_source_node_index == 1) {
source_node_accessor = result.m_socket_accessor;
} else {
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;
if (connection.m_target_node_index == 0) {
target_node_accessor = result.m_socket_accessor;
} else {
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;
}
Socket* source_socket =
&source_node_accessor->m_outputs[connection.m_source_socket_index];
Socket* target_socket =
&target_node_accessor->m_inputs[connection.m_target_socket_index];
if (source_socket->m_type != target_socket->m_type) {
std::cerr << "Cannot connect sockets: invalid types!" << std::endl;
}
(*source_socket->m_value.ptr_ptr) = target_socket->m_value.ptr;
size_t target_node_index = target_node->m_index;
std::vector<NodeInput>& node_inputs =
result.m_node_inputs[target_node_index];
for (int j = 0, n = node_inputs.size(); j < n; j++) {
if (node_inputs[j].m_input_name == target_socket->m_name) {
node_inputs[j].m_node = source_node;
}
}
if (target_node_accessor != result.m_socket_accessor) {
delete target_node_accessor;
}
if (source_node_accessor != result.m_socket_accessor) {
delete source_node_accessor;
}
}
result.updateOrderedNodes();
result.reset();
return result;
}
};
} // namespace AniGraph
#endif //ANIMTESTBED_ANIMGRAPHRESOURCE_H

View File

@ -1,5 +0,0 @@
//
// Created by martin on 12.11.21.
//
#include "AnimNode.h"

View File

@ -1,56 +0,0 @@
//
// Created by martin on 12.11.21.
//
#ifndef ANIMTESTBED_ANIMNODE_H
#define ANIMTESTBED_ANIMNODE_H
#include <ozz/base/maths/transform.h>
#include <string>
#include "AnimationController.h"
#include "SkinnedMesh.h"
enum class AnimNodeType { Blend, SpeedScale, AnimSampler };
struct AnimNode {
AnimNode(AnimationController* animation_controller)
: m_animation_controller(animation_controller),
m_time_current(0.f),
m_is_time_synced(false) {}
virtual ~AnimNode(){};
AnimNodeType m_anim_node_type;
std::string m_name;
AnimationController* m_animation_controller;
// When synced then current time is relative to the node's anim duration.:w
bool m_is_time_synced;
float m_time_current;
SyncTrack m_sync_track;
virtual void Reset() { m_time_current = 0.f; }
// Mark current node according to is_synced and propagate flag to animation inputs.
virtual void UpdateIsSynced(bool is_synced) = 0;
// Evaluate the animation duration of this node. All input nodes must have already evaluated
// their anim durations.
virtual void UpdateSyncTrack() = 0;
// Evaluate current time and propagate time step to inputs.
virtual void UpdateTime(float dt) = 0;
// Evaluate the current node and write output to local_matrices.
virtual void Evaluate(
ozz::vector<ozz::math::SoaTransform>* local_matrices,
ozz::math::Transform* root_transform) = 0;
// Returns a list of animation nodes that provide input for this node.
virtual void GetInputNodes(std::vector<AnimNode*>& input_nodes) const = 0 ;
virtual void DrawDebugUi(){};
};
#endif //ANIMTESTBED_ANIMNODE_H

View File

@ -1,74 +0,0 @@
//
// Created by martin on 12.11.21.
//
#include "AnimSamplerNode.h"
#include "ozzutils.h"
#include <imgui.h>
#include "../SkinnedMesh.h"
#include "../ozzutils.h"
void AnimSamplerNode::SetAnimation(ozz::animation::Animation* animation, const SyncTrack& sync_track) {
m_animation = animation;
const SkinnedMesh* skinned_mesh = m_animation_controller->m_skinned_mesh;
const int num_soa_joints = skinned_mesh->m_skeleton.num_soa_joints();
const int num_joints = skinned_mesh->m_skeleton.num_joints();
m_local_matrices.resize(num_soa_joints);
m_sampling_cache.Resize(num_joints);
m_sync_track = sync_track;
}
void AnimSamplerNode::Evaluate(
ozz::vector<ozz::math::SoaTransform>* local_matrices,
ozz::math::Transform* root_transform) {
ozz::animation::SamplingJob sampling_job;
sampling_job.animation = m_animation;
sampling_job.cache = &m_sampling_cache;
sampling_job.ratio = m_anim_ratio;
sampling_job.output = make_span(*local_matrices);
if (!sampling_job.Run()) {
ozz::log::Err() << "Error sampling animation." << std::endl;
}
if (root_transform == nullptr) {
return;
}
ozz::math::Transform root_trans_prev;
calc_bone_translation (m_anim_ratio_prev, 0, m_animation, root_trans_prev);
ozz::math::Transform root_trans_cur;
calc_bone_translation (m_anim_ratio, 0, m_animation, root_trans_cur);
root_transform->translation = root_trans_cur.translation - root_trans_prev.translation;
}
void AnimSamplerNode::DrawDebugUi() {
const SkinnedMesh* skinned_mesh = m_animation_controller->m_skinned_mesh;
int anim_count = skinned_mesh->m_animation_names.size();
const char* items[255] = {0};
int item_current = 0;
for (int i = 0; i < anim_count; i++) {
items[i] = skinned_mesh->m_animation_names[i].c_str();
if (skinned_mesh->m_animations[i] == m_animation) {
item_current = i;
}
}
if (ImGui::Combo("Animation", &item_current, items, anim_count)) {
m_animation = skinned_mesh->m_animations[item_current];
m_sync_track = skinned_mesh->m_animation_sync_track[item_current];
}
ImGui::Checkbox("Override", &m_override_ratio);
ImGui::SameLine();
ImGui::SliderFloat("Ratio", &m_anim_ratio, 0.f, 1.f);
ImGui::Text("SyncTrack");
m_sync_track.DrawDebugUi();
}

View File

@ -1,85 +0,0 @@
//
// Created by martin on 12.11.21.
//
#ifndef ANIMTESTBED_ANIMSAMPLERNODE_H
#define ANIMTESTBED_ANIMSAMPLERNODE_H
#include "../AnimNode.h"
struct AnimSamplerNode : public AnimNode {
AnimSamplerNode(AnimationController* animation_controller)
: AnimNode(animation_controller),
m_time_prev(0.f),
m_override_ratio(false),
m_anim_ratio(0.f),
m_root_bone_index(0) {
assert(m_time_current < 100.0f);
m_anim_node_type = AnimNodeType::AnimSampler;
};
virtual ~AnimSamplerNode() {}
ozz::animation::Animation* m_animation;
float m_time_prev;
bool m_override_ratio;
float m_anim_ratio;
float m_anim_ratio_prev;
bool m_root_bone_index;
ozz::vector<ozz::math::SoaTransform> m_local_matrices;
ozz::vector<ozz::math::SoaTransform> m_root_output;
ozz::animation::SamplingCache m_sampling_cache;
void SetAnimation(
ozz::animation::Animation* animation,
const SyncTrack& sync_track);
virtual void UpdateIsSynced(bool is_synced) override {
m_is_time_synced = is_synced;
}
virtual void UpdateSyncTrack() override {}
virtual void UpdateTime(float dt) override {
if (m_override_ratio) {
return;
}
m_time_prev = m_time_current;
m_time_current += dt;
if (m_is_time_synced) {
m_anim_ratio = m_sync_track.CalcRatioFromSyncTime(m_time_current);
float prev_sync_time = m_time_current - dt;
if (m_time_current < 0) {
prev_sync_time += m_sync_track.m_num_intervals;
}
m_anim_ratio_prev = m_sync_track.CalcRatioFromSyncTime(prev_sync_time);
} else {
m_anim_ratio =
fmodf((float)m_time_current / m_animation->duration(), 1.0f);
m_anim_ratio_prev =
fmodf((float)m_time_prev / m_animation->duration(), 1.0f);
if (m_anim_ratio_prev < 0) {
m_anim_ratio_prev += 1.0f;
}
}
if (m_anim_ratio < 0.f) {
m_anim_ratio += 1.0f;
}
}
virtual void Evaluate(
ozz::vector<ozz::math::SoaTransform>* local_matrices,
ozz::math::Transform* root_transform) override;
virtual void GetInputNodes(
std::vector<AnimNode*>& input_nodes) const override{};
virtual void DrawDebugUi();
};
#endif //ANIMTESTBED_ANIMSAMPLERNODE_H

View File

@ -1,63 +0,0 @@
//
// Created by martin on 12.11.21.
//
#include "BlendNode.h"
#include <imgui.h>
#include <ozz/animation/runtime/blending_job.h>
#include "../SkinnedMesh.h"
BlendNode::BlendNode(AnimationController* animation_controller)
: AnimNode(animation_controller),
m_input_A(nullptr),
m_input_B(nullptr),
m_weight(0.f),
m_sync_inputs(false) {
m_anim_node_type = AnimNodeType::Blend;
const SkinnedMesh* skinned_mesh = m_animation_controller->m_skinned_mesh;
const int num_soa_joints = skinned_mesh->m_skeleton.num_soa_joints();
const int num_joints = skinned_mesh->m_skeleton.num_joints();
m_local_matrices_A.resize(num_soa_joints);
m_local_matrices_B.resize(num_soa_joints);
}
void BlendNode::Evaluate(
ozz::vector<ozz::math::SoaTransform>* local_matrices,
ozz::math::Transform* root_transform) {
const SkinnedMesh* skinned_mesh = m_animation_controller->m_skinned_mesh;
m_input_A->Evaluate(
&m_local_matrices_A,
root_transform != nullptr ? &m_root_transform_A : nullptr);
m_input_B->Evaluate(
&m_local_matrices_B,
root_transform != nullptr ? &m_root_transform_B : nullptr);
// perform blend
ozz::animation::BlendingJob::Layer layers[2];
layers[0].transform = make_span(m_local_matrices_A);
layers[0].weight = (1.0f - m_weight);
layers[1].transform = make_span(m_local_matrices_B);
layers[1].weight = (m_weight);
ozz::animation::BlendingJob blend_job;
blend_job.threshold = ozz::animation::BlendingJob().threshold;
blend_job.layers = layers;
blend_job.bind_pose = skinned_mesh->m_skeleton.joint_bind_poses();
blend_job.output = make_span(*local_matrices);
if (!blend_job.Run()) {
ozz::log::Err() << "Error blending animations." << std::endl;
}
}
void BlendNode::DrawDebugUi() {
ImGui::SliderFloat("Weight", &m_weight, 0.f, 1.f);
ImGui::Checkbox("Sync Inputs", &m_sync_inputs);
ImGui::Text("SyncTrack");
m_sync_track.DrawDebugUi();
}

View File

@ -1,80 +0,0 @@
//
// Created by martin on 12.11.21.
//
#ifndef ANIMTESTBED_BLENDNODE_H
#define ANIMTESTBED_BLENDNODE_H
#include "../AnimNode.h"
struct BlendNode : public AnimNode {
BlendNode(AnimationController* animation_controller);
virtual ~BlendNode() {}
AnimNode* m_input_A;
AnimNode* m_input_B;
float m_weight;
bool m_sync_inputs;
ozz::vector<ozz::math::SoaTransform> m_local_matrices_A;
ozz::vector<ozz::math::SoaTransform> m_local_matrices_B;
ozz::math::Transform m_root_transform_A;
ozz::math::Transform m_root_transform_B;
virtual void Reset() { m_time_current = 0.f; }
virtual void UpdateIsSynced(bool is_synced) override {
m_is_time_synced = is_synced;
if (m_sync_inputs) {
m_is_time_synced = true;
}
m_input_B->UpdateIsSynced(m_is_time_synced);
m_input_A->UpdateIsSynced(m_is_time_synced);
}
virtual void UpdateSyncTrack() override {
if (m_is_time_synced) {
m_sync_track = SyncTrack::Blend(
m_weight,
m_input_A->m_sync_track,
m_input_B->m_sync_track);
} else {
assert(false);
}
}
virtual void UpdateTime(float dt) {
if (m_is_time_synced) {
float sync_time_old = m_sync_track.CalcSyncFromAbsTime(m_time_current);
m_time_current = fmodf(m_time_current + dt, m_sync_track.m_duration);
float sync_time_dt =
m_sync_track.CalcSyncFromAbsTime(m_time_current) - sync_time_old;
m_input_A->m_time_current = sync_time_old;
m_input_B->m_time_current = sync_time_old;
m_input_A->UpdateTime(sync_time_dt);
m_input_B->UpdateTime(sync_time_dt);
} else {
m_time_current += dt;
m_input_A->UpdateTime(dt);
m_input_B->UpdateTime(dt);
}
}
virtual void Evaluate(
ozz::vector<ozz::math::SoaTransform>* local_matrices,
ozz::math::Transform* root_transform) override;
virtual void GetInputNodes(
std::vector<AnimNode*>& input_nodes) const override {
input_nodes.push_back(m_input_A);
input_nodes.push_back(m_input_B);
};
virtual void DrawDebugUi();
};
#endif //ANIMTESTBED_BLENDNODE_H

View File

@ -1,67 +0,0 @@
//
// Created by martin on 19.11.21.
//
#include "BlendSpace1D.h"
#include <imgui.h>
#include <ozz/animation/runtime/blending_job.h>
#include "../SkinnedMesh.h"
BlendSpace1D::BlendSpace1D(AnimationController* animation_controller)
: AnimNode(animation_controller),
m_input_0(nullptr),
m_weight_0(0.f),
m_input_1(nullptr),
m_weight_1(0.f),
m_weight(0.f),
m_sync_inputs(false) {
m_anim_node_type = AnimNodeType::Blend;
const SkinnedMesh* skinned_mesh = m_animation_controller->m_skinned_mesh;
const int num_soa_joints = skinned_mesh->m_skeleton.num_soa_joints();
const int num_joints = skinned_mesh->m_skeleton.num_joints();
m_local_matrices_0.resize(num_soa_joints);
m_local_matrices_1.resize(num_soa_joints);
}
void BlendSpace1D::Evaluate(
ozz::vector<ozz::math::SoaTransform>* local_matrices,
ozz::math::Transform* root_transform) {
const SkinnedMesh* skinned_mesh = m_animation_controller->m_skinned_mesh;
m_input_0->Evaluate(
&m_local_matrices_0,
root_transform != nullptr ? &m_root_transform_0 : nullptr);
m_input_1->Evaluate(
&m_local_matrices_1,
root_transform != nullptr ? &m_root_transform_1 : nullptr);
// perform blend
ozz::animation::BlendingJob::Layer layers[2];
layers[0].transform = make_span(m_local_matrices_0);
layers[0].weight = (1.0f - m_normalized_weight);
layers[1].transform = make_span(m_local_matrices_1);
layers[1].weight = (m_normalized_weight);
ozz::animation::BlendingJob blend_job;
blend_job.threshold = ozz::animation::BlendingJob().threshold;
blend_job.layers = layers;
blend_job.bind_pose = skinned_mesh->m_skeleton.joint_bind_poses();
blend_job.output = make_span(*local_matrices);
if (!blend_job.Run()) {
ozz::log::Err() << "Error blending animations." << std::endl;
}
}
void BlendSpace1D::DrawDebugUi() {
float min_weight = m_input_weights[0];
float max_weight = m_input_weights.back();
ImGui::SliderFloat("Weight", &m_weight, min_weight, max_weight);
ImGui::Checkbox("Sync Inputs", &m_sync_inputs);
ImGui::Text("SyncTrack");
m_sync_track.DrawDebugUi();
}

View File

@ -1,110 +0,0 @@
//
// Created by martin on 19.11.21.
//
#ifndef ANIMTESTBED_BLENDSPACE1D_H
#define ANIMTESTBED_BLENDSPACE1D_H
#include "../AnimNode.h"
struct BlendSpace1D : public AnimNode {
BlendSpace1D(AnimationController* animation_controller);
virtual ~BlendSpace1D() {}
int m_num_inputs;
std::vector<float> m_input_weights;
std::vector<AnimNode*> m_inputs;
float m_weight;
bool m_sync_inputs;
AnimNode* m_input_0;
AnimNode* m_input_1;
float m_normalized_weight;
float m_weight_0;
float m_weight_1;
ozz::vector<ozz::math::SoaTransform> m_local_matrices_0;
ozz::vector<ozz::math::SoaTransform> m_local_matrices_1;
ozz::math::Transform m_root_transform_0;
ozz::math::Transform m_root_transform_1;
virtual void Reset() { m_time_current = 0.f; }
virtual void UpdateIsSynced(bool is_synced) override {
m_is_time_synced = is_synced;
if (m_sync_inputs) {
m_is_time_synced = true;
}
assert(m_input_weights.size() > 0);
assert(
m_weight >= m_input_weights[0]
&& m_weight <= m_input_weights[m_input_weights.size() - 1]);
int prev_idx = 0;
for (int next_idx = 1; next_idx < m_input_weights.size(); next_idx++) {
if (m_input_weights[prev_idx] <= m_weight
&& m_input_weights[next_idx] >= m_weight) {
m_input_0 = m_inputs[prev_idx];
m_weight_0 = m_input_weights[prev_idx];
m_input_1 = m_inputs[next_idx];
m_weight_1 = m_input_weights[next_idx];
break;
}
prev_idx = next_idx;
}
m_input_0->UpdateIsSynced(m_is_time_synced);
m_input_1->UpdateIsSynced(m_is_time_synced);
m_normalized_weight = (m_weight - m_weight_0) / (m_weight_1 - m_weight_0);
}
virtual void UpdateSyncTrack() override {
if (m_is_time_synced) {
m_sync_track = SyncTrack::Blend(
m_normalized_weight,
m_input_0->m_sync_track,
m_input_1->m_sync_track);
} else {
assert(false);
}
}
virtual void UpdateTime(float dt) {
if (m_is_time_synced) {
float sync_time_old = m_sync_track.CalcSyncFromAbsTime(m_time_current);
m_time_current = fmodf(m_time_current + dt, m_sync_track.m_duration);
float sync_time_dt =
m_sync_track.CalcSyncFromAbsTime(m_time_current) - sync_time_old;
m_input_0->m_time_current = sync_time_old;
m_input_1->m_time_current = sync_time_old;
m_input_0->UpdateTime(sync_time_dt);
m_input_1->UpdateTime(sync_time_dt);
} else {
m_time_current += dt;
m_input_0->UpdateTime(dt);
m_input_1->UpdateTime(dt);
}
}
virtual void Evaluate(
ozz::vector<ozz::math::SoaTransform>* local_matrices,
ozz::math::Transform* root_transform) override;
virtual void GetInputNodes(
std::vector<AnimNode*>& input_nodes) const override {
for (int i = 0; i < m_inputs.size(); i++) {
input_nodes.push_back(m_inputs[i]);
}
};
virtual void DrawDebugUi();
};
#endif //ANIMTESTBED_BLENDSPACE1D_H

View File

@ -1,61 +0,0 @@
//
// Created by martin on 16.11.21.
//
#include "LockTranslationNode.h"
#include <imgui.h>
#include "ozz/base/maths/soa_transform.h"
void LockTranslationNode::Evaluate(
ozz::vector<ozz::math::SoaTransform>* local_matrices,
ozz::math::Transform* root_transform) {
m_input->Evaluate(local_matrices, root_transform);
ozz::math::SoaFloat3 translation =
(*local_matrices)[m_locked_bone_index].translation;
float x[4];
float y[4];
float z[4];
_mm_store_ps(x, translation.x);
_mm_store_ps(y, translation.y);
_mm_store_ps(z, translation.z);
if (m_lock_x) {
x[0] = 0.f;
}
if (m_lock_y) {
y[0] = 0.f;
}
if (m_lock_z) {
z[0] = 0.f;
}
translation.x = _mm_load_ps(x);
translation.y = _mm_load_ps(y);
translation.z = _mm_load_ps(z);
//translation = ozz::math::SoaFloat3::zero();
// ozz::math::SetX(translation, 0.f);
// ozz::math::SetZ(translation, 0.f);
(*local_matrices)[m_locked_bone_index].translation = translation;
}
void LockTranslationNode::DrawDebugUi() {
const ozz::animation::Skeleton& skeleton =
m_animation_controller->m_skinned_mesh->m_skeleton;
ozz::span<const char* const> joint_names = skeleton.joint_names();
const char* items[255] = {0};
int item_current = 0;
for (int i = 0; i < joint_names.size(); i++) {
items[i] = joint_names[i];
}
ImGui::Combo("Bone", &m_locked_bone_index, items, joint_names.size());
ImGui::Checkbox("Lock X", &m_lock_x);
ImGui::Checkbox("Lock Y", &m_lock_y);
ImGui::Checkbox("Lock Z", &m_lock_z);
}

View File

@ -1,53 +0,0 @@
//
// Created by martin on 16.11.21.
//
#ifndef ANIMTESTBED_LOCKBONES_H
#define ANIMTESTBED_LOCKBONES_H
#include "../AnimNode.h"
struct LockTranslationNode : public AnimNode {
LockTranslationNode(AnimationController* animation_controller)
: AnimNode(animation_controller),
m_input(nullptr),
m_locked_bone_index(0),
m_lock_x(false),
m_lock_y(false),
m_lock_z(false) {}
virtual ~LockTranslationNode() {}
AnimNode* m_input;
int m_locked_bone_index;
bool m_lock_x;
bool m_lock_y;
bool m_lock_z;
virtual void Reset() { m_time_current = 0.f; }
virtual void UpdateIsSynced(bool is_synced) override {
m_is_time_synced = is_synced;
m_input->UpdateIsSynced(m_is_time_synced);
}
virtual void UpdateSyncTrack() override {
m_sync_track = m_input->m_sync_track;
}
virtual void UpdateTime(float dt) { m_input->UpdateTime(dt); }
virtual void Evaluate(
ozz::vector<ozz::math::SoaTransform>* local_matrices,
ozz::math::Transform* root_transform = nullptr) override;
virtual void GetInputNodes(
std::vector<AnimNode*>& input_nodes) const override {
input_nodes.push_back(m_input);
};
virtual void DrawDebugUi() override;
};
#endif //ANIMTESTBED_LOCKBONES_H

View File

@ -1,20 +0,0 @@
//
// Created by martin on 12.11.21.
//
#include "SpeedScaleNode.h"
#include <imgui.h>
void SpeedScaleNode::DrawDebugUi() {
bool is_negative = m_time_scale < 0.f;
if (ImGui::Checkbox("Reverse Time", &is_negative)) {
m_time_scale = m_time_scale * -1.f;
}
// ensure m_time_scale is positive
m_time_scale = m_time_scale * (is_negative ? -1.f : 1.f);
ImGui::SliderFloat("Time Scale", &m_time_scale, 0.01f, 5.f);
// and back to the original negative or positive sign
m_time_scale = m_time_scale * (is_negative ? -1.f : 1.f);
}

View File

@ -1,57 +0,0 @@
//
// Created by martin on 12.11.21.
//
#ifndef ANIMTESTBED_SPEEDSCALENODE_H
#define ANIMTESTBED_SPEEDSCALENODE_H
#include "../AnimNode.h"
struct SpeedScaleNode : public AnimNode {
SpeedScaleNode(AnimationController* animation_controller)
: AnimNode(animation_controller), m_time_scale(1.f) {
m_anim_node_type = AnimNodeType::SpeedScale;
}
float m_time_scale;
AnimNode* m_input_node;
virtual void Reset() { m_time_current = 0.f; }
virtual void UpdateIsSynced(bool is_synced) override {
m_is_time_synced = is_synced;
m_input_node->UpdateIsSynced(is_synced);
}
virtual void UpdateSyncTrack() override {
assert(fabs(m_time_scale) >= 0.01f);
m_sync_track = m_input_node->m_sync_track;
m_sync_track.m_duration =
fabsf(m_input_node->m_sync_track.m_duration / m_time_scale);
}
virtual void UpdateTime(float dt) {
if (!m_is_time_synced) {
m_time_current += dt * m_time_scale;
m_input_node->UpdateTime(dt * m_time_scale);
} else {
m_time_current += dt;
m_input_node->UpdateTime(dt);
}
}
virtual void Evaluate(
ozz::vector<ozz::math::SoaTransform>* local_matrices,
ozz::math::Transform* root_transform) override {
m_input_node->Evaluate(local_matrices, root_transform);
};
virtual void GetInputNodes(
std::vector<AnimNode*>& input_nodes) const override {
input_nodes.push_back(m_input_node);
};
virtual void DrawDebugUi() override;
};
#endif //ANIMTESTBED_SPEEDSCALENODE_H

View File

@ -1,242 +0,0 @@
//
// Created by martin on 12.11.21.
//
#include "AnimationController.h"
#include <imgui.h>
#include <ozz/animation/runtime/local_to_model_job.h>
#include <ozz/animation/runtime/sampling_job.h>
#include <queue>
#include "AnimNodes/AnimSamplerNode.h"
#include "AnimNodes/BlendNode.h"
#include "AnimNodes/BlendSpace1D.h"
#include "AnimNodes/LockTranslationNode.h"
#include "AnimNodes/SpeedScaleNode.h"
#include "SkinnedMesh.h"
AnimationController::AnimationController(SkinnedMesh* skinned_mesh)
: m_current_time(0.f), m_paused(true), m_skinned_mesh(skinned_mesh) {
const int num_soa_joints = skinned_mesh->m_skeleton.num_soa_joints();
const int num_joints = skinned_mesh->m_skeleton.num_joints();
skinned_mesh->m_local_matrices.resize(num_soa_joints);
skinned_mesh->m_model_matrices.resize(num_joints);
ResetAnims();
AnimSamplerNode* sampler_node0 = new AnimSamplerNode(this);
sampler_node0->m_name = "AnimSampler0";
sampler_node0->SetAnimation(
skinned_mesh->m_animations[1],
skinned_mesh->m_animation_sync_track[1]);
m_anim_nodes.push_back(sampler_node0);
AnimSamplerNode* sampler_node1 = new AnimSamplerNode(this);
sampler_node1->m_name = "AnimSampler1";
sampler_node1->SetAnimation(
skinned_mesh->m_animations[2],
skinned_mesh->m_animation_sync_track[2]);
m_anim_nodes.push_back(sampler_node1);
AnimSamplerNode* sampler_node2 = new AnimSamplerNode(this);
sampler_node2->m_name = "AnimSampler2";
sampler_node2->SetAnimation(
skinned_mesh->m_animations[3],
skinned_mesh->m_animation_sync_track[3]);
m_anim_nodes.push_back(sampler_node2);
BlendSpace1D* blend_space = new BlendSpace1D(this);
blend_space->m_name = "BlendSpace0";
blend_space->m_weight = 0.f;
blend_space->m_inputs.push_back(sampler_node0);
blend_space->m_input_weights.push_back(-1.f);
blend_space->m_inputs.push_back(sampler_node1);
blend_space->m_input_weights.push_back(0.f);
blend_space->m_inputs.push_back(sampler_node2);
blend_space->m_input_weights.push_back(1.f);
m_anim_nodes.push_back(blend_space);
SpeedScaleNode* speed_node = new SpeedScaleNode(this);
speed_node->m_name = "SpeedNode0";
speed_node->m_input_node = sampler_node0;
m_anim_nodes.push_back(speed_node);
BlendNode* blend_node = new BlendNode(this);
blend_node->m_name = "Blend0";
blend_node->m_input_A = speed_node;
blend_node->m_input_B = sampler_node1;
blend_node->m_sync_inputs = true;
m_anim_nodes.push_back(blend_node);
SpeedScaleNode* speed_node1 = new SpeedScaleNode(this);
speed_node1->m_name = "SpeedNode1";
speed_node1->m_input_node = blend_space;
m_anim_nodes.push_back(speed_node1);
LockTranslationNode* lock_node = new LockTranslationNode(this);
lock_node->m_name = "LockNode0";
lock_node->m_locked_bone_index = 0;
lock_node->m_input = speed_node1;
m_anim_nodes.push_back(lock_node);
m_output_node = m_anim_nodes.back();
UpdateOrderedNodes();
m_output_node->Reset();
}
AnimationController::~AnimationController() {
while (m_anim_nodes.size() > 0) {
delete m_anim_nodes[m_anim_nodes.size() - 1];
m_anim_nodes.pop_back();
}
m_output_node = nullptr;
}
void AnimationController::ResetAnims() {
for (int i = 0; i < m_ordered_nodes.size(); i++) {
m_ordered_nodes[i]->Reset();
}
}
void AnimationController::UpdateOrderedNodes() {
std::vector<AnimNode*> node_stack;
node_stack.push_back(m_output_node);
m_ordered_nodes.clear();
while (node_stack.size() > 0) {
AnimNode* node = node_stack.back();
m_ordered_nodes.push_back(node);
node_stack.pop_back();
std::vector<AnimNode*> node_inputs;
node->GetInputNodes(node_inputs);
for (int i = node_inputs.size() - 1; i >= 0; i--) {
node_stack.push_back(node_inputs[i]);
}
}
}
void AnimationController::UpdateTime(float dt) {
if (m_output_node == nullptr) {
return;
}
// Mark all nodes that evaluate time using sync tracks.
m_output_node->UpdateIsSynced(false);
if (m_paused) {
return;
}
// For all synced nodes calculate their current sync track durations
for (int i = m_ordered_nodes.size() - 1; i >= 0; i--) {
AnimNode* node = m_ordered_nodes[i];
if (node->m_is_time_synced) {
node->UpdateSyncTrack();
}
}
// Update the time of all nodes.
m_output_node->UpdateTime(dt);
}
void AnimationController::Evaluate() {
if (m_output_node == nullptr) {
return;
}
m_output_node->Evaluate(
&m_skinned_mesh->m_local_matrices,
m_calc_root_transform ? &m_root_transform : nullptr);
};
void AnimationController::DrawDebugUi() {
ImGui::SetNextWindowSize(ImVec2(500, 300), ImGuiCond_FirstUseEver);
ImGui::Begin("AnimationController");
if (ImGui::Button("Reset")) {
ResetAnims();
}
ImGui::SameLine();
if (m_paused) {
if (ImGui::Button("Play")) {
m_paused = false;
}
} else {
if (ImGui::Button("Pause")) {
m_paused = true;
}
}
ImGui::SameLine();
if (ImGui::Button("Step")) {
bool was_paused = m_paused;
m_paused = false;
UpdateTime(0.1);
Evaluate();
m_paused = was_paused;
}
ImVec2 node_size(200, 100);
for (int i = 0; i < m_ordered_nodes.size(); i++) {
AnimNode* node = m_ordered_nodes[i];
ImGui::SetNextWindowSize(node_size, ImGuiCond_FirstUseEver);
ImGui::SetNextWindowPos(
ImVec2((m_ordered_nodes.size() - 1 - i) * node_size.x - i * 10, 300),
ImGuiCond_FirstUseEver);
ImGui::Begin(node->m_name.c_str());
node->DrawDebugUi();
ImGui::End();
}
ImGui::Text("Node States");
ImGui::Columns(4, "Node States"); // 4-ways, with border
ImGui::Separator();
ImGui::Text("Name");
ImGui::NextColumn();
ImGui::Text("Synced");
ImGui::NextColumn();
ImGui::Text("Duration");
ImGui::NextColumn();
ImGui::Text("Time");
ImGui::NextColumn();
ImGui::Separator();
static int selected = -1;
for (int i = 0; i < m_ordered_nodes.size(); i++) {
AnimNode* node = m_ordered_nodes[i];
if (ImGui::Selectable(
node->m_name.c_str(),
selected == i,
ImGuiSelectableFlags_SpanAllColumns))
selected = i;
bool hovered = ImGui::IsItemHovered();
ImGui::NextColumn();
ImGui::Text(node->m_is_time_synced ? "X" : "-");
ImGui::NextColumn();
ImGui::PushID((void*)&node->m_sync_track.m_duration);
ImGui::Text("%2.3f", node->m_sync_track.m_duration);
ImGui::NextColumn();
ImGui::PopID();
ImGui::PushID((void*)&node->m_time_current);
ImGui::Text("%2.3f", node->m_time_current);
ImGui::NextColumn();
ImGui::PopID();
}
ImGui::Columns(1);
ImGui::Separator();
ImGui::End();
}

View File

@ -1,48 +0,0 @@
//
// Created by martin on 12.11.21.
//
#ifndef ANIMTESTBED_ANIMATIONCONTROLLER_H
#define ANIMTESTBED_ANIMATIONCONTROLLER_H
#include <ozz/animation/runtime/animation.h>
#include <ozz/animation/runtime/sampling_job.h>
#include <ozz/base/containers/vector.h>
#include <ozz/base/maths/soa_transform.h>
#include <ozz/base/maths/transform.h>
#include <ozz/base/maths/vec_float.h>
struct SkinnedMesh;
struct AnimNode;
struct AnimationController {
explicit AnimationController(SkinnedMesh* skinned_mesh);
virtual ~AnimationController();
void ResetAnims();
// Creates a list of nodes where for node at index i for all inputs holds index > i.
void UpdateOrderedNodes();
// Updates all nodes.
void UpdateTime(float dt);
// Recursively evaluates all nodes.
void Evaluate();
void DrawDebugUi();
float m_current_time;
bool m_paused;
bool m_calc_root_transform;
ozz::math::Transform m_root_transform;
SkinnedMesh* m_skinned_mesh = nullptr;
AnimNode* m_output_node;
std::vector<AnimNode*> m_anim_nodes;
std::vector<AnimNode*> m_ordered_nodes;
};
#endif //ANIMTESTBED_ANIMATIONCONTROLLER_H

View File

@ -17,7 +17,7 @@
#include "Camera.h"
#include "SkinnedMesh.h"
#include "AnimGraphEditor.h"
#include "src/AnimGraph/AnimGraphEditor.h"
#include "GLFW/glfw3.h"
const int Width = 1024;
@ -28,6 +28,7 @@ const int MaxIndices = MaxVertices * 3;
uint64_t last_time = 0;
bool show_imgui_demo_window = false;
bool show_another_window = false;
bool show_graph_editor = true;
sg_pass_action pass_action;
sg_pipeline pip;
@ -48,7 +49,6 @@ static void draw_imgui(ImDrawData*);
#include <cmath> // fmodf
#include <memory> // std::unique_ptr, std::make_unique
#include "AnimationController.h"
#include "SkinnedMeshRenderer.h"
#include "ozz/animation/runtime/animation.h"
#include "ozz/animation/runtime/local_to_model_job.h"
@ -110,15 +110,6 @@ 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];
@ -206,7 +197,7 @@ int main() {
sgldesc.sample_count = 0;
sgl_setup(&sgldesc);
printf ("default allocator: 0x%p\n", (void*)ozz::memory::default_allocator());
// Animation setup
SkinnedMesh skinned_mesh;
skinned_mesh.LoadSkeleton("../media/MixamoYBot-skeleton.ozz");
skinned_mesh.LoadAnimation("../media/Idle-loop.ozz");
@ -242,9 +233,6 @@ int main() {
skinned_mesh.SetCurrentAnimation(0);
AnimationController animation_controller (&skinned_mesh);
// state.ozz = std::make_unique<ozz_t>();
state.time.factor = 1.0f;
Camera_Init(&state.camera);
@ -433,20 +421,13 @@ 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("Graph Editor", &show_graph_editor);
ImGui::Checkbox("ImGui Demo", &show_imgui_demo_window);
ImGui::EndMainMenuBar();
}
if (gAppMode == AppMode::AnimRuntime) {
// Animation Runtime
{
ImGui::Begin("Camera");
ImGui::SliderFloat3("pos", state.camera.pos, -100.f, 100.f);
ImGui::SliderFloat("near", &state.camera.near, 0.001f, 10.f);
@ -460,13 +441,9 @@ int main() {
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();
// TODO: add AnimGraph to calculate pose
// skinned_mesh.CalcModelMatrices();
sgl_defaults();
sgl_matrix_mode_projection();
@ -474,19 +451,22 @@ int main() {
sgl_matrix_mode_modelview();
sgl_load_matrix((const float*)&state.camera.mtxView);
RenderSkinnedMesh(skinned_mesh);
} else if (gAppMode == AppMode::GraphEditor) {
}
// Animation Graph Editor
if (show_graph_editor) {
ImGui::SetNextWindowPos(ImVec2(20, 20), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSize(ImVec2(500, 400), ImGuiCond_FirstUseEver);
ImGui::Begin("Graph Editor", nullptr, ImGuiWindowFlags_MenuBar);
ImGui::Begin(
"Graph Editor",
&show_graph_editor,
ImGuiWindowFlags_MenuBar);
AnimGraphEditorUpdate();
ImGui::End();
} else if (gAppMode == AppMode::Debug) {
ImGui::SetNextWindowPos(ImVec2(460, 20), ImGuiCond_FirstUseEver);
ImGui::Begin("Debug");
ImGui::End();
}
// 3. Show the ImGui test window. Most of the sample code is in ImGui::ShowDemoWindow()
if (show_imgui_demo_window) {
ImGui::SetNextWindowPos(ImVec2(460, 20), ImGuiCond_FirstUseEver);

View File

@ -2,11 +2,11 @@
// Created by martin on 04.02.22.
//
#include "AnimGraphResource.h"
#include "AnimGraph/AnimGraph.h"
#include "AnimGraph/AnimGraphEditor.h"
#include "AnimGraph/AnimGraphResource.h"
#include "catch.hpp"
using namespace AniGraph;
TEST_CASE("BasicGraph", "[AnimGraphResource]") {
AnimGraphResource graph_resource;
@ -24,9 +24,9 @@ TEST_CASE("BasicGraph", "[AnimGraphResource]") {
AnimNodeResource& walk_node = graph_resource.m_nodes[walk_node_index];
walk_node.m_name = "WalkAnim";
AnimNodeResource& run_node = graph_resource.m_nodes[run_node_index];
walk_node.m_name = "RunAnim";
run_node.m_name = "RunAnim";
AnimNodeResource& blend_node = graph_resource.m_nodes[blend_node_index];
walk_node.m_name = "BlendWalkRun";
blend_node.m_name = "BlendWalkRun";
AnimNodeResource& graph_node = graph_resource.m_nodes[0];
graph_node.m_socket_accessor->RegisterInput<AnimData>("GraphOutput", nullptr);
@ -35,36 +35,30 @@ TEST_CASE("BasicGraph", "[AnimGraphResource]") {
REQUIRE(blend_node.m_socket_accessor->GetInputIndex("Input0") == 0);
REQUIRE(blend_node.m_socket_accessor->GetInputIndex("Input1") == 1);
AnimGraphConnection walk_to_blend;
walk_to_blend.m_source_node_index = walk_node_index;
walk_to_blend.m_source_socket_index =
walk_node.m_socket_accessor->GetOutputIndex("Output");
walk_to_blend.m_target_node_index = blend_node_index;
walk_to_blend.m_target_socket_index =
blend_node.m_socket_accessor->GetInputIndex("Input0");
AnimGraphConnectionResource walk_to_blend;
walk_to_blend.source_node_index = walk_node_index;
walk_to_blend.source_socket_name = "Output";
walk_to_blend.target_node_index = blend_node_index;
walk_to_blend.target_socket_name = "Input0";
graph_resource.m_connections.push_back(walk_to_blend);
AnimGraphConnection run_to_blend;
run_to_blend.m_source_node_index = run_node_index;
run_to_blend.m_source_socket_index =
run_node.m_socket_accessor->GetOutputIndex("Output");
run_to_blend.m_target_node_index = blend_node_index;
run_to_blend.m_target_socket_index =
blend_node.m_socket_accessor->GetInputIndex("Input1");
AnimGraphConnectionResource run_to_blend;
run_to_blend.source_node_index = run_node_index;
run_to_blend.source_socket_name = "Output";
run_to_blend.target_node_index = blend_node_index;
run_to_blend.target_socket_name = "Input1";
graph_resource.m_connections.push_back(run_to_blend);
AnimGraphConnection blend_to_output;
blend_to_output.m_source_node_index = blend_node_index;
blend_to_output.m_source_socket_index =
blend_node.m_socket_accessor->GetOutputIndex("Output");
blend_to_output.m_target_node_index = 0;
blend_to_output.m_target_socket_index =
graph_node.m_socket_accessor->GetInputIndex("GraphOutput");
AnimGraphConnectionResource blend_to_output;
blend_to_output.source_node_index = blend_node_index;
blend_to_output.source_socket_name = "Output";
blend_to_output.target_node_index = 0;
blend_to_output.target_socket_name = "GraphOutput";
graph_resource.m_connections.push_back(blend_to_output);
graph_resource.saveToFile("WalkGraph.animgraph.json");
AnimGraph graph = AnimGraph::createFromResource(graph_resource);
AnimGraph graph = graph_resource.createInstance();
REQUIRE(graph.m_nodes.size() == 5);
REQUIRE(graph.m_nodes[0]->m_node_type_name == "BlendTree");
@ -74,32 +68,61 @@ TEST_CASE("BasicGraph", "[AnimGraphResource]") {
REQUIRE(graph.m_nodes[4]->m_node_type_name == "Blend2");
// connections within the graph
AnimSamplerNode* anim_sampler_instance0 =
AnimSamplerNode* anim_sampler_walk =
dynamic_cast<AnimSamplerNode*>(graph.m_nodes[2]);
AnimSamplerNode* anim_sampler_instance1 =
AnimSamplerNode* anim_sampler_run =
dynamic_cast<AnimSamplerNode*>(graph.m_nodes[3]);
Blend2Node* blend2_instance = dynamic_cast<Blend2Node*>(graph.m_nodes[4]);
CHECK(anim_sampler_instance0->m_output == &blend2_instance->m_input0);
CHECK(anim_sampler_instance1->m_output == &blend2_instance->m_input1);
// connections from graph to the graph node
CHECK(graph.m_socket_accessor->m_inputs.size() == 1);
CHECK(graph.m_socket_accessor->FindInputSocket("GraphOutput"));
CHECK(
reinterpret_cast<char*>(blend2_instance->m_output)
== graph.getOutput("GraphOutput"));
// check node input dependencies
size_t anim_sampler_index0 = anim_sampler_instance0->m_index;
size_t anim_sampler_index1 = anim_sampler_instance1->m_index;
size_t anim_sampler_index0 = anim_sampler_walk->m_index;
size_t anim_sampler_index1 = anim_sampler_run->m_index;
size_t blend_index = blend2_instance->m_index;
CHECK(graph.m_node_inputs[anim_sampler_index0].size() == 0);
CHECK(graph.m_node_inputs[anim_sampler_index1].size() == 0);
CHECK(graph.m_node_inputs[blend_index].size() == 3);
CHECK(graph.m_node_inputs[blend_index][0].m_node == anim_sampler_instance0);
CHECK(graph.m_node_inputs[blend_index][1].m_node == anim_sampler_instance1);
CHECK(graph.m_node_inputs[blend_index][2].m_node == nullptr);
REQUIRE(graph.m_node_input_connections[blend_index].size() == 2);
CHECK(
graph.m_node_input_connections[blend_index][0].m_source_node
== anim_sampler_walk);
CHECK(
graph.m_node_input_connections[blend_index][1].m_source_node
== anim_sampler_run);
REQUIRE(graph.m_node_output_connections[anim_sampler_index0].size() == 1);
CHECK(
graph.m_node_output_connections[anim_sampler_index0][0].m_target_node
== blend2_instance);
REQUIRE(graph.m_node_output_connections[anim_sampler_index1].size() == 1);
CHECK(
graph.m_node_output_connections[anim_sampler_index1][0].m_target_node
== blend2_instance);
// Emulate evaluation
CHECK(graph.m_anim_data_work_buffer.m_available_data.size() == 5);
graph.prepareNodeEval(walk_node_index);
graph.finishNodeEval(walk_node_index);
CHECK(graph.m_anim_data_work_buffer.m_available_data.size() == 4);
graph.prepareNodeEval(run_node_index);
graph.finishNodeEval(run_node_index);
CHECK(graph.m_anim_data_work_buffer.m_available_data.size() == 3);
graph.prepareNodeEval(blend_node_index);
CHECK(blend2_instance->i_input0 == anim_sampler_walk->o_output);
CHECK(blend2_instance->i_input1 == anim_sampler_run->o_output);
CHECK(graph.m_anim_data_work_buffer.m_available_data.size() == 2);
graph.finishNodeEval(blend_node_index);
CHECK(anim_sampler_walk->o_output == nullptr);
CHECK(anim_sampler_run->o_output == nullptr);
CHECK(graph.m_anim_data_work_buffer.m_available_data.size() == 4);
graph.prepareNodeEval(0);
const Socket* graph_output_socket = graph.getOutputSocket("GraphOutput");
CHECK(blend2_instance->o_output == (*graph_output_socket->m_value.ptr_ptr));
AnimData* graph_output =
static_cast<AnimData*>(*graph_output_socket->m_value.ptr_ptr);
graph.finishNodeEval(0);
CHECK(blend2_instance->o_output == nullptr);
CHECK(graph_output == (*graph_output_socket->m_value.ptr_ptr));
CHECK(graph.m_anim_data_work_buffer.m_available_data.size() == 5);
}
TEST_CASE("InputAttributeConversion", "[AnimGraphResource]") {
@ -121,30 +144,62 @@ TEST_CASE("InputAttributeConversion", "[AnimGraphResource]") {
CHECK(output_index == parsed_output_index);
}
TEST_CASE("ResourceSaveLoadGraphInputs", "[AnimGraphResource]") {
TEST_CASE("ResourceSaveLoadMathGraphInputs", "[AnimGraphResource]") {
AnimGraphResource graph_resource_origin;
graph_resource_origin.clear();
graph_resource_origin.m_name = "TestInputOutputGraph";
AnimNodeResource& graph_output_node = graph_resource_origin.m_nodes[0];
graph_output_node.m_socket_accessor->RegisterInput<AnimData>(
"GraphOutput",
nullptr);
size_t float_to_vec3_node_index =
graph_resource_origin.addNode(AnimNodeResourceFactory("MathFloatToVec3Node"));
AnimNodeResource& graph_output_node =
graph_resource_origin.getGraphOutputNode();
graph_output_node.m_socket_accessor->RegisterInput<float>(
"SomeFloatOutput",
"GraphFloatOutput",
nullptr);
graph_output_node.m_socket_accessor->RegisterInput<Vec3>(
"GraphVec3Output",
nullptr);
AnimNodeResource& graph_input_node = graph_resource_origin.m_nodes[1];
graph_input_node.m_socket_accessor->RegisterOutput<AnimData>(
"GraphAnimInput",
nullptr);
AnimNodeResource& graph_input_node =
graph_resource_origin.getGraphInputNode();
graph_input_node.m_socket_accessor->RegisterOutput<float>(
"GraphFloatInput",
nullptr);
graph_input_node.m_socket_accessor->RegisterOutput<bool>(
"GraphBoolInput",
nullptr);
// Prepare graph inputs and outputs
AnimNodeResource& float_to_vec3_node =
graph_resource_origin.m_nodes[float_to_vec3_node_index];
REQUIRE(graph_resource_origin.connectSockets(
graph_input_node,
"GraphFloatInput",
graph_output_node,
"GraphFloatOutput"));
REQUIRE(graph_resource_origin.connectSockets(
graph_input_node,
"GraphFloatInput",
float_to_vec3_node,
"Input0"));
REQUIRE(graph_resource_origin.connectSockets(
graph_input_node,
"GraphFloatInput",
float_to_vec3_node,
"Input1"));
REQUIRE(graph_resource_origin.connectSockets(
graph_input_node,
"GraphFloatInput",
float_to_vec3_node,
"Input2"));
REQUIRE(graph_resource_origin.connectSockets(
float_to_vec3_node,
"Output",
graph_output_node,
"GraphVec3Output"));
WHEN("Saving and loading graph resource") {
const char* filename = "ResourceSaveLoadGraphInputs.json";
@ -167,43 +222,49 @@ TEST_CASE("ResourceSaveLoadGraphInputs", "[AnimGraphResource]") {
graph_input_node.m_socket_accessor->m_outputs.size()
== graph_loaded_input_node.m_socket_accessor->m_outputs.size());
REQUIRE(
graph_loaded_input_node.m_socket_accessor->FindOutputSocket(
"GraphAnimInput")
!= nullptr);
REQUIRE(
graph_loaded_input_node.m_socket_accessor->FindOutputSocket(
"GraphFloatInput")
!= nullptr);
REQUIRE(
graph_loaded_input_node.m_socket_accessor->FindOutputSocket(
"GraphBoolInput")
!= nullptr);
REQUIRE(
graph_loaded_output_node.m_socket_accessor->FindInputSocket(
"GraphOutput")
"GraphFloatOutput")
!= nullptr);
REQUIRE(
graph_loaded_output_node.m_socket_accessor->FindInputSocket(
"SomeFloatOutput")
"GraphVec3Output")
!= nullptr);
}
WHEN("Instantiating an AnimGraph") {
AnimGraph anim_graph =
AnimGraph::createFromResource(graph_resource_loaded);
REQUIRE(
anim_graph.getOutput("GraphOutput") == anim_graph.m_output_buffer);
REQUIRE(
anim_graph.getOutput("SomeFloatOutput")
== anim_graph.m_output_buffer + sizeof(AnimData));
AnimGraph anim_graph = graph_resource_loaded.createInstance();
REQUIRE(anim_graph.getInput("GraphAnimInput") == nullptr);
REQUIRE(anim_graph.getInput("GraphFloatInput") == nullptr);
REQUIRE(anim_graph.getInput("GraphBoolInput") == nullptr);
REQUIRE(anim_graph.getInputSocket("GraphFloatInput") != nullptr);
REQUIRE(anim_graph.getInputPtr("GraphFloatInput") == anim_graph.m_input_buffer);
float* graph_float_input = nullptr;
graph_float_input = static_cast<float*>(anim_graph.getInputPtr("GraphFloatInput"));
*graph_float_input = 123.456f;
anim_graph.updateTime(0.f);
anim_graph.evaluate();
anim_graph.evalOutputNode();
Socket* float_output_socket = anim_graph.getOutputSocket("GraphFloatOutput");
Socket* vec3_output_socket = anim_graph.getOutputSocket("GraphVec3Output");
Vec3& vec3_output = *static_cast<Vec3*>(vec3_output_socket->m_value.ptr);
CHECK(vec3_output[0] == *graph_float_input);
CHECK(vec3_output[1] == *graph_float_input);
CHECK(vec3_output[2] == *graph_float_input);
}
}
}
}
/*
WHEN("Connecting input to output and instantiating the graph") {
AnimNodeResource& graph_output_node = graph_resource_origin.m_nodes[0];
@ -215,7 +276,7 @@ TEST_CASE("ResourceSaveLoadGraphInputs", "[AnimGraphResource]") {
graph_output_node,
"GraphOutput"));
AnimGraph anim_graph = AnimGraph::createFromResource(graph_resource_origin);
AnimGraph anim_graph = graph_resource_origin.createInstance();
void* graph_anim_input_ptr = anim_graph.getInput("GraphAnimInput");
void* graph_output_ptr = anim_graph.getOutput("GraphOutput");
@ -264,7 +325,7 @@ TEST_CASE("GraphInputOutputConnectivity", "[AnimGraphResource]") {
graph_resource.getGraphOutputNode(),
"GraphFloatOutput"));
AnimGraph anim_graph = AnimGraph::createFromResource(graph_resource);
AnimGraph anim_graph = graph_resource.createInstance();
THEN("Writing to the input pointer changes the value of the output.") {
float* float_input_ptr = (float*)anim_graph.getInput("GraphFloatInput");
@ -291,15 +352,16 @@ TEST_CASE("GraphInputOutputConnectivity", "[AnimGraphResource]") {
"Weight"));
THEN("Connected float input points to the blend weight.") {
AnimGraph anim_graph = AnimGraph::createFromResource(graph_resource);
AnimGraph anim_graph = graph_resource.createInstance();
Blend2Node* blend2_node =
dynamic_cast<Blend2Node*>(anim_graph.m_nodes[blend2_node_index]);
REQUIRE(
*anim_graph.m_socket_accessor->m_outputs[0].m_value.ptr_ptr
== &blend2_node->m_blend_weight);
== blend2_node->i_blend_weight);
float* float_input_ptr = (float*)anim_graph.getInput("GraphFloatInput");
REQUIRE(float_input_ptr == &blend2_node->m_blend_weight);
REQUIRE(float_input_ptr == blend2_node->i_blend_weight);
}
WHEN(
@ -326,27 +388,28 @@ TEST_CASE("GraphInputOutputConnectivity", "[AnimGraphResource]") {
THEN(
"AnimData from output gets blended and result is written to "
"Output.") {
AnimGraph anim_graph = AnimGraph::createFromResource(graph_resource);
AnimGraph anim_graph = graph_resource.createInstance();
Blend2Node* blend2_node =
dynamic_cast<Blend2Node*>(anim_graph.m_nodes[blend2_node_index]);
AnimData* graph_input0 =
(AnimData*)anim_graph.getInput("GraphAnimInput0");
REQUIRE(graph_input0 == &blend2_node->m_input0);
REQUIRE(graph_input0 == blend2_node->i_input0);
REQUIRE(
anim_graph.m_nodes[1]
== anim_graph.getAnimNodeForInput(blend2_node_index, "Input0"));
AnimData* graph_input1 =
(AnimData*)anim_graph.getInput("GraphAnimInput1");
REQUIRE(graph_input1 == &blend2_node->m_input1);
REQUIRE(graph_input1 == blend2_node->i_input1);
REQUIRE(
anim_graph.m_nodes[1]
== anim_graph.getAnimNodeForInput(blend2_node_index, "Input1"));
AnimData* graph_output =
(AnimData*)anim_graph.getOutput("GraphAnimOutput");
REQUIRE(graph_output == blend2_node->m_output);
REQUIRE(graph_output == blend2_node->o_output);
REQUIRE(
anim_graph.m_nodes[blend2_node_index]
== anim_graph.getAnimNodeForInput(0, "GraphAnimOutput"));
@ -406,7 +469,7 @@ TEST_CASE("GraphInputOutputConnectivity", "[AnimGraphResource]") {
"GraphAnimOutput"));
THEN("Data flow and node ordering must be correct.") {
AnimGraph anim_graph = AnimGraph::createFromResource(graph_resource);
AnimGraph anim_graph = graph_resource.createInstance();
Blend2Node* blend2_node =
dynamic_cast<Blend2Node*>(anim_graph.m_nodes[blend2_node_index]);
SpeedScaleNode* speed_scale_node = dynamic_cast<SpeedScaleNode*>(
@ -419,7 +482,7 @@ TEST_CASE("GraphInputOutputConnectivity", "[AnimGraphResource]") {
//
AnimData* graph_input0 =
(AnimData*)anim_graph.getInput("GraphAnimInput0");
REQUIRE(graph_input0 == &blend2_node->m_input0);
REQUIRE(graph_input0 == blend2_node->i_input0);
REQUIRE(
anim_graph.m_nodes[1]
== anim_graph.getAnimNodeForInput(blend2_node_index, "Input0"));
@ -428,19 +491,19 @@ TEST_CASE("GraphInputOutputConnectivity", "[AnimGraphResource]") {
(AnimData*)anim_graph.getInput("GraphAnimInput1");
REQUIRE(graph_input1 == nullptr);
REQUIRE(sampler_node->m_output == &speed_scale_node->m_input);
REQUIRE(sampler_node->o_output == speed_scale_node->i_input);
REQUIRE(
sampler_node
== anim_graph.getAnimNodeForInput(speed_scale_node_index, "Input"));
REQUIRE(speed_scale_node->m_output == &blend2_node->m_input1);
REQUIRE(speed_scale_node->i_output == blend2_node->i_input1);
REQUIRE(
speed_scale_node
== anim_graph.getAnimNodeForInput(blend2_node_index, "Input1"));
AnimData* graph_output =
(AnimData*)anim_graph.getOutput("GraphAnimOutput");
REQUIRE(graph_output == blend2_node->m_output);
REQUIRE(graph_output == blend2_node->o_output);
REQUIRE(
anim_graph.m_nodes[blend2_node_index]
== anim_graph.getAnimNodeForInput(0, "GraphAnimOutput"));
@ -460,7 +523,7 @@ TEST_CASE("GraphInputOutputConnectivity", "[AnimGraphResource]") {
}
WHEN("Instantiating graph") {
AnimGraph anim_graph = AnimGraph::createFromResource(graph_resource);
AnimGraph anim_graph = graph_resource.createInstance();
float* blend_weight_input =
reinterpret_cast<float*>(anim_graph.getInput("GraphFloatInput"));
@ -533,3 +596,5 @@ TEST_CASE("GraphInputOutputConnectivity", "[AnimGraphResource]") {
}
}
}
*/

View File

@ -1,126 +0,0 @@
//
// Created by martin on 21.11.21.
//
#include "AnimNodes/AnimSamplerNode.h"
#define OZZ_INCLUDE_PRIVATE_HEADER
#include "../src/animation/runtime/animation_keyframe.h"
#include "catch.hpp"
#include "ozzutils.h"
void get_bone_transform(
int i_bone_idx,
const ozz::vector<ozz::math::SoaTransform>& i_local_matrices,
ozz::math::Transform& o_transform) {
int matrix_index = i_bone_idx / 4;
short simd_component = i_bone_idx % 4;
o_transform.translation.x =
i_local_matrices[matrix_index].translation.x[simd_component];
o_transform.translation.y =
i_local_matrices[matrix_index].translation.y[simd_component];
o_transform.translation.z =
i_local_matrices[matrix_index].translation.z[simd_component];
}
void sample_bone_transform(
float ratio,
int i_bone_idx,
const ozz::animation::Animation* i_animation,
ozz::animation::SamplingCache& io_cache,
ozz::vector<ozz::math::SoaTransform>& io_local_matrices,
ozz::math::Transform& o_transform) {
ozz::animation::SamplingJob sampling_job;
sampling_job.animation = i_animation;
sampling_job.cache = &io_cache;
sampling_job.ratio = ratio;
sampling_job.output = make_span(io_local_matrices);
if (!sampling_job.Run()) {
ozz::log::Err() << "Error sampling animation." << std::endl;
}
get_bone_transform(i_bone_idx, io_local_matrices, o_transform);
}
TEST_CASE("Sample single bone channel", "[AnimSamplerNode]") {
SkinnedMesh skinned_mesh;
skinned_mesh.LoadSkeleton("../media/MixamoYBot-skeleton.ozz");
skinned_mesh.LoadAnimation("../media/Walking-loop.ozz");
ozz::animation::Animation* animation = skinned_mesh.m_animations[0];
const int num_soa_joints = skinned_mesh.m_skeleton.num_soa_joints();
const int num_joints = skinned_mesh.m_skeleton.num_joints();
ozz::vector<ozz::math::SoaTransform> local_matrices;
ozz::animation::SamplingCache sampling_cache;
local_matrices.resize(num_soa_joints);
sampling_cache.Resize(num_joints);
// sample at ratio 0.
float ratio = 0.f;
ozz::animation::SamplingJob sampling_job;
sampling_job.animation = animation;
sampling_job.cache = &sampling_cache;
sampling_job.ratio = ratio;
sampling_job.output = make_span(local_matrices);
if (!sampling_job.Run()) {
ozz::log::Err() << "Error sampling animation." << std::endl;
}
ozz::math::Transform root_transform_0;
sample_bone_transform(
0.f,
0,
animation,
sampling_cache,
local_matrices,
root_transform_0);
int n_samples = 53;
for (int i = 0; i <= n_samples; i++) {
float ratio = i * 1.f / n_samples;
ozz::math::Transform sampled_root_transform;
sample_bone_transform(
i * 1.f / n_samples,
0,
animation,
sampling_cache,
local_matrices,
sampled_root_transform);
ozz::math::Transform calculated_root_transform;
calc_bone_translation(ratio, 0, animation, calculated_root_transform);
// std::cout << "ratio: " << ratio << "\t" << "err: " << ozz::math::Length(sampled_root_transform.translation - calculated_root_transform.translation) << std::endl;
}
ozz::math::Transform root_transform_1;
calc_bone_translation(0.f, 0, animation, root_transform_0);
calc_bone_translation(1.f, 0, animation, root_transform_1);
}
TEST_CASE("Root Bone Sampling", "[AnimSamplerNode]") {
SkinnedMesh skinned_mesh;
skinned_mesh.LoadSkeleton("../media/MixamoYBot-skeleton.ozz");
int anim_idx = 0;
skinned_mesh.LoadAnimation("../media/Walking-loop.ozz");
float walking_markers[] = {0.293, 0.762};
skinned_mesh.m_animation_sync_track[anim_idx] = SyncTrack::CreateFromMarkers(
skinned_mesh.m_animations[anim_idx]->duration(),
2,
walking_markers);
// AnimationController anim_controller(&skinned_mesh);
// AnimSamplerNode test_node(&anim_controller);
// test_node.SetAnimation(skinned_mesh.m_animations[0], SyncTrack());
//
// test_node.Reset();
// test_node.UpdateTime(0.2f);
//
// ozz::math::Transform root_transform;
// test_node.Evaluate(&skinned_mesh.m_local_matrices, &root_transform);
}