Initial version with working synced blending.

This commit is contained in:
Martin Felis 2025-12-31 18:50:42 +01:00
parent f1641f3ac3
commit 69bb2d7980
3 changed files with 70 additions and 25 deletions

View File

@ -57,11 +57,11 @@ struct SyncTrack {
return -1.f; return -1.f;
} }
float calc_ratio_from_sync_time(float sync_time) const { double calc_ratio_from_sync_time(double sync_time) const {
float interval_ratio = fmodf(sync_time, 1.0f); float interval_ratio = fmod(sync_time, 1.0f);
int interval = int(sync_time - interval_ratio); int interval = int(sync_time - interval_ratio);
return fmodf( return fmod(
interval_start_ratio[interval] + interval_duration_ratio[interval] * interval_ratio, interval_start_ratio[interval] + interval_duration_ratio[interval] * interval_ratio,
1.0f); 1.0f);
} }

View File

@ -202,6 +202,20 @@ bool AnimationSamplerNode::initialize(GraphEvaluationContext &context) {
return false; return false;
} }
if (animation_name == "animation_library/TestAnimationA") {
// Corresponds to the walking animation
node_time_info.sync_track = SyncTrack::create_from_markers(animation->get_length(), { 0.8117, 0.314 });
print_line(vformat("Using hardcoded sync track for animation %s.", animation_name));
} else if (animation_name == "animation_library/TestAnimationB") {
// Corresponds to the running animation
node_time_info.sync_track = SyncTrack::create_from_markers(animation->get_length(), { 0.6256, 0.2721 });
print_line(vformat("Using hardcoded sync track for animation %s.", animation_name));
} else if (animation_name == "animation_library/TestAnimationC") {
// Corresponds to the limping animation
node_time_info.sync_track = SyncTrack::create_from_markers(animation->get_length(), { 0.0674, 1.1047 });
print_line(vformat("Using hardcoded sync track for animation %s.", animation_name));
}
node_time_info.length = animation->get_length(); node_time_info.length = animation->get_length();
node_time_info.loop_mode = Animation::LOOP_LINEAR; node_time_info.loop_mode = Animation::LOOP_LINEAR;
@ -211,8 +225,14 @@ bool AnimationSamplerNode::initialize(GraphEvaluationContext &context) {
void AnimationSamplerNode::evaluate(GraphEvaluationContext &context, const LocalVector<AnimationData *> &inputs, AnimationData &output) { void AnimationSamplerNode::evaluate(GraphEvaluationContext &context, const LocalVector<AnimationData *> &inputs, AnimationData &output) {
assert(inputs.size() == 0); assert(inputs.size() == 0);
double sample_time = node_time_info.position;
if (node_time_info.is_synced) {
sample_time = node_time_info.sync_track.calc_ratio_from_sync_time(node_time_info.sync_position) * animation->get_length();
}
output.clear(); output.clear();
output.sample_from_animation(animation, context.skeleton_3d, node_time_info.position); output.sample_from_animation(animation, context.skeleton_3d, sample_time);
} }
void AnimationSamplerNode::set_animation(const StringName &p_name) { void AnimationSamplerNode::set_animation(const StringName &p_name) {

View File

@ -230,7 +230,7 @@ public:
double sync_delta = 0.0; double sync_delta = 0.0;
bool is_synced = false; bool is_synced = false;
Animation::LoopMode loop_mode = Animation::LOOP_NONE; Animation::LoopMode loop_mode = Animation::LOOP_LINEAR;
SyncTrack sync_track; SyncTrack sync_track;
}; };
NodeTimeInfo node_time_info; NodeTimeInfo node_time_info;
@ -246,6 +246,7 @@ public:
// By default, all inputs nodes are activated. // By default, all inputs nodes are activated.
for (const Ref<SyncedAnimationNode> &node : input_nodes) { for (const Ref<SyncedAnimationNode> &node : input_nodes) {
node->active = true; node->active = true;
node->node_time_info.is_synced = node_time_info.is_synced;
} }
} }
virtual void calculate_sync_track(Vector<Ref<SyncedAnimationNode>> input_nodes) { virtual void calculate_sync_track(Vector<Ref<SyncedAnimationNode>> input_nodes) {
@ -254,25 +255,29 @@ public:
node_time_info.sync_track = input_nodes[0]->node_time_info.sync_track; node_time_info.sync_track = input_nodes[0]->node_time_info.sync_track;
} }
} }
virtual void update_time(double p_delta) { virtual void update_time(double p_time) {
node_time_info.delta = p_delta; if (node_time_info.is_synced) {
node_time_info.position += p_delta; node_time_info.sync_position = p_time;
if (node_time_info.position > node_time_info.length) { } else {
switch (node_time_info.loop_mode) { node_time_info.delta = p_time;
case Animation::LOOP_NONE: { node_time_info.position += p_time;
node_time_info.position = node_time_info.length; if (node_time_info.position > node_time_info.length) {
break; switch (node_time_info.loop_mode) {
} case Animation::LOOP_NONE: {
case Animation::LOOP_LINEAR: { node_time_info.position = node_time_info.length;
assert(node_time_info.length > 0.0); break;
while (node_time_info.position > node_time_info.length) { }
node_time_info.position -= node_time_info.length; 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;
} }
break;
}
case Animation::LOOP_PINGPONG: {
assert(false && !"Not yet implemented.");
break;
} }
} }
} }
@ -336,13 +341,33 @@ class AnimationBlend2Node : public SyncedAnimationNode {
public: public:
StringName blend_amount = PNAME("blend_amount"); StringName blend_amount = PNAME("blend_amount");
float blend_weight = 0.0f; float blend_weight = 0.0f;
bool sync = false; bool sync = true;
void get_input_names(Vector<StringName> &inputs) const override { void get_input_names(Vector<StringName> &inputs) const override {
inputs.push_back("Input0"); inputs.push_back("Input0");
inputs.push_back("Input1"); inputs.push_back("Input1");
} }
void activate_inputs(Vector<Ref<SyncedAnimationNode>> input_nodes) override {
for (const Ref<SyncedAnimationNode> &node : input_nodes) {
node->active = true;
// If this Blend2 node is already synced then inputs are also synced. Otherwise, inputs are only set to synced if synced blending is active in this node.
node->node_time_info.is_synced = node_time_info.is_synced || sync;
}
}
void calculate_sync_track(Vector<Ref<SyncedAnimationNode>> input_nodes) override {
if (node_time_info.is_synced || sync) {
node_time_info.sync_track = SyncTrack::blend(blend_weight, input_nodes[0]->node_time_info.sync_track, input_nodes[1]->node_time_info.sync_track);
node_time_info.length = node_time_info.sync_track.duration;
}
}
void update_time(double p_delta) override {
SyncedAnimationNode::update_time(p_delta);
if (sync && !node_time_info.is_synced) {
node_time_info.sync_position = node_time_info.sync_track.calc_sync_from_abs_time(node_time_info.position);
}
}
void evaluate(GraphEvaluationContext &context, const LocalVector<AnimationData *> &inputs, AnimationData &output) override; void evaluate(GraphEvaluationContext &context, const LocalVector<AnimationData *> &inputs, AnimationData &output) override;
void set_use_sync(bool p_sync); void set_use_sync(bool p_sync);
@ -717,7 +742,7 @@ public:
const Ref<SyncedAnimationNode> &node_parent = tree_graph.nodes[tree_graph.node_connection_info[i].parent_node_index]; const Ref<SyncedAnimationNode> &node_parent = tree_graph.nodes[tree_graph.node_connection_info[i].parent_node_index];
if (node->node_time_info.is_synced) { if (node->node_time_info.is_synced) {
node->update_time(node_parent->node_time_info.position); node->update_time(node_parent->node_time_info.sync_position);
} else { } else {
node->update_time(node_parent->node_time_info.delta); node->update_time(node_parent->node_time_info.delta);
} }