Added simple unit test that uses an AnimationSamplerNode and a procedural animation.
This commit is contained in:
parent
757c5ee51c
commit
1732ecb8bd
8
SCsub
8
SCsub
@ -2,4 +2,10 @@
|
|||||||
|
|
||||||
Import('env')
|
Import('env')
|
||||||
|
|
||||||
env.add_source_files(env.modules_sources, "*.cpp") # Add all cpp files to the build
|
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")
|
||||||
@ -1,6 +1,5 @@
|
|||||||
def can_build(env, platform):
|
def can_build(env, platform):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def configure(env):
|
def configure(env):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@ -5,8 +5,6 @@
|
|||||||
#include "scene/animation/animation_player.h"
|
#include "scene/animation/animation_player.h"
|
||||||
|
|
||||||
void SyncedAnimationGraph::_bind_methods() {
|
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("set_active", "active"), &SyncedAnimationGraph::set_active);
|
||||||
ClassDB::bind_method(D_METHOD("is_active"), &SyncedAnimationGraph::is_active);
|
ClassDB::bind_method(D_METHOD("is_active"), &SyncedAnimationGraph::is_active);
|
||||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "active"), "set_active", "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());
|
_process_graph(get_physics_process_delta_time());
|
||||||
}
|
}
|
||||||
} break;
|
} 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<AnimationPlayer>(get_node_or_null(animation_player_path));
|
graph_context.animation_player = Object::cast_to<AnimationPlayer>(get_node_or_null(animation_player_path));
|
||||||
|
|
||||||
|
_setup_evaluation_context();
|
||||||
|
_setup_graph();
|
||||||
|
|
||||||
emit_signal(SNAME("animation_player_changed")); // Needs to unpin AnimationPlayerEditor.
|
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<Skeleton3D>(get_node_or_null(skeleton_path));
|
graph_context.skeleton_3d = Object::cast_to<Skeleton3D>(get_node_or_null(skeleton_path));
|
||||||
|
|
||||||
|
_setup_evaluation_context();
|
||||||
|
_setup_graph();
|
||||||
|
|
||||||
emit_signal(SNAME("skeleton_changed")); // Needs to unpin AnimationPlayerEditor.
|
emit_signal(SNAME("skeleton_changed")); // Needs to unpin AnimationPlayerEditor.
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,22 +152,32 @@ NodePath SyncedAnimationGraph::get_skeleton() const {
|
|||||||
return skeleton_path;
|
return skeleton_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SyncedAnimationGraph::set_graph_root_node(const Ref<SyncedAnimationNode> &p_animation_node) {
|
||||||
|
if (graph_root_node != p_animation_node) {
|
||||||
|
graph_root_node = p_animation_node;
|
||||||
|
_setup_graph();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ref<SyncedAnimationNode> SyncedAnimationGraph::get_graph_root_node() const {
|
||||||
|
return graph_root_node;
|
||||||
|
}
|
||||||
|
|
||||||
void SyncedAnimationGraph::_process_graph(double p_delta, bool p_update_only) {
|
void SyncedAnimationGraph::_process_graph(double p_delta, bool p_update_only) {
|
||||||
if (root_node == nullptr) {
|
if (!graph_root_node.is_valid()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
root_node->activate_inputs();
|
graph_root_node->activate_inputs();
|
||||||
root_node->calculate_sync_track();
|
graph_root_node->calculate_sync_track();
|
||||||
root_node->update_time(p_delta);
|
graph_root_node->update_time(p_delta);
|
||||||
AnimationData output_data;
|
graph_root_node->evaluate(graph_context, graph_output);
|
||||||
root_node->evaluate(graph_context, output_data);
|
|
||||||
|
|
||||||
_apply_animation_data(output_data);
|
_apply_animation_data(graph_output);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SyncedAnimationGraph::_apply_animation_data(AnimationData output_data) const {
|
void SyncedAnimationGraph::_apply_animation_data(const AnimationData& output_data) const {
|
||||||
for (KeyValue<Animation::TypeHash, AnimationData::TrackValue *> &K : output_data.track_values) {
|
for (const KeyValue<Animation::TypeHash, AnimationData::TrackValue *> &K : output_data.track_values) {
|
||||||
const AnimationData::TrackValue *track_value = K.value;
|
const AnimationData::TrackValue *track_value = K.value;
|
||||||
switch (track_value->type) {
|
switch (track_value->type) {
|
||||||
case AnimationData::TrackType::TYPE_POSITION_3D: {
|
case AnimationData::TrackType::TYPE_POSITION_3D: {
|
||||||
@ -219,24 +242,11 @@ void SyncedAnimationGraph::_cleanup_evaluation_context() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SyncedAnimationGraph::_setup_graph() {
|
void SyncedAnimationGraph::_setup_graph() {
|
||||||
if (root_node != nullptr) {
|
if (graph_context.animation_player == nullptr || graph_context.skeleton_3d == nullptr || !graph_root_node.is_valid()) {
|
||||||
_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) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
memfree(root_node);
|
graph_root_node->initialize(graph_context);
|
||||||
}
|
}
|
||||||
|
|
||||||
SyncedAnimationGraph::SyncedAnimationGraph() {
|
SyncedAnimationGraph::SyncedAnimationGraph() {
|
||||||
|
|||||||
@ -15,13 +15,8 @@ private:
|
|||||||
NodePath skeleton_path;
|
NodePath skeleton_path;
|
||||||
|
|
||||||
GraphEvaluationContext graph_context = {};
|
GraphEvaluationContext graph_context = {};
|
||||||
SyncedAnimationNode* root_node = nullptr;
|
Ref<SyncedAnimationNode> graph_root_node = nullptr;
|
||||||
|
AnimationData graph_output;
|
||||||
void set_animation_player(const NodePath &p_path);
|
|
||||||
NodePath get_animation_player() const;
|
|
||||||
|
|
||||||
void set_skeleton(const NodePath &p_path);
|
|
||||||
NodePath get_skeleton() const;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void _notification(int p_what);
|
void _notification(int p_what);
|
||||||
@ -37,11 +32,20 @@ protected:
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
void _process_graph(double p_delta, bool p_update_only = false);
|
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);
|
void set_active(bool p_active);
|
||||||
bool is_active() const;
|
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<SyncedAnimationNode> &p_animation_node);
|
||||||
|
Ref<SyncedAnimationNode> get_graph_root_node() const;
|
||||||
|
|
||||||
void set_callback_mode_process(AnimationMixer::AnimationCallbackModeProcess p_mode);
|
void set_callback_mode_process(AnimationMixer::AnimationCallbackModeProcess p_mode);
|
||||||
AnimationMixer::AnimationCallbackModeProcess get_callback_mode_process() const;
|
AnimationMixer::AnimationCallbackModeProcess get_callback_mode_process() const;
|
||||||
|
|
||||||
@ -60,5 +64,4 @@ private:
|
|||||||
void _cleanup_evaluation_context();
|
void _cleanup_evaluation_context();
|
||||||
|
|
||||||
void _setup_graph();
|
void _setup_graph();
|
||||||
void _cleanup_graph();
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -10,11 +10,10 @@ void AnimationSamplerNode::initialize(GraphEvaluationContext &context) {
|
|||||||
node_time_info.loop_mode = Animation::LOOP_LINEAR;
|
node_time_info.loop_mode = Animation::LOOP_LINEAR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void AnimationSamplerNode::evaluate(GraphEvaluationContext &context, AnimationData &output) {
|
void AnimationSamplerNode::evaluate(GraphEvaluationContext &context, AnimationData &output) {
|
||||||
const Vector<Animation::Track *> tracks = animation->get_tracks();
|
const Vector<Animation::Track *> tracks = animation->get_tracks();
|
||||||
Animation::Track *const *tracks_ptr = tracks.ptr();
|
Animation::Track *const *tracks_ptr = tracks.ptr();
|
||||||
// real_t a_length = animation->get_length();
|
|
||||||
int count = tracks.size();
|
int count = tracks.size();
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
AnimationData::TrackValue *track_value = nullptr;
|
AnimationData::TrackValue *track_value = nullptr;
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "scene/animation/animation_player.h"
|
#include "scene/animation/animation_player.h"
|
||||||
|
|
||||||
|
#include "core/io/resource.h"
|
||||||
#include "scene/3d/skeleton_3d.h"
|
#include "scene/3d/skeleton_3d.h"
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
@ -79,7 +80,9 @@ struct SyncTrack {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class SyncedAnimationNode {
|
class SyncedAnimationNode: public Resource {
|
||||||
|
GDCLASS(SyncedAnimationNode, Resource);
|
||||||
|
|
||||||
friend class SyncedAnimationGraph;
|
friend class SyncedAnimationGraph;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@ -141,6 +144,8 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
class AnimationSamplerNode : public SyncedAnimationNode {
|
class AnimationSamplerNode : public SyncedAnimationNode {
|
||||||
|
GDCLASS(AnimationSamplerNode, SyncedAnimationNode);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
StringName animation_name;
|
StringName animation_name;
|
||||||
|
|
||||||
|
|||||||
65
tests/test_synced_animation_graph.h
Normal file
65
tests/test_synced_animation_graph.h
Normal file
@ -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> 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<AnimationLibrary> 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<AnimationSamplerNode> 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user