From 5472558a98d39bb4e210454ee24a4c10544016bf Mon Sep 17 00:00:00 2001 From: "Silc Lizard (Tokage) Renew" <61938263+TokageItLab@users.noreply.github.com> Date: Sat, 11 Jan 2025 12:58:22 +0900 Subject: [PATCH] Implement SpringBoneSimulator3D to wiggle chained bones Co-authored-by: lyuma Co-authored-by: fire Co-authored-by: SaracenOne --- doc/classes/EditorSettings.xml | 9 + doc/classes/SpringBoneCollision3D.xml | 35 + doc/classes/SpringBoneCollisionCapsule3D.xml | 22 + doc/classes/SpringBoneCollisionPlane3D.xml | 11 + doc/classes/SpringBoneCollisionSphere3D.xml | 19 + doc/classes/SpringBoneSimulator3D.xml | 658 +++++++ editor/editor_inspector.cpp | 26 +- editor/editor_inspector.h | 5 +- editor/editor_settings.cpp | 3 + editor/icons/SpringBoneCollision3D.svg | 1 + editor/icons/SpringBoneCollisionCapsule3D.svg | 1 + editor/icons/SpringBoneCollisionPlane3D.svg | 1 + editor/icons/SpringBoneCollisionSphere3D.svg | 1 + editor/icons/SpringBoneSimulator3D.svg | 1 + .../gizmos/spring_bone_3d_gizmo_plugin.cpp | 424 +++++ .../gizmos/spring_bone_3d_gizmo_plugin.h | 89 + editor/plugins/node_3d_editor_plugin.cpp | 3 + scene/3d/spring_bone_collision_3d.cpp | 192 ++ scene/3d/spring_bone_collision_3d.h | 75 + scene/3d/spring_bone_collision_capsule_3d.cpp | 112 ++ scene/3d/spring_bone_collision_capsule_3d.h | 60 + scene/3d/spring_bone_collision_plane_3d.cpp | 44 + scene/3d/spring_bone_collision_plane_3d.h | 43 + scene/3d/spring_bone_collision_sphere_3d.cpp | 78 + scene/3d/spring_bone_collision_sphere_3d.h | 59 + scene/3d/spring_bone_simulator_3d.cpp | 1601 +++++++++++++++++ scene/3d/spring_bone_simulator_3d.h | 281 +++ scene/register_scene_types.cpp | 10 + 28 files changed, 3853 insertions(+), 11 deletions(-) create mode 100644 doc/classes/SpringBoneCollision3D.xml create mode 100644 doc/classes/SpringBoneCollisionCapsule3D.xml create mode 100644 doc/classes/SpringBoneCollisionPlane3D.xml create mode 100644 doc/classes/SpringBoneCollisionSphere3D.xml create mode 100644 doc/classes/SpringBoneSimulator3D.xml create mode 100644 editor/icons/SpringBoneCollision3D.svg create mode 100644 editor/icons/SpringBoneCollisionCapsule3D.svg create mode 100644 editor/icons/SpringBoneCollisionPlane3D.svg create mode 100644 editor/icons/SpringBoneCollisionSphere3D.svg create mode 100644 editor/icons/SpringBoneSimulator3D.svg create mode 100644 editor/plugins/gizmos/spring_bone_3d_gizmo_plugin.cpp create mode 100644 editor/plugins/gizmos/spring_bone_3d_gizmo_plugin.h create mode 100644 scene/3d/spring_bone_collision_3d.cpp create mode 100644 scene/3d/spring_bone_collision_3d.h create mode 100644 scene/3d/spring_bone_collision_capsule_3d.cpp create mode 100644 scene/3d/spring_bone_collision_capsule_3d.h create mode 100644 scene/3d/spring_bone_collision_plane_3d.cpp create mode 100644 scene/3d/spring_bone_collision_plane_3d.h create mode 100644 scene/3d/spring_bone_collision_sphere_3d.cpp create mode 100644 scene/3d/spring_bone_collision_sphere_3d.h create mode 100644 scene/3d/spring_bone_simulator_3d.cpp create mode 100644 scene/3d/spring_bone_simulator_3d.h diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index 7aad56f89c8..fcabcf14600 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -480,6 +480,15 @@ The 3D editor gizmo color used for [Skeleton3D] nodes. + + The 3D editor gizmo color used for [SpringBoneCollision3D] nodes. + + + The 3D editor gizmo color used for [SpringBoneCollision3D] nodes with inside mode. + + + The 3D editor gizmo color used for [SpringBoneSimulator3D] nodes. + The 3D editor gizmo color used for [AudioStreamPlayer3D]'s emission angle. diff --git a/doc/classes/SpringBoneCollision3D.xml b/doc/classes/SpringBoneCollision3D.xml new file mode 100644 index 00000000000..ea4556674cf --- /dev/null +++ b/doc/classes/SpringBoneCollision3D.xml @@ -0,0 +1,35 @@ + + + + A base class of the collision that interacts with [SpringBoneSimulator3D]. + + + A collision can be a child of [SpringBoneSimulator3D]. If it is not a child of [SpringBoneSimulator3D], it has no effect. + The colliding and sliding are done in the [SpringBoneSimulator3D]'s modification process in order of its collision list which is set by [method SpringBoneSimulator3D.set_collision_path]. If [method SpringBoneSimulator3D.are_all_child_collisions_enabled] is [code]true[/code], the order matches [SceneTree]. + If [member bone] is set, it synchronizes with the bone pose of the ancestor [Skeleton3D], which is done in before the [SpringBoneSimulator3D]'s modification process as the pre-process. + + + + + + + + Get parent [Skeleton3D] node of the parent [SpringBoneSimulator3D] if found. + + + + + + The index of the attached bone. + + + The name of the attached bone. + + + The offset of the position from [Skeleton3D]'s [member bone] pose position. + + + The offset of the rotation from [Skeleton3D]'s [member bone] pose rotation. + + + diff --git a/doc/classes/SpringBoneCollisionCapsule3D.xml b/doc/classes/SpringBoneCollisionCapsule3D.xml new file mode 100644 index 00000000000..9af9aca331a --- /dev/null +++ b/doc/classes/SpringBoneCollisionCapsule3D.xml @@ -0,0 +1,22 @@ + + + + A capsule shape collision that interacts with [SpringBoneSimulator3D]. + + + A capsule shape collision that interacts with [SpringBoneSimulator3D]. + + + + + + The capsule's height. + + + If [code]true[/code], the collision acts to trap the joint within the collision. + + + The capsule's radius. + + + diff --git a/doc/classes/SpringBoneCollisionPlane3D.xml b/doc/classes/SpringBoneCollisionPlane3D.xml new file mode 100644 index 00000000000..d9f7199f5d1 --- /dev/null +++ b/doc/classes/SpringBoneCollisionPlane3D.xml @@ -0,0 +1,11 @@ + + + + A infinite plane collision that interacts with [SpringBoneSimulator3D]. + + + A infinite plane collision that interacts with [SpringBoneSimulator3D]. It is an infinite size XZ plane, and the +Y direction is treated as normal. + + + + diff --git a/doc/classes/SpringBoneCollisionSphere3D.xml b/doc/classes/SpringBoneCollisionSphere3D.xml new file mode 100644 index 00000000000..3ccb42a93e9 --- /dev/null +++ b/doc/classes/SpringBoneCollisionSphere3D.xml @@ -0,0 +1,19 @@ + + + + A sphere shape collision that interacts with [SpringBoneSimulator3D]. + + + A sphere shape collision that interacts with [SpringBoneSimulator3D]. + + + + + + If [code]true[/code], the collision acts to trap the joint within the collision. + + + The sphere's radius. + + + diff --git a/doc/classes/SpringBoneSimulator3D.xml b/doc/classes/SpringBoneSimulator3D.xml new file mode 100644 index 00000000000..6c639105d9e --- /dev/null +++ b/doc/classes/SpringBoneSimulator3D.xml @@ -0,0 +1,658 @@ + + + + A [SkeletonModifier3D] to apply inertial wavering to bone chains. + + + This [SkeletonModifier3D] can be used to wiggle hair, cloth, and tails. This modifier behaves differently from [PhysicalBoneSimulator3D] as it attempts to return the original pose after modification. + If you setup [method set_root_bone] and [method set_end_bone], it is treated as one bone chain. Note that it does not support a branched chain like Y-shaped chains. + When a bone chain is created, an array is generated from the bones that exist in between and listed in the joint list. + Several properties can be applied to each joint, such as [method set_joint_stiffness], [method set_joint_drag], and [method set_joint_gravity]. + For simplicity, you can set values to all joints at the same time by using a [Curve]. If you want to specify detailed values individually, set [method set_individual_config] to [code]true[/code]. + For physical simulation, [SpringBoneSimulator3D] can have children as self-standing collisions that are not related to [PhysicsServer3D], see also [SpringBoneCollision3D]. + + + + + + + + + Returns [code]true[/code] if the all child [SpringBoneCollision3D]s are contained in the collision list at [param index] in the settings. + + + + + + + Clears all collisions from the collision list at [param index] in the settings when [method are_all_child_collisions_enabled] is [code]false[/code]. + + + + + + + Clears all exclude collisions from the collision list at [param index] in the settings when [method are_all_child_collisions_enabled] is [code]true[/code]. + + + + + + Clears all settings. + + + + + + + Returns the center bone index of the bone chain. + + + + + + + Returns the center bone name of the bone chain. + + + + + + + Returns what the center originates from in the bone chain. + + + + + + + Returns the center node path of the bone chain. + + + + + + + Returns the collision count of the bone chain's collision list when [method are_all_child_collisions_enabled] is [code]false[/code]. + + + + + + + + Returns the node path of the [SpringBoneCollision3D] at [param collision] in the bone chain's collision list when [method are_all_child_collisions_enabled] is [code]false[/code]. + + + + + + + Returns the drag force damping curve of the bone chain. + + + + + + + Returns the drag force damping curve of the bone chain. + + + + + + + Returns the end bone index of the bone chain. + + + + + + + Returns the end bone's tail direction of the bone chain when [method is_end_bone_extended] is [code]true[/code]. + + + + + + + Returns the end bone's tail length of the bone chain when [method is_end_bone_extended] is [code]true[/code]. + + + + + + + Returns the end bone name of the bone chain. + + + + + + + Returns the end bone tip radius of the bone chain when [method is_end_bone_extended] is [code]true[/code]. + + + + + + + Returns the exclude collision count of the bone chain's exclude collision list when [method are_all_child_collisions_enabled] is [code]true[/code]. + + + + + + + + Returns the node path of the [SpringBoneCollision3D] at [param collision] in the bone chain's exclude collision list when [method are_all_child_collisions_enabled] is [code]true[/code]. + + + + + + + Returns the gravity amount of the bone chain. + + + + + + + Returns the gravity amount damping curve of the bone chain. + + + + + + + Returns the gravity direction of the bone chain. + + + + + + + + Returns the bone index at [param joint] in the bone chain's joint list. + + + + + + + + Returns the bone name at [param joint] in the bone chain's joint list. + + + + + + + Returns the joint count of the bone chain's joint list. + + + + + + + + Returns the drag force at [param joint] in the bone chain's joint list. + + + + + + + + Returns the gravity amount at [param joint] in the bone chain's joint list. + + + + + + + + Returns the gravity direction at [param joint] in the bone chain's joint list. + + + + + + + + Returns the radius at [param joint] in the bone chain's joint list. + + + + + + + + Returns the rotation axis at [param joint] in the bone chain's joint list. + + + + + + + + Returns the stiffness force at [param joint] in the bone chain's joint list. + + + + + + + Returns the joint radius of the bone chain. + + + + + + + Returns the joint radius damping curve of the bone chain. + + + + + + + Returns the root bone index of the bone chain. + + + + + + + Returns the root bone name of the bone chain. + + + + + + + Returns the rotation axis of the bone chain. + + + + + + + Returns the stiffness force of the bone chain. + + + + + + + Returns the stiffness force damping curve of the bone chain. + + + + + + + Returns [code]true[/code] if the config can be edited individually for each joint. + + + + + + + Returns [code]true[/code] if the end bone is extended to have the tail. + + + + + + Resets a simulating state with respect to the current bone pose. + It is useful to prevent the simulation result getting violent. For example, calling this immediately after a call to [method AnimationPlayer.play] without a fading, or within the previous [signal SkeletonModifier3D.modification_processed] signal if it's condition changes significantly. + + + + + + + + Sets the center bone index of the bone chain. + + + + + + + + Sets the center bone name of the bone chain. + + + + + + + + Sets what the center originates from in the bone chain. + Bone movement is calculated based on the difference in relative distance between center and bone in the previous and next frames. + For example, if the parent [Skeleton3D] is used as the center, the bones are considered to have not moved if the [Skeleton3D] moves in the world. + In this case, only a change in the bone pose is considered to be a bone movement. + + + + + + + + Sets the center node path of the bone chain. + + + + + + + + Sets the number of collisions in the collision list at [param index] in the settings when [method are_all_child_collisions_enabled] is [code]false[/code]. + + + + + + + + + Sets the node path of the [SpringBoneCollision3D] at [param collision] in the bone chain's collision list when [method are_all_child_collisions_enabled] is [code]false[/code]. + + + + + + + + Sets the drag force of the bone chain. The greater the value, the more suppressed the wiggling. + The value is scaled by [method set_drag_damping_curve] and cached in each joint setting in the joint list. + + + + + + + + Sets the drag force damping curve of the bone chain. + + + + + + + + If sets [param enabled] to [code]true[/code], the all child [SpringBoneCollision3D]s are collided and [method set_exclude_collision_path] is enabled as an exclusion list at [param index] in the settings. + If sets [param enabled] to [code]false[/code], you need to manually register all valid collisions with [method set_collision_path]. + + + + + + + + Sets the end bone index of the bone chain. + + + + + + + + Sets the end bone tail direction of the bone chain when [method is_end_bone_extended] is [code]true[/code]. + + + + + + + + Sets the end bone tail length of the bone chain when [method is_end_bone_extended] is [code]true[/code]. + + + + + + + + Sets the end bone name of the bone chain. + [b]Note:[/b] End bone must be the root bone or a child of the root bone. If they are the same, the tail must be extended by [method set_extend_end_bone] to jiggle the bone. + + + + + + + + Sets the end bone tip radius of the bone chain when [method is_end_bone_extended] is [code]true[/code]. + + + + + + + + Sets the number of exclude collisions in the exclude collision list at [param index] in the settings when [method are_all_child_collisions_enabled] is [code]true[/code]. + + + + + + + + + Sets the node path of the [SpringBoneCollision3D] at [param collision] in the bone chain's exclude collision list when [method are_all_child_collisions_enabled] is [code]true[/code]. + + + + + + + + If [param enabled] is [code]true[/code], the end bone is extended to have the tail. + + + + + + + + Sets the gravity amount of the bone chain. + If [param gravity] is not [code]0[/code], the modified pose will not return to the original pose since it is always affected by gravity. + The value is scaled by [method set_gravity_damping_curve] and cached in each joint setting in the joint list. + + + + + + + + Sets the gravity amount damping curve of the bone chain. + + + + + + + + Sets the gravity direction of the bone chain. + The value is cached in each joint setting in the joint list. + + + + + + + + If [param enabled] is [code]true[/code], the config can be edited individually for each joint. + + + + + + + + + Sets the drag force at [param joint] in the bone chain's joint list when [method is_config_individual] is [code]true[/code]. + + + + + + + + + Sets the gravity amount at [param joint] in the bone chain's joint list when [method is_config_individual] is [code]true[/code]. + + + + + + + + + Sets the gravity direction at [param joint] in the bone chain's joint list when [method is_config_individual] is [code]true[/code]. + + + + + + + + + Sets the joint radius at [param joint] in the bone chain's joint list when [method is_config_individual] is [code]true[/code]. + + + + + + + + + Sets the rotation axis at [param joint] in the bone chain's joint list when [method is_config_individual] is [code]true[/code]. + + + + + + + + + Sets the stiffness force at [param joint] in the bone chain's joint list when [method is_config_individual] is [code]true[/code]. + + + + + + + + Sets the joint radius of the bone chain. It is used to move and slide with the [SpringBoneCollision3D] in the collision list. + The value is scaled by [method set_radius_damping_curve] and cached in each joint setting in the joint list. + + + + + + + + Sets the joint radius damping curve of the bone chain. + + + + + + + + Sets the root bone index of the bone chain. + + + + + + + + Sets the root bone name of the bone chain. + + + + + + + + Sets the rotation axis of the bone chain. If sets a specific axis, it acts like a hinge joint. + The value is cached in each joint setting in the joint list. + + + + + + + + Sets the stiffness force of the bone chain. The greater the value, the faster it recovers to its initial pose. + If [param stiffness] is [code]0[/code], the modified pose will not return to the original pose. + The value is scaled by [method set_stiffness_damping_curve] and cached in each joint setting in the joint list. + + + + + + + + Sets the stiffness force damping curve of the bone chain. + + + + + + The number of settings. + + + + + Enumerated value for the +X axis. + + + Enumerated value for the -X axis. + + + Enumerated value for the +Y axis. + + + Enumerated value for the -Y axis. + + + Enumerated value for the +Z axis. + + + Enumerated value for the -Z axis. + + + Enumerated value for the axis from a parent bone to the child bone. + + + The world origin is defined as center. + + + The [Node3D] specified by [method set_center_node] is defined as center. + If [Node3D] is not found, the parent [Skeleton3D] is treated as center. + + + The bone pose origin of the parent [Skeleton3D] specified by [method set_center_bone] is defined as center. + If [Node3D] is not found, the parent [Skeleton3D] is treated as center. + + + Enumerated value for the rotation of the X axis. + + + Enumerated value for the rotation of the Y axis. + + + Enumerated value for the rotation of the Z axis. + + + Enumerated value for the unconstrained rotation. + + + diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index 41992965131..2a8ad890b61 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -2365,6 +2365,7 @@ void EditorInspectorArray::_setup() { Ref numbers_font; int numbers_min_w = 0; + bool unresizable = is_const || read_only; if (numbered) { numbers_font = get_theme_font(SNAME("bold"), EditorStringName(EditorFonts)); @@ -2461,15 +2462,17 @@ void EditorInspectorArray::_setup() { ae.vbox->set_v_size_flags(SIZE_EXPAND_FILL); ae.hbox->add_child(ae.vbox); - ae.erase = memnew(Button); - ae.erase->set_button_icon(get_editor_theme_icon(SNAME("Remove"))); - ae.erase->set_v_size_flags(SIZE_SHRINK_CENTER); - ae.erase->connect(SceneStringName(pressed), callable_mp(this, &EditorInspectorArray::_remove_item).bind(element_position)); - ae.hbox->add_child(ae.erase); + if (!unresizable) { + ae.erase = memnew(Button); + ae.erase->set_button_icon(get_editor_theme_icon(SNAME("Remove"))); + ae.erase->set_v_size_flags(SIZE_SHRINK_CENTER); + ae.erase->connect(SceneStringName(pressed), callable_mp(this, &EditorInspectorArray::_remove_item).bind(element_position)); + ae.hbox->add_child(ae.erase); + } } // Hide/show the add button. - add_button->set_visible(page == max_page); + add_button->set_visible(page == max_page && !unresizable); // Add paginator if there's more than 1 page. if (max_page > 0) { @@ -2587,12 +2590,13 @@ void EditorInspectorArray::_bind_methods() { ADD_SIGNAL(MethodInfo("page_change_request")); } -void EditorInspectorArray::setup_with_move_element_function(Object *p_object, const String &p_label, const StringName &p_array_element_prefix, int p_page, const Color &p_bg_color, bool p_foldable, bool p_movable, bool p_numbered, int p_page_length, const String &p_add_item_text) { +void EditorInspectorArray::setup_with_move_element_function(Object *p_object, const String &p_label, const StringName &p_array_element_prefix, int p_page, const Color &p_bg_color, bool p_foldable, bool p_movable, bool p_is_const, bool p_numbered, int p_page_length, const String &p_add_item_text) { count_property = ""; mode = MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION; array_element_prefix = p_array_element_prefix; page = p_page; movable = p_movable; + is_const = p_is_const; page_length = p_page_length; numbered = p_numbered; @@ -2601,12 +2605,13 @@ void EditorInspectorArray::setup_with_move_element_function(Object *p_object, co _setup(); } -void EditorInspectorArray::setup_with_count_property(Object *p_object, const String &p_label, const StringName &p_count_property, const StringName &p_array_element_prefix, int p_page, const Color &p_bg_color, bool p_foldable, bool p_movable, bool p_numbered, int p_page_length, const String &p_add_item_text, const String &p_swap_method) { +void EditorInspectorArray::setup_with_count_property(Object *p_object, const String &p_label, const StringName &p_count_property, const StringName &p_array_element_prefix, int p_page, const Color &p_bg_color, bool p_foldable, bool p_movable, bool p_is_const, bool p_numbered, int p_page_length, const String &p_add_item_text, const String &p_swap_method) { count_property = p_count_property; mode = MODE_USE_COUNT_PROPERTY; array_element_prefix = p_array_element_prefix; page = p_page; movable = p_movable; + is_const = p_is_const; page_length = p_page_length; numbered = p_numbered; swap_method = p_swap_method; @@ -3442,6 +3447,7 @@ void EditorInspector::update_tree() { int page_size = 5; bool movable = true; + bool is_const = false; bool numbered = false; bool foldable = use_folding; String add_button_text = TTR("Add Element"); @@ -3453,6 +3459,8 @@ void EditorInspector::update_tree() { add_button_text = class_name_components[i].get_slice("=", 1).strip_edges(); } else if (class_name_components[i] == "static") { movable = false; + } else if (class_name_components[i] == "const") { + is_const = true; } else if (class_name_components[i] == "numbered") { numbered = true; } else if (class_name_components[i] == "unfoldable") { @@ -3479,7 +3487,7 @@ void EditorInspector::update_tree() { editor_inspector_array = memnew(EditorInspectorArray(all_read_only)); int page = per_array_page.has(array_element_prefix) ? per_array_page[array_element_prefix] : 0; - editor_inspector_array->setup_with_count_property(object, class_name_components[0], p.name, array_element_prefix, page, c, foldable, movable, numbered, page_size, add_button_text, swap_method); + editor_inspector_array->setup_with_count_property(object, class_name_components[0], p.name, array_element_prefix, page, c, foldable, movable, is_const, numbered, page_size, add_button_text, swap_method); editor_inspector_array->connect("page_change_request", callable_mp(this, &EditorInspector::_page_change_request).bind(array_element_prefix)); } } diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h index 8279c354a46..d703510f71d 100644 --- a/editor/editor_inspector.h +++ b/editor/editor_inspector.h @@ -391,6 +391,7 @@ class EditorInspectorArray : public EditorInspectorSection { bool read_only = false; bool movable = true; + bool is_const = false; bool numbered = false; enum MenuOptions { @@ -457,8 +458,8 @@ protected: static void _bind_methods(); public: - void setup_with_move_element_function(Object *p_object, const String &p_label, const StringName &p_array_element_prefix, int p_page, const Color &p_bg_color, bool p_foldable, bool p_movable = true, bool p_numbered = false, int p_page_length = 5, const String &p_add_item_text = ""); - void setup_with_count_property(Object *p_object, const String &p_label, const StringName &p_count_property, const StringName &p_array_element_prefix, int p_page, const Color &p_bg_color, bool p_foldable, bool p_movable = true, bool p_numbered = false, int p_page_length = 5, const String &p_add_item_text = "", const String &p_swap_method = ""); + void setup_with_move_element_function(Object *p_object, const String &p_label, const StringName &p_array_element_prefix, int p_page, const Color &p_bg_color, bool p_foldable, bool p_movable = true, bool p_is_const = false, bool p_numbered = false, int p_page_length = 5, const String &p_add_item_text = ""); + void setup_with_count_property(Object *p_object, const String &p_label, const StringName &p_count_property, const StringName &p_array_element_prefix, int p_page, const Color &p_bg_color, bool p_foldable, bool p_movable = true, bool p_is_const = false, bool p_numbered = false, int p_page_length = 5, const String &p_add_item_text = "", const String &p_swap_method = ""); VBoxContainer *get_vbox(int p_index); EditorInspectorArray(bool p_read_only); diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index 79454eab6f0..dabd437f5e5 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -795,6 +795,9 @@ void EditorSettings::_load_defaults(Ref p_extra_config) { EDITOR_SETTING_USAGE(Variant::COLOR, PROPERTY_HINT_NONE, "editors/3d_gizmos/gizmo_colors/selected_bone", Color(0.8, 0.3, 0.0), "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) EDITOR_SETTING_USAGE(Variant::COLOR, PROPERTY_HINT_NONE, "editors/3d_gizmos/gizmo_colors/csg", Color(0.0, 0.4, 1, 0.15), "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) EDITOR_SETTING(Variant::COLOR, PROPERTY_HINT_NONE, "editors/3d_gizmos/gizmo_colors/gridmap_grid", Color(0.8, 0.5, 0.1), "") + EDITOR_SETTING_USAGE(Variant::COLOR, PROPERTY_HINT_NONE, "editors/3d_gizmos/gizmo_colors/spring_bone_joint", Color(0.8, 0.9, 0.6), "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) + EDITOR_SETTING_USAGE(Variant::COLOR, PROPERTY_HINT_NONE, "editors/3d_gizmos/gizmo_colors/spring_bone_collision", Color(0.6, 0.8, 0.9), "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) + EDITOR_SETTING_USAGE(Variant::COLOR, PROPERTY_HINT_NONE, "editors/3d_gizmos/gizmo_colors/spring_bone_inside_collision", Color(0.9, 0.6, 0.8), "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) _initial_set("editors/3d_gizmos/gizmo_settings/bone_axis_length", (float)0.1); EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "editors/3d_gizmos/gizmo_settings/bone_shape", 1, "Wire,Octahedron"); EDITOR_SETTING_USAGE(Variant::FLOAT, PROPERTY_HINT_NONE, "editors/3d_gizmos/gizmo_settings/path3d_tilt_disk_size", 0.8, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) diff --git a/editor/icons/SpringBoneCollision3D.svg b/editor/icons/SpringBoneCollision3D.svg new file mode 100644 index 00000000000..564c44a4118 --- /dev/null +++ b/editor/icons/SpringBoneCollision3D.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/editor/icons/SpringBoneCollisionCapsule3D.svg b/editor/icons/SpringBoneCollisionCapsule3D.svg new file mode 100644 index 00000000000..3c433562d8a --- /dev/null +++ b/editor/icons/SpringBoneCollisionCapsule3D.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/editor/icons/SpringBoneCollisionPlane3D.svg b/editor/icons/SpringBoneCollisionPlane3D.svg new file mode 100644 index 00000000000..7ccb674ab1a --- /dev/null +++ b/editor/icons/SpringBoneCollisionPlane3D.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/editor/icons/SpringBoneCollisionSphere3D.svg b/editor/icons/SpringBoneCollisionSphere3D.svg new file mode 100644 index 00000000000..7d39ee952f7 --- /dev/null +++ b/editor/icons/SpringBoneCollisionSphere3D.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/editor/icons/SpringBoneSimulator3D.svg b/editor/icons/SpringBoneSimulator3D.svg new file mode 100644 index 00000000000..eeb37314c39 --- /dev/null +++ b/editor/icons/SpringBoneSimulator3D.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/editor/plugins/gizmos/spring_bone_3d_gizmo_plugin.cpp b/editor/plugins/gizmos/spring_bone_3d_gizmo_plugin.cpp new file mode 100644 index 00000000000..1cb943b0412 --- /dev/null +++ b/editor/plugins/gizmos/spring_bone_3d_gizmo_plugin.cpp @@ -0,0 +1,424 @@ +/**************************************************************************/ +/* spring_bone_3d_gizmo_plugin.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "spring_bone_3d_gizmo_plugin.h" + +#include "editor/editor_settings.h" +#include "scene/3d/spring_bone_collision_capsule_3d.h" +#include "scene/3d/spring_bone_collision_plane_3d.h" +#include "scene/3d/spring_bone_collision_sphere_3d.h" + +// SpringBoneSimulator3D + +SpringBoneSimulator3DGizmoPlugin::SelectionMaterials SpringBoneSimulator3DGizmoPlugin::selection_materials; + +SpringBoneSimulator3DGizmoPlugin::SpringBoneSimulator3DGizmoPlugin() { + selection_materials.unselected_mat.instantiate(); + selection_materials.unselected_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + selection_materials.unselected_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); + selection_materials.unselected_mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); + selection_materials.unselected_mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); + selection_materials.unselected_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); + + selection_materials.selected_mat.instantiate(); + Ref sh; + sh.instantiate(); + sh->set_code(R"( +// Skeleton 3D gizmo bones shader. + +shader_type spatial; +render_mode unshaded, shadows_disabled; +void vertex() { + if (!OUTPUT_IS_SRGB) { + COLOR.rgb = mix( pow((COLOR.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)), COLOR.rgb* (1.0 / 12.92), lessThan(COLOR.rgb,vec3(0.04045)) ); + } + VERTEX = VERTEX; + POSITION = PROJECTION_MATRIX * VIEW_MATRIX * MODEL_MATRIX * vec4(VERTEX.xyz, 1.0); + POSITION.z = mix(POSITION.z, POSITION.w, 0.998); +} +void fragment() { + ALBEDO = COLOR.rgb; + ALPHA = COLOR.a; +} +)"); + selection_materials.selected_mat->set_shader(sh); +} + +SpringBoneSimulator3DGizmoPlugin::~SpringBoneSimulator3DGizmoPlugin() { + selection_materials.unselected_mat.unref(); + selection_materials.selected_mat.unref(); +} + +bool SpringBoneSimulator3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { + return Object::cast_to(p_spatial) != nullptr; +} + +String SpringBoneSimulator3DGizmoPlugin::get_gizmo_name() const { + return "SpringBoneSimulator3D"; +} + +int SpringBoneSimulator3DGizmoPlugin::get_priority() const { + return -1; +} + +void SpringBoneSimulator3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { + SpringBoneSimulator3D *simulator = Object::cast_to(p_gizmo->get_node_3d()); + p_gizmo->clear(); + + if (!simulator->get_setting_count()) { + return; + } + + Skeleton3D *skeleton = simulator->get_skeleton(); + if (!skeleton) { + return; + } + + Ref mesh = get_joints_mesh(skeleton, simulator, p_gizmo->is_selected()); + Transform3D skel_tr = simulator->get_global_transform().inverse() * skeleton->get_global_transform(); + p_gizmo->add_mesh(mesh, Ref(), skel_tr, skeleton->register_skin(skeleton->create_skin_from_rest_transforms())); +} + +Ref SpringBoneSimulator3DGizmoPlugin::get_joints_mesh(Skeleton3D *p_skeleton, SpringBoneSimulator3D *p_simulator, bool p_is_selected) { + Color bone_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/spring_bone_joint"); + + Ref surface_tool; + surface_tool.instantiate(); + surface_tool->begin(Mesh::PRIMITIVE_LINES); + + if (p_is_selected) { + surface_tool->set_material(selection_materials.selected_mat); + } else { + selection_materials.unselected_mat->set_albedo(bone_color); + surface_tool->set_material(selection_materials.unselected_mat); + } + + LocalVector bones; + LocalVector weights; + bones.resize(4); + weights.resize(4); + for (int i = 0; i < 4; i++) { + bones[i] = 0; + weights[i] = 0; + } + weights[0] = 1; + + for (int i = 0; i < p_simulator->get_setting_count(); i++) { + int current_bone = -1; + int prev_bone = -1; + int joint_end = p_simulator->get_joint_count(i) - 1; + for (int j = 0; j <= joint_end; j++) { + current_bone = p_simulator->get_joint_bone(i, j); + if (j > 0) { + Transform3D parent_global_pose = p_skeleton->get_bone_global_rest(prev_bone); + Transform3D global_pose = p_skeleton->get_bone_global_rest(current_bone); + draw_line(surface_tool, parent_global_pose.origin, global_pose.origin, bone_color); + draw_sphere(surface_tool, global_pose.basis, global_pose.origin, p_simulator->get_joint_radius(i, j), bone_color); + } + if (j == joint_end && p_simulator->is_end_bone_extended(i) && p_simulator->get_end_bone_length(i) > 0) { + Vector3 axis = p_simulator->get_end_bone_axis(current_bone, p_simulator->get_end_bone_direction(i)); + if (axis.is_zero_approx()) { + continue; + } + bones[0] = current_bone; + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + Transform3D global_pose = p_skeleton->get_bone_global_rest(current_bone); + axis = global_pose.xform(axis * p_simulator->get_end_bone_length(i)); + draw_line(surface_tool, global_pose.origin, axis, bone_color); + draw_sphere(surface_tool, global_pose.basis, axis, p_simulator->get_end_bone_tip_radius(i), bone_color); + } else { + bones[0] = current_bone; + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + } + prev_bone = current_bone; + } + } + + return surface_tool->commit(); +} + +void SpringBoneSimulator3DGizmoPlugin::draw_sphere(Ref &p_surface_tool, const Basis &p_basis, const Vector3 &p_center, float p_radius, const Color &p_color) { + static const Vector3 VECTOR3_RIGHT = Vector3(1, 0, 0); + static const Vector3 VECTOR3_UP = Vector3(0, 1, 0); + static const Vector3 VECTOR3_FORWARD = Vector3(0, 0, 1); + static const int STEP = 16; + static const float SPPI = Math_TAU / (float)STEP; + + for (int i = 1; i <= STEP; i++) { + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex(p_center + ((p_basis.xform(VECTOR3_UP * p_radius)).rotated(p_basis.xform(VECTOR3_RIGHT), SPPI * ((i - 1) % STEP)))); + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex(p_center + ((p_basis.xform(VECTOR3_UP * p_radius)).rotated(p_basis.xform(VECTOR3_RIGHT), SPPI * (i % STEP)))); + } + for (int i = 1; i <= STEP; i++) { + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex(p_center + ((p_basis.xform(VECTOR3_RIGHT * p_radius)).rotated(p_basis.xform(VECTOR3_FORWARD), SPPI * ((i - 1) % STEP)))); + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex(p_center + ((p_basis.xform(VECTOR3_RIGHT * p_radius)).rotated(p_basis.xform(VECTOR3_FORWARD), SPPI * (i % STEP)))); + } + for (int i = 1; i <= STEP; i++) { + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex(p_center + ((p_basis.xform(VECTOR3_FORWARD * p_radius)).rotated(p_basis.xform(VECTOR3_UP), SPPI * ((i - 1) % STEP)))); + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex(p_center + ((p_basis.xform(VECTOR3_FORWARD * p_radius)).rotated(p_basis.xform(VECTOR3_UP), SPPI * (i % STEP)))); + } +} + +void SpringBoneSimulator3DGizmoPlugin::draw_line(Ref &p_surface_tool, const Vector3 &p_begin_pos, const Vector3 &p_end_pos, const Color &p_color) { + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex(p_begin_pos); + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex(p_end_pos); +} + +// SpringBoneCollision3D + +SpringBoneCollision3DGizmoPlugin::SelectionMaterials SpringBoneCollision3DGizmoPlugin::selection_materials; + +SpringBoneCollision3DGizmoPlugin::SpringBoneCollision3DGizmoPlugin() { + selection_materials.unselected_mat.instantiate(); + selection_materials.unselected_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + selection_materials.unselected_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); + selection_materials.unselected_mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); + selection_materials.unselected_mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); + selection_materials.unselected_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); + + selection_materials.selected_mat.instantiate(); + Ref sh; + sh.instantiate(); + sh->set_code(R"( +// Skeleton 3D gizmo bones shader. + +shader_type spatial; +render_mode unshaded, shadows_disabled; +void vertex() { + if (!OUTPUT_IS_SRGB) { + COLOR.rgb = mix( pow((COLOR.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)), COLOR.rgb* (1.0 / 12.92), lessThan(COLOR.rgb,vec3(0.04045)) ); + } + VERTEX = VERTEX; + POSITION = PROJECTION_MATRIX * VIEW_MATRIX * MODEL_MATRIX * vec4(VERTEX.xyz, 1.0); + POSITION.z = mix(POSITION.z, POSITION.w, 0.998); +} +void fragment() { + ALBEDO = COLOR.rgb; + ALPHA = COLOR.a; +} +)"); + selection_materials.selected_mat->set_shader(sh); +} + +SpringBoneCollision3DGizmoPlugin::~SpringBoneCollision3DGizmoPlugin() { + selection_materials.unselected_mat.unref(); + selection_materials.selected_mat.unref(); +} + +bool SpringBoneCollision3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { + return Object::cast_to(p_spatial) != nullptr; +} + +String SpringBoneCollision3DGizmoPlugin::get_gizmo_name() const { + return "SpringBoneCollision3D"; +} + +int SpringBoneCollision3DGizmoPlugin::get_priority() const { + return -1; +} + +void SpringBoneCollision3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { + SpringBoneCollision3D *collision = Object::cast_to(p_gizmo->get_node_3d()); + p_gizmo->clear(); + + Ref mesh = get_collision_mesh(collision, p_gizmo->is_selected()); + p_gizmo->add_mesh(mesh); +} + +Ref SpringBoneCollision3DGizmoPlugin::get_collision_mesh(SpringBoneCollision3D *p_collision, bool p_is_selected) { + Color collision_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/spring_bone_collision"); + Color inside_collision_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/spring_bone_inside_collision"); + + Ref surface_tool; + surface_tool.instantiate(); + surface_tool->begin(Mesh::PRIMITIVE_LINES); + + if (p_is_selected) { + surface_tool->set_material(selection_materials.selected_mat); + } else { + selection_materials.unselected_mat->set_albedo(collision_color); + surface_tool->set_material(selection_materials.unselected_mat); + } + + SpringBoneCollisionSphere3D *sphere = Object::cast_to(p_collision); + if (sphere) { + draw_sphere(surface_tool, sphere->get_radius(), sphere->is_inside() ? inside_collision_color : collision_color); + return surface_tool->commit(); + } + + SpringBoneCollisionCapsule3D *capsule = Object::cast_to(p_collision); + if (capsule) { + draw_capsule(surface_tool, capsule->get_radius(), capsule->get_height(), capsule->is_inside() ? inside_collision_color : collision_color); + return surface_tool->commit(); + } + + SpringBoneCollisionPlane3D *plane = Object::cast_to(p_collision); + if (plane) { + draw_plane(surface_tool, collision_color); + return surface_tool->commit(); + } + + return surface_tool->commit(); +} + +void SpringBoneCollision3DGizmoPlugin::draw_sphere(Ref &p_surface_tool, float p_radius, const Color &p_color) { + static const Vector3 VECTOR3_RIGHT = Vector3(1, 0, 0); + static const Vector3 VECTOR3_UP = Vector3(0, 1, 0); + static const Vector3 VECTOR3_FORWARD = Vector3(0, 0, 1); + static const int STEP = 16; + static const float SPPI = Math_TAU / (float)STEP; + + for (int i = 1; i <= STEP; i++) { + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex((VECTOR3_UP * p_radius).rotated(VECTOR3_RIGHT, SPPI * ((i - 1) % STEP))); + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex((VECTOR3_UP * p_radius).rotated(VECTOR3_RIGHT, SPPI * (i % STEP))); + } + for (int i = 1; i <= STEP; i++) { + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex((VECTOR3_RIGHT * p_radius).rotated(VECTOR3_FORWARD, SPPI * ((i - 1) % STEP))); + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex((VECTOR3_RIGHT * p_radius).rotated(VECTOR3_FORWARD, SPPI * (i % STEP))); + } + for (int i = 1; i <= STEP; i++) { + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex((VECTOR3_FORWARD * p_radius).rotated(VECTOR3_UP, SPPI * ((i - 1) % STEP))); + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex((VECTOR3_FORWARD * p_radius).rotated(VECTOR3_UP, SPPI * (i % STEP))); + } +} + +void SpringBoneCollision3DGizmoPlugin::draw_capsule(Ref &p_surface_tool, float p_radius, float p_height, const Color &p_color) { + static const Vector3 VECTOR3_RIGHT = Vector3(1, 0, 0); + static const Vector3 VECTOR3_UP = Vector3(0, 1, 0); + static const Vector3 VECTOR3_FORWARD = Vector3(0, 0, 1); + static const int STEP = 16; + static const int HALF_STEP = 8; + static const float SPPI = Math_TAU / (float)STEP; + static const float HALF_PI = Math_PI * 0.5; + + Vector3 top = VECTOR3_UP * (p_height * 0.5 - p_radius); + Vector3 bottom = -top; + + for (int i = 1; i <= STEP; i++) { + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex((i - 1 < HALF_STEP ? top : bottom) + (VECTOR3_UP * p_radius).rotated(VECTOR3_RIGHT, -HALF_PI + SPPI * ((i - 1) % STEP))); + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex((i - 1 < HALF_STEP ? top : bottom) + (VECTOR3_UP * p_radius).rotated(VECTOR3_RIGHT, -HALF_PI + SPPI * (i % STEP))); + } + for (int i = 1; i <= STEP; i++) { + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex((i - 1 < HALF_STEP ? top : bottom) + (VECTOR3_RIGHT * p_radius).rotated(VECTOR3_FORWARD, SPPI * ((i - 1) % STEP))); + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex((i - 1 < HALF_STEP ? top : bottom) + (VECTOR3_RIGHT * p_radius).rotated(VECTOR3_FORWARD, SPPI * (i % STEP))); + } + for (int i = 1; i <= STEP; i++) { + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex(top + (VECTOR3_FORWARD * p_radius).rotated(VECTOR3_UP, SPPI * ((i - 1) % STEP))); + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex(top + (VECTOR3_FORWARD * p_radius).rotated(VECTOR3_UP, SPPI * (i % STEP))); + } + for (int i = 1; i <= STEP; i++) { + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex(bottom + (VECTOR3_FORWARD * p_radius).rotated(VECTOR3_UP, SPPI * ((i - 1) % STEP))); + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex(bottom + (VECTOR3_FORWARD * p_radius).rotated(VECTOR3_UP, SPPI * (i % STEP))); + } + LocalVector directions; + directions.resize(4); + directions[0] = VECTOR3_RIGHT; + directions[1] = -VECTOR3_RIGHT; + directions[2] = VECTOR3_FORWARD; + directions[3] = -VECTOR3_FORWARD; + for (int i = 0; i < 4; i++) { + Vector3 dir = directions[i] * p_radius; + p_surface_tool->set_color(p_color); + p_surface_tool->add_vertex(top + dir); + p_surface_tool->add_vertex(bottom + dir); + } +} + +void SpringBoneCollision3DGizmoPlugin::draw_plane(Ref &p_surface_tool, const Color &p_color) { + static const Vector3 VECTOR3_UP = Vector3(0, 1, 0); + static const float HALF_PI = Math_PI * 0.5; + static const float ARROW_LENGTH = 0.3; + static const float ARROW_HALF_WIDTH = 0.05; + static const float ARROW_TOP_HALF_WIDTH = 0.1; + static const float ARROW_TOP = 0.5; + static const float RECT_SIZE = 1.0; + static const int RECT_STEP_COUNT = 9; + static const float RECT_HALF_SIZE = RECT_SIZE * 0.5; + static const float RECT_STEP = RECT_SIZE / (float)RECT_STEP_COUNT; + + p_surface_tool->set_color(p_color); + + // Draw arrow of the normal. + LocalVector arrow; + arrow.resize(7); + arrow[0] = Vector3(0, ARROW_TOP, 0); + arrow[1] = Vector3(-ARROW_TOP_HALF_WIDTH, ARROW_LENGTH, 0); + arrow[2] = Vector3(-ARROW_HALF_WIDTH, ARROW_LENGTH, 0); + arrow[3] = Vector3(-ARROW_HALF_WIDTH, 0, 0); + arrow[4] = Vector3(ARROW_HALF_WIDTH, 0, 0); + arrow[5] = Vector3(ARROW_HALF_WIDTH, ARROW_LENGTH, 0); + arrow[6] = Vector3(ARROW_TOP_HALF_WIDTH, ARROW_LENGTH, 0); + for (int i = 0; i < 2; i++) { + Basis ma(VECTOR3_UP, HALF_PI * i); + for (uint32_t j = 0; j < arrow.size(); j++) { + Vector3 v1 = arrow[j]; + Vector3 v2 = arrow[(j + 1) % arrow.size()]; + p_surface_tool->add_vertex(ma.xform(v1)); + p_surface_tool->add_vertex(ma.xform(v2)); + } + } + + // Draw dashed line of the rect. + for (int i = 0; i < 4; i++) { + Basis ma(VECTOR3_UP, HALF_PI * i); + for (int j = 0; j < RECT_STEP_COUNT; j++) { + if (j % 2 == 1) { + continue; + } + Vector3 v1 = Vector3(RECT_HALF_SIZE, 0, RECT_HALF_SIZE - RECT_STEP * j); + Vector3 v2 = Vector3(RECT_HALF_SIZE, 0, RECT_HALF_SIZE - RECT_STEP * (j + 1)); + p_surface_tool->add_vertex(ma.xform(v1)); + p_surface_tool->add_vertex(ma.xform(v2)); + } + } +} diff --git a/editor/plugins/gizmos/spring_bone_3d_gizmo_plugin.h b/editor/plugins/gizmos/spring_bone_3d_gizmo_plugin.h new file mode 100644 index 00000000000..8d6be59b5b0 --- /dev/null +++ b/editor/plugins/gizmos/spring_bone_3d_gizmo_plugin.h @@ -0,0 +1,89 @@ +/**************************************************************************/ +/* spring_bone_3d_gizmo_plugin.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef SPRING_BONE_3D_GIZMO_PLUGIN_H +#define SPRING_BONE_3D_GIZMO_PLUGIN_H + +#include "editor/plugins/editor_plugin.h" +#include "editor/plugins/node_3d_editor_plugin.h" +#include "scene/3d/spring_bone_collision_3d.h" +#include "scene/3d/spring_bone_simulator_3d.h" +#include "scene/resources/surface_tool.h" + +class SpringBoneSimulator3DGizmoPlugin : public EditorNode3DGizmoPlugin { + GDCLASS(SpringBoneSimulator3DGizmoPlugin, EditorNode3DGizmoPlugin); + + struct SelectionMaterials { + Ref unselected_mat; + Ref selected_mat; + }; + static SelectionMaterials selection_materials; + +public: + static Ref get_joints_mesh(Skeleton3D *p_skeleton, SpringBoneSimulator3D *p_simulator, bool p_is_selected); + static void draw_sphere(Ref &p_surface_tool, const Basis &p_basis, const Vector3 &p_center, float p_radius, const Color &p_color); + static void draw_line(Ref &p_surface_tool, const Vector3 &p_begin_pos, const Vector3 &p_end_pos, const Color &p_color); + + bool has_gizmo(Node3D *p_spatial) override; + String get_gizmo_name() const override; + int get_priority() const override; + + void redraw(EditorNode3DGizmo *p_gizmo) override; + + SpringBoneSimulator3DGizmoPlugin(); + ~SpringBoneSimulator3DGizmoPlugin(); +}; + +class SpringBoneCollision3DGizmoPlugin : public EditorNode3DGizmoPlugin { + GDCLASS(SpringBoneCollision3DGizmoPlugin, EditorNode3DGizmoPlugin); + + struct SelectionMaterials { + Ref unselected_mat; + Ref selected_mat; + }; + static SelectionMaterials selection_materials; + +public: + static Ref get_collision_mesh(SpringBoneCollision3D *p_collision, bool p_is_selected); + static void draw_sphere(Ref &p_surface_tool, float p_radius, const Color &p_color); + static void draw_capsule(Ref &p_surface_tool, float p_radius, float p_height, const Color &p_color); + static void draw_plane(Ref &p_surface_tool, const Color &p_color); + + bool has_gizmo(Node3D *p_spatial) override; + String get_gizmo_name() const override; + int get_priority() const override; + + void redraw(EditorNode3DGizmo *p_gizmo) override; + + SpringBoneCollision3DGizmoPlugin(); + ~SpringBoneCollision3DGizmoPlugin(); +}; + +#endif // SPRING_BONE_3D_GIZMO_PLUGIN_H diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index b803d2e5d82..28a2bd89c56 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -74,6 +74,7 @@ #include "editor/plugins/gizmos/shape_cast_3d_gizmo_plugin.h" #include "editor/plugins/gizmos/soft_body_3d_gizmo_plugin.h" #include "editor/plugins/gizmos/spring_arm_3d_gizmo_plugin.h" +#include "editor/plugins/gizmos/spring_bone_3d_gizmo_plugin.h" #include "editor/plugins/gizmos/sprite_base_3d_gizmo_plugin.h" #include "editor/plugins/gizmos/vehicle_body_3d_gizmo_plugin.h" #include "editor/plugins/gizmos/visible_on_screen_notifier_3d_gizmo_plugin.h" @@ -8555,6 +8556,8 @@ void Node3DEditor::_register_all_gizmos() { add_gizmo_plugin(Ref(memnew(RayCast3DGizmoPlugin))); add_gizmo_plugin(Ref(memnew(ShapeCast3DGizmoPlugin))); add_gizmo_plugin(Ref(memnew(SpringArm3DGizmoPlugin))); + add_gizmo_plugin(Ref(memnew(SpringBoneCollision3DGizmoPlugin))); + add_gizmo_plugin(Ref(memnew(SpringBoneSimulator3DGizmoPlugin))); add_gizmo_plugin(Ref(memnew(VehicleWheel3DGizmoPlugin))); add_gizmo_plugin(Ref(memnew(VisibleOnScreenNotifier3DGizmoPlugin))); add_gizmo_plugin(Ref(memnew(GPUParticles3DGizmoPlugin))); diff --git a/scene/3d/spring_bone_collision_3d.cpp b/scene/3d/spring_bone_collision_3d.cpp new file mode 100644 index 00000000000..25ad37fecad --- /dev/null +++ b/scene/3d/spring_bone_collision_3d.cpp @@ -0,0 +1,192 @@ +/**************************************************************************/ +/* spring_bone_collision_3d.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "spring_bone_collision_3d.h" + +#include "scene/3d/spring_bone_simulator_3d.h" + +PackedStringArray SpringBoneCollision3D::get_configuration_warnings() const { + PackedStringArray warnings = Node3D::get_configuration_warnings(); + + SpringBoneSimulator3D *parent = Object::cast_to(get_parent()); + if (!parent) { + warnings.push_back(RTR("Parent node should be a SpringBoneSimulator3D node.")); + } + + return warnings; +} + +void SpringBoneCollision3D::_validate_property(PropertyInfo &p_property) const { + if (p_property.name == "bone_name") { + Skeleton3D *sk = get_skeleton(); + if (sk) { + p_property.hint = PROPERTY_HINT_ENUM_SUGGESTION; + p_property.hint_string = sk->get_concatenated_bone_names(); + } else { + p_property.hint = PROPERTY_HINT_NONE; + p_property.hint_string = ""; + } + } else if (bone < 0 && (p_property.name == "position_offset" || p_property.name == "rotation_offset")) { + p_property.usage = PROPERTY_USAGE_NONE; + } +} + +Skeleton3D *SpringBoneCollision3D::get_skeleton() const { + SpringBoneSimulator3D *parent = Object::cast_to(get_parent()); + if (!parent) { + return nullptr; + } + return parent->get_skeleton(); +} + +void SpringBoneCollision3D::set_bone_name(const String &p_name) { + bone_name = p_name; + Skeleton3D *sk = get_skeleton(); + if (sk) { + set_bone(sk->find_bone(bone_name)); + } +} + +String SpringBoneCollision3D::get_bone_name() const { + return bone_name; +} + +void SpringBoneCollision3D::set_bone(int p_bone) { + bone = p_bone; + + Skeleton3D *sk = get_skeleton(); + if (sk) { + if (bone <= -1 || bone >= sk->get_bone_count()) { + WARN_PRINT("Bone index out of range! Cannot connect BoneAttachment to node!"); + bone = -1; + } else { + bone_name = sk->get_bone_name(bone); + } + } + + notify_property_list_changed(); +} + +int SpringBoneCollision3D::get_bone() const { + return bone; +} + +void SpringBoneCollision3D::set_position_offset(const Vector3 &p_offset) { + if (position_offset == p_offset) { + return; + } + position_offset = p_offset; + sync_pose(); +#ifdef TOOLS_ENABLED + update_gizmos(); +#endif // TOOLS_ENABLED +} + +Vector3 SpringBoneCollision3D::get_position_offset() const { + return position_offset; +} + +void SpringBoneCollision3D::set_rotation_offset(const Quaternion &p_offset) { + if (rotation_offset == p_offset) { + return; + } + rotation_offset = p_offset; + sync_pose(); +#ifdef TOOLS_ENABLED + update_gizmos(); +#endif // TOOLS_ENABLED +} + +Quaternion SpringBoneCollision3D::get_rotation_offset() const { + return rotation_offset; +} + +void SpringBoneCollision3D::sync_pose() { + if (bone >= 0) { + Skeleton3D *sk = get_skeleton(); + if (sk) { + Transform3D tr = sk->get_global_transform() * sk->get_bone_global_pose(bone); + tr.origin += tr.basis.get_rotation_quaternion().xform(position_offset); + tr.basis *= Basis(rotation_offset); + set_global_transform(tr); + } + } +} + +Transform3D SpringBoneCollision3D::get_transform_from_skeleton(const Transform3D &p_center) const { + Transform3D gtr = get_global_transform(); + Skeleton3D *sk = get_skeleton(); + if (sk) { + Transform3D tr = sk->get_global_transform(); + gtr = tr.affine_inverse() * p_center * gtr; + } + return gtr; +} + +void SpringBoneCollision3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_skeleton"), &SpringBoneCollision3D::get_skeleton); + + ClassDB::bind_method(D_METHOD("set_bone_name", "bone_name"), &SpringBoneCollision3D::set_bone_name); + ClassDB::bind_method(D_METHOD("get_bone_name"), &SpringBoneCollision3D::get_bone_name); + + ClassDB::bind_method(D_METHOD("set_bone", "bone"), &SpringBoneCollision3D::set_bone); + ClassDB::bind_method(D_METHOD("get_bone"), &SpringBoneCollision3D::get_bone); + + ClassDB::bind_method(D_METHOD("set_position_offset", "offset"), &SpringBoneCollision3D::set_position_offset); + ClassDB::bind_method(D_METHOD("get_position_offset"), &SpringBoneCollision3D::get_position_offset); + + ClassDB::bind_method(D_METHOD("set_rotation_offset", "offset"), &SpringBoneCollision3D::set_rotation_offset); + ClassDB::bind_method(D_METHOD("get_rotation_offset"), &SpringBoneCollision3D::get_rotation_offset); + + ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "bone_name"), "set_bone_name", "get_bone_name"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "bone", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_bone", "get_bone"); + + ADD_GROUP("Offset", ""); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "position_offset"), "set_position_offset", "get_position_offset"); + ADD_PROPERTY(PropertyInfo(Variant::QUATERNION, "rotation_offset"), "set_rotation_offset", "get_rotation_offset"); +} + +Vector3 SpringBoneCollision3D::collide(const Transform3D &p_center, float p_bone_radius, float p_bone_length, const Vector3 &p_current) const { + return _collide(p_center, p_bone_radius, p_bone_length, p_current); +} + +Vector3 SpringBoneCollision3D::_collide(const Transform3D &p_center, float p_bone_radius, float p_bone_length, const Vector3 &p_current) const { + return Vector3(0, 0, 0); +} + +#ifdef TOOLS_ENABLED +void SpringBoneCollision3D::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_EDITOR_PRE_SAVE: { + sync_pose(); + } break; + } +} +#endif // TOOLS_ENABLED diff --git a/scene/3d/spring_bone_collision_3d.h b/scene/3d/spring_bone_collision_3d.h new file mode 100644 index 00000000000..713891e04d5 --- /dev/null +++ b/scene/3d/spring_bone_collision_3d.h @@ -0,0 +1,75 @@ +/**************************************************************************/ +/* spring_bone_collision_3d.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef SPRING_BONE_COLLISION_3D_H +#define SPRING_BONE_COLLISION_3D_H + +#include "scene/3d/skeleton_3d.h" + +class SpringBoneCollision3D : public Node3D { + GDCLASS(SpringBoneCollision3D, Node3D); + + String bone_name; + int bone = -1; + + Vector3 position_offset; + Quaternion rotation_offset; + +protected: + PackedStringArray get_configuration_warnings() const override; + + void _validate_property(PropertyInfo &p_property) const; + static void _bind_methods(); +#ifdef TOOLS_ENABLED + virtual void _notification(int p_what); +#endif // TOOLS_ENABLED + + virtual Vector3 _collide(const Transform3D &p_center, float p_bone_radius, float p_bone_length, const Vector3 &p_current) const; + +public: + Skeleton3D *get_skeleton() const; + + void set_bone_name(const String &p_name); + String get_bone_name() const; + void set_bone(int p_bone); + int get_bone() const; + + void set_position_offset(const Vector3 &p_offset); + Vector3 get_position_offset() const; + void set_rotation_offset(const Quaternion &p_offset); + Quaternion get_rotation_offset() const; + + void sync_pose(); + Transform3D get_transform_from_skeleton(const Transform3D &p_center) const; + + Vector3 collide(const Transform3D &p_center, float p_bone_radius, float p_bone_length, const Vector3 &p_current) const; +}; + +#endif // SPRING_BONE_COLLISION_3D_H diff --git a/scene/3d/spring_bone_collision_capsule_3d.cpp b/scene/3d/spring_bone_collision_capsule_3d.cpp new file mode 100644 index 00000000000..39acc5acebb --- /dev/null +++ b/scene/3d/spring_bone_collision_capsule_3d.cpp @@ -0,0 +1,112 @@ +/**************************************************************************/ +/* spring_bone_collision_capsule_3d.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "spring_bone_collision_capsule_3d.h" + +#include "scene/3d/spring_bone_collision_sphere_3d.h" + +void SpringBoneCollisionCapsule3D::set_radius(float p_radius) { + radius = p_radius; + if (radius > height * 0.5) { + height = radius * 2.0; + } +#ifdef TOOLS_ENABLED + update_gizmos(); +#endif // TOOLS_ENABLED +} + +float SpringBoneCollisionCapsule3D::get_radius() const { + return radius; +} + +void SpringBoneCollisionCapsule3D::set_height(float p_height) { + height = p_height; + if (radius > height * 0.5) { + radius = height * 0.5; + } +#ifdef TOOLS_ENABLED + update_gizmos(); +#endif // TOOLS_ENABLED +} + +float SpringBoneCollisionCapsule3D::get_height() const { + return height; +} + +void SpringBoneCollisionCapsule3D::set_inside(bool p_enabled) { + inside = p_enabled; +#ifdef TOOLS_ENABLED + update_gizmos(); +#endif // TOOLS_ENABLED +} + +bool SpringBoneCollisionCapsule3D::is_inside() const { + return inside; +} + +Pair SpringBoneCollisionCapsule3D::get_head_and_tail(const Transform3D &p_center) const { + static const Vector3 VECTOR3_UP = Vector3(0, 1, 0); + static const Vector3 VECTOR3_DOWN = Vector3(0, -1, 0); + Transform3D tr = get_transform_from_skeleton(p_center); + return Pair(tr.origin + tr.basis.xform(VECTOR3_UP * (height * 0.5 - radius)), tr.origin + tr.basis.xform(VECTOR3_DOWN * (height * 0.5 - radius))); +} + +void SpringBoneCollisionCapsule3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_radius", "radius"), &SpringBoneCollisionCapsule3D::set_radius); + ClassDB::bind_method(D_METHOD("get_radius"), &SpringBoneCollisionCapsule3D::get_radius); + ClassDB::bind_method(D_METHOD("set_height", "height"), &SpringBoneCollisionCapsule3D::set_height); + ClassDB::bind_method(D_METHOD("get_height"), &SpringBoneCollisionCapsule3D::get_height); + ClassDB::bind_method(D_METHOD("set_inside", "enabled"), &SpringBoneCollisionCapsule3D::set_inside); + ClassDB::bind_method(D_METHOD("is_inside"), &SpringBoneCollisionCapsule3D::is_inside); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0,1,0.001,or_greater,suffix:m"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0,1,0.001,or_greater,suffix:m"), "set_height", "get_height"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "inside"), "set_inside", "is_inside"); +} + +Vector3 SpringBoneCollisionCapsule3D::_collide(const Transform3D &p_center, float p_bone_radius, float p_bone_length, const Vector3 &p_current) const { + Pair head_tail = get_head_and_tail(p_center); + Vector3 head = head_tail.first; + Vector3 tail = head_tail.second; + Vector3 p = tail - head; + Vector3 q = p_current - head; + float dot = p.dot(q); + if (dot <= 0) { + return SpringBoneCollisionSphere3D::_collide_sphere(head, radius, inside, p_bone_radius, p_bone_length, p_current); + } + float pls = p.length_squared(); + if (Math::is_zero_approx(pls)) { + return p_current; + } + if (pls <= dot) { + return SpringBoneCollisionSphere3D::_collide_sphere(head + p, radius, inside, p_bone_radius, p_bone_length, p_current); + } + return SpringBoneCollisionSphere3D::_collide_sphere(head + p * (dot / pls), radius, inside, p_bone_radius, p_bone_length, p_current); +} diff --git a/scene/3d/spring_bone_collision_capsule_3d.h b/scene/3d/spring_bone_collision_capsule_3d.h new file mode 100644 index 00000000000..0e24bed6407 --- /dev/null +++ b/scene/3d/spring_bone_collision_capsule_3d.h @@ -0,0 +1,60 @@ +/**************************************************************************/ +/* spring_bone_collision_capsule_3d.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef SPRING_BONE_COLLISION_CAPSULE_3D_H +#define SPRING_BONE_COLLISION_CAPSULE_3D_H + +#include "scene/3d/spring_bone_collision_3d.h" + +class SpringBoneCollisionCapsule3D : public SpringBoneCollision3D { + GDCLASS(SpringBoneCollisionCapsule3D, SpringBoneCollision3D); + + float radius = 0.1; + float height = 0.5; + bool inside = false; + +protected: + static void _bind_methods(); + + virtual Vector3 _collide(const Transform3D &p_center, float p_bone_radius, float p_bone_length, const Vector3 &p_current) const override; + +public: + void set_radius(float p_radius); + float get_radius() const; + void set_height(float p_height); + float get_height() const; + void set_inside(bool p_enabled); + bool is_inside() const; + + // Helper. + Pair get_head_and_tail(const Transform3D &p_center) const; +}; + +#endif // SPRING_BONE_COLLISION_CAPSULE_3D_H diff --git a/scene/3d/spring_bone_collision_plane_3d.cpp b/scene/3d/spring_bone_collision_plane_3d.cpp new file mode 100644 index 00000000000..8fd29834622 --- /dev/null +++ b/scene/3d/spring_bone_collision_plane_3d.cpp @@ -0,0 +1,44 @@ +/**************************************************************************/ +/* spring_bone_collision_plane_3d.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "spring_bone_collision_plane_3d.h" + +Vector3 SpringBoneCollisionPlane3D::_collide(const Transform3D &p_center, float p_bone_radius, float p_bone_length, const Vector3 &p_current) const { + static const Vector3 VECTOR3_UP = Vector3(0, 1, 0); + Transform3D tr = get_transform_from_skeleton(p_center); + Vector3 pos = tr.origin; + Vector3 normal = tr.basis.get_rotation_quaternion().xform(VECTOR3_UP); + Vector3 to_vec = p_current - pos; + float distance = to_vec.dot(normal) - p_bone_radius; + if (distance > 0) { + return p_current; + } + return p_current + normal * -distance; +} diff --git a/scene/3d/spring_bone_collision_plane_3d.h b/scene/3d/spring_bone_collision_plane_3d.h new file mode 100644 index 00000000000..0f3896aa209 --- /dev/null +++ b/scene/3d/spring_bone_collision_plane_3d.h @@ -0,0 +1,43 @@ +/**************************************************************************/ +/* spring_bone_collision_plane_3d.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef SPRING_BONE_COLLISION_PLANE_3D_H +#define SPRING_BONE_COLLISION_PLANE_3D_H + +#include "scene/3d/spring_bone_collision_3d.h" + +class SpringBoneCollisionPlane3D : public SpringBoneCollision3D { + GDCLASS(SpringBoneCollisionPlane3D, SpringBoneCollision3D); + +protected: + virtual Vector3 _collide(const Transform3D &p_center, float p_bone_radius, float p_bone_length, const Vector3 &p_current) const override; +}; + +#endif // SPRING_BONE_COLLISION_PLANE_3D_H diff --git a/scene/3d/spring_bone_collision_sphere_3d.cpp b/scene/3d/spring_bone_collision_sphere_3d.cpp new file mode 100644 index 00000000000..3560932bc93 --- /dev/null +++ b/scene/3d/spring_bone_collision_sphere_3d.cpp @@ -0,0 +1,78 @@ +/**************************************************************************/ +/* spring_bone_collision_sphere_3d.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "spring_bone_collision_sphere_3d.h" + +void SpringBoneCollisionSphere3D::set_radius(float p_radius) { + radius = p_radius; +#ifdef TOOLS_ENABLED + update_gizmos(); +#endif // TOOLS_ENABLED +} + +float SpringBoneCollisionSphere3D::get_radius() const { + return radius; +} + +void SpringBoneCollisionSphere3D::set_inside(bool p_enabled) { + inside = p_enabled; +#ifdef TOOLS_ENABLED + update_gizmos(); +#endif // TOOLS_ENABLED +} + +bool SpringBoneCollisionSphere3D::is_inside() const { + return inside; +} + +void SpringBoneCollisionSphere3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_radius", "radius"), &SpringBoneCollisionSphere3D::set_radius); + ClassDB::bind_method(D_METHOD("get_radius"), &SpringBoneCollisionSphere3D::get_radius); + ClassDB::bind_method(D_METHOD("set_inside", "enabled"), &SpringBoneCollisionSphere3D::set_inside); + ClassDB::bind_method(D_METHOD("is_inside"), &SpringBoneCollisionSphere3D::is_inside); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0,1,0.001,or_greater,suffix:m"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "inside"), "set_inside", "is_inside"); +} + +Vector3 SpringBoneCollisionSphere3D::_collide_sphere(const Vector3 &p_origin, float p_radius, bool p_inside, float p_bone_radius, float p_bone_length, const Vector3 &p_current) { + Vector3 diff = p_current - p_origin; + float length = diff.length(); + float r = p_inside ? p_radius - p_bone_radius : p_bone_radius + p_radius; + float distance = p_inside ? r - length : length - r; + if (distance > 0) { + return p_current; + } + return p_origin + diff.normalized() * r; +} + +Vector3 SpringBoneCollisionSphere3D::_collide(const Transform3D &p_center, float p_bone_radius, float p_bone_length, const Vector3 &p_current) const { + return _collide_sphere(get_transform_from_skeleton(p_center).origin, radius, inside, p_bone_radius, p_bone_length, p_current); +} diff --git a/scene/3d/spring_bone_collision_sphere_3d.h b/scene/3d/spring_bone_collision_sphere_3d.h new file mode 100644 index 00000000000..4de0f93700c --- /dev/null +++ b/scene/3d/spring_bone_collision_sphere_3d.h @@ -0,0 +1,59 @@ +/**************************************************************************/ +/* spring_bone_collision_sphere_3d.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef SPRING_BONE_COLLISION_SPHERE_3D_H +#define SPRING_BONE_COLLISION_SPHERE_3D_H + +#include "scene/3d/spring_bone_collision_3d.h" + +class SpringBoneCollisionCapsule3D; + +class SpringBoneCollisionSphere3D : public SpringBoneCollision3D { + GDCLASS(SpringBoneCollisionSphere3D, SpringBoneCollision3D); + + friend class SpringBoneCollisionCapsule3D; + + float radius = 0.1; + bool inside = false; + +protected: + static void _bind_methods(); + + static Vector3 _collide_sphere(const Vector3 &p_origin, float p_radius, bool p_inside, float p_bone_radius, float p_bone_length, const Vector3 &p_current); + virtual Vector3 _collide(const Transform3D &p_center, float p_bone_radius, float p_bone_length, const Vector3 &p_current) const override; + +public: + void set_radius(float p_radius); + float get_radius() const; + void set_inside(bool p_enabled); + bool is_inside() const; +}; + +#endif // SPRING_BONE_COLLISION_SPHERE_3D_H diff --git a/scene/3d/spring_bone_simulator_3d.cpp b/scene/3d/spring_bone_simulator_3d.cpp new file mode 100644 index 00000000000..db2755288bf --- /dev/null +++ b/scene/3d/spring_bone_simulator_3d.cpp @@ -0,0 +1,1601 @@ +/**************************************************************************/ +/* spring_bone_simulator_3d.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "spring_bone_simulator_3d.h" + +#include "scene/3d/spring_bone_collision_3d.h" + +// Original VRM Spring Bone movement logic was distributed by (c) VRM Consortium. Licensed under the MIT license. + +bool SpringBoneSimulator3D::_set(const StringName &p_path, const Variant &p_value) { + String path = p_path; + + if (path.begins_with("settings/")) { + int which = path.get_slicec('/', 1).to_int(); + String what = path.get_slicec('/', 2); + ERR_FAIL_INDEX_V(which, settings.size(), false); + + if (what == "root_bone_name") { + set_root_bone_name(which, p_value); + } else if (what == "root_bone") { + set_root_bone(which, p_value); + } else if (what == "end_bone_name") { + set_end_bone_name(which, p_value); + } else if (what == "end_bone") { + String opt = path.get_slicec('/', 3); + if (opt.is_empty()) { + set_end_bone(which, p_value); + } else if (opt == "direction") { + set_end_bone_direction(which, static_cast((int)p_value)); + } else if (opt == "length") { + set_end_bone_length(which, p_value); + } else if (opt == "tip_radius") { + set_end_bone_tip_radius(which, p_value); + } else { + return false; + } + } else if (what == "extend_end_bone") { + set_extend_end_bone(which, p_value); + } else if (what == "center_from") { + set_center_from(which, static_cast((int)p_value)); + } else if (what == "center_node") { + set_center_node(which, p_value); + } else if (what == "center_bone") { + set_center_bone(which, p_value); + } else if (what == "center_bone_name") { + set_center_bone_name(which, p_value); + } else if (what == "individual_config") { + set_individual_config(which, p_value); + } else if (what == "rotation_axis") { + set_rotation_axis(which, static_cast((int)p_value)); + } else if (what == "radius") { + String opt = path.get_slicec('/', 3); + if (opt == "value") { + set_radius(which, p_value); + } else if (opt == "damping_curve") { + set_radius_damping_curve(which, p_value); + } else { + return false; + } + } else if (what == "stiffness") { + String opt = path.get_slicec('/', 3); + if (opt == "value") { + set_stiffness(which, p_value); + } else if (opt == "damping_curve") { + set_stiffness_damping_curve(which, p_value); + } else { + return false; + } + } else if (what == "drag") { + String opt = path.get_slicec('/', 3); + if (opt == "value") { + set_drag(which, p_value); + } else if (opt == "damping_curve") { + set_drag_damping_curve(which, p_value); + } else { + return false; + } + } else if (what == "gravity") { + String opt = path.get_slicec('/', 3); + if (opt == "value") { + set_gravity(which, p_value); + } else if (opt == "damping_curve") { + set_gravity_damping_curve(which, p_value); + } else if (opt == "direction") { + set_gravity_direction(which, p_value); + } else { + return false; + } + } else if (what == "enable_all_child_collisions") { + set_enable_all_child_collisions(which, p_value); + } else if (what == "joint_count") { + set_joint_count(which, p_value); + } else if (what == "joints") { + int idx = path.get_slicec('/', 3).to_int(); + String prop = path.get_slicec('/', 4); + if (prop == "bone_name") { + set_joint_bone_name(which, idx, p_value); + } else if (prop == "bone") { + set_joint_bone(which, idx, p_value); + } else if (prop == "rotation_axis") { + set_joint_rotation_axis(which, idx, static_cast((int)p_value)); + } else if (prop == "radius") { + set_joint_radius(which, idx, p_value); + } else if (prop == "stiffness") { + set_joint_stiffness(which, idx, p_value); + } else if (prop == "drag") { + set_joint_drag(which, idx, p_value); + } else if (prop == "gravity") { + set_joint_gravity(which, idx, p_value); + } else if (prop == "gravity_direction") { + set_joint_gravity_direction(which, idx, p_value); + } else { + return false; + } + } else if (what == "exclude_collision_count") { + set_exclude_collision_count(which, p_value); + } else if (what == "exclude_collisions") { + int idx = path.get_slicec('/', 3).to_int(); + set_exclude_collision_path(which, idx, p_value); + } else if (what == "collision_count") { + set_collision_count(which, p_value); + } else if (what == "collisions") { + int idx = path.get_slicec('/', 3).to_int(); + set_collision_path(which, idx, p_value); + } else { + return false; + } + } + return true; +} + +bool SpringBoneSimulator3D::_get(const StringName &p_path, Variant &r_ret) const { + String path = p_path; + + if (path.begins_with("settings/")) { + int which = path.get_slicec('/', 1).to_int(); + String what = path.get_slicec('/', 2); + ERR_FAIL_INDEX_V(which, settings.size(), false); + + if (what == "root_bone_name") { + r_ret = get_root_bone_name(which); + } else if (what == "root_bone") { + r_ret = get_root_bone(which); + } else if (what == "end_bone_name") { + r_ret = get_end_bone_name(which); + } else if (what == "end_bone") { + String opt = path.get_slicec('/', 3); + if (opt.is_empty()) { + r_ret = get_end_bone(which); + } else if (opt == "direction") { + r_ret = (int)get_end_bone_direction(which); + } else if (opt == "length") { + r_ret = get_end_bone_length(which); + } else if (opt == "tip_radius") { + r_ret = get_end_bone_tip_radius(which); + } else { + return false; + } + } else if (what == "extend_end_bone") { + r_ret = is_end_bone_extended(which); + } else if (what == "center_from") { + r_ret = (int)get_center_from(which); + } else if (what == "center_node") { + r_ret = get_center_node(which); + } else if (what == "center_bone") { + r_ret = get_center_bone(which); + } else if (what == "center_bone_name") { + r_ret = get_center_bone_name(which); + } else if (what == "individual_config") { + r_ret = is_config_individual(which); + } else if (what == "rotation_axis") { + r_ret = (int)get_rotation_axis(which); + } else if (what == "radius") { + String opt = path.get_slicec('/', 3); + if (opt == "value") { + r_ret = get_radius(which); + } else if (opt == "damping_curve") { + r_ret = get_radius_damping_curve(which); + } else { + return false; + } + } else if (what == "stiffness") { + String opt = path.get_slicec('/', 3); + if (opt == "value") { + r_ret = get_stiffness(which); + } else if (opt == "damping_curve") { + r_ret = get_stiffness_damping_curve(which); + } else { + return false; + } + } else if (what == "drag") { + String opt = path.get_slicec('/', 3); + if (opt == "value") { + r_ret = get_drag(which); + } else if (opt == "damping_curve") { + r_ret = get_drag_damping_curve(which); + } else { + return false; + } + } else if (what == "gravity") { + String opt = path.get_slicec('/', 3); + if (opt == "value") { + r_ret = get_gravity(which); + } else if (opt == "damping_curve") { + r_ret = get_gravity_damping_curve(which); + } else if (opt == "direction") { + r_ret = get_gravity_direction(which); + } else { + return false; + } + } else if (what == "enable_all_child_collisions") { + r_ret = are_all_child_collisions_enabled(which); + } else if (what == "joint_count") { + r_ret = get_joint_count(which); + } else if (what == "joints") { + int idx = path.get_slicec('/', 3).to_int(); + String prop = path.get_slicec('/', 4); + if (prop == "bone_name") { + r_ret = get_joint_bone_name(which, idx); + } else if (prop == "bone") { + r_ret = get_joint_bone(which, idx); + } else if (prop == "rotation_axis") { + r_ret = (int)get_joint_rotation_axis(which, idx); + } else if (prop == "radius") { + r_ret = get_joint_radius(which, idx); + } else if (prop == "stiffness") { + r_ret = get_joint_stiffness(which, idx); + } else if (prop == "drag") { + r_ret = get_joint_drag(which, idx); + } else if (prop == "gravity") { + r_ret = get_joint_gravity(which, idx); + } else if (prop == "gravity_direction") { + r_ret = get_joint_gravity_direction(which, idx); + } else { + return false; + } + } else if (what == "exclude_collision_count") { + r_ret = get_exclude_collision_count(which); + } else if (what == "exclude_collisions") { + int idx = path.get_slicec('/', 3).to_int(); + r_ret = get_exclude_collision_path(which, idx); + } else if (what == "collision_count") { + r_ret = get_collision_count(which); + } else if (what == "collisions") { + int idx = path.get_slicec('/', 3).to_int(); + r_ret = get_collision_path(which, idx); + } else { + return false; + } + } + return true; +} + +void SpringBoneSimulator3D::_get_property_list(List *p_list) const { + String enum_hint; + Skeleton3D *skeleton = get_skeleton(); + if (skeleton) { + enum_hint = skeleton->get_concatenated_bone_names(); + } + + for (int i = 0; i < settings.size(); i++) { + String path = "settings/" + itos(i) + "/"; + p_list->push_back(PropertyInfo(Variant::STRING, path + "root_bone_name", PROPERTY_HINT_ENUM_SUGGESTION, enum_hint)); + p_list->push_back(PropertyInfo(Variant::INT, path + "root_bone", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); + p_list->push_back(PropertyInfo(Variant::STRING, path + "end_bone_name", PROPERTY_HINT_ENUM_SUGGESTION, enum_hint)); + p_list->push_back(PropertyInfo(Variant::INT, path + "end_bone", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); + p_list->push_back(PropertyInfo(Variant::BOOL, path + "extend_end_bone")); + p_list->push_back(PropertyInfo(Variant::INT, path + "end_bone/direction", PROPERTY_HINT_ENUM, "+X,-X,+Y,-Y,+Z,-Z,FromParent")); + p_list->push_back(PropertyInfo(Variant::FLOAT, path + "end_bone/length", PROPERTY_HINT_RANGE, "0,1,0.001,or_greater,suffix:m")); + p_list->push_back(PropertyInfo(Variant::FLOAT, path + "end_bone/tip_radius", PROPERTY_HINT_RANGE, "0,1,0.001,or_greater,suffix:m")); + p_list->push_back(PropertyInfo(Variant::INT, path + "center_from", PROPERTY_HINT_ENUM, "WorldOrigin,Node,Bone")); + p_list->push_back(PropertyInfo(Variant::NODE_PATH, path + "center_node")); + p_list->push_back(PropertyInfo(Variant::STRING, path + "center_bone_name", PROPERTY_HINT_ENUM_SUGGESTION, enum_hint)); + p_list->push_back(PropertyInfo(Variant::INT, path + "center_bone", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); + p_list->push_back(PropertyInfo(Variant::BOOL, path + "individual_config")); + p_list->push_back(PropertyInfo(Variant::INT, path + "rotation_axis", PROPERTY_HINT_ENUM, "X,Y,Z,All")); + p_list->push_back(PropertyInfo(Variant::FLOAT, path + "radius/value", PROPERTY_HINT_RANGE, "0,1,0.001,or_greater,suffix:m")); + p_list->push_back(PropertyInfo(Variant::OBJECT, path + "radius/damping_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve")); + p_list->push_back(PropertyInfo(Variant::FLOAT, path + "stiffness/value", PROPERTY_HINT_RANGE, "0,4,0.01,or_greater")); + p_list->push_back(PropertyInfo(Variant::OBJECT, path + "stiffness/damping_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve")); + p_list->push_back(PropertyInfo(Variant::FLOAT, path + "drag/value", PROPERTY_HINT_RANGE, "0,1,0.01,or_greater")); + p_list->push_back(PropertyInfo(Variant::OBJECT, path + "drag/damping_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve")); + p_list->push_back(PropertyInfo(Variant::FLOAT, path + "gravity/value", PROPERTY_HINT_RANGE, "0,1,0.01,or_greater")); + p_list->push_back(PropertyInfo(Variant::OBJECT, path + "gravity/damping_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve")); + p_list->push_back(PropertyInfo(Variant::VECTOR3, path + "gravity/direction")); + p_list->push_back(PropertyInfo(Variant::INT, path + "joint_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ARRAY, "Joints," + path + "joints/,static,const")); + for (int j = 0; j < settings[i]->joints.size(); j++) { + String joint_path = path + "joints/" + itos(j) + "/"; + p_list->push_back(PropertyInfo(Variant::STRING, joint_path + "bone_name", PROPERTY_HINT_ENUM_SUGGESTION, enum_hint, PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY | PROPERTY_USAGE_STORAGE)); + p_list->push_back(PropertyInfo(Variant::INT, joint_path + "bone", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_READ_ONLY)); + p_list->push_back(PropertyInfo(Variant::INT, joint_path + "rotation_axis", PROPERTY_HINT_ENUM, "X,Y,Z,All")); + p_list->push_back(PropertyInfo(Variant::FLOAT, joint_path + "radius", PROPERTY_HINT_RANGE, "0,1,0.001,or_greater,suffix:m")); + p_list->push_back(PropertyInfo(Variant::FLOAT, joint_path + "stiffness", PROPERTY_HINT_RANGE, "0,4,0.01,or_greater")); + p_list->push_back(PropertyInfo(Variant::FLOAT, joint_path + "drag", PROPERTY_HINT_RANGE, "0,1,0.01,or_greater")); + p_list->push_back(PropertyInfo(Variant::FLOAT, joint_path + "gravity", PROPERTY_HINT_RANGE, "0,1,0.01,or_greater")); + p_list->push_back(PropertyInfo(Variant::VECTOR3, joint_path + "gravity_direction")); + } + p_list->push_back(PropertyInfo(Variant::BOOL, path + "enable_all_child_collisions")); + p_list->push_back(PropertyInfo(Variant::INT, path + "exclude_collision_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ARRAY, "Exclude Collisions," + path + "exclude_collisions/")); + for (int j = 0; j < settings[i]->exclude_collisions.size(); j++) { + String collision_path = path + "exclude_collisions/" + itos(j); + p_list->push_back(PropertyInfo(Variant::NODE_PATH, collision_path, PROPERTY_HINT_NODE_PATH_VALID_TYPES, "SpringBoneCollision3D")); + } + p_list->push_back(PropertyInfo(Variant::INT, path + "collision_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ARRAY, "Collisions," + path + "collisions/")); + for (int j = 0; j < settings[i]->collisions.size(); j++) { + String collision_path = path + "collisions/" + itos(j); + p_list->push_back(PropertyInfo(Variant::NODE_PATH, collision_path, PROPERTY_HINT_NODE_PATH_VALID_TYPES, "SpringBoneCollision3D")); + } + } + + for (PropertyInfo &E : *p_list) { + _validate_property(E); + } +} + +void SpringBoneSimulator3D::_validate_property(PropertyInfo &p_property) const { + PackedStringArray split = p_property.name.split("/"); + if (split.size() > 2 && split[0] == "settings") { + int which = split[1].to_int(); + + // Extended end bone option. + if (split[2] == "end_bone" && !is_end_bone_extended(which) && split.size() > 3) { + p_property.usage = PROPERTY_USAGE_NONE; + } + + // Center option. + if (get_center_from(which) != CENTER_FROM_BONE && (split[2] == "center_bone" || split[2] == "center_bone_name")) { + p_property.usage = PROPERTY_USAGE_NONE; + } + if (get_center_from(which) != CENTER_FROM_NODE && split[2] == "center_node") { + p_property.usage = PROPERTY_USAGE_NONE; + } + + // Joints option. + if (is_config_individual(which)) { + if (split[2] == "rotation_axis" || split[2] == "radius" || split[2] == "radius_damping_curve" || + split[2] == "stiffness" || split[2] == "stiffness_damping_curve" || + split[2] == "drag" || split[2] == "drag_damping_curve" || + split[2] == "gravity" || split[2] == "gravity_damping_curve" || split[2] == "gravity_direction") { + p_property.usage = PROPERTY_USAGE_NONE; + } + } else { + if (split[2] == "joints" || split[2] == "joint_count") { + // Don't storage them since they are overridden by _update_joints(). + p_property.usage ^= PROPERTY_USAGE_STORAGE; + p_property.usage |= PROPERTY_USAGE_READ_ONLY; + } + } + + // Collisions option. + if (are_all_child_collisions_enabled(which)) { + if (split[2] == "collisions" || split[2] == "collision_count") { + p_property.usage = PROPERTY_USAGE_NONE; + } + } else { + if (split[2] == "exclude_collisions" || split[2] == "exclude_collision_count") { + p_property.usage = PROPERTY_USAGE_NONE; + } + } + } +} + +void SpringBoneSimulator3D::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + set_notify_local_transform(true); // Used for updating gizmo in editor. + } +#endif // TOOLS_ENABLED + _make_collisions_dirty(); + _make_all_joints_dirty(); + } break; +#ifdef TOOLS_ENABLED + case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: { + update_gizmos(); + } break; +#endif // TOOLS_ENABLED + } +} + +// Setting. + +void SpringBoneSimulator3D::set_root_bone_name(int p_index, const String &p_bone_name) { + ERR_FAIL_INDEX(p_index, settings.size()); + settings[p_index]->root_bone_name = p_bone_name; + Skeleton3D *sk = get_skeleton(); + if (sk) { + set_root_bone(p_index, sk->find_bone(settings[p_index]->root_bone_name)); + } +} + +String SpringBoneSimulator3D::get_root_bone_name(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), String()); + return settings[p_index]->root_bone_name; +} + +void SpringBoneSimulator3D::set_root_bone(int p_index, int p_bone) { + ERR_FAIL_INDEX(p_index, settings.size()); + bool changed = settings[p_index]->root_bone != p_bone; + settings[p_index]->root_bone = p_bone; + Skeleton3D *sk = get_skeleton(); + if (sk) { + if (settings[p_index]->root_bone <= -1 || settings[p_index]->root_bone >= sk->get_bone_count()) { + WARN_PRINT("Root bone index out of range!"); + settings[p_index]->root_bone = -1; + } else { + settings[p_index]->root_bone_name = sk->get_bone_name(settings[p_index]->root_bone); + } + } + if (changed) { + _update_joint_array(p_index); + } +} + +int SpringBoneSimulator3D::get_root_bone(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), -1); + return settings[p_index]->root_bone; +} + +void SpringBoneSimulator3D::set_end_bone_name(int p_index, const String &p_bone_name) { + ERR_FAIL_INDEX(p_index, settings.size()); + settings[p_index]->end_bone_name = p_bone_name; + Skeleton3D *sk = get_skeleton(); + if (sk) { + set_end_bone(p_index, sk->find_bone(settings[p_index]->end_bone_name)); + } +} + +String SpringBoneSimulator3D::get_end_bone_name(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), String()); + return settings[p_index]->end_bone_name; +} + +void SpringBoneSimulator3D::set_end_bone(int p_index, int p_bone) { + ERR_FAIL_INDEX(p_index, settings.size()); + bool changed = settings[p_index]->end_bone != p_bone; + settings[p_index]->end_bone = p_bone; + Skeleton3D *sk = get_skeleton(); + if (sk) { + if (settings[p_index]->end_bone <= -1 || settings[p_index]->end_bone >= sk->get_bone_count()) { + WARN_PRINT("End bone index out of range!"); + settings[p_index]->end_bone = -1; + } else { + settings[p_index]->end_bone_name = sk->get_bone_name(settings[p_index]->end_bone); + } + } + if (changed) { + _update_joint_array(p_index); + } +} + +int SpringBoneSimulator3D::get_end_bone(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), -1); + return settings[p_index]->end_bone; +} + +void SpringBoneSimulator3D::set_extend_end_bone(int p_index, bool p_enabled) { + ERR_FAIL_INDEX(p_index, settings.size()); + settings[p_index]->extend_end_bone = p_enabled; + _make_joints_dirty(p_index); + notify_property_list_changed(); +} + +bool SpringBoneSimulator3D::is_end_bone_extended(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), false); + return settings[p_index]->extend_end_bone; +} + +void SpringBoneSimulator3D::set_end_bone_direction(int p_index, BoneDirection p_bone_direction) { + ERR_FAIL_INDEX(p_index, settings.size()); + settings[p_index]->end_bone_direction = p_bone_direction; + _make_joints_dirty(p_index); +} + +SpringBoneSimulator3D::BoneDirection SpringBoneSimulator3D::get_end_bone_direction(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), BONE_DIRECTION_FROM_PARENT); + return settings[p_index]->end_bone_direction; +} + +void SpringBoneSimulator3D::set_end_bone_length(int p_index, float p_length) { + ERR_FAIL_INDEX(p_index, settings.size()); + settings[p_index]->end_bone_length = p_length; + _make_joints_dirty(p_index); +} + +float SpringBoneSimulator3D::get_end_bone_length(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), 0); + return settings[p_index]->end_bone_length; +} + +void SpringBoneSimulator3D::set_end_bone_tip_radius(int p_index, float p_radius) { + ERR_FAIL_INDEX(p_index, settings.size()); + settings[p_index]->end_bone_tip_radius = p_radius; + _make_joints_dirty(p_index); +} + +float SpringBoneSimulator3D::get_end_bone_tip_radius(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), 0); + return settings[p_index]->end_bone_tip_radius; +} + +Vector3 SpringBoneSimulator3D::get_end_bone_axis(int p_end_bone, BoneDirection p_direction) const { + Vector3 axis; + if (p_direction == BONE_DIRECTION_FROM_PARENT) { + Skeleton3D *sk = get_skeleton(); + if (sk) { + axis = sk->get_bone_rest(p_end_bone).basis.xform_inv(sk->get_bone_rest(p_end_bone).origin); + axis.normalize(); + } + } else { + axis = get_vector_from_bone_axis(static_cast((int)p_direction)); + } + return axis; +} + +void SpringBoneSimulator3D::set_center_from(int p_index, CenterFrom p_center_from) { + ERR_FAIL_INDEX(p_index, settings.size()); + bool center_changed = settings[p_index]->center_from != p_center_from; + settings[p_index]->center_from = p_center_from; + if (center_changed) { + reset(); + } + notify_property_list_changed(); +} + +SpringBoneSimulator3D::CenterFrom SpringBoneSimulator3D::get_center_from(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), CENTER_FROM_WORLD_ORIGIN); + return settings[p_index]->center_from; +} + +void SpringBoneSimulator3D::set_center_node(int p_index, const NodePath &p_node_path) { + ERR_FAIL_INDEX(p_index, settings.size()); + bool center_changed = settings[p_index]->center_node != p_node_path; + settings[p_index]->center_node = p_node_path; + if (center_changed) { + reset(); + } +} + +NodePath SpringBoneSimulator3D::get_center_node(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), NodePath()); + return settings[p_index]->center_node; +} + +void SpringBoneSimulator3D::set_center_bone_name(int p_index, const String &p_bone_name) { + ERR_FAIL_INDEX(p_index, settings.size()); + settings[p_index]->center_bone_name = p_bone_name; + Skeleton3D *sk = get_skeleton(); + if (sk) { + set_center_bone(p_index, sk->find_bone(settings[p_index]->center_bone_name)); + } +} + +String SpringBoneSimulator3D::get_center_bone_name(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), String()); + return settings[p_index]->center_bone_name; +} + +void SpringBoneSimulator3D::set_center_bone(int p_index, int p_bone) { + ERR_FAIL_INDEX(p_index, settings.size()); + bool center_changed = settings[p_index]->center_bone != p_bone; + settings[p_index]->center_bone = p_bone; + Skeleton3D *sk = get_skeleton(); + if (sk) { + if (settings[p_index]->center_bone <= -1 || settings[p_index]->center_bone >= sk->get_bone_count()) { + WARN_PRINT("Center bone index out of range!"); + settings[p_index]->center_bone = -1; + } else { + settings[p_index]->center_bone_name = sk->get_bone_name(settings[p_index]->center_bone); + } + } + if (center_changed) { + reset(); + } +} + +int SpringBoneSimulator3D::get_center_bone(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), -1); + return settings[p_index]->center_bone; +} + +void SpringBoneSimulator3D::set_radius(int p_index, float p_radius) { + ERR_FAIL_INDEX(p_index, settings.size()); + if (is_config_individual(p_index)) { + return; // Joint config is individual mode. + } + settings[p_index]->radius = p_radius; + _make_joints_dirty(p_index); +} + +float SpringBoneSimulator3D::get_radius(int p_index) const { + return settings[p_index]->radius; +} + +void SpringBoneSimulator3D::set_radius_damping_curve(int p_index, const Ref &p_damping_curve) { + ERR_FAIL_INDEX(p_index, settings.size()); + if (is_config_individual(p_index)) { + return; // Joint config is individual mode. + } + if (settings[p_index]->radius_damping_curve.is_valid()) { + settings[p_index]->radius_damping_curve->disconnect_changed(callable_mp(this, &SpringBoneSimulator3D::_make_joints_dirty)); + } + settings[p_index]->radius_damping_curve = p_damping_curve; + if (settings[p_index]->radius_damping_curve.is_valid()) { + settings[p_index]->radius_damping_curve->connect_changed(callable_mp(this, &SpringBoneSimulator3D::_make_joints_dirty).bind(p_index)); + } + _make_joints_dirty(p_index); +} + +Ref SpringBoneSimulator3D::get_radius_damping_curve(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), Ref()); + return settings[p_index]->radius_damping_curve; +} + +void SpringBoneSimulator3D::set_stiffness(int p_index, float p_stiffness) { + ERR_FAIL_INDEX(p_index, settings.size()); + if (is_config_individual(p_index)) { + return; // Joint config is individual mode. + } + settings[p_index]->stiffness = p_stiffness; + _make_joints_dirty(p_index); +} + +float SpringBoneSimulator3D::get_stiffness(int p_index) const { + return settings[p_index]->stiffness; +} + +void SpringBoneSimulator3D::set_stiffness_damping_curve(int p_index, const Ref &p_damping_curve) { + ERR_FAIL_INDEX(p_index, settings.size()); + if (is_config_individual(p_index)) { + return; // Joint config is individual mode. + } + if (settings[p_index]->stiffness_damping_curve.is_valid()) { + settings[p_index]->stiffness_damping_curve->disconnect_changed(callable_mp(this, &SpringBoneSimulator3D::_make_joints_dirty)); + } + settings[p_index]->stiffness_damping_curve = p_damping_curve; + if (settings[p_index]->stiffness_damping_curve.is_valid()) { + settings[p_index]->stiffness_damping_curve->connect_changed(callable_mp(this, &SpringBoneSimulator3D::_make_joints_dirty).bind(p_index)); + } + _make_joints_dirty(p_index); +} + +Ref SpringBoneSimulator3D::get_stiffness_damping_curve(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), Ref()); + return settings[p_index]->stiffness_damping_curve; +} + +void SpringBoneSimulator3D::set_drag(int p_index, float p_drag) { + ERR_FAIL_INDEX(p_index, settings.size()); + if (is_config_individual(p_index)) { + return; // Joint config is individual mode. + } + settings[p_index]->drag = p_drag; + _make_joints_dirty(p_index); +} + +float SpringBoneSimulator3D::get_drag(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), 0); + return settings[p_index]->drag; +} + +void SpringBoneSimulator3D::set_drag_damping_curve(int p_index, const Ref &p_damping_curve) { + ERR_FAIL_INDEX(p_index, settings.size()); + if (is_config_individual(p_index)) { + return; // Joint config is individual mode. + } + if (settings[p_index]->drag_damping_curve.is_valid()) { + settings[p_index]->drag_damping_curve->disconnect_changed(callable_mp(this, &SpringBoneSimulator3D::_make_joints_dirty)); + } + settings[p_index]->drag_damping_curve = p_damping_curve; + if (settings[p_index]->drag_damping_curve.is_valid()) { + settings[p_index]->drag_damping_curve->connect_changed(callable_mp(this, &SpringBoneSimulator3D::_make_joints_dirty).bind(p_index)); + } + _make_joints_dirty(p_index); +} + +Ref SpringBoneSimulator3D::get_drag_damping_curve(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), Ref()); + return settings[p_index]->drag_damping_curve; +} + +void SpringBoneSimulator3D::set_gravity(int p_index, float p_gravity) { + ERR_FAIL_INDEX(p_index, settings.size()); + if (is_config_individual(p_index)) { + return; // Joint config is individual mode. + } + settings[p_index]->gravity = p_gravity; + _make_joints_dirty(p_index); +} + +float SpringBoneSimulator3D::get_gravity(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), 0); + return settings[p_index]->gravity; +} + +void SpringBoneSimulator3D::set_gravity_damping_curve(int p_index, const Ref &p_damping_curve) { + ERR_FAIL_INDEX(p_index, settings.size()); + if (is_config_individual(p_index)) { + return; // Joint config is individual mode. + } + if (settings[p_index]->gravity_damping_curve.is_valid()) { + settings[p_index]->gravity_damping_curve->disconnect_changed(callable_mp(this, &SpringBoneSimulator3D::_make_joints_dirty)); + } + settings[p_index]->gravity_damping_curve = p_damping_curve; + if (settings[p_index]->gravity_damping_curve.is_valid()) { + settings[p_index]->gravity_damping_curve->connect_changed(callable_mp(this, &SpringBoneSimulator3D::_make_joints_dirty).bind(p_index)); + } + _make_joints_dirty(p_index); +} + +Ref SpringBoneSimulator3D::get_gravity_damping_curve(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), Ref()); + return settings[p_index]->gravity_damping_curve; +} + +void SpringBoneSimulator3D::set_gravity_direction(int p_index, const Vector3 &p_gravity_direction) { + ERR_FAIL_INDEX(p_index, settings.size()); + ERR_FAIL_COND(p_gravity_direction.is_zero_approx()); + if (is_config_individual(p_index)) { + return; // Joint config is individual mode. + } + settings[p_index]->gravity_direction = p_gravity_direction; + _make_joints_dirty(p_index); +} + +Vector3 SpringBoneSimulator3D::get_gravity_direction(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), Vector3(0, -1, 0)); + return settings[p_index]->gravity_direction; +} + +void SpringBoneSimulator3D::set_rotation_axis(int p_index, RotationAxis p_axis) { + ERR_FAIL_INDEX(p_index, settings.size()); + if (is_config_individual(p_index)) { + return; // Joint config is individual mode. + } + settings[p_index]->rotation_axis = p_axis; + _make_joints_dirty(p_index); +} + +SpringBoneSimulator3D::RotationAxis SpringBoneSimulator3D::get_rotation_axis(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), ROTATION_AXIS_ALL); + return settings[p_index]->rotation_axis; +} + +void SpringBoneSimulator3D::set_setting_count(int p_count) { + ERR_FAIL_COND(p_count < 0); + int delta = p_count - settings.size() + 1; + settings.resize(p_count); + if (delta > 1) { + for (int i = 1; i < delta; i++) { + settings.write[p_count - i] = memnew(SpringBone3DSetting); + } + } + notify_property_list_changed(); +} + +int SpringBoneSimulator3D::get_setting_count() const { + return settings.size(); +} + +void SpringBoneSimulator3D::clear_settings() { + set_setting_count(0); +} + +// Individual joints. + +void SpringBoneSimulator3D::set_individual_config(int p_index, bool p_enabled) { + ERR_FAIL_INDEX(p_index, settings.size()); + settings[p_index]->individual_config = p_enabled; + _make_joints_dirty(p_index); + notify_property_list_changed(); +} + +bool SpringBoneSimulator3D::is_config_individual(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), false); + return settings[p_index]->individual_config; +} + +void SpringBoneSimulator3D::set_joint_bone_name(int p_index, int p_joint, const String &p_bone_name) { + ERR_FAIL_INDEX(p_index, settings.size()); + Vector &joints = settings[p_index]->joints; + ERR_FAIL_INDEX(p_joint, joints.size()); + joints[p_joint]->bone_name = p_bone_name; + Skeleton3D *sk = get_skeleton(); + if (sk) { + set_joint_bone(p_index, p_joint, sk->find_bone(joints[p_joint]->bone_name)); + } +} + +String SpringBoneSimulator3D::get_joint_bone_name(int p_index, int p_joint) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), String()); + Vector joints = settings[p_index]->joints; + ERR_FAIL_INDEX_V(p_joint, joints.size(), String()); + return joints[p_joint]->bone_name; +} + +void SpringBoneSimulator3D::set_joint_bone(int p_index, int p_joint, int p_bone) { + ERR_FAIL_INDEX(p_index, settings.size()); + Vector &joints = settings[p_index]->joints; + ERR_FAIL_INDEX(p_joint, joints.size()); + joints[p_joint]->bone = p_bone; + Skeleton3D *sk = get_skeleton(); + if (sk) { + if (joints[p_joint]->bone <= -1 || joints[p_joint]->bone >= sk->get_bone_count()) { + WARN_PRINT("Joint bone index out of range!"); + joints[p_joint]->bone = -1; + } else { + joints[p_joint]->bone_name = sk->get_bone_name(joints[p_joint]->bone); + } + } +} + +int SpringBoneSimulator3D::get_joint_bone(int p_index, int p_joint) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), -1); + Vector joints = settings[p_index]->joints; + ERR_FAIL_INDEX_V(p_joint, joints.size(), -1); + return joints[p_joint]->bone; +} + +void SpringBoneSimulator3D::set_joint_radius(int p_index, int p_joint, float p_radius) { + ERR_FAIL_INDEX(p_index, settings.size()); + if (!is_config_individual(p_index)) { + return; // Joints are read-only. + } + Vector &joints = settings[p_index]->joints; + ERR_FAIL_INDEX(p_joint, joints.size()); + joints[p_joint]->radius = p_radius; +} + +float SpringBoneSimulator3D::get_joint_radius(int p_index, int p_joint) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), 0); + Vector joints = settings[p_index]->joints; + ERR_FAIL_INDEX_V(p_joint, joints.size(), 0); + return joints[p_joint]->radius; +} + +void SpringBoneSimulator3D::set_joint_stiffness(int p_index, int p_joint, float p_stiffness) { + ERR_FAIL_INDEX(p_index, settings.size()); + if (!is_config_individual(p_index)) { + return; // Joints are read-only. + } + Vector &joints = settings[p_index]->joints; + ERR_FAIL_INDEX(p_joint, joints.size()); + joints[p_joint]->stiffness = p_stiffness; +} + +float SpringBoneSimulator3D::get_joint_stiffness(int p_index, int p_joint) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), 0); + Vector joints = settings[p_index]->joints; + ERR_FAIL_INDEX_V(p_joint, joints.size(), 0); + return joints[p_joint]->stiffness; +} + +void SpringBoneSimulator3D::set_joint_drag(int p_index, int p_joint, float p_drag) { + ERR_FAIL_INDEX(p_index, settings.size()); + if (!is_config_individual(p_index)) { + return; // Joints are read-only. + } + Vector &joints = settings[p_index]->joints; + ERR_FAIL_INDEX(p_joint, joints.size()); + joints[p_joint]->drag = p_drag; +} + +float SpringBoneSimulator3D::get_joint_drag(int p_index, int p_joint) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), 0); + Vector joints = settings[p_index]->joints; + ERR_FAIL_INDEX_V(p_joint, joints.size(), 0); + return joints[p_joint]->drag; +} + +void SpringBoneSimulator3D::set_joint_gravity(int p_index, int p_joint, float p_gravity) { + ERR_FAIL_INDEX(p_index, settings.size()); + if (!is_config_individual(p_index)) { + return; // Joints are read-only. + } + Vector &joints = settings[p_index]->joints; + ERR_FAIL_INDEX(p_joint, joints.size()); + joints[p_joint]->gravity = p_gravity; +} + +float SpringBoneSimulator3D::get_joint_gravity(int p_index, int p_joint) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), 0); + Vector joints = settings[p_index]->joints; + ERR_FAIL_INDEX_V(p_joint, joints.size(), 0); + return joints[p_joint]->gravity; +} + +void SpringBoneSimulator3D::set_joint_gravity_direction(int p_index, int p_joint, const Vector3 &p_gravity_direction) { + ERR_FAIL_INDEX(p_index, settings.size()); + ERR_FAIL_COND(p_gravity_direction.is_zero_approx()); + if (!is_config_individual(p_index)) { + return; // Joints are read-only. + } + Vector &joints = settings[p_index]->joints; + ERR_FAIL_INDEX(p_joint, joints.size()); + joints[p_joint]->gravity_direction = p_gravity_direction; +} + +Vector3 SpringBoneSimulator3D::get_joint_gravity_direction(int p_index, int p_joint) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), Vector3(0, -1, 0)); + Vector joints = settings[p_index]->joints; + ERR_FAIL_INDEX_V(p_joint, joints.size(), Vector3(0, -1, 0)); + return joints[p_joint]->gravity_direction; +} + +void SpringBoneSimulator3D::set_joint_rotation_axis(int p_index, int p_joint, RotationAxis p_axis) { + ERR_FAIL_INDEX(p_index, settings.size()); + if (!is_config_individual(p_index)) { + return; // Joints are read-only. + } + Vector &joints = settings[p_index]->joints; + ERR_FAIL_INDEX(p_joint, joints.size()); + joints[p_joint]->rotation_axis = p_axis; +} + +SpringBoneSimulator3D::RotationAxis SpringBoneSimulator3D::get_joint_rotation_axis(int p_index, int p_joint) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), ROTATION_AXIS_ALL); + Vector joints = settings[p_index]->joints; + ERR_FAIL_INDEX_V(p_joint, joints.size(), ROTATION_AXIS_ALL); + return joints[p_joint]->rotation_axis; +} + +void SpringBoneSimulator3D::set_joint_count(int p_index, int p_count) { + ERR_FAIL_INDEX(p_index, settings.size()); + ERR_FAIL_COND(p_count < 0); + Vector &joints = settings[p_index]->joints; + int delta = p_count - joints.size() + 1; + joints.resize(p_count); + if (delta > 1) { + for (int i = 1; i < delta; i++) { + joints.write[p_count - i] = memnew(SpringBone3DJointSetting); + } + } + notify_property_list_changed(); +} + +int SpringBoneSimulator3D::get_joint_count(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), 0); + Vector joints = settings[p_index]->joints; + return joints.size(); +} + +// Individual collisions. + +void SpringBoneSimulator3D::set_enable_all_child_collisions(int p_index, bool p_enabled) { + ERR_FAIL_INDEX(p_index, settings.size()); + settings[p_index]->enable_all_child_collisions = p_enabled; + notify_property_list_changed(); +} + +bool SpringBoneSimulator3D::are_all_child_collisions_enabled(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), false); + return settings[p_index]->enable_all_child_collisions; +} + +void SpringBoneSimulator3D::set_exclude_collision_path(int p_index, int p_collision, const NodePath &p_node_path) { + ERR_FAIL_INDEX(p_index, settings.size()); + if (!are_all_child_collisions_enabled(p_index)) { + return; // Exclude collision list is disabled. + } + Vector &setting_exclude_collisions = settings[p_index]->exclude_collisions; + ERR_FAIL_INDEX(p_collision, setting_exclude_collisions.size()); + setting_exclude_collisions.write[p_collision] = NodePath(); // Reset first. + if (is_inside_tree()) { + Node *node = get_node_or_null(p_node_path); + if (!node) { + _make_collisions_dirty(); + return; + } + node = node->get_parent(); + if (!node || node != this) { + _make_collisions_dirty(); + ERR_FAIL_EDMSG("Collision must be child of current SpringBoneSimulator3D."); + } + } + setting_exclude_collisions.write[p_collision] = p_node_path; + _make_collisions_dirty(); +} + +NodePath SpringBoneSimulator3D::get_exclude_collision_path(int p_index, int p_collision) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), NodePath()); + Vector setting_exclude_collisions = settings[p_index]->exclude_collisions; + ERR_FAIL_INDEX_V(p_collision, setting_exclude_collisions.size(), NodePath()); + return setting_exclude_collisions[p_collision]; +} + +void SpringBoneSimulator3D::set_exclude_collision_count(int p_index, int p_count) { + ERR_FAIL_INDEX(p_index, settings.size()); + if (!are_all_child_collisions_enabled(p_index)) { + return; // Exclude collision list is disabled. + } + Vector &setting_exclude_collisions = settings[p_index]->exclude_collisions; + setting_exclude_collisions.resize(p_count); + _make_collisions_dirty(); + notify_property_list_changed(); +} + +int SpringBoneSimulator3D::get_exclude_collision_count(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), 0); + Vector setting_exclude_collisions = settings[p_index]->exclude_collisions; + return setting_exclude_collisions.size(); +} + +void SpringBoneSimulator3D::clear_exclude_collisions(int p_index) { + ERR_FAIL_INDEX(p_index, settings.size()); + if (!are_all_child_collisions_enabled(p_index)) { + return; // Exclude collision list is disabled. + } + set_exclude_collision_count(p_index, 0); +} + +void SpringBoneSimulator3D::set_collision_path(int p_index, int p_collision, const NodePath &p_node_path) { + ERR_FAIL_INDEX(p_index, settings.size()); + if (are_all_child_collisions_enabled(p_index)) { + return; // Collision list is disabled. + } + Vector &setting_collisions = settings[p_index]->collisions; + ERR_FAIL_INDEX(p_collision, setting_collisions.size()); + setting_collisions.write[p_collision] = NodePath(); // Reset first. + if (is_inside_tree()) { + Node *node = get_node_or_null(p_node_path); + if (!node) { + _make_collisions_dirty(); + return; + } + node = node->get_parent(); + if (!node || node != this) { + _make_collisions_dirty(); + ERR_FAIL_EDMSG("Collision must be child of current SpringBoneSimulator3D."); + } + } + setting_collisions.write[p_collision] = p_node_path; + _make_collisions_dirty(); +} + +NodePath SpringBoneSimulator3D::get_collision_path(int p_index, int p_collision) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), NodePath()); + Vector setting_collisions = settings[p_index]->collisions; + ERR_FAIL_INDEX_V(p_collision, setting_collisions.size(), NodePath()); + return setting_collisions[p_collision]; +} + +void SpringBoneSimulator3D::set_collision_count(int p_index, int p_count) { + ERR_FAIL_INDEX(p_index, settings.size()); + if (are_all_child_collisions_enabled(p_index)) { + return; // Collision list is disabled. + } + Vector &setting_collisions = settings[p_index]->collisions; + setting_collisions.resize(p_count); + _make_collisions_dirty(); + notify_property_list_changed(); +} + +int SpringBoneSimulator3D::get_collision_count(int p_index) const { + ERR_FAIL_INDEX_V(p_index, settings.size(), 0); + Vector setting_collisions = settings[p_index]->collisions; + return setting_collisions.size(); +} + +void SpringBoneSimulator3D::clear_collisions(int p_index) { + ERR_FAIL_INDEX(p_index, settings.size()); + if (are_all_child_collisions_enabled(p_index)) { + return; // Collision list is disabled. + } + set_collision_count(p_index, 0); +} + +LocalVector SpringBoneSimulator3D::get_valid_collision_instance_ids(int p_index) { + ERR_FAIL_INDEX_V(p_index, settings.size(), LocalVector()); + if (collisions_dirty) { + _find_collisions(); + } + return settings[p_index]->cached_collisions; +} + +void SpringBoneSimulator3D::_bind_methods() { + // Setting. + ClassDB::bind_method(D_METHOD("set_root_bone_name", "index", "bone_name"), &SpringBoneSimulator3D::set_root_bone_name); + ClassDB::bind_method(D_METHOD("get_root_bone_name", "index"), &SpringBoneSimulator3D::get_root_bone_name); + ClassDB::bind_method(D_METHOD("set_root_bone", "index", "bone"), &SpringBoneSimulator3D::set_root_bone); + ClassDB::bind_method(D_METHOD("get_root_bone", "index"), &SpringBoneSimulator3D::get_root_bone); + + ClassDB::bind_method(D_METHOD("set_end_bone_name", "index", "bone_name"), &SpringBoneSimulator3D::set_end_bone_name); + ClassDB::bind_method(D_METHOD("get_end_bone_name", "index"), &SpringBoneSimulator3D::get_end_bone_name); + ClassDB::bind_method(D_METHOD("set_end_bone", "index", "bone"), &SpringBoneSimulator3D::set_end_bone); + ClassDB::bind_method(D_METHOD("get_end_bone", "index"), &SpringBoneSimulator3D::get_end_bone); + + ClassDB::bind_method(D_METHOD("set_extend_end_bone", "index", "enabled"), &SpringBoneSimulator3D::set_extend_end_bone); + ClassDB::bind_method(D_METHOD("is_end_bone_extended", "index"), &SpringBoneSimulator3D::is_end_bone_extended); + ClassDB::bind_method(D_METHOD("set_end_bone_direction", "index", "bone_direction"), &SpringBoneSimulator3D::set_end_bone_direction); + ClassDB::bind_method(D_METHOD("get_end_bone_direction", "index"), &SpringBoneSimulator3D::get_end_bone_direction); + ClassDB::bind_method(D_METHOD("set_end_bone_length", "index", "length"), &SpringBoneSimulator3D::set_end_bone_length); + ClassDB::bind_method(D_METHOD("get_end_bone_length", "index"), &SpringBoneSimulator3D::get_end_bone_length); + ClassDB::bind_method(D_METHOD("set_end_bone_tip_radius", "index", "radius"), &SpringBoneSimulator3D::set_end_bone_tip_radius); + ClassDB::bind_method(D_METHOD("get_end_bone_tip_radius", "index"), &SpringBoneSimulator3D::get_end_bone_tip_radius); + + ClassDB::bind_method(D_METHOD("set_center_from", "index", "center_from"), &SpringBoneSimulator3D::set_center_from); + ClassDB::bind_method(D_METHOD("get_center_from", "index"), &SpringBoneSimulator3D::get_center_from); + ClassDB::bind_method(D_METHOD("set_center_node", "index", "node_path"), &SpringBoneSimulator3D::set_center_node); + ClassDB::bind_method(D_METHOD("get_center_node", "index"), &SpringBoneSimulator3D::get_center_node); + ClassDB::bind_method(D_METHOD("set_center_bone_name", "index", "bone_name"), &SpringBoneSimulator3D::set_center_bone_name); + ClassDB::bind_method(D_METHOD("get_center_bone_name", "index"), &SpringBoneSimulator3D::get_center_bone_name); + ClassDB::bind_method(D_METHOD("set_center_bone", "index", "bone"), &SpringBoneSimulator3D::set_center_bone); + ClassDB::bind_method(D_METHOD("get_center_bone", "index"), &SpringBoneSimulator3D::get_center_bone); + + ClassDB::bind_method(D_METHOD("set_radius", "index", "radius"), &SpringBoneSimulator3D::set_radius); + ClassDB::bind_method(D_METHOD("get_radius", "index"), &SpringBoneSimulator3D::get_radius); + ClassDB::bind_method(D_METHOD("set_rotation_axis", "index", "axis"), &SpringBoneSimulator3D::set_rotation_axis); + ClassDB::bind_method(D_METHOD("get_rotation_axis", "index"), &SpringBoneSimulator3D::get_rotation_axis); + ClassDB::bind_method(D_METHOD("set_radius_damping_curve", "index", "curve"), &SpringBoneSimulator3D::set_radius_damping_curve); + ClassDB::bind_method(D_METHOD("get_radius_damping_curve", "index"), &SpringBoneSimulator3D::get_radius_damping_curve); + ClassDB::bind_method(D_METHOD("set_stiffness", "index", "stiffness"), &SpringBoneSimulator3D::set_stiffness); + ClassDB::bind_method(D_METHOD("get_stiffness", "index"), &SpringBoneSimulator3D::get_stiffness); + ClassDB::bind_method(D_METHOD("set_stiffness_damping_curve", "index", "curve"), &SpringBoneSimulator3D::set_stiffness_damping_curve); + ClassDB::bind_method(D_METHOD("get_stiffness_damping_curve", "index"), &SpringBoneSimulator3D::get_stiffness_damping_curve); + ClassDB::bind_method(D_METHOD("set_drag", "index", "drag"), &SpringBoneSimulator3D::set_drag); + ClassDB::bind_method(D_METHOD("get_drag", "index"), &SpringBoneSimulator3D::get_drag); + ClassDB::bind_method(D_METHOD("set_drag_damping_curve", "index", "curve"), &SpringBoneSimulator3D::set_drag_damping_curve); + ClassDB::bind_method(D_METHOD("get_drag_damping_curve", "index"), &SpringBoneSimulator3D::get_drag_damping_curve); + ClassDB::bind_method(D_METHOD("set_gravity", "index", "gravity"), &SpringBoneSimulator3D::set_gravity); + ClassDB::bind_method(D_METHOD("get_gravity", "index"), &SpringBoneSimulator3D::get_gravity); + ClassDB::bind_method(D_METHOD("set_gravity_damping_curve", "index", "curve"), &SpringBoneSimulator3D::set_gravity_damping_curve); + ClassDB::bind_method(D_METHOD("get_gravity_damping_curve", "index"), &SpringBoneSimulator3D::get_gravity_damping_curve); + ClassDB::bind_method(D_METHOD("set_gravity_direction", "index", "gravity_direction"), &SpringBoneSimulator3D::set_gravity_direction); + ClassDB::bind_method(D_METHOD("get_gravity_direction", "index"), &SpringBoneSimulator3D::get_gravity_direction); + + ClassDB::bind_method(D_METHOD("set_setting_count", "count"), &SpringBoneSimulator3D::set_setting_count); + ClassDB::bind_method(D_METHOD("get_setting_count"), &SpringBoneSimulator3D::get_setting_count); + ClassDB::bind_method(D_METHOD("clear_settings"), &SpringBoneSimulator3D::clear_settings); + + // Individual joints. + ClassDB::bind_method(D_METHOD("set_individual_config", "index", "enabled"), &SpringBoneSimulator3D::set_individual_config); + ClassDB::bind_method(D_METHOD("is_config_individual", "index"), &SpringBoneSimulator3D::is_config_individual); + + ClassDB::bind_method(D_METHOD("get_joint_bone_name", "index", "joint"), &SpringBoneSimulator3D::get_joint_bone_name); + ClassDB::bind_method(D_METHOD("get_joint_bone", "index", "joint"), &SpringBoneSimulator3D::get_joint_bone); + ClassDB::bind_method(D_METHOD("set_joint_rotation_axis", "index", "joint", "axis"), &SpringBoneSimulator3D::set_joint_rotation_axis); + ClassDB::bind_method(D_METHOD("get_joint_rotation_axis", "index", "joint"), &SpringBoneSimulator3D::get_joint_rotation_axis); + ClassDB::bind_method(D_METHOD("set_joint_radius", "index", "joint", "radius"), &SpringBoneSimulator3D::set_joint_radius); + ClassDB::bind_method(D_METHOD("get_joint_radius", "index", "joint"), &SpringBoneSimulator3D::get_joint_radius); + ClassDB::bind_method(D_METHOD("set_joint_stiffness", "index", "joint", "stiffness"), &SpringBoneSimulator3D::set_joint_stiffness); + ClassDB::bind_method(D_METHOD("get_joint_stiffness", "index", "joint"), &SpringBoneSimulator3D::get_joint_stiffness); + ClassDB::bind_method(D_METHOD("set_joint_drag", "index", "joint", "drag"), &SpringBoneSimulator3D::set_joint_drag); + ClassDB::bind_method(D_METHOD("get_joint_drag", "index", "joint"), &SpringBoneSimulator3D::get_joint_drag); + ClassDB::bind_method(D_METHOD("set_joint_gravity", "index", "joint", "gravity"), &SpringBoneSimulator3D::set_joint_gravity); + ClassDB::bind_method(D_METHOD("get_joint_gravity", "index", "joint"), &SpringBoneSimulator3D::get_joint_gravity); + ClassDB::bind_method(D_METHOD("set_joint_gravity_direction", "index", "joint", "gravity_direction"), &SpringBoneSimulator3D::set_joint_gravity_direction); + ClassDB::bind_method(D_METHOD("get_joint_gravity_direction", "index", "joint"), &SpringBoneSimulator3D::get_joint_gravity_direction); + + ClassDB::bind_method(D_METHOD("get_joint_count", "index"), &SpringBoneSimulator3D::get_joint_count); + + // Individual collisions. + ClassDB::bind_method(D_METHOD("set_enable_all_child_collisions", "index", "enabled"), &SpringBoneSimulator3D::set_enable_all_child_collisions); + ClassDB::bind_method(D_METHOD("are_all_child_collisions_enabled", "index"), &SpringBoneSimulator3D::are_all_child_collisions_enabled); + + ClassDB::bind_method(D_METHOD("set_exclude_collision_path", "index", "collision", "node_path"), &SpringBoneSimulator3D::set_exclude_collision_path); + ClassDB::bind_method(D_METHOD("get_exclude_collision_path", "index", "collision"), &SpringBoneSimulator3D::get_exclude_collision_path); + + ClassDB::bind_method(D_METHOD("set_exclude_collision_count", "index", "count"), &SpringBoneSimulator3D::set_exclude_collision_count); + ClassDB::bind_method(D_METHOD("get_exclude_collision_count", "index"), &SpringBoneSimulator3D::get_exclude_collision_count); + ClassDB::bind_method(D_METHOD("clear_exclude_collisions", "index"), &SpringBoneSimulator3D::clear_exclude_collisions); + + ClassDB::bind_method(D_METHOD("set_collision_path", "index", "collision", "node_path"), &SpringBoneSimulator3D::set_collision_path); + ClassDB::bind_method(D_METHOD("get_collision_path", "index", "collision"), &SpringBoneSimulator3D::get_collision_path); + + ClassDB::bind_method(D_METHOD("set_collision_count", "index", "count"), &SpringBoneSimulator3D::set_collision_count); + ClassDB::bind_method(D_METHOD("get_collision_count", "index"), &SpringBoneSimulator3D::get_collision_count); + ClassDB::bind_method(D_METHOD("clear_collisions", "index"), &SpringBoneSimulator3D::clear_collisions); + + // To process manually. + ClassDB::bind_method(D_METHOD("reset"), &SpringBoneSimulator3D::reset); + + ADD_ARRAY_COUNT("Settings", "setting_count", "set_setting_count", "get_setting_count", "settings/"); + + BIND_ENUM_CONSTANT(BONE_DIRECTION_PLUS_X); + BIND_ENUM_CONSTANT(BONE_DIRECTION_MINUS_X); + BIND_ENUM_CONSTANT(BONE_DIRECTION_PLUS_Y); + BIND_ENUM_CONSTANT(BONE_DIRECTION_MINUS_Y); + BIND_ENUM_CONSTANT(BONE_DIRECTION_PLUS_Z); + BIND_ENUM_CONSTANT(BONE_DIRECTION_MINUS_Z); + BIND_ENUM_CONSTANT(BONE_DIRECTION_FROM_PARENT); + + BIND_ENUM_CONSTANT(CENTER_FROM_WORLD_ORIGIN); + BIND_ENUM_CONSTANT(CENTER_FROM_NODE); + BIND_ENUM_CONSTANT(CENTER_FROM_BONE); + + BIND_ENUM_CONSTANT(ROTATION_AXIS_X); + BIND_ENUM_CONSTANT(ROTATION_AXIS_Y); + BIND_ENUM_CONSTANT(ROTATION_AXIS_Z); + BIND_ENUM_CONSTANT(ROTATION_AXIS_ALL); +} + +void SpringBoneSimulator3D::_make_joints_dirty(int p_index) { + ERR_FAIL_INDEX(p_index, settings.size()); + settings[p_index]->joints_dirty = true; + if (joints_dirty) { + return; + } + joints_dirty = true; + callable_mp(this, &SpringBoneSimulator3D::_update_joints).call_deferred(); +} + +void SpringBoneSimulator3D::_make_all_joints_dirty() { + for (int i = 0; i < settings.size(); i++) { + _update_joint_array(i); + } +} + +void SpringBoneSimulator3D::add_child_notify(Node *p_child) { + if (Object::cast_to(p_child)) { + _make_collisions_dirty(); + } +} + +void SpringBoneSimulator3D::move_child_notify(Node *p_child) { + if (Object::cast_to(p_child)) { + _make_collisions_dirty(); + } +} + +void SpringBoneSimulator3D::remove_child_notify(Node *p_child) { + if (Object::cast_to(p_child)) { + _make_collisions_dirty(); + } +} + +void SpringBoneSimulator3D::_find_collisions() { + if (!collisions_dirty) { + return; + } + collisions.clear(); + for (int i = 0; i < get_child_count(); i++) { + SpringBoneCollision3D *c = Object::cast_to(get_child(i)); + if (c) { + collisions.push_back(c->get_instance_id()); + } + } + + bool setting_updated = false; + + for (int i = 0; i < settings.size(); i++) { + LocalVector &cache = settings[i]->cached_collisions; + cache.clear(); + if (!settings[i]->enable_all_child_collisions) { + // Allow list. + Vector &setting_collisions = settings[i]->collisions; + for (int j = 0; j < setting_collisions.size(); j++) { + Node *n = get_node_or_null(setting_collisions[j]); + if (!n) { + continue; + } + ObjectID id = n->get_instance_id(); + if (!collisions.has(id)) { + setting_collisions.write[j] = NodePath(); // Clear path if not found. + } else { + cache.push_back(id); + } + } + } else { + // Deny list. + LocalVector masks; + Vector &setting_exclude_collisions = settings[i]->exclude_collisions; + for (int j = 0; j < setting_exclude_collisions.size(); j++) { + Node *n = get_node_or_null(setting_exclude_collisions[j]); + if (!n) { + continue; + } + ObjectID id = n->get_instance_id(); + int find = collisions.find(id); + if (find < 0) { + setting_exclude_collisions.write[j] = NodePath(); // Clear path if not found. + } else { + masks.push_back((uint32_t)find); + } + } + uint32_t mask_index = 0; + for (uint32_t j = 0; j < collisions.size(); j++) { + if (mask_index < masks.size() && j == masks[mask_index]) { + mask_index++; + continue; + } + cache.push_back(collisions[j]); + } + } + } + + collisions_dirty = false; + + if (setting_updated) { + notify_property_list_changed(); + } +} + +void SpringBoneSimulator3D::_process_collisions() { + for (const ObjectID &oid : collisions) { + Object *t_obj = ObjectDB::get_instance(oid); + if (!t_obj) { + continue; + } + SpringBoneCollision3D *col = Object::cast_to(t_obj); + if (!col) { + continue; + } + col->sync_pose(); + } +} + +void SpringBoneSimulator3D::_make_collisions_dirty() { + collisions_dirty = true; +} + +void SpringBoneSimulator3D::_update_joint_array(int p_index) { + _make_joints_dirty(p_index); + set_joint_count(p_index, 0); + + Skeleton3D *sk = get_skeleton(); + int current_bone = settings[p_index]->end_bone; + int root_bone = settings[p_index]->root_bone; + if (!sk || current_bone < 0 || root_bone < 0) { + return; + } + + // Validation. + bool valid = false; + while (current_bone >= 0) { + if (current_bone == root_bone) { + valid = true; + break; + } + current_bone = sk->get_bone_parent(current_bone); + } + ERR_FAIL_COND_EDMSG(!valid, "End bone must be the same as or a child of root bone."); + + Vector new_joints; + current_bone = settings[p_index]->end_bone; + while (current_bone != root_bone) { + new_joints.push_back(current_bone); + current_bone = sk->get_bone_parent(current_bone); + } + new_joints.push_back(current_bone); + new_joints.reverse(); + + set_joint_count(p_index, new_joints.size()); + for (int i = 0; i < new_joints.size(); i++) { + set_joint_bone(p_index, i, new_joints[i]); + } +} + +void SpringBoneSimulator3D::_update_joints() { + if (!joints_dirty) { + return; + } + for (int i = 0; i < settings.size(); i++) { + if (!settings[i]->joints_dirty) { + continue; + } + if (settings[i]->individual_config) { + settings[i]->joints_dirty = false; + continue; // Abort. + } + Vector &joints = settings[i]->joints; + float unit = joints.size() > 0 ? (1.0 / float(joints.size() - 1)) : 0.0; + for (int j = 0; j < joints.size(); j++) { + float offset = j * unit; + + if (settings[i]->radius_damping_curve.is_valid()) { + joints[j]->radius = settings[i]->radius * settings[i]->radius_damping_curve->sample_baked(offset); + } else { + joints[j]->radius = settings[i]->radius; + } + + if (settings[i]->stiffness_damping_curve.is_valid()) { + joints[j]->stiffness = settings[i]->stiffness * settings[i]->stiffness_damping_curve->sample_baked(offset); + } else { + joints[j]->stiffness = settings[i]->stiffness; + } + + if (settings[i]->drag_damping_curve.is_valid()) { + joints[j]->drag = settings[i]->drag * settings[i]->drag_damping_curve->sample_baked(offset); + } else { + joints[j]->drag = settings[i]->drag; + } + + if (settings[i]->gravity_damping_curve.is_valid()) { + joints[j]->gravity = settings[i]->gravity * settings[i]->gravity_damping_curve->sample_baked(offset); + } else { + joints[j]->gravity = settings[i]->gravity; + } + + joints[j]->gravity_direction = settings[i]->gravity_direction; + joints[j]->rotation_axis = settings[i]->rotation_axis; + } + settings[i]->simulation_dirty = true; + settings[i]->joints_dirty = false; + } + joints_dirty = false; +#ifdef TOOLS_ENABLED + update_gizmos(); +#endif // TOOLS_ENABLED +} + +void SpringBoneSimulator3D::_set_active(bool p_active) { + if (p_active) { + reset(); + } +} + +void SpringBoneSimulator3D::_process_modification() { + Skeleton3D *skeleton = get_skeleton(); + if (!skeleton) { + return; + } + _find_collisions(); + _process_collisions(); + double delta = skeleton->get_modifier_callback_mode_process() == Skeleton3D::MODIFIER_CALLBACK_MODE_PROCESS_IDLE ? skeleton->get_process_delta_time() : skeleton->get_physics_process_delta_time(); + for (int i = 0; i < settings.size(); i++) { + _init_joints(skeleton, settings[i]); + _process_joints(delta, skeleton, settings[i]->joints, get_valid_collision_instance_ids(i), settings[i]->cached_center, settings[i]->cached_inverted_center, settings[i]->cached_inverted_center.basis.get_rotation_quaternion()); + } +} + +void SpringBoneSimulator3D::reset() { + Skeleton3D *skeleton = get_skeleton(); + if (!skeleton) { + return; + } + _find_collisions(); + _process_collisions(); + for (int i = 0; i < settings.size(); i++) { + settings[i]->simulation_dirty = true; + _init_joints(skeleton, settings[i]); + } +} + +void SpringBoneSimulator3D::_init_joints(Skeleton3D *p_skeleton, SpringBone3DSetting *setting) { + if (setting->center_from == CENTER_FROM_WORLD_ORIGIN) { + setting->cached_center = p_skeleton->get_global_transform(); + } else if (setting->center_from == CENTER_FROM_NODE) { + if (setting->center_node == NodePath()) { + setting->cached_center = Transform3D(); + } else { + Node3D *nd = Object::cast_to(get_node_or_null(setting->center_node)); + if (!nd) { + setting->cached_center = Transform3D(); + } else { + setting->cached_center = nd->get_global_transform().affine_inverse() * p_skeleton->get_global_transform(); + } + } + } else { + if (setting->center_bone >= 0) { + setting->cached_center = p_skeleton->get_bone_global_pose(setting->center_bone); + } else { + setting->cached_center = Transform3D(); + } + } + setting->cached_inverted_center = setting->cached_center.affine_inverse(); + + if (!setting->simulation_dirty) { + return; + } + for (int i = 0; i < setting->joints.size(); i++) { + if (setting->joints[i]->verlet) { + memdelete(setting->joints[i]->verlet); + setting->joints[i]->verlet = nullptr; + } + if (i < setting->joints.size() - 1) { + setting->joints[i]->verlet = memnew(SpringBone3DVerletInfo); + Vector3 axis = p_skeleton->get_bone_rest(setting->joints[i + 1]->bone).origin; + setting->joints[i]->verlet->current_tail = setting->cached_center.xform(p_skeleton->get_bone_global_pose(setting->joints[i]->bone).xform(axis)); + setting->joints[i]->verlet->prev_tail = setting->joints[i]->verlet->current_tail; + setting->joints[i]->verlet->forward_vector = axis.normalized(); + setting->joints[i]->verlet->length = axis.length(); + } else if (setting->extend_end_bone && setting->end_bone_length > 0) { + Vector3 axis = get_end_bone_axis(setting->end_bone, setting->end_bone_direction); + if (axis.is_zero_approx()) { + continue; + } + setting->joints[i]->verlet = memnew(SpringBone3DVerletInfo); + setting->joints[i]->verlet->forward_vector = axis; + setting->joints[i]->verlet->length = setting->end_bone_length; + setting->joints[i]->verlet->current_tail = setting->cached_center.xform(p_skeleton->get_bone_global_pose(setting->joints[i]->bone).xform(axis * setting->end_bone_length)); + setting->joints[i]->verlet->prev_tail = setting->joints[i]->verlet->current_tail; + } + } + setting->simulation_dirty = false; +} + +Vector3 SpringBoneSimulator3D::snap_position_to_plane(const Transform3D &p_rest, RotationAxis p_axis, const Vector3 &p_position) { + if (p_axis == ROTATION_AXIS_ALL) { + return p_position; + } + Vector3 result = p_position; + result = p_rest.affine_inverse().xform(result); + result[(int)p_axis] = 0; + result = p_rest.xform(result); + return result; +} + +Vector3 SpringBoneSimulator3D::limit_length(const Vector3 &p_origin, const Vector3 &p_destination, float p_length) { + return p_origin + (p_destination - p_origin).normalized() * p_length; +} + +void SpringBoneSimulator3D::_process_joints(double p_delta, Skeleton3D *p_skeleton, Vector &p_joints, const LocalVector &p_collisions, const Transform3D &p_center_transform, const Transform3D &p_inverted_center_transform, const Quaternion &p_inverted_center_rotation) { + for (int i = 0; i < p_joints.size(); i++) { + SpringBone3DVerletInfo *verlet = p_joints[i]->verlet; + if (!verlet) { + continue; // Means not extended end bone. + } + Transform3D current_global_pose = p_skeleton->get_bone_global_pose(p_joints[i]->bone); + Transform3D current_world_pose = p_center_transform * current_global_pose; + Quaternion current_rot = current_global_pose.basis.get_rotation_quaternion(); + Vector3 current_origin = p_center_transform.xform(current_global_pose.origin); + Vector3 external = p_inverted_center_rotation.xform(p_joints[i]->gravity_direction * p_joints[i]->gravity * p_delta); + + // Integration of velocity by verlet. + Vector3 next_tail = verlet->current_tail + + (verlet->current_tail - verlet->prev_tail) * (1.0 - p_joints[i]->drag) + + p_center_transform.basis.get_rotation_quaternion().xform(current_rot.xform(verlet->forward_vector * (p_joints[i]->stiffness * p_delta)) + external); + // Snap to plane if axis locked. + next_tail = snap_position_to_plane(current_world_pose, p_joints[i]->rotation_axis, next_tail); + // Limit bone length. + next_tail = limit_length(current_origin, next_tail, verlet->length); + + // Collision movement. + for (uint32_t j = 0; j < p_collisions.size(); j++) { + Object *obj = ObjectDB::get_instance(p_collisions[j]); + if (!obj) { + continue; + } + SpringBoneCollision3D *col = Object::cast_to(obj); + if (col) { + // Collider movement should separate from the effect of the center. + next_tail = col->collide(p_center_transform, p_joints[i]->radius, verlet->length, next_tail); + // Snap to plane if axis locked. + next_tail = snap_position_to_plane(current_world_pose, p_joints[i]->rotation_axis, next_tail); + // Limit bone length. + next_tail = limit_length(current_origin, next_tail, verlet->length); + } + } + + // Store current tails for next process. + verlet->prev_tail = verlet->current_tail; + verlet->current_tail = next_tail; + + // Apply rotation. + Vector3 from = current_rot.xform(verlet->forward_vector); + Vector3 to = p_inverted_center_transform.basis.xform(next_tail - current_origin).normalized(); + Quaternion from_to = get_from_to_rotation(from, to); + from_to *= current_rot; + from_to = get_local_pose_rotation(p_skeleton, p_joints[i]->bone, from_to); + p_skeleton->set_bone_pose_rotation(p_joints[i]->bone, from_to); + } +} + +Quaternion SpringBoneSimulator3D::get_local_pose_rotation(Skeleton3D *p_skeleton, int p_bone, const Quaternion &p_global_pose_rotation) { + int parent = p_skeleton->get_bone_parent(p_bone); + if (parent < 0) { + return p_global_pose_rotation; + } + return p_skeleton->get_bone_global_pose(parent).basis.orthonormalized().inverse() * p_global_pose_rotation; +} + +Quaternion SpringBoneSimulator3D::get_from_to_rotation(const Vector3 &p_from, const Vector3 &p_to) { + Vector3 axis = p_from.cross(p_to); + if (axis.is_zero_approx()) { + return Quaternion(0, 0, 0, 1); + } + float angle = p_from.angle_to(p_to); + if (Math::is_zero_approx(angle)) { + angle = 0.0; + } + return Quaternion(axis.normalized(), angle); +} diff --git a/scene/3d/spring_bone_simulator_3d.h b/scene/3d/spring_bone_simulator_3d.h new file mode 100644 index 00000000000..9c9aa913736 --- /dev/null +++ b/scene/3d/spring_bone_simulator_3d.h @@ -0,0 +1,281 @@ +/**************************************************************************/ +/* spring_bone_simulator_3d.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef SPRING_BONE_SIMULATOR_3D_H +#define SPRING_BONE_SIMULATOR_3D_H + +#include "scene/3d/skeleton_modifier_3d.h" + +class SpringBoneSimulator3D : public SkeletonModifier3D { + GDCLASS(SpringBoneSimulator3D, SkeletonModifier3D); + + bool joints_dirty = false; + + LocalVector collisions; // To process collisions for sync position with skeleton. + bool collisions_dirty = false; + void _find_collisions(); + void _process_collisions(); + void _make_collisions_dirty(); + +public: + enum BoneDirection { + BONE_DIRECTION_PLUS_X, + BONE_DIRECTION_MINUS_X, + BONE_DIRECTION_PLUS_Y, + BONE_DIRECTION_MINUS_Y, + BONE_DIRECTION_PLUS_Z, + BONE_DIRECTION_MINUS_Z, + BONE_DIRECTION_FROM_PARENT, + }; + + enum CenterFrom { + CENTER_FROM_WORLD_ORIGIN, + CENTER_FROM_NODE, + CENTER_FROM_BONE, + }; + + enum RotationAxis { + ROTATION_AXIS_X, + ROTATION_AXIS_Y, + ROTATION_AXIS_Z, + ROTATION_AXIS_ALL, + }; + + struct SpringBone3DVerletInfo { + Vector3 prev_tail; + Vector3 current_tail; + Vector3 forward_vector; + float length = 0.0; + }; + + struct SpringBone3DJointSetting { + String bone_name; + int bone = -1; + + RotationAxis rotation_axis = ROTATION_AXIS_ALL; + float radius = 0.1; + float stiffness = 1.0; + float drag = 0.0; + float gravity = 0.0; + Vector3 gravity_direction = Vector3(0, -1, 0); + + // To process. + SpringBone3DVerletInfo *verlet = nullptr; + }; + + struct SpringBone3DSetting { + bool joints_dirty = false; + + String root_bone_name; + int root_bone = -1; + + String end_bone_name; + int end_bone = -1; + + // To make virtual end joint. + bool extend_end_bone = false; + BoneDirection end_bone_direction = BONE_DIRECTION_FROM_PARENT; + float end_bone_length = 0.0; + float end_bone_tip_radius = 0.02; + + CenterFrom center_from = CENTER_FROM_WORLD_ORIGIN; + NodePath center_node; + String center_bone_name; + int center_bone = -1; + + // Cache into joints. + bool individual_config = false; + float radius = 0.02; + Ref radius_damping_curve; + float stiffness = 1.0; + Ref stiffness_damping_curve; + float drag = 0.4; + Ref drag_damping_curve; + float gravity = 0.0; + Ref gravity_damping_curve; + Vector3 gravity_direction = Vector3(0, -1, 0); + RotationAxis rotation_axis = ROTATION_AXIS_ALL; + Vector joints; + + // Cache into collisions. + bool enable_all_child_collisions = true; + Vector collisions; + Vector exclude_collisions; + LocalVector cached_collisions; + + // To process. + bool simulation_dirty = false; + Transform3D cached_center; + Transform3D cached_inverted_center; + }; + +protected: + Vector settings; + + bool _get(const StringName &p_path, Variant &r_ret) const; + bool _set(const StringName &p_path, const Variant &p_value); + void _get_property_list(List *p_list) const; + void _validate_property(PropertyInfo &p_property) const; + + void _notification(int p_what); + + static void _bind_methods(); + + virtual void _set_active(bool p_active) override; + virtual void _process_modification() override; + void _init_joints(Skeleton3D *p_skeleton, SpringBone3DSetting *p_setting); + void _process_joints(double p_delta, Skeleton3D *p_skeleton, Vector &p_joints, const LocalVector &p_collisions, const Transform3D &p_center_transform, const Transform3D &p_inverted_center_transform, const Quaternion &p_inverted_center_rotation); + + void _make_joints_dirty(int p_index); + void _make_all_joints_dirty(); + + void _update_joint_array(int p_index); + void _update_joints(); + + virtual void add_child_notify(Node *p_child) override; + virtual void move_child_notify(Node *p_child) override; + virtual void remove_child_notify(Node *p_child) override; + +public: + // Setting. + void set_root_bone_name(int p_index, const String &p_bone_name); + String get_root_bone_name(int p_index) const; + void set_root_bone(int p_index, int p_bone); + int get_root_bone(int p_index) const; + + void set_end_bone_name(int p_index, const String &p_bone_name); + String get_end_bone_name(int p_index) const; + void set_end_bone(int p_index, int p_bone); + int get_end_bone(int p_index) const; + + void set_extend_end_bone(int p_index, bool p_enabled); + bool is_end_bone_extended(int p_index) const; + void set_end_bone_direction(int p_index, BoneDirection p_bone_direction); + BoneDirection get_end_bone_direction(int p_index) const; + void set_end_bone_length(int p_index, float p_length); + float get_end_bone_length(int p_index) const; + void set_end_bone_tip_radius(int p_index, float p_radius); + float get_end_bone_tip_radius(int p_index) const; + Vector3 get_end_bone_axis(int p_end_bone, BoneDirection p_direction) const; // Helper. + + void set_center_from(int p_index, CenterFrom p_center_from); + CenterFrom get_center_from(int p_index) const; + void set_center_node(int p_index, const NodePath &p_node_path); + NodePath get_center_node(int p_index) const; + void set_center_bone_name(int p_index, const String &p_bone_name); + String get_center_bone_name(int p_index) const; + void set_center_bone(int p_index, int p_bone); + int get_center_bone(int p_index) const; + + void set_rotation_axis(int p_index, RotationAxis p_axis); + RotationAxis get_rotation_axis(int p_index) const; + void set_radius(int p_index, float p_radius); + float get_radius(int p_index) const; + void set_radius_damping_curve(int p_index, const Ref &p_damping_curve); + Ref get_radius_damping_curve(int p_index) const; + void set_stiffness(int p_index, float p_stiffness); + float get_stiffness(int p_index) const; + void set_stiffness_damping_curve(int p_index, const Ref &p_damping_curve); + Ref get_stiffness_damping_curve(int p_index) const; + void set_drag(int p_index, float p_drag); + float get_drag(int p_index) const; + void set_drag_damping_curve(int p_index, const Ref &p_damping_curve); + Ref get_drag_damping_curve(int p_index) const; + void set_gravity(int p_index, float p_gravity); + float get_gravity(int p_index) const; + void set_gravity_damping_curve(int p_index, const Ref &p_damping_curve); + Ref get_gravity_damping_curve(int p_index) const; + void set_gravity_direction(int p_index, const Vector3 &p_gravity_direction); + Vector3 get_gravity_direction(int p_index) const; + + void set_setting_count(int p_count); + int get_setting_count() const; + void clear_settings(); + + // Individual joints. + void set_individual_config(int p_index, bool p_enabled); + bool is_config_individual(int p_index) const; + + void set_joint_bone_name(int p_index, int p_joint, const String &p_bone_name); + String get_joint_bone_name(int p_index, int p_joint) const; + void set_joint_bone(int p_index, int p_joint, int p_bone); + int get_joint_bone(int p_index, int p_joint) const; + + void set_joint_rotation_axis(int p_index, int p_joint, RotationAxis p_axis); + RotationAxis get_joint_rotation_axis(int p_index, int p_joint) const; + void set_joint_radius(int p_index, int p_joint, float p_radius); + float get_joint_radius(int p_index, int p_joint) const; + void set_joint_stiffness(int p_index, int p_joint, float p_stiffness); + float get_joint_stiffness(int p_index, int p_joint) const; + void set_joint_drag(int p_index, int p_joint, float p_drag); + float get_joint_drag(int p_index, int p_joint) const; + void set_joint_gravity(int p_index, int p_joint, float p_gravity); + float get_joint_gravity(int p_index, int p_joint) const; + void set_joint_gravity_direction(int p_index, int p_joint, const Vector3 &p_gravity_direction); + Vector3 get_joint_gravity_direction(int p_index, int p_joint) const; + + void set_joint_count(int p_index, int p_count); + int get_joint_count(int p_index) const; + + // Individual collisions. + void set_enable_all_child_collisions(int p_index, bool p_enabled); + bool are_all_child_collisions_enabled(int p_index) const; + + void set_exclude_collision_path(int p_index, int p_collision, const NodePath &p_node_path); + NodePath get_exclude_collision_path(int p_index, int p_collision) const; + + void set_exclude_collision_count(int p_index, int p_count); + int get_exclude_collision_count(int p_index) const; + void clear_exclude_collisions(int p_index); + + void set_collision_path(int p_index, int p_collision, const NodePath &p_node_path); + NodePath get_collision_path(int p_index, int p_collision) const; + + void set_collision_count(int p_index, int p_count); + int get_collision_count(int p_index) const; + void clear_collisions(int p_index); + + LocalVector get_valid_collision_instance_ids(int p_index); + + // Helper. + static Quaternion get_local_pose_rotation(Skeleton3D *p_skeleton, int p_bone, const Quaternion &p_global_pose_rotation); + static Quaternion get_from_to_rotation(const Vector3 &p_from, const Vector3 &p_to); + static Vector3 snap_position_to_plane(const Transform3D &p_rest, RotationAxis p_axis, const Vector3 &p_position); + static Vector3 limit_length(const Vector3 &p_origin, const Vector3 &p_destination, float p_length); + + // To process manually. + void reset(); +}; + +VARIANT_ENUM_CAST(SpringBoneSimulator3D::BoneDirection); +VARIANT_ENUM_CAST(SpringBoneSimulator3D::CenterFrom); +VARIANT_ENUM_CAST(SpringBoneSimulator3D::RotationAxis); + +#endif // SPRING_BONE_SIMULATOR_3D_H diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 53d8043329c..97a878a96be 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -284,6 +284,11 @@ #include "scene/3d/skeleton_ik_3d.h" #include "scene/3d/skeleton_modifier_3d.h" #include "scene/3d/soft_body_3d.h" +#include "scene/3d/spring_bone_collision_3d.h" +#include "scene/3d/spring_bone_collision_capsule_3d.h" +#include "scene/3d/spring_bone_collision_plane_3d.h" +#include "scene/3d/spring_bone_collision_sphere_3d.h" +#include "scene/3d/spring_bone_simulator_3d.h" #include "scene/3d/sprite_3d.h" #include "scene/3d/visible_on_screen_notifier_3d.h" #include "scene/3d/voxel_gi.h" @@ -600,6 +605,11 @@ void register_scene_types() { GDREGISTER_CLASS(RootMotionView); GDREGISTER_VIRTUAL_CLASS(SkeletonModifier3D); GDREGISTER_CLASS(RetargetModifier3D); + GDREGISTER_CLASS(SpringBoneSimulator3D); + GDREGISTER_VIRTUAL_CLASS(SpringBoneCollision3D); + GDREGISTER_CLASS(SpringBoneCollisionSphere3D); + GDREGISTER_CLASS(SpringBoneCollisionCapsule3D); + GDREGISTER_CLASS(SpringBoneCollisionPlane3D); OS::get_singleton()->yield(); // may take time to init