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"
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 ;
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 {
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-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
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 ) {
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
}
}
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
}
}
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
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.
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 ;
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 ;
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 :
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
} ;
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
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 " ) ;
}
2026-01-11 21:35:51 +01:00
bool initialize ( GraphEvaluationContext & context ) override {
2026-01-17 00:35:53 +01:00
bool result = BLTAnimationNode : : initialize ( context ) ;
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 ;
}
return result ;
}
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 {
const Ref < BLTAnimationNode > source_node = nullptr ;
const Ref < BLTAnimationNode > target_node = nullptr ;
2025-12-10 09:22:33 +01:00
const StringName target_port_name = " " ;
} ;
2025-12-13 22:38:45 +01:00
/**
2026-01-17 00:35:53 +01:00
* @ class BLTBlendTreeGraph
2025-12-13 22:38:45 +01:00
* Helper class that is used to build runtime blend trees and also to validate connections .
*/
2026-01-17 00:35:53 +01:00
struct BLTBlendTreeGraph {
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 ;
2026-01-17 00:35:53 +01:00
explicit NodeConnectionInfo ( const BLTAnimationNode * node ) {
2025-12-12 10:44:18 +01:00
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
} ;
2026-01-17 00:35:53 +01:00
Vector < Ref < BLTAnimationNode > > nodes ; // All added nodes
2025-12-12 10:44:18 +01:00
LocalVector < NodeConnectionInfo > node_connection_info ;
2026-01-17 00:35:53 +01:00
LocalVector < BLTBlendTreeConnection > connections ;
2025-12-08 22:47:00 +01:00
2026-01-17 00:35:53 +01:00
BLTBlendTreeGraph ( ) {
Ref < BLTAnimationNodeOutput > output_node ;
2025-12-10 09:22:33 +01:00
output_node . instantiate ( ) ;
output_node - > name = " Output " ;
add_node ( output_node ) ;
}
2025-12-03 22:43:24 +01:00
2026-01-17 00:35:53 +01:00
Ref < BLTAnimationNode > get_output_node ( ) const {
2025-12-10 09:22:33 +01:00
return nodes [ 0 ] ;
}
2025-12-03 22:43:24 +01:00
2026-01-17 00:35:53 +01:00
int find_node_index ( const Ref < BLTAnimationNode > & 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 ;
}
2026-01-17 00:35:53 +01:00
void add_node ( const Ref < BLTAnimationNode > & 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 ( ) ;
2026-01-17 00:35:53 +01:00
Vector < Ref < BLTAnimationNode > > sorted_nodes ;
2026-01-16 09:51:49 +01:00
LocalVector < NodeConnectionInfo > old_node_connection_info = node_connection_info ;
2025-12-13 22:38:45 +01:00
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 ) ;
}
2026-01-17 00:35:53 +01:00
bool add_connection ( const Ref < BLTAnimationNode > & source_node , const Ref < BLTAnimationNode > & target_node , const StringName & target_port_name ) {
2025-12-12 10:44:18 +01:00
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 ;
2026-01-17 00:35:53 +01:00
connections . push_back ( BLTBlendTreeConnection { 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 ;
}
2026-01-17 00:35:53 +01:00
bool is_connection_valid ( const Ref < BLTAnimationNode > & source_node , const Ref < BLTAnimationNode > & 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
} ;
2026-01-17 00:35:53 +01:00
class BLTAnimationNodeBlendTree : public BLTAnimationNode {
GDCLASS ( BLTAnimationNodeBlendTree , BLTAnimationNode ) ;
2025-12-29 15:25:10 +01:00
2026-01-17 00:35:53 +01:00
BLTBlendTreeGraph 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 + + ) {
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)
2025-12-29 15:56:29 +01:00
for ( int 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 ] ;
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
Ref < BLTAnimationNode > 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
}
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
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
}
2026-01-17 00:35:53 +01:00
Ref < BLTAnimationNode > get_node ( int node_index ) {
2026-01-11 21:35:51 +01:00
if ( node_index < 0 | | node_index > tree_graph . nodes . size ( ) ) {
return nullptr ;
}
return tree_graph . nodes [ node_index ] ;
}
2026-01-17 00:35:53 +01:00
void add_node ( const Ref < BLTAnimationNode > & node ) {
2025-12-13 22:38:45 +01:00
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
}
2026-01-17 00:35:53 +01:00
bool add_connection ( const Ref < BLTAnimationNode > & source_node , const Ref < BLTAnimationNode > & target_node , const StringName & target_port_name ) {
2025-12-13 22:38:45 +01:00
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
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-17 00:35:53 +01:00
void activate_inputs ( const Vector < Ref < BLTAnimationNode > > & input_nodes ) override {
2026-01-16 09:51:49 +01:00
GodotProfileZone ( " SyncedBlendTree::activate_inputs " ) ;
2025-12-29 15:56:29 +01:00
tree_graph . nodes [ 0 ] - > active = true ;
for ( int 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 " ) ;
2025-12-29 15:56:29 +01:00
for ( int 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
2025-12-29 15:56:29 +01:00
for ( int 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-16 09:51:49 +01:00
ZoneScopedN ( " SyncedBlendTree::evaluate " ) ;
2025-12-29 15:56:29 +01:00
for ( int 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
} ;