Added back the skeleton modules.
7
modules/skeleton_2d/.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
.import
|
||||
*.d
|
||||
*.o
|
||||
*.meta
|
||||
*.obj
|
||||
*.pyc
|
||||
*.bc
|
23
modules/skeleton_2d/SCsub
Normal file
@ -0,0 +1,23 @@
|
||||
import os
|
||||
|
||||
Import('env')
|
||||
|
||||
module_env = env.Clone()
|
||||
|
||||
module_env.add_source_files(env.modules_sources,"register_types.cpp")
|
||||
|
||||
module_env.add_source_files(env.modules_sources,"nodes/skeleton_2d.cpp")
|
||||
module_env.add_source_files(env.modules_sources,"nodes/physical_bone_2d.cpp")
|
||||
|
||||
module_env.add_source_files(env.modules_sources,"resources/skeleton_modification_2d.cpp")
|
||||
module_env.add_source_files(env.modules_sources,"resources/skeleton_modification_2d_ccdik.cpp")
|
||||
module_env.add_source_files(env.modules_sources,"resources/skeleton_modification_2d_fabrik.cpp")
|
||||
module_env.add_source_files(env.modules_sources,"resources/skeleton_modification_2d_jiggle.cpp")
|
||||
module_env.add_source_files(env.modules_sources,"resources/skeleton_modification_2d_lookat.cpp")
|
||||
module_env.add_source_files(env.modules_sources,"resources/skeleton_modification_2d_physicalbones.cpp")
|
||||
module_env.add_source_files(env.modules_sources,"resources/skeleton_modification_2d_stackholder.cpp")
|
||||
module_env.add_source_files(env.modules_sources,"resources/skeleton_modification_2d_twoboneik.cpp")
|
||||
module_env.add_source_files(env.modules_sources,"resources/skeleton_modification_stack_2d.cpp")
|
||||
|
||||
if env["tools"]:
|
||||
module_env.add_source_files(env.modules_sources,"editor/skeleton_2d_editor_plugin.cpp")
|
30
modules/skeleton_2d/config.py
Normal file
@ -0,0 +1,30 @@
|
||||
|
||||
|
||||
def can_build(env, platform):
|
||||
return True
|
||||
|
||||
|
||||
def configure(env):
|
||||
pass
|
||||
|
||||
|
||||
def get_doc_classes():
|
||||
return [
|
||||
"Skeleton2D",
|
||||
"PhysicalBone2D",
|
||||
"Bone2D",
|
||||
|
||||
"SkeletonModificationStack2D",
|
||||
|
||||
"SkeletonModification2D",
|
||||
"SkeletonModification2DCCDIK",
|
||||
"SkeletonModification2DFABRIK",
|
||||
"SkeletonModification2DJiggle",
|
||||
"SkeletonModification2DLookAt",
|
||||
"SkeletonModification2DPhysicalBones",
|
||||
"SkeletonModification2DStackHolder",
|
||||
"SkeletonModification2DTwoBoneIK",
|
||||
]
|
||||
|
||||
def get_doc_path():
|
||||
return "doc_classes"
|
95
modules/skeleton_2d/doc_classes/Bone2D.xml
Normal file
@ -0,0 +1,95 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="Bone2D" inherits="Node2D" version="4.2">
|
||||
<brief_description>
|
||||
Joint used with [Skeleton2D] to control and animate other nodes.
|
||||
</brief_description>
|
||||
<description>
|
||||
Use a hierarchy of [code]Bone2D[/code] bound to a [Skeleton2D] to control, and animate other [Node2D] nodes.
|
||||
You can use [code]Bone2D[/code] and [code]Skeleton2D[/code] nodes to animate 2D meshes created with the Polygon 2D UV editor.
|
||||
Each bone has a [member rest] transform that you can reset to with [method apply_rest]. These rest poses are relative to the bone's parent.
|
||||
If in the editor, you can set the rest pose of an entire skeleton using a menu option, from the code, you need to iterate over the bones to set their individual rest poses.
|
||||
</description>
|
||||
<tutorials>
|
||||
</tutorials>
|
||||
<methods>
|
||||
<method name="apply_rest">
|
||||
<return type="void" />
|
||||
<description>
|
||||
Stores the node's current transforms in [member rest].
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_autocalculate_length_and_angle" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<description>
|
||||
Returns whether this [code]Bone2D[/code] node is going to autocalculate its length and bone angle using its first [code]Bone2D[/code] child node, if one exists. If there are no [code]Bone2D[/code] children, then it cannot autocalculate these values and will print a warning.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_bone_angle" qualifiers="const">
|
||||
<return type="float" />
|
||||
<description>
|
||||
Returns the angle of the bone in the [code]Bone2D[/code] node.
|
||||
[b]Note:[/b] This is different from the [code]Bone2D[/code]'s rotation. The bone angle is the rotation of the bone shown by the [code]Bone2D[/code] gizmo, and because [code]Bone2D[/code] bones are based on positions, this can vary from the actual rotation of the [code]Bone2D[/code] node.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_default_length" qualifiers="const">
|
||||
<return type="float" />
|
||||
<description>
|
||||
Deprecated. Please use [code]get_length[/code] instead.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_index_in_skeleton" qualifiers="const">
|
||||
<return type="int" />
|
||||
<description>
|
||||
Returns the node's index as part of the entire skeleton. See [Skeleton2D].
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_length" qualifiers="const">
|
||||
<return type="float" />
|
||||
<description>
|
||||
Returns the length of the bone in the [code]Bone2D[/code] node.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_skeleton_rest" qualifiers="const">
|
||||
<return type="Transform2D" />
|
||||
<description>
|
||||
Returns the node's [member rest] [code]Transform2D[/code] if it doesn't have a parent, or its rest pose relative to its parent.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_autocalculate_length_and_angle">
|
||||
<return type="void" />
|
||||
<argument index="0" name="auto_calculate" type="bool" />
|
||||
<description>
|
||||
When set to [code]true[/code], the [code]Bone2D[/code] node will attempt to automatically calculate the bone angle and length using the first child [code]Bone2D[/code] node, if one exists. If none exist, the [code]Bone2D[/code] cannot automatically calculate these values and will print a warning.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_bone_angle">
|
||||
<return type="void" />
|
||||
<argument index="0" name="angle" type="float" />
|
||||
<description>
|
||||
Sets the bone angle for the [code]Bone2D[/code] node. This is typically set to the rotation from the [code]Bone2D[/code] node to a child [code]Bone2D[/code] node.
|
||||
[b]Note:[/b] This is different from the [code]Bone2D[/code]'s rotation. The bone angle is the rotation of the bone shown by the [code]Bone2D[/code] gizmo, and because [code]Bone2D[/code] bones are based on positions, this can vary from the actual rotation of the [code]Bone2D[/code] node.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_default_length">
|
||||
<return type="void" />
|
||||
<argument index="0" name="default_length" type="float" />
|
||||
<description>
|
||||
Deprecated. Please use [code]set_length[/code] instead.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_length">
|
||||
<return type="void" />
|
||||
<argument index="0" name="length" type="float" />
|
||||
<description>
|
||||
Sets the length of the bone in the [code]Bone2D[/code] node.
|
||||
</description>
|
||||
</method>
|
||||
</methods>
|
||||
<members>
|
||||
<member name="rest" type="Transform2D" setter="set_rest" getter="get_rest" default="Transform2D( 0, 0, 0, 0, 0, 0 )">
|
||||
Rest transform of the bone. You can reset the node's transforms to this value using [method apply_rest].
|
||||
</member>
|
||||
</members>
|
||||
<constants>
|
||||
</constants>
|
||||
</class>
|
47
modules/skeleton_2d/doc_classes/PhysicalBone2D.xml
Normal file
@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="PhysicalBone2D" inherits="RigidBody2D" version="4.2">
|
||||
<brief_description>
|
||||
A 2D node that can be used for physically aware bones in 2D.
|
||||
</brief_description>
|
||||
<description>
|
||||
The [code]PhysicalBone2D[/code] node is a [RigidDynamicBody2D]-based node that can be used to make [Bone2D] nodes in a [Skeleton2D] react to physics. This node is very similar to the [PhysicalBone3D] node, just for 2D instead of 3D.
|
||||
[b]Note:[/b] To have the Bone2D nodes visually follow the [code]PhysicalBone2D[/code] node, use a [SkeletonModification2DPhysicalBones] modification on the [Skeleton2D] node with the [Bone2D] nodes.
|
||||
[b]Note:[/b] The PhysicalBone2D node does not automatically create a [Joint2D] node to keep [code]PhysicalBone2D[/code] nodes together. You will need to create these manually. For most cases, you want to use a [PinJoint2D] node. The [code]PhysicalBone2D[/code] node can automatically configure the [Joint2D] node once it's been created as a child node.
|
||||
</description>
|
||||
<tutorials>
|
||||
</tutorials>
|
||||
<methods>
|
||||
<method name="get_joint" qualifiers="const">
|
||||
<return type="Joint2D" />
|
||||
<description>
|
||||
Returns the first [Joint2D] child node, if one exists. This is mainly a helper function to make it easier to get the [Joint2D] that the [code]PhysicalBone2D[/code] is autoconfiguring.
|
||||
</description>
|
||||
</method>
|
||||
<method name="is_simulating_physics" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<description>
|
||||
Returns a boolean that indicates whether the [code]PhysicalBone2D[/code] node is running and simulating using the Godot 2D physics engine. When [code]true[/code], the PhysicalBone2D node is using physics.
|
||||
</description>
|
||||
</method>
|
||||
</methods>
|
||||
<members>
|
||||
<member name="auto_configure_joint" type="bool" setter="set_auto_configure_joint" getter="get_auto_configure_joint" default="true">
|
||||
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].
|
||||
</member>
|
||||
<member name="bone2d_index" type="int" setter="set_bone2d_index" getter="get_bone2d_index" default="-1">
|
||||
The index of the [Bone2D] node that this [code]PhysicalBone2D[/code] node is supposed to be simulating.
|
||||
</member>
|
||||
<member name="bone2d_nodepath" type="NodePath" setter="set_bone2d_nodepath" getter="get_bone2d_nodepath" default="NodePath("")">
|
||||
The [NodePath] to the [Bone2D] node that this [code]PhysicalBone2D[/code] node is supposed to be simulating.
|
||||
</member>
|
||||
<member name="follow_bone_when_simulating" type="bool" setter="set_follow_bone_when_simulating" getter="get_follow_bone_when_simulating" default="false">
|
||||
If [code]true[/code], the [code]PhysicalBone2D[/code] will keep the transform of the bone it is bound to when simulating physics.
|
||||
</member>
|
||||
<member name="simulate_physics" type="bool" setter="set_simulate_physics" getter="get_simulate_physics" default="false">
|
||||
If [code]true[/code], the [code]PhysicalBone2D[/code] will start simulating using physics. If [code]false[/code], the [code]PhysicalBone2D[/code] will follow the transform of the [Bone2D] node.
|
||||
[b]Note:[/b] To have the Bone2D nodes visually follow the [code]PhysicalBone2D[/code] node, use a [SkeletonModification2DPhysicalBones] modification on the [Skeleton2D] node with the [Bone2D] nodes.
|
||||
</member>
|
||||
</members>
|
||||
<constants>
|
||||
</constants>
|
||||
</class>
|
82
modules/skeleton_2d/doc_classes/Skeleton2D.xml
Normal file
@ -0,0 +1,82 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="Skeleton2D" inherits="Node2D" version="4.2">
|
||||
<brief_description>
|
||||
Skeleton for 2D characters and animated objects.
|
||||
</brief_description>
|
||||
<description>
|
||||
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.
|
||||
</description>
|
||||
<tutorials>
|
||||
<link>$DOCS_URL/tutorials/animation/2d_skeletons.md</link>
|
||||
</tutorials>
|
||||
<methods>
|
||||
<method name="execute_modifications">
|
||||
<return type="void" />
|
||||
<argument index="0" name="delta" type="float" />
|
||||
<argument index="1" name="execution_mode" type="int" />
|
||||
<description>
|
||||
Executes all the modifications on the [SkeletonModificationStack2D], if the Skeleton3D has one assigned.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_bone">
|
||||
<return type="Bone2D" />
|
||||
<argument index="0" name="idx" type="int" />
|
||||
<description>
|
||||
Returns a [Bone2D] from the node hierarchy parented by Skeleton2D. The object to return is identified by the parameter [code]idx[/code]. Bones are indexed by descending the node hierarchy from top to bottom, adding the children of each branch before moving to the next sibling.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_bone_count" qualifiers="const">
|
||||
<return type="int" />
|
||||
<description>
|
||||
Returns the number of [Bone2D] nodes in the node hierarchy parented by Skeleton2D.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_bone_local_pose_override">
|
||||
<return type="Transform2D" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<description>
|
||||
Returns the local pose override transform for [code]bone_idx[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_modification_stack" qualifiers="const">
|
||||
<return type="SkeletonModificationStack2D" />
|
||||
<description>
|
||||
Returns the [SkeletonModificationStack2D] attached to this skeleton, if one exists.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_skeleton" qualifiers="const">
|
||||
<return type="RID" />
|
||||
<description>
|
||||
Returns the [RID] of a Skeleton2D instance.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_bone_local_pose_override">
|
||||
<return type="void" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<argument index="1" name="override_pose" type="Transform2D" />
|
||||
<argument index="2" name="strength" type="float" />
|
||||
<argument index="3" name="persistent" type="bool" />
|
||||
<description>
|
||||
Sets the local pose transform, [code]pose[/code], for the bone at [code]bone_idx[/code].
|
||||
[code]amount[/code] is the interpolation strength that will be used when applying the pose, and [code]persistent[/code] determines if the applied pose will remain.
|
||||
[b]Note:[/b] The pose transform needs to be a local transform relative to the [Bone2D] node at [code]bone_idx[/code]!
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_modification_stack">
|
||||
<return type="void" />
|
||||
<argument index="0" name="modification_stack" type="SkeletonModificationStack2D" />
|
||||
<description>
|
||||
Sets the [SkeletonModificationStack2D] attached to this skeleton.
|
||||
</description>
|
||||
</method>
|
||||
</methods>
|
||||
<signals>
|
||||
<signal name="bone_setup_changed">
|
||||
<description>
|
||||
Emitted when the [Bone2D] setup attached to this skeletons changes. This is primarily used internally within the skeleton.
|
||||
</description>
|
||||
</signal>
|
||||
</signals>
|
||||
<constants>
|
||||
</constants>
|
||||
</class>
|
99
modules/skeleton_2d/doc_classes/SkeletonModification2D.xml
Normal file
@ -0,0 +1,99 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="SkeletonModification2D" inherits="Resource" version="4.2">
|
||||
<brief_description>
|
||||
A resource that operates on [Bone2D] nodes in a [Skeleton2D].
|
||||
</brief_description>
|
||||
<description>
|
||||
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.
|
||||
</description>
|
||||
<tutorials>
|
||||
</tutorials>
|
||||
<methods>
|
||||
<method name="_draw_editor_gizmo" qualifiers="virtual">
|
||||
<return type="void" />
|
||||
<description>
|
||||
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.
|
||||
</description>
|
||||
</method>
|
||||
<method name="_execute" qualifiers="virtual">
|
||||
<return type="void" />
|
||||
<argument index="0" name="delta" type="Object" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="_setup_modification" qualifiers="virtual">
|
||||
<return type="void" />
|
||||
<argument index="0" name="stack" type="SkeletonModificationStack2D" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="clamp_angle">
|
||||
<return type="float" />
|
||||
<argument index="0" name="angle" type="float" />
|
||||
<argument index="1" name="min" type="float" />
|
||||
<argument index="2" name="max" type="float" />
|
||||
<argument index="3" name="invert" type="bool" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="draw_editor_gizmo">
|
||||
<return type="void" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="execute">
|
||||
<return type="void" />
|
||||
<argument index="0" name="delta" type="float" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_editor_draw_gizmo" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<description>
|
||||
Returns whether this modification will call [method _draw_editor_gizmo] in the Godot editor to draw modification-specific gizmos.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_is_setup" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<description>
|
||||
Returns whether this modification has been successfully setup or not.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_modification_stack">
|
||||
<return type="SkeletonModificationStack2D" />
|
||||
<description>
|
||||
Returns the [SkeletonModificationStack2D] that this modification is bound to. Through the modification stack, you can access the Skeleton3D the modification is operating on.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_editor_draw_gizmo">
|
||||
<return type="void" />
|
||||
<argument index="0" name="draw_gizmo" type="bool" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_is_setup">
|
||||
<return type="void" />
|
||||
<argument index="0" name="is_setup" type="bool" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="setup_modification">
|
||||
<return type="void" />
|
||||
<argument index="0" name="stack" type="SkeletonModificationStack2D" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
</methods>
|
||||
<members>
|
||||
<member name="enabled" type="bool" setter="set_enabled" getter="get_enabled" default="true">
|
||||
If [code]true[/code], the modification's [method _execute] function will be called by the [SkeletonModificationStack2D].
|
||||
</member>
|
||||
<member name="execution_mode" type="int" setter="set_execution_mode" getter="get_execution_mode" default="0">
|
||||
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.
|
||||
</member>
|
||||
</members>
|
||||
<constants>
|
||||
</constants>
|
||||
</class>
|
120
modules/skeleton_2d/doc_classes/SkeletonModification2DCCDIK.xml
Normal file
@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="SkeletonModification2DCCDIK" inherits="SkeletonModification2D" version="4.2">
|
||||
<brief_description>
|
||||
A modification that uses CCDIK to manipulate a series of bones to reach a target in 2D.
|
||||
</brief_description>
|
||||
<description>
|
||||
This [SkeletonModification2D] uses an algorithm called Cyclic Coordinate Descent Inverse Kinematics, or CCDIK, to manipulate a chain of bones in a [Skeleton2D] so it reaches a defined target.
|
||||
CCDIK works by rotating a set of bones, typically called a "bone chain", on a single axis. Each bone is rotated to face the target from the tip (by default), which over a chain of bones allow it to rotate properly to reach the target. Because the bones only rotate on a single axis, CCDIK [i]can[/i] look more robotic than other IK solvers.
|
||||
[b]Note:[/b] The CCDIK modifier has [code]ccdik_joints[/code], which are the data objects that hold the data for each joint in the CCDIK chain. This is different from a bone! CCDIK joints hold the data needed for each bone in the bone chain used by CCDIK.
|
||||
CCDIK also fully supports angle constraints, allowing for more control over how a solution is met.
|
||||
</description>
|
||||
<tutorials>
|
||||
</tutorials>
|
||||
<methods>
|
||||
<method name="get_ccdik_joint_bone2d_node" qualifiers="const">
|
||||
<return type="NodePath" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_ccdik_joint_bone_index" qualifiers="const">
|
||||
<return type="int" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_ccdik_joint_constraint_angle_invert" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_ccdik_joint_constraint_angle_max" qualifiers="const">
|
||||
<return type="float" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_ccdik_joint_constraint_angle_min" qualifiers="const">
|
||||
<return type="float" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_ccdik_joint_enable_constraint" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_ccdik_joint_rotate_from_joint" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_ccdik_joint_bone2d_node">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="bone2d_nodepath" type="NodePath" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_ccdik_joint_bone_index">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="bone_idx" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_ccdik_joint_constraint_angle_invert">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="invert" type="bool" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_ccdik_joint_constraint_angle_max">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="angle_max" type="float" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_ccdik_joint_constraint_angle_min">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="angle_min" type="float" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_ccdik_joint_enable_constraint">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="enable_constraint" type="bool" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_ccdik_joint_rotate_from_joint">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="rotate_from_joint" type="bool" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
</methods>
|
||||
<members>
|
||||
<member name="ccdik_data_chain_length" type="int" setter="set_ccdik_data_chain_length" getter="get_ccdik_data_chain_length" default="0">
|
||||
The amount of CCDIK joints in the CCDIK modification.
|
||||
</member>
|
||||
<member name="target_nodepath" type="NodePath" setter="set_target_node" getter="get_target_node" default="NodePath("")">
|
||||
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.
|
||||
</member>
|
||||
<member name="tip_nodepath" type="NodePath" setter="set_tip_node" getter="get_tip_node" default="NodePath("")">
|
||||
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.
|
||||
</member>
|
||||
</members>
|
||||
<constants>
|
||||
</constants>
|
||||
</class>
|
@ -0,0 +1,79 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="SkeletonModification2DFABRIK" inherits="SkeletonModification2D" version="4.2">
|
||||
<brief_description>
|
||||
A modification that uses FABRIK to manipulate a series of [Bone2D] nodes to reach a target.
|
||||
</brief_description>
|
||||
<description>
|
||||
This [SkeletonModification2D] uses an algorithm called Forward And Backward Reaching Inverse Kinematics, or FABRIK, to rotate a bone chain so that it reaches a target.
|
||||
FABRIK works by knowing the positions and lengths of a series of bones, typically called a "bone chain". It first starts by running a forward pass, which places the final bone at the target's position. Then all other bones are moved towards the tip bone, so they stay at the defined bone length away. Then a backwards pass is performed, where the root/first bone in the FABRIK chain is placed back at the origin. then all other bones are moved so they stay at the defined bone length away. This positions the bone chain so that it reaches the target when possible, but all of the bones stay the correct length away from each other.
|
||||
Because of how FABRIK works, it often gives more natural results than those seen in [SkeletonModification2DCCDIK]. FABRIK also supports angle constraints, which are fully taken into account when solving.
|
||||
[b]Note:[/b] The FABRIK modifier has [code]fabrik_joints[/code], which are the data objects that hold the data for each joint in the FABRIK chain. This is different from [Bone2D] nodes! FABRIK joints hold the data needed for each [Bone2D] in the bone chain used by FABRIK.
|
||||
To help control how the FABRIK joints move, a magnet vector can be passed, which can nudge the bones in a certain direction prior to solving, giving a level of control over the final result.
|
||||
</description>
|
||||
<tutorials>
|
||||
</tutorials>
|
||||
<methods>
|
||||
<method name="get_fabrik_joint_bone2d_node" qualifiers="const">
|
||||
<return type="NodePath" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_fabrik_joint_bone_index" qualifiers="const">
|
||||
<return type="int" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_fabrik_joint_magnet_position" qualifiers="const">
|
||||
<return type="Vector2" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_fabrik_joint_use_target_rotation" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_fabrik_joint_bone2d_node">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="bone2d_nodepath" type="NodePath" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_fabrik_joint_bone_index">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="bone_idx" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_fabrik_joint_magnet_position">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="magnet_position" type="Vector2" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_fabrik_joint_use_target_rotation">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="use_target_rotation" type="bool" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
</methods>
|
||||
<members>
|
||||
<member name="fabrik_data_chain_length" type="int" setter="set_fabrik_data_chain_length" getter="get_fabrik_data_chain_length" default="0">
|
||||
The amount of FABRIK joints in the FABRIK modification.
|
||||
</member>
|
||||
<member name="target_nodepath" type="NodePath" setter="set_target_node" getter="get_target_node" default="NodePath("")">
|
||||
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.
|
||||
</member>
|
||||
</members>
|
||||
<constants>
|
||||
</constants>
|
||||
</class>
|
168
modules/skeleton_2d/doc_classes/SkeletonModification2DJiggle.xml
Normal file
@ -0,0 +1,168 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="SkeletonModification2DJiggle" inherits="SkeletonModification2D" version="4.2">
|
||||
<brief_description>
|
||||
A modification that jiggles [Bone2D] nodes as they move towards a target.
|
||||
</brief_description>
|
||||
<description>
|
||||
This modification moves a series of bones, typically called a bone chain, towards a target. What makes this modification special is that it calculates the velocity and acceleration for each bone in the bone chain, and runs a very light physics-like calculation using the inputted values. This allows the bones to overshoot the target and "jiggle" around. It can be configured to act more like a spring, or sway around like cloth might.
|
||||
This modification is useful for adding additional motion to things like hair, the edges of clothing, and more. It has several settings to that allow control over how the joint moves when the target moves.
|
||||
[b]Note:[/b] The Jiggle modifier has [code]jiggle_joints[/code], which are the data objects that hold the data for each joint in the Jiggle chain. This is different from than [Bone2D] nodes! Jiggle joints hold the data needed for each [Bone2D] in the bone chain used by the Jiggle modification.
|
||||
</description>
|
||||
<tutorials>
|
||||
</tutorials>
|
||||
<methods>
|
||||
<method name="get_collision_mask" qualifiers="const">
|
||||
<return type="int" />
|
||||
<description>
|
||||
Returns the collision mask used by the Jiggle modifier when collisions are enabled.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_jiggle_joint_bone2d_node" qualifiers="const">
|
||||
<return type="NodePath" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_jiggle_joint_bone_index" qualifiers="const">
|
||||
<return type="int" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_jiggle_joint_damping" qualifiers="const">
|
||||
<return type="float" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_jiggle_joint_gravity" qualifiers="const">
|
||||
<return type="Vector2" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_jiggle_joint_mass" qualifiers="const">
|
||||
<return type="float" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_jiggle_joint_override" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_jiggle_joint_stiffness" qualifiers="const">
|
||||
<return type="float" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_jiggle_joint_use_gravity" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_use_colliders" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<description>
|
||||
Returns whether the jiggle modifier is taking physics colliders into account when solving.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_collision_mask">
|
||||
<return type="void" />
|
||||
<argument index="0" name="collision_mask" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_jiggle_joint_bone2d_node">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="bone2d_node" type="NodePath" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_jiggle_joint_bone_index">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="bone_idx" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_jiggle_joint_damping">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="damping" type="float" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_jiggle_joint_gravity">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="gravity" type="Vector2" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_jiggle_joint_mass">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="mass" type="float" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_jiggle_joint_override">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="override" type="bool" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_jiggle_joint_stiffness">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="stiffness" type="float" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_jiggle_joint_use_gravity">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="use_gravity" type="bool" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_use_colliders">
|
||||
<return type="void" />
|
||||
<argument index="0" name="use_colliders" type="bool" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
</methods>
|
||||
<members>
|
||||
<member name="damping" type="float" setter="set_damping" getter="get_damping" default="0.75">
|
||||
The default amount of dampening applied to the Jiggle joints, if they are not overridden. Higher values lead to more of the calculated velocity being applied.
|
||||
</member>
|
||||
<member name="gravity" type="Vector2" setter="set_gravity" getter="get_gravity" default="Vector2( 0, 6 )">
|
||||
The default amount of gravity applied to the Jiggle joints, if they are not overridden.
|
||||
</member>
|
||||
<member name="jiggle_data_chain_length" type="int" setter="set_jiggle_data_chain_length" getter="get_jiggle_data_chain_length" default="0">
|
||||
The amount of Jiggle joints in the Jiggle modification.
|
||||
</member>
|
||||
<member name="mass" type="float" setter="set_mass" getter="get_mass" default="0.75">
|
||||
The default amount of mass assigned to the Jiggle joints, if they are not overridden. Higher values lead to faster movements and more overshooting.
|
||||
</member>
|
||||
<member name="stiffness" type="float" setter="set_stiffness" getter="get_stiffness" default="3.0">
|
||||
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.
|
||||
</member>
|
||||
<member name="target_nodepath" type="NodePath" setter="set_target_node" getter="get_target_node" default="NodePath("")">
|
||||
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.
|
||||
</member>
|
||||
<member name="use_gravity" type="bool" setter="set_use_gravity" getter="get_use_gravity" default="false">
|
||||
Whether the gravity vector, [member gravity], should be applied to the Jiggle joints, assuming they are not overriding the default settings.
|
||||
</member>
|
||||
</members>
|
||||
<constants>
|
||||
</constants>
|
||||
</class>
|
@ -0,0 +1,86 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="SkeletonModification2DLookAt" inherits="SkeletonModification2D" version="4.2">
|
||||
<brief_description>
|
||||
A modification that rotates a [Bone2D] node to look at a target.
|
||||
</brief_description>
|
||||
<description>
|
||||
This [SkeletonModification2D] rotates a bone to look a target. This is extremely helpful for moving character's head to look at the player, rotating a turret to look at a target, or any other case where you want to make a bone rotate towards something quickly and easily.
|
||||
</description>
|
||||
<tutorials>
|
||||
</tutorials>
|
||||
<methods>
|
||||
<method name="get_additional_rotation" qualifiers="const">
|
||||
<return type="float" />
|
||||
<description>
|
||||
Returns the amount of additional rotation that is applied after the LookAt modification executes.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_constraint_angle_invert" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<description>
|
||||
Returns whether the constraints to this modification are inverted or not.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_constraint_angle_max" qualifiers="const">
|
||||
<return type="float" />
|
||||
<description>
|
||||
Returns the constraint's maximum allowed angle.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_constraint_angle_min" qualifiers="const">
|
||||
<return type="float" />
|
||||
<description>
|
||||
Returns the constraint's minimum allowed angle.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_enable_constraint" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<description>
|
||||
Returns [code]true[/code] if the LookAt modification is using constraints.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_additional_rotation">
|
||||
<return type="void" />
|
||||
<argument index="0" name="rotation" type="float" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_constraint_angle_invert">
|
||||
<return type="void" />
|
||||
<argument index="0" name="invert" type="bool" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_constraint_angle_max">
|
||||
<return type="void" />
|
||||
<argument index="0" name="angle_max" type="float" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_constraint_angle_min">
|
||||
<return type="void" />
|
||||
<argument index="0" name="angle_min" type="float" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_enable_constraint">
|
||||
<return type="void" />
|
||||
<argument index="0" name="enable_constraint" type="bool" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
</methods>
|
||||
<members>
|
||||
<member name="bone2d_node" type="NodePath" setter="set_bone2d_node" getter="get_bone2d_node" default="NodePath("")">
|
||||
The [Bone2D] node that the modification will operate on.
|
||||
</member>
|
||||
<member name="bone_index" type="int" setter="set_bone_index" getter="get_bone_index" default="-1">
|
||||
The index of the [Bone2D] node that the modification will oeprate on.
|
||||
</member>
|
||||
<member name="target_nodepath" type="NodePath" setter="set_target_node" getter="get_target_node" default="NodePath("")">
|
||||
The NodePath to the node that is the target for the LookAt modification. This node is what the modification will rotate the [Bone2D] to.
|
||||
</member>
|
||||
</members>
|
||||
<constants>
|
||||
</constants>
|
||||
</class>
|
@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="SkeletonModification2DPhysicalBones" inherits="SkeletonModification2D" version="4.2">
|
||||
<brief_description>
|
||||
A modification that applies the transforms of [PhysicalBone2D] nodes to [Bone2D] nodes.
|
||||
</brief_description>
|
||||
<description>
|
||||
This modification takes the transforms of [PhysicalBone2D] nodes and applies them to [Bone2D] nodes. This allows the [Bone2D] nodes to react to physics thanks to the linked [PhysicalBone2D] nodes.
|
||||
</description>
|
||||
<tutorials>
|
||||
</tutorials>
|
||||
<methods>
|
||||
<method name="fetch_physical_bones">
|
||||
<return type="void" />
|
||||
<description>
|
||||
Empties the list of [PhysicalBone2D] nodes and populates it will all [PhysicalBone2D] nodes that are children of the [Skeleton2D].
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_physical_bone_node" qualifiers="const">
|
||||
<return type="NodePath" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_physical_bone_node">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="physicalbone2d_node" type="NodePath" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="start_simulation">
|
||||
<return type="void" />
|
||||
<argument index="0" name="bones" type="PoolStringArray" default="PoolStringArray( )" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="stop_simulation">
|
||||
<return type="void" />
|
||||
<argument index="0" name="bones" type="PoolStringArray" default="PoolStringArray( )" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
</methods>
|
||||
<members>
|
||||
<member name="physical_bone_chain_length" type="int" setter="set_physical_bone_chain_length" getter="get_physical_bone_chain_length" default="0">
|
||||
The amount of [PhysicalBone2D] nodes linked in this modification.
|
||||
</member>
|
||||
</members>
|
||||
<constants>
|
||||
</constants>
|
||||
</class>
|
@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="SkeletonModification2DStackHolder" inherits="SkeletonModification2D" version="4.2">
|
||||
<brief_description>
|
||||
A modification that holds and executes a [SkeletonModificationStack2D].
|
||||
</brief_description>
|
||||
<description>
|
||||
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.
|
||||
</description>
|
||||
<tutorials>
|
||||
</tutorials>
|
||||
<methods>
|
||||
<method name="get_held_modification_stack" qualifiers="const">
|
||||
<return type="SkeletonModificationStack2D" />
|
||||
<description>
|
||||
Returns the [SkeletonModificationStack2D] that this modification is holding.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_held_modification_stack">
|
||||
<return type="void" />
|
||||
<argument index="0" name="held_modification_stack" type="SkeletonModificationStack2D" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
</methods>
|
||||
<constants>
|
||||
</constants>
|
||||
</class>
|
@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="SkeletonModification2DTwoBoneIK" inherits="SkeletonModification2D" version="4.2">
|
||||
<brief_description>
|
||||
A modification that rotates two bones using the law of cosigns to reach the target.
|
||||
</brief_description>
|
||||
<description>
|
||||
This [SkeletonModification2D] uses an algorithm typically called TwoBoneIK. This algorithm works by leveraging the law of cosigns and the lengths of the bones to figure out what rotation the bones currently have, and what rotation they need to make a complete triangle, where the first bone, the second bone, and the target form the three vertices of the triangle. Because the algorithm works by making a triangle, it can only operate on two bones.
|
||||
TwoBoneIK is great for arms, legs, and really any joints that can be represented by just two bones that bend to reach a target. This solver is more lightweight than [SkeletonModification2DFABRIK], but gives similar, natural looking results.
|
||||
</description>
|
||||
<tutorials>
|
||||
</tutorials>
|
||||
<methods>
|
||||
<method name="get_joint_one_bone2d_node" qualifiers="const">
|
||||
<return type="NodePath" />
|
||||
<description>
|
||||
Returns the [Bone2D] node that is being used as the first bone in the TwoBoneIK modification.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_joint_one_bone_idx" qualifiers="const">
|
||||
<return type="int" />
|
||||
<description>
|
||||
Returns the index of the [Bone2D] node that is being used as the first bone in the TwoBoneIK modification.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_joint_two_bone2d_node" qualifiers="const">
|
||||
<return type="NodePath" />
|
||||
<description>
|
||||
Returns the [Bone2D] node that is being used as the second bone in the TwoBoneIK modification.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_joint_two_bone_idx" qualifiers="const">
|
||||
<return type="int" />
|
||||
<description>
|
||||
Returns the index of the [Bone2D] node that is being used as the second bone in the TwoBoneIK modification.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_joint_one_bone2d_node">
|
||||
<return type="void" />
|
||||
<argument index="0" name="bone2d_node" type="NodePath" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_joint_one_bone_idx">
|
||||
<return type="void" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_joint_two_bone2d_node">
|
||||
<return type="void" />
|
||||
<argument index="0" name="bone2d_node" type="NodePath" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_joint_two_bone_idx">
|
||||
<return type="void" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
</methods>
|
||||
<members>
|
||||
<member name="flip_bend_direction" type="bool" setter="set_flip_bend_direction" getter="get_flip_bend_direction" default="false">
|
||||
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.
|
||||
</member>
|
||||
<member name="target_maximum_distance" type="float" setter="set_target_maximum_distance" getter="get_target_maximum_distance" default="0.0">
|
||||
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.
|
||||
</member>
|
||||
<member name="target_minimum_distance" type="float" setter="set_target_minimum_distance" getter="get_target_minimum_distance" default="0.0">
|
||||
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.
|
||||
</member>
|
||||
<member name="target_nodepath" type="NodePath" setter="set_target_node" getter="get_target_node" default="NodePath("")">
|
||||
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.
|
||||
</member>
|
||||
</members>
|
||||
<constants>
|
||||
</constants>
|
||||
</class>
|
@ -0,0 +1,84 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="SkeletonModificationStack2D" inherits="Resource" version="4.2">
|
||||
<brief_description>
|
||||
A resource that holds a stack of [SkeletonModification2D]s.
|
||||
</brief_description>
|
||||
<description>
|
||||
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].
|
||||
</description>
|
||||
<tutorials>
|
||||
</tutorials>
|
||||
<methods>
|
||||
<method name="add_modification">
|
||||
<return type="void" />
|
||||
<argument index="0" name="modification" type="SkeletonModification2D" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="delete_modification">
|
||||
<return type="void" />
|
||||
<argument index="0" name="mod_idx" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="enable_all_modifications">
|
||||
<return type="void" />
|
||||
<argument index="0" name="enabled" type="bool" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="execute">
|
||||
<return type="void" />
|
||||
<argument index="0" name="delta" type="float" />
|
||||
<argument index="1" name="execution_mode" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_is_setup" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<description>
|
||||
Returns a boolean that indicates whether the modification stack is setup and can execute.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_modification" qualifiers="const">
|
||||
<return type="SkeletonModification2D" />
|
||||
<argument index="0" name="mod_idx" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_skeleton" qualifiers="const">
|
||||
<return type="Skeleton2D" />
|
||||
<description>
|
||||
Returns the [Skeleton2D] node that the SkeletonModificationStack2D is bound to.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_modification">
|
||||
<return type="void" />
|
||||
<argument index="0" name="mod_idx" type="int" />
|
||||
<argument index="1" name="modification" type="SkeletonModification2D" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="setup">
|
||||
<return type="void" />
|
||||
<description>
|
||||
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.
|
||||
</description>
|
||||
</method>
|
||||
</methods>
|
||||
<members>
|
||||
<member name="enabled" type="bool" setter="set_enabled" getter="get_enabled" default="false">
|
||||
If [code]true[/code], the modification's in the stack will be called. This is handled automatically through the [Skeleton2D] node.
|
||||
</member>
|
||||
<member name="modification_count" type="int" setter="set_modification_count" getter="get_modification_count" default="0">
|
||||
The number of modifications in the stack.
|
||||
</member>
|
||||
<member name="strength" type="float" setter="set_strength" getter="get_strength" default="1.0">
|
||||
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.
|
||||
</member>
|
||||
</members>
|
||||
<constants>
|
||||
</constants>
|
||||
</class>
|
148
modules/skeleton_2d/editor/skeleton_2d_editor_plugin.cpp
Normal file
@ -0,0 +1,148 @@
|
||||
/*************************************************************************/
|
||||
/* skeleton_2d_editor_plugin.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "skeleton_2d_editor_plugin.h"
|
||||
|
||||
#include "editor/plugins/canvas_item_editor_plugin.h"
|
||||
#include "core/object/class_db.h"
|
||||
#include "core/os/memory.h"
|
||||
#include "core/object/undo_redo.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "../nodes/skeleton_2d.h"
|
||||
#include "scene/gui/dialogs.h"
|
||||
#include "scene/gui/menu_button.h"
|
||||
#include "scene/gui/popup_menu.h"
|
||||
|
||||
class Node;
|
||||
|
||||
void Skeleton2DEditor::_node_removed(Node *p_node) {
|
||||
if (p_node == node) {
|
||||
node = nullptr;
|
||||
options->hide();
|
||||
}
|
||||
}
|
||||
|
||||
void Skeleton2DEditor::edit(Skeleton2D *p_sprite) {
|
||||
node = p_sprite;
|
||||
}
|
||||
|
||||
void Skeleton2DEditor::_menu_option(int p_option) {
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (p_option) {
|
||||
case MENU_OPTION_SET_REST: {
|
||||
if (node->get_bone_count() == 0) {
|
||||
err_dialog->set_text(TTR("This skeleton has no bones, create some children Bone2D nodes."));
|
||||
err_dialog->popup_centered_minsize();
|
||||
return;
|
||||
}
|
||||
UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo();
|
||||
ur->create_action(TTR("Set Rest Pose to Bones"));
|
||||
for (int i = 0; i < node->get_bone_count(); i++) {
|
||||
Bone2D *bone = node->get_bone(i);
|
||||
ur->add_do_method(bone, "set_transform", bone->get_rest());
|
||||
ur->add_undo_method(bone, "set_transform", bone->get_transform());
|
||||
}
|
||||
ur->commit_action();
|
||||
|
||||
} break;
|
||||
case MENU_OPTION_MAKE_REST: {
|
||||
if (node->get_bone_count() == 0) {
|
||||
err_dialog->set_text(TTR("This skeleton has no bones, create some children Bone2D nodes."));
|
||||
err_dialog->popup_centered_minsize();
|
||||
return;
|
||||
}
|
||||
UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo();
|
||||
ur->create_action(TTR("Create Rest Pose from Bones"));
|
||||
for (int i = 0; i < node->get_bone_count(); i++) {
|
||||
Bone2D *bone = node->get_bone(i);
|
||||
ur->add_do_method(bone, "set_rest", bone->get_transform());
|
||||
ur->add_undo_method(bone, "set_rest", bone->get_rest());
|
||||
}
|
||||
ur->commit_action();
|
||||
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void Skeleton2DEditor::_bind_methods() {
|
||||
ClassDB::bind_method("_menu_option", &Skeleton2DEditor::_menu_option);
|
||||
}
|
||||
|
||||
Skeleton2DEditor::Skeleton2DEditor() {
|
||||
options = memnew(MenuButton);
|
||||
|
||||
CanvasItemEditor::get_singleton()->add_control_to_menu_panel(options);
|
||||
|
||||
options->set_text(TTR("Skeleton2D"));
|
||||
options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("Skeleton2D", "EditorIcons"));
|
||||
|
||||
options->get_popup()->add_item(TTR("Reset to Rest Pose"), MENU_OPTION_SET_REST);
|
||||
options->get_popup()->add_separator();
|
||||
// Use the "Overwrite" word to highlight that this is a destructive operation.
|
||||
options->get_popup()->add_item(TTR("Overwrite Rest Pose"), MENU_OPTION_MAKE_REST);
|
||||
options->set_switch_on_hover(true);
|
||||
|
||||
options->get_popup()->connect("id_pressed", this, "_menu_option");
|
||||
|
||||
err_dialog = memnew(AcceptDialog);
|
||||
add_child(err_dialog);
|
||||
}
|
||||
|
||||
void Skeleton2DEditorPlugin::edit(Object *p_object) {
|
||||
sprite_editor->edit(Object::cast_to<Skeleton2D>(p_object));
|
||||
}
|
||||
|
||||
bool Skeleton2DEditorPlugin::handles(Object *p_object) const {
|
||||
return p_object->is_class("Skeleton2D");
|
||||
}
|
||||
|
||||
void Skeleton2DEditorPlugin::make_visible(bool p_visible) {
|
||||
if (p_visible) {
|
||||
sprite_editor->options->show();
|
||||
} else {
|
||||
sprite_editor->options->hide();
|
||||
sprite_editor->edit(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
Skeleton2DEditorPlugin::Skeleton2DEditorPlugin(EditorNode *p_node) {
|
||||
editor = p_node;
|
||||
sprite_editor = memnew(Skeleton2DEditor);
|
||||
editor->get_viewport()->add_child(sprite_editor);
|
||||
make_visible(false);
|
||||
|
||||
//sprite_editor->options->hide();
|
||||
}
|
||||
|
||||
Skeleton2DEditorPlugin::~Skeleton2DEditorPlugin() {
|
||||
}
|
90
modules/skeleton_2d/editor/skeleton_2d_editor_plugin.h
Normal file
@ -0,0 +1,90 @@
|
||||
#ifndef SKELETON_2D_EDITOR_PLUGIN_H
|
||||
#define SKELETON_2D_EDITOR_PLUGIN_H
|
||||
|
||||
/*************************************************************************/
|
||||
/* skeleton_2d_editor_plugin.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "editor/editor_plugin.h"
|
||||
#include "scene/main/control.h"
|
||||
|
||||
#include "core/object/object.h"
|
||||
#include "core/string/ustring.h"
|
||||
|
||||
class AcceptDialog;
|
||||
class EditorNode;
|
||||
class MenuButton;
|
||||
class Node;
|
||||
class Skeleton2D;
|
||||
|
||||
class Skeleton2DEditor : public Control {
|
||||
GDCLASS(Skeleton2DEditor, Control);
|
||||
|
||||
enum Menu {
|
||||
MENU_OPTION_SET_REST,
|
||||
MENU_OPTION_MAKE_REST,
|
||||
};
|
||||
|
||||
Skeleton2D *node;
|
||||
|
||||
MenuButton *options;
|
||||
AcceptDialog *err_dialog;
|
||||
|
||||
void _menu_option(int p_option);
|
||||
|
||||
//void _create_uv_lines();
|
||||
friend class Skeleton2DEditorPlugin;
|
||||
|
||||
protected:
|
||||
void _node_removed(Node *p_node);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void edit(Skeleton2D *p_sprite);
|
||||
Skeleton2DEditor();
|
||||
};
|
||||
|
||||
class Skeleton2DEditorPlugin : public EditorPlugin {
|
||||
GDCLASS(Skeleton2DEditorPlugin, EditorPlugin);
|
||||
|
||||
Skeleton2DEditor *sprite_editor;
|
||||
EditorNode *editor;
|
||||
|
||||
public:
|
||||
virtual String get_name() const { return "Skeleton2D"; }
|
||||
bool has_main_screen() const { return false; }
|
||||
virtual void edit(Object *p_object);
|
||||
virtual bool handles(Object *p_object) const;
|
||||
virtual void make_visible(bool p_visible);
|
||||
|
||||
Skeleton2DEditorPlugin(EditorNode *p_node);
|
||||
~Skeleton2DEditorPlugin();
|
||||
};
|
||||
|
||||
#endif // SKELETON_2D_EDITOR_PLUGIN_H
|
1
modules/skeleton_2d/icons/icon_bone_2_d.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m10.478 1037.4a2.4664 2.4663 0 0 0 -1.7804.7205 2.4664 2.4663 0 0 0 -.31408 3.1041l-3.559 3.5608a2.4664 2.4663 0 0 0 -3.1023.3121 2.4664 2.4663 0 0 0 0 3.4876 2.4664 2.4663 0 0 0 1.397.6955 2.4664 2.4663 0 0 0 .69561 1.397 2.4664 2.4663 0 0 0 3.4877 0 2.4664 2.4663 0 0 0 .31408-3.1041l3.5609-3.5608a2.4664 2.4663 0 0 0 3.1004-.3102 2.4664 2.4663 0 0 0 0-3.4875 2.4664 2.4663 0 0 0 -1.397-.6974 2.4664 2.4663 0 0 0 -.69561-1.3971 2.4664 2.4663 0 0 0 -1.7072-.7205z" fill="#a5b7f3" transform="translate(0 -1036.4)"/></svg>
|
After Width: | Height: | Size: 614 B |
1
modules/skeleton_2d/icons/icon_skeleton_2d.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m6 2a4 4 0 0 0 -4 4 4 4 0 0 0 2 3.4531v3.5469a2 2 0 0 0 1 1.7324 2 2 0 0 0 1 .26562v.001953h4v-.001953a2 2 0 0 0 1-.26562 2 2 0 0 0 1-1.7324v-3.5469a4 4 0 0 0 2-3.4531 4 4 0 0 0 -4-4zm-1 3a1 1 0 0 1 1 1 1 1 0 0 1 -1 1 1 1 0 0 1 -1-1 1 1 0 0 1 1-1zm6 0a1 1 0 0 1 1 1 1 1 0 0 1 -1 1 1 1 0 0 1 -1-1 1 1 0 0 1 1-1zm-4 2h2v1h-2zm-2 2h1v1h1v-1h1 1v1h1v-1h1v.86719 3.1328h-1v-1h-1v1h-1-1v-1h-1v1h-1v-3.1309-.86914z" fill="#a5b7f3"/></svg>
|
After Width: | Height: | Size: 524 B |
307
modules/skeleton_2d/nodes/physical_bone_2d.cpp
Normal file
@ -0,0 +1,307 @@
|
||||
/*************************************************************************/
|
||||
/* physical_bone_2d.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "physical_bone_2d.h"
|
||||
|
||||
#include "scene/2d/joints_2d.h"
|
||||
|
||||
void PhysicalBone2D::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
|
||||
// Position the RigidDynamicBody 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();
|
||||
|
||||
// Configure joint.
|
||||
if (child_joint && auto_configure_joint) {
|
||||
_auto_configure_joint();
|
||||
}
|
||||
|
||||
// Simulate physics if set.
|
||||
if (simulate_physics) {
|
||||
_start_physics_simulation();
|
||||
} else {
|
||||
_stop_physics_simulation();
|
||||
}
|
||||
|
||||
set_physics_process_internal(true);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicalBone2D::_find_skeleton_parent() {
|
||||
Node *current_parent = get_parent();
|
||||
|
||||
while (current_parent != nullptr) {
|
||||
Skeleton2D *potential_skeleton = Object::cast_to<Skeleton2D>(current_parent);
|
||||
if (potential_skeleton) {
|
||||
parent_skeleton = potential_skeleton;
|
||||
break;
|
||||
} else {
|
||||
PhysicalBone2D *potential_parent_bone = Object::cast_to<PhysicalBone2D>(current_parent);
|
||||
if (potential_parent_bone) {
|
||||
current_parent = potential_parent_bone->get_parent();
|
||||
} else {
|
||||
current_parent = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicalBone2D::_find_joint_child() {
|
||||
for (int i = 0; i < get_child_count(); i++) {
|
||||
Node *child_node = get_child(i);
|
||||
Joint2D *potential_joint = Object::cast_to<Joint2D>(child_node);
|
||||
if (potential_joint) {
|
||||
child_joint = potential_joint;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String PhysicalBone2D::get_configuration_warning() const {
|
||||
String warning = RigidBody2D::get_configuration_warning();
|
||||
|
||||
if (!parent_skeleton) {
|
||||
warning += "A PhysicalBone2D only works with a Skeleton2D or another PhysicalBone2D as a parent node!";
|
||||
}
|
||||
if (parent_skeleton && bone2d_index <= -1) {
|
||||
warning += "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<PhysicalBone2D>(get_parent());
|
||||
if (parent_bone) {
|
||||
warning += "A PhysicalBone2D node should have a Joint2D-based child node to keep bones connected! Please add a Joint2D-based node as a child to this node!";
|
||||
}
|
||||
}
|
||||
|
||||
return warning;
|
||||
}
|
||||
|
||||
void PhysicalBone2D::_auto_configure_joint() {
|
||||
if (!auto_configure_joint) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (child_joint) {
|
||||
// Node A = parent | Node B = this node
|
||||
Node *parent_node = get_parent();
|
||||
PhysicalBone2D *potential_parent_bone = Object::cast_to<PhysicalBone2D>(parent_node);
|
||||
|
||||
if (potential_parent_bone) {
|
||||
child_joint->set_node_a(child_joint->get_path_to(potential_parent_bone));
|
||||
child_joint->set_node_b(child_joint->get_path_to(this));
|
||||
} else {
|
||||
WARN_PRINT("Cannot setup joint without a parent PhysicalBone2D node.");
|
||||
}
|
||||
|
||||
// Place the child joint at this node's position.
|
||||
child_joint->set_global_position(get_global_position());
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicalBone2D::_start_physics_simulation() {
|
||||
if (_internal_simulate_physics) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset to Bone2D position.
|
||||
_position_at_bone2d();
|
||||
|
||||
// Apply the layers and masks.
|
||||
Physics2DServer::get_singleton()->body_set_collision_layer(get_rid(), get_collision_layer());
|
||||
Physics2DServer::get_singleton()->body_set_collision_mask(get_rid(), get_collision_mask());
|
||||
|
||||
// Apply the correct mode.
|
||||
//_apply_body_mode();
|
||||
|
||||
_internal_simulate_physics = true;
|
||||
set_physics_process_internal(true);
|
||||
}
|
||||
|
||||
void PhysicalBone2D::_stop_physics_simulation() {
|
||||
if (_internal_simulate_physics) {
|
||||
_internal_simulate_physics = false;
|
||||
|
||||
// Reset to Bone2D position
|
||||
_position_at_bone2d();
|
||||
|
||||
set_physics_process_internal(false);
|
||||
Physics2DServer::get_singleton()->body_set_collision_layer(get_rid(), 0);
|
||||
Physics2DServer::get_singleton()->body_set_collision_mask(get_rid(), 0);
|
||||
Physics2DServer::get_singleton()->body_set_mode(get_rid(), Physics2DServer::BodyMode::BODY_MODE_STATIC);
|
||||
}
|
||||
}
|
||||
|
||||
Joint2D *PhysicalBone2D::get_joint() const {
|
||||
return child_joint;
|
||||
}
|
||||
|
||||
bool PhysicalBone2D::get_auto_configure_joint() const {
|
||||
return auto_configure_joint;
|
||||
}
|
||||
|
||||
void PhysicalBone2D::set_auto_configure_joint(bool p_auto_configure) {
|
||||
auto_configure_joint = p_auto_configure;
|
||||
_auto_configure_joint();
|
||||
}
|
||||
|
||||
void PhysicalBone2D::set_simulate_physics(bool p_simulate) {
|
||||
if (p_simulate == simulate_physics) {
|
||||
return;
|
||||
}
|
||||
simulate_physics = p_simulate;
|
||||
|
||||
if (simulate_physics) {
|
||||
_start_physics_simulation();
|
||||
} else {
|
||||
_stop_physics_simulation();
|
||||
}
|
||||
}
|
||||
|
||||
bool PhysicalBone2D::get_simulate_physics() const {
|
||||
return simulate_physics;
|
||||
}
|
||||
|
||||
bool PhysicalBone2D::is_simulating_physics() const {
|
||||
return _internal_simulate_physics;
|
||||
}
|
||||
|
||||
void PhysicalBone2D::set_bone2d_nodepath(const NodePath &p_nodepath) {
|
||||
bone2d_nodepath = p_nodepath;
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
int PhysicalBone2D::get_bone2d_index() const {
|
||||
return bone2d_index;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
bool PhysicalBone2D::get_follow_bone_when_simulating() const {
|
||||
return follow_bone_when_simulating;
|
||||
}
|
||||
|
||||
void PhysicalBone2D::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("get_joint"), &PhysicalBone2D::get_joint);
|
||||
ClassDB::bind_method(D_METHOD("get_auto_configure_joint"), &PhysicalBone2D::get_auto_configure_joint);
|
||||
ClassDB::bind_method(D_METHOD("set_auto_configure_joint", "auto_configure_joint"), &PhysicalBone2D::set_auto_configure_joint);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_simulate_physics", "simulate_physics"), &PhysicalBone2D::set_simulate_physics);
|
||||
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("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::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");
|
||||
}
|
||||
|
||||
PhysicalBone2D::PhysicalBone2D() {
|
||||
parent_skeleton = nullptr;
|
||||
bone2d_index = -1;
|
||||
follow_bone_when_simulating = false;
|
||||
|
||||
child_joint = nullptr;
|
||||
auto_configure_joint = true;
|
||||
|
||||
simulate_physics = false;
|
||||
_internal_simulate_physics = false;
|
||||
|
||||
// Stop the RigidDynamicBody from executing its force integration.
|
||||
Physics2DServer::get_singleton()->body_set_collision_layer(get_rid(), 0);
|
||||
Physics2DServer::get_singleton()->body_set_collision_mask(get_rid(), 0);
|
||||
Physics2DServer::get_singleton()->body_set_mode(get_rid(), Physics2DServer::BodyMode::BODY_MODE_STATIC);
|
||||
|
||||
child_joint = nullptr;
|
||||
}
|
||||
|
||||
PhysicalBone2D::~PhysicalBone2D() {
|
||||
}
|
88
modules/skeleton_2d/nodes/physical_bone_2d.h
Normal file
@ -0,0 +1,88 @@
|
||||
/*************************************************************************/
|
||||
/* physical_bone_2d.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#ifndef PHYSICAL_BONE_2D_H
|
||||
#define PHYSICAL_BONE_2D_H
|
||||
|
||||
#include "scene/2d/physics_body_2d.h"
|
||||
#include "skeleton_2d.h"
|
||||
|
||||
class Joint2D;
|
||||
|
||||
class PhysicalBone2D : public RigidBody2D {
|
||||
GDCLASS(PhysicalBone2D, RigidBody2D);
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
private:
|
||||
Skeleton2D *parent_skeleton;
|
||||
int bone2d_index;
|
||||
NodePath bone2d_nodepath;
|
||||
bool follow_bone_when_simulating;
|
||||
|
||||
Joint2D *child_joint;
|
||||
bool auto_configure_joint;
|
||||
|
||||
bool simulate_physics;
|
||||
bool _internal_simulate_physics;
|
||||
|
||||
void _find_skeleton_parent();
|
||||
void _find_joint_child();
|
||||
void _auto_configure_joint();
|
||||
|
||||
void _start_physics_simulation();
|
||||
void _stop_physics_simulation();
|
||||
void _position_at_bone2d();
|
||||
|
||||
public:
|
||||
Joint2D *get_joint() const;
|
||||
bool get_auto_configure_joint() const;
|
||||
void set_auto_configure_joint(bool p_auto_configure);
|
||||
|
||||
void set_simulate_physics(bool p_simulate);
|
||||
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;
|
||||
void set_follow_bone_when_simulating(bool p_follow);
|
||||
bool get_follow_bone_when_simulating() const;
|
||||
|
||||
virtual String get_configuration_warning() const;
|
||||
|
||||
PhysicalBone2D();
|
||||
~PhysicalBone2D();
|
||||
};
|
||||
|
||||
#endif // PHYSICAL_BONE_2D_H
|
814
modules/skeleton_2d/nodes/skeleton_2d.cpp
Normal file
@ -0,0 +1,814 @@
|
||||
/*************************************************************************/
|
||||
/* skeleton_2d.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "skeleton_2d.h"
|
||||
|
||||
#include "../resources/skeleton_modification_2d.h"
|
||||
#include "../resources/skeleton_modification_stack_2d.h"
|
||||
#include "core/config/engine.h"
|
||||
#include "scene/main/scene_string_names.h"
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
#include "editor/editor_data.h"
|
||||
#include "editor/editor_settings.h"
|
||||
#include "editor/plugins/canvas_item_editor_plugin.h"
|
||||
#endif //TOOLS_ENABLED
|
||||
|
||||
bool Bone2D::_set(const StringName &p_path, const Variant &p_value) {
|
||||
String path = p_path;
|
||||
|
||||
if (path.begins_with("auto_calculate_length_and_angle")) {
|
||||
set_autocalculate_length_and_angle(p_value);
|
||||
} else if (path.begins_with("length")) {
|
||||
set_length(p_value);
|
||||
} else if (path.begins_with("bone_angle")) {
|
||||
set_bone_angle(Math::deg2rad(float(p_value)));
|
||||
} else if (path.begins_with("default_length")) {
|
||||
set_length(p_value);
|
||||
}
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (path.begins_with("editor_settings/show_bone_gizmo")) {
|
||||
_editor_set_show_bone_gizmo(p_value);
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Bone2D::_get(const StringName &p_path, Variant &r_ret) const {
|
||||
String path = p_path;
|
||||
|
||||
if (path.begins_with("auto_calculate_length_and_angle")) {
|
||||
r_ret = get_autocalculate_length_and_angle();
|
||||
} else if (path.begins_with("length")) {
|
||||
r_ret = get_length();
|
||||
} else if (path.begins_with("bone_angle")) {
|
||||
r_ret = Math::rad2deg(get_bone_angle());
|
||||
} else if (path.begins_with("default_length")) {
|
||||
r_ret = get_length();
|
||||
}
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (path.begins_with("editor_settings/show_bone_gizmo")) {
|
||||
r_ret = _editor_get_show_bone_gizmo();
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Bone2D::_get_property_list(List<PropertyInfo> *p_list) const {
|
||||
p_list->push_back(PropertyInfo(Variant::BOOL, "auto_calculate_length_and_angle", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
|
||||
if (!autocalculate_length_and_angle) {
|
||||
p_list->push_back(PropertyInfo(Variant::REAL, "length", PROPERTY_HINT_RANGE, "1,1024,1", PROPERTY_USAGE_DEFAULT));
|
||||
p_list->push_back(PropertyInfo(Variant::REAL, "bone_angle", PROPERTY_HINT_RANGE, "-360,360,0.01", PROPERTY_USAGE_DEFAULT));
|
||||
}
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
p_list->push_back(PropertyInfo(Variant::BOOL, "editor_settings/show_bone_gizmo", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
|
||||
#endif // TOOLS_ENABLED
|
||||
}
|
||||
|
||||
void Bone2D::_notification(int p_what) {
|
||||
if (p_what == NOTIFICATION_ENTER_TREE) {
|
||||
Node *parent = get_parent();
|
||||
parent_bone = Object::cast_to<Bone2D>(parent);
|
||||
skeleton = nullptr;
|
||||
while (parent) {
|
||||
skeleton = Object::cast_to<Skeleton2D>(parent);
|
||||
if (skeleton) {
|
||||
break;
|
||||
}
|
||||
if (!Object::cast_to<Bone2D>(parent)) {
|
||||
break; //skeletons must be chained to Bone2Ds.
|
||||
}
|
||||
|
||||
parent = parent->get_parent();
|
||||
}
|
||||
|
||||
if (skeleton) {
|
||||
Skeleton2D::Bone bone;
|
||||
bone.bone = this;
|
||||
skeleton->bones.push_back(bone);
|
||||
skeleton->_make_bone_setup_dirty();
|
||||
get_parent()->connect(SceneStringNames::get_singleton()->child_order_changed, skeleton, "_make_bone_setup_dirty", varray(), CONNECT_REFERENCE_COUNTED);
|
||||
}
|
||||
|
||||
cache_transform = get_transform();
|
||||
copy_transform_to_cache = true;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
// Only draw the gizmo in the editor!
|
||||
if (Engine::get_singleton()->is_editor_hint() == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
update();
|
||||
#endif // TOOLS_ENABLED
|
||||
} else if (p_what == NOTIFICATION_LOCAL_TRANSFORM_CHANGED) {
|
||||
if (skeleton) {
|
||||
skeleton->_make_transform_dirty();
|
||||
}
|
||||
|
||||
if (copy_transform_to_cache) {
|
||||
cache_transform = get_transform();
|
||||
}
|
||||
#ifdef TOOLS_ENABLED
|
||||
// Only draw the gizmo in the editor!
|
||||
if (Engine::get_singleton()->is_editor_hint() == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
update();
|
||||
|
||||
if (get_parent()) {
|
||||
Bone2D *parent_bone = Object::cast_to<Bone2D>(get_parent());
|
||||
if (parent_bone) {
|
||||
parent_bone->update();
|
||||
}
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
} else if (p_what == NOTIFICATION_EXIT_TREE) {
|
||||
if (skeleton) {
|
||||
for (int i = 0; i < skeleton->bones.size(); i++) {
|
||||
if (skeleton->bones[i].bone == this) {
|
||||
skeleton->bones.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
skeleton->_make_bone_setup_dirty();
|
||||
get_parent()->disconnect(SceneStringNames::get_singleton()->child_order_changed, skeleton, "_make_bone_setup_dirty");
|
||||
skeleton = nullptr;
|
||||
}
|
||||
parent_bone = nullptr;
|
||||
set_transform(cache_transform);
|
||||
} else if (p_what == NOTIFICATION_READY) {
|
||||
if (autocalculate_length_and_angle) {
|
||||
calculate_length_and_rotation();
|
||||
}
|
||||
}
|
||||
#ifdef TOOLS_ENABLED
|
||||
else if (p_what == NOTIFICATION_EDITOR_PRE_SAVE || p_what == NOTIFICATION_EDITOR_POST_SAVE) {
|
||||
Transform2D tmp_trans = get_transform();
|
||||
set_transform(cache_transform);
|
||||
cache_transform = tmp_trans;
|
||||
}
|
||||
// Bone2D Editor gizmo drawing:
|
||||
|
||||
//TODO Bone2D gizmo drawing needs to be moved to an editor plugin
|
||||
|
||||
else if (p_what == NOTIFICATION_DRAW) {
|
||||
// Only draw the gizmo in the editor!
|
||||
if (Engine::get_singleton()->is_editor_hint() == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!editor_gizmo_rid.is_valid()) {
|
||||
editor_gizmo_rid = RenderingServer::get_singleton()->canvas_item_create();
|
||||
RenderingServer::get_singleton()->canvas_item_set_parent(editor_gizmo_rid, get_canvas_item());
|
||||
RenderingServer::get_singleton()->canvas_item_set_z_as_relative_to_parent(editor_gizmo_rid, true);
|
||||
RenderingServer::get_singleton()->canvas_item_set_z_index(editor_gizmo_rid, 10);
|
||||
}
|
||||
RenderingServer::get_singleton()->canvas_item_clear(editor_gizmo_rid);
|
||||
|
||||
if (!_editor_show_bone_gizmo) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Undo scaling
|
||||
Transform2D editor_gizmo_trans = Transform2D();
|
||||
editor_gizmo_trans.set_scale(Vector2(1, 1) / get_global_scale());
|
||||
RenderingServer::get_singleton()->canvas_item_set_transform(editor_gizmo_rid, editor_gizmo_trans);
|
||||
|
||||
Color bone_color1 = EditorSettings::get_singleton()->get("editors/2d/bone_color1");
|
||||
Color bone_color2 = EditorSettings::get_singleton()->get("editors/2d/bone_color2");
|
||||
Color bone_ik_color = EditorSettings::get_singleton()->get("editors/2d/bone_ik_color");
|
||||
Color bone_outline_color = EditorSettings::get_singleton()->get("editors/2d/bone_outline_color");
|
||||
Color bone_selected_color = EditorSettings::get_singleton()->get("editors/2d/bone_selected_color");
|
||||
|
||||
bool Bone2D_found = false;
|
||||
for (int i = 0; i < get_child_count(); i++) {
|
||||
Bone2D *child_node = nullptr;
|
||||
child_node = Object::cast_to<Bone2D>(get_child(i));
|
||||
if (!child_node) {
|
||||
continue;
|
||||
}
|
||||
Bone2D_found = true;
|
||||
|
||||
Vector<Vector2> bone_shape;
|
||||
Vector<Vector2> bone_shape_outline;
|
||||
|
||||
_editor_get_bone_shape(&bone_shape, &bone_shape_outline, child_node);
|
||||
|
||||
Vector<Color> colors;
|
||||
if (has_meta("_local_pose_override_enabled_")) {
|
||||
colors.push_back(bone_ik_color);
|
||||
colors.push_back(bone_ik_color);
|
||||
colors.push_back(bone_ik_color);
|
||||
colors.push_back(bone_ik_color);
|
||||
} else {
|
||||
colors.push_back(bone_color1);
|
||||
colors.push_back(bone_color2);
|
||||
colors.push_back(bone_color1);
|
||||
colors.push_back(bone_color2);
|
||||
}
|
||||
|
||||
Vector<Color> outline_colors;
|
||||
if (CanvasItemEditor::get_singleton()->editor_selection->is_selected(this)) {
|
||||
outline_colors.push_back(bone_selected_color);
|
||||
outline_colors.push_back(bone_selected_color);
|
||||
outline_colors.push_back(bone_selected_color);
|
||||
outline_colors.push_back(bone_selected_color);
|
||||
outline_colors.push_back(bone_selected_color);
|
||||
outline_colors.push_back(bone_selected_color);
|
||||
} else {
|
||||
outline_colors.push_back(bone_outline_color);
|
||||
outline_colors.push_back(bone_outline_color);
|
||||
outline_colors.push_back(bone_outline_color);
|
||||
outline_colors.push_back(bone_outline_color);
|
||||
outline_colors.push_back(bone_outline_color);
|
||||
outline_colors.push_back(bone_outline_color);
|
||||
}
|
||||
|
||||
RenderingServer::get_singleton()->canvas_item_add_polygon(editor_gizmo_rid, bone_shape_outline, outline_colors);
|
||||
RenderingServer::get_singleton()->canvas_item_add_polygon(editor_gizmo_rid, bone_shape, colors);
|
||||
}
|
||||
|
||||
if (!Bone2D_found) {
|
||||
Vector<Vector2> bone_shape;
|
||||
Vector<Vector2> bone_shape_outline;
|
||||
|
||||
_editor_get_bone_shape(&bone_shape, &bone_shape_outline, nullptr);
|
||||
|
||||
Vector<Color> colors;
|
||||
if (has_meta("_local_pose_override_enabled_")) {
|
||||
colors.push_back(bone_ik_color);
|
||||
colors.push_back(bone_ik_color);
|
||||
colors.push_back(bone_ik_color);
|
||||
colors.push_back(bone_ik_color);
|
||||
} else {
|
||||
colors.push_back(bone_color1);
|
||||
colors.push_back(bone_color2);
|
||||
colors.push_back(bone_color1);
|
||||
colors.push_back(bone_color2);
|
||||
}
|
||||
|
||||
Vector<Color> outline_colors;
|
||||
if (CanvasItemEditor::get_singleton()->editor_selection->is_selected(this)) {
|
||||
outline_colors.push_back(bone_selected_color);
|
||||
outline_colors.push_back(bone_selected_color);
|
||||
outline_colors.push_back(bone_selected_color);
|
||||
outline_colors.push_back(bone_selected_color);
|
||||
outline_colors.push_back(bone_selected_color);
|
||||
outline_colors.push_back(bone_selected_color);
|
||||
} else {
|
||||
outline_colors.push_back(bone_outline_color);
|
||||
outline_colors.push_back(bone_outline_color);
|
||||
outline_colors.push_back(bone_outline_color);
|
||||
outline_colors.push_back(bone_outline_color);
|
||||
outline_colors.push_back(bone_outline_color);
|
||||
outline_colors.push_back(bone_outline_color);
|
||||
}
|
||||
|
||||
RenderingServer::get_singleton()->canvas_item_add_polygon(editor_gizmo_rid, bone_shape_outline, outline_colors);
|
||||
RenderingServer::get_singleton()->canvas_item_add_polygon(editor_gizmo_rid, bone_shape, colors);
|
||||
}
|
||||
}
|
||||
#endif // TOOLS_ENALBED
|
||||
}
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
bool Bone2D::_editor_get_bone_shape(Vector<Vector2> *p_shape, Vector<Vector2> *p_outline_shape, Bone2D *p_other_bone) {
|
||||
int bone_width = EditorSettings::get_singleton()->get("editors/2d/bone_width");
|
||||
int bone_outline_width = EditorSettings::get_singleton()->get("editors/2d/bone_outline_size");
|
||||
|
||||
if (!is_inside_tree()) {
|
||||
return false; //may have been removed
|
||||
}
|
||||
if (!p_other_bone && length <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Vector2 rel;
|
||||
if (p_other_bone) {
|
||||
rel = (p_other_bone->get_global_transform().get_origin() - get_global_transform().get_origin());
|
||||
rel = rel.rotated(-get_global_rotation()); // Undo Bone2D node's rotation so its drawn correctly regardless of the node's rotation
|
||||
} else {
|
||||
float angle_to_use = get_rotation() + bone_angle;
|
||||
rel = Vector2(cos(angle_to_use), sin(angle_to_use)) * (length * MIN(get_global_scale().x, get_global_scale().y));
|
||||
rel = rel.rotated(-get_rotation()); // Undo Bone2D node's rotation so its drawn correctly regardless of the node's rotation
|
||||
}
|
||||
|
||||
Vector2 relt = rel.rotated(Math_PI * 0.5).normalized() * bone_width;
|
||||
Vector2 reln = rel.normalized();
|
||||
Vector2 reltn = relt.normalized();
|
||||
|
||||
if (p_shape) {
|
||||
p_shape->clear();
|
||||
p_shape->push_back(Vector2(0, 0));
|
||||
p_shape->push_back(rel * 0.2 + relt);
|
||||
p_shape->push_back(rel);
|
||||
p_shape->push_back(rel * 0.2 - relt);
|
||||
}
|
||||
|
||||
if (p_outline_shape) {
|
||||
p_outline_shape->clear();
|
||||
p_outline_shape->push_back((-reln - reltn) * bone_outline_width);
|
||||
p_outline_shape->push_back((-reln + reltn) * bone_outline_width);
|
||||
p_outline_shape->push_back(rel * 0.2 + relt + reltn * bone_outline_width);
|
||||
p_outline_shape->push_back(rel + (reln + reltn) * bone_outline_width);
|
||||
p_outline_shape->push_back(rel + (reln - reltn) * bone_outline_width);
|
||||
p_outline_shape->push_back(rel * 0.2 - relt - reltn * bone_outline_width);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Bone2D::_editor_set_show_bone_gizmo(bool p_show_gizmo) {
|
||||
_editor_show_bone_gizmo = p_show_gizmo;
|
||||
update();
|
||||
}
|
||||
|
||||
bool Bone2D::_editor_get_show_bone_gizmo() const {
|
||||
return _editor_show_bone_gizmo;
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
void Bone2D::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_rest", "rest"), &Bone2D::set_rest);
|
||||
ClassDB::bind_method(D_METHOD("get_rest"), &Bone2D::get_rest);
|
||||
ClassDB::bind_method(D_METHOD("apply_rest"), &Bone2D::apply_rest);
|
||||
ClassDB::bind_method(D_METHOD("get_skeleton_rest"), &Bone2D::get_skeleton_rest);
|
||||
ClassDB::bind_method(D_METHOD("get_index_in_skeleton"), &Bone2D::get_index_in_skeleton);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_default_length", "default_length"), &Bone2D::set_default_length);
|
||||
ClassDB::bind_method(D_METHOD("get_default_length"), &Bone2D::get_default_length);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_autocalculate_length_and_angle", "auto_calculate"), &Bone2D::set_autocalculate_length_and_angle);
|
||||
ClassDB::bind_method(D_METHOD("get_autocalculate_length_and_angle"), &Bone2D::get_autocalculate_length_and_angle);
|
||||
ClassDB::bind_method(D_METHOD("set_length", "length"), &Bone2D::set_length);
|
||||
ClassDB::bind_method(D_METHOD("get_length"), &Bone2D::get_length);
|
||||
ClassDB::bind_method(D_METHOD("set_bone_angle", "angle"), &Bone2D::set_bone_angle);
|
||||
ClassDB::bind_method(D_METHOD("get_bone_angle"), &Bone2D::get_bone_angle);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "rest"), "set_rest", "get_rest");
|
||||
}
|
||||
|
||||
void Bone2D::set_rest(const Transform2D &p_rest) {
|
||||
rest = p_rest;
|
||||
if (skeleton) {
|
||||
skeleton->_make_bone_setup_dirty();
|
||||
}
|
||||
|
||||
update_configuration_warning();
|
||||
}
|
||||
|
||||
Transform2D Bone2D::get_rest() const {
|
||||
return rest;
|
||||
}
|
||||
|
||||
Transform2D Bone2D::get_skeleton_rest() const {
|
||||
if (parent_bone) {
|
||||
return parent_bone->get_skeleton_rest() * rest;
|
||||
} else {
|
||||
return rest;
|
||||
}
|
||||
}
|
||||
|
||||
void Bone2D::apply_rest() {
|
||||
set_transform(rest);
|
||||
}
|
||||
|
||||
void Bone2D::set_default_length(float p_length) {
|
||||
WARN_DEPRECATED_MSG("set_default_length is deprecated. Please use set_length instead!");
|
||||
set_length(p_length);
|
||||
}
|
||||
|
||||
float Bone2D::get_default_length() const {
|
||||
WARN_DEPRECATED_MSG("get_default_length is deprecated. Please use get_length instead!");
|
||||
return get_length();
|
||||
}
|
||||
|
||||
int Bone2D::get_index_in_skeleton() const {
|
||||
ERR_FAIL_COND_V(!skeleton, -1);
|
||||
skeleton->_update_bone_setup();
|
||||
return skeleton_index;
|
||||
}
|
||||
|
||||
String Bone2D::get_configuration_warning() const {
|
||||
String warning = Node2D::get_configuration_warning();
|
||||
if (!skeleton) {
|
||||
if (warning != String()) {
|
||||
warning += "\n\n";
|
||||
}
|
||||
if (parent_bone) {
|
||||
warning += TTR("This Bone2D chain should end at a Skeleton2D node.");
|
||||
} else {
|
||||
warning += TTR("A Bone2D only works with a Skeleton2D or another Bone2D as parent node.");
|
||||
}
|
||||
}
|
||||
|
||||
if (rest == Transform2D(0, 0, 0, 0, 0, 0)) {
|
||||
if (warning != String()) {
|
||||
warning += "\n\n";
|
||||
}
|
||||
warning += TTR("This bone lacks a proper REST pose. Go to the Skeleton2D node and set one.");
|
||||
}
|
||||
|
||||
return warning;
|
||||
}
|
||||
|
||||
void Bone2D::calculate_length_and_rotation() {
|
||||
// if there is at least a single child Bone2D node, we can calculate
|
||||
// the length and direction. We will always just use the first Bone2D for this.
|
||||
bool calculated = false;
|
||||
int child_count = get_child_count();
|
||||
if (child_count > 0) {
|
||||
for (int i = 0; i < child_count; i++) {
|
||||
Bone2D *child = Object::cast_to<Bone2D>(get_child(i));
|
||||
if (child) {
|
||||
Vector2 child_local_pos = to_local(child->get_global_transform().get_origin());
|
||||
length = child_local_pos.length();
|
||||
bone_angle = Math::atan2(child_local_pos.normalized().y, child_local_pos.normalized().x);
|
||||
calculated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (calculated) {
|
||||
return; // Finished!
|
||||
} else {
|
||||
WARN_PRINT("No Bone2D children of node " + get_name() + ". Cannot calculate bone length or angle reliably.\nUsing transform rotation for bone angle");
|
||||
bone_angle = get_transform().get_rotation();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Bone2D::set_autocalculate_length_and_angle(bool p_autocalculate) {
|
||||
autocalculate_length_and_angle = p_autocalculate;
|
||||
if (autocalculate_length_and_angle) {
|
||||
calculate_length_and_rotation();
|
||||
}
|
||||
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
bool Bone2D::get_autocalculate_length_and_angle() const {
|
||||
return autocalculate_length_and_angle;
|
||||
}
|
||||
|
||||
void Bone2D::set_length(float p_length) {
|
||||
length = p_length;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
update();
|
||||
#endif // TOOLS_ENABLED
|
||||
}
|
||||
|
||||
float Bone2D::get_length() const {
|
||||
return length;
|
||||
}
|
||||
|
||||
void Bone2D::set_bone_angle(float p_angle) {
|
||||
bone_angle = p_angle;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
update();
|
||||
#endif // TOOLS_ENABLED
|
||||
}
|
||||
|
||||
float Bone2D::get_bone_angle() const {
|
||||
return bone_angle;
|
||||
}
|
||||
|
||||
Bone2D::Bone2D() {
|
||||
skeleton = nullptr;
|
||||
parent_bone = nullptr;
|
||||
skeleton_index = -1;
|
||||
length = 16;
|
||||
bone_angle = 0;
|
||||
autocalculate_length_and_angle = true;
|
||||
|
||||
set_notify_local_transform(true);
|
||||
//this is a clever hack so the bone knows no rest has been set yet, allowing to show an error.
|
||||
for (int i = 0; i < 3; i++) {
|
||||
rest[i] = Vector2(0, 0);
|
||||
}
|
||||
|
||||
copy_transform_to_cache = true;
|
||||
}
|
||||
|
||||
Bone2D::~Bone2D() {
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (editor_gizmo_rid.is_valid()) {
|
||||
RenderingServer::get_singleton()->free(editor_gizmo_rid);
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
}
|
||||
|
||||
//////////////////////////////////////
|
||||
|
||||
bool Skeleton2D::_set(const StringName &p_path, const Variant &p_value) {
|
||||
String path = p_path;
|
||||
|
||||
if (path.begins_with("modification_stack")) {
|
||||
set_modification_stack(p_value);
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Skeleton2D::_get(const StringName &p_path, Variant &r_ret) const {
|
||||
String path = p_path;
|
||||
|
||||
if (path.begins_with("modification_stack")) {
|
||||
r_ret = get_modification_stack();
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Skeleton2D::_get_property_list(List<PropertyInfo> *p_list) const {
|
||||
p_list->push_back(
|
||||
PropertyInfo(Variant::OBJECT, "modification_stack",
|
||||
PROPERTY_HINT_RESOURCE_TYPE,
|
||||
"SkeletonModificationStack2D",
|
||||
PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE));
|
||||
}
|
||||
|
||||
void Skeleton2D::_make_bone_setup_dirty() {
|
||||
if (bone_setup_dirty) {
|
||||
return;
|
||||
}
|
||||
bone_setup_dirty = true;
|
||||
if (is_inside_tree()) {
|
||||
call_deferred("_update_bone_setup");
|
||||
}
|
||||
}
|
||||
|
||||
void Skeleton2D::_update_bone_setup() {
|
||||
if (!bone_setup_dirty) {
|
||||
return;
|
||||
}
|
||||
|
||||
bone_setup_dirty = false;
|
||||
RS::get_singleton()->skeleton_allocate(skeleton, bones.size(), true);
|
||||
|
||||
bones.sort(); //sorty so they are always in the same order/index
|
||||
|
||||
for (int i = 0; i < bones.size(); i++) {
|
||||
bones.write[i].rest_inverse = bones[i].bone->get_skeleton_rest().affine_inverse(); //bind pose
|
||||
bones.write[i].bone->skeleton_index = i;
|
||||
|
||||
Bone2D *parent_bone = Object::cast_to<Bone2D>(bones[i].bone->get_parent());
|
||||
|
||||
if (parent_bone) {
|
||||
bones.write[i].parent_index = parent_bone->skeleton_index;
|
||||
} else {
|
||||
bones.write[i].parent_index = -1;
|
||||
}
|
||||
|
||||
bones.write[i].local_pose_override = bones[i].bone->get_skeleton_rest();
|
||||
}
|
||||
|
||||
transform_dirty = true;
|
||||
_update_transform();
|
||||
emit_signal("bone_setup_changed");
|
||||
}
|
||||
|
||||
void Skeleton2D::_make_transform_dirty() {
|
||||
if (transform_dirty) {
|
||||
return;
|
||||
}
|
||||
transform_dirty = true;
|
||||
if (is_inside_tree()) {
|
||||
call_deferred("_update_transform");
|
||||
}
|
||||
}
|
||||
|
||||
void Skeleton2D::_update_transform() {
|
||||
if (bone_setup_dirty) {
|
||||
_update_bone_setup();
|
||||
return; //above will update transform anyway
|
||||
}
|
||||
if (!transform_dirty) {
|
||||
return;
|
||||
}
|
||||
|
||||
transform_dirty = false;
|
||||
|
||||
for (int i = 0; i < bones.size(); i++) {
|
||||
ERR_CONTINUE(bones[i].parent_index >= i);
|
||||
if (bones[i].parent_index >= 0) {
|
||||
bones.write[i].accum_transform = bones[bones[i].parent_index].accum_transform * bones[i].bone->get_transform();
|
||||
} else {
|
||||
bones.write[i].accum_transform = bones[i].bone->get_transform();
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < bones.size(); i++) {
|
||||
Transform2D final_xform = bones[i].accum_transform * bones[i].rest_inverse;
|
||||
RS::get_singleton()->skeleton_bone_set_transform_2d(skeleton, i, final_xform);
|
||||
}
|
||||
}
|
||||
|
||||
int Skeleton2D::get_bone_count() const {
|
||||
ERR_FAIL_COND_V(!is_inside_tree(), 0);
|
||||
|
||||
if (bone_setup_dirty) {
|
||||
const_cast<Skeleton2D *>(this)->_update_bone_setup();
|
||||
}
|
||||
|
||||
return bones.size();
|
||||
}
|
||||
|
||||
Bone2D *Skeleton2D::get_bone(int p_idx) {
|
||||
ERR_FAIL_COND_V(!is_inside_tree(), nullptr);
|
||||
ERR_FAIL_INDEX_V(p_idx, bones.size(), nullptr);
|
||||
|
||||
return bones[p_idx].bone;
|
||||
}
|
||||
|
||||
void Skeleton2D::_notification(int p_what) {
|
||||
if (p_what == NOTIFICATION_READY) {
|
||||
if (bone_setup_dirty) {
|
||||
_update_bone_setup();
|
||||
}
|
||||
if (transform_dirty) {
|
||||
_update_transform();
|
||||
}
|
||||
|
||||
request_ready();
|
||||
}
|
||||
|
||||
if (p_what == NOTIFICATION_TRANSFORM_CHANGED) {
|
||||
RS::get_singleton()->skeleton_set_base_transform_2d(skeleton, get_global_transform());
|
||||
} else if (p_what == NOTIFICATION_INTERNAL_PROCESS) {
|
||||
if (modification_stack.is_valid()) {
|
||||
execute_modifications(get_process_delta_time(), SkeletonModificationStack2D::EXECUTION_MODE::execution_mode_process);
|
||||
}
|
||||
} else if (p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) {
|
||||
if (modification_stack.is_valid()) {
|
||||
execute_modifications(get_physics_process_delta_time(), SkeletonModificationStack2D::EXECUTION_MODE::execution_mode_physics_process);
|
||||
}
|
||||
}
|
||||
#ifdef TOOLS_ENABLED
|
||||
else if (p_what == NOTIFICATION_DRAW) {
|
||||
if (Engine::get_singleton()->is_editor_hint()) {
|
||||
if (modification_stack.is_valid()) {
|
||||
modification_stack->draw_editor_gizmos();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
}
|
||||
|
||||
RID Skeleton2D::get_skeleton() const {
|
||||
return skeleton;
|
||||
}
|
||||
|
||||
void Skeleton2D::set_bone_local_pose_override(int p_bone_idx, Transform2D p_override, float p_amount, bool p_persistent) {
|
||||
ERR_FAIL_INDEX_MSG(p_bone_idx, bones.size(), "Bone index is out of range!");
|
||||
bones.write[p_bone_idx].local_pose_override = p_override;
|
||||
bones.write[p_bone_idx].local_pose_override_amount = p_amount;
|
||||
bones.write[p_bone_idx].local_pose_override_persistent = p_persistent;
|
||||
}
|
||||
|
||||
Transform2D Skeleton2D::get_bone_local_pose_override(int p_bone_idx) {
|
||||
ERR_FAIL_INDEX_V_MSG(p_bone_idx, bones.size(), Transform2D(), "Bone index is out of range!");
|
||||
return bones[p_bone_idx].local_pose_override;
|
||||
}
|
||||
|
||||
void Skeleton2D::set_modification_stack(Ref<SkeletonModificationStack2D> p_stack) {
|
||||
if (modification_stack.is_valid()) {
|
||||
modification_stack->is_setup = false;
|
||||
modification_stack->set_skeleton(nullptr);
|
||||
|
||||
set_process_internal(false);
|
||||
set_physics_process_internal(false);
|
||||
}
|
||||
modification_stack = p_stack;
|
||||
if (modification_stack.is_valid()) {
|
||||
modification_stack->set_skeleton(this);
|
||||
modification_stack->setup();
|
||||
|
||||
set_process_internal(true);
|
||||
set_physics_process_internal(true);
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
modification_stack->set_editor_gizmos_dirty(true);
|
||||
#endif // TOOLS_ENABLED
|
||||
}
|
||||
}
|
||||
|
||||
Ref<SkeletonModificationStack2D> Skeleton2D::get_modification_stack() const {
|
||||
return modification_stack;
|
||||
}
|
||||
|
||||
void Skeleton2D::execute_modifications(float p_delta, int p_execution_mode) {
|
||||
if (!modification_stack.is_valid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not cache the transform changes caused by the modifications!
|
||||
for (int i = 0; i < bones.size(); i++) {
|
||||
bones[i].bone->copy_transform_to_cache = false;
|
||||
}
|
||||
|
||||
if (modification_stack->skeleton != this) {
|
||||
modification_stack->set_skeleton(this);
|
||||
}
|
||||
|
||||
modification_stack->execute(p_delta, p_execution_mode);
|
||||
|
||||
// Only apply the local pose override on _process. Otherwise, just calculate the local_pose_override and reset the transform.
|
||||
if (p_execution_mode == SkeletonModificationStack2D::EXECUTION_MODE::execution_mode_process) {
|
||||
for (int i = 0; i < bones.size(); i++) {
|
||||
if (bones[i].local_pose_override_amount > 0) {
|
||||
bones[i].bone->set_meta("_local_pose_override_enabled_", true);
|
||||
|
||||
Transform2D final_trans = bones[i].bone->cache_transform;
|
||||
final_trans = final_trans.interpolate_with(bones[i].local_pose_override, bones[i].local_pose_override_amount);
|
||||
bones[i].bone->set_transform(final_trans);
|
||||
bones[i].bone->propagate_call("force_update_transform");
|
||||
|
||||
if (bones[i].local_pose_override_persistent) {
|
||||
bones.write[i].local_pose_override_amount = 0.0;
|
||||
}
|
||||
} else {
|
||||
// TODO: see if there is a way to undo the override without having to resort to setting every bone's transform.
|
||||
bones[i].bone->remove_meta("_local_pose_override_enabled_");
|
||||
bones[i].bone->set_transform(bones[i].bone->cache_transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cache any future transform changes
|
||||
for (int i = 0; i < bones.size(); i++) {
|
||||
bones[i].bone->copy_transform_to_cache = true;
|
||||
}
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
modification_stack->set_editor_gizmos_dirty(true);
|
||||
#endif // TOOLS_ENABLED
|
||||
}
|
||||
|
||||
void Skeleton2D::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("_update_bone_setup"), &Skeleton2D::_update_bone_setup);
|
||||
ClassDB::bind_method(D_METHOD("_update_transform"), &Skeleton2D::_update_transform);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_bone_count"), &Skeleton2D::get_bone_count);
|
||||
ClassDB::bind_method(D_METHOD("get_bone", "idx"), &Skeleton2D::get_bone);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_skeleton"), &Skeleton2D::get_skeleton);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_modification_stack", "modification_stack"), &Skeleton2D::set_modification_stack);
|
||||
ClassDB::bind_method(D_METHOD("get_modification_stack"), &Skeleton2D::get_modification_stack);
|
||||
ClassDB::bind_method(D_METHOD("execute_modifications", "delta", "execution_mode"), &Skeleton2D::execute_modifications);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_bone_local_pose_override", "bone_idx", "override_pose", "strength", "persistent"), &Skeleton2D::set_bone_local_pose_override);
|
||||
ClassDB::bind_method(D_METHOD("get_bone_local_pose_override", "bone_idx"), &Skeleton2D::get_bone_local_pose_override);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("_make_bone_setup_dirty"), &Skeleton2D::_make_bone_setup_dirty);
|
||||
|
||||
ADD_SIGNAL(MethodInfo("bone_setup_changed"));
|
||||
}
|
||||
|
||||
Skeleton2D::Skeleton2D() {
|
||||
bone_setup_dirty = true;
|
||||
transform_dirty = true;
|
||||
|
||||
skeleton = RID_PRIME(RS::get_singleton()->skeleton_create());
|
||||
set_notify_transform(true);
|
||||
}
|
||||
|
||||
Skeleton2D::~Skeleton2D() {
|
||||
RS::get_singleton()->free(skeleton);
|
||||
}
|
170
modules/skeleton_2d/nodes/skeleton_2d.h
Normal file
@ -0,0 +1,170 @@
|
||||
#ifndef SKELETON_2D_H
|
||||
#define SKELETON_2D_H
|
||||
|
||||
/*************************************************************************/
|
||||
/* skeleton_2d.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "scene/main/node_2d.h"
|
||||
#include "../resources/skeleton_modification_2d.h"
|
||||
|
||||
class Skeleton2D;
|
||||
|
||||
class Bone2D : public Node2D {
|
||||
GDCLASS(Bone2D, Node2D);
|
||||
|
||||
friend class Skeleton2D;
|
||||
#ifdef TOOLS_ENABLED
|
||||
friend class AnimatedValuesBackup;
|
||||
#endif
|
||||
|
||||
Bone2D *parent_bone;
|
||||
Skeleton2D *skeleton;
|
||||
Transform2D rest;
|
||||
|
||||
bool autocalculate_length_and_angle;
|
||||
float length;
|
||||
float bone_angle;
|
||||
|
||||
int skeleton_index;
|
||||
|
||||
void calculate_length_and_rotation();
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
RID editor_gizmo_rid;
|
||||
bool _editor_get_bone_shape(Vector<Vector2> *p_shape, Vector<Vector2> *p_outline_shape, Bone2D *p_other_bone);
|
||||
bool _editor_show_bone_gizmo = true;
|
||||
#endif // TOOLS ENABLED
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
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<PropertyInfo> *p_list) const;
|
||||
|
||||
public:
|
||||
Transform2D cache_transform;
|
||||
bool copy_transform_to_cache = true;
|
||||
|
||||
void set_rest(const Transform2D &p_rest);
|
||||
Transform2D get_rest() const;
|
||||
void apply_rest();
|
||||
Transform2D get_skeleton_rest() const;
|
||||
|
||||
String get_configuration_warning() const;
|
||||
|
||||
void set_default_length(float p_length);
|
||||
float get_default_length() const;
|
||||
|
||||
void set_autocalculate_length_and_angle(bool p_autocalculate);
|
||||
bool get_autocalculate_length_and_angle() const;
|
||||
|
||||
void set_length(float p_length);
|
||||
float get_length() const;
|
||||
|
||||
void set_bone_angle(float p_angle);
|
||||
float get_bone_angle() const;
|
||||
|
||||
int get_index_in_skeleton() const;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
void _editor_set_show_bone_gizmo(bool p_show_gizmo);
|
||||
bool _editor_get_show_bone_gizmo() const;
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
Bone2D();
|
||||
~Bone2D();
|
||||
};
|
||||
|
||||
class SkeletonModificationStack2D;
|
||||
|
||||
class Skeleton2D : public Node2D {
|
||||
GDCLASS(Skeleton2D, Node2D);
|
||||
|
||||
friend class Bone2D;
|
||||
#ifdef TOOLS_ENABLED
|
||||
friend class AnimatedValuesBackup;
|
||||
#endif
|
||||
|
||||
struct Bone {
|
||||
bool operator<(const Bone &p_bone) const {
|
||||
return p_bone.bone->is_greater_than(bone);
|
||||
}
|
||||
|
||||
Bone2D *bone;
|
||||
int parent_index;
|
||||
Transform2D accum_transform;
|
||||
Transform2D rest_inverse;
|
||||
|
||||
//Transform2D local_pose_cache;
|
||||
Transform2D local_pose_override;
|
||||
float local_pose_override_amount = 0;
|
||||
bool local_pose_override_persistent = false;
|
||||
};
|
||||
|
||||
Vector<Bone> bones;
|
||||
|
||||
bool bone_setup_dirty;
|
||||
void _make_bone_setup_dirty();
|
||||
void _update_bone_setup();
|
||||
|
||||
bool transform_dirty;
|
||||
void _make_transform_dirty();
|
||||
void _update_transform();
|
||||
|
||||
RID skeleton;
|
||||
|
||||
Ref<SkeletonModificationStack2D> modification_stack;
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
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<PropertyInfo> *p_list) const;
|
||||
|
||||
public:
|
||||
int get_bone_count() const;
|
||||
Bone2D *get_bone(int p_idx);
|
||||
|
||||
RID get_skeleton() const;
|
||||
|
||||
void set_bone_local_pose_override(int p_bone_idx, Transform2D p_override, float p_amount, bool p_persistent = true);
|
||||
Transform2D get_bone_local_pose_override(int p_bone_idx);
|
||||
|
||||
Ref<SkeletonModificationStack2D> get_modification_stack() const;
|
||||
void set_modification_stack(Ref<SkeletonModificationStack2D> p_stack);
|
||||
void execute_modifications(float p_delta, int p_execution_mode);
|
||||
|
||||
Skeleton2D();
|
||||
~Skeleton2D();
|
||||
};
|
||||
|
||||
#endif // SKELETON_2D_H
|
48
modules/skeleton_2d/register_types.cpp
Normal file
@ -0,0 +1,48 @@
|
||||
|
||||
#include "register_types.h"
|
||||
|
||||
#include "nodes/physical_bone_2d.h"
|
||||
#include "nodes/skeleton_2d.h"
|
||||
|
||||
#include "resources/skeleton_modification_2d.h"
|
||||
#include "resources/skeleton_modification_2d_ccdik.h"
|
||||
#include "resources/skeleton_modification_2d_fabrik.h"
|
||||
#include "resources/skeleton_modification_2d_jiggle.h"
|
||||
#include "resources/skeleton_modification_2d_lookat.h"
|
||||
#include "resources/skeleton_modification_2d_physicalbones.h"
|
||||
#include "resources/skeleton_modification_2d_stackholder.h"
|
||||
#include "resources/skeleton_modification_2d_twoboneik.h"
|
||||
#include "resources/skeleton_modification_stack_2d.h"
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
#include "editor/skeleton_2d_editor_plugin.h"
|
||||
#endif
|
||||
|
||||
void register_skeleton_2d_types(ModuleRegistrationLevel p_level) {
|
||||
if (p_level == MODULE_REGISTRATION_LEVEL_SCENE) {
|
||||
ClassDB::register_class<Skeleton2D>();
|
||||
ClassDB::register_class<Bone2D>();
|
||||
ClassDB::register_class<PhysicalBone2D>();
|
||||
|
||||
ClassDB::register_class<SkeletonModificationStack2D>();
|
||||
ClassDB::register_class<SkeletonModification2D>();
|
||||
ClassDB::register_class<SkeletonModification2DLookAt>();
|
||||
ClassDB::register_class<SkeletonModification2DCCDIK>();
|
||||
ClassDB::register_class<SkeletonModification2DFABRIK>();
|
||||
ClassDB::register_class<SkeletonModification2DJiggle>();
|
||||
ClassDB::register_class<SkeletonModification2DTwoBoneIK>();
|
||||
ClassDB::register_class<SkeletonModification2DStackHolder>();
|
||||
|
||||
ClassDB::register_class<PhysicalBone2D>();
|
||||
ClassDB::register_class<SkeletonModification2DPhysicalBones>();
|
||||
}
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (p_level == MODULE_REGISTRATION_LEVEL_EDITOR) {
|
||||
EditorPlugins::add_by_type<Skeleton2DEditorPlugin>();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void unregister_skeleton_2d_types(ModuleRegistrationLevel p_level) {
|
||||
}
|
10
modules/skeleton_2d/register_types.h
Normal file
@ -0,0 +1,10 @@
|
||||
#ifndef SKELETON_2D_REGISTER_TYPES_H
|
||||
#define SKELETON_2D_REGISTER_TYPES_H
|
||||
|
||||
#include "modules/register_module_types.h"
|
||||
|
||||
|
||||
void register_skeleton_2d_types(ModuleRegistrationLevel p_level);
|
||||
void unregister_skeleton_2d_types(ModuleRegistrationLevel p_level);
|
||||
|
||||
#endif
|
259
modules/skeleton_2d/resources/skeleton_modification_2d.cpp
Normal file
@ -0,0 +1,259 @@
|
||||
/*************************************************************************/
|
||||
/* skeleton_modification_2d.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "skeleton_modification_2d.h"
|
||||
#include "../nodes/skeleton_2d.h"
|
||||
|
||||
#include "core/config/engine.h"
|
||||
#include "scene/2d/collision_object_2d.h"
|
||||
#include "scene/2d/collision_shape_2d.h"
|
||||
#include "../nodes/physical_bone_2d.h"
|
||||
|
||||
#include "skeleton_modification_stack_2d.h"
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
#include "editor/editor_settings.h"
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
///////////////////////////////////////
|
||||
// Modification2D
|
||||
///////////////////////////////////////
|
||||
|
||||
void SkeletonModification2D::execute(float p_delta) {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
call("_execute", p_delta);
|
||||
}
|
||||
void SkeletonModification2D::setup_modification(Ref<SkeletonModificationStack2D> p_stack) {
|
||||
call("_setup_modification", p_stack);
|
||||
}
|
||||
void SkeletonModification2D::draw_editor_gizmo() {
|
||||
call("_draw_editor_gizmo");
|
||||
}
|
||||
|
||||
void SkeletonModification2D::_execute(float p_delta) {
|
||||
}
|
||||
|
||||
void SkeletonModification2D::_setup_modification(Ref<SkeletonModificationStack2D> p_stack) {
|
||||
}
|
||||
|
||||
void SkeletonModification2D::_draw_editor_gizmo() {
|
||||
}
|
||||
|
||||
void SkeletonModification2D::set_enabled(bool p_enabled) {
|
||||
enabled = p_enabled;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (editor_draw_gizmo) {
|
||||
if (stack) {
|
||||
stack->set_editor_gizmos_dirty(true);
|
||||
}
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
}
|
||||
|
||||
bool SkeletonModification2D::get_enabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
float SkeletonModification2D::clamp_angle(float p_angle, float p_min_bound, float 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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Color bone_ik_color = Color(1.0, 0.65, 0.0, 0.4);
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (Engine::get_singleton()->is_editor_hint()) {
|
||||
bone_ik_color = EditorSettings::get_singleton()->get("editors/2d/bone_ik_color");
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
float arc_angle_min = p_min_bound;
|
||||
float arc_angle_max = p_max_bound;
|
||||
if (arc_angle_min < 0) {
|
||||
arc_angle_min = (Math_PI * 2) + arc_angle_min;
|
||||
}
|
||||
if (arc_angle_max < 0) {
|
||||
arc_angle_max = (Math_PI * 2) + arc_angle_max;
|
||||
}
|
||||
if (arc_angle_min > arc_angle_max) {
|
||||
SWAP(arc_angle_min, arc_angle_max);
|
||||
}
|
||||
arc_angle_min += p_operation_bone->get_bone_angle();
|
||||
arc_angle_max += p_operation_bone->get_bone_angle();
|
||||
|
||||
if (p_constraint_enabled) {
|
||||
if (p_constraint_in_localspace) {
|
||||
Node *operation_bone_parent = p_operation_bone->get_parent();
|
||||
Bone2D *operation_bone_parent_bone = Object::cast_to<Bone2D>(operation_bone_parent);
|
||||
|
||||
if (operation_bone_parent_bone) {
|
||||
stack->skeleton->draw_set_transform(
|
||||
stack->skeleton->to_local(p_operation_bone->get_global_position()),
|
||||
operation_bone_parent_bone->get_global_rotation() - stack->skeleton->get_global_rotation(), Size2(1, 1));
|
||||
} else {
|
||||
stack->skeleton->draw_set_transform(stack->skeleton->to_local(p_operation_bone->get_global_position()), 0, Size2(1, 1));
|
||||
}
|
||||
} else {
|
||||
stack->skeleton->draw_set_transform(stack->skeleton->to_local(p_operation_bone->get_global_position()), 0, Size2(1, 1));
|
||||
}
|
||||
|
||||
if (p_constraint_inverted) {
|
||||
stack->skeleton->draw_arc(Vector2(0, 0), p_operation_bone->get_length(),
|
||||
arc_angle_min + (Math_PI * 2), arc_angle_max, 32, bone_ik_color, 1.0);
|
||||
} else {
|
||||
stack->skeleton->draw_arc(Vector2(0, 0), p_operation_bone->get_length(),
|
||||
arc_angle_min, arc_angle_max, 32, bone_ik_color, 1.0);
|
||||
}
|
||||
stack->skeleton->draw_line(Vector2(0, 0), Vector2(Math::cos(arc_angle_min), Math::sin(arc_angle_min)) * p_operation_bone->get_length(), bone_ik_color, 1.0);
|
||||
stack->skeleton->draw_line(Vector2(0, 0), Vector2(Math::cos(arc_angle_max), Math::sin(arc_angle_max)) * p_operation_bone->get_length(), bone_ik_color, 1.0);
|
||||
|
||||
} else {
|
||||
stack->skeleton->draw_set_transform(stack->skeleton->to_local(p_operation_bone->get_global_position()), 0, Size2(1, 1));
|
||||
stack->skeleton->draw_arc(Vector2(0, 0), p_operation_bone->get_length(), 0, Math_PI * 2, 32, bone_ik_color, 1.0);
|
||||
stack->skeleton->draw_line(Vector2(0, 0), Vector2(1, 0) * p_operation_bone->get_length(), bone_ik_color, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
Ref<SkeletonModificationStack2D> SkeletonModification2D::get_modification_stack() {
|
||||
return stack;
|
||||
}
|
||||
|
||||
void SkeletonModification2D::set_is_setup(bool p_setup) {
|
||||
is_setup = p_setup;
|
||||
}
|
||||
|
||||
bool SkeletonModification2D::get_is_setup() const {
|
||||
return is_setup;
|
||||
}
|
||||
|
||||
void SkeletonModification2D::set_execution_mode(int p_mode) {
|
||||
execution_mode = p_mode;
|
||||
}
|
||||
|
||||
int SkeletonModification2D::get_execution_mode() const {
|
||||
return execution_mode;
|
||||
}
|
||||
|
||||
void SkeletonModification2D::set_editor_draw_gizmo(bool p_draw_gizmo) {
|
||||
editor_draw_gizmo = p_draw_gizmo;
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (is_setup) {
|
||||
if (stack) {
|
||||
stack->set_editor_gizmos_dirty(true);
|
||||
}
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
}
|
||||
|
||||
bool SkeletonModification2D::get_editor_draw_gizmo() const {
|
||||
return editor_draw_gizmo;
|
||||
}
|
||||
|
||||
void SkeletonModification2D::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &SkeletonModification2D::set_enabled);
|
||||
ClassDB::bind_method(D_METHOD("get_enabled"), &SkeletonModification2D::get_enabled);
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "get_enabled");
|
||||
|
||||
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);
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "execution_mode", PROPERTY_HINT_ENUM, "process, physics_process"), "set_execution_mode", "get_execution_mode");
|
||||
|
||||
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("get_modification_stack"), &SkeletonModification2D::get_modification_stack);
|
||||
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);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("execute", "delta"), &SkeletonModification2D::execute);
|
||||
ClassDB::bind_method(D_METHOD("setup_modification", "stack"), &SkeletonModification2D::setup_modification);
|
||||
ClassDB::bind_method(D_METHOD("draw_editor_gizmo"), &SkeletonModification2D::draw_editor_gizmo);
|
||||
|
||||
BIND_VMETHOD(MethodInfo("_execute", PropertyInfo(Variant::OBJECT, "delta")));
|
||||
BIND_VMETHOD(MethodInfo("_setup_modification", PropertyInfo(Variant::OBJECT, "stack", PROPERTY_HINT_RESOURCE_TYPE, "SkeletonModificationStack2D")));
|
||||
BIND_VMETHOD(MethodInfo("_draw_editor_gizmo"));
|
||||
|
||||
ClassDB::bind_method(D_METHOD("_execute", "delta"), &SkeletonModification2D::_execute);
|
||||
ClassDB::bind_method(D_METHOD("_setup_modification", "stack"), &SkeletonModification2D::_setup_modification);
|
||||
ClassDB::bind_method(D_METHOD("_draw_editor_gizmo"), &SkeletonModification2D::_draw_editor_gizmo);
|
||||
}
|
||||
|
||||
SkeletonModification2D::SkeletonModification2D() {
|
||||
stack = nullptr;
|
||||
execution_mode = 0; // 0 = process
|
||||
enabled = true;
|
||||
is_setup = false;
|
||||
|
||||
editor_draw_gizmo = false;
|
||||
}
|
||||
|
||||
SkeletonModification2D::~SkeletonModification2D() {
|
||||
}
|
90
modules/skeleton_2d/resources/skeleton_modification_2d.h
Normal file
@ -0,0 +1,90 @@
|
||||
#ifndef SKELETON_MODIFICATION_2D_H
|
||||
#define SKELETON_MODIFICATION_2D_H
|
||||
|
||||
/*************************************************************************/
|
||||
/* skeleton_modification_2d.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "core/object/resource.h"
|
||||
|
||||
///////////////////////////////////////
|
||||
// SkeletonModification2D
|
||||
///////////////////////////////////////
|
||||
|
||||
class SkeletonModificationStack2D;
|
||||
class Bone2D;
|
||||
class Skeleton2D;
|
||||
|
||||
class SkeletonModification2D : public Resource {
|
||||
GDCLASS(SkeletonModification2D, Resource);
|
||||
friend class Skeleton2D;
|
||||
friend class Bone2D;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
SkeletonModificationStack2D *stack;
|
||||
int execution_mode; // 0 = process
|
||||
|
||||
bool enabled;
|
||||
bool is_setup;
|
||||
|
||||
bool _print_execution_error(bool p_condition, String p_message);
|
||||
|
||||
public:
|
||||
void execute(float p_delta);
|
||||
void setup_modification(Ref<SkeletonModificationStack2D> p_stack);
|
||||
void draw_editor_gizmo();
|
||||
|
||||
virtual void _execute(float _delta);
|
||||
virtual void _setup_modification(Ref<SkeletonModificationStack2D> p_stack);
|
||||
virtual void _draw_editor_gizmo();
|
||||
|
||||
bool editor_draw_gizmo;
|
||||
void set_editor_draw_gizmo(bool p_draw_gizmo);
|
||||
bool get_editor_draw_gizmo() const;
|
||||
|
||||
void set_enabled(bool p_enabled);
|
||||
bool get_enabled();
|
||||
|
||||
Ref<SkeletonModificationStack2D> get_modification_stack();
|
||||
void set_is_setup(bool p_setup);
|
||||
bool get_is_setup() const;
|
||||
|
||||
void set_execution_mode(int p_mode);
|
||||
int get_execution_mode() const;
|
||||
|
||||
float clamp_angle(float p_angle, float p_min_bound, float p_max_bound, bool p_invert_clamp = false);
|
||||
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();
|
||||
~SkeletonModification2D();
|
||||
};
|
||||
|
||||
#endif // SKELETON_MODIFICATION_2D_H
|
551
modules/skeleton_2d/resources/skeleton_modification_2d_ccdik.cpp
Normal file
@ -0,0 +1,551 @@
|
||||
/*************************************************************************/
|
||||
/* skeleton_modification_2d_ccdik.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "skeleton_modification_2d_ccdik.h"
|
||||
#include "../nodes/skeleton_2d.h"
|
||||
#include "skeleton_modification_stack_2d.h"
|
||||
#include "scene/main/node_2d.h"
|
||||
#include "core/config/engine.h"
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
#include "editor/editor_settings.h"
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
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);
|
||||
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);
|
||||
} else if (what == "rotate_from_joint") {
|
||||
set_ccdik_joint_rotate_from_joint(which, p_value);
|
||||
} else if (what == "enable_constraint") {
|
||||
set_ccdik_joint_enable_constraint(which, p_value);
|
||||
} else if (what == "constraint_angle_min") {
|
||||
set_ccdik_joint_constraint_angle_min(which, Math::deg2rad(float(p_value)));
|
||||
} else if (what == "constraint_angle_max") {
|
||||
set_ccdik_joint_constraint_angle_max(which, Math::deg2rad(float(p_value)));
|
||||
} else if (what == "constraint_angle_invert") {
|
||||
set_ccdik_joint_constraint_angle_invert(which, p_value);
|
||||
} else if (what == "constraint_in_localspace") {
|
||||
set_ccdik_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);
|
||||
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);
|
||||
} else if (what == "rotate_from_joint") {
|
||||
r_ret = get_ccdik_joint_rotate_from_joint(which);
|
||||
} else if (what == "enable_constraint") {
|
||||
r_ret = get_ccdik_joint_enable_constraint(which);
|
||||
} else if (what == "constraint_angle_min") {
|
||||
r_ret = Math::rad2deg(get_ccdik_joint_constraint_angle_min(which));
|
||||
} else if (what == "constraint_angle_max") {
|
||||
r_ret = Math::rad2deg(get_ccdik_joint_constraint_angle_max(which));
|
||||
} else if (what == "constraint_angle_invert") {
|
||||
r_ret = get_ccdik_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);
|
||||
}
|
||||
#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<PropertyInfo> *p_list) const {
|
||||
for (int i = 0; i < ccdik_data_chain.size(); i++) {
|
||||
String base_string = "joint_data/" + 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::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::REAL, base_string + "constraint_angle_min", PROPERTY_HINT_RANGE, "-360, 360, 0.01", PROPERTY_USAGE_DEFAULT));
|
||||
p_list->push_back(PropertyInfo(Variant::REAL, base_string + "constraint_angle_max", PROPERTY_HINT_RANGE, "-360, 360, 0.01", 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(float p_delta) {
|
||||
ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr,
|
||||
"Modification is not setup and therefore cannot execute!");
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (target_node_cache == 0) {
|
||||
WARN_PRINT_ONCE("Target cache is out of date. Attempting to update...");
|
||||
update_target_cache();
|
||||
return;
|
||||
}
|
||||
if (tip_node_cache == 0) {
|
||||
WARN_PRINT_ONCE("Tip cache is out of date. Attempting to update...");
|
||||
update_tip_cache();
|
||||
return;
|
||||
}
|
||||
|
||||
Node2D *target = Object::cast_to<Node2D>(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<Node2D>(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);
|
||||
}
|
||||
}
|
||||
|
||||
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 > stack->skeleton->get_bone_count()) {
|
||||
ERR_PRINT_ONCE("2D CCDIK joint: bone index not found!");
|
||||
return;
|
||||
}
|
||||
|
||||
Bone2D *operation_bone = stack->skeleton->get_bone(ccdik_data.bone_idx);
|
||||
Transform2D operation_transform = operation_bone->get_global_transform();
|
||||
|
||||
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());
|
||||
} 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());
|
||||
operation_transform.set_rotation(
|
||||
operation_transform.get_rotation() + (joint_to_target - joint_to_tip));
|
||||
}
|
||||
|
||||
// Reset scale
|
||||
operation_transform.set_scale(operation_bone->get_global_scale());
|
||||
|
||||
// Apply constraints in globalspace:
|
||||
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));
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
// Set the local pose override, and to make sure child bones are also updated, set the transform of the bone.
|
||||
stack->skeleton->set_bone_local_pose_override(ccdik_data.bone_idx, operation_transform, stack->strength, true);
|
||||
operation_bone->set_transform(operation_transform);
|
||||
operation_bone->notification(operation_bone->NOTIFICATION_TRANSFORM_CHANGED);
|
||||
}
|
||||
|
||||
void SkeletonModification2DCCDIK::_setup_modification(Ref<SkeletonModificationStack2D> p_stack) {
|
||||
stack = p_stack.ptr();
|
||||
|
||||
if (stack != nullptr) {
|
||||
is_setup = true;
|
||||
update_target_cache();
|
||||
update_tip_cache();
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModification2DCCDIK::_draw_editor_gizmo() {
|
||||
if (!enabled || !is_setup) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < ccdik_data_chain.size(); i++) {
|
||||
if (!ccdik_data_chain[i].editor_draw_gizmo) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Bone2D *operation_bone = stack->skeleton->get_bone(ccdik_data_chain[i].bone_idx);
|
||||
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 || !stack) {
|
||||
ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!");
|
||||
return;
|
||||
}
|
||||
|
||||
target_node_cache = ObjectID();
|
||||
if (stack->skeleton) {
|
||||
if (stack->skeleton->is_inside_tree()) {
|
||||
if (stack->skeleton->has_node(target_node)) {
|
||||
Node *node = stack->skeleton->get_node(target_node);
|
||||
ERR_FAIL_COND_MSG(!node || stack->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 || !stack) {
|
||||
ERR_PRINT_ONCE("Cannot update tip cache: modification is not properly setup!");
|
||||
return;
|
||||
}
|
||||
|
||||
tip_node_cache = ObjectID();
|
||||
if (stack->skeleton) {
|
||||
if (stack->skeleton->is_inside_tree()) {
|
||||
if (stack->skeleton->has_node(tip_node)) {
|
||||
Node *node = stack->skeleton->get_node(tip_node);
|
||||
ERR_FAIL_COND_MSG(!node || stack->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 || !stack) {
|
||||
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 (stack->skeleton) {
|
||||
if (stack->skeleton->is_inside_tree()) {
|
||||
if (stack->skeleton->has_node(ccdik_data_chain[p_joint_idx].bone2d_node)) {
|
||||
Node *node = stack->skeleton->get_node(ccdik_data_chain[p_joint_idx].bone2d_node);
|
||||
ERR_FAIL_COND_MSG(!node || stack->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<Bone2D>(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();
|
||||
}
|
||||
|
||||
NodePath SkeletonModification2DCCDIK::get_target_node() const {
|
||||
return target_node;
|
||||
}
|
||||
|
||||
void SkeletonModification2DCCDIK::set_tip_node(const NodePath &p_tip_node) {
|
||||
tip_node = p_tip_node;
|
||||
update_tip_cache();
|
||||
}
|
||||
|
||||
NodePath SkeletonModification2DCCDIK::get_tip_node() const {
|
||||
return tip_node;
|
||||
}
|
||||
|
||||
void SkeletonModification2DCCDIK::set_ccdik_data_chain_length(int p_length) {
|
||||
ccdik_data_chain.resize(p_length);
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
int SkeletonModification2DCCDIK::get_ccdik_data_chain_length() {
|
||||
return ccdik_data_chain.size();
|
||||
}
|
||||
|
||||
void SkeletonModification2DCCDIK::set_ccdik_joint_bone2d_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);
|
||||
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
NodePath SkeletonModification2DCCDIK::get_ccdik_joint_bone2d_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;
|
||||
}
|
||||
|
||||
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 (stack->skeleton) {
|
||||
ERR_FAIL_INDEX_MSG(p_bone_idx, stack->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 = stack->skeleton->get_bone(p_bone_idx)->get_instance_id();
|
||||
ccdik_data_chain.write[p_joint_idx].bone2d_node = stack->skeleton->get_path_to(stack->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;
|
||||
}
|
||||
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
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) {
|
||||
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 {
|
||||
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) {
|
||||
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;
|
||||
property_list_changed_notify();
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (stack && is_setup) {
|
||||
stack->set_editor_gizmos_dirty(true);
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
}
|
||||
|
||||
bool SkeletonModification2DCCDIK::get_ccdik_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) {
|
||||
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;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (stack && is_setup) {
|
||||
stack->set_editor_gizmos_dirty(true);
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
}
|
||||
|
||||
float SkeletonModification2DCCDIK::get_ccdik_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) {
|
||||
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;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (stack && is_setup) {
|
||||
stack->set_editor_gizmos_dirty(true);
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
}
|
||||
|
||||
float SkeletonModification2DCCDIK::get_ccdik_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) {
|
||||
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;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (stack && is_setup) {
|
||||
stack->set_editor_gizmos_dirty(true);
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
}
|
||||
|
||||
bool SkeletonModification2DCCDIK::get_ccdik_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) {
|
||||
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;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (stack && is_setup) {
|
||||
stack->set_editor_gizmos_dirty(true);
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
}
|
||||
|
||||
bool SkeletonModification2DCCDIK::get_ccdik_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;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (stack && is_setup) {
|
||||
stack->set_editor_gizmos_dirty(true);
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
SkeletonModification2DCCDIK::SkeletonModification2DCCDIK() {
|
||||
stack = nullptr;
|
||||
is_setup = false;
|
||||
enabled = true;
|
||||
editor_draw_gizmo = true;
|
||||
|
||||
target_node_cache = 0;
|
||||
tip_node_cache = 0;
|
||||
}
|
||||
|
||||
SkeletonModification2DCCDIK::~SkeletonModification2DCCDIK() {
|
||||
}
|
132
modules/skeleton_2d/resources/skeleton_modification_2d_ccdik.h
Normal file
@ -0,0 +1,132 @@
|
||||
#ifndef SKELETON_MODIFICATION_2D_CCDIK_H
|
||||
#define SKELETON_MODIFICATION_2D_CCDIK_H
|
||||
|
||||
/*************************************************************************/
|
||||
/* skeleton_modification_2d_ccdik.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "skeleton_modification_2d.h"
|
||||
|
||||
///////////////////////////////////////
|
||||
// SkeletonModification2DCCDIK
|
||||
///////////////////////////////////////
|
||||
|
||||
class SkeletonModificationStack2D;
|
||||
class Node2D;
|
||||
|
||||
class SkeletonModification2DCCDIK : public SkeletonModification2D {
|
||||
GDCLASS(SkeletonModification2DCCDIK, SkeletonModification2D);
|
||||
|
||||
private:
|
||||
struct CCDIK_Joint_Data2D {
|
||||
int bone_idx;
|
||||
NodePath bone2d_node;
|
||||
ObjectID bone2d_node_cache;
|
||||
bool rotate_from_joint;
|
||||
|
||||
bool enable_constraint;
|
||||
float constraint_angle_min;
|
||||
float constraint_angle_max;
|
||||
bool constraint_angle_invert;
|
||||
bool constraint_in_localspace;
|
||||
|
||||
bool editor_draw_gizmo;
|
||||
|
||||
CCDIK_Joint_Data2D() {
|
||||
bone_idx = -1;
|
||||
bone2d_node_cache = 0;
|
||||
rotate_from_joint = false;
|
||||
|
||||
enable_constraint = false;
|
||||
constraint_angle_min = 0;
|
||||
constraint_angle_max = (2.0 * Math_PI);
|
||||
constraint_angle_invert = false;
|
||||
constraint_in_localspace = true;
|
||||
|
||||
editor_draw_gizmo = true;
|
||||
}
|
||||
};
|
||||
|
||||
Vector<CCDIK_Joint_Data2D> ccdik_data_chain;
|
||||
|
||||
NodePath target_node;
|
||||
ObjectID target_node_cache;
|
||||
void update_target_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);
|
||||
|
||||
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<PropertyInfo> *p_list) const;
|
||||
|
||||
public:
|
||||
void _execute(float p_delta);
|
||||
void _setup_modification(Ref<SkeletonModificationStack2D> p_stack);
|
||||
void _draw_editor_gizmo();
|
||||
|
||||
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;
|
||||
|
||||
SkeletonModification2DCCDIK();
|
||||
~SkeletonModification2DCCDIK();
|
||||
};
|
||||
|
||||
#endif // SKELETON_MODIFICATION_2D_CCDIK_H
|
@ -0,0 +1,450 @@
|
||||
/*************************************************************************/
|
||||
/* skeleton_modification_2d_fabrik.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "skeleton_modification_2d_fabrik.h"
|
||||
#include "../nodes/skeleton_2d.h"
|
||||
#include "skeleton_modification_stack_2d.h"
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
#include "editor/editor_settings.h"
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
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);
|
||||
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);
|
||||
} else if (what == "magnet_position") {
|
||||
set_fabrik_joint_magnet_position(which, p_value);
|
||||
} else if (what == "use_target_rotation") {
|
||||
set_fabrik_joint_use_target_rotation(which, p_value);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
} else if (what == "magnet_position") {
|
||||
r_ret = get_fabrik_joint_magnet_position(which);
|
||||
} else if (what == "use_target_rotation") {
|
||||
r_ret = get_fabrik_joint_use_target_rotation(which);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void SkeletonModification2DFABRIK::_get_property_list(List<PropertyInfo> *p_list) const {
|
||||
for (int i = 0; i < fabrik_data_chain.size(); i++) {
|
||||
String base_string = "joint_data/" + 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));
|
||||
|
||||
if (i > 0) {
|
||||
p_list->push_back(PropertyInfo(Variant::VECTOR2, base_string + "magnet_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
|
||||
}
|
||||
if (i == fabrik_data_chain.size() - 1) {
|
||||
p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "use_target_rotation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModification2DFABRIK::_execute(float p_delta) {
|
||||
ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr,
|
||||
"Modification is not setup and therefore cannot execute!");
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (target_node_cache == 0) {
|
||||
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;
|
||||
}
|
||||
|
||||
Node2D *target = Object::cast_to<Node2D>(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();
|
||||
|
||||
if (fabrik_data_chain[0].bone2d_node_cache == 0 && !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 *origin_bone2d_node = Object::cast_to<Bone2D>(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;
|
||||
}
|
||||
|
||||
origin_global_pose = origin_bone2d_node->get_global_transform();
|
||||
|
||||
if (fabrik_transform_chain.size() != fabrik_data_chain.size()) {
|
||||
fabrik_transform_chain.resize(fabrik_data_chain.size());
|
||||
}
|
||||
|
||||
for (int i = 0; i < fabrik_data_chain.size(); i++) {
|
||||
// Update the transform chain
|
||||
if (fabrik_data_chain[i].bone2d_node_cache == 0 && !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<Bone2D>(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();
|
||||
}
|
||||
|
||||
Bone2D *final_bone2d_node = Object::cast_to<Bone2D>(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 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<Bone2D>(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);
|
||||
stack->skeleton->set_bone_local_pose_override(fabrik_data_chain[i].bone_idx, joint_bone2d_node->get_transform(), stack->strength, true);
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModification2DFABRIK::chain_backwards() {
|
||||
int final_joint_index = fabrik_data_chain.size() - 1;
|
||||
Bone2D *final_bone2d_node = Object::cast_to<Bone2D>(ObjectDB::get_instance(fabrik_data_chain[final_joint_index].bone2d_node_cache));
|
||||
Transform2D final_bone2d_trans = fabrik_transform_chain[final_joint_index];
|
||||
|
||||
// Apply magnet position
|
||||
if (final_joint_index != 0) {
|
||||
final_bone2d_trans.set_origin(final_bone2d_trans.get_origin() + fabrik_data_chain[final_joint_index].magnet_position);
|
||||
}
|
||||
|
||||
// Set the rotation of the tip bone
|
||||
final_bone2d_trans = final_bone2d_trans.looking_at(target_global_pose.get_origin());
|
||||
|
||||
// Set the position of the tip bone
|
||||
float final_bone2d_angle = final_bone2d_trans.get_rotation();
|
||||
if (fabrik_data_chain[final_joint_index].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);
|
||||
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;
|
||||
|
||||
int i = final_joint_index;
|
||||
while (i >= 1) {
|
||||
Transform2D previous_pose = fabrik_transform_chain[i];
|
||||
i -= 1;
|
||||
Bone2D *current_bone2d_node = Object::cast_to<Bone2D>(ObjectDB::get_instance(fabrik_data_chain[i].bone2d_node_cache));
|
||||
Transform2D current_pose = fabrik_transform_chain[i];
|
||||
|
||||
// Apply magnet position
|
||||
if (i != 0) {
|
||||
current_pose.set_origin(current_pose.get_origin() + fabrik_data_chain[i].magnet_position);
|
||||
}
|
||||
|
||||
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 / (current_pose.get_origin().distance_to(previous_pose.get_origin()));
|
||||
Vector2 finish_position = previous_pose.get_origin().linear_interpolate(current_pose.get_origin(), length);
|
||||
current_pose.set_origin(finish_position);
|
||||
|
||||
// Save the transform
|
||||
fabrik_transform_chain.write[i] = current_pose;
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModification2DFABRIK::chain_forwards() {
|
||||
Transform2D origin_bone2d_trans = fabrik_transform_chain[0];
|
||||
origin_bone2d_trans.set_origin(origin_global_pose.get_origin());
|
||||
// Save the position
|
||||
fabrik_transform_chain.write[0] = origin_bone2d_trans;
|
||||
|
||||
for (int i = 0; i < fabrik_data_chain.size() - 1; i++) {
|
||||
Bone2D *current_bone2d_node = Object::cast_to<Bone2D>(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];
|
||||
|
||||
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()));
|
||||
Vector2 finish_position = current_pose.get_origin().linear_interpolate(next_pose.get_origin(), length);
|
||||
current_pose.set_origin(finish_position);
|
||||
|
||||
// Apply to the bone
|
||||
fabrik_transform_chain.write[i + 1] = current_pose;
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModification2DFABRIK::_setup_modification(Ref<SkeletonModificationStack2D> p_stack) {
|
||||
stack = p_stack.ptr();
|
||||
|
||||
if (stack != nullptr) {
|
||||
is_setup = true;
|
||||
update_target_cache();
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModification2DFABRIK::update_target_cache() {
|
||||
if (!is_setup || !stack) {
|
||||
ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!");
|
||||
return;
|
||||
}
|
||||
|
||||
target_node_cache = ObjectID();
|
||||
if (stack->skeleton) {
|
||||
if (stack->skeleton->is_inside_tree()) {
|
||||
if (stack->skeleton->has_node(target_node)) {
|
||||
Node *node = stack->skeleton->get_node(target_node);
|
||||
ERR_FAIL_COND_MSG(!node || stack->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 || !stack) {
|
||||
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 (stack->skeleton) {
|
||||
if (stack->skeleton->is_inside_tree()) {
|
||||
if (stack->skeleton->has_node(fabrik_data_chain[p_joint_idx].bone2d_node)) {
|
||||
Node *node = stack->skeleton->get_node(fabrik_data_chain[p_joint_idx].bone2d_node);
|
||||
ERR_FAIL_COND_MSG(!node || stack->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<Bone2D>(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!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModification2DFABRIK::set_target_node(const NodePath &p_target_node) {
|
||||
target_node = p_target_node;
|
||||
update_target_cache();
|
||||
}
|
||||
|
||||
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);
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
int SkeletonModification2DFABRIK::get_fabrik_data_chain_length() {
|
||||
return fabrik_data_chain.size();
|
||||
}
|
||||
|
||||
void SkeletonModification2DFABRIK::set_fabrik_joint_bone2d_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);
|
||||
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
NodePath SkeletonModification2DFABRIK::get_fabrik_joint_bone2d_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 (stack->skeleton) {
|
||||
ERR_FAIL_INDEX_MSG(p_bone_idx, stack->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 = stack->skeleton->get_bone(p_bone_idx)->get_instance_id();
|
||||
fabrik_data_chain.write[p_joint_idx].bone2d_node = stack->skeleton->get_path_to(stack->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;
|
||||
}
|
||||
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void SkeletonModification2DFABRIK::set_fabrik_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 {
|
||||
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) {
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
|
||||
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_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);
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
SkeletonModification2DFABRIK::SkeletonModification2DFABRIK() {
|
||||
stack = nullptr;
|
||||
is_setup = false;
|
||||
enabled = true;
|
||||
editor_draw_gizmo = false;
|
||||
|
||||
chain_tolarance = 0.01;
|
||||
chain_max_iterations = 10;
|
||||
chain_iterations = 0;
|
||||
}
|
||||
|
||||
SkeletonModification2DFABRIK::~SkeletonModification2DFABRIK() {
|
||||
}
|
116
modules/skeleton_2d/resources/skeleton_modification_2d_fabrik.h
Normal file
@ -0,0 +1,116 @@
|
||||
#ifndef SKELETON_MODIFICATION_2D_FABRIK_H
|
||||
#define SKELETON_MODIFICATION_2D_FABRIK_H
|
||||
|
||||
/*************************************************************************/
|
||||
/* skeleton_modification_2d_fabrik.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "skeleton_modification_2d.h"
|
||||
|
||||
///////////////////////////////////////
|
||||
// SkeletonModification2DFABRIK
|
||||
///////////////////////////////////////
|
||||
|
||||
class SkeletonModificationStack2D;
|
||||
|
||||
class SkeletonModification2DFABRIK : public SkeletonModification2D {
|
||||
GDCLASS(SkeletonModification2DFABRIK, SkeletonModification2D);
|
||||
|
||||
private:
|
||||
struct FABRIK_Joint_Data2D {
|
||||
int bone_idx;
|
||||
NodePath bone2d_node;
|
||||
ObjectID bone2d_node_cache;
|
||||
|
||||
Vector2 magnet_position;
|
||||
bool use_target_rotation;
|
||||
|
||||
bool editor_draw_gizmo;
|
||||
|
||||
FABRIK_Joint_Data2D() {
|
||||
bone_idx = -1;
|
||||
bone2d_node_cache = 0;
|
||||
use_target_rotation = false;
|
||||
editor_draw_gizmo = true;
|
||||
}
|
||||
};
|
||||
|
||||
Vector<FABRIK_Joint_Data2D> 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<Transform2D> fabrik_transform_chain;
|
||||
|
||||
NodePath target_node;
|
||||
ObjectID target_node_cache;
|
||||
void update_target_cache();
|
||||
|
||||
float chain_tolarance;
|
||||
int chain_max_iterations;
|
||||
int chain_iterations;
|
||||
Transform2D target_global_pose;
|
||||
Transform2D origin_global_pose;
|
||||
|
||||
void fabrik_joint_update_bone2d_cache(int p_joint_idx);
|
||||
void chain_backwards();
|
||||
void chain_forwards();
|
||||
|
||||
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<PropertyInfo> *p_list) const;
|
||||
|
||||
public:
|
||||
void _execute(float p_delta);
|
||||
void _setup_modification(Ref<SkeletonModificationStack2D> p_stack);
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
SkeletonModification2DFABRIK();
|
||||
~SkeletonModification2DFABRIK();
|
||||
};
|
||||
|
||||
#endif // SKELETON_MODIFICATION_2D_FABRIK_H
|
@ -0,0 +1,570 @@
|
||||
/*************************************************************************/
|
||||
/* skeleton_modification_2d_jiggle.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "skeleton_modification_2d_jiggle.h"
|
||||
|
||||
#include "scene/main/node_2d.h"
|
||||
#include "../nodes/skeleton_2d.h"
|
||||
#include "scene/resources/world_2d.h"
|
||||
#include "skeleton_modification_stack_2d.h"
|
||||
|
||||
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);
|
||||
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);
|
||||
} else if (what == "override_defaults") {
|
||||
set_jiggle_joint_override(which, p_value);
|
||||
} else if (what == "stiffness") {
|
||||
set_jiggle_joint_stiffness(which, p_value);
|
||||
} else if (what == "mass") {
|
||||
set_jiggle_joint_mass(which, p_value);
|
||||
} else if (what == "damping") {
|
||||
set_jiggle_joint_damping(which, p_value);
|
||||
} else if (what == "use_gravity") {
|
||||
set_jiggle_joint_use_gravity(which, p_value);
|
||||
} else if (what == "gravity") {
|
||||
set_jiggle_joint_gravity(which, p_value);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
if (path == "use_colliders") {
|
||||
set_use_colliders(p_value);
|
||||
} else if (path == "collision_mask") {
|
||||
set_collision_mask(p_value);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
} else if (what == "override_defaults") {
|
||||
r_ret = get_jiggle_joint_override(which);
|
||||
} else if (what == "stiffness") {
|
||||
r_ret = get_jiggle_joint_stiffness(which);
|
||||
} else if (what == "mass") {
|
||||
r_ret = get_jiggle_joint_mass(which);
|
||||
} else if (what == "damping") {
|
||||
r_ret = get_jiggle_joint_damping(which);
|
||||
} else if (what == "use_gravity") {
|
||||
r_ret = get_jiggle_joint_use_gravity(which);
|
||||
} else if (what == "gravity") {
|
||||
r_ret = get_jiggle_joint_gravity(which);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
if (path == "use_colliders") {
|
||||
r_ret = get_use_colliders();
|
||||
} else if (path == "collision_mask") {
|
||||
r_ret = get_collision_mask();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void SkeletonModification2DJiggle::_get_property_list(List<PropertyInfo> *p_list) const {
|
||||
p_list->push_back(PropertyInfo(Variant::BOOL, "use_colliders", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
|
||||
if (use_colliders) {
|
||||
p_list->push_back(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS, "", PROPERTY_USAGE_DEFAULT));
|
||||
}
|
||||
|
||||
for (int i = 0; i < jiggle_data_chain.size(); i++) {
|
||||
String base_string = "joint_data/" + 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::BOOL, base_string + "override_defaults", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
|
||||
|
||||
if (jiggle_data_chain[i].override_defaults) {
|
||||
p_list->push_back(PropertyInfo(Variant::REAL, base_string + "stiffness", PROPERTY_HINT_RANGE, "0, 1000, 0.01", PROPERTY_USAGE_DEFAULT));
|
||||
p_list->push_back(PropertyInfo(Variant::REAL, base_string + "mass", PROPERTY_HINT_RANGE, "0, 1000, 0.01", PROPERTY_USAGE_DEFAULT));
|
||||
p_list->push_back(PropertyInfo(Variant::REAL, base_string + "damping", PROPERTY_HINT_RANGE, "0, 1, 0.01", PROPERTY_USAGE_DEFAULT));
|
||||
p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "use_gravity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
|
||||
if (jiggle_data_chain[i].use_gravity) {
|
||||
p_list->push_back(PropertyInfo(Variant::VECTOR2, base_string + "gravity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModification2DJiggle::_execute(float p_delta) {
|
||||
ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr,
|
||||
"Modification is not setup and therefore cannot execute!");
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
if (target_node_cache == 0) {
|
||||
WARN_PRINT_ONCE("Target cache is out of date. Attempting to update...");
|
||||
update_target_cache();
|
||||
return;
|
||||
}
|
||||
Node2D *target = Object::cast_to<Node2D>(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;
|
||||
}
|
||||
|
||||
for (int i = 0; i < jiggle_data_chain.size(); i++) {
|
||||
_execute_jiggle_joint(i, target, p_delta);
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModification2DJiggle::_execute_jiggle_joint(int p_joint_idx, Node2D *p_target, 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 > stack->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 == 0 && !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 = stack->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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (jiggle_data_chain[p_joint_idx].use_gravity) {
|
||||
jiggle_data_chain.write[p_joint_idx].force += jiggle_data_chain[p_joint_idx].gravity * p_delta;
|
||||
}
|
||||
|
||||
jiggle_data_chain.write[p_joint_idx].acceleration = jiggle_data_chain[p_joint_idx].force / jiggle_data_chain[p_joint_idx].mass;
|
||||
jiggle_data_chain.write[p_joint_idx].velocity += jiggle_data_chain[p_joint_idx].acceleration * (1 - jiggle_data_chain[p_joint_idx].damping);
|
||||
|
||||
jiggle_data_chain.write[p_joint_idx].dynamic_position += jiggle_data_chain[p_joint_idx].velocity + jiggle_data_chain[p_joint_idx].force;
|
||||
jiggle_data_chain.write[p_joint_idx].dynamic_position += operation_bone_trans.get_origin() - jiggle_data_chain[p_joint_idx].last_position;
|
||||
jiggle_data_chain.write[p_joint_idx].last_position = operation_bone_trans.get_origin();
|
||||
|
||||
// Collision detection/response
|
||||
if (use_colliders) {
|
||||
if (execution_mode == SkeletonModificationStack2D::EXECUTION_MODE::execution_mode_physics_process) {
|
||||
Ref<World2D> world_2d = stack->skeleton->get_world_2d();
|
||||
ERR_FAIL_COND(world_2d.is_null());
|
||||
Physics2DDirectSpaceState *space_state = Physics2DServer::get_singleton()->space_get_direct_state(world_2d->get_space());
|
||||
Physics2DDirectSpaceState::RayResult ray_result;
|
||||
|
||||
// Add exception support?
|
||||
bool ray_hit = space_state->intersect_ray(operation_bone_trans.get_origin(), jiggle_data_chain[p_joint_idx].dynamic_position, ray_result, RBSet<RID>(), collision_mask);
|
||||
|
||||
if (ray_hit) {
|
||||
jiggle_data_chain.write[p_joint_idx].dynamic_position = jiggle_data_chain[p_joint_idx].last_noncollision_position;
|
||||
jiggle_data_chain.write[p_joint_idx].acceleration = Vector2(0, 0);
|
||||
jiggle_data_chain.write[p_joint_idx].velocity = Vector2(0, 0);
|
||||
} else {
|
||||
jiggle_data_chain.write[p_joint_idx].last_noncollision_position = jiggle_data_chain[p_joint_idx].dynamic_position;
|
||||
}
|
||||
} else {
|
||||
WARN_PRINT_ONCE("Jiggle 2D modifier: You cannot detect colliders without the stack mode being set to _physics_process!");
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
stack->skeleton->set_bone_local_pose_override(jiggle_data_chain[p_joint_idx].bone_idx, operation_bone->get_transform(), stack->strength, true);
|
||||
}
|
||||
|
||||
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::_setup_modification(Ref<SkeletonModificationStack2D> p_stack) {
|
||||
stack = p_stack.ptr();
|
||||
|
||||
if (stack) {
|
||||
is_setup = true;
|
||||
|
||||
if (stack->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 < stack->skeleton->get_bone_count()) {
|
||||
Bone2D *bone2d_node = stack->skeleton->get_bone(bone_idx);
|
||||
jiggle_data_chain.write[i].dynamic_position = bone2d_node->get_global_position();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
update_target_cache();
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModification2DJiggle::update_target_cache() {
|
||||
if (!is_setup || !stack) {
|
||||
ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!");
|
||||
return;
|
||||
}
|
||||
|
||||
target_node_cache = ObjectID();
|
||||
if (stack->skeleton) {
|
||||
if (stack->skeleton->is_inside_tree()) {
|
||||
if (stack->skeleton->has_node(target_node)) {
|
||||
Node *node = stack->skeleton->get_node(target_node);
|
||||
ERR_FAIL_COND_MSG(!node || stack->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 || !stack) {
|
||||
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 (stack->skeleton) {
|
||||
if (stack->skeleton->is_inside_tree()) {
|
||||
if (stack->skeleton->has_node(jiggle_data_chain[p_joint_idx].bone2d_node)) {
|
||||
Node *node = stack->skeleton->get_node(jiggle_data_chain[p_joint_idx].bone2d_node);
|
||||
ERR_FAIL_COND_MSG(!node || stack->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<Bone2D>(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!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModification2DJiggle::set_target_node(const NodePath &p_target_node) {
|
||||
target_node = p_target_node;
|
||||
update_target_cache();
|
||||
}
|
||||
|
||||
NodePath SkeletonModification2DJiggle::get_target_node() const {
|
||||
return target_node;
|
||||
}
|
||||
|
||||
void SkeletonModification2DJiggle::set_stiffness(float p_stiffness) {
|
||||
ERR_FAIL_COND_MSG(p_stiffness < 0, "Stiffness cannot be set to a negative value!");
|
||||
stiffness = p_stiffness;
|
||||
_update_jiggle_joint_data();
|
||||
}
|
||||
|
||||
float SkeletonModification2DJiggle::get_stiffness() const {
|
||||
return stiffness;
|
||||
}
|
||||
|
||||
void SkeletonModification2DJiggle::set_mass(float p_mass) {
|
||||
ERR_FAIL_COND_MSG(p_mass < 0, "Mass cannot be set to a negative value!");
|
||||
mass = p_mass;
|
||||
_update_jiggle_joint_data();
|
||||
}
|
||||
|
||||
float SkeletonModification2DJiggle::get_mass() const {
|
||||
return mass;
|
||||
}
|
||||
|
||||
void SkeletonModification2DJiggle::set_damping(float p_damping) {
|
||||
ERR_FAIL_COND_MSG(p_damping < 0, "Damping cannot be set to a negative value!");
|
||||
ERR_FAIL_COND_MSG(p_damping > 1, "Damping cannot be more than one!");
|
||||
damping = p_damping;
|
||||
_update_jiggle_joint_data();
|
||||
}
|
||||
|
||||
float SkeletonModification2DJiggle::get_damping() const {
|
||||
return damping;
|
||||
}
|
||||
|
||||
void SkeletonModification2DJiggle::set_use_gravity(bool p_use_gravity) {
|
||||
use_gravity = p_use_gravity;
|
||||
_update_jiggle_joint_data();
|
||||
}
|
||||
|
||||
bool SkeletonModification2DJiggle::get_use_gravity() const {
|
||||
return use_gravity;
|
||||
}
|
||||
|
||||
void SkeletonModification2DJiggle::set_gravity(Vector2 p_gravity) {
|
||||
gravity = p_gravity;
|
||||
_update_jiggle_joint_data();
|
||||
}
|
||||
|
||||
Vector2 SkeletonModification2DJiggle::get_gravity() const {
|
||||
return gravity;
|
||||
}
|
||||
|
||||
void SkeletonModification2DJiggle::set_use_colliders(bool p_use_colliders) {
|
||||
use_colliders = p_use_colliders;
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
bool SkeletonModification2DJiggle::get_use_colliders() const {
|
||||
return use_colliders;
|
||||
}
|
||||
|
||||
void SkeletonModification2DJiggle::set_collision_mask(int p_mask) {
|
||||
collision_mask = p_mask;
|
||||
}
|
||||
|
||||
int SkeletonModification2DJiggle::get_collision_mask() const {
|
||||
return collision_mask;
|
||||
}
|
||||
|
||||
// Jiggle joint data functions
|
||||
int SkeletonModification2DJiggle::get_jiggle_data_chain_length() {
|
||||
return jiggle_data_chain.size();
|
||||
}
|
||||
|
||||
void SkeletonModification2DJiggle::set_jiggle_data_chain_length(int p_length) {
|
||||
ERR_FAIL_COND(p_length < 0);
|
||||
jiggle_data_chain.resize(p_length);
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
void SkeletonModification2DJiggle::set_jiggle_joint_bone2d_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);
|
||||
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
NodePath SkeletonModification2DJiggle::get_jiggle_joint_bone2d_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;
|
||||
}
|
||||
|
||||
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 (stack->skeleton) {
|
||||
ERR_FAIL_INDEX_MSG(p_bone_idx, stack->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 = stack->skeleton->get_bone(p_bone_idx)->get_instance_id();
|
||||
jiggle_data_chain.write[p_joint_idx].bone2d_node = stack->skeleton->get_path_to(stack->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;
|
||||
}
|
||||
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
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) {
|
||||
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();
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
bool SkeletonModification2DJiggle::get_jiggle_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) {
|
||||
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 {
|
||||
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) {
|
||||
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 {
|
||||
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) {
|
||||
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 {
|
||||
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) {
|
||||
ERR_FAIL_INDEX(p_joint_idx, jiggle_data_chain.size());
|
||||
jiggle_data_chain.write[p_joint_idx].use_gravity = p_use_gravity;
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
bool SkeletonModification2DJiggle::get_jiggle_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) {
|
||||
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 {
|
||||
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("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_stiffness", "stiffness"), &SkeletonModification2DJiggle::set_stiffness);
|
||||
ClassDB::bind_method(D_METHOD("get_stiffness"), &SkeletonModification2DJiggle::get_stiffness);
|
||||
ClassDB::bind_method(D_METHOD("set_mass", "mass"), &SkeletonModification2DJiggle::set_mass);
|
||||
ClassDB::bind_method(D_METHOD("get_mass"), &SkeletonModification2DJiggle::get_mass);
|
||||
ClassDB::bind_method(D_METHOD("set_damping", "damping"), &SkeletonModification2DJiggle::set_damping);
|
||||
ClassDB::bind_method(D_METHOD("get_damping"), &SkeletonModification2DJiggle::get_damping);
|
||||
ClassDB::bind_method(D_METHOD("set_use_gravity", "use_gravity"), &SkeletonModification2DJiggle::set_use_gravity);
|
||||
ClassDB::bind_method(D_METHOD("get_use_gravity"), &SkeletonModification2DJiggle::get_use_gravity);
|
||||
ClassDB::bind_method(D_METHOD("set_gravity", "gravity"), &SkeletonModification2DJiggle::set_gravity);
|
||||
ClassDB::bind_method(D_METHOD("get_gravity"), &SkeletonModification2DJiggle::get_gravity);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_use_colliders", "use_colliders"), &SkeletonModification2DJiggle::set_use_colliders);
|
||||
ClassDB::bind_method(D_METHOD("get_use_colliders"), &SkeletonModification2DJiggle::get_use_colliders);
|
||||
ClassDB::bind_method(D_METHOD("set_collision_mask", "collision_mask"), &SkeletonModification2DJiggle::set_collision_mask);
|
||||
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");
|
||||
ADD_GROUP("Default Joint Settings", "");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::REAL, "stiffness"), "set_stiffness", "get_stiffness");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::REAL, "mass"), "set_mass", "get_mass");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::REAL, "damping", PROPERTY_HINT_RANGE, "0, 1, 0.01"), "set_damping", "get_damping");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_gravity"), "set_use_gravity", "get_use_gravity");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "gravity"), "set_gravity", "get_gravity");
|
||||
}
|
||||
|
||||
SkeletonModification2DJiggle::SkeletonModification2DJiggle() {
|
||||
stack = nullptr;
|
||||
is_setup = false;
|
||||
|
||||
target_node_cache = 0;
|
||||
|
||||
stiffness = 3;
|
||||
mass = 0.75;
|
||||
damping = 0.75;
|
||||
use_gravity = false;
|
||||
gravity = Vector2(0, 6.0);
|
||||
use_colliders = false;
|
||||
collision_mask = 1;
|
||||
enabled = true;
|
||||
editor_draw_gizmo = false; // Nothing to really show in a gizmo right now.
|
||||
}
|
||||
|
||||
SkeletonModification2DJiggle::~SkeletonModification2DJiggle() {
|
||||
}
|
153
modules/skeleton_2d/resources/skeleton_modification_2d_jiggle.h
Normal file
@ -0,0 +1,153 @@
|
||||
#ifndef SKELETON_MODIFICATION_2D_JIGGLE_H
|
||||
#define SKELETON_MODIFICATION_2D_JIGGLE_H
|
||||
|
||||
/*************************************************************************/
|
||||
/* skeleton_modification_2d_jiggle.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "skeleton_modification_2d.h"
|
||||
|
||||
///////////////////////////////////////
|
||||
// SkeletonModification2DJIGGLE
|
||||
///////////////////////////////////////
|
||||
|
||||
class SkeletonModificationStack2D;
|
||||
class Node2D;
|
||||
|
||||
class SkeletonModification2DJiggle : public SkeletonModification2D {
|
||||
GDCLASS(SkeletonModification2DJiggle, SkeletonModification2D);
|
||||
|
||||
private:
|
||||
struct Jiggle_Joint_Data2D {
|
||||
int bone_idx;
|
||||
NodePath bone2d_node;
|
||||
ObjectID bone2d_node_cache;
|
||||
|
||||
bool override_defaults;
|
||||
float stiffness;
|
||||
float mass;
|
||||
float damping;
|
||||
bool use_gravity;
|
||||
Vector2 gravity;
|
||||
|
||||
Vector2 force;
|
||||
Vector2 acceleration;
|
||||
Vector2 velocity;
|
||||
Vector2 last_position;
|
||||
Vector2 dynamic_position;
|
||||
|
||||
Vector2 last_noncollision_position;
|
||||
|
||||
Jiggle_Joint_Data2D() {
|
||||
bone_idx = -1;
|
||||
bone2d_node_cache = 0;
|
||||
|
||||
override_defaults = false;
|
||||
stiffness = 3;
|
||||
mass = 0.75;
|
||||
damping = 0.75;
|
||||
use_gravity = false;
|
||||
gravity = Vector2(0, 6.0);
|
||||
}
|
||||
};
|
||||
|
||||
Vector<Jiggle_Joint_Data2D> jiggle_data_chain;
|
||||
|
||||
NodePath target_node;
|
||||
ObjectID target_node_cache;
|
||||
void update_target_cache();
|
||||
|
||||
float stiffness;
|
||||
float mass;
|
||||
float damping;
|
||||
bool use_gravity;
|
||||
Vector2 gravity;
|
||||
|
||||
bool use_colliders;
|
||||
uint32_t collision_mask;
|
||||
|
||||
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 _update_jiggle_joint_data();
|
||||
|
||||
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<PropertyInfo> *p_list) const;
|
||||
|
||||
public:
|
||||
void _execute(float p_delta);
|
||||
void _setup_modification(Ref<SkeletonModificationStack2D> p_stack);
|
||||
|
||||
void set_target_node(const NodePath &p_target_node);
|
||||
NodePath get_target_node() const;
|
||||
|
||||
void set_stiffness(float p_stiffness);
|
||||
float get_stiffness() const;
|
||||
void set_mass(float p_mass);
|
||||
float get_mass() const;
|
||||
void set_damping(float p_damping);
|
||||
float get_damping() const;
|
||||
void set_use_gravity(bool p_use_gravity);
|
||||
bool get_use_gravity() const;
|
||||
void set_gravity(Vector2 p_gravity);
|
||||
Vector2 get_gravity() const;
|
||||
|
||||
void set_use_colliders(bool p_use_colliders);
|
||||
bool get_use_colliders() const;
|
||||
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;
|
||||
|
||||
SkeletonModification2DJiggle();
|
||||
~SkeletonModification2DJiggle();
|
||||
};
|
||||
|
||||
#endif // SKELETON_MODIFICATION_2D_JIGGLE_H
|
@ -0,0 +1,413 @@
|
||||
/*************************************************************************/
|
||||
/* skeleton_modification_2d_lookat.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "skeleton_modification_2d_lookat.h"
|
||||
#include "../nodes/skeleton_2d.h"
|
||||
#include "skeleton_modification_stack_2d.h"
|
||||
#include "core/config/engine.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::deg2rad(float(p_value)));
|
||||
} else if (path.begins_with("constraint_angle_max")) {
|
||||
set_constraint_angle_max(Math::deg2rad(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::deg2rad(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::rad2deg(get_constraint_angle_min());
|
||||
} else if (path.begins_with("constraint_angle_max")) {
|
||||
r_ret = Math::rad2deg(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::rad2deg(get_additional_rotation());
|
||||
}
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (path.begins_with("editor/draw_gizmo")) {
|
||||
r_ret = get_editor_draw_gizmo();
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SkeletonModification2DLookAt::_get_property_list(List<PropertyInfo> *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::REAL, "constraint_angle_min", PROPERTY_HINT_RANGE, "-360,360,0.01", PROPERTY_USAGE_DEFAULT));
|
||||
p_list->push_back(PropertyInfo(Variant::REAL, "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::REAL, "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::_execute(float p_delta) {
|
||||
ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr,
|
||||
"Modification is not setup and therefore cannot execute!");
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (target_node_cache == 0) {
|
||||
WARN_PRINT_ONCE("Target cache is out of date. Attempting to update...");
|
||||
update_target_cache();
|
||||
return;
|
||||
}
|
||||
|
||||
if (bone2d_node_cache == 0 && !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<Node2D>(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 = stack->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.
|
||||
stack->skeleton->set_bone_local_pose_override(bone_idx, operation_transform, stack->strength, true);
|
||||
operation_bone->set_transform(operation_transform);
|
||||
}
|
||||
|
||||
void SkeletonModification2DLookAt::_setup_modification(Ref<SkeletonModificationStack2D> p_stack) {
|
||||
stack = p_stack.ptr();
|
||||
|
||||
if (stack != nullptr) {
|
||||
is_setup = true;
|
||||
update_target_cache();
|
||||
update_bone2d_cache();
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModification2DLookAt::_draw_editor_gizmo() {
|
||||
if (!enabled || !is_setup) {
|
||||
return;
|
||||
}
|
||||
|
||||
Bone2D *operation_bone = stack->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 || !stack) {
|
||||
ERR_PRINT_ONCE("Cannot update Bone2D cache: modification is not properly setup!");
|
||||
return;
|
||||
}
|
||||
|
||||
bone2d_node_cache = ObjectID();
|
||||
if (stack->skeleton) {
|
||||
if (stack->skeleton->is_inside_tree()) {
|
||||
if (stack->skeleton->has_node(bone2d_node)) {
|
||||
Node *node = stack->skeleton->get_node(bone2d_node);
|
||||
ERR_FAIL_COND_MSG(!node || stack->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<Bone2D>(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();
|
||||
}
|
||||
|
||||
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 && stack) {
|
||||
if (stack->skeleton) {
|
||||
ERR_FAIL_INDEX_MSG(p_bone_idx, stack->skeleton->get_bone_count(), "Passed-in Bone index is out of range!");
|
||||
bone_idx = p_bone_idx;
|
||||
bone2d_node_cache = stack->skeleton->get_bone(p_bone_idx)->get_instance_id();
|
||||
bone2d_node = stack->skeleton->get_path_to(stack->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;
|
||||
}
|
||||
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
void SkeletonModification2DLookAt::update_target_cache() {
|
||||
if (!is_setup || !stack) {
|
||||
ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!");
|
||||
return;
|
||||
}
|
||||
|
||||
target_node_cache = ObjectID();
|
||||
if (stack->skeleton) {
|
||||
if (stack->skeleton->is_inside_tree()) {
|
||||
if (stack->skeleton->has_node(target_node)) {
|
||||
Node *node = stack->skeleton->get_node(target_node);
|
||||
ERR_FAIL_COND_MSG(!node || stack->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 SkeletonModification2DLookAt::set_target_node(const NodePath &p_target_node) {
|
||||
target_node = p_target_node;
|
||||
update_target_cache();
|
||||
}
|
||||
|
||||
NodePath SkeletonModification2DLookAt::get_target_node() const {
|
||||
return target_node;
|
||||
}
|
||||
|
||||
float SkeletonModification2DLookAt::get_additional_rotation() const {
|
||||
return additional_rotation;
|
||||
}
|
||||
|
||||
void SkeletonModification2DLookAt::set_additional_rotation(float p_rotation) {
|
||||
additional_rotation = p_rotation;
|
||||
}
|
||||
|
||||
void SkeletonModification2DLookAt::set_enable_constraint(bool p_constraint) {
|
||||
enable_constraint = p_constraint;
|
||||
property_list_changed_notify();
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (stack && is_setup) {
|
||||
stack->set_editor_gizmos_dirty(true);
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
}
|
||||
|
||||
bool SkeletonModification2DLookAt::get_enable_constraint() const {
|
||||
return enable_constraint;
|
||||
}
|
||||
|
||||
void SkeletonModification2DLookAt::set_constraint_angle_min(float p_angle_min) {
|
||||
constraint_angle_min = p_angle_min;
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (stack && is_setup) {
|
||||
stack->set_editor_gizmos_dirty(true);
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
}
|
||||
|
||||
float SkeletonModification2DLookAt::get_constraint_angle_min() const {
|
||||
return constraint_angle_min;
|
||||
}
|
||||
|
||||
void SkeletonModification2DLookAt::set_constraint_angle_max(float p_angle_max) {
|
||||
constraint_angle_max = p_angle_max;
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (stack && is_setup) {
|
||||
stack->set_editor_gizmos_dirty(true);
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
}
|
||||
|
||||
float SkeletonModification2DLookAt::get_constraint_angle_max() const {
|
||||
return constraint_angle_max;
|
||||
}
|
||||
|
||||
void SkeletonModification2DLookAt::set_constraint_angle_invert(bool p_invert) {
|
||||
constraint_angle_invert = p_invert;
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (stack && is_setup) {
|
||||
stack->set_editor_gizmos_dirty(true);
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
}
|
||||
|
||||
bool SkeletonModification2DLookAt::get_constraint_angle_invert() const {
|
||||
return constraint_angle_invert;
|
||||
}
|
||||
|
||||
void SkeletonModification2DLookAt::set_constraint_in_localspace(bool p_constraint_in_localspace) {
|
||||
constraint_in_localspace = p_constraint_in_localspace;
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (stack && is_setup) {
|
||||
stack->set_editor_gizmos_dirty(true);
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
}
|
||||
|
||||
bool SkeletonModification2DLookAt::get_constraint_in_localspace() const {
|
||||
return constraint_in_localspace;
|
||||
}
|
||||
|
||||
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_target_node", "target_nodepath"), &SkeletonModification2DLookAt::set_target_node);
|
||||
ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification2DLookAt::get_target_node);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_additional_rotation", "rotation"), &SkeletonModification2DLookAt::set_additional_rotation);
|
||||
ClassDB::bind_method(D_METHOD("get_additional_rotation"), &SkeletonModification2DLookAt::get_additional_rotation);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_enable_constraint", "enable_constraint"), &SkeletonModification2DLookAt::set_enable_constraint);
|
||||
ClassDB::bind_method(D_METHOD("get_enable_constraint"), &SkeletonModification2DLookAt::get_enable_constraint);
|
||||
ClassDB::bind_method(D_METHOD("set_constraint_angle_min", "angle_min"), &SkeletonModification2DLookAt::set_constraint_angle_min);
|
||||
ClassDB::bind_method(D_METHOD("get_constraint_angle_min"), &SkeletonModification2DLookAt::get_constraint_angle_min);
|
||||
ClassDB::bind_method(D_METHOD("set_constraint_angle_max", "angle_max"), &SkeletonModification2DLookAt::set_constraint_angle_max);
|
||||
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);
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
SkeletonModification2DLookAt::SkeletonModification2DLookAt() {
|
||||
stack = nullptr;
|
||||
is_setup = false;
|
||||
bone_idx = -1;
|
||||
bone2d_node_cache = 0;
|
||||
target_node_cache = 0;
|
||||
target_node_reference = nullptr;
|
||||
additional_rotation = 0;
|
||||
enable_constraint = false;
|
||||
constraint_angle_min = 0;
|
||||
constraint_angle_max = Math_PI * 2;
|
||||
constraint_angle_invert = false;
|
||||
constraint_in_localspace = true;
|
||||
enabled = true;
|
||||
|
||||
editor_draw_gizmo = true;
|
||||
}
|
||||
|
||||
SkeletonModification2DLookAt::~SkeletonModification2DLookAt() {
|
||||
}
|
102
modules/skeleton_2d/resources/skeleton_modification_2d_lookat.h
Normal file
@ -0,0 +1,102 @@
|
||||
#ifndef SKELETON_MODIFICATION_2D_LOOKAT_H
|
||||
#define SKELETON_MODIFICATION_2D_LOOKAT_H
|
||||
|
||||
/*************************************************************************/
|
||||
/* skeleton_modification_2d_lookat.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "skeleton_modification_2d.h"
|
||||
|
||||
///////////////////////////////////////
|
||||
// SkeletonModification2DLookAt
|
||||
///////////////////////////////////////
|
||||
|
||||
class SkeletonModificationStack2D;
|
||||
class Node2D;
|
||||
|
||||
class SkeletonModification2DLookAt : public SkeletonModification2D {
|
||||
GDCLASS(SkeletonModification2DLookAt, SkeletonModification2D);
|
||||
|
||||
private:
|
||||
int bone_idx;
|
||||
NodePath bone2d_node;
|
||||
ObjectID bone2d_node_cache;
|
||||
|
||||
NodePath target_node;
|
||||
ObjectID target_node_cache;
|
||||
Node2D *target_node_reference;
|
||||
|
||||
float additional_rotation;
|
||||
bool enable_constraint;
|
||||
float constraint_angle_min;
|
||||
float constraint_angle_max;
|
||||
bool constraint_angle_invert;
|
||||
bool constraint_in_localspace;
|
||||
|
||||
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<PropertyInfo> *p_list) const;
|
||||
|
||||
public:
|
||||
void _execute(float p_delta);
|
||||
void _setup_modification(Ref<SkeletonModificationStack2D> p_stack);
|
||||
void _draw_editor_gizmo();
|
||||
|
||||
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_target_node(const NodePath &p_target_node);
|
||||
NodePath get_target_node() const;
|
||||
|
||||
void set_additional_rotation(float p_rotation);
|
||||
float get_additional_rotation() const;
|
||||
|
||||
void set_enable_constraint(bool p_constraint);
|
||||
bool get_enable_constraint() const;
|
||||
void set_constraint_angle_min(float p_angle_min);
|
||||
float get_constraint_angle_min() const;
|
||||
void set_constraint_angle_max(float p_angle_max);
|
||||
float get_constraint_angle_max() const;
|
||||
void set_constraint_angle_invert(bool p_invert);
|
||||
bool get_constraint_angle_invert() const;
|
||||
void set_constraint_in_localspace(bool p_constraint_in_localspace);
|
||||
bool get_constraint_in_localspace() const;
|
||||
|
||||
SkeletonModification2DLookAt();
|
||||
~SkeletonModification2DLookAt();
|
||||
};
|
||||
|
||||
#endif // SKELETON_MODIFICATION_2D_LOOKAT_H
|
@ -0,0 +1,300 @@
|
||||
/*************************************************************************/
|
||||
/* skeleton_modification_2d_physicalbones.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "skeleton_modification_2d_physicalbones.h"
|
||||
#include "../nodes/physical_bone_2d.h"
|
||||
#include "../nodes/skeleton_2d.h"
|
||||
#include "skeleton_modification_stack_2d.h"
|
||||
#include "core/config/engine.h"
|
||||
|
||||
bool SkeletonModification2DPhysicalBones::_set(const StringName &p_path, const Variant &p_value) {
|
||||
String path = p_path;
|
||||
|
||||
#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();
|
||||
property_list_changed_notify();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif //TOOLS_ENABLED
|
||||
|
||||
if (path.begins_with("joint_")) {
|
||||
int which = path.get_slicec('_', 1).to_int();
|
||||
String what = path.get_slicec('_', 2);
|
||||
ERR_FAIL_INDEX_V(which, physical_bone_chain.size(), false);
|
||||
|
||||
if (what == "nodepath") {
|
||||
set_physical_bone_node(which, p_value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SkeletonModification2DPhysicalBones::_get(const StringName &p_path, Variant &r_ret) const {
|
||||
String path = p_path;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (Engine::get_singleton()->is_editor_hint()) {
|
||||
if (path.begins_with("fetch_bones")) {
|
||||
return true; // Do nothing!
|
||||
}
|
||||
}
|
||||
#endif //TOOLS_ENABLED
|
||||
|
||||
if (path.begins_with("joint_")) {
|
||||
int which = path.get_slicec('_', 1).to_int();
|
||||
String what = path.get_slicec('_', 2);
|
||||
ERR_FAIL_INDEX_V(which, physical_bone_chain.size(), false);
|
||||
|
||||
if (what == "nodepath") {
|
||||
r_ret = get_physical_bone_node(which);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void SkeletonModification2DPhysicalBones::_get_property_list(List<PropertyInfo> *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));
|
||||
}
|
||||
#endif //TOOLS_ENABLED
|
||||
|
||||
for (int i = 0; i < physical_bone_chain.size(); 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));
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModification2DPhysicalBones::_execute(float p_delta) {
|
||||
ERR_FAIL_COND_MSG(!stack || !is_setup || stack->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 == 0) {
|
||||
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<PhysicalBone2D>(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() > stack->skeleton->get_bone_count()) {
|
||||
ERR_PRINT_ONCE("PhysicalBone2D at index " + itos(i) + " has invalid Bone2D!");
|
||||
return;
|
||||
}
|
||||
Bone2D *bone_2d = stack->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());
|
||||
stack->skeleton->set_bone_local_pose_override(physical_bone->get_bone2d_index(), bone_2d->get_transform(), stack->strength, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModification2DPhysicalBones::_setup_modification(Ref<SkeletonModificationStack2D> p_stack) {
|
||||
stack = p_stack.ptr();
|
||||
|
||||
if (stack) {
|
||||
is_setup = true;
|
||||
|
||||
if (stack->skeleton) {
|
||||
for (int i = 0; i < physical_bone_chain.size(); i++) {
|
||||
_physical_bone_update_cache(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 || !stack) {
|
||||
if (!stack) {
|
||||
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 (stack->skeleton) {
|
||||
if (stack->skeleton->is_inside_tree()) {
|
||||
if (stack->skeleton->has_node(physical_bone_chain[p_joint_idx].physical_bone_node)) {
|
||||
Node *node = stack->skeleton->get_node(physical_bone_chain[p_joint_idx].physical_bone_node);
|
||||
ERR_FAIL_COND_MSG(!node || stack->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() {
|
||||
return physical_bone_chain.size();
|
||||
}
|
||||
|
||||
void SkeletonModification2DPhysicalBones::set_physical_bone_chain_length(int p_length) {
|
||||
ERR_FAIL_COND(p_length < 0);
|
||||
physical_bone_chain.resize(p_length);
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
void SkeletonModification2DPhysicalBones::fetch_physical_bones() {
|
||||
ERR_FAIL_COND_MSG(!stack, "No modification stack found! Cannot fetch physical bones!");
|
||||
ERR_FAIL_COND_MSG(!stack->skeleton, "No skeleton found! Cannot fetch physical bones!");
|
||||
|
||||
physical_bone_chain.clear();
|
||||
|
||||
List<Node *> node_queue = List<Node *>();
|
||||
node_queue.push_back(stack->skeleton);
|
||||
|
||||
while (node_queue.size() > 0) {
|
||||
Node *node_to_process = node_queue[0];
|
||||
node_queue.pop_front();
|
||||
|
||||
if (node_to_process != nullptr) {
|
||||
PhysicalBone2D *potential_bone = Object::cast_to<PhysicalBone2D>(node_to_process);
|
||||
if (potential_bone) {
|
||||
PhysicalBone_Data2D new_data = PhysicalBone_Data2D();
|
||||
new_data.physical_bone_node = stack->skeleton->get_path_to(potential_bone);
|
||||
new_data.physical_bone_node_cache = potential_bone->get_instance_id();
|
||||
physical_bone_chain.push_back(new_data);
|
||||
}
|
||||
for (int i = 0; i < node_to_process->get_child_count(); i++) {
|
||||
node_queue.push_back(node_to_process->get_child(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModification2DPhysicalBones::start_simulation(const Vector<StringName> &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 Vector<StringName> &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<PhysicalBone2D>(stack->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<PhysicalBone2D>(ObjectDB::get_instance(physical_bone_chain[i].physical_bone_node_cache));
|
||||
if (!physical_bone) {
|
||||
continue;
|
||||
}
|
||||
if (_simulation_state_dirty_names.find(physical_bone->get_name()) != -1) {
|
||||
physical_bone->set_simulate_physics(_simulation_state_dirty_process);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
NodePath SkeletonModification2DPhysicalBones::get_physical_bone_node(int p_joint_idx) const {
|
||||
ERR_FAIL_INDEX_V_MSG(p_joint_idx, physical_bone_chain.size(), NodePath(), "Joint index out of range!");
|
||||
return physical_bone_chain[p_joint_idx].physical_bone_node;
|
||||
}
|
||||
|
||||
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_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(Vector<StringName>()));
|
||||
ClassDB::bind_method(D_METHOD("stop_simulation", "bones"), &SkeletonModification2DPhysicalBones::stop_simulation, DEFVAL(Vector<StringName>()));
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
SkeletonModification2DPhysicalBones::SkeletonModification2DPhysicalBones() {
|
||||
stack = nullptr;
|
||||
is_setup = false;
|
||||
physical_bone_chain = Vector<PhysicalBone_Data2D>();
|
||||
enabled = true;
|
||||
editor_draw_gizmo = false; // Nothing to really show in a gizmo right now.
|
||||
}
|
||||
|
||||
SkeletonModification2DPhysicalBones::~SkeletonModification2DPhysicalBones() {
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
|
||||
#ifndef SKELETON_MODIFICATION_2D_PHYSICALBONES_H
|
||||
#define SKELETON_MODIFICATION_2D_PHYSICALBONES_H
|
||||
|
||||
/*************************************************************************/
|
||||
/* skeleton_modification_2d_physicalbones.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "core/containers/vector.h"
|
||||
#include "core/object/object_id.h"
|
||||
|
||||
#include "skeleton_modification_2d.h"
|
||||
|
||||
///////////////////////////////////////
|
||||
// SkeletonModification2DJIGGLE
|
||||
///////////////////////////////////////
|
||||
|
||||
class SkeletonModificationStack2D;
|
||||
|
||||
class SkeletonModification2DPhysicalBones : public SkeletonModification2D {
|
||||
GDCLASS(SkeletonModification2DPhysicalBones, SkeletonModification2D);
|
||||
|
||||
private:
|
||||
struct PhysicalBone_Data2D {
|
||||
NodePath physical_bone_node;
|
||||
ObjectID physical_bone_node_cache;
|
||||
|
||||
PhysicalBone_Data2D() {
|
||||
physical_bone_node_cache = 0;
|
||||
}
|
||||
};
|
||||
|
||||
Vector<PhysicalBone_Data2D> physical_bone_chain;
|
||||
|
||||
void _physical_bone_update_cache(int p_joint_idx);
|
||||
|
||||
bool _simulation_state_dirty = false;
|
||||
Vector<StringName> _simulation_state_dirty_names;
|
||||
bool _simulation_state_dirty_process = false;
|
||||
void _update_simulation_state();
|
||||
|
||||
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<PropertyInfo> *p_list) const;
|
||||
|
||||
public:
|
||||
void _execute(float p_delta);
|
||||
void _setup_modification(Ref<SkeletonModificationStack2D> p_stack);
|
||||
|
||||
int get_physical_bone_chain_length();
|
||||
void set_physical_bone_chain_length(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 Vector<StringName> &p_bones);
|
||||
void stop_simulation(const Vector<StringName> &p_bones);
|
||||
|
||||
SkeletonModification2DPhysicalBones();
|
||||
~SkeletonModification2DPhysicalBones();
|
||||
};
|
||||
|
||||
#endif // SKELETON_MODIFICATION_2D_PHYSICALBONES_H
|
@ -0,0 +1,133 @@
|
||||
/*************************************************************************/
|
||||
/* skeleton_modification_2d_stackholder.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "skeleton_modification_2d_stackholder.h"
|
||||
#include "../nodes/skeleton_2d.h"
|
||||
#include "skeleton_modification_stack_2d.h"
|
||||
#include "core/config/engine.h"
|
||||
|
||||
bool SkeletonModification2DStackHolder::_set(const StringName &p_path, const Variant &p_value) {
|
||||
String path = p_path;
|
||||
|
||||
if (path == "held_modification_stack") {
|
||||
set_held_modification_stack(p_value);
|
||||
}
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (path == "editor/draw_gizmo") {
|
||||
set_editor_draw_gizmo(p_value);
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SkeletonModification2DStackHolder::_get(const StringName &p_path, Variant &r_ret) const {
|
||||
String path = p_path;
|
||||
|
||||
if (path == "held_modification_stack") {
|
||||
r_ret = get_held_modification_stack();
|
||||
}
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (path == "editor/draw_gizmo") {
|
||||
r_ret = get_editor_draw_gizmo();
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SkeletonModification2DStackHolder::_get_property_list(List<PropertyInfo> *p_list) const {
|
||||
p_list->push_back(PropertyInfo(Variant::OBJECT, "held_modification_stack", PROPERTY_HINT_RESOURCE_TYPE, "SkeletonModificationStack2D", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE));
|
||||
|
||||
#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 SkeletonModification2DStackHolder::_execute(float p_delta) {
|
||||
ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr,
|
||||
"Modification is not setup and therefore cannot execute!");
|
||||
|
||||
if (held_modification_stack.is_valid()) {
|
||||
held_modification_stack->execute(p_delta, execution_mode);
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModification2DStackHolder::_setup_modification(Ref<SkeletonModificationStack2D> p_stack) {
|
||||
stack = p_stack.ptr();
|
||||
|
||||
if (stack != nullptr) {
|
||||
is_setup = true;
|
||||
|
||||
if (held_modification_stack.is_valid()) {
|
||||
held_modification_stack->set_skeleton(stack->get_skeleton());
|
||||
held_modification_stack->setup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModification2DStackHolder::_draw_editor_gizmo() {
|
||||
if (stack) {
|
||||
if (held_modification_stack.is_valid()) {
|
||||
held_modification_stack->draw_editor_gizmos();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModification2DStackHolder::set_held_modification_stack(Ref<SkeletonModificationStack2D> p_held_stack) {
|
||||
held_modification_stack = p_held_stack;
|
||||
|
||||
if (is_setup && held_modification_stack.is_valid()) {
|
||||
held_modification_stack->set_skeleton(stack->get_skeleton());
|
||||
held_modification_stack->setup();
|
||||
}
|
||||
}
|
||||
|
||||
Ref<SkeletonModificationStack2D> SkeletonModification2DStackHolder::get_held_modification_stack() const {
|
||||
return held_modification_stack;
|
||||
}
|
||||
|
||||
void SkeletonModification2DStackHolder::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_held_modification_stack", "held_modification_stack"), &SkeletonModification2DStackHolder::set_held_modification_stack);
|
||||
ClassDB::bind_method(D_METHOD("get_held_modification_stack"), &SkeletonModification2DStackHolder::get_held_modification_stack);
|
||||
}
|
||||
|
||||
SkeletonModification2DStackHolder::SkeletonModification2DStackHolder() {
|
||||
stack = nullptr;
|
||||
is_setup = false;
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
SkeletonModification2DStackHolder::~SkeletonModification2DStackHolder() {
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
#ifndef SKELETON_MODIFICATION_2D_STACKHOLDER_H
|
||||
#define SKELETON_MODIFICATION_2D_STACKHOLDER_H
|
||||
|
||||
/*************************************************************************/
|
||||
/* skeleton_modification_2d_stackholder.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "skeleton_modification_2d.h"
|
||||
|
||||
///////////////////////////////////////
|
||||
// SkeletonModification2DJIGGLE
|
||||
///////////////////////////////////////
|
||||
|
||||
class SkeletonModificationStack2D;
|
||||
|
||||
class SkeletonModification2DStackHolder : public SkeletonModification2D {
|
||||
GDCLASS(SkeletonModification2DStackHolder, SkeletonModification2D);
|
||||
|
||||
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<PropertyInfo> *p_list) const;
|
||||
|
||||
public:
|
||||
Ref<SkeletonModificationStack2D> held_modification_stack;
|
||||
|
||||
void _execute(float p_delta);
|
||||
void _setup_modification(Ref<SkeletonModificationStack2D> p_stack);
|
||||
void _draw_editor_gizmo();
|
||||
|
||||
void set_held_modification_stack(Ref<SkeletonModificationStack2D> p_held_stack);
|
||||
Ref<SkeletonModificationStack2D> get_held_modification_stack() const;
|
||||
|
||||
SkeletonModification2DStackHolder();
|
||||
~SkeletonModification2DStackHolder();
|
||||
};
|
||||
|
||||
#endif // SKELETON_MODIFICATION_2D_STACKHOLDER_H
|
@ -0,0 +1,497 @@
|
||||
/*************************************************************************/
|
||||
/* skeleton_modification_2d_twoboneik.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "skeleton_modification_2d_twoboneik.h"
|
||||
#include "../nodes/skeleton_2d.h"
|
||||
#include "skeleton_modification_stack_2d.h"
|
||||
#include "core/config/engine.h"
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
#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<PropertyInfo> *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::_execute(float p_delta) {
|
||||
ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr,
|
||||
"Modification is not setup and therefore cannot execute!");
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (target_node_cache == 0) {
|
||||
WARN_PRINT_ONCE("Target cache is out of date. Attempting to update...");
|
||||
update_target_cache();
|
||||
return;
|
||||
}
|
||||
|
||||
if (joint_one_bone2d_node_cache == 0 && !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 == 0 && !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<Node2D>(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 = stack->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 = stack->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());
|
||||
}
|
||||
|
||||
stack->skeleton->set_bone_local_pose_override(joint_one_bone_idx, joint_one_bone->get_transform(), stack->strength, true);
|
||||
stack->skeleton->set_bone_local_pose_override(joint_two_bone_idx, joint_two_bone->get_transform(), stack->strength, true);
|
||||
}
|
||||
|
||||
void SkeletonModification2DTwoBoneIK::_setup_modification(Ref<SkeletonModificationStack2D> p_stack) {
|
||||
stack = p_stack.ptr();
|
||||
|
||||
if (stack) {
|
||||
is_setup = true;
|
||||
update_target_cache();
|
||||
update_joint_one_bone2d_cache();
|
||||
update_joint_two_bone2d_cache();
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModification2DTwoBoneIK::_draw_editor_gizmo() {
|
||||
if (!enabled || !is_setup) {
|
||||
return;
|
||||
}
|
||||
|
||||
Bone2D *operation_bone_one = stack->skeleton->get_bone(joint_one_bone_idx);
|
||||
if (!operation_bone_one) {
|
||||
return;
|
||||
}
|
||||
stack->skeleton->draw_set_transform(
|
||||
stack->skeleton->to_local(operation_bone_one->get_global_position()),
|
||||
operation_bone_one->get_global_rotation() - stack->skeleton->get_global_rotation(), Size2(1, 1));
|
||||
|
||||
Color bone_ik_color = Color(1.0, 0.65, 0.0, 0.4);
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (Engine::get_singleton()->is_editor_hint()) {
|
||||
bone_ik_color = EditorSettings::get_singleton()->get("editors/2d/bone_ik_color");
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
if (flip_bend_direction) {
|
||||
float angle = -(Math_PI * 0.5) + operation_bone_one->get_bone_angle();
|
||||
stack->skeleton->draw_line(Vector2(0, 0), Vector2(Math::cos(angle), sin(angle)) * (operation_bone_one->get_length() * 0.5), bone_ik_color, 2.0);
|
||||
} else {
|
||||
float angle = (Math_PI * 0.5) + operation_bone_one->get_bone_angle();
|
||||
stack->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 != 0) {
|
||||
stack->skeleton->draw_set_transform(Vector2(0, 0), 0.0, Size2(1, 1));
|
||||
Node2D *target = Object::cast_to<Node2D>(ObjectDB::get_instance(target_node_cache));
|
||||
target_direction = operation_bone_one->get_global_position().direction_to(target->get_global_position());
|
||||
}
|
||||
|
||||
stack->skeleton->draw_circle(target_direction * target_minimum_distance, 8, bone_ik_color);
|
||||
stack->skeleton->draw_circle(target_direction * target_maximum_distance, 8, bone_ik_color);
|
||||
stack->skeleton->draw_line(target_direction * target_minimum_distance, target_direction * target_maximum_distance, bone_ik_color, 2.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
}
|
||||
|
||||
void SkeletonModification2DTwoBoneIK::update_target_cache() {
|
||||
if (!is_setup || !stack) {
|
||||
ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!");
|
||||
return;
|
||||
}
|
||||
|
||||
target_node_cache = ObjectID();
|
||||
if (stack->skeleton) {
|
||||
if (stack->skeleton->is_inside_tree()) {
|
||||
if (stack->skeleton->has_node(target_node)) {
|
||||
Node *node = stack->skeleton->get_node(target_node);
|
||||
ERR_FAIL_COND_MSG(!node || stack->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 || !stack) {
|
||||
ERR_PRINT_ONCE("Cannot update joint one Bone2D cache: modification is not properly setup!");
|
||||
return;
|
||||
}
|
||||
|
||||
joint_one_bone2d_node_cache = ObjectID();
|
||||
if (stack->skeleton) {
|
||||
if (stack->skeleton->is_inside_tree()) {
|
||||
if (stack->skeleton->has_node(joint_one_bone2d_node)) {
|
||||
Node *node = stack->skeleton->get_node(joint_one_bone2d_node);
|
||||
ERR_FAIL_COND_MSG(!node || stack->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<Bone2D>(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 || !stack) {
|
||||
ERR_PRINT_ONCE("Cannot update joint two Bone2D cache: modification is not properly setup!");
|
||||
return;
|
||||
}
|
||||
|
||||
joint_two_bone2d_node_cache = ObjectID();
|
||||
if (stack->skeleton) {
|
||||
if (stack->skeleton->is_inside_tree()) {
|
||||
if (stack->skeleton->has_node(joint_two_bone2d_node)) {
|
||||
Node *node = stack->skeleton->get_node(joint_two_bone2d_node);
|
||||
ERR_FAIL_COND_MSG(!node || stack->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<Bone2D>(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!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModification2DTwoBoneIK::set_target_node(const NodePath &p_target_node) {
|
||||
target_node = p_target_node;
|
||||
update_target_cache();
|
||||
}
|
||||
|
||||
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();
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
float SkeletonModification2DTwoBoneIK::get_target_minimum_distance() const {
|
||||
return target_minimum_distance;
|
||||
}
|
||||
|
||||
void SkeletonModification2DTwoBoneIK::set_target_maximum_distance(float p_distance) {
|
||||
ERR_FAIL_COND_MSG(p_distance < 0, "Target maximum distance cannot be less than zero!");
|
||||
target_maximum_distance = p_distance;
|
||||
}
|
||||
|
||||
float SkeletonModification2DTwoBoneIK::get_target_maximum_distance() const {
|
||||
return target_maximum_distance;
|
||||
}
|
||||
|
||||
void SkeletonModification2DTwoBoneIK::set_flip_bend_direction(bool p_flip_direction) {
|
||||
flip_bend_direction = p_flip_direction;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (stack && is_setup) {
|
||||
stack->set_editor_gizmos_dirty(true);
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
}
|
||||
|
||||
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_two_bone2d_node(const NodePath &p_target_node) {
|
||||
joint_two_bone2d_node = p_target_node;
|
||||
update_joint_two_bone2d_cache();
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
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 (stack->skeleton) {
|
||||
ERR_FAIL_INDEX_MSG(p_bone_idx, stack->skeleton->get_bone_count(), "Passed-in Bone index is out of range!");
|
||||
joint_one_bone_idx = p_bone_idx;
|
||||
joint_one_bone2d_node_cache = stack->skeleton->get_bone(p_bone_idx)->get_instance_id();
|
||||
joint_one_bone2d_node = stack->skeleton->get_path_to(stack->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;
|
||||
}
|
||||
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
int SkeletonModification2DTwoBoneIK::get_joint_one_bone_idx() const {
|
||||
return joint_one_bone_idx;
|
||||
}
|
||||
|
||||
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 (stack->skeleton) {
|
||||
ERR_FAIL_INDEX_MSG(p_bone_idx, stack->skeleton->get_bone_count(), "Passed-in Bone index is out of range!");
|
||||
joint_two_bone_idx = p_bone_idx;
|
||||
joint_two_bone2d_node_cache = stack->skeleton->get_bone(p_bone_idx)->get_instance_id();
|
||||
joint_two_bone2d_node = stack->skeleton->get_path_to(stack->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;
|
||||
}
|
||||
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_target_minimum_distance", "minimum_distance"), &SkeletonModification2DTwoBoneIK::set_target_minimum_distance);
|
||||
ClassDB::bind_method(D_METHOD("get_target_minimum_distance"), &SkeletonModification2DTwoBoneIK::get_target_minimum_distance);
|
||||
ClassDB::bind_method(D_METHOD("set_target_maximum_distance", "maximum_distance"), &SkeletonModification2DTwoBoneIK::set_target_maximum_distance);
|
||||
ClassDB::bind_method(D_METHOD("get_target_maximum_distance"), &SkeletonModification2DTwoBoneIK::get_target_maximum_distance);
|
||||
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_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::REAL, "target_minimum_distance", PROPERTY_HINT_RANGE, "0,100000000,0.01,suffix:m"), "set_target_minimum_distance", "get_target_minimum_distance");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::REAL, "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");
|
||||
}
|
||||
|
||||
SkeletonModification2DTwoBoneIK::SkeletonModification2DTwoBoneIK() {
|
||||
stack = nullptr;
|
||||
is_setup = false;
|
||||
enabled = true;
|
||||
editor_draw_gizmo = true;
|
||||
|
||||
target_node_cache = 0;
|
||||
target_minimum_distance = 0;
|
||||
target_maximum_distance = 0;
|
||||
flip_bend_direction = false;
|
||||
|
||||
joint_one_bone2d_node_cache = 0;
|
||||
joint_one_bone_idx = -1;
|
||||
|
||||
joint_two_bone2d_node_cache = 0;
|
||||
joint_two_bone_idx = -1;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
editor_draw_min_max = false;
|
||||
#endif // TOOLS_ENABLED
|
||||
}
|
||||
|
||||
SkeletonModification2DTwoBoneIK::~SkeletonModification2DTwoBoneIK() {
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
#ifndef SKELETON_MODIFICATION_2D_TWOBONEIK_H
|
||||
#define SKELETON_MODIFICATION_2D_TWOBONEIK_H
|
||||
|
||||
/*************************************************************************/
|
||||
/* skeleton_modification_2d_twoboneik.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "skeleton_modification_2d.h"
|
||||
|
||||
///////////////////////////////////////
|
||||
// SkeletonModification2DJIGGLE
|
||||
///////////////////////////////////////
|
||||
|
||||
class SkeletonModificationStack2D;
|
||||
|
||||
class SkeletonModification2DTwoBoneIK : public SkeletonModification2D {
|
||||
GDCLASS(SkeletonModification2DTwoBoneIK, SkeletonModification2D);
|
||||
|
||||
private:
|
||||
NodePath target_node;
|
||||
ObjectID target_node_cache;
|
||||
float target_minimum_distance;
|
||||
float target_maximum_distance;
|
||||
bool flip_bend_direction;
|
||||
|
||||
NodePath joint_one_bone2d_node;
|
||||
ObjectID joint_one_bone2d_node_cache;
|
||||
int joint_one_bone_idx;
|
||||
|
||||
NodePath joint_two_bone2d_node;
|
||||
ObjectID joint_two_bone2d_node_cache;
|
||||
int joint_two_bone_idx;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
bool editor_draw_min_max;
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
void update_target_cache();
|
||||
void update_joint_one_bone2d_cache();
|
||||
void update_joint_two_bone2d_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<PropertyInfo> *p_list) const;
|
||||
|
||||
public:
|
||||
void _execute(float p_delta);
|
||||
void _setup_modification(Ref<SkeletonModificationStack2D> p_stack);
|
||||
void _draw_editor_gizmo();
|
||||
|
||||
void set_target_node(const NodePath &p_target_node);
|
||||
NodePath get_target_node() const;
|
||||
|
||||
void set_target_minimum_distance(float p_minimum_distance);
|
||||
float get_target_minimum_distance() const;
|
||||
void set_target_maximum_distance(float p_maximum_distance);
|
||||
float get_target_maximum_distance() const;
|
||||
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;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
void set_editor_draw_min_max(bool p_draw);
|
||||
bool get_editor_draw_min_max() const;
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
SkeletonModification2DTwoBoneIK();
|
||||
~SkeletonModification2DTwoBoneIK();
|
||||
};
|
||||
|
||||
#endif // SKELETON_MODIFICATION_2D_TWOBONEIK_H
|
281
modules/skeleton_2d/resources/skeleton_modification_stack_2d.cpp
Normal file
@ -0,0 +1,281 @@
|
||||
/*************************************************************************/
|
||||
/* skeleton_modification_stack_2d.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "skeleton_modification_stack_2d.h"
|
||||
#include "../nodes/skeleton_2d.h"
|
||||
#include "skeleton_modification_2d.h"
|
||||
|
||||
void SkeletonModificationStack2D::_get_property_list(List<PropertyInfo> *p_list) const {
|
||||
for (int i = 0; i < modifications.size(); i++) {
|
||||
p_list->push_back(
|
||||
PropertyInfo(Variant::OBJECT, "modifications/" + itos(i),
|
||||
PROPERTY_HINT_RESOURCE_TYPE,
|
||||
"SkeletonModification2D",
|
||||
PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE));
|
||||
}
|
||||
}
|
||||
|
||||
bool SkeletonModificationStack2D::_set(const StringName &p_path, const Variant &p_value) {
|
||||
String path = p_path;
|
||||
|
||||
if (path.begins_with("modifications/")) {
|
||||
int mod_idx = path.get_slicec('/', 1).to_int();
|
||||
set_modification(mod_idx, p_value);
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SkeletonModificationStack2D::_get(const StringName &p_path, Variant &r_ret) const {
|
||||
String path = p_path;
|
||||
|
||||
if (path.begins_with("modifications/")) {
|
||||
int mod_idx = path.get_slicec('/', 1).to_int();
|
||||
r_ret = get_modification(mod_idx);
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void SkeletonModificationStack2D::setup() {
|
||||
if (is_setup) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (skeleton != nullptr) {
|
||||
is_setup = true;
|
||||
for (int i = 0; i < modifications.size(); i++) {
|
||||
if (!modifications[i].is_valid()) {
|
||||
continue;
|
||||
}
|
||||
modifications.get(i)->setup_modification(this);
|
||||
}
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
set_editor_gizmos_dirty(true);
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
} else {
|
||||
WARN_PRINT("Cannot setup SkeletonModificationStack2D: no Skeleton2D set!");
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModificationStack2D::execute(float p_delta, int p_execution_mode) {
|
||||
ERR_FAIL_COND_MSG(!is_setup || skeleton == nullptr || is_queued_for_deletion(),
|
||||
"Modification stack is not properly setup and therefore cannot execute!");
|
||||
|
||||
if (!skeleton->is_inside_tree()) {
|
||||
ERR_PRINT_ONCE("Skeleton is not inside SceneTree! Cannot execute modification!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < modifications.size(); i++) {
|
||||
if (!modifications[i].is_valid()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (modifications[i]->get_execution_mode() == p_execution_mode) {
|
||||
modifications.get(i)->execute(p_delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModificationStack2D::draw_editor_gizmos() {
|
||||
if (!is_setup) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (editor_gizmo_dirty) {
|
||||
for (int i = 0; i < modifications.size(); i++) {
|
||||
if (!modifications[i].is_valid()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (modifications[i]->editor_draw_gizmo) {
|
||||
modifications.get(i)->draw_editor_gizmo();
|
||||
}
|
||||
}
|
||||
|
||||
skeleton->draw_set_transform(Vector2(0, 0), 0, Size2(1, 1));
|
||||
editor_gizmo_dirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModificationStack2D::set_editor_gizmos_dirty(bool p_dirty) {
|
||||
if (!is_setup) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!editor_gizmo_dirty && p_dirty) {
|
||||
editor_gizmo_dirty = p_dirty;
|
||||
if (skeleton) {
|
||||
skeleton->update();
|
||||
}
|
||||
} else {
|
||||
editor_gizmo_dirty = p_dirty;
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModificationStack2D::enable_all_modifications(bool p_enabled) {
|
||||
for (int i = 0; i < modifications.size(); i++) {
|
||||
if (!modifications[i].is_valid()) {
|
||||
continue;
|
||||
}
|
||||
modifications.get(i)->set_enabled(p_enabled);
|
||||
}
|
||||
}
|
||||
|
||||
Ref<SkeletonModification2D> SkeletonModificationStack2D::get_modification(int p_mod_idx) const {
|
||||
ERR_FAIL_INDEX_V(p_mod_idx, modifications.size(), nullptr);
|
||||
return modifications[p_mod_idx];
|
||||
}
|
||||
|
||||
void SkeletonModificationStack2D::add_modification(Ref<SkeletonModification2D> p_mod) {
|
||||
ERR_FAIL_COND(!p_mod.is_valid());
|
||||
|
||||
p_mod->setup_modification(this);
|
||||
modifications.push_back(p_mod);
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
set_editor_gizmos_dirty(true);
|
||||
#endif // TOOLS_ENABLED
|
||||
}
|
||||
|
||||
void SkeletonModificationStack2D::delete_modification(int p_mod_idx) {
|
||||
ERR_FAIL_INDEX(p_mod_idx, modifications.size());
|
||||
modifications.remove(p_mod_idx);
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
set_editor_gizmos_dirty(true);
|
||||
#endif // TOOLS_ENABLED
|
||||
}
|
||||
|
||||
void SkeletonModificationStack2D::set_modification(int p_mod_idx, Ref<SkeletonModification2D> p_mod) {
|
||||
ERR_FAIL_INDEX(p_mod_idx, modifications.size());
|
||||
|
||||
if (p_mod == nullptr) {
|
||||
modifications.insert(p_mod_idx, nullptr);
|
||||
} else {
|
||||
p_mod->setup_modification(this);
|
||||
modifications.insert(p_mod_idx, p_mod);
|
||||
}
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
set_editor_gizmos_dirty(true);
|
||||
#endif // TOOLS_ENABLED
|
||||
}
|
||||
|
||||
void SkeletonModificationStack2D::set_modification_count(int p_count) {
|
||||
ERR_FAIL_COND_MSG(p_count < 0, "Modification count cannot be less than zero.");
|
||||
modifications.resize(p_count);
|
||||
property_list_changed_notify();
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
set_editor_gizmos_dirty(true);
|
||||
#endif // TOOLS_ENABLED
|
||||
}
|
||||
|
||||
int SkeletonModificationStack2D::get_modification_count() const {
|
||||
return modifications.size();
|
||||
}
|
||||
|
||||
void SkeletonModificationStack2D::set_skeleton(Skeleton2D *p_skeleton) {
|
||||
skeleton = p_skeleton;
|
||||
}
|
||||
|
||||
Skeleton2D *SkeletonModificationStack2D::get_skeleton() const {
|
||||
return skeleton;
|
||||
}
|
||||
|
||||
bool SkeletonModificationStack2D::get_is_setup() const {
|
||||
return is_setup;
|
||||
}
|
||||
|
||||
void SkeletonModificationStack2D::set_enabled(bool p_enabled) {
|
||||
enabled = p_enabled;
|
||||
}
|
||||
|
||||
bool SkeletonModificationStack2D::get_enabled() const {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
void SkeletonModificationStack2D::set_strength(float p_strength) {
|
||||
ERR_FAIL_COND_MSG(p_strength < 0, "Strength cannot be less than zero!");
|
||||
ERR_FAIL_COND_MSG(p_strength > 1, "Strength cannot be more than one!");
|
||||
strength = p_strength;
|
||||
}
|
||||
|
||||
float SkeletonModificationStack2D::get_strength() const {
|
||||
return strength;
|
||||
}
|
||||
|
||||
void SkeletonModificationStack2D::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("setup"), &SkeletonModificationStack2D::setup);
|
||||
ClassDB::bind_method(D_METHOD("execute", "delta", "execution_mode"), &SkeletonModificationStack2D::execute);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("enable_all_modifications", "enabled"), &SkeletonModificationStack2D::enable_all_modifications);
|
||||
ClassDB::bind_method(D_METHOD("get_modification", "mod_idx"), &SkeletonModificationStack2D::get_modification);
|
||||
ClassDB::bind_method(D_METHOD("add_modification", "modification"), &SkeletonModificationStack2D::add_modification);
|
||||
ClassDB::bind_method(D_METHOD("delete_modification", "mod_idx"), &SkeletonModificationStack2D::delete_modification);
|
||||
ClassDB::bind_method(D_METHOD("set_modification", "mod_idx", "modification"), &SkeletonModificationStack2D::set_modification);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_modification_count", "count"), &SkeletonModificationStack2D::set_modification_count);
|
||||
ClassDB::bind_method(D_METHOD("get_modification_count"), &SkeletonModificationStack2D::get_modification_count);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_is_setup"), &SkeletonModificationStack2D::get_is_setup);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &SkeletonModificationStack2D::set_enabled);
|
||||
ClassDB::bind_method(D_METHOD("get_enabled"), &SkeletonModificationStack2D::get_enabled);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_strength", "strength"), &SkeletonModificationStack2D::set_strength);
|
||||
ClassDB::bind_method(D_METHOD("get_strength"), &SkeletonModificationStack2D::get_strength);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_skeleton"), &SkeletonModificationStack2D::get_skeleton);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "get_enabled");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::REAL, "strength", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_strength", "get_strength");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "modification_count", PROPERTY_HINT_RANGE, "0,100,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_modification_count", "get_modification_count");
|
||||
}
|
||||
|
||||
SkeletonModificationStack2D::SkeletonModificationStack2D() {
|
||||
skeleton = nullptr;
|
||||
is_setup = false;
|
||||
enabled = false;
|
||||
strength = 1.0;
|
||||
|
||||
editor_gizmo_dirty = false;
|
||||
}
|
||||
|
||||
SkeletonModificationStack2D::~SkeletonModificationStack2D() {
|
||||
}
|
100
modules/skeleton_2d/resources/skeleton_modification_stack_2d.h
Normal file
@ -0,0 +1,100 @@
|
||||
|
||||
#ifndef SKELETON_MODIFICATION_STACK_2D_H
|
||||
#define SKELETON_MODIFICATION_STACK_2D_H
|
||||
|
||||
/*************************************************************************/
|
||||
/* skeleton_modification_stack_2d.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "core/object/resource.h"
|
||||
|
||||
///////////////////////////////////////
|
||||
// SkeletonModificationStack2D
|
||||
///////////////////////////////////////
|
||||
|
||||
class Skeleton2D;
|
||||
class SkeletonModification2D;
|
||||
class Bone2D;
|
||||
|
||||
class SkeletonModificationStack2D : public Resource {
|
||||
GDCLASS(SkeletonModificationStack2D, Resource);
|
||||
friend class Skeleton2D;
|
||||
friend class SkeletonModification2D;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
void _get_property_list(List<PropertyInfo> *p_list) const;
|
||||
bool _set(const StringName &p_path, const Variant &p_value);
|
||||
bool _get(const StringName &p_path, Variant &r_ret) const;
|
||||
|
||||
public:
|
||||
Skeleton2D *skeleton;
|
||||
bool is_setup;
|
||||
bool enabled;
|
||||
float strength;
|
||||
|
||||
enum EXECUTION_MODE {
|
||||
execution_mode_process,
|
||||
execution_mode_physics_process
|
||||
};
|
||||
|
||||
Vector<Ref<SkeletonModification2D>> modifications;
|
||||
|
||||
void setup();
|
||||
void execute(float p_delta, int p_execution_mode);
|
||||
|
||||
bool editor_gizmo_dirty;
|
||||
void draw_editor_gizmos();
|
||||
void set_editor_gizmos_dirty(bool p_dirty);
|
||||
|
||||
void enable_all_modifications(bool p_enable);
|
||||
Ref<SkeletonModification2D> get_modification(int p_mod_idx) const;
|
||||
void add_modification(Ref<SkeletonModification2D> p_mod);
|
||||
void delete_modification(int p_mod_idx);
|
||||
void set_modification(int p_mod_idx, Ref<SkeletonModification2D> p_mod);
|
||||
|
||||
void set_modification_count(int p_count);
|
||||
int get_modification_count() const;
|
||||
|
||||
void set_skeleton(Skeleton2D *p_skeleton);
|
||||
Skeleton2D *get_skeleton() const;
|
||||
|
||||
bool get_is_setup() const;
|
||||
|
||||
void set_enabled(bool p_enabled);
|
||||
bool get_enabled() const;
|
||||
|
||||
void set_strength(float p_strength);
|
||||
float get_strength() const;
|
||||
|
||||
SkeletonModificationStack2D();
|
||||
~SkeletonModificationStack2D();
|
||||
};
|
||||
|
||||
#endif // SKELETON_MODIFICATION_STACK_2D_H
|
7
modules/skeleton_3d/.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
.import
|
||||
*.d
|
||||
*.o
|
||||
*.meta
|
||||
*.obj
|
||||
*.pyc
|
||||
*.bc
|
31
modules/skeleton_3d/SCsub
Normal file
@ -0,0 +1,31 @@
|
||||
import os
|
||||
|
||||
Import('env')
|
||||
|
||||
module_env = env.Clone()
|
||||
|
||||
module_env.add_source_files(env.modules_sources,"register_types.cpp")
|
||||
|
||||
module_env.add_source_files(env.modules_sources,"nodes/skeleton.cpp")
|
||||
module_env.add_source_files(env.modules_sources,"resources/skin.cpp")
|
||||
|
||||
module_env.add_source_files(env.modules_sources,"nodes/physical_bone.cpp")
|
||||
|
||||
module_env.add_source_files(env.modules_sources,"nodes/bone_attachment.cpp")
|
||||
|
||||
module_env.add_source_files(env.modules_sources,"nodes/skeleton_ik.cpp")
|
||||
|
||||
module_env.add_source_files(env.modules_sources,"resources/skeleton_modification_3d.cpp")
|
||||
module_env.add_source_files(env.modules_sources,"resources/skeleton_modification_3d_ccdik.cpp")
|
||||
module_env.add_source_files(env.modules_sources,"resources/skeleton_modification_3d_fabrik.cpp")
|
||||
module_env.add_source_files(env.modules_sources,"resources/skeleton_modification_3d_jiggle.cpp")
|
||||
module_env.add_source_files(env.modules_sources,"resources/skeleton_modification_3d_lookat.cpp")
|
||||
module_env.add_source_files(env.modules_sources,"resources/skeleton_modification_3d_stackholder.cpp")
|
||||
module_env.add_source_files(env.modules_sources,"resources/skeleton_modification_3d_twoboneik.cpp")
|
||||
module_env.add_source_files(env.modules_sources,"resources/skeleton_modification_stack_3d.cpp")
|
||||
|
||||
if env["tools"]:
|
||||
env.add_source_files(env.modules_sources, "editor/physical_bone_editor_plugin.cpp")
|
||||
env.add_source_files(env.modules_sources, "editor/skeleton_editor_plugin.cpp")
|
||||
env.add_source_files(env.modules_sources, "editor/skeleton_ik_editor_plugin.cpp")
|
||||
env.add_source_files(env.modules_sources, "editor/physical_bone_plugin.cpp")
|
35
modules/skeleton_3d/config.py
Normal file
@ -0,0 +1,35 @@
|
||||
|
||||
|
||||
def can_build(env, platform):
|
||||
return True
|
||||
|
||||
|
||||
def configure(env):
|
||||
pass
|
||||
|
||||
|
||||
def get_doc_classes():
|
||||
return [
|
||||
"Skeleton",
|
||||
"Skin",
|
||||
"SkinReference",
|
||||
|
||||
"BoneAttachment",
|
||||
|
||||
"PhysicalBone",
|
||||
|
||||
"SkeletonIK",
|
||||
|
||||
"SkeletonModificationStack3D",
|
||||
|
||||
"SkeletonModification3D",
|
||||
"SkeletonModification3DCCDIK",
|
||||
"SkeletonModification3DFABRIK",
|
||||
"SkeletonModification3DJiggle",
|
||||
"SkeletonModification3DLookAt",
|
||||
"SkeletonModification3DStackHolder",
|
||||
"SkeletonModification3DTwoBoneIK",
|
||||
]
|
||||
|
||||
def get_doc_path():
|
||||
return "doc_classes"
|
83
modules/skeleton_3d/doc_classes/BoneAttachment.xml
Normal file
@ -0,0 +1,83 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="BoneAttachment" inherits="Spatial" version="4.2">
|
||||
<brief_description>
|
||||
A node that will attach to a bone.
|
||||
</brief_description>
|
||||
<description>
|
||||
This node will allow you to select a bone for this node to attach to. The BoneAttachment3D node can copy the transform of the select bone, or can override the transform of the selected bone.
|
||||
The BoneAttachment3D node must either be a child of a [Skeleton] node or be given an external [Skeleton] to use in order to function properly.
|
||||
</description>
|
||||
<tutorials>
|
||||
</tutorials>
|
||||
<methods>
|
||||
<method name="get_external_skeleton" qualifiers="const">
|
||||
<return type="NodePath" />
|
||||
<description>
|
||||
Returns the [NodePath] to the external [Skeleton3D] node, if one has been set.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_override_mode" qualifiers="const">
|
||||
<return type="int" />
|
||||
<description>
|
||||
Returns the override mode for the BoneAttachment3D node.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_override_pose" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<description>
|
||||
Returns whether the BoneAttachment3D node is overriding the bone pose of the bone it's attached to.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_use_external_skeleton" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<description>
|
||||
Returns whether the BoneAttachment3D node is using an external [Skeleton3D] rather than attempting to use its parent node as the [Skeleton3D].
|
||||
</description>
|
||||
</method>
|
||||
<method name="on_bone_pose_update">
|
||||
<return type="void" />
|
||||
<argument index="0" name="bone_index" type="int" />
|
||||
<description>
|
||||
A function that is called automatically when the [Skeleton3D] the BoneAttachment3D node is using has a bone that has changed its pose. This function is where the BoneAttachment3D node updates its position so it is correctly bound when it is [i]not[/i] set to override the bone pose.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_external_skeleton">
|
||||
<return type="void" />
|
||||
<argument index="0" name="external_skeleton" type="NodePath" />
|
||||
<description>
|
||||
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].
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_override_mode">
|
||||
<return type="void" />
|
||||
<argument index="0" name="override_mode" type="int" />
|
||||
<description>
|
||||
Sets the override mode for the BoneAttachment3D node. The override mode defines which of the bone poses the BoneAttachment3D node will override.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_override_pose">
|
||||
<return type="void" />
|
||||
<argument index="0" name="override_pose" type="bool" />
|
||||
<description>
|
||||
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.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_use_external_skeleton">
|
||||
<return type="void" />
|
||||
<argument index="0" name="use_external_skeleton" type="bool" />
|
||||
<description>
|
||||
Sets whether the BoneAttachment3D node will use an extenral [Skeleton3D] node rather than attenpting to use its parent node as the [Skeleton3D]. When set to [code]true[/code], the BoneAttachment3D node will use the external [Skeleton3D] node set in [code]set_external_skeleton[/code].
|
||||
</description>
|
||||
</method>
|
||||
</methods>
|
||||
<members>
|
||||
<member name="bone_idx" type="int" setter="set_bone_idx" getter="get_bone_idx" default="-1">
|
||||
The index of the attached bone.
|
||||
</member>
|
||||
<member name="bone_name" type="String" setter="set_bone_name" getter="get_bone_name" default="""">
|
||||
The name of the attached bone.
|
||||
</member>
|
||||
</members>
|
||||
<constants>
|
||||
</constants>
|
||||
</class>
|
72
modules/skeleton_3d/doc_classes/PhysicalBone.xml
Normal file
@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="PhysicalBone" inherits="PhysicsBody" version="4.2">
|
||||
<brief_description>
|
||||
</brief_description>
|
||||
<description>
|
||||
[b]Warning:[/b] With a non-uniform scale this node will probably not function as expected. Please make sure to keep its scale uniform (i.e. the same on all axes), and change the size(s) of its collision shape(s) instead.
|
||||
</description>
|
||||
<tutorials>
|
||||
</tutorials>
|
||||
<methods>
|
||||
<method name="apply_central_impulse">
|
||||
<return type="void" />
|
||||
<argument index="0" name="impulse" type="Vector3" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="apply_impulse">
|
||||
<return type="void" />
|
||||
<argument index="0" name="position" type="Vector3" />
|
||||
<argument index="1" name="impulse" type="Vector3" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_bone_id" qualifiers="const">
|
||||
<return type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_simulate_physics">
|
||||
<return type="bool" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="is_simulating_physics">
|
||||
<return type="bool" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
</methods>
|
||||
<members>
|
||||
<member name="body_offset" type="Transform" setter="set_body_offset" getter="get_body_offset" default="Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0 )">
|
||||
</member>
|
||||
<member name="bounce" type="float" setter="set_bounce" getter="get_bounce" default="0.0">
|
||||
</member>
|
||||
<member name="friction" type="float" setter="set_friction" getter="get_friction" default="1.0">
|
||||
</member>
|
||||
<member name="gravity_scale" type="float" setter="set_gravity_scale" getter="get_gravity_scale" default="1.0">
|
||||
</member>
|
||||
<member name="joint_offset" type="Transform" setter="set_joint_offset" getter="get_joint_offset" default="Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0 )">
|
||||
</member>
|
||||
<member name="joint_type" type="int" setter="set_joint_type" getter="get_joint_type" enum="PhysicalBone.JointType" default="0">
|
||||
</member>
|
||||
<member name="mass" type="float" setter="set_mass" getter="get_mass" default="1.0">
|
||||
</member>
|
||||
<member name="weight" type="float" setter="set_weight" getter="get_weight" default="9.8">
|
||||
</member>
|
||||
</members>
|
||||
<constants>
|
||||
<constant name="JOINT_TYPE_NONE" value="0" enum="JointType">
|
||||
</constant>
|
||||
<constant name="JOINT_TYPE_PIN" value="1" enum="JointType">
|
||||
</constant>
|
||||
<constant name="JOINT_TYPE_CONE" value="2" enum="JointType">
|
||||
</constant>
|
||||
<constant name="JOINT_TYPE_HINGE" value="3" enum="JointType">
|
||||
</constant>
|
||||
<constant name="JOINT_TYPE_SLIDER" value="4" enum="JointType">
|
||||
</constant>
|
||||
<constant name="JOINT_TYPE_6DOF" value="5" enum="JointType">
|
||||
</constant>
|
||||
</constants>
|
||||
</class>
|
451
modules/skeleton_3d/doc_classes/Skeleton.xml
Normal file
@ -0,0 +1,451 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="Skeleton" inherits="Spatial" version="4.2">
|
||||
<brief_description>
|
||||
Skeleton for characters and animated objects.
|
||||
</brief_description>
|
||||
<description>
|
||||
Skeleton 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.
|
||||
</description>
|
||||
<tutorials>
|
||||
<link title="3D Inverse Kinematics Demo">https://godotengine.org/asset-library/asset/523</link>
|
||||
<link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link>
|
||||
</tutorials>
|
||||
<methods>
|
||||
<method name="add_bone">
|
||||
<return type="void" />
|
||||
<argument index="0" name="name" type="String" />
|
||||
<description>
|
||||
Adds a bone, with name [code]name[/code]. [method get_bone_count] will become the bone index.
|
||||
</description>
|
||||
</method>
|
||||
<method name="add_bone_child">
|
||||
<return type="void" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<argument index="1" name="child_bone_idx" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="clear_bones">
|
||||
<return type="void" />
|
||||
<description>
|
||||
Clear all the bones in this skeleton.
|
||||
</description>
|
||||
</method>
|
||||
<method name="clear_bones_global_pose_override">
|
||||
<return type="void" />
|
||||
<description>
|
||||
Removes the global pose override on all bones in the skeleton.
|
||||
</description>
|
||||
</method>
|
||||
<method name="clear_bones_local_pose_override">
|
||||
<return type="void" />
|
||||
<description>
|
||||
Removes the local pose override on all bones in the skeleton.
|
||||
</description>
|
||||
</method>
|
||||
<method name="create_skin_from_rest_transforms">
|
||||
<return type="Skin" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="execute_modifications">
|
||||
<return type="void" />
|
||||
<argument index="0" name="delta" type="float" />
|
||||
<argument index="1" name="execution_mode" type="int" />
|
||||
<description>
|
||||
Executes all the modifications on the [SkeletonModificationStack3D], if the Skeleton3D has one assigned.
|
||||
</description>
|
||||
</method>
|
||||
<method name="find_bone" qualifiers="const">
|
||||
<return type="int" />
|
||||
<argument index="0" name="name" type="String" />
|
||||
<description>
|
||||
Returns the bone index that matches [code]name[/code] as its name.
|
||||
</description>
|
||||
</method>
|
||||
<method name="force_update_all_bone_transforms">
|
||||
<return type="void" />
|
||||
<description>
|
||||
Force updates the bone transforms/poses for all bones in the skeleton.
|
||||
</description>
|
||||
</method>
|
||||
<method name="force_update_all_dirty_bones">
|
||||
<return type="void" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="force_update_bone_children_transforms">
|
||||
<return type="void" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<description>
|
||||
Force updates the bone transform for the bone at [code]bone_idx[/code] and all of its children.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_bone_children">
|
||||
<return type="PoolIntArray" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<description>
|
||||
Returns an array containing the bone indexes of all the children node of the passed in bone, [code]bone_idx[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_bone_count" qualifiers="const">
|
||||
<return type="int" />
|
||||
<description>
|
||||
Returns the amount of bones in the skeleton.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_bone_global_pose" qualifiers="const">
|
||||
<return type="Transform" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<description>
|
||||
Returns the overall transform of the specified bone, with respect to the skeleton. Being relative to the skeleton frame, this is not the actual "global" transform of the bone.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_bone_global_pose_no_override" qualifiers="const">
|
||||
<return type="Transform" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<description>
|
||||
Returns the overall transform of the specified bone, with respect to the skeleton, but without any global pose overrides. Being relative to the skeleton frame, this is not the actual "global" transform of the bone.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_bone_global_pose_override" qualifiers="const">
|
||||
<return type="Transform" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<description>
|
||||
Returns the global pose override transform for [code]bone_idx[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_bone_global_rest" qualifiers="const">
|
||||
<return type="Transform" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<description>
|
||||
Returns the global rest transform for [code]bone_idx[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_bone_local_pose_override" qualifiers="const">
|
||||
<return type="Transform" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<description>
|
||||
Returns the local pose override transform for [code]bone_idx[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_bone_name" qualifiers="const">
|
||||
<return type="String" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<description>
|
||||
Returns the name of the bone at index [code]index[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_bone_parent" qualifiers="const">
|
||||
<return type="int" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<description>
|
||||
Returns the bone index which is the parent of the bone at [code]bone_idx[/code]. If -1, then bone has no parent.
|
||||
[b]Note:[/b] The parent bone returned will always be less than [code]bone_idx[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_bone_pose" qualifiers="const">
|
||||
<return type="Transform" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<description>
|
||||
Returns the pose transform of the specified bone. Pose is applied on top of the custom pose, which is applied on top the rest pose.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_bone_pose_position" qualifiers="const">
|
||||
<return type="Vector3" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_bone_pose_rotation" qualifiers="const">
|
||||
<return type="Quaternion" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_bone_pose_scale" qualifiers="const">
|
||||
<return type="Vector3" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_bone_rest" qualifiers="const">
|
||||
<return type="Transform" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<description>
|
||||
Returns the rest transform for a bone [code]bone_idx[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_modification_stack">
|
||||
<return type="SkeletonModificationStack3D" />
|
||||
<description>
|
||||
Returns the modification stack attached to this skeleton, if one exists.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_parentless_bones">
|
||||
<return type="PoolIntArray" />
|
||||
<description>
|
||||
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.
|
||||
</description>
|
||||
</method>
|
||||
<method name="global_pose_to_local_pose">
|
||||
<return type="Transform" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<argument index="1" name="global_pose" type="Transform" />
|
||||
<description>
|
||||
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].
|
||||
</description>
|
||||
</method>
|
||||
<method name="global_pose_to_world_transform">
|
||||
<return type="Transform" />
|
||||
<argument index="0" name="global_pose" type="Transform" />
|
||||
<description>
|
||||
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 Spatial.global_transform] for example.
|
||||
</description>
|
||||
</method>
|
||||
<method name="global_pose_z_forward_to_bone_forward">
|
||||
<return type="Basis" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<argument index="1" name="basis" type="Basis" />
|
||||
<description>
|
||||
Rotates the given [Basis] so that the forward axis of the Basis is facing in the forward direction of the bone at [code]bone_idx[/code].
|
||||
This is helper function to make using [method Transform.looking_at] easier with bone poses.
|
||||
</description>
|
||||
</method>
|
||||
<method name="init_pose">
|
||||
<return type="void" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="is_bone_enabled" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<description>
|
||||
Returns whether the bone pose for the bone at [code]bone_idx[/code] is enabled.
|
||||
</description>
|
||||
</method>
|
||||
<method name="local_pose_to_global_pose">
|
||||
<return type="Transform" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<argument index="1" name="local_pose" type="Transform" />
|
||||
<description>
|
||||
Converts the passed-in local pose to a global pose relative to the inputted bone, [code]bone_idx[/code].
|
||||
This could be used to convert [method get_bone_pose] for use with the [method set_bone_global_pose_override] function.
|
||||
</description>
|
||||
</method>
|
||||
<method name="localize_rests">
|
||||
<return type="void" />
|
||||
<description>
|
||||
Returns all bones in the skeleton to their rest poses.
|
||||
</description>
|
||||
</method>
|
||||
<method name="physical_bones_add_collision_exception">
|
||||
<return type="void" />
|
||||
<argument index="0" name="exception" type="RID" />
|
||||
<description>
|
||||
Adds a collision exception to the physical bone.
|
||||
Works just like the [RigidBody3D] node.
|
||||
</description>
|
||||
</method>
|
||||
<method name="physical_bones_remove_collision_exception">
|
||||
<return type="void" />
|
||||
<argument index="0" name="exception" type="RID" />
|
||||
<description>
|
||||
Removes a collision exception to the physical bone.
|
||||
Works just like the [RigidBody3D] node.
|
||||
</description>
|
||||
</method>
|
||||
<method name="physical_bones_start_simulation">
|
||||
<return type="void" />
|
||||
<argument index="0" name="bones" type="Array" default="[ ]" />
|
||||
<description>
|
||||
Tells the [PhysicalBone3D] nodes in the Skeleton to start simulating and reacting to the physics world.
|
||||
Optionally, a list of bone names can be passed-in, allowing only the passed-in bones to be simulated.
|
||||
</description>
|
||||
</method>
|
||||
<method name="physical_bones_stop_simulation">
|
||||
<return type="void" />
|
||||
<description>
|
||||
Tells the [PhysicalBone3D] nodes in the Skeleton to stop simulating.
|
||||
</description>
|
||||
</method>
|
||||
<method name="register_skin">
|
||||
<return type="SkinReference" />
|
||||
<argument index="0" name="skin" type="Skin" />
|
||||
<description>
|
||||
Binds the given Skin to the Skeleton.
|
||||
</description>
|
||||
</method>
|
||||
<method name="remove_bone">
|
||||
<return type="void" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="remove_bone_child">
|
||||
<return type="void" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<argument index="1" name="child_bone_idx" type="int" />
|
||||
<description>
|
||||
Removes the passed in child bone index, [code]child_bone_idx[/code], from the passed-in bone, [code]bone_idx[/code], if it exists.
|
||||
[b]Note:[/b] This does not remove the child bone, but instead it removes the connection it has to the parent bone.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_bone_children">
|
||||
<return type="void" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<argument index="1" name="bone_children" type="PoolIntArray" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_bone_enabled">
|
||||
<return type="void" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<argument index="1" name="enabled" type="bool" default="true" />
|
||||
<description>
|
||||
Disables the pose for the bone at [code]bone_idx[/code] if [code]false[/code], enables the bone pose if [code]true[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_bone_global_pose_override">
|
||||
<return type="void" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<argument index="1" name="pose" type="Transform" />
|
||||
<argument index="2" name="amount" type="float" />
|
||||
<argument index="3" name="persistent" type="bool" default="false" />
|
||||
<description>
|
||||
Sets the global pose transform, [code]pose[/code], for the bone at [code]bone_idx[/code].
|
||||
[code]amount[/code] is the interpolation strength that will be used when applying the pose, and [code]persistent[/code] 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 [Spatial], to a global pose.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_bone_local_pose_override">
|
||||
<return type="void" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<argument index="1" name="pose" type="Transform" />
|
||||
<argument index="2" name="amount" type="float" />
|
||||
<argument index="3" name="persistent" type="bool" default="false" />
|
||||
<description>
|
||||
Sets the local pose transform, [code]pose[/code], for the bone at [code]bone_idx[/code].
|
||||
[code]amount[/code] is the interpolation strength that will be used when applying the pose, and [code]persistent[/code] 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.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_bone_name">
|
||||
<return type="void" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<argument index="1" name="name" type="String" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_bone_parent">
|
||||
<return type="void" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<argument index="1" name="parent_idx" type="int" />
|
||||
<description>
|
||||
Sets the bone index [code]parent_idx[/code] as the parent of the bone at [code]bone_idx[/code]. If -1, then bone has no parent.
|
||||
[b]Note:[/b] [code]parent_idx[/code] must be less than [code]bone_idx[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_bone_pose">
|
||||
<return type="void" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<argument index="1" name="pose" type="Transform" />
|
||||
<description>
|
||||
Sets the pose transform for bone [code]bone_idx[/code]. Prefer set_bone_pose_position, set_bone_pose_rotation, or set_bone_pose_scale instead.
|
||||
[b]Note:[/b] The pose transform needs to be in bone space. Use [method world_transform_to_global_pose] to convert a world transform, like one you can get from a [Spatial], to bone space.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_bone_pose_position">
|
||||
<return type="void" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<argument index="1" name="position" type="Vector3" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_bone_pose_rotation">
|
||||
<return type="void" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<argument index="1" name="rotation" type="Quaternion" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_bone_pose_scale">
|
||||
<return type="void" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<argument index="1" name="scale" type="Vector3" />
|
||||
<description>
|
||||
Sets the pose transform for bone [code]bone_idx[/code].
|
||||
[b]Note:[/b] The pose transform needs to be in bone space. Use [method world_transform_to_global_pose] to convert a world transform, like one you can get from a [Spatial], to bone space.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_bone_rest">
|
||||
<return type="void" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<argument index="1" name="rest" type="Transform" />
|
||||
<description>
|
||||
Sets the rest transform for bone [code]bone_idx[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_modification_stack">
|
||||
<return type="void" />
|
||||
<argument index="0" name="modification_stack" type="SkeletonModificationStack3D" />
|
||||
<description>
|
||||
Sets the modification stack for this skeleton to the passed-in modification stack, [code]modification_stack[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="unparent_bone_and_rest">
|
||||
<return type="void" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<description>
|
||||
Unparents the bone at [code]bone_idx[/code] and sets its rest position to that of it's parent prior to being reset.
|
||||
</description>
|
||||
</method>
|
||||
<method name="world_transform_to_global_pose">
|
||||
<return type="Transform" />
|
||||
<argument index="0" name="world_transform" type="Transform" />
|
||||
<description>
|
||||
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 Spatial.global_transform] to a global pose usable with [method set_bone_global_pose_override], for example.
|
||||
</description>
|
||||
</method>
|
||||
</methods>
|
||||
<members>
|
||||
<member name="animate_physical_bones" type="bool" setter="set_animate_physical_bones" getter="get_animate_physical_bones" default="true">
|
||||
</member>
|
||||
<member name="show_rest_only" type="bool" setter="set_show_rest_only" getter="is_show_rest_only" default="false">
|
||||
</member>
|
||||
</members>
|
||||
<signals>
|
||||
<signal name="bone_enabled_changed">
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</signal>
|
||||
<signal name="bone_pose_changed">
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<description>
|
||||
This signal is emitted when one of the bones in the Skeleton3D node have changed their pose. This is used to inform nodes that rely on bone positions that one of the bones in the Skeleton3D have changed their transform/pose.
|
||||
</description>
|
||||
</signal>
|
||||
<signal name="bones_updated">
|
||||
<description>
|
||||
</description>
|
||||
</signal>
|
||||
<signal name="pose_updated">
|
||||
<description>
|
||||
</description>
|
||||
</signal>
|
||||
<signal name="show_rest_only_changed">
|
||||
<description>
|
||||
</description>
|
||||
</signal>
|
||||
</signals>
|
||||
<constants>
|
||||
<constant name="NOTIFICATION_UPDATE_SKELETON" value="50">
|
||||
</constant>
|
||||
</constants>
|
||||
</class>
|
92
modules/skeleton_3d/doc_classes/SkeletonIK.xml
Normal file
@ -0,0 +1,92 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="SkeletonIK" inherits="Node" version="4.2">
|
||||
<brief_description>
|
||||
SkeletonIK is used to place the end bone of a [Skeleton] bone chain at a certain point in 3D by rotating all bones in the chain accordingly.
|
||||
</brief_description>
|
||||
<description>
|
||||
SkeletonIK is used to place the end bone of a [Skeleton] bone chain at a certain point in 3D by rotating all bones in the chain accordingly. A typical scenario for IK in games is to place a characters feet on the ground or a characters hands on a currently hold object. SkeletonIK uses FabrikInverseKinematic internally to solve the bone chain and applies the results to the [Skeleton] [code]bones_global_pose_override[/code] property for all affected bones in the chain. If fully applied this overwrites any bone transform from [Animation]s or bone custom poses set by users. The applied amount can be controlled with the [code]interpolation[/code] property.
|
||||
[codeblock]
|
||||
# Apply IK effect automatically on every new frame (not the current)
|
||||
skeleton_ik_node.start()
|
||||
|
||||
# Apply IK effect only on the current frame
|
||||
skeleton_ik_node.start(true)
|
||||
|
||||
# Stop IK effect and reset bones_global_pose_override on Skeleton
|
||||
skeleton_ik_node.stop()
|
||||
|
||||
# Apply full IK effect
|
||||
skeleton_ik_node.set_interpolation(1.0)
|
||||
|
||||
# Apply half IK effect
|
||||
skeleton_ik_node.set_interpolation(0.5)
|
||||
|
||||
# Apply zero IK effect (a value at or below 0.01 also removes bones_global_pose_override on Skeleton)
|
||||
skeleton_ik_node.set_interpolation(0.0)
|
||||
[/codeblock]
|
||||
</description>
|
||||
<tutorials>
|
||||
<link title="3D Inverse Kinematics Demo">https://godotengine.org/asset-library/asset/523</link>
|
||||
</tutorials>
|
||||
<methods>
|
||||
<method name="get_parent_skeleton" qualifiers="const">
|
||||
<return type="Skeleton" />
|
||||
<description>
|
||||
Returns the parent [Skeleton] Node that was present when SkeletonIK entered the [SceneTree]. Returns null if the parent node was not a [Skeleton] Node when SkeletonIK entered the [SceneTree].
|
||||
</description>
|
||||
</method>
|
||||
<method name="is_running">
|
||||
<return type="bool" />
|
||||
<description>
|
||||
Returns [code]true[/code] if SkeletonIK is applying IK effects on continues frames to the [Skeleton] bones. Returns [code]false[/code] if SkeletonIK is stopped or [method start] was used with the [code]one_time[/code] parameter set to [code]true[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="start">
|
||||
<return type="void" />
|
||||
<argument index="0" name="one_time" type="bool" default="false" />
|
||||
<description>
|
||||
Starts applying IK effects on each frame to the [Skeleton] bones but will only take effect starting on the next frame. If [code]one_time[/code] is [code]true[/code], this will take effect immediately but also reset on the next frame.
|
||||
</description>
|
||||
</method>
|
||||
<method name="stop">
|
||||
<return type="void" />
|
||||
<description>
|
||||
Stops applying IK effects on each frame to the [Skeleton] bones and also calls [method Skeleton.clear_bones_global_pose_override] to remove existing overrides on all bones.
|
||||
</description>
|
||||
</method>
|
||||
</methods>
|
||||
<members>
|
||||
<member name="interpolation" type="float" setter="set_interpolation" getter="get_interpolation" default="1.0">
|
||||
Interpolation value for how much the IK results are applied to the current skeleton bone chain. A value of [code]1.0[/code] will overwrite all skeleton bone transforms completely while a value of [code]0.0[/code] will visually disable the SkeletonIK. A value at or below [code]0.01[/code] also calls [method Skeleton.clear_bones_global_pose_override].
|
||||
</member>
|
||||
<member name="magnet" type="Vector3" setter="set_magnet_position" getter="get_magnet_position" default="Vector3( 0, 0, 0 )">
|
||||
Secondary target position (first is [member target] property or [member target_node]) for the IK chain. Use magnet position (pole target) to control the bending of the IK chain. Only works if the bone chain has more than 2 bones. The middle chain bone position will be linearly interpolated with the magnet position.
|
||||
</member>
|
||||
<member name="max_iterations" type="int" setter="set_max_iterations" getter="get_max_iterations" default="10">
|
||||
Number of iteration loops used by the IK solver to produce more accurate (and elegant) bone chain results.
|
||||
</member>
|
||||
<member name="min_distance" type="float" setter="set_min_distance" getter="get_min_distance" default="0.01">
|
||||
The minimum distance between bone and goal target. If the distance is below this value, the IK solver stops further iterations.
|
||||
</member>
|
||||
<member name="override_tip_basis" type="bool" setter="set_override_tip_basis" getter="is_override_tip_basis" default="true">
|
||||
If [code]true[/code] overwrites the rotation of the tip bone with the rotation of the [member target] (or [member target_node] if defined).
|
||||
</member>
|
||||
<member name="root_bone" type="StringName" setter="set_root_bone" getter="get_root_bone" default="@""">
|
||||
The name of the current root bone, the first bone in the IK chain.
|
||||
</member>
|
||||
<member name="target" type="Transform" setter="set_target_transform" getter="get_target_transform" default="Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0 )">
|
||||
First target of the IK chain where the tip bone is placed and, if [member override_tip_basis] is [code]true[/code], how the tip bone is rotated. If a [member target_node] path is available the nodes transform is used instead and this property is ignored.
|
||||
</member>
|
||||
<member name="target_node" type="NodePath" setter="set_target_node" getter="get_target_node" default="NodePath("")">
|
||||
Target node [NodePath] for the IK chain. If available, the node's current [Transform] is used instead of the [member target] property.
|
||||
</member>
|
||||
<member name="tip_bone" type="StringName" setter="set_tip_bone" getter="get_tip_bone" default="@""">
|
||||
The name of the current tip bone, the last bone in the IK chain placed at the [member target] transform (or [member target_node] if defined).
|
||||
</member>
|
||||
<member name="use_magnet" type="bool" setter="set_use_magnet" getter="is_using_magnet" default="false">
|
||||
If [code]true[/code], instructs the IK solver to consider the secondary magnet target (pole target) when calculating the bone chain. Use the magnet position (pole target) to control the bending of the IK chain.
|
||||
</member>
|
||||
</members>
|
||||
<constants>
|
||||
</constants>
|
||||
</class>
|
79
modules/skeleton_3d/doc_classes/SkeletonModification3D.xml
Normal file
@ -0,0 +1,79 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="SkeletonModification3D" inherits="Resource" version="4.2">
|
||||
<brief_description>
|
||||
A resource that operates on bones in a [Skeleton3D].
|
||||
</brief_description>
|
||||
<description>
|
||||
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.
|
||||
</description>
|
||||
<tutorials>
|
||||
</tutorials>
|
||||
<methods>
|
||||
<method name="_execute" qualifiers="virtual">
|
||||
<return type="void" />
|
||||
<argument index="0" name="delta" type="Object" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="_setup_modification" qualifiers="virtual">
|
||||
<return type="void" />
|
||||
<argument index="0" name="stack" type="SkeletonModificationStack3D" />
|
||||
<description>
|
||||
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!
|
||||
</description>
|
||||
</method>
|
||||
<method name="clamp_angle">
|
||||
<return type="float" />
|
||||
<argument index="0" name="angle" type="float" />
|
||||
<argument index="1" name="min" type="float" />
|
||||
<argument index="2" name="max" type="float" />
|
||||
<argument index="3" name="invert" type="bool" />
|
||||
<description>
|
||||
Takes a angle and clamps it so it is within the passed-in [code]min[/code] and [code]max[/code] range. [code]invert[/code] will inversely clamp the angle, clamping it to the range outside of the given bounds.
|
||||
</description>
|
||||
</method>
|
||||
<method name="execute">
|
||||
<return type="void" />
|
||||
<argument index="0" name="p_delta" type="float" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_is_setup" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<description>
|
||||
Returns whether this modification has been successfully setup or not.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_modification_stack">
|
||||
<return type="SkeletonModificationStack3D" />
|
||||
<description>
|
||||
Returns the [SkeletonModificationStack3D] that this modification is bound to. Through the modification stack, you can access the Skeleton3D the modification is operating on.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_is_setup">
|
||||
<return type="void" />
|
||||
<argument index="0" name="is_setup" type="bool" />
|
||||
<description>
|
||||
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.
|
||||
</description>
|
||||
</method>
|
||||
<method name="setup_modification">
|
||||
<return type="void" />
|
||||
<argument index="0" name="stack" type="SkeletonModificationStack3D" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
</methods>
|
||||
<members>
|
||||
<member name="enabled" type="bool" setter="set_enabled" getter="get_enabled" default="true">
|
||||
When true, the modification's [method _execute] function will be called by the [SkeletonModificationStack3D].
|
||||
</member>
|
||||
<member name="execution_mode" type="int" setter="set_execution_mode" getter="get_execution_mode" default="0">
|
||||
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.
|
||||
</member>
|
||||
</members>
|
||||
<constants>
|
||||
</constants>
|
||||
</class>
|
138
modules/skeleton_3d/doc_classes/SkeletonModification3DCCDIK.xml
Normal file
@ -0,0 +1,138 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="SkeletonModification3DCCDIK" inherits="SkeletonModification3D" version="4.2">
|
||||
<brief_description>
|
||||
A modification that uses CCDIK to manipulate a series of bones to reach a target.
|
||||
</brief_description>
|
||||
<description>
|
||||
This [SkeletonModification3D] uses an algorithm called Cyclic Coordinate Descent Inverse Kinematics, or CCDIK, to manipulate a chain of bones in a Skeleton so it reaches a defined target.
|
||||
CCDIK works by rotating a set of bones, typically called a "bone chain", on a single axis. Each bone is rotated to face the target from the tip (by default), which over a chain of bones allow it to rotate properly to reach the target. Because the bones only rotate on a single axis, CCDIK [i]can[/i] look more robotic than other IK solvers.
|
||||
[b]Note:[/b] The CCDIK modifier has [code]ccdik_joints[/code], which are the data objects that hold the data for each joint in the CCDIK chain. This is different from a bone! CCDIK joints hold the data needed for each bone in the bone chain used by CCDIK.
|
||||
CCDIK also fully supports angle constraints, allowing for more control over how a solution is met.
|
||||
</description>
|
||||
<tutorials>
|
||||
</tutorials>
|
||||
<methods>
|
||||
<method name="get_ccdik_joint_bone_index" qualifiers="const">
|
||||
<return type="int" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
Returns the bone index of the bone assigned to the CCDIK joint at [code]joint_idx[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_ccdik_joint_bone_name" qualifiers="const">
|
||||
<return type="String" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
Returns the name of the bone that is assigned to the CCDIK joint at [code]joint_idx[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_ccdik_joint_ccdik_axis" qualifiers="const">
|
||||
<return type="int" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
Returns the integer representing the joint axis of the CCDIK joint at [code]joint_idx[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_ccdik_joint_constraint_angle_max" qualifiers="const">
|
||||
<return type="float" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
Returns the maximum angle constraint for the joint at [code]joint_idx[/code]. [b]Note:[/b] This angle is in degrees!
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_ccdik_joint_constraint_angle_min" qualifiers="const">
|
||||
<return type="float" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
Returns the minimum angle constraint for the joint at [code]joint_idx[/code]. [b]Note:[/b] This angle is in degrees!
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_ccdik_joint_constraint_invert" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
Returns whether the CCDIK joint at [code]joint_idx[/code] uses an inverted joint constraint. See [method set_ccdik_joint_constraint_invert] for details.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_ccdik_joint_enable_joint_constraint" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
Enables angle constraints to the CCDIK joint at [code]joint_idx[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_ccdik_joint_bone_index">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="bone_index" type="int" />
|
||||
<description>
|
||||
Sets the bone index, [code]bone_index[/code], of the CCDIK joint at [code]joint_idx[/code]. When possible, this will also update the [code]bone_name[/code] of the CCDIK joint based on data provided by the linked skeleton.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_ccdik_joint_bone_name">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="bone_name" type="String" />
|
||||
<description>
|
||||
Sets the bone name, [code]bone_name[/code], of the CCDIK joint at [code]joint_idx[/code]. When possible, this will also update the [code]bone_index[/code] of the CCDIK joint based on data provided by the linked skeleton.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_ccdik_joint_ccdik_axis">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="axis" type="int" />
|
||||
<description>
|
||||
Sets the joint axis of the CCDIK joint at [code]joint_idx[/code] to the passed-in joint axis, [code]axis[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_ccdik_joint_constraint_angle_max">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="max_angle" type="float" />
|
||||
<description>
|
||||
Sets the maximum angle constraint for the joint at [code]joint_idx[/code]. [b]Note:[/b] This angle must be in radians!
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_ccdik_joint_constraint_angle_min">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="min_angle" type="float" />
|
||||
<description>
|
||||
Sets the minimum angle constraint for the joint at [code]joint_idx[/code]. [b]Note:[/b] This angle must be in radians!
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_ccdik_joint_constraint_invert">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="invert" type="bool" />
|
||||
<description>
|
||||
Sets whether the CCDIK joint at [code]joint_idx[/code] uses an inverted joint constraint.
|
||||
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.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_ccdik_joint_enable_joint_constraint">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="enable" type="bool" />
|
||||
<description>
|
||||
Sets whether joint constraints are enabled for the CCDIK joint at [code]joint_idx[/code].
|
||||
</description>
|
||||
</method>
|
||||
</methods>
|
||||
<members>
|
||||
<member name="ccdik_data_chain_length" type="int" setter="set_ccdik_data_chain_length" getter="get_ccdik_data_chain_length" default="0">
|
||||
The amount of CCDIK joints in the CCDIK modification.
|
||||
</member>
|
||||
<member name="high_quality_solve" type="bool" setter="set_use_high_quality_solve" getter="get_use_high_quality_solve" default="true">
|
||||
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.
|
||||
</member>
|
||||
<member name="target_nodepath" type="NodePath" setter="set_target_node" getter="get_target_node" default="NodePath("")">
|
||||
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.
|
||||
</member>
|
||||
<member name="tip_nodepath" type="NodePath" setter="set_tip_node" getter="get_tip_node" default="NodePath("")">
|
||||
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.
|
||||
</member>
|
||||
</members>
|
||||
<constants>
|
||||
</constants>
|
||||
</class>
|
163
modules/skeleton_3d/doc_classes/SkeletonModification3DFABRIK.xml
Normal file
@ -0,0 +1,163 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="SkeletonModification3DFABRIK" inherits="SkeletonModification3D" version="4.2">
|
||||
<brief_description>
|
||||
A modification that uses FABRIK to manipulate a series of bones to reach a target.
|
||||
</brief_description>
|
||||
<description>
|
||||
This [SkeletonModification3D] uses an algorithm called Forward And Backward Reaching Inverse Kinematics, or FABRIK, to rotate a bone chain so that it reaches a target.
|
||||
FABRIK works by knowing the positions and lengths of a series of bones, typically called a "bone chain". It first starts by running a forward pass, which places the final bone at the target's position. Then all other bones are moved towards the tip bone, so they stay at the defined bone length away. Then a backwards pass is performed, where the root/first bone in the FABRIK chain is placed back at the origin. then all other bones are moved so they stay at the defined bone length away. This positions the bone chain so that it reaches the target when possible, but all of the bones stay the correct length away from each other.
|
||||
Because of how FABRIK works, it often gives more natural results than those seen in [SkeletonModification3DCCDIK], though FABRIK currently does not support joint constraints.
|
||||
[b]Note:[/b] The FABRIK modifier has [code]fabrik_joints[/code], which are the data objects that hold the data for each joint in the FABRIK chain. This is different from a bone! FABRIK joints hold the data needed for each bone in the bone chain used by FABRIK.
|
||||
To help control how the FABRIK joints move, a magnet vector can be passed, which can nudge the bones in a certain direction prior to solving, giving a level of control over the final result.
|
||||
</description>
|
||||
<tutorials>
|
||||
</tutorials>
|
||||
<methods>
|
||||
<method name="fabrik_joint_auto_calculate_length">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
Will attempt to automatically calculate the length of the bone assigned to the FABRIK joint at [code]joint_idx[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_fabrik_joint_auto_calculate_length" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
Returns a boolean that indicates whether this modification will attempt to autocalculate the length of the bone assigned to the FABRIK joint at [code]joint_idx[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_fabrik_joint_bone_index" qualifiers="const">
|
||||
<return type="int" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
Returns the bone index of the bone assigned to the FABRIK joint at [code]joint_idx[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_fabrik_joint_bone_name" qualifiers="const">
|
||||
<return type="String" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
Returns the name of the bone that is assigned to the FABRIK joint at [code]joint_idx[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_fabrik_joint_length" qualifiers="const">
|
||||
<return type="float" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
Returns the length of the FABRIK joint at [code]joint_idx[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_fabrik_joint_magnet" qualifiers="const">
|
||||
<return type="Vector3" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
Returns the magnet vector of the FABRIK joint at [code]joint_idx[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_fabrik_joint_tip_node" qualifiers="const">
|
||||
<return type="NodePath" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
Returns the [Spatial]-based node placed at the tip of the FABRIK joint at [code]joint_idx[/code], if one has been set.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_fabrik_joint_use_target_basis" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
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.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_fabrik_joint_use_tip_node" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
Sets the [Spatial]-based node that will be used as the tip of the FABRIK joint at [code]joint_idx[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_fabrik_joint_auto_calculate_length">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="auto_calculate_length" type="bool" />
|
||||
<description>
|
||||
When [code]true[/code], this modification will attempt to automatically calculate the length of the bone for the FABRIK joint at [code]joint_idx[/code]. 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.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_fabrik_joint_bone_index">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="bone_index" type="int" />
|
||||
<description>
|
||||
Sets the bone index, [code]bone_index[/code], of the FABRIK joint at [code]joint_idx[/code]. When possible, this will also update the [code]bone_name[/code] of the FABRIK joint based on data provided by the [Skeleton3D].
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_fabrik_joint_bone_name">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="bone_name" type="String" />
|
||||
<description>
|
||||
Sets the bone name, [code]bone_name[/code], of the FABRIK joint at [code]joint_idx[/code]. When possible, this will also update the [code]bone_index[/code] of the FABRIK joint based on data provided by the [Skeleton3D].
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_fabrik_joint_length">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="length" type="float" />
|
||||
<description>
|
||||
Sets the joint length, [code]length[/code], of the FABRIK joint at [code]joint_idx[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_fabrik_joint_magnet">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="magnet_position" type="Vector3" />
|
||||
<description>
|
||||
Sets the magenet position to [code]magnet_position[/code] for the joint at [code]joint_idx[/code]. 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.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_fabrik_joint_tip_node">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="tip_node" type="NodePath" />
|
||||
<description>
|
||||
Sets the nodepath of the FARIK joint at [code]joint_idx[/code] to [code]tip_node[/code]. 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.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_fabrik_joint_use_target_basis">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="use_target_basis" type="bool" />
|
||||
<description>
|
||||
Sets whether the FABRIK joint at [code]joint_idx[/code] 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.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_fabrik_joint_use_tip_node">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="use_tip_node" type="bool" />
|
||||
<description>
|
||||
Sets whether the tip node should be used when autocalculating the joint length for the FABRIK joint at [code]joint_idx[/code]. This will only work if there is a node assigned to the tip nodepath for this joint.
|
||||
</description>
|
||||
</method>
|
||||
</methods>
|
||||
<members>
|
||||
<member name="chain_max_iterations" type="int" setter="set_chain_max_iterations" getter="get_chain_max_iterations" default="10">
|
||||
The number of times FABRIK will try to solve each time the [code]execute[/code] function is called. Setting this value to a lower number will be result in better performance, but this can also result in harsher movements and slower solves.
|
||||
</member>
|
||||
<member name="chain_tolerance" type="float" setter="set_chain_tolerance" getter="get_chain_tolerance" default="0.01">
|
||||
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.
|
||||
</member>
|
||||
<member name="fabrik_data_chain_length" type="int" setter="set_fabrik_data_chain_length" getter="get_fabrik_data_chain_length" default="0">
|
||||
The amount of FABRIK joints in the FABRIK modification.
|
||||
</member>
|
||||
<member name="target_nodepath" type="NodePath" setter="set_target_node" getter="get_target_node" default="NodePath("")">
|
||||
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.
|
||||
</member>
|
||||
</members>
|
||||
<constants>
|
||||
</constants>
|
||||
</class>
|
201
modules/skeleton_3d/doc_classes/SkeletonModification3DJiggle.xml
Normal file
@ -0,0 +1,201 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="SkeletonModification3DJiggle" inherits="SkeletonModification3D" version="4.2">
|
||||
<brief_description>
|
||||
A modification that jiggles bones as they move towards a target.
|
||||
</brief_description>
|
||||
<description>
|
||||
This modification moves a series of bones, typically called a bone chain, towards a target. What makes this modification special is that it calculates the velocity and acceleration for each bone in the bone chain, and runs a very light physics-like calculation using the inputted values. This allows the bones to overshoot the target and "jiggle" around. It can be configured to act more like a spring, or sway around like cloth might.
|
||||
This modification is useful for adding additional motion to things like hair, the edges of clothing, and more. It has several settings to that allow control over how the joint moves when the target moves.
|
||||
[b]Note:[/b] The Jiggle modifier has [code]jiggle_joints[/code], which are the data objects that hold the data for each joint in the Jiggle chain. This is different from a bone! Jiggle joints hold the data needed for each bone in the bone chain used by the Jiggle modification.
|
||||
</description>
|
||||
<tutorials>
|
||||
</tutorials>
|
||||
<methods>
|
||||
<method name="get_collision_mask" qualifiers="const">
|
||||
<return type="int" />
|
||||
<description>
|
||||
Returns the collision mask that the Jiggle modifier will take into account when performing physics calculations.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_jiggle_joint_bone_index" qualifiers="const">
|
||||
<return type="int" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
Returns the bone index of the bone assigned to the Jiggle joint at [code]joint_idx[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_jiggle_joint_bone_name" qualifiers="const">
|
||||
<return type="String" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
Returns the name of the bone that is assigned to the Jiggle joint at [code]joint_idx[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_jiggle_joint_damping" qualifiers="const">
|
||||
<return type="float" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
Returns the amount of dampening of the Jiggle joint at [code]joint_idx[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_jiggle_joint_gravity" qualifiers="const">
|
||||
<return type="Vector3" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
Returns a [Vector3] representign the amount of gravity the Jiggle joint at [code]joint_idx[/code] is influenced by.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_jiggle_joint_mass" qualifiers="const">
|
||||
<return type="float" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
Returns the amount of mass of the Jiggle joint at [code]joint_idx[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_jiggle_joint_override" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
Returns a boolean that indicates whether the joint at [code]joint_idx[/code] is overriding the default jiggle joint data defined in the modification.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_jiggle_joint_roll" qualifiers="const">
|
||||
<return type="float" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
Returns the amount of roll/twist applied to the bone that the Jiggle joint is applied to.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_jiggle_joint_stiffness" qualifiers="const">
|
||||
<return type="float" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
Returns the stiffness of the Jiggle joint at [code]joint_idx[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_jiggle_joint_use_gravity" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<description>
|
||||
Returns a boolean that indicates whether the joint at [code]joint_idx[/code] is using gravity or not.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_use_colliders" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<description>
|
||||
Returns whether the Jiggle modifier is taking physics colliders into account when solving.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_collision_mask">
|
||||
<return type="void" />
|
||||
<argument index="0" name="mask" type="int" />
|
||||
<description>
|
||||
Sets the collision mask that the Jiggle modifier takes into account when performing physics calculations.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_jiggle_joint_bone_index">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="bone_idx" type="int" />
|
||||
<description>
|
||||
Sets the bone index, [code]bone_index[/code], of the Jiggle joint at [code]joint_idx[/code]. When possible, this will also update the [code]bone_name[/code] of the Jiggle joint based on data provided by the [Skeleton3D].
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_jiggle_joint_bone_name">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="name" type="String" />
|
||||
<description>
|
||||
Sets the bone name, [code]bone_name[/code], of the Jiggle joint at [code]joint_idx[/code]. When possible, this will also update the [code]bone_index[/code] of the Jiggle joint based on data provided by the [Skeleton3D].
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_jiggle_joint_damping">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="damping" type="float" />
|
||||
<description>
|
||||
Sets the amount of dampening of the Jiggle joint at [code]joint_idx[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_jiggle_joint_gravity">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="gravity" type="Vector3" />
|
||||
<description>
|
||||
Sets the gravity vector of the Jiggle joint at [code]joint_idx[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_jiggle_joint_mass">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="mass" type="float" />
|
||||
<description>
|
||||
Sets the of mass of the Jiggle joint at [code]joint_idx[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_jiggle_joint_override">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="override" type="bool" />
|
||||
<description>
|
||||
Sets whether the Jiggle joint at [code]joint_idx[/code] 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.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_jiggle_joint_roll">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="roll" type="float" />
|
||||
<description>
|
||||
Sets the amount of roll/twist on the bone the Jiggle joint is attached to.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_jiggle_joint_stiffness">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="stiffness" type="float" />
|
||||
<description>
|
||||
Sets the of stiffness of the Jiggle joint at [code]joint_idx[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_jiggle_joint_use_gravity">
|
||||
<return type="void" />
|
||||
<argument index="0" name="joint_idx" type="int" />
|
||||
<argument index="1" name="use_gravity" type="bool" />
|
||||
<description>
|
||||
Sets whether the Jiggle joint at [code]joint_idx[/code] should use gravity.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_use_colliders">
|
||||
<return type="void" />
|
||||
<argument index="0" name="use_colliders" type="bool" />
|
||||
<description>
|
||||
When [code]true[/code], the Jiggle modifier will use raycasting to prevent the Jiggle joints from rotating themselves into collision objects when solving.
|
||||
</description>
|
||||
</method>
|
||||
</methods>
|
||||
<members>
|
||||
<member name="damping" type="float" setter="set_damping" getter="get_damping" default="0.75">
|
||||
The default amount of dampening applied to the Jiggle joints, if they are not overridden. Higher values lead to more of the calculated velocity being applied.
|
||||
</member>
|
||||
<member name="gravity" type="Vector3" setter="set_gravity" getter="get_gravity" default="Vector3( 0, -6, 0 )">
|
||||
The default amount of gravity applied to the Jiggle joints, if they are not overridden.
|
||||
</member>
|
||||
<member name="jiggle_data_chain_length" type="int" setter="set_jiggle_data_chain_length" getter="get_jiggle_data_chain_length" default="0">
|
||||
The amount of Jiggle joints in the Jiggle modification.
|
||||
</member>
|
||||
<member name="mass" type="float" setter="set_mass" getter="get_mass" default="0.75">
|
||||
The default amount of mass assigned to the Jiggle joints, if they are not overridden. Higher values lead to faster movements and more overshooting.
|
||||
</member>
|
||||
<member name="stiffness" type="float" setter="set_stiffness" getter="get_stiffness" default="3.0">
|
||||
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.
|
||||
</member>
|
||||
<member name="target_nodepath" type="NodePath" setter="set_target_node" getter="get_target_node" default="NodePath("")">
|
||||
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.
|
||||
</member>
|
||||
<member name="use_gravity" type="bool" setter="set_use_gravity" getter="get_use_gravity" default="false">
|
||||
Whether the gravity vector, [member gravity], should be applied to the Jiggle joints, assuming they are not overriding the default settings.
|
||||
</member>
|
||||
</members>
|
||||
<constants>
|
||||
</constants>
|
||||
</class>
|
@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="SkeletonModification3DLookAt" inherits="SkeletonModification3D" version="4.2">
|
||||
<brief_description>
|
||||
A modification that rotates a bone to look at a target.
|
||||
</brief_description>
|
||||
<description>
|
||||
This [SkeletonModification3D] rotates a bone to look a target. This is extremely helpful for moving character's heads to look at the player, rotating a turret to look at a target, or any other case where you want to make a bone rotate towards something quickly and easily.
|
||||
</description>
|
||||
<tutorials>
|
||||
</tutorials>
|
||||
<methods>
|
||||
<method name="get_additional_rotation" qualifiers="const">
|
||||
<return type="Vector3" />
|
||||
<description>
|
||||
Returns the amount of extra rotation that is applied to the bone after the LookAt modification executes.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_lock_rotation_plane" qualifiers="const">
|
||||
<return type="int" />
|
||||
<description>
|
||||
Returns the plane that the LookAt modification is limiting rotation to.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_lock_rotation_to_plane" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<description>
|
||||
Returns whether the LookAt modification is limiting rotation to a single plane in 3D space.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_additional_rotation">
|
||||
<return type="void" />
|
||||
<argument index="0" name="additional_rotation" type="Vector3" />
|
||||
<description>
|
||||
Sets the amount of extra rotation to be applied after the LookAt modification executes. This allows you to adjust the finished result.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_lock_rotation_plane">
|
||||
<return type="void" />
|
||||
<argument index="0" name="plane" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_lock_rotation_to_plane">
|
||||
<return type="void" />
|
||||
<argument index="0" name="lock_to_plane" type="bool" />
|
||||
<description>
|
||||
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.
|
||||
</description>
|
||||
</method>
|
||||
</methods>
|
||||
<members>
|
||||
<member name="bone_index" type="int" setter="set_bone_index" getter="get_bone_index" default="-1">
|
||||
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].
|
||||
</member>
|
||||
<member name="bone_name" type="String" setter="set_bone_name" getter="get_bone_name" default="""">
|
||||
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].
|
||||
</member>
|
||||
<member name="target_nodepath" type="NodePath" setter="set_target_node" getter="get_target_node" default="NodePath("")">
|
||||
The NodePath to the node that is the target for the modification.
|
||||
</member>
|
||||
</members>
|
||||
<constants>
|
||||
</constants>
|
||||
</class>
|
@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="SkeletonModification3DStackHolder" inherits="SkeletonModification3D" version="4.2">
|
||||
<brief_description>
|
||||
A modification that holds and executes a [SkeletonModificationStack3D].
|
||||
</brief_description>
|
||||
<description>
|
||||
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.
|
||||
</description>
|
||||
<tutorials>
|
||||
</tutorials>
|
||||
<methods>
|
||||
<method name="get_held_modification_stack" qualifiers="const">
|
||||
<return type="SkeletonModificationStack3D" />
|
||||
<description>
|
||||
Returns the [SkeletonModificationStack3D] that this modification is holding.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_held_modification_stack">
|
||||
<return type="void" />
|
||||
<argument index="0" name="held_modification_stack" type="SkeletonModificationStack3D" />
|
||||
<description>
|
||||
Sets the [SkeletonModificationStack3D] that this modification is holding. This modification stack will then be executed when this modification is executed.
|
||||
</description>
|
||||
</method>
|
||||
</methods>
|
||||
<constants>
|
||||
</constants>
|
||||
</class>
|
@ -0,0 +1,193 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="SkeletonModification3DTwoBoneIK" inherits="SkeletonModification3D" version="4.2">
|
||||
<brief_description>
|
||||
A modification that moves two bones to reach the target.
|
||||
</brief_description>
|
||||
<description>
|
||||
This [SkeletonModification3D] uses an algorithm typically called TwoBoneIK. This algorithm works by leveraging the law of cosigns and the lengths of the bones to figure out what rotation the bones currently have, and what rotation they need to make a complete triangle, where the first bone, the second bone, and the target form the three vertices of the triangle. Because the algorithm works by making a triangle, it can only operate on two bones.
|
||||
TwoBoneIK is great for arms, legs, and really any joints that can be represented by just two bones that bend to reach a target. This solver is more lightweight than [SkeletonModification3DFABRIK], but gives similar, natural looking results.
|
||||
A [Spatial]-based node can be used to define the pole, or bend direction, allowing control over which direction the joint takes when bending to reach the target when the target is within reach.
|
||||
</description>
|
||||
<tutorials>
|
||||
</tutorials>
|
||||
<methods>
|
||||
<method name="get_auto_calculate_joint_length" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<description>
|
||||
Returns whether the TwoBoneIK modification will attempt to autocalculate the lengths of the two bones.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_joint_one_bone_idx" qualifiers="const">
|
||||
<return type="int" />
|
||||
<description>
|
||||
Returns the bone index of the first bone in the TwoBoneIK modification.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_joint_one_bone_name" qualifiers="const">
|
||||
<return type="String" />
|
||||
<description>
|
||||
Returns the name of the first bone in the TwoBoneIK modification.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_joint_one_length" qualifiers="const">
|
||||
<return type="float" />
|
||||
<description>
|
||||
Returns the length of the first bone in the TwoBoneIK modification.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_joint_one_roll" qualifiers="const">
|
||||
<return type="float" />
|
||||
<description>
|
||||
Returns the amount of roll/twist applied to the first bone in the TwoBoneIK modification.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_joint_two_bone_idx" qualifiers="const">
|
||||
<return type="int" />
|
||||
<description>
|
||||
Returns the bone index of the second bone in the TwoBoneIK modification.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_joint_two_bone_name" qualifiers="const">
|
||||
<return type="String" />
|
||||
<description>
|
||||
Returns the name of the second bone in the TwoBoneIK modification.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_joint_two_length" qualifiers="const">
|
||||
<return type="float" />
|
||||
<description>
|
||||
Returns the length of the second bone in the TwoBoneIK modification.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_joint_two_roll" qualifiers="const">
|
||||
<return type="float" />
|
||||
<description>
|
||||
Returns the amount of roll/twist applied to the second bone in the TwoBoneIK modification.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_pole_node" qualifiers="const">
|
||||
<return type="NodePath" />
|
||||
<description>
|
||||
Returns the node that is being used as the pole node for the TwoBoneIK modification, if a pole node has been set.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_tip_node" qualifiers="const">
|
||||
<return type="NodePath" />
|
||||
<description>
|
||||
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.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_use_pole_node" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<description>
|
||||
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.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_use_tip_node" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<description>
|
||||
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.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_auto_calculate_joint_length">
|
||||
<return type="void" />
|
||||
<argument index="0" name="auto_calculate_joint_length" type="bool" />
|
||||
<description>
|
||||
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.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_joint_one_bone_idx">
|
||||
<return type="void" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<description>
|
||||
Sets the bone index, [code]bone_index[/code], 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].
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_joint_one_bone_name">
|
||||
<return type="void" />
|
||||
<argument index="0" name="bone_name" type="String" />
|
||||
<description>
|
||||
Sets the bone name, [code]bone_name[/code], 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].
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_joint_one_length">
|
||||
<return type="void" />
|
||||
<argument index="0" name="bone_length" type="float" />
|
||||
<description>
|
||||
Sets the length of the first bone in the TwoBoneIK modification.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_joint_one_roll">
|
||||
<return type="void" />
|
||||
<argument index="0" name="roll" type="float" />
|
||||
<description>
|
||||
Sets the amount of roll/twist applied to the first bone in the TwoBoneIK modification.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_joint_two_bone_idx">
|
||||
<return type="void" />
|
||||
<argument index="0" name="bone_idx" type="int" />
|
||||
<description>
|
||||
Sets the bone index, [code]bone_index[/code], 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].
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_joint_two_bone_name">
|
||||
<return type="void" />
|
||||
<argument index="0" name="bone_name" type="String" />
|
||||
<description>
|
||||
Sets the bone name, [code]bone_name[/code], 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].
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_joint_two_length">
|
||||
<return type="void" />
|
||||
<argument index="0" name="bone_length" type="float" />
|
||||
<description>
|
||||
Sets the length of the second bone in the TwoBoneIK modification.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_joint_two_roll">
|
||||
<return type="void" />
|
||||
<argument index="0" name="roll" type="float" />
|
||||
<description>
|
||||
Sets the amount of roll/twist applied to the second bone in the TwoBoneIK modification.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_pole_node">
|
||||
<return type="void" />
|
||||
<argument index="0" name="pole_nodepath" type="NodePath" />
|
||||
<description>
|
||||
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.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_tip_node">
|
||||
<return type="void" />
|
||||
<argument index="0" name="tip_nodepath" type="NodePath" />
|
||||
<description>
|
||||
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.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_use_pole_node">
|
||||
<return type="void" />
|
||||
<argument index="0" name="use_pole_node" type="bool" />
|
||||
<description>
|
||||
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.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_use_tip_node">
|
||||
<return type="void" />
|
||||
<argument index="0" name="use_tip_node" type="bool" />
|
||||
<description>
|
||||
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.
|
||||
</description>
|
||||
</method>
|
||||
</methods>
|
||||
<members>
|
||||
<member name="target_nodepath" type="NodePath" setter="set_target_node" getter="get_target_node" default="NodePath("")">
|
||||
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.
|
||||
</member>
|
||||
</members>
|
||||
<constants>
|
||||
</constants>
|
||||
</class>
|
@ -0,0 +1,90 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="SkeletonModificationStack3D" inherits="Resource" version="4.2">
|
||||
<brief_description>
|
||||
A resource that holds a stack of [SkeletonModification3D]s.
|
||||
</brief_description>
|
||||
<description>
|
||||
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.
|
||||
</description>
|
||||
<tutorials>
|
||||
</tutorials>
|
||||
<methods>
|
||||
<method name="add_modification">
|
||||
<return type="void" />
|
||||
<argument index="0" name="modification" type="SkeletonModification3D" />
|
||||
<description>
|
||||
Adds the passed-in [SkeletonModification3D] to the stack.
|
||||
</description>
|
||||
</method>
|
||||
<method name="delete_modification">
|
||||
<return type="void" />
|
||||
<argument index="0" name="mod_idx" type="int" />
|
||||
<description>
|
||||
Deletes the [SkeletonModification3D] at the index position [code]mod_idx[/code], if it exists.
|
||||
</description>
|
||||
</method>
|
||||
<method name="enable_all_modifications">
|
||||
<return type="void" />
|
||||
<argument index="0" name="enabled" type="bool" />
|
||||
<description>
|
||||
Enables all [SkeletonModification3D]s in the stack.
|
||||
</description>
|
||||
</method>
|
||||
<method name="execute">
|
||||
<return type="void" />
|
||||
<argument index="0" name="delta" type="float" />
|
||||
<argument index="1" name="execution_mode" type="int" />
|
||||
<description>
|
||||
Executes all of the [SkeletonModification3D]s in the stack that use the same execution mode as the passed-in [code]execution_mode[/code], 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.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_is_setup" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<description>
|
||||
Returns a boolean that indicates whether the modification stack is setup and can execute.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_modification" qualifiers="const">
|
||||
<return type="SkeletonModification3D" />
|
||||
<argument index="0" name="mod_idx" type="int" />
|
||||
<description>
|
||||
Returns the [SkeletonModification3D] at the passed-in index, [code]mod_idx[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_skeleton" qualifiers="const">
|
||||
<return type="Skeleton" />
|
||||
<description>
|
||||
Returns the [Skeleton3D] node that the SkeletonModificationStack3D is bound to.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_modification">
|
||||
<return type="void" />
|
||||
<argument index="0" name="mod_idx" type="int" />
|
||||
<argument index="1" name="modification" type="SkeletonModification3D" />
|
||||
<description>
|
||||
Sets the modification at [code]mod_idx[/code] to the passed-in modification, [code]modification[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="setup">
|
||||
<return type="void" />
|
||||
<description>
|
||||
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.
|
||||
</description>
|
||||
</method>
|
||||
</methods>
|
||||
<members>
|
||||
<member name="enabled" type="bool" setter="set_enabled" getter="get_enabled" default="false">
|
||||
When true, the modification's in the stack will be called. This is handled automatically through the [Skeleton3D] node.
|
||||
</member>
|
||||
<member name="modification_count" type="int" setter="set_modification_count" getter="get_modification_count" default="0">
|
||||
The amount of modifications in the stack.
|
||||
</member>
|
||||
<member name="strength" type="float" setter="set_strength" getter="get_strength" default="1.0">
|
||||
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.
|
||||
</member>
|
||||
</members>
|
||||
<constants>
|
||||
</constants>
|
||||
</class>
|
75
modules/skeleton_3d/doc_classes/Skin.xml
Normal file
@ -0,0 +1,75 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="Skin" inherits="Resource" version="4.2">
|
||||
<brief_description>
|
||||
</brief_description>
|
||||
<description>
|
||||
</description>
|
||||
<tutorials>
|
||||
</tutorials>
|
||||
<methods>
|
||||
<method name="add_bind">
|
||||
<return type="void" />
|
||||
<argument index="0" name="bone" type="int" />
|
||||
<argument index="1" name="pose" type="Transform" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="clear_binds">
|
||||
<return type="void" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_bind_bone" qualifiers="const">
|
||||
<return type="int" />
|
||||
<argument index="0" name="bind_index" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_bind_count" qualifiers="const">
|
||||
<return type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_bind_name" qualifiers="const">
|
||||
<return type="StringName" />
|
||||
<argument index="0" name="bind_index" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_bind_pose" qualifiers="const">
|
||||
<return type="Transform" />
|
||||
<argument index="0" name="bind_index" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_bind_bone">
|
||||
<return type="void" />
|
||||
<argument index="0" name="bind_index" type="int" />
|
||||
<argument index="1" name="bone" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_bind_count">
|
||||
<return type="void" />
|
||||
<argument index="0" name="bind_count" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_bind_name">
|
||||
<return type="void" />
|
||||
<argument index="0" name="bind_index" type="int" />
|
||||
<argument index="1" name="name" type="StringName" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_bind_pose">
|
||||
<return type="void" />
|
||||
<argument index="0" name="bind_index" type="int" />
|
||||
<argument index="1" name="pose" type="Transform" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
</methods>
|
||||
<constants>
|
||||
</constants>
|
||||
</class>
|
23
modules/skeleton_3d/doc_classes/SkinReference.xml
Normal file
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="SkinReference" inherits="Reference" version="4.2">
|
||||
<brief_description>
|
||||
</brief_description>
|
||||
<description>
|
||||
</description>
|
||||
<tutorials>
|
||||
</tutorials>
|
||||
<methods>
|
||||
<method name="get_skeleton" qualifiers="const">
|
||||
<return type="RID" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_skin" qualifiers="const">
|
||||
<return type="Skin" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
</methods>
|
||||
<constants>
|
||||
</constants>
|
||||
</class>
|
139
modules/skeleton_3d/editor/physical_bone_editor_plugin.cpp
Normal file
@ -0,0 +1,139 @@
|
||||
|
||||
#include "physical_bone_editor_plugin.h"
|
||||
|
||||
#include "editor/editor_settings.h"
|
||||
|
||||
#include "../nodes/skeleton.h"
|
||||
#include "../resources/skin.h"
|
||||
#include "../nodes/physical_bone.h"
|
||||
|
||||
PhysicalBoneSpatialGizmoPlugin::PhysicalBoneSpatialGizmoPlugin() {
|
||||
create_material("joint_material", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/joint", Color(0.5, 0.8, 1)));
|
||||
}
|
||||
|
||||
bool PhysicalBoneSpatialGizmoPlugin::has_gizmo(Spatial *p_spatial) {
|
||||
return Object::cast_to<PhysicalBone>(p_spatial) != nullptr;
|
||||
}
|
||||
|
||||
String PhysicalBoneSpatialGizmoPlugin::get_gizmo_name() const {
|
||||
return "PhysicalBones";
|
||||
}
|
||||
|
||||
int PhysicalBoneSpatialGizmoPlugin::get_priority() const {
|
||||
return -1;
|
||||
}
|
||||
|
||||
void PhysicalBoneSpatialGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) {
|
||||
p_gizmo->clear();
|
||||
|
||||
PhysicalBone *physical_bone = Object::cast_to<PhysicalBone>(p_gizmo->get_spatial_node());
|
||||
|
||||
if (!physical_bone) {
|
||||
return;
|
||||
}
|
||||
|
||||
Skeleton *sk(physical_bone->find_skeleton_parent());
|
||||
if (!sk) {
|
||||
return;
|
||||
}
|
||||
|
||||
PhysicalBone *pb(sk->get_physical_bone(physical_bone->get_bone_id()));
|
||||
if (!pb) {
|
||||
return;
|
||||
}
|
||||
|
||||
PhysicalBone *pbp(sk->get_physical_bone_parent(physical_bone->get_bone_id()));
|
||||
if (!pbp) {
|
||||
return;
|
||||
}
|
||||
|
||||
Vector<Vector3> points;
|
||||
|
||||
switch (physical_bone->get_joint_type()) {
|
||||
case PhysicalBone::JOINT_TYPE_PIN: {
|
||||
JointSpatialGizmoPlugin::CreatePinJointGizmo(physical_bone->get_joint_offset(), points);
|
||||
} break;
|
||||
case PhysicalBone::JOINT_TYPE_CONE: {
|
||||
const PhysicalBone::ConeJointData *cjd(static_cast<const PhysicalBone::ConeJointData *>(physical_bone->get_joint_data()));
|
||||
JointSpatialGizmoPlugin::CreateConeTwistJointGizmo(
|
||||
physical_bone->get_joint_offset(),
|
||||
physical_bone->get_global_transform() * physical_bone->get_joint_offset(),
|
||||
pb->get_global_transform(),
|
||||
pbp->get_global_transform(),
|
||||
cjd->swing_span,
|
||||
cjd->twist_span,
|
||||
&points,
|
||||
&points);
|
||||
} break;
|
||||
case PhysicalBone::JOINT_TYPE_HINGE: {
|
||||
const PhysicalBone::HingeJointData *hjd(static_cast<const PhysicalBone::HingeJointData *>(physical_bone->get_joint_data()));
|
||||
JointSpatialGizmoPlugin::CreateHingeJointGizmo(
|
||||
physical_bone->get_joint_offset(),
|
||||
physical_bone->get_global_transform() * physical_bone->get_joint_offset(),
|
||||
pb->get_global_transform(),
|
||||
pbp->get_global_transform(),
|
||||
hjd->angular_limit_lower,
|
||||
hjd->angular_limit_upper,
|
||||
hjd->angular_limit_enabled,
|
||||
points,
|
||||
&points,
|
||||
&points);
|
||||
} break;
|
||||
case PhysicalBone::JOINT_TYPE_SLIDER: {
|
||||
const PhysicalBone::SliderJointData *sjd(static_cast<const PhysicalBone::SliderJointData *>(physical_bone->get_joint_data()));
|
||||
JointSpatialGizmoPlugin::CreateSliderJointGizmo(
|
||||
physical_bone->get_joint_offset(),
|
||||
physical_bone->get_global_transform() * physical_bone->get_joint_offset(),
|
||||
pb->get_global_transform(),
|
||||
pbp->get_global_transform(),
|
||||
sjd->angular_limit_lower,
|
||||
sjd->angular_limit_upper,
|
||||
sjd->linear_limit_lower,
|
||||
sjd->linear_limit_upper,
|
||||
points,
|
||||
&points,
|
||||
&points);
|
||||
} break;
|
||||
case PhysicalBone::JOINT_TYPE_6DOF: {
|
||||
const PhysicalBone::SixDOFJointData *sdofjd(static_cast<const PhysicalBone::SixDOFJointData *>(physical_bone->get_joint_data()));
|
||||
JointSpatialGizmoPlugin::CreateGeneric6DOFJointGizmo(
|
||||
physical_bone->get_joint_offset(),
|
||||
|
||||
physical_bone->get_global_transform() * physical_bone->get_joint_offset(),
|
||||
pb->get_global_transform(),
|
||||
pbp->get_global_transform(),
|
||||
|
||||
sdofjd->axis_data[0].angular_limit_lower,
|
||||
sdofjd->axis_data[0].angular_limit_upper,
|
||||
sdofjd->axis_data[0].linear_limit_lower,
|
||||
sdofjd->axis_data[0].linear_limit_upper,
|
||||
sdofjd->axis_data[0].angular_limit_enabled,
|
||||
sdofjd->axis_data[0].linear_limit_enabled,
|
||||
|
||||
sdofjd->axis_data[1].angular_limit_lower,
|
||||
sdofjd->axis_data[1].angular_limit_upper,
|
||||
sdofjd->axis_data[1].linear_limit_lower,
|
||||
sdofjd->axis_data[1].linear_limit_upper,
|
||||
sdofjd->axis_data[1].angular_limit_enabled,
|
||||
sdofjd->axis_data[1].linear_limit_enabled,
|
||||
|
||||
sdofjd->axis_data[2].angular_limit_lower,
|
||||
sdofjd->axis_data[2].angular_limit_upper,
|
||||
sdofjd->axis_data[2].linear_limit_lower,
|
||||
sdofjd->axis_data[2].linear_limit_upper,
|
||||
sdofjd->axis_data[2].angular_limit_enabled,
|
||||
sdofjd->axis_data[2].linear_limit_enabled,
|
||||
|
||||
points,
|
||||
&points,
|
||||
&points);
|
||||
} break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
Ref<Material> material = get_material("joint_material", p_gizmo);
|
||||
|
||||
p_gizmo->add_collision_segments(points);
|
||||
p_gizmo->add_lines(points, material);
|
||||
}
|
21
modules/skeleton_3d/editor/physical_bone_editor_plugin.h
Normal file
@ -0,0 +1,21 @@
|
||||
#ifndef PHYSICAL_BONE_EDITOR_PLUGIN_H
|
||||
#define PHYSICAL_BONE_EDITOR_PLUGIN_H
|
||||
|
||||
#include "core/string/ustring.h"
|
||||
|
||||
#include "editor/spatial_editor_gizmos.h"
|
||||
|
||||
class PhysicalBoneSpatialGizmoPlugin : public EditorSpatialGizmoPlugin {
|
||||
GDCLASS(PhysicalBoneSpatialGizmoPlugin, EditorSpatialGizmoPlugin);
|
||||
|
||||
public:
|
||||
bool has_gizmo(Spatial *p_spatial);
|
||||
String get_gizmo_name() const;
|
||||
int get_priority() const;
|
||||
void redraw(EditorSpatialGizmo *p_gizmo);
|
||||
|
||||
PhysicalBoneSpatialGizmoPlugin();
|
||||
};
|
||||
|
||||
|
||||
#endif
|
119
modules/skeleton_3d/editor/physical_bone_plugin.cpp
Normal file
@ -0,0 +1,119 @@
|
||||
/*************************************************************************/
|
||||
/* physical_bone_plugin.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "physical_bone_plugin.h"
|
||||
|
||||
#include "editor/plugins/spatial_editor_plugin.h"
|
||||
#include "scene/3d/physics_body.h"
|
||||
#include "../nodes/physical_bone.h"
|
||||
#include "core/object/class_db.h"
|
||||
#include "core/error/error_macros.h"
|
||||
#include "core/os/memory.h"
|
||||
#include "scene/gui/box_container.h"
|
||||
#include "scene/main/control.h"
|
||||
#include "scene/gui/separator.h"
|
||||
#include "scene/gui/tool_button.h"
|
||||
|
||||
class EditorNode;
|
||||
|
||||
void PhysicalBoneEditor::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("_on_toggle_button_transform_joint", "is_pressed"), &PhysicalBoneEditor::_on_toggle_button_transform_joint);
|
||||
}
|
||||
|
||||
void PhysicalBoneEditor::_on_toggle_button_transform_joint(bool p_is_pressed) {
|
||||
_set_move_joint();
|
||||
}
|
||||
|
||||
void PhysicalBoneEditor::_set_move_joint() {
|
||||
if (selected) {
|
||||
selected->_set_gizmo_move_joint(button_transform_joint->is_pressed());
|
||||
}
|
||||
}
|
||||
|
||||
PhysicalBoneEditor::PhysicalBoneEditor(EditorNode *p_editor) :
|
||||
editor(p_editor),
|
||||
selected(nullptr) {
|
||||
spatial_editor_hb = memnew(HBoxContainer);
|
||||
spatial_editor_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
spatial_editor_hb->set_alignment(BoxContainer::ALIGN_BEGIN);
|
||||
SpatialEditor::get_singleton()->add_control_to_menu_panel(spatial_editor_hb);
|
||||
|
||||
spatial_editor_hb->add_child(memnew(VSeparator));
|
||||
|
||||
button_transform_joint = memnew(ToolButton);
|
||||
spatial_editor_hb->add_child(button_transform_joint);
|
||||
|
||||
button_transform_joint->set_text(TTR("Move Joint"));
|
||||
button_transform_joint->set_icon(SpatialEditor::get_singleton()->get_theme_icon("PhysicalBone", "EditorIcons"));
|
||||
button_transform_joint->set_toggle_mode(true);
|
||||
button_transform_joint->connect("toggled", this, "_on_toggle_button_transform_joint");
|
||||
|
||||
hide();
|
||||
}
|
||||
|
||||
PhysicalBoneEditor::~PhysicalBoneEditor() {}
|
||||
|
||||
void PhysicalBoneEditor::set_selected(PhysicalBone *p_pb) {
|
||||
button_transform_joint->set_pressed(false);
|
||||
|
||||
_set_move_joint();
|
||||
selected = p_pb;
|
||||
_set_move_joint();
|
||||
}
|
||||
|
||||
void PhysicalBoneEditor::hide() {
|
||||
spatial_editor_hb->hide();
|
||||
}
|
||||
|
||||
void PhysicalBoneEditor::show() {
|
||||
spatial_editor_hb->show();
|
||||
}
|
||||
|
||||
PhysicalBonePlugin::PhysicalBonePlugin(EditorNode *p_editor) :
|
||||
editor(p_editor),
|
||||
selected(nullptr),
|
||||
physical_bone_editor(editor) {}
|
||||
|
||||
void PhysicalBonePlugin::make_visible(bool p_visible) {
|
||||
if (p_visible) {
|
||||
physical_bone_editor.show();
|
||||
} else {
|
||||
physical_bone_editor.hide();
|
||||
physical_bone_editor.set_selected(nullptr);
|
||||
selected = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicalBonePlugin::edit(Object *p_node) {
|
||||
selected = static_cast<PhysicalBone *>(p_node); // Trust it
|
||||
ERR_FAIL_COND(!selected);
|
||||
|
||||
physical_bone_editor.set_selected(selected);
|
||||
}
|
84
modules/skeleton_3d/editor/physical_bone_plugin.h
Normal file
@ -0,0 +1,84 @@
|
||||
#ifndef PHYSICAL_BONE_PLUGIN_H
|
||||
#define PHYSICAL_BONE_PLUGIN_H
|
||||
/*************************************************************************/
|
||||
/* physical_bone_plugin.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "core/object/object.h"
|
||||
#include "core/string/ustring.h"
|
||||
#include "editor/editor_plugin.h"
|
||||
|
||||
class PhysicalBone;
|
||||
class EditorNode;
|
||||
class HBoxContainer;
|
||||
class ToolButton;
|
||||
|
||||
class PhysicalBoneEditor : public Object {
|
||||
GDCLASS(PhysicalBoneEditor, Object);
|
||||
|
||||
EditorNode *editor;
|
||||
HBoxContainer *spatial_editor_hb;
|
||||
ToolButton *button_transform_joint;
|
||||
|
||||
PhysicalBone *selected;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
private:
|
||||
void _on_toggle_button_transform_joint(bool p_is_pressed);
|
||||
void _set_move_joint();
|
||||
|
||||
public:
|
||||
PhysicalBoneEditor(EditorNode *p_editor);
|
||||
~PhysicalBoneEditor();
|
||||
|
||||
void set_selected(PhysicalBone *p_pb);
|
||||
|
||||
void hide();
|
||||
void show();
|
||||
};
|
||||
|
||||
class PhysicalBonePlugin : public EditorPlugin {
|
||||
GDCLASS(PhysicalBonePlugin, EditorPlugin);
|
||||
|
||||
EditorNode *editor;
|
||||
PhysicalBone *selected;
|
||||
PhysicalBoneEditor physical_bone_editor;
|
||||
|
||||
public:
|
||||
virtual String get_name() const { return "PhysicalBone"; }
|
||||
virtual bool handles(Object *p_object) const { return p_object->is_class("PhysicalBone"); }
|
||||
virtual void make_visible(bool p_visible);
|
||||
virtual void edit(Object *p_node);
|
||||
|
||||
PhysicalBonePlugin(EditorNode *p_editor);
|
||||
};
|
||||
|
||||
#endif
|
1677
modules/skeleton_3d/editor/skeleton_editor_plugin.cpp
Normal file
315
modules/skeleton_3d/editor/skeleton_editor_plugin.h
Normal file
@ -0,0 +1,315 @@
|
||||
#ifndef MODULE_SKELETON_EDITOR_PLUGIN_H
|
||||
#define MODULE_SKELETON_EDITOR_PLUGIN_H
|
||||
/*************************************************************************/
|
||||
/* skeleton_editor_plugin.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "core/input/input_event.h"
|
||||
#include "editor/editor_inspector.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/editor_plugin.h"
|
||||
#include "editor/spatial_editor_gizmos.h"
|
||||
#include "scene/3d/camera.h"
|
||||
#include "scene/3d/mesh_instance.h"
|
||||
#include "../nodes/skeleton.h"
|
||||
#include "scene/resources/mesh/immediate_mesh.h"
|
||||
|
||||
class EditorInspectorPluginSkeleton;
|
||||
class Joint;
|
||||
class PhysicalBone;
|
||||
class SkeletonEditorPlugin;
|
||||
class Button;
|
||||
class CheckBox;
|
||||
class EditorSpinSlider;
|
||||
class EditorInspectorSection;
|
||||
class GridContainer;
|
||||
class Tree;
|
||||
class Label;
|
||||
class PopupMenu;
|
||||
class CheckBox;
|
||||
class VSeparator;
|
||||
class EditorPropertyTransform;
|
||||
class EditorPropertyVector3;
|
||||
class EditorPropertyCheck;
|
||||
class EditorPropertyQuaternion;
|
||||
|
||||
class BoneTransformEditor : public VBoxContainer {
|
||||
GDCLASS(BoneTransformEditor, VBoxContainer);
|
||||
|
||||
EditorInspectorSection *section;
|
||||
|
||||
EditorPropertyCheck *enabled_checkbox = nullptr;
|
||||
EditorPropertyVector3 *position_property = nullptr;
|
||||
EditorPropertyQuaternion *rotation_property = nullptr;
|
||||
EditorPropertyVector3 *scale_property;
|
||||
|
||||
EditorInspectorSection *rest_section = nullptr;
|
||||
EditorPropertyTransform *rest_matrix = nullptr;
|
||||
|
||||
Rect2 background_rects[5];
|
||||
|
||||
Skeleton *skeleton;
|
||||
//String property;
|
||||
|
||||
UndoRedo *undo_redo;
|
||||
|
||||
bool toggle_enabled;
|
||||
bool updating;
|
||||
|
||||
String label;
|
||||
|
||||
void create_editors();
|
||||
|
||||
void _value_changed(const String &p_path, const Variant &p_value, const String &p_name = "", bool changing = false);
|
||||
|
||||
void _property_keyed(const String &p_path, bool p_advance);
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
BoneTransformEditor(Skeleton *p_skeleton);
|
||||
|
||||
// Which transform target to modify
|
||||
void set_target(const String &p_prop);
|
||||
void set_label(const String &p_label) { label = p_label; }
|
||||
void set_keyable(const bool p_keyable);
|
||||
|
||||
void _update_properties();
|
||||
};
|
||||
|
||||
class SkeletonEditor : public VBoxContainer {
|
||||
GDCLASS(SkeletonEditor, VBoxContainer);
|
||||
|
||||
friend class SkeletonEditorPlugin;
|
||||
|
||||
enum SkeletonOption {
|
||||
SKELETON_OPTION_INIT_ALL_POSES,
|
||||
SKELETON_OPTION_INIT_SELECTED_POSES,
|
||||
SKELETON_OPTION_ALL_POSES_TO_RESTS,
|
||||
SKELETON_OPTION_SELECTED_POSES_TO_RESTS,
|
||||
SKELETON_OPTION_CREATE_PHYSICAL_SKELETON,
|
||||
SKELETON_OPTION_ADD_BONE,
|
||||
SKELETON_OPTION_REMOVE_BONE,
|
||||
SKELETON_OPTION_RENAME_BONE
|
||||
};
|
||||
|
||||
struct BoneInfo {
|
||||
PhysicalBone *physical_bone;
|
||||
Transform relative_rest; // Relative to skeleton node
|
||||
|
||||
BoneInfo() {
|
||||
physical_bone = NULL;
|
||||
}
|
||||
};
|
||||
|
||||
EditorNode *editor;
|
||||
EditorInspectorPluginSkeleton *editor_plugin;
|
||||
|
||||
Skeleton *skeleton;
|
||||
|
||||
Tree *joint_tree;
|
||||
BoneTransformEditor *rest_editor;
|
||||
BoneTransformEditor *pose_editor;
|
||||
|
||||
VSeparator *separator;
|
||||
MenuButton *skeleton_options;
|
||||
Button *edit_mode_button;
|
||||
|
||||
bool edit_mode;
|
||||
|
||||
HBoxContainer *animation_hb;
|
||||
Button *key_loc_button;
|
||||
Button *key_rot_button;
|
||||
Button *key_scale_button;
|
||||
Button *key_insert_button;
|
||||
Button *key_insert_all_button;
|
||||
|
||||
EditorFileDialog *file_dialog;
|
||||
|
||||
bool keyable;
|
||||
|
||||
static SkeletonEditor *singleton;
|
||||
|
||||
void _on_click_skeleton_option(int p_skeleton_option);
|
||||
void _file_selected(const String &p_file);
|
||||
TreeItem *_find(TreeItem *p_node, const NodePath &p_path);
|
||||
void edit_mode_toggled(const bool pressed);
|
||||
|
||||
EditorFileDialog *file_export_lib;
|
||||
|
||||
void update_joint_tree();
|
||||
void update_editors();
|
||||
|
||||
void create_editors();
|
||||
|
||||
void init_pose(const bool p_all_bones);
|
||||
void pose_to_rest(const bool p_all_bones);
|
||||
|
||||
void insert_keys(const bool p_all_bones);
|
||||
|
||||
void create_physical_skeleton();
|
||||
PhysicalBone *create_physical_bone(int bone_id, int bone_child_id, const Vector<BoneInfo> &bones_infos);
|
||||
|
||||
Variant get_drag_data_fw(const Point2 &p_point, Control *p_from);
|
||||
bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const;
|
||||
void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from);
|
||||
|
||||
void set_keyable(const bool p_keyable);
|
||||
void set_bone_options_enabled(const bool p_bone_options_enabled);
|
||||
|
||||
MeshInstance *handles_mesh_instance;
|
||||
Ref<ImmediateMesh> handles_mesh;
|
||||
Ref<ShaderMaterial> handle_material;
|
||||
Ref<Shader> handle_shader;
|
||||
|
||||
Vector3 bone_original_position;
|
||||
Quaternion bone_original_rotation;
|
||||
Vector3 bone_original_scale;
|
||||
|
||||
void _update_gizmo_visible();
|
||||
void _bone_enabled_changed(const int p_bone_id);
|
||||
|
||||
void _hide_handles();
|
||||
|
||||
void _draw_gizmo();
|
||||
void _draw_handles();
|
||||
|
||||
void _joint_tree_selection_changed();
|
||||
void _joint_tree_rmb_select(const Vector2 &p_pos);
|
||||
void _update_properties();
|
||||
|
||||
void _subgizmo_selection_change();
|
||||
|
||||
int selected_bone;
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
void _node_removed(Node *p_node);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
static SkeletonEditor *get_singleton() { return singleton; }
|
||||
|
||||
void select_bone(int p_idx);
|
||||
|
||||
int get_selected_bone() const;
|
||||
|
||||
void move_skeleton_bone(NodePath p_skeleton_path, int32_t p_selected_boneidx, int32_t p_target_boneidx);
|
||||
|
||||
Skeleton *get_skeleton() const { return skeleton; };
|
||||
|
||||
bool is_edit_mode() const { return edit_mode; }
|
||||
|
||||
void update_bone_original();
|
||||
Vector3 get_bone_original_position() const { return bone_original_position; };
|
||||
Quaternion get_bone_original_rotation() const { return bone_original_rotation; };
|
||||
Vector3 get_bone_original_scale() const { return bone_original_scale; };
|
||||
|
||||
SkeletonEditor(EditorInspectorPluginSkeleton *e_plugin, EditorNode *p_editor, Skeleton *skeleton);
|
||||
~SkeletonEditor();
|
||||
|
||||
void add_bone();
|
||||
void remove_bone();
|
||||
void rename_bone();
|
||||
|
||||
void _add_bone_callback();
|
||||
void _remove_bone_callback();
|
||||
void _rename_bone_callback();
|
||||
|
||||
void create_bone_tool_popups();
|
||||
static void _bind_tool_popup_methods();
|
||||
|
||||
ConfirmationDialog *_bone_add_dialog;
|
||||
LineEdit *_bone_add_line_edit;
|
||||
ConfirmationDialog *_bone_rename_dialog;
|
||||
LineEdit *_bone_rename_line_edit;
|
||||
ConfirmationDialog *_bone_remove_dialog;
|
||||
};
|
||||
|
||||
class EditorInspectorPluginSkeleton : public EditorInspectorPlugin {
|
||||
GDCLASS(EditorInspectorPluginSkeleton, EditorInspectorPlugin);
|
||||
|
||||
friend class SkeletonEditorPlugin;
|
||||
|
||||
SkeletonEditor *skel_editor;
|
||||
EditorNode *editor;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
virtual bool can_handle(Object *p_object);
|
||||
virtual void parse_begin(Object *p_object);
|
||||
};
|
||||
|
||||
class SkeletonEditorPlugin : public EditorPlugin {
|
||||
GDCLASS(SkeletonEditorPlugin, EditorPlugin);
|
||||
|
||||
EditorInspectorPluginSkeleton *skeleton_plugin;
|
||||
EditorNode *editor;
|
||||
|
||||
public:
|
||||
virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera *p_camera, const Ref<InputEvent> &p_event);
|
||||
|
||||
bool has_main_screen() const { return false; }
|
||||
virtual bool handles(Object *p_object) const;
|
||||
|
||||
virtual String get_name() const { return "Skeleton"; }
|
||||
|
||||
SkeletonEditorPlugin(EditorNode *p_node);
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
};
|
||||
|
||||
class SkeletonGizmoPlugin : public EditorSpatialGizmoPlugin {
|
||||
GDCLASS(SkeletonGizmoPlugin, EditorSpatialGizmoPlugin);
|
||||
|
||||
Ref<SpatialMaterial> unselected_mat;
|
||||
Ref<ShaderMaterial> selected_mat;
|
||||
Ref<Shader> selected_sh;
|
||||
|
||||
public:
|
||||
bool has_gizmo(Spatial *p_spatial);
|
||||
String get_gizmo_name() const;
|
||||
int get_priority() const;
|
||||
|
||||
int subgizmos_intersect_ray(const EditorSpatialGizmo *p_gizmo, Camera *p_camera, const Vector2 &p_point) const;
|
||||
Transform get_subgizmo_transform(const EditorSpatialGizmo *p_gizmo, int p_id) const;
|
||||
void set_subgizmo_transform(const EditorSpatialGizmo *p_gizmo, int p_id, Transform p_transform);
|
||||
void commit_subgizmos(const EditorSpatialGizmo *p_gizmo, const Vector<int> &p_ids, const Vector<Transform> &p_restore, bool p_cancel);
|
||||
|
||||
void redraw(EditorSpatialGizmo *p_gizmo);
|
||||
|
||||
SkeletonGizmoPlugin();
|
||||
};
|
||||
|
||||
#endif // SKELETON_EDITOR_PLUGIN_H
|
102
modules/skeleton_3d/editor/skeleton_ik_editor_plugin.cpp
Normal file
@ -0,0 +1,102 @@
|
||||
/*************************************************************************/
|
||||
/* skeleton_ik_editor_plugin.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "skeleton_ik_editor_plugin.h"
|
||||
|
||||
#include "../nodes/skeleton.h"
|
||||
#include "../nodes/skeleton_ik.h"
|
||||
#include "core/object/class_db.h"
|
||||
#include "core/os/memory.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "scene/gui/button.h"
|
||||
#include "scene/main/control.h"
|
||||
|
||||
void SkeletonIKEditorPlugin::_play() {
|
||||
if (!skeleton_ik) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!skeleton_ik->get_parent_skeleton()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (play_btn->is_pressed()) {
|
||||
skeleton_ik->start();
|
||||
} else {
|
||||
skeleton_ik->stop();
|
||||
skeleton_ik->get_parent_skeleton()->clear_bones_global_pose_override();
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonIKEditorPlugin::edit(Object *p_object) {
|
||||
if (p_object != skeleton_ik) {
|
||||
if (skeleton_ik) {
|
||||
play_btn->set_pressed(false);
|
||||
_play();
|
||||
}
|
||||
}
|
||||
|
||||
SkeletonIK *s = Object::cast_to<SkeletonIK>(p_object);
|
||||
if (!s) {
|
||||
return;
|
||||
}
|
||||
|
||||
skeleton_ik = s;
|
||||
}
|
||||
|
||||
bool SkeletonIKEditorPlugin::handles(Object *p_object) const {
|
||||
return p_object->is_class("SkeletonIK");
|
||||
}
|
||||
|
||||
void SkeletonIKEditorPlugin::make_visible(bool p_visible) {
|
||||
if (p_visible) {
|
||||
play_btn->show();
|
||||
} else {
|
||||
play_btn->hide();
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonIKEditorPlugin::_bind_methods() {
|
||||
ClassDB::bind_method("_play", &SkeletonIKEditorPlugin::_play);
|
||||
}
|
||||
|
||||
SkeletonIKEditorPlugin::SkeletonIKEditorPlugin(EditorNode *p_node) {
|
||||
editor = p_node;
|
||||
play_btn = memnew(Button);
|
||||
play_btn->set_icon(editor->get_gui_base()->get_theme_icon("Play", "EditorIcons"));
|
||||
play_btn->set_text(TTR("Play IK"));
|
||||
play_btn->set_toggle_mode(true);
|
||||
play_btn->hide();
|
||||
play_btn->connect("pressed", this, "_play");
|
||||
add_control_to_container(CONTAINER_SPATIAL_EDITOR_MENU, play_btn);
|
||||
skeleton_ik = nullptr;
|
||||
}
|
||||
|
||||
SkeletonIKEditorPlugin::~SkeletonIKEditorPlugin() {}
|
66
modules/skeleton_3d/editor/skeleton_ik_editor_plugin.h
Normal file
@ -0,0 +1,66 @@
|
||||
#ifndef SKELETON_IK_EDITOR_PLUGIN_H
|
||||
#define SKELETON_IK_EDITOR_PLUGIN_H
|
||||
/*************************************************************************/
|
||||
/* skeleton_ik_editor_plugin.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "editor/editor_plugin.h"
|
||||
|
||||
#include "core/object/object.h"
|
||||
#include "core/string/ustring.h"
|
||||
|
||||
class SkeletonIK;
|
||||
class Button;
|
||||
class EditorNode;
|
||||
|
||||
class SkeletonIKEditorPlugin : public EditorPlugin {
|
||||
GDCLASS(SkeletonIKEditorPlugin, EditorPlugin);
|
||||
|
||||
SkeletonIK *skeleton_ik;
|
||||
|
||||
Button *play_btn;
|
||||
EditorNode *editor;
|
||||
|
||||
void _play();
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
virtual String get_name() const { return "SkeletonIK"; }
|
||||
bool has_main_screen() const { return false; }
|
||||
virtual void edit(Object *p_object);
|
||||
virtual bool handles(Object *p_object) const;
|
||||
virtual void make_visible(bool p_visible);
|
||||
|
||||
SkeletonIKEditorPlugin(EditorNode *p_node);
|
||||
~SkeletonIKEditorPlugin();
|
||||
};
|
||||
|
||||
#endif // SKELETON_IK_EDITOR_PLUGIN_H
|
1
modules/skeleton_3d/icons/icon_editor_bone_handle.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg height="8" viewBox="0 0 8 8" width="8" xmlns="http://www.w3.org/2000/svg"><circle cx="4" cy="4" fill="#fff" r="4"/><circle cx="4" cy="4" fill="#000" r="2.5"/></svg>
|
After Width: | Height: | Size: 170 B |
1
modules/skeleton_3d/icons/icon_physical_bone.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill="#fc9c9c" transform="translate(-2.5625 -18.4375)"><path d="m13.107422 19.382812a2.4664 2.4663 0 0 0 -1.78125.720704 2.4664 2.4663 0 0 0 -.185547.21289l1.332031 2.433594-1.605469.603516-3.414062 3.414062a2.4664 2.4663 0 0 0 -3.1015625.3125 2.4664 2.4663 0 0 0 0 3.488281 2.4664 2.4663 0 0 0 1.3964844.695313 2.4664 2.4663 0 0 0 .6953125 1.396484 2.4664 2.4663 0 0 0 3.4882812 0 2.4664 2.4663 0 0 0 .3144534-3.103515l3.560547-3.560547a2.4664 2.4663 0 0 0 3.099609-.310547 2.4664 2.4663 0 0 0 0-3.488281 2.4664 2.4663 0 0 0 -1.396484-.697266 2.4664 2.4663 0 0 0 -.695313-1.396484 2.4664 2.4663 0 0 0 -1.707031-.720704z"/><path d="m3.7211033 21.208326.9608286 4.82644 1.3962404-.524494z"/><path d="m6.4843278 19.465234.9608285 4.82644 1.3962404-.524494z"/><path d="m9.6964655 19.33678.7108285 3.51394 1.39624-.524494z"/></g></svg>
|
After Width: | Height: | Size: 918 B |
1
modules/skeleton_3d/icons/icon_skeleton.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m6 2a4 4 0 0 0 -4 4 4 4 0 0 0 2 3.4531v3.5469a2 2 0 0 0 1 1.7324 2 2 0 0 0 1 .26562v.001953h4v-.001953a2 2 0 0 0 1-.26562 2 2 0 0 0 1-1.7324v-3.5469a4 4 0 0 0 2-3.4531 4 4 0 0 0 -4-4zm-1 3a1 1 0 0 1 1 1 1 1 0 0 1 -1 1 1 1 0 0 1 -1-1 1 1 0 0 1 1-1zm6 0a1 1 0 0 1 1 1 1 1 0 0 1 -1 1 1 1 0 0 1 -1-1 1 1 0 0 1 1-1zm-4 2h2v1h-2zm-2 2h1v1h1v-1h1 1v1h1v-1h1v.86719 3.1328h-1v-1h-1v1h-1-1v-1h-1v1h-1v-3.1309-.86914z" fill="#fc9c9c" fill-opacity=".99608"/></svg>
|
After Width: | Height: | Size: 546 B |
1
modules/skeleton_3d/icons/icon_skeleton_i_k.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m6 2a4 4 0 0 0 -4 4 4 4 0 0 0 2 3.453125v3.546875a2 2 0 0 0 1 1.732422 2 2 0 0 0 1 .265625v.001953h2v-2h-1v-1h-1v1h-1v-3.1308594-.8691406h1v1h1v-1h1v-1h-1v-1h1v-5zm-1 3a1 1 0 0 1 1 1 1 1 0 0 1 -1 1 1 1 0 0 1 -1-1 1 1 0 0 1 1-1z" fill="#e0e0e0"/><path d="m8 2v5h1v1h-1v1h1v1h1v-1h1v.8671875 3.1328125h-1v-1h-1v1h-1v2h2v-.001953a2 2 0 0 0 1-.265625 2 2 0 0 0 1-1.732422v-3.546875a4 4 0 0 0 2-3.453125 4 4 0 0 0 -4-4zm3 3a1 1 0 0 1 1 1 1 1 0 0 1 -1 1 1 1 0 0 1 -1-1 1 1 0 0 1 1-1z" fill="#fc9c9c"/></svg>
|
After Width: | Height: | Size: 594 B |
1
modules/skeleton_3d/icons/icon_tool_bone_move.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg enable-background="new 0 0 16 16" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill="#e0e0e0" fill-opacity=".9961"><path d="m11.5 14.448-.831-.831-.909.909 1.286 1.285c.251.251.657.251.908 0l1.286-1.285-.909-.909z"/><circle cx="11.5" cy="11.5" r="1.286"/><path d="m8.753 10.038-.278-.278-1.286 1.286c-.251.251-.251.657 0 .908l.824.824.462.462.909-.909-.832-.831.832-.831z"/><path d="m15.812 11.046-1.286-1.286-.909.909.831.831-.831.831.909.909 1.285-1.286c.252-.251.252-.657.001-.908z"/><path d="m11.954 7.188c-.061-.061-.133-.107-.21-.139-.028-.012-.059-.01-.088-.018-.051-.013-.1-.03-.151-.03-.005 0-.01 0-.015 0-.167.002-.327.069-.444.187l-1.286 1.287.278.278.631.63.831-.831.831.831.909-.908-.465-.465z"/><path d="m6.128 9.985 1.286-1.286c.241-.242.545-.384.859-.426.044-.323.193-.626.426-.859l1.286-1.286c.396-.397.925-.619 1.47-.625h.05c.562 0 1.112.228 1.51.626l1.215 1.215c.016-.015.033-.025.048-.04.964-.963.964-2.524 0-3.488-.378-.378-.868-.623-1.397-.698-.074-.529-.318-1.019-.695-1.397-.455-.453-1.067-.711-1.707-.721-.667-.01-1.309.25-1.782.72-.828.829-.96 2.126-.314 3.105l-3.558 3.561c-.978-.646-2.274-.515-3.103.312-.963.962-.963 2.524 0 3.487.378.377.868.621 1.396.695.075.529.319 1.02.696 1.396.963.964 2.525.964 3.488 0 .015-.015.025-.032.04-.048l-1.215-1.215c-.835-.833-.835-2.193.001-3.028z"/></g></svg>
|
After Width: | Height: | Size: 1.3 KiB |
1
modules/skeleton_3d/icons/icon_tool_bone_rest.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m16 8c0 1.409-.72 2.641-1.824 3.345l1.824 1.823v2.832h-2.832l-4-4h-5.168v4h-4v-16h12c2.208 0 4 1.792 4 4zm-12 0h8v-4h-8z" fill="#e0e0e0"/></svg>
|
After Width: | Height: | Size: 237 B |
1
modules/skeleton_3d/icons/icon_tool_bone_rotate.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg enable-background="new 0 0 16 16" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill="#e0e0e0" fill-opacity=".9961"><circle cx="11.501" cy="11.5" r="1.286"/><path d="m15.536 9.51c-.568-1.152-1.591-1.985-2.79-2.331-.027-.008-.056-.011-.084-.019-.163-.043-.327-.084-.496-.109-.199-.03-.402-.048-.608-.05-.027 0-.054 0-.081 0-.039 0-.077 0-.115.001-.149.004-.299.017-.448.037-1.911.251-3.45 1.693-3.826 3.584-.049.248-.069.497-.076.745-.002.062-.002.123-.001.185.004.496.092.983.256 1.447.019.054.04.106.061.16.091.228.194.45.321.661.001.001.001.003.002.004.194.321.429.62.704.889h-.71v1.286h2.571c.355 0 .643-.287.643-.643 0-.053-.006-.105-.019-.156l-.643-2.571-1.248.313.181.721c-.54-.591-.841-1.362-.843-2.163 0-.442.09-.863.251-1.246.08-.189.178-.369.291-.537.001-.005.003-.009.005-.013.348-.516.841-.924 1.42-1.168.384-.162.805-.251 1.247-.251 1.775 0 3.214 1.439 3.214 3.214-.001.853-.34 1.669-.942 2.271l.91.91c1.363-1.363 1.706-3.442.853-5.171z"/><path d="m5.616 10.33c.502-2.522 2.553-4.443 5.103-4.778.199-.026.399-.043.598-.049l.16-.002h.1c1.182.015 2.294.375 3.236 1 .369-.894.191-1.959-.535-2.686-.378-.377-.868-.622-1.397-.697-.074-.529-.318-1.019-.695-1.397-.455-.453-1.067-.711-1.707-.721-.667-.01-1.309.25-1.782.72-.828.829-.96 2.126-.314 3.105l-3.558 3.561c-.978-.646-2.274-.515-3.103.312-.963.962-.963 2.524 0 3.487.378.377.868.621 1.396.695.075.529.319 1.02.696 1.396.632.633 1.52.842 2.329.645v-.208c0-.14.019-.275.055-.403-.639-1.202-.856-2.601-.582-3.98z"/></g></svg>
|
After Width: | Height: | Size: 1.5 KiB |
1
modules/skeleton_3d/icons/icon_tool_bone_scale.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg enable-background="new 0 0 16 16" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill="#e0e0e0" fill-opacity=".9961"><path d="m10.857 14.714h-1.662l.83-.831-.908-.909-.832.832v-1.663c0-.355-.288-.643-.643-.643s-.642.287-.642.643v2.383.832c0 .355.288.642.643.642h3.214c.355 0 .643-.287.643-.643s-.287-.643-.643-.643z"/><path d="m15.357 7h-.832-2.383c-.355 0-.642.288-.642.643s.287.643.643.643h1.663l-.832.832.909.908.831-.83v1.662c0 .355.288.643.644.643s.642-.288.642-.644v-3.214c0-.355-.287-.643-.643-.643z"/><circle cx="11.5" cy="11.5" r="1.286"/><path d="m8.573 10.218 1.645-1.645c-.137-.282-.218-.596-.218-.93 0-1.182.961-2.143 2.143-2.143h2.852c-.014-.611-.25-1.218-.717-1.685-.378-.377-.868-.622-1.397-.697-.074-.529-.318-1.019-.695-1.397-.455-.453-1.067-.711-1.707-.721-.667-.01-1.309.25-1.782.72-.828.829-.96 2.126-.314 3.105l-3.558 3.561c-.978-.646-2.274-.515-3.103.312-.963.962-.963 2.524 0 3.487.378.377.868.621 1.396.695.075.529.319 1.02.696 1.396.467.467 1.074.703 1.685.717v-2.852c.001-1.18.962-2.141 2.144-2.141.334 0 .648.081.93.218z"/></g></svg>
|
After Width: | Height: | Size: 1.1 KiB |
1
modules/skeleton_3d/icons/icon_tool_bone_select.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg enable-background="new 0 0 16 16" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill="#e0e0e0" fill-opacity=".9961"><path d="m16 11.46-6.142-2.527-1.572-.647.647 1.572 2.527 6.142.913-2.72 1.815 1.817.91-.909-1.818-1.815z"/><path d="m7.784 11.008-.886-2.152c-.23-.56-.102-1.203.327-1.631.287-.287.67-.439 1.061-.439.192 0 .386.037.57.113l2.151.885.17-.17c.977.645 2.271.516 3.1-.311.964-.963.964-2.524 0-3.488-.377-.377-.867-.622-1.396-.697-.074-.529-.318-1.019-.695-1.397-.455-.453-1.067-.711-1.707-.721-.667-.01-1.309.25-1.782.72-.828.829-.96 2.126-.314 3.105l-3.558 3.561c-.978-.646-2.274-.515-3.103.312-.963.962-.963 2.524 0 3.487.378.377.868.621 1.396.695.075.529.319 1.02.696 1.396.963.964 2.525.964 3.488 0 .828-.828.96-2.125.314-3.104z"/></g></svg>
|
After Width: | Height: | Size: 797 B |
416
modules/skeleton_3d/nodes/bone_attachment.cpp
Normal file
@ -0,0 +1,416 @@
|
||||
/*************************************************************************/
|
||||
/* bone_attachment.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "bone_attachment.h"
|
||||
|
||||
void BoneAttachment::set_bone_name(const String &p_name) {
|
||||
bone_name = p_name;
|
||||
Skeleton *sk = _get_skeleton();
|
||||
if (sk) {
|
||||
set_bone_idx(sk->find_bone(bone_name));
|
||||
}
|
||||
}
|
||||
|
||||
String BoneAttachment::get_bone_name() const {
|
||||
return bone_name;
|
||||
}
|
||||
|
||||
void BoneAttachment::set_bone_idx(const int p_idx) {
|
||||
if (is_inside_tree()) {
|
||||
_check_unbind();
|
||||
}
|
||||
|
||||
bone_idx = p_idx;
|
||||
|
||||
Skeleton *sk = _get_skeleton();
|
||||
|
||||
if (sk) {
|
||||
if (bone_idx <= -1 || bone_idx >= sk->get_bone_count()) {
|
||||
WARN_PRINT("Bone index out of range! Cannot connect BoneAttachment to node!");
|
||||
bone_idx = -1;
|
||||
} else {
|
||||
bone_name = sk->get_bone_name(bone_idx);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_inside_tree()) {
|
||||
_check_bind();
|
||||
}
|
||||
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
int BoneAttachment::get_bone_idx() const {
|
||||
return bone_idx;
|
||||
}
|
||||
|
||||
void BoneAttachment::set_override_pose(bool p_override) {
|
||||
override_pose = p_override;
|
||||
set_notify_local_transform(override_pose);
|
||||
set_process_internal(override_pose);
|
||||
|
||||
if (!override_pose) {
|
||||
Skeleton *sk = _get_skeleton();
|
||||
if (sk) {
|
||||
if (override_mode == OVERRIDE_MODES::MODE_GLOBAL_POSE) {
|
||||
sk->set_bone_global_pose_override(bone_idx, Transform(), 0.0, false);
|
||||
} else if (override_mode == OVERRIDE_MODES::MODE_LOCAL_POSE) {
|
||||
sk->set_bone_local_pose_override(bone_idx, Transform(), 0.0, false);
|
||||
}
|
||||
}
|
||||
_transform_changed();
|
||||
}
|
||||
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
bool BoneAttachment::get_override_pose() const {
|
||||
return override_pose;
|
||||
}
|
||||
|
||||
void BoneAttachment::set_override_mode(int p_mode) {
|
||||
if (override_pose) {
|
||||
Skeleton *sk = _get_skeleton();
|
||||
if (sk) {
|
||||
if (override_mode == OVERRIDE_MODES::MODE_GLOBAL_POSE) {
|
||||
sk->set_bone_global_pose_override(bone_idx, Transform(), 0.0, false);
|
||||
} else if (override_mode == OVERRIDE_MODES::MODE_LOCAL_POSE) {
|
||||
sk->set_bone_local_pose_override(bone_idx, Transform(), 0.0, false);
|
||||
}
|
||||
}
|
||||
|
||||
override_mode = p_mode;
|
||||
_transform_changed();
|
||||
return;
|
||||
}
|
||||
override_mode = p_mode;
|
||||
}
|
||||
|
||||
int BoneAttachment::get_override_mode() const {
|
||||
return override_mode;
|
||||
}
|
||||
|
||||
void BoneAttachment::set_use_external_skeleton(bool p_use_external) {
|
||||
use_external_skeleton = p_use_external;
|
||||
|
||||
if (use_external_skeleton) {
|
||||
_check_unbind();
|
||||
_update_external_skeleton_cache();
|
||||
_check_bind();
|
||||
_transform_changed();
|
||||
}
|
||||
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
bool BoneAttachment::get_use_external_skeleton() const {
|
||||
return use_external_skeleton;
|
||||
}
|
||||
|
||||
void BoneAttachment::set_external_skeleton(NodePath p_path) {
|
||||
external_skeleton_node = p_path;
|
||||
_update_external_skeleton_cache();
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
NodePath BoneAttachment::get_external_skeleton() const {
|
||||
return external_skeleton_node;
|
||||
}
|
||||
|
||||
void BoneAttachment::on_bone_pose_update(int p_bone_index) {
|
||||
if (bone_idx == p_bone_index) {
|
||||
Skeleton *sk = _get_skeleton();
|
||||
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)));
|
||||
} else {
|
||||
set_transform(sk->get_bone_global_pose(bone_idx));
|
||||
}
|
||||
} else {
|
||||
if (!_override_dirty) {
|
||||
_transform_changed();
|
||||
_override_dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String BoneAttachment::get_configuration_warning() const {
|
||||
String warnings = Spatial::get_configuration_warning();
|
||||
|
||||
if (use_external_skeleton) {
|
||||
if (external_skeleton_node_cache == 0) {
|
||||
warnings += RTR("External Skeleton3D node not set! Please set a path to an external Skeleton3D node.");
|
||||
}
|
||||
} else {
|
||||
Skeleton *parent = Object::cast_to<Skeleton>(get_parent());
|
||||
if (!parent) {
|
||||
warnings += RTR("Parent node is not a Skeleton3D node! Please use an external Skeleton3D if you intend to use the BoneAttachment3D without it being a child of a Skeleton3D node.");
|
||||
}
|
||||
}
|
||||
|
||||
if (bone_idx == -1) {
|
||||
warnings += RTR("BoneAttachment3D node is not bound to any bones! Please select a bone to attach this node.");
|
||||
}
|
||||
|
||||
return warnings;
|
||||
}
|
||||
|
||||
BoneAttachment::BoneAttachment() {
|
||||
bound = false;
|
||||
bone_idx = -1;
|
||||
override_pose = false;
|
||||
override_mode = MODE_GLOBAL_POSE;
|
||||
_override_dirty = false;
|
||||
use_external_skeleton = false;
|
||||
external_skeleton_node_cache = 0;
|
||||
|
||||
set_notify_transform(true);
|
||||
}
|
||||
|
||||
void BoneAttachment::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_ENTER_TREE: {
|
||||
if (use_external_skeleton) {
|
||||
_update_external_skeleton_cache();
|
||||
}
|
||||
_check_bind();
|
||||
} break;
|
||||
case NOTIFICATION_EXIT_TREE: {
|
||||
_check_unbind();
|
||||
} break;
|
||||
case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: {
|
||||
_transform_changed();
|
||||
} break;
|
||||
case NOTIFICATION_INTERNAL_PROCESS: {
|
||||
if (_override_dirty) {
|
||||
_override_dirty = false;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void BoneAttachment::_validate_property(PropertyInfo &property) const {
|
||||
if (property.name == "bone_name") {
|
||||
// Because it is a constant function, we cannot use the _get_skeleton_3d function.
|
||||
const Skeleton *parent = nullptr;
|
||||
if (use_external_skeleton) {
|
||||
if (external_skeleton_node_cache != 0) {
|
||||
parent = Object::cast_to<Skeleton>(ObjectDB::get_instance(external_skeleton_node_cache));
|
||||
}
|
||||
} else {
|
||||
parent = Object::cast_to<Skeleton>(get_parent());
|
||||
}
|
||||
|
||||
if (parent) {
|
||||
String names;
|
||||
for (int i = 0; i < parent->get_bone_count(); i++) {
|
||||
if (i > 0) {
|
||||
names += ",";
|
||||
}
|
||||
names += parent->get_bone_name(i);
|
||||
}
|
||||
|
||||
property.hint = PROPERTY_HINT_ENUM;
|
||||
property.hint_string = names;
|
||||
} else {
|
||||
property.hint = PROPERTY_HINT_NONE;
|
||||
property.hint_string = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool BoneAttachment::_set(const StringName &p_path, const Variant &p_value) {
|
||||
if (p_path == "override_pose") {
|
||||
set_override_pose(p_value);
|
||||
} else if (p_path == "override_mode") {
|
||||
set_override_mode(p_value);
|
||||
} else if (p_path == "use_external_skeleton") {
|
||||
set_use_external_skeleton(p_value);
|
||||
} else if (p_path == "external_skeleton") {
|
||||
set_external_skeleton(p_value);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BoneAttachment::_get(const StringName &p_path, Variant &r_ret) const {
|
||||
if (p_path == "override_pose") {
|
||||
r_ret = get_override_pose();
|
||||
} else if (p_path == "override_mode") {
|
||||
r_ret = get_override_mode();
|
||||
} else if (p_path == "use_external_skeleton") {
|
||||
r_ret = get_use_external_skeleton();
|
||||
} else if (p_path == "external_skeleton") {
|
||||
r_ret = get_external_skeleton();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void BoneAttachment::_get_property_list(List<PropertyInfo> *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"));
|
||||
}
|
||||
}
|
||||
|
||||
void BoneAttachment::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_bone_name", "bone_name"), &BoneAttachment::set_bone_name);
|
||||
ClassDB::bind_method(D_METHOD("get_bone_name"), &BoneAttachment::get_bone_name);
|
||||
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "bone_name"), "set_bone_name", "get_bone_name");
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_bone_idx", "bone_idx"), &BoneAttachment::set_bone_idx);
|
||||
ClassDB::bind_method(D_METHOD("get_bone_idx"), &BoneAttachment::get_bone_idx);
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "bone_idx"), "set_bone_idx", "get_bone_idx");
|
||||
|
||||
ClassDB::bind_method(D_METHOD("on_bone_pose_update", "bone_index"), &BoneAttachment::on_bone_pose_update);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_override_pose", "override_pose"), &BoneAttachment::set_override_pose);
|
||||
ClassDB::bind_method(D_METHOD("get_override_pose"), &BoneAttachment::get_override_pose);
|
||||
ClassDB::bind_method(D_METHOD("set_override_mode", "override_mode"), &BoneAttachment::set_override_mode);
|
||||
ClassDB::bind_method(D_METHOD("get_override_mode"), &BoneAttachment::get_override_mode);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_use_external_skeleton", "use_external_skeleton"), &BoneAttachment::set_use_external_skeleton);
|
||||
ClassDB::bind_method(D_METHOD("get_use_external_skeleton"), &BoneAttachment::get_use_external_skeleton);
|
||||
ClassDB::bind_method(D_METHOD("set_external_skeleton", "external_skeleton"), &BoneAttachment::set_external_skeleton);
|
||||
ClassDB::bind_method(D_METHOD("get_external_skeleton"), &BoneAttachment::get_external_skeleton);
|
||||
}
|
||||
|
||||
void BoneAttachment::_check_bind() {
|
||||
Skeleton *sk = _get_skeleton();
|
||||
|
||||
if (sk && !bound) {
|
||||
if (bone_idx <= -1) {
|
||||
bone_idx = sk->find_bone(bone_name);
|
||||
}
|
||||
|
||||
if (bone_idx != -1) {
|
||||
//sk->call_deferred("connect", "bone_pose_changed", this, "on_bone_pose_update");
|
||||
//call_deferred("on_bone_pose_update", bone_idx);
|
||||
bound = true;
|
||||
on_bone_pose_update(bone_idx);
|
||||
sk->connect("bone_pose_changed", this, "on_bone_pose_update");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BoneAttachment::_check_unbind() {
|
||||
if (bound) {
|
||||
Skeleton *sk = _get_skeleton();
|
||||
|
||||
if (sk) {
|
||||
if (bone_idx != -1) {
|
||||
sk->disconnect("bone_pose_changed", this, "on_bone_pose_update");
|
||||
}
|
||||
}
|
||||
bound = false;
|
||||
}
|
||||
}
|
||||
|
||||
void BoneAttachment::_update_external_skeleton_cache() {
|
||||
external_skeleton_node_cache = 0;
|
||||
if (has_node(external_skeleton_node)) {
|
||||
Node *node = get_node(external_skeleton_node);
|
||||
ERR_FAIL_COND_MSG(!node, "Cannot update external skeleton cache: Node cannot be found!");
|
||||
|
||||
// Make sure it's a skeleton3D
|
||||
Skeleton *sk = Object::cast_to<Skeleton>(node);
|
||||
ERR_FAIL_COND_MSG(!sk, "Cannot update external skeleton cache: Skeleton3D Nodepath does not point to a Skeleton3D node!");
|
||||
|
||||
external_skeleton_node_cache = node->get_instance_id();
|
||||
} else {
|
||||
if (external_skeleton_node.is_empty()) {
|
||||
BoneAttachment *parent_attachment = Object::cast_to<BoneAttachment>(get_parent());
|
||||
if (parent_attachment) {
|
||||
parent_attachment->_update_external_skeleton_cache();
|
||||
if (parent_attachment->has_node(parent_attachment->external_skeleton_node)) {
|
||||
Node *node = parent_attachment->get_node(parent_attachment->external_skeleton_node);
|
||||
ERR_FAIL_COND_MSG(!node, "Cannot update external skeleton cache: Parent's Skeleton3D node cannot be found!");
|
||||
|
||||
// Make sure it's a skeleton3D
|
||||
Skeleton *sk = Object::cast_to<Skeleton>(node);
|
||||
ERR_FAIL_COND_MSG(!sk, "Cannot update external skeleton cache: Parent Skeleton3D Nodepath does not point to a Skeleton3D node!");
|
||||
|
||||
external_skeleton_node_cache = node->get_instance_id();
|
||||
external_skeleton_node = get_path_to(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Skeleton *BoneAttachment::_get_skeleton() {
|
||||
if (use_external_skeleton) {
|
||||
if (external_skeleton_node_cache != 0) {
|
||||
return Object::cast_to<Skeleton>(ObjectDB::get_instance(external_skeleton_node_cache));
|
||||
} else {
|
||||
_update_external_skeleton_cache();
|
||||
if (external_skeleton_node_cache != 0) {
|
||||
return Object::cast_to<Skeleton>(ObjectDB::get_instance(external_skeleton_node_cache));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Object::cast_to<Skeleton>(get_parent());
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void BoneAttachment::_transform_changed() {
|
||||
if (!is_inside_tree()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (override_pose) {
|
||||
Skeleton *sk = _get_skeleton();
|
||||
|
||||
ERR_FAIL_COND_MSG(!sk, "Cannot override pose: Skeleton not found!");
|
||||
ERR_FAIL_INDEX_MSG(bone_idx, sk->get_bone_count(), "Cannot override pose: Bone index is out of range!");
|
||||
|
||||
Transform our_trans = get_transform();
|
||||
if (use_external_skeleton) {
|
||||
our_trans = sk->world_transform_to_global_pose(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);
|
||||
}
|
||||
}
|
||||
}
|
100
modules/skeleton_3d/nodes/bone_attachment.h
Normal file
@ -0,0 +1,100 @@
|
||||
#ifndef BONE_ATTACHMENT_H
|
||||
#define BONE_ATTACHMENT_H
|
||||
/*************************************************************************/
|
||||
/* bone_attachment.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "skeleton.h"
|
||||
|
||||
class BoneAttachment : public Spatial {
|
||||
GDCLASS(BoneAttachment, Spatial);
|
||||
|
||||
public:
|
||||
void set_bone_name(const String &p_name);
|
||||
String get_bone_name() const;
|
||||
|
||||
void set_bone_idx(const int p_idx);
|
||||
int get_bone_idx() const;
|
||||
|
||||
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;
|
||||
|
||||
void set_external_skeleton(NodePath p_skeleton);
|
||||
NodePath get_external_skeleton() const;
|
||||
|
||||
virtual void on_bone_pose_update(int p_bone_index);
|
||||
|
||||
String get_configuration_warning() const;
|
||||
|
||||
BoneAttachment();
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
|
||||
void _validate_property(PropertyInfo &property) const;
|
||||
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<PropertyInfo> *p_list) const;
|
||||
|
||||
static void _bind_methods();
|
||||
|
||||
private:
|
||||
void _check_bind();
|
||||
void _check_unbind();
|
||||
|
||||
void _transform_changed();
|
||||
void _update_external_skeleton_cache();
|
||||
Skeleton *_get_skeleton();
|
||||
|
||||
private:
|
||||
enum OVERRIDE_MODES {
|
||||
MODE_GLOBAL_POSE,
|
||||
MODE_LOCAL_POSE,
|
||||
};
|
||||
|
||||
bool bound;
|
||||
String bone_name;
|
||||
int bone_idx;
|
||||
|
||||
bool override_pose;
|
||||
int override_mode;
|
||||
bool _override_dirty;
|
||||
|
||||
bool use_external_skeleton = false;
|
||||
NodePath external_skeleton_node;
|
||||
ObjectID external_skeleton_node_cache;
|
||||
};
|
||||
|
||||
#endif // BONE_ATTACHMENT_H
|
1242
modules/skeleton_3d/nodes/physical_bone.cpp
Normal file
321
modules/skeleton_3d/nodes/physical_bone.h
Normal file
@ -0,0 +1,321 @@
|
||||
#ifndef PHYSICAL_BONE_H
|
||||
#define PHYSICAL_BONE_H
|
||||
|
||||
/*************************************************************************/
|
||||
/* physics_body.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "core/object/reference.h"
|
||||
#include "core/containers/vset.h"
|
||||
#include "scene/3d/collision_object.h"
|
||||
#include "servers/physics_server.h"
|
||||
#include "scene/3d/physics_body.h"
|
||||
|
||||
class PhysicsMaterial;
|
||||
class Skeleton;
|
||||
|
||||
class PhysicalBone : public PhysicsBody {
|
||||
GDCLASS(PhysicalBone, PhysicsBody);
|
||||
|
||||
public:
|
||||
enum JointType {
|
||||
JOINT_TYPE_NONE,
|
||||
JOINT_TYPE_PIN,
|
||||
JOINT_TYPE_CONE,
|
||||
JOINT_TYPE_HINGE,
|
||||
JOINT_TYPE_SLIDER,
|
||||
JOINT_TYPE_6DOF
|
||||
};
|
||||
|
||||
struct JointData {
|
||||
virtual JointType get_joint_type() { return JOINT_TYPE_NONE; }
|
||||
|
||||
/// "j" is used to set the parameter inside the PhysicsServer
|
||||
virtual bool _set(const StringName &p_name, const Variant &p_value, RID j);
|
||||
virtual bool _get(const StringName &p_name, Variant &r_ret) const;
|
||||
virtual void _get_property_list(List<PropertyInfo> *p_list) const;
|
||||
|
||||
virtual ~JointData() {}
|
||||
};
|
||||
|
||||
struct PinJointData : public JointData {
|
||||
virtual JointType get_joint_type() { return JOINT_TYPE_PIN; }
|
||||
|
||||
virtual bool _set(const StringName &p_name, const Variant &p_value, RID j);
|
||||
virtual bool _get(const StringName &p_name, Variant &r_ret) const;
|
||||
virtual void _get_property_list(List<PropertyInfo> *p_list) const;
|
||||
|
||||
real_t bias;
|
||||
real_t damping;
|
||||
real_t impulse_clamp;
|
||||
|
||||
PinJointData() :
|
||||
bias(0.3),
|
||||
damping(1.),
|
||||
impulse_clamp(0) {}
|
||||
};
|
||||
|
||||
struct ConeJointData : public JointData {
|
||||
virtual JointType get_joint_type() { return JOINT_TYPE_CONE; }
|
||||
|
||||
virtual bool _set(const StringName &p_name, const Variant &p_value, RID j);
|
||||
virtual bool _get(const StringName &p_name, Variant &r_ret) const;
|
||||
virtual void _get_property_list(List<PropertyInfo> *p_list) const;
|
||||
|
||||
real_t swing_span;
|
||||
real_t twist_span;
|
||||
real_t bias;
|
||||
real_t softness;
|
||||
real_t relaxation;
|
||||
|
||||
ConeJointData() :
|
||||
swing_span(Math_PI * 0.25),
|
||||
twist_span(Math_PI),
|
||||
bias(0.3),
|
||||
softness(0.8),
|
||||
relaxation(1.) {}
|
||||
};
|
||||
|
||||
struct HingeJointData : public JointData {
|
||||
virtual JointType get_joint_type() { return JOINT_TYPE_HINGE; }
|
||||
|
||||
virtual bool _set(const StringName &p_name, const Variant &p_value, RID j);
|
||||
virtual bool _get(const StringName &p_name, Variant &r_ret) const;
|
||||
virtual void _get_property_list(List<PropertyInfo> *p_list) const;
|
||||
|
||||
bool angular_limit_enabled;
|
||||
real_t angular_limit_upper;
|
||||
real_t angular_limit_lower;
|
||||
real_t angular_limit_bias;
|
||||
real_t angular_limit_softness;
|
||||
real_t angular_limit_relaxation;
|
||||
|
||||
HingeJointData() :
|
||||
angular_limit_enabled(false),
|
||||
angular_limit_upper(Math_PI * 0.5),
|
||||
angular_limit_lower(-Math_PI * 0.5),
|
||||
angular_limit_bias(0.3),
|
||||
angular_limit_softness(0.9),
|
||||
angular_limit_relaxation(1.) {}
|
||||
};
|
||||
|
||||
struct SliderJointData : public JointData {
|
||||
virtual JointType get_joint_type() { return JOINT_TYPE_SLIDER; }
|
||||
|
||||
virtual bool _set(const StringName &p_name, const Variant &p_value, RID j);
|
||||
virtual bool _get(const StringName &p_name, Variant &r_ret) const;
|
||||
virtual void _get_property_list(List<PropertyInfo> *p_list) const;
|
||||
|
||||
real_t linear_limit_upper;
|
||||
real_t linear_limit_lower;
|
||||
real_t linear_limit_softness;
|
||||
real_t linear_limit_restitution;
|
||||
real_t linear_limit_damping;
|
||||
real_t angular_limit_upper;
|
||||
real_t angular_limit_lower;
|
||||
real_t angular_limit_softness;
|
||||
real_t angular_limit_restitution;
|
||||
real_t angular_limit_damping;
|
||||
|
||||
SliderJointData() :
|
||||
linear_limit_upper(1.),
|
||||
linear_limit_lower(-1.),
|
||||
linear_limit_softness(1.),
|
||||
linear_limit_restitution(0.7),
|
||||
linear_limit_damping(1.),
|
||||
angular_limit_upper(0),
|
||||
angular_limit_lower(0),
|
||||
angular_limit_softness(1.),
|
||||
angular_limit_restitution(0.7),
|
||||
angular_limit_damping(1.) {}
|
||||
};
|
||||
|
||||
struct SixDOFJointData : public JointData {
|
||||
struct SixDOFAxisData {
|
||||
bool linear_limit_enabled;
|
||||
real_t linear_limit_upper;
|
||||
real_t linear_limit_lower;
|
||||
real_t linear_limit_softness;
|
||||
real_t linear_restitution;
|
||||
real_t linear_damping;
|
||||
bool linear_spring_enabled;
|
||||
real_t linear_spring_stiffness;
|
||||
real_t linear_spring_damping;
|
||||
real_t linear_equilibrium_point;
|
||||
bool angular_limit_enabled;
|
||||
real_t angular_limit_upper;
|
||||
real_t angular_limit_lower;
|
||||
real_t angular_limit_softness;
|
||||
real_t angular_restitution;
|
||||
real_t angular_damping;
|
||||
real_t erp;
|
||||
bool angular_spring_enabled;
|
||||
real_t angular_spring_stiffness;
|
||||
real_t angular_spring_damping;
|
||||
real_t angular_equilibrium_point;
|
||||
|
||||
SixDOFAxisData() :
|
||||
linear_limit_enabled(true),
|
||||
linear_limit_upper(0),
|
||||
linear_limit_lower(0),
|
||||
linear_limit_softness(0.7),
|
||||
linear_restitution(0.5),
|
||||
linear_damping(1.),
|
||||
linear_spring_enabled(false),
|
||||
linear_spring_stiffness(0),
|
||||
linear_spring_damping(0),
|
||||
linear_equilibrium_point(0),
|
||||
angular_limit_enabled(true),
|
||||
angular_limit_upper(0),
|
||||
angular_limit_lower(0),
|
||||
angular_limit_softness(0.5),
|
||||
angular_restitution(0),
|
||||
angular_damping(1.),
|
||||
erp(0.5),
|
||||
angular_spring_enabled(false),
|
||||
angular_spring_stiffness(0),
|
||||
angular_spring_damping(0.),
|
||||
angular_equilibrium_point(0) {}
|
||||
};
|
||||
|
||||
virtual JointType get_joint_type() { return JOINT_TYPE_6DOF; }
|
||||
|
||||
virtual bool _set(const StringName &p_name, const Variant &p_value, RID j);
|
||||
virtual bool _get(const StringName &p_name, Variant &r_ret) const;
|
||||
virtual void _get_property_list(List<PropertyInfo> *p_list) const;
|
||||
|
||||
SixDOFAxisData axis_data[3];
|
||||
|
||||
SixDOFJointData() {}
|
||||
};
|
||||
|
||||
private:
|
||||
#ifdef TOOLS_ENABLED
|
||||
// if false gizmo move body
|
||||
bool gizmo_move_joint;
|
||||
#endif
|
||||
|
||||
JointData *joint_data;
|
||||
Transform joint_offset;
|
||||
RID joint;
|
||||
|
||||
Skeleton *parent_skeleton;
|
||||
Transform body_offset;
|
||||
Transform body_offset_inverse;
|
||||
bool simulate_physics;
|
||||
bool _internal_simulate_physics;
|
||||
int bone_id;
|
||||
|
||||
String bone_name;
|
||||
real_t bounce;
|
||||
real_t mass;
|
||||
real_t friction;
|
||||
real_t gravity_scale;
|
||||
|
||||
protected:
|
||||
bool _set(const StringName &p_name, const Variant &p_value);
|
||||
bool _get(const StringName &p_name, Variant &r_ret) const;
|
||||
void _get_property_list(List<PropertyInfo> *p_list) const;
|
||||
void _notification(int p_what);
|
||||
void _direct_state_changed(Object *p_state);
|
||||
|
||||
static void _bind_methods();
|
||||
|
||||
private:
|
||||
static Skeleton *find_skeleton_parent(Node *p_parent);
|
||||
|
||||
void _fix_joint_offset();
|
||||
void _reload_joint();
|
||||
|
||||
public:
|
||||
void _on_bone_parent_changed();
|
||||
void _set_gizmo_move_joint(bool p_move_joint);
|
||||
|
||||
public:
|
||||
#ifdef TOOLS_ENABLED
|
||||
virtual Transform get_global_gizmo_transform() const;
|
||||
virtual Transform get_local_gizmo_transform() const;
|
||||
#endif
|
||||
|
||||
const JointData *get_joint_data() const;
|
||||
Skeleton *find_skeleton_parent();
|
||||
|
||||
int get_bone_id() const { return bone_id; }
|
||||
|
||||
void set_joint_type(JointType p_joint_type);
|
||||
JointType get_joint_type() const;
|
||||
|
||||
void set_joint_offset(const Transform &p_offset);
|
||||
const Transform &get_joint_offset() const;
|
||||
|
||||
void set_body_offset(const Transform &p_offset);
|
||||
const Transform &get_body_offset() const;
|
||||
|
||||
void set_simulate_physics(bool p_simulate);
|
||||
bool get_simulate_physics();
|
||||
bool is_simulating_physics();
|
||||
|
||||
void set_bone_name(const String &p_name);
|
||||
const String &get_bone_name() const;
|
||||
|
||||
void set_mass(real_t p_mass);
|
||||
real_t get_mass() const;
|
||||
|
||||
void set_weight(real_t p_weight);
|
||||
real_t get_weight() const;
|
||||
|
||||
void set_friction(real_t p_friction);
|
||||
real_t get_friction() const;
|
||||
|
||||
void set_bounce(real_t p_bounce);
|
||||
real_t get_bounce() const;
|
||||
|
||||
void set_gravity_scale(real_t p_gravity_scale);
|
||||
real_t get_gravity_scale() const;
|
||||
|
||||
void apply_central_impulse(const Vector3 &p_impulse);
|
||||
void apply_impulse(const Vector3 &p_pos, const Vector3 &p_impulse);
|
||||
|
||||
void reset_physics_simulation_state();
|
||||
void reset_to_rest_position();
|
||||
|
||||
PhysicalBone();
|
||||
~PhysicalBone();
|
||||
|
||||
private:
|
||||
void update_bone_id();
|
||||
void update_offset();
|
||||
|
||||
void _start_physics_simulation();
|
||||
void _stop_physics_simulation();
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(PhysicalBone::JointType);
|
||||
|
||||
#endif // PHYSICS_BODY__H
|
1475
modules/skeleton_3d/nodes/skeleton.cpp
Normal file
314
modules/skeleton_3d/nodes/skeleton.h
Normal file
@ -0,0 +1,314 @@
|
||||
#ifndef SKELETON_H
|
||||
#define SKELETON_H
|
||||
/*************************************************************************/
|
||||
/* skeleton.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "core/object/reference.h"
|
||||
#include "core/containers/rid.h"
|
||||
#include "scene/main/spatial.h"
|
||||
|
||||
#ifndef _3D_DISABLED
|
||||
typedef int BoneId;
|
||||
|
||||
class PhysicalBone;
|
||||
#endif // _3D_DISABLED
|
||||
|
||||
class SkeletonModificationStack3D;
|
||||
class Skeleton;
|
||||
class Skin;
|
||||
|
||||
class SkinReference : public Reference {
|
||||
GDCLASS(SkinReference, Reference)
|
||||
friend class Skeleton;
|
||||
|
||||
Skeleton *skeleton_node = nullptr;
|
||||
RID skeleton;
|
||||
Ref<Skin> skin;
|
||||
uint32_t bind_count = 0;
|
||||
uint64_t skeleton_version = 0;
|
||||
Vector<uint32_t> skin_bone_indices;
|
||||
uint32_t *skin_bone_indices_ptrs = nullptr;
|
||||
void _skin_changed();
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
RID get_skeleton() const;
|
||||
Skeleton *get_skeleton_node() const;
|
||||
Ref<Skin> get_skin() const;
|
||||
~SkinReference();
|
||||
};
|
||||
|
||||
class Skeleton : public Spatial {
|
||||
GDCLASS(Skeleton, Spatial);
|
||||
|
||||
private:
|
||||
friend class SkinReference;
|
||||
|
||||
RBSet<SkinReference *> skin_bindings;
|
||||
|
||||
void _skin_changed();
|
||||
|
||||
struct Bone {
|
||||
String name;
|
||||
|
||||
bool enabled;
|
||||
int parent;
|
||||
|
||||
Transform rest;
|
||||
Transform global_rest;
|
||||
|
||||
_FORCE_INLINE_ void update_pose_cache() {
|
||||
if (pose_cache_dirty) {
|
||||
pose_cache.basis.set_quaternion_scale(pose_rotation, pose_scale);
|
||||
pose_cache.origin = pose_position;
|
||||
pose_cache_dirty = false;
|
||||
}
|
||||
}
|
||||
bool pose_cache_dirty;
|
||||
Transform pose_cache;
|
||||
Vector3 pose_position;
|
||||
Quaternion pose_rotation;
|
||||
Vector3 pose_scale;
|
||||
|
||||
Transform pose_global;
|
||||
Transform pose_global_no_override;
|
||||
|
||||
float global_pose_override_amount;
|
||||
bool global_pose_override_reset;
|
||||
Transform global_pose_override;
|
||||
|
||||
#ifndef _3D_DISABLED
|
||||
PhysicalBone *physical_bone;
|
||||
PhysicalBone *cache_parent_physical_bone;
|
||||
#endif // _3D_DISABLED
|
||||
|
||||
real_t local_pose_override_amount;
|
||||
bool local_pose_override_reset;
|
||||
Transform local_pose_override;
|
||||
|
||||
Vector<int> 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;
|
||||
|
||||
Bone() {
|
||||
parent = -1;
|
||||
enabled = true;
|
||||
pose_cache_dirty = true;
|
||||
pose_scale = Vector3(1, 1, 1);
|
||||
global_pose_override_amount = 0;
|
||||
global_pose_override_reset = false;
|
||||
#ifndef _3D_DISABLED
|
||||
physical_bone = nullptr;
|
||||
cache_parent_physical_bone = nullptr;
|
||||
#endif // _3D_DISABLED
|
||||
local_pose_override_amount = 0;
|
||||
local_pose_override_reset = false;
|
||||
child_bones = Vector<int>();
|
||||
|
||||
rest_bone_forward_vector = Vector3(0, 0, 0);
|
||||
rest_bone_forward_axis = -1;
|
||||
}
|
||||
};
|
||||
|
||||
bool animate_physical_bones;
|
||||
Vector<Bone> bones;
|
||||
bool process_order_dirty;
|
||||
|
||||
Vector<int> parentless_bones;
|
||||
|
||||
void _make_dirty();
|
||||
bool dirty;
|
||||
bool rest_dirty;
|
||||
int updating;
|
||||
|
||||
bool show_rest_only;
|
||||
|
||||
uint64_t version;
|
||||
|
||||
void _update_process_order();
|
||||
|
||||
protected:
|
||||
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<PropertyInfo> *p_list) const;
|
||||
//void _validate_property(PropertyInfo &property) const;
|
||||
|
||||
void _notification(int p_what);
|
||||
|
||||
static void _bind_methods();
|
||||
|
||||
#ifndef _3D_DISABLED
|
||||
Ref<SkeletonModificationStack3D> modification_stack;
|
||||
#endif // _3D_DISABLED
|
||||
|
||||
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
|
||||
void add_bone(const String &p_name);
|
||||
int find_bone(const String &p_name) const;
|
||||
String get_bone_name(int p_bone) const;
|
||||
void set_bone_name(int p_bone, const String &p_name);
|
||||
|
||||
bool is_bone_parent_of(int p_bone_id, int p_parent_bone_id) const;
|
||||
|
||||
void set_bone_parent(int p_bone, int p_parent);
|
||||
int get_bone_parent(int p_bone) const;
|
||||
|
||||
void unparent_bone_and_rest(int p_bone);
|
||||
|
||||
Vector<int> get_bone_children(int p_bone);
|
||||
void set_bone_children(int p_bone, Vector<int> p_children);
|
||||
void add_bone_child(int p_bone, int p_child);
|
||||
void remove_bone_child(int p_bone, int p_child);
|
||||
Vector<int> get_parentless_bones();
|
||||
|
||||
int get_bone_count() const;
|
||||
|
||||
void set_bone_rest(int p_bone, const Transform &p_rest);
|
||||
Transform get_bone_rest(int p_bone) const;
|
||||
Transform get_bone_global_rest(int p_bone) const;
|
||||
Transform get_bone_global_pose(int p_bone) const;
|
||||
Transform get_bone_global_pose_no_override(int p_bone) const;
|
||||
|
||||
void set_bone_enabled(int p_bone, bool p_enabled);
|
||||
bool is_bone_enabled(int p_bone) const;
|
||||
|
||||
void set_show_rest_only(bool p_enabled);
|
||||
bool is_show_rest_only() const;
|
||||
|
||||
void clear_bones();
|
||||
|
||||
// posing api
|
||||
void set_bone_pose(int p_bone, const Transform &p_pose);
|
||||
void set_bone_pose_position(int p_bone, const Vector3 &p_position);
|
||||
void set_bone_pose_rotation(int p_bone, const Quaternion &p_rotation);
|
||||
void set_bone_pose_scale(int p_bone, const Vector3 &p_scale);
|
||||
|
||||
Transform get_bone_pose(int p_bone) const;
|
||||
Vector3 get_bone_pose_position(int p_bone) const;
|
||||
Quaternion get_bone_pose_rotation(int p_bone) const;
|
||||
Vector3 get_bone_pose_scale(int p_bone) const;
|
||||
|
||||
void clear_bones_global_pose_override();
|
||||
Transform get_bone_global_pose_override(int p_bone) const;
|
||||
void set_bone_global_pose_override(int p_bone, const Transform &p_pose, real_t p_amount, bool p_persistent = false);
|
||||
|
||||
void clear_bones_local_pose_override();
|
||||
Transform get_bone_local_pose_override(int p_bone) const;
|
||||
void set_bone_local_pose_override(int p_bone, const Transform &p_pose, real_t p_amount, bool p_persistent = false);
|
||||
|
||||
void localize_rests(); // used for loaders and tools
|
||||
|
||||
Ref<Skin> create_skin_from_rest_transforms();
|
||||
|
||||
Ref<SkinReference> register_skin(const Ref<Skin> &p_skin);
|
||||
|
||||
void init_pose();
|
||||
|
||||
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
|
||||
Transform global_pose_to_world_transform(Transform p_global_pose);
|
||||
Transform world_transform_to_global_pose(Transform p_transform);
|
||||
Transform global_pose_to_local_pose(int p_bone_idx, Transform p_global_pose);
|
||||
Transform local_pose_to_global_pose(int p_bone_idx, Transform p_local_pose);
|
||||
|
||||
Basis global_pose_z_forward_to_bone_forward(int p_bone_idx, Basis p_basis);
|
||||
|
||||
// Modifications
|
||||
#ifndef _3D_DISABLED
|
||||
Ref<SkeletonModificationStack3D> get_modification_stack();
|
||||
void set_modification_stack(Ref<SkeletonModificationStack3D> p_stack);
|
||||
void execute_modifications(real_t p_delta, int p_execution_mode);
|
||||
#endif // _3D_DISABLED
|
||||
|
||||
// Physical bone API
|
||||
#ifndef _3D_DISABLED
|
||||
|
||||
void set_animate_physical_bones(bool p_animate);
|
||||
bool get_animate_physical_bones() const;
|
||||
|
||||
void bind_physical_bone_to_bone(int p_bone, PhysicalBone *p_physical_bone);
|
||||
void unbind_physical_bone_from_bone(int p_bone);
|
||||
|
||||
PhysicalBone *get_physical_bone(int p_bone);
|
||||
PhysicalBone *get_physical_bone_parent(int p_bone);
|
||||
|
||||
private:
|
||||
/// This is a slow API os it's cached
|
||||
PhysicalBone *_get_physical_bone_parent(int p_bone);
|
||||
void _rebuild_physical_bones_cache();
|
||||
|
||||
public:
|
||||
void physical_bones_stop_simulation();
|
||||
void physical_bones_start_simulation_on(const Array &p_bones);
|
||||
void physical_bones_add_collision_exception(RID p_exception);
|
||||
void physical_bones_remove_collision_exception(RID p_exception);
|
||||
#endif // _3D_DISABLED
|
||||
|
||||
public:
|
||||
Skeleton();
|
||||
~Skeleton();
|
||||
|
||||
public:
|
||||
void set_selected_bone(int p_bone);
|
||||
int get_selected_bone() const;
|
||||
|
||||
int selected_bone = -1;
|
||||
|
||||
void remove_bone(const int p_bone_idx);
|
||||
};
|
||||
|
||||
#endif
|
595
modules/skeleton_3d/nodes/skeleton_ik.cpp
Normal file
@ -0,0 +1,595 @@
|
||||
/*************************************************************************/
|
||||
/* skeleton_ik.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
/**
|
||||
* @author AndreaCatania
|
||||
*/
|
||||
|
||||
#include "skeleton_ik.h"
|
||||
|
||||
#ifndef _3D_DISABLED
|
||||
|
||||
#include "skeleton.h"
|
||||
|
||||
FabrikInverseKinematic::ChainItem *FabrikInverseKinematic::ChainItem::find_child(const BoneId p_bone_id) {
|
||||
for (int i = children.size() - 1; 0 <= i; --i) {
|
||||
if (p_bone_id == children[i].bone) {
|
||||
return &children.write[i];
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FabrikInverseKinematic::ChainItem *FabrikInverseKinematic::ChainItem::add_child(const BoneId p_bone_id) {
|
||||
const int infant_child_id = children.size();
|
||||
children.resize(infant_child_id + 1);
|
||||
children.write[infant_child_id].bone = p_bone_id;
|
||||
children.write[infant_child_id].parent_item = this;
|
||||
return &children.write[infant_child_id];
|
||||
}
|
||||
|
||||
/// Build a chain that starts from the root to tip
|
||||
bool FabrikInverseKinematic::build_chain(Task *p_task, bool p_force_simple_chain) {
|
||||
ERR_FAIL_COND_V(-1 == p_task->root_bone, false);
|
||||
|
||||
Chain &chain(p_task->chain);
|
||||
|
||||
chain.tips.resize(p_task->end_effectors.size());
|
||||
chain.chain_root.bone = p_task->root_bone;
|
||||
chain.chain_root.initial_transform = p_task->skeleton->get_bone_global_pose(chain.chain_root.bone);
|
||||
chain.chain_root.current_pos = chain.chain_root.initial_transform.origin;
|
||||
chain.middle_chain_item = nullptr;
|
||||
|
||||
// Holds all IDs that are composing a single chain in reverse order
|
||||
Vector<BoneId> chain_ids;
|
||||
// This is used to know the chain size
|
||||
int sub_chain_size;
|
||||
// Resize only one time in order to fit all joints for performance reason
|
||||
chain_ids.resize(p_task->skeleton->get_bone_count());
|
||||
|
||||
for (int x = p_task->end_effectors.size() - 1; 0 <= x; --x) {
|
||||
const EndEffector *ee(&p_task->end_effectors[x]);
|
||||
ERR_FAIL_COND_V(p_task->root_bone >= ee->tip_bone, false);
|
||||
ERR_FAIL_INDEX_V(ee->tip_bone, p_task->skeleton->get_bone_count(), false);
|
||||
|
||||
sub_chain_size = 0;
|
||||
// Picks all IDs that composing a single chain in reverse order (except the root)
|
||||
BoneId chain_sub_tip(ee->tip_bone);
|
||||
while (chain_sub_tip > p_task->root_bone) {
|
||||
chain_ids.write[sub_chain_size++] = chain_sub_tip;
|
||||
chain_sub_tip = p_task->skeleton->get_bone_parent(chain_sub_tip);
|
||||
}
|
||||
|
||||
BoneId middle_chain_item_id = (((float)sub_chain_size) * 0.5);
|
||||
|
||||
// Build chain by reading chain ids in reverse order
|
||||
// For each chain item id will be created a ChainItem if doesn't exists
|
||||
ChainItem *sub_chain(&chain.chain_root);
|
||||
for (int i = sub_chain_size - 1; 0 <= i; --i) {
|
||||
ChainItem *child_ci(sub_chain->find_child(chain_ids[i]));
|
||||
if (!child_ci) {
|
||||
child_ci = sub_chain->add_child(chain_ids[i]);
|
||||
|
||||
child_ci->initial_transform = p_task->skeleton->get_bone_global_pose(child_ci->bone);
|
||||
child_ci->current_pos = child_ci->initial_transform.origin;
|
||||
|
||||
if (child_ci->parent_item) {
|
||||
child_ci->length = (child_ci->current_pos - child_ci->parent_item->current_pos).length();
|
||||
}
|
||||
}
|
||||
|
||||
sub_chain = child_ci;
|
||||
|
||||
if (middle_chain_item_id == i) {
|
||||
chain.middle_chain_item = child_ci;
|
||||
}
|
||||
}
|
||||
|
||||
if (!middle_chain_item_id) {
|
||||
chain.middle_chain_item = nullptr;
|
||||
}
|
||||
|
||||
// Initialize current tip
|
||||
chain.tips.write[x].chain_item = sub_chain;
|
||||
chain.tips.write[x].end_effector = ee;
|
||||
|
||||
if (p_force_simple_chain) {
|
||||
// NOTE:
|
||||
// This is an "hack" that force to create only one tip per chain since the solver of multi tip (end effector)
|
||||
// is not yet created.
|
||||
// Remove this code when this is done
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void FabrikInverseKinematic::solve_simple(Task *p_task, bool p_solve_magnet, Vector3 p_origin_pos) {
|
||||
real_t distance_to_goal(1e4);
|
||||
real_t previous_distance_to_goal(0);
|
||||
int can_solve(p_task->max_iterations);
|
||||
while (distance_to_goal > p_task->min_distance && Math::abs(previous_distance_to_goal - distance_to_goal) > 0.005 && can_solve) {
|
||||
previous_distance_to_goal = distance_to_goal;
|
||||
--can_solve;
|
||||
|
||||
solve_simple_backwards(p_task->chain, p_solve_magnet);
|
||||
solve_simple_forwards(p_task->chain, p_solve_magnet, p_origin_pos);
|
||||
|
||||
distance_to_goal = (p_task->chain.tips[0].chain_item->current_pos - p_task->chain.tips[0].end_effector->goal_transform.origin).length();
|
||||
}
|
||||
}
|
||||
|
||||
void FabrikInverseKinematic::solve_simple_backwards(Chain &r_chain, bool p_solve_magnet) {
|
||||
if (p_solve_magnet && !r_chain.middle_chain_item) {
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 goal;
|
||||
ChainItem *sub_chain_tip;
|
||||
if (p_solve_magnet) {
|
||||
goal = r_chain.magnet_position;
|
||||
sub_chain_tip = r_chain.middle_chain_item;
|
||||
} else {
|
||||
goal = r_chain.tips[0].end_effector->goal_transform.origin;
|
||||
sub_chain_tip = r_chain.tips[0].chain_item;
|
||||
}
|
||||
|
||||
while (sub_chain_tip) {
|
||||
sub_chain_tip->current_pos = goal;
|
||||
|
||||
if (sub_chain_tip->parent_item) {
|
||||
// Not yet in the chain root
|
||||
// So calculate next goal location
|
||||
|
||||
const Vector3 look_parent((sub_chain_tip->parent_item->current_pos - sub_chain_tip->current_pos).normalized());
|
||||
goal = sub_chain_tip->current_pos + (look_parent * sub_chain_tip->length);
|
||||
|
||||
// [TODO] Constraints goes here
|
||||
}
|
||||
|
||||
sub_chain_tip = sub_chain_tip->parent_item;
|
||||
}
|
||||
}
|
||||
|
||||
void FabrikInverseKinematic::solve_simple_forwards(Chain &r_chain, bool p_solve_magnet, Vector3 p_origin_pos) {
|
||||
if (p_solve_magnet && !r_chain.middle_chain_item) {
|
||||
return;
|
||||
}
|
||||
|
||||
ChainItem *sub_chain_root(&r_chain.chain_root);
|
||||
Vector3 origin = p_origin_pos;
|
||||
|
||||
while (sub_chain_root) { // Reach the tip
|
||||
sub_chain_root->current_pos = origin;
|
||||
|
||||
if (!sub_chain_root->children.empty()) {
|
||||
ChainItem &child(sub_chain_root->children.write[0]);
|
||||
|
||||
// Is not tip
|
||||
// So calculate next origin location
|
||||
|
||||
// Look child
|
||||
sub_chain_root->current_ori = (child.current_pos - sub_chain_root->current_pos).normalized();
|
||||
origin = sub_chain_root->current_pos + (sub_chain_root->current_ori * child.length);
|
||||
|
||||
// [TODO] Constraints goes here
|
||||
|
||||
if (p_solve_magnet && sub_chain_root == r_chain.middle_chain_item) {
|
||||
// In case of magnet solving this is the tip
|
||||
sub_chain_root = nullptr;
|
||||
} else {
|
||||
sub_chain_root = &child;
|
||||
}
|
||||
} else {
|
||||
// Is tip
|
||||
sub_chain_root = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FabrikInverseKinematic::Task *FabrikInverseKinematic::create_simple_task(Skeleton *p_sk, BoneId root_bone, BoneId tip_bone, const Transform &goal_transform) {
|
||||
FabrikInverseKinematic::EndEffector ee;
|
||||
ee.tip_bone = tip_bone;
|
||||
|
||||
Task *task(memnew(Task));
|
||||
task->skeleton = p_sk;
|
||||
task->root_bone = root_bone;
|
||||
task->end_effectors.push_back(ee);
|
||||
task->goal_global_transform = goal_transform;
|
||||
|
||||
if (!build_chain(task)) {
|
||||
free_task(task);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
void FabrikInverseKinematic::free_task(Task *p_task) {
|
||||
if (p_task) {
|
||||
memdelete(p_task);
|
||||
}
|
||||
}
|
||||
|
||||
void FabrikInverseKinematic::set_goal(Task *p_task, const Transform &p_goal) {
|
||||
p_task->goal_global_transform = p_goal;
|
||||
}
|
||||
|
||||
void FabrikInverseKinematic::make_goal(Task *p_task, const Transform &p_inverse_transf, real_t blending_delta) {
|
||||
if (blending_delta >= 0.99f) {
|
||||
// Update the end_effector (local transform) without blending
|
||||
p_task->end_effectors.write[0].goal_transform = p_inverse_transf * p_task->goal_global_transform;
|
||||
} else {
|
||||
// End effector in local transform
|
||||
const Transform end_effector_pose(p_task->skeleton->get_bone_global_pose_no_override(p_task->end_effectors[0].tip_bone));
|
||||
|
||||
// Update the end_effector (local transform) by blending with current pose
|
||||
p_task->end_effectors.write[0].goal_transform = end_effector_pose.interpolate_with(p_inverse_transf * p_task->goal_global_transform, blending_delta);
|
||||
}
|
||||
}
|
||||
|
||||
void FabrikInverseKinematic::solve(Task *p_task, real_t blending_delta, bool override_tip_basis, bool p_use_magnet, const Vector3 &p_magnet_position) {
|
||||
if (blending_delta <= 0.01f) {
|
||||
// Before skipping, make sure we undo the global pose overrides
|
||||
ChainItem *ci(&p_task->chain.chain_root);
|
||||
while (ci) {
|
||||
p_task->skeleton->set_bone_global_pose_override(ci->bone, ci->initial_transform, 0.0, false);
|
||||
|
||||
if (!ci->children.empty()) {
|
||||
ci = &ci->children.write[0];
|
||||
} else {
|
||||
ci = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return; // Skip solving
|
||||
}
|
||||
|
||||
// Update the initial root transform so its synced with any animation changes
|
||||
_update_chain(p_task->skeleton, &p_task->chain.chain_root);
|
||||
|
||||
p_task->skeleton->set_bone_global_pose_override(p_task->chain.chain_root.bone, Transform(), 0.0, false);
|
||||
Vector3 origin_pos = p_task->skeleton->get_bone_global_pose(p_task->chain.chain_root.bone).origin;
|
||||
|
||||
make_goal(p_task, p_task->skeleton->get_global_transform().affine_inverse(), blending_delta);
|
||||
|
||||
if (p_use_magnet && p_task->chain.middle_chain_item) {
|
||||
p_task->chain.magnet_position = p_task->chain.middle_chain_item->initial_transform.origin.linear_interpolate(p_magnet_position, blending_delta);
|
||||
solve_simple(p_task, true, origin_pos);
|
||||
}
|
||||
solve_simple(p_task, false, origin_pos);
|
||||
|
||||
// Assign new bone position.
|
||||
ChainItem *ci(&p_task->chain.chain_root);
|
||||
while (ci) {
|
||||
Transform new_bone_pose(ci->initial_transform);
|
||||
new_bone_pose.origin = ci->current_pos;
|
||||
|
||||
if (!ci->children.empty()) {
|
||||
/// Rotate basis
|
||||
const Vector3 initial_ori((ci->children[0].initial_transform.origin - ci->initial_transform.origin).normalized());
|
||||
const Vector3 rot_axis(initial_ori.cross(ci->current_ori).normalized());
|
||||
|
||||
if (rot_axis[0] != 0 && rot_axis[1] != 0 && rot_axis[2] != 0) {
|
||||
// acos does clamping.
|
||||
const real_t rot_angle(Math::acos(initial_ori.dot(ci->current_ori)));
|
||||
new_bone_pose.basis.rotate(rot_axis, rot_angle);
|
||||
}
|
||||
|
||||
} else {
|
||||
// Set target orientation to tip
|
||||
if (override_tip_basis)
|
||||
new_bone_pose.basis = p_task->chain.tips[0].end_effector->goal_transform.basis;
|
||||
else
|
||||
new_bone_pose.basis = new_bone_pose.basis * p_task->chain.tips[0].end_effector->goal_transform.basis;
|
||||
}
|
||||
|
||||
// IK should not affect scale, so undo any scaling
|
||||
new_bone_pose.basis.orthonormalize();
|
||||
new_bone_pose.basis.scale(p_task->skeleton->get_bone_global_pose(ci->bone).basis.get_scale());
|
||||
|
||||
p_task->skeleton->set_bone_global_pose_override(ci->bone, new_bone_pose, 1.0, true);
|
||||
|
||||
if (!ci->children.empty()) {
|
||||
ci = &ci->children.write[0];
|
||||
} else {
|
||||
ci = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FabrikInverseKinematic::_update_chain(const Skeleton *p_sk, ChainItem *p_chain_item) {
|
||||
if (!p_chain_item) {
|
||||
return;
|
||||
}
|
||||
|
||||
p_chain_item->initial_transform = p_sk->get_bone_global_pose_no_override(p_chain_item->bone);
|
||||
p_chain_item->current_pos = p_chain_item->initial_transform.origin;
|
||||
|
||||
ChainItem *items = p_chain_item->children.ptrw();
|
||||
for (int i = 0; i < p_chain_item->children.size(); i += 1) {
|
||||
_update_chain(p_sk, items + i);
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonIK::_validate_property(PropertyInfo &property) const {
|
||||
if (property.name == "root_bone" || property.name == "tip_bone") {
|
||||
if (skeleton) {
|
||||
String names("--,");
|
||||
for (int i = 0; i < skeleton->get_bone_count(); i++) {
|
||||
if (i > 0) {
|
||||
names += ",";
|
||||
}
|
||||
names += skeleton->get_bone_name(i);
|
||||
}
|
||||
|
||||
property.hint = PROPERTY_HINT_ENUM;
|
||||
property.hint_string = names;
|
||||
} else {
|
||||
property.hint = PROPERTY_HINT_NONE;
|
||||
property.hint_string = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonIK::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_root_bone", "root_bone"), &SkeletonIK::set_root_bone);
|
||||
ClassDB::bind_method(D_METHOD("get_root_bone"), &SkeletonIK::get_root_bone);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_tip_bone", "tip_bone"), &SkeletonIK::set_tip_bone);
|
||||
ClassDB::bind_method(D_METHOD("get_tip_bone"), &SkeletonIK::get_tip_bone);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_interpolation", "interpolation"), &SkeletonIK::set_interpolation);
|
||||
ClassDB::bind_method(D_METHOD("get_interpolation"), &SkeletonIK::get_interpolation);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_target_transform", "target"), &SkeletonIK::set_target_transform);
|
||||
ClassDB::bind_method(D_METHOD("get_target_transform"), &SkeletonIK::get_target_transform);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_target_node", "node"), &SkeletonIK::set_target_node);
|
||||
ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonIK::get_target_node);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_override_tip_basis", "override"), &SkeletonIK::set_override_tip_basis);
|
||||
ClassDB::bind_method(D_METHOD("is_override_tip_basis"), &SkeletonIK::is_override_tip_basis);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_use_magnet", "use"), &SkeletonIK::set_use_magnet);
|
||||
ClassDB::bind_method(D_METHOD("is_using_magnet"), &SkeletonIK::is_using_magnet);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_magnet_position", "local_position"), &SkeletonIK::set_magnet_position);
|
||||
ClassDB::bind_method(D_METHOD("get_magnet_position"), &SkeletonIK::get_magnet_position);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_parent_skeleton"), &SkeletonIK::get_parent_skeleton);
|
||||
ClassDB::bind_method(D_METHOD("is_running"), &SkeletonIK::is_running);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_min_distance", "min_distance"), &SkeletonIK::set_min_distance);
|
||||
ClassDB::bind_method(D_METHOD("get_min_distance"), &SkeletonIK::get_min_distance);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_max_iterations", "iterations"), &SkeletonIK::set_max_iterations);
|
||||
ClassDB::bind_method(D_METHOD("get_max_iterations"), &SkeletonIK::get_max_iterations);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("start", "one_time"), &SkeletonIK::start, DEFVAL(false));
|
||||
ClassDB::bind_method(D_METHOD("stop"), &SkeletonIK::stop);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "root_bone"), "set_root_bone", "get_root_bone");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "tip_bone"), "set_tip_bone", "get_tip_bone");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::REAL, "interpolation", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_interpolation", "get_interpolation");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM, "target"), "set_target_transform", "get_target_transform");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "override_tip_basis"), "set_override_tip_basis", "is_override_tip_basis");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_magnet"), "set_use_magnet", "is_using_magnet");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "magnet"), "set_magnet_position", "get_magnet_position");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_node"), "set_target_node", "get_target_node");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::REAL, "min_distance"), "set_min_distance", "get_min_distance");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "max_iterations"), "set_max_iterations", "get_max_iterations");
|
||||
}
|
||||
|
||||
void SkeletonIK::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_ENTER_TREE: {
|
||||
skeleton = Object::cast_to<Skeleton>(get_parent());
|
||||
set_process_priority(1);
|
||||
reload_chain();
|
||||
} break;
|
||||
case NOTIFICATION_INTERNAL_PROCESS: {
|
||||
if (target_node_override) {
|
||||
reload_goal();
|
||||
}
|
||||
|
||||
_solve_chain();
|
||||
|
||||
} break;
|
||||
case NOTIFICATION_EXIT_TREE: {
|
||||
reload_chain();
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
SkeletonIK::SkeletonIK() :
|
||||
interpolation(1),
|
||||
override_tip_basis(true),
|
||||
use_magnet(false),
|
||||
min_distance(0.01),
|
||||
max_iterations(10),
|
||||
skeleton(nullptr),
|
||||
target_node_override(nullptr),
|
||||
task(nullptr) {
|
||||
}
|
||||
|
||||
SkeletonIK::~SkeletonIK() {
|
||||
FabrikInverseKinematic::free_task(task);
|
||||
task = nullptr;
|
||||
}
|
||||
|
||||
void SkeletonIK::set_root_bone(const StringName &p_root_bone) {
|
||||
root_bone = p_root_bone;
|
||||
reload_chain();
|
||||
}
|
||||
|
||||
StringName SkeletonIK::get_root_bone() const {
|
||||
return root_bone;
|
||||
}
|
||||
|
||||
void SkeletonIK::set_tip_bone(const StringName &p_tip_bone) {
|
||||
tip_bone = p_tip_bone;
|
||||
reload_chain();
|
||||
}
|
||||
|
||||
StringName SkeletonIK::get_tip_bone() const {
|
||||
return tip_bone;
|
||||
}
|
||||
|
||||
void SkeletonIK::set_interpolation(real_t p_interpolation) {
|
||||
interpolation = p_interpolation;
|
||||
}
|
||||
|
||||
real_t SkeletonIK::get_interpolation() const {
|
||||
return interpolation;
|
||||
}
|
||||
|
||||
void SkeletonIK::set_target_transform(const Transform &p_target) {
|
||||
target = p_target;
|
||||
reload_goal();
|
||||
}
|
||||
|
||||
const Transform &SkeletonIK::get_target_transform() const {
|
||||
return target;
|
||||
}
|
||||
|
||||
void SkeletonIK::set_target_node(const NodePath &p_node) {
|
||||
target_node_path_override = p_node;
|
||||
target_node_override = nullptr;
|
||||
reload_goal();
|
||||
}
|
||||
|
||||
NodePath SkeletonIK::get_target_node() {
|
||||
return target_node_path_override;
|
||||
}
|
||||
|
||||
void SkeletonIK::set_override_tip_basis(bool p_override) {
|
||||
override_tip_basis = p_override;
|
||||
}
|
||||
|
||||
bool SkeletonIK::is_override_tip_basis() const {
|
||||
return override_tip_basis;
|
||||
}
|
||||
|
||||
void SkeletonIK::set_use_magnet(bool p_use) {
|
||||
use_magnet = p_use;
|
||||
}
|
||||
|
||||
bool SkeletonIK::is_using_magnet() const {
|
||||
return use_magnet;
|
||||
}
|
||||
|
||||
void SkeletonIK::set_magnet_position(const Vector3 &p_local_position) {
|
||||
magnet_position = p_local_position;
|
||||
}
|
||||
|
||||
const Vector3 &SkeletonIK::get_magnet_position() const {
|
||||
return magnet_position;
|
||||
}
|
||||
|
||||
void SkeletonIK::set_min_distance(real_t p_min_distance) {
|
||||
min_distance = p_min_distance;
|
||||
}
|
||||
|
||||
void SkeletonIK::set_max_iterations(int p_iterations) {
|
||||
max_iterations = p_iterations;
|
||||
}
|
||||
|
||||
bool SkeletonIK::is_running() {
|
||||
return is_processing_internal();
|
||||
}
|
||||
|
||||
void SkeletonIK::start(bool p_one_time) {
|
||||
if (p_one_time) {
|
||||
set_process_internal(false);
|
||||
|
||||
if (target_node_override) {
|
||||
reload_goal();
|
||||
}
|
||||
|
||||
_solve_chain();
|
||||
} else {
|
||||
set_process_internal(true);
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonIK::stop() {
|
||||
set_process_internal(false);
|
||||
if (skeleton) {
|
||||
skeleton->clear_bones_global_pose_override();
|
||||
}
|
||||
}
|
||||
|
||||
Transform SkeletonIK::_get_target_transform() {
|
||||
if (!target_node_override && !target_node_path_override.is_empty()) {
|
||||
target_node_override = Object::cast_to<Spatial>(get_node(target_node_path_override));
|
||||
}
|
||||
|
||||
if (target_node_override && target_node_override->is_inside_tree()) {
|
||||
// Make sure to use the interpolated transform as target.
|
||||
// This will pass through to get_global_transform() when physics interpolation is off, and when using interpolation,
|
||||
// ensure that the target matches the interpolated visual position of the target when updating the IK each frame.
|
||||
return target_node_override->get_global_transform_interpolated();
|
||||
} else {
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonIK::reload_chain() {
|
||||
FabrikInverseKinematic::free_task(task);
|
||||
task = nullptr;
|
||||
|
||||
if (!skeleton) {
|
||||
return;
|
||||
}
|
||||
|
||||
task = FabrikInverseKinematic::create_simple_task(skeleton, skeleton->find_bone(root_bone), skeleton->find_bone(tip_bone), _get_target_transform());
|
||||
if (task) {
|
||||
task->max_iterations = max_iterations;
|
||||
task->min_distance = min_distance;
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonIK::reload_goal() {
|
||||
if (!task) {
|
||||
return;
|
||||
}
|
||||
|
||||
FabrikInverseKinematic::set_goal(task, _get_target_transform());
|
||||
}
|
||||
|
||||
void SkeletonIK::_solve_chain() {
|
||||
if (!task) {
|
||||
return;
|
||||
}
|
||||
FabrikInverseKinematic::solve(task, interpolation, override_tip_basis, use_magnet, magnet_position);
|
||||
}
|
||||
|
||||
#endif // _3D_DISABLED
|
215
modules/skeleton_3d/nodes/skeleton_ik.h
Normal file
@ -0,0 +1,215 @@
|
||||
#ifndef SKELETON_IK_H
|
||||
#define SKELETON_IK_H
|
||||
/*************************************************************************/
|
||||
/* skeleton_ik.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#ifndef _3D_DISABLED
|
||||
|
||||
/**
|
||||
* @author AndreaCatania
|
||||
*/
|
||||
|
||||
#include "core/math/transform.h"
|
||||
#include "scene/main/node.h"
|
||||
|
||||
class Skeleton;
|
||||
|
||||
typedef int BoneId;
|
||||
|
||||
class FabrikInverseKinematic {
|
||||
struct EndEffector {
|
||||
BoneId tip_bone;
|
||||
Transform goal_transform;
|
||||
};
|
||||
|
||||
struct ChainItem {
|
||||
Vector<ChainItem> children;
|
||||
ChainItem *parent_item;
|
||||
|
||||
// Bone info
|
||||
BoneId bone;
|
||||
|
||||
real_t length;
|
||||
/// Positions relative to root bone
|
||||
Transform initial_transform;
|
||||
Vector3 current_pos;
|
||||
// Direction from this bone to child
|
||||
Vector3 current_ori;
|
||||
|
||||
ChainItem() :
|
||||
parent_item(nullptr),
|
||||
bone(-1),
|
||||
length(0) {}
|
||||
|
||||
ChainItem *find_child(const BoneId p_bone_id);
|
||||
ChainItem *add_child(const BoneId p_bone_id);
|
||||
};
|
||||
|
||||
struct ChainTip {
|
||||
ChainItem *chain_item;
|
||||
const EndEffector *end_effector;
|
||||
|
||||
ChainTip() :
|
||||
chain_item(nullptr),
|
||||
end_effector(nullptr) {}
|
||||
|
||||
ChainTip(ChainItem *p_chain_item, const EndEffector *p_end_effector) :
|
||||
chain_item(p_chain_item),
|
||||
end_effector(p_end_effector) {}
|
||||
};
|
||||
|
||||
struct Chain {
|
||||
ChainItem chain_root;
|
||||
ChainItem *middle_chain_item;
|
||||
Vector<ChainTip> tips;
|
||||
Vector3 magnet_position;
|
||||
};
|
||||
|
||||
public:
|
||||
struct Task : public RID_Data {
|
||||
RID self;
|
||||
Skeleton *skeleton;
|
||||
|
||||
Chain chain;
|
||||
|
||||
// Settings
|
||||
real_t min_distance;
|
||||
int max_iterations;
|
||||
|
||||
// Bone data
|
||||
BoneId root_bone;
|
||||
Vector<EndEffector> end_effectors;
|
||||
|
||||
Transform goal_global_transform;
|
||||
|
||||
Task() :
|
||||
skeleton(nullptr),
|
||||
min_distance(0.01),
|
||||
max_iterations(10),
|
||||
root_bone(-1) {}
|
||||
};
|
||||
|
||||
private:
|
||||
/// Init a chain that starts from the root to tip
|
||||
static bool build_chain(Task *p_task, bool p_force_simple_chain = true);
|
||||
|
||||
static void solve_simple(Task *p_task, bool p_solve_magnet, Vector3 p_origin_pos);
|
||||
/// Special solvers that solve only chains with one end effector
|
||||
static void solve_simple_backwards(Chain &r_chain, bool p_solve_magnet);
|
||||
static void solve_simple_forwards(Chain &r_chain, bool p_solve_magnet, Vector3 p_origin_pos);
|
||||
|
||||
public:
|
||||
static Task *create_simple_task(Skeleton *p_sk, BoneId root_bone, BoneId tip_bone, const Transform &goal_transform);
|
||||
static void free_task(Task *p_task);
|
||||
// The goal of chain should be always in local space
|
||||
static void set_goal(Task *p_task, const Transform &p_goal);
|
||||
static void make_goal(Task *p_task, const Transform &p_inverse_transf, real_t blending_delta);
|
||||
static void solve(Task *p_task, real_t blending_delta, bool override_tip_basis, bool p_use_magnet, const Vector3 &p_magnet_position);
|
||||
|
||||
static void _update_chain(const Skeleton *p_skeleton, ChainItem *p_chain_item);
|
||||
};
|
||||
|
||||
class SkeletonIK : public Node {
|
||||
GDCLASS(SkeletonIK, Node);
|
||||
|
||||
StringName root_bone;
|
||||
StringName tip_bone;
|
||||
real_t interpolation;
|
||||
Transform target;
|
||||
NodePath target_node_path_override;
|
||||
bool override_tip_basis;
|
||||
bool use_magnet;
|
||||
Vector3 magnet_position;
|
||||
|
||||
real_t min_distance;
|
||||
int max_iterations;
|
||||
|
||||
Skeleton *skeleton;
|
||||
Spatial *target_node_override;
|
||||
FabrikInverseKinematic::Task *task;
|
||||
|
||||
protected:
|
||||
virtual void
|
||||
_validate_property(PropertyInfo &property) const;
|
||||
|
||||
static void _bind_methods();
|
||||
virtual void _notification(int p_what);
|
||||
|
||||
public:
|
||||
SkeletonIK();
|
||||
virtual ~SkeletonIK();
|
||||
|
||||
void set_root_bone(const StringName &p_root_bone);
|
||||
StringName get_root_bone() const;
|
||||
|
||||
void set_tip_bone(const StringName &p_tip_bone);
|
||||
StringName get_tip_bone() const;
|
||||
|
||||
void set_interpolation(real_t p_interpolation);
|
||||
real_t get_interpolation() const;
|
||||
|
||||
void set_target_transform(const Transform &p_target);
|
||||
const Transform &get_target_transform() const;
|
||||
|
||||
void set_target_node(const NodePath &p_node);
|
||||
NodePath get_target_node();
|
||||
|
||||
void set_override_tip_basis(bool p_override);
|
||||
bool is_override_tip_basis() const;
|
||||
|
||||
void set_use_magnet(bool p_use);
|
||||
bool is_using_magnet() const;
|
||||
|
||||
void set_magnet_position(const Vector3 &p_local_position);
|
||||
const Vector3 &get_magnet_position() const;
|
||||
|
||||
void set_min_distance(real_t p_min_distance);
|
||||
real_t get_min_distance() const { return min_distance; }
|
||||
|
||||
void set_max_iterations(int p_iterations);
|
||||
int get_max_iterations() const { return max_iterations; }
|
||||
|
||||
Skeleton *get_parent_skeleton() const { return skeleton; }
|
||||
|
||||
bool is_running();
|
||||
|
||||
void start(bool p_one_time = false);
|
||||
void stop();
|
||||
|
||||
private:
|
||||
Transform _get_target_transform();
|
||||
void reload_chain();
|
||||
void reload_goal();
|
||||
void _solve_chain();
|
||||
};
|
||||
|
||||
#endif // _3D_DISABLED
|
||||
|
||||
#endif // SKELETON_IK_H
|
59
modules/skeleton_3d/register_types.cpp
Normal file
@ -0,0 +1,59 @@
|
||||
|
||||
#include "register_types.h"
|
||||
|
||||
#include "nodes/skeleton.h"
|
||||
#include "resources/skin.h"
|
||||
|
||||
#include "nodes/bone_attachment.h"
|
||||
#include "nodes/skeleton_ik.h"
|
||||
|
||||
#include "nodes/physical_bone.h"
|
||||
|
||||
#include "resources/skeleton_modification_3d.h"
|
||||
#include "resources/skeleton_modification_3d_ccdik.h"
|
||||
#include "resources/skeleton_modification_3d_fabrik.h"
|
||||
#include "resources/skeleton_modification_3d_jiggle.h"
|
||||
#include "resources/skeleton_modification_3d_lookat.h"
|
||||
#include "resources/skeleton_modification_3d_stackholder.h"
|
||||
#include "resources/skeleton_modification_3d_twoboneik.h"
|
||||
#include "resources/skeleton_modification_stack_3d.h"
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
#include "editor/physical_bone_plugin.h"
|
||||
#include "editor/skeleton_editor_plugin.h"
|
||||
#include "editor/skeleton_ik_editor_plugin.h"
|
||||
#endif
|
||||
|
||||
void register_skeleton_3d_types(ModuleRegistrationLevel p_level) {
|
||||
if (p_level == MODULE_REGISTRATION_LEVEL_SCENE) {
|
||||
ClassDB::register_class<Skeleton>();
|
||||
ClassDB::register_class<Skin>();
|
||||
ClassDB::register_class<SkinReference>();
|
||||
|
||||
ClassDB::register_class<BoneAttachment>();
|
||||
|
||||
ClassDB::register_class<SkeletonIK>();
|
||||
|
||||
ClassDB::register_class<PhysicalBone>();
|
||||
|
||||
ClassDB::register_class<SkeletonModificationStack3D>();
|
||||
ClassDB::register_class<SkeletonModification3D>();
|
||||
ClassDB::register_class<SkeletonModification3DLookAt>();
|
||||
ClassDB::register_class<SkeletonModification3DCCDIK>();
|
||||
ClassDB::register_class<SkeletonModification3DFABRIK>();
|
||||
ClassDB::register_class<SkeletonModification3DJiggle>();
|
||||
ClassDB::register_class<SkeletonModification3DTwoBoneIK>();
|
||||
ClassDB::register_class<SkeletonModification3DStackHolder>();
|
||||
}
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (p_level == MODULE_REGISTRATION_LEVEL_EDITOR) {
|
||||
EditorPlugins::add_by_type<SkeletonEditorPlugin>();
|
||||
EditorPlugins::add_by_type<SkeletonIKEditorPlugin>();
|
||||
EditorPlugins::add_by_type<PhysicalBonePlugin>();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void unregister_skeleton_3d_types(ModuleRegistrationLevel p_level) {
|
||||
}
|
10
modules/skeleton_3d/register_types.h
Normal file
@ -0,0 +1,10 @@
|
||||
#ifndef SKELETON_3D_REGISTER_TYPES_H
|
||||
#define SKELETON_3D_REGISTER_TYPES_H
|
||||
|
||||
#include "modules/register_module_types.h"
|
||||
|
||||
|
||||
void register_skeleton_3d_types(ModuleRegistrationLevel p_level);
|
||||
void unregister_skeleton_3d_types(ModuleRegistrationLevel p_level);
|
||||
|
||||
#endif
|
169
modules/skeleton_3d/resources/skeleton_modification_3d.cpp
Normal file
@ -0,0 +1,169 @@
|
||||
/*************************************************************************/
|
||||
/* skeleton_modification_3d.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "skeleton_modification_3d.h"
|
||||
#include "../nodes/skeleton.h"
|
||||
|
||||
#include "skeleton_modification_stack_3d.h"
|
||||
|
||||
void SkeletonModification3D::set_enabled(bool p_enabled) {
|
||||
enabled = p_enabled;
|
||||
}
|
||||
|
||||
bool SkeletonModification3D::get_enabled() {
|
||||
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;
|
||||
}
|
||||
|
||||
// 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 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;
|
||||
}
|
||||
|
||||
if (p_condition && !execution_error_found) {
|
||||
ERR_PRINT(p_message);
|
||||
execution_error_found = true;
|
||||
}
|
||||
return p_condition;
|
||||
}
|
||||
|
||||
Ref<SkeletonModificationStack3D> SkeletonModification3D::get_modification_stack() {
|
||||
return stack;
|
||||
}
|
||||
|
||||
void SkeletonModification3D::set_is_setup(bool p_is_setup) {
|
||||
is_setup = p_is_setup;
|
||||
}
|
||||
|
||||
bool SkeletonModification3D::get_is_setup() const {
|
||||
return is_setup;
|
||||
}
|
||||
|
||||
void SkeletonModification3D::set_execution_mode(int p_mode) {
|
||||
execution_mode = p_mode;
|
||||
}
|
||||
|
||||
int SkeletonModification3D::get_execution_mode() const {
|
||||
return execution_mode;
|
||||
}
|
||||
|
||||
void SkeletonModification3D::execute(real_t p_delta) {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
call("_execute", p_delta);
|
||||
}
|
||||
|
||||
void SkeletonModification3D::setup_modification(Ref<SkeletonModificationStack3D> p_stack) {
|
||||
stack = p_stack.ptr();
|
||||
|
||||
if (stack) {
|
||||
is_setup = true;
|
||||
} else {
|
||||
WARN_PRINT("Could not setup modification with name " + this->get_name());
|
||||
}
|
||||
|
||||
call("_setup_modification", p_stack);
|
||||
}
|
||||
|
||||
void SkeletonModification3D::_execute(real_t p_delta) {
|
||||
}
|
||||
void SkeletonModification3D::_setup_modification(Ref<SkeletonModificationStack3D> p_stack) {
|
||||
}
|
||||
|
||||
void SkeletonModification3D::_bind_methods() {
|
||||
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("get_modification_stack"), &SkeletonModification3D::get_modification_stack);
|
||||
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);
|
||||
|
||||
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");
|
||||
|
||||
ClassDB::bind_method(D_METHOD("execute", "p_delta"), &SkeletonModification3D::execute);
|
||||
ClassDB::bind_method(D_METHOD("setup_modification", "stack"), &SkeletonModification3D::setup_modification);
|
||||
|
||||
BIND_VMETHOD(MethodInfo("_execute", PropertyInfo(Variant::OBJECT, "delta")));
|
||||
BIND_VMETHOD(MethodInfo("_setup_modification", PropertyInfo(Variant::OBJECT, "stack", PROPERTY_HINT_RESOURCE_TYPE, "SkeletonModificationStack3D")));
|
||||
ClassDB::bind_method(D_METHOD("_execute", "p_delta"), &SkeletonModification3D::_execute);
|
||||
ClassDB::bind_method(D_METHOD("_setup_modification", "stack"), &SkeletonModification3D::_setup_modification);
|
||||
}
|
||||
|
||||
SkeletonModification3D::SkeletonModification3D() {
|
||||
stack = nullptr;
|
||||
is_setup = false;
|
||||
|
||||
execution_mode = 0; // 0 = process
|
||||
|
||||
enabled = true;
|
||||
execution_error_found = false;
|
||||
}
|
79
modules/skeleton_3d/resources/skeleton_modification_3d.h
Normal file
@ -0,0 +1,79 @@
|
||||
/*************************************************************************/
|
||||
/* skeleton_modification_3d.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#ifndef SKELETON_MODIFICATION_3D_H
|
||||
#define SKELETON_MODIFICATION_3D_H
|
||||
|
||||
#include "core/object/reference.h"
|
||||
#include "../nodes/skeleton.h"
|
||||
|
||||
class SkeletonModificationStack3D;
|
||||
|
||||
class SkeletonModification3D : public Resource {
|
||||
GDCLASS(SkeletonModification3D, Resource);
|
||||
friend class Skeleton;
|
||||
friend class SkeletonModificationStack3D;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
SkeletonModificationStack3D *stack;
|
||||
int execution_mode; // 0 = process
|
||||
|
||||
bool enabled;
|
||||
bool is_setup;
|
||||
bool execution_error_found;
|
||||
|
||||
bool _print_execution_error(bool p_condition, String p_message);
|
||||
|
||||
public:
|
||||
real_t clamp_angle(real_t p_angle, real_t p_min_bound, real_t p_max_bound, bool p_invert);
|
||||
|
||||
void set_enabled(bool p_enabled);
|
||||
bool get_enabled();
|
||||
|
||||
void set_execution_mode(int p_mode);
|
||||
int get_execution_mode() const;
|
||||
|
||||
Ref<SkeletonModificationStack3D> get_modification_stack();
|
||||
|
||||
void set_is_setup(bool p_setup);
|
||||
bool get_is_setup() const;
|
||||
|
||||
void execute(real_t p_delta);
|
||||
void setup_modification(Ref<SkeletonModificationStack3D> p_stack);
|
||||
|
||||
virtual void _execute(real_t p_delta);
|
||||
virtual void _setup_modification(Ref<SkeletonModificationStack3D> p_stack);
|
||||
|
||||
SkeletonModification3D();
|
||||
};
|
||||
|
||||
#endif // SKELETON_MODIFICATION_3D_H
|
479
modules/skeleton_3d/resources/skeleton_modification_3d_ccdik.cpp
Normal file
@ -0,0 +1,479 @@
|
||||
/*************************************************************************/
|
||||
/* skeleton_modification_3d_ccdik.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "skeleton_modification_3d_ccdik.h"
|
||||
#include "../nodes/skeleton.h"
|
||||
#include "skeleton_modification_stack_3d.h"
|
||||
|
||||
bool SkeletonModification3DCCDIK::_set(const StringName &p_path, const Variant &p_value) {
|
||||
String path = p_path;
|
||||
|
||||
if (path.begins_with("joint_data/")) {
|
||||
int ccdik_data_size = ccdik_data_chain.size();
|
||||
int which = path.get_slicec('/', 1).to_int();
|
||||
String what = path.get_slicec('/', 2);
|
||||
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);
|
||||
} else if (what == "ccdik_axis") {
|
||||
set_ccdik_joint_ccdik_axis(which, p_value);
|
||||
} else if (what == "enable_joint_constraint") {
|
||||
set_ccdik_joint_enable_constraint(which, p_value);
|
||||
} else if (what == "joint_constraint_angle_min") {
|
||||
set_ccdik_joint_constraint_angle_min(which, Math::deg2rad(real_t(p_value)));
|
||||
} else if (what == "joint_constraint_angle_max") {
|
||||
set_ccdik_joint_constraint_angle_max(which, Math::deg2rad(real_t(p_value)));
|
||||
} else if (what == "joint_constraint_angles_invert") {
|
||||
set_ccdik_joint_constraint_invert(which, p_value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SkeletonModification3DCCDIK::_get(const StringName &p_path, Variant &r_ret) const {
|
||||
String path = p_path;
|
||||
|
||||
if (path.begins_with("joint_data/")) {
|
||||
const int ccdik_data_size = ccdik_data_chain.size();
|
||||
int which = path.get_slicec('/', 1).to_int();
|
||||
String what = path.get_slicec('/', 2);
|
||||
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);
|
||||
} else if (what == "ccdik_axis") {
|
||||
r_ret = get_ccdik_joint_ccdik_axis(which);
|
||||
} else if (what == "enable_joint_constraint") {
|
||||
r_ret = get_ccdik_joint_enable_constraint(which);
|
||||
} else if (what == "joint_constraint_angle_min") {
|
||||
r_ret = Math::rad2deg(get_ccdik_joint_constraint_angle_min(which));
|
||||
} else if (what == "joint_constraint_angle_max") {
|
||||
r_ret = Math::rad2deg(get_ccdik_joint_constraint_angle_max(which));
|
||||
} else if (what == "joint_constraint_angles_invert") {
|
||||
r_ret = get_ccdik_joint_constraint_invert(which);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void SkeletonModification3DCCDIK::_get_property_list(List<PropertyInfo> *p_list) const {
|
||||
for (uint32_t i = 0; i < ccdik_data_chain.size(); i++) {
|
||||
String base_string = "joint_data/" + 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::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::REAL, base_string + "joint_constraint_angle_min", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
|
||||
p_list->push_back(PropertyInfo(Variant::REAL, base_string + "joint_constraint_angle_max", PROPERTY_HINT_NONE, "", 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(real_t p_delta) {
|
||||
ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr, "Modification is not setup and therefore cannot execute!");
|
||||
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (target_node_cache== 0) {
|
||||
_print_execution_error(true, "Target cache is out of date. Attempting to update");
|
||||
update_target_cache();
|
||||
return;
|
||||
}
|
||||
if (tip_node_cache == 0) {
|
||||
_print_execution_error(true, "Tip cache is out of date. Attempting to update");
|
||||
update_tip_cache();
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset the local bone overrides for CCDIK affected nodes
|
||||
for (uint32_t i = 0; i < ccdik_data_chain.size(); i++) {
|
||||
stack->skeleton->set_bone_local_pose_override(ccdik_data_chain[i].bone_idx,
|
||||
stack->skeleton->get_bone_local_pose_override(ccdik_data_chain[i].bone_idx),
|
||||
0.0, false);
|
||||
}
|
||||
|
||||
Spatial *node_target = Object::cast_to<Spatial>(ObjectDB::get_instance(target_node_cache));
|
||||
Spatial *node_tip = Object::cast_to<Spatial>(ObjectDB::get_instance(tip_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;
|
||||
}
|
||||
if (_print_execution_error(!node_tip || !node_tip->is_inside_tree(), "Tip node is not in the scene tree. Cannot execute modification!")) {
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
execution_error_found = false;
|
||||
}
|
||||
|
||||
void SkeletonModification3DCCDIK::_execute_ccdik_joint(int p_joint_idx, Spatial *p_target, Spatial *p_tip) {
|
||||
CCDIK_Joint_Data ccdik_data = ccdik_data_chain[p_joint_idx];
|
||||
|
||||
if (_print_execution_error(ccdik_data.bone_idx < 0 || ccdik_data.bone_idx > stack->skeleton->get_bone_count(),
|
||||
"CCDIK joint: bone index for joint" + itos(p_joint_idx) + " not found. Cannot execute modification!")) {
|
||||
return;
|
||||
}
|
||||
|
||||
Transform bone_trans = stack->skeleton->global_pose_to_local_pose(ccdik_data.bone_idx, stack->skeleton->get_bone_global_pose(ccdik_data.bone_idx));
|
||||
Transform tip_trans = stack->skeleton->global_pose_to_local_pose(ccdik_data.bone_idx, stack->skeleton->world_transform_to_global_pose(p_tip->get_global_transform()));
|
||||
Transform target_trans = stack->skeleton->global_pose_to_local_pose(ccdik_data.bone_idx, stack->skeleton->world_transform_to_global_pose(p_target->get_global_transform()));
|
||||
|
||||
if (tip_trans.origin.distance_to(target_trans.origin) <= 0.01) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Inspired (and very loosely based on) by the CCDIK algorithm made by Zalo on GitHub (https://github.com/zalo/MathUtilities)
|
||||
// Convert the 3D position to a 2D position so we can use Atan2 (via the angle function)
|
||||
// to know how much rotation we need on the given axis to place the tip at the target.
|
||||
Vector2 tip_pos_2d;
|
||||
Vector2 target_pos_2d;
|
||||
if (ccdik_data.ccdik_axis == CCDIK_Axes::AXIS_X) {
|
||||
tip_pos_2d = Vector2(tip_trans.origin.y, tip_trans.origin.z);
|
||||
target_pos_2d = Vector2(target_trans.origin.y, target_trans.origin.z);
|
||||
bone_trans.basis.rotate_local(Vector3(1, 0, 0), target_pos_2d.angle() - tip_pos_2d.angle());
|
||||
} else if (ccdik_data.ccdik_axis == CCDIK_Axes::AXIS_Y) {
|
||||
tip_pos_2d = Vector2(tip_trans.origin.z, tip_trans.origin.x);
|
||||
target_pos_2d = Vector2(target_trans.origin.z, target_trans.origin.x);
|
||||
bone_trans.basis.rotate_local(Vector3(0, 1, 0), target_pos_2d.angle() - tip_pos_2d.angle());
|
||||
} else if (ccdik_data.ccdik_axis == CCDIK_Axes::AXIS_Z) {
|
||||
tip_pos_2d = Vector2(tip_trans.origin.x, tip_trans.origin.y);
|
||||
target_pos_2d = Vector2(target_trans.origin.x, target_trans.origin.y);
|
||||
bone_trans.basis.rotate_local(Vector3(0, 0, 1), target_pos_2d.angle() - tip_pos_2d.angle());
|
||||
} else {
|
||||
// Should never happen, but...
|
||||
ERR_FAIL_MSG("CCDIK joint: Unknown axis vector passed for joint" + itos(p_joint_idx) + ". Cannot execute modification!");
|
||||
}
|
||||
|
||||
if (ccdik_data.enable_constraint) {
|
||||
Vector3 rotation_axis;
|
||||
real_t rotation_angle;
|
||||
bone_trans.basis.get_axis_angle(rotation_axis, rotation_angle);
|
||||
|
||||
// Note: When the axis has a negative direction, the angle is OVER 180 degrees and therefore we need to account for this
|
||||
// when constraining.
|
||||
if (ccdik_data.ccdik_axis == CCDIK_Axes::AXIS_X) {
|
||||
if (rotation_axis.x < 0) {
|
||||
rotation_angle += Math_PI;
|
||||
rotation_axis = Vector3(1, 0, 0);
|
||||
}
|
||||
} else if (ccdik_data.ccdik_axis == CCDIK_Axes::AXIS_Y) {
|
||||
if (rotation_axis.y < 0) {
|
||||
rotation_angle += Math_PI;
|
||||
rotation_axis = Vector3(0, 1, 0);
|
||||
}
|
||||
} else if (ccdik_data.ccdik_axis == CCDIK_Axes::AXIS_Z) {
|
||||
if (rotation_axis.z < 0) {
|
||||
rotation_angle += Math_PI;
|
||||
rotation_axis = Vector3(0, 0, 1);
|
||||
}
|
||||
} else {
|
||||
// Should never happen, but...
|
||||
ERR_FAIL_MSG("CCDIK joint: Unknown axis vector passed for joint" + itos(p_joint_idx) + ". Cannot execute modification!");
|
||||
}
|
||||
rotation_angle = clamp_angle(rotation_angle, ccdik_data.constraint_angle_min, ccdik_data.constraint_angle_max, ccdik_data.constraint_angles_invert);
|
||||
|
||||
bone_trans.basis.set_axis_angle(rotation_axis, rotation_angle);
|
||||
}
|
||||
|
||||
stack->skeleton->set_bone_local_pose_override(ccdik_data.bone_idx, bone_trans, stack->strength, true);
|
||||
stack->skeleton->force_update_bone_children_transforms(ccdik_data.bone_idx);
|
||||
}
|
||||
|
||||
void SkeletonModification3DCCDIK::_setup_modification(Ref<SkeletonModificationStack3D> p_stack) {
|
||||
stack = p_stack.ptr();
|
||||
|
||||
if (stack != nullptr) {
|
||||
is_setup = true;
|
||||
execution_error_found = false;
|
||||
update_target_cache();
|
||||
update_tip_cache();
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModification3DCCDIK::update_target_cache() {
|
||||
if (!is_setup || !stack) {
|
||||
_print_execution_error(true, "Cannot update target cache: modification is not properly setup!");
|
||||
return;
|
||||
}
|
||||
|
||||
target_node_cache = ObjectID();
|
||||
if (stack->skeleton) {
|
||||
if (stack->skeleton->is_inside_tree()) {
|
||||
if (stack->skeleton->has_node(target_node)) {
|
||||
Node *node = stack->skeleton->get_node(target_node);
|
||||
ERR_FAIL_COND_MSG(!node || stack->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::update_tip_cache() {
|
||||
if (!is_setup || !stack) {
|
||||
_print_execution_error(true, "Cannot update tip cache: modification is not properly setup!");
|
||||
return;
|
||||
}
|
||||
|
||||
tip_node_cache = ObjectID();
|
||||
if (stack->skeleton) {
|
||||
if (stack->skeleton->is_inside_tree()) {
|
||||
if (stack->skeleton->has_node(tip_node)) {
|
||||
Node *node = stack->skeleton->get_node(tip_node);
|
||||
ERR_FAIL_COND_MSG(!node || stack->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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModification3DCCDIK::set_target_node(const NodePath &p_target_node) {
|
||||
target_node = p_target_node;
|
||||
update_target_cache();
|
||||
}
|
||||
|
||||
NodePath SkeletonModification3DCCDIK::get_target_node() const {
|
||||
return target_node;
|
||||
}
|
||||
|
||||
void SkeletonModification3DCCDIK::set_tip_node(const NodePath &p_tip_node) {
|
||||
tip_node = p_tip_node;
|
||||
update_tip_cache();
|
||||
}
|
||||
|
||||
NodePath SkeletonModification3DCCDIK::get_tip_node() const {
|
||||
return tip_node;
|
||||
}
|
||||
|
||||
void SkeletonModification3DCCDIK::set_use_high_quality_solve(bool p_high_quality) {
|
||||
use_high_quality_solve = p_high_quality;
|
||||
}
|
||||
|
||||
bool SkeletonModification3DCCDIK::get_use_high_quality_solve() const {
|
||||
return use_high_quality_solve;
|
||||
}
|
||||
|
||||
// CCDIK joint data functions
|
||||
String SkeletonModification3DCCDIK::get_ccdik_joint_bone_name(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) {
|
||||
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 (stack) {
|
||||
if (stack->skeleton) {
|
||||
ccdik_data_chain[p_joint_idx].bone_idx = stack->skeleton->find_bone(p_bone_name);
|
||||
}
|
||||
}
|
||||
execution_error_found = false;
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
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 (stack) {
|
||||
if (stack->skeleton) {
|
||||
ccdik_data_chain[p_joint_idx].bone_name = stack->skeleton->get_bone_name(p_bone_idx);
|
||||
}
|
||||
}
|
||||
execution_error_found = false;
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
int SkeletonModification3DCCDIK::get_ccdik_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) {
|
||||
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!");
|
||||
ccdik_data_chain[p_joint_idx].ccdik_axis = p_axis;
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
bool SkeletonModification3DCCDIK::get_ccdik_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) {
|
||||
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;
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
real_t SkeletonModification3DCCDIK::get_ccdik_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) {
|
||||
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 {
|
||||
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) {
|
||||
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 {
|
||||
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) {
|
||||
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() {
|
||||
return ccdik_data_chain.size();
|
||||
}
|
||||
void SkeletonModification3DCCDIK::set_ccdik_data_chain_length(int p_length) {
|
||||
ERR_FAIL_COND(p_length < 0);
|
||||
ccdik_data_chain.resize(p_length);
|
||||
execution_error_found = false;
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
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_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_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, "Spatial"), "set_target_node", "get_target_node");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "tip_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Spatial"), "set_tip_node", "get_tip_node");
|
||||
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");
|
||||
}
|
||||
|
||||
SkeletonModification3DCCDIK::SkeletonModification3DCCDIK() {
|
||||
stack = nullptr;
|
||||
is_setup = false;
|
||||
enabled = true;
|
||||
|
||||
target_node_cache = 0;
|
||||
tip_node_cache = 0;
|
||||
use_high_quality_solve = true;
|
||||
}
|
||||
|
||||
SkeletonModification3DCCDIK::~SkeletonModification3DCCDIK() {
|
||||
}
|
125
modules/skeleton_3d/resources/skeleton_modification_3d_ccdik.h
Normal file
@ -0,0 +1,125 @@
|
||||
|
||||
#ifndef SKELETON_MODIFICATION_3D_CCDIK_H
|
||||
#define SKELETON_MODIFICATION_3D_CCDIK_H
|
||||
|
||||
/*************************************************************************/
|
||||
/* skeleton_modification_3d_ccdik.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "core/containers/local_vector.h"
|
||||
#include "../nodes/skeleton.h"
|
||||
#include "skeleton_modification_3d.h"
|
||||
|
||||
class SkeletonModification3DCCDIK : public SkeletonModification3D {
|
||||
GDCLASS(SkeletonModification3DCCDIK, SkeletonModification3D);
|
||||
|
||||
private:
|
||||
enum CCDIK_Axes {
|
||||
AXIS_X,
|
||||
AXIS_Y,
|
||||
AXIS_Z
|
||||
};
|
||||
|
||||
struct CCDIK_Joint_Data {
|
||||
String bone_name;
|
||||
int bone_idx;
|
||||
int ccdik_axis;
|
||||
|
||||
bool enable_constraint;
|
||||
real_t constraint_angle_min;
|
||||
real_t constraint_angle_max;
|
||||
bool constraint_angles_invert;
|
||||
|
||||
CCDIK_Joint_Data() {
|
||||
bone_idx = -1;
|
||||
ccdik_axis = 0;
|
||||
|
||||
enable_constraint = false;
|
||||
constraint_angle_min = 0;
|
||||
constraint_angle_max = (2.0 * Math_PI);
|
||||
constraint_angles_invert = false;
|
||||
}
|
||||
};
|
||||
|
||||
LocalVector<CCDIK_Joint_Data> ccdik_data_chain;
|
||||
NodePath target_node;
|
||||
ObjectID target_node_cache;
|
||||
|
||||
NodePath tip_node;
|
||||
ObjectID tip_node_cache;
|
||||
|
||||
bool use_high_quality_solve;
|
||||
|
||||
void update_target_cache();
|
||||
void update_tip_cache();
|
||||
|
||||
void _execute_ccdik_joint(int p_joint_idx, Spatial *p_target, Spatial *p_tip);
|
||||
|
||||
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<PropertyInfo> *p_list) const;
|
||||
|
||||
public:
|
||||
virtual void _execute(real_t p_delta);
|
||||
virtual void _setup_modification(Ref<SkeletonModificationStack3D> p_stack);
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
SkeletonModification3DCCDIK();
|
||||
~SkeletonModification3DCCDIK();
|
||||
};
|
||||
|
||||
#endif // SKELETON_MODIFICATION_3D_CCDIK_H
|
@ -0,0 +1,638 @@
|
||||
/*************************************************************************/
|
||||
/* skeleton_modification_3d_fabrik.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "skeleton_modification_3d_fabrik.h"
|
||||
#include "../nodes/skeleton.h"
|
||||
#include "skeleton_modification_stack_3d.h"
|
||||
|
||||
bool SkeletonModification3DFABRIK::_set(const StringName &p_path, const Variant &p_value) {
|
||||
String path = p_path;
|
||||
|
||||
if (path.begins_with("joint_data/")) {
|
||||
int fabrik_data_size = fabrik_data_chain.size();
|
||||
int which = path.get_slicec('/', 1).to_int();
|
||||
String what = path.get_slicec('/', 2);
|
||||
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);
|
||||
} else if (what == "length") {
|
||||
set_fabrik_joint_length(which, p_value);
|
||||
} else if (what == "magnet_position") {
|
||||
set_fabrik_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);
|
||||
} else if (what == "tip_node") {
|
||||
set_fabrik_joint_tip_node(which, p_value);
|
||||
} else if (what == "use_target_basis") {
|
||||
set_fabrik_joint_use_target_basis(which, p_value);
|
||||
} else if (what == "roll") {
|
||||
set_fabrik_joint_roll(which, Math::deg2rad(real_t(p_value)));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SkeletonModification3DFABRIK::_get(const StringName &p_path, Variant &r_ret) const {
|
||||
String path = p_path;
|
||||
|
||||
if (path.begins_with("joint_data/")) {
|
||||
const int fabrik_data_size = fabrik_data_chain.size();
|
||||
int which = path.get_slicec('/', 1).to_int();
|
||||
String what = path.get_slicec('/', 2);
|
||||
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);
|
||||
} else if (what == "length") {
|
||||
r_ret = get_fabrik_joint_length(which);
|
||||
} else if (what == "magnet_position") {
|
||||
r_ret = get_fabrik_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);
|
||||
} else if (what == "tip_node") {
|
||||
r_ret = get_fabrik_joint_tip_node(which);
|
||||
} else if (what == "use_target_basis") {
|
||||
r_ret = get_fabrik_joint_use_target_basis(which);
|
||||
} else if (what == "roll") {
|
||||
r_ret = Math::rad2deg(get_fabrik_joint_roll(which));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void SkeletonModification3DFABRIK::_get_property_list(List<PropertyInfo> *p_list) const {
|
||||
for (uint32_t i = 0; i < fabrik_data_chain.size(); i++) {
|
||||
String base_string = "joint_data/" + 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::REAL, base_string + "roll", PROPERTY_HINT_RANGE, "-360,360,0.01", 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::REAL, 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, "Spatial", PROPERTY_USAGE_DEFAULT));
|
||||
}
|
||||
}
|
||||
|
||||
// Cannot apply magnet to the origin of the chain, as it will not do anything.
|
||||
if (i > 0) {
|
||||
p_list->push_back(PropertyInfo(Variant::VECTOR3, base_string + "magnet_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
|
||||
}
|
||||
// Only give the override basis option on the last bone in the chain, so only include it for the last bone.
|
||||
if (i == fabrik_data_chain.size() - 1) {
|
||||
p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "use_target_basis", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModification3DFABRIK::_execute(real_t p_delta) {
|
||||
ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr,
|
||||
"Modification is not setup and therefore cannot execute!");
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (target_node_cache == 0) {
|
||||
_print_execution_error(true, "Target cache is out of date. Attempting to update...");
|
||||
update_target_cache();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_print_execution_error(fabrik_data_chain.size() <= 1, "FABRIK requires at least two joints to operate. Cannot execute modification!")) {
|
||||
return;
|
||||
}
|
||||
|
||||
Spatial *node_target = Object::cast_to<Spatial>(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;
|
||||
}
|
||||
|
||||
// Make sure the transform cache is the correct size
|
||||
if (fabrik_transforms.size() != fabrik_data_chain.size()) {
|
||||
fabrik_transforms.resize(fabrik_data_chain.size());
|
||||
}
|
||||
|
||||
// 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] = stack->skeleton->get_bone_global_pose(fabrik_data_chain[i].bone_idx);
|
||||
|
||||
// Apply magnet positions:
|
||||
if (stack->skeleton->get_bone_parent(fabrik_data_chain[i].bone_idx) >= 0) {
|
||||
int parent_bone_idx = stack->skeleton->get_bone_parent(fabrik_data_chain[i].bone_idx);
|
||||
Transform conversion_transform = (stack->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;
|
||||
}
|
||||
}
|
||||
Transform origin_global_pose_trans = stack->skeleton->get_bone_global_pose_no_override(fabrik_data_chain[0].bone_idx);
|
||||
|
||||
target_global_pose = stack->skeleton->world_transform_to_global_pose(node_target->get_global_transform());
|
||||
origin_global_pose = origin_global_pose_trans;
|
||||
|
||||
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;
|
||||
|
||||
while (target_distance > chain_tolerance) {
|
||||
chain_backwards();
|
||||
chain_forwards();
|
||||
|
||||
// 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();
|
||||
|
||||
execution_error_found = false;
|
||||
}
|
||||
|
||||
void SkeletonModification3DFABRIK::chain_backwards() {
|
||||
int final_bone_idx = fabrik_data_chain[final_joint_idx].bone_idx;
|
||||
Transform final_joint_trans = fabrik_transforms[final_joint_idx];
|
||||
|
||||
// Get the direction the final bone is facing in.
|
||||
stack->skeleton->update_bone_rest_forward_vector(final_bone_idx);
|
||||
Transform final_bone_direction_trans = final_joint_trans.looking_at(target_global_pose.origin, Vector3(0, 1, 0));
|
||||
final_bone_direction_trans.basis = stack->skeleton->global_pose_z_forward_to_bone_forward(final_bone_idx, final_bone_direction_trans.basis);
|
||||
Vector3 direction = final_bone_direction_trans.basis.xform(stack->skeleton->get_bone_axis_forward_vector(final_bone_idx)).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(stack->skeleton->get_bone_axis_forward_vector(final_bone_idx)).normalized();
|
||||
}
|
||||
|
||||
// set the position of the final joint to the target position
|
||||
final_joint_trans.origin = target_global_pose.origin - (direction * fabrik_data_chain[final_joint_idx].length);
|
||||
fabrik_transforms[final_joint_idx] = final_joint_trans;
|
||||
|
||||
// for all other joints, move them towards the target
|
||||
int i = final_joint_idx;
|
||||
while (i >= 1) {
|
||||
Transform next_bone_trans = fabrik_transforms[i];
|
||||
i -= 1;
|
||||
Transform current_trans = fabrik_transforms[i];
|
||||
|
||||
real_t length = fabrik_data_chain[i].length / (current_trans.origin.distance_to(next_bone_trans.origin));
|
||||
current_trans.origin = next_bone_trans.origin.linear_interpolate(current_trans.origin, length);
|
||||
|
||||
// Save the result
|
||||
fabrik_transforms[i] = current_trans;
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModification3DFABRIK::chain_forwards() {
|
||||
// Set root at the initial position.
|
||||
Transform root_transform = fabrik_transforms[0];
|
||||
|
||||
root_transform.origin = origin_global_pose.origin;
|
||||
fabrik_transforms[0] = origin_global_pose;
|
||||
|
||||
for (uint32_t i = 0; i < fabrik_data_chain.size() - 1; i++) {
|
||||
Transform current_trans = fabrik_transforms[i];
|
||||
Transform next_bone_trans = fabrik_transforms[i + 1];
|
||||
|
||||
real_t length = fabrik_data_chain[i].length / (next_bone_trans.origin.distance_to(current_trans.origin));
|
||||
next_bone_trans.origin = current_trans.origin.linear_interpolate(next_bone_trans.origin, length);
|
||||
|
||||
// Save the result
|
||||
fabrik_transforms[i + 1] = next_bone_trans;
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModification3DFABRIK::chain_apply() {
|
||||
for (uint32_t i = 0; i < fabrik_data_chain.size(); i++) {
|
||||
int current_bone_idx = fabrik_data_chain[i].bone_idx;
|
||||
Transform current_trans = fabrik_transforms[i];
|
||||
Transform current_bone_trans = stack->skeleton->get_bone_global_rest(current_bone_idx);
|
||||
|
||||
// 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.
|
||||
stack->skeleton->update_bone_rest_forward_vector(current_bone_idx);
|
||||
Vector3 forward_vector = stack->skeleton->get_bone_axis_forward_vector(current_bone_idx);
|
||||
// Rotate the bone towards the target:
|
||||
current_bone_trans.basis.rotate_to_align(forward_vector, current_trans.origin.direction_to(target_global_pose.origin));
|
||||
current_bone_trans.basis.rotate_local(forward_vector, fabrik_data_chain[i].roll);
|
||||
} else { // Use the target's Basis...
|
||||
current_bone_trans.basis = target_global_pose.basis.orthonormalized().scaled(current_trans.basis.get_scale());
|
||||
}
|
||||
} else { // every other bone in the chain...
|
||||
Transform next_trans = fabrik_transforms[i + 1];
|
||||
|
||||
// Get the forward direction that the basis is facing in right now.
|
||||
stack->skeleton->update_bone_rest_forward_vector(current_bone_idx);
|
||||
Vector3 forward_vector = stack->skeleton->get_bone_axis_forward_vector(current_bone_idx);
|
||||
// Rotate the bone towards the next bone in the chain:
|
||||
current_bone_trans.basis.rotate_to_align(forward_vector, current_trans.origin.direction_to(next_trans.origin));
|
||||
current_bone_trans.basis.rotate_local(forward_vector, fabrik_data_chain[i].roll);
|
||||
}
|
||||
current_bone_trans.origin = current_trans.origin;
|
||||
stack->skeleton->set_bone_local_pose_override(current_bone_idx, stack->skeleton->global_pose_to_local_pose(current_bone_idx, current_bone_trans), stack->strength, true);
|
||||
}
|
||||
|
||||
// Update all the bones so the next modification has up-to-date data.
|
||||
stack->skeleton->force_update_all_bone_transforms();
|
||||
}
|
||||
|
||||
void SkeletonModification3DFABRIK::_setup_modification(Ref<SkeletonModificationStack3D> p_stack) {
|
||||
stack = p_stack.ptr();
|
||||
if (stack != nullptr) {
|
||||
is_setup = true;
|
||||
execution_error_found = false;
|
||||
update_target_cache();
|
||||
|
||||
for (uint32_t i = 0; i < fabrik_data_chain.size(); i++) {
|
||||
update_joint_tip_cache(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModification3DFABRIK::update_target_cache() {
|
||||
if (!is_setup || !stack) {
|
||||
_print_execution_error(true, "Cannot update target cache: modification is not properly setup!");
|
||||
return;
|
||||
}
|
||||
target_node_cache = ObjectID();
|
||||
if (stack->skeleton) {
|
||||
if (stack->skeleton->is_inside_tree() && target_node.is_empty() == false) {
|
||||
if (stack->skeleton->has_node(target_node)) {
|
||||
Node *node = stack->skeleton->get_node(target_node);
|
||||
ERR_FAIL_COND_MSG(!node || stack->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 || !stack) {
|
||||
_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 (stack->skeleton) {
|
||||
if (stack->skeleton->is_inside_tree() && fabrik_data_chain[p_joint_idx].tip_node.is_empty() == false) {
|
||||
if (stack->skeleton->has_node(fabrik_data_chain[p_joint_idx].tip_node)) {
|
||||
Node *node = stack->skeleton->get_node(fabrik_data_chain[p_joint_idx].tip_node);
|
||||
ERR_FAIL_COND_MSG(!node || stack->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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModification3DFABRIK::set_target_node(const NodePath &p_target_node) {
|
||||
target_node = p_target_node;
|
||||
update_target_cache();
|
||||
}
|
||||
|
||||
NodePath SkeletonModification3DFABRIK::get_target_node() const {
|
||||
return target_node;
|
||||
}
|
||||
|
||||
int SkeletonModification3DFABRIK::get_fabrik_data_chain_length() {
|
||||
return fabrik_data_chain.size();
|
||||
}
|
||||
|
||||
void SkeletonModification3DFABRIK::set_fabrik_data_chain_length(int p_length) {
|
||||
ERR_FAIL_COND(p_length < 0);
|
||||
fabrik_data_chain.resize(p_length);
|
||||
fabrik_transforms.resize(p_length);
|
||||
execution_error_found = false;
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
real_t SkeletonModification3DFABRIK::get_chain_tolerance() {
|
||||
return chain_tolerance;
|
||||
}
|
||||
|
||||
void SkeletonModification3DFABRIK::set_chain_tolerance(real_t p_tolerance) {
|
||||
ERR_FAIL_COND_MSG(p_tolerance <= 0, "FABRIK chain tolerance must be more than zero!");
|
||||
chain_tolerance = p_tolerance;
|
||||
}
|
||||
|
||||
int SkeletonModification3DFABRIK::get_chain_max_iterations() {
|
||||
return chain_max_iterations;
|
||||
}
|
||||
void SkeletonModification3DFABRIK::set_chain_max_iterations(int p_iterations) {
|
||||
ERR_FAIL_COND_MSG(p_iterations <= 0, "FABRIK chain iterations must be at least one. Set enabled to false to disable the FABRIK chain.");
|
||||
chain_max_iterations = p_iterations;
|
||||
}
|
||||
|
||||
// FABRIK joint data functions
|
||||
String SkeletonModification3DFABRIK::get_fabrik_joint_bone_name(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) {
|
||||
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 (stack) {
|
||||
if (stack->skeleton) {
|
||||
fabrik_data_chain[p_joint_idx].bone_idx = stack->skeleton->find_bone(p_bone_name);
|
||||
}
|
||||
}
|
||||
execution_error_found = false;
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
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 (stack) {
|
||||
if (stack->skeleton) {
|
||||
fabrik_data_chain[p_joint_idx].bone_name = stack->skeleton->get_bone_name(p_bone_idx);
|
||||
}
|
||||
}
|
||||
execution_error_found = false;
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
real_t SkeletonModification3DFABRIK::get_fabrik_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) {
|
||||
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;
|
||||
}
|
||||
|
||||
Vector3 SkeletonModification3DFABRIK::get_fabrik_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) {
|
||||
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 {
|
||||
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) {
|
||||
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);
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
void SkeletonModification3DFABRIK::fabrik_joint_auto_calculate_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 (!stack || !stack->skeleton || !is_setup) {
|
||||
_print_execution_error(true, "Cannot auto calculate joint length: modification is not properly setup!");
|
||||
return;
|
||||
}
|
||||
ERR_FAIL_INDEX_MSG(fabrik_data_chain[p_joint_idx].bone_idx, stack->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.
|
||||
|
||||
update_joint_tip_cache(p_joint_idx);
|
||||
|
||||
Spatial *tip_node = Object::cast_to<Spatial>(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 Spatial-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...");
|
||||
|
||||
Transform node_trans = tip_node->get_global_transform();
|
||||
node_trans = stack->skeleton->world_transform_to_global_pose(node_trans);
|
||||
//node_trans = stack->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();
|
||||
|
||||
fabrik_data_chain[p_joint_idx].length = stack->skeleton->get_bone_global_pose(fabrik_data_chain[p_joint_idx].bone_idx).origin.distance_to(node_trans.origin);
|
||||
|
||||
} else { // Use child bone(s) to update joint length, if possible
|
||||
Vector<int> bone_children = stack->skeleton->get_bone_children(fabrik_data_chain[p_joint_idx].bone_idx);
|
||||
if (bone_children.size() <= 0) {
|
||||
ERR_FAIL_MSG("Cannot calculate length for joint " + itos(p_joint_idx) + "joint uses leaf bone. \nPlease manually set the bone length or use a tip node!");
|
||||
return;
|
||||
}
|
||||
|
||||
Transform bone_trans = stack->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++) {
|
||||
Transform child_transform = stack->skeleton->get_bone_global_pose(bone_children[i]);
|
||||
final_length += bone_trans.origin.distance_to(child_transform.origin);
|
||||
//final_length += stack->skeleton->global_pose_to_local_pose(fabrik_data_chain[p_joint_idx].bone_idx, child_transform).origin.length();
|
||||
}
|
||||
fabrik_data_chain[p_joint_idx].length = final_length / bone_children.size();
|
||||
}
|
||||
execution_error_found = false;
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
bool SkeletonModification3DFABRIK::get_fabrik_joint_use_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;
|
||||
}
|
||||
|
||||
void SkeletonModification3DFABRIK::set_fabrik_joint_use_tip_node(int p_joint_idx, bool p_use_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;
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
NodePath SkeletonModification3DFABRIK::get_fabrik_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, NodePath());
|
||||
return fabrik_data_chain[p_joint_idx].tip_node;
|
||||
}
|
||||
|
||||
void SkeletonModification3DFABRIK::set_fabrik_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].tip_node = p_tip_node;
|
||||
update_joint_tip_cache(p_joint_idx);
|
||||
}
|
||||
|
||||
bool SkeletonModification3DFABRIK::get_fabrik_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) {
|
||||
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 {
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
|
||||
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_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, "Spatial"), "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::REAL, "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() {
|
||||
stack = nullptr;
|
||||
is_setup = false;
|
||||
enabled = true;
|
||||
|
||||
target_node_cache = 0;
|
||||
|
||||
chain_tolerance = 0.01;
|
||||
chain_max_iterations = 10;
|
||||
chain_iterations = 0;
|
||||
|
||||
final_joint_idx = 0;
|
||||
}
|
||||
|
||||
SkeletonModification3DFABRIK::~SkeletonModification3DFABRIK() {
|
||||
}
|
135
modules/skeleton_3d/resources/skeleton_modification_3d_fabrik.h
Normal file
@ -0,0 +1,135 @@
|
||||
#ifndef SKELETON_MODIFICATION_3D_FABRIK_H
|
||||
#define SKELETON_MODIFICATION_3D_FABRIK_H
|
||||
|
||||
/*************************************************************************/
|
||||
/* skeleton_modification_3d_fabrik.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "core/containers/local_vector.h"
|
||||
#include "skeleton_modification_3d.h"
|
||||
|
||||
class SkeletonModification3DFABRIK : public SkeletonModification3D {
|
||||
GDCLASS(SkeletonModification3DFABRIK, SkeletonModification3D);
|
||||
|
||||
private:
|
||||
struct FABRIK_Joint_Data {
|
||||
String bone_name;
|
||||
int bone_idx;
|
||||
real_t length;
|
||||
Vector3 magnet_position;
|
||||
|
||||
bool auto_calculate_length;
|
||||
bool use_tip_node;
|
||||
NodePath tip_node;
|
||||
ObjectID tip_node_cache;
|
||||
|
||||
bool use_target_basis;
|
||||
real_t roll;
|
||||
|
||||
FABRIK_Joint_Data() {
|
||||
bone_idx = -1;
|
||||
length = -1;
|
||||
|
||||
auto_calculate_length = true;
|
||||
use_tip_node = false;
|
||||
tip_node_cache = 0;
|
||||
|
||||
use_target_basis = false;
|
||||
roll = 0;
|
||||
}
|
||||
};
|
||||
|
||||
LocalVector<FABRIK_Joint_Data> fabrik_data_chain;
|
||||
LocalVector<Transform> fabrik_transforms;
|
||||
|
||||
NodePath target_node;
|
||||
ObjectID target_node_cache;
|
||||
|
||||
real_t chain_tolerance;
|
||||
int chain_max_iterations;
|
||||
int chain_iterations;
|
||||
|
||||
void update_target_cache();
|
||||
void update_joint_tip_cache(int p_joint_idx);
|
||||
|
||||
int final_joint_idx;
|
||||
Transform target_global_pose;
|
||||
Transform origin_global_pose;
|
||||
|
||||
void chain_backwards();
|
||||
void chain_forwards();
|
||||
void chain_apply();
|
||||
|
||||
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<PropertyInfo> *p_list) const;
|
||||
|
||||
public:
|
||||
virtual void _execute(real_t p_delta);
|
||||
virtual void _setup_modification(Ref<SkeletonModificationStack3D> p_stack);
|
||||
|
||||
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);
|
||||
|
||||
real_t get_chain_tolerance();
|
||||
void set_chain_tolerance(real_t p_tolerance);
|
||||
|
||||
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);
|
||||
|
||||
SkeletonModification3DFABRIK();
|
||||
~SkeletonModification3DFABRIK();
|
||||
};
|
||||
|
||||
#endif // SKELETON_MODIFICATION_3D_FABRIK_H
|
@ -0,0 +1,582 @@
|
||||
/*************************************************************************/
|
||||
/* skeleton_modification_3d_jiggle.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "skeleton_modification_3d_jiggle.h"
|
||||
#include "../nodes/skeleton.h"
|
||||
#include "skeleton_modification_stack_3d.h"
|
||||
#include "servers/physics_server.h"
|
||||
#include "scene/resources/world_3d.h"
|
||||
|
||||
bool SkeletonModification3DJiggle::_set(const StringName &p_path, const Variant &p_value) {
|
||||
String path = p_path;
|
||||
|
||||
if (path.begins_with("joint_data/")) {
|
||||
const int jiggle_size = jiggle_data_chain.size();
|
||||
int which = path.get_slicec('/', 1).to_int();
|
||||
String what = path.get_slicec('/', 2);
|
||||
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);
|
||||
} else if (what == "override_defaults") {
|
||||
set_jiggle_joint_override(which, p_value);
|
||||
} else if (what == "stiffness") {
|
||||
set_jiggle_joint_stiffness(which, p_value);
|
||||
} else if (what == "mass") {
|
||||
set_jiggle_joint_mass(which, p_value);
|
||||
} else if (what == "damping") {
|
||||
set_jiggle_joint_damping(which, p_value);
|
||||
} else if (what == "use_gravity") {
|
||||
set_jiggle_joint_use_gravity(which, p_value);
|
||||
} else if (what == "gravity") {
|
||||
set_jiggle_joint_gravity(which, p_value);
|
||||
} else if (what == "roll") {
|
||||
set_jiggle_joint_roll(which, Math::deg2rad(real_t(p_value)));
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
if (path == "use_colliders") {
|
||||
set_use_colliders(p_value);
|
||||
} else if (path == "collision_mask") {
|
||||
set_collision_mask(p_value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SkeletonModification3DJiggle::_get(const StringName &p_path, Variant &r_ret) const {
|
||||
String path = p_path;
|
||||
|
||||
if (path.begins_with("joint_data/")) {
|
||||
const int jiggle_size = jiggle_data_chain.size();
|
||||
int which = path.get_slicec('/', 1).to_int();
|
||||
String what = path.get_slicec('/', 2);
|
||||
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);
|
||||
} else if (what == "override_defaults") {
|
||||
r_ret = get_jiggle_joint_override(which);
|
||||
} else if (what == "stiffness") {
|
||||
r_ret = get_jiggle_joint_stiffness(which);
|
||||
} else if (what == "mass") {
|
||||
r_ret = get_jiggle_joint_mass(which);
|
||||
} else if (what == "damping") {
|
||||
r_ret = get_jiggle_joint_damping(which);
|
||||
} else if (what == "use_gravity") {
|
||||
r_ret = get_jiggle_joint_use_gravity(which);
|
||||
} else if (what == "gravity") {
|
||||
r_ret = get_jiggle_joint_gravity(which);
|
||||
} else if (what == "roll") {
|
||||
r_ret = Math::rad2deg(get_jiggle_joint_roll(which));
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
if (path == "use_colliders") {
|
||||
r_ret = get_use_colliders();
|
||||
} else if (path == "collision_mask") {
|
||||
r_ret = get_collision_mask();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void SkeletonModification3DJiggle::_get_property_list(List<PropertyInfo> *p_list) const {
|
||||
p_list->push_back(PropertyInfo(Variant::BOOL, "use_colliders", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
|
||||
if (use_colliders) {
|
||||
p_list->push_back(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_3D_PHYSICS, "", PROPERTY_USAGE_DEFAULT));
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < jiggle_data_chain.size(); i++) {
|
||||
String base_string = "joint_data/" + 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::REAL, 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));
|
||||
|
||||
if (jiggle_data_chain[i].override_defaults) {
|
||||
p_list->push_back(PropertyInfo(Variant::REAL, base_string + "stiffness", PROPERTY_HINT_RANGE, "0, 1000, 0.01", PROPERTY_USAGE_DEFAULT));
|
||||
p_list->push_back(PropertyInfo(Variant::REAL, base_string + "mass", PROPERTY_HINT_RANGE, "0, 1000, 0.01", PROPERTY_USAGE_DEFAULT));
|
||||
p_list->push_back(PropertyInfo(Variant::REAL, base_string + "damping", PROPERTY_HINT_RANGE, "0, 1, 0.01", PROPERTY_USAGE_DEFAULT));
|
||||
p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "use_gravity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
|
||||
if (jiggle_data_chain[i].use_gravity) {
|
||||
p_list->push_back(PropertyInfo(Variant::VECTOR3, base_string + "gravity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModification3DJiggle::_execute(real_t p_delta) {
|
||||
ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr,
|
||||
"Modification is not setup and therefore cannot execute!");
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
if (target_node_cache == 0) {
|
||||
_print_execution_error(true, "Target cache is out of date. Attempting to update...");
|
||||
update_cache();
|
||||
return;
|
||||
}
|
||||
Spatial *target = Object::cast_to<Spatial>(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!");
|
||||
|
||||
for (uint32_t i = 0; i < jiggle_data_chain.size(); i++) {
|
||||
_execute_jiggle_joint(i, target, p_delta);
|
||||
}
|
||||
|
||||
execution_error_found = false;
|
||||
}
|
||||
|
||||
void SkeletonModification3DJiggle::_execute_jiggle_joint(int p_joint_idx, Spatial *p_target, 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 = stack->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 > stack->skeleton->get_bone_count(),
|
||||
"Jiggle joint " + itos(p_joint_idx) + " bone index is invalid. Cannot execute modification!")) {
|
||||
return;
|
||||
}
|
||||
|
||||
Transform bone_local_pos = stack->skeleton->get_bone_local_pose_override(jiggle_data_chain[p_joint_idx].bone_idx);
|
||||
if (bone_local_pos == Transform()) {
|
||||
bone_local_pos = stack->skeleton->get_bone_pose(jiggle_data_chain[p_joint_idx].bone_idx);
|
||||
}
|
||||
|
||||
Transform new_bone_trans = stack->skeleton->local_pose_to_global_pose(jiggle_data_chain[p_joint_idx].bone_idx, bone_local_pos);
|
||||
Vector3 target_position = stack->skeleton->world_transform_to_global_pose(p_target->get_global_transform()).origin;
|
||||
|
||||
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;
|
||||
|
||||
if (jiggle_data_chain[p_joint_idx].use_gravity) {
|
||||
Vector3 gravity_to_apply = new_bone_trans.basis.inverse().xform(jiggle_data_chain[p_joint_idx].gravity);
|
||||
jiggle_data_chain[p_joint_idx].force += gravity_to_apply * p_delta;
|
||||
}
|
||||
|
||||
jiggle_data_chain[p_joint_idx].acceleration = jiggle_data_chain[p_joint_idx].force / jiggle_data_chain[p_joint_idx].mass;
|
||||
jiggle_data_chain[p_joint_idx].velocity += jiggle_data_chain[p_joint_idx].acceleration * (1 - jiggle_data_chain[p_joint_idx].damping);
|
||||
|
||||
jiggle_data_chain[p_joint_idx].dynamic_position += jiggle_data_chain[p_joint_idx].velocity + jiggle_data_chain[p_joint_idx].force;
|
||||
jiggle_data_chain[p_joint_idx].dynamic_position += new_bone_trans.origin - jiggle_data_chain[p_joint_idx].last_position;
|
||||
jiggle_data_chain[p_joint_idx].last_position = new_bone_trans.origin;
|
||||
|
||||
// Collision detection/response
|
||||
if (use_colliders) {
|
||||
if (execution_mode == SkeletonModificationStack3D::EXECUTION_MODE::execution_mode_physics_process) {
|
||||
Ref<World3D> world_3d = stack->skeleton->get_world_3d();
|
||||
ERR_FAIL_COND(world_3d.is_null());
|
||||
PhysicsDirectSpaceState *space_state = PhysicsServer::get_singleton()->space_get_direct_state(world_3d->get_space());
|
||||
PhysicsDirectSpaceState::RayResult ray_result;
|
||||
|
||||
// Convert to world transforms, which is what the physics server needs
|
||||
Transform new_bone_trans_world = stack->skeleton->global_pose_to_world_transform(new_bone_trans);
|
||||
Transform dynamic_position_world = stack->skeleton->global_pose_to_world_transform(Transform(Basis(), jiggle_data_chain[p_joint_idx].dynamic_position));
|
||||
|
||||
bool ray_hit = space_state->intersect_ray(new_bone_trans_world.origin, dynamic_position_world.get_origin(), ray_result, RBSet<RID>(), collision_mask);
|
||||
|
||||
if (ray_hit) {
|
||||
jiggle_data_chain[p_joint_idx].dynamic_position = jiggle_data_chain[p_joint_idx].last_noncollision_position;
|
||||
jiggle_data_chain[p_joint_idx].acceleration = Vector3(0, 0, 0);
|
||||
jiggle_data_chain[p_joint_idx].velocity = Vector3(0, 0, 0);
|
||||
} else {
|
||||
jiggle_data_chain[p_joint_idx].last_noncollision_position = jiggle_data_chain[p_joint_idx].dynamic_position;
|
||||
}
|
||||
|
||||
} else {
|
||||
WARN_PRINT_ONCE("Jiggle modifier: You cannot detect colliders without the stack mode being set to _physics_process!");
|
||||
}
|
||||
}
|
||||
|
||||
// Get the forward direction that the basis is facing in right now.
|
||||
stack->skeleton->update_bone_rest_forward_vector(jiggle_data_chain[p_joint_idx].bone_idx);
|
||||
Vector3 forward_vector = stack->skeleton->get_bone_axis_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));
|
||||
|
||||
// Roll
|
||||
new_bone_trans.basis.rotate_local(forward_vector, jiggle_data_chain[p_joint_idx].roll);
|
||||
|
||||
new_bone_trans = stack->skeleton->global_pose_to_local_pose(jiggle_data_chain[p_joint_idx].bone_idx, new_bone_trans);
|
||||
stack->skeleton->set_bone_local_pose_override(jiggle_data_chain[p_joint_idx].bone_idx, new_bone_trans, stack->strength, true);
|
||||
stack->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::_setup_modification(Ref<SkeletonModificationStack3D> p_stack) {
|
||||
stack = p_stack.ptr();
|
||||
|
||||
if (stack) {
|
||||
is_setup = true;
|
||||
execution_error_found = false;
|
||||
|
||||
if (stack->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 < stack->skeleton->get_bone_count()) {
|
||||
jiggle_data_chain[i].dynamic_position = stack->skeleton->local_pose_to_global_pose(bone_idx, stack->skeleton->get_bone_local_pose_override(bone_idx)).origin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
update_cache();
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModification3DJiggle::update_cache() {
|
||||
if (!is_setup || !stack) {
|
||||
_print_execution_error(true, "Cannot update target cache: modification is not properly setup!");
|
||||
return;
|
||||
}
|
||||
|
||||
target_node_cache = ObjectID();
|
||||
if (stack->skeleton) {
|
||||
if (stack->skeleton->is_inside_tree()) {
|
||||
if (stack->skeleton->has_node(target_node)) {
|
||||
Node *node = stack->skeleton->get_node(target_node);
|
||||
ERR_FAIL_COND_MSG(!node || stack->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 SkeletonModification3DJiggle::set_target_node(const NodePath &p_target_node) {
|
||||
target_node = p_target_node;
|
||||
update_cache();
|
||||
}
|
||||
|
||||
NodePath SkeletonModification3DJiggle::get_target_node() const {
|
||||
return target_node;
|
||||
}
|
||||
|
||||
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;
|
||||
_update_jiggle_joint_data();
|
||||
}
|
||||
|
||||
real_t SkeletonModification3DJiggle::get_stiffness() const {
|
||||
return stiffness;
|
||||
}
|
||||
|
||||
void SkeletonModification3DJiggle::set_mass(real_t p_mass) {
|
||||
ERR_FAIL_COND_MSG(p_mass < 0, "Mass cannot be set to a negative value!");
|
||||
mass = p_mass;
|
||||
_update_jiggle_joint_data();
|
||||
}
|
||||
|
||||
real_t SkeletonModification3DJiggle::get_mass() const {
|
||||
return mass;
|
||||
}
|
||||
|
||||
void SkeletonModification3DJiggle::set_damping(real_t p_damping) {
|
||||
ERR_FAIL_COND_MSG(p_damping < 0, "Damping cannot be set to a negative value!");
|
||||
ERR_FAIL_COND_MSG(p_damping > 1, "Damping cannot be more than one!");
|
||||
damping = p_damping;
|
||||
_update_jiggle_joint_data();
|
||||
}
|
||||
|
||||
real_t SkeletonModification3DJiggle::get_damping() const {
|
||||
return damping;
|
||||
}
|
||||
|
||||
void SkeletonModification3DJiggle::set_use_gravity(bool p_use_gravity) {
|
||||
use_gravity = p_use_gravity;
|
||||
_update_jiggle_joint_data();
|
||||
}
|
||||
|
||||
bool SkeletonModification3DJiggle::get_use_gravity() const {
|
||||
return use_gravity;
|
||||
}
|
||||
|
||||
void SkeletonModification3DJiggle::set_gravity(Vector3 p_gravity) {
|
||||
gravity = p_gravity;
|
||||
_update_jiggle_joint_data();
|
||||
}
|
||||
|
||||
Vector3 SkeletonModification3DJiggle::get_gravity() const {
|
||||
return gravity;
|
||||
}
|
||||
|
||||
void SkeletonModification3DJiggle::set_use_colliders(bool p_use_collider) {
|
||||
use_colliders = p_use_collider;
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
bool SkeletonModification3DJiggle::get_use_colliders() const {
|
||||
return use_colliders;
|
||||
}
|
||||
|
||||
void SkeletonModification3DJiggle::set_collision_mask(int p_mask) {
|
||||
collision_mask = p_mask;
|
||||
}
|
||||
|
||||
int SkeletonModification3DJiggle::get_collision_mask() const {
|
||||
return collision_mask;
|
||||
}
|
||||
|
||||
// Jiggle joint data functions
|
||||
int SkeletonModification3DJiggle::get_jiggle_data_chain_length() {
|
||||
return jiggle_data_chain.size();
|
||||
}
|
||||
|
||||
void SkeletonModification3DJiggle::set_jiggle_data_chain_length(int p_length) {
|
||||
ERR_FAIL_COND(p_length < 0);
|
||||
jiggle_data_chain.resize(p_length);
|
||||
execution_error_found = false;
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
void SkeletonModification3DJiggle::set_jiggle_joint_bone_name(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 (stack && stack->skeleton) {
|
||||
jiggle_data_chain[p_joint_idx].bone_idx = stack->skeleton->find_bone(p_name);
|
||||
}
|
||||
execution_error_found = false;
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
String SkeletonModification3DJiggle::get_jiggle_joint_bone_name(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 (stack) {
|
||||
if (stack->skeleton) {
|
||||
jiggle_data_chain[p_joint_idx].bone_name = stack->skeleton->get_bone_name(p_bone_idx);
|
||||
}
|
||||
}
|
||||
execution_error_found = false;
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
void SkeletonModification3DJiggle::set_jiggle_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;
|
||||
_update_jiggle_joint_data();
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
bool SkeletonModification3DJiggle::get_jiggle_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) {
|
||||
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 {
|
||||
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) {
|
||||
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 {
|
||||
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) {
|
||||
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 {
|
||||
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) {
|
||||
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;
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
bool SkeletonModification3DJiggle::get_jiggle_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) {
|
||||
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 {
|
||||
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) {
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
|
||||
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_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_stiffness", "stiffness"), &SkeletonModification3DJiggle::set_stiffness);
|
||||
ClassDB::bind_method(D_METHOD("get_stiffness"), &SkeletonModification3DJiggle::get_stiffness);
|
||||
ClassDB::bind_method(D_METHOD("set_mass", "mass"), &SkeletonModification3DJiggle::set_mass);
|
||||
ClassDB::bind_method(D_METHOD("get_mass"), &SkeletonModification3DJiggle::get_mass);
|
||||
ClassDB::bind_method(D_METHOD("set_damping", "damping"), &SkeletonModification3DJiggle::set_damping);
|
||||
ClassDB::bind_method(D_METHOD("get_damping"), &SkeletonModification3DJiggle::get_damping);
|
||||
ClassDB::bind_method(D_METHOD("set_use_gravity", "use_gravity"), &SkeletonModification3DJiggle::set_use_gravity);
|
||||
ClassDB::bind_method(D_METHOD("get_use_gravity"), &SkeletonModification3DJiggle::get_use_gravity);
|
||||
ClassDB::bind_method(D_METHOD("set_gravity", "gravity"), &SkeletonModification3DJiggle::set_gravity);
|
||||
ClassDB::bind_method(D_METHOD("get_gravity"), &SkeletonModification3DJiggle::get_gravity);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_use_colliders", "use_colliders"), &SkeletonModification3DJiggle::set_use_colliders);
|
||||
ClassDB::bind_method(D_METHOD("get_use_colliders"), &SkeletonModification3DJiggle::get_use_colliders);
|
||||
ClassDB::bind_method(D_METHOD("set_collision_mask", "mask"), &SkeletonModification3DJiggle::set_collision_mask);
|
||||
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, "Spatial"), "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");
|
||||
ADD_GROUP("Default Joint Settings", "");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::REAL, "stiffness"), "set_stiffness", "get_stiffness");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::REAL, "mass"), "set_mass", "get_mass");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::REAL, "damping", PROPERTY_HINT_RANGE, "0, 1, 0.01"), "set_damping", "get_damping");
|
||||
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("", "");
|
||||
}
|
||||
|
||||
SkeletonModification3DJiggle::SkeletonModification3DJiggle() {
|
||||
stack = nullptr;
|
||||
is_setup = false;
|
||||
target_node_cache = 0;
|
||||
stiffness = 3;
|
||||
mass = 0.75;
|
||||
damping = 0.75;
|
||||
use_gravity = false;
|
||||
gravity = Vector3(0, -6.0, 0);
|
||||
enabled = true;
|
||||
|
||||
use_colliders = false;
|
||||
collision_mask = 1;
|
||||
}
|
||||
|
||||
SkeletonModification3DJiggle::~SkeletonModification3DJiggle() {
|
||||
}
|
148
modules/skeleton_3d/resources/skeleton_modification_3d_jiggle.h
Normal file
@ -0,0 +1,148 @@
|
||||
#ifndef SKELETON_MODIFICATION_3D_JIGGLE_H
|
||||
#define SKELETON_MODIFICATION_3D_JIGGLE_H
|
||||
|
||||
/*************************************************************************/
|
||||
/* skeleton_modification_3d_jiggle.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "core/containers/local_vector.h"
|
||||
#include "skeleton_modification_3d.h"
|
||||
|
||||
class SkeletonModification3DJiggle : public SkeletonModification3D {
|
||||
GDCLASS(SkeletonModification3DJiggle, SkeletonModification3D);
|
||||
|
||||
private:
|
||||
struct Jiggle_Joint_Data {
|
||||
String bone_name;
|
||||
int bone_idx;
|
||||
|
||||
bool override_defaults;
|
||||
real_t stiffness;
|
||||
real_t mass;
|
||||
real_t damping;
|
||||
bool use_gravity;
|
||||
Vector3 gravity;
|
||||
real_t roll;
|
||||
|
||||
Vector3 cached_rotation;
|
||||
Vector3 force;
|
||||
Vector3 acceleration;
|
||||
Vector3 velocity;
|
||||
Vector3 last_position;
|
||||
Vector3 dynamic_position;
|
||||
|
||||
Vector3 last_noncollision_position;
|
||||
|
||||
Jiggle_Joint_Data() {
|
||||
bone_idx = -1;
|
||||
override_defaults = false;
|
||||
stiffness = 3;
|
||||
mass = 0.75;
|
||||
damping = 0.75;
|
||||
use_gravity = false;
|
||||
gravity = Vector3(0, -6.0, 0);
|
||||
roll = 0;
|
||||
}
|
||||
};
|
||||
|
||||
NodePath target_node;
|
||||
ObjectID target_node_cache;
|
||||
LocalVector<Jiggle_Joint_Data> jiggle_data_chain;
|
||||
|
||||
real_t stiffness;
|
||||
real_t mass;
|
||||
real_t damping;
|
||||
bool use_gravity;
|
||||
Vector3 gravity;
|
||||
|
||||
bool use_colliders;
|
||||
uint32_t collision_mask;
|
||||
|
||||
void update_cache();
|
||||
void _execute_jiggle_joint(int p_joint_idx, Spatial *p_target, real_t p_delta);
|
||||
void _update_jiggle_joint_data();
|
||||
|
||||
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<PropertyInfo> *p_list) const;
|
||||
|
||||
public:
|
||||
virtual void _execute(real_t p_delta);
|
||||
virtual void _setup_modification(Ref<SkeletonModificationStack3D> p_stack);
|
||||
|
||||
void set_target_node(const NodePath &p_target_node);
|
||||
NodePath get_target_node() const;
|
||||
|
||||
void set_stiffness(real_t p_stiffness);
|
||||
real_t get_stiffness() const;
|
||||
void set_mass(real_t p_mass);
|
||||
real_t get_mass() const;
|
||||
void set_damping(real_t p_damping);
|
||||
real_t get_damping() const;
|
||||
|
||||
void set_use_gravity(bool p_use_gravity);
|
||||
bool get_use_gravity() const;
|
||||
void set_gravity(Vector3 p_gravity);
|
||||
Vector3 get_gravity() const;
|
||||
|
||||
void set_use_colliders(bool p_use_colliders);
|
||||
bool get_use_colliders() const;
|
||||
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;
|
||||
|
||||
SkeletonModification3DJiggle();
|
||||
~SkeletonModification3DJiggle();
|
||||
};
|
||||
|
||||
#endif // SKELETON_MODIFICATION_3D_JIGGLE_H
|
@ -0,0 +1,269 @@
|
||||
/*************************************************************************/
|
||||
/* skeleton_modification_3d_lookat.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "skeleton_modification_3d_lookat.h"
|
||||
#include "../nodes/skeleton.h"
|
||||
#include "skeleton_modification_stack_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::deg2rad(tmp.x);
|
||||
tmp.y = Math::deg2rad(tmp.y);
|
||||
tmp.z = Math::deg2rad(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::rad2deg(tmp.x);
|
||||
tmp.y = Math::rad2deg(tmp.y);
|
||||
tmp.z = Math::rad2deg(tmp.z);
|
||||
r_ret = tmp;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SkeletonModification3DLookAt::_get_property_list(List<PropertyInfo> *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::_execute(real_t p_delta) {
|
||||
ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr,
|
||||
"Modification is not setup and therefore cannot execute!");
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (target_node_cache == 0) {
|
||||
_print_execution_error(true, "Target cache is out of date. Attempting to update...");
|
||||
update_cache();
|
||||
return;
|
||||
}
|
||||
|
||||
if (bone_idx <= -2) {
|
||||
bone_idx = stack->skeleton->find_bone(bone_name);
|
||||
}
|
||||
|
||||
Spatial *target = Object::cast_to<Spatial>(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;
|
||||
}
|
||||
Transform new_bone_trans = stack->skeleton->get_bone_local_pose_override(bone_idx);
|
||||
if (new_bone_trans == Transform()) {
|
||||
new_bone_trans = stack->skeleton->get_bone_pose(bone_idx);
|
||||
}
|
||||
Vector3 target_pos = stack->skeleton->global_pose_to_local_pose(bone_idx, stack->skeleton->world_transform_to_global_pose(target->get_global_transform())).origin;
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
stack->skeleton->update_bone_rest_forward_vector(bone_idx);
|
||||
new_bone_trans.basis = stack->skeleton->global_pose_z_forward_to_bone_forward(bone_idx, new_bone_trans.basis);
|
||||
|
||||
// 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);
|
||||
|
||||
stack->skeleton->set_bone_local_pose_override(bone_idx, new_bone_trans, stack->strength, true);
|
||||
stack->skeleton->force_update_bone_children_transforms(bone_idx);
|
||||
|
||||
// If we completed it successfully, then we can set execution_error_found to false
|
||||
execution_error_found = false;
|
||||
}
|
||||
|
||||
void SkeletonModification3DLookAt::_setup_modification(Ref<SkeletonModificationStack3D> p_stack) {
|
||||
stack = p_stack.ptr();
|
||||
|
||||
if (stack != nullptr) {
|
||||
is_setup = true;
|
||||
execution_error_found = false;
|
||||
update_cache();
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModification3DLookAt::set_bone_name(String p_name) {
|
||||
bone_name = p_name;
|
||||
if (stack) {
|
||||
if (stack->skeleton) {
|
||||
bone_idx = stack->skeleton->find_bone(bone_name);
|
||||
}
|
||||
}
|
||||
execution_error_found = false;
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
String SkeletonModification3DLookAt::get_bone_name() 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 (stack) {
|
||||
if (stack->skeleton) {
|
||||
bone_name = stack->skeleton->get_bone_name(p_bone_idx);
|
||||
}
|
||||
}
|
||||
execution_error_found = false;
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
void SkeletonModification3DLookAt::update_cache() {
|
||||
if (!is_setup || !stack) {
|
||||
_print_execution_error(true, "Cannot update target cache: modification is not properly setup!");
|
||||
return;
|
||||
}
|
||||
|
||||
target_node_cache = ObjectID();
|
||||
if (stack->skeleton) {
|
||||
if (stack->skeleton->is_inside_tree()) {
|
||||
if (stack->skeleton->has_node(target_node)) {
|
||||
Node *node = stack->skeleton->get_node(target_node);
|
||||
ERR_FAIL_COND_MSG(!node || stack->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 SkeletonModification3DLookAt::set_target_node(const NodePath &p_target_node) {
|
||||
target_node = p_target_node;
|
||||
update_cache();
|
||||
}
|
||||
|
||||
NodePath SkeletonModification3DLookAt::get_target_node() const {
|
||||
return target_node;
|
||||
}
|
||||
|
||||
Vector3 SkeletonModification3DLookAt::get_additional_rotation() const {
|
||||
return additional_rotation;
|
||||
}
|
||||
|
||||
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;
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
int SkeletonModification3DLookAt::get_lock_rotation_plane() const {
|
||||
return lock_rotation_plane;
|
||||
}
|
||||
|
||||
void SkeletonModification3DLookAt::set_lock_rotation_plane(int 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_target_node", "target_nodepath"), &SkeletonModification3DLookAt::set_target_node);
|
||||
ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification3DLookAt::get_target_node);
|
||||
|
||||
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, "Spatial"), "set_target_node", "get_target_node");
|
||||
}
|
||||
|
||||
SkeletonModification3DLookAt::SkeletonModification3DLookAt() {
|
||||
stack = nullptr;
|
||||
is_setup = false;
|
||||
|
||||
bone_idx = -1;
|
||||
target_node_cache = 0;
|
||||
additional_rotation = Vector3(1, 0, 0);
|
||||
lock_rotation_to_plane = false;
|
||||
enabled = true;
|
||||
lock_rotation_plane = ROTATION_PLANE_X;
|
||||
}
|
||||
|
||||
SkeletonModification3DLookAt::~SkeletonModification3DLookAt() {
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
#ifndef SKELETON_MODIFICATION_3D_LOOKAT_H
|
||||
#define SKELETON_MODIFICATION_3D_LOOKAT_H
|
||||
|
||||
/*************************************************************************/
|
||||
/* skeleton_modification_3d_lookat.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "skeleton_modification_3d.h"
|
||||
|
||||
class SkeletonModification3DLookAt : public SkeletonModification3D {
|
||||
GDCLASS(SkeletonModification3DLookAt, SkeletonModification3D);
|
||||
|
||||
private:
|
||||
String bone_name;
|
||||
int bone_idx;
|
||||
NodePath target_node;
|
||||
ObjectID target_node_cache;
|
||||
|
||||
Vector3 additional_rotation;
|
||||
bool lock_rotation_to_plane;
|
||||
int lock_rotation_plane;
|
||||
|
||||
void update_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<PropertyInfo> *p_list) const;
|
||||
|
||||
public:
|
||||
enum ROTATION_PLANE {
|
||||
ROTATION_PLANE_X,
|
||||
ROTATION_PLANE_Y,
|
||||
ROTATION_PLANE_Z
|
||||
};
|
||||
|
||||
virtual void _execute(real_t p_delta);
|
||||
virtual void _setup_modification(Ref<SkeletonModificationStack3D> p_stack);
|
||||
|
||||
void set_bone_name(String p_name);
|
||||
String get_bone_name() const;
|
||||
|
||||
void set_bone_index(int p_idx);
|
||||
int get_bone_index() const;
|
||||
|
||||
void set_target_node(const NodePath &p_target_node);
|
||||
NodePath get_target_node() 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;
|
||||
|
||||
SkeletonModification3DLookAt();
|
||||
~SkeletonModification3DLookAt();
|
||||
};
|
||||
|
||||
#endif // SKELETON_MODIFICATION_3D_LOOKAT_H
|
@ -0,0 +1,103 @@
|
||||
/*************************************************************************/
|
||||
/* skeleton_modification_3d_stackholder.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "skeleton_modification_3d_stackholder.h"
|
||||
#include "../nodes/skeleton.h"
|
||||
#include "skeleton_modification_stack_3d.h"
|
||||
|
||||
bool SkeletonModification3DStackHolder::_set(const StringName &p_path, const Variant &p_value) {
|
||||
String path = p_path;
|
||||
|
||||
if (path == "held_modification_stack") {
|
||||
set_held_modification_stack(p_value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SkeletonModification3DStackHolder::_get(const StringName &p_path, Variant &r_ret) const {
|
||||
String path = p_path;
|
||||
|
||||
if (path == "held_modification_stack") {
|
||||
r_ret = get_held_modification_stack();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void SkeletonModification3DStackHolder::_get_property_list(List<PropertyInfo> *p_list) const {
|
||||
p_list->push_back(PropertyInfo(Variant::OBJECT, "held_modification_stack", PROPERTY_HINT_RESOURCE_TYPE, "SkeletonModificationStack3D", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE));
|
||||
}
|
||||
|
||||
void SkeletonModification3DStackHolder::_execute(real_t p_delta) {
|
||||
ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr, "Modification is not setup and therefore cannot execute!");
|
||||
|
||||
if (held_modification_stack.is_valid()) {
|
||||
held_modification_stack->execute(p_delta, execution_mode);
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModification3DStackHolder::_setup_modification(Ref<SkeletonModificationStack3D> p_stack) {
|
||||
stack = p_stack.ptr();
|
||||
|
||||
if (stack != nullptr) {
|
||||
is_setup = true;
|
||||
|
||||
if (held_modification_stack.is_valid()) {
|
||||
held_modification_stack->set_skeleton(stack->get_skeleton());
|
||||
held_modification_stack->setup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModification3DStackHolder::set_held_modification_stack(Ref<SkeletonModificationStack3D> p_held_stack) {
|
||||
held_modification_stack = p_held_stack;
|
||||
|
||||
if (is_setup && held_modification_stack.is_valid()) {
|
||||
held_modification_stack->set_skeleton(stack->get_skeleton());
|
||||
held_modification_stack->setup();
|
||||
}
|
||||
}
|
||||
|
||||
Ref<SkeletonModificationStack3D> SkeletonModification3DStackHolder::get_held_modification_stack() const {
|
||||
return held_modification_stack;
|
||||
}
|
||||
|
||||
void SkeletonModification3DStackHolder::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_held_modification_stack", "held_modification_stack"), &SkeletonModification3DStackHolder::set_held_modification_stack);
|
||||
ClassDB::bind_method(D_METHOD("get_held_modification_stack"), &SkeletonModification3DStackHolder::get_held_modification_stack);
|
||||
}
|
||||
|
||||
SkeletonModification3DStackHolder::SkeletonModification3DStackHolder() {
|
||||
stack = nullptr;
|
||||
is_setup = false;
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
SkeletonModification3DStackHolder::~SkeletonModification3DStackHolder() {
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
#ifndef SKELETON_MODIFICATION_3D_STACKHOLDER_H
|
||||
#define SKELETON_MODIFICATION_3D_STACKHOLDER_H
|
||||
|
||||
/*************************************************************************/
|
||||
/* skeleton_modification_3d_stackholder.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "skeleton_modification_3d.h"
|
||||
|
||||
class SkeletonModification3DStackHolder : public SkeletonModification3D {
|
||||
GDCLASS(SkeletonModification3DStackHolder, SkeletonModification3D);
|
||||
|
||||
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<PropertyInfo> *p_list) const;
|
||||
|
||||
public:
|
||||
Ref<SkeletonModificationStack3D> held_modification_stack;
|
||||
|
||||
virtual void _execute(real_t p_delta);
|
||||
virtual void _setup_modification(Ref<SkeletonModificationStack3D> p_stack);
|
||||
|
||||
void set_held_modification_stack(Ref<SkeletonModificationStack3D> p_held_stack);
|
||||
Ref<SkeletonModificationStack3D> get_held_modification_stack() const;
|
||||
|
||||
SkeletonModification3DStackHolder();
|
||||
~SkeletonModification3DStackHolder();
|
||||
};
|
||||
|
||||
#endif // SKELETON_MODIFICATION_3D_STACKHOLDER_H
|
@ -0,0 +1,639 @@
|
||||
/*************************************************************************/
|
||||
/* skeleton_modification_3d_twoboneik.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "skeleton_modification_3d_twoboneik.h"
|
||||
#include "../nodes/skeleton.h"
|
||||
#include "skeleton_modification_stack_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::deg2rad(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::deg2rad(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::rad2deg(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::rad2deg(get_joint_two_roll());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SkeletonModification3DTwoBoneIK::_get_property_list(List<PropertyInfo> *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, "Spatial", 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::REAL, "joint_one_length", PROPERTY_HINT_RANGE, "-1, 10000, 0.001", PROPERTY_USAGE_DEFAULT));
|
||||
p_list->push_back(PropertyInfo(Variant::REAL, "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, "Spatial", 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::REAL, "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::REAL, "joint_two/roll", PROPERTY_HINT_RANGE, "-360, 360, 0.01", PROPERTY_USAGE_DEFAULT));
|
||||
}
|
||||
|
||||
void SkeletonModification3DTwoBoneIK::_execute(real_t p_delta) {
|
||||
ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr, "Modification is not setup and therefore cannot execute!");
|
||||
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (target_node_cache == 0) {
|
||||
_print_execution_error(true, "Target cache is out of date. Attempting to update...");
|
||||
update_cache_target();
|
||||
return;
|
||||
}
|
||||
|
||||
// Update joint lengths (if needed)
|
||||
if (auto_calculate_joint_length && (joint_one_length < 0 || joint_two_length < 0)) {
|
||||
calculate_joint_lengths();
|
||||
}
|
||||
|
||||
// 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
|
||||
Spatial *target = Object::cast_to<Spatial>(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;
|
||||
}
|
||||
|
||||
Transform target_trans = stack->skeleton->world_transform_to_global_pose(target->get_global_transform());
|
||||
|
||||
Transform bone_one_trans;
|
||||
Transform 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 == 0) {
|
||||
_print_execution_error(true, "Pole cache is out of date. Attempting to update...");
|
||||
update_cache_pole();
|
||||
return;
|
||||
}
|
||||
|
||||
Spatial *pole = Object::cast_to<Spatial>(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;
|
||||
}
|
||||
|
||||
Transform pole_trans = stack->skeleton->world_transform_to_global_pose(pole->get_global_transform());
|
||||
|
||||
Transform bone_one_local_pos = stack->skeleton->get_bone_local_pose_override(joint_one_bone_idx);
|
||||
if (bone_one_local_pos == Transform()) {
|
||||
bone_one_local_pos = stack->skeleton->get_bone_pose(joint_one_bone_idx);
|
||||
}
|
||||
|
||||
Transform bone_two_local_pos = stack->skeleton->get_bone_local_pose_override(joint_two_bone_idx);
|
||||
if (bone_two_local_pos == Transform()) {
|
||||
bone_two_local_pos = stack->skeleton->get_bone_pose(joint_two_bone_idx);
|
||||
}
|
||||
|
||||
bone_one_trans = stack->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 = stack->skeleton->global_pose_z_forward_to_bone_forward(joint_one_bone_idx, bone_one_trans.basis);
|
||||
stack->skeleton->update_bone_rest_forward_vector(joint_one_bone_idx);
|
||||
bone_one_trans.basis.rotate_local(stack->skeleton->get_bone_axis_forward_vector(joint_one_bone_idx), joint_one_roll);
|
||||
stack->skeleton->set_bone_local_pose_override(joint_one_bone_idx, stack->skeleton->global_pose_to_local_pose(joint_one_bone_idx, bone_one_trans), stack->strength, true);
|
||||
stack->skeleton->force_update_bone_children_transforms(joint_one_bone_idx);
|
||||
|
||||
bone_two_trans = stack->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 = stack->skeleton->global_pose_z_forward_to_bone_forward(joint_two_bone_idx, bone_two_trans.basis);
|
||||
stack->skeleton->update_bone_rest_forward_vector(joint_two_bone_idx);
|
||||
bone_two_trans.basis.rotate_local(stack->skeleton->get_bone_axis_forward_vector(joint_two_bone_idx), joint_two_roll);
|
||||
stack->skeleton->set_bone_local_pose_override(joint_two_bone_idx, stack->skeleton->global_pose_to_local_pose(joint_two_bone_idx, bone_two_trans), stack->strength, true);
|
||||
stack->skeleton->force_update_bone_children_transforms(joint_two_bone_idx);
|
||||
} else {
|
||||
Transform bone_one_local_pos = stack->skeleton->get_bone_local_pose_override(joint_one_bone_idx);
|
||||
if (bone_one_local_pos == Transform()) {
|
||||
bone_one_local_pos = stack->skeleton->get_bone_pose(joint_one_bone_idx);
|
||||
}
|
||||
Transform bone_two_local_pos = stack->skeleton->get_bone_local_pose_override(joint_two_bone_idx);
|
||||
if (bone_two_local_pos == Transform()) {
|
||||
bone_two_local_pos = stack->skeleton->get_bone_pose(joint_two_bone_idx);
|
||||
}
|
||||
|
||||
bone_one_trans = stack->skeleton->local_pose_to_global_pose(joint_one_bone_idx, bone_one_local_pos);
|
||||
bone_two_trans = stack->skeleton->local_pose_to_global_pose(joint_two_bone_idx, bone_two_local_pos);
|
||||
}
|
||||
|
||||
Transform bone_two_tip_trans;
|
||||
if (use_tip_node) {
|
||||
if (tip_node_cache == 0) {
|
||||
_print_execution_error(true, "Tip cache is out of date. Attempting to update...");
|
||||
update_cache_tip();
|
||||
return;
|
||||
}
|
||||
|
||||
Spatial *tip = Object::cast_to<Spatial>(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 = stack->skeleton->world_transform_to_global_pose(tip->get_global_transform());
|
||||
} else {
|
||||
stack->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(stack->skeleton->get_bone_axis_forward_vector(joint_two_bone_idx)).normalized() * joint_two_length;
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
stack->skeleton->update_bone_rest_forward_vector(joint_one_bone_idx);
|
||||
bone_one_trans.basis.rotate_local(stack->skeleton->get_bone_axis_forward_vector(joint_one_bone_idx), joint_one_roll);
|
||||
|
||||
// Apply the rotation to the first joint
|
||||
bone_one_trans = stack->skeleton->global_pose_to_local_pose(joint_one_bone_idx, bone_one_trans);
|
||||
bone_one_trans.origin = Vector3(0, 0, 0);
|
||||
stack->skeleton->set_bone_local_pose_override(joint_one_bone_idx, bone_one_trans, stack->strength, true);
|
||||
stack->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 = stack->skeleton->local_pose_to_global_pose(joint_two_bone_idx, stack->skeleton->get_bone_local_pose_override(joint_two_bone_idx));
|
||||
stack->skeleton->update_bone_rest_forward_vector(joint_two_bone_idx);
|
||||
Vector3 forward_vector = stack->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));
|
||||
|
||||
stack->skeleton->update_bone_rest_forward_vector(joint_two_bone_idx);
|
||||
bone_two_trans.basis.rotate_local(stack->skeleton->get_bone_axis_forward_vector(joint_two_bone_idx), joint_two_roll);
|
||||
|
||||
bone_two_trans = stack->skeleton->global_pose_to_local_pose(joint_two_bone_idx, bone_two_trans);
|
||||
stack->skeleton->set_bone_local_pose_override(joint_two_bone_idx, bone_two_trans, stack->strength, true);
|
||||
stack->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);
|
||||
|
||||
stack->skeleton->update_bone_rest_forward_vector(joint_two_bone_idx);
|
||||
bone_two_trans.basis.rotate_local(stack->skeleton->get_bone_axis_forward_vector(joint_two_bone_idx), joint_two_roll);
|
||||
|
||||
bone_two_trans = stack->skeleton->global_pose_to_local_pose(joint_two_bone_idx, bone_two_trans);
|
||||
bone_two_trans.origin = Vector3(0, 0, 0);
|
||||
stack->skeleton->set_bone_local_pose_override(joint_two_bone_idx, bone_two_trans, stack->strength, true);
|
||||
stack->skeleton->force_update_bone_children_transforms(joint_two_bone_idx);
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModification3DTwoBoneIK::_setup_modification(Ref<SkeletonModificationStack3D> p_stack) {
|
||||
stack = p_stack.ptr();
|
||||
|
||||
if (stack != nullptr) {
|
||||
is_setup = true;
|
||||
execution_error_found = false;
|
||||
update_cache_target();
|
||||
update_cache_tip();
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonModification3DTwoBoneIK::update_cache_target() {
|
||||
if (!is_setup || !stack) {
|
||||
_print_execution_error(true, "Cannot update target cache: modification is not properly setup!");
|
||||
return;
|
||||
}
|
||||
|
||||
target_node_cache = ObjectID();
|
||||
if (stack->skeleton) {
|
||||
if (stack->skeleton->is_inside_tree() && target_node.is_empty() == false) {
|
||||
if (stack->skeleton->has_node(target_node)) {
|
||||
Node *node = stack->skeleton->get_node(target_node);
|
||||
ERR_FAIL_COND_MSG(!node || stack->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 || !stack) {
|
||||
_print_execution_error(true, "Cannot update tip cache: modification is not properly setup!");
|
||||
return;
|
||||
}
|
||||
|
||||
tip_node_cache = ObjectID();
|
||||
if (stack->skeleton) {
|
||||
if (stack->skeleton->is_inside_tree()) {
|
||||
if (stack->skeleton->has_node(tip_node)) {
|
||||
Node *node = stack->skeleton->get_node(tip_node);
|
||||
ERR_FAIL_COND_MSG(!node || stack->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 || !stack) {
|
||||
_print_execution_error(true, "Cannot update pole cache: modification is not properly setup!");
|
||||
return;
|
||||
}
|
||||
|
||||
pole_node_cache = ObjectID();
|
||||
if (stack->skeleton) {
|
||||
if (stack->skeleton->is_inside_tree()) {
|
||||
if (stack->skeleton->has_node(pole_node)) {
|
||||
Node *node = stack->skeleton->get_node(pole_node);
|
||||
ERR_FAIL_COND_MSG(!node || stack->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();
|
||||
}
|
||||
|
||||
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;
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
bool SkeletonModification3DTwoBoneIK::get_use_tip_node() const {
|
||||
return use_tip_node;
|
||||
}
|
||||
|
||||
void SkeletonModification3DTwoBoneIK::set_tip_node(const NodePath &p_tip_node) {
|
||||
tip_node = p_tip_node;
|
||||
update_cache_tip();
|
||||
}
|
||||
|
||||
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;
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
bool SkeletonModification3DTwoBoneIK::get_use_pole_node() const {
|
||||
return use_pole_node;
|
||||
}
|
||||
|
||||
void SkeletonModification3DTwoBoneIK::set_pole_node(const NodePath &p_pole_node) {
|
||||
pole_node = p_pole_node;
|
||||
update_cache_pole();
|
||||
}
|
||||
|
||||
NodePath SkeletonModification3DTwoBoneIK::get_pole_node() const {
|
||||
return pole_node;
|
||||
}
|
||||
|
||||
void SkeletonModification3DTwoBoneIK::set_auto_calculate_joint_length(bool p_calculate) {
|
||||
auto_calculate_joint_length = p_calculate;
|
||||
if (p_calculate) {
|
||||
calculate_joint_lengths();
|
||||
}
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
bool SkeletonModification3DTwoBoneIK::get_auto_calculate_joint_length() const {
|
||||
return auto_calculate_joint_length;
|
||||
}
|
||||
|
||||
void SkeletonModification3DTwoBoneIK::calculate_joint_lengths() {
|
||||
if (!is_setup) {
|
||||
return; // fail silently, as we likely just loaded the scene.
|
||||
}
|
||||
ERR_FAIL_COND_MSG(!stack || stack->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!");
|
||||
|
||||
Transform bone_one_rest_trans = stack->skeleton->get_bone_global_pose(joint_one_bone_idx);
|
||||
Transform bone_two_rest_trans = stack->skeleton->get_bone_global_pose(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 == 0) {
|
||||
update_cache_tip();
|
||||
WARN_PRINT("Tip cache is out of date. Updating...");
|
||||
}
|
||||
|
||||
Spatial *tip = Object::cast_to<Spatial>(ObjectDB::get_instance(tip_node_cache));
|
||||
if (tip) {
|
||||
Transform bone_tip_trans = stack->skeleton->world_transform_to_global_pose(tip->get_global_transform());
|
||||
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<int> bone_two_children = stack->skeleton->get_bone_children(joint_two_bone_idx);
|
||||
if (bone_two_children.size() > 0) {
|
||||
joint_two_length = 0;
|
||||
for (int i = 0; i < bone_two_children.size(); i++) {
|
||||
joint_two_length += bone_two_rest_trans.origin.distance_to(
|
||||
stack->skeleton->get_bone_global_pose(bone_two_children[i]).origin);
|
||||
}
|
||||
joint_two_length = joint_two_length / bone_two_children.size();
|
||||
} else {
|
||||
WARN_PRINT("TwoBoneIK modification: Cannot auto calculate length for joint 2! Auto setting the length to 1...");
|
||||
joint_two_length = 1.0;
|
||||
}
|
||||
}
|
||||
execution_error_found = false;
|
||||
}
|
||||
|
||||
void SkeletonModification3DTwoBoneIK::set_joint_one_bone_name(String p_bone_name) {
|
||||
joint_one_bone_name = p_bone_name;
|
||||
if (stack && stack->skeleton) {
|
||||
joint_one_bone_idx = stack->skeleton->find_bone(p_bone_name);
|
||||
}
|
||||
execution_error_found = false;
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
String SkeletonModification3DTwoBoneIK::get_joint_one_bone_name() 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 (stack && stack->skeleton) {
|
||||
joint_one_bone_name = stack->skeleton->get_bone_name(p_bone_idx);
|
||||
}
|
||||
execution_error_found = false;
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
real_t SkeletonModification3DTwoBoneIK::get_joint_one_length() const {
|
||||
return joint_one_length;
|
||||
}
|
||||
|
||||
void SkeletonModification3DTwoBoneIK::set_joint_two_bone_name(String p_bone_name) {
|
||||
joint_two_bone_name = p_bone_name;
|
||||
if (stack && stack->skeleton) {
|
||||
joint_two_bone_idx = stack->skeleton->find_bone(p_bone_name);
|
||||
}
|
||||
execution_error_found = false;
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
String SkeletonModification3DTwoBoneIK::get_joint_two_bone_name() 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 (stack && stack->skeleton) {
|
||||
joint_two_bone_name = stack->skeleton->get_bone_name(p_bone_idx);
|
||||
}
|
||||
execution_error_found = false;
|
||||
property_list_changed_notify();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
real_t SkeletonModification3DTwoBoneIK::get_joint_two_length() const {
|
||||
return joint_two_length;
|
||||
}
|
||||
|
||||
void SkeletonModification3DTwoBoneIK::set_joint_one_roll(real_t p_roll) {
|
||||
joint_one_roll = p_roll;
|
||||
}
|
||||
|
||||
real_t SkeletonModification3DTwoBoneIK::get_joint_one_roll() const {
|
||||
return joint_one_roll;
|
||||
}
|
||||
|
||||
void SkeletonModification3DTwoBoneIK::set_joint_two_roll(real_t p_roll) {
|
||||
joint_two_roll = p_roll;
|
||||
}
|
||||
|
||||
real_t SkeletonModification3DTwoBoneIK::get_joint_two_roll() const {
|
||||
return joint_two_roll;
|
||||
}
|
||||
|
||||
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_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_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_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_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_length", "bone_length"), &SkeletonModification3DTwoBoneIK::set_joint_two_length);
|
||||
ClassDB::bind_method(D_METHOD("get_joint_two_length"), &SkeletonModification3DTwoBoneIK::get_joint_two_length);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_joint_one_roll", "roll"), &SkeletonModification3DTwoBoneIK::set_joint_one_roll);
|
||||
ClassDB::bind_method(D_METHOD("get_joint_one_roll"), &SkeletonModification3DTwoBoneIK::get_joint_one_roll);
|
||||
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, "Spatial"), "set_target_node", "get_target_node");
|
||||
ADD_GROUP("", "");
|
||||
}
|
||||
|
||||
SkeletonModification3DTwoBoneIK::SkeletonModification3DTwoBoneIK() {
|
||||
stack = nullptr;
|
||||
is_setup = false;
|
||||
|
||||
target_node_cache = 0;
|
||||
|
||||
use_tip_node = false;
|
||||
tip_node_cache = 0;
|
||||
|
||||
use_pole_node = false;
|
||||
pole_node_cache = 0;
|
||||
|
||||
joint_one_bone_idx = -1;
|
||||
joint_two_bone_idx = -1;
|
||||
|
||||
auto_calculate_joint_length = false;
|
||||
joint_one_length = -1;
|
||||
joint_two_length = -1;
|
||||
|
||||
joint_one_roll = 0;
|
||||
joint_two_roll = 0;
|
||||
}
|
||||
|
||||
SkeletonModification3DTwoBoneIK::~SkeletonModification3DTwoBoneIK() {
|
||||
}
|