2025-12-03 22:43:24 +01:00
# pragma once
# include "scene/animation/animation_player.h"
2025-12-05 17:20:35 +01:00
# include "core/io/resource.h"
2025-12-03 22:43:24 +01:00
# include "scene/3d/skeleton_3d.h"
2025-12-31 17:16:19 +01:00
# include "sync_track.h"
2025-12-03 22:43:24 +01:00
# include <cassert>
2025-12-21 18:12:34 +01:00
/**
* @ class AnimationData
* Represents data that is transported via animation connections in the SyncedAnimationGraph .
*
* Essentially , it is a hash map for all Animation : : Track values that can are sampled from an Animation .
*/
2025-12-03 22:43:24 +01:00
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 ;
2025-12-22 00:37:27 +01:00
virtual ~ TrackValue ( ) = default ;
virtual void blend ( const TrackValue & to_value , const float lambda ) {
print_error ( vformat ( " Blending of TrackValue of type %d with TrackValue of type %d not yet implemented. " , type , to_value . type ) ) ;
}
virtual bool operator = = ( const TrackValue & other_value ) const {
print_error ( vformat ( " Comparing TrackValue of type %d with TrackValue of type %d not yet implemented. " , type , other_value . type ) ) ;
return false ;
}
bool operator ! = ( const TrackValue & other_value ) const {
return ! ( * this = = other_value ) ;
}
virtual TrackValue * clone ( ) const {
print_error ( vformat ( " Cannot clone TrackValue of type %d: not yet implemented. " , type ) ) ;
return nullptr ;
}
2025-12-03 22:43:24 +01:00
} ;
struct PositionTrackValue : public TrackValue {
int bone_idx = - 1 ;
Vector3 position = Vector3 ( 0 , 0 , 0 ) ;
PositionTrackValue ( ) { type = TYPE_POSITION_3D ; }
2025-12-22 00:37:27 +01:00
void blend ( const TrackValue & to_value , const float lambda ) override {
const PositionTrackValue * to_value_casted = & static_cast < const PositionTrackValue & > ( to_value ) ;
assert ( bone_idx = = to_value_casted - > bone_idx ) ;
position = ( 1. - lambda ) * position + lambda * to_value_casted - > position ;
}
bool operator = = ( const TrackValue & other_value ) const override {
if ( type ! = other_value . type ) {
return false ;
}
const PositionTrackValue * other_value_casted = & static_cast < const PositionTrackValue & > ( other_value ) ;
return bone_idx = = other_value_casted - > bone_idx & & position = = other_value_casted - > position ;
}
TrackValue * clone ( ) const override {
PositionTrackValue * result = memnew ( PositionTrackValue ) ;
2025-12-27 16:27:54 +01:00
result - > track = track ;
2025-12-22 00:37:27 +01:00
result - > bone_idx = bone_idx ;
result - > position = position ;
return result ;
}
2025-12-03 22:43:24 +01:00
} ;
struct RotationTrackValue : public TrackValue {
int bone_idx = - 1 ;
Quaternion rotation = Quaternion ( 0 , 0 , 0 , 1 ) ;
RotationTrackValue ( ) { type = TYPE_ROTATION_3D ; }
2025-12-22 00:37:27 +01:00
void blend ( const TrackValue & to_value , const float lambda ) override {
const RotationTrackValue * to_value_casted = & static_cast < const RotationTrackValue & > ( to_value ) ;
assert ( bone_idx = = to_value_casted - > bone_idx ) ;
rotation = rotation . slerp ( to_value_casted - > rotation , lambda ) ;
}
bool operator = = ( const TrackValue & other_value ) const override {
if ( type ! = other_value . type ) {
return false ;
}
const RotationTrackValue * other_value_casted = & static_cast < const RotationTrackValue & > ( other_value ) ;
return bone_idx = = other_value_casted - > bone_idx & & rotation = = other_value_casted - > rotation ;
}
TrackValue * clone ( ) const override {
RotationTrackValue * result = memnew ( RotationTrackValue ) ;
2025-12-27 16:27:54 +01:00
result - > track = track ;
2025-12-22 00:37:27 +01:00
result - > bone_idx = bone_idx ;
result - > rotation = rotation ;
return result ;
}
2025-12-03 22:43:24 +01:00
} ;
struct ScaleTrackValue : public TrackValue {
int bone_idx = - 1 ;
Vector3 scale ;
ScaleTrackValue ( ) { type = TYPE_SCALE_3D ; }
} ;
AnimationData ( ) = default ;
~ AnimationData ( ) {
_clear_values ( ) ;
}
2025-12-22 00:37:27 +01:00
AnimationData ( const AnimationData & other ) {
for ( const KeyValue < Animation : : TypeHash , TrackValue * > & K : other . track_values ) {
track_values . insert ( K . key , K . value - > clone ( ) ) ;
}
}
AnimationData ( AnimationData & & other ) noexcept :
track_values ( std : : exchange ( other . track_values , AHashMap < Animation : : TypeHash , TrackValue * , HashHasher > ( ) ) ) {
}
AnimationData & operator = ( const AnimationData & other ) {
AnimationData temp ( other ) ;
std : : swap ( track_values , temp . track_values ) ;
return * this ;
}
AnimationData & operator = ( AnimationData & & other ) noexcept {
std : : swap ( track_values , other . track_values ) ;
return * this ;
}
2025-12-03 22:43:24 +01:00
2025-12-22 00:37:27 +01:00
void
2025-12-27 16:27:54 +01:00
set_value ( const Animation : : TypeHash & thash , TrackValue * value ) {
2025-12-03 22:43:24 +01:00
if ( ! track_values . has ( thash ) ) {
track_values . insert ( thash , value ) ;
} else {
track_values [ thash ] = value ;
}
}
void clear ( ) {
_clear_values ( ) ;
}
2025-12-22 00:37:27 +01:00
bool has_same_tracks ( const AnimationData & other ) const {
HashSet < Animation : : TypeHash > valid_track_hashes ;
for ( const KeyValue < Animation : : TypeHash , TrackValue * > & K : track_values ) {
valid_track_hashes . insert ( K . key ) ;
}
for ( const KeyValue < Animation : : TypeHash , TrackValue * > & K : other . track_values ) {
if ( HashSet < Animation : : TypeHash > : : Iterator entry = valid_track_hashes . find ( K . key ) ) {
valid_track_hashes . remove ( entry ) ;
} else {
return false ;
}
}
return valid_track_hashes . size ( ) = = 0 ;
}
void blend ( const AnimationData & to_data , const float lambda ) {
if ( ! has_same_tracks ( to_data ) ) {
print_error ( " Cannot blend AnimationData: tracks do not match. " ) ;
return ;
}
for ( const KeyValue < Animation : : TypeHash , TrackValue * > & K : track_values ) {
TrackValue * track_value = K . value ;
TrackValue * other_track_value = to_data . track_values [ K . key ] ;
track_value - > blend ( * other_track_value , lambda ) ;
}
}
void sample_from_animation ( const Ref < Animation > & animation , const Skeleton3D * skeleton_3d , double p_time ) ;
2025-12-03 22:43:24 +01:00
AHashMap < Animation : : TypeHash , TrackValue * , HashHasher > track_values ; // Animation::Track to TrackValue
protected :
void _clear_values ( ) {
for ( KeyValue < Animation : : TypeHash , TrackValue * > & K : track_values ) {
memdelete ( K . value ) ;
}
}
} ;
2025-12-19 10:53:19 +01:00
struct GraphEvaluationContext {
AnimationPlayer * animation_player = nullptr ;
Skeleton3D * skeleton_3d = nullptr ;
} ;
2025-12-21 18:12:34 +01:00
/**
* @ class SyncedAnimationNode
* Base class for all nodes in an SyncedAnimationGraph including BlendTree nodes and StateMachine states .
*/
2025-12-12 10:44:18 +01:00
class SyncedAnimationNode : public Resource {
2025-12-05 17:20:35 +01:00
GDCLASS ( SyncedAnimationNode , Resource ) ;
2025-12-03 22:43:24 +01:00
friend class SyncedAnimationGraph ;
2025-12-31 13:47:45 +01:00
protected :
static void _bind_methods ( ) ;
virtual void get_parameter_list ( List < PropertyInfo > * r_list ) const ;
virtual Variant get_parameter_default_value ( const StringName & p_parameter ) const ;
virtual bool is_parameter_read_only ( const StringName & p_parameter ) const ;
virtual void set_parameter ( const StringName & p_name , const Variant & p_value ) ;
virtual Variant get_parameter ( const StringName & p_name ) const ;
virtual void _tree_changed ( ) ;
virtual void _animation_node_renamed ( const ObjectID & p_oid , const String & p_old_name , const String & p_new_name ) ;
virtual void _animation_node_removed ( const ObjectID & p_oid , const StringName & p_node ) ;
2025-12-03 22:43:24 +01:00
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 ;
2025-12-19 10:53:19 +01:00
bool is_synced = false ;
2025-12-03 22:43:24 +01:00
2025-12-31 18:50:42 +01:00
Animation : : LoopMode loop_mode = Animation : : LOOP_LINEAR ;
2025-12-03 22:43:24 +01:00
SyncTrack sync_track ;
} ;
NodeTimeInfo node_time_info ;
2025-12-21 18:12:34 +01:00
bool active = false ;
2025-12-03 22:43:24 +01:00
2025-12-08 22:47:00 +01:00
StringName name ;
2025-12-29 15:25:10 +01:00
Vector2 position ;
2025-12-08 22:47:00 +01:00
2025-12-21 18:12:34 +01:00
virtual ~ SyncedAnimationNode ( ) override = default ;
2025-12-31 13:47:45 +01:00
virtual bool initialize ( GraphEvaluationContext & context ) { return true ; }
2025-12-21 18:12:34 +01:00
2025-12-13 22:38:45 +01:00
virtual void activate_inputs ( Vector < Ref < SyncedAnimationNode > > input_nodes ) {
// By default, all inputs nodes are activated.
2025-12-21 18:12:34 +01:00
for ( const Ref < SyncedAnimationNode > & node : input_nodes ) {
2025-12-13 22:38:45 +01:00
node - > active = true ;
2025-12-31 18:50:42 +01:00
node - > node_time_info . is_synced = node_time_info . is_synced ;
2025-12-13 22:38:45 +01:00
}
}
virtual void calculate_sync_track ( Vector < Ref < SyncedAnimationNode > > input_nodes ) {
// By default, use the SyncTrack of the first input.
if ( input_nodes . size ( ) > 0 ) {
node_time_info . sync_track = input_nodes [ 0 ] - > node_time_info . sync_track ;
}
}
2025-12-31 18:50:42 +01:00
virtual void update_time ( double p_time ) {
if ( node_time_info . is_synced ) {
node_time_info . sync_position = p_time ;
} else {
node_time_info . delta = p_time ;
node_time_info . position + = p_time ;
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 ;
2025-12-03 22:43:24 +01:00
}
}
}
}
}
2025-12-27 16:27:54 +01:00
virtual void evaluate ( GraphEvaluationContext & context , const LocalVector < AnimationData * > & input_datas , AnimationData & output_data ) {
2025-12-13 22:38:45 +01:00
// By default, use the AnimationData of the first input.
if ( input_datas . size ( ) > 0 ) {
output_data = * input_datas [ 0 ] ;
}
}
2025-12-03 22:43:24 +01:00
bool set_input_node ( const StringName & socket_name , SyncedAnimationNode * node ) ;
2025-12-12 10:44:18 +01:00
virtual void get_input_names ( Vector < StringName > & inputs ) const { }
2025-12-31 13:47:45 +01:00
int get_input_index ( const StringName & port_name ) const {
2025-12-12 10:44:18 +01:00
Vector < StringName > inputs ;
get_input_names ( inputs ) ;
return inputs . find ( port_name ) ;
}
2025-12-31 13:47:45 +01:00
int get_input_count ( ) const {
2025-12-12 10:44:18 +01:00
Vector < StringName > inputs ;
get_input_names ( inputs ) ;
return inputs . size ( ) ;
}
2025-12-31 13:47:45 +01:00
// Creates a list of nodes nested within the current node. E.g. all nodes within a BlendTree node.
virtual void get_child_nodes ( List < Ref < SyncedAnimationNode > > * r_child_nodes ) const { }
2025-12-03 22:43:24 +01:00
} ;
class AnimationSamplerNode : public SyncedAnimationNode {
2025-12-05 17:20:35 +01:00
GDCLASS ( AnimationSamplerNode , SyncedAnimationNode ) ;
2025-12-03 22:43:24 +01:00
public :
StringName animation_name ;
2025-12-29 15:25:10 +01:00
void set_animation ( const StringName & p_name ) ;
StringName get_animation ( ) const ;
2025-12-03 22:43:24 +01:00
private :
Ref < Animation > animation ;
2025-12-31 13:47:45 +01:00
bool initialize ( GraphEvaluationContext & context ) override ;
2025-12-27 16:27:54 +01:00
void evaluate ( GraphEvaluationContext & context , const LocalVector < AnimationData * > & inputs , AnimationData & output ) override ;
2025-12-29 15:25:10 +01:00
protected :
static void _bind_methods ( ) ;
2025-12-08 22:47:00 +01:00
} ;
class OutputNode : public SyncedAnimationNode {
2025-12-29 15:25:10 +01:00
GDCLASS ( OutputNode , SyncedAnimationNode ) ;
2025-12-08 22:47:00 +01:00
public :
2025-12-12 10:44:18 +01:00
void get_input_names ( Vector < StringName > & inputs ) const override {
2025-12-08 22:47:00 +01:00
inputs . push_back ( " Input " ) ;
}
2025-12-03 22:43:24 +01:00
} ;
2025-12-10 09:22:33 +01:00
class AnimationBlend2Node : public SyncedAnimationNode {
2025-12-29 15:25:10 +01:00
GDCLASS ( AnimationBlend2Node , SyncedAnimationNode ) ;
2025-12-10 09:22:33 +01:00
public :
2025-12-29 15:25:10 +01:00
StringName blend_amount = PNAME ( " blend_amount " ) ;
2025-12-22 00:37:27 +01:00
float blend_weight = 0.0f ;
2025-12-31 18:50:42 +01:00
bool sync = true ;
2025-12-22 00:37:27 +01:00
2025-12-12 10:44:18 +01:00
void get_input_names ( Vector < StringName > & inputs ) const override {
2025-12-10 09:22:33 +01:00
inputs . push_back ( " Input0 " ) ;
inputs . push_back ( " Input1 " ) ;
}
2025-12-31 18:50:42 +01:00
void activate_inputs ( Vector < Ref < SyncedAnimationNode > > input_nodes ) override {
for ( const Ref < SyncedAnimationNode > & node : input_nodes ) {
node - > active = true ;
2025-12-22 00:37:27 +01:00
2025-12-31 18:50:42 +01:00
// 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 ) ;
}
}
2025-12-27 16:27:54 +01:00
void evaluate ( GraphEvaluationContext & context , const LocalVector < AnimationData * > & inputs , AnimationData & output ) override ;
2025-12-29 15:25:10 +01:00
void set_use_sync ( bool p_sync ) ;
bool is_using_sync ( ) const ;
protected :
static void _bind_methods ( ) ;
2025-12-31 13:47:45 +01:00
void get_parameter_list ( List < PropertyInfo > * p_list ) const override ;
Variant get_parameter_default_value ( const StringName & p_parameter ) const override ;
void set_parameter ( const StringName & p_name , const Variant & p_value ) override ;
Variant get_parameter ( const StringName & p_name ) const override ;
2025-12-29 15:25:10 +01:00
void _get_property_list ( List < PropertyInfo > * p_list ) const ;
bool _get ( const StringName & p_name , Variant & r_value ) const ;
bool _set ( const StringName & p_name , const Variant & p_value ) ;
2025-12-10 09:22:33 +01:00
} ;
struct BlendTreeConnection {
const Ref < SyncedAnimationNode > source_node = nullptr ;
const Ref < SyncedAnimationNode > target_node = nullptr ;
const StringName target_port_name = " " ;
} ;
2025-12-13 22:38:45 +01:00
/**
2025-12-29 15:56:29 +01:00
* @ class BlendTreeGraph
2025-12-13 22:38:45 +01:00
* Helper class that is used to build runtime blend trees and also to validate connections .
*/
2025-12-29 15:56:29 +01:00
struct BlendTreeGraph {
2025-12-12 10:44:18 +01:00
struct NodeConnectionInfo {
int parent_node_index = - 1 ;
2025-12-13 22:38:45 +01:00
HashSet < int > input_subtree_node_indices ; // Contains all nodes down to the tree leaves that influence this node.
LocalVector < int > connected_child_node_index_at_port ; // Contains for each input port the index of the node that is connected to it.
2025-12-12 10:44:18 +01:00
NodeConnectionInfo ( ) = default ;
explicit NodeConnectionInfo ( const SyncedAnimationNode * node ) {
parent_node_index = - 1 ;
2025-12-31 13:47:45 +01:00
for ( int i = 0 ; i < node - > get_input_count ( ) ; i + + ) {
2025-12-12 10:44:18 +01:00
connected_child_node_index_at_port . push_back ( - 1 ) ;
}
}
void _print_subtree ( ) const {
String result = vformat ( " subtree node indices (%d): " , input_subtree_node_indices . size ( ) ) ;
bool is_first = true ;
for ( int index : input_subtree_node_indices ) {
if ( is_first ) {
result + = vformat ( " %d " , index ) ;
is_first = false ;
} else {
result + = vformat ( " , %d " , index ) ;
}
}
print_line ( result ) ;
}
2025-12-13 22:38:45 +01:00
2025-12-21 18:12:34 +01:00
void apply_node_mapping ( const LocalVector < int > & node_index_mapping ) {
2025-12-13 22:38:45 +01:00
// Map connected node indices
for ( unsigned int j = 0 ; j < connected_child_node_index_at_port . size ( ) ; j + + ) {
int connected_node_index = connected_child_node_index_at_port [ j ] ;
connected_child_node_index_at_port [ j ] = node_index_mapping . find ( connected_node_index ) ;
}
// Map connected subtrees
HashSet < int > old_indices = input_subtree_node_indices ;
input_subtree_node_indices . clear ( ) ;
2025-12-19 10:53:19 +01:00
for ( int old_index : old_indices ) {
2025-12-13 22:38:45 +01:00
input_subtree_node_indices . insert ( node_index_mapping . find ( old_index ) ) ;
}
}
2025-12-12 10:44:18 +01:00
} ;
Vector < Ref < SyncedAnimationNode > > nodes ; // All added nodes
LocalVector < NodeConnectionInfo > node_connection_info ;
2025-12-29 15:25:10 +01:00
LocalVector < BlendTreeConnection > connections ;
2025-12-08 22:47:00 +01:00
2025-12-29 15:56:29 +01:00
BlendTreeGraph ( ) {
2025-12-10 09:22:33 +01:00
Ref < OutputNode > output_node ;
output_node . instantiate ( ) ;
output_node - > name = " Output " ;
add_node ( output_node ) ;
}
2025-12-03 22:43:24 +01:00
2025-12-12 10:44:18 +01:00
Ref < SyncedAnimationNode > get_output_node ( ) const {
2025-12-10 09:22:33 +01:00
return nodes [ 0 ] ;
}
2025-12-03 22:43:24 +01:00
2025-12-29 15:25:10 +01:00
int find_node_index ( const Ref < SyncedAnimationNode > & node ) const {
2025-12-08 22:47:00 +01:00
for ( int i = 0 ; i < nodes . size ( ) ; i + + ) {
2025-12-10 09:22:33 +01:00
if ( nodes [ i ] = = node ) {
return i ;
}
}
return - 1 ;
}
2025-12-29 15:25:10 +01:00
int find_node_index_by_name ( const StringName & name ) const {
for ( int i = 0 ; i < nodes . size ( ) ; i + + ) {
if ( nodes [ i ] - > name = = name ) {
return i ;
}
}
return - 1 ;
}
2025-12-12 10:44:18 +01:00
void add_node ( const Ref < SyncedAnimationNode > & node ) {
2025-12-29 15:25:10 +01:00
StringName node_base_name = node - > name ;
if ( node_base_name . is_empty ( ) ) {
node_base_name = node - > get_class_name ( ) ;
}
node - > name = node_base_name ;
int number_suffix = 1 ;
while ( find_node_index_by_name ( node - > name ) ! = - 1 ) {
node - > name = vformat ( " %s %d " , node_base_name , number_suffix ) ;
number_suffix + + ;
}
2025-12-10 09:22:33 +01:00
nodes . push_back ( node ) ;
2025-12-12 10:44:18 +01:00
node_connection_info . push_back ( NodeConnectionInfo ( node . ptr ( ) ) ) ;
2025-12-10 09:22:33 +01:00
}
2025-12-13 22:38:45 +01:00
void sort_nodes_and_references ( ) {
LocalVector < int > sorted_node_indices = get_sorted_node_indices ( ) ;
Vector < Ref < SyncedAnimationNode > > sorted_nodes ;
Vector < NodeConnectionInfo > old_node_connection_info = node_connection_info ;
for ( unsigned int i = 0 ; i < sorted_node_indices . size ( ) ; i + + ) {
int node_index = sorted_node_indices [ i ] ;
sorted_nodes . push_back ( nodes [ node_index ] ) ;
node_connection_info [ i ] = old_node_connection_info [ node_index ] ;
}
nodes = sorted_nodes ;
2025-12-19 10:53:19 +01:00
for ( NodeConnectionInfo & connection_info : node_connection_info ) {
2025-12-21 18:12:34 +01:00
if ( connection_info . parent_node_index ! = - 1 ) {
connection_info . parent_node_index = sorted_node_indices [ connection_info . parent_node_index ] ;
}
2025-12-13 22:38:45 +01:00
connection_info . apply_node_mapping ( sorted_node_indices ) ;
}
}
LocalVector < int > get_sorted_node_indices ( ) {
LocalVector < int > result ;
sort_nodes_recursive ( 0 , result ) ;
result . reverse ( ) ;
return result ;
}
void sort_nodes_recursive ( int node_index , LocalVector < int > & result ) {
for ( int input_node_index : node_connection_info [ node_index ] . connected_child_node_index_at_port ) {
2025-12-19 10:53:19 +01:00
if ( input_node_index > = 0 ) {
sort_nodes_recursive ( input_node_index , result ) ;
}
2025-12-13 22:38:45 +01:00
}
result . push_back ( node_index ) ;
}
2025-12-12 10:44:18 +01:00
void add_index_and_update_subtrees_recursive ( int node , int node_parent ) {
if ( node_parent = = - 1 ) {
return ;
2025-12-10 09:22:33 +01:00
}
2025-12-12 10:44:18 +01:00
node_connection_info [ node_parent ] . input_subtree_node_indices . insert ( node ) ;
for ( int index : node_connection_info [ node ] . input_subtree_node_indices ) {
node_connection_info [ node_parent ] . input_subtree_node_indices . insert ( index ) ;
}
add_index_and_update_subtrees_recursive ( node_parent , node_connection_info [ node_parent ] . parent_node_index ) ;
}
bool add_connection ( const Ref < SyncedAnimationNode > & source_node , const Ref < SyncedAnimationNode > & target_node , const StringName & target_port_name ) {
if ( ! is_connection_valid ( source_node , target_node , target_port_name ) ) {
2025-12-10 09:22:33 +01:00
return false ;
}
2025-12-29 15:25:10 +01:00
int source_node_index = find_node_index ( source_node ) ;
int target_node_index = find_node_index ( target_node ) ;
2025-12-31 13:47:45 +01:00
int target_input_port_index = target_node - > get_input_index ( target_port_name ) ;
2025-12-12 10:44:18 +01:00
node_connection_info [ source_node_index ] . parent_node_index = target_node_index ;
node_connection_info [ target_node_index ] . connected_child_node_index_at_port [ target_input_port_index ] = source_node_index ;
2025-12-29 15:25:10 +01:00
connections . push_back ( BlendTreeConnection { source_node , target_node , target_port_name } ) ;
2025-12-12 10:44:18 +01:00
add_index_and_update_subtrees_recursive ( source_node_index , target_node_index ) ;
2025-12-10 09:22:33 +01:00
return true ;
}
2025-12-12 10:44:18 +01:00
bool is_connection_valid ( const Ref < SyncedAnimationNode > & source_node , const Ref < SyncedAnimationNode > & target_node , StringName target_port_name ) {
2025-12-29 15:25:10 +01:00
int source_node_index = find_node_index ( source_node ) ;
2025-12-12 10:44:18 +01:00
if ( source_node_index = = - 1 ) {
2025-12-10 09:22:33 +01:00
print_error ( " Cannot connect nodes: source node not found. " ) ;
return false ;
}
2025-12-12 10:44:18 +01:00
if ( node_connection_info [ source_node_index ] . parent_node_index ! = - 1 ) {
print_error ( " Cannot connect node: source node already has a parent. " ) ;
return false ;
}
2025-12-29 15:25:10 +01:00
int target_node_index = find_node_index ( target_node ) ;
2025-12-12 10:44:18 +01:00
if ( target_node_index = = - 1 ) {
2025-12-10 09:22:33 +01:00
print_error ( " Cannot connect nodes: target node not found. " ) ;
return false ;
2025-12-08 22:47:00 +01:00
}
2025-12-10 09:22:33 +01:00
Vector < StringName > target_inputs ;
target_node - > get_input_names ( target_inputs ) ;
if ( ! target_inputs . has ( target_port_name ) ) {
print_error ( " Cannot connect nodes: target port not found. " ) ;
return false ;
}
2025-12-31 13:47:45 +01:00
int target_input_port_index = target_node - > get_input_index ( target_port_name ) ;
2025-12-12 10:44:18 +01:00
if ( node_connection_info [ target_node_index ] . connected_child_node_index_at_port [ target_input_port_index ] ! = - 1 ) {
print_error ( " Cannot connect node: target port already connected " ) ;
return false ;
}
if ( node_connection_info [ source_node_index ] . input_subtree_node_indices . has ( target_node_index ) ) {
print_error ( " Cannot connect node: connection would create loop. " ) ;
return false ;
}
2025-12-10 09:22:33 +01:00
return true ;
2025-12-08 22:47:00 +01:00
}
2025-12-10 09:22:33 +01:00
} ;
class SyncedBlendTree : public SyncedAnimationNode {
2025-12-29 15:25:10 +01:00
GDCLASS ( SyncedBlendTree , SyncedAnimationNode ) ;
2025-12-29 15:56:29 +01:00
BlendTreeGraph tree_graph ;
2025-12-13 22:38:45 +01:00
bool tree_initialized = false ;
2025-12-27 16:27:54 +01:00
void sort_nodes ( ) {
2025-12-13 22:38:45 +01:00
_node_runtime_data . clear ( ) ;
2025-12-29 15:56:29 +01:00
tree_graph . sort_nodes_and_references ( ) ;
2025-12-27 16:27:54 +01:00
}
2025-12-13 22:38:45 +01:00
2025-12-27 16:27:54 +01:00
void setup_runtime_data ( ) {
2025-12-13 22:38:45 +01:00
// Add nodes and allocate runtime data
2025-12-29 15:56:29 +01:00
for ( int i = 0 ; i < tree_graph . nodes . size ( ) ; i + + ) {
const Ref < SyncedAnimationNode > node = tree_graph . nodes [ i ] ;
2025-12-13 22:38:45 +01:00
NodeRuntimeData node_runtime_data ;
2025-12-31 13:47:45 +01:00
for ( int ni = 0 ; ni < node - > get_input_count ( ) ; ni + + ) {
2025-12-13 22:38:45 +01:00
node_runtime_data . input_data . push_back ( nullptr ) ;
}
node_runtime_data . output_data = nullptr ;
_node_runtime_data . push_back ( node_runtime_data ) ;
}
// Populate runtime data (only now is this.nodes populated to retrieve the nodes)
2025-12-29 15:56:29 +01:00
for ( int i = 0 ; i < tree_graph . nodes . size ( ) ; i + + ) {
Ref < SyncedAnimationNode > node = tree_graph . nodes [ i ] ;
2025-12-19 10:53:19 +01:00
NodeRuntimeData & node_runtime_data = _node_runtime_data [ i ] ;
2025-12-13 22:38:45 +01:00
2025-12-31 13:47:45 +01:00
for ( int port_index = 0 ; port_index < node - > get_input_count ( ) ; port_index + + ) {
2025-12-29 15:56:29 +01:00
const int connected_node_index = tree_graph . node_connection_info [ i ] . connected_child_node_index_at_port [ port_index ] ;
node_runtime_data . input_nodes . push_back ( tree_graph . nodes [ connected_node_index ] ) ;
2025-12-13 22:38:45 +01:00
}
}
2025-12-10 09:22:33 +01:00
}
2025-12-29 15:25:10 +01:00
protected :
void _get_property_list ( List < PropertyInfo > * p_list ) const ;
bool _get ( const StringName & p_name , Variant & r_value ) const ;
bool _set ( const StringName & p_name , const Variant & p_value ) ;
2025-12-03 22:43:24 +01:00
public :
2025-12-27 16:27:54 +01:00
struct NodeRuntimeData {
Vector < Ref < SyncedAnimationNode > > input_nodes ;
LocalVector < AnimationData * > input_data ;
AnimationData * output_data = nullptr ;
} ;
LocalVector < NodeRuntimeData > _node_runtime_data ;
2025-12-19 10:53:19 +01:00
Ref < SyncedAnimationNode > get_output_node ( ) const {
2025-12-29 15:56:29 +01:00
return tree_graph . nodes [ 0 ] ;
2025-12-08 22:47:00 +01:00
}
2025-12-29 15:25:10 +01:00
int find_node_index ( const Ref < SyncedAnimationNode > & node ) const {
2025-12-29 15:56:29 +01:00
return tree_graph . find_node_index ( node ) ;
2025-12-29 15:25:10 +01:00
}
2025-12-08 22:47:00 +01:00
2025-12-29 15:25:10 +01:00
int find_node_index_by_name ( const StringName & name ) const {
2025-12-29 15:56:29 +01:00
return tree_graph . find_node_index_by_name ( name ) ;
2025-12-08 22:47:00 +01:00
}
2025-12-13 22:38:45 +01:00
void add_node ( const Ref < SyncedAnimationNode > & node ) {
if ( tree_initialized ) {
print_error ( " Cannot add node to BlendTree: BlendTree already initialized. " ) ;
return ;
}
2025-12-29 15:56:29 +01:00
tree_graph . add_node ( node ) ;
2025-12-13 22:38:45 +01:00
}
bool add_connection ( const Ref < SyncedAnimationNode > & source_node , const Ref < SyncedAnimationNode > & target_node , const StringName & target_port_name ) {
if ( tree_initialized ) {
print_error ( " Cannot add connection to BlendTree: BlendTree already initialized. " ) ;
return false ;
}
2025-12-29 15:56:29 +01:00
return tree_graph . add_connection ( source_node , target_node , target_port_name ) ;
2025-12-08 22:47:00 +01:00
}
// overrides from SyncedAnimationNode
2025-12-31 13:47:45 +01:00
bool initialize ( GraphEvaluationContext & context ) override {
2025-12-27 16:27:54 +01:00
sort_nodes ( ) ;
setup_runtime_data ( ) ;
2025-12-19 10:53:19 +01:00
2025-12-29 15:56:29 +01:00
for ( const Ref < SyncedAnimationNode > & node : tree_graph . nodes ) {
2025-12-31 13:47:45 +01:00
if ( ! node - > initialize ( context ) ) {
return false ;
}
2025-12-08 22:47:00 +01:00
}
2025-12-27 16:27:54 +01:00
tree_initialized = true ;
2025-12-31 13:47:45 +01:00
return true ;
2025-12-08 22:47:00 +01:00
}
2025-12-13 22:38:45 +01:00
void activate_inputs ( Vector < Ref < SyncedAnimationNode > > input_nodes ) override {
2025-12-29 15:56:29 +01:00
tree_graph . nodes [ 0 ] - > active = true ;
for ( int i = 0 ; i < tree_graph . nodes . size ( ) ; i + + ) {
const Ref < SyncedAnimationNode > & node = tree_graph . nodes [ i ] ;
2025-12-13 22:38:45 +01:00
if ( ! node - > active ) {
continue ;
}
2025-12-19 10:53:19 +01:00
const NodeRuntimeData & node_runtime_data = _node_runtime_data [ i ] ;
2025-12-13 22:38:45 +01:00
node - > activate_inputs ( node_runtime_data . input_nodes ) ;
}
2025-12-08 22:47:00 +01:00
}
2025-12-13 22:38:45 +01:00
void calculate_sync_track ( Vector < Ref < SyncedAnimationNode > > input_nodes ) override {
2025-12-29 15:56:29 +01:00
for ( int i = tree_graph . nodes . size ( ) - 1 ; i > 0 ; i - - ) {
const Ref < SyncedAnimationNode > & node = tree_graph . nodes [ i ] ;
2025-12-13 22:38:45 +01:00
if ( ! node - > active ) {
continue ;
}
2025-12-19 10:53:19 +01:00
const NodeRuntimeData & node_runtime_data = _node_runtime_data [ i ] ;
2025-12-13 22:38:45 +01:00
node - > calculate_sync_track ( node_runtime_data . input_nodes ) ;
}
2025-12-08 22:47:00 +01:00
}
void update_time ( double p_delta ) override {
2025-12-29 15:56:29 +01:00
tree_graph . nodes [ 0 ] - > node_time_info . delta = p_delta ;
tree_graph . nodes [ 0 ] - > node_time_info . position + = p_delta ;
2025-12-19 10:53:19 +01:00
2025-12-29 15:56:29 +01:00
for ( int i = 1 ; i < tree_graph . nodes . size ( ) ; i + + ) {
const Ref < SyncedAnimationNode > & node = tree_graph . nodes [ i ] ;
2025-12-13 22:38:45 +01:00
if ( ! node - > active ) {
continue ;
}
2025-12-29 15:56:29 +01:00
const Ref < SyncedAnimationNode > & node_parent = tree_graph . nodes [ tree_graph . node_connection_info [ i ] . parent_node_index ] ;
2025-12-13 22:38:45 +01:00
2025-12-19 10:53:19 +01:00
if ( node - > node_time_info . is_synced ) {
2025-12-31 18:50:42 +01:00
node - > update_time ( node_parent - > node_time_info . sync_position ) ;
2025-12-19 10:53:19 +01:00
} else {
node - > update_time ( node_parent - > node_time_info . delta ) ;
}
2025-12-13 22:38:45 +01:00
}
2025-12-08 22:47:00 +01:00
}
2025-12-27 16:27:54 +01:00
void evaluate ( GraphEvaluationContext & context , const LocalVector < AnimationData * > & input_datas , AnimationData & output_data ) override {
2025-12-29 15:56:29 +01:00
for ( int i = tree_graph . nodes . size ( ) - 1 ; i > 0 ; i - - ) {
const Ref < SyncedAnimationNode > & node = tree_graph . nodes [ i ] ;
2025-12-19 10:53:19 +01:00
if ( ! node - > active ) {
continue ;
}
NodeRuntimeData & node_runtime_data = _node_runtime_data [ i ] ;
2025-12-27 16:27:54 +01:00
// Populate the inputs
for ( unsigned int j = 0 ; j < node_runtime_data . input_data . size ( ) ; j + + ) {
2025-12-29 15:56:29 +01:00
int child_index = tree_graph . node_connection_info [ i ] . connected_child_node_index_at_port [ j ] ;
2025-12-27 16:27:54 +01:00
node_runtime_data . input_data [ j ] = _node_runtime_data [ child_index ] . output_data ;
}
// Set output pointer
2025-12-19 10:53:19 +01:00
if ( i = = 1 ) {
node_runtime_data . output_data = & output_data ;
} else {
node_runtime_data . output_data = memnew ( AnimationData ) ;
}
2025-12-27 16:27:54 +01:00
2025-12-19 10:53:19 +01:00
node - > evaluate ( context , node_runtime_data . input_data , * node_runtime_data . output_data ) ;
2025-12-27 16:27:54 +01:00
// All inputs have been consumed and can now be freed.
2025-12-29 15:56:29 +01:00
for ( const int child_index : tree_graph . node_connection_info [ i ] . connected_child_node_index_at_port ) {
2025-12-19 10:53:19 +01:00
memfree ( _node_runtime_data [ child_index ] . output_data ) ;
}
}
2025-12-08 22:47:00 +01:00
}
2025-12-31 13:47:45 +01:00
void get_child_nodes ( List < Ref < SyncedAnimationNode > > * r_child_nodes ) const override {
for ( const Ref < SyncedAnimationNode > & node : tree_graph . nodes ) {
r_child_nodes - > push_back ( node . ptr ( ) ) ;
}
}
2025-12-07 22:09:28 +01:00
} ;