summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/core_bind.cpp22
-rw-r--r--core/core_bind.h5
-rw-r--r--doc/classes/EditorPlugin.xml19
-rw-r--r--doc/classes/Node3D.xml9
-rw-r--r--doc/classes/Skeleton3D.xml26
-rw-r--r--doc/classes/Thread.xml16
-rw-r--r--editor/animation_track_editor.cpp47
-rw-r--r--editor/animation_track_editor.h1
-rw-r--r--editor/editor_node.cpp16
-rw-r--r--editor/editor_node.h2
-rw-r--r--editor/editor_plugin.cpp8
-rw-r--r--editor/editor_plugin.h10
-rw-r--r--editor/editor_themes.cpp1
-rw-r--r--editor/icons/EditorBoneHandle.svg1
-rw-r--r--editor/icons/ToolBoneSelect.svg1
-rw-r--r--editor/inspector_dock.cpp6
-rw-r--r--editor/plugins/animation_player_editor_plugin.cpp4
-rw-r--r--editor/plugins/animation_player_editor_plugin.h4
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp24
-rw-r--r--editor/plugins/collision_polygon_3d_editor_plugin.cpp28
-rw-r--r--editor/plugins/collision_polygon_3d_editor_plugin.h4
-rw-r--r--editor/plugins/node_3d_editor_gizmos.cpp163
-rw-r--r--editor/plugins/node_3d_editor_gizmos.h12
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp87
-rw-r--r--editor/plugins/node_3d_editor_plugin.h3
-rw-r--r--editor/plugins/path_3d_editor_plugin.cpp22
-rw-r--r--editor/plugins/path_3d_editor_plugin.h2
-rw-r--r--editor/plugins/skeleton_3d_editor_plugin.cpp937
-rw-r--r--editor/plugins/skeleton_3d_editor_plugin.h135
-rw-r--r--editor/plugins/tiles/tile_set_atlas_source_editor.cpp200
-rw-r--r--editor/scene_tree_dock.cpp12
-rw-r--r--editor/scene_tree_editor.cpp4
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp25
-rw-r--r--modules/gdscript/tests/gdscript_test_runner.cpp13
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/auto_inferred_type_dont_error.out4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/class_inference_is_weak.gd11
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/class_inference_is_weak.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/local_inference_is_weak.gd14
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/local_inference_is_weak.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/subscript_self.gd8
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/subscript_self.out3
-rw-r--r--modules/gridmap/grid_map_editor_plugin.cpp48
-rw-r--r--modules/gridmap/grid_map_editor_plugin.h4
-rw-r--r--scene/3d/node_3d.cpp13
-rw-r--r--scene/3d/node_3d.h4
-rw-r--r--scene/3d/skeleton_3d.cpp75
-rw-r--r--scene/3d/skeleton_3d.h6
-rw-r--r--scene/scene_string_names.cpp3
-rw-r--r--scene/scene_string_names.h3
49 files changed, 1558 insertions, 511 deletions
diff --git a/core/core_bind.cpp b/core/core_bind.cpp
index f630adc39e..e11d9ab9c1 100644
--- a/core/core_bind.cpp
+++ b/core/core_bind.cpp
@@ -1771,6 +1771,7 @@ void Thread::_start_func(void *ud) {
Object *target_instance = t->target_callable.get_object();
if (!target_instance) {
+ t->running.clear();
ERR_FAIL_MSG(vformat("Could not call function '%s' on previously freed instance to start thread %s.", t->target_callable.get_method(), t->get_id()));
}
@@ -1813,19 +1814,22 @@ void Thread::_start_func(void *ud) {
t->target_callable.call(arg, argc, t->ret, ce);
if (ce.error != Callable::CallError::CALL_OK) {
+ t->running.clear();
ERR_FAIL_MSG("Could not call function '" + t->target_callable.get_method().operator String() + "' to start thread " + t->get_id() + ": " + Variant::get_callable_error_text(t->target_callable, arg, argc, ce) + ".");
}
+
+ t->running.clear();
}
Error Thread::start(const Callable &p_callable, const Variant &p_userdata, Priority p_priority) {
- ERR_FAIL_COND_V_MSG(active.is_set(), ERR_ALREADY_IN_USE, "Thread already started.");
+ ERR_FAIL_COND_V_MSG(is_started(), ERR_ALREADY_IN_USE, "Thread already started.");
ERR_FAIL_COND_V(p_callable.is_null(), ERR_INVALID_PARAMETER);
ERR_FAIL_INDEX_V(p_priority, PRIORITY_MAX, ERR_INVALID_PARAMETER);
ret = Variant();
target_callable = p_callable;
userdata = p_userdata;
- active.set();
+ running.set();
Ref<Thread> *ud = memnew(Ref<Thread>(this));
@@ -1840,15 +1844,18 @@ String Thread::get_id() const {
return itos(thread.get_id());
}
-bool Thread::is_active() const {
- return active.is_set();
+bool Thread::is_started() const {
+ return thread.is_started();
+}
+
+bool Thread::is_alive() const {
+ return running.is_set();
}
Variant Thread::wait_to_finish() {
- ERR_FAIL_COND_V_MSG(!active.is_set(), Variant(), "Thread must be active to wait for its completion.");
+ ERR_FAIL_COND_V_MSG(!is_started(), Variant(), "Thread must have been started to wait for its completion.");
thread.wait_to_finish();
Variant r = ret;
- active.clear();
target_callable = Callable();
userdata = Variant();
@@ -1858,7 +1865,8 @@ Variant Thread::wait_to_finish() {
void Thread::_bind_methods() {
ClassDB::bind_method(D_METHOD("start", "callable", "userdata", "priority"), &Thread::start, DEFVAL(Variant()), DEFVAL(PRIORITY_NORMAL));
ClassDB::bind_method(D_METHOD("get_id"), &Thread::get_id);
- ClassDB::bind_method(D_METHOD("is_active"), &Thread::is_active);
+ ClassDB::bind_method(D_METHOD("is_started"), &Thread::is_started);
+ ClassDB::bind_method(D_METHOD("is_alive"), &Thread::is_alive);
ClassDB::bind_method(D_METHOD("wait_to_finish"), &Thread::wait_to_finish);
BIND_ENUM_CONSTANT(PRIORITY_LOW);
diff --git a/core/core_bind.h b/core/core_bind.h
index 84a284f948..4eab085dda 100644
--- a/core/core_bind.h
+++ b/core/core_bind.h
@@ -538,7 +538,7 @@ class Thread : public RefCounted {
protected:
Variant ret;
Variant userdata;
- SafeFlag active;
+ SafeFlag running;
Callable target_callable;
::Thread thread;
static void _bind_methods();
@@ -554,7 +554,8 @@ public:
Error start(const Callable &p_callable, const Variant &p_userdata = Variant(), Priority p_priority = PRIORITY_NORMAL);
String get_id() const;
- bool is_active() const;
+ bool is_started() const;
+ bool is_alive() const;
Variant wait_to_finish();
};
diff --git a/doc/classes/EditorPlugin.xml b/doc/classes/EditorPlugin.xml
index 43e65f1aa0..4aa6963f57 100644
--- a/doc/classes/EditorPlugin.xml
+++ b/doc/classes/EditorPlugin.xml
@@ -96,7 +96,7 @@
</description>
</method>
<method name="_forward_3d_gui_input" qualifiers="virtual">
- <return type="bool" />
+ <return type="int" />
<argument index="0" name="viewport_camera" type="Camera3D" />
<argument index="1" name="event" type="InputEvent" />
<description>
@@ -105,13 +105,13 @@
[gdscript]
# Prevents the InputEvent to reach other Editor classes.
func _forward_3d_gui_input(camera, event):
- return true
+ return EditorPlugin.AFTER_GUI_INPUT_STOP
[/gdscript]
[csharp]
// Prevents the InputEvent to reach other Editor classes.
public override bool _Forward3dGuiInput(Camera3D camera, InputEvent @event)
{
- return true;
+ return EditorPlugin.AFTER_GUI_INPUT_STOP;
}
[/csharp]
[/codeblocks]
@@ -185,12 +185,12 @@
Called when there is a root node in the current edited scene, [method _handles] is implemented and an [InputEvent] happens in the 2D viewport. Intercepts the [InputEvent], if [code]return true[/code] [EditorPlugin] consumes the [code]event[/code], otherwise forwards [code]event[/code] to other Editor classes. Example:
[codeblocks]
[gdscript]
- # Prevents the InputEvent to reach other Editor classes
+ # Prevents the InputEvent to reach other Editor classes.
func _forward_canvas_gui_input(event):
return true
[/gdscript]
[csharp]
- // Prevents the InputEvent to reach other Editor classes
+ // Prevents the InputEvent to reach other Editor classes.
public override bool ForwardCanvasGuiInput(InputEvent @event)
{
return true;
@@ -202,13 +202,18 @@
[gdscript]
# Consumes InputEventMouseMotion and forwards other InputEvent types.
func _forward_canvas_gui_input(event):
- return event is InputEventMouseMotion
+ if (event is InputEventMouseMotion):
+ return true
+ return false
[/gdscript]
[csharp]
// Consumes InputEventMouseMotion and forwards other InputEvent types.
public override bool ForwardCanvasGuiInput(InputEvent @event)
{
- return @event is InputEventMouseMotion;
+ if (@event is InputEventMouseMotion) {
+ return true;
+ }
+ return false
}
[/csharp]
[/codeblocks]
diff --git a/doc/classes/Node3D.xml b/doc/classes/Node3D.xml
index 49901dc4d9..28acd77a39 100644
--- a/doc/classes/Node3D.xml
+++ b/doc/classes/Node3D.xml
@@ -212,6 +212,15 @@
Sets whether the node notifies about its global and local transformation changes. [Node3D] will not propagate this by default, unless it is in the editor context and it has a valid gizmo.
</description>
</method>
+ <method name="set_subgizmo_selection">
+ <return type="void" />
+ <argument index="0" name="gizmo" type="Node3DGizmo" />
+ <argument index="1" name="id" type="int" />
+ <argument index="2" name="transform" type="Transform3D" />
+ <description>
+ Set subgizmo selection for this node in the editor.
+ </description>
+ </method>
<method name="show">
<return type="void" />
<description>
diff --git a/doc/classes/Skeleton3D.xml b/doc/classes/Skeleton3D.xml
index c2b514f232..6ab5ed55a3 100644
--- a/doc/classes/Skeleton3D.xml
+++ b/doc/classes/Skeleton3D.xml
@@ -189,6 +189,13 @@
This is helper function to make using [method Transform3D.looking_at] easier with bone poses.
</description>
</method>
+ <method name="is_bone_enabled" qualifiers="const">
+ <return type="bool" />
+ <argument index="0" name="bone_idx" type="int" />
+ <description>
+ Returns whether the bone pose for the bone at [code]bone_idx[/code] is enabled.
+ </description>
+ </method>
<method name="is_bone_rest_disabled" qualifiers="const">
<return type="bool" />
<argument index="0" name="bone_idx" type="int" />
@@ -282,6 +289,14 @@
Disables the rest pose for the bone at [code]bone_idx[/code] if [code]true[/code], enables the bone rest if [code]false[/code].
</description>
</method>
+ <method name="set_bone_enabled">
+ <return type="void" />
+ <argument index="0" name="bone_idx" type="int" />
+ <argument index="1" name="enabled" type="bool" default="true" />
+ <description>
+ Disables the pose for the bone at [code]bone_idx[/code] if [code]false[/code], enables the bone pose if [code]true[/code].
+ </description>
+ </method>
<method name="set_bone_global_pose_override">
<return type="void" />
<argument index="0" name="bone_idx" type="int" />
@@ -365,8 +380,15 @@
<members>
<member name="animate_physical_bones" type="bool" setter="set_animate_physical_bones" getter="get_animate_physical_bones" default="true">
</member>
+ <member name="show_rest_only" type="bool" setter="set_show_rest_only" getter="is_show_rest_only" default="false">
+ </member>
</members>
<signals>
+ <signal name="bone_enabled_changed">
+ <argument index="0" name="bone_idx" type="int" />
+ <description>
+ </description>
+ </signal>
<signal name="bone_pose_changed">
<argument index="0" name="bone_idx" type="int" />
<description>
@@ -377,6 +399,10 @@
<description>
</description>
</signal>
+ <signal name="show_rest_only_changed">
+ <description>
+ </description>
+ </signal>
</signals>
<constants>
<constant name="NOTIFICATION_UPDATE_SKELETON" value="50">
diff --git a/doc/classes/Thread.xml b/doc/classes/Thread.xml
index d5069dd8f3..eb2df8a4f9 100644
--- a/doc/classes/Thread.xml
+++ b/doc/classes/Thread.xml
@@ -19,10 +19,17 @@
Returns the current [Thread]'s ID, uniquely identifying it among all threads. If the [Thread] is not running this returns an empty string.
</description>
</method>
- <method name="is_active" qualifiers="const">
+ <method name="is_alive" qualifiers="const">
<return type="bool" />
<description>
- Returns [code]true[/code] if this [Thread] is currently active. An active [Thread] cannot start work on a new method but can be joined with [method wait_to_finish].
+ Returns [code]true[/code] if this [Thread] is currently running. This is useful for determining if [method wait_to_finish] can be called without blocking the calling thread.
+ To check if a [Thread] is joinable, use [method is_started].
+ </description>
+ </method>
+ <method name="is_started" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code] if this [Thread] has been started. Once started, this will return [code]true[/code] until it is joined using [method wait_to_finish]. For checking if a [Thread] is still executing its task, use [method is_alive].
</description>
</method>
<method name="start">
@@ -31,15 +38,16 @@
<argument index="1" name="userdata" type="Variant" default="null" />
<argument index="2" name="priority" type="int" enum="Thread.Priority" default="1" />
<description>
- Starts a new [Thread] that calls [code]callable[/code] with [code]userdata[/code] passed as an argument. Even if no userdata is passed, [code]method[/code] must accept one argument and it will be null. The [code]priority[/code] of the [Thread] can be changed by passing a value from the [enum Priority] enum.
+ Starts a new [Thread] that calls [code]callable[/code] with [code]userdata[/code] passed as an argument. Even if no userdata is passed, [code]callable[/code] must accept one argument and it will be null. The [code]priority[/code] of the [Thread] can be changed by passing a value from the [enum Priority] enum.
Returns [constant OK] on success, or [constant ERR_CANT_CREATE] on failure.
</description>
</method>
<method name="wait_to_finish">
<return type="Variant" />
<description>
- Joins the [Thread] and waits for it to finish. Returns what the method called returned.
+ Joins the [Thread] and waits for it to finish. Returns the output of the [Callable] passed to [method start].
Should either be used when you want to retrieve the value returned from the method called by the [Thread] or before freeing the instance that contains the [Thread].
+ To determine if this can be called without blocking the calling thread, check if [method is_alive] is [code]false[/code].
[b]Note:[/b] After the [Thread] finishes joining it will be disposed. If you want to use it again you will have to create a new instance of it.
</description>
</method>
diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp
index d5afd5020c..aad94717b2 100644
--- a/editor/animation_track_editor.cpp
+++ b/editor/animation_track_editor.cpp
@@ -3359,7 +3359,7 @@ void AnimationTrackEditor::_query_insert(const InsertData &p_id) {
insert_data.push_back(p_id);
bool reset_allowed = true;
- AnimationPlayer *player = AnimationPlayerEditor::singleton->get_player();
+ AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player();
if (player->has_animation("RESET") && player->get_animation("RESET") == animation) {
// Avoid messing with the reset animation itself
reset_allowed = false;
@@ -3528,6 +3528,31 @@ void AnimationTrackEditor::insert_transform_key(Node3D *p_node, const String &p_
_query_insert(id);
}
+bool AnimationTrackEditor::has_transform_track(Node3D *p_node, const String &p_sub) {
+ if (!keying) {
+ return false;
+ }
+ if (!animation.is_valid()) {
+ return false;
+ }
+ if (!root) {
+ return false;
+ }
+
+ //let's build a node path
+ String path = root->get_path_to(p_node);
+ if (p_sub != "") {
+ path += ":" + p_sub;
+ }
+ int track_id = animation->find_track(path);
+ if (track_id >= 0) {
+ if (animation->track_get_type(track_id) == Animation::TYPE_TRANSFORM3D) {
+ return true;
+ }
+ }
+ return false;
+}
+
void AnimationTrackEditor::_insert_animation_key(NodePath p_path, const Variant &p_value) {
String path = p_path;
@@ -3571,7 +3596,7 @@ void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_p
String path = root->get_path_to(node);
if (Object::cast_to<AnimationPlayer>(node) && p_property == "current_animation") {
- if (node == AnimationPlayerEditor::singleton->get_player()) {
+ if (node == AnimationPlayerEditor::get_singleton()->get_player()) {
EditorNode::get_singleton()->show_warning(TTR("AnimationPlayer can't animate itself, only other players."));
return;
}
@@ -3672,7 +3697,7 @@ void AnimationTrackEditor::insert_value_key(const String &p_property, const Vari
String path = root->get_path_to(node);
if (Object::cast_to<AnimationPlayer>(node) && p_property == "current_animation") {
- if (node == AnimationPlayerEditor::singleton->get_player()) {
+ if (node == AnimationPlayerEditor::get_singleton()->get_player()) {
EditorNode::get_singleton()->show_warning(TTR("AnimationPlayer can't animate itself, only other players."));
return;
}
@@ -3752,7 +3777,7 @@ void AnimationTrackEditor::insert_value_key(const String &p_property, const Vari
}
Ref<Animation> AnimationTrackEditor::_create_and_get_reset_animation() {
- AnimationPlayer *player = AnimationPlayerEditor::singleton->get_player();
+ AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player();
if (player->has_animation("RESET")) {
return player->get_animation("RESET");
} else {
@@ -3760,9 +3785,9 @@ Ref<Animation> AnimationTrackEditor::_create_and_get_reset_animation() {
reset_anim.instantiate();
reset_anim->set_length(ANIM_MIN_LENGTH);
undo_redo->add_do_method(player, "add_animation", "RESET", reset_anim);
- undo_redo->add_do_method(AnimationPlayerEditor::singleton, "_animation_player_changed", player);
+ undo_redo->add_do_method(AnimationPlayerEditor::get_singleton(), "_animation_player_changed", player);
undo_redo->add_undo_method(player, "remove_animation", "RESET");
- undo_redo->add_undo_method(AnimationPlayerEditor::singleton, "_animation_player_changed", player);
+ undo_redo->add_undo_method(AnimationPlayerEditor::get_singleton(), "_animation_player_changed", player);
return reset_anim;
}
}
@@ -4446,7 +4471,7 @@ void AnimationTrackEditor::_new_track_node_selected(NodePath p_path) {
return;
}
- if (node == AnimationPlayerEditor::singleton->get_player()) {
+ if (node == AnimationPlayerEditor::get_singleton()->get_player()) {
EditorNode::get_singleton()->show_warning(TTR("AnimationPlayer can't animate itself, only other players."));
return;
}
@@ -5173,7 +5198,7 @@ void AnimationTrackEditor::_anim_duplicate_keys(bool transpose) {
}
void AnimationTrackEditor::_edit_menu_about_to_popup() {
- AnimationPlayer *player = AnimationPlayerEditor::singleton->get_player();
+ AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player();
edit->get_popup()->set_item_disabled(edit->get_popup()->get_item_index(EDIT_APPLY_RESET), !player->can_apply_reset());
}
@@ -5517,7 +5542,7 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) {
goto_prev_step(false);
} break;
case EDIT_APPLY_RESET: {
- AnimationPlayerEditor::singleton->get_player()->apply_reset(true);
+ AnimationPlayerEditor::get_singleton()->get_player()->apply_reset(true);
} break;
case EDIT_OPTIMIZE_ANIMATION: {
@@ -5537,9 +5562,9 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) {
case EDIT_CLEAN_UP_ANIMATION_CONFIRM: {
if (cleanup_all->is_pressed()) {
List<StringName> names;
- AnimationPlayerEditor::singleton->get_player()->get_animation_list(&names);
+ AnimationPlayerEditor::get_singleton()->get_player()->get_animation_list(&names);
for (const StringName &E : names) {
- _cleanup_animation(AnimationPlayerEditor::singleton->get_player()->get_animation(E));
+ _cleanup_animation(AnimationPlayerEditor::get_singleton()->get_player()->get_animation(E));
}
} else {
_cleanup_animation(animation);
diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h
index 4da708dd1c..1b268fde47 100644
--- a/editor/animation_track_editor.h
+++ b/editor/animation_track_editor.h
@@ -531,6 +531,7 @@ public:
void insert_node_value_key(Node *p_node, const String &p_property, const Variant &p_value, bool p_only_if_exists = false);
void insert_value_key(const String &p_property, const Variant &p_value, bool p_advance);
void insert_transform_key(Node3D *p_node, const String &p_sub, const Transform3D &p_xform);
+ bool has_transform_track(Node3D *p_node, const String &p_sub);
void show_select_node_warning(bool p_show);
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index a8fdca1b20..5fd0a41788 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -6928,7 +6928,7 @@ EditorNode::EditorNode() {
add_child(editor_interface);
//more visually meaningful to have this later
- raise_bottom_panel_item(AnimationPlayerEditor::singleton);
+ raise_bottom_panel_item(AnimationPlayerEditor::get_singleton());
add_editor_plugin(VersionControlEditorPlugin::get_singleton());
add_editor_plugin(memnew(ShaderEditorPlugin(this)));
@@ -7202,20 +7202,24 @@ bool EditorPluginList::forward_gui_input(const Ref<InputEvent> &p_event) {
return discard;
}
-bool EditorPluginList::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event, bool serve_when_force_input_enabled) {
- bool discard = false;
+EditorPlugin::AfterGUIInput EditorPluginList::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event, bool serve_when_force_input_enabled) {
+ EditorPlugin::AfterGUIInput after = EditorPlugin::AFTER_GUI_INPUT_PASS;
for (int i = 0; i < plugins_list.size(); i++) {
if ((!serve_when_force_input_enabled) && plugins_list[i]->is_input_event_forwarding_always_enabled()) {
continue;
}
- if (plugins_list[i]->forward_spatial_gui_input(p_camera, p_event)) {
- discard = true;
+ EditorPlugin::AfterGUIInput current_after = plugins_list[i]->forward_spatial_gui_input(p_camera, p_event);
+ if (current_after == EditorPlugin::AFTER_GUI_INPUT_STOP) {
+ after = EditorPlugin::AFTER_GUI_INPUT_STOP;
+ }
+ if (after != EditorPlugin::AFTER_GUI_INPUT_STOP && current_after == EditorPlugin::AFTER_GUI_INPUT_DESELECT) {
+ after = EditorPlugin::AFTER_GUI_INPUT_DESELECT;
}
}
- return discard;
+ return after;
}
void EditorPluginList::forward_canvas_draw_over_viewport(Control *p_overlay) {
diff --git a/editor/editor_node.h b/editor/editor_node.h
index 07c834eeca..73feeecfee 100644
--- a/editor/editor_node.h
+++ b/editor/editor_node.h
@@ -942,7 +942,7 @@ public:
bool forward_gui_input(const Ref<InputEvent> &p_event);
void forward_canvas_draw_over_viewport(Control *p_overlay);
void forward_canvas_force_draw_over_viewport(Control *p_overlay);
- bool forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event, bool serve_when_force_input_enabled);
+ EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event, bool serve_when_force_input_enabled);
void forward_spatial_draw_over_viewport(Control *p_overlay);
void forward_spatial_force_draw_over_viewport(Control *p_overlay);
void add_plugin(EditorPlugin *p_plugin);
diff --git a/editor/editor_plugin.cpp b/editor/editor_plugin.cpp
index 7afc8d4c83..aee8322a97 100644
--- a/editor/editor_plugin.cpp
+++ b/editor/editor_plugin.cpp
@@ -593,14 +593,14 @@ int EditorPlugin::update_overlays() const {
}
}
-bool EditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) {
- bool success;
+EditorPlugin::AfterGUIInput EditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) {
+ int success;
if (GDVIRTUAL_CALL(_forward_3d_gui_input, p_camera, p_event, success)) {
- return success;
+ return static_cast<EditorPlugin::AfterGUIInput>(success);
}
- return false;
+ return EditorPlugin::AFTER_GUI_INPUT_PASS;
}
void EditorPlugin::forward_spatial_draw_over_viewport(Control *p_overlay) {
diff --git a/editor/editor_plugin.h b/editor/editor_plugin.h
index 169106d901..57830df327 100644
--- a/editor/editor_plugin.h
+++ b/editor/editor_plugin.h
@@ -151,7 +151,7 @@ protected:
GDVIRTUAL1R(bool, _forward_canvas_gui_input, Ref<InputEvent>)
GDVIRTUAL1(_forward_canvas_draw_over_viewport, Control *)
GDVIRTUAL1(_forward_canvas_force_draw_over_viewport, Control *)
- GDVIRTUAL2R(bool, _forward_3d_gui_input, Camera3D *, Ref<InputEvent>)
+ GDVIRTUAL2R(int, _forward_3d_gui_input, Camera3D *, Ref<InputEvent>)
GDVIRTUAL1(_forward_3d_draw_over_viewport, Control *)
GDVIRTUAL1(_forward_3d_force_draw_over_viewport, Control *)
GDVIRTUAL0RC(String, _get_plugin_name)
@@ -200,6 +200,12 @@ public:
DOCK_SLOT_MAX
};
+ enum AfterGUIInput {
+ AFTER_GUI_INPUT_PASS,
+ AFTER_GUI_INPUT_STOP,
+ AFTER_GUI_INPUT_DESELECT
+ };
+
//TODO: send a resource for editing to the editor node?
void add_control_to_container(CustomControlContainer p_location, Control *p_control);
@@ -228,7 +234,7 @@ public:
virtual void forward_canvas_draw_over_viewport(Control *p_overlay);
virtual void forward_canvas_force_draw_over_viewport(Control *p_overlay);
- virtual bool forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event);
+ virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event);
virtual void forward_spatial_draw_over_viewport(Control *p_overlay);
virtual void forward_spatial_force_draw_over_viewport(Control *p_overlay);
diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp
index 6efbcbc61e..2d4db48f2a 100644
--- a/editor/editor_themes.cpp
+++ b/editor/editor_themes.cpp
@@ -213,6 +213,7 @@ void editor_register_and_generate_icons(Ref<Theme> p_theme, bool p_dark_theme =
exceptions.insert("EditorPivot");
exceptions.insert("EditorHandle");
exceptions.insert("Editor3DHandle");
+ exceptions.insert("EditorBoneHandle");
exceptions.insert("Godot");
exceptions.insert("Sky");
exceptions.insert("EditorControlAnchor");
diff --git a/editor/icons/EditorBoneHandle.svg b/editor/icons/EditorBoneHandle.svg
new file mode 100644
index 0000000000..a6d7c3f878
--- /dev/null
+++ b/editor/icons/EditorBoneHandle.svg
@@ -0,0 +1 @@
+<svg height="8" viewBox="0 0 8 8" width="8" xmlns="http://www.w3.org/2000/svg"><circle cx="4" cy="4" fill="#fff" r="4"/><circle cx="4" cy="4" fill="#000" r="2.5"/></svg>
diff --git a/editor/icons/ToolBoneSelect.svg b/editor/icons/ToolBoneSelect.svg
new file mode 100644
index 0000000000..cc12b69a82
--- /dev/null
+++ b/editor/icons/ToolBoneSelect.svg
@@ -0,0 +1 @@
+<svg enable-background="new 0 0 16 16" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill="#e0e0e0" fill-opacity=".9961"><path d="m16 11.46-6.142-2.527-1.572-.647.647 1.572 2.527 6.142.913-2.72 1.815 1.817.91-.909-1.818-1.815z"/><path d="m7.784 11.008-.886-2.152c-.23-.56-.102-1.203.327-1.631.287-.287.67-.439 1.061-.439.192 0 .386.037.57.113l2.151.885.17-.17c.977.645 2.271.516 3.1-.311.964-.963.964-2.524 0-3.488-.377-.377-.867-.622-1.396-.697-.074-.529-.318-1.019-.695-1.397-.455-.453-1.067-.711-1.707-.721-.667-.01-1.309.25-1.782.72-.828.829-.96 2.126-.314 3.105l-3.558 3.561c-.978-.646-2.274-.515-3.103.312-.963.962-.963 2.524 0 3.487.378.377.868.621 1.396.695.075.529.319 1.02.696 1.396.963.964 2.525.964 3.488 0 .828-.828.96-2.125.314-3.104z"/></g></svg>
diff --git a/editor/inspector_dock.cpp b/editor/inspector_dock.cpp
index a559f05785..59d0b92ba0 100644
--- a/editor/inspector_dock.cpp
+++ b/editor/inspector_dock.cpp
@@ -383,7 +383,7 @@ void InspectorDock::_menu_expandall() {
}
void InspectorDock::_property_keyed(const String &p_keyed, const Variant &p_value, bool p_advance) {
- AnimationPlayerEditor::singleton->get_track_editor()->insert_value_key(p_keyed, p_value, p_advance);
+ AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_value_key(p_keyed, p_value, p_advance);
}
void InspectorDock::_transform_keyed(Object *sp, const String &p_sub, const Transform3D &p_key) {
@@ -391,7 +391,7 @@ void InspectorDock::_transform_keyed(Object *sp, const String &p_sub, const Tran
if (!s) {
return;
}
- AnimationPlayerEditor::singleton->get_track_editor()->insert_transform_key(s, p_sub, p_key);
+ AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_transform_key(s, p_sub, p_key);
}
void InspectorDock::_warning_pressed() {
@@ -545,7 +545,7 @@ void InspectorDock::go_back() {
void InspectorDock::update_keying() {
bool valid = false;
- if (AnimationPlayerEditor::singleton->get_track_editor()->has_keying()) {
+ if (AnimationPlayerEditor::get_singleton()->get_track_editor()->has_keying()) {
EditorHistory *editor_history = EditorNode::get_singleton()->get_editor_history();
if (editor_history->get_path_size() >= 1) {
Object *obj = ObjectDB::get_instance(editor_history->get_path_object(0));
diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp
index 902b0aa9ec..ea025dad3e 100644
--- a/editor/plugins/animation_player_editor_plugin.cpp
+++ b/editor/plugins/animation_player_editor_plugin.cpp
@@ -298,7 +298,7 @@ void AnimationPlayerEditor::_animation_selected(int p_which) {
autoplay->set_pressed(current == player->get_autoplay());
- AnimationPlayerEditor::singleton->get_track_editor()->update_keying();
+ AnimationPlayerEditor::get_singleton()->get_track_editor()->update_keying();
EditorNode::get_singleton()->update_keying();
_animation_key_editor_seek(timeline_position, false);
}
@@ -826,7 +826,7 @@ void AnimationPlayerEditor::_update_player() {
pin->set_disabled(player == nullptr);
if (!player) {
- AnimationPlayerEditor::singleton->get_track_editor()->update_keying();
+ AnimationPlayerEditor::get_singleton()->get_track_editor()->update_keying();
EditorNode::get_singleton()->update_keying();
return;
}
diff --git a/editor/plugins/animation_player_editor_plugin.h b/editor/plugins/animation_player_editor_plugin.h
index 0a514d3ff1..eb8db2eaba 100644
--- a/editor/plugins/animation_player_editor_plugin.h
+++ b/editor/plugins/animation_player_editor_plugin.h
@@ -128,6 +128,7 @@ class AnimationPlayerEditor : public VBoxContainer {
bool updating_blends;
AnimationTrackEditor *track_editor;
+ static AnimationPlayerEditor *singleton;
// Onion skinning.
struct {
@@ -226,7 +227,8 @@ protected:
public:
AnimationPlayer *get_player() const;
- static AnimationPlayerEditor *singleton;
+
+ static AnimationPlayerEditor *get_singleton() { return singleton; }
bool is_pinned() const { return pin->is_pressed(); }
void unpin() { pin->set_pressed(false); }
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index a49dd916f3..ef872bcead 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -513,7 +513,7 @@ Object *CanvasItemEditor::_get_editor_data(Object *p_what) {
}
void CanvasItemEditor::_keying_changed() {
- if (AnimationPlayerEditor::singleton->get_track_editor()->is_visible_in_tree()) {
+ if (AnimationPlayerEditor::get_singleton()->get_track_editor()->is_visible_in_tree()) {
animation_hb->show();
} else {
animation_hb->hide();
@@ -3816,7 +3816,7 @@ void CanvasItemEditor::_notification(int p_what) {
select_sb->set_default_margin(Side(i), 4);
}
- AnimationPlayerEditor::singleton->get_track_editor()->connect("visibility_changed", callable_mp(this, &CanvasItemEditor::_keying_changed));
+ AnimationPlayerEditor::get_singleton()->get_track_editor()->connect("visibility_changed", callable_mp(this, &CanvasItemEditor::_keying_changed));
_keying_changed();
} else if (p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) {
@@ -4277,13 +4277,13 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation,
Node2D *n2d = Object::cast_to<Node2D>(canvas_item);
if (key_pos && p_location) {
- AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(n2d, "position", n2d->get_position(), p_on_existing);
+ AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(n2d, "position", n2d->get_position(), p_on_existing);
}
if (key_rot && p_rotation) {
- AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(n2d, "rotation", n2d->get_rotation(), p_on_existing);
+ AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(n2d, "rotation", n2d->get_rotation(), p_on_existing);
}
if (key_scale && p_scale) {
- AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(n2d, "scale", n2d->get_scale(), p_on_existing);
+ AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(n2d, "scale", n2d->get_scale(), p_on_existing);
}
if (n2d->has_meta("_edit_bone_") && n2d->get_parent_item()) {
@@ -4309,13 +4309,13 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation,
if (has_chain && ik_chain.size()) {
for (Node2D *&F : ik_chain) {
if (key_pos) {
- AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F, "position", F->get_position(), p_on_existing);
+ AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(F, "position", F->get_position(), p_on_existing);
}
if (key_rot) {
- AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F, "rotation", F->get_rotation(), p_on_existing);
+ AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(F, "rotation", F->get_rotation(), p_on_existing);
}
if (key_scale) {
- AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F, "scale", F->get_scale(), p_on_existing);
+ AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(F, "scale", F->get_scale(), p_on_existing);
}
}
}
@@ -4325,13 +4325,13 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation,
Control *ctrl = Object::cast_to<Control>(canvas_item);
if (key_pos) {
- AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl, "rect_position", ctrl->get_position(), p_on_existing);
+ AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(ctrl, "rect_position", ctrl->get_position(), p_on_existing);
}
if (key_rot) {
- AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl, "rect_rotation", ctrl->get_rotation(), p_on_existing);
+ AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(ctrl, "rect_rotation", ctrl->get_rotation(), p_on_existing);
}
if (key_scale) {
- AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl, "rect_size", ctrl->get_size(), p_on_existing);
+ AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(ctrl, "rect_size", ctrl->get_size(), p_on_existing);
}
}
}
@@ -4771,7 +4771,7 @@ void CanvasItemEditor::_popup_callback(int p_op) {
}
/*
if (key_scale)
- AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl,"rect/size",ctrl->get_size());
+ AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(ctrl,"rect/size",ctrl->get_size());
*/
}
}
diff --git a/editor/plugins/collision_polygon_3d_editor_plugin.cpp b/editor/plugins/collision_polygon_3d_editor_plugin.cpp
index 8b354c33a1..1ee834a974 100644
--- a/editor/plugins/collision_polygon_3d_editor_plugin.cpp
+++ b/editor/plugins/collision_polygon_3d_editor_plugin.cpp
@@ -103,9 +103,9 @@ void CollisionPolygon3DEditor::_wip_close() {
undo_redo->commit_action();
}
-bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) {
+EditorPlugin::AfterGUIInput CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) {
if (!node) {
- return false;
+ return EditorPlugin::AFTER_GUI_INPUT_PASS;
}
Transform3D gt = node->get_global_transform();
@@ -124,7 +124,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con
Vector3 spoint;
if (!p.intersects_ray(ray_from, ray_dir, &spoint)) {
- return false;
+ return EditorPlugin::AFTER_GUI_INPUT_PASS;
}
spoint = gi.xform(spoint);
@@ -151,19 +151,19 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con
snap_ignore = false;
_polygon_draw();
edited_point = 1;
- return true;
+ return EditorPlugin::AFTER_GUI_INPUT_STOP;
} else {
if (wip.size() > 1 && p_camera->unproject_position(gt.xform(Vector3(wip[0].x, wip[0].y, depth))).distance_to(gpoint) < grab_threshold) {
//wip closed
_wip_close();
- return true;
+ return EditorPlugin::AFTER_GUI_INPUT_STOP;
} else {
wip.push_back(cpoint);
edited_point = wip.size();
snap_ignore = false;
_polygon_draw();
- return true;
+ return EditorPlugin::AFTER_GUI_INPUT_STOP;
}
}
} else if (mb->get_button_index() == MOUSE_BUTTON_RIGHT && mb->is_pressed() && wip_active) {
@@ -184,7 +184,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con
undo_redo->add_do_method(this, "_polygon_draw");
undo_redo->add_undo_method(this, "_polygon_draw");
undo_redo->commit_action();
- return true;
+ return EditorPlugin::AFTER_GUI_INPUT_STOP;
}
//search edges
@@ -219,7 +219,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con
_polygon_draw();
snap_ignore = true;
- return true;
+ return EditorPlugin::AFTER_GUI_INPUT_STOP;
}
} else {
//look for points to move
@@ -244,7 +244,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con
edited_point_pos = poly[closest_idx];
_polygon_draw();
snap_ignore = false;
- return true;
+ return EditorPlugin::AFTER_GUI_INPUT_STOP;
}
}
} else {
@@ -253,7 +253,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con
if (edited_point != -1) {
//apply
- ERR_FAIL_INDEX_V(edited_point, poly.size(), false);
+ ERR_FAIL_INDEX_V(edited_point, poly.size(), EditorPlugin::AFTER_GUI_INPUT_PASS);
poly.write[edited_point] = edited_point_pos;
undo_redo->create_action(TTR("Edit Poly"));
undo_redo->add_do_method(node, "set_polygon", poly);
@@ -263,7 +263,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con
undo_redo->commit_action();
edited_point = -1;
- return true;
+ return EditorPlugin::AFTER_GUI_INPUT_STOP;
}
}
}
@@ -290,7 +290,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con
undo_redo->add_do_method(this, "_polygon_draw");
undo_redo->add_undo_method(this, "_polygon_draw");
undo_redo->commit_action();
- return true;
+ return EditorPlugin::AFTER_GUI_INPUT_STOP;
}
}
@@ -310,7 +310,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con
Vector3 spoint;
if (!p.intersects_ray(ray_from, ray_dir, &spoint)) {
- return false;
+ return EditorPlugin::AFTER_GUI_INPUT_PASS;
}
spoint = gi.xform(spoint);
@@ -332,7 +332,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con
}
}
- return false;
+ return EditorPlugin::AFTER_GUI_INPUT_PASS;
}
float CollisionPolygon3DEditor::_get_depth() {
diff --git a/editor/plugins/collision_polygon_3d_editor_plugin.h b/editor/plugins/collision_polygon_3d_editor_plugin.h
index 5db0f7308a..10b0adf76c 100644
--- a/editor/plugins/collision_polygon_3d_editor_plugin.h
+++ b/editor/plugins/collision_polygon_3d_editor_plugin.h
@@ -88,7 +88,7 @@ protected:
static void _bind_methods();
public:
- virtual bool forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event);
+ virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event);
void edit(Node *p_collision_polygon);
CollisionPolygon3DEditor(EditorNode *p_editor);
~CollisionPolygon3DEditor();
@@ -101,7 +101,7 @@ class Polygon3DEditorPlugin : public EditorPlugin {
EditorNode *editor;
public:
- virtual bool forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override { return collision_polygon_editor->forward_spatial_gui_input(p_camera, p_event); }
+ virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override { return collision_polygon_editor->forward_spatial_gui_input(p_camera, p_event); }
virtual String get_name() const override { return "Polygon3DEditor"; }
bool has_main_screen() const override { return false; }
diff --git a/editor/plugins/node_3d_editor_gizmos.cpp b/editor/plugins/node_3d_editor_gizmos.cpp
index 7b0fc07fe7..f1b25c0dde 100644
--- a/editor/plugins/node_3d_editor_gizmos.cpp
+++ b/editor/plugins/node_3d_editor_gizmos.cpp
@@ -2029,169 +2029,6 @@ void Position3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
p_gizmo->add_collision_segments(cursor_points);
}
-/////
-
-Skeleton3DGizmoPlugin::Skeleton3DGizmoPlugin() {
- Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/skeleton", Color(1, 0.8, 0.4));
- create_material("skeleton_material", gizmo_color);
-}
-
-bool Skeleton3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
- return Object::cast_to<Skeleton3D>(p_spatial) != nullptr;
-}
-
-String Skeleton3DGizmoPlugin::get_gizmo_name() const {
- return "Skeleton3D";
-}
-
-int Skeleton3DGizmoPlugin::get_priority() const {
- return -1;
-}
-
-void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
- Skeleton3D *skel = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node());
-
- p_gizmo->clear();
-
- Ref<Material> material = get_material("skeleton_material", p_gizmo);
-
- Ref<SurfaceTool> surface_tool(memnew(SurfaceTool));
-
- surface_tool->begin(Mesh::PRIMITIVE_LINES);
- surface_tool->set_material(material);
- LocalVector<Transform3D> grests;
- grests.resize(skel->get_bone_count());
-
- LocalVector<int> bones;
- LocalVector<float> weights;
- bones.resize(4);
- weights.resize(4);
-
- for (int i = 0; i < 4; i++) {
- bones[i] = 0;
- weights[i] = 0;
- }
-
- weights[0] = 1;
-
- AABB aabb;
-
- Color bonecolor = Color(1.0, 0.4, 0.4, 0.3);
- Color rootcolor = Color(0.4, 1.0, 0.4, 0.1);
-
- LocalVector<int> bones_to_process;
- bones_to_process = skel->get_parentless_bones();
-
- while (bones_to_process.size() > 0) {
- int current_bone_idx = bones_to_process[0];
- bones_to_process.erase(current_bone_idx);
-
- LocalVector<int> child_bones_vector;
- child_bones_vector = skel->get_bone_children(current_bone_idx);
- int child_bones_size = child_bones_vector.size();
-
- if (skel->get_bone_parent(current_bone_idx) < 0) {
- grests[current_bone_idx] = skel->get_bone_rest(current_bone_idx);
- }
-
- for (int i = 0; i < child_bones_size; i++) {
- int child_bone_idx = child_bones_vector[i];
-
- grests[child_bone_idx] = grests[current_bone_idx] * skel->get_bone_rest(child_bone_idx);
- Vector3 v0 = grests[current_bone_idx].origin;
- Vector3 v1 = grests[child_bone_idx].origin;
- Vector3 d = (v1 - v0).normalized();
- real_t dist = v0.distance_to(v1);
-
- // Find closest axis.
- int closest = -1;
- real_t closest_d = 0.0;
- for (int j = 0; j < 3; j++) {
- real_t dp = Math::abs(grests[current_bone_idx].basis[j].normalized().dot(d));
- if (j == 0 || dp > closest_d) {
- closest = j;
- }
- }
-
- // Find closest other.
- Vector3 first;
- Vector3 points[4];
- int point_idx = 0;
- for (int j = 0; j < 3; j++) {
- bones[0] = current_bone_idx;
- surface_tool->set_bones(bones);
- surface_tool->set_weights(weights);
- surface_tool->set_color(rootcolor);
- surface_tool->add_vertex(v0 - grests[current_bone_idx].basis[j].normalized() * dist * 0.05);
- surface_tool->set_bones(bones);
- surface_tool->set_weights(weights);
- surface_tool->set_color(rootcolor);
- surface_tool->add_vertex(v0 + grests[current_bone_idx].basis[j].normalized() * dist * 0.05);
-
- if (j == closest) {
- continue;
- }
-
- Vector3 axis;
- if (first == Vector3()) {
- axis = d.cross(d.cross(grests[current_bone_idx].basis[j])).normalized();
- first = axis;
- } else {
- axis = d.cross(first).normalized();
- }
-
- for (int k = 0; k < 2; k++) {
- if (k == 1) {
- axis = -axis;
- }
- Vector3 point = v0 + d * dist * 0.2;
- point += axis * dist * 0.1;
-
- bones[0] = current_bone_idx;
- surface_tool->set_bones(bones);
- surface_tool->set_weights(weights);
- surface_tool->set_color(bonecolor);
- surface_tool->add_vertex(v0);
- surface_tool->set_bones(bones);
- surface_tool->set_weights(weights);
- surface_tool->set_color(bonecolor);
- surface_tool->add_vertex(point);
-
- bones[0] = current_bone_idx;
- surface_tool->set_bones(bones);
- surface_tool->set_weights(weights);
- surface_tool->set_color(bonecolor);
- surface_tool->add_vertex(point);
- bones[0] = child_bone_idx;
- surface_tool->set_bones(bones);
- surface_tool->set_weights(weights);
- surface_tool->set_color(bonecolor);
- surface_tool->add_vertex(v1);
- points[point_idx++] = point;
- }
- }
- SWAP(points[1], points[2]);
- for (int j = 0; j < 4; j++) {
- bones[0] = current_bone_idx;
- surface_tool->set_bones(bones);
- surface_tool->set_weights(weights);
- surface_tool->set_color(bonecolor);
- surface_tool->add_vertex(points[j]);
- surface_tool->set_bones(bones);
- surface_tool->set_weights(weights);
- surface_tool->set_color(bonecolor);
- surface_tool->add_vertex(points[(j + 1) % 4]);
- }
-
- // Add the bone's children to the list of bones to be processed.
- bones_to_process.push_back(child_bones_vector[i]);
- }
- }
-
- Ref<ArrayMesh> m = surface_tool->commit();
- p_gizmo->add_mesh(m, Ref<Material>(), Transform3D(), skel->register_skin(Ref<Skin>()));
-}
-
////
PhysicalBone3DGizmoPlugin::PhysicalBone3DGizmoPlugin() {
diff --git a/editor/plugins/node_3d_editor_gizmos.h b/editor/plugins/node_3d_editor_gizmos.h
index 24b4a23d4b..d1aca4d92e 100644
--- a/editor/plugins/node_3d_editor_gizmos.h
+++ b/editor/plugins/node_3d_editor_gizmos.h
@@ -332,18 +332,6 @@ public:
Position3DGizmoPlugin();
};
-class Skeleton3DGizmoPlugin : public EditorNode3DGizmoPlugin {
- GDCLASS(Skeleton3DGizmoPlugin, EditorNode3DGizmoPlugin);
-
-public:
- bool has_gizmo(Node3D *p_spatial) override;
- String get_gizmo_name() const override;
- int get_priority() const override;
- void redraw(EditorNode3DGizmo *p_gizmo) override;
-
- Skeleton3DGizmoPlugin();
-};
-
class PhysicalBone3DGizmoPlugin : public EditorNode3DGizmoPlugin {
GDCLASS(PhysicalBone3DGizmoPlugin, EditorNode3DGizmoPlugin);
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index 3173d2c7f0..ea6ef8ab84 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -1291,24 +1291,31 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
return; //do NONE
}
+ EditorPlugin::AfterGUIInput after = EditorPlugin::AFTER_GUI_INPUT_PASS;
{
EditorNode *en = editor;
EditorPluginList *force_input_forwarding_list = en->get_editor_plugins_force_input_forwarding();
if (!force_input_forwarding_list->is_empty()) {
- bool discard = force_input_forwarding_list->forward_spatial_gui_input(camera, p_event, true);
- if (discard) {
+ EditorPlugin::AfterGUIInput discard = force_input_forwarding_list->forward_spatial_gui_input(camera, p_event, true);
+ if (discard == EditorPlugin::AFTER_GUI_INPUT_STOP) {
return;
}
+ if (discard == EditorPlugin::AFTER_GUI_INPUT_DESELECT) {
+ after = EditorPlugin::AFTER_GUI_INPUT_DESELECT;
+ }
}
}
{
EditorNode *en = editor;
EditorPluginList *over_plugin_list = en->get_editor_plugins_over();
if (!over_plugin_list->is_empty()) {
- bool discard = over_plugin_list->forward_spatial_gui_input(camera, p_event, false);
- if (discard) {
+ EditorPlugin::AfterGUIInput discard = over_plugin_list->forward_spatial_gui_input(camera, p_event, false);
+ if (discard == EditorPlugin::AFTER_GUI_INPUT_STOP) {
return;
}
+ if (discard == EditorPlugin::AFTER_GUI_INPUT_DESELECT) {
+ after = EditorPlugin::AFTER_GUI_INPUT_DESELECT;
+ }
}
}
@@ -1573,17 +1580,19 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
break;
}
- clicked = _select_ray(b->get_position());
+ if (after != EditorPlugin::AFTER_GUI_INPUT_DESELECT) {
+ clicked = _select_ray(b->get_position());
- //clicking is always deferred to either move or release
+ //clicking is always deferred to either move or release
- clicked_wants_append = b->is_shift_pressed();
+ clicked_wants_append = b->is_shift_pressed();
- if (clicked.is_null()) {
- //default to regionselect
- cursor.region_select = true;
- cursor.region_begin = b->get_position();
- cursor.region_end = b->get_position();
+ if (clicked.is_null()) {
+ //default to regionselect
+ cursor.region_select = true;
+ cursor.region_begin = b->get_position();
+ cursor.region_end = b->get_position();
+ }
}
surface->update();
@@ -1594,14 +1603,16 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
break;
}
- if (clicked.is_valid()) {
- _select_clicked(false);
- }
+ if (after != EditorPlugin::AFTER_GUI_INPUT_DESELECT) {
+ if (clicked.is_valid()) {
+ _select_clicked(false);
+ }
- if (cursor.region_select) {
- _select_region();
- cursor.region_select = false;
- surface->update();
+ if (cursor.region_select) {
+ _select_region();
+ cursor.region_select = false;
+ surface->update();
+ }
}
if (_edit.mode != TRANSFORM_NONE) {
@@ -2237,7 +2248,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
return;
}
- if (!AnimationPlayerEditor::singleton->get_track_editor()->has_keying()) {
+ if (!AnimationPlayerEditor::get_singleton()->get_track_editor()->has_keying()) {
set_message(TTR("Keying is disabled (no key inserted)."));
return;
}
@@ -6757,6 +6768,33 @@ void Node3DEditor::_request_gizmo(Object *p_obj) {
}
}
+void Node3DEditor::_set_subgizmo_selection(Object *p_obj, Ref<Node3DGizmo> p_gizmo, int p_id, Transform3D p_transform) {
+ if (p_id == -1) {
+ _clear_subgizmo_selection(p_obj);
+ return;
+ }
+
+ Node3D *sp = nullptr;
+ if (p_obj) {
+ sp = Object::cast_to<Node3D>(p_obj);
+ } else {
+ sp = selected;
+ }
+
+ if (!sp) {
+ return;
+ }
+
+ Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);
+ if (se) {
+ se->subgizmos.clear();
+ se->subgizmos.insert(p_id, p_transform);
+ se->gizmo = p_gizmo;
+ sp->update_gizmos();
+ update_transform_gizmo();
+ }
+}
+
void Node3DEditor::_clear_subgizmo_selection(Object *p_obj) {
Node3D *sp = nullptr;
if (p_obj) {
@@ -6882,7 +6920,6 @@ void Node3DEditor::_register_all_gizmos() {
add_gizmo_plugin(Ref<OccluderInstance3DGizmoPlugin>(memnew(OccluderInstance3DGizmoPlugin)));
add_gizmo_plugin(Ref<SoftDynamicBody3DGizmoPlugin>(memnew(SoftDynamicBody3DGizmoPlugin)));
add_gizmo_plugin(Ref<Sprite3DGizmoPlugin>(memnew(Sprite3DGizmoPlugin)));
- add_gizmo_plugin(Ref<Skeleton3DGizmoPlugin>(memnew(Skeleton3DGizmoPlugin)));
add_gizmo_plugin(Ref<Position3DGizmoPlugin>(memnew(Position3DGizmoPlugin)));
add_gizmo_plugin(Ref<RayCast3DGizmoPlugin>(memnew(RayCast3DGizmoPlugin)));
add_gizmo_plugin(Ref<SpringArm3DGizmoPlugin>(memnew(SpringArm3DGizmoPlugin)));
@@ -6907,6 +6944,7 @@ void Node3DEditor::_register_all_gizmos() {
void Node3DEditor::_bind_methods() {
ClassDB::bind_method("_get_editor_data", &Node3DEditor::_get_editor_data);
ClassDB::bind_method("_request_gizmo", &Node3DEditor::_request_gizmo);
+ ClassDB::bind_method("_set_subgizmo_selection", &Node3DEditor::_set_subgizmo_selection);
ClassDB::bind_method("_clear_subgizmo_selection", &Node3DEditor::_clear_subgizmo_selection);
ClassDB::bind_method("_refresh_menu_icons", &Node3DEditor::_refresh_menu_icons);
@@ -7733,6 +7771,13 @@ Vector3 Node3DEditor::snap_point(Vector3 p_target, Vector3 p_start) const {
return p_target;
}
+bool Node3DEditor::is_gizmo_visible() const {
+ if (selected) {
+ return gizmo.visible && selected->is_transform_gizmo_visible();
+ }
+ return gizmo.visible;
+}
+
double Node3DEditor::get_translate_snap() const {
double snap_value;
if (Input::get_singleton()->is_key_pressed(KEY_SHIFT)) {
diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h
index e8948071e6..2d5aeaa981 100644
--- a/editor/plugins/node_3d_editor_plugin.h
+++ b/editor/plugins/node_3d_editor_plugin.h
@@ -665,6 +665,7 @@ private:
Node3D *selected;
void _request_gizmo(Object *p_obj);
+ void _set_subgizmo_selection(Object *p_obj, Ref<Node3DGizmo> p_gizmo, int p_id, Transform3D p_transform = Transform3D());
void _clear_subgizmo_selection(Object *p_obj = nullptr);
static Node3DEditor *singleton;
@@ -758,7 +759,7 @@ public:
float get_fov() const { return settings_fov->get_value(); }
Transform3D get_gizmo_transform() const { return gizmo.transform; }
- bool is_gizmo_visible() const { return gizmo.visible; }
+ bool is_gizmo_visible() const;
ToolMode get_tool_mode() const { return tool_mode; }
bool are_local_coords_enabled() const { return tool_option_button[Node3DEditor::TOOL_OPT_LOCAL_COORDS]->is_pressed(); }
diff --git a/editor/plugins/path_3d_editor_plugin.cpp b/editor/plugins/path_3d_editor_plugin.cpp
index bb0c270baa..0268b6e5ea 100644
--- a/editor/plugins/path_3d_editor_plugin.cpp
+++ b/editor/plugins/path_3d_editor_plugin.cpp
@@ -294,13 +294,13 @@ Path3DGizmo::Path3DGizmo(Path3D *p_path) {
orig_out_length = 0;
}
-bool Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) {
+EditorPlugin::AfterGUIInput Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) {
if (!path) {
- return false;
+ return EditorPlugin::AFTER_GUI_INPUT_PASS;
}
Ref<Curve3D> c = path->get_curve();
if (c.is_null()) {
- return false;
+ return EditorPlugin::AFTER_GUI_INPUT_PASS;
}
Transform3D gt = path->get_global_transform();
Transform3D it = gt.affine_inverse();
@@ -329,14 +329,14 @@ bool Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref
const Vector3 *r = v3a.ptr();
if (p_camera->unproject_position(gt.xform(c->get_point_position(0))).distance_to(mbpos) < click_dist) {
- return false; //nope, existing
+ return EditorPlugin::AFTER_GUI_INPUT_PASS; //nope, existing
}
for (int i = 0; i < c->get_point_count() - 1; i++) {
//find the offset and point index of the place to break up
int j = idx;
if (p_camera->unproject_position(gt.xform(c->get_point_position(i + 1))).distance_to(mbpos) < click_dist) {
- return false; //nope, existing
+ return EditorPlugin::AFTER_GUI_INPUT_PASS; //nope, existing
}
while (j < rc && c->get_point_position(i + 1) != r[j]) {
@@ -386,7 +386,7 @@ bool Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref
ur->add_do_method(c.ptr(), "add_point", closest_seg_point, Vector3(), Vector3(), closest_seg + 1);
ur->add_undo_method(c.ptr(), "remove_point", closest_seg + 1);
ur->commit_action();
- return true;
+ return EditorPlugin::AFTER_GUI_INPUT_STOP;
} else {
Vector3 org;
@@ -405,7 +405,7 @@ bool Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref
ur->add_do_method(c.ptr(), "add_point", it.xform(inters), Vector3(), Vector3(), -1);
ur->add_undo_method(c.ptr(), "remove_point", c->get_point_count());
ur->commit_action();
- return true;
+ return EditorPlugin::AFTER_GUI_INPUT_STOP;
}
//add new at pos
@@ -425,27 +425,27 @@ bool Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref
ur->add_do_method(c.ptr(), "remove_point", i);
ur->add_undo_method(c.ptr(), "add_point", c->get_point_position(i), c->get_point_in(i), c->get_point_out(i), i);
ur->commit_action();
- return true;
+ return EditorPlugin::AFTER_GUI_INPUT_STOP;
} else if (dist_to_p_out < click_dist) {
UndoRedo *ur = editor->get_undo_redo();
ur->create_action(TTR("Remove Out-Control Point"));
ur->add_do_method(c.ptr(), "set_point_out", i, Vector3());
ur->add_undo_method(c.ptr(), "set_point_out", i, c->get_point_out(i));
ur->commit_action();
- return true;
+ return EditorPlugin::AFTER_GUI_INPUT_STOP;
} else if (dist_to_p_in < click_dist) {
UndoRedo *ur = editor->get_undo_redo();
ur->create_action(TTR("Remove In-Control Point"));
ur->add_do_method(c.ptr(), "set_point_in", i, Vector3());
ur->add_undo_method(c.ptr(), "set_point_in", i, c->get_point_in(i));
ur->commit_action();
- return true;
+ return EditorPlugin::AFTER_GUI_INPUT_STOP;
}
}
}
}
- return false;
+ return EditorPlugin::AFTER_GUI_INPUT_PASS;
}
void Path3DEditorPlugin::edit(Object *p_object) {
diff --git a/editor/plugins/path_3d_editor_plugin.h b/editor/plugins/path_3d_editor_plugin.h
index b74d7cc59e..974234ba8f 100644
--- a/editor/plugins/path_3d_editor_plugin.h
+++ b/editor/plugins/path_3d_editor_plugin.h
@@ -100,7 +100,7 @@ public:
Path3D *get_edited_path() { return path; }
static Path3DEditorPlugin *singleton;
- virtual bool forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override;
+ virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override;
virtual String get_name() const override { return "Path3D"; }
bool has_main_screen() const override { return false; }
diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp
index 4e3ab5380b..52ba8f1bcd 100644
--- a/editor/plugins/skeleton_3d_editor_plugin.cpp
+++ b/editor/plugins/skeleton_3d_editor_plugin.cpp
@@ -42,6 +42,7 @@
#include "scene/3d/physics_body_3d.h"
#include "scene/resources/capsule_shape_3d.h"
#include "scene/resources/sphere_shape_3d.h"
+#include "scene/resources/surface_tool.h"
void BoneTransformEditor::create_editors() {
const Color section_color = get_theme_color(SNAME("prop_subsection"), SNAME("Editor"));
@@ -50,6 +51,11 @@ void BoneTransformEditor::create_editors() {
section->setup("trf_properties", label, this, section_color, true);
add_child(section);
+ enabled_checkbox = memnew(CheckBox(TTR("Pose Enabled")));
+ enabled_checkbox->set_flat(true);
+ enabled_checkbox->set_visible(toggle_enabled);
+ section->get_vbox()->add_child(enabled_checkbox);
+
key_button = memnew(Button);
key_button->set_text(TTR("Key Transform"));
key_button->set_visible(keyable);
@@ -57,49 +63,40 @@ void BoneTransformEditor::create_editors() {
key_button->set_flat(true);
section->get_vbox()->add_child(key_button);
- enabled_checkbox = memnew(CheckBox(TTR("Pose Enabled")));
- enabled_checkbox->set_flat(true);
- enabled_checkbox->set_visible(toggle_enabled);
- section->get_vbox()->add_child(enabled_checkbox);
-
- // Translation property
+ // Translation property.
translation_property = memnew(EditorPropertyVector3());
translation_property->setup(-10000, 10000, 0.001f, true);
translation_property->set_label("Translation");
translation_property->set_use_folding(true);
- translation_property->set_read_only(false);
translation_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed_vector3));
section->get_vbox()->add_child(translation_property);
- // Rotation property
+ // Rotation property.
rotation_property = memnew(EditorPropertyVector3());
rotation_property->setup(-10000, 10000, 0.001f, true);
rotation_property->set_label("Rotation Degrees");
rotation_property->set_use_folding(true);
- rotation_property->set_read_only(false);
rotation_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed_vector3));
section->get_vbox()->add_child(rotation_property);
- // Scale property
+ // Scale property.
scale_property = memnew(EditorPropertyVector3());
scale_property->setup(-10000, 10000, 0.001f, true);
scale_property->set_label("Scale");
scale_property->set_use_folding(true);
- scale_property->set_read_only(false);
scale_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed_vector3));
section->get_vbox()->add_child(scale_property);
- // Transform/Matrix section
+ // Transform/Matrix section.
transform_section = memnew(EditorInspectorSection);
transform_section->setup("trf_properties_transform", "Matrix", this, section_color, true);
section->get_vbox()->add_child(transform_section);
- // Transform/Matrix property
+ // Transform/Matrix property.
transform_property = memnew(EditorPropertyTransform3D());
transform_property->setup(-10000, 10000, 0.001f, true);
transform_property->set_label("Transform");
transform_property->set_use_folding(true);
- transform_property->set_read_only(false);
transform_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed_transform));
transform_section->get_vbox()->add_child(transform_property);
}
@@ -109,7 +106,7 @@ void BoneTransformEditor::_notification(int p_what) {
case NOTIFICATION_ENTER_TREE: {
create_editors();
key_button->connect("pressed", callable_mp(this, &BoneTransformEditor::_key_button_pressed));
- enabled_checkbox->connect("toggled", callable_mp(this, &BoneTransformEditor::_checkbox_toggled));
+ enabled_checkbox->connect("pressed", callable_mp(this, &BoneTransformEditor::_checkbox_pressed));
[[fallthrough]];
}
case NOTIFICATION_SORT_CHILDREN: {
@@ -229,7 +226,7 @@ void BoneTransformEditor::_update_properties() {
return;
}
- if (skeleton == nullptr) {
+ if (!skeleton) {
return;
}
@@ -244,7 +241,7 @@ void BoneTransformEditor::_update_custom_pose_properties() {
return;
}
- if (skeleton == nullptr) {
+ if (!skeleton) {
return;
}
@@ -281,11 +278,32 @@ void BoneTransformEditor::set_target(const String &p_prop) {
void BoneTransformEditor::set_keyable(const bool p_keyable) {
keyable = p_keyable;
+}
+
+void BoneTransformEditor::_update_key_button(const bool p_keyable) {
+ bool is_keyable = keyable && p_keyable;
if (key_button) {
- key_button->set_visible(p_keyable);
+ key_button->set_visible(is_keyable);
}
}
+void BoneTransformEditor::set_properties_read_only(const bool p_readonly) {
+ enabled_checkbox->set_disabled(p_readonly);
+ enabled_checkbox->update();
+}
+
+void BoneTransformEditor::set_transform_read_only(const bool p_readonly) {
+ translation_property->set_read_only(p_readonly);
+ rotation_property->set_read_only(p_readonly);
+ scale_property->set_read_only(p_readonly);
+ transform_property->set_read_only(p_readonly);
+ translation_property->update();
+ rotation_property->update();
+ scale_property->update();
+ transform_property->update();
+ _update_key_button(!p_readonly);
+}
+
void BoneTransformEditor::set_toggle_enabled(const bool p_enabled) {
toggle_enabled = p_enabled;
if (enabled_checkbox) {
@@ -294,7 +312,7 @@ void BoneTransformEditor::set_toggle_enabled(const bool p_enabled) {
}
void BoneTransformEditor::_key_button_pressed() {
- if (skeleton == nullptr) {
+ if (!skeleton) {
return;
}
@@ -305,30 +323,150 @@ void BoneTransformEditor::_key_button_pressed() {
return;
}
- // Need to normalize the basis before you key it
Transform3D tform = compute_transform_from_vector3s();
- tform.orthonormalize();
- AnimationPlayerEditor::singleton->get_track_editor()->insert_transform_key(skeleton, name, tform);
+ AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_transform_key(skeleton, name, tform);
}
-void BoneTransformEditor::_checkbox_toggled(const bool p_toggled) {
+void BoneTransformEditor::_checkbox_pressed() {
+ if (!skeleton) {
+ return;
+ }
+
+ const BoneId bone_id = property.get_slicec('/', 1).to_int();
if (enabled_checkbox) {
- const String path = "bones/" + property.get_slicec('/', 1) + "/enabled";
- skeleton->set(path, p_toggled);
+ undo_redo->create_action(TTR("Set Pose Enabled"));
+ bool enabled = skeleton->is_bone_enabled(bone_id);
+ undo_redo->add_do_method(skeleton, "set_bone_enabled", bone_id, !enabled);
+ undo_redo->add_undo_method(skeleton, "set_bone_enabled", bone_id, enabled);
+ undo_redo->commit_action();
}
}
-void Skeleton3DEditor::_on_click_option(int p_option) {
+Skeleton3DEditor *Skeleton3DEditor::singleton = nullptr;
+
+void Skeleton3DEditor::set_keyable(const bool p_keyable) {
+ keyable = p_keyable;
+ skeleton_options->get_popup()->set_item_disabled(SKELETON_OPTION_INSERT_KEYS, !p_keyable);
+ skeleton_options->get_popup()->set_item_disabled(SKELETON_OPTION_INSERT_KEYS_EXISTED, !p_keyable);
+};
+
+void Skeleton3DEditor::set_rest_options_enabled(const bool p_rest_options_enabled) {
+ rest_options->get_popup()->set_item_disabled(REST_OPTION_POSE_TO_REST, !p_rest_options_enabled);
+};
+
+void Skeleton3DEditor::_update_show_rest_only() {
+ _update_pose_enabled(-1);
+}
+
+void Skeleton3DEditor::_update_pose_enabled(int p_bone) {
if (!skeleton) {
return;
}
+ if (pose_editor) {
+ pose_editor->set_properties_read_only(skeleton->is_show_rest_only());
- switch (p_option) {
- case MENU_OPTION_CREATE_PHYSICAL_SKELETON: {
+ if (selected_bone > 0) {
+ pose_editor->set_transform_read_only(skeleton->is_show_rest_only() || !(skeleton->is_bone_enabled(selected_bone)));
+ }
+ }
+ _update_gizmo_visible();
+}
+
+void Skeleton3DEditor::_on_click_skeleton_option(int p_skeleton_option) {
+ if (!skeleton) {
+ return;
+ }
+
+ switch (p_skeleton_option) {
+ case SKELETON_OPTION_CREATE_PHYSICAL_SKELETON: {
create_physical_skeleton();
break;
}
+ case SKELETON_OPTION_INIT_POSE: {
+ init_pose();
+ break;
+ }
+ case SKELETON_OPTION_INSERT_KEYS: {
+ insert_keys(true);
+ break;
+ }
+ case SKELETON_OPTION_INSERT_KEYS_EXISTED: {
+ insert_keys(false);
+ break;
+ }
+ }
+}
+
+void Skeleton3DEditor::_on_click_rest_option(int p_rest_option) {
+ if (!skeleton) {
+ return;
+ }
+
+ switch (p_rest_option) {
+ case REST_OPTION_POSE_TO_REST: {
+ pose_to_rest();
+ break;
+ }
+ }
+}
+
+void Skeleton3DEditor::init_pose() {
+ const int bone_len = skeleton->get_bone_count();
+ if (!bone_len) {
+ return;
+ }
+ UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo();
+ ur->create_action(TTR("Set Bone Transform"), UndoRedo::MERGE_ENDS);
+ for (int i = 0; i < bone_len; i++) {
+ ur->add_do_method(skeleton, "set_bone_pose", i, Transform3D());
+ ur->add_undo_method(skeleton, "set_bone_pose", i, skeleton->get_bone_pose(i));
+ }
+ ur->commit_action();
+}
+
+void Skeleton3DEditor::insert_keys(bool p_all_bones) {
+ if (!skeleton) {
+ return;
+ }
+
+ int bone_len = skeleton->get_bone_count();
+ Node *root = EditorNode::get_singleton()->get_tree()->get_root();
+ String path = root->get_path_to(skeleton);
+
+ AnimationTrackEditor *te = AnimationPlayerEditor::get_singleton()->get_track_editor();
+ for (int i = 0; i < bone_len; i++) {
+ const String name = skeleton->get_bone_name(i);
+
+ if (name.is_empty()) {
+ continue;
+ }
+
+ if (!p_all_bones && !te->has_transform_track(skeleton, name)) {
+ continue;
+ }
+
+ Transform3D tform = skeleton->get_bone_pose(i);
+ te->insert_transform_key(skeleton, name, tform);
+ }
+}
+
+void Skeleton3DEditor::pose_to_rest() {
+ if (!skeleton) {
+ return;
}
+
+ // Todo: Do method with multiple bone selection.
+ UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo();
+ ur->create_action(TTR("Set Bone Transform"), UndoRedo::MERGE_ENDS);
+
+ ur->add_do_method(skeleton, "set_bone_pose", selected_bone, Transform3D());
+ ur->add_undo_method(skeleton, "set_bone_pose", selected_bone, skeleton->get_bone_pose(selected_bone));
+ ur->add_do_method(skeleton, "set_bone_custom_pose", selected_bone, Transform3D());
+ ur->add_undo_method(skeleton, "set_bone_custom_pose", selected_bone, skeleton->get_bone_custom_pose(selected_bone));
+ ur->add_do_method(skeleton, "set_bone_rest", selected_bone, skeleton->get_bone_rest(selected_bone) * skeleton->get_bone_custom_pose(selected_bone) * skeleton->get_bone_pose(selected_bone));
+ ur->add_undo_method(skeleton, "set_bone_rest", selected_bone, skeleton->get_bone_rest(selected_bone));
+
+ ur->commit_action();
}
void Skeleton3DEditor::create_physical_skeleton() {
@@ -356,7 +494,7 @@ void Skeleton3DEditor::create_physical_skeleton() {
bones_infos.write[bone_id].relative_rest = bones_infos[parent].relative_rest * skeleton->get_bone_rest(bone_id);
- /// create physical bone on parent
+ // Create physical bone on parent.
if (!bones_infos[parent].physical_bone) {
bones_infos.write[parent].physical_bone = create_physical_bone(parent, bone_id, bones_infos);
@@ -370,7 +508,7 @@ void Skeleton3DEditor::create_physical_skeleton() {
bones_infos[parent].physical_bone->set_owner(owner);
bones_infos[parent].physical_bone->get_child(0)->set_owner(owner); // set shape owner
- /// Create joint between parent of parent
+ // Create joint between parent of parent.
if (-1 != parent_parent) {
bones_infos[parent].physical_bone->set_joint_type(PhysicalBone3D::JOINT_TYPE_PIN);
}
@@ -483,7 +621,7 @@ void Skeleton3DEditor::move_skeleton_bone(NodePath p_skeleton_path, int32_t p_se
ERR_FAIL_NULL(skeleton);
UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo();
ur->create_action(TTR("Set Bone Parentage"));
- // If the target is a child of ourselves, we move only *us* and not our children
+ // If the target is a child of ourselves, we move only *us* and not our children.
if (skeleton->is_bone_parent_of(p_target_boneidx, p_selected_boneidx)) {
const BoneId parent_idx = skeleton->get_bone_parent(p_selected_boneidx);
const int bone_count = skeleton->get_bone_count();
@@ -505,24 +643,30 @@ void Skeleton3DEditor::move_skeleton_bone(NodePath p_skeleton_path, int32_t p_se
void Skeleton3DEditor::_joint_tree_selection_changed() {
TreeItem *selected = joint_tree->get_selected();
- const String path = selected->get_metadata(0);
+ if (selected) {
+ const String path = selected->get_metadata(0);
- if (path.begins_with("bones/")) {
- const int b_idx = path.get_slicec('/', 1).to_int();
- const String bone_path = "bones/" + itos(b_idx) + "/";
+ if (path.begins_with("bones/")) {
+ const int b_idx = path.get_slicec('/', 1).to_int();
+ const String bone_path = "bones/" + itos(b_idx) + "/";
- pose_editor->set_target(bone_path + "pose");
- rest_editor->set_target(bone_path + "rest");
- custom_pose_editor->set_target(bone_path + "custom_pose");
+ pose_editor->set_target(bone_path + "pose");
+ rest_editor->set_target(bone_path + "rest");
+ custom_pose_editor->set_target(bone_path + "custom_pose");
- _update_properties();
+ pose_editor->set_visible(true);
+ rest_editor->set_visible(true);
+ custom_pose_editor->set_visible(true);
- pose_editor->set_visible(true);
- rest_editor->set_visible(true);
- custom_pose_editor->set_visible(true);
+ selected_bone = b_idx;
+ }
}
+ set_rest_options_enabled(selected);
+ _update_properties();
+ _update_pose_enabled();
}
+// May be not used with single select mode.
void Skeleton3DEditor::_joint_tree_rmb_select(const Vector2 &p_pos) {
}
@@ -536,12 +680,13 @@ void Skeleton3DEditor::_update_properties() {
if (custom_pose_editor) {
custom_pose_editor->_update_custom_pose_properties();
}
+ _update_gizmo_transform();
}
void Skeleton3DEditor::update_joint_tree() {
joint_tree->clear();
- if (skeleton == nullptr) {
+ if (!skeleton) {
return;
}
@@ -569,7 +714,7 @@ void Skeleton3DEditor::update_joint_tree() {
joint_item->set_selectable(0, true);
joint_item->set_metadata(0, "bones/" + itos(current_bone_idx));
- // Add the bone's children to the list of bones to be processed
+ // Add the bone's children to the list of bones to be processed.
Vector<int> current_bone_child_bones = skeleton->get_bone_children(current_bone_idx);
int child_bone_size = current_bone_child_bones.size();
for (int i = 0; i < child_bone_size; i++) {
@@ -587,16 +732,56 @@ void Skeleton3DEditor::create_editors() {
set_focus_mode(FOCUS_ALL);
- // Create Top Menu Bar
- options = memnew(MenuButton);
- Node3DEditor::get_singleton()->add_control_to_menu_panel(options);
+ Node3DEditor *ne = Node3DEditor::get_singleton();
+ AnimationTrackEditor *te = AnimationPlayerEditor::get_singleton()->get_track_editor();
- options->set_text(TTR("Skeleton3D"));
- options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Skeleton3D"), SNAME("EditorIcons")));
+ // Create Top Menu Bar.
+ separator = memnew(VSeparator);
+ ne->add_control_to_menu_panel(separator);
- options->get_popup()->add_item(TTR("Create physical skeleton"), MENU_OPTION_CREATE_PHYSICAL_SKELETON);
+ // Create Skeleton Option in Top Menu Bar.
+ skeleton_options = memnew(MenuButton);
+ ne->add_control_to_menu_panel(skeleton_options);
- options->get_popup()->connect("id_pressed", callable_mp(this, &Skeleton3DEditor::_on_click_option));
+ skeleton_options->set_text(TTR("Skeleton3D"));
+ skeleton_options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Skeleton3D"), SNAME("EditorIcons")));
+
+ skeleton_options->get_popup()->add_item(TTR("Init pose"), SKELETON_OPTION_INIT_POSE);
+ skeleton_options->get_popup()->add_item(TTR("Insert key of all bone poses"), SKELETON_OPTION_INSERT_KEYS);
+ skeleton_options->get_popup()->add_item(TTR("Insert key of bone poses already exist track"), SKELETON_OPTION_INSERT_KEYS_EXISTED);
+ skeleton_options->get_popup()->add_item(TTR("Create physical skeleton"), SKELETON_OPTION_CREATE_PHYSICAL_SKELETON);
+
+ skeleton_options->get_popup()->connect("id_pressed", callable_mp(this, &Skeleton3DEditor::_on_click_skeleton_option));
+
+ // Create Rest Option in Top Menu Bar.
+ rest_options = memnew(MenuButton);
+ ne->add_control_to_menu_panel(rest_options);
+
+ rest_options->set_text(TTR("Edit Rest"));
+ rest_options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("BoneAttachment3D"), SNAME("EditorIcons")));
+
+ rest_options->get_popup()->add_item(TTR("Apply current pose to rest"), REST_OPTION_POSE_TO_REST);
+ rest_options->get_popup()->connect("id_pressed", callable_mp(this, &Skeleton3DEditor::_on_click_rest_option));
+ set_rest_options_enabled(false);
+
+ Vector<Variant> button_binds;
+ button_binds.resize(1);
+
+ edit_mode_button = memnew(Button);
+ ne->add_control_to_menu_panel(edit_mode_button);
+ edit_mode_button->set_tooltip(TTR("Edit Mode\nShow buttons on joints."));
+ edit_mode_button->set_toggle_mode(true);
+ edit_mode_button->set_flat(true);
+ edit_mode_button->connect("toggled", callable_mp(this, &Skeleton3DEditor::edit_mode_toggled));
+
+ edit_mode = false;
+
+ set_keyable(te->has_keying());
+
+ if (skeleton) {
+ skeleton->add_child(handles_mesh_instance);
+ handles_mesh_instance->set_skeleton_path(NodePath(""));
+ }
const Color section_color = get_theme_color(SNAME("prop_subsection"), SNAME("Editor"));
@@ -612,7 +797,7 @@ void Skeleton3DEditor::create_editors() {
joint_tree = memnew(Tree);
joint_tree->set_columns(1);
- joint_tree->set_focus_mode(Control::FocusMode::FOCUS_NONE);
+ joint_tree->set_focus_mode(Control::FOCUS_NONE);
joint_tree->set_select_mode(Tree::SELECT_SINGLE);
joint_tree->set_hide_root(true);
joint_tree->set_v_size_flags(SIZE_EXPAND_FILL);
@@ -623,8 +808,8 @@ void Skeleton3DEditor::create_editors() {
pose_editor = memnew(BoneTransformEditor(skeleton));
pose_editor->set_label(TTR("Bone Pose"));
- pose_editor->set_keyable(AnimationPlayerEditor::singleton->get_track_editor()->has_keying());
pose_editor->set_toggle_enabled(true);
+ pose_editor->set_keyable(te->has_keying());
pose_editor->set_visible(false);
add_child(pose_editor);
@@ -632,27 +817,34 @@ void Skeleton3DEditor::create_editors() {
rest_editor->set_label(TTR("Bone Rest"));
rest_editor->set_visible(false);
add_child(rest_editor);
+ rest_editor->set_transform_read_only(true);
custom_pose_editor = memnew(BoneTransformEditor(skeleton));
custom_pose_editor->set_label(TTR("Bone Custom Pose"));
custom_pose_editor->set_visible(false);
add_child(custom_pose_editor);
+ custom_pose_editor->set_transform_read_only(true);
}
void Skeleton3DEditor::_notification(int p_what) {
switch (p_what) {
+ case NOTIFICATION_READY: {
+ edit_mode_button->set_icon(get_theme_icon("ToolBoneSelect", "EditorIcons"));
+ get_tree()->connect("node_removed", callable_mp(this, &Skeleton3DEditor::_node_removed), Vector<Variant>(), Object::CONNECT_ONESHOT);
+ break;
+ }
case NOTIFICATION_ENTER_TREE: {
create_editors();
update_joint_tree();
update_editors();
-
- get_tree()->connect("node_removed", callable_mp(this, &Skeleton3DEditor::_node_removed), Vector<Variant>(), Object::CONNECT_ONESHOT);
joint_tree->connect("item_selected", callable_mp(this, &Skeleton3DEditor::_joint_tree_selection_changed));
joint_tree->connect("item_rmb_selected", callable_mp(this, &Skeleton3DEditor::_joint_tree_rmb_select));
#ifdef TOOLS_ENABLED
+ skeleton->connect("pose_updated", callable_mp(this, &Skeleton3DEditor::_draw_gizmo));
skeleton->connect("pose_updated", callable_mp(this, &Skeleton3DEditor::_update_properties));
-#endif // TOOLS_ENABLED
-
+ skeleton->connect("bone_enabled_changed", callable_mp(this, &Skeleton3DEditor::_update_pose_enabled));
+ skeleton->connect("show_rest_only_changed", callable_mp(this, &Skeleton3DEditor::_update_show_rest_only));
+#endif
break;
}
}
@@ -661,7 +853,8 @@ void Skeleton3DEditor::_notification(int p_what) {
void Skeleton3DEditor::_node_removed(Node *p_node) {
if (skeleton && p_node == skeleton) {
skeleton = nullptr;
- options->hide();
+ skeleton_options->hide();
+ rest_options->hide();
}
_update_properties();
@@ -671,24 +864,237 @@ void Skeleton3DEditor::_bind_methods() {
ClassDB::bind_method(D_METHOD("_node_removed"), &Skeleton3DEditor::_node_removed);
ClassDB::bind_method(D_METHOD("_joint_tree_selection_changed"), &Skeleton3DEditor::_joint_tree_selection_changed);
ClassDB::bind_method(D_METHOD("_joint_tree_rmb_select"), &Skeleton3DEditor::_joint_tree_rmb_select);
+ ClassDB::bind_method(D_METHOD("_update_show_rest_only"), &Skeleton3DEditor::_update_show_rest_only);
+ ClassDB::bind_method(D_METHOD("_update_pose_enabled"), &Skeleton3DEditor::_update_pose_enabled);
ClassDB::bind_method(D_METHOD("_update_properties"), &Skeleton3DEditor::_update_properties);
- ClassDB::bind_method(D_METHOD("_on_click_option"), &Skeleton3DEditor::_on_click_option);
+ ClassDB::bind_method(D_METHOD("_on_click_skeleton_option"), &Skeleton3DEditor::_on_click_skeleton_option);
+ ClassDB::bind_method(D_METHOD("_on_click_rest_option"), &Skeleton3DEditor::_on_click_rest_option);
+
+ ClassDB::bind_method(D_METHOD("get_drag_data_fw"), &Skeleton3DEditor::get_drag_data_fw);
+ ClassDB::bind_method(D_METHOD("can_drop_data_fw"), &Skeleton3DEditor::can_drop_data_fw);
+ ClassDB::bind_method(D_METHOD("drop_data_fw"), &Skeleton3DEditor::drop_data_fw);
- ClassDB::bind_method(D_METHOD("_get_drag_data_fw"), &Skeleton3DEditor::get_drag_data_fw);
- ClassDB::bind_method(D_METHOD("_can_drop_data_fw"), &Skeleton3DEditor::can_drop_data_fw);
- ClassDB::bind_method(D_METHOD("_drop_data_fw"), &Skeleton3DEditor::drop_data_fw);
ClassDB::bind_method(D_METHOD("move_skeleton_bone"), &Skeleton3DEditor::move_skeleton_bone);
+
+ ClassDB::bind_method(D_METHOD("_draw_gizmo"), &Skeleton3DEditor::_draw_gizmo);
+}
+
+void Skeleton3DEditor::edit_mode_toggled(const bool pressed) {
+ edit_mode = pressed;
+ _update_gizmo_visible();
}
Skeleton3DEditor::Skeleton3DEditor(EditorInspectorPluginSkeleton *e_plugin, EditorNode *p_editor, Skeleton3D *p_skeleton) :
editor(p_editor),
editor_plugin(e_plugin),
skeleton(p_skeleton) {
+ singleton = this;
+
+ // Handle.
+ handle_material = Ref<ShaderMaterial>(memnew(ShaderMaterial));
+ handle_shader = Ref<Shader>(memnew(Shader));
+ handle_shader->set_code(R"(
+// Skeleton 3D gizmo handle shader.
+
+shader_type spatial;
+render_mode unshaded, shadows_disabled, depth_draw_always;
+uniform sampler2D texture_albedo : hint_albedo;
+uniform float point_size : hint_range(0,128) = 32;
+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*INV_CAMERA_MATRIX*WORLD_MATRIX*vec4(VERTEX.xyz,1.0);
+ POSITION.z = mix(POSITION.z, 0, 0.999);
+ POINT_SIZE = point_size;
+}
+void fragment() {
+ vec4 albedo_tex = texture(texture_albedo,POINT_COORD);
+ vec3 col = albedo_tex.rgb + COLOR.rgb;
+ col = vec3(min(col.r,1.0),min(col.g,1.0),min(col.b,1.0));
+ ALBEDO = col;
+ if (albedo_tex.a < 0.5) { discard; }
+ ALPHA = albedo_tex.a;
+}
+)");
+ handle_material->set_shader(handle_shader);
+ Ref<Texture2D> handle = editor->get_gui_base()->get_theme_icon("EditorBoneHandle", "EditorIcons");
+ handle_material->set_shader_param("point_size", handle->get_width());
+ handle_material->set_shader_param("texture_albedo", handle);
+
+ handles_mesh_instance = memnew(MeshInstance3D);
+ handles_mesh_instance->set_cast_shadows_setting(GeometryInstance3D::SHADOW_CASTING_SETTING_OFF);
+ handles_mesh.instantiate();
+ handles_mesh_instance->set_mesh(handles_mesh);
+}
+
+void Skeleton3DEditor::update_bone_original() {
+ if (!skeleton) {
+ return;
+ }
+ if (skeleton->get_bone_count() == 0 || selected_bone == -1) {
+ return;
+ }
+ bone_original = skeleton->get_bone_pose(selected_bone);
+}
+
+void Skeleton3DEditor::_hide_handles() {
+ handles_mesh_instance->hide();
+}
+
+void Skeleton3DEditor::_draw_gizmo() {
+ if (!skeleton) {
+ return;
+ }
+
+ // If you call get_bone_global_pose() while drawing the surface, such as toggle rest mode,
+ // the skeleton update will be done first and
+ // the drawing surface will be interrupted once and an error will occur.
+ skeleton->force_update_all_dirty_bones();
+
+ // Handles.
+ if (edit_mode) {
+ _draw_handles();
+ } else {
+ _hide_handles();
+ }
+}
+
+void Skeleton3DEditor::_draw_handles() {
+ handles_mesh_instance->show();
+
+ const int bone_len = skeleton->get_bone_count();
+ handles_mesh->clear_surfaces();
+ handles_mesh->surface_begin(Mesh::PRIMITIVE_POINTS);
+
+ for (int i = 0; i < bone_len; i++) {
+ Color c;
+ if (i == selected_bone) {
+ c = Color(1, 1, 0);
+ } else {
+ c = Color(0.1, 0.25, 0.8);
+ }
+ Vector3 point = skeleton->get_bone_global_pose(i).origin;
+ handles_mesh->surface_set_color(c);
+ handles_mesh->surface_add_vertex(point);
+ }
+ handles_mesh->surface_end();
+ handles_mesh->surface_set_material(0, handle_material);
+}
+
+TreeItem *Skeleton3DEditor::_find(TreeItem *p_node, const NodePath &p_path) {
+ if (!p_node) {
+ return nullptr;
+ }
+
+ NodePath np = p_node->get_metadata(0);
+ if (np == p_path) {
+ return p_node;
+ }
+
+ TreeItem *children = p_node->get_first_child();
+ while (children) {
+ TreeItem *n = _find(children, p_path);
+ if (n) {
+ return n;
+ }
+ children = children->get_next();
+ }
+
+ return nullptr;
+}
+
+void Skeleton3DEditor::_subgizmo_selection_change() {
+ if (!skeleton) {
+ return;
+ }
+
+ // Once validated by subgizmos_intersect_ray, but required if through inspector's bones tree.
+ if (!edit_mode) {
+ skeleton->clear_subgizmo_selection();
+ return;
+ }
+
+ int selected = -1;
+ Skeleton3DEditor *se = Skeleton3DEditor::get_singleton();
+ if (se) {
+ selected = se->get_selected_bone();
+ }
+
+ if (selected >= 0) {
+ Vector<Ref<Node3DGizmo>> gizmos = skeleton->get_gizmos();
+ for (int i = 0; i < gizmos.size(); i++) {
+ Ref<EditorNode3DGizmo> gizmo = gizmos[i];
+ if (!gizmo.is_valid()) {
+ continue;
+ }
+ Ref<Skeleton3DGizmoPlugin> plugin = gizmo->get_plugin();
+ if (!plugin.is_valid()) {
+ continue;
+ }
+ skeleton->set_subgizmo_selection(gizmo, selected, skeleton->get_bone_global_pose(selected));
+ break;
+ }
+ } else {
+ skeleton->clear_subgizmo_selection();
+ }
+}
+
+void Skeleton3DEditor::select_bone(int p_idx) {
+ if (p_idx >= 0) {
+ TreeItem *ti = _find(joint_tree->get_root(), "bones/" + itos(p_idx));
+ if (ti) {
+ // Make visible when it's collapsed.
+ TreeItem *node = ti->get_parent();
+ while (node && node != joint_tree->get_root()) {
+ node->set_collapsed(false);
+ node = node->get_parent();
+ }
+ ti->select(0);
+ joint_tree->scroll_to_item(ti);
+ }
+ } else {
+ selected_bone = -1;
+ joint_tree->deselect_all();
+ _joint_tree_selection_changed();
+ }
}
Skeleton3DEditor::~Skeleton3DEditor() {
- if (options) {
- Node3DEditor::get_singleton()->remove_control_from_menu_panel(options);
+ if (skeleton) {
+#ifdef TOOLS_ENABLED
+ skeleton->disconnect("show_rest_only_changed", callable_mp(this, &Skeleton3DEditor::_update_show_rest_only));
+ skeleton->disconnect("bone_enabled_changed", callable_mp(this, &Skeleton3DEditor::_update_pose_enabled));
+ skeleton->disconnect("pose_updated", callable_mp(this, &Skeleton3DEditor::_draw_gizmo));
+ skeleton->disconnect("pose_updated", callable_mp(this, &Skeleton3DEditor::_update_properties));
+ skeleton->set_transform_gizmo_visible(true);
+#endif
+ handles_mesh_instance->get_parent()->remove_child(handles_mesh_instance);
+ }
+
+ handles_mesh_instance->queue_delete();
+
+ Node3DEditor *ne = Node3DEditor::get_singleton();
+
+ if (separator) {
+ ne->remove_control_from_menu_panel(separator);
+ memdelete(separator);
+ }
+
+ if (skeleton_options) {
+ ne->remove_control_from_menu_panel(skeleton_options);
+ memdelete(skeleton_options);
+ }
+
+ if (rest_options) {
+ ne->remove_control_from_menu_panel(rest_options);
+ memdelete(rest_options);
+ }
+
+ if (edit_mode_button) {
+ ne->remove_control_from_menu_panel(edit_mode_button);
+ memdelete(edit_mode_button);
}
}
@@ -700,16 +1106,413 @@ void EditorInspectorPluginSkeleton::parse_begin(Object *p_object) {
Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_object);
ERR_FAIL_COND(!skeleton);
- Skeleton3DEditor *skel_editor = memnew(Skeleton3DEditor(this, editor, skeleton));
+ skel_editor = memnew(Skeleton3DEditor(this, editor, skeleton));
add_custom_control(skel_editor);
}
Skeleton3DEditorPlugin::Skeleton3DEditorPlugin(EditorNode *p_node) {
editor = p_node;
- Ref<EditorInspectorPluginSkeleton> skeleton_plugin;
- skeleton_plugin.instantiate();
+ skeleton_plugin = memnew(EditorInspectorPluginSkeleton);
skeleton_plugin->editor = editor;
EditorInspector::add_inspector_plugin(skeleton_plugin);
+
+ Ref<Skeleton3DGizmoPlugin> gizmo_plugin = Ref<Skeleton3DGizmoPlugin>(memnew(Skeleton3DGizmoPlugin));
+ Node3DEditor::get_singleton()->add_gizmo_plugin(gizmo_plugin);
+}
+
+EditorPlugin::AfterGUIInput Skeleton3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) {
+ Skeleton3DEditor *se = Skeleton3DEditor::get_singleton();
+ Node3DEditor *ne = Node3DEditor::get_singleton();
+ if (se->is_edit_mode()) {
+ const Ref<InputEventMouseButton> mb = p_event;
+ if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
+ if (ne->get_tool_mode() != Node3DEditor::TOOL_MODE_SELECT) {
+ if (!ne->is_gizmo_visible()) {
+ return EditorPlugin::AFTER_GUI_INPUT_STOP;
+ }
+ }
+ if (mb->is_pressed()) {
+ se->update_bone_original();
+ }
+ }
+ return EditorPlugin::AFTER_GUI_INPUT_DESELECT;
+ }
+ return EditorPlugin::AFTER_GUI_INPUT_PASS;
+}
+
+bool Skeleton3DEditorPlugin::handles(Object *p_object) const {
+ return p_object->is_class("Skeleton3D");
+}
+
+void Skeleton3DEditor::_update_gizmo_transform() {
+ Node3DEditor::get_singleton()->update_transform_gizmo();
+};
+
+void Skeleton3DEditor::_update_gizmo_visible() {
+ _subgizmo_selection_change();
+ if (edit_mode) {
+ if (selected_bone == -1) {
+#ifdef TOOLS_ENABLED
+ skeleton->set_transform_gizmo_visible(false);
+#endif
+ } else {
+#ifdef TOOLS_ENABLED
+ if (skeleton->is_bone_enabled(selected_bone) && !skeleton->is_show_rest_only()) {
+ skeleton->set_transform_gizmo_visible(true);
+ } else {
+ skeleton->set_transform_gizmo_visible(false);
+ }
+#endif
+ }
+ } else {
+#ifdef TOOLS_ENABLED
+ skeleton->set_transform_gizmo_visible(true);
+#endif
+ }
+ _draw_gizmo();
+}
+
+int Skeleton3DEditor::get_selected_bone() const {
+ return selected_bone;
+}
+
+Skeleton3DGizmoPlugin::Skeleton3DGizmoPlugin() {
+ unselected_mat = Ref<StandardMaterial3D>(memnew(StandardMaterial3D));
+ unselected_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
+ unselected_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
+ unselected_mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
+ unselected_mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true);
+
+ selected_mat = Ref<ShaderMaterial>(memnew(ShaderMaterial));
+ selected_sh = Ref<Shader>(memnew(Shader));
+ selected_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*INV_CAMERA_MATRIX*WORLD_MATRIX*vec4(VERTEX.xyz,1.0);
+ POSITION.z = mix(POSITION.z, 0, 0.998);
+}
+void fragment() {
+ ALBEDO = COLOR.rgb;
+ ALPHA = COLOR.a;
+}
+)");
+ selected_mat->set_shader(selected_sh);
+
+ // Regist properties in editor settings.
+ EDITOR_DEF("editors/3d_gizmos/gizmo_colors/skeleton", Color(1, 0.8, 0.4));
+ EDITOR_DEF("editors/3d_gizmos/gizmo_colors/selected_bone", Color(0.8, 0.3, 0.0));
+ EDITOR_DEF("editors/3d_gizmos/gizmo_settings/bone_axis_length", (float)0.1);
+ EDITOR_DEF("editors/3d_gizmos/gizmo_settings/bone_shape", 1);
+ EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "editors/3d_gizmos/gizmo_settings/bone_shape", PROPERTY_HINT_ENUM, "Wire,Octahedron"));
+}
+
+bool Skeleton3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<Skeleton3D>(p_spatial) != nullptr;
+}
+
+String Skeleton3DGizmoPlugin::get_gizmo_name() const {
+ return "Skeleton3D";
+}
+
+int Skeleton3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+int Skeleton3DGizmoPlugin::subgizmos_intersect_ray(const EditorNode3DGizmo *p_gizmo, Camera3D *p_camera, const Vector2 &p_point) const {
+ Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node());
+ ERR_FAIL_COND_V(!skeleton, -1);
+
+ Skeleton3DEditor *se = Skeleton3DEditor::get_singleton();
+
+ if (!se->is_edit_mode()) {
+ return -1;
+ }
+
+ if (Node3DEditor::get_singleton()->get_tool_mode() != Node3DEditor::TOOL_MODE_SELECT) {
+ return -1;
+ }
+
+ // Select bone.
+ real_t grab_threshold = 4 * EDSCALE;
+ Vector3 ray_from = p_camera->get_global_transform().origin;
+ Transform3D gt = skeleton->get_global_transform();
+ int closest_idx = -1;
+ real_t closest_dist = 1e10;
+ const int bone_len = skeleton->get_bone_count();
+ for (int i = 0; i < bone_len; i++) {
+ Vector3 joint_pos_3d = gt.xform(skeleton->get_bone_global_pose(i).origin);
+ Vector2 joint_pos_2d = p_camera->unproject_position(joint_pos_3d);
+ real_t dist_3d = ray_from.distance_to(joint_pos_3d);
+ real_t dist_2d = p_point.distance_to(joint_pos_2d);
+ if (dist_2d < grab_threshold && dist_3d < closest_dist) {
+ closest_dist = dist_3d;
+ closest_idx = i;
+ }
+ }
+
+ if (closest_idx >= 0) {
+ WARN_PRINT("ray:");
+ WARN_PRINT(itos(closest_idx));
+ se->select_bone(closest_idx);
+ return closest_idx;
+ }
+
+ se->select_bone(-1);
+ return -1;
+}
+
+Transform3D Skeleton3DGizmoPlugin::get_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node());
+ ERR_FAIL_COND_V(!skeleton, Transform3D());
+
+ return skeleton->get_bone_global_pose(p_id);
+}
+
+void Skeleton3DGizmoPlugin::set_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id, Transform3D p_transform) {
+ Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node());
+ ERR_FAIL_COND(!skeleton);
+
+ // Prepare for global to local.
+ Transform3D original_to_local = Transform3D();
+ int parent_idx = skeleton->get_bone_parent(p_id);
+ if (parent_idx >= 0) {
+ original_to_local = original_to_local * skeleton->get_bone_global_pose(parent_idx);
+ }
+ original_to_local = original_to_local * skeleton->get_bone_rest(p_id) * skeleton->get_bone_custom_pose(p_id);
+ Basis to_local = original_to_local.get_basis().inverse();
+
+ // Prepare transform.
+ Transform3D t = Transform3D();
+
+ // Basis.
+ t.basis = to_local * p_transform.get_basis();
+
+ // Origin.
+ Vector3 orig = Vector3();
+ orig = skeleton->get_bone_pose(p_id).origin;
+ Vector3 sub = p_transform.origin - skeleton->get_bone_global_pose(p_id).origin;
+ t.origin = orig + to_local.xform(sub);
+
+ // Apply transform.
+ skeleton->set_bone_pose(p_id, t);
+}
+
+void Skeleton3DGizmoPlugin::commit_subgizmos(const EditorNode3DGizmo *p_gizmo, const Vector<int> &p_ids, const Vector<Transform3D> &p_restore, bool p_cancel) {
+ Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node());
+ ERR_FAIL_COND(!skeleton);
+
+ Skeleton3DEditor *se = Skeleton3DEditor::get_singleton();
+
+ UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo();
+ for (int i = 0; i < p_ids.size(); i++) {
+ ur->create_action(TTR("Set Bone Transform"));
+ ur->add_do_method(skeleton, "set_bone_pose", p_ids[i], skeleton->get_bone_pose(p_ids[i]));
+ ur->add_undo_method(skeleton, "set_bone_pose", p_ids[i], se->get_bone_original());
+ }
+ ur->commit_action();
+}
+
+void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node());
+ p_gizmo->clear();
+
+ int selected = -1;
+ Skeleton3DEditor *se = Skeleton3DEditor::get_singleton();
+ if (se) {
+ selected = se->get_selected_bone();
+ }
+
+ Color bone_color = EditorSettings::get_singleton()->get("editors/3d_gizmos/gizmo_colors/skeleton");
+ Color selected_bone_color = EditorSettings::get_singleton()->get("editors/3d_gizmos/gizmo_colors/selected_bone");
+ real_t bone_axis_length = EditorSettings::get_singleton()->get("editors/3d_gizmos/gizmo_settings/bone_axis_length");
+ int bone_shape = EditorSettings::get_singleton()->get("editors/3d_gizmos/gizmo_settings/bone_shape");
+
+ LocalVector<Color> axis_colors;
+ axis_colors.push_back(Node3DEditor::get_singleton()->get_theme_color(SNAME("axis_x_color"), SNAME("Editor")));
+ axis_colors.push_back(Node3DEditor::get_singleton()->get_theme_color(SNAME("axis_y_color"), SNAME("Editor")));
+ axis_colors.push_back(Node3DEditor::get_singleton()->get_theme_color(SNAME("axis_z_color"), SNAME("Editor")));
+
+ Ref<SurfaceTool> surface_tool(memnew(SurfaceTool));
+ surface_tool->begin(Mesh::PRIMITIVE_LINES);
+
+ if (p_gizmo->is_selected()) {
+ surface_tool->set_material(selected_mat);
+ } else {
+ unselected_mat->set_albedo(bone_color);
+ surface_tool->set_material(unselected_mat);
+ }
+
+ Vector<Transform3D> grests;
+ grests.resize(skeleton->get_bone_count());
+
+ LocalVector<int> bones;
+ LocalVector<float> weights;
+ bones.resize(4);
+ weights.resize(4);
+ for (int i = 0; i < 4; i++) {
+ bones[i] = 0;
+ weights[i] = 0;
+ }
+ weights[0] = 1;
+
+ int current_bone_index = 0;
+ Vector<int> bones_to_process = skeleton->get_parentless_bones();
+
+ while (bones_to_process.size() > current_bone_index) {
+ int current_bone_idx = bones_to_process[current_bone_index];
+ current_bone_index++;
+
+ Color current_bone_color = (current_bone_idx == selected) ? selected_bone_color : bone_color;
+
+ Vector<int> child_bones_vector;
+ child_bones_vector = skeleton->get_bone_children(current_bone_idx);
+ int child_bones_size = child_bones_vector.size();
+
+ // You have children but no parent, then you must be a root/parentless bone.
+ if (skeleton->get_bone_parent(current_bone_idx) < 0) {
+ grests.write[current_bone_idx] = skeleton->get_bone_rest(current_bone_idx);
+ }
+
+ for (int i = 0; i < child_bones_size; i++) {
+ // Something wrong.
+ if (child_bones_vector[i] < 0) {
+ continue;
+ }
+
+ int child_bone_idx = child_bones_vector[i];
+
+ grests.write[child_bone_idx] = grests[current_bone_idx] * skeleton->get_bone_rest(child_bone_idx);
+
+ Vector3 v0 = grests[current_bone_idx].origin;
+ Vector3 v1 = grests[child_bone_idx].origin;
+ Vector3 d = (v1 - v0).normalized();
+ real_t dist = v0.distance_to(v1);
+
+ // Find closest axis.
+ int closest = -1;
+ real_t closest_d = 0.0;
+ for (int j = 0; j < 3; j++) {
+ real_t dp = Math::abs(grests[current_bone_idx].basis[j].normalized().dot(d));
+ if (j == 0 || dp > closest_d) {
+ closest = j;
+ }
+ }
+
+ // Draw bone.
+ switch (bone_shape) {
+ case 0: { // Wire shape.
+ surface_tool->set_color(current_bone_color);
+ bones[0] = current_bone_idx;
+ surface_tool->set_bones(bones);
+ surface_tool->set_weights(weights);
+ surface_tool->add_vertex(v0);
+ bones[0] = child_bone_idx;
+ surface_tool->set_bones(bones);
+ surface_tool->set_weights(weights);
+ surface_tool->add_vertex(v1);
+ } break;
+
+ case 1: { // Octahedron shape.
+ Vector3 first;
+ Vector3 points[6];
+ int point_idx = 0;
+ for (int j = 0; j < 3; j++) {
+ Vector3 axis;
+ if (first == Vector3()) {
+ axis = d.cross(d.cross(grests[current_bone_idx].basis[j])).normalized();
+ first = axis;
+ } else {
+ axis = d.cross(first).normalized();
+ }
+
+ surface_tool->set_color(current_bone_color);
+ for (int k = 0; k < 2; k++) {
+ if (k == 1) {
+ axis = -axis;
+ }
+ Vector3 point = v0 + d * dist * 0.2;
+ point += axis * dist * 0.1;
+
+ bones[0] = current_bone_idx;
+ surface_tool->set_bones(bones);
+ surface_tool->set_weights(weights);
+ surface_tool->add_vertex(v0);
+ surface_tool->set_bones(bones);
+ surface_tool->set_weights(weights);
+ surface_tool->add_vertex(point);
+
+ surface_tool->set_bones(bones);
+ surface_tool->set_weights(weights);
+ surface_tool->add_vertex(point);
+ bones[0] = child_bone_idx;
+ surface_tool->set_bones(bones);
+ surface_tool->set_weights(weights);
+ surface_tool->add_vertex(v1);
+ points[point_idx++] = point;
+ }
+ }
+ surface_tool->set_color(current_bone_color);
+ SWAP(points[1], points[2]);
+ bones[0] = current_bone_idx;
+ for (int j = 0; j < 6; j++) {
+ surface_tool->set_bones(bones);
+ surface_tool->set_weights(weights);
+ surface_tool->add_vertex(points[j]);
+ surface_tool->set_bones(bones);
+ surface_tool->set_weights(weights);
+ surface_tool->add_vertex(points[(j + 1) % 6]);
+ }
+ } break;
+ }
+
+ // Axis as root of the bone.
+ for (int j = 0; j < 3; j++) {
+ bones[0] = current_bone_idx;
+ surface_tool->set_color(axis_colors[j]);
+ surface_tool->set_bones(bones);
+ surface_tool->set_weights(weights);
+ surface_tool->add_vertex(v0);
+ surface_tool->set_bones(bones);
+ surface_tool->set_weights(weights);
+ surface_tool->add_vertex(v0 + (grests[current_bone_idx].basis.inverse())[j].normalized() * dist * bone_axis_length);
+
+ if (j == closest) {
+ continue;
+ }
+ }
+
+ // Axis at the end of the bone children.
+ if (i == child_bones_size - 1) {
+ for (int j = 0; j < 3; j++) {
+ bones[0] = child_bone_idx;
+ surface_tool->set_color(axis_colors[j]);
+ surface_tool->set_bones(bones);
+ surface_tool->set_weights(weights);
+ surface_tool->add_vertex(v1);
+ surface_tool->set_bones(bones);
+ surface_tool->set_weights(weights);
+ surface_tool->add_vertex(v1 + (grests[child_bone_idx].basis.inverse())[j].normalized() * dist * bone_axis_length);
+
+ if (j == closest) {
+ continue;
+ }
+ }
+ }
+
+ // Add the bone's children to the list of bones to be processed.
+ bones_to_process.push_back(child_bones_vector[i]);
+ }
+ }
+
+ Ref<ArrayMesh> m = surface_tool->commit();
+ p_gizmo->add_mesh(m, Ref<Material>(), Transform3D(), skeleton->register_skin(Ref<Skin>()));
}
diff --git a/editor/plugins/skeleton_3d_editor_plugin.h b/editor/plugins/skeleton_3d_editor_plugin.h
index 9de52c6fa8..e2a1d9a628 100644
--- a/editor/plugins/skeleton_3d_editor_plugin.h
+++ b/editor/plugins/skeleton_3d_editor_plugin.h
@@ -33,7 +33,12 @@
#include "editor/editor_node.h"
#include "editor/editor_plugin.h"
+#include "editor/editor_properties.h"
+#include "node_3d_editor_plugin.h"
+#include "scene/3d/camera_3d.h"
+#include "scene/3d/mesh_instance_3d.h"
#include "scene/3d/skeleton_3d.h"
+#include "scene/resources/immediate_mesh.h"
class EditorInspectorPluginSkeleton;
class Joint;
@@ -41,8 +46,6 @@ class PhysicalBone3D;
class Skeleton3DEditorPlugin;
class Button;
class CheckBox;
-class EditorPropertyTransform3D;
-class EditorPropertyVector3;
class BoneTransformEditor : public VBoxContainer {
GDCLASS(BoneTransformEditor, VBoxContainer);
@@ -81,6 +84,8 @@ class BoneTransformEditor : public VBoxContainer {
void _value_changed_transform(const String p_property_name, const Transform3D p_transform, const StringName p_edited_property_name, const bool p_boolean);
// Changes the transform to the given transform and updates the UI accordingly.
void _change_transform(Transform3D p_new_transform);
+ // Update it is truely keyable then.
+ void _update_key_button(const bool p_keyable);
// Creates a Transform using the EditorPropertyVector3 properties.
Transform3D compute_transform_from_vector3s() const;
@@ -92,7 +97,7 @@ protected:
public:
BoneTransformEditor(Skeleton3D *p_skeleton);
- // Which transform target to modify
+ // Which transform target to modify.
void set_target(const String &p_prop);
void set_label(const String &p_label) { label = p_label; }
@@ -100,20 +105,21 @@ public:
void _update_custom_pose_properties();
void _update_transform_properties(Transform3D p_transform);
- // Can/cannot modify the spinner values for the Transform
- void set_read_only(const bool p_read_only);
-
- // Transform can be keyed, whether or not to show the button
+ // Transform can be keyed, whether or not to show the button.
void set_keyable(const bool p_keyable);
- // Bone can be toggled enabled or disabled, whether or not to show the checkbox
+ // When rest mode, pose and custom_pose editor are diasbled.
+ void set_properties_read_only(const bool p_readonly);
+ void set_transform_read_only(const bool p_readonly);
+
+ // Bone can be toggled enabled or disabled, whether or not to show the checkbox.
void set_toggle_enabled(const bool p_enabled);
- // Key Transform Button pressed
+ // Key Transform Button pressed.
void _key_button_pressed();
- // Bone Enabled Checkbox toggled
- void _checkbox_toggled(const bool p_toggled);
+ // Bone Enabled Checkbox toggled.
+ void _checkbox_pressed();
};
class Skeleton3DEditor : public VBoxContainer {
@@ -121,13 +127,20 @@ class Skeleton3DEditor : public VBoxContainer {
friend class Skeleton3DEditorPlugin;
- enum Menu {
- MENU_OPTION_CREATE_PHYSICAL_SKELETON
+ enum SkeletonOption {
+ SKELETON_OPTION_INIT_POSE,
+ SKELETON_OPTION_INSERT_KEYS,
+ SKELETON_OPTION_INSERT_KEYS_EXISTED,
+ SKELETON_OPTION_CREATE_PHYSICAL_SKELETON
+ };
+
+ enum RestOption {
+ REST_OPTION_POSE_TO_REST
};
struct BoneInfo {
PhysicalBone3D *physical_bone = nullptr;
- Transform3D relative_rest; // Relative to skeleton node
+ Transform3D relative_rest; // Relative to skeleton node.
};
EditorNode *editor;
@@ -140,13 +153,24 @@ class Skeleton3DEditor : public VBoxContainer {
BoneTransformEditor *pose_editor = nullptr;
BoneTransformEditor *custom_pose_editor = nullptr;
- MenuButton *options = nullptr;
+ VSeparator *separator;
+ MenuButton *skeleton_options = nullptr;
+ MenuButton *rest_options = nullptr;
+ Button *edit_mode_button;
+
+ bool edit_mode = false;
+
EditorFileDialog *file_dialog = nullptr;
- UndoRedo *undo_redo = nullptr;
+ bool keyable;
+
+ static Skeleton3DEditor *singleton;
- void _on_click_option(int p_option);
+ void _on_click_skeleton_option(int p_skeleton_option);
+ void _on_click_rest_option(int p_rest_option);
void _file_selected(const String &p_file);
+ TreeItem *_find(TreeItem *p_node, const NodePath &p_path);
+ void edit_mode_toggled(const bool pressed);
EditorFileDialog *file_export_lib = nullptr;
@@ -155,6 +179,10 @@ class Skeleton3DEditor : public VBoxContainer {
void create_editors();
+ void init_pose();
+ void insert_keys(bool p_all_bones);
+ void pose_to_rest();
+
void create_physical_skeleton();
PhysicalBone3D *create_physical_bone(int bone_id, int bone_child_id, const Vector<BoneInfo> &bones_infos);
@@ -162,20 +190,56 @@ class Skeleton3DEditor : public VBoxContainer {
bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const;
void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from);
+ void set_keyable(const bool p_keyable);
+ void set_rest_options_enabled(const bool p_rest_options_enabled);
+
+ // Handle.
+ MeshInstance3D *handles_mesh_instance;
+ Ref<ImmediateMesh> handles_mesh;
+ Ref<ShaderMaterial> handle_material;
+ Ref<Shader> handle_shader;
+
+ Transform3D bone_original;
+
+ void _update_pose_enabled(int p_bone = -1);
+ void _update_show_rest_only();
+
+ void _update_gizmo_transform();
+ void _update_gizmo_visible();
+
+ void _hide_handles();
+
+ void _draw_gizmo();
+ void _draw_handles();
+
+ void _joint_tree_selection_changed();
+ void _joint_tree_rmb_select(const Vector2 &p_pos);
+ void _update_properties();
+
+ void _subgizmo_selection_change();
+
+ int selected_bone = -1;
+
protected:
void _notification(int p_what);
void _node_removed(Node *p_node);
static void _bind_methods();
public:
+ static Skeleton3DEditor *get_singleton() { return singleton; }
+
+ void select_bone(int p_idx);
+
+ int get_selected_bone() const;
+
void move_skeleton_bone(NodePath p_skeleton_path, int32_t p_selected_boneidx, int32_t p_target_boneidx);
Skeleton3D *get_skeleton() const { return skeleton; };
- void _joint_tree_selection_changed();
- void _joint_tree_rmb_select(const Vector2 &p_pos);
+ bool is_edit_mode() const { return edit_mode; }
- void _update_properties();
+ void update_bone_original();
+ Transform3D get_bone_original() { return bone_original; };
Skeleton3DEditor(EditorInspectorPluginSkeleton *e_plugin, EditorNode *p_editor, Skeleton3D *skeleton);
~Skeleton3DEditor();
@@ -186,6 +250,7 @@ class EditorInspectorPluginSkeleton : public EditorInspectorPlugin {
friend class Skeleton3DEditorPlugin;
+ Skeleton3DEditor *skel_editor;
EditorNode *editor;
public:
@@ -196,12 +261,40 @@ public:
class Skeleton3DEditorPlugin : public EditorPlugin {
GDCLASS(Skeleton3DEditorPlugin, EditorPlugin);
+ EditorInspectorPluginSkeleton *skeleton_plugin;
EditorNode *editor;
public:
- Skeleton3DEditorPlugin(EditorNode *p_node);
+ virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override;
+
+ bool has_main_screen() const override { return false; }
+ virtual bool handles(Object *p_object) const override;
virtual String get_name() const override { return "Skeleton3D"; }
+
+ Skeleton3DEditorPlugin(EditorNode *p_node);
+};
+
+class Skeleton3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(Skeleton3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+ Ref<StandardMaterial3D> unselected_mat;
+ Ref<ShaderMaterial> selected_mat;
+ Ref<Shader> selected_sh;
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+
+ int subgizmos_intersect_ray(const EditorNode3DGizmo *p_gizmo, Camera3D *p_camera, const Vector2 &p_point) const override;
+ Transform3D get_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ void set_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id, Transform3D p_transform) override;
+ void commit_subgizmos(const EditorNode3DGizmo *p_gizmo, const Vector<int> &p_ids, const Vector<Transform3D> &p_restore, bool p_cancel) override;
+
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ Skeleton3DGizmoPlugin();
};
#endif // SKELETON_3D_EDITOR_PLUGIN_H
diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp
index bc026146ef..3fbd315aec 100644
--- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp
+++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp
@@ -131,6 +131,7 @@ bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_set(const StringName &p_na
return false;
}
+ // ID and size related properties.
if (tiles.size() == 1) {
const Vector2i &coords = tiles.front()->get().tile;
const int &alternative = tiles.front()->get().alternative;
@@ -160,36 +161,6 @@ bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_set(const StringName &p_na
tile_set_atlas_source->move_tile_in_atlas(coords, TileSetSource::INVALID_ATLAS_COORDS, as_vector2i);
emit_signal(SNAME("changed"), "size_in_atlas");
return true;
- } else if (p_name == "animation_columns") {
- bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(coords, tile_set_atlas_source->get_tile_size_in_atlas(coords), p_value, tile_set_atlas_source->get_tile_animation_separation(coords), tile_set_atlas_source->get_tile_animation_frames_count(coords), coords);
- ERR_FAIL_COND_V(!has_room_for_tile, false);
- tile_set_atlas_source->set_tile_animation_columns(coords, p_value);
- emit_signal(SNAME("changed"), "animation_columns");
- return true;
- } else if (p_name == "animation_separation") {
- bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(coords, tile_set_atlas_source->get_tile_size_in_atlas(coords), tile_set_atlas_source->get_tile_animation_columns(coords), p_value, tile_set_atlas_source->get_tile_animation_frames_count(coords), coords);
- ERR_FAIL_COND_V(!has_room_for_tile, false);
- tile_set_atlas_source->set_tile_animation_separation(coords, p_value);
- emit_signal(SNAME("changed"), "animation_separation");
- return true;
- } else if (p_name == "animation_speed") {
- tile_set_atlas_source->set_tile_animation_speed(coords, p_value);
- emit_signal(SNAME("changed"), "animation_speed");
- return true;
- } else if (p_name == "animation_frames_count") {
- bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(coords, tile_set_atlas_source->get_tile_size_in_atlas(coords), tile_set_atlas_source->get_tile_animation_columns(coords), tile_set_atlas_source->get_tile_animation_separation(coords), p_value, coords);
- ERR_FAIL_COND_V(!has_room_for_tile, false);
- tile_set_atlas_source->set_tile_animation_frames_count(coords, p_value);
- notify_property_list_changed();
- emit_signal(SNAME("changed"), "animation_separation");
- return true;
- } else if (components.size() == 2 && components[0].begins_with("animation_frame_") && components[0].trim_prefix("animation_frame_").is_valid_int()) {
- int frame = components[0].trim_prefix("animation_frame_").to_int();
- ERR_FAIL_INDEX_V(frame, tile_set_atlas_source->get_tile_animation_frames_count(coords), false);
- if (components[1] == "duration") {
- tile_set_atlas_source->set_tile_animation_frame_duration(coords, frame, p_value);
- return true;
- }
}
} else if (alternative > 0) {
if (p_name == "alternative_id") {
@@ -213,6 +184,74 @@ bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_set(const StringName &p_na
}
}
+ // Animation.
+ // Check if all tiles have an alternative_id of 0.
+ bool all_alternatve_id_zero = true;
+ for (TileSelection tile : tiles) {
+ if (tile.alternative != 0) {
+ all_alternatve_id_zero = false;
+ break;
+ }
+ }
+
+ if (all_alternatve_id_zero) {
+ Vector<String> components = String(p_name).split("/", true, 2);
+ if (p_name == "animation_columns") {
+ for (TileSelection tile : tiles) {
+ bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(tile.tile, tile_set_atlas_source->get_tile_size_in_atlas(tile.tile), p_value, tile_set_atlas_source->get_tile_animation_separation(tile.tile), tile_set_atlas_source->get_tile_animation_frames_count(tile.tile), tile.tile);
+ if (!has_room_for_tile) {
+ ERR_PRINT("No room for tile");
+ } else {
+ tile_set_atlas_source->set_tile_animation_columns(tile.tile, p_value);
+ }
+ }
+ emit_signal(SNAME("changed"), "animation_columns");
+ return true;
+ } else if (p_name == "animation_separation") {
+ for (TileSelection tile : tiles) {
+ bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(tile.tile, tile_set_atlas_source->get_tile_size_in_atlas(tile.tile), tile_set_atlas_source->get_tile_animation_columns(tile.tile), p_value, tile_set_atlas_source->get_tile_animation_frames_count(tile.tile), tile.tile);
+ if (!has_room_for_tile) {
+ ERR_PRINT("No room for tile");
+ } else {
+ tile_set_atlas_source->set_tile_animation_separation(tile.tile, p_value);
+ }
+ }
+ emit_signal(SNAME("changed"), "animation_separation");
+ return true;
+ } else if (p_name == "animation_speed") {
+ for (TileSelection tile : tiles) {
+ tile_set_atlas_source->set_tile_animation_speed(tile.tile, p_value);
+ }
+ emit_signal(SNAME("changed"), "animation_speed");
+ return true;
+ } else if (p_name == "animation_frames_count") {
+ for (TileSelection tile : tiles) {
+ bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(tile.tile, tile_set_atlas_source->get_tile_size_in_atlas(tile.tile), tile_set_atlas_source->get_tile_animation_columns(tile.tile), tile_set_atlas_source->get_tile_animation_separation(tile.tile), p_value, tile.tile);
+ if (!has_room_for_tile) {
+ ERR_PRINT("No room for tile");
+ } else {
+ tile_set_atlas_source->set_tile_animation_frames_count(tile.tile, p_value);
+ }
+ }
+ notify_property_list_changed();
+ emit_signal(SNAME("changed"), "animation_separation");
+ return true;
+ } else if (components.size() == 2 && components[0].begins_with("animation_frame_") && components[0].trim_prefix("animation_frame_").is_valid_int()) {
+ for (TileSelection tile : tiles) {
+ int frame = components[0].trim_prefix("animation_frame_").to_int();
+ if (frame < 0 || frame >= tile_set_atlas_source->get_tile_animation_frames_count(tile.tile)) {
+ ERR_PRINT(vformat("No tile animation frame with index %d", frame));
+ } else {
+ if (components[1] == "duration") {
+ tile_set_atlas_source->set_tile_animation_frame_duration(tile.tile, frame, p_value);
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ // Other properties.
bool any_valid = false;
for (Set<TileSelection>::Element *E = tiles.front(); E; E = E->next()) {
const Vector2i &coords = E->get().tile;
@@ -238,6 +277,7 @@ bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_get(const StringName &p_na
return false;
}
+ // ID and size related properties.s
if (tiles.size() == 1) {
const Vector2i &coords = tiles.front()->get().tile;
const int &alternative = tiles.front()->get().alternative;
@@ -250,27 +290,6 @@ bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_get(const StringName &p_na
} else if (p_name == "size_in_atlas") {
r_ret = tile_set_atlas_source->get_tile_size_in_atlas(coords);
return true;
- } else if (p_name == "animation_columns") {
- r_ret = tile_set_atlas_source->get_tile_animation_columns(coords);
- return true;
- } else if (p_name == "animation_separation") {
- r_ret = tile_set_atlas_source->get_tile_animation_separation(coords);
- return true;
- } else if (p_name == "animation_speed") {
- r_ret = tile_set_atlas_source->get_tile_animation_speed(coords);
- return true;
- } else if (p_name == "animation_frames_count") {
- r_ret = tile_set_atlas_source->get_tile_animation_frames_count(coords);
- return true;
- } else if (components.size() == 2 && components[0].begins_with("animation_frame_") && components[0].trim_prefix("animation_frame_").is_valid_int()) {
- int frame = components[0].trim_prefix("animation_frame_").to_int();
- if (components[1] == "duration") {
- if (frame < 0 || frame >= tile_set_atlas_source->get_tile_animation_frames_count(coords)) {
- return false;
- }
- r_ret = tile_set_atlas_source->get_tile_animation_frame_duration(coords, frame);
- return true;
- }
}
} else if (alternative > 0) {
if (p_name == "alternative_id") {
@@ -280,6 +299,44 @@ bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_get(const StringName &p_na
}
}
+ // Animation.
+ // Check if all tiles have an alternative_id of 0.
+ bool all_alternatve_id_zero = true;
+ for (TileSelection tile : tiles) {
+ if (tile.alternative != 0) {
+ all_alternatve_id_zero = false;
+ break;
+ }
+ }
+
+ if (all_alternatve_id_zero) {
+ const Vector2i &coords = tiles.front()->get().tile;
+
+ Vector<String> components = String(p_name).split("/", true, 2);
+ if (p_name == "animation_columns") {
+ r_ret = tile_set_atlas_source->get_tile_animation_columns(coords);
+ return true;
+ } else if (p_name == "animation_separation") {
+ r_ret = tile_set_atlas_source->get_tile_animation_separation(coords);
+ return true;
+ } else if (p_name == "animation_speed") {
+ r_ret = tile_set_atlas_source->get_tile_animation_speed(coords);
+ return true;
+ } else if (p_name == "animation_frames_count") {
+ r_ret = tile_set_atlas_source->get_tile_animation_frames_count(coords);
+ return true;
+ } else if (components.size() == 2 && components[0].begins_with("animation_frame_") && components[0].trim_prefix("animation_frame_").is_valid_int()) {
+ int frame = components[0].trim_prefix("animation_frame_").to_int();
+ if (components[1] == "duration") {
+ if (frame < 0 || frame >= tile_set_atlas_source->get_tile_animation_frames_count(coords)) {
+ return false;
+ }
+ r_ret = tile_set_atlas_source->get_tile_animation_frame_duration(coords, frame);
+ return true;
+ }
+ }
+ }
+
for (Set<TileSelection>::Element *E = tiles.front(); E; E = E->next()) {
// Return the first tile with a property matching the name.
// Note: It's a little bit annoying, but the behavior is the same the one in MultiNodeEdit.
@@ -304,29 +361,42 @@ void TileSetAtlasSourceEditor::AtlasTileProxyObject::_get_property_list(List<Pro
return;
}
+ // ID and size related properties.
if (tiles.size() == 1) {
if (tiles.front()->get().alternative == 0) {
p_list->push_back(PropertyInfo(Variant::VECTOR2I, "atlas_coords", PROPERTY_HINT_NONE, ""));
p_list->push_back(PropertyInfo(Variant::VECTOR2I, "size_in_atlas", PROPERTY_HINT_NONE, ""));
-
- // Animation.
- p_list->push_back(PropertyInfo(Variant::NIL, "Animation", PROPERTY_HINT_NONE, "animation_", PROPERTY_USAGE_GROUP));
- p_list->push_back(PropertyInfo(Variant::INT, "animation_columns", PROPERTY_HINT_NONE, ""));
- p_list->push_back(PropertyInfo(Variant::VECTOR2I, "animation_separation", PROPERTY_HINT_NONE, ""));
- p_list->push_back(PropertyInfo(Variant::FLOAT, "animation_speed", PROPERTY_HINT_NONE, ""));
- p_list->push_back(PropertyInfo(Variant::INT, "animation_frames_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ARRAY, "Frames,animation_frame_"));
- if (tile_set_atlas_source->get_tile_animation_frames_count(tiles.front()->get().tile) == 1) {
- p_list->push_back(PropertyInfo(Variant::FLOAT, "animation_frame_0/duration", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY));
- } else {
- for (int i = 0; i < tile_set_atlas_source->get_tile_animation_frames_count(tiles.front()->get().tile); i++) {
- p_list->push_back(PropertyInfo(Variant::FLOAT, vformat("animation_frame_%d/duration", i), PROPERTY_HINT_NONE, ""));
- }
- }
} else {
p_list->push_back(PropertyInfo(Variant::INT, "alternative_id", PROPERTY_HINT_NONE, ""));
}
}
+ // Animation.
+ // Check if all tiles have an alternative_id of 0.
+ bool all_alternatve_id_zero = true;
+ for (TileSelection tile : tiles) {
+ if (tile.alternative != 0) {
+ all_alternatve_id_zero = false;
+ break;
+ }
+ }
+
+ if (all_alternatve_id_zero) {
+ p_list->push_back(PropertyInfo(Variant::NIL, "Animation", PROPERTY_HINT_NONE, "animation_", PROPERTY_USAGE_GROUP));
+ p_list->push_back(PropertyInfo(Variant::INT, "animation_columns", PROPERTY_HINT_NONE, ""));
+ p_list->push_back(PropertyInfo(Variant::VECTOR2I, "animation_separation", PROPERTY_HINT_NONE, ""));
+ p_list->push_back(PropertyInfo(Variant::FLOAT, "animation_speed", PROPERTY_HINT_NONE, ""));
+ p_list->push_back(PropertyInfo(Variant::INT, "animation_frames_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ARRAY, "Frames,animation_frame_"));
+ // Not optimal, but returns value for the first tile. This is similar to what MultiNodeEdit does.
+ if (tile_set_atlas_source->get_tile_animation_frames_count(tiles.front()->get().tile) == 1) {
+ p_list->push_back(PropertyInfo(Variant::FLOAT, "animation_frame_0/duration", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY));
+ } else {
+ for (int i = 0; i < tile_set_atlas_source->get_tile_animation_frames_count(tiles.front()->get().tile); i++) {
+ p_list->push_back(PropertyInfo(Variant::FLOAT, vformat("animation_frame_%d/duration", i), PROPERTY_HINT_NONE, ""));
+ }
+ }
+ }
+
// Get the list of properties common to all tiles (similar to what's done in MultiNodeEdit).
struct PropertyId {
int occurence_id = 0;
diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp
index a5b607c0e8..4a59eb4fb3 100644
--- a/editor/scene_tree_dock.cpp
+++ b/editor/scene_tree_dock.cpp
@@ -1835,8 +1835,8 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V
editor_data->get_undo_redo().add_do_method(this, "_set_owners", edited_scene, owners);
- if (AnimationPlayerEditor::singleton->get_track_editor()->get_root() == node) {
- editor_data->get_undo_redo().add_do_method(AnimationPlayerEditor::singleton->get_track_editor(), "set_root", node);
+ if (AnimationPlayerEditor::get_singleton()->get_track_editor()->get_root() == node) {
+ editor_data->get_undo_redo().add_do_method(AnimationPlayerEditor::get_singleton()->get_track_editor(), "set_root", node);
}
editor_data->get_undo_redo().add_undo_method(new_parent, "remove_child", node);
@@ -1861,8 +1861,8 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V
editor_data->get_undo_redo().add_undo_method(node->get_parent(), "add_child", node);
editor_data->get_undo_redo().add_undo_method(node->get_parent(), "move_child", node, child_pos);
editor_data->get_undo_redo().add_undo_method(this, "_set_owners", edited_scene, owners);
- if (AnimationPlayerEditor::singleton->get_track_editor()->get_root() == node) {
- editor_data->get_undo_redo().add_undo_method(AnimationPlayerEditor::singleton->get_track_editor(), "set_root", node);
+ if (AnimationPlayerEditor::get_singleton()->get_track_editor()->get_root() == node) {
+ editor_data->get_undo_redo().add_undo_method(AnimationPlayerEditor::get_singleton()->get_track_editor(), "set_root", node);
}
if (p_keep_global_xform) {
@@ -2073,8 +2073,8 @@ void SceneTreeDock::_delete_confirm(bool p_cut) {
editor_data->get_undo_redo().add_do_method(n->get_parent(), "remove_child", n);
editor_data->get_undo_redo().add_undo_method(n->get_parent(), "add_child", n);
editor_data->get_undo_redo().add_undo_method(n->get_parent(), "move_child", n, n->get_index());
- if (AnimationPlayerEditor::singleton->get_track_editor()->get_root() == n) {
- editor_data->get_undo_redo().add_undo_method(AnimationPlayerEditor::singleton->get_track_editor(), "set_root", n);
+ if (AnimationPlayerEditor::get_singleton()->get_track_editor()->get_root() == n) {
+ editor_data->get_undo_redo().add_undo_method(AnimationPlayerEditor::get_singleton()->get_track_editor(), "set_root", n);
}
editor_data->get_undo_redo().add_undo_method(this, "_set_owners", edited_scene, owners);
editor_data->get_undo_redo().add_undo_reference(n);
diff --git a/editor/scene_tree_editor.cpp b/editor/scene_tree_editor.cpp
index 8ffaf0829e..c944068455 100644
--- a/editor/scene_tree_editor.cpp
+++ b/editor/scene_tree_editor.cpp
@@ -102,7 +102,7 @@ void SceneTreeEditor::_cell_button_pressed(Object *p_item, int p_column, int p_i
undo_redo->commit_action();
} else if (p_id == BUTTON_PIN) {
if (n->is_class("AnimationPlayer")) {
- AnimationPlayerEditor::singleton->unpin();
+ AnimationPlayerEditor::get_singleton()->unpin();
_update_tree();
}
@@ -386,7 +386,7 @@ bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent, bool p_scroll
_update_visibility_color(p_node, item);
} else if (p_node->is_class("AnimationPlayer")) {
- bool is_pinned = AnimationPlayerEditor::singleton->get_player() == p_node && AnimationPlayerEditor::singleton->is_pinned();
+ bool is_pinned = AnimationPlayerEditor::get_singleton()->get_player() == p_node && AnimationPlayerEditor::get_singleton()->is_pinned();
if (is_pinned) {
item->add_button(0, get_theme_icon(SNAME("Pin"), SNAME("EditorIcons")), BUTTON_PIN, false, TTR("AnimationPlayer is pinned.\nClick to unpin."));
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index c07849bfa8..3abd8672fa 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -244,6 +244,7 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class,
class_type.kind = GDScriptParser::DataType::CLASS;
class_type.class_type = p_class;
class_type.script_path = parser->script_path;
+ class_type.builtin_type = Variant::OBJECT;
p_class->set_datatype(class_type);
if (!p_class->extends_used) {
@@ -1742,7 +1743,7 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
push_error("Cannot assign a new value to a constant.", p_assignment->assignee);
}
- if (!assignee_type.is_variant() && !assigned_value_type.is_variant()) {
+ if (!assignee_type.is_variant() && assigned_value_type.is_hard_type()) {
bool compatible = true;
GDScriptParser::DataType op_type = assigned_value_type;
if (p_assignment->operation != GDScriptParser::AssignmentNode::OP_NONE) {
@@ -1794,27 +1795,24 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
case GDScriptParser::IdentifierNode::FUNCTION_PARAMETER: {
GDScriptParser::DataType id_type = identifier->parameter_source->get_datatype();
if (!id_type.is_hard_type()) {
- id_type = assigned_value_type;
- id_type.type_source = GDScriptParser::DataType::INFERRED;
- id_type.is_constant = false;
+ id_type.kind = GDScriptParser::DataType::VARIANT;
+ id_type.type_source = GDScriptParser::DataType::UNDETECTED;
identifier->parameter_source->set_datatype(id_type);
}
} break;
case GDScriptParser::IdentifierNode::LOCAL_VARIABLE: {
GDScriptParser::DataType id_type = identifier->variable_source->get_datatype();
if (!id_type.is_hard_type()) {
- id_type = assigned_value_type;
- id_type.type_source = GDScriptParser::DataType::INFERRED;
- id_type.is_constant = false;
+ id_type.kind = GDScriptParser::DataType::VARIANT;
+ id_type.type_source = GDScriptParser::DataType::UNDETECTED;
identifier->variable_source->set_datatype(id_type);
}
} break;
case GDScriptParser::IdentifierNode::LOCAL_ITERATOR: {
GDScriptParser::DataType id_type = identifier->bind_source->get_datatype();
if (!id_type.is_hard_type()) {
- id_type = assigned_value_type;
- id_type.type_source = GDScriptParser::DataType::INFERRED;
- id_type.is_constant = false;
+ id_type.kind = GDScriptParser::DataType::VARIANT;
+ id_type.type_source = GDScriptParser::DataType::UNDETECTED;
identifier->variable_source->set_datatype(id_type);
}
} break;
@@ -2941,7 +2939,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
} else {
GDScriptParser::DataType base_type = p_subscript->base->get_datatype();
- if (base_type.is_variant()) {
+ if (base_type.is_variant() || !base_type.is_hard_type()) {
result_type.kind = GDScriptParser::DataType::VARIANT;
mark_node_unsafe(p_subscript);
} else {
@@ -3064,6 +3062,9 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
result_type.kind = GDScriptParser::DataType::BUILTIN;
result_type.type_source = base_type.is_hard_type() ? GDScriptParser::DataType::ANNOTATED_INFERRED : GDScriptParser::DataType::INFERRED;
+ if (base_type.kind != GDScriptParser::DataType::BUILTIN) {
+ base_type.builtin_type = Variant::OBJECT;
+ }
switch (base_type.builtin_type) {
// Can't index at all.
case Variant::RID:
@@ -3124,6 +3125,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
case Variant::PLANE:
case Variant::COLOR:
case Variant::DICTIONARY:
+ case Variant::OBJECT:
result_type.kind = GDScriptParser::DataType::VARIANT;
result_type.type_source = GDScriptParser::DataType::UNDETECTED;
break;
@@ -3138,7 +3140,6 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
}
break;
// Here for completeness.
- case Variant::OBJECT:
case Variant::VARIANT_MAX:
break;
}
diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp
index c383830c82..50c1f68440 100644
--- a/modules/gdscript/tests/gdscript_test_runner.cpp
+++ b/modules/gdscript/tests/gdscript_test_runner.cpp
@@ -133,13 +133,12 @@ GDScriptTestRunner::GDScriptTestRunner(const String &p_source_dir, bool p_init_l
if (do_init_languages) {
init_language(p_source_dir);
-
- // Enable all warnings for GDScript, so we can test them.
- ProjectSettings::get_singleton()->set_setting("debug/gdscript/warnings/enable", true);
- for (int i = 0; i < (int)GDScriptWarning::WARNING_MAX; i++) {
- String warning = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)i).to_lower();
- ProjectSettings::get_singleton()->set_setting("debug/gdscript/warnings/" + warning, true);
- }
+ }
+ // Enable all warnings for GDScript, so we can test them.
+ ProjectSettings::get_singleton()->set_setting("debug/gdscript/warnings/enable", true);
+ for (int i = 0; i < (int)GDScriptWarning::WARNING_MAX; i++) {
+ String warning = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)i).to_lower();
+ ProjectSettings::get_singleton()->set_setting("debug/gdscript/warnings/" + warning, true);
}
// Enable printing to show results
diff --git a/modules/gdscript/tests/scripts/analyzer/features/auto_inferred_type_dont_error.out b/modules/gdscript/tests/scripts/analyzer/features/auto_inferred_type_dont_error.out
index 0e9f482af4..481016138a 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/auto_inferred_type_dont_error.out
+++ b/modules/gdscript/tests/scripts/analyzer/features/auto_inferred_type_dont_error.out
@@ -1,2 +1,6 @@
GDTEST_OK
+>> WARNING
+>> Line: 6
+>> UNSAFE_METHOD_ACCESS
+>> The method 'free' is not present on the inferred type 'Variant' (but may be present on a subtype).
Ok
diff --git a/modules/gdscript/tests/scripts/analyzer/features/class_inference_is_weak.gd b/modules/gdscript/tests/scripts/analyzer/features/class_inference_is_weak.gd
new file mode 100644
index 0000000000..630b20c282
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/class_inference_is_weak.gd
@@ -0,0 +1,11 @@
+# https://github.com/godotengine/godot/issues/43503
+
+var test_var = null
+
+
+func test():
+ print(test_var.x)
+
+
+func _init():
+ test_var = Vector3()
diff --git a/modules/gdscript/tests/scripts/analyzer/features/class_inference_is_weak.out b/modules/gdscript/tests/scripts/analyzer/features/class_inference_is_weak.out
new file mode 100644
index 0000000000..94e2ec2af8
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/class_inference_is_weak.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+0
diff --git a/modules/gdscript/tests/scripts/analyzer/features/local_inference_is_weak.gd b/modules/gdscript/tests/scripts/analyzer/features/local_inference_is_weak.gd
new file mode 100644
index 0000000000..b3784dffa3
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/local_inference_is_weak.gd
@@ -0,0 +1,14 @@
+# https://github.com/godotengine/godot/issues/41064
+var x = true
+
+func test():
+ var int_var: int = 0
+ var dyn_var = 2
+
+ if x:
+ dyn_var = 5
+ else:
+ dyn_var = Node.new()
+
+ int_var = dyn_var
+ print(int_var)
diff --git a/modules/gdscript/tests/scripts/analyzer/features/local_inference_is_weak.out b/modules/gdscript/tests/scripts/analyzer/features/local_inference_is_weak.out
new file mode 100644
index 0000000000..952029f665
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/local_inference_is_weak.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+5
diff --git a/modules/gdscript/tests/scripts/analyzer/features/subscript_self.gd b/modules/gdscript/tests/scripts/analyzer/features/subscript_self.gd
new file mode 100644
index 0000000000..f9a8b23b92
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/subscript_self.gd
@@ -0,0 +1,8 @@
+# https://github.com/godotengine/godot/issues/43221
+extends Node
+
+func test():
+ name = "Node"
+ print(self["name"])
+ self["name"] = "Changed"
+ print(name)
diff --git a/modules/gdscript/tests/scripts/analyzer/features/subscript_self.out b/modules/gdscript/tests/scripts/analyzer/features/subscript_self.out
new file mode 100644
index 0000000000..6417f4f8da
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/subscript_self.out
@@ -0,0 +1,3 @@
+GDTEST_OK
+Node
+Changed
diff --git a/modules/gridmap/grid_map_editor_plugin.cpp b/modules/gridmap/grid_map_editor_plugin.cpp
index c170bb107e..08d55de976 100644
--- a/modules/gridmap/grid_map_editor_plugin.cpp
+++ b/modules/gridmap/grid_map_editor_plugin.cpp
@@ -603,9 +603,9 @@ void GridMapEditor::_do_paste() {
_clear_clipboard_data();
}
-bool GridMapEditor::forward_spatial_input_event(Camera3D *p_camera, const Ref<InputEvent> &p_event) {
+EditorPlugin::AfterGUIInput GridMapEditor::forward_spatial_input_event(Camera3D *p_camera, const Ref<InputEvent> &p_event) {
if (!node) {
- return false;
+ return EditorPlugin::AFTER_GUI_INPUT_PASS;
}
Ref<InputEventMouseButton> mb = p_event;
@@ -616,12 +616,12 @@ bool GridMapEditor::forward_spatial_input_event(Camera3D *p_camera, const Ref<In
floor->set_value(floor->get_value() + mb->get_factor());
}
- return true; // Eaten.
+ return EditorPlugin::AFTER_GUI_INPUT_STOP; // Eaten.
} else if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && (mb->is_command_pressed() || mb->is_shift_pressed())) {
if (mb->is_pressed()) {
floor->set_value(floor->get_value() - mb->get_factor());
}
- return true;
+ return EditorPlugin::AFTER_GUI_INPUT_STOP;
}
if (mb->is_pressed()) {
@@ -648,19 +648,22 @@ bool GridMapEditor::forward_spatial_input_event(Camera3D *p_camera, const Ref<In
_clear_clipboard_data();
input_action = INPUT_NONE;
_update_paste_indicator();
- return true;
+ return EditorPlugin::AFTER_GUI_INPUT_STOP;
} else if (selection.active) {
_set_selection(false);
- return true;
+ return EditorPlugin::AFTER_GUI_INPUT_STOP;
} else {
input_action = INPUT_ERASE;
set_items.clear();
}
} else {
- return false;
+ return EditorPlugin::AFTER_GUI_INPUT_PASS;
}
- return do_input_action(p_camera, Point2(mb->get_position().x, mb->get_position().y), true);
+ if (do_input_action(p_camera, Point2(mb->get_position().x, mb->get_position().y), true)) {
+ return EditorPlugin::AFTER_GUI_INPUT_STOP;
+ }
+ return EditorPlugin::AFTER_GUI_INPUT_PASS;
} else {
if ((mb->get_button_index() == MOUSE_BUTTON_RIGHT && input_action == INPUT_ERASE) || (mb->get_button_index() == MOUSE_BUTTON_LEFT && input_action == INPUT_PAINT)) {
if (set_items.size()) {
@@ -677,7 +680,11 @@ bool GridMapEditor::forward_spatial_input_event(Camera3D *p_camera, const Ref<In
}
set_items.clear();
input_action = INPUT_NONE;
- return set_items.size() > 0;
+
+ if (set_items.size() > 0) {
+ return EditorPlugin::AFTER_GUI_INPUT_STOP;
+ }
+ return EditorPlugin::AFTER_GUI_INPUT_PASS;
}
if (mb->get_button_index() == MOUSE_BUTTON_LEFT && input_action == INPUT_SELECT) {
@@ -690,11 +697,11 @@ bool GridMapEditor::forward_spatial_input_event(Camera3D *p_camera, const Ref<In
if (mb->get_button_index() == MOUSE_BUTTON_LEFT && input_action != INPUT_NONE) {
set_items.clear();
input_action = INPUT_NONE;
- return true;
+ return EditorPlugin::AFTER_GUI_INPUT_STOP;
}
if (mb->get_button_index() == MOUSE_BUTTON_RIGHT && (input_action == INPUT_ERASE || input_action == INPUT_PASTE)) {
input_action = INPUT_NONE;
- return true;
+ return EditorPlugin::AFTER_GUI_INPUT_STOP;
}
}
}
@@ -702,7 +709,10 @@ bool GridMapEditor::forward_spatial_input_event(Camera3D *p_camera, const Ref<In
Ref<InputEventMouseMotion> mm = p_event;
if (mm.is_valid()) {
- return do_input_action(p_camera, mm->get_position(), false);
+ if (do_input_action(p_camera, mm->get_position(), false)) {
+ return EditorPlugin::AFTER_GUI_INPUT_STOP;
+ }
+ return EditorPlugin::AFTER_GUI_INPUT_PASS;
}
Ref<InputEventKey> k = p_event;
@@ -714,16 +724,16 @@ bool GridMapEditor::forward_spatial_input_event(Camera3D *p_camera, const Ref<In
_clear_clipboard_data();
input_action = INPUT_NONE;
_update_paste_indicator();
- return true;
+ return EditorPlugin::AFTER_GUI_INPUT_STOP;
} else if (selection.active) {
_set_selection(false);
- return true;
+ return EditorPlugin::AFTER_GUI_INPUT_STOP;
} else {
selected_palette = -1;
mesh_library_palette->deselect_all();
update_palette();
_update_cursor_instance();
- return true;
+ return EditorPlugin::AFTER_GUI_INPUT_STOP;
}
}
@@ -731,12 +741,12 @@ bool GridMapEditor::forward_spatial_input_event(Camera3D *p_camera, const Ref<In
if (k->get_keycode() == options->get_popup()->get_item_accelerator(options->get_popup()->get_item_index(MENU_OPTION_PREV_LEVEL))) {
selection.click[edit_axis]--;
_validate_selection();
- return true;
+ return EditorPlugin::AFTER_GUI_INPUT_STOP;
}
if (k->get_keycode() == options->get_popup()->get_item_accelerator(options->get_popup()->get_item_index(MENU_OPTION_NEXT_LEVEL))) {
selection.click[edit_axis]++;
_validate_selection();
- return true;
+ return EditorPlugin::AFTER_GUI_INPUT_STOP;
}
}
}
@@ -755,12 +765,12 @@ bool GridMapEditor::forward_spatial_input_event(Camera3D *p_camera, const Ref<In
if (step) {
floor->set_value(floor->get_value() + step);
}
- return true;
+ return EditorPlugin::AFTER_GUI_INPUT_STOP;
}
}
accumulated_floor_delta = 0.0;
- return false;
+ return EditorPlugin::AFTER_GUI_INPUT_PASS;
}
struct _CGMEItemSort {
diff --git a/modules/gridmap/grid_map_editor_plugin.h b/modules/gridmap/grid_map_editor_plugin.h
index 4bc849e071..1a6b1310d8 100644
--- a/modules/gridmap/grid_map_editor_plugin.h
+++ b/modules/gridmap/grid_map_editor_plugin.h
@@ -232,7 +232,7 @@ protected:
static void _bind_methods();
public:
- bool forward_spatial_input_event(Camera3D *p_camera, const Ref<InputEvent> &p_event);
+ EditorPlugin::AfterGUIInput forward_spatial_input_event(Camera3D *p_camera, const Ref<InputEvent> &p_event);
void edit(GridMap *p_gridmap);
GridMapEditor() {}
@@ -250,7 +250,7 @@ protected:
void _notification(int p_what);
public:
- virtual bool forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override { return grid_map_editor->forward_spatial_input_event(p_camera, p_event); }
+ virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override { return grid_map_editor->forward_spatial_input_event(p_camera, p_event); }
virtual String get_name() const override { return "GridMap"; }
bool has_main_screen() const override { return false; }
virtual void edit(Object *p_object) override;
diff --git a/scene/3d/node_3d.cpp b/scene/3d/node_3d.cpp
index 12470939f5..272a06bce4 100644
--- a/scene/3d/node_3d.cpp
+++ b/scene/3d/node_3d.cpp
@@ -376,6 +376,18 @@ void Node3D::update_gizmos() {
#endif
}
+void Node3D::set_subgizmo_selection(Ref<Node3DGizmo> p_gizmo, int p_id, Transform3D p_transform) {
+#ifdef TOOLS_ENABLED
+ if (!is_inside_world()) {
+ return;
+ }
+
+ if (Engine::get_singleton()->is_editor_hint() && get_tree()->is_node_being_edited(this)) {
+ get_tree()->call_group_flags(0, SceneStringNames::get_singleton()->_spatial_editor_group, SceneStringNames::get_singleton()->_set_subgizmo_selection, this, p_gizmo, p_id, p_transform);
+ }
+#endif
+}
+
void Node3D::clear_subgizmo_selection() {
#ifdef TOOLS_ENABLED
if (!is_inside_world()) {
@@ -792,6 +804,7 @@ void Node3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_gizmo", "gizmo"), &Node3D::add_gizmo);
ClassDB::bind_method(D_METHOD("get_gizmos"), &Node3D::get_gizmos_bind);
ClassDB::bind_method(D_METHOD("clear_gizmos"), &Node3D::clear_gizmos);
+ ClassDB::bind_method(D_METHOD("set_subgizmo_selection", "gizmo", "id", "transform"), &Node3D::set_subgizmo_selection);
ClassDB::bind_method(D_METHOD("clear_subgizmo_selection"), &Node3D::clear_subgizmo_selection);
ClassDB::bind_method(D_METHOD("set_visible", "visible"), &Node3D::set_visible);
diff --git a/scene/3d/node_3d.h b/scene/3d/node_3d.h
index 0fd0c4e205..d6dcdd96fe 100644
--- a/scene/3d/node_3d.h
+++ b/scene/3d/node_3d.h
@@ -92,6 +92,7 @@ class Node3D : public Node {
Vector<Ref<Node3DGizmo>> gizmos;
bool gizmos_disabled = false;
bool gizmos_dirty = false;
+ bool transform_gizmo_visible = true;
#endif
} data;
@@ -145,6 +146,8 @@ public:
#ifdef TOOLS_ENABLED
virtual Transform3D get_global_gizmo_transform() const;
virtual Transform3D get_local_gizmo_transform() const;
+ virtual void set_transform_gizmo_visible(bool p_enabled) { data.transform_gizmo_visible = p_enabled; };
+ virtual bool is_transform_gizmo_visible() const { return data.transform_gizmo_visible; };
#endif
void set_as_top_level(bool p_enabled);
@@ -155,6 +158,7 @@ public:
void set_disable_gizmos(bool p_enabled);
void update_gizmos();
+ void set_subgizmo_selection(Ref<Node3DGizmo> p_gizmo, int p_id, Transform3D p_transform = Transform3D());
void clear_subgizmo_selection();
Vector<Ref<Node3DGizmo>> get_gizmos() const;
Array get_gizmos_bind() const;
diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp
index b7a79a2645..0a19e1d07b 100644
--- a/scene/3d/skeleton_3d.cpp
+++ b/scene/3d/skeleton_3d.cpp
@@ -32,6 +32,7 @@
#include "core/object/message_queue.h"
#include "core/variant/type_info.h"
+#include "editor/plugins/skeleton_3d_editor_plugin.h"
#include "scene/3d/physics_body_3d.h"
#include "scene/resources/skeleton_modification_3d.h"
#include "scene/resources/surface_tool.h"
@@ -178,7 +179,7 @@ void Skeleton3D::_update_process_order() {
for (int i = 0; i < len; i++) {
if (bonesptr[i].parent >= len) {
- //validate this just in case
+ // Validate this just in case.
ERR_PRINT("Bone " + itos(i) + " has invalid parent: " + itos(bonesptr[i].parent));
bonesptr[i].parent = -1;
}
@@ -186,9 +187,9 @@ void Skeleton3D::_update_process_order() {
if (bonesptr[i].parent != -1) {
int parent_bone_idx = bonesptr[i].parent;
- // Check to see if this node is already added to the parent:
+ // Check to see if this node is already added to the parent.
if (bonesptr[parent_bone_idx].child_bones.find(i) < 0) {
- // Add the child node
+ // Add the child node.
bonesptr[parent_bone_idx].child_bones.push_back(i);
} else {
ERR_PRINT("Skeleton3D parenthood graph is cyclic");
@@ -210,10 +211,10 @@ void Skeleton3D::_notification(int p_what) {
int len = bones.size();
dirty = false;
- // Update bone transforms
+ // Update bone transforms.
force_update_all_bone_transforms();
- //update skins
+ // Update skins.
for (Set<SkinReference *>::Element *E = skin_bindings.front(); E; E = E->next()) {
const Skin *skin = E->get()->skin.operator->();
RID skeleton = E->get()->skeleton;
@@ -231,7 +232,7 @@ void Skeleton3D::_notification(int p_what) {
StringName bind_name = skin->get_bind_name(i);
if (bind_name != StringName()) {
- //bind name used, use this
+ // Bind name used, use this.
bool found = false;
for (int j = 0; j < len; j++) {
if (bonesptr[j].name == bind_name) {
@@ -453,7 +454,8 @@ int Skeleton3D::get_bone_axis_forward_enum(int p_bone) {
return bones[p_bone].rest_bone_forward_axis;
}
-// skeleton creation api
+// Skeleton creation api
+
void Skeleton3D::add_bone(const String &p_name) {
ERR_FAIL_COND(p_name == "" || p_name.find(":") != -1 || p_name.find("/") != -1);
@@ -626,6 +628,7 @@ void Skeleton3D::set_bone_enabled(int p_bone, bool p_enabled) {
ERR_FAIL_INDEX(p_bone, bone_size);
bones.write[p_bone].enabled = p_enabled;
+ emit_signal(SceneStringNames::get_singleton()->bone_enabled_changed, p_bone);
_make_dirty();
}
@@ -635,6 +638,16 @@ bool Skeleton3D::is_bone_enabled(int p_bone) const {
return bones[p_bone].enabled;
}
+void Skeleton3D::set_show_rest_only(bool p_enabled) {
+ show_rest_only = p_enabled;
+ emit_signal(SceneStringNames::get_singleton()->show_rest_only_changed);
+ _make_dirty();
+}
+
+bool Skeleton3D::is_show_rest_only() const {
+ return show_rest_only;
+}
+
void Skeleton3D::clear_bones() {
bones.clear();
process_order_dirty = true;
@@ -642,7 +655,7 @@ void Skeleton3D::clear_bones() {
_make_dirty();
}
-// posing api
+// Posing api
void Skeleton3D::set_bone_pose(int p_bone, const Transform3D &p_pose) {
const int bone_size = bones.size();
@@ -697,7 +710,7 @@ void Skeleton3D::localize_rests() {
set_bone_rest(current_bone_idx, bones[bones[current_bone_idx].parent].rest.affine_inverse() * bones[current_bone_idx].rest);
}
- // Add the bone's children to the list of bones to be processed
+ // Add the bone's children to the list of bones to be processed.
int child_bone_size = bones[current_bone_idx].child_bones.size();
for (int i = 0; i < child_bone_size; i++) {
bones_to_process.push_back(bones[current_bone_idx].child_bones[i]);
@@ -831,7 +844,7 @@ void Skeleton3D::physical_bones_start_simulation_on(const TypedArray<StringName>
Vector<int> sim_bones;
if (p_bones.size() <= 0) {
- sim_bones.push_back(0); // if no bones is specified, activate ragdoll on full body
+ sim_bones.push_back(0); // If no bones is specified, activate ragdoll on full body.
} else {
sim_bones.resize(p_bones.size());
int c = 0;
@@ -884,19 +897,19 @@ Ref<SkinReference> Skeleton3D::register_skin(const Ref<Skin> &p_skin) {
Ref<Skin> skin = p_skin;
if (skin.is_null()) {
- //need to create one from existing code, this is for compatibility only
- //when skeletons did not support skins. It is also used by gizmo
- //to display the skeleton.
+ // Need to create one from existing code, this is for compatibility only
+ // when skeletons did not support skins. It is also used by gizmo
+ // to display the skeleton.
skin.instantiate();
skin->set_bind_count(bones.size());
- _update_process_order(); //just in case
+ _update_process_order(); // Just in case.
- // pose changed, rebuild cache of inverses
+ // Pose changed, rebuild cache of inverses.
const Bone *bonesptr = bones.ptr();
int len = bones.size();
- // calculate global rests and invert them
+ // Calculate global rests and invert them.
LocalVector<int> bones_to_process;
bones_to_process = get_parentless_bones();
while (bones_to_process.size() > 0) {
@@ -919,7 +932,7 @@ Ref<SkinReference> Skeleton3D::register_skin(const Ref<Skin> &p_skin) {
}
for (int i = 0; i < len; i++) {
- //the inverse is what is actually required
+ // The inverse is what is actually required.
skin->set_bind_bone(i, i);
skin->set_bind_pose(i, skin->get_bind_pose(i).affine_inverse());
}
@@ -940,11 +953,17 @@ Ref<SkinReference> Skeleton3D::register_skin(const Ref<Skin> &p_skin) {
skin->connect("changed", Callable(skin_ref.operator->(), "_skin_changed"));
- _make_dirty(); //skin needs to be updated, so update skeleton
+ _make_dirty(); // Skin needs to be updated, so update skeleton.
return skin_ref;
}
+void Skeleton3D::force_update_all_dirty_bones() {
+ if (dirty) {
+ const_cast<Skeleton3D *>(this)->notification(NOTIFICATION_UPDATE_SKELETON);
+ }
+}
+
void Skeleton3D::force_update_all_bone_transforms() {
_update_process_order();
@@ -966,9 +985,10 @@ void Skeleton3D::force_update_bone_children_transforms(int p_bone_idx) {
bones_to_process.erase(current_bone_idx);
Bone &b = bonesptr[current_bone_idx];
+ bool bone_enabled = b.enabled && !show_rest_only;
if (b.disable_rest) {
- if (b.enabled) {
+ if (bone_enabled) {
Transform3D pose = b.pose;
if (b.custom_pose_enable) {
pose = b.custom_pose * pose;
@@ -991,7 +1011,7 @@ void Skeleton3D::force_update_bone_children_transforms(int p_bone_idx) {
}
} else {
- if (b.enabled) {
+ if (bone_enabled) {
Transform3D pose = b.pose;
if (b.custom_pose_enable) {
pose = b.custom_pose * pose;
@@ -1035,7 +1055,7 @@ void Skeleton3D::force_update_bone_children_transforms(int p_bone_idx) {
b.global_pose_override_amount = 0.0;
}
- // Add the bone's children to the list of bones to be processed
+ // Add the bone's children to the list of bones to be processed.
int child_bone_size = b.child_bones.size();
for (int i = 0; i < child_bone_size; i++) {
bones_to_process.push_back(b.child_bones[i]);
@@ -1045,7 +1065,7 @@ void Skeleton3D::force_update_bone_children_transforms(int p_bone_idx) {
}
}
-// helper functions
+// Helper functions
Transform3D Skeleton3D::global_pose_to_world_transform(Transform3D p_global_pose) {
return get_global_transform() * p_global_pose;
@@ -1175,6 +1195,9 @@ void Skeleton3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_bone_pose", "bone_idx"), &Skeleton3D::get_bone_pose);
ClassDB::bind_method(D_METHOD("set_bone_pose", "bone_idx", "pose"), &Skeleton3D::set_bone_pose);
+ ClassDB::bind_method(D_METHOD("is_bone_enabled", "bone_idx"), &Skeleton3D::is_bone_enabled);
+ ClassDB::bind_method(D_METHOD("set_bone_enabled", "bone_idx", "enabled"), &Skeleton3D::set_bone_enabled, DEFVAL(true));
+
ClassDB::bind_method(D_METHOD("clear_bones_global_pose_override"), &Skeleton3D::clear_bones_global_pose_override);
ClassDB::bind_method(D_METHOD("set_bone_global_pose_override", "bone_idx", "pose", "amount", "persistent"), &Skeleton3D::set_bone_global_pose_override, DEFVAL(false));
ClassDB::bind_method(D_METHOD("get_bone_global_pose_override", "bone_idx"), &Skeleton3D::get_bone_global_pose_override);
@@ -1198,6 +1221,9 @@ void Skeleton3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("local_pose_to_global_pose", "bone_idx", "local_pose"), &Skeleton3D::local_pose_to_global_pose);
ClassDB::bind_method(D_METHOD("global_pose_z_forward_to_bone_forward", "bone_idx", "basis"), &Skeleton3D::global_pose_z_forward_to_bone_forward);
+ ClassDB::bind_method(D_METHOD("set_show_rest_only"), &Skeleton3D::set_show_rest_only);
+ ClassDB::bind_method(D_METHOD("is_show_rest_only"), &Skeleton3D::is_show_rest_only);
+
ClassDB::bind_method(D_METHOD("set_animate_physical_bones"), &Skeleton3D::set_animate_physical_bones);
ClassDB::bind_method(D_METHOD("get_animate_physical_bones"), &Skeleton3D::get_animate_physical_bones);
@@ -1212,6 +1238,7 @@ void Skeleton3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("execute_modifications", "delta", "execution_mode"), &Skeleton3D::execute_modifications);
#ifndef _3D_DISABLED
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_rest_only"), "set_show_rest_only", "is_show_rest_only");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "animate_physical_bones"), "set_animate_physical_bones", "get_animate_physical_bones");
#endif // _3D_DISABLED
@@ -1220,6 +1247,8 @@ void Skeleton3D::_bind_methods() {
#endif // TOOLS_ENABLED
ADD_SIGNAL(MethodInfo("bone_pose_changed", PropertyInfo(Variant::INT, "bone_idx")));
+ ADD_SIGNAL(MethodInfo("bone_enabled_changed", PropertyInfo(Variant::INT, "bone_idx")));
+ ADD_SIGNAL(MethodInfo("show_rest_only_changed"));
BIND_CONSTANT(NOTIFICATION_UPDATE_SKELETON);
}
@@ -1228,7 +1257,7 @@ Skeleton3D::Skeleton3D() {
}
Skeleton3D::~Skeleton3D() {
- //some skins may remain bound
+ // Some skins may remain bound.
for (Set<SkinReference *>::Element *E = skin_bindings.front(); E; E = E->next()) {
E->get()->skeleton_node = nullptr;
}
diff --git a/scene/3d/skeleton_3d.h b/scene/3d/skeleton_3d.h
index c8a19db813..a50dc158f3 100644
--- a/scene/3d/skeleton_3d.h
+++ b/scene/3d/skeleton_3d.h
@@ -137,6 +137,8 @@ private:
void _make_dirty();
bool dirty = false;
+ bool show_rest_only = false;
+
uint64_t version = 1;
void _update_process_order();
@@ -197,6 +199,9 @@ public:
void set_bone_enabled(int p_bone, bool p_enabled);
bool is_bone_enabled(int p_bone) const;
+
+ void set_show_rest_only(bool p_enabled);
+ bool is_show_rest_only() const;
void clear_bones();
// posing api
@@ -219,6 +224,7 @@ public:
Ref<SkinReference> register_skin(const Ref<Skin> &p_skin);
+ void force_update_all_dirty_bones();
void force_update_all_bone_transforms();
void force_update_bone_children_transforms(int bone_idx);
diff --git a/scene/scene_string_names.cpp b/scene/scene_string_names.cpp
index 29e5dd6056..186764e69e 100644
--- a/scene/scene_string_names.cpp
+++ b/scene/scene_string_names.cpp
@@ -64,6 +64,8 @@ SceneStringNames::SceneStringNames() {
pose_updated = StaticCString::create("pose_updated");
bone_pose_changed = StaticCString::create("bone_pose_changed");
+ bone_enabled_changed = StaticCString::create("bone_enabled_changed");
+ show_rest_only_changed = StaticCString::create("show_rest_only_changed");
mouse_entered = StaticCString::create("mouse_entered");
mouse_exited = StaticCString::create("mouse_exited");
@@ -135,6 +137,7 @@ SceneStringNames::SceneStringNames() {
_spatial_editor_group = StaticCString::create("_spatial_editor_group");
_request_gizmo = StaticCString::create("_request_gizmo");
+ _set_subgizmo_selection = StaticCString::create("_set_subgizmo_selection");
_clear_subgizmo_selection = StaticCString::create("_clear_subgizmo_selection");
offset = StaticCString::create("offset");
diff --git a/scene/scene_string_names.h b/scene/scene_string_names.h
index 5e3195952e..67007c85e0 100644
--- a/scene/scene_string_names.h
+++ b/scene/scene_string_names.h
@@ -100,6 +100,8 @@ public:
StringName pose_updated;
StringName bone_pose_changed;
+ StringName bone_enabled_changed;
+ StringName show_rest_only_changed;
StringName body_shape_entered;
StringName body_entered;
@@ -155,6 +157,7 @@ public:
StringName _spatial_editor_group;
StringName _request_gizmo;
+ StringName _set_subgizmo_selection;
StringName _clear_subgizmo_selection;
StringName offset;