Working on unified BlendTree and StateMachine handling.
parent
c7d2d195a3
commit
ccb9bc4e9b
|
@ -46,14 +46,22 @@ add_library(AnimTestbedCode OBJECT
|
|||
src/SyncTrack.cc
|
||||
src/SyncTrack.h
|
||||
src/ozzutils.cc
|
||||
src/AnimGraph/AnimGraphResource.cc
|
||||
src/AnimGraph/AnimGraphResource.h
|
||||
# src/AnimGraph/AnimGraphBlendTreeResource.cc
|
||||
# src/AnimGraph/AnimGraphBlendTreeResource.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)
|
||||
src/AnimGraph/AnimGraphData.h
|
||||
src/AnimGraph/AnimGraphBlendTree.cc
|
||||
src/AnimGraph/AnimGraphBlendTree.h
|
||||
src/AnimGraph/AnimGraphStateMachine.cc
|
||||
src/AnimGraph/AnimGraphStateMachine.h
|
||||
src/AnimGraph/AnimNode.cc
|
||||
src/AnimGraph/AnimNode.h
|
||||
src/AnimGraph/AnimGraphResource.cc
|
||||
src/AnimGraph/AnimGraphResource.h)
|
||||
|
||||
target_include_directories(
|
||||
AnimTestbedCode
|
||||
|
@ -120,7 +128,7 @@ set(ozz_offline_test_objs
|
|||
|
||||
target_sources(runtests PRIVATE
|
||||
tests/AnimGraphResourceTests.cc
|
||||
tests/AnimGraphEvalTests.cc
|
||||
# tests/AnimGraphEvalTests.cc
|
||||
tests/NodeDescriptorTests.cc
|
||||
tests/SyncTrackTests.cc
|
||||
tests/main.cc
|
||||
|
|
|
@ -1,202 +1,3 @@
|
|||
//
|
||||
// Created by martin on 25.03.22.
|
||||
//
|
||||
|
||||
#include "AnimGraph.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
bool AnimGraph::init(AnimGraphContext& context) {
|
||||
context.m_graph = this;
|
||||
|
||||
for (size_t i = 2; i < m_nodes.size(); i++) {
|
||||
if (!m_nodes[i]->Init(context)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < m_animdata_blocks.size(); i++) {
|
||||
int num_soa_joints = context.m_skeleton->num_soa_joints();
|
||||
m_animdata_blocks[i]->m_local_matrices.resize(num_soa_joints);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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.at(i).m_source_node);
|
||||
|
||||
if (input_node_index == 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
updateOrderedNodesRecursive(input_node_index);
|
||||
}
|
||||
|
||||
if (node_index != 0) {
|
||||
// In case we have multiple output connections from the node we here
|
||||
// ensure that use the node evaluation that is the furthest away from
|
||||
// the output.
|
||||
std::vector<AnimNode*>::iterator find_iter = std::find(
|
||||
m_eval_ordered_nodes.begin(),
|
||||
m_eval_ordered_nodes.end(),
|
||||
node);
|
||||
if (find_iter != m_eval_ordered_nodes.end()) {
|
||||
m_eval_ordered_nodes.erase(find_iter);
|
||||
}
|
||||
|
||||
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 = m_eval_ordered_nodes.size() - 1; i > 0; 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::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;
|
||||
float node_time_now = node->m_time_now;
|
||||
float node_time_last = node->m_time_last;
|
||||
|
||||
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++) {
|
||||
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(AnimGraphContext& context) {
|
||||
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;
|
||||
}
|
||||
|
||||
node->Evaluate(context);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
|
@ -5,227 +5,34 @@
|
|||
#ifndef ANIMTESTBED_ANIMGRAPH_H
|
||||
#define ANIMTESTBED_ANIMGRAPH_H
|
||||
|
||||
#include "AnimNode.h"
|
||||
#include "AnimGraphData.h"
|
||||
#include "AnimGraphNodes.h"
|
||||
|
||||
//
|
||||
// AnimGraph (Runtime)
|
||||
//
|
||||
struct AnimGraph {
|
||||
AnimData m_local_transforms;
|
||||
~AnimGraph() {}
|
||||
|
||||
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;
|
||||
std::vector<AnimData*> m_animdata_blocks;
|
||||
NodeDescriptorBase* m_node_descriptor = nullptr;
|
||||
char* m_input_buffer = nullptr;
|
||||
char* m_output_buffer = nullptr;
|
||||
char* m_connection_data_storage = nullptr;
|
||||
char* m_const_node_inputs = nullptr;
|
||||
|
||||
std::vector<Socket>& getGraphOutputs() { return m_node_descriptor->m_inputs; }
|
||||
std::vector<Socket>& getGraphInputs() { return m_node_descriptor->m_outputs; }
|
||||
|
||||
AnimDataAllocator m_anim_data_allocator;
|
||||
|
||||
~AnimGraph() { dealloc(); }
|
||||
|
||||
bool init(AnimGraphContext& context);
|
||||
void dealloc() {
|
||||
for (size_t i = 0; i < m_animdata_blocks.size(); i++) {
|
||||
m_animdata_blocks[i]->m_local_matrices.vector::~vector();
|
||||
bool Init(AnimGraphContext& context) {
|
||||
m_root_node->Init(context);
|
||||
}
|
||||
m_animdata_blocks.clear();
|
||||
|
||||
m_node_input_connections.clear();
|
||||
m_node_output_connections.clear();
|
||||
|
||||
delete[] m_input_buffer;
|
||||
delete[] m_output_buffer;
|
||||
delete[] m_connection_data_storage;
|
||||
delete[] m_const_node_inputs;
|
||||
|
||||
for (int i = 0; i < m_nodes.size(); i++) {
|
||||
delete m_nodes[i];
|
||||
void UpdateTime(float dt) {
|
||||
m_time_last = m_time_now;
|
||||
m_time_now = m_time_now + dt;
|
||||
m_root_node->UpdateTime(m_time_last, m_time_now);
|
||||
}
|
||||
m_nodes.clear();
|
||||
void Evaluate(AnimGraphContext& context) {
|
||||
|
||||
delete m_node_descriptor;
|
||||
}
|
||||
|
||||
void updateOrderedNodes();
|
||||
void updateOrderedNodesRecursive(int node_index);
|
||||
void markActiveNodes();
|
||||
bool checkIsNodeActive(AnimNode* node) {
|
||||
return node->m_state != AnimNodeEvalState::Deactivated;
|
||||
}
|
||||
AnimNode* m_root_node = nullptr;
|
||||
|
||||
void evalSyncTracks();
|
||||
void updateTime(float dt);
|
||||
void evaluate(AnimGraphContext& context);
|
||||
void resetNodeStates() {
|
||||
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;
|
||||
}
|
||||
}
|
||||
float m_time_now = 0.f;
|
||||
float m_time_last = 0.f;
|
||||
|
||||
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;
|
||||
|
||||
/** Sets the address that is used for the specified AnimGraph input Socket.
|
||||
*
|
||||
* @tparam T Type of the Socket.
|
||||
* @param name Name of the Socket.
|
||||
* @param value_ptr Pointer where the input is fetched during evaluation.
|
||||
*/
|
||||
template <typename T>
|
||||
void SetInput(const char* name, T* value_ptr) {
|
||||
m_node_descriptor->SetOutput(name, value_ptr);
|
||||
|
||||
for (int i = 0; i < m_node_output_connections[1].size(); i++) {
|
||||
const AnimGraphConnection& graph_input_connection =
|
||||
m_node_output_connections[1][i];
|
||||
|
||||
if (graph_input_connection.m_source_socket.m_name == name) {
|
||||
*graph_input_connection.m_target_socket.m_reference.ptr_ptr = value_ptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Sets the address that is used for the specified AnimGraph output Socket.
|
||||
*
|
||||
* @tparam T Type of the Socket.
|
||||
* @param name Name of the Socket.
|
||||
* @param value_ptr Pointer where the graph output output is written to at the end of evaluation.
|
||||
*/
|
||||
template <typename T>
|
||||
void SetOutput(const char* name, T* value_ptr) {
|
||||
m_node_descriptor->SetInput(name, value_ptr);
|
||||
|
||||
for (int i = 0; i < m_node_input_connections[0].size(); i++) {
|
||||
const AnimGraphConnection& graph_output_connection =
|
||||
m_node_input_connections[0][i];
|
||||
|
||||
if (graph_output_connection.m_target_socket.m_name == name) {
|
||||
if (graph_output_connection.m_source_node == m_nodes[1]
|
||||
&& graph_output_connection.m_target_node == m_nodes[0]) {
|
||||
std::cerr << "Error: cannot set output for direct graph input to graph "
|
||||
"output connections. Use GetOutptPtr for output instead!"
|
||||
<< std::endl;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
*graph_output_connection.m_source_socket.m_reference.ptr_ptr =
|
||||
value_ptr;
|
||||
|
||||
// Make sure all other output connections of this pin use the same output pointer
|
||||
int source_node_index = getAnimNodeIndex(graph_output_connection.m_source_node);
|
||||
for (int j = 0; j < m_node_output_connections[source_node_index].size(); j++) {
|
||||
const AnimGraphConnection& source_output_connection = m_node_output_connections[source_node_index][j];
|
||||
if (source_output_connection.m_target_node == m_nodes[0]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (source_output_connection.m_source_socket.m_name == graph_output_connection.m_source_socket.m_name) {
|
||||
*source_output_connection.m_target_socket.m_reference.ptr_ptr = value_ptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the address that is used for the specified AnimGraph output Socket.
|
||||
*
|
||||
* This function is needed for connections that directly connect an AnimGraph
|
||||
* input Socket to an output Socket of the same AnimGraph.
|
||||
*
|
||||
* @tparam T Type of the Socket.
|
||||
* @param name Name of the Socket.
|
||||
* @return Address that is used for the specified AnimGraph output Socket.
|
||||
*/
|
||||
template <typename T>
|
||||
T* GetOutputPtr(const char* name) {
|
||||
for (int i = 0; i < m_node_input_connections[0].size(); i++) {
|
||||
const AnimGraphConnection& graph_output_connection =
|
||||
m_node_input_connections[0][i];
|
||||
if (graph_output_connection.m_target_socket.m_name == name) {
|
||||
return static_cast<float*>(*graph_output_connection.m_source_socket.m_reference.ptr_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void* getInputPtr(const std::string& name) const {
|
||||
const Socket* input_socket = getInputSocket(name);
|
||||
if (input_socket != nullptr) {
|
||||
return input_socket->m_reference.ptr;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void* getOutputPtr(const std::string& name) const {
|
||||
const Socket* input_socket = getOutputSocket(name);
|
||||
if (input_socket != nullptr) {
|
||||
return input_socket->m_reference.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;
|
||||
}
|
||||
Vec3 m_root_bone_translation = {};
|
||||
Quat m_root_bone_rotation = {};
|
||||
};
|
||||
|
||||
#endif // ANIMTESTBED_ANIMGRAPH_H
|
||||
|
|
|
@ -0,0 +1,201 @@
|
|||
//
|
||||
// Created by martin on 17.03.24.
|
||||
//
|
||||
|
||||
#include "AnimGraphBlendTree.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
bool AnimGraphBlendTree::Init(AnimGraphContext& context) {
|
||||
for (size_t i = 2; i < m_nodes.size(); i++) {
|
||||
if (!m_nodes[i]->Init(context)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < m_animdata_blocks.size(); i++) {
|
||||
int num_soa_joints = context.m_skeleton->num_soa_joints();
|
||||
m_animdata_blocks[i]->m_local_matrices.resize(num_soa_joints);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AnimGraphBlendTree::UpdateOrderedNodes() {
|
||||
m_eval_ordered_nodes.clear();
|
||||
UpdateOrderedNodesRecursive(0);
|
||||
}
|
||||
|
||||
void AnimGraphBlendTree::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.at(i).m_source_node);
|
||||
|
||||
if (input_node_index == 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
UpdateOrderedNodesRecursive(input_node_index);
|
||||
}
|
||||
|
||||
if (node_index != 0) {
|
||||
// In case we have multiple output connections from the node we here
|
||||
// ensure that use the node evaluation that is the furthest away from
|
||||
// the output.
|
||||
std::vector<AnimNode*>::iterator find_iter = std::find(
|
||||
m_eval_ordered_nodes.begin(),
|
||||
m_eval_ordered_nodes.end(),
|
||||
node);
|
||||
if (find_iter != m_eval_ordered_nodes.end()) {
|
||||
m_eval_ordered_nodes.erase(find_iter);
|
||||
}
|
||||
|
||||
m_eval_ordered_nodes.push_back(node);
|
||||
}
|
||||
}
|
||||
|
||||
void AnimGraphBlendTree::MarkActiveInputs() {
|
||||
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 = m_eval_ordered_nodes.size() - 1; i > 0; i--) {
|
||||
AnimNode* node = m_eval_ordered_nodes[i];
|
||||
if (checkIsNodeActive(node)) {
|
||||
node->MarkActiveInputs();
|
||||
size_t node_index = GetAnimNodeIndex(node);
|
||||
|
||||
// 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 AnimGraphBlendTree::CalcSyncTrack() {
|
||||
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->CalcSyncTrack();
|
||||
}
|
||||
}
|
||||
|
||||
void AnimGraphBlendTree::UpdateTime(float time_last, float time_now) {
|
||||
float dt = time_now - time_last;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
size_t node_index = GetAnimNodeIndex(node);
|
||||
float node_time_now = node->m_time_now;
|
||||
float node_time_last = node->m_time_last;
|
||||
|
||||
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++) {
|
||||
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 AnimGraphBlendTree::Evaluate(AnimGraphContext& context) {
|
||||
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;
|
||||
}
|
||||
|
||||
node->Evaluate(context);
|
||||
}
|
||||
}
|
||||
|
||||
Socket* AnimGraphBlendTree::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* AnimGraphBlendTree::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* AnimGraphBlendTree::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* AnimGraphBlendTree::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;
|
||||
}
|
|
@ -0,0 +1,234 @@
|
|||
//
|
||||
// Created by martin on 17.03.24.
|
||||
//
|
||||
|
||||
#ifndef ANIMTESTBED_ANIMGRAPHBLENDTREE_H
|
||||
#define ANIMTESTBED_ANIMGRAPHBLENDTREE_H
|
||||
|
||||
#include "AnimNode.h"
|
||||
|
||||
//
|
||||
// AnimGraph (Runtime)
|
||||
//
|
||||
struct AnimGraphBlendTree : public AnimNode {
|
||||
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;
|
||||
std::vector<AnimData*> m_animdata_blocks;
|
||||
NodeDescriptorBase* m_node_descriptor = nullptr;
|
||||
char* m_input_buffer = nullptr;
|
||||
char* m_output_buffer = nullptr;
|
||||
char* m_connection_data_storage = nullptr;
|
||||
char* m_const_node_inputs = nullptr;
|
||||
|
||||
std::vector<Socket>& getGraphOutputs() { return m_node_descriptor->m_inputs; }
|
||||
std::vector<Socket>& getGraphInputs() { return m_node_descriptor->m_outputs; }
|
||||
|
||||
AnimDataAllocator m_anim_data_allocator;
|
||||
|
||||
~AnimGraphBlendTree() { dealloc(); }
|
||||
|
||||
// AnimNode overrides
|
||||
bool Init(AnimGraphContext& context);
|
||||
void MarkActiveInputs() override;
|
||||
void CalcSyncTrack() override;
|
||||
void UpdateTime(float time_last, float time_now) override;
|
||||
void Evaluate(AnimGraphContext& context) override;
|
||||
|
||||
void dealloc() {
|
||||
for (size_t i = 0; i < m_animdata_blocks.size(); i++) {
|
||||
m_animdata_blocks[i]->m_local_matrices.vector::~vector();
|
||||
}
|
||||
m_animdata_blocks.clear();
|
||||
|
||||
m_node_input_connections.clear();
|
||||
m_node_output_connections.clear();
|
||||
|
||||
delete[] m_input_buffer;
|
||||
delete[] m_output_buffer;
|
||||
delete[] m_connection_data_storage;
|
||||
delete[] m_const_node_inputs;
|
||||
|
||||
for (int i = 0; i < m_nodes.size(); i++) {
|
||||
delete m_nodes[i];
|
||||
}
|
||||
m_nodes.clear();
|
||||
|
||||
delete m_node_descriptor;
|
||||
}
|
||||
|
||||
void UpdateOrderedNodes();
|
||||
void UpdateOrderedNodesRecursive(int node_index);
|
||||
bool checkIsNodeActive(AnimNode* node) {
|
||||
return node->m_state != AnimNodeEvalState::Deactivated;
|
||||
}
|
||||
|
||||
void ResetNodeStates() {
|
||||
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;
|
||||
|
||||
/** Sets the address that is used for the specified AnimGraph input Socket.
|
||||
*
|
||||
* @tparam T Type of the Socket.
|
||||
* @param name Name of the Socket.
|
||||
* @param value_ptr Pointer where the input is fetched during evaluation.
|
||||
*/
|
||||
template <typename T>
|
||||
void SetInput(const char* name, T* value_ptr) {
|
||||
m_node_descriptor->SetOutput(name, value_ptr);
|
||||
|
||||
for (int i = 0; i < m_node_output_connections[1].size(); i++) {
|
||||
const AnimGraphConnection& graph_input_connection =
|
||||
m_node_output_connections[1][i];
|
||||
|
||||
if (graph_input_connection.m_source_socket.m_name == name) {
|
||||
*graph_input_connection.m_target_socket.m_reference.ptr_ptr = value_ptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Sets the address that is used for the specified AnimGraph output Socket.
|
||||
*
|
||||
* @tparam T Type of the Socket.
|
||||
* @param name Name of the Socket.
|
||||
* @param value_ptr Pointer where the graph output output is written to at the end of evaluation.
|
||||
*/
|
||||
template <typename T>
|
||||
void SetOutput(const char* name, T* value_ptr) {
|
||||
m_node_descriptor->SetInput(name, value_ptr);
|
||||
|
||||
for (int i = 0; i < m_node_input_connections[0].size(); i++) {
|
||||
const AnimGraphConnection& graph_output_connection =
|
||||
m_node_input_connections[0][i];
|
||||
|
||||
if (graph_output_connection.m_target_socket.m_name == name) {
|
||||
if (graph_output_connection.m_source_node == m_nodes[1]
|
||||
&& graph_output_connection.m_target_node == m_nodes[0]) {
|
||||
std::cerr << "Error: cannot set output for direct graph input to graph "
|
||||
"output connections. Use GetOutptPtr for output instead!"
|
||||
<< std::endl;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
*graph_output_connection.m_source_socket.m_reference.ptr_ptr =
|
||||
value_ptr;
|
||||
|
||||
// Make sure all other output connections of this pin use the same output pointer
|
||||
int source_node_index =
|
||||
GetAnimNodeIndex(graph_output_connection.m_source_node);
|
||||
for (int j = 0; j < m_node_output_connections[source_node_index].size(); j++) {
|
||||
const AnimGraphConnection& source_output_connection = m_node_output_connections[source_node_index][j];
|
||||
if (source_output_connection.m_target_node == m_nodes[0]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (source_output_connection.m_source_socket.m_name == graph_output_connection.m_source_socket.m_name) {
|
||||
*source_output_connection.m_target_socket.m_reference.ptr_ptr = value_ptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the address that is used for the specified AnimGraph output Socket.
|
||||
*
|
||||
* This function is needed for connections that directly connect an AnimGraph
|
||||
* input Socket to an output Socket of the same AnimGraph.
|
||||
*
|
||||
* @tparam T Type of the Socket.
|
||||
* @param name Name of the Socket.
|
||||
* @return Address that is used for the specified AnimGraph output Socket.
|
||||
*/
|
||||
template <typename T>
|
||||
T* GetOutputPtr(const char* name) {
|
||||
for (int i = 0; i < m_node_input_connections[0].size(); i++) {
|
||||
const AnimGraphConnection& graph_output_connection =
|
||||
m_node_input_connections[0][i];
|
||||
if (graph_output_connection.m_target_socket.m_name == name) {
|
||||
return static_cast<float*>(*graph_output_connection.m_source_socket.m_reference.ptr_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void* getInputPtr(const std::string& name) const {
|
||||
const Socket* input_socket = getInputSocket(name);
|
||||
if (input_socket != nullptr) {
|
||||
return input_socket->m_reference.ptr;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void* getOutputPtr(const std::string& name) const {
|
||||
const Socket* input_socket = getOutputSocket(name);
|
||||
if (input_socket != nullptr) {
|
||||
return input_socket->m_reference.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_ANIMGRAPHBLENDTREE_H
|
|
@ -0,0 +1,616 @@
|
|||
//
|
||||
// Created by martin on 04.02.22.
|
||||
//
|
||||
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
|
||||
#include "3rdparty/json/json.hpp"
|
||||
|
||||
#include "AnimGraphBlendTreeResource.h"
|
||||
#include "AnimGraphNodes.h"
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
//
|
||||
// Socket <-> json
|
||||
//
|
||||
std::string sSocketTypeToStr(SocketType pin_type) {
|
||||
if (pin_type < SocketType::SocketTypeUndefined
|
||||
|| pin_type >= SocketType::SocketTypeLast) {
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
return SocketTypeNames[static_cast<int>(pin_type)];
|
||||
}
|
||||
|
||||
json sSocketToJson(const Socket& socket) {
|
||||
json result;
|
||||
result["name"] = socket.m_name;
|
||||
result["type"] = sSocketTypeToStr(socket.m_type);
|
||||
|
||||
if (socket.m_type == SocketType::SocketTypeString
|
||||
&& !socket.m_value_string.empty()) {
|
||||
result["value"] = socket.m_value_string;
|
||||
} else if (socket.m_value.flag) {
|
||||
if (socket.m_type == SocketType::SocketTypeBool) {
|
||||
result["value"] = socket.m_value.flag;
|
||||
} else if (socket.m_type == SocketType::SocketTypeAnimation) {
|
||||
} else if (socket.m_type == SocketType::SocketTypeInt) {
|
||||
result["value"] = socket.m_value.int_value;
|
||||
} else if (socket.m_type == SocketType::SocketTypeFloat) {
|
||||
result["value"] = socket.m_value.float_value;
|
||||
} else if (socket.m_type == SocketType::SocketTypeVec3) {
|
||||
result["value"][0] = socket.m_value.vec3.v[0];
|
||||
result["value"][1] = socket.m_value.vec3.v[1];
|
||||
result["value"][2] = socket.m_value.vec3.v[2];
|
||||
} else if (socket.m_type == SocketType::SocketTypeQuat) {
|
||||
result["value"][0] = socket.m_value.quat.v[0];
|
||||
result["value"][1] = socket.m_value.quat.v[1];
|
||||
result["value"][2] = socket.m_value.quat.v[2];
|
||||
result["value"][3] = socket.m_value.quat.v[3];
|
||||
} else {
|
||||
std::cerr << "Invalid socket type '" << static_cast<int>(socket.m_type)
|
||||
<< "'." << std::endl;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Socket sJsonToSocket(const json& json_data) {
|
||||
Socket result;
|
||||
result.m_type = SocketType::SocketTypeUndefined;
|
||||
result.m_reference.ptr = &result.m_value.int_value;
|
||||
result.m_name = json_data["name"];
|
||||
|
||||
std::string type_string = json_data["type"];
|
||||
bool have_value = json_data.contains("value");
|
||||
|
||||
if (type_string == "Bool") {
|
||||
result.m_type = SocketType::SocketTypeBool;
|
||||
result.m_type_size = sizeof(bool);
|
||||
result.m_reference.ptr = &result.m_value.int_value;
|
||||
|
||||
if (have_value) {
|
||||
result.m_value.flag = json_data["value"];
|
||||
}
|
||||
} else if (type_string == "Animation") {
|
||||
result.m_type = SocketType::SocketTypeAnimation;
|
||||
result.m_type_size = sizeof(AnimData);
|
||||
} else if (type_string == "Int") {
|
||||
result.m_type = SocketType::SocketTypeInt;
|
||||
result.m_type_size = sizeof(int);
|
||||
if (have_value) {
|
||||
result.m_value.int_value = json_data["value"];
|
||||
}
|
||||
} else if (type_string == "Float") {
|
||||
result.m_type = SocketType::SocketTypeFloat;
|
||||
result.m_type_size = sizeof(float);
|
||||
if (have_value) {
|
||||
result.m_value.float_value = json_data["value"];
|
||||
}
|
||||
} else if (type_string == "Vec3") {
|
||||
result.m_type = SocketType::SocketTypeVec3;
|
||||
result.m_type_size = sizeof(Vec3);
|
||||
if (have_value) {
|
||||
result.m_value.vec3.x = json_data["value"][0];
|
||||
result.m_value.vec3.y = json_data["value"][1];
|
||||
result.m_value.vec3.z = json_data["value"][2];
|
||||
}
|
||||
} else if (type_string == "Quat") {
|
||||
result.m_type = SocketType::SocketTypeQuat;
|
||||
result.m_type_size = sizeof(Quat);
|
||||
if (have_value) {
|
||||
result.m_value.quat.x = json_data["value"][0];
|
||||
result.m_value.quat.y = json_data["value"][1];
|
||||
result.m_value.quat.z = json_data["value"][2];
|
||||
result.m_value.quat.w = json_data["value"][3];
|
||||
}
|
||||
} else if (type_string == "String") {
|
||||
result.m_type = SocketType::SocketTypeString;
|
||||
result.m_type_size = sizeof(std::string);
|
||||
if (have_value) {
|
||||
result.m_value_string = json_data["value"];
|
||||
}
|
||||
} else {
|
||||
std::cerr << "Invalid socket type '" << type_string << "'." << std::endl;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//
|
||||
// AnimGraphNode <-> json
|
||||
//
|
||||
json sAnimGraphNodeToJson(
|
||||
const AnimNodeResource& node,
|
||||
size_t node_index,
|
||||
const std::vector<AnimGraphConnectionResource>& connections) {
|
||||
json result;
|
||||
|
||||
result["name"] = node.m_name;
|
||||
result["type"] = "AnimNodeResource";
|
||||
result["node_type"] = node.m_type_name;
|
||||
|
||||
for (size_t j = 0; j < 2; j++) {
|
||||
result["position"][j] = node.m_position[j];
|
||||
}
|
||||
|
||||
for (const auto & socket : node.m_socket_accessor->m_inputs) {
|
||||
if (socket.m_type == SocketType::SocketTypeAnimation) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool socket_connected = false;
|
||||
for (const auto & connection : connections) {
|
||||
if (connection.source_node_index == node_index
|
||||
&& connection.source_socket_name == socket.m_name) {
|
||||
socket_connected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!socket_connected) {
|
||||
result["inputs"].push_back(sSocketToJson(socket));
|
||||
}
|
||||
}
|
||||
|
||||
for (auto & property : node.m_socket_accessor->m_properties) {
|
||||
result["properties"][property.m_name] = sSocketToJson(property);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
AnimNodeResource sAnimGraphNodeFromJson(const json& json_node, size_t node_index) {
|
||||
AnimNodeResource result;
|
||||
|
||||
result.m_name = json_node["name"];
|
||||
result.m_type_name = json_node["node_type"];
|
||||
result.m_position[0] = json_node["position"][0];
|
||||
result.m_position[1] = json_node["position"][1];
|
||||
|
||||
result.m_anim_node = AnimNodeFactory(result.m_type_name);
|
||||
result.m_socket_accessor =
|
||||
AnimNodeDescriptorFactory(result.m_type_name, result.m_anim_node);
|
||||
|
||||
for (auto & property : result.m_socket_accessor->m_properties) {
|
||||
property = sJsonToSocket(json_node["properties"][property.m_name]);
|
||||
}
|
||||
|
||||
if (node_index != 0 && node_index != 1 && json_node.contains("inputs")) {
|
||||
for (size_t j = 0, n = json_node["inputs"].size(); j < n; j++) {
|
||||
assert(json_node["inputs"][j].contains("name"));
|
||||
std::string input_name = json_node["inputs"][j]["name"];
|
||||
Socket* input_socket =
|
||||
result.m_socket_accessor->GetInputSocket(input_name.c_str());
|
||||
if (input_socket == nullptr) {
|
||||
std::cerr << "Could not find input socket with name " << input_name
|
||||
<< " for node type " << result.m_type_name << std::endl;
|
||||
abort();
|
||||
}
|
||||
*input_socket = sJsonToSocket(json_node["inputs"][j]);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//
|
||||
// AnimGraphConnectionResource <-> Json
|
||||
//
|
||||
json sAnimGraphConnectionToJson(
|
||||
const AnimGraphConnectionResource& connection) {
|
||||
json result;
|
||||
|
||||
result["type"] = "AnimGraphConnectionResource";
|
||||
|
||||
result["source_node_index"] = connection.source_node_index;
|
||||
result["source_socket_name"] = connection.source_socket_name;
|
||||
|
||||
result["target_node_index"] = connection.target_node_index;
|
||||
result["target_socket_name"] = connection.target_socket_name;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
AnimGraphConnectionResource sAnimGraphConnectionFromJson(
|
||||
const json& json_node) {
|
||||
AnimGraphConnectionResource connection;
|
||||
|
||||
connection.source_node_index = json_node["source_node_index"];
|
||||
connection.source_socket_name = json_node["source_socket_name"];
|
||||
|
||||
connection.target_node_index = json_node["target_node_index"];
|
||||
connection.target_socket_name = json_node["target_socket_name"];
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
void AnimGraphBlendTreeResource::clear() {
|
||||
m_name = "";
|
||||
|
||||
clearNodes();
|
||||
m_connections.clear();
|
||||
|
||||
initGraphConnectors();
|
||||
}
|
||||
|
||||
void AnimGraphBlendTreeResource::clearNodes() {
|
||||
for (auto & m_node : m_nodes) {
|
||||
delete m_node.m_socket_accessor;
|
||||
m_node.m_socket_accessor = nullptr;
|
||||
delete m_node.m_anim_node;
|
||||
m_node.m_anim_node = nullptr;
|
||||
}
|
||||
m_nodes.clear();
|
||||
}
|
||||
|
||||
void AnimGraphBlendTreeResource::initGraphConnectors() {
|
||||
m_nodes.push_back(AnimNodeResourceFactory("BlendTree"));
|
||||
m_nodes[0].m_name = "Outputs";
|
||||
m_nodes.push_back(AnimNodeResourceFactory("BlendTree"));
|
||||
m_nodes[1].m_name = "Inputs";
|
||||
}
|
||||
|
||||
bool AnimGraphBlendTreeResource::saveToFile(const char* filename) const {
|
||||
json result;
|
||||
|
||||
result["name"] = m_name;
|
||||
result["type"] = "AnimGraphResource";
|
||||
|
||||
for (size_t i = 0; i < m_nodes.size(); i++) {
|
||||
const AnimNodeResource& node = m_nodes[i];
|
||||
result["nodes"][i] = sAnimGraphNodeToJson(node, i, m_connections);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < m_connections.size(); i++) {
|
||||
const AnimGraphConnectionResource& connection = m_connections[i];
|
||||
result["connections"][i] = sAnimGraphConnectionToJson(connection);
|
||||
}
|
||||
|
||||
// Graph inputs and outputs
|
||||
{
|
||||
const AnimNodeResource& graph_output_node = m_nodes[0];
|
||||
const std::vector<Socket> graph_inputs =
|
||||
graph_output_node.m_socket_accessor->m_inputs;
|
||||
for (size_t i = 0; i < graph_inputs.size(); i++) {
|
||||
result["nodes"][0]["inputs"][i] = sSocketToJson(graph_inputs[i]);
|
||||
}
|
||||
|
||||
const AnimNodeResource& graph_input_node = m_nodes[1];
|
||||
const std::vector<Socket> graph_outputs =
|
||||
graph_input_node.m_socket_accessor->m_outputs;
|
||||
for (size_t i = 0; i < graph_outputs.size(); i++) {
|
||||
result["nodes"][1]["outputs"][i] = sSocketToJson(graph_outputs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
std::ofstream output_file;
|
||||
output_file.open(filename);
|
||||
output_file << result.dump(4, ' ') << std::endl;
|
||||
output_file.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AnimGraphBlendTreeResource::loadFromFile(const char* filename) {
|
||||
std::ifstream input_file;
|
||||
input_file.open(filename);
|
||||
std::stringstream buffer;
|
||||
buffer << input_file.rdbuf();
|
||||
|
||||
json json_data = json::parse(buffer.str(), nullptr, false);
|
||||
if (json_data.is_discarded()) {
|
||||
std::cerr << "Error parsing json of file '" << filename << "'."
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
if (json_data["type"] != "AnimGraphResource") {
|
||||
std::cerr
|
||||
<< "Invalid json object. Expected type 'AnimGraphResource' but got '"
|
||||
<< json_data["type"] << "'." << std::endl;
|
||||
}
|
||||
|
||||
clear();
|
||||
clearNodes();
|
||||
|
||||
m_name = json_data["name"];
|
||||
|
||||
// Load nodes
|
||||
for (size_t i = 0, n = json_data["nodes"].size(); i < n; i++) {
|
||||
const json& json_node = json_data["nodes"][i];
|
||||
if (json_node["type"] != "AnimNodeResource") {
|
||||
std::cerr
|
||||
<< "Invalid json object. Expected type 'AnimNodeResource' but got '"
|
||||
<< json_node["type"] << "'." << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
AnimNodeResource node = sAnimGraphNodeFromJson(json_node, i);
|
||||
m_nodes.push_back(node);
|
||||
}
|
||||
|
||||
// Setup graph inputs and outputs
|
||||
const json& graph_outputs = json_data["nodes"][0]["inputs"];
|
||||
for (const auto & graph_output : graph_outputs) {
|
||||
AnimNodeResource& graph_node = m_nodes[0];
|
||||
graph_node.m_socket_accessor->m_inputs.push_back(
|
||||
sJsonToSocket(graph_output));
|
||||
}
|
||||
|
||||
const json& graph_inputs = json_data["nodes"][1]["outputs"];
|
||||
for (const auto & graph_input : graph_inputs) {
|
||||
AnimNodeResource& graph_node = m_nodes[1];
|
||||
graph_node.m_socket_accessor->m_outputs.push_back(
|
||||
sJsonToSocket(graph_input));
|
||||
}
|
||||
|
||||
// Load connections
|
||||
for (const auto & json_connection : json_data["connections"]) {
|
||||
if (json_connection["type"] != "AnimGraphConnectionResource") {
|
||||
std::cerr
|
||||
<< "Invalid json object. Expected type 'AnimGraphConnectionResource' "
|
||||
"but got '"
|
||||
<< json_connection["type"] << "'." << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
AnimGraphConnectionResource connection =
|
||||
sAnimGraphConnectionFromJson(json_connection);
|
||||
m_connections.push_back(connection);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AnimGraphBlendTreeResource::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);
|
||||
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.emplace_back();
|
||||
instance.m_node_output_connections.emplace_back();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void AnimGraphBlendTreeResource::prepareGraphIOData(AnimGraph& instance) const {
|
||||
instance.m_node_descriptor =
|
||||
AnimNodeDescriptorFactory("BlendTree", instance.m_nodes[0]);
|
||||
instance.m_node_descriptor->m_outputs =
|
||||
m_nodes[1].m_socket_accessor->m_outputs;
|
||||
instance.m_node_descriptor->m_inputs = m_nodes[0].m_socket_accessor->m_inputs;
|
||||
|
||||
//
|
||||
// graph 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*);
|
||||
}
|
||||
|
||||
if (input_block_size > 0) {
|
||||
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_reference.ptr =
|
||||
(void*)&instance.m_input_buffer[input_block_offset];
|
||||
instance.m_node_descriptor->m_outputs[i].m_reference.ptr =
|
||||
&instance.m_input_buffer[input_block_offset];
|
||||
input_block_offset += sizeof(void*);
|
||||
}
|
||||
|
||||
//
|
||||
// graph 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 += sizeof(void*);
|
||||
}
|
||||
|
||||
if (output_block_size > 0) {
|
||||
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++) {
|
||||
instance.m_node_descriptor->m_inputs[i].m_reference.ptr =
|
||||
&instance.m_output_buffer[output_block_offset];
|
||||
output_block_offset += sizeof(void*);
|
||||
}
|
||||
|
||||
// connections: make source and target sockets point to the same address in the connection data storage.
|
||||
// TODO: instead of every connection, only create data blocks for the source sockets and make sure every source socket gets allocated once.
|
||||
size_t connection_data_storage_size = 0;
|
||||
for (const auto & connection : m_connections) {
|
||||
const AnimNodeResource& source_node = m_nodes[connection.source_node_index];
|
||||
Socket* source_socket = source_node.m_socket_accessor->GetOutputSocket(
|
||||
connection.source_socket_name.c_str());
|
||||
connection_data_storage_size += source_socket->m_type_size;
|
||||
}
|
||||
|
||||
if (connection_data_storage_size > 0) {
|
||||
instance.m_connection_data_storage = new char[connection_data_storage_size];
|
||||
memset(instance.m_connection_data_storage, 0, connection_data_storage_size);
|
||||
}
|
||||
|
||||
std::vector<NodeDescriptorBase*> instance_node_descriptors(
|
||||
m_nodes.size(),
|
||||
nullptr);
|
||||
for (int i = 0; i < m_nodes.size(); i++) {
|
||||
instance_node_descriptors[i] = AnimNodeDescriptorFactory(
|
||||
m_nodes[i].m_type_name,
|
||||
instance.m_nodes[i]);
|
||||
}
|
||||
|
||||
instance_node_descriptors[0]->m_inputs = instance.m_node_descriptor->m_inputs;
|
||||
instance_node_descriptors[1]->m_outputs =
|
||||
instance.m_node_descriptor->m_outputs;
|
||||
|
||||
size_t connection_data_offset = 0;
|
||||
for (const auto & connection : m_connections) {
|
||||
NodeDescriptorBase* source_node_descriptor =
|
||||
instance_node_descriptors[connection.source_node_index];
|
||||
NodeDescriptorBase* target_node_descriptor =
|
||||
instance_node_descriptors[connection.target_node_index];
|
||||
|
||||
AnimNode* source_node = instance.m_nodes[connection.source_node_index];
|
||||
AnimNode* target_node = instance.m_nodes[connection.target_node_index];
|
||||
|
||||
Socket* source_socket = source_node_descriptor->GetOutputSocket(
|
||||
connection.source_socket_name.c_str());
|
||||
Socket* target_socket = target_node_descriptor->GetInputSocket(
|
||||
connection.target_socket_name.c_str());
|
||||
|
||||
AnimGraphConnection instance_connection;
|
||||
instance_connection.m_source_node = source_node;
|
||||
instance_connection.m_source_socket = *source_socket;
|
||||
instance_connection.m_target_node = target_node;
|
||||
instance_connection.m_target_socket = *target_socket;
|
||||
instance.m_node_input_connections[connection.target_node_index].push_back(
|
||||
instance_connection);
|
||||
instance.m_node_output_connections[connection.source_node_index].push_back(
|
||||
instance_connection);
|
||||
|
||||
source_node_descriptor->SetOutputUnchecked(
|
||||
connection.source_socket_name.c_str(),
|
||||
&instance.m_connection_data_storage[connection_data_offset]);
|
||||
|
||||
target_node_descriptor->SetInputUnchecked(
|
||||
connection.target_socket_name.c_str(),
|
||||
&instance.m_connection_data_storage[connection_data_offset]);
|
||||
|
||||
if (source_socket->m_type == SocketType::SocketTypeAnimation) {
|
||||
instance.m_animdata_blocks.push_back(
|
||||
(AnimData*)(&instance
|
||||
.m_connection_data_storage[connection_data_offset]));
|
||||
}
|
||||
|
||||
connection_data_offset += source_socket->m_type_size;
|
||||
}
|
||||
|
||||
//
|
||||
// const node inputs
|
||||
//
|
||||
std::vector<Socket*> const_inputs =
|
||||
getConstNodeInputs( instance_node_descriptors);
|
||||
size_t const_node_inputs_buffer_size = 0;
|
||||
for (auto & const_input : const_inputs) {
|
||||
if (const_input->m_type == SocketType::SocketTypeString) {
|
||||
// TODO: implement string const node input support
|
||||
std::cerr << "Error: const inputs for strings not yet implemented!"
|
||||
<< std::endl;
|
||||
abort();
|
||||
}
|
||||
const_node_inputs_buffer_size += const_input->m_type_size;
|
||||
}
|
||||
|
||||
if (const_node_inputs_buffer_size > 0) {
|
||||
instance.m_const_node_inputs = new char[const_node_inputs_buffer_size];
|
||||
memset(instance.m_const_node_inputs, '\0', const_node_inputs_buffer_size);
|
||||
}
|
||||
|
||||
size_t const_input_buffer_offset = 0;
|
||||
for (auto & i : const_inputs) {
|
||||
Socket* const_input = i;
|
||||
|
||||
// TODO: implement string const node input support
|
||||
assert(const_input->m_type != SocketType::SocketTypeString);
|
||||
|
||||
*const_input->m_reference.ptr_ptr =
|
||||
&instance.m_const_node_inputs[const_input_buffer_offset];
|
||||
memcpy (*const_input->m_reference.ptr_ptr, &const_input->m_value, i->m_type_size);
|
||||
|
||||
const_input_buffer_offset += i->m_type_size;
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_nodes.size(); i++) {
|
||||
delete instance_node_descriptors[i];
|
||||
}
|
||||
}
|
||||
|
||||
void AnimGraphBlendTreeResource::setRuntimeNodeProperties(AnimGraph& instance) const {
|
||||
for (int i = 2; i < m_nodes.size(); i++) {
|
||||
const AnimNodeResource& node_resource = m_nodes[i];
|
||||
|
||||
NodeDescriptorBase* node_instance_accessor = AnimNodeDescriptorFactory(
|
||||
node_resource.m_type_name,
|
||||
instance.m_nodes[i]);
|
||||
|
||||
std::vector<Socket>& resource_properties =
|
||||
node_resource.m_socket_accessor->m_properties;
|
||||
for (const auto & property : resource_properties) {
|
||||
const std::string& name = property.m_name;
|
||||
|
||||
switch (property.m_type) {
|
||||
case SocketType::SocketTypeBool:
|
||||
node_instance_accessor->SetProperty(
|
||||
name.c_str(),
|
||||
property.m_value.flag);
|
||||
break;
|
||||
case SocketType::SocketTypeInt:
|
||||
node_instance_accessor->SetProperty(
|
||||
name.c_str(),
|
||||
property.m_value.int_value);
|
||||
break;
|
||||
case SocketType::SocketTypeFloat:
|
||||
node_instance_accessor->SetProperty(
|
||||
name.c_str(),
|
||||
property.m_value.float_value);
|
||||
break;
|
||||
case SocketType::SocketTypeVec3:
|
||||
node_instance_accessor->SetProperty<Vec3>(
|
||||
name.c_str(),
|
||||
property.m_value.vec3);
|
||||
break;
|
||||
case SocketType::SocketTypeQuat:
|
||||
node_instance_accessor->SetProperty(
|
||||
name.c_str(),
|
||||
property.m_value.quat);
|
||||
break;
|
||||
case SocketType::SocketTypeString:
|
||||
node_instance_accessor->SetProperty(
|
||||
name.c_str(),
|
||||
property.m_value_string);
|
||||
break;
|
||||
default:
|
||||
std::cerr << "Invalid socket type "
|
||||
<< static_cast<int>(property.m_type) << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
delete node_instance_accessor;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Socket*> AnimGraphBlendTreeResource::getConstNodeInputs(
|
||||
std::vector<NodeDescriptorBase*>& instance_node_descriptors) const {
|
||||
std::vector<Socket*> result;
|
||||
|
||||
for (size_t i = 0; i < m_nodes.size(); i++) {
|
||||
for (size_t j = 0, num_inputs = instance_node_descriptors[i]->m_inputs.size();
|
||||
j < num_inputs;
|
||||
j++) {
|
||||
Socket& input = instance_node_descriptors[i]->m_inputs[j];
|
||||
|
||||
if (*input.m_reference.ptr_ptr == nullptr) {
|
||||
memcpy(&input.m_value, &m_nodes[i].m_socket_accessor->m_inputs[j].m_value, sizeof(Socket::SocketValue));
|
||||
result.push_back(&input);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
//
|
||||
// Created by martin on 04.02.22.
|
||||
//
|
||||
|
||||
#ifndef ANIMTESTBED_ANIMGRAPHBLENDTREERESOURCE_H
|
||||
#define ANIMTESTBED_ANIMGRAPHBLENDTREERESOURCE_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 AnimNodeResource {
|
||||
std::string m_name;
|
||||
std::string m_type_name;
|
||||
AnimNode* m_anim_node = nullptr;
|
||||
NodeDescriptorBase* m_socket_accessor = nullptr;
|
||||
float m_position[2] = {0.f, 0.f};
|
||||
};
|
||||
|
||||
|
||||
|
||||
//
|
||||
// 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 AnimGraphBlendTreeResource {
|
||||
std::string m_name;
|
||||
std::vector<AnimNodeResource> m_nodes;
|
||||
std::vector<AnimGraphConnectionResource> m_connections;
|
||||
|
||||
~AnimGraphBlendTreeResource() {
|
||||
for (auto & m_node : m_nodes) {
|
||||
delete m_node.m_anim_node;
|
||||
delete m_node.m_socket_accessor;
|
||||
}
|
||||
}
|
||||
|
||||
AnimGraphBlendTreeResource() { 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 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(const 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->GetOutputSocket(source_socket_name.c_str());
|
||||
Socket* target_socket =
|
||||
target_node.m_socket_accessor->GetInputSocket(target_socket_name.c_str());
|
||||
|
||||
if (source_socket == nullptr || target_socket == nullptr) {
|
||||
std::cerr << "Cannot connect nodes: could not find sockets." << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
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) {
|
||||
size_t node_index = getNodeIndex(node);
|
||||
for (const auto & connection : m_connections) {
|
||||
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;
|
||||
}
|
||||
|
||||
void createInstance(AnimGraph& result) const;
|
||||
|
||||
void createRuntimeNodeInstances(AnimGraph& instance) const;
|
||||
void prepareGraphIOData(AnimGraph& instance) const;
|
||||
void setRuntimeNodeProperties(AnimGraph& instance) const;
|
||||
std::vector<Socket*> getConstNodeInputs(std::vector<NodeDescriptorBase*>& instance_node_descriptors) const;
|
||||
};
|
||||
|
||||
#endif //ANIMTESTBED_ANIMGRAPHBLENDTREERESOURCE_H
|
|
@ -22,8 +22,8 @@
|
|||
//
|
||||
// Data types
|
||||
//
|
||||
|
||||
struct AnimGraph;
|
||||
struct AnimNode;
|
||||
|
||||
struct AnimData {
|
||||
ozz::vector<ozz::math::SoaTransform> m_local_matrices;
|
||||
|
@ -260,6 +260,14 @@ SocketType GetSocketType() {
|
|||
return SocketType::SocketTypeUndefined;
|
||||
}
|
||||
|
||||
struct AnimGraphConnection {
|
||||
AnimNode* m_source_node = nullptr;
|
||||
Socket m_source_socket;
|
||||
AnimNode* m_target_node = nullptr;
|
||||
Socket m_target_socket;
|
||||
};
|
||||
|
||||
|
||||
struct NodeDescriptorBase {
|
||||
std::vector<Socket> m_inputs;
|
||||
std::vector<Socket> m_outputs;
|
||||
|
|
|
@ -7,13 +7,14 @@
|
|||
#include <sstream>
|
||||
|
||||
#include "3rdparty/imgui-node-editor/imgui_node_editor.h"
|
||||
#include "AnimGraphResource.h"
|
||||
#include "AnimGraphBlendTreeResource.h"
|
||||
#include "SkinnedMesh.h"
|
||||
#include "imgui.h"
|
||||
#include "imnodes.h"
|
||||
#include "misc/cpp/imgui_stdlib.h"
|
||||
|
||||
static AnimGraphResource sGraphGresource = AnimGraphResource();
|
||||
static AnimGraphBlendTreeResource sGraphGresource =
|
||||
AnimGraphBlendTreeResource();
|
||||
static bool sGraphLoadedThisFrame = false;
|
||||
|
||||
ImNodesPinShape sGetSocketShapeFromSocketType(const SocketType& socket_type) {
|
||||
|
@ -58,7 +59,7 @@ void NodeSocketEditor(Socket& socket) {
|
|||
}
|
||||
|
||||
void RemoveConnectionsForSocket(
|
||||
AnimGraphResource& graph_resource,
|
||||
AnimGraphBlendTreeResource& graph_resource,
|
||||
AnimNodeResource& node_resource,
|
||||
Socket& socket) {
|
||||
std::vector<AnimGraphConnectionResource>::iterator iter =
|
||||
|
@ -159,7 +160,7 @@ void SkinnedMeshWidget(SkinnedMesh* skinned_mesh) {
|
|||
}
|
||||
|
||||
void AnimGraphEditorRenderSidebar(
|
||||
AnimGraphResource& graph_resource,
|
||||
AnimGraphBlendTreeResource& graph_resource,
|
||||
AnimNodeResource& node_resource) {
|
||||
ImGui::Text("[%s]", node_resource.m_type_name.c_str());
|
||||
|
||||
|
|
|
@ -7,72 +7,13 @@
|
|||
|
||||
#include <vector>
|
||||
|
||||
#include "AnimNode.h"
|
||||
#include "AnimGraphData.h"
|
||||
#include "SyncTrack.h"
|
||||
#include "ozz/animation/runtime/sampling_job.h"
|
||||
|
||||
struct AnimNode;
|
||||
|
||||
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() = default;
|
||||
|
||||
virtual bool Init(AnimGraphContext& context) { return true; };
|
||||
|
||||
virtual void MarkActiveInputs(const std::vector<AnimGraphConnection>& inputs) {
|
||||
for (const auto & input : inputs) {
|
||||
AnimNode* input_node = input.m_source_node;
|
||||
if (input_node != nullptr) {
|
||||
input_node->m_state = AnimNodeEvalState::Activated;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual void CalcSyncTrack(const std::vector<AnimGraphConnection>& inputs) {
|
||||
for (const auto & input : inputs) {
|
||||
AnimNode* input_node = input.m_source_node;
|
||||
if (input_node != nullptr
|
||||
&& input.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(AnimGraphContext& context){};
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// BlendTreeNode
|
||||
//
|
||||
|
@ -93,8 +34,8 @@ struct Blend2Node : public AnimNode {
|
|||
float* i_blend_weight = nullptr;
|
||||
bool m_sync_blend = false;
|
||||
|
||||
virtual void MarkActiveInputs(const std::vector<AnimGraphConnection>& inputs) override {
|
||||
for (const auto & input : inputs) {
|
||||
virtual void MarkActiveInputs() override {
|
||||
for (const auto & input : m_inputs) {
|
||||
AnimNode* input_node = input.m_source_node;
|
||||
if (input_node == nullptr) {
|
||||
continue;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// Created by martin on 04.02.22.
|
||||
// Created by martin on 17.03.24.
|
||||
//
|
||||
|
||||
#include "AnimGraphResource.h"
|
||||
|
@ -9,6 +9,9 @@
|
|||
|
||||
#include "3rdparty/json/json.hpp"
|
||||
|
||||
#include "AnimGraphBlendTree.h"
|
||||
#include "AnimGraphNodes.h"
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
//
|
||||
|
@ -124,7 +127,7 @@ Socket sJsonToSocket(const json& json_data) {
|
|||
json sAnimGraphNodeToJson(
|
||||
const AnimNodeResource& node,
|
||||
size_t node_index,
|
||||
const std::vector<AnimGraphConnectionResource>& connections) {
|
||||
const std::vector<BlendTreeConnectionResource>& connections) {
|
||||
json result;
|
||||
|
||||
result["name"] = node.m_name;
|
||||
|
@ -161,7 +164,9 @@ json sAnimGraphNodeToJson(
|
|||
return result;
|
||||
}
|
||||
|
||||
AnimNodeResource sAnimGraphNodeFromJson(const json& json_node, size_t node_index) {
|
||||
AnimNodeResource sAnimGraphNodeFromJson(
|
||||
const json& json_node,
|
||||
size_t node_index) {
|
||||
AnimNodeResource result;
|
||||
|
||||
result.m_name = json_node["name"];
|
||||
|
@ -198,8 +203,7 @@ AnimNodeResource sAnimGraphNodeFromJson(const json& json_node, size_t node_index
|
|||
//
|
||||
// AnimGraphConnectionResource <-> Json
|
||||
//
|
||||
json sAnimGraphConnectionToJson(
|
||||
const AnimGraphConnectionResource& connection) {
|
||||
json sAnimGraphConnectionToJson(const BlendTreeConnectionResource& connection) {
|
||||
json result;
|
||||
|
||||
result["type"] = "AnimGraphConnectionResource";
|
||||
|
@ -213,9 +217,9 @@ json sAnimGraphConnectionToJson(
|
|||
return result;
|
||||
}
|
||||
|
||||
AnimGraphConnectionResource sAnimGraphConnectionFromJson(
|
||||
BlendTreeConnectionResource sAnimGraphConnectionFromJson(
|
||||
const json& json_node) {
|
||||
AnimGraphConnectionResource connection;
|
||||
BlendTreeConnectionResource connection;
|
||||
|
||||
connection.source_node_index = json_node["source_node_index"];
|
||||
connection.source_socket_name = json_node["source_socket_name"];
|
||||
|
@ -226,58 +230,136 @@ AnimGraphConnectionResource sAnimGraphConnectionFromJson(
|
|||
return connection;
|
||||
}
|
||||
|
||||
void AnimGraphResource::clear() {
|
||||
m_name = "";
|
||||
bool AnimGraphResource::LoadFromFile(const char* filename) {
|
||||
std::ifstream input_file;
|
||||
input_file.open(filename);
|
||||
std::stringstream buffer;
|
||||
buffer << input_file.rdbuf();
|
||||
|
||||
clearNodes();
|
||||
m_connections.clear();
|
||||
json json_data = json::parse(buffer.str(), nullptr, false);
|
||||
if (json_data.is_discarded()) {
|
||||
std::cerr << "Error parsing json of file '" << filename << "'."
|
||||
<< std::endl;
|
||||
|
||||
initGraphConnectors();
|
||||
return false;
|
||||
}
|
||||
|
||||
void AnimGraphResource::clearNodes() {
|
||||
for (auto & m_node : m_nodes) {
|
||||
delete m_node.m_socket_accessor;
|
||||
m_node.m_socket_accessor = nullptr;
|
||||
delete m_node.m_anim_node;
|
||||
m_node.m_anim_node = nullptr;
|
||||
}
|
||||
m_nodes.clear();
|
||||
if (json_data["type"] != "AnimNodeResource") {
|
||||
std::cerr
|
||||
<< "Invalid json object. Expected type 'AnimNodeResource' but got '"
|
||||
<< json_data["type"] << "'." << std::endl;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void AnimGraphResource::initGraphConnectors() {
|
||||
m_nodes.push_back(AnimNodeResourceFactory("BlendTree"));
|
||||
m_nodes[0].m_name = "Outputs";
|
||||
m_nodes.push_back(AnimNodeResourceFactory("BlendTree"));
|
||||
m_nodes[1].m_name = "Inputs";
|
||||
if (json_data["node_type"] == "BlendTree") {
|
||||
return LoadBlendTreeResourceFromJson(json_data);
|
||||
} else if (json_data["node_type"] == "StateMachine") {
|
||||
return LoadStateMachineResourceFromJson(json_data);
|
||||
}
|
||||
|
||||
bool AnimGraphResource::saveToFile(const char* filename) const {
|
||||
std::cerr << "Invalid node_type. Expected type 'BlendTree' or "
|
||||
"'StateMachine' but got '"
|
||||
<< json_data["node_type"] << "'." << std::endl;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AnimGraphResource::LoadBlendTreeResourceFromJson(nlohmann::json const& json_data) {
|
||||
m_blend_tree_resource.Reset();
|
||||
m_type = "BlendTree";
|
||||
m_name = json_data["name"];
|
||||
|
||||
// Load nodes
|
||||
for (size_t i = 0, n = json_data["nodes"].size(); i < n; i++) {
|
||||
const json& json_node = json_data["nodes"][i];
|
||||
if (json_node["type"] != "AnimNodeResource") {
|
||||
std::cerr
|
||||
<< "Invalid json object. Expected type 'AnimNodeResource' but got '"
|
||||
<< json_node["type"] << "'." << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
AnimNodeResource node = sAnimGraphNodeFromJson(json_node, i);
|
||||
m_blend_tree_resource.m_nodes.push_back(node);
|
||||
}
|
||||
|
||||
// Setup graph inputs and outputs
|
||||
const json& graph_outputs = json_data["nodes"][0]["inputs"];
|
||||
for (const auto& graph_output : graph_outputs) {
|
||||
AnimNodeResource& graph_node = m_blend_tree_resource.m_nodes[0];
|
||||
graph_node.m_socket_accessor->m_inputs.push_back(
|
||||
sJsonToSocket(graph_output));
|
||||
}
|
||||
|
||||
const json& graph_inputs = json_data["nodes"][1]["outputs"];
|
||||
for (const auto& graph_input : graph_inputs) {
|
||||
AnimNodeResource& graph_node = m_blend_tree_resource.m_nodes[1];
|
||||
graph_node.m_socket_accessor->m_outputs.push_back(
|
||||
sJsonToSocket(graph_input));
|
||||
}
|
||||
|
||||
// Load connections
|
||||
for (const auto& json_connection : json_data["connections"]) {
|
||||
if (json_connection["type"] != "AnimGraphConnectionResource") {
|
||||
std::cerr << "Invalid json object. Expected type "
|
||||
"'AnimGraphConnectionResource' "
|
||||
"but got '"
|
||||
<< json_connection["type"] << "'." << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
BlendTreeConnectionResource connection =
|
||||
sAnimGraphConnectionFromJson(json_connection);
|
||||
m_blend_tree_resource.m_connections.push_back(connection);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AnimGraphResource::SaveToFile(const char* filename) const {
|
||||
if (m_type == "BlendTree") {
|
||||
return SaveBlendTreeResourceToFile(filename);
|
||||
} else if (m_type == "StateMachine") {
|
||||
return SaveStateMachineResourceToFile(filename);
|
||||
}
|
||||
|
||||
std::cerr << "Invalid AnimGraphResource type: " << m_type << "." << std::endl;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AnimGraphResource::SaveBlendTreeResourceToFile(
|
||||
const char* filename) const {
|
||||
json result;
|
||||
|
||||
result["name"] = m_name;
|
||||
result["type"] = "AnimGraphResource";
|
||||
result["type"] = "AnimNodeResource";
|
||||
result["node_type"] = "BlendTree";
|
||||
|
||||
for (size_t i = 0; i < m_nodes.size(); i++) {
|
||||
const AnimNodeResource& node = m_nodes[i];
|
||||
result["nodes"][i] = sAnimGraphNodeToJson(node, i, m_connections);
|
||||
for (size_t i = 0; i < m_blend_tree_resource.m_nodes.size(); i++) {
|
||||
const AnimNodeResource& node = m_blend_tree_resource.m_nodes[i];
|
||||
result["nodes"][i] =
|
||||
sAnimGraphNodeToJson(node, i, m_blend_tree_resource.m_connections);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < m_connections.size(); i++) {
|
||||
const AnimGraphConnectionResource& connection = m_connections[i];
|
||||
for (size_t i = 0; i < m_blend_tree_resource.m_connections.size(); i++) {
|
||||
const BlendTreeConnectionResource& connection =
|
||||
m_blend_tree_resource.m_connections[i];
|
||||
result["connections"][i] = sAnimGraphConnectionToJson(connection);
|
||||
}
|
||||
|
||||
// Graph inputs and outputs
|
||||
{
|
||||
const AnimNodeResource& graph_output_node = m_nodes[0];
|
||||
const AnimNodeResource& graph_output_node =
|
||||
m_blend_tree_resource.m_nodes[0];
|
||||
const std::vector<Socket> graph_inputs =
|
||||
graph_output_node.m_socket_accessor->m_inputs;
|
||||
for (size_t i = 0; i < graph_inputs.size(); i++) {
|
||||
result["nodes"][0]["inputs"][i] = sSocketToJson(graph_inputs[i]);
|
||||
}
|
||||
|
||||
const AnimNodeResource& graph_input_node = m_nodes[1];
|
||||
const AnimNodeResource& graph_input_node = m_blend_tree_resource.m_nodes[1];
|
||||
const std::vector<Socket> graph_outputs =
|
||||
graph_input_node.m_socket_accessor->m_outputs;
|
||||
for (size_t i = 0; i < graph_outputs.size(); i++) {
|
||||
|
@ -293,107 +375,45 @@ bool AnimGraphResource::saveToFile(const char* filename) const {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool AnimGraphResource::loadFromFile(const char* filename) {
|
||||
std::ifstream input_file;
|
||||
input_file.open(filename);
|
||||
std::stringstream buffer;
|
||||
buffer << input_file.rdbuf();
|
||||
|
||||
json json_data = json::parse(buffer.str(), nullptr, false);
|
||||
if (json_data.is_discarded()) {
|
||||
std::cerr << "Error parsing json of file '" << filename << "'."
|
||||
<< std::endl;
|
||||
void AnimGraphResource::CreateBlendTreeInstance(
|
||||
AnimGraphBlendTree& result) const {
|
||||
if (m_type != "BlendTree") {
|
||||
std::cerr << "Invalid AnimGraphResource. Expected type 'BlendTree' but got '"
|
||||
<< m_type << "'." << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
if (json_data["type"] != "AnimGraphResource") {
|
||||
std::cerr
|
||||
<< "Invalid json object. Expected type 'AnimGraphResource' but got '"
|
||||
<< json_data["type"] << "'." << std::endl;
|
||||
CreateBlendTreeRuntimeNodeInstances(result);
|
||||
PrepareBlendTreeIOData(result);
|
||||
SetRuntimeNodeProperties(result);
|
||||
|
||||
result.UpdateOrderedNodes();
|
||||
result.ResetNodeStates();
|
||||
}
|
||||
|
||||
clear();
|
||||
clearNodes();
|
||||
|
||||
m_name = json_data["name"];
|
||||
|
||||
// Load nodes
|
||||
for (size_t i = 0, n = json_data["nodes"].size(); i < n; i++) {
|
||||
const json& json_node = json_data["nodes"][i];
|
||||
if (json_node["type"] != "AnimNodeResource") {
|
||||
std::cerr
|
||||
<< "Invalid json object. Expected type 'AnimNodeResource' but got '"
|
||||
<< json_node["type"] << "'." << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
AnimNodeResource node = sAnimGraphNodeFromJson(json_node, i);
|
||||
m_nodes.push_back(node);
|
||||
}
|
||||
|
||||
// Setup graph inputs and outputs
|
||||
const json& graph_outputs = json_data["nodes"][0]["inputs"];
|
||||
for (const auto & graph_output : graph_outputs) {
|
||||
AnimNodeResource& graph_node = m_nodes[0];
|
||||
graph_node.m_socket_accessor->m_inputs.push_back(
|
||||
sJsonToSocket(graph_output));
|
||||
}
|
||||
|
||||
const json& graph_inputs = json_data["nodes"][1]["outputs"];
|
||||
for (const auto & graph_input : graph_inputs) {
|
||||
AnimNodeResource& graph_node = m_nodes[1];
|
||||
graph_node.m_socket_accessor->m_outputs.push_back(
|
||||
sJsonToSocket(graph_input));
|
||||
}
|
||||
|
||||
// Load connections
|
||||
for (const auto & json_connection : json_data["connections"]) {
|
||||
if (json_connection["type"] != "AnimGraphConnectionResource") {
|
||||
std::cerr
|
||||
<< "Invalid json object. Expected type 'AnimGraphConnectionResource' "
|
||||
"but got '"
|
||||
<< json_connection["type"] << "'." << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
AnimGraphConnectionResource connection =
|
||||
sAnimGraphConnectionFromJson(json_connection);
|
||||
m_connections.push_back(connection);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AnimGraphResource::createInstance(AnimGraph& result) const {
|
||||
createRuntimeNodeInstances(result);
|
||||
prepareGraphIOData(result);
|
||||
setRuntimeNodeProperties(result);
|
||||
|
||||
result.updateOrderedNodes();
|
||||
result.resetNodeStates();
|
||||
}
|
||||
|
||||
void AnimGraphResource::createRuntimeNodeInstances(AnimGraph& instance) const {
|
||||
for (int i = 0; i < m_nodes.size(); i++) {
|
||||
const AnimNodeResource& node_resource = m_nodes[i];
|
||||
void AnimGraphResource::CreateBlendTreeRuntimeNodeInstances(
|
||||
AnimGraphBlendTree& result) const {
|
||||
for (int i = 0; i < m_blend_tree_resource.m_nodes.size(); i++) {
|
||||
const AnimNodeResource& node_resource = m_blend_tree_resource.m_nodes[i];
|
||||
AnimNode* node = AnimNodeFactory(node_resource.m_type_name);
|
||||
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);
|
||||
result.m_nodes.push_back(node);
|
||||
|
||||
// runtime node connections
|
||||
instance.m_node_input_connections.emplace_back();
|
||||
instance.m_node_output_connections.emplace_back();
|
||||
|
||||
result.m_node_input_connections.emplace_back();
|
||||
result.m_node_output_connections.emplace_back();
|
||||
}
|
||||
}
|
||||
|
||||
void AnimGraphResource::prepareGraphIOData(AnimGraph& instance) const {
|
||||
void AnimGraphResource::PrepareBlendTreeIOData(
|
||||
AnimGraphBlendTree& instance) const {
|
||||
instance.m_node_descriptor =
|
||||
AnimNodeDescriptorFactory("BlendTree", instance.m_nodes[0]);
|
||||
instance.m_node_descriptor->m_outputs =
|
||||
m_nodes[1].m_socket_accessor->m_outputs;
|
||||
instance.m_node_descriptor->m_inputs = m_nodes[0].m_socket_accessor->m_inputs;
|
||||
m_blend_tree_resource.m_nodes[1].m_socket_accessor->m_outputs;
|
||||
instance.m_node_descriptor->m_inputs =
|
||||
m_blend_tree_resource.m_nodes[0].m_socket_accessor->m_inputs;
|
||||
|
||||
//
|
||||
// graph inputs
|
||||
|
@ -442,8 +462,9 @@ void AnimGraphResource::prepareGraphIOData(AnimGraph& instance) const {
|
|||
// connections: make source and target sockets point to the same address in the connection data storage.
|
||||
// TODO: instead of every connection, only create data blocks for the source sockets and make sure every source socket gets allocated once.
|
||||
size_t connection_data_storage_size = 0;
|
||||
for (const auto & connection : m_connections) {
|
||||
const AnimNodeResource& source_node = m_nodes[connection.source_node_index];
|
||||
for (const auto& connection : m_blend_tree_resource.m_connections) {
|
||||
const AnimNodeResource& source_node =
|
||||
m_blend_tree_resource.m_nodes[connection.source_node_index];
|
||||
Socket* source_socket = source_node.m_socket_accessor->GetOutputSocket(
|
||||
connection.source_socket_name.c_str());
|
||||
connection_data_storage_size += source_socket->m_type_size;
|
||||
|
@ -455,11 +476,11 @@ void AnimGraphResource::prepareGraphIOData(AnimGraph& instance) const {
|
|||
}
|
||||
|
||||
std::vector<NodeDescriptorBase*> instance_node_descriptors(
|
||||
m_nodes.size(),
|
||||
m_blend_tree_resource.m_nodes.size(),
|
||||
nullptr);
|
||||
for (int i = 0; i < m_nodes.size(); i++) {
|
||||
for (int i = 0; i < m_blend_tree_resource.m_nodes.size(); i++) {
|
||||
instance_node_descriptors[i] = AnimNodeDescriptorFactory(
|
||||
m_nodes[i].m_type_name,
|
||||
m_blend_tree_resource.m_nodes[i].m_type_name,
|
||||
instance.m_nodes[i]);
|
||||
}
|
||||
|
||||
|
@ -468,7 +489,7 @@ void AnimGraphResource::prepareGraphIOData(AnimGraph& instance) const {
|
|||
instance.m_node_descriptor->m_outputs;
|
||||
|
||||
size_t connection_data_offset = 0;
|
||||
for (const auto & connection : m_connections) {
|
||||
for (const auto& connection : m_blend_tree_resource.m_connections) {
|
||||
NodeDescriptorBase* source_node_descriptor =
|
||||
instance_node_descriptors[connection.source_node_index];
|
||||
NodeDescriptorBase* target_node_descriptor =
|
||||
|
@ -513,7 +534,7 @@ void AnimGraphResource::prepareGraphIOData(AnimGraph& instance) const {
|
|||
// const node inputs
|
||||
//
|
||||
std::vector<Socket*> const_inputs =
|
||||
getConstNodeInputs( instance_node_descriptors);
|
||||
m_blend_tree_resource.GetConstantNodeInputs(instance_node_descriptors);
|
||||
size_t const_node_inputs_buffer_size = 0;
|
||||
for (auto& const_input : const_inputs) {
|
||||
if (const_input->m_type == SocketType::SocketTypeString) {
|
||||
|
@ -539,23 +560,26 @@ void AnimGraphResource::prepareGraphIOData(AnimGraph& instance) const {
|
|||
|
||||
*const_input->m_reference.ptr_ptr =
|
||||
&instance.m_const_node_inputs[const_input_buffer_offset];
|
||||
memcpy (*const_input->m_reference.ptr_ptr, &const_input->m_value, i->m_type_size);
|
||||
memcpy(
|
||||
*const_input->m_reference.ptr_ptr,
|
||||
&const_input->m_value,
|
||||
i->m_type_size);
|
||||
|
||||
const_input_buffer_offset += i->m_type_size;
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_nodes.size(); i++) {
|
||||
for (int i = 0; i < m_blend_tree_resource.m_nodes.size(); i++) {
|
||||
delete instance_node_descriptors[i];
|
||||
}
|
||||
}
|
||||
|
||||
void AnimGraphResource::setRuntimeNodeProperties(AnimGraph& instance) const {
|
||||
for (int i = 2; i < m_nodes.size(); i++) {
|
||||
const AnimNodeResource& node_resource = m_nodes[i];
|
||||
void AnimGraphResource::SetRuntimeNodeProperties(
|
||||
AnimGraphBlendTree& result) const {
|
||||
for (int i = 2; i < m_blend_tree_resource.m_nodes.size(); i++) {
|
||||
const AnimNodeResource& node_resource = m_blend_tree_resource.m_nodes[i];
|
||||
|
||||
NodeDescriptorBase* node_instance_accessor = AnimNodeDescriptorFactory(
|
||||
node_resource.m_type_name,
|
||||
instance.m_nodes[i]);
|
||||
NodeDescriptorBase* node_instance_accessor =
|
||||
AnimNodeDescriptorFactory(node_resource.m_type_name, result.m_nodes[i]);
|
||||
|
||||
std::vector<Socket>& resource_properties =
|
||||
node_resource.m_socket_accessor->m_properties;
|
||||
|
@ -603,22 +627,15 @@ void AnimGraphResource::setRuntimeNodeProperties(AnimGraph& instance) const {
|
|||
}
|
||||
}
|
||||
|
||||
std::vector<Socket*> AnimGraphResource::getConstNodeInputs(
|
||||
std::vector<NodeDescriptorBase*>& instance_node_descriptors) const {
|
||||
std::vector<Socket*> result;
|
||||
bool AnimGraphResource::SaveStateMachineResourceToFile(
|
||||
const char* filename) const {
|
||||
assert(false && "Not yet implemented");
|
||||
|
||||
for (size_t i = 0; i < m_nodes.size(); i++) {
|
||||
for (size_t j = 0, num_inputs = instance_node_descriptors[i]->m_inputs.size();
|
||||
j < num_inputs;
|
||||
j++) {
|
||||
Socket& input = instance_node_descriptors[i]->m_inputs[j];
|
||||
|
||||
if (*input.m_reference.ptr_ptr == nullptr) {
|
||||
memcpy(&input.m_value, &m_nodes[i].m_socket_accessor->m_inputs[j].m_value, sizeof(Socket::SocketValue));
|
||||
result.push_back(&input);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return result;
|
||||
bool AnimGraphResource::LoadStateMachineResourceFromJson(nlohmann::json const& json_data) {
|
||||
assert(false && "Not yet implemented");
|
||||
|
||||
return false;
|
||||
}
|
|
@ -1,23 +1,17 @@
|
|||
//
|
||||
// Created by martin on 04.02.22.
|
||||
// Created by martin on 17.03.24.
|
||||
//
|
||||
|
||||
#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;
|
||||
#include "3rdparty/json/json.hpp"
|
||||
|
||||
struct AnimGraphBlendTree;
|
||||
struct AnimGraphStateMachine;
|
||||
|
||||
struct AnimNodeResource {
|
||||
std::string m_name;
|
||||
|
@ -37,40 +31,33 @@ static inline AnimNodeResource AnimNodeResourceFactory(
|
|||
return result;
|
||||
}
|
||||
|
||||
//
|
||||
// AnimGraphResource
|
||||
//
|
||||
struct AnimGraphConnectionResource {
|
||||
struct BlendTreeConnectionResource {
|
||||
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;
|
||||
struct BlendTreeResource {
|
||||
std::vector<AnimNodeResource> m_nodes;
|
||||
std::vector<AnimGraphConnectionResource> m_connections;
|
||||
std::vector<BlendTreeConnectionResource> m_connections;
|
||||
|
||||
~AnimGraphResource() {
|
||||
for (auto & m_node : m_nodes) {
|
||||
delete m_node.m_anim_node;
|
||||
delete m_node.m_socket_accessor;
|
||||
}
|
||||
void Reset() {
|
||||
m_nodes.clear();
|
||||
m_connections.clear();
|
||||
}
|
||||
|
||||
AnimGraphResource() { clear(); }
|
||||
void InitGraphConnectors() {
|
||||
m_nodes.push_back(AnimNodeResourceFactory("BlendTree"));
|
||||
m_nodes[0].m_name = "Outputs";
|
||||
m_nodes.push_back(AnimNodeResourceFactory("BlendTree"));
|
||||
m_nodes[1].m_name = "Inputs";
|
||||
}
|
||||
|
||||
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]; }
|
||||
|
||||
AnimNodeResource& getGraphOutputNode() { return m_nodes[0]; }
|
||||
AnimNodeResource& getGraphInputNode() { return m_nodes[1]; }
|
||||
|
||||
size_t getNodeIndex(const AnimNodeResource& node_resource) const {
|
||||
size_t GetNodeIndex(const AnimNodeResource& node_resource) const {
|
||||
for (size_t i = 0, n = m_nodes.size(); i < n; i++) {
|
||||
if (&m_nodes[i] == &node_resource) {
|
||||
return i;
|
||||
|
@ -80,18 +67,13 @@ struct AnimGraphResource {
|
|||
return -1;
|
||||
}
|
||||
|
||||
size_t addNode(const AnimNodeResource &node_resource) {
|
||||
m_nodes.push_back(node_resource);
|
||||
return m_nodes.size() - 1;
|
||||
}
|
||||
|
||||
bool connectSockets(
|
||||
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);
|
||||
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()) {
|
||||
|
@ -109,7 +91,7 @@ struct AnimGraphResource {
|
|||
return false;
|
||||
}
|
||||
|
||||
AnimGraphConnectionResource connection;
|
||||
BlendTreeConnectionResource connection;
|
||||
connection.source_node_index = source_node_index;
|
||||
connection.source_socket_name = source_socket_name;
|
||||
connection.target_node_index = target_node_index;
|
||||
|
@ -119,28 +101,62 @@ struct AnimGraphResource {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool isSocketConnected(
|
||||
const AnimNodeResource& node,
|
||||
const std::string& socket_name) {
|
||||
size_t node_index = getNodeIndex(node);
|
||||
for (const auto & connection : m_connections) {
|
||||
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;
|
||||
std::vector<Socket*> GetConstantNodeInputs(
|
||||
std::vector<NodeDescriptorBase*>& instance_node_descriptors) const {
|
||||
std::vector<Socket*> result;
|
||||
|
||||
for (size_t i = 0; i < m_nodes.size(); i++) {
|
||||
for (size_t j = 0, num_inputs = instance_node_descriptors[i]->m_inputs.size();
|
||||
j < num_inputs;
|
||||
j++) {
|
||||
Socket& input = instance_node_descriptors[i]->m_inputs[j];
|
||||
|
||||
if (*input.m_reference.ptr_ptr == nullptr) {
|
||||
memcpy(&input.m_value, &m_nodes[i].m_socket_accessor->m_inputs[j].m_value, sizeof(Socket::SocketValue));
|
||||
result.push_back(&input);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
void createInstance(AnimGraph& result) const;
|
||||
struct StateMachineTransitionResources {
|
||||
size_t source_state_index = -1;
|
||||
size_t target_state_index = -1;
|
||||
float blend_time = 0.f;
|
||||
bool sync_blend = false;
|
||||
};
|
||||
|
||||
void createRuntimeNodeInstances(AnimGraph& instance) const;
|
||||
void prepareGraphIOData(AnimGraph& instance) const;
|
||||
void setRuntimeNodeProperties(AnimGraph& instance) const;
|
||||
std::vector<Socket*> getConstNodeInputs(std::vector<NodeDescriptorBase*>& instance_node_descriptors) const;
|
||||
struct StateMachineResource {
|
||||
std::vector<AnimNodeResource> m_states;
|
||||
std::vector<StateMachineTransitionResources> m_transitions;
|
||||
};
|
||||
|
||||
struct AnimGraphResource {
|
||||
std::string m_type;
|
||||
std::string m_name;
|
||||
|
||||
BlendTreeResource m_blend_tree_resource;
|
||||
StateMachineResource m_state_machine_resource;
|
||||
|
||||
bool SaveToFile(const char* filename) const;
|
||||
bool LoadFromFile(const char* filename);
|
||||
|
||||
void CreateBlendTreeInstance(AnimGraphBlendTree& result) const;
|
||||
void CreateStateMachineInstance(AnimGraphStateMachine& result) const;
|
||||
|
||||
private:
|
||||
// BlendTree
|
||||
bool SaveBlendTreeResourceToFile(const char* filename) const;
|
||||
bool LoadBlendTreeResourceFromJson(nlohmann::json const& json_data);
|
||||
void CreateBlendTreeRuntimeNodeInstances(AnimGraphBlendTree& result) const;
|
||||
void PrepareBlendTreeIOData(AnimGraphBlendTree& instance) const;
|
||||
void SetRuntimeNodeProperties(AnimGraphBlendTree& result) const;
|
||||
|
||||
bool SaveStateMachineResourceToFile(const char* filename) const;
|
||||
bool LoadStateMachineResourceFromJson(nlohmann::json const& json_data);
|
||||
};
|
||||
|
||||
#endif //ANIMTESTBED_ANIMGRAPHRESOURCE_H
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
//
|
||||
// Created by martin on 17.03.24.
|
||||
//
|
||||
|
||||
#include "AnimGraphStateMachine.h"
|
||||
|
||||
bool AnimGraphStateMachine::Init(AnimGraphContext& context) {
|
||||
|
||||
}
|
||||
|
||||
void AnimGraphStateMachine::MarkActiveInputs() {
|
||||
|
||||
}
|
||||
|
||||
void AnimGraphStateMachine::CalcSyncTrack() {
|
||||
|
||||
}
|
||||
|
||||
void AnimGraphStateMachine::UpdateTime(float time_last, float time_now) {
|
||||
|
||||
}
|
||||
|
||||
void AnimGraphStateMachine::Evaluate(AnimGraphContext& context) {
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
//
|
||||
// Created by martin on 17.03.24.
|
||||
//
|
||||
|
||||
#ifndef ANIMTESTBED_ANIMGRAPHSTATEMACHINE_H
|
||||
#define ANIMTESTBED_ANIMGRAPHSTATEMACHINE_H
|
||||
|
||||
#include "AnimGraphNodes.h"
|
||||
|
||||
struct Transition {
|
||||
AnimNode* m_source_state = nullptr;
|
||||
AnimNode* m_target_state = nullptr;
|
||||
|
||||
float m_blend_time = 0.f;
|
||||
bool m_sync_blend = false;
|
||||
};
|
||||
|
||||
struct AnimGraphStateMachine : public AnimNode {
|
||||
std::vector<AnimNode> m_states;
|
||||
std::vector<Transition> m_transitions;
|
||||
std::vector<std::vector<Transition*> > m_state_out_transitions;
|
||||
|
||||
AnimNode* m_next_state = nullptr;
|
||||
AnimNode* m_current_state = nullptr;
|
||||
Transition* m_active_transition = nullptr;
|
||||
|
||||
bool Init(AnimGraphContext& context);
|
||||
void MarkActiveInputs() override;
|
||||
void CalcSyncTrack() override;
|
||||
void UpdateTime(float time_last, float time_now) override;
|
||||
void Evaluate(AnimGraphContext& context) override;
|
||||
};
|
||||
|
||||
|
||||
#endif //ANIMTESTBED_ANIMGRAPHSTATEMACHINE_H
|
|
@ -0,0 +1,5 @@
|
|||
//
|
||||
// Created by martin on 17.03.24.
|
||||
//
|
||||
|
||||
#include "AnimNode.h"
|
|
@ -0,0 +1,70 @@
|
|||
//
|
||||
// Created by martin on 17.03.24.
|
||||
//
|
||||
|
||||
#ifndef ANIMTESTBED_ANIMNODE_H
|
||||
#define ANIMTESTBED_ANIMNODE_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "SyncTrack.h"
|
||||
#include "AnimGraphData.h"
|
||||
|
||||
struct AnimNode;
|
||||
|
||||
enum class AnimNodeEvalState {
|
||||
Undefined,
|
||||
Deactivated,
|
||||
Activated,
|
||||
SyncTrackUpdated,
|
||||
TimeUpdated,
|
||||
Evaluated
|
||||
};
|
||||
|
||||
struct AnimNode {
|
||||
std::string m_name;
|
||||
std::string m_node_type_name;
|
||||
AnimNodeEvalState m_state = AnimNodeEvalState::Undefined;
|
||||
|
||||
float m_time_now = 0.f;
|
||||
float m_time_last = 0.f;
|
||||
SyncTrack m_sync_track;
|
||||
|
||||
std::vector<AnimGraphConnection> m_inputs;
|
||||
|
||||
virtual ~AnimNode() = default;
|
||||
|
||||
virtual bool Init(AnimGraphContext& context) { return true; };
|
||||
|
||||
virtual void MarkActiveInputs() {
|
||||
for (const auto & input : m_inputs) {
|
||||
AnimNode* input_node = input.m_source_node;
|
||||
if (input_node != nullptr) {
|
||||
input_node->m_state = AnimNodeEvalState::Activated;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual void CalcSyncTrack() {
|
||||
for (const auto & input : m_inputs) {
|
||||
AnimNode* input_node = input.m_source_node;
|
||||
if (input_node != nullptr
|
||||
&& input.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(AnimGraphContext& context){};
|
||||
};
|
||||
|
||||
#endif //ANIMTESTBED_ANIMNODE_H
|
|
@ -3,8 +3,8 @@
|
|||
//
|
||||
|
||||
#include "AnimGraph/AnimGraph.h"
|
||||
#include "AnimGraph/AnimGraphBlendTreeResource.h"
|
||||
#include "AnimGraph/AnimGraphEditor.h"
|
||||
#include "AnimGraph/AnimGraphResource.h"
|
||||
#include "catch.hpp"
|
||||
#include "ozz/animation/offline/animation_builder.h"
|
||||
#include "ozz/animation/offline/raw_animation.h"
|
||||
|
@ -141,7 +141,7 @@ TEST_CASE_METHOD(
|
|||
SimpleAnimFixture,
|
||||
"AnimGraphSimpleEval",
|
||||
"[AnimGraphEvalTests]") {
|
||||
AnimGraphResource graph_resource;
|
||||
AnimGraphBlendTreeResource graph_resource;
|
||||
|
||||
// Add nodes
|
||||
size_t trans_x_node_index =
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
//
|
||||
|
||||
#include "AnimGraph/AnimGraph.h"
|
||||
#include "AnimGraph/AnimGraphBlendTree.h"
|
||||
#include "AnimGraph/AnimGraphEditor.h"
|
||||
#include "AnimGraph/AnimGraphNodes.h"
|
||||
#include "AnimGraph/AnimGraphResource.h"
|
||||
#include "catch.hpp"
|
||||
#include "ozz/base/io/archive.h"
|
||||
|
@ -33,61 +35,65 @@ bool load_skeleton(ozz::animation::Skeleton& skeleton, const char* filename) {
|
|||
|
||||
TEST_CASE("AnimSamplerGraph", "[AnimGraphResource]") {
|
||||
AnimGraphResource graph_resource;
|
||||
graph_resource.m_name = "AnimSamplerBlendTree";
|
||||
graph_resource.m_type = "BlendTree";
|
||||
|
||||
graph_resource.clear();
|
||||
graph_resource.m_name = "AnimSamplerGraph";
|
||||
BlendTreeResource& blend_tree_resource = graph_resource.m_blend_tree_resource;
|
||||
blend_tree_resource.Reset();
|
||||
blend_tree_resource.InitGraphConnectors();
|
||||
|
||||
// Prepare graph inputs and outputs
|
||||
size_t walk_node_index =
|
||||
graph_resource.addNode(AnimNodeResourceFactory("AnimSampler"));
|
||||
blend_tree_resource.m_nodes.push_back(AnimNodeResourceFactory("AnimSampler"));
|
||||
size_t walk_node_index = blend_tree_resource.m_nodes.size() - 1;
|
||||
|
||||
AnimNodeResource& walk_node = graph_resource.m_nodes[walk_node_index];
|
||||
AnimNodeResource& walk_node = blend_tree_resource.m_nodes[walk_node_index];
|
||||
walk_node.m_name = "WalkAnim";
|
||||
walk_node.m_socket_accessor->SetPropertyValue(
|
||||
"Filename",
|
||||
std::string("media/Walking-loop.ozz"));
|
||||
|
||||
AnimNodeResource& graph_node = graph_resource.m_nodes[0];
|
||||
AnimNodeResource& graph_node = blend_tree_resource.m_nodes[0];
|
||||
graph_node.m_socket_accessor->RegisterInput<AnimData>("GraphOutput", nullptr);
|
||||
|
||||
graph_resource.connectSockets(
|
||||
blend_tree_resource.ConnectSockets(
|
||||
walk_node,
|
||||
"Output",
|
||||
graph_resource.getGraphOutputNode(),
|
||||
blend_tree_resource.GetGraphOutputNode(),
|
||||
"GraphOutput");
|
||||
|
||||
graph_resource.saveToFile("AnimSamplerGraph.animgraph.json");
|
||||
AnimGraphResource graph_resource_loaded;
|
||||
graph_resource_loaded.loadFromFile("AnimSamplerGraph.animgraph.json");
|
||||
graph_resource.SaveToFile("AnimSamplerBlendTree.json");
|
||||
|
||||
AnimGraph graph;
|
||||
graph_resource_loaded.createInstance(graph);
|
||||
AnimGraphResource graph_resource_loaded;
|
||||
graph_resource_loaded.LoadFromFile("AnimSamplerBlendTree.json");
|
||||
|
||||
AnimGraphBlendTree anim_graph_blend_tree;
|
||||
graph_resource_loaded.CreateBlendTreeInstance(anim_graph_blend_tree);
|
||||
AnimGraphContext graph_context;
|
||||
|
||||
ozz::animation::Skeleton skeleton;
|
||||
REQUIRE(load_skeleton(skeleton, "media/skeleton.ozz"));
|
||||
graph_context.m_skeleton = &skeleton;
|
||||
|
||||
REQUIRE(graph.init(graph_context));
|
||||
REQUIRE(anim_graph_blend_tree.Init(graph_context));
|
||||
|
||||
REQUIRE(graph.m_nodes.size() == 3);
|
||||
REQUIRE(graph.m_nodes[0]->m_node_type_name == "BlendTree");
|
||||
REQUIRE(graph.m_nodes[1]->m_node_type_name == "BlendTree");
|
||||
REQUIRE(graph.m_nodes[2]->m_node_type_name == "AnimSampler");
|
||||
REQUIRE(anim_graph_blend_tree.m_nodes.size() == 3);
|
||||
REQUIRE(anim_graph_blend_tree.m_nodes[0]->m_node_type_name == "BlendTree");
|
||||
REQUIRE(anim_graph_blend_tree.m_nodes[1]->m_node_type_name == "BlendTree");
|
||||
REQUIRE(anim_graph_blend_tree.m_nodes[2]->m_node_type_name == "AnimSampler");
|
||||
|
||||
// connections within the graph
|
||||
AnimSamplerNode* anim_sampler_walk =
|
||||
dynamic_cast<AnimSamplerNode*>(graph.m_nodes[2]);
|
||||
dynamic_cast<AnimSamplerNode*>(anim_graph_blend_tree.m_nodes[2]);
|
||||
|
||||
BlendTreeNode* graph_output_node =
|
||||
dynamic_cast<BlendTreeNode*>(graph.m_nodes[0]);
|
||||
dynamic_cast<BlendTreeNode*>(anim_graph_blend_tree.m_nodes[0]);
|
||||
|
||||
// check node input dependencies
|
||||
size_t anim_sampler_index = anim_sampler_walk->m_index;
|
||||
size_t anim_sampler_index = anim_graph_blend_tree.GetAnimNodeIndex(anim_sampler_walk);
|
||||
|
||||
REQUIRE(graph.m_node_output_connections[anim_sampler_index].size() == 1);
|
||||
REQUIRE(anim_graph_blend_tree.m_node_output_connections[anim_sampler_index].size() == 1);
|
||||
CHECK(
|
||||
graph.m_node_output_connections[anim_sampler_index][0].m_target_node
|
||||
anim_graph_blend_tree.m_node_output_connections[anim_sampler_index][0].m_target_node
|
||||
== graph_output_node);
|
||||
|
||||
// Ensure animation sampler nodes use the correct files
|
||||
|
@ -97,11 +103,11 @@ TEST_CASE("AnimSamplerGraph", "[AnimGraphResource]") {
|
|||
// Ensure that outputs are properly propagated.
|
||||
AnimData output;
|
||||
output.m_local_matrices.resize(skeleton.num_soa_joints());
|
||||
graph.SetOutput("GraphOutput", &output);
|
||||
anim_graph_blend_tree.SetOutput("GraphOutput", &output);
|
||||
REQUIRE(anim_sampler_walk->o_output == &output);
|
||||
|
||||
WHEN("Emulating Graph Evaluation") {
|
||||
CHECK(graph.m_anim_data_allocator.size() == 0);
|
||||
CHECK(anim_graph_blend_tree.m_anim_data_allocator.size() == 0);
|
||||
anim_sampler_walk->Evaluate(graph_context);
|
||||
}
|
||||
|
||||
|
@ -109,10 +115,12 @@ TEST_CASE("AnimSamplerGraph", "[AnimGraphResource]") {
|
|||
}
|
||||
|
||||
/*
|
||||
* Checks that node const inputs are properly set.
|
||||
*/
|
||||
|
||||
//
|
||||
// Checks that node const inputs are properly set.
|
||||
//
|
||||
TEST_CASE("AnimSamplerSpeedScaleGraph", "[AnimGraphResource]") {
|
||||
AnimGraphResource graph_resource;
|
||||
AnimGraphBlendTreeResource graph_resource;
|
||||
|
||||
graph_resource.clear();
|
||||
graph_resource.m_name = "AnimSamplerSpeedScaleGraph";
|
||||
|
@ -150,7 +158,7 @@ TEST_CASE("AnimSamplerSpeedScaleGraph", "[AnimGraphResource]") {
|
|||
"GraphOutput");
|
||||
|
||||
graph_resource.saveToFile("AnimSamplerSpeedScaleGraph.animgraph.json");
|
||||
AnimGraphResource graph_resource_loaded;
|
||||
AnimGraphBlendTreeResource graph_resource_loaded;
|
||||
graph_resource_loaded.loadFromFile(
|
||||
"AnimSamplerSpeedScaleGraph.animgraph.json");
|
||||
|
||||
|
@ -172,7 +180,7 @@ TEST_CASE("AnimSamplerSpeedScaleGraph", "[AnimGraphResource]") {
|
|||
|
||||
|
||||
TEST_CASE("Blend2Graph", "[AnimGraphResource]") {
|
||||
AnimGraphResource graph_resource;
|
||||
AnimGraphBlendTreeResource graph_resource;
|
||||
|
||||
graph_resource.clear();
|
||||
graph_resource.m_name = "WalkRunBlendGraph";
|
||||
|
@ -214,7 +222,7 @@ TEST_CASE("Blend2Graph", "[AnimGraphResource]") {
|
|||
"GraphOutput");
|
||||
|
||||
graph_resource.saveToFile("Blend2Graph.animgraph.json");
|
||||
AnimGraphResource graph_resource_loaded;
|
||||
AnimGraphBlendTreeResource graph_resource_loaded;
|
||||
graph_resource_loaded.loadFromFile("Blend2Graph.animgraph.json");
|
||||
|
||||
AnimGraph graph;
|
||||
|
@ -311,7 +319,7 @@ TEST_CASE("InputAttributeConversion", "[AnimGraphResource]") {
|
|||
}
|
||||
|
||||
TEST_CASE("ResourceSaveLoadMathGraphInputs", "[AnimGraphResource]") {
|
||||
AnimGraphResource graph_resource_origin;
|
||||
AnimGraphBlendTreeResource graph_resource_origin;
|
||||
|
||||
graph_resource_origin.clear();
|
||||
graph_resource_origin.m_name = "TestInputOutputGraph";
|
||||
|
@ -370,7 +378,7 @@ TEST_CASE("ResourceSaveLoadMathGraphInputs", "[AnimGraphResource]") {
|
|||
const char* filename = "ResourceSaveLoadGraphInputs.json";
|
||||
graph_resource_origin.saveToFile(filename);
|
||||
|
||||
AnimGraphResource graph_resource_loaded;
|
||||
AnimGraphBlendTreeResource graph_resource_loaded;
|
||||
graph_resource_loaded.loadFromFile(filename);
|
||||
|
||||
const AnimNodeResource& graph_loaded_output_node =
|
||||
|
@ -444,7 +452,7 @@ TEST_CASE("ResourceSaveLoadMathGraphInputs", "[AnimGraphResource]") {
|
|||
}
|
||||
|
||||
TEST_CASE("SimpleMathEvaluations", "[AnimGraphResource]") {
|
||||
AnimGraphResource graph_resource_origin;
|
||||
AnimGraphBlendTreeResource graph_resource_origin;
|
||||
|
||||
graph_resource_origin.clear();
|
||||
graph_resource_origin.m_name = "TestInputOutputGraph";
|
||||
|
@ -529,7 +537,7 @@ TEST_CASE("SimpleMathEvaluations", "[AnimGraphResource]") {
|
|||
const char* filename = "ResourceSaveLoadGraphInputs.json";
|
||||
graph_resource_origin.saveToFile(filename);
|
||||
|
||||
AnimGraphResource graph_resource_loaded;
|
||||
AnimGraphBlendTreeResource graph_resource_loaded;
|
||||
graph_resource_loaded.loadFromFile(filename);
|
||||
|
||||
const AnimNodeResource& graph_loaded_output_node =
|
||||
|
@ -580,3 +588,5 @@ TEST_CASE("SimpleMathEvaluations", "[AnimGraphResource]") {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
Loading…
Reference in New Issue