Added support of time updates for simple embedded graphs.

RefactorUnifiedBlendTreeStateMachineHandling
Martin Felis 2024-04-01 12:33:23 +02:00
parent 0aebe44bd5
commit 76ea38f118
12 changed files with 392 additions and 198 deletions

View File

@ -32,8 +32,12 @@ void AnimGraphBlendTree::UpdateOrderedNodesRecursive(int 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++) {
if (node_input_connections[i].m_crosses_hierarchy) {
continue;
}
int input_node_index =
GetAnimNodeIndex(node_input_connections.at(i).m_source_node);
GetAnimNodeIndex(node_input_connections[i].m_source_node);
if (input_node_index == 1) {
continue;
@ -58,10 +62,12 @@ void AnimGraphBlendTree::UpdateOrderedNodesRecursive(int node_index) {
}
}
void AnimGraphBlendTree::MarkActiveInputs() {
void AnimGraphBlendTree::MarkActiveInputs(const std::vector<AnimGraphConnection>& input_connections) {
for (size_t i = 0, n = m_nodes.size(); i < n; i++) {
if (m_nodes[i]->m_tick_number != m_tick_number) {
m_nodes[i]->m_state = AnimNodeEvalState::Deactivated;
}
}
const std::vector<AnimGraphConnection>& graph_output_inputs =
m_node_input_connections[0];
@ -69,15 +75,16 @@ void AnimGraphBlendTree::MarkActiveInputs() {
const AnimGraphConnection& graph_input = graph_output_inputs[i];
AnimNode* node = graph_input.m_source_node;
if (node != nullptr) {
node->m_tick_number = m_tick_number;
node->m_state = AnimNodeEvalState::Activated;
}
}
for (size_t i = m_eval_ordered_nodes.size() - 1; i >= 0; i--) {
for (int i = m_eval_ordered_nodes.size() - 1; i >= 0; i--) {
AnimNode* node = m_eval_ordered_nodes[i];
if (checkIsNodeActive(node)) {
node->MarkActiveInputs();
if (CheckIsNodeActive(node)) {
size_t node_index = GetAnimNodeIndex(node);
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();
@ -95,14 +102,14 @@ void AnimGraphBlendTree::MarkActiveInputs() {
}
}
void AnimGraphBlendTree::CalcSyncTrack() {
void AnimGraphBlendTree::CalcSyncTrack(const std::vector<AnimGraphConnection>& input_connections) {
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();
node->CalcSyncTrack(m_node_input_connections[GetAnimNodeIndex(node)]);
}
}
@ -113,18 +120,38 @@ void AnimGraphBlendTree::UpdateTime(float time_last, float time_now) {
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);
if (node != nullptr && node->m_state != AnimNodeEvalState::TimeUpdated) {
node->UpdateTime(time_last, time_now);
}
}
for (size_t i = m_eval_ordered_nodes.size() - 1; i > 0; --i) {
for (int i = m_eval_ordered_nodes.size() - 1; i >= 0; --i) {
AnimNode* node = m_eval_ordered_nodes[i];
if (node->m_state != AnimNodeEvalState::TimeUpdated) {
continue;
}
PropagateTimeToNodeInputs(node);
}
m_state = AnimNodeEvalState::TimeUpdated;
}
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);
}
}
void AnimGraphBlendTree::PropagateTimeToNodeInputs(const AnimNode* node) {
size_t node_index = GetAnimNodeIndex(node);
float node_time_now = node->m_time_now;
float node_time_last = node->m_time_last;
@ -142,19 +169,6 @@ void AnimGraphBlendTree::UpdateTime(float time_last, float 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++) {

View File

@ -31,13 +31,19 @@ struct AnimGraphBlendTree : public AnimNode {
~AnimGraphBlendTree() override { dealloc(); }
void StartUpdateTick() { m_tick_number++; }
// AnimNode overrides
bool Init(AnimGraphContext& context) override;
void MarkActiveInputs() override;
void CalcSyncTrack() override;
void MarkActiveInputs(
const std::vector<AnimGraphConnection>& input_connections) override;
void CalcSyncTrack(
const std::vector<AnimGraphConnection>& input_connections) override;
void UpdateTime(float time_last, float time_now) override;
void Evaluate(AnimGraphContext& context) override;
void PropagateTimeToNodeInputs(const AnimNode* node);
void dealloc() {
for (size_t i = 0; i < m_animdata_blocks.size(); i++) {
m_animdata_blocks[i]->m_local_matrices.vector::~vector();
@ -62,8 +68,8 @@ struct AnimGraphBlendTree : public AnimNode {
void UpdateOrderedNodes();
void UpdateOrderedNodesRecursive(int node_index);
bool checkIsNodeActive(AnimNode* node) {
return node->m_state != AnimNodeEvalState::Deactivated;
bool CheckIsNodeActive(AnimNode* node) {
return node->m_tick_number == m_tick_number;
}
void ResetNodeStates() {
@ -117,7 +123,8 @@ struct AnimGraphBlendTree : public AnimNode {
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 "
std::cerr
<< "Error: cannot set output for direct graph input to graph "
"output connections. Use GetOutptPtr for output instead!"
<< std::endl;
@ -130,14 +137,18 @@ struct AnimGraphBlendTree : public AnimNode {
// 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];
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;
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;
}
}
}
@ -159,7 +170,8 @@ struct AnimGraphBlendTree : public AnimNode {
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 static_cast<float*>(
*graph_output_connection.m_source_socket.m_reference.ptr_ptr);
}
}
@ -219,7 +231,7 @@ struct AnimGraphBlendTree : public AnimNode {
return nullptr;
}
size_t GetAnimNodeIndex(AnimNode* node) {
size_t GetAnimNodeIndex(const AnimNode* node) const {
for (size_t i = 0; i < m_nodes.size(); i++) {
if (m_nodes[i] == node) {
return i;
@ -230,5 +242,14 @@ struct AnimGraphBlendTree : public AnimNode {
}
};
//
// BlendTreeSocketNode
//
struct BlendTreeSocketNode : public AnimNode {};
template <>
struct NodeDescriptor<BlendTreeSocketNode> : public NodeDescriptorBase {
NodeDescriptor(BlendTreeSocketNode* node_) {}
};
#endif //ANIMTESTBED_ANIMGRAPHBLENDTREE_H

View File

@ -265,6 +265,7 @@ struct AnimGraphConnection {
Socket m_source_socket;
AnimNode* m_target_node = nullptr;
Socket m_target_socket;
bool m_crosses_hierarchy = false;
};

View File

@ -7,14 +7,14 @@
#include <sstream>
#include "3rdparty/imgui-node-editor/imgui_node_editor.h"
#include "AnimGraphBlendTreeResource.h"
#include "AnimGraphResource.h"
#include "SkinnedMesh.h"
#include "imgui.h"
#include "imnodes.h"
#include "misc/cpp/imgui_stdlib.h"
static AnimGraphBlendTreeResource sGraphGresource =
AnimGraphBlendTreeResource();
static AnimGraphResource sGraphGresource =
AnimGraphResource();
static bool sGraphLoadedThisFrame = false;
ImNodesPinShape sGetSocketShapeFromSocketType(const SocketType& socket_type) {
@ -59,13 +59,13 @@ void NodeSocketEditor(Socket& socket) {
}
void RemoveConnectionsForSocket(
AnimGraphBlendTreeResource& graph_resource,
AnimGraphResource& graph_resource,
AnimNodeResource& node_resource,
Socket& socket) {
std::vector<AnimGraphConnectionResource>::iterator iter =
graph_resource.m_connections.begin();
std::vector<BlendTreeConnectionResource>::iterator iter =
graph_resource.m_blend_tree_resource.m_connections.begin();
while (iter != graph_resource.m_connections.end()) {
while (iter != graph_resource.m_blend_tree_resource.m_connections.end()) {
// TODO adjust for refactor
assert(false);

View File

@ -3,13 +3,13 @@
//
#include "AnimGraphNodes.h"
#include "AnimGraphBlendTree.h"
#include "ozz/base/log.h"
#include "ozz/animation/runtime/blending_job.h"
#include "AnimGraphBlendTree.h"
#include "ozz/animation/runtime/animation.h"
#include "ozz/animation/runtime/blending_job.h"
#include "ozz/base/io/archive.h"
#include "ozz/base/io/stream.h"
#include "ozz/base/log.h"
AnimNode* AnimNodeFactory(const std::string& name) {
AnimNode* result;
@ -24,7 +24,7 @@ AnimNode* AnimNodeFactory(const std::string& name) {
} else if (name == "BlendTree") {
result = new AnimGraphBlendTree;
} else if (name == "BlendTreeSockets") {
result = new BlendTreeNode;
result = new BlendTreeSocketNode;
} else if (name == "MathAddNode") {
result = new MathAddNode;
} else if (name == "MathFloatToVec3Node") {
@ -42,6 +42,33 @@ AnimNode* AnimNodeFactory(const std::string& name) {
return nullptr;
}
NodeDescriptorBase* AnimNodeDescriptorFactory(
const std::string& node_type_name,
AnimNode* node) {
if (node_type_name == "Blend2") {
return CreateNodeDescriptor<Blend2Node>(node);
} else if (node_type_name == "SpeedScale") {
return CreateNodeDescriptor<SpeedScaleNode>(node);
} else if (node_type_name == "AnimSampler") {
return CreateNodeDescriptor<AnimSamplerNode>(node);
} else if (node_type_name == "LockTranslationNode") {
return CreateNodeDescriptor<LockTranslationNode>(node);
} else if (node_type_name == "BlendTree") {
return CreateNodeDescriptor<BlendTreeSocketNode>(node);
} else if (node_type_name == "BlendTreeSockets") {
return CreateNodeDescriptor<BlendTreeSocketNode>(node);
} else if (node_type_name == "MathAddNode") {
return CreateNodeDescriptor<MathAddNode>(node);
} else if (node_type_name == "MathFloatToVec3Node") {
return CreateNodeDescriptor<MathFloatToVec3Node>(node);
} else if (node_type_name == "ConstScalarNode") {
return CreateNodeDescriptor<ConstScalarNode>(node);
} else {
std::cerr << "Invalid node type name " << node_type_name << "."
<< std::endl;
}
return nullptr;
}
void Blend2Node::Evaluate(AnimGraphContext& context) {
assert(i_input0 != nullptr);
@ -71,9 +98,7 @@ void Blend2Node::Evaluate(AnimGraphContext& context) {
//
// AnimSamplerNode
//
AnimSamplerNode::~AnimSamplerNode() noexcept {
m_animation = nullptr;
}
AnimSamplerNode::~AnimSamplerNode() noexcept { m_animation = nullptr; }
bool AnimSamplerNode::Init(AnimGraphContext& context) {
assert(m_animation == nullptr);

View File

@ -7,23 +7,13 @@
#include <vector>
#include "AnimNode.h"
#include "AnimGraphData.h"
#include "AnimNode.h"
#include "SyncTrack.h"
#include "ozz/animation/runtime/sampling_job.h"
struct AnimNode;
//
// BlendTreeNode
//
struct BlendTreeNode : public AnimNode {};
template <>
struct NodeDescriptor<BlendTreeNode> : public NodeDescriptorBase {
NodeDescriptor(BlendTreeNode* node_) {}
};
//
// Blend2Node
//
@ -34,8 +24,9 @@ struct Blend2Node : public AnimNode {
float* i_blend_weight = nullptr;
bool m_sync_blend = false;
virtual void MarkActiveInputs() override {
for (const auto & input : m_inputs) {
void MarkActiveInputs(
const std::vector<AnimGraphConnection>& input_connections) override {
for (const auto& input : input_connections) {
AnimNode* input_node = input.m_source_node;
if (input_node == nullptr) {
continue;
@ -53,7 +44,7 @@ struct Blend2Node : public AnimNode {
}
}
virtual void Evaluate(AnimGraphContext& context) override;
void Evaluate(AnimGraphContext& context) override;
};
template <>
@ -80,7 +71,6 @@ struct NodeDescriptor<Blend2Node> : public NodeDescriptorBase {
}
};
//
// SpeedScaleNode
//
@ -116,7 +106,6 @@ struct NodeDescriptor<SpeedScaleNode> : public NodeDescriptorBase {
}
};
//
// AnimSamplerNode
//
@ -172,7 +161,6 @@ struct NodeDescriptor<LockTranslationNode> : public NodeDescriptorBase {
}
};
//
// ConstScalarNode
//
@ -180,9 +168,7 @@ struct ConstScalarNode : public AnimNode {
float* o_value = nullptr;
float value = 0.f;
virtual void Evaluate(AnimGraphContext& context){
*o_value = value;
};
virtual void Evaluate(AnimGraphContext& context) { *o_value = value; };
};
template <>
@ -193,7 +179,6 @@ struct NodeDescriptor<ConstScalarNode> : public NodeDescriptorBase {
}
};
//
// MathAddNode
//
@ -249,35 +234,10 @@ struct NodeDescriptor<MathFloatToVec3Node> : public NodeDescriptorBase {
}
};
AnimNode* AnimNodeFactory(const std::string& name);
static inline NodeDescriptorBase* AnimNodeDescriptorFactory(
NodeDescriptorBase* AnimNodeDescriptorFactory(
const std::string& node_type_name,
AnimNode* node) {
if (node_type_name == "Blend2") {
return CreateNodeDescriptor<Blend2Node>(node);
} else if (node_type_name == "SpeedScale") {
return CreateNodeDescriptor<SpeedScaleNode>(node);
} else if (node_type_name == "AnimSampler") {
return CreateNodeDescriptor<AnimSamplerNode>(node);
} else if (node_type_name == "LockTranslationNode") {
return CreateNodeDescriptor<LockTranslationNode>(node);
} else if (node_type_name == "BlendTree") {
return CreateNodeDescriptor<BlendTreeNode>(node);
} else if (node_type_name == "BlendTreeSockets") {
return CreateNodeDescriptor<BlendTreeNode>(node);
} else if (node_type_name == "MathAddNode") {
return CreateNodeDescriptor<MathAddNode>(node);
} else if (node_type_name == "MathFloatToVec3Node") {
return CreateNodeDescriptor<MathFloatToVec3Node>(node);
} else if (node_type_name == "ConstScalarNode") {
return CreateNodeDescriptor<ConstScalarNode>(node);
} else {
std::cerr << "Invalid node type name " << node_type_name << "."
<< std::endl;
}
return nullptr;
}
AnimNode* node);
#endif //ANIMTESTBED_ANIMGRAPHNODES_H

View File

@ -8,15 +8,17 @@
#include <fstream>
#include "3rdparty/json/json.hpp"
#include "AnimGraphBlendTree.h"
#include "AnimGraphNodes.h"
using json = nlohmann::json;
// forward declarations
static json sAnimGraphResourceBlendTreeToJson(const AnimGraphResource& anim_graph_resource);
static bool sAnimGraphResourceBlendTreeFromJson(const json& json_data, AnimGraphResource* result_graph_resource);
static json sAnimGraphResourceBlendTreeToJson(
const AnimGraphResource& anim_graph_resource);
static bool sAnimGraphResourceBlendTreeFromJson(
const json& json_data,
AnimGraphResource* result_graph_resource);
//
// Socket <-> json
@ -143,7 +145,6 @@ json sAnimGraphNodeToJson(
}
if (node->m_node_type_name == "BlendTree") {
}
for (const auto& socket : node->m_socket_accessor->m_inputs) {
@ -175,7 +176,6 @@ json sAnimGraphNodeToJson(
AnimNodeResource* sAnimGraphNodeFromJson(
const json& json_node,
size_t node_index) {
std::string node_type = json_node["node_type"];
if (node_type == "BlendTree") {
@ -247,20 +247,23 @@ BlendTreeConnectionResource sAnimGraphConnectionFromJson(
return connection;
}
static json sAnimGraphResourceBlendTreeToJson(const AnimGraphResource& anim_graph_resource) {
static json sAnimGraphResourceBlendTreeToJson(
const AnimGraphResource& anim_graph_resource) {
json result;
result["name"] = anim_graph_resource.m_name;
result["type"] = "AnimNodeResource";
result["node_type"] = "BlendTree";
const BlendTreeResource& blend_tree_resource = anim_graph_resource.m_blend_tree_resource;
const BlendTreeResource& blend_tree_resource =
anim_graph_resource.m_blend_tree_resource;
for (size_t i = 0; i < blend_tree_resource.m_nodes.size(); i++) {
const AnimNodeResource* node = blend_tree_resource.m_nodes[i];
if (node->m_node_type_name == "BlendTree") {
const AnimGraphResource* graph_resource = dynamic_cast<const AnimGraphResource*>(node);
const AnimGraphResource* graph_resource =
dynamic_cast<const AnimGraphResource*>(node);
result["nodes"][i] = sAnimGraphResourceBlendTreeToJson(*graph_resource);
} else {
result["nodes"][i] =
@ -276,8 +279,7 @@ static json sAnimGraphResourceBlendTreeToJson(const AnimGraphResource& anim_grap
// Graph inputs and outputs
{
const AnimNodeResource* graph_output_node =
blend_tree_resource.m_nodes[0];
const AnimNodeResource* graph_output_node = blend_tree_resource.m_nodes[0];
const std::vector<Socket> graph_inputs =
graph_output_node->m_socket_accessor->m_inputs;
for (size_t i = 0; i < graph_inputs.size(); i++) {
@ -294,8 +296,11 @@ static json sAnimGraphResourceBlendTreeToJson(const AnimGraphResource& anim_grap
return result;
}
static bool sAnimGraphResourceBlendTreeFromJson(const json& json_data, AnimGraphResource* result_graph_resource) {
BlendTreeResource& blend_tree_resource = result_graph_resource->m_blend_tree_resource;
static bool sAnimGraphResourceBlendTreeFromJson(
const json& json_data,
AnimGraphResource* result_graph_resource) {
BlendTreeResource& blend_tree_resource =
result_graph_resource->m_blend_tree_resource;
result_graph_resource->m_graph_type_name = "BlendTree";
result_graph_resource->m_node_type_name = "BlendTree";
@ -369,21 +374,27 @@ bool BlendTreeResource::ConnectSockets (
Socket* target_socket;
if (target_node->m_node_type_name == "BlendTree") {
const AnimGraphResource* target_graph_resource = dynamic_cast<const AnimGraphResource*>(target_node);
AnimNodeResource* graph_output_node = target_graph_resource->m_blend_tree_resource.GetGraphInputNode();
target_socket = graph_output_node->m_socket_accessor->GetOutputSocket(target_socket_name.c_str());
const AnimGraphResource* target_graph_resource =
dynamic_cast<const AnimGraphResource*>(target_node);
AnimNodeResource* graph_output_node =
target_graph_resource->m_blend_tree_resource.GetGraphInputNode();
target_socket = graph_output_node->m_socket_accessor->GetOutputSocket(
target_socket_name.c_str());
} else {
target_socket =
target_node->m_socket_accessor->GetInputSocket(target_socket_name.c_str());
target_socket = target_node->m_socket_accessor->GetInputSocket(
target_socket_name.c_str());
}
if (source_node->m_node_type_name == "BlendTree") {
const AnimGraphResource* source_graph_resource = dynamic_cast<const AnimGraphResource*>(source_node);
AnimNodeResource* graph_output_node = source_graph_resource->m_blend_tree_resource.GetGraphOutputNode();
source_socket = graph_output_node->m_socket_accessor->GetInputSocket(source_socket_name.c_str());
const AnimGraphResource* source_graph_resource =
dynamic_cast<const AnimGraphResource*>(source_node);
AnimNodeResource* graph_output_node =
source_graph_resource->m_blend_tree_resource.GetGraphOutputNode();
source_socket = graph_output_node->m_socket_accessor->GetInputSocket(
source_socket_name.c_str());
} else {
source_socket =
source_node->m_socket_accessor->GetOutputSocket(source_socket_name.c_str());
source_socket = source_node->m_socket_accessor->GetOutputSocket(
source_socket_name.c_str());
}
if (source_socket == nullptr || target_socket == nullptr) {
@ -443,7 +454,8 @@ bool AnimGraphResource::SaveToFile(const char* filename) const {
return SaveStateMachineResourceToFile(filename);
}
std::cerr << "Invalid AnimGraphResource type: " << m_graph_type_name << "." << std::endl;
std::cerr << "Invalid AnimGraphResource type: " << m_graph_type_name << "."
<< std::endl;
return false;
}
@ -452,8 +464,7 @@ bool AnimGraphResource::SaveBlendTreeResourceToFile(
const char* filename) const {
json result;
result = sAnimGraphResourceBlendTreeToJson(
*this);
result = sAnimGraphResourceBlendTreeToJson(*this);
std::ofstream output_file;
output_file.open(filename);
@ -466,14 +477,18 @@ bool AnimGraphResource::SaveBlendTreeResourceToFile(
void AnimGraphResource::CreateBlendTreeInstance(
AnimGraphBlendTree& result) const {
if (m_node_type_name != "BlendTree") {
std::cerr << "Invalid AnimGraphResource. Expected type 'BlendTree' but got '"
std::cerr
<< "Invalid AnimGraphResource. Expected type 'BlendTree' but got '"
<< m_graph_type_name << "'." << std::endl;
return;
}
result.m_name = m_name;
CreateBlendTreeRuntimeNodeInstances(result);
PrepareBlendTreeIOData(result);
SetRuntimeNodeProperties(result);
CreateBlendTreeConnectionInstances(result);
result.UpdateOrderedNodes();
result.ResetNodeStates();
@ -485,14 +500,19 @@ void AnimGraphResource::CreateBlendTreeRuntimeNodeInstances(
AnimNode* node = AnimNodeFactory(node_resource->m_node_type_name);
if (node_resource->m_node_type_name == "BlendTree") {
AnimGraphResource* embedded_blend_tree_resource = dynamic_cast<AnimGraphResource*>(node_resource);
AnimGraphResource* embedded_blend_tree_resource =
dynamic_cast<AnimGraphResource*>(node_resource);
assert(embedded_blend_tree_resource != nullptr);
AnimGraphBlendTree* embedded_blend_tree = dynamic_cast<AnimGraphBlendTree*>(node);
AnimGraphBlendTree* embedded_blend_tree =
dynamic_cast<AnimGraphBlendTree*>(node);
assert(embedded_blend_tree != nullptr);
embedded_blend_tree_resource->CreateBlendTreeInstance(*embedded_blend_tree);
embedded_blend_tree_resource->m_socket_accessor->m_inputs = embedded_blend_tree->m_node_descriptor->m_outputs;
embedded_blend_tree_resource->m_socket_accessor->m_outputs = embedded_blend_tree->m_node_descriptor->m_inputs;
embedded_blend_tree_resource->CreateBlendTreeInstance(
*embedded_blend_tree);
embedded_blend_tree_resource->m_socket_accessor->m_inputs =
embedded_blend_tree->m_node_descriptor->m_outputs;
embedded_blend_tree_resource->m_socket_accessor->m_outputs =
embedded_blend_tree->m_node_descriptor->m_inputs;
}
node->m_name = node_resource->m_name;
@ -574,7 +594,10 @@ void AnimGraphResource::PrepareBlendTreeIOData(
instance.m_connection_data_storage = new char[connection_data_storage_size];
memset(instance.m_connection_data_storage, 0, connection_data_storage_size);
}
}
void AnimGraphResource::CreateBlendTreeConnectionInstances(
AnimGraphBlendTree& instance) const {
std::vector<NodeDescriptorBase*> instance_node_descriptors(
m_blend_tree_resource.m_nodes.size(),
nullptr);
@ -583,7 +606,8 @@ void AnimGraphResource::PrepareBlendTreeIOData(
m_blend_tree_resource.m_nodes[i]->m_node_type_name,
instance.m_nodes[i]);
if (i > 1 && m_blend_tree_resource.m_nodes[i]->m_node_type_name == "BlendTree") {
if (i > 1
&& m_blend_tree_resource.m_nodes[i]->m_node_type_name == "BlendTree") {
instance_node_descriptors[i]->m_inputs =
m_blend_tree_resource.m_nodes[i]->m_socket_accessor->m_inputs;
instance_node_descriptors[i]->m_outputs =
@ -611,6 +635,72 @@ void AnimGraphResource::PrepareBlendTreeIOData(
connection.target_socket_name.c_str());
AnimGraphConnection instance_connection;
if (source_node->m_node_type_name == "BlendTree") {
// For embedded subgraphs we have to ensure two things:
// 1. The parent graph target node must activate the source node within
// the embedded subgraph.
// 2. The parent graph target node must activate the AnimNode of the
// parent graph which contains the embedded subgraph. Otherwise, the
// embedded graph does not get evaluated.
// For each case we have to add a connection here. First we insert the
// default connection within the graph (i.e. for case 2). Then we alter
// the source node such that it points to the node within the embedded
// subgraph.
AnimGraphConnection embedded_graph_activation_connection;
embedded_graph_activation_connection.m_source_node = source_node;
embedded_graph_activation_connection.m_source_socket = *source_socket;
embedded_graph_activation_connection.m_target_node = target_node;
embedded_graph_activation_connection.m_target_socket = *target_socket;
instance.m_node_input_connections[connection.target_node_index].push_back(
embedded_graph_activation_connection);
instance.m_node_output_connections[connection.source_node_index]
.push_back(embedded_graph_activation_connection);
AnimGraphResource* source_blend_tree_resource =
dynamic_cast<AnimGraphResource*>(
m_blend_tree_resource.m_nodes[connection.source_node_index]);
AnimGraphBlendTree* source_blend_tree =
dynamic_cast<AnimGraphBlendTree*>(source_node);
size_t source_blend_tree_output_node_index =
source_blend_tree_resource->m_blend_tree_resource
.GetNodeIndexForOutputSocket(connection.source_socket_name);
source_node =
source_blend_tree->m_nodes[source_blend_tree_output_node_index];
instance_connection.m_crosses_hierarchy = true;
} else if (target_node->m_node_type_name == "BlendTree") {
// When a connection points to an embedded blend tree we have to ensure
// that the embedded node knows about its connection partner in the parent
// tree. This allows the embedded node to properly activate the node in
// the parent graph.
AnimGraphResource* target_blend_tree_resource =
dynamic_cast<AnimGraphResource*>(
m_blend_tree_resource.m_nodes[connection.target_node_index]);
AnimGraphBlendTree* target_blend_tree =
dynamic_cast<AnimGraphBlendTree*>(target_node);
size_t target_blend_tree_output_node_index =
target_blend_tree_resource->m_blend_tree_resource
.GetNodeIndexForInputSocket(connection.target_socket_name);
target_node =
target_blend_tree->m_nodes[target_blend_tree_output_node_index];
// Make sure that the embedded node input activates the parent graph node.
std::vector<AnimGraphConnection>& embeded_target_node_inputs =
target_blend_tree
->m_node_input_connections[target_blend_tree_output_node_index];
for (size_t j = 0; j < embeded_target_node_inputs.size(); j++) {
AnimGraphConnection& embedded_target_connection =
embeded_target_node_inputs[j];
if (embedded_target_connection.m_source_socket.m_name
== target_socket->m_name) {
embedded_target_connection.m_source_node = source_node;
embedded_target_connection.m_crosses_hierarchy = true;
}
}
}
instance_connection.m_source_node = source_node;
instance_connection.m_source_socket = *source_socket;
instance_connection.m_target_node = target_node;
@ -685,8 +775,9 @@ void AnimGraphResource::SetRuntimeNodeProperties(
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_node_type_name, result.m_nodes[i]);
NodeDescriptorBase* node_instance_accessor = AnimNodeDescriptorFactory(
node_resource->m_node_type_name,
result.m_nodes[i]);
std::vector<Socket>& resource_properties =
node_resource->m_socket_accessor->m_properties;
@ -741,7 +832,8 @@ bool AnimGraphResource::SaveStateMachineResourceToFile(
return false;
}
bool AnimGraphResource::LoadStateMachineResourceFromJson(nlohmann::json const& json_data) {
bool AnimGraphResource::LoadStateMachineResourceFromJson(
nlohmann::json const& json_data) {
assert(false && "Not yet implemented");
return false;

View File

@ -100,6 +100,32 @@ struct BlendTreeResource {
return result;
}
size_t GetNodeIndexForOutputSocket(const std::string& socket_name) const {
for (size_t i = 0; i < m_connections.size(); i++) {
const BlendTreeConnectionResource& connection = m_connections[i];
if (connection.target_node_index == 0 && connection.target_socket_name == socket_name) {
return connection.source_node_index;
}
}
std::cerr << "Error: could not find a node connected to output '" << socket_name << "'." << std::endl;
return -1;
}
size_t GetNodeIndexForInputSocket(const std::string& socket_name) const {
for (size_t i = 0; i < m_connections.size(); i++) {
const BlendTreeConnectionResource& connection = m_connections[i];
if (connection.source_node_index == 1 && connection.source_socket_name == socket_name) {
return connection.target_node_index;
}
}
std::cerr << "Error: could not find a node connected to input '" << socket_name << "'." << std::endl;
return -1;
}
};
struct StateMachineTransitionResources {
@ -131,6 +157,7 @@ struct AnimGraphResource: AnimNodeResource {
bool SaveBlendTreeResourceToFile(const char* filename) const;
void CreateBlendTreeRuntimeNodeInstances(AnimGraphBlendTree& result) const;
void PrepareBlendTreeIOData(AnimGraphBlendTree& instance) const;
void CreateBlendTreeConnectionInstances(AnimGraphBlendTree& instance) const;
void SetRuntimeNodeProperties(AnimGraphBlendTree& result) const;
bool SaveStateMachineResourceToFile(const char* filename) const;

View File

@ -8,11 +8,11 @@ bool AnimGraphStateMachine::Init(AnimGraphContext& context) {
}
void AnimGraphStateMachine::MarkActiveInputs() {
void AnimGraphStateMachine::MarkActiveInputs(const std::vector<AnimGraphConnection>& input_connections) {
}
void AnimGraphStateMachine::CalcSyncTrack() {
void AnimGraphStateMachine::CalcSyncTrack(const std::vector<AnimGraphConnection>& input_connections) {
}

View File

@ -25,8 +25,8 @@ struct AnimGraphStateMachine : public AnimNode {
Transition* m_active_transition = nullptr;
bool Init(AnimGraphContext& context);
void MarkActiveInputs() override;
void CalcSyncTrack() override;
void MarkActiveInputs(const std::vector<AnimGraphConnection>& input_connections) override;
void CalcSyncTrack(const std::vector<AnimGraphConnection>& input_connections) override;
void UpdateTime(float time_last, float time_now) override;
void Evaluate(AnimGraphContext& context) override;
};

View File

@ -8,8 +8,8 @@
#include <string>
#include <vector>
#include "SyncTrack.h"
#include "AnimGraphData.h"
#include "SyncTrack.h"
struct AnimNode;
@ -25,29 +25,35 @@ enum class AnimNodeEvalState {
struct AnimNode {
std::string m_name;
std::string m_node_type_name;
int m_tick_number = 0;
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 bool Init(AnimGraphContext& context) {
m_time_now = 0.f;
m_time_last = 0.f;
return true;
};
virtual void MarkActiveInputs() {
for (const auto & input : m_inputs) {
virtual void MarkActiveInputs(
const std::vector<AnimGraphConnection>& input_connections) {
for (const auto& input : input_connections) {
AnimNode* input_node = input.m_source_node;
if (input_node != nullptr) {
input_node->m_tick_number = m_tick_number;
input_node->m_state = AnimNodeEvalState::Activated;
}
}
}
virtual void CalcSyncTrack() {
for (const auto & input : m_inputs) {
virtual void CalcSyncTrack(
const std::vector<AnimGraphConnection>& input_connections) {
for (const auto& input : input_connections) {
AnimNode* input_node = input.m_source_node;
if (input_node != nullptr
&& input.m_source_socket.m_type == SocketType::SocketTypeAnimation

View File

@ -140,7 +140,9 @@ class EmbeddedBlendTreeGraphResource {
BlendTreeResource* embedded_blend_tree_resource = nullptr;
size_t walk_node_index = -1;
AnimNodeResource* walk_node = nullptr;
AnimNodeResource* walk_node_resource = nullptr;
size_t embedded_blend_tree_node_index = -1;
size_t embedded_speed_scale_index = -1;
public:
EmbeddedBlendTreeGraphResource() {
@ -163,10 +165,11 @@ class EmbeddedBlendTreeGraphResource {
// Parent AnimSampler
parent_blend_tree_resource->m_nodes.push_back(
AnimNodeResourceFactory("AnimSampler"));
walk_node_index = parent_blend_tree_resource->m_nodes.size() - 1;
walk_node = parent_blend_tree_resource->m_nodes.back();
walk_node->m_name = "WalkAnim";
walk_node->m_socket_accessor->SetPropertyValue(
walk_node_resource = parent_blend_tree_resource->m_nodes[walk_node_index];
walk_node_resource->m_name = "WalkAnim";
walk_node_resource->m_socket_accessor->SetPropertyValue(
"Filename",
std::string("media/Walking-loop.ozz"));
@ -175,7 +178,7 @@ class EmbeddedBlendTreeGraphResource {
//
parent_blend_tree_resource->m_nodes.push_back(
AnimNodeResourceFactory("BlendTree"));
size_t embedded_blend_tree_node_index =
embedded_blend_tree_node_index =
parent_blend_tree_resource->m_nodes.size() - 1;
embedded_graph = dynamic_cast<AnimGraphResource*>(
parent_blend_tree_resource->m_nodes.back());
@ -201,9 +204,13 @@ class EmbeddedBlendTreeGraphResource {
// Embedded: SpeedScale node
embedded_blend_tree_resource->m_nodes.push_back(
AnimNodeResourceFactory("SpeedScale"));
embedded_speed_scale_index =
embedded_blend_tree_resource->m_nodes.size() - 1;
AnimNodeResource* embedded_speed_scale_resource =
embedded_blend_tree_resource->m_nodes.back();
embedded_speed_scale_resource->m_socket_accessor->SetInputValue("SpeedScale", 0.1f);
embedded_blend_tree_resource->m_nodes[embedded_speed_scale_index];
embedded_speed_scale_resource->m_socket_accessor->SetInputValue(
"SpeedScale",
0.1f);
// Embedded: setup connections
embedded_blend_tree_resource->ConnectSockets(
@ -219,7 +226,7 @@ class EmbeddedBlendTreeGraphResource {
// Parent: setup connections
REQUIRE(parent_blend_tree_resource->ConnectSockets(
walk_node,
walk_node_resource,
"Output",
embedded_graph,
"AnimInput"));
@ -346,8 +353,8 @@ TEST_CASE_METHOD(
AnimSamplerNode* anim_sampler_walk =
dynamic_cast<AnimSamplerNode*>(anim_graph_blend_tree.m_nodes[2]);
BlendTreeNode* graph_output_node =
dynamic_cast<BlendTreeNode*>(anim_graph_blend_tree.m_nodes[0]);
BlendTreeSocketNode* graph_output_node =
dynamic_cast<BlendTreeSocketNode*>(anim_graph_blend_tree.m_nodes[0]);
// check node input dependencies
size_t anim_sampler_index =
@ -937,7 +944,8 @@ TEST_CASE_METHOD(
parent_blend_tree_resource->m_nodes.size()
== parent_blend_tree_resource_loaded.m_nodes.size());
for (size_t i = 0; i < parent_blend_tree_resource->m_nodes.size(); i++) {
const AnimNodeResource* parent_node = parent_blend_tree_resource->m_nodes[i];
const AnimNodeResource* parent_node =
parent_blend_tree_resource->m_nodes[i];
const AnimNodeResource* parent_node_loaded =
parent_blend_tree_resource_loaded.m_nodes[i];
@ -949,7 +957,8 @@ TEST_CASE_METHOD(
CHECK(
parent_blend_tree_resource->m_connections.size()
== parent_blend_tree_resource_loaded.m_connections.size());
for (size_t i = 0; i < parent_blend_tree_resource->m_connections.size(); i++) {
for (size_t i = 0; i < parent_blend_tree_resource->m_connections.size();
i++) {
const BlendTreeConnectionResource& parent_connection =
parent_blend_tree_resource->m_connections[i];
const BlendTreeConnectionResource& parent_connection_loaded =
@ -1039,22 +1048,61 @@ TEST_CASE_METHOD(
graph_context.m_skeleton = &skeleton;
blend_tree.Init(graph_context);
// Marking of active inputs is not properly working as we do not properly
// populate AnimNode::m_inputs.
//
// Here comes an iffy problem: How to mark nodes in a parent blend tree
// as active?
//
// - Simplest would be if the AnimNodes don't distinguish between nodes within
// the tree they are contained or in a possible parent (or nested) tree.
// - But then how to propagate active connections across multiple layers?
// - Therefore probably better to have a blend tree store which sockets are
// active and use that within the graph.
// - But then: AnimGraphConnection already stores raw AnimNode pointers of the
// connected nodes. Still... populating them accross layers could be messy.
blend_tree.MarkActiveInputs();
blend_tree.UpdateTime(0.f, 0.1f);
const AnimSamplerNode* walk_node =
dynamic_cast<AnimSamplerNode*>(blend_tree.m_nodes[walk_node_index]);
const AnimGraphBlendTree* embedded_blend_tree_node =
dynamic_cast<AnimGraphBlendTree*>(
blend_tree.m_nodes[embedded_blend_tree_node_index]);
const SpeedScaleNode* speed_scale_node = dynamic_cast<SpeedScaleNode*>(
embedded_blend_tree_node->m_nodes[walk_node_index]);
blend_tree.StartUpdateTick();
blend_tree.MarkActiveInputs(std::vector<AnimGraphConnection>());
REQUIRE(embedded_blend_tree_node->m_state == AnimNodeEvalState::Activated);
REQUIRE(speed_scale_node->m_state == AnimNodeEvalState::Activated);
REQUIRE(walk_node->m_state == AnimNodeEvalState::Activated);
float time_last = 0.f;
float dt = 0.1f;
blend_tree.UpdateTime(time_last, time_last + dt);
CHECK(embedded_blend_tree_node->m_state == AnimNodeEvalState::TimeUpdated);
CHECK(speed_scale_node->m_state == AnimNodeEvalState::TimeUpdated);
CHECK(walk_node->m_state == AnimNodeEvalState::TimeUpdated);
CHECK_THAT(
walk_node->m_time_last,
Catch::Matchers::WithinAbs(time_last, 0.001));
CHECK_THAT(
walk_node->m_time_now,
Catch::Matchers::WithinAbs(
time_last + dt * (*speed_scale_node->i_speed_scale),
0.001));
blend_tree.Evaluate(graph_context);
WHEN("Updating the time a second time") {
// Perform another update
time_last = time_last + dt;
dt = 0.3f;
blend_tree.StartUpdateTick();
blend_tree.MarkActiveInputs(std::vector<AnimGraphConnection>());
blend_tree.UpdateTime(time_last, time_last + dt);
CHECK(embedded_blend_tree_node->m_state == AnimNodeEvalState::TimeUpdated);
CHECK(speed_scale_node->m_state == AnimNodeEvalState::TimeUpdated);
CHECK(walk_node->m_state == AnimNodeEvalState::TimeUpdated);
CHECK_THAT(
walk_node->m_time_last,
Catch::Matchers::WithinAbs(
time_last * (*speed_scale_node->i_speed_scale),
0.001));
CHECK_THAT(
walk_node->m_time_now,
Catch::Matchers::WithinAbs(
(time_last + dt) * (*speed_scale_node->i_speed_scale),
0.001));
}
graph_context.freeAnimations();
}