diff --git a/doc/classes/AnimatableBody2D.xml b/doc/classes/AnimatableBody2D.xml index 831f7d73d05..e0479f68288 100644 --- a/doc/classes/AnimatableBody2D.xml +++ b/doc/classes/AnimatableBody2D.xml @@ -4,7 +4,7 @@ A 2D physics body that can't be moved by external forces. When moved manually, it affects other bodies in its path. - An animatable 2D physics body. It can't be moved by external forces or contacts, but can be moved manually by other means such as code, [AnimationPlayer]s (with [member AnimationPlayer.playback_process_mode] set to [constant AnimationPlayer.ANIMATION_PROCESS_PHYSICS]), and [RemoteTransform2D]. + An animatable 2D physics body. It can't be moved by external forces or contacts, but can be moved manually by other means such as code, [AnimationMixer]s (with [member AnimationMixer.callback_mode_process] set to [constant AnimationMixer.ANIMATION_CALLBACK_MODE_PROCESS_PHYSICS]), and [RemoteTransform2D]. When [AnimatableBody2D] is moved, its linear and angular velocity are estimated and used to affect other physics bodies in its path. This makes it useful for moving platforms, doors, and other moving objects. diff --git a/doc/classes/AnimatableBody3D.xml b/doc/classes/AnimatableBody3D.xml index f1b3eb6ad85..f5c62174774 100644 --- a/doc/classes/AnimatableBody3D.xml +++ b/doc/classes/AnimatableBody3D.xml @@ -4,7 +4,7 @@ A 3D physics body that can't be moved by external forces. When moved manually, it affects other bodies in its path. - An animatable 3D physics body. It can't be moved by external forces or contacts, but can be moved manually by other means such as code, [AnimationPlayer]s (with [member AnimationPlayer.playback_process_mode] set to [constant AnimationPlayer.ANIMATION_PROCESS_PHYSICS]), and [RemoteTransform3D]. + An animatable 3D physics body. It can't be moved by external forces or contacts, but can be moved manually by other means such as code, [AnimationMixer]s (with [member AnimationMixer.callback_mode_process] set to [constant AnimationMixer.ANIMATION_CALLBACK_MODE_PROCESS_PHYSICS]), and [RemoteTransform3D]. When [AnimatableBody3D] is moved, its linear and angular velocity are estimated and used to affect other physics bodies in its path. This makes it useful for moving platforms, doors, and other moving objects. diff --git a/doc/classes/AnimationMixer.xml b/doc/classes/AnimationMixer.xml new file mode 100644 index 00000000000..4a489a3ef23 --- /dev/null +++ b/doc/classes/AnimationMixer.xml @@ -0,0 +1,342 @@ + + + + Base class for [AnimationPlayer] and [AnimationTree]. + + + Base class for [AnimationPlayer] and [AnimationTree] to manage animation lists. It also has general properties and methods for playback and blending. + After instantiating the playback information data within the extended class, the blending is processed by the [AnimationMixer]. + + + + + + + + + + + + + A virtual function for processing after key getting during playback. + + + + + + + + Adds [param library] to the animation player, under the key [param name]. + + + + + + + Manually advance the animations by the specified time (in seconds). + + + + + + [AnimationMixer] caches animated nodes. It may not notice if a node disappears; [method clear_caches] forces it to update the cache again. + + + + + + + Returns the key of [param animation] or an empty [StringName] if not found. + + + + + + + Returns the key for the [AnimationLibrary] that contains [param animation] or an empty [StringName] if not found. + + + + + + + Returns the [Animation] with the key [param name]. If the animation does not exist, [code]null[/code] is returned and an error is logged. + + + + + + + Returns the first [AnimationLibrary] with key [param name] or [code]null[/code] if not found. + To get the [AnimationPlayer]'s global animation library, use [code]get_animation_library("")[/code]. + + + + + + Returns the list of stored library keys. + + + + + + Returns the list of stored animation keys. + + + + + + Retrieve the motion delta of position with the [member root_motion_track] as a [Vector3] that can be used elsewhere. + If [member root_motion_track] is not a path to a track of type [constant Animation.TYPE_POSITION_3D], returns [code]Vector3(0, 0, 0)[/code]. + See also [member root_motion_track] and [RootMotionView]. + The most basic example is applying position to [CharacterBody3D]: + [codeblocks] + [gdscript] + var current_rotation: Quaternion + + func _process(delta): + if Input.is_action_just_pressed("animate"): + current_rotation = get_quaternion() + state_machine.travel("Animate") + var velocity: Vector3 = current_rotation * animation_tree.get_root_motion_position() / delta + set_velocity(velocity) + move_and_slide() + [/gdscript] + [/codeblocks] + By using this in combination with [method get_root_motion_position_accumulator], you can apply the root motion position more correctly to account for the rotation of the node. + [codeblocks] + [gdscript] + func _process(delta): + if Input.is_action_just_pressed("animate"): + state_machine.travel("Animate") + set_quaternion(get_quaternion() * animation_tree.get_root_motion_rotation()) + var velocity: Vector3 = (animation_tree.get_root_motion_rotation_accumulator().inverse() * get_quaternion()) * animation_tree.get_root_motion_position() / delta + set_velocity(velocity) + move_and_slide() + [/gdscript] + [/codeblocks] + + + + + + Retrieve the blended value of the position tracks with the [member root_motion_track] as a [Vector3] that can be used elsewhere. + This is useful in cases where you want to respect the initial key values of the animation. + For example, if an animation with only one key [code]Vector3(0, 0, 0)[/code] is played in the previous frame and then an animation with only one key [code]Vector3(1, 0, 1)[/code] is played in the next frame, the difference can be calculated as follows: + [codeblocks] + [gdscript] + var prev_root_motion_position_accumulator: Vector3 + + func _process(delta): + if Input.is_action_just_pressed("animate"): + state_machine.travel("Animate") + var current_root_motion_position_accumulator: Vector3 = animation_tree.get_root_motion_position_accumulator() + var difference: Vector3 = current_root_motion_position_accumulator - prev_root_motion_position_accumulator + prev_root_motion_position_accumulator = current_root_motion_position_accumulator + transform.origin += difference + [/gdscript] + [/codeblocks] + However, if the animation loops, an unintended discrete change may occur, so this is only useful for some simple use cases. + + + + + + Retrieve the motion delta of rotation with the [member root_motion_track] as a [Quaternion] that can be used elsewhere. + If [member root_motion_track] is not a path to a track of type [constant Animation.TYPE_ROTATION_3D], returns [code]Quaternion(0, 0, 0, 1)[/code]. + See also [member root_motion_track] and [RootMotionView]. + The most basic example is applying rotation to [CharacterBody3D]: + [codeblocks] + [gdscript] + func _process(delta): + if Input.is_action_just_pressed("animate"): + state_machine.travel("Animate") + set_quaternion(get_quaternion() * animation_tree.get_root_motion_rotation()) + [/gdscript] + [/codeblocks] + + + + + + Retrieve the blended value of the rotation tracks with the [member root_motion_track] as a [Quaternion] that can be used elsewhere. + This is necessary to apply the root motion position correctly, taking rotation into account. See also [method get_root_motion_position]. + Also, this is useful in cases where you want to respect the initial key values of the animation. + For example, if an animation with only one key [code]Quaternion(0, 0, 0, 1)[/code] is played in the previous frame and then an animation with only one key [code]Quaternion(0, 0.707, 0, 0.707)[/code] is played in the next frame, the difference can be calculated as follows: + [codeblocks] + [gdscript] + var prev_root_motion_rotation_accumulator: Quaternion + + func _process(delta): + if Input.is_action_just_pressed("animate"): + state_machine.travel("Animate") + var current_root_motion_rotation_accumulator: Quaternion = animation_tree.get_root_motion_Quaternion_accumulator() + var difference: Quaternion = prev_root_motion_rotation_accumulator.inverse() * current_root_motion_rotation_accumulator + prev_root_motion_rotation_accumulator = current_root_motion_rotation_accumulator + transform.basis *= difference + [/gdscript] + [/codeblocks] + However, if the animation loops, an unintended discrete change may occur, so this is only useful for some simple use cases. + + + + + + Retrieve the motion delta of scale with the [member root_motion_track] as a [Vector3] that can be used elsewhere. + If [member root_motion_track] is not a path to a track of type [constant Animation.TYPE_SCALE_3D], returns [code]Vector3(0, 0, 0)[/code]. + See also [member root_motion_track] and [RootMotionView]. + The most basic example is applying scale to [CharacterBody3D]: + [codeblocks] + [gdscript] + var current_scale: Vector3 = Vector3(1, 1, 1) + var scale_accum: Vector3 = Vector3(1, 1, 1) + + func _process(delta): + if Input.is_action_just_pressed("animate"): + current_scale = get_scale() + scale_accum = Vector3(1, 1, 1) + state_machine.travel("Animate") + scale_accum += animation_tree.get_root_motion_scale() + set_scale(current_scale * scale_accum) + [/gdscript] + [/codeblocks] + + + + + + Retrieve the blended value of the scale tracks with the [member root_motion_track] as a [Vector3] that can be used elsewhere. + For example, if an animation with only one key [code]Vector3(1, 1, 1)[/code] is played in the previous frame and then an animation with only one key [code]Vector3(2, 2, 2)[/code] is played in the next frame, the difference can be calculated as follows: + [codeblocks] + [gdscript] + var prev_root_motion_scale_accumulator: Vector3 + + func _process(delta): + if Input.is_action_just_pressed("animate"): + state_machine.travel("Animate") + var current_root_motion_scale_accumulator: Vector3 = animation_tree.get_root_motion_scale_accumulator() + var difference: Vector3 = current_root_motion_scale_accumulator - prev_root_motion_scale_accumulator + prev_root_motion_scale_accumulator = current_root_motion_scale_accumulator + transform.basis = transform.basis.scaled(difference) + [/gdscript] + [/codeblocks] + However, if the animation loops, an unintended discrete change may occur, so this is only useful for some simple use cases. + + + + + + + Returns [code]true[/code] if the [AnimationPlayer] stores an [Animation] with key [param name]. + + + + + + + Returns [code]true[/code] if the [AnimationPlayer] stores an [AnimationLibrary] with key [param name]. + + + + + + + Removes the [AnimationLibrary] associated with the key [param name]. + + + + + + + + Moves the [AnimationLibrary] associated with the key [param name] to the key [param newname]. + + + + + + If [code]true[/code], the [AnimationMixer] will be processing. + + + The number of possible simultaneous sounds for each of the assigned AudioStreamPlayers. + For example, if this value is [code]32[/code] and the animation has two audio tracks, the two [AudioStreamPlayer]s assigned can play simultaneously up to [code]32[/code] voices each. + + + The call mode to use for Call Method tracks. + + + The process notification in which to update animations. + + + If [code]true[/code], the blending uses the deterministic algorithm. The total weight is not normalized and the result is accumulated with an initial value ([code]0[/code] or a [code]"RESET"[/code] animation if present). + This means that if the total amount of blending is [code]0.0[/code], the result is equal to the [code]"RESET"[/code] animation. + If the number of tracks between the blended animations is different, the animation with the missing track is treated as if it had the initial value. + If [code]false[/code], The blend does not use the deterministic algorithm. The total weight is normalized and always [code]1.0[/code]. If the number of tracks between the blended animations is different, nothing is done about the animation that is missing a track. + [b]Note:[/b] In [AnimationTree], the blending with [AnimationNodeAdd2], [AnimationNodeAdd3], [AnimationNodeSub2] or the weight greater than [code]1.0[/code] may produce unexpected results. + For example, if [AnimationNodeAdd2] blends two nodes with the amount [code]1.0[/code], then total weight is [code]2.0[/code] but it will be normalized to make the total amount [code]1.0[/code] and the result will be equal to [AnimationNodeBlend2] with the amount [code]0.5[/code]. + + + This is used by the editor. If set to [code]true[/code], the scene will be saved with the effects of the reset animation (the animation with the key [code]"RESET"[/code]) applied as if it had been seeked to time 0, with the editor keeping the values that the scene had before saving. + This makes it more convenient to preview and edit animations in the editor, as changes to the scene will not be saved as long as they are set in the reset animation. + + + The path to the Animation track used for root motion. Paths must be valid scene-tree paths to a node, and must be specified starting from the parent node of the node that will reproduce the animation. To specify a track that controls properties or bones, append its name after the path, separated by [code]":"[/code]. For example, [code]"character/skeleton:ankle"[/code] or [code]"character/mesh:transform/local"[/code]. + If the track has type [constant Animation.TYPE_POSITION_3D], [constant Animation.TYPE_ROTATION_3D] or [constant Animation.TYPE_SCALE_3D] the transformation will be canceled visually, and the animation will appear to stay in place. See also [method get_root_motion_position], [method get_root_motion_rotation], [method get_root_motion_scale] and [RootMotionView]. + + + The node from which node path references will travel. + + + + + + + Notifies when an animation finished playing. + [b]Note:[/b] This signal is not emitted if an animation is looping. + + + + + Notifies when the animation libraries have changed. + + + + + Notifies when an animation list is changed. + + + + + + Notifies when an animation starts playing. + + + + + Notifies when the caches have been cleared, either automatically, or manually via [method clear_caches]. + + + + + Editor only. Notifies when the property have been updated to update dummy [AnimationPlayer] in animation player editor. + + + + + + Process animation during physics frames (see [constant Node.NOTIFICATION_INTERNAL_PHYSICS_PROCESS]). This is especially useful when animating physics bodies. + + + Process animation during process frames (see [constant Node.NOTIFICATION_INTERNAL_PROCESS]). + + + Do not process animation. Use [method advance] to process the animation manually. + + + Batch method calls during the animation process, then do the calls after events are processed. This avoids bugs involving deleting nodes or modifying the AnimationPlayer while playing. + + + Make method calls immediately when reached in the animation. + + + diff --git a/doc/classes/AnimationPlayer.xml b/doc/classes/AnimationPlayer.xml index b7f384d0042..dda0187e8b2 100644 --- a/doc/classes/AnimationPlayer.xml +++ b/doc/classes/AnimationPlayer.xml @@ -1,5 +1,5 @@ - + A node used for animation playback. @@ -15,51 +15,19 @@ https://godotengine.org/asset-library/asset/678 - - - - - - - - - A virtual function for processing after key getting during playback. - - - - - - - - Adds [param library] to the animation player, under the key [param name]. - - - - - - - Shifts position in the animation timeline and immediately updates the animation. [param delta] is the time in seconds to shift. Events between the current frame and [param delta] are handled. - - - + - Returns the key of the animation which is queued to play after the [param anim_from] animation. + Returns the key of the animation which is queued to play after the [param animation_from] animation. - - + + - Triggers the [param anim_to] animation when the [param anim_from] animation completes. - - - - - - [AnimationPlayer] caches animated nodes. It may not notice if a node disappears; [method clear_caches] forces it to update the cache again. + Triggers the [param animation_to] animation when the [param animation_from] animation completes. @@ -68,55 +36,20 @@ Clears all queued, unplayed animations. - - - - - Returns the key of [param animation] or an empty [StringName] if not found. - - - - - - - Returns the key for the [AnimationLibrary] that contains [param animation] or an empty [StringName] if not found. - - - - - - - Returns the [Animation] with the key [param name]. If the animation does not exist, [code]null[/code] is returned and an error is logged. - - - - - - - Returns the first [AnimationLibrary] with key [param name] or [code]null[/code] if not found. - To get the [AnimationPlayer]'s global animation library, use [code]get_animation_library("")[/code]. - - - - - - Returns the list of stored library keys. - - - - - - Returns the list of stored animation keys. - - - - + + Returns the blend time (in seconds) between two animations, referenced by their keys. + + + + For backward compatibility. See [enum AnimationMixer.AnimationCallbackModeMethod]. + + @@ -124,24 +57,22 @@ Returns a negative value if the current animation is playing backwards. + + + + For backward compatibility. See [enum AnimationMixer.AnimationCallbackModeProcess]. + + Returns a list of the animation keys that are currently queued to play. - - - + + - Returns [code]true[/code] if the [AnimationPlayer] stores an [Animation] with key [param name]. - - - - - - - Returns [code]true[/code] if the [AnimationPlayer] stores an [AnimationLibrary] with key [param name]. + For backward compatibility. See [member AnimationMixer.root_node]. @@ -187,39 +118,47 @@ [b]Note:[/b] If a looped animation is currently playing, the queued animation will never play unless the looped animation is stopped somehow. - - - - - Removes the [AnimationLibrary] associated with the key [param name]. - - - - - - - - Moves the [AnimationLibrary] associated with the key [param name] to the key [param newname]. - - + Seeks the animation to the [param seconds] point in time (in seconds). If [param update] is [code]true[/code], the animation updates too, otherwise it updates at process time. Events between the current frame and [param seconds] are skipped. - [b]Note:[/b] Seeking to the end of the animation doesn't emit [signal animation_finished]. If you want to skip animation and emit the signal, use [method advance]. + If [param update_only] is true, the method / audio / animation playback tracks will not be processed. + [b]Note:[/b] Seeking to the end of the animation doesn't emit [signal AnimationMixer.animation_finished]. If you want to skip animation and emit the signal, use [method AnimationMixer.advance]. - - + + Specifies a blend time (in seconds) between two animations, referenced by their keys. + + + + + For backward compatibility. See [enum AnimationMixer.AnimationCallbackModeMethod]. + + + + + + + For backward compatibility. See [enum AnimationMixer.AnimationCallbackModeProcess]. + + + + + + + For backward compatibility. See [member AnimationMixer.root_node]. + + @@ -234,10 +173,6 @@ If playing, the current animation's key, otherwise, the animation last played. When set, this changes the animation, but will not play it unless already playing. See also [member current_animation]. - - The number of possible simultaneous sounds for each of the assigned AudioStreamPlayers. - For example, if this value is [code]32[/code] and the animation has two audio tracks, the two [AudioStreamPlayer]s assigned can play simultaneously up to [code]32[/code] voices each. - The key of the animation to play when the scene loads. @@ -251,29 +186,13 @@ The position (in seconds) of the currently playing animation. - - The call mode to use for Call Method tracks. - If [code]true[/code] and the engine is running in Movie Maker mode (see [MovieWriter]), exits the engine with [method SceneTree.quit] as soon as an animation is done playing in this [AnimationPlayer]. A message is printed when the engine quits for this reason. - [b]Note:[/b] This obeys the same logic as the [signal animation_finished] signal, so it will not quit the engine if the animation is set to be looping. - - - If [code]true[/code], updates animations in response to process-related notifications. + [b]Note:[/b] This obeys the same logic as the [signal AnimationMixer.animation_finished] signal, so it will not quit the engine if the animation is set to be looping. The default time in which to blend animations. Ranges from 0 to 4096 with 0.01 precision. - - The process notification in which to update animations. - - - This is used by the editor. If set to [code]true[/code], the scene will be saved with the effects of the reset animation (the animation with the key [code]"RESET"[/code]) applied as if it had been seeked to time 0, with the editor keeping the values that the scene had before saving. - This makes it more convenient to preview and edit animations in the editor, as changes to the scene will not be saved as long as they are set in the reset animation. - - - The node from which node path references will travel. - The speed scaling ratio. For example, if this value is [code]1[/code], then the animation plays at normal speed. If it's [code]0.5[/code], then it plays at half speed. If it's [code]2[/code], then it plays at double speed. If set to a negative value, the animation is played in reverse. If set to [code]0[/code], the animation will not advance. @@ -284,54 +203,32 @@ - Emitted when a queued animation plays after the previous animation finished. See [method queue]. - [b]Note:[/b] The signal is not emitted when the animation is changed via [method play] or by an [AnimationTree]. + Emitted when a queued animation plays after the previous animation finished. See also [method AnimationPlayer.queue]. + [b]Note:[/b] The signal is not emitted when the animation is changed via [method AnimationPlayer.play] or by an [AnimationTree]. - - + + - Notifies when an animation finished playing. - [b]Note:[/b] This signal is not emitted if an animation is looping. - - - - - Notifies when the animation libraries have changed. - - - - - Notifies when an animation list is changed. - - - - - - Notifies when an animation starts playing. - - - - - Notifies when the caches have been cleared, either automatically, or manually via [method clear_caches]. + Emitted when [member current_animation] changes. - - Process animation during physics frames (see [constant Node.NOTIFICATION_INTERNAL_PHYSICS_PROCESS]). This is especially useful when animating physics bodies. + + For backward compatibility. See [constant AnimationMixer.ANIMATION_CALLBACK_MODE_PROCESS_PHYSICS]. - - Process animation during process frames (see [constant Node.NOTIFICATION_INTERNAL_PROCESS]). + + For backward compatibility. See [constant AnimationMixer.ANIMATION_CALLBACK_MODE_PROCESS_IDLE]. - - Do not process animation. Use [method advance] to process the animation manually. + + For backward compatibility. See [constant AnimationMixer.ANIMATION_CALLBACK_MODE_PROCESS_MANUAL]. - - Batch method calls during the animation process, then do the calls after events are processed. This avoids bugs involving deleting nodes or modifying the AnimationPlayer while playing. + + For backward compatibility. See [constant AnimationMixer.ANIMATION_CALLBACK_MODE_METHOD_DEFERRED]. - - Make method calls immediately when reached in the animation. + + For backward compatibility. See [constant AnimationMixer.ANIMATION_CALLBACK_MODE_METHOD_IMMEDIATE]. diff --git a/doc/classes/AnimationTree.xml b/doc/classes/AnimationTree.xml index 88d2d8f5939..79e84192dfe 100644 --- a/doc/classes/AnimationTree.xml +++ b/doc/classes/AnimationTree.xml @@ -1,5 +1,5 @@ - + A node used for advanced animation transitions in an [AnimationPlayer]. @@ -12,220 +12,48 @@ https://godotengine.org/asset-library/asset/678 - - - - - - - + + - A virtual function for processing after key getting during playback. + For backward compatibility. See [enum AnimationMixer.AnimationCallbackModeProcess]. - + - + - Manually advance the animations by the specified time (in seconds). - - - - - - Retrieve the motion delta of position with the [member root_motion_track] as a [Vector3] that can be used elsewhere. - If [member root_motion_track] is not a path to a track of type [constant Animation.TYPE_POSITION_3D], returns [code]Vector3(0, 0, 0)[/code]. - See also [member root_motion_track] and [RootMotionView]. - The most basic example is applying position to [CharacterBody3D]: - [codeblocks] - [gdscript] - var current_rotation: Quaternion - - func _process(delta): - if Input.is_action_just_pressed("animate"): - current_rotation = get_quaternion() - state_machine.travel("Animate") - var velocity: Vector3 = current_rotation * animation_tree.get_root_motion_position() / delta - set_velocity(velocity) - move_and_slide() - [/gdscript] - [/codeblocks] - By using this in combination with [method get_root_motion_position_accumulator], you can apply the root motion position more correctly to account for the rotation of the node. - [codeblocks] - [gdscript] - func _process(delta): - if Input.is_action_just_pressed("animate"): - state_machine.travel("Animate") - set_quaternion(get_quaternion() * animation_tree.get_root_motion_rotation()) - var velocity: Vector3 = (animation_tree.get_root_motion_rotation_accumulator().inverse() * get_quaternion()) * animation_tree.get_root_motion_position() / delta - set_velocity(velocity) - move_and_slide() - [/gdscript] - [/codeblocks] - - - - - - Retrieve the blended value of the position tracks with the [member root_motion_track] as a [Vector3] that can be used elsewhere. - This is useful in cases where you want to respect the initial key values of the animation. - For example, if an animation with only one key [code]Vector3(0, 0, 0)[/code] is played in the previous frame and then an animation with only one key [code]Vector3(1, 0, 1)[/code] is played in the next frame, the difference can be calculated as follows: - [codeblocks] - [gdscript] - var prev_root_motion_position_accumulator: Vector3 - - func _process(delta): - if Input.is_action_just_pressed("animate"): - state_machine.travel("Animate") - var current_root_motion_position_accumulator: Vector3 = animation_tree.get_root_motion_position_accumulator() - var difference: Vector3 = current_root_motion_position_accumulator - prev_root_motion_position_accumulator - prev_root_motion_position_accumulator = current_root_motion_position_accumulator - transform.origin += difference - [/gdscript] - [/codeblocks] - However, if the animation loops, an unintended discrete change may occur, so this is only useful for some simple use cases. - - - - - - Retrieve the motion delta of rotation with the [member root_motion_track] as a [Quaternion] that can be used elsewhere. - If [member root_motion_track] is not a path to a track of type [constant Animation.TYPE_ROTATION_3D], returns [code]Quaternion(0, 0, 0, 1)[/code]. - See also [member root_motion_track] and [RootMotionView]. - The most basic example is applying rotation to [CharacterBody3D]: - [codeblocks] - [gdscript] - func _process(delta): - if Input.is_action_just_pressed("animate"): - state_machine.travel("Animate") - set_quaternion(get_quaternion() * animation_tree.get_root_motion_rotation()) - [/gdscript] - [/codeblocks] - - - - - - Retrieve the blended value of the rotation tracks with the [member root_motion_track] as a [Quaternion] that can be used elsewhere. - This is necessary to apply the root motion position correctly, taking rotation into account. See also [method get_root_motion_position]. - Also, this is useful in cases where you want to respect the initial key values of the animation. - For example, if an animation with only one key [code]Quaternion(0, 0, 0, 1)[/code] is played in the previous frame and then an animation with only one key [code]Quaternion(0, 0.707, 0, 0.707)[/code] is played in the next frame, the difference can be calculated as follows: - [codeblocks] - [gdscript] - var prev_root_motion_rotation_accumulator: Quaternion - - func _process(delta): - if Input.is_action_just_pressed("animate"): - state_machine.travel("Animate") - var current_root_motion_rotation_accumulator: Quaternion = animation_tree.get_root_motion_Quaternion_accumulator() - var difference: Quaternion = prev_root_motion_rotation_accumulator.inverse() * current_root_motion_rotation_accumulator - prev_root_motion_rotation_accumulator = current_root_motion_rotation_accumulator - transform.basis *= difference - [/gdscript] - [/codeblocks] - However, if the animation loops, an unintended discrete change may occur, so this is only useful for some simple use cases. - - - - - - Retrieve the motion delta of scale with the [member root_motion_track] as a [Vector3] that can be used elsewhere. - If [member root_motion_track] is not a path to a track of type [constant Animation.TYPE_SCALE_3D], returns [code]Vector3(0, 0, 0)[/code]. - See also [member root_motion_track] and [RootMotionView]. - The most basic example is applying scale to [CharacterBody3D]: - [codeblocks] - [gdscript] - var current_scale: Vector3 = Vector3(1, 1, 1) - var scale_accum: Vector3 = Vector3(1, 1, 1) - - func _process(delta): - if Input.is_action_just_pressed("animate"): - current_scale = get_scale() - scale_accum = Vector3(1, 1, 1) - state_machine.travel("Animate") - scale_accum += animation_tree.get_root_motion_scale() - set_scale(current_scale * scale_accum) - [/gdscript] - [/codeblocks] - - - - - - Retrieve the blended value of the scale tracks with the [member root_motion_track] as a [Vector3] that can be used elsewhere. - For example, if an animation with only one key [code]Vector3(1, 1, 1)[/code] is played in the previous frame and then an animation with only one key [code]Vector3(2, 2, 2)[/code] is played in the next frame, the difference can be calculated as follows: - [codeblocks] - [gdscript] - var prev_root_motion_scale_accumulator: Vector3 - - func _process(delta): - if Input.is_action_just_pressed("animate"): - state_machine.travel("Animate") - var current_root_motion_scale_accumulator: Vector3 = animation_tree.get_root_motion_scale_accumulator() - var difference: Vector3 = current_root_motion_scale_accumulator - prev_root_motion_scale_accumulator - prev_root_motion_scale_accumulator = current_root_motion_scale_accumulator - transform.basis = transform.basis.scaled(difference) - [/gdscript] - [/codeblocks] - However, if the animation loops, an unintended discrete change may occur, so this is only useful for some simple use cases. + For backward compatibility. See [enum AnimationMixer.AnimationCallbackModeProcess]. - - If [code]true[/code], the [AnimationTree] will be processing. - - The path to the [Node] used to evaluate the AnimationNode [Expression] if one is not explicitly specified internally. + The path to the [Node] used to evaluate the [AnimationNode] [Expression] if one is not explicitly specified internally. The path to the [AnimationPlayer] used for animating. - - The number of possible simultaneous sounds for each of the assigned AudioStreamPlayers. - For example, if this value is [code]32[/code] and the animation has two audio tracks, the two [AudioStreamPlayer]s assigned can play simultaneously up to [code]32[/code] voices each. - - - The process mode of this [AnimationTree]. See [enum AnimationProcessCallback] for available modes. - - - The path to the Animation track used for root motion. Paths must be valid scene-tree paths to a node, and must be specified starting from the parent node of the node that will reproduce the animation. To specify a track that controls properties or bones, append its name after the path, separated by [code]":"[/code]. For example, [code]"character/skeleton:ankle"[/code] or [code]"character/mesh:transform/local"[/code]. - If the track has type [constant Animation.TYPE_POSITION_3D], [constant Animation.TYPE_ROTATION_3D] or [constant Animation.TYPE_SCALE_3D] the transformation will be canceled visually, and the animation will appear to stay in place. See also [method get_root_motion_position], [method get_root_motion_rotation], [method get_root_motion_scale] and [RootMotionView]. - - - The root animation node of this [AnimationTree]. See [AnimationNode]. + + + The root animation node of this [AnimationTree]. See [AnimationRootNode]. - - - - Notifies when an animation finished playing. - [b]Note:[/b] This signal is not emitted if an animation is looping or aborted. Also be aware of the possibility of unseen playback by sync and xfade. - - Emitted when the [member anim_player] is changed. - - - - Notifies when an animation starts playing. - [b]Note:[/b] This signal is not emitted if an animation is looping or playbacked from the middle. Also be aware of the possibility of unseen playback by sync and xfade. - - - - The animations will progress during physics frames (see [constant Node.NOTIFICATION_INTERNAL_PHYSICS_PROCESS]). + + For backward compatibility. See [constant AnimationMixer.ANIMATION_CALLBACK_MODE_PROCESS_PHYSICS]. - - The animations will progress during process frames (see [constant Node.NOTIFICATION_INTERNAL_PROCESS]). + + For backward compatibility. See [constant AnimationMixer.ANIMATION_CALLBACK_MODE_PROCESS_IDLE]. - - The animations will only progress manually (see [method advance]). + + For backward compatibility. See [constant AnimationMixer.ANIMATION_CALLBACK_MODE_PROCESS_MANUAL]. diff --git a/doc/classes/RootMotionView.xml b/doc/classes/RootMotionView.xml index 20485ed08f8..648ea04a7dd 100644 --- a/doc/classes/RootMotionView.xml +++ b/doc/classes/RootMotionView.xml @@ -1,10 +1,10 @@ - Editor-only helper for setting up root motion in [AnimationTree]. + Editor-only helper for setting up root motion in [AnimationMixer]. - [i]Root motion[/i] refers to an animation technique where a mesh's skeleton is used to give impulse to a character. When working with 3D animations, a popular technique is for animators to use the root skeleton bone to give motion to the rest of the skeleton. This allows animating characters in a way where steps actually match the floor below. It also allows precise interaction with objects during cinematics. See also [AnimationTree]. + [i]Root motion[/i] refers to an animation technique where a mesh's skeleton is used to give impulse to a character. When working with 3D animations, a popular technique is for animators to use the root skeleton bone to give motion to the rest of the skeleton. This allows animating characters in a way where steps actually match the floor below. It also allows precise interaction with objects during cinematics. See also [AnimationMixer]. [b]Note:[/b] [RootMotionView] is only visible in the editor. It will be hidden automatically in the running project. @@ -12,7 +12,7 @@ - Path to an [AnimationTree] node to use as a basis for root motion. + Path to an [AnimationMixer] node to use as a basis for root motion. The grid's cell size in 3D units. diff --git a/doc/classes/StaticBody2D.xml b/doc/classes/StaticBody2D.xml index ac297545081..e732d4d58fa 100644 --- a/doc/classes/StaticBody2D.xml +++ b/doc/classes/StaticBody2D.xml @@ -4,7 +4,7 @@ A 2D physics body that can't be moved by external forces. When moved manually, it doesn't affect other bodies in its path. - A static 2D physics body. It can't be moved by external forces or contacts, but can be moved manually by other means such as code, [AnimationPlayer]s (with [member AnimationPlayer.playback_process_mode] set to [constant AnimationPlayer.ANIMATION_PROCESS_PHYSICS]), and [RemoteTransform2D]. + A static 2D physics body. It can't be moved by external forces or contacts, but can be moved manually by other means such as code, [AnimationMixer]s (with [member AnimationMixer.callback_mode_process] set to [constant AnimationMixer.ANIMATION_CALLBACK_MODE_PROCESS_PHYSICS]), and [RemoteTransform2D]. When [StaticBody2D] is moved, it is teleported to its new position without affecting other physics bodies in its path. If this is not desired, use [AnimatableBody2D] instead. [StaticBody2D] is useful for completely static objects like floors and walls, as well as moving surfaces like conveyor belts and circular revolving platforms (by using [member constant_linear_velocity] and [member constant_angular_velocity]). diff --git a/doc/classes/StaticBody3D.xml b/doc/classes/StaticBody3D.xml index c2fd5e03bdd..81eda8505b8 100644 --- a/doc/classes/StaticBody3D.xml +++ b/doc/classes/StaticBody3D.xml @@ -4,7 +4,7 @@ A 3D physics body that can't be moved by external forces. When moved manually, it doesn't affect other bodies in its path. - A static 3D physics body. It can't be moved by external forces or contacts, but can be moved manually by other means such as code, [AnimationPlayer]s (with [member AnimationPlayer.playback_process_mode] set to [constant AnimationPlayer.ANIMATION_PROCESS_PHYSICS]), and [RemoteTransform3D]. + A static 3D physics body. It can't be moved by external forces or contacts, but can be moved manually by other means such as code, [AnimationMixer]s (with [member AnimationMixer.callback_mode_process] set to [constant AnimationMixer.ANIMATION_CALLBACK_MODE_PROCESS_PHYSICS]), and [RemoteTransform3D]. When [StaticBody3D] is moved, it is teleported to its new position without affecting other physics bodies in its path. If this is not desired, use [AnimatableBody3D] instead. [StaticBody3D] is useful for completely static objects like floors and walls, as well as moving surfaces like conveyor belts and circular revolving platforms (by using [member constant_linear_velocity] and [member constant_angular_velocity]). diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index 4f6e7c4b91d..7105ff280a3 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -2695,7 +2695,7 @@ void AnimationTrackEdit::gui_input(const Ref &p_event) { AnimationPlayer *ap = ape->get_player(); if (ap) { NodePath npath = animation->track_get_path(track); - Node *nd = ap->get_node(ap->get_root())->get_node(NodePath(npath.get_concatenated_names())); + Node *nd = ap->get_node(ap->get_root_node())->get_node(NodePath(npath.get_concatenated_names())); StringName prop = npath.get_concatenated_subnames(); PropertyInfo prop_info; ClassDB::get_property_info(nd->get_class(), prop, &prop_info); @@ -4000,13 +4000,15 @@ Ref AnimationTrackEditor::_create_and_get_reset_animation() { return player->get_animation(SceneStringNames::get_singleton()->RESET); } else { Ref al; - if (!player->has_animation_library("")) { - al.instantiate(); - player->add_animation_library("", al); - } else { - al = player->get_animation_library(""); + AnimationMixer *mixer = AnimationPlayerEditor::get_singleton()->fetch_mixer_for_library(); + if (mixer) { + if (!mixer->has_animation_library("")) { + al.instantiate(); + mixer->add_animation_library("", al); + } else { + al = mixer->get_animation_library(""); + } } - Ref reset_anim; reset_anim.instantiate(); reset_anim->set_length(ANIM_MIN_LENGTH); @@ -4293,6 +4295,14 @@ void AnimationTrackEditor::show_select_node_warning(bool p_show) { info_message->set_visible(p_show); } +void AnimationTrackEditor::show_dummy_player_warning(bool p_show) { + dummy_player_warning->set_visible(p_show); +} + +void AnimationTrackEditor::show_inactive_player_warning(bool p_show) { + inactive_player_warning->set_visible(p_show); +} + bool AnimationTrackEditor::is_key_selected(int p_track, int p_key) const { SelectedKey sk; sk.key = p_key; @@ -4626,6 +4636,8 @@ void AnimationTrackEditor::_notification(int p_what) { view_group->set_icon(get_editor_theme_icon(view_group->is_pressed() ? SNAME("AnimationTrackList") : SNAME("AnimationTrackGroup"))); selected_filter->set_icon(get_editor_theme_icon(SNAME("AnimationFilter"))); imported_anim_warning->set_icon(get_editor_theme_icon(SNAME("NodeWarning"))); + dummy_player_warning->set_icon(get_editor_theme_icon(SNAME("NodeWarning"))); + inactive_player_warning->set_icon(get_editor_theme_icon(SNAME("NodeWarning"))); main_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("Tree"))); edit->get_popup()->set_item_icon(edit->get_popup()->get_item_index(EDIT_APPLY_RESET), get_editor_theme_icon(SNAME("Reload"))); } break; @@ -6303,8 +6315,17 @@ float AnimationTrackEditor::snap_time(float p_value, bool p_relative) { void AnimationTrackEditor::_show_imported_anim_warning() { // It looks terrible on a single line but the TTR extractor doesn't support line breaks yet. EditorNode::get_singleton()->show_warning( - TTR("This animation belongs to an imported scene, so changes to imported tracks will not be saved.\n\nTo modify this animation, navigate to the scene's Advanced Import settings and select the animation.\nSome options, including looping, are available here. To add custom tracks, enable \"Save To File\" and\n\"Keep Custom Tracks\"."), - TTR("Warning: Editing imported animation")); + TTR("This animation belongs to an imported scene, so changes to imported tracks will not be saved.\n\nTo modify this animation, navigate to the scene's Advanced Import settings and select the animation.\nSome options, including looping, are available here. To add custom tracks, enable \"Save To File\" and\n\"Keep Custom Tracks\".")); +} + +void AnimationTrackEditor::_show_dummy_player_warning() { + EditorNode::get_singleton()->show_warning( + TTR("Some AnimationPlayerEditor's options are disabled since this is the dummy AnimationPlayer for preview.\n\nThe dummy player is forced active, non-deterministic and doesn't have the root motion track. Furthermore, the original node is inactive temporary.")); +} + +void AnimationTrackEditor::_show_inactive_player_warning() { + EditorNode::get_singleton()->show_warning( + TTR("AnimationPlayer is inactive. The playback will not be processed.")); } void AnimationTrackEditor::_select_all_tracks_for_copy() { @@ -6489,6 +6510,20 @@ AnimationTrackEditor::AnimationTrackEditor() { imported_anim_warning->connect("pressed", callable_mp(this, &AnimationTrackEditor::_show_imported_anim_warning)); bottom_hb->add_child(imported_anim_warning); + dummy_player_warning = memnew(Button); + dummy_player_warning->hide(); + dummy_player_warning->set_text(TTR("Dummy Player")); + dummy_player_warning->set_tooltip_text(TTR("Warning: Editing dummy AnimationPlayer")); + dummy_player_warning->connect("pressed", callable_mp(this, &AnimationTrackEditor::_show_dummy_player_warning)); + bottom_hb->add_child(dummy_player_warning); + + inactive_player_warning = memnew(Button); + inactive_player_warning->hide(); + inactive_player_warning->set_text(TTR("Inactive Player")); + inactive_player_warning->set_tooltip_text(TTR("Warning: AnimationPlayer is inactive")); + inactive_player_warning->connect("pressed", callable_mp(this, &AnimationTrackEditor::_show_inactive_player_warning)); + bottom_hb->add_child(inactive_player_warning); + bottom_hb->add_spacer(); bezier_edit_icon = memnew(Button); diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h index cef726f6c01..53270995172 100644 --- a/editor/animation_track_editor.h +++ b/editor/animation_track_editor.h @@ -387,6 +387,12 @@ class AnimationTrackEditor : public VBoxContainer { Button *imported_anim_warning = nullptr; void _show_imported_anim_warning(); + Button *dummy_player_warning = nullptr; + void _show_dummy_player_warning(); + + Button *inactive_player_warning = nullptr; + void _show_inactive_player_warning(); + void _snap_mode_changed(int p_mode); Vector track_edits; Vector groups; @@ -645,6 +651,8 @@ public: void commit_insert_queue(); void show_select_node_warning(bool p_show); + void show_dummy_player_warning(bool p_show); + void show_inactive_player_warning(bool p_show); bool is_key_selected(int p_track, int p_key) const; bool is_selection_active() const; diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index b2763ab1732..e69dcb22781 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -1701,16 +1701,19 @@ int EditorNode::_save_external_resources() { return saved; } -static void _reset_animation_players(Node *p_node, List> *r_anim_backups) { +static void _reset_animation_mixers(Node *p_node, List>> *r_anim_backups) { for (int i = 0; i < p_node->get_child_count(); i++) { - AnimationPlayer *player = Object::cast_to(p_node->get_child(i)); - if (player && player->is_reset_on_save_enabled() && player->can_apply_reset()) { - Ref old_values = player->apply_reset(); - if (old_values.is_valid()) { - r_anim_backups->push_back(old_values); + AnimationMixer *mixer = Object::cast_to(p_node->get_child(i)); + if (mixer && mixer->is_reset_on_save_enabled() && mixer->can_apply_reset()) { + Ref backup = mixer->apply_reset(); + if (backup.is_valid()) { + Pair> pair; + pair.first = mixer; + pair.second = backup; + r_anim_backups->push_back(pair); } } - _reset_animation_players(p_node->get_child(i), r_anim_backups); + _reset_animation_mixers(p_node->get_child(i), r_anim_backups); } } @@ -1730,8 +1733,8 @@ void EditorNode::_save_scene(String p_file, int idx) { scene->propagate_notification(NOTIFICATION_EDITOR_PRE_SAVE); editor_data.apply_changes_in_editors(); - List> anim_backups; - _reset_animation_players(scene, &anim_backups); + List>> anim_backups; + _reset_animation_mixers(scene, &anim_backups); save_default_environment(); _save_editor_states(p_file, idx); @@ -1773,8 +1776,8 @@ void EditorNode::_save_scene(String p_file, int idx) { _save_external_resources(); editor_data.save_editor_external_data(); - for (Ref &E : anim_backups) { - E->restore(); + for (Pair> &E : anim_backups) { + E.first->restore(E.second); } if (err == OK) { diff --git a/editor/gui/scene_tree_editor.cpp b/editor/gui/scene_tree_editor.cpp index fff46904454..26fdb74bd1f 100644 --- a/editor/gui/scene_tree_editor.cpp +++ b/editor/gui/scene_tree_editor.cpp @@ -115,7 +115,7 @@ void SceneTreeEditor::_cell_button_pressed(Object *p_item, int p_column, int p_i } undo_redo->commit_action(); } else if (p_id == BUTTON_PIN) { - if (n->is_class("AnimationPlayer")) { + if (n->is_class("AnimationMixer")) { AnimationPlayerEditor::get_singleton()->unpin(); _update_tree(); } @@ -465,8 +465,8 @@ void SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent) { } _update_visibility_color(p_node, item); - } else if (p_node->is_class("AnimationPlayer")) { - bool is_pinned = AnimationPlayerEditor::get_singleton()->get_player() == p_node && AnimationPlayerEditor::get_singleton()->is_pinned(); + } else if (p_node->is_class("AnimationMixer")) { + bool is_pinned = AnimationPlayerEditor::get_singleton()->get_editing_node() == p_node && AnimationPlayerEditor::get_singleton()->is_pinned(); if (is_pinned) { item->add_button(0, get_editor_theme_icon(SNAME("Pin")), BUTTON_PIN, false, TTR("AnimationPlayer is pinned.\nClick to unpin.")); diff --git a/editor/icons/AnimationMixer.svg b/editor/icons/AnimationMixer.svg new file mode 100644 index 00000000000..61f5bf6474f --- /dev/null +++ b/editor/icons/AnimationMixer.svg @@ -0,0 +1 @@ + diff --git a/editor/import/post_import_plugin_skeleton_renamer.cpp b/editor/import/post_import_plugin_skeleton_renamer.cpp index 59e07a71171..0c48c8ef76e 100644 --- a/editor/import/post_import_plugin_skeleton_renamer.cpp +++ b/editor/import/post_import_plugin_skeleton_renamer.cpp @@ -102,7 +102,7 @@ void PostImportPluginSkeletonRenamer::_internal_process(InternalImportCategory p continue; } String track_path = String(anim->track_get_path(i).get_concatenated_names()); - Node *node = (ap->get_node(ap->get_root()))->get_node(NodePath(track_path)); + Node *node = (ap->get_node(ap->get_root_node()))->get_node(NodePath(track_path)); if (node) { Skeleton3D *track_skeleton = Object::cast_to(node); if (track_skeleton && track_skeleton == skeleton) { @@ -219,8 +219,8 @@ void PostImportPluginSkeletonRenamer::internal_process(InternalImportCategory p_ int track_len = anim->get_track_count(); for (int i = 0; i < track_len; i++) { String track_path = String(anim->track_get_path(i).get_concatenated_names()); - Node *orig_node = (ap->get_node(ap->get_root()))->get_node(NodePath(track_path)); - Node *node = (ap->get_node(ap->get_root()))->get_node(NodePath(track_path)); + Node *orig_node = (ap->get_node(ap->get_root_node()))->get_node(NodePath(track_path)); + Node *node = (ap->get_node(ap->get_root_node()))->get_node(NodePath(track_path)); while (node) { Skeleton3D *track_skeleton = Object::cast_to(node); if (track_skeleton && track_skeleton == skeleton) { diff --git a/editor/import/post_import_plugin_skeleton_rest_fixer.cpp b/editor/import/post_import_plugin_skeleton_rest_fixer.cpp index bffc100faf1..cd31499b800 100644 --- a/editor/import/post_import_plugin_skeleton_rest_fixer.cpp +++ b/editor/import/post_import_plugin_skeleton_rest_fixer.cpp @@ -146,7 +146,7 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory } String track_path = String(anim->track_get_path(i).get_concatenated_names()); - Node *node = (ap->get_node(ap->get_root()))->get_node(NodePath(track_path)); + Node *node = (ap->get_node(ap->get_root_node()))->get_node(NodePath(track_path)); ERR_CONTINUE(!node); Skeleton3D *track_skeleton = Object::cast_to(node); @@ -213,7 +213,7 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory continue; } track_path = String(anim->track_get_path(i).get_concatenated_names()); - Node *node = (ap->get_node(ap->get_root()))->get_node(NodePath(track_path)); + Node *node = (ap->get_node(ap->get_root_node()))->get_node(NodePath(track_path)); if (node) { Skeleton3D *track_skeleton = Object::cast_to(node); if (track_skeleton && track_skeleton == src_skeleton) { @@ -388,7 +388,7 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory } String track_path = String(anim->track_get_path(i).get_concatenated_names()); - Node *node = (ap->get_node(ap->get_root()))->get_node(NodePath(track_path)); + Node *node = (ap->get_node(ap->get_root_node()))->get_node(NodePath(track_path)); ERR_CONTINUE(!node); Skeleton3D *track_skeleton = Object::cast_to(node); @@ -448,7 +448,7 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory } String track_path = String(anim->track_get_path(i).get_concatenated_names()); - Node *node = (ap->get_node(ap->get_root()))->get_node(NodePath(track_path)); + Node *node = (ap->get_node(ap->get_root_node()))->get_node(NodePath(track_path)); ERR_CONTINUE(!node); Skeleton3D *track_skeleton = Object::cast_to(node); @@ -544,7 +544,7 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory } String track_path = String(anim->track_get_path(i).get_concatenated_names()); - Node *node = (ap->get_node(ap->get_root()))->get_node(NodePath(track_path)); + Node *node = (ap->get_node(ap->get_root_node()))->get_node(NodePath(track_path)); ERR_CONTINUE(!node); Skeleton3D *track_skeleton = Object::cast_to(node); diff --git a/editor/import/post_import_plugin_skeleton_track_organizer.cpp b/editor/import/post_import_plugin_skeleton_track_organizer.cpp index 829d97dea7d..e5a8e879fce 100644 --- a/editor/import/post_import_plugin_skeleton_track_organizer.cpp +++ b/editor/import/post_import_plugin_skeleton_track_organizer.cpp @@ -78,7 +78,7 @@ void PostImportPluginSkeletonTrackOrganizer::internal_process(InternalImportCate Vector remove_indices; for (int i = 0; i < track_len; i++) { String track_path = String(anim->track_get_path(i).get_concatenated_names()); - Node *node = (ap->get_node(ap->get_root()))->get_node(NodePath(track_path)); + Node *node = (ap->get_node(ap->get_root_node()))->get_node(NodePath(track_path)); if (!node) { if (remove_except_bone) { remove_indices.push_back(i); diff --git a/editor/import/resource_importer_scene.cpp b/editor/import/resource_importer_scene.cpp index 452a6127537..2f889287f93 100644 --- a/editor/import/resource_importer_scene.cpp +++ b/editor/import/resource_importer_scene.cpp @@ -595,7 +595,7 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, HashMap(p_node); // Node paths in animation tracks are relative to the following path (this is used to fix node paths below). - Node *ap_root = ap->get_node(ap->get_root()); + Node *ap_root = ap->get_node(ap->get_root_node()); NodePath path_prefix = p_root->get_path_to(ap_root); bool nodes_were_renamed = r_node_renames.size() != 0; diff --git a/editor/plugins/animation_blend_space_1d_editor.cpp b/editor/plugins/animation_blend_space_1d_editor.cpp index 89c21ddb682..184cc5302a7 100644 --- a/editor/plugins/animation_blend_space_1d_editor.cpp +++ b/editor/plugins/animation_blend_space_1d_editor.cpp @@ -78,18 +78,12 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_gui_input(const Refadd_submenu_item(TTR("Add Animation"), "animations"); - if (tree->has_node(tree->get_animation_player())) { - AnimationPlayer *ap = Object::cast_to(tree->get_node(tree->get_animation_player())); + List names; + tree->get_animation_list(&names); - if (ap) { - List names; - ap->get_animation_list(&names); - - for (const StringName &E : names) { - animations_menu->add_icon_item(get_editor_theme_icon(SNAME("Animation")), E); - animations_to_add.push_back(E); - } - } + for (const StringName &E : names) { + animations_menu->add_icon_item(get_editor_theme_icon(SNAME("Animation")), E); + animations_to_add.push_back(E); } for (const StringName &E : classes) { diff --git a/editor/plugins/animation_blend_space_2d_editor.cpp b/editor/plugins/animation_blend_space_2d_editor.cpp index 3bc5e0c8071..c987d7d6b69 100644 --- a/editor/plugins/animation_blend_space_2d_editor.cpp +++ b/editor/plugins/animation_blend_space_2d_editor.cpp @@ -123,16 +123,11 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Refadd_submenu_item(TTR("Add Animation"), "animations"); - if (tree->has_node(tree->get_animation_player())) { - AnimationPlayer *ap = Object::cast_to(tree->get_node(tree->get_animation_player())); - if (ap) { - List names; - ap->get_animation_list(&names); - for (const StringName &E : names) { - animations_menu->add_icon_item(get_editor_theme_icon(SNAME("Animation")), E); - animations_to_add.push_back(E); - } - } + List names; + tree->get_animation_list(&names); + for (const StringName &E : names) { + animations_menu->add_icon_item(get_editor_theme_icon(SNAME("Animation")), E); + animations_to_add.push_back(E); } for (const StringName &E : classes) { diff --git a/editor/plugins/animation_blend_tree_editor_plugin.cpp b/editor/plugins/animation_blend_tree_editor_plugin.cpp index 157278e4bc0..dc3729393f0 100644 --- a/editor/plugins/animation_blend_tree_editor_plugin.cpp +++ b/editor/plugins/animation_blend_tree_editor_plugin.cpp @@ -238,21 +238,16 @@ void AnimationNodeBlendTreeEditor::update_graph() { ProgressBar *pb = memnew(ProgressBar); - if (tree->has_node(tree->get_animation_player())) { - AnimationPlayer *ap = Object::cast_to(tree->get_node(tree->get_animation_player())); - if (ap) { - List anims; - ap->get_animation_list(&anims); + List anims; + tree->get_animation_list(&anims); - for (const StringName &F : anims) { - mb->get_popup()->add_item(F); - options.push_back(F); - } + for (const StringName &F : anims) { + mb->get_popup()->add_item(F); + options.push_back(F); + } - if (ap->has_animation(anim->get_animation())) { - pb->set_max(ap->get_animation(anim->get_animation())->get_length()); - } - } + if (tree->has_animation(anim->get_animation())) { + pb->set_max(tree->get_animation(anim->get_animation())->get_length()); } pb->set_show_percentage(false); @@ -625,21 +620,7 @@ bool AnimationNodeBlendTreeEditor::_update_filters(const Ref &ano return false; } - NodePath player_path = tree->get_animation_player(); - - if (!tree->has_node(player_path)) { - EditorNode::get_singleton()->show_warning(TTR("No animation player set, so unable to retrieve track names.")); - return false; - } - - AnimationPlayer *player = Object::cast_to(tree->get_node(player_path)); - if (!player) { - EditorNode::get_singleton()->show_warning(TTR("Player path set is invalid, so unable to retrieve track names.")); - return false; - } - - Node *base = player->get_node(player->get_root()); - + Node *base = tree->get_node(tree->get_root_node()); if (!base) { EditorNode::get_singleton()->show_warning(TTR("Animation player has no valid root node path, so unable to retrieve track names.")); return false; @@ -651,10 +632,10 @@ bool AnimationNodeBlendTreeEditor::_update_filters(const Ref &ano HashMap> types; { List animation_list; - player->get_animation_list(&animation_list); + tree->get_animation_list(&animation_list); for (const StringName &E : animation_list) { - Ref anim = player->get_animation(E); + Ref anim = tree->get_animation(E); for (int i = 0; i < anim->get_track_count(); i++) { String track_path = anim->track_get_path(i); paths.insert(track_path); @@ -885,23 +866,16 @@ void AnimationNodeBlendTreeEditor::_notification(int p_what) { graph->set_connection_activity(E.output_node, 0, E.input_node, E.input_index, activity); } - AnimationPlayer *player = nullptr; - if (tree->has_node(tree->get_animation_player())) { - player = Object::cast_to(tree->get_node(tree->get_animation_player())); - } - - if (player) { - for (const KeyValue &E : animations) { - Ref an = blend_tree->get_node(E.key); - if (an.is_valid()) { - if (player->has_animation(an->get_animation())) { - Ref anim = player->get_animation(an->get_animation()); - if (anim.is_valid()) { - E.value->set_max(anim->get_length()); - //StringName path = AnimationTreeEditor::get_singleton()->get_base_path() + E.input_node; - StringName time_path = AnimationTreeEditor::get_singleton()->get_base_path() + String(E.key) + "/time"; - E.value->set_value(tree->get(time_path)); - } + for (const KeyValue &E : animations) { + Ref an = blend_tree->get_node(E.key); + if (an.is_valid()) { + if (tree->has_animation(an->get_animation())) { + Ref anim = tree->get_animation(an->get_animation()); + if (anim.is_valid()) { + E.value->set_max(anim->get_length()); + //StringName path = AnimationTreeEditor::get_singleton()->get_base_path() + E.input_node; + StringName time_path = AnimationTreeEditor::get_singleton()->get_base_path() + String(E.key) + "/time"; + E.value->set_value(tree->get(time_path)); } } } diff --git a/editor/plugins/animation_library_editor.cpp b/editor/plugins/animation_library_editor.cpp index 6a54bc654fd..c4c0799daad 100644 --- a/editor/plugins/animation_library_editor.cpp +++ b/editor/plugins/animation_library_editor.cpp @@ -36,8 +36,8 @@ #include "editor/editor_undo_redo_manager.h" #include "editor/gui/editor_file_dialog.h" -void AnimationLibraryEditor::set_animation_player(Object *p_player) { - player = p_player; +void AnimationLibraryEditor::set_animation_mixer(Object *p_mixer) { + mixer = p_mixer; } void AnimationLibraryEditor::_add_library() { @@ -54,7 +54,7 @@ void AnimationLibraryEditor::_add_library_validate(const String &p_name) { String error; if (adding_animation) { - Ref al = player->call("get_animation_library", adding_animation_to_library); + Ref al = mixer->call("get_animation_library", adding_animation_to_library); ERR_FAIL_COND(al.is_null()); if (p_name == "") { error = TTR("Animation name can't be empty."); @@ -64,11 +64,11 @@ void AnimationLibraryEditor::_add_library_validate(const String &p_name) { error = TTR("Animation with the same name already exists."); } } else { - if (p_name == "" && bool(player->call("has_animation_library", ""))) { + if (p_name == "" && bool(mixer->call("has_animation_library", ""))) { error = TTR("Enter a library name."); } else if (!AnimationLibrary::is_valid_library_name(p_name)) { error = TTR("Library name contains invalid characters: '/', ':', ',' or '['."); - } else if (bool(player->call("has_animation_library", p_name))) { + } else if (bool(mixer->call("has_animation_library", p_name))) { error = TTR("Library with the same name already exists."); } } @@ -97,7 +97,7 @@ void AnimationLibraryEditor::_add_library_confirm() { String anim_name = add_library_name->get_text(); EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - Ref al = player->call("get_animation_library", adding_animation_to_library); + Ref al = mixer->call("get_animation_library", adding_animation_to_library); ERR_FAIL_COND(!al.is_valid()); Ref anim; @@ -106,8 +106,8 @@ void AnimationLibraryEditor::_add_library_confirm() { undo_redo->create_action(vformat(TTR("Add Animation to Library: %s"), anim_name)); undo_redo->add_do_method(al.ptr(), "add_animation", anim_name, anim); undo_redo->add_undo_method(al.ptr(), "remove_animation", anim_name); - undo_redo->add_do_method(this, "_update_editor", player); - undo_redo->add_undo_method(this, "_update_editor", player); + undo_redo->add_do_method(this, "_update_editor", mixer); + undo_redo->add_undo_method(this, "_update_editor", mixer); undo_redo->commit_action(); } else { @@ -118,10 +118,10 @@ void AnimationLibraryEditor::_add_library_confirm() { al.instantiate(); undo_redo->create_action(vformat(TTR("Add Animation Library: %s"), lib_name)); - undo_redo->add_do_method(player, "add_animation_library", lib_name, al); - undo_redo->add_undo_method(player, "remove_animation_library", lib_name); - undo_redo->add_do_method(this, "_update_editor", player); - undo_redo->add_undo_method(this, "_update_editor", player); + undo_redo->add_do_method(mixer, "add_animation_library", lib_name, al); + undo_redo->add_undo_method(mixer, "remove_animation_library", lib_name); + undo_redo->add_do_method(this, "_update_editor", mixer); + undo_redo->add_undo_method(this, "_update_editor", mixer); undo_redo->commit_action(); } } @@ -144,7 +144,7 @@ void AnimationLibraryEditor::_load_library() { } void AnimationLibraryEditor::_file_popup_selected(int p_id) { - Ref al = player->call("get_animation_library", file_dialog_library); + Ref al = mixer->call("get_animation_library", file_dialog_library); Ref anim; if (file_dialog_animation != StringName()) { anim = al->get_animation(file_dialog_animation); @@ -214,12 +214,12 @@ void AnimationLibraryEditor::_file_popup_selected(int p_id) { EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(vformat(TTR("Make Animation Library Unique: %s"), lib_name)); - undo_redo->add_do_method(player, "remove_animation_library", lib_name); - undo_redo->add_do_method(player, "add_animation_library", lib_name, ald); - undo_redo->add_undo_method(player, "remove_animation_library", lib_name); - undo_redo->add_undo_method(player, "add_animation_library", lib_name, al); - undo_redo->add_do_method(this, "_update_editor", player); - undo_redo->add_undo_method(this, "_update_editor", player); + undo_redo->add_do_method(mixer, "remove_animation_library", lib_name); + undo_redo->add_do_method(mixer, "add_animation_library", lib_name, ald); + undo_redo->add_undo_method(mixer, "remove_animation_library", lib_name); + undo_redo->add_undo_method(mixer, "add_animation_library", lib_name, al); + undo_redo->add_do_method(this, "_update_editor", mixer); + undo_redo->add_undo_method(this, "_update_editor", mixer); undo_redo->commit_action(); update_tree(); @@ -287,8 +287,8 @@ void AnimationLibraryEditor::_file_popup_selected(int p_id) { undo_redo->add_do_method(al.ptr(), "add_animation", anim_name, animd); undo_redo->add_undo_method(al.ptr(), "remove_animation", anim_name); undo_redo->add_undo_method(al.ptr(), "add_animation", anim_name, anim); - undo_redo->add_do_method(this, "_update_editor", player); - undo_redo->add_undo_method(this, "_update_editor", player); + undo_redo->add_do_method(this, "_update_editor", mixer); + undo_redo->add_undo_method(this, "_update_editor", mixer); undo_redo->commit_action(); update_tree(); @@ -308,12 +308,12 @@ void AnimationLibraryEditor::_load_file(String p_path) { return; } - TypedArray libs = player->call("get_animation_library_list"); + TypedArray libs = mixer->call("get_animation_library_list"); for (int i = 0; i < libs.size(); i++) { const StringName K = libs[i]; - Ref al2 = player->call("get_animation_library", K); + Ref al2 = mixer->call("get_animation_library", K); if (al2 == al) { - error_dialog->set_text(TTR("This library is already added to the player.")); + error_dialog->set_text(TTR("This library is already added to the mixer.")); error_dialog->popup_centered(); return; @@ -324,7 +324,7 @@ void AnimationLibraryEditor::_load_file(String p_path) { int attempt = 1; - while (bool(player->call("has_animation_library", name))) { + while (bool(mixer->call("has_animation_library", name))) { attempt++; name = p_path.get_file().get_basename() + " " + itos(attempt); } @@ -332,10 +332,10 @@ void AnimationLibraryEditor::_load_file(String p_path) { EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(vformat(TTR("Add Animation Library: %s"), name)); - undo_redo->add_do_method(player, "add_animation_library", name, al); - undo_redo->add_undo_method(player, "remove_animation_library", name); - undo_redo->add_do_method(this, "_update_editor", player); - undo_redo->add_undo_method(this, "_update_editor", player); + undo_redo->add_do_method(mixer, "add_animation_library", name, al); + undo_redo->add_undo_method(mixer, "remove_animation_library", name); + undo_redo->add_do_method(this, "_update_editor", mixer); + undo_redo->add_undo_method(this, "_update_editor", mixer); undo_redo->commit_action(); } break; case FILE_DIALOG_ACTION_OPEN_ANIMATION: { @@ -346,7 +346,7 @@ void AnimationLibraryEditor::_load_file(String p_path) { return; } - Ref al = player->call("get_animation_library", adding_animation_to_library); + Ref al = mixer->call("get_animation_library", adding_animation_to_library); List anims; al->get_animation_list(&anims); for (const StringName &K : anims) { @@ -372,13 +372,13 @@ void AnimationLibraryEditor::_load_file(String p_path) { undo_redo->create_action(vformat(TTR("Load Animation into Library: %s"), name)); undo_redo->add_do_method(al.ptr(), "add_animation", name, anim); undo_redo->add_undo_method(al.ptr(), "remove_animation", name); - undo_redo->add_do_method(this, "_update_editor", player); - undo_redo->add_undo_method(this, "_update_editor", player); + undo_redo->add_do_method(this, "_update_editor", mixer); + undo_redo->add_undo_method(this, "_update_editor", mixer); undo_redo->commit_action(); } break; case FILE_DIALOG_ACTION_SAVE_LIBRARY: { - Ref al = player->call("get_animation_library", file_dialog_library); + Ref al = mixer->call("get_animation_library", file_dialog_library); String prev_path = al->get_path(); EditorNode::get_singleton()->save_resource_in_path(al, p_path); @@ -388,14 +388,14 @@ void AnimationLibraryEditor::_load_file(String p_path) { undo_redo->create_action(vformat(TTR("Save Animation library to File: %s"), file_dialog_library)); undo_redo->add_do_method(al.ptr(), "set_path", al->get_path()); undo_redo->add_undo_method(al.ptr(), "set_path", prev_path); - undo_redo->add_do_method(this, "_update_editor", player); - undo_redo->add_undo_method(this, "_update_editor", player); + undo_redo->add_do_method(this, "_update_editor", mixer); + undo_redo->add_undo_method(this, "_update_editor", mixer); undo_redo->commit_action(); } } break; case FILE_DIALOG_ACTION_SAVE_ANIMATION: { - Ref al = player->call("get_animation_library", file_dialog_library); + Ref al = mixer->call("get_animation_library", file_dialog_library); Ref anim; if (file_dialog_animation != StringName()) { anim = al->get_animation(file_dialog_animation); @@ -409,8 +409,8 @@ void AnimationLibraryEditor::_load_file(String p_path) { undo_redo->create_action(vformat(TTR("Save Animation to File: %s"), file_dialog_animation)); undo_redo->add_do_method(anim.ptr(), "set_path", anim->get_path()); undo_redo->add_undo_method(anim.ptr(), "set_path", prev_path); - undo_redo->add_do_method(this, "_update_editor", player); - undo_redo->add_undo_method(this, "_update_editor", player); + undo_redo->add_do_method(this, "_update_editor", mixer); + undo_redo->add_undo_method(this, "_update_editor", mixer); undo_redo->commit_action(); } } break; @@ -430,14 +430,14 @@ void AnimationLibraryEditor::_item_renamed() { if (ti->get_parent() == tree->get_root()) { // Renamed library - if (player->call("has_animation_library", text)) { + if (mixer->call("has_animation_library", text)) { restore_text = true; } else { undo_redo->create_action(vformat(TTR("Rename Animation Library: %s"), text)); - undo_redo->add_do_method(player, "rename_animation_library", old_text, text); - undo_redo->add_undo_method(player, "rename_animation_library", text, old_text); - undo_redo->add_do_method(this, "_update_editor", player); - undo_redo->add_undo_method(this, "_update_editor", player); + undo_redo->add_do_method(mixer, "rename_animation_library", old_text, text); + undo_redo->add_undo_method(mixer, "rename_animation_library", text, old_text); + undo_redo->add_do_method(this, "_update_editor", mixer); + undo_redo->add_undo_method(this, "_update_editor", mixer); updating = true; undo_redo->commit_action(); updating = false; @@ -451,7 +451,7 @@ void AnimationLibraryEditor::_item_renamed() { } else { // Renamed anim StringName library = ti->get_parent()->get_metadata(0); - Ref al = player->call("get_animation_library", library); + Ref al = mixer->call("get_animation_library", library); if (al.is_valid()) { if (al->has_animation(text)) { @@ -460,8 +460,8 @@ void AnimationLibraryEditor::_item_renamed() { undo_redo->create_action(vformat(TTR("Rename Animation: %s"), text)); undo_redo->add_do_method(al.ptr(), "rename_animation", old_text, text); undo_redo->add_undo_method(al.ptr(), "rename_animation", text, old_text); - undo_redo->add_do_method(this, "_update_editor", player); - undo_redo->add_undo_method(this, "_update_editor", player); + undo_redo->add_do_method(this, "_update_editor", mixer); + undo_redo->add_undo_method(this, "_update_editor", mixer); updating = true; undo_redo->commit_action(); updating = false; @@ -483,7 +483,7 @@ void AnimationLibraryEditor::_button_pressed(TreeItem *p_item, int p_column, int if (p_item->get_parent() == tree->get_root()) { // Library StringName lib_name = p_item->get_metadata(0); - Ref al = player->call("get_animation_library", lib_name); + Ref al = mixer->call("get_animation_library", lib_name); switch (p_id) { case LIB_BUTTON_ADD: { add_library_dialog->set_title(TTR("Animation Name:")); @@ -541,8 +541,8 @@ void AnimationLibraryEditor::_button_pressed(TreeItem *p_item, int p_column, int undo_redo->create_action(vformat(TTR("Add Animation to Library: %s"), name)); undo_redo->add_do_method(al.ptr(), "add_animation", name, anim); undo_redo->add_undo_method(al.ptr(), "remove_animation", name); - undo_redo->add_do_method(this, "_update_editor", player); - undo_redo->add_undo_method(this, "_update_editor", player); + undo_redo->add_do_method(this, "_update_editor", mixer); + undo_redo->add_undo_method(this, "_update_editor", mixer); undo_redo->commit_action(); } break; @@ -564,10 +564,10 @@ void AnimationLibraryEditor::_button_pressed(TreeItem *p_item, int p_column, int case LIB_BUTTON_DELETE: { EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(vformat(TTR("Remove Animation Library: %s"), lib_name)); - undo_redo->add_do_method(player, "remove_animation_library", lib_name); - undo_redo->add_undo_method(player, "add_animation_library", lib_name, al); - undo_redo->add_do_method(this, "_update_editor", player); - undo_redo->add_undo_method(this, "_update_editor", player); + undo_redo->add_do_method(mixer, "remove_animation_library", lib_name); + undo_redo->add_undo_method(mixer, "add_animation_library", lib_name, al); + undo_redo->add_do_method(this, "_update_editor", mixer); + undo_redo->add_undo_method(this, "_update_editor", mixer); undo_redo->commit_action(); } break; } @@ -576,7 +576,7 @@ void AnimationLibraryEditor::_button_pressed(TreeItem *p_item, int p_column, int // Animation StringName lib_name = p_item->get_parent()->get_metadata(0); StringName anim_name = p_item->get_metadata(0); - Ref al = player->call("get_animation_library", lib_name); + Ref al = mixer->call("get_animation_library", lib_name); Ref anim = al->get_animation(anim_name); ERR_FAIL_COND(!anim.is_valid()); switch (p_id) { @@ -607,8 +607,8 @@ void AnimationLibraryEditor::_button_pressed(TreeItem *p_item, int p_column, int undo_redo->create_action(vformat(TTR("Remove Animation from Library: %s"), anim_name)); undo_redo->add_do_method(al.ptr(), "remove_animation", anim_name); undo_redo->add_undo_method(al.ptr(), "add_animation", anim_name, anim); - undo_redo->add_do_method(this, "_update_editor", player); - undo_redo->add_undo_method(this, "_update_editor", player); + undo_redo->add_do_method(this, "_update_editor", mixer); + undo_redo->add_undo_method(this, "_update_editor", mixer); undo_redo->commit_action(); } break; } @@ -621,12 +621,12 @@ void AnimationLibraryEditor::update_tree() { } tree->clear(); - ERR_FAIL_NULL(player); + ERR_FAIL_NULL(mixer); Color ss_color = get_theme_color(SNAME("prop_subsection"), EditorStringName(Editor)); TreeItem *root = tree->create_item(); - TypedArray libs = player->call("get_animation_library_list"); + TypedArray libs = mixer->call("get_animation_library_list"); for (int i = 0; i < libs.size(); i++) { const StringName K = libs[i]; @@ -638,7 +638,7 @@ void AnimationLibraryEditor::update_tree() { libitem->set_suffix(0, ""); } - Ref al = player->call("get_animation_library", K); + Ref al = mixer->call("get_animation_library", K); bool animation_library_is_foreign = false; String al_path = al->get_path(); if (!al_path.is_resource_file()) { @@ -727,12 +727,12 @@ void AnimationLibraryEditor::show_dialog() { popup_centered_ratio(0.5); } -void AnimationLibraryEditor::_update_editor(Object *p_player) { - emit_signal("update_editor", p_player); +void AnimationLibraryEditor::_update_editor(Object *p_mixer) { + emit_signal("update_editor", p_mixer); } void AnimationLibraryEditor::_bind_methods() { - ClassDB::bind_method(D_METHOD("_update_editor", "player"), &AnimationLibraryEditor::_update_editor); + ClassDB::bind_method(D_METHOD("_update_editor", "mixer"), &AnimationLibraryEditor::_update_editor); ADD_SIGNAL(MethodInfo("update_editor")); } diff --git a/editor/plugins/animation_library_editor.h b/editor/plugins/animation_library_editor.h index a9b2c72b9dc..49a5c786d32 100644 --- a/editor/plugins/animation_library_editor.h +++ b/editor/plugins/animation_library_editor.h @@ -33,7 +33,7 @@ #include "editor/animation_track_editor.h" #include "editor/editor_plugin.h" -#include "scene/animation/animation_player.h" +#include "scene/animation/animation_mixer.h" #include "scene/gui/dialogs.h" #include "scene/gui/tree.h" @@ -90,7 +90,7 @@ class AnimationLibraryEditor : public AcceptDialog { Tree *tree = nullptr; - Object *player = nullptr; + Object *mixer = nullptr; void _add_library(); void _add_library_validate(const String &p_name); @@ -106,11 +106,11 @@ class AnimationLibraryEditor : public AcceptDialog { bool updating = false; protected: - void _update_editor(Object *p_player); + void _update_editor(Object *p_mixer); static void _bind_methods(); public: - void set_animation_player(Object *p_player); + void set_animation_mixer(Object *p_mixer); void show_dialog(); void update_tree(); AnimationLibraryEditor(); diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp index 51d195e0e1b..f2225fabadf 100644 --- a/editor/plugins/animation_player_editor_plugin.cpp +++ b/editor/plugins/animation_player_editor_plugin.cpp @@ -44,6 +44,7 @@ #include "editor/plugins/canvas_item_editor_plugin.h" // For onion skinning. #include "editor/plugins/node_3d_editor_plugin.h" // For onion skinning. #include "editor/scene_tree_dock.h" +#include "scene/animation/animation_tree.h" #include "scene/gui/separator.h" #include "scene/main/window.h" #include "scene/resources/animation.h" @@ -54,7 +55,11 @@ /////////////////////////////////// void AnimationPlayerEditor::_node_removed(Node *p_node) { - if (player && player == p_node) { + if (player && original_node == p_node) { + if (is_dummy) { + plugin->_clear_dummy_player(); + } + player = nullptr; set_process(false); @@ -63,12 +68,20 @@ void AnimationPlayerEditor::_node_removed(Node *p_node) { track_editor->set_root(nullptr); track_editor->show_select_node_warning(true); _update_player(); + + _ensure_dummy_player(); } } void AnimationPlayerEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_PROCESS: { + if (!player || is_dummy) { + track_editor->show_inactive_player_warning(false); + } else { + track_editor->show_inactive_player_warning(!player->is_active()); + } + if (!player) { return; } @@ -103,13 +116,13 @@ void AnimationPlayerEditor::_notification(int p_what) { } break; case NOTIFICATION_ENTER_TREE: { - tool_anim->get_popup()->connect("id_pressed", callable_mp(this, &AnimationPlayerEditor::_animation_tool_menu)); + tool_anim->get_popup()->connect(SNAME("id_pressed"), callable_mp(this, &AnimationPlayerEditor::_animation_tool_menu)); - onion_skinning->get_popup()->connect("id_pressed", callable_mp(this, &AnimationPlayerEditor::_onion_skinning_menu)); + onion_skinning->get_popup()->connect(SNAME("id_pressed"), callable_mp(this, &AnimationPlayerEditor::_onion_skinning_menu)); - blend_editor.next->connect("item_selected", callable_mp(this, &AnimationPlayerEditor::_blend_editor_next_changed)); + blend_editor.next->connect(SNAME("item_selected"), callable_mp(this, &AnimationPlayerEditor::_blend_editor_next_changed)); - get_tree()->connect("node_removed", callable_mp(this, &AnimationPlayerEditor::_node_removed)); + get_tree()->connect(SNAME("node_removed"), callable_mp(this, &AnimationPlayerEditor::_node_removed)); add_theme_style_override("panel", EditorNode::get_singleton()->get_editor_theme()->get_stylebox(SNAME("panel"), SNAME("Panel"))); } break; @@ -167,6 +180,10 @@ void AnimationPlayerEditor::_notification(int p_what) { _update_animation_list_icons(); } break; + + case NOTIFICATION_VISIBILITY_CHANGED: { + _ensure_dummy_player(); + } break; } } @@ -303,20 +320,19 @@ void AnimationPlayerEditor::_animation_selected(int p_which) { bool animation_library_is_foreign = EditorNode::get_singleton()->is_resource_read_only(anim); track_editor->set_animation(anim, animation_library_is_foreign); - Node *root = player->get_node(player->get_root()); + Node *root = player->get_node_or_null(player->get_root_node()); if (root) { track_editor->set_root(root); } } frame->set_max((double)anim->get_length()); - + autoplay->set_pressed(current == player->get_autoplay()); } else { track_editor->set_animation(Ref(), true); track_editor->set_root(nullptr); + autoplay->set_pressed(false); } - autoplay->set_pressed(current == player->get_autoplay()); - AnimationPlayerEditor::get_singleton()->get_track_editor()->update_keying(); _animation_key_editor_seek(timeline_position, false); @@ -506,6 +522,9 @@ void AnimationPlayerEditor::_animation_name_edited() { undo_redo->add_undo_method(this, "_animation_player_changed", player); undo_redo->commit_action(); + if (is_dummy) { + plugin->_update_dummy_player(original_node); + } _select_anim_by_name(new_library_prefix + new_name); } break; @@ -534,7 +553,7 @@ void AnimationPlayerEditor::_animation_name_edited() { if (al.is_null()) { al.instantiate(); lib_added = true; - undo_redo->add_do_method(player, "add_animation_library", "", al); + undo_redo->add_do_method(fetch_mixer_for_library(), "add_animation_library", "", al); library_name = ""; } @@ -547,13 +566,17 @@ void AnimationPlayerEditor::_animation_name_edited() { undo_redo->add_undo_method(this, "_stop_onion_skinning"); } if (lib_added) { - undo_redo->add_undo_method(player, "remove_animation_library", ""); + undo_redo->add_undo_method(fetch_mixer_for_library(), "remove_animation_library", ""); } undo_redo->commit_action(); if (library_name != "") { library_name = library_name + "/"; } + + if (is_dummy) { + plugin->_update_dummy_player(original_node); + } _select_anim_by_name(library_name + new_name); } break; @@ -602,6 +625,10 @@ void AnimationPlayerEditor::_animation_name_edited() { if (library_name != "") { library_name = library_name + "/"; } + + if (is_dummy) { + plugin->_update_dummy_player(original_node); + } _select_anim_by_name(library_name + new_name); } break; } @@ -714,7 +741,7 @@ void AnimationPlayerEditor::_blend_edited() { } void AnimationPlayerEditor::ensure_visibility() { - if (player && pin->is_pressed()) { + if (player) { return; // another player is pinned, don't reset } @@ -724,11 +751,13 @@ void AnimationPlayerEditor::ensure_visibility() { Dictionary AnimationPlayerEditor::get_state() const { Dictionary d; - d["visible"] = is_visible_in_tree(); - if (EditorNode::get_singleton()->get_edited_scene() && is_visible_in_tree() && player) { - d["player"] = EditorNode::get_singleton()->get_edited_scene()->get_path_to(player); - d["animation"] = player->get_assigned_animation(); - d["track_editor_state"] = track_editor->get_state(); + if (!is_dummy) { + d["visible"] = is_visible_in_tree(); + if (EditorNode::get_singleton()->get_edited_scene() && is_visible_in_tree() && player) { + d["player"] = EditorNode::get_singleton()->get_edited_scene()->get_path_to(player); + d["animation"] = player->get_assigned_animation(); + d["track_editor_state"] = track_editor->get_state(); + } } return d; @@ -746,14 +775,20 @@ void AnimationPlayerEditor::set_state(const Dictionary &p_state) { Node *n = EditorNode::get_singleton()->get_edited_scene()->get_node(p_state["player"]); if (Object::cast_to(n) && EditorNode::get_singleton()->get_editor_selection()->is_selected(n)) { if (player) { - if (player->is_connected("animation_libraries_updated", callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated))) { - player->disconnect("animation_libraries_updated", callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated)); + if (player->is_connected(SNAME("animation_list_changed"), callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated))) { + player->disconnect(SNAME("animation_list_changed"), callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated)); + } + if (player->is_connected(SNAME("current_animation_changed"), callable_mp(this, &AnimationPlayerEditor::_current_animation_changed))) { + player->disconnect(SNAME("current_animation_changed"), callable_mp(this, &AnimationPlayerEditor::_current_animation_changed)); } } player = Object::cast_to(n); if (player) { - if (!player->is_connected("animation_libraries_updated", callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated))) { - player->connect("animation_libraries_updated", callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated)); + if (!player->is_connected(SNAME("animation_list_changed"), callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated))) { + player->connect(SNAME("animation_list_changed"), callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated), CONNECT_DEFERRED); + } + if (!player->is_connected(SNAME("current_animation_changed"), callable_mp(this, &AnimationPlayerEditor::_current_animation_changed))) { + player->connect(SNAME("current_animation_changed"), callable_mp(this, &AnimationPlayerEditor::_current_animation_changed), CONNECT_DEFERRED); } } @@ -794,7 +829,7 @@ void AnimationPlayerEditor::_animation_edit() { track_editor->set_animation(anim, animation_library_is_foreign); - Node *root = player->get_node(player->get_root()); + Node *root = player->get_node_or_null(player->get_root_node()); if (root) { track_editor->set_root(root); } @@ -935,7 +970,7 @@ void AnimationPlayerEditor::_update_player() { bool animation_library_is_foreign = EditorNode::get_singleton()->is_resource_read_only(anim); track_editor->set_animation(anim, animation_library_is_foreign); - Node *root = player->get_node(player->get_root()); + Node *root = player->get_node_or_null(player->get_root_node()); if (root) { track_editor->set_root(root); } @@ -1007,21 +1042,69 @@ void AnimationPlayerEditor::_update_name_dialog_library_dropdown() { } } -void AnimationPlayerEditor::edit(AnimationPlayer *p_player) { +void AnimationPlayerEditor::_ensure_dummy_player() { + bool dummy_exists = is_dummy && player && original_node; + if (dummy_exists) { + if (is_visible()) { + player->set_active(true); + original_node->set_editing(true); + } else { + player->set_active(false); + original_node->set_editing(false); + } + } + + // Make some options disabled. + tool_anim->get_popup()->set_item_disabled(tool_anim->get_popup()->get_item_index(TOOL_EDIT_TRANSITIONS), dummy_exists); + onion_toggle->set_disabled(dummy_exists); + onion_skinning->set_disabled(dummy_exists); + int selected = animation->get_selected(); + autoplay->set_disabled(selected != -1 ? (animation->get_item_text(selected).is_empty() ? true : dummy_exists) : true); + + // Show warning. + if (track_editor) { + track_editor->show_dummy_player_warning(dummy_exists); + } +} + +void AnimationPlayerEditor::edit(AnimationMixer *p_node, AnimationPlayer *p_player, bool p_is_dummy) { if (player && pin->is_pressed()) { return; // Ignore, pinned. } if (player) { - if (player->is_connected("animation_libraries_updated", callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated))) { - player->disconnect("animation_libraries_updated", callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated)); + if (player->is_connected(SNAME("animation_list_changed"), callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated))) { + player->disconnect(SNAME("animation_list_changed"), callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated)); + } + if (player->is_connected(SNAME("current_animation_changed"), callable_mp(this, &AnimationPlayerEditor::_current_animation_changed))) { + player->disconnect(SNAME("current_animation_changed"), callable_mp(this, &AnimationPlayerEditor::_current_animation_changed)); } } + + AnimationTree *tree = Object::cast_to(p_node); + + if (tree) { + if (tree->is_connected(SNAME("animation_player_changed"), callable_mp(this, &AnimationPlayerEditor::unpin))) { + tree->disconnect(SNAME("animation_player_changed"), callable_mp(this, &AnimationPlayerEditor::unpin)); + } + } + + original_node = p_node; player = p_player; + is_dummy = p_is_dummy; + + if (tree) { + if (!tree->is_connected(SNAME("animation_player_changed"), callable_mp(this, &AnimationPlayerEditor::unpin))) { + tree->connect(SNAME("animation_player_changed"), callable_mp(this, &AnimationPlayerEditor::unpin)); + } + } if (player) { - if (!player->is_connected("animation_libraries_updated", callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated))) { - player->connect("animation_libraries_updated", callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated)); + if (!player->is_connected(SNAME("animation_list_changed"), callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated))) { + player->connect(SNAME("animation_list_changed"), callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated), CONNECT_DEFERRED); + } + if (!player->is_connected(SNAME("current_animation_changed"), callable_mp(this, &AnimationPlayerEditor::_current_animation_changed))) { + player->connect(SNAME("current_animation_changed"), callable_mp(this, &AnimationPlayerEditor::_current_animation_changed)); } _update_player(); @@ -1042,7 +1125,9 @@ void AnimationPlayerEditor::edit(AnimationPlayer *p_player) { track_editor->show_select_node_warning(true); } - library_editor->set_animation_player(player); + library_editor->set_animation_mixer(fetch_mixer_for_library()); + + _ensure_dummy_player(); } void AnimationPlayerEditor::forward_force_draw_over_viewport(Control *p_overlay) { @@ -1181,12 +1266,12 @@ void AnimationPlayerEditor::_seek_value_changed(float p_value, bool p_set, bool if (!p_timeline_only) { if (player->is_valid() && !p_set) { - float cpos = player->get_current_animation_position(); - - player->seek_delta(pos, pos - cpos); + double delta = pos - player->get_current_animation_position(); + player->seek(pos, true, true); + player->seek(pos + delta, true, true); } else { player->stop(); - player->seek(pos, true); + player->seek(pos, true, true); } } @@ -1194,14 +1279,12 @@ void AnimationPlayerEditor::_seek_value_changed(float p_value, bool p_set, bool }; void AnimationPlayerEditor::_animation_player_changed(Object *p_pl) { - if (player == p_pl && is_visible_in_tree()) { - _update_player(); - if (blend_editor.dialog->is_visible()) { - _animation_blend(); // Update. - } - if (library_editor->is_visible()) { - library_editor->update_tree(); - } + _update_player(); + if (blend_editor.dialog->is_visible()) { + _animation_blend(); // Update. + } + if (library_editor->is_visible()) { + library_editor->update_tree(); } } @@ -1215,6 +1298,25 @@ void AnimationPlayerEditor::_list_changed() { } } +void AnimationPlayerEditor::_current_animation_changed(const String &p_name) { + if (is_visible_in_tree()) { + if (p_name.is_empty()) { + // Means [stop]. + frame->set_value(0); + track_editor->set_anim_pos(0); + _update_animation(); + return; + } + Ref anim = player->get_animation(p_name); + if (anim.is_null()) { + return; + } + bool animation_library_is_foreign = EditorNode::get_singleton()->is_resource_read_only(anim); + track_editor->set_animation(anim, animation_library_is_foreign); + _update_animation(); + } +} + void AnimationPlayerEditor::_animation_key_editor_anim_len_changed(float p_len) { frame->set_max(p_len); } @@ -1257,7 +1359,7 @@ void AnimationPlayerEditor::_animation_tool_menu(int p_option) { _animation_new(); } break; case TOOL_ANIM_LIBRARY: { - library_editor->set_animation_player(player); + library_editor->set_animation_mixer(fetch_mixer_for_library()); library_editor->show_dialog(); } break; case TOOL_DUPLICATE_ANIM: { @@ -1267,6 +1369,9 @@ void AnimationPlayerEditor::_animation_tool_menu(int p_option) { _animation_rename(); } break; case TOOL_EDIT_TRANSITIONS: { + if (is_dummy) { + break; + } _animation_blend(); } break; case TOOL_REMOVE_ANIM: { @@ -1506,7 +1611,7 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() { } // Backup current animation state. - Ref values_backup = player->backup_animated_values(); + Ref backup_current = player->make_backup(); float cpos = player->get_current_animation_position(); // Render every past/future step with the capture shader. @@ -1536,7 +1641,6 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() { if (valid) { player->seek(pos, true); get_tree()->flush_transform_notifications(); // Needed for transforms of Node3Ds. - values_backup->update_skeletons(); // Needed for Skeletons (2D & 3D). RS::get_singleton()->viewport_set_active(onion.captures[cidx], true); RS::get_singleton()->viewport_set_parent_viewport(root_vp, onion.captures[cidx]); @@ -1556,7 +1660,7 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() { // (Seeking with update=true wouldn't do the trick because the current value of the properties // may not match their value for the current point in the animation). player->seek(cpos, false); - values_backup->restore(); + player->restore(backup_current); // Restore state of main editors. if (Node3DEditor::get_singleton()->is_visible()) { @@ -1574,14 +1678,14 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() { void AnimationPlayerEditor::_start_onion_skinning() { // FIXME: Using "process_frame" makes onion layers update one frame behind the current. - if (!get_tree()->is_connected("process_frame", callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred))) { - get_tree()->connect("process_frame", callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred)); + if (!get_tree()->is_connected(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred))) { + get_tree()->connect(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred)); } } void AnimationPlayerEditor::_stop_onion_skinning() { - if (get_tree()->is_connected("process_frame", callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred))) { - get_tree()->disconnect("process_frame", callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred)); + if (get_tree()->is_connected(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred))) { + get_tree()->disconnect(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred)); _free_onion_layers(); @@ -1595,6 +1699,21 @@ void AnimationPlayerEditor::_pin_pressed() { SceneTreeDock::get_singleton()->get_tree_editor()->update_tree(); } +AnimationMixer *AnimationPlayerEditor::fetch_mixer_for_library() const { + if (!original_node) { + return nullptr; + } + // Does AnimationTree have AnimationPlayer? + if (original_node->is_class("AnimationTree")) { + AnimationTree *src_tree = Object::cast_to(original_node); + Node *src_player = src_tree->get_node_or_null(src_tree->get_animation_player()); + if (src_player) { + return Object::cast_to(src_player); + } + } + return original_node; +} + bool AnimationPlayerEditor::_validate_tracks(const Ref p_anim) { bool is_valid = true; if (!p_anim.is_valid()) { @@ -1668,6 +1787,10 @@ AnimationPlayer *AnimationPlayerEditor::get_player() const { return player; } +AnimationMixer *AnimationPlayerEditor::get_editing_node() const { + return original_node; +} + AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plugin) { plugin = p_plugin; singleton = this; @@ -1724,7 +1847,7 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug delete_dialog = memnew(ConfirmationDialog); add_child(delete_dialog); - delete_dialog->connect("confirmed", callable_mp(this, &AnimationPlayerEditor::_animation_remove_confirmed)); + delete_dialog->connect(SNAME("confirmed"), callable_mp(this, &AnimationPlayerEditor::_animation_remove_confirmed)); tool_anim = memnew(MenuButton); tool_anim->set_shortcut_context(this); @@ -1769,7 +1892,7 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug onion_toggle->set_theme_type_variation("FlatButton"); onion_toggle->set_toggle_mode(true); onion_toggle->set_tooltip_text(TTR("Enable Onion Skinning")); - onion_toggle->connect("pressed", callable_mp(this, &AnimationPlayerEditor::_onion_skinning_menu).bind(ONION_SKINNING_ENABLE)); + onion_toggle->connect(SNAME("pressed"), callable_mp(this, &AnimationPlayerEditor::_onion_skinning_menu).bind(ONION_SKINNING_ENABLE)); hb->add_child(onion_toggle); onion_skinning = memnew(MenuButton); @@ -1808,7 +1931,7 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug pin->set_toggle_mode(true); pin->set_tooltip_text(TTR("Pin AnimationPlayer")); hb->add_child(pin); - pin->connect("pressed", callable_mp(this, &AnimationPlayerEditor::_pin_pressed)); + pin->connect(SNAME("pressed"), callable_mp(this, &AnimationPlayerEditor::_pin_pressed)); file = memnew(EditorFileDialog); add_child(file); @@ -1838,7 +1961,7 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug error_dialog->set_title(TTR("Error!")); add_child(error_dialog); - name_dialog->connect("confirmed", callable_mp(this, &AnimationPlayerEditor::_animation_name_edited)); + name_dialog->connect(SNAME("confirmed"), callable_mp(this, &AnimationPlayerEditor::_animation_name_edited)); blend_editor.dialog = memnew(AcceptDialog); add_child(blend_editor.dialog); @@ -1855,20 +1978,20 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug blend_editor.dialog->set_title(TTR("Cross-Animation Blend Times")); updating_blends = false; - blend_editor.tree->connect("item_edited", callable_mp(this, &AnimationPlayerEditor::_blend_edited)); + blend_editor.tree->connect(SNAME("item_edited"), callable_mp(this, &AnimationPlayerEditor::_blend_edited)); - autoplay->connect("pressed", callable_mp(this, &AnimationPlayerEditor::_autoplay_pressed)); + autoplay->connect(SNAME("pressed"), callable_mp(this, &AnimationPlayerEditor::_autoplay_pressed)); autoplay->set_toggle_mode(true); - play->connect("pressed", callable_mp(this, &AnimationPlayerEditor::_play_pressed)); - play_from->connect("pressed", callable_mp(this, &AnimationPlayerEditor::_play_from_pressed)); - play_bw->connect("pressed", callable_mp(this, &AnimationPlayerEditor::_play_bw_pressed)); - play_bw_from->connect("pressed", callable_mp(this, &AnimationPlayerEditor::_play_bw_from_pressed)); - stop->connect("pressed", callable_mp(this, &AnimationPlayerEditor::_stop_pressed)); + play->connect(SNAME("pressed"), callable_mp(this, &AnimationPlayerEditor::_play_pressed)); + play_from->connect(SNAME("pressed"), callable_mp(this, &AnimationPlayerEditor::_play_from_pressed)); + play_bw->connect(SNAME("pressed"), callable_mp(this, &AnimationPlayerEditor::_play_bw_pressed)); + play_bw_from->connect(SNAME("pressed"), callable_mp(this, &AnimationPlayerEditor::_play_bw_from_pressed)); + stop->connect(SNAME("pressed"), callable_mp(this, &AnimationPlayerEditor::_stop_pressed)); - animation->connect("item_selected", callable_mp(this, &AnimationPlayerEditor::_animation_selected)); + animation->connect(SNAME("item_selected"), callable_mp(this, &AnimationPlayerEditor::_animation_selected)); - frame->connect("value_changed", callable_mp(this, &AnimationPlayerEditor::_seek_value_changed).bind(true, false)); - scale->connect("text_submitted", callable_mp(this, &AnimationPlayerEditor::_scale_changed)); + frame->connect(SNAME("value_changed"), callable_mp(this, &AnimationPlayerEditor::_seek_value_changed).bind(true, false)); + scale->connect(SNAME("text_submitted"), callable_mp(this, &AnimationPlayerEditor::_scale_changed)); last_active = false; timeline_position = 0; @@ -1877,18 +2000,18 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug add_child(track_editor); track_editor->set_v_size_flags(SIZE_EXPAND_FILL); - track_editor->connect("timeline_changed", callable_mp(this, &AnimationPlayerEditor::_animation_key_editor_seek)); - track_editor->connect("animation_len_changed", callable_mp(this, &AnimationPlayerEditor::_animation_key_editor_anim_len_changed)); + track_editor->connect(SNAME("timeline_changed"), callable_mp(this, &AnimationPlayerEditor::_animation_key_editor_seek)); + track_editor->connect(SNAME("animation_len_changed"), callable_mp(this, &AnimationPlayerEditor::_animation_key_editor_anim_len_changed)); _update_player(); library_editor = memnew(AnimationLibraryEditor); add_child(library_editor); - library_editor->connect("update_editor", callable_mp(this, &AnimationPlayerEditor::_animation_player_changed)); + library_editor->connect(SNAME("update_editor"), callable_mp(this, &AnimationPlayerEditor::_animation_player_changed)); // Onion skinning. - track_editor->connect("visibility_changed", callable_mp(this, &AnimationPlayerEditor::_editor_visibility_changed)); + track_editor->connect(SNAME("visibility_changed"), callable_mp(this, &AnimationPlayerEditor::_editor_visibility_changed)); onion.enabled = false; onion.past = true; @@ -1943,10 +2066,10 @@ AnimationPlayerEditor::~AnimationPlayerEditor() { void AnimationPlayerEditorPlugin::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { - Node3DEditor::get_singleton()->connect("transform_key_request", callable_mp(this, &AnimationPlayerEditorPlugin::_transform_key_request)); - InspectorDock::get_inspector_singleton()->connect("property_keyed", callable_mp(this, &AnimationPlayerEditorPlugin::_property_keyed)); - anim_editor->get_track_editor()->connect("keying_changed", callable_mp(this, &AnimationPlayerEditorPlugin::_update_keying)); - InspectorDock::get_inspector_singleton()->connect("edited_object_changed", callable_mp(anim_editor->get_track_editor(), &AnimationTrackEditor::update_keying)); + Node3DEditor::get_singleton()->connect(SNAME("transform_key_request"), callable_mp(this, &AnimationPlayerEditorPlugin::_transform_key_request)); + InspectorDock::get_inspector_singleton()->connect(SNAME("property_keyed"), callable_mp(this, &AnimationPlayerEditorPlugin::_property_keyed)); + anim_editor->get_track_editor()->connect(SNAME("keying_changed"), callable_mp(this, &AnimationPlayerEditorPlugin::_update_keying)); + InspectorDock::get_inspector_singleton()->connect(SNAME("edited_object_changed"), callable_mp(anim_editor->get_track_editor(), &AnimationTrackEditor::update_keying)); set_force_draw_over_forwarding_enabled(); } break; } @@ -1979,14 +2102,88 @@ void AnimationPlayerEditorPlugin::_update_keying() { } void AnimationPlayerEditorPlugin::edit(Object *p_object) { + if (player && anim_editor && anim_editor->is_pinned()) { + return; // Ignore, pinned. + } + + player = nullptr; if (!p_object) { return; } - anim_editor->edit(Object::cast_to(p_object)); + last_mixer = p_object->get_instance_id(); + + AnimationMixer *src_node = Object::cast_to(p_object); + bool is_dummy = false; + if (!p_object->is_class("AnimationPlayer")) { + // If it needs dummy AnimationPlayer, assign original AnimationMixer to LibraryEditor. + _update_dummy_player(src_node); + + is_dummy = true; + + if (!src_node->is_connected(SNAME("mixer_updated"), callable_mp(this, &AnimationPlayerEditorPlugin::_update_dummy_player))) { + src_node->connect(SNAME("mixer_updated"), callable_mp(this, &AnimationPlayerEditorPlugin::_update_dummy_player).bind(src_node), CONNECT_DEFERRED); + } + if (!src_node->is_connected(SNAME("animation_libraries_updated"), callable_mp(this, &AnimationPlayerEditorPlugin::_update_dummy_player))) { + src_node->connect(SNAME("animation_libraries_updated"), callable_mp(this, &AnimationPlayerEditorPlugin::_update_dummy_player).bind(src_node), CONNECT_DEFERRED); + } + } else { + _clear_dummy_player(); + player = Object::cast_to(p_object); + } + player->set_dummy(is_dummy); + + anim_editor->edit(src_node, player, is_dummy); +} + +void AnimationPlayerEditorPlugin::_clear_dummy_player() { + if (!dummy_player) { + return; + } + Node *parent = dummy_player->get_parent(); + if (parent) { + parent->call_deferred("remove_child", dummy_player); + } + dummy_player->queue_free(); + dummy_player = nullptr; +} + +void AnimationPlayerEditorPlugin::_update_dummy_player(AnimationMixer *p_mixer) { + // Check current editing object. + if (p_mixer->get_instance_id() != last_mixer && p_mixer->is_connected(SNAME("mixer_updated"), callable_mp(this, &AnimationPlayerEditorPlugin::_update_dummy_player))) { + p_mixer->disconnect(SNAME("mixer_updated"), callable_mp(this, &AnimationPlayerEditorPlugin::_update_dummy_player)); + return; + } + + // Add dummy player to scene. + if (!dummy_player) { + Node *parent = p_mixer->get_parent(); + ERR_FAIL_NULL(parent); + dummy_player = memnew(AnimationPlayer); + parent->add_child(dummy_player); + } + player = dummy_player; + + // Convert AnimationTree (AnimationMixer) to AnimationPlayer. + AnimationMixer *default_node = memnew(AnimationMixer); + List pinfo; + default_node->get_property_list(&pinfo); + for (const PropertyInfo &E : pinfo) { + if (!(E.usage & PROPERTY_USAGE_STORAGE)) { + continue; + } + if (E.name != "script" && E.name != "active" && E.name != "deterministic" && E.name != "root_motion_track") { + dummy_player->set(E.name, p_mixer->get(E.name)); + } + } + memdelete(default_node); + + if (anim_editor) { + anim_editor->_update_player(); + } } bool AnimationPlayerEditorPlugin::handles(Object *p_object) const { - return p_object->is_class("AnimationPlayer"); + return p_object->is_class("AnimationPlayer") || p_object->is_class("AnimationTree") || p_object->is_class("AnimationMixer"); } void AnimationPlayerEditorPlugin::make_visible(bool p_visible) { @@ -2003,6 +2200,12 @@ AnimationPlayerEditorPlugin::AnimationPlayerEditorPlugin() { } AnimationPlayerEditorPlugin::~AnimationPlayerEditorPlugin() { + if (dummy_player) { + memdelete(dummy_player); + } + if (player) { + memdelete(player); + } } // AnimationTrackKeyEditEditorPlugin diff --git a/editor/plugins/animation_player_editor_plugin.h b/editor/plugins/animation_player_editor_plugin.h index 8c46b5c36e6..4763a008fe2 100644 --- a/editor/plugins/animation_player_editor_plugin.h +++ b/editor/plugins/animation_player_editor_plugin.h @@ -47,8 +47,12 @@ class ImageTexture; class AnimationPlayerEditor : public VBoxContainer { GDCLASS(AnimationPlayerEditor, VBoxContainer); + friend AnimationPlayerEditorPlugin; + AnimationPlayerEditorPlugin *plugin = nullptr; - AnimationPlayer *player = nullptr; + AnimationMixer *original_node = nullptr; // For pinned mark in SceneTree. + AnimationPlayer *player = nullptr; // For AnimationPlayerEditor, could be dummy. + bool is_dummy = false; enum { TOOL_NEW_ANIM, @@ -189,6 +193,7 @@ class AnimationPlayerEditor : public VBoxContainer { void _blend_editor_next_changed(const int p_idx); void _list_changed(); + void _current_animation_changed(const String &p_name); void _update_animation(); void _update_player(); void _update_animation_list_icons(); @@ -220,6 +225,8 @@ class AnimationPlayerEditor : public VBoxContainer { void _pin_pressed(); String _get_current() const; + void _ensure_dummy_player(); + ~AnimationPlayerEditor(); protected: @@ -228,19 +235,24 @@ protected: static void _bind_methods(); public: + AnimationMixer *get_editing_node() const; AnimationPlayer *get_player() const; + AnimationMixer *fetch_mixer_for_library() const; static AnimationPlayerEditor *get_singleton() { return singleton; } bool is_pinned() const { return pin->is_pressed(); } - void unpin() { pin->set_pressed(false); } + void unpin() { + pin->set_pressed(false); + _pin_pressed(); + } AnimationTrackEditor *get_track_editor() { return track_editor; } Dictionary get_state() const; void set_state(const Dictionary &p_state); void ensure_visibility(); - void edit(AnimationPlayer *p_player); + void edit(AnimationMixer *p_node, AnimationPlayer *p_player, bool p_is_dummy); void forward_force_draw_over_viewport(Control *p_overlay); AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plugin); @@ -249,7 +261,15 @@ public: class AnimationPlayerEditorPlugin : public EditorPlugin { GDCLASS(AnimationPlayerEditorPlugin, EditorPlugin); + friend AnimationPlayerEditor; + AnimationPlayerEditor *anim_editor = nullptr; + AnimationPlayer *player = nullptr; + AnimationPlayer *dummy_player = nullptr; + ObjectID last_mixer; + + void _update_dummy_player(AnimationMixer *p_mixer); + void _clear_dummy_player(); protected: void _notification(int p_what); diff --git a/editor/plugins/animation_state_machine_editor.cpp b/editor/plugins/animation_state_machine_editor.cpp index f25f6ccd04a..746a901a0a1 100644 --- a/editor/plugins/animation_state_machine_editor.cpp +++ b/editor/plugins/animation_state_machine_editor.cpp @@ -92,7 +92,7 @@ String AnimationNodeStateMachineEditor::_get_root_playback_path(String &r_node_d if (edited_path.size()) { while (!is_playable_anodesm_found) { base_path = String("/").join(edited_path); - Ref anodesm = !edited_path.size() ? tree->get_tree_root() : tree->get_tree_root()->find_node_by_path(base_path); + Ref anodesm = !edited_path.size() ? Ref(tree->get_root_animation_node().ptr()) : tree->get_root_animation_node()->find_node_by_path(base_path); if (!anodesm.is_valid()) { break; } else { @@ -562,13 +562,7 @@ void AnimationNodeStateMachineEditor::_open_menu(const Vector2 &p_position) { animations_to_add.clear(); List animation_names; - if (tree->has_node(tree->get_animation_player())) { - AnimationPlayer *ap = Object::cast_to(tree->get_node(tree->get_animation_player())); - if (ap) { - ap->get_animation_list(&animation_names); - } - } - + tree->get_animation_list(&animation_names); menu->add_submenu_item(TTR("Add Animation"), "animations"); if (animation_names.is_empty()) { menu->set_item_disabled(menu->get_item_idx_from_text(TTR("Add Animation")), true); diff --git a/editor/plugins/animation_tree_editor_plugin.cpp b/editor/plugins/animation_tree_editor_plugin.cpp index 7b5f8aa7f77..1c642f909a7 100644 --- a/editor/plugins/animation_tree_editor_plugin.cpp +++ b/editor/plugins/animation_tree_editor_plugin.cpp @@ -50,16 +50,16 @@ #include "scene/scene_string_names.h" void AnimationTreeEditor::edit(AnimationTree *p_tree) { - if (p_tree && !p_tree->is_connected("animation_player_changed", callable_mp(this, &AnimationTreeEditor::_animation_list_changed))) { - p_tree->connect("animation_player_changed", callable_mp(this, &AnimationTreeEditor::_animation_list_changed), CONNECT_DEFERRED); + if (p_tree && !p_tree->is_connected("animation_list_changed", callable_mp(this, &AnimationTreeEditor::_animation_list_changed))) { + p_tree->connect("animation_list_changed", callable_mp(this, &AnimationTreeEditor::_animation_list_changed), CONNECT_DEFERRED); } if (tree == p_tree) { return; } - if (tree && tree->is_connected("animation_player_changed", callable_mp(this, &AnimationTreeEditor::_animation_list_changed))) { - tree->disconnect("animation_player_changed", callable_mp(this, &AnimationTreeEditor::_animation_list_changed)); + if (tree && tree->is_connected("animation_list_changed", callable_mp(this, &AnimationTreeEditor::_animation_list_changed))) { + tree->disconnect("animation_list_changed", callable_mp(this, &AnimationTreeEditor::_animation_list_changed)); } tree = p_tree; @@ -122,7 +122,7 @@ void AnimationTreeEditor::_update_path() { void AnimationTreeEditor::edit_path(const Vector &p_path) { button_path.clear(); - Ref node = tree->get_tree_root(); + Ref node = tree->get_root_animation_node(); if (node.is_valid()) { current_root = node->get_instance_id(); @@ -185,8 +185,8 @@ void AnimationTreeEditor::_notification(int p_what) { } break; case NOTIFICATION_PROCESS: { ObjectID root; - if (tree && tree->get_tree_root().is_valid()) { - root = tree->get_tree_root()->get_instance_id(); + if (tree && tree->get_root_animation_node().is_valid()) { + root = tree->get_root_animation_node()->get_instance_id(); } if (root != current_root) { @@ -247,18 +247,12 @@ Vector AnimationTreeEditor::get_animation_list() { } AnimationTree *tree = singleton->tree; - if (!tree || !tree->has_node(tree->get_animation_player())) { - return Vector(); - } - - AnimationPlayer *ap = Object::cast_to(tree->get_node(tree->get_animation_player())); - - if (!ap) { + if (!tree) { return Vector(); } List anims; - ap->get_animation_list(&anims); + tree->get_animation_list(&anims); Vector ret; for (const StringName &E : anims) { ret.push_back(E); diff --git a/editor/plugins/root_motion_editor_plugin.cpp b/editor/plugins/root_motion_editor_plugin.cpp index 10b0b214aeb..bc6507155ad 100644 --- a/editor/plugins/root_motion_editor_plugin.cpp +++ b/editor/plugins/root_motion_editor_plugin.cpp @@ -29,9 +29,10 @@ /**************************************************************************/ #include "root_motion_editor_plugin.h" + #include "editor/editor_node.h" -#include "scene/animation/animation_player.h" -#include "scene/animation/animation_tree.h" +#include "editor/editor_scale.h" +#include "scene/animation/animation_mixer.h" #include "scene/gui/button.h" #include "scene/gui/dialogs.h" #include "scene/gui/tree.h" @@ -50,31 +51,26 @@ void EditorPropertyRootMotion::_confirmed() { } void EditorPropertyRootMotion::_node_assign() { - AnimationTree *atree = Object::cast_to(get_edited_object()); - if (!atree->has_node(atree->get_animation_player())) { - EditorNode::get_singleton()->show_warning(TTR("AnimationTree has no path set to an AnimationPlayer")); - return; - } - AnimationPlayer *player = Object::cast_to(atree->get_node(atree->get_animation_player())); - if (!player) { - EditorNode::get_singleton()->show_warning(TTR("Path to AnimationPlayer is invalid")); + AnimationMixer *mixer = Object::cast_to(get_edited_object()); + if (!mixer) { + EditorNode::get_singleton()->show_warning(TTR("Path to AnimationMixer is invalid")); return; } - Node *base = player->get_node(player->get_root()); + Node *base = mixer->get_node(mixer->get_root_node()); if (!base) { - EditorNode::get_singleton()->show_warning(TTR("Animation player has no valid root node path, so unable to retrieve track names.")); + EditorNode::get_singleton()->show_warning(TTR("AnimationMixer has no valid root node path, so unable to retrieve track names.")); return; } HashSet paths; { List animations; - player->get_animation_list(&animations); + mixer->get_animation_list(&animations); for (const StringName &E : animations) { - Ref anim = player->get_animation(E); + Ref anim = mixer->get_animation(E); for (int i = 0; i < anim->get_track_count(); i++) { String pathname = anim->track_get_path(i).get_concatenated_names(); if (!paths.has(pathname)) { @@ -160,7 +156,7 @@ void EditorPropertyRootMotion::_node_assign() { } filters->ensure_cursor_is_visible(); - filter_dialog->popup_centered_ratio(); + filter_dialog->popup_centered(Size2(500, 500) * EDSCALE); } void EditorPropertyRootMotion::_node_clear() { @@ -232,7 +228,7 @@ bool EditorInspectorRootMotionPlugin::can_handle(Object *p_object) { } bool EditorInspectorRootMotionPlugin::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField p_usage, const bool p_wide) { - if (p_path == "root_motion_track" && p_object->is_class("AnimationTree") && p_type == Variant::NODE_PATH) { + if (p_path == "root_motion_track" && p_object->is_class("AnimationMixer") && p_type == Variant::NODE_PATH) { EditorPropertyRootMotion *editor = memnew(EditorPropertyRootMotion); add_property_editor(p_path, editor); return true; diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index d7c6cb04a49..a83dedad6db 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -1548,7 +1548,7 @@ void SceneTreeDock::_fill_path_renames(Vector base_path, Vector &p_to_delete) const { AnimationPlayer *ap = Object::cast_to(p_node); if (ap) { - Node *root = ap->get_node(ap->get_root()); + Node *root = ap->get_node(ap->get_root_node()); if (root && !p_to_delete.find(root)) { List anims; ap->get_animation_list(&anims); @@ -1735,7 +1735,7 @@ void SceneTreeDock::perform_node_renames(Node *p_base, HashMap AnimationPlayer *ap = Object::cast_to(p_base); List anims; ap->get_animation_list(&anims); - Node *root = ap->get_node(ap->get_root()); + Node *root = ap->get_node(ap->get_root_node()); if (root) { HashMap::Iterator found_root_path = p_renames->find(root); diff --git a/misc/extension_api_validation/4.1-stable.expected b/misc/extension_api_validation/4.1-stable.expected index 0fb834bbbf2..a14d0fe935f 100644 --- a/misc/extension_api_validation/4.1-stable.expected +++ b/misc/extension_api_validation/4.1-stable.expected @@ -117,6 +117,7 @@ Validate extension JSON: API was removed: classes/GLTFDocumentExtensionTextureWe Excluded unexposed classes from extension_api.json. + GH-79311 -------- @@ -205,3 +206,66 @@ GH-82403 Validate extension JSON: Error: Field 'native_structures/PhysicsServer3DExtensionRayResult': format changed value in new API, from "Vector3 position;Vector3 normal;RID rid;ObjectID collider_id;Object *collider;int shape" to "Vector3 position;Vector3 normal;RID rid;ObjectID collider_id;Object *collider;int shape;int face_index". Added/moved face_index field (introduced in GH-71233) to end of struct. Should still be compatible with 4.1. + + +GH-80813 +-------- +Validate extension JSON: API was removed: classes/AnimationPlayer/methods/_post_process_key_value +Validate extension JSON: API was removed: classes/AnimationPlayer/methods/add_animation_library +Validate extension JSON: API was removed: classes/AnimationPlayer/methods/advance +Validate extension JSON: API was removed: classes/AnimationPlayer/methods/clear_caches +Validate extension JSON: API was removed: classes/AnimationPlayer/methods/find_animation +Validate extension JSON: API was removed: classes/AnimationPlayer/methods/find_animation_library +Validate extension JSON: API was removed: classes/AnimationPlayer/methods/get_animation +Validate extension JSON: API was removed: classes/AnimationPlayer/methods/get_animation_library +Validate extension JSON: API was removed: classes/AnimationPlayer/methods/get_animation_library_list +Validate extension JSON: API was removed: classes/AnimationPlayer/methods/get_animation_list +Validate extension JSON: API was removed: classes/AnimationPlayer/methods/get_audio_max_polyphony +Validate extension JSON: API was removed: classes/AnimationPlayer/methods/has_animation +Validate extension JSON: API was removed: classes/AnimationPlayer/methods/has_animation_library +Validate extension JSON: API was removed: classes/AnimationPlayer/methods/is_active +Validate extension JSON: API was removed: classes/AnimationPlayer/methods/is_reset_on_save_enabled +Validate extension JSON: API was removed: classes/AnimationPlayer/methods/remove_animation_library +Validate extension JSON: API was removed: classes/AnimationPlayer/methods/rename_animation_library +Validate extension JSON: API was removed: classes/AnimationPlayer/methods/set_active +Validate extension JSON: API was removed: classes/AnimationPlayer/methods/set_audio_max_polyphony +Validate extension JSON: API was removed: classes/AnimationPlayer/methods/set_reset_on_save_enabled +Validate extension JSON: API was removed: classes/AnimationPlayer/properties/audio_max_polyphony +Validate extension JSON: API was removed: classes/AnimationPlayer/properties/method_call_mode +Validate extension JSON: API was removed: classes/AnimationPlayer/properties/playback_active +Validate extension JSON: API was removed: classes/AnimationPlayer/properties/playback_process_mode +Validate extension JSON: API was removed: classes/AnimationPlayer/properties/reset_on_save +Validate extension JSON: API was removed: classes/AnimationPlayer/properties/root_node +Validate extension JSON: API was removed: classes/AnimationTree/methods/_post_process_key_value +Validate extension JSON: API was removed: classes/AnimationTree/methods/advance +Validate extension JSON: API was removed: classes/AnimationTree/methods/get_audio_max_polyphony +Validate extension JSON: API was removed: classes/AnimationTree/methods/get_root_motion_position +Validate extension JSON: API was removed: classes/AnimationTree/methods/get_root_motion_position_accumulator +Validate extension JSON: API was removed: classes/AnimationTree/methods/get_root_motion_rotation +Validate extension JSON: API was removed: classes/AnimationTree/methods/get_root_motion_rotation_accumulator +Validate extension JSON: API was removed: classes/AnimationTree/methods/get_root_motion_scale +Validate extension JSON: API was removed: classes/AnimationTree/methods/get_root_motion_scale_accumulator +Validate extension JSON: API was removed: classes/AnimationTree/methods/get_root_motion_track +Validate extension JSON: API was removed: classes/AnimationTree/methods/is_active +Validate extension JSON: API was removed: classes/AnimationTree/methods/set_active +Validate extension JSON: API was removed: classes/AnimationTree/methods/set_audio_max_polyphony +Validate extension JSON: API was removed: classes/AnimationTree/methods/set_root_motion_track +Validate extension JSON: API was removed: classes/AnimationTree/properties/active +Validate extension JSON: API was removed: classes/AnimationTree/properties/audio_max_polyphony +Validate extension JSON: API was removed: classes/AnimationTree/properties/process_callback +Validate extension JSON: API was removed: classes/AnimationTree/properties/root_motion_track +Validate extension JSON: API was removed: classes/AnimationPlayer/signals/animation_finished +Validate extension JSON: API was removed: classes/AnimationPlayer/signals/animation_libraries_updated +Validate extension JSON: API was removed: classes/AnimationPlayer/signals/animation_list_changed +Validate extension JSON: API was removed: classes/AnimationPlayer/signals/animation_started +Validate extension JSON: API was removed: classes/AnimationPlayer/signals/caches_cleared +Validate extension JSON: API was removed: classes/AnimationPlayer/signals/animation_changed +Validate extension JSON: API was removed: classes/AnimationTree/signals/animation_finished +Validate extension JSON: API was removed: classes/AnimationTree/signals/animation_started +Validate extension JSON: Error: Field 'classes/AnimationPlayer/methods/seek/arguments': size changed value in new API, from 2 to 3. +Validate extension JSON: Error: Field 'classes/AnimationTree/methods/get_tree_root/return_value': type changed value in new API, from "AnimationNode" to "AnimationRootNode". +Validate extension JSON: Error: Field 'classes/AnimationTree/methods/set_tree_root/arguments/0': type changed value in new API, from "AnimationNode" to "AnimationRootNode". + +These definitions have been moved to those base classes, so the APIs are actually available. +Some properties were renamed for integration, but the old setter/getters are kept. +Also changed some methods name/argument/signature. Compatibility methods registered. diff --git a/scene/animation/animation_blend_space_1d.cpp b/scene/animation/animation_blend_space_1d.cpp index 1e0584e1dbe..981bb88bc4e 100644 --- a/scene/animation/animation_blend_space_1d.cpp +++ b/scene/animation/animation_blend_space_1d.cpp @@ -272,14 +272,17 @@ void AnimationNodeBlendSpace1D::_add_blend_point(int p_index, const Refset_backward(na_c->is_backward()); } //see how much animation remains - from = cur_length_internal - blend_node(blend_points[cur_closest].name, blend_points[cur_closest].node, p_time, false, p_is_external_seeking, 0.0, FILTER_IGNORE, true, p_test_only); + pi.seeked = false; + pi.weight = 0; + from = cur_length_internal - blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, p_test_only); } - max_time_remaining = blend_node(blend_points[new_closest].name, blend_points[new_closest].node, from, true, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only); + pi.time = from; + pi.seeked = true; + pi.weight = 1.0; + max_time_remaining = blend_node(blend_points[new_closest].node, blend_points[new_closest].name, pi, FILTER_IGNORE, true, p_test_only); cur_length_internal = from + max_time_remaining; - cur_closest = new_closest; - } else { - max_time_remaining = blend_node(blend_points[cur_closest].name, blend_points[cur_closest].node, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only); + pi.weight = 1.0; + max_time_remaining = blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, p_test_only); } if (sync) { + pi = p_playback_info; + pi.weight = 0; for (int i = 0; i < blend_points_used; i++) { if (i != cur_closest) { - blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, 0, FILTER_IGNORE, true, p_test_only); + blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only); } } } diff --git a/scene/animation/animation_blend_space_1d.h b/scene/animation/animation_blend_space_1d.h index db2e0045a61..40679d55ef1 100644 --- a/scene/animation/animation_blend_space_1d.h +++ b/scene/animation/animation_blend_space_1d.h @@ -114,7 +114,7 @@ public: void set_use_sync(bool p_sync); bool is_using_sync() const; - virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override; + virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; String get_caption() const override; Ref get_child_by_name(const StringName &p_name) const override; diff --git a/scene/animation/animation_blend_space_2d.cpp b/scene/animation/animation_blend_space_2d.cpp index ff0be091ac9..37c360b8f8d 100644 --- a/scene/animation/animation_blend_space_2d.cpp +++ b/scene/animation/animation_blend_space_2d.cpp @@ -442,7 +442,7 @@ void AnimationNodeBlendSpace2D::_blend_triangle(const Vector2 &p_pos, const Vect r_weights[2] = w; } -double AnimationNodeBlendSpace2D::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) { +double AnimationNodeBlendSpace2D::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { _update_triangles(); Vector2 blend_pos = get_parameter(blend_position); @@ -450,6 +450,8 @@ double AnimationNodeBlendSpace2D::_process(double p_time, bool p_seek, bool p_is double cur_length_internal = get_parameter(length_internal); double mind = 0.0; //time of min distance point + AnimationMixer::PlaybackInfo pi = p_playback_info; + if (blend_mode == BLEND_MODE_INTERPOLATED) { if (triangles.size() == 0) { return 0; @@ -512,7 +514,8 @@ double AnimationNodeBlendSpace2D::_process(double p_time, bool p_seek, bool p_is for (int j = 0; j < 3; j++) { if (i == triangle_points[j]) { //blend with the given weight - double t = blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, blend_weights[j], FILTER_IGNORE, true, p_test_only); + pi.weight = blend_weights[j]; + double t = blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only); if (first || t < mind) { mind = t; first = false; @@ -523,7 +526,8 @@ double AnimationNodeBlendSpace2D::_process(double p_time, bool p_seek, bool p_is } if (sync && !found) { - blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, 0, FILTER_IGNORE, true, p_test_only); + pi.weight = 0; + blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only); } } } else { @@ -548,22 +552,28 @@ double AnimationNodeBlendSpace2D::_process(double p_time, bool p_seek, bool p_is na_n->set_backward(na_c->is_backward()); } //see how much animation remains - from = cur_length_internal - blend_node(blend_points[cur_closest].name, blend_points[cur_closest].node, p_time, false, p_is_external_seeking, 0.0, FILTER_IGNORE, true, p_test_only); + pi.seeked = false; + pi.weight = 0; + from = cur_length_internal - blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, p_test_only); } - mind = blend_node(blend_points[new_closest].name, blend_points[new_closest].node, from, true, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only); + pi.time = from; + pi.seeked = true; + pi.weight = 1.0; + mind = blend_node(blend_points[new_closest].node, blend_points[new_closest].name, pi, FILTER_IGNORE, true, p_test_only); cur_length_internal = from + mind; - cur_closest = new_closest; - } else { - mind = blend_node(blend_points[cur_closest].name, blend_points[cur_closest].node, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only); + pi.weight = 1.0; + mind = blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, p_test_only); } if (sync) { + pi = p_playback_info; + pi.weight = 0; for (int i = 0; i < blend_points_used; i++) { if (i != cur_closest) { - blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, 0, FILTER_IGNORE, true, p_test_only); + blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only); } } } diff --git a/scene/animation/animation_blend_space_2d.h b/scene/animation/animation_blend_space_2d.h index a18ae3b2586..33a821d80cd 100644 --- a/scene/animation/animation_blend_space_2d.h +++ b/scene/animation/animation_blend_space_2d.h @@ -129,7 +129,7 @@ public: void set_y_label(const String &p_label); String get_y_label() const; - virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override; + virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; virtual String get_caption() const override; Vector2 get_closest_point(const Vector2 &p_point); diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp index b9cbd3cf94a..6ed61b4f261 100644 --- a/scene/animation/animation_blend_tree.cpp +++ b/scene/animation/animation_blend_tree.cpp @@ -64,14 +64,11 @@ void AnimationNodeAnimation::_validate_property(PropertyInfo &p_property) const } } -double AnimationNodeAnimation::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) { - AnimationPlayer *ap = state->player; - ERR_FAIL_NULL_V(ap, 0); - +double AnimationNodeAnimation::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { double cur_time = get_parameter(time); - if (!ap->has_animation(animation)) { - AnimationNodeBlendTree *tree = Object::cast_to(parent); + if (!process_state->tree->has_animation(animation)) { + AnimationNodeBlendTree *tree = Object::cast_to(node_state.parent); if (tree) { String node_name = tree->get_node_name(Ref(this)); make_invalid(vformat(RTR("On BlendTree node '%s', animation not found: '%s'"), node_name, animation)); @@ -83,14 +80,18 @@ double AnimationNodeAnimation::_process(double p_time, bool p_seek, bool p_is_ex return 0; } - Ref anim = ap->get_animation(animation); + Ref anim = process_state->tree->get_animation(animation); double anim_size = (double)anim->get_length(); double step = 0.0; double prev_time = cur_time; Animation::LoopedFlag looped_flag = Animation::LOOPED_FLAG_NONE; bool node_backward = play_mode == PLAY_MODE_BACKWARD; - if (p_seek) { + double p_time = p_playback_info.time; + bool p_seek = p_playback_info.seeked; + bool p_is_external_seeking = p_playback_info.is_external_seeking; + + if (p_playback_info.seeked) { step = p_time - cur_time; cur_time = p_time; } else { @@ -150,24 +151,30 @@ double AnimationNodeAnimation::_process(double p_time, bool p_seek, bool p_is_ex // Emit start & finish signal. Internally, the detections are the same for backward. // We should use call_deferred since the track keys are still being processed. - if (state->tree && !p_test_only) { + if (process_state->tree && !p_test_only) { // AnimationTree uses seek to 0 "internally" to process the first key of the animation, which is used as the start detection. if (p_seek && !p_is_external_seeking && cur_time == 0) { - state->tree->call_deferred(SNAME("emit_signal"), "animation_started", animation); + process_state->tree->call_deferred(SNAME("emit_signal"), "animation_started", animation); } // Finished. if (prev_time < anim_size && cur_time >= anim_size) { - state->tree->call_deferred(SNAME("emit_signal"), "animation_finished", animation); + process_state->tree->call_deferred(SNAME("emit_signal"), "animation_finished", animation); } } } if (!p_test_only) { + AnimationMixer::PlaybackInfo pi = p_playback_info; if (play_mode == PLAY_MODE_FORWARD) { - blend_animation(animation, cur_time, step, p_seek, p_is_external_seeking, 1.0, looped_flag); + pi.time = cur_time; + pi.delta = step; } else { - blend_animation(animation, anim_size - cur_time, -step, p_seek, p_is_external_seeking, 1.0, looped_flag); + pi.time = anim_size - cur_time; + pi.delta = -step; } + pi.weight = 1.0; + pi.looped_flag = looped_flag; + blend_animation(animation, pi); } set_parameter(time, cur_time); @@ -333,7 +340,7 @@ bool AnimationNodeOneShot::has_filter() const { return true; } -double AnimationNodeOneShot::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) { +double AnimationNodeOneShot::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { OneShotRequest cur_request = static_cast((int)get_parameter(request)); bool cur_active = get_parameter(active); bool cur_internal_active = get_parameter(internal_active); @@ -348,6 +355,10 @@ double AnimationNodeOneShot::_process(double p_time, bool p_seek, bool p_is_exte bool clear_remaining_fade = false; bool is_fading_out = cur_active == true && cur_internal_active == false; + double p_time = p_playback_info.time; + bool p_seek = p_playback_info.seeked; + bool p_is_external_seeking = p_playback_info.is_external_seeking; + if (p_time == 0 && p_seek && !p_is_external_seeking) { clear_remaining_fade = true; // Reset occurs. } @@ -396,7 +407,9 @@ double AnimationNodeOneShot::_process(double p_time, bool p_seek, bool p_is_exte } if (!is_shooting) { - return blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync, p_test_only); + AnimationMixer::PlaybackInfo pi = p_playback_info; + pi.weight = 1.0; + return blend_input(0, pi, FILTER_IGNORE, sync, p_test_only); } if (do_start) { @@ -437,13 +450,23 @@ double AnimationNodeOneShot::_process(double p_time, bool p_seek, bool p_is_exte } } + AnimationMixer::PlaybackInfo pi = p_playback_info; double main_rem = 0.0; if (mix == MIX_MODE_ADD) { - main_rem = blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync, p_test_only); + pi.weight = 1.0; + main_rem = blend_input(0, pi, FILTER_IGNORE, sync, p_test_only); } else { - main_rem = blend_input(0, p_time, use_blend && p_seek, p_is_external_seeking, 1.0 - blend, FILTER_BLEND, sync, p_test_only); // Unlike below, processing this edge is a corner case. + pi.seeked &= use_blend; + pi.weight = 1.0 - blend; + main_rem = blend_input(0, pi, FILTER_BLEND, sync, p_test_only); // Unlike below, processing this edge is a corner case. } - double os_rem = blend_input(1, os_seek ? cur_time : p_time, os_seek, p_is_external_seeking, Math::is_zero_approx(blend) ? CMP_EPSILON : blend, FILTER_PASS, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge. + pi = p_playback_info; + if (os_seek) { + pi.time = cur_time; + } + pi.seeked = os_seek; + pi.weight = Math::is_zero_approx(blend) ? CMP_EPSILON : blend; + double os_rem = blend_input(1, pi, FILTER_PASS, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge. if (do_start) { cur_remaining = os_rem; @@ -542,10 +565,14 @@ bool AnimationNodeAdd2::has_filter() const { return true; } -double AnimationNodeAdd2::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) { +double AnimationNodeAdd2::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { double amount = get_parameter(add_amount); - double rem0 = blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync, p_test_only); - blend_input(1, p_time, p_seek, p_is_external_seeking, amount, FILTER_PASS, sync, p_test_only); + + AnimationMixer::PlaybackInfo pi = p_playback_info; + pi.weight = 1.0; + double rem0 = blend_input(0, pi, FILTER_IGNORE, sync, p_test_only); + pi.weight = amount; + blend_input(1, pi, FILTER_PASS, sync, p_test_only); return rem0; } @@ -576,11 +603,16 @@ bool AnimationNodeAdd3::has_filter() const { return true; } -double AnimationNodeAdd3::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) { +double AnimationNodeAdd3::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { double amount = get_parameter(add_amount); - blend_input(0, p_time, p_seek, p_is_external_seeking, MAX(0, -amount), FILTER_PASS, sync, p_test_only); - double rem0 = blend_input(1, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync, p_test_only); - blend_input(2, p_time, p_seek, p_is_external_seeking, MAX(0, amount), FILTER_PASS, sync, p_test_only); + + AnimationMixer::PlaybackInfo pi = p_playback_info; + pi.weight = MAX(0, -amount); + blend_input(0, pi, FILTER_PASS, sync, p_test_only); + pi.weight = 1.0; + double rem0 = blend_input(1, pi, FILTER_IGNORE, sync, p_test_only); + pi.weight = MAX(0, amount); + blend_input(2, pi, FILTER_PASS, sync, p_test_only); return rem0; } @@ -608,11 +640,14 @@ String AnimationNodeBlend2::get_caption() const { return "Blend2"; } -double AnimationNodeBlend2::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) { +double AnimationNodeBlend2::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { double amount = get_parameter(blend_amount); - double rem0 = blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0 - amount, FILTER_BLEND, sync, p_test_only); - double rem1 = blend_input(1, p_time, p_seek, p_is_external_seeking, amount, FILTER_PASS, sync, p_test_only); + AnimationMixer::PlaybackInfo pi = p_playback_info; + pi.weight = 1.0 - amount; + double rem0 = blend_input(0, pi, FILTER_BLEND, sync, p_test_only); + pi.weight = amount; + double rem1 = blend_input(1, pi, FILTER_PASS, sync, p_test_only); return amount > 0.5 ? rem1 : rem0; // Hacky but good enough. } @@ -643,11 +678,16 @@ String AnimationNodeBlend3::get_caption() const { return "Blend3"; } -double AnimationNodeBlend3::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) { +double AnimationNodeBlend3::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { double amount = get_parameter(blend_amount); - double rem0 = blend_input(0, p_time, p_seek, p_is_external_seeking, MAX(0, -amount), FILTER_IGNORE, sync, p_test_only); - double rem1 = blend_input(1, p_time, p_seek, p_is_external_seeking, 1.0 - ABS(amount), FILTER_IGNORE, sync, p_test_only); - double rem2 = blend_input(2, p_time, p_seek, p_is_external_seeking, MAX(0, amount), FILTER_IGNORE, sync, p_test_only); + + AnimationMixer::PlaybackInfo pi = p_playback_info; + pi.weight = MAX(0, -amount); + double rem0 = blend_input(0, pi, FILTER_IGNORE, sync, p_test_only); + pi.weight = 1.0 - ABS(amount); + double rem1 = blend_input(1, pi, FILTER_IGNORE, sync, p_test_only); + pi.weight = MAX(0, amount); + double rem2 = blend_input(2, pi, FILTER_IGNORE, sync, p_test_only); return amount > 0.5 ? rem2 : (amount < -0.5 ? rem0 : rem1); // Hacky but good enough. } @@ -679,11 +719,16 @@ bool AnimationNodeSub2::has_filter() const { return true; } -double AnimationNodeSub2::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) { +double AnimationNodeSub2::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { double amount = get_parameter(sub_amount); + + AnimationMixer::PlaybackInfo pi = p_playback_info; // Out = Sub.Transform3D^(-1) * In.Transform3D - blend_input(1, p_time, p_seek, p_is_external_seeking, -amount, FILTER_PASS, sync, p_test_only); - return blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync, p_test_only); + pi.weight = -amount; + blend_input(1, pi, FILTER_PASS, sync, p_test_only); + pi.weight = 1.0; + + return blend_input(0, pi, FILTER_IGNORE, sync, p_test_only); } void AnimationNodeSub2::_bind_methods() { @@ -708,13 +753,16 @@ String AnimationNodeTimeScale::get_caption() const { return "TimeScale"; } -double AnimationNodeTimeScale::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) { +double AnimationNodeTimeScale::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { double cur_scale = get_parameter(scale); - if (p_seek) { - return blend_input(0, p_time, true, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only); - } else { - return blend_input(0, p_time * cur_scale, false, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only); + + AnimationMixer::PlaybackInfo pi = p_playback_info; + pi.weight = 1.0; + if (!pi.seeked) { + pi.time *= cur_scale; } + + return blend_input(0, pi, FILTER_IGNORE, true, p_test_only); } void AnimationNodeTimeScale::_bind_methods() { @@ -738,17 +786,19 @@ String AnimationNodeTimeSeek::get_caption() const { return "TimeSeek"; } -double AnimationNodeTimeSeek::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) { +double AnimationNodeTimeSeek::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { double cur_seek_pos = get_parameter(seek_pos_request); - if (p_seek) { - return blend_input(0, p_time, true, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only); - } else if (cur_seek_pos >= 0) { - double ret = blend_input(0, cur_seek_pos, true, true, 1.0, FILTER_IGNORE, true, p_test_only); + + AnimationMixer::PlaybackInfo pi = p_playback_info; + pi.weight = 1.0; + if (cur_seek_pos >= 0) { + pi.time = cur_seek_pos; + pi.seeked = true; + pi.is_external_seeking = true; set_parameter(seek_pos_request, -1.0); // Reset. - return ret; - } else { - return blend_input(0, p_time, false, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only); } + + return blend_input(0, pi, FILTER_IGNORE, true, p_test_only); } void AnimationNodeTimeSeek::_bind_methods() { @@ -931,7 +981,7 @@ bool AnimationNodeTransition::is_allow_transition_to_self() const { return allow_transition_to_self; } -double AnimationNodeTransition::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) { +double AnimationNodeTransition::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { String cur_transition_request = get_parameter(transition_request); int cur_current_index = get_parameter(current_index); int cur_prev_index = get_parameter(prev_index); @@ -959,6 +1009,10 @@ double AnimationNodeTransition::_process(double p_time, bool p_seek, bool p_is_e pending_update = false; } + double p_time = p_playback_info.time; + bool p_seek = p_playback_info.seeked; + bool p_is_external_seeking = p_playback_info.is_external_seeking; + if (p_time == 0 && p_seek && !p_is_external_seeking) { clear_remaining_fade = true; // Reset occurs. } @@ -994,10 +1048,15 @@ double AnimationNodeTransition::_process(double p_time, bool p_seek, bool p_is_e set_parameter(prev_index, -1); } + AnimationMixer::PlaybackInfo pi = p_playback_info; + // Special case for restart. if (restart) { set_parameter(time, 0); - return blend_input(cur_current_index, 0, true, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only); + pi.time = 0; + pi.seeked = true; + pi.weight = 1.0; + return blend_input(cur_current_index, pi, FILTER_IGNORE, true, p_test_only); } if (switched) { @@ -1013,16 +1072,18 @@ double AnimationNodeTransition::_process(double p_time, bool p_seek, bool p_is_e double abs_time = Math::abs(p_time); if (sync) { + pi.weight = 0; for (int i = 0; i < get_input_count(); i++) { if (i != cur_current_index && i != cur_prev_index) { - blend_input(i, p_time, p_seek, p_is_external_seeking, 0, FILTER_IGNORE, true, p_test_only); + blend_input(i, pi, FILTER_IGNORE, true, p_test_only); } } } if (cur_prev_index < 0) { // Process current animation, check for transition. - rem = blend_input(cur_current_index, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only); + pi.weight = 1.0; + rem = blend_input(cur_current_index, pi, FILTER_IGNORE, true, p_test_only); if (p_seek) { cur_time = abs_time; @@ -1051,13 +1112,17 @@ double AnimationNodeTransition::_process(double p_time, bool p_seek, bool p_is_e } // Blend values must be more than CMP_EPSILON to process discrete keys in edge. + pi.weight = blend_inv; if (input_data[cur_current_index].reset && !p_seek && switched) { // Just switched, seek to start of current. - rem = blend_input(cur_current_index, 0, true, p_is_external_seeking, blend_inv, FILTER_IGNORE, true, p_test_only); - } else { - rem = blend_input(cur_current_index, p_time, p_seek, p_is_external_seeking, blend_inv, FILTER_IGNORE, true, p_test_only); + pi.time = 0; + pi.seeked = true; } + rem = blend_input(cur_current_index, pi, FILTER_IGNORE, true, p_test_only); - blend_input(cur_prev_index, p_time, use_blend && p_seek, p_is_external_seeking, blend, FILTER_IGNORE, true, p_test_only); + pi = p_playback_info; + pi.seeked &= use_blend; + pi.weight = blend; + blend_input(cur_prev_index, pi, FILTER_IGNORE, true, p_test_only); if (p_seek) { cur_time = abs_time; } else { @@ -1116,8 +1181,10 @@ String AnimationNodeOutput::get_caption() const { return "Output"; } -double AnimationNodeOutput::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) { - return blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only); +double AnimationNodeOutput::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { + AnimationMixer::PlaybackInfo pi = p_playback_info; + pi.weight = 1.0; + return blend_input(0, pi, FILTER_IGNORE, true, p_test_only); } AnimationNodeOutput::AnimationNodeOutput() { @@ -1140,9 +1207,9 @@ void AnimationNodeBlendTree::add_node(const StringName &p_name, Refconnect("tree_changed", callable_mp(this, &AnimationNodeBlendTree::_tree_changed), CONNECT_REFERENCE_COUNTED); - p_node->connect("animation_node_renamed", callable_mp(this, &AnimationNodeBlendTree::_animation_node_renamed), CONNECT_REFERENCE_COUNTED); - p_node->connect("animation_node_removed", callable_mp(this, &AnimationNodeBlendTree::_animation_node_removed), CONNECT_REFERENCE_COUNTED); + p_node->connect(SNAME("tree_changed"), callable_mp(this, &AnimationNodeBlendTree::_tree_changed), CONNECT_REFERENCE_COUNTED); + p_node->connect(SNAME("animation_node_renamed"), callable_mp(this, &AnimationNodeBlendTree::_animation_node_renamed), CONNECT_REFERENCE_COUNTED); + p_node->connect(SNAME("animation_node_removed"), callable_mp(this, &AnimationNodeBlendTree::_animation_node_removed), CONNECT_REFERENCE_COUNTED); p_node->connect_changed(callable_mp(this, &AnimationNodeBlendTree::_node_changed).bind(p_name), CONNECT_REFERENCE_COUNTED); } @@ -1202,9 +1269,9 @@ void AnimationNodeBlendTree::remove_node(const StringName &p_name) { { Ref node = nodes[p_name].node; - node->disconnect("tree_changed", callable_mp(this, &AnimationNodeBlendTree::_tree_changed)); - node->disconnect("animation_node_renamed", callable_mp(this, &AnimationNodeBlendTree::_animation_node_renamed)); - node->disconnect("animation_node_removed", callable_mp(this, &AnimationNodeBlendTree::_animation_node_removed)); + node->disconnect(SNAME("tree_changed"), callable_mp(this, &AnimationNodeBlendTree::_tree_changed)); + node->disconnect(SNAME("animation_node_renamed"), callable_mp(this, &AnimationNodeBlendTree::_animation_node_renamed)); + node->disconnect(SNAME("animation_node_removed"), callable_mp(this, &AnimationNodeBlendTree::_animation_node_removed)); node->disconnect_changed(callable_mp(this, &AnimationNodeBlendTree::_node_changed)); } @@ -1333,9 +1400,14 @@ String AnimationNodeBlendTree::get_caption() const { return "BlendTree"; } -double AnimationNodeBlendTree::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) { +double AnimationNodeBlendTree::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { Ref output = nodes[SceneStringNames::get_singleton()->output].node; - return _blend_node("output", nodes[SceneStringNames::get_singleton()->output].connections, this, output, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true, nullptr, p_test_only); + node_state.connections = nodes[SceneStringNames::get_singleton()->output].connections; + + AnimationMixer::PlaybackInfo pi = p_playback_info; + pi.weight = 1.0; + + return _blend_node(output, "output", this, pi, FILTER_IGNORE, true, p_test_only, nullptr); } void AnimationNodeBlendTree::get_node_list(List *r_list) { @@ -1496,7 +1568,7 @@ void AnimationNodeBlendTree::_bind_methods() { BIND_CONSTANT(CONNECTION_ERROR_SAME_NODE); BIND_CONSTANT(CONNECTION_ERROR_CONNECTION_EXISTS); - ADD_SIGNAL(MethodInfo("node_changed", PropertyInfo(Variant::STRING_NAME, "node_name"))); + ADD_SIGNAL(MethodInfo(SNAME("node_changed"), PropertyInfo(Variant::STRING_NAME, "node_name"))); } void AnimationNodeBlendTree::_initialize_node_tree() { diff --git a/scene/animation/animation_blend_tree.h b/scene/animation/animation_blend_tree.h index bf95c211f6d..f38d7ebde8c 100644 --- a/scene/animation/animation_blend_tree.h +++ b/scene/animation/animation_blend_tree.h @@ -53,7 +53,7 @@ public: static Vector (*get_editable_animation_list)(); virtual String get_caption() const override; - virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override; + virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; void set_animation(const StringName &p_name); StringName get_animation() const; @@ -161,7 +161,7 @@ public: MixMode get_mix_mode() const; virtual bool has_filter() const override; - virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override; + virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; AnimationNodeOneShot(); }; @@ -184,7 +184,7 @@ public: virtual String get_caption() const override; virtual bool has_filter() const override; - virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override; + virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; AnimationNodeAdd2(); }; @@ -204,7 +204,7 @@ public: virtual String get_caption() const override; virtual bool has_filter() const override; - virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override; + virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; AnimationNodeAdd3(); }; @@ -222,7 +222,7 @@ public: virtual Variant get_parameter_default_value(const StringName &p_parameter) const override; virtual String get_caption() const override; - virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override; + virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; virtual bool has_filter() const override; AnimationNodeBlend2(); @@ -242,7 +242,7 @@ public: virtual String get_caption() const override; - virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override; + virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; AnimationNodeBlend3(); }; @@ -261,7 +261,7 @@ public: virtual String get_caption() const override; virtual bool has_filter() const override; - virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override; + virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; AnimationNodeSub2(); }; @@ -280,7 +280,7 @@ public: virtual String get_caption() const override; - virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override; + virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; AnimationNodeTimeScale(); }; @@ -299,7 +299,7 @@ public: virtual String get_caption() const override; - virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override; + virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; AnimationNodeTimeSeek(); }; @@ -363,7 +363,7 @@ public: void set_allow_transition_to_self(bool p_enable); bool is_allow_transition_to_self() const; - virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override; + virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; AnimationNodeTransition(); }; @@ -373,7 +373,7 @@ class AnimationNodeOutput : public AnimationNode { public: virtual String get_caption() const override; - virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override; + virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; AnimationNodeOutput(); }; @@ -445,7 +445,7 @@ public: void get_node_connections(List *r_connections) const; virtual String get_caption() const override; - virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override; + virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; void get_node_list(List *r_list); diff --git a/scene/animation/animation_mixer.cpp b/scene/animation/animation_mixer.cpp new file mode 100644 index 00000000000..dafee025445 --- /dev/null +++ b/scene/animation/animation_mixer.cpp @@ -0,0 +1,2115 @@ +/**************************************************************************/ +/* animation_mixer.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "animation_mixer.h" + +#include "core/config/engine.h" +#include "scene/animation/animation_player.h" +#include "scene/resources/animation.h" +#include "scene/scene_string_names.h" +#include "servers/audio/audio_stream.h" + +#ifdef TOOLS_ENABLED +#include "editor/editor_node.h" +#include "editor/editor_undo_redo_manager.h" +#endif // TOOLS_ENABLED + +bool AnimationMixer::_set(const StringName &p_name, const Variant &p_value) { + String name = p_name; + +#ifndef DISABLE_DEPRECATED + if (name.begins_with("anims/")) { + // Backwards compatibility with 3.x, add them to "default" library. + String which = name.get_slicec('/', 1); + + Ref anim = p_value; + Ref al; + if (!has_animation_library(StringName())) { + al.instantiate(); + add_animation_library(StringName(), al); + } else { + al = get_animation_library(StringName()); + } + al->add_animation(which, anim); + } else if (name.begins_with("libraries")) { +#else + if (name.begins_with("libraries")) { +#endif // DISABLE_DEPRECATED + Dictionary d = p_value; + while (animation_libraries.size()) { + remove_animation_library(animation_libraries[0].name); + } + List keys; + d.get_key_list(&keys); + for (const Variant &K : keys) { + StringName lib_name = K; + Ref lib = d[lib_name]; + add_animation_library(lib_name, lib); + } + emit_signal(SNAME("animation_libraries_updated")); + + } else { + return false; + } + + return true; +} + +bool AnimationMixer::_get(const StringName &p_name, Variant &r_ret) const { + String name = p_name; + + if (name.begins_with("libraries")) { + Dictionary d; + for (const AnimationLibraryData &lib : animation_libraries) { + d[lib.name] = lib.library; + } + r_ret = d; + } else { + return false; + } + + return true; +} + +void AnimationMixer::_get_property_list(List *p_list) const { + List anim_names; + anim_names.push_back(PropertyInfo(Variant::DICTIONARY, PNAME("libraries"))); + for (const PropertyInfo &E : anim_names) { + p_list->push_back(E); + } + + for (PropertyInfo &E : *p_list) { + _validate_property(E); + } +} + +void AnimationMixer::_validate_property(PropertyInfo &p_property) const { +#ifdef TOOLS_ENABLED + if (editing && (p_property.name == "active" || p_property.name == "deterministic" || p_property.name == "root_motion_track")) { + p_property.usage |= PROPERTY_USAGE_READ_ONLY; + } +#endif // TOOLS_ENABLED +} + +/* -------------------------------------------- */ +/* -- Data lists ------------------------------ */ +/* -------------------------------------------- */ + +void AnimationMixer::_animation_set_cache_update() { + // Relatively fast function to update all animations. + animation_set_update_pass++; + bool clear_cache_needed = false; + + // Update changed and add otherwise. + for (const AnimationLibraryData &lib : animation_libraries) { + for (const KeyValue> &K : lib.library->animations) { + StringName key = lib.name == StringName() ? K.key : StringName(String(lib.name) + "/" + String(K.key)); + if (!animation_set.has(key)) { + AnimationData ad; + ad.animation = K.value; + ad.animation_library = lib.name; + ad.name = key; + ad.last_update = animation_set_update_pass; + animation_set.insert(ad.name, ad); + } else { + AnimationData &ad = animation_set[key]; + if (ad.last_update != animation_set_update_pass) { + // Was not updated, update. If the animation is duplicated, the second one will be ignored. + if (ad.animation != K.value || ad.animation_library != lib.name) { + // Animation changed, update and clear caches. + clear_cache_needed = true; + ad.animation = K.value; + ad.animation_library = lib.name; + } + + ad.last_update = animation_set_update_pass; + } + } + } + } + + // Check removed. + List to_erase; + for (const KeyValue &E : animation_set) { + if (E.value.last_update != animation_set_update_pass) { + // Was not updated, must be erased. + to_erase.push_back(E.key); + clear_cache_needed = true; + } + } + + while (to_erase.size()) { + animation_set.erase(to_erase.front()->get()); + to_erase.pop_front(); + } + + if (clear_cache_needed) { + // If something was modified or removed, caches need to be cleared. + _clear_caches(); + } + + emit_signal(SNAME("animation_list_changed")); +} + +void AnimationMixer::_animation_added(const StringName &p_name, const StringName &p_library) { + _animation_set_cache_update(); +} + +void AnimationMixer::_animation_removed(const StringName &p_name, const StringName &p_library) { + StringName name = p_library == StringName() ? p_name : StringName(String(p_library) + "/" + String(p_name)); + + if (!animation_set.has(name)) { + return; // No need to update because not the one from the library being used. + } + + _animation_set_cache_update(); + + _remove_animation(name); +} + +void AnimationMixer::_animation_renamed(const StringName &p_name, const StringName &p_to_name, const StringName &p_library) { + StringName from_name = p_library == StringName() ? p_name : StringName(String(p_library) + "/" + String(p_name)); + StringName to_name = p_library == StringName() ? p_to_name : StringName(String(p_library) + "/" + String(p_to_name)); + + if (!animation_set.has(from_name)) { + return; // No need to update because not the one from the library being used. + } + _animation_set_cache_update(); + + _rename_animation(from_name, to_name); +} + +void AnimationMixer::_animation_changed(const StringName &p_name) { + _clear_caches(); +} + +void AnimationMixer::_set_active(bool p_active) { + // +} + +void AnimationMixer::_remove_animation(const StringName &p_name) { + // +} + +void AnimationMixer::_rename_animation(const StringName &p_from_name, const StringName &p_to_name) { + // +} + +TypedArray AnimationMixer::_get_animation_library_list() const { + TypedArray ret; + for (const AnimationLibraryData &lib : animation_libraries) { + ret.push_back(lib.name); + } + return ret; +} + +void AnimationMixer::get_animation_library_list(List *p_libraries) const { + for (const AnimationLibraryData &lib : animation_libraries) { + p_libraries->push_back(lib.name); + } +} + +Ref AnimationMixer::get_animation_library(const StringName &p_name) const { + for (const AnimationLibraryData &lib : animation_libraries) { + if (lib.name == p_name) { + return lib.library; + } + } + ERR_FAIL_V(Ref()); +} + +bool AnimationMixer::has_animation_library(const StringName &p_name) const { + for (const AnimationLibraryData &lib : animation_libraries) { + if (lib.name == p_name) { + return true; + } + } + + return false; +} + +StringName AnimationMixer::find_animation_library(const Ref &p_animation) const { + for (const KeyValue &E : animation_set) { + if (E.value.animation == p_animation) { + return E.value.animation_library; + } + } + return StringName(); +} + +Error AnimationMixer::add_animation_library(const StringName &p_name, const Ref &p_animation_library) { + ERR_FAIL_COND_V(p_animation_library.is_null(), ERR_INVALID_PARAMETER); +#ifdef DEBUG_ENABLED + ERR_FAIL_COND_V_MSG(String(p_name).contains("/") || String(p_name).contains(":") || String(p_name).contains(",") || String(p_name).contains("["), ERR_INVALID_PARAMETER, "Invalid animation name: " + String(p_name) + "."); +#endif + + int insert_pos = 0; + + for (const AnimationLibraryData &lib : animation_libraries) { + ERR_FAIL_COND_V_MSG(lib.name == p_name, ERR_ALREADY_EXISTS, "Can't add animation library twice with name: " + String(p_name)); + ERR_FAIL_COND_V_MSG(lib.library == p_animation_library, ERR_ALREADY_EXISTS, "Can't add animation library twice (adding as '" + p_name.operator String() + "', exists as '" + lib.name.operator String() + "'."); + + if (lib.name.operator String() >= p_name.operator String()) { + break; + } + + insert_pos++; + } + + AnimationLibraryData ald; + ald.name = p_name; + ald.library = p_animation_library; + + animation_libraries.insert(insert_pos, ald); + + ald.library->connect(SNAME("animation_added"), callable_mp(this, &AnimationMixer::_animation_added).bind(p_name)); + ald.library->connect(SNAME("animation_removed"), callable_mp(this, &AnimationMixer::_animation_removed).bind(p_name)); + ald.library->connect(SNAME("animation_renamed"), callable_mp(this, &AnimationMixer::_animation_renamed).bind(p_name)); + ald.library->connect(SNAME("animation_changed"), callable_mp(this, &AnimationMixer::_animation_changed)); + + _animation_set_cache_update(); + + notify_property_list_changed(); + + return OK; +} + +void AnimationMixer::remove_animation_library(const StringName &p_name) { + int at_pos = -1; + + for (uint32_t i = 0; i < animation_libraries.size(); i++) { + if (animation_libraries[i].name == p_name) { + at_pos = i; + break; + } + } + + ERR_FAIL_COND(at_pos == -1); + + animation_libraries[at_pos].library->disconnect(SNAME("animation_added"), callable_mp(this, &AnimationMixer::_animation_added)); + animation_libraries[at_pos].library->disconnect(SNAME("animation_removed"), callable_mp(this, &AnimationMixer::_animation_removed)); + animation_libraries[at_pos].library->disconnect(SNAME("animation_renamed"), callable_mp(this, &AnimationMixer::_animation_renamed)); + animation_libraries[at_pos].library->disconnect(SNAME("animation_changed"), callable_mp(this, &AnimationMixer::_animation_changed)); + + animation_libraries.remove_at(at_pos); + _animation_set_cache_update(); + + notify_property_list_changed(); +} + +void AnimationMixer::rename_animation_library(const StringName &p_name, const StringName &p_new_name) { + if (p_name == p_new_name) { + return; + } +#ifdef DEBUG_ENABLED + ERR_FAIL_COND_MSG(String(p_new_name).contains("/") || String(p_new_name).contains(":") || String(p_new_name).contains(",") || String(p_new_name).contains("["), "Invalid animation library name: " + String(p_new_name) + "."); +#endif + + bool found = false; + for (AnimationLibraryData &lib : animation_libraries) { + ERR_FAIL_COND_MSG(lib.name == p_new_name, "Can't rename animation library to another existing name: " + String(p_new_name) + "."); + if (lib.name == p_name) { + found = true; + lib.name = p_new_name; + // rename connections + lib.library->disconnect(SNAME("animation_added"), callable_mp(this, &AnimationMixer::_animation_added)); + lib.library->disconnect(SNAME("animation_removed"), callable_mp(this, &AnimationMixer::_animation_removed)); + lib.library->disconnect(SNAME("animation_renamed"), callable_mp(this, &AnimationMixer::_animation_renamed)); + + lib.library->connect(SNAME("animation_added"), callable_mp(this, &AnimationMixer::_animation_added).bind(p_new_name)); + lib.library->connect(SNAME("animation_removed"), callable_mp(this, &AnimationMixer::_animation_removed).bind(p_new_name)); + lib.library->connect(SNAME("animation_renamed"), callable_mp(this, &AnimationMixer::_animation_renamed).bind(p_new_name)); + + for (const KeyValue> &K : lib.library->animations) { + StringName old_name = p_name == StringName() ? K.key : StringName(String(p_name) + "/" + String(K.key)); + StringName new_name = p_new_name == StringName() ? K.key : StringName(String(p_new_name) + "/" + String(K.key)); + _rename_animation(old_name, new_name); + } + } + } + + ERR_FAIL_COND(!found); + + animation_libraries.sort(); // Must keep alphabetical order. + + _animation_set_cache_update(); // Update cache. + + notify_property_list_changed(); +} + +void AnimationMixer::get_animation_list(List *p_animations) const { + List anims; + for (const KeyValue &E : animation_set) { + anims.push_back(E.key); + } + anims.sort(); + for (const String &E : anims) { + p_animations->push_back(E); + } +} + +Ref AnimationMixer::get_animation(const StringName &p_name) const { + ERR_FAIL_COND_V_MSG(!animation_set.has(p_name), Ref(), vformat("Animation not found: \"%s\".", p_name)); + const AnimationData &anim_data = animation_set[p_name]; + return anim_data.animation; +} + +bool AnimationMixer::has_animation(const StringName &p_name) const { + return animation_set.has(p_name); +} + +StringName AnimationMixer::find_animation(const Ref &p_animation) const { + for (const KeyValue &E : animation_set) { + if (E.value.animation == p_animation) { + return E.key; + } + } + return StringName(); +} + +/* -------------------------------------------- */ +/* -- General settings for animation ---------- */ +/* -------------------------------------------- */ + +void AnimationMixer::_set_process(bool p_process, bool p_force) { + if (processing == p_process && !p_force) { + return; + } + + switch (callback_mode_process) { + case ANIMATION_CALLBACK_MODE_PROCESS_PHYSICS: +#ifdef TOOLS_ENABLED + set_physics_process_internal(p_process && active && !editing); +#else + set_physics_process_internal(p_process && active); +#endif // TOOLS_ENABLED + break; + case ANIMATION_CALLBACK_MODE_PROCESS_IDLE: +#ifdef TOOLS_ENABLED + set_process_internal(p_process && active && !editing); +#else + set_process_internal(p_process && active); +#endif // TOOLS_ENABLED + break; + case ANIMATION_CALLBACK_MODE_PROCESS_MANUAL: + break; + } + + processing = p_process; +} + +void AnimationMixer::set_active(bool p_active) { + if (active == p_active) { + return; + } + + active = p_active; + _set_active(active); + _set_process(processing, true); + + if (!active && is_inside_tree()) { + _clear_caches(); + } +} + +bool AnimationMixer::is_active() const { + return active; +} + +void AnimationMixer::set_root_node(const NodePath &p_path) { + root_node = p_path; + clear_caches(); +} + +NodePath AnimationMixer::get_root_node() const { + return root_node; +} + +void AnimationMixer::set_deterministic(bool p_deterministic) { + deterministic = p_deterministic; + clear_caches(); +} + +bool AnimationMixer::is_deterministic() const { + return deterministic; +} + +void AnimationMixer::set_callback_mode_process(AnimationCallbackModeProcess p_mode) { + if (callback_mode_process == p_mode) { + return; + } + + bool was_active = is_active(); + if (was_active) { + set_active(false); + } + + callback_mode_process = p_mode; + + if (was_active) { + set_active(true); + } + +#ifdef TOOLS_ENABLED + emit_signal(SNAME("mixer_updated")); +#endif // TOOLS_ENABLED +} + +AnimationMixer::AnimationCallbackModeProcess AnimationMixer::get_callback_mode_process() const { + return callback_mode_process; +} + +void AnimationMixer::set_callback_mode_method(AnimationCallbackModeMethod p_mode) { + callback_mode_method = p_mode; +#ifdef TOOLS_ENABLED + emit_signal(SNAME("mixer_updated")); +#endif // TOOLS_ENABLED +} + +AnimationMixer::AnimationCallbackModeMethod AnimationMixer::get_callback_mode_method() const { + return callback_mode_method; +} + +void AnimationMixer::set_audio_max_polyphony(int p_audio_max_polyphony) { + ERR_FAIL_COND(p_audio_max_polyphony < 0 || p_audio_max_polyphony > 128); + audio_max_polyphony = p_audio_max_polyphony; +} + +int AnimationMixer::get_audio_max_polyphony() const { + return audio_max_polyphony; +} + +#ifdef TOOLS_ENABLED +void AnimationMixer::set_editing(bool p_editing) { + if (editing == p_editing) { + return; + } + + editing = p_editing; + _set_process(processing, true); + + if (editing && is_inside_tree()) { + _clear_caches(); + } + + notify_property_list_changed(); // To make active readonly. +} + +bool AnimationMixer::is_editing() const { + return editing; +} + +void AnimationMixer::set_dummy(bool p_dummy) { + dummy = p_dummy; +} + +bool AnimationMixer::is_dummy() const { + return dummy; +} +#endif // TOOLS_ENABLED + +/* -------------------------------------------- */ +/* -- Caches for blending --------------------- */ +/* -------------------------------------------- */ + +void AnimationMixer::_clear_caches() { + _init_root_motion_cache(); + _clear_audio_streams(); + _clear_playing_caches(); + for (KeyValue &K : track_cache) { + memdelete(K.value); + } + track_cache.clear(); + cache_valid = false; + + emit_signal(SNAME("caches_cleared")); +} + +void AnimationMixer::_clear_audio_streams() { + for (int i = 0; i < playing_audio_stream_players.size(); i++) { + playing_audio_stream_players[i]->call(SNAME("stop")); + playing_audio_stream_players[i]->call(SNAME("set_stream"), Ref()); + } + playing_audio_stream_players.clear(); +} + +void AnimationMixer::_clear_playing_caches() { + for (const TrackCache *E : playing_caches) { + if (ObjectDB::get_instance(E->object_id)) { + E->object->call(SNAME("stop")); + } + } + playing_caches.clear(); +} + +void AnimationMixer::_init_root_motion_cache() { + root_motion_cache.loc = Vector3(0, 0, 0); + root_motion_cache.rot = Quaternion(0, 0, 0, 1); + root_motion_cache.scale = Vector3(1, 1, 1); + root_motion_position = Vector3(0, 0, 0); + root_motion_rotation = Quaternion(0, 0, 0, 1); + root_motion_scale = Vector3(0, 0, 0); + root_motion_position_accumulator = Vector3(0, 0, 0); + root_motion_rotation_accumulator = Quaternion(0, 0, 0, 1); + root_motion_scale_accumulator = Vector3(1, 1, 1); +} + +bool AnimationMixer::_update_caches() { + setup_pass++; + + root_motion_cache.loc = Vector3(0, 0, 0); + root_motion_cache.rot = Quaternion(0, 0, 0, 1); + root_motion_cache.scale = Vector3(1, 1, 1); + + List sname; + get_animation_list(&sname); + + Node *parent = get_node_or_null(root_node); + if (!parent) { + cache_valid = false; + return false; + } + + Ref reset_anim; + bool has_reset_anim = has_animation(SceneStringNames::get_singleton()->RESET); + if (has_reset_anim) { + reset_anim = get_animation(SceneStringNames::get_singleton()->RESET); + } + for (const StringName &E : sname) { + Ref anim = get_animation(E); + for (int i = 0; i < anim->get_track_count(); i++) { + NodePath path = anim->track_get_path(i); + Animation::TrackType track_type = anim->track_get_type(i); + + Animation::TrackType track_cache_type = track_type; + if (track_cache_type == Animation::TYPE_POSITION_3D || track_cache_type == Animation::TYPE_ROTATION_3D || track_cache_type == Animation::TYPE_SCALE_3D) { + track_cache_type = Animation::TYPE_POSITION_3D; // Reference them as position3D tracks, even if they modify rotation or scale. + } + + TrackCache *track = nullptr; + if (track_cache.has(path)) { + track = track_cache.get(path); + } + + // If not valid, delete track. + if (track && (track->type != track_cache_type || ObjectDB::get_instance(track->object_id) == nullptr)) { + playing_caches.erase(track); + memdelete(track); + track_cache.erase(path); + track = nullptr; + } + + if (!track) { + Ref resource; + Vector leftover_path; + Node *child = parent->get_node_and_resource(path, resource, leftover_path); + + if (!child) { + ERR_PRINT("AnimationMixer: '" + String(E) + "', couldn't resolve track: '" + String(path) + "'."); + continue; + } + + switch (track_type) { + case Animation::TYPE_VALUE: { + TrackCacheValue *track_value = memnew(TrackCacheValue); + + if (resource.is_valid()) { + track_value->object = resource.ptr(); + } else { + track_value->object = child; + } + + track_value->is_continuous = anim->value_track_get_update_mode(i) != Animation::UPDATE_DISCRETE; + track_value->is_using_angle = anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_LINEAR_ANGLE || anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_CUBIC_ANGLE; + + track_value->subpath = leftover_path; + track_value->object_id = track_value->object->get_instance_id(); + + track = track_value; + + // If a value track without a key is cached first, the initial value cannot be determined. + // It is a corner case, but which may cause problems with blending. + ERR_CONTINUE_MSG(anim->track_get_key_count(i) == 0, "AnimationMixer: '" + String(E) + "', Value Track: '" + String(path) + "' must have at least one key to cache for blending."); + track_value->init_value = anim->track_get_key_value(i, 0); + track_value->init_value.zero(); + + // If there is a Reset Animation, it takes precedence by overwriting. + if (has_reset_anim) { + int rt = reset_anim->find_track(path, track_type); + if (rt >= 0 && reset_anim->track_get_key_count(rt) > 0) { + track_value->init_value = reset_anim->track_get_key_value(rt, 0); + } + } + } break; + case Animation::TYPE_POSITION_3D: + case Animation::TYPE_ROTATION_3D: + case Animation::TYPE_SCALE_3D: { +#ifndef _3D_DISABLED + Node3D *node_3d = Object::cast_to(child); + + if (!node_3d) { + ERR_PRINT("AnimationMixer: '" + String(E) + "', transform track does not point to Node3D: '" + String(path) + "'."); + continue; + } + + TrackCacheTransform *track_xform = memnew(TrackCacheTransform); + track_xform->type = Animation::TYPE_POSITION_3D; + + track_xform->node_3d = node_3d; + track_xform->skeleton = nullptr; + track_xform->bone_idx = -1; + + bool has_rest = false; + if (path.get_subname_count() == 1 && Object::cast_to(node_3d)) { + Skeleton3D *sk = Object::cast_to(node_3d); + track_xform->skeleton = sk; + int bone_idx = sk->find_bone(path.get_subname(0)); + if (bone_idx != -1) { + has_rest = true; + track_xform->bone_idx = bone_idx; + Transform3D rest = sk->get_bone_rest(bone_idx); + track_xform->init_loc = rest.origin; + track_xform->init_rot = rest.basis.get_rotation_quaternion(); + track_xform->init_scale = rest.basis.get_scale(); + } + } + + track_xform->object = node_3d; + track_xform->object_id = track_xform->object->get_instance_id(); + + track = track_xform; + + switch (track_type) { + case Animation::TYPE_POSITION_3D: { + track_xform->loc_used = true; + } break; + case Animation::TYPE_ROTATION_3D: { + track_xform->rot_used = true; + } break; + case Animation::TYPE_SCALE_3D: { + track_xform->scale_used = true; + } break; + default: { + } + } + + // For non Skeleton3D bone animation. + if (has_reset_anim && !has_rest) { + int rt = reset_anim->find_track(path, track_type); + if (rt >= 0 && reset_anim->track_get_key_count(rt) > 0) { + switch (track_type) { + case Animation::TYPE_POSITION_3D: { + track_xform->init_loc = reset_anim->track_get_key_value(rt, 0); + } break; + case Animation::TYPE_ROTATION_3D: { + track_xform->init_rot = reset_anim->track_get_key_value(rt, 0); + } break; + case Animation::TYPE_SCALE_3D: { + track_xform->init_scale = reset_anim->track_get_key_value(rt, 0); + } break; + default: { + } + } + } + } +#endif // _3D_DISABLED + } break; + case Animation::TYPE_BLEND_SHAPE: { +#ifndef _3D_DISABLED + if (path.get_subname_count() != 1) { + ERR_PRINT("AnimationMixer: '" + String(E) + "', blend shape track does not contain a blend shape subname: '" + String(path) + "'."); + continue; + } + MeshInstance3D *mesh_3d = Object::cast_to(child); + + if (!mesh_3d) { + ERR_PRINT("AnimationMixer: '" + String(E) + "', blend shape track does not point to MeshInstance3D: '" + String(path) + "'."); + continue; + } + + StringName blend_shape_name = path.get_subname(0); + int blend_shape_idx = mesh_3d->find_blend_shape_by_name(blend_shape_name); + if (blend_shape_idx == -1) { + ERR_PRINT("AnimationMixer: '" + String(E) + "', blend shape track points to a non-existing name: '" + String(blend_shape_name) + "'."); + continue; + } + + TrackCacheBlendShape *track_bshape = memnew(TrackCacheBlendShape); + + track_bshape->mesh_3d = mesh_3d; + track_bshape->shape_index = blend_shape_idx; + + track_bshape->object = mesh_3d; + track_bshape->object_id = mesh_3d->get_instance_id(); + track = track_bshape; + + if (has_reset_anim) { + int rt = reset_anim->find_track(path, track_type); + if (rt >= 0 && reset_anim->track_get_key_count(rt) > 0) { + track_bshape->init_value = reset_anim->track_get_key_value(rt, 0); + } + } +#endif + } break; + case Animation::TYPE_METHOD: { + TrackCacheMethod *track_method = memnew(TrackCacheMethod); + + if (resource.is_valid()) { + track_method->object = resource.ptr(); + } else { + track_method->object = child; + } + + track_method->object_id = track_method->object->get_instance_id(); + + track = track_method; + + } break; + case Animation::TYPE_BEZIER: { + TrackCacheBezier *track_bezier = memnew(TrackCacheBezier); + + if (resource.is_valid()) { + track_bezier->object = resource.ptr(); + } else { + track_bezier->object = child; + } + + track_bezier->subpath = leftover_path; + track_bezier->object_id = track_bezier->object->get_instance_id(); + + track = track_bezier; + + if (has_reset_anim) { + int rt = reset_anim->find_track(path, track_type); + if (rt >= 0 && reset_anim->track_get_key_count(rt) > 0) { + track_bezier->init_value = (reset_anim->track_get_key_value(rt, 0).operator Array())[0]; + } + } + } break; + case Animation::TYPE_AUDIO: { + TrackCacheAudio *track_audio = memnew(TrackCacheAudio); + + track_audio->object = child; + track_audio->object_id = track_audio->object->get_instance_id(); + track_audio->audio_stream.instantiate(); + track_audio->audio_stream->set_polyphony(audio_max_polyphony); + + track = track_audio; + + } break; + case Animation::TYPE_ANIMATION: { + TrackCacheAnimation *track_animation = memnew(TrackCacheAnimation); + + track_animation->object = child; + track_animation->object_id = track_animation->object->get_instance_id(); + + track = track_animation; + + } break; + default: { + ERR_PRINT("Animation corrupted (invalid track type)."); + continue; + } + } + + track_cache[path] = track; + } else if (track_cache_type == Animation::TYPE_POSITION_3D) { + TrackCacheTransform *track_xform = static_cast(track); + if (track->setup_pass != setup_pass) { + track_xform->loc_used = false; + track_xform->rot_used = false; + track_xform->scale_used = false; + } + switch (track_type) { + case Animation::TYPE_POSITION_3D: { + track_xform->loc_used = true; + } break; + case Animation::TYPE_ROTATION_3D: { + track_xform->rot_used = true; + } break; + case Animation::TYPE_SCALE_3D: { + track_xform->scale_used = true; + } break; + default: { + } + } + } else if (track_cache_type == Animation::TYPE_VALUE) { + // If it has at least one angle interpolation, it also uses angle interpolation for blending. + TrackCacheValue *track_value = static_cast(track); + bool was_continuous = track_value->is_continuous; + bool was_using_angle = track_value->is_using_angle; + track_value->is_continuous |= anim->value_track_get_update_mode(i) != Animation::UPDATE_DISCRETE; + track_value->is_using_angle |= anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_LINEAR_ANGLE || anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_CUBIC_ANGLE; + + if (was_continuous != track_value->is_continuous) { + WARN_PRINT_ONCE_ED("Value Track: " + String(path) + " has different update modes between some animations may be blended. Blending prioritizes UpdateMode.UPDATE_CONTINUOUS, so the process treat UpdateMode.UPDATE_DISCRETE as UpdateMode.UPDATE_CONTINUOUS with InterpolationType.INTERPOLATION_NEAREST."); + } + if (was_using_angle != track_value->is_using_angle) { + WARN_PRINT_ONCE_ED("Value Track: " + String(path) + " has different interpolation types for rotation between some animations may be blended. Blending prioritizes angle interpolation, so the blending result uses the shortest path referenced to the initial (RESET animation) value."); + } + } + + track->setup_pass = setup_pass; + } + } + + List to_delete; + + for (const KeyValue &K : track_cache) { + TrackCache *tc = track_cache[K.key]; + if (tc->setup_pass != setup_pass) { + to_delete.push_back(K.key); + } + } + + while (to_delete.front()) { + NodePath np = to_delete.front()->get(); + memdelete(track_cache[np]); + track_cache.erase(np); + to_delete.pop_front(); + } + + track_map.clear(); + + int idx = 0; + for (const KeyValue &K : track_cache) { + track_map[K.key] = idx; + idx++; + } + + track_count = idx; + + cache_valid = true; + + return true; +} + +/* -------------------------------------------- */ +/* -- Blending processor ---------------------- */ +/* -------------------------------------------- */ + +void AnimationMixer::_process_animation(double p_delta, bool p_update_only) { + _blend_init(); + if (_blend_pre_process(p_delta, track_count, track_map)) { + if (!deterministic) { + _blend_calc_total_weight(); + } + _blend_process(p_delta, p_update_only); + _blend_apply(); + _blend_post_process(); + }; + clear_animation_instances(); +} + +Variant AnimationMixer::post_process_key_value(const Ref &p_anim, int p_track, Variant p_value, const Object *p_object, int p_object_idx) { + Variant res; + if (GDVIRTUAL_CALL(_post_process_key_value, p_anim, p_track, p_value, const_cast(p_object), p_object_idx, res)) { + return res; + } + return _post_process_key_value(p_anim, p_track, p_value, p_object, p_object_idx); +} + +Variant AnimationMixer::_post_process_key_value(const Ref &p_anim, int p_track, Variant p_value, const Object *p_object, int p_object_idx) { + switch (p_anim->track_get_type(p_track)) { +#ifndef _3D_DISABLED + case Animation::TYPE_POSITION_3D: { + if (p_object_idx >= 0) { + const Skeleton3D *skel = Object::cast_to(p_object); + return Vector3(p_value) * skel->get_motion_scale(); + } + return p_value; + } break; +#endif // _3D_DISABLED + default: { + } break; + } + return p_value; +} + +void AnimationMixer::_blend_init() { + // Check all tracks, see if they need modification. + root_motion_position = Vector3(0, 0, 0); + root_motion_rotation = Quaternion(0, 0, 0, 1); + root_motion_scale = Vector3(0, 0, 0); + root_motion_position_accumulator = Vector3(0, 0, 0); + root_motion_rotation_accumulator = Quaternion(0, 0, 0, 1); + root_motion_scale_accumulator = Vector3(1, 1, 1); + + if (!cache_valid) { + if (!_update_caches()) { + return; + } + } + + // Init all value/transform/blend/bezier tracks that track_cache has. + for (const KeyValue &K : track_cache) { + TrackCache *track = K.value; + + track->total_weight = 0.0; + + switch (track->type) { + case Animation::TYPE_POSITION_3D: { + TrackCacheTransform *t = static_cast(track); + if (track->root_motion) { + root_motion_cache.loc = Vector3(0, 0, 0); + root_motion_cache.rot = Quaternion(0, 0, 0, 1); + root_motion_cache.scale = Vector3(1, 1, 1); + } + t->loc = t->init_loc; + t->rot = t->init_rot; + t->scale = t->init_scale; + } break; + case Animation::TYPE_BLEND_SHAPE: { + TrackCacheBlendShape *t = static_cast(track); + t->value = t->init_value; + } break; + case Animation::TYPE_VALUE: { + TrackCacheValue *t = static_cast(track); + t->value = t->init_value; + } break; + case Animation::TYPE_BEZIER: { + TrackCacheBezier *t = static_cast(track); + t->value = t->init_value; + } break; + case Animation::TYPE_AUDIO: { + TrackCacheAudio *t = static_cast(track); + for (KeyValue &L : t->playing_streams) { + PlayingAudioTrackInfo &track_info = L.value; + track_info.volume = 0.0; + } + } break; + default: { + } break; + } + } +} + +bool AnimationMixer::_blend_pre_process(double p_delta, int p_track_count, const HashMap &p_track_map) { + return true; +} + +void AnimationMixer::_blend_post_process() { + // +} + +void AnimationMixer::_blend_calc_total_weight() { + for (const AnimationInstance &ai : animation_instances) { + Ref a = ai.animation_data.animation; + real_t weight = ai.playback_info.weight; + Vector track_weights = ai.playback_info.track_weights; + Vector processed_indices; + for (int i = 0; i < a->get_track_count(); i++) { + if (!a->track_is_enabled(i)) { + continue; + } + NodePath path = a->track_get_path(i); + if (!track_cache.has(path)) { + continue; // No path, but avoid error spamming. + } + TrackCache *track = track_cache[path]; + int blend_idx = track_map[path]; + if (processed_indices.has(blend_idx)) { + continue; // There is the case different track type with same path... Is there more faster iterating way than has()? + } + ERR_CONTINUE(blend_idx < 0 || blend_idx >= track_count); + real_t blend = blend_idx < track_weights.size() ? track_weights[blend_idx] * weight : weight; + track->total_weight += blend; + processed_indices.push_back(blend_idx); + } + } +} + +void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { + // Apply value/transform/blend/bezier blends to track caches and execute method/audio/animation tracks. +#ifdef TOOLS_ENABLED + bool can_call = is_inside_tree() && !Engine::get_singleton()->is_editor_hint(); +#endif // TOOLS_ENABLED + for (const AnimationInstance &ai : animation_instances) { + Ref a = ai.animation_data.animation; + double time = ai.playback_info.time; + double delta = ai.playback_info.delta; + bool seeked = ai.playback_info.seeked; + Animation::LoopedFlag looped_flag = ai.playback_info.looped_flag; + bool is_external_seeking = ai.playback_info.is_external_seeking; + real_t weight = ai.playback_info.weight; + Vector track_weights = ai.playback_info.track_weights; + bool backward = signbit(delta); // This flag is used by the root motion calculates or detecting the end of audio stream. +#ifndef _3D_DISABLED + bool calc_root = !seeked || is_external_seeking; +#endif // _3D_DISABLED + + for (int i = 0; i < a->get_track_count(); i++) { + if (!a->track_is_enabled(i)) { + continue; + } + NodePath path = a->track_get_path(i); + if (!track_cache.has(path)) { + continue; // No path, but avoid error spamming. + } + TrackCache *track = track_cache[path]; + ERR_CONTINUE(!track_map.has(path)); + int blend_idx = track_map[path]; + ERR_CONTINUE(blend_idx < 0 || blend_idx >= track_count); + real_t blend = blend_idx < track_weights.size() ? track_weights[blend_idx] * weight : weight; + if (!deterministic) { + // If undeterministic, do nomalization. + // It would be better to make this if statement outside the for loop, but come here since too much code... + if (Math::is_zero_approx(track->total_weight)) { + continue; + } + blend = blend / track->total_weight; + } + Animation::TrackType ttype = a->track_get_type(i); + if (ttype != Animation::TYPE_POSITION_3D && ttype != Animation::TYPE_ROTATION_3D && ttype != Animation::TYPE_SCALE_3D && track->type != ttype) { + // Broken animation, but avoid error spamming. + continue; + } + track->root_motion = root_motion_track == path; + switch (ttype) { + case Animation::TYPE_POSITION_3D: { +#ifndef _3D_DISABLED + if (Math::is_zero_approx(blend)) { + continue; // Nothing to blend. + } + TrackCacheTransform *t = static_cast(track); + if (track->root_motion && calc_root) { + double prev_time = time - delta; + if (!backward) { + if (prev_time < 0) { + switch (a->get_loop_mode()) { + case Animation::LOOP_NONE: { + prev_time = 0; + } break; + case Animation::LOOP_LINEAR: { + prev_time = Math::fposmod(prev_time, (double)a->get_length()); + } break; + case Animation::LOOP_PINGPONG: { + prev_time = Math::pingpong(prev_time, (double)a->get_length()); + } break; + default: + break; + } + } + } else { + if (prev_time > a->get_length()) { + switch (a->get_loop_mode()) { + case Animation::LOOP_NONE: { + prev_time = (double)a->get_length(); + } break; + case Animation::LOOP_LINEAR: { + prev_time = Math::fposmod(prev_time, (double)a->get_length()); + } break; + case Animation::LOOP_PINGPONG: { + prev_time = Math::pingpong(prev_time, (double)a->get_length()); + } break; + default: + break; + } + } + } + Vector3 loc[2]; + if (!backward) { + if (prev_time > time) { + Error err = a->try_position_track_interpolate(i, prev_time, &loc[0]); + if (err != OK) { + continue; + } + loc[0] = post_process_key_value(a, i, loc[0], t->object, t->bone_idx); + a->try_position_track_interpolate(i, (double)a->get_length(), &loc[1]); + loc[1] = post_process_key_value(a, i, loc[1], t->object, t->bone_idx); + root_motion_cache.loc += (loc[1] - loc[0]) * blend; + prev_time = 0; + } + } else { + if (prev_time < time) { + Error err = a->try_position_track_interpolate(i, prev_time, &loc[0]); + if (err != OK) { + continue; + } + loc[0] = post_process_key_value(a, i, loc[0], t->object, t->bone_idx); + a->try_position_track_interpolate(i, 0, &loc[1]); + loc[1] = post_process_key_value(a, i, loc[1], t->object, t->bone_idx); + root_motion_cache.loc += (loc[1] - loc[0]) * blend; + prev_time = (double)a->get_length(); + } + } + Error err = a->try_position_track_interpolate(i, prev_time, &loc[0]); + if (err != OK) { + continue; + } + loc[0] = post_process_key_value(a, i, loc[0], t->object, t->bone_idx); + a->try_position_track_interpolate(i, time, &loc[1]); + loc[1] = post_process_key_value(a, i, loc[1], t->object, t->bone_idx); + root_motion_cache.loc += (loc[1] - loc[0]) * blend; + prev_time = !backward ? 0 : (double)a->get_length(); + } + { + Vector3 loc; + Error err = a->try_position_track_interpolate(i, time, &loc); + if (err != OK) { + continue; + } + loc = post_process_key_value(a, i, loc, t->object, t->bone_idx); + t->loc += (loc - t->init_loc) * blend; + } +#endif // _3D_DISABLED + } break; + case Animation::TYPE_ROTATION_3D: { +#ifndef _3D_DISABLED + if (Math::is_zero_approx(blend)) { + continue; // Nothing to blend. + } + TrackCacheTransform *t = static_cast(track); + if (track->root_motion && calc_root) { + double prev_time = time - delta; + if (!backward) { + if (prev_time < 0) { + switch (a->get_loop_mode()) { + case Animation::LOOP_NONE: { + prev_time = 0; + } break; + case Animation::LOOP_LINEAR: { + prev_time = Math::fposmod(prev_time, (double)a->get_length()); + } break; + case Animation::LOOP_PINGPONG: { + prev_time = Math::pingpong(prev_time, (double)a->get_length()); + } break; + default: + break; + } + } + } else { + if (prev_time > a->get_length()) { + switch (a->get_loop_mode()) { + case Animation::LOOP_NONE: { + prev_time = (double)a->get_length(); + } break; + case Animation::LOOP_LINEAR: { + prev_time = Math::fposmod(prev_time, (double)a->get_length()); + } break; + case Animation::LOOP_PINGPONG: { + prev_time = Math::pingpong(prev_time, (double)a->get_length()); + } break; + default: + break; + } + } + } + Quaternion rot[2]; + if (!backward) { + if (prev_time > time) { + Error err = a->try_rotation_track_interpolate(i, prev_time, &rot[0]); + if (err != OK) { + continue; + } + rot[0] = post_process_key_value(a, i, rot[0], t->object, t->bone_idx); + a->try_rotation_track_interpolate(i, (double)a->get_length(), &rot[1]); + rot[1] = post_process_key_value(a, i, rot[1], t->object, t->bone_idx); + root_motion_cache.rot = (root_motion_cache.rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized(); + prev_time = 0; + } + } else { + if (prev_time < time) { + Error err = a->try_rotation_track_interpolate(i, prev_time, &rot[0]); + if (err != OK) { + continue; + } + rot[0] = post_process_key_value(a, i, rot[0], t->object, t->bone_idx); + a->try_rotation_track_interpolate(i, 0, &rot[1]); + root_motion_cache.rot = (root_motion_cache.rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized(); + prev_time = (double)a->get_length(); + } + } + Error err = a->try_rotation_track_interpolate(i, prev_time, &rot[0]); + if (err != OK) { + continue; + } + rot[0] = post_process_key_value(a, i, rot[0], t->object, t->bone_idx); + a->try_rotation_track_interpolate(i, time, &rot[1]); + rot[1] = post_process_key_value(a, i, rot[1], t->object, t->bone_idx); + root_motion_cache.rot = (root_motion_cache.rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized(); + prev_time = !backward ? 0 : (double)a->get_length(); + } + { + Quaternion rot; + Error err = a->try_rotation_track_interpolate(i, time, &rot); + if (err != OK) { + continue; + } + rot = post_process_key_value(a, i, rot, t->object, t->bone_idx); + t->rot = (t->rot * Quaternion().slerp(t->init_rot.inverse() * rot, blend)).normalized(); + } +#endif // _3D_DISABLED + } break; + case Animation::TYPE_SCALE_3D: { +#ifndef _3D_DISABLED + if (Math::is_zero_approx(blend)) { + continue; // Nothing to blend. + } + TrackCacheTransform *t = static_cast(track); + if (track->root_motion && calc_root) { + double prev_time = time - delta; + if (!backward) { + if (prev_time < 0) { + switch (a->get_loop_mode()) { + case Animation::LOOP_NONE: { + prev_time = 0; + } break; + case Animation::LOOP_LINEAR: { + prev_time = Math::fposmod(prev_time, (double)a->get_length()); + } break; + case Animation::LOOP_PINGPONG: { + prev_time = Math::pingpong(prev_time, (double)a->get_length()); + } break; + default: + break; + } + } + } else { + if (prev_time > a->get_length()) { + switch (a->get_loop_mode()) { + case Animation::LOOP_NONE: { + prev_time = (double)a->get_length(); + } break; + case Animation::LOOP_LINEAR: { + prev_time = Math::fposmod(prev_time, (double)a->get_length()); + } break; + case Animation::LOOP_PINGPONG: { + prev_time = Math::pingpong(prev_time, (double)a->get_length()); + } break; + default: + break; + } + } + } + Vector3 scale[2]; + if (!backward) { + if (prev_time > time) { + Error err = a->try_scale_track_interpolate(i, prev_time, &scale[0]); + if (err != OK) { + continue; + } + scale[0] = post_process_key_value(a, i, scale[0], t->object, t->bone_idx); + a->try_scale_track_interpolate(i, (double)a->get_length(), &scale[1]); + root_motion_cache.scale += (scale[1] - scale[0]) * blend; + scale[1] = post_process_key_value(a, i, scale[1], t->object, t->bone_idx); + prev_time = 0; + } + } else { + if (prev_time < time) { + Error err = a->try_scale_track_interpolate(i, prev_time, &scale[0]); + if (err != OK) { + continue; + } + scale[0] = post_process_key_value(a, i, scale[0], t->object, t->bone_idx); + a->try_scale_track_interpolate(i, 0, &scale[1]); + scale[1] = post_process_key_value(a, i, scale[1], t->object, t->bone_idx); + root_motion_cache.scale += (scale[1] - scale[0]) * blend; + prev_time = (double)a->get_length(); + } + } + Error err = a->try_scale_track_interpolate(i, prev_time, &scale[0]); + if (err != OK) { + continue; + } + scale[0] = post_process_key_value(a, i, scale[0], t->object, t->bone_idx); + a->try_scale_track_interpolate(i, time, &scale[1]); + scale[1] = post_process_key_value(a, i, scale[1], t->object, t->bone_idx); + root_motion_cache.scale += (scale[1] - scale[0]) * blend; + prev_time = !backward ? 0 : (double)a->get_length(); + } + { + Vector3 scale; + Error err = a->try_scale_track_interpolate(i, time, &scale); + if (err != OK) { + continue; + } + scale = post_process_key_value(a, i, scale, t->object, t->bone_idx); + t->scale += (scale - t->init_scale) * blend; + } +#endif // _3D_DISABLED + } break; + case Animation::TYPE_BLEND_SHAPE: { +#ifndef _3D_DISABLED + if (Math::is_zero_approx(blend)) { + continue; // Nothing to blend. + } + TrackCacheBlendShape *t = static_cast(track); + float value; + Error err = a->try_blend_shape_track_interpolate(i, time, &value); + //ERR_CONTINUE(err!=OK); //used for testing, should be removed + if (err != OK) { + continue; + } + value = post_process_key_value(a, i, value, t->object, t->shape_index); + t->value += (value - t->init_value) * blend; +#endif // _3D_DISABLED + } break; + case Animation::TYPE_VALUE: { + if (Math::is_zero_approx(blend)) { + continue; // Nothing to blend. + } + TrackCacheValue *t = static_cast(track); + if (t->is_continuous) { + Variant value = a->value_track_interpolate(i, time); + value = post_process_key_value(a, i, value, t->object); + if (value == Variant()) { + continue; + } + // Special case for angle interpolation. + if (t->is_using_angle) { + // For blending consistency, it prevents rotation of more than 180 degrees from init_value. + // This is the same as for Quaternion blends. + float rot_a = t->value; + float rot_b = value; + float rot_init = t->init_value; + rot_a = Math::fposmod(rot_a, (float)Math_TAU); + rot_b = Math::fposmod(rot_b, (float)Math_TAU); + rot_init = Math::fposmod(rot_init, (float)Math_TAU); + if (rot_init < Math_PI) { + rot_a = rot_a > rot_init + Math_PI ? rot_a - Math_TAU : rot_a; + rot_b = rot_b > rot_init + Math_PI ? rot_b - Math_TAU : rot_b; + } else { + rot_a = rot_a < rot_init - Math_PI ? rot_a + Math_TAU : rot_a; + rot_b = rot_b < rot_init - Math_PI ? rot_b + Math_TAU : rot_b; + } + t->value = Math::fposmod(rot_a + (rot_b - rot_init) * (float)blend, (float)Math_TAU); + } else { + if (t->init_value.get_type() == Variant::BOOL) { + value = Animation::subtract_variant(value.operator real_t(), t->init_value.operator real_t()); + t->value = Animation::blend_variant(t->value.operator real_t(), value.operator real_t(), blend); + } else { + value = Animation::subtract_variant(value, t->init_value); + t->value = Animation::blend_variant(t->value, value, blend); + } + } + } else { + if (seeked) { + int idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT); + if (idx < 0) { + continue; + } + Variant value = a->track_get_key_value(i, idx); + value = post_process_key_value(a, i, value, t->object); + t->object->set_indexed(t->subpath, value); + } else { + List indices; + a->track_get_key_indices_in_range(i, time, delta, &indices, looped_flag); + for (int &F : indices) { + Variant value = a->track_get_key_value(i, F); + value = post_process_key_value(a, i, value, t->object); + t->object->set_indexed(t->subpath, value); + } + } + } + } break; + case Animation::TYPE_METHOD: { +#ifdef TOOLS_ENABLED + if (!can_call) { + continue; + } +#endif // TOOLS_ENABLED + if (p_update_only || Math::is_zero_approx(blend)) { + continue; + } + TrackCacheMethod *t = static_cast(track); + if (seeked) { + int idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT); + if (idx < 0) { + continue; + } + StringName method = a->method_track_get_name(i, idx); + Vector params = a->method_track_get_params(i, idx); + _call_object(t->object, method, params, callback_mode_method == ANIMATION_CALLBACK_MODE_METHOD_DEFERRED); + } else { + List indices; + a->track_get_key_indices_in_range(i, time, delta, &indices, looped_flag); + for (int &F : indices) { + StringName method = a->method_track_get_name(i, F); + Vector params = a->method_track_get_params(i, F); + _call_object(t->object, method, params, callback_mode_method == ANIMATION_CALLBACK_MODE_METHOD_DEFERRED); + } + } + } break; + case Animation::TYPE_BEZIER: { + if (Math::is_zero_approx(blend)) { + continue; // Nothing to blend. + } + TrackCacheBezier *t = static_cast(track); + real_t bezier = a->bezier_track_interpolate(i, time); + bezier = post_process_key_value(a, i, bezier, t->object); + t->value += (bezier - t->init_value) * blend; + } break; + case Animation::TYPE_AUDIO: { + // The end of audio should be observed even if the blend value is 0, build up the information and store to the cache for that. + TrackCacheAudio *t = static_cast(track); + Node *asp = Object::cast_to(t->object); + if (!asp) { + t->playing_streams.clear(); + continue; + } + ObjectID oid = a->get_instance_id(); + if (!t->playing_streams.has(oid)) { + t->playing_streams[oid] = PlayingAudioTrackInfo(); + } + + PlayingAudioTrackInfo &track_info = t->playing_streams[oid]; + track_info.length = a->get_length(); + track_info.time = time; + track_info.volume += blend; + track_info.loop = a->get_loop_mode() != Animation::LOOP_NONE; + track_info.backward = backward; + track_info.use_blend = a->audio_track_is_use_blend(i); + HashMap &map = track_info.stream_info; + + // Main process to fire key is started from here. + if (p_update_only) { + continue; + } + // Find stream. + int idx = -1; + if (seeked) { + idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT); + // Discard previous stream when seeking. + if (map.has(idx)) { + t->audio_stream_playback->stop_stream(map[idx].index); + map.erase(idx); + } + } else { + List to_play; + a->track_get_key_indices_in_range(i, time, delta, &to_play, looped_flag); + if (to_play.size()) { + idx = to_play.back()->get(); + } + } + if (idx < 0) { + continue; + } + // Play stream. + Ref stream = a->audio_track_get_key_stream(i, idx); + if (stream.is_valid()) { + double start_ofs = a->audio_track_get_key_start_offset(i, idx); + double end_ofs = a->audio_track_get_key_end_offset(i, idx); + double len = stream->get_length(); + if (seeked) { + start_ofs += time - a->track_get_key_time(i, idx); + } + if (t->object->call(SNAME("get_stream")) != t->audio_stream) { + t->object->call(SNAME("set_stream"), t->audio_stream); + t->audio_stream_playback.unref(); + if (!playing_audio_stream_players.has(asp)) { + playing_audio_stream_players.push_back(asp); + } + } + if (!t->object->call(SNAME("is_playing"))) { + t->object->call(SNAME("play")); + } + if (!t->object->call(SNAME("has_stream_playback"))) { + t->audio_stream_playback.unref(); + continue; + } + if (t->audio_stream_playback.is_null()) { + t->audio_stream_playback = t->object->call(SNAME("get_stream_playback")); + } + PlayingAudioStreamInfo pasi; + pasi.index = t->audio_stream_playback->play_stream(stream, start_ofs); + pasi.start = time; + if (len && end_ofs > 0) { // Force an end at a time. + pasi.len = len - start_ofs - end_ofs; + } else { + pasi.len = 0; + } + map[idx] = pasi; + } + } break; + case Animation::TYPE_ANIMATION: { + if (p_update_only || Math::is_zero_approx(blend)) { + continue; + } + TrackCacheAnimation *t = static_cast(track); + AnimationPlayer *player2 = Object::cast_to(t->object); + if (!player2) { + continue; + } + if (seeked) { + // Seek. + int idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT); + if (idx < 0) { + continue; + } + double pos = a->track_get_key_time(i, idx); + StringName anim_name = a->animation_track_get_key_animation(i, idx); + if (String(anim_name) == "[stop]" || !player2->has_animation(anim_name)) { + continue; + } + Ref anim = player2->get_animation(anim_name); + double at_anim_pos = 0.0; + switch (anim->get_loop_mode()) { + case Animation::LOOP_NONE: { + at_anim_pos = MAX((double)anim->get_length(), time - pos); //seek to end + } break; + case Animation::LOOP_LINEAR: { + at_anim_pos = Math::fposmod(time - pos, (double)anim->get_length()); //seek to loop + } break; + case Animation::LOOP_PINGPONG: { + at_anim_pos = Math::pingpong(time - pos, (double)a->get_length()); + } break; + default: + break; + } + if (player2->is_playing() || seeked) { + player2->seek(at_anim_pos); + player2->play(anim_name); + t->playing = true; + playing_caches.insert(t); + } else { + player2->set_assigned_animation(anim_name); + player2->seek(at_anim_pos, true); + } + } else { + // Find stuff to play. + List to_play; + a->track_get_key_indices_in_range(i, time, delta, &to_play, looped_flag); + if (to_play.size()) { + int idx = to_play.back()->get(); + StringName anim_name = a->animation_track_get_key_animation(i, idx); + if (String(anim_name) == "[stop]" || !player2->has_animation(anim_name)) { + if (playing_caches.has(t)) { + playing_caches.erase(t); + player2->stop(); + t->playing = false; + } + } else { + player2->play(anim_name); + t->playing = true; + playing_caches.insert(t); + } + } + } + } break; + } + } + } +} + +void AnimationMixer::_blend_apply() { + // Finally, set the tracks. + for (const KeyValue &K : track_cache) { + TrackCache *track = K.value; + if (!deterministic && Math::is_zero_approx(track->total_weight)) { + continue; + } + switch (track->type) { + case Animation::TYPE_POSITION_3D: { +#ifndef _3D_DISABLED + TrackCacheTransform *t = static_cast(track); + + if (t->root_motion) { + root_motion_position = root_motion_cache.loc; + root_motion_rotation = root_motion_cache.rot; + root_motion_scale = root_motion_cache.scale - Vector3(1, 1, 1); + root_motion_position_accumulator = t->loc; + root_motion_rotation_accumulator = t->rot; + root_motion_scale_accumulator = t->scale; + } else if (t->skeleton && t->bone_idx >= 0) { + if (t->loc_used) { + t->skeleton->set_bone_pose_position(t->bone_idx, t->loc); + } + if (t->rot_used) { + t->skeleton->set_bone_pose_rotation(t->bone_idx, t->rot); + } + if (t->scale_used) { + t->skeleton->set_bone_pose_scale(t->bone_idx, t->scale); + } + + } else if (!t->skeleton) { + if (t->loc_used) { + t->node_3d->set_position(t->loc); + } + if (t->rot_used) { + t->node_3d->set_rotation(t->rot.get_euler()); + } + if (t->scale_used) { + t->node_3d->set_scale(t->scale); + } + } +#endif // _3D_DISABLED + } break; + case Animation::TYPE_BLEND_SHAPE: { +#ifndef _3D_DISABLED + TrackCacheBlendShape *t = static_cast(track); + + if (t->mesh_3d) { + t->mesh_3d->set_blend_shape_value(t->shape_index, t->value); + } +#endif // _3D_DISABLED + } break; + case Animation::TYPE_VALUE: { + TrackCacheValue *t = static_cast(track); + + if (!t->is_continuous) { + break; // Don't overwrite the value set by UPDATE_DISCRETE. + } + + if (t->init_value.get_type() == Variant::BOOL) { + t->object->set_indexed(t->subpath, t->value.operator real_t() >= 0.5); + } else { + t->object->set_indexed(t->subpath, t->value); + } + + } break; + case Animation::TYPE_BEZIER: { + TrackCacheBezier *t = static_cast(track); + + t->object->set_indexed(t->subpath, t->value); + + } break; + case Animation::TYPE_AUDIO: { + TrackCacheAudio *t = static_cast(track); + + // Audio ending process. + LocalVector erase_maps; + for (KeyValue &L : t->playing_streams) { + PlayingAudioTrackInfo &track_info = L.value; + float db = Math::linear_to_db(track_info.use_blend ? track_info.volume : 1.0); + LocalVector erase_streams; + HashMap &map = track_info.stream_info; + for (const KeyValue &M : map) { + PlayingAudioStreamInfo pasi = M.value; + + bool stop = false; + if (!t->audio_stream_playback->is_stream_playing(pasi.index)) { + stop = true; + } + if (!track_info.loop) { + if (!track_info.backward) { + if (track_info.time < pasi.start) { + stop = true; + } + } else if (track_info.backward) { + if (track_info.time > pasi.start) { + stop = true; + } + } + } + if (pasi.len > 0) { + double len = 0.0; + if (!track_info.backward) { + len = pasi.start > track_info.time ? (track_info.length - pasi.start) + track_info.time : track_info.time - pasi.start; + } else { + len = pasi.start < track_info.time ? (track_info.length - track_info.time) + pasi.start : pasi.start - track_info.time; + } + if (len > pasi.len) { + stop = true; + } + } + if (stop) { + // Time to stop. + t->audio_stream_playback->stop_stream(pasi.index); + erase_streams.push_back(M.key); + } else { + t->audio_stream_playback->set_stream_volume(pasi.index, db); + } + } + for (uint32_t erase_idx = 0; erase_idx < erase_streams.size(); erase_idx++) { + map.erase(erase_streams[erase_idx]); + } + if (map.size() == 0) { + erase_maps.push_back(L.key); + } + } + for (uint32_t erase_idx = 0; erase_idx < erase_maps.size(); erase_idx++) { + t->playing_streams.erase(erase_maps[erase_idx]); + } + } break; + default: { + } // The rest don't matter. + } + } +} + +void AnimationMixer::_call_object(Object *p_object, const StringName &p_method, const Vector &p_params, bool p_deferred) { + // Separate function to use alloca() more efficiently + const Variant **argptrs = (const Variant **)alloca(sizeof(const Variant **) * p_params.size()); + const Variant *args = p_params.ptr(); + uint32_t argcount = p_params.size(); + for (uint32_t i = 0; i < argcount; i++) { + argptrs[i] = &args[i]; + } + if (p_deferred) { + MessageQueue::get_singleton()->push_callp(p_object, p_method, argptrs, argcount); + } else { + Callable::CallError ce; + p_object->callp(p_method, argptrs, argcount, ce); + } +} + +void AnimationMixer::make_animation_instance(const StringName &p_name, const PlaybackInfo p_playback_info) { + ERR_FAIL_COND(!has_animation(p_name)); + + AnimationData ad; + ad.name = p_name; + ad.animation = get_animation(p_name); + ad.animation_library = find_animation_library(ad.animation); + + AnimationInstance ai; + ai.animation_data = ad; + ai.playback_info = p_playback_info; + + animation_instances.push_back(ai); +} + +void AnimationMixer::clear_animation_instances() { + animation_instances.clear(); +} + +void AnimationMixer::advance(double p_time) { + _process_animation(p_time); +} + +void AnimationMixer::clear_caches() { + _clear_caches(); +} + +/* -------------------------------------------- */ +/* -- Root motion ----------------------------- */ +/* -------------------------------------------- */ + +void AnimationMixer::set_root_motion_track(const NodePath &p_track) { + root_motion_track = p_track; +} + +NodePath AnimationMixer::get_root_motion_track() const { + return root_motion_track; +} + +Vector3 AnimationMixer::get_root_motion_position() const { + return root_motion_position; +} + +Quaternion AnimationMixer::get_root_motion_rotation() const { + return root_motion_rotation; +} + +Vector3 AnimationMixer::get_root_motion_scale() const { + return root_motion_scale; +} + +Vector3 AnimationMixer::get_root_motion_position_accumulator() const { + return root_motion_position_accumulator; +} + +Quaternion AnimationMixer::get_root_motion_rotation_accumulator() const { + return root_motion_rotation_accumulator; +} + +Vector3 AnimationMixer::get_root_motion_scale_accumulator() const { + return root_motion_scale_accumulator; +} + +#ifdef TOOLS_ENABLED +void AnimationMixer::set_reset_on_save_enabled(bool p_enabled) { + reset_on_save = p_enabled; +} + +bool AnimationMixer::is_reset_on_save_enabled() const { + return reset_on_save; +} + +bool AnimationMixer::can_apply_reset() const { + return has_animation(SceneStringNames::get_singleton()->RESET); +} + +void AnimationMixer::_build_backup_track_cache() { + for (const KeyValue &K : track_cache) { + TrackCache *track = K.value; + track->total_weight = 1.0; + switch (track->type) { + case Animation::TYPE_POSITION_3D: { +#ifndef _3D_DISABLED + TrackCacheTransform *t = static_cast(track); + if (t->root_motion) { + // Do nothing. + } else if (t->skeleton && t->bone_idx >= 0) { + if (t->loc_used) { + t->loc = t->skeleton->get_bone_pose_position(t->bone_idx); + } + if (t->rot_used) { + t->rot = t->skeleton->get_bone_pose_rotation(t->bone_idx); + } + if (t->scale_used) { + t->scale = t->skeleton->get_bone_pose_scale(t->bone_idx); + } + } else if (!t->skeleton) { + if (t->loc_used) { + t->loc = t->node_3d->get_position(); + } + if (t->rot_used) { + t->rot = t->node_3d->get_quaternion(); + } + if (t->scale_used) { + t->scale = t->node_3d->get_scale(); + } + } +#endif // _3D_DISABLED + } break; + case Animation::TYPE_BLEND_SHAPE: { +#ifndef _3D_DISABLED + TrackCacheBlendShape *t = static_cast(track); + if (t->mesh_3d) { + t->value = t->mesh_3d->get_blend_shape_value(t->shape_index); + } +#endif // _3D_DISABLED + } break; + case Animation::TYPE_VALUE: { + TrackCacheValue *t = static_cast(track); + t->value = t->object->get_indexed(t->subpath); + t->is_continuous = true; + } break; + case Animation::TYPE_BEZIER: { + TrackCacheBezier *t = static_cast(track); + t->value = t->object->get_indexed(t->subpath); + } break; + case Animation::TYPE_AUDIO: { + TrackCacheAudio *t = static_cast(track); + Node *asp = Object::cast_to(t->object); + if (asp) { + t->object->call(SNAME("set_stream"), Ref()); + } + track = memnew(TrackCache); // Make disable this track cache. + } break; + default: { + } // The rest don't matter. + } + } +} + +Ref AnimationMixer::make_backup() { + Ref backup; + backup.instantiate(); + + Ref reset_anim = animation_set[SceneStringNames::get_singleton()->RESET].animation; + ERR_FAIL_COND_V(reset_anim.is_null(), Ref()); + + _blend_init(); + PlaybackInfo pi; + pi.time = 0; + pi.delta = 0; + pi.seeked = true; + pi.weight = 1.0; + make_animation_instance(SceneStringNames::get_singleton()->RESET, pi); + _build_backup_track_cache(); + + backup->set_data(track_cache); + clear_animation_instances(); + + return backup; +} + +Ref AnimationMixer::apply_reset(bool p_user_initiated) { + if (!p_user_initiated && dummy) { + return Ref(); + } + ERR_FAIL_COND_V(!can_apply_reset(), Ref()); + + Ref reset_anim = animation_set[SceneStringNames::get_singleton()->RESET].animation; + ERR_FAIL_COND_V(reset_anim.is_null(), Ref()); + + Ref backup_current = make_backup(); + if (p_user_initiated) { + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + ur->create_action(TTR("Animation Apply Reset")); + ur->add_do_method(this, "_reset"); + ur->add_undo_method(this, "_restore", backup_current); + ur->commit_action(); + } else { + reset(); + } + + return backup_current; +} + +void AnimationMixer::reset() { + ERR_FAIL_COND(!can_apply_reset()); + + Ref reset_anim = animation_set[SceneStringNames::get_singleton()->RESET].animation; + ERR_FAIL_COND(reset_anim.is_null()); + + Node *root_node_object = get_node_or_null(root_node); + ERR_FAIL_NULL(root_node_object); + + AnimationPlayer *aux_player = memnew(AnimationPlayer); + EditorNode::get_singleton()->add_child(aux_player); + Ref al; + al.instantiate(); + al->add_animation(SceneStringNames::get_singleton()->RESET, reset_anim); + aux_player->set_root_node(aux_player->get_path_to(root_node_object)); + aux_player->add_animation_library("", al); + aux_player->set_assigned_animation(SceneStringNames::get_singleton()->RESET); + aux_player->seek(0.0f, true); + aux_player->queue_free(); +} + +void AnimationMixer::restore(const Ref &p_backup) { + track_cache = p_backup->get_data(); + _blend_apply(); + track_cache = HashMap(); + cache_valid = false; +} +#endif // TOOLS_ENABLED + +/* -------------------------------------------- */ +/* -- General functions ----------------------- */ +/* -------------------------------------------- */ + +void AnimationMixer::_node_removed(Node *p_node) { + _clear_caches(); +} + +void AnimationMixer::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + if (!processing) { + set_physics_process_internal(false); + set_process_internal(false); + } + _clear_caches(); + } break; + + case NOTIFICATION_INTERNAL_PROCESS: { + if (active && callback_mode_process == ANIMATION_CALLBACK_MODE_PROCESS_IDLE) { + _process_animation(get_process_delta_time()); + } + } break; + + case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { + if (active && callback_mode_process == ANIMATION_CALLBACK_MODE_PROCESS_PHYSICS) { + _process_animation(get_physics_process_delta_time()); + } + } break; + + case NOTIFICATION_EXIT_TREE: { + _clear_caches(); + } break; + } +} + +void AnimationMixer::_bind_methods() { + /* ---- Data lists ---- */ + ClassDB::bind_method(D_METHOD("add_animation_library", "name", "library"), &AnimationMixer::add_animation_library); + ClassDB::bind_method(D_METHOD("remove_animation_library", "name"), &AnimationMixer::remove_animation_library); + ClassDB::bind_method(D_METHOD("rename_animation_library", "name", "newname"), &AnimationMixer::rename_animation_library); + ClassDB::bind_method(D_METHOD("has_animation_library", "name"), &AnimationMixer::has_animation_library); + ClassDB::bind_method(D_METHOD("get_animation_library", "name"), &AnimationMixer::get_animation_library); + ClassDB::bind_method(D_METHOD("get_animation_library_list"), &AnimationMixer::_get_animation_library_list); + + ClassDB::bind_method(D_METHOD("has_animation", "name"), &AnimationMixer::has_animation); + ClassDB::bind_method(D_METHOD("get_animation", "name"), &AnimationMixer::get_animation); + ClassDB::bind_method(D_METHOD("get_animation_list"), &AnimationMixer::_get_animation_list); + + /* ---- General settings for animation ---- */ + ClassDB::bind_method(D_METHOD("set_active", "active"), &AnimationMixer::set_active); + ClassDB::bind_method(D_METHOD("is_active"), &AnimationMixer::is_active); + + ClassDB::bind_method(D_METHOD("set_deterministic", "deterministic"), &AnimationMixer::set_deterministic); + ClassDB::bind_method(D_METHOD("is_deterministic"), &AnimationMixer::is_deterministic); + + ClassDB::bind_method(D_METHOD("set_root_node", "path"), &AnimationMixer::set_root_node); + ClassDB::bind_method(D_METHOD("get_root_node"), &AnimationMixer::get_root_node); + + ClassDB::bind_method(D_METHOD("set_callback_mode_process", "mode"), &AnimationMixer::set_callback_mode_process); + ClassDB::bind_method(D_METHOD("get_callback_mode_process"), &AnimationMixer::get_callback_mode_process); + + ClassDB::bind_method(D_METHOD("set_callback_mode_method", "mode"), &AnimationMixer::set_callback_mode_method); + ClassDB::bind_method(D_METHOD("get_callback_mode_method"), &AnimationMixer::get_callback_mode_method); + + ClassDB::bind_method(D_METHOD("set_audio_max_polyphony", "max_polyphony"), &AnimationMixer::set_audio_max_polyphony); + ClassDB::bind_method(D_METHOD("get_audio_max_polyphony"), &AnimationMixer::get_audio_max_polyphony); + + /* ---- Root motion accumulator for Skeleton3D ---- */ + ClassDB::bind_method(D_METHOD("set_root_motion_track", "path"), &AnimationMixer::set_root_motion_track); + ClassDB::bind_method(D_METHOD("get_root_motion_track"), &AnimationMixer::get_root_motion_track); + + ClassDB::bind_method(D_METHOD("get_root_motion_position"), &AnimationMixer::get_root_motion_position); + ClassDB::bind_method(D_METHOD("get_root_motion_rotation"), &AnimationMixer::get_root_motion_rotation); + ClassDB::bind_method(D_METHOD("get_root_motion_scale"), &AnimationMixer::get_root_motion_scale); + ClassDB::bind_method(D_METHOD("get_root_motion_position_accumulator"), &AnimationMixer::get_root_motion_position_accumulator); + ClassDB::bind_method(D_METHOD("get_root_motion_rotation_accumulator"), &AnimationMixer::get_root_motion_rotation_accumulator); + ClassDB::bind_method(D_METHOD("get_root_motion_scale_accumulator"), &AnimationMixer::get_root_motion_scale_accumulator); + + /* ---- Blending processor ---- */ + ClassDB::bind_method(D_METHOD("clear_caches"), &AnimationMixer::clear_caches); + ClassDB::bind_method(D_METHOD("advance", "delta"), &AnimationMixer::advance); + GDVIRTUAL_BIND(_post_process_key_value, "animation", "track", "value", "object", "object_idx"); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "active"), "set_active", "is_active"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deterministic"), "set_deterministic", "is_deterministic"); + +#ifdef TOOLS_ENABLED + ClassDB::bind_method(D_METHOD("_reset"), &AnimationMixer::reset); + ClassDB::bind_method(D_METHOD("_restore", "backup"), &AnimationMixer::restore); + ClassDB::bind_method(D_METHOD("set_reset_on_save_enabled", "enabled"), &AnimationMixer::set_reset_on_save_enabled); + ClassDB::bind_method(D_METHOD("is_reset_on_save_enabled"), &AnimationMixer::is_reset_on_save_enabled); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "reset_on_save", PROPERTY_HINT_NONE, ""), "set_reset_on_save_enabled", "is_reset_on_save_enabled"); + ADD_SIGNAL(MethodInfo("mixer_updated")); // For updating dummy player. +#endif // TOOLS_ENABLED + + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_node"), "set_root_node", "get_root_node"); + + ADD_GROUP("Root Motion", "root_motion_"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_motion_track"), "set_root_motion_track", "get_root_motion_track"); + + ADD_GROUP("Audio", "audio_"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "audio_max_polyphony", PROPERTY_HINT_RANGE, "1,127,1"), "set_audio_max_polyphony", "get_audio_max_polyphony"); + + ADD_GROUP("Callback Mode", "callback_mode_"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "callback_mode_process", PROPERTY_HINT_ENUM, "Physics,Idle,Manual"), "set_callback_mode_process", "get_callback_mode_process"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "callback_mode_method", PROPERTY_HINT_ENUM, "Deferred,Immediate"), "set_callback_mode_method", "get_callback_mode_method"); + + BIND_ENUM_CONSTANT(ANIMATION_CALLBACK_MODE_PROCESS_PHYSICS); + BIND_ENUM_CONSTANT(ANIMATION_CALLBACK_MODE_PROCESS_IDLE); + BIND_ENUM_CONSTANT(ANIMATION_CALLBACK_MODE_PROCESS_MANUAL); + + BIND_ENUM_CONSTANT(ANIMATION_CALLBACK_MODE_METHOD_DEFERRED); + BIND_ENUM_CONSTANT(ANIMATION_CALLBACK_MODE_METHOD_IMMEDIATE); + + ADD_SIGNAL(MethodInfo(SNAME("animation_list_changed"))); + ADD_SIGNAL(MethodInfo(SNAME("animation_libraries_updated"))); + ADD_SIGNAL(MethodInfo(SNAME("animation_finished"), PropertyInfo(Variant::STRING_NAME, "anim_name"))); + ADD_SIGNAL(MethodInfo(SNAME("animation_started"), PropertyInfo(Variant::STRING_NAME, "anim_name"))); + ADD_SIGNAL(MethodInfo(SNAME("caches_cleared"))); +} + +AnimationMixer::AnimationMixer() { + root_node = SceneStringNames::get_singleton()->path_pp; +} + +AnimationMixer::~AnimationMixer() { +} diff --git a/scene/animation/animation_mixer.h b/scene/animation/animation_mixer.h new file mode 100644 index 00000000000..53eacbf8e82 --- /dev/null +++ b/scene/animation/animation_mixer.h @@ -0,0 +1,384 @@ +/**************************************************************************/ +/* animation_mixer.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef ANIMATION_MIXER_H +#define ANIMATION_MIXER_H + +#include "scene/3d/mesh_instance_3d.h" +#include "scene/3d/node_3d.h" +#include "scene/3d/skeleton_3d.h" +#include "scene/resources/animation.h" +#include "scene/resources/animation_library.h" +#include "scene/resources/audio_stream_polyphonic.h" + +#ifdef TOOLS_ENABLED +class AnimatedValuesBackup; +#endif // TOOLS_ENABLED + +class AnimationMixer : public Node { + GDCLASS(AnimationMixer, Node); +#ifdef TOOLS_ENABLED + friend AnimatedValuesBackup; + bool reset_on_save = true; + bool editing = false; + bool dummy = false; +#endif // TOOLS_ENABLED + +public: + enum AnimationCallbackModeProcess { + ANIMATION_CALLBACK_MODE_PROCESS_PHYSICS, + ANIMATION_CALLBACK_MODE_PROCESS_IDLE, + ANIMATION_CALLBACK_MODE_PROCESS_MANUAL, + }; + + enum AnimationCallbackModeMethod { + ANIMATION_CALLBACK_MODE_METHOD_DEFERRED, + ANIMATION_CALLBACK_MODE_METHOD_IMMEDIATE, + }; + + /* ---- Data ---- */ + struct AnimationLibraryData { + StringName name; + Ref library; + bool operator<(const AnimationLibraryData &p_data) const { return name.operator String() < p_data.name.operator String(); } + }; + + struct AnimationData { + String name; + Ref animation; + StringName animation_library; + uint64_t last_update = 0; + }; + + struct PlaybackInfo { + double time = 0.0; + double delta = 0.0; + bool seeked = false; + bool is_external_seeking = false; + Animation::LoopedFlag looped_flag = Animation::LOOPED_FLAG_NONE; + real_t weight = 0.0; + Vector track_weights; + }; + + struct AnimationInstance { + AnimationData animation_data; + PlaybackInfo playback_info; + }; + +protected: + /* ---- Data lists ---- */ + LocalVector animation_libraries; + HashMap animation_set; // HashMap + + TypedArray _get_animation_library_list() const; + Vector _get_animation_list() const { + List animations; + get_animation_list(&animations); + Vector ret; + while (animations.size()) { + ret.push_back(animations.front()->get()); + animations.pop_front(); + } + return ret; + } + + // For caches. + uint64_t animation_set_update_pass = 1; + void _animation_set_cache_update(); + + // Signals. + virtual void _animation_added(const StringName &p_name, const StringName &p_library); + virtual void _animation_removed(const StringName &p_name, const StringName &p_library); + virtual void _animation_renamed(const StringName &p_name, const StringName &p_to_name, const StringName &p_library); + virtual void _animation_changed(const StringName &p_name); + + /* ---- General settings for animation ---- */ + AnimationCallbackModeProcess callback_mode_process = ANIMATION_CALLBACK_MODE_PROCESS_IDLE; + AnimationCallbackModeMethod callback_mode_method = ANIMATION_CALLBACK_MODE_METHOD_DEFERRED; + int audio_max_polyphony = 32; + NodePath root_node; + + bool processing = false; + bool active = true; + + void _set_process(bool p_process, bool p_force = false); + + /* ---- Caches for blending ---- */ + bool cache_valid = false; + uint64_t setup_pass = 1; + uint64_t process_pass = 1; + + struct TrackCache { + bool root_motion = false; + uint64_t setup_pass = 0; + Animation::TrackType type = Animation::TrackType::TYPE_ANIMATION; + Object *object = nullptr; + ObjectID object_id; + real_t total_weight = 0.0; + }; + + struct TrackCacheTransform : public TrackCache { +#ifndef _3D_DISABLED + Node3D *node_3d = nullptr; + Skeleton3D *skeleton = nullptr; +#endif // _3D_DISABLED + int bone_idx = -1; + 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; + + TrackCacheTransform() { + type = Animation::TYPE_POSITION_3D; + } + }; + + struct RootMotionCache { + Vector3 loc = Vector3(0, 0, 0); + Quaternion rot = Quaternion(0, 0, 0, 1); + Vector3 scale = Vector3(1, 1, 1); + }; + + struct TrackCacheBlendShape : public TrackCache { + MeshInstance3D *mesh_3d = nullptr; + float init_value = 0; + float value = 0; + int shape_index = -1; + TrackCacheBlendShape() { type = Animation::TYPE_BLEND_SHAPE; } + }; + + struct TrackCacheValue : public TrackCache { + Variant init_value; + Variant value; + Vector subpath; + bool is_continuous = false; + bool is_using_angle = false; + TrackCacheValue() { type = Animation::TYPE_VALUE; } + }; + + struct TrackCacheMethod : public TrackCache { + TrackCacheMethod() { type = Animation::TYPE_METHOD; } + }; + + struct TrackCacheBezier : public TrackCache { + real_t init_value = 0.0; + real_t value = 0.0; + Vector subpath; + TrackCacheBezier() { + type = Animation::TYPE_BEZIER; + } + }; + + // Audio stream information for each audio stream placed on the track. + struct PlayingAudioStreamInfo { + AudioStreamPlaybackPolyphonic::ID index = -1; // ID retrieved from AudioStreamPlaybackPolyphonic. + double start = 0.0; + double len = 0.0; + }; + + // Audio track information for mixng and ending. + struct PlayingAudioTrackInfo { + HashMap stream_info; + double length = 0.0; + double time = 0.0; + real_t volume = 0.0; + bool loop = false; + bool backward = false; + bool use_blend = false; + }; + + struct TrackCacheAudio : public TrackCache { + Ref audio_stream; + Ref audio_stream_playback; + HashMap playing_streams; // Key is Animation resource ObjectID. + + TrackCacheAudio() { + type = Animation::TYPE_AUDIO; + } + }; + + struct TrackCacheAnimation : public TrackCache { + bool playing = false; + + TrackCacheAnimation() { + type = Animation::TYPE_ANIMATION; + } + }; + + RootMotionCache root_motion_cache; + HashMap track_cache; + HashSet playing_caches; + Vector playing_audio_stream_players; + + // Helpers. + void _clear_caches(); + void _clear_audio_streams(); + void _clear_playing_caches(); + void _init_root_motion_cache(); + bool _update_caches(); + + /* ---- Blending processor ---- */ + LocalVector animation_instances; + HashMap track_map; + int track_count = 0; + bool deterministic = false; + + /* ---- Root motion accumulator for Skeleton3D ---- */ + NodePath root_motion_track; + Vector3 root_motion_position = Vector3(0, 0, 0); + Quaternion root_motion_rotation = Quaternion(0, 0, 0, 1); + Vector3 root_motion_scale = Vector3(0, 0, 0); + Vector3 root_motion_position_accumulator = Vector3(0, 0, 0); + Quaternion root_motion_rotation_accumulator = Quaternion(0, 0, 0, 1); + Vector3 root_motion_scale_accumulator = Vector3(1, 1, 1); + + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List *p_list) const; + void _notification(int p_what); + virtual void _validate_property(PropertyInfo &p_property) const; + + static void _bind_methods(); + void _node_removed(Node *p_node); + + // Helper for extended class. + virtual void _set_active(bool p_active); + virtual void _remove_animation(const StringName &p_name); + virtual void _rename_animation(const StringName &p_from_name, const StringName &p_to_name); + + /* ---- Blending processor ---- */ + virtual void _process_animation(double p_delta, bool p_update_only = false); + virtual Variant _post_process_key_value(const Ref &p_anim, int p_track, Variant p_value, const Object *p_object, int p_object_idx = -1); + Variant post_process_key_value(const Ref &p_anim, int p_track, Variant p_value, const Object *p_object, int p_object_idx = -1); + GDVIRTUAL5RC(Variant, _post_process_key_value, Ref, int, Variant, Object *, int); + + void _blend_init(); + virtual bool _blend_pre_process(double p_delta, int p_track_count, const HashMap &p_track_map); + void _blend_calc_total_weight(); // For undeterministic blending. + void _blend_process(double p_delta, bool p_update_only = false); + void _blend_apply(); + virtual void _blend_post_process(); + void _call_object(Object *p_object, const StringName &p_method, const Vector &p_params, bool p_deferred); + +public: + /* ---- Data lists ---- */ + Dictionary *get_animation_libraries(); + + void get_animation_library_list(List *p_animations) const; + Ref get_animation_library(const StringName &p_name) const; + bool has_animation_library(const StringName &p_name) const; + StringName find_animation_library(const Ref &p_animation) const; + Error add_animation_library(const StringName &p_name, const Ref &p_animation_library); + void remove_animation_library(const StringName &p_name); + void rename_animation_library(const StringName &p_name, const StringName &p_new_name); + + void get_animation_list(List *p_animations) const; + Ref get_animation(const StringName &p_name) const; + bool has_animation(const StringName &p_name) const; + StringName find_animation(const Ref &p_animation) const; + + /* ---- General settings for animation ---- */ + void set_active(bool p_active); + bool is_active() const; + + void set_deterministic(bool p_deterministic); + bool is_deterministic() const; + + void set_root_node(const NodePath &p_path); + NodePath get_root_node() const; + + void set_callback_mode_process(AnimationCallbackModeProcess p_mode); + AnimationCallbackModeProcess get_callback_mode_process() const; + + void set_callback_mode_method(AnimationCallbackModeMethod p_mode); + AnimationCallbackModeMethod get_callback_mode_method() const; + + void set_audio_max_polyphony(int p_audio_max_polyphony); + int get_audio_max_polyphony() const; + + /* ---- Root motion accumulator for Skeleton3D ---- */ + void set_root_motion_track(const NodePath &p_track); + NodePath get_root_motion_track() const; + + Vector3 get_root_motion_position() const; + Quaternion get_root_motion_rotation() const; + Vector3 get_root_motion_scale() const; + + Vector3 get_root_motion_position_accumulator() const; + Quaternion get_root_motion_rotation_accumulator() const; + Vector3 get_root_motion_scale_accumulator() const; + + /* ---- Blending processor ---- */ + void make_animation_instance(const StringName &p_name, const PlaybackInfo p_playback_info); + void clear_animation_instances(); + virtual void advance(double p_time); + virtual void clear_caches(); ///< must be called by hand if an animation was modified after added + +#ifdef TOOLS_ENABLED + void set_editing(bool p_editing); + bool is_editing() const; + + void set_dummy(bool p_dummy); + bool is_dummy() const; + + void set_reset_on_save_enabled(bool p_enabled); + bool is_reset_on_save_enabled() const; + bool can_apply_reset() const; + void _build_backup_track_cache(); + Ref make_backup(); + Ref apply_reset(bool p_user_initiated = false); + void restore(const Ref &p_backup); + void reset(); +#endif // TOOLS_ENABLED + AnimationMixer(); + ~AnimationMixer(); +}; + +#ifdef TOOLS_ENABLED +class AnimatedValuesBackup : public RefCounted { + GDCLASS(AnimatedValuesBackup, RefCounted); + + HashMap data; + +public: + void set_data(const HashMap p_data) { data = p_data; }; + HashMap get_data() const { return data; }; +}; +#endif + +VARIANT_ENUM_CAST(AnimationMixer::AnimationCallbackModeProcess); +VARIANT_ENUM_CAST(AnimationMixer::AnimationCallbackModeMethod); + +#endif // ANIMATION_MIXER_H diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp index eeacef4ef71..3b2da915ba7 100644 --- a/scene/animation/animation_node_state_machine.cpp +++ b/scene/animation/animation_node_state_machine.cpp @@ -665,8 +665,8 @@ bool AnimationNodeStateMachinePlayback::_make_travel_path(AnimationTree *p_tree, } } -double AnimationNodeStateMachinePlayback::process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) { - double rem = _process(p_base_path, p_state_machine, p_time, p_seek, p_is_external_seeking, p_test_only); +double AnimationNodeStateMachinePlayback::process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { + double rem = _process(p_base_path, p_state_machine, p_playback_info, p_test_only); start_request = StringName(); next_request = false; stop_request = false; @@ -674,10 +674,14 @@ double AnimationNodeStateMachinePlayback::process(const String &p_base_path, Ani return rem; } -double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) { +double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { _set_base_path(p_base_path); - AnimationTree *tree = p_state_machine->state->tree; + AnimationTree *tree = p_state_machine->process_state->tree; + + double p_time = p_playback_info.time; + bool p_seek = p_playback_info.seeked; + bool p_is_external_seeking = p_playback_info.is_external_seeking; // Check seek to 0 (means reset) by parent AnimationNode. if (p_time == 0 && p_seek && !p_is_external_seeking) { @@ -767,6 +771,8 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An } } + AnimationMixer::PlaybackInfo pi = p_playback_info; + if (teleport_request) { teleport_request = false; // Clear fadeing on teleport. @@ -774,7 +780,13 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An fading_pos = 0; // Init current length. pos_current = 0; // Overwritten suddenly in main process. - len_current = p_state_machine->blend_node(current, p_state_machine->states[current].node, 0, true, false, 0, AnimationNode::FILTER_IGNORE, true, true); + + pi.time = 0; + pi.seeked = true; + pi.is_external_seeking = false; + pi.weight = 0; + + len_current = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, true); // Don't process first node if not necessary, insteads process next node. _transition_to_next_recursive(tree, p_state_machine, p_test_only); } @@ -818,12 +830,16 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An // Main process. double rem = 0.0; + pi = p_playback_info; + pi.weight = fade_blend; if (reset_request) { reset_request = false; - len_current = p_state_machine->blend_node(current, p_state_machine->states[current].node, 0, true, p_is_external_seeking, fade_blend, AnimationNode::FILTER_IGNORE, true, p_test_only); + pi.time = 0; + pi.seeked = true; + len_current = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, p_test_only); rem = len_current; } else { - rem = p_state_machine->blend_node(current, p_state_machine->states[current].node, p_time, p_seek, p_is_external_seeking, fade_blend, AnimationNode::FILTER_IGNORE, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge. + rem = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge. } // Cross-fade process. @@ -837,12 +853,14 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An } float fading_from_rem = 0.0; + pi = p_playback_info; + pi.weight = fade_blend_inv; if (_reset_request_for_fading_from) { _reset_request_for_fading_from = false; - fading_from_rem = p_state_machine->blend_node(fading_from, p_state_machine->states[fading_from].node, 0, true, p_is_external_seeking, fade_blend_inv, AnimationNode::FILTER_IGNORE, true); // Blend values must be more than CMP_EPSILON to process discrete keys in edge. - } else { - fading_from_rem = p_state_machine->blend_node(fading_from, p_state_machine->states[fading_from].node, p_time, p_seek, p_is_external_seeking, fade_blend_inv, AnimationNode::FILTER_IGNORE, true); // Blend values must be more than CMP_EPSILON to process discrete keys in edge. + pi.time = 0; + pi.seeked = true; } + fading_from_rem = p_state_machine->blend_node(p_state_machine->states[fading_from].node, fading_from, pi, AnimationNode::FILTER_IGNORE, true); // Blend values must be more than CMP_EPSILON to process discrete keys in edge. // Guess playback position. if (fading_from_rem > len_fade_from) { /// Weird but ok. @@ -900,6 +918,7 @@ bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationT bool is_state_changed = false; + AnimationMixer::PlaybackInfo pi; NextInfo next; Vector transition_path; transition_path.push_back(current); @@ -926,7 +945,11 @@ bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationT } else { if (reset_request) { // There is no possibility of processing doubly. Now we can apply reset actually in here. - p_state_machine->blend_node(current, p_state_machine->states[current].node, 0, true, false, 0, AnimationNode::FILTER_IGNORE, true, p_test_only); + pi.time = 0; + pi.seeked = true; + pi.is_external_seeking = false; + pi.weight = 0; + p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, p_test_only); } fading_from = StringName(); fading_time = 0; @@ -949,16 +972,25 @@ bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationT len_fade_from = len_current; if (next.switch_mode == AnimationNodeStateMachineTransition::SWITCH_MODE_SYNC) { - p_state_machine->blend_node(current, p_state_machine->states[current].node, MIN(pos_current, len_current), true, false, 0, AnimationNode::FILTER_IGNORE, true); + pi.time = MIN(pos_current, len_current); + pi.seeked = true; + pi.is_external_seeking = false; + pi.weight = 0; + p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true); } // Just get length to find next recursive. double rem = 0.0; + pi.time = 0; + pi.is_external_seeking = false; + pi.weight = 0; if (next.is_reset) { - len_current = p_state_machine->blend_node(current, p_state_machine->states[current].node, 0, true, false, 0, AnimationNode::FILTER_IGNORE, true, true); // Just retrieve remain length, don't process. + pi.seeked = true; + len_current = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, true); // Just retrieve remain length, don't process. rem = len_current; } else { - rem = p_state_machine->blend_node(current, p_state_machine->states[current].node, 0, false, false, 0, AnimationNode::FILTER_IGNORE, true, true); // Just retrieve remain length, don't process. + pi.seeked = false; + rem = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, true); // Just retrieve remain length, don't process. } // Guess playback position. @@ -1158,7 +1190,7 @@ Ref AnimationNodeStateMachinePlayback::_get_parent_st Vector split = base_path.split("/"); ERR_FAIL_COND_V_MSG(split.size() < 3, Ref(), "Path is too short."); split = split.slice(1, split.size() - 2); - Ref root = p_tree->get_tree_root(); + Ref root = p_tree->get_root_animation_node(); ERR_FAIL_COND_V_MSG(root.is_null(), Ref(), "There is no root AnimationNode in AnimationTree: " + String(p_tree->get_name())); String anodesm_path = String("/").join(split); Ref anodesm = !anodesm_path.size() ? root : root->find_node_by_path(anodesm_path); @@ -1590,14 +1622,14 @@ Vector2 AnimationNodeStateMachine::get_graph_offset() const { return graph_offset; } -double AnimationNodeStateMachine::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) { +double AnimationNodeStateMachine::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { Ref playback_new = get_parameter(playback); ERR_FAIL_COND_V(playback_new.is_null(), 0.0); playback_new->_set_grouped(state_machine_type == STATE_MACHINE_TYPE_GROUPED); if (p_test_only) { playback_new = playback_new->duplicate(); // Don't process original when testing. } - return playback_new->process(base_path, this, p_time, p_seek, p_is_external_seeking, p_test_only); + return playback_new->process(node_state.base_path, this, p_playback_info, p_test_only); } String AnimationNodeStateMachine::get_caption() const { diff --git a/scene/animation/animation_node_state_machine.h b/scene/animation/animation_node_state_machine.h index e45aae9e2ba..ec20c68a4b8 100644 --- a/scene/animation/animation_node_state_machine.h +++ b/scene/animation/animation_node_state_machine.h @@ -210,7 +210,7 @@ public: void set_graph_offset(const Vector2 &p_offset); Vector2 get_graph_offset() const; - virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override; + virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override; virtual String get_caption() const override; virtual Ref get_child_by_name(const StringName &p_name) const override; @@ -297,8 +297,8 @@ class AnimationNodeStateMachinePlayback : public Resource { bool _travel_children(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, const String &p_path, bool p_is_allow_transition_to_self, bool p_is_parent_same_state, bool p_test_only); void _start_children(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, const String &p_path, bool p_test_only); - double process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only); - double _process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only); + double process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only); + double _process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only); bool _check_advance_condition(const Ref p_state_machine, const Ref p_transition) const; bool _transition_to_next_recursive(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_test_only); diff --git a/scene/animation/animation_player.compat.inc b/scene/animation/animation_player.compat.inc new file mode 100644 index 00000000000..974eb2a7d82 --- /dev/null +++ b/scene/animation/animation_player.compat.inc @@ -0,0 +1,76 @@ +/**************************************************************************/ +/* animation_player.compat.inc */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef DISABLE_DEPRECATED + +void AnimationPlayer::_set_process_callback_bind_compat_80813(AnimationPlayer::AnimationProcessCallback p_mode) { + set_callback_mode_process(static_cast(static_cast(p_mode))); +} + +AnimationPlayer::AnimationProcessCallback AnimationPlayer::_get_process_callback_bind_compat_80813() const { + return static_cast(static_cast(get_callback_mode_process())); +} + +void AnimationPlayer::_set_method_call_mode_bind_compat_80813(AnimationPlayer::AnimationMethodCallMode p_mode) { + set_callback_mode_method(static_cast(static_cast(p_mode))); +} + +AnimationPlayer::AnimationMethodCallMode AnimationPlayer::_get_method_call_mode_bind_compat_80813() const { + return static_cast(static_cast(get_callback_mode_method())); +} + +void AnimationPlayer::_set_root_bind_compat_80813(const NodePath &p_root) { + set_root_node(p_root); +} + +NodePath AnimationPlayer::_get_root_bind_compat_80813() const { + return get_root_node(); +} + +void AnimationPlayer::_seek_bind_compat_80813(double p_time, bool p_update) { + seek(p_time, p_update, false); +} + +void AnimationPlayer::_bind_compatibility_methods() { + ClassDB::bind_method(D_METHOD("set_process_callback", "mode"), &AnimationPlayer::_set_process_callback_bind_compat_80813); + ClassDB::bind_method(D_METHOD("get_process_callback"), &AnimationPlayer::_get_process_callback_bind_compat_80813); + ClassDB::bind_method(D_METHOD("set_method_call_mode", "mode"), &AnimationPlayer::_set_method_call_mode_bind_compat_80813); + ClassDB::bind_method(D_METHOD("get_method_call_mode"), &AnimationPlayer::_get_method_call_mode_bind_compat_80813); + ClassDB::bind_method(D_METHOD("set_root", "path"), &AnimationPlayer::_set_root_bind_compat_80813); + ClassDB::bind_method(D_METHOD("get_root"), &AnimationPlayer::_get_root_bind_compat_80813); + ClassDB::bind_compatibility_method(D_METHOD("seek", "seconds", "update"), &AnimationPlayer::_seek_bind_compat_80813, DEFVAL(false)); + BIND_ENUM_CONSTANT(ANIMATION_PROCESS_PHYSICS); + BIND_ENUM_CONSTANT(ANIMATION_PROCESS_IDLE); + BIND_ENUM_CONSTANT(ANIMATION_PROCESS_MANUAL); + BIND_ENUM_CONSTANT(ANIMATION_METHOD_CALL_DEFERRED); + BIND_ENUM_CONSTANT(ANIMATION_METHOD_CALL_IMMEDIATE); +} + +#endif // DISABLE_DEPRECATED diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp index 9fa3f6871d4..8e08653abd1 100644 --- a/scene/animation/animation_player.cpp +++ b/scene/animation/animation_player.cpp @@ -29,91 +29,18 @@ /**************************************************************************/ #include "animation_player.h" +#include "animation_player.compat.inc" #include "core/config/engine.h" -#include "core/object/message_queue.h" #include "scene/scene_string_names.h" -#include "servers/audio/audio_stream.h" - -#ifdef TOOLS_ENABLED -#include "editor/editor_node.h" -#include "editor/editor_undo_redo_manager.h" -#include "scene/2d/skeleton_2d.h" - -void AnimatedValuesBackup::update_skeletons() { - for (int i = 0; i < entries.size(); i++) { - if (entries[i].bone_idx != -1) { - // 3D bone - Object::cast_to(entries[i].object)->notification(Skeleton3D::NOTIFICATION_UPDATE_SKELETON); - } else { - Bone2D *bone = Object::cast_to(entries[i].object); - if (bone && bone->skeleton) { - // 2D bone - bone->skeleton->_update_transform(); - } - } - } -} - -void AnimatedValuesBackup::restore() const { - for (int i = 0; i < entries.size(); i++) { - const AnimatedValuesBackup::Entry *entry = &entries[i]; - if (entry->bone_idx == -1) { - entry->object->set_indexed(entry->subpath, entry->value); - } else { - Array arr = entry->value; - if (arr.size() == 3) { - Object::cast_to(entry->object)->set_bone_pose_position(entry->bone_idx, arr[0]); - Object::cast_to(entry->object)->set_bone_pose_rotation(entry->bone_idx, arr[1]); - Object::cast_to(entry->object)->set_bone_pose_scale(entry->bone_idx, arr[2]); - } - } - } -} - -void AnimatedValuesBackup::_bind_methods() { - ClassDB::bind_method(D_METHOD("restore"), &AnimatedValuesBackup::restore); -} -#endif bool AnimationPlayer::_set(const StringName &p_name, const Variant &p_value) { String name = p_name; - - if (name.begins_with("playback/play")) { // bw compatibility - + if (name.begins_with("playback/play")) { // For backward compatibility. set_current_animation(p_value); - - } else if (name.begins_with("anims/")) { - // Backwards compatibility with 3.x, add them to "default" library. - String which = name.get_slicec('/', 1); - - Ref anim = p_value; - Ref al; - if (!has_animation_library(StringName())) { - al.instantiate(); - add_animation_library(StringName(), al); - } else { - al = get_animation_library(StringName()); - } - al->add_animation(which, anim); - - } else if (name.begins_with("libraries")) { - Dictionary d = p_value; - while (animation_libraries.size()) { - remove_animation_library(animation_libraries[0].name); - } - List keys; - d.get_key_list(&keys); - for (const Variant &K : keys) { - StringName lib_name = K; - Ref lib = d[lib_name]; - add_animation_library(lib_name, lib); - } - emit_signal("animation_libraries_updated"); } else if (name.begins_with("next/")) { String which = name.get_slicec('/', 1); animation_set_next(which, p_value); - } else if (p_name == SceneStringNames::get_singleton()->blend_times) { Array array = p_value; int len = array.size(); @@ -123,35 +50,31 @@ bool AnimationPlayer::_set(const StringName &p_name, const Variant &p_value) { StringName from = array[i * 3 + 0]; StringName to = array[i * 3 + 1]; float time = array[i * 3 + 2]; - set_blend_time(from, to, time); } - +#ifndef DISABLE_DEPRECATED + } else if (p_name == "method_call_mode") { + set_callback_mode_method(static_cast((int)p_value)); + } else if (p_name == "playback_process_mode") { + set_callback_mode_process(static_cast((int)p_value)); + } else if (p_name == "playback_active") { + set_active(p_value); +#endif // DISABLE_DEPRECATED } else { return false; } - return true; } bool AnimationPlayer::_get(const StringName &p_name, Variant &r_ret) const { String name = p_name; - if (name == "playback/play") { // bw compatibility + if (name == "playback/play") { // For backward compatibility. r_ret = get_current_animation(); - } else if (name.begins_with("libraries")) { - Dictionary d; - for (const AnimationLibraryData &lib : animation_libraries) { - d[lib.name] = lib.library; - } - - r_ret = d; - } else if (name.begins_with("next/")) { String which = name.get_slicec('/', 1); - r_ret = animation_get_next(which); } else if (name == "blend_times") { @@ -168,6 +91,14 @@ bool AnimationPlayer::_get(const StringName &p_name, Variant &r_ret) const { } r_ret = array; +#ifndef DISABLE_DEPRECATED + } else if (name == "method_call_mode") { + r_ret = get_callback_mode_method(); + } else if (name == "playback_process_mode") { + r_ret = get_callback_mode_process(); + } else if (name == "playback_active") { + r_ret = is_active(); +#endif // DISABLE_DEPRECATED } else { return false; } @@ -176,13 +107,14 @@ bool AnimationPlayer::_get(const StringName &p_name, Variant &r_ret) const { } void AnimationPlayer::_validate_property(PropertyInfo &p_property) const { + AnimationMixer::_validate_property(p_property); + if (p_property.name == "current_animation") { List names; for (const KeyValue &E : animation_set) { names.push_back(E.key); } - names.sort(); names.push_front("[stop]"); String hint; for (List::Element *E = names.front(); E; E = E->next()) { @@ -199,16 +131,13 @@ void AnimationPlayer::_validate_property(PropertyInfo &p_property) const { void AnimationPlayer::_get_property_list(List *p_list) const { List anim_names; - anim_names.push_back(PropertyInfo(Variant::DICTIONARY, PNAME("libraries"))); - for (const KeyValue &E : animation_set) { - if (E.value.next != StringName()) { + HashMap::ConstIterator F = animation_next_set.find(E.key); + if (F && F->value != StringName()) { anim_names.push_back(PropertyInfo(Variant::STRING, "next/" + String(E.key), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); } } - anim_names.sort(); - for (const PropertyInfo &E : anim_names) { p_list->push_back(E); } @@ -216,799 +145,22 @@ void AnimationPlayer::_get_property_list(List *p_list) const { p_list->push_back(PropertyInfo(Variant::ARRAY, "blend_times", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); } -void AnimationPlayer::advance(double p_time) { - _animation_process(p_time); -} - void AnimationPlayer::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - if (!processing) { - //make sure that a previous process state was not saved - //only process if "processing" is set - set_physics_process_internal(false); - set_process_internal(false); - } - clear_caches(); - } break; - case NOTIFICATION_READY: { if (!Engine::get_singleton()->is_editor_hint() && animation_set.has(autoplay)) { + set_active(true); play(autoplay); - _animation_process(0); } } break; - - case NOTIFICATION_INTERNAL_PROCESS: { - if (process_callback == ANIMATION_PROCESS_PHYSICS) { - break; - } - - if (processing) { - _animation_process(get_process_delta_time()); - } - } break; - - case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { - if (process_callback == ANIMATION_PROCESS_IDLE) { - break; - } - - if (processing) { - _animation_process(get_physics_process_delta_time()); - } - } break; - - case NOTIFICATION_EXIT_TREE: { - clear_caches(); - } break; } } -void AnimationPlayer::_ensure_node_caches(AnimationData *p_anim, Node *p_root_override) { - // Already cached? - if (p_anim->node_cache.size() == p_anim->animation->get_track_count()) { - return; - } - - Node *parent = p_root_override ? p_root_override : get_node_or_null(root); - - ERR_FAIL_NULL(parent); - - Animation *a = p_anim->animation.operator->(); - - p_anim->node_cache.resize(a->get_track_count()); - - setup_pass++; - - for (int i = 0; i < a->get_track_count(); i++) { - p_anim->node_cache.write[i] = nullptr; - - if (!a->track_is_enabled(i)) { - continue; - } - - Ref resource; - Vector leftover_path; - Node *child = parent->get_node_and_resource(a->track_get_path(i), resource, leftover_path); - ERR_CONTINUE_MSG(!child, "On Animation: '" + p_anim->name + "', couldn't resolve track: '" + String(a->track_get_path(i)) + "'."); // couldn't find the child node - ObjectID id = resource.is_valid() ? resource->get_instance_id() : child->get_instance_id(); - int bone_idx = -1; - int blend_shape_idx = -1; - -#ifndef _3D_DISABLED - if (a->track_get_path(i).get_subname_count() == 1 && Object::cast_to(child)) { - Skeleton3D *sk = Object::cast_to(child); - bone_idx = sk->find_bone(a->track_get_path(i).get_subname(0)); - if (bone_idx == -1) { - continue; - } - } - - if (a->track_get_type(i) == Animation::TYPE_BLEND_SHAPE) { - MeshInstance3D *mi_3d = Object::cast_to(child); - if (!mi_3d) { - continue; - } - if (a->track_get_path(i).get_subname_count() != 1) { - continue; - } - - blend_shape_idx = mi_3d->find_blend_shape_by_name(a->track_get_path(i).get_subname(0)); - if (blend_shape_idx == -1) { - continue; - } - } - -#endif // _3D_DISABLED - - if (!child->is_connected("tree_exiting", callable_mp(this, &AnimationPlayer::_node_removed))) { - child->connect("tree_exiting", callable_mp(this, &AnimationPlayer::_node_removed).bind(child), CONNECT_ONE_SHOT); - } - - TrackNodeCacheKey key; - key.id = id; - key.bone_idx = bone_idx; - key.blend_shape_idx = blend_shape_idx; - - if (!node_cache_map.has(key)) { - node_cache_map[key] = TrackNodeCache(); - } - - TrackNodeCache *node_cache = &node_cache_map[key]; - p_anim->node_cache.write[i] = node_cache; - - node_cache->path = a->track_get_path(i); - node_cache->node = child; - node_cache->resource = resource; - node_cache->node_2d = Object::cast_to(child); -#ifndef _3D_DISABLED - if (a->track_get_type(i) == Animation::TYPE_POSITION_3D || a->track_get_type(i) == Animation::TYPE_ROTATION_3D || a->track_get_type(i) == Animation::TYPE_SCALE_3D) { - // special cases and caches for transform tracks - - if (node_cache->last_setup_pass != setup_pass) { - node_cache->loc_used = false; - node_cache->rot_used = false; - node_cache->scale_used = false; - } - - // cache node_3d - node_cache->node_3d = Object::cast_to(child); - // cache skeleton - node_cache->skeleton = Object::cast_to(child); - if (node_cache->skeleton) { - if (a->track_get_path(i).get_subname_count() == 1) { - StringName bone_name = a->track_get_path(i).get_subname(0); - - node_cache->bone_idx = node_cache->skeleton->find_bone(bone_name); - if (node_cache->bone_idx < 0) { - // broken track (nonexistent bone) - node_cache->skeleton = nullptr; - node_cache->node_3d = nullptr; - ERR_CONTINUE(node_cache->bone_idx < 0); - } - Transform3D rest = node_cache->skeleton->get_bone_rest(bone_idx); - node_cache->init_loc = rest.origin; - node_cache->init_rot = rest.basis.get_rotation_quaternion(); - node_cache->init_scale = rest.basis.get_scale(); - } else { - // Not a skeleton, the node can be accessed with the node_3d member. - node_cache->skeleton = nullptr; - } - } - - switch (a->track_get_type(i)) { - case Animation::TYPE_POSITION_3D: { - node_cache->loc_used = true; - } break; - case Animation::TYPE_ROTATION_3D: { - node_cache->rot_used = true; - } break; - case Animation::TYPE_SCALE_3D: { - node_cache->scale_used = true; - } break; - default: { - } - } - } - - if (a->track_get_type(i) == Animation::TYPE_BLEND_SHAPE) { - // special cases and caches for transform tracks - node_cache->node_blend_shape = Object::cast_to(child); - node_cache->blend_shape_idx = blend_shape_idx; - } - -#endif // _3D_DISABLED - - if (a->track_get_type(i) == Animation::TYPE_VALUE) { - if (!node_cache->property_anim.has(a->track_get_path(i).get_concatenated_subnames())) { - TrackNodeCache::PropertyAnim pa; - pa.subpath = leftover_path; - pa.object = resource.is_valid() ? (Object *)resource.ptr() : (Object *)child; - pa.special = SP_NONE; - pa.owner = p_anim->node_cache[i]; - if (false && node_cache->node_2d) { - if (leftover_path.size() == 1 && leftover_path[0] == SceneStringNames::get_singleton()->transform_pos) { - pa.special = SP_NODE2D_POS; - } else if (leftover_path.size() == 1 && leftover_path[0] == SceneStringNames::get_singleton()->transform_rot) { - pa.special = SP_NODE2D_ROT; - } else if (leftover_path.size() == 1 && leftover_path[0] == SceneStringNames::get_singleton()->transform_scale) { - pa.special = SP_NODE2D_SCALE; - } - } - node_cache->property_anim[a->track_get_path(i).get_concatenated_subnames()] = pa; - } - } - - if (a->track_get_type(i) == Animation::TYPE_BEZIER && leftover_path.size()) { - if (!node_cache->bezier_anim.has(a->track_get_path(i).get_concatenated_subnames())) { - TrackNodeCache::BezierAnim ba; - ba.bezier_property = leftover_path; - ba.object = resource.is_valid() ? (Object *)resource.ptr() : (Object *)child; - ba.owner = p_anim->node_cache[i]; - - node_cache->bezier_anim[a->track_get_path(i).get_concatenated_subnames()] = ba; - } - } - - if (a->track_get_type(i) == Animation::TYPE_AUDIO) { - if (!node_cache->audio_anim.has(a->track_get_path(i).get_concatenated_names())) { - TrackNodeCache::AudioAnim aa; - aa.object = (Object *)child; - aa.audio_stream.instantiate(); - aa.audio_stream->set_polyphony(audio_max_polyphony); - - node_cache->audio_anim[a->track_get_path(i).get_concatenated_names()] = aa; - } - } - - node_cache->last_setup_pass = setup_pass; - } -} - -static void _call_object(Object *p_object, const StringName &p_method, const Vector &p_params, bool p_deferred) { - // Separate function to use alloca() more efficiently - const Variant **argptrs = (const Variant **)alloca(sizeof(const Variant **) * p_params.size()); - const Variant *args = p_params.ptr(); - uint32_t argcount = p_params.size(); - for (uint32_t i = 0; i < argcount; i++) { - argptrs[i] = &args[i]; - } - if (p_deferred) { - MessageQueue::get_singleton()->push_callp(p_object, p_method, argptrs, argcount); - } else { - Callable::CallError ce; - p_object->callp(p_method, argptrs, argcount, ce); - } -} - -Variant AnimationPlayer::post_process_key_value(const Ref &p_anim, int p_track, Variant p_value, const Object *p_object, int p_object_idx) { - Variant res; - if (GDVIRTUAL_CALL(_post_process_key_value, p_anim, p_track, p_value, const_cast(p_object), p_object_idx, res)) { - return res; - } - - return _post_process_key_value(p_anim, p_track, p_value, p_object, p_object_idx); -} - -Variant AnimationPlayer::_post_process_key_value(const Ref &p_anim, int p_track, Variant p_value, const Object *p_object, int p_object_idx) { - switch (p_anim->track_get_type(p_track)) { -#ifndef _3D_DISABLED - case Animation::TYPE_POSITION_3D: { - if (p_object_idx >= 0) { - const Skeleton3D *skel = Object::cast_to(p_object); - return Vector3(p_value) * skel->get_motion_scale(); - } - return p_value; - } break; -#endif // _3D_DISABLED - default: { - } break; - } - return p_value; -} - -void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double p_prev_time, double p_time, double p_delta, float p_interp, bool p_is_current, bool p_seeked, bool p_started, Animation::LoopedFlag p_looped_flag) { - _ensure_node_caches(p_anim); - ERR_FAIL_COND(p_anim->node_cache.size() != p_anim->animation->get_track_count()); - - Animation *a = p_anim->animation.operator->(); -#ifdef TOOLS_ENABLED - bool can_call = is_inside_tree() && !Engine::get_singleton()->is_editor_hint(); -#endif // TOOLS_ENABLED - bool backward = signbit(p_delta); - - for (int i = 0; i < a->get_track_count(); i++) { - // If an animation changes this animation (or it animates itself) - // we need to recreate our animation cache - if (p_anim->node_cache.size() != a->get_track_count()) { - _ensure_node_caches(p_anim); - } - - TrackNodeCache *nc = p_anim->node_cache[i]; - - if (!nc) { - continue; // no node cache for this track, skip it - } - - if (!a->track_is_enabled(i)) { - continue; // do nothing if the track is disabled - } - - if (a->track_get_key_count(i) == 0) { - continue; // do nothing if track is empty - } - - switch (a->track_get_type(i)) { - case Animation::TYPE_POSITION_3D: { -#ifndef _3D_DISABLED - if (!nc->node_3d) { - continue; - } - - Vector3 loc; - - Error err = a->try_position_track_interpolate(i, p_time, &loc); - //ERR_CONTINUE(err!=OK); //used for testing, should be removed - - if (err != OK) { - continue; - } - loc = post_process_key_value(a, i, loc, nc->node_3d, nc->bone_idx); - - if (nc->accum_pass != accum_pass) { - ERR_CONTINUE(cache_update_size >= NODE_CACHE_UPDATE_MAX); - cache_update[cache_update_size++] = nc; - nc->accum_pass = accum_pass; - nc->loc_accum = loc; - nc->rot_accum = nc->init_rot; - nc->scale_accum = nc->init_scale; - } else { - nc->loc_accum = nc->loc_accum.lerp(loc, p_interp); - } -#endif // _3D_DISABLED - } break; - case Animation::TYPE_ROTATION_3D: { -#ifndef _3D_DISABLED - if (!nc->node_3d) { - continue; - } - - Quaternion rot; - - Error err = a->try_rotation_track_interpolate(i, p_time, &rot); - //ERR_CONTINUE(err!=OK); //used for testing, should be removed - - if (err != OK) { - continue; - } - rot = post_process_key_value(a, i, rot, nc->node_3d, nc->bone_idx); - - if (nc->accum_pass != accum_pass) { - ERR_CONTINUE(cache_update_size >= NODE_CACHE_UPDATE_MAX); - cache_update[cache_update_size++] = nc; - nc->accum_pass = accum_pass; - nc->loc_accum = nc->init_loc; - nc->rot_accum = rot; - nc->scale_accum = nc->init_scale; - } else { - nc->rot_accum = nc->rot_accum.slerp(rot, p_interp); - } -#endif // _3D_DISABLED - } break; - case Animation::TYPE_SCALE_3D: { -#ifndef _3D_DISABLED - if (!nc->node_3d) { - continue; - } - - Vector3 scale; - - Error err = a->try_scale_track_interpolate(i, p_time, &scale); - //ERR_CONTINUE(err!=OK); //used for testing, should be removed - - if (err != OK) { - continue; - } - scale = post_process_key_value(a, i, scale, nc->node_3d, nc->bone_idx); - - if (nc->accum_pass != accum_pass) { - ERR_CONTINUE(cache_update_size >= NODE_CACHE_UPDATE_MAX); - cache_update[cache_update_size++] = nc; - nc->accum_pass = accum_pass; - nc->loc_accum = nc->init_loc; - nc->rot_accum = nc->init_rot; - nc->scale_accum = scale; - } else { - nc->scale_accum = nc->scale_accum.lerp(scale, p_interp); - } -#endif // _3D_DISABLED - } break; - case Animation::TYPE_BLEND_SHAPE: { -#ifndef _3D_DISABLED - if (!nc->node_blend_shape) { - continue; - } - - float blend; - - Error err = a->try_blend_shape_track_interpolate(i, p_time, &blend); - //ERR_CONTINUE(err!=OK); //used for testing, should be removed - - if (err != OK) { - continue; - } - blend = post_process_key_value(a, i, blend, nc->node_blend_shape, nc->blend_shape_idx); - - if (nc->accum_pass != accum_pass) { - ERR_CONTINUE(cache_update_size >= NODE_CACHE_UPDATE_MAX); - nc->accum_pass = accum_pass; - cache_update[cache_update_size++] = nc; - nc->blend_shape_accum = blend; - } else { - nc->blend_shape_accum = Math::lerp(nc->blend_shape_accum, blend, p_interp); - } -#endif // _3D_DISABLED - } break; - case Animation::TYPE_VALUE: { - if (!nc->node) { - continue; - } - - //StringName property=a->track_get_path(i).get_property(); - - HashMap::Iterator E = nc->property_anim.find(a->track_get_path(i).get_concatenated_subnames()); - ERR_CONTINUE(!E); //should it continue, or create a new one? - - TrackNodeCache::PropertyAnim *pa = &E->value; - - Animation::UpdateMode update_mode = a->value_track_get_update_mode(i); - - if (update_mode == Animation::UPDATE_CAPTURE) { - if (p_started || pa->capture == Variant()) { - pa->capture = pa->object->get_indexed(pa->subpath); - } - - int key_count = a->track_get_key_count(i); - if (key_count == 0) { - continue; //eeh not worth it - } - - double first_key_time = a->track_get_key_time(i, 0); - double transition = 1.0; - int first_key = 0; - - if (first_key_time == 0.0) { - //ignore, use for transition - if (key_count == 1) { - continue; //with one key we can't do anything - } - transition = (double)a->track_get_key_transition(i, 0); - first_key_time = a->track_get_key_time(i, 1); - first_key = 1; - } - - if (p_time < first_key_time) { - double c = Math::ease(p_time / first_key_time, transition); - Variant first_value = a->track_get_key_value(i, first_key); - first_value = post_process_key_value(a, i, first_value, nc->node); - Variant interp_value = Animation::interpolate_variant(pa->capture, first_value, c); - if (pa->accum_pass != accum_pass) { - ERR_CONTINUE(cache_update_prop_size >= NODE_CACHE_UPDATE_MAX); - cache_update_prop[cache_update_prop_size++] = pa; - pa->value_accum = interp_value; - pa->accum_pass = accum_pass; - } else { - pa->value_accum = Animation::interpolate_variant(pa->value_accum, interp_value, p_interp); - } - - continue; //handled - } - } - - if (update_mode == Animation::UPDATE_CONTINUOUS || update_mode == Animation::UPDATE_CAPTURE) { - Variant value = a->value_track_interpolate(i, p_time); - - if (value == Variant()) { - continue; - } - value = post_process_key_value(a, i, value, nc->node); - - if (pa->accum_pass != accum_pass) { - ERR_CONTINUE(cache_update_prop_size >= NODE_CACHE_UPDATE_MAX); - cache_update_prop[cache_update_prop_size++] = pa; - pa->value_accum = value; - pa->accum_pass = accum_pass; - } else { - pa->value_accum = Animation::interpolate_variant(pa->value_accum, value, p_interp); - } - - } else { - List indices; - - if (p_seeked) { - int found_key = a->track_find_key(i, p_time); - if (found_key >= 0) { - indices.push_back(found_key); - } - } else { - if (p_started) { - int first_key = a->track_find_key(i, p_prev_time, Animation::FIND_MODE_EXACT); - if (first_key >= 0) { - indices.push_back(first_key); - } - } - a->track_get_key_indices_in_range(i, p_time, p_delta, &indices, p_looped_flag); - } - - for (int &F : indices) { - Variant value = a->track_get_key_value(i, F); - value = post_process_key_value(a, i, value, nc->node); - switch (pa->special) { - case SP_NONE: { - bool valid; - pa->object->set_indexed(pa->subpath, value, &valid); //you are not speshul -#ifdef DEBUG_ENABLED - if (!valid) { - ERR_PRINT("Failed setting track value '" + String(pa->owner->path) + "'. Check if the property exists or the type of key is valid. Animation '" + a->get_name() + "' at node '" + get_path() + "'."); - } -#endif - - } break; - case SP_NODE2D_POS: { -#ifdef DEBUG_ENABLED - if (value.get_type() != Variant::VECTOR2) { - ERR_PRINT("Position key at time " + rtos(p_time) + " in Animation Track '" + String(pa->owner->path) + "' not of type Vector2(). Animation '" + a->get_name() + "' at node '" + get_path() + "'."); - } -#endif - static_cast(pa->object)->set_position(value); - } break; - case SP_NODE2D_ROT: { -#ifdef DEBUG_ENABLED - if (value.is_num()) { - ERR_PRINT("Rotation key at time " + rtos(p_time) + " in Animation Track '" + String(pa->owner->path) + "' not numerical. Animation '" + a->get_name() + "' at node '" + get_path() + "'."); - } -#endif - - static_cast(pa->object)->set_rotation((double)value); - } break; - case SP_NODE2D_SCALE: { -#ifdef DEBUG_ENABLED - if (value.get_type() != Variant::VECTOR2) { - ERR_PRINT("Scale key at time " + rtos(p_time) + " in Animation Track '" + String(pa->owner->path) + "' not of type Vector2()." + a->get_name() + "' at node '" + get_path() + "'."); - } -#endif - - static_cast(pa->object)->set_scale(value); - } break; - } - } - } - - } break; - case Animation::TYPE_METHOD: { -#ifdef TOOLS_ENABLED - if (!can_call) { - continue; - } -#endif // TOOLS_ENABLED - if (!p_is_current || !nc->node || is_stopping) { - continue; - } - - List indices; - - if (p_seeked) { - int found_key = a->track_find_key(i, p_time, Animation::FIND_MODE_EXACT); - if (found_key >= 0) { - indices.push_back(found_key); - } - } else { - if (p_started) { - int first_key = a->track_find_key(i, p_prev_time, Animation::FIND_MODE_EXACT); - if (first_key >= 0) { - indices.push_back(first_key); - } - } - a->track_get_key_indices_in_range(i, p_time, p_delta, &indices, p_looped_flag); - } - - for (int &E : indices) { - StringName method = a->method_track_get_name(i, E); - Vector params = a->method_track_get_params(i, E); -#ifdef DEBUG_ENABLED - if (!nc->node->has_method(method)) { - ERR_PRINT("Invalid method call '" + method + "'. '" + a->get_name() + "' at node '" + get_path() + "'."); - } -#endif - _call_object(nc->node, method, params, method_call_mode == ANIMATION_METHOD_CALL_DEFERRED); - } - - } break; - case Animation::TYPE_BEZIER: { - if (!nc->node) { - continue; - } - - HashMap::Iterator E = nc->bezier_anim.find(a->track_get_path(i).get_concatenated_subnames()); - ERR_CONTINUE(!E); //should it continue, or create a new one? - - TrackNodeCache::BezierAnim *ba = &E->value; - - real_t bezier = a->bezier_track_interpolate(i, p_time); - bezier = post_process_key_value(a, i, bezier, nc->node); - if (ba->accum_pass != accum_pass) { - ERR_CONTINUE(cache_update_bezier_size >= NODE_CACHE_UPDATE_MAX); - cache_update_bezier[cache_update_bezier_size++] = ba; - ba->bezier_accum = bezier; - ba->accum_pass = accum_pass; - } else { - ba->bezier_accum = Math::lerp(ba->bezier_accum, (float)bezier, p_interp); - } - - } break; - case Animation::TYPE_AUDIO: { - if (!nc->node || is_stopping) { - continue; - } -#ifdef TOOLS_ENABLED - if (p_seeked && !can_call) { - continue; // To avoid spamming the preview in editor. - } -#endif // TOOLS_ENABLED - HashMap::Iterator E = nc->audio_anim.find(a->track_get_path(i).get_concatenated_names()); - ERR_CONTINUE(!E); //should it continue, or create a new one? - - TrackNodeCache::AudioAnim *aa = &E->value; - Node *asp = Object::cast_to(aa->object); - if (!asp) { - continue; - } - aa->length = a->get_length(); - aa->time = p_time; - aa->loop = a->get_loop_mode() != Animation::LOOP_NONE; - aa->backward = backward; - if (aa->accum_pass != accum_pass) { - ERR_CONTINUE(cache_update_audio_size >= NODE_CACHE_UPDATE_MAX); - cache_update_audio[cache_update_audio_size++] = aa; - aa->accum_pass = accum_pass; - } - - HashMap &map = aa->playing_streams; - // Find stream. - int idx = -1; - if (p_seeked || p_started) { - idx = a->track_find_key(i, p_time); - // Discard previous stream when seeking. - if (map.has(idx)) { - aa->audio_stream_playback->stop_stream(map[idx].index); - map.erase(idx); - } - } else { - List to_play; - - a->track_get_key_indices_in_range(i, p_time, p_delta, &to_play, p_looped_flag); - if (to_play.size()) { - idx = to_play.back()->get(); - } - } - if (idx < 0) { - continue; - } - - // Play stream. - Ref stream = a->audio_track_get_key_stream(i, idx); - if (stream.is_valid()) { - double start_ofs = a->audio_track_get_key_start_offset(i, idx); - double end_ofs = a->audio_track_get_key_end_offset(i, idx); - double len = stream->get_length(); - - if (p_seeked || p_started) { - start_ofs += p_time - a->track_get_key_time(i, idx); - } - - if (aa->object->call(SNAME("get_stream")) != aa->audio_stream) { - aa->object->call(SNAME("set_stream"), aa->audio_stream); - aa->audio_stream_playback.unref(); - if (!playing_audio_stream_players.has(asp)) { - playing_audio_stream_players.push_back(asp); - } - } - if (!aa->object->call(SNAME("is_playing"))) { - aa->object->call(SNAME("play")); - } - if (!aa->object->call(SNAME("has_stream_playback"))) { - aa->audio_stream_playback.unref(); - continue; - } - if (aa->audio_stream_playback.is_null()) { - aa->audio_stream_playback = aa->object->call(SNAME("get_stream_playback")); - } - - TrackNodeCache::PlayingAudioStreamInfo pasi; - pasi.index = aa->audio_stream_playback->play_stream(stream, start_ofs); - pasi.start = p_time; - if (len && end_ofs > 0) { // Force an end at a time. - pasi.len = len - start_ofs - end_ofs; - } else { - pasi.len = 0; - } - map[idx] = pasi; - } - - } break; - case Animation::TYPE_ANIMATION: { - if (is_stopping) { - continue; - } - - AnimationPlayer *player = Object::cast_to(nc->node); - if (!player) { - continue; - } - - if (p_seeked) { - //seek - int idx = a->track_find_key(i, p_time); - if (idx < 0) { - continue; - } - - double pos = a->track_get_key_time(i, idx); - - StringName anim_name = a->animation_track_get_key_animation(i, idx); - if (String(anim_name) == "[stop]" || !player->has_animation(anim_name)) { - continue; - } - - Ref anim = player->get_animation(anim_name); - - double at_anim_pos = 0.0; - - switch (anim->get_loop_mode()) { - case Animation::LOOP_NONE: { - at_anim_pos = MIN((double)anim->get_length(), p_time - pos); //seek to end - } break; - - case Animation::LOOP_LINEAR: { - at_anim_pos = Math::fposmod(p_time - pos, (double)anim->get_length()); //seek to loop - } break; - - case Animation::LOOP_PINGPONG: { - at_anim_pos = Math::pingpong(p_time - pos, (double)anim->get_length()); - } break; - - default: - break; - } - - if (player->is_playing()) { - player->seek(at_anim_pos); - player->play(anim_name); - nc->animation_playing = true; - playing_caches.insert(nc); - } else { - player->set_assigned_animation(anim_name); - player->seek(at_anim_pos, true); - } - } else { - //find stuff to play - List to_play; - if (p_started) { - int first_key = a->track_find_key(i, p_prev_time, Animation::FIND_MODE_EXACT); - if (first_key >= 0) { - to_play.push_back(first_key); - } - } - a->track_get_key_indices_in_range(i, p_time, p_delta, &to_play, p_looped_flag); - if (to_play.size()) { - int idx = to_play.back()->get(); - - StringName anim_name = a->animation_track_get_key_animation(i, idx); - if (String(anim_name) == "[stop]" || !player->has_animation(anim_name)) { - if (playing_caches.has(nc)) { - playing_caches.erase(nc); - player->stop(); - nc->animation_playing = false; - } - } else { - player->seek(0.0); - player->play(anim_name); - nc->animation_playing = true; - playing_caches.insert(nc); - } - } - } - - } break; - } - } -} - -void AnimationPlayer::_animation_process_data(PlaybackData &cd, double p_delta, float p_blend, bool p_seeked, bool p_started) { - double delta = p_delta * speed_scale * cd.speed_scale; +void AnimationPlayer::_process_playback_data(PlaybackData &cd, double p_delta, float p_blend, bool p_seeked, bool p_started, bool p_is_current) { + double speed = speed_scale * cd.speed_scale; + bool backwards = signbit(speed); // Negative zero means playing backwards too. + double delta = p_started ? 0 : p_delta * speed; double next_pos = cd.pos + delta; - bool backwards = signbit(delta); // Negative zero means playing backwards too. real_t len = cd.from->animation->get_length(); Animation::LoopedFlag looped_flag = Animation::LOOPED_FLAG_NONE; @@ -1052,331 +204,456 @@ void AnimationPlayer::_animation_process_data(PlaybackData &cd, double p_delta, double prev_pos = cd.pos; // The animation may be changed during process, so it is safer that the state is changed before process. cd.pos = next_pos; - AnimationData *prev_from = cd.from; - _animation_process_animation(cd.from, prev_pos, cd.pos, delta, p_blend, &cd == &playback.current, p_seeked, p_started, looped_flag); - // End detection. - if (cd.from->animation->get_loop_mode() == Animation::LOOP_NONE) { - if (prev_from != playback.current.from) { - return; // Animation has been changed in the process (may be caused by method track), abort process. - } - if (!backwards && prev_pos <= len && next_pos == len) { - // Playback finished. - end_reached = true; - end_notify = prev_pos < len; // Notify only if not already at the end. - } - if (backwards && prev_pos >= 0 && next_pos == 0) { - // Playback finished. - end_reached = true; - end_notify = prev_pos > 0; // Notify only if not already at the beginning. + if (p_is_current) { + if (cd.from->animation->get_loop_mode() == Animation::LOOP_NONE) { + if (!backwards && prev_pos <= len && next_pos == len) { + // Playback finished. + end_reached = true; + end_notify = prev_pos < len; // Notify only if not already at the end. + p_blend = 1.0; + } + if (backwards && prev_pos >= 0 && next_pos == 0) { + // Playback finished. + end_reached = true; + end_notify = prev_pos > 0; // Notify only if not already at the beginning. + p_blend = 1.0; + } } } + + PlaybackInfo pi; + if (p_started) { + pi.time = prev_pos; + pi.delta = 0; + pi.seeked = true; + } else { + pi.time = next_pos; + pi.delta = delta; + pi.seeked = p_seeked; + } + pi.is_external_seeking = false; + pi.looped_flag = looped_flag; + pi.weight = p_blend; + make_animation_instance(cd.from->name, pi); } -void AnimationPlayer::_animation_process2(double p_delta, bool p_started) { +void AnimationPlayer::_blend_playback_data(double p_delta, bool p_started) { Playback &c = playback; - accum_pass++; - bool seeked = c.seeked; // The animation may be changed during process, so it is safer that the state is changed before process. if (p_delta != 0) { c.seeked = false; } - float blend = 1.0; // First animation we play at 100% blend - - List::Element *next = NULL; - for (List::Element *E = c.blend.front(); E; E = next) { + // First, calc all blends weight. + float blend = 1.0; + for (List::Element *E = c.blend.front(); E; E = E->next()) { Blend &b = E->get(); + blend = MAX(0, blend - b.blend_left); + b.blend_left = MAX(0, b.blend_left - Math::absf(speed_scale * p_delta) / b.blend_time); + } + + // Second, process current animation to check if the animation end reached. + _process_playback_data(c.current, p_delta, blend, seeked, p_started, true); + + // Finally, if not end the animation, do blending. + if (end_reached) { + playback.blend.clear(); + return; + } + List::Element *> to_erase; + for (List::Element *E = c.blend.front(); E; E = E->next()) { + Blend &b = E->get(); + if (b.blend_left <= 0) { + to_erase.push_back(E); + b.blend_left = CMP_EPSILON; // May want to play last frame. + } // Note: There may be issues if an animation event triggers an animation change while this blend is active, // so it is best to use "deferred" calls instead of "immediate" for animation events that can trigger new animations. - _animation_process_data(b.data, p_delta, blend, false, false); - blend = 1.0 - b.blend_left / b.blend_time; // This is how much to blend the NEXT animation - b.blend_left -= Math::absf(speed_scale * p_delta); - next = E->next(); - if (b.blend_left < 0) { - // If the blend of this has finished, we need to remove ALL the previous blends - List::Element *prev; - while (E) { - prev = E->prev(); - c.blend.erase(E); - E = prev; - } - } + _process_playback_data(b.data, p_delta, b.blend_left, false, false); } - - _animation_process_data(c.current, p_delta, blend, seeked, p_started); -} - -void AnimationPlayer::_animation_update_transforms() { - { - Transform3D t; - for (int i = 0; i < cache_update_size; i++) { - TrackNodeCache *nc = cache_update[i]; - - ERR_CONTINUE(nc->accum_pass != accum_pass); -#ifndef _3D_DISABLED - if (nc->skeleton && nc->bone_idx >= 0) { - if (nc->loc_used) { - nc->skeleton->set_bone_pose_position(nc->bone_idx, nc->loc_accum); - } - if (nc->rot_used) { - nc->skeleton->set_bone_pose_rotation(nc->bone_idx, nc->rot_accum); - } - if (nc->scale_used) { - nc->skeleton->set_bone_pose_scale(nc->bone_idx, nc->scale_accum); - } - - } else if (nc->node_blend_shape) { - nc->node_blend_shape->set_blend_shape_value(nc->blend_shape_idx, nc->blend_shape_accum); - } else if (nc->node_3d) { - if (nc->loc_used) { - nc->node_3d->set_position(nc->loc_accum); - } - if (nc->rot_used) { - nc->node_3d->set_rotation(nc->rot_accum.get_euler()); - } - if (nc->scale_used) { - nc->node_3d->set_scale(nc->scale_accum); - } - } - -#endif // _3D_DISABLED - } - } - - for (int i = 0; i < cache_update_prop_size; i++) { - TrackNodeCache::PropertyAnim *pa = cache_update_prop[i]; - - ERR_CONTINUE(pa->accum_pass != accum_pass); - - switch (pa->special) { - case SP_NONE: { - bool valid; - pa->object->set_indexed(pa->subpath, pa->value_accum, &valid); //you are not speshul -#ifdef DEBUG_ENABLED - - if (!valid) { - // Get subpath as string for printing the error - // Cannot use `String::join(Vector)` because this is a vector of StringName - String key_debug; - if (pa->subpath.size() > 0) { - key_debug = pa->subpath[0]; - for (int subpath_index = 1; subpath_index < pa->subpath.size(); ++subpath_index) { - key_debug += "."; - key_debug += pa->subpath[subpath_index]; - } - } - ERR_PRINT("Failed setting key '" + key_debug + - "' at time " + rtos(playback.current.pos) + - " in Animation '" + get_current_animation() + - "' at Node '" + get_path() + - "', Track '" + String(pa->owner->path) + - "'. Check if the property exists or the type of key is right for the property."); - } -#endif - - } break; - case SP_NODE2D_POS: { -#ifdef DEBUG_ENABLED - if (pa->value_accum.get_type() != Variant::VECTOR2) { - ERR_PRINT("Position key at time " + rtos(playback.current.pos) + " in Animation '" + get_current_animation() + "' at Node '" + get_path() + "', Track '" + String(pa->owner->path) + "' not of type Vector2()"); - } -#endif - static_cast(pa->object)->set_position(pa->value_accum); - } break; - case SP_NODE2D_ROT: { -#ifdef DEBUG_ENABLED - if (pa->value_accum.is_num()) { - ERR_PRINT("Rotation key at time " + rtos(playback.current.pos) + " in Animation '" + get_current_animation() + "' at Node '" + get_path() + "', Track '" + String(pa->owner->path) + "' not numerical"); - } -#endif - - static_cast(pa->object)->set_rotation(Math::deg_to_rad((double)pa->value_accum)); - } break; - case SP_NODE2D_SCALE: { -#ifdef DEBUG_ENABLED - if (pa->value_accum.get_type() != Variant::VECTOR2) { - ERR_PRINT("Scale key at time " + rtos(playback.current.pos) + " in Animation '" + get_current_animation() + "' at Node '" + get_path() + "', Track '" + String(pa->owner->path) + "' not of type Vector2()"); - } -#endif - - static_cast(pa->object)->set_scale(pa->value_accum); - } break; - } - } - - for (int i = 0; i < cache_update_bezier_size; i++) { - TrackNodeCache::BezierAnim *ba = cache_update_bezier[i]; - - ERR_CONTINUE(ba->accum_pass != accum_pass); - ba->object->set_indexed(ba->bezier_property, ba->bezier_accum); - } - - for (int i = 0; i < cache_update_audio_size; i++) { - TrackNodeCache::AudioAnim *aa = cache_update_audio[i]; - - ERR_CONTINUE(aa->accum_pass != accum_pass); - - // Audio ending process. - LocalVector erase_list; - for (const KeyValue &K : aa->playing_streams) { - TrackNodeCache::PlayingAudioStreamInfo pasi = K.value; - - bool stop = false; - if (!aa->audio_stream_playback->is_stream_playing(pasi.index)) { - stop = true; - } - if (!aa->loop) { - if (!aa->backward) { - if (aa->time < pasi.start) { - stop = true; - } - } else { - if (aa->time > pasi.start) { - stop = true; - } - } - } - if (pasi.len > 0) { - double len = 0.0; - if (!aa->backward) { - len = pasi.start > aa->time ? (aa->length - pasi.start) + aa->time : aa->time - pasi.start; - } else { - len = pasi.start < aa->time ? (aa->length - aa->time) + pasi.start : pasi.start - aa->time; - } - if (len > pasi.len) { - stop = true; - } - } - if (stop) { - // Time to stop. - aa->audio_stream_playback->stop_stream(pasi.index); - erase_list.push_back(K.key); - } - } - for (uint32_t erase_idx = 0; erase_idx < erase_list.size(); erase_idx++) { - aa->playing_streams.erase(erase_list[erase_idx]); - } + for (List::Element *&E : to_erase) { + c.blend.erase(E); } } -void AnimationPlayer::_animation_process(double p_delta) { - if (playback.current.from) { - end_reached = false; - end_notify = false; +bool AnimationPlayer::_blend_pre_process(double p_delta, int p_track_count, const HashMap &p_track_map) { + if (!playback.current.from) { + _set_process(false); + return false; + } - bool started = playback.started; // The animation may be changed during process, so it is safer that the state is changed before process. - if (playback.started) { - playback.started = false; - } + tmp_from = playback.current.from->animation->get_instance_id(); + end_reached = false; + end_notify = false; - cache_update_size = 0; - cache_update_prop_size = 0; - cache_update_bezier_size = 0; - cache_update_audio_size = 0; + bool started = playback.started; // The animation may be changed during process, so it is safer that the state is changed before process. + if (playback.started) { + playback.started = false; + } - AnimationData *prev_from = playback.current.from; - _animation_process2(p_delta, started); - if (prev_from != playback.current.from) { - return; // Animation has been changed in the process (may be caused by method track), abort process. - } - _animation_update_transforms(); + AnimationData *prev_from = playback.current.from; + _blend_playback_data(p_delta, started); - if (end_reached) { - _clear_audio_streams(); - _stop_playing_caches(false); - if (queued.size()) { + if (prev_from != playback.current.from) { + return false; // Animation has been changed in the process (may be caused by method track), abort process. + } + + return true; +} + +void AnimationPlayer::_blend_post_process() { + if (end_reached) { + // If the method track changes current animation, the animation is not finished. + if (tmp_from == playback.current.from->animation->get_instance_id()) { + if (playback_queue.size()) { String old = playback.assigned; - play(queued.front()->get()); + play(playback_queue.front()->get()); String new_name = playback.assigned; - queued.pop_front(); + playback_queue.pop_front(); if (end_notify) { emit_signal(SceneStringNames::get_singleton()->animation_changed, old, new_name); } } else { + _clear_caches(); playing = false; _set_process(false); if (end_notify) { emit_signal(SceneStringNames::get_singleton()->animation_finished, playback.assigned); - if (movie_quit_on_finish && OS::get_singleton()->has_feature("movie")) { print_line(vformat("Movie Maker mode is enabled. Quitting on animation finish as requested by: %s", get_path())); get_tree()->quit(); } } } - end_reached = false; } + end_reached = false; + end_notify = false; + } + tmp_from = ObjectID(); +} +void AnimationPlayer::queue(const StringName &p_name) { + if (!is_playing()) { + play(p_name); } else { - _set_process(false); + playback_queue.push_back(p_name); } } -void AnimationPlayer::_animation_set_cache_update() { - // Relatively fast function to update all animations. +Vector AnimationPlayer::get_queue() { + Vector ret; + for (const StringName &E : playback_queue) { + ret.push_back(E); + } - animation_set_update_pass++; - bool clear_cache_needed = false; + return ret; +} - // Update changed and add otherwise - for (const AnimationLibraryData &lib : animation_libraries) { - for (const KeyValue> &K : lib.library->animations) { - StringName key = lib.name == StringName() ? K.key : StringName(String(lib.name) + "/" + String(K.key)); - if (!animation_set.has(key)) { - AnimationData ad; - ad.animation = K.value; - ad.animation_library = lib.name; - ad.name = key; - ad.last_update = animation_set_update_pass; - animation_set.insert(ad.name, ad); +void AnimationPlayer::clear_queue() { + playback_queue.clear(); +} + +void AnimationPlayer::play_backwards(const StringName &p_name, double p_custom_blend) { + play(p_name, p_custom_blend, -1, true); +} + +void AnimationPlayer::play(const StringName &p_name, double p_custom_blend, float p_custom_scale, bool p_from_end) { + StringName name = p_name; + + if (String(name) == "") { + name = playback.assigned; + } + + ERR_FAIL_COND_MSG(!animation_set.has(name), vformat("Animation not found: %s.", name)); + + Playback &c = playback; + + if (c.current.from) { + double blend_time = 0.0; + // Find if it can blend. + BlendKey bk; + bk.from = c.current.from->name; + bk.to = name; + + if (p_custom_blend >= 0) { + blend_time = p_custom_blend; + } else if (blend_times.has(bk)) { + blend_time = blend_times[bk]; + } else { + bk.from = "*"; + if (blend_times.has(bk)) { + blend_time = blend_times[bk]; } else { - AnimationData &ad = animation_set[key]; - if (ad.last_update != animation_set_update_pass) { - // Was not updated, update. If the animation is duplicated, the second one will be ignored. - if (ad.animation != K.value || ad.animation_library != lib.name) { - // Animation changed, update and clear caches. - clear_cache_needed = true; - ad.animation = K.value; - ad.animation_library = lib.name; - } + bk.from = c.current.from->name; + bk.to = "*"; - ad.last_update = animation_set_update_pass; + if (blend_times.has(bk)) { + blend_time = blend_times[bk]; } } } - } - // Check removed - List to_erase; - for (const KeyValue &E : animation_set) { - if (E.value.last_update != animation_set_update_pass) { - // Was not updated, must be erased - to_erase.push_back(E.key); - clear_cache_needed = true; + if (p_custom_blend < 0 && blend_time == 0 && default_blend_time) { + blend_time = default_blend_time; + } + if (blend_time > 0) { + Blend b; + b.data = c.current; + b.blend_left = 1.0; + b.blend_time = blend_time; + c.blend.push_back(b); + } else { + c.blend.clear(); } } - while (to_erase.size()) { - animation_set.erase(to_erase.front()->get()); - to_erase.pop_front(); + if (get_current_animation() != p_name) { + _clear_caches(); } - if (clear_cache_needed) { - // If something was modified or removed, caches need to be cleared - clear_caches(); + c.current.from = &animation_set[name]; + + if (c.assigned != name) { // Reset. + c.current.pos = p_from_end ? c.current.from->animation->get_length() : 0; + } else { + if (p_from_end && c.current.pos == 0) { + // Animation reset but played backwards, set position to the end. + c.current.pos = c.current.from->animation->get_length(); + } else if (!p_from_end && c.current.pos == c.current.from->animation->get_length()) { + // Animation resumed but already ended, set position to the beginning. + c.current.pos = 0; + } } - emit_signal(SNAME("animation_list_changed")); + c.current.speed_scale = p_custom_scale; + c.assigned = name; + emit_signal(SNAME("current_animation_changed"), c.assigned); + c.seeked = false; + c.started = true; + + if (!end_reached) { + playback_queue.clear(); + } + _set_process(true); // Always process when starting an animation. + playing = true; + + emit_signal(SceneStringNames::get_singleton()->animation_started, c.assigned); + + if (is_inside_tree() && Engine::get_singleton()->is_editor_hint()) { + return; // No next in this case. + } + + StringName next = animation_get_next(p_name); + if (next != StringName() && animation_set.has(next)) { + queue(next); + } } -void AnimationPlayer::_animation_added(const StringName &p_name, const StringName &p_library) { - _animation_set_cache_update(); +bool AnimationPlayer::is_playing() const { + return playing; +} + +void AnimationPlayer::set_current_animation(const String &p_animation) { + if (p_animation == "[stop]" || p_animation.is_empty()) { + stop(); + } else if (!is_playing()) { + play(p_animation); + } else if (playback.assigned != p_animation) { + float speed = playback.current.speed_scale; + play(p_animation, -1.0, speed, signbit(speed)); + } else { + // Same animation, do not replay from start. + } +} + +String AnimationPlayer::get_current_animation() const { + return (is_playing() ? playback.assigned : ""); +} + +void AnimationPlayer::set_assigned_animation(const String &p_animation) { + if (is_playing()) { + float speed = playback.current.speed_scale; + play(p_animation, -1.0, speed, signbit(speed)); + } else { + ERR_FAIL_COND_MSG(!animation_set.has(p_animation), vformat("Animation not found: %s.", p_animation)); + playback.current.pos = 0; + playback.current.from = &animation_set[p_animation]; + playback.assigned = p_animation; + emit_signal(SNAME("current_animation_changed"), playback.assigned); + } +} + +String AnimationPlayer::get_assigned_animation() const { + return playback.assigned; +} + +void AnimationPlayer::pause() { + _stop_internal(false, false); +} + +void AnimationPlayer::stop(bool p_keep_state) { + _stop_internal(true, p_keep_state); +} + +void AnimationPlayer::set_speed_scale(float p_speed) { + speed_scale = p_speed; +} + +float AnimationPlayer::get_speed_scale() const { + return speed_scale; +} + +float AnimationPlayer::get_playing_speed() const { + if (!playing) { + return 0; + } + return speed_scale * playback.current.speed_scale; +} + +void AnimationPlayer::seek(double p_time, bool p_update, bool p_update_only) { + if (!active) { + return; + } + + playback.current.pos = p_time; + + if (!playback.current.from) { + if (playback.assigned) { + ERR_FAIL_COND_MSG(!animation_set.has(playback.assigned), vformat("Animation not found: %s.", playback.assigned)); + playback.current.from = &animation_set[playback.assigned]; + } + if (!playback.current.from) { + return; // There is no animation. + } + } + + playback.seeked = true; + if (p_update) { + _process_animation(0, p_update_only); + } +} + +bool AnimationPlayer::is_valid() const { + return (playback.current.from); +} + +double AnimationPlayer::get_current_animation_position() const { + ERR_FAIL_COND_V_MSG(!playback.current.from, 0, "AnimationPlayer has no current animation"); + return playback.current.pos; +} + +double AnimationPlayer::get_current_animation_length() const { + ERR_FAIL_COND_V_MSG(!playback.current.from, 0, "AnimationPlayer has no current animation"); + return playback.current.from->animation->get_length(); +} + +void AnimationPlayer::set_autoplay(const String &p_name) { + if (is_inside_tree() && !Engine::get_singleton()->is_editor_hint()) { + WARN_PRINT("Setting autoplay after the node has been added to the scene has no effect."); + } + + autoplay = p_name; +} + +String AnimationPlayer::get_autoplay() const { + return autoplay; +} + +void AnimationPlayer::set_movie_quit_on_finish_enabled(bool p_enabled) { + movie_quit_on_finish = p_enabled; +} + +bool AnimationPlayer::is_movie_quit_on_finish_enabled() const { + return movie_quit_on_finish; +} + +void AnimationPlayer::_stop_internal(bool p_reset, bool p_keep_state) { + _clear_caches(); + Playback &c = playback; + // c.blend.clear(); + if (p_reset) { + c.blend.clear(); + if (p_keep_state) { + c.current.pos = 0; + } else { + is_stopping = true; + seek(0, true, true); + is_stopping = false; + } + c.current.from = nullptr; + c.current.speed_scale = 1; + emit_signal(SNAME("current_animation_changed"), ""); + } + _set_process(false); + playback_queue.clear(); + playing = false; +} + +void AnimationPlayer::animation_set_next(const StringName &p_animation, const StringName &p_next) { + ERR_FAIL_COND_MSG(!animation_set.has(p_animation), vformat("Animation not found: %s.", p_animation)); + animation_next_set[p_animation] = p_next; +} + +StringName AnimationPlayer::animation_get_next(const StringName &p_animation) const { + if (!animation_next_set.has(p_animation)) { + return StringName(); + } + return animation_next_set[p_animation]; +} + +void AnimationPlayer::set_default_blend_time(double p_default) { + default_blend_time = p_default; +} + +double AnimationPlayer::get_default_blend_time() const { + return default_blend_time; +} + +void AnimationPlayer::set_blend_time(const StringName &p_animation1, const StringName &p_animation2, double p_time) { + ERR_FAIL_COND_MSG(!animation_set.has(p_animation1), vformat("Animation not found: %s.", p_animation1)); + ERR_FAIL_COND_MSG(!animation_set.has(p_animation2), vformat("Animation not found: %s.", p_animation2)); + ERR_FAIL_COND_MSG(p_time < 0, "Blend time cannot be smaller than 0."); + + BlendKey bk; + bk.from = p_animation1; + bk.to = p_animation2; + if (p_time == 0) { + blend_times.erase(bk); + } else { + blend_times[bk] = p_time; + } +} + +double AnimationPlayer::get_blend_time(const StringName &p_animation1, const StringName &p_animation2) const { + BlendKey bk; + bk.from = p_animation1; + bk.to = p_animation2; + + if (blend_times.has(bk)) { + return blend_times[bk]; + } else { + return 0; + } +} + +void AnimationPlayer::get_argument_options(const StringName &p_function, int p_idx, List *r_options) const { + String pf = p_function; + if (p_idx == 0 && (p_function == "play" || p_function == "play_backwards" || p_function == "has_animation" || p_function == "queue")) { + List al; + get_animation_list(&al); + for (const StringName &name : al) { + r_options->push_back(String(name).quote()); + } + } + Node::get_argument_options(p_function, p_idx, r_options); } void AnimationPlayer::_animation_removed(const StringName &p_name, const StringName &p_library) { + AnimationMixer::_animation_removed(p_name, p_library); + StringName name = p_library == StringName() ? p_name : StringName(String(p_library) + "/" + String(p_name)); if (!animation_set.has(name)) { @@ -1401,6 +678,8 @@ void AnimationPlayer::_animation_removed(const StringName &p_name, const StringN } void AnimationPlayer::_rename_animation(const StringName &p_from_name, const StringName &p_to_name) { + AnimationMixer::_rename_animation(p_from_name, p_to_name); + // Rename autoplay or blends if needed. List to_erase; HashMap to_insert; @@ -1438,787 +717,12 @@ void AnimationPlayer::_rename_animation(const StringName &p_from_name, const Str } } -void AnimationPlayer::_animation_renamed(const StringName &p_name, const StringName &p_to_name, const StringName &p_library) { - StringName from_name = p_library == StringName() ? p_name : StringName(String(p_library) + "/" + String(p_name)); - StringName to_name = p_library == StringName() ? p_to_name : StringName(String(p_library) + "/" + String(p_to_name)); - - if (!animation_set.has(from_name)) { - return; // No need to update because not the one from the library being used. - } - _animation_set_cache_update(); - - _rename_animation(from_name, to_name); -} - -Error AnimationPlayer::add_animation_library(const StringName &p_name, const Ref &p_animation_library) { - ERR_FAIL_COND_V(p_animation_library.is_null(), ERR_INVALID_PARAMETER); -#ifdef DEBUG_ENABLED - ERR_FAIL_COND_V_MSG(String(p_name).contains("/") || String(p_name).contains(":") || String(p_name).contains(",") || String(p_name).contains("["), ERR_INVALID_PARAMETER, "Invalid animation name: " + String(p_name) + "."); -#endif - - int insert_pos = 0; - - for (const AnimationLibraryData &lib : animation_libraries) { - ERR_FAIL_COND_V_MSG(lib.name == p_name, ERR_ALREADY_EXISTS, "Can't add animation library twice with name: " + String(p_name)); - ERR_FAIL_COND_V_MSG(lib.library == p_animation_library, ERR_ALREADY_EXISTS, "Can't add animation library twice (adding as '" + p_name.operator String() + "', exists as '" + lib.name.operator String() + "'."); - - if (lib.name.operator String() >= p_name.operator String()) { - break; - } - - insert_pos++; - } - - AnimationLibraryData ald; - ald.name = p_name; - ald.library = p_animation_library; - - animation_libraries.insert(insert_pos, ald); - - ald.library->connect(SNAME("animation_added"), callable_mp(this, &AnimationPlayer::_animation_added).bind(p_name)); - ald.library->connect(SNAME("animation_removed"), callable_mp(this, &AnimationPlayer::_animation_removed).bind(p_name)); - ald.library->connect(SNAME("animation_renamed"), callable_mp(this, &AnimationPlayer::_animation_renamed).bind(p_name)); - ald.library->connect(SNAME("animation_changed"), callable_mp(this, &AnimationPlayer::_animation_changed)); - - _animation_set_cache_update(); - - notify_property_list_changed(); - - return OK; -} - -void AnimationPlayer::remove_animation_library(const StringName &p_name) { - int at_pos = -1; - - for (uint32_t i = 0; i < animation_libraries.size(); i++) { - if (animation_libraries[i].name == p_name) { - at_pos = i; - break; - } - } - - ERR_FAIL_COND(at_pos == -1); - - animation_libraries[at_pos].library->disconnect(SNAME("animation_added"), callable_mp(this, &AnimationPlayer::_animation_added)); - animation_libraries[at_pos].library->disconnect(SNAME("animation_removed"), callable_mp(this, &AnimationPlayer::_animation_removed)); - animation_libraries[at_pos].library->disconnect(SNAME("animation_renamed"), callable_mp(this, &AnimationPlayer::_animation_renamed)); - animation_libraries[at_pos].library->disconnect(SNAME("animation_changed"), callable_mp(this, &AnimationPlayer::_animation_changed)); - - stop(); - - animation_libraries.remove_at(at_pos); - _animation_set_cache_update(); - - notify_property_list_changed(); -} - -void AnimationPlayer::rename_animation_library(const StringName &p_name, const StringName &p_new_name) { - if (p_name == p_new_name) { - return; - } -#ifdef DEBUG_ENABLED - ERR_FAIL_COND_MSG(String(p_new_name).contains("/") || String(p_new_name).contains(":") || String(p_new_name).contains(",") || String(p_new_name).contains("["), "Invalid animation library name: " + String(p_new_name) + "."); -#endif - - bool found = false; - for (AnimationLibraryData &lib : animation_libraries) { - ERR_FAIL_COND_MSG(lib.name == p_new_name, "Can't rename animation library to another existing name: " + String(p_new_name)); - if (lib.name == p_name) { - found = true; - lib.name = p_new_name; - // rename connections - lib.library->disconnect(SNAME("animation_added"), callable_mp(this, &AnimationPlayer::_animation_added)); - lib.library->disconnect(SNAME("animation_removed"), callable_mp(this, &AnimationPlayer::_animation_removed)); - lib.library->disconnect(SNAME("animation_renamed"), callable_mp(this, &AnimationPlayer::_animation_renamed)); - - lib.library->connect(SNAME("animation_added"), callable_mp(this, &AnimationPlayer::_animation_added).bind(p_new_name)); - lib.library->connect(SNAME("animation_removed"), callable_mp(this, &AnimationPlayer::_animation_removed).bind(p_new_name)); - lib.library->connect(SNAME("animation_renamed"), callable_mp(this, &AnimationPlayer::_animation_renamed).bind(p_new_name)); - - for (const KeyValue> &K : lib.library->animations) { - StringName old_name = p_name == StringName() ? K.key : StringName(String(p_name) + "/" + String(K.key)); - StringName new_name = p_new_name == StringName() ? K.key : StringName(String(p_new_name) + "/" + String(K.key)); - _rename_animation(old_name, new_name); - } - } - } - - ERR_FAIL_COND(!found); - - stop(); - - animation_libraries.sort(); // Must keep alphabetical order. - - _animation_set_cache_update(); // Update cache. - - notify_property_list_changed(); -} - -bool AnimationPlayer::has_animation_library(const StringName &p_name) const { - for (const AnimationLibraryData &lib : animation_libraries) { - if (lib.name == p_name) { - return true; - } - } - - return false; -} - -Ref AnimationPlayer::get_animation_library(const StringName &p_name) const { - for (const AnimationLibraryData &lib : animation_libraries) { - if (lib.name == p_name) { - return lib.library; - } - } - ERR_FAIL_V(Ref()); -} - -TypedArray AnimationPlayer::_get_animation_library_list() const { - TypedArray ret; - for (const AnimationLibraryData &lib : animation_libraries) { - ret.push_back(lib.name); - } - return ret; -} - -void AnimationPlayer::get_animation_library_list(List *p_libraries) const { - for (const AnimationLibraryData &lib : animation_libraries) { - p_libraries->push_back(lib.name); - } -} - -bool AnimationPlayer::has_animation(const StringName &p_name) const { - return animation_set.has(p_name); -} - -Ref AnimationPlayer::get_animation(const StringName &p_name) const { - ERR_FAIL_COND_V_MSG(!animation_set.has(p_name), Ref(), vformat("Animation not found: \"%s\".", p_name)); - - const AnimationData &anim_data = animation_set[p_name]; - - return anim_data.animation; -} - -void AnimationPlayer::get_animation_list(List *p_animations) const { - List anims; - - for (const KeyValue &E : animation_set) { - anims.push_back(E.key); - } - - anims.sort(); - - for (const String &E : anims) { - p_animations->push_back(E); - } -} - -void AnimationPlayer::set_blend_time(const StringName &p_animation1, const StringName &p_animation2, double p_time) { - ERR_FAIL_COND_MSG(!animation_set.has(p_animation1), vformat("Animation not found: %s.", p_animation1)); - ERR_FAIL_COND_MSG(!animation_set.has(p_animation2), vformat("Animation not found: %s.", p_animation2)); - ERR_FAIL_COND_MSG(p_time < 0, "Blend time cannot be smaller than 0."); - - BlendKey bk; - bk.from = p_animation1; - bk.to = p_animation2; - if (p_time == 0) { - blend_times.erase(bk); - } else { - blend_times[bk] = p_time; - } -} - -double AnimationPlayer::get_blend_time(const StringName &p_animation1, const StringName &p_animation2) const { - BlendKey bk; - bk.from = p_animation1; - bk.to = p_animation2; - - if (blend_times.has(bk)) { - return blend_times[bk]; - } else { - return 0; - } -} - -void AnimationPlayer::queue(const StringName &p_name) { - if (!is_playing()) { - play(p_name); - } else { - queued.push_back(p_name); - } -} - -Vector AnimationPlayer::get_queue() { - Vector ret; - for (const StringName &E : queued) { - ret.push_back(E); - } - - return ret; -} - -void AnimationPlayer::clear_queue() { - queued.clear(); -} - -void AnimationPlayer::play_backwards(const StringName &p_name, double p_custom_blend) { - play(p_name, p_custom_blend, -1, true); -} - -void AnimationPlayer::play(const StringName &p_name, double p_custom_blend, float p_custom_scale, bool p_from_end) { - StringName name = p_name; - - if (String(name) == "") { - name = playback.assigned; - } - - ERR_FAIL_COND_MSG(!animation_set.has(name), vformat("Animation not found: %s.", name)); - - Playback &c = playback; - - if (c.current.from) { - double blend_time = 0.0; - // find if it can blend - BlendKey bk; - bk.from = c.current.from->name; - bk.to = name; - - if (p_custom_blend >= 0) { - blend_time = p_custom_blend; - } else if (blend_times.has(bk)) { - blend_time = blend_times[bk]; - } else { - bk.from = "*"; - if (blend_times.has(bk)) { - blend_time = blend_times[bk]; - } else { - bk.from = c.current.from->name; - bk.to = "*"; - - if (blend_times.has(bk)) { - blend_time = blend_times[bk]; - } - } - } - - if (p_custom_blend < 0 && blend_time == 0 && default_blend_time) { - blend_time = default_blend_time; - } - if (blend_time > 0) { - Blend b; - b.data = c.current; - b.blend_time = b.blend_left = blend_time; - c.blend.push_back(b); - } else { - c.blend.clear(); - } - } - - if (get_current_animation() != p_name) { - _clear_audio_streams(); - _stop_playing_caches(false); - } - - c.current.from = &animation_set[name]; - - if (c.assigned != name) { // reset - c.current.pos = p_from_end ? c.current.from->animation->get_length() : 0; - } else { - if (p_from_end && c.current.pos == 0) { - // Animation reset BUT played backwards, set position to the end - c.current.pos = c.current.from->animation->get_length(); - } else if (!p_from_end && c.current.pos == c.current.from->animation->get_length()) { - // Animation resumed but already ended, set position to the beginning - c.current.pos = 0; - } - } - - c.current.speed_scale = p_custom_scale; - c.assigned = name; - c.seeked = false; - c.started = true; - - if (!end_reached) { - queued.clear(); - } - _set_process(true); // always process when starting an animation - playing = true; - - emit_signal(SceneStringNames::get_singleton()->animation_started, c.assigned); - - if (is_inside_tree() && Engine::get_singleton()->is_editor_hint()) { - return; // no next in this case - } - - StringName next = animation_get_next(p_name); - if (next != StringName() && animation_set.has(next)) { - queue(next); - } -} - -bool AnimationPlayer::is_playing() const { - return playing; -} - -void AnimationPlayer::set_current_animation(const String &p_anim) { - if (p_anim == "[stop]" || p_anim.is_empty()) { - stop(); - } else if (!is_playing()) { - play(p_anim); - } else if (playback.assigned != p_anim) { - float speed = playback.current.speed_scale; - play(p_anim, -1.0, speed, signbit(speed)); - } else { - // Same animation, do not replay from start - } -} - -String AnimationPlayer::get_current_animation() const { - return (is_playing() ? playback.assigned : ""); -} - -void AnimationPlayer::set_assigned_animation(const String &p_anim) { - if (is_playing()) { - float speed = playback.current.speed_scale; - play(p_anim, -1.0, speed, signbit(speed)); - } else { - ERR_FAIL_COND_MSG(!animation_set.has(p_anim), vformat("Animation not found: %s.", p_anim)); - playback.current.pos = 0; - playback.current.from = &animation_set[p_anim]; - playback.assigned = p_anim; - } -} - -String AnimationPlayer::get_assigned_animation() const { - return playback.assigned; -} - -void AnimationPlayer::pause() { - _stop_internal(false, false); -} - -void AnimationPlayer::stop(bool p_keep_state) { - _stop_internal(true, p_keep_state); -} - -void AnimationPlayer::set_speed_scale(float p_speed) { - speed_scale = p_speed; -} - -float AnimationPlayer::get_speed_scale() const { - return speed_scale; -} - -float AnimationPlayer::get_playing_speed() const { - if (!playing) { - return 0; - } - return speed_scale * playback.current.speed_scale; -} - -void AnimationPlayer::seek(double p_time, bool p_update) { - playback.current.pos = p_time; - - if (!playback.current.from) { - if (playback.assigned) { - ERR_FAIL_COND_MSG(!animation_set.has(playback.assigned), vformat("Animation not found: %s.", playback.assigned)); - playback.current.from = &animation_set[playback.assigned]; - } - if (!playback.current.from) { - return; // There is no animation. - } - } - - playback.seeked = true; - if (p_update) { - _animation_process(0); - } -} - -void AnimationPlayer::seek_delta(double p_time, double p_delta) { - playback.current.pos = p_time - p_delta; - - if (!playback.current.from) { - if (playback.assigned) { - ERR_FAIL_COND_MSG(!animation_set.has(playback.assigned), vformat("Animation not found: %s.", playback.assigned)); - playback.current.from = &animation_set[playback.assigned]; - } - if (!playback.current.from) { - return; // There is no animation. - } - } - - if (speed_scale != 0.0) { - p_delta /= speed_scale; - } - _animation_process(p_delta); -} - -bool AnimationPlayer::is_valid() const { - return (playback.current.from); -} - -double AnimationPlayer::get_current_animation_position() const { - ERR_FAIL_COND_V_MSG(!playback.current.from, 0, "AnimationPlayer has no current animation"); - return playback.current.pos; -} - -double AnimationPlayer::get_current_animation_length() const { - ERR_FAIL_COND_V_MSG(!playback.current.from, 0, "AnimationPlayer has no current animation"); - return playback.current.from->animation->get_length(); -} - -void AnimationPlayer::_animation_changed(const StringName &p_name) { - clear_caches(); - if (is_playing()) { - playback.seeked = true; //need to restart stuff, like audio - } -} - -void AnimationPlayer::_stop_playing_caches(bool p_reset) { - for (TrackNodeCache *E : playing_caches) { - if (E->node && E->audio_playing) { - E->node->call(SNAME("stop")); - } - if (E->node && E->animation_playing) { - AnimationPlayer *player = Object::cast_to(E->node); - if (!player) { - continue; - } - - if (p_reset) { - player->stop(); - } else { - player->pause(); - } - } - } - - playing_caches.clear(); -} - -void AnimationPlayer::_node_removed(Node *p_node) { - clear_caches(); // nodes contained here are being removed, clear the caches -} - -void AnimationPlayer::clear_caches() { - _clear_audio_streams(); - _stop_playing_caches(true); - - node_cache_map.clear(); - - for (KeyValue &E : animation_set) { - E.value.node_cache.clear(); - } - - cache_update_size = 0; - cache_update_prop_size = 0; - cache_update_bezier_size = 0; - cache_update_audio_size = 0; - - emit_signal(SNAME("caches_cleared")); -} - -void AnimationPlayer::_clear_audio_streams() { - for (int i = 0; i < playing_audio_stream_players.size(); i++) { - playing_audio_stream_players[i]->call(SNAME("stop")); - playing_audio_stream_players[i]->call(SNAME("set_stream"), Ref()); - } - playing_audio_stream_players.clear(); -} - -void AnimationPlayer::set_active(bool p_active) { - if (active == p_active) { - return; - } - - active = p_active; - _set_process(processing, true); -} - -bool AnimationPlayer::is_active() const { - return active; -} - -StringName AnimationPlayer::find_animation(const Ref &p_animation) const { - for (const KeyValue &E : animation_set) { - if (E.value.animation == p_animation) { - return E.key; - } - } - - return StringName(); -} - -StringName AnimationPlayer::find_animation_library(const Ref &p_animation) const { - for (const KeyValue &E : animation_set) { - if (E.value.animation == p_animation) { - return E.value.animation_library; - } - } - return StringName(); -} - -void AnimationPlayer::set_autoplay(const String &p_name) { - if (is_inside_tree() && !Engine::get_singleton()->is_editor_hint()) { - WARN_PRINT("Setting autoplay after the node has been added to the scene has no effect."); - } - - autoplay = p_name; -} - -String AnimationPlayer::get_autoplay() const { - return autoplay; -} - -void AnimationPlayer::set_reset_on_save_enabled(bool p_enabled) { - reset_on_save = p_enabled; -} - -bool AnimationPlayer::is_reset_on_save_enabled() const { - return reset_on_save; -} - -void AnimationPlayer::set_process_callback(AnimationProcessCallback p_mode) { - if (process_callback == p_mode) { - return; - } - - bool pr = processing; - if (pr) { - _set_process(false); - } - process_callback = p_mode; - if (pr) { - _set_process(true); - } -} - -AnimationPlayer::AnimationProcessCallback AnimationPlayer::get_process_callback() const { - return process_callback; -} - -void AnimationPlayer::set_method_call_mode(AnimationMethodCallMode p_mode) { - method_call_mode = p_mode; -} - -AnimationPlayer::AnimationMethodCallMode AnimationPlayer::get_method_call_mode() const { - return method_call_mode; -} - -void AnimationPlayer::set_audio_max_polyphony(int p_audio_max_polyphony) { - ERR_FAIL_COND(p_audio_max_polyphony < 0 || p_audio_max_polyphony > 128); - audio_max_polyphony = p_audio_max_polyphony; -} - -int AnimationPlayer::get_audio_max_polyphony() const { - return audio_max_polyphony; -} - -void AnimationPlayer::set_movie_quit_on_finish_enabled(bool p_enabled) { - movie_quit_on_finish = p_enabled; -} - -bool AnimationPlayer::is_movie_quit_on_finish_enabled() const { - return movie_quit_on_finish; -} - -void AnimationPlayer::_set_process(bool p_process, bool p_force) { - if (processing == p_process && !p_force) { - return; - } - - switch (process_callback) { - case ANIMATION_PROCESS_PHYSICS: - set_physics_process_internal(p_process && active); - break; - case ANIMATION_PROCESS_IDLE: - set_process_internal(p_process && active); - break; - case ANIMATION_PROCESS_MANUAL: - break; - } - - processing = p_process; -} - -void AnimationPlayer::_stop_internal(bool p_reset, bool p_keep_state) { - _clear_audio_streams(); - _stop_playing_caches(p_reset); - Playback &c = playback; - c.blend.clear(); - if (p_reset) { - if (p_keep_state) { - c.current.pos = 0; - } else { - is_stopping = true; - seek(0, true); - is_stopping = false; - } - c.current.from = nullptr; - c.current.speed_scale = 1; - } - _set_process(false); - queued.clear(); - playing = false; -} - -void AnimationPlayer::animation_set_next(const StringName &p_animation, const StringName &p_next) { - ERR_FAIL_COND_MSG(!animation_set.has(p_animation), vformat("Animation not found: %s.", p_animation)); - animation_set[p_animation].next = p_next; -} - -StringName AnimationPlayer::animation_get_next(const StringName &p_animation) const { - if (!animation_set.has(p_animation)) { - return StringName(); - } - return animation_set[p_animation].next; -} - -void AnimationPlayer::set_default_blend_time(double p_default) { - default_blend_time = p_default; -} - -double AnimationPlayer::get_default_blend_time() const { - return default_blend_time; -} - -void AnimationPlayer::set_root(const NodePath &p_root) { - root = p_root; - clear_caches(); -} - -NodePath AnimationPlayer::get_root() const { - return root; -} - -void AnimationPlayer::get_argument_options(const StringName &p_function, int p_idx, List *r_options) const { - String pf = p_function; - if (p_idx == 0 && (p_function == "play" || p_function == "play_backwards" || p_function == "has_animation" || p_function == "queue")) { - List al; - get_animation_list(&al); - for (const StringName &name : al) { - r_options->push_back(String(name).quote()); - } - } - Node::get_argument_options(p_function, p_idx, r_options); -} - -#ifdef TOOLS_ENABLED -Ref AnimationPlayer::backup_animated_values(Node *p_root_override) { - Ref backup; - if (!playback.current.from) { - return backup; - } - - _ensure_node_caches(playback.current.from, p_root_override); - - backup.instantiate(); - for (int i = 0; i < playback.current.from->node_cache.size(); i++) { - TrackNodeCache *nc = playback.current.from->node_cache[i]; - if (!nc) { - continue; - } - - if (nc->skeleton) { - if (nc->bone_idx == -1) { - continue; - } - - AnimatedValuesBackup::Entry entry; - entry.object = nc->skeleton; - entry.bone_idx = nc->bone_idx; - Array arr; - arr.resize(3); - arr[0] = nc->skeleton->get_bone_pose_position(nc->bone_idx); - arr[1] = nc->skeleton->get_bone_pose_rotation(nc->bone_idx); - arr[2] = nc->skeleton->get_bone_pose_scale(nc->bone_idx); - entry.value = nc; - backup->entries.push_back(entry); - } else { - if (nc->node_3d) { - AnimatedValuesBackup::Entry entry; - entry.object = nc->node_3d; - entry.subpath.push_back("transform"); - entry.value = nc->node_3d->get_transform(); - entry.bone_idx = -1; - backup->entries.push_back(entry); - } else { - for (const KeyValue &E : nc->property_anim) { - AnimatedValuesBackup::Entry entry; - entry.object = E.value.object; - entry.subpath = E.value.subpath; - bool valid; - entry.value = E.value.object->get_indexed(E.value.subpath, &valid); - entry.bone_idx = -1; - if (valid) { - backup->entries.push_back(entry); - } - } - } - } - } - - return backup; -} - -Ref AnimationPlayer::apply_reset(bool p_user_initiated) { - ERR_FAIL_COND_V(!can_apply_reset(), Ref()); - - Ref reset_anim = animation_set[SceneStringNames::get_singleton()->RESET].animation; - ERR_FAIL_COND_V(reset_anim.is_null(), Ref()); - - Node *root_node = get_node_or_null(root); - ERR_FAIL_NULL_V(root_node, Ref()); - - AnimationPlayer *aux_player = memnew(AnimationPlayer); - EditorNode::get_singleton()->add_child(aux_player); - Ref al; - al.instantiate(); - al->add_animation(SceneStringNames::get_singleton()->RESET, reset_anim); - aux_player->add_animation_library("", al); - aux_player->set_assigned_animation(SceneStringNames::get_singleton()->RESET); - // Forcing the use of the original root because the scene where original player belongs may be not the active one - Ref old_values = aux_player->backup_animated_values(get_node(get_root())); - aux_player->seek(0.0f, true); - aux_player->queue_free(); - - if (p_user_initiated) { - Ref new_values = aux_player->backup_animated_values(); - old_values->restore(); - - EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); - ur->create_action(TTR("Animation Apply Reset")); - ur->add_do_method(new_values.ptr(), "restore"); - ur->add_undo_method(old_values.ptr(), "restore"); - ur->commit_action(); - } - - return old_values; -} - -bool AnimationPlayer::can_apply_reset() const { - return has_animation(SceneStringNames::get_singleton()->RESET) && playback.assigned != SceneStringNames::get_singleton()->RESET; -} -#endif // TOOLS_ENABLED - void AnimationPlayer::_bind_methods() { - ClassDB::bind_method(D_METHOD("add_animation_library", "name", "library"), &AnimationPlayer::add_animation_library); - ClassDB::bind_method(D_METHOD("remove_animation_library", "name"), &AnimationPlayer::remove_animation_library); - ClassDB::bind_method(D_METHOD("rename_animation_library", "name", "newname"), &AnimationPlayer::rename_animation_library); - ClassDB::bind_method(D_METHOD("has_animation_library", "name"), &AnimationPlayer::has_animation_library); - ClassDB::bind_method(D_METHOD("get_animation_library", "name"), &AnimationPlayer::get_animation_library); - ClassDB::bind_method(D_METHOD("get_animation_library_list"), &AnimationPlayer::_get_animation_library_list); + ClassDB::bind_method(D_METHOD("animation_set_next", "animation_from", "animation_to"), &AnimationPlayer::animation_set_next); + ClassDB::bind_method(D_METHOD("animation_get_next", "animation_from"), &AnimationPlayer::animation_get_next); - ClassDB::bind_method(D_METHOD("has_animation", "name"), &AnimationPlayer::has_animation); - ClassDB::bind_method(D_METHOD("get_animation", "name"), &AnimationPlayer::get_animation); - ClassDB::bind_method(D_METHOD("get_animation_list"), &AnimationPlayer::_get_animation_list); - - ClassDB::bind_method(D_METHOD("animation_set_next", "anim_from", "anim_to"), &AnimationPlayer::animation_set_next); - ClassDB::bind_method(D_METHOD("animation_get_next", "anim_from"), &AnimationPlayer::animation_get_next); - - ClassDB::bind_method(D_METHOD("set_blend_time", "anim_from", "anim_to", "sec"), &AnimationPlayer::set_blend_time); - ClassDB::bind_method(D_METHOD("get_blend_time", "anim_from", "anim_to"), &AnimationPlayer::get_blend_time); + ClassDB::bind_method(D_METHOD("set_blend_time", "animation_from", "animation_to", "sec"), &AnimationPlayer::set_blend_time); + ClassDB::bind_method(D_METHOD("get_blend_time", "animation_from", "animation_to"), &AnimationPlayer::get_blend_time); ClassDB::bind_method(D_METHOD("set_default_blend_time", "sec"), &AnimationPlayer::set_default_blend_time); ClassDB::bind_method(D_METHOD("get_default_blend_time"), &AnimationPlayer::get_default_blend_time); @@ -2229,17 +733,14 @@ void AnimationPlayer::_bind_methods() { ClassDB::bind_method(D_METHOD("stop", "keep_state"), &AnimationPlayer::stop, DEFVAL(false)); ClassDB::bind_method(D_METHOD("is_playing"), &AnimationPlayer::is_playing); - ClassDB::bind_method(D_METHOD("set_current_animation", "anim"), &AnimationPlayer::set_current_animation); + ClassDB::bind_method(D_METHOD("set_current_animation", "animation"), &AnimationPlayer::set_current_animation); ClassDB::bind_method(D_METHOD("get_current_animation"), &AnimationPlayer::get_current_animation); - ClassDB::bind_method(D_METHOD("set_assigned_animation", "anim"), &AnimationPlayer::set_assigned_animation); + ClassDB::bind_method(D_METHOD("set_assigned_animation", "animation"), &AnimationPlayer::set_assigned_animation); ClassDB::bind_method(D_METHOD("get_assigned_animation"), &AnimationPlayer::get_assigned_animation); ClassDB::bind_method(D_METHOD("queue", "name"), &AnimationPlayer::queue); ClassDB::bind_method(D_METHOD("get_queue"), &AnimationPlayer::get_queue); ClassDB::bind_method(D_METHOD("clear_queue"), &AnimationPlayer::clear_queue); - ClassDB::bind_method(D_METHOD("set_active", "active"), &AnimationPlayer::set_active); - ClassDB::bind_method(D_METHOD("is_active"), &AnimationPlayer::is_active); - ClassDB::bind_method(D_METHOD("set_speed_scale", "speed"), &AnimationPlayer::set_speed_scale); ClassDB::bind_method(D_METHOD("get_speed_scale"), &AnimationPlayer::get_speed_scale); ClassDB::bind_method(D_METHOD("get_playing_speed"), &AnimationPlayer::get_playing_speed); @@ -2247,72 +748,34 @@ void AnimationPlayer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_autoplay", "name"), &AnimationPlayer::set_autoplay); ClassDB::bind_method(D_METHOD("get_autoplay"), &AnimationPlayer::get_autoplay); - ClassDB::bind_method(D_METHOD("set_reset_on_save_enabled", "enabled"), &AnimationPlayer::set_reset_on_save_enabled); - ClassDB::bind_method(D_METHOD("is_reset_on_save_enabled"), &AnimationPlayer::is_reset_on_save_enabled); - - ClassDB::bind_method(D_METHOD("set_root", "path"), &AnimationPlayer::set_root); - ClassDB::bind_method(D_METHOD("get_root"), &AnimationPlayer::get_root); - ClassDB::bind_method(D_METHOD("find_animation", "animation"), &AnimationPlayer::find_animation); ClassDB::bind_method(D_METHOD("find_animation_library", "animation"), &AnimationPlayer::find_animation_library); - ClassDB::bind_method(D_METHOD("clear_caches"), &AnimationPlayer::clear_caches); - - ClassDB::bind_method(D_METHOD("set_process_callback", "mode"), &AnimationPlayer::set_process_callback); - ClassDB::bind_method(D_METHOD("get_process_callback"), &AnimationPlayer::get_process_callback); - - ClassDB::bind_method(D_METHOD("set_method_call_mode", "mode"), &AnimationPlayer::set_method_call_mode); - ClassDB::bind_method(D_METHOD("get_method_call_mode"), &AnimationPlayer::get_method_call_mode); - - ClassDB::bind_method(D_METHOD("set_audio_max_polyphony", "max_polyphony"), &AnimationPlayer::set_audio_max_polyphony); - ClassDB::bind_method(D_METHOD("get_audio_max_polyphony"), &AnimationPlayer::get_audio_max_polyphony); - ClassDB::bind_method(D_METHOD("set_movie_quit_on_finish_enabled", "enabled"), &AnimationPlayer::set_movie_quit_on_finish_enabled); ClassDB::bind_method(D_METHOD("is_movie_quit_on_finish_enabled"), &AnimationPlayer::is_movie_quit_on_finish_enabled); ClassDB::bind_method(D_METHOD("get_current_animation_position"), &AnimationPlayer::get_current_animation_position); ClassDB::bind_method(D_METHOD("get_current_animation_length"), &AnimationPlayer::get_current_animation_length); - ClassDB::bind_method(D_METHOD("seek", "seconds", "update"), &AnimationPlayer::seek, DEFVAL(false)); - ClassDB::bind_method(D_METHOD("advance", "delta"), &AnimationPlayer::advance); + ClassDB::bind_method(D_METHOD("seek", "seconds", "update", "update_only"), &AnimationPlayer::seek, DEFVAL(false), DEFVAL(false)); - GDVIRTUAL_BIND(_post_process_key_value, "animation", "track", "value", "object", "object_idx"); - - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_node"), "set_root", "get_root"); ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "current_animation", PROPERTY_HINT_ENUM, "", PROPERTY_USAGE_EDITOR), "set_current_animation", "get_current_animation"); ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "assigned_animation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_assigned_animation", "get_assigned_animation"); ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "autoplay", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_autoplay", "get_autoplay"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "reset_on_save", PROPERTY_HINT_NONE, ""), "set_reset_on_save_enabled", "is_reset_on_save_enabled"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "current_animation_length", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "", "get_current_animation_length"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "current_animation_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "", "get_current_animation_position"); ADD_GROUP("Playback Options", "playback_"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "playback_process_mode", PROPERTY_HINT_ENUM, "Physics,Idle,Manual"), "set_process_callback", "get_process_callback"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "playback_default_blend_time", PROPERTY_HINT_RANGE, "0,4096,0.01,suffix:s"), "set_default_blend_time", "get_default_blend_time"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playback_active", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_active", "is_active"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed_scale", PROPERTY_HINT_RANGE, "-64,64,0.01"), "set_speed_scale", "get_speed_scale"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "method_call_mode", PROPERTY_HINT_ENUM, "Deferred,Immediate"), "set_method_call_mode", "get_method_call_mode"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "audio_max_polyphony", PROPERTY_HINT_RANGE, "1,127,1"), "set_audio_max_polyphony", "get_audio_max_polyphony"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed_scale", PROPERTY_HINT_RANGE, "-64,64,0.01"), "set_speed_scale", "get_speed_scale"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "movie_quit_on_finish"), "set_movie_quit_on_finish_enabled", "is_movie_quit_on_finish_enabled"); - ADD_SIGNAL(MethodInfo("animation_finished", PropertyInfo(Variant::STRING_NAME, "anim_name"))); - ADD_SIGNAL(MethodInfo("animation_changed", PropertyInfo(Variant::STRING_NAME, "old_name"), PropertyInfo(Variant::STRING_NAME, "new_name"))); - ADD_SIGNAL(MethodInfo("animation_started", PropertyInfo(Variant::STRING_NAME, "anim_name"))); - ADD_SIGNAL(MethodInfo("animation_list_changed")); - ADD_SIGNAL(MethodInfo("animation_libraries_updated")); - ADD_SIGNAL(MethodInfo("caches_cleared")); - - BIND_ENUM_CONSTANT(ANIMATION_PROCESS_PHYSICS); - BIND_ENUM_CONSTANT(ANIMATION_PROCESS_IDLE); - BIND_ENUM_CONSTANT(ANIMATION_PROCESS_MANUAL); - - BIND_ENUM_CONSTANT(ANIMATION_METHOD_CALL_DEFERRED); - BIND_ENUM_CONSTANT(ANIMATION_METHOD_CALL_IMMEDIATE); + ADD_SIGNAL(MethodInfo(SNAME("current_animation_changed"), PropertyInfo(Variant::STRING, "name"))); + ADD_SIGNAL(MethodInfo(SNAME("animation_changed"), PropertyInfo(Variant::STRING_NAME, "old_name"), PropertyInfo(Variant::STRING_NAME, "new_name"))); } AnimationPlayer::AnimationPlayer() { - root = SceneStringNames::get_singleton()->path_pp; } AnimationPlayer::~AnimationPlayer() { diff --git a/scene/animation/animation_player.h b/scene/animation/animation_player.h index b0975fbead0..51beb672602 100644 --- a/scene/animation/animation_player.h +++ b/scene/animation/animation_player.h @@ -31,211 +31,52 @@ #ifndef ANIMATION_PLAYER_H #define ANIMATION_PLAYER_H +#include "animation_mixer.h" #include "scene/2d/node_2d.h" -#include "scene/3d/mesh_instance_3d.h" -#include "scene/3d/node_3d.h" -#include "scene/3d/skeleton_3d.h" #include "scene/resources/animation.h" -#include "scene/resources/animation_library.h" -#include "scene/resources/audio_stream_polyphonic.h" -#ifdef TOOLS_ENABLED -class AnimatedValuesBackup : public RefCounted { - GDCLASS(AnimatedValuesBackup, RefCounted); - - struct Entry { - Object *object = nullptr; - Vector subpath; // Unused if bone - int bone_idx = -1; // -1 if not a bone - Variant value; - }; - Vector entries; - - friend class AnimationPlayer; - -protected: - static void _bind_methods(); - -public: - void update_skeletons(); - void restore() const; -}; -#endif - -class AnimationPlayer : public Node { - GDCLASS(AnimationPlayer, Node); +class AnimationPlayer : public AnimationMixer { + GDCLASS(AnimationPlayer, AnimationMixer); +#ifndef DISABLE_DEPRECATED public: enum AnimationProcessCallback { ANIMATION_PROCESS_PHYSICS, ANIMATION_PROCESS_IDLE, ANIMATION_PROCESS_MANUAL, }; - enum AnimationMethodCallMode { ANIMATION_METHOD_CALL_DEFERRED, ANIMATION_METHOD_CALL_IMMEDIATE, }; +#endif // DISABLE_DEPRECATED private: - enum { - NODE_CACHE_UPDATE_MAX = 1024, - BLEND_FROM_MAX = 3 - }; + HashMap animation_next_set; // For auto advance. - enum SpecialProperty { - SP_NONE, - SP_NODE2D_POS, - SP_NODE2D_ROT, - SP_NODE2D_SCALE, - }; - - uint32_t setup_pass = 1; - - struct TrackNodeCache { - NodePath path; - uint32_t id = 0; - Ref resource; - Node *node = nullptr; - Node2D *node_2d = nullptr; -#ifndef _3D_DISABLED - Node3D *node_3d = nullptr; - Skeleton3D *skeleton = nullptr; - MeshInstance3D *node_blend_shape = nullptr; - int blend_shape_idx = -1; -#endif // _3D_DISABLED - int bone_idx = -1; - // accumulated transforms - - 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_accum; - Quaternion rot_accum; - Vector3 scale_accum; - float blend_shape_accum = 0; - uint64_t accum_pass = 0; - - bool audio_playing = false; - float audio_start = 0.0; - float audio_len = 0.0; - - bool animation_playing = false; - - struct PropertyAnim { - TrackNodeCache *owner = nullptr; - SpecialProperty special = SP_NONE; //small optimization - Vector subpath; - Object *object = nullptr; - Variant value_accum; - uint64_t accum_pass = 0; - Variant capture; - }; - - HashMap property_anim; - - struct BezierAnim { - Vector bezier_property; - TrackNodeCache *owner = nullptr; - float bezier_accum = 0.0; - Object *object = nullptr; - uint64_t accum_pass = 0; - }; - - HashMap bezier_anim; - - struct PlayingAudioStreamInfo { - AudioStreamPlaybackPolyphonic::ID index = -1; - double start = 0.0; - double len = 0.0; - }; - - struct AudioAnim { - Ref audio_stream; - Ref audio_stream_playback; - HashMap playing_streams; - Object *object = nullptr; - uint64_t accum_pass = 0; - double length = 0.0; - double time = 0.0; - bool loop = false; - bool backward = false; - }; - - HashMap audio_anim; - - uint32_t last_setup_pass = 0; - TrackNodeCache() {} - }; - - struct TrackNodeCacheKey { - ObjectID id; - int bone_idx = -1; - int blend_shape_idx = -1; - - static uint32_t hash(const TrackNodeCacheKey &p_key) { - uint32_t h = hash_one_uint64(p_key.id); - h = hash_murmur3_one_32(p_key.bone_idx, h); - return hash_fmix32(hash_murmur3_one_32(p_key.blend_shape_idx, h)); - } - - inline bool operator==(const TrackNodeCacheKey &p_right) const { - return id == p_right.id && bone_idx == p_right.bone_idx && blend_shape_idx == p_right.blend_shape_idx; - } - - inline bool operator<(const TrackNodeCacheKey &p_right) const { - if (id == p_right.id) { - if (blend_shape_idx == p_right.blend_shape_idx) { - return bone_idx < p_right.bone_idx; - } else { - return blend_shape_idx < p_right.blend_shape_idx; - } - } else { - return id < p_right.id; - } - } - }; - - HashMap node_cache_map; - - TrackNodeCache *cache_update[NODE_CACHE_UPDATE_MAX]; - int cache_update_size = 0; - TrackNodeCache::PropertyAnim *cache_update_prop[NODE_CACHE_UPDATE_MAX]; - int cache_update_prop_size = 0; - TrackNodeCache::BezierAnim *cache_update_bezier[NODE_CACHE_UPDATE_MAX]; - int cache_update_bezier_size = 0; - TrackNodeCache::AudioAnim *cache_update_audio[NODE_CACHE_UPDATE_MAX]; - int cache_update_audio_size = 0; - HashSet playing_caches; - Vector playing_audio_stream_players; - - uint64_t accum_pass = 1; float speed_scale = 1.0; double default_blend_time = 0.0; bool is_stopping = false; - struct AnimationData { - String name; - StringName next; - Vector node_cache; - Ref animation; - StringName animation_library; - uint64_t last_update = 0; + struct PlaybackData { + AnimationData *from = nullptr; + double pos = 0.0; + float speed_scale = 1.0; }; - HashMap animation_set; - - struct AnimationLibraryData { - StringName name; - Ref library; - bool operator<(const AnimationLibraryData &p_data) const { return name.operator String() < p_data.name.operator String(); } + struct Blend { + PlaybackData data; + double blend_time = 0.0; + double blend_left = 0.0; }; - LocalVector animation_libraries; + struct Playback { + PlaybackData current; + StringName assigned; + bool seeked = false; + bool started = false; + List blend; + } playback; struct BlendKey { StringName from; @@ -257,117 +98,57 @@ private: HashMap blend_times; - struct PlaybackData { - AnimationData *from = nullptr; - double pos = 0.0; - float speed_scale = 1.0; - }; - - struct Blend { - PlaybackData data; - - double blend_time = 0.0; - double blend_left = 0.0; - }; - - struct Playback { - List blend; - PlaybackData current; - StringName assigned; - bool seeked = false; - bool started = false; - } playback; - - List queued; - + List playback_queue; + ObjectID tmp_from; bool end_reached = false; bool end_notify = false; - String autoplay; + StringName autoplay; + bool reset_on_save = true; - AnimationProcessCallback process_callback = ANIMATION_PROCESS_IDLE; - AnimationMethodCallMode method_call_mode = ANIMATION_METHOD_CALL_DEFERRED; - int audio_max_polyphony = 32; bool movie_quit_on_finish = false; - bool processing = false; - bool active = true; - NodePath root; - - void _animation_process_animation(AnimationData *p_anim, double p_prev_time, double p_time, double p_delta, float p_interp, bool p_is_current = true, bool p_seeked = false, bool p_started = false, Animation::LoopedFlag p_looped_flag = Animation::LOOPED_FLAG_NONE); - - void _ensure_node_caches(AnimationData *p_anim, Node *p_root_override = nullptr); - void _animation_process_data(PlaybackData &cd, double p_delta, float p_blend, bool p_seeked, bool p_started); - void _animation_process2(double p_delta, bool p_started); - void _animation_update_transforms(); - void _animation_process(double p_delta); - - void _node_removed(Node *p_node); - void _clear_audio_streams(); - void _stop_playing_caches(bool p_reset); - - // bind helpers - Vector _get_animation_list() const { - List animations; - get_animation_list(&animations); - Vector ret; - while (animations.size()) { - ret.push_back(animations.front()->get()); - animations.pop_front(); - } - return ret; - } - - void _animation_changed(const StringName &p_name); - - void _set_process(bool p_process, bool p_force = false); + void _process_playback_data(PlaybackData &cd, double p_delta, float p_blend, bool p_seeked, bool p_started, bool p_is_current = false); + void _blend_playback_data(double p_delta, bool p_started); void _stop_internal(bool p_reset, bool p_keep_state); bool playing = false; - uint64_t animation_set_update_pass = 1; - void _animation_set_cache_update(); - void _animation_added(const StringName &p_name, const StringName &p_library); - void _animation_removed(const StringName &p_name, const StringName &p_library); - void _animation_renamed(const StringName &p_name, const StringName &p_to_name, const StringName &p_library); - void _rename_animation(const StringName &p_from_name, const StringName &p_to_name); - - TypedArray _get_animation_library_list() const; - protected: bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; - void _validate_property(PropertyInfo &p_property) const; + virtual void _validate_property(PropertyInfo &p_property) const override; void _get_property_list(List *p_list) const; void _notification(int p_what); static void _bind_methods(); - GDVIRTUAL5RC(Variant, _post_process_key_value, Ref, int, Variant, Object *, int); - Variant post_process_key_value(const Ref &p_anim, int p_track, Variant p_value, const Object *p_object, int p_object_idx = -1); - virtual Variant _post_process_key_value(const Ref &p_anim, int p_track, Variant p_value, const Object *p_object, int p_object_idx = -1); + // Make animation instances. + virtual bool _blend_pre_process(double p_delta, int p_track_count, const HashMap &p_track_map) override; + virtual void _blend_post_process() override; + + virtual void _animation_removed(const StringName &p_name, const StringName &p_library) override; + virtual void _rename_animation(const StringName &p_from_name, const StringName &p_to_name) override; + +#ifndef DISABLE_DEPRECATED + void _set_process_callback_bind_compat_80813(AnimationProcessCallback p_mode); + AnimationProcessCallback _get_process_callback_bind_compat_80813() const; + void _set_method_call_mode_bind_compat_80813(AnimationMethodCallMode p_mode); + AnimationMethodCallMode _get_method_call_mode_bind_compat_80813() const; + void _set_root_bind_compat_80813(const NodePath &p_root); + NodePath _get_root_bind_compat_80813() const; + void _seek_bind_compat_80813(double p_time, bool p_update = false); + + static void _bind_compatibility_methods(); +#endif // DISABLE_DEPRECATED public: - StringName find_animation(const Ref &p_animation) const; - StringName find_animation_library(const Ref &p_animation) const; - - Error add_animation_library(const StringName &p_name, const Ref &p_animation_library); - void remove_animation_library(const StringName &p_name); - void rename_animation_library(const StringName &p_name, const StringName &p_new_name); - Ref get_animation_library(const StringName &p_name) const; - void get_animation_library_list(List *p_animations) const; - bool has_animation_library(const StringName &p_name) const; - - Ref get_animation(const StringName &p_name) const; - void get_animation_list(List *p_animations) const; - bool has_animation(const StringName &p_name) const; + void animation_set_next(const StringName &p_animation, const StringName &p_next); + StringName animation_get_next(const StringName &p_animation) const; void set_blend_time(const StringName &p_animation1, const StringName &p_animation2, double p_time); double get_blend_time(const StringName &p_animation1, const StringName &p_animation2) const; - void animation_set_next(const StringName &p_animation, const StringName &p_next); - StringName animation_get_next(const StringName &p_animation) const; - void set_default_blend_time(double p_default); double get_default_blend_time() const; @@ -380,11 +161,9 @@ public: void stop(bool p_keep_state = false); bool is_playing() const; String get_current_animation() const; - void set_current_animation(const String &p_anim); + void set_current_animation(const String &p_animation); String get_assigned_animation() const; - void set_assigned_animation(const String &p_anim); - void set_active(bool p_active); - bool is_active() const; + void set_assigned_animation(const String &p_animation); bool is_valid() const; void set_speed_scale(float p_speed); @@ -394,46 +173,23 @@ public: void set_autoplay(const String &p_name); String get_autoplay() const; - void set_reset_on_save_enabled(bool p_enabled); - bool is_reset_on_save_enabled() const; - - void set_process_callback(AnimationProcessCallback p_mode); - AnimationProcessCallback get_process_callback() const; - - void set_method_call_mode(AnimationMethodCallMode p_mode); - AnimationMethodCallMode get_method_call_mode() const; - - void set_audio_max_polyphony(int p_audio_max_polyphony); - int get_audio_max_polyphony() const; - void set_movie_quit_on_finish_enabled(bool p_enabled); bool is_movie_quit_on_finish_enabled() const; - void seek(double p_time, bool p_update = false); - void seek_delta(double p_time, double p_delta); + void seek(double p_time, bool p_update = false, bool p_update_only = false); + double get_current_animation_position() const; double get_current_animation_length() const; - void advance(double p_time); - - void set_root(const NodePath &p_root); - NodePath get_root() const; - - void clear_caches(); ///< must be called by hand if an animation was modified after added - void get_argument_options(const StringName &p_function, int p_idx, List *r_options) const override; -#ifdef TOOLS_ENABLED - Ref backup_animated_values(Node *p_root_override = nullptr); - Ref apply_reset(bool p_user_initiated = false); - bool can_apply_reset() const; -#endif - AnimationPlayer(); ~AnimationPlayer(); }; +#ifndef DISABLE_DEPRECATED VARIANT_ENUM_CAST(AnimationPlayer::AnimationProcessCallback); VARIANT_ENUM_CAST(AnimationPlayer::AnimationMethodCallMode); +#endif // DISABLE_DEPRECATED #endif // ANIMATION_PLAYER_H diff --git a/scene/animation/animation_tree.compat.inc b/scene/animation/animation_tree.compat.inc new file mode 100644 index 00000000000..29635f5b523 --- /dev/null +++ b/scene/animation/animation_tree.compat.inc @@ -0,0 +1,64 @@ +/**************************************************************************/ +/* animation_tree.compat.inc */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef DISABLE_DEPRECATED + +void AnimationTree::_set_process_callback_bind_compat_80813(AnimationTree::AnimationProcessCallback p_mode) { + set_callback_mode_process(static_cast(static_cast(p_mode))); +} + +AnimationTree::AnimationProcessCallback AnimationTree::_get_process_callback_bind_compat_80813() const { + return static_cast(static_cast(get_callback_mode_process())); +} + +void AnimationTree::_set_tree_root_bind_compat_80813(const Ref &p_root) { + const Ref rn = Ref(p_root.ptr()); + if (rn.is_null()) { + return; + } + return (set_root_animation_node(rn)); +} + +Ref AnimationTree::_get_tree_root_bind_compat_80813() const { + const Ref rn = Ref(get_root_animation_node().ptr()); + return rn; +} + +void AnimationTree::_bind_compatibility_methods() { + ClassDB::bind_method(D_METHOD("set_process_callback", "mode"), &AnimationTree::_set_process_callback_bind_compat_80813); + ClassDB::bind_method(D_METHOD("get_process_callback"), &AnimationTree::_get_process_callback_bind_compat_80813); + ClassDB::bind_compatibility_method(D_METHOD("set_tree_root", "root"), &AnimationTree::_set_tree_root_bind_compat_80813); + ClassDB::bind_compatibility_method(D_METHOD("get_tree_root"), &AnimationTree::_get_tree_root_bind_compat_80813); + BIND_ENUM_CONSTANT(ANIMATION_PROCESS_PHYSICS); + BIND_ENUM_CONSTANT(ANIMATION_PROCESS_IDLE); + BIND_ENUM_CONSTANT(ANIMATION_PROCESS_MANUAL); +} + +#endif // DISABLE_DEPRECATED diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp index df6e4fed73e..bdadc4ecac5 100644 --- a/scene/animation/animation_tree.cpp +++ b/scene/animation/animation_tree.cpp @@ -29,12 +29,11 @@ /**************************************************************************/ #include "animation_tree.h" +#include "animation_tree.compat.inc" #include "animation_blend_tree.h" #include "core/config/engine.h" -#include "scene/resources/animation.h" #include "scene/scene_string_names.h" -#include "servers/audio/audio_stream.h" void AnimationNode::get_parameter_list(List *r_list) const { Array parameters; @@ -61,24 +60,24 @@ bool AnimationNode::is_parameter_read_only(const StringName &p_parameter) const } void AnimationNode::set_parameter(const StringName &p_name, const Variant &p_value) { - if (is_testing) { + ERR_FAIL_NULL(process_state); + if (process_state->is_testing) { return; } - ERR_FAIL_NULL(state); - ERR_FAIL_COND(!state->tree->property_parent_map.has(base_path)); - ERR_FAIL_COND(!state->tree->property_parent_map[base_path].has(p_name)); - StringName path = state->tree->property_parent_map[base_path][p_name]; + ERR_FAIL_COND(!process_state->tree->property_parent_map.has(node_state.base_path)); + ERR_FAIL_COND(!process_state->tree->property_parent_map[node_state.base_path].has(p_name)); + StringName path = process_state->tree->property_parent_map[node_state.base_path][p_name]; - state->tree->property_map[path].first = p_value; + process_state->tree->property_map[path].first = p_value; } Variant AnimationNode::get_parameter(const StringName &p_name) const { - ERR_FAIL_NULL_V(state, Variant()); - ERR_FAIL_COND_V(!state->tree->property_parent_map.has(base_path), Variant()); - ERR_FAIL_COND_V(!state->tree->property_parent_map[base_path].has(p_name), Variant()); + ERR_FAIL_NULL_V(process_state, Variant()); + ERR_FAIL_COND_V(!process_state->tree->property_parent_map.has(node_state.base_path), Variant()); + ERR_FAIL_COND_V(!process_state->tree->property_parent_map[node_state.base_path].has(p_name), Variant()); - StringName path = state->tree->property_parent_map[base_path][p_name]; - return state->tree->property_map[path].first; + StringName path = process_state->tree->property_parent_map[node_state.base_path][p_name]; + return process_state->tree->property_map[path].first; } void AnimationNode::get_child_nodes(List *r_child_nodes) { @@ -95,141 +94,107 @@ void AnimationNode::get_child_nodes(List *r_child_nodes) { } } -void AnimationNode::blend_animation(const StringName &p_animation, double p_time, double p_delta, bool p_seeked, bool p_is_external_seeking, real_t p_blend, Animation::LoopedFlag p_looped_flag) { - ERR_FAIL_NULL(state); - ERR_FAIL_COND(!state->player->has_animation(p_animation)); - - Ref animation = state->player->get_animation(p_animation); - - if (animation.is_null()) { - AnimationNodeBlendTree *btree = Object::cast_to(parent); - if (btree) { - String node_name = btree->get_node_name(Ref(this)); - make_invalid(vformat(RTR("In node '%s', invalid animation: '%s'."), node_name, p_animation)); - } else { - make_invalid(vformat(RTR("Invalid animation: '%s'."), p_animation)); - } - return; - } - - ERR_FAIL_COND(!animation.is_valid()); - - AnimationState anim_state; - anim_state.blend = p_blend; - anim_state.track_blends = blends; - anim_state.delta = p_delta; - anim_state.time = p_time; - anim_state.animation = animation; - anim_state.seeked = p_seeked; - anim_state.looped_flag = p_looped_flag; - anim_state.is_external_seeking = p_is_external_seeking; - - state->animation_states.push_back(anim_state); +void AnimationNode::blend_animation(const StringName &p_animation, AnimationMixer::PlaybackInfo p_playback_info) { + ERR_FAIL_NULL(process_state); + p_playback_info.track_weights = node_state.track_weights; + process_state->tree->make_animation_instance(p_animation, p_playback_info); } -double AnimationNode::_pre_process(const StringName &p_base_path, AnimationNode *p_parent, State *p_state, double p_time, bool p_seek, bool p_is_external_seeking, const Vector &p_connections, bool p_test_only) { - base_path = p_base_path; - parent = p_parent; - connections = p_connections; - state = p_state; - - double t = process(p_time, p_seek, p_is_external_seeking, p_test_only); - - state = nullptr; - parent = nullptr; - base_path = StringName(); - connections.clear(); - +double AnimationNode::_pre_process(ProcessState *p_process_state, AnimationMixer::PlaybackInfo p_playback_info) { + process_state = p_process_state; + double t = process(p_playback_info); + process_state = nullptr; return t; } -AnimationTree *AnimationNode::get_animation_tree() const { - ERR_FAIL_NULL_V(state, nullptr); - return state->tree; -} - void AnimationNode::make_invalid(const String &p_reason) { - ERR_FAIL_NULL(state); - state->valid = false; - if (!state->invalid_reasons.is_empty()) { - state->invalid_reasons += "\n"; + ERR_FAIL_NULL(process_state); + process_state->valid = false; + if (!process_state->invalid_reasons.is_empty()) { + process_state->invalid_reasons += "\n"; } - state->invalid_reasons += String::utf8("• ") + p_reason; + process_state->invalid_reasons += String::utf8("• ") + p_reason; } -double AnimationNode::blend_input(int p_input, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter, bool p_sync, bool p_test_only) { - ERR_FAIL_INDEX_V(p_input, inputs.size(), 0); - ERR_FAIL_NULL_V(state, 0); +AnimationTree *AnimationNode::get_animation_tree() const { + ERR_FAIL_NULL_V(process_state, nullptr); + return process_state->tree; +} - AnimationNodeBlendTree *blend_tree = Object::cast_to(parent); +double AnimationNode::blend_input(int p_input, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter, bool p_sync, bool p_test_only) { + ERR_FAIL_INDEX_V(p_input, inputs.size(), 0); + + AnimationNodeBlendTree *blend_tree = Object::cast_to(node_state.parent); ERR_FAIL_NULL_V(blend_tree, 0); - StringName node_name = connections[p_input]; + // Update connections. + StringName current_name = blend_tree->get_node_name(Ref(this)); + node_state.connections = blend_tree->get_node_connection_array(current_name); + // Get node which is connected input port. + StringName node_name = node_state.connections[p_input]; if (!blend_tree->has_node(node_name)) { - String node_name2 = blend_tree->get_node_name(Ref(this)); - make_invalid(vformat(RTR("Nothing connected to input '%s' of node '%s'."), get_input_name(p_input), node_name2)); + make_invalid(vformat(RTR("Nothing connected to input '%s' of node '%s'."), get_input_name(p_input), current_name)); return 0; } Ref node = blend_tree->get_node(node_name); - //inputs.write[p_input].last_pass = state->last_pass; real_t activity = 0.0; - double ret = _blend_node(node_name, blend_tree->get_node_connection_array(node_name), nullptr, node, p_time, p_seek, p_is_external_seeking, p_blend, p_filter, p_sync, &activity, p_test_only); - - Vector *activity_ptr = state->tree->input_activity_map.getptr(base_path); + Vector *activity_ptr = process_state->tree->input_activity_map.getptr(node_state.base_path); + double ret = _blend_node(node, node_name, nullptr, p_playback_info, p_filter, p_sync, p_test_only, &activity); if (activity_ptr && p_input < activity_ptr->size()) { - activity_ptr->write[p_input].last_pass = state->last_pass; + activity_ptr->write[p_input].last_pass = process_state->last_pass; activity_ptr->write[p_input].activity = activity; } return ret; } -double AnimationNode::blend_node(const StringName &p_sub_path, Ref p_node, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter, bool p_sync, bool p_test_only) { - return _blend_node(p_sub_path, Vector(), this, p_node, p_time, p_seek, p_is_external_seeking, p_blend, p_filter, p_sync, nullptr, p_test_only); +double AnimationNode::blend_node(Ref p_node, const StringName &p_subpath, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter, bool p_sync, bool p_test_only) { + node_state.connections.clear(); + return _blend_node(p_node, p_subpath, this, p_playback_info, p_filter, p_sync, p_test_only, nullptr); } -double AnimationNode::_blend_node(const StringName &p_subpath, const Vector &p_connections, AnimationNode *p_new_parent, Ref p_node, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter, bool p_sync, real_t *r_max, bool p_test_only) { +double AnimationNode::_blend_node(Ref p_node, const StringName &p_subpath, AnimationNode *p_new_parent, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter, bool p_sync, bool p_test_only, real_t *r_activity) { ERR_FAIL_COND_V(!p_node.is_valid(), 0); - ERR_FAIL_NULL_V(state, 0); + ERR_FAIL_NULL_V(process_state, 0); - int blend_count = blends.size(); + int blend_count = node_state.track_weights.size(); - if (p_node->blends.size() != blend_count) { - p_node->blends.resize(blend_count); + if (p_node->node_state.track_weights.size() != blend_count) { + p_node->node_state.track_weights.resize(blend_count); } - real_t *blendw = p_node->blends.ptrw(); - const real_t *blendr = blends.ptr(); + real_t *blendw = p_node->node_state.track_weights.ptrw(); + const real_t *blendr = node_state.track_weights.ptr(); bool any_valid = false; if (has_filter() && is_filter_enabled() && p_filter != FILTER_IGNORE) { for (int i = 0; i < blend_count; i++) { - blendw[i] = 0.0; //all to zero by default + blendw[i] = 0.0; // All to zero by default. } for (const KeyValue &E : filter) { - if (!state->track_map.has(E.key)) { + if (!process_state->track_map.has(E.key)) { continue; } - int idx = state->track_map[E.key]; - blendw[idx] = 1.0; //filtered goes to one + int idx = process_state->track_map[E.key]; + blendw[idx] = 1.0; // Filtered goes to one. } switch (p_filter) { case FILTER_IGNORE: - break; //will not happen anyway + break; // Will not happen anyway. case FILTER_PASS: { - //values filtered pass, the rest don't + // Values filtered pass, the rest don't. for (int i = 0; i < blend_count; i++) { - if (blendw[i] == 0) { //not filtered, does not pass + if (blendw[i] == 0) { // Not filtered, does not pass. continue; } - blendw[i] = blendr[i] * p_blend; + blendw[i] = blendr[i] * p_playback_info.weight; if (!Math::is_zero_approx(blendw[i])) { any_valid = true; } @@ -237,14 +202,14 @@ double AnimationNode::_blend_node(const StringName &p_subpath, const Vector 0) { //filtered, does not pass + if (blendw[i] > 0) { // Filtered, does not pass. continue; } - blendw[i] = blendr[i] * p_blend; + blendw[i] = blendr[i] * p_playback_info.weight; if (!Math::is_zero_approx(blendw[i])) { any_valid = true; } @@ -252,13 +217,13 @@ double AnimationNode::_blend_node(const StringName &p_subpath, const Vectorbase_path) + String(p_subpath) + "/"; + ERR_FAIL_NULL_V(node_state.parent, 0); + new_parent = node_state.parent; + new_path = String(new_parent->node_state.base_path) + String(p_subpath) + "/"; } // This process, which depends on p_sync is needed to process sync correctly in the case of // that a synced AnimationNodeSync exists under the un-synced AnimationNodeSync. - if (!p_seek && !p_sync && !any_valid) { - return p_node->_pre_process(new_path, new_parent, state, 0, p_seek, p_is_external_seeking, p_connections, p_test_only); + p_node->node_state.base_path = new_path; + p_node->node_state.parent = new_parent; + if (!p_playback_info.seeked && !p_sync && !any_valid) { + p_playback_info.time = 0.0; + return p_node->_pre_process(process_state, p_playback_info); } - return p_node->_pre_process(new_path, new_parent, state, p_time, p_seek, p_is_external_seeking, p_connections, p_test_only); + return p_node->_pre_process(process_state, p_playback_info); } String AnimationNode::get_caption() const { @@ -313,7 +281,7 @@ String AnimationNode::get_caption() const { } bool AnimationNode::add_input(const String &p_name) { - //root nodes can't add inputs + // Root nodes can't add inputs. ERR_FAIL_COND_V(Object::cast_to(this) != nullptr, false); Input input; ERR_FAIL_COND_V(p_name.contains(".") || p_name.contains("/"), false); @@ -357,14 +325,14 @@ int AnimationNode::find_input(const String &p_name) const { return idx; } -double AnimationNode::process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) { - is_testing = p_test_only; - return _process(p_time, p_seek, p_is_external_seeking, p_test_only); +double AnimationNode::process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { + process_state->is_testing = p_test_only; + return _process(p_playback_info, p_test_only); } -double AnimationNode::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) { +double AnimationNode::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) { double ret = 0; - GDVIRTUAL_CALL(_process, p_time, p_seek, p_is_external_seeking, p_test_only, ret); + GDVIRTUAL_CALL(_process, p_playback_info.time, p_playback_info.seeked, p_playback_info.is_external_seeking, p_test_only, ret); return ret; } @@ -406,9 +374,9 @@ Array AnimationNode::_get_filters() const { Array paths; for (const KeyValue &E : filter) { - paths.push_back(String(E.key)); //use strings, so sorting is possible + paths.push_back(String(E.key)); // Use strings, so sorting is possible. } - paths.sort(); //done so every time the scene is saved, it does not change + paths.sort(); // Done so every time the scene is saved, it does not change. return paths; } @@ -444,6 +412,35 @@ Ref AnimationNode::find_node_by_path(const String &p_name) const return ret; } +void AnimationNode::blend_animation_ex(const StringName &p_animation, double p_time, double p_delta, bool p_seeked, bool p_is_external_seeking, real_t p_blend, Animation::LoopedFlag p_looped_flag) { + AnimationMixer::PlaybackInfo info; + info.time = p_time; + info.delta = p_delta; + info.seeked = p_seeked; + info.is_external_seeking = p_is_external_seeking; + info.weight = p_blend; + info.looped_flag = p_looped_flag; + blend_animation(p_animation, info); +} + +double AnimationNode::blend_node_ex(const StringName &p_sub_path, Ref p_node, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter, bool p_sync, bool p_test_only) { + AnimationMixer::PlaybackInfo info; + info.time = p_time; + info.seeked = p_seek; + info.is_external_seeking = p_is_external_seeking; + info.weight = p_blend; + return blend_node(p_node, p_sub_path, info, p_filter, p_sync, p_test_only); +} + +double AnimationNode::blend_input_ex(int p_input, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter, bool p_sync, bool p_test_only) { + AnimationMixer::PlaybackInfo info; + info.time = p_time; + info.seeked = p_seek; + info.is_external_seeking = p_is_external_seeking; + info.weight = p_blend; + return blend_input(p_input, info, p_filter, p_sync, p_test_only); +} + void AnimationNode::_bind_methods() { ClassDB::bind_method(D_METHOD("add_input", "name"), &AnimationNode::add_input); ClassDB::bind_method(D_METHOD("remove_input", "index"), &AnimationNode::remove_input); @@ -461,9 +458,9 @@ void AnimationNode::_bind_methods() { ClassDB::bind_method(D_METHOD("_set_filters", "filters"), &AnimationNode::_set_filters); ClassDB::bind_method(D_METHOD("_get_filters"), &AnimationNode::_get_filters); - ClassDB::bind_method(D_METHOD("blend_animation", "animation", "time", "delta", "seeked", "is_external_seeking", "blend", "looped_flag"), &AnimationNode::blend_animation, DEFVAL(Animation::LOOPED_FLAG_NONE)); - ClassDB::bind_method(D_METHOD("blend_node", "name", "node", "time", "seek", "is_external_seeking", "blend", "filter", "sync", "test_only"), &AnimationNode::blend_node, DEFVAL(FILTER_IGNORE), DEFVAL(true), DEFVAL(false)); - ClassDB::bind_method(D_METHOD("blend_input", "input_index", "time", "seek", "is_external_seeking", "blend", "filter", "sync", "test_only"), &AnimationNode::blend_input, DEFVAL(FILTER_IGNORE), DEFVAL(true), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("blend_animation", "animation", "time", "delta", "seeked", "is_external_seeking", "blend", "looped_flag"), &AnimationNode::blend_animation_ex, DEFVAL(Animation::LOOPED_FLAG_NONE)); + ClassDB::bind_method(D_METHOD("blend_node", "name", "node", "time", "seek", "is_external_seeking", "blend", "filter", "sync", "test_only"), &AnimationNode::blend_node_ex, DEFVAL(FILTER_IGNORE), DEFVAL(true), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("blend_input", "input_index", "time", "seek", "is_external_seeking", "blend", "filter", "sync", "test_only"), &AnimationNode::blend_input_ex, DEFVAL(FILTER_IGNORE), DEFVAL(true), DEFVAL(false)); ClassDB::bind_method(D_METHOD("set_parameter", "name", "value"), &AnimationNode::set_parameter); ClassDB::bind_method(D_METHOD("get_parameter", "name"), &AnimationNode::get_parameter); @@ -509,19 +506,19 @@ void AnimationRootNode::_animation_node_removed(const ObjectID &p_oid, const Str //////////////////// -void AnimationTree::set_tree_root(const Ref &p_root) { - if (root.is_valid()) { - root->disconnect("tree_changed", callable_mp(this, &AnimationTree::_tree_changed)); - root->disconnect("animation_node_renamed", callable_mp(this, &AnimationTree::_animation_node_renamed)); - root->disconnect("animation_node_removed", callable_mp(this, &AnimationTree::_animation_node_removed)); +void AnimationTree::set_root_animation_node(const Ref &p_animation_node) { + if (root_animation_node.is_valid()) { + root_animation_node->disconnect(SNAME("tree_changed"), callable_mp(this, &AnimationTree::_tree_changed)); + root_animation_node->disconnect(SNAME("animation_node_renamed"), callable_mp(this, &AnimationTree::_animation_node_renamed)); + root_animation_node->disconnect(SNAME("animation_node_removed"), callable_mp(this, &AnimationTree::_animation_node_removed)); } - root = p_root; + root_animation_node = p_animation_node; - if (root.is_valid()) { - root->connect("tree_changed", callable_mp(this, &AnimationTree::_tree_changed)); - root->connect("animation_node_renamed", callable_mp(this, &AnimationTree::_animation_node_renamed)); - root->connect("animation_node_removed", callable_mp(this, &AnimationTree::_animation_node_removed)); + if (root_animation_node.is_valid()) { + root_animation_node->connect(SNAME("tree_changed"), callable_mp(this, &AnimationTree::_tree_changed)); + root_animation_node->connect(SNAME("animation_node_renamed"), callable_mp(this, &AnimationTree::_animation_node_renamed)); + root_animation_node->connect(SNAME("animation_node_removed"), callable_mp(this, &AnimationTree::_animation_node_removed)); } properties_dirty = true; @@ -529,1464 +526,79 @@ void AnimationTree::set_tree_root(const Ref &p_root) { update_configuration_warnings(); } -Ref AnimationTree::get_tree_root() const { - return root; +Ref AnimationTree::get_root_animation_node() const { + return root_animation_node; } -void AnimationTree::set_active(bool p_active) { - if (active == p_active) { - return; - } +bool AnimationTree::_blend_pre_process(double p_delta, int p_track_count, const HashMap &p_track_map) { + _update_properties(); // If properties need updating, update them. - active = p_active; - started = active; - - if (process_callback == ANIMATION_PROCESS_IDLE) { - set_process_internal(active); - } else { - set_physics_process_internal(active); - } - - if (!active && is_inside_tree()) { - _clear_caches(); - } -} - -bool AnimationTree::is_active() const { - return active; -} - -void AnimationTree::set_process_callback(AnimationProcessCallback p_mode) { - if (process_callback == p_mode) { - return; - } - - bool was_active = is_active(); - if (was_active) { - set_active(false); - } - - process_callback = p_mode; - - if (was_active) { - set_active(true); - } -} - -AnimationTree::AnimationProcessCallback AnimationTree::get_process_callback() const { - return process_callback; -} - -void AnimationTree::_node_removed(Node *p_node) { - cache_valid = false; -} - -bool AnimationTree::_update_caches(AnimationPlayer *player) { - setup_pass++; - - if (!player->has_node(player->get_root())) { - ERR_PRINT("AnimationTree: AnimationPlayer root is invalid."); - set_active(false); - _clear_caches(); + if (!root_animation_node.is_valid()) { return false; } - Node *parent = player->get_node(player->get_root()); - List sname; - player->get_animation_list(&sname); + { // Setup. + process_pass++; - root_motion_cache.loc = Vector3(0, 0, 0); - root_motion_cache.rot = Quaternion(0, 0, 0, 1); - root_motion_cache.scale = Vector3(1, 1, 1); + // Init process state. + process_state = AnimationNode::ProcessState(); + process_state.tree = this; + process_state.valid = true; + process_state.invalid_reasons = ""; + process_state.last_pass = process_pass; + process_state.track_map = p_track_map; - Ref reset_anim; - bool has_reset_anim = player->has_animation(SceneStringNames::get_singleton()->RESET); - if (has_reset_anim) { - reset_anim = player->get_animation(SceneStringNames::get_singleton()->RESET); + // Init node state for root AnimationNode. + root_animation_node->node_state.track_weights.resize(p_track_count); + real_t *src_blendsw = root_animation_node->node_state.track_weights.ptrw(); + for (int i = 0; i < p_track_count; i++) { + src_blendsw[i] = 1.0; // By default all go to 1 for the root input. + } + root_animation_node->node_state.base_path = SceneStringNames::get_singleton()->parameters_base_path; + root_animation_node->node_state.parent = nullptr; } - for (const StringName &E : sname) { - Ref anim = player->get_animation(E); - for (int i = 0; i < anim->get_track_count(); i++) { - NodePath path = anim->track_get_path(i); - Animation::TrackType track_type = anim->track_get_type(i); - Animation::TrackType track_cache_type = track_type; - if (track_cache_type == Animation::TYPE_POSITION_3D || track_cache_type == Animation::TYPE_ROTATION_3D || track_cache_type == Animation::TYPE_SCALE_3D) { - track_cache_type = Animation::TYPE_POSITION_3D; //reference them as position3D tracks, even if they modify rotation or scale - } + // Process. + { + PlaybackInfo pi; - TrackCache *track = nullptr; - if (track_cache.has(path)) { - track = track_cache.get(path); - } - - //if not valid, delete track - if (track && (track->type != track_cache_type || ObjectDB::get_instance(track->object_id) == nullptr)) { - playing_caches.erase(track); - memdelete(track); - track_cache.erase(path); - track = nullptr; - } - - if (!track) { - Ref resource; - Vector leftover_path; - Node *child = parent->get_node_and_resource(path, resource, leftover_path); - - if (!child) { - ERR_PRINT("AnimationTree: '" + String(E) + "', couldn't resolve track: '" + String(path) + "'"); - continue; - } - - if (!child->is_connected("tree_exited", callable_mp(this, &AnimationTree::_node_removed))) { - child->connect("tree_exited", callable_mp(this, &AnimationTree::_node_removed).bind(child)); - } - - switch (track_type) { - case Animation::TYPE_VALUE: { - TrackCacheValue *track_value = memnew(TrackCacheValue); - - if (resource.is_valid()) { - track_value->object = resource.ptr(); - } else { - track_value->object = child; - } - - track_value->is_discrete = anim->value_track_get_update_mode(i) == Animation::UPDATE_DISCRETE; - track_value->is_using_angle = anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_LINEAR_ANGLE || anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_CUBIC_ANGLE; - - track_value->subpath = leftover_path; - track_value->object_id = track_value->object->get_instance_id(); - - track = track_value; - - // If a value track without a key is cached first, the initial value cannot be determined. - // It is a corner case, but which may cause problems with blending. - ERR_CONTINUE_MSG(anim->track_get_key_count(i) == 0, "AnimationTree: '" + String(E) + "', Value Track: '" + String(path) + "' must have at least one key to cache for blending."); - track_value->init_value = anim->track_get_key_value(i, 0); - track_value->init_value.zero(); - - // If there is a Reset Animation, it takes precedence by overwriting. - if (has_reset_anim) { - int rt = reset_anim->find_track(path, track_type); - if (rt >= 0 && reset_anim->track_get_key_count(rt) > 0) { - track_value->init_value = reset_anim->track_get_key_value(rt, 0); - } - } - } break; - case Animation::TYPE_POSITION_3D: - case Animation::TYPE_ROTATION_3D: - case Animation::TYPE_SCALE_3D: { -#ifndef _3D_DISABLED - Node3D *node_3d = Object::cast_to(child); - - if (!node_3d) { - ERR_PRINT("AnimationTree: '" + String(E) + "', transform track does not point to Node3D: '" + String(path) + "'"); - continue; - } - - TrackCacheTransform *track_xform = memnew(TrackCacheTransform); - track_xform->type = Animation::TYPE_POSITION_3D; - - track_xform->node_3d = node_3d; - track_xform->skeleton = nullptr; - track_xform->bone_idx = -1; - - bool has_rest = false; - if (path.get_subname_count() == 1 && Object::cast_to(node_3d)) { - Skeleton3D *sk = Object::cast_to(node_3d); - track_xform->skeleton = sk; - int bone_idx = sk->find_bone(path.get_subname(0)); - if (bone_idx != -1) { - has_rest = true; - track_xform->bone_idx = bone_idx; - Transform3D rest = sk->get_bone_rest(bone_idx); - track_xform->init_loc = rest.origin; - track_xform->init_rot = rest.basis.get_rotation_quaternion(); - track_xform->init_scale = rest.basis.get_scale(); - } - } - - track_xform->object = node_3d; - track_xform->object_id = track_xform->object->get_instance_id(); - - track = track_xform; - - switch (track_type) { - case Animation::TYPE_POSITION_3D: { - track_xform->loc_used = true; - } break; - case Animation::TYPE_ROTATION_3D: { - track_xform->rot_used = true; - } break; - case Animation::TYPE_SCALE_3D: { - track_xform->scale_used = true; - } break; - default: { - } - } - - // For non Skeleton3D bone animation. - if (has_reset_anim && !has_rest) { - int rt = reset_anim->find_track(path, track_type); - if (rt >= 0 && reset_anim->track_get_key_count(rt) > 0) { - switch (track_type) { - case Animation::TYPE_POSITION_3D: { - track_xform->init_loc = reset_anim->track_get_key_value(rt, 0); - } break; - case Animation::TYPE_ROTATION_3D: { - track_xform->init_rot = reset_anim->track_get_key_value(rt, 0); - } break; - case Animation::TYPE_SCALE_3D: { - track_xform->init_scale = reset_anim->track_get_key_value(rt, 0); - } break; - default: { - } - } - } - } -#endif // _3D_DISABLED - } break; - case Animation::TYPE_BLEND_SHAPE: { -#ifndef _3D_DISABLED - if (path.get_subname_count() != 1) { - ERR_PRINT("AnimationTree: '" + String(E) + "', blend shape track does not contain a blend shape subname: '" + String(path) + "'"); - continue; - } - MeshInstance3D *mesh_3d = Object::cast_to(child); - - if (!mesh_3d) { - ERR_PRINT("AnimationTree: '" + String(E) + "', blend shape track does not point to MeshInstance3D: '" + String(path) + "'"); - continue; - } - - StringName blend_shape_name = path.get_subname(0); - int blend_shape_idx = mesh_3d->find_blend_shape_by_name(blend_shape_name); - if (blend_shape_idx == -1) { - ERR_PRINT("AnimationTree: '" + String(E) + "', blend shape track points to a non-existing name: '" + String(blend_shape_name) + "'"); - continue; - } - - TrackCacheBlendShape *track_bshape = memnew(TrackCacheBlendShape); - - track_bshape->mesh_3d = mesh_3d; - track_bshape->shape_index = blend_shape_idx; - - track_bshape->object = mesh_3d; - track_bshape->object_id = mesh_3d->get_instance_id(); - track = track_bshape; - - if (has_reset_anim) { - int rt = reset_anim->find_track(path, track_type); - if (rt >= 0 && reset_anim->track_get_key_count(rt) > 0) { - track_bshape->init_value = reset_anim->track_get_key_value(rt, 0); - } - } -#endif - } break; - case Animation::TYPE_METHOD: { - TrackCacheMethod *track_method = memnew(TrackCacheMethod); - - if (resource.is_valid()) { - track_method->object = resource.ptr(); - } else { - track_method->object = child; - } - - track_method->object_id = track_method->object->get_instance_id(); - - track = track_method; - - } break; - case Animation::TYPE_BEZIER: { - TrackCacheBezier *track_bezier = memnew(TrackCacheBezier); - - if (resource.is_valid()) { - track_bezier->object = resource.ptr(); - } else { - track_bezier->object = child; - } - - track_bezier->subpath = leftover_path; - track_bezier->object_id = track_bezier->object->get_instance_id(); - - track = track_bezier; - - if (has_reset_anim) { - int rt = reset_anim->find_track(path, track_type); - if (rt >= 0 && reset_anim->track_get_key_count(rt) > 0) { - track_bezier->init_value = (reset_anim->track_get_key_value(rt, 0).operator Array())[0]; - } - } - } break; - case Animation::TYPE_AUDIO: { - TrackCacheAudio *track_audio = memnew(TrackCacheAudio); - - track_audio->object = child; - track_audio->object_id = track_audio->object->get_instance_id(); - track_audio->audio_stream.instantiate(); - track_audio->audio_stream->set_polyphony(audio_max_polyphony); - - track = track_audio; - - } break; - case Animation::TYPE_ANIMATION: { - TrackCacheAnimation *track_animation = memnew(TrackCacheAnimation); - - track_animation->object = child; - track_animation->object_id = track_animation->object->get_instance_id(); - - track = track_animation; - - } break; - default: { - ERR_PRINT("Animation corrupted (invalid track type)"); - continue; - } - } - - track_cache[path] = track; - } else if (track_cache_type == Animation::TYPE_POSITION_3D) { - TrackCacheTransform *track_xform = static_cast(track); - if (track->setup_pass != setup_pass) { - track_xform->loc_used = false; - track_xform->rot_used = false; - track_xform->scale_used = false; - } - switch (track_type) { - case Animation::TYPE_POSITION_3D: { - track_xform->loc_used = true; - } break; - case Animation::TYPE_ROTATION_3D: { - track_xform->rot_used = true; - } break; - case Animation::TYPE_SCALE_3D: { - track_xform->scale_used = true; - } break; - default: { - } - } - } else if (track_cache_type == Animation::TYPE_VALUE) { - // If it has at least one angle interpolation, it also uses angle interpolation for blending. - TrackCacheValue *track_value = static_cast(track); - bool was_discrete = track_value->is_discrete; - bool was_using_angle = track_value->is_using_angle; - track_value->is_discrete |= anim->value_track_get_update_mode(i) == Animation::UPDATE_DISCRETE; - track_value->is_using_angle |= anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_LINEAR_ANGLE || anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_CUBIC_ANGLE; - - if (was_discrete != track_value->is_discrete) { - ERR_PRINT_ED("Value Track: " + String(path) + " with different update modes are blended. Blending prioritizes Discrete mode, so other update mode tracks will not be blended."); - } - if (was_using_angle != track_value->is_using_angle) { - WARN_PRINT_ED("Value Track: " + String(path) + " with different interpolation types for rotation are blended. Blending prioritizes angle interpolation, so the blending result uses the shortest path referenced to the initial (RESET animation) value."); - } - } - - track->setup_pass = setup_pass; + if (started) { + // If started, seek. + pi.seeked = true; + root_animation_node->_pre_process(&process_state, pi); + started = false; + } else { + pi.time = p_delta; + root_animation_node->_pre_process(&process_state, pi); } } - List to_delete; - - for (const KeyValue &K : track_cache) { - TrackCache *tc = track_cache[K.key]; - if (tc->setup_pass != setup_pass) { - to_delete.push_back(K.key); - } + if (!process_state.valid) { + return false; // State is not valid, abort process. } - while (to_delete.front()) { - NodePath np = to_delete.front()->get(); - memdelete(track_cache[np]); - track_cache.erase(np); - to_delete.pop_front(); - } - - state.track_map.clear(); - - int idx = 0; - for (const KeyValue &K : track_cache) { - state.track_map[K.key] = idx; - idx++; - } - - state.track_count = idx; - - cache_valid = true; - return true; } -void AnimationTree::_animation_player_changed() { - emit_signal(SNAME("animation_player_changed")); - _clear_caches(); +void AnimationTree::_set_active(bool p_active) { + _set_process(p_active); + started = p_active; } -void AnimationTree::_clear_caches() { - _clear_audio_streams(); - _clear_playing_caches(); - for (KeyValue &K : track_cache) { - memdelete(K.value); - } - track_cache.clear(); - cache_valid = false; -} - -void AnimationTree::_clear_audio_streams() { - for (int i = 0; i < playing_audio_stream_players.size(); i++) { - playing_audio_stream_players[i]->call(SNAME("stop")); - playing_audio_stream_players[i]->call(SNAME("set_stream"), Ref()); - } - playing_audio_stream_players.clear(); -} - -void AnimationTree::_clear_playing_caches() { - for (const TrackCache *E : playing_caches) { - if (ObjectDB::get_instance(E->object_id)) { - E->object->call(SNAME("stop")); - } - } - playing_caches.clear(); -} - -void AnimationTree::_call_object(Object *p_object, const StringName &p_method, const Vector &p_params, bool p_deferred) { - // Separate function to use alloca() more efficiently - const Variant **argptrs = (const Variant **)alloca(sizeof(const Variant **) * p_params.size()); - const Variant *args = p_params.ptr(); - uint32_t argcount = p_params.size(); - for (uint32_t i = 0; i < argcount; i++) { - argptrs[i] = &args[i]; - } - if (p_deferred) { - MessageQueue::get_singleton()->push_callp(p_object, p_method, argptrs, argcount); - } else { - Callable::CallError ce; - p_object->callp(p_method, argptrs, argcount, ce); - } -} -void AnimationTree::_process_graph(double p_delta) { - _update_properties(); //if properties need updating, update them - - //check all tracks, see if they need modification - root_motion_position = Vector3(0, 0, 0); - root_motion_rotation = Quaternion(0, 0, 0, 1); - root_motion_scale = Vector3(0, 0, 0); - - if (!root.is_valid()) { - ERR_PRINT("AnimationTree: root AnimationNode is not set, disabling playback."); - set_active(false); - cache_valid = false; - return; - } - - if (!has_node(animation_player)) { - ERR_PRINT("AnimationTree: no valid AnimationPlayer path set, disabling playback"); - set_active(false); - cache_valid = false; - return; - } - - AnimationPlayer *player = Object::cast_to(get_node(animation_player)); - - ObjectID current_animation_player; - - if (player) { - current_animation_player = player->get_instance_id(); - } - - if (last_animation_player != current_animation_player) { - if (last_animation_player.is_valid()) { - Object *old_player = ObjectDB::get_instance(last_animation_player); - if (old_player) { - old_player->disconnect("caches_cleared", callable_mp(this, &AnimationTree::_clear_caches)); - } - } - - if (player) { - player->connect("caches_cleared", callable_mp(this, &AnimationTree::_clear_caches)); - } - - last_animation_player = current_animation_player; - } - - if (!player) { - ERR_PRINT("AnimationTree: path points to a node not an AnimationPlayer, disabling playback"); - set_active(false); - cache_valid = false; - return; - } - - if (!cache_valid) { - if (!_update_caches(player)) { - return; - } - } - - { //setup - - process_pass++; - - state.valid = true; - state.invalid_reasons = ""; - state.animation_states.clear(); //will need to be re-created - state.player = player; - state.last_pass = process_pass; - state.tree = this; - - // root source blends - - root->blends.resize(state.track_count); - real_t *src_blendsw = root->blends.ptrw(); - for (int i = 0; i < state.track_count; i++) { - src_blendsw[i] = 1.0; //by default all go to 1 for the root input - } - } - - //process - - { - if (started) { - //if started, seek - root->_pre_process(SceneStringNames::get_singleton()->parameters_base_path, nullptr, &state, 0, true, false, Vector()); - started = false; - } - - root->_pre_process(SceneStringNames::get_singleton()->parameters_base_path, nullptr, &state, p_delta, false, false, Vector()); - } - - if (!state.valid) { - return; //state is not valid. do nothing. - } - - // Init all value/transform/blend/bezier tracks that track_cache has. - { - for (const KeyValue &K : track_cache) { - TrackCache *track = K.value; - - switch (track->type) { - case Animation::TYPE_POSITION_3D: { - TrackCacheTransform *t = static_cast(track); - if (track->root_motion) { - root_motion_cache.loc = Vector3(0, 0, 0); - root_motion_cache.rot = Quaternion(0, 0, 0, 1); - root_motion_cache.scale = Vector3(1, 1, 1); - } - t->loc = t->init_loc; - t->rot = t->init_rot; - t->scale = t->init_scale; - } break; - case Animation::TYPE_BLEND_SHAPE: { - TrackCacheBlendShape *t = static_cast(track); - t->value = t->init_value; - } break; - case Animation::TYPE_VALUE: { - TrackCacheValue *t = static_cast(track); - t->value = t->init_value; - } break; - case Animation::TYPE_BEZIER: { - TrackCacheBezier *t = static_cast(track); - t->value = t->init_value; - } break; - case Animation::TYPE_AUDIO: { - TrackCacheAudio *t = static_cast(track); - for (KeyValue &L : t->playing_streams) { - PlayingAudioTrackInfo &track_info = L.value; - track_info.volume = 0.0; - } - } break; - default: { - } break; - } - } - } - - // Apply value/transform/blend/bezier blends to track caches and execute method/audio/animation tracks. - { -#ifdef TOOLS_ENABLED - bool can_call = is_inside_tree() && !Engine::get_singleton()->is_editor_hint(); -#endif // TOOLS_ENABLED - for (const AnimationNode::AnimationState &as : state.animation_states) { - Ref a = as.animation; - double time = as.time; - double delta = as.delta; - real_t weight = as.blend; - bool seeked = as.seeked; - Animation::LoopedFlag looped_flag = as.looped_flag; - bool is_external_seeking = as.is_external_seeking; - bool backward = signbit(delta); // This flag is used by the root motion calculates or detecting the end of audio stream. -#ifndef _3D_DISABLED - bool calc_root = !seeked || is_external_seeking; -#endif // _3D_DISABLED - - for (int i = 0; i < a->get_track_count(); i++) { - if (!a->track_is_enabled(i)) { - continue; - } - - NodePath path = a->track_get_path(i); - if (!track_cache.has(path)) { - continue; // No path, but avoid error spamming. - } - TrackCache *track = track_cache[path]; - - ERR_CONTINUE(!state.track_map.has(path)); - int blend_idx = state.track_map[path]; - ERR_CONTINUE(blend_idx < 0 || blend_idx >= state.track_count); - real_t blend = (as.track_blends)[blend_idx] * weight; - - Animation::TrackType ttype = a->track_get_type(i); - if (ttype != Animation::TYPE_POSITION_3D && ttype != Animation::TYPE_ROTATION_3D && ttype != Animation::TYPE_SCALE_3D && track->type != ttype) { - //broken animation, but avoid error spamming - continue; - } - track->root_motion = root_motion_track == path; - - switch (ttype) { - case Animation::TYPE_POSITION_3D: { -#ifndef _3D_DISABLED - if (Math::is_zero_approx(blend)) { - continue; // Nothing to blend. - } - TrackCacheTransform *t = static_cast(track); - - if (track->root_motion && calc_root) { - double prev_time = time - delta; - if (!backward) { - if (prev_time < 0) { - switch (a->get_loop_mode()) { - case Animation::LOOP_NONE: { - prev_time = 0; - } break; - case Animation::LOOP_LINEAR: { - prev_time = Math::fposmod(prev_time, (double)a->get_length()); - } break; - case Animation::LOOP_PINGPONG: { - prev_time = Math::pingpong(prev_time, (double)a->get_length()); - } break; - default: - break; - } - } - } else { - if (prev_time > a->get_length()) { - switch (a->get_loop_mode()) { - case Animation::LOOP_NONE: { - prev_time = (double)a->get_length(); - } break; - case Animation::LOOP_LINEAR: { - prev_time = Math::fposmod(prev_time, (double)a->get_length()); - } break; - case Animation::LOOP_PINGPONG: { - prev_time = Math::pingpong(prev_time, (double)a->get_length()); - } break; - default: - break; - } - } - } - - Vector3 loc[2]; - - if (!backward) { - if (prev_time > time) { - Error err = a->try_position_track_interpolate(i, prev_time, &loc[0]); - if (err != OK) { - continue; - } - loc[0] = post_process_key_value(a, i, loc[0], t->object, t->bone_idx); - a->try_position_track_interpolate(i, (double)a->get_length(), &loc[1]); - loc[1] = post_process_key_value(a, i, loc[1], t->object, t->bone_idx); - root_motion_cache.loc += (loc[1] - loc[0]) * blend; - prev_time = 0; - } - } else { - if (prev_time < time) { - Error err = a->try_position_track_interpolate(i, prev_time, &loc[0]); - if (err != OK) { - continue; - } - loc[0] = post_process_key_value(a, i, loc[0], t->object, t->bone_idx); - a->try_position_track_interpolate(i, 0, &loc[1]); - loc[1] = post_process_key_value(a, i, loc[1], t->object, t->bone_idx); - root_motion_cache.loc += (loc[1] - loc[0]) * blend; - prev_time = (double)a->get_length(); - } - } - - Error err = a->try_position_track_interpolate(i, prev_time, &loc[0]); - if (err != OK) { - continue; - } - loc[0] = post_process_key_value(a, i, loc[0], t->object, t->bone_idx); - a->try_position_track_interpolate(i, time, &loc[1]); - loc[1] = post_process_key_value(a, i, loc[1], t->object, t->bone_idx); - root_motion_cache.loc += (loc[1] - loc[0]) * blend; - prev_time = !backward ? 0 : (double)a->get_length(); - } - - { - Vector3 loc; - - Error err = a->try_position_track_interpolate(i, time, &loc); - if (err != OK) { - continue; - } - loc = post_process_key_value(a, i, loc, t->object, t->bone_idx); - - t->loc += (loc - t->init_loc) * blend; - } -#endif // _3D_DISABLED - } break; - case Animation::TYPE_ROTATION_3D: { -#ifndef _3D_DISABLED - if (Math::is_zero_approx(blend)) { - continue; // Nothing to blend. - } - TrackCacheTransform *t = static_cast(track); - - if (track->root_motion && calc_root) { - double prev_time = time - delta; - if (!backward) { - if (prev_time < 0) { - switch (a->get_loop_mode()) { - case Animation::LOOP_NONE: { - prev_time = 0; - } break; - case Animation::LOOP_LINEAR: { - prev_time = Math::fposmod(prev_time, (double)a->get_length()); - } break; - case Animation::LOOP_PINGPONG: { - prev_time = Math::pingpong(prev_time, (double)a->get_length()); - } break; - default: - break; - } - } - } else { - if (prev_time > a->get_length()) { - switch (a->get_loop_mode()) { - case Animation::LOOP_NONE: { - prev_time = (double)a->get_length(); - } break; - case Animation::LOOP_LINEAR: { - prev_time = Math::fposmod(prev_time, (double)a->get_length()); - } break; - case Animation::LOOP_PINGPONG: { - prev_time = Math::pingpong(prev_time, (double)a->get_length()); - } break; - default: - break; - } - } - } - - Quaternion rot[2]; - - if (!backward) { - if (prev_time > time) { - Error err = a->try_rotation_track_interpolate(i, prev_time, &rot[0]); - if (err != OK) { - continue; - } - rot[0] = post_process_key_value(a, i, rot[0], t->object, t->bone_idx); - a->try_rotation_track_interpolate(i, (double)a->get_length(), &rot[1]); - rot[1] = post_process_key_value(a, i, rot[1], t->object, t->bone_idx); - root_motion_cache.rot = (root_motion_cache.rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized(); - prev_time = 0; - } - } else { - if (prev_time < time) { - Error err = a->try_rotation_track_interpolate(i, prev_time, &rot[0]); - if (err != OK) { - continue; - } - rot[0] = post_process_key_value(a, i, rot[0], t->object, t->bone_idx); - a->try_rotation_track_interpolate(i, 0, &rot[1]); - root_motion_cache.rot = (root_motion_cache.rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized(); - prev_time = (double)a->get_length(); - } - } - - Error err = a->try_rotation_track_interpolate(i, prev_time, &rot[0]); - if (err != OK) { - continue; - } - rot[0] = post_process_key_value(a, i, rot[0], t->object, t->bone_idx); - - a->try_rotation_track_interpolate(i, time, &rot[1]); - rot[1] = post_process_key_value(a, i, rot[1], t->object, t->bone_idx); - root_motion_cache.rot = (root_motion_cache.rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized(); - prev_time = !backward ? 0 : (double)a->get_length(); - } - - { - Quaternion rot; - - Error err = a->try_rotation_track_interpolate(i, time, &rot); - if (err != OK) { - continue; - } - rot = post_process_key_value(a, i, rot, t->object, t->bone_idx); - - t->rot = (t->rot * Quaternion().slerp(t->init_rot.inverse() * rot, blend)).normalized(); - } -#endif // _3D_DISABLED - } break; - case Animation::TYPE_SCALE_3D: { -#ifndef _3D_DISABLED - if (Math::is_zero_approx(blend)) { - continue; // Nothing to blend. - } - TrackCacheTransform *t = static_cast(track); - - if (track->root_motion && calc_root) { - double prev_time = time - delta; - if (!backward) { - if (prev_time < 0) { - switch (a->get_loop_mode()) { - case Animation::LOOP_NONE: { - prev_time = 0; - } break; - case Animation::LOOP_LINEAR: { - prev_time = Math::fposmod(prev_time, (double)a->get_length()); - } break; - case Animation::LOOP_PINGPONG: { - prev_time = Math::pingpong(prev_time, (double)a->get_length()); - } break; - default: - break; - } - } - } else { - if (prev_time > a->get_length()) { - switch (a->get_loop_mode()) { - case Animation::LOOP_NONE: { - prev_time = (double)a->get_length(); - } break; - case Animation::LOOP_LINEAR: { - prev_time = Math::fposmod(prev_time, (double)a->get_length()); - } break; - case Animation::LOOP_PINGPONG: { - prev_time = Math::pingpong(prev_time, (double)a->get_length()); - } break; - default: - break; - } - } - } - - Vector3 scale[2]; - - if (!backward) { - if (prev_time > time) { - Error err = a->try_scale_track_interpolate(i, prev_time, &scale[0]); - if (err != OK) { - continue; - } - scale[0] = post_process_key_value(a, i, scale[0], t->object, t->bone_idx); - a->try_scale_track_interpolate(i, (double)a->get_length(), &scale[1]); - root_motion_cache.scale += (scale[1] - scale[0]) * blend; - scale[1] = post_process_key_value(a, i, scale[1], t->object, t->bone_idx); - prev_time = 0; - } - } else { - if (prev_time < time) { - Error err = a->try_scale_track_interpolate(i, prev_time, &scale[0]); - if (err != OK) { - continue; - } - scale[0] = post_process_key_value(a, i, scale[0], t->object, t->bone_idx); - a->try_scale_track_interpolate(i, 0, &scale[1]); - scale[1] = post_process_key_value(a, i, scale[1], t->object, t->bone_idx); - root_motion_cache.scale += (scale[1] - scale[0]) * blend; - prev_time = (double)a->get_length(); - } - } - - Error err = a->try_scale_track_interpolate(i, prev_time, &scale[0]); - if (err != OK) { - continue; - } - scale[0] = post_process_key_value(a, i, scale[0], t->object, t->bone_idx); - - a->try_scale_track_interpolate(i, time, &scale[1]); - scale[1] = post_process_key_value(a, i, scale[1], t->object, t->bone_idx); - root_motion_cache.scale += (scale[1] - scale[0]) * blend; - prev_time = !backward ? 0 : (double)a->get_length(); - } - - { - Vector3 scale; - - Error err = a->try_scale_track_interpolate(i, time, &scale); - if (err != OK) { - continue; - } - scale = post_process_key_value(a, i, scale, t->object, t->bone_idx); - - t->scale += (scale - t->init_scale) * blend; - } -#endif // _3D_DISABLED - } break; - case Animation::TYPE_BLEND_SHAPE: { -#ifndef _3D_DISABLED - if (Math::is_zero_approx(blend)) { - continue; // Nothing to blend. - } - TrackCacheBlendShape *t = static_cast(track); - - float value; - - Error err = a->try_blend_shape_track_interpolate(i, time, &value); - //ERR_CONTINUE(err!=OK); //used for testing, should be removed - - if (err != OK) { - continue; - } - value = post_process_key_value(a, i, value, t->object, t->shape_index); - - t->value += (value - t->init_value) * blend; -#endif // _3D_DISABLED - } break; - case Animation::TYPE_VALUE: { - if (Math::is_zero_approx(blend)) { - continue; // Nothing to blend. - } - TrackCacheValue *t = static_cast(track); - - Animation::UpdateMode update_mode = a->value_track_get_update_mode(i); - - if (update_mode == Animation::UPDATE_CONTINUOUS || update_mode == Animation::UPDATE_CAPTURE) { - Variant value = a->value_track_interpolate(i, time); - value = post_process_key_value(a, i, value, t->object); - - if (value == Variant()) { - continue; - } - - // Special case for angle interpolation. - if (t->is_using_angle) { - // For blending consistency, it prevents rotation of more than 180 degrees from init_value. - // This is the same as for Quaternion blends. - float rot_a = t->value; - float rot_b = value; - float rot_init = t->init_value; - rot_a = Math::fposmod(rot_a, (float)Math_TAU); - rot_b = Math::fposmod(rot_b, (float)Math_TAU); - rot_init = Math::fposmod(rot_init, (float)Math_TAU); - if (rot_init < Math_PI) { - rot_a = rot_a > rot_init + Math_PI ? rot_a - Math_TAU : rot_a; - rot_b = rot_b > rot_init + Math_PI ? rot_b - Math_TAU : rot_b; - } else { - rot_a = rot_a < rot_init - Math_PI ? rot_a + Math_TAU : rot_a; - rot_b = rot_b < rot_init - Math_PI ? rot_b + Math_TAU : rot_b; - } - t->value = Math::fposmod(rot_a + (rot_b - rot_init) * (float)blend, (float)Math_TAU); - } else { - if (t->init_value.get_type() == Variant::BOOL) { - value = Animation::subtract_variant(value.operator real_t(), t->init_value.operator real_t()); - t->value = Animation::blend_variant(t->value.operator real_t(), value.operator real_t(), blend); - } else { - value = Animation::subtract_variant(value, t->init_value); - t->value = Animation::blend_variant(t->value, value, blend); - } - } - } else { - if (seeked) { - int idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT); - if (idx < 0) { - continue; - } - Variant value = a->track_get_key_value(i, idx); - value = post_process_key_value(a, i, value, t->object); - t->object->set_indexed(t->subpath, value); - } else { - List indices; - a->track_get_key_indices_in_range(i, time, delta, &indices, looped_flag); - for (int &F : indices) { - Variant value = a->track_get_key_value(i, F); - value = post_process_key_value(a, i, value, t->object); - t->object->set_indexed(t->subpath, value); - } - } - } - - } break; - case Animation::TYPE_METHOD: { -#ifdef TOOLS_ENABLED - if (!can_call) { - continue; - } -#endif // TOOLS_ENABLED - if (Math::is_zero_approx(blend)) { - continue; // Nothing to blend. - } - TrackCacheMethod *t = static_cast(track); - - if (seeked) { - int idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT); - if (idx < 0) { - continue; - } - StringName method = a->method_track_get_name(i, idx); - Vector params = a->method_track_get_params(i, idx); - _call_object(t->object, method, params, false); - } else { - List indices; - a->track_get_key_indices_in_range(i, time, delta, &indices, looped_flag); - for (int &F : indices) { - StringName method = a->method_track_get_name(i, F); - Vector params = a->method_track_get_params(i, F); - _call_object(t->object, method, params, true); - } - } - } break; - case Animation::TYPE_BEZIER: { - if (Math::is_zero_approx(blend)) { - continue; // Nothing to blend. - } - TrackCacheBezier *t = static_cast(track); - - real_t bezier = a->bezier_track_interpolate(i, time); - bezier = post_process_key_value(a, i, bezier, t->object); - - t->value += (bezier - t->init_value) * blend; - } break; - case Animation::TYPE_AUDIO: { - TrackCacheAudio *t = static_cast(track); - - Node *asp = Object::cast_to(t->object); - if (!asp) { - t->playing_streams.clear(); - continue; - } - - ObjectID oid = a->get_instance_id(); - if (!t->playing_streams.has(oid)) { - t->playing_streams[oid] = PlayingAudioTrackInfo(); - } - // The end of audio should be observed even if the blend value is 0, build up the information and store to the cache for that. - PlayingAudioTrackInfo &track_info = t->playing_streams[oid]; - track_info.length = a->get_length(); - track_info.time = time; - track_info.volume += blend; - track_info.loop = a->get_loop_mode() != Animation::LOOP_NONE; - track_info.backward = backward; - track_info.use_blend = a->audio_track_is_use_blend(i); - - HashMap &map = track_info.stream_info; - // Find stream. - int idx = -1; - if (seeked) { - idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT); - // Discard previous stream when seeking. - if (map.has(idx)) { - t->audio_stream_playback->stop_stream(map[idx].index); - map.erase(idx); - } - } else { - List to_play; - a->track_get_key_indices_in_range(i, time, delta, &to_play, looped_flag); - if (to_play.size()) { - idx = to_play.back()->get(); - } - } - if (idx < 0) { - continue; - } - - // Play stream. - Ref stream = a->audio_track_get_key_stream(i, idx); - if (stream.is_valid()) { - double start_ofs = a->audio_track_get_key_start_offset(i, idx); - double end_ofs = a->audio_track_get_key_end_offset(i, idx); - double len = stream->get_length(); - - if (seeked) { - start_ofs += time - a->track_get_key_time(i, idx); - } - - if (t->object->call(SNAME("get_stream")) != t->audio_stream) { - t->object->call(SNAME("set_stream"), t->audio_stream); - t->audio_stream_playback.unref(); - if (!playing_audio_stream_players.has(asp)) { - playing_audio_stream_players.push_back(asp); - } - } - if (!t->object->call(SNAME("is_playing"))) { - t->object->call(SNAME("play")); - } - if (!t->object->call(SNAME("has_stream_playback"))) { - t->audio_stream_playback.unref(); - continue; - } - if (t->audio_stream_playback.is_null()) { - t->audio_stream_playback = t->object->call(SNAME("get_stream_playback")); - } - - PlayingAudioStreamInfo pasi; - pasi.index = t->audio_stream_playback->play_stream(stream, start_ofs); - pasi.start = time; - if (len && end_ofs > 0) { // Force an end at a time. - pasi.len = len - start_ofs - end_ofs; - } else { - pasi.len = 0; - } - map[idx] = pasi; - } - - } break; - case Animation::TYPE_ANIMATION: { - if (Math::is_zero_approx(blend)) { - continue; // Nothing to blend. - } - TrackCacheAnimation *t = static_cast(track); - - AnimationPlayer *player2 = Object::cast_to(t->object); - - if (!player2) { - continue; - } - - if (seeked) { - //seek - int idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT); - if (idx < 0) { - continue; - } - - double pos = a->track_get_key_time(i, idx); - - StringName anim_name = a->animation_track_get_key_animation(i, idx); - if (String(anim_name) == "[stop]" || !player2->has_animation(anim_name)) { - continue; - } - - Ref anim = player2->get_animation(anim_name); - - double at_anim_pos = 0.0; - - switch (anim->get_loop_mode()) { - case Animation::LOOP_NONE: { - at_anim_pos = MAX((double)anim->get_length(), time - pos); //seek to end - } break; - case Animation::LOOP_LINEAR: { - at_anim_pos = Math::fposmod(time - pos, (double)anim->get_length()); //seek to loop - } break; - case Animation::LOOP_PINGPONG: { - at_anim_pos = Math::pingpong(time - pos, (double)a->get_length()); - } break; - default: - break; - } - - if (player2->is_playing() || seeked) { - player2->seek(at_anim_pos); - player2->play(anim_name); - t->playing = true; - playing_caches.insert(t); - } else { - player2->set_assigned_animation(anim_name); - player2->seek(at_anim_pos, true); - } - } else { - //find stuff to play - List to_play; - a->track_get_key_indices_in_range(i, time, delta, &to_play, looped_flag); - if (to_play.size()) { - int idx = to_play.back()->get(); - - StringName anim_name = a->animation_track_get_key_animation(i, idx); - if (String(anim_name) == "[stop]" || !player2->has_animation(anim_name)) { - if (playing_caches.has(t)) { - playing_caches.erase(t); - player2->stop(); - t->playing = false; - } - } else { - player2->play(anim_name); - t->playing = true; - playing_caches.insert(t); - } - } - } - - } break; - } - } - } - } - - { - // finally, set the tracks - for (const KeyValue &K : track_cache) { - TrackCache *track = K.value; - - switch (track->type) { - case Animation::TYPE_POSITION_3D: { -#ifndef _3D_DISABLED - TrackCacheTransform *t = static_cast(track); - - if (t->root_motion) { - root_motion_position = root_motion_cache.loc; - root_motion_rotation = root_motion_cache.rot; - root_motion_scale = root_motion_cache.scale - Vector3(1, 1, 1); - root_motion_position_accumulator = t->loc; - root_motion_rotation_accumulator = t->rot; - root_motion_scale_accumulator = t->scale; - } else if (t->skeleton && t->bone_idx >= 0) { - if (t->loc_used) { - t->skeleton->set_bone_pose_position(t->bone_idx, t->loc); - } - if (t->rot_used) { - t->skeleton->set_bone_pose_rotation(t->bone_idx, t->rot); - } - if (t->scale_used) { - t->skeleton->set_bone_pose_scale(t->bone_idx, t->scale); - } - - } else if (!t->skeleton) { - if (t->loc_used) { - t->node_3d->set_position(t->loc); - } - if (t->rot_used) { - t->node_3d->set_rotation(t->rot.get_euler()); - } - if (t->scale_used) { - t->node_3d->set_scale(t->scale); - } - } -#endif // _3D_DISABLED - } break; - case Animation::TYPE_BLEND_SHAPE: { -#ifndef _3D_DISABLED - TrackCacheBlendShape *t = static_cast(track); - - if (t->mesh_3d) { - t->mesh_3d->set_blend_shape_value(t->shape_index, t->value); - } -#endif // _3D_DISABLED - } break; - case Animation::TYPE_VALUE: { - TrackCacheValue *t = static_cast(track); - - if (t->is_discrete) { - break; // Don't overwrite the value set by UPDATE_DISCRETE. - } - - if (t->init_value.get_type() == Variant::BOOL) { - t->object->set_indexed(t->subpath, t->value.operator real_t() >= 0.5); - } else { - t->object->set_indexed(t->subpath, t->value); - } - - } break; - case Animation::TYPE_BEZIER: { - TrackCacheBezier *t = static_cast(track); - - t->object->set_indexed(t->subpath, t->value); - - } break; - case Animation::TYPE_AUDIO: { - TrackCacheAudio *t = static_cast(track); - - // Audio ending process. - LocalVector erase_maps; - for (KeyValue &L : t->playing_streams) { - PlayingAudioTrackInfo &track_info = L.value; - float db = Math::linear_to_db(track_info.use_blend ? track_info.volume : 1.0); - LocalVector erase_streams; - HashMap &map = track_info.stream_info; - for (const KeyValue &M : map) { - PlayingAudioStreamInfo pasi = M.value; - - bool stop = false; - if (!t->audio_stream_playback->is_stream_playing(pasi.index)) { - stop = true; - } - if (!track_info.loop) { - if (!track_info.backward) { - if (track_info.time < pasi.start) { - stop = true; - } - } else if (track_info.backward) { - if (track_info.time > pasi.start) { - stop = true; - } - } - } - if (pasi.len > 0) { - double len = 0.0; - if (!track_info.backward) { - len = pasi.start > track_info.time ? (track_info.length - pasi.start) + track_info.time : track_info.time - pasi.start; - } else { - len = pasi.start < track_info.time ? (track_info.length - track_info.time) + pasi.start : pasi.start - track_info.time; - } - if (len > pasi.len) { - stop = true; - } - } - if (stop) { - // Time to stop. - t->audio_stream_playback->stop_stream(pasi.index); - erase_streams.push_back(M.key); - } else { - t->audio_stream_playback->set_stream_volume(pasi.index, db); - } - } - for (uint32_t erase_idx = 0; erase_idx < erase_streams.size(); erase_idx++) { - map.erase(erase_streams[erase_idx]); - } - if (map.size() == 0) { - erase_maps.push_back(L.key); - } - } - for (uint32_t erase_idx = 0; erase_idx < erase_maps.size(); erase_idx++) { - t->playing_streams.erase(erase_maps[erase_idx]); - } - } break; - default: { - } //the rest don't matter - } - } - } -} - -Variant AnimationTree::post_process_key_value(const Ref &p_anim, int p_track, Variant p_value, const Object *p_object, int p_object_idx) { - Variant res; - if (GDVIRTUAL_CALL(_post_process_key_value, p_anim, p_track, p_value, const_cast(p_object), p_object_idx, res)) { - return res; - } - - return _post_process_key_value(p_anim, p_track, p_value, p_object, p_object_idx); -} - -Variant AnimationTree::_post_process_key_value(const Ref &p_anim, int p_track, Variant p_value, const Object *p_object, int p_object_idx) { - switch (p_anim->track_get_type(p_track)) { -#ifndef _3D_DISABLED - case Animation::TYPE_POSITION_3D: { - if (p_object_idx >= 0) { - const Skeleton3D *skel = Object::cast_to(p_object); - return Vector3(p_value) * skel->get_motion_scale(); - } - return p_value; - } break; -#endif // _3D_DISABLED - default: { - } break; - } - return p_value; -} - -void AnimationTree::advance(double p_time) { - _process_graph(p_time); -} - -void AnimationTree::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - _setup_animation_player(); - if (last_animation_player.is_valid()) { - Object *player = ObjectDB::get_instance(last_animation_player); - if (player) { - player->connect("caches_cleared", callable_mp(this, &AnimationTree::_clear_caches)); - } - } - } break; - - case NOTIFICATION_EXIT_TREE: { - _clear_caches(); - if (last_animation_player.is_valid()) { - Object *player = ObjectDB::get_instance(last_animation_player); - if (player) { - player->disconnect("caches_cleared", callable_mp(this, &AnimationTree::_clear_caches)); - } - } - } break; - - case NOTIFICATION_INTERNAL_PROCESS: { - if (active && process_callback == ANIMATION_PROCESS_IDLE) { - _process_graph(get_process_delta_time()); - } - } break; - - case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { - if (active && process_callback == ANIMATION_PROCESS_PHYSICS) { - _process_graph(get_physics_process_delta_time()); - } - } break; - } -} - -void AnimationTree::_setup_animation_player() { - if (!is_inside_tree()) { - return; - } - - cache_valid = false; - - AnimationPlayer *new_player = nullptr; - if (!animation_player.is_empty()) { - new_player = Object::cast_to(get_node_or_null(animation_player)); - if (new_player && !new_player->is_connected("animation_list_changed", callable_mp(this, &AnimationTree::_animation_player_changed))) { - new_player->connect("animation_list_changed", callable_mp(this, &AnimationTree::_animation_player_changed)); - } - } - - if (new_player) { - if (!last_animation_player.is_valid()) { - // Animation player set newly. - emit_signal(SNAME("animation_player_changed")); - return; - } else if (last_animation_player == new_player->get_instance_id()) { - // Animation player isn't changed. - return; - } - } else if (!last_animation_player.is_valid()) { - // Animation player is being empty. - return; - } - - AnimationPlayer *old_player = Object::cast_to(ObjectDB::get_instance(last_animation_player)); - if (old_player && old_player->is_connected("animation_list_changed", callable_mp(this, &AnimationTree::_animation_player_changed))) { - old_player->disconnect("animation_list_changed", callable_mp(this, &AnimationTree::_animation_player_changed)); - } - emit_signal(SNAME("animation_player_changed")); -} - -void AnimationTree::set_animation_player(const NodePath &p_player) { - animation_player = p_player; - _setup_animation_player(); - update_configuration_warnings(); -} - -NodePath AnimationTree::get_animation_player() const { - return animation_player; -} - -void AnimationTree::set_advance_expression_base_node(const NodePath &p_advance_expression_base_node) { - advance_expression_base_node = p_advance_expression_base_node; +void AnimationTree::set_advance_expression_base_node(const NodePath &p_path) { + advance_expression_base_node = p_path; } NodePath AnimationTree::get_advance_expression_base_node() const { return advance_expression_base_node; } -void AnimationTree::set_audio_max_polyphony(int p_audio_max_polyphony) { - ERR_FAIL_COND(p_audio_max_polyphony < 0 || p_audio_max_polyphony > 128); - audio_max_polyphony = p_audio_max_polyphony; -} - -int AnimationTree::get_audio_max_polyphony() const { - return audio_max_polyphony; -} - bool AnimationTree::is_state_invalid() const { - return !state.valid; + return !process_state.valid; } String AnimationTree::get_invalid_state_reason() const { - return state.invalid_reasons; + return process_state.invalid_reasons; } uint64_t AnimationTree::get_last_process_pass() const { @@ -1995,58 +607,12 @@ uint64_t AnimationTree::get_last_process_pass() const { PackedStringArray AnimationTree::get_configuration_warnings() const { PackedStringArray warnings = Node::get_configuration_warnings(); - - if (!root.is_valid()) { + if (!root_animation_node.is_valid()) { warnings.push_back(RTR("No root AnimationNode for the graph is set.")); } - - if (!has_node(animation_player)) { - warnings.push_back(RTR("Path to an AnimationPlayer node containing animations is not set.")); - } else { - AnimationPlayer *player = Object::cast_to(get_node(animation_player)); - - if (!player) { - warnings.push_back(RTR("Path set for AnimationPlayer does not lead to an AnimationPlayer node.")); - } else if (!player->has_node(player->get_root())) { - warnings.push_back(RTR("The AnimationPlayer root node is not a valid node.")); - } - } - return warnings; } -void AnimationTree::set_root_motion_track(const NodePath &p_track) { - root_motion_track = p_track; -} - -NodePath AnimationTree::get_root_motion_track() const { - return root_motion_track; -} - -Vector3 AnimationTree::get_root_motion_position() const { - return root_motion_position; -} - -Quaternion AnimationTree::get_root_motion_rotation() const { - return root_motion_rotation; -} - -Vector3 AnimationTree::get_root_motion_scale() const { - return root_motion_scale; -} - -Vector3 AnimationTree::get_root_motion_position_accumulator() const { - return root_motion_position_accumulator; -} - -Quaternion AnimationTree::get_root_motion_rotation_accumulator() const { - return root_motion_rotation_accumulator; -} - -Vector3 AnimationTree::get_root_motion_scale_accumulator() const { - return root_motion_scale_accumulator; -} - void AnimationTree::_tree_changed() { if (properties_dirty) { return; @@ -2069,7 +635,7 @@ void AnimationTree::_animation_node_renamed(const ObjectID &p_oid, const String } } - //update tree second + // Update tree second. properties_dirty = true; _update_properties(); } @@ -2083,23 +649,23 @@ void AnimationTree::_animation_node_removed(const ObjectID &p_oid, const StringN } } - //update tree second + // Update tree second. properties_dirty = true; _update_properties(); } -void AnimationTree::_update_properties_for_node(const String &p_base_path, Ref node) { - ERR_FAIL_COND(node.is_null()); +void AnimationTree::_update_properties_for_node(const String &p_base_path, Ref p_node) { + ERR_FAIL_COND(p_node.is_null()); if (!property_parent_map.has(p_base_path)) { property_parent_map[p_base_path] = HashMap(); } - if (!property_reference_map.has(node->get_instance_id())) { - property_reference_map[node->get_instance_id()] = p_base_path; + if (!property_reference_map.has(p_node->get_instance_id())) { + property_reference_map[p_node->get_instance_id()] = p_base_path; } - if (node->get_input_count() && !input_activity_map.has(p_base_path)) { + if (p_node->get_input_count() && !input_activity_map.has(p_base_path)) { Vector activity; - for (int i = 0; i < node->get_input_count(); i++) { + for (int i = 0; i < p_node->get_input_count(); i++) { Activity a; a.activity = 0; a.last_pass = 0; @@ -2110,14 +676,14 @@ void AnimationTree::_update_properties_for_node(const String &p_base_path, Ref plist; - node->get_parameter_list(&plist); + p_node->get_parameter_list(&plist); for (PropertyInfo &pinfo : plist) { StringName key = pinfo.name; if (!property_map.has(p_base_path + key)) { Pair param; - param.first = node->get_parameter_default_value(key); - param.second = node->is_parameter_read_only(key); + param.first = p_node->get_parameter_default_value(key); + param.second = p_node->is_parameter_read_only(key); property_map[p_base_path + key] = param; } @@ -2128,7 +694,7 @@ void AnimationTree::_update_properties_for_node(const String &p_base_path, Ref children; - node->get_child_nodes(&children); + p_node->get_child_nodes(&children); for (const AnimationNode::ChildNode &E : children) { _update_properties_for_node(p_base_path + E.name + "/", E.node); @@ -2146,8 +712,8 @@ void AnimationTree::_update_properties() { input_activity_map.clear(); input_activity_map_get.clear(); - if (root.is_valid()) { - _update_properties_for_node(SceneStringNames::get_singleton()->parameters_base_path, root); + if (root_animation_node.is_valid()) { + _update_properties_for_node(SceneStringNames::get_singleton()->parameters_base_path, root_animation_node); } properties_dirty = false; @@ -2155,7 +721,95 @@ void AnimationTree::_update_properties() { notify_property_list_changed(); } +void AnimationTree::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + _setup_animation_player(); + if (active) { + _set_process(true); + } + } break; + } +} + +void AnimationTree::set_animation_player(const NodePath &p_path) { + animation_player = p_path; + if (p_path.is_empty()) { + set_root_node(SceneStringNames::get_singleton()->path_pp); + while (animation_libraries.size()) { + remove_animation_library(animation_libraries[0].name); + } + } +#ifdef TOOLS_ENABLED + emit_signal(SNAME("animation_player_changed")); // Needs to unpin AnimationPlayerEditor. + emit_signal(SNAME("mixer_updated")); +#endif // TOOLS_ENABLED + _setup_animation_player(); + notify_property_list_changed(); +} + +NodePath AnimationTree::get_animation_player() const { + return animation_player; +} + +void AnimationTree::_setup_animation_player() { + if (!is_inside_tree()) { + return; + } + + cache_valid = false; + + if (animation_player.is_empty()) { + clear_caches(); + return; + } + + AnimationMixer *mixer = Object::cast_to(get_node_or_null(animation_player)); + if (mixer) { + if (!mixer->is_connected(SNAME("caches_cleared"), callable_mp(this, &AnimationTree::_setup_animation_player))) { + mixer->connect(SNAME("caches_cleared"), callable_mp(this, &AnimationTree::_setup_animation_player), CONNECT_DEFERRED); + } + if (!mixer->is_connected(SNAME("animation_list_changed"), callable_mp(this, &AnimationTree::_setup_animation_player))) { + mixer->connect(SNAME("animation_list_changed"), callable_mp(this, &AnimationTree::_setup_animation_player), CONNECT_DEFERRED); + } + Node *root = mixer->get_node_or_null(mixer->get_root_node()); + if (root) { + set_root_node(get_path_to(root, true)); + } + while (animation_libraries.size()) { + remove_animation_library(animation_libraries[0].name); + } + List list; + mixer->get_animation_library_list(&list); + for (int i = 0; i < list.size(); i++) { + Ref lib = mixer->get_animation_library(list[i]); + if (lib.is_valid()) { + add_animation_library(list[i], lib); + } + } + } + + clear_caches(); +} + +void AnimationTree::_validate_property(PropertyInfo &p_property) const { + AnimationMixer::_validate_property(p_property); + + if (!animation_player.is_empty()) { + if (p_property.name == "root_node" || p_property.name.begins_with("libraries")) { + p_property.usage |= PROPERTY_USAGE_READ_ONLY; + } + } +} + bool AnimationTree::_set(const StringName &p_name, const Variant &p_value) { +#ifndef DISABLE_DEPRECATED + String name = p_name; + if (name == "process_callback") { + set_callback_mode_process(static_cast((int)p_value)); + return true; + } +#endif // DISABLE_DEPRECATED if (properties_dirty) { _update_properties(); } @@ -2172,6 +826,12 @@ bool AnimationTree::_set(const StringName &p_name, const Variant &p_value) { } bool AnimationTree::_get(const StringName &p_name, Variant &r_ret) const { +#ifndef DISABLE_DEPRECATED + if (p_name == "process_callback") { + r_ret = get_callback_mode_process(); + return true; + } +#endif // DISABLE_DEPRECATED if (properties_dirty) { const_cast(this)->_update_properties(); } @@ -2212,63 +872,28 @@ real_t AnimationTree::get_connection_activity(const StringName &p_path, int p_co } void AnimationTree::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_active", "active"), &AnimationTree::set_active); - ClassDB::bind_method(D_METHOD("is_active"), &AnimationTree::is_active); + ClassDB::bind_method(D_METHOD("set_tree_root", "animation_node"), &AnimationTree::set_root_animation_node); + ClassDB::bind_method(D_METHOD("get_tree_root"), &AnimationTree::get_root_animation_node); - ClassDB::bind_method(D_METHOD("set_tree_root", "root"), &AnimationTree::set_tree_root); - ClassDB::bind_method(D_METHOD("get_tree_root"), &AnimationTree::get_tree_root); - - ClassDB::bind_method(D_METHOD("set_process_callback", "mode"), &AnimationTree::set_process_callback); - ClassDB::bind_method(D_METHOD("get_process_callback"), &AnimationTree::get_process_callback); - - ClassDB::bind_method(D_METHOD("set_animation_player", "root"), &AnimationTree::set_animation_player); - ClassDB::bind_method(D_METHOD("get_animation_player"), &AnimationTree::get_animation_player); - - ClassDB::bind_method(D_METHOD("set_advance_expression_base_node", "node"), &AnimationTree::set_advance_expression_base_node); + ClassDB::bind_method(D_METHOD("set_advance_expression_base_node", "path"), &AnimationTree::set_advance_expression_base_node); ClassDB::bind_method(D_METHOD("get_advance_expression_base_node"), &AnimationTree::get_advance_expression_base_node); - ClassDB::bind_method(D_METHOD("set_root_motion_track", "path"), &AnimationTree::set_root_motion_track); - ClassDB::bind_method(D_METHOD("get_root_motion_track"), &AnimationTree::get_root_motion_track); - - ClassDB::bind_method(D_METHOD("set_audio_max_polyphony", "max_polyphony"), &AnimationTree::set_audio_max_polyphony); - ClassDB::bind_method(D_METHOD("get_audio_max_polyphony"), &AnimationTree::get_audio_max_polyphony); - - ClassDB::bind_method(D_METHOD("get_root_motion_position"), &AnimationTree::get_root_motion_position); - ClassDB::bind_method(D_METHOD("get_root_motion_rotation"), &AnimationTree::get_root_motion_rotation); - ClassDB::bind_method(D_METHOD("get_root_motion_scale"), &AnimationTree::get_root_motion_scale); - ClassDB::bind_method(D_METHOD("get_root_motion_position_accumulator"), &AnimationTree::get_root_motion_position_accumulator); - ClassDB::bind_method(D_METHOD("get_root_motion_rotation_accumulator"), &AnimationTree::get_root_motion_rotation_accumulator); - ClassDB::bind_method(D_METHOD("get_root_motion_scale_accumulator"), &AnimationTree::get_root_motion_scale_accumulator); + ClassDB::bind_method(D_METHOD("set_animation_player", "path"), &AnimationTree::set_animation_player); + ClassDB::bind_method(D_METHOD("get_animation_player"), &AnimationTree::get_animation_player); ClassDB::bind_method(D_METHOD("_update_properties"), &AnimationTree::_update_properties); - ClassDB::bind_method(D_METHOD("advance", "delta"), &AnimationTree::advance); - - GDVIRTUAL_BIND(_post_process_key_value, "animation", "track", "value", "object", "object_idx"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "tree_root", PROPERTY_HINT_RESOURCE_TYPE, "AnimationRootNode"), "set_tree_root", "get_tree_root"); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "anim_player", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "AnimationPlayer"), "set_animation_player", "get_animation_player"); ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "advance_expression_base_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node"), "set_advance_expression_base_node", "get_advance_expression_base_node"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "anim_player", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "AnimationPlayer"), "set_animation_player", "get_animation_player"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "active"), "set_active", "is_active"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "process_callback", PROPERTY_HINT_ENUM, "Physics,Idle,Manual"), "set_process_callback", "get_process_callback"); - ADD_GROUP("Audio", "audio_"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "audio_max_polyphony", PROPERTY_HINT_RANGE, "1,127,1"), "set_audio_max_polyphony", "get_audio_max_polyphony"); - ADD_GROUP("Root Motion", "root_motion_"); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_motion_track"), "set_root_motion_track", "get_root_motion_track"); - - BIND_ENUM_CONSTANT(ANIMATION_PROCESS_PHYSICS); - BIND_ENUM_CONSTANT(ANIMATION_PROCESS_IDLE); - BIND_ENUM_CONSTANT(ANIMATION_PROCESS_MANUAL); - - ADD_SIGNAL(MethodInfo("animation_player_changed")); - - // Signals from AnimationNodes. - ADD_SIGNAL(MethodInfo("animation_started", PropertyInfo(Variant::STRING_NAME, "anim_name"))); - ADD_SIGNAL(MethodInfo("animation_finished", PropertyInfo(Variant::STRING_NAME, "anim_name"))); +#ifdef TOOLS_ENABLED + ADD_SIGNAL(MethodInfo(SNAME("animation_player_changed"))); +#endif // TOOLS_ENABLED } AnimationTree::AnimationTree() { + deterministic = true; } AnimationTree::~AnimationTree() { diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h index 93ca20f8f56..0be487d3fd3 100644 --- a/scene/animation/animation_tree.h +++ b/scene/animation/animation_tree.h @@ -31,24 +31,22 @@ #ifndef ANIMATION_TREE_H #define ANIMATION_TREE_H -#include "animation_player.h" -#include "scene/3d/node_3d.h" -#include "scene/3d/skeleton_3d.h" +#include "animation_mixer.h" #include "scene/resources/animation.h" -#include "scene/resources/audio_stream_polyphonic.h" -#define HUGE_LENGTH 31540000 // 31540000 seconds mean 1 year... is it too long? It must be longer than any Animation length and Transition xfade time to prevent time inversion. +#define HUGE_LENGTH 31540000 // 31540000 seconds mean 1 year... is it too long? It must be longer than any Animation length and Transition xfade time to prevent time inversion for AnimationNodeStateMachine. class AnimationNodeBlendTree; class AnimationNodeStartState; class AnimationNodeEndState; -class AnimationPlayer; class AnimationTree; class AnimationNode : public Resource { GDCLASS(AnimationNode, Resource); public: + friend class AnimationTree; + enum FilterAction { FILTER_IGNORE, FILTER_PASS, @@ -60,61 +58,47 @@ public: String name; }; + bool closable = false; Vector inputs; - - friend class AnimationTree; - - struct AnimationState { - Ref animation; - double time = 0.0; - double delta = 0.0; - Vector track_blends; - real_t blend = 0.0; - bool seeked = false; - bool is_external_seeking = false; - Animation::LoopedFlag looped_flag = Animation::LOOPED_FLAG_NONE; - }; - - struct State { - int track_count = 0; - HashMap track_map; - List animation_states; - bool valid = false; - AnimationPlayer *player = nullptr; - AnimationTree *tree = nullptr; - String invalid_reasons; - uint64_t last_pass = 0; - }; - - Vector blends; - State *state = nullptr; - - bool is_testing = false; - - double _pre_process(const StringName &p_base_path, AnimationNode *p_parent, State *p_state, double p_time, bool p_seek, bool p_is_external_seeking, const Vector &p_connections, bool p_test_only = false); - - //all this is temporary - StringName base_path; - Vector connections; - AnimationNode *parent = nullptr; - HashMap filter; bool filter_enabled = false; - bool closable = false; + // Temporary state for blending process which needs to be stored in each AnimationNodes. + struct NodeState { + StringName base_path; + AnimationNode *parent = nullptr; + Vector connections; + Vector track_weights; + } node_state; + + // Temporary state for blending process which needs to be started in the AnimationTree, pass through the AnimationNodes, and then return to the AnimationTree. + struct ProcessState { + AnimationTree *tree = nullptr; + HashMap track_map; // TODO: Is there a better way to manage filter/tracks? + bool is_testing = false; + bool valid = false; + String invalid_reasons; + uint64_t last_pass = 0; + } *process_state = nullptr; Array _get_filters() const; void _set_filters(const Array &p_filters); friend class AnimationNodeBlendTree; - double _blend_node(const StringName &p_subpath, const Vector &p_connections, AnimationNode *p_new_parent, Ref p_node, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, real_t *r_max = nullptr, bool p_test_only = false); + double _blend_node(Ref p_node, const StringName &p_subpath, AnimationNode *p_new_parent, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false, real_t *r_activity = nullptr); + double _pre_process(ProcessState *p_process_state, AnimationMixer::PlaybackInfo p_playback_info); protected: - virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false); - double process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false); + virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false); + double process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false); - void blend_animation(const StringName &p_animation, double p_time, double p_delta, bool p_seeked, bool p_is_external_seeking, real_t p_blend, Animation::LoopedFlag p_looped_flag = Animation::LOOPED_FLAG_NONE); - double blend_node(const StringName &p_sub_path, Ref p_node, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false); - double blend_input(int p_input, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false); + void blend_animation(const StringName &p_animation, AnimationMixer::PlaybackInfo p_playback_info); + double blend_node(Ref p_node, const StringName &p_subpath, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false); + double blend_input(int p_input, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false); + + // Bind-able methods to expose for compatibility, moreover AnimationMixer::PlaybackInfo is not exposed. + void blend_animation_ex(const StringName &p_animation, double p_time, double p_delta, bool p_seeked, bool p_is_external_seeking, real_t p_blend, Animation::LoopedFlag p_looped_flag = Animation::LOOPED_FLAG_NONE); + double blend_node_ex(const StringName &p_sub_path, Ref p_node, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false); + double blend_input_ex(int p_input, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false); void make_invalid(const String &p_reason); AnimationTree *get_animation_tree() const; @@ -175,7 +159,7 @@ public: VARIANT_ENUM_CAST(AnimationNode::FilterAction) -//root node does not allow inputs +// Root node does not allow inputs. class AnimationRootNode : public AnimationNode { GDCLASS(AnimationRootNode, AnimationNode); @@ -196,240 +180,102 @@ class AnimationNodeEndState : public AnimationRootNode { GDCLASS(AnimationNodeEndState, AnimationRootNode); }; -class AnimationTree : public Node { - GDCLASS(AnimationTree, Node); - - void _call_object(Object *p_object, const StringName &p_method, const Vector &p_params, bool p_deferred); +class AnimationTree : public AnimationMixer { + GDCLASS(AnimationTree, AnimationMixer); +#ifndef DISABLE_DEPRECATED public: enum AnimationProcessCallback { ANIMATION_PROCESS_PHYSICS, ANIMATION_PROCESS_IDLE, ANIMATION_PROCESS_MANUAL, }; +#endif // DISABLE_DEPRECATED private: - struct TrackCache { - bool root_motion = false; - uint64_t setup_pass = 0; - Animation::TrackType type = Animation::TrackType::TYPE_ANIMATION; - Object *object = nullptr; - ObjectID object_id; - - TrackCache() { - } - virtual ~TrackCache() {} - }; - - struct TrackCacheTransform : public TrackCache { -#ifndef _3D_DISABLED - Node3D *node_3d = nullptr; - Skeleton3D *skeleton = nullptr; -#endif // _3D_DISABLED - int bone_idx = -1; - 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; - - TrackCacheTransform() { - type = Animation::TYPE_POSITION_3D; - } - }; - - struct RootMotionCache { - Vector3 loc = Vector3(0, 0, 0); - Quaternion rot = Quaternion(0, 0, 0, 1); - Vector3 scale = Vector3(1, 1, 1); - }; - - struct TrackCacheBlendShape : public TrackCache { - MeshInstance3D *mesh_3d = nullptr; - float init_value = 0; - float value = 0; - int shape_index = -1; - TrackCacheBlendShape() { type = Animation::TYPE_BLEND_SHAPE; } - }; - - struct TrackCacheValue : public TrackCache { - Variant init_value; - Variant value; - Vector subpath; - bool is_discrete = false; - bool is_using_angle = false; - TrackCacheValue() { type = Animation::TYPE_VALUE; } - }; - - struct TrackCacheMethod : public TrackCache { - TrackCacheMethod() { type = Animation::TYPE_METHOD; } - }; - - struct TrackCacheBezier : public TrackCache { - real_t init_value = 0.0; - real_t value = 0.0; - Vector subpath; - TrackCacheBezier() { - type = Animation::TYPE_BEZIER; - } - }; - - // Audio stream information for each audio stream placed on the track. - struct PlayingAudioStreamInfo { - AudioStreamPlaybackPolyphonic::ID index = -1; // ID retrieved from AudioStreamPlaybackPolyphonic. - double start = 0.0; - double len = 0.0; - }; - - // Audio track information for mixng and ending. - struct PlayingAudioTrackInfo { - HashMap stream_info; - double length = 0.0; - double time = 0.0; - real_t volume = 0.0; - bool loop = false; - bool backward = false; - bool use_blend = false; - }; - - struct TrackCacheAudio : public TrackCache { - Ref audio_stream; - Ref audio_stream_playback; - HashMap playing_streams; // Key is Animation resource ObjectID. - - TrackCacheAudio() { - type = Animation::TYPE_AUDIO; - } - }; - - struct TrackCacheAnimation : public TrackCache { - bool playing = false; - - TrackCacheAnimation() { - type = Animation::TYPE_ANIMATION; - } - }; - - RootMotionCache root_motion_cache; - HashMap track_cache; - HashSet playing_caches; - Vector playing_audio_stream_players; - - Ref root; + Ref root_animation_node; NodePath advance_expression_base_node = NodePath(String(".")); - AnimationProcessCallback process_callback = ANIMATION_PROCESS_IDLE; - bool active = false; - NodePath animation_player; - int audio_max_polyphony = 32; - - AnimationNode::State state; - bool cache_valid = false; - void _node_removed(Node *p_node); - - void _setup_animation_player(); - void _animation_player_changed(); - void _clear_caches(); - void _clear_playing_caches(); - void _clear_audio_streams(); - bool _update_caches(AnimationPlayer *player); - void _process_graph(double p_delta); - - uint64_t setup_pass = 1; + AnimationNode::ProcessState process_state; uint64_t process_pass = 1; bool started = true; - NodePath root_motion_track; - Vector3 root_motion_position = Vector3(0, 0, 0); - Quaternion root_motion_rotation = Quaternion(0, 0, 0, 1); - Vector3 root_motion_scale = Vector3(0, 0, 0); - Vector3 root_motion_position_accumulator = Vector3(0, 0, 0); - Quaternion root_motion_rotation_accumulator = Quaternion(0, 0, 0, 1); - Vector3 root_motion_scale_accumulator = Vector3(1, 1, 1); - friend class AnimationNode; - bool properties_dirty = true; - void _tree_changed(); - void _animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name); - void _animation_node_removed(const ObjectID &p_oid, const StringName &p_node); - void _update_properties(); + List properties; HashMap> property_parent_map; HashMap property_reference_map; HashMap> property_map; // Property value and read-only flag. + bool properties_dirty = true; + + void _update_properties(); + void _update_properties_for_node(const String &p_base_path, Ref p_node); + + void _tree_changed(); + void _animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name); + void _animation_node_removed(const ObjectID &p_oid, const StringName &p_node); + struct Activity { uint64_t last_pass = 0; real_t activity = 0.0; }; - HashMap> input_activity_map; HashMap *> input_activity_map_get; - void _update_properties_for_node(const String &p_base_path, Ref node); + NodePath animation_player; - ObjectID last_animation_player; + void _setup_animation_player(); + void _animation_player_changed(); -protected: bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; void _get_property_list(List *p_list) const; - + virtual void _validate_property(PropertyInfo &p_property) const override; void _notification(int p_what); + static void _bind_methods(); - GDVIRTUAL5RC(Variant, _post_process_key_value, Ref, int, Variant, Object *, int); - Variant post_process_key_value(const Ref &p_anim, int p_track, Variant p_value, const Object *p_object, int p_object_idx = -1); - virtual Variant _post_process_key_value(const Ref &p_anim, int p_track, Variant p_value, const Object *p_object, int p_object_idx = -1); + virtual void _set_active(bool p_active) override; + + // Make animation instances. + virtual bool _blend_pre_process(double p_delta, int p_track_count, const HashMap &p_track_map) override; + +#ifndef DISABLE_DEPRECATED + void _set_process_callback_bind_compat_80813(AnimationProcessCallback p_mode); + AnimationProcessCallback _get_process_callback_bind_compat_80813() const; + void _set_tree_root_bind_compat_80813(const Ref &p_root); + Ref _get_tree_root_bind_compat_80813() const; + + static void _bind_compatibility_methods(); +#endif // DISABLE_DEPRECATED public: - void set_tree_root(const Ref &p_root); - Ref get_tree_root() const; - - void set_active(bool p_active); - bool is_active() const; - - void set_process_callback(AnimationProcessCallback p_mode); - AnimationProcessCallback get_process_callback() const; - - void set_animation_player(const NodePath &p_player); + void set_animation_player(const NodePath &p_path); NodePath get_animation_player() const; - void set_advance_expression_base_node(const NodePath &p_advance_expression_base_node); - NodePath get_advance_expression_base_node() const; + void set_root_animation_node(const Ref &p_animation_node); + Ref get_root_animation_node() const; - void set_audio_max_polyphony(int p_audio_max_polyphony); - int get_audio_max_polyphony() const; + void set_advance_expression_base_node(const NodePath &p_path); + NodePath get_advance_expression_base_node() const; PackedStringArray get_configuration_warnings() const override; bool is_state_invalid() const; String get_invalid_state_reason() const; - void set_root_motion_track(const NodePath &p_track); - NodePath get_root_motion_track() const; - - Vector3 get_root_motion_position() const; - Quaternion get_root_motion_rotation() const; - Vector3 get_root_motion_scale() const; - - Vector3 get_root_motion_position_accumulator() const; - Quaternion get_root_motion_rotation_accumulator() const; - Vector3 get_root_motion_scale_accumulator() const; - real_t get_connection_activity(const StringName &p_path, int p_connection) const; - void advance(double p_time); uint64_t get_last_process_pass() const; + AnimationTree(); ~AnimationTree(); }; -VARIANT_ENUM_CAST(AnimationTree::AnimationProcessCallback) +#ifndef DISABLE_DEPRECATED +VARIANT_ENUM_CAST(AnimationTree::AnimationProcessCallback); +#endif // DISABLE_DEPRECATED #endif // ANIMATION_TREE_H diff --git a/scene/animation/root_motion_view.cpp b/scene/animation/root_motion_view.cpp index fc758b9456f..645850015b2 100644 --- a/scene/animation/root_motion_view.cpp +++ b/scene/animation/root_motion_view.cpp @@ -33,12 +33,12 @@ #include "scene/animation/animation_tree.h" #include "scene/resources/material.h" -void RootMotionView::set_animation_path(const NodePath &p_path) { +void RootMotionView::set_animation_mixer(const NodePath &p_path) { path = p_path; first = true; } -NodePath RootMotionView::get_animation_path() const { +NodePath RootMotionView::get_animation_mixer() const { return path; } @@ -93,20 +93,20 @@ void RootMotionView::_notification(int p_what) { if (has_node(path)) { Node *node = get_node(path); - AnimationTree *tree = Object::cast_to(node); - if (tree && tree->is_active() && tree->get_root_motion_track() != NodePath()) { - if (is_processing_internal() && tree->get_process_callback() == AnimationTree::ANIMATION_PROCESS_PHYSICS) { + AnimationMixer *mixer = Object::cast_to(node); + if (mixer && mixer->is_active() && mixer->get_root_motion_track() != NodePath()) { + if (is_processing_internal() && mixer->get_callback_mode_process() == AnimationMixer::ANIMATION_CALLBACK_MODE_PROCESS_PHYSICS) { set_process_internal(false); set_physics_process_internal(true); } - if (is_physics_processing_internal() && tree->get_process_callback() == AnimationTree::ANIMATION_PROCESS_IDLE) { + if (is_physics_processing_internal() && mixer->get_callback_mode_process() == AnimationMixer::ANIMATION_CALLBACK_MODE_PROCESS_IDLE) { set_process_internal(true); set_physics_process_internal(false); } - transform.origin = tree->get_root_motion_position(); - transform.basis = tree->get_root_motion_rotation(); // Scale is meaningless. - diff = tree->get_root_motion_rotation_accumulator(); + transform.origin = mixer->get_root_motion_position(); + transform.basis = mixer->get_root_motion_rotation(); // Scale is meaningless. + diff = mixer->get_root_motion_rotation_accumulator(); } } @@ -170,8 +170,8 @@ AABB RootMotionView::get_aabb() const { } void RootMotionView::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_animation_path", "path"), &RootMotionView::set_animation_path); - ClassDB::bind_method(D_METHOD("get_animation_path"), &RootMotionView::get_animation_path); + ClassDB::bind_method(D_METHOD("set_animation_path", "path"), &RootMotionView::set_animation_mixer); + ClassDB::bind_method(D_METHOD("get_animation_path"), &RootMotionView::get_animation_mixer); ClassDB::bind_method(D_METHOD("set_color", "color"), &RootMotionView::set_color); ClassDB::bind_method(D_METHOD("get_color"), &RootMotionView::get_color); @@ -185,7 +185,7 @@ void RootMotionView::_bind_methods() { ClassDB::bind_method(D_METHOD("set_zero_y", "enable"), &RootMotionView::set_zero_y); ClassDB::bind_method(D_METHOD("get_zero_y"), &RootMotionView::get_zero_y); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "animation_path", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "AnimationTree"), "set_animation_path", "get_animation_path"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "animation_path", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "AnimationMixer"), "set_animation_path", "get_animation_path"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "cell_size", PROPERTY_HINT_RANGE, "0.1,16,0.01,or_greater,suffix:m"), "set_cell_size", "get_cell_size"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.1,16,0.01,or_greater,suffix:m"), "set_radius", "get_radius"); diff --git a/scene/animation/root_motion_view.h b/scene/animation/root_motion_view.h index 74861c30ab4..662259da568 100644 --- a/scene/animation/root_motion_view.h +++ b/scene/animation/root_motion_view.h @@ -55,8 +55,8 @@ private: static void _bind_methods(); public: - void set_animation_path(const NodePath &p_path); - NodePath get_animation_path() const; + void set_animation_mixer(const NodePath &p_path); + NodePath get_animation_mixer() const; void set_color(const Color &p_color); Color get_color() const; diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 03085edb86a..b7c98b0ea9a 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -74,6 +74,7 @@ #include "scene/animation/animation_blend_space_1d.h" #include "scene/animation/animation_blend_space_2d.h" #include "scene/animation/animation_blend_tree.h" +#include "scene/animation/animation_mixer.h" #include "scene/animation/animation_node_state_machine.h" #include "scene/animation/animation_player.h" #include "scene/animation/animation_tree.h" @@ -454,8 +455,6 @@ void register_scene_types() { #endif /* REGISTER ANIMATION */ - - GDREGISTER_CLASS(AnimationPlayer); GDREGISTER_CLASS(Tween); GDREGISTER_ABSTRACT_CLASS(Tweener); GDREGISTER_CLASS(PropertyTweener); @@ -463,6 +462,8 @@ void register_scene_types() { GDREGISTER_CLASS(CallbackTweener); GDREGISTER_CLASS(MethodTweener); + GDREGISTER_ABSTRACT_CLASS(AnimationMixer); + GDREGISTER_CLASS(AnimationPlayer); GDREGISTER_CLASS(AnimationTree); GDREGISTER_CLASS(AnimationNode); GDREGISTER_CLASS(AnimationRootNode); diff --git a/scene/resources/animation_library.h b/scene/resources/animation_library.h index 5013f725ca2..b2152fd7c54 100644 --- a/scene/resources/animation_library.h +++ b/scene/resources/animation_library.h @@ -44,7 +44,7 @@ class AnimationLibrary : public Resource { void _animation_changed(const StringName &p_name); - friend class AnimationPlayer; //for faster access + friend class AnimationMixer; // For faster access. HashMap> animations; protected: