diff --git a/doc/classes/BoneAttachment3D.xml b/doc/classes/BoneAttachment3D.xml index dc3d448621de..2fa7739f5ffa 100644 --- a/doc/classes/BoneAttachment3D.xml +++ b/doc/classes/BoneAttachment3D.xml @@ -16,18 +16,6 @@ Returns the [NodePath] to the external [Skeleton3D] node, if one has been set. - - - - Returns the override mode for the BoneAttachment3D node. - - - - - - Returns whether the BoneAttachment3D node is overriding the bone pose of the bone it's attached to. - - @@ -48,20 +36,6 @@ Sets the [NodePath] to the external skeleton that the BoneAttachment3D node should use. The external [Skeleton3D] node is only used when [code]use_external_skeleton[/code] is set to [code]true[/code]. - - - - - Sets the override mode for the BoneAttachment3D node. The override mode defines which of the bone poses the BoneAttachment3D node will override. - - - - - - - Sets whether the BoneAttachment3D node will override the bone pose of the bone it is attached to. When set to [code]true[/code], the BoneAttachment3D node can change the pose of the bone. - - @@ -77,5 +51,8 @@ The name of the attached bone. + + If true, the BoneAttachment3D node will override the bone pose of the bone it is attached to. When set to [code]true[/code], the BoneAttachment3D node can change the pose of the bone. When set to [code]false[/code], the BoneAttachment3D will always be set to the bone's transform. + diff --git a/doc/classes/PhysicalBone2D.xml b/doc/classes/PhysicalBone2D.xml index 26fce1a90b54..829bff07a765 100644 --- a/doc/classes/PhysicalBone2D.xml +++ b/doc/classes/PhysicalBone2D.xml @@ -11,6 +11,11 @@ + + + + + @@ -28,10 +33,7 @@ If [code]true[/code], the [code]PhysicalBone2D[/code] node will automatically configure the first [Joint2D] child node. The automatic configuration is limited to setting up the node properties and positioning the [Joint2D]. - - The index of the [Bone2D] node that this [code]PhysicalBone2D[/code] node is supposed to be simulating. - - + The [NodePath] to the [Bone2D] node that this [code]PhysicalBone2D[/code] node is supposed to be simulating. diff --git a/doc/classes/Skeleton2D.xml b/doc/classes/Skeleton2D.xml index 808f93b491df..2dd694d3c916 100644 --- a/doc/classes/Skeleton2D.xml +++ b/doc/classes/Skeleton2D.xml @@ -5,20 +5,12 @@ Skeleton2D parents a hierarchy of [Bone2D] objects. It is a requirement of [Bone2D]. Skeleton2D holds a reference to the rest pose of its children and acts as a single point of access to its bones. - To setup different types of inverse kinematics for the given Skeleton2D, a [SkeletonModificationStack2D] should be created. They can be applied by creating the desired number of modifications, which can be done by increasing [member SkeletonModificationStack2D.modification_count]. + To setup different types of inverse kinematics for the given Skeleton2D, various [SkeletonModification2D] nodes should be added as children. They will update in node order. $DOCS_URL/tutorials/animation/2d_skeletons.html - - - - - - Executes all the modifications on the [SkeletonModificationStack2D], if the Skeleton3D has one assigned. - - @@ -39,12 +31,6 @@ Returns the local pose override transform for [param bone_idx]. - - - - Returns the [SkeletonModificationStack2D] attached to this skeleton, if one exists. - - @@ -63,13 +49,6 @@ [b]Note:[/b] The pose transform needs to be a local transform relative to the [Bone2D] node at [param bone_idx]! - - - - - Sets the [SkeletonModificationStack2D] attached to this skeleton. - - diff --git a/doc/classes/Skeleton3D.xml b/doc/classes/Skeleton3D.xml index 5a0766263a05..73c588c86dee 100644 --- a/doc/classes/Skeleton3D.xml +++ b/doc/classes/Skeleton3D.xml @@ -7,6 +7,7 @@ Skeleton3D provides a hierarchical interface for managing bones, including pose, rest and animation (see [Animation]). It can also use ragdoll physics. The overall transform of a bone with respect to the skeleton is determined by the following hierarchical order: rest pose, custom pose and pose. Note that "global pose" below refers to the overall transform of the bone with respect to skeleton, so it not the actual global/world transform of the bone. + To setup different types of inverse kinematics for the given Skeleton3D, various [SkeletonModification2D] nodes should be added as children. They will update in node order. https://godotengine.org/asset-library/asset/523 @@ -32,25 +33,11 @@ Removes the global pose override on all bones in the skeleton. - - - - Removes the local pose override on all bones in the skeleton. - - - - - - - - Executes all the modifications on the [SkeletonModificationStack3D], if the Skeleton3D has one assigned. - - @@ -58,12 +45,6 @@ Returns the bone index that matches [param name] as its name. - - - - Force updates the bone transforms/poses for all bones in the skeleton. - - @@ -112,13 +93,6 @@ Returns the global rest transform for [param bone_idx]. - - - - - Returns the local pose override transform for [param bone_idx]. - - @@ -166,42 +140,15 @@ Returns the rest transform for a bone [param bone_idx]. - - - - Returns the modification stack attached to this skeleton, if one exists. - - Returns an array with all of the bones that are parentless. Another way to look at this is that it returns the indexes of all the bones that are not dependent or modified by other bones in the Skeleton. - - - - - - Takes the passed-in global pose and converts it to local pose transform. - This can be used to easily convert a global pose from [method get_bone_global_pose] to a global transform in [method set_bone_local_pose_override]. - - - - - - - Takes the passed-in global pose and converts it to a world transform. - This can be used to easily convert a global pose from [method get_bone_global_pose] to a global transform usable with a node's transform, like [member Node3D.global_transform] for example. - - - - - - + + - Rotates the given [Basis] so that the forward axis of the Basis is facing in the forward direction of the bone at [param bone_idx]. - This is helper function to make using [method Transform3D.looking_at] easier with bone poses. @@ -211,15 +158,6 @@ Returns whether the bone pose for the bone at [param bone_idx] is enabled. - - - - - - Converts the passed-in local pose to a global pose relative to the inputted bone, [param bone_idx]. - This could be used to convert [method get_bone_pose] for use with the [method set_bone_global_pose_override] function. - - @@ -293,19 +231,7 @@ Sets the global pose transform, [param pose], for the bone at [param bone_idx]. [param amount] is the interpolation strength that will be used when applying the pose, and [param persistent] determines if the applied pose will remain. - [b]Note:[/b] The pose transform needs to be a global pose! Use [method world_transform_to_global_pose] to convert a world transform, like one you can get from a [Node3D], to a global pose. - - - - - - - - - - Sets the local pose transform, [param pose], for the bone at [param bone_idx]. - [param amount] is the interpolation strength that will be used when applying the pose, and [param persistent] determines if the applied pose will remain. - [b]Note:[/b] The pose transform needs to be a local pose! Use [method global_pose_to_local_pose] to convert a global pose to a local pose. + [b]Note:[/b] The pose transform needs to be a global pose! To convert a world transform from a [Node3D] to a global bone pose, multiply the [method Transform3D.affine_inverse] of the node's [member Node3D.global_transform] by the desired world transform. @@ -353,13 +279,6 @@ Sets the rest transform for bone [param bone_idx]. - - - - - Sets the modification stack for this skeleton to the passed-in modification stack, [param modification_stack]. - - @@ -367,14 +286,6 @@ Unparents the bone at [param bone_idx] and sets its rest position to that of its parent prior to being reset. - - - - - Takes the passed-in global transform and converts it to a global pose. - This can be used to easily convert a global transform from [member Node3D.global_transform] to a global pose usable with [method set_bone_global_pose_override], for example. - - diff --git a/doc/classes/SkeletonIK3D.xml b/doc/classes/SkeletonIK3D.xml deleted file mode 100644 index 788ba3e24827..000000000000 --- a/doc/classes/SkeletonIK3D.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - https://godotengine.org/asset-library/asset/523 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/doc/classes/SkeletonModification2D.xml b/doc/classes/SkeletonModification2D.xml index 46d32aef4160..2474f7d157fa 100644 --- a/doc/classes/SkeletonModification2D.xml +++ b/doc/classes/SkeletonModification2D.xml @@ -1,11 +1,12 @@ - + A resource that operates on [Bone2D] nodes in a [Skeleton2D]. This resource provides an interface that can be expanded so code that operates on [Bone2D] nodes in a [Skeleton2D] can be mixed and matched together to create complex interactions. This is used to provide Godot with a flexible and powerful Inverse Kinematics solution that can be adapted for many different uses. + Modifications will update in node order. Set [member Node.process_priority] to control the order relative to other nodes or to ensure updating after [AnimationPlayer]. @@ -13,22 +14,21 @@ - Used for drawing [b]editor-only[/b] modification gizmos. This function will only be called in the Godot editor and can be overridden to draw custom gizmos. - [b]Note:[/b] You will need to use the Skeleton2D from [method SkeletonModificationStack2D.get_skeleton] and it's draw functions, as the [SkeletonModification2D] resource cannot draw on its own. + Handle rendering of a custom gizmo by calling draw_ methods on [member skeleton_path]. You must first call [method set_editor_gizmo_dirty] to force the gizmo to draw, if in editor. - Executes the given modification. This is where the modification performs whatever function it is designed to do. + Allows custom logic to run on this modification during internal process. delta is the frame time since last execute. - - - + + + - Called when the modification is setup. This is where the modification performs initialization. + Return true if this property should be hidden from the inspector. @@ -41,45 +41,80 @@ Takes a angle and clamps it so it is within the passed-in [param min] and [param max] range. [param invert] will inversely clamp the angle, clamping it to the range outside of the given bounds. - - + + - Returns whether this modification will call [method _draw_editor_gizmo] in the Godot editor to draw modification-specific gizmos. + Manually render the editor gizmo on the [Skeleton2D] node, if in editor. - - + + + + + + + + - Returns whether this modification has been successfully setup or not. + Helper function to draw a gizmo for an angle constraint. - - + + + - Returns the [SkeletonModificationStack2D] that this modification is bound to. Through the modification stack, you can access the Skeleton3D the modification is operating on. + Manually process the modification. If the modification is enabled, execute runs automatically during internal process. - - - + + + + + Returns the global position of the cached bone or node. + + + + + + + Returns the global rotation angle of the cached bone or node. + + + + + + + Returns the global [Transform2D] of the cached bone or node. + + + + + + + Resolves the NodePath and validates it can be cast to a Bone2D. + + + + + - Sets whether this modification will call [method _draw_editor_gizmo] in the Godot editor to draw modification-specific gizmos. + Resolves the NodePath and validates it can be cast to a CanvasItem. - + - + - Manually allows you to set the setup state of the modification. This function should only rarely be used, as the [SkeletonModificationStack2D] the modification is bound to should handle setting the modification up. + Trigger [method _draw_editor_gizmo] to be run on the next frame, if in editor. - If [code]true[/code], the modification's [method _execute] function will be called by the [SkeletonModificationStack2D]. + If [code]true[/code], the modification's [method _execute] function will be called every frame during internal process. - - The execution mode for the modification. This tells the modification stack when to execute the modification. Some modifications have settings that are only available in certain execution modes. + + Node path to the attached [Skeleton2D]. diff --git a/doc/classes/SkeletonModification2DCCDIK.xml b/doc/classes/SkeletonModification2DCCDIK.xml index c8fee3f94dc0..caf0d9cd384d 100644 --- a/doc/classes/SkeletonModification2DCCDIK.xml +++ b/doc/classes/SkeletonModification2DCCDIK.xml @@ -12,72 +12,64 @@ - + Returns the [Bone2D] node assigned to the CCDIK joint at [param joint_idx]. - - - - - Returns the index of the [Bone2D] node assigned to the CCDIK joint at [param joint_idx]. - - - + - Returns whether the CCDIK joint at [param joint_idx] uses an inverted joint constraint. See [method set_ccdik_joint_constraint_angle_invert] for details. + Returns whether the CCDIK joint at [param joint_idx] uses an inverted joint constraint. See [method set_joint_constraint_angle_invert] for details. - + Returns the maximum angle constraint for the joint at [param joint_idx]. - + Returns the minimum angle constraint for the joint at [param joint_idx]. - + - Returns whether angle constraints on the CCDIK joint at [param joint_idx] are enabled. + Returns whether the rotation constraint for this joint is in local coordinates. - + - Returns whether the joint at [param joint_idx] is set to rotate from the joint, [code]true[/code], or to rotate from the tip, [code]false[/code]. The default is to rotate from the tip. + Returns whether angle constraints on the CCDIK joint at [param joint_idx] are enabled. - - + + - - Sets the [Bone2D] node assigned to the CCDIK joint at [param joint_idx]. + Returns whether the joint at [param joint_idx] is set to rotate from the joint, [code]true[/code], or to rotate from the tip, [code]false[/code]. The default is to rotate from the tip. - + - + - Sets the bone index, [param bone_idx], of the CCDIK joint at [param joint_idx]. When possible, this will also update the [code]bone2d_node[/code] of the CCDIK joint based on data provided by the linked skeleton. + Sets the [Bone2D] node assigned to the CCDIK joint at [param joint_idx]. - + @@ -86,7 +78,7 @@ An inverted joint constraint only constraints the CCDIK joint to the angles [i]outside of[/i] the inputted minimum and maximum angles. For this reason, it is referred to as an inverted joint constraint, as it constraints the joint to the outside of the inputted values. - + @@ -94,7 +86,7 @@ Sets the maximum angle constraint for the joint at [param joint_idx]. - + @@ -102,7 +94,15 @@ Sets the minimum angle constraint for the joint at [param joint_idx]. - + + + + + + If true, applies the rotation constraint for this joint in local coordinates, after converting from global to local angle. + + + @@ -110,7 +110,7 @@ Determines whether angle constraints on the CCDIK joint at [param joint_idx] are enabled. When [code]true[/code], constraints will be enabled and taken into account when solving. - + @@ -120,13 +120,13 @@ - - The number of CCDIK joints in the CCDIK modification. + + The number of joints in the joint array for this CCDIK chain. - + The NodePath to the node that is the target for the CCDIK modification. This node is what the CCDIK chain will attempt to rotate the bone chain to. - + The end position of the CCDIK chain. Typically, this should be a child of a [Bone2D] node attached to the final [Bone2D] in the CCDIK chain. diff --git a/doc/classes/SkeletonModification2DFABRIK.xml b/doc/classes/SkeletonModification2DFABRIK.xml index ff3a65fe1a68..b1893e13edae 100644 --- a/doc/classes/SkeletonModification2DFABRIK.xml +++ b/doc/classes/SkeletonModification2DFABRIK.xml @@ -13,35 +13,28 @@ - + Returns the [Bone2D] node assigned to the FABRIK joint at [param joint_idx]. - - - - - Returns the index of the [Bone2D] node assigned to the FABRIK joint at [param joint_idx]. - - - + Returns the magnet position vector for the joint at [param joint_idx]. - + Returns whether the joint is using the target's rotation rather than allowing FABRIK to rotate the joint. This option only applies to the tip/final joint in the chain. - + @@ -49,15 +42,7 @@ Sets the [Bone2D] node assigned to the FABRIK joint at [param joint_idx]. - - - - - - Sets the bone index, [param bone_idx], of the FABRIK joint at [param joint_idx]. When possible, this will also update the [code]bone2d_node[/code] of the FABRIK joint based on data provided by the linked skeleton. - - - + @@ -65,7 +50,7 @@ Sets the magnet position vector for the joint at [param joint_idx]. - + @@ -76,10 +61,10 @@ - - The number of FABRIK joints in the FABRIK modification. + + The number of joints in the joint array for this FABRIK chain. - + The NodePath to the node that is the target for the FABRIK modification. This node is what the FABRIK chain will attempt to rotate the bone chain to. diff --git a/doc/classes/SkeletonModification2DJiggle.xml b/doc/classes/SkeletonModification2DJiggle.xml index 7329b2d86505..e5fa8710d88e 100644 --- a/doc/classes/SkeletonModification2DJiggle.xml +++ b/doc/classes/SkeletonModification2DJiggle.xml @@ -17,56 +17,49 @@ Returns the collision mask used by the Jiggle modifier when collisions are enabled. - + Returns the [Bone2D] node assigned to the Jiggle joint at [param joint_idx]. - - - - - Returns the index of the [Bone2D] node assigned to the Jiggle joint at [param joint_idx]. - - - + Returns the amount of damping of the Jiggle joint at [param joint_idx]. - + Returns a [Vector2] representing the amount of gravity the Jiggle joint at [param joint_idx] is influenced by. - + Returns the amount of mass of the jiggle joint at [param joint_idx]. - + Returns a boolean that indicates whether the joint at [param joint_idx] is overriding the default Jiggle joint data defined in the modification. - + Returns the stiffness of the Jiggle joint at [param joint_idx]. - + @@ -86,7 +79,7 @@ Sets the collision mask that the Jiggle modifier will use when reacting to colliders, if the Jiggle modifier is set to take colliders into account. - + @@ -94,15 +87,7 @@ Sets the [Bone2D] node assigned to the Jiggle joint at [param joint_idx]. - - - - - - Sets the bone index, [param bone_idx], of the Jiggle joint at [param joint_idx]. When possible, this will also update the [code]bone2d_node[/code] of the Jiggle joint based on data provided by the linked skeleton. - - - + @@ -110,7 +95,7 @@ Sets the amount of dampening of the Jiggle joint at [param joint_idx]. - + @@ -118,7 +103,7 @@ Sets the gravity vector of the Jiggle joint at [param joint_idx]. - + @@ -126,7 +111,7 @@ Sets the of mass of the Jiggle joint at [param joint_idx]. - + @@ -134,7 +119,7 @@ Sets whether the Jiggle joint at [param joint_idx] should override the default Jiggle joint settings. Setting this to [code]true[/code] will make the joint use its own settings rather than the default ones attached to the modification. - + @@ -142,7 +127,7 @@ Sets the of stiffness of the Jiggle joint at [param joint_idx]. - + @@ -165,8 +150,8 @@ The default amount of gravity applied to the Jiggle joints, if they are not overridden. - - The amount of Jiggle joints in the Jiggle modification. + + The number of joints in the joint array for this jiggle chain. The default amount of mass assigned to the Jiggle joints, if they are not overridden. Higher values lead to faster movements and more overshooting. @@ -174,7 +159,7 @@ The default amount of stiffness assigned to the Jiggle joints, if they are not overridden. Higher values act more like springs, quickly moving into the correct position. - + The NodePath to the node that is the target for the Jiggle modification. This node is what the Jiggle chain will attempt to rotate the bone chain to. diff --git a/doc/classes/SkeletonModification2DLookAt.xml b/doc/classes/SkeletonModification2DLookAt.xml index 4747b06056a3..cca90ee47698 100644 --- a/doc/classes/SkeletonModification2DLookAt.xml +++ b/doc/classes/SkeletonModification2DLookAt.xml @@ -8,82 +8,30 @@ - - - - - Returns the amount of additional rotation that is applied after the LookAt modification executes. - - - - - - Returns whether the constraints to this modification are inverted or not. - - - - - - Returns the constraint's maximum allowed angle. - - - - - - Returns the constraint's minimum allowed angle. - - - - - - Returns [code]true[/code] if the LookAt modification is using constraints. - - - - - - - Sets the amount of additional rotation that is to be applied after executing the modification. This allows for offsetting the results by the inputted rotation amount. - - - - - - - When [code]true[/code], the modification will use an inverted joint constraint. - An inverted joint constraint only constraints the [Bone2D] to the angles [i]outside of[/i] the inputted minimum and maximum angles. For this reason, it is referred to as an inverted joint constraint, as it constraints the joint to the outside of the inputted values. - - - - - - - Sets the constraint's maximum allowed angle. - - - - - - - Sets the constraint's minimum allowed angle. - - - - - - - Sets whether this modification will use constraints or not. When [code]true[/code], constraints will be applied when solving the LookAt modification. - - - - + + The amount of additional rotation that is to be applied after executing the modification. This allows for offsetting the results by the inputted rotation amount. + + The [Bone2D] node that the modification will operate on. - - The index of the [Bone2D] node that the modification will oeprate on. + + When [code]true[/code], the modification will use an inverted joint constraint. + An inverted joint constraint only constraints the [Bone2D] to the angles [i]outside of[/i] the inputted minimum and maximum angles. For this reason, it is referred to as an inverted joint constraint, as it constraints the joint to the outside of the inputted values. + + + The constraint's maximum allowed angle. + + + The constraint's minimum allowed angle. + + + Applies the rotation constraint in local coordinates, after converting from global to local angle. + + + Sets whether this modification will use constraints or not. When [code]true[/code], constraints will be applied when solving the LookAt modification. - + The NodePath to the node that is the target for the LookAt modification. This node is what the modification will rotate the [Bone2D] to. diff --git a/doc/classes/SkeletonModification2DPhysicalBones.xml b/doc/classes/SkeletonModification2DPhysicalBones.xml index d5f46b2ea09a..a082d3f855e4 100644 --- a/doc/classes/SkeletonModification2DPhysicalBones.xml +++ b/doc/classes/SkeletonModification2DPhysicalBones.xml @@ -9,12 +9,6 @@ - - - - Empties the list of [PhysicalBone2D] nodes and populates it will all [PhysicalBone2D] nodes that are children of the [Skeleton2D]. - - @@ -31,26 +25,10 @@ [b]Note:[/b] This is just the index used for this modification, not the bone index used in the [Skeleton2D]. - - - - - Tell the [PhysicalBone2D] nodes to start simulating and interacting with the physics world. - Optionally, an array of bone names can be passed to this function, and that will cause only [PhysicalBone2D] nodes with those names to start simulating. - - - - - - - Tell the [PhysicalBone2D] nodes to stop simulating and interacting with the physics world. - Optionally, an array of bone names can be passed to this function, and that will cause only [PhysicalBone2D] nodes with those names to stop simulating. - - - - The number of [PhysicalBone2D] nodes linked in this modification. + + The number of physical bones simulated in this modification. The joints should be in order from parent to child, but may consist of multiple chains. diff --git a/doc/classes/SkeletonModification2DStackHolder.xml b/doc/classes/SkeletonModification2DStackHolder.xml deleted file mode 100644 index 791dea2fb120..000000000000 --- a/doc/classes/SkeletonModification2DStackHolder.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - A modification that holds and executes a [SkeletonModificationStack2D]. - - - This [SkeletonModification2D] holds a reference to a [SkeletonModificationStack2D], allowing you to use multiple modification stacks on a single [Skeleton2D]. - [b]Note:[/b] The modifications in the held [SkeletonModificationStack2D] will only be executed if their execution mode matches the execution mode of the SkeletonModification2DStackHolder. - - - - - - - - Returns the [SkeletonModificationStack2D] that this modification is holding. - - - - - - - Sets the [SkeletonModificationStack2D] that this modification is holding. This modification stack will then be executed when this modification is executed. - - - - diff --git a/doc/classes/SkeletonModification2DTwoBoneIK.xml b/doc/classes/SkeletonModification2DTwoBoneIK.xml index edd5431a0ca1..d37c97f350dd 100644 --- a/doc/classes/SkeletonModification2DTwoBoneIK.xml +++ b/doc/classes/SkeletonModification2DTwoBoneIK.xml @@ -9,71 +9,21 @@ - - - - - Returns the [Bone2D] node that is being used as the first bone in the TwoBoneIK modification. - - - - - - Returns the index of the [Bone2D] node that is being used as the first bone in the TwoBoneIK modification. - - - - - - Returns the [Bone2D] node that is being used as the second bone in the TwoBoneIK modification. - - - - - - Returns the index of the [Bone2D] node that is being used as the second bone in the TwoBoneIK modification. - - - - - - - Sets the [Bone2D] node that is being used as the first bone in the TwoBoneIK modification. - - - - - - - Sets the index of the [Bone2D] node that is being used as the first bone in the TwoBoneIK modification. - - - - - - - Sets the [Bone2D] node that is being used as the second bone in the TwoBoneIK modification. - - - - - - - Sets the index of the [Bone2D] node that is being used as the second bone in the TwoBoneIK modification. - - - If [code]true[/code], the bones in the modification will blend outward as opposed to inwards when contracting. If [code]false[/code], the bones will bend inwards when contracting. + + + + The maximum distance the target can be at. If the target is farther than this distance, the modification will solve as if it's at this maximum distance. When set to [code]0[/code], the modification will solve without distance constraints. The minimum distance the target can be at. If the target is closer than this distance, the modification will solve as if it's at this minimum distance. When set to [code]0[/code], the modification will solve without distance constraints. - + The NodePath to the node that is the target for the TwoBoneIK modification. This node is what the modification will use when bending the [Bone2D] nodes. diff --git a/doc/classes/SkeletonModification3D.xml b/doc/classes/SkeletonModification3D.xml index 84571796511d..26268a291448 100644 --- a/doc/classes/SkeletonModification3D.xml +++ b/doc/classes/SkeletonModification3D.xml @@ -1,11 +1,12 @@ - + A resource that operates on bones in a [Skeleton3D]. This resource provides an interface that can be expanded so code that operates on bones in a [Skeleton3D] can be mixed and matched together to create complex interactions. This is used to provide Godot with a flexible and powerful Inverse Kinematics solution that can be adapted for many different uses. + Modifications will update in node order. Set [member Node.process_priority] to control the order relative to other nodes or to ensure updating after [AnimationPlayer]. @@ -14,53 +15,91 @@ - Executes the given modification. This is where the modification performs whatever function it is designed to do. + Allows custom logic to run on this modification during internal process. delta is the frame time since last execute. - + + + + + Return true if this property references a bone in [member skeleton_path] and should show a bone picker. + + + + + + + Return true if this property should be hidden from the inspector. Should return true for target bones if the correspnding target nodepath is assigned. + + + - + - Sets up the modification so it can be executed. This function should be called automatically by the [SkeletonModificationStack3D] containing this modification. - If you need to initialize a modification before use, this is the place to do it! + Called if the skeleton changed or the bone list was updated. Use this to clear cached nodes and bone indices. - - - - - - + + + - Takes a angle and clamps it so it is within the passed-in [param min] and [param max] range. [param invert] will inversely clamp the angle, clamping it to the range outside of the given bounds. + Manually process the modification. If the modification is enabled, execute runs automatically during internal process. - - + + + - Returns whether this modification has been successfully setup or not. + Returns the forward direction of the bone at [param bone_idx], either taking the average child bone of children if any, or pointing away from the parent bone if [param bone_idx] represents a leaf bone. Intended to be used together with [method global_pose_z_forward_to_bone_forward] - - + + + - Returns the [SkeletonModificationStack3D] that this modification is bound to. Through the modification stack, you can access the Skeleton3D the modification is operating on. + Returns the quaternion of the bone or node, relative to [member skeleton_path]. [param resolved_target] may be an integer bone, a [Node3D] node, or the cached result of [method resolve_bone] or [method resolve_target]. - - - + + + + + Returns the [Transform3D] of the bone or node, relative to [member skeleton_path]. [param resolved_target] may be an integer bone, a [Node3D] node, or the cached result of [method resolve_bone] or [method resolve_target]. + + + + + + + + Rotates the given [Basis] so that the forward axis of the Basis is facing in the [param bone_forward_vector] of a bone, as returned by [method get_bone_rest_forward_vector]. + Use this helper function together with [method Transform3D.looking_at] to set bone poses. + + + + + + + Return the bone index for this bone name, for caching purposes. + + + + + + - Manually allows you to set the setup state of the modification. This function should only rarely be used, as the [SkeletonModificationStack3D] the modification is bound to should handle setting the modification up. + Return an opaque [Variant] representing this [param target_bone_name] or [param target_node_path] for caching purposes. Use together with [method get_target_transform] or [method get_target_quaternion]. This system allows easily building modifications which can target either bones or nodes, without requiring bone attachments. + + Returns a boolean (false) on failure. Will not return null. Any value not of a boolean type should be treated as a success. - When true, the modification's [method _execute] function will be called by the [SkeletonModificationStack3D]. + When true, the modification's [method _execute] function will be called every frame during internal process. - - The execution mode for the modification. This tells the modification stack when to execute the modification. Some modifications have settings that are only available in certain execution modes. + + Node path to the attached [Skeleton3D]. diff --git a/doc/classes/SkeletonModification3DCCDIK.xml b/doc/classes/SkeletonModification3DCCDIK.xml index dec0fbe99fc8..335a50b4355f 100644 --- a/doc/classes/SkeletonModification3DCCDIK.xml +++ b/doc/classes/SkeletonModification3DCCDIK.xml @@ -12,72 +12,57 @@ - - - - - Returns the bone index of the bone assigned to the CCDIK joint at [param joint_idx]. - - - + Returns the name of the bone that is assigned to the CCDIK joint at [param joint_idx]. - + Returns the integer representing the joint axis of the CCDIK joint at [param joint_idx]. - + Returns the maximum angle constraint for the joint at [param joint_idx]. [b]Note:[/b] This angle is in degrees! - + Returns the minimum angle constraint for the joint at [param joint_idx]. [b]Note:[/b] This angle is in degrees! - + - Returns whether the CCDIK joint at [param joint_idx] uses an inverted joint constraint. See [method set_ccdik_joint_constraint_invert] for details. + Returns whether the CCDIK joint at [param joint_idx] uses an inverted joint constraint. See [method set_joint_constraint_invert] for details. - + Enables angle constraints to the CCDIK joint at [param joint_idx]. - - - - - - Sets the bone index, [param bone_index], of the CCDIK joint at [param joint_idx]. When possible, this will also update the [code]bone_name[/code] of the CCDIK joint based on data provided by the linked skeleton. - - - + - Sets the bone name, [param bone_name], of the CCDIK joint at [param joint_idx]. When possible, this will also update the [code]bone_index[/code] of the CCDIK joint based on data provided by the linked skeleton. + Sets the bone name, [param bone_name], of the CCDIK joint at [param joint_idx]. - + @@ -85,7 +70,7 @@ Sets the joint axis of the CCDIK joint at [param joint_idx] to the passed-in joint axis, [param axis]. - + @@ -93,7 +78,7 @@ Sets the maximum angle constraint for the joint at [param joint_idx]. [b]Note:[/b] This angle must be in radians! - + @@ -101,7 +86,7 @@ Sets the minimum angle constraint for the joint at [param joint_idx]. [b]Note:[/b] This angle must be in radians! - + @@ -110,7 +95,7 @@ An inverted joint constraint only constraints the CCDIK joint to the angles [i]outside of[/i] the inputted minimum and maximum angles. For this reason, it is referred to as an inverted joint constraint, as it constraints the joint to the outside of the inputted values. - + @@ -120,16 +105,22 @@ - - The number of CCDIK joints in the CCDIK modification. - When true, the CCDIK algorithm will perform a higher quality solve that returns more natural results. A high quality solve requires more computation power to solve though, and therefore can be disabled to save performance. - + + The number of joints in the joint array for this CCDIK chain. + + + The name of the bone that is the target for the CCDIK modification, if [member target_node] is unset. This bone is what the CCDIK chain will attempt to rotate the bone chain to. + + The NodePath to the node that is the target for the CCDIK modification. This node is what the CCDIK chain will attempt to rotate the bone chain to. - + + The end position of the CCDIK chain, if [member tip_node] is unset. Typically, this should be a leaf bone child of the final bone in the CCDIK chain, where this leaf bone is offset so it is at the end of the final bone. + + The end position of the CCDIK chain. Typically, this should be a child of a [BoneAttachment3D] node attached to the final bone in the CCDIK chain, where the child node is offset so it is at the end of the final bone. diff --git a/doc/classes/SkeletonModification3DFABRIK.xml b/doc/classes/SkeletonModification3DFABRIK.xml index 325cc2a12edf..2c5b342e518f 100644 --- a/doc/classes/SkeletonModification3DFABRIK.xml +++ b/doc/classes/SkeletonModification3DFABRIK.xml @@ -13,95 +13,80 @@ - + Will attempt to automatically calculate the length of the bone assigned to the FABRIK joint at [param joint_idx]. - + Returns a boolean that indicates whether this modification will attempt to autocalculate the length of the bone assigned to the FABRIK joint at [param joint_idx]. - - - - - Returns the bone index of the bone assigned to the FABRIK joint at [param joint_idx]. - - - + Returns the name of the bone that is assigned to the FABRIK joint at [param joint_idx]. - + Returns the length of the FABRIK joint at [param joint_idx]. - + Returns the magnet vector of the FABRIK joint at [param joint_idx]. - - + + - Returns the [Node3D]-based node placed at the tip of the FABRIK joint at [param joint_idx], if one has been set. + Returns the leaf bone child of this FABRIK joint at [param joint_idx], if one has been set. - - + + - Returns a boolean indicating whether the FABRIK joint uses the target's [Basis] for its rotation. - [b]Note:[/b] This option is only available for the final bone in the FABRIK chain, with this setting being ignored for all other bones. + Returns the [Node3D]-based node placed at the tip of the FABRIK joint at [param joint_idx], if one has been set. - + - Sets the [Node3D]-based node that will be used as the tip of the FABRIK joint at [param joint_idx]. + Returns a boolean indicating whether the FABRIK joint uses the target's [Basis] for its rotation. + [b]Note:[/b] This option is only available for the final bone in the FABRIK chain, with this setting being ignored for all other bones. - + - When [code]true[/code], this modification will attempt to automatically calculate the length of the bone for the FABRIK joint at [param joint_idx]. It does this by either using the tip node assigned, if there is one assigned, or the distance the of the bone's children, if the bone has any. If the bone has no children and no tip node is assigned, then the modification [b]cannot[/b] autocalculate the joint's length. In this case, the joint length should be entered manually or a tip node assigned. + When [code]true[/code], this modification will attempt to automatically calculate the length of the bone for the FABRIK joint at [param joint_idx]. It does this by either using the tip node or bone assigned, if there is one assigned, or the distance the of the bone's children, if the bone has any. If the bone has no children and no tip node or bone is assigned, then the modification [b]cannot[/b] autocalculate the joint's length. In this case, the joint length should be entered manually or a tip node or bone assigned. - - - - - - Sets the bone index, [param bone_index], of the FABRIK joint at [param joint_idx]. When possible, this will also update the [code]bone_name[/code] of the FABRIK joint based on data provided by the [Skeleton3D]. - - - + - Sets the bone name, [param bone_name], of the FABRIK joint at [param joint_idx]. When possible, this will also update the [code]bone_index[/code] of the FABRIK joint based on data provided by the [Skeleton3D]. + Sets the bone name, [param bone_name], of the FABRIK joint at [param joint_idx]. - + @@ -109,7 +94,7 @@ Sets the joint length, [param length], of the FABRIK joint at [param joint_idx]. - + @@ -117,30 +102,31 @@ Sets the magenet position to [param magnet_position] for the joint at [param joint_idx]. The magnet position is used to nudge the joint in that direction when solving, which gives some control over how that joint will bend when being solved. - + - + - Sets the nodepath of the FARIK joint at [param joint_idx] to [param tip_node]. The tip node is used to calculate the length of the FABRIK joint when set to automatically calculate joint length. - [b]Note:[/b] The tip node should generally be a child node of a [BoneAttachment3D] node attached to the bone that this FABRIK joint operates on, with the child node being offset so it is at the end of the bone. + Sets the tip of the FABRIK joint [param joint_idx] to the bone named [param tip_bone]. The tip bone is used to calculate the length of the FABRIK joint when set to automatically calculate joint length. + [b]Note:[/b] The tip bone should generally be a leaf bone child of the bone that this FABRIK joint operates on, offset so it is at the end of the bone. Only one of [method set_joint_tip_bone] and [method set_joint_tip_node] may be used for a given joint. - + - + - Sets whether the FABRIK joint at [param joint_idx] uses the target's [Basis] for its rotation. - [b]Note:[/b] This option is only available for the final bone in the FABRIK chain, with this setting being ignored for all other bones. + Sets the tip of the FABRIK joint [param joint_idx] to the node at path [param tip_node]. The tip node is used to calculate the length of the FABRIK joint when set to automatically calculate joint length. + [b]Note:[/b] The tip node should generally be a child node of a [BoneAttachment3D] node attached to the bone that this FABRIK joint operates on, with the child node being offset so it is at the end of the bone. Only one of [method set_joint_tip_bone] and [method set_joint_tip_node] may be used for a given joint. - + - + - Sets whether the tip node should be used when autocalculating the joint length for the FABRIK joint at [param joint_idx]. This will only work if there is a node assigned to the tip nodepath for this joint. + Sets whether the FABRIK joint at [param joint_idx] uses the target's [Basis] for its rotation. + [b]Note:[/b] This option is only available for the final bone in the FABRIK chain, with this setting being ignored for all other bones. @@ -151,10 +137,13 @@ The minimum distance the target has to be from the tip of the final bone in the bone chain. Setting this value to a higher number allows for greater performance, but less accurate solves. - - The amount of FABRIK joints in the FABRIK modification. + + The number of joints in the joint array for this FABRIK chain. + + + The name of the bone that is the target for the FABRIK modification, if [member target_node] is unset. This node is what the FABRIK chain will attempt to rotate the bone chain to. - + The NodePath to the node that is the target for the FABRIK modification. This node is what the FABRIK chain will attempt to rotate the bone chain to. diff --git a/doc/classes/SkeletonModification3DJiggle.xml b/doc/classes/SkeletonModification3DJiggle.xml index ef469d42eaa9..1ce6b9538b01 100644 --- a/doc/classes/SkeletonModification3DJiggle.xml +++ b/doc/classes/SkeletonModification3DJiggle.xml @@ -17,63 +17,56 @@ Returns the collision mask that the Jiggle modifier will take into account when performing physics calculations. - - - - - Returns the bone index of the bone assigned to the Jiggle joint at [param joint_idx]. - - - + Returns the name of the bone that is assigned to the Jiggle joint at [param joint_idx]. - + Returns the amount of dampening of the Jiggle joint at [param joint_idx]. - + Returns a [Vector3] representign the amount of gravity the Jiggle joint at [param joint_idx] is influenced by. - + Returns the amount of mass of the Jiggle joint at [param joint_idx]. - + Returns a boolean that indicates whether the joint at [param joint_idx] is overriding the default jiggle joint data defined in the modification. - + Returns the amount of roll/twist applied to the bone that the Jiggle joint is applied to. - + Returns the stiffness of the Jiggle joint at [param joint_idx]. - + @@ -93,23 +86,15 @@ Sets the collision mask that the Jiggle modifier takes into account when performing physics calculations. - - - - - - Sets the bone index, [param bone_idx], of the Jiggle joint at [param joint_idx]. When possible, this will also update the [code]bone_name[/code] of the Jiggle joint based on data provided by the [Skeleton3D]. - - - + - Sets the bone name, [param name], of the Jiggle joint at [param joint_idx]. When possible, this will also update the [code]bone_index[/code] of the Jiggle joint based on data provided by the [Skeleton3D]. + Sets the bone name, [param name], of the Jiggle joint at [param joint_idx]. - + @@ -117,7 +102,7 @@ Sets the amount of dampening of the Jiggle joint at [param joint_idx]. - + @@ -125,7 +110,7 @@ Sets the gravity vector of the Jiggle joint at [param joint_idx]. - + @@ -133,7 +118,7 @@ Sets the of mass of the Jiggle joint at [param joint_idx]. - + @@ -141,7 +126,7 @@ Sets whether the Jiggle joint at [param joint_idx] should override the default Jiggle joint settings. Setting this to true will make the joint use its own settings rather than the default ones attached to the modification. - + @@ -149,7 +134,7 @@ Sets the amount of roll/twist on the bone the Jiggle joint is attached to. - + @@ -157,7 +142,7 @@ Sets the of stiffness of the Jiggle joint at [param joint_idx]. - + @@ -180,8 +165,7 @@ The default amount of gravity applied to the Jiggle joints, if they are not overridden. - - The amount of Jiggle joints in the Jiggle modification. + The default amount of mass assigned to the Jiggle joints, if they are not overridden. Higher values lead to faster movements and more overshooting. @@ -189,7 +173,10 @@ The default amount of stiffness assigned to the Jiggle joints, if they are not overridden. Higher values act more like springs, quickly moving into the correct position. - + + The name of the bone that is the target for the Jiggle modification, if [member target_node] is unset. This node is what the Jiggle chain will attempt to rotate the bone chain to. + + The NodePath to the node that is the target for the Jiggle modification. This node is what the Jiggle chain will attempt to rotate the bone chain to. diff --git a/doc/classes/SkeletonModification3DLookAt.xml b/doc/classes/SkeletonModification3DLookAt.xml index 3602cfad955a..2c6cd8a56b17 100644 --- a/doc/classes/SkeletonModification3DLookAt.xml +++ b/doc/classes/SkeletonModification3DLookAt.xml @@ -8,57 +8,35 @@ - - - - - Returns the amount of extra rotation that is applied to the bone after the LookAt modification executes. - - - - - - Returns the plane that the LookAt modification is limiting rotation to. - - - - - - Returns whether the LookAt modification is limiting rotation to a single plane in 3D space. - - - - - - - Sets the amount of extra rotation to be applied after the LookAt modification executes. This allows you to adjust the finished result. - - - - - - - - - - - - - When [code]true[/code], the LookAt modification will limit its rotation to a single plane in 3D space. The plane used can be configured using the [code]set_lock_rotation_plane[/code] function. - - - - - The bone index of the bone that should be operated on by this modification. - When possible, this will also update the [member bone_name] based on data provided by the [Skeleton3D]. + + The amount of extra rotation that is applied to the bone after the LookAt modification executes. - - The name of the bone that should be operated on by this modification. - When possible, this will also update the [member bone_index] based on data provided by the [Skeleton3D]. + + The name of the bone that should be rotated by this modification. - - The NodePath to the node that is the target for the modification. + + The plane that the LookAt modification is limiting rotation to. If set to anything other than ROTATION_UNLOCKED, the LookAt modification will limit its rotation to this single plane in 3D space. + + + The name of the target bone this [member bone] will aim towards. + + + The NodePath to the node that this [member bone] will aim towards. + + + This bone will not be constrained. + + + This bone will be constrained to the X-axis. + + + This bone will be constrained to the X-axis. + + + This bone will be constrained to the X-axis. + + diff --git a/doc/classes/SkeletonModification3DStackHolder.xml b/doc/classes/SkeletonModification3DStackHolder.xml deleted file mode 100644 index 24240236a45e..000000000000 --- a/doc/classes/SkeletonModification3DStackHolder.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - A modification that holds and executes a [SkeletonModificationStack3D]. - - - This [SkeletonModification3D] holds a reference to a [SkeletonModificationStack3D], allowing you to use multiple modification stacks on a single [Skeleton3D]. - [b]Note:[/b] The modifications in the held [SkeletonModificationStack3D] will only be executed if their execution mode matches the execution mode of the SkeletonModification3DStackHolder. - - - - - - - - Returns the [SkeletonModificationStack3D] that this modification is holding. - - - - - - - Sets the [SkeletonModificationStack3D] that this modification is holding. This modification stack will then be executed when this modification is executed. - - - - diff --git a/doc/classes/SkeletonModification3DTwoBoneIK.xml b/doc/classes/SkeletonModification3DTwoBoneIK.xml index 6618ebbcfb70..d343b954f0ee 100644 --- a/doc/classes/SkeletonModification3DTwoBoneIK.xml +++ b/doc/classes/SkeletonModification3DTwoBoneIK.xml @@ -10,182 +10,58 @@ - - - - - Returns whether the TwoBoneIK modification will attempt to autocalculate the lengths of the two bones. - - - - - - Returns the bone index of the first bone in the TwoBoneIK modification. - - - - - - Returns the name of the first bone in the TwoBoneIK modification. - - - - - - Returns the length of the first bone in the TwoBoneIK modification. - - - - - - Returns the amount of roll/twist applied to the first bone in the TwoBoneIK modification. - - - - - - Returns the bone index of the second bone in the TwoBoneIK modification. - - - - - - Returns the name of the second bone in the TwoBoneIK modification. - - - - - - Returns the length of the second bone in the TwoBoneIK modification. - - - - - - Returns the amount of roll/twist applied to the second bone in the TwoBoneIK modification. - - - - - - Returns the node that is being used as the pole node for the TwoBoneIK modification, if a pole node has been set. - - - - - - Returns the node that is being used to calculate the tip position of the second bone in the TwoBoneIK modification, if a tip node has been set. - - - - - - Returns whether the TwoBoneIK modification will attempt to use the pole node to figure out which direction to bend, if a pole node has been set. - - - - - - Returns whether the TwoBoneIK modification will attempt to use the tip node to figure out the length and position of the tip of the second bone. - - - - - - - If true, the TwoBoneIK modification will attempt to autocalculate the lengths of the bones being used. The first bone will be calculated by using the distance from the origin of the first bone to the origin of the second bone. - The second bone will be calculated either using the tip node if that setting is enabled, or by using the distances of the second bone's children. If the tip node is not enabled and the bone has no children, then the length cannot be autocalculated. In this case, the length will either have to be manually inputted or a tip node used to calculate the length. - - - - - - - Sets the bone index, [param bone_idx], of the first bone. When possible, this will also update the [code]bone_name[/code] of the first bone based on data provided by the [Skeleton3D]. - - - - - - - Sets the bone name, [param bone_name], of the first bone. When possible, this will also update the [code]bone_index[/code] of the first bone based on data provided by the [Skeleton3D]. - - - - - - - Sets the length of the first bone in the TwoBoneIK modification. - - - - - - - Sets the amount of roll/twist applied to the first bone in the TwoBoneIK modification. - - - - - - - Sets the bone index, [param bone_idx], of the second bone. When possible, this will also update the [code]bone_name[/code] of the second bone based on data provided by the [Skeleton3D]. - - - - - - - Sets the bone name, [param bone_name], of the second bone. When possible, this will also update the [code]bone_index[/code] of the second bone based on data provided by the [Skeleton3D]. - - - - - - - Sets the length of the second bone in the TwoBoneIK modification. - - - - - - - Sets the amount of roll/twist applied to the second bone in the TwoBoneIK modification. - - - - - - - Sets the node to be used as the for the pole of the TwoBoneIK. When a node is set and the modification is set to use the pole node, the TwoBoneIK modification will bend the nodes in the direction towards this node when the bones need to bend. - - - - - - - Sets the node to be used as the tip for the second bone. This is used to calculate the length and position of the end of the second bone in the TwoBoneIK modification. - [b]Note:[/b] The tip node should generally be a child node of a [BoneAttachment3D] node attached to the second bone, with the child node being offset so it is at the end of the bone. - - - - - - - When [code]true[/code], the TwoBoneIK modification will bend the bones towards the pole node, if one has been set. This gives control over the direction the TwoBoneIK solver will bend, which is helpful for joints like elbows that only bend in certain directions. - - - - - - - When [code]true[/code], the TwoBoneIK modification will use the tip node to calculate the distance and position of the end/tip of the second bone. This is the most stable solution for knowing the tip position and length of the second bone. - - - - + + Whether the TwoBoneIK modification will attempt to autocalculate the lengths of the two bones. + + If true, the TwoBoneIK modification will attempt to autocalculate the lengths of the bones being used. The first bone will be calculated by using the distance from the origin of the first bone to the origin of the second bone. + The second bone will be calculated either using the tip node if that setting is enabled, or by using the distances of the second bone's children. If the tip node is not enabled and the bone has no children, then the length cannot be autocalculated. In this case, the length will either have to be manually inputted or a tip node used to calculate the length. + + + The name of the first bone in the TwoBoneIK modification. + + + The length of the first bone in the TwoBoneIK modification. + + + The amount of roll/twist applied to the first bone in the TwoBoneIK modification. + + + The name of the second bone in the TwoBoneIK modification. + + + The length of the second bone in the TwoBoneIK modification. + + + The amount of roll/twist applied to the second bone in the TwoBoneIK modification. + + + Optional bone name that is being used as the pole node for the TwoBoneIK modification, if [member pole_node] is unset. + + When assigned, the TwoBoneIK modification will bend the bones in the direction towards this bone when the bones need to bend. This gives control over the direction the TwoBoneIK solver will bend, which is helpful for joints like elbows that only bend in certain directions. + + + Optional node that is being used as the pole node for the TwoBoneIK modification. + + When assigned, the TwoBoneIK modification will bend the bones in the direction towards this node when the bones need to bend. This gives control over the direction the TwoBoneIK solver will bend, which is helpful for joints like elbows that only bend in certain directions. + + + The bone name that is the target for the TwoBoneIK modification, if [member target_node] is unset. This node is what the modification will attempt to rotate the bones to reach. + + The NodePath to the node that is the target for the TwoBoneIK modification. This node is what the modification will attempt to rotate the bones to reach. + + Optional bone name that is being used as the tip for the TwoBoneIK modification, if [member tip_node] is unset. + + If set, the TwoBoneIK modification will attempt to use the tip bone to figure out the length and position of the tip of the second bone. + [b]Note:[/b] The tip bone should generally be a child of the second bone, being offset so it is at the end of the bone. + + + Optional node that is being used as the tip for the TwoBoneIK modification. + + If set, the TwoBoneIK modification will attempt to use the tip node to figure out the length and position of the tip of the second bone. This is the most stable solution for knowing the tip position and length of the second bone. + [b]Note:[/b] The tip node should generally be a child node of a [BoneAttachment3D] node attached to the second bone, with the child node being offset so it is at the end of the bone. + diff --git a/doc/classes/SkeletonModificationStack2D.xml b/doc/classes/SkeletonModificationStack2D.xml deleted file mode 100644 index 950e52e62271..000000000000 --- a/doc/classes/SkeletonModificationStack2D.xml +++ /dev/null @@ -1,89 +0,0 @@ - - - - A resource that holds a stack of [SkeletonModification2D]s. - - - This resource is used by the Skeleton and holds a stack of [SkeletonModification2D]s. - This controls the order of the modifications and how they are applied. Modification order is especially important for full-body IK setups, as you need to execute the modifications in the correct order to get the desired results. For example, you want to execute a modification on the spine [i]before[/i] the arms on a humanoid skeleton. - This resource also controls how strongly all of the modifications are applied to the [Skeleton2D]. - - - - - - - - - Adds the passed-in [SkeletonModification2D] to the stack. - - - - - - - Deletes the [SkeletonModification2D] at the index position [param mod_idx], if it exists. - - - - - - - Enables all [SkeletonModification2D]s in the stack. - - - - - - - - Executes all of the [SkeletonModification2D]s in the stack that use the same execution mode as the passed-in [param execution_mode], starting from index [code]0[/code] to [member modification_count]. - [b]Note:[/b] The order of the modifications can matter depending on the modifications. For example, modifications on a spine should operate before modifications on the arms in order to get proper results. - - - - - - Returns a boolean that indicates whether the modification stack is setup and can execute. - - - - - - - Returns the [SkeletonModification2D] at the passed-in index, [param mod_idx]. - - - - - - Returns the [Skeleton2D] node that the SkeletonModificationStack2D is bound to. - - - - - - - - Sets the modification at [param mod_idx] to the passed-in modification, [param modification]. - - - - - - Sets up the modification stack so it can execute. This function should be called by [Skeleton2D] and shouldn't be manually called unless you know what you are doing. - - - - - - If [code]true[/code], the modification's in the stack will be called. This is handled automatically through the [Skeleton2D] node. - - - The number of modifications in the stack. - - - The interpolation strength of the modifications in stack. A value of [code]0[/code] will make it where the modifications are not applied, a strength of [code]0.5[/code] will be half applied, and a strength of [code]1[/code] will allow the modifications to be fully applied and override the [Skeleton2D] [Bone2D] poses. - - - diff --git a/doc/classes/SkeletonModificationStack3D.xml b/doc/classes/SkeletonModificationStack3D.xml deleted file mode 100644 index 34c7099bca2c..000000000000 --- a/doc/classes/SkeletonModificationStack3D.xml +++ /dev/null @@ -1,88 +0,0 @@ - - - - A resource that holds a stack of [SkeletonModification3D]s. - - - This resource is used by the Skeleton and holds a stack of [SkeletonModification3D]s. The SkeletonModificationStack3D controls the order of the modifications, which controls how they are applied. Modification order is especially important for full-body IK setups, as you need to execute the modifications in the correct order to get the desired results. For example, you want to execute a modification on the spine [i]before[/i] the arms on a humanoid skeleton. - Additionally, the SkeletonModificationStack3D also controls how strongly the modifications are applied to the [Skeleton3D] node. - - - - - - - - - Adds the passed-in [SkeletonModification3D] to the stack. - - - - - - - Deletes the [SkeletonModification3D] at the index position [param mod_idx], if it exists. - - - - - - - Enables all [SkeletonModification3D]s in the stack. - - - - - - - - Executes all of the [SkeletonModification3D]s in the stack that use the same execution mode as the passed-in [param execution_mode], starting from index [code]0[/code] to [member modification_count]. - [b]Note:[/b] The order of the modifications can matter depending on the modifications. For example, modifications on a spine should operate before modifications on the arms in order to get proper results. - - - - - - Returns a boolean that indicates whether the modification stack is setup and can execute. - - - - - - - Returns the [SkeletonModification3D] at the passed-in index, [param mod_idx]. - - - - - - Returns the [Skeleton3D] node that the SkeletonModificationStack3D is bound to. - - - - - - - - Sets the modification at [param mod_idx] to the passed-in modification, [param modification]. - - - - - - Sets up the modification stack so it can execute. This function should be called by [Skeleton3D] and shouldn't be called unless you know what you are doing. - - - - - - When true, the modification's in the stack will be called. This is handled automatically through the [Skeleton3D] node. - - - The number of modifications in the stack. - - - The interpolation strength of the modifications in stack. A value of [code]0[/code] will make it where the modifications are not applied, a strength of [code]0.5[/code] will be half applied, and a strength of [code]1[/code] will allow the modifications to be fully applied and override the skeleton bone poses. - - - diff --git a/editor/project_converter_3_to_4.cpp b/editor/project_converter_3_to_4.cpp index 9c717a051876..b8262ba9c0e2 100644 --- a/editor/project_converter_3_to_4.cpp +++ b/editor/project_converter_3_to_4.cpp @@ -1442,7 +1442,7 @@ static const char *class_renames[][2] = { { "Shape", "Shape3D" }, // Be careful, this will be used everywhere { "ShortCut", "Shortcut" }, { "Skeleton", "Skeleton3D" }, - { "SkeletonIK", "SkeletonIK3D" }, + { "SkeletonIK", "SkeletonModification3DFABRIK" }, { "SliderJoint", "SliderJoint3D" }, { "SoftBody", "SoftBody3D" }, { "Spatial", "Node3D" }, diff --git a/scene/2d/physical_bone_2d.cpp b/scene/2d/physical_bone_2d.cpp index e6933b8a408c..69d6cb9ed49b 100644 --- a/scene/2d/physical_bone_2d.cpp +++ b/scene/2d/physical_bone_2d.cpp @@ -34,21 +34,9 @@ void PhysicalBone2D::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { - // Position the RigidBody in the correct position. - if (follow_bone_when_simulating) { - _position_at_bone2d(); - } - - // Keep the child joint in the correct position. - if (child_joint && auto_configure_joint) { - child_joint->set_global_position(get_global_position()); - } - } break; - case NOTIFICATION_READY: { - _find_skeleton_parent(); _find_joint_child(); + bone_node_cache = Variant(get_node(bone_nodepath)); // Configure joint. if (child_joint && auto_configure_joint) { @@ -64,35 +52,39 @@ void PhysicalBone2D::_notification(int p_what) { set_physics_process_internal(true); } break; - } -} + case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { + // Position the RigidBody in the correct position. + if (follow_bone_when_simulating) { + _position_at_bone2d(); + } -void PhysicalBone2D::_position_at_bone2d() { - // Reset to Bone2D position - if (parent_skeleton) { - Bone2D *bone_to_use = parent_skeleton->get_bone(bone2d_index); - ERR_FAIL_COND_MSG(bone_to_use == nullptr, "It's not possible to position the bone with ID: " + itos(bone2d_index)); - set_global_transform(bone_to_use->get_global_transform()); + // Keep the child joint in the correct position. + if (child_joint && auto_configure_joint) { + child_joint->set_global_position(get_global_position()); + } + } break; + // Currently updated using SkeletonModification2DPhysicalBones + //case NOTIFICATION_INTERNAL_PROCESS: { + // // Apply the simulation result. TODO: interpolation? + // if (simulate_physics && !follow_bone_when_simulating) { + // if (parent_skeleton) { + // Bone2D *bone_to_use = cast_to((Object*)bone_node_cache); + // bone_to_use->set_global_transform(get_global_transform()); + // } + // } + //} break; } } -void PhysicalBone2D::_find_skeleton_parent() { - Node *current_parent = get_parent(); +Node2D *PhysicalBone2D::get_cached_bone_node() { + return cast_to((Object *)bone_node_cache); +} - while (current_parent != nullptr) { - Skeleton2D *potential_skeleton = Object::cast_to(current_parent); - if (potential_skeleton) { - parent_skeleton = potential_skeleton; - break; - } else { - PhysicalBone2D *potential_parent_bone = Object::cast_to(current_parent); - if (potential_parent_bone) { - current_parent = potential_parent_bone->get_parent(); - } else { - current_parent = nullptr; - } - } - } +void PhysicalBone2D::_position_at_bone2d() { + // Reset to Bone2D position + Node2D *bone_to_use = get_cached_bone_node(); + ERR_FAIL_COND(bone_to_use == nullptr); + set_global_transform(bone_to_use->get_global_transform()); } void PhysicalBone2D::_find_joint_child() { @@ -109,12 +101,6 @@ void PhysicalBone2D::_find_joint_child() { TypedArray PhysicalBone2D::get_configuration_warnings() const { TypedArray warnings = Node::get_configuration_warnings(); - if (!parent_skeleton) { - warnings.push_back(RTR("A PhysicalBone2D only works with a Skeleton2D or another PhysicalBone2D as a parent node!")); - } - if (parent_skeleton && bone2d_index <= -1) { - warnings.push_back(RTR("A PhysicalBone2D needs to be assigned to a Bone2D node in order to function! Please set a Bone2D node in the inspector.")); - } if (!child_joint) { PhysicalBone2D *parent_bone = Object::cast_to(get_parent()); if (parent_bone) { @@ -161,6 +147,8 @@ void PhysicalBone2D::_start_physics_simulation() { PhysicsServer2D::get_singleton()->body_set_collision_priority(get_rid(), get_collision_priority()); // Apply the correct mode. + set_freeze_mode(RigidBody2D::FREEZE_MODE_KINEMATIC); + set_freeze_enabled(follow_bone_when_simulating ? true : false); _apply_body_mode(); _internal_simulate_physics = true; @@ -178,7 +166,11 @@ void PhysicalBone2D::_stop_physics_simulation() { PhysicsServer2D::get_singleton()->body_set_collision_layer(get_rid(), 0); PhysicsServer2D::get_singleton()->body_set_collision_mask(get_rid(), 0); PhysicsServer2D::get_singleton()->body_set_collision_priority(get_rid(), 1.0); - PhysicsServer2D::get_singleton()->body_set_mode(get_rid(), PhysicsServer2D::BodyMode::BODY_MODE_STATIC); + + // Apply the correct mode. + set_freeze_mode(RigidBody2D::FREEZE_MODE_STATIC); + set_freeze_enabled(true); + _apply_body_mode(); } } @@ -216,47 +208,21 @@ bool PhysicalBone2D::is_simulating_physics() const { return _internal_simulate_physics; } -void PhysicalBone2D::set_bone2d_nodepath(const NodePath &p_nodepath) { - bone2d_nodepath = p_nodepath; - notify_property_list_changed(); -} - -NodePath PhysicalBone2D::get_bone2d_nodepath() const { - return bone2d_nodepath; -} - -void PhysicalBone2D::set_bone2d_index(int p_bone_idx) { - ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!"); - - if (!is_inside_tree()) { - bone2d_index = p_bone_idx; - return; - } - - if (parent_skeleton) { - ERR_FAIL_INDEX_MSG(p_bone_idx, parent_skeleton->get_bone_count(), "Passed-in Bone index is out of range!"); - bone2d_index = p_bone_idx; - - bone2d_nodepath = get_path_to(parent_skeleton->get_bone(bone2d_index)); - } else { - WARN_PRINT("Cannot verify bone index..."); - bone2d_index = p_bone_idx; +void PhysicalBone2D::set_bone_node(const NodePath &p_nodepath) { + bone_nodepath = p_nodepath; + if (is_inside_tree()) { + bone_node_cache = get_node(bone_nodepath); } - notify_property_list_changed(); } -int PhysicalBone2D::get_bone2d_index() const { - return bone2d_index; +NodePath PhysicalBone2D::get_bone_node() const { + return bone_nodepath; } void PhysicalBone2D::set_follow_bone_when_simulating(bool p_follow_bone) { follow_bone_when_simulating = p_follow_bone; - - if (_internal_simulate_physics) { - _stop_physics_simulation(); - _start_physics_simulation(); - } + set_freeze_enabled(follow_bone_when_simulating ? true : false); } bool PhysicalBone2D::get_follow_bone_when_simulating() const { @@ -272,15 +238,13 @@ void PhysicalBone2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_simulate_physics"), &PhysicalBone2D::get_simulate_physics); ClassDB::bind_method(D_METHOD("is_simulating_physics"), &PhysicalBone2D::is_simulating_physics); - ClassDB::bind_method(D_METHOD("set_bone2d_nodepath", "nodepath"), &PhysicalBone2D::set_bone2d_nodepath); - ClassDB::bind_method(D_METHOD("get_bone2d_nodepath"), &PhysicalBone2D::get_bone2d_nodepath); - ClassDB::bind_method(D_METHOD("set_bone2d_index", "bone_index"), &PhysicalBone2D::set_bone2d_index); - ClassDB::bind_method(D_METHOD("get_bone2d_index"), &PhysicalBone2D::get_bone2d_index); + ClassDB::bind_method(D_METHOD("get_cached_bone_node"), &PhysicalBone2D::get_cached_bone_node); + ClassDB::bind_method(D_METHOD("set_bone_node", "nodepath"), &PhysicalBone2D::set_bone_node); + ClassDB::bind_method(D_METHOD("get_bone_node"), &PhysicalBone2D::get_bone_node); ClassDB::bind_method(D_METHOD("set_follow_bone_when_simulating", "follow_bone"), &PhysicalBone2D::set_follow_bone_when_simulating); ClassDB::bind_method(D_METHOD("get_follow_bone_when_simulating"), &PhysicalBone2D::get_follow_bone_when_simulating); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "bone2d_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Bone2D"), "set_bone2d_nodepath", "get_bone2d_nodepath"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "bone2d_index", PROPERTY_HINT_RANGE, "-1, 1000, 1"), "set_bone2d_index", "get_bone2d_index"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "bone_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node2D"), "set_bone_node", "get_bone_node"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_configure_joint"), "set_auto_configure_joint", "get_auto_configure_joint"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "simulate_physics"), "set_simulate_physics", "get_simulate_physics"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "follow_bone_when_simulating"), "set_follow_bone_when_simulating", "get_follow_bone_when_simulating"); diff --git a/scene/2d/physical_bone_2d.h b/scene/2d/physical_bone_2d.h index 9fbfa0410079..e82926678fe1 100644 --- a/scene/2d/physical_bone_2d.h +++ b/scene/2d/physical_bone_2d.h @@ -44,9 +44,8 @@ class PhysicalBone2D : public RigidBody2D { static void _bind_methods(); private: - Skeleton2D *parent_skeleton = nullptr; - int bone2d_index = -1; - NodePath bone2d_nodepath; + NodePath bone_nodepath; + mutable Variant bone_node_cache; bool follow_bone_when_simulating = false; Joint2D *child_joint = nullptr; @@ -55,7 +54,6 @@ class PhysicalBone2D : public RigidBody2D { bool simulate_physics = false; bool _internal_simulate_physics = false; - void _find_skeleton_parent(); void _find_joint_child(); void _auto_configure_joint(); @@ -72,10 +70,9 @@ class PhysicalBone2D : public RigidBody2D { bool get_simulate_physics() const; bool is_simulating_physics() const; - void set_bone2d_nodepath(const NodePath &p_nodepath); - NodePath get_bone2d_nodepath() const; - void set_bone2d_index(int p_bone_idx); - int get_bone2d_index() const; + Node2D *get_cached_bone_node(); + void set_bone_node(const NodePath &p_nodepath); + NodePath get_bone_node() const; void set_follow_bone_when_simulating(bool p_follow); bool get_follow_bone_when_simulating() const; diff --git a/scene/2d/skeleton_2d.h b/scene/2d/skeleton_2d.h index 2e4c55dc44e4..d2b3c42d6333 100644 --- a/scene/2d/skeleton_2d.h +++ b/scene/2d/skeleton_2d.h @@ -151,8 +151,6 @@ class Skeleton2D : public Node2D { void set_bone_local_pose_override(int p_bone_idx, Transform2D p_override, real_t p_amount, bool p_persistent = true); Transform2D get_bone_local_pose_override(int p_bone_idx); - void execute_modifications(real_t p_delta, int p_execution_mode); - Skeleton2D(); ~Skeleton2D(); }; diff --git a/scene/2d/skeleton_modification_2d.cpp b/scene/2d/skeleton_modification_2d.cpp index d28a39e558fa..f9678e4190e0 100644 --- a/scene/2d/skeleton_modification_2d.cpp +++ b/scene/2d/skeleton_modification_2d.cpp @@ -29,6 +29,8 @@ /*************************************************************************/ #include "skeleton_modification_2d.h" +#include "scene/scene_string_names.h" + #include "scene/2d/skeleton_2d.h" #include "scene/2d/collision_object_2d.h" @@ -43,8 +45,10 @@ // Modification2D /////////////////////////////////////// -void SkeletonModification2D::_draw_editor_gizmo() { - GDVIRTUAL_CALL(_draw_editor_gizmo); +void SkeletonModification2D::_validate_property(PropertyInfo &p_property) const { + if (is_property_hidden(p_property.name)) { + p_property.usage = PROPERTY_USAGE_NO_EDITOR; + } } void SkeletonModification2D::set_enabled(bool p_enabled) { @@ -91,11 +95,163 @@ float SkeletonModification2D::clamp_angle(float p_angle, float p_min_bound, floa return p_angle; } +NodePath SkeletonModification2D::get_skeleton_path() const { + return skeleton_path; +} + +void SkeletonModification2D::set_skeleton_path(NodePath p_path) { + if (p_path.is_empty()) { + p_path = NodePath(".."); + } +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint() && get_skeleton()) { + get_skeleton()->disconnect(SceneStringNames::get_singleton()->draw, Callable(this, SNAME("draw_editor_gizmo"))); + } +#endif + skeleton_path = p_path; + skeleton_change_queued = true; + cached_skeleton = Variant(); +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint() && get_skeleton()) { + get_skeleton()->connect(SceneStringNames::get_singleton()->draw, Callable(this, SNAME("draw_editor_gizmo"))); + } + update_configuration_warnings(); +#endif +} + +Skeleton2D *SkeletonModification2D::get_skeleton() const { + Skeleton2D *skeleton_node = cast_to(cached_skeleton); + if (skeleton_node == nullptr) { + skeleton_node = cast_to(get_node_or_null(skeleton_path)); + cached_skeleton = skeleton_node; + } + return skeleton_node; +} + +void SkeletonModification2D::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + set_process_internal(get_enabled()); + set_physics_process_internal(false); + cached_skeleton = Variant(); +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + call_deferred(SNAME("update_configuration_warnings")); + } +#endif + } break; + case NOTIFICATION_READY: { +#ifdef TOOLS_ENABLED + Skeleton2D *skel = get_skeleton(); + if (Engine::get_singleton()->is_editor_hint() && skel) { + skel->connect(SceneStringNames::get_singleton()->draw, Callable(this, SNAME("draw_editor_gizmo"))); + } +#endif + } break; + case NOTIFICATION_INTERNAL_PROCESS: { + ERR_FAIL_COND(!enabled); + execute(get_process_delta_time()); + } break; + } +} + +void SkeletonModification2D::_do_gizmo_draw() { +#ifdef TOOLS_ENABLED + if (editor_gizmo_dirty && Engine::get_singleton()->is_editor_hint()) { + draw_editor_gizmo(); + Skeleton2D *skel = get_skeleton(); + if (skel) { + skel->draw_set_transform(Vector2(0, 0)); + } + editor_gizmo_dirty = false; + } +#endif // TOOLS_ENABLED +} + +void SkeletonModification2D::draw_editor_gizmo() { + GDVIRTUAL_CALL(_draw_editor_gizmo); +} + +void SkeletonModification2D::set_editor_gizmo_dirty(bool p_dirty) { +#ifdef TOOLS_ENABLED + if (!editor_gizmo_dirty && p_dirty && Engine::get_singleton()->is_editor_hint()) { + editor_gizmo_dirty = p_dirty; + Skeleton2D *skeleton = get_skeleton(); + if (skeleton) { + skeleton->queue_redraw(); + } + } else { + editor_gizmo_dirty = p_dirty; + } +#endif +} + +Variant SkeletonModification2D::resolve_node(const NodePath &target_node_path) const { + Node *resolved_node = get_node(target_node_path); + if (cast_to(resolved_node)) { + return Variant(resolved_node); + } + return Variant(false); +} + +Variant SkeletonModification2D::resolve_bone(const NodePath &target_node_path) const { + Node *resolved_node = get_node(target_node_path); + if (cast_to(resolved_node)) { + return Variant(resolved_node); + } + return Variant(false); +} + +bool SkeletonModification2D::_cache_node(Variant &cache, const NodePath &target_node_path) const { + if (cache.get_type() == Variant::NIL) { + cache = resolve_node(target_node_path); + } + return cache.get_type() == Variant::OBJECT; +} + +Bone2D *SkeletonModification2D::_cache_bone(Variant &cache, const NodePath &target_node_path) const { + if (cache.get_type() == Variant::NIL) { + cache = resolve_node(target_node_path); + } + if (cache.get_type() == Variant::OBJECT) { + return cast_to((Object *)cache); + } + return nullptr; +} + +Transform2D SkeletonModification2D::get_target_transform(Variant resolved_target) const { + if (resolved_target.get_type() == Variant::OBJECT) { + CanvasItem *resolved_node = cast_to((Object *)resolved_target); + return resolved_node->get_global_transform(); + } + ERR_FAIL_V_MSG(Transform2D(), "Looking up transform of unresolved target."); +} + +real_t SkeletonModification2D::get_target_rotation(Variant resolved_target) const { + if (resolved_target.get_type() == Variant::OBJECT) { + CanvasItem *resolved_node = cast_to((Object *)resolved_target); + return resolved_node->get_global_transform().get_rotation(); + } + ERR_FAIL_V_MSG(0.0f, "Looking up quaternion of unresolved target."); +} + +Vector2 SkeletonModification2D::get_target_position(Variant resolved_target) const { + if (resolved_target.get_type() == Variant::OBJECT) { + CanvasItem *resolved_node = cast_to((Object *)resolved_target); + return resolved_node->get_global_transform().get_origin(); + } + ERR_FAIL_V_MSG(Vector2(), "Looking up quaternion of unresolved target."); +} + void SkeletonModification2D::editor_draw_angle_constraints(Bone2D *p_operation_bone, float p_min_bound, float p_max_bound, bool p_constraint_enabled, bool p_constraint_in_localspace, bool p_constraint_inverted) { if (!p_operation_bone) { return; } + Skeleton2D *skeleton = get_skeleton(); + if (!skeleton) { + return; + } Color bone_ik_color = Color(1.0, 0.65, 0.0, 0.4); #ifdef TOOLS_ENABLED @@ -151,43 +307,46 @@ void SkeletonModification2D::editor_draw_angle_constraints(Bone2D *p_operation_b } } -void SkeletonModification2D::set_is_setup(bool p_setup) { - is_setup = p_setup; -} +void SkeletonModification2D::_bind_methods() { + GDVIRTUAL_BIND(_execute, "delta"); + GDVIRTUAL_BIND(_draw_editor_gizmo); + GDVIRTUAL_BIND(_is_property_hidden, "property_name"); -bool SkeletonModification2D::get_is_setup() const { - return is_setup; -} + ClassDB::bind_method(D_METHOD("set_skeleton_path", "path"), &SkeletonModification2D::set_skeleton_path); + ClassDB::bind_method(D_METHOD("get_skeleton_path"), &SkeletonModification2D::get_skeleton_path); + ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &SkeletonModification2D::set_enabled); + ClassDB::bind_method(D_METHOD("get_enabled"), &SkeletonModification2D::get_enabled); + ClassDB::bind_method(D_METHOD("execute", "delta"), &SkeletonModification2D::execute); + ClassDB::bind_method(D_METHOD("draw_editor_gizmo"), &SkeletonModification2D::_do_gizmo_draw); + ClassDB::bind_method(D_METHOD("set_editor_gizmo_dirty", "is_dirty"), &SkeletonModification2D::set_editor_gizmo_dirty); -void SkeletonModification2D::set_execution_mode(int p_mode) { - execution_mode = p_mode; -} + ClassDB::bind_method(D_METHOD("clamp_angle", "angle", "min", "max", "invert"), &SkeletonModification2D::clamp_angle); + ClassDB::bind_method(D_METHOD("editor_draw_angle_constraints", "p_operation_bone", "min_bound", "max_bound", "constraint_enabled", "constraint_in_localspace", "constraint_inverted"), &SkeletonModification2D::editor_draw_angle_constraints); -int SkeletonModification2D::get_execution_mode() const { - return execution_mode; -} + ClassDB::bind_method(D_METHOD("resolve_node", "target_node_path"), &SkeletonModification2D::resolve_node); + ClassDB::bind_method(D_METHOD("resolve_bone", "target_bone_path"), &SkeletonModification2D::resolve_bone); + ClassDB::bind_method(D_METHOD("get_target_transform", "resolved_target"), &SkeletonModification2D::get_target_transform); + ClassDB::bind_method(D_METHOD("get_target_rotation", "resolved_target"), &SkeletonModification2D::get_target_rotation); + ClassDB::bind_method(D_METHOD("get_target_position", "resolved_target"), &SkeletonModification2D::get_target_position); -void SkeletonModification2D::set_editor_draw_gizmo(bool p_draw_gizmo) { - editor_draw_gizmo = p_draw_gizmo; + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "get_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "skeleton_path"), "set_skeleton_path", "get_skeleton_path"); } -bool SkeletonModification2D::get_editor_draw_gizmo() const { - return editor_draw_gizmo; +void SkeletonModification2D::execute(real_t p_delta) { + GDVIRTUAL_CALL(_execute, p_delta); } -void SkeletonModification2D::_bind_methods() { - GDVIRTUAL_BIND(_draw_editor_gizmo) - - ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &SkeletonModification2D::set_enabled); - ClassDB::bind_method(D_METHOD("get_enabled"), &SkeletonModification2D::get_enabled); - ClassDB::bind_method(D_METHOD("set_is_setup", "is_setup"), &SkeletonModification2D::set_is_setup); - ClassDB::bind_method(D_METHOD("get_is_setup"), &SkeletonModification2D::get_is_setup); - ClassDB::bind_method(D_METHOD("set_execution_mode", "execution_mode"), &SkeletonModification2D::set_execution_mode); - ClassDB::bind_method(D_METHOD("get_execution_mode"), &SkeletonModification2D::get_execution_mode); - ClassDB::bind_method(D_METHOD("clamp_angle", "angle", "min", "max", "invert"), &SkeletonModification2D::clamp_angle); - ClassDB::bind_method(D_METHOD("set_editor_draw_gizmo", "draw_gizmo"), &SkeletonModification2D::set_editor_draw_gizmo); - ClassDB::bind_method(D_METHOD("get_editor_draw_gizmo"), &SkeletonModification2D::get_editor_draw_gizmo); +bool SkeletonModification2D::is_property_hidden(String p_property_name) const { + bool ret = false; + const_cast(this)->GDVIRTUAL_CALL(_is_property_hidden, p_property_name, ret); + return ret; +} - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "get_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "execution_mode", PROPERTY_HINT_ENUM, "process, physics_process"), "set_execution_mode", "get_execution_mode"); +TypedArray SkeletonModification2D::get_configuration_warnings() const { + TypedArray ret = Node::get_configuration_warnings(); + if (!get_skeleton()) { + ret.push_back("Modification skeleton_path must point to a Skeleton2D node."); + } + return ret; } diff --git a/scene/2d/skeleton_modification_2d.h b/scene/2d/skeleton_modification_2d.h index f15e5681a7b1..86e67ce752b9 100644 --- a/scene/2d/skeleton_modification_2d.h +++ b/scene/2d/skeleton_modification_2d.h @@ -39,48 +39,53 @@ class Bone2D; -class SkeletonModification2D : public Node2D { - GDCLASS(SkeletonModification2D, Node2D); +class SkeletonModification2D : public Node { + GDCLASS(SkeletonModification2D, Node); -protected: +private: static void _bind_methods(); - int execution_mode = 0; // 0 = process - + bool editor_gizmo_dirty = false; bool enabled = true; - bool is_setup = false; - Skeleton2D *skeleton = nullptr; - NodePath skeleton_node = NodePath(".."); + bool skeleton_change_queued = true; + mutable Variant cached_skeleton; + NodePath skeleton_path = NodePath(".."); - bool _print_execution_error(bool p_condition, String p_message); + void _do_gizmo_draw(); - GDVIRTUAL1(_execute, double) - GDVIRTUAL0(_draw_editor_gizmo) +protected: + bool _cache_node(Variant &cache, const NodePath &target_node_path) const; + Bone2D *_cache_bone(Variant &cache, const NodePath &target_node_path) const; + TypedArray get_configuration_warnings() const override; public: - NodePath get_skeleton_path() const { - return skeleton_node; - } - - void set_skeleton_path(NodePath p_path) { - skeleton_node = p_path; - } - virtual void _draw_editor_gizmo(); - - bool editor_draw_gizmo = false; - void set_editor_draw_gizmo(bool p_draw_gizmo); - bool get_editor_draw_gizmo() const; + enum { UNCACHED_BONE_IDX = -2 }; void set_enabled(bool p_enabled); bool get_enabled(); - void set_is_setup(bool p_setup); - bool get_is_setup() const; + NodePath get_skeleton_path() const; + void set_skeleton_path(NodePath p_path); + Skeleton2D *get_skeleton() const; + + void _validate_property(PropertyInfo &p_property) const; + void _notification(int32_t p_what); + + virtual void execute(real_t delta); + GDVIRTUAL1(_execute, real_t); + virtual void draw_editor_gizmo(); + GDVIRTUAL0(_draw_editor_gizmo); + virtual bool is_property_hidden(String property_name) const; + GDVIRTUAL1R(bool, _is_property_hidden, String); + void set_editor_gizmo_dirty(bool p_dirty); - void set_execution_mode(int p_mode); - int get_execution_mode() const; + Variant resolve_node(const NodePath &target_node_path) const; + Variant resolve_bone(const NodePath &target_node_path) const; + Transform2D get_target_transform(Variant resolved_target) const; + real_t get_target_rotation(Variant resolved_target) const; + Vector2 get_target_position(Variant resolved_target) const; - float clamp_angle(float p_angle, float p_min_bound, float p_max_bound, bool p_invert_clamp = false); + float clamp_angle(float p_angle, float p_min_bound, float p_max_bound, bool p_invert); void editor_draw_angle_constraints(Bone2D *p_operation_bone, float p_min_bound, float p_max_bound, bool p_constraint_enabled, bool p_constraint_in_localspace, bool p_constraint_inverted); SkeletonModification2D() {} diff --git a/scene/2d/skeleton_modification_2d_ccdik.cpp b/scene/2d/skeleton_modification_2d_ccdik.cpp index 087863c53aa5..1656b618f346 100644 --- a/scene/2d/skeleton_modification_2d_ccdik.cpp +++ b/scene/2d/skeleton_modification_2d_ccdik.cpp @@ -38,140 +38,97 @@ bool SkeletonModification2DCCDIK::_set(const StringName &p_path, const Variant &p_value) { String path = p_path; - if (path.begins_with("joint_data/")) { - int which = path.get_slicec('/', 1).to_int(); - String what = path.get_slicec('/', 2); + if (path.begins_with("joint_")) { + int which = path.get_slicec('/', 0).substr(6).to_int(); + String what = path.get_slicec('/', 1); ERR_FAIL_INDEX_V(which, ccdik_data_chain.size(), false); - if (what == "bone2d_node") { - set_ccdik_joint_bone2d_node(which, p_value); - } else if (what == "bone_index") { - set_ccdik_joint_bone_index(which, p_value); + if (what == "bone_node") { + set_joint_bone_node(which, p_value); } else if (what == "rotate_from_joint") { - set_ccdik_joint_rotate_from_joint(which, p_value); + set_joint_rotate_from_joint(which, p_value); } else if (what == "enable_constraint") { - set_ccdik_joint_enable_constraint(which, p_value); + set_joint_enable_constraint(which, p_value); } else if (what == "constraint_angle_min") { - set_ccdik_joint_constraint_angle_min(which, Math::deg_to_rad(float(p_value))); + set_joint_constraint_angle_min(which, float(p_value)); } else if (what == "constraint_angle_max") { - set_ccdik_joint_constraint_angle_max(which, Math::deg_to_rad(float(p_value))); + set_joint_constraint_angle_max(which, float(p_value)); } else if (what == "constraint_angle_invert") { - set_ccdik_joint_constraint_angle_invert(which, p_value); + set_joint_constraint_angle_invert(which, p_value); } else if (what == "constraint_in_localspace") { - set_ccdik_joint_constraint_in_localspace(which, p_value); + set_joint_constraint_in_localspace(which, p_value); } -#ifdef TOOLS_ENABLED - if (what.begins_with("editor_draw_gizmo")) { - set_ccdik_joint_editor_draw_gizmo(which, p_value); - } -#endif // TOOLS_ENABLED - return true; } -#ifdef TOOLS_ENABLED - if (path.begins_with("editor/draw_gizmo")) { - set_editor_draw_gizmo(p_value); - } -#endif // TOOLS_ENABLED - return true; } bool SkeletonModification2DCCDIK::_get(const StringName &p_path, Variant &r_ret) const { String path = p_path; - if (path.begins_with("joint_data/")) { - int which = path.get_slicec('/', 1).to_int(); - String what = path.get_slicec('/', 2); + if (path.begins_with("joint_")) { + int which = path.get_slicec('/', 0).substr(6).to_int(); + String what = path.get_slicec('/', 1); ERR_FAIL_INDEX_V(which, ccdik_data_chain.size(), false); - if (what == "bone2d_node") { - r_ret = get_ccdik_joint_bone2d_node(which); - } else if (what == "bone_index") { - r_ret = get_ccdik_joint_bone_index(which); + if (what == "bone_node") { + r_ret = get_joint_bone_node(which); } else if (what == "rotate_from_joint") { - r_ret = get_ccdik_joint_rotate_from_joint(which); + r_ret = get_joint_rotate_from_joint(which); } else if (what == "enable_constraint") { - r_ret = get_ccdik_joint_enable_constraint(which); + r_ret = get_joint_enable_constraint(which); } else if (what == "constraint_angle_min") { - r_ret = Math::rad_to_deg(get_ccdik_joint_constraint_angle_min(which)); + r_ret = get_joint_constraint_angle_min(which); } else if (what == "constraint_angle_max") { - r_ret = Math::rad_to_deg(get_ccdik_joint_constraint_angle_max(which)); + r_ret = get_joint_constraint_angle_max(which); } else if (what == "constraint_angle_invert") { - r_ret = get_ccdik_joint_constraint_angle_invert(which); + r_ret = get_joint_constraint_angle_invert(which); } else if (what == "constraint_in_localspace") { - r_ret = get_ccdik_joint_constraint_in_localspace(which); - } - -#ifdef TOOLS_ENABLED - if (what.begins_with("editor_draw_gizmo")) { - r_ret = get_ccdik_joint_editor_draw_gizmo(which); + r_ret = get_joint_constraint_in_localspace(which); } -#endif // TOOLS_ENABLED - return true; } -#ifdef TOOLS_ENABLED - if (path.begins_with("editor/draw_gizmo")) { - r_ret = get_editor_draw_gizmo(); - } -#endif // TOOLS_ENABLED - return true; } void SkeletonModification2DCCDIK::_get_property_list(List *p_list) const { for (int i = 0; i < ccdik_data_chain.size(); i++) { - String base_string = "joint_data/" + itos(i) + "/"; + String base_string = "joint_" + itos(i) + "/"; - p_list->push_back(PropertyInfo(Variant::INT, base_string + "bone_index", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - p_list->push_back(PropertyInfo(Variant::NODE_PATH, base_string + "bone2d_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Bone2D", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::NODE_PATH, base_string + "bone_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Bone2D", PROPERTY_USAGE_DEFAULT)); p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "rotate_from_joint", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "enable_constraint", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); if (ccdik_data_chain[i].enable_constraint) { - p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "constraint_angle_min", PROPERTY_HINT_RANGE, "-360, 360, 0.01", PROPERTY_USAGE_DEFAULT)); - p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "constraint_angle_max", PROPERTY_HINT_RANGE, "-360, 360, 0.01", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "constraint_angle_min", PROPERTY_HINT_RANGE, "-360,360,0.01,radians", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "constraint_angle_max", PROPERTY_HINT_RANGE, "-360,360,0.01,radians", PROPERTY_USAGE_DEFAULT)); p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "constraint_angle_invert", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "constraint_in_localspace", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); } - -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "editor_draw_gizmo", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - } -#endif // TOOLS_ENABLED } - -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - p_list->push_back(PropertyInfo(Variant::BOOL, "editor/draw_gizmo", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - } -#endif // TOOLS_ENABLED } -void SkeletonModification2DCCDIK::_execute_ccdik_joint(int p_joint_idx, Node2D *p_target, Node2D *p_tip) { - CCDIK_Joint_Data2D ccdik_data = ccdik_data_chain[p_joint_idx]; - if (ccdik_data.bone_idx < 0 || ccdik_data.bone_idx > skeleton->get_bone_count()) { - ERR_PRINT_ONCE("2D CCDIK joint: bone index not found!"); +void SkeletonModification2DCCDIK::_execute_ccdik_joint(int p_joint_idx, Vector2 p_target_position, Vector2 p_tip_position) { + const CCDIK_Joint_Data2D &ccdik_data = ccdik_data_chain[p_joint_idx]; + + Bone2D *operation_bone = _cache_bone(ccdik_data.bone_node_cache, ccdik_data.bone_node); + if (operation_bone == nullptr) { return; } - - Bone2D *operation_bone = skeleton->get_bone(ccdik_data.bone_idx); - Transform2D operation_transform = operation_bone->get_global_transform(); + Transform2D operation_transform = get_target_transform(operation_bone); if (ccdik_data.rotate_from_joint) { // To rotate from the joint, simply look at the target! operation_transform.set_rotation( - operation_transform.looking_at(p_target->get_global_position()).get_rotation() - operation_bone->get_bone_angle()); + operation_transform.looking_at(p_target_position).get_rotation() - operation_bone->get_bone_angle()); } else { // How to rotate from the tip: get the difference of rotation needed from the tip to the target, from the perspective of the joint. // Because we are only using the offset, we do not need to account for the bone angle of the Bone2D node. - float joint_to_tip = p_tip->get_global_position().angle_to_point(operation_transform.get_origin()); - float joint_to_target = p_target->get_global_position().angle_to_point(operation_transform.get_origin()); + float joint_to_tip = p_tip_position.angle_to_point(operation_transform.get_origin()); + float joint_to_target = p_target_position.angle_to_point(operation_transform.get_origin()); operation_transform.set_rotation( operation_transform.get_rotation() + (joint_to_target - joint_to_tip)); } @@ -185,110 +142,33 @@ void SkeletonModification2DCCDIK::_execute_ccdik_joint(int p_joint_idx, Node2D * } // Convert from a global transform to a delta and then apply the delta to the local transform. - operation_bone->set_global_transform(operation_transform); - operation_transform = operation_bone->get_transform(); + float parent_rotation = 0.0f; + CanvasItem *parent_node = cast_to(operation_bone->get_parent()); + if (parent_node != nullptr) { + parent_rotation = get_target_rotation(parent_node); + } + float final_rotation = operation_transform.get_rotation() - parent_rotation; // Apply constraints in localspace: if (ccdik_data.enable_constraint && ccdik_data.constraint_in_localspace) { - operation_transform.set_rotation(clamp_angle(operation_transform.get_rotation(), ccdik_data.constraint_angle_min, ccdik_data.constraint_angle_max, ccdik_data.constraint_angle_invert)); + final_rotation = clamp_angle(final_rotation, ccdik_data.constraint_angle_min, ccdik_data.constraint_angle_max, ccdik_data.constraint_angle_invert); } // Set the local pose override, and to make sure child bones are also updated, set the transform of the bone. - skeleton->set_bone_local_pose_override(ccdik_data.bone_idx, operation_transform, 1.0, true); - operation_bone->set_transform(operation_transform); - operation_bone->notification(operation_bone->NOTIFICATION_TRANSFORM_CHANGED); + operation_bone->set_rotation(final_rotation); } -void SkeletonModification2DCCDIK::_draw_editor_gizmo() { - if (!enabled || !is_setup) { - return; - } - +void SkeletonModification2DCCDIK::draw_editor_gizmo() { for (int i = 0; i < ccdik_data_chain.size(); i++) { - if (!ccdik_data_chain[i].editor_draw_gizmo) { - continue; - } - - Bone2D *operation_bone = skeleton->get_bone(ccdik_data_chain[i].bone_idx); + Bone2D *operation_bone = _cache_bone(ccdik_data_chain[i].bone_node_cache, ccdik_data_chain[i].bone_node); editor_draw_angle_constraints(operation_bone, ccdik_data_chain[i].constraint_angle_min, ccdik_data_chain[i].constraint_angle_max, ccdik_data_chain[i].enable_constraint, ccdik_data_chain[i].constraint_in_localspace, ccdik_data_chain[i].constraint_angle_invert); } } -void SkeletonModification2DCCDIK::update_target_cache() { - if (!is_setup) { - ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!"); - return; - } - - target_node_cache = ObjectID(); - if (skeleton) { - if (skeleton->is_inside_tree()) { - if (skeleton->has_node(target_node)) { - Node *node = skeleton->get_node(target_node); - ERR_FAIL_COND_MSG(!node || skeleton == node, - "Cannot update target cache: node is this modification's skeleton or cannot be found!"); - ERR_FAIL_COND_MSG(!node->is_inside_tree(), - "Cannot update target cache: node is not in the scene tree!"); - target_node_cache = node->get_instance_id(); - } - } - } -} - -void SkeletonModification2DCCDIK::update_tip_cache() { - if (!is_setup) { - ERR_PRINT_ONCE("Cannot update tip cache: modification is not properly setup!"); - return; - } - - tip_node_cache = ObjectID(); - if (skeleton) { - if (skeleton->is_inside_tree()) { - if (skeleton->has_node(tip_node)) { - Node *node = skeleton->get_node(tip_node); - ERR_FAIL_COND_MSG(!node || skeleton == node, - "Cannot update tip cache: node is this modification's skeleton or cannot be found!"); - ERR_FAIL_COND_MSG(!node->is_inside_tree(), - "Cannot update tip cache: node is not in the scene tree!"); - tip_node_cache = node->get_instance_id(); - } - } - } -} - -void SkeletonModification2DCCDIK::ccdik_joint_update_bone2d_cache(int p_joint_idx) { - ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "Cannot update bone2d cache: joint index out of range!"); - if (!is_setup) { - ERR_PRINT_ONCE("Cannot update CCDIK Bone2D cache: modification is not properly setup!"); - return; - } - - ccdik_data_chain.write[p_joint_idx].bone2d_node_cache = ObjectID(); - if (skeleton) { - if (skeleton->is_inside_tree()) { - if (skeleton->has_node(ccdik_data_chain[p_joint_idx].bone2d_node)) { - Node *node = skeleton->get_node(ccdik_data_chain[p_joint_idx].bone2d_node); - ERR_FAIL_COND_MSG(!node || skeleton == node, - "Cannot update CCDIK joint " + itos(p_joint_idx) + " Bone2D cache: node is this modification's skeleton or cannot be found!"); - ERR_FAIL_COND_MSG(!node->is_inside_tree(), - "Cannot update CCDIK joint " + itos(p_joint_idx) + " Bone2D cache: node is not in the scene tree!"); - ccdik_data_chain.write[p_joint_idx].bone2d_node_cache = node->get_instance_id(); - - Bone2D *bone = Object::cast_to(node); - if (bone) { - ccdik_data_chain.write[p_joint_idx].bone_idx = bone->get_index_in_skeleton(); - } else { - ERR_FAIL_MSG("CCDIK joint " + itos(p_joint_idx) + " Bone2D cache: Nodepath to Bone2D is not a Bone2D node!"); - } - } - } - } -} - void SkeletonModification2DCCDIK::set_target_node(const NodePath &p_target_node) { target_node = p_target_node; - update_target_cache(); + target_node_cache = Variant(); } NodePath SkeletonModification2DCCDIK::get_target_node() const { @@ -297,220 +177,166 @@ NodePath SkeletonModification2DCCDIK::get_target_node() const { void SkeletonModification2DCCDIK::set_tip_node(const NodePath &p_tip_node) { tip_node = p_tip_node; - update_tip_cache(); + tip_node_cache = Variant(); } NodePath SkeletonModification2DCCDIK::get_tip_node() const { return tip_node; } -void SkeletonModification2DCCDIK::set_ccdik_data_chain_length(int p_length) { +void SkeletonModification2DCCDIK::set_joint_count(int p_length) { ccdik_data_chain.resize(p_length); notify_property_list_changed(); } -int SkeletonModification2DCCDIK::get_ccdik_data_chain_length() { +int SkeletonModification2DCCDIK::get_joint_count() { return ccdik_data_chain.size(); } -void SkeletonModification2DCCDIK::set_ccdik_joint_bone2d_node(int p_joint_idx, const NodePath &p_target_node) { +void SkeletonModification2DCCDIK::set_joint_bone_node(int p_joint_idx, const NodePath &p_target_node) { ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCDIK joint out of range!"); - ccdik_data_chain.write[p_joint_idx].bone2d_node = p_target_node; - ccdik_joint_update_bone2d_cache(p_joint_idx); + ccdik_data_chain.write[p_joint_idx].bone_node = p_target_node; + ccdik_data_chain.write[p_joint_idx].bone_node_cache = Variant(); notify_property_list_changed(); } -NodePath SkeletonModification2DCCDIK::get_ccdik_joint_bone2d_node(int p_joint_idx) const { +NodePath SkeletonModification2DCCDIK::get_joint_bone_node(int p_joint_idx) const { ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), NodePath(), "CCDIK joint out of range!"); - return ccdik_data_chain[p_joint_idx].bone2d_node; + return ccdik_data_chain[p_joint_idx].bone_node; } -void SkeletonModification2DCCDIK::set_ccdik_joint_bone_index(int p_joint_idx, int p_bone_idx) { - ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCCDIK joint out of range!"); - ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!"); - - if (is_setup) { - if (skeleton) { - ERR_FAIL_INDEX_MSG(p_bone_idx, skeleton->get_bone_count(), "Passed-in Bone index is out of range!"); - ccdik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; - ccdik_data_chain.write[p_joint_idx].bone2d_node_cache = skeleton->get_bone(p_bone_idx)->get_instance_id(); - ccdik_data_chain.write[p_joint_idx].bone2d_node = skeleton->get_path_to(skeleton->get_bone(p_bone_idx)); - } else { - WARN_PRINT("Cannot verify the CCDIK joint " + itos(p_joint_idx) + " bone index for this modification..."); - ccdik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; - } - } else { - WARN_PRINT("Cannot verify the CCDIK joint " + itos(p_joint_idx) + " bone index for this modification..."); - ccdik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; - } - - notify_property_list_changed(); -} - -int SkeletonModification2DCCDIK::get_ccdik_joint_bone_index(int p_joint_idx) const { - ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), -1, "CCDIK joint out of range!"); - return ccdik_data_chain[p_joint_idx].bone_idx; -} - -void SkeletonModification2DCCDIK::set_ccdik_joint_rotate_from_joint(int p_joint_idx, bool p_rotate_from_joint) { +void SkeletonModification2DCCDIK::set_joint_rotate_from_joint(int p_joint_idx, bool p_rotate_from_joint) { ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCDIK joint out of range!"); ccdik_data_chain.write[p_joint_idx].rotate_from_joint = p_rotate_from_joint; } -bool SkeletonModification2DCCDIK::get_ccdik_joint_rotate_from_joint(int p_joint_idx) const { +bool SkeletonModification2DCCDIK::get_joint_rotate_from_joint(int p_joint_idx) const { ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), false, "CCDIK joint out of range!"); return ccdik_data_chain[p_joint_idx].rotate_from_joint; } -void SkeletonModification2DCCDIK::set_ccdik_joint_enable_constraint(int p_joint_idx, bool p_constraint) { +void SkeletonModification2DCCDIK::set_joint_enable_constraint(int p_joint_idx, bool p_constraint) { ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCDIK joint out of range!"); ccdik_data_chain.write[p_joint_idx].enable_constraint = p_constraint; notify_property_list_changed(); } -bool SkeletonModification2DCCDIK::get_ccdik_joint_enable_constraint(int p_joint_idx) const { +bool SkeletonModification2DCCDIK::get_joint_enable_constraint(int p_joint_idx) const { ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), false, "CCDIK joint out of range!"); return ccdik_data_chain[p_joint_idx].enable_constraint; } -void SkeletonModification2DCCDIK::set_ccdik_joint_constraint_angle_min(int p_joint_idx, float p_angle_min) { +void SkeletonModification2DCCDIK::set_joint_constraint_angle_min(int p_joint_idx, float p_angle_min) { ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCDIK joint out of range!"); ccdik_data_chain.write[p_joint_idx].constraint_angle_min = p_angle_min; } -float SkeletonModification2DCCDIK::get_ccdik_joint_constraint_angle_min(int p_joint_idx) const { +float SkeletonModification2DCCDIK::get_joint_constraint_angle_min(int p_joint_idx) const { ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), 0.0, "CCDIK joint out of range!"); return ccdik_data_chain[p_joint_idx].constraint_angle_min; } -void SkeletonModification2DCCDIK::set_ccdik_joint_constraint_angle_max(int p_joint_idx, float p_angle_max) { +void SkeletonModification2DCCDIK::set_joint_constraint_angle_max(int p_joint_idx, float p_angle_max) { ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCDIK joint out of range!"); ccdik_data_chain.write[p_joint_idx].constraint_angle_max = p_angle_max; } -float SkeletonModification2DCCDIK::get_ccdik_joint_constraint_angle_max(int p_joint_idx) const { +float SkeletonModification2DCCDIK::get_joint_constraint_angle_max(int p_joint_idx) const { ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), 0.0, "CCDIK joint out of range!"); return ccdik_data_chain[p_joint_idx].constraint_angle_max; } -void SkeletonModification2DCCDIK::set_ccdik_joint_constraint_angle_invert(int p_joint_idx, bool p_invert) { +void SkeletonModification2DCCDIK::set_joint_constraint_angle_invert(int p_joint_idx, bool p_invert) { ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCDIK joint out of range!"); ccdik_data_chain.write[p_joint_idx].constraint_angle_invert = p_invert; } -bool SkeletonModification2DCCDIK::get_ccdik_joint_constraint_angle_invert(int p_joint_idx) const { +bool SkeletonModification2DCCDIK::get_joint_constraint_angle_invert(int p_joint_idx) const { ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), false, "CCDIK joint out of range!"); return ccdik_data_chain[p_joint_idx].constraint_angle_invert; } -void SkeletonModification2DCCDIK::set_ccdik_joint_constraint_in_localspace(int p_joint_idx, bool p_constraint_in_localspace) { +void SkeletonModification2DCCDIK::set_joint_constraint_in_localspace(int p_joint_idx, bool p_constraint_in_localspace) { ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCDIK joint out of range!"); ccdik_data_chain.write[p_joint_idx].constraint_in_localspace = p_constraint_in_localspace; } -bool SkeletonModification2DCCDIK::get_ccdik_joint_constraint_in_localspace(int p_joint_idx) const { +bool SkeletonModification2DCCDIK::get_joint_constraint_in_localspace(int p_joint_idx) const { ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), false, "CCDIK joint out of range!"); return ccdik_data_chain[p_joint_idx].constraint_in_localspace; } -void SkeletonModification2DCCDIK::set_ccdik_joint_editor_draw_gizmo(int p_joint_idx, bool p_draw_gizmo) { - ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCDIK joint out of range!"); - ccdik_data_chain.write[p_joint_idx].editor_draw_gizmo = p_draw_gizmo; -} - -bool SkeletonModification2DCCDIK::get_ccdik_joint_editor_draw_gizmo(int p_joint_idx) const { - ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), false, "CCDIK joint out of range!"); - return ccdik_data_chain[p_joint_idx].editor_draw_gizmo; -} - void SkeletonModification2DCCDIK::_bind_methods() { ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification2DCCDIK::set_target_node); ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification2DCCDIK::get_target_node); ClassDB::bind_method(D_METHOD("set_tip_node", "tip_nodepath"), &SkeletonModification2DCCDIK::set_tip_node); ClassDB::bind_method(D_METHOD("get_tip_node"), &SkeletonModification2DCCDIK::get_tip_node); - ClassDB::bind_method(D_METHOD("set_ccdik_data_chain_length", "length"), &SkeletonModification2DCCDIK::set_ccdik_data_chain_length); - ClassDB::bind_method(D_METHOD("get_ccdik_data_chain_length"), &SkeletonModification2DCCDIK::get_ccdik_data_chain_length); - - ClassDB::bind_method(D_METHOD("set_ccdik_joint_bone2d_node", "joint_idx", "bone2d_nodepath"), &SkeletonModification2DCCDIK::set_ccdik_joint_bone2d_node); - ClassDB::bind_method(D_METHOD("get_ccdik_joint_bone2d_node", "joint_idx"), &SkeletonModification2DCCDIK::get_ccdik_joint_bone2d_node); - ClassDB::bind_method(D_METHOD("set_ccdik_joint_bone_index", "joint_idx", "bone_idx"), &SkeletonModification2DCCDIK::set_ccdik_joint_bone_index); - ClassDB::bind_method(D_METHOD("get_ccdik_joint_bone_index", "joint_idx"), &SkeletonModification2DCCDIK::get_ccdik_joint_bone_index); - ClassDB::bind_method(D_METHOD("set_ccdik_joint_rotate_from_joint", "joint_idx", "rotate_from_joint"), &SkeletonModification2DCCDIK::set_ccdik_joint_rotate_from_joint); - ClassDB::bind_method(D_METHOD("get_ccdik_joint_rotate_from_joint", "joint_idx"), &SkeletonModification2DCCDIK::get_ccdik_joint_rotate_from_joint); - ClassDB::bind_method(D_METHOD("set_ccdik_joint_enable_constraint", "joint_idx", "enable_constraint"), &SkeletonModification2DCCDIK::set_ccdik_joint_enable_constraint); - ClassDB::bind_method(D_METHOD("get_ccdik_joint_enable_constraint", "joint_idx"), &SkeletonModification2DCCDIK::get_ccdik_joint_enable_constraint); - ClassDB::bind_method(D_METHOD("set_ccdik_joint_constraint_angle_min", "joint_idx", "angle_min"), &SkeletonModification2DCCDIK::set_ccdik_joint_constraint_angle_min); - ClassDB::bind_method(D_METHOD("get_ccdik_joint_constraint_angle_min", "joint_idx"), &SkeletonModification2DCCDIK::get_ccdik_joint_constraint_angle_min); - ClassDB::bind_method(D_METHOD("set_ccdik_joint_constraint_angle_max", "joint_idx", "angle_max"), &SkeletonModification2DCCDIK::set_ccdik_joint_constraint_angle_max); - ClassDB::bind_method(D_METHOD("get_ccdik_joint_constraint_angle_max", "joint_idx"), &SkeletonModification2DCCDIK::get_ccdik_joint_constraint_angle_max); - ClassDB::bind_method(D_METHOD("set_ccdik_joint_constraint_angle_invert", "joint_idx", "invert"), &SkeletonModification2DCCDIK::set_ccdik_joint_constraint_angle_invert); - ClassDB::bind_method(D_METHOD("get_ccdik_joint_constraint_angle_invert", "joint_idx"), &SkeletonModification2DCCDIK::get_ccdik_joint_constraint_angle_invert); - - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node2D"), "set_target_node", "get_target_node"); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "tip_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node2D"), "set_tip_node", "get_tip_node"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "ccdik_data_chain_length", PROPERTY_HINT_RANGE, "0, 100, 1"), "set_ccdik_data_chain_length", "get_ccdik_data_chain_length"); + ClassDB::bind_method(D_METHOD("set_joint_count", "length"), &SkeletonModification2DCCDIK::set_joint_count); + ClassDB::bind_method(D_METHOD("get_joint_count"), &SkeletonModification2DCCDIK::get_joint_count); + + ClassDB::bind_method(D_METHOD("set_joint_bone_node", "joint_idx", "bone2d_nodepath"), &SkeletonModification2DCCDIK::set_joint_bone_node); + ClassDB::bind_method(D_METHOD("get_joint_bone_node", "joint_idx"), &SkeletonModification2DCCDIK::get_joint_bone_node); + ClassDB::bind_method(D_METHOD("set_joint_rotate_from_joint", "joint_idx", "rotate_from_joint"), &SkeletonModification2DCCDIK::set_joint_rotate_from_joint); + ClassDB::bind_method(D_METHOD("get_joint_rotate_from_joint", "joint_idx"), &SkeletonModification2DCCDIK::get_joint_rotate_from_joint); + ClassDB::bind_method(D_METHOD("set_joint_enable_constraint", "joint_idx", "enable_constraint"), &SkeletonModification2DCCDIK::set_joint_enable_constraint); + ClassDB::bind_method(D_METHOD("get_joint_enable_constraint", "joint_idx"), &SkeletonModification2DCCDIK::get_joint_enable_constraint); + ClassDB::bind_method(D_METHOD("set_joint_constraint_angle_min", "joint_idx", "angle_min"), &SkeletonModification2DCCDIK::set_joint_constraint_angle_min); + ClassDB::bind_method(D_METHOD("get_joint_constraint_angle_min", "joint_idx"), &SkeletonModification2DCCDIK::get_joint_constraint_angle_min); + ClassDB::bind_method(D_METHOD("set_joint_constraint_angle_max", "joint_idx", "angle_max"), &SkeletonModification2DCCDIK::set_joint_constraint_angle_max); + ClassDB::bind_method(D_METHOD("get_joint_constraint_angle_max", "joint_idx"), &SkeletonModification2DCCDIK::get_joint_constraint_angle_max); + ClassDB::bind_method(D_METHOD("set_joint_constraint_angle_invert", "joint_idx", "invert"), &SkeletonModification2DCCDIK::set_joint_constraint_angle_invert); + ClassDB::bind_method(D_METHOD("get_joint_constraint_angle_invert", "joint_idx"), &SkeletonModification2DCCDIK::get_joint_constraint_angle_invert); + ClassDB::bind_method(D_METHOD("set_joint_constraint_in_localspace", "joint_idx", "use_local_coords"), &SkeletonModification2DCCDIK::set_joint_constraint_in_localspace); + ClassDB::bind_method(D_METHOD("get_joint_constraint_in_localspace", "joint_idx"), &SkeletonModification2DCCDIK::get_joint_constraint_in_localspace); + + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "CanvasItem"), "set_target_node", "get_target_node"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "tip_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "CanvasItem"), "set_tip_node", "get_tip_node"); + ADD_ARRAY_COUNT("CCDIK Joint Chain", "joint_count", "set_joint_count", "get_joint_count", "joint_"); } SkeletonModification2DCCDIK::SkeletonModification2DCCDIK() { - is_setup = false; - enabled = true; - editor_draw_gizmo = true; } SkeletonModification2DCCDIK::~SkeletonModification2DCCDIK() { } -void SkeletonModification2DCCDIK::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - set_process_internal(false); - set_physics_process_internal(false); - if (get_execution_mode() == 0) { - set_process_internal(true); - } else if (get_execution_mode() == 1) { - set_physics_process_internal(true); - } - skeleton = cast_to(get_node_or_null(get_skeleton_path())); - is_setup = true; - update_target_cache(); - update_tip_cache(); - } break; - case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: - [[fallthrough]]; - case NOTIFICATION_INTERNAL_PROCESS: { - ERR_FAIL_COND_MSG(!is_setup || skeleton == nullptr, - "Modification is not setup and therefore cannot execute!"); - if (!enabled) { - return; - } - - if (target_node_cache.is_null()) { - WARN_PRINT_ONCE("Target cache is out of date. Attempting to update..."); - update_target_cache(); - return; - } - if (tip_node_cache.is_null()) { - WARN_PRINT_ONCE("Tip cache is out of date. Attempting to update..."); - update_tip_cache(); - return; - } - - Node2D *target = Object::cast_to(ObjectDB::get_instance(target_node_cache)); - if (!target || !target->is_inside_tree()) { - ERR_PRINT_ONCE("Target node is not in the scene tree. Cannot execute modification!"); - return; - } - - Node2D *tip = Object::cast_to(ObjectDB::get_instance(tip_node_cache)); - if (!tip || !tip->is_inside_tree()) { - ERR_PRINT_ONCE("Tip node is not in the scene tree. Cannot execute modification!"); - return; - } - - for (int i = 0; i < ccdik_data_chain.size(); i++) { - _execute_ccdik_joint(i, target, tip); - } - } break; + +void SkeletonModification2DCCDIK::execute(real_t p_delta) { + SkeletonModification2D::execute(p_delta); + + if (!_cache_node(target_node_cache, target_node) || !_cache_node(tip_node_cache, tip_node)) { + WARN_PRINT_ONCE("2DCCDIK: Failed to get target and tip nodes"); + return; + } + for (int i = 0; i < ccdik_data_chain.size(); i++) { + if (!_cache_bone(ccdik_data_chain[i].bone_node_cache, ccdik_data_chain[i].bone_node)) { + WARN_PRINT_ONCE("2DCCDIK: Failed to get chain bone node"); + return; + } + } + Vector2 target_transform = get_target_position(target_node_cache); + Vector2 tip_transform = get_target_position(tip_node_cache); + for (int i = 0; i < ccdik_data_chain.size(); i++) { + _execute_ccdik_joint(i, target_transform, tip_transform); + } +} + +TypedArray SkeletonModification2DCCDIK::get_configuration_warnings() const { + TypedArray ret = SkeletonModification2D::get_configuration_warnings(); + if (!get_skeleton()) { + return ret; + } + if (!_cache_node(target_node_cache, target_node)) { + ret.append(vformat("Target node %s was not found.", (String)target_node)); + } + if (!_cache_node(tip_node_cache, tip_node)) { + ret.append(vformat("Tip node %s was not found.", (String)tip_node)); + } + for (int i = 0; i < ccdik_data_chain.size(); i++) { + if (!_cache_bone(ccdik_data_chain[i].bone_node_cache, ccdik_data_chain[i].bone_node)) { + ret.append(vformat("Joint %d bone %s was not found.", i, ccdik_data_chain[i].bone_node)); + } } + return ret; } diff --git a/scene/2d/skeleton_modification_2d_ccdik.h b/scene/2d/skeleton_modification_2d_ccdik.h index 53334cb783a8..b43d6ba4d846 100644 --- a/scene/2d/skeleton_modification_2d_ccdik.h +++ b/scene/2d/skeleton_modification_2d_ccdik.h @@ -43,9 +43,8 @@ class SkeletonModification2DCCDIK : public SkeletonModification2D { private: struct CCDIK_Joint_Data2D { - int bone_idx = -1; - NodePath bone2d_node; - ObjectID bone2d_node_cache; + NodePath bone_node; + mutable Variant bone_node_cache; bool rotate_from_joint = false; bool enable_constraint = false; @@ -53,60 +52,50 @@ class SkeletonModification2DCCDIK : public SkeletonModification2D { float constraint_angle_max = (2.0 * Math_PI); bool constraint_angle_invert = false; bool constraint_in_localspace = true; - - bool editor_draw_gizmo = true; }; Vector ccdik_data_chain; NodePath target_node; - ObjectID target_node_cache; - void update_target_cache(); + mutable Variant target_node_cache; NodePath tip_node; - ObjectID tip_node_cache; - void update_tip_cache(); - - void ccdik_joint_update_bone2d_cache(int p_joint_idx); - void _execute_ccdik_joint(int p_joint_idx, Node2D *p_target, Node2D *p_tip); + mutable Variant tip_node_cache; + void _execute_ccdik_joint(int p_joint_idx, Vector2 p_target_position, Vector2 p_tip_position); protected: static void _bind_methods(); bool _set(const StringName &p_path, const Variant &p_value); bool _get(const StringName &p_path, Variant &r_ret) const; void _get_property_list(List *p_list) const; + void execute(real_t p_delta) override; + void draw_editor_gizmo() override; + TypedArray get_configuration_warnings() const override; public: - void _notification(int p_what); - void _draw_editor_gizmo() override; - void set_target_node(const NodePath &p_target_node); NodePath get_target_node() const; void set_tip_node(const NodePath &p_tip_node); NodePath get_tip_node() const; - int get_ccdik_data_chain_length(); - void set_ccdik_data_chain_length(int p_new_length); - - void set_ccdik_joint_bone2d_node(int p_joint_idx, const NodePath &p_target_node); - NodePath get_ccdik_joint_bone2d_node(int p_joint_idx) const; - void set_ccdik_joint_bone_index(int p_joint_idx, int p_bone_idx); - int get_ccdik_joint_bone_index(int p_joint_idx) const; - - void set_ccdik_joint_rotate_from_joint(int p_joint_idx, bool p_rotate_from_joint); - bool get_ccdik_joint_rotate_from_joint(int p_joint_idx) const; - void set_ccdik_joint_enable_constraint(int p_joint_idx, bool p_constraint); - bool get_ccdik_joint_enable_constraint(int p_joint_idx) const; - void set_ccdik_joint_constraint_angle_min(int p_joint_idx, float p_angle_min); - float get_ccdik_joint_constraint_angle_min(int p_joint_idx) const; - void set_ccdik_joint_constraint_angle_max(int p_joint_idx, float p_angle_max); - float get_ccdik_joint_constraint_angle_max(int p_joint_idx) const; - void set_ccdik_joint_constraint_angle_invert(int p_joint_idx, bool p_invert); - bool get_ccdik_joint_constraint_angle_invert(int p_joint_idx) const; - void set_ccdik_joint_constraint_in_localspace(int p_joint_idx, bool p_constraint_in_localspace); - bool get_ccdik_joint_constraint_in_localspace(int p_joint_idx) const; - void set_ccdik_joint_editor_draw_gizmo(int p_joint_idx, bool p_draw_gizmo); - bool get_ccdik_joint_editor_draw_gizmo(int p_joint_idx) const; + int get_joint_count(); + void set_joint_count(int p_new_length); + + void set_joint_bone_node(int p_joint_idx, const NodePath &p_target_node); + NodePath get_joint_bone_node(int p_joint_idx) const; + + void set_joint_rotate_from_joint(int p_joint_idx, bool p_rotate_from_joint); + bool get_joint_rotate_from_joint(int p_joint_idx) const; + void set_joint_enable_constraint(int p_joint_idx, bool p_constraint); + bool get_joint_enable_constraint(int p_joint_idx) const; + void set_joint_constraint_angle_min(int p_joint_idx, float p_angle_min); + float get_joint_constraint_angle_min(int p_joint_idx) const; + void set_joint_constraint_angle_max(int p_joint_idx, float p_angle_max); + float get_joint_constraint_angle_max(int p_joint_idx) const; + void set_joint_constraint_angle_invert(int p_joint_idx, bool p_invert); + bool get_joint_constraint_angle_invert(int p_joint_idx) const; + void set_joint_constraint_in_localspace(int p_joint_idx, bool p_constraint_in_localspace); + bool get_joint_constraint_in_localspace(int p_joint_idx) const; SkeletonModification2DCCDIK(); ~SkeletonModification2DCCDIK(); diff --git a/scene/2d/skeleton_modification_2d_fabrik.cpp b/scene/2d/skeleton_modification_2d_fabrik.cpp index cd3a5fcb7186..8dda18ffa4e8 100644 --- a/scene/2d/skeleton_modification_2d_fabrik.cpp +++ b/scene/2d/skeleton_modification_2d_fabrik.cpp @@ -38,19 +38,17 @@ bool SkeletonModification2DFABRIK::_set(const StringName &p_path, const Variant &p_value) { String path = p_path; - if (path.begins_with("joint_data/")) { - int which = path.get_slicec('/', 1).to_int(); - String what = path.get_slicec('/', 2); + if (path.begins_with("joint_")) { + int which = path.get_slicec('/', 0).substr(6).to_int(); + String what = path.get_slicec('/', 1); ERR_FAIL_INDEX_V(which, fabrik_data_chain.size(), false); - if (what == "bone2d_node") { - set_fabrik_joint_bone2d_node(which, p_value); - } else if (what == "bone_index") { - set_fabrik_joint_bone_index(which, p_value); + if (what == "bone_node") { + set_joint_bone_node(which, p_value); } else if (what == "magnet_position") { - set_fabrik_joint_magnet_position(which, p_value); + set_joint_magnet_position(which, p_value); } else if (what == "use_target_rotation") { - set_fabrik_joint_use_target_rotation(which, p_value); + set_joint_use_target_rotation(which, p_value); } } @@ -60,19 +58,17 @@ bool SkeletonModification2DFABRIK::_set(const StringName &p_path, const Variant bool SkeletonModification2DFABRIK::_get(const StringName &p_path, Variant &r_ret) const { String path = p_path; - if (path.begins_with("joint_data/")) { - int which = path.get_slicec('/', 1).to_int(); - String what = path.get_slicec('/', 2); + if (path.begins_with("joint_")) { + int which = path.get_slicec('/', 0).substr(6).to_int(); + String what = path.get_slicec('/', 1); ERR_FAIL_INDEX_V(which, fabrik_data_chain.size(), false); - if (what == "bone2d_node") { - r_ret = get_fabrik_joint_bone2d_node(which); - } else if (what == "bone_index") { - r_ret = get_fabrik_joint_bone_index(which); + if (what == "bone_node") { + r_ret = get_joint_bone_node(which); } else if (what == "magnet_position") { - r_ret = get_fabrik_joint_magnet_position(which); + r_ret = get_joint_magnet_position(which); } else if (what == "use_target_rotation") { - r_ret = get_fabrik_joint_use_target_rotation(which); + r_ret = get_joint_use_target_rotation(which); } return true; } @@ -81,10 +77,9 @@ bool SkeletonModification2DFABRIK::_get(const StringName &p_path, Variant &r_ret void SkeletonModification2DFABRIK::_get_property_list(List *p_list) const { for (int i = 0; i < fabrik_data_chain.size(); i++) { - String base_string = "joint_data/" + itos(i) + "/"; + String base_string = "joint_" + itos(i) + "/"; - p_list->push_back(PropertyInfo(Variant::INT, base_string + "bone_index", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - p_list->push_back(PropertyInfo(Variant::NODE_PATH, base_string + "bone2d_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Bone2D", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::NODE_PATH, base_string + "bone_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "CanvasItem", PROPERTY_USAGE_DEFAULT)); if (i > 0) { p_list->push_back(PropertyInfo(Variant::VECTOR2, base_string + "magnet_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); @@ -97,8 +92,8 @@ void SkeletonModification2DFABRIK::_get_property_list(List *p_list void SkeletonModification2DFABRIK::chain_backwards() { int final_joint_index = fabrik_data_chain.size() - 1; - Bone2D *final_bone2d_node = Object::cast_to(ObjectDB::get_instance(fabrik_data_chain[final_joint_index].bone2d_node_cache)); - Transform2D final_bone2d_trans = fabrik_transform_chain[final_joint_index]; + Bone2D *final_bone2d_node = Object::cast_to(ObjectDB::get_instance(fabrik_data_chain[final_joint_index].bone_node_cache)); + Transform2D final_bone2d_trans = fabrik_data_chain[final_joint_index].global_transform; // Apply magnet position if (final_joint_index != 0) { @@ -118,14 +113,15 @@ void SkeletonModification2DFABRIK::chain_backwards() { final_bone2d_trans.set_origin(target_global_pose.get_origin() - (final_bone2d_direction * final_bone2d_length)); // Save the transform - fabrik_transform_chain.write[final_joint_index] = final_bone2d_trans; + fabrik_data_chain.write[final_joint_index].global_transform = final_bone2d_trans; int i = final_joint_index; while (i >= 1) { - Transform2D previous_pose = fabrik_transform_chain[i]; + Transform2D previous_pose = fabrik_data_chain[i].global_transform; i -= 1; - Bone2D *current_bone2d_node = Object::cast_to(ObjectDB::get_instance(fabrik_data_chain[i].bone2d_node_cache)); - Transform2D current_pose = fabrik_transform_chain[i]; + Bone2D *current_bone2d_node = Object::cast_to((Object *)fabrik_data_chain[i].bone_node_cache); + ERR_FAIL_COND(current_bone2d_node == nullptr); + Transform2D current_pose = fabrik_data_chain[i].global_transform; // Apply magnet position if (i != 0) { @@ -138,20 +134,20 @@ void SkeletonModification2DFABRIK::chain_backwards() { current_pose.set_origin(finish_position); // Save the transform - fabrik_transform_chain.write[i] = current_pose; + fabrik_data_chain.write[i].global_transform = current_pose; } } void SkeletonModification2DFABRIK::chain_forwards() { - Transform2D origin_bone2d_trans = fabrik_transform_chain[0]; + Transform2D origin_bone2d_trans = fabrik_data_chain[0].global_transform; origin_bone2d_trans.set_origin(origin_global_pose.get_origin()); // Save the position - fabrik_transform_chain.write[0] = origin_bone2d_trans; + fabrik_data_chain.write[0].global_transform = origin_bone2d_trans; for (int i = 0; i < fabrik_data_chain.size() - 1; i++) { - Bone2D *current_bone2d_node = Object::cast_to(ObjectDB::get_instance(fabrik_data_chain[i].bone2d_node_cache)); - Transform2D current_pose = fabrik_transform_chain[i]; - Transform2D next_pose = fabrik_transform_chain[i + 1]; + Bone2D *current_bone2d_node = Object::cast_to(ObjectDB::get_instance(fabrik_data_chain[i].bone_node_cache)); + Transform2D current_pose = fabrik_data_chain[i].global_transform; + Transform2D next_pose = fabrik_data_chain[i + 1].global_transform; float current_bone2d_node_length = current_bone2d_node->get_length() * MIN(current_bone2d_node->get_global_scale().x, current_bone2d_node->get_global_scale().y); float length = current_bone2d_node_length / (next_pose.get_origin().distance_to(current_pose.get_origin())); @@ -159,135 +155,55 @@ void SkeletonModification2DFABRIK::chain_forwards() { current_pose.set_origin(finish_position); // Apply to the bone - fabrik_transform_chain.write[i + 1] = current_pose; - } -} - - -void SkeletonModification2DFABRIK::update_target_cache() { - if (!is_setup) { - ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!"); - return; - } - - target_node_cache = ObjectID(); - if (skeleton) { - if (skeleton->is_inside_tree()) { - if (skeleton->has_node(target_node)) { - Node *node = skeleton->get_node(target_node); - ERR_FAIL_COND_MSG(!node || skeleton == node, - "Cannot update target cache: node is this modification's skeleton or cannot be found!"); - ERR_FAIL_COND_MSG(!node->is_inside_tree(), - "Cannot update target cache: node is not in scene tree!"); - target_node_cache = node->get_instance_id(); - } - } - } -} - -void SkeletonModification2DFABRIK::fabrik_joint_update_bone2d_cache(int p_joint_idx) { - ERR_FAIL_INDEX_MSG(p_joint_idx, fabrik_data_chain.size(), "Cannot update bone2d cache: joint index out of range!"); - if (!is_setup) { - ERR_PRINT_ONCE("Cannot update FABRIK Bone2D cache: modification is not properly setup!"); - return; - } - - fabrik_data_chain.write[p_joint_idx].bone2d_node_cache = ObjectID(); - if (skeleton) { - if (skeleton->is_inside_tree()) { - if (skeleton->has_node(fabrik_data_chain[p_joint_idx].bone2d_node)) { - Node *node = skeleton->get_node(fabrik_data_chain[p_joint_idx].bone2d_node); - ERR_FAIL_COND_MSG(!node || skeleton == node, - "Cannot update FABRIK joint " + itos(p_joint_idx) + " Bone2D cache: node is this modification's skeleton or cannot be found!"); - ERR_FAIL_COND_MSG(!node->is_inside_tree(), - "Cannot update FABRIK joint " + itos(p_joint_idx) + " Bone2D cache: node is not in scene tree!"); - fabrik_data_chain.write[p_joint_idx].bone2d_node_cache = node->get_instance_id(); - - Bone2D *bone = Object::cast_to(node); - if (bone) { - fabrik_data_chain.write[p_joint_idx].bone_idx = bone->get_index_in_skeleton(); - } else { - ERR_FAIL_MSG("FABRIK joint " + itos(p_joint_idx) + " Bone2D cache: Nodepath to Bone2D is not a Bone2D node!"); - } - } - } + fabrik_data_chain.write[i + 1].global_transform = current_pose; } } void SkeletonModification2DFABRIK::set_target_node(const NodePath &p_target_node) { target_node = p_target_node; - update_target_cache(); + target_node_cache = Variant(); } NodePath SkeletonModification2DFABRIK::get_target_node() const { return target_node; } -void SkeletonModification2DFABRIK::set_fabrik_data_chain_length(int p_length) { - fabrik_data_chain.resize(p_length); +void SkeletonModification2DFABRIK::set_joint_count(int p_fabrik_chain_length) { + fabrik_data_chain.resize(p_fabrik_chain_length); notify_property_list_changed(); } -int SkeletonModification2DFABRIK::get_fabrik_data_chain_length() { +int SkeletonModification2DFABRIK::get_joint_count() { return fabrik_data_chain.size(); } -void SkeletonModification2DFABRIK::set_fabrik_joint_bone2d_node(int p_joint_idx, const NodePath &p_target_node) { +void SkeletonModification2DFABRIK::set_joint_bone_node(int p_joint_idx, const NodePath &p_target_node) { ERR_FAIL_INDEX_MSG(p_joint_idx, fabrik_data_chain.size(), "FABRIK joint out of range!"); - fabrik_data_chain.write[p_joint_idx].bone2d_node = p_target_node; - fabrik_joint_update_bone2d_cache(p_joint_idx); - - notify_property_list_changed(); + fabrik_data_chain.write[p_joint_idx].bone_node = p_target_node; + fabrik_data_chain.write[p_joint_idx].bone_node_cache = Variant(); } -NodePath SkeletonModification2DFABRIK::get_fabrik_joint_bone2d_node(int p_joint_idx) const { +NodePath SkeletonModification2DFABRIK::get_joint_bone_node(int p_joint_idx) const { ERR_FAIL_INDEX_V_MSG(p_joint_idx, fabrik_data_chain.size(), NodePath(), "FABRIK joint out of range!"); - return fabrik_data_chain[p_joint_idx].bone2d_node; -} - -void SkeletonModification2DFABRIK::set_fabrik_joint_bone_index(int p_joint_idx, int p_bone_idx) { - ERR_FAIL_INDEX_MSG(p_joint_idx, fabrik_data_chain.size(), "FABRIK joint out of range!"); - ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!"); - - if (is_setup) { - if (skeleton) { - ERR_FAIL_INDEX_MSG(p_bone_idx, skeleton->get_bone_count(), "Passed-in Bone index is out of range!"); - fabrik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; - fabrik_data_chain.write[p_joint_idx].bone2d_node_cache = skeleton->get_bone(p_bone_idx)->get_instance_id(); - fabrik_data_chain.write[p_joint_idx].bone2d_node = skeleton->get_path_to(skeleton->get_bone(p_bone_idx)); - } else { - WARN_PRINT("Cannot verify the FABRIK joint " + itos(p_joint_idx) + " bone index for this modification..."); - fabrik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; - } - } else { - WARN_PRINT("Cannot verify the FABRIK joint " + itos(p_joint_idx) + " bone index for this modification..."); - fabrik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; - } - - notify_property_list_changed(); -} - -int SkeletonModification2DFABRIK::get_fabrik_joint_bone_index(int p_joint_idx) const { - ERR_FAIL_INDEX_V_MSG(p_joint_idx, fabrik_data_chain.size(), -1, "FABRIK joint out of range!"); - return fabrik_data_chain[p_joint_idx].bone_idx; + return fabrik_data_chain[p_joint_idx].bone_node; } -void SkeletonModification2DFABRIK::set_fabrik_joint_magnet_position(int p_joint_idx, Vector2 p_magnet_position) { +void SkeletonModification2DFABRIK::set_joint_magnet_position(int p_joint_idx, Vector2 p_magnet_position) { ERR_FAIL_INDEX_MSG(p_joint_idx, fabrik_data_chain.size(), "FABRIK joint out of range!"); fabrik_data_chain.write[p_joint_idx].magnet_position = p_magnet_position; } -Vector2 SkeletonModification2DFABRIK::get_fabrik_joint_magnet_position(int p_joint_idx) const { +Vector2 SkeletonModification2DFABRIK::get_joint_magnet_position(int p_joint_idx) const { ERR_FAIL_INDEX_V_MSG(p_joint_idx, fabrik_data_chain.size(), Vector2(), "FABRIK joint out of range!"); return fabrik_data_chain[p_joint_idx].magnet_position; } -void SkeletonModification2DFABRIK::set_fabrik_joint_use_target_rotation(int p_joint_idx, bool p_use_target_rotation) { +void SkeletonModification2DFABRIK::set_joint_use_target_rotation(int p_joint_idx, bool p_use_target_rotation) { ERR_FAIL_INDEX_MSG(p_joint_idx, fabrik_data_chain.size(), "FABRIK joint out of range!"); fabrik_data_chain.write[p_joint_idx].use_target_rotation = p_use_target_rotation; } -bool SkeletonModification2DFABRIK::get_fabrik_joint_use_target_rotation(int p_joint_idx) const { +bool SkeletonModification2DFABRIK::get_joint_use_target_rotation(int p_joint_idx) const { ERR_FAIL_INDEX_V_MSG(p_joint_idx, fabrik_data_chain.size(), false, "FABRIK joint out of range!"); return fabrik_data_chain[p_joint_idx].use_target_rotation; } @@ -296,161 +212,108 @@ void SkeletonModification2DFABRIK::_bind_methods() { ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification2DFABRIK::set_target_node); ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification2DFABRIK::get_target_node); - ClassDB::bind_method(D_METHOD("set_fabrik_data_chain_length", "length"), &SkeletonModification2DFABRIK::set_fabrik_data_chain_length); - ClassDB::bind_method(D_METHOD("get_fabrik_data_chain_length"), &SkeletonModification2DFABRIK::get_fabrik_data_chain_length); + ClassDB::bind_method(D_METHOD("set_joint_count", "fabrik_chain_length"), &SkeletonModification2DFABRIK::set_joint_count); + ClassDB::bind_method(D_METHOD("get_joint_count"), &SkeletonModification2DFABRIK::get_joint_count); - ClassDB::bind_method(D_METHOD("set_fabrik_joint_bone2d_node", "joint_idx", "bone2d_nodepath"), &SkeletonModification2DFABRIK::set_fabrik_joint_bone2d_node); - ClassDB::bind_method(D_METHOD("get_fabrik_joint_bone2d_node", "joint_idx"), &SkeletonModification2DFABRIK::get_fabrik_joint_bone2d_node); - ClassDB::bind_method(D_METHOD("set_fabrik_joint_bone_index", "joint_idx", "bone_idx"), &SkeletonModification2DFABRIK::set_fabrik_joint_bone_index); - ClassDB::bind_method(D_METHOD("get_fabrik_joint_bone_index", "joint_idx"), &SkeletonModification2DFABRIK::get_fabrik_joint_bone_index); - ClassDB::bind_method(D_METHOD("set_fabrik_joint_magnet_position", "joint_idx", "magnet_position"), &SkeletonModification2DFABRIK::set_fabrik_joint_magnet_position); - ClassDB::bind_method(D_METHOD("get_fabrik_joint_magnet_position", "joint_idx"), &SkeletonModification2DFABRIK::get_fabrik_joint_magnet_position); - ClassDB::bind_method(D_METHOD("set_fabrik_joint_use_target_rotation", "joint_idx", "use_target_rotation"), &SkeletonModification2DFABRIK::set_fabrik_joint_use_target_rotation); - ClassDB::bind_method(D_METHOD("get_fabrik_joint_use_target_rotation", "joint_idx"), &SkeletonModification2DFABRIK::get_fabrik_joint_use_target_rotation); + ClassDB::bind_method(D_METHOD("set_joint_bone_node", "joint_idx", "bone2d_nodepath"), &SkeletonModification2DFABRIK::set_joint_bone_node); + ClassDB::bind_method(D_METHOD("get_joint_bone_node", "joint_idx"), &SkeletonModification2DFABRIK::get_joint_bone_node); + ClassDB::bind_method(D_METHOD("set_joint_magnet_position", "joint_idx", "magnet_position"), &SkeletonModification2DFABRIK::set_joint_magnet_position); + ClassDB::bind_method(D_METHOD("get_joint_magnet_position", "joint_idx"), &SkeletonModification2DFABRIK::get_joint_magnet_position); + ClassDB::bind_method(D_METHOD("set_joint_use_target_rotation", "joint_idx", "use_target_rotation"), &SkeletonModification2DFABRIK::set_joint_use_target_rotation); + ClassDB::bind_method(D_METHOD("get_joint_use_target_rotation", "joint_idx"), &SkeletonModification2DFABRIK::get_joint_use_target_rotation); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node2D"), "set_target_node", "get_target_node"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "fabrik_data_chain_length", PROPERTY_HINT_RANGE, "0, 100, 1"), "set_fabrik_data_chain_length", "get_fabrik_data_chain_length"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "CanvasItem"), "set_target_node", "get_target_node"); + ADD_ARRAY_COUNT("FABRIK Joint Chain", "joint_count", "set_joint_count", "get_joint_count", "joint_"); } SkeletonModification2DFABRIK::SkeletonModification2DFABRIK() { - is_setup = false; - enabled = true; - editor_draw_gizmo = false; } SkeletonModification2DFABRIK::~SkeletonModification2DFABRIK() { } -void SkeletonModification2DFABRIK::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - set_process_internal(false); - set_physics_process_internal(false); - if (get_execution_mode() == 0) { - set_process_internal(true); - } else if (get_execution_mode() == 1) { - set_physics_process_internal(true); - } - skeleton = cast_to(get_node_or_null(get_skeleton_path())); - is_setup = true; - update_target_cache(); - - update_target_cache(); - } break; - case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: - [[fallthrough]]; - case NOTIFICATION_INTERNAL_PROCESS: { - ERR_FAIL_COND_MSG(!is_setup || skeleton == nullptr, - "Modification is not setup and therefore cannot execute!"); - if (!enabled) { - return; - } +void SkeletonModification2DFABRIK::execute(real_t delta) { + SkeletonModification2D::execute(delta); - if (target_node_cache.is_null()) { - WARN_PRINT_ONCE("Target cache is out of date. Attempting to update..."); - update_target_cache(); - return; - } - - if (fabrik_data_chain.size() <= 1) { - ERR_PRINT_ONCE("FABRIK requires at least two joints to operate! Cannot execute modification!"); - return; - } + if (!_cache_node(target_node_cache, target_node)) { + WARN_PRINT_ONCE("2DFABRIK was unable get target node"); + return; + } + ERR_FAIL_COND(fabrik_data_chain.size() == 0); + for (int i = 0; i < fabrik_data_chain.size(); i++) { + if (!_cache_node(fabrik_data_chain[i].bone_node_cache, fabrik_data_chain[i].bone_node)) { + WARN_PRINT_ONCE("2DFABRIK was unable get chain node"); + return; + } + fabrik_data_chain.write[i].global_transform = get_target_transform(fabrik_data_chain[i].bone_node_cache); + } + target_global_pose = get_target_transform(target_node_cache); - Node2D *target = Object::cast_to(ObjectDB::get_instance(target_node_cache)); - if (!target || !target->is_inside_tree()) { - ERR_PRINT_ONCE("Target node is not in the scene tree. Cannot execute modification!"); - return; - } - target_global_pose = target->get_global_transform(); + origin_global_pose = get_target_transform(fabrik_data_chain[0].bone_node_cache); - if (fabrik_data_chain[0].bone2d_node_cache.is_null() && !fabrik_data_chain[0].bone2d_node.is_empty()) { - fabrik_joint_update_bone2d_cache(0); - WARN_PRINT("Bone2D cache for origin joint is out of date. Updating..."); - } + Bone2D *final_bone2d_node = Object::cast_to((Object *)(fabrik_data_chain[fabrik_data_chain.size() - 1].bone_node_cache)); + float final_bone2d_angle = final_bone2d_node->get_global_rotation(); + if (fabrik_data_chain[fabrik_data_chain.size() - 1].use_target_rotation) { + final_bone2d_angle = target_global_pose.get_rotation(); + } + Vector2 final_bone2d_direction = Vector2(Math::cos(final_bone2d_angle), Math::sin(final_bone2d_angle)); + float final_bone2d_length = final_bone2d_node->get_length() * MIN(final_bone2d_node->get_global_scale().x, final_bone2d_node->get_global_scale().y); + float target_distance = (final_bone2d_node->get_global_position() + (final_bone2d_direction * final_bone2d_length)).distance_to(target_global_pose.get_origin()); + chain_iterations = 0; - Bone2D *origin_bone2d_node = Object::cast_to(ObjectDB::get_instance(fabrik_data_chain[0].bone2d_node_cache)); - if (!origin_bone2d_node || !origin_bone2d_node->is_inside_tree()) { - ERR_PRINT_ONCE("Origin joint's Bone2D node is not in the scene tree. Cannot execute modification!"); - return; - } + while (target_distance > chain_tolarance) { + chain_backwards(); + chain_forwards(); - origin_global_pose = origin_bone2d_node->get_global_transform(); + final_bone2d_angle = final_bone2d_node->get_global_rotation(); + if (fabrik_data_chain[fabrik_data_chain.size() - 1].use_target_rotation) { + final_bone2d_angle = target_global_pose.get_rotation(); + } + final_bone2d_direction = Vector2(Math::cos(final_bone2d_angle), Math::sin(final_bone2d_angle)); + target_distance = (final_bone2d_node->get_global_position() + (final_bone2d_direction * final_bone2d_length)).distance_to(target_global_pose.get_origin()); - if (fabrik_transform_chain.size() != fabrik_data_chain.size()) { - fabrik_transform_chain.resize(fabrik_data_chain.size()); - } + chain_iterations += 1; + if (chain_iterations >= chain_max_iterations) { + break; + } + } - for (int i = 0; i < fabrik_data_chain.size(); i++) { - // Update the transform chain - if (fabrik_data_chain[i].bone2d_node_cache.is_null() && !fabrik_data_chain[i].bone2d_node.is_empty()) { - WARN_PRINT_ONCE("Bone2D cache for joint " + itos(i) + " is out of date.. Attempting to update..."); - fabrik_joint_update_bone2d_cache(i); - } - Bone2D *joint_bone2d_node = Object::cast_to(ObjectDB::get_instance(fabrik_data_chain[i].bone2d_node_cache)); - if (!joint_bone2d_node) { - ERR_PRINT_ONCE("FABRIK Joint " + itos(i) + " does not have a Bone2D node set! Cannot execute modification!"); - return; - } - fabrik_transform_chain.write[i] = joint_bone2d_node->get_global_transform(); - } + // Apply all of the saved transforms to the Bone2D nodes + for (int i = 0; i < fabrik_data_chain.size(); i++) { + Bone2D *joint_bone2d_node = Object::cast_to((Object *)(fabrik_data_chain[i].bone_node_cache)); + if (!joint_bone2d_node) { + ERR_PRINT_ONCE("FABRIK Joint " + itos(i) + " does not have a Bone2D node set!"); + continue; + } + Transform2D chain_trans = fabrik_data_chain[i].global_transform; - Bone2D *final_bone2d_node = Object::cast_to(ObjectDB::get_instance(fabrik_data_chain[fabrik_data_chain.size() - 1].bone2d_node_cache)); - float final_bone2d_angle = final_bone2d_node->get_global_rotation(); - if (fabrik_data_chain[fabrik_data_chain.size() - 1].use_target_rotation) { - final_bone2d_angle = target_global_pose.get_rotation(); - } - Vector2 final_bone2d_direction = Vector2(Math::cos(final_bone2d_angle), Math::sin(final_bone2d_angle)); - float final_bone2d_length = final_bone2d_node->get_length() * MIN(final_bone2d_node->get_global_scale().x, final_bone2d_node->get_global_scale().y); - float target_distance = (final_bone2d_node->get_global_position() + (final_bone2d_direction * final_bone2d_length)).distance_to(target->get_global_position()); - chain_iterations = 0; - - while (target_distance > chain_tolarance) { - chain_backwards(); - chain_forwards(); - - final_bone2d_angle = final_bone2d_node->get_global_rotation(); - if (fabrik_data_chain[fabrik_data_chain.size() - 1].use_target_rotation) { - final_bone2d_angle = target_global_pose.get_rotation(); - } - final_bone2d_direction = Vector2(Math::cos(final_bone2d_angle), Math::sin(final_bone2d_angle)); - target_distance = (final_bone2d_node->get_global_position() + (final_bone2d_direction * final_bone2d_length)).distance_to(target->get_global_position()); - - chain_iterations += 1; - if (chain_iterations >= chain_max_iterations) { - break; - } + // Apply rotation + if (i + 1 < fabrik_data_chain.size()) { + chain_trans = chain_trans.looking_at(fabrik_data_chain[i + 1].global_transform.get_origin()); + } else { + if (fabrik_data_chain[i].use_target_rotation) { + chain_trans.set_rotation(target_global_pose.get_rotation()); + } else { + chain_trans = chain_trans.looking_at(target_global_pose.get_origin()); } + } + // Adjust for the bone angle and apply to the bone. + joint_bone2d_node->set_global_rotation(chain_trans.get_rotation() - joint_bone2d_node->get_bone_angle()); + } +} - // Apply all of the saved transforms to the Bone2D nodes - for (int i = 0; i < fabrik_data_chain.size(); i++) { - Bone2D *joint_bone2d_node = Object::cast_to(ObjectDB::get_instance(fabrik_data_chain[i].bone2d_node_cache)); - if (!joint_bone2d_node) { - ERR_PRINT_ONCE("FABRIK Joint " + itos(i) + " does not have a Bone2D node set!"); - continue; - } - Transform2D chain_trans = fabrik_transform_chain[i]; - - // Apply rotation - if (i + 1 < fabrik_data_chain.size()) { - chain_trans = chain_trans.looking_at(fabrik_transform_chain[i + 1].get_origin()); - } else { - if (fabrik_data_chain[i].use_target_rotation) { - chain_trans.set_rotation(target_global_pose.get_rotation()); - } else { - chain_trans = chain_trans.looking_at(target_global_pose.get_origin()); - } - } - // Adjust for the bone angle - chain_trans.set_rotation(chain_trans.get_rotation() - joint_bone2d_node->get_bone_angle()); - - // Reset scale - chain_trans.set_scale(joint_bone2d_node->get_global_scale()); - - // Apply to the bone, and to the override - joint_bone2d_node->set_global_transform(chain_trans); - skeleton->set_bone_local_pose_override(fabrik_data_chain[i].bone_idx, joint_bone2d_node->get_transform(), 1.0, true); - } - } break; +TypedArray SkeletonModification2DFABRIK::get_configuration_warnings() const { + TypedArray ret = SkeletonModification2D::get_configuration_warnings(); + if (!get_skeleton()) { + return ret; + } + if (!_cache_node(target_node_cache, target_node)) { + ret.append(vformat("Target node %s was not found.", (String)target_node)); + } + for (int i = 0; i < fabrik_data_chain.size(); i++) { + if (!_cache_bone(fabrik_data_chain[i].bone_node_cache, fabrik_data_chain[i].bone_node)) { + ret.append(vformat("Joint %d bone %s was not found.", i, fabrik_data_chain[i].bone_node)); + } } + return ret; } diff --git a/scene/2d/skeleton_modification_2d_fabrik.h b/scene/2d/skeleton_modification_2d_fabrik.h index a25cf584b911..f0fb9a2241d1 100644 --- a/scene/2d/skeleton_modification_2d_fabrik.h +++ b/scene/2d/skeleton_modification_2d_fabrik.h @@ -43,27 +43,23 @@ class SkeletonModification2DFABRIK : public SkeletonModification2D { private: struct FABRIK_Joint_Data2D { - int bone_idx = -1; - NodePath bone2d_node; - ObjectID bone2d_node_cache; + NodePath bone_node; + mutable Variant bone_node_cache; + + // Unlike in 3D, we need a vector of Transform2D objects to perform FABRIK. + // This is because FABRIK (unlike CCDIK) needs to operate on transforms that are NOT + // affected by each other, making the transforms stored in Bone2D unusable, as well as those in Skeleton2D. + // For this reason, this modification stores a vector of Transform2Ds used for the calculations, which are then applied at the end. + Transform2D global_transform; Vector2 magnet_position = Vector2(0, 0); bool use_target_rotation = false; - - bool editor_draw_gizmo = true; }; Vector fabrik_data_chain; - // Unlike in 3D, we need a vector of Transform2D objects to perform FABRIK. - // This is because FABRIK (unlike CCDIK) needs to operate on transforms that are NOT - // affected by each other, making the transforms stored in Bone2D unusable, as well as those in Skeleton2D. - // For this reason, this modification stores a vector of Transform2Ds used for the calculations, which are then applied at the end. - Vector fabrik_transform_chain; - NodePath target_node; - ObjectID target_node_cache; - void update_target_cache(); + mutable Variant target_node_cache; float chain_tolarance = 0.01; int chain_max_iterations = 10; @@ -71,7 +67,6 @@ class SkeletonModification2DFABRIK : public SkeletonModification2D { Transform2D target_global_pose = Transform2D(); Transform2D origin_global_pose = Transform2D(); - void fabrik_joint_update_bone2d_cache(int p_joint_idx); void chain_backwards(); void chain_forwards(); @@ -80,25 +75,22 @@ class SkeletonModification2DFABRIK : public SkeletonModification2D { bool _set(const StringName &p_path, const Variant &p_value); bool _get(const StringName &p_path, Variant &r_ret) const; void _get_property_list(List *p_list) const; + void execute(real_t delta) override; + TypedArray get_configuration_warnings() const override; public: - void _notification(int p_what); - void set_target_node(const NodePath &p_target_node); NodePath get_target_node() const; - int get_fabrik_data_chain_length(); - void set_fabrik_data_chain_length(int p_new_length); - - void set_fabrik_joint_bone2d_node(int p_joint_idx, const NodePath &p_target_node); - NodePath get_fabrik_joint_bone2d_node(int p_joint_idx) const; - void set_fabrik_joint_bone_index(int p_joint_idx, int p_bone_idx); - int get_fabrik_joint_bone_index(int p_joint_idx) const; + int get_joint_count(); + void set_joint_count(int p_new_length); - void set_fabrik_joint_magnet_position(int p_joint_idx, Vector2 p_magnet_position); - Vector2 get_fabrik_joint_magnet_position(int p_joint_idx) const; - void set_fabrik_joint_use_target_rotation(int p_joint_idx, bool p_use_target_rotation); - bool get_fabrik_joint_use_target_rotation(int p_joint_idx) const; + void set_joint_bone_node(int p_joint_idx, const NodePath &p_target_node); + NodePath get_joint_bone_node(int p_joint_idx) const; + void set_joint_magnet_position(int p_joint_idx, Vector2 p_magnet_position); + Vector2 get_joint_magnet_position(int p_joint_idx) const; + void set_joint_use_target_rotation(int p_joint_idx, bool p_use_target_rotation); + bool get_joint_use_target_rotation(int p_joint_idx) const; SkeletonModification2DFABRIK(); ~SkeletonModification2DFABRIK(); diff --git a/scene/2d/skeleton_modification_2d_jiggle.cpp b/scene/2d/skeleton_modification_2d_jiggle.cpp index e9304d7a5880..8e35818ce456 100644 --- a/scene/2d/skeleton_modification_2d_jiggle.cpp +++ b/scene/2d/skeleton_modification_2d_jiggle.cpp @@ -36,27 +36,25 @@ bool SkeletonModification2DJiggle::_set(const StringName &p_path, const Variant &p_value) { String path = p_path; - if (path.begins_with("joint_data/")) { - int which = path.get_slicec('/', 1).to_int(); - String what = path.get_slicec('/', 2); + if (path.begins_with("joint_")) { + int which = path.get_slicec('/', 0).substr(6).to_int(); + String what = path.get_slicec('/', 1); ERR_FAIL_INDEX_V(which, jiggle_data_chain.size(), false); - if (what == "bone2d_node") { - set_jiggle_joint_bone2d_node(which, p_value); - } else if (what == "bone_index") { - set_jiggle_joint_bone_index(which, p_value); + if (what == "bone_node") { + set_joint_bone_node(which, p_value); } else if (what == "override_defaults") { - set_jiggle_joint_override(which, p_value); + set_joint_override(which, p_value); } else if (what == "stiffness") { - set_jiggle_joint_stiffness(which, p_value); + set_joint_stiffness(which, p_value); } else if (what == "mass") { - set_jiggle_joint_mass(which, p_value); + set_joint_mass(which, p_value); } else if (what == "damping") { - set_jiggle_joint_damping(which, p_value); + set_joint_damping(which, p_value); } else if (what == "use_gravity") { - set_jiggle_joint_use_gravity(which, p_value); + set_joint_use_gravity(which, p_value); } else if (what == "gravity") { - set_jiggle_joint_gravity(which, p_value); + set_joint_gravity(which, p_value); } return true; } else { @@ -72,27 +70,25 @@ bool SkeletonModification2DJiggle::_set(const StringName &p_path, const Variant bool SkeletonModification2DJiggle::_get(const StringName &p_path, Variant &r_ret) const { String path = p_path; - if (path.begins_with("joint_data/")) { - int which = path.get_slicec('/', 1).to_int(); - String what = path.get_slicec('/', 2); + if (path.begins_with("joint_")) { + int which = path.get_slicec('/', 0).substr(6).to_int(); + String what = path.get_slicec('/', 1); ERR_FAIL_INDEX_V(which, jiggle_data_chain.size(), false); - if (what == "bone2d_node") { - r_ret = get_jiggle_joint_bone2d_node(which); - } else if (what == "bone_index") { - r_ret = get_jiggle_joint_bone_index(which); + if (what == "bone_node") { + r_ret = get_joint_bone_node(which); } else if (what == "override_defaults") { - r_ret = get_jiggle_joint_override(which); + r_ret = get_joint_override(which); } else if (what == "stiffness") { - r_ret = get_jiggle_joint_stiffness(which); + r_ret = get_joint_stiffness(which); } else if (what == "mass") { - r_ret = get_jiggle_joint_mass(which); + r_ret = get_joint_mass(which); } else if (what == "damping") { - r_ret = get_jiggle_joint_damping(which); + r_ret = get_joint_damping(which); } else if (what == "use_gravity") { - r_ret = get_jiggle_joint_use_gravity(which); + r_ret = get_joint_use_gravity(which); } else if (what == "gravity") { - r_ret = get_jiggle_joint_gravity(which); + r_ret = get_joint_gravity(which); } return true; } else { @@ -112,10 +108,10 @@ void SkeletonModification2DJiggle::_get_property_list(List *p_list } for (int i = 0; i < jiggle_data_chain.size(); i++) { - String base_string = "joint_data/" + itos(i) + "/"; + String base_string = "joint_" + itos(i) + "/"; p_list->push_back(PropertyInfo(Variant::INT, base_string + "bone_index", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - p_list->push_back(PropertyInfo(Variant::NODE_PATH, base_string + "bone2d_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Bone2D", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::NODE_PATH, base_string + "bone_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Bone2D", PROPERTY_USAGE_DEFAULT)); p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "override_defaults", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); if (jiggle_data_chain[i].override_defaults) { @@ -130,28 +126,13 @@ void SkeletonModification2DJiggle::_get_property_list(List *p_list } } -void SkeletonModification2DJiggle::_execute_jiggle_joint(int p_joint_idx, Node2D *p_target, float p_delta) { +void SkeletonModification2DJiggle::_execute_jiggle_joint(int p_joint_idx, Vector2 target_position, float p_delta) { // Adopted from: https://wiki.unity3d.com/index.php/JiggleBone // With modifications by TwistedTwigleg. - if (jiggle_data_chain[p_joint_idx].bone_idx <= -1 || jiggle_data_chain[p_joint_idx].bone_idx > skeleton->get_bone_count()) { - ERR_PRINT_ONCE("Jiggle joint " + itos(p_joint_idx) + " bone index is invalid. Cannot execute modification on joint..."); - return; - } - - if (jiggle_data_chain[p_joint_idx].bone2d_node_cache.is_null() && !jiggle_data_chain[p_joint_idx].bone2d_node.is_empty()) { - WARN_PRINT_ONCE("Bone2D cache for joint " + itos(p_joint_idx) + " is out of date. Updating..."); - jiggle_joint_update_bone2d_cache(p_joint_idx); - } - - Bone2D *operation_bone = skeleton->get_bone(jiggle_data_chain[p_joint_idx].bone_idx); - if (!operation_bone) { - ERR_PRINT_ONCE("Jiggle joint " + itos(p_joint_idx) + " does not have a Bone2D node or it cannot be found!"); - return; - } - + Bone2D *operation_bone = _cache_bone(jiggle_data_chain[p_joint_idx].bone_node_cache, jiggle_data_chain[p_joint_idx].bone_node); + ERR_FAIL_COND(operation_bone == nullptr); Transform2D operation_bone_trans = operation_bone->get_global_transform(); - Vector2 target_position = p_target->get_global_position(); jiggle_data_chain.write[p_joint_idx].force = (target_position - jiggle_data_chain[p_joint_idx].dynamic_position) * jiggle_data_chain[p_joint_idx].stiffness * p_delta; @@ -168,7 +149,8 @@ void SkeletonModification2DJiggle::_execute_jiggle_joint(int p_joint_idx, Node2D // Collision detection/response if (use_colliders) { - if (is_physics_processing() || is_physics_processing_internal()) { + Skeleton2D *skeleton = get_skeleton(); + if (skeleton && (is_physics_processing() || is_physics_processing_internal())) { Ref world_2d = skeleton->get_world_2d(); ERR_FAIL_COND(world_2d.is_null()); PhysicsDirectSpaceState2D *space_state = PhysicsServer2D::get_singleton()->space_get_direct_state(world_2d->get_space()); @@ -196,78 +178,25 @@ void SkeletonModification2DJiggle::_execute_jiggle_joint(int p_joint_idx, Node2D // Rotate the bone using the dynamic position! operation_bone_trans = operation_bone_trans.looking_at(jiggle_data_chain[p_joint_idx].dynamic_position); - operation_bone_trans.set_rotation(operation_bone_trans.get_rotation() - operation_bone->get_bone_angle()); - // Reset scale - operation_bone_trans.set_scale(operation_bone->get_global_scale()); - - operation_bone->set_global_transform(operation_bone_trans); - skeleton->set_bone_local_pose_override(jiggle_data_chain[p_joint_idx].bone_idx, operation_bone->get_transform(), 1.0, true); + operation_bone->set_global_rotation(operation_bone_trans.get_rotation() - operation_bone->get_bone_angle()); } void SkeletonModification2DJiggle::_update_jiggle_joint_data() { for (int i = 0; i < jiggle_data_chain.size(); i++) { if (!jiggle_data_chain[i].override_defaults) { - set_jiggle_joint_stiffness(i, stiffness); - set_jiggle_joint_mass(i, mass); - set_jiggle_joint_damping(i, damping); - set_jiggle_joint_use_gravity(i, use_gravity); - set_jiggle_joint_gravity(i, gravity); - } - } -} - -void SkeletonModification2DJiggle::update_target_cache() { - if (!is_setup) { - ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!"); - return; - } - - target_node_cache = ObjectID(); - if (is_inside_tree()) { - if (has_node(target_node)) { - Node *node = get_node_or_null(target_node); - ERR_FAIL_COND_MSG(!node || skeleton == node, - "Cannot update target cache: node is this modification's skeleton or cannot be found!"); - ERR_FAIL_COND_MSG(!node->is_inside_tree(), - "Cannot update target cache: node is not in scene tree!"); - target_node_cache = node->get_instance_id(); - } - } -} - -void SkeletonModification2DJiggle::jiggle_joint_update_bone2d_cache(int p_joint_idx) { - ERR_FAIL_INDEX_MSG(p_joint_idx, jiggle_data_chain.size(), "Cannot update bone2d cache: joint index out of range!"); - if (!is_setup) { - ERR_PRINT_ONCE("Cannot update Jiggle " + itos(p_joint_idx) + " Bone2D cache: modification is not properly setup!"); - return; - } - - jiggle_data_chain.write[p_joint_idx].bone2d_node_cache = ObjectID(); - if (skeleton) { - if (skeleton->is_inside_tree()) { - if (skeleton->has_node(jiggle_data_chain[p_joint_idx].bone2d_node)) { - Node *node = skeleton->get_node(jiggle_data_chain[p_joint_idx].bone2d_node); - ERR_FAIL_COND_MSG(!node || skeleton == node, - "Cannot update Jiggle joint " + itos(p_joint_idx) + " Bone2D cache: node is this modification's skeleton or cannot be found!"); - ERR_FAIL_COND_MSG(!node->is_inside_tree(), - "Cannot update Jiggle joint " + itos(p_joint_idx) + " Bone2D cache: node is not in scene tree!"); - jiggle_data_chain.write[p_joint_idx].bone2d_node_cache = node->get_instance_id(); - - Bone2D *bone = Object::cast_to(node); - if (bone) { - jiggle_data_chain.write[p_joint_idx].bone_idx = bone->get_index_in_skeleton(); - } else { - ERR_FAIL_MSG("Jiggle joint " + itos(p_joint_idx) + " Bone2D cache: Nodepath to Bone2D is not a Bone2D node!"); - } - } + set_joint_stiffness(i, stiffness); + set_joint_mass(i, mass); + set_joint_damping(i, damping); + set_joint_use_gravity(i, use_gravity); + set_joint_gravity(i, gravity); } } } void SkeletonModification2DJiggle::set_target_node(const NodePath &p_target_node) { target_node = p_target_node; - update_target_cache(); + target_node_cache = Variant(); } NodePath SkeletonModification2DJiggle::get_target_node() const { @@ -341,128 +270,99 @@ int SkeletonModification2DJiggle::get_collision_mask() const { } // Jiggle joint data functions -int SkeletonModification2DJiggle::get_jiggle_data_chain_length() { +int SkeletonModification2DJiggle::get_joint_count() { return jiggle_data_chain.size(); } -void SkeletonModification2DJiggle::set_jiggle_data_chain_length(int p_length) { +void SkeletonModification2DJiggle::set_joint_count(int p_length) { ERR_FAIL_COND(p_length < 0); jiggle_data_chain.resize(p_length); notify_property_list_changed(); } -void SkeletonModification2DJiggle::set_jiggle_joint_bone2d_node(int p_joint_idx, const NodePath &p_target_node) { +void SkeletonModification2DJiggle::set_joint_bone_node(int p_joint_idx, const NodePath &p_target_node) { ERR_FAIL_INDEX_MSG(p_joint_idx, jiggle_data_chain.size(), "Jiggle joint out of range!"); - jiggle_data_chain.write[p_joint_idx].bone2d_node = p_target_node; - jiggle_joint_update_bone2d_cache(p_joint_idx); - - notify_property_list_changed(); + jiggle_data_chain.write[p_joint_idx].bone_node = p_target_node; + jiggle_data_chain.write[p_joint_idx].bone_node_cache = Variant(); } -NodePath SkeletonModification2DJiggle::get_jiggle_joint_bone2d_node(int p_joint_idx) const { +NodePath SkeletonModification2DJiggle::get_joint_bone_node(int p_joint_idx) const { ERR_FAIL_INDEX_V_MSG(p_joint_idx, jiggle_data_chain.size(), NodePath(), "Jiggle joint out of range!"); - return jiggle_data_chain[p_joint_idx].bone2d_node; + return jiggle_data_chain[p_joint_idx].bone_node; } -void SkeletonModification2DJiggle::set_jiggle_joint_bone_index(int p_joint_idx, int p_bone_idx) { - ERR_FAIL_INDEX_MSG(p_joint_idx, jiggle_data_chain.size(), "Jiggle joint out of range!"); - ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!"); - - if (is_setup) { - if (skeleton) { - ERR_FAIL_INDEX_MSG(p_bone_idx, skeleton->get_bone_count(), "Passed-in Bone index is out of range!"); - jiggle_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; - jiggle_data_chain.write[p_joint_idx].bone2d_node_cache = skeleton->get_bone(p_bone_idx)->get_instance_id(); - jiggle_data_chain.write[p_joint_idx].bone2d_node = skeleton->get_path_to(skeleton->get_bone(p_bone_idx)); - } else { - WARN_PRINT("Cannot verify the Jiggle joint " + itos(p_joint_idx) + " bone index for this modification..."); - jiggle_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; - } - } else { - WARN_PRINT("Cannot verify the Jiggle joint " + itos(p_joint_idx) + " bone index for this modification..."); - jiggle_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; - } - - notify_property_list_changed(); -} - -int SkeletonModification2DJiggle::get_jiggle_joint_bone_index(int p_joint_idx) const { - ERR_FAIL_INDEX_V_MSG(p_joint_idx, jiggle_data_chain.size(), -1, "Jiggle joint out of range!"); - return jiggle_data_chain[p_joint_idx].bone_idx; -} - -void SkeletonModification2DJiggle::set_jiggle_joint_override(int p_joint_idx, bool p_override) { +void SkeletonModification2DJiggle::set_joint_override(int p_joint_idx, bool p_override) { ERR_FAIL_INDEX(p_joint_idx, jiggle_data_chain.size()); jiggle_data_chain.write[p_joint_idx].override_defaults = p_override; _update_jiggle_joint_data(); notify_property_list_changed(); } -bool SkeletonModification2DJiggle::get_jiggle_joint_override(int p_joint_idx) const { +bool SkeletonModification2DJiggle::get_joint_override(int p_joint_idx) const { ERR_FAIL_INDEX_V(p_joint_idx, jiggle_data_chain.size(), false); return jiggle_data_chain[p_joint_idx].override_defaults; } -void SkeletonModification2DJiggle::set_jiggle_joint_stiffness(int p_joint_idx, float p_stiffness) { +void SkeletonModification2DJiggle::set_joint_stiffness(int p_joint_idx, float p_stiffness) { ERR_FAIL_COND_MSG(p_stiffness < 0, "Stiffness cannot be set to a negative value!"); ERR_FAIL_INDEX(p_joint_idx, jiggle_data_chain.size()); jiggle_data_chain.write[p_joint_idx].stiffness = p_stiffness; } -float SkeletonModification2DJiggle::get_jiggle_joint_stiffness(int p_joint_idx) const { +float SkeletonModification2DJiggle::get_joint_stiffness(int p_joint_idx) const { ERR_FAIL_INDEX_V(p_joint_idx, jiggle_data_chain.size(), -1); return jiggle_data_chain[p_joint_idx].stiffness; } -void SkeletonModification2DJiggle::set_jiggle_joint_mass(int p_joint_idx, float p_mass) { +void SkeletonModification2DJiggle::set_joint_mass(int p_joint_idx, float p_mass) { ERR_FAIL_COND_MSG(p_mass < 0, "Mass cannot be set to a negative value!"); ERR_FAIL_INDEX(p_joint_idx, jiggle_data_chain.size()); jiggle_data_chain.write[p_joint_idx].mass = p_mass; } -float SkeletonModification2DJiggle::get_jiggle_joint_mass(int p_joint_idx) const { +float SkeletonModification2DJiggle::get_joint_mass(int p_joint_idx) const { ERR_FAIL_INDEX_V(p_joint_idx, jiggle_data_chain.size(), -1); return jiggle_data_chain[p_joint_idx].mass; } -void SkeletonModification2DJiggle::set_jiggle_joint_damping(int p_joint_idx, float p_damping) { +void SkeletonModification2DJiggle::set_joint_damping(int p_joint_idx, float p_damping) { ERR_FAIL_COND_MSG(p_damping < 0, "Damping cannot be set to a negative value!"); ERR_FAIL_INDEX(p_joint_idx, jiggle_data_chain.size()); jiggle_data_chain.write[p_joint_idx].damping = p_damping; } -float SkeletonModification2DJiggle::get_jiggle_joint_damping(int p_joint_idx) const { +float SkeletonModification2DJiggle::get_joint_damping(int p_joint_idx) const { ERR_FAIL_INDEX_V(p_joint_idx, jiggle_data_chain.size(), -1); return jiggle_data_chain[p_joint_idx].damping; } -void SkeletonModification2DJiggle::set_jiggle_joint_use_gravity(int p_joint_idx, bool p_use_gravity) { +void SkeletonModification2DJiggle::set_joint_use_gravity(int p_joint_idx, bool p_use_gravity) { ERR_FAIL_INDEX(p_joint_idx, jiggle_data_chain.size()); jiggle_data_chain.write[p_joint_idx].use_gravity = p_use_gravity; notify_property_list_changed(); } -bool SkeletonModification2DJiggle::get_jiggle_joint_use_gravity(int p_joint_idx) const { +bool SkeletonModification2DJiggle::get_joint_use_gravity(int p_joint_idx) const { ERR_FAIL_INDEX_V(p_joint_idx, jiggle_data_chain.size(), false); return jiggle_data_chain[p_joint_idx].use_gravity; } -void SkeletonModification2DJiggle::set_jiggle_joint_gravity(int p_joint_idx, Vector2 p_gravity) { +void SkeletonModification2DJiggle::set_joint_gravity(int p_joint_idx, Vector2 p_gravity) { ERR_FAIL_INDEX(p_joint_idx, jiggle_data_chain.size()); jiggle_data_chain.write[p_joint_idx].gravity = p_gravity; } -Vector2 SkeletonModification2DJiggle::get_jiggle_joint_gravity(int p_joint_idx) const { +Vector2 SkeletonModification2DJiggle::get_joint_gravity(int p_joint_idx) const { ERR_FAIL_INDEX_V(p_joint_idx, jiggle_data_chain.size(), Vector2(0, 0)); return jiggle_data_chain[p_joint_idx].gravity; } void SkeletonModification2DJiggle::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification2DJiggle::set_target_node); + ClassDB::bind_method(D_METHOD("set_target_node", "target_node"), &SkeletonModification2DJiggle::set_target_node); ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification2DJiggle::get_target_node); - ClassDB::bind_method(D_METHOD("set_jiggle_data_chain_length", "length"), &SkeletonModification2DJiggle::set_jiggle_data_chain_length); - ClassDB::bind_method(D_METHOD("get_jiggle_data_chain_length"), &SkeletonModification2DJiggle::get_jiggle_data_chain_length); + ClassDB::bind_method(D_METHOD("set_joint_count", "length"), &SkeletonModification2DJiggle::set_joint_count); + ClassDB::bind_method(D_METHOD("get_joint_count"), &SkeletonModification2DJiggle::get_joint_count); ClassDB::bind_method(D_METHOD("set_stiffness", "stiffness"), &SkeletonModification2DJiggle::set_stiffness); ClassDB::bind_method(D_METHOD("get_stiffness"), &SkeletonModification2DJiggle::get_stiffness); @@ -481,25 +381,23 @@ void SkeletonModification2DJiggle::_bind_methods() { ClassDB::bind_method(D_METHOD("get_collision_mask"), &SkeletonModification2DJiggle::get_collision_mask); // Jiggle joint data functions - ClassDB::bind_method(D_METHOD("set_jiggle_joint_bone2d_node", "joint_idx", "bone2d_node"), &SkeletonModification2DJiggle::set_jiggle_joint_bone2d_node); - ClassDB::bind_method(D_METHOD("get_jiggle_joint_bone2d_node", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_bone2d_node); - ClassDB::bind_method(D_METHOD("set_jiggle_joint_bone_index", "joint_idx", "bone_idx"), &SkeletonModification2DJiggle::set_jiggle_joint_bone_index); - ClassDB::bind_method(D_METHOD("get_jiggle_joint_bone_index", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_bone_index); - ClassDB::bind_method(D_METHOD("set_jiggle_joint_override", "joint_idx", "override"), &SkeletonModification2DJiggle::set_jiggle_joint_override); - ClassDB::bind_method(D_METHOD("get_jiggle_joint_override", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_override); - ClassDB::bind_method(D_METHOD("set_jiggle_joint_stiffness", "joint_idx", "stiffness"), &SkeletonModification2DJiggle::set_jiggle_joint_stiffness); - ClassDB::bind_method(D_METHOD("get_jiggle_joint_stiffness", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_stiffness); - ClassDB::bind_method(D_METHOD("set_jiggle_joint_mass", "joint_idx", "mass"), &SkeletonModification2DJiggle::set_jiggle_joint_mass); - ClassDB::bind_method(D_METHOD("get_jiggle_joint_mass", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_mass); - ClassDB::bind_method(D_METHOD("set_jiggle_joint_damping", "joint_idx", "damping"), &SkeletonModification2DJiggle::set_jiggle_joint_damping); - ClassDB::bind_method(D_METHOD("get_jiggle_joint_damping", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_damping); - ClassDB::bind_method(D_METHOD("set_jiggle_joint_use_gravity", "joint_idx", "use_gravity"), &SkeletonModification2DJiggle::set_jiggle_joint_use_gravity); - ClassDB::bind_method(D_METHOD("get_jiggle_joint_use_gravity", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_use_gravity); - ClassDB::bind_method(D_METHOD("set_jiggle_joint_gravity", "joint_idx", "gravity"), &SkeletonModification2DJiggle::set_jiggle_joint_gravity); - ClassDB::bind_method(D_METHOD("get_jiggle_joint_gravity", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_gravity); - - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node2D"), "set_target_node", "get_target_node"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "jiggle_data_chain_length", PROPERTY_HINT_RANGE, "0,100,1"), "set_jiggle_data_chain_length", "get_jiggle_data_chain_length"); + ClassDB::bind_method(D_METHOD("set_joint_bone_node", "joint_idx", "bone2d_node"), &SkeletonModification2DJiggle::set_joint_bone_node); + ClassDB::bind_method(D_METHOD("get_joint_bone_node", "joint_idx"), &SkeletonModification2DJiggle::get_joint_bone_node); + ClassDB::bind_method(D_METHOD("set_joint_override", "joint_idx", "override"), &SkeletonModification2DJiggle::set_joint_override); + ClassDB::bind_method(D_METHOD("get_joint_override", "joint_idx"), &SkeletonModification2DJiggle::get_joint_override); + ClassDB::bind_method(D_METHOD("set_joint_stiffness", "joint_idx", "stiffness"), &SkeletonModification2DJiggle::set_joint_stiffness); + ClassDB::bind_method(D_METHOD("get_joint_stiffness", "joint_idx"), &SkeletonModification2DJiggle::get_joint_stiffness); + ClassDB::bind_method(D_METHOD("set_joint_mass", "joint_idx", "mass"), &SkeletonModification2DJiggle::set_joint_mass); + ClassDB::bind_method(D_METHOD("get_joint_mass", "joint_idx"), &SkeletonModification2DJiggle::get_joint_mass); + ClassDB::bind_method(D_METHOD("set_joint_damping", "joint_idx", "damping"), &SkeletonModification2DJiggle::set_joint_damping); + ClassDB::bind_method(D_METHOD("get_joint_damping", "joint_idx"), &SkeletonModification2DJiggle::get_joint_damping); + ClassDB::bind_method(D_METHOD("set_joint_use_gravity", "joint_idx", "use_gravity"), &SkeletonModification2DJiggle::set_joint_use_gravity); + ClassDB::bind_method(D_METHOD("get_joint_use_gravity", "joint_idx"), &SkeletonModification2DJiggle::get_joint_use_gravity); + ClassDB::bind_method(D_METHOD("set_joint_gravity", "joint_idx", "gravity"), &SkeletonModification2DJiggle::set_joint_gravity); + ClassDB::bind_method(D_METHOD("get_joint_gravity", "joint_idx"), &SkeletonModification2DJiggle::get_joint_gravity); + + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "CanvasItem"), "set_target_node", "get_target_node"); + ADD_ARRAY_COUNT("Jiggle joint chain length", "joint_count", "set_joint_count", "get_joint_count", "joint_"); ADD_GROUP("Default Joint Settings", ""); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "stiffness"), "set_stiffness", "get_stiffness"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass"), "set_mass", "get_mass"); @@ -510,71 +408,49 @@ void SkeletonModification2DJiggle::_bind_methods() { } SkeletonModification2DJiggle::SkeletonModification2DJiggle() { - is_setup = false; jiggle_data_chain = Vector(); stiffness = 3; mass = 0.75; damping = 0.75; use_gravity = false; gravity = Vector2(0, 6.0); - enabled = true; - editor_draw_gizmo = false; // Nothing to really show in a gizmo right now. } SkeletonModification2DJiggle::~SkeletonModification2DJiggle() { } -void SkeletonModification2DJiggle::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - set_process_internal(false); - set_physics_process_internal(false); - if (get_execution_mode() == 0) { - set_process_internal(true); - } else if (get_execution_mode() == 1) { - set_physics_process_internal(true); - } - skeleton = cast_to(get_node_or_null(get_skeleton_path())); - is_setup = true; - if (skeleton) { - for (int i = 0; i < jiggle_data_chain.size(); i++) { - int bone_idx = jiggle_data_chain[i].bone_idx; - if (bone_idx > 0 && bone_idx < skeleton->get_bone_count()) { - Bone2D *bone2d_node = skeleton->get_bone(bone_idx); - jiggle_data_chain.write[i].dynamic_position = bone2d_node->get_global_position(); - } - } - } +void SkeletonModification2DJiggle::execute(real_t p_delta) { + SkeletonModification2D::execute(p_delta); - update_target_cache(); - } break; - case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: - [[fallthrough]]; - case NOTIFICATION_INTERNAL_PROCESS: { - ERR_FAIL_COND_MSG(!is_setup || skeleton == nullptr, - "Modification is not setup and therefore cannot execute!"); - if (!enabled) { - return; - } - if (target_node_cache.is_null()) { - WARN_PRINT_ONCE("Target cache is out of date. Attempting to update..."); - update_target_cache(); - return; - } - Node2D *target = Object::cast_to(ObjectDB::get_instance(target_node_cache)); - if (!target || !target->is_inside_tree()) { - ERR_PRINT_ONCE("Target node is not in the scene tree. Cannot execute modification!"); - return; - } - double delta = 0.0; - if (get_execution_mode() == 0) { - delta = get_process_delta_time(); - } else if (get_execution_mode() == 1) { - delta = get_physics_process_delta_time(); - } - for (int i = 0; i < jiggle_data_chain.size(); i++) { - _execute_jiggle_joint(i, target, delta); - } - } break; + for (int i = 0; i < jiggle_data_chain.size(); i++) { + if (!_cache_bone(jiggle_data_chain[i].bone_node_cache, jiggle_data_chain[i].bone_node)) { + WARN_PRINT_ONCE("2DJiggle: unable to lookup joint"); + return; + } + jiggle_data_chain.write[i].dynamic_position = get_target_position(jiggle_data_chain[i].bone_node_cache); + } + if (!_cache_node(target_node_cache, target_node)) { + WARN_PRINT_ONCE("2DJiggle: unable to lookup target"); + return; + } + Vector2 target_position = get_target_position(target_node_cache); + for (int i = 0; i < jiggle_data_chain.size(); i++) { + _execute_jiggle_joint(i, target_position, p_delta); + } +} + +TypedArray SkeletonModification2DJiggle::get_configuration_warnings() const { + TypedArray ret = SkeletonModification2D::get_configuration_warnings(); + if (!get_skeleton()) { + return ret; + } + if (!_cache_node(target_node_cache, target_node)) { + ret.append(vformat("Target node %s was not found.", (String)target_node)); + } + for (int i = 0; i < jiggle_data_chain.size(); i++) { + if (!_cache_bone(jiggle_data_chain[i].bone_node_cache, jiggle_data_chain[i].bone_node)) { + ret.append(vformat("Joint %d bone %s was not found.", i, jiggle_data_chain[i].bone_node)); + } } + return ret; } diff --git a/scene/2d/skeleton_modification_2d_jiggle.h b/scene/2d/skeleton_modification_2d_jiggle.h index eafa76192db8..2745b8f8f865 100644 --- a/scene/2d/skeleton_modification_2d_jiggle.h +++ b/scene/2d/skeleton_modification_2d_jiggle.h @@ -43,9 +43,8 @@ class SkeletonModification2DJiggle : public SkeletonModification2D { private: struct Jiggle_Joint_Data2D { - int bone_idx = -1; - NodePath bone2d_node; - ObjectID bone2d_node_cache; + NodePath bone_node; + mutable Variant bone_node_cache; bool override_defaults = false; float stiffness = 3; @@ -66,8 +65,7 @@ class SkeletonModification2DJiggle : public SkeletonModification2D { Vector jiggle_data_chain; NodePath target_node; - ObjectID target_node_cache; - void update_target_cache(); + mutable Variant target_node_cache; float stiffness = 3; float mass = 0.75; @@ -78,8 +76,7 @@ class SkeletonModification2DJiggle : public SkeletonModification2D { bool use_colliders = false; uint32_t collision_mask = 1; - void jiggle_joint_update_bone2d_cache(int p_joint_idx); - void _execute_jiggle_joint(int p_joint_idx, Node2D *p_target, float p_delta); + void _execute_jiggle_joint(int p_joint_idx, Vector2 p_target_position, float p_delta); void _update_jiggle_joint_data(); protected: @@ -87,10 +84,10 @@ class SkeletonModification2DJiggle : public SkeletonModification2D { bool _set(const StringName &p_path, const Variant &p_value); bool _get(const StringName &p_path, Variant &r_ret) const; void _get_property_list(List *p_list) const; + void execute(real_t p_delta) override; + TypedArray get_configuration_warnings() const override; public: - void _notification(int p_what); - void set_target_node(const NodePath &p_target_node); NodePath get_target_node() const; @@ -110,26 +107,24 @@ class SkeletonModification2DJiggle : public SkeletonModification2D { void set_collision_mask(int p_mask); int get_collision_mask() const; - int get_jiggle_data_chain_length(); - void set_jiggle_data_chain_length(int p_new_length); - - void set_jiggle_joint_bone2d_node(int p_joint_idx, const NodePath &p_target_node); - NodePath get_jiggle_joint_bone2d_node(int p_joint_idx) const; - void set_jiggle_joint_bone_index(int p_joint_idx, int p_bone_idx); - int get_jiggle_joint_bone_index(int p_joint_idx) const; - - void set_jiggle_joint_override(int p_joint_idx, bool p_override); - bool get_jiggle_joint_override(int p_joint_idx) const; - void set_jiggle_joint_stiffness(int p_joint_idx, float p_stiffness); - float get_jiggle_joint_stiffness(int p_joint_idx) const; - void set_jiggle_joint_mass(int p_joint_idx, float p_mass); - float get_jiggle_joint_mass(int p_joint_idx) const; - void set_jiggle_joint_damping(int p_joint_idx, float p_damping); - float get_jiggle_joint_damping(int p_joint_idx) const; - void set_jiggle_joint_use_gravity(int p_joint_idx, bool p_use_gravity); - bool get_jiggle_joint_use_gravity(int p_joint_idx) const; - void set_jiggle_joint_gravity(int p_joint_idx, Vector2 p_gravity); - Vector2 get_jiggle_joint_gravity(int p_joint_idx) const; + int get_joint_count(); + void set_joint_count(int p_new_length); + + void set_joint_bone_node(int p_joint_idx, const NodePath &p_target_node); + NodePath get_joint_bone_node(int p_joint_idx) const; + + void set_joint_override(int p_joint_idx, bool p_override); + bool get_joint_override(int p_joint_idx) const; + void set_joint_stiffness(int p_joint_idx, float p_stiffness); + float get_joint_stiffness(int p_joint_idx) const; + void set_joint_mass(int p_joint_idx, float p_mass); + float get_joint_mass(int p_joint_idx) const; + void set_joint_damping(int p_joint_idx, float p_damping); + float get_joint_damping(int p_joint_idx) const; + void set_joint_use_gravity(int p_joint_idx, bool p_use_gravity); + bool get_joint_use_gravity(int p_joint_idx) const; + void set_joint_gravity(int p_joint_idx, Vector2 p_gravity); + Vector2 get_joint_gravity(int p_joint_idx) const; SkeletonModification2DJiggle(); ~SkeletonModification2DJiggle(); diff --git a/scene/2d/skeleton_modification_2d_lookat.cpp b/scene/2d/skeleton_modification_2d_lookat.cpp index 380cd128da14..c8dfa1f39ecc 100644 --- a/scene/2d/skeleton_modification_2d_lookat.cpp +++ b/scene/2d/skeleton_modification_2d_lookat.cpp @@ -31,174 +31,33 @@ #include "skeleton_modification_2d_lookat.h" #include "scene/2d/skeleton_2d.h" -#ifdef TOOLS_ENABLED -#include "editor/editor_settings.h" -#endif // TOOLS_ENABLED - -bool SkeletonModification2DLookAt::_set(const StringName &p_path, const Variant &p_value) { - String path = p_path; - - if (path.begins_with("enable_constraint")) { - set_enable_constraint(p_value); - } else if (path.begins_with("constraint_angle_min")) { - set_constraint_angle_min(Math::deg_to_rad(float(p_value))); - } else if (path.begins_with("constraint_angle_max")) { - set_constraint_angle_max(Math::deg_to_rad(float(p_value))); - } else if (path.begins_with("constraint_angle_invert")) { - set_constraint_angle_invert(p_value); - } else if (path.begins_with("constraint_in_localspace")) { - set_constraint_in_localspace(p_value); - } else if (path.begins_with("additional_rotation")) { - set_additional_rotation(Math::deg_to_rad(float(p_value))); - } - -#ifdef TOOLS_ENABLED - if (path.begins_with("editor/draw_gizmo")) { - set_editor_draw_gizmo(p_value); - } -#endif // TOOLS_ENABLED - - return true; -} - -bool SkeletonModification2DLookAt::_get(const StringName &p_path, Variant &r_ret) const { - String path = p_path; - - if (path.begins_with("enable_constraint")) { - r_ret = get_enable_constraint(); - } else if (path.begins_with("constraint_angle_min")) { - r_ret = Math::rad_to_deg(get_constraint_angle_min()); - } else if (path.begins_with("constraint_angle_max")) { - r_ret = Math::rad_to_deg(get_constraint_angle_max()); - } else if (path.begins_with("constraint_angle_invert")) { - r_ret = get_constraint_angle_invert(); - } else if (path.begins_with("constraint_in_localspace")) { - r_ret = get_constraint_in_localspace(); - } else if (path.begins_with("additional_rotation")) { - r_ret = Math::rad_to_deg(get_additional_rotation()); - } - -#ifdef TOOLS_ENABLED - if (path.begins_with("editor/draw_gizmo")) { - r_ret = get_editor_draw_gizmo(); - } -#endif // TOOLS_ENABLED - - return true; +bool SkeletonModification2DLookAt::is_property_hidden(String p_property_name) const { + return (!enable_constraint && p_property_name.begins_with("constraint_")); } -void SkeletonModification2DLookAt::_get_property_list(List *p_list) const { - p_list->push_back(PropertyInfo(Variant::BOOL, "enable_constraint", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - if (enable_constraint) { - p_list->push_back(PropertyInfo(Variant::FLOAT, "constraint_angle_min", PROPERTY_HINT_RANGE, "-360, 360, 0.01", PROPERTY_USAGE_DEFAULT)); - p_list->push_back(PropertyInfo(Variant::FLOAT, "constraint_angle_max", PROPERTY_HINT_RANGE, "-360, 360, 0.01", PROPERTY_USAGE_DEFAULT)); - p_list->push_back(PropertyInfo(Variant::BOOL, "constraint_angle_invert", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - p_list->push_back(PropertyInfo(Variant::BOOL, "constraint_in_localspace", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - } - p_list->push_back(PropertyInfo(Variant::FLOAT, "additional_rotation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - p_list->push_back(PropertyInfo(Variant::BOOL, "editor/draw_gizmo", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - } -#endif // TOOLS_ENABLED -} +void SkeletonModification2DLookAt::draw_editor_gizmo() { + SkeletonModification2D::draw_editor_gizmo(); -void SkeletonModification2DLookAt::_draw_editor_gizmo() { - if (!enabled || !is_setup) { + Bone2D *operation_bone = _cache_bone(bone_node_cache, bone_node); + if (!operation_bone) { return; } - - Bone2D *operation_bone = skeleton->get_bone(bone_idx); editor_draw_angle_constraints(operation_bone, constraint_angle_min, constraint_angle_max, enable_constraint, constraint_in_localspace, constraint_angle_invert); } -void SkeletonModification2DLookAt::update_bone2d_cache() { - if (!is_setup) { - ERR_PRINT_ONCE("Cannot update Bone2D cache: modification is not properly setup!"); - return; - } - - bone2d_node_cache = ObjectID(); - if (is_inside_tree()) { - if (has_node(bone2d_node)) { - Node *node = get_node_or_null(bone2d_node); - ERR_FAIL_COND_MSG(!node || skeleton == node, - "Cannot update Bone2D cache: node is this modification's skeleton or cannot be found!"); - ERR_FAIL_COND_MSG(!node->is_inside_tree(), - "Cannot update Bone2D cache: node is not in the scene tree!"); - bone2d_node_cache = node->get_instance_id(); - - Bone2D *bone = Object::cast_to(node); - if (bone) { - bone_idx = bone->get_index_in_skeleton(); - } else { - ERR_FAIL_MSG("Error Bone2D cache: Nodepath to Bone2D is not a Bone2D node!"); - } - - // Set this to null so we update it - target_node_reference = nullptr; - } - } -} - -void SkeletonModification2DLookAt::set_bone2d_node(const NodePath &p_target_node) { - bone2d_node = p_target_node; - update_bone2d_cache(); +void SkeletonModification2DLookAt::set_bone_node(const NodePath &p_bone_node) { + bone_node = p_bone_node; + bone_node_cache = Variant(); } -NodePath SkeletonModification2DLookAt::get_bone2d_node() const { - return bone2d_node; -} - -int SkeletonModification2DLookAt::get_bone_index() const { - return bone_idx; -} - -void SkeletonModification2DLookAt::set_bone_index(int p_bone_idx) { - ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!"); - - if (is_setup) { - if (skeleton) { - ERR_FAIL_INDEX_MSG(p_bone_idx, skeleton->get_bone_count(), "Passed-in Bone index is out of range!"); - bone_idx = p_bone_idx; - bone2d_node_cache = skeleton->get_bone(p_bone_idx)->get_instance_id(); - bone2d_node = skeleton->get_path_to(skeleton->get_bone(p_bone_idx)); - } else { - WARN_PRINT("Cannot verify the bone index for this modification..."); - bone_idx = p_bone_idx; - } - } else { - WARN_PRINT("Cannot verify the bone index for this modification..."); - bone_idx = p_bone_idx; - } - - notify_property_list_changed(); -} - -void SkeletonModification2DLookAt::update_target_cache() { - if (!is_setup) { - ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!"); - return; - } - - target_node_cache = ObjectID(); - if (is_inside_tree()) { - if (has_node(target_node)) { - Node *node = get_node_or_null(target_node); - ERR_FAIL_COND_MSG(!node || skeleton == node, - "Cannot update target cache: node is this modification's skeleton or cannot be found!"); - ERR_FAIL_COND_MSG(!node->is_inside_tree(), - "Cannot update target cache: node is not in the scene tree!"); - target_node_cache = node->get_instance_id(); - } - } +NodePath SkeletonModification2DLookAt::get_bone_node() const { + return bone_node; } void SkeletonModification2DLookAt::set_target_node(const NodePath &p_target_node) { target_node = p_target_node; - update_target_cache(); + target_node_cache = Variant(); } NodePath SkeletonModification2DLookAt::get_target_node() const { @@ -255,10 +114,8 @@ bool SkeletonModification2DLookAt::get_constraint_in_localspace() const { } void SkeletonModification2DLookAt::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_bone2d_node", "bone2d_nodepath"), &SkeletonModification2DLookAt::set_bone2d_node); - ClassDB::bind_method(D_METHOD("get_bone2d_node"), &SkeletonModification2DLookAt::get_bone2d_node); - ClassDB::bind_method(D_METHOD("set_bone_index", "bone_idx"), &SkeletonModification2DLookAt::set_bone_index); - ClassDB::bind_method(D_METHOD("get_bone_index"), &SkeletonModification2DLookAt::get_bone_index); + ClassDB::bind_method(D_METHOD("set_bone_node", "bone_nodepath"), &SkeletonModification2DLookAt::set_bone_node); + ClassDB::bind_method(D_METHOD("get_bone_node"), &SkeletonModification2DLookAt::get_bone_node); ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification2DLookAt::set_target_node); ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification2DLookAt::get_target_node); @@ -274,112 +131,80 @@ void SkeletonModification2DLookAt::_bind_methods() { ClassDB::bind_method(D_METHOD("get_constraint_angle_max"), &SkeletonModification2DLookAt::get_constraint_angle_max); ClassDB::bind_method(D_METHOD("set_constraint_angle_invert", "invert"), &SkeletonModification2DLookAt::set_constraint_angle_invert); ClassDB::bind_method(D_METHOD("get_constraint_angle_invert"), &SkeletonModification2DLookAt::get_constraint_angle_invert); + ClassDB::bind_method(D_METHOD("set_constraint_in_localspace", "is_local_space"), &SkeletonModification2DLookAt::set_constraint_in_localspace); + ClassDB::bind_method(D_METHOD("get_constraint_in_localspace"), &SkeletonModification2DLookAt::get_constraint_in_localspace); + + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "bone_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Bone2D"), "set_bone_node", "get_bone_node"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "CanvasItem"), "set_target_node", "get_target_node"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "bone_index"), "set_bone_index", "get_bone_index"); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "bone2d_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Bone2D"), "set_bone2d_node", "get_bone2d_node"); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node2D"), "set_target_node", "get_target_node"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enable_constraint", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT), "set_enable_constraint", "get_enable_constraint"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "constraint_angle_min", PROPERTY_HINT_RANGE, "-360, 360, 0.01", PROPERTY_USAGE_DEFAULT), "set_constraint_angle_min", "get_constraint_angle_min"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "constraint_angle_max", PROPERTY_HINT_RANGE, "-360, 360, 0.01", PROPERTY_USAGE_DEFAULT), "set_constraint_angle_max", "get_constraint_angle_max"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "constraint_angle_invert", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT), "set_constraint_angle_invert", "get_constraint_angle_invert"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "constraint_in_localspace", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT), "set_constraint_in_localspace", "get_constraint_in_localspace"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "additional_rotation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT), "set_additional_rotation", "get_additional_rotation"); } SkeletonModification2DLookAt::SkeletonModification2DLookAt() { - is_setup = false; - bone_idx = -1; - additional_rotation = 0; - enable_constraint = false; - constraint_angle_min = 0; - constraint_angle_max = Math_PI * 2; - constraint_angle_invert = false; - enabled = true; - - editor_draw_gizmo = true; } SkeletonModification2DLookAt::~SkeletonModification2DLookAt() { } -void SkeletonModification2DLookAt::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - set_process_internal(false); - set_physics_process_internal(false); - if (get_execution_mode() == 0) { - set_process_internal(true); - } else if (get_execution_mode() == 1) { - set_physics_process_internal(true); - } - skeleton = cast_to(get_node_or_null(get_skeleton_path())); - is_setup = true; - update_target_cache(); - update_bone2d_cache(); - } break; - case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: - [[fallthrough]]; - case NOTIFICATION_INTERNAL_PROCESS: { - ERR_FAIL_COND_MSG(!is_setup || skeleton == nullptr, - "Modification is not setup and therefore cannot execute!"); - if (!enabled) { - return; - } - - if (target_node_cache.is_null()) { - WARN_PRINT_ONCE("Target cache is out of date. Attempting to update..."); - update_target_cache(); - return; - } - - if (bone2d_node_cache.is_null() && !bone2d_node.is_empty()) { - update_bone2d_cache(); - WARN_PRINT_ONCE("Bone2D node cache is out of date. Attempting to update..."); - return; - } - - if (target_node_reference == nullptr) { - target_node_reference = Object::cast_to(ObjectDB::get_instance(target_node_cache)); - } - if (!target_node_reference || !target_node_reference->is_inside_tree()) { - ERR_PRINT_ONCE("Target node is not in the scene tree. Cannot execute modification!"); - return; - } - if (bone_idx <= -1) { - ERR_PRINT_ONCE("Bone index is invalid. Cannot execute modification!"); - return; - } - - Bone2D *operation_bone = skeleton->get_bone(bone_idx); - if (operation_bone == nullptr) { - ERR_PRINT_ONCE("bone_idx for modification does not point to a valid bone! Cannot execute modification"); - return; - } - - Transform2D operation_transform = operation_bone->get_global_transform(); - Transform2D target_trans = target_node_reference->get_global_transform(); - - // Look at the target! - operation_transform = operation_transform.looking_at(target_trans.get_origin()); - // Apply whatever scale it had prior to looking_at - operation_transform.set_scale(operation_bone->get_global_scale()); - - // Account for the direction the bone faces in: - operation_transform.set_rotation(operation_transform.get_rotation() - operation_bone->get_bone_angle()); - - // Apply additional rotation - operation_transform.set_rotation(operation_transform.get_rotation() + additional_rotation); - - // Apply constraints in globalspace: - if (enable_constraint && !constraint_in_localspace) { - operation_transform.set_rotation(clamp_angle(operation_transform.get_rotation(), constraint_angle_min, constraint_angle_max, constraint_angle_invert)); - } - - // Convert from a global transform to a local transform via the Bone2D node - operation_bone->set_global_transform(operation_transform); - operation_transform = operation_bone->get_transform(); - - // Apply constraints in localspace: - if (enable_constraint && constraint_in_localspace) { - operation_transform.set_rotation(clamp_angle(operation_transform.get_rotation(), constraint_angle_min, constraint_angle_max, constraint_angle_invert)); - } - - // Set the local pose override, and to make sure child bones are also updated, set the transform of the bone. - skeleton->set_bone_local_pose_override(bone_idx, operation_transform, 1.0, true); - operation_bone->set_transform(operation_transform); - } break; + +void SkeletonModification2DLookAt::execute(real_t delta) { + SkeletonModification2D::execute(delta); + + if (!_cache_node(bone_node_cache, bone_node) || !_cache_node(target_node_cache, target_node)) { + WARN_PRINT_ONCE("SkeletonModification2DLookAt not initialized in time to execute"); + return; + } + Bone2D *operation_bone = cast_to((Object *)bone_node_cache); + if (operation_bone == nullptr) { + ERR_PRINT_ONCE("bone_idx for modification does not point to a valid bone! Cannot execute modification"); + return; + } + + Transform2D operation_transform = operation_bone->get_global_transform(); + Transform2D target_trans = get_target_transform(target_node_cache); + + // Look at the target! + operation_transform = operation_transform.looking_at(target_trans.get_origin()); + // Apply whatever scale it had prior to looking_at + operation_transform.set_scale(operation_bone->get_global_scale()); + + // Account for the direction the bone faces in: + operation_transform.set_rotation(operation_transform.get_rotation() - operation_bone->get_bone_angle()); + + // Apply additional rotation + operation_transform.set_rotation(operation_transform.get_rotation() + additional_rotation); + + // Apply constraints in globalspace: + if (enable_constraint && !constraint_in_localspace) { + operation_transform.set_rotation(clamp_angle(operation_transform.get_rotation(), constraint_angle_min, constraint_angle_max, constraint_angle_invert)); + } + + // Convert from a global transform to a local transform via the Bone2D node + operation_bone->set_global_transform(operation_transform); + operation_transform = operation_bone->get_transform(); + + // Apply constraints in localspace: + if (enable_constraint && constraint_in_localspace) { + operation_transform.set_rotation(clamp_angle(operation_transform.get_rotation(), constraint_angle_min, constraint_angle_max, constraint_angle_invert)); + } + + operation_bone->set_transform(operation_transform); +} + +TypedArray SkeletonModification2DLookAt::get_configuration_warnings() const { + TypedArray ret = SkeletonModification2D::get_configuration_warnings(); + if (!get_skeleton()) { + return ret; + } + if (!_cache_node(target_node_cache, target_node)) { + ret.append(vformat("Target node %s was not found.", (String)target_node)); + } + if (!_cache_node(bone_node_cache, bone_node)) { + ret.append(vformat("Bone2d node %s was not found.", (String)bone_node)); } + return ret; } diff --git a/scene/2d/skeleton_modification_2d_lookat.h b/scene/2d/skeleton_modification_2d_lookat.h index 51f32d59a1ba..f79b5d5d279d 100644 --- a/scene/2d/skeleton_modification_2d_lookat.h +++ b/scene/2d/skeleton_modification_2d_lookat.h @@ -42,13 +42,11 @@ class SkeletonModification2DLookAt : public SkeletonModification2D { GDCLASS(SkeletonModification2DLookAt, SkeletonModification2D); private: - int bone_idx = -1; - NodePath bone2d_node; - ObjectID bone2d_node_cache; + NodePath bone_node; + mutable Variant bone_node_cache; NodePath target_node; - ObjectID target_node_cache; - Node2D *target_node_reference = nullptr; + mutable Variant target_node_cache; float additional_rotation = 0; bool enable_constraint = false; @@ -57,23 +55,16 @@ class SkeletonModification2DLookAt : public SkeletonModification2D { bool constraint_angle_invert = false; bool constraint_in_localspace = true; - void update_bone2d_cache(); - void update_target_cache(); - protected: static void _bind_methods(); - bool _set(const StringName &p_path, const Variant &p_value); - bool _get(const StringName &p_path, Variant &r_ret) const; - void _get_property_list(List *p_list) const; + void execute(real_t delta) override; + void draw_editor_gizmo() override; + bool is_property_hidden(String property_name) const override; + TypedArray get_configuration_warnings() const override; public: - void _notification(int p_what); - void _draw_editor_gizmo() override; - - void set_bone2d_node(const NodePath &p_target_node); - NodePath get_bone2d_node() const; - void set_bone_index(int p_idx); - int get_bone_index() const; + void set_bone_node(const NodePath &p_target_node); + NodePath get_bone_node() const; void set_target_node(const NodePath &p_target_node); NodePath get_target_node() const; diff --git a/scene/2d/skeleton_modification_2d_physicalbones.cpp b/scene/2d/skeleton_modification_2d_physicalbones.cpp index 74b12a2bf606..4591cd0e17c9 100644 --- a/scene/2d/skeleton_modification_2d_physicalbones.cpp +++ b/scene/2d/skeleton_modification_2d_physicalbones.cpp @@ -37,23 +37,21 @@ bool SkeletonModification2DPhysicalBones::_set(const StringName &p_path, const V #ifdef TOOLS_ENABLED // Exposes a way to fetch the PhysicalBone2D nodes from the Godot editor. - if (is_setup) { - if (Engine::get_singleton()->is_editor_hint()) { - if (path.begins_with("fetch_bones")) { - fetch_physical_bones(); - notify_property_list_changed(); - return true; - } + if (Engine::get_singleton()->is_editor_hint()) { + if (path.begins_with("fetch_bones")) { + _fetch_physical_bones(); + notify_property_list_changed(); + return true; } } #endif //TOOLS_ENABLED if (path.begins_with("joint_")) { - int which = path.get_slicec('_', 1).to_int(); - String what = path.get_slicec('_', 2); + int which = path.get_slicec('_', 0).substr(6).to_int(); + String what = path.get_slicec('_', 1); ERR_FAIL_INDEX_V(which, physical_bone_chain.size(), false); - if (what == "nodepath") { + if (what == "node") { set_physical_bone_node(which, p_value); } return true; @@ -73,11 +71,11 @@ bool SkeletonModification2DPhysicalBones::_get(const StringName &p_path, Variant #endif //TOOLS_ENABLED if (path.begins_with("joint_")) { - int which = path.get_slicec('_', 1).to_int(); - String what = path.get_slicec('_', 2); + int which = path.get_slicec('_', 0).substr(6).to_int(); + String what = path.get_slicec('_', 1); ERR_FAIL_INDEX_V(which, physical_bone_chain.size(), false); - if (what == "nodepath") { + if (what == "node") { r_ret = get_physical_bone_node(which); } return true; @@ -88,48 +86,30 @@ bool SkeletonModification2DPhysicalBones::_get(const StringName &p_path, Variant void SkeletonModification2DPhysicalBones::_get_property_list(List *p_list) const { #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint()) { - p_list->push_back(PropertyInfo(Variant::BOOL, "fetch_bones", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::BOOL, "fetch_bones", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); } #endif //TOOLS_ENABLED for (int i = 0; i < physical_bone_chain.size(); i++) { - String base_string = "joint_" + itos(i) + "_"; + String base_string = "joint_" + itos(i) + "/"; - p_list->push_back(PropertyInfo(Variant::NODE_PATH, base_string + "nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "PhysicalBone2D", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::NODE_PATH, base_string + "node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "PhysicalBone2D", PROPERTY_USAGE_DEFAULT)); } } -void SkeletonModification2DPhysicalBones::_physical_bone_update_cache(int p_joint_idx) { - ERR_FAIL_INDEX_MSG(p_joint_idx, physical_bone_chain.size(), "Cannot update PhysicalBone2D cache: joint index out of range!"); - if (!is_setup) { - ERR_PRINT_ONCE("Cannot update PhysicalBone2D cache: modification is not properly setup!"); - return; - } - - physical_bone_chain.write[p_joint_idx].physical_bone_node_cache = ObjectID(); - if (is_inside_tree()) { - if (has_node(physical_bone_chain[p_joint_idx].physical_bone_node)) { - Node *node = get_node(physical_bone_chain[p_joint_idx].physical_bone_node); - ERR_FAIL_COND_MSG(!node || skeleton == node, - "Cannot update Physical Bone2D " + itos(p_joint_idx) + " cache: node is this modification's skeleton or cannot be found!"); - ERR_FAIL_COND_MSG(!node->is_inside_tree(), - "Cannot update Physical Bone2D " + itos(p_joint_idx) + " cache: node is not in scene tree!"); - physical_bone_chain.write[p_joint_idx].physical_bone_node_cache = node->get_instance_id(); - } - } -} - -int SkeletonModification2DPhysicalBones::get_physical_bone_chain_length() { +int SkeletonModification2DPhysicalBones::get_joint_count() { return physical_bone_chain.size(); } -void SkeletonModification2DPhysicalBones::set_physical_bone_chain_length(int p_length) { +void SkeletonModification2DPhysicalBones::set_joint_count(int p_length) { ERR_FAIL_COND(p_length < 0); physical_bone_chain.resize(p_length); notify_property_list_changed(); } -void SkeletonModification2DPhysicalBones::fetch_physical_bones() { +#ifdef TOOLS_ENABLED +void SkeletonModification2DPhysicalBones::_fetch_physical_bones() { + Skeleton2D *skeleton = get_skeleton(); ERR_FAIL_COND_MSG(!skeleton, "No skeleton found! Cannot fetch physical bones!"); physical_bone_chain.clear(); @@ -155,59 +135,12 @@ void SkeletonModification2DPhysicalBones::fetch_physical_bones() { } } } - -void SkeletonModification2DPhysicalBones::start_simulation(const TypedArray &p_bones) { - _simulation_state_dirty = true; - _simulation_state_dirty_names = p_bones; - _simulation_state_dirty_process = true; - - if (is_setup) { - _update_simulation_state(); - } -} - -void SkeletonModification2DPhysicalBones::stop_simulation(const TypedArray &p_bones) { - _simulation_state_dirty = true; - _simulation_state_dirty_names = p_bones; - _simulation_state_dirty_process = false; - - if (is_setup) { - _update_simulation_state(); - } -} - -void SkeletonModification2DPhysicalBones::_update_simulation_state() { - if (!_simulation_state_dirty) { - return; - } - _simulation_state_dirty = false; - - if (_simulation_state_dirty_names.size() <= 0) { - for (int i = 0; i < physical_bone_chain.size(); i++) { - PhysicalBone2D *physical_bone = Object::cast_to(skeleton->get_node(physical_bone_chain[i].physical_bone_node)); - if (!physical_bone) { - continue; - } - - physical_bone->set_simulate_physics(_simulation_state_dirty_process); - } - } else { - for (int i = 0; i < physical_bone_chain.size(); i++) { - PhysicalBone2D *physical_bone = Object::cast_to(ObjectDB::get_instance(physical_bone_chain[i].physical_bone_node_cache)); - if (!physical_bone) { - continue; - } - if (_simulation_state_dirty_names.has(physical_bone->get_name())) { - physical_bone->set_simulate_physics(_simulation_state_dirty_process); - } - } - } -} +#endif void SkeletonModification2DPhysicalBones::set_physical_bone_node(int p_joint_idx, const NodePath &p_nodepath) { ERR_FAIL_INDEX_MSG(p_joint_idx, physical_bone_chain.size(), "Joint index out of range!"); physical_bone_chain.write[p_joint_idx].physical_bone_node = p_nodepath; - _physical_bone_update_cache(p_joint_idx); + physical_bone_chain.write[p_joint_idx].physical_bone_node_cache = Variant(); } NodePath SkeletonModification2DPhysicalBones::get_physical_bone_node(int p_joint_idx) const { @@ -216,25 +149,63 @@ NodePath SkeletonModification2DPhysicalBones::get_physical_bone_node(int p_joint } void SkeletonModification2DPhysicalBones::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_physical_bone_chain_length", "length"), &SkeletonModification2DPhysicalBones::set_physical_bone_chain_length); - ClassDB::bind_method(D_METHOD("get_physical_bone_chain_length"), &SkeletonModification2DPhysicalBones::get_physical_bone_chain_length); + ClassDB::bind_method(D_METHOD("set_joint_count", "length"), &SkeletonModification2DPhysicalBones::set_joint_count); + ClassDB::bind_method(D_METHOD("get_joint_count"), &SkeletonModification2DPhysicalBones::get_joint_count); ClassDB::bind_method(D_METHOD("set_physical_bone_node", "joint_idx", "physicalbone2d_node"), &SkeletonModification2DPhysicalBones::set_physical_bone_node); ClassDB::bind_method(D_METHOD("get_physical_bone_node", "joint_idx"), &SkeletonModification2DPhysicalBones::get_physical_bone_node); - ClassDB::bind_method(D_METHOD("fetch_physical_bones"), &SkeletonModification2DPhysicalBones::fetch_physical_bones); - ClassDB::bind_method(D_METHOD("start_simulation", "bones"), &SkeletonModification2DPhysicalBones::start_simulation, DEFVAL(Array())); - ClassDB::bind_method(D_METHOD("stop_simulation", "bones"), &SkeletonModification2DPhysicalBones::stop_simulation, DEFVAL(Array())); - - ADD_PROPERTY(PropertyInfo(Variant::INT, "physical_bone_chain_length", PROPERTY_HINT_RANGE, "0,100,1"), "set_physical_bone_chain_length", "get_physical_bone_chain_length"); + ADD_ARRAY_COUNT("Physical bone chain", "joint_count", "set_joint_count", "get_joint_count", "joint_"); } SkeletonModification2DPhysicalBones::SkeletonModification2DPhysicalBones() { - is_setup = false; physical_bone_chain = Vector(); - enabled = true; - editor_draw_gizmo = false; // Nothing to really show in a gizmo right now. } SkeletonModification2DPhysicalBones::~SkeletonModification2DPhysicalBones() { } + +void SkeletonModification2DPhysicalBones::execute(real_t delta) { + SkeletonModification2D::execute(delta); + + for (int i = 0; i < physical_bone_chain.size(); i++) { + const PhysicalBone_Data2D &bone_data = physical_bone_chain[i]; + if (_cache_bone(bone_data.physical_bone_node_cache, bone_data.physical_bone_node)) { + WARN_PRINT_ONCE("2DPhysicalBones unable to get a physical bone"); + return; + } + } + + for (int i = 0; i < physical_bone_chain.size(); i++) { + const PhysicalBone_Data2D &bone_data = physical_bone_chain[i]; + PhysicalBone2D *physical_bone = Object::cast_to(_cache_bone(bone_data.physical_bone_node_cache, bone_data.physical_bone_node)); + if (physical_bone) { + Node2D *bone_2d = physical_bone->get_cached_bone_node(); + if (bone_2d && physical_bone->get_simulate_physics() && !physical_bone->get_follow_bone_when_simulating()) { + bone_2d->set_global_transform(physical_bone->get_global_transform()); + } + } + } +} + +TypedArray SkeletonModification2DPhysicalBones::get_configuration_warnings() const { + TypedArray ret = SkeletonModification2D::get_configuration_warnings(); + if (!get_skeleton()) { + return ret; + } + for (int i = 0; i < physical_bone_chain.size(); i++) { + if (!_cache_node(physical_bone_chain[i].physical_bone_node_cache, physical_bone_chain[i].physical_bone_node)) { + ret.append(vformat("Joint %d physical bone path %s was not found.", i, physical_bone_chain[i].physical_bone_node)); + } + PhysicalBone2D *physbone = cast_to((Object *)physical_bone_chain[i].physical_bone_node_cache); + if (!physbone) { + ret.append(vformat("Joint %d physical bone path %s is not a PhysicalBone2D.", i, physical_bone_chain[i].physical_bone_node)); + } else { + Node2D *bone = physbone->get_cached_bone_node(); + if (!bone) { + ret.append(vformat("Joint %d physical bone %s not connected to a bone.", i, physical_bone_chain[i].physical_bone_node)); + } + } + } + return ret; +} diff --git a/scene/2d/skeleton_modification_2d_physicalbones.h b/scene/2d/skeleton_modification_2d_physicalbones.h index f17d3843092c..dfad42134a47 100644 --- a/scene/2d/skeleton_modification_2d_physicalbones.h +++ b/scene/2d/skeleton_modification_2d_physicalbones.h @@ -45,93 +45,29 @@ class SkeletonModification2DPhysicalBones : public SkeletonModification2D { private: struct PhysicalBone_Data2D { NodePath physical_bone_node; - ObjectID physical_bone_node_cache; + mutable Variant physical_bone_node_cache; }; Vector physical_bone_chain; - void _physical_bone_update_cache(int p_joint_idx); - - bool _simulation_state_dirty = false; - TypedArray _simulation_state_dirty_names; - bool _simulation_state_dirty_process = false; - void _update_simulation_state(); +#ifdef TOOLS_ENABLED + void _fetch_physical_bones(); +#endif protected: static void _bind_methods(); bool _get(const StringName &p_path, Variant &r_ret) const; bool _set(const StringName &p_path, const Variant &p_value); void _get_property_list(List *p_list) const; + void execute(real_t p_delta) override; + TypedArray get_configuration_warnings() const override; public: - void _notification(int p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - set_process_internal(false); - set_physics_process_internal(false); - if (get_execution_mode() == 0) { - set_process_internal(true); - } else if (get_execution_mode() == 1) { - set_physics_process_internal(true); - } - is_setup = true; - skeleton = cast_to(get_node_or_null(get_skeleton_path())); - if (skeleton) { - for (int i = 0; i < physical_bone_chain.size(); i++) { - _physical_bone_update_cache(i); - } - } - } break; - case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: - [[fallthrough]]; - case NOTIFICATION_INTERNAL_PROCESS: { - ERR_FAIL_COND_MSG(!is_setup || skeleton == nullptr, - "Modification is not setup and therefore cannot execute!"); - if (!enabled) { - return; - } - - if (_simulation_state_dirty) { - _update_simulation_state(); - } - - for (int i = 0; i < physical_bone_chain.size(); i++) { - PhysicalBone_Data2D bone_data = physical_bone_chain[i]; - if (bone_data.physical_bone_node_cache.is_null()) { - WARN_PRINT_ONCE("PhysicalBone2D cache " + itos(i) + " is out of date. Attempting to update..."); - _physical_bone_update_cache(i); - continue; - } - - PhysicalBone2D *physical_bone = Object::cast_to(ObjectDB::get_instance(bone_data.physical_bone_node_cache)); - if (!physical_bone) { - ERR_PRINT_ONCE("PhysicalBone2D not found at index " + itos(i) + "!"); - return; - } - if (physical_bone->get_bone2d_index() < 0 || physical_bone->get_bone2d_index() > skeleton->get_bone_count()) { - ERR_PRINT_ONCE("PhysicalBone2D at index " + itos(i) + " has invalid Bone2D!"); - return; - } - Bone2D *bone_2d = skeleton->get_bone(physical_bone->get_bone2d_index()); - - if (physical_bone->get_simulate_physics() && !physical_bone->get_follow_bone_when_simulating()) { - bone_2d->set_global_transform(physical_bone->get_global_transform()); - skeleton->set_bone_local_pose_override(physical_bone->get_bone2d_index(), bone_2d->get_transform(), 1.0, true); - } - } - } break; - } - } - - int get_physical_bone_chain_length(); - void set_physical_bone_chain_length(int p_new_length); + int get_joint_count(); + void set_joint_count(int p_new_length); void set_physical_bone_node(int p_joint_idx, const NodePath &p_path); NodePath get_physical_bone_node(int p_joint_idx) const; - void fetch_physical_bones(); - void start_simulation(const TypedArray &p_bones); - void stop_simulation(const TypedArray &p_bones); - SkeletonModification2DPhysicalBones(); ~SkeletonModification2DPhysicalBones(); }; diff --git a/scene/2d/skeleton_modification_2d_twoboneik.cpp b/scene/2d/skeleton_modification_2d_twoboneik.cpp index 7c09c84e192b..dd3b570b19c0 100644 --- a/scene/2d/skeleton_modification_2d_twoboneik.cpp +++ b/scene/2d/skeleton_modification_2d_twoboneik.cpp @@ -35,75 +35,12 @@ #include "editor/editor_settings.h" #endif // TOOLS_ENABLED -bool SkeletonModification2DTwoBoneIK::_set(const StringName &p_path, const Variant &p_value) { - String path = p_path; - - if (path == "joint_one_bone_idx") { - set_joint_one_bone_idx(p_value); - } else if (path == "joint_one_bone2d_node") { - set_joint_one_bone2d_node(p_value); - } else if (path == "joint_two_bone_idx") { - set_joint_two_bone_idx(p_value); - } else if (path == "joint_two_bone2d_node") { - set_joint_two_bone2d_node(p_value); - } - -#ifdef TOOLS_ENABLED - if (path.begins_with("editor/draw_gizmo")) { - set_editor_draw_gizmo(p_value); - } else if (path.begins_with("editor/draw_min_max")) { - set_editor_draw_min_max(p_value); - } -#endif // TOOLS_ENABLED - - return true; -} - -bool SkeletonModification2DTwoBoneIK::_get(const StringName &p_path, Variant &r_ret) const { - String path = p_path; - - if (path == "joint_one_bone_idx") { - r_ret = get_joint_one_bone_idx(); - } else if (path == "joint_one_bone2d_node") { - r_ret = get_joint_one_bone2d_node(); - } else if (path == "joint_two_bone_idx") { - r_ret = get_joint_two_bone_idx(); - } else if (path == "joint_two_bone2d_node") { - r_ret = get_joint_two_bone2d_node(); - } - -#ifdef TOOLS_ENABLED - if (path.begins_with("editor/draw_gizmo")) { - r_ret = get_editor_draw_gizmo(); - } else if (path.begins_with("editor/draw_min_max")) { - r_ret = get_editor_draw_min_max(); - } -#endif // TOOLS_ENABLED - - return true; -} - -void SkeletonModification2DTwoBoneIK::_get_property_list(List *p_list) const { - p_list->push_back(PropertyInfo(Variant::INT, "joint_one_bone_idx", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - p_list->push_back(PropertyInfo(Variant::NODE_PATH, "joint_one_bone2d_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Bone2D", PROPERTY_USAGE_DEFAULT)); - - p_list->push_back(PropertyInfo(Variant::INT, "joint_two_bone_idx", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - p_list->push_back(PropertyInfo(Variant::NODE_PATH, "joint_two_bone2d_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Bone2D", PROPERTY_USAGE_DEFAULT)); - -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - p_list->push_back(PropertyInfo(Variant::BOOL, "editor/draw_gizmo", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - p_list->push_back(PropertyInfo(Variant::BOOL, "editor/draw_min_max", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - } -#endif // TOOLS_ENABLED -} - -void SkeletonModification2DTwoBoneIK::_draw_editor_gizmo() { - if (!enabled || !is_setup) { +void SkeletonModification2DTwoBoneIK::draw_editor_gizmo() { + Skeleton2D *skeleton = get_skeleton(); + if (!skeleton) { return; } - - Bone2D *operation_bone_one = skeleton->get_bone(joint_one_bone_idx); + Bone2D *operation_bone_one = _cache_bone(joint_one_bone_node_cache, joint_one_bone_node); if (!operation_bone_one) { return; } @@ -126,118 +63,30 @@ void SkeletonModification2DTwoBoneIK::_draw_editor_gizmo() { skeleton->draw_line(Vector2(0, 0), Vector2(Math::cos(angle), sin(angle)) * (operation_bone_one->get_length() * 0.5), bone_ik_color, 2.0); } -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - if (editor_draw_min_max) { - if (target_maximum_distance != 0.0 || target_minimum_distance != 0.0) { - Vector2 target_direction = Vector2(0, 1); - if (target_node_cache.is_valid()) { - skeleton->draw_set_transform(Vector2(0, 0), 0.0); - Node2D *target = Object::cast_to(ObjectDB::get_instance(target_node_cache)); - target_direction = operation_bone_one->get_global_position().direction_to(target->get_global_position()); - } - - skeleton->draw_circle(target_direction * target_minimum_distance, 8, bone_ik_color); - skeleton->draw_circle(target_direction * target_maximum_distance, 8, bone_ik_color); - skeleton->draw_line(target_direction * target_minimum_distance, target_direction * target_maximum_distance, bone_ik_color, 2.0); - } + if (target_maximum_distance != 0.0 || target_minimum_distance != 0.0) { + Vector2 target_direction = Vector2(0, 1); + _cache_node(target_node_cache, target_node); + Node2D *target = Object::cast_to((Object *)target_node_cache); + if (target) { + skeleton->draw_set_transform(Vector2(0, 0), 0.0); + target_direction = operation_bone_one->get_global_position().direction_to(target->get_global_position()); } - } -#endif // TOOLS_ENABLED -} -void SkeletonModification2DTwoBoneIK::update_target_cache() { - if (!is_setup) { - ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!"); - return; - } - - target_node_cache = ObjectID(); - if (skeleton) { - if (skeleton->is_inside_tree()) { - if (skeleton->has_node(target_node)) { - Node *node = skeleton->get_node(target_node); - ERR_FAIL_COND_MSG(!node || skeleton == node, - "Cannot update target cache: node is this modification's skeleton or cannot be found!"); - ERR_FAIL_COND_MSG(!node->is_inside_tree(), - "Cannot update target cache: node is not in the scene tree!"); - target_node_cache = node->get_instance_id(); - } - } - } -} - -void SkeletonModification2DTwoBoneIK::update_joint_one_bone2d_cache() { - if (!is_setup) { - ERR_PRINT_ONCE("Cannot update joint one Bone2D cache: modification is not properly setup!"); - return; - } - - joint_one_bone2d_node_cache = ObjectID(); - if (skeleton) { - if (skeleton->is_inside_tree()) { - if (skeleton->has_node(joint_one_bone2d_node)) { - Node *node = skeleton->get_node(joint_one_bone2d_node); - ERR_FAIL_COND_MSG(!node || skeleton == node, - "Cannot update update joint one Bone2D cache: node is this modification's skeleton or cannot be found!"); - ERR_FAIL_COND_MSG(!node->is_inside_tree(), - "Cannot update update joint one Bone2D cache: node is not in the scene tree!"); - joint_one_bone2d_node_cache = node->get_instance_id(); - - Bone2D *bone = Object::cast_to(node); - if (bone) { - joint_one_bone_idx = bone->get_index_in_skeleton(); - } else { - ERR_FAIL_MSG("update joint one Bone2D cache: Nodepath to Bone2D is not a Bone2D node!"); - } - } - } - } -} - -void SkeletonModification2DTwoBoneIK::update_joint_two_bone2d_cache() { - if (!is_setup) { - ERR_PRINT_ONCE("Cannot update joint two Bone2D cache: modification is not properly setup!"); - return; - } - - joint_two_bone2d_node_cache = ObjectID(); - if (skeleton) { - if (skeleton->is_inside_tree()) { - if (skeleton->has_node(joint_two_bone2d_node)) { - Node *node = skeleton->get_node(joint_two_bone2d_node); - ERR_FAIL_COND_MSG(!node || skeleton == node, - "Cannot update update joint two Bone2D cache: node is this modification's skeleton or cannot be found!"); - ERR_FAIL_COND_MSG(!node->is_inside_tree(), - "Cannot update update joint two Bone2D cache: node is not in scene tree!"); - joint_two_bone2d_node_cache = node->get_instance_id(); - - Bone2D *bone = Object::cast_to(node); - if (bone) { - joint_two_bone_idx = bone->get_index_in_skeleton(); - } else { - ERR_FAIL_MSG("update joint two Bone2D cache: Nodepath to Bone2D is not a Bone2D node!"); - } - } - } + skeleton->draw_circle(target_direction * target_minimum_distance, 8, bone_ik_color); + skeleton->draw_circle(target_direction * target_maximum_distance, 8, bone_ik_color); + skeleton->draw_line(target_direction * target_minimum_distance, target_direction * target_maximum_distance, bone_ik_color, 2.0); } } void SkeletonModification2DTwoBoneIK::set_target_node(const NodePath &p_target_node) { target_node = p_target_node; - update_target_cache(); + target_node_cache = Variant(); } NodePath SkeletonModification2DTwoBoneIK::get_target_node() const { return target_node; } -void SkeletonModification2DTwoBoneIK::set_joint_one_bone2d_node(const NodePath &p_target_node) { - joint_one_bone2d_node = p_target_node; - update_joint_one_bone2d_cache(); - notify_property_list_changed(); -} - void SkeletonModification2DTwoBoneIK::set_target_minimum_distance(float p_distance) { ERR_FAIL_COND_MSG(p_distance < 0, "Target minimum distance cannot be less than zero!"); target_minimum_distance = p_distance; @@ -264,80 +113,24 @@ bool SkeletonModification2DTwoBoneIK::get_flip_bend_direction() const { return flip_bend_direction; } -NodePath SkeletonModification2DTwoBoneIK::get_joint_one_bone2d_node() const { - return joint_one_bone2d_node; +void SkeletonModification2DTwoBoneIK::set_joint_one_bone_node(const NodePath &p_target_node) { + joint_one_bone_node = p_target_node; + joint_one_bone_node_cache = Variant(); } -void SkeletonModification2DTwoBoneIK::set_joint_two_bone2d_node(const NodePath &p_target_node) { - joint_two_bone2d_node = p_target_node; - update_joint_two_bone2d_cache(); - notify_property_list_changed(); +NodePath SkeletonModification2DTwoBoneIK::get_joint_one_bone_node() const { + return joint_one_bone_node; } -NodePath SkeletonModification2DTwoBoneIK::get_joint_two_bone2d_node() const { - return joint_two_bone2d_node; -} - -void SkeletonModification2DTwoBoneIK::set_joint_one_bone_idx(int p_bone_idx) { - ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!"); - - if (is_setup) { - if (skeleton) { - ERR_FAIL_INDEX_MSG(p_bone_idx, skeleton->get_bone_count(), "Passed-in Bone index is out of range!"); - joint_one_bone_idx = p_bone_idx; - joint_one_bone2d_node_cache = skeleton->get_bone(p_bone_idx)->get_instance_id(); - joint_one_bone2d_node = skeleton->get_path_to(skeleton->get_bone(p_bone_idx)); - } else { - WARN_PRINT("TwoBoneIK: Cannot verify the joint bone index for joint one..."); - joint_one_bone_idx = p_bone_idx; - } - } else { - WARN_PRINT("TwoBoneIK: Cannot verify the joint bone index for joint one..."); - joint_one_bone_idx = p_bone_idx; - } - - notify_property_list_changed(); +void SkeletonModification2DTwoBoneIK::set_joint_two_bone_node(const NodePath &p_target_node) { + joint_two_bone_node = p_target_node; + joint_two_bone_node_cache = Variant(); } -int SkeletonModification2DTwoBoneIK::get_joint_one_bone_idx() const { - return joint_one_bone_idx; +NodePath SkeletonModification2DTwoBoneIK::get_joint_two_bone_node() const { + return joint_two_bone_node; } -void SkeletonModification2DTwoBoneIK::set_joint_two_bone_idx(int p_bone_idx) { - ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!"); - - if (is_setup) { - if (skeleton) { - ERR_FAIL_INDEX_MSG(p_bone_idx, skeleton->get_bone_count(), "Passed-in Bone index is out of range!"); - joint_two_bone_idx = p_bone_idx; - joint_two_bone2d_node_cache = skeleton->get_bone(p_bone_idx)->get_instance_id(); - joint_two_bone2d_node = skeleton->get_path_to(skeleton->get_bone(p_bone_idx)); - } else { - WARN_PRINT("TwoBoneIK: Cannot verify the joint bone index for joint two..."); - joint_two_bone_idx = p_bone_idx; - } - } else { - WARN_PRINT("TwoBoneIK: Cannot verify the joint bone index for joint two..."); - joint_two_bone_idx = p_bone_idx; - } - - notify_property_list_changed(); -} - -int SkeletonModification2DTwoBoneIK::get_joint_two_bone_idx() const { - return joint_two_bone_idx; -} - -#ifdef TOOLS_ENABLED -void SkeletonModification2DTwoBoneIK::set_editor_draw_min_max(bool p_draw) { - editor_draw_min_max = p_draw; -} - -bool SkeletonModification2DTwoBoneIK::get_editor_draw_min_max() const { - return editor_draw_min_max; -} -#endif // TOOLS_ENABLED - void SkeletonModification2DTwoBoneIK::_bind_methods() { ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification2DTwoBoneIK::set_target_node); ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification2DTwoBoneIK::get_target_node); @@ -349,139 +142,92 @@ void SkeletonModification2DTwoBoneIK::_bind_methods() { ClassDB::bind_method(D_METHOD("set_flip_bend_direction", "flip_direction"), &SkeletonModification2DTwoBoneIK::set_flip_bend_direction); ClassDB::bind_method(D_METHOD("get_flip_bend_direction"), &SkeletonModification2DTwoBoneIK::get_flip_bend_direction); - ClassDB::bind_method(D_METHOD("set_joint_one_bone2d_node", "bone2d_node"), &SkeletonModification2DTwoBoneIK::set_joint_one_bone2d_node); - ClassDB::bind_method(D_METHOD("get_joint_one_bone2d_node"), &SkeletonModification2DTwoBoneIK::get_joint_one_bone2d_node); - ClassDB::bind_method(D_METHOD("set_joint_one_bone_idx", "bone_idx"), &SkeletonModification2DTwoBoneIK::set_joint_one_bone_idx); - ClassDB::bind_method(D_METHOD("get_joint_one_bone_idx"), &SkeletonModification2DTwoBoneIK::get_joint_one_bone_idx); + ClassDB::bind_method(D_METHOD("set_joint_one_bone_node", "bone_node"), &SkeletonModification2DTwoBoneIK::set_joint_one_bone_node); + ClassDB::bind_method(D_METHOD("get_joint_one_bone_node"), &SkeletonModification2DTwoBoneIK::get_joint_one_bone_node); + ClassDB::bind_method(D_METHOD("set_joint_two_bone_node", "bone_node"), &SkeletonModification2DTwoBoneIK::set_joint_two_bone_node); + ClassDB::bind_method(D_METHOD("get_joint_two_bone_node"), &SkeletonModification2DTwoBoneIK::get_joint_two_bone_node); - ClassDB::bind_method(D_METHOD("set_joint_two_bone2d_node", "bone2d_node"), &SkeletonModification2DTwoBoneIK::set_joint_two_bone2d_node); - ClassDB::bind_method(D_METHOD("get_joint_two_bone2d_node"), &SkeletonModification2DTwoBoneIK::get_joint_two_bone2d_node); - ClassDB::bind_method(D_METHOD("set_joint_two_bone_idx", "bone_idx"), &SkeletonModification2DTwoBoneIK::set_joint_two_bone_idx); - ClassDB::bind_method(D_METHOD("get_joint_two_bone_idx"), &SkeletonModification2DTwoBoneIK::get_joint_two_bone_idx); - - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node2D"), "set_target_node", "get_target_node"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "CanvasItem"), "set_target_node", "get_target_node"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "target_minimum_distance", PROPERTY_HINT_RANGE, "0,100000000,0.01,suffix:m"), "set_target_minimum_distance", "get_target_minimum_distance"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "target_maximum_distance", PROPERTY_HINT_NONE, "0,100000000,0.01,suffix:m"), "set_target_maximum_distance", "get_target_maximum_distance"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_bend_direction", PROPERTY_HINT_NONE, ""), "set_flip_bend_direction", "get_flip_bend_direction"); - ADD_GROUP("", ""); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "joint_one_bone_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Bone2D", PROPERTY_USAGE_DEFAULT), "set_joint_one_bone_node", "get_joint_one_bone_node"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "joint_two_bone_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Bone2D", PROPERTY_USAGE_DEFAULT), "set_joint_two_bone_node", "get_joint_two_bone_node"); } SkeletonModification2DTwoBoneIK::SkeletonModification2DTwoBoneIK() { - is_setup = false; - enabled = true; - editor_draw_gizmo = true; } SkeletonModification2DTwoBoneIK::~SkeletonModification2DTwoBoneIK() { } -void SkeletonModification2DTwoBoneIK::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - set_process_internal(false); - set_physics_process_internal(false); - if (get_execution_mode() == 0) { - set_process_internal(true); - } else if (get_execution_mode() == 1) { - set_physics_process_internal(true); - } - skeleton = cast_to(get_node_or_null(get_skeleton_path())); - is_setup = true; - update_target_cache(); - update_joint_one_bone2d_cache(); - update_joint_two_bone2d_cache(); - } break; - case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: - [[fallthrough]]; - case NOTIFICATION_INTERNAL_PROCESS: { - if (!is_setup) { - ERR_PRINT_ONCE("Modification is not setup."); - } - if (!skeleton) { - ERR_PRINT_ONCE("Modification does not have a skeleton path."); - } - if (!enabled) { - return; - } - if (target_node_cache.is_null()) { - WARN_PRINT_ONCE("Target cache is out of date. Attempting to update..."); - update_target_cache(); - return; - } - - if (joint_one_bone2d_node_cache.is_null() && !joint_one_bone2d_node.is_empty()) { - WARN_PRINT_ONCE("Joint one Bone2D node cache is out of date. Attempting to update..."); - update_joint_one_bone2d_cache(); - } - if (joint_two_bone2d_node_cache.is_null() && !joint_two_bone2d_node.is_empty()) { - WARN_PRINT_ONCE("Joint two Bone2D node cache is out of date. Attempting to update..."); - update_joint_two_bone2d_cache(); - } - - Node2D *target = Object::cast_to(ObjectDB::get_instance(target_node_cache)); - if (!target || !target->is_inside_tree()) { - ERR_PRINT_ONCE("Target node is not in the scene tree. Cannot execute modification!"); - return; - } - - Bone2D *joint_one_bone = skeleton->get_bone(joint_one_bone_idx); - if (joint_one_bone == nullptr) { - ERR_PRINT_ONCE("Joint one bone_idx does not point to a valid bone! Cannot execute modification!"); - return; - } - - Bone2D *joint_two_bone = skeleton->get_bone(joint_two_bone_idx); - if (joint_two_bone == nullptr) { - ERR_PRINT_ONCE("Joint two bone_idx does not point to a valid bone! Cannot execute modification!"); - return; - } - - // Adopted from the links below: - // http://theorangeduck.com/page/simple-two-joint - // https://www.alanzucconi.com/2018/05/02/ik-2d-2/ - // With modifications by TwistedTwigleg - Vector2 target_difference = target->get_global_position() - joint_one_bone->get_global_position(); - float joint_one_to_target = target_difference.length(); - float angle_atan = target_difference.angle(); - - float bone_one_length = joint_one_bone->get_length() * MIN(joint_one_bone->get_global_scale().x, joint_one_bone->get_global_scale().y); - float bone_two_length = joint_two_bone->get_length() * MIN(joint_two_bone->get_global_scale().x, joint_two_bone->get_global_scale().y); - bool override_angles_due_to_out_of_range = false; - - if (joint_one_to_target < target_minimum_distance) { - joint_one_to_target = target_minimum_distance; - } - if (joint_one_to_target > target_maximum_distance && target_maximum_distance > 0.0) { - joint_one_to_target = target_maximum_distance; - } - - if (bone_one_length + bone_two_length < joint_one_to_target) { - override_angles_due_to_out_of_range = true; - } - - if (!override_angles_due_to_out_of_range) { - float angle_0 = Math::acos(((joint_one_to_target * joint_one_to_target) + (bone_one_length * bone_one_length) - (bone_two_length * bone_two_length)) / (2.0 * joint_one_to_target * bone_one_length)); - float angle_1 = Math::acos(((bone_two_length * bone_two_length) + (bone_one_length * bone_one_length) - (joint_one_to_target * joint_one_to_target)) / (2.0 * bone_two_length * bone_one_length)); - - if (flip_bend_direction) { - angle_0 = -angle_0; - angle_1 = -angle_1; - } - - if (isnan(angle_0) || isnan(angle_1)) { - // We cannot solve for this angle! Do nothing to avoid setting the rotation (and scale) to NaN. - } else { - joint_one_bone->set_global_rotation(angle_atan - angle_0 - joint_one_bone->get_bone_angle()); - joint_two_bone->set_rotation(-Math_PI - angle_1 - joint_two_bone->get_bone_angle() + joint_one_bone->get_bone_angle()); - } - } else { - joint_one_bone->set_global_rotation(angle_atan - joint_one_bone->get_bone_angle()); - joint_two_bone->set_global_rotation(angle_atan - joint_two_bone->get_bone_angle()); - } - - skeleton->set_bone_local_pose_override(joint_one_bone_idx, joint_one_bone->get_transform(), 1.0, true); - skeleton->set_bone_local_pose_override(joint_two_bone_idx, joint_two_bone->get_transform(), 1.0, true); - - } break; +void SkeletonModification2DTwoBoneIK::execute(real_t p_delta) { + SkeletonModification2D::execute(p_delta); + + Bone2D *joint_one_bone = _cache_bone(joint_one_bone_node_cache, joint_one_bone_node); + Bone2D *joint_two_bone = _cache_bone(joint_two_bone_node_cache, joint_two_bone_node); + if (!_cache_node(target_node_cache, target_node) || + !joint_one_bone || !joint_two_bone) { + WARN_PRINT_ONCE("2DTwoBoneIK unable to get nodes"); + return; + } + Vector2 target_position = get_target_position(target_node_cache); + + // Adopted from the links below: + // http://theorangeduck.com/page/simple-two-joint + // https://www.alanzucconi.com/2018/05/02/ik-2d-2/ + // With modifications by TwistedTwigleg + Vector2 target_difference = target_position - joint_one_bone->get_global_position(); + float joint_one_to_target = target_difference.length(); + float angle_atan = target_difference.angle(); + + float bone_one_length = joint_one_bone->get_length() * MIN(joint_one_bone->get_global_scale().x, joint_one_bone->get_global_scale().y); + float bone_two_length = joint_two_bone->get_length() * MIN(joint_two_bone->get_global_scale().x, joint_two_bone->get_global_scale().y); + bool override_angles_due_to_out_of_range = false; + + if (joint_one_to_target < target_minimum_distance) { + joint_one_to_target = target_minimum_distance; + } + if (joint_one_to_target > target_maximum_distance && target_maximum_distance > 0.0) { + joint_one_to_target = target_maximum_distance; + } + + if (bone_one_length + bone_two_length < joint_one_to_target) { + override_angles_due_to_out_of_range = true; + } + + if (!override_angles_due_to_out_of_range) { + float angle_0 = Math::acos(((joint_one_to_target * joint_one_to_target) + (bone_one_length * bone_one_length) - (bone_two_length * bone_two_length)) / (2.0 * joint_one_to_target * bone_one_length)); + float angle_1 = Math::acos(((bone_two_length * bone_two_length) + (bone_one_length * bone_one_length) - (joint_one_to_target * joint_one_to_target)) / (2.0 * bone_two_length * bone_one_length)); + + if (flip_bend_direction) { + angle_0 = -angle_0; + angle_1 = -angle_1; + } + + if (isfinite(angle_0) && isfinite(angle_1)) { + joint_one_bone->set_global_rotation(angle_atan - angle_0 - joint_one_bone->get_bone_angle()); + joint_two_bone->set_rotation(-Math_PI - angle_1 - joint_two_bone->get_bone_angle() + joint_one_bone->get_bone_angle()); + } + } else { + joint_one_bone->set_global_rotation(angle_atan - joint_one_bone->get_bone_angle()); + joint_two_bone->set_global_rotation(angle_atan - joint_two_bone->get_bone_angle()); + } +} + +TypedArray SkeletonModification2DTwoBoneIK::get_configuration_warnings() const { + TypedArray ret = SkeletonModification2D::get_configuration_warnings(); + if (!get_skeleton()) { + return ret; + } + if (!_cache_node(target_node_cache, target_node)) { + ret.append(vformat("Target node %s was not found.", (String)target_node)); + } + if (!_cache_bone(joint_one_bone_node_cache, joint_one_bone_node)) { + ret.append(vformat("Joint one bone %s was not found.", joint_one_bone_node)); + } + if (!_cache_bone(joint_two_bone_node_cache, joint_two_bone_node)) { + ret.append(vformat("Joint two bone %s was not found.", joint_two_bone_node)); } + return ret; } diff --git a/scene/2d/skeleton_modification_2d_twoboneik.h b/scene/2d/skeleton_modification_2d_twoboneik.h index 5088c07e0ce8..662fdc46fd93 100644 --- a/scene/2d/skeleton_modification_2d_twoboneik.h +++ b/scene/2d/skeleton_modification_2d_twoboneik.h @@ -43,37 +43,24 @@ class SkeletonModification2DTwoBoneIK : public SkeletonModification2D { private: NodePath target_node; - ObjectID target_node_cache; + mutable Variant target_node_cache; float target_minimum_distance = 0; float target_maximum_distance = 0; bool flip_bend_direction = false; - NodePath joint_one_bone2d_node; - ObjectID joint_one_bone2d_node_cache; - int joint_one_bone_idx = -1; + NodePath joint_one_bone_node; + mutable Variant joint_one_bone_node_cache; - NodePath joint_two_bone2d_node; - ObjectID joint_two_bone2d_node_cache; - int joint_two_bone_idx = -1; - -#ifdef TOOLS_ENABLED - bool editor_draw_min_max = false; -#endif // TOOLS_ENABLED - - void update_target_cache(); - void update_joint_one_bone2d_cache(); - void update_joint_two_bone2d_cache(); + NodePath joint_two_bone_node; + mutable Variant joint_two_bone_node_cache; protected: static void _bind_methods(); - bool _get(const StringName &p_path, Variant &r_ret) const; - bool _set(const StringName &p_path, const Variant &p_value); - void _get_property_list(List *p_list) const; + void execute(real_t delta) override; + TypedArray get_configuration_warnings() const override; + void draw_editor_gizmo() override; public: - void _notification(int p_what); - void _draw_editor_gizmo() override; - void set_target_node(const NodePath &p_target_node); NodePath get_target_node() const; @@ -84,20 +71,11 @@ class SkeletonModification2DTwoBoneIK : public SkeletonModification2D { void set_flip_bend_direction(bool p_flip_direction); bool get_flip_bend_direction() const; - void set_joint_one_bone2d_node(const NodePath &p_node); - NodePath get_joint_one_bone2d_node() const; - void set_joint_one_bone_idx(int p_bone_idx); - int get_joint_one_bone_idx() const; - - void set_joint_two_bone2d_node(const NodePath &p_node); - NodePath get_joint_two_bone2d_node() const; - void set_joint_two_bone_idx(int p_bone_idx); - int get_joint_two_bone_idx() const; + void set_joint_one_bone_node(const NodePath &p_node); + NodePath get_joint_one_bone_node() const; -#ifdef TOOLS_ENABLED - void set_editor_draw_min_max(bool p_draw); - bool get_editor_draw_min_max() const; -#endif // TOOLS_ENABLED + void set_joint_two_bone_node(const NodePath &p_node); + NodePath get_joint_two_bone_node() const; SkeletonModification2DTwoBoneIK(); ~SkeletonModification2DTwoBoneIK(); diff --git a/scene/3d/bone_attachment_3d.cpp b/scene/3d/bone_attachment_3d.cpp index b3ff6497a78f..2ad959cd14f2 100644 --- a/scene/3d/bone_attachment_3d.cpp +++ b/scene/3d/bone_attachment_3d.cpp @@ -61,11 +61,7 @@ void BoneAttachment3D::_validate_property(PropertyInfo &p_property) const { } bool BoneAttachment3D::_set(const StringName &p_path, const Variant &p_value) { - if (p_path == SNAME("override_pose")) { - set_override_pose(p_value); - } else if (p_path == SNAME("override_mode")) { - set_override_mode(p_value); - } else if (p_path == SNAME("use_external_skeleton")) { + if (p_path == SNAME("use_external_skeleton")) { set_use_external_skeleton(p_value); } else if (p_path == SNAME("external_skeleton")) { set_external_skeleton(p_value); @@ -75,11 +71,7 @@ bool BoneAttachment3D::_set(const StringName &p_path, const Variant &p_value) { } bool BoneAttachment3D::_get(const StringName &p_path, Variant &r_ret) const { - if (p_path == SNAME("override_pose")) { - r_ret = get_override_pose(); - } else if (p_path == SNAME("override_mode")) { - r_ret = get_override_mode(); - } else if (p_path == SNAME("use_external_skeleton")) { + if (p_path == SNAME("use_external_skeleton")) { r_ret = get_use_external_skeleton(); } else if (p_path == SNAME("external_skeleton")) { r_ret = get_external_skeleton(); @@ -89,11 +81,6 @@ bool BoneAttachment3D::_get(const StringName &p_path, Variant &r_ret) const { } void BoneAttachment3D::_get_property_list(List *p_list) const { - p_list->push_back(PropertyInfo(Variant::BOOL, "override_pose", PROPERTY_HINT_NONE, "")); - if (override_pose) { - p_list->push_back(PropertyInfo(Variant::INT, "override_mode", PROPERTY_HINT_ENUM, "Global Pose Override, Local Pose Override, Custom Pose")); - } - p_list->push_back(PropertyInfo(Variant::BOOL, "use_external_skeleton", PROPERTY_HINT_NONE, "")); if (use_external_skeleton) { p_list->push_back(PropertyInfo(Variant::NODE_PATH, "external_skeleton", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Skeleton3D")); @@ -208,14 +195,10 @@ void BoneAttachment3D::_transform_changed() { Transform3D our_trans = get_transform(); if (use_external_skeleton) { - our_trans = sk->world_transform_to_global_pose(get_global_transform()); + our_trans = sk->get_global_transform().affine_inverse() * get_global_transform(); } - if (override_mode == OVERRIDE_MODES::MODE_GLOBAL_POSE) { - sk->set_bone_global_pose_override(bone_idx, our_trans, 1.0, true); - } else if (override_mode == OVERRIDE_MODES::MODE_LOCAL_POSE) { - sk->set_bone_local_pose_override(bone_idx, sk->global_pose_to_local_pose(bone_idx, our_trans), 1.0, true); - } + sk->set_bone_global_pose_override(bone_idx, our_trans, 1.0, true); } } @@ -267,11 +250,7 @@ void BoneAttachment3D::set_override_pose(bool p_override) { if (!override_pose) { Skeleton3D *sk = _get_skeleton3d(); if (sk) { - if (override_mode == OVERRIDE_MODES::MODE_GLOBAL_POSE) { - sk->set_bone_global_pose_override(bone_idx, Transform3D(), 0.0, false); - } else if (override_mode == OVERRIDE_MODES::MODE_LOCAL_POSE) { - sk->set_bone_local_pose_override(bone_idx, Transform3D(), 0.0, false); - } + sk->set_bone_global_pose_override(bone_idx, Transform3D(), 0.0, false); } _transform_changed(); } @@ -282,27 +261,6 @@ bool BoneAttachment3D::get_override_pose() const { return override_pose; } -void BoneAttachment3D::set_override_mode(int p_mode) { - if (override_pose) { - Skeleton3D *sk = _get_skeleton3d(); - if (sk) { - if (override_mode == OVERRIDE_MODES::MODE_GLOBAL_POSE) { - sk->set_bone_global_pose_override(bone_idx, Transform3D(), 0.0, false); - } else if (override_mode == OVERRIDE_MODES::MODE_LOCAL_POSE) { - sk->set_bone_local_pose_override(bone_idx, Transform3D(), 0.0, false); - } - } - override_mode = p_mode; - _transform_changed(); - return; - } - override_mode = p_mode; -} - -int BoneAttachment3D::get_override_mode() const { - return override_mode; -} - void BoneAttachment3D::set_use_external_skeleton(bool p_use_external) { use_external_skeleton = p_use_external; @@ -361,7 +319,7 @@ void BoneAttachment3D::on_bone_pose_update(int p_bone_index) { if (sk) { if (!override_pose) { if (use_external_skeleton) { - set_global_transform(sk->global_pose_to_world_transform(sk->get_bone_global_pose(bone_idx))); + set_global_transform(sk->get_global_transform() * sk->get_bone_global_pose(bone_idx)); } else { set_transform(sk->get_bone_global_pose(bone_idx)); } @@ -407,8 +365,6 @@ void BoneAttachment3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_override_pose", "override_pose"), &BoneAttachment3D::set_override_pose); ClassDB::bind_method(D_METHOD("get_override_pose"), &BoneAttachment3D::get_override_pose); - ClassDB::bind_method(D_METHOD("set_override_mode", "override_mode"), &BoneAttachment3D::set_override_mode); - ClassDB::bind_method(D_METHOD("get_override_mode"), &BoneAttachment3D::get_override_mode); ClassDB::bind_method(D_METHOD("set_use_external_skeleton", "use_external_skeleton"), &BoneAttachment3D::set_use_external_skeleton); ClassDB::bind_method(D_METHOD("get_use_external_skeleton"), &BoneAttachment3D::get_use_external_skeleton); @@ -420,4 +376,5 @@ void BoneAttachment3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "bone_name"), "set_bone_name", "get_bone_name"); ADD_PROPERTY(PropertyInfo(Variant::INT, "bone_idx"), "set_bone_idx", "get_bone_idx"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "override_pose"), "set_override_pose", "get_override_pose"); } diff --git a/scene/3d/bone_attachment_3d.h b/scene/3d/bone_attachment_3d.h index f85053e614a9..93cc26283ea2 100644 --- a/scene/3d/bone_attachment_3d.h +++ b/scene/3d/bone_attachment_3d.h @@ -44,14 +44,8 @@ class BoneAttachment3D : public Node3D { int bone_idx = -1; bool override_pose = false; - int override_mode = 0; bool _override_dirty = false; - enum OVERRIDE_MODES { - MODE_GLOBAL_POSE, - MODE_LOCAL_POSE, - }; - bool use_external_skeleton = false; NodePath external_skeleton_node; ObjectID external_skeleton_node_cache; @@ -86,8 +80,6 @@ class BoneAttachment3D : public Node3D { void set_override_pose(bool p_override); bool get_override_pose() const; - void set_override_mode(int p_mode); - int get_override_mode() const; void set_use_external_skeleton(bool p_external_skeleton); bool get_use_external_skeleton() const; diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp index 437df5931a9e..8a6a572c3967 100644 --- a/scene/3d/skeleton_3d.cpp +++ b/scene/3d/skeleton_3d.cpp @@ -236,8 +236,10 @@ void Skeleton3D::_notification(int p_what) { dirty = false; // Update bone transforms. - force_update_all_bone_transforms(); - + _update_process_order(); + for (int i = 0; i < parentless_bones.size(); i++) { + force_update_bone_children_transforms(parentless_bones[i]); + } // Update skins. for (SkinReference *E : skin_bindings) { const Skin *skin = E->skin.operator->(); @@ -361,99 +363,6 @@ Transform3D Skeleton3D::get_bone_global_pose_no_override(int p_bone) const { return bones[p_bone].pose_global_no_override; } -void Skeleton3D::clear_bones_local_pose_override() { - for (int i = 0; i < bones.size(); i += 1) { - bones.write[i].local_pose_override_amount = 0; - } - _make_dirty(); -} - -void Skeleton3D::set_bone_local_pose_override(int p_bone, const Transform3D &p_pose, real_t p_amount, bool p_persistent) { - const int bone_size = bones.size(); - ERR_FAIL_INDEX(p_bone, bone_size); - bones.write[p_bone].local_pose_override_amount = p_amount; - bones.write[p_bone].local_pose_override = p_pose; - bones.write[p_bone].local_pose_override_reset = !p_persistent; - _make_dirty(); -} - -Transform3D Skeleton3D::get_bone_local_pose_override(int p_bone) const { - const int bone_size = bones.size(); - ERR_FAIL_INDEX_V(p_bone, bone_size, Transform3D()); - return bones[p_bone].local_pose_override; -} - -void Skeleton3D::update_bone_rest_forward_vector(int p_bone, bool p_force_update) { - const int bone_size = bones.size(); - ERR_FAIL_INDEX(p_bone, bone_size); - - if (bones[p_bone].rest_bone_forward_vector.length_squared() > 0 && p_force_update == false) { - update_bone_rest_forward_axis(p_bone, p_force_update); - } - - // If it is a child/leaf bone... - if (get_bone_parent(p_bone) > 0) { - bones.write[p_bone].rest_bone_forward_vector = bones[p_bone].rest.origin.normalized(); - } else { - // If it has children... - Vector child_bones = get_bone_children(p_bone); - if (child_bones.size() > 0) { - Vector3 combined_child_dir = Vector3(0, 0, 0); - for (int i = 0; i < child_bones.size(); i++) { - combined_child_dir += bones[child_bones[i]].rest.origin.normalized(); - } - combined_child_dir = combined_child_dir / child_bones.size(); - bones.write[p_bone].rest_bone_forward_vector = combined_child_dir.normalized(); - } else { - WARN_PRINT_ONCE("Cannot calculate forward direction for bone " + itos(p_bone)); - WARN_PRINT_ONCE("Assuming direction of (0, 1, 0) for bone"); - bones.write[p_bone].rest_bone_forward_vector = Vector3(0, 1, 0); - } - } - update_bone_rest_forward_axis(p_bone, p_force_update); -} - -void Skeleton3D::update_bone_rest_forward_axis(int p_bone, bool p_force_update) { - const int bone_size = bones.size(); - ERR_FAIL_INDEX(p_bone, bone_size); - if (bones[p_bone].rest_bone_forward_axis > -1 && p_force_update == false) { - return; - } - - Vector3 forward_axis_absolute = bones[p_bone].rest_bone_forward_vector.abs(); - if (forward_axis_absolute.x > forward_axis_absolute.y && forward_axis_absolute.x > forward_axis_absolute.z) { - if (bones[p_bone].rest_bone_forward_vector.x > 0) { - bones.write[p_bone].rest_bone_forward_axis = BONE_AXIS_X_FORWARD; - } else { - bones.write[p_bone].rest_bone_forward_axis = BONE_AXIS_NEGATIVE_X_FORWARD; - } - } else if (forward_axis_absolute.y > forward_axis_absolute.x && forward_axis_absolute.y > forward_axis_absolute.z) { - if (bones[p_bone].rest_bone_forward_vector.y > 0) { - bones.write[p_bone].rest_bone_forward_axis = BONE_AXIS_Y_FORWARD; - } else { - bones.write[p_bone].rest_bone_forward_axis = BONE_AXIS_NEGATIVE_Y_FORWARD; - } - } else { - if (bones[p_bone].rest_bone_forward_vector.z > 0) { - bones.write[p_bone].rest_bone_forward_axis = BONE_AXIS_Z_FORWARD; - } else { - bones.write[p_bone].rest_bone_forward_axis = BONE_AXIS_NEGATIVE_Z_FORWARD; - } - } -} - -Vector3 Skeleton3D::get_bone_axis_forward_vector(int p_bone) { - const int bone_size = bones.size(); - ERR_FAIL_INDEX_V(p_bone, bone_size, Vector3(0, 0, 0)); - return bones[p_bone].rest_bone_forward_vector; -} - -int Skeleton3D::get_bone_axis_forward_enum(int p_bone) { - const int bone_size = bones.size(); - ERR_FAIL_INDEX_V(p_bone, bone_size, -1); - return bones[p_bone].rest_bone_forward_axis; -} - void Skeleton3D::set_motion_scale(float p_motion_scale) { if (p_motion_scale <= 0) { motion_scale = 1; @@ -469,6 +378,10 @@ float Skeleton3D::get_motion_scale() const { // Skeleton creation api +uint64_t Skeleton3D::get_version() const { + return version; +} + void Skeleton3D::add_bone(const String &p_name) { ERR_FAIL_COND(p_name.is_empty() || p_name.contains(":") || p_name.contains("/")); @@ -512,6 +425,7 @@ void Skeleton3D::set_bone_name(int p_bone, const String &p_name) { } bones.write[p_bone].name = p_name; + version++; } bool Skeleton3D::is_bone_parent_of(int p_bone, int p_parent_bone_id) const { @@ -985,14 +899,6 @@ void Skeleton3D::force_update_all_dirty_bones() { } } -void Skeleton3D::force_update_all_bone_transforms() { - _update_process_order(); - - for (int i = 0; i < parentless_bones.size(); i++) { - force_update_bone_children_transforms(parentless_bones[i]); - } -} - void Skeleton3D::force_update_bone_children_transforms(int p_bone_idx) { const int bone_size = bones.size(); ERR_FAIL_INDEX(p_bone_idx, bone_size); @@ -1032,23 +938,10 @@ void Skeleton3D::force_update_bone_children_transforms(int p_bone_idx) { b.global_rest = b.parent >= 0 ? bonesptr[b.parent].global_rest * b.rest : b.rest; } - if (b.local_pose_override_amount >= CMP_EPSILON) { - Transform3D override_local_pose; - if (b.parent >= 0) { - override_local_pose = bonesptr[b.parent].pose_global * b.local_pose_override; - } else { - override_local_pose = b.local_pose_override; - } - b.pose_global = b.pose_global.interpolate_with(override_local_pose, b.local_pose_override_amount); - } - if (b.global_pose_override_amount >= CMP_EPSILON) { b.pose_global = b.pose_global.interpolate_with(b.global_pose_override, b.global_pose_override_amount); } - if (b.local_pose_override_reset) { - b.local_pose_override_amount = 0.0; - } if (b.global_pose_override_reset) { b.global_pose_override_amount = 0.0; } @@ -1064,70 +957,12 @@ void Skeleton3D::force_update_bone_children_transforms(int p_bone_idx) { rest_dirty = false; } -// Helper functions - -Transform3D Skeleton3D::global_pose_to_world_transform(Transform3D p_global_pose) { - return get_global_transform() * p_global_pose; -} - -Transform3D Skeleton3D::world_transform_to_global_pose(Transform3D p_world_transform) { - return get_global_transform().affine_inverse() * p_world_transform; -} - -Transform3D Skeleton3D::global_pose_to_local_pose(int p_bone_idx, Transform3D p_global_pose) { - const int bone_size = bones.size(); - ERR_FAIL_INDEX_V(p_bone_idx, bone_size, Transform3D()); - if (bones[p_bone_idx].parent >= 0) { - int parent_bone_idx = bones[p_bone_idx].parent; - Transform3D conversion_transform = get_bone_global_pose(parent_bone_idx).affine_inverse(); - return conversion_transform * p_global_pose; - } else { - return p_global_pose; - } -} - -Transform3D Skeleton3D::local_pose_to_global_pose(int p_bone_idx, Transform3D p_local_pose) { - const int bone_size = bones.size(); - ERR_FAIL_INDEX_V(p_bone_idx, bone_size, Transform3D()); - if (bones[p_bone_idx].parent >= 0) { - int parent_bone_idx = bones[p_bone_idx].parent; - return bones[parent_bone_idx].pose_global * p_local_pose; - } else { - return p_local_pose; - } -} - -Basis Skeleton3D::global_pose_z_forward_to_bone_forward(int p_bone_idx, Basis p_basis) { - const int bone_size = bones.size(); - ERR_FAIL_INDEX_V(p_bone_idx, bone_size, Basis()); - Basis return_basis = p_basis; - - if (bones[p_bone_idx].rest_bone_forward_axis < 0) { - update_bone_rest_forward_vector(p_bone_idx, true); - } - - if (bones[p_bone_idx].rest_bone_forward_axis == BONE_AXIS_X_FORWARD) { - return_basis.rotate_local(Vector3(0, 1, 0), (Math_PI / 2.0)); - } else if (bones[p_bone_idx].rest_bone_forward_axis == BONE_AXIS_NEGATIVE_X_FORWARD) { - return_basis.rotate_local(Vector3(0, 1, 0), -(Math_PI / 2.0)); - } else if (bones[p_bone_idx].rest_bone_forward_axis == BONE_AXIS_Y_FORWARD) { - return_basis.rotate_local(Vector3(1, 0, 0), -(Math_PI / 2.0)); - } else if (bones[p_bone_idx].rest_bone_forward_axis == BONE_AXIS_NEGATIVE_Y_FORWARD) { - return_basis.rotate_local(Vector3(1, 0, 0), (Math_PI / 2.0)); - } else if (bones[p_bone_idx].rest_bone_forward_axis == BONE_AXIS_Z_FORWARD) { - // Do nothing! - } else if (bones[p_bone_idx].rest_bone_forward_axis == BONE_AXIS_NEGATIVE_Z_FORWARD) { - return_basis.rotate_local(Vector3(0, 0, 1), Math_PI); - } - - return return_basis; -} - void Skeleton3D::_bind_methods() { ClassDB::bind_method(D_METHOD("add_bone", "name"), &Skeleton3D::add_bone); ClassDB::bind_method(D_METHOD("find_bone", "name"), &Skeleton3D::find_bone); ClassDB::bind_method(D_METHOD("get_bone_name", "bone_idx"), &Skeleton3D::get_bone_name); ClassDB::bind_method(D_METHOD("set_bone_name", "bone_idx", "name"), &Skeleton3D::set_bone_name); + ClassDB::bind_method(D_METHOD("get_version"), &Skeleton3D::get_version); ClassDB::bind_method(D_METHOD("get_bone_parent", "bone_idx"), &Skeleton3D::get_bone_parent); ClassDB::bind_method(D_METHOD("set_bone_parent", "bone_idx", "parent_idx"), &Skeleton3D::set_bone_parent); @@ -1172,23 +1007,11 @@ void Skeleton3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_bone_global_pose", "bone_idx"), &Skeleton3D::get_bone_global_pose); ClassDB::bind_method(D_METHOD("get_bone_global_pose_no_override", "bone_idx"), &Skeleton3D::get_bone_global_pose_no_override); - ClassDB::bind_method(D_METHOD("clear_bones_local_pose_override"), &Skeleton3D::clear_bones_local_pose_override); - ClassDB::bind_method(D_METHOD("set_bone_local_pose_override", "bone_idx", "pose", "amount", "persistent"), &Skeleton3D::set_bone_local_pose_override, DEFVAL(false)); - ClassDB::bind_method(D_METHOD("get_bone_local_pose_override", "bone_idx"), &Skeleton3D::get_bone_local_pose_override); - - ClassDB::bind_method(D_METHOD("force_update_all_bone_transforms"), &Skeleton3D::force_update_all_bone_transforms); ClassDB::bind_method(D_METHOD("force_update_bone_child_transform", "bone_idx"), &Skeleton3D::force_update_bone_children_transforms); ClassDB::bind_method(D_METHOD("set_motion_scale", "motion_scale"), &Skeleton3D::set_motion_scale); ClassDB::bind_method(D_METHOD("get_motion_scale"), &Skeleton3D::get_motion_scale); - // Helper functions - ClassDB::bind_method(D_METHOD("global_pose_to_world_transform", "global_pose"), &Skeleton3D::global_pose_to_world_transform); - ClassDB::bind_method(D_METHOD("world_transform_to_global_pose", "world_transform"), &Skeleton3D::world_transform_to_global_pose); - ClassDB::bind_method(D_METHOD("global_pose_to_local_pose", "bone_idx", "global_pose"), &Skeleton3D::global_pose_to_local_pose); - ClassDB::bind_method(D_METHOD("local_pose_to_global_pose", "bone_idx", "local_pose"), &Skeleton3D::local_pose_to_global_pose); - ClassDB::bind_method(D_METHOD("global_pose_z_forward_to_bone_forward", "bone_idx", "basis"), &Skeleton3D::global_pose_z_forward_to_bone_forward); - ClassDB::bind_method(D_METHOD("set_show_rest_only", "enabled"), &Skeleton3D::set_show_rest_only); ClassDB::bind_method(D_METHOD("is_show_rest_only"), &Skeleton3D::is_show_rest_only); diff --git a/scene/3d/skeleton_3d.h b/scene/3d/skeleton_3d.h index 94e59e6c5a84..eb687fdba0cc 100644 --- a/scene/3d/skeleton_3d.h +++ b/scene/3d/skeleton_3d.h @@ -100,17 +100,8 @@ class Skeleton3D : public Node3D { PhysicalBone3D *physical_bone = nullptr; PhysicalBone3D *cache_parent_physical_bone = nullptr; - real_t local_pose_override_amount; - bool local_pose_override_reset; - Transform3D local_pose_override; - Vector child_bones; - // The forward direction vector and rest bone forward axis are cached because they do not change - // 99% of the time, but recalculating them can be expensive on models with many bones. - Vector3 rest_bone_forward_vector; - int rest_bone_forward_axis = -1; - Bone() { parent = -1; enabled = true; @@ -120,12 +111,7 @@ class Skeleton3D : public Node3D { physical_bone = nullptr; cache_parent_physical_bone = nullptr; #endif // _3D_DISABLED - local_pose_override_amount = 0; - local_pose_override_reset = false; child_bones = Vector(); - - rest_bone_forward_vector = Vector3(0, 0, 0); - rest_bone_forward_axis = -1; } }; @@ -159,20 +145,12 @@ class Skeleton3D : public Node3D { static void _bind_methods(); public: - enum Bone_Forward_Axis { - BONE_AXIS_X_FORWARD = 0, - BONE_AXIS_Y_FORWARD = 1, - BONE_AXIS_Z_FORWARD = 2, - BONE_AXIS_NEGATIVE_X_FORWARD = 3, - BONE_AXIS_NEGATIVE_Y_FORWARD = 4, - BONE_AXIS_NEGATIVE_Z_FORWARD = 5, - }; - enum { NOTIFICATION_UPDATE_SKELETON = 50 }; // skeleton creation api + uint64_t get_version() const; void add_bone(const String &p_name); int find_bone(const String &p_name) const; String get_bone_name(int p_bone) const; @@ -225,10 +203,6 @@ class Skeleton3D : public Node3D { Transform3D get_bone_global_pose_override(int p_bone) const; void set_bone_global_pose_override(int p_bone, const Transform3D &p_pose, real_t p_amount, bool p_persistent = false); - void clear_bones_local_pose_override(); - Transform3D get_bone_local_pose_override(int p_bone) const; - void set_bone_local_pose_override(int p_bone, const Transform3D &p_pose, real_t p_amount, bool p_persistent = false); - void localize_rests(); // used for loaders and tools Ref create_skin_from_rest_transforms(); @@ -236,22 +210,8 @@ class Skeleton3D : public Node3D { Ref register_skin(const Ref &p_skin); void force_update_all_dirty_bones(); - void force_update_all_bone_transforms(); void force_update_bone_children_transforms(int bone_idx); - void update_bone_rest_forward_vector(int p_bone, bool p_force_update = false); - void update_bone_rest_forward_axis(int p_bone, bool p_force_update = false); - Vector3 get_bone_axis_forward_vector(int p_bone); - int get_bone_axis_forward_enum(int p_bone); - - // Helper functions - Transform3D global_pose_to_world_transform(Transform3D p_global_pose); - Transform3D world_transform_to_global_pose(Transform3D p_transform); - Transform3D global_pose_to_local_pose(int p_bone_idx, Transform3D p_global_pose); - Transform3D local_pose_to_global_pose(int p_bone_idx, Transform3D p_local_pose); - - Basis global_pose_z_forward_to_bone_forward(int p_bone_idx, Basis p_basis); - // Physical bone API void set_animate_physical_bones(bool p_enabled); diff --git a/scene/3d/skeleton_modification_3d.cpp b/scene/3d/skeleton_modification_3d.cpp index 483d103637a0..570e35424621 100644 --- a/scene/3d/skeleton_modification_3d.cpp +++ b/scene/3d/skeleton_modification_3d.cpp @@ -31,101 +31,287 @@ #include "skeleton_modification_3d.h" #include "scene/3d/skeleton_3d.h" +void SkeletonModification3D::_validate_property(PropertyInfo &p_property) const { + if (is_property_hidden(p_property.name)) { + p_property.usage = PROPERTY_USAGE_NO_EDITOR; + } + if (is_bone_property(p_property.name)) { + // Because it is a constant function, we cannot use the _get_skeleton_3d function. + const Skeleton3D *skel = get_skeleton(); + + if (skel) { + if (bone_name_list.is_empty()) { + for (int i = 0; i < skel->get_bone_count(); i++) { + if (i > 0) { + bone_name_list += ","; + } + bone_name_list += skel->get_bone_name(i); + } + } + + p_property.hint = PROPERTY_HINT_ENUM; + p_property.hint_string = bone_name_list; + } else { + p_property.hint = PROPERTY_HINT_NONE; + p_property.hint_string = ""; + } + } +} + void SkeletonModification3D::set_enabled(bool p_enabled) { enabled = p_enabled; } -bool SkeletonModification3D::get_enabled() { +bool SkeletonModification3D::get_enabled() const { return enabled; } -// Helper function. Needed for CCDIK. -real_t SkeletonModification3D::clamp_angle(real_t p_angle, real_t p_min_bound, real_t p_max_bound, bool p_invert) { - // Map to the 0 to 360 range (in radians though) instead of the -180 to 180 range. - if (p_angle < 0) { - p_angle = Math_TAU + p_angle; - } +void SkeletonModification3D::_bind_methods() { + GDVIRTUAL_BIND(_execute, "delta"); + GDVIRTUAL_BIND(_skeleton_changed, "skeleton"); + GDVIRTUAL_BIND(_is_bone_property, "property_name"); + GDVIRTUAL_BIND(_is_property_hidden, "property_name"); + + ClassDB::bind_method(D_METHOD("set_skeleton_path", "path"), &SkeletonModification3D::set_skeleton_path); + ClassDB::bind_method(D_METHOD("get_skeleton_path"), &SkeletonModification3D::get_skeleton_path); + ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &SkeletonModification3D::set_enabled); + ClassDB::bind_method(D_METHOD("get_enabled"), &SkeletonModification3D::get_enabled); + ClassDB::bind_method(D_METHOD("execute", "delta"), &SkeletonModification3D::execute); - // Make min and max in the range of 0 to 360 (in radians), and make sure they are in the right order - if (p_min_bound < 0) { - p_min_bound = Math_TAU + p_min_bound; + ClassDB::bind_method(D_METHOD("resolve_bone", "target_bone_name"), &SkeletonModification3D::resolve_bone); + ClassDB::bind_method(D_METHOD("resolve_target", "target_node_path", "target_bone_name"), &SkeletonModification3D::resolve_target); + ClassDB::bind_method(D_METHOD("get_target_transform", "resolved_target"), &SkeletonModification3D::get_target_transform); + ClassDB::bind_method(D_METHOD("get_target_quaternion", "resolved_target"), &SkeletonModification3D::get_target_quaternion); + ClassDB::bind_method(D_METHOD("get_bone_rest_forward_vector", "bone_idx"), &SkeletonModification3D::get_bone_rest_forward_vector); + ClassDB::bind_method(D_METHOD("global_pose_z_forward_to_bone_forward", "bone_forward_vector", "basis"), &SkeletonModification3D::global_pose_z_forward_to_bone_forward); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "get_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "skeleton_path"), "set_skeleton_path", "get_skeleton_path"); +} + +NodePath SkeletonModification3D::get_skeleton_path() const { + return skeleton_path; +} + +void SkeletonModification3D::set_skeleton_path(NodePath p_path) { + if (p_path.is_empty()) { + p_path = NodePath(".."); } - if (p_max_bound < 0) { - p_max_bound = Math_TAU + p_max_bound; + skeleton_path = p_path; + skeleton_change_queued = true; + cached_skeleton = Variant(); + bone_name_list.clear(); + update_configuration_warnings(); +} + +Skeleton3D *SkeletonModification3D::get_skeleton() const { + Skeleton3D *skeleton_node = cast_to(cached_skeleton); + if (skeleton_node == nullptr) { + skeleton_node = cast_to(get_node_or_null(skeleton_path)); + cached_skeleton = skeleton_node; } - if (p_min_bound > p_max_bound) { - SWAP(p_min_bound, p_max_bound); + return skeleton_node; +} + +void SkeletonModification3D::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + set_process_internal(get_enabled()); + set_physics_process_internal(false); + cached_skeleton = Variant(); + if (Engine::get_singleton()->is_editor_hint()) { + call_deferred(SNAME("update_configuration_warnings")); + } + } break; + case NOTIFICATION_READY: { + Skeleton3D *skel = get_skeleton(); + if (skel) { + skeleton_changed(skel); + } + } break; + case NOTIFICATION_INTERNAL_PROCESS: { + ERR_FAIL_COND(!enabled); + execute(get_process_delta_time()); + } break; } +} - bool is_beyond_bounds = (p_angle < p_min_bound || p_angle > p_max_bound); - bool is_within_bounds = (p_angle > p_min_bound && p_angle < p_max_bound); +void SkeletonModification3D::skeleton_changed(Skeleton3D *skeleton) { + bone_name_list.clear(); + cached_skeleton_version = skeleton->get_version(); + skeleton_change_queued = false; + GDVIRTUAL_CALL(_skeleton_changed, skeleton); +} - // Note: May not be the most optimal way to clamp, but it always constraints to the nearest angle. - if ((!p_invert && is_beyond_bounds) || (p_invert && is_within_bounds)) { - Vector2 min_bound_vec = Vector2(Math::cos(p_min_bound), Math::sin(p_min_bound)); - Vector2 max_bound_vec = Vector2(Math::cos(p_max_bound), Math::sin(p_max_bound)); - Vector2 angle_vec = Vector2(Math::cos(p_angle), Math::sin(p_angle)); +int SkeletonModification3D::resolve_bone(const String &target_bone_name) const { + Skeleton3D *skel = get_skeleton(); + if (skel) { + return skel->find_bone(target_bone_name); + } + return -1; +} - if (angle_vec.distance_squared_to(min_bound_vec) <= angle_vec.distance_squared_to(max_bound_vec)) { - p_angle = p_min_bound; - } else { - p_angle = p_max_bound; +Variant SkeletonModification3D::resolve_target(const NodePath &target_node_path, const String &target_bone_name) const { + if (target_node_path.is_empty()) { + Skeleton3D *skel = get_skeleton(); + if (skel) { + int found_bone = skel->find_bone(target_bone_name); + if (found_bone >= 0) { + return Variant(found_bone); + } + } + } else { + Node *resolved_node = get_node(target_node_path); + if (cast_to(resolved_node)) { + return Variant(resolved_node); } } + return Variant(false); +} - return p_angle; +Transform3D SkeletonModification3D::get_target_transform(Variant resolved_target) const { + if (resolved_target.get_type() == Variant::OBJECT) { + Skeleton3D *skel = get_skeleton(); + Node3D *resolved_node3d = cast_to((Object *)resolved_target); + return skel->get_global_transform().affine_inverse() * resolved_node3d->get_global_transform(); + } else if (resolved_target.get_type() == Variant::INT) { + int resolved_bone = (int)resolved_target; + return get_bone_transform(resolved_bone); + } + ERR_FAIL_V_MSG(Transform3D(), "Looking up transform of unresolved target."); } -bool SkeletonModification3D::_print_execution_error(bool p_condition, String p_message) { - // If the modification is not setup, don't bother printing the error - if (!is_setup) { - return p_condition; +Quaternion SkeletonModification3D::get_target_quaternion(Variant resolved_target) const { + if (resolved_target.get_type() == Variant::OBJECT) { + Skeleton3D *skel = get_skeleton(); + Node3D *resolved_node3d = cast_to((Object *)resolved_target); + return skel->get_global_transform().basis.get_rotation_quaternion().inverse() * resolved_node3d->get_global_transform().basis.get_rotation_quaternion(); + } else if (resolved_target.get_type() == Variant::INT) { + int resolved_bone = (int)resolved_target; + return get_bone_quaternion(resolved_bone); } + ERR_FAIL_V_MSG(Quaternion(), "Looking up quaternion of unresolved target."); +} - if (p_condition && !execution_error_found) { - ERR_PRINT(p_message); - execution_error_found = true; +Transform3D SkeletonModification3D::get_bone_transform(int resolved_bone) const { + Skeleton3D *skel = get_skeleton(); + ERR_FAIL_COND_V(resolved_bone < 0, Transform3D()); + Transform3D xform = skel->get_bone_pose(resolved_bone); + resolved_bone = skel->get_bone_parent(resolved_bone); + while (resolved_bone >= 0) { + xform = skel->get_bone_pose(resolved_bone) * xform; + resolved_bone = skel->get_bone_parent(resolved_bone); } - return p_condition; + return xform; } -void SkeletonModification3D::set_is_setup(bool p_is_setup) { - is_setup = p_is_setup; +Quaternion SkeletonModification3D::get_bone_quaternion(int resolved_bone) const { + Skeleton3D *skel = get_skeleton(); + ERR_FAIL_COND_V(resolved_bone < 0, Quaternion()); + Quaternion quat = skel->get_bone_pose_rotation(resolved_bone); + resolved_bone = skel->get_bone_parent(resolved_bone); + while (resolved_bone >= 0) { + quat = skel->get_bone_pose_rotation(resolved_bone) * quat; + resolved_bone = skel->get_bone_parent(resolved_bone); + } + return quat; } -bool SkeletonModification3D::get_is_setup() const { - return is_setup; +// The forward direction vector and rest bone forward axis should be cached because they do +// not change 99% of the time, but recalculating them can be expensive on models with many bones. +Vector3 SkeletonModification3D::get_bone_rest_forward_vector(int p_bone) { + Skeleton3D *skeleton = get_skeleton(); + ERR_FAIL_COND_V(skeleton == nullptr, Vector3(0, 1, 0)); + + Transform3D rest = skeleton->get_bone_rest(p_bone); + + // If it is a child/leaf bone... + if (skeleton->get_bone_parent(p_bone) > 0) { + return rest.origin.normalized(); + } else { + // If it has children... + Vector child_bones = skeleton->get_bone_children(p_bone); + if (child_bones.size() > 0) { + Vector3 combined_child_dir = Vector3(0, 0, 0); + for (int i = 0; i < child_bones.size(); i++) { + combined_child_dir += skeleton->get_bone_rest(child_bones[i]).origin.normalized(); + } + combined_child_dir = combined_child_dir / child_bones.size(); + return combined_child_dir.normalized(); + } else { + WARN_PRINT_ONCE("Cannot calculate forward direction for bone " + itos(p_bone)); + WARN_PRINT_ONCE("Assuming direction of (0, 1, 0) for bone"); + return Vector3(0, 1, 0); + } + } } -void SkeletonModification3D::set_execution_mode(int p_mode) { - execution_mode = p_mode; +SkeletonModification3D::Bone_Forward_Axis SkeletonModification3D::vector_to_forward_axis(Vector3 p_rest_bone_forward_vector) { + Vector3 forward_axis_absolute = p_rest_bone_forward_vector.abs(); + if (forward_axis_absolute.x > forward_axis_absolute.y && forward_axis_absolute.x > forward_axis_absolute.z) { + return (p_rest_bone_forward_vector.x > 0) ? BONE_AXIS_X_FORWARD : BONE_AXIS_NEGATIVE_X_FORWARD; + } else if (forward_axis_absolute.y > forward_axis_absolute.x && forward_axis_absolute.y > forward_axis_absolute.z) { + return (p_rest_bone_forward_vector.y > 0) ? BONE_AXIS_Y_FORWARD : BONE_AXIS_NEGATIVE_Y_FORWARD; + } else { + return (p_rest_bone_forward_vector.z > 0) ? BONE_AXIS_Z_FORWARD : BONE_AXIS_NEGATIVE_Z_FORWARD; + } } -int SkeletonModification3D::get_execution_mode() const { - return execution_mode; +Basis SkeletonModification3D::global_pose_z_forward_to_bone_forward(Vector3 p_bone_forward_vector, Basis p_basis) { + Basis return_basis = p_basis; + + Bone_Forward_Axis axis = vector_to_forward_axis(p_bone_forward_vector); + switch (axis) { + case BONE_AXIS_X_FORWARD: + return_basis.rotate_local(Vector3(0, 1, 0), (Math_PI / 2.0)); + break; + case BONE_AXIS_NEGATIVE_X_FORWARD: + return_basis.rotate_local(Vector3(0, 1, 0), -(Math_PI / 2.0)); + break; + case BONE_AXIS_Y_FORWARD: + return_basis.rotate_local(Vector3(1, 0, 0), -(Math_PI / 2.0)); + break; + case BONE_AXIS_NEGATIVE_Y_FORWARD: + return_basis.rotate_local(Vector3(1, 0, 0), (Math_PI / 2.0)); + break; + case BONE_AXIS_Z_FORWARD: + // Do nothing! + break; + case BONE_AXIS_NEGATIVE_Z_FORWARD: + return_basis.rotate_local(Vector3(0, 0, 1), Math_PI); + break; + } + return return_basis; } -void SkeletonModification3D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_skeleton_path", "path"), &SkeletonModification3D::set_skeleton_path); - ClassDB::bind_method(D_METHOD("get_skeleton_path"), &SkeletonModification3D::get_skeleton_path); - ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &SkeletonModification3D::set_enabled); - ClassDB::bind_method(D_METHOD("get_enabled"), &SkeletonModification3D::get_enabled); - ClassDB::bind_method(D_METHOD("set_is_setup", "is_setup"), &SkeletonModification3D::set_is_setup); - ClassDB::bind_method(D_METHOD("get_is_setup"), &SkeletonModification3D::get_is_setup); - ClassDB::bind_method(D_METHOD("set_execution_mode", "execution_mode"), &SkeletonModification3D::set_execution_mode); - ClassDB::bind_method(D_METHOD("get_execution_mode"), &SkeletonModification3D::get_execution_mode); - ClassDB::bind_method(D_METHOD("clamp_angle", "angle", "min", "max", "invert"), &SkeletonModification3D::clamp_angle); +void SkeletonModification3D::execute(real_t delta) { + Skeleton3D *skel = get_skeleton(); + if (skel != nullptr) { + if (skel->get_version() != cached_skeleton_version || skeleton_change_queued) { + skeleton_changed(skel); + } + } + GDVIRTUAL_CALL(_execute, delta); +} - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "get_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "skeleton_path"), "set_skeleton_path", "get_skeleton_path"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "execution_mode", PROPERTY_HINT_ENUM, "process, physics_process"), "set_execution_mode", "get_execution_mode"); +bool SkeletonModification3D::is_property_hidden(String p_property_name) const { + bool ret = false; + const_cast(this)->GDVIRTUAL_CALL(_is_property_hidden, p_property_name, ret); + return ret; } -NodePath SkeletonModification3D::get_skeleton_path() const { - return skeleton_path; +bool SkeletonModification3D::is_bone_property(String p_property_name) const { + bool ret = false; + const_cast(this)->GDVIRTUAL_CALL(_is_bone_property, p_property_name, ret); + return ret; } -void SkeletonModification3D::set_skeleton_path(NodePath p_path) { - skeleton_path = p_path; - skeleton = cast_to(get_node_or_null(p_path)); +TypedArray SkeletonModification3D::get_configuration_warnings() const { + TypedArray ret = Node::get_configuration_warnings(); + + if (!get_skeleton()) { + ret.push_back("Modification skeleton_path must point to a Skeleton3D node."); + } + + return ret; } diff --git a/scene/3d/skeleton_modification_3d.h b/scene/3d/skeleton_modification_3d.h index eafab8ed096a..b0295726abdc 100644 --- a/scene/3d/skeleton_modification_3d.h +++ b/scene/3d/skeleton_modification_3d.h @@ -34,36 +34,77 @@ #include "core/string/node_path.h" #include "scene/3d/skeleton_3d.h" -class SkeletonModification3D : public Node3D { - GDCLASS(SkeletonModification3D, Node3D); +class SkeletonModification3D : public Node { + GDCLASS(SkeletonModification3D, Node); -protected: +private: static void _bind_methods(); - int execution_mode = 0; // 0 = process - bool enabled = true; - bool is_setup = false; - bool execution_error_found = false; - Skeleton3D *skeleton = nullptr; + bool skeleton_change_queued = true; + mutable Variant cached_skeleton; + mutable String bone_name_list; + uint64_t cached_skeleton_version = 0; NodePath skeleton_path = NodePath(".."); - bool _print_execution_error(bool p_condition, String p_message); +protected: + bool _cache_bone(int &bone_cache, const String &target_bone_name) const { + if (bone_cache == UNCACHED_BONE_IDX) { + bone_cache = resolve_bone(target_bone_name); + } + return bone_cache >= 0; + } + bool _cache_target(Variant &cache, const NodePath &target_node_path, const String &target_bone_name) const { + if (cache.get_type() == Variant::NIL) { + cache = resolve_target(target_node_path, target_bone_name); + } + return cache.get_type() == Variant::OBJECT || cache.get_type() == Variant::INT; + } -public: - real_t clamp_angle(real_t p_angle, real_t p_min_bound, real_t p_max_bound, bool p_invert); + enum Bone_Forward_Axis { + BONE_AXIS_X_FORWARD = 0, + BONE_AXIS_Y_FORWARD = 1, + BONE_AXIS_Z_FORWARD = 2, + BONE_AXIS_NEGATIVE_X_FORWARD = 3, + BONE_AXIS_NEGATIVE_Y_FORWARD = 4, + BONE_AXIS_NEGATIVE_Z_FORWARD = 5, + }; - void set_enabled(bool p_enabled); - bool get_enabled(); + // The forward direction vector and rest bone forward axis should be cached because they do + // not change 99% of the time, but recalculating them can be expensive on models with many bones. + static Bone_Forward_Axis vector_to_forward_axis(Vector3 p_rest_bone_forward_vector); - void set_execution_mode(int p_mode); - int get_execution_mode() const; +public: + enum { UNCACHED_BONE_IDX = -2 }; - void set_is_setup(bool p_setup); - bool get_is_setup() const; + void set_enabled(bool p_enabled); + bool get_enabled() const; NodePath get_skeleton_path() const; void set_skeleton_path(NodePath p_path); + Skeleton3D *get_skeleton() const; + + void _validate_property(PropertyInfo &p_property) const; + void _notification(int32_t p_what); + + virtual void skeleton_changed(Skeleton3D *skeleton); + GDVIRTUAL1(_skeleton_changed, Skeleton3D *); + virtual void execute(real_t delta); + GDVIRTUAL1(_execute, real_t); + virtual bool is_bone_property(String property_name) const; + GDVIRTUAL1R(bool, _is_bone_property, String); + virtual bool is_property_hidden(String property_name) const; + GDVIRTUAL1R(bool, _is_property_hidden, String); + TypedArray get_configuration_warnings() const override; + + int resolve_bone(const String &target_bone_name) const; + Variant resolve_target(const NodePath &target_node_path, const String &target_bone_name) const; + Transform3D get_target_transform(Variant resolved_target) const; + Quaternion get_target_quaternion(Variant resolved_target) const; + Transform3D get_bone_transform(int bone_idx) const; + Quaternion get_bone_quaternion(int bone_idx) const; + Vector3 get_bone_rest_forward_vector(int p_bone); + Basis global_pose_z_forward_to_bone_forward(Vector3 p_bone_forward_vector, Basis p_basis); SkeletonModification3D() {} }; diff --git a/scene/3d/skeleton_modification_3d_ccdik.cpp b/scene/3d/skeleton_modification_3d_ccdik.cpp index fa7220ce4db7..8885d871b758 100644 --- a/scene/3d/skeleton_modification_3d_ccdik.cpp +++ b/scene/3d/skeleton_modification_3d_ccdik.cpp @@ -32,29 +32,64 @@ #include "scene/3d/skeleton_3d.h" #include "scene/3d/skeleton_modification_3d.h" +// Helper function. Needed for CCDIK. +static real_t clamp_angle(real_t p_angle, real_t p_min_bound, real_t p_max_bound, bool p_invert) { + // Map to the 0 to 360 range (in radians though) instead of the -180 to 180 range. + if (p_angle < 0) { + p_angle = Math_TAU + p_angle; + } + + // Make min and max in the range of 0 to 360 (in radians), and make sure they are in the right order + if (p_min_bound < 0) { + p_min_bound = Math_TAU + p_min_bound; + } + if (p_max_bound < 0) { + p_max_bound = Math_TAU + p_max_bound; + } + if (p_min_bound > p_max_bound) { + SWAP(p_min_bound, p_max_bound); + } + + bool is_beyond_bounds = (p_angle < p_min_bound || p_angle > p_max_bound); + bool is_within_bounds = (p_angle > p_min_bound && p_angle < p_max_bound); + + // Note: May not be the most optimal way to clamp, but it always constraints to the nearest angle. + if ((!p_invert && is_beyond_bounds) || (p_invert && is_within_bounds)) { + Vector2 min_bound_vec = Vector2(Math::cos(p_min_bound), Math::sin(p_min_bound)); + Vector2 max_bound_vec = Vector2(Math::cos(p_max_bound), Math::sin(p_max_bound)); + Vector2 angle_vec = Vector2(Math::cos(p_angle), Math::sin(p_angle)); + + if (angle_vec.distance_squared_to(min_bound_vec) <= angle_vec.distance_squared_to(max_bound_vec)) { + p_angle = p_min_bound; + } else { + p_angle = p_max_bound; + } + } + + return p_angle; +} + bool SkeletonModification3DCCDIK::_set(const StringName &p_path, const Variant &p_value) { String path = p_path; - if (path.begins_with("joint_data/")) { + if (path.begins_with("joint_")) { int ccdik_data_size = ccdik_data_chain.size(); - int which = path.get_slicec('/', 1).to_int(); - String what = path.get_slicec('/', 2); + int which = path.get_slicec('/', 0).substr(6).to_int(); + String what = path.get_slicec('/', 1); ERR_FAIL_INDEX_V(which, ccdik_data_size, false); - if (what == "bone_name") { - set_ccdik_joint_bone_name(which, p_value); - } else if (what == "bone_index") { - set_ccdik_joint_bone_index(which, p_value); + if (what == "bone") { + set_joint_bone(which, p_value); } else if (what == "ccdik_axis") { - set_ccdik_joint_ccdik_axis(which, p_value); + set_joint_ccdik_axis(which, p_value); } else if (what == "enable_joint_constraint") { - set_ccdik_joint_enable_constraint(which, p_value); + set_joint_enable_constraint(which, p_value); } else if (what == "joint_constraint_angle_min") { - set_ccdik_joint_constraint_angle_min(which, Math::deg_to_rad(real_t(p_value))); + set_joint_constraint_angle_min(which, real_t(p_value)); } else if (what == "joint_constraint_angle_max") { - set_ccdik_joint_constraint_angle_max(which, Math::deg_to_rad(real_t(p_value))); + set_joint_constraint_angle_max(which, real_t(p_value)); } else if (what == "joint_constraint_angles_invert") { - set_ccdik_joint_constraint_invert(which, p_value); + set_joint_constraint_invert(which, p_value); } return true; } @@ -64,26 +99,24 @@ bool SkeletonModification3DCCDIK::_set(const StringName &p_path, const Variant & bool SkeletonModification3DCCDIK::_get(const StringName &p_path, Variant &r_ret) const { String path = p_path; - if (path.begins_with("joint_data/")) { + if (path.begins_with("joint_")) { const int ccdik_data_size = ccdik_data_chain.size(); - int which = path.get_slicec('/', 1).to_int(); - String what = path.get_slicec('/', 2); + int which = path.get_slicec('/', 0).substr(6).to_int(); + String what = path.get_slicec('/', 1); ERR_FAIL_INDEX_V(which, ccdik_data_size, false); - if (what == "bone_name") { - r_ret = get_ccdik_joint_bone_name(which); - } else if (what == "bone_index") { - r_ret = get_ccdik_joint_bone_index(which); + if (what == "bone") { + r_ret = get_joint_bone(which); } else if (what == "ccdik_axis") { - r_ret = get_ccdik_joint_ccdik_axis(which); + r_ret = get_joint_ccdik_axis(which); } else if (what == "enable_joint_constraint") { - r_ret = get_ccdik_joint_enable_constraint(which); + r_ret = get_joint_enable_constraint(which); } else if (what == "joint_constraint_angle_min") { - r_ret = Math::rad_to_deg(get_ccdik_joint_constraint_angle_min(which)); + r_ret = get_joint_constraint_angle_min(which); } else if (what == "joint_constraint_angle_max") { - r_ret = Math::rad_to_deg(get_ccdik_joint_constraint_angle_max(which)); + r_ret = get_joint_constraint_angle_max(which); } else if (what == "joint_constraint_angles_invert") { - r_ret = get_ccdik_joint_constraint_invert(which); + r_ret = get_joint_constraint_invert(which); } return true; } @@ -92,34 +125,32 @@ bool SkeletonModification3DCCDIK::_get(const StringName &p_path, Variant &r_ret) void SkeletonModification3DCCDIK::_get_property_list(List *p_list) const { for (uint32_t i = 0; i < ccdik_data_chain.size(); i++) { - String base_string = "joint_data/" + itos(i) + "/"; + String base_string = "joint_" + itos(i) + "/"; - p_list->push_back(PropertyInfo(Variant::STRING_NAME, base_string + "bone_name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - p_list->push_back(PropertyInfo(Variant::INT, base_string + "bone_index", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::STRING_NAME, base_string + "bone", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); p_list->push_back(PropertyInfo(Variant::INT, base_string + "ccdik_axis", PROPERTY_HINT_ENUM, "X Axis, Y Axis, Z Axis", PROPERTY_USAGE_DEFAULT)); p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "enable_joint_constraint", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); if (ccdik_data_chain[i].enable_constraint) { - p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "joint_constraint_angle_min", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "joint_constraint_angle_max", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "joint_constraint_angle_min", PROPERTY_HINT_RANGE, "-360,360,0.01,radians", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "joint_constraint_angle_max", PROPERTY_HINT_RANGE, "-360,360,0.01,radians", PROPERTY_USAGE_DEFAULT)); p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "joint_constraint_angles_invert", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); } } } -void SkeletonModification3DCCDIK::_execute_ccdik_joint(int p_joint_idx, Node3D *p_target, Node3D *p_tip) { - CCDIK_Joint_Data ccdik_data = ccdik_data_chain[p_joint_idx]; +void SkeletonModification3DCCDIK::_execute_ccdik_joint(int p_joint_idx, const Transform3D &target_trans, const Transform3D &tip_trans) { + const CCDIK_Joint_Data &ccdik_data = ccdik_data_chain[p_joint_idx]; + Skeleton3D *skeleton = get_skeleton(); - if (_print_execution_error(ccdik_data.bone_idx < 0 || ccdik_data.bone_idx > skeleton->get_bone_count(), - "CCDIK joint: bone index for joint" + itos(p_joint_idx) + " not found. Cannot execute modification!")) { + if (skeleton == nullptr || !_cache_bone(ccdik_data.bone_idx, ccdik_data.bone_name)) { + WARN_PRINT_ONCE(String("CCDIK joint: bone index for joint") + itos(p_joint_idx) + " not found. Cannot execute modification!"); return; } - Transform3D bone_trans = skeleton->global_pose_to_local_pose(ccdik_data.bone_idx, skeleton->get_bone_global_pose(ccdik_data.bone_idx)); - Transform3D tip_trans = skeleton->global_pose_to_local_pose(ccdik_data.bone_idx, skeleton->world_transform_to_global_pose(p_tip->get_global_transform())); - Transform3D target_trans = skeleton->global_pose_to_local_pose(ccdik_data.bone_idx, skeleton->world_transform_to_global_pose(p_target->get_global_transform())); + Transform3D bone_trans; if (tip_trans.origin.distance_to(target_trans.origin) <= 0.01) { return; @@ -180,69 +211,56 @@ void SkeletonModification3DCCDIK::_execute_ccdik_joint(int p_joint_idx, Node3D * skeleton->set_bone_pose_position(ccdik_data.bone_idx, bone_trans.origin); skeleton->set_bone_pose_rotation(ccdik_data.bone_idx, bone_trans.basis.get_rotation_quaternion()); skeleton->set_bone_pose_scale(ccdik_data.bone_idx, bone_trans.basis.get_scale()); - skeleton->force_update_bone_children_transforms(ccdik_data.bone_idx); } -void SkeletonModification3DCCDIK::update_target_cache() { - if (!is_setup) { - _print_execution_error(true, "Cannot update target cache: modification is not properly setup!"); - return; - } - - target_node_cache = ObjectID(); - if (is_inside_tree()) { - if (has_node(target_node)) { - Node *node = get_node_or_null(target_node); - ERR_FAIL_COND_MSG(!node || skeleton == node, - "Cannot update target cache: node is this modification's skeleton or cannot be found!"); - ERR_FAIL_COND_MSG(!node->is_inside_tree(), - "Cannot update target cache: node is not in scene tree!"); - target_node_cache = node->get_instance_id(); - - execution_error_found = false; - } +void SkeletonModification3DCCDIK::set_target_node(const NodePath &p_target_node) { + target_node = p_target_node; + if (target_node != NodePath()) { + target_bone = String(); } + target_cache = Variant(); } -void SkeletonModification3DCCDIK::update_tip_cache() { - if (!is_setup) { - _print_execution_error(true, "Cannot update tip cache: modification is not properly setup!"); - return; - } - - tip_node_cache = ObjectID(); - if (is_inside_tree()) { - if (has_node(tip_node)) { - Node *node = get_node_or_null(tip_node); - ERR_FAIL_COND_MSG(!node || skeleton == node, - "Cannot update tip cache: node is this modification's skeleton or cannot be found!"); - ERR_FAIL_COND_MSG(!node->is_inside_tree(), - "Cannot update tip cache: node is not in scene tree!"); - tip_node_cache = node->get_instance_id(); - - execution_error_found = false; - } - } +NodePath SkeletonModification3DCCDIK::get_target_node() const { + return target_node; } -void SkeletonModification3DCCDIK::set_target_node(const NodePath &p_target_node) { - target_node = p_target_node; - update_target_cache(); +void SkeletonModification3DCCDIK::set_target_bone(const String &p_target_bone) { + target_bone = p_target_bone; + if (target_bone != String()) { + target_node = NodePath(); + } + target_cache = Variant(); } -NodePath SkeletonModification3DCCDIK::get_target_node() const { - return target_node; +String SkeletonModification3DCCDIK::get_target_bone() const { + return target_bone; } void SkeletonModification3DCCDIK::set_tip_node(const NodePath &p_tip_node) { tip_node = p_tip_node; - update_tip_cache(); + if (tip_node != NodePath()) { + tip_bone = String(); + } + tip_cache = Variant(); } NodePath SkeletonModification3DCCDIK::get_tip_node() const { return tip_node; } +void SkeletonModification3DCCDIK::set_tip_bone(const String &p_tip_bone) { + tip_bone = p_tip_bone; + if (tip_bone != String()) { + tip_node = NodePath(); + } + tip_cache = Variant(); +} + +String SkeletonModification3DCCDIK::get_tip_bone() const { + return tip_bone; +} + void SkeletonModification3DCCDIK::set_use_high_quality_solve(bool p_high_quality) { use_high_quality_solve = p_high_quality; } @@ -252,50 +270,26 @@ bool SkeletonModification3DCCDIK::get_use_high_quality_solve() const { } // CCDIK joint data functions -String SkeletonModification3DCCDIK::get_ccdik_joint_bone_name(int p_joint_idx) const { +String SkeletonModification3DCCDIK::get_joint_bone(int p_joint_idx) const { const int bone_chain_size = ccdik_data_chain.size(); ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, String()); return ccdik_data_chain[p_joint_idx].bone_name; } -void SkeletonModification3DCCDIK::set_ccdik_joint_bone_name(int p_joint_idx, String p_bone_name) { +void SkeletonModification3DCCDIK::set_joint_bone(int p_joint_idx, String p_bone_name) { const int bone_chain_size = ccdik_data_chain.size(); ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); ccdik_data_chain[p_joint_idx].bone_name = p_bone_name; - - if (skeleton) { - ccdik_data_chain[p_joint_idx].bone_idx = skeleton->find_bone(p_bone_name); - } - execution_error_found = false; - notify_property_list_changed(); -} - -int SkeletonModification3DCCDIK::get_ccdik_joint_bone_index(int p_joint_idx) const { - const int bone_chain_size = ccdik_data_chain.size(); - ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, -1); - return ccdik_data_chain[p_joint_idx].bone_idx; -} - -void SkeletonModification3DCCDIK::set_ccdik_joint_bone_index(int p_joint_idx, int p_bone_idx) { - const int bone_chain_size = ccdik_data_chain.size(); - ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); - ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!"); - ccdik_data_chain[p_joint_idx].bone_idx = p_bone_idx; - - if (skeleton) { - ccdik_data_chain[p_joint_idx].bone_name = skeleton->get_bone_name(p_bone_idx); - } - execution_error_found = false; - notify_property_list_changed(); + ccdik_data_chain[p_joint_idx].bone_idx = UNCACHED_BONE_IDX; } -int SkeletonModification3DCCDIK::get_ccdik_joint_ccdik_axis(int p_joint_idx) const { +int SkeletonModification3DCCDIK::get_joint_ccdik_axis(int p_joint_idx) const { const int bone_chain_size = ccdik_data_chain.size(); ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, -1); return ccdik_data_chain[p_joint_idx].ccdik_axis; } -void SkeletonModification3DCCDIK::set_ccdik_joint_ccdik_axis(int p_joint_idx, int p_axis) { +void SkeletonModification3DCCDIK::set_joint_ccdik_axis(int p_joint_idx, int p_axis) { const int bone_chain_size = ccdik_data_chain.size(); ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); ERR_FAIL_COND_MSG(p_axis < 0, "CCDIK axis is out of range: The axis mode is too low!"); @@ -303,168 +297,166 @@ void SkeletonModification3DCCDIK::set_ccdik_joint_ccdik_axis(int p_joint_idx, in notify_property_list_changed(); } -bool SkeletonModification3DCCDIK::get_ccdik_joint_enable_constraint(int p_joint_idx) const { +bool SkeletonModification3DCCDIK::get_joint_enable_constraint(int p_joint_idx) const { const int bone_chain_size = ccdik_data_chain.size(); ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, false); return ccdik_data_chain[p_joint_idx].enable_constraint; } -void SkeletonModification3DCCDIK::set_ccdik_joint_enable_constraint(int p_joint_idx, bool p_enable) { +void SkeletonModification3DCCDIK::set_joint_enable_constraint(int p_joint_idx, bool p_enable) { const int bone_chain_size = ccdik_data_chain.size(); ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); ccdik_data_chain[p_joint_idx].enable_constraint = p_enable; notify_property_list_changed(); } -real_t SkeletonModification3DCCDIK::get_ccdik_joint_constraint_angle_min(int p_joint_idx) const { +real_t SkeletonModification3DCCDIK::get_joint_constraint_angle_min(int p_joint_idx) const { const int bone_chain_size = ccdik_data_chain.size(); ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, false); return ccdik_data_chain[p_joint_idx].constraint_angle_min; } -void SkeletonModification3DCCDIK::set_ccdik_joint_constraint_angle_min(int p_joint_idx, real_t p_angle_min) { +void SkeletonModification3DCCDIK::set_joint_constraint_angle_min(int p_joint_idx, real_t p_angle_min) { const int bone_chain_size = ccdik_data_chain.size(); ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); ccdik_data_chain[p_joint_idx].constraint_angle_min = p_angle_min; } -real_t SkeletonModification3DCCDIK::get_ccdik_joint_constraint_angle_max(int p_joint_idx) const { +real_t SkeletonModification3DCCDIK::get_joint_constraint_angle_max(int p_joint_idx) const { const int bone_chain_size = ccdik_data_chain.size(); ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, false); return ccdik_data_chain[p_joint_idx].constraint_angle_max; } -void SkeletonModification3DCCDIK::set_ccdik_joint_constraint_angle_max(int p_joint_idx, real_t p_angle_max) { +void SkeletonModification3DCCDIK::set_joint_constraint_angle_max(int p_joint_idx, real_t p_angle_max) { const int bone_chain_size = ccdik_data_chain.size(); ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); ccdik_data_chain[p_joint_idx].constraint_angle_max = p_angle_max; } -bool SkeletonModification3DCCDIK::get_ccdik_joint_constraint_invert(int p_joint_idx) const { +bool SkeletonModification3DCCDIK::get_joint_constraint_invert(int p_joint_idx) const { const int bone_chain_size = ccdik_data_chain.size(); ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, false); return ccdik_data_chain[p_joint_idx].constraint_angles_invert; } -void SkeletonModification3DCCDIK::set_ccdik_joint_constraint_invert(int p_joint_idx, bool p_invert) { +void SkeletonModification3DCCDIK::set_joint_constraint_invert(int p_joint_idx, bool p_invert) { const int bone_chain_size = ccdik_data_chain.size(); ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); ccdik_data_chain[p_joint_idx].constraint_angles_invert = p_invert; } -int SkeletonModification3DCCDIK::get_ccdik_data_chain_length() { +int SkeletonModification3DCCDIK::get_joint_count() { return ccdik_data_chain.size(); } -void SkeletonModification3DCCDIK::set_ccdik_data_chain_length(int p_length) { +void SkeletonModification3DCCDIK::set_joint_count(int p_length) { ERR_FAIL_COND(p_length < 0); ccdik_data_chain.resize(p_length); - execution_error_found = false; notify_property_list_changed(); } void SkeletonModification3DCCDIK::_bind_methods() { ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification3DCCDIK::set_target_node); ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification3DCCDIK::get_target_node); + ClassDB::bind_method(D_METHOD("set_target_bone", "target_bone_name"), &SkeletonModification3DCCDIK::set_target_bone); + ClassDB::bind_method(D_METHOD("get_target_bone"), &SkeletonModification3DCCDIK::get_target_bone); ClassDB::bind_method(D_METHOD("set_tip_node", "tip_nodepath"), &SkeletonModification3DCCDIK::set_tip_node); ClassDB::bind_method(D_METHOD("get_tip_node"), &SkeletonModification3DCCDIK::get_tip_node); + ClassDB::bind_method(D_METHOD("set_tip_bone", "tip_bone_name"), &SkeletonModification3DCCDIK::set_tip_bone); + ClassDB::bind_method(D_METHOD("get_tip_bone"), &SkeletonModification3DCCDIK::get_tip_bone); ClassDB::bind_method(D_METHOD("set_use_high_quality_solve", "high_quality_solve"), &SkeletonModification3DCCDIK::set_use_high_quality_solve); ClassDB::bind_method(D_METHOD("get_use_high_quality_solve"), &SkeletonModification3DCCDIK::get_use_high_quality_solve); // CCDIK joint data functions - ClassDB::bind_method(D_METHOD("get_ccdik_joint_bone_name", "joint_idx"), &SkeletonModification3DCCDIK::get_ccdik_joint_bone_name); - ClassDB::bind_method(D_METHOD("set_ccdik_joint_bone_name", "joint_idx", "bone_name"), &SkeletonModification3DCCDIK::set_ccdik_joint_bone_name); - ClassDB::bind_method(D_METHOD("get_ccdik_joint_bone_index", "joint_idx"), &SkeletonModification3DCCDIK::get_ccdik_joint_bone_index); - ClassDB::bind_method(D_METHOD("set_ccdik_joint_bone_index", "joint_idx", "bone_index"), &SkeletonModification3DCCDIK::set_ccdik_joint_bone_index); - ClassDB::bind_method(D_METHOD("get_ccdik_joint_ccdik_axis", "joint_idx"), &SkeletonModification3DCCDIK::get_ccdik_joint_ccdik_axis); - ClassDB::bind_method(D_METHOD("set_ccdik_joint_ccdik_axis", "joint_idx", "axis"), &SkeletonModification3DCCDIK::set_ccdik_joint_ccdik_axis); - ClassDB::bind_method(D_METHOD("get_ccdik_joint_enable_joint_constraint", "joint_idx"), &SkeletonModification3DCCDIK::get_ccdik_joint_enable_constraint); - ClassDB::bind_method(D_METHOD("set_ccdik_joint_enable_joint_constraint", "joint_idx", "enable"), &SkeletonModification3DCCDIK::set_ccdik_joint_enable_constraint); - ClassDB::bind_method(D_METHOD("get_ccdik_joint_constraint_angle_min", "joint_idx"), &SkeletonModification3DCCDIK::get_ccdik_joint_constraint_angle_min); - ClassDB::bind_method(D_METHOD("set_ccdik_joint_constraint_angle_min", "joint_idx", "min_angle"), &SkeletonModification3DCCDIK::set_ccdik_joint_constraint_angle_min); - ClassDB::bind_method(D_METHOD("get_ccdik_joint_constraint_angle_max", "joint_idx"), &SkeletonModification3DCCDIK::get_ccdik_joint_constraint_angle_max); - ClassDB::bind_method(D_METHOD("set_ccdik_joint_constraint_angle_max", "joint_idx", "max_angle"), &SkeletonModification3DCCDIK::set_ccdik_joint_constraint_angle_max); - ClassDB::bind_method(D_METHOD("get_ccdik_joint_constraint_invert", "joint_idx"), &SkeletonModification3DCCDIK::get_ccdik_joint_constraint_invert); - ClassDB::bind_method(D_METHOD("set_ccdik_joint_constraint_invert", "joint_idx", "invert"), &SkeletonModification3DCCDIK::set_ccdik_joint_constraint_invert); - - ClassDB::bind_method(D_METHOD("set_ccdik_data_chain_length", "length"), &SkeletonModification3DCCDIK::set_ccdik_data_chain_length); - ClassDB::bind_method(D_METHOD("get_ccdik_data_chain_length"), &SkeletonModification3DCCDIK::get_ccdik_data_chain_length); - - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node3D"), "set_target_node", "get_target_node"); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "tip_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node3D"), "set_tip_node", "get_tip_node"); + ClassDB::bind_method(D_METHOD("get_joint_bone", "joint_idx"), &SkeletonModification3DCCDIK::get_joint_bone); + ClassDB::bind_method(D_METHOD("set_joint_bone", "joint_idx", "bone_name"), &SkeletonModification3DCCDIK::set_joint_bone); + ClassDB::bind_method(D_METHOD("get_joint_ccdik_axis", "joint_idx"), &SkeletonModification3DCCDIK::get_joint_ccdik_axis); + ClassDB::bind_method(D_METHOD("set_joint_ccdik_axis", "joint_idx", "axis"), &SkeletonModification3DCCDIK::set_joint_ccdik_axis); + ClassDB::bind_method(D_METHOD("get_joint_enable_joint_constraint", "joint_idx"), &SkeletonModification3DCCDIK::get_joint_enable_constraint); + ClassDB::bind_method(D_METHOD("set_joint_enable_joint_constraint", "joint_idx", "enable"), &SkeletonModification3DCCDIK::set_joint_enable_constraint); + ClassDB::bind_method(D_METHOD("get_joint_constraint_angle_min", "joint_idx"), &SkeletonModification3DCCDIK::get_joint_constraint_angle_min); + ClassDB::bind_method(D_METHOD("set_joint_constraint_angle_min", "joint_idx", "min_angle"), &SkeletonModification3DCCDIK::set_joint_constraint_angle_min); + ClassDB::bind_method(D_METHOD("get_joint_constraint_angle_max", "joint_idx"), &SkeletonModification3DCCDIK::get_joint_constraint_angle_max); + ClassDB::bind_method(D_METHOD("set_joint_constraint_angle_max", "joint_idx", "max_angle"), &SkeletonModification3DCCDIK::set_joint_constraint_angle_max); + ClassDB::bind_method(D_METHOD("get_joint_constraint_invert", "joint_idx"), &SkeletonModification3DCCDIK::get_joint_constraint_invert); + ClassDB::bind_method(D_METHOD("set_joint_constraint_invert", "joint_idx", "invert"), &SkeletonModification3DCCDIK::set_joint_constraint_invert); + + ClassDB::bind_method(D_METHOD("set_joint_count", "ccdik_chain_length"), &SkeletonModification3DCCDIK::set_joint_count); + ClassDB::bind_method(D_METHOD("get_joint_count"), &SkeletonModification3DCCDIK::get_joint_count); + + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node3D"), "set_target_node", "get_target_node"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "target_bone", PROPERTY_HINT_NONE, ""), "set_target_bone", "get_target_bone"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "tip_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node3D"), "set_tip_node", "get_tip_node"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "tip_bone", PROPERTY_HINT_NONE, ""), "set_tip_bone", "get_tip_bone"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "high_quality_solve", PROPERTY_HINT_NONE, ""), "set_use_high_quality_solve", "get_use_high_quality_solve"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "ccdik_data_chain_length", PROPERTY_HINT_RANGE, "0,100,1"), "set_ccdik_data_chain_length", "get_ccdik_data_chain_length"); + ADD_ARRAY_COUNT("CCDIK Joint Chain", "joint_count", "set_joint_count", "get_joint_count", "joint_"); } SkeletonModification3DCCDIK::SkeletonModification3DCCDIK() { - is_setup = false; - enabled = true; } SkeletonModification3DCCDIK::~SkeletonModification3DCCDIK() { } -void SkeletonModification3DCCDIK::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - execution_error_found = false; - update_target_cache(); - update_tip_cache(); - set_process_internal(false); - set_physics_process_internal(false); - if (get_execution_mode() == 0) { - set_process_internal(true); - } else if (get_execution_mode() == 1) { - set_physics_process_internal(true); - } - skeleton = cast_to(get_node_or_null(get_skeleton_path())); - is_setup = true; - } break; - case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: - [[fallthrough]]; - case NOTIFICATION_INTERNAL_PROCESS: { - if (!is_setup || skeleton == nullptr) { - ERR_PRINT_ONCE("Modification is not setup and therefore cannot execute!"); - return; - } - if (!enabled) { - return; - } +void SkeletonModification3DCCDIK::execute(real_t delta) { + SkeletonModification3D::execute(delta); - if (target_node_cache.is_null()) { - _print_execution_error(true, "Target cache is out of date. Attempting to update"); - update_target_cache(); - return; + Transform3D target_transform = get_target_transform(target_cache); + Transform3D tip_transform = get_target_transform(tip_cache); + if (use_high_quality_solve) { + for (uint32_t i = 0; i < ccdik_data_chain.size(); i++) { + for (uint32_t j = i; j < ccdik_data_chain.size(); j++) { + _execute_ccdik_joint(j, target_transform, tip_transform); } - if (tip_node_cache.is_null()) { - _print_execution_error(true, "Tip cache is out of date. Attempting to update"); - update_tip_cache(); - return; - } - Node3D *node_target = Object::cast_to(ObjectDB::get_instance(target_node_cache)); - Node3D *node_tip = Object::cast_to(ObjectDB::get_instance(tip_node_cache)); + } + } else { + for (uint32_t i = 0; i < ccdik_data_chain.size(); i++) { + _execute_ccdik_joint(i, target_transform, tip_transform); + } + } +} - if (_print_execution_error(!node_target || !node_target->is_inside_tree(), "Target node is not in the scene tree. Cannot execute modification!")) { - return; - } - if (_print_execution_error(!node_tip || !node_tip->is_inside_tree(), "Tip node is not in the scene tree. Cannot execute modification!")) { - return; - } +void SkeletonModification3DCCDIK::skeleton_changed(Skeleton3D *skeleton) { + target_cache = Variant(); + tip_cache = Variant(); + for (uint32_t i = 0; i < ccdik_data_chain.size(); i++) { + ccdik_data_chain[i].bone_idx = UNCACHED_BONE_IDX; + } + SkeletonModification3D::skeleton_changed(skeleton); +} - if (use_high_quality_solve) { - for (uint32_t i = 0; i < ccdik_data_chain.size(); i++) { - for (uint32_t j = i; j < ccdik_data_chain.size(); j++) { - _execute_ccdik_joint(j, node_target, node_tip); - } - } - } else { - for (uint32_t i = 0; i < ccdik_data_chain.size(); i++) { - _execute_ccdik_joint(i, node_target, node_tip); - } - } +bool SkeletonModification3DCCDIK::is_property_hidden(String p_property_name) const { + if ((p_property_name == "target_bone" && !target_node.is_empty()) || + (p_property_name == "tip_bone" && !tip_node.is_empty())) { + return true; + } + return SkeletonModification3D::is_property_hidden(p_property_name); +} - execution_error_found = false; - } break; +bool SkeletonModification3DCCDIK::is_bone_property(String p_property_name) const { + if (p_property_name == "target_bone" || p_property_name == "tip_bone" || p_property_name.ends_with("/bone")) { + return true; + } + return SkeletonModification3D::is_bone_property(p_property_name); +} + +TypedArray SkeletonModification3DCCDIK::get_configuration_warnings() const { + TypedArray ret = SkeletonModification3D::get_configuration_warnings(); + if (!get_skeleton()) { + return ret; + } + if (!_cache_target(target_cache, target_node, target_bone)) { + ret.append(vformat("Target %s %s was not found.", target_node.is_empty() ? "bone" : "node", target_node.is_empty() ? target_bone : (String)target_node)); + } + if (!_cache_target(tip_cache, tip_node, tip_bone)) { + ret.append(vformat("Tip %s %s was not found.", tip_node.is_empty() ? "bone" : "node", tip_node.is_empty() ? tip_bone : (String)tip_node)); + } + for (uint32_t i = 0; i < ccdik_data_chain.size(); i++) { + if (!_cache_bone(ccdik_data_chain[i].bone_idx, ccdik_data_chain[i].bone_name)) { + ret.append(vformat("Joint %d bone %s was not found.", i, ccdik_data_chain[i].bone_name)); + } } + return ret; } diff --git a/scene/3d/skeleton_modification_3d_ccdik.h b/scene/3d/skeleton_modification_3d_ccdik.h index 4c3243a90296..2d6c13012656 100644 --- a/scene/3d/skeleton_modification_3d_ccdik.h +++ b/scene/3d/skeleton_modification_3d_ccdik.h @@ -47,7 +47,7 @@ class SkeletonModification3DCCDIK : public SkeletonModification3D { struct CCDIK_Joint_Data { String bone_name = ""; - int bone_idx = -1; + mutable int bone_idx = UNCACHED_BONE_IDX; int ccdik_axis = 0; bool enable_constraint = false; @@ -58,52 +58,57 @@ class SkeletonModification3DCCDIK : public SkeletonModification3D { LocalVector ccdik_data_chain; NodePath target_node; - ObjectID target_node_cache; + String target_bone; + mutable Variant target_cache; NodePath tip_node; - ObjectID tip_node_cache; + String tip_bone; + mutable Variant tip_cache; bool use_high_quality_solve = true; - void update_target_cache(); - void update_tip_cache(); - - void _execute_ccdik_joint(int p_joint_idx, Node3D *p_target, Node3D *p_tip); + void _execute_ccdik_joint(int p_joint_idx, const Transform3D &p_target_transform, const Transform3D &p_tip_transform); protected: static void _bind_methods(); bool _get(const StringName &p_path, Variant &r_ret) const; bool _set(const StringName &p_path, const Variant &p_value); void _get_property_list(List *p_list) const; + void skeleton_changed(Skeleton3D *skeleton) override; + void execute(real_t p_delta) override; + bool is_property_hidden(String property_name) const override; + bool is_bone_property(String property_name) const override; + TypedArray get_configuration_warnings() const override; public: - void _notification(int p_what); void set_target_node(const NodePath &p_target_node); NodePath get_target_node() const; + void set_target_bone(const String &p_target_bone); + String get_target_bone() const; void set_tip_node(const NodePath &p_tip_node); NodePath get_tip_node() const; + void set_tip_bone(const String &p_tip_bone); + String get_tip_bone() const; void set_use_high_quality_solve(bool p_solve); bool get_use_high_quality_solve() const; - String get_ccdik_joint_bone_name(int p_joint_idx) const; - void set_ccdik_joint_bone_name(int p_joint_idx, String p_bone_name); - int get_ccdik_joint_bone_index(int p_joint_idx) const; - void set_ccdik_joint_bone_index(int p_joint_idx, int p_bone_idx); - int get_ccdik_joint_ccdik_axis(int p_joint_idx) const; - void set_ccdik_joint_ccdik_axis(int p_joint_idx, int p_axis); - bool get_ccdik_joint_enable_constraint(int p_joint_idx) const; - void set_ccdik_joint_enable_constraint(int p_joint_idx, bool p_enable); - real_t get_ccdik_joint_constraint_angle_min(int p_joint_idx) const; - void set_ccdik_joint_constraint_angle_min(int p_joint_idx, real_t p_angle_min); - real_t get_ccdik_joint_constraint_angle_max(int p_joint_idx) const; - void set_ccdik_joint_constraint_angle_max(int p_joint_idx, real_t p_angle_max); - bool get_ccdik_joint_constraint_invert(int p_joint_idx) const; - void set_ccdik_joint_constraint_invert(int p_joint_idx, bool p_invert); - - int get_ccdik_data_chain_length(); - void set_ccdik_data_chain_length(int p_new_length); + String get_joint_bone(int p_joint_idx) const; + void set_joint_bone(int p_joint_idx, String p_bone_name); + int get_joint_ccdik_axis(int p_joint_idx) const; + void set_joint_ccdik_axis(int p_joint_idx, int p_axis); + bool get_joint_enable_constraint(int p_joint_idx) const; + void set_joint_enable_constraint(int p_joint_idx, bool p_enable); + real_t get_joint_constraint_angle_min(int p_joint_idx) const; + void set_joint_constraint_angle_min(int p_joint_idx, real_t p_angle_min); + real_t get_joint_constraint_angle_max(int p_joint_idx) const; + void set_joint_constraint_angle_max(int p_joint_idx, real_t p_angle_max); + bool get_joint_constraint_invert(int p_joint_idx) const; + void set_joint_constraint_invert(int p_joint_idx, bool p_invert); + + int get_joint_count(); + void set_joint_count(int p_ccdik_chain_length); SkeletonModification3DCCDIK(); ~SkeletonModification3DCCDIK(); diff --git a/scene/3d/skeleton_modification_3d_fabrik.cpp b/scene/3d/skeleton_modification_3d_fabrik.cpp index 7115f3310430..6657876adc14 100644 --- a/scene/3d/skeleton_modification_3d_fabrik.cpp +++ b/scene/3d/skeleton_modification_3d_fabrik.cpp @@ -35,30 +35,28 @@ bool SkeletonModification3DFABRIK::_set(const StringName &p_path, const Variant &p_value) { String path = p_path; - if (path.begins_with("joint_data/")) { + if (path.begins_with("joint_")) { int fabrik_data_size = fabrik_data_chain.size(); - int which = path.get_slicec('/', 1).to_int(); - String what = path.get_slicec('/', 2); + int which = path.get_slicec('/', 0).substr(6).to_int(); + String what = path.get_slicec('/', 1); ERR_FAIL_INDEX_V(which, fabrik_data_size, false); - if (what == "bone_name") { - set_fabrik_joint_bone_name(which, p_value); - } else if (what == "bone_index") { - set_fabrik_joint_bone_index(which, p_value); + if (what == "bone") { + set_joint_bone(which, p_value); } else if (what == "length") { - set_fabrik_joint_length(which, p_value); + set_joint_length(which, p_value); } else if (what == "magnet_position") { - set_fabrik_joint_magnet(which, p_value); + set_joint_magnet(which, p_value); } else if (what == "auto_calculate_length") { - set_fabrik_joint_auto_calculate_length(which, p_value); - } else if (what == "use_tip_node") { - set_fabrik_joint_use_tip_node(which, p_value); + set_joint_auto_calculate_length(which, p_value); } else if (what == "tip_node") { - set_fabrik_joint_tip_node(which, p_value); + set_joint_tip_node(which, p_value); + } else if (what == "tip_bone") { + set_joint_tip_bone(which, p_value); } else if (what == "use_target_basis") { - set_fabrik_joint_use_target_basis(which, p_value); + set_joint_use_target_basis(which, p_value); } else if (what == "roll") { - set_fabrik_joint_roll(which, Math::deg_to_rad(real_t(p_value))); + set_joint_roll(which, real_t(p_value)); } return true; } @@ -68,30 +66,28 @@ bool SkeletonModification3DFABRIK::_set(const StringName &p_path, const Variant bool SkeletonModification3DFABRIK::_get(const StringName &p_path, Variant &r_ret) const { String path = p_path; - if (path.begins_with("joint_data/")) { + if (path.begins_with("joint_")) { const int fabrik_data_size = fabrik_data_chain.size(); - int which = path.get_slicec('/', 1).to_int(); - String what = path.get_slicec('/', 2); + int which = path.get_slicec('/', 0).substr(6).to_int(); + String what = path.get_slicec('/', 1); ERR_FAIL_INDEX_V(which, fabrik_data_size, false); - if (what == "bone_name") { - r_ret = get_fabrik_joint_bone_name(which); - } else if (what == "bone_index") { - r_ret = get_fabrik_joint_bone_index(which); + if (what == "bone") { + r_ret = get_joint_bone(which); } else if (what == "length") { - r_ret = get_fabrik_joint_length(which); + r_ret = get_joint_length(which); } else if (what == "magnet_position") { - r_ret = get_fabrik_joint_magnet(which); + r_ret = get_joint_magnet(which); } else if (what == "auto_calculate_length") { - r_ret = get_fabrik_joint_auto_calculate_length(which); - } else if (what == "use_tip_node") { - r_ret = get_fabrik_joint_use_tip_node(which); + r_ret = get_joint_auto_calculate_length(which); } else if (what == "tip_node") { - r_ret = get_fabrik_joint_tip_node(which); + r_ret = get_joint_tip_node(which); + } else if (what == "tip_bone") { + r_ret = get_joint_tip_bone(which); } else if (what == "use_target_basis") { - r_ret = get_fabrik_joint_use_target_basis(which); + r_ret = get_joint_use_target_basis(which); } else if (what == "roll") { - r_ret = Math::rad_to_deg(get_fabrik_joint_roll(which)); + r_ret = get_joint_roll(which); } return true; } @@ -100,20 +96,17 @@ bool SkeletonModification3DFABRIK::_get(const StringName &p_path, Variant &r_ret void SkeletonModification3DFABRIK::_get_property_list(List *p_list) const { for (uint32_t i = 0; i < fabrik_data_chain.size(); i++) { - String base_string = "joint_data/" + itos(i) + "/"; + String base_string = "joint_" + itos(i) + "/"; - p_list->push_back(PropertyInfo(Variant::STRING_NAME, base_string + "bone_name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - p_list->push_back(PropertyInfo(Variant::INT, base_string + "bone_index", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "roll", PROPERTY_HINT_RANGE, "-360,360,0.01", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::STRING_NAME, base_string + "bone", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "roll", PROPERTY_HINT_RANGE, "-360,360,0.01,radians", PROPERTY_USAGE_DEFAULT)); p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "auto_calculate_length", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); if (!fabrik_data_chain[i].auto_calculate_length) { p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "length", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); } else { - p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "use_tip_node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - if (fabrik_data_chain[i].use_tip_node) { - p_list->push_back(PropertyInfo(Variant::NODE_PATH, base_string + "tip_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node3D", PROPERTY_USAGE_DEFAULT)); - } + p_list->push_back(PropertyInfo(Variant::NODE_PATH, base_string + "tip_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node3D", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::STRING, base_string + "tip_bone", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); } // Cannot apply magnet to the origin of the chain, as it will not do anything. @@ -130,16 +123,20 @@ void SkeletonModification3DFABRIK::_get_property_list(List *p_list void SkeletonModification3DFABRIK::chain_backwards() { int final_bone_idx = fabrik_data_chain[final_joint_idx].bone_idx; Transform3D final_joint_trans = fabrik_transforms[final_joint_idx]; + Skeleton3D *skeleton = get_skeleton(); + if (!skeleton) { + return; + } // Get the direction the final bone is facing in. - skeleton->update_bone_rest_forward_vector(final_bone_idx); + Vector3 forward_vector = get_bone_rest_forward_vector(final_bone_idx); Transform3D final_bone_direction_trans = final_joint_trans.looking_at(target_global_pose.origin, Vector3(0, 1, 0)); - final_bone_direction_trans.basis = skeleton->global_pose_z_forward_to_bone_forward(final_bone_idx, final_bone_direction_trans.basis); - Vector3 direction = final_bone_direction_trans.basis.xform(skeleton->get_bone_axis_forward_vector(final_bone_idx)).normalized(); + final_bone_direction_trans.basis = global_pose_z_forward_to_bone_forward(forward_vector, final_bone_direction_trans.basis); + Vector3 direction = final_bone_direction_trans.basis.xform(forward_vector).normalized(); // If set to override, then use the target's Basis rather than the bone's if (fabrik_data_chain[final_joint_idx].use_target_basis) { - direction = target_global_pose.basis.xform(skeleton->get_bone_axis_forward_vector(final_bone_idx)).normalized(); + direction = target_global_pose.basis.xform(forward_vector).normalized(); } // set the position of the final joint to the target position @@ -181,16 +178,42 @@ void SkeletonModification3DFABRIK::chain_forwards() { } void SkeletonModification3DFABRIK::chain_apply() { + Skeleton3D *skeleton = get_skeleton(); + if (!skeleton) { + return; + } + int prev_bone_idx = -1; + Transform3D prev_transform; + for (uint32_t i = 0; i < fabrik_data_chain.size(); i++) { int current_bone_idx = fabrik_data_chain[i].bone_idx; + if (!_cache_bone(fabrik_data_chain[i].bone_idx, fabrik_data_chain[i].bone_name)) { + return; + } + int bone_idx = fabrik_data_chain[i].bone_idx; + int par_idx = skeleton->get_bone_parent(bone_idx); + Transform3D parent_bone_transform; + bool first = true; + while (par_idx >= 0 && par_idx != prev_bone_idx) { + if (first) { + parent_bone_transform = skeleton->get_bone_pose(par_idx); + first = false; + } else { + parent_bone_transform = skeleton->get_bone_pose(par_idx) * parent_bone_transform; + } + par_idx = skeleton->get_bone_parent(par_idx); + } + if (i != 0 && par_idx < 0) { + parent_bone_transform = prev_transform * parent_bone_transform; + } + Transform3D current_trans = fabrik_transforms[i]; // If this is the last bone in the chain... if (i == fabrik_data_chain.size() - 1) { if (fabrik_data_chain[i].use_target_basis == false) { // Point to target... // Get the forward direction that the basis is facing in right now. - skeleton->update_bone_rest_forward_vector(current_bone_idx); - Vector3 forward_vector = skeleton->get_bone_axis_forward_vector(current_bone_idx); + Vector3 forward_vector = get_bone_rest_forward_vector(current_bone_idx); // Rotate the bone towards the target: current_trans.basis.rotate_to_align(forward_vector, current_trans.origin.direction_to(target_global_pose.origin)); current_trans.basis.rotate_local(forward_vector, fabrik_data_chain[i].roll); @@ -201,82 +224,51 @@ void SkeletonModification3DFABRIK::chain_apply() { Transform3D next_trans = fabrik_transforms[i + 1]; // Get the forward direction that the basis is facing in right now. - skeleton->update_bone_rest_forward_vector(current_bone_idx); - Vector3 forward_vector = skeleton->get_bone_axis_forward_vector(current_bone_idx); + Vector3 forward_vector = get_bone_rest_forward_vector(current_bone_idx); // Rotate the bone towards the next bone in the chain: current_trans.basis.rotate_to_align(forward_vector, current_trans.origin.direction_to(next_trans.origin)); current_trans.basis.rotate_local(forward_vector, fabrik_data_chain[i].roll); } - Transform3D new_bone_trans_local = skeleton->global_pose_to_local_pose(current_bone_idx, current_trans); - skeleton->set_bone_pose_position(current_bone_idx, new_bone_trans_local.origin); + Transform3D new_bone_trans_local = parent_bone_transform.affine_inverse() * current_trans; skeleton->set_bone_pose_rotation(current_bone_idx, new_bone_trans_local.basis.get_rotation_quaternion()); - skeleton->set_bone_pose_scale(current_bone_idx, new_bone_trans_local.basis.get_scale()); - } - - // Update all the bones so the next modification has up-to-date data. - skeleton->force_update_all_bone_transforms(); -} -void SkeletonModification3DFABRIK::update_target_cache() { - if (!is_setup) { - _print_execution_error(true, "Cannot update target cache: modification is not properly setup!"); - return; - } - target_node_cache = ObjectID(); - if (is_inside_tree() && target_node.is_empty() == false) { - if (has_node(target_node)) { - Node *node = get_node_or_null(target_node); - ERR_FAIL_COND_MSG(!node || skeleton == node, - "Cannot update target cache: node is this modification's skeleton or cannot be found!"); - ERR_FAIL_COND_MSG(!node->is_inside_tree(), - "Cannot update target cache: node is not in the scene tree!"); - target_node_cache = node->get_instance_id(); - - execution_error_found = false; - } - } -} - -void SkeletonModification3DFABRIK::update_joint_tip_cache(int p_joint_idx) { - const int bone_chain_size = fabrik_data_chain.size(); - ERR_FAIL_INDEX_MSG(p_joint_idx, bone_chain_size, "FABRIK joint not found"); - if (!is_setup) { - _print_execution_error(true, "Cannot update tip cache: modification is not properly setup!"); - return; - } - fabrik_data_chain[p_joint_idx].tip_node_cache = ObjectID(); - if (is_inside_tree() && fabrik_data_chain[p_joint_idx].tip_node.is_empty() == false) { - if (has_node(fabrik_data_chain[p_joint_idx].tip_node)) { - Node *node = get_node(fabrik_data_chain[p_joint_idx].tip_node); - ERR_FAIL_COND_MSG(!node || skeleton == node, - "Cannot update tip cache for joint " + itos(p_joint_idx) + ": node is this modification's skeleton or cannot be found!"); - ERR_FAIL_COND_MSG(!node->is_inside_tree(), - "Cannot update tip cache for joint " + itos(p_joint_idx) + ": node is not in scene tree!"); - fabrik_data_chain[p_joint_idx].tip_node_cache = node->get_instance_id(); - - execution_error_found = false; - } + prev_transform = parent_bone_transform * new_bone_trans_local; + prev_bone_idx = bone_idx; } } void SkeletonModification3DFABRIK::set_target_node(const NodePath &p_target_node) { target_node = p_target_node; - update_target_cache(); + if (!target_node.is_empty()) { + target_bone = String(); + } + target_cache = Variant(); } NodePath SkeletonModification3DFABRIK::get_target_node() const { return target_node; } -int SkeletonModification3DFABRIK::get_fabrik_data_chain_length() { +void SkeletonModification3DFABRIK::set_target_bone(const String &p_target_bone) { + target_bone = p_target_bone; + if (!target_bone.is_empty()) { + target_node = NodePath(); + } + target_cache = Variant(); +} + +String SkeletonModification3DFABRIK::get_target_bone() const { + return target_bone; +} + +int SkeletonModification3DFABRIK::get_joint_count() { return fabrik_data_chain.size(); } -void SkeletonModification3DFABRIK::set_fabrik_data_chain_length(int p_length) { +void SkeletonModification3DFABRIK::set_joint_count(int p_length) { ERR_FAIL_COND(p_length < 0); fabrik_data_chain.resize(p_length); fabrik_transforms.resize(p_length); - execution_error_found = false; notify_property_list_changed(); } @@ -298,123 +290,85 @@ void SkeletonModification3DFABRIK::set_chain_max_iterations(int p_iterations) { } // FABRIK joint data functions -String SkeletonModification3DFABRIK::get_fabrik_joint_bone_name(int p_joint_idx) const { +String SkeletonModification3DFABRIK::get_joint_bone(int p_joint_idx) const { const int bone_chain_size = fabrik_data_chain.size(); ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, String()); return fabrik_data_chain[p_joint_idx].bone_name; } -void SkeletonModification3DFABRIK::set_fabrik_joint_bone_name(int p_joint_idx, String p_bone_name) { +void SkeletonModification3DFABRIK::set_joint_bone(int p_joint_idx, String p_bone_name) { const int bone_chain_size = fabrik_data_chain.size(); ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); fabrik_data_chain[p_joint_idx].bone_name = p_bone_name; - - if (skeleton) { - fabrik_data_chain[p_joint_idx].bone_idx = skeleton->find_bone(p_bone_name); - } - execution_error_found = false; - notify_property_list_changed(); -} - -int SkeletonModification3DFABRIK::get_fabrik_joint_bone_index(int p_joint_idx) const { - const int bone_chain_size = fabrik_data_chain.size(); - ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, -1); - return fabrik_data_chain[p_joint_idx].bone_idx; -} - -void SkeletonModification3DFABRIK::set_fabrik_joint_bone_index(int p_joint_idx, int p_bone_idx) { - const int bone_chain_size = fabrik_data_chain.size(); - ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); - ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!"); - fabrik_data_chain[p_joint_idx].bone_idx = p_bone_idx; - - if (skeleton) { - fabrik_data_chain[p_joint_idx].bone_name = skeleton->get_bone_name(p_bone_idx); - } - execution_error_found = false; - notify_property_list_changed(); + fabrik_data_chain[p_joint_idx].bone_idx = UNCACHED_BONE_IDX; } -real_t SkeletonModification3DFABRIK::get_fabrik_joint_length(int p_joint_idx) const { +real_t SkeletonModification3DFABRIK::get_joint_length(int p_joint_idx) const { const int bone_chain_size = fabrik_data_chain.size(); ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, -1); return fabrik_data_chain[p_joint_idx].length; } -void SkeletonModification3DFABRIK::set_fabrik_joint_length(int p_joint_idx, real_t p_bone_length) { +void SkeletonModification3DFABRIK::set_joint_length(int p_joint_idx, real_t p_bone_length) { const int bone_chain_size = fabrik_data_chain.size(); ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); ERR_FAIL_COND_MSG(p_bone_length < 0, "FABRIK joint length cannot be less than zero!"); - - if (!is_setup) { - fabrik_data_chain[p_joint_idx].length = p_bone_length; - return; - } - - if (fabrik_data_chain[p_joint_idx].auto_calculate_length) { - WARN_PRINT("FABRIK Length not set: auto calculate length is enabled for this joint!"); - fabrik_joint_auto_calculate_length(p_joint_idx); - } else { - fabrik_data_chain[p_joint_idx].length = p_bone_length; - } - - execution_error_found = false; + fabrik_data_chain[p_joint_idx].length = p_bone_length; } -Vector3 SkeletonModification3DFABRIK::get_fabrik_joint_magnet(int p_joint_idx) const { +Vector3 SkeletonModification3DFABRIK::get_joint_magnet(int p_joint_idx) const { const int bone_chain_size = fabrik_data_chain.size(); ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, Vector3()); return fabrik_data_chain[p_joint_idx].magnet_position; } -void SkeletonModification3DFABRIK::set_fabrik_joint_magnet(int p_joint_idx, Vector3 p_magnet) { +void SkeletonModification3DFABRIK::set_joint_magnet(int p_joint_idx, Vector3 p_magnet) { const int bone_chain_size = fabrik_data_chain.size(); ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); fabrik_data_chain[p_joint_idx].magnet_position = p_magnet; } -bool SkeletonModification3DFABRIK::get_fabrik_joint_auto_calculate_length(int p_joint_idx) const { +bool SkeletonModification3DFABRIK::get_joint_auto_calculate_length(int p_joint_idx) const { const int bone_chain_size = fabrik_data_chain.size(); ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, false); return fabrik_data_chain[p_joint_idx].auto_calculate_length; } -void SkeletonModification3DFABRIK::set_fabrik_joint_auto_calculate_length(int p_joint_idx, bool p_auto_calculate) { +void SkeletonModification3DFABRIK::set_joint_auto_calculate_length(int p_joint_idx, bool p_auto_calculate) { const int bone_chain_size = fabrik_data_chain.size(); ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); fabrik_data_chain[p_joint_idx].auto_calculate_length = p_auto_calculate; - fabrik_joint_auto_calculate_length(p_joint_idx); + if (get_skeleton()) { + calculate_fabrik_joint_length(p_joint_idx); + } notify_property_list_changed(); } -void SkeletonModification3DFABRIK::fabrik_joint_auto_calculate_length(int p_joint_idx) { +void SkeletonModification3DFABRIK::calculate_fabrik_joint_length(int p_joint_idx) { const int bone_chain_size = fabrik_data_chain.size(); ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); if (!fabrik_data_chain[p_joint_idx].auto_calculate_length) { return; } - if (!skeleton || !is_setup) { - _print_execution_error(true, "Cannot auto calculate joint length: modification is not properly setup!"); + Skeleton3D *skeleton = get_skeleton(); + if (!skeleton) { + WARN_PRINT_ONCE("Cannot auto calculate joint length: modification is not properly setup!"); return; } ERR_FAIL_INDEX_MSG(fabrik_data_chain[p_joint_idx].bone_idx, skeleton->get_bone_count(), "Bone for joint " + itos(p_joint_idx) + " is not set or points to an unknown bone!"); - if (fabrik_data_chain[p_joint_idx].use_tip_node) { // Use the tip node to update joint length. + Transform3D bone_trans = get_bone_transform(fabrik_data_chain[p_joint_idx].bone_idx); - update_joint_tip_cache(p_joint_idx); + if (fabrik_data_chain[p_joint_idx].tip_node != NodePath() || fabrik_data_chain[p_joint_idx].tip_bone != String()) { // Use the tip node to update joint length. - Node3D *tip_node = Object::cast_to(ObjectDB::get_instance(fabrik_data_chain[p_joint_idx].tip_node_cache)); - ERR_FAIL_COND_MSG(!tip_node, "Tip node for joint " + itos(p_joint_idx) + "is not a Node3D-based node. Cannot calculate length..."); - ERR_FAIL_COND_MSG(!tip_node->is_inside_tree(), "Tip node for joint " + itos(p_joint_idx) + "is not in the scene tree. Cannot calculate length..."); - - Transform3D node_trans = tip_node->get_global_transform(); - node_trans = skeleton->world_transform_to_global_pose(node_trans); - //node_trans = skeleton->global_pose_to_local_pose(fabrik_data_chain[p_joint_idx].bone_idx, node_trans); - //fabrik_data_chain[p_joint_idx].length = node_trans.origin.length(); + if (!_cache_target(fabrik_data_chain[p_joint_idx].tip_cache, fabrik_data_chain[p_joint_idx].tip_node, fabrik_data_chain[p_joint_idx].tip_bone)) { + ERR_FAIL_MSG("Tip node for joint " + itos(p_joint_idx) + "is not a Node3D-based node. Cannot calculate length..."); + } - fabrik_data_chain[p_joint_idx].length = skeleton->get_bone_global_pose(fabrik_data_chain[p_joint_idx].bone_idx).origin.distance_to(node_trans.origin); + Transform3D node_trans = get_target_transform(fabrik_data_chain[p_joint_idx].tip_cache); + fabrik_data_chain[p_joint_idx].length = bone_trans.get_origin().distance_to(node_trans.origin); } else { // Use child bone(s) to update joint length, if possible Vector bone_children = skeleton->get_bone_children(fabrik_data_chain[p_joint_idx].bone_idx); @@ -423,65 +377,66 @@ void SkeletonModification3DFABRIK::fabrik_joint_auto_calculate_length(int p_join return; } - Transform3D bone_trans = skeleton->get_bone_global_pose(fabrik_data_chain[p_joint_idx].bone_idx); - real_t final_length = 0; for (int i = 0; i < bone_children.size(); i++) { - Transform3D child_transform = skeleton->get_bone_global_pose(bone_children[i]); - final_length += bone_trans.origin.distance_to(child_transform.origin); - //final_length += skeleton->global_pose_to_local_pose(fabrik_data_chain[p_joint_idx].bone_idx, child_transform).origin.length(); + final_length += bone_trans.get_basis().xform(skeleton->get_bone_pose_position(bone_children[i])).length(); } fabrik_data_chain[p_joint_idx].length = final_length / bone_children.size(); } - execution_error_found = false; notify_property_list_changed(); } -bool SkeletonModification3DFABRIK::get_fabrik_joint_use_tip_node(int p_joint_idx) const { +NodePath SkeletonModification3DFABRIK::get_joint_tip_node(int p_joint_idx) const { const int bone_chain_size = fabrik_data_chain.size(); - ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, false); - return fabrik_data_chain[p_joint_idx].use_tip_node; + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, NodePath()); + return fabrik_data_chain[p_joint_idx].tip_node; } -void SkeletonModification3DFABRIK::set_fabrik_joint_use_tip_node(int p_joint_idx, bool p_use_tip_node) { +void SkeletonModification3DFABRIK::set_joint_tip_node(int p_joint_idx, NodePath p_tip_node) { const int bone_chain_size = fabrik_data_chain.size(); ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); - fabrik_data_chain[p_joint_idx].use_tip_node = p_use_tip_node; - notify_property_list_changed(); + fabrik_data_chain[p_joint_idx].tip_node = p_tip_node; + if (!p_tip_node.is_empty()) { + fabrik_data_chain[p_joint_idx].tip_bone = String(); + } + fabrik_data_chain[p_joint_idx].tip_cache = Variant(); } -NodePath SkeletonModification3DFABRIK::get_fabrik_joint_tip_node(int p_joint_idx) const { +String SkeletonModification3DFABRIK::get_joint_tip_bone(int p_joint_idx) const { const int bone_chain_size = fabrik_data_chain.size(); - ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, NodePath()); - return fabrik_data_chain[p_joint_idx].tip_node; + ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, String()); + return fabrik_data_chain[p_joint_idx].tip_bone; } -void SkeletonModification3DFABRIK::set_fabrik_joint_tip_node(int p_joint_idx, NodePath p_tip_node) { +void SkeletonModification3DFABRIK::set_joint_tip_bone(int p_joint_idx, String p_tip_bone) { const int bone_chain_size = fabrik_data_chain.size(); ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); - fabrik_data_chain[p_joint_idx].tip_node = p_tip_node; - update_joint_tip_cache(p_joint_idx); + fabrik_data_chain[p_joint_idx].tip_bone = p_tip_bone; + if (!p_tip_bone.is_empty()) { + fabrik_data_chain[p_joint_idx].tip_node = String(); + } + fabrik_data_chain[p_joint_idx].tip_cache = Variant(); } -bool SkeletonModification3DFABRIK::get_fabrik_joint_use_target_basis(int p_joint_idx) const { +bool SkeletonModification3DFABRIK::get_joint_use_target_basis(int p_joint_idx) const { const int bone_chain_size = fabrik_data_chain.size(); ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, false); return fabrik_data_chain[p_joint_idx].use_target_basis; } -void SkeletonModification3DFABRIK::set_fabrik_joint_use_target_basis(int p_joint_idx, bool p_use_target_basis) { +void SkeletonModification3DFABRIK::set_joint_use_target_basis(int p_joint_idx, bool p_use_target_basis) { const int bone_chain_size = fabrik_data_chain.size(); ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); fabrik_data_chain[p_joint_idx].use_target_basis = p_use_target_basis; } -real_t SkeletonModification3DFABRIK::get_fabrik_joint_roll(int p_joint_idx) const { +real_t SkeletonModification3DFABRIK::get_joint_roll(int p_joint_idx) const { const int bone_chain_size = fabrik_data_chain.size(); ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, 0.0); return fabrik_data_chain[p_joint_idx].roll; } -void SkeletonModification3DFABRIK::set_fabrik_joint_roll(int p_joint_idx, real_t p_roll) { +void SkeletonModification3DFABRIK::set_joint_roll(int p_joint_idx, real_t p_roll) { const int bone_chain_size = fabrik_data_chain.size(); ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); fabrik_data_chain[p_joint_idx].roll = p_roll; @@ -490,145 +445,172 @@ void SkeletonModification3DFABRIK::set_fabrik_joint_roll(int p_joint_idx, real_t void SkeletonModification3DFABRIK::_bind_methods() { ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification3DFABRIK::set_target_node); ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification3DFABRIK::get_target_node); - ClassDB::bind_method(D_METHOD("set_fabrik_data_chain_length", "length"), &SkeletonModification3DFABRIK::set_fabrik_data_chain_length); - ClassDB::bind_method(D_METHOD("get_fabrik_data_chain_length"), &SkeletonModification3DFABRIK::get_fabrik_data_chain_length); + ClassDB::bind_method(D_METHOD("set_target_bone", "target_bone_name"), &SkeletonModification3DFABRIK::set_target_bone); + ClassDB::bind_method(D_METHOD("get_target_bone"), &SkeletonModification3DFABRIK::get_target_bone); + ClassDB::bind_method(D_METHOD("set_joint_count", "fabrik_chain_length"), &SkeletonModification3DFABRIK::set_joint_count); + ClassDB::bind_method(D_METHOD("get_joint_count"), &SkeletonModification3DFABRIK::get_joint_count); ClassDB::bind_method(D_METHOD("set_chain_tolerance", "tolerance"), &SkeletonModification3DFABRIK::set_chain_tolerance); ClassDB::bind_method(D_METHOD("get_chain_tolerance"), &SkeletonModification3DFABRIK::get_chain_tolerance); ClassDB::bind_method(D_METHOD("set_chain_max_iterations", "max_iterations"), &SkeletonModification3DFABRIK::set_chain_max_iterations); ClassDB::bind_method(D_METHOD("get_chain_max_iterations"), &SkeletonModification3DFABRIK::get_chain_max_iterations); // FABRIK joint data functions - ClassDB::bind_method(D_METHOD("get_fabrik_joint_bone_name", "joint_idx"), &SkeletonModification3DFABRIK::get_fabrik_joint_bone_name); - ClassDB::bind_method(D_METHOD("set_fabrik_joint_bone_name", "joint_idx", "bone_name"), &SkeletonModification3DFABRIK::set_fabrik_joint_bone_name); - ClassDB::bind_method(D_METHOD("get_fabrik_joint_bone_index", "joint_idx"), &SkeletonModification3DFABRIK::get_fabrik_joint_bone_index); - ClassDB::bind_method(D_METHOD("set_fabrik_joint_bone_index", "joint_idx", "bone_index"), &SkeletonModification3DFABRIK::set_fabrik_joint_bone_index); - ClassDB::bind_method(D_METHOD("get_fabrik_joint_length", "joint_idx"), &SkeletonModification3DFABRIK::get_fabrik_joint_length); - ClassDB::bind_method(D_METHOD("set_fabrik_joint_length", "joint_idx", "length"), &SkeletonModification3DFABRIK::set_fabrik_joint_length); - ClassDB::bind_method(D_METHOD("get_fabrik_joint_magnet", "joint_idx"), &SkeletonModification3DFABRIK::get_fabrik_joint_magnet); - ClassDB::bind_method(D_METHOD("set_fabrik_joint_magnet", "joint_idx", "magnet_position"), &SkeletonModification3DFABRIK::set_fabrik_joint_magnet); - ClassDB::bind_method(D_METHOD("get_fabrik_joint_auto_calculate_length", "joint_idx"), &SkeletonModification3DFABRIK::get_fabrik_joint_auto_calculate_length); - ClassDB::bind_method(D_METHOD("set_fabrik_joint_auto_calculate_length", "joint_idx", "auto_calculate_length"), &SkeletonModification3DFABRIK::set_fabrik_joint_auto_calculate_length); - ClassDB::bind_method(D_METHOD("fabrik_joint_auto_calculate_length", "joint_idx"), &SkeletonModification3DFABRIK::fabrik_joint_auto_calculate_length); - ClassDB::bind_method(D_METHOD("get_fabrik_joint_use_tip_node", "joint_idx"), &SkeletonModification3DFABRIK::get_fabrik_joint_use_tip_node); - ClassDB::bind_method(D_METHOD("set_fabrik_joint_use_tip_node", "joint_idx", "use_tip_node"), &SkeletonModification3DFABRIK::set_fabrik_joint_use_tip_node); - ClassDB::bind_method(D_METHOD("get_fabrik_joint_tip_node", "joint_idx"), &SkeletonModification3DFABRIK::get_fabrik_joint_tip_node); - ClassDB::bind_method(D_METHOD("set_fabrik_joint_tip_node", "joint_idx", "tip_node"), &SkeletonModification3DFABRIK::set_fabrik_joint_tip_node); - ClassDB::bind_method(D_METHOD("get_fabrik_joint_use_target_basis", "joint_idx"), &SkeletonModification3DFABRIK::get_fabrik_joint_use_target_basis); - ClassDB::bind_method(D_METHOD("set_fabrik_joint_use_target_basis", "joint_idx", "use_target_basis"), &SkeletonModification3DFABRIK::set_fabrik_joint_use_target_basis); - - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node3D"), "set_target_node", "get_target_node"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "fabrik_data_chain_length", PROPERTY_HINT_RANGE, "0,100,1"), "set_fabrik_data_chain_length", "get_fabrik_data_chain_length"); + ClassDB::bind_method(D_METHOD("get_joint_bone", "joint_idx"), &SkeletonModification3DFABRIK::get_joint_bone); + ClassDB::bind_method(D_METHOD("set_joint_bone", "joint_idx", "bone_name"), &SkeletonModification3DFABRIK::set_joint_bone); + ClassDB::bind_method(D_METHOD("get_joint_length", "joint_idx"), &SkeletonModification3DFABRIK::get_joint_length); + ClassDB::bind_method(D_METHOD("set_joint_length", "joint_idx", "length"), &SkeletonModification3DFABRIK::set_joint_length); + ClassDB::bind_method(D_METHOD("get_joint_magnet", "joint_idx"), &SkeletonModification3DFABRIK::get_joint_magnet); + ClassDB::bind_method(D_METHOD("set_joint_magnet", "joint_idx", "magnet_position"), &SkeletonModification3DFABRIK::set_joint_magnet); + ClassDB::bind_method(D_METHOD("get_joint_auto_calculate_length", "joint_idx"), &SkeletonModification3DFABRIK::get_joint_auto_calculate_length); + ClassDB::bind_method(D_METHOD("set_joint_auto_calculate_length", "joint_idx", "auto_calculate_length"), &SkeletonModification3DFABRIK::set_joint_auto_calculate_length); + ClassDB::bind_method(D_METHOD("calculate_fabrik_joint_length", "joint_idx"), &SkeletonModification3DFABRIK::calculate_fabrik_joint_length); + ClassDB::bind_method(D_METHOD("get_joint_tip_node", "joint_idx"), &SkeletonModification3DFABRIK::get_joint_tip_node); + ClassDB::bind_method(D_METHOD("set_joint_tip_node", "joint_idx", "tip_node"), &SkeletonModification3DFABRIK::set_joint_tip_node); + ClassDB::bind_method(D_METHOD("get_joint_tip_bone", "joint_idx"), &SkeletonModification3DFABRIK::get_joint_tip_bone); + ClassDB::bind_method(D_METHOD("set_joint_tip_bone", "joint_idx", "tip_bone"), &SkeletonModification3DFABRIK::set_joint_tip_bone); + ClassDB::bind_method(D_METHOD("get_joint_use_target_basis", "joint_idx"), &SkeletonModification3DFABRIK::get_joint_use_target_basis); + ClassDB::bind_method(D_METHOD("set_joint_use_target_basis", "joint_idx", "use_target_basis"), &SkeletonModification3DFABRIK::set_joint_use_target_basis); + + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node3D"), "set_target_node", "get_target_node"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "target_bone", PROPERTY_HINT_NONE, ""), "set_target_bone", "get_target_bone"); + ADD_ARRAY_COUNT("FABRIK Joint Chain", "joint_count", "set_joint_count", "get_joint_count", "joint_"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "chain_tolerance", PROPERTY_HINT_RANGE, "0,100,0.001"), "set_chain_tolerance", "get_chain_tolerance"); ADD_PROPERTY(PropertyInfo(Variant::INT, "chain_max_iterations", PROPERTY_HINT_RANGE, "1,50,1"), "set_chain_max_iterations", "get_chain_max_iterations"); } SkeletonModification3DFABRIK::SkeletonModification3DFABRIK() { - is_setup = false; - enabled = true; } SkeletonModification3DFABRIK::~SkeletonModification3DFABRIK() { } -void SkeletonModification3DFABRIK::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - execution_error_found = false; - update_target_cache(); +void SkeletonModification3DFABRIK::execute(real_t p_delta) { + SkeletonModification3D::execute(p_delta); - for (uint32_t i = 0; i < fabrik_data_chain.size(); i++) { - update_joint_tip_cache(i); - } - set_process_internal(false); - set_physics_process_internal(false); - if (get_execution_mode() == 0) { - set_process_internal(true); - } else if (get_execution_mode() == 1) { - set_physics_process_internal(true); - } - skeleton = cast_to(get_node_or_null(get_skeleton_path())); - is_setup = true; - } break; - case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: - [[fallthrough]]; - case NOTIFICATION_INTERNAL_PROCESS: { - if (!is_setup || skeleton == nullptr) { - ERR_PRINT_ONCE("Modification is not setup and therefore cannot execute!"); - return; - } - if (!enabled) { - return; - } + Skeleton3D *skeleton = get_skeleton(); + if (!skeleton || !_cache_target(target_cache, target_node, target_bone)) { + WARN_PRINT_ONCE("FABRIK: Unable to resolve target"); + return; + } - if (target_node_cache.is_null()) { - _print_execution_error(true, "Target cache is out of date. Attempting to update..."); - update_target_cache(); - return; - } + // Make sure the transform cache is the correct size + if (fabrik_transforms.size() != fabrik_data_chain.size()) { + fabrik_transforms.resize(fabrik_data_chain.size()); + } - if (_print_execution_error(fabrik_data_chain.size() <= 1, "FABRIK requires at least two joints to operate. Cannot execute modification!")) { - return; - } + Transform3D origin_global_pose_trans; + int prev_bone_idx = -1; + Transform3D prev_transform; - Node3D *node_target = Object::cast_to(ObjectDB::get_instance(target_node_cache)); - if (_print_execution_error(!node_target || !node_target->is_inside_tree(), "Target node is not in the scene tree. Cannot execute modification!")) { - return; - } + // Verify that all joints have a valid bone ID, and that all bone lengths are zero or more + // Also, while we are here, apply magnet positions. + for (uint32_t i = 0; i < fabrik_data_chain.size(); i++) { + if (!_cache_bone(fabrik_data_chain[i].bone_idx, fabrik_data_chain[i].bone_name)) { + WARN_PRINT_ONCE("FABRIK: Failed to cache joint bone " + itos(i)); + return; + } - // Make sure the transform cache is the correct size - if (fabrik_transforms.size() != fabrik_data_chain.size()) { - fabrik_transforms.resize(fabrik_data_chain.size()); - } + if (fabrik_data_chain[i].length < 0 && fabrik_data_chain[i].auto_calculate_length) { + calculate_fabrik_joint_length(i); + } + int par_idx = skeleton->get_bone_parent(fabrik_data_chain[i].bone_idx); + Transform3D bone_transform = skeleton->get_bone_pose(fabrik_data_chain[i].bone_idx); + while (par_idx >= 0 && par_idx != prev_bone_idx) { + bone_transform = skeleton->get_bone_pose(par_idx) * bone_transform; + par_idx = skeleton->get_bone_parent(par_idx); + } + if (i != 0 && par_idx < 0) { + bone_transform = prev_transform * bone_transform; + } + prev_transform = bone_transform; + prev_bone_idx = fabrik_data_chain[i].bone_idx; - // Verify that all joints have a valid bone ID, and that all bone lengths are zero or more - // Also, while we are here, apply magnet positions. - for (uint32_t i = 0; i < fabrik_data_chain.size(); i++) { - if (_print_execution_error(fabrik_data_chain[i].bone_idx < 0, "FABRIK Joint " + itos(i) + " has an invalid bone ID. Cannot execute!")) { - return; - } - - if (fabrik_data_chain[i].length < 0 && fabrik_data_chain[i].auto_calculate_length) { - fabrik_joint_auto_calculate_length(i); - } - if (_print_execution_error(fabrik_data_chain[i].length < 0, "FABRIK Joint " + itos(i) + " has an invalid joint length. Cannot execute!")) { - return; - } - fabrik_transforms[i] = skeleton->get_bone_global_pose(fabrik_data_chain[i].bone_idx); - - // Apply magnet positions: - if (skeleton->get_bone_parent(fabrik_data_chain[i].bone_idx) >= 0) { - int parent_bone_idx = skeleton->get_bone_parent(fabrik_data_chain[i].bone_idx); - Transform3D conversion_transform = (skeleton->get_bone_global_pose(parent_bone_idx)); - fabrik_transforms[i].origin += conversion_transform.basis.xform_inv(fabrik_data_chain[i].magnet_position); - } else { - fabrik_transforms[i].origin += fabrik_data_chain[i].magnet_position; - } - } - Transform3D origin_global_pose_trans = skeleton->get_bone_global_pose(fabrik_data_chain[0].bone_idx); + ERR_FAIL_COND_MSG(fabrik_data_chain[i].length < 0, "FABRIK Joint " + itos(i) + " has an invalid joint length. Cannot execute!"); + fabrik_transforms[i] = bone_transform; + if (i == 0) { + origin_global_pose_trans = bone_transform; + } - target_global_pose = skeleton->world_transform_to_global_pose(node_target->get_global_transform()); - origin_global_pose = origin_global_pose_trans; + // Apply magnet positions: + if (skeleton->get_bone_parent(fabrik_data_chain[i].bone_idx) >= 0) { + // bone_transform is the global bone pose. + // global_pose * local_pose.inverse() gives the parent's pose. + Basis conversion_basis = bone_transform.get_basis() * skeleton->get_bone_pose(fabrik_data_chain[i].bone_idx).get_basis().inverse(); + fabrik_transforms[i].origin += conversion_basis.xform_inv(fabrik_data_chain[i].magnet_position); + } else { + fabrik_transforms[i].origin += fabrik_data_chain[i].magnet_position; + } + } - final_joint_idx = fabrik_data_chain.size() - 1; - real_t target_distance = fabrik_transforms[final_joint_idx].origin.distance_to(target_global_pose.origin); - chain_iterations = 0; + target_global_pose = get_target_transform(target_cache); + origin_global_pose = origin_global_pose_trans; - while (target_distance > chain_tolerance) { - chain_backwards(); - chain_forwards(); + final_joint_idx = fabrik_data_chain.size() - 1; + real_t target_distance = fabrik_transforms[final_joint_idx].origin.distance_to(target_global_pose.origin); + chain_iterations = 0; - // update the target distance - target_distance = fabrik_transforms[final_joint_idx].origin.distance_to(target_global_pose.origin); + while (target_distance > chain_tolerance) { + chain_backwards(); + chain_forwards(); - // update chain iterations - chain_iterations += 1; - if (chain_iterations >= chain_max_iterations) { - break; - } - } - chain_apply(); + // update the target distance + target_distance = fabrik_transforms[final_joint_idx].origin.distance_to(target_global_pose.origin); + + // update chain iterations + chain_iterations += 1; + if (chain_iterations >= chain_max_iterations) { + break; + } + } + chain_apply(); +} + +void SkeletonModification3DFABRIK::skeleton_changed(Skeleton3D *skeleton) { + target_cache = Variant(); + for (uint32_t i = 0; i < fabrik_data_chain.size(); i++) { + fabrik_data_chain[i].bone_idx = UNCACHED_BONE_IDX; + fabrik_data_chain[i].tip_cache = Variant(); + } + SkeletonModification3D::skeleton_changed(skeleton); +} + +bool SkeletonModification3DFABRIK::is_bone_property(String p_property_name) const { + if (p_property_name == "target_bone" || p_property_name.ends_with("/tip_bone") || p_property_name.ends_with("/bone")) { + return true; + } + return SkeletonModification3D::is_bone_property(p_property_name); +} + +bool SkeletonModification3DFABRIK::is_property_hidden(String p_property_name) const { + if (p_property_name.begins_with("joint_") && p_property_name.ends_with("/tip_bone")) { + const int fabrik_data_size = fabrik_data_chain.size(); + int which = p_property_name.get_slicec('/', 0).substr(6).to_int(); + ERR_FAIL_INDEX_V(which, fabrik_data_size, false); + if (!fabrik_data_chain[which].tip_node.is_empty()) { + return true; + } + } + if (p_property_name == "target_bone" && !target_node.is_empty()) { + return true; + } + return SkeletonModification3D::is_property_hidden(p_property_name); +} - execution_error_found = false; - } break; +TypedArray SkeletonModification3DFABRIK::get_configuration_warnings() const { + TypedArray ret = SkeletonModification3D::get_configuration_warnings(); + if (!get_skeleton()) { + return ret; + } + if (!_cache_target(target_cache, target_node, target_bone)) { + ret.append(vformat("Target %s %s was not found.", target_node.is_empty() ? "bone" : "node", target_node.is_empty() ? target_bone : (String)target_node)); + } + for (uint32_t i = 0; i < fabrik_data_chain.size(); i++) { + if (!_cache_bone(fabrik_data_chain[i].bone_idx, fabrik_data_chain[i].bone_name)) { + ret.append(vformat("Joint %d bone %s was not found.", i, fabrik_data_chain[i].bone_name)); + } + if (!_cache_target(fabrik_data_chain[i].tip_cache, fabrik_data_chain[i].tip_node, fabrik_data_chain[i].tip_bone)) { + ret.append(vformat("Joint %d tip %s %s was not found.", i, fabrik_data_chain[i].tip_node.is_empty() ? "bone" : "node", fabrik_data_chain[i].tip_node.is_empty() ? fabrik_data_chain[i].tip_bone : (String)fabrik_data_chain[i].tip_node)); + } } + return ret; } diff --git a/scene/3d/skeleton_modification_3d_fabrik.h b/scene/3d/skeleton_modification_3d_fabrik.h index 3105018a79b6..da6c9a586197 100644 --- a/scene/3d/skeleton_modification_3d_fabrik.h +++ b/scene/3d/skeleton_modification_3d_fabrik.h @@ -40,15 +40,15 @@ class SkeletonModification3DFABRIK : public SkeletonModification3D { private: struct FABRIK_Joint_Data { - String bone_name = ""; - int bone_idx = -1; + String bone_name; + mutable int bone_idx = UNCACHED_BONE_IDX; real_t length = -1; Vector3 magnet_position = Vector3(0, 0, 0); bool auto_calculate_length = true; - bool use_tip_node = false; - NodePath tip_node = NodePath(); - ObjectID tip_node_cache; + NodePath tip_node; + String tip_bone; + mutable Variant tip_cache; bool use_target_basis = false; real_t roll = 0; @@ -58,15 +58,13 @@ class SkeletonModification3DFABRIK : public SkeletonModification3D { LocalVector fabrik_transforms; NodePath target_node; - ObjectID target_node_cache; + String target_bone; + mutable Variant target_cache; real_t chain_tolerance = 0.01; int chain_max_iterations = 10; int chain_iterations = 0; - void update_target_cache(); - void update_joint_tip_cache(int p_joint_idx); - int final_joint_idx = 0; Transform3D target_global_pose = Transform3D(); Transform3D origin_global_pose = Transform3D(); @@ -80,14 +78,20 @@ class SkeletonModification3DFABRIK : public SkeletonModification3D { bool _get(const StringName &p_path, Variant &r_ret) const; bool _set(const StringName &p_path, const Variant &p_value); void _get_property_list(List *p_list) const; + void skeleton_changed(Skeleton3D *skeleton) override; + void execute(real_t p_delta) override; + bool is_property_hidden(String property_name) const override; + bool is_bone_property(String property_name) const override; + TypedArray get_configuration_warnings() const override; public: - void _notification(int p_what); void set_target_node(const NodePath &p_target_node); NodePath get_target_node() const; + void set_target_bone(const String &p_target_node); + String get_target_bone() const; - int get_fabrik_data_chain_length(); - void set_fabrik_data_chain_length(int p_new_length); + int get_joint_count(); + void set_joint_count(int p_new_length); real_t get_chain_tolerance(); void set_chain_tolerance(real_t p_tolerance); @@ -95,25 +99,23 @@ class SkeletonModification3DFABRIK : public SkeletonModification3D { int get_chain_max_iterations(); void set_chain_max_iterations(int p_iterations); - String get_fabrik_joint_bone_name(int p_joint_idx) const; - void set_fabrik_joint_bone_name(int p_joint_idx, String p_bone_name); - int get_fabrik_joint_bone_index(int p_joint_idx) const; - void set_fabrik_joint_bone_index(int p_joint_idx, int p_bone_idx); - real_t get_fabrik_joint_length(int p_joint_idx) const; - void set_fabrik_joint_length(int p_joint_idx, real_t p_bone_length); - Vector3 get_fabrik_joint_magnet(int p_joint_idx) const; - void set_fabrik_joint_magnet(int p_joint_idx, Vector3 p_magnet); - bool get_fabrik_joint_auto_calculate_length(int p_joint_idx) const; - void set_fabrik_joint_auto_calculate_length(int p_joint_idx, bool p_auto_calculate); - void fabrik_joint_auto_calculate_length(int p_joint_idx); - bool get_fabrik_joint_use_tip_node(int p_joint_idx) const; - void set_fabrik_joint_use_tip_node(int p_joint_idx, bool p_use_tip_node); - NodePath get_fabrik_joint_tip_node(int p_joint_idx) const; - void set_fabrik_joint_tip_node(int p_joint_idx, NodePath p_tip_node); - bool get_fabrik_joint_use_target_basis(int p_joint_idx) const; - void set_fabrik_joint_use_target_basis(int p_joint_idx, bool p_use_basis); - real_t get_fabrik_joint_roll(int p_joint_idx) const; - void set_fabrik_joint_roll(int p_joint_idx, real_t p_roll); + String get_joint_bone(int p_joint_idx) const; + void set_joint_bone(int p_joint_idx, String p_bone_name); + real_t get_joint_length(int p_joint_idx) const; + void set_joint_length(int p_joint_idx, real_t p_bone_length); + Vector3 get_joint_magnet(int p_joint_idx) const; + void set_joint_magnet(int p_joint_idx, Vector3 p_magnet); + bool get_joint_auto_calculate_length(int p_joint_idx) const; + void set_joint_auto_calculate_length(int p_joint_idx, bool p_auto_calculate); + void calculate_fabrik_joint_length(int p_joint_idx); + NodePath get_joint_tip_node(int p_joint_idx) const; + void set_joint_tip_node(int p_joint_idx, NodePath p_tip_node); + String get_joint_tip_bone(int p_joint_idx) const; + void set_joint_tip_bone(int p_joint_idx, String p_tip_bone); + bool get_joint_use_target_basis(int p_joint_idx) const; + void set_joint_use_target_basis(int p_joint_idx, bool p_use_basis); + real_t get_joint_roll(int p_joint_idx) const; + void set_joint_roll(int p_joint_idx, real_t p_roll); SkeletonModification3DFABRIK(); ~SkeletonModification3DFABRIK(); diff --git a/scene/3d/skeleton_modification_3d_jiggle.cpp b/scene/3d/skeleton_modification_3d_jiggle.cpp index 50aacb0e7151..69925b38b828 100644 --- a/scene/3d/skeleton_modification_3d_jiggle.cpp +++ b/scene/3d/skeleton_modification_3d_jiggle.cpp @@ -35,30 +35,28 @@ bool SkeletonModification3DJiggle::_set(const StringName &p_path, const Variant &p_value) { String path = p_path; - if (path.begins_with("joint_data/")) { + if (path.begins_with("joint_")) { const int jiggle_size = jiggle_data_chain.size(); - int which = path.get_slicec('/', 1).to_int(); - String what = path.get_slicec('/', 2); + int which = path.get_slicec('/', 0).substr(6).to_int(); + String what = path.get_slicec('/', 1); ERR_FAIL_INDEX_V(which, jiggle_size, false); - if (what == "bone_name") { - set_jiggle_joint_bone_name(which, p_value); - } else if (what == "bone_index") { - set_jiggle_joint_bone_index(which, p_value); + if (what == "bone") { + set_joint_bone(which, p_value); } else if (what == "override_defaults") { - set_jiggle_joint_override(which, p_value); + set_joint_override(which, p_value); } else if (what == "stiffness") { - set_jiggle_joint_stiffness(which, p_value); + set_joint_stiffness(which, p_value); } else if (what == "mass") { - set_jiggle_joint_mass(which, p_value); + set_joint_mass(which, p_value); } else if (what == "damping") { - set_jiggle_joint_damping(which, p_value); + set_joint_damping(which, p_value); } else if (what == "use_gravity") { - set_jiggle_joint_use_gravity(which, p_value); + set_joint_use_gravity(which, p_value); } else if (what == "gravity") { - set_jiggle_joint_gravity(which, p_value); + set_joint_gravity(which, p_value); } else if (what == "roll") { - set_jiggle_joint_roll(which, Math::deg_to_rad(real_t(p_value))); + set_joint_roll(which, Math::deg_to_rad(real_t(p_value))); } return true; } else { @@ -75,30 +73,28 @@ bool SkeletonModification3DJiggle::_set(const StringName &p_path, const Variant bool SkeletonModification3DJiggle::_get(const StringName &p_path, Variant &r_ret) const { String path = p_path; - if (path.begins_with("joint_data/")) { + if (path.begins_with("joint_")) { const int jiggle_size = jiggle_data_chain.size(); - int which = path.get_slicec('/', 1).to_int(); - String what = path.get_slicec('/', 2); + int which = path.get_slicec('/', 0).substr(6).to_int(); + String what = path.get_slicec('/', 1); ERR_FAIL_INDEX_V(which, jiggle_size, false); - if (what == "bone_name") { - r_ret = get_jiggle_joint_bone_name(which); - } else if (what == "bone_index") { - r_ret = get_jiggle_joint_bone_index(which); + if (what == "bone") { + r_ret = get_joint_bone(which); } else if (what == "override_defaults") { - r_ret = get_jiggle_joint_override(which); + r_ret = get_joint_override(which); } else if (what == "stiffness") { - r_ret = get_jiggle_joint_stiffness(which); + r_ret = get_joint_stiffness(which); } else if (what == "mass") { - r_ret = get_jiggle_joint_mass(which); + r_ret = get_joint_mass(which); } else if (what == "damping") { - r_ret = get_jiggle_joint_damping(which); + r_ret = get_joint_damping(which); } else if (what == "use_gravity") { - r_ret = get_jiggle_joint_use_gravity(which); + r_ret = get_joint_use_gravity(which); } else if (what == "gravity") { - r_ret = get_jiggle_joint_gravity(which); + r_ret = get_joint_gravity(which); } else if (what == "roll") { - r_ret = Math::rad_to_deg(get_jiggle_joint_roll(which)); + r_ret = Math::rad_to_deg(get_joint_roll(which)); } return true; } else { @@ -119,10 +115,9 @@ void SkeletonModification3DJiggle::_get_property_list(List *p_list } for (uint32_t i = 0; i < jiggle_data_chain.size(); i++) { - String base_string = "joint_data/" + itos(i) + "/"; + String base_string = "joint_" + itos(i) + "/"; - p_list->push_back(PropertyInfo(Variant::STRING_NAME, base_string + "bone_name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - p_list->push_back(PropertyInfo(Variant::INT, base_string + "bone_index", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::STRING_NAME, base_string + "bone", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "roll", PROPERTY_HINT_RANGE, "-360,360,0.01", PROPERTY_USAGE_DEFAULT)); p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "override_defaults", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); @@ -138,20 +133,16 @@ void SkeletonModification3DJiggle::_get_property_list(List *p_list } } -void SkeletonModification3DJiggle::_execute_jiggle_joint(int p_joint_idx, Node3D *p_target, real_t p_delta) { +void SkeletonModification3DJiggle::_execute_jiggle_joint(int p_joint_idx, Transform3D parent_transform, Vector3 target_position, real_t p_delta) { // Adopted from: https://wiki.unity3d.com/index.php/JiggleBone // With modifications by TwistedTwigleg. - if (jiggle_data_chain[p_joint_idx].bone_idx <= -2) { - jiggle_data_chain[p_joint_idx].bone_idx = skeleton->find_bone(jiggle_data_chain[p_joint_idx].bone_name); - } - if (_print_execution_error( - jiggle_data_chain[p_joint_idx].bone_idx < 0 || jiggle_data_chain[p_joint_idx].bone_idx > skeleton->get_bone_count(), - "Jiggle joint " + itos(p_joint_idx) + " bone index is invalid. Cannot execute modification!")) { + Skeleton3D *skeleton = get_skeleton(); + if (!skeleton || !_cache_bone(jiggle_data_chain[p_joint_idx].bone_idx, jiggle_data_chain[p_joint_idx].bone_name)) { + WARN_PRINT_ONCE(String("Jiggle joint ") + itos(p_joint_idx) + " bone index is invalid. Cannot execute modification!"); return; } - Transform3D new_bone_trans = skeleton->get_bone_global_pose(jiggle_data_chain[p_joint_idx].bone_idx); - Vector3 target_position = skeleton->world_transform_to_global_pose(p_target->get_global_transform()).origin; + Transform3D new_bone_trans = parent_transform * skeleton->get_bone_pose(jiggle_data_chain[p_joint_idx].bone_idx); jiggle_data_chain[p_joint_idx].force = (target_position - jiggle_data_chain[p_joint_idx].dynamic_position) * jiggle_data_chain[p_joint_idx].stiffness * p_delta; @@ -176,8 +167,8 @@ void SkeletonModification3DJiggle::_execute_jiggle_joint(int p_joint_idx, Node3D PhysicsDirectSpaceState3D::RayResult ray_result; // Convert to world transforms, which is what the physics server needs - Transform3D new_bone_trans_world = skeleton->global_pose_to_world_transform(new_bone_trans); - Transform3D dynamic_position_world = skeleton->global_pose_to_world_transform(Transform3D(Basis(), jiggle_data_chain[p_joint_idx].dynamic_position)); + Transform3D new_bone_trans_world = skeleton->get_global_transform() * new_bone_trans; + Transform3D dynamic_position_world = skeleton->get_global_transform() * Transform3D(Basis(), jiggle_data_chain[p_joint_idx].dynamic_position); PhysicsDirectSpaceState3D::RayParameters ray_params; ray_params.from = new_bone_trans_world.origin; @@ -200,8 +191,7 @@ void SkeletonModification3DJiggle::_execute_jiggle_joint(int p_joint_idx, Node3D } // Get the forward direction that the basis is facing in right now. - skeleton->update_bone_rest_forward_vector(jiggle_data_chain[p_joint_idx].bone_idx); - Vector3 forward_vector = skeleton->get_bone_axis_forward_vector(jiggle_data_chain[p_joint_idx].bone_idx); + Vector3 forward_vector = get_bone_rest_forward_vector(jiggle_data_chain[p_joint_idx].bone_idx); // Rotate the bone using the dynamic position! new_bone_trans.basis.rotate_to_align(forward_vector, new_bone_trans.origin.direction_to(jiggle_data_chain[p_joint_idx].dynamic_position)); @@ -209,55 +199,46 @@ void SkeletonModification3DJiggle::_execute_jiggle_joint(int p_joint_idx, Node3D // Roll new_bone_trans.basis.rotate_local(forward_vector, jiggle_data_chain[p_joint_idx].roll); - new_bone_trans = skeleton->global_pose_to_local_pose(jiggle_data_chain[p_joint_idx].bone_idx, new_bone_trans); - skeleton->set_bone_pose_position(jiggle_data_chain[p_joint_idx].bone_idx, new_bone_trans.origin); + new_bone_trans = parent_transform.affine_inverse() * new_bone_trans; skeleton->set_bone_pose_rotation(jiggle_data_chain[p_joint_idx].bone_idx, new_bone_trans.basis.get_rotation_quaternion()); - skeleton->set_bone_pose_scale(jiggle_data_chain[p_joint_idx].bone_idx, new_bone_trans.basis.get_scale()); - skeleton->force_update_bone_children_transforms(jiggle_data_chain[p_joint_idx].bone_idx); } void SkeletonModification3DJiggle::_update_jiggle_joint_data() { for (uint32_t i = 0; i < jiggle_data_chain.size(); i++) { if (!jiggle_data_chain[i].override_defaults) { - set_jiggle_joint_stiffness(i, stiffness); - set_jiggle_joint_mass(i, mass); - set_jiggle_joint_damping(i, damping); - set_jiggle_joint_use_gravity(i, use_gravity); - set_jiggle_joint_gravity(i, gravity); - } - } -} - -void SkeletonModification3DJiggle::update_cache() { - if (!is_setup) { - _print_execution_error(true, "Cannot update target cache: modification is not properly setup!"); - return; - } - - target_node_cache = ObjectID(); - if (is_inside_tree()) { - if (has_node(target_node)) { - Node *node = get_node_or_null(target_node); - ERR_FAIL_COND_MSG(!node || skeleton == node, - "Cannot update target cache: node is this modification's skeleton or cannot be found!"); - ERR_FAIL_COND_MSG(!node->is_inside_tree(), - "Cannot update target cache: node is not in the scene tree!"); - target_node_cache = node->get_instance_id(); - - execution_error_found = false; + set_joint_stiffness(i, stiffness); + set_joint_mass(i, mass); + set_joint_damping(i, damping); + set_joint_use_gravity(i, use_gravity); + set_joint_gravity(i, gravity); } } } void SkeletonModification3DJiggle::set_target_node(const NodePath &p_target_node) { target_node = p_target_node; - update_cache(); + if (!target_node.is_empty()) { + target_bone = String(); + } + target_cache = Variant(); } NodePath SkeletonModification3DJiggle::get_target_node() const { return target_node; } +void SkeletonModification3DJiggle::set_target_bone(const String &p_target_bone) { + target_bone = p_target_bone; + if (!target_bone.is_empty()) { + target_node = NodePath(); + } + target_cache = Variant(); +} + +String SkeletonModification3DJiggle::get_target_bone() const { + return target_bone; +} + void SkeletonModification3DJiggle::set_stiffness(real_t p_stiffness) { ERR_FAIL_COND_MSG(p_stiffness < 0, "Stiffness cannot be set to a negative value!"); stiffness = p_stiffness; @@ -325,55 +306,31 @@ int SkeletonModification3DJiggle::get_collision_mask() const { } // Jiggle joint data functions -int SkeletonModification3DJiggle::get_jiggle_data_chain_length() { +int SkeletonModification3DJiggle::get_joint_count() { return jiggle_data_chain.size(); } -void SkeletonModification3DJiggle::set_jiggle_data_chain_length(int p_length) { +void SkeletonModification3DJiggle::set_joint_count(int p_length) { ERR_FAIL_COND(p_length < 0); jiggle_data_chain.resize(p_length); - execution_error_found = false; notify_property_list_changed(); } -void SkeletonModification3DJiggle::set_jiggle_joint_bone_name(int p_joint_idx, String p_name) { +void SkeletonModification3DJiggle::set_joint_bone(int p_joint_idx, String p_name) { const int bone_chain_size = jiggle_data_chain.size(); ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); jiggle_data_chain[p_joint_idx].bone_name = p_name; - if (skeleton) { - jiggle_data_chain[p_joint_idx].bone_idx = skeleton->find_bone(p_name); - } - execution_error_found = false; - notify_property_list_changed(); + jiggle_data_chain[p_joint_idx].bone_idx = UNCACHED_BONE_IDX; } -String SkeletonModification3DJiggle::get_jiggle_joint_bone_name(int p_joint_idx) const { +String SkeletonModification3DJiggle::get_joint_bone(int p_joint_idx) const { const int bone_chain_size = jiggle_data_chain.size(); ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, ""); return jiggle_data_chain[p_joint_idx].bone_name; } -int SkeletonModification3DJiggle::get_jiggle_joint_bone_index(int p_joint_idx) const { - const int bone_chain_size = jiggle_data_chain.size(); - ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, -1); - return jiggle_data_chain[p_joint_idx].bone_idx; -} - -void SkeletonModification3DJiggle::set_jiggle_joint_bone_index(int p_joint_idx, int p_bone_idx) { - const int bone_chain_size = jiggle_data_chain.size(); - ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); - ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!"); - jiggle_data_chain[p_joint_idx].bone_idx = p_bone_idx; - - if (skeleton) { - jiggle_data_chain[p_joint_idx].bone_name = skeleton->get_bone_name(p_bone_idx); - } - execution_error_found = false; - notify_property_list_changed(); -} - -void SkeletonModification3DJiggle::set_jiggle_joint_override(int p_joint_idx, bool p_override) { +void SkeletonModification3DJiggle::set_joint_override(int p_joint_idx, bool p_override) { const int bone_chain_size = jiggle_data_chain.size(); ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); jiggle_data_chain[p_joint_idx].override_defaults = p_override; @@ -381,83 +338,83 @@ void SkeletonModification3DJiggle::set_jiggle_joint_override(int p_joint_idx, bo notify_property_list_changed(); } -bool SkeletonModification3DJiggle::get_jiggle_joint_override(int p_joint_idx) const { +bool SkeletonModification3DJiggle::get_joint_override(int p_joint_idx) const { const int bone_chain_size = jiggle_data_chain.size(); ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, false); return jiggle_data_chain[p_joint_idx].override_defaults; } -void SkeletonModification3DJiggle::set_jiggle_joint_stiffness(int p_joint_idx, real_t p_stiffness) { +void SkeletonModification3DJiggle::set_joint_stiffness(int p_joint_idx, real_t p_stiffness) { const int bone_chain_size = jiggle_data_chain.size(); ERR_FAIL_COND_MSG(p_stiffness < 0, "Stiffness cannot be set to a negative value!"); ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); jiggle_data_chain[p_joint_idx].stiffness = p_stiffness; } -real_t SkeletonModification3DJiggle::get_jiggle_joint_stiffness(int p_joint_idx) const { +real_t SkeletonModification3DJiggle::get_joint_stiffness(int p_joint_idx) const { const int bone_chain_size = jiggle_data_chain.size(); ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, -1); return jiggle_data_chain[p_joint_idx].stiffness; } -void SkeletonModification3DJiggle::set_jiggle_joint_mass(int p_joint_idx, real_t p_mass) { +void SkeletonModification3DJiggle::set_joint_mass(int p_joint_idx, real_t p_mass) { const int bone_chain_size = jiggle_data_chain.size(); ERR_FAIL_COND_MSG(p_mass < 0, "Mass cannot be set to a negative value!"); ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); jiggle_data_chain[p_joint_idx].mass = p_mass; } -real_t SkeletonModification3DJiggle::get_jiggle_joint_mass(int p_joint_idx) const { +real_t SkeletonModification3DJiggle::get_joint_mass(int p_joint_idx) const { const int bone_chain_size = jiggle_data_chain.size(); ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, -1); return jiggle_data_chain[p_joint_idx].mass; } -void SkeletonModification3DJiggle::set_jiggle_joint_damping(int p_joint_idx, real_t p_damping) { +void SkeletonModification3DJiggle::set_joint_damping(int p_joint_idx, real_t p_damping) { const int bone_chain_size = jiggle_data_chain.size(); ERR_FAIL_COND_MSG(p_damping < 0, "Damping cannot be set to a negative value!"); ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); jiggle_data_chain[p_joint_idx].damping = p_damping; } -real_t SkeletonModification3DJiggle::get_jiggle_joint_damping(int p_joint_idx) const { +real_t SkeletonModification3DJiggle::get_joint_damping(int p_joint_idx) const { const int bone_chain_size = jiggle_data_chain.size(); ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, -1); return jiggle_data_chain[p_joint_idx].damping; } -void SkeletonModification3DJiggle::set_jiggle_joint_use_gravity(int p_joint_idx, bool p_use_gravity) { +void SkeletonModification3DJiggle::set_joint_use_gravity(int p_joint_idx, bool p_use_gravity) { const int bone_chain_size = jiggle_data_chain.size(); ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); jiggle_data_chain[p_joint_idx].use_gravity = p_use_gravity; notify_property_list_changed(); } -bool SkeletonModification3DJiggle::get_jiggle_joint_use_gravity(int p_joint_idx) const { +bool SkeletonModification3DJiggle::get_joint_use_gravity(int p_joint_idx) const { const int bone_chain_size = jiggle_data_chain.size(); ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, false); return jiggle_data_chain[p_joint_idx].use_gravity; } -void SkeletonModification3DJiggle::set_jiggle_joint_gravity(int p_joint_idx, Vector3 p_gravity) { +void SkeletonModification3DJiggle::set_joint_gravity(int p_joint_idx, Vector3 p_gravity) { const int bone_chain_size = jiggle_data_chain.size(); ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); jiggle_data_chain[p_joint_idx].gravity = p_gravity; } -Vector3 SkeletonModification3DJiggle::get_jiggle_joint_gravity(int p_joint_idx) const { +Vector3 SkeletonModification3DJiggle::get_joint_gravity(int p_joint_idx) const { const int bone_chain_size = jiggle_data_chain.size(); ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, Vector3(0, 0, 0)); return jiggle_data_chain[p_joint_idx].gravity; } -void SkeletonModification3DJiggle::set_jiggle_joint_roll(int p_joint_idx, real_t p_roll) { +void SkeletonModification3DJiggle::set_joint_roll(int p_joint_idx, real_t p_roll) { const int bone_chain_size = jiggle_data_chain.size(); ERR_FAIL_INDEX(p_joint_idx, bone_chain_size); jiggle_data_chain[p_joint_idx].roll = p_roll; } -real_t SkeletonModification3DJiggle::get_jiggle_joint_roll(int p_joint_idx) const { +real_t SkeletonModification3DJiggle::get_joint_roll(int p_joint_idx) const { const int bone_chain_size = jiggle_data_chain.size(); ERR_FAIL_INDEX_V(p_joint_idx, bone_chain_size, 0.0); return jiggle_data_chain[p_joint_idx].roll; @@ -466,9 +423,11 @@ real_t SkeletonModification3DJiggle::get_jiggle_joint_roll(int p_joint_idx) cons void SkeletonModification3DJiggle::_bind_methods() { ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification3DJiggle::set_target_node); ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification3DJiggle::get_target_node); + ClassDB::bind_method(D_METHOD("set_target_bone", "target_bone_name"), &SkeletonModification3DJiggle::set_target_bone); + ClassDB::bind_method(D_METHOD("get_target_bone"), &SkeletonModification3DJiggle::get_target_bone); - ClassDB::bind_method(D_METHOD("set_jiggle_data_chain_length", "length"), &SkeletonModification3DJiggle::set_jiggle_data_chain_length); - ClassDB::bind_method(D_METHOD("get_jiggle_data_chain_length"), &SkeletonModification3DJiggle::get_jiggle_data_chain_length); + ClassDB::bind_method(D_METHOD("set_joint_count", "length"), &SkeletonModification3DJiggle::set_joint_count); + ClassDB::bind_method(D_METHOD("get_joint_count"), &SkeletonModification3DJiggle::get_joint_count); ClassDB::bind_method(D_METHOD("set_stiffness", "stiffness"), &SkeletonModification3DJiggle::set_stiffness); ClassDB::bind_method(D_METHOD("get_stiffness"), &SkeletonModification3DJiggle::get_stiffness); @@ -487,27 +446,25 @@ void SkeletonModification3DJiggle::_bind_methods() { ClassDB::bind_method(D_METHOD("get_collision_mask"), &SkeletonModification3DJiggle::get_collision_mask); // Jiggle joint data functions - ClassDB::bind_method(D_METHOD("set_jiggle_joint_bone_name", "joint_idx", "name"), &SkeletonModification3DJiggle::set_jiggle_joint_bone_name); - ClassDB::bind_method(D_METHOD("get_jiggle_joint_bone_name", "joint_idx"), &SkeletonModification3DJiggle::get_jiggle_joint_bone_name); - ClassDB::bind_method(D_METHOD("set_jiggle_joint_bone_index", "joint_idx", "bone_idx"), &SkeletonModification3DJiggle::set_jiggle_joint_bone_index); - ClassDB::bind_method(D_METHOD("get_jiggle_joint_bone_index", "joint_idx"), &SkeletonModification3DJiggle::get_jiggle_joint_bone_index); - ClassDB::bind_method(D_METHOD("set_jiggle_joint_override", "joint_idx", "override"), &SkeletonModification3DJiggle::set_jiggle_joint_override); - ClassDB::bind_method(D_METHOD("get_jiggle_joint_override", "joint_idx"), &SkeletonModification3DJiggle::get_jiggle_joint_override); - ClassDB::bind_method(D_METHOD("set_jiggle_joint_stiffness", "joint_idx", "stiffness"), &SkeletonModification3DJiggle::set_jiggle_joint_stiffness); - ClassDB::bind_method(D_METHOD("get_jiggle_joint_stiffness", "joint_idx"), &SkeletonModification3DJiggle::get_jiggle_joint_stiffness); - ClassDB::bind_method(D_METHOD("set_jiggle_joint_mass", "joint_idx", "mass"), &SkeletonModification3DJiggle::set_jiggle_joint_mass); - ClassDB::bind_method(D_METHOD("get_jiggle_joint_mass", "joint_idx"), &SkeletonModification3DJiggle::get_jiggle_joint_mass); - ClassDB::bind_method(D_METHOD("set_jiggle_joint_damping", "joint_idx", "damping"), &SkeletonModification3DJiggle::set_jiggle_joint_damping); - ClassDB::bind_method(D_METHOD("get_jiggle_joint_damping", "joint_idx"), &SkeletonModification3DJiggle::get_jiggle_joint_damping); - ClassDB::bind_method(D_METHOD("set_jiggle_joint_use_gravity", "joint_idx", "use_gravity"), &SkeletonModification3DJiggle::set_jiggle_joint_use_gravity); - ClassDB::bind_method(D_METHOD("get_jiggle_joint_use_gravity", "joint_idx"), &SkeletonModification3DJiggle::get_jiggle_joint_use_gravity); - ClassDB::bind_method(D_METHOD("set_jiggle_joint_gravity", "joint_idx", "gravity"), &SkeletonModification3DJiggle::set_jiggle_joint_gravity); - ClassDB::bind_method(D_METHOD("get_jiggle_joint_gravity", "joint_idx"), &SkeletonModification3DJiggle::get_jiggle_joint_gravity); - ClassDB::bind_method(D_METHOD("set_jiggle_joint_roll", "joint_idx", "roll"), &SkeletonModification3DJiggle::set_jiggle_joint_roll); - ClassDB::bind_method(D_METHOD("get_jiggle_joint_roll", "joint_idx"), &SkeletonModification3DJiggle::get_jiggle_joint_roll); - - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node3D"), "set_target_node", "get_target_node"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "jiggle_data_chain_length", PROPERTY_HINT_RANGE, "0,100,1"), "set_jiggle_data_chain_length", "get_jiggle_data_chain_length"); + ClassDB::bind_method(D_METHOD("set_joint_bone", "joint_idx", "name"), &SkeletonModification3DJiggle::set_joint_bone); + ClassDB::bind_method(D_METHOD("get_joint_bone", "joint_idx"), &SkeletonModification3DJiggle::get_joint_bone); + ClassDB::bind_method(D_METHOD("set_joint_override", "joint_idx", "override"), &SkeletonModification3DJiggle::set_joint_override); + ClassDB::bind_method(D_METHOD("get_joint_override", "joint_idx"), &SkeletonModification3DJiggle::get_joint_override); + ClassDB::bind_method(D_METHOD("set_joint_stiffness", "joint_idx", "stiffness"), &SkeletonModification3DJiggle::set_joint_stiffness); + ClassDB::bind_method(D_METHOD("get_joint_stiffness", "joint_idx"), &SkeletonModification3DJiggle::get_joint_stiffness); + ClassDB::bind_method(D_METHOD("set_joint_mass", "joint_idx", "mass"), &SkeletonModification3DJiggle::set_joint_mass); + ClassDB::bind_method(D_METHOD("get_joint_mass", "joint_idx"), &SkeletonModification3DJiggle::get_joint_mass); + ClassDB::bind_method(D_METHOD("set_joint_damping", "joint_idx", "damping"), &SkeletonModification3DJiggle::set_joint_damping); + ClassDB::bind_method(D_METHOD("get_joint_damping", "joint_idx"), &SkeletonModification3DJiggle::get_joint_damping); + ClassDB::bind_method(D_METHOD("set_joint_use_gravity", "joint_idx", "use_gravity"), &SkeletonModification3DJiggle::set_joint_use_gravity); + ClassDB::bind_method(D_METHOD("get_joint_use_gravity", "joint_idx"), &SkeletonModification3DJiggle::get_joint_use_gravity); + ClassDB::bind_method(D_METHOD("set_joint_gravity", "joint_idx", "gravity"), &SkeletonModification3DJiggle::set_joint_gravity); + ClassDB::bind_method(D_METHOD("get_joint_gravity", "joint_idx"), &SkeletonModification3DJiggle::get_joint_gravity); + ClassDB::bind_method(D_METHOD("set_joint_roll", "joint_idx", "roll"), &SkeletonModification3DJiggle::set_joint_roll); + ClassDB::bind_method(D_METHOD("get_joint_roll", "joint_idx"), &SkeletonModification3DJiggle::get_joint_roll); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "target_bone"), "set_target_bone", "get_target_bone"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node3D"), "set_target_node", "get_target_node"); ADD_GROUP("Default Joint Settings", ""); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "stiffness"), "set_stiffness", "get_stiffness"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass"), "set_mass", "get_mass"); @@ -515,74 +472,101 @@ void SkeletonModification3DJiggle::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_gravity"), "set_use_gravity", "get_use_gravity"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "gravity"), "set_gravity", "get_gravity"); ADD_GROUP("", ""); + ADD_ARRAY_COUNT("Jiggle Chain Joint Count", "joint_count", "set_joint_count", "get_joint_count", "joint_"); } SkeletonModification3DJiggle::SkeletonModification3DJiggle() { - is_setup = false; jiggle_data_chain = Vector(); stiffness = 3; mass = 0.75; damping = 0.75; use_gravity = false; gravity = Vector3(0, -6.0, 0); - enabled = true; } SkeletonModification3DJiggle::~SkeletonModification3DJiggle() { } -void SkeletonModification3DJiggle::_notification(int32_t p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - execution_error_found = false; - skeleton = cast_to(get_node_or_null(get_skeleton_path())); - if (skeleton) { - for (uint32_t i = 0; i < jiggle_data_chain.size(); i++) { - int bone_idx = jiggle_data_chain[i].bone_idx; - if (bone_idx > 0 && bone_idx < skeleton->get_bone_count()) { - jiggle_data_chain[i].dynamic_position = skeleton->local_pose_to_global_pose(bone_idx, skeleton->get_bone_local_pose_override(bone_idx)).origin; - } - } - } +void SkeletonModification3DJiggle::execute(real_t delta) { + SkeletonModification3D::execute(delta); - update_cache(); - set_process_internal(false); - set_physics_process_internal(false); - if (get_execution_mode() == 0) { - set_process_internal(true); - } else if (get_execution_mode() == 1) { - set_physics_process_internal(true); - } - is_setup = true; - } break; - case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: - [[fallthrough]]; - case NOTIFICATION_INTERNAL_PROCESS: { - if (!is_setup || skeleton == nullptr) { - ERR_PRINT_ONCE("Modification is not setup and therefore cannot execute!"); - return; - } - if (!enabled) { - return; - } - if (target_node_cache.is_null()) { - _print_execution_error(true, "Target cache is out of date. Attempting to update..."); - update_cache(); - return; - } - Node3D *target = Object::cast_to(ObjectDB::get_instance(target_node_cache)); - _print_execution_error(!target || !target->is_inside_tree(), "Target node is not in the scene tree. Cannot execute modification!"); - real_t delta = 0.0f; - if (p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) { - delta = get_physics_process_delta_time(); - } else if (p_what == NOTIFICATION_INTERNAL_PROCESS) { - delta = get_process_delta_time(); + Skeleton3D *skeleton = get_skeleton(); + if (skeleton == nullptr || !_cache_target(target_cache, target_node, target_bone)) { + return; + } + + if (!initialized_dynamic_position) { + initialized_dynamic_position = true; + for (uint32_t i = 0; i < jiggle_data_chain.size(); i++) { + int bone_idx = jiggle_data_chain[i].bone_idx; + if (bone_idx > 0 && bone_idx < skeleton->get_bone_count()) { + jiggle_data_chain[i].dynamic_position = get_bone_transform(bone_idx).get_origin(); } - for (uint32_t i = 0; i < jiggle_data_chain.size(); i++) { - _execute_jiggle_joint(i, target, delta); + } + } + Vector3 target_position = get_target_transform(target_cache).origin; + int prev_bone_idx = -1; + Transform3D prev_transform; + for (uint32_t i = 0; i < jiggle_data_chain.size(); i++) { + if (!_cache_bone(jiggle_data_chain[i].bone_idx, jiggle_data_chain[i].bone_name)) { + return; + } + int bone_idx = jiggle_data_chain[i].bone_idx; + int par_idx = skeleton->get_bone_parent(bone_idx); + Transform3D parent_bone_transform; + bool first = true; + while (par_idx >= 0 && par_idx != prev_bone_idx) { + if (first) { + parent_bone_transform = skeleton->get_bone_pose(par_idx); + first = false; + } else { + parent_bone_transform = skeleton->get_bone_pose(par_idx) * parent_bone_transform; } + par_idx = skeleton->get_bone_parent(par_idx); + } + if (i != 0 && par_idx < 0) { + parent_bone_transform = prev_transform * parent_bone_transform; + } + _execute_jiggle_joint(i, parent_bone_transform, target_position, delta); + prev_transform = parent_bone_transform * skeleton->get_bone_pose(bone_idx); + prev_bone_idx = bone_idx; + } +} - execution_error_found = false; +void SkeletonModification3DJiggle::skeleton_changed(Skeleton3D *skeleton) { + target_cache = Variant(); + for (uint32_t i = 0; i < jiggle_data_chain.size(); i++) { + jiggle_data_chain[i].bone_idx = UNCACHED_BONE_IDX; + } + SkeletonModification3D::skeleton_changed(skeleton); +} + +bool SkeletonModification3DJiggle::is_bone_property(String property_name) const { + if (property_name == "target_bone" || property_name.ends_with("/bone")) { + return true; + } + return SkeletonModification3D::is_bone_property(property_name); +} + +bool SkeletonModification3DJiggle::is_property_hidden(String p_property_name) const { + if (p_property_name == "target_bone" && !target_node.is_empty()) { + return true; + } + return SkeletonModification3D::is_property_hidden(p_property_name); +} + +TypedArray SkeletonModification3DJiggle::get_configuration_warnings() const { + TypedArray ret = SkeletonModification3D::get_configuration_warnings(); + if (!get_skeleton()) { + return ret; + } + if (!_cache_target(target_cache, target_node, target_bone)) { + ret.append(vformat("Target %s %s was not found.", target_node.is_empty() ? "bone" : "node", target_node.is_empty() ? target_bone : (String)target_node)); + } + for (uint32_t i = 0; i < jiggle_data_chain.size(); i++) { + if (!_cache_bone(jiggle_data_chain[i].bone_idx, jiggle_data_chain[i].bone_name)) { + ret.append(vformat("Joint %d bone %s was not found.", i, jiggle_data_chain[i].bone_name)); } } + return ret; } diff --git a/scene/3d/skeleton_modification_3d_jiggle.h b/scene/3d/skeleton_modification_3d_jiggle.h index 63a31617fd1e..1f5c4a98424b 100644 --- a/scene/3d/skeleton_modification_3d_jiggle.h +++ b/scene/3d/skeleton_modification_3d_jiggle.h @@ -41,7 +41,7 @@ class SkeletonModification3DJiggle : public SkeletonModification3D { private: struct Jiggle_Joint_Data { String bone_name = ""; - int bone_idx = -1; + mutable int bone_idx = UNCACHED_BONE_IDX; bool override_defaults = false; real_t stiffness = 3; @@ -62,7 +62,8 @@ class SkeletonModification3DJiggle : public SkeletonModification3D { }; NodePath target_node; - ObjectID target_node_cache; + String target_bone; + mutable Variant target_cache; LocalVector jiggle_data_chain; real_t stiffness = 3; @@ -71,11 +72,12 @@ class SkeletonModification3DJiggle : public SkeletonModification3D { bool use_gravity = false; Vector3 gravity = Vector3(0, -6.0, 0); + bool initialized_dynamic_position = false; bool use_colliders = false; uint32_t collision_mask = 1; void update_cache(); - void _execute_jiggle_joint(int p_joint_idx, Node3D *p_target, real_t p_delta); + void _execute_jiggle_joint(int p_joint_idx, Transform3D parent_transform, Vector3 target_position, real_t p_delta); void _update_jiggle_joint_data(); protected: @@ -83,11 +85,17 @@ class SkeletonModification3DJiggle : public SkeletonModification3D { bool _get(const StringName &p_path, Variant &r_ret) const; bool _set(const StringName &p_path, const Variant &p_value); void _get_property_list(List *p_list) const; - void _notification(int32_t p_what); + void skeleton_changed(Skeleton3D *skeleton) override; + void execute(real_t p_delta) override; + bool is_property_hidden(String property_name) const override; + bool is_bone_property(String property_name) const override; + TypedArray get_configuration_warnings() const override; public: void set_target_node(const NodePath &p_target_node); NodePath get_target_node() const; + void set_target_bone(const String &p_target_node); + String get_target_bone() const; void set_stiffness(real_t p_stiffness); real_t get_stiffness() const; @@ -106,28 +114,26 @@ class SkeletonModification3DJiggle : public SkeletonModification3D { void set_collision_mask(int p_mask); int get_collision_mask() const; - int get_jiggle_data_chain_length(); - void set_jiggle_data_chain_length(int p_new_length); - - void set_jiggle_joint_bone_name(int p_joint_idx, String p_name); - String get_jiggle_joint_bone_name(int p_joint_idx) const; - void set_jiggle_joint_bone_index(int p_joint_idx, int p_idx); - int get_jiggle_joint_bone_index(int p_joint_idx) const; - - void set_jiggle_joint_override(int p_joint_idx, bool p_override); - bool get_jiggle_joint_override(int p_joint_idx) const; - void set_jiggle_joint_stiffness(int p_joint_idx, real_t p_stiffness); - real_t get_jiggle_joint_stiffness(int p_joint_idx) const; - void set_jiggle_joint_mass(int p_joint_idx, real_t p_mass); - real_t get_jiggle_joint_mass(int p_joint_idx) const; - void set_jiggle_joint_damping(int p_joint_idx, real_t p_damping); - real_t get_jiggle_joint_damping(int p_joint_idx) const; - void set_jiggle_joint_use_gravity(int p_joint_idx, bool p_use_gravity); - bool get_jiggle_joint_use_gravity(int p_joint_idx) const; - void set_jiggle_joint_gravity(int p_joint_idx, Vector3 p_gravity); - Vector3 get_jiggle_joint_gravity(int p_joint_idx) const; - void set_jiggle_joint_roll(int p_joint_idx, real_t p_roll); - real_t get_jiggle_joint_roll(int p_joint_idx) const; + int get_joint_count(); + void set_joint_count(int p_new_length); + + void set_joint_bone(int p_joint_idx, String p_name); + String get_joint_bone(int p_joint_idx) const; + + void set_joint_override(int p_joint_idx, bool p_override); + bool get_joint_override(int p_joint_idx) const; + void set_joint_stiffness(int p_joint_idx, real_t p_stiffness); + real_t get_joint_stiffness(int p_joint_idx) const; + void set_joint_mass(int p_joint_idx, real_t p_mass); + real_t get_joint_mass(int p_joint_idx) const; + void set_joint_damping(int p_joint_idx, real_t p_damping); + real_t get_joint_damping(int p_joint_idx) const; + void set_joint_use_gravity(int p_joint_idx, bool p_use_gravity); + bool get_joint_use_gravity(int p_joint_idx) const; + void set_joint_gravity(int p_joint_idx, Vector3 p_gravity); + Vector3 get_joint_gravity(int p_joint_idx) const; + void set_joint_roll(int p_joint_idx, real_t p_roll); + real_t get_joint_roll(int p_joint_idx) const; SkeletonModification3DJiggle(); ~SkeletonModification3DJiggle(); diff --git a/scene/3d/skeleton_modification_3d_lookat.cpp b/scene/3d/skeleton_modification_3d_lookat.cpp index 374a02fa0d45..e2fbc3fb1224 100644 --- a/scene/3d/skeleton_modification_3d_lookat.cpp +++ b/scene/3d/skeleton_modification_3d_lookat.cpp @@ -32,102 +32,37 @@ #include "scene/3d/skeleton_3d.h" #include "scene/3d/skeleton_modification_3d.h" -bool SkeletonModification3DLookAt::_set(const StringName &p_path, const Variant &p_value) { - if (p_path == "lock_rotation_to_plane") { - set_lock_rotation_to_plane(p_value); - } else if (p_path == "lock_rotation_plane") { - set_lock_rotation_plane(p_value); - } else if (p_path == "additional_rotation") { - Vector3 tmp = p_value; - tmp.x = Math::deg_to_rad(tmp.x); - tmp.y = Math::deg_to_rad(tmp.y); - tmp.z = Math::deg_to_rad(tmp.z); - set_additional_rotation(tmp); - } - - return true; -} - -bool SkeletonModification3DLookAt::_get(const StringName &p_path, Variant &r_ret) const { - if (p_path == "lock_rotation_to_plane") { - r_ret = get_lock_rotation_to_plane(); - } else if (p_path == "lock_rotation_plane") { - r_ret = get_lock_rotation_plane(); - } else if (p_path == "additional_rotation") { - Vector3 tmp = get_additional_rotation(); - tmp.x = Math::rad_to_deg(tmp.x); - tmp.y = Math::rad_to_deg(tmp.y); - tmp.z = Math::rad_to_deg(tmp.z); - r_ret = tmp; - } - - return true; -} - -void SkeletonModification3DLookAt::_get_property_list(List *p_list) const { - p_list->push_back(PropertyInfo(Variant::BOOL, "lock_rotation_to_plane", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - if (lock_rotation_to_plane) { - p_list->push_back(PropertyInfo(Variant::INT, "lock_rotation_plane", PROPERTY_HINT_ENUM, "X plane, Y plane, Z plane", PROPERTY_USAGE_DEFAULT)); - } - p_list->push_back(PropertyInfo(Variant::VECTOR3, "additional_rotation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); -} - -void SkeletonModification3DLookAt::set_bone_name(String p_name) { +void SkeletonModification3DLookAt::set_bone(const String &p_name) { bone_name = p_name; - if (skeleton) { - bone_idx = skeleton->find_bone(bone_name); - } - execution_error_found = false; - notify_property_list_changed(); + bone_idx = UNCACHED_BONE_IDX; } -String SkeletonModification3DLookAt::get_bone_name() const { +String SkeletonModification3DLookAt::get_bone() const { return bone_name; } -int SkeletonModification3DLookAt::get_bone_index() const { - return bone_idx; -} - -void SkeletonModification3DLookAt::set_bone_index(int p_bone_idx) { - ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!"); - bone_idx = p_bone_idx; - - if (skeleton) { - bone_name = skeleton->get_bone_name(p_bone_idx); +void SkeletonModification3DLookAt::set_target_node(const NodePath &p_target_node) { + target_node = p_target_node; + if (!p_target_node.is_empty()) { + target_bone = StringName(); } - execution_error_found = false; - notify_property_list_changed(); + target_cache = Variant(); } -void SkeletonModification3DLookAt::update_cache() { - if (!is_setup) { - _print_execution_error(true, "Cannot update target cache: modification is not properly setup!"); - return; - } - - target_node_cache = ObjectID(); - if (is_inside_tree()) { - if (has_node(target_node)) { - Node *node = get_node(target_node); - ERR_FAIL_COND_MSG(!node || skeleton == node, - "Cannot update target cache: node is this modification's skeleton or cannot be found!"); - ERR_FAIL_COND_MSG(!node->is_inside_tree(), - "Cannot update target cache: Node is not in the scene tree!"); - target_node_cache = node->get_instance_id(); - - execution_error_found = false; - } - } +NodePath SkeletonModification3DLookAt::get_target_node() const { + return target_node; } -void SkeletonModification3DLookAt::set_target_node(const NodePath &p_target_node) { - target_node = p_target_node; - update_cache(); +void SkeletonModification3DLookAt::set_target_bone(const String &p_target_bone) { + target_bone = p_target_bone; + if (!p_target_bone.is_empty()) { + target_node = NodePath(); + } + target_cache = Variant(); } -NodePath SkeletonModification3DLookAt::get_target_node() const { - return target_node; +String SkeletonModification3DLookAt::get_target_bone() const { + return target_bone; } Vector3 SkeletonModification3DLookAt::get_additional_rotation() const { @@ -138,127 +73,106 @@ void SkeletonModification3DLookAt::set_additional_rotation(Vector3 p_offset) { additional_rotation = p_offset; } -bool SkeletonModification3DLookAt::get_lock_rotation_to_plane() const { - return lock_rotation_plane; -} - -void SkeletonModification3DLookAt::set_lock_rotation_to_plane(bool p_lock_rotation) { - lock_rotation_to_plane = p_lock_rotation; - notify_property_list_changed(); -} - -int SkeletonModification3DLookAt::get_lock_rotation_plane() const { +SkeletonModification3DLookAt::LockRotationPlane SkeletonModification3DLookAt::get_lock_rotation_plane() const { return lock_rotation_plane; } -void SkeletonModification3DLookAt::set_lock_rotation_plane(int p_plane) { +void SkeletonModification3DLookAt::set_lock_rotation_plane(LockRotationPlane p_plane) { lock_rotation_plane = p_plane; } void SkeletonModification3DLookAt::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_bone_name", "name"), &SkeletonModification3DLookAt::set_bone_name); - ClassDB::bind_method(D_METHOD("get_bone_name"), &SkeletonModification3DLookAt::get_bone_name); - - ClassDB::bind_method(D_METHOD("set_bone_index", "bone_idx"), &SkeletonModification3DLookAt::set_bone_index); - ClassDB::bind_method(D_METHOD("get_bone_index"), &SkeletonModification3DLookAt::get_bone_index); + ClassDB::bind_method(D_METHOD("set_bone", "bone_name"), &SkeletonModification3DLookAt::set_bone); + ClassDB::bind_method(D_METHOD("get_bone"), &SkeletonModification3DLookAt::get_bone); - ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification3DLookAt::set_target_node); + ClassDB::bind_method(D_METHOD("set_target_node", "nodepath"), &SkeletonModification3DLookAt::set_target_node); ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification3DLookAt::get_target_node); + ClassDB::bind_method(D_METHOD("set_target_bone", "bone_name"), &SkeletonModification3DLookAt::set_target_bone); + ClassDB::bind_method(D_METHOD("get_target_bone"), &SkeletonModification3DLookAt::get_target_bone); ClassDB::bind_method(D_METHOD("set_additional_rotation", "additional_rotation"), &SkeletonModification3DLookAt::set_additional_rotation); ClassDB::bind_method(D_METHOD("get_additional_rotation"), &SkeletonModification3DLookAt::get_additional_rotation); - ClassDB::bind_method(D_METHOD("set_lock_rotation_to_plane", "lock_to_plane"), &SkeletonModification3DLookAt::set_lock_rotation_to_plane); - ClassDB::bind_method(D_METHOD("get_lock_rotation_to_plane"), &SkeletonModification3DLookAt::get_lock_rotation_to_plane); ClassDB::bind_method(D_METHOD("set_lock_rotation_plane", "plane"), &SkeletonModification3DLookAt::set_lock_rotation_plane); ClassDB::bind_method(D_METHOD("get_lock_rotation_plane"), &SkeletonModification3DLookAt::get_lock_rotation_plane); - ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "bone_name"), "set_bone_name", "get_bone_name"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "bone_index"), "set_bone_index", "get_bone_index"); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node3D"), "set_target_node", "get_target_node"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "bone"), "set_bone", "get_bone"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node3D"), "set_target_node", "get_target_node"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "target_bone"), "set_target_bone", "get_target_bone"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "lock_rotation_plane", PROPERTY_HINT_ENUM, "Unlocked,X plane,Y plane,Z plane", PROPERTY_USAGE_DEFAULT), "set_lock_rotation_plane", "get_lock_rotation_plane"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "additional_rotation", PROPERTY_HINT_NONE, "radians", PROPERTY_USAGE_DEFAULT), "set_additional_rotation", "get_additional_rotation"); + + BIND_ENUM_CONSTANT(ROTATION_UNLOCKED); + BIND_ENUM_CONSTANT(ROTATION_PLANE_X); + BIND_ENUM_CONSTANT(ROTATION_PLANE_Y); + BIND_ENUM_CONSTANT(ROTATION_PLANE_Z); } -void SkeletonModification3DLookAt::_notification(int32_t p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - execution_error_found = false; - update_cache(); - set_process_internal(false); - set_physics_process_internal(false); - if (get_execution_mode() == 0) { - set_process_internal(true); - } else if (get_execution_mode() == 1) { - set_physics_process_internal(true); - } - skeleton = cast_to(get_node_or_null(get_skeleton_path())); - is_setup = true; - } break; - case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: - [[fallthrough]]; - case NOTIFICATION_INTERNAL_PROCESS: { - if (!is_setup) { - ERR_PRINT_ONCE("Modification is not setup."); - return; - } - if (!skeleton) { - ERR_PRINT_ONCE("Modification does not have a skeleton."); - return; - } - if (!enabled) { - return; - } +void SkeletonModification3DLookAt::execute(real_t delta) { + SkeletonModification3D::execute(delta); - if (target_node_cache.is_null()) { - _print_execution_error(true, "Target cache is out of date. Attempting to update..."); - update_cache(); - return; - } + Skeleton3D *skeleton = get_skeleton(); + if (!skeleton || + !_cache_target(target_cache, target_node, target_bone) || + !_cache_bone(bone_idx, bone_name)) { + WARN_PRINT_ONCE("SkeletonModification3DLookAt not initialized in time to execute"); + return; + } + Transform3D new_bone_trans = skeleton->get_bone_pose(bone_idx); + Vector3 target_pos = get_bone_transform(bone_idx).affine_inverse().xform(get_target_transform(target_cache).get_origin()); + + // Lock the rotation to a plane relative to the bone by changing the target position + if (lock_rotation_plane == LockRotationPlane::ROTATION_PLANE_X) { + target_pos.x = new_bone_trans.origin.x; + } else if (lock_rotation_plane == LockRotationPlane::ROTATION_PLANE_Y) { + target_pos.y = new_bone_trans.origin.y; + } else if (lock_rotation_plane == LockRotationPlane::ROTATION_PLANE_Z) { + target_pos.z = new_bone_trans.origin.z; + } - if (bone_idx <= -2) { - bone_idx = skeleton->find_bone(bone_name); - } + // Look at the target! + new_bone_trans = new_bone_trans.looking_at(target_pos, Vector3(0, 1, 0)); + // Convert from Z-forward to whatever direction the bone faces. + ; + new_bone_trans.basis = global_pose_z_forward_to_bone_forward(get_bone_rest_forward_vector(bone_idx), new_bone_trans.basis); - Node3D *target = Object::cast_to(ObjectDB::get_instance(target_node_cache)); - if (_print_execution_error(!target || !target->is_inside_tree(), "Target node is not in the scene tree. Cannot execute modification!")) { - return; - } - if (_print_execution_error(bone_idx <= -1, "Bone index is invalid. Cannot execute modification!")) { - return; - } - Transform3D new_bone_trans = skeleton->get_bone_local_pose_override(bone_idx); - if (new_bone_trans == Transform3D()) { - new_bone_trans = skeleton->get_bone_pose(bone_idx); - } - Vector3 target_pos = skeleton->global_pose_to_local_pose(bone_idx, skeleton->world_transform_to_global_pose(target->get_global_transform())).origin; + // Apply additional rotation + new_bone_trans.basis.rotate_local(Vector3(1, 0, 0), additional_rotation.x); + new_bone_trans.basis.rotate_local(Vector3(0, 1, 0), additional_rotation.y); + new_bone_trans.basis.rotate_local(Vector3(0, 0, 1), additional_rotation.z); + skeleton->set_bone_pose_rotation(bone_idx, new_bone_trans.basis.get_rotation_quaternion()); +} - // Lock the rotation to a plane relative to the bone by changing the target position - if (lock_rotation_to_plane) { - if (lock_rotation_plane == ROTATION_PLANE::ROTATION_PLANE_X) { - target_pos.x = new_bone_trans.origin.x; - } else if (lock_rotation_plane == ROTATION_PLANE::ROTATION_PLANE_Y) { - target_pos.y = new_bone_trans.origin.y; - } else if (lock_rotation_plane == ROTATION_PLANE::ROTATION_PLANE_Z) { - target_pos.z = new_bone_trans.origin.z; - } - } +void SkeletonModification3DLookAt::skeleton_changed(Skeleton3D *skeleton) { + target_cache = Variant(); + bone_idx = UNCACHED_BONE_IDX; + SkeletonModification3D::skeleton_changed(skeleton); +} - // Look at the target! - new_bone_trans = new_bone_trans.looking_at(target_pos, Vector3(0, 1, 0)); - // Convert from Z-forward to whatever direction the bone faces. - skeleton->update_bone_rest_forward_vector(bone_idx); - new_bone_trans.basis = skeleton->global_pose_z_forward_to_bone_forward(bone_idx, new_bone_trans.basis); +bool SkeletonModification3DLookAt::is_bone_property(String p_property_name) const { + if (p_property_name == "target_bone" || p_property_name == "bone") { + return true; + } + return SkeletonModification3D::is_bone_property(p_property_name); +} - // Apply additional rotation - new_bone_trans.basis.rotate_local(Vector3(1, 0, 0), additional_rotation.x); - new_bone_trans.basis.rotate_local(Vector3(0, 1, 0), additional_rotation.y); - new_bone_trans.basis.rotate_local(Vector3(0, 0, 1), additional_rotation.z); - skeleton->set_bone_pose_position(bone_idx, new_bone_trans.origin); - skeleton->set_bone_pose_rotation(bone_idx, new_bone_trans.basis.get_rotation_quaternion()); - skeleton->set_bone_pose_scale(bone_idx, new_bone_trans.basis.get_scale()); - skeleton->force_update_bone_children_transforms(bone_idx); +bool SkeletonModification3DLookAt::is_property_hidden(String p_property_name) const { + if (p_property_name == "target_bone" && !target_node.is_empty()) { + return true; + } + return SkeletonModification3D::is_property_hidden(p_property_name); +} - // If we completed it successfully, then we can set execution_error_found to false - execution_error_found = false; - } +TypedArray SkeletonModification3DLookAt::get_configuration_warnings() const { + TypedArray ret = SkeletonModification3D::get_configuration_warnings(); + if (!get_skeleton()) { + return ret; + } + if (!_cache_target(target_cache, target_node, target_bone)) { + ret.append(vformat("Target %s %s was not found.", target_node.is_empty() ? "bone" : "node", target_node.is_empty() ? target_bone : (String)target_node)); + } + if (!_cache_bone(bone_idx, bone_name)) { + ret.append(vformat("Bone %s was not found.", target_bone)); } + return ret; } diff --git a/scene/3d/skeleton_modification_3d_lookat.h b/scene/3d/skeleton_modification_3d_lookat.h index 70f62c9e3ccc..48c32006b41a 100644 --- a/scene/3d/skeleton_modification_3d_lookat.h +++ b/scene/3d/skeleton_modification_3d_lookat.h @@ -37,49 +37,52 @@ class SkeletonModification3DLookAt : public SkeletonModification3D { GDCLASS(SkeletonModification3DLookAt, SkeletonModification3D); +public: + enum LockRotationPlane { + ROTATION_UNLOCKED, + ROTATION_PLANE_X, + ROTATION_PLANE_Y, + ROTATION_PLANE_Z + }; + private: - String bone_name = ""; - int bone_idx = -1; + String bone_name; + mutable int bone_idx = UNCACHED_BONE_IDX; NodePath target_node; - ObjectID target_node_cache; + String target_bone; + mutable Variant target_cache; Vector3 additional_rotation = Vector3(); bool lock_rotation_to_plane = false; - int lock_rotation_plane = ROTATION_PLANE_X; - - void update_cache(); + LockRotationPlane lock_rotation_plane = ROTATION_UNLOCKED; protected: static void _bind_methods(); - void _notification(int32_t p_what); - bool _get(const StringName &p_path, Variant &r_ret) const; - bool _set(const StringName &p_path, const Variant &p_value); - void _get_property_list(List *p_list) const; -public: - enum ROTATION_PLANE { - ROTATION_PLANE_X, - ROTATION_PLANE_Y, - ROTATION_PLANE_Z - }; - void set_bone_name(String p_name); - String get_bone_name() const; + void execute(real_t delta) override; + void skeleton_changed(Skeleton3D *skeleton) override; + bool is_property_hidden(String property_name) const override; + bool is_bone_property(String property_name) const override; + TypedArray get_configuration_warnings() const override; - void set_bone_index(int p_idx); - int get_bone_index() const; +public: + void set_bone(const String &p_name); + String get_bone() const; void set_target_node(const NodePath &p_target_node); NodePath get_target_node() const; + void set_target_bone(const String &p_target_bone); + String get_target_bone() const; void set_additional_rotation(Vector3 p_offset); Vector3 get_additional_rotation() const; - void set_lock_rotation_to_plane(bool p_lock_to_plane); - bool get_lock_rotation_to_plane() const; - void set_lock_rotation_plane(int p_plane); - int get_lock_rotation_plane() const; + void set_lock_rotation_plane(LockRotationPlane p_plane); + LockRotationPlane get_lock_rotation_plane() const; SkeletonModification3DLookAt() {} ~SkeletonModification3DLookAt() {} }; +VARIANT_ENUM_CAST(SkeletonModification3DLookAt::LockRotationPlane); + #endif // SKELETON_MODIFICATION_3D_LOOKAT_H diff --git a/scene/3d/skeleton_modification_3d_twoboneik.cpp b/scene/3d/skeleton_modification_3d_twoboneik.cpp index cbb914f5fbd8..348bbd96d60d 100644 --- a/scene/3d/skeleton_modification_3d_twoboneik.cpp +++ b/scene/3d/skeleton_modification_3d_twoboneik.cpp @@ -33,214 +33,78 @@ #include "scene/3d/skeleton_3d.h" #include "scene/3d/skeleton_modification_3d.h" -bool SkeletonModification3DTwoBoneIK::_set(const StringName &p_path, const Variant &p_value) { - String path = p_path; - - if (path == "use_tip_node") { - set_use_tip_node(p_value); - } else if (path == "tip_node") { - set_tip_node(p_value); - } else if (path == "auto_calculate_joint_length") { - set_auto_calculate_joint_length(p_value); - } else if (path == "use_pole_node") { - set_use_pole_node(p_value); - } else if (path == "pole_node") { - set_pole_node(p_value); - } else if (path == "joint_one_length") { - set_joint_one_length(p_value); - } else if (path == "joint_two_length") { - set_joint_two_length(p_value); - } else if (path == "joint_one/bone_name") { - set_joint_one_bone_name(p_value); - } else if (path == "joint_one/bone_idx") { - set_joint_one_bone_idx(p_value); - } else if (path == "joint_one/roll") { - set_joint_one_roll(Math::deg_to_rad(real_t(p_value))); - } else if (path == "joint_two/bone_name") { - set_joint_two_bone_name(p_value); - } else if (path == "joint_two/bone_idx") { - set_joint_two_bone_idx(p_value); - } else if (path == "joint_two/roll") { - set_joint_two_roll(Math::deg_to_rad(real_t(p_value))); - } - - return true; -} - -bool SkeletonModification3DTwoBoneIK::_get(const StringName &p_path, Variant &r_ret) const { - String path = p_path; - - if (path == "use_tip_node") { - r_ret = get_use_tip_node(); - } else if (path == "tip_node") { - r_ret = get_tip_node(); - } else if (path == "auto_calculate_joint_length") { - r_ret = get_auto_calculate_joint_length(); - } else if (path == "use_pole_node") { - r_ret = get_use_pole_node(); - } else if (path == "pole_node") { - r_ret = get_pole_node(); - } else if (path == "joint_one_length") { - r_ret = get_joint_one_length(); - } else if (path == "joint_two_length") { - r_ret = get_joint_two_length(); - } else if (path == "joint_one/bone_name") { - r_ret = get_joint_one_bone_name(); - } else if (path == "joint_one/bone_idx") { - r_ret = get_joint_one_bone_idx(); - } else if (path == "joint_one/roll") { - r_ret = Math::rad_to_deg(get_joint_one_roll()); - } else if (path == "joint_two/bone_name") { - r_ret = get_joint_two_bone_name(); - } else if (path == "joint_two/bone_idx") { - r_ret = get_joint_two_bone_idx(); - } else if (path == "joint_two/roll") { - r_ret = Math::rad_to_deg(get_joint_two_roll()); - } - - return true; -} - -void SkeletonModification3DTwoBoneIK::_get_property_list(List *p_list) const { - p_list->push_back(PropertyInfo(Variant::BOOL, "use_tip_node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - if (use_tip_node) { - p_list->push_back(PropertyInfo(Variant::NODE_PATH, "tip_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node3D", PROPERTY_USAGE_DEFAULT)); - } - - p_list->push_back(PropertyInfo(Variant::BOOL, "auto_calculate_joint_length", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - if (!auto_calculate_joint_length) { - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_one_length", PROPERTY_HINT_RANGE, "-1, 10000, 0.001", PROPERTY_USAGE_DEFAULT)); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_two_length", PROPERTY_HINT_RANGE, "-1, 10000, 0.001", PROPERTY_USAGE_DEFAULT)); - } - - p_list->push_back(PropertyInfo(Variant::BOOL, "use_pole_node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - if (use_pole_node) { - p_list->push_back(PropertyInfo(Variant::NODE_PATH, "pole_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node3D", PROPERTY_USAGE_DEFAULT)); - } - - p_list->push_back(PropertyInfo(Variant::STRING_NAME, "joint_one/bone_name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - p_list->push_back(PropertyInfo(Variant::INT, "joint_one/bone_idx", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_one/roll", PROPERTY_HINT_RANGE, "-360, 360, 0.01", PROPERTY_USAGE_DEFAULT)); - - p_list->push_back(PropertyInfo(Variant::STRING_NAME, "joint_two/bone_name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - p_list->push_back(PropertyInfo(Variant::INT, "joint_two/bone_idx", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - p_list->push_back(PropertyInfo(Variant::FLOAT, "joint_two/roll", PROPERTY_HINT_RANGE, "-360, 360, 0.01", PROPERTY_USAGE_DEFAULT)); -} - -void SkeletonModification3DTwoBoneIK::update_cache_target() { - if (!is_setup) { - _print_execution_error(true, "Cannot update target cache: modification is not properly setup!"); - return; - } - - target_node_cache = ObjectID(); - if (skeleton) { - if (skeleton->is_inside_tree() && target_node.is_empty() == false) { - if (skeleton->has_node(target_node)) { - Node *node = skeleton->get_node(target_node); - ERR_FAIL_COND_MSG(!node || skeleton == node, - "Cannot update target cache: Target node is this modification's skeleton or cannot be found. Cannot execute modification"); - ERR_FAIL_COND_MSG(!node->is_inside_tree(), - "Cannot update target cache: Target node is not in the scene tree. Cannot execute modification!"); - target_node_cache = node->get_instance_id(); - - execution_error_found = false; - } - } - } -} - -void SkeletonModification3DTwoBoneIK::update_cache_tip() { - if (!is_setup) { - _print_execution_error(true, "Cannot update tip cache: modification is not properly setup!"); - return; - } - - tip_node_cache = ObjectID(); - if (skeleton) { - if (skeleton->is_inside_tree()) { - if (skeleton->has_node(tip_node)) { - Node *node = skeleton->get_node(tip_node); - ERR_FAIL_COND_MSG(!node || skeleton == node, - "Cannot update tip cache: Tip node is this modification's skeleton or cannot be found!"); - ERR_FAIL_COND_MSG(!node->is_inside_tree(), - "Cannot update tip cache: Tip node is not in the scene tree. Cannot execute modification!"); - tip_node_cache = node->get_instance_id(); - - execution_error_found = false; - } - } - } -} - -void SkeletonModification3DTwoBoneIK::update_cache_pole() { - if (!is_setup) { - _print_execution_error(true, "Cannot update pole cache: modification is not properly setup!"); - return; - } - - pole_node_cache = ObjectID(); - if (skeleton) { - if (skeleton->is_inside_tree()) { - if (skeleton->has_node(pole_node)) { - Node *node = skeleton->get_node(pole_node); - ERR_FAIL_COND_MSG(!node || skeleton == node, - "Cannot update pole cache: Pole node is this modification's skeleton or cannot be found!"); - ERR_FAIL_COND_MSG(!node->is_inside_tree(), - "Cannot update pole cache: Pole node is not in the scene tree. Cannot execute modification!"); - pole_node_cache = node->get_instance_id(); - - execution_error_found = false; - } - } - } -} - void SkeletonModification3DTwoBoneIK::set_target_node(const NodePath &p_target_node) { target_node = p_target_node; - update_cache_target(); + if (!p_target_node.is_empty()) { + target_bone = String(); + } + target_cache = Variant(); } NodePath SkeletonModification3DTwoBoneIK::get_target_node() const { return target_node; } -void SkeletonModification3DTwoBoneIK::set_use_tip_node(const bool p_use_tip_node) { - use_tip_node = p_use_tip_node; - notify_property_list_changed(); +void SkeletonModification3DTwoBoneIK::set_target_bone(const String &p_target_bone) { + target_bone = p_target_bone; + if (!p_target_bone.is_empty()) { + target_node = NodePath(); + } + target_cache = Variant(); } -bool SkeletonModification3DTwoBoneIK::get_use_tip_node() const { - return use_tip_node; +String SkeletonModification3DTwoBoneIK::get_target_bone() const { + return target_bone; } void SkeletonModification3DTwoBoneIK::set_tip_node(const NodePath &p_tip_node) { tip_node = p_tip_node; - update_cache_tip(); + if (!p_tip_node.is_empty()) { + tip_bone = String(); + } + tip_cache = Variant(); } NodePath SkeletonModification3DTwoBoneIK::get_tip_node() const { return tip_node; } -void SkeletonModification3DTwoBoneIK::set_use_pole_node(const bool p_use_pole_node) { - use_pole_node = p_use_pole_node; - notify_property_list_changed(); +void SkeletonModification3DTwoBoneIK::set_tip_bone(const String &p_tip_bone) { + tip_bone = p_tip_bone; + if (!p_tip_bone.is_empty()) { + tip_node = NodePath(); + } + tip_cache = Variant(); } -bool SkeletonModification3DTwoBoneIK::get_use_pole_node() const { - return use_pole_node; +String SkeletonModification3DTwoBoneIK::get_tip_bone() const { + return tip_bone; } void SkeletonModification3DTwoBoneIK::set_pole_node(const NodePath &p_pole_node) { pole_node = p_pole_node; - update_cache_pole(); + if (!p_pole_node.is_empty()) { + pole_bone = String(); + } + pole_cache = Variant(); } NodePath SkeletonModification3DTwoBoneIK::get_pole_node() const { return pole_node; } +void SkeletonModification3DTwoBoneIK::set_pole_bone(const String &p_pole_bone) { + pole_bone = p_pole_bone; + if (!p_pole_bone.is_empty()) { + pole_node = NodePath(); + } + pole_cache = Variant(); +} + +String SkeletonModification3DTwoBoneIK::get_pole_bone() const { + return pole_bone; +} + void SkeletonModification3DTwoBoneIK::set_auto_calculate_joint_length(bool p_calculate) { auto_calculate_joint_length = p_calculate; if (p_calculate) { @@ -254,30 +118,23 @@ bool SkeletonModification3DTwoBoneIK::get_auto_calculate_joint_length() const { } void SkeletonModification3DTwoBoneIK::calculate_joint_lengths() { - if (!is_setup) { - return; // fail silently, as we likely just loaded the scene. + Skeleton3D *skeleton = get_skeleton(); + if (skeleton == nullptr || !_cache_bone(joint_one_bone_idx, joint_one_bone_name) || !_cache_bone(joint_two_bone_idx, joint_two_bone_name)) { + WARN_PRINT_ONCE("Failed to cache joints during calculate_joint_lengths"); + return; } - ERR_FAIL_COND_MSG(skeleton == nullptr, - "Modification is not setup and therefore cannot calculate joint lengths!"); - ERR_FAIL_COND_MSG(joint_one_bone_idx <= -1 || joint_two_bone_idx <= -1, - "One of the bones in the TwoBoneIK modification are not set! Cannot calculate joint lengths!"); - Transform3D bone_one_rest_trans = skeleton->get_bone_global_pose(joint_one_bone_idx); - Transform3D bone_two_rest_trans = skeleton->get_bone_global_pose(joint_two_bone_idx); + Transform3D bone_one_rest_trans = get_bone_transform(joint_one_bone_idx); + Transform3D bone_two_rest_trans = get_bone_transform(joint_two_bone_idx); joint_one_length = bone_one_rest_trans.origin.distance_to(bone_two_rest_trans.origin); - if (use_tip_node) { - if (tip_node_cache.is_null()) { - update_cache_tip(); - WARN_PRINT("Tip cache is out of date. Updating..."); - } - - Node3D *tip = Object::cast_to(ObjectDB::get_instance(tip_node_cache)); - if (tip) { - Transform3D bone_tip_trans = skeleton->world_transform_to_global_pose(tip->get_global_transform()); - joint_two_length = bone_two_rest_trans.origin.distance_to(bone_tip_trans.origin); + if (!tip_node.is_empty() || !tip_bone.is_empty()) { + if (!_cache_target(tip_cache, tip_node, tip_bone)) { + return; } + Transform3D bone_tip_trans = get_target_transform(tip_cache); + joint_two_length = bone_two_rest_trans.origin.distance_to(bone_tip_trans.origin); } else { // Attempt to use children bones to get the length Vector bone_two_children = skeleton->get_bone_children(joint_two_bone_idx); @@ -285,7 +142,7 @@ void SkeletonModification3DTwoBoneIK::calculate_joint_lengths() { joint_two_length = 0; for (int i = 0; i < bone_two_children.size(); i++) { joint_two_length += bone_two_rest_trans.origin.distance_to( - skeleton->get_bone_global_pose(bone_two_children[i]).origin); + get_bone_transform(bone_two_children[i]).origin); } joint_two_length = joint_two_length / bone_two_children.size(); } else { @@ -293,35 +150,17 @@ void SkeletonModification3DTwoBoneIK::calculate_joint_lengths() { joint_two_length = 1.0; } } - execution_error_found = false; } -void SkeletonModification3DTwoBoneIK::set_joint_one_bone_name(String p_bone_name) { +void SkeletonModification3DTwoBoneIK::set_joint_one_bone(String p_bone_name) { joint_one_bone_name = p_bone_name; - if (skeleton) { - joint_one_bone_idx = skeleton->find_bone(p_bone_name); - } - execution_error_found = false; - notify_property_list_changed(); + joint_one_bone_idx = UNCACHED_BONE_IDX; } -String SkeletonModification3DTwoBoneIK::get_joint_one_bone_name() const { +String SkeletonModification3DTwoBoneIK::get_joint_one_bone() const { return joint_one_bone_name; } -void SkeletonModification3DTwoBoneIK::set_joint_one_bone_idx(int p_bone_idx) { - joint_one_bone_idx = p_bone_idx; - if (skeleton) { - joint_one_bone_name = skeleton->get_bone_name(p_bone_idx); - } - execution_error_found = false; - notify_property_list_changed(); -} - -int SkeletonModification3DTwoBoneIK::get_joint_one_bone_idx() const { - return joint_one_bone_idx; -} - void SkeletonModification3DTwoBoneIK::set_joint_one_length(real_t p_length) { joint_one_length = p_length; } @@ -330,32 +169,15 @@ real_t SkeletonModification3DTwoBoneIK::get_joint_one_length() const { return joint_one_length; } -void SkeletonModification3DTwoBoneIK::set_joint_two_bone_name(String p_bone_name) { +void SkeletonModification3DTwoBoneIK::set_joint_two_bone(String p_bone_name) { joint_two_bone_name = p_bone_name; - if (skeleton) { - joint_two_bone_idx = skeleton->find_bone(p_bone_name); - } - execution_error_found = false; - notify_property_list_changed(); + joint_two_bone_idx = UNCACHED_BONE_IDX; } -String SkeletonModification3DTwoBoneIK::get_joint_two_bone_name() const { +String SkeletonModification3DTwoBoneIK::get_joint_two_bone() const { return joint_two_bone_name; } -void SkeletonModification3DTwoBoneIK::set_joint_two_bone_idx(int p_bone_idx) { - joint_two_bone_idx = p_bone_idx; - if (skeleton) { - joint_two_bone_name = skeleton->get_bone_name(p_bone_idx); - } - execution_error_found = false; - notify_property_list_changed(); -} - -int SkeletonModification3DTwoBoneIK::get_joint_two_bone_idx() const { - return joint_two_bone_idx; -} - void SkeletonModification3DTwoBoneIK::set_joint_two_length(real_t p_length) { joint_two_length = p_length; } @@ -383,31 +205,29 @@ real_t SkeletonModification3DTwoBoneIK::get_joint_two_roll() const { void SkeletonModification3DTwoBoneIK::_bind_methods() { ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification3DTwoBoneIK::set_target_node); ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification3DTwoBoneIK::get_target_node); + ClassDB::bind_method(D_METHOD("set_target_bone", "target_bone_name"), &SkeletonModification3DTwoBoneIK::set_target_bone); + ClassDB::bind_method(D_METHOD("get_target_bone"), &SkeletonModification3DTwoBoneIK::get_target_bone); - ClassDB::bind_method(D_METHOD("set_use_pole_node", "use_pole_node"), &SkeletonModification3DTwoBoneIK::set_use_pole_node); - ClassDB::bind_method(D_METHOD("get_use_pole_node"), &SkeletonModification3DTwoBoneIK::get_use_pole_node); ClassDB::bind_method(D_METHOD("set_pole_node", "pole_nodepath"), &SkeletonModification3DTwoBoneIK::set_pole_node); ClassDB::bind_method(D_METHOD("get_pole_node"), &SkeletonModification3DTwoBoneIK::get_pole_node); + ClassDB::bind_method(D_METHOD("set_pole_bone", "pole_bone_name"), &SkeletonModification3DTwoBoneIK::set_pole_bone); + ClassDB::bind_method(D_METHOD("get_pole_bone"), &SkeletonModification3DTwoBoneIK::get_pole_bone); - ClassDB::bind_method(D_METHOD("set_use_tip_node", "use_tip_node"), &SkeletonModification3DTwoBoneIK::set_use_tip_node); - ClassDB::bind_method(D_METHOD("get_use_tip_node"), &SkeletonModification3DTwoBoneIK::get_use_tip_node); ClassDB::bind_method(D_METHOD("set_tip_node", "tip_nodepath"), &SkeletonModification3DTwoBoneIK::set_tip_node); ClassDB::bind_method(D_METHOD("get_tip_node"), &SkeletonModification3DTwoBoneIK::get_tip_node); + ClassDB::bind_method(D_METHOD("set_tip_bone", "tip_bone_name"), &SkeletonModification3DTwoBoneIK::set_tip_bone); + ClassDB::bind_method(D_METHOD("get_tip_bone"), &SkeletonModification3DTwoBoneIK::get_tip_bone); ClassDB::bind_method(D_METHOD("set_auto_calculate_joint_length", "auto_calculate_joint_length"), &SkeletonModification3DTwoBoneIK::set_auto_calculate_joint_length); ClassDB::bind_method(D_METHOD("get_auto_calculate_joint_length"), &SkeletonModification3DTwoBoneIK::get_auto_calculate_joint_length); - ClassDB::bind_method(D_METHOD("set_joint_one_bone_name", "bone_name"), &SkeletonModification3DTwoBoneIK::set_joint_one_bone_name); - ClassDB::bind_method(D_METHOD("get_joint_one_bone_name"), &SkeletonModification3DTwoBoneIK::get_joint_one_bone_name); - ClassDB::bind_method(D_METHOD("set_joint_one_bone_idx", "bone_idx"), &SkeletonModification3DTwoBoneIK::set_joint_one_bone_idx); - ClassDB::bind_method(D_METHOD("get_joint_one_bone_idx"), &SkeletonModification3DTwoBoneIK::get_joint_one_bone_idx); + ClassDB::bind_method(D_METHOD("set_joint_one_bone", "bone_name"), &SkeletonModification3DTwoBoneIK::set_joint_one_bone); + ClassDB::bind_method(D_METHOD("get_joint_one_bone"), &SkeletonModification3DTwoBoneIK::get_joint_one_bone); ClassDB::bind_method(D_METHOD("set_joint_one_length", "bone_length"), &SkeletonModification3DTwoBoneIK::set_joint_one_length); ClassDB::bind_method(D_METHOD("get_joint_one_length"), &SkeletonModification3DTwoBoneIK::get_joint_one_length); - ClassDB::bind_method(D_METHOD("set_joint_two_bone_name", "bone_name"), &SkeletonModification3DTwoBoneIK::set_joint_two_bone_name); - ClassDB::bind_method(D_METHOD("get_joint_two_bone_name"), &SkeletonModification3DTwoBoneIK::get_joint_two_bone_name); - ClassDB::bind_method(D_METHOD("set_joint_two_bone_idx", "bone_idx"), &SkeletonModification3DTwoBoneIK::set_joint_two_bone_idx); - ClassDB::bind_method(D_METHOD("get_joint_two_bone_idx"), &SkeletonModification3DTwoBoneIK::get_joint_two_bone_idx); + ClassDB::bind_method(D_METHOD("set_joint_two_bone", "bone_name"), &SkeletonModification3DTwoBoneIK::set_joint_two_bone); + ClassDB::bind_method(D_METHOD("get_joint_two_bone"), &SkeletonModification3DTwoBoneIK::get_joint_two_bone); ClassDB::bind_method(D_METHOD("set_joint_two_length", "bone_length"), &SkeletonModification3DTwoBoneIK::set_joint_two_length); ClassDB::bind_method(D_METHOD("get_joint_two_length"), &SkeletonModification3DTwoBoneIK::get_joint_two_length); @@ -416,233 +236,205 @@ void SkeletonModification3DTwoBoneIK::_bind_methods() { ClassDB::bind_method(D_METHOD("set_joint_two_roll", "roll"), &SkeletonModification3DTwoBoneIK::set_joint_two_roll); ClassDB::bind_method(D_METHOD("get_joint_two_roll"), &SkeletonModification3DTwoBoneIK::get_joint_two_roll); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node3D"), "set_target_node", "get_target_node"); - ADD_GROUP("", ""); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "target_bone", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT), "set_target_bone", "get_target_bone"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node3D", PROPERTY_USAGE_DEFAULT), "set_target_node", "get_target_node"); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "tip_bone", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT), "set_tip_bone", "get_tip_bone"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "tip_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node3D", PROPERTY_USAGE_DEFAULT), "set_tip_node", "get_tip_node"); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_calculate_joint_length", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT), "set_auto_calculate_joint_length", "get_auto_calculate_joint_length"); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "pole_bone", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT), "set_pole_bone", "get_pole_bone"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "pole_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node3D", PROPERTY_USAGE_DEFAULT), "set_pole_node", "get_pole_node"); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "joint_one_bone", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT), "set_joint_one_bone", "get_joint_one_bone"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "joint_one_roll", PROPERTY_HINT_RANGE, "-360, 360, 0.01", PROPERTY_USAGE_DEFAULT), "set_joint_one_roll", "get_joint_one_roll"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "joint_one_length", PROPERTY_HINT_RANGE, "-1, 10000, 0.001", PROPERTY_USAGE_DEFAULT), "set_joint_one_length", "get_joint_one_length"); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "joint_two_bone", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT), "set_joint_two_bone", "get_joint_two_bone"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "joint_two_roll", PROPERTY_HINT_RANGE, "-360, 360, 0.01", PROPERTY_USAGE_DEFAULT), "set_joint_two_roll", "get_joint_two_roll"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "joint_two_length", PROPERTY_HINT_RANGE, "-1, 10000, 0.001", PROPERTY_USAGE_DEFAULT), "set_joint_two_length", "get_joint_two_length"); } SkeletonModification3DTwoBoneIK::SkeletonModification3DTwoBoneIK() { - is_setup = false; } SkeletonModification3DTwoBoneIK::~SkeletonModification3DTwoBoneIK() { } -void SkeletonModification3DTwoBoneIK::_notification(int32_t p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - execution_error_found = false; - update_cache_target(); - update_cache_tip(); - set_process_internal(false); - set_physics_process_internal(false); - if (get_execution_mode() == 0) { - set_process_internal(true); - } else if (get_execution_mode() == 1) { - set_physics_process_internal(true); - } - skeleton = cast_to(get_node_or_null(get_skeleton_path())); - is_setup = true; - } break; - case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: - [[fallthrough]]; - case NOTIFICATION_INTERNAL_PROCESS: { - if (!is_setup || skeleton == nullptr) { - ERR_PRINT_ONCE("Modification is not setup and therefore cannot execute!"); - return; - } +void SkeletonModification3DTwoBoneIK::execute(real_t delta) { + SkeletonModification3D::execute(delta); - if (!enabled) { - return; - } + // Update joint lengths (if needed) + if (auto_calculate_joint_length && (joint_one_length < 0 || joint_two_length < 0)) { + calculate_joint_lengths(); + } - if (_print_execution_error(joint_one_bone_idx < 0 || joint_two_bone_idx < 0, - "One (or more) of the bones in the modification have invalid bone indexes. Cannot execute modification!")) { - return; - } + // Adopted from the links below: + // http://theorangeduck.com/page/simple-two-joint + // https://www.alanzucconi.com/2018/05/02/ik-2d-2/ + // With modifications by TwistedTwigleg + Transform3D target_trans = get_target_transform(target_cache); + Skeleton3D *skeleton = get_skeleton(); + if (skeleton == nullptr) { + WARN_PRINT_ONCE("SkeletonModification3DTwoBoneIK skeleton not initialized in time to execute"); + return; + } - if (target_node_cache.is_null()) { - _print_execution_error(true, "Target cache is out of date. Attempting to update..."); - update_cache_target(); - return; - } + Vector3 bone_one_forward = get_bone_rest_forward_vector(joint_one_bone_idx); + Vector3 bone_two_forward = get_bone_rest_forward_vector(joint_two_bone_idx); + + Transform3D bone_one_trans = get_bone_transform(joint_one_bone_idx); + //Transform3D bone_one_local_pos = skeleton->get_bone_pose(joint_one_bone_idx); + //Transform3D bone_two_trans = get_bone_transform(joint_two_bone_idx); + Transform3D bone_two_trans; + Transform3D bone_two_local_pos = skeleton->get_bone_pose(joint_two_bone_idx); + + int bone_one_parent = skeleton->get_bone_parent(joint_one_bone_idx); + int bone_two_parent = skeleton->get_bone_parent(joint_two_bone_idx); + Transform3D bone_one_parent_trans = get_bone_transform(bone_one_parent); + Transform3D bone_one_to_two_parent_trans; + if (bone_two_parent != joint_one_bone_idx) { + Transform3D bone_two_parent_trans = get_bone_transform(bone_one_parent); + bone_one_to_two_parent_trans = bone_one_trans.affine_inverse() * bone_two_parent_trans; + } - // Update joint lengths (if needed) - if (auto_calculate_joint_length && (joint_one_length < 0 || joint_two_length < 0)) { - calculate_joint_lengths(); - } + // Make the first joint look at the pole, and the second look at the target. That way, the + // TwoBoneIK solver has to really only handle extension/contraction, which should make it align with the pole. + if (!pole_node.is_empty() || !pole_bone.is_empty()) { + if (!_cache_target(pole_cache, pole_node, pole_bone)) { + WARN_PRINT_ONCE("SkeletonModification3DTwoBoneIK pole not initialized in time to execute"); + return; + } + Transform3D pole_trans = get_target_transform(pole_cache); - // Adopted from the links below: - // http://theorangeduck.com/page/simple-two-joint - // https://www.alanzucconi.com/2018/05/02/ik-2d-2/ - // With modifications by TwistedTwigleg - Node3D *target = Object::cast_to(ObjectDB::get_instance(target_node_cache)); - if (_print_execution_error(!target || !target->is_inside_tree(), "Target node is not in the scene tree. Cannot execute modification!")) { - return; - } - Transform3D target_trans = skeleton->world_transform_to_global_pose(target->get_global_transform()); - - Transform3D bone_one_trans; - Transform3D bone_two_trans; - - // Make the first joint look at the pole, and the second look at the target. That way, the - // TwoBoneIK solver has to really only handle extension/contraction, which should make it align with the pole. - if (use_pole_node) { - if (pole_node_cache.is_null()) { - _print_execution_error(true, "Pole cache is out of date. Attempting to update..."); - update_cache_pole(); - return; - } - - Node3D *pole = Object::cast_to(ObjectDB::get_instance(pole_node_cache)); - if (_print_execution_error(!pole || !pole->is_inside_tree(), "Pole node is not in the scene tree. Cannot execute modification!")) { - return; - } - Transform3D pole_trans = skeleton->world_transform_to_global_pose(pole->get_global_transform()); - - Transform3D bone_one_global_pose = skeleton->get_bone_global_pose(joint_one_bone_idx); - Transform3D bone_one_local_pos = skeleton->global_pose_to_local_pose(joint_one_bone_idx, bone_one_global_pose); - if (bone_one_local_pos == Transform3D()) { - bone_one_local_pos = skeleton->get_bone_pose(joint_one_bone_idx); - } - Transform3D bone_two_global_pose = skeleton->get_bone_global_pose(joint_two_bone_idx); - Transform3D bone_two_local_pos = skeleton->global_pose_to_local_pose(joint_two_bone_idx, bone_two_global_pose); - if (bone_two_local_pos == Transform3D()) { - bone_two_local_pos = skeleton->get_bone_pose(joint_two_bone_idx); - } - - bone_one_trans = skeleton->local_pose_to_global_pose(joint_one_bone_idx, bone_one_local_pos); - bone_one_trans = bone_one_trans.looking_at(pole_trans.origin, Vector3(0, 1, 0)); - bone_one_trans.basis = skeleton->global_pose_z_forward_to_bone_forward(joint_one_bone_idx, bone_one_trans.basis); - skeleton->update_bone_rest_forward_vector(joint_one_bone_idx); - bone_one_trans.basis.rotate_local(skeleton->get_bone_axis_forward_vector(joint_one_bone_idx), joint_one_roll); - Transform3D bone_one_trans_local = skeleton->global_pose_to_local_pose(joint_one_bone_idx, bone_one_trans); - skeleton->set_bone_pose_position(joint_one_bone_idx, bone_one_trans_local.origin); - skeleton->set_bone_pose_rotation(joint_one_bone_idx, bone_one_trans_local.basis.get_rotation_quaternion()); - skeleton->set_bone_pose_scale(joint_one_bone_idx, bone_one_trans_local.basis.get_scale()); - - skeleton->force_update_bone_children_transforms(joint_one_bone_idx); - - bone_two_trans = skeleton->local_pose_to_global_pose(joint_two_bone_idx, bone_two_local_pos); - bone_two_trans = bone_two_trans.looking_at(target_trans.origin, Vector3(0, 1, 0)); - bone_two_trans.basis = skeleton->global_pose_z_forward_to_bone_forward(joint_two_bone_idx, bone_two_trans.basis); - skeleton->update_bone_rest_forward_vector(joint_two_bone_idx); - bone_two_trans.basis.rotate_local(skeleton->get_bone_axis_forward_vector(joint_two_bone_idx), joint_two_roll); - Transform3D bone_two_trans_local = skeleton->global_pose_to_local_pose(joint_two_bone_idx, bone_two_trans); - skeleton->set_bone_pose_position(joint_two_bone_idx, bone_two_trans_local.origin); - skeleton->set_bone_pose_rotation(joint_two_bone_idx, bone_two_trans_local.basis.get_rotation_quaternion()); - skeleton->set_bone_pose_scale(joint_two_bone_idx, bone_two_trans_local.basis.get_scale()); - - skeleton->force_update_bone_children_transforms(joint_two_bone_idx); - } else { - Transform3D bone_one_global_pose = skeleton->get_bone_global_pose(joint_one_bone_idx); - Transform3D bone_one_local_pos = skeleton->global_pose_to_local_pose(joint_one_bone_idx, bone_one_global_pose); - if (bone_one_local_pos == Transform3D()) { - bone_one_local_pos = skeleton->get_bone_pose(joint_one_bone_idx); - } - Transform3D bone_two_global_pose = skeleton->get_bone_global_pose(joint_two_bone_idx); - Transform3D bone_two_local_pos = skeleton->global_pose_to_local_pose(joint_two_bone_idx, bone_two_global_pose); - if (bone_two_local_pos == Transform3D()) { - bone_two_local_pos = skeleton->get_bone_pose(joint_two_bone_idx); - } - - bone_one_trans = skeleton->local_pose_to_global_pose(joint_one_bone_idx, bone_one_local_pos); - bone_two_trans = skeleton->local_pose_to_global_pose(joint_two_bone_idx, bone_two_local_pos); - } + bone_one_trans = bone_one_trans.looking_at(pole_trans.origin, Vector3(0, 1, 0)); + bone_one_trans.basis = global_pose_z_forward_to_bone_forward(bone_one_forward, bone_one_trans.basis); + bone_one_trans.basis.rotate_local(bone_one_forward, joint_one_roll); - Transform3D bone_two_tip_trans; - if (use_tip_node) { - if (tip_node_cache.is_null()) { - _print_execution_error(true, "Tip cache is out of date. Attempting to update..."); - update_cache_tip(); - return; - } - Node3D *tip = Object::cast_to(ObjectDB::get_instance(tip_node_cache)); - if (_print_execution_error(!tip || !tip->is_inside_tree(), "Tip node is not in the scene tree. Cannot execute modification!")) { - return; - } - bone_two_tip_trans = skeleton->world_transform_to_global_pose(tip->get_global_transform()); - } else { - skeleton->update_bone_rest_forward_vector(joint_two_bone_idx); - bone_two_tip_trans = bone_two_trans; - bone_two_tip_trans.origin += bone_two_trans.basis.xform(skeleton->get_bone_axis_forward_vector(joint_two_bone_idx)).normalized() * joint_two_length; - } + bone_two_trans = bone_one_to_two_parent_trans * bone_two_local_pos; + bone_two_trans = bone_two_trans.looking_at(target_trans.origin, Vector3(0, 1, 0)); + bone_two_trans.basis = global_pose_z_forward_to_bone_forward(bone_two_forward, bone_two_trans.basis); + bone_two_trans.basis.rotate_local(bone_two_forward, joint_two_roll); + } else { + bone_two_trans = get_bone_transform(joint_two_bone_idx); + } - real_t joint_one_to_target_length = bone_one_trans.origin.distance_to(target_trans.origin); - if (joint_one_length + joint_two_length < joint_one_to_target_length) { - // Set the target *just* out of reach to straighten the bones - joint_one_to_target_length = joint_one_length + joint_two_length + 0.01; - } else if (joint_one_to_target_length < joint_one_length) { - // Place the target in reach so the solver doesn't do crazy things - joint_one_to_target_length = joint_one_length; - } + Transform3D bone_two_tip_trans; + if (!tip_node.is_empty() || !tip_bone.is_empty()) { + if (!_cache_target(tip_cache, tip_node, tip_bone)) { + WARN_PRINT_ONCE("SkeletonModification3DTwoBoneIK tip not initialized in time to execute"); + return; + } + bone_two_tip_trans = get_target_transform(tip_cache); + } else { + bone_two_tip_trans = bone_two_trans; + bone_two_tip_trans.origin += bone_two_trans.basis.xform(bone_two_forward).normalized() * joint_two_length; + } - // Get the square lengths for all three sides of the triangle we'll use to calculate the angles - real_t sqr_one_length = joint_one_length * joint_one_length; - real_t sqr_two_length = joint_two_length * joint_two_length; - real_t sqr_three_length = joint_one_to_target_length * joint_one_to_target_length; - - // Calculate the angles for the first joint using the law of cosigns - real_t ac_ab_0 = Math::acos(CLAMP(bone_two_tip_trans.origin.direction_to(bone_one_trans.origin).dot(bone_two_trans.origin.direction_to(bone_one_trans.origin)), -1, 1)); - real_t ac_at_0 = Math::acos(CLAMP(bone_one_trans.origin.direction_to(bone_two_tip_trans.origin).dot(bone_one_trans.origin.direction_to(target_trans.origin)), -1, 1)); - real_t ac_ab_1 = Math::acos(CLAMP((sqr_two_length - sqr_one_length - sqr_three_length) / (-2.0 * joint_one_length * joint_one_to_target_length), -1, 1)); - - // Calculate the angles of rotation. Angle 0 is the extension/contraction axis, while angle 1 is the rotation axis to align the triangle to the target - Vector3 axis_0 = bone_one_trans.origin.direction_to(bone_two_tip_trans.origin).cross(bone_one_trans.origin.direction_to(bone_two_trans.origin)); - Vector3 axis_1 = bone_one_trans.origin.direction_to(bone_two_tip_trans.origin).cross(bone_one_trans.origin.direction_to(target_trans.origin)); - - // Make a quaternion with the delta rotation needed to rotate the first joint into alignment and apply it to the transform. - Quaternion bone_one_quat = bone_one_trans.basis.get_rotation_quaternion(); - Quaternion rot_0 = Quaternion(bone_one_quat.inverse().xform(axis_0).normalized(), (ac_ab_1 - ac_ab_0)); - Quaternion rot_2 = Quaternion(bone_one_quat.inverse().xform(axis_1).normalized(), ac_at_0); - bone_one_trans.basis.set_quaternion(bone_one_quat * (rot_0 * rot_2)); - - skeleton->update_bone_rest_forward_vector(joint_one_bone_idx); - bone_one_trans.basis.rotate_local(skeleton->get_bone_axis_forward_vector(joint_one_bone_idx), joint_one_roll); - - // Apply the rotation to the first joint - bone_one_trans = skeleton->global_pose_to_local_pose(joint_one_bone_idx, bone_one_trans); - bone_one_trans.origin = Vector3(0, 0, 0); - skeleton->set_bone_pose_position(joint_one_bone_idx, bone_one_trans.origin); - skeleton->set_bone_pose_rotation(joint_one_bone_idx, bone_one_trans.basis.get_rotation_quaternion()); - skeleton->set_bone_pose_scale(joint_one_bone_idx, bone_one_trans.basis.get_scale()); - skeleton->force_update_bone_children_transforms(joint_one_bone_idx); - - if (use_pole_node) { - // Update bone_two_trans so its at the latest position, with the rotation of bone_one_trans taken into account, then look at the target. - bone_two_trans = skeleton->get_bone_global_pose(joint_two_bone_idx); - skeleton->update_bone_rest_forward_vector(joint_two_bone_idx); - Vector3 forward_vector = skeleton->get_bone_axis_forward_vector(joint_two_bone_idx); - bone_two_trans.basis.rotate_to_align(forward_vector, bone_two_trans.origin.direction_to(target_trans.origin)); - - skeleton->update_bone_rest_forward_vector(joint_two_bone_idx); - bone_two_trans.basis.rotate_local(skeleton->get_bone_axis_forward_vector(joint_two_bone_idx), joint_two_roll); - - bone_two_trans = skeleton->global_pose_to_local_pose(joint_two_bone_idx, bone_two_trans); - skeleton->set_bone_pose_position(joint_two_bone_idx, bone_two_trans.origin); - skeleton->set_bone_pose_rotation(joint_two_bone_idx, bone_two_trans.basis.get_rotation_quaternion()); - skeleton->set_bone_pose_scale(joint_two_bone_idx, bone_two_trans.basis.get_scale()); - - skeleton->force_update_bone_children_transforms(joint_two_bone_idx); - } else { - // Calculate the angles for the second joint using the law of cosigns, make a quaternion with the delta rotation needed to rotate the joint into - // alignment, and then apply it to the second joint. - real_t ba_bc_0 = Math::acos(CLAMP(bone_two_trans.origin.direction_to(bone_one_trans.origin).dot(bone_two_trans.origin.direction_to(bone_two_tip_trans.origin)), -1, 1)); - real_t ba_bc_1 = Math::acos(CLAMP((sqr_three_length - sqr_one_length - sqr_two_length) / (-2.0 * joint_one_length * joint_two_length), -1, 1)); - Quaternion bone_two_quat = bone_two_trans.basis.get_rotation_quaternion(); - Quaternion rot_1 = Quaternion(bone_two_quat.inverse().xform(axis_0).normalized(), (ba_bc_1 - ba_bc_0)); - bone_two_trans.basis.set_quaternion(bone_two_quat * rot_1); - - skeleton->update_bone_rest_forward_vector(joint_two_bone_idx); - bone_two_trans.basis.rotate_local(skeleton->get_bone_axis_forward_vector(joint_two_bone_idx), joint_two_roll); - - bone_two_trans = skeleton->global_pose_to_local_pose(joint_two_bone_idx, bone_two_trans); - bone_two_trans.origin = Vector3(0, 0, 0); - skeleton->set_bone_pose_position(joint_two_bone_idx, bone_two_trans.origin); - skeleton->set_bone_pose_rotation(joint_two_bone_idx, bone_two_trans.basis.get_rotation_quaternion()); - skeleton->set_bone_pose_scale(joint_two_bone_idx, bone_two_trans.basis.get_scale()); - - skeleton->force_update_bone_children_transforms(joint_two_bone_idx); - } + real_t joint_one_to_target_length = bone_one_trans.origin.distance_to(target_trans.origin); + if (joint_one_length + joint_two_length < joint_one_to_target_length) { + // Set the target *just* out of reach to straighten the bones + joint_one_to_target_length = joint_one_length + joint_two_length + 0.01; + } else if (joint_one_to_target_length < joint_one_length) { + // Place the target in reach so the solver doesn't do crazy things + joint_one_to_target_length = joint_one_length; + } + + // Get the square lengths for all three sides of the triangle we'll use to calculate the angles + real_t sqr_one_length = joint_one_length * joint_one_length; + real_t sqr_two_length = joint_two_length * joint_two_length; + real_t sqr_three_length = joint_one_to_target_length * joint_one_to_target_length; + + // Calculate the angles for the first joint using the law of cosigns + real_t ac_ab_0 = Math::acos(CLAMP(bone_two_tip_trans.origin.direction_to(bone_one_trans.origin).dot(bone_two_trans.origin.direction_to(bone_one_trans.origin)), -1, 1)); + real_t ac_at_0 = Math::acos(CLAMP(bone_one_trans.origin.direction_to(bone_two_tip_trans.origin).dot(bone_one_trans.origin.direction_to(target_trans.origin)), -1, 1)); + real_t ac_ab_1 = Math::acos(CLAMP((sqr_two_length - sqr_one_length - sqr_three_length) / (-2.0 * joint_one_length * joint_one_to_target_length), -1, 1)); + + // Calculate the angles of rotation. Angle 0 is the extension/contraction axis, while angle 1 is the rotation axis to align the triangle to the target + Vector3 axis_0 = bone_one_trans.origin.direction_to(bone_two_tip_trans.origin).cross(bone_one_trans.origin.direction_to(bone_two_trans.origin)); + Vector3 axis_1 = bone_one_trans.origin.direction_to(bone_two_tip_trans.origin).cross(bone_one_trans.origin.direction_to(target_trans.origin)); + + // Make a quaternion with the delta rotation needed to rotate the first joint into alignment and apply it to the transform. + Quaternion bone_one_quat = bone_one_trans.basis.get_rotation_quaternion(); + Quaternion rot_0 = Quaternion(bone_one_quat.inverse().xform(axis_0).normalized(), (ac_ab_1 - ac_ab_0)); + Quaternion rot_2 = Quaternion(bone_one_quat.inverse().xform(axis_1).normalized(), ac_at_0); + bone_one_trans.basis.set_quaternion(bone_one_quat * (rot_0 * rot_2)); + + bone_one_trans.basis.rotate_local(bone_one_forward, joint_one_roll); + + // Apply the rotation to the first joint + skeleton->set_bone_pose_rotation(joint_one_bone_idx, + (bone_one_parent_trans.basis.inverse() * bone_one_trans.basis).get_rotation_quaternion()); + Transform3D bone_two_parent_trans = bone_two_trans = bone_one_trans * bone_one_to_two_parent_trans; + bone_two_trans = bone_two_parent_trans * bone_two_local_pos; + + if (!pole_node.is_empty() || !pole_bone.is_empty()) { + // Update bone_two_trans so its at the latest position, with the rotation of bone_one_trans taken into account, then look at the target. + bone_two_trans.basis.rotate_to_align(bone_two_forward, bone_two_trans.origin.direction_to(target_trans.origin)); + } else { + // Calculate the angles for the second joint using the law of cosigns, make a quaternion with the delta rotation needed to rotate the joint into + // alignment, and then apply it to the second joint. + real_t ba_bc_0 = Math::acos(CLAMP(bone_two_trans.origin.direction_to(bone_one_trans.origin).dot(bone_two_trans.origin.direction_to(bone_two_tip_trans.origin)), -1, 1)); + real_t ba_bc_1 = Math::acos(CLAMP((sqr_three_length - sqr_one_length - sqr_two_length) / (-2.0 * joint_one_length * joint_two_length), -1, 1)); + Quaternion bone_two_quat = bone_two_trans.basis.get_rotation_quaternion(); + Quaternion rot_1 = Quaternion(bone_two_quat.inverse().xform(axis_0).normalized(), (ba_bc_1 - ba_bc_0)); + bone_two_trans.basis.set_quaternion(bone_two_quat * rot_1); + } + bone_two_trans.basis.rotate_local(bone_two_forward, joint_two_roll); + + skeleton->set_bone_pose_rotation(joint_two_bone_idx, + (bone_two_parent_trans.basis.inverse() * bone_two_trans.basis).get_rotation_quaternion()); +} + +void SkeletonModification3DTwoBoneIK::skeleton_changed(Skeleton3D *skeleton) { + target_cache = Variant(); + tip_cache = Variant(); + pole_cache = Variant(); + joint_one_bone_idx = UNCACHED_BONE_IDX; + joint_two_bone_idx = UNCACHED_BONE_IDX; + SkeletonModification3D::skeleton_changed(skeleton); +} + +bool SkeletonModification3DTwoBoneIK::is_bone_property(String p_property_name) const { + if (p_property_name == "target_bone" || p_property_name == "tip_bone" || p_property_name == "pole_bone" || p_property_name == "joint_one_bone" || p_property_name == "joint_two_bone") { + return true; + } + return SkeletonModification3D::is_bone_property(p_property_name); +} + +bool SkeletonModification3DTwoBoneIK::is_property_hidden(String p_property_name) const { + if ((p_property_name == "target_bone" && !target_node.is_empty()) || + (p_property_name == "tip_bone" && !tip_node.is_empty()) || + (p_property_name == "pole_bone" && !pole_node.is_empty())) { + return true; + } + return SkeletonModification3D::is_property_hidden(p_property_name); +} + +TypedArray SkeletonModification3DTwoBoneIK::get_configuration_warnings() const { + TypedArray ret = SkeletonModification3D::get_configuration_warnings(); + if (!get_skeleton()) { + return ret; + } + if (!_cache_target(target_cache, target_node, target_bone)) { + ret.append(vformat("Target %s %s was not found.", target_node.is_empty() ? "bone" : "node", target_node.is_empty() ? target_bone : (String)target_node)); + } + if (!tip_node.is_empty() || !tip_bone.is_empty()) { // optional + if (!_cache_target(tip_cache, tip_node, tip_bone)) { + ret.append(vformat("Target %s %s was not found.", tip_node.is_empty() ? "bone" : "node", tip_node.is_empty() ? tip_bone : (String)tip_node)); } } + if (!pole_node.is_empty() || !pole_bone.is_empty()) { // optional + if (!_cache_target(pole_cache, pole_node, pole_bone)) { + ret.append(vformat("Target %s %s was not found.", pole_node.is_empty() ? "bone" : "node", pole_node.is_empty() ? pole_bone : (String)pole_node)); + } + } + if (!_cache_bone(joint_one_bone_idx, joint_one_bone_name)) { + ret.append(vformat("Joint one bone %s was not found.", joint_one_bone_name)); + } + if (!_cache_bone(joint_two_bone_idx, joint_two_bone_name)) { + ret.append(vformat("Joint two bone %s was not found.", joint_two_bone_name)); + } + return ret; } diff --git a/scene/3d/skeleton_modification_3d_twoboneik.h b/scene/3d/skeleton_modification_3d_twoboneik.h index e9db6404bf6e..abf1ece5615f 100644 --- a/scene/3d/skeleton_modification_3d_twoboneik.h +++ b/scene/3d/skeleton_modification_3d_twoboneik.h @@ -39,20 +39,22 @@ class SkeletonModification3DTwoBoneIK : public SkeletonModification3D { private: NodePath target_node; - ObjectID target_node_cache; + String target_bone; + mutable Variant target_cache; - bool use_tip_node = false; NodePath tip_node; - ObjectID tip_node_cache; + String tip_bone; + mutable Variant tip_cache; - bool use_pole_node = false; NodePath pole_node; - ObjectID pole_node_cache; + String pole_bone; + mutable Variant pole_cache; - String joint_one_bone_name = ""; - int joint_one_bone_idx = -1; - String joint_two_bone_name = ""; - int joint_two_bone_idx = -1; + mutable int bone_idx = UNCACHED_BONE_IDX; + String joint_one_bone_name; + mutable int joint_one_bone_idx = UNCACHED_BONE_IDX; + String joint_two_bone_name; + mutable int joint_two_bone_idx = UNCACHED_BONE_IDX; bool auto_calculate_joint_length = false; real_t joint_one_length = -1; @@ -61,28 +63,27 @@ class SkeletonModification3DTwoBoneIK : public SkeletonModification3D { real_t joint_one_roll = 0; real_t joint_two_roll = 0; - void update_cache_target(); - void update_cache_tip(); - void update_cache_pole(); - protected: - void _notification(int32_t p_what); + void execute(real_t delta) override; static void _bind_methods(); - bool _get(const StringName &p_path, Variant &r_ret) const; - bool _set(const StringName &p_path, const Variant &p_value); - void _get_property_list(List *p_list) const; + void skeleton_changed(Skeleton3D *skeleton) override; + bool is_property_hidden(String property_name) const override; + bool is_bone_property(String property_name) const override; + TypedArray get_configuration_warnings() const override; public: + void set_target_bone(const String &p_target_node); + String get_target_bone() const; void set_target_node(const NodePath &p_target_node); NodePath get_target_node() const; - void set_use_tip_node(const bool p_use_tip_node); - bool get_use_tip_node() const; + void set_tip_bone(const String &p_tip_bone); + String get_tip_bone() const; void set_tip_node(const NodePath &p_tip_node); NodePath get_tip_node() const; - void set_use_pole_node(const bool p_use_pole_node); - bool get_use_pole_node() const; + void set_pole_bone(const String &p_pole_bone); + String get_pole_bone() const; void set_pole_node(const NodePath &p_pole_node); NodePath get_pole_node() const; @@ -90,17 +91,13 @@ class SkeletonModification3DTwoBoneIK : public SkeletonModification3D { bool get_auto_calculate_joint_length() const; void calculate_joint_lengths(); - void set_joint_one_bone_name(String p_bone_name); - String get_joint_one_bone_name() const; - void set_joint_one_bone_idx(int p_bone_idx); - int get_joint_one_bone_idx() const; + void set_joint_one_bone(String p_bone_name); + String get_joint_one_bone() const; void set_joint_one_length(real_t p_length); real_t get_joint_one_length() const; - void set_joint_two_bone_name(String p_bone_name); - String get_joint_two_bone_name() const; - void set_joint_two_bone_idx(int p_bone_idx); - int get_joint_two_bone_idx() const; + void set_joint_two_bone(String p_bone_name); + String get_joint_two_bone() const; void set_joint_two_length(real_t p_length); real_t get_joint_two_length() const;