Removed old animation graph code.
This commit is contained in:
		
							parent
							
								
									72a67195e6
								
							
						
					
					
						commit
						6c0c0599f8
					
				@ -48,20 +48,18 @@ add_library(AnimTestbedCode OBJECT
 | 
			
		||||
        src/SkinnedMesh.h
 | 
			
		||||
        src/SyncTrack.cc
 | 
			
		||||
        src/SyncTrack.h
 | 
			
		||||
        src/AnimNode.cc
 | 
			
		||||
        src/AnimNodes/AnimSamplerNode.cc
 | 
			
		||||
        src/AnimNodes/SpeedScaleNode.cc
 | 
			
		||||
        src/AnimNodes/BlendSpace1D.cc
 | 
			
		||||
        src/AnimNodes/BlendNode.cc
 | 
			
		||||
        src/AnimNodes/LockTranslationNode.cc
 | 
			
		||||
        src/AnimationController.cc
 | 
			
		||||
        src/ozzutils.cc
 | 
			
		||||
        3rdparty/imgui/imgui.cpp
 | 
			
		||||
        3rdparty/imgui/imgui_draw.cpp
 | 
			
		||||
        3rdparty/imgui/imgui_widgets.cpp
 | 
			
		||||
        3rdparty/imgui/misc/cpp/imgui_stdlib.cpp
 | 
			
		||||
        3rdparty/imnodes/imnodes.cpp
 | 
			
		||||
        src/AnimGraphResource.cc
 | 
			
		||||
        src/AnimGraphResource.h src/AnimGraphEditor.cc src/AnimGraphEditor.h)
 | 
			
		||||
        src/AnimGraph/AnimGraphResource.cc
 | 
			
		||||
        src/AnimGraph/AnimGraphResource.h
 | 
			
		||||
        src/AnimGraph/AnimGraphEditor.cc
 | 
			
		||||
        src/AnimGraph/AnimGraphEditor.h
 | 
			
		||||
        src/AnimGraph/AnimGraph.cc
 | 
			
		||||
        src/AnimGraph/AnimGraph.h src/AnimGraph/AnimGraphNodes.cc src/AnimGraph/AnimGraphNodes.h src/AnimGraph/AnimGraphData.cc src/AnimGraph/AnimGraphData.h)
 | 
			
		||||
 | 
			
		||||
target_include_directories(
 | 
			
		||||
        AnimTestbedCode
 | 
			
		||||
@ -96,7 +94,6 @@ add_executable(runtests)
 | 
			
		||||
 | 
			
		||||
target_sources(runtests PRIVATE
 | 
			
		||||
        tests/AnimGraphResourceTests.cc
 | 
			
		||||
        tests/AnimSampleNodeTests.cc
 | 
			
		||||
        tests/SyncTrackTests.cc
 | 
			
		||||
        tests/main.cc
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										147
									
								
								src/AnimGraph/AnimGraph.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								src/AnimGraph/AnimGraph.cc
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,147 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by martin on 25.03.22.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "AnimGraph.h"
 | 
			
		||||
 | 
			
		||||
void AnimGraph::updateOrderedNodes() {
 | 
			
		||||
  std::vector<int> node_index_stack;
 | 
			
		||||
  node_index_stack.push_back(0);
 | 
			
		||||
 | 
			
		||||
  m_eval_ordered_nodes.clear();
 | 
			
		||||
 | 
			
		||||
  while (node_index_stack.size() > 0) {
 | 
			
		||||
    std::vector<NodeInput>& node_inputs =
 | 
			
		||||
        m_node_inputs[node_index_stack.back()];
 | 
			
		||||
    node_index_stack.pop_back();
 | 
			
		||||
 | 
			
		||||
    for (size_t i = 0, n = node_inputs.size(); i < n; i++) {
 | 
			
		||||
      AnimNode* input_node = node_inputs[i].m_node;
 | 
			
		||||
      if (input_node == nullptr) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      int input_node_index = input_node->m_index;
 | 
			
		||||
      bool is_node_processed = false;
 | 
			
		||||
      for (size_t j = 0, m = m_eval_ordered_nodes.size(); j < m; j++) {
 | 
			
		||||
        if (m_eval_ordered_nodes[j] == input_node) {
 | 
			
		||||
          is_node_processed = true;
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (is_node_processed) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      m_eval_ordered_nodes.push_back(input_node);
 | 
			
		||||
      node_index_stack.push_back(input_node_index);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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<NodeInput> graph_output_inputs = m_node_inputs[0];
 | 
			
		||||
  for (size_t i = 0, n = graph_output_inputs.size(); i < n; i++) {
 | 
			
		||||
    const NodeInput& graph_input = graph_output_inputs[i];
 | 
			
		||||
    AnimNode* node = graph_input.m_node;
 | 
			
		||||
    if (node != nullptr) {
 | 
			
		||||
      node->m_state = AnimNodeEvalState::Activated;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  for (size_t i = 0, n = m_eval_ordered_nodes.size(); i < n; i++) {
 | 
			
		||||
    AnimNode* node = m_eval_ordered_nodes[i];
 | 
			
		||||
    if (checkIsNodeActive(node)) {
 | 
			
		||||
      int node_index = node->m_index;
 | 
			
		||||
      node->MarkActiveInputs(m_node_inputs[node_index]);
 | 
			
		||||
 | 
			
		||||
      // Non-animation data inputs are always active.
 | 
			
		||||
      for (size_t j = 0, nj = m_node_inputs[node_index].size(); j < nj; j++) {
 | 
			
		||||
        const NodeInput& input = m_node_inputs[node_index][j];
 | 
			
		||||
        if (input.m_node != nullptr && input.m_type != SocketType::SocketTypeAnimation) {
 | 
			
		||||
          input.m_node->m_state = AnimNodeEvalState::Activated;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnimGraph::evalSyncTracks() {
 | 
			
		||||
  for (size_t i = m_eval_ordered_nodes.size() - 1; i >= 0; i--) {
 | 
			
		||||
    AnimNode* node = m_eval_ordered_nodes[i];
 | 
			
		||||
    int node_index = node->m_index;
 | 
			
		||||
    if (node->m_state == AnimNodeEvalState::Deactivated) {
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    node->CalcSyncTrack(m_node_inputs[node_index]);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnimGraph::updateTime(float dt) {
 | 
			
		||||
  const std::vector<NodeInput> graph_output_inputs = m_node_inputs[0];
 | 
			
		||||
  for (size_t i = 0, n = graph_output_inputs.size(); i < n; i++) {
 | 
			
		||||
    AnimNode* node = m_eval_ordered_nodes[i];
 | 
			
		||||
    if (node != nullptr) {
 | 
			
		||||
      node->UpdateTime(node->m_time_now, node->m_time_now + dt);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  for (size_t i = 0, n = m_eval_ordered_nodes.size(); i < n; i++) {
 | 
			
		||||
    AnimNode* node = m_eval_ordered_nodes[i];
 | 
			
		||||
    if (node->m_state != AnimNodeEvalState::TimeUpdated) {
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    int node_index = node->m_index;
 | 
			
		||||
    const std::vector<NodeInput> node_inputs =
 | 
			
		||||
        m_node_inputs[node_index];
 | 
			
		||||
    float node_time_now = node->m_time_now;
 | 
			
		||||
    float node_time_last = node->m_time_last;
 | 
			
		||||
 | 
			
		||||
    for (size_t i = 0, n = node_inputs.size(); i < n; i++) {
 | 
			
		||||
      AnimNode* input_node = node_inputs[i].m_node;
 | 
			
		||||
 | 
			
		||||
      // Only propagate time updates via animation sockets.
 | 
			
		||||
      if (input_node != nullptr
 | 
			
		||||
          && node_inputs[i].m_type == SocketType::SocketTypeAnimation
 | 
			
		||||
          && input_node->m_state == AnimNodeEvalState::Activated) {
 | 
			
		||||
        input_node->UpdateTime(node_time_last, node_time_now);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnimGraph::evaluate() {
 | 
			
		||||
  for (size_t i = m_eval_ordered_nodes.size() - 1; i >= 0; i--) {
 | 
			
		||||
    AnimNode* node = m_eval_ordered_nodes[i];
 | 
			
		||||
    if (node->m_state == AnimNodeEvalState::Deactivated) {
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    node->Evaluate();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void* AnimGraph::getOutput(const std::string& name) const {
 | 
			
		||||
  Socket* socket = m_socket_accessor->FindInputSocket(name);
 | 
			
		||||
  if (socket == nullptr) {
 | 
			
		||||
    return nullptr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return socket->m_value.ptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void* AnimGraph::getInput(const std::string& name) const {
 | 
			
		||||
  Socket* socket = m_socket_accessor->FindOutputSocket(name);
 | 
			
		||||
  if (socket == nullptr) {
 | 
			
		||||
    return nullptr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return *(socket->m_value.ptr_ptr);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										291
									
								
								src/AnimGraph/AnimGraph.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										291
									
								
								src/AnimGraph/AnimGraph.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,291 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by martin on 25.03.22.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef ANIMTESTBED_ANIMGRAPH_H
 | 
			
		||||
#define ANIMTESTBED_ANIMGRAPH_H
 | 
			
		||||
 | 
			
		||||
#include "AnimGraphResource.h"
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// AnimGraph (Runtime)
 | 
			
		||||
//
 | 
			
		||||
struct AnimGraph {
 | 
			
		||||
  AnimData m_local_transforms;
 | 
			
		||||
 | 
			
		||||
  std::vector<AnimNode*> m_nodes;
 | 
			
		||||
  std::vector<AnimNode*> m_eval_ordered_nodes;
 | 
			
		||||
  std::vector<std::vector<NodeInput> > m_node_inputs;
 | 
			
		||||
  NodeSocketAccessorBase* m_socket_accessor;
 | 
			
		||||
  char* m_input_buffer = nullptr;
 | 
			
		||||
  char* m_output_buffer = nullptr;
 | 
			
		||||
 | 
			
		||||
  std::vector<Socket>& getGraphOutputs() { return m_socket_accessor->m_inputs; }
 | 
			
		||||
  std::vector<Socket>& getGraphInputs() { return m_socket_accessor->m_outputs; }
 | 
			
		||||
 | 
			
		||||
  ~AnimGraph() {
 | 
			
		||||
    delete[] m_input_buffer;
 | 
			
		||||
    delete[] m_output_buffer;
 | 
			
		||||
 | 
			
		||||
    for (int i = 0; i < m_nodes.size(); i++) {
 | 
			
		||||
      delete m_nodes[i];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    delete m_socket_accessor;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void updateOrderedNodes();
 | 
			
		||||
  void markActiveNodes();
 | 
			
		||||
  bool checkIsNodeActive(AnimNode* node) {
 | 
			
		||||
    return node->m_state != AnimNodeEvalState::Deactivated;
 | 
			
		||||
  }
 | 
			
		||||
  void evalSyncTracks();
 | 
			
		||||
  void updateTime(float dt);
 | 
			
		||||
  void evaluate();
 | 
			
		||||
  void reset() {
 | 
			
		||||
    for (size_t i = 0, n = m_nodes.size(); i < n; i++) {
 | 
			
		||||
      m_nodes[i]->m_time_now = 0.f;
 | 
			
		||||
      m_nodes[i]->m_time_last = 0.f;
 | 
			
		||||
      m_nodes[i]->m_state = AnimNodeEvalState::Undefined;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void* getOutput(const std::string& name) const;
 | 
			
		||||
  void* getInput(const std::string& name) const;
 | 
			
		||||
 | 
			
		||||
  int getNodeEvalOrderIndex(const AnimNode* node) {
 | 
			
		||||
    for (size_t i = 0, n = m_eval_ordered_nodes.size(); i < n; i++) {
 | 
			
		||||
      if (m_eval_ordered_nodes[i] == node) {
 | 
			
		||||
        return i;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return -1;
 | 
			
		||||
  }
 | 
			
		||||
  AnimNode* getAnimNodeForInput(
 | 
			
		||||
      size_t node_index,
 | 
			
		||||
      const std::string& input_name) {
 | 
			
		||||
    assert(node_index < m_nodes.size());
 | 
			
		||||
    assert(node_index < m_node_inputs.size());
 | 
			
		||||
 | 
			
		||||
    std::vector<NodeInput>& node_inputs = m_node_inputs[node_index];
 | 
			
		||||
    for (size_t i = 0, n = node_inputs.size(); i < n; i++) {
 | 
			
		||||
      if (node_inputs[i].m_input_name == input_name) {
 | 
			
		||||
        return node_inputs[i].m_node;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return nullptr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  AnimNode* getAnimNode(const char* name) {
 | 
			
		||||
    for (size_t i = 0; i < m_nodes.size(); i++) {
 | 
			
		||||
      if (m_nodes[i]->m_name == name) {
 | 
			
		||||
        return m_nodes[i];
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return nullptr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static AnimGraph createFromResource(const AnimGraphResource& resource) {
 | 
			
		||||
    AnimGraph result;
 | 
			
		||||
 | 
			
		||||
    // create nodes
 | 
			
		||||
    for (int i = 0; i < resource.m_nodes.size(); i++) {
 | 
			
		||||
      const AnimNodeResource& node_resource = resource.m_nodes[i];
 | 
			
		||||
      AnimNode* node = AnimNodeFactory(node_resource.m_type_name.c_str());
 | 
			
		||||
      node->m_name = node_resource.m_name;
 | 
			
		||||
      node->m_node_type_name = node_resource.m_type_name;
 | 
			
		||||
      node->m_index = i;
 | 
			
		||||
      result.m_nodes.push_back(node);
 | 
			
		||||
 | 
			
		||||
      assert(node_resource.m_socket_accessor != nullptr);
 | 
			
		||||
      result.m_node_inputs.push_back(std::vector<NodeInput>());
 | 
			
		||||
      std::vector<NodeInput>& node_inputs = result.m_node_inputs.back();
 | 
			
		||||
 | 
			
		||||
      for (int j = 0, n = node_resource.m_socket_accessor->m_inputs.size();
 | 
			
		||||
           j < n;
 | 
			
		||||
           j++) {
 | 
			
		||||
        const Socket& input_socket =
 | 
			
		||||
            node_resource.m_socket_accessor->m_inputs[j];
 | 
			
		||||
 | 
			
		||||
        NodeInput input;
 | 
			
		||||
        input.m_node = nullptr;
 | 
			
		||||
        input.m_type = input_socket.m_type;
 | 
			
		||||
        input.m_input_name = input_socket.m_name;
 | 
			
		||||
 | 
			
		||||
        node_inputs.push_back(input);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Prepare graph inputs
 | 
			
		||||
    result.m_socket_accessor =
 | 
			
		||||
        AnimNodeAccessorFactory("BlendTree", result.m_nodes[0]);
 | 
			
		||||
    result.m_socket_accessor->m_outputs =
 | 
			
		||||
        resource.m_nodes[1].m_socket_accessor->m_outputs;
 | 
			
		||||
    result.m_socket_accessor->m_inputs =
 | 
			
		||||
        resource.m_nodes[0].m_socket_accessor->m_inputs;
 | 
			
		||||
 | 
			
		||||
    // inputs
 | 
			
		||||
    int input_block_size = 0;
 | 
			
		||||
    std::vector<Socket>& graph_inputs = result.getGraphInputs();
 | 
			
		||||
    for (int i = 0; i < graph_inputs.size(); i++) {
 | 
			
		||||
      input_block_size += sizeof(void*);
 | 
			
		||||
    }
 | 
			
		||||
    result.m_input_buffer = new char[input_block_size];
 | 
			
		||||
    memset(result.m_input_buffer, 0, input_block_size);
 | 
			
		||||
 | 
			
		||||
    int input_block_offset = 0;
 | 
			
		||||
    for (int i = 0; i < graph_inputs.size(); i++) {
 | 
			
		||||
      if (graph_inputs[i].m_type == SocketType::SocketTypeAnimation) {
 | 
			
		||||
      }
 | 
			
		||||
      graph_inputs[i].m_value.ptr =
 | 
			
		||||
          (void*)&result.m_input_buffer[input_block_offset];
 | 
			
		||||
      input_block_offset += sizeof(void*);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // outputs
 | 
			
		||||
    int output_block_size = 0;
 | 
			
		||||
    std::vector<Socket>& graph_outputs = result.getGraphOutputs();
 | 
			
		||||
    for (int i = 0; i < graph_outputs.size(); i++) {
 | 
			
		||||
      output_block_size += graph_outputs[i].m_type_size;
 | 
			
		||||
    }
 | 
			
		||||
    result.m_output_buffer = new char[output_block_size];
 | 
			
		||||
    memset(result.m_output_buffer, 0, output_block_size);
 | 
			
		||||
 | 
			
		||||
    int output_block_offset = 0;
 | 
			
		||||
    for (int i = 0; i < graph_outputs.size(); i++) {
 | 
			
		||||
      if (graph_outputs[i].m_type == SocketType::SocketTypeAnimation) {
 | 
			
		||||
      }
 | 
			
		||||
      graph_outputs[i].m_value.ptr =
 | 
			
		||||
          (void*)&result.m_output_buffer[output_block_offset];
 | 
			
		||||
      output_block_offset += graph_outputs[i].m_type_size;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // connect the nodes
 | 
			
		||||
    for (int i = 0; i < resource.m_connections.size(); i++) {
 | 
			
		||||
      const AnimGraphConnection& connection = resource.m_connections[i];
 | 
			
		||||
      std::string source_node_type = "";
 | 
			
		||||
      std::string target_node_type = "";
 | 
			
		||||
      std::string source_node_name = "";
 | 
			
		||||
      std::string target_node_name = "";
 | 
			
		||||
      AnimNode* source_node = nullptr;
 | 
			
		||||
      AnimNode* target_node = nullptr;
 | 
			
		||||
      NodeSocketAccessorBase* source_node_accessor = nullptr;
 | 
			
		||||
      NodeSocketAccessorBase* target_node_accessor = nullptr;
 | 
			
		||||
      SocketType source_type;
 | 
			
		||||
      SocketType target_type;
 | 
			
		||||
      size_t source_socket_index = -1;
 | 
			
		||||
      size_t target_socket_index = -1;
 | 
			
		||||
 | 
			
		||||
      if (connection.m_source_node != nullptr) {
 | 
			
		||||
        size_t node_index = resource.getNodeIndex(*connection.m_source_node);
 | 
			
		||||
        if (node_index == -1) {
 | 
			
		||||
          std::cerr << "Could not find source node index." << std::endl;
 | 
			
		||||
          continue;
 | 
			
		||||
        }
 | 
			
		||||
        source_node = result.m_nodes[node_index];
 | 
			
		||||
        source_node_name = source_node->m_name;
 | 
			
		||||
        source_node_type = source_node->m_node_type_name;
 | 
			
		||||
        if (node_index == 1) {
 | 
			
		||||
          source_node_accessor = result.m_socket_accessor;
 | 
			
		||||
        } else {
 | 
			
		||||
          source_node_accessor =
 | 
			
		||||
              AnimNodeAccessorFactory(source_node_type, source_node);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (connection.m_target_node != nullptr) {
 | 
			
		||||
        size_t node_index = resource.getNodeIndex(*connection.m_target_node);
 | 
			
		||||
        if (node_index == -1) {
 | 
			
		||||
          std::cerr << "Could not find source node index." << std::endl;
 | 
			
		||||
          continue;
 | 
			
		||||
        }
 | 
			
		||||
        target_node = result.m_nodes[node_index];
 | 
			
		||||
        target_node_name = target_node->m_name;
 | 
			
		||||
        target_node_type = target_node->m_node_type_name;
 | 
			
		||||
        if (node_index == 0) {
 | 
			
		||||
          target_node_accessor = result.m_socket_accessor;
 | 
			
		||||
        } else {
 | 
			
		||||
          target_node_accessor =
 | 
			
		||||
              AnimNodeAccessorFactory(target_node_type, target_node);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      assert(source_node != nullptr);
 | 
			
		||||
      assert(target_node != nullptr);
 | 
			
		||||
 | 
			
		||||
      //
 | 
			
		||||
      // Map resource node sockets to graph instance node sockets
 | 
			
		||||
      //
 | 
			
		||||
      if (connection.m_source_socket == nullptr) {
 | 
			
		||||
        std::cerr << "Invalid source socket for connection " << i << "."
 | 
			
		||||
                  << std::endl;
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (connection.m_target_socket == nullptr) {
 | 
			
		||||
        std::cerr << "Invalid source socket for connection " << i << "."
 | 
			
		||||
                  << std::endl;
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      source_socket_index = source_node_accessor->GetOutputIndex(
 | 
			
		||||
          connection.m_source_socket->m_name);
 | 
			
		||||
      if (source_socket_index == -1) {
 | 
			
		||||
        std::cerr << "Invalid source socket "
 | 
			
		||||
                  << connection.m_source_socket->m_name << " for node "
 | 
			
		||||
                  << connection.m_source_node->m_name << "." << std::endl;
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      const Socket* source_socket =
 | 
			
		||||
          &source_node_accessor->m_outputs[source_socket_index];
 | 
			
		||||
 | 
			
		||||
      target_socket_index = target_node_accessor->GetInputIndex(
 | 
			
		||||
          connection.m_target_socket->m_name);
 | 
			
		||||
      if (target_socket_index == -1) {
 | 
			
		||||
        std::cerr << "Invalid target socket "
 | 
			
		||||
                  << connection.m_target_socket->m_name << " for node "
 | 
			
		||||
                  << connection.m_target_node->m_name << "." << std::endl;
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      const Socket* target_socket =
 | 
			
		||||
          &target_node_accessor->m_inputs[target_socket_index];
 | 
			
		||||
 | 
			
		||||
      if (source_socket->m_type != target_socket->m_type) {
 | 
			
		||||
        std::cerr << "Cannot connect sockets: invalid types!" << std::endl;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      //
 | 
			
		||||
      // Wire up outputs to inputs.
 | 
			
		||||
      //
 | 
			
		||||
      (*source_socket->m_value.ptr_ptr) = target_socket->m_value.ptr;
 | 
			
		||||
 | 
			
		||||
      size_t target_node_index = target_node->m_index;
 | 
			
		||||
 | 
			
		||||
      std::vector<NodeInput>& node_inputs =
 | 
			
		||||
          result.m_node_inputs[target_node_index];
 | 
			
		||||
      for (int j = 0, n = node_inputs.size(); j < n; j++) {
 | 
			
		||||
        if (node_inputs[j].m_input_name == target_socket->m_name) {
 | 
			
		||||
          node_inputs[j].m_node = source_node;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (target_node_accessor != result.m_socket_accessor) {
 | 
			
		||||
        delete target_node_accessor;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (source_node_accessor != result.m_socket_accessor) {
 | 
			
		||||
        delete source_node_accessor;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    result.updateOrderedNodes();
 | 
			
		||||
    result.reset();
 | 
			
		||||
 | 
			
		||||
    return result;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif  //ANIMTESTBED_ANIMGRAPH_H
 | 
			
		||||
							
								
								
									
										5
									
								
								src/AnimGraph/AnimGraphData.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/AnimGraph/AnimGraphData.cc
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by martin on 25.03.22.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "AnimGraphData.h"
 | 
			
		||||
							
								
								
									
										244
									
								
								src/AnimGraph/AnimGraphData.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										244
									
								
								src/AnimGraph/AnimGraphData.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,244 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by martin on 25.03.22.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef ANIMTESTBED_ANIMGRAPHDATA_H
 | 
			
		||||
#define ANIMTESTBED_ANIMGRAPHDATA_H
 | 
			
		||||
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include <iostream>
 | 
			
		||||
 | 
			
		||||
#include "SyncTrack.h"
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// Data types
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
struct AnimData {
 | 
			
		||||
  float m_bone_transforms[16];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
typedef float Vec3[3];
 | 
			
		||||
typedef float Quat[4];
 | 
			
		||||
 | 
			
		||||
enum class SocketType {
 | 
			
		||||
  SocketTypeUndefined = 0,
 | 
			
		||||
  SocketTypeBool,
 | 
			
		||||
  SocketTypeAnimation,
 | 
			
		||||
  SocketTypeFloat,
 | 
			
		||||
  SocketTypeVec3,
 | 
			
		||||
  SocketTypeQuat,
 | 
			
		||||
  SocketTypeString,
 | 
			
		||||
  SocketTypeLast
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const char* SocketTypeNames[] =
 | 
			
		||||
    {"", "Bool", "Animation", "Float", "Vec3", "Quat", "String"};
 | 
			
		||||
 | 
			
		||||
enum SocketFlags { SocketFlagAffectsTime = 1 };
 | 
			
		||||
 | 
			
		||||
struct Socket {
 | 
			
		||||
  std::string m_name;
 | 
			
		||||
  SocketType m_type = SocketType::SocketTypeUndefined;
 | 
			
		||||
  union SocketValue {
 | 
			
		||||
    void* ptr;
 | 
			
		||||
    void** ptr_ptr;
 | 
			
		||||
  };
 | 
			
		||||
  SocketValue m_value = {nullptr};
 | 
			
		||||
  int m_flags = 0;
 | 
			
		||||
  size_t m_type_size = 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct NodeSocketAccessorBase {
 | 
			
		||||
  std::vector<Socket> m_properties;
 | 
			
		||||
  std::vector<Socket> m_inputs;
 | 
			
		||||
  std::vector<Socket> m_outputs;
 | 
			
		||||
 | 
			
		||||
  NodeSocketAccessorBase() {}
 | 
			
		||||
  virtual ~NodeSocketAccessorBase() {}
 | 
			
		||||
 | 
			
		||||
  virtual void UpdateFlags(){};
 | 
			
		||||
 | 
			
		||||
  Socket* FindSocket(std::vector<Socket>& sockets, const std::string& name) {
 | 
			
		||||
    Socket* result = nullptr;
 | 
			
		||||
    for (size_t i = 0, n = sockets.size(); i < n; i++) {
 | 
			
		||||
      if (sockets[i].m_name == name) {
 | 
			
		||||
        result = &sockets[i];
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return result;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const Socket* FindSocket(
 | 
			
		||||
      const std::vector<Socket>& sockets,
 | 
			
		||||
      const std::string& name) const {
 | 
			
		||||
    const Socket* result = nullptr;
 | 
			
		||||
    for (size_t i = 0, n = sockets.size(); i < n; i++) {
 | 
			
		||||
      if (sockets[i].m_name == name) {
 | 
			
		||||
        result = &sockets[i];
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return result;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  SocketType GetSocketType(
 | 
			
		||||
      const std::vector<Socket>& sockets,
 | 
			
		||||
      const std::string& name) {
 | 
			
		||||
    const Socket* socket = FindSocket(sockets, name);
 | 
			
		||||
    if (socket == nullptr) {
 | 
			
		||||
      return SocketType::SocketTypeUndefined;
 | 
			
		||||
    }
 | 
			
		||||
    return socket->m_type;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  size_t GetSocketIndex(
 | 
			
		||||
      const std::vector<Socket>& sockets,
 | 
			
		||||
      const std::string& name) const {
 | 
			
		||||
    for (size_t i = 0, n = sockets.size(); i < n; i++) {
 | 
			
		||||
      if (sockets[i].m_name == name) {
 | 
			
		||||
        return i;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return -1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename T>
 | 
			
		||||
  T GetSocketValue(
 | 
			
		||||
      const std::vector<Socket>& sockets,
 | 
			
		||||
      const std::string& name,
 | 
			
		||||
      T default_value) {
 | 
			
		||||
    const Socket* socket = FindSocket(sockets, name);
 | 
			
		||||
    if (socket == nullptr) {
 | 
			
		||||
      return default_value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return *static_cast<T*>(socket->m_value.ptr);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename T>
 | 
			
		||||
  void SetSocketValue(
 | 
			
		||||
      const std::vector<Socket>& sockets,
 | 
			
		||||
      const std::string& name,
 | 
			
		||||
      const T& value) {
 | 
			
		||||
    const Socket* socket = FindSocket(sockets, name);
 | 
			
		||||
    if (socket == nullptr) {
 | 
			
		||||
      std::cerr << "Error: could not set value of socket with name " << name
 | 
			
		||||
                << ": no socket found." << std::endl;
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    *static_cast<T*>(socket->m_value.ptr) = value;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename T>
 | 
			
		||||
  bool RegisterSocket(
 | 
			
		||||
      std::vector<Socket>& sockets,
 | 
			
		||||
      const std::string& name,
 | 
			
		||||
      T* value_ptr,
 | 
			
		||||
      int flags = 0) {
 | 
			
		||||
    Socket* socket = FindSocket(sockets, name);
 | 
			
		||||
    if (socket != nullptr) {
 | 
			
		||||
      std::cerr << "Socket " << name << " already registered." << std::endl;
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    sockets.push_back(Socket());
 | 
			
		||||
    socket = &sockets[sockets.size() - 1];
 | 
			
		||||
    socket->m_name = name;
 | 
			
		||||
    socket->m_type_size = sizeof(T);
 | 
			
		||||
    socket->m_flags = flags;
 | 
			
		||||
 | 
			
		||||
    if constexpr (std::is_same<T, float>::value) {
 | 
			
		||||
      socket->m_type = SocketType::SocketTypeFloat;
 | 
			
		||||
    } else if constexpr (std::is_same<T, bool>::value) {
 | 
			
		||||
      socket->m_type = SocketType::SocketTypeBool;
 | 
			
		||||
    } else if constexpr (std::is_same<T, Vec3>::value) {
 | 
			
		||||
      socket->m_type = SocketType::SocketTypeVec3;
 | 
			
		||||
    } else if constexpr (std::is_same<T, Quat>::value) {
 | 
			
		||||
      socket->m_type = SocketType::SocketTypeQuat;
 | 
			
		||||
    } else if constexpr (std::is_same<T, AnimData>::value) {
 | 
			
		||||
      socket->m_type = SocketType::SocketTypeAnimation;
 | 
			
		||||
    } else if constexpr (std::is_same<T, std::string>::value) {
 | 
			
		||||
      socket->m_type = SocketType::SocketTypeString;
 | 
			
		||||
    } else if constexpr (std::is_same<T, float*>::value) {
 | 
			
		||||
      socket->m_type = SocketType::SocketTypeFloat;
 | 
			
		||||
    } else if constexpr (std::is_same<T, bool*>::value) {
 | 
			
		||||
      socket->m_type = SocketType::SocketTypeBool;
 | 
			
		||||
    } else if constexpr (std::is_same<T, Vec3*>::value) {
 | 
			
		||||
      socket->m_type = SocketType::SocketTypeVec3;
 | 
			
		||||
    } else if constexpr (std::is_same<T, Quat*>::value) {
 | 
			
		||||
      socket->m_type = SocketType::SocketTypeQuat;
 | 
			
		||||
    } else if constexpr (std::is_same<T, AnimData*>::value) {
 | 
			
		||||
      socket->m_type = SocketType::SocketTypeAnimation;
 | 
			
		||||
    } else if constexpr (std::is_same<T, std::string*>::value) {
 | 
			
		||||
      socket->m_type = SocketType::SocketTypeString;
 | 
			
		||||
    } else {
 | 
			
		||||
      std::cerr << "Cannot register socket, invalid type." << std::endl;
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    socket->m_value.ptr = value_ptr;
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename T>
 | 
			
		||||
  bool RegisterProperty(const std::string& name, T* value) {
 | 
			
		||||
    return RegisterSocket(m_properties, name, value);
 | 
			
		||||
  }
 | 
			
		||||
  template <typename T>
 | 
			
		||||
  void SetProperty(const std::string& name, const T& value) {
 | 
			
		||||
    SetSocketValue(m_properties, name, value);
 | 
			
		||||
  }
 | 
			
		||||
  template <typename T>
 | 
			
		||||
  T GetProperty(const std::string& name, T default_value) {
 | 
			
		||||
    return GetSocketValue(m_properties, name, default_value);
 | 
			
		||||
  }
 | 
			
		||||
  SocketType GetPropertyType(const std::string& name) {
 | 
			
		||||
    return GetSocketType(m_properties, name);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename T>
 | 
			
		||||
  bool RegisterInput(const std::string& name, T* value, int flags = 0) {
 | 
			
		||||
    return RegisterSocket(m_inputs, name, value, flags);
 | 
			
		||||
  }
 | 
			
		||||
  template <typename T>
 | 
			
		||||
  T* GetInput(const std::string& name, T* value) {
 | 
			
		||||
    return GetSocketValue(m_inputs, name, value);
 | 
			
		||||
  }
 | 
			
		||||
  Socket* FindInputSocket(const std::string& name) {
 | 
			
		||||
    return FindSocket(m_inputs, name);
 | 
			
		||||
  }
 | 
			
		||||
  SocketType GetInputType(const std::string& name) {
 | 
			
		||||
    return GetSocketType(m_inputs, name);
 | 
			
		||||
  }
 | 
			
		||||
  size_t GetInputIndex(const std::string& name) {
 | 
			
		||||
    return GetSocketIndex(m_inputs, name);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename T>
 | 
			
		||||
  bool RegisterOutput(const std::string& name, T** value, int flags = 0) {
 | 
			
		||||
    return RegisterSocket(m_outputs, name, value, flags);
 | 
			
		||||
  }
 | 
			
		||||
  SocketType GetOutputType(const std::string& name) {
 | 
			
		||||
    return GetSocketType(m_outputs, name);
 | 
			
		||||
  }
 | 
			
		||||
  Socket* FindOutputSocket(const std::string& name) {
 | 
			
		||||
    return FindSocket(m_outputs, name);
 | 
			
		||||
  }
 | 
			
		||||
  size_t GetOutputIndex(const std::string& name) {
 | 
			
		||||
    return GetSocketIndex(m_outputs, name);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
struct NodeSocketAccessor : public NodeSocketAccessorBase {
 | 
			
		||||
  virtual ~NodeSocketAccessor() {}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif  //ANIMTESTBED_ANIMGRAPHDATA_H
 | 
			
		||||
@ -5,9 +5,9 @@
 | 
			
		||||
#include "AnimGraphEditor.h"
 | 
			
		||||
 | 
			
		||||
#include "AnimGraphResource.h"
 | 
			
		||||
#include "imgui.h"
 | 
			
		||||
#include "imnodes.h"
 | 
			
		||||
 | 
			
		||||
using namespace AniGraph;
 | 
			
		||||
#include "misc/cpp/imgui_stdlib.h"
 | 
			
		||||
 | 
			
		||||
ImNodesPinShape sGetSocketShapeFromSocketType(const SocketType& socket_type) {
 | 
			
		||||
  switch (socket_type) {
 | 
			
		||||
@ -28,7 +28,39 @@ ImNodesPinShape sGetSocketShapeFromSocketType(const SocketType& socket_type) {
 | 
			
		||||
  return ImNodesPinShape_Quad;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnimGraphEditorRenderSidebar(AnimNodeResource& node_resource) {
 | 
			
		||||
void NodeSocketEditor(Socket& socket) {
 | 
			
		||||
  int mode_current = static_cast<int>(socket.m_type);
 | 
			
		||||
  ImGui::InputText("Name", &socket.m_name);
 | 
			
		||||
  if (ImGui::Combo(
 | 
			
		||||
          "Type",
 | 
			
		||||
          &mode_current,
 | 
			
		||||
          SocketTypeNames,
 | 
			
		||||
          sizeof(SocketTypeNames) / sizeof(char*))) {
 | 
			
		||||
    socket.m_type = static_cast<SocketType>(mode_current);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RemoveConnectionsForSocket(
 | 
			
		||||
    AnimGraphResource& graph_resource,
 | 
			
		||||
    AnimNodeResource& node_resource,
 | 
			
		||||
    Socket& socket) {
 | 
			
		||||
  std::vector<AnimGraphConnection>::iterator iter =
 | 
			
		||||
      graph_resource.m_connections.begin();
 | 
			
		||||
 | 
			
		||||
  while (iter != graph_resource.m_connections.end()) {
 | 
			
		||||
    AnimGraphConnection& connection = *iter;
 | 
			
		||||
    if (connection.m_source_node == &node_resource
 | 
			
		||||
        && connection.m_source_socket == &socket) {
 | 
			
		||||
      iter = graph_resource.m_connections.erase(iter);
 | 
			
		||||
    } else {
 | 
			
		||||
      iter++;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnimGraphEditorRenderSidebar(
 | 
			
		||||
    AnimGraphResource& graph_resource,
 | 
			
		||||
    AnimNodeResource& node_resource) {
 | 
			
		||||
  ImGui::Text("[%s]", node_resource.m_type_name.c_str());
 | 
			
		||||
 | 
			
		||||
  char node_name_buffer[256];
 | 
			
		||||
@ -72,6 +104,48 @@ void AnimGraphEditorRenderSidebar(AnimNodeResource& node_resource) {
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (&node_resource == &graph_resource.getGraphOutputNode()) {
 | 
			
		||||
    ImGui::Text("Outputs");
 | 
			
		||||
 | 
			
		||||
    // Graph outputs are the inputs of the output node!
 | 
			
		||||
    std::vector<Socket>& outputs = node_resource.m_socket_accessor->m_inputs;
 | 
			
		||||
 | 
			
		||||
    std::vector<Socket>::iterator iter = outputs.begin();
 | 
			
		||||
    while (iter != outputs.end()) {
 | 
			
		||||
      Socket& output = *iter;
 | 
			
		||||
      ImGui::PushID(&output);
 | 
			
		||||
      NodeSocketEditor(output);
 | 
			
		||||
      if (ImGui::Button("X")) {
 | 
			
		||||
        RemoveConnectionsForSocket(graph_resource, node_resource, output);
 | 
			
		||||
        iter = outputs.erase(iter);
 | 
			
		||||
      } else {
 | 
			
		||||
        iter++;
 | 
			
		||||
      }
 | 
			
		||||
      ImGui::PopID();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (&node_resource == &graph_resource.getGraphInputNode()) {
 | 
			
		||||
    ImGui::Text("Inputs");
 | 
			
		||||
 | 
			
		||||
    // Graph inputs are the outputs of the input node!
 | 
			
		||||
    std::vector<Socket>& inputs = node_resource.m_socket_accessor->m_outputs;
 | 
			
		||||
 | 
			
		||||
    std::vector<Socket>::iterator iter = inputs.begin();
 | 
			
		||||
    while (iter != inputs.end()) {
 | 
			
		||||
      Socket& input = *iter;
 | 
			
		||||
      ImGui::PushID(&input);
 | 
			
		||||
      NodeSocketEditor(input);
 | 
			
		||||
      if (ImGui::Button("X")) {
 | 
			
		||||
        RemoveConnectionsForSocket(graph_resource, node_resource, input);
 | 
			
		||||
        iter = inputs.erase(iter);
 | 
			
		||||
      } else {
 | 
			
		||||
        iter++;
 | 
			
		||||
      }
 | 
			
		||||
      ImGui::PopID();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnimGraphEditorUpdate() {
 | 
			
		||||
@ -248,12 +322,22 @@ void AnimGraphEditorUpdate() {
 | 
			
		||||
  for (size_t i = 0, n = graph_resource.m_connections.size(); i < n; i++) {
 | 
			
		||||
    const AnimGraphConnection& connection = graph_resource.m_connections[i];
 | 
			
		||||
    int start_attr, end_attr;
 | 
			
		||||
    start_attr = GenerateOutputAttributeId(
 | 
			
		||||
        connection.m_source_node_index,
 | 
			
		||||
        connection.m_source_socket_index);
 | 
			
		||||
    end_attr = GenerateInputAttributeId(
 | 
			
		||||
        connection.m_target_node_index,
 | 
			
		||||
        connection.m_target_socket_index);
 | 
			
		||||
 | 
			
		||||
    int source_node_index =
 | 
			
		||||
        graph_resource.getNodeIndex(*connection.m_source_node);
 | 
			
		||||
    int source_socket_index =
 | 
			
		||||
        connection.m_source_node->m_socket_accessor->GetOutputIndex(
 | 
			
		||||
            connection.m_source_socket->m_name);
 | 
			
		||||
    start_attr =
 | 
			
		||||
        GenerateOutputAttributeId(source_node_index, source_socket_index);
 | 
			
		||||
 | 
			
		||||
    int target_node_index =
 | 
			
		||||
        graph_resource.getNodeIndex(*connection.m_target_node);
 | 
			
		||||
    int target_socket_index =
 | 
			
		||||
        connection.m_target_node->m_socket_accessor->GetInputIndex(
 | 
			
		||||
            connection.m_target_socket->m_name);
 | 
			
		||||
    end_attr = GenerateInputAttributeId(target_node_index, target_socket_index);
 | 
			
		||||
 | 
			
		||||
    ImNodes::Link(i, start_attr, end_attr);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -274,11 +358,12 @@ void AnimGraphEditorUpdate() {
 | 
			
		||||
    SplitInputAttributeId(end_attr, &node_end_id, &node_end_input_index);
 | 
			
		||||
 | 
			
		||||
    AnimGraphConnection connection;
 | 
			
		||||
    connection.m_source_node_index = node_start_id;
 | 
			
		||||
    connection.m_source_socket_index = node_start_output_index;
 | 
			
		||||
    connection.m_source_node = &graph_resource.m_nodes[node_start_id];
 | 
			
		||||
    connection.m_source_socket = &connection.m_source_node->m_socket_accessor->m_outputs[node_start_output_index];
 | 
			
		||||
 | 
			
		||||
    connection.m_target_node = &graph_resource.m_nodes[node_end_id];
 | 
			
		||||
    connection.m_target_socket = &connection.m_target_node->m_socket_accessor->m_inputs[node_end_input_index];
 | 
			
		||||
 | 
			
		||||
    connection.m_target_node_index = node_end_id;
 | 
			
		||||
    connection.m_target_socket_index = node_end_input_index;
 | 
			
		||||
    graph_resource.m_connections.push_back(connection);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -301,7 +386,7 @@ void AnimGraphEditorUpdate() {
 | 
			
		||||
    if (selected_nodes[0] < graph_resource.m_nodes.size()) {
 | 
			
		||||
      AnimNodeResource& selected_node =
 | 
			
		||||
          graph_resource.m_nodes[selected_nodes[0]];
 | 
			
		||||
      AnimGraphEditorRenderSidebar(selected_node);
 | 
			
		||||
      AnimGraphEditorRenderSidebar(graph_resource, selected_node);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										30
									
								
								src/AnimGraph/AnimGraphEditor.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/AnimGraph/AnimGraphEditor.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,30 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by martin on 11.02.22.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef ANIMTESTBED_ANIMGRAPHEDITOR_H
 | 
			
		||||
#define ANIMTESTBED_ANIMGRAPHEDITOR_H
 | 
			
		||||
 | 
			
		||||
inline int GenerateInputAttributeId(int node_id, int input_index) {
 | 
			
		||||
  return ((input_index + 1) << 14) + node_id;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline void
 | 
			
		||||
SplitInputAttributeId(int attribute_id, int* node_id, int* input_index) {
 | 
			
		||||
  *node_id = attribute_id & ((1 << 14) - 1);
 | 
			
		||||
  *input_index = (attribute_id >> 14) - 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline int GenerateOutputAttributeId(int node_id, int output_index) {
 | 
			
		||||
  return ((output_index + 1) << 23) + node_id;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline void
 | 
			
		||||
SplitOutputAttributeId(int attribute_id, int* node_id, int* output_index) {
 | 
			
		||||
  *node_id = attribute_id & ((1 << 14) - 1);
 | 
			
		||||
  *output_index = (attribute_id >> 23) - 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnimGraphEditorUpdate();
 | 
			
		||||
 | 
			
		||||
#endif  //ANIMTESTBED_ANIMGRAPHEDITOR_H
 | 
			
		||||
							
								
								
									
										5
									
								
								src/AnimGraph/AnimGraphNodes.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/AnimGraph/AnimGraphNodes.cc
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by martin on 25.03.22.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "AnimGraphNodes.h"
 | 
			
		||||
							
								
								
									
										208
									
								
								src/AnimGraph/AnimGraphNodes.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										208
									
								
								src/AnimGraph/AnimGraphNodes.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,208 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by martin on 25.03.22.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef ANIMTESTBED_ANIMGRAPHNODES_H
 | 
			
		||||
#define ANIMTESTBED_ANIMGRAPHNODES_H
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
#include "AnimGraphData.h"
 | 
			
		||||
#include "SyncTrack.h"
 | 
			
		||||
 | 
			
		||||
struct AnimNode;
 | 
			
		||||
 | 
			
		||||
struct NodeInput {
 | 
			
		||||
  AnimNode* m_node;
 | 
			
		||||
  SocketType m_type = SocketType::SocketTypeUndefined;
 | 
			
		||||
  std::string m_input_name;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum class AnimNodeEvalState {
 | 
			
		||||
  Undefined,
 | 
			
		||||
  Deactivated,
 | 
			
		||||
  Activated,
 | 
			
		||||
  SyncTrackUpdated,
 | 
			
		||||
  TimeUpdated,
 | 
			
		||||
  Evaluated
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct AnimNode {
 | 
			
		||||
  std::string m_name;
 | 
			
		||||
  std::string m_node_type_name;
 | 
			
		||||
  float m_time_now = 0.f;
 | 
			
		||||
  float m_time_last = 0.f;
 | 
			
		||||
  size_t m_index = -1;
 | 
			
		||||
  AnimNodeEvalState m_state = AnimNodeEvalState::Undefined;
 | 
			
		||||
  SyncTrack m_sync_track;
 | 
			
		||||
 | 
			
		||||
  virtual ~AnimNode(){};
 | 
			
		||||
 | 
			
		||||
  virtual void MarkActiveInputs(const std::vector<NodeInput>& inputs) {
 | 
			
		||||
    for (size_t i = 0, n = inputs.size(); i < n; i++) {
 | 
			
		||||
      AnimNode* input_node = inputs[i].m_node;
 | 
			
		||||
      if (input_node != nullptr) {
 | 
			
		||||
        input_node->m_state = AnimNodeEvalState::Activated;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  virtual void CalcSyncTrack(const std::vector<NodeInput>& inputs) {
 | 
			
		||||
    for (size_t i = 0, n = inputs.size(); i < n; i++) {
 | 
			
		||||
      AnimNode* input_node = inputs[i].m_node;
 | 
			
		||||
      if (input_node != nullptr
 | 
			
		||||
          && inputs[i].m_type == SocketType::SocketTypeAnimation
 | 
			
		||||
          && input_node->m_state != AnimNodeEvalState::Deactivated) {
 | 
			
		||||
        m_sync_track = input_node->m_sync_track;
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  virtual void UpdateTime(float time_last, float time_now) {
 | 
			
		||||
    m_time_last = time_last;
 | 
			
		||||
    m_time_now = time_now;
 | 
			
		||||
    m_state = AnimNodeEvalState::TimeUpdated;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  virtual void Evaluate(){};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// BlendTreeNode
 | 
			
		||||
//
 | 
			
		||||
struct BlendTreeNode : public AnimNode {};
 | 
			
		||||
 | 
			
		||||
template <>
 | 
			
		||||
struct NodeSocketAccessor<BlendTreeNode> : public NodeSocketAccessorBase {
 | 
			
		||||
  NodeSocketAccessor(AnimNode* node_) {}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// Blend2Node
 | 
			
		||||
//
 | 
			
		||||
struct Blend2Node : public AnimNode {
 | 
			
		||||
  AnimData m_input0;
 | 
			
		||||
  AnimData m_input1;
 | 
			
		||||
  AnimData* m_output = nullptr;
 | 
			
		||||
  float m_blend_weight = 0.f;
 | 
			
		||||
  bool m_sync_blend = false;
 | 
			
		||||
 | 
			
		||||
  virtual void MarkActiveInputs(const std::vector<NodeInput>& inputs) override {
 | 
			
		||||
    for (size_t i = 0, n = inputs.size(); i < n; i++) {
 | 
			
		||||
      AnimNode* input_node = inputs[i].m_node;
 | 
			
		||||
      if (input_node == nullptr) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (inputs[i].m_input_name == "Input0" && m_blend_weight < 0.999) {
 | 
			
		||||
        input_node->m_state = AnimNodeEvalState::Activated;
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (inputs[i].m_input_name == "Input1" && m_blend_weight > 0.001) {
 | 
			
		||||
        input_node->m_state = AnimNodeEvalState::Activated;
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  virtual void UpdateTime(float dt, std::vector<NodeInput>& inputs) {
 | 
			
		||||
    if (!m_sync_blend) {
 | 
			
		||||
      m_time_now = m_time_now + dt;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (size_t i = 0, n = inputs.size(); i < n; i++) {
 | 
			
		||||
      AnimNode* input_node = inputs[i].m_node;
 | 
			
		||||
      if (input_node == nullptr) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (input_node->m_state != AnimNodeEvalState::Deactivated) {
 | 
			
		||||
        if (!m_sync_blend) {
 | 
			
		||||
          input_node->m_time_now = m_time_now;
 | 
			
		||||
        }
 | 
			
		||||
        input_node->m_state = AnimNodeEvalState::TimeUpdated;
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <>
 | 
			
		||||
struct NodeSocketAccessor<Blend2Node> : public NodeSocketAccessorBase {
 | 
			
		||||
  NodeSocketAccessor(AnimNode* node_) {
 | 
			
		||||
    Blend2Node* node = dynamic_cast<Blend2Node*>(node_);
 | 
			
		||||
    RegisterInput("Input0", &node->m_input0);
 | 
			
		||||
    RegisterInput("Input1", &node->m_input1);
 | 
			
		||||
    RegisterInput(
 | 
			
		||||
        "Weight",
 | 
			
		||||
        &node->m_blend_weight,
 | 
			
		||||
        SocketFlags::SocketFlagAffectsTime);
 | 
			
		||||
 | 
			
		||||
    RegisterOutput("Output", &node->m_output);
 | 
			
		||||
 | 
			
		||||
    RegisterProperty("Sync", &node->m_sync_blend);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  virtual void UpdateFlags() override {
 | 
			
		||||
    Socket* weight_input_socket = FindSocket(m_inputs, "Weight");
 | 
			
		||||
    assert(weight_input_socket != nullptr);
 | 
			
		||||
 | 
			
		||||
    if (GetProperty<bool>("Sync", false) == true) {
 | 
			
		||||
      weight_input_socket->m_flags = SocketFlags::SocketFlagAffectsTime;
 | 
			
		||||
    } else {
 | 
			
		||||
      weight_input_socket->m_flags = 0;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// SpeedScaleNode
 | 
			
		||||
//
 | 
			
		||||
struct SpeedScaleNode : public AnimNode {
 | 
			
		||||
  AnimData m_input;
 | 
			
		||||
  AnimData* m_output = nullptr;
 | 
			
		||||
  float m_speed_scale = 0.f;
 | 
			
		||||
 | 
			
		||||
  virtual void UpdateTime(float time_last, float time_now) {
 | 
			
		||||
    m_time_last = time_last;
 | 
			
		||||
    m_time_now = time_last + (time_now - time_last) * m_speed_scale;
 | 
			
		||||
    m_state = AnimNodeEvalState::TimeUpdated;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <>
 | 
			
		||||
struct NodeSocketAccessor<SpeedScaleNode> : public NodeSocketAccessorBase {
 | 
			
		||||
  NodeSocketAccessor(AnimNode* node_) {
 | 
			
		||||
    SpeedScaleNode* node = dynamic_cast<SpeedScaleNode*>(node_);
 | 
			
		||||
    RegisterInput(
 | 
			
		||||
        "SpeedScale",
 | 
			
		||||
        &node->m_speed_scale,
 | 
			
		||||
        SocketFlags::SocketFlagAffectsTime);
 | 
			
		||||
    RegisterInput("Input", &node->m_input);
 | 
			
		||||
 | 
			
		||||
    RegisterOutput("Output", &node->m_output);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// AnimSamplerNode
 | 
			
		||||
//
 | 
			
		||||
struct AnimSamplerNode : public AnimNode {
 | 
			
		||||
  AnimData* m_output = nullptr;
 | 
			
		||||
  std::string m_filename;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <>
 | 
			
		||||
struct NodeSocketAccessor<AnimSamplerNode> : public NodeSocketAccessorBase {
 | 
			
		||||
  NodeSocketAccessor(AnimNode* node_) {
 | 
			
		||||
    AnimSamplerNode* node = dynamic_cast<AnimSamplerNode*>(node_);
 | 
			
		||||
    RegisterOutput("Output", &node->m_output);
 | 
			
		||||
 | 
			
		||||
    RegisterProperty("Filename", &node->m_filename);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif  //ANIMTESTBED_ANIMGRAPHNODES_H
 | 
			
		||||
@ -8,8 +8,6 @@
 | 
			
		||||
 | 
			
		||||
#include "3rdparty/json/json.hpp"
 | 
			
		||||
 | 
			
		||||
namespace AniGraph {
 | 
			
		||||
 | 
			
		||||
using json = nlohmann::json;
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
@ -342,148 +340,4 @@ bool AnimGraphResource::loadFromFile(const char* filename) {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnimGraph::updateOrderedNodes() {
 | 
			
		||||
  std::vector<int> node_index_stack;
 | 
			
		||||
  node_index_stack.push_back(0);
 | 
			
		||||
 | 
			
		||||
  m_eval_ordered_nodes.clear();
 | 
			
		||||
 | 
			
		||||
  while (node_index_stack.size() > 0) {
 | 
			
		||||
    std::vector<NodeInput>& node_inputs =
 | 
			
		||||
        m_node_inputs[node_index_stack.back()];
 | 
			
		||||
    node_index_stack.pop_back();
 | 
			
		||||
 | 
			
		||||
    for (size_t i = 0, n = node_inputs.size(); i < n; i++) {
 | 
			
		||||
      AnimNode* input_node = node_inputs[i].m_node;
 | 
			
		||||
      if (input_node == nullptr) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      int input_node_index = input_node->m_index;
 | 
			
		||||
      bool is_node_processed = false;
 | 
			
		||||
      for (size_t j = 0, m = m_eval_ordered_nodes.size(); j < m; j++) {
 | 
			
		||||
        if (m_eval_ordered_nodes[j] == input_node) {
 | 
			
		||||
          is_node_processed = true;
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (is_node_processed) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      m_eval_ordered_nodes.push_back(input_node);
 | 
			
		||||
      node_index_stack.push_back(input_node_index);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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<NodeInput> graph_output_inputs = m_node_inputs[0];
 | 
			
		||||
  for (size_t i = 0, n = graph_output_inputs.size(); i < n; i++) {
 | 
			
		||||
    const NodeInput& graph_input = graph_output_inputs[i];
 | 
			
		||||
    AnimNode* node = graph_input.m_node;
 | 
			
		||||
    if (node != nullptr) {
 | 
			
		||||
      node->m_state = AnimNodeEvalState::Activated;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  for (size_t i = 0, n = m_eval_ordered_nodes.size(); i < n; i++) {
 | 
			
		||||
    AnimNode* node = m_eval_ordered_nodes[i];
 | 
			
		||||
    if (checkIsNodeActive(node)) {
 | 
			
		||||
      int node_index = node->m_index;
 | 
			
		||||
      node->MarkActiveInputs(m_node_inputs[node_index]);
 | 
			
		||||
 | 
			
		||||
      // Non-animation data inputs are always active.
 | 
			
		||||
      for (size_t j = 0, nj = m_node_inputs[node_index].size(); j < nj; j++) {
 | 
			
		||||
        const NodeInput& input = m_node_inputs[node_index][j];
 | 
			
		||||
        if (input.m_node != nullptr && input.m_type != SocketType::SocketTypeAnimation) {
 | 
			
		||||
          input.m_node->m_state = AnimNodeEvalState::Activated;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnimGraph::evalSyncTracks() {
 | 
			
		||||
  for (size_t i = m_eval_ordered_nodes.size() - 1; i >= 0; i--) {
 | 
			
		||||
    AnimNode* node = m_eval_ordered_nodes[i];
 | 
			
		||||
    int node_index = node->m_index;
 | 
			
		||||
    if (node->m_state == AnimNodeEvalState::Deactivated) {
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    node->CalcSyncTrack(m_node_inputs[node_index]);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnimGraph::updateTime(float dt) {
 | 
			
		||||
  const std::vector<NodeInput> graph_output_inputs = m_node_inputs[0];
 | 
			
		||||
  for (size_t i = 0, n = graph_output_inputs.size(); i < n; i++) {
 | 
			
		||||
    AnimNode* node = m_eval_ordered_nodes[i];
 | 
			
		||||
    if (node != nullptr) {
 | 
			
		||||
      node->UpdateTime(node->m_time_now, node->m_time_now + dt);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  for (size_t i = 0, n = m_eval_ordered_nodes.size(); i < n; i++) {
 | 
			
		||||
    AnimNode* node = m_eval_ordered_nodes[i];
 | 
			
		||||
    if (node->m_state != AnimNodeEvalState::TimeUpdated) {
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    int node_index = node->m_index;
 | 
			
		||||
    const std::vector<NodeInput> node_inputs =
 | 
			
		||||
        m_node_inputs[node_index];
 | 
			
		||||
    float node_time_now = node->m_time_now;
 | 
			
		||||
    float node_time_last = node->m_time_last;
 | 
			
		||||
 | 
			
		||||
    for (size_t i = 0, n = node_inputs.size(); i < n; i++) {
 | 
			
		||||
      AnimNode* input_node = node_inputs[i].m_node;
 | 
			
		||||
 | 
			
		||||
      // Only propagate time updates via animation sockets.
 | 
			
		||||
      if (input_node != nullptr
 | 
			
		||||
          && node_inputs[i].m_type == SocketType::SocketTypeAnimation
 | 
			
		||||
          && input_node->m_state == AnimNodeEvalState::Activated) {
 | 
			
		||||
        input_node->UpdateTime(node_time_last, node_time_now);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnimGraph::evaluate() {
 | 
			
		||||
  for (size_t i = m_eval_ordered_nodes.size() - 1; i >= 0; i--) {
 | 
			
		||||
    AnimNode* node = m_eval_ordered_nodes[i];
 | 
			
		||||
    if (node->m_state == AnimNodeEvalState::Deactivated) {
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    node->Evaluate();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void* AnimGraph::getOutput(const std::string& name) const {
 | 
			
		||||
  Socket* socket = m_socket_accessor->FindInputSocket(name);
 | 
			
		||||
  if (socket == nullptr) {
 | 
			
		||||
    return nullptr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return socket->m_value.ptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void* AnimGraph::getInput(const std::string& name) const {
 | 
			
		||||
  Socket* socket = m_socket_accessor->FindOutputSocket(name);
 | 
			
		||||
  if (socket == nullptr) {
 | 
			
		||||
    return nullptr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return *(socket->m_value.ptr_ptr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace AniGraph
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										179
									
								
								src/AnimGraph/AnimGraphResource.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										179
									
								
								src/AnimGraph/AnimGraphResource.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,179 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by martin on 04.02.22.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef ANIMTESTBED_ANIMGRAPHRESOURCE_H
 | 
			
		||||
#define ANIMTESTBED_ANIMGRAPHRESOURCE_H
 | 
			
		||||
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include <iostream>
 | 
			
		||||
#include <map>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <type_traits>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
#include "SyncTrack.h"
 | 
			
		||||
#include "AnimGraphData.h"
 | 
			
		||||
#include "AnimGraphNodes.h"
 | 
			
		||||
 | 
			
		||||
struct AnimNode;
 | 
			
		||||
struct NodeSocketAccessorBase;
 | 
			
		||||
 | 
			
		||||
struct AnimNodeResource {
 | 
			
		||||
  std::string m_name;
 | 
			
		||||
  std::string m_type_name;
 | 
			
		||||
  AnimNode* m_anim_node = nullptr;
 | 
			
		||||
  NodeSocketAccessorBase* m_socket_accessor = nullptr;
 | 
			
		||||
  float m_position[2] = {0.f, 0.f};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// AnimGraphResource
 | 
			
		||||
//
 | 
			
		||||
struct AnimGraphConnection {
 | 
			
		||||
  const AnimNodeResource* m_source_node = nullptr;
 | 
			
		||||
  const Socket* m_source_socket = nullptr;
 | 
			
		||||
  const AnimNodeResource* m_target_node = nullptr;
 | 
			
		||||
  const Socket* m_target_socket = nullptr;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct AnimGraphResource {
 | 
			
		||||
  std::string m_name;
 | 
			
		||||
  std::vector<AnimNodeResource> m_nodes;
 | 
			
		||||
  std::vector<AnimGraphConnection> m_connections;
 | 
			
		||||
 | 
			
		||||
  ~AnimGraphResource() {
 | 
			
		||||
    for (size_t i = 0, n = m_nodes.size(); i < n; i++) {
 | 
			
		||||
      delete m_nodes[i].m_anim_node;
 | 
			
		||||
      delete m_nodes[i].m_socket_accessor;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  AnimGraphResource() { clear(); }
 | 
			
		||||
 | 
			
		||||
  void clear();
 | 
			
		||||
  void clearNodes();
 | 
			
		||||
  void initGraphConnectors();
 | 
			
		||||
  bool saveToFile(const char* filename) const;
 | 
			
		||||
  bool loadFromFile(const char* filename);
 | 
			
		||||
 | 
			
		||||
  AnimNodeResource& getGraphOutputNode() { return m_nodes[0]; }
 | 
			
		||||
  AnimNodeResource& getGraphInputNode() { return m_nodes[1]; }
 | 
			
		||||
 | 
			
		||||
  const AnimNodeResource& getGraphOutputNode() const { return m_nodes[0]; }
 | 
			
		||||
  const AnimNodeResource& getGraphInputNode() const { return m_nodes[1]; }
 | 
			
		||||
 | 
			
		||||
  size_t getNodeIndex(const AnimNodeResource& node_resource) const {
 | 
			
		||||
    for (size_t i = 0, n = m_nodes.size(); i < n; i++) {
 | 
			
		||||
      if (&m_nodes[i] == &node_resource) {
 | 
			
		||||
        return i;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return -1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  size_t addNode(AnimNodeResource node_resource) {
 | 
			
		||||
    m_nodes.push_back(node_resource);
 | 
			
		||||
    return m_nodes.size() - 1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool connectSockets(
 | 
			
		||||
      const AnimNodeResource& source_node,
 | 
			
		||||
      const std::string& source_socket_name,
 | 
			
		||||
      const AnimNodeResource& target_node,
 | 
			
		||||
      const std::string& target_socket_name) {
 | 
			
		||||
    size_t source_index = -1;
 | 
			
		||||
    size_t target_index = -1;
 | 
			
		||||
    for (size_t i = 0, n = m_nodes.size(); i < n; i++) {
 | 
			
		||||
      if (&source_node == &m_nodes[i]) {
 | 
			
		||||
        source_index = i;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (&target_node == &m_nodes[i]) {
 | 
			
		||||
        target_index = i;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (source_index < m_nodes.size() && target_index < m_nodes.size()) {
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (source_index >= m_nodes.size() || target_index >= m_nodes.size()) {
 | 
			
		||||
      std::cerr << "Cannot connect nodes: could not find nodes." << std::endl;
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Socket* source_socket =
 | 
			
		||||
        source_node.m_socket_accessor->FindOutputSocket(source_socket_name);
 | 
			
		||||
    Socket* target_socket =
 | 
			
		||||
        target_node.m_socket_accessor->FindInputSocket(target_socket_name);
 | 
			
		||||
 | 
			
		||||
    if (source_socket == nullptr || target_socket == nullptr) {
 | 
			
		||||
      std::cerr << "Cannot connect nodes: could not find sockets." << std::endl;
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    AnimGraphConnection connection;
 | 
			
		||||
    connection.m_source_node = &source_node;
 | 
			
		||||
    connection.m_source_socket = source_socket;
 | 
			
		||||
    connection.m_target_node = &target_node;
 | 
			
		||||
    connection.m_target_socket = target_socket;
 | 
			
		||||
    m_connections.push_back(connection);
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static inline AnimNode* AnimNodeFactory(const std::string& name) {
 | 
			
		||||
  AnimNode* result;
 | 
			
		||||
  if (name == "Blend2") {
 | 
			
		||||
    result = new Blend2Node;
 | 
			
		||||
  } else if (name == "SpeedScale") {
 | 
			
		||||
    result = new SpeedScaleNode;
 | 
			
		||||
  } else if (name == "AnimSampler") {
 | 
			
		||||
    result = new AnimSamplerNode;
 | 
			
		||||
  } else if (name == "BlendTree") {
 | 
			
		||||
    result = new BlendTreeNode;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (result != nullptr) {
 | 
			
		||||
    result->m_node_type_name = name;
 | 
			
		||||
    return result;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  std::cerr << "Invalid node type: " << name << std::endl;
 | 
			
		||||
  return nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static inline NodeSocketAccessorBase* AnimNodeAccessorFactory(
 | 
			
		||||
    const std::string& node_type_name,
 | 
			
		||||
    AnimNode* node) {
 | 
			
		||||
  if (node_type_name == "Blend2") {
 | 
			
		||||
    return new NodeSocketAccessor<Blend2Node>(node);
 | 
			
		||||
  } else if (node_type_name == "SpeedScale") {
 | 
			
		||||
    return new NodeSocketAccessor<SpeedScaleNode>(node);
 | 
			
		||||
  } else if (node_type_name == "AnimSampler") {
 | 
			
		||||
    return new NodeSocketAccessor<AnimSamplerNode>(node);
 | 
			
		||||
  } else if (node_type_name == "BlendTree") {
 | 
			
		||||
    return new NodeSocketAccessor<BlendTreeNode>(node);
 | 
			
		||||
  } else {
 | 
			
		||||
    std::cerr << "Invalid node type name " << node_type_name << "."
 | 
			
		||||
              << std::endl;
 | 
			
		||||
  }
 | 
			
		||||
  return nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static inline AnimNodeResource AnimNodeResourceFactory(
 | 
			
		||||
    const std::string& node_type_name) {
 | 
			
		||||
  AnimNodeResource result;
 | 
			
		||||
  result.m_type_name = node_type_name;
 | 
			
		||||
  result.m_anim_node = AnimNodeFactory(node_type_name);
 | 
			
		||||
  result.m_socket_accessor =
 | 
			
		||||
      AnimNodeAccessorFactory(node_type_name, result.m_anim_node);
 | 
			
		||||
  return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif  //ANIMTESTBED_ANIMGRAPHRESOURCE_H
 | 
			
		||||
@ -1,10 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by martin on 11.02.22.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef ANIMTESTBED_ANIMGRAPHEDITOR_H
 | 
			
		||||
#define ANIMTESTBED_ANIMGRAPHEDITOR_H
 | 
			
		||||
 | 
			
		||||
void AnimGraphEditorUpdate();
 | 
			
		||||
 | 
			
		||||
#endif  //ANIMTESTBED_ANIMGRAPHEDITOR_H
 | 
			
		||||
@ -1,909 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by martin on 04.02.22.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef ANIMTESTBED_ANIMGRAPHRESOURCE_H
 | 
			
		||||
#define ANIMTESTBED_ANIMGRAPHRESOURCE_H
 | 
			
		||||
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include <iostream>
 | 
			
		||||
#include <map>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <type_traits>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
#include "SyncTrack.h"
 | 
			
		||||
 | 
			
		||||
namespace AniGraph {
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// Data types
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
struct AnimData {
 | 
			
		||||
  float m_bone_transforms[16];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
typedef float Vec3[3];
 | 
			
		||||
typedef float Quat[4];
 | 
			
		||||
 | 
			
		||||
inline int GenerateInputAttributeId(int node_id, int input_index) {
 | 
			
		||||
  return ((input_index + 1) << 14) + node_id;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline void
 | 
			
		||||
SplitInputAttributeId(int attribute_id, int* node_id, int* input_index) {
 | 
			
		||||
  *node_id = attribute_id & ((1 << 14) - 1);
 | 
			
		||||
  *input_index = (attribute_id >> 14) - 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline int GenerateOutputAttributeId(int node_id, int output_index) {
 | 
			
		||||
  return ((output_index + 1) << 23) + node_id;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline void
 | 
			
		||||
SplitOutputAttributeId(int attribute_id, int* node_id, int* output_index) {
 | 
			
		||||
  *node_id = attribute_id & ((1 << 14) - 1);
 | 
			
		||||
  *output_index = (attribute_id >> 23) - 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum class SocketType {
 | 
			
		||||
  SocketTypeUndefined = 0,
 | 
			
		||||
  SocketTypeBool,
 | 
			
		||||
  SocketTypeAnimation,
 | 
			
		||||
  SocketTypeFloat,
 | 
			
		||||
  SocketTypeVec3,
 | 
			
		||||
  SocketTypeQuat,
 | 
			
		||||
  SocketTypeString,
 | 
			
		||||
  SocketTypeLast
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const char* SocketTypeNames[] =
 | 
			
		||||
    {"", "Bool", "Animation", "Float", "Vec3", "Quat", "String"};
 | 
			
		||||
 | 
			
		||||
enum SocketFlags { SocketFlagAffectsTime = 1 };
 | 
			
		||||
 | 
			
		||||
struct Socket {
 | 
			
		||||
  std::string m_name;
 | 
			
		||||
  SocketType m_type = SocketType::SocketTypeUndefined;
 | 
			
		||||
  union SocketValue {
 | 
			
		||||
    void* ptr;
 | 
			
		||||
    void** ptr_ptr;
 | 
			
		||||
  };
 | 
			
		||||
  SocketValue m_value = {nullptr};
 | 
			
		||||
  int m_flags = 0;
 | 
			
		||||
  size_t m_type_size = 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct AnimNode;
 | 
			
		||||
struct NodeSocketAccessorBase;
 | 
			
		||||
 | 
			
		||||
struct AnimNodeResource {
 | 
			
		||||
  std::string m_name;
 | 
			
		||||
  std::string m_type_name;
 | 
			
		||||
  AnimNode* m_anim_node = nullptr;
 | 
			
		||||
  NodeSocketAccessorBase* m_socket_accessor = nullptr;
 | 
			
		||||
  float m_position[2] = {0.f, 0.f};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct NodeInput {
 | 
			
		||||
  AnimNode* m_node;
 | 
			
		||||
  SocketType m_type = SocketType::SocketTypeUndefined;
 | 
			
		||||
  std::string m_input_name;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum class AnimNodeEvalState {
 | 
			
		||||
  Undefined,
 | 
			
		||||
  Deactivated,
 | 
			
		||||
  Activated,
 | 
			
		||||
  SyncTrackUpdated,
 | 
			
		||||
  TimeUpdated,
 | 
			
		||||
  Evaluated
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct AnimNode {
 | 
			
		||||
  std::string m_name;
 | 
			
		||||
  std::string m_node_type_name;
 | 
			
		||||
  float m_time_now = 0.f;
 | 
			
		||||
  float m_time_last = 0.f;
 | 
			
		||||
  size_t m_index = -1;
 | 
			
		||||
  AnimNodeEvalState m_state = AnimNodeEvalState::Undefined;
 | 
			
		||||
  SyncTrack m_sync_track;
 | 
			
		||||
 | 
			
		||||
  virtual ~AnimNode(){};
 | 
			
		||||
 | 
			
		||||
  virtual void MarkActiveInputs(const std::vector<NodeInput>& inputs) {
 | 
			
		||||
    for (size_t i = 0, n = inputs.size(); i < n; i++) {
 | 
			
		||||
      AnimNode* input_node = inputs[i].m_node;
 | 
			
		||||
      if (input_node != nullptr) {
 | 
			
		||||
        input_node->m_state = AnimNodeEvalState::Activated;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  virtual void CalcSyncTrack(const std::vector<NodeInput>& inputs) {
 | 
			
		||||
    for (size_t i = 0, n = inputs.size(); i < n; i++) {
 | 
			
		||||
      AnimNode* input_node = inputs[i].m_node;
 | 
			
		||||
      if (input_node != nullptr
 | 
			
		||||
          && inputs[i].m_type == SocketType::SocketTypeAnimation
 | 
			
		||||
          && input_node->m_state != AnimNodeEvalState::Deactivated) {
 | 
			
		||||
        m_sync_track = input_node->m_sync_track;
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  virtual void UpdateTime(float time_last, float time_now) {
 | 
			
		||||
    m_time_last = time_last;
 | 
			
		||||
    m_time_now = time_now;
 | 
			
		||||
    m_state = AnimNodeEvalState::TimeUpdated;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  virtual void Evaluate(){};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct NodeSocketAccessorBase {
 | 
			
		||||
  std::vector<Socket> m_properties;
 | 
			
		||||
  std::vector<Socket> m_inputs;
 | 
			
		||||
  std::vector<Socket> m_outputs;
 | 
			
		||||
 | 
			
		||||
  NodeSocketAccessorBase() {}
 | 
			
		||||
  virtual ~NodeSocketAccessorBase() {}
 | 
			
		||||
 | 
			
		||||
  virtual void UpdateFlags(){};
 | 
			
		||||
 | 
			
		||||
  Socket* FindSocket(std::vector<Socket>& sockets, const std::string& name) {
 | 
			
		||||
    Socket* result = nullptr;
 | 
			
		||||
    for (size_t i = 0, n = sockets.size(); i < n; i++) {
 | 
			
		||||
      if (sockets[i].m_name == name) {
 | 
			
		||||
        result = &sockets[i];
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return result;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const Socket* FindSocket(
 | 
			
		||||
      const std::vector<Socket>& sockets,
 | 
			
		||||
      const std::string& name) const {
 | 
			
		||||
    const Socket* result = nullptr;
 | 
			
		||||
    for (size_t i = 0, n = sockets.size(); i < n; i++) {
 | 
			
		||||
      if (sockets[i].m_name == name) {
 | 
			
		||||
        result = &sockets[i];
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return result;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  SocketType GetSocketType(
 | 
			
		||||
      const std::vector<Socket>& sockets,
 | 
			
		||||
      const std::string& name) {
 | 
			
		||||
    const Socket* socket = FindSocket(sockets, name);
 | 
			
		||||
    if (socket == nullptr) {
 | 
			
		||||
      return SocketType::SocketTypeUndefined;
 | 
			
		||||
    }
 | 
			
		||||
    return socket->m_type;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  size_t GetSocketIndex(
 | 
			
		||||
      const std::vector<Socket>& sockets,
 | 
			
		||||
      const std::string& name) const {
 | 
			
		||||
    for (size_t i = 0, n = sockets.size(); i < n; i++) {
 | 
			
		||||
      if (sockets[i].m_name == name) {
 | 
			
		||||
        return i;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return -1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename T>
 | 
			
		||||
  T GetSocketValue(
 | 
			
		||||
      const std::vector<Socket>& sockets,
 | 
			
		||||
      const std::string& name,
 | 
			
		||||
      T default_value) {
 | 
			
		||||
    const Socket* socket = FindSocket(sockets, name);
 | 
			
		||||
    if (socket == nullptr) {
 | 
			
		||||
      return default_value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return *static_cast<T*>(socket->m_value.ptr);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename T>
 | 
			
		||||
  void SetSocketValue(
 | 
			
		||||
      const std::vector<Socket>& sockets,
 | 
			
		||||
      const std::string& name,
 | 
			
		||||
      const T& value) {
 | 
			
		||||
    const Socket* socket = FindSocket(sockets, name);
 | 
			
		||||
    if (socket == nullptr) {
 | 
			
		||||
      std::cerr << "Error: could not set value of socket with name " << name
 | 
			
		||||
                << ": no socket found." << std::endl;
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    *static_cast<T*>(socket->m_value.ptr) = value;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename T>
 | 
			
		||||
  bool RegisterSocket(
 | 
			
		||||
      std::vector<Socket>& sockets,
 | 
			
		||||
      const std::string& name,
 | 
			
		||||
      T* value_ptr,
 | 
			
		||||
      int flags = 0) {
 | 
			
		||||
    Socket* socket = FindSocket(sockets, name);
 | 
			
		||||
    if (socket != nullptr) {
 | 
			
		||||
      std::cerr << "Socket " << name << " already registered." << std::endl;
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    sockets.push_back(Socket());
 | 
			
		||||
    socket = &sockets[sockets.size() - 1];
 | 
			
		||||
    socket->m_name = name;
 | 
			
		||||
    socket->m_type_size = sizeof(T);
 | 
			
		||||
    socket->m_flags = flags;
 | 
			
		||||
 | 
			
		||||
    if constexpr (std::is_same<T, float>::value) {
 | 
			
		||||
      socket->m_type = SocketType::SocketTypeFloat;
 | 
			
		||||
    } else if constexpr (std::is_same<T, bool>::value) {
 | 
			
		||||
      socket->m_type = SocketType::SocketTypeBool;
 | 
			
		||||
    } else if constexpr (std::is_same<T, Vec3>::value) {
 | 
			
		||||
      socket->m_type = SocketType::SocketTypeVec3;
 | 
			
		||||
    } else if constexpr (std::is_same<T, Quat>::value) {
 | 
			
		||||
      socket->m_type = SocketType::SocketTypeQuat;
 | 
			
		||||
    } else if constexpr (std::is_same<T, AnimData>::value) {
 | 
			
		||||
      socket->m_type = SocketType::SocketTypeAnimation;
 | 
			
		||||
    } else if constexpr (std::is_same<T, std::string>::value) {
 | 
			
		||||
      socket->m_type = SocketType::SocketTypeString;
 | 
			
		||||
    } else if constexpr (std::is_same<T, float*>::value) {
 | 
			
		||||
      socket->m_type = SocketType::SocketTypeFloat;
 | 
			
		||||
    } else if constexpr (std::is_same<T, bool*>::value) {
 | 
			
		||||
      socket->m_type = SocketType::SocketTypeBool;
 | 
			
		||||
    } else if constexpr (std::is_same<T, Vec3*>::value) {
 | 
			
		||||
      socket->m_type = SocketType::SocketTypeVec3;
 | 
			
		||||
    } else if constexpr (std::is_same<T, Quat*>::value) {
 | 
			
		||||
      socket->m_type = SocketType::SocketTypeQuat;
 | 
			
		||||
    } else if constexpr (std::is_same<T, AnimData*>::value) {
 | 
			
		||||
      socket->m_type = SocketType::SocketTypeAnimation;
 | 
			
		||||
    } else if constexpr (std::is_same<T, std::string*>::value) {
 | 
			
		||||
      socket->m_type = SocketType::SocketTypeString;
 | 
			
		||||
    } else {
 | 
			
		||||
      std::cerr << "Cannot register socket, invalid type." << std::endl;
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    socket->m_value.ptr = value_ptr;
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename T>
 | 
			
		||||
  bool RegisterProperty(const std::string& name, T* value) {
 | 
			
		||||
    return RegisterSocket(m_properties, name, value);
 | 
			
		||||
  }
 | 
			
		||||
  template <typename T>
 | 
			
		||||
  void SetProperty(const std::string& name, const T& value) {
 | 
			
		||||
    SetSocketValue(m_properties, name, value);
 | 
			
		||||
  }
 | 
			
		||||
  template <typename T>
 | 
			
		||||
  T GetProperty(const std::string& name, T default_value) {
 | 
			
		||||
    return GetSocketValue(m_properties, name, default_value);
 | 
			
		||||
  }
 | 
			
		||||
  SocketType GetPropertyType(const std::string& name) {
 | 
			
		||||
    return GetSocketType(m_properties, name);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename T>
 | 
			
		||||
  bool RegisterInput(const std::string& name, T* value, int flags = 0) {
 | 
			
		||||
    return RegisterSocket(m_inputs, name, value, flags);
 | 
			
		||||
  }
 | 
			
		||||
  template <typename T>
 | 
			
		||||
  T* GetInput(const std::string& name, T* value) {
 | 
			
		||||
    return GetSocketValue(m_inputs, name, value);
 | 
			
		||||
  }
 | 
			
		||||
  Socket* FindInputSocket(const std::string& name) {
 | 
			
		||||
    return FindSocket(m_inputs, name);
 | 
			
		||||
  }
 | 
			
		||||
  SocketType GetInputType(const std::string& name) {
 | 
			
		||||
    return GetSocketType(m_inputs, name);
 | 
			
		||||
  }
 | 
			
		||||
  size_t GetInputIndex(const std::string& name) {
 | 
			
		||||
    return GetSocketIndex(m_inputs, name);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  template <typename T>
 | 
			
		||||
  bool RegisterOutput(const std::string& name, T** value, int flags = 0) {
 | 
			
		||||
    return RegisterSocket(m_outputs, name, value, flags);
 | 
			
		||||
  }
 | 
			
		||||
  SocketType GetOutputType(const std::string& name) {
 | 
			
		||||
    return GetSocketType(m_outputs, name);
 | 
			
		||||
  }
 | 
			
		||||
  Socket* FindOutputSocket(const std::string& name) {
 | 
			
		||||
    return FindSocket(m_outputs, name);
 | 
			
		||||
  }
 | 
			
		||||
  size_t GetOutputIndex(const std::string& name) {
 | 
			
		||||
    return GetSocketIndex(m_outputs, name);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <typename T>
 | 
			
		||||
struct NodeSocketAccessor : public NodeSocketAccessorBase {
 | 
			
		||||
  virtual ~NodeSocketAccessor() {}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct NodeRegistry {
 | 
			
		||||
  AnimNode* createNode(const std::string& node_type);
 | 
			
		||||
  NodeSocketAccessorBase* createNodeAccessor(
 | 
			
		||||
      const std::string& node_type,
 | 
			
		||||
      AnimNode* node);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// BlendTreeNode
 | 
			
		||||
//
 | 
			
		||||
struct BlendTreeNode : public AnimNode {};
 | 
			
		||||
 | 
			
		||||
template <>
 | 
			
		||||
struct NodeSocketAccessor<BlendTreeNode> : public NodeSocketAccessorBase {
 | 
			
		||||
  NodeSocketAccessor(AnimNode* node_) {}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// Blend2Node
 | 
			
		||||
//
 | 
			
		||||
struct Blend2Node : public AnimNode {
 | 
			
		||||
  AnimData m_input0;
 | 
			
		||||
  AnimData m_input1;
 | 
			
		||||
  AnimData* m_output = nullptr;
 | 
			
		||||
  float m_blend_weight = 0.f;
 | 
			
		||||
  bool m_sync_blend = false;
 | 
			
		||||
 | 
			
		||||
  virtual void MarkActiveInputs(const std::vector<NodeInput>& inputs) override {
 | 
			
		||||
    for (size_t i = 0, n = inputs.size(); i < n; i++) {
 | 
			
		||||
      AnimNode* input_node = inputs[i].m_node;
 | 
			
		||||
      if (input_node == nullptr) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (inputs[i].m_input_name == "Input0" && m_blend_weight < 0.999) {
 | 
			
		||||
        input_node->m_state = AnimNodeEvalState::Activated;
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (inputs[i].m_input_name == "Input1" && m_blend_weight > 0.001) {
 | 
			
		||||
        input_node->m_state = AnimNodeEvalState::Activated;
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  virtual void UpdateTime(float dt, std::vector<NodeInput>& inputs) {
 | 
			
		||||
    if (!m_sync_blend) {
 | 
			
		||||
      m_time_now = m_time_now + dt;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (size_t i = 0, n = inputs.size(); i < n; i++) {
 | 
			
		||||
      AnimNode* input_node = inputs[i].m_node;
 | 
			
		||||
      if (input_node == nullptr) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (input_node->m_state != AnimNodeEvalState::Deactivated) {
 | 
			
		||||
        if (!m_sync_blend) {
 | 
			
		||||
          input_node->m_time_now = m_time_now;
 | 
			
		||||
        }
 | 
			
		||||
        input_node->m_state = AnimNodeEvalState::TimeUpdated;
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <>
 | 
			
		||||
struct NodeSocketAccessor<Blend2Node> : public NodeSocketAccessorBase {
 | 
			
		||||
  NodeSocketAccessor(AnimNode* node_) {
 | 
			
		||||
    Blend2Node* node = dynamic_cast<Blend2Node*>(node_);
 | 
			
		||||
    RegisterInput("Input0", &node->m_input0);
 | 
			
		||||
    RegisterInput("Input1", &node->m_input1);
 | 
			
		||||
    RegisterInput(
 | 
			
		||||
        "Weight",
 | 
			
		||||
        &node->m_blend_weight,
 | 
			
		||||
        SocketFlags::SocketFlagAffectsTime);
 | 
			
		||||
 | 
			
		||||
    RegisterOutput("Output", &node->m_output);
 | 
			
		||||
 | 
			
		||||
    RegisterProperty("Sync", &node->m_sync_blend);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  virtual void UpdateFlags() override {
 | 
			
		||||
    Socket* weight_input_socket = FindSocket(m_inputs, "Weight");
 | 
			
		||||
    assert(weight_input_socket != nullptr);
 | 
			
		||||
 | 
			
		||||
    if (GetProperty<bool>("Sync", false) == true) {
 | 
			
		||||
      weight_input_socket->m_flags = SocketFlags::SocketFlagAffectsTime;
 | 
			
		||||
    } else {
 | 
			
		||||
      weight_input_socket->m_flags = 0;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// SpeedScaleNode
 | 
			
		||||
//
 | 
			
		||||
struct SpeedScaleNode : public AnimNode {
 | 
			
		||||
  AnimData m_input;
 | 
			
		||||
  AnimData* m_output = nullptr;
 | 
			
		||||
  float m_speed_scale = 0.f;
 | 
			
		||||
 | 
			
		||||
  virtual void UpdateTime(float time_last, float time_now) {
 | 
			
		||||
    m_time_last = time_last;
 | 
			
		||||
    m_time_now = time_last + (time_now - time_last) * m_speed_scale;
 | 
			
		||||
    m_state = AnimNodeEvalState::TimeUpdated;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <>
 | 
			
		||||
struct NodeSocketAccessor<SpeedScaleNode> : public NodeSocketAccessorBase {
 | 
			
		||||
  NodeSocketAccessor(AnimNode* node_) {
 | 
			
		||||
    SpeedScaleNode* node = dynamic_cast<SpeedScaleNode*>(node_);
 | 
			
		||||
    RegisterInput(
 | 
			
		||||
        "SpeedScale",
 | 
			
		||||
        &node->m_speed_scale,
 | 
			
		||||
        SocketFlags::SocketFlagAffectsTime);
 | 
			
		||||
    RegisterInput("Input", &node->m_input);
 | 
			
		||||
 | 
			
		||||
    RegisterOutput("Output", &node->m_output);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// AnimSamplerNode
 | 
			
		||||
//
 | 
			
		||||
struct AnimSamplerNode : public AnimNode {
 | 
			
		||||
  AnimData* m_output = nullptr;
 | 
			
		||||
  std::string m_filename;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <>
 | 
			
		||||
struct NodeSocketAccessor<AnimSamplerNode> : public NodeSocketAccessorBase {
 | 
			
		||||
  NodeSocketAccessor(AnimNode* node_) {
 | 
			
		||||
    AnimSamplerNode* node = dynamic_cast<AnimSamplerNode*>(node_);
 | 
			
		||||
    RegisterOutput("Output", &node->m_output);
 | 
			
		||||
 | 
			
		||||
    RegisterProperty("Filename", &node->m_filename);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// AnimGraphResource
 | 
			
		||||
//
 | 
			
		||||
struct AnimGraphConnection {
 | 
			
		||||
  const AnimNodeResource* m_source_node = nullptr;
 | 
			
		||||
  const Socket* m_source_socket = nullptr;
 | 
			
		||||
  const AnimNodeResource* m_target_node = nullptr;
 | 
			
		||||
  const Socket* m_target_socket = nullptr;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct AnimGraphResource {
 | 
			
		||||
  std::string m_name;
 | 
			
		||||
  std::vector<AnimNodeResource> m_nodes;
 | 
			
		||||
  std::vector<AnimGraphConnection> m_connections;
 | 
			
		||||
 | 
			
		||||
  ~AnimGraphResource() {
 | 
			
		||||
    for (size_t i = 0, n = m_nodes.size(); i < n; i++) {
 | 
			
		||||
      delete m_nodes[i].m_anim_node;
 | 
			
		||||
      delete m_nodes[i].m_socket_accessor;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  AnimGraphResource() { clear(); }
 | 
			
		||||
 | 
			
		||||
  void clear();
 | 
			
		||||
  void clearNodes();
 | 
			
		||||
  void initGraphConnectors();
 | 
			
		||||
  bool saveToFile(const char* filename) const;
 | 
			
		||||
  bool loadFromFile(const char* filename);
 | 
			
		||||
 | 
			
		||||
  AnimNodeResource& getGraphOutputNode() { return m_nodes[0]; }
 | 
			
		||||
  AnimNodeResource& getGraphInputNode() { return m_nodes[1]; }
 | 
			
		||||
 | 
			
		||||
  const AnimNodeResource& getGraphOutputNode() const { return m_nodes[0]; }
 | 
			
		||||
  const AnimNodeResource& getGraphInputNode() const { return m_nodes[1]; }
 | 
			
		||||
 | 
			
		||||
  size_t getNodeIndex(const AnimNodeResource& node_resource) const {
 | 
			
		||||
    for (size_t i = 0, n = m_nodes.size(); i < n; i++) {
 | 
			
		||||
      if (&m_nodes[i] == &node_resource) {
 | 
			
		||||
        return i;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return -1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  size_t addNode(AnimNodeResource node_resource) {
 | 
			
		||||
    m_nodes.push_back(node_resource);
 | 
			
		||||
    return m_nodes.size() - 1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool connectSockets(
 | 
			
		||||
      const AnimNodeResource& source_node,
 | 
			
		||||
      const std::string& source_socket_name,
 | 
			
		||||
      const AnimNodeResource& target_node,
 | 
			
		||||
      const std::string& target_socket_name) {
 | 
			
		||||
    size_t source_index = -1;
 | 
			
		||||
    size_t target_index = -1;
 | 
			
		||||
    for (size_t i = 0, n = m_nodes.size(); i < n; i++) {
 | 
			
		||||
      if (&source_node == &m_nodes[i]) {
 | 
			
		||||
        source_index = i;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (&target_node == &m_nodes[i]) {
 | 
			
		||||
        target_index = i;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (source_index < m_nodes.size() && target_index < m_nodes.size()) {
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (source_index >= m_nodes.size() || target_index >= m_nodes.size()) {
 | 
			
		||||
      std::cerr << "Cannot connect nodes: could not find nodes." << std::endl;
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Socket* source_socket =
 | 
			
		||||
        source_node.m_socket_accessor->FindOutputSocket(source_socket_name);
 | 
			
		||||
    Socket* target_socket =
 | 
			
		||||
        target_node.m_socket_accessor->FindInputSocket(target_socket_name);
 | 
			
		||||
 | 
			
		||||
    if (source_socket == nullptr || target_socket == nullptr) {
 | 
			
		||||
      std::cerr << "Cannot connect nodes: could not find sockets." << std::endl;
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    AnimGraphConnection connection;
 | 
			
		||||
    connection.m_source_node = &source_node;
 | 
			
		||||
    connection.m_source_socket = source_socket;
 | 
			
		||||
    connection.m_target_node = &target_node;
 | 
			
		||||
    connection.m_target_socket = target_socket;
 | 
			
		||||
    m_connections.push_back(connection);
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static inline AnimNode* AnimNodeFactory(const std::string& name) {
 | 
			
		||||
  AnimNode* result;
 | 
			
		||||
  if (name == "Blend2") {
 | 
			
		||||
    result = new Blend2Node;
 | 
			
		||||
  } else if (name == "SpeedScale") {
 | 
			
		||||
    result = new SpeedScaleNode;
 | 
			
		||||
  } else if (name == "AnimSampler") {
 | 
			
		||||
    result = new AnimSamplerNode;
 | 
			
		||||
  } else if (name == "BlendTree") {
 | 
			
		||||
    result = new BlendTreeNode;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (result != nullptr) {
 | 
			
		||||
    result->m_node_type_name = name;
 | 
			
		||||
    return result;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  std::cerr << "Invalid node type: " << name << std::endl;
 | 
			
		||||
  return nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static inline NodeSocketAccessorBase* AnimNodeAccessorFactory(
 | 
			
		||||
    const std::string& node_type_name,
 | 
			
		||||
    AnimNode* node) {
 | 
			
		||||
  if (node_type_name == "Blend2") {
 | 
			
		||||
    return new NodeSocketAccessor<Blend2Node>(node);
 | 
			
		||||
  } else if (node_type_name == "SpeedScale") {
 | 
			
		||||
    return new NodeSocketAccessor<SpeedScaleNode>(node);
 | 
			
		||||
  } else if (node_type_name == "AnimSampler") {
 | 
			
		||||
    return new NodeSocketAccessor<AnimSamplerNode>(node);
 | 
			
		||||
  } else if (node_type_name == "BlendTree") {
 | 
			
		||||
    return new NodeSocketAccessor<BlendTreeNode>(node);
 | 
			
		||||
  } else {
 | 
			
		||||
    std::cerr << "Invalid node type name " << node_type_name << "."
 | 
			
		||||
              << std::endl;
 | 
			
		||||
  }
 | 
			
		||||
  return nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static inline AnimNodeResource AnimNodeResourceFactory(
 | 
			
		||||
    const std::string& node_type_name) {
 | 
			
		||||
  AnimNodeResource result;
 | 
			
		||||
  result.m_type_name = node_type_name;
 | 
			
		||||
  result.m_anim_node = AnimNodeFactory(node_type_name);
 | 
			
		||||
  result.m_socket_accessor =
 | 
			
		||||
      AnimNodeAccessorFactory(node_type_name, result.m_anim_node);
 | 
			
		||||
  return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// AnimGraph (Runtime)
 | 
			
		||||
//
 | 
			
		||||
struct AnimGraph {
 | 
			
		||||
  AnimData m_local_transforms;
 | 
			
		||||
 | 
			
		||||
  std::vector<AnimNode*> m_nodes;
 | 
			
		||||
  std::vector<AnimNode*> m_eval_ordered_nodes;
 | 
			
		||||
  std::vector<std::vector<NodeInput> > m_node_inputs;
 | 
			
		||||
  NodeSocketAccessorBase* m_socket_accessor;
 | 
			
		||||
  char* m_input_buffer = nullptr;
 | 
			
		||||
  char* m_output_buffer = nullptr;
 | 
			
		||||
 | 
			
		||||
  std::vector<Socket>& getGraphOutputs() { return m_socket_accessor->m_inputs; }
 | 
			
		||||
  std::vector<Socket>& getGraphInputs() { return m_socket_accessor->m_outputs; }
 | 
			
		||||
 | 
			
		||||
  ~AnimGraph() {
 | 
			
		||||
    delete[] m_input_buffer;
 | 
			
		||||
    delete[] m_output_buffer;
 | 
			
		||||
 | 
			
		||||
    for (int i = 0; i < m_nodes.size(); i++) {
 | 
			
		||||
      delete m_nodes[i];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    delete m_socket_accessor;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void updateOrderedNodes();
 | 
			
		||||
  void markActiveNodes();
 | 
			
		||||
  bool checkIsNodeActive(AnimNode* node) {
 | 
			
		||||
    return node->m_state != AnimNodeEvalState::Deactivated;
 | 
			
		||||
  }
 | 
			
		||||
  void evalSyncTracks();
 | 
			
		||||
  void updateTime(float dt);
 | 
			
		||||
  void evaluate();
 | 
			
		||||
  void reset() {
 | 
			
		||||
    for (size_t i = 0, n = m_nodes.size(); i < n; i++) {
 | 
			
		||||
      m_nodes[i]->m_time_now = 0.f;
 | 
			
		||||
      m_nodes[i]->m_time_last = 0.f;
 | 
			
		||||
      m_nodes[i]->m_state = AnimNodeEvalState::Undefined;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void* getOutput(const std::string& name) const;
 | 
			
		||||
  void* getInput(const std::string& name) const;
 | 
			
		||||
 | 
			
		||||
  int getNodeEvalOrderIndex(const AnimNode* node) {
 | 
			
		||||
    for (size_t i = 0, n = m_eval_ordered_nodes.size(); i < n; i++) {
 | 
			
		||||
      if (m_eval_ordered_nodes[i] == node) {
 | 
			
		||||
        return i;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return -1;
 | 
			
		||||
  }
 | 
			
		||||
  AnimNode* getAnimNodeForInput(
 | 
			
		||||
      size_t node_index,
 | 
			
		||||
      const std::string& input_name) {
 | 
			
		||||
    assert(node_index < m_nodes.size());
 | 
			
		||||
    assert(node_index < m_node_inputs.size());
 | 
			
		||||
 | 
			
		||||
    std::vector<NodeInput>& node_inputs = m_node_inputs[node_index];
 | 
			
		||||
    for (size_t i = 0, n = node_inputs.size(); i < n; i++) {
 | 
			
		||||
      if (node_inputs[i].m_input_name == input_name) {
 | 
			
		||||
        return node_inputs[i].m_node;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return nullptr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  AnimNode* getAnimNode(const char* name) {
 | 
			
		||||
    for (size_t i = 0; i < m_nodes.size(); i++) {
 | 
			
		||||
      if (m_nodes[i]->m_name == name) {
 | 
			
		||||
        return m_nodes[i];
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return nullptr;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static AnimGraph createFromResource(const AnimGraphResource& resource) {
 | 
			
		||||
    AnimGraph result;
 | 
			
		||||
 | 
			
		||||
    // create nodes
 | 
			
		||||
    for (int i = 0; i < resource.m_nodes.size(); i++) {
 | 
			
		||||
      const AnimNodeResource& node_resource = resource.m_nodes[i];
 | 
			
		||||
      AnimNode* node = AnimNodeFactory(node_resource.m_type_name.c_str());
 | 
			
		||||
      node->m_name = node_resource.m_name;
 | 
			
		||||
      node->m_node_type_name = node_resource.m_type_name;
 | 
			
		||||
      node->m_index = i;
 | 
			
		||||
      result.m_nodes.push_back(node);
 | 
			
		||||
 | 
			
		||||
      assert(node_resource.m_socket_accessor != nullptr);
 | 
			
		||||
      result.m_node_inputs.push_back(std::vector<NodeInput>());
 | 
			
		||||
      std::vector<NodeInput>& node_inputs = result.m_node_inputs.back();
 | 
			
		||||
 | 
			
		||||
      for (int j = 0, n = node_resource.m_socket_accessor->m_inputs.size();
 | 
			
		||||
           j < n;
 | 
			
		||||
           j++) {
 | 
			
		||||
        const Socket& input_socket =
 | 
			
		||||
            node_resource.m_socket_accessor->m_inputs[j];
 | 
			
		||||
 | 
			
		||||
        NodeInput input;
 | 
			
		||||
        input.m_node = nullptr;
 | 
			
		||||
        input.m_type = input_socket.m_type;
 | 
			
		||||
        input.m_input_name = input_socket.m_name;
 | 
			
		||||
 | 
			
		||||
        node_inputs.push_back(input);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Prepare graph inputs
 | 
			
		||||
    result.m_socket_accessor =
 | 
			
		||||
        AnimNodeAccessorFactory("BlendTree", result.m_nodes[0]);
 | 
			
		||||
    result.m_socket_accessor->m_outputs =
 | 
			
		||||
        resource.m_nodes[1].m_socket_accessor->m_outputs;
 | 
			
		||||
    result.m_socket_accessor->m_inputs =
 | 
			
		||||
        resource.m_nodes[0].m_socket_accessor->m_inputs;
 | 
			
		||||
 | 
			
		||||
    // inputs
 | 
			
		||||
    int input_block_size = 0;
 | 
			
		||||
    std::vector<Socket>& graph_inputs = result.getGraphInputs();
 | 
			
		||||
    for (int i = 0; i < graph_inputs.size(); i++) {
 | 
			
		||||
      input_block_size += sizeof(void*);
 | 
			
		||||
    }
 | 
			
		||||
    result.m_input_buffer = new char[input_block_size];
 | 
			
		||||
    memset(result.m_input_buffer, 0, input_block_size);
 | 
			
		||||
 | 
			
		||||
    int input_block_offset = 0;
 | 
			
		||||
    for (int i = 0; i < graph_inputs.size(); i++) {
 | 
			
		||||
      if (graph_inputs[i].m_type == SocketType::SocketTypeAnimation) {
 | 
			
		||||
      }
 | 
			
		||||
      graph_inputs[i].m_value.ptr =
 | 
			
		||||
          (void*)&result.m_input_buffer[input_block_offset];
 | 
			
		||||
      input_block_offset += sizeof(void*);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // outputs
 | 
			
		||||
    int output_block_size = 0;
 | 
			
		||||
    std::vector<Socket>& graph_outputs = result.getGraphOutputs();
 | 
			
		||||
    for (int i = 0; i < graph_outputs.size(); i++) {
 | 
			
		||||
      output_block_size += graph_outputs[i].m_type_size;
 | 
			
		||||
    }
 | 
			
		||||
    result.m_output_buffer = new char[output_block_size];
 | 
			
		||||
    memset(result.m_output_buffer, 0, output_block_size);
 | 
			
		||||
 | 
			
		||||
    int output_block_offset = 0;
 | 
			
		||||
    for (int i = 0; i < graph_outputs.size(); i++) {
 | 
			
		||||
      if (graph_outputs[i].m_type == SocketType::SocketTypeAnimation) {
 | 
			
		||||
      }
 | 
			
		||||
      graph_outputs[i].m_value.ptr =
 | 
			
		||||
          (void*)&result.m_output_buffer[output_block_offset];
 | 
			
		||||
      output_block_offset += graph_outputs[i].m_type_size;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // connect the nodes
 | 
			
		||||
    for (int i = 0; i < resource.m_connections.size(); i++) {
 | 
			
		||||
      const AnimGraphConnection& connection = resource.m_connections[i];
 | 
			
		||||
      std::string source_node_type = "";
 | 
			
		||||
      std::string target_node_type = "";
 | 
			
		||||
      std::string source_node_name = "";
 | 
			
		||||
      std::string target_node_name = "";
 | 
			
		||||
      AnimNode* source_node = nullptr;
 | 
			
		||||
      AnimNode* target_node = nullptr;
 | 
			
		||||
      NodeSocketAccessorBase* source_node_accessor = nullptr;
 | 
			
		||||
      NodeSocketAccessorBase* target_node_accessor = nullptr;
 | 
			
		||||
      SocketType source_type;
 | 
			
		||||
      SocketType target_type;
 | 
			
		||||
      size_t source_socket_index = -1;
 | 
			
		||||
      size_t target_socket_index = -1;
 | 
			
		||||
 | 
			
		||||
      if (connection.m_source_node != nullptr) {
 | 
			
		||||
        size_t node_index = resource.getNodeIndex(*connection.m_source_node);
 | 
			
		||||
        if (node_index == -1) {
 | 
			
		||||
          std::cerr << "Could not find source node index." << std::endl;
 | 
			
		||||
          continue;
 | 
			
		||||
        }
 | 
			
		||||
        source_node = result.m_nodes[node_index];
 | 
			
		||||
        source_node_name = source_node->m_name;
 | 
			
		||||
        source_node_type = source_node->m_node_type_name;
 | 
			
		||||
        if (node_index == 1) {
 | 
			
		||||
          source_node_accessor = result.m_socket_accessor;
 | 
			
		||||
        } else {
 | 
			
		||||
          source_node_accessor =
 | 
			
		||||
              AnimNodeAccessorFactory(source_node_type, source_node);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (connection.m_target_node != nullptr) {
 | 
			
		||||
        size_t node_index = resource.getNodeIndex(*connection.m_target_node);
 | 
			
		||||
        if (node_index == -1) {
 | 
			
		||||
          std::cerr << "Could not find source node index." << std::endl;
 | 
			
		||||
          continue;
 | 
			
		||||
        }
 | 
			
		||||
        target_node = result.m_nodes[node_index];
 | 
			
		||||
        target_node_name = target_node->m_name;
 | 
			
		||||
        target_node_type = target_node->m_node_type_name;
 | 
			
		||||
        if (node_index == 0) {
 | 
			
		||||
          target_node_accessor = result.m_socket_accessor;
 | 
			
		||||
        } else {
 | 
			
		||||
          target_node_accessor =
 | 
			
		||||
              AnimNodeAccessorFactory(target_node_type, target_node);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      assert(source_node != nullptr);
 | 
			
		||||
      assert(target_node != nullptr);
 | 
			
		||||
 | 
			
		||||
      //
 | 
			
		||||
      // Map resource node sockets to graph instance node sockets
 | 
			
		||||
      //
 | 
			
		||||
      if (connection.m_source_socket == nullptr) {
 | 
			
		||||
        std::cerr << "Invalid source socket for connection " << i << "."
 | 
			
		||||
                  << std::endl;
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (connection.m_target_socket == nullptr) {
 | 
			
		||||
        std::cerr << "Invalid source socket for connection " << i << "."
 | 
			
		||||
                  << std::endl;
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      source_socket_index = source_node_accessor->GetOutputIndex(
 | 
			
		||||
          connection.m_source_socket->m_name);
 | 
			
		||||
      if (source_socket_index == -1) {
 | 
			
		||||
        std::cerr << "Invalid source socket "
 | 
			
		||||
                  << connection.m_source_socket->m_name << " for node "
 | 
			
		||||
                  << connection.m_source_node->m_name << "." << std::endl;
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      const Socket* source_socket =
 | 
			
		||||
          &source_node_accessor->m_outputs[source_socket_index];
 | 
			
		||||
 | 
			
		||||
      target_socket_index = target_node_accessor->GetInputIndex(
 | 
			
		||||
          connection.m_target_socket->m_name);
 | 
			
		||||
      if (target_socket_index == -1) {
 | 
			
		||||
        std::cerr << "Invalid target socket "
 | 
			
		||||
                  << connection.m_target_socket->m_name << " for node "
 | 
			
		||||
                  << connection.m_target_node->m_name << "." << std::endl;
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      const Socket* target_socket =
 | 
			
		||||
          &target_node_accessor->m_inputs[target_socket_index];
 | 
			
		||||
 | 
			
		||||
      if (source_socket->m_type != target_socket->m_type) {
 | 
			
		||||
        std::cerr << "Cannot connect sockets: invalid types!" << std::endl;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      //
 | 
			
		||||
      // Wire up outputs to inputs.
 | 
			
		||||
      //
 | 
			
		||||
      (*source_socket->m_value.ptr_ptr) = target_socket->m_value.ptr;
 | 
			
		||||
 | 
			
		||||
      size_t target_node_index = target_node->m_index;
 | 
			
		||||
 | 
			
		||||
      std::vector<NodeInput>& node_inputs =
 | 
			
		||||
          result.m_node_inputs[target_node_index];
 | 
			
		||||
      for (int j = 0, n = node_inputs.size(); j < n; j++) {
 | 
			
		||||
        if (node_inputs[j].m_input_name == target_socket->m_name) {
 | 
			
		||||
          node_inputs[j].m_node = source_node;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (target_node_accessor != result.m_socket_accessor) {
 | 
			
		||||
        delete target_node_accessor;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (source_node_accessor != result.m_socket_accessor) {
 | 
			
		||||
        delete source_node_accessor;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    result.updateOrderedNodes();
 | 
			
		||||
    result.reset();
 | 
			
		||||
 | 
			
		||||
    return result;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace AniGraph
 | 
			
		||||
 | 
			
		||||
#endif  //ANIMTESTBED_ANIMGRAPHRESOURCE_H
 | 
			
		||||
@ -1,5 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by martin on 12.11.21.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "AnimNode.h"
 | 
			
		||||
@ -1,56 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by martin on 12.11.21.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef ANIMTESTBED_ANIMNODE_H
 | 
			
		||||
#define ANIMTESTBED_ANIMNODE_H
 | 
			
		||||
 | 
			
		||||
#include <ozz/base/maths/transform.h>
 | 
			
		||||
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
#include "AnimationController.h"
 | 
			
		||||
#include "SkinnedMesh.h"
 | 
			
		||||
 | 
			
		||||
enum class AnimNodeType { Blend, SpeedScale, AnimSampler };
 | 
			
		||||
 | 
			
		||||
struct AnimNode {
 | 
			
		||||
  AnimNode(AnimationController* animation_controller)
 | 
			
		||||
      : m_animation_controller(animation_controller),
 | 
			
		||||
        m_time_current(0.f),
 | 
			
		||||
        m_is_time_synced(false) {}
 | 
			
		||||
  virtual ~AnimNode(){};
 | 
			
		||||
 | 
			
		||||
  AnimNodeType m_anim_node_type;
 | 
			
		||||
  std::string m_name;
 | 
			
		||||
  AnimationController* m_animation_controller;
 | 
			
		||||
 | 
			
		||||
  // When synced then current time is relative to the node's anim duration.:w
 | 
			
		||||
  bool m_is_time_synced;
 | 
			
		||||
  float m_time_current;
 | 
			
		||||
  SyncTrack m_sync_track;
 | 
			
		||||
 | 
			
		||||
  virtual void Reset() { m_time_current = 0.f; }
 | 
			
		||||
 | 
			
		||||
  // Mark current node according to is_synced and propagate flag to animation inputs.
 | 
			
		||||
  virtual void UpdateIsSynced(bool is_synced) = 0;
 | 
			
		||||
 | 
			
		||||
  // Evaluate the animation duration of this node. All input nodes must have already evaluated
 | 
			
		||||
  // their anim durations.
 | 
			
		||||
  virtual void UpdateSyncTrack() = 0;
 | 
			
		||||
 | 
			
		||||
  // Evaluate current time and propagate time step to inputs.
 | 
			
		||||
  virtual void UpdateTime(float dt) = 0;
 | 
			
		||||
 | 
			
		||||
  // Evaluate the current node and write output to local_matrices.
 | 
			
		||||
  virtual void Evaluate(
 | 
			
		||||
      ozz::vector<ozz::math::SoaTransform>* local_matrices,
 | 
			
		||||
      ozz::math::Transform* root_transform) = 0;
 | 
			
		||||
 | 
			
		||||
  // Returns a list of animation nodes that provide input for this node.
 | 
			
		||||
  virtual void GetInputNodes(std::vector<AnimNode*>& input_nodes) const = 0 ;
 | 
			
		||||
 | 
			
		||||
  virtual void DrawDebugUi(){};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif  //ANIMTESTBED_ANIMNODE_H
 | 
			
		||||
@ -1,74 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by martin on 12.11.21.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "AnimSamplerNode.h"
 | 
			
		||||
 | 
			
		||||
#include "ozzutils.h"
 | 
			
		||||
 | 
			
		||||
#include <imgui.h>
 | 
			
		||||
 | 
			
		||||
#include "../SkinnedMesh.h"
 | 
			
		||||
#include "../ozzutils.h"
 | 
			
		||||
 | 
			
		||||
void AnimSamplerNode::SetAnimation(ozz::animation::Animation* animation, const SyncTrack& sync_track) {
 | 
			
		||||
  m_animation = animation;
 | 
			
		||||
 | 
			
		||||
  const SkinnedMesh* skinned_mesh = m_animation_controller->m_skinned_mesh;
 | 
			
		||||
  const int num_soa_joints = skinned_mesh->m_skeleton.num_soa_joints();
 | 
			
		||||
  const int num_joints = skinned_mesh->m_skeleton.num_joints();
 | 
			
		||||
  m_local_matrices.resize(num_soa_joints);
 | 
			
		||||
  m_sampling_cache.Resize(num_joints);
 | 
			
		||||
 | 
			
		||||
  m_sync_track = sync_track;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnimSamplerNode::Evaluate(
 | 
			
		||||
    ozz::vector<ozz::math::SoaTransform>* local_matrices,
 | 
			
		||||
    ozz::math::Transform* root_transform) {
 | 
			
		||||
  ozz::animation::SamplingJob sampling_job;
 | 
			
		||||
  sampling_job.animation = m_animation;
 | 
			
		||||
  sampling_job.cache = &m_sampling_cache;
 | 
			
		||||
  sampling_job.ratio = m_anim_ratio;
 | 
			
		||||
  sampling_job.output = make_span(*local_matrices);
 | 
			
		||||
  if (!sampling_job.Run()) {
 | 
			
		||||
    ozz::log::Err() << "Error sampling animation." << std::endl;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (root_transform == nullptr) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ozz::math::Transform root_trans_prev;
 | 
			
		||||
  calc_bone_translation (m_anim_ratio_prev, 0, m_animation, root_trans_prev);
 | 
			
		||||
 | 
			
		||||
  ozz::math::Transform root_trans_cur;
 | 
			
		||||
  calc_bone_translation (m_anim_ratio, 0, m_animation, root_trans_cur);
 | 
			
		||||
 | 
			
		||||
  root_transform->translation = root_trans_cur.translation - root_trans_prev.translation;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnimSamplerNode::DrawDebugUi() {
 | 
			
		||||
  const SkinnedMesh* skinned_mesh = m_animation_controller->m_skinned_mesh;
 | 
			
		||||
  int anim_count = skinned_mesh->m_animation_names.size();
 | 
			
		||||
  const char* items[255] = {0};
 | 
			
		||||
  int item_current = 0;
 | 
			
		||||
  for (int i = 0; i < anim_count; i++) {
 | 
			
		||||
    items[i] = skinned_mesh->m_animation_names[i].c_str();
 | 
			
		||||
    if (skinned_mesh->m_animations[i] == m_animation) {
 | 
			
		||||
      item_current = i;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (ImGui::Combo("Animation", &item_current, items, anim_count)) {
 | 
			
		||||
    m_animation = skinned_mesh->m_animations[item_current];
 | 
			
		||||
    m_sync_track = skinned_mesh->m_animation_sync_track[item_current];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ImGui::Checkbox("Override", &m_override_ratio);
 | 
			
		||||
  ImGui::SameLine();
 | 
			
		||||
  ImGui::SliderFloat("Ratio", &m_anim_ratio, 0.f, 1.f);
 | 
			
		||||
 | 
			
		||||
  ImGui::Text("SyncTrack");
 | 
			
		||||
  m_sync_track.DrawDebugUi();
 | 
			
		||||
}
 | 
			
		||||
@ -1,85 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by martin on 12.11.21.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef ANIMTESTBED_ANIMSAMPLERNODE_H
 | 
			
		||||
#define ANIMTESTBED_ANIMSAMPLERNODE_H
 | 
			
		||||
 | 
			
		||||
#include "../AnimNode.h"
 | 
			
		||||
 | 
			
		||||
struct AnimSamplerNode : public AnimNode {
 | 
			
		||||
  AnimSamplerNode(AnimationController* animation_controller)
 | 
			
		||||
      : AnimNode(animation_controller),
 | 
			
		||||
        m_time_prev(0.f),
 | 
			
		||||
        m_override_ratio(false),
 | 
			
		||||
        m_anim_ratio(0.f),
 | 
			
		||||
        m_root_bone_index(0) {
 | 
			
		||||
    assert(m_time_current < 100.0f);
 | 
			
		||||
    m_anim_node_type = AnimNodeType::AnimSampler;
 | 
			
		||||
  };
 | 
			
		||||
  virtual ~AnimSamplerNode() {}
 | 
			
		||||
 | 
			
		||||
  ozz::animation::Animation* m_animation;
 | 
			
		||||
 | 
			
		||||
  float m_time_prev;
 | 
			
		||||
  bool m_override_ratio;
 | 
			
		||||
  float m_anim_ratio;
 | 
			
		||||
  float m_anim_ratio_prev;
 | 
			
		||||
  bool m_root_bone_index;
 | 
			
		||||
 | 
			
		||||
  ozz::vector<ozz::math::SoaTransform> m_local_matrices;
 | 
			
		||||
  ozz::vector<ozz::math::SoaTransform> m_root_output;
 | 
			
		||||
  ozz::animation::SamplingCache m_sampling_cache;
 | 
			
		||||
 | 
			
		||||
  void SetAnimation(
 | 
			
		||||
      ozz::animation::Animation* animation,
 | 
			
		||||
      const SyncTrack& sync_track);
 | 
			
		||||
 | 
			
		||||
  virtual void UpdateIsSynced(bool is_synced) override {
 | 
			
		||||
    m_is_time_synced = is_synced;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  virtual void UpdateSyncTrack() override {}
 | 
			
		||||
 | 
			
		||||
  virtual void UpdateTime(float dt) override {
 | 
			
		||||
    if (m_override_ratio) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    m_time_prev = m_time_current;
 | 
			
		||||
    m_time_current += dt;
 | 
			
		||||
 | 
			
		||||
    if (m_is_time_synced) {
 | 
			
		||||
      m_anim_ratio = m_sync_track.CalcRatioFromSyncTime(m_time_current);
 | 
			
		||||
      float prev_sync_time = m_time_current - dt;
 | 
			
		||||
      if (m_time_current < 0) {
 | 
			
		||||
        prev_sync_time += m_sync_track.m_num_intervals;
 | 
			
		||||
      }
 | 
			
		||||
      m_anim_ratio_prev = m_sync_track.CalcRatioFromSyncTime(prev_sync_time);
 | 
			
		||||
    } else {
 | 
			
		||||
      m_anim_ratio =
 | 
			
		||||
          fmodf((float)m_time_current / m_animation->duration(), 1.0f);
 | 
			
		||||
      m_anim_ratio_prev =
 | 
			
		||||
          fmodf((float)m_time_prev / m_animation->duration(), 1.0f);
 | 
			
		||||
 | 
			
		||||
      if (m_anim_ratio_prev < 0) {
 | 
			
		||||
        m_anim_ratio_prev += 1.0f;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (m_anim_ratio < 0.f) {
 | 
			
		||||
      m_anim_ratio += 1.0f;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  virtual void Evaluate(
 | 
			
		||||
      ozz::vector<ozz::math::SoaTransform>* local_matrices,
 | 
			
		||||
      ozz::math::Transform* root_transform) override;
 | 
			
		||||
 | 
			
		||||
  virtual void GetInputNodes(
 | 
			
		||||
      std::vector<AnimNode*>& input_nodes) const override{};
 | 
			
		||||
 | 
			
		||||
  virtual void DrawDebugUi();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif  //ANIMTESTBED_ANIMSAMPLERNODE_H
 | 
			
		||||
@ -1,63 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by martin on 12.11.21.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "BlendNode.h"
 | 
			
		||||
 | 
			
		||||
#include <imgui.h>
 | 
			
		||||
#include <ozz/animation/runtime/blending_job.h>
 | 
			
		||||
 | 
			
		||||
#include "../SkinnedMesh.h"
 | 
			
		||||
 | 
			
		||||
BlendNode::BlendNode(AnimationController* animation_controller)
 | 
			
		||||
    : AnimNode(animation_controller),
 | 
			
		||||
      m_input_A(nullptr),
 | 
			
		||||
      m_input_B(nullptr),
 | 
			
		||||
      m_weight(0.f),
 | 
			
		||||
      m_sync_inputs(false) {
 | 
			
		||||
  m_anim_node_type = AnimNodeType::Blend;
 | 
			
		||||
  const SkinnedMesh* skinned_mesh = m_animation_controller->m_skinned_mesh;
 | 
			
		||||
  const int num_soa_joints = skinned_mesh->m_skeleton.num_soa_joints();
 | 
			
		||||
  const int num_joints = skinned_mesh->m_skeleton.num_joints();
 | 
			
		||||
  m_local_matrices_A.resize(num_soa_joints);
 | 
			
		||||
  m_local_matrices_B.resize(num_soa_joints);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BlendNode::Evaluate(
 | 
			
		||||
    ozz::vector<ozz::math::SoaTransform>* local_matrices,
 | 
			
		||||
    ozz::math::Transform* root_transform) {
 | 
			
		||||
  const SkinnedMesh* skinned_mesh = m_animation_controller->m_skinned_mesh;
 | 
			
		||||
 | 
			
		||||
  m_input_A->Evaluate(
 | 
			
		||||
      &m_local_matrices_A,
 | 
			
		||||
      root_transform != nullptr ? &m_root_transform_A : nullptr);
 | 
			
		||||
  m_input_B->Evaluate(
 | 
			
		||||
      &m_local_matrices_B,
 | 
			
		||||
      root_transform != nullptr ? &m_root_transform_B : nullptr);
 | 
			
		||||
 | 
			
		||||
  // perform blend
 | 
			
		||||
  ozz::animation::BlendingJob::Layer layers[2];
 | 
			
		||||
  layers[0].transform = make_span(m_local_matrices_A);
 | 
			
		||||
  layers[0].weight = (1.0f - m_weight);
 | 
			
		||||
 | 
			
		||||
  layers[1].transform = make_span(m_local_matrices_B);
 | 
			
		||||
  layers[1].weight = (m_weight);
 | 
			
		||||
 | 
			
		||||
  ozz::animation::BlendingJob blend_job;
 | 
			
		||||
  blend_job.threshold = ozz::animation::BlendingJob().threshold;
 | 
			
		||||
  blend_job.layers = layers;
 | 
			
		||||
  blend_job.bind_pose = skinned_mesh->m_skeleton.joint_bind_poses();
 | 
			
		||||
  blend_job.output = make_span(*local_matrices);
 | 
			
		||||
 | 
			
		||||
  if (!blend_job.Run()) {
 | 
			
		||||
    ozz::log::Err() << "Error blending animations." << std::endl;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BlendNode::DrawDebugUi() {
 | 
			
		||||
  ImGui::SliderFloat("Weight", &m_weight, 0.f, 1.f);
 | 
			
		||||
  ImGui::Checkbox("Sync Inputs", &m_sync_inputs);
 | 
			
		||||
 | 
			
		||||
  ImGui::Text("SyncTrack");
 | 
			
		||||
  m_sync_track.DrawDebugUi();
 | 
			
		||||
}
 | 
			
		||||
@ -1,80 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by martin on 12.11.21.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef ANIMTESTBED_BLENDNODE_H
 | 
			
		||||
#define ANIMTESTBED_BLENDNODE_H
 | 
			
		||||
 | 
			
		||||
#include "../AnimNode.h"
 | 
			
		||||
 | 
			
		||||
struct BlendNode : public AnimNode {
 | 
			
		||||
  BlendNode(AnimationController* animation_controller);
 | 
			
		||||
 | 
			
		||||
  virtual ~BlendNode() {}
 | 
			
		||||
 | 
			
		||||
  AnimNode* m_input_A;
 | 
			
		||||
  AnimNode* m_input_B;
 | 
			
		||||
  float m_weight;
 | 
			
		||||
  bool m_sync_inputs;
 | 
			
		||||
 | 
			
		||||
  ozz::vector<ozz::math::SoaTransform> m_local_matrices_A;
 | 
			
		||||
  ozz::vector<ozz::math::SoaTransform> m_local_matrices_B;
 | 
			
		||||
  ozz::math::Transform m_root_transform_A;
 | 
			
		||||
  ozz::math::Transform m_root_transform_B;
 | 
			
		||||
 | 
			
		||||
  virtual void Reset() { m_time_current = 0.f; }
 | 
			
		||||
 | 
			
		||||
  virtual void UpdateIsSynced(bool is_synced) override {
 | 
			
		||||
    m_is_time_synced = is_synced;
 | 
			
		||||
 | 
			
		||||
    if (m_sync_inputs) {
 | 
			
		||||
      m_is_time_synced = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    m_input_B->UpdateIsSynced(m_is_time_synced);
 | 
			
		||||
    m_input_A->UpdateIsSynced(m_is_time_synced);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  virtual void UpdateSyncTrack() override {
 | 
			
		||||
    if (m_is_time_synced) {
 | 
			
		||||
      m_sync_track = SyncTrack::Blend(
 | 
			
		||||
          m_weight,
 | 
			
		||||
          m_input_A->m_sync_track,
 | 
			
		||||
          m_input_B->m_sync_track);
 | 
			
		||||
    } else {
 | 
			
		||||
      assert(false);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  virtual void UpdateTime(float dt) {
 | 
			
		||||
    if (m_is_time_synced) {
 | 
			
		||||
      float sync_time_old = m_sync_track.CalcSyncFromAbsTime(m_time_current);
 | 
			
		||||
      m_time_current = fmodf(m_time_current + dt, m_sync_track.m_duration);
 | 
			
		||||
      float sync_time_dt =
 | 
			
		||||
          m_sync_track.CalcSyncFromAbsTime(m_time_current) - sync_time_old;
 | 
			
		||||
 | 
			
		||||
      m_input_A->m_time_current = sync_time_old;
 | 
			
		||||
      m_input_B->m_time_current = sync_time_old;
 | 
			
		||||
      m_input_A->UpdateTime(sync_time_dt);
 | 
			
		||||
      m_input_B->UpdateTime(sync_time_dt);
 | 
			
		||||
    } else {
 | 
			
		||||
      m_time_current += dt;
 | 
			
		||||
      m_input_A->UpdateTime(dt);
 | 
			
		||||
      m_input_B->UpdateTime(dt);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  virtual void Evaluate(
 | 
			
		||||
      ozz::vector<ozz::math::SoaTransform>* local_matrices,
 | 
			
		||||
      ozz::math::Transform* root_transform) override;
 | 
			
		||||
 | 
			
		||||
  virtual void GetInputNodes(
 | 
			
		||||
      std::vector<AnimNode*>& input_nodes) const override {
 | 
			
		||||
    input_nodes.push_back(m_input_A);
 | 
			
		||||
    input_nodes.push_back(m_input_B);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  virtual void DrawDebugUi();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif  //ANIMTESTBED_BLENDNODE_H
 | 
			
		||||
@ -1,67 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by martin on 19.11.21.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "BlendSpace1D.h"
 | 
			
		||||
 | 
			
		||||
#include <imgui.h>
 | 
			
		||||
#include <ozz/animation/runtime/blending_job.h>
 | 
			
		||||
 | 
			
		||||
#include "../SkinnedMesh.h"
 | 
			
		||||
 | 
			
		||||
BlendSpace1D::BlendSpace1D(AnimationController* animation_controller)
 | 
			
		||||
    : AnimNode(animation_controller),
 | 
			
		||||
      m_input_0(nullptr),
 | 
			
		||||
      m_weight_0(0.f),
 | 
			
		||||
      m_input_1(nullptr),
 | 
			
		||||
      m_weight_1(0.f),
 | 
			
		||||
      m_weight(0.f),
 | 
			
		||||
      m_sync_inputs(false) {
 | 
			
		||||
  m_anim_node_type = AnimNodeType::Blend;
 | 
			
		||||
  const SkinnedMesh* skinned_mesh = m_animation_controller->m_skinned_mesh;
 | 
			
		||||
  const int num_soa_joints = skinned_mesh->m_skeleton.num_soa_joints();
 | 
			
		||||
  const int num_joints = skinned_mesh->m_skeleton.num_joints();
 | 
			
		||||
  m_local_matrices_0.resize(num_soa_joints);
 | 
			
		||||
  m_local_matrices_1.resize(num_soa_joints);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BlendSpace1D::Evaluate(
 | 
			
		||||
    ozz::vector<ozz::math::SoaTransform>* local_matrices,
 | 
			
		||||
    ozz::math::Transform* root_transform) {
 | 
			
		||||
  const SkinnedMesh* skinned_mesh = m_animation_controller->m_skinned_mesh;
 | 
			
		||||
 | 
			
		||||
  m_input_0->Evaluate(
 | 
			
		||||
      &m_local_matrices_0,
 | 
			
		||||
      root_transform != nullptr ? &m_root_transform_0 : nullptr);
 | 
			
		||||
  m_input_1->Evaluate(
 | 
			
		||||
      &m_local_matrices_1,
 | 
			
		||||
      root_transform != nullptr ? &m_root_transform_1 : nullptr);
 | 
			
		||||
 | 
			
		||||
  // perform blend
 | 
			
		||||
  ozz::animation::BlendingJob::Layer layers[2];
 | 
			
		||||
  layers[0].transform = make_span(m_local_matrices_0);
 | 
			
		||||
  layers[0].weight = (1.0f - m_normalized_weight);
 | 
			
		||||
 | 
			
		||||
  layers[1].transform = make_span(m_local_matrices_1);
 | 
			
		||||
  layers[1].weight = (m_normalized_weight);
 | 
			
		||||
 | 
			
		||||
  ozz::animation::BlendingJob blend_job;
 | 
			
		||||
  blend_job.threshold = ozz::animation::BlendingJob().threshold;
 | 
			
		||||
  blend_job.layers = layers;
 | 
			
		||||
  blend_job.bind_pose = skinned_mesh->m_skeleton.joint_bind_poses();
 | 
			
		||||
  blend_job.output = make_span(*local_matrices);
 | 
			
		||||
 | 
			
		||||
  if (!blend_job.Run()) {
 | 
			
		||||
    ozz::log::Err() << "Error blending animations." << std::endl;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BlendSpace1D::DrawDebugUi() {
 | 
			
		||||
  float min_weight = m_input_weights[0];
 | 
			
		||||
  float max_weight = m_input_weights.back();
 | 
			
		||||
  ImGui::SliderFloat("Weight", &m_weight, min_weight, max_weight);
 | 
			
		||||
  ImGui::Checkbox("Sync Inputs", &m_sync_inputs);
 | 
			
		||||
 | 
			
		||||
  ImGui::Text("SyncTrack");
 | 
			
		||||
  m_sync_track.DrawDebugUi();
 | 
			
		||||
}
 | 
			
		||||
@ -1,110 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by martin on 19.11.21.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef ANIMTESTBED_BLENDSPACE1D_H
 | 
			
		||||
#define ANIMTESTBED_BLENDSPACE1D_H
 | 
			
		||||
 | 
			
		||||
#include "../AnimNode.h"
 | 
			
		||||
 | 
			
		||||
struct BlendSpace1D : public AnimNode {
 | 
			
		||||
  BlendSpace1D(AnimationController* animation_controller);
 | 
			
		||||
 | 
			
		||||
  virtual ~BlendSpace1D() {}
 | 
			
		||||
 | 
			
		||||
  int m_num_inputs;
 | 
			
		||||
  std::vector<float> m_input_weights;
 | 
			
		||||
  std::vector<AnimNode*> m_inputs;
 | 
			
		||||
  float m_weight;
 | 
			
		||||
  bool m_sync_inputs;
 | 
			
		||||
 | 
			
		||||
  AnimNode* m_input_0;
 | 
			
		||||
  AnimNode* m_input_1;
 | 
			
		||||
 | 
			
		||||
  float m_normalized_weight;
 | 
			
		||||
  float m_weight_0;
 | 
			
		||||
  float m_weight_1;
 | 
			
		||||
  ozz::vector<ozz::math::SoaTransform> m_local_matrices_0;
 | 
			
		||||
  ozz::vector<ozz::math::SoaTransform> m_local_matrices_1;
 | 
			
		||||
  ozz::math::Transform m_root_transform_0;
 | 
			
		||||
  ozz::math::Transform m_root_transform_1;
 | 
			
		||||
 | 
			
		||||
  virtual void Reset() { m_time_current = 0.f; }
 | 
			
		||||
 | 
			
		||||
  virtual void UpdateIsSynced(bool is_synced) override {
 | 
			
		||||
    m_is_time_synced = is_synced;
 | 
			
		||||
 | 
			
		||||
    if (m_sync_inputs) {
 | 
			
		||||
      m_is_time_synced = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    assert(m_input_weights.size() > 0);
 | 
			
		||||
    assert(
 | 
			
		||||
        m_weight >= m_input_weights[0]
 | 
			
		||||
        && m_weight <= m_input_weights[m_input_weights.size() - 1]);
 | 
			
		||||
 | 
			
		||||
    int prev_idx = 0;
 | 
			
		||||
    for (int next_idx = 1; next_idx < m_input_weights.size(); next_idx++) {
 | 
			
		||||
      if (m_input_weights[prev_idx] <= m_weight
 | 
			
		||||
          && m_input_weights[next_idx] >= m_weight) {
 | 
			
		||||
        m_input_0 = m_inputs[prev_idx];
 | 
			
		||||
        m_weight_0 = m_input_weights[prev_idx];
 | 
			
		||||
 | 
			
		||||
        m_input_1 = m_inputs[next_idx];
 | 
			
		||||
        m_weight_1 = m_input_weights[next_idx];
 | 
			
		||||
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      prev_idx = next_idx;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    m_input_0->UpdateIsSynced(m_is_time_synced);
 | 
			
		||||
    m_input_1->UpdateIsSynced(m_is_time_synced);
 | 
			
		||||
 | 
			
		||||
    m_normalized_weight = (m_weight - m_weight_0) / (m_weight_1 - m_weight_0);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  virtual void UpdateSyncTrack() override {
 | 
			
		||||
    if (m_is_time_synced) {
 | 
			
		||||
      m_sync_track = SyncTrack::Blend(
 | 
			
		||||
          m_normalized_weight,
 | 
			
		||||
          m_input_0->m_sync_track,
 | 
			
		||||
          m_input_1->m_sync_track);
 | 
			
		||||
    } else {
 | 
			
		||||
      assert(false);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  virtual void UpdateTime(float dt) {
 | 
			
		||||
    if (m_is_time_synced) {
 | 
			
		||||
      float sync_time_old = m_sync_track.CalcSyncFromAbsTime(m_time_current);
 | 
			
		||||
      m_time_current = fmodf(m_time_current + dt, m_sync_track.m_duration);
 | 
			
		||||
      float sync_time_dt =
 | 
			
		||||
          m_sync_track.CalcSyncFromAbsTime(m_time_current) - sync_time_old;
 | 
			
		||||
 | 
			
		||||
      m_input_0->m_time_current = sync_time_old;
 | 
			
		||||
      m_input_1->m_time_current = sync_time_old;
 | 
			
		||||
      m_input_0->UpdateTime(sync_time_dt);
 | 
			
		||||
      m_input_1->UpdateTime(sync_time_dt);
 | 
			
		||||
    } else {
 | 
			
		||||
      m_time_current += dt;
 | 
			
		||||
      m_input_0->UpdateTime(dt);
 | 
			
		||||
      m_input_1->UpdateTime(dt);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  virtual void Evaluate(
 | 
			
		||||
      ozz::vector<ozz::math::SoaTransform>* local_matrices,
 | 
			
		||||
      ozz::math::Transform* root_transform) override;
 | 
			
		||||
 | 
			
		||||
  virtual void GetInputNodes(
 | 
			
		||||
      std::vector<AnimNode*>& input_nodes) const override {
 | 
			
		||||
    for (int i = 0; i < m_inputs.size(); i++) {
 | 
			
		||||
      input_nodes.push_back(m_inputs[i]);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  virtual void DrawDebugUi();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif  //ANIMTESTBED_BLENDSPACE1D_H
 | 
			
		||||
@ -1,61 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by martin on 16.11.21.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "LockTranslationNode.h"
 | 
			
		||||
 | 
			
		||||
#include <imgui.h>
 | 
			
		||||
 | 
			
		||||
#include "ozz/base/maths/soa_transform.h"
 | 
			
		||||
 | 
			
		||||
void LockTranslationNode::Evaluate(
 | 
			
		||||
    ozz::vector<ozz::math::SoaTransform>* local_matrices,
 | 
			
		||||
    ozz::math::Transform* root_transform) {
 | 
			
		||||
  m_input->Evaluate(local_matrices, root_transform);
 | 
			
		||||
  ozz::math::SoaFloat3 translation =
 | 
			
		||||
      (*local_matrices)[m_locked_bone_index].translation;
 | 
			
		||||
  float x[4];
 | 
			
		||||
  float y[4];
 | 
			
		||||
  float z[4];
 | 
			
		||||
  _mm_store_ps(x, translation.x);
 | 
			
		||||
  _mm_store_ps(y, translation.y);
 | 
			
		||||
  _mm_store_ps(z, translation.z);
 | 
			
		||||
 | 
			
		||||
  if (m_lock_x) {
 | 
			
		||||
    x[0] = 0.f;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (m_lock_y) {
 | 
			
		||||
    y[0] = 0.f;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (m_lock_z) {
 | 
			
		||||
    z[0] = 0.f;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  translation.x = _mm_load_ps(x);
 | 
			
		||||
  translation.y = _mm_load_ps(y);
 | 
			
		||||
  translation.z = _mm_load_ps(z);
 | 
			
		||||
 | 
			
		||||
  //translation = ozz::math::SoaFloat3::zero();
 | 
			
		||||
  //  ozz::math::SetX(translation, 0.f);
 | 
			
		||||
  //  ozz::math::SetZ(translation, 0.f);
 | 
			
		||||
  (*local_matrices)[m_locked_bone_index].translation = translation;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LockTranslationNode::DrawDebugUi() {
 | 
			
		||||
  const ozz::animation::Skeleton& skeleton =
 | 
			
		||||
      m_animation_controller->m_skinned_mesh->m_skeleton;
 | 
			
		||||
  ozz::span<const char* const> joint_names = skeleton.joint_names();
 | 
			
		||||
 | 
			
		||||
  const char* items[255] = {0};
 | 
			
		||||
  int item_current = 0;
 | 
			
		||||
  for (int i = 0; i < joint_names.size(); i++) {
 | 
			
		||||
    items[i] = joint_names[i];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ImGui::Combo("Bone", &m_locked_bone_index, items, joint_names.size());
 | 
			
		||||
  ImGui::Checkbox("Lock X", &m_lock_x);
 | 
			
		||||
  ImGui::Checkbox("Lock Y", &m_lock_y);
 | 
			
		||||
  ImGui::Checkbox("Lock Z", &m_lock_z);
 | 
			
		||||
}
 | 
			
		||||
@ -1,53 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by martin on 16.11.21.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef ANIMTESTBED_LOCKBONES_H
 | 
			
		||||
#define ANIMTESTBED_LOCKBONES_H
 | 
			
		||||
 | 
			
		||||
#include "../AnimNode.h"
 | 
			
		||||
 | 
			
		||||
struct LockTranslationNode : public AnimNode {
 | 
			
		||||
  LockTranslationNode(AnimationController* animation_controller)
 | 
			
		||||
      : AnimNode(animation_controller),
 | 
			
		||||
        m_input(nullptr),
 | 
			
		||||
        m_locked_bone_index(0),
 | 
			
		||||
        m_lock_x(false),
 | 
			
		||||
        m_lock_y(false),
 | 
			
		||||
        m_lock_z(false) {}
 | 
			
		||||
 | 
			
		||||
  virtual ~LockTranslationNode() {}
 | 
			
		||||
 | 
			
		||||
  AnimNode* m_input;
 | 
			
		||||
  int m_locked_bone_index;
 | 
			
		||||
  bool m_lock_x;
 | 
			
		||||
  bool m_lock_y;
 | 
			
		||||
  bool m_lock_z;
 | 
			
		||||
 | 
			
		||||
  virtual void Reset() { m_time_current = 0.f; }
 | 
			
		||||
 | 
			
		||||
  virtual void UpdateIsSynced(bool is_synced) override {
 | 
			
		||||
    m_is_time_synced = is_synced;
 | 
			
		||||
 | 
			
		||||
    m_input->UpdateIsSynced(m_is_time_synced);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  virtual void UpdateSyncTrack() override {
 | 
			
		||||
    m_sync_track = m_input->m_sync_track;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  virtual void UpdateTime(float dt) { m_input->UpdateTime(dt); }
 | 
			
		||||
 | 
			
		||||
  virtual void Evaluate(
 | 
			
		||||
      ozz::vector<ozz::math::SoaTransform>* local_matrices,
 | 
			
		||||
      ozz::math::Transform* root_transform = nullptr) override;
 | 
			
		||||
 | 
			
		||||
  virtual void GetInputNodes(
 | 
			
		||||
      std::vector<AnimNode*>& input_nodes) const override {
 | 
			
		||||
    input_nodes.push_back(m_input);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  virtual void DrawDebugUi() override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif  //ANIMTESTBED_LOCKBONES_H
 | 
			
		||||
@ -1,20 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by martin on 12.11.21.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "SpeedScaleNode.h"
 | 
			
		||||
 | 
			
		||||
#include <imgui.h>
 | 
			
		||||
 | 
			
		||||
void SpeedScaleNode::DrawDebugUi() {
 | 
			
		||||
  bool is_negative = m_time_scale < 0.f;
 | 
			
		||||
  if (ImGui::Checkbox("Reverse Time", &is_negative)) {
 | 
			
		||||
    m_time_scale = m_time_scale * -1.f;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // ensure m_time_scale is positive
 | 
			
		||||
  m_time_scale = m_time_scale * (is_negative ? -1.f : 1.f);
 | 
			
		||||
  ImGui::SliderFloat("Time Scale", &m_time_scale, 0.01f, 5.f);
 | 
			
		||||
  // and back to the original negative or positive sign
 | 
			
		||||
  m_time_scale = m_time_scale * (is_negative ? -1.f : 1.f);
 | 
			
		||||
}
 | 
			
		||||
@ -1,57 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by martin on 12.11.21.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef ANIMTESTBED_SPEEDSCALENODE_H
 | 
			
		||||
#define ANIMTESTBED_SPEEDSCALENODE_H
 | 
			
		||||
 | 
			
		||||
#include "../AnimNode.h"
 | 
			
		||||
 | 
			
		||||
struct SpeedScaleNode : public AnimNode {
 | 
			
		||||
  SpeedScaleNode(AnimationController* animation_controller)
 | 
			
		||||
      : AnimNode(animation_controller), m_time_scale(1.f) {
 | 
			
		||||
    m_anim_node_type = AnimNodeType::SpeedScale;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  float m_time_scale;
 | 
			
		||||
  AnimNode* m_input_node;
 | 
			
		||||
 | 
			
		||||
  virtual void Reset() { m_time_current = 0.f; }
 | 
			
		||||
 | 
			
		||||
  virtual void UpdateIsSynced(bool is_synced) override {
 | 
			
		||||
    m_is_time_synced = is_synced;
 | 
			
		||||
    m_input_node->UpdateIsSynced(is_synced);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  virtual void UpdateSyncTrack() override {
 | 
			
		||||
    assert(fabs(m_time_scale) >= 0.01f);
 | 
			
		||||
    m_sync_track = m_input_node->m_sync_track;
 | 
			
		||||
    m_sync_track.m_duration =
 | 
			
		||||
        fabsf(m_input_node->m_sync_track.m_duration / m_time_scale);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  virtual void UpdateTime(float dt) {
 | 
			
		||||
    if (!m_is_time_synced) {
 | 
			
		||||
      m_time_current += dt * m_time_scale;
 | 
			
		||||
      m_input_node->UpdateTime(dt * m_time_scale);
 | 
			
		||||
    } else {
 | 
			
		||||
      m_time_current += dt;
 | 
			
		||||
      m_input_node->UpdateTime(dt);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  virtual void Evaluate(
 | 
			
		||||
      ozz::vector<ozz::math::SoaTransform>* local_matrices,
 | 
			
		||||
      ozz::math::Transform* root_transform) override {
 | 
			
		||||
    m_input_node->Evaluate(local_matrices, root_transform);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  virtual void GetInputNodes(
 | 
			
		||||
      std::vector<AnimNode*>& input_nodes) const override {
 | 
			
		||||
    input_nodes.push_back(m_input_node);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  virtual void DrawDebugUi() override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif  //ANIMTESTBED_SPEEDSCALENODE_H
 | 
			
		||||
@ -1,242 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by martin on 12.11.21.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "AnimationController.h"
 | 
			
		||||
 | 
			
		||||
#include <imgui.h>
 | 
			
		||||
#include <ozz/animation/runtime/local_to_model_job.h>
 | 
			
		||||
#include <ozz/animation/runtime/sampling_job.h>
 | 
			
		||||
 | 
			
		||||
#include <queue>
 | 
			
		||||
 | 
			
		||||
#include "AnimNodes/AnimSamplerNode.h"
 | 
			
		||||
#include "AnimNodes/BlendNode.h"
 | 
			
		||||
#include "AnimNodes/BlendSpace1D.h"
 | 
			
		||||
#include "AnimNodes/LockTranslationNode.h"
 | 
			
		||||
#include "AnimNodes/SpeedScaleNode.h"
 | 
			
		||||
#include "SkinnedMesh.h"
 | 
			
		||||
 | 
			
		||||
AnimationController::AnimationController(SkinnedMesh* skinned_mesh)
 | 
			
		||||
    : m_current_time(0.f), m_paused(true), m_skinned_mesh(skinned_mesh) {
 | 
			
		||||
  const int num_soa_joints = skinned_mesh->m_skeleton.num_soa_joints();
 | 
			
		||||
  const int num_joints = skinned_mesh->m_skeleton.num_joints();
 | 
			
		||||
  skinned_mesh->m_local_matrices.resize(num_soa_joints);
 | 
			
		||||
  skinned_mesh->m_model_matrices.resize(num_joints);
 | 
			
		||||
 | 
			
		||||
  ResetAnims();
 | 
			
		||||
 | 
			
		||||
  AnimSamplerNode* sampler_node0 = new AnimSamplerNode(this);
 | 
			
		||||
  sampler_node0->m_name = "AnimSampler0";
 | 
			
		||||
  sampler_node0->SetAnimation(
 | 
			
		||||
      skinned_mesh->m_animations[1],
 | 
			
		||||
      skinned_mesh->m_animation_sync_track[1]);
 | 
			
		||||
  m_anim_nodes.push_back(sampler_node0);
 | 
			
		||||
 | 
			
		||||
  AnimSamplerNode* sampler_node1 = new AnimSamplerNode(this);
 | 
			
		||||
  sampler_node1->m_name = "AnimSampler1";
 | 
			
		||||
  sampler_node1->SetAnimation(
 | 
			
		||||
      skinned_mesh->m_animations[2],
 | 
			
		||||
      skinned_mesh->m_animation_sync_track[2]);
 | 
			
		||||
  m_anim_nodes.push_back(sampler_node1);
 | 
			
		||||
 | 
			
		||||
  AnimSamplerNode* sampler_node2 = new AnimSamplerNode(this);
 | 
			
		||||
  sampler_node2->m_name = "AnimSampler2";
 | 
			
		||||
  sampler_node2->SetAnimation(
 | 
			
		||||
      skinned_mesh->m_animations[3],
 | 
			
		||||
      skinned_mesh->m_animation_sync_track[3]);
 | 
			
		||||
  m_anim_nodes.push_back(sampler_node2);
 | 
			
		||||
 | 
			
		||||
  BlendSpace1D* blend_space = new BlendSpace1D(this);
 | 
			
		||||
  blend_space->m_name = "BlendSpace0";
 | 
			
		||||
  blend_space->m_weight = 0.f;
 | 
			
		||||
  blend_space->m_inputs.push_back(sampler_node0);
 | 
			
		||||
  blend_space->m_input_weights.push_back(-1.f);
 | 
			
		||||
  blend_space->m_inputs.push_back(sampler_node1);
 | 
			
		||||
  blend_space->m_input_weights.push_back(0.f);
 | 
			
		||||
  blend_space->m_inputs.push_back(sampler_node2);
 | 
			
		||||
  blend_space->m_input_weights.push_back(1.f);
 | 
			
		||||
  m_anim_nodes.push_back(blend_space);
 | 
			
		||||
 | 
			
		||||
  SpeedScaleNode* speed_node = new SpeedScaleNode(this);
 | 
			
		||||
  speed_node->m_name = "SpeedNode0";
 | 
			
		||||
  speed_node->m_input_node = sampler_node0;
 | 
			
		||||
  m_anim_nodes.push_back(speed_node);
 | 
			
		||||
 | 
			
		||||
  BlendNode* blend_node = new BlendNode(this);
 | 
			
		||||
  blend_node->m_name = "Blend0";
 | 
			
		||||
  blend_node->m_input_A = speed_node;
 | 
			
		||||
  blend_node->m_input_B = sampler_node1;
 | 
			
		||||
  blend_node->m_sync_inputs = true;
 | 
			
		||||
  m_anim_nodes.push_back(blend_node);
 | 
			
		||||
 | 
			
		||||
  SpeedScaleNode* speed_node1 = new SpeedScaleNode(this);
 | 
			
		||||
  speed_node1->m_name = "SpeedNode1";
 | 
			
		||||
  speed_node1->m_input_node = blend_space;
 | 
			
		||||
  m_anim_nodes.push_back(speed_node1);
 | 
			
		||||
 | 
			
		||||
  LockTranslationNode* lock_node = new LockTranslationNode(this);
 | 
			
		||||
  lock_node->m_name = "LockNode0";
 | 
			
		||||
  lock_node->m_locked_bone_index = 0;
 | 
			
		||||
  lock_node->m_input = speed_node1;
 | 
			
		||||
  m_anim_nodes.push_back(lock_node);
 | 
			
		||||
 | 
			
		||||
  m_output_node = m_anim_nodes.back();
 | 
			
		||||
 | 
			
		||||
  UpdateOrderedNodes();
 | 
			
		||||
 | 
			
		||||
  m_output_node->Reset();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AnimationController::~AnimationController() {
 | 
			
		||||
  while (m_anim_nodes.size() > 0) {
 | 
			
		||||
    delete m_anim_nodes[m_anim_nodes.size() - 1];
 | 
			
		||||
    m_anim_nodes.pop_back();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  m_output_node = nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnimationController::ResetAnims() {
 | 
			
		||||
  for (int i = 0; i < m_ordered_nodes.size(); i++) {
 | 
			
		||||
    m_ordered_nodes[i]->Reset();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnimationController::UpdateOrderedNodes() {
 | 
			
		||||
  std::vector<AnimNode*> node_stack;
 | 
			
		||||
  node_stack.push_back(m_output_node);
 | 
			
		||||
 | 
			
		||||
  m_ordered_nodes.clear();
 | 
			
		||||
 | 
			
		||||
  while (node_stack.size() > 0) {
 | 
			
		||||
    AnimNode* node = node_stack.back();
 | 
			
		||||
    m_ordered_nodes.push_back(node);
 | 
			
		||||
    node_stack.pop_back();
 | 
			
		||||
 | 
			
		||||
    std::vector<AnimNode*> node_inputs;
 | 
			
		||||
    node->GetInputNodes(node_inputs);
 | 
			
		||||
    for (int i = node_inputs.size() - 1; i >= 0; i--) {
 | 
			
		||||
      node_stack.push_back(node_inputs[i]);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnimationController::UpdateTime(float dt) {
 | 
			
		||||
  if (m_output_node == nullptr) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Mark all nodes that evaluate time using sync tracks.
 | 
			
		||||
  m_output_node->UpdateIsSynced(false);
 | 
			
		||||
 | 
			
		||||
  if (m_paused) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // For all synced nodes calculate their current sync track durations
 | 
			
		||||
  for (int i = m_ordered_nodes.size() - 1; i >= 0; i--) {
 | 
			
		||||
    AnimNode* node = m_ordered_nodes[i];
 | 
			
		||||
    if (node->m_is_time_synced) {
 | 
			
		||||
      node->UpdateSyncTrack();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Update the time of all nodes.
 | 
			
		||||
  m_output_node->UpdateTime(dt);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AnimationController::Evaluate() {
 | 
			
		||||
  if (m_output_node == nullptr) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  m_output_node->Evaluate(
 | 
			
		||||
      &m_skinned_mesh->m_local_matrices,
 | 
			
		||||
      m_calc_root_transform ? &m_root_transform : nullptr);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void AnimationController::DrawDebugUi() {
 | 
			
		||||
  ImGui::SetNextWindowSize(ImVec2(500, 300), ImGuiCond_FirstUseEver);
 | 
			
		||||
  ImGui::Begin("AnimationController");
 | 
			
		||||
 | 
			
		||||
  if (ImGui::Button("Reset")) {
 | 
			
		||||
    ResetAnims();
 | 
			
		||||
  }
 | 
			
		||||
  ImGui::SameLine();
 | 
			
		||||
  if (m_paused) {
 | 
			
		||||
    if (ImGui::Button("Play")) {
 | 
			
		||||
      m_paused = false;
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    if (ImGui::Button("Pause")) {
 | 
			
		||||
      m_paused = true;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  ImGui::SameLine();
 | 
			
		||||
  if (ImGui::Button("Step")) {
 | 
			
		||||
    bool was_paused = m_paused;
 | 
			
		||||
    m_paused = false;
 | 
			
		||||
    UpdateTime(0.1);
 | 
			
		||||
    Evaluate();
 | 
			
		||||
    m_paused = was_paused;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ImVec2 node_size(200, 100);
 | 
			
		||||
  for (int i = 0; i < m_ordered_nodes.size(); i++) {
 | 
			
		||||
    AnimNode* node = m_ordered_nodes[i];
 | 
			
		||||
 | 
			
		||||
    ImGui::SetNextWindowSize(node_size, ImGuiCond_FirstUseEver);
 | 
			
		||||
    ImGui::SetNextWindowPos(
 | 
			
		||||
        ImVec2((m_ordered_nodes.size() - 1 - i) * node_size.x - i * 10, 300),
 | 
			
		||||
        ImGuiCond_FirstUseEver);
 | 
			
		||||
    ImGui::Begin(node->m_name.c_str());
 | 
			
		||||
    node->DrawDebugUi();
 | 
			
		||||
    ImGui::End();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ImGui::Text("Node States");
 | 
			
		||||
 | 
			
		||||
  ImGui::Columns(4, "Node States");  // 4-ways, with border
 | 
			
		||||
  ImGui::Separator();
 | 
			
		||||
  ImGui::Text("Name");
 | 
			
		||||
  ImGui::NextColumn();
 | 
			
		||||
  ImGui::Text("Synced");
 | 
			
		||||
  ImGui::NextColumn();
 | 
			
		||||
  ImGui::Text("Duration");
 | 
			
		||||
  ImGui::NextColumn();
 | 
			
		||||
  ImGui::Text("Time");
 | 
			
		||||
  ImGui::NextColumn();
 | 
			
		||||
 | 
			
		||||
  ImGui::Separator();
 | 
			
		||||
  static int selected = -1;
 | 
			
		||||
 | 
			
		||||
  for (int i = 0; i < m_ordered_nodes.size(); i++) {
 | 
			
		||||
    AnimNode* node = m_ordered_nodes[i];
 | 
			
		||||
    if (ImGui::Selectable(
 | 
			
		||||
            node->m_name.c_str(),
 | 
			
		||||
            selected == i,
 | 
			
		||||
            ImGuiSelectableFlags_SpanAllColumns))
 | 
			
		||||
      selected = i;
 | 
			
		||||
    bool hovered = ImGui::IsItemHovered();
 | 
			
		||||
    ImGui::NextColumn();
 | 
			
		||||
 | 
			
		||||
    ImGui::Text(node->m_is_time_synced ? "X" : "-");
 | 
			
		||||
    ImGui::NextColumn();
 | 
			
		||||
 | 
			
		||||
    ImGui::PushID((void*)&node->m_sync_track.m_duration);
 | 
			
		||||
    ImGui::Text("%2.3f", node->m_sync_track.m_duration);
 | 
			
		||||
    ImGui::NextColumn();
 | 
			
		||||
    ImGui::PopID();
 | 
			
		||||
 | 
			
		||||
    ImGui::PushID((void*)&node->m_time_current);
 | 
			
		||||
    ImGui::Text("%2.3f", node->m_time_current);
 | 
			
		||||
    ImGui::NextColumn();
 | 
			
		||||
    ImGui::PopID();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ImGui::Columns(1);
 | 
			
		||||
  ImGui::Separator();
 | 
			
		||||
 | 
			
		||||
  ImGui::End();
 | 
			
		||||
}
 | 
			
		||||
@ -1,48 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by martin on 12.11.21.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef ANIMTESTBED_ANIMATIONCONTROLLER_H
 | 
			
		||||
#define ANIMTESTBED_ANIMATIONCONTROLLER_H
 | 
			
		||||
 | 
			
		||||
#include <ozz/animation/runtime/animation.h>
 | 
			
		||||
#include <ozz/animation/runtime/sampling_job.h>
 | 
			
		||||
#include <ozz/base/containers/vector.h>
 | 
			
		||||
#include <ozz/base/maths/soa_transform.h>
 | 
			
		||||
#include <ozz/base/maths/transform.h>
 | 
			
		||||
#include <ozz/base/maths/vec_float.h>
 | 
			
		||||
 | 
			
		||||
struct SkinnedMesh;
 | 
			
		||||
struct AnimNode;
 | 
			
		||||
 | 
			
		||||
struct AnimationController {
 | 
			
		||||
  explicit AnimationController(SkinnedMesh* skinned_mesh);
 | 
			
		||||
  virtual ~AnimationController();
 | 
			
		||||
 | 
			
		||||
  void ResetAnims();
 | 
			
		||||
 | 
			
		||||
  // Creates a list of nodes where for node at index i for all inputs holds index > i.
 | 
			
		||||
  void UpdateOrderedNodes();
 | 
			
		||||
 | 
			
		||||
  // Updates all nodes.
 | 
			
		||||
  void UpdateTime(float dt);
 | 
			
		||||
 | 
			
		||||
  // Recursively evaluates all nodes.
 | 
			
		||||
  void Evaluate();
 | 
			
		||||
 | 
			
		||||
  void DrawDebugUi();
 | 
			
		||||
 | 
			
		||||
  float m_current_time;
 | 
			
		||||
  bool m_paused;
 | 
			
		||||
  bool m_calc_root_transform;
 | 
			
		||||
  ozz::math::Transform m_root_transform;
 | 
			
		||||
 | 
			
		||||
  SkinnedMesh* m_skinned_mesh = nullptr;
 | 
			
		||||
 | 
			
		||||
  AnimNode* m_output_node;
 | 
			
		||||
 | 
			
		||||
  std::vector<AnimNode*> m_anim_nodes;
 | 
			
		||||
  std::vector<AnimNode*> m_ordered_nodes;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif  //ANIMTESTBED_ANIMATIONCONTROLLER_H
 | 
			
		||||
							
								
								
									
										54
									
								
								src/main.cc
									
									
									
									
									
								
							
							
						
						
									
										54
									
								
								src/main.cc
									
									
									
									
									
								
							@ -17,7 +17,7 @@
 | 
			
		||||
 | 
			
		||||
#include "Camera.h"
 | 
			
		||||
#include "SkinnedMesh.h"
 | 
			
		||||
#include "AnimGraphEditor.h"
 | 
			
		||||
#include "src/AnimGraph/AnimGraphEditor.h"
 | 
			
		||||
#include "GLFW/glfw3.h"
 | 
			
		||||
 | 
			
		||||
const int Width = 1024;
 | 
			
		||||
@ -28,6 +28,7 @@ const int MaxIndices = MaxVertices * 3;
 | 
			
		||||
uint64_t last_time = 0;
 | 
			
		||||
bool show_imgui_demo_window = false;
 | 
			
		||||
bool show_another_window = false;
 | 
			
		||||
bool show_graph_editor = true;
 | 
			
		||||
 | 
			
		||||
sg_pass_action pass_action;
 | 
			
		||||
sg_pipeline pip;
 | 
			
		||||
@ -48,7 +49,6 @@ static void draw_imgui(ImDrawData*);
 | 
			
		||||
#include <cmath>   // fmodf
 | 
			
		||||
#include <memory>  // std::unique_ptr, std::make_unique
 | 
			
		||||
 | 
			
		||||
#include "AnimationController.h"
 | 
			
		||||
#include "SkinnedMeshRenderer.h"
 | 
			
		||||
#include "ozz/animation/runtime/animation.h"
 | 
			
		||||
#include "ozz/animation/runtime/local_to_model_job.h"
 | 
			
		||||
@ -110,15 +110,6 @@ enum class ControlMode {
 | 
			
		||||
 | 
			
		||||
ControlMode gControlMode = ControlMode::ControlModeNone;
 | 
			
		||||
 | 
			
		||||
enum class AppMode {
 | 
			
		||||
  AnimRuntime = 0,
 | 
			
		||||
  GraphEditor = 1,
 | 
			
		||||
  Debug = 2
 | 
			
		||||
};
 | 
			
		||||
const char* AppModeNames[] = {"Runtime", "Graph Editor", "Debug"};
 | 
			
		||||
 | 
			
		||||
AppMode gAppMode = AppMode::GraphEditor;
 | 
			
		||||
 | 
			
		||||
// io buffers for skeleton and animation data files, we know the max file size upfront
 | 
			
		||||
static uint8_t skel_data_buffer[4 * 1024];
 | 
			
		||||
static uint8_t anim_data_buffer[32 * 1024];
 | 
			
		||||
@ -206,7 +197,7 @@ int main() {
 | 
			
		||||
  sgldesc.sample_count = 0;
 | 
			
		||||
  sgl_setup(&sgldesc);
 | 
			
		||||
 | 
			
		||||
  printf ("default allocator: 0x%p\n", (void*)ozz::memory::default_allocator());
 | 
			
		||||
  // Animation setup
 | 
			
		||||
  SkinnedMesh skinned_mesh;
 | 
			
		||||
  skinned_mesh.LoadSkeleton("../media/MixamoYBot-skeleton.ozz");
 | 
			
		||||
  skinned_mesh.LoadAnimation("../media/Idle-loop.ozz");
 | 
			
		||||
@ -242,9 +233,6 @@ int main() {
 | 
			
		||||
 | 
			
		||||
  skinned_mesh.SetCurrentAnimation(0);
 | 
			
		||||
 | 
			
		||||
  AnimationController animation_controller (&skinned_mesh);
 | 
			
		||||
 | 
			
		||||
//  state.ozz = std::make_unique<ozz_t>();
 | 
			
		||||
  state.time.factor = 1.0f;
 | 
			
		||||
 | 
			
		||||
  Camera_Init(&state.camera);
 | 
			
		||||
@ -433,20 +421,13 @@ int main() {
 | 
			
		||||
 | 
			
		||||
    if (ImGui::BeginMainMenuBar()) {
 | 
			
		||||
      ImGui::Text("AnimTestbed");
 | 
			
		||||
      int mode_current = static_cast<int>(gAppMode);
 | 
			
		||||
      if (ImGui::Combo("Mode", &mode_current, AppModeNames, sizeof(AppModeNames) / sizeof(char*))) {
 | 
			
		||||
        switch (mode_current) {
 | 
			
		||||
          case 0: gAppMode = AppMode::AnimRuntime; break;
 | 
			
		||||
          case 1: gAppMode = AppMode::GraphEditor; break;
 | 
			
		||||
          case 2: gAppMode = AppMode::Debug; break;
 | 
			
		||||
          default: break;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      ImGui::Checkbox("Graph Editor", &show_graph_editor);
 | 
			
		||||
      ImGui::Checkbox("ImGui Demo", &show_imgui_demo_window);
 | 
			
		||||
      ImGui::EndMainMenuBar();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (gAppMode == AppMode::AnimRuntime) {
 | 
			
		||||
    // Animation Runtime
 | 
			
		||||
    {
 | 
			
		||||
      ImGui::Begin("Camera");
 | 
			
		||||
      ImGui::SliderFloat3("pos", state.camera.pos, -100.f, 100.f);
 | 
			
		||||
      ImGui::SliderFloat("near", &state.camera.near, 0.001f, 10.f);
 | 
			
		||||
@ -460,13 +441,9 @@ int main() {
 | 
			
		||||
 | 
			
		||||
      draw_grid();
 | 
			
		||||
      skinned_mesh.DrawDebugUi();
 | 
			
		||||
      animation_controller.DrawDebugUi();
 | 
			
		||||
 | 
			
		||||
      if (!skinned_mesh.m_sync_track_override) {
 | 
			
		||||
        animation_controller.UpdateTime(state.time.frame);
 | 
			
		||||
        animation_controller.Evaluate();
 | 
			
		||||
      }
 | 
			
		||||
      skinned_mesh.CalcModelMatrices();
 | 
			
		||||
      // TODO: add AnimGraph to calculate pose
 | 
			
		||||
      // skinned_mesh.CalcModelMatrices();
 | 
			
		||||
 | 
			
		||||
      sgl_defaults();
 | 
			
		||||
      sgl_matrix_mode_projection();
 | 
			
		||||
@ -474,19 +451,22 @@ int main() {
 | 
			
		||||
      sgl_matrix_mode_modelview();
 | 
			
		||||
      sgl_load_matrix((const float*)&state.camera.mtxView);
 | 
			
		||||
      RenderSkinnedMesh(skinned_mesh);
 | 
			
		||||
    } else if (gAppMode == AppMode::GraphEditor) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Animation Graph Editor
 | 
			
		||||
    if (show_graph_editor) {
 | 
			
		||||
      ImGui::SetNextWindowPos(ImVec2(20, 20), ImGuiCond_FirstUseEver);
 | 
			
		||||
      ImGui::SetNextWindowSize(ImVec2(500, 400), ImGuiCond_FirstUseEver);
 | 
			
		||||
 | 
			
		||||
      ImGui::Begin("Graph Editor", nullptr, ImGuiWindowFlags_MenuBar);
 | 
			
		||||
      ImGui::Begin(
 | 
			
		||||
          "Graph Editor",
 | 
			
		||||
          &show_graph_editor,
 | 
			
		||||
          ImGuiWindowFlags_MenuBar);
 | 
			
		||||
      AnimGraphEditorUpdate();
 | 
			
		||||
      ImGui::End();
 | 
			
		||||
    } else if (gAppMode == AppMode::Debug) {
 | 
			
		||||
      ImGui::SetNextWindowPos(ImVec2(460, 20), ImGuiCond_FirstUseEver);
 | 
			
		||||
      ImGui::Begin("Debug");
 | 
			
		||||
      ImGui::End();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    // 3. Show the ImGui test window. Most of the sample code is in ImGui::ShowDemoWindow()
 | 
			
		||||
    if (show_imgui_demo_window) {
 | 
			
		||||
      ImGui::SetNextWindowPos(ImVec2(460, 20), ImGuiCond_FirstUseEver);
 | 
			
		||||
 | 
			
		||||
@ -2,10 +2,11 @@
 | 
			
		||||
// Created by martin on 04.02.22.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "AnimGraphResource.h"
 | 
			
		||||
#include "catch.hpp"
 | 
			
		||||
#include "AnimGraph/AnimGraphResource.h"
 | 
			
		||||
#include "AnimGraph/AnimGraph.h"
 | 
			
		||||
#include "AnimGraph/AnimGraphEditor.h"
 | 
			
		||||
 | 
			
		||||
using namespace AniGraph;
 | 
			
		||||
#include "catch.hpp"
 | 
			
		||||
 | 
			
		||||
TEST_CASE("BasicGraph", "[AnimGraphResource]") {
 | 
			
		||||
  AnimGraphResource graph_resource;
 | 
			
		||||
 | 
			
		||||
@ -1,126 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by martin on 21.11.21.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "AnimNodes/AnimSamplerNode.h"
 | 
			
		||||
#define OZZ_INCLUDE_PRIVATE_HEADER
 | 
			
		||||
#include "../src/animation/runtime/animation_keyframe.h"
 | 
			
		||||
#include "catch.hpp"
 | 
			
		||||
#include "ozzutils.h"
 | 
			
		||||
 | 
			
		||||
void get_bone_transform(
 | 
			
		||||
    int i_bone_idx,
 | 
			
		||||
    const ozz::vector<ozz::math::SoaTransform>& i_local_matrices,
 | 
			
		||||
    ozz::math::Transform& o_transform) {
 | 
			
		||||
  int matrix_index = i_bone_idx / 4;
 | 
			
		||||
  short simd_component = i_bone_idx % 4;
 | 
			
		||||
  o_transform.translation.x =
 | 
			
		||||
      i_local_matrices[matrix_index].translation.x[simd_component];
 | 
			
		||||
  o_transform.translation.y =
 | 
			
		||||
      i_local_matrices[matrix_index].translation.y[simd_component];
 | 
			
		||||
  o_transform.translation.z =
 | 
			
		||||
      i_local_matrices[matrix_index].translation.z[simd_component];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void sample_bone_transform(
 | 
			
		||||
    float ratio,
 | 
			
		||||
    int i_bone_idx,
 | 
			
		||||
    const ozz::animation::Animation* i_animation,
 | 
			
		||||
    ozz::animation::SamplingCache& io_cache,
 | 
			
		||||
    ozz::vector<ozz::math::SoaTransform>& io_local_matrices,
 | 
			
		||||
    ozz::math::Transform& o_transform) {
 | 
			
		||||
  ozz::animation::SamplingJob sampling_job;
 | 
			
		||||
  sampling_job.animation = i_animation;
 | 
			
		||||
  sampling_job.cache = &io_cache;
 | 
			
		||||
  sampling_job.ratio = ratio;
 | 
			
		||||
  sampling_job.output = make_span(io_local_matrices);
 | 
			
		||||
 | 
			
		||||
  if (!sampling_job.Run()) {
 | 
			
		||||
    ozz::log::Err() << "Error sampling animation." << std::endl;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get_bone_transform(i_bone_idx, io_local_matrices, o_transform);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
TEST_CASE("Sample single bone channel", "[AnimSamplerNode]") {
 | 
			
		||||
  SkinnedMesh skinned_mesh;
 | 
			
		||||
  skinned_mesh.LoadSkeleton("../media/MixamoYBot-skeleton.ozz");
 | 
			
		||||
  skinned_mesh.LoadAnimation("../media/Walking-loop.ozz");
 | 
			
		||||
 | 
			
		||||
  ozz::animation::Animation* animation = skinned_mesh.m_animations[0];
 | 
			
		||||
  const int num_soa_joints = skinned_mesh.m_skeleton.num_soa_joints();
 | 
			
		||||
  const int num_joints = skinned_mesh.m_skeleton.num_joints();
 | 
			
		||||
 | 
			
		||||
  ozz::vector<ozz::math::SoaTransform> local_matrices;
 | 
			
		||||
  ozz::animation::SamplingCache sampling_cache;
 | 
			
		||||
 | 
			
		||||
  local_matrices.resize(num_soa_joints);
 | 
			
		||||
  sampling_cache.Resize(num_joints);
 | 
			
		||||
 | 
			
		||||
  // sample at ratio 0.
 | 
			
		||||
  float ratio = 0.f;
 | 
			
		||||
 | 
			
		||||
  ozz::animation::SamplingJob sampling_job;
 | 
			
		||||
  sampling_job.animation = animation;
 | 
			
		||||
  sampling_job.cache = &sampling_cache;
 | 
			
		||||
  sampling_job.ratio = ratio;
 | 
			
		||||
  sampling_job.output = make_span(local_matrices);
 | 
			
		||||
 | 
			
		||||
  if (!sampling_job.Run()) {
 | 
			
		||||
    ozz::log::Err() << "Error sampling animation." << std::endl;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ozz::math::Transform root_transform_0;
 | 
			
		||||
  sample_bone_transform(
 | 
			
		||||
      0.f,
 | 
			
		||||
      0,
 | 
			
		||||
      animation,
 | 
			
		||||
      sampling_cache,
 | 
			
		||||
      local_matrices,
 | 
			
		||||
      root_transform_0);
 | 
			
		||||
 | 
			
		||||
  int n_samples = 53;
 | 
			
		||||
  for (int i = 0; i <= n_samples; i++) {
 | 
			
		||||
    float ratio = i * 1.f / n_samples;
 | 
			
		||||
    ozz::math::Transform sampled_root_transform;
 | 
			
		||||
    sample_bone_transform(
 | 
			
		||||
        i * 1.f / n_samples,
 | 
			
		||||
        0,
 | 
			
		||||
        animation,
 | 
			
		||||
        sampling_cache,
 | 
			
		||||
        local_matrices,
 | 
			
		||||
        sampled_root_transform);
 | 
			
		||||
 | 
			
		||||
    ozz::math::Transform calculated_root_transform;
 | 
			
		||||
    calc_bone_translation(ratio, 0, animation, calculated_root_transform);
 | 
			
		||||
 | 
			
		||||
//    std::cout << "ratio: " << ratio << "\t" << "err: " << ozz::math::Length(sampled_root_transform.translation - calculated_root_transform.translation) << std::endl;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ozz::math::Transform root_transform_1;
 | 
			
		||||
  calc_bone_translation(0.f, 0, animation, root_transform_0);
 | 
			
		||||
  calc_bone_translation(1.f, 0, animation, root_transform_1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_CASE("Root Bone Sampling", "[AnimSamplerNode]") {
 | 
			
		||||
  SkinnedMesh skinned_mesh;
 | 
			
		||||
  skinned_mesh.LoadSkeleton("../media/MixamoYBot-skeleton.ozz");
 | 
			
		||||
  int anim_idx = 0;
 | 
			
		||||
  skinned_mesh.LoadAnimation("../media/Walking-loop.ozz");
 | 
			
		||||
  float walking_markers[] = {0.293, 0.762};
 | 
			
		||||
  skinned_mesh.m_animation_sync_track[anim_idx] = SyncTrack::CreateFromMarkers(
 | 
			
		||||
      skinned_mesh.m_animations[anim_idx]->duration(),
 | 
			
		||||
      2,
 | 
			
		||||
      walking_markers);
 | 
			
		||||
 | 
			
		||||
//  AnimationController anim_controller(&skinned_mesh);
 | 
			
		||||
//  AnimSamplerNode test_node(&anim_controller);
 | 
			
		||||
//  test_node.SetAnimation(skinned_mesh.m_animations[0], SyncTrack());
 | 
			
		||||
//
 | 
			
		||||
//  test_node.Reset();
 | 
			
		||||
//  test_node.UpdateTime(0.2f);
 | 
			
		||||
//
 | 
			
		||||
//  ozz::math::Transform root_transform;
 | 
			
		||||
//  test_node.Evaluate(&skinned_mesh.m_local_matrices, &root_transform);
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user