Compare commits
20
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ac22ebac0 | ||
|
|
64cdddea96 | ||
|
|
65c7a9aaaf | ||
|
|
5d01dfcca2 | ||
|
|
89fedce539 | ||
|
|
f6f7e92cea | ||
|
|
d32c247cc0 | ||
|
|
36c7f7a11e | ||
|
|
3b5537fc9d | ||
|
|
c173707a18 | ||
|
|
887131af37 | ||
|
|
86ea476881 | ||
|
|
f52b19a8d2 | ||
|
|
283306f225 | ||
|
|
b4eda31242 | ||
|
|
1870a9d214 | ||
|
|
07d02a2e42 | ||
|
|
2ea2c56bbb | ||
|
|
f351939d61 | ||
|
|
a977698336 |
+2
-1
@@ -929,7 +929,8 @@ struct Example:
|
||||
{
|
||||
auto cursorTopLeft = ImGui::GetCursorScreenPos();
|
||||
|
||||
util::BlueprintNodeBuilder builder(m_HeaderBackground, GetTextureWidth(m_HeaderBackground), GetTextureHeight(m_HeaderBackground));
|
||||
// util::BlueprintNodeBuilder builder(m_HeaderBackground, GetTextureWidth(m_HeaderBackground), GetTextureHeight(m_HeaderBackground));
|
||||
util::BlueprintNodeBuilder builder;
|
||||
|
||||
for (auto& node : m_Nodes)
|
||||
{
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
# include <imgui_node_editor.h>
|
||||
# include "3rdparty/imgui-node-editor/imgui_node_editor.h"
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
+8
-5
@@ -1256,6 +1256,9 @@ void ed::EditorContext::End()
|
||||
auto control = BuildControl(m_CurrentAction && m_CurrentAction->IsDragging()); // NavigateAction.IsMovingOverEdge()
|
||||
//auto& editorStyle = GetStyle();
|
||||
|
||||
// martin.felis, 2024-05-01, Start: expose Hot Node
|
||||
m_HotNode = control.HotNode ? control.HotNode->m_ID : 0;
|
||||
// martin.felis, 2024-05-01, End: expose Hot Node
|
||||
m_HoveredNode = control.HotNode && m_CurrentAction == nullptr ? control.HotNode->m_ID : 0;
|
||||
m_HoveredPin = control.HotPin && m_CurrentAction == nullptr ? control.HotPin->m_ID : 0;
|
||||
m_HoveredLink = control.HotLink && m_CurrentAction == nullptr ? control.HotLink->m_ID : 0;
|
||||
@@ -4391,15 +4394,15 @@ ed::EditorAction::AcceptResult ed::ShortcutAction::Accept(const Control& control
|
||||
Action candidateAction = None;
|
||||
|
||||
auto& io = ImGui::GetIO();
|
||||
if (io.KeyCtrl && !io.KeyShift && !io.KeyAlt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_X)))
|
||||
if (io.KeyCtrl && !io.KeyShift && !io.KeyAlt && ImGui::IsKeyPressed(ImGuiKey_X))
|
||||
candidateAction = Cut;
|
||||
if (io.KeyCtrl && !io.KeyShift && !io.KeyAlt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_C)))
|
||||
if (io.KeyCtrl && !io.KeyShift && !io.KeyAlt && ImGui::IsKeyPressed(ImGuiKey_C))
|
||||
candidateAction = Copy;
|
||||
if (io.KeyCtrl && !io.KeyShift && !io.KeyAlt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_V)))
|
||||
if (io.KeyCtrl && !io.KeyShift && !io.KeyAlt && ImGui::IsKeyPressed(ImGuiKey_V))
|
||||
candidateAction = Paste;
|
||||
if (io.KeyCtrl && !io.KeyShift && !io.KeyAlt && ImGui::IsKeyPressed(GetKeyIndexForD()))
|
||||
candidateAction = Duplicate;
|
||||
if (!io.KeyCtrl && !io.KeyShift && !io.KeyAlt && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Space)))
|
||||
if (!io.KeyCtrl && !io.KeyShift && !io.KeyAlt && ImGui::IsKeyPressed(ImGuiKey_Space))
|
||||
candidateAction = CreateNode;
|
||||
|
||||
if (candidateAction != None)
|
||||
@@ -4953,7 +4956,7 @@ ed::EditorAction::AcceptResult ed::DeleteItemsAction::Accept(const Control& cont
|
||||
return False;
|
||||
|
||||
auto& io = ImGui::GetIO();
|
||||
if (Editor->CanAcceptUserInput() && ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Delete)) && Editor->AreShortcutsEnabled())
|
||||
if (Editor->CanAcceptUserInput() && ImGui::IsKeyPressed(ImGuiKey_Delete) && Editor->AreShortcutsEnabled())
|
||||
{
|
||||
auto& selection = Editor->GetSelectedObjects();
|
||||
if (!selection.empty())
|
||||
|
||||
@@ -409,6 +409,9 @@ IMGUI_NODE_EDITOR_API void EndShortcut();
|
||||
|
||||
IMGUI_NODE_EDITOR_API float GetCurrentZoom();
|
||||
|
||||
// martin.felis, 2024-05-01, Start: expose Hot Node
|
||||
IMGUI_NODE_EDITOR_API NodeId GetHotNode();
|
||||
// martin.felis, 2024-05-01, Stop: expose Hot Node
|
||||
IMGUI_NODE_EDITOR_API NodeId GetHoveredNode();
|
||||
IMGUI_NODE_EDITOR_API PinId GetHoveredPin();
|
||||
IMGUI_NODE_EDITOR_API LinkId GetHoveredLink();
|
||||
|
||||
@@ -667,6 +667,13 @@ float ax::NodeEditor::GetCurrentZoom()
|
||||
return s_Editor->GetView().InvScale;
|
||||
}
|
||||
|
||||
// martin.felis, 2024-05-01, Start: expose Hot Node
|
||||
ax::NodeEditor::NodeId ax::NodeEditor::GetHotNode()
|
||||
{
|
||||
return s_Editor->GetHotNode();
|
||||
}
|
||||
// martin.felis, 2024-05-01, End: expose Hot Node
|
||||
|
||||
ax::NodeEditor::NodeId ax::NodeEditor::GetHoveredNode()
|
||||
{
|
||||
return s_Editor->GetHoveredNode();
|
||||
|
||||
@@ -1447,6 +1447,9 @@ struct EditorContext
|
||||
void EnableShortcuts(bool enable);
|
||||
bool AreShortcutsEnabled();
|
||||
|
||||
// martin.felis, 2024-05-01, Start: expose Hot Node
|
||||
NodeId GetHotNode() const { return m_HotNode; }
|
||||
// martin.felis, 2024-05-01, End: expose Hot Node
|
||||
NodeId GetHoveredNode() const { return m_HoveredNode; }
|
||||
PinId GetHoveredPin() const { return m_HoveredPin; }
|
||||
LinkId GetHoveredLink() const { return m_HoveredLink; }
|
||||
@@ -1528,6 +1531,9 @@ private:
|
||||
vector<AnimationController*> m_AnimationControllers;
|
||||
FlowAnimationController m_FlowAnimationController;
|
||||
|
||||
// martin.felis, 2024-05-01, Start: expose Hot Node
|
||||
NodeId m_HotNode;
|
||||
// martin.felis, 2024-05-01, End: expose Hot Node
|
||||
NodeId m_HoveredNode;
|
||||
PinId m_HoveredPin;
|
||||
LinkId m_HoveredLink;
|
||||
|
||||
+9
-4
@@ -45,8 +45,6 @@ set(ThirdPartyIncludeDeps
|
||||
|
||||
# Shared code by main executable and tests
|
||||
add_library(AnimTestbedCode OBJECT
|
||||
src/SyncTrack.cc
|
||||
src/SyncTrack.h
|
||||
src/ozzutils.cc
|
||||
src/AnimGraph/AnimGraphNodes.cc
|
||||
src/AnimGraph/AnimGraphNodes.h
|
||||
@@ -59,7 +57,11 @@ add_library(AnimTestbedCode OBJECT
|
||||
src/AnimGraph/AnimNode.cc
|
||||
src/AnimGraph/AnimNode.h
|
||||
src/AnimGraph/AnimGraphResource.cc
|
||||
src/AnimGraph/AnimGraphResource.h)
|
||||
src/AnimGraph/AnimGraphResource.h
|
||||
src/AnimGraph/SyncTrack.cc
|
||||
src/AnimGraph/SyncTrack.h
|
||||
src/AnimGraph/AnimLibrary.h
|
||||
)
|
||||
|
||||
target_include_directories(
|
||||
AnimTestbedCode
|
||||
@@ -147,9 +149,12 @@ set(ozz_offline_test_objs
|
||||
target_sources(runtests PRIVATE
|
||||
tests/AnimGraphResourceTests.cc
|
||||
tests/AnimGraphEditorTests.cc
|
||||
# tests/AnimGraphEvalTests.cc
|
||||
tests/AnimGraphEvalTests.cc
|
||||
tests/NodeDescriptorTests.cc
|
||||
tests/SyncTrackTests.cc
|
||||
tests/AnimDataTests.cc
|
||||
tests/TestAnimData.cc
|
||||
tests/TestAnimData.h
|
||||
tests/main.cc
|
||||
${ozz_offline_test_objs}
|
||||
)
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
## Graph
|
||||
|
||||
### 1. Support of math nodes (or non-AnimNodes in general)
|
||||
|
||||
* Enables animators to add custom math for blend inputs or to adjust other inputs (e.g. LookAt or IK
|
||||
targets).
|
||||
|
||||
**Open Issues**
|
||||
|
||||
1. When to do the evaluation? Two types of subgraphs:
|
||||
a) Instant inputs (needed for blend node inputs) that have to be evaluated before
|
||||
UpdateConnections
|
||||
b) Processing nodes, e.g. for extracted bones.
|
||||
|
||||
### 2. Support of multiple output sockets
|
||||
|
||||
* E.g. extract Bone transform
|
||||
|
||||
* Increases Node complexity:
|
||||
* AnimOutput
|
||||
* AnimOutput + Data
|
||||
* Data
|
||||
|
||||
(Data = bool, float, vec3, quat, ...)
|
||||
|
||||
**Open Issues**
|
||||
|
||||
1. Unclear when this is actually needed. Using more specific nodes that perform the desired logic
|
||||
may be better (
|
||||
c.f. https://dev.epicgames.com/documentation/en-us/unreal-engine/animation-blueprint-bone-driven-controller-in-unreal-engine).
|
||||
Likely this is not crucial so should be avoided for now.
|
||||
|
||||
### 3. Multi-skeleton evaluation
|
||||
|
||||
Use case: riding on a horse, interaction between two characters.
|
||||
@@ -15,9 +15,9 @@ bool AnimGraphBlendTree::Init(AnimGraphContext& context) {
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < m_animdata_blocks.size(); i++) {
|
||||
for (size_t i = 0; i < m_pose_blocks.size(); i++) {
|
||||
int num_soa_joints = context.m_skeleton->num_soa_joints();
|
||||
m_animdata_blocks[i]->m_local_matrices.resize(num_soa_joints);
|
||||
m_pose_blocks[i]->m_local_matrices.resize(num_soa_joints);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#define ANIMTESTBED_ANIMGRAPHBLENDTREE_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
|
||||
#include "AnimNode.h"
|
||||
|
||||
@@ -13,8 +14,6 @@
|
||||
// AnimGraph (Runtime)
|
||||
//
|
||||
struct AnimGraphBlendTree : public AnimNode {
|
||||
AnimData m_local_transforms;
|
||||
|
||||
std::vector<AnimNode*> m_nodes;
|
||||
std::vector<AnimNode*> m_eval_ordered_nodes;
|
||||
std::vector<std::vector<AnimGraphConnection> > m_node_input_connections;
|
||||
@@ -30,15 +29,17 @@ struct AnimGraphBlendTree : public AnimNode {
|
||||
return m_node_input_connections[0];
|
||||
}
|
||||
|
||||
std::vector<AnimData*> m_animdata_blocks;
|
||||
std::vector<Pose*> m_pose_blocks;
|
||||
NodeDescriptorBase* m_node_descriptor = nullptr;
|
||||
char* m_input_buffer = nullptr;
|
||||
char* m_output_buffer = nullptr;
|
||||
char* m_connection_data_storage = nullptr;
|
||||
char* m_const_node_inputs = nullptr;
|
||||
|
||||
std::vector<Socket>& GetGraphOutputs() { return m_node_descriptor->m_inputs; }
|
||||
std::vector<Socket>& GetGraphInputs() { return m_node_descriptor->m_outputs; }
|
||||
std::vector<Socket>& GetGraphOutputs() {
|
||||
return m_node_descriptor->m_outputs;
|
||||
}
|
||||
std::vector<Socket>& GetGraphInputs() { return m_node_descriptor->m_inputs; }
|
||||
|
||||
AnimDataAllocator m_anim_data_allocator;
|
||||
|
||||
@@ -48,6 +49,13 @@ struct AnimGraphBlendTree : public AnimNode {
|
||||
|
||||
// AnimNode overrides
|
||||
bool Init(AnimGraphContext& context) override;
|
||||
|
||||
/// Determines which nodes in the BlendTree are active.
|
||||
///
|
||||
/// Note: this does not use the provided input_connections, instead it marks
|
||||
/// all nodes directly connected to the BlendTree outputs as active and then
|
||||
/// propagates the node state throught the tree. For this each active node's
|
||||
/// AnimNode::MarkActiveInputs() gets called.
|
||||
void MarkActiveInputs(
|
||||
const std::vector<AnimGraphConnection>& input_connections) override;
|
||||
void CalcSyncTrack(
|
||||
@@ -58,10 +66,10 @@ struct AnimGraphBlendTree : public AnimNode {
|
||||
void PropagateTimeToNodeInputs(const AnimNode* node);
|
||||
|
||||
void dealloc() {
|
||||
for (size_t i = 0; i < m_animdata_blocks.size(); i++) {
|
||||
m_animdata_blocks[i]->m_local_matrices.vector::~vector();
|
||||
for (size_t i = 0; i < m_pose_blocks.size(); i++) {
|
||||
m_pose_blocks[i]->m_local_matrices.vector::~vector();
|
||||
}
|
||||
m_animdata_blocks.clear();
|
||||
m_pose_blocks.clear();
|
||||
|
||||
m_node_input_connections.clear();
|
||||
m_node_output_connections.clear();
|
||||
@@ -93,7 +101,7 @@ struct AnimGraphBlendTree : public AnimNode {
|
||||
}
|
||||
}
|
||||
|
||||
/** Sets the address that is used for the specified AnimGraph input Socket.
|
||||
/** Sets the address that is used for the specified BlendTree input Socket.
|
||||
*
|
||||
* @tparam T Type of the Socket.
|
||||
* @param name Name of the Socket.
|
||||
@@ -101,7 +109,7 @@ struct AnimGraphBlendTree : public AnimNode {
|
||||
*/
|
||||
template <typename T>
|
||||
void SetInput(const char* name, T* value_ptr) {
|
||||
m_node_descriptor->SetOutput(name, value_ptr);
|
||||
m_node_descriptor->SetInput(name, value_ptr);
|
||||
|
||||
std::vector<size_t> connected_node_indices;
|
||||
|
||||
@@ -115,14 +123,14 @@ struct AnimGraphBlendTree : public AnimNode {
|
||||
}
|
||||
}
|
||||
|
||||
/** Sets the address that is used for the specified AnimGraph output Socket.
|
||||
/** Sets the address that is used for the specified BlendTree output Socket.
|
||||
*
|
||||
* We update the pointer of the outputting node. We also have to ensure that
|
||||
* all usages of that output use the same pointer.
|
||||
*
|
||||
* @tparam T Type of the Socket.
|
||||
* @param name Name of the Socket.
|
||||
* @param value_ptr Pointer where the graph output output is written to at the end of evaluation.
|
||||
* @param value_ptr Pointer where the graph output is written to at the end of evaluation.
|
||||
*/
|
||||
template <typename T>
|
||||
void SetOutput(const char* name, T* value_ptr) {
|
||||
@@ -148,6 +156,7 @@ struct AnimGraphBlendTree : public AnimNode {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure that all connections that consume the output use the updated address.
|
||||
size_t output_node_index =
|
||||
GetAnimNodeIndex(graph_output_connection->m_source_node);
|
||||
|
||||
@@ -161,6 +170,12 @@ struct AnimGraphBlendTree : public AnimNode {
|
||||
}
|
||||
|
||||
*graph_output_connection->m_socket.m_reference.ptr_ptr = value_ptr;
|
||||
|
||||
// And additionally update the BlendTree's node descriptor:
|
||||
Socket* blend_tree_output_socket = m_node_descriptor->GetOutputSocket(name);
|
||||
assert(blend_tree_output_socket != nullptr);
|
||||
|
||||
*blend_tree_output_socket->m_reference.ptr_ptr = value_ptr;
|
||||
}
|
||||
|
||||
/** Returns the address that is used for the specified AnimGraph output Socket.
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include <ozz/base/maths/soa_transform.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <string>
|
||||
@@ -25,26 +24,53 @@
|
||||
struct AnimGraph;
|
||||
struct AnimNode;
|
||||
|
||||
struct AnimData {
|
||||
struct Pose {
|
||||
ozz::vector<ozz::math::SoaTransform> m_local_matrices;
|
||||
};
|
||||
|
||||
struct AnimDataRef {
|
||||
AnimData* ptr = nullptr;
|
||||
Pose* ptr = nullptr;
|
||||
};
|
||||
|
||||
struct AnimationResource {
|
||||
std::string m_name;
|
||||
std::string m_filename;
|
||||
|
||||
ozz::animation::Animation* m_animation;
|
||||
SyncTrack m_sync_track;
|
||||
};
|
||||
|
||||
inline void to_json(
|
||||
nlohmann::json& j,
|
||||
const AnimationResource& animation_resource) {
|
||||
j["type"] = "AnimationResource";
|
||||
j["name"] = animation_resource.m_name;
|
||||
j["filename"] = animation_resource.m_filename;
|
||||
j["synctrack"] = animation_resource.m_sync_track;
|
||||
}
|
||||
|
||||
inline void from_json(
|
||||
const nlohmann::json& j,
|
||||
AnimationResource& animation_resource) {
|
||||
assert(j["type"] == "AnimationResource");
|
||||
|
||||
animation_resource.m_name = j["name"];
|
||||
animation_resource.m_filename = j["filename"];
|
||||
animation_resource.m_sync_track = j["synctrack"];
|
||||
}
|
||||
|
||||
struct AnimDataAllocator {
|
||||
struct AnimDataList {
|
||||
AnimData* m_anim_data = nullptr;
|
||||
AnimDataList* next = nullptr;
|
||||
struct PoseList {
|
||||
Pose* m_anim_data = nullptr;
|
||||
PoseList* next = nullptr;
|
||||
};
|
||||
|
||||
std::list<AnimData*> m_anim_data_list;
|
||||
std::list<Pose*> m_anim_data_list;
|
||||
size_t m_num_allocations = 0;
|
||||
|
||||
~AnimDataAllocator() {
|
||||
while (!m_anim_data_list.empty()) {
|
||||
AnimData* front = m_anim_data_list.front();
|
||||
Pose* front = m_anim_data_list.front();
|
||||
#ifdef ANIM_DATA_ALLOCATOR_DEBUG
|
||||
std::cout << "about to delete with size "
|
||||
<< front->m_anim_data->m_local_matrices.size()
|
||||
@@ -55,9 +81,9 @@ struct AnimDataAllocator {
|
||||
}
|
||||
}
|
||||
|
||||
AnimData* allocate(ozz::animation::Skeleton* skeleton) {
|
||||
Pose* allocate(ozz::animation::Skeleton* skeleton) {
|
||||
if (m_anim_data_list.empty()) {
|
||||
AnimData* result = new AnimData();
|
||||
Pose* result = new Pose();
|
||||
result->m_local_matrices.resize(skeleton->num_soa_joints());
|
||||
#ifdef ANIM_DATA_ALLOCATOR_DEBUG
|
||||
std::cout << "Allocated with size " << result->m_local_matrices.size()
|
||||
@@ -67,7 +93,7 @@ struct AnimDataAllocator {
|
||||
return result;
|
||||
}
|
||||
|
||||
AnimData* result = m_anim_data_list.front();
|
||||
Pose* result = m_anim_data_list.front();
|
||||
m_anim_data_list.pop_front();
|
||||
|
||||
#ifdef ANIM_DATA_ALLOCATOR_DEBUG
|
||||
@@ -78,7 +104,7 @@ struct AnimDataAllocator {
|
||||
return result;
|
||||
}
|
||||
|
||||
void free(AnimData* anim_data) {
|
||||
void free(Pose* anim_data) {
|
||||
#ifdef ANIM_DATA_ALLOCATOR_DEBUG
|
||||
std::cout << "Storing buffer with size "
|
||||
<< anim_data->m_local_matrices.size() << " " << anim_data
|
||||
@@ -98,18 +124,20 @@ enum class AnimGraphType {
|
||||
GraphTypeLast
|
||||
};
|
||||
|
||||
/*** Defines data on which an animation graph is executed (i.e. skeleton, animations).
|
||||
*/
|
||||
struct AnimGraphContext {
|
||||
AnimGraph* m_graph = nullptr;
|
||||
ozz::animation::Skeleton* m_skeleton = nullptr;
|
||||
|
||||
typedef std::map<std::string, ozz::animation::Animation*> AnimationFileMap;
|
||||
typedef std::map<std::string, AnimationResource> AnimationFileMap;
|
||||
AnimationFileMap m_animation_map;
|
||||
|
||||
void freeAnimations() {
|
||||
AnimationFileMap::iterator animation_map_iter = m_animation_map.begin();
|
||||
|
||||
while (animation_map_iter != m_animation_map.end()) {
|
||||
delete animation_map_iter->second;
|
||||
delete animation_map_iter->second.m_animation;
|
||||
animation_map_iter++;
|
||||
}
|
||||
}
|
||||
@@ -238,7 +266,7 @@ SocketType GetSocketType() {
|
||||
return SocketType::SocketTypeBool;
|
||||
}
|
||||
|
||||
if constexpr (std::is_same<T, AnimData>::value) {
|
||||
if constexpr (std::is_same<T, Pose>::value) {
|
||||
return SocketType::SocketTypeAnimation;
|
||||
}
|
||||
|
||||
@@ -373,11 +401,6 @@ struct NodeDescriptorBase {
|
||||
*socket->m_reference.ptr_ptr = value_ptr;
|
||||
}
|
||||
|
||||
void SetOutputUnchecked(const char* name, void* value_ptr) {
|
||||
Socket* socket = FindSocket(name, m_outputs);
|
||||
*socket->m_reference.ptr_ptr = value_ptr;
|
||||
}
|
||||
|
||||
Socket* GetOutputSocket(const char* name) const {
|
||||
return FindSocket(name, m_outputs);
|
||||
}
|
||||
|
||||
@@ -132,7 +132,8 @@ bool AnimSamplerNode::Init(AnimGraphContext& context) {
|
||||
AnimGraphContext::AnimationFileMap::const_iterator animation_map_iter;
|
||||
animation_map_iter = context.m_animation_map.find(m_filename);
|
||||
if (animation_map_iter != context.m_animation_map.end()) {
|
||||
m_animation = animation_map_iter->second;
|
||||
m_animation = animation_map_iter->second.m_animation;
|
||||
m_sync_track = animation_map_iter->second.m_sync_track;
|
||||
} else {
|
||||
m_animation = new ozz::animation::Animation();
|
||||
ozz::io::File file(m_filename.c_str(), "rb");
|
||||
@@ -150,7 +151,11 @@ bool AnimSamplerNode::Init(AnimGraphContext& context) {
|
||||
|
||||
archive >> *m_animation;
|
||||
|
||||
context.m_animation_map[m_filename] = m_animation;
|
||||
context.m_animation_map[m_filename] = {
|
||||
m_filename,
|
||||
m_filename,
|
||||
m_animation,
|
||||
SyncTrack()};
|
||||
}
|
||||
|
||||
assert(context.m_skeleton != nullptr);
|
||||
|
||||
@@ -18,9 +18,9 @@ struct AnimNode;
|
||||
// Blend2Node
|
||||
//
|
||||
struct Blend2Node : public AnimNode {
|
||||
AnimData* i_input0 = nullptr;
|
||||
AnimData* i_input1 = nullptr;
|
||||
AnimData* o_output = nullptr;
|
||||
Pose* i_input0 = nullptr;
|
||||
Pose* i_input1 = nullptr;
|
||||
Pose* o_output = nullptr;
|
||||
float* i_blend_weight = nullptr;
|
||||
bool m_sync_blend = false;
|
||||
|
||||
@@ -33,12 +33,12 @@ struct Blend2Node : public AnimNode {
|
||||
}
|
||||
|
||||
if (input.m_target_socket_name == "Input0" && *i_blend_weight < 0.999) {
|
||||
input_node->m_state = AnimNodeEvalState::Activated;
|
||||
input_node->Activate(m_tick_number);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (input.m_target_socket_name == "Input1" && *i_blend_weight > 0.001) {
|
||||
input_node->m_state = AnimNodeEvalState::Activated;
|
||||
input_node->Activate(m_tick_number);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -59,6 +59,8 @@ struct NodeDescriptor<Blend2Node> : public NodeDescriptorBase {
|
||||
RegisterProperty("Sync", &node->m_sync_blend);
|
||||
}
|
||||
|
||||
virtual ~NodeDescriptor() = default;
|
||||
|
||||
void UpdateFlags() override {
|
||||
Socket* weight_input_socket = FindSocket("Weight", m_inputs);
|
||||
assert(weight_input_socket != nullptr);
|
||||
@@ -75,8 +77,8 @@ struct NodeDescriptor<Blend2Node> : public NodeDescriptorBase {
|
||||
// SpeedScaleNode
|
||||
//
|
||||
struct SpeedScaleNode : public AnimNode {
|
||||
AnimData* i_input = nullptr;
|
||||
AnimData* o_output = nullptr;
|
||||
Pose* i_input = nullptr;
|
||||
Pose* o_output = nullptr;
|
||||
float* i_speed_scale = nullptr;
|
||||
|
||||
void UpdateTime(float time_last, float time_now) override {
|
||||
@@ -104,13 +106,15 @@ struct NodeDescriptor<SpeedScaleNode> : public NodeDescriptorBase {
|
||||
|
||||
RegisterOutput("Output", &node->o_output);
|
||||
}
|
||||
|
||||
virtual ~NodeDescriptor() = default;
|
||||
};
|
||||
|
||||
//
|
||||
// AnimSamplerNode
|
||||
//
|
||||
struct AnimSamplerNode : public AnimNode {
|
||||
AnimData* o_output = nullptr;
|
||||
Pose* o_output = nullptr;
|
||||
std::string m_filename;
|
||||
ozz::animation::SamplingJob::Context m_sampling_context;
|
||||
ozz::animation::Animation* m_animation = nullptr;
|
||||
@@ -132,14 +136,16 @@ struct NodeDescriptor<AnimSamplerNode> : public NodeDescriptorBase {
|
||||
|
||||
RegisterProperty("Filename", &node->m_filename);
|
||||
}
|
||||
|
||||
virtual ~NodeDescriptor() = default;
|
||||
};
|
||||
|
||||
//
|
||||
// LockTranslationNode
|
||||
//
|
||||
struct LockTranslationNode : public AnimNode {
|
||||
AnimData* i_input = nullptr;
|
||||
AnimData* o_output = nullptr;
|
||||
Pose* i_input = nullptr;
|
||||
Pose* o_output = nullptr;
|
||||
int m_locked_bone_index = 0;
|
||||
bool m_lock_x = false;
|
||||
bool m_lock_y = false;
|
||||
@@ -159,6 +165,8 @@ struct NodeDescriptor<LockTranslationNode> : public NodeDescriptorBase {
|
||||
RegisterProperty("LockAxisY", &node->m_lock_y);
|
||||
RegisterProperty("LockAxisZ", &node->m_lock_z);
|
||||
}
|
||||
|
||||
virtual ~NodeDescriptor() = default;
|
||||
};
|
||||
|
||||
//
|
||||
@@ -177,6 +185,8 @@ struct NodeDescriptor<ConstScalarNode> : public NodeDescriptorBase {
|
||||
RegisterOutput("ScalarOutput", &node->o_value);
|
||||
RegisterProperty("ScalarValue", &node->value);
|
||||
}
|
||||
|
||||
virtual ~NodeDescriptor() = default;
|
||||
};
|
||||
|
||||
//
|
||||
@@ -202,6 +212,8 @@ struct NodeDescriptor<MathAddNode> : public NodeDescriptorBase {
|
||||
RegisterInput("Input1", &node->i_input1);
|
||||
RegisterOutput("Output", &node->o_output);
|
||||
}
|
||||
|
||||
virtual ~NodeDescriptor() = default;
|
||||
};
|
||||
|
||||
//
|
||||
@@ -232,6 +244,8 @@ struct NodeDescriptor<MathFloatToVec3Node> : public NodeDescriptorBase {
|
||||
RegisterInput("Input2", &node->i_input2);
|
||||
RegisterOutput("Output", &node->o_output);
|
||||
}
|
||||
|
||||
virtual ~NodeDescriptor() = default;
|
||||
};
|
||||
|
||||
AnimNode* AnimNodeFactory(const std::string& name);
|
||||
|
||||
+368
-393
@@ -16,11 +16,11 @@
|
||||
using json = nlohmann::json;
|
||||
|
||||
// forward declarations
|
||||
static json sAnimGraphResourceBlendTreeToJson(
|
||||
const AnimGraphResource& anim_graph_resource);
|
||||
static bool sAnimGraphResourceBlendTreeFromJson(
|
||||
static json sBlendTreeToJson(const BlendTreeResource& anim_graph_resource);
|
||||
|
||||
static bool sBlendTreeFromJson(
|
||||
const json& json_data,
|
||||
AnimGraphResource* result_graph_resource);
|
||||
BlendTreeResource* result_graph_resource);
|
||||
|
||||
//
|
||||
// Socket <-> json
|
||||
@@ -86,7 +86,7 @@ Socket sJsonToSocket(const json& json_data) {
|
||||
}
|
||||
} else if (type_string == "Animation") {
|
||||
result.m_type = SocketType::SocketTypeAnimation;
|
||||
result.m_type_size = sizeof(AnimData);
|
||||
result.m_type_size = sizeof(Pose);
|
||||
} else if (type_string == "Int") {
|
||||
result.m_type = SocketType::SocketTypeInt;
|
||||
result.m_type_size = sizeof(int);
|
||||
@@ -134,7 +134,7 @@ Socket sJsonToSocket(const json& json_data) {
|
||||
//
|
||||
json sAnimGraphNodeToJson(
|
||||
const AnimNodeResource* node,
|
||||
size_t node_index,
|
||||
const size_t node_index,
|
||||
const std::vector<BlendTreeConnectionResource>& connections) {
|
||||
json result;
|
||||
|
||||
@@ -181,9 +181,9 @@ AnimNodeResource* sAnimGraphNodeFromJson(
|
||||
std::string node_type = json_node["node_type"];
|
||||
|
||||
if (node_type == "BlendTree") {
|
||||
AnimGraphResource* result =
|
||||
dynamic_cast<AnimGraphResource*>(AnimNodeResourceFactory("BlendTree"));
|
||||
sAnimGraphResourceBlendTreeFromJson(json_node, result);
|
||||
BlendTreeResource* result =
|
||||
dynamic_cast<BlendTreeResource*>(AnimNodeResourceFactory("BlendTree"));
|
||||
sBlendTreeFromJson(json_node, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -244,26 +244,25 @@ BlendTreeConnectionResource sAnimGraphConnectionFromJson(
|
||||
return connection;
|
||||
}
|
||||
|
||||
static json sAnimGraphResourceBlendTreeToJson(
|
||||
const AnimGraphResource& anim_graph_resource) {
|
||||
//
|
||||
// BlendTreeResource <-> Json
|
||||
//
|
||||
static json sBlendTreeToJson(const BlendTreeResource& blend_tree_resource) {
|
||||
json result;
|
||||
|
||||
result["name"] = anim_graph_resource.m_name;
|
||||
result["name"] = blend_tree_resource.m_name;
|
||||
result["type"] = "AnimNodeResource";
|
||||
result["node_type"] = "BlendTree";
|
||||
result["position"][0] = anim_graph_resource.m_position[0];
|
||||
result["position"][1] = anim_graph_resource.m_position[1];
|
||||
|
||||
const BlendTreeResource& blend_tree_resource =
|
||||
anim_graph_resource.m_blend_tree_resource;
|
||||
result["position"][0] = blend_tree_resource.m_position[0];
|
||||
result["position"][1] = blend_tree_resource.m_position[1];
|
||||
|
||||
for (size_t i = 0; i < blend_tree_resource.GetNumNodes(); i++) {
|
||||
const AnimNodeResource* node = blend_tree_resource.GetNode(i);
|
||||
|
||||
if (node->m_node_type_name == "BlendTree") {
|
||||
const AnimGraphResource* graph_resource =
|
||||
dynamic_cast<const AnimGraphResource*>(node);
|
||||
result["nodes"][i] = sAnimGraphResourceBlendTreeToJson(*graph_resource);
|
||||
const BlendTreeResource* blend_tree_resource =
|
||||
dynamic_cast<const BlendTreeResource*>(node);
|
||||
result["nodes"][i] = sBlendTreeToJson(*blend_tree_resource);
|
||||
} else {
|
||||
result["nodes"][i] =
|
||||
sAnimGraphNodeToJson(node, i, blend_tree_resource.GetConnections());
|
||||
@@ -295,20 +294,16 @@ static json sAnimGraphResourceBlendTreeToJson(
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool sAnimGraphResourceBlendTreeFromJson(
|
||||
static bool sBlendTreeFromJson(
|
||||
const json& json_data,
|
||||
AnimGraphResource* result_graph_resource) {
|
||||
BlendTreeResource& blend_tree_resource =
|
||||
result_graph_resource->m_blend_tree_resource;
|
||||
|
||||
result_graph_resource->m_graph_type_name = "BlendTree";
|
||||
result_graph_resource->m_node_type_name = "BlendTree";
|
||||
result_graph_resource->m_name = json_data["name"];
|
||||
result_graph_resource->m_position[0] = json_data["position"][0];
|
||||
result_graph_resource->m_position[1] = json_data["position"][1];
|
||||
BlendTreeResource* result_blend_tree_resource) {
|
||||
result_blend_tree_resource->m_node_type_name = "BlendTree";
|
||||
result_blend_tree_resource->m_name = json_data["name"];
|
||||
result_blend_tree_resource->m_position[0] = json_data["position"][0];
|
||||
result_blend_tree_resource->m_position[1] = json_data["position"][1];
|
||||
|
||||
// Clear all nodes as we overwrite them here anyway.
|
||||
blend_tree_resource.ClearAllNodes();
|
||||
result_blend_tree_resource->ClearAllNodes();
|
||||
|
||||
// Load nodes
|
||||
for (size_t i = 0, n = json_data["nodes"].size(); i < n; i++) {
|
||||
@@ -321,14 +316,14 @@ static bool sAnimGraphResourceBlendTreeFromJson(
|
||||
}
|
||||
|
||||
AnimNodeResource* node = sAnimGraphNodeFromJson(json_node, i);
|
||||
blend_tree_resource.AddNode(node);
|
||||
result_blend_tree_resource->AddNode(node);
|
||||
}
|
||||
|
||||
// Graph outputs
|
||||
if (json_data["nodes"][0].contains("inputs")) {
|
||||
const json& graph_outputs = json_data["nodes"][0]["inputs"];
|
||||
for (const auto& graph_output : graph_outputs) {
|
||||
result_graph_resource->RegisterBlendTreeOutputSocket(
|
||||
result_blend_tree_resource->RegisterBlendTreeOutputSocket(
|
||||
sJsonToSocket(graph_output));
|
||||
}
|
||||
}
|
||||
@@ -337,7 +332,7 @@ static bool sAnimGraphResourceBlendTreeFromJson(
|
||||
if (json_data["nodes"][1].contains("outputs")) {
|
||||
const json& graph_inputs = json_data["nodes"][1]["outputs"];
|
||||
for (const auto& graph_input : graph_inputs) {
|
||||
result_graph_resource->RegisterBlendTreeInputSocket(
|
||||
result_blend_tree_resource->RegisterBlendTreeInputSocket(
|
||||
sJsonToSocket(graph_input));
|
||||
}
|
||||
}
|
||||
@@ -356,10 +351,10 @@ static bool sAnimGraphResourceBlendTreeFromJson(
|
||||
BlendTreeConnectionResource connection =
|
||||
sAnimGraphConnectionFromJson(json_connection);
|
||||
|
||||
blend_tree_resource.ConnectSockets(
|
||||
blend_tree_resource.GetNode(connection.source_node_index),
|
||||
result_blend_tree_resource->ConnectSockets(
|
||||
result_blend_tree_resource->GetNode(connection.source_node_index),
|
||||
connection.source_socket_name,
|
||||
blend_tree_resource.GetNode(connection.target_node_index),
|
||||
result_blend_tree_resource->GetNode(connection.target_node_index),
|
||||
connection.target_socket_name);
|
||||
}
|
||||
}
|
||||
@@ -375,6 +370,92 @@ static bool sAnimGraphResourceStateMachineFromJson(
|
||||
return false;
|
||||
}
|
||||
|
||||
AnimGraphResource* AnimGraphResource::CreateFromFile(const char* filename) {
|
||||
std::ifstream input_file;
|
||||
input_file.open(filename);
|
||||
std::stringstream buffer;
|
||||
buffer << input_file.rdbuf();
|
||||
|
||||
json json_data = json::parse(buffer.str(), nullptr, false);
|
||||
if (json_data.is_discarded()) {
|
||||
std::cerr << "Error parsing json of file '" << filename << "'."
|
||||
<< std::endl;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (json_data["type"] != "AnimNodeResource") {
|
||||
std::cerr
|
||||
<< "Invalid json object. Expected type 'AnimNodeResource' but got '"
|
||||
<< json_data["type"] << "'." << std::endl;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AnimGraphResource* result = nullptr;
|
||||
if (json_data["node_type"] == "BlendTree") {
|
||||
result = BlendTreeResource::CreateFromFile(filename);
|
||||
} else if (json_data["node_type"] == "StateMachine") {
|
||||
sAnimGraphResourceStateMachineFromJson(json_data, result);
|
||||
} else {
|
||||
std::cerr << "Invalid node_type. Expected type 'BlendTree' or "
|
||||
"'StateMachine' but got '"
|
||||
<< json_data["node_type"] << "'." << std::endl;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool BlendTreeResource::SaveToFile(const char* filename) const {
|
||||
json result;
|
||||
|
||||
result = sBlendTreeToJson(*this);
|
||||
|
||||
std::ofstream output_file;
|
||||
output_file.open(filename);
|
||||
output_file << result.dump(4, ' ') << std::endl;
|
||||
output_file.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BlendTreeResource* BlendTreeResource::CreateFromFile(const char* filename) {
|
||||
std::ifstream input_file;
|
||||
input_file.open(filename);
|
||||
std::stringstream buffer;
|
||||
buffer << input_file.rdbuf();
|
||||
|
||||
json json_data = json::parse(buffer.str(), nullptr, false);
|
||||
if (json_data.is_discarded()) {
|
||||
std::cerr << "Error parsing json of file '" << filename << "'."
|
||||
<< std::endl;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (json_data["type"] != "AnimNodeResource") {
|
||||
std::cerr
|
||||
<< "Invalid json object. Expected type 'AnimNodeResource' but got '"
|
||||
<< json_data["type"] << "'." << std::endl;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (json_data["node_type"] != "BlendTree") {
|
||||
std::cerr << "Invalid node type. Expected type 'BlendTree' but got '"
|
||||
<< json_data["node_type"] << "'." << std::endl;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BlendTreeResource* result = nullptr;
|
||||
result =
|
||||
dynamic_cast<BlendTreeResource*>(AnimNodeResourceFactory("BlendTree"));
|
||||
sBlendTreeFromJson(json_data, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void BlendTreeResource::RemoveConnectionsForSocket(
|
||||
const AnimNodeResource* node_resource,
|
||||
const Socket& socket) {
|
||||
@@ -563,12 +644,10 @@ Socket* BlendTreeResource::GetNodeOutputSocket(
|
||||
}
|
||||
|
||||
if (output_socket == nullptr && node->m_node_type_name == "BlendTree") {
|
||||
const AnimGraphResource* graph_resource =
|
||||
dynamic_cast<const AnimGraphResource*>(node);
|
||||
const BlendTreeResource& blend_tree_resource =
|
||||
graph_resource->m_blend_tree_resource;
|
||||
const BlendTreeResource* blend_tree_resource =
|
||||
dynamic_cast<const BlendTreeResource*>(node);
|
||||
output_socket =
|
||||
blend_tree_resource.GetGraphOutputSocket(output_socket_name.c_str());
|
||||
blend_tree_resource->GetGraphOutputSocket(output_socket_name.c_str());
|
||||
}
|
||||
|
||||
return output_socket;
|
||||
@@ -582,12 +661,10 @@ const Socket* BlendTreeResource::GetNodeOutputSocketByIndex(
|
||||
if (node->m_virtual_socket_accessor) {
|
||||
output_sockets = &node->m_virtual_socket_accessor->m_outputs;
|
||||
} else if (node->m_node_type_name == "BlendTree") {
|
||||
const AnimGraphResource* graph_resource =
|
||||
dynamic_cast<const AnimGraphResource*>(node);
|
||||
const BlendTreeResource& blend_tree_resource =
|
||||
graph_resource->m_blend_tree_resource;
|
||||
const BlendTreeResource* blend_tree_resource =
|
||||
dynamic_cast<const BlendTreeResource*>(node);
|
||||
|
||||
output_sockets = &blend_tree_resource.GetGraphOutputNode()
|
||||
output_sockets = &blend_tree_resource->GetGraphOutputNode()
|
||||
->m_virtual_socket_accessor->m_outputs;
|
||||
}
|
||||
|
||||
@@ -610,12 +687,10 @@ Socket* BlendTreeResource::GetNodeInputSocket(
|
||||
}
|
||||
|
||||
if (input_socket == nullptr && node->m_node_type_name == "BlendTree") {
|
||||
const AnimGraphResource* graph_resource =
|
||||
dynamic_cast<const AnimGraphResource*>(node);
|
||||
const BlendTreeResource& blend_tree_resource =
|
||||
graph_resource->m_blend_tree_resource;
|
||||
const BlendTreeResource* blend_tree_resource =
|
||||
dynamic_cast<const BlendTreeResource*>(node);
|
||||
input_socket =
|
||||
blend_tree_resource.GetGraphInputSocket(input_socket_name.c_str());
|
||||
blend_tree_resource->GetGraphInputSocket(input_socket_name.c_str());
|
||||
}
|
||||
|
||||
return input_socket;
|
||||
@@ -629,12 +704,10 @@ const Socket* BlendTreeResource::GetNodeInputSocketByIndex(
|
||||
if (node->m_virtual_socket_accessor) {
|
||||
output_sockets = &node->m_virtual_socket_accessor->m_inputs;
|
||||
} else if (node->m_node_type_name == "BlendTree") {
|
||||
const AnimGraphResource* graph_resource =
|
||||
dynamic_cast<const AnimGraphResource*>(node);
|
||||
const BlendTreeResource& blend_tree_resource =
|
||||
graph_resource->m_blend_tree_resource;
|
||||
const BlendTreeResource* blend_tree_resource =
|
||||
dynamic_cast<const BlendTreeResource*>(node);
|
||||
|
||||
output_sockets = &blend_tree_resource.GetGraphOutputNode()
|
||||
output_sockets = &blend_tree_resource->GetGraphOutputNode()
|
||||
->m_virtual_socket_accessor->m_outputs;
|
||||
}
|
||||
|
||||
@@ -653,11 +726,10 @@ std::vector<Socket> BlendTreeResource::GetNodeOutputSockets(
|
||||
}
|
||||
|
||||
if (node->m_node_type_name == "BlendTree") {
|
||||
const AnimGraphResource* graph_resource =
|
||||
dynamic_cast<const AnimGraphResource*>(node);
|
||||
const BlendTreeResource& blend_tree_resource =
|
||||
graph_resource->m_blend_tree_resource;
|
||||
return blend_tree_resource.GetGraphOutputNode()
|
||||
const BlendTreeResource* blend_tree_resource =
|
||||
dynamic_cast<const BlendTreeResource*>(node);
|
||||
|
||||
return blend_tree_resource->GetGraphOutputNode()
|
||||
->m_virtual_socket_accessor->m_inputs;
|
||||
}
|
||||
|
||||
@@ -671,11 +743,10 @@ std::vector<Socket> BlendTreeResource::GetNodeInputSockets(
|
||||
}
|
||||
|
||||
if (node->m_node_type_name == "BlendTree") {
|
||||
const AnimGraphResource* graph_resource =
|
||||
dynamic_cast<const AnimGraphResource*>(node);
|
||||
const BlendTreeResource& blend_tree_resource =
|
||||
graph_resource->m_blend_tree_resource;
|
||||
return blend_tree_resource.GetGraphInputNode()
|
||||
const BlendTreeResource* blend_tree_resource =
|
||||
dynamic_cast<const BlendTreeResource*>(node);
|
||||
|
||||
return blend_tree_resource->GetGraphInputNode()
|
||||
->m_virtual_socket_accessor->m_outputs;
|
||||
}
|
||||
|
||||
@@ -824,92 +895,12 @@ void BlendTreeResource::UpdateNodeSubtrees() {
|
||||
}
|
||||
}
|
||||
|
||||
AnimGraphResource::AnimGraphResource(AnimGraphType graph_type) {
|
||||
if (graph_type == AnimGraphType::GraphTypeBlendTree) {
|
||||
m_graph_type_name = "BlendTree";
|
||||
m_virtual_socket_accessor = VirtualAnimNodeDescriptorFactory("BlendTree");
|
||||
|
||||
m_blend_tree_resource.InitGraphConnectors();
|
||||
RegisterBlendTreeOutputSocket<AnimData>(
|
||||
AnimGraphResource::DefaultAnimOutput);
|
||||
} else {
|
||||
std::cerr
|
||||
<< "Warning: construction of state machine graphs not yet implemented!"
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
AnimGraphResource* AnimGraphResource::CreateFromFile(const char* filename) {
|
||||
std::ifstream input_file;
|
||||
input_file.open(filename);
|
||||
std::stringstream buffer;
|
||||
buffer << input_file.rdbuf();
|
||||
|
||||
json json_data = json::parse(buffer.str(), nullptr, false);
|
||||
if (json_data.is_discarded()) {
|
||||
std::cerr << "Error parsing json of file '" << filename << "'."
|
||||
<< std::endl;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (json_data["type"] != "AnimNodeResource") {
|
||||
std::cerr
|
||||
<< "Invalid json object. Expected type 'AnimNodeResource' but got '"
|
||||
<< json_data["type"] << "'." << std::endl;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AnimGraphResource* result = nullptr;
|
||||
if (json_data["node_type"] == "BlendTree") {
|
||||
result =
|
||||
dynamic_cast<AnimGraphResource*>(AnimNodeResourceFactory("BlendTree"));
|
||||
sAnimGraphResourceBlendTreeFromJson(json_data, result);
|
||||
} else if (json_data["node_type"] == "StateMachine") {
|
||||
sAnimGraphResourceStateMachineFromJson(json_data, result);
|
||||
} else {
|
||||
std::cerr << "Invalid node_type. Expected type 'BlendTree' or "
|
||||
"'StateMachine' but got '"
|
||||
<< json_data["node_type"] << "'." << std::endl;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool AnimGraphResource::SaveToFile(const char* filename) const {
|
||||
if (m_graph_type_name == "BlendTree") {
|
||||
return SaveBlendTreeResourceToFile(filename);
|
||||
} else if (m_graph_type_name == "StateMachine") {
|
||||
return SaveStateMachineResourceToFile(filename);
|
||||
}
|
||||
|
||||
std::cerr << "Invalid AnimGraphResource type: " << m_graph_type_name << "."
|
||||
<< std::endl;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AnimGraphResource::SaveBlendTreeResourceToFile(
|
||||
const char* filename) const {
|
||||
json result;
|
||||
|
||||
result = sAnimGraphResourceBlendTreeToJson(*this);
|
||||
|
||||
std::ofstream output_file;
|
||||
output_file.open(filename);
|
||||
output_file << result.dump(4, ' ') << std::endl;
|
||||
output_file.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AnimGraphResource::CreateBlendTreeInstance(
|
||||
void BlendTreeResource::CreateBlendTreeInstance(
|
||||
AnimGraphBlendTree& result) const {
|
||||
if (m_node_type_name != "BlendTree") {
|
||||
std::cerr
|
||||
<< "Invalid AnimGraphResource. Expected type 'BlendTree' but got '"
|
||||
<< m_graph_type_name << "'." << std::endl;
|
||||
<< m_node_type_name << "'." << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -926,15 +917,14 @@ void AnimGraphResource::CreateBlendTreeInstance(
|
||||
result.ResetNodeStates();
|
||||
}
|
||||
|
||||
void AnimGraphResource::CreateBlendTreeRuntimeNodeInstances(
|
||||
void BlendTreeResource::CreateBlendTreeRuntimeNodeInstances(
|
||||
AnimGraphBlendTree& result) const {
|
||||
for (const AnimNodeResource* node_resource :
|
||||
m_blend_tree_resource.GetNodes()) {
|
||||
for (const AnimNodeResource* node_resource : GetNodes()) {
|
||||
AnimNode* node = AnimNodeFactory(node_resource->m_node_type_name);
|
||||
|
||||
if (node_resource->m_node_type_name == "BlendTree") {
|
||||
const AnimGraphResource* embedded_blend_tree_resource =
|
||||
dynamic_cast<const AnimGraphResource*>(node_resource);
|
||||
const BlendTreeResource* embedded_blend_tree_resource =
|
||||
dynamic_cast<const BlendTreeResource*>(node_resource);
|
||||
assert(embedded_blend_tree_resource != nullptr);
|
||||
AnimGraphBlendTree* embedded_blend_tree =
|
||||
dynamic_cast<AnimGraphBlendTree*>(node);
|
||||
@@ -943,9 +933,9 @@ void AnimGraphResource::CreateBlendTreeRuntimeNodeInstances(
|
||||
embedded_blend_tree_resource->CreateBlendTreeInstance(
|
||||
*embedded_blend_tree);
|
||||
embedded_blend_tree_resource->m_virtual_socket_accessor->m_inputs =
|
||||
embedded_blend_tree->m_node_descriptor->m_outputs;
|
||||
embedded_blend_tree_resource->m_virtual_socket_accessor->m_outputs =
|
||||
embedded_blend_tree->m_node_descriptor->m_inputs;
|
||||
embedded_blend_tree_resource->m_virtual_socket_accessor->m_outputs =
|
||||
embedded_blend_tree->m_node_descriptor->m_outputs;
|
||||
}
|
||||
|
||||
node->m_name = node_resource->m_name;
|
||||
@@ -958,18 +948,16 @@ void AnimGraphResource::CreateBlendTreeRuntimeNodeInstances(
|
||||
}
|
||||
}
|
||||
|
||||
void AnimGraphResource::PrepareBlendTreeIOData(
|
||||
void BlendTreeResource::PrepareBlendTreeIOData(
|
||||
AnimGraphBlendTree& instance,
|
||||
NodeSocketDataOffsetMap& node_offset_map) const {
|
||||
instance.m_node_descriptor =
|
||||
AnimNodeDescriptorFactory("BlendTree", instance.m_nodes[0]);
|
||||
|
||||
instance.m_node_descriptor->m_outputs =
|
||||
m_blend_tree_resource.GetGraphInputNode()
|
||||
->m_virtual_socket_accessor->m_outputs;
|
||||
GetGraphOutputNode()->m_virtual_socket_accessor->m_inputs;
|
||||
instance.m_node_descriptor->m_inputs =
|
||||
m_blend_tree_resource.GetGraphOutputNode()
|
||||
->m_virtual_socket_accessor->m_inputs;
|
||||
GetGraphInputNode()->m_virtual_socket_accessor->m_outputs;
|
||||
|
||||
//
|
||||
// graph inputs
|
||||
@@ -989,7 +977,7 @@ void AnimGraphResource::PrepareBlendTreeIOData(
|
||||
for (int i = 0; i < graph_inputs.size(); i++) {
|
||||
graph_inputs[i].m_reference.ptr =
|
||||
(void*)&instance.m_input_buffer[input_block_offset];
|
||||
instance.m_node_descriptor->m_outputs[i].m_reference.ptr =
|
||||
instance.m_node_descriptor->m_inputs[i].m_reference.ptr =
|
||||
&instance.m_input_buffer[input_block_offset];
|
||||
input_block_offset += sizeof(void*);
|
||||
}
|
||||
@@ -1010,24 +998,27 @@ void AnimGraphResource::PrepareBlendTreeIOData(
|
||||
|
||||
int output_block_offset = 0;
|
||||
for (int i = 0; i < graph_outputs.size(); i++) {
|
||||
instance.m_node_descriptor->m_inputs[i].m_reference.ptr =
|
||||
instance.m_node_descriptor->m_outputs[i].m_reference.ptr =
|
||||
&instance.m_output_buffer[output_block_offset];
|
||||
output_block_offset += sizeof(void*);
|
||||
}
|
||||
|
||||
//
|
||||
// connecton data storage
|
||||
// connection data storage: we reserve memory for the used outputs of all
|
||||
// nodes:
|
||||
// * If a node output does not get used: we do not reserve a memory block.
|
||||
// * If a node output gets used by multiple nodes: we only reserve a single
|
||||
// memory block.
|
||||
// The resulting memory offsets are stored in the node_offset_map.
|
||||
//
|
||||
size_t connection_data_storage_size = 0;
|
||||
for (const BlendTreeConnectionResource& connection :
|
||||
m_blend_tree_resource.GetConnections()) {
|
||||
const AnimNodeResource* source_node =
|
||||
m_blend_tree_resource.GetNode(connection.source_node_index);
|
||||
for (const BlendTreeConnectionResource& connection : GetConnections()) {
|
||||
const AnimNodeResource* source_node = GetNode(connection.source_node_index);
|
||||
Socket* source_socket =
|
||||
source_node->m_virtual_socket_accessor->GetOutputSocket(
|
||||
connection.source_socket_name.c_str());
|
||||
|
||||
NodeSocketPair source_socket_pair{source_node, source_socket->m_name};
|
||||
NodeSocketNamePair source_socket_pair{source_node, source_socket->m_name};
|
||||
if (node_offset_map.find(source_socket_pair) == node_offset_map.end()) {
|
||||
node_offset_map.insert(
|
||||
{source_socket_pair, connection_data_storage_size});
|
||||
@@ -1042,221 +1033,10 @@ void AnimGraphResource::PrepareBlendTreeIOData(
|
||||
}
|
||||
}
|
||||
|
||||
void AnimGraphResource::CreateBlendTreeConnectionInstances(
|
||||
AnimGraphBlendTree& instance,
|
||||
NodeSocketDataOffsetMap& node_offset_map) const {
|
||||
std::vector<NodeDescriptorBase*> instance_node_descriptors(
|
||||
m_blend_tree_resource.GetNumNodes(),
|
||||
nullptr);
|
||||
for (int i = 0; i < m_blend_tree_resource.GetNumNodes(); i++) {
|
||||
instance_node_descriptors[i] = AnimNodeDescriptorFactory(
|
||||
m_blend_tree_resource.GetNode(i)->m_node_type_name,
|
||||
instance.m_nodes[i]);
|
||||
|
||||
if (i > 1
|
||||
&& m_blend_tree_resource.GetNode(i)->m_node_type_name == "BlendTree") {
|
||||
instance_node_descriptors[i]->m_inputs =
|
||||
m_blend_tree_resource.GetNode(i)->m_virtual_socket_accessor->m_inputs;
|
||||
instance_node_descriptors[i]->m_outputs =
|
||||
m_blend_tree_resource.GetNode(i)
|
||||
->m_virtual_socket_accessor->m_outputs;
|
||||
}
|
||||
}
|
||||
|
||||
instance_node_descriptors[0]->m_inputs = instance.m_node_descriptor->m_inputs;
|
||||
instance_node_descriptors[1]->m_outputs =
|
||||
instance.m_node_descriptor->m_outputs;
|
||||
|
||||
for (const BlendTreeConnectionResource& connection :
|
||||
m_blend_tree_resource.GetConnections()) {
|
||||
NodeDescriptorBase* source_node_descriptor =
|
||||
instance_node_descriptors[connection.source_node_index];
|
||||
NodeDescriptorBase* target_node_descriptor =
|
||||
instance_node_descriptors[connection.target_node_index];
|
||||
|
||||
AnimNode* source_node = instance.m_nodes[connection.source_node_index];
|
||||
AnimNode* target_node = instance.m_nodes[connection.target_node_index];
|
||||
|
||||
Socket* source_socket = source_node_descriptor->GetOutputSocket(
|
||||
connection.source_socket_name.c_str());
|
||||
Socket* target_socket = target_node_descriptor->GetInputSocket(
|
||||
connection.target_socket_name.c_str());
|
||||
|
||||
AnimGraphConnection instance_connection;
|
||||
|
||||
if (source_node->m_node_type_name == "BlendTree") {
|
||||
// For embedded subgraphs we have to ensure two things:
|
||||
// 1. The parent graph target node must activate the source node within
|
||||
// the embedded subgraph.
|
||||
// 2. The parent graph target node must activate the AnimNode of the
|
||||
// parent graph which contains the embedded subgraph. Otherwise, the
|
||||
// embedded graph does not get evaluated.
|
||||
// For each case we have to add a connection here. First we insert the
|
||||
// default connection within the graph (i.e. for case 2). Then we alter
|
||||
// the source node such that it points to the node within the embedded
|
||||
// subgraph.
|
||||
AnimGraphConnection embedded_graph_activation_connection;
|
||||
embedded_graph_activation_connection.m_source_node = source_node;
|
||||
embedded_graph_activation_connection.m_source_socket_name =
|
||||
source_socket->m_name;
|
||||
embedded_graph_activation_connection.m_target_node = target_node;
|
||||
embedded_graph_activation_connection.m_target_socket_name =
|
||||
target_socket->m_name;
|
||||
|
||||
embedded_graph_activation_connection.m_socket = *target_socket;
|
||||
|
||||
instance.m_node_input_connections[connection.target_node_index].push_back(
|
||||
embedded_graph_activation_connection);
|
||||
instance.m_node_output_connections[connection.source_node_index]
|
||||
.push_back(embedded_graph_activation_connection);
|
||||
|
||||
const AnimGraphResource* source_blend_tree_resource =
|
||||
dynamic_cast<const AnimGraphResource*>(
|
||||
m_blend_tree_resource.GetNode(connection.source_node_index));
|
||||
AnimGraphBlendTree* source_blend_tree =
|
||||
dynamic_cast<AnimGraphBlendTree*>(source_node);
|
||||
|
||||
size_t source_blend_tree_output_node_index =
|
||||
source_blend_tree_resource->m_blend_tree_resource
|
||||
.GetNodeIndexForOutputSocket(connection.source_socket_name);
|
||||
source_node =
|
||||
source_blend_tree->m_nodes[source_blend_tree_output_node_index];
|
||||
instance_connection.m_crosses_hierarchy = true;
|
||||
} else if (target_node->m_node_type_name == "BlendTree") {
|
||||
// When a connection points to an embedded blend tree we have to ensure
|
||||
// that the embedded node knows about its connection partner in the parent
|
||||
// tree. This allows the embedded node to properly activate the node in
|
||||
// the parent graph.
|
||||
const AnimGraphResource* target_blend_tree_resource =
|
||||
dynamic_cast<const AnimGraphResource*>(
|
||||
m_blend_tree_resource.GetNode(connection.target_node_index));
|
||||
AnimGraphBlendTree* target_blend_tree =
|
||||
dynamic_cast<AnimGraphBlendTree*>(target_node);
|
||||
|
||||
size_t target_blend_tree_output_node_index =
|
||||
target_blend_tree_resource->m_blend_tree_resource
|
||||
.GetNodeIndexForInputSocket(connection.target_socket_name);
|
||||
target_node =
|
||||
target_blend_tree->m_nodes[target_blend_tree_output_node_index];
|
||||
|
||||
// Make sure that the embedded node input activates the parent graph node.
|
||||
std::vector<AnimGraphConnection>& embeded_target_node_inputs =
|
||||
target_blend_tree
|
||||
->m_node_input_connections[target_blend_tree_output_node_index];
|
||||
for (size_t j = 0; j < embeded_target_node_inputs.size(); j++) {
|
||||
AnimGraphConnection& embedded_target_connection =
|
||||
embeded_target_node_inputs[j];
|
||||
if (embedded_target_connection.m_source_socket_name
|
||||
== target_socket->m_name) {
|
||||
embedded_target_connection.m_source_node = source_node;
|
||||
embedded_target_connection.m_crosses_hierarchy = true;
|
||||
|
||||
// In addition: make sure we expose the embedded socket to the
|
||||
// connection in the parent blend tree. That way
|
||||
// parent_tree.SetValue<>() correctly propagates the value to the
|
||||
// embedded node socket.
|
||||
target_socket = &embedded_target_connection.m_socket;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
instance_connection.m_source_node = source_node;
|
||||
instance_connection.m_source_socket_name = source_socket->m_name;
|
||||
instance_connection.m_target_node = target_node;
|
||||
instance_connection.m_target_socket_name = target_socket->m_name;
|
||||
instance_connection.m_socket = *target_socket;
|
||||
|
||||
size_t socket_data_offset = 0;
|
||||
|
||||
if (connection.source_node_index == 1) {
|
||||
instance_connection.m_socket = *target_socket;
|
||||
} else {
|
||||
if (connection.target_node_index == 0) {
|
||||
instance_connection.m_socket = *source_socket;
|
||||
}
|
||||
|
||||
NodeSocketPair node_socket_pair{
|
||||
m_blend_tree_resource.GetNode(connection.source_node_index),
|
||||
source_socket->m_name};
|
||||
|
||||
NodeSocketDataOffsetMap::const_iterator socket_data_offset_iter =
|
||||
node_offset_map.find(node_socket_pair);
|
||||
|
||||
if (socket_data_offset_iter != node_offset_map.end()) {
|
||||
socket_data_offset = socket_data_offset_iter->second;
|
||||
} else {
|
||||
std::cerr << "Error: could not find data offset for node '"
|
||||
<< source_node->m_name << "' and socket '"
|
||||
<< source_socket->m_name << "'." << std::endl;
|
||||
assert(false);
|
||||
abort();
|
||||
}
|
||||
|
||||
*target_socket->m_reference.ptr_ptr =
|
||||
&instance.m_connection_data_storage[socket_data_offset];
|
||||
*source_socket->m_reference.ptr_ptr =
|
||||
&instance.m_connection_data_storage[socket_data_offset];
|
||||
|
||||
if (source_socket->m_type == SocketType::SocketTypeAnimation) {
|
||||
instance.m_animdata_blocks.push_back(
|
||||
(AnimData*)(&instance
|
||||
.m_connection_data_storage[socket_data_offset]));
|
||||
}
|
||||
}
|
||||
|
||||
instance.m_node_input_connections[connection.target_node_index].push_back(
|
||||
instance_connection);
|
||||
instance.m_node_output_connections[connection.source_node_index].push_back(
|
||||
instance_connection);
|
||||
}
|
||||
|
||||
//
|
||||
// const node inputs
|
||||
//
|
||||
std::vector<Socket*> const_inputs =
|
||||
m_blend_tree_resource.GetConstantNodeInputs(instance_node_descriptors);
|
||||
size_t const_node_inputs_buffer_size = 0;
|
||||
for (auto& const_input : const_inputs) {
|
||||
if (const_input->m_type == SocketType::SocketTypeString) {
|
||||
// TODO: implement string const node input support
|
||||
std::cerr << "Error: const inputs for strings not yet implemented!"
|
||||
<< std::endl;
|
||||
abort();
|
||||
}
|
||||
const_node_inputs_buffer_size += const_input->m_type_size;
|
||||
}
|
||||
|
||||
if (const_node_inputs_buffer_size > 0) {
|
||||
instance.m_const_node_inputs = new char[const_node_inputs_buffer_size];
|
||||
memset(instance.m_const_node_inputs, '\0', const_node_inputs_buffer_size);
|
||||
}
|
||||
|
||||
size_t const_input_buffer_offset = 0;
|
||||
for (auto& i : const_inputs) {
|
||||
Socket* const_input = i;
|
||||
|
||||
// TODO: implement string const node input support
|
||||
assert(const_input->m_type != SocketType::SocketTypeString);
|
||||
|
||||
*const_input->m_reference.ptr_ptr =
|
||||
&instance.m_const_node_inputs[const_input_buffer_offset];
|
||||
memcpy(
|
||||
*const_input->m_reference.ptr_ptr,
|
||||
&const_input->m_value,
|
||||
i->m_type_size);
|
||||
|
||||
const_input_buffer_offset += i->m_type_size;
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_blend_tree_resource.GetNumNodes(); i++) {
|
||||
delete instance_node_descriptors[i];
|
||||
}
|
||||
}
|
||||
|
||||
void AnimGraphResource::SetRuntimeNodeProperties(
|
||||
void BlendTreeResource::SetRuntimeNodeProperties(
|
||||
AnimGraphBlendTree& result) const {
|
||||
for (int i = 2; i < m_blend_tree_resource.GetNumNodes(); i++) {
|
||||
const AnimNodeResource* node_resource = m_blend_tree_resource.GetNode(i);
|
||||
for (int i = 2; i < GetNumNodes(); i++) {
|
||||
const AnimNodeResource* node_resource = GetNode(i);
|
||||
|
||||
NodeDescriptorBase* node_instance_accessor = AnimNodeDescriptorFactory(
|
||||
node_resource->m_node_type_name,
|
||||
@@ -1308,17 +1088,212 @@ void AnimGraphResource::SetRuntimeNodeProperties(
|
||||
}
|
||||
}
|
||||
|
||||
bool AnimGraphResource::SaveStateMachineResourceToFile(
|
||||
const char* filename) const {
|
||||
assert(false && "Not yet implemented");
|
||||
void BlendTreeResource::CreateBlendTreeConnectionInstances(
|
||||
AnimGraphBlendTree& instance,
|
||||
NodeSocketDataOffsetMap& node_offset_map) const {
|
||||
std::vector<NodeDescriptorBase*> instance_node_descriptors(
|
||||
GetNumNodes(),
|
||||
nullptr);
|
||||
for (int i = 0; i < GetNumNodes(); i++) {
|
||||
instance_node_descriptors[i] = AnimNodeDescriptorFactory(
|
||||
GetNode(i)->m_node_type_name,
|
||||
instance.m_nodes[i]);
|
||||
|
||||
return false;
|
||||
if (i > 1 && GetNode(i)->m_node_type_name == "BlendTree") {
|
||||
instance_node_descriptors[i]->m_inputs =
|
||||
GetNode(i)->m_virtual_socket_accessor->m_inputs;
|
||||
instance_node_descriptors[i]->m_outputs =
|
||||
GetNode(i)->m_virtual_socket_accessor->m_outputs;
|
||||
}
|
||||
}
|
||||
|
||||
instance_node_descriptors[0]->m_inputs =
|
||||
instance.m_node_descriptor->m_outputs;
|
||||
instance_node_descriptors[1]->m_outputs =
|
||||
instance.m_node_descriptor->m_inputs;
|
||||
|
||||
for (const BlendTreeConnectionResource& connection : GetConnections()) {
|
||||
NodeDescriptorBase* source_node_descriptor =
|
||||
instance_node_descriptors[connection.source_node_index];
|
||||
NodeDescriptorBase* target_node_descriptor =
|
||||
instance_node_descriptors[connection.target_node_index];
|
||||
|
||||
AnimNode* source_node = instance.m_nodes[connection.source_node_index];
|
||||
AnimNode* target_node = instance.m_nodes[connection.target_node_index];
|
||||
|
||||
Socket* source_socket = source_node_descriptor->GetOutputSocket(
|
||||
connection.source_socket_name.c_str());
|
||||
Socket* target_socket = target_node_descriptor->GetInputSocket(
|
||||
connection.target_socket_name.c_str());
|
||||
|
||||
AnimGraphConnection instance_connection;
|
||||
|
||||
if (source_node->m_node_type_name == "BlendTree") {
|
||||
// For embedded subgraphs we have to ensure two things:
|
||||
// 1. The parent graph target node must activate the source node within
|
||||
// the embedded subgraph.
|
||||
// 2. The parent graph target node must activate the AnimNode of the
|
||||
// parent graph which contains the embedded subgraph. Otherwise, the
|
||||
// embedded graph does not get evaluated.
|
||||
// For each case we have to add a connection here. First we insert the
|
||||
// default connection within the graph (i.e. for case 2). Then we alter
|
||||
// the source node such that it points to the node within the embedded
|
||||
// subgraph.
|
||||
AnimGraphConnection embedded_graph_activation_connection;
|
||||
embedded_graph_activation_connection.m_source_node = source_node;
|
||||
embedded_graph_activation_connection.m_source_socket_name =
|
||||
source_socket->m_name;
|
||||
embedded_graph_activation_connection.m_target_node = target_node;
|
||||
embedded_graph_activation_connection.m_target_socket_name =
|
||||
target_socket->m_name;
|
||||
|
||||
embedded_graph_activation_connection.m_socket = *target_socket;
|
||||
|
||||
instance.m_node_input_connections[connection.target_node_index].push_back(
|
||||
embedded_graph_activation_connection);
|
||||
instance.m_node_output_connections[connection.source_node_index]
|
||||
.push_back(embedded_graph_activation_connection);
|
||||
|
||||
const BlendTreeResource* source_blend_tree_resource =
|
||||
dynamic_cast<const BlendTreeResource*>(
|
||||
GetNode(connection.source_node_index));
|
||||
AnimGraphBlendTree* source_blend_tree =
|
||||
dynamic_cast<AnimGraphBlendTree*>(source_node);
|
||||
|
||||
size_t source_blend_tree_output_node_index =
|
||||
source_blend_tree_resource->GetNodeIndexForOutputSocket(
|
||||
connection.source_socket_name);
|
||||
source_node =
|
||||
source_blend_tree->m_nodes[source_blend_tree_output_node_index];
|
||||
instance_connection.m_crosses_hierarchy = true;
|
||||
} else if (target_node->m_node_type_name == "BlendTree") {
|
||||
// When a connection points to an embedded blend tree we have to ensure
|
||||
// that the embedded node knows about its connection partner in the parent
|
||||
// tree. This allows the embedded node to properly activate the node in
|
||||
// the parent graph.
|
||||
const BlendTreeResource* target_blend_tree_resource =
|
||||
dynamic_cast<const BlendTreeResource*>(
|
||||
GetNode(connection.target_node_index));
|
||||
AnimGraphBlendTree* target_blend_tree =
|
||||
dynamic_cast<AnimGraphBlendTree*>(target_node);
|
||||
|
||||
size_t target_blend_tree_output_node_index =
|
||||
target_blend_tree_resource->GetNodeIndexForInputSocket(
|
||||
connection.target_socket_name);
|
||||
target_node =
|
||||
target_blend_tree->m_nodes[target_blend_tree_output_node_index];
|
||||
|
||||
// Make sure that the embedded node input activates the parent graph node.
|
||||
std::vector<AnimGraphConnection>& embeded_target_node_inputs =
|
||||
target_blend_tree
|
||||
->m_node_input_connections[target_blend_tree_output_node_index];
|
||||
for (size_t j = 0; j < embeded_target_node_inputs.size(); j++) {
|
||||
AnimGraphConnection& embedded_target_connection =
|
||||
embeded_target_node_inputs[j];
|
||||
if (embedded_target_connection.m_source_socket_name
|
||||
== target_socket->m_name) {
|
||||
embedded_target_connection.m_source_node = source_node;
|
||||
embedded_target_connection.m_crosses_hierarchy = true;
|
||||
|
||||
// In addition: make sure we expose the embedded socket to the
|
||||
// connection in the parent blend tree. That way
|
||||
// parent_tree.SetValue<>() correctly propagates the value to the
|
||||
// embedded node socket.
|
||||
target_socket = &embedded_target_connection.m_socket;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
instance_connection.m_source_node = source_node;
|
||||
instance_connection.m_source_socket_name = source_socket->m_name;
|
||||
instance_connection.m_target_node = target_node;
|
||||
instance_connection.m_target_socket_name = target_socket->m_name;
|
||||
instance_connection.m_socket = *target_socket;
|
||||
|
||||
size_t socket_data_offset = 0;
|
||||
|
||||
if (connection.source_node_index == 1) {
|
||||
instance_connection.m_socket = *target_socket;
|
||||
} else {
|
||||
if (connection.target_node_index == 0) {
|
||||
instance_connection.m_socket = *source_socket;
|
||||
}
|
||||
|
||||
NodeSocketNamePair node_socket_pair{
|
||||
GetNode(connection.source_node_index),
|
||||
source_socket->m_name};
|
||||
|
||||
NodeSocketDataOffsetMap::const_iterator socket_data_offset_iter =
|
||||
node_offset_map.find(node_socket_pair);
|
||||
|
||||
if (socket_data_offset_iter != node_offset_map.end()) {
|
||||
socket_data_offset = socket_data_offset_iter->second;
|
||||
} else {
|
||||
std::cerr << "Error: could not find data offset for node '"
|
||||
<< source_node->m_name << "' and socket '"
|
||||
<< source_socket->m_name << "'." << std::endl;
|
||||
assert(false);
|
||||
abort();
|
||||
}
|
||||
|
||||
*target_socket->m_reference.ptr_ptr =
|
||||
&instance.m_connection_data_storage[socket_data_offset];
|
||||
*source_socket->m_reference.ptr_ptr =
|
||||
&instance.m_connection_data_storage[socket_data_offset];
|
||||
|
||||
if (source_socket->m_type == SocketType::SocketTypeAnimation) {
|
||||
instance.m_pose_blocks.push_back(
|
||||
(Pose*)(&instance.m_connection_data_storage[socket_data_offset]));
|
||||
}
|
||||
}
|
||||
|
||||
instance.m_node_input_connections[connection.target_node_index].push_back(
|
||||
instance_connection);
|
||||
instance.m_node_output_connections[connection.source_node_index].push_back(
|
||||
instance_connection);
|
||||
}
|
||||
|
||||
//
|
||||
// const node inputs
|
||||
//
|
||||
std::vector<Socket*> const_inputs =
|
||||
GetConstantNodeInputs(instance_node_descriptors);
|
||||
size_t const_node_inputs_buffer_size = 0;
|
||||
for (auto& const_input : const_inputs) {
|
||||
if (const_input->m_type == SocketType::SocketTypeString) {
|
||||
// TODO: implement string const node input support
|
||||
std::cerr << "Error: const inputs for strings not yet implemented!"
|
||||
<< std::endl;
|
||||
abort();
|
||||
}
|
||||
const_node_inputs_buffer_size += const_input->m_type_size;
|
||||
}
|
||||
|
||||
if (const_node_inputs_buffer_size > 0) {
|
||||
instance.m_const_node_inputs = new char[const_node_inputs_buffer_size];
|
||||
memset(instance.m_const_node_inputs, '\0', const_node_inputs_buffer_size);
|
||||
}
|
||||
|
||||
size_t const_input_buffer_offset = 0;
|
||||
for (auto& i : const_inputs) {
|
||||
Socket* const_input = i;
|
||||
|
||||
// TODO: implement string const node input support
|
||||
assert(const_input->m_type != SocketType::SocketTypeString);
|
||||
|
||||
*const_input->m_reference.ptr_ptr =
|
||||
&instance.m_const_node_inputs[const_input_buffer_offset];
|
||||
memcpy(
|
||||
*const_input->m_reference.ptr_ptr,
|
||||
&const_input->m_value,
|
||||
i->m_type_size);
|
||||
|
||||
const_input_buffer_offset += i->m_type_size;
|
||||
}
|
||||
|
||||
for (int i = 0; i < GetNumNodes(); i++) {
|
||||
delete instance_node_descriptors[i];
|
||||
}
|
||||
}
|
||||
|
||||
bool AnimGraphResource::LoadStateMachineResourceFromJson(
|
||||
nlohmann::json const& json_data) {
|
||||
assert(false && "Not yet implemented");
|
||||
|
||||
return false;
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
+122
-114
@@ -5,14 +5,17 @@
|
||||
#ifndef ANIMTESTBED_ANIMGRAPHRESOURCE_H
|
||||
#define ANIMTESTBED_ANIMGRAPHRESOURCE_H
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "3rdparty/json/json.hpp"
|
||||
#include "AnimGraphNodes.h"
|
||||
|
||||
struct AnimGraphBlendTree;
|
||||
struct AnimGraphStateMachine;
|
||||
struct BlendTreeResource;
|
||||
|
||||
struct AnimNodeResource {
|
||||
virtual ~AnimNodeResource() { delete m_virtual_socket_accessor; };
|
||||
virtual ~AnimNodeResource() { delete m_virtual_socket_accessor; }
|
||||
|
||||
std::string m_name;
|
||||
std::string m_node_type_name;
|
||||
@@ -23,6 +26,30 @@ struct AnimNodeResource {
|
||||
static inline AnimNodeResource* AnimNodeResourceFactory(
|
||||
const std::string& node_type_name);
|
||||
|
||||
struct StateMachineTransitionResources {
|
||||
size_t source_state_index = -1;
|
||||
size_t target_state_index = -1;
|
||||
float blend_time = 0.f;
|
||||
bool sync_blend = false;
|
||||
};
|
||||
|
||||
struct StateMachineResource {
|
||||
std::vector<AnimNodeResource> m_states;
|
||||
std::vector<StateMachineTransitionResources> m_transitions;
|
||||
};
|
||||
|
||||
struct AnimGraphResource : AnimNodeResource {
|
||||
~AnimGraphResource() override = default;
|
||||
|
||||
static constexpr char DefaultAnimOutput[] = "Output";
|
||||
|
||||
[[maybe_unused]] virtual bool SaveToFile(const char* filename) const = 0;
|
||||
|
||||
static AnimGraphResource* CreateFromFile(const char* filename);
|
||||
};
|
||||
|
||||
typedef std::unique_ptr<AnimGraphResource> AnimGraphResourcePtr;
|
||||
|
||||
struct BlendTreeConnectionResource {
|
||||
int source_node_index = -1;
|
||||
std::string source_socket_name;
|
||||
@@ -39,12 +66,26 @@ struct BlendTreeConnectionResource {
|
||||
}
|
||||
};
|
||||
|
||||
struct BlendTreeResource {
|
||||
struct BlendTreeResource : AnimGraphResource {
|
||||
typedef std::pair<const AnimNodeResource*, std::string> NodeSocketNamePair;
|
||||
typedef std::map<NodeSocketNamePair, int> NodeSocketDataOffsetMap;
|
||||
|
||||
std::vector<std::vector<size_t> > m_node_input_connection_indices;
|
||||
std::vector<std::vector<size_t> > m_node_inputs_subtree;
|
||||
|
||||
BlendTreeResource() {
|
||||
m_virtual_socket_accessor = VirtualAnimNodeDescriptorFactory("BlendTree");
|
||||
|
||||
InitGraphConnectors();
|
||||
RegisterBlendTreeOutputSocket<Pose>(AnimGraphResource::DefaultAnimOutput);
|
||||
}
|
||||
~BlendTreeResource() { ClearAllNodes(); }
|
||||
|
||||
[[maybe_unused]] bool SaveToFile(const char* filename) const override;
|
||||
static BlendTreeResource* CreateFromFile(const char* filename);
|
||||
|
||||
void CreateBlendTreeInstance(AnimGraphBlendTree& result) const;
|
||||
|
||||
void Reset() {
|
||||
ClearAllNodes();
|
||||
|
||||
@@ -65,18 +106,77 @@ struct BlendTreeResource {
|
||||
[[nodiscard]] AnimNodeResource* GetGraphOutputNode() const {
|
||||
return m_nodes[0];
|
||||
}
|
||||
|
||||
[[nodiscard]] AnimNodeResource* GetGraphInputNode() const {
|
||||
return m_nodes[1];
|
||||
}
|
||||
|
||||
Socket* GetGraphOutputSocket(const char* socket_name) const {
|
||||
return GetGraphOutputNode()->m_virtual_socket_accessor->GetInputSocket(
|
||||
socket_name);
|
||||
}
|
||||
|
||||
Socket* GetGraphInputSocket(const char* socket_name) const {
|
||||
return GetGraphInputNode()->m_virtual_socket_accessor->GetOutputSocket(
|
||||
socket_name);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool RegisterBlendTreeInputSocket(const std::string& socket_name) {
|
||||
Socket socket;
|
||||
socket.m_name = socket_name;
|
||||
socket.m_type = GetSocketType<T>();
|
||||
socket.m_type_size = sizeof(T);
|
||||
|
||||
return RegisterBlendTreeInputSocket(socket);
|
||||
}
|
||||
|
||||
bool RegisterBlendTreeInputSocket(const Socket& socket) {
|
||||
AnimNodeResource* input_node = GetGraphInputNode();
|
||||
|
||||
Socket* input_socket = GetGraphInputSocket(socket.m_name.c_str());
|
||||
|
||||
if (input_socket != nullptr) {
|
||||
std::cerr << "Error: cannot register output socket as socket with name '"
|
||||
<< socket.m_name << "' already exists!" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
input_node->m_virtual_socket_accessor->m_outputs.push_back(socket);
|
||||
m_virtual_socket_accessor->m_inputs =
|
||||
input_node->m_virtual_socket_accessor->m_outputs;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool RegisterBlendTreeOutputSocket(const std::string& socket_name) {
|
||||
Socket socket;
|
||||
socket.m_name = socket_name;
|
||||
socket.m_type = GetSocketType<T>();
|
||||
socket.m_type_size = sizeof(T);
|
||||
|
||||
return RegisterBlendTreeOutputSocket(socket);
|
||||
}
|
||||
|
||||
bool RegisterBlendTreeOutputSocket(const Socket& socket) {
|
||||
AnimNodeResource* output_node = GetGraphOutputNode();
|
||||
|
||||
Socket* output_socket = GetGraphOutputSocket(socket.m_name.c_str());
|
||||
|
||||
if (output_socket != nullptr) {
|
||||
std::cerr << "Error: cannot register output socket as socket with name '"
|
||||
<< socket.m_name << "' already exists!" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
output_node->m_virtual_socket_accessor->m_inputs.push_back(socket);
|
||||
m_virtual_socket_accessor->m_outputs =
|
||||
output_node->m_virtual_socket_accessor->m_inputs;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int GetNodeIndex(const AnimNodeResource* node_resource) const {
|
||||
for (size_t i = 0, n = m_nodes.size(); i < n; i++) {
|
||||
if (m_nodes[i] == node_resource) {
|
||||
@@ -125,14 +225,18 @@ struct BlendTreeResource {
|
||||
void RemoveConnectionsForSocket(
|
||||
const AnimNodeResource* node_resource,
|
||||
const Socket& socket);
|
||||
|
||||
void RemoveNodeConnections(AnimNodeResource* node_resource);
|
||||
|
||||
[[maybe_unused]] bool RemoveNode(AnimNodeResource* node_resource);
|
||||
|
||||
[[nodiscard]] size_t GetNumNodes() const { return m_nodes.size(); }
|
||||
[[nodiscard]] AnimNodeResource* GetNode(size_t i) { return m_nodes[i]; }
|
||||
|
||||
[[nodiscard]] const AnimNodeResource* GetNode(size_t i) const {
|
||||
return m_nodes[i];
|
||||
}
|
||||
|
||||
[[nodiscard]] const std::vector<AnimNodeResource*>& GetNodes() const {
|
||||
return m_nodes;
|
||||
}
|
||||
@@ -172,6 +276,7 @@ struct BlendTreeResource {
|
||||
const size_t socket_input_index) const;
|
||||
|
||||
std::vector<Socket> GetNodeOutputSockets(const AnimNodeResource* node) const;
|
||||
|
||||
std::vector<Socket> GetNodeInputSockets(const AnimNodeResource* node) const;
|
||||
|
||||
bool ConnectSockets(
|
||||
@@ -265,6 +370,18 @@ struct BlendTreeResource {
|
||||
}
|
||||
|
||||
private:
|
||||
void CreateBlendTreeRuntimeNodeInstances(AnimGraphBlendTree& result) const;
|
||||
|
||||
void PrepareBlendTreeIOData(
|
||||
AnimGraphBlendTree& instance,
|
||||
NodeSocketDataOffsetMap& node_offset_map) const;
|
||||
|
||||
void SetRuntimeNodeProperties(AnimGraphBlendTree& result) const;
|
||||
|
||||
void CreateBlendTreeConnectionInstances(
|
||||
AnimGraphBlendTree& instance,
|
||||
NodeSocketDataOffsetMap& node_offset_map) const;
|
||||
|
||||
void InitGraphConnectors() {
|
||||
AddNode(AnimNodeResourceFactory("BlendTreeSockets"));
|
||||
AnimNodeResource* output_node = GetGraphOutputNode();
|
||||
@@ -281,7 +398,9 @@ struct BlendTreeResource {
|
||||
m_node_eval_order.clear();
|
||||
UpdateNodeEvalOrderRecursive(0);
|
||||
}
|
||||
|
||||
void UpdateNodeEvalOrderRecursive(size_t node_index);
|
||||
|
||||
void UpdateNodeSubtrees();
|
||||
|
||||
std::vector<AnimNodeResource*> m_nodes;
|
||||
@@ -291,123 +410,12 @@ struct BlendTreeResource {
|
||||
friend class AnimGraphResource;
|
||||
};
|
||||
|
||||
struct StateMachineTransitionResources {
|
||||
size_t source_state_index = -1;
|
||||
size_t target_state_index = -1;
|
||||
float blend_time = 0.f;
|
||||
bool sync_blend = false;
|
||||
};
|
||||
|
||||
struct StateMachineResource {
|
||||
std::vector<AnimNodeResource> m_states;
|
||||
std::vector<StateMachineTransitionResources> m_transitions;
|
||||
};
|
||||
|
||||
struct AnimGraphResource : AnimNodeResource {
|
||||
explicit AnimGraphResource(AnimGraphType graph_type);
|
||||
virtual ~AnimGraphResource() { Clear(); };
|
||||
|
||||
static constexpr char DefaultAnimOutput[] = "Output";
|
||||
|
||||
std::string m_graph_type_name;
|
||||
|
||||
BlendTreeResource m_blend_tree_resource;
|
||||
typedef std::pair<const AnimNodeResource*, std::string> NodeSocketPair;
|
||||
typedef std::map<NodeSocketPair, int> NodeSocketDataOffsetMap;
|
||||
|
||||
StateMachineResource m_state_machine_resource;
|
||||
|
||||
void Clear() { m_blend_tree_resource.Reset(); }
|
||||
[[maybe_unused]] bool SaveToFile(const char* filename) const;
|
||||
static AnimGraphResource* CreateFromFile(const char* filename);
|
||||
|
||||
void CreateBlendTreeInstance(AnimGraphBlendTree& result) const;
|
||||
|
||||
template <typename T>
|
||||
bool RegisterBlendTreeInputSocket(const std::string& socket_name) {
|
||||
Socket socket;
|
||||
socket.m_name = socket_name;
|
||||
socket.m_type = GetSocketType<T>();
|
||||
socket.m_type_size = sizeof(T);
|
||||
|
||||
return RegisterBlendTreeInputSocket(socket);
|
||||
}
|
||||
|
||||
bool RegisterBlendTreeInputSocket(const Socket& socket) {
|
||||
AnimNodeResource* input_node = m_blend_tree_resource.GetGraphInputNode();
|
||||
|
||||
Socket* input_socket =
|
||||
m_blend_tree_resource.GetGraphInputSocket(socket.m_name.c_str());
|
||||
|
||||
if (input_socket != nullptr) {
|
||||
std::cerr << "Error: cannot register output socket as socket with name '"
|
||||
<< socket.m_name << "' already exists!" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
input_node->m_virtual_socket_accessor->m_outputs.push_back(socket);
|
||||
m_virtual_socket_accessor->m_inputs =
|
||||
input_node->m_virtual_socket_accessor->m_outputs;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool RegisterBlendTreeOutputSocket(const std::string& socket_name) {
|
||||
Socket socket;
|
||||
socket.m_name = socket_name;
|
||||
socket.m_type = GetSocketType<T>();
|
||||
socket.m_type_size = sizeof(T);
|
||||
|
||||
return RegisterBlendTreeOutputSocket(socket);
|
||||
}
|
||||
|
||||
bool RegisterBlendTreeOutputSocket(const Socket& socket) {
|
||||
AnimNodeResource* output_node = m_blend_tree_resource.GetGraphOutputNode();
|
||||
|
||||
Socket* output_socket =
|
||||
m_blend_tree_resource.GetGraphOutputSocket(socket.m_name.c_str());
|
||||
|
||||
if (output_socket != nullptr) {
|
||||
std::cerr << "Error: cannot register output socket as socket with name '"
|
||||
<< socket.m_name << "' already exists!" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
output_node->m_virtual_socket_accessor->m_inputs.push_back(socket);
|
||||
m_virtual_socket_accessor->m_outputs =
|
||||
output_node->m_virtual_socket_accessor->m_inputs;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CreateStateMachineInstance(AnimGraphStateMachine& result) const;
|
||||
|
||||
private:
|
||||
// BlendTree
|
||||
bool SaveBlendTreeResourceToFile(const char* filename) const;
|
||||
void CreateBlendTreeRuntimeNodeInstances(AnimGraphBlendTree& result) const;
|
||||
void PrepareBlendTreeIOData(
|
||||
AnimGraphBlendTree& instance,
|
||||
NodeSocketDataOffsetMap& node_offset_map) const;
|
||||
void CreateBlendTreeConnectionInstances(
|
||||
AnimGraphBlendTree& instance,
|
||||
NodeSocketDataOffsetMap& node_offset_map) const;
|
||||
void SetRuntimeNodeProperties(AnimGraphBlendTree& result) const;
|
||||
|
||||
bool SaveStateMachineResourceToFile(const char* filename) const;
|
||||
bool LoadStateMachineResourceFromJson(nlohmann::json const& json_data);
|
||||
};
|
||||
|
||||
typedef std::unique_ptr<AnimGraphResource> AnimGraphResourcePtr;
|
||||
|
||||
inline AnimNodeResource* AnimNodeResourceFactory(
|
||||
const std::string& node_type_name) {
|
||||
AnimNodeResource* result;
|
||||
|
||||
if (node_type_name == "BlendTree") {
|
||||
AnimGraphResource* blend_tree_resource =
|
||||
new AnimGraphResource(AnimGraphType::GraphTypeBlendTree);
|
||||
AnimGraphResource* blend_tree_resource = new BlendTreeResource();
|
||||
result = blend_tree_resource;
|
||||
} else {
|
||||
result = new AnimNodeResource();
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
//
|
||||
// Created by martin on 11.04.25.
|
||||
//
|
||||
|
||||
#ifndef ANIMLIBRARY_H
|
||||
#define ANIMLIBRARY_H
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "AnimGraph/AnimGraphData.h"
|
||||
#include "ozz/base/io/archive.h"
|
||||
#include "ozz/base/io/stream.h"
|
||||
#include "ozz/base/log.h"
|
||||
|
||||
/** Manage a set of animations used for an AnimGraph.
|
||||
*
|
||||
* By default, it behaves like a resource that allows to resolve animation names to
|
||||
* AnimationResources. However, it can also trigger loading of the referenced resources from their
|
||||
* filenames. This only happens on-demand.
|
||||
*/
|
||||
struct AnimLibrary {
|
||||
typedef std::map<std::string, AnimationResource> AnimationResourceMap;
|
||||
AnimationResourceMap mAnimations = {};
|
||||
std::vector<ozz::animation::Animation*> mManagedAnimations = {};
|
||||
static constexpr const char* EXTERNAL_ANIMATION = "<external>";
|
||||
|
||||
AnimLibrary() = default;
|
||||
~AnimLibrary() { Reset(); }
|
||||
|
||||
bool AddAnimation(
|
||||
const std::string& name,
|
||||
ozz::animation::Animation* animation) {
|
||||
AnimationResource animation_resource;
|
||||
animation_resource.m_name = name;
|
||||
animation_resource.m_filename = EXTERNAL_ANIMATION;
|
||||
animation_resource.m_animation = animation;
|
||||
mAnimations[name] = animation_resource;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AddAnimationFile(const std::string& name, const std::string& filename) {
|
||||
if (mAnimations.find(name) != mAnimations.end()) {
|
||||
std::cerr << "Cannot add animation '" << name
|
||||
<< "' to library. Animation already exists." << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
AnimationResource animation_resource;
|
||||
animation_resource.m_name = name;
|
||||
animation_resource.m_animation = nullptr;
|
||||
animation_resource.m_filename = filename;
|
||||
|
||||
mAnimations[name] = animation_resource;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Reset() {
|
||||
for (ozz::animation::Animation* animation : mManagedAnimations) {
|
||||
assert(animation->num_tracks() < 5);
|
||||
delete animation;
|
||||
}
|
||||
|
||||
mManagedAnimations.clear();
|
||||
mAnimations.clear();
|
||||
}
|
||||
|
||||
void LoadAnimations() {
|
||||
for (AnimationResourceMap::iterator iter = mAnimations.begin();
|
||||
iter != mAnimations.end();
|
||||
++iter) {
|
||||
if (iter->second.m_filename == EXTERNAL_ANIMATION) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (iter->second.m_animation != nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
assert(!iter->second.m_filename.empty());
|
||||
ozz::io::File file(iter->second.m_filename.c_str(), "rb");
|
||||
if (!file.opened()) {
|
||||
ozz::log::Err() << "Failed to open animation file "
|
||||
<< iter->second.m_filename << "." << std::endl;
|
||||
continue;
|
||||
}
|
||||
ozz::io::IArchive archive(&file);
|
||||
if (!archive.TestTag<ozz::animation::Animation>()) {
|
||||
ozz::log::Err() << "Failed to load animation instance from file "
|
||||
<< iter->second.m_filename << "." << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
iter->second.m_animation = new ozz::animation::Animation;
|
||||
|
||||
archive >> *iter->second.m_animation;
|
||||
|
||||
mManagedAnimations.push_back(iter->second.m_animation);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
inline void to_json(nlohmann::json& j, const AnimLibrary& animation_library) {
|
||||
j["type"] = "AnimationLibrary";
|
||||
|
||||
for (AnimLibrary::AnimationResourceMap::const_iterator iter =
|
||||
animation_library.mAnimations.cbegin();
|
||||
iter != animation_library.mAnimations.cend();
|
||||
++iter) {
|
||||
j["animations"][iter->first] = iter->second;
|
||||
}
|
||||
}
|
||||
|
||||
inline void from_json(const nlohmann::json& j, AnimLibrary& animation_library) {
|
||||
animation_library.Reset();
|
||||
|
||||
if (!j.contains("type") || j["type"] != "AnimationLibrary") {
|
||||
std::cerr << "Invalid type. Expected 'AnimationLibrary'." << std::endl;
|
||||
}
|
||||
|
||||
if (!j.contains("animations")) {
|
||||
std::cerr << "Invalid AnimationLibrary. Expected 'animations' key."
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
for (nlohmann::json::const_iterator iter = j["animations"].begin();
|
||||
iter != j["animations"].cend();
|
||||
++iter) {
|
||||
AnimationResource animation_resource = *iter;
|
||||
|
||||
if (!animation_resource.m_filename.empty()) {
|
||||
animation_library.AddAnimationFile(
|
||||
iter.key(),
|
||||
j["animations"][iter.key()]["filename"]);
|
||||
animation_library.mAnimations[iter.key()].m_sync_track =
|
||||
animation_resource.m_sync_track;
|
||||
} else {
|
||||
animation_library.mAnimations[iter.key()] = *iter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif //ANIMLIBRARY_H
|
||||
@@ -45,8 +45,7 @@ struct AnimNode {
|
||||
for (const auto& input : input_connections) {
|
||||
AnimNode* input_node = input.m_source_node;
|
||||
if (input_node != nullptr) {
|
||||
input_node->m_tick_number = m_tick_number;
|
||||
input_node->m_state = AnimNodeEvalState::Activated;
|
||||
input_node->Activate(m_tick_number);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,13 +63,18 @@ struct AnimNode {
|
||||
}
|
||||
}
|
||||
|
||||
virtual void UpdateTime(float time_last, float time_now) {
|
||||
virtual void UpdateTime(const float time_last, const float time_now) {
|
||||
m_time_last = time_last;
|
||||
m_time_now = time_now;
|
||||
m_state = AnimNodeEvalState::TimeUpdated;
|
||||
}
|
||||
|
||||
virtual void Evaluate(AnimGraphContext& context){};
|
||||
virtual void Evaluate(AnimGraphContext& context) {};
|
||||
|
||||
void Activate(const int tick_number) {
|
||||
m_tick_number = tick_number;
|
||||
m_state = AnimNodeEvalState::Activated;
|
||||
}
|
||||
};
|
||||
|
||||
#endif //ANIMTESTBED_ANIMNODE_H
|
||||
|
||||
@@ -3,7 +3,3 @@
|
||||
//
|
||||
|
||||
#include "SyncTrack.h"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <sstream>
|
||||
@@ -0,0 +1,206 @@
|
||||
//
|
||||
// Created by martin on 19.11.21.
|
||||
//
|
||||
|
||||
#ifndef ANIMTESTBED_SYNCTRACK_H
|
||||
#define ANIMTESTBED_SYNCTRACK_H
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "3rdparty/json/json.hpp"
|
||||
|
||||
constexpr int cSyncTrackMaxIntervals = 8;
|
||||
|
||||
/// Metadata used for synced animation blending.
|
||||
//
|
||||
// A SyncTrack consists of multiple SyncInterval that are adjacent to each
|
||||
// other.
|
||||
//
|
||||
// Important definitions:
|
||||
//
|
||||
// - Absolute Time: time within an animation duration in seconds.
|
||||
// - Ratio: time relative to the animations duration, e.g. 0.5 corresponds to
|
||||
// 50% of the duration.
|
||||
// - SyncTime is a floating point value where the integer parts defines the
|
||||
// SyncInterval and the fractional part the fraction within the interval. I.e.
|
||||
// a SyncTime of 5.332 means it is ~33% through interval 5.
|
||||
//
|
||||
// A SyncInterval is defined by a ratio of the starting point and the ratio of
|
||||
// the interval's duration.
|
||||
struct SyncTrack {
|
||||
SyncTrack() : m_duration(0.f), m_num_intervals(1) {
|
||||
for (int i = 0; i < cSyncTrackMaxIntervals; i++) {
|
||||
m_interval_start_ratio[i] = 0.f;
|
||||
m_interval_duration_ratio[i] = 0.f;
|
||||
}
|
||||
|
||||
m_interval_duration_ratio[0] = 1.0f;
|
||||
}
|
||||
|
||||
float m_duration;
|
||||
int m_num_intervals;
|
||||
float m_interval_start_ratio
|
||||
[cSyncTrackMaxIntervals]; //< Starting time of interval in absolute time.
|
||||
float m_interval_duration_ratio[cSyncTrackMaxIntervals]; //<
|
||||
|
||||
float CalcSyncFromAbsTime(float abs_time) {
|
||||
for (int i = 0; i < m_num_intervals; i++) {
|
||||
float query_abs_time = abs_time;
|
||||
float interval_start = m_interval_start_ratio[i] * m_duration;
|
||||
float interval_end =
|
||||
interval_start + m_interval_duration_ratio[i] * m_duration;
|
||||
|
||||
if (query_abs_time < interval_start) {
|
||||
query_abs_time += m_duration;
|
||||
}
|
||||
if (query_abs_time >= interval_start && query_abs_time < interval_end) {
|
||||
return float(i)
|
||||
+ (query_abs_time - interval_start)
|
||||
/ (interval_end - interval_start);
|
||||
}
|
||||
}
|
||||
|
||||
assert(false && "Invalid absolute time");
|
||||
return -1.f;
|
||||
}
|
||||
|
||||
float CalcRatioFromSyncTime(float sync_time) {
|
||||
float interval_ratio = fmodf(sync_time, 1.0f);
|
||||
int interval = int(sync_time - interval_ratio);
|
||||
|
||||
return fmodf(
|
||||
m_interval_start_ratio[interval]
|
||||
+ m_interval_duration_ratio[interval] * interval_ratio,
|
||||
1.0f);
|
||||
}
|
||||
|
||||
bool operator==(const SyncTrack& other) const {
|
||||
bool result = m_duration == other.m_duration
|
||||
&& m_num_intervals == other.m_num_intervals;
|
||||
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_num_intervals; i++) {
|
||||
if ((fabsf(m_interval_start_ratio[i] - other.m_interval_start_ratio[i])
|
||||
> 1.0e-5)
|
||||
|| (fabsf(
|
||||
m_interval_duration_ratio[i]
|
||||
- other.m_interval_duration_ratio[i])
|
||||
> 1.0e-5)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Constructs SyncTrack from markers.
|
||||
*
|
||||
* Markers are specified in absolute time and must be >= 0 and <= duration.
|
||||
* They define the start of the interval. The last marker is implicitly the
|
||||
* (possibly looped) first marker.
|
||||
*/
|
||||
static SyncTrack CreateFromMarkers(
|
||||
float duration,
|
||||
const std::vector<float>& markers) {
|
||||
assert(markers.size() > 0);
|
||||
assert(markers.size() < cSyncTrackMaxIntervals);
|
||||
|
||||
SyncTrack result;
|
||||
result.m_duration = duration;
|
||||
result.m_num_intervals = markers.size();
|
||||
|
||||
for (int i = 0; i < markers.size(); i++) {
|
||||
assert(markers[i] >= 0.f && markers[i] <= duration);
|
||||
|
||||
int end_index = i == (markers.size() - 1) ? 0 : i + 1;
|
||||
|
||||
float interval_start = markers[i];
|
||||
float interval_end = markers[end_index];
|
||||
|
||||
if (interval_end == interval_start) {
|
||||
interval_end = interval_start + duration;
|
||||
} else if (interval_end < interval_start) {
|
||||
interval_end += duration;
|
||||
}
|
||||
|
||||
result.m_interval_start_ratio[i] = interval_start / duration;
|
||||
result.m_interval_duration_ratio[i] =
|
||||
(interval_end - interval_start) / duration;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static SyncTrack
|
||||
Blend(float weight, const SyncTrack& track_A, const SyncTrack& track_B) {
|
||||
assert(track_A.m_num_intervals == track_B.m_num_intervals);
|
||||
|
||||
SyncTrack result;
|
||||
result.m_num_intervals = track_A.m_num_intervals;
|
||||
|
||||
result.m_duration =
|
||||
(1.0f - weight) * track_A.m_duration + weight * track_B.m_duration;
|
||||
|
||||
float interval_0_offset =
|
||||
track_B.m_interval_start_ratio[0] - track_A.m_interval_start_ratio[0];
|
||||
if (interval_0_offset > 0.5f) {
|
||||
interval_0_offset = -fmodf(1.f - interval_0_offset, 1.0f);
|
||||
} else if (interval_0_offset < -0.5) {
|
||||
interval_0_offset = fmodf(1.f + interval_0_offset, 1.0f);
|
||||
}
|
||||
|
||||
result.m_interval_start_ratio[0] = fmodf(
|
||||
1.0 + (1.0f - weight) * track_A.m_interval_start_ratio[0]
|
||||
+ weight * (track_A.m_interval_start_ratio[0] + interval_0_offset),
|
||||
1.0f);
|
||||
|
||||
for (int i = 0; i < result.m_num_intervals; i++) {
|
||||
float interval_duration_A = track_A.m_interval_duration_ratio[i];
|
||||
float interval_duration_B = track_B.m_interval_duration_ratio[i];
|
||||
result.m_interval_duration_ratio[i] =
|
||||
(1.0f - weight) * interval_duration_A + weight * interval_duration_B;
|
||||
|
||||
if (i < cSyncTrackMaxIntervals) {
|
||||
result.m_interval_start_ratio[i + 1] =
|
||||
result.m_interval_start_ratio[i]
|
||||
+ result.m_interval_duration_ratio[i];
|
||||
if (result.m_interval_start_ratio[i + 1] > 1.0f) {
|
||||
result.m_interval_start_ratio[i + 1] =
|
||||
fmodf(result.m_interval_start_ratio[i + 1], 1.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert(result.m_num_intervals < cSyncTrackMaxIntervals);
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
inline void to_json(nlohmann::json& j, const SyncTrack& sync_track) {
|
||||
j["type"] = "SyncTrack";
|
||||
j["duration"] = sync_track.m_duration;
|
||||
for (int i = 0; i < sync_track.m_num_intervals; i++) {
|
||||
j["interval"][i]["start"] = sync_track.m_interval_start_ratio[i];
|
||||
j["interval"][i]["ratio"] = sync_track.m_interval_duration_ratio[i];
|
||||
}
|
||||
}
|
||||
|
||||
inline void from_json(const nlohmann::json& j, SyncTrack& sync_track) {
|
||||
assert(j["type"] == "SyncTrack");
|
||||
|
||||
sync_track.m_duration = j["duration"];
|
||||
|
||||
sync_track.m_num_intervals = j["interval"].size();
|
||||
assert(sync_track.m_num_intervals < cSyncTrackMaxIntervals);
|
||||
|
||||
for (int i = 0; i < sync_track.m_num_intervals; i++) {
|
||||
sync_track.m_interval_start_ratio[i] = j["interval"][i]["start"];
|
||||
sync_track.m_interval_duration_ratio[i] = j["interval"][i]["ratio"];
|
||||
}
|
||||
}
|
||||
|
||||
#endif //ANIMTESTBED_SYNCTRACK_H
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "imnodes.h"
|
||||
#include "misc/cpp/imgui_stdlib.h"
|
||||
#include "nfd.h"
|
||||
#include "ozz/base/log.h"
|
||||
#include "src/AnimGraph/AnimGraphResource.h"
|
||||
|
||||
struct EditorState {
|
||||
@@ -154,21 +155,7 @@ void SyncTrackEditor(SyncTrack* sync_track) {
|
||||
}
|
||||
|
||||
ImGui::Text("Marker:");
|
||||
for (int i = 0; i < sync_track->m_num_intervals; i++) {
|
||||
ImGui::Text("%2d:", i);
|
||||
ImGui::SameLine();
|
||||
std::ostringstream marker_stream;
|
||||
marker_stream << i;
|
||||
ImGui::SliderFloat(
|
||||
marker_stream.str().c_str(),
|
||||
&sync_track->m_sync_markers[i],
|
||||
0.f,
|
||||
1.f);
|
||||
}
|
||||
|
||||
if (ImGui::Button("Update Intervals")) {
|
||||
sync_track->CalcIntervals();
|
||||
}
|
||||
ImGui::Text("TODO");
|
||||
}
|
||||
|
||||
void SkinnedMeshWidget(SkinnedMesh* skinned_mesh) {
|
||||
@@ -214,9 +201,15 @@ void SkinnedMeshWidget(SkinnedMesh* skinned_mesh) {
|
||||
}
|
||||
}
|
||||
|
||||
void AnimGraphEditorRenderSidebar(
|
||||
BlendTreeResource& blend_tree_resource,
|
||||
void BlendTreeEditorRenderSidebar(
|
||||
BlendTreeResource* blend_tree_resource,
|
||||
AnimNodeResource* node_resource) {
|
||||
BlendTreeResource* current_blend_tree_resource =
|
||||
dynamic_cast<BlendTreeResource*>(
|
||||
sEditorState.hierarchyStack[sEditorState.hierarchyStackIndex]);
|
||||
|
||||
assert(current_blend_tree_resource != nullptr);
|
||||
|
||||
ImGui::Text(
|
||||
"[%s (%2.2f, %2.2f)]",
|
||||
node_resource->m_node_type_name.c_str(),
|
||||
@@ -276,7 +269,7 @@ void AnimGraphEditorRenderSidebar(
|
||||
}
|
||||
}
|
||||
|
||||
if (node_resource == blend_tree_resource.GetGraphOutputNode()) {
|
||||
if (node_resource == blend_tree_resource->GetGraphOutputNode()) {
|
||||
ImGui::Text("Outputs");
|
||||
|
||||
// Graph outputs are the inputs of the output node!
|
||||
@@ -319,17 +312,14 @@ void AnimGraphEditorRenderSidebar(
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
if (ImGui::Button("+")) {
|
||||
AnimGraphResource* current_graph_resource =
|
||||
sEditorState.hierarchyStack[sEditorState.hierarchyStackIndex];
|
||||
current_graph_resource->RegisterBlendTreeOutputSocket<float>(
|
||||
current_blend_tree_resource->RegisterBlendTreeOutputSocket<float>(
|
||||
"GraphFloatOutput"
|
||||
+ std::to_string(current_graph_resource->m_blend_tree_resource
|
||||
.GetGraphOutputNode()
|
||||
+ std::to_string(current_blend_tree_resource->GetGraphOutputNode()
|
||||
->m_virtual_socket_accessor->m_inputs.size()));
|
||||
}
|
||||
}
|
||||
|
||||
if (node_resource == blend_tree_resource.GetGraphInputNode()) {
|
||||
if (node_resource == blend_tree_resource->GetGraphInputNode()) {
|
||||
ImGui::Text("Inputs");
|
||||
|
||||
// Graph inputs are the outputs of the input node!
|
||||
@@ -346,7 +336,7 @@ void AnimGraphEditorRenderSidebar(
|
||||
current_graph_resource->m_virtual_socket_accessor->m_inputs = inputs;
|
||||
}
|
||||
if (ImGui::Button("X")) {
|
||||
blend_tree_resource.RemoveConnectionsForSocket(node_resource, input);
|
||||
blend_tree_resource->RemoveConnectionsForSocket(node_resource, input);
|
||||
iter = inputs.erase(iter);
|
||||
} else {
|
||||
iter++;
|
||||
@@ -355,13 +345,10 @@ void AnimGraphEditorRenderSidebar(
|
||||
}
|
||||
|
||||
if (ImGui::Button("+")) {
|
||||
AnimGraphResource* current_graph_resource =
|
||||
sEditorState.hierarchyStack[sEditorState.hierarchyStackIndex];
|
||||
current_graph_resource->RegisterBlendTreeInputSocket<float>(
|
||||
current_blend_tree_resource->RegisterBlendTreeInputSocket<float>(
|
||||
"GraphFloatInput"
|
||||
+ std::to_string(
|
||||
current_graph_resource->m_blend_tree_resource.GetGraphInputNode()
|
||||
->m_virtual_socket_accessor->m_outputs.size()));
|
||||
+ std::to_string(current_blend_tree_resource->GetGraphInputNode()
|
||||
->m_virtual_socket_accessor->m_outputs.size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -439,12 +426,15 @@ void BlendTreeEditorNodePopup() {
|
||||
}
|
||||
|
||||
if (!node_type_name.empty()) {
|
||||
BlendTreeResource* current_blend_tree_resource =
|
||||
dynamic_cast<BlendTreeResource*>(
|
||||
sEditorState.hierarchyStack[sEditorState.hierarchyStackIndex]);
|
||||
|
||||
AnimNodeResource* node_resource = AnimNodeResourceFactory(node_type_name);
|
||||
ax::NodeEditor::SetNodePosition(
|
||||
ax::NodeEditor::NodeId(node_resource),
|
||||
sEditorState.mousePopupStart);
|
||||
sEditorState.hierarchyStack[sEditorState.hierarchyStackIndex]
|
||||
->m_blend_tree_resource.AddNode(node_resource);
|
||||
current_blend_tree_resource->AddNode(node_resource);
|
||||
}
|
||||
|
||||
ImGui::EndPopup();
|
||||
@@ -559,7 +549,7 @@ void AnimGraphEditorBreadcrumbNavigation() {
|
||||
}
|
||||
}
|
||||
|
||||
void HandleConnectionCreation(BlendTreeResource& current_blend_tree) {
|
||||
void BlendTreeHandleConnectionCreation(BlendTreeResource* current_blend_tree) {
|
||||
if (ax::NodeEditor::BeginCreate()) {
|
||||
ax::NodeEditor::PinId input_pin_id, output_pin_id;
|
||||
if (ax::NodeEditor::QueryNewLink(&input_pin_id, &output_pin_id)) {
|
||||
@@ -603,14 +593,14 @@ void HandleConnectionCreation(BlendTreeResource& current_blend_tree) {
|
||||
&source_node_index,
|
||||
&source_node_socket_index);
|
||||
|
||||
source_node = current_blend_tree.GetNode(source_node_index);
|
||||
source_node = current_blend_tree->GetNode(source_node_index);
|
||||
|
||||
if (source_node != nullptr) {
|
||||
if (source_node->m_virtual_socket_accessor->m_outputs.size()
|
||||
< source_node_socket_index) {
|
||||
source_node_socket_index = -1;
|
||||
} else {
|
||||
source_socket = current_blend_tree.GetNodeOutputSocketByIndex(
|
||||
source_socket = current_blend_tree->GetNodeOutputSocketByIndex(
|
||||
source_node,
|
||||
source_node_socket_index);
|
||||
}
|
||||
@@ -628,14 +618,14 @@ void HandleConnectionCreation(BlendTreeResource& current_blend_tree) {
|
||||
&target_node_index,
|
||||
&target_node_socket_index);
|
||||
|
||||
target_node = current_blend_tree.GetNode(target_node_index);
|
||||
target_node = current_blend_tree->GetNode(target_node_index);
|
||||
|
||||
if (target_node != nullptr) {
|
||||
if (target_node->m_virtual_socket_accessor->m_inputs.size()
|
||||
< target_node_socket_index) {
|
||||
target_node_socket_index = -1;
|
||||
} else {
|
||||
target_socket = current_blend_tree.GetNodeInputSocketByIndex(
|
||||
target_socket = current_blend_tree->GetNodeInputSocketByIndex(
|
||||
target_node,
|
||||
target_node_socket_index);
|
||||
}
|
||||
@@ -658,14 +648,14 @@ void HandleConnectionCreation(BlendTreeResource& current_blend_tree) {
|
||||
|
||||
if (!source_pin.Invalid && !target_pin.Invalid) {
|
||||
if (source_socket == nullptr || target_socket == nullptr
|
||||
|| !current_blend_tree.IsConnectionValid(
|
||||
|| !current_blend_tree->IsConnectionValid(
|
||||
source_node,
|
||||
source_socket->m_name,
|
||||
target_node,
|
||||
target_socket->m_name)) {
|
||||
ax::NodeEditor::RejectNewItem();
|
||||
} else if (ax::NodeEditor::AcceptNewItem()) {
|
||||
current_blend_tree.ConnectSockets(
|
||||
current_blend_tree->ConnectSockets(
|
||||
source_node,
|
||||
source_socket->m_name,
|
||||
target_node,
|
||||
@@ -678,12 +668,12 @@ void HandleConnectionCreation(BlendTreeResource& current_blend_tree) {
|
||||
}
|
||||
|
||||
void BlendTreeRenderNodes(
|
||||
BlendTreeResource& current_blend_tree,
|
||||
BlendTreeResource* current_blend_tree,
|
||||
ax::NodeEditor::Utilities::BlueprintNodeBuilder& builder) {
|
||||
for (size_t node_index = 0, n = current_blend_tree.GetNumNodes();
|
||||
for (size_t node_index = 0, n = current_blend_tree->GetNumNodes();
|
||||
node_index < n;
|
||||
node_index++) {
|
||||
AnimNodeResource* node_resource = current_blend_tree.GetNode(node_index);
|
||||
AnimNodeResource* node_resource = current_blend_tree->GetNode(node_index);
|
||||
|
||||
ax::NodeEditor::NodeId node_id(node_resource);
|
||||
|
||||
@@ -706,7 +696,7 @@ void BlendTreeRenderNodes(
|
||||
|
||||
// Inputs
|
||||
std::vector<Socket> node_inputs =
|
||||
current_blend_tree.GetNodeInputSockets(node_resource);
|
||||
current_blend_tree->GetNodeInputSockets(node_resource);
|
||||
for (size_t j = 0, ni = node_inputs.size(); j < ni; j++) {
|
||||
Socket& socket = node_inputs[j];
|
||||
ax::NodeEditor::PinId input_pin = NodeIndexAndSocketIndexToInputPinId(
|
||||
@@ -718,7 +708,7 @@ void BlendTreeRenderNodes(
|
||||
|
||||
DrawSocketIcon(
|
||||
socket.m_type,
|
||||
current_blend_tree.IsSocketConnected(node_resource, socket.m_name));
|
||||
current_blend_tree->IsSocketConnected(node_resource, socket.m_name));
|
||||
ImGui::Spring(0);
|
||||
|
||||
//ImGui::PushItemWidth(100.0f);
|
||||
@@ -729,7 +719,7 @@ void BlendTreeRenderNodes(
|
||||
|
||||
// Outputs
|
||||
std::vector<Socket> node_outputs =
|
||||
current_blend_tree.GetNodeOutputSockets(node_resource);
|
||||
current_blend_tree->GetNodeOutputSockets(node_resource);
|
||||
for (size_t j = 0, ni = node_outputs.size(); j < ni; j++) {
|
||||
Socket& socket = node_outputs[j];
|
||||
builder.Output(NodeIndexAndSocketIndexToOutputPinId(
|
||||
@@ -741,7 +731,7 @@ void BlendTreeRenderNodes(
|
||||
ImGui::Spring(0);
|
||||
DrawSocketIcon(
|
||||
socket.m_type,
|
||||
current_blend_tree.IsSocketConnected(node_resource, socket.m_name));
|
||||
current_blend_tree->IsSocketConnected(node_resource, socket.m_name));
|
||||
|
||||
builder.EndOutput();
|
||||
}
|
||||
@@ -754,21 +744,21 @@ void BlendTreeRenderNodes(
|
||||
}
|
||||
}
|
||||
|
||||
void BlendTreeRenderConnections(BlendTreeResource& current_blend_tree) {
|
||||
for (size_t connection_id = 0, n = current_blend_tree.GetNumConnections();
|
||||
void BlendTreeRenderConnections(BlendTreeResource* current_blend_tree) {
|
||||
for (size_t connection_id = 0, n = current_blend_tree->GetNumConnections();
|
||||
connection_id < n;
|
||||
connection_id++) {
|
||||
const BlendTreeConnectionResource* connection_resource =
|
||||
current_blend_tree.GetConnection(connection_id);
|
||||
current_blend_tree->GetConnection(connection_id);
|
||||
|
||||
const AnimNodeResource* source_node_resource =
|
||||
current_blend_tree.GetNode(connection_resource->source_node_index);
|
||||
current_blend_tree->GetNode(connection_resource->source_node_index);
|
||||
int source_socket_index =
|
||||
source_node_resource->m_virtual_socket_accessor->GetOutputIndex(
|
||||
connection_resource->source_socket_name.c_str());
|
||||
|
||||
const AnimNodeResource* target_node_resource =
|
||||
current_blend_tree.GetNode(connection_resource->target_node_index);
|
||||
current_blend_tree->GetNode(connection_resource->target_node_index);
|
||||
int target_socket_index =
|
||||
target_node_resource->m_virtual_socket_accessor->GetInputIndex(
|
||||
connection_resource->target_socket_name.c_str());
|
||||
@@ -786,16 +776,17 @@ void BlendTreeRenderConnections(BlendTreeResource& current_blend_tree) {
|
||||
target_socket_pin_id);
|
||||
}
|
||||
}
|
||||
void AnimGraphEditorDebugWidget() {
|
||||
|
||||
void BlendTreeEditorDebugWidget() {
|
||||
ImGui::Begin("Connection Debug Panel");
|
||||
ImGui::BeginTable("Connection", 3);
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::Text("Pin");
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%x", sNodeConnectionDebugState.sourceSocket.pin.AsPointer());
|
||||
ImGui::Text("%p", sNodeConnectionDebugState.sourceSocket.pin.AsPointer());
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%x", sNodeConnectionDebugState.targetSocket.pin.AsPointer());
|
||||
ImGui::Text("%p", sNodeConnectionDebugState.targetSocket.pin.AsPointer());
|
||||
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
@@ -882,17 +873,19 @@ void AnimGraphEditorUpdate(ax::NodeEditor::EditorContext* context) {
|
||||
graph_size.y -= 20;
|
||||
ax::NodeEditor::Begin("Graph Editor", graph_size);
|
||||
|
||||
AnimGraphResource* current_graph =
|
||||
sEditorState.hierarchyStack[sEditorState.hierarchyStackIndex];
|
||||
BlendTreeResource& current_blend_tree = current_graph->m_blend_tree_resource;
|
||||
BlendTreeResource* current_blend_tree_resource =
|
||||
dynamic_cast<BlendTreeResource*>(
|
||||
sEditorState.hierarchyStack[sEditorState.hierarchyStackIndex]);
|
||||
|
||||
ax::NodeEditor::Utilities::BlueprintNodeBuilder builder;
|
||||
if (current_blend_tree_resource) {
|
||||
ax::NodeEditor::Utilities::BlueprintNodeBuilder builder;
|
||||
|
||||
BlendTreeRenderNodes(current_blend_tree, builder);
|
||||
BlendTreeRenderConnections(current_blend_tree);
|
||||
BlendTreeRenderNodes(current_blend_tree_resource, builder);
|
||||
BlendTreeRenderConnections(current_blend_tree_resource);
|
||||
BlendTreeHandleConnectionCreation(current_blend_tree_resource);
|
||||
|
||||
HandleConnectionCreation(current_blend_tree);
|
||||
BlendTreeEditorNodePopup();
|
||||
BlendTreeEditorNodePopup();
|
||||
}
|
||||
|
||||
ax::NodeEditor::End();
|
||||
|
||||
@@ -903,22 +896,22 @@ void AnimGraphEditorUpdate(ax::NodeEditor::EditorContext* context) {
|
||||
//
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
|
||||
if (ax::NodeEditor::GetSelectedObjectCount() > 0) {
|
||||
if (current_blend_tree_resource
|
||||
&& ax::NodeEditor::GetSelectedObjectCount() > 0) {
|
||||
ax::NodeEditor::NodeId selected_node_id = 0;
|
||||
|
||||
ax::NodeEditor::GetSelectedNodes(&selected_node_id, 1);
|
||||
|
||||
if (selected_node_id.Get() != 0) {
|
||||
AnimGraphEditorRenderSidebar(
|
||||
sEditorState.hierarchyStack[sEditorState.hierarchyStackIndex]
|
||||
->m_blend_tree_resource,
|
||||
BlendTreeEditorRenderSidebar(
|
||||
current_blend_tree_resource,
|
||||
selected_node_id.AsPointer<AnimNodeResource>());
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
|
||||
AnimGraphEditorDebugWidget();
|
||||
BlendTreeEditorDebugWidget();
|
||||
|
||||
// Clear flag, however it may be re-set further down when handling double
|
||||
// clicking into subgraphs.
|
||||
@@ -962,15 +955,14 @@ void AnimGraphEditorUpdate(ax::NodeEditor::EditorContext* context) {
|
||||
BlendTreeConnectionResource* connection_resource =
|
||||
hovered_link.AsPointer<BlendTreeConnectionResource>();
|
||||
|
||||
if (connection_resource && ImGui::IsKeyPressed(ImGuiKey_Delete)) {
|
||||
BlendTreeResource* blend_tree_resource =
|
||||
&sEditorState.hierarchyStack[sEditorState.hierarchyStackIndex]
|
||||
->m_blend_tree_resource;
|
||||
|
||||
blend_tree_resource->DisconnectSockets(
|
||||
blend_tree_resource->GetNode(connection_resource->source_node_index),
|
||||
if (connection_resource && current_blend_tree_resource
|
||||
&& ImGui::IsKeyPressed(ImGuiKey_Delete)) {
|
||||
current_blend_tree_resource->DisconnectSockets(
|
||||
current_blend_tree_resource->GetNode(
|
||||
connection_resource->source_node_index),
|
||||
connection_resource->source_socket_name,
|
||||
blend_tree_resource->GetNode(connection_resource->target_node_index),
|
||||
current_blend_tree_resource->GetNode(
|
||||
connection_resource->target_node_index),
|
||||
connection_resource->target_socket_name);
|
||||
|
||||
ax::NodeEditor::DeleteLink(hovered_link);
|
||||
@@ -982,19 +974,20 @@ void AnimGraphEditorUpdate(ax::NodeEditor::EditorContext* context) {
|
||||
AnimNodeResource* node_resource =
|
||||
hovered_node.AsPointer<AnimNodeResource>();
|
||||
|
||||
if (node_resource && ImGui::IsKeyPressed(ImGuiKey_Delete)) {
|
||||
AnimGraphResource* current_graph_resource =
|
||||
sEditorState.hierarchyStack[sEditorState.hierarchyStackIndex];
|
||||
|
||||
current_graph_resource->m_blend_tree_resource.RemoveNodeConnections(
|
||||
node_resource);
|
||||
current_graph_resource->m_blend_tree_resource.RemoveNode(node_resource);
|
||||
if (node_resource && current_blend_tree_resource
|
||||
&& ImGui::IsKeyPressed(ImGuiKey_Delete)) {
|
||||
current_blend_tree_resource->RemoveNodeConnections(node_resource);
|
||||
current_blend_tree_resource->RemoveNode(node_resource);
|
||||
}
|
||||
}
|
||||
|
||||
ax::NodeEditor::SetCurrentEditor(nullptr);
|
||||
}
|
||||
|
||||
void AnimGraphEditorGetRuntimeGraph(AnimGraphBlendTree& blend_tree) {
|
||||
sEditorState.rootGraphResource->CreateBlendTreeInstance(blend_tree);
|
||||
void AnimGraphEditorGetRuntimeBlendTree(AnimGraphBlendTree& blend_tree) {
|
||||
BlendTreeResource* root_blend_tree_resource =
|
||||
dynamic_cast<BlendTreeResource*>(sEditorState.rootGraphResource);
|
||||
assert(root_blend_tree_resource);
|
||||
|
||||
root_blend_tree_resource->CreateBlendTreeInstance(blend_tree);
|
||||
}
|
||||
@@ -83,6 +83,6 @@ void AnimGraphEditorClear();
|
||||
|
||||
void AnimGraphEditorUpdate(ax::NodeEditor::EditorContext* context);
|
||||
|
||||
void AnimGraphEditorGetRuntimeGraph(AnimGraphBlendTree& anim_graph);
|
||||
void AnimGraphEditorGetRuntimeBlendTree(AnimGraphBlendTree& anim_graph);
|
||||
|
||||
#endif //ANIMTESTBED_ANIMGRAPHEDITOR_H
|
||||
|
||||
+21
-20
@@ -42,8 +42,8 @@ inline void Camera_Init(Camera* camera) {
|
||||
camera->pitch = 10 * M_PI / 180.0f;
|
||||
|
||||
memcpy(&camera->mtxView, &mtx_identity, sizeof(camera->mtxView));
|
||||
Camera_CalcToMatrix(camera, &camera->mtxView);
|
||||
Camera_CalcFromMatrix(camera, &camera->mtxView);
|
||||
Camera_CalcToMatrix(camera, &camera->mtxView[0]);
|
||||
Camera_CalcFromMatrix(camera, &camera->mtxView[0]);
|
||||
}
|
||||
|
||||
void Camera_CalcFromMatrix(Camera* camera, float* mat) {
|
||||
@@ -72,11 +72,11 @@ void Camera_CalcFromMatrix(Camera* camera, float* mat) {
|
||||
camera->pos[1] = -simd4f_get_y(eye);
|
||||
camera->pos[2] = -simd4f_get_z(eye);
|
||||
|
||||
// gLog ("ViewMat");
|
||||
// gLog ("%f, %f, %f, %f", mtx->x[0], mtx->x[1], mtx->x[2], mtx->x[3]);
|
||||
// gLog ("%f, %f, %f, %f", mtx->y[0], mtx->y[1], mtx->y[2], mtx->y[3]);
|
||||
// gLog ("%f, %f, %f, %f", mtx->z[0], mtx->z[1], mtx->z[2], mtx->z[3]);
|
||||
// gLog ("%f, %f, %f, %f", mtx->w[0], mtx->w[1], mtx->w[2], mtx->w[3]);
|
||||
// gLog ("ViewMat");
|
||||
// gLog ("%f, %f, %f, %f", mtx->x[0], mtx->x[1], mtx->x[2], mtx->x[3]);
|
||||
// gLog ("%f, %f, %f, %f", mtx->y[0], mtx->y[1], mtx->y[2], mtx->y[3]);
|
||||
// gLog ("%f, %f, %f, %f", mtx->z[0], mtx->z[1], mtx->z[2], mtx->z[3]);
|
||||
// gLog ("%f, %f, %f, %f", mtx->w[0], mtx->w[1], mtx->w[2], mtx->w[3]);
|
||||
}
|
||||
|
||||
void Camera_CalcToMatrix(Camera* camera, float* mat) {
|
||||
@@ -87,9 +87,10 @@ void Camera_CalcToMatrix(Camera* camera, float* mat) {
|
||||
|
||||
const float d = 10.0f;
|
||||
|
||||
simd4f eye = simd4f_create (camera->pos[0], camera->pos[1], camera->pos[2], 1.f);
|
||||
simd4f forward = simd4f_create (-cp * ch, -sp, cp * sh, 0.f);
|
||||
simd4f right = simd4f_cross3 (forward, simd4f_create (0.f, 1.f, 0.f, 1.f));
|
||||
simd4f eye =
|
||||
simd4f_create(camera->pos[0], camera->pos[1], camera->pos[2], 1.f);
|
||||
simd4f forward = simd4f_create(-cp * ch, -sp, cp * sh, 0.f);
|
||||
simd4f right = simd4f_cross3(forward, simd4f_create(0.f, 1.f, 0.f, 1.f));
|
||||
simd4f up = simd4f_cross3(right, forward);
|
||||
simd4f center = simd4f_add(simd4f_mul(forward, simd4f_splat(d)), eye);
|
||||
|
||||
@@ -105,9 +106,9 @@ void Camera_CalcToMatrix(Camera* camera, float* mat) {
|
||||
simd4x4f_lookat(&mtx, eye, center, up);
|
||||
|
||||
simd4f_ustore4(mtx.x, mat);
|
||||
simd4f_ustore4(mtx.y, mat +4);
|
||||
simd4f_ustore4(mtx.z, mat +8);
|
||||
simd4f_ustore4(mtx.w, mat +12);
|
||||
simd4f_ustore4(mtx.y, mat + 4);
|
||||
simd4f_ustore4(mtx.z, mat + 8);
|
||||
simd4f_ustore4(mtx.w, mat + 12);
|
||||
}
|
||||
|
||||
inline void Camera_Update(
|
||||
@@ -120,14 +121,14 @@ inline void Camera_Update(
|
||||
float accel[3]) {
|
||||
assert(camera);
|
||||
assert((width > 0) && (height > 0));
|
||||
const float w = (float) width;
|
||||
const float h = (float) height;
|
||||
const float w = (float)width;
|
||||
const float h = (float)height;
|
||||
simd4x4f proj;
|
||||
simd4x4f_perspective(&proj, camera->fov, w/h, camera->near, camera->far);
|
||||
simd4x4f_perspective(&proj, camera->fov, w / h, camera->near, camera->far);
|
||||
simd4f_ustore4(proj.x, camera->mtxProj);
|
||||
simd4f_ustore4(proj.y, camera->mtxProj +4);
|
||||
simd4f_ustore4(proj.z, camera->mtxProj +8);
|
||||
simd4f_ustore4(proj.w, camera->mtxProj +12);
|
||||
simd4f_ustore4(proj.y, camera->mtxProj + 4);
|
||||
simd4f_ustore4(proj.z, camera->mtxProj + 8);
|
||||
simd4f_ustore4(proj.w, camera->mtxProj + 12);
|
||||
|
||||
if (mouse_dx != 0.f || mouse_dy != 0.f || accel != NULL) {
|
||||
const float mouse_sensitivity = 20.0f;
|
||||
@@ -153,6 +154,6 @@ inline void Camera_Update(
|
||||
camera->vel[i] = camera->vel[i] * 0.1;
|
||||
}
|
||||
|
||||
Camera_CalcToMatrix(camera, &camera->mtxView);
|
||||
Camera_CalcToMatrix(camera, &camera->mtxView[0]);
|
||||
}
|
||||
}
|
||||
+5
-2
@@ -4,9 +4,13 @@
|
||||
|
||||
#include "SkinnedMesh.h"
|
||||
|
||||
#include <HandmadeMath.h>
|
||||
#include <imgui.h>
|
||||
|
||||
#include "ozz/animation/runtime/local_to_model_job.h"
|
||||
#include "ozz/base/io/archive.h"
|
||||
#include "ozz/base/io/stream.h"
|
||||
#include "ozz/base/log.h"
|
||||
|
||||
SkinnedMesh::~SkinnedMesh() {
|
||||
while (m_animations.size() > 0) {
|
||||
ozz::animation::Animation* animation_ptr =
|
||||
@@ -98,4 +102,3 @@ void SkinnedMesh::CalcModelMatrices() {
|
||||
}
|
||||
|
||||
void SkinnedMesh::DrawSkeleton() {}
|
||||
|
||||
|
||||
+1
-9
@@ -5,21 +5,13 @@
|
||||
#ifndef ANIMTESTBED_SKINNEDMESH_H
|
||||
#define ANIMTESTBED_SKINNEDMESH_H
|
||||
|
||||
// ozz-animation headers
|
||||
#include <cmath> // fmodf
|
||||
#include <memory> // std::unique_ptr, std::make_unique
|
||||
#include "AnimGraph/SyncTrack.h"
|
||||
|
||||
#include "SyncTrack.h"
|
||||
#include "ozz/animation/runtime/animation.h"
|
||||
#include "ozz/animation/runtime/local_to_model_job.h"
|
||||
#include "ozz/animation/runtime/sampling_job.h"
|
||||
#include "ozz/animation/runtime/skeleton.h"
|
||||
#include "ozz/base/containers/vector.h"
|
||||
#include "ozz/base/io/archive.h"
|
||||
#include "ozz/base/io/stream.h"
|
||||
#include "ozz/base/log.h"
|
||||
#include "ozz/base/maths/soa_transform.h"
|
||||
#include "ozz/base/maths/vec_float.h"
|
||||
|
||||
struct SkinnedMesh {
|
||||
SkinnedMesh() : m_sync_track_override(false), m_override_anim(0.f) {}
|
||||
|
||||
@@ -5,72 +5,15 @@
|
||||
#include "SkinnedMeshResource.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#include "3rdparty/json/json.hpp"
|
||||
|
||||
inline void to_json(nlohmann::json& j, const SyncTrack& syncTrack) {
|
||||
j["type"] = "SyncTrack";
|
||||
j["duration"] = syncTrack.m_duration;
|
||||
for (int i = 0; i < syncTrack.m_num_intervals; i++) {
|
||||
j["markers"][i] = syncTrack.m_sync_markers[i];
|
||||
}
|
||||
}
|
||||
|
||||
inline void from_json(const nlohmann::json& j, SyncTrack& syncTrack) {
|
||||
if (!j.contains("type") || j["type"] != "SyncTrack") {
|
||||
std::cerr << "Unable to parse SyncTrack: wrong json type!" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
syncTrack.m_duration = j["duration"];
|
||||
syncTrack.m_num_intervals = j["markers"].size();
|
||||
|
||||
if (syncTrack.m_num_intervals > cSyncTrackMaxIntervals) {
|
||||
std::cerr << "Invalid number of sync intervals: found " << syncTrack.m_num_intervals << " maximum is " << cSyncTrackMaxIntervals << "." << std::endl;
|
||||
syncTrack = SyncTrack();
|
||||
}
|
||||
|
||||
for (int i = 0; i < syncTrack.m_num_intervals; i++) {
|
||||
syncTrack.m_sync_markers[i] = j["markers"].at(i);
|
||||
}
|
||||
}
|
||||
|
||||
inline void to_json(nlohmann::json& j, const SkinnedMeshResource& skinnedMeshResource) {
|
||||
j["type"] = "SkinnedMeshResource";
|
||||
j["skeleton"]["file"] = skinnedMeshResource.m_skeleton_file;
|
||||
for (int i = 0; i < skinnedMeshResource.m_animation_files.size(); i++) {
|
||||
j["animations"][i]["file"] = skinnedMeshResource.m_animation_files[i];
|
||||
j["animations"][i]["sync_track"] = skinnedMeshResource.m_sync_tracks[i];
|
||||
}
|
||||
}
|
||||
|
||||
inline void from_json(const nlohmann::json& j, SkinnedMeshResource& skinnedMeshResource) {
|
||||
if (!j.contains("type") || j["type"] != "SkinnedMeshResource") {
|
||||
std::cerr << "Unable to parse SkinnedMeshResource: wrong json type!" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!j.contains("skeleton") || !j["skeleton"].contains("file")) {
|
||||
std::cerr << "Unable to parse SkinnedMeshResource: no skeleton file found!" << std::endl;
|
||||
|
||||
}
|
||||
|
||||
skinnedMeshResource.m_skeleton_file = j["skeleton"]["file"];
|
||||
|
||||
if (j.contains("animations")) {
|
||||
int num_animations = j["animations"].size();
|
||||
|
||||
for (int i = 0; i < num_animations; i++) {
|
||||
if (!j["animations"][i].contains("file") || !j["animations"][i].contains("sync_track")) {
|
||||
std::cerr << "Unable to parse SkinnedMeshResource: invalid animation definition" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
skinnedMeshResource.m_animation_files.push_back(j["animations"][i]["file"]);
|
||||
skinnedMeshResource.m_sync_tracks.push_back(j["animations"][i]["sync_track"].get<SyncTrack>());
|
||||
}
|
||||
}
|
||||
}
|
||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(
|
||||
SkinnedMeshResource,
|
||||
m_type,
|
||||
m_resource_file,
|
||||
m_skeleton_file);
|
||||
|
||||
bool SkinnedMeshResource::saveToFile(const char* filename) const {
|
||||
nlohmann::json j = *this;
|
||||
@@ -103,10 +46,4 @@ bool SkinnedMeshResource::loadFromFile(const char* filename) {
|
||||
|
||||
void SkinnedMeshResource::createInstance(SkinnedMesh& skinnedMesh) const {
|
||||
skinnedMesh.LoadSkeleton(m_skeleton_file.c_str());
|
||||
for (int i = 0; i < m_animation_files.size(); i++) {
|
||||
skinnedMesh.LoadAnimation(m_animation_files[i].c_str());
|
||||
skinnedMesh.m_animation_sync_track.back() = m_sync_tracks[i];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -8,14 +8,14 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "SyncTrack.h"
|
||||
#include "AnimGraph/SyncTrack.h"
|
||||
#include "SkinnedMesh.h"
|
||||
|
||||
struct SkinnedMeshResource {
|
||||
constexpr static char TypeStr[] = "SkinnedMeshResource";
|
||||
std::string m_type = TypeStr;
|
||||
std::string m_resource_file;
|
||||
std::string m_skeleton_file;
|
||||
std::vector<std::string> m_animation_files;
|
||||
std::vector<SyncTrack> m_sync_tracks;
|
||||
|
||||
bool saveToFile(const char* filename) const;
|
||||
bool loadFromFile(const char* filename);
|
||||
|
||||
-155
@@ -1,155 +0,0 @@
|
||||
//
|
||||
// Created by martin on 19.11.21.
|
||||
//
|
||||
|
||||
#ifndef ANIMTESTBED_SYNCTRACK_H
|
||||
#define ANIMTESTBED_SYNCTRACK_H
|
||||
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
|
||||
constexpr int cSyncTrackMaxIntervals = 8;
|
||||
|
||||
struct SyncTrack {
|
||||
SyncTrack() : m_duration(0.f), m_num_intervals(0) {
|
||||
for (int i = 0; i < cSyncTrackMaxIntervals; i++) {
|
||||
m_sync_markers[i] = 0.f;
|
||||
m_interval_ratio[i] = 0.f;
|
||||
m_interval_ratio[i] = 0.f;
|
||||
}
|
||||
}
|
||||
|
||||
float m_duration;
|
||||
int m_num_intervals;
|
||||
float m_sync_markers[cSyncTrackMaxIntervals];
|
||||
float m_interval_start[cSyncTrackMaxIntervals];
|
||||
float m_interval_ratio[cSyncTrackMaxIntervals];
|
||||
|
||||
void CalcIntervals() {
|
||||
if (m_num_intervals == 0) {
|
||||
m_num_intervals = 1;
|
||||
m_sync_markers[0] = 0.f;
|
||||
}
|
||||
for (int i = 0; i < m_num_intervals; i++) {
|
||||
assert(m_sync_markers[i] >= 0.f && m_sync_markers[i] <= 1.0f);
|
||||
|
||||
int end_index = i < m_num_intervals - 1 ? i + 1 : 0;
|
||||
|
||||
m_interval_start[i] = m_sync_markers[i];
|
||||
float interval_end = m_sync_markers[end_index];
|
||||
|
||||
if (interval_end < m_interval_start[i]) {
|
||||
interval_end += 1.0f;
|
||||
}
|
||||
|
||||
m_interval_ratio[i] = interval_end - m_interval_start[i];
|
||||
}
|
||||
}
|
||||
|
||||
float CalcSyncFromAbsTime(float abs_time) {
|
||||
float sync_time = fmodf(abs_time, m_duration) / m_duration;
|
||||
|
||||
int interval_index = 0;
|
||||
while (sync_time >= m_interval_ratio[interval_index]) {
|
||||
sync_time -= m_interval_ratio[interval_index];
|
||||
interval_index++;
|
||||
}
|
||||
|
||||
return float(interval_index) + sync_time / m_interval_ratio[interval_index];
|
||||
}
|
||||
|
||||
float CalcRatioFromSyncTime(float sync_time) {
|
||||
float interval_ratio = fmodf(sync_time, 1.0f);
|
||||
int interval = int(sync_time - interval_ratio);
|
||||
|
||||
return fmodf(
|
||||
m_interval_start[interval]
|
||||
+ m_interval_ratio[interval] * interval_ratio,
|
||||
1.0f);
|
||||
}
|
||||
|
||||
bool operator==(const SyncTrack& other) const {
|
||||
bool result = m_duration == other.m_duration
|
||||
&& m_num_intervals == other.m_num_intervals;
|
||||
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_num_intervals; i++) {
|
||||
if ((fabsf(m_interval_start[i] - other.m_interval_start[i]) > 1.0e-5)
|
||||
|| (fabsf(m_interval_ratio[i] - other.m_interval_ratio[i])
|
||||
> 1.0e-5)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static SyncTrack CreateFromMarkers(
|
||||
float duration,
|
||||
int n_markers,
|
||||
float markers[cSyncTrackMaxIntervals]) {
|
||||
SyncTrack result;
|
||||
result.m_duration = duration;
|
||||
result.m_num_intervals = n_markers;
|
||||
for (int i = 0; i < n_markers; i++) {
|
||||
result.m_sync_markers[i] = markers[i];
|
||||
}
|
||||
|
||||
result.CalcIntervals();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static SyncTrack
|
||||
Blend(float weight, const SyncTrack& track_A, const SyncTrack& track_B) {
|
||||
assert(track_A.m_num_intervals == track_B.m_num_intervals);
|
||||
|
||||
SyncTrack result;
|
||||
result.m_num_intervals = track_A.m_num_intervals;
|
||||
|
||||
result.m_duration =
|
||||
(1.0f - weight) * track_A.m_duration + weight * track_B.m_duration;
|
||||
|
||||
float interval_0_offset =
|
||||
track_B.m_interval_start[0] - track_A.m_interval_start[0];
|
||||
if (interval_0_offset > 0.5f) {
|
||||
interval_0_offset = -fmodf(1.f - interval_0_offset, 1.0f);
|
||||
} else if (interval_0_offset < -0.5) {
|
||||
interval_0_offset = fmodf(1.f + interval_0_offset, 1.0f);
|
||||
}
|
||||
|
||||
result.m_interval_start[0] = fmodf(
|
||||
1.0 + (1.0f - weight) * track_A.m_interval_start[0]
|
||||
+ weight * (track_A.m_interval_start[0] + interval_0_offset),
|
||||
1.0f);
|
||||
result.m_sync_markers[0] = result.m_interval_start[0];
|
||||
|
||||
for (int i = 0; i < result.m_num_intervals; i++) {
|
||||
float interval_duration_A = track_A.m_interval_ratio[i];
|
||||
float interval_duration_B = track_B.m_interval_ratio[i];
|
||||
result.m_interval_ratio[i] =
|
||||
(1.0f - weight) * interval_duration_A + weight * interval_duration_B;
|
||||
|
||||
if (i < cSyncTrackMaxIntervals) {
|
||||
result.m_interval_start[i + 1] =
|
||||
result.m_interval_start[i] + result.m_interval_ratio[i];
|
||||
if (result.m_interval_start[i + 1] > 1.0f) {
|
||||
result.m_interval_start[i + 1] =
|
||||
fmodf(result.m_interval_start[i + 1], 1.0f);
|
||||
}
|
||||
|
||||
result.m_sync_markers[i + 1] = result.m_interval_start[i + 1];
|
||||
}
|
||||
}
|
||||
|
||||
assert (result.m_num_intervals < cSyncTrackMaxIntervals);
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
#endif //ANIMTESTBED_SYNCTRACK_H
|
||||
+475
-617
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,45 @@
|
||||
//
|
||||
// Created by martin on 11.04.25.
|
||||
//
|
||||
|
||||
#include "3rdparty/json/json.hpp"
|
||||
#include "AnimGraph/AnimLibrary.h"
|
||||
#include "TestAnimData.h"
|
||||
#include "catch.hpp"
|
||||
|
||||
using namespace nlohmann;
|
||||
|
||||
TEST_CASE("Serialize AnimLibrary", "[AnimLibrary]") {
|
||||
AnimLibrary library;
|
||||
|
||||
TestAnimData::SingleBoneSkeleton single_bone_testdata;
|
||||
|
||||
REQUIRE(library.AddAnimationFile(
|
||||
"translation_x",
|
||||
single_bone_testdata.animation_translate_x_resource.m_filename));
|
||||
REQUIRE(library.AddAnimationFile(
|
||||
"translation_y",
|
||||
single_bone_testdata.animation_translate_y_resource.m_filename));
|
||||
|
||||
// We're not actually doing anything with the animations, however loading them here ensures that
|
||||
// when running valgrind a memory leak is detected.
|
||||
library.LoadAnimations();
|
||||
|
||||
// serialize
|
||||
json library_data;
|
||||
library_data = library;
|
||||
|
||||
// deserialize
|
||||
AnimLibrary library_deserialized;
|
||||
library_deserialized = library_data;
|
||||
|
||||
CHECK(library_deserialized.mAnimations.size() == library.mAnimations.size());
|
||||
for (AnimLibrary::AnimationResourceMap::const_iterator iter =
|
||||
library.mAnimations.cbegin();
|
||||
iter != library.mAnimations.cend();
|
||||
++iter) {
|
||||
CHECK(
|
||||
library_deserialized.mAnimations.find(iter->first)
|
||||
!= library_deserialized.mAnimations.end());
|
||||
}
|
||||
}
|
||||
+169
-61
@@ -2,25 +2,31 @@
|
||||
// Created by martin on 04.02.22.
|
||||
//
|
||||
|
||||
#include "AnimGraph/AnimGraph.h"
|
||||
#include "AnimGraph/AnimGraphBlendTreeResource.h"
|
||||
#include "AnimGraph/AnimGraphEditor.h"
|
||||
#include "AnimGraph/AnimGraphBlendTree.h"
|
||||
#include "AnimGraph/AnimGraphResource.h"
|
||||
#include "catch.hpp"
|
||||
#include "ozz/animation/offline/animation_builder.h"
|
||||
#include "ozz/animation/offline/raw_animation.h"
|
||||
#include "ozz/animation/offline/raw_skeleton.h"
|
||||
#include "ozz/animation/offline/skeleton_builder.h"
|
||||
#include "ozz/animation/runtime/animation.h"
|
||||
#include "ozz/animation/runtime/sampling_job.h"
|
||||
#include "ozz/animation/runtime/skeleton.h"
|
||||
#include "ozz/base/io/archive.h"
|
||||
#include "ozz/base/io/stream.h"
|
||||
#include "ozz/base/log.h"
|
||||
#include "ozz/base/maths/soa_transform.h"
|
||||
|
||||
struct SimpleAnimFixture {
|
||||
ozz::unique_ptr<ozz::animation::Skeleton> skeleton = nullptr;
|
||||
|
||||
ozz::animation::offline::RawAnimation raw_animation_translation_x;
|
||||
ozz::unique_ptr<ozz::animation::Animation> animation_translate_x = nullptr;
|
||||
SyncTrack animation_translate_x_sync_track = {};
|
||||
|
||||
ozz::animation::offline::RawAnimation raw_animation_translation_y;
|
||||
ozz::unique_ptr<ozz::animation::Animation> animation_translate_y = nullptr;
|
||||
SyncTrack animation_translate_y_sync_track = {};
|
||||
|
||||
ozz::vector<ozz::math::SoaTransform> animation_output;
|
||||
ozz::animation::SamplingJob::Context sampling_context;
|
||||
|
||||
@@ -63,7 +69,7 @@ struct SimpleAnimFixture {
|
||||
bone0_translations.push_back(translation_key);
|
||||
|
||||
translation_key.time = 1.f;
|
||||
translation_key.value = ozz::math::Float3(1.f, 0.f, 9.f);
|
||||
translation_key.value = ozz::math::Float3(1.f, 0.f, 0.f);
|
||||
bone0_translations.push_back(translation_key);
|
||||
|
||||
bone0_track.translations = bone0_translations;
|
||||
@@ -120,99 +126,201 @@ TEST_CASE_METHOD(
|
||||
sampled_translation.z[0] == Approx(translation_key.value.z).margin(0.01));
|
||||
}
|
||||
|
||||
TEST_CASE("AnimDataPlacementNew", "[AnimGraphEval]") {
|
||||
int anim_data_size = sizeof(AnimData);
|
||||
char* buf = new char[anim_data_size];
|
||||
TEST_CASE("PosePlacementNew", "[AnimGraphEval]") {
|
||||
int pose_size = sizeof(Pose);
|
||||
char* buf = new char[pose_size];
|
||||
|
||||
AnimData* anim_data_newed = new AnimData;
|
||||
anim_data_newed->m_local_matrices.resize(2);
|
||||
delete anim_data_newed;
|
||||
Pose* pose_newed = new Pose;
|
||||
pose_newed->m_local_matrices.resize(2);
|
||||
delete pose_newed;
|
||||
|
||||
AnimData* anim_data_ptr = new (buf) AnimData;
|
||||
anim_data_ptr->m_local_matrices.resize(4);
|
||||
anim_data_ptr->m_local_matrices.resize(0);
|
||||
anim_data_ptr->m_local_matrices.vector::~vector();
|
||||
Pose* pose_ptr = new (buf) Pose;
|
||||
pose_ptr->m_local_matrices.resize(4);
|
||||
pose_ptr->m_local_matrices.resize(0);
|
||||
pose_ptr->m_local_matrices.vector::~vector();
|
||||
|
||||
delete[] buf;
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(
|
||||
SimpleAnimFixture,
|
||||
"AnimGraphSimpleEval",
|
||||
"[AnimGraphEvalTests]") {
|
||||
AnimGraphBlendTreeResource graph_resource;
|
||||
BlendTreeResource* blend_tree_resource =
|
||||
dynamic_cast<BlendTreeResource*>(AnimNodeResourceFactory("BlendTree"));
|
||||
|
||||
// Add nodes
|
||||
size_t trans_x_node_index =
|
||||
graph_resource.addNode(AnimNodeResourceFactory("AnimSampler"));
|
||||
blend_tree_resource->AddNode(AnimNodeResourceFactory("AnimSampler"));
|
||||
size_t trans_y_node_index =
|
||||
graph_resource.addNode(AnimNodeResourceFactory("AnimSampler"));
|
||||
blend_tree_resource->AddNode(AnimNodeResourceFactory("AnimSampler"));
|
||||
size_t blend_node_index =
|
||||
graph_resource.addNode(AnimNodeResourceFactory("Blend2"));
|
||||
blend_tree_resource->AddNode(AnimNodeResourceFactory("Blend2"));
|
||||
|
||||
// Setup nodes
|
||||
AnimNodeResource& trans_x_node = graph_resource.m_nodes[trans_x_node_index];
|
||||
trans_x_node.m_socket_accessor->SetPropertyValue("Filename", std::string("trans_x"));
|
||||
trans_x_node.m_name = "trans_x";
|
||||
AnimNodeResource* trans_x_node =
|
||||
blend_tree_resource->GetNode(trans_x_node_index);
|
||||
trans_x_node->m_virtual_socket_accessor->SetPropertyValue(
|
||||
"Filename",
|
||||
std::string("trans_x"));
|
||||
trans_x_node->m_name = "trans_x";
|
||||
|
||||
AnimNodeResource& trans_y_node = graph_resource.m_nodes[trans_y_node_index];
|
||||
trans_y_node.m_socket_accessor->SetPropertyValue("Filename", std::string("trans_y"));
|
||||
trans_y_node.m_name = "trans_y";
|
||||
AnimNodeResource* trans_y_node =
|
||||
blend_tree_resource->GetNode(trans_y_node_index);
|
||||
trans_y_node->m_virtual_socket_accessor->SetPropertyValue(
|
||||
"Filename",
|
||||
std::string("trans_y"));
|
||||
trans_y_node->m_name = "trans_y";
|
||||
|
||||
AnimNodeResource& blend_node = graph_resource.m_nodes[blend_node_index];
|
||||
blend_node.m_name = "BlendWalkRun";
|
||||
AnimNodeResource* blend_node = blend_tree_resource->GetNode(blend_node_index);
|
||||
blend_node->m_name = "BlendWalkRun";
|
||||
|
||||
// Setup graph outputs and inputs
|
||||
AnimNodeResource& graph_output_node = graph_resource.getGraphOutputNode();
|
||||
graph_output_node.m_socket_accessor->RegisterInput<AnimData>("GraphOutput", nullptr);
|
||||
|
||||
AnimNodeResource& graph_input_node =
|
||||
graph_resource.getGraphInputNode();
|
||||
graph_input_node.m_socket_accessor->RegisterOutput<float>(
|
||||
"GraphFloatInput",
|
||||
nullptr);
|
||||
blend_tree_resource->RegisterBlendTreeInputSocket<float>("GraphFloatInput");
|
||||
|
||||
// Wire up nodes
|
||||
graph_resource.connectSockets(trans_x_node, "Output", blend_node, "Input0");
|
||||
graph_resource.connectSockets(trans_y_node, "Output", blend_node, "Input1");
|
||||
graph_resource.connectSockets(
|
||||
CHECK(blend_tree_resource
|
||||
->ConnectSockets(trans_x_node, "Output", blend_node, "Input0"));
|
||||
CHECK(blend_tree_resource
|
||||
->ConnectSockets(trans_y_node, "Output", blend_node, "Input1"));
|
||||
|
||||
CHECK(blend_tree_resource->ConnectSockets(
|
||||
blend_node,
|
||||
"Output",
|
||||
graph_resource.getGraphOutputNode(),
|
||||
"GraphOutput");
|
||||
REQUIRE(graph_resource.connectSockets(graph_input_node, "GraphFloatInput", blend_node, "Weight"));
|
||||
blend_tree_resource->GetGraphOutputNode(),
|
||||
"Output"));
|
||||
|
||||
CHECK(blend_tree_resource->ConnectSockets(
|
||||
blend_tree_resource->GetGraphInputNode(),
|
||||
"GraphFloatInput",
|
||||
blend_node,
|
||||
"Weight"));
|
||||
|
||||
// Prepare animation maps
|
||||
AnimGraphContext graph_context;
|
||||
graph_context.m_skeleton = skeleton.get();
|
||||
graph_context.m_animation_map["trans_x"] = animation_translate_x.get();
|
||||
graph_context.m_animation_map["trans_y"] = animation_translate_y.get();
|
||||
graph_context.m_animation_map["trans_x"] = {
|
||||
"trans_x",
|
||||
"",
|
||||
animation_translate_x.get(),
|
||||
animation_translate_x_sync_track};
|
||||
graph_context.m_animation_map["trans_y"] = {
|
||||
"trans_y",
|
||||
"",
|
||||
animation_translate_y.get(),
|
||||
animation_translate_y_sync_track};
|
||||
|
||||
// Instantiate graph
|
||||
AnimGraph graph;
|
||||
graph_resource.createInstance(graph);
|
||||
graph.init(graph_context);
|
||||
AnimGraphBlendTree blend_tree;
|
||||
blend_tree_resource->CreateBlendTreeInstance(blend_tree);
|
||||
|
||||
blend_tree.Init(graph_context);
|
||||
|
||||
// Get runtime graph inputs and outputs
|
||||
float graph_float_input = 0.f;
|
||||
graph.SetInput("GraphFloatInput", &graph_float_input);
|
||||
blend_tree.SetInput("GraphFloatInput", &graph_float_input);
|
||||
CHECK(blend_tree.GetGraphInputs().size() == 1);
|
||||
CHECK(
|
||||
*blend_tree.GetGraphInputs()[0].m_reference.ptr_ptr
|
||||
== &graph_float_input);
|
||||
|
||||
AnimData graph_anim_output;
|
||||
Pose graph_anim_output;
|
||||
graph_anim_output.m_local_matrices.resize(skeleton->num_joints());
|
||||
graph.SetOutput("GraphOutput", &graph_anim_output);
|
||||
blend_tree.SetOutput("Output", &graph_anim_output);
|
||||
|
||||
// Evaluate graph
|
||||
graph_float_input = 0.1f;
|
||||
CHECK(blend_tree.GetGraphOutputs().size() == 1);
|
||||
CHECK(
|
||||
*blend_tree.GetGraphOutputs()[0].m_reference.ptr_ptr
|
||||
== &graph_anim_output);
|
||||
|
||||
graph.markActiveNodes();
|
||||
CHECK(graph.m_nodes[trans_x_node_index]->m_state == AnimNodeEvalState::Activated);
|
||||
CHECK(graph.m_nodes[trans_y_node_index]->m_state == AnimNodeEvalState::Activated);
|
||||
CHECK(graph.m_nodes[blend_node_index]->m_state == AnimNodeEvalState::Activated);
|
||||
WHEN("Blend Weight == 0.") {
|
||||
// Evaluate graph
|
||||
graph_float_input = 0.f;
|
||||
|
||||
graph.updateTime(0.5f);
|
||||
graph.evaluate(graph_context);
|
||||
blend_tree.StartUpdateTick();
|
||||
blend_tree.MarkActiveInputs({});
|
||||
|
||||
CHECK(graph_anim_output.m_local_matrices[0].translation.x[0] == Approx(0.5).margin(0.1));
|
||||
CHECK(graph_anim_output.m_local_matrices[0].translation.y[0] == Approx(0.05).margin(0.01));
|
||||
THEN("Only Blend2 and first input of Blend2 node is active.") {
|
||||
CHECK(
|
||||
blend_tree.m_nodes[trans_x_node_index]->m_state
|
||||
== AnimNodeEvalState::Activated);
|
||||
CHECK(
|
||||
blend_tree.m_nodes[trans_y_node_index]->m_state
|
||||
== AnimNodeEvalState::Deactivated);
|
||||
CHECK(
|
||||
blend_tree.m_nodes[blend_node_index]->m_state
|
||||
== AnimNodeEvalState::Activated);
|
||||
}
|
||||
|
||||
blend_tree.UpdateTime(0.0, 0.5f);
|
||||
blend_tree.Evaluate(graph_context);
|
||||
|
||||
CHECK(
|
||||
graph_anim_output.m_local_matrices[0].translation.x[0]
|
||||
== Approx(0.5).margin(0.01));
|
||||
CHECK(
|
||||
graph_anim_output.m_local_matrices[0].translation.y[0]
|
||||
== Approx(0.0).margin(0.01));
|
||||
}
|
||||
|
||||
WHEN("Blend Weight 0.1") {
|
||||
// Evaluate graph
|
||||
graph_float_input = 0.1f;
|
||||
|
||||
blend_tree.StartUpdateTick();
|
||||
blend_tree.MarkActiveInputs({});
|
||||
|
||||
THEN("All nodes are active.") {
|
||||
CHECK(
|
||||
blend_tree.m_nodes[trans_x_node_index]->m_state
|
||||
== AnimNodeEvalState::Activated);
|
||||
CHECK(
|
||||
blend_tree.m_nodes[trans_y_node_index]->m_state
|
||||
== AnimNodeEvalState::Activated);
|
||||
CHECK(
|
||||
blend_tree.m_nodes[blend_node_index]->m_state
|
||||
== AnimNodeEvalState::Activated);
|
||||
}
|
||||
|
||||
blend_tree.UpdateTime(0.0, 0.5f);
|
||||
blend_tree.Evaluate(graph_context);
|
||||
|
||||
CHECK(
|
||||
graph_anim_output.m_local_matrices[0].translation.x[0]
|
||||
== Approx(0.45).margin(0.01));
|
||||
CHECK(
|
||||
graph_anim_output.m_local_matrices[0].translation.y[0]
|
||||
== Approx(0.05).margin(0.01));
|
||||
}
|
||||
|
||||
WHEN("Blend Weight 1.") {
|
||||
// Evaluate graph
|
||||
graph_float_input = 1.f;
|
||||
|
||||
blend_tree.StartUpdateTick();
|
||||
blend_tree.MarkActiveInputs({});
|
||||
|
||||
THEN("Only Blend2 and second input of Blend2 are active.") {
|
||||
CHECK(
|
||||
blend_tree.m_nodes[trans_x_node_index]->m_state
|
||||
== AnimNodeEvalState::Deactivated);
|
||||
CHECK(
|
||||
blend_tree.m_nodes[trans_y_node_index]->m_state
|
||||
== AnimNodeEvalState::Activated);
|
||||
CHECK(
|
||||
blend_tree.m_nodes[blend_node_index]->m_state
|
||||
== AnimNodeEvalState::Activated);
|
||||
}
|
||||
|
||||
blend_tree.UpdateTime(0.0, 0.5f);
|
||||
blend_tree.Evaluate(graph_context);
|
||||
|
||||
CHECK(
|
||||
graph_anim_output.m_local_matrices[0].translation.x[0]
|
||||
== Approx(0.).margin(0.01));
|
||||
CHECK(
|
||||
graph_anim_output.m_local_matrices[0].translation.y[0]
|
||||
== Approx(0.5).margin(0.01));
|
||||
}
|
||||
|
||||
delete blend_tree_resource;
|
||||
}
|
||||
+181
-229
@@ -11,22 +11,26 @@
|
||||
#include "ozz/base/io/stream.h"
|
||||
#include "ozz/base/log.h"
|
||||
|
||||
class SimpleAnimSamplerGraphResource {
|
||||
protected:
|
||||
AnimGraphResource graph_resource;
|
||||
class BlendTreeResourceFixture {
|
||||
public:
|
||||
BlendTreeResourceFixture() {
|
||||
blend_tree_resource =
|
||||
dynamic_cast<BlendTreeResource*>(AnimNodeResourceFactory("BlendTree"));
|
||||
}
|
||||
|
||||
virtual ~BlendTreeResourceFixture() { delete blend_tree_resource; }
|
||||
|
||||
BlendTreeResource* blend_tree_resource = nullptr;
|
||||
};
|
||||
|
||||
class SimpleAnimSamplerBlendTreeResourceFixture
|
||||
: public BlendTreeResourceFixture {
|
||||
protected:
|
||||
size_t walk_node_index = -1;
|
||||
AnimNodeResource* walk_node = nullptr;
|
||||
|
||||
public:
|
||||
SimpleAnimSamplerGraphResource()
|
||||
: graph_resource(AnimGraphType::GraphTypeBlendTree) {
|
||||
graph_resource.m_name = "AnimSamplerBlendTree";
|
||||
graph_resource.m_node_type_name = "BlendTree";
|
||||
graph_resource.m_graph_type_name = "BlendTree";
|
||||
|
||||
blend_tree_resource = &graph_resource.m_blend_tree_resource;
|
||||
|
||||
SimpleAnimSamplerBlendTreeResourceFixture() {
|
||||
// Prepare graph inputs and outputs
|
||||
walk_node_index =
|
||||
blend_tree_resource->AddNode(AnimNodeResourceFactory("AnimSampler"));
|
||||
@@ -37,7 +41,7 @@ class SimpleAnimSamplerGraphResource {
|
||||
std::string("media/Walking-loop.ozz"));
|
||||
|
||||
AnimNodeResource* graph_node = blend_tree_resource->GetGraphOutputNode();
|
||||
graph_node->m_virtual_socket_accessor->RegisterInput<AnimData>(
|
||||
graph_node->m_virtual_socket_accessor->RegisterInput<Pose>(
|
||||
"GraphOutput",
|
||||
nullptr);
|
||||
|
||||
@@ -49,10 +53,8 @@ class SimpleAnimSamplerGraphResource {
|
||||
}
|
||||
};
|
||||
|
||||
class Blend2GraphResource {
|
||||
class Blend2BlendTreeResource : public BlendTreeResourceFixture {
|
||||
protected:
|
||||
AnimGraphResourcePtr graph_resource;
|
||||
BlendTreeResource* blend_tree_resource = nullptr;
|
||||
size_t walk_node_index = -1;
|
||||
size_t run_node_index = -1;
|
||||
size_t blend_node_index = -1;
|
||||
@@ -61,13 +63,8 @@ class Blend2GraphResource {
|
||||
AnimNodeResource* blend_node = nullptr;
|
||||
|
||||
public:
|
||||
Blend2GraphResource()
|
||||
: graph_resource(
|
||||
dynamic_cast<AnimGraphResource*>(
|
||||
AnimNodeResourceFactory("BlendTree"))) {
|
||||
graph_resource->m_name = "WalkRunBlendGraph";
|
||||
|
||||
blend_tree_resource = &graph_resource->m_blend_tree_resource;
|
||||
Blend2BlendTreeResource() {
|
||||
blend_tree_resource->m_name = "WalkRunBlendGraph";
|
||||
|
||||
// Prepare graph inputs and outputs
|
||||
walk_node_index =
|
||||
@@ -129,12 +126,8 @@ class Blend2GraphResource {
|
||||
// | |
|
||||
// +----------------------------------------+
|
||||
//
|
||||
class EmbeddedBlendTreeGraphResource {
|
||||
class EmbeddedBlendTreeGraphResource : public BlendTreeResourceFixture {
|
||||
protected:
|
||||
AnimGraphResource parent_graph_resource;
|
||||
BlendTreeResource* parent_blend_tree_resource = nullptr;
|
||||
|
||||
AnimGraphResource* embedded_graph = nullptr;
|
||||
BlendTreeResource* embedded_blend_tree_resource = nullptr;
|
||||
|
||||
size_t walk_node_index = -1;
|
||||
@@ -143,19 +136,14 @@ class EmbeddedBlendTreeGraphResource {
|
||||
size_t embedded_speed_scale_index = -1;
|
||||
|
||||
public:
|
||||
EmbeddedBlendTreeGraphResource()
|
||||
: parent_graph_resource(AnimGraphType::GraphTypeBlendTree) {
|
||||
parent_graph_resource.m_name = "ParentBlendTree";
|
||||
parent_graph_resource.m_graph_type_name = "BlendTree";
|
||||
parent_graph_resource.m_node_type_name = "BlendTree";
|
||||
|
||||
parent_blend_tree_resource = &parent_graph_resource.m_blend_tree_resource;
|
||||
EmbeddedBlendTreeGraphResource() {
|
||||
blend_tree_resource->m_name = "ParentBlendTree";
|
||||
|
||||
// Parent AnimSampler
|
||||
walk_node_index = parent_blend_tree_resource->AddNode(
|
||||
AnimNodeResourceFactory("AnimSampler"));
|
||||
walk_node_index =
|
||||
blend_tree_resource->AddNode(AnimNodeResourceFactory("AnimSampler"));
|
||||
|
||||
walk_node_resource = parent_blend_tree_resource->GetNode(walk_node_index);
|
||||
walk_node_resource = blend_tree_resource->GetNode(walk_node_index);
|
||||
walk_node_resource->m_name = "WalkAnim";
|
||||
walk_node_resource->m_virtual_socket_accessor->SetPropertyValue(
|
||||
"Filename",
|
||||
@@ -164,26 +152,23 @@ class EmbeddedBlendTreeGraphResource {
|
||||
//
|
||||
// Embedded Tree
|
||||
//
|
||||
embedded_blend_tree_node_index = parent_blend_tree_resource->AddNode(
|
||||
AnimNodeResourceFactory("BlendTree"));
|
||||
embedded_graph = dynamic_cast<AnimGraphResource*>(
|
||||
parent_blend_tree_resource->GetNode(embedded_blend_tree_node_index));
|
||||
embedded_graph->m_name = "EmbeddedBlendTree";
|
||||
embedded_graph->m_node_type_name = "BlendTree";
|
||||
embedded_graph->m_graph_type_name = "BlendTree";
|
||||
embedded_blend_tree_resource = &embedded_graph->m_blend_tree_resource;
|
||||
embedded_blend_tree_node_index =
|
||||
blend_tree_resource->AddNode(AnimNodeResourceFactory("BlendTree"));
|
||||
embedded_blend_tree_resource = dynamic_cast<BlendTreeResource*>(
|
||||
blend_tree_resource->GetNode(embedded_blend_tree_node_index));
|
||||
embedded_blend_tree_resource->m_name = "EmbeddedBlendTree";
|
||||
|
||||
// Embedded: outputs
|
||||
AnimNodeResource* embedded_outputs =
|
||||
embedded_blend_tree_resource->GetGraphOutputNode();
|
||||
embedded_outputs->m_virtual_socket_accessor->RegisterInput<AnimData>(
|
||||
embedded_outputs->m_virtual_socket_accessor->RegisterInput<Pose>(
|
||||
"AnimOutput",
|
||||
nullptr);
|
||||
|
||||
// Embedded: inputs
|
||||
AnimNodeResource* embedded_inputs =
|
||||
embedded_blend_tree_resource->GetGraphInputNode();
|
||||
embedded_inputs->m_virtual_socket_accessor->RegisterOutput<AnimData>(
|
||||
embedded_inputs->m_virtual_socket_accessor->RegisterOutput<Pose>(
|
||||
"AnimInput",
|
||||
nullptr);
|
||||
|
||||
@@ -210,15 +195,15 @@ class EmbeddedBlendTreeGraphResource {
|
||||
|
||||
// Parent: setup connections
|
||||
const AnimNodeResource* parent_blend_tree_outputs =
|
||||
parent_blend_tree_resource->GetGraphOutputNode();
|
||||
blend_tree_resource->GetGraphOutputNode();
|
||||
|
||||
REQUIRE(parent_blend_tree_resource->ConnectSockets(
|
||||
REQUIRE(blend_tree_resource->ConnectSockets(
|
||||
walk_node_resource,
|
||||
"Output",
|
||||
embedded_graph,
|
||||
embedded_blend_tree_resource,
|
||||
"AnimInput"));
|
||||
REQUIRE(parent_blend_tree_resource->ConnectSockets(
|
||||
embedded_graph,
|
||||
REQUIRE(blend_tree_resource->ConnectSockets(
|
||||
embedded_blend_tree_resource,
|
||||
"AnimOutput",
|
||||
parent_blend_tree_outputs,
|
||||
"Output"));
|
||||
@@ -238,12 +223,8 @@ class EmbeddedBlendTreeGraphResource {
|
||||
// | |
|
||||
// +----------------------------------------+
|
||||
//
|
||||
class EmbeddedTreeBlend2GraphResource {
|
||||
class EmbeddedTreeBlend2GraphResource : public BlendTreeResourceFixture {
|
||||
protected:
|
||||
AnimGraphResource parent_graph_resource;
|
||||
BlendTreeResource* parent_blend_tree_resource = nullptr;
|
||||
|
||||
AnimGraphResource* embedded_graph = nullptr;
|
||||
BlendTreeResource* embedded_blend_tree_resource = nullptr;
|
||||
|
||||
size_t walk_node_index = -1;
|
||||
@@ -256,26 +237,21 @@ class EmbeddedTreeBlend2GraphResource {
|
||||
AnimNodeResource* embedded_run_node_resource = nullptr;
|
||||
|
||||
public:
|
||||
EmbeddedTreeBlend2GraphResource()
|
||||
: parent_graph_resource(AnimGraphType::GraphTypeBlendTree) {
|
||||
parent_graph_resource.m_name = "ParentBlendTree";
|
||||
parent_graph_resource.m_graph_type_name = "BlendTree";
|
||||
parent_graph_resource.m_node_type_name = "BlendTree";
|
||||
|
||||
parent_blend_tree_resource = &parent_graph_resource.m_blend_tree_resource;
|
||||
EmbeddedTreeBlend2GraphResource() {
|
||||
blend_tree_resource->m_name = "ParentBlendTree";
|
||||
|
||||
// Setup parent inputs
|
||||
AnimNodeResource* parent_blend_tree_inputs =
|
||||
parent_blend_tree_resource->GetGraphInputNode();
|
||||
blend_tree_resource->GetGraphInputNode();
|
||||
parent_blend_tree_inputs->m_virtual_socket_accessor->RegisterOutput<float>(
|
||||
"EmbeddedBlend2Weight",
|
||||
nullptr);
|
||||
|
||||
// Parent AnimSampler
|
||||
walk_node_index = parent_blend_tree_resource->AddNode(
|
||||
AnimNodeResourceFactory("AnimSampler"));
|
||||
walk_node_index =
|
||||
blend_tree_resource->AddNode(AnimNodeResourceFactory("AnimSampler"));
|
||||
|
||||
walk_node_resource = parent_blend_tree_resource->GetNode(walk_node_index);
|
||||
walk_node_resource = blend_tree_resource->GetNode(walk_node_index);
|
||||
walk_node_resource->m_name = "WalkAnim";
|
||||
walk_node_resource->m_virtual_socket_accessor->SetPropertyValue(
|
||||
"Filename",
|
||||
@@ -284,18 +260,17 @@ class EmbeddedTreeBlend2GraphResource {
|
||||
//
|
||||
// Embedded Tree
|
||||
//
|
||||
embedded_blend_tree_node_index = parent_blend_tree_resource->AddNode(
|
||||
AnimNodeResourceFactory("BlendTree"));
|
||||
embedded_graph = dynamic_cast<AnimGraphResource*>(
|
||||
parent_blend_tree_resource->GetNode(embedded_blend_tree_node_index));
|
||||
embedded_graph->m_name = "EmbeddedTreeBlend2GraphResource";
|
||||
embedded_graph->m_node_type_name = "BlendTree";
|
||||
embedded_graph->m_graph_type_name = "BlendTree";
|
||||
embedded_blend_tree_resource = &embedded_graph->m_blend_tree_resource;
|
||||
embedded_blend_tree_node_index =
|
||||
blend_tree_resource->AddNode(AnimNodeResourceFactory("BlendTree"));
|
||||
embedded_blend_tree_resource = dynamic_cast<BlendTreeResource*>(
|
||||
blend_tree_resource->GetNode(embedded_blend_tree_node_index));
|
||||
embedded_blend_tree_resource->m_name = "EmbeddedTreeBlend2GraphResource";
|
||||
|
||||
// Embedded: inputs
|
||||
embedded_graph->RegisterBlendTreeInputSocket<AnimData>("AnimInput");
|
||||
embedded_graph->RegisterBlendTreeInputSocket<float>("BlendWeight");
|
||||
embedded_blend_tree_resource->RegisterBlendTreeInputSocket<Pose>(
|
||||
"AnimInput");
|
||||
embedded_blend_tree_resource->RegisterBlendTreeInputSocket<float>(
|
||||
"BlendWeight");
|
||||
|
||||
// Embedded nodes
|
||||
embedded_blend2_node_index = embedded_blend_tree_resource->AddNode(
|
||||
@@ -342,22 +317,22 @@ class EmbeddedTreeBlend2GraphResource {
|
||||
|
||||
// Parent: setup connections
|
||||
AnimNodeResource* parent_blend_tree_outputs =
|
||||
parent_blend_tree_resource->GetGraphOutputNode();
|
||||
blend_tree_resource->GetGraphOutputNode();
|
||||
|
||||
REQUIRE(parent_blend_tree_resource->ConnectSockets(
|
||||
REQUIRE(blend_tree_resource->ConnectSockets(
|
||||
walk_node_resource,
|
||||
"Output",
|
||||
embedded_graph,
|
||||
embedded_blend_tree_resource,
|
||||
"AnimInput"));
|
||||
REQUIRE(parent_blend_tree_resource->ConnectSockets(
|
||||
embedded_graph,
|
||||
REQUIRE(blend_tree_resource->ConnectSockets(
|
||||
embedded_blend_tree_resource,
|
||||
AnimGraphResource::DefaultAnimOutput,
|
||||
parent_blend_tree_outputs,
|
||||
AnimGraphResource::DefaultAnimOutput));
|
||||
REQUIRE(parent_blend_tree_resource->ConnectSockets(
|
||||
REQUIRE(blend_tree_resource->ConnectSockets(
|
||||
parent_blend_tree_inputs,
|
||||
"EmbeddedBlend2Weight",
|
||||
embedded_graph,
|
||||
embedded_blend_tree_resource,
|
||||
"BlendWeight"));
|
||||
}
|
||||
};
|
||||
@@ -386,6 +361,14 @@ bool load_skeleton(ozz::animation::Skeleton& skeleton, const char* filename) {
|
||||
void CheckBlendTreeResourcesEqual(
|
||||
const BlendTreeResource* blend_tree_resource_reference,
|
||||
const BlendTreeResource* blend_tree_resource_rhs) {
|
||||
REQUIRE(
|
||||
blend_tree_resource_reference->m_node_type_name
|
||||
== blend_tree_resource_rhs->m_node_type_name);
|
||||
REQUIRE(blend_tree_resource_reference->m_node_type_name == "BlendTree");
|
||||
|
||||
REQUIRE(
|
||||
blend_tree_resource_reference->m_name == blend_tree_resource_rhs->m_name);
|
||||
|
||||
REQUIRE(
|
||||
blend_tree_resource_reference->GetNumNodes()
|
||||
== blend_tree_resource_rhs->GetNumNodes());
|
||||
@@ -420,27 +403,6 @@ void CheckBlendTreeResourcesEqual(
|
||||
}
|
||||
}
|
||||
|
||||
void CheckAnimGraphResourceEqual(
|
||||
const AnimGraphResource& graph_resource_reference,
|
||||
const AnimGraphResource& graph_resource_rhs) {
|
||||
REQUIRE(
|
||||
graph_resource_reference.m_graph_type_name
|
||||
== graph_resource_rhs.m_graph_type_name);
|
||||
REQUIRE(graph_resource_reference.m_name == graph_resource_rhs.m_name);
|
||||
|
||||
REQUIRE(graph_resource_reference.m_graph_type_name == "BlendTree");
|
||||
|
||||
const BlendTreeResource* blend_tree_resource_reference =
|
||||
&graph_resource_reference.m_blend_tree_resource;
|
||||
|
||||
const BlendTreeResource* blend_tree_resource_rhs =
|
||||
&graph_resource_rhs.m_blend_tree_resource;
|
||||
|
||||
CheckBlendTreeResourcesEqual(
|
||||
blend_tree_resource_reference,
|
||||
blend_tree_resource_rhs);
|
||||
}
|
||||
|
||||
TEST_CASE("InputAttributeConversion", "[AnimGraphResource]") {
|
||||
int node_id = 3321;
|
||||
int input_index = 221;
|
||||
@@ -461,24 +423,26 @@ TEST_CASE("InputAttributeConversion", "[AnimGraphResource]") {
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(
|
||||
SimpleAnimSamplerGraphResource,
|
||||
SimpleAnimSamplerBlendTreeResourceFixture,
|
||||
"SimpleAnimSamplerGraphResource saving and loading results in same "
|
||||
"resource",
|
||||
"[SimpleAnimSamplerGraphResource]") {
|
||||
graph_resource.SaveToFile("TestGraphAnimSamplerBlendTree.json");
|
||||
blend_tree_resource->SaveToFile("TestGraphAnimSamplerBlendTree.json");
|
||||
|
||||
std::unique_ptr<AnimGraphResource> graph_resource_loaded(
|
||||
AnimGraphResource::CreateFromFile("TestGraphAnimSamplerBlendTree.json"));
|
||||
std::unique_ptr<BlendTreeResource> blend_tree_resource_loaded(
|
||||
BlendTreeResource::CreateFromFile("TestGraphAnimSamplerBlendTree.json"));
|
||||
|
||||
CheckAnimGraphResourceEqual(graph_resource, *graph_resource_loaded);
|
||||
CheckBlendTreeResourcesEqual(
|
||||
blend_tree_resource,
|
||||
blend_tree_resource_loaded.get());
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(
|
||||
SimpleAnimSamplerGraphResource,
|
||||
SimpleAnimSamplerBlendTreeResourceFixture,
|
||||
"SimpleAnimSamplerGraphResource emulated evaluation",
|
||||
"[SimpleAnimSamplerGraphResource]") {
|
||||
AnimGraphBlendTree anim_graph_blend_tree;
|
||||
graph_resource.CreateBlendTreeInstance(anim_graph_blend_tree);
|
||||
blend_tree_resource->CreateBlendTreeInstance(anim_graph_blend_tree);
|
||||
AnimGraphContext graph_context;
|
||||
|
||||
ozz::animation::Skeleton skeleton;
|
||||
@@ -518,7 +482,7 @@ TEST_CASE_METHOD(
|
||||
REQUIRE(anim_sampler_walk->m_animation != nullptr);
|
||||
|
||||
// Ensure that outputs are properly propagated.
|
||||
AnimData output;
|
||||
Pose output;
|
||||
output.m_local_matrices.resize(skeleton.num_soa_joints());
|
||||
anim_graph_blend_tree.SetOutput(
|
||||
AnimGraphResource::DefaultAnimOutput,
|
||||
@@ -537,29 +501,25 @@ TEST_CASE_METHOD(
|
||||
// Checks that node const inputs are properly set.
|
||||
//
|
||||
TEST_CASE("AnimSamplerSpeedScaleGraph", "[AnimGraphResource]") {
|
||||
AnimGraphResourcePtr graph_resource(
|
||||
dynamic_cast<AnimGraphResource*>(AnimNodeResourceFactory("BlendTree")));
|
||||
graph_resource->m_name = "AnimSamplerSpeedScaleGraph";
|
||||
graph_resource->m_graph_type_name = "BlendTree";
|
||||
|
||||
BlendTreeResource& blend_tree_resource =
|
||||
graph_resource->m_blend_tree_resource;
|
||||
BlendTreeResource* blend_tree_resource =
|
||||
dynamic_cast<BlendTreeResource*>(AnimNodeResourceFactory("BlendTree"));
|
||||
blend_tree_resource->m_name = "AnimSamplerSpeedScaleBlendTree";
|
||||
|
||||
// Prepare graph inputs and outputs
|
||||
size_t walk_node_index =
|
||||
blend_tree_resource.AddNode(AnimNodeResourceFactory("AnimSampler"));
|
||||
blend_tree_resource->AddNode(AnimNodeResourceFactory("AnimSampler"));
|
||||
|
||||
size_t speed_scale_node_index =
|
||||
blend_tree_resource.AddNode(AnimNodeResourceFactory("SpeedScale"));
|
||||
blend_tree_resource->AddNode(AnimNodeResourceFactory("SpeedScale"));
|
||||
|
||||
AnimNodeResource* walk_node = blend_tree_resource.GetNode(walk_node_index);
|
||||
AnimNodeResource* walk_node = blend_tree_resource->GetNode(walk_node_index);
|
||||
walk_node->m_name = "WalkAnim";
|
||||
walk_node->m_virtual_socket_accessor->SetPropertyValue(
|
||||
"Filename",
|
||||
std::string("media/Walking-loop.ozz"));
|
||||
|
||||
AnimNodeResource* speed_scale_node =
|
||||
blend_tree_resource.GetNode(speed_scale_node_index);
|
||||
blend_tree_resource->GetNode(speed_scale_node_index);
|
||||
speed_scale_node->m_name = "SpeedScale";
|
||||
float speed_scale_value = 1.35f;
|
||||
speed_scale_node->m_virtual_socket_accessor->SetInputValue(
|
||||
@@ -567,25 +527,23 @@ TEST_CASE("AnimSamplerSpeedScaleGraph", "[AnimGraphResource]") {
|
||||
speed_scale_value);
|
||||
|
||||
blend_tree_resource
|
||||
.ConnectSockets(walk_node, "Output", speed_scale_node, "Input");
|
||||
->ConnectSockets(walk_node, "Output", speed_scale_node, "Input");
|
||||
|
||||
blend_tree_resource.ConnectSockets(
|
||||
blend_tree_resource->ConnectSockets(
|
||||
speed_scale_node,
|
||||
"Output",
|
||||
blend_tree_resource.GetGraphOutputNode(),
|
||||
blend_tree_resource->GetGraphOutputNode(),
|
||||
AnimGraphResource::DefaultAnimOutput);
|
||||
|
||||
graph_resource->SaveToFile(
|
||||
"TestGraphAnimSamplerSpeedScaleGraph.animgraph.json");
|
||||
AnimGraphResourcePtr graph_resource_loaded(
|
||||
AnimGraphResource::CreateFromFile(
|
||||
"TestGraphAnimSamplerSpeedScaleGraph.animgraph.json"));
|
||||
constexpr char filename[] =
|
||||
"TestGraphAnimSamplerSpeedScaleGraph.animgraph.json";
|
||||
|
||||
BlendTreeResource& blend_tree_resource_loaded =
|
||||
graph_resource_loaded->m_blend_tree_resource;
|
||||
REQUIRE(blend_tree_resource->SaveToFile(filename));
|
||||
BlendTreeResource* blend_tree_resource_loaded =
|
||||
BlendTreeResource::CreateFromFile(filename);
|
||||
|
||||
Socket* speed_scale_resource_loaded_input =
|
||||
blend_tree_resource_loaded.GetNode(speed_scale_node_index)
|
||||
blend_tree_resource_loaded->GetNode(speed_scale_node_index)
|
||||
->m_virtual_socket_accessor->GetInputSocket("SpeedScale");
|
||||
REQUIRE(speed_scale_resource_loaded_input != nullptr);
|
||||
|
||||
@@ -594,7 +552,7 @@ TEST_CASE("AnimSamplerSpeedScaleGraph", "[AnimGraphResource]") {
|
||||
Catch::Matchers::WithinAbs(speed_scale_value, 0.1));
|
||||
|
||||
AnimGraphBlendTree blend_tree;
|
||||
graph_resource_loaded->CreateBlendTreeInstance(blend_tree);
|
||||
blend_tree_resource_loaded->CreateBlendTreeInstance(blend_tree);
|
||||
|
||||
REQUIRE_THAT(
|
||||
*dynamic_cast<SpeedScaleNode*>(blend_tree.m_nodes[speed_scale_node_index])
|
||||
@@ -603,7 +561,7 @@ TEST_CASE("AnimSamplerSpeedScaleGraph", "[AnimGraphResource]") {
|
||||
|
||||
WHEN("Checking node eval order and node subtrees") {
|
||||
const std::vector<size_t>& eval_order =
|
||||
graph_resource_loaded->m_blend_tree_resource.GetNodeEvalOrder();
|
||||
blend_tree_resource_loaded->GetNodeEvalOrder();
|
||||
|
||||
THEN("Walk node gets evaluated before speed scale node") {
|
||||
CHECK(eval_order.size() == 2);
|
||||
@@ -613,23 +571,26 @@ TEST_CASE("AnimSamplerSpeedScaleGraph", "[AnimGraphResource]") {
|
||||
|
||||
THEN("Subtree of the speed scale node contains only the walk node") {
|
||||
CHECK(
|
||||
graph_resource_loaded->m_blend_tree_resource
|
||||
.m_node_inputs_subtree[speed_scale_node_index]
|
||||
blend_tree_resource_loaded
|
||||
->m_node_inputs_subtree[speed_scale_node_index]
|
||||
.size()
|
||||
== 1);
|
||||
CHECK(
|
||||
graph_resource_loaded->m_blend_tree_resource
|
||||
.m_node_inputs_subtree[speed_scale_node_index][0]
|
||||
blend_tree_resource_loaded
|
||||
->m_node_inputs_subtree[speed_scale_node_index][0]
|
||||
== walk_node_index);
|
||||
}
|
||||
}
|
||||
|
||||
delete blend_tree_resource_loaded;
|
||||
delete blend_tree_resource;
|
||||
}
|
||||
|
||||
//
|
||||
// Checks that connections additions and removals are properly validated.
|
||||
//
|
||||
TEST_CASE_METHOD(
|
||||
Blend2GraphResource,
|
||||
Blend2BlendTreeResource,
|
||||
"Connectivity Tests",
|
||||
"[AnimGraphResource][Blend2GraphResource]") {
|
||||
INFO("Removing Blend2 -> Output Connection")
|
||||
@@ -704,18 +665,16 @@ TEST_CASE("FreeAnimGraphResource", "[Test]") {
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(
|
||||
Blend2GraphResource,
|
||||
Blend2BlendTreeResource,
|
||||
"Blend2GraphResource saving and loading results in same resource",
|
||||
"[Blend2GraphResource]") {
|
||||
graph_resource->SaveToFile("TestGraphBlend2Graph.animgraph.json");
|
||||
|
||||
AnimGraphResourcePtr graph_resource_loaded(
|
||||
AnimGraphResource::CreateFromFile("TestGraphBlend2Graph.animgraph.json"));
|
||||
|
||||
CheckAnimGraphResourceEqual(*graph_resource, *graph_resource_loaded);
|
||||
constexpr char filename[] = "TestGraphBlend2Graph.animgraph.json";
|
||||
|
||||
REQUIRE(blend_tree_resource->SaveToFile(filename));
|
||||
BlendTreeResource* blend_tree_resource_loaded =
|
||||
&graph_resource_loaded->m_blend_tree_resource;
|
||||
BlendTreeResource::CreateFromFile(filename);
|
||||
|
||||
CheckBlendTreeResourcesEqual(blend_tree_resource, blend_tree_resource_loaded);
|
||||
|
||||
// Check that the constant weight of the Blend2 node was properly applied when
|
||||
// loading the resource.
|
||||
@@ -729,14 +688,16 @@ TEST_CASE_METHOD(
|
||||
Catch::Matchers::WithinAbs(
|
||||
blend2_node_descriptor_loaded->GetInputValue<float>("Weight"),
|
||||
0.01));
|
||||
|
||||
delete blend_tree_resource_loaded;
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(
|
||||
Blend2GraphResource,
|
||||
Blend2BlendTreeResource,
|
||||
"Blend2GraphResource graph unsynced evaluation",
|
||||
"[Blend2GraphResource]") {
|
||||
AnimGraphBlendTree blend_tree_graph;
|
||||
graph_resource->CreateBlendTreeInstance(blend_tree_graph);
|
||||
blend_tree_resource->CreateBlendTreeInstance(blend_tree_graph);
|
||||
AnimGraphContext graph_context;
|
||||
|
||||
ozz::animation::Skeleton skeleton;
|
||||
@@ -803,9 +764,8 @@ TEST_CASE_METHOD(
|
||||
CHECK(blend2_instance->i_input0 == anim_sampler_walk->o_output);
|
||||
CHECK(blend2_instance->i_input1 == anim_sampler_run->o_output);
|
||||
|
||||
AnimData* graph_output =
|
||||
static_cast<AnimData*>(blend_tree_graph.GetOutputPtr<AnimData>(
|
||||
AnimGraphResource::DefaultAnimOutput));
|
||||
Pose* graph_output = blend_tree_graph.GetOutputPtr<Pose>(
|
||||
AnimGraphResource::DefaultAnimOutput);
|
||||
|
||||
CHECK(
|
||||
graph_output->m_local_matrices.size()
|
||||
@@ -835,19 +795,16 @@ TEST_CASE_METHOD(
|
||||
//
|
||||
//
|
||||
TEST_CASE("ResourceSaveLoadMathGraphInputs", "[AnimGraphResource]") {
|
||||
AnimGraphResource graph_resource_origin(AnimGraphType::GraphTypeBlendTree);
|
||||
graph_resource_origin.m_name = "TestInputOutputGraph";
|
||||
graph_resource_origin.m_graph_type_name = "BlendTree";
|
||||
|
||||
BlendTreeResource& blend_tree_resource =
|
||||
graph_resource_origin.m_blend_tree_resource;
|
||||
BlendTreeResource* blend_tree_resource =
|
||||
dynamic_cast<BlendTreeResource*>(AnimNodeResourceFactory("BlendTree"));
|
||||
blend_tree_resource->m_name = "TestInputOutputGraph";
|
||||
|
||||
// Prepare graph inputs and outputs
|
||||
size_t float_to_vec3_node_index = blend_tree_resource.AddNode(
|
||||
size_t float_to_vec3_node_index = blend_tree_resource->AddNode(
|
||||
AnimNodeResourceFactory("MathFloatToVec3Node"));
|
||||
|
||||
AnimNodeResource* graph_output_node =
|
||||
blend_tree_resource.GetGraphOutputNode();
|
||||
blend_tree_resource->GetGraphOutputNode();
|
||||
graph_output_node->m_virtual_socket_accessor->RegisterInput<float>(
|
||||
"GraphFloatOutput",
|
||||
nullptr);
|
||||
@@ -856,38 +813,38 @@ TEST_CASE("ResourceSaveLoadMathGraphInputs", "[AnimGraphResource]") {
|
||||
nullptr);
|
||||
|
||||
AnimNodeResource* graph_input_node_resource =
|
||||
blend_tree_resource.GetGraphInputNode();
|
||||
blend_tree_resource->GetGraphInputNode();
|
||||
graph_input_node_resource->m_virtual_socket_accessor->RegisterOutput<float>(
|
||||
"GraphFloatInput",
|
||||
nullptr);
|
||||
|
||||
// Prepare graph inputs and outputs
|
||||
AnimNodeResource* float_to_vec3_node_resource =
|
||||
blend_tree_resource.GetNode(float_to_vec3_node_index);
|
||||
blend_tree_resource->GetNode(float_to_vec3_node_index);
|
||||
|
||||
REQUIRE(blend_tree_resource.ConnectSockets(
|
||||
REQUIRE(blend_tree_resource->ConnectSockets(
|
||||
graph_input_node_resource,
|
||||
"GraphFloatInput",
|
||||
graph_output_node,
|
||||
"GraphFloatOutput"));
|
||||
|
||||
REQUIRE(blend_tree_resource.ConnectSockets(
|
||||
REQUIRE(blend_tree_resource->ConnectSockets(
|
||||
graph_input_node_resource,
|
||||
"GraphFloatInput",
|
||||
float_to_vec3_node_resource,
|
||||
"Input0"));
|
||||
REQUIRE(blend_tree_resource.ConnectSockets(
|
||||
REQUIRE(blend_tree_resource->ConnectSockets(
|
||||
graph_input_node_resource,
|
||||
"GraphFloatInput",
|
||||
float_to_vec3_node_resource,
|
||||
"Input1"));
|
||||
REQUIRE(blend_tree_resource.ConnectSockets(
|
||||
REQUIRE(blend_tree_resource->ConnectSockets(
|
||||
graph_input_node_resource,
|
||||
"GraphFloatInput",
|
||||
float_to_vec3_node_resource,
|
||||
"Input2"));
|
||||
|
||||
REQUIRE(blend_tree_resource.ConnectSockets(
|
||||
REQUIRE(blend_tree_resource->ConnectSockets(
|
||||
float_to_vec3_node_resource,
|
||||
"Output",
|
||||
graph_output_node,
|
||||
@@ -895,18 +852,15 @@ TEST_CASE("ResourceSaveLoadMathGraphInputs", "[AnimGraphResource]") {
|
||||
|
||||
WHEN("Saving and loading graph resource") {
|
||||
const char* filename = "TestGraphResourceSaveLoadGraphInputs.json";
|
||||
graph_resource_origin.SaveToFile(filename);
|
||||
REQUIRE(blend_tree_resource->SaveToFile(filename));
|
||||
|
||||
AnimGraphResourcePtr graph_resource_loaded(
|
||||
AnimGraphResource::CreateFromFile(filename));
|
||||
|
||||
BlendTreeResource& graph_blend_tree_loaded =
|
||||
graph_resource_loaded->m_blend_tree_resource;
|
||||
BlendTreeResource* blend_tree_resource_loaded =
|
||||
BlendTreeResource::CreateFromFile(filename);
|
||||
|
||||
const AnimNodeResource* graph_loaded_output_node =
|
||||
graph_blend_tree_loaded.GetGraphOutputNode();
|
||||
blend_tree_resource_loaded->GetGraphOutputNode();
|
||||
const AnimNodeResource* graph_loaded_input_node =
|
||||
graph_blend_tree_loaded.GetGraphInputNode();
|
||||
blend_tree_resource_loaded->GetGraphInputNode();
|
||||
|
||||
THEN("Graph inputs and outputs must be in loaded resource as well.") {
|
||||
REQUIRE(
|
||||
@@ -935,7 +889,7 @@ TEST_CASE("ResourceSaveLoadMathGraphInputs", "[AnimGraphResource]") {
|
||||
|
||||
WHEN("Instantiating an AnimGraph") {
|
||||
AnimGraphBlendTree blend_tree_node;
|
||||
graph_resource_loaded->CreateBlendTreeInstance(blend_tree_node);
|
||||
blend_tree_resource_loaded->CreateBlendTreeInstance(blend_tree_node);
|
||||
|
||||
float graph_float_input = 123.456f;
|
||||
blend_tree_node.SetInput("GraphFloatInput", &graph_float_input);
|
||||
@@ -975,7 +929,11 @@ TEST_CASE("ResourceSaveLoadMathGraphInputs", "[AnimGraphResource]") {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delete blend_tree_resource_loaded;
|
||||
}
|
||||
|
||||
delete blend_tree_resource;
|
||||
}
|
||||
|
||||
//
|
||||
@@ -992,21 +950,18 @@ TEST_CASE("ResourceSaveLoadMathGraphInputs", "[AnimGraphResource]") {
|
||||
// GraphFloat1Output -> GraphFLoatInputSingle * 3
|
||||
//
|
||||
TEST_CASE("SimpleMathEvaluations", "[AnimGraphResource]") {
|
||||
AnimGraphResource graph_resource_origin(AnimGraphType::GraphTypeBlendTree);
|
||||
graph_resource_origin.m_name = "TestSimpleMathGraph";
|
||||
graph_resource_origin.m_graph_type_name = "BlendTree";
|
||||
|
||||
BlendTreeResource& blend_tree_resource =
|
||||
graph_resource_origin.m_blend_tree_resource;
|
||||
BlendTreeResource* blend_tree_resource =
|
||||
dynamic_cast<BlendTreeResource*>(AnimNodeResourceFactory("BlendTree"));
|
||||
blend_tree_resource->m_name = "TestSimpleMathGraph";
|
||||
|
||||
// Prepare graph inputs and outputs
|
||||
size_t math_add0_node_index =
|
||||
blend_tree_resource.AddNode(AnimNodeResourceFactory("MathAddNode"));
|
||||
blend_tree_resource->AddNode(AnimNodeResourceFactory("MathAddNode"));
|
||||
size_t math_add1_node_index =
|
||||
blend_tree_resource.AddNode(AnimNodeResourceFactory("MathAddNode"));
|
||||
blend_tree_resource->AddNode(AnimNodeResourceFactory("MathAddNode"));
|
||||
|
||||
AnimNodeResource* graph_output_node =
|
||||
blend_tree_resource.GetGraphOutputNode();
|
||||
blend_tree_resource->GetGraphOutputNode();
|
||||
|
||||
graph_output_node->m_virtual_socket_accessor->RegisterInput<float>(
|
||||
"GraphFloat0Output",
|
||||
@@ -1018,57 +973,57 @@ TEST_CASE("SimpleMathEvaluations", "[AnimGraphResource]") {
|
||||
"GraphFloat2Output",
|
||||
nullptr);
|
||||
|
||||
AnimNodeResource* graph_input_node = blend_tree_resource.GetGraphInputNode();
|
||||
AnimNodeResource* graph_input_node = blend_tree_resource->GetGraphInputNode();
|
||||
graph_input_node->m_virtual_socket_accessor->RegisterOutput<float>(
|
||||
"GraphFloatInput",
|
||||
nullptr);
|
||||
|
||||
// Prepare graph inputs and outputs
|
||||
AnimNodeResource* math_add0_node =
|
||||
blend_tree_resource.GetNode(math_add0_node_index);
|
||||
blend_tree_resource->GetNode(math_add0_node_index);
|
||||
AnimNodeResource* math_add1_node =
|
||||
blend_tree_resource.GetNode(math_add1_node_index);
|
||||
blend_tree_resource->GetNode(math_add1_node_index);
|
||||
|
||||
// direct output
|
||||
REQUIRE(blend_tree_resource.ConnectSockets(
|
||||
REQUIRE(blend_tree_resource->ConnectSockets(
|
||||
graph_input_node,
|
||||
"GraphFloatInput",
|
||||
graph_output_node,
|
||||
"GraphFloat0Output"));
|
||||
|
||||
// add0 node
|
||||
REQUIRE(blend_tree_resource.ConnectSockets(
|
||||
REQUIRE(blend_tree_resource->ConnectSockets(
|
||||
graph_input_node,
|
||||
"GraphFloatInput",
|
||||
math_add0_node,
|
||||
"Input0"));
|
||||
|
||||
REQUIRE(blend_tree_resource.ConnectSockets(
|
||||
REQUIRE(blend_tree_resource->ConnectSockets(
|
||||
graph_input_node,
|
||||
"GraphFloatInput",
|
||||
math_add0_node,
|
||||
"Input1"));
|
||||
|
||||
REQUIRE(blend_tree_resource.ConnectSockets(
|
||||
REQUIRE(blend_tree_resource->ConnectSockets(
|
||||
math_add0_node,
|
||||
"Output",
|
||||
graph_output_node,
|
||||
"GraphFloat1Output"));
|
||||
|
||||
// add1 node
|
||||
REQUIRE(blend_tree_resource.ConnectSockets(
|
||||
REQUIRE(blend_tree_resource->ConnectSockets(
|
||||
math_add0_node,
|
||||
"Output",
|
||||
math_add1_node,
|
||||
"Input0"));
|
||||
|
||||
REQUIRE(blend_tree_resource.ConnectSockets(
|
||||
REQUIRE(blend_tree_resource->ConnectSockets(
|
||||
graph_input_node,
|
||||
"GraphFloatInput",
|
||||
math_add1_node,
|
||||
"Input1"));
|
||||
|
||||
REQUIRE(blend_tree_resource.ConnectSockets(
|
||||
REQUIRE(blend_tree_resource->ConnectSockets(
|
||||
math_add1_node,
|
||||
"Output",
|
||||
graph_output_node,
|
||||
@@ -1076,14 +1031,14 @@ TEST_CASE("SimpleMathEvaluations", "[AnimGraphResource]") {
|
||||
|
||||
WHEN("Saving and loading graph resource") {
|
||||
const char* filename = "TestGraphResourceSaveLoadGraphInputs.json";
|
||||
graph_resource_origin.SaveToFile(filename);
|
||||
REQUIRE(blend_tree_resource->SaveToFile(filename));
|
||||
|
||||
AnimGraphResourcePtr graph_resource_loaded(
|
||||
AnimGraphResource::CreateFromFile(filename));
|
||||
BlendTreeResource* blend_tree_resource_loaded =
|
||||
BlendTreeResource::CreateFromFile(filename);
|
||||
|
||||
WHEN("Instantiating an AnimGraph") {
|
||||
AnimGraphBlendTree blend_tree;
|
||||
graph_resource_loaded->CreateBlendTreeInstance(blend_tree);
|
||||
blend_tree_resource_loaded->CreateBlendTreeInstance(blend_tree);
|
||||
|
||||
float graph_float_input = 123.456f;
|
||||
blend_tree.SetInput("GraphFloatInput", &graph_float_input);
|
||||
@@ -1117,7 +1072,11 @@ TEST_CASE("SimpleMathEvaluations", "[AnimGraphResource]") {
|
||||
context.freeAnimations();
|
||||
}
|
||||
}
|
||||
|
||||
delete blend_tree_resource_loaded;
|
||||
}
|
||||
|
||||
delete blend_tree_resource;
|
||||
}
|
||||
|
||||
//
|
||||
@@ -1137,33 +1096,28 @@ TEST_CASE_METHOD(
|
||||
"EmbeddedBlendTreeGraphResource saving and loading results in same "
|
||||
"resource",
|
||||
"[EmbeddedBlendTreeGraphResource]") {
|
||||
parent_graph_resource.SaveToFile("TestGraphEmbeddedBlendTree.json");
|
||||
constexpr char filename[] = "TestGraphEmbeddedBlendTree.json";
|
||||
REQUIRE(blend_tree_resource->SaveToFile(filename));
|
||||
|
||||
AnimGraphResourcePtr parent_graph_resource_loaded(
|
||||
AnimGraphResource::CreateFromFile("TestGraphEmbeddedBlendTree.json"));
|
||||
BlendTreeResource* blend_tree_resource_loaded =
|
||||
BlendTreeResource::CreateFromFile(filename);
|
||||
|
||||
// Check the loaded parent graph
|
||||
CheckAnimGraphResourceEqual(
|
||||
parent_graph_resource,
|
||||
*parent_graph_resource_loaded);
|
||||
|
||||
const BlendTreeResource& parent_blend_tree_resource_loaded =
|
||||
parent_graph_resource_loaded->m_blend_tree_resource;
|
||||
CheckBlendTreeResourcesEqual(blend_tree_resource, blend_tree_resource_loaded);
|
||||
|
||||
// Check the loaded embedded graph
|
||||
REQUIRE(
|
||||
parent_blend_tree_resource_loaded.GetNode(3)->m_node_type_name
|
||||
== "BlendTree");
|
||||
blend_tree_resource_loaded->GetNode(3)->m_node_type_name == "BlendTree");
|
||||
|
||||
const AnimGraphResource* embedded_graph_loaded =
|
||||
dynamic_cast<const AnimGraphResource*>(
|
||||
parent_blend_tree_resource_loaded.GetNode(3));
|
||||
const BlendTreeResource* embedded_blend_tree_resource_loaded =
|
||||
&embedded_graph_loaded->m_blend_tree_resource;
|
||||
dynamic_cast<const BlendTreeResource*>(
|
||||
blend_tree_resource_loaded->GetNode(3));
|
||||
|
||||
CheckBlendTreeResourcesEqual(
|
||||
embedded_blend_tree_resource,
|
||||
embedded_blend_tree_resource_loaded);
|
||||
|
||||
delete blend_tree_resource_loaded;
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(
|
||||
@@ -1172,7 +1126,7 @@ TEST_CASE_METHOD(
|
||||
"[EmbeddedBlendTreeGraphResource]") {
|
||||
AnimGraphBlendTree blend_tree;
|
||||
|
||||
parent_graph_resource.CreateBlendTreeInstance(blend_tree);
|
||||
blend_tree_resource->CreateBlendTreeInstance(blend_tree);
|
||||
AnimGraphContext graph_context;
|
||||
|
||||
ozz::animation::Skeleton skeleton;
|
||||
@@ -1249,7 +1203,7 @@ TEST_CASE_METHOD(
|
||||
"[EmbeddedTreeBlend2GraphResource]") {
|
||||
AnimGraphBlendTree blend_tree;
|
||||
|
||||
parent_graph_resource.CreateBlendTreeInstance(blend_tree);
|
||||
blend_tree_resource->CreateBlendTreeInstance(blend_tree);
|
||||
AnimGraphContext graph_context;
|
||||
|
||||
ozz::animation::Skeleton skeleton;
|
||||
@@ -1332,21 +1286,19 @@ TEST_CASE_METHOD(
|
||||
TEST_CASE(
|
||||
"Register AnimGraphResource Blendtree Sockets",
|
||||
"[AnimGraphResource]") {
|
||||
AnimNodeResource* blend_tree_anim_node_resource =
|
||||
AnimNodeResourceFactory("BlendTree");
|
||||
AnimGraphResource* blend_tree_graph_resource =
|
||||
dynamic_cast<AnimGraphResource*>(blend_tree_anim_node_resource);
|
||||
BlendTreeResource* blend_tree_resource =
|
||||
dynamic_cast<BlendTreeResource*>(AnimNodeResourceFactory("BlendTree"));
|
||||
|
||||
Socket socket;
|
||||
socket.m_name = "FloatSocket";
|
||||
socket.m_type = SocketType::SocketTypeFloat;
|
||||
socket.m_reference.ptr = nullptr;
|
||||
|
||||
CHECK(blend_tree_graph_resource->RegisterBlendTreeInputSocket(socket));
|
||||
CHECK(!blend_tree_graph_resource->RegisterBlendTreeInputSocket(socket));
|
||||
CHECK(blend_tree_resource->RegisterBlendTreeInputSocket(socket));
|
||||
CHECK(!blend_tree_resource->RegisterBlendTreeInputSocket(socket));
|
||||
|
||||
CHECK(blend_tree_graph_resource->RegisterBlendTreeOutputSocket(socket));
|
||||
CHECK(!blend_tree_graph_resource->RegisterBlendTreeOutputSocket(socket));
|
||||
CHECK(blend_tree_resource->RegisterBlendTreeOutputSocket(socket));
|
||||
CHECK(!blend_tree_resource->RegisterBlendTreeOutputSocket(socket));
|
||||
|
||||
delete blend_tree_anim_node_resource;
|
||||
delete blend_tree_resource;
|
||||
}
|
||||
|
||||
@@ -8,26 +8,34 @@
|
||||
|
||||
TEST_CASE("Descriptor Access", "[NodeDescriptorTests]") {
|
||||
Blend2Node blend2Node;
|
||||
NodeDescriptor<Blend2Node> blend2Descriptor (&blend2Node);
|
||||
NodeDescriptor<Blend2Node> blend2Descriptor(&blend2Node);
|
||||
CHECK(blend2Descriptor.m_inputs.size() == 3);
|
||||
CHECK(*blend2Descriptor.m_inputs[0].m_reference.ptr_ptr == blend2Node.i_input0);
|
||||
CHECK(*blend2Descriptor.m_inputs[1].m_reference.ptr_ptr == blend2Node.i_input1);
|
||||
CHECK(*blend2Descriptor.m_inputs[2].m_reference.ptr_ptr == blend2Node.i_blend_weight);
|
||||
CHECK(
|
||||
*blend2Descriptor.m_inputs[0].m_reference.ptr_ptr == blend2Node.i_input0);
|
||||
CHECK(
|
||||
*blend2Descriptor.m_inputs[1].m_reference.ptr_ptr == blend2Node.i_input1);
|
||||
CHECK(
|
||||
*blend2Descriptor.m_inputs[2].m_reference.ptr_ptr
|
||||
== blend2Node.i_blend_weight);
|
||||
|
||||
CHECK(blend2Descriptor.m_inputs[0].m_type_size == sizeof(AnimData));
|
||||
CHECK(blend2Descriptor.m_inputs[0].m_type_size == sizeof(Pose));
|
||||
CHECK(blend2Descriptor.m_inputs[2].m_type_size == 4);
|
||||
|
||||
CHECK(blend2Descriptor.m_outputs.size() == 1);
|
||||
CHECK(*blend2Descriptor.m_outputs[0].m_reference.ptr_ptr == blend2Node.o_output);
|
||||
CHECK(
|
||||
*blend2Descriptor.m_outputs[0].m_reference.ptr_ptr
|
||||
== blend2Node.o_output);
|
||||
|
||||
CHECK(blend2Descriptor.m_properties.size() == 1);
|
||||
CHECK(blend2Descriptor.m_properties[0].m_reference.ptr == &blend2Node.m_sync_blend);
|
||||
CHECK(
|
||||
blend2Descriptor.m_properties[0].m_reference.ptr
|
||||
== &blend2Node.m_sync_blend);
|
||||
|
||||
// Check we can properly update inputs
|
||||
CHECK(blend2Node.i_input0 == nullptr);
|
||||
AnimData some_anim_data;
|
||||
blend2Descriptor.SetInput("Input0", &some_anim_data);
|
||||
CHECK(blend2Node.i_input0 == &some_anim_data);
|
||||
Pose pose;
|
||||
blend2Descriptor.SetInput("Input0", &pose);
|
||||
CHECK(blend2Node.i_input0 == &pose);
|
||||
|
||||
// Check we properly can set properties
|
||||
CHECK(blend2Node.m_sync_blend == false);
|
||||
@@ -41,6 +49,7 @@ TEST_CASE("Descriptor Access", "[NodeDescriptorTests]") {
|
||||
blend2Descriptor.UpdateFlags();
|
||||
Socket* weight_input_socket = blend2Descriptor.GetInputSocket("Weight");
|
||||
CHECK(weight_input_socket != nullptr);
|
||||
CHECK(weight_input_socket->m_flags & SocketFlagAffectsTime == SocketFlagAffectsTime);
|
||||
CHECK(
|
||||
weight_input_socket->m_flags
|
||||
& SocketFlagAffectsTime == SocketFlagAffectsTime);
|
||||
}
|
||||
|
||||
|
||||
+160
-129
@@ -2,201 +2,232 @@
|
||||
// Created by martin on 16.11.21.
|
||||
//
|
||||
|
||||
#include "SyncTrack.h"
|
||||
#include "AnimGraph/SyncTrack.h"
|
||||
#include "catch.hpp"
|
||||
|
||||
TEST_CASE("Basic", "[SyncTrack]") {
|
||||
SyncTrack track_A;
|
||||
track_A.m_num_intervals = 2;
|
||||
track_A.m_duration = 2.0;
|
||||
track_A.m_interval_start[0] = 0.f;
|
||||
track_A.m_interval_ratio[0] = 0.7;
|
||||
track_A.m_interval_start[1] = 0.7f;
|
||||
track_A.m_interval_ratio[1] = 0.3;
|
||||
SyncTrack track_a;
|
||||
track_a.m_num_intervals = 2;
|
||||
track_a.m_duration = 2.0;
|
||||
track_a.m_interval_start_ratio[0] = 0.f;
|
||||
track_a.m_interval_duration_ratio[0] = 0.7;
|
||||
track_a.m_interval_start_ratio[1] = 0.7f;
|
||||
track_a.m_interval_duration_ratio[1] = 0.3;
|
||||
|
||||
SyncTrack track_B;
|
||||
track_B.m_num_intervals = 2;
|
||||
track_B.m_duration = 1.5;
|
||||
track_B.m_interval_start[0] = 0.0f;
|
||||
track_B.m_interval_ratio[0] = 0.6;
|
||||
track_B.m_interval_start[1] = 0.6f;
|
||||
track_B.m_interval_ratio[1] = 0.4;
|
||||
SyncTrack track_b;
|
||||
track_b.m_num_intervals = 2;
|
||||
track_b.m_duration = 1.5;
|
||||
track_b.m_interval_start_ratio[0] = 0.0f;
|
||||
track_b.m_interval_duration_ratio[0] = 0.6;
|
||||
track_b.m_interval_start_ratio[1] = 0.6f;
|
||||
track_b.m_interval_duration_ratio[1] = 0.4;
|
||||
|
||||
WHEN("Calculating sync time of track_B at 0.5 duration") {
|
||||
float sync_time_at_0_75 =
|
||||
track_B.CalcSyncFromAbsTime(0.5 * track_B.m_duration);
|
||||
track_b.CalcSyncFromAbsTime(0.5 * track_b.m_duration);
|
||||
REQUIRE(sync_time_at_0_75 == Catch::Detail::Approx(0.83333));
|
||||
}
|
||||
|
||||
WHEN("Calculating sync time of track_B at 0.6 duration") {
|
||||
float sync_time_at_0_6 =
|
||||
track_B.CalcSyncFromAbsTime(0.6 * track_B.m_duration);
|
||||
track_b.CalcSyncFromAbsTime(0.6 * track_b.m_duration);
|
||||
REQUIRE(sync_time_at_0_6 == Catch::Detail::Approx(1.0));
|
||||
}
|
||||
|
||||
WHEN("Calculating sync time of track_B at 0.7 duration") {
|
||||
float sync_time_at_0_7 =
|
||||
track_B.CalcSyncFromAbsTime(0.7 * track_B.m_duration);
|
||||
track_b.CalcSyncFromAbsTime(0.7 * track_b.m_duration);
|
||||
REQUIRE(sync_time_at_0_7 == Catch::Detail::Approx(1.25));
|
||||
}
|
||||
|
||||
WHEN("Calculating sync time of track_B at 0.0 duration") {
|
||||
float sync_time_at_1_0 =
|
||||
track_B.CalcSyncFromAbsTime(0.0 * track_B.m_duration);
|
||||
track_b.CalcSyncFromAbsTime(0.0 * track_b.m_duration);
|
||||
REQUIRE(sync_time_at_1_0 == Catch::Detail::Approx(0.0));
|
||||
}
|
||||
|
||||
WHEN("Calculating sync time of track_B at 1.0 duration") {
|
||||
float sync_time_at_1_0 =
|
||||
track_B.CalcSyncFromAbsTime(0.9999 * track_B.m_duration);
|
||||
track_b.CalcSyncFromAbsTime(0.9999 * track_b.m_duration);
|
||||
REQUIRE(sync_time_at_1_0 == Catch::Detail::Approx(2.0).epsilon(0.001f));
|
||||
}
|
||||
|
||||
WHEN("Calculating ratio from sync time on track_A at 0.83333") {
|
||||
float ratio = track_A.CalcRatioFromSyncTime(0.83333333);
|
||||
float ratio = track_a.CalcRatioFromSyncTime(0.83333333);
|
||||
REQUIRE(ratio == Catch::Detail::Approx(0.5833333));
|
||||
}
|
||||
|
||||
WHEN("Calculating ratio from sync time on track_A at 0.83333") {
|
||||
float ratio = track_A.CalcRatioFromSyncTime(1.25);
|
||||
float ratio = track_a.CalcRatioFromSyncTime(1.25);
|
||||
REQUIRE(ratio == Catch::Detail::Approx(0.775));
|
||||
}
|
||||
|
||||
WHEN("Blending two synctracks with weight 0.") {
|
||||
SyncTrack blended = SyncTrack::Blend(0.f, track_A, track_B);
|
||||
SyncTrack blended = SyncTrack::Blend(0.f, track_a, track_b);
|
||||
|
||||
THEN("Result must equal track_A") { REQUIRE(track_A == blended); }
|
||||
THEN("Result must equal track_A") { REQUIRE(track_a == blended); }
|
||||
}
|
||||
|
||||
WHEN("Blending two synctracks with weight 1.") {
|
||||
SyncTrack blended = SyncTrack::Blend(1.f, track_A, track_B);
|
||||
SyncTrack blended = SyncTrack::Blend(1.f, track_a, track_b);
|
||||
|
||||
THEN("Result must equal track_B") { REQUIRE(track_B == blended); }
|
||||
THEN("Result must equal track_B") { REQUIRE(track_b == blended); }
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Sync Marker Interval Calculation", "[SyncTrack]") {
|
||||
SyncTrack track_A;
|
||||
track_A.m_num_intervals = 2;
|
||||
track_A.m_duration = 2.0;
|
||||
track_A.m_sync_markers[0] = 0.9;
|
||||
track_A.m_sync_markers[1] = 0.2;
|
||||
TEST_CASE("Sync Track From Marker", "[SyncTrack]") {
|
||||
SyncTrack track = SyncTrack::CreateFromMarkers(2.0f, {0.9f, 0.2f});
|
||||
|
||||
WHEN("Calculating intervals") {
|
||||
track_A.CalcIntervals();
|
||||
WHEN("Querying Ratios") {
|
||||
CHECK(track.m_interval_start_ratio[0] == Catch::Detail::Approx(0.45f));
|
||||
CHECK(track.m_interval_duration_ratio[0] == Catch::Detail::Approx(0.65f));
|
||||
|
||||
CHECK(track_A.m_interval_start[0] == Catch::Detail::Approx(0.9f));
|
||||
CHECK(track_A.m_interval_ratio[0] == Catch::Detail::Approx(0.3f));
|
||||
CHECK(track.m_interval_start_ratio[1] == Catch::Detail::Approx(0.1f));
|
||||
CHECK(track.m_interval_duration_ratio[1] == Catch::Detail::Approx(0.35f));
|
||||
|
||||
CHECK(track_A.m_interval_start[1] == Catch::Detail::Approx(0.2f));
|
||||
CHECK(track_A.m_interval_ratio[1] == Catch::Detail::Approx(0.7f));
|
||||
WHEN("Querying ratio at sync time at 0.001") {
|
||||
float ratio = track.CalcRatioFromSyncTime(0.0001f);
|
||||
CHECK(ratio == Catch::Detail::Approx(0.45).epsilon(0.001));
|
||||
}
|
||||
|
||||
WHEN("Querying ratio at sync time at 1.001") {
|
||||
float ratio = track_A.CalcRatioFromSyncTime(1.0001f);
|
||||
CHECK(ratio == Catch::Detail::Approx(0.2).epsilon(0.001));
|
||||
WHEN("Querying ratio at sync time at 0.9999") {
|
||||
float ratio = track.CalcRatioFromSyncTime(0.9999f);
|
||||
CHECK(ratio == Catch::Detail::Approx(0.1).epsilon(0.001));
|
||||
}
|
||||
|
||||
WHEN("Querying ratio at sync time at 1.001") {
|
||||
float ratio = track_A.CalcRatioFromSyncTime(0.0001f);
|
||||
CHECK(ratio == Catch::Detail::Approx(0.9).epsilon(0.001));
|
||||
float ratio = track.CalcRatioFromSyncTime(1.0001f);
|
||||
CHECK(ratio == Catch::Detail::Approx(0.1).epsilon(0.001));
|
||||
}
|
||||
|
||||
WHEN("Querying ratio at sync time at 1.9999") {
|
||||
float ratio = track_A.CalcRatioFromSyncTime(0.9999f);
|
||||
CHECK(ratio == Catch::Detail::Approx(0.2).epsilon(0.001));
|
||||
float ratio = track.CalcRatioFromSyncTime(1.9999f);
|
||||
CHECK(ratio == Catch::Detail::Approx(0.45).epsilon(0.001));
|
||||
}
|
||||
}
|
||||
|
||||
WHEN("Blending sync track with 3 events") {
|
||||
track_A.m_num_intervals = 3;
|
||||
track_A.m_duration = 2.0;
|
||||
track_A.m_sync_markers[0] = 0.;
|
||||
track_A.m_sync_markers[1] = 0.3;
|
||||
track_A.m_sync_markers[2] = 0.9;
|
||||
track_A.CalcIntervals();
|
||||
|
||||
SyncTrack track_B;
|
||||
track_B.m_num_intervals = 3;
|
||||
track_B.m_duration = 1.5;
|
||||
track_B.m_sync_markers[0] = 0.7;
|
||||
track_B.m_sync_markers[1] = 0.9;
|
||||
track_B.m_sync_markers[2] = 0.2;
|
||||
track_B.CalcIntervals();
|
||||
|
||||
WHEN("Calculating A's durations") {
|
||||
CHECK(track_A.m_interval_ratio[0] == Catch::Detail::Approx(0.3));
|
||||
CHECK(track_A.m_interval_ratio[1] == Catch::Detail::Approx(0.6));
|
||||
CHECK(track_A.m_interval_ratio[2] == Catch::Detail::Approx(0.1));
|
||||
WHEN("Querying SyncTime from Absolute Time") {
|
||||
WHEN("Querying absolute time at 0.9001s") {
|
||||
float sync_time = track.CalcSyncFromAbsTime(0.9001f);
|
||||
CHECK_THAT(sync_time, Catch::WithinAbs(0.0, 0.001));
|
||||
}
|
||||
|
||||
WHEN("Calculating B's durations") {
|
||||
CHECK(track_B.m_interval_ratio[0] == Catch::Detail::Approx(0.2));
|
||||
CHECK(track_B.m_interval_ratio[1] == Catch::Detail::Approx(0.3));
|
||||
CHECK(track_B.m_interval_ratio[2] == Catch::Detail::Approx(0.5));
|
||||
WHEN("Querying absolute time at 0.2001s") {
|
||||
float sync_time = track.CalcSyncFromAbsTime(0.2001f);
|
||||
CHECK_THAT(sync_time, Catch::WithinAbs(1.0, 0.001));
|
||||
}
|
||||
|
||||
WHEN("Blending two synctracks with weight 0.") {
|
||||
SyncTrack blended = SyncTrack::Blend(0.f, track_A, track_B);
|
||||
|
||||
THEN("Result must equal track_A") { REQUIRE(track_A == blended); }
|
||||
WHEN("Querying absolute time at 0.8999s") {
|
||||
float sync_time = track.CalcSyncFromAbsTime(0.8999f);
|
||||
CHECK_THAT(sync_time, Catch::WithinAbs(1.999, 0.001));
|
||||
}
|
||||
|
||||
WHEN("Blending two synctracks with weight 1.") {
|
||||
SyncTrack blended = SyncTrack::Blend(1.f, track_A, track_B);
|
||||
|
||||
THEN("Result must equal track_B") { REQUIRE(track_B == blended); }
|
||||
}
|
||||
|
||||
WHEN("Blending with weight 0.2") {
|
||||
float weight = 0.2f;
|
||||
SyncTrack blended = SyncTrack::Blend(weight, track_A, track_B);
|
||||
|
||||
REQUIRE(
|
||||
blended.m_duration
|
||||
== (1.0f - weight) * track_A.m_duration
|
||||
+ weight * track_B.m_duration);
|
||||
REQUIRE(
|
||||
blended.m_interval_start[0]
|
||||
== fmodf(
|
||||
(1.0f - weight) * (track_A.m_interval_start[0] + 1.0f)
|
||||
+ weight * (track_B.m_interval_start[0]),
|
||||
1.0f));
|
||||
REQUIRE(
|
||||
blended.m_interval_ratio[1]
|
||||
== (1.0f - weight) * (track_A.m_interval_ratio[1])
|
||||
+ weight * (track_B.m_interval_ratio[1])
|
||||
);
|
||||
REQUIRE(
|
||||
blended.m_interval_ratio[2]
|
||||
== (1.0f - weight) * (track_A.m_interval_ratio[2])
|
||||
+ weight * (track_B.m_interval_ratio[2])
|
||||
);
|
||||
}
|
||||
|
||||
WHEN("Inverted blending with weight 0.2") {
|
||||
float weight = 0.2f;
|
||||
SyncTrack blended = SyncTrack::Blend(weight, track_B, track_A);
|
||||
|
||||
REQUIRE(
|
||||
blended.m_duration
|
||||
== (1.0f - weight) * track_B.m_duration
|
||||
+ weight * track_A.m_duration);
|
||||
REQUIRE(
|
||||
blended.m_interval_start[0]
|
||||
== fmodf(
|
||||
(1.0f - weight) * (track_B.m_interval_start[0])
|
||||
+ weight * (track_A.m_interval_start[0] + 1.0f),
|
||||
1.0f));
|
||||
REQUIRE(
|
||||
blended.m_interval_ratio[1]
|
||||
== (1.0f - weight) * (track_B.m_interval_ratio[1])
|
||||
+ weight * (track_A.m_interval_ratio[1])
|
||||
);
|
||||
REQUIRE(
|
||||
blended.m_interval_ratio[2]
|
||||
== (1.0f - weight) * (track_B.m_interval_ratio[2])
|
||||
+ weight * (track_A.m_interval_ratio[2])
|
||||
);
|
||||
WHEN("Querying absolute time at 1.9999s") {
|
||||
float sync_time = track.CalcSyncFromAbsTime(1.9999f);
|
||||
CHECK_THAT(sync_time, Catch::WithinAbs(0.84615384, 0.001));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Sync Track Blending", "[SyncTrack]") {
|
||||
SyncTrack track_a = SyncTrack::CreateFromMarkers(2.0, {0., 0.6, 1.8});
|
||||
SyncTrack track_b = SyncTrack::CreateFromMarkers(1.5f, {1.05, 1.35, 0.3});
|
||||
|
||||
WHEN("Calculating A's durations") {
|
||||
CHECK(track_a.m_interval_duration_ratio[0] == Catch::Detail::Approx(0.3));
|
||||
CHECK(track_a.m_interval_duration_ratio[1] == Catch::Detail::Approx(0.6));
|
||||
CHECK(track_a.m_interval_duration_ratio[2] == Catch::Detail::Approx(0.1));
|
||||
}
|
||||
|
||||
WHEN("Calculating B's durations") {
|
||||
CHECK(track_b.m_interval_duration_ratio[0] == Catch::Detail::Approx(0.2));
|
||||
CHECK(track_b.m_interval_duration_ratio[1] == Catch::Detail::Approx(0.3));
|
||||
CHECK(track_b.m_interval_duration_ratio[2] == Catch::Detail::Approx(0.5));
|
||||
}
|
||||
|
||||
WHEN("Blending two synctracks with weight 0.") {
|
||||
SyncTrack blended = SyncTrack::Blend(0.f, track_a, track_b);
|
||||
|
||||
THEN("Result must equal track_A") { REQUIRE(track_a == blended); }
|
||||
}
|
||||
|
||||
WHEN("Blending two synctracks with weight 1.") {
|
||||
SyncTrack blended = SyncTrack::Blend(1.f, track_a, track_b);
|
||||
|
||||
THEN("Result must equal track_B") { REQUIRE(track_b == blended); }
|
||||
}
|
||||
|
||||
WHEN("Blending with weight 0.2") {
|
||||
float weight = 0.2f;
|
||||
SyncTrack blended = SyncTrack::Blend(weight, track_a, track_b);
|
||||
|
||||
REQUIRE(
|
||||
blended.m_duration
|
||||
== (1.0f - weight) * track_a.m_duration + weight * track_b.m_duration);
|
||||
REQUIRE(
|
||||
blended.m_interval_start_ratio[0]
|
||||
== fmodf(
|
||||
(1.0f - weight) * (track_a.m_interval_start_ratio[0] + 1.0f)
|
||||
+ weight * (track_b.m_interval_start_ratio[0]),
|
||||
1.0f));
|
||||
REQUIRE(
|
||||
blended.m_interval_duration_ratio[1]
|
||||
== (1.0f - weight) * (track_a.m_interval_duration_ratio[1])
|
||||
+ weight * (track_b.m_interval_duration_ratio[1]));
|
||||
REQUIRE(
|
||||
blended.m_interval_duration_ratio[2]
|
||||
== (1.0f - weight) * (track_a.m_interval_duration_ratio[2])
|
||||
+ weight * (track_b.m_interval_duration_ratio[2]));
|
||||
}
|
||||
|
||||
WHEN("Inverted blending with weight 0.2") {
|
||||
float weight = 0.2f;
|
||||
SyncTrack blended = SyncTrack::Blend(weight, track_b, track_a);
|
||||
|
||||
REQUIRE(
|
||||
blended.m_duration
|
||||
== (1.0f - weight) * track_b.m_duration + weight * track_a.m_duration);
|
||||
REQUIRE(
|
||||
blended.m_interval_start_ratio[0]
|
||||
== fmodf(
|
||||
(1.0f - weight) * (track_b.m_interval_start_ratio[0])
|
||||
+ weight * (track_a.m_interval_start_ratio[0] + 1.0f),
|
||||
1.0f));
|
||||
REQUIRE(
|
||||
blended.m_interval_duration_ratio[1]
|
||||
== (1.0f - weight) * (track_b.m_interval_duration_ratio[1])
|
||||
+ weight * (track_a.m_interval_duration_ratio[1]));
|
||||
REQUIRE(
|
||||
blended.m_interval_duration_ratio[2]
|
||||
== (1.0f - weight) * (track_b.m_interval_duration_ratio[2])
|
||||
+ weight * (track_a.m_interval_duration_ratio[2]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Serialization", "[SyncTrack]") {
|
||||
SyncTrack track;
|
||||
track.m_num_intervals = 3;
|
||||
track.m_duration = 2.0;
|
||||
track.m_interval_start_ratio[0] = 0.f;
|
||||
track.m_interval_duration_ratio[0] = 0.7;
|
||||
track.m_interval_start_ratio[1] = 0.7f;
|
||||
track.m_interval_duration_ratio[1] = 0.3;
|
||||
track.m_interval_start_ratio[2] = 0.7f;
|
||||
track.m_interval_duration_ratio[2] = 0.3;
|
||||
|
||||
nlohmann::json synctrack_json = track;
|
||||
|
||||
const SyncTrack synctrack_deserialized = synctrack_json;
|
||||
|
||||
CHECK(synctrack_deserialized.m_duration == track.m_duration);
|
||||
CHECK(synctrack_deserialized.m_num_intervals == track.m_num_intervals);
|
||||
|
||||
for (int i = 0; i < track.m_num_intervals; i++) {
|
||||
CHECK(
|
||||
synctrack_deserialized.m_interval_start_ratio[i]
|
||||
== track.m_interval_start_ratio[i]);
|
||||
CHECK(
|
||||
synctrack_deserialized.m_interval_duration_ratio[i]
|
||||
== track.m_interval_duration_ratio[i]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
//
|
||||
// Created by martin on 11.04.25.
|
||||
//
|
||||
|
||||
#include "TestAnimData.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "ozz/animation/offline/animation_builder.h"
|
||||
#include "ozz/animation/offline/raw_animation.h"
|
||||
#include "ozz/animation/offline/raw_skeleton.h"
|
||||
#include "ozz/base/io/archive.h"
|
||||
#include "ozz/base/log.h"
|
||||
|
||||
namespace TestAnimData {
|
||||
|
||||
SingleBoneSkeleton::SingleBoneSkeleton() {
|
||||
using namespace ozz::animation::offline;
|
||||
|
||||
RawSkeleton raw_skeleton;
|
||||
RawSkeleton::Joint raw_joint;
|
||||
|
||||
raw_joint.name = "Bone0";
|
||||
raw_joint.transform.translation.x = 1.f;
|
||||
raw_joint.transform.translation.y = 2.f;
|
||||
raw_joint.transform.translation.z = 3.f;
|
||||
|
||||
raw_skeleton.roots.push_back(raw_joint);
|
||||
|
||||
SkeletonBuilder skeleton_builder;
|
||||
skeleton = skeleton_builder(raw_skeleton);
|
||||
|
||||
// SingleBoneSkeleton Animations
|
||||
ozz::animation::offline::RawAnimation raw_animation_translation_x;
|
||||
raw_animation_translation_x.name = "TranslationX";
|
||||
RawAnimation::JointTrack bone0_track;
|
||||
RawAnimation::JointTrack::Translations bone0_translations;
|
||||
|
||||
// animation_translate_x
|
||||
RawAnimation::TranslationKey translation_key;
|
||||
translation_key.time = 0.f;
|
||||
translation_key.value = ozz::math::Float3(0.f, 0.f, 0.f);
|
||||
bone0_translations.push_back(translation_key);
|
||||
|
||||
translation_key.time = 1.f;
|
||||
translation_key.value = ozz::math::Float3(1.f, 0.f, 0.f);
|
||||
bone0_translations.push_back(translation_key);
|
||||
|
||||
bone0_track.translations = bone0_translations;
|
||||
raw_animation_translation_x.tracks.push_back(bone0_track);
|
||||
raw_animation_translation_x.duration = 1.f;
|
||||
if (!raw_animation_translation_x.Validate()) {
|
||||
std::cerr << "Error: could animation raw data invalid!" << std::endl;
|
||||
}
|
||||
|
||||
AnimationBuilder animation_builder;
|
||||
animation_translate_x = animation_builder(raw_animation_translation_x);
|
||||
|
||||
animation_translate_x_resource.m_animation = animation_translate_x.get();
|
||||
SaveAnimation("single_bone_translation_z.ozz", animation_translate_x.get());
|
||||
animation_translate_x_resource.m_name = "single_bone_translation_z";
|
||||
animation_translate_x_resource.m_filename = "single_bone_translation_z.ozz";
|
||||
|
||||
// animation_translate_y
|
||||
ozz::animation::offline::RawAnimation raw_animation_translation_y;
|
||||
raw_animation_translation_y.name = "TranslationY";
|
||||
bone0_translations.clear();
|
||||
|
||||
translation_key.time = 0.f;
|
||||
translation_key.value = ozz::math::Float3(0.f, 0.f, 0.f);
|
||||
bone0_translations.push_back(translation_key);
|
||||
|
||||
translation_key.time = 1.f;
|
||||
translation_key.value = ozz::math::Float3(0.f, 1.f, 0.f);
|
||||
bone0_translations.push_back(translation_key);
|
||||
|
||||
bone0_track.translations = bone0_translations;
|
||||
raw_animation_translation_y.tracks.push_back(bone0_track);
|
||||
raw_animation_translation_y.duration = 1.f;
|
||||
if (!raw_animation_translation_y.Validate()) {
|
||||
std::cerr << "Error: could animation raw data invalid!" << std::endl;
|
||||
}
|
||||
|
||||
animation_translate_y = animation_builder(raw_animation_translation_y);
|
||||
|
||||
animation_translate_y_resource.m_animation = animation_translate_y.get();
|
||||
SaveAnimation("single_bone_translation_y.ozz", animation_translate_y.get());
|
||||
animation_translate_y_resource.m_name = "single_bone_translation_y";
|
||||
animation_translate_y_resource.m_filename = "single_bone_translation_y.ozz";
|
||||
}
|
||||
|
||||
bool SingleBoneSkeleton::SaveSkeleton(
|
||||
const char* filename,
|
||||
ozz::animation::Skeleton* skeleton) {
|
||||
assert(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SingleBoneSkeleton::SaveAnimation(
|
||||
const char* filename,
|
||||
ozz::animation::Animation* animation) {
|
||||
ozz::io::File file(filename, "wb");
|
||||
if (!file.opened()) {
|
||||
ozz::log::Err() << "Failed to create animation file " << filename << "."
|
||||
<< std::endl;
|
||||
delete animation;
|
||||
return false;
|
||||
}
|
||||
|
||||
ozz::io::OArchive archive(&file);
|
||||
|
||||
archive << *animation;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace TestAnimData
|
||||
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// Created by martin on 11.04.25.
|
||||
//
|
||||
|
||||
#ifndef TESTANIMDATA_H
|
||||
#define TESTANIMDATA_H
|
||||
|
||||
#include "AnimGraph/AnimGraphData.h"
|
||||
#include "AnimGraph/SyncTrack.h"
|
||||
#include "ozz/animation/offline/skeleton_builder.h"
|
||||
#include "ozz/animation/runtime/animation.h"
|
||||
#include "ozz/animation/runtime/skeleton.h"
|
||||
|
||||
namespace TestAnimData {
|
||||
|
||||
struct SingleBoneSkeleton {
|
||||
SingleBoneSkeleton();
|
||||
|
||||
ozz::unique_ptr<ozz::animation::Skeleton> skeleton = nullptr;
|
||||
|
||||
ozz::unique_ptr<ozz::animation::Animation> animation_translate_x = nullptr;
|
||||
AnimationResource animation_translate_x_resource;
|
||||
SyncTrack animation_translate_x_sync_track = {};
|
||||
|
||||
ozz::unique_ptr<ozz::animation::Animation> animation_translate_y = nullptr;
|
||||
AnimationResource animation_translate_y_resource;
|
||||
SyncTrack animation_translate_y_sync_track = {};
|
||||
|
||||
bool SaveSkeleton(const char* filename, ozz::animation::Skeleton* skeleton);
|
||||
bool SaveAnimation(
|
||||
const char* filename,
|
||||
ozz::animation::Animation* animation);
|
||||
};
|
||||
|
||||
} // namespace TestAnimData
|
||||
|
||||
#endif //TESTANIMDATA_H
|
||||
Reference in New Issue
Block a user