2025-12-03 22:43:24 +01:00
# pragma once
2025-12-05 17:20:35 +01:00
# include "core/io/resource.h"
2026-01-16 09:51:49 +01:00
# include "core/profiling/profiling.h"
2025-12-03 22:43:24 +01:00
# include "scene/3d/skeleton_3d.h"
2026-01-16 09:51:49 +01:00
# include "scene/animation/animation_player.h"
2026-01-30 15:33:27 +01:00
# include "scene/resources/animation_library.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 .
*
2026-01-16 15:27:33 +01:00
* In general AnimationData objects should be obtained using the AnimationDataAllocator .
*
* The class consists of a buffer containing the data and a hashmap that resolves the
* Animation : : TypeHash of an Animation : : Track to the corresponding AnimationData : : TrackValue
* block within the buffer .
2025-12-21 18:12:34 +01:00
*/
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 {
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
} ;
2025-12-31 20:30:28 +01:00
struct TransformTrackValue : public TrackValue {
2025-12-03 22:43:24 +01:00
int bone_idx = - 1 ;
2025-12-31 20:30:28 +01:00
bool loc_used = false ;
bool rot_used = false ;
bool scale_used = false ;
Vector3 init_loc = Vector3 ( 0 , 0 , 0 ) ;
Quaternion init_rot = Quaternion ( 0 , 0 , 0 , 1 ) ;
Vector3 init_scale = Vector3 ( 1 , 1 , 1 ) ;
Vector3 loc ;
Quaternion rot ;
Vector3 scale ;
TransformTrackValue ( ) { type = TYPE_POSITION_3D ; }
2025-12-22 00:37:27 +01:00
void blend ( const TrackValue & to_value , const float lambda ) override {
2025-12-31 20:30:28 +01:00
const TransformTrackValue * to_value_casted = & static_cast < const TransformTrackValue & > ( to_value ) ;
2025-12-22 00:37:27 +01:00
assert ( bone_idx = = to_value_casted - > bone_idx ) ;
2025-12-31 20:30:28 +01:00
if ( loc_used ) {
loc = ( 1. - lambda ) * loc + lambda * to_value_casted - > loc ;
}
if ( rot_used ) {
rot = rot . slerp ( to_value_casted - > rot , lambda ) ;
}
if ( scale_used ) {
scale = ( 1. - lambda ) * scale + lambda * to_value_casted - > scale ;
}
2025-12-22 00:37:27 +01:00
}
bool operator = = ( const TrackValue & other_value ) const override {
if ( type ! = other_value . type ) {
return false ;
}
2025-12-31 20:30:28 +01:00
const TransformTrackValue * other_value_casted = & static_cast < const TransformTrackValue & > ( other_value ) ;
return bone_idx = = other_value_casted - > bone_idx & & loc = = other_value_casted - > loc & & rot = = other_value_casted - > rot & & scale = = other_value_casted - > scale ;
2025-12-22 00:37:27 +01:00
}
2025-12-03 22:43:24 +01:00
} ;
AnimationData ( ) = default ;
2026-01-16 09:51:49 +01:00
~ AnimationData ( ) = default ;
2025-12-22 00:37:27 +01:00
AnimationData ( const AnimationData & other ) {
2026-01-16 15:27:33 +01:00
value_buffer_offset = other . value_buffer_offset ;
buffer = other . buffer ;
2025-12-22 00:37:27 +01:00
}
AnimationData ( AnimationData & & other ) noexcept :
2026-01-16 15:27:33 +01:00
value_buffer_offset ( std : : exchange ( other . value_buffer_offset , AHashMap < Animation : : TypeHash , size_t , HashHasher > ( ) ) ) ,
buffer ( std : : exchange ( other . buffer , LocalVector < uint8_t > ( ) ) ) {
2025-12-22 00:37:27 +01:00
}
AnimationData & operator = ( const AnimationData & other ) {
AnimationData temp ( other ) ;
2026-01-16 15:27:33 +01:00
std : : swap ( value_buffer_offset , temp . value_buffer_offset ) ;
std : : swap ( buffer , temp . buffer ) ;
2025-12-22 00:37:27 +01:00
return * this ;
}
AnimationData & operator = ( AnimationData & & other ) noexcept {
2026-01-16 15:27:33 +01:00
std : : swap ( value_buffer_offset , other . value_buffer_offset ) ;
std : : swap ( buffer , other . buffer ) ;
2025-12-22 00:37:27 +01:00
return * this ;
}
2025-12-03 22:43:24 +01:00
2026-01-16 15:27:33 +01:00
void allocate_track_value ( const Animation : : Track * animation_track , const Skeleton3D * skeleton_3d ) ;
void allocate_track_values ( const Ref < Animation > & animation , const Skeleton3D * skeleton_3d ) ;
template < typename TrackValueType >
TrackValueType * get_value ( const Animation : : TypeHash & thash ) {
return reinterpret_cast < TrackValueType * > ( & buffer [ value_buffer_offset [ thash ] ] ) ;
2025-12-03 22:43:24 +01:00
}
2026-01-16 15:27:33 +01:00
template < typename TrackValueType >
const TrackValueType * get_value ( const Animation : : TypeHash & thash ) const {
return reinterpret_cast < const TrackValueType * > ( & buffer [ value_buffer_offset [ thash ] ] ) ;
2025-12-03 22:43:24 +01:00
}
2025-12-22 00:37:27 +01:00
bool has_same_tracks ( const AnimationData & other ) const {
HashSet < Animation : : TypeHash > valid_track_hashes ;
2026-01-16 15:27:33 +01:00
for ( const KeyValue < Animation : : TypeHash , size_t > & K : value_buffer_offset ) {
2025-12-22 00:37:27 +01:00
valid_track_hashes . insert ( K . key ) ;
}
2026-01-16 15:27:33 +01:00
for ( const KeyValue < Animation : : TypeHash , size_t > & K : other . value_buffer_offset ) {
2025-12-22 00:37:27 +01:00
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 ) {
2026-01-16 09:51:49 +01:00
GodotProfileZone ( " AnimationData::blend " ) ;
2025-12-22 00:37:27 +01:00
if ( ! has_same_tracks ( to_data ) ) {
print_error ( " Cannot blend AnimationData: tracks do not match. " ) ;
return ;
}
2026-01-16 15:27:33 +01:00
for ( const KeyValue < Animation : : TypeHash , size_t > & K : value_buffer_offset ) {
TrackValue * track_value = get_value < TrackValue > ( K . key ) ;
const TrackValue * other_track_value = to_data . get_value < TrackValue > ( K . key ) ;
2025-12-22 00:37:27 +01:00
track_value - > blend ( * other_track_value , lambda ) ;
}
}
void sample_from_animation ( const Ref < Animation > & animation , const Skeleton3D * skeleton_3d , double p_time ) ;
2026-01-16 15:27:33 +01:00
AHashMap < Animation : : TypeHash , size_t , HashHasher > value_buffer_offset ;
LocalVector < uint8_t > buffer ;
} ;
2025-12-03 22:43:24 +01:00
2026-01-16 15:27:33 +01:00
/**
* @ class AnimationDataAllocator
*
* Allows reusing of already allocated AnimationData objects . Stores the default values for all
* tracks . An allocated AnimationData object always has a resetted state where all TrackValues
* have the default value .
*
* During SyncedAnimationGraph initialization all nodes that generate values for AnimationData
* must register their tracks in the AnimationDataAllocator to ensure all allocated AnimationData
* have corresponding tracks .
*/
class AnimationDataAllocator {
AnimationData default_data ;
List < AnimationData * > allocated_data ;
public :
~ AnimationDataAllocator ( ) {
while ( ! allocated_data . is_empty ( ) ) {
memfree ( allocated_data . front ( ) - > get ( ) ) ;
allocated_data . pop_front ( ) ;
2025-12-03 22:43:24 +01:00
}
}
2026-01-16 15:27:33 +01:00
/// @brief Registers all animation track values for the default_data value.
void register_track_values ( const Ref < Animation > & animation , const Skeleton3D * skeleton_3d ) ;
AnimationData * allocate ( ) {
GodotProfileZone ( " AnimationDataAllocator::allocate_template " ) ;
if ( ! allocated_data . is_empty ( ) ) {
GodotProfileZone ( " AnimationDataAllocator::allocate_from_list " ) ;
AnimationData * result = allocated_data . front ( ) - > get ( ) ;
allocated_data . pop_front ( ) ;
// We copy the whole block as the assignment operator copies entries element wise.
memcpy ( result - > buffer . ptr ( ) , default_data . buffer . ptr ( ) , default_data . buffer . size ( ) ) ;
return result ;
}
AnimationData * result = memnew ( AnimationData ) ;
* result = default_data ;
return result ;
}
void free ( AnimationData * data ) {
allocated_data . push_front ( data ) ;
}
2025-12-03 22:43:24 +01:00
} ;
2025-12-19 10:53:19 +01:00
struct GraphEvaluationContext {
AnimationPlayer * animation_player = nullptr ;
Skeleton3D * skeleton_3d = nullptr ;
2026-01-16 15:27:33 +01:00
AnimationDataAllocator animation_data_allocator ;
2025-12-19 10:53:19 +01:00
} ;
2025-12-21 18:12:34 +01:00
/**
2026-01-17 00:35:53 +01:00
* @ class BLTAnimationNode
2025-12-21 18:12:34 +01:00
* Base class for all nodes in an SyncedAnimationGraph including BlendTree nodes and StateMachine states .
*/
2026-01-17 00:35:53 +01:00
class BLTAnimationNode : public Resource {
GDCLASS ( BLTAnimationNode , Resource ) ;
2025-12-05 17:20:35 +01:00
2026-01-17 00:35:53 +01:00
friend class BLTAnimationGraph ;
2025-12-03 22:43:24 +01:00
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 ;
2026-01-30 15:33:27 +01:00
virtual void _node_changed ( ) ;
2025-12-31 13:47:45 +01:00
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 {
2026-01-11 21:35:51 +01:00
double delta = 0.0 ;
2025-12-03 22:43:24 +01:00
double position = 0.0 ;
double sync_position = 0.0 ;
2025-12-19 10:53:19 +01:00
bool is_synced = false ;
2025-12-03 22:43:24 +01:00
2026-01-11 21:35:51 +01:00
Animation : : LoopMode loop_mode = Animation : : LOOP_NONE ;
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-29 15:25:10 +01:00
Vector2 position ;
2025-12-08 22:47:00 +01:00
2026-01-17 00:35:53 +01:00
virtual ~ BLTAnimationNode ( ) override = default ;
2026-01-11 21:35:51 +01:00
virtual bool initialize ( GraphEvaluationContext & context ) {
node_time_info = { } ;
return true ;
}
2025-12-21 18:12:34 +01:00
2026-01-17 00:35:53 +01:00
virtual void activate_inputs ( const Vector < Ref < BLTAnimationNode > > & input_nodes ) {
2025-12-13 22:38:45 +01:00
// By default, all inputs nodes are activated.
2026-01-17 00:35:53 +01:00
for ( const Ref < BLTAnimationNode > & node : input_nodes ) {
2026-01-30 15:33:27 +01:00
if ( node . ptr ( ) = = nullptr ) {
// TODO: add checking whether tree can be evaluated, i.e. whether all inputs are properly connected.
continue ;
}
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
}
}
2026-01-17 00:35:53 +01:00
virtual void calculate_sync_track ( const Vector < Ref < BLTAnimationNode > > & input_nodes ) {
2025-12-13 22:38:45 +01:00
// 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 ;
2026-01-11 21:35:51 +01:00
node_time_info . loop_mode = input_nodes [ 0 ] - > node_time_info . loop_mode ;
2025-12-13 22:38:45 +01:00
}
}
2026-01-24 15:38:27 +01:00
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 ;
2025-12-03 22:43:24 +01:00
}
}
2026-01-24 15:38:27 +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
2026-01-24 15:38:27 +01:00
void set_position ( const Vector2 & p_position ) {
position = p_position ;
}
Vector2 get_position ( ) const {
return position ;
}
virtual Vector < StringName > get_input_names ( ) const { return { } ; }
TypedArray < StringName > get_input_names_as_typed_array ( ) const {
TypedArray < StringName > typed_arr ;
Vector < StringName > vec = get_input_names ( ) ;
typed_arr . resize ( vec . size ( ) ) ;
for ( uint32_t i = 0 ; i < vec . size ( ) ; i + + ) {
typed_arr [ i ] = vec [ i ] ;
}
return typed_arr ;
}
2025-12-12 10:44:18 +01:00
2025-12-31 13:47:45 +01:00
int get_input_index ( const StringName & port_name ) const {
2026-01-24 15:38:27 +01:00
Vector < StringName > inputs = get_input_names ( ) ;
2025-12-12 10:44:18 +01:00
return inputs . find ( port_name ) ;
}
2025-12-31 13:47:45 +01:00
int get_input_count ( ) const {
2026-01-24 15:38:27 +01:00
Vector < StringName > inputs = get_input_names ( ) ;
2025-12-12 10:44:18 +01:00
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.
2026-01-17 00:35:53 +01:00
virtual void get_child_nodes ( List < Ref < BLTAnimationNode > > * r_child_nodes ) const { }
2025-12-03 22:43:24 +01:00
} ;
2026-01-17 00:35:53 +01:00
class BLTAnimationNodeSampler : public BLTAnimationNode {
GDCLASS ( BLTAnimationNodeSampler , BLTAnimationNode ) ;
2025-12-05 17:20:35 +01:00
2025-12-03 22:43:24 +01:00
public :
StringName animation_name ;
2026-01-30 15:33:27 +01:00
AnimationPlayer * animation_player = nullptr ;
2025-12-03 22:43:24 +01:00
2026-01-30 15:33:27 +01:00
void set_animation_player ( AnimationPlayer * p_player ) ;
2025-12-29 15:25:10 +01:00
void set_animation ( const StringName & p_name ) ;
StringName get_animation ( ) const ;
2026-01-30 15:33:27 +01:00
TypedArray < StringName > get_animations_as_typed_array ( ) 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 ;
2026-01-11 21:35:51 +01:00
void update_time ( double p_time ) 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
} ;
2026-01-17 00:35:53 +01:00
class BLTAnimationNodeOutput : public BLTAnimationNode {
GDCLASS ( BLTAnimationNodeOutput , BLTAnimationNode ) ;
2025-12-29 15:25:10 +01:00
2025-12-08 22:47:00 +01:00
public :
2026-01-24 15:38:27 +01:00
Vector < StringName > get_input_names ( ) const override {
return { " Output " } ;
2025-12-08 22:47:00 +01:00
}
2025-12-03 22:43:24 +01:00
} ;
2026-01-17 00:35:53 +01:00
class BLTAnimationNodeBlend2 : public BLTAnimationNode {
GDCLASS ( BLTAnimationNodeBlend2 , BLTAnimationNode ) ;
2025-12-29 15:25:10 +01:00
2025-12-10 09:22:33 +01:00
public :
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
2026-01-24 15:38:27 +01:00
Vector < StringName > get_input_names ( ) const override {
return { " Input0 " , " Input1 " } ;
2025-12-10 09:22:33 +01:00
}
2026-01-11 21:35:51 +01:00
bool initialize ( GraphEvaluationContext & context ) override {
2026-01-24 15:38:27 +01:00
if ( ! BLTAnimationNode : : initialize ( context ) ) {
return false ;
}
2026-01-11 21:35:51 +01:00
if ( sync ) {
// TODO: do we always want looping in this case or do we traverse the graph to check what's reasonable?
node_time_info . loop_mode = Animation : : LOOP_LINEAR ;
}
2026-01-24 15:38:27 +01:00
return true ;
2026-01-11 21:35:51 +01:00
}
2026-01-17 00:35:53 +01:00
void activate_inputs ( const Vector < Ref < BLTAnimationNode > > & input_nodes ) override {
2026-01-16 15:27:33 +01:00
input_nodes [ 0 ] - > active = true ;
input_nodes [ 1 ] - > active = true ;
2025-12-22 00:37:27 +01:00
2026-01-16 15:27:33 +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.
input_nodes [ 0 ] - > node_time_info . is_synced = node_time_info . is_synced | | sync ;
input_nodes [ 1 ] - > node_time_info . is_synced = node_time_info . is_synced | | sync ;
2025-12-31 18:50:42 +01:00
}
2026-01-11 21:35:51 +01:00
2026-01-17 00:35:53 +01:00
void calculate_sync_track ( const Vector < Ref < BLTAnimationNode > > & input_nodes ) override {
2025-12-31 18:50:42 +01:00
if ( node_time_info . is_synced | | sync ) {
2026-01-11 21:35:51 +01:00
assert ( input_nodes [ 0 ] - > node_time_info . loop_mode = = input_nodes [ 1 ] - > node_time_info . loop_mode ) ;
2025-12-31 18:50:42 +01:00
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 ) ;
}
}
2026-01-11 21:35:51 +01:00
2025-12-31 18:50:42 +01:00
void update_time ( double p_delta ) override {
2026-01-17 00:35:53 +01:00
BLTAnimationNode : : update_time ( p_delta ) ;
2025-12-31 18:50:42 +01:00
if ( sync & & ! node_time_info . is_synced ) {
2026-01-11 21:35:51 +01:00
if ( node_time_info . loop_mode ! = Animation : : LOOP_NONE ) {
if ( node_time_info . loop_mode = = Animation : : LOOP_LINEAR ) {
if ( ! Math : : is_zero_approx ( node_time_info . sync_track . duration ) ) {
node_time_info . position = Math : : fposmod ( static_cast < float > ( node_time_info . position ) , node_time_info . sync_track . duration ) ;
node_time_info . sync_position = node_time_info . sync_track . calc_sync_from_abs_time ( node_time_info . position ) ;
} else {
assert ( false & & ! " Loop mode ping-pong not yet supported " ) ;
}
}
}
2025-12-31 18:50:42 +01:00
}
}
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 ) ;
2026-01-11 21:35:51 +01:00
private :
StringName blend_weight_pname = PNAME ( " blend_amount " ) ;
StringName sync_pname = PNAME ( " sync " ) ;
2025-12-10 09:22:33 +01:00
} ;
2026-01-17 00:35:53 +01:00
struct BLTBlendTreeConnection {
2026-01-24 23:10:40 +01:00
Ref < BLTAnimationNode > source_node = nullptr ;
Ref < BLTAnimationNode > target_node = nullptr ;
StringName target_port_name = " " ;
2025-12-10 09:22:33 +01:00
} ;
2026-01-18 23:02:06 +01:00
class BLTAnimationNodeBlendTree : public BLTAnimationNode {
GDCLASS ( BLTAnimationNodeBlendTree , BLTAnimationNode ) ;
2025-12-13 22:38:45 +01:00
2026-01-18 23:02:06 +01:00
public :
enum ConnectionError {
CONNECTION_OK ,
CONNECTION_ERROR_GRAPH_ALREADY_INITIALIZED ,
CONNECTION_ERROR_NO_SOURCE_NODE ,
CONNECTION_ERROR_NO_TARGET_NODE ,
CONNECTION_ERROR_PARENT_EXISTS ,
CONNECTION_ERROR_TARGET_PORT_NOT_FOUND ,
CONNECTION_ERROR_TARGET_PORT_ALREADY_CONNECTED ,
CONNECTION_ERROR_CONNECTION_CREATES_LOOP ,
2026-01-30 15:33:27 +01:00
CONNECTION_ERROR_CONNECTION_NOT_FOUND
2025-12-12 10:44:18 +01:00
} ;
2026-01-18 23:02:06 +01:00
/**
* @ class BLTBlendTreeGraph
* Helper class that is used to build runtime blend trees and also to validate connections .
*/
struct BLTBlendTreeGraph {
struct NodeConnectionInfo {
int parent_node_index = - 1 ;
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.
NodeConnectionInfo ( ) = default ;
explicit NodeConnectionInfo ( const BLTAnimationNode * node ) {
parent_node_index = - 1 ;
for ( int i = 0 ; i < node - > get_input_count ( ) ; i + + ) {
connected_child_node_index_at_port . push_back ( - 1 ) ;
}
2025-12-21 18:12:34 +01:00
}
2025-12-13 22:38:45 +01:00
2026-01-18 23:02:06 +01:00
void apply_node_mapping ( const LocalVector < int > & node_index_mapping ) {
// 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 ) ;
}
2025-12-13 22:38:45 +01:00
2026-01-18 23:02:06 +01:00
// Map connected subtrees
HashSet < int > old_indices = input_subtree_node_indices ;
input_subtree_node_indices . clear ( ) ;
for ( int old_index : old_indices ) {
input_subtree_node_indices . insert ( node_index_mapping . find ( old_index ) ) ;
}
2025-12-19 10:53:19 +01:00
}
2025-12-10 09:22:33 +01:00
2026-01-24 23:10:40 +01:00
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 ) ;
}
2026-01-18 23:02:06 +01:00
} ;
2025-12-10 09:22:33 +01:00
2026-01-25 01:31:02 +01:00
LocalVector < Ref < BLTAnimationNode > > nodes ; // All added nodes
2026-01-18 23:02:06 +01:00
LocalVector < NodeConnectionInfo > node_connection_info ;
LocalVector < BLTBlendTreeConnection > connections ;
2025-12-10 09:22:33 +01:00
2026-01-18 23:02:06 +01:00
BLTBlendTreeGraph ( ) ;
2025-12-12 10:44:18 +01:00
2026-01-18 23:02:06 +01:00
Ref < BLTAnimationNode > get_output_node ( ) ;
int find_node_index ( const Ref < BLTAnimationNode > & node ) const ;
int find_node_index_by_name ( const StringName & name ) const ;
void sort_nodes_and_references ( ) ;
LocalVector < int > get_sorted_node_indices ( ) ;
void sort_nodes_recursive ( int node_index , LocalVector < int > & result ) ;
2026-01-24 23:10:40 +01:00
void add_index_and_update_subtrees_recursive ( int node_index , int node_parent_index ) ;
void remove_subtree_and_update_subtrees_recursive ( int node , const HashSet < int > & removed_subtree_indices ) ;
2025-12-10 09:22:33 +01:00
2026-01-18 23:02:06 +01:00
void add_node ( const Ref < BLTAnimationNode > & node ) ;
2026-01-30 15:33:27 +01:00
bool remove_node ( const Ref < BLTAnimationNode > & node ) ;
2026-01-24 23:10:40 +01:00
ConnectionError is_connection_valid ( const Ref < BLTAnimationNode > & source_node , const Ref < BLTAnimationNode > & target_node , StringName target_port_name ) const ;
2026-01-18 23:02:06 +01:00
ConnectionError add_connection ( const Ref < BLTAnimationNode > & source_node , const Ref < BLTAnimationNode > & target_node , const StringName & target_port_name ) ;
2026-01-24 23:10:40 +01:00
int find_connection_index ( const Ref < BLTAnimationNode > & source_node , const Ref < BLTAnimationNode > & target_node , const StringName & target_port_name ) const ;
ConnectionError remove_connection ( const Ref < BLTAnimationNode > & source_node , const Ref < BLTAnimationNode > & target_node , const StringName & target_port_name ) ;
2026-01-18 23:02:06 +01:00
} ;
2025-12-29 15:25:10 +01:00
2026-01-18 23:02:06 +01:00
private :
2026-01-17 00:35:53 +01:00
BLTBlendTreeGraph tree_graph ;
2025-12-13 22:38:45 +01:00
bool tree_initialized = false ;
2026-01-30 15:33:27 +01:00
GraphEvaluationContext * _graph_evaluation_context = nullptr ;
2025-12-13 22:38:45 +01:00
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
2026-01-25 01:31:02 +01:00
for ( uint32_t i = 0 ; i < tree_graph . nodes . size ( ) ; i + + ) {
2026-01-17 00:35:53 +01:00
const Ref < BLTAnimationNode > 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)
2026-01-25 01:31:02 +01:00
for ( uint32_t i = 0 ; i < tree_graph . nodes . size ( ) ; i + + ) {
2026-01-17 00:35:53 +01:00
Ref < BLTAnimationNode > 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 ] ;
2026-01-30 15:33:27 +01:00
if ( connected_node_index = = - 1 ) {
node_runtime_data . input_nodes . push_back ( nullptr ) ;
} else {
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 :
2026-01-17 00:35:53 +01:00
static void _bind_methods ( ) ;
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-03 22:43:24 +01:00
public :
2025-12-27 16:27:54 +01:00
struct NodeRuntimeData {
2026-01-17 00:35:53 +01:00
Vector < Ref < BLTAnimationNode > > input_nodes ;
2025-12-27 16:27:54 +01:00
LocalVector < AnimationData * > input_data ;
AnimationData * output_data = nullptr ;
} ;
LocalVector < NodeRuntimeData > _node_runtime_data ;
2026-01-17 00:35:53 +01:00
int find_node_index ( const Ref < BLTAnimationNode > & 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
2026-01-30 15:33:27 +01:00
int find_node_index_by_name ( const StringName & p_name ) const {
return tree_graph . find_node_index_by_name ( p_name ) ;
2025-12-08 22:47:00 +01:00
}
2026-01-24 15:38:27 +01:00
void add_node ( const Ref < BLTAnimationNode > & node ) {
tree_graph . add_node ( node ) ;
2026-01-30 15:33:27 +01:00
if ( _graph_evaluation_context ! = nullptr ) {
node - > initialize ( * _graph_evaluation_context ) ;
}
2026-01-24 15:38:27 +01:00
}
2026-01-25 01:31:02 +01:00
void remove_node ( const Ref < BLTAnimationNode > & node ) {
2026-01-30 15:33:27 +01:00
if ( tree_graph . remove_node ( node ) ) {
_node_changed ( ) ;
2026-01-25 01:31:02 +01:00
}
}
2026-01-24 15:38:27 +01:00
TypedArray < StringName > get_node_names_as_typed_array ( ) const {
Vector < StringName > vec ;
for ( const Ref < BLTAnimationNode > & node : tree_graph . nodes ) {
vec . push_back ( node - > get_name ( ) ) ;
}
TypedArray < StringName > typed_arr ;
typed_arr . resize ( vec . size ( ) ) ;
for ( uint32_t i = 0 ; i < vec . size ( ) ; i + + ) {
typed_arr [ i ] = vec [ i ] ;
}
return typed_arr ;
}
Ref < BLTAnimationNode > get_node ( const StringName & node_name ) const {
int node_index = tree_graph . find_node_index_by_name ( node_name ) ;
if ( node_index > = 0 ) {
return tree_graph . nodes [ node_index ] ;
}
return nullptr ;
}
Ref < BLTAnimationNode > get_node_by_index ( int node_index ) const {
2026-01-25 01:31:02 +01:00
if ( node_index < 0 | | node_index > static_cast < int > ( tree_graph . nodes . size ( ) ) ) {
2026-01-11 21:35:51 +01:00
return nullptr ;
}
return tree_graph . nodes [ node_index ] ;
}
2026-01-24 15:38:27 +01:00
Ref < BLTAnimationNode > get_output_node ( ) const {
return tree_graph . nodes [ 0 ] ;
}
ConnectionError is_connection_valid ( const Ref < BLTAnimationNode > & source_node , const Ref < BLTAnimationNode > & target_node , const StringName & target_port_name ) {
return tree_graph . is_connection_valid ( source_node , target_node , target_port_name ) ;
2025-12-13 22:38:45 +01:00
}
2026-01-18 23:02:06 +01:00
ConnectionError add_connection ( const Ref < BLTAnimationNode > & source_node , const Ref < BLTAnimationNode > & target_node , const StringName & target_port_name ) {
2026-01-30 15:33:27 +01:00
ConnectionError result = tree_graph . add_connection ( source_node , target_node , target_port_name ) ;
if ( result = = CONNECTION_OK ) {
_node_changed ( ) ;
2025-12-13 22:38:45 +01:00
}
2026-01-30 15:33:27 +01:00
return result ;
2025-12-08 22:47:00 +01:00
}
2026-01-24 23:10:40 +01:00
ConnectionError remove_connection ( const Ref < BLTAnimationNode > & source_node , const Ref < BLTAnimationNode > & target_node , const StringName & target_port_name ) {
2026-01-30 15:33:27 +01:00
ConnectionError result = tree_graph . remove_connection ( source_node , target_node , target_port_name ) ;
if ( result = = CONNECTION_OK ) {
_node_changed ( ) ;
2026-01-24 23:10:40 +01:00
}
2026-01-30 15:33:27 +01:00
return result ;
2026-01-24 23:10:40 +01:00
}
2026-01-24 15:38:27 +01:00
Array get_connections_as_array ( ) const {
Array result ;
for ( const BLTBlendTreeConnection & connection : tree_graph . connections ) {
result . push_back ( connection . source_node ) ;
result . push_back ( connection . target_node ) ;
result . push_back ( connection . target_port_name ) ;
}
return result ;
}
2026-01-25 01:31:02 +01:00
// overrides from BLTAnimationNode
2025-12-31 13:47:45 +01:00
bool initialize ( GraphEvaluationContext & context ) override {
2026-01-24 15:38:27 +01:00
if ( ! BLTAnimationNode : : initialize ( context ) ) {
return false ;
}
2026-01-30 15:33:27 +01:00
_graph_evaluation_context = & context ;
2025-12-27 16:27:54 +01:00
sort_nodes ( ) ;
setup_runtime_data ( ) ;
2025-12-19 10:53:19 +01:00
2026-01-17 00:35:53 +01:00
for ( const Ref < BLTAnimationNode > & 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
}
2026-01-18 23:02:06 +01:00
void
activate_inputs ( const Vector < Ref < BLTAnimationNode > > & input_nodes ) override {
2026-01-16 09:51:49 +01:00
GodotProfileZone ( " SyncedBlendTree::activate_inputs " ) ;
2026-01-30 15:33:27 +01:00
// TODO: add checking whether tree can be evaluated, i.e. whether all inputs are properly connected.
if ( tree_graph . nodes . size ( ) = = 1 ) {
return ;
}
2025-12-29 15:56:29 +01:00
tree_graph . nodes [ 0 ] - > active = true ;
2026-01-25 01:31:02 +01:00
for ( uint32_t i = 0 ; i < tree_graph . nodes . size ( ) ; i + + ) {
2026-01-17 00:35:53 +01:00
const Ref < BLTAnimationNode > & 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
}
2026-01-17 00:35:53 +01:00
void calculate_sync_track ( const Vector < Ref < BLTAnimationNode > > & input_nodes ) override {
2026-01-16 09:51:49 +01:00
GodotProfileZone ( " SyncedBlendTree::calculate_sync_track " ) ;
2026-01-25 01:31:02 +01:00
for ( uint32_t i = tree_graph . nodes . size ( ) - 1 ; i > 0 ; i - - ) {
2026-01-17 00:35:53 +01:00
const Ref < BLTAnimationNode > & 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 {
2026-01-16 09:51:49 +01:00
GodotProfileZone ( " SyncedBlendTree::update_time " ) ;
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
2026-01-25 01:31:02 +01:00
for ( uint32_t i = 1 ; i < tree_graph . nodes . size ( ) ; i + + ) {
2026-01-17 00:35:53 +01:00
const Ref < BLTAnimationNode > & node = tree_graph . nodes [ i ] ;
2025-12-13 22:38:45 +01:00
if ( ! node - > active ) {
continue ;
}
2026-01-17 00:35:53 +01:00
const Ref < BLTAnimationNode > & 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 {
2026-01-30 15:33:27 +01:00
GodotProfileZone ( " SyncedBlendTree::evaluate " ) ;
2026-01-16 09:51:49 +01:00
2026-01-25 01:31:02 +01:00
for ( uint32_t i = tree_graph . nodes . size ( ) - 1 ; i > 0 ; i - - ) {
2026-01-17 00:35:53 +01:00
const Ref < BLTAnimationNode > & 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 {
2026-01-16 15:27:33 +01:00
node_runtime_data . output_data = context . animation_data_allocator . allocate ( ) ;
2025-12-19 10:53:19 +01:00
}
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 ) {
2026-01-16 15:27:33 +01:00
context . animation_data_allocator . free ( _node_runtime_data [ child_index ] . output_data ) ;
2025-12-19 10:53:19 +01:00
}
}
2025-12-08 22:47:00 +01:00
}
2025-12-31 13:47:45 +01:00
2026-01-17 00:35:53 +01:00
void get_child_nodes ( List < Ref < BLTAnimationNode > > * r_child_nodes ) const override {
for ( const Ref < BLTAnimationNode > & node : tree_graph . nodes ) {
2025-12-31 13:47:45 +01:00
r_child_nodes - > push_back ( node . ptr ( ) ) ;
}
}
2025-12-07 22:09:28 +01:00
} ;
2026-01-18 23:02:06 +01:00
VARIANT_ENUM_CAST ( BLTAnimationNodeBlendTree : : ConnectionError )