diff --git a/SCsub b/SCsub index 3c12e73..2533397 100644 --- a/SCsub +++ b/SCsub @@ -2,4 +2,10 @@ Import('env') -env.add_source_files(env.modules_sources, "*.cpp") # Add all cpp files to the build \ No newline at end of file +module_env = env.Clone() + +module_env.add_source_files(env.modules_sources, "*.cpp") # Add all cpp files to the build + +if env["tests"]: + module_env.Append(CPPDEFINES=["TESTS_ENABLED"]) + module_env.add_source_files(env.modules_sources, "./tests/*.cpp") \ No newline at end of file diff --git a/config.py b/config.py index d22f945..1c8cd12 100644 --- a/config.py +++ b/config.py @@ -1,6 +1,5 @@ def can_build(env, platform): return True - def configure(env): pass diff --git a/synced_animation_graph.cpp b/synced_animation_graph.cpp index 41337fa..6b09e84 100644 --- a/synced_animation_graph.cpp +++ b/synced_animation_graph.cpp @@ -5,8 +5,6 @@ #include "scene/animation/animation_player.h" void SyncedAnimationGraph::_bind_methods() { - print_line(vformat("binding methods")); - ClassDB::bind_method(D_METHOD("set_active", "active"), &SyncedAnimationGraph::set_active); ClassDB::bind_method(D_METHOD("is_active"), &SyncedAnimationGraph::is_active); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "active"), "set_active", "is_active"); @@ -50,6 +48,15 @@ void SyncedAnimationGraph::_notification(int p_what) { _process_graph(get_physics_process_delta_time()); } } break; + + case Node::NOTIFICATION_EXIT_TREE: { + _cleanup_evaluation_context(); + break; + } + + default: { + break; + } } } @@ -115,6 +122,9 @@ void SyncedAnimationGraph::set_animation_player(const NodePath &p_path) { } graph_context.animation_player = Object::cast_to(get_node_or_null(animation_player_path)); + _setup_evaluation_context(); + _setup_graph(); + emit_signal(SNAME("animation_player_changed")); // Needs to unpin AnimationPlayerEditor. } @@ -132,6 +142,9 @@ void SyncedAnimationGraph::set_skeleton(const NodePath &p_path) { } graph_context.skeleton_3d = Object::cast_to(get_node_or_null(skeleton_path)); + _setup_evaluation_context(); + _setup_graph(); + emit_signal(SNAME("skeleton_changed")); // Needs to unpin AnimationPlayerEditor. } @@ -139,22 +152,32 @@ NodePath SyncedAnimationGraph::get_skeleton() const { return skeleton_path; } +void SyncedAnimationGraph::set_graph_root_node(const Ref &p_animation_node) { + if (graph_root_node != p_animation_node) { + graph_root_node = p_animation_node; + _setup_graph(); + } +} + +Ref SyncedAnimationGraph::get_graph_root_node() const { + return graph_root_node; +} + void SyncedAnimationGraph::_process_graph(double p_delta, bool p_update_only) { - if (root_node == nullptr) { + if (!graph_root_node.is_valid()) { return; } - root_node->activate_inputs(); - root_node->calculate_sync_track(); - root_node->update_time(p_delta); - AnimationData output_data; - root_node->evaluate(graph_context, output_data); + graph_root_node->activate_inputs(); + graph_root_node->calculate_sync_track(); + graph_root_node->update_time(p_delta); + graph_root_node->evaluate(graph_context, graph_output); - _apply_animation_data(output_data); + _apply_animation_data(graph_output); } -void SyncedAnimationGraph::_apply_animation_data(AnimationData output_data) const { - for (KeyValue &K : output_data.track_values) { +void SyncedAnimationGraph::_apply_animation_data(const AnimationData& output_data) const { + for (const KeyValue &K : output_data.track_values) { const AnimationData::TrackValue *track_value = K.value; switch (track_value->type) { case AnimationData::TrackType::TYPE_POSITION_3D: { @@ -219,24 +242,11 @@ void SyncedAnimationGraph::_cleanup_evaluation_context() { } void SyncedAnimationGraph::_setup_graph() { - if (root_node != nullptr) { - _cleanup_graph(); - } - - AnimationSamplerNode *sampler_node = memnew(AnimationSamplerNode); - sampler_node->animation_name = "animation_library/Walk-InPlace"; - - root_node = sampler_node; - - root_node->initialize(graph_context); -} - -void SyncedAnimationGraph::_cleanup_graph() { - if (root_node == nullptr) { + if (graph_context.animation_player == nullptr || graph_context.skeleton_3d == nullptr || !graph_root_node.is_valid()) { return; } - memfree(root_node); + graph_root_node->initialize(graph_context); } SyncedAnimationGraph::SyncedAnimationGraph() { diff --git a/synced_animation_graph.h b/synced_animation_graph.h index 5efd65b..cea3c9f 100644 --- a/synced_animation_graph.h +++ b/synced_animation_graph.h @@ -15,13 +15,8 @@ private: NodePath skeleton_path; GraphEvaluationContext graph_context = {}; - SyncedAnimationNode* root_node = nullptr; - - void set_animation_player(const NodePath &p_path); - NodePath get_animation_player() const; - - void set_skeleton(const NodePath &p_path); - NodePath get_skeleton() const; + Ref graph_root_node = nullptr; + AnimationData graph_output; protected: void _notification(int p_what); @@ -37,11 +32,20 @@ protected: public: void _process_graph(double p_delta, bool p_update_only = false); - void _apply_animation_data(AnimationData output_data) const; + void _apply_animation_data(const AnimationData& output_data) const; void set_active(bool p_active); bool is_active() const; + void set_animation_player(const NodePath &p_path); + NodePath get_animation_player() const; + + void set_skeleton(const NodePath &p_path); + NodePath get_skeleton() const; + + void set_graph_root_node(const Ref &p_animation_node); + Ref get_graph_root_node() const; + void set_callback_mode_process(AnimationMixer::AnimationCallbackModeProcess p_mode); AnimationMixer::AnimationCallbackModeProcess get_callback_mode_process() const; @@ -60,5 +64,4 @@ private: void _cleanup_evaluation_context(); void _setup_graph(); - void _cleanup_graph(); }; diff --git a/synced_animation_node.cpp b/synced_animation_node.cpp index 76efed4..f168842 100644 --- a/synced_animation_node.cpp +++ b/synced_animation_node.cpp @@ -10,11 +10,10 @@ void AnimationSamplerNode::initialize(GraphEvaluationContext &context) { node_time_info.loop_mode = Animation::LOOP_LINEAR; } - void AnimationSamplerNode::evaluate(GraphEvaluationContext &context, AnimationData &output) { const Vector tracks = animation->get_tracks(); Animation::Track *const *tracks_ptr = tracks.ptr(); - // real_t a_length = animation->get_length(); + int count = tracks.size(); for (int i = 0; i < count; i++) { AnimationData::TrackValue *track_value = nullptr; diff --git a/synced_animation_node.h b/synced_animation_node.h index 8fbc967..81fd872 100644 --- a/synced_animation_node.h +++ b/synced_animation_node.h @@ -2,6 +2,7 @@ #include "scene/animation/animation_player.h" +#include "core/io/resource.h" #include "scene/3d/skeleton_3d.h" #include @@ -79,7 +80,9 @@ struct SyncTrack { }; -class SyncedAnimationNode { +class SyncedAnimationNode: public Resource { + GDCLASS(SyncedAnimationNode, Resource); + friend class SyncedAnimationGraph; public: @@ -141,6 +144,8 @@ private: }; class AnimationSamplerNode : public SyncedAnimationNode { + GDCLASS(AnimationSamplerNode, SyncedAnimationNode); + public: StringName animation_name; diff --git a/tests/test_synced_animation_graph.h b/tests/test_synced_animation_graph.h new file mode 100644 index 0000000..974fb12 --- /dev/null +++ b/tests/test_synced_animation_graph.h @@ -0,0 +1,65 @@ +#pragma once + +#include "../synced_animation_graph.h" +#include "scene/main/window.h" +#include "servers/rendering/rendering_server_default.h" + +#include "tests/test_macros.h" + +namespace TestSyncedAnimationGraph { +TEST_CASE("[SceneTree][SyncedAnimationGraph] Simple") { + Node* character_node = memnew(Node); + character_node->set_name("CharacterNode"); + SceneTree::get_singleton()->get_root()->add_child(character_node); + + Skeleton3D *skeleton_node = memnew(Skeleton3D); + skeleton_node->set_name("Skeleton"); + character_node->add_child(skeleton_node); + + skeleton_node->add_bone("Root"); + int hip_bone_index = skeleton_node->add_bone("Hips"); + + AnimationPlayer *player_node = memnew(AnimationPlayer); + player_node->set_name("AnimationPlayer"); + + Ref animation = memnew(Animation); + const int track_index = animation->add_track(Animation::TYPE_POSITION_3D); + CHECK(track_index == 0); + animation->track_insert_key(track_index, 0.0, Vector3(0., 0., 0.)); + animation->track_insert_key(track_index, 1.0, Vector3(1., 2., 3.)); + animation->track_set_path(track_index, NodePath(vformat("%s:%s", skeleton_node->get_path().get_concatenated_names(),"Hips"))); + + Ref animation_library; + animation_library.instantiate(); + animation_library->add_animation("TestAnimation", animation); + + player_node->add_animation_library("animation_library", animation_library); + SceneTree::get_singleton()->get_root()->add_child(player_node); + + SyncedAnimationGraph *synced_animation_graph = memnew(SyncedAnimationGraph); + SceneTree::get_singleton()->get_root()->add_child(synced_animation_graph); + + synced_animation_graph->set_animation_player(player_node->get_path()); + synced_animation_graph->set_skeleton(skeleton_node->get_path()); + + Ref animation_sampler_node; + animation_sampler_node.instantiate(); + animation_sampler_node->animation_name = "animation_library/TestAnimation"; + + synced_animation_graph->set_graph_root_node(animation_sampler_node); + + Vector3 hip_bone_position = skeleton_node->get_bone_global_pose(hip_bone_index).origin; + + CHECK(hip_bone_position.x == doctest::Approx(0.0)); + CHECK(hip_bone_position.y == doctest::Approx(0.0)); + CHECK(hip_bone_position.z == doctest::Approx(0.0)); + + SceneTree::get_singleton()->process(0.01); + + hip_bone_position = skeleton_node->get_bone_global_pose(hip_bone_index).origin; + + CHECK(hip_bone_position.x == doctest::Approx(0.01)); + CHECK(hip_bone_position.y == doctest::Approx(0.02)); + CHECK(hip_bone_position.z == doctest::Approx(0.03)); +} +}