diff --git a/synced_animation_graph.cpp b/synced_animation_graph.cpp index 9c93ee2..41337fa 100644 --- a/synced_animation_graph.cpp +++ b/synced_animation_graph.cpp @@ -31,7 +31,12 @@ void SyncedAnimationGraph::_bind_methods() { void SyncedAnimationGraph::_notification(int p_what) { switch (p_what) { case Node::NOTIFICATION_READY: { - _set_process(true); + _setup_evaluation_context(); + _setup_graph(); + + if (active) { + _set_process(true); + } } break; case Node::NOTIFICATION_INTERNAL_PROCESS: { @@ -108,7 +113,7 @@ void SyncedAnimationGraph::set_animation_player(const NodePath &p_path) { // remove_animation_library(animation_libraries[0].name); // } } - _graph_context.animation_player = Object::cast_to(get_node_or_null(animation_player_path)); + graph_context.animation_player = Object::cast_to(get_node_or_null(animation_player_path)); emit_signal(SNAME("animation_player_changed")); // Needs to unpin AnimationPlayerEditor. } @@ -125,6 +130,8 @@ void SyncedAnimationGraph::set_skeleton(const NodePath &p_path) { // remove_animation_library(animation_libraries[0].name); // } } + graph_context.skeleton_3d = Object::cast_to(get_node_or_null(skeleton_path)); + emit_signal(SNAME("skeleton_changed")); // Needs to unpin AnimationPlayerEditor. } @@ -132,84 +139,60 @@ NodePath SyncedAnimationGraph::get_skeleton() const { return skeleton_path; } -void SyncedAnimationGraph::_ready(const NodePath &p_path) { - print_line(vformat("synced animation graph ready!")); +void SyncedAnimationGraph::_process_graph(double p_delta, bool p_update_only) { + if (root_node == nullptr) { + 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); + + _apply_animation_data(output_data); } -void SyncedAnimationGraph::_process_graph(double p_delta, bool p_update_only) { - if (skeleton_path.is_empty()) { - return; - } +void SyncedAnimationGraph::_apply_animation_data(AnimationData output_data) const { + for (KeyValue &K : output_data.track_values) { + const AnimationData::TrackValue *track_value = K.value; + switch (track_value->type) { + case AnimationData::TrackType::TYPE_POSITION_3D: { + const AnimationData::PositionTrackValue *position_value = static_cast(track_value); - float current_time = Time::get_singleton()->get_unix_time_from_system(); - Skeleton3D *skeleton = Object::cast_to(get_node_or_null(skeleton_path)); - if (!skeleton) { - return; - } + NodePath path = position_value->track->path; - Ref animation = _graph_context.animation_player->get_animation("animation_library/Walk-InPlace"); - if (!animation.is_valid()) { - return; - } - - static double debug_time = 0.; - debug_time += p_delta; - if (debug_time > 2.0) { - debug_time = 0.; - } - - current_time = debug_time; - - // LocalVector &track_num_to_track_cache = animation_track_num_to_track_cache[a]; - 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++) { - const Animation::Track *animation_track = tracks_ptr[i]; - if (!animation_track->enabled) { - continue; - } - - Animation::TrackType ttype = animation_track->type; - switch (ttype) { - case Animation::TYPE_POSITION_3D: { - NodePath path = animation->track_get_path(i); - - double animation_time = Math::fposmod(current_time, animation->get_length()); if (path.get_subname_count() == 1) { - - int bone_idx = skeleton->find_bone(path.get_subname(0)); + int bone_idx = graph_context.skeleton_3d->find_bone(path.get_subname(0)); if (bone_idx != -1) { - Vector3 pos; - animation->try_position_track_interpolate(i, animation_time, &pos); - skeleton->set_bone_pose_position(bone_idx, pos); + graph_context.skeleton_3d->set_bone_pose_position(position_value->bone_idx, position_value->position); } } + break; } - case Animation::TYPE_ROTATION_3D: { - NodePath path = animation->track_get_path(i); + case AnimationData::TrackType::TYPE_ROTATION_3D: { + const AnimationData::RotationTrackValue *rotation_value = static_cast(track_value); + + NodePath path = rotation_value->track->path; - double animation_time = Math::fposmod(current_time, animation->get_length()); if (path.get_subname_count() == 1) { - int bone_idx = skeleton->find_bone(path.get_subname(0)); + int bone_idx = graph_context.skeleton_3d->find_bone(path.get_subname(0)); if (bone_idx != -1) { - Quaternion rot; - animation->try_rotation_track_interpolate(i, animation_time, &rot); - skeleton->set_bone_pose_rotation(bone_idx, rot); + graph_context.skeleton_3d->set_bone_pose_rotation(rotation_value->bone_idx, rotation_value->rotation); } } + break; } default: { + print_line(vformat("Unsupported track type %d", track_value->type)); break; } } } - // skeleton->set_bone_pose_position(3, Vector3(sin(current_time) * 10., 0., 0.)); - skeleton->force_update_all_bone_transforms(); + graph_context.skeleton_3d->force_update_all_bone_transforms(); } void SyncedAnimationGraph::_set_process(bool p_process, bool p_force) { @@ -223,70 +206,38 @@ void SyncedAnimationGraph::_set_process(bool p_process, bool p_force) { processing = p_process; } +void SyncedAnimationGraph::_setup_evaluation_context() { + _cleanup_evaluation_context(); + + graph_context.animation_player = Object::cast_to(get_node_or_null(animation_player_path)); + graph_context.skeleton_3d = Object::cast_to(get_node_or_null(skeleton_path)); +} + +void SyncedAnimationGraph::_cleanup_evaluation_context() { + graph_context.animation_player = nullptr; + graph_context.skeleton_3d = nullptr; +} + +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) { + return; + } + + memfree(root_node); +} + SyncedAnimationGraph::SyncedAnimationGraph() { } - -void AnimationSamplerNode::initialize(GraphEvaluationContext &context) { - animation = context.animation_tree->get_animation(animation_name); -} - - -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; - const Animation::Track *animation_track = tracks_ptr[i]; - const NodePath& track_node_path = animation_track->path; - if (!animation_track->enabled) { - continue; - } - - Animation::TrackType ttype = animation_track->type; - switch (ttype) { - case Animation::TYPE_POSITION_3D: { - AnimationData::PositionTrackValue *position_track_value = memnew(AnimationData::PositionTrackValue); - - if (track_node_path.get_subname_count() == 1) { - int bone_idx = context.skeleton_3d->find_bone(track_node_path.get_subname(0)); - if (bone_idx != -1) { - position_track_value->bone_idx = bone_idx; - animation->try_position_track_interpolate(i, node_time_info.position, &position_track_value->position); - } - } else { - // TODO - assert(false && !"Not yet implemented"); - } - - track_value = position_track_value; - break; - } - case Animation::TYPE_ROTATION_3D: { - AnimationData::RotationTrackValue *rotation_track_value = memnew(AnimationData::RotationTrackValue); - - if (track_node_path.get_subname_count() == 1) { - int bone_idx = context.skeleton_3d->find_bone(track_node_path.get_subname(0)); - if (bone_idx != -1) { - rotation_track_value->bone_idx = bone_idx; - animation->try_rotation_track_interpolate(i, node_time_info.position, &rotation_track_value->rotation); - } - } else { - // TODO - assert(false && !"Not yet implemented"); - } - - track_value = rotation_track_value; - break; - } - default: { - // TODO - assert(false && !"Not yet implemented"); - break; - } - } - - output.set_value(animation_track->thash, track_value); - } -} \ No newline at end of file diff --git a/synced_animation_graph.h b/synced_animation_graph.h index d8e0c55..5efd65b 100644 --- a/synced_animation_graph.h +++ b/synced_animation_graph.h @@ -1,7 +1,7 @@ #pragma once #include "scene/animation/animation_player.h" -#include "scene/animation/animation_tree.h" +#include "synced_animation_node.h" #include @@ -14,14 +14,15 @@ private: NodePath animation_player_path; 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; - // AnimationMixer::TrackCache - protected: void _notification(int p_what); static void _bind_methods(); @@ -36,6 +37,7 @@ protected: public: void _process_graph(double p_delta, bool p_update_only = false); + void _apply_animation_data(AnimationData output_data) const; void set_active(bool p_active); bool is_active() const; @@ -53,153 +55,10 @@ public: private: void _set_process(bool p_process, bool p_force = false); + + void _setup_evaluation_context(); + void _cleanup_evaluation_context(); + + void _setup_graph(); + void _cleanup_graph(); }; - -struct AnimationData { - struct TrackValue { - Animation::Track *track = nullptr; - }; - - struct PositionTrackValue : public TrackValue { - int bone_idx = -1; - Vector3 position = Vector3(0, 0, 0); - }; - - struct RotationTrackValue : public TrackValue { - int bone_idx = -1; - Quaternion rotation = Quaternion(0, 0, 0, 1); - }; - - struct ScaleTrackValue : public TrackValue { - int bone_idx = -1; - Vector3 scale; - }; - - AnimationData() = default; - ~AnimationData() { - _clear_values(); - } - - void set_value(Animation::TypeHash thash, TrackValue *value) { - if (!track_values.has(thash)) { - track_values.insert(thash, value); - } else { - track_values[thash] = value; - } - } - - void clear() { - _clear_values(); - } - - AHashMap track_values; // Animation::Track to TrackValue - -protected: - void _clear_values() { - for (KeyValue &K : track_values) { - memdelete(K.value); - } - } -}; - -struct GraphEvaluationContext { - AnimationTree *animation_tree = nullptr; - AnimationPlayer *animation_player = nullptr; - Skeleton3D *skeleton_3d = nullptr; -}; - -struct SyncTrack { - -}; - -class SyncedAnimationNode { - friend class SyncedAnimationGraph; - -public: - struct NodeTimeInfo { - double length = 0.0; - double position = 0.0; - double sync_position = 0.0; - double delta = 0.0; - double sync_delta = 0.0; - - Animation::LoopMode loop_mode = Animation::LOOP_NONE; - SyncTrack sync_track; - }; - NodeTimeInfo node_time_info; - - struct InputSocket { - StringName name; - SyncedAnimationNode *node; - }; - - Vector input_sockets; - - virtual ~SyncedAnimationNode() = default; - virtual void initialize(GraphEvaluationContext &context) {} - virtual void activate_inputs(GraphEvaluationContext &context, Vector input_names) {} - virtual void calculate_sync_track() {} - virtual void update_time(double p_delta) { - node_time_info.delta = p_delta; - node_time_info.position += p_delta; - if (node_time_info.position > node_time_info.length) { - switch (node_time_info.loop_mode) { - case Animation::LOOP_NONE: { - node_time_info.position = node_time_info.length; - break; - } - case Animation::LOOP_LINEAR: { - assert(node_time_info.length > 0.0); - while (node_time_info.position > node_time_info.length) { - node_time_info.position -= node_time_info.length; - } - break; - } - case Animation::LOOP_PINGPONG: { - assert(false && !"Not yet implemented."); - break; - } - } - } - } - virtual void evaluate(GraphEvaluationContext &context, AnimationData &output) {} - - bool is_active() const { return active; } - bool set_input_node(const StringName &socket_name, SyncedAnimationNode *node); - void get_input_names(Vector &inputs); - -private: - AnimationData *output = nullptr; - bool active = false; -}; - -class AnimationSamplerNode : public SyncedAnimationNode { - StringName animation_name; - Ref animation; - - void initialize(GraphEvaluationContext &context) override; - void evaluate(GraphEvaluationContext &context, AnimationData &output) override; -}; - -class BlendTree : public SyncedAnimationNode { - struct Connection { - const SyncedAnimationNode* source_node = nullptr; - const SyncedAnimationNode* target_node = nullptr; - const StringName target_socket_name = ""; - }; - - Vector nodes; - Vector node_parent; - Vector connections; - -public: - void connect_nodes(const SyncedAnimationNode* source_node, const SyncedAnimationNode* target_node, StringName target_socket_name) { - // TODO - // connections.append(Connection{source_node, target_node, target_socket_name}); - // sort_nodes_by_evaluation_order(); - } - - void sort_nodes_by_evaluation_order() { - // TODO: sort nodes and node_parent s.t. for node i all children have index > i. - } -}; \ No newline at end of file diff --git a/synced_animation_node.cpp b/synced_animation_node.cpp new file mode 100644 index 0000000..76efed4 --- /dev/null +++ b/synced_animation_node.cpp @@ -0,0 +1,73 @@ +// +// Created by martin on 03.12.25. +// + +#include "synced_animation_node.h" + +void AnimationSamplerNode::initialize(GraphEvaluationContext &context) { + animation = context.animation_player->get_animation(animation_name); + node_time_info.length = animation->get_length(); + 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; + const Animation::Track *animation_track = tracks_ptr[i]; + const NodePath& track_node_path = animation_track->path; + if (!animation_track->enabled) { + continue; + } + + Animation::TrackType ttype = animation_track->type; + switch (ttype) { + case Animation::TYPE_POSITION_3D: { + AnimationData::PositionTrackValue *position_track_value = memnew(AnimationData::PositionTrackValue); + + if (track_node_path.get_subname_count() == 1) { + int bone_idx = context.skeleton_3d->find_bone(track_node_path.get_subname(0)); + if (bone_idx != -1) { + position_track_value->bone_idx = bone_idx; + } + animation->try_position_track_interpolate(i, node_time_info.position, &position_track_value->position); + } else { + // TODO + assert(false && !"Not yet implemented"); + } + + track_value = position_track_value; + break; + } + case Animation::TYPE_ROTATION_3D: { + AnimationData::RotationTrackValue *rotation_track_value = memnew(AnimationData::RotationTrackValue); + + if (track_node_path.get_subname_count() == 1) { + int bone_idx = context.skeleton_3d->find_bone(track_node_path.get_subname(0)); + if (bone_idx != -1) { + rotation_track_value->bone_idx = bone_idx; + } + animation->try_rotation_track_interpolate(i, node_time_info.position, &rotation_track_value->rotation); + } else { + // TODO + assert(false && !"Not yet implemented"); + } + + track_value = rotation_track_value; + break; + } + default: { + // TODO + assert(false && !"Not yet implemented"); + break; + } + } + + track_value->track = tracks_ptr[i]; + output.set_value(animation_track->thash, track_value); + } +} \ No newline at end of file diff --git a/synced_animation_node.h b/synced_animation_node.h new file mode 100644 index 0000000..8fbc967 --- /dev/null +++ b/synced_animation_node.h @@ -0,0 +1,175 @@ +#pragma once + +#include "scene/animation/animation_player.h" + +#include "scene/3d/skeleton_3d.h" + +#include + +struct GraphEvaluationContext { + AnimationPlayer *animation_player = nullptr; + Skeleton3D *skeleton_3d = nullptr; +}; + + +struct AnimationData { + enum TrackType : uint8_t { + TYPE_VALUE, // Set a value in a property, can be interpolated. + TYPE_POSITION_3D, // Position 3D track, can be compressed. + TYPE_ROTATION_3D, // Rotation 3D track, can be compressed. + TYPE_SCALE_3D, // Scale 3D track, can be compressed. + TYPE_BLEND_SHAPE, // Blend Shape track, can be compressed. + TYPE_METHOD, // Call any method on a specific node. + TYPE_BEZIER, // Bezier curve. + TYPE_AUDIO, + TYPE_ANIMATION, + }; + + struct TrackValue { + Animation::Track *track = nullptr; + TrackType type = TYPE_ANIMATION; + }; + + struct PositionTrackValue : public TrackValue { + int bone_idx = -1; + Vector3 position = Vector3(0, 0, 0); + PositionTrackValue() { type = TYPE_POSITION_3D; } + }; + + struct RotationTrackValue : public TrackValue { + int bone_idx = -1; + Quaternion rotation = Quaternion(0, 0, 0, 1); + RotationTrackValue() { type = TYPE_ROTATION_3D; } + }; + + struct ScaleTrackValue : public TrackValue { + int bone_idx = -1; + Vector3 scale; + ScaleTrackValue() { type = TYPE_SCALE_3D; } + }; + + AnimationData() = default; + ~AnimationData() { + _clear_values(); + } + + void set_value(Animation::TypeHash thash, TrackValue *value) { + if (!track_values.has(thash)) { + track_values.insert(thash, value); + } else { + track_values[thash] = value; + } + } + + void clear() { + _clear_values(); + } + + AHashMap track_values; // Animation::Track to TrackValue + +protected: + void _clear_values() { + for (KeyValue &K : track_values) { + memdelete(K.value); + } + } +}; + +struct SyncTrack { + +}; + +class SyncedAnimationNode { + friend class SyncedAnimationGraph; + +public: + struct NodeTimeInfo { + double length = 0.0; + double position = 0.0; + double sync_position = 0.0; + double delta = 0.0; + double sync_delta = 0.0; + + Animation::LoopMode loop_mode = Animation::LOOP_NONE; + SyncTrack sync_track; + }; + NodeTimeInfo node_time_info; + + struct InputSocket { + StringName name; + SyncedAnimationNode *node; + }; + + Vector input_sockets; + + virtual ~SyncedAnimationNode() = default; + virtual void initialize(GraphEvaluationContext &context) {} + virtual void activate_inputs() {} + virtual void calculate_sync_track() {} + virtual void update_time(double p_delta) { + node_time_info.delta = p_delta; + node_time_info.position += p_delta; + if (node_time_info.position > node_time_info.length) { + switch (node_time_info.loop_mode) { + case Animation::LOOP_NONE: { + node_time_info.position = node_time_info.length; + break; + } + case Animation::LOOP_LINEAR: { + assert(node_time_info.length > 0.0); + while (node_time_info.position > node_time_info.length) { + node_time_info.position -= node_time_info.length; + } + break; + } + case Animation::LOOP_PINGPONG: { + assert(false && !"Not yet implemented."); + break; + } + } + } + } + virtual void evaluate(GraphEvaluationContext &context, AnimationData &output) {} + + bool is_active() const { return active; } + bool set_input_node(const StringName &socket_name, SyncedAnimationNode *node); + void get_input_names(Vector &inputs); + +private: + AnimationData *output = nullptr; + bool active = false; +}; + +class AnimationSamplerNode : public SyncedAnimationNode { +public: + StringName animation_name; + +private: + Ref animation; + + void initialize(GraphEvaluationContext &context) override; + void evaluate(GraphEvaluationContext &context, AnimationData &output) override; +}; + +class BlendTree : public SyncedAnimationNode { + struct Connection { + const SyncedAnimationNode* source_node = nullptr; + const SyncedAnimationNode* target_node = nullptr; + const StringName target_socket_name = ""; + }; + + Vector nodes; + Vector node_parent; + Vector connections; + +public: + void connect_nodes(const SyncedAnimationNode* source_node, const SyncedAnimationNode* target_node, StringName target_socket_name) { + // TODO + // connections.append(Connection{source_node, target_node, target_socket_name}); + // sort_nodes_by_evaluation_order(); + } + + void sort_nodes_by_evaluation_order() { + // TODO: sort nodes and node_parent s.t. for node i all children have index > i. + } +}; \ No newline at end of file