From dba67e552f3ded667d287aee3c2d24abf454db82 Mon Sep 17 00:00:00 2001 From: Saracen Date: Sat, 2 May 2020 10:11:48 +0100 Subject: [PATCH] Custom Skeleton3DEditorPlugin Add fallthrough hint Add missing semicolon Fix regression from Skeleton3D Inspector redesign It can be reproduced by reimporting many FBX files with a skeleton. A change in commit f7fdc87 [Merged on May 27] mistakenly changed the Skeleton3D "pose" property from PROPERTY_USAGE_EDITOR to PROPERTY_USAGE_NOEDITOR. skel edit fix selection --- doc/classes/EditorPlugin.xml | 10 +- editor/animation_track_editor.cpp | 20 + editor/animation_track_editor.h | 2 + editor/editor_node.cpp | 4 +- editor/editor_node.h | 2 +- editor/editor_plugin.cpp | 6 +- editor/editor_plugin.h | 2 +- editor/editor_themes.cpp | 1 + editor/icons/icon_editor_bone_handle.svg | 1 + editor/icons/icon_tool_bone_move.svg | 1 + editor/icons/icon_tool_bone_rest.svg | 1 + editor/icons/icon_tool_bone_rotate.svg | 1 + editor/icons/icon_tool_bone_scale.svg | 1 + editor/icons/icon_tool_bone_select.svg | 1 + .../collision_polygon_editor_plugin.cpp | 2 +- .../plugins/collision_polygon_editor_plugin.h | 4 +- editor/plugins/path_editor_plugin.cpp | 2 +- editor/plugins/path_editor_plugin.h | 2 +- editor/plugins/skeleton_editor_plugin.cpp | 1705 ++++++++++++++++- editor/plugins/skeleton_editor_plugin.h | 234 ++- editor/plugins/spatial_editor_plugin.cpp | 173 +- editor/plugins/spatial_editor_plugin.h | 99 +- editor/spatial_editor_gizmos.cpp | 94 +- editor/spatial_editor_gizmos.h | 5 + modules/gridmap/grid_map_editor_plugin.h | 2 +- scene/3d/skeleton.cpp | 37 +- scene/3d/skeleton.h | 6 + 27 files changed, 2223 insertions(+), 195 deletions(-) create mode 100644 editor/icons/icon_editor_bone_handle.svg create mode 100644 editor/icons/icon_tool_bone_move.svg create mode 100644 editor/icons/icon_tool_bone_rest.svg create mode 100644 editor/icons/icon_tool_bone_rotate.svg create mode 100644 editor/icons/icon_tool_bone_scale.svg create mode 100644 editor/icons/icon_tool_bone_select.svg diff --git a/doc/classes/EditorPlugin.xml b/doc/classes/EditorPlugin.xml index 3f741d3e76..585580261b 100644 --- a/doc/classes/EditorPlugin.xml +++ b/doc/classes/EditorPlugin.xml @@ -278,22 +278,24 @@ - + - + + + Called when there is a root node in the current edited scene, [method handles] is implemented and an [InputEvent] happens in the 3D 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: [codeblock] # Prevents the InputEvent to reach other Editor classes - func forward_spatial_gui_input(camera, event): + func forward_spatial_gui_input(index, camera, event): var forward = true return forward [/codeblock] Must [code]return false[/code] in order to forward the [InputEvent] to other Editor classes. Example: [codeblock] # Consumes InputEventMouseMotion and forwards other InputEvent types - func forward_spatial_gui_input(camera, event): + func forward_spatial_gui_input(index, camera, event): var forward = false if event is InputEventMouseMotion: forward = true diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index b5792034cf..cc9725d0bc 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -3562,6 +3562,26 @@ void AnimationTrackEditor::_insert_delay() { insert_queue = false; } +bool AnimationTrackEditor::has_transform_key(Spatial *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; + + if (animation->find_track(path) >= 0) { + return true; + } + return false; +} + void AnimationTrackEditor::insert_transform_key(Spatial *p_node, const String &p_sub, const Transform &p_xform) { if (!keying) diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h index e1798affa9..fa8ac3ff69 100644 --- a/editor/animation_track_editor.h +++ b/editor/animation_track_editor.h @@ -514,6 +514,8 @@ public: void insert_value_key(const String &p_property, const Variant &p_value, bool p_advance); void insert_transform_key(Spatial *p_node, const String &p_sub, const Transform &p_xform); + bool has_transform_key(Spatial *p_node, const String &p_sub); + void show_select_node_warning(bool p_show); bool is_key_selected(int p_track, int p_key) const; diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 1db054c26f..d99598f817 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -7151,7 +7151,7 @@ bool EditorPluginList::forward_gui_input(const Ref &p_event) { return discard; } -bool EditorPluginList::forward_spatial_gui_input(Camera *p_camera, const Ref &p_event, bool serve_when_force_input_enabled) { +bool EditorPluginList::forward_spatial_gui_input(int p_index, Camera *p_camera, const Ref &p_event, bool serve_when_force_input_enabled) { bool discard = false; for (int i = 0; i < plugins_list.size(); i++) { @@ -7159,7 +7159,7 @@ bool EditorPluginList::forward_spatial_gui_input(Camera *p_camera, const Refforward_spatial_gui_input(p_camera, p_event)) { + if (plugins_list[i]->forward_spatial_gui_input(p_index, p_camera, p_event)) { discard = true; } } diff --git a/editor/editor_node.h b/editor/editor_node.h index cddc0c84e4..b8d247abc9 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -907,7 +907,7 @@ public: bool forward_gui_input(const Ref &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(Camera *p_camera, const Ref &p_event, bool serve_when_force_input_enabled); + bool forward_spatial_gui_input(int p_index, Camera *p_camera, const Ref &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 d4c16ee042..89e3316680 100644 --- a/editor/editor_plugin.cpp +++ b/editor/editor_plugin.cpp @@ -617,10 +617,10 @@ int EditorPlugin::update_overlays() const { } } -bool EditorPlugin::forward_spatial_gui_input(Camera *p_camera, const Ref &p_event) { +bool EditorPlugin::forward_spatial_gui_input(int p_index, Camera *p_camera, const Ref &p_event) { if (get_script_instance() && get_script_instance()->has_method("forward_spatial_gui_input")) { - return get_script_instance()->call("forward_spatial_gui_input", p_camera, p_event); + return get_script_instance()->call("forward_spatial_gui_input", p_index, p_camera, p_event); } return false; @@ -903,7 +903,7 @@ void EditorPlugin::_bind_methods() { ClassDB::add_virtual_method(get_class_static(), MethodInfo(Variant::BOOL, "forward_canvas_gui_input", PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent"))); ClassDB::add_virtual_method(get_class_static(), MethodInfo("forward_canvas_draw_over_viewport", PropertyInfo(Variant::OBJECT, "overlay", PROPERTY_HINT_RESOURCE_TYPE, "Control"))); ClassDB::add_virtual_method(get_class_static(), MethodInfo("forward_canvas_force_draw_over_viewport", PropertyInfo(Variant::OBJECT, "overlay", PROPERTY_HINT_RESOURCE_TYPE, "Control"))); - ClassDB::add_virtual_method(get_class_static(), MethodInfo(Variant::BOOL, "forward_spatial_gui_input", PropertyInfo(Variant::OBJECT, "camera", PROPERTY_HINT_RESOURCE_TYPE, "Camera"), PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent"))); + ClassDB::add_virtual_method(get_class_static(), MethodInfo(Variant::BOOL, "forward_spatial_gui_input", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::OBJECT, "camera", PROPERTY_HINT_RESOURCE_TYPE, "Camera"), PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent"))); ClassDB::add_virtual_method(get_class_static(), MethodInfo("forward_spatial_draw_over_viewport", PropertyInfo(Variant::OBJECT, "overlay", PROPERTY_HINT_RESOURCE_TYPE, "Control"))); ClassDB::add_virtual_method(get_class_static(), MethodInfo("forward_spatial_force_draw_over_viewport", PropertyInfo(Variant::OBJECT, "overlay", PROPERTY_HINT_RESOURCE_TYPE, "Control"))); ClassDB::add_virtual_method(get_class_static(), MethodInfo(Variant::STRING, "get_plugin_name")); diff --git a/editor/editor_plugin.h b/editor/editor_plugin.h index 21293cbbfc..af6732b867 100644 --- a/editor/editor_plugin.h +++ b/editor/editor_plugin.h @@ -193,7 +193,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(Camera *p_camera, const Ref &p_event); + virtual bool forward_spatial_gui_input(int p_index, Camera *p_camera, const Ref &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 2864ed652a..7b164c6223 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -213,6 +213,7 @@ void editor_register_and_generate_icons(Ref p_theme, bool p_dark_theme = exceptions.insert("EditorPivot"); exceptions.insert("EditorHandle"); exceptions.insert("Editor3DHandle"); + exceptions.insert("EditorBoneHandle"); exceptions.insert("Godot"); exceptions.insert("PanoramaSky"); exceptions.insert("ProceduralSky"); diff --git a/editor/icons/icon_editor_bone_handle.svg b/editor/icons/icon_editor_bone_handle.svg new file mode 100644 index 0000000000..7658b90f7e --- /dev/null +++ b/editor/icons/icon_editor_bone_handle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/editor/icons/icon_tool_bone_move.svg b/editor/icons/icon_tool_bone_move.svg new file mode 100644 index 0000000000..b9b15eee3f --- /dev/null +++ b/editor/icons/icon_tool_bone_move.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/editor/icons/icon_tool_bone_rest.svg b/editor/icons/icon_tool_bone_rest.svg new file mode 100644 index 0000000000..ab4f915e3d --- /dev/null +++ b/editor/icons/icon_tool_bone_rest.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/editor/icons/icon_tool_bone_rotate.svg b/editor/icons/icon_tool_bone_rotate.svg new file mode 100644 index 0000000000..1c81687245 --- /dev/null +++ b/editor/icons/icon_tool_bone_rotate.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/editor/icons/icon_tool_bone_scale.svg b/editor/icons/icon_tool_bone_scale.svg new file mode 100644 index 0000000000..b1facc4049 --- /dev/null +++ b/editor/icons/icon_tool_bone_scale.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/editor/icons/icon_tool_bone_select.svg b/editor/icons/icon_tool_bone_select.svg new file mode 100644 index 0000000000..73e79a191d --- /dev/null +++ b/editor/icons/icon_tool_bone_select.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/editor/plugins/collision_polygon_editor_plugin.cpp b/editor/plugins/collision_polygon_editor_plugin.cpp index 9cd7a60946..b207590996 100644 --- a/editor/plugins/collision_polygon_editor_plugin.cpp +++ b/editor/plugins/collision_polygon_editor_plugin.cpp @@ -109,7 +109,7 @@ void Polygon3DEditor::_wip_close() { undo_redo->commit_action(); } -bool Polygon3DEditor::forward_spatial_gui_input(Camera *p_camera, const Ref &p_event) { +bool Polygon3DEditor::forward_spatial_gui_input(int p_index, Camera *p_camera, const Ref &p_event) { if (!node) return false; diff --git a/editor/plugins/collision_polygon_editor_plugin.h b/editor/plugins/collision_polygon_editor_plugin.h index 26e0a22713..ed2bf8cee6 100644 --- a/editor/plugins/collision_polygon_editor_plugin.h +++ b/editor/plugins/collision_polygon_editor_plugin.h @@ -90,7 +90,7 @@ protected: static void _bind_methods(); public: - virtual bool forward_spatial_gui_input(Camera *p_camera, const Ref &p_event); + virtual bool forward_spatial_gui_input(int p_index, Camera *p_camera, const Ref &p_event); void edit(Node *p_collision_polygon); Polygon3DEditor(EditorNode *p_editor); ~Polygon3DEditor(); @@ -104,7 +104,7 @@ class Polygon3DEditorPlugin : public EditorPlugin { EditorNode *editor; public: - virtual bool forward_spatial_gui_input(Camera *p_camera, const Ref &p_event) { return collision_polygon_editor->forward_spatial_gui_input(p_camera, p_event); } + virtual bool forward_spatial_gui_input(int p_index, Camera *p_camera, const Ref &p_event) { return collision_polygon_editor->forward_spatial_gui_input(p_index, p_camera, p_event); } virtual String get_name() const { return "Polygon3DEditor"; } bool has_main_screen() const { return false; } diff --git a/editor/plugins/path_editor_plugin.cpp b/editor/plugins/path_editor_plugin.cpp index 35eef6102e..e5221fc17e 100644 --- a/editor/plugins/path_editor_plugin.cpp +++ b/editor/plugins/path_editor_plugin.cpp @@ -292,7 +292,7 @@ PathSpatialGizmo::PathSpatialGizmo(Path *p_path) { set_spatial_node(p_path); } -bool PathEditorPlugin::forward_spatial_gui_input(Camera *p_camera, const Ref &p_event) { +bool PathEditorPlugin::forward_spatial_gui_input(int p_index, Camera *p_camera, const Ref &p_event) { if (!path) return false; diff --git a/editor/plugins/path_editor_plugin.h b/editor/plugins/path_editor_plugin.h index ea908b654a..f9e736ea56 100644 --- a/editor/plugins/path_editor_plugin.h +++ b/editor/plugins/path_editor_plugin.h @@ -101,7 +101,7 @@ public: Path *get_edited_path() { return path; } static PathEditorPlugin *singleton; - virtual bool forward_spatial_gui_input(Camera *p_camera, const Ref &p_event); + virtual bool forward_spatial_gui_input(int p_index, Camera *p_camera, const Ref &p_event); //virtual bool forward_gui_input(const InputEvent& p_event) { return collision_polygon_editor->forward_gui_input(p_event); } //virtual Ref create_spatial_gizmo(Spatial *p_spatial); diff --git a/editor/plugins/skeleton_editor_plugin.cpp b/editor/plugins/skeleton_editor_plugin.cpp index 92d396b903..5cd989efce 100644 --- a/editor/plugins/skeleton_editor_plugin.cpp +++ b/editor/plugins/skeleton_editor_plugin.cpp @@ -30,27 +30,523 @@ #include "skeleton_editor_plugin.h" +#include "core/io/resource_saver.h" +#include "editor/editor_file_dialog.h" +#include "editor/editor_properties.h" +#include "editor/editor_scale.h" +#include "editor/plugins/animation_player_editor_plugin.h" #include "scene/3d/collision_shape.h" +#include "scene/3d/mesh_instance.h" #include "scene/3d/physics_body.h" #include "scene/3d/physics_joint.h" #include "scene/resources/capsule_shape.h" #include "scene/resources/sphere_shape.h" #include "spatial_editor_plugin.h" +#define DISTANCE_DEFAULT 4 + +#define GIZMO_ARROW_SIZE 0.35 +#define GIZMO_RING_HALF_WIDTH 0.1 +#define GIZMO_SCALE_DEFAULT 0.15 +#define GIZMO_PLANE_SIZE 0.2 +#define GIZMO_PLANE_DST 0.3 +#define GIZMO_CIRCLE_SIZE 1.1 +#define GIZMO_SCALE_OFFSET (GIZMO_CIRCLE_SIZE + 0.3) +#define GIZMO_ARROW_OFFSET (GIZMO_CIRCLE_SIZE + 0.3) + +#define ZOOM_MIN_DISTANCE 0.001 +#define ZOOM_MULTIPLIER 1.08 +#define ZOOM_INDICATOR_DELAY_S 1.5 + +#define FREELOOK_MIN_SPEED 0.01 +#define FREELOOK_SPEED_MULTIPLIER 1.08 + +#define MIN_Z 0.01 +#define MAX_Z 1000000.0 + +#define MIN_FOV 0.01 +#define MAX_FOV 179 + +void BoneTransformEditor::create_editors() { + const Color section_color = get_color("prop_subsection", "Editor"); + + section = memnew(EditorInspectorSection); + section->setup("trf_properties", label, this, section_color, true); + add_child(section); + + key_button = memnew(Button); + key_button->set_text(TTR("Key Transform")); + key_button->set_visible(keyable); + key_button->set_icon(get_icon("Key", "EditorIcons")); + 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); + + Label *l1 = memnew(Label(TTR("Translation"))); + section->get_vbox()->add_child(l1); + + translation_grid = memnew(GridContainer()); + translation_grid->set_columns(TRANSLATION_COMPONENTS); + section->get_vbox()->add_child(translation_grid); + + Label *l2 = memnew(Label(TTR("Rotation Degrees"))); + section->get_vbox()->add_child(l2); + + rotation_grid = memnew(GridContainer()); + rotation_grid->set_columns(ROTATION_DEGREES_COMPONENTS); + section->get_vbox()->add_child(rotation_grid); + + Label *l3 = memnew(Label(TTR("Scale"))); + section->get_vbox()->add_child(l3); + + scale_grid = memnew(GridContainer()); + scale_grid->set_columns(SCALE_COMPONENTS); + section->get_vbox()->add_child(scale_grid); + + Label *l4 = memnew(Label(TTR("Transform"))); + section->get_vbox()->add_child(l4); + + transform_grid = memnew(GridContainer()); + transform_grid->set_columns(TRANSFORM_CONTROL_COMPONENTS); + section->get_vbox()->add_child(transform_grid); + + static const char *desc[TRANSFORM_COMPONENTS] = { "x", "y", "z", "x", "y", "z", "x", "y", "z", "x", "y", "z" }; + float snap = EDITOR_GET("interface/inspector/default_float_step"); + + for (int i = 0; i < TRANSFORM_CONTROL_COMPONENTS; ++i) { + translation_slider[i] = memnew(EditorSpinSlider()); + translation_slider[i]->set_label(desc[i]); + translation_slider[i]->set_step(snap); + setup_spinner(translation_slider[i], false); + translation_grid->add_child(translation_slider[i]); + + rotation_slider[i] = memnew(EditorSpinSlider()); + rotation_slider[i]->set_label(desc[i]); + rotation_slider[i]->set_step(snap); + setup_spinner(rotation_slider[i], false); + rotation_grid->add_child(rotation_slider[i]); + + scale_slider[i] = memnew(EditorSpinSlider()); + scale_slider[i]->set_label(desc[i]); + scale_slider[i]->set_step(snap); + setup_spinner(scale_slider[i], false); + scale_grid->add_child(scale_slider[i]); + } + + for (int i = 0; i < TRANSFORM_COMPONENTS; ++i) { + transform_slider[i] = memnew(EditorSpinSlider()); + transform_slider[i]->set_label(desc[i]); + transform_slider[i]->set_step(snap); + setup_spinner(transform_slider[i], true); + transform_grid->add_child(transform_slider[i]); + } +} + +void BoneTransformEditor::setup_spinner(EditorSpinSlider *spinner, const bool is_transform_spinner) { + spinner->set_flat(true); + spinner->set_min(-10000); + spinner->set_max(10000); + spinner->set_hide_slider(true); + spinner->set_allow_greater(true); + spinner->set_allow_lesser(true); + spinner->set_h_size_flags(SIZE_EXPAND_FILL); + + spinner->connect("value_changed", this, "_value_changed", varray(is_transform_spinner)); +} + +void BoneTransformEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + create_editors(); + key_button->connect("pressed", this, "_key_button_pressed"); + enabled_checkbox->connect("toggled", this, "_checkbox_toggled"); + FALLTHROUGH; + } + case NOTIFICATION_THEME_CHANGED: { + const Color base = get_color("accent_color", "Editor"); + const Color bg_color = get_color("property_color", "Editor"); + const Color bg_lbl_color(bg_color.r, bg_color.g, bg_color.b, 0.5); + + for (int i = 0; i < TRANSLATION_COMPONENTS; i++) { + Color c = base; + c.set_hsv(float(i % TRANSLATION_COMPONENTS) / TRANSLATION_COMPONENTS + 0.05, c.get_s() * 0.75, c.get_v()); + if (!translation_slider[i]) { + continue; + } + translation_slider[i]->set_custom_label_color(true, c); + } + + for (int i = 0; i < ROTATION_DEGREES_COMPONENTS; i++) { + Color c = base; + c.set_hsv(float(i % ROTATION_DEGREES_COMPONENTS) / ROTATION_DEGREES_COMPONENTS + 0.05, c.get_s() * 0.75, c.get_v()); + if (!rotation_slider[i]) { + continue; + } + rotation_slider[i]->set_custom_label_color(true, c); + } + + for (int i = 0; i < SCALE_COMPONENTS; i++) { + Color c = base; + c.set_hsv(float(i % SCALE_COMPONENTS) / SCALE_COMPONENTS + 0.05, c.get_s() * 0.75, c.get_v()); + if (!scale_slider[i]) { + continue; + } + scale_slider[i]->set_custom_label_color(true, c); + } + + for (int i = 0; i < TRANSFORM_COMPONENTS; i++) { + Color c = base; + c.set_hsv(float(i % TRANSFORM_COMPONENTS) / TRANSFORM_COMPONENTS + 0.05, c.get_s() * 0.75, c.get_v()); + if (!transform_slider[i]) { + continue; + } + transform_slider[i]->set_custom_label_color(true, c); + } + + break; + } + case NOTIFICATION_SORT_CHILDREN: { + const Ref font = get_font("font", "Tree"); + + Point2 buffer; + buffer.x += get_constant("inspector_margin", "Editor"); + buffer.y += font->get_height(); + buffer.y += get_constant("vseparation", "Tree"); + + const float vector_height = translation_grid->get_size().y; + const float transform_height = transform_grid->get_size().y; + const float button_height = key_button->get_size().y; + + const float width = get_size().x - get_constant("inspector_margin", "Editor"); + Vector input_rects; + if (keyable && section->get_vbox()->is_visible()) { + input_rects.push_back(Rect2(key_button->get_position() + buffer, Size2(width, button_height))); + } else { + input_rects.push_back(Rect2(0, 0, 0, 0)); + } + + if (section->get_vbox()->is_visible()) { + input_rects.push_back(Rect2(translation_grid->get_position() + buffer, Size2(width, vector_height))); + input_rects.push_back(Rect2(rotation_grid->get_position() + buffer, Size2(width, vector_height))); + input_rects.push_back(Rect2(scale_grid->get_position() + buffer, Size2(width, vector_height))); + input_rects.push_back(Rect2(transform_grid->get_position() + buffer, Size2(width, transform_height))); + } else { + const int32_t start = input_rects.size(); + const int32_t empty_input_rect_elements = 4; + const int32_t end = start + empty_input_rect_elements; + for (int i = start; i < end; ++i) { + input_rects.push_back(Rect2(0, 0, 0, 0)); + } + } + + for (int32_t i = 0; i < input_rects.size(); i++) { + background_rects[i] = input_rects[i]; + } + + update(); + break; + } + case NOTIFICATION_DRAW: { + const Color dark_color = get_color("dark_color_2", "Editor"); + + for (int i = 0; i < 5; ++i) { + draw_rect(background_rects[i], dark_color); + } + + break; + } + } +} + +void BoneTransformEditor::_value_changed(const double p_value, const bool p_from_transform) { + if (updating) + return; + + if (property.get_slicec('/', 0) == "bones" && property.get_slicec('/', 2) == "custom_pose") { + const Transform tform = compute_transform(p_from_transform); + + undo_redo->create_action(TTR("Set Custom Bone Pose Transform"), UndoRedo::MERGE_ENDS); + undo_redo->add_undo_method(skeleton, "set_bone_custom_pose", property.get_slicec('/', 1).to_int(), skeleton->get_bone_custom_pose(property.get_slicec('/', 1).to_int())); + undo_redo->add_do_method(skeleton, "set_bone_custom_pose", property.get_slicec('/', 1).to_int(), tform); + undo_redo->commit_action(); + } else if (property.get_slicec('/', 0) == "bones") { + const Transform tform = compute_transform(p_from_transform); + + undo_redo->create_action(TTR("Set Bone Transform"), UndoRedo::MERGE_ENDS); + undo_redo->add_undo_property(skeleton, property, skeleton->get(property)); + undo_redo->add_do_property(skeleton, property, tform); + undo_redo->commit_action(); + } +} + +Transform BoneTransformEditor::compute_transform(const bool p_from_transform) const { + + // Last modified was a raw transform column... + if (p_from_transform) { + Transform tform; + + for (int i = 0; i < BASIS_COMPONENTS; ++i) { + tform.basis[i / BASIS_SPLIT_COMPONENTS][i % BASIS_SPLIT_COMPONENTS] = transform_slider[i]->get_value(); + } + + for (int i = 0; i < TRANSLATION_COMPONENTS; ++i) { + tform.origin[i] = transform_slider[i + BASIS_COMPONENTS]->get_value(); + } + + return tform; + } + + return Transform( + Basis(Vector3(Math::deg2rad(rotation_slider[0]->get_value()), Math::deg2rad(rotation_slider[1]->get_value()), Math::deg2rad(rotation_slider[2]->get_value())), + Vector3(scale_slider[0]->get_value(), scale_slider[1]->get_value(), scale_slider[2]->get_value())), + Vector3(translation_slider[0]->get_value(), translation_slider[1]->get_value(), translation_slider[2]->get_value())); +} + +void BoneTransformEditor::update_enabled_checkbox() { + if (enabled_checkbox) { + const String path = "bones/" + property.get_slicec('/', 1) + "/enabled"; + const bool is_enabled = skeleton->get(path); + enabled_checkbox->set_pressed(is_enabled); + } +} + +void BoneTransformEditor::_bind_methods() { + ClassDB::bind_method(D_METHOD("_value_changed", "value"), &BoneTransformEditor::_value_changed); + ClassDB::bind_method(D_METHOD("_key_button_pressed"), &BoneTransformEditor::_key_button_pressed); + ClassDB::bind_method(D_METHOD("_checkbox_toggled", "toggled"), &BoneTransformEditor::_checkbox_toggled); +} + +void BoneTransformEditor::_update_properties() { + if (updating) + return; + + if (skeleton == nullptr) + return; + + updating = true; + + Transform tform = skeleton->get(property); + _update_transform_properties(tform); +} + +void BoneTransformEditor::_update_custom_pose_properties() { + if (updating) + return; + + if (skeleton == nullptr) + return; + + updating = true; + + Transform tform = skeleton->get_bone_custom_pose(property.to_int()); + _update_transform_properties(tform); +} + +void BoneTransformEditor::_update_transform_properties(Transform tform) { + + Quat rot = tform.get_basis().orthonormalized(); + Vector3 rot_rad = rot.get_euler(); + Vector3 rot_degrees = Vector3(Math::rad2deg(rot_rad.x), Math::rad2deg(rot_rad.y), Math::rad2deg(rot_rad.z)); + Vector3 tr = tform.get_origin(); + Vector3 scale = tform.basis.get_scale(); + + for (int i = 0; i < TRANSLATION_COMPONENTS; i++) { + translation_slider[i]->set_value(tr[i]); + } + + for (int i = 0; i < ROTATION_DEGREES_COMPONENTS; i++) { + rotation_slider[i]->set_value(rot_degrees[i]); + } + + for (int i = 0; i < SCALE_COMPONENTS; i++) { + scale_slider[i]->set_value(scale[i]); + } + + transform_slider[0]->set_value(tform.get_basis()[Vector3::AXIS_X].x); + transform_slider[1]->set_value(tform.get_basis()[Vector3::AXIS_X].y); + transform_slider[2]->set_value(tform.get_basis()[Vector3::AXIS_X].z); + transform_slider[3]->set_value(tform.get_basis()[Vector3::AXIS_Y].x); + transform_slider[4]->set_value(tform.get_basis()[Vector3::AXIS_Y].y); + transform_slider[5]->set_value(tform.get_basis()[Vector3::AXIS_Y].z); + transform_slider[6]->set_value(tform.get_basis()[Vector3::AXIS_Z].x); + transform_slider[7]->set_value(tform.get_basis()[Vector3::AXIS_Z].y); + transform_slider[8]->set_value(tform.get_basis()[Vector3::AXIS_Z].z); + + for (int i = 0; i < TRANSLATION_COMPONENTS; i++) { + transform_slider[BASIS_COMPONENTS + i]->set_value(tform.get_origin()[i]); + } + + update_enabled_checkbox(); + updating = false; +} + +BoneTransformEditor::BoneTransformEditor(Skeleton *p_skeleton) : + translation_slider(), + rotation_slider(), + scale_slider(), + transform_slider(), + skeleton(p_skeleton), + key_button(nullptr), + enabled_checkbox(nullptr), + keyable(false), + toggle_enabled(false), + updating(false) { + + undo_redo = EditorNode::get_undo_redo(); +} + +void BoneTransformEditor::set_target(const String &p_prop) { + property = p_prop; +} + +void BoneTransformEditor::set_keyable(const bool p_keyable) { + keyable = p_keyable; + if (key_button) { + key_button->set_visible(p_keyable); + } +} + +void BoneTransformEditor::set_toggle_enabled(const bool p_enabled) { + toggle_enabled = p_enabled; + if (enabled_checkbox) { + enabled_checkbox->set_visible(p_enabled); + } +} + +void BoneTransformEditor::_key_button_pressed() { + if (skeleton == nullptr) + return; + + const BoneId bone_id = property.get_slicec('/', 1).to_int(); + const String name = skeleton->get_bone_name(bone_id); + + if (name.empty()) + return; + + // Need to normalize the basis before you key it + Transform tform = compute_transform(true); + tform.orthonormalize(); + AnimationPlayerEditor::singleton->get_track_editor()->insert_transform_key(skeleton, name, tform); +} + +void BoneTransformEditor::_checkbox_toggled(const bool p_toggled) { + if (enabled_checkbox) { + const String path = "bones/" + property.get_slicec('/', 1) + "/enabled"; + skeleton->set(path, p_toggled); + } +} + +void BoneTransformEditor::set_read_only(const bool p_read_only) { + for (int i = 0; i < TRANSLATION_COMPONENTS; i++) { + translation_slider[i]->set_read_only(p_read_only); + } + for (int i = 0; i < ROTATION_DEGREES_COMPONENTS; i++) { + rotation_slider[i]->set_read_only(p_read_only); + } + for (int i = 0; i < SCALE_COMPONENTS; i++) { + scale_slider[i]->set_read_only(p_read_only); + } + for (int i = 0; i < TRANSFORM_COMPONENTS; i++) { + transform_slider[i]->set_read_only(p_read_only); + } +} + + +void SkeletonEditor::set_keyable(const bool p_keyable) { + keyable = p_keyable; + options->get_popup()->set_item_disabled(MENU_OPTION_INSERT_KEYS, !p_keyable); + options->get_popup()->set_item_disabled(MENU_OPTION_INSERT_KEYS_EXISTED, !p_keyable); +}; + void SkeletonEditor::_on_click_option(int p_option) { if (!skeleton) { return; } switch (p_option) { + case MENU_OPTION_INIT_POSE: { + init_pose(); + } break; + case MENU_OPTION_INSERT_KEYS: { + insert_keys(true); + } break; + case MENU_OPTION_INSERT_KEYS_EXISTED: { + insert_keys(false); + } break; + case MENU_OPTION_POSE_TO_REST: { + pose_to_rest(); + } break; case MENU_OPTION_CREATE_PHYSICAL_SKELETON: { create_physical_skeleton(); } break; } } +void SkeletonEditor::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, Transform()); + ur->add_undo_method(skeleton, "set_bone_pose", i, skeleton->get_bone_pose(i)); + } + ur->commit_action(); +} + +void SkeletonEditor::insert_keys(bool p_all_bones) { + if (skeleton == nullptr) + return; + + int bone_len = skeleton->get_bone_count(); + Node *root = EditorNode::get_singleton()->get_tree()->get_root(); + String path = root->get_path_to(skeleton); + + for (int i = 0; i < bone_len; i++) { + const String name = skeleton->get_bone_name(i); + + if (name.empty()) + continue; + + if (!p_all_bones && !AnimationPlayerEditor::singleton->get_track_editor()->has_transform_key(skeleton, name)) { + continue; + } + + // Need to normalize the basis before you key it + Transform tform = skeleton->get_bone_pose(i); + tform.orthonormalize(); + AnimationPlayerEditor::singleton->get_track_editor()->insert_transform_key(skeleton, name, tform); + } + +} + +void SkeletonEditor::pose_to_rest() { + 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, Transform()); + ur->add_undo_method(skeleton, "set_bone_pose", i, skeleton->get_bone_pose(i)); + ur->add_do_method(skeleton, "set_bone_custom_pose", i, Transform()); + ur->add_undo_method(skeleton, "set_bone_custom_pose", i, skeleton->get_bone_custom_pose(i)); + ur->add_do_method(skeleton, "set_bone_rest", i, skeleton->get_bone_rest(i) * skeleton->get_bone_custom_pose(i) * skeleton->get_bone_pose(i)); + ur->add_undo_method(skeleton, "set_bone_rest", i, skeleton->get_bone_rest(i)); + } + ur->commit_action(); +} + void SkeletonEditor::create_physical_skeleton() { UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + ERR_FAIL_COND(!get_tree()); Node *owner = skeleton == get_tree()->get_edited_scene_root() ? skeleton : skeleton->get_owner(); const int bc = skeleton->get_bone_count(); @@ -131,68 +627,1213 @@ PhysicalBone *SkeletonEditor::create_physical_bone(int bone_id, int bone_child_i return physical_bone; } -void SkeletonEditor::edit(Skeleton *p_node) { +Variant SkeletonEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) { + TreeItem *selected = joint_tree->get_selected(); + + if (!selected) + return Variant(); + + Ref icon = selected->get_icon(0); + + VBoxContainer *vb = memnew(VBoxContainer); + HBoxContainer *hb = memnew(HBoxContainer); + TextureRect *tf = memnew(TextureRect); + tf->set_texture(icon); + tf->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED); + hb->add_child(tf); + Label *label = memnew(Label(selected->get_text(0))); + hb->add_child(label); + vb->add_child(hb); + hb->set_modulate(Color(1, 1, 1, 1)); + + set_drag_preview(vb); + Dictionary drag_data; + drag_data["type"] = "nodes"; + drag_data["node"] = selected; + + return drag_data; +} + +bool SkeletonEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const { + + TreeItem *target = joint_tree->get_item_at_position(p_point); + if (!target) + return false; + + const String path = target->get_metadata(0); + if (!path.begins_with("bones/")) + return false; - skeleton = p_node; + TreeItem *selected = Object::cast_to(Dictionary(p_data)["node"]); + if (target == selected) + return false; + + const String path2 = target->get_metadata(0); + if (!path2.begins_with("bones/")) + return false; + + return true; } -void SkeletonEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_TREE) { - get_tree()->connect("node_removed", this, "_node_removed"); +void SkeletonEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) { + if (!can_drop_data_fw(p_point, p_data, p_from)) + return; + + TreeItem *target = joint_tree->get_item_at_position(p_point); + TreeItem *selected = Object::cast_to(Dictionary(p_data)["node"]); + + const BoneId target_boneidx = String(target->get_metadata(0)).get_slicec('/', 1).to_int(); + const BoneId selected_boneidx = String(selected->get_metadata(0)).get_slicec('/', 1).to_int(); + + move_skeleton_bone(skeleton->get_path(), selected_boneidx, target_boneidx); +} + +void SkeletonEditor::move_skeleton_bone(NodePath p_skeleton_path, int32_t p_selected_boneidx, int32_t p_target_boneidx) { + Node *node = get_node_or_null(p_skeleton_path); + Skeleton *skeleton = Object::cast_to(node); + 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 (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(); + for (BoneId i = 0; i < bone_count; ++i) { + if (skeleton->get_bone_parent(i) == p_selected_boneidx) { + ur->add_undo_method(skeleton, "set_bone_parent", i, skeleton->get_bone_parent(i)); + ur->add_do_method(skeleton, "set_bone_parent", i, parent_idx); + skeleton->set_bone_parent(i, parent_idx); + } + } } + ur->add_undo_method(skeleton, "set_bone_parent", p_selected_boneidx, skeleton->get_bone_parent(p_selected_boneidx)); + ur->add_do_method(skeleton, "set_bone_parent", p_selected_boneidx, p_target_boneidx); + skeleton->set_bone_parent(p_selected_boneidx, p_target_boneidx); + + update_joint_tree(); + ur->commit_action(); } -void SkeletonEditor::_node_removed(Node *p_node) { +void SkeletonEditor::_update_spatial_transform_gizmo() { + SpatialEditor::get_singleton()->clear_externals(); + if (skeleton->get_selected_bone() >= 0) { + SpatialEditor::get_singleton()->append_to_externals(skeleton->get_global_transform() * skeleton->get_bone_global_pose(skeleton->get_selected_bone())); + } + SpatialEditor::get_singleton()->update_transform_gizmo(); +}; - if (p_node == skeleton) { - skeleton = NULL; - options->hide(); +void SkeletonEditor::_joint_tree_selection_changed() { + TreeItem *selected = joint_tree->get_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) + "/"; + + 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_visible(true); + rest_editor->set_visible(true); + custom_pose_editor->set_visible(true); + + skeleton->set_selected_bone(b_idx); } + + _update_properties(); } -void SkeletonEditor::_bind_methods() { - ClassDB::bind_method("_on_click_option", &SkeletonEditor::_on_click_option); - ClassDB::bind_method("_node_removed", &SkeletonEditor::_node_removed); +void SkeletonEditor::_joint_tree_rmb_select(const Vector2 &p_pos) { + skeleton->set_selected_bone(-1); + _update_spatial_transform_gizmo(); +} + +void SkeletonEditor::_update_properties() { + if (rest_editor) + rest_editor->_update_properties(); + if (pose_editor) + pose_editor->_update_properties(); + if (custom_pose_editor) + custom_pose_editor->_update_custom_pose_properties(); + _update_spatial_transform_gizmo(); +} + +void SkeletonEditor::update_joint_tree() { + joint_tree->clear(); + + if (skeleton == nullptr) + return; + + TreeItem *root = joint_tree->create_item(); + + Map items; + + items.insert(-1, root); + + const Vector &joint_porder = skeleton->get_bone_process_order(); + + Ref bone_icon = get_icon("Bone", "EditorIcons"); + + for (int i = 0; i < joint_porder.size(); ++i) { + const int b_idx = joint_porder[i]; + + const int p_idx = skeleton->get_bone_parent(b_idx); + TreeItem *p_item = items.find(p_idx)->get(); + + TreeItem *joint_item = joint_tree->create_item(p_item); + items.insert(b_idx, joint_item); + + joint_item->set_text(0, skeleton->get_bone_name(b_idx)); + joint_item->set_icon(0, bone_icon); + joint_item->set_selectable(0, true); + joint_item->set_metadata(0, "bones/" + itos(b_idx)); + } } -SkeletonEditor::SkeletonEditor() { - skeleton = NULL; +void SkeletonEditor::update_editors() { +} + +void SkeletonEditor::create_editors() { + + set_h_size_flags(SIZE_EXPAND_FILL); + add_constant_override("separation", 0); + + set_focus_mode(FOCUS_ALL); + + // Create Top Menu Bar + separators[0] = memnew(VSeparator); + separators[1] = memnew(VSeparator); + + SpatialEditor::get_singleton()->add_control_to_menu_panel(separators[0]); + options = memnew(MenuButton); SpatialEditor::get_singleton()->add_control_to_menu_panel(options); - options->set_text(TTR("Skeleton")); options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_icon("Skeleton", "EditorIcons")); - + options->get_popup()->add_item(TTR("Init pose"), MENU_OPTION_INIT_POSE); + options->get_popup()->add_item(TTR("Insert key of all bone poses"), MENU_OPTION_INSERT_KEYS); + options->get_popup()->add_item(TTR("Insert key of bone poses already exist track"), MENU_OPTION_INSERT_KEYS_EXISTED); + options->get_popup()->add_item(TTR("Apply current pose to rest"), MENU_OPTION_POSE_TO_REST); options->get_popup()->add_item(TTR("Create physical skeleton"), MENU_OPTION_CREATE_PHYSICAL_SKELETON); - options->get_popup()->connect("id_pressed", this, "_on_click_option"); - options->hide(); + + Vector button_binds; + button_binds.resize(1); + + tool_button[TOOL_MODE_BONE_SELECT] = memnew(ToolButton); + SpatialEditor::get_singleton()->add_control_to_menu_panel(tool_button[TOOL_MODE_BONE_SELECT]); + tool_button[TOOL_MODE_BONE_SELECT]->set_tooltip(TTR("Transform Bone Mode")); + tool_button[TOOL_MODE_BONE_SELECT]->set_toggle_mode(true); + tool_button[TOOL_MODE_BONE_SELECT]->set_flat(true); + button_binds.write[0] = MENU_TOOL_BONE_SELECT; + tool_button[TOOL_MODE_BONE_SELECT]->connect("pressed", this, "_menu_tool_item_pressed", button_binds); + + tool_button[TOOL_MODE_BONE_MOVE] = memnew(ToolButton); + SpatialEditor::get_singleton()->add_control_to_menu_panel(tool_button[TOOL_MODE_BONE_MOVE]); + tool_button[TOOL_MODE_BONE_MOVE]->set_tooltip(TTR("Move Bone Mode")); + tool_button[TOOL_MODE_BONE_MOVE]->set_toggle_mode(true); + tool_button[TOOL_MODE_BONE_MOVE]->set_flat(true); + button_binds.write[0] = MENU_TOOL_BONE_MOVE; + tool_button[TOOL_MODE_BONE_MOVE]->connect("pressed", this, "_menu_tool_item_pressed", button_binds); + + tool_button[TOOL_MODE_BONE_ROTATE] = memnew(ToolButton); + SpatialEditor::get_singleton()->add_control_to_menu_panel(tool_button[TOOL_MODE_BONE_ROTATE]); + tool_button[TOOL_MODE_BONE_ROTATE]->set_tooltip(TTR("Rotate Bone Mode")); + tool_button[TOOL_MODE_BONE_ROTATE]->set_toggle_mode(true); + tool_button[TOOL_MODE_BONE_ROTATE]->set_flat(true); + button_binds.write[0] = MENU_TOOL_BONE_ROTATE; + tool_button[TOOL_MODE_BONE_ROTATE]->connect("pressed", this, "_menu_tool_item_pressed", button_binds); + + tool_button[TOOL_MODE_BONE_SCALE] = memnew(ToolButton); + SpatialEditor::get_singleton()->add_control_to_menu_panel(tool_button[TOOL_MODE_BONE_SCALE]); + tool_button[TOOL_MODE_BONE_SCALE]->set_tooltip(TTR("Scale Bone Mode")); + tool_button[TOOL_MODE_BONE_SCALE]->set_toggle_mode(true); + tool_button[TOOL_MODE_BONE_SCALE]->set_flat(true); + button_binds.write[0] = MENU_TOOL_BONE_SCALE; + tool_button[TOOL_MODE_BONE_SCALE]->connect("pressed", this, "_menu_tool_item_pressed", button_binds); + + tool_button[TOOL_MODE_BONE_NONE] = memnew(ToolButton); + button_binds.write[0] = MENU_TOOL_BONE_NONE; + tool_button[TOOL_MODE_BONE_NONE]->connect("pressed", this, "_menu_tool_item_pressed", button_binds); + SpatialEditor::get_singleton()->connect("change_tool_mode", this, "_menu_tool_item_pressed", button_binds); + + tool_mode = TOOL_MODE_BONE_NONE; + + SpatialEditor::get_singleton()->add_control_to_menu_panel(separators[1]); + + rest_mode_button = memnew(ToolButton); + SpatialEditor::get_singleton()->add_control_to_menu_panel(rest_mode_button); + rest_mode_button->set_tooltip(TTR("Rest Mode\nNote: Bone poses are disabled during Rest Mode.")); + rest_mode_button->set_toggle_mode(true); + rest_mode_button->set_flat(true); + rest_mode_button->connect("toggled", this, "rest_mode_toggled"); + + rest_mode = false; + + set_keyable(AnimationPlayerEditor::singleton->get_track_editor()->has_keying()); + + if (skeleton) { + skeleton->add_child(pointsm); + pointsm->set_skeleton_path(NodePath("")); + skeleton->connect("pose_updated", this, "_draw_handles"); + } + + const Color section_color = get_color("prop_subsection", "Editor"); + + EditorInspectorSection *bones_section = memnew(EditorInspectorSection); + bones_section->setup("bones", "Bones", skeleton, section_color, true); + add_child(bones_section); + bones_section->unfold(); + + ScrollContainer *s_con = memnew(ScrollContainer); + s_con->set_h_size_flags(SIZE_EXPAND_FILL); + s_con->set_custom_minimum_size(Size2(1, 350) * EDSCALE); + bones_section->get_vbox()->add_child(s_con); + + joint_tree = memnew(Tree); + joint_tree->set_columns(1); + joint_tree->set_focus_mode(Control::FocusMode::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); + joint_tree->set_h_size_flags(SIZE_EXPAND_FILL); + joint_tree->set_allow_rmb_select(true); + joint_tree->set_drag_forwarding(this); + s_con->add_child(joint_tree); + + 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_visible(false); + add_child(pose_editor); + + rest_editor = memnew(BoneTransformEditor(skeleton)); + rest_editor->set_label(TTR("Bone Rest")); + rest_editor->set_visible(false); + add_child(rest_editor); + + 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); + + skeleton->set_selected_bone(-1); } -SkeletonEditor::~SkeletonEditor() {} +void SkeletonEditor::_notification(int p_what) { + + switch (p_what) { + case NOTIFICATION_READY: { + tool_button[TOOL_MODE_BONE_SELECT]->set_icon(get_icon("ToolBoneSelect", "EditorIcons")); + tool_button[TOOL_MODE_BONE_MOVE]->set_icon(get_icon("ToolBoneMove", "EditorIcons")); + tool_button[TOOL_MODE_BONE_ROTATE]->set_icon(get_icon("ToolBoneRotate", "EditorIcons")); + tool_button[TOOL_MODE_BONE_SCALE]->set_icon(get_icon("ToolBoneScale", "EditorIcons")); + rest_mode_button->set_icon(get_icon("ToolBoneRest", "EditorIcons")); + } break; + case NOTIFICATION_ENTER_TREE: { + create_editors(); + update_joint_tree(); + update_editors(); + + get_tree()->connect("node_removed", this, "_node_removed", Vector(), Object::CONNECT_ONESHOT); + joint_tree->connect("item_selected", this, "_joint_tree_selection_changed"); + joint_tree->connect("item_rmb_selected", this, "_joint_tree_rmb_select"); + +#ifdef TOOLS_ENABLED + skeleton->connect("pose_updated", this, "_update_properties"); +#endif // TOOLS_ENABLED -void SkeletonEditorPlugin::edit(Object *p_object) { - skeleton_editor->edit(Object::cast_to(p_object)); + break; + } + } } -bool SkeletonEditorPlugin::handles(Object *p_object) const { - return p_object->is_class("Skeleton"); +void SkeletonEditor::_node_removed(Node *p_node) { + if (skeleton && p_node == skeleton) { + skeleton = nullptr; + } +} + +void SkeletonEditor::_bind_methods() { + ClassDB::bind_method(D_METHOD("_node_removed"), &SkeletonEditor::_node_removed); + ClassDB::bind_method(D_METHOD("_joint_tree_selection_changed"), &SkeletonEditor::_joint_tree_selection_changed); + ClassDB::bind_method(D_METHOD("_joint_tree_rmb_select"), &SkeletonEditor::_joint_tree_rmb_select); + ClassDB::bind_method(D_METHOD("_update_properties"), &SkeletonEditor::_update_properties); + ClassDB::bind_method(D_METHOD("_on_click_option"), &SkeletonEditor::_on_click_option); + ClassDB::bind_method(D_METHOD("_menu_tool_item_pressed"), &SkeletonEditor::_menu_tool_item_pressed); + ClassDB::bind_method(D_METHOD("rest_mode_toggled"), &SkeletonEditor::rest_mode_toggled); + ClassDB::bind_method(D_METHOD("set_rest_mode_toggled"), &SkeletonEditor::set_rest_mode_toggled); + + ClassDB::bind_method(D_METHOD("get_drag_data_fw"), &SkeletonEditor::get_drag_data_fw); + ClassDB::bind_method(D_METHOD("can_drop_data_fw"), &SkeletonEditor::can_drop_data_fw); + ClassDB::bind_method(D_METHOD("drop_data_fw"), &SkeletonEditor::drop_data_fw); + ClassDB::bind_method(D_METHOD("move_skeleton_bone"), &SkeletonEditor::move_skeleton_bone); + + ClassDB::bind_method(D_METHOD("_draw_handles"), &SkeletonEditor::_draw_handles); +} + +void SkeletonEditor::_menu_tool_item_pressed(int p_option) { + + if (p_option != TOOL_MODE_BONE_NONE && !SpatialEditor::get_singleton()->is_tool_external()) { + SpatialEditor::get_singleton()->set_tool_mode(SpatialEditor::TOOL_MODE_EXTERNAL); + } + for (int i = 0; i < TOOL_MODE_BONE_MAX; i++) + tool_button[i]->set_pressed(i == p_option); + tool_mode = (ToolMode)p_option; + if (skeleton) { + if (p_option == TOOL_MODE_BONE_NONE) { + _hide_handles(); + } else { + _draw_handles(); + if (skeleton->get_selected_bone() >= 0) { + SpatialEditor::get_singleton()->clear_externals(); + SpatialEditor::get_singleton()->append_to_externals(skeleton->get_global_transform() * skeleton->get_bone_global_pose(skeleton->get_selected_bone())); + } + } + } + + switch (p_option) { + case TOOL_MODE_BONE_SELECT: { + SpatialEditor::get_singleton()->set_external_tool_mode(SpatialEditor::EX_TOOL_MODE_SELECT); + } break; + case TOOL_MODE_BONE_MOVE: { + SpatialEditor::get_singleton()->set_external_tool_mode(SpatialEditor::EX_TOOL_MODE_MOVE); + } break; + case TOOL_MODE_BONE_ROTATE: { + SpatialEditor::get_singleton()->set_external_tool_mode(SpatialEditor::EX_TOOL_MODE_ROTATE); + } break; + case TOOL_MODE_BONE_SCALE: { + SpatialEditor::get_singleton()->set_external_tool_mode(SpatialEditor::EX_TOOL_MODE_SCALE); + } break; + case TOOL_MODE_BONE_NONE: + break; + } + + _update_spatial_transform_gizmo(); +} + +void SkeletonEditor::rest_mode_toggled(const bool pressed) { + bool before_val = rest_mode; + + // Prevent that bone pose will be undo during rest mode. + // However SkeletonEditor will be memdeleted, + // so it need to record in SpatialEditor with calling method in + // EditorInspectorPluginSkeleton and it will not be memdeleted. + UndoRedo *ur = SpatialEditor::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Toggled Rest Mode")); + set_rest_mode_toggled(pressed); + ur->add_undo_method(editor_plugin, "set_rest_mode_toggled", before_val); + ur->add_do_method(editor_plugin, "set_rest_mode_toggled", pressed); + ur->commit_action(); +} + +void SkeletonEditor::set_rest_mode_toggled(const bool pressed) { + rest_mode_button->disconnect("toggled", this, "rest_mode_toggled"); + rest_mode_button->set_pressed(pressed); + rest_mode_button->connect("toggled", this, "rest_mode_toggled"); + + rest_mode = pressed; + const int bone_len = skeleton->get_bone_count(); + for (int i = 0; i < bone_len; i++) { + skeleton->set_bone_enabled(i, !rest_mode); + } + if (pose_editor) { + pose_editor->set_read_only(rest_mode); + } + if (custom_pose_editor) { + custom_pose_editor->set_read_only(rest_mode); + } + set_keyable(AnimationPlayerEditor::singleton->get_track_editor()->has_keying() && !rest_mode); +} + +SkeletonEditor::SkeletonEditor(EditorInspectorPluginSkeleton *e_plugin, EditorNode *p_editor, Skeleton *p_skeleton) : + editor(p_editor), + editor_plugin(e_plugin), + skeleton(p_skeleton) { + handle_material = Ref(memnew(ShaderMaterial)); + handle_shader = Ref(memnew(Shader)); + handle_shader->set_code(" \ + shader_type spatial; \ + render_mode unshaded; \ + uniform vec4 albedo : hint_color = vec4(1,1,1,1); \ + 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)) ); \ + } \ + POINT_SIZE=point_size; \ + VERTEX = VERTEX; \ + POSITION=PROJECTION_MATRIX*INV_CAMERA_MATRIX*WORLD_MATRIX*vec4(VERTEX.xyz,1.0); \ + POSITION.z = mix(POSITION.z, -POSITION.w, 0.999); \ + } \ + void fragment() { \ + vec4 albedo_tex = texture(texture_albedo,POINT_COORD); \ + if (albedo.a * albedo_tex.a < 0.5) { discard; } \ + 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 = albedo.rgb * col; \ + } \ + "); + handle_material->set_shader(handle_shader); + // handle_material->set_flag(SpatialMaterial::FLAG_DISABLE_DEPTH_TEST, true); + handle_material->set_render_priority(SpatialMaterial::RENDER_PRIORITY_MIN); + // handle_material->set_flag(SpatialMaterial::FLAG_UNSHADED, true); + // handle_material->set_flag(SpatialMaterial::FLAG_USE_POINT_SIZE, true); + // handle_material->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true); + Ref handle = editor->get_gui_base()->get_icon("EditorBoneHandle", "EditorIcons"); + handle_material->set_shader_param("point_size", handle->get_width()); + handle_material->set_shader_param("texture_albedo", handle); + //handle_material->set_texture(SpatialMaterial::TEXTURE_ALBEDO, handle); + + pointsm = memnew(MeshInstance); + am.instance(); + pointsm->set_mesh(am); + pointsm->set_transform(Transform(Basis(), Vector3(0, 0, 0.00001))); +} + +SkeletonEditor::~SkeletonEditor() { + set_rest_mode_toggled(false); + SpatialEditor::get_singleton()->disconnect("change_tool_mode", this, "_menu_tool_item_pressed"); + if (skeleton) { + pointsm->get_parent()->remove_child(pointsm); + skeleton->set_selected_bone(-1); + skeleton->disconnect("pose_updated", this, "_draw_handles"); + memdelete(pointsm); + } + for (int i = 0; i < 2; i++) { + if (separators[i]) { + SpatialEditor::get_singleton()->remove_control_from_menu_panel(separators[i]); + memdelete(separators[i]); + } + } + if (options) { + SpatialEditor::get_singleton()->remove_control_from_menu_panel(options); + memdelete(options); + } + SpatialEditor::get_singleton()->remove_control_from_menu_panel(tool_button[TOOL_MODE_BONE_SELECT]); + SpatialEditor::get_singleton()->remove_control_from_menu_panel(tool_button[TOOL_MODE_BONE_MOVE]); + SpatialEditor::get_singleton()->remove_control_from_menu_panel(tool_button[TOOL_MODE_BONE_ROTATE]); + SpatialEditor::get_singleton()->remove_control_from_menu_panel(tool_button[TOOL_MODE_BONE_SCALE]); + for (int i = 0; i < TOOL_MODE_BONE_MAX; i++) { + if (tool_button[i]) { + memdelete(tool_button[i]); + } + } + SpatialEditor::get_singleton()->remove_control_from_menu_panel(rest_mode_button); + if (rest_mode_button) { + memdelete(rest_mode_button); + } + if (SpatialEditor::get_singleton()->is_tool_external()) { + SpatialEditor::get_singleton()->set_tool_mode(SpatialEditor::TOOL_MODE_SELECT); + SpatialEditor::get_singleton()->set_external_tool_mode(SpatialEditor::EX_TOOL_MODE_SELECT); + } + +} + +void SkeletonEditor::_hide_handles() { + if (!skeleton) + return; + + pointsm->hide(); +} + +void SkeletonEditor::_draw_handles() { + + if (!skeleton || tool_mode == TOOL_MODE_BONE_NONE) + return; + + while (am->get_surface_count()) { + am->surface_remove(0); + } + + pointsm->show(); + + Array a; + a.resize(Mesh::ARRAY_MAX); + PoolVector va; + PoolVector ca; + { + const int bone_len = skeleton->get_bone_count(); + va.resize(bone_len); + ca.resize(bone_len); + PoolVector::Write vaw = va.write(); + PoolVector::Write caw = ca.write(); + + for (int i = 0; i < bone_len; i++) { + Vector3 point = skeleton->get_bone_global_pose(i).origin; + vaw[i] = point; + Color c; + if (i == skeleton->get_selected_bone()) { + c = Color(1,1,0); + } else { + c = Color(0,0,1); + } + caw[i] = c; + } + + } + a[Mesh::ARRAY_VERTEX] = va; + a[Mesh::ARRAY_COLOR] = ca; + am->add_surface_from_arrays(Mesh::PRIMITIVE_POINTS, a); + am->surface_set_material(0, handle_material); +} + +bool SkeletonEditor::forward_spatial_gui_input(int p_index, Camera *p_camera, const Ref &p_event) { + + if (!skeleton || tool_mode == TOOL_MODE_BONE_NONE) + return false; + + SpatialEditor *se = SpatialEditor::get_singleton(); + SpatialEditorViewport *sev = se->get_editor_viewport(p_index); + + Ref mb = p_event; + if (mb.is_valid()) { + + Transform gt = skeleton->get_global_transform(); + Vector3 ray_from = p_camera->get_global_transform().origin; + Vector2 gpoint = mb->get_position(); + real_t grab_threshold = 4 * EDSCALE; + + switch (mb->get_button_index()) { + case BUTTON_LEFT: { + if (mb->is_pressed()) { + + _edit.mouse_pos = mb->get_position(); + _edit.snap = se->is_snap_enabled(); + _edit.mode = SpatialEditorViewport::TRANSFORM_NONE; + + // check gizmo + if (_gizmo_select(p_index, _edit.mouse_pos)) { + return true; + } + + // select bone + 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 = gpoint.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) { + TreeItem *ti = _find(joint_tree->get_root(), "bones/" + itos(closest_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 { + skeleton->set_selected_bone(-1); + joint_tree->deselect_all(); + } + + } else { + if (_edit.mode != SpatialEditorViewport::TRANSFORM_NONE) { + if (skeleton && (skeleton->get_selected_bone() >= 0)) { + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Set Bone Transform"), UndoRedo::MERGE_ENDS); + if (rest_mode) { + ur->add_do_method(skeleton, "set_bone_rest", skeleton->get_selected_bone(), skeleton->get_bone_rest(skeleton->get_selected_bone())); + ur->add_undo_method(skeleton, "set_bone_rest", skeleton->get_selected_bone(), original_local); + } else { + ur->add_do_method(skeleton, "set_bone_pose", skeleton->get_selected_bone(), skeleton->get_bone_pose(skeleton->get_selected_bone())); + ur->add_undo_method(skeleton, "set_bone_pose", skeleton->get_selected_bone(), original_local); + } + ur->commit_action(); + _edit.mode = SpatialEditorViewport::TRANSFORM_NONE; + } + } + } + return true; + } break; + default: + break; + } + + } + + Ref mm = p_event; + if (mm.is_valid()) { + _edit.mouse_pos = mm->get_position(); + if (!(mm->get_button_mask() & 1)) { + _gizmo_select(p_index, _edit.mouse_pos, true); + } + if (mm->get_button_mask() & BUTTON_MASK_LEFT) { + if (_edit.mode == SpatialEditorViewport::TRANSFORM_NONE) + return true; + + Vector3 ray_pos = sev->get_ray_pos(mm->get_position()); + Vector3 ray = sev->get_ray(mm->get_position()); + float snap = EDITOR_GET("interface/inspector/default_float_step"); + + switch (_edit.mode) { + + case SpatialEditorViewport::TRANSFORM_SCALE: { + + Vector3 motion_mask; + Plane plane; + bool plane_mv = false; + + switch (_edit.plane) { + case SpatialEditorViewport::TRANSFORM_VIEW: + motion_mask = Vector3(0, 0, 0); + plane = Plane(_edit.center, sev->get_camera_normal()); + break; + case SpatialEditorViewport::TRANSFORM_X_AXIS: + motion_mask = se->get_gizmo_transform().basis.get_axis(0); + plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(sev->get_camera_normal())).normalized()); + break; + case SpatialEditorViewport::TRANSFORM_Y_AXIS: + motion_mask = se->get_gizmo_transform().basis.get_axis(1); + plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(sev->get_camera_normal())).normalized()); + break; + case SpatialEditorViewport::TRANSFORM_Z_AXIS: + motion_mask = se->get_gizmo_transform().basis.get_axis(2); + plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(sev->get_camera_normal())).normalized()); + break; + case SpatialEditorViewport::TRANSFORM_YZ: + motion_mask = se->get_gizmo_transform().basis.get_axis(2) + se->get_gizmo_transform().basis.get_axis(1); + plane = Plane(_edit.center, se->get_gizmo_transform().basis.get_axis(0)); + plane_mv = true; + break; + case SpatialEditorViewport::TRANSFORM_XZ: + motion_mask = se->get_gizmo_transform().basis.get_axis(2) + se->get_gizmo_transform().basis.get_axis(0); + plane = Plane(_edit.center, se->get_gizmo_transform().basis.get_axis(1)); + plane_mv = true; + break; + case SpatialEditorViewport::TRANSFORM_XY: + motion_mask = se->get_gizmo_transform().basis.get_axis(0) + se->get_gizmo_transform().basis.get_axis(1); + plane = Plane(_edit.center, se->get_gizmo_transform().basis.get_axis(2)); + plane_mv = true; + break; + } + + Vector3 intersection; + if (!plane.intersects_ray(ray_pos, ray, &intersection)) + break; + + Vector3 click; + if (!plane.intersects_ray(_edit.click_ray_pos, _edit.click_ray, &click)) + break; + + Vector3 motion = intersection - click; + if (_edit.plane != SpatialEditorViewport::TRANSFORM_VIEW) { + motion = motion_mask.dot(motion) * motion_mask; + } else { + float center_click_dist = click.distance_to(_edit.center); + float center_inters_dist = intersection.distance_to(_edit.center); + if (center_click_dist == 0) + break; + + float scale = center_inters_dist - center_click_dist; + motion = Vector3(scale, scale, scale); + } + + bool local_coords = (se->are_local_coords_enabled() && _edit.plane != SpatialEditorViewport::TRANSFORM_VIEW); + + if (_edit.snap || se->is_snap_enabled()) { + snap = se->get_scale_snap() / 100; + } + + Transform t; + + if (local_coords) { + Basis g = original_global.basis; + motion = g.inverse().xform(motion); + if (_edit.snap || se->is_snap_enabled()) { + motion.snap(Vector3(snap, snap, snap)); + } + Vector3 local_scale = original_local.basis.get_scale() * (motion + Vector3(1, 1, 1)); + // Prevent scaling to 0 it would break the gizmo + Basis check = original_local.basis; + check.scale(local_scale); + if (check.determinant() != 0) { + t = original_local; + t.basis = t.basis.scaled_local(motion + Vector3(1, 1, 1)); + } + } else { + if (_edit.snap || se->is_snap_enabled()) { + motion.snap(Vector3(snap, snap, snap)); + } + t = original_local; + Transform r; + r.basis.scale(motion + Vector3(1, 1, 1)); + Basis base = original_to_local.get_basis().orthonormalized().inverse(); + t.basis = base * (r.get_basis() * (base.inverse() * original_local.get_basis())); + } + + // Apply scale + if (rest_mode) { + skeleton->set_bone_rest(skeleton->get_selected_bone(), t); + } else { + skeleton->set_bone_pose(skeleton->get_selected_bone(), t); + } + + sev->update_surface(); + + } break; + + case SpatialEditorViewport::TRANSFORM_TRANSLATE: { + + Vector3 motion_mask; + Plane plane; + bool plane_mv = false; + + switch (_edit.plane) { + case SpatialEditorViewport::TRANSFORM_VIEW: + plane = Plane(_edit.center, sev->get_camera_normal()); + break; + case SpatialEditorViewport::TRANSFORM_X_AXIS: + motion_mask = se->get_gizmo_transform().basis.get_axis(0); + plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(sev->get_camera_normal())).normalized()); + break; + case SpatialEditorViewport::TRANSFORM_Y_AXIS: + motion_mask = se->get_gizmo_transform().basis.get_axis(1); + plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(sev->get_camera_normal())).normalized()); + break; + case SpatialEditorViewport::TRANSFORM_Z_AXIS: + motion_mask = se->get_gizmo_transform().basis.get_axis(2); + plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(sev->get_camera_normal())).normalized()); + break; + case SpatialEditorViewport::TRANSFORM_YZ: + plane = Plane(_edit.center, se->get_gizmo_transform().basis.get_axis(0)); + plane_mv = true; + break; + case SpatialEditorViewport::TRANSFORM_XZ: + plane = Plane(_edit.center, se->get_gizmo_transform().basis.get_axis(1)); + plane_mv = true; + break; + case SpatialEditorViewport::TRANSFORM_XY: + plane = Plane(_edit.center, se->get_gizmo_transform().basis.get_axis(2)); + plane_mv = true; + break; + } + + Vector3 intersection; + if (!plane.intersects_ray(ray_pos, ray, &intersection)) + break; + + Vector3 click; + if (!plane.intersects_ray(_edit.click_ray_pos, _edit.click_ray, &click)) + break; + + Vector3 motion = intersection - click; + if (_edit.plane != SpatialEditorViewport::TRANSFORM_VIEW) { + if (!plane_mv) { + motion = motion_mask.dot(motion) * motion_mask; + } + } + + if (_edit.snap || se->is_snap_enabled()) { + snap = se->get_translate_snap(); + } + + motion = original_to_local.basis.inverse().xform(motion); + if (_edit.snap || se->is_snap_enabled()) { + motion.snap(Vector3(snap, snap, snap)); + } + + Transform t; + // Apply translation + t = original_local; + t.origin += motion; + + if (rest_mode) { + skeleton->set_bone_rest(skeleton->get_selected_bone(), t); + } else { + skeleton->set_bone_pose(skeleton->get_selected_bone(), t); + } + + sev->update_surface(); + + } break; + + case SpatialEditorViewport::TRANSFORM_ROTATE: { + + Plane plane; + Vector3 axis; + + switch (_edit.plane) { + case SpatialEditorViewport::TRANSFORM_VIEW: + plane = Plane(_edit.center, sev->get_camera_normal()); + break; + case SpatialEditorViewport::TRANSFORM_X_AXIS: + plane = Plane(_edit.center, se->get_gizmo_transform().basis.get_axis(0)); + axis = Vector3(1, 0, 0); + break; + case SpatialEditorViewport::TRANSFORM_Y_AXIS: + plane = Plane(_edit.center, se->get_gizmo_transform().basis.get_axis(1)); + axis = Vector3(0, 1, 0); + break; + case SpatialEditorViewport::TRANSFORM_Z_AXIS: + plane = Plane(_edit.center, se->get_gizmo_transform().basis.get_axis(2)); + axis = Vector3(0, 0, 1); + break; + case SpatialEditorViewport::TRANSFORM_YZ: + case SpatialEditorViewport::TRANSFORM_XZ: + case SpatialEditorViewport::TRANSFORM_XY: + break; + } + + Vector3 intersection; + if (!plane.intersects_ray(ray_pos, ray, &intersection)) + break; + + Vector3 click; + if (!plane.intersects_ray(_edit.click_ray_pos, _edit.click_ray, &click)) + break; + + Vector3 y_axis = (click - _edit.center).normalized(); + Vector3 x_axis = plane.normal.cross(y_axis).normalized(); + + float angle = Math::atan2(x_axis.dot(intersection - _edit.center), y_axis.dot(intersection - _edit.center)); + + if (_edit.snap || se->is_snap_enabled()) { + snap = se->get_rotate_snap(); + } + angle = Math::rad2deg(angle) + snap * 0.5; //else it won't reach +180 + angle -= Math::fmod(angle, snap); + // set_message(vformat(TTR("Rotating %s degrees."), String::num(angle, snap_step_decimals))); + angle = Math::deg2rad(angle); + + bool local_coords = (se->are_local_coords_enabled() && _edit.plane != SpatialEditorViewport::TRANSFORM_VIEW); // Disable local transformation for TRANSFORM_VIEW + + Transform t; + + if (local_coords) { + Basis rot = Basis(axis, angle); + t.basis = original_local.get_basis().orthonormalized() * rot; + t.basis = t.basis.scaled_local(original_local.basis.get_scale()); + t.origin = original_local.origin; + } else { + Transform r; + Basis base = original_to_local.get_basis().orthonormalized().inverse(); + r.basis.rotate(plane.normal, angle); + t.basis = base * r.get_basis() * base.inverse() * original_local.get_basis(); + // t.basis = t.basis.scaled(original_local.basis.get_scale()); + t.origin = original_local.origin; + } + + // Apply rotation + if (rest_mode) { + skeleton->set_bone_rest(skeleton->get_selected_bone(), t); + } else { + skeleton->set_bone_pose(skeleton->get_selected_bone(), t); + } + + sev->update_surface(); + + } break; + default: { + } + } + + return true; + } + } + + return false; +} + +void EditorInspectorPluginSkeleton::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_rest_mode_toggled"), &EditorInspectorPluginSkeleton::set_rest_mode_toggled); +} + +bool EditorInspectorPluginSkeleton::can_handle(Object *p_object) { + return Object::cast_to(p_object) != nullptr; } -void SkeletonEditorPlugin::make_visible(bool p_visible) { - if (p_visible) { - skeleton_editor->options->show(); - } else { +void EditorInspectorPluginSkeleton::parse_begin(Object *p_object) { + Skeleton *skeleton = Object::cast_to(p_object); + ERR_FAIL_COND(!skeleton); - skeleton_editor->options->hide(); - skeleton_editor->edit(NULL); + skel_editor = memnew(SkeletonEditor(this, editor, skeleton)); + add_custom_control(skel_editor); +} + +void EditorInspectorPluginSkeleton::set_rest_mode_toggled(const bool p_pressed) { + if (SpatialEditor::get_singleton()->get_selected()->get_class() == "Skeleton" && skel_editor) { + skel_editor->set_rest_mode_toggled(p_pressed); } } SkeletonEditorPlugin::SkeletonEditorPlugin(EditorNode *p_node) { editor = p_node; - skeleton_editor = memnew(SkeletonEditor); - editor->get_viewport()->add_child(skeleton_editor); + + skeleton_plugin = memnew(EditorInspectorPluginSkeleton); + skeleton_plugin->editor = editor; + + EditorInspector::add_inspector_plugin(skeleton_plugin); +} + +bool SkeletonEditorPlugin::handles(Object *p_object) const { + return p_object->is_class("Skeleton"); } -SkeletonEditorPlugin::~SkeletonEditorPlugin() {} +void SkeletonEditor::_compute_edit(int p_index, const Point2 &p_point) { + + SpatialEditor *se = SpatialEditor::get_singleton(); + SpatialEditorViewport *sev = se->get_editor_viewport(p_index); + + _edit.click_ray = sev->get_ray(Vector2(p_point.x, p_point.y)); + _edit.click_ray_pos = sev->get_ray_pos(Vector2(p_point.x, p_point.y)); + _edit.plane = SpatialEditorViewport::TRANSFORM_VIEW; + _update_spatial_transform_gizmo(); + _edit.center = se->get_gizmo_transform().origin; + + if (skeleton->get_selected_bone() != -1) { + original_global = skeleton->get_global_transform() * skeleton->get_bone_global_pose(skeleton->get_selected_bone()); + if (rest_mode) { + original_local = skeleton->get_bone_rest(skeleton->get_selected_bone()); + } else { + original_local = skeleton->get_bone_pose(skeleton->get_selected_bone()); + } + original_to_local = skeleton->get_global_transform(); + int parent_idx = skeleton->get_bone_parent(skeleton->get_selected_bone()); + if (parent_idx >= 0) { + original_to_local = original_to_local * skeleton->get_bone_global_pose(parent_idx); + } + if (!rest_mode) { + original_to_local = original_to_local * skeleton->get_bone_rest(skeleton->get_selected_bone()) * skeleton->get_bone_custom_pose(skeleton->get_selected_bone()); + } + } +} + +bool SkeletonEditor::_gizmo_select(int p_index, const Vector2 &p_screenpos, bool p_highlight_only) { + + SpatialEditor *se = SpatialEditor::get_singleton(); + SpatialEditorViewport *sev = se->get_editor_viewport(p_index); + + if (!se->is_gizmo_visible()) + return false; + if (skeleton->get_selected_bone() == -1) { + if (p_highlight_only) + se->select_gizmo_highlight_axis(-1); + return false; + } + + Vector3 ray_pos = sev->get_ray_pos(Vector2(p_screenpos.x, p_screenpos.y)); + Vector3 ray = sev->get_ray(Vector2(p_screenpos.x, p_screenpos.y)); + + Transform gt = se->get_gizmo_transform(); + float gs = sev->get_gizmo_scale(); + + if (se->get_external_tool_mode() == SpatialEditor::EX_TOOL_MODE_SELECT || se->get_external_tool_mode() == SpatialEditor::EX_TOOL_MODE_MOVE) { + + int col_axis = -1; + float col_d = 1e20; + + for (int i = 0; i < 3; i++) { + + Vector3 grabber_pos = gt.origin + gt.basis.get_axis(i) * gs * (GIZMO_ARROW_OFFSET + (GIZMO_ARROW_SIZE * 0.5)); + float grabber_radius = gs * GIZMO_ARROW_SIZE; + + Vector3 r; + + if (Geometry::segment_intersects_sphere(ray_pos, ray_pos + ray * MAX_Z, grabber_pos, grabber_radius, &r)) { + float d = r.distance_to(ray_pos); + if (d < col_d) { + col_d = d; + col_axis = i; + } + } + } + + bool is_plane_translate = false; + // plane select + if (col_axis == -1) { + col_d = 1e20; + + for (int i = 0; i < 3; i++) { + + Vector3 ivec2 = gt.basis.get_axis((i + 1) % 3).normalized(); + Vector3 ivec3 = gt.basis.get_axis((i + 2) % 3).normalized(); + + Vector3 grabber_pos = gt.origin + (ivec2 + ivec3) * gs * (GIZMO_PLANE_SIZE + GIZMO_PLANE_DST); + + Vector3 r; + Plane plane(gt.origin, gt.basis.get_axis(i).normalized()); + + if (plane.intersects_ray(ray_pos, ray, &r)) { + + float dist = r.distance_to(grabber_pos); + if (dist < (gs * GIZMO_PLANE_SIZE)) { + + float d = ray_pos.distance_to(r); + if (d < col_d) { + col_d = d; + col_axis = i; + + is_plane_translate = true; + } + } + } + } + } + + if (col_axis != -1) { + + if (p_highlight_only) { + + se->select_gizmo_highlight_axis(col_axis + (is_plane_translate ? 6 : 0)); + + } else { + //handle plane translate + _edit.mode = SpatialEditorViewport::TRANSFORM_TRANSLATE; + _compute_edit(p_index, Point2(p_screenpos.x, p_screenpos.y)); + _edit.plane = SpatialEditorViewport::TransformPlane(SpatialEditorViewport::TRANSFORM_X_AXIS + col_axis + (is_plane_translate ? 3 : 0)); + } + return true; + } + } + + if (se->get_external_tool_mode() == SpatialEditor::EX_TOOL_MODE_SELECT || se->get_external_tool_mode() == SpatialEditor::EX_TOOL_MODE_ROTATE) { + + int col_axis = -1; + float col_d = 1e20; + + for (int i = 0; i < 3; i++) { + + Plane plane(gt.origin, gt.basis.get_axis(i).normalized()); + Vector3 r; + if (!plane.intersects_ray(ray_pos, ray, &r)) + continue; + + float dist = r.distance_to(gt.origin); + + if (dist > gs * (GIZMO_CIRCLE_SIZE - GIZMO_RING_HALF_WIDTH) && dist < gs * (GIZMO_CIRCLE_SIZE + GIZMO_RING_HALF_WIDTH)) { + + float d = ray_pos.distance_to(r); + if (d < col_d) { + col_d = d; + col_axis = i; + } + } + } + + if (col_axis != -1) { + + if (p_highlight_only) { + + se->select_gizmo_highlight_axis(col_axis + 3); + } else { + //handle rotate + _edit.mode = SpatialEditorViewport::TRANSFORM_ROTATE; + _compute_edit(p_index, Point2(p_screenpos.x, p_screenpos.y)); + _edit.plane = SpatialEditorViewport::TransformPlane(SpatialEditorViewport::TRANSFORM_X_AXIS + col_axis); + } + return true; + } + } + + if (se->get_external_tool_mode() == SpatialEditor::EX_TOOL_MODE_SCALE) { + + int col_axis = -1; + float col_d = 1e20; + + for (int i = 0; i < 3; i++) { + + Vector3 grabber_pos = gt.origin + gt.basis.get_axis(i) * gs * GIZMO_SCALE_OFFSET; + float grabber_radius = gs * GIZMO_ARROW_SIZE; + + Vector3 r; + + if (Geometry::segment_intersects_sphere(ray_pos, ray_pos + ray * MAX_Z, grabber_pos, grabber_radius, &r)) { + float d = r.distance_to(ray_pos); + if (d < col_d) { + col_d = d; + col_axis = i; + } + } + } + + bool is_plane_scale = false; + // plane select + if (col_axis == -1) { + col_d = 1e20; + + for (int i = 0; i < 3; i++) { + + Vector3 ivec2 = gt.basis.get_axis((i + 1) % 3).normalized(); + Vector3 ivec3 = gt.basis.get_axis((i + 2) % 3).normalized(); + + Vector3 grabber_pos = gt.origin + (ivec2 + ivec3) * gs * (GIZMO_PLANE_SIZE + GIZMO_PLANE_DST); + + Vector3 r; + Plane plane(gt.origin, gt.basis.get_axis(i).normalized()); + + if (plane.intersects_ray(ray_pos, ray, &r)) { + + float dist = r.distance_to(grabber_pos); + if (dist < (gs * GIZMO_PLANE_SIZE)) { + + float d = ray_pos.distance_to(r); + if (d < col_d) { + col_d = d; + col_axis = i; + + is_plane_scale = true; + } + } + } + } + } + + if (col_axis != -1) { + + if (p_highlight_only) { + + se->select_gizmo_highlight_axis(col_axis + (is_plane_scale ? 12 : 9)); + + } else { + //handle scale + _edit.mode = SpatialEditorViewport::TRANSFORM_SCALE; + _compute_edit(p_index, Point2(p_screenpos.x, p_screenpos.y)); + _edit.plane = SpatialEditorViewport::TransformPlane(SpatialEditorViewport::TRANSFORM_X_AXIS + col_axis + (is_plane_scale ? 3 : 0)); + } + return true; + } + } + + if (p_highlight_only) + se->select_gizmo_highlight_axis(-1); + + return false; +} + +TreeItem *SkeletonEditor::_find(TreeItem *p_node, const NodePath &p_path) { + if (!p_node) { + return NULL; + } + + NodePath np = p_node->get_metadata(0); + if (np == p_path) { + return p_node; + } + + TreeItem *children = p_node->get_children(); + while (children) { + TreeItem *n = _find(children, p_path); + if (n) { + return n; + } + children = children->get_next(); + } + + return NULL; +} \ No newline at end of file diff --git a/editor/plugins/skeleton_editor_plugin.h b/editor/plugins/skeleton_editor_plugin.h index de3f752af6..b701d1af1e 100644 --- a/editor/plugins/skeleton_editor_plugin.h +++ b/editor/plugins/skeleton_editor_plugin.h @@ -31,20 +31,133 @@ #ifndef SKELETON_EDITOR_PLUGIN_H #define SKELETON_EDITOR_PLUGIN_H +#include "core/os/input_event.h" #include "editor/editor_node.h" #include "editor/editor_plugin.h" +#include "spatial_editor_plugin.h" +#include "scene/3d/camera.h" +#include "scene/3d/mesh_instance.h" #include "scene/3d/skeleton.h" -class PhysicalBone; +class EditorInspectorPluginSkeleton; class Joint; +class PhysicalBone; +class SkeletonEditorPlugin; +class Button; +class CheckBox; + +class BoneTransformEditor : public VBoxContainer { + GDCLASS(BoneTransformEditor, VBoxContainer); + + static const int32_t TRANSLATION_COMPONENTS = 3; + static const int32_t ROTATION_DEGREES_COMPONENTS = 3; + static const int32_t SCALE_COMPONENTS = 3; + static const int32_t BASIS_COMPONENTS = 9; + static const int32_t BASIS_SPLIT_COMPONENTS = 3; + static const int32_t TRANSFORM_COMPONENTS = 12; + static const int32_t TRANSFORM_SPLIT_COMPONENTS = 3; + static const int32_t TRANSFORM_CONTROL_COMPONENTS = 3; + + EditorInspectorSection *section; + + GridContainer *translation_grid; + GridContainer *rotation_grid; + GridContainer *scale_grid; + GridContainer *transform_grid; + + EditorSpinSlider *translation_slider[TRANSLATION_COMPONENTS]; + EditorSpinSlider *rotation_slider[ROTATION_DEGREES_COMPONENTS]; + EditorSpinSlider *scale_slider[SCALE_COMPONENTS]; + EditorSpinSlider *transform_slider[TRANSFORM_COMPONENTS]; + + Rect2 background_rects[5]; + + Skeleton *skeleton; + String property; + + UndoRedo *undo_redo; + + Button *key_button; + CheckBox *enabled_checkbox; + + bool keyable; + bool toggle_enabled; + bool updating; + + String label; + + void create_editors(); + void setup_spinner(EditorSpinSlider *spinner, const bool is_transform_spinner); + + void _value_changed(const double p_value, const bool p_from_transform); + + Transform compute_transform(const bool p_from_transform) const; + + void update_enabled_checkbox(); + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + BoneTransformEditor(Skeleton *p_skeleton); + + // Which transform target to modify + void set_target(const String &p_prop); + void set_label(const String &p_label) { label = p_label; } + + void _update_properties(); + void _update_custom_pose_properties(); + void _update_transform_properties(Transform 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 + void set_keyable(const bool p_keyable); + + // 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 + void _key_button_pressed(); -class SkeletonEditor : public Node { - GDCLASS(SkeletonEditor, Node); + // Bone Enabled Checkbox toggled + void _checkbox_toggled(const bool p_toggled); +}; + +class SkeletonEditor : public VBoxContainer { + + GDCLASS(SkeletonEditor, VBoxContainer); + + friend class SkeletonEditorPlugin; enum Menu { + MENU_OPTION_INIT_POSE, + MENU_OPTION_INSERT_KEYS, + MENU_OPTION_INSERT_KEYS_EXISTED, + MENU_OPTION_POSE_TO_REST, MENU_OPTION_CREATE_PHYSICAL_SKELETON }; + enum ToolMode { + TOOL_MODE_BONE_SELECT, + TOOL_MODE_BONE_MOVE, + TOOL_MODE_BONE_ROTATE, + TOOL_MODE_BONE_SCALE, + TOOL_MODE_BONE_NONE, + TOOL_MODE_BONE_MAX + }; + + enum MenuToolOption { + MENU_TOOL_BONE_SELECT, + MENU_TOOL_BONE_MOVE, + MENU_TOOL_BONE_ROTATE, + MENU_TOOL_BONE_SCALE, + MENU_TOOL_BONE_NONE, + MENU_TOOL_BONE_MAX + }; + struct BoneInfo { PhysicalBone *physical_bone; Transform relative_rest; // Relative to skeleton node @@ -52,45 +165,134 @@ class SkeletonEditor : public Node { physical_bone(NULL) {} }; + EditorNode *editor; + EditorInspectorPluginSkeleton *editor_plugin; + Skeleton *skeleton; + Tree *joint_tree; + BoneTransformEditor *rest_editor; + BoneTransformEditor *pose_editor; + BoneTransformEditor *custom_pose_editor; + + VSeparator *separators[2]; MenuButton *options; + ToolButton *tool_button[TOOL_MODE_BONE_MAX]; + ToolButton *rest_mode_button; + + ToolMode tool_mode = TOOL_MODE_BONE_NONE; + bool rest_mode = false; + + EditorFileDialog *file_dialog; + + UndoRedo *undo_redo; + + bool keyable; void _on_click_option(int p_option); + void _file_selected(const String &p_file); + void _menu_tool_item_pressed(int p_option); + TreeItem *_find(TreeItem *p_node, const NodePath &p_path); + void rest_mode_toggled(const bool pressed); - friend class SkeletonEditorPlugin; + EditorFileDialog *file_export_lib; + + void update_joint_tree(); + void update_editors(); + + void create_editors(); + + void init_pose(); + void insert_keys(bool p_all_bones); + void pose_to_rest(); + void create_physical_skeleton(); + PhysicalBone *create_physical_bone(int bone_id, int bone_child_id, const Vector &bones_infos); + + Variant get_drag_data_fw(const Point2 &p_point, Control *p_from); + bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; + void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); + + Ref handle_material; + Ref handle_shader; + MeshInstance *pointsm; + Ref am; + void _hide_handles(); + void _draw_handles(); + + SpatialEditorViewport::EditData _edit; + void _compute_edit(int p_index, const Point2 &p_point); + bool _gizmo_select(int p_index, const Vector2 &p_screenpos, bool p_highlight_only = false); + + Transform original_local; + Transform original_global; + Transform original_to_local; + + void _update_spatial_transform_gizmo(); protected: void _notification(int p_what); void _node_removed(Node *p_node); static void _bind_methods(); - void create_physical_skeleton(); - PhysicalBone *create_physical_bone(int bone_id, int bone_child_id, const Vector &bones_infos); - public: - void edit(Skeleton *p_node); + virtual bool forward_spatial_gui_input(int p_index, Camera *p_camera, const Ref &p_event); + void move_skeleton_bone(NodePath p_skeleton_path, int32_t p_selected_boneidx, int32_t p_target_boneidx); - SkeletonEditor(); + // Transform can be keyed, whether or not to show the button + void set_keyable(const bool p_keyable); + + Skeleton *get_skeleton() const { return skeleton; }; + + void set_rest_mode_toggled(const bool pressed); + + void _joint_tree_selection_changed(); + void _joint_tree_rmb_select(const Vector2 &p_pos); + + void _update_properties(); + + SkeletonEditor(EditorInspectorPluginSkeleton *e_plugin, EditorNode *p_editor, Skeleton *skeleton); ~SkeletonEditor(); }; -class SkeletonEditorPlugin : public EditorPlugin { +class EditorInspectorPluginSkeleton : public EditorInspectorPlugin { + GDCLASS(EditorInspectorPluginSkeleton, EditorInspectorPlugin); + + friend class SkeletonEditorPlugin; + + SkeletonEditor *skel_editor; + EditorNode *editor; + UndoRedo *undo_redo; + void set_rest_mode_toggled (const bool p_pressed); + +protected: + static void _bind_methods(); +public: + virtual bool forward_spatial_gui_input(int p_index, Camera *p_camera, const Ref &p_event) { return skel_editor->forward_spatial_gui_input(p_index, p_camera, p_event); } + virtual bool can_handle(Object *p_object); + virtual void parse_begin(Object *p_object); + UndoRedo *get_undo_redo() { return undo_redo; } +}; + +class SkeletonEditorPlugin : public EditorPlugin { GDCLASS(SkeletonEditorPlugin, EditorPlugin); + EditorInspectorPluginSkeleton *skeleton_plugin; EditorNode *editor; - SkeletonEditor *skeleton_editor; public: - virtual String get_name() const { return "Skeleton"; } - virtual bool has_main_screen() const { return false; } - virtual void edit(Object *p_object); + virtual bool forward_spatial_gui_input(int p_index, Camera *p_camera, const Ref &p_event) { + if (SpatialEditor::get_singleton()->get_tool_mode() != SpatialEditor::TOOL_MODE_EXTERNAL) { + return false; + } + return skeleton_plugin->forward_spatial_gui_input(p_index, p_camera, p_event); + } + bool has_main_screen() const { return false; } virtual bool handles(Object *p_object) const; - virtual void make_visible(bool p_visible); + + virtual String get_name() const { return "Skeleton"; } SkeletonEditorPlugin(EditorNode *p_node); - ~SkeletonEditorPlugin(); }; #endif // SKELETON_EDITOR_PLUGIN_H diff --git a/editor/plugins/spatial_editor_plugin.cpp b/editor/plugins/spatial_editor_plugin.cpp index 483dff7616..4ff84c658c 100644 --- a/editor/plugins/spatial_editor_plugin.cpp +++ b/editor/plugins/spatial_editor_plugin.cpp @@ -422,18 +422,15 @@ Point2 SpatialEditorViewport::_point_to_screen(const Vector3 &p_point) { return camera->unproject_position(p_point) * viewport_container->get_stretch_shrink(); } -Vector3 SpatialEditorViewport::_get_ray_pos(const Vector2 &p_pos) const { - +Vector3 SpatialEditorViewport::get_ray_pos(const Vector2 &p_pos) const { return camera->project_ray_origin(p_pos / viewport_container->get_stretch_shrink()); } -Vector3 SpatialEditorViewport::_get_camera_normal() const { - +Vector3 SpatialEditorViewport::get_camera_normal() const { return -_get_camera_transform().basis.get_axis(2); } -Vector3 SpatialEditorViewport::_get_ray(const Vector2 &p_pos) const { - +Vector3 SpatialEditorViewport::get_ray(const Vector2 &p_pos) const { return camera->project_ray_normal(p_pos / viewport_container->get_stretch_shrink()); } @@ -490,8 +487,8 @@ ObjectID SpatialEditorViewport::_select_ray(const Point2 &p_pos, bool p_append, if (r_gizmo_handle) *r_gizmo_handle = -1; - Vector3 ray = _get_ray(p_pos); - Vector3 pos = _get_ray_pos(p_pos); + Vector3 ray = get_ray(p_pos); + Vector3 pos = get_ray_pos(p_pos); Vector2 shrinked_pos = p_pos / viewport_container->get_stretch_shrink(); Vector instances = VisualServer::get_singleton()->instances_cull_ray(pos, ray, get_tree()->get_root()->get_world()->get_scenario()); @@ -554,8 +551,8 @@ ObjectID SpatialEditorViewport::_select_ray(const Point2 &p_pos, bool p_append, void SpatialEditorViewport::_find_items_at_pos(const Point2 &p_pos, bool &r_includes_current, Vector<_RayResult> &results, bool p_alt_select) { - Vector3 ray = _get_ray(p_pos); - Vector3 pos = _get_ray_pos(p_pos); + Vector3 ray = get_ray(p_pos); + Vector3 pos = get_ray_pos(p_pos); Vector instances = VisualServer::get_singleton()->instances_cull_ray(pos, ray, get_tree()->get_root()->get_world()->get_scenario()); Set > found_gizmos; @@ -668,7 +665,7 @@ void SpatialEditorViewport::_select_region() { } } - Plane near(cam_pos, -_get_camera_normal()); + Plane near(cam_pos, -get_camera_normal()); near.d -= get_znear(); frustum.push_back(near); @@ -740,8 +737,8 @@ void SpatialEditorViewport::_update_name() { void SpatialEditorViewport::_compute_edit(const Point2 &p_point) { - _edit.click_ray = _get_ray(Vector2(p_point.x, p_point.y)); - _edit.click_ray_pos = _get_ray_pos(Vector2(p_point.x, p_point.y)); + _edit.click_ray = get_ray(Vector2(p_point.x, p_point.y)); + _edit.click_ray_pos = get_ray_pos(Vector2(p_point.x, p_point.y)); _edit.plane = TRANSFORM_VIEW; spatial_editor->update_transform_gizmo(); _edit.center = spatial_editor->get_gizmo_transform().origin; @@ -798,8 +795,8 @@ bool SpatialEditorViewport::_gizmo_select(const Vector2 &p_screenpos, bool p_hig return false; } - Vector3 ray_pos = _get_ray_pos(Vector2(p_screenpos.x, p_screenpos.y)); - Vector3 ray = _get_ray(Vector2(p_screenpos.x, p_screenpos.y)); + Vector3 ray_pos = get_ray_pos(Vector2(p_screenpos.x, p_screenpos.y)); + Vector3 ray = get_ray(Vector2(p_screenpos.x, p_screenpos.y)); Transform gt = spatial_editor->get_gizmo_transform(); float gs = gizmo_scale; @@ -887,7 +884,7 @@ bool SpatialEditorViewport::_gizmo_select(const Vector2 &p_screenpos, bool p_hig float dist = r.distance_to(gt.origin); Vector3 r_dir = (r - gt.origin).normalized(); - if (_get_camera_normal().dot(r_dir) <= 0.005) { + if (get_camera_normal().dot(r_dir) <= 0.005) { if (dist > gs * (GIZMO_CIRCLE_SIZE - GIZMO_RING_HALF_WIDTH) && dist < gs * (GIZMO_CIRCLE_SIZE + GIZMO_RING_HALF_WIDTH)) { float d = ray_pos.distance_to(r); if (d < col_d) { @@ -982,7 +979,7 @@ bool SpatialEditorViewport::_gizmo_select(const Vector2 &p_screenpos, bool p_hig } } - if (p_highlight_only) + if (p_highlight_only && spatial_editor->get_tool_mode() != SpatialEditor::TOOL_MODE_EXTERNAL) spatial_editor->select_gizmo_highlight_axis(-1); return false; @@ -1071,7 +1068,7 @@ void SpatialEditorViewport::_sinput(const Ref &p_event) { EditorNode *en = editor; EditorPluginList *force_input_forwarding_list = en->get_editor_plugins_force_input_forwarding(); if (!force_input_forwarding_list->empty()) { - bool discard = force_input_forwarding_list->forward_spatial_gui_input(camera, p_event, true); + bool discard = force_input_forwarding_list->forward_spatial_gui_input(index, camera, p_event, true); if (discard) return; } @@ -1080,7 +1077,7 @@ void SpatialEditorViewport::_sinput(const Ref &p_event) { EditorNode *en = editor; EditorPluginList *over_plugin_list = en->get_editor_plugins_over(); if (!over_plugin_list->empty()) { - bool discard = over_plugin_list->forward_spatial_gui_input(camera, p_event, false); + bool discard = over_plugin_list->forward_spatial_gui_input(index, camera, p_event, false); if (discard) return; } @@ -1462,8 +1459,8 @@ void SpatialEditorViewport::_sinput(const Ref &p_event) { if (_edit.mode == TRANSFORM_NONE) return; - Vector3 ray_pos = _get_ray_pos(m->get_position()); - Vector3 ray = _get_ray(m->get_position()); + Vector3 ray_pos = get_ray_pos(m->get_position()); + Vector3 ray = get_ray(m->get_position()); float snap = EDITOR_GET("interface/inspector/default_float_step"); int snap_step_decimals = Math::range_step_decimals(snap); @@ -1478,19 +1475,19 @@ void SpatialEditorViewport::_sinput(const Ref &p_event) { switch (_edit.plane) { case TRANSFORM_VIEW: motion_mask = Vector3(0, 0, 0); - plane = Plane(_edit.center, _get_camera_normal()); + plane = Plane(_edit.center, get_camera_normal()); break; case TRANSFORM_X_AXIS: motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(0); - plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized()); + plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(get_camera_normal())).normalized()); break; case TRANSFORM_Y_AXIS: motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(1); - plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized()); + plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(get_camera_normal())).normalized()); break; case TRANSFORM_Z_AXIS: motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(2); - plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized()); + plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(get_camera_normal())).normalized()); break; case TRANSFORM_YZ: motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(2) + spatial_editor->get_gizmo_transform().basis.get_axis(1); @@ -1625,19 +1622,19 @@ void SpatialEditorViewport::_sinput(const Ref &p_event) { switch (_edit.plane) { case TRANSFORM_VIEW: - plane = Plane(_edit.center, _get_camera_normal()); + plane = Plane(_edit.center, get_camera_normal()); break; case TRANSFORM_X_AXIS: motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(0); - plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized()); + plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(get_camera_normal())).normalized()); break; case TRANSFORM_Y_AXIS: motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(1); - plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized()); + plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(get_camera_normal())).normalized()); break; case TRANSFORM_Z_AXIS: motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(2); - plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized()); + plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(get_camera_normal())).normalized()); break; case TRANSFORM_YZ: plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(0)); @@ -1734,7 +1731,7 @@ void SpatialEditorViewport::_sinput(const Ref &p_event) { switch (_edit.plane) { case TRANSFORM_VIEW: - plane = Plane(_edit.center, _get_camera_normal()); + plane = Plane(_edit.center, get_camera_normal()); break; case TRANSFORM_X_AXIS: plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(0)); @@ -3215,7 +3212,6 @@ void SpatialEditorViewport::update_transform_gizmo_view() { return; Transform xform = spatial_editor->get_gizmo_transform(); - Transform camera_xform = camera->get_transform(); if (xform.origin.distance_squared_to(camera_xform.origin) < 0.01) { @@ -3254,17 +3250,32 @@ void SpatialEditorViewport::update_transform_gizmo_view() { xform.basis.scale(scale); - for (int i = 0; i < 3; i++) { - VisualServer::get_singleton()->instance_set_transform(move_gizmo_instance[i], xform); - VisualServer::get_singleton()->instance_set_visible(move_gizmo_instance[i], spatial_editor->is_gizmo_visible() && (spatial_editor->get_tool_mode() == SpatialEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == SpatialEditor::TOOL_MODE_MOVE)); - VisualServer::get_singleton()->instance_set_transform(move_plane_gizmo_instance[i], xform); - VisualServer::get_singleton()->instance_set_visible(move_plane_gizmo_instance[i], spatial_editor->is_gizmo_visible() && (spatial_editor->get_tool_mode() == SpatialEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == SpatialEditor::TOOL_MODE_MOVE)); - VisualServer::get_singleton()->instance_set_transform(rotate_gizmo_instance[i], xform); - VisualServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[i], spatial_editor->is_gizmo_visible() && (spatial_editor->get_tool_mode() == SpatialEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == SpatialEditor::TOOL_MODE_ROTATE)); - VisualServer::get_singleton()->instance_set_transform(scale_gizmo_instance[i], xform); - VisualServer::get_singleton()->instance_set_visible(scale_gizmo_instance[i], spatial_editor->is_gizmo_visible() && (spatial_editor->get_tool_mode() == SpatialEditor::TOOL_MODE_SCALE)); - VisualServer::get_singleton()->instance_set_transform(scale_plane_gizmo_instance[i], xform); - VisualServer::get_singleton()->instance_set_visible(scale_plane_gizmo_instance[i], spatial_editor->is_gizmo_visible() && (spatial_editor->get_tool_mode() == SpatialEditor::TOOL_MODE_SCALE)); + if (spatial_editor->is_tool_external()) { + for (int i = 0; i < 3; i++) { + VisualServer::get_singleton()->instance_set_transform(move_gizmo_instance[i], xform); + VisualServer::get_singleton()->instance_set_visible(move_gizmo_instance[i], spatial_editor->is_gizmo_visible() && (spatial_editor->get_external_tool_mode() == SpatialEditor::EX_TOOL_MODE_SELECT || spatial_editor->get_external_tool_mode() == SpatialEditor::EX_TOOL_MODE_MOVE)); + VisualServer::get_singleton()->instance_set_transform(move_plane_gizmo_instance[i], xform); + VisualServer::get_singleton()->instance_set_visible(move_plane_gizmo_instance[i], spatial_editor->is_gizmo_visible() && (spatial_editor->get_external_tool_mode() == SpatialEditor::EX_TOOL_MODE_SELECT || spatial_editor->get_external_tool_mode() == SpatialEditor::EX_TOOL_MODE_MOVE)); + VisualServer::get_singleton()->instance_set_transform(rotate_gizmo_instance[i], xform); + VisualServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[i], spatial_editor->is_gizmo_visible() && (spatial_editor->get_external_tool_mode() == SpatialEditor::EX_TOOL_MODE_SELECT || spatial_editor->get_external_tool_mode() == SpatialEditor::EX_TOOL_MODE_ROTATE)); + VisualServer::get_singleton()->instance_set_transform(scale_gizmo_instance[i], xform); + VisualServer::get_singleton()->instance_set_visible(scale_gizmo_instance[i], spatial_editor->is_gizmo_visible() && (spatial_editor->get_external_tool_mode() == SpatialEditor::EX_TOOL_MODE_SCALE)); + VisualServer::get_singleton()->instance_set_transform(scale_plane_gizmo_instance[i], xform); + VisualServer::get_singleton()->instance_set_visible(scale_plane_gizmo_instance[i], spatial_editor->is_gizmo_visible() && (spatial_editor->get_external_tool_mode() == SpatialEditor::EX_TOOL_MODE_SCALE)); + } + } else { + for (int i = 0; i < 3; i++) { + VisualServer::get_singleton()->instance_set_transform(move_gizmo_instance[i], xform); + VisualServer::get_singleton()->instance_set_visible(move_gizmo_instance[i], spatial_editor->is_gizmo_visible() && (spatial_editor->get_tool_mode() == SpatialEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == SpatialEditor::TOOL_MODE_MOVE)); + VisualServer::get_singleton()->instance_set_transform(move_plane_gizmo_instance[i], xform); + VisualServer::get_singleton()->instance_set_visible(move_plane_gizmo_instance[i], spatial_editor->is_gizmo_visible() && (spatial_editor->get_tool_mode() == SpatialEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == SpatialEditor::TOOL_MODE_MOVE)); + VisualServer::get_singleton()->instance_set_transform(rotate_gizmo_instance[i], xform); + VisualServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[i], spatial_editor->is_gizmo_visible() && (spatial_editor->get_tool_mode() == SpatialEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == SpatialEditor::TOOL_MODE_ROTATE)); + VisualServer::get_singleton()->instance_set_transform(scale_gizmo_instance[i], xform); + VisualServer::get_singleton()->instance_set_visible(scale_gizmo_instance[i], spatial_editor->is_gizmo_visible() && (spatial_editor->get_tool_mode() == SpatialEditor::TOOL_MODE_SCALE)); + VisualServer::get_singleton()->instance_set_transform(scale_plane_gizmo_instance[i], xform); + VisualServer::get_singleton()->instance_set_visible(scale_plane_gizmo_instance[i], spatial_editor->is_gizmo_visible() && (spatial_editor->get_tool_mode() == SpatialEditor::TOOL_MODE_SCALE)); + } } // Rotation white outline VisualServer::get_singleton()->instance_set_transform(rotate_gizmo_instance[3], xform); @@ -3498,8 +3509,8 @@ void SpatialEditorViewport::assign_pending_data_pointers(Spatial *p_preview_node Vector3 SpatialEditorViewport::_get_instance_position(const Point2 &p_pos) const { const float MAX_DISTANCE = 10; - Vector3 world_ray = _get_ray(p_pos); - Vector3 world_pos = _get_ray_pos(p_pos); + Vector3 world_ray = get_ray(p_pos); + Vector3 world_pos = get_ray_pos(p_pos); Vector instances = VisualServer::get_singleton()->instances_cull_ray(world_pos, world_ray, get_tree()->get_root()->get_world()->get_scenario()); Set > found_gizmos; @@ -4410,42 +4421,60 @@ void SpatialEditor::select_gizmo_highlight_axis(int p_axis) { void SpatialEditor::update_transform_gizmo() { - List &selection = editor_selection->get_selected_node_list(); AABB center; bool first = true; Basis gizmo_basis; bool local_gizmo_coords = are_local_coords_enabled(); - for (List::Element *E = selection.front(); E; E = E->next()) { + if (is_tool_external()) { + Vector transforms = get_externals(); + for (int i=0; i < transforms.size(); i++) { + Transform xf = transforms[i]; + if (first) { + center.position = xf.origin; + first = false; + if (local_gizmo_coords) { + gizmo_basis = xf.basis; + gizmo_basis.orthonormalize(); + } + } else { + center.expand_to(xf.origin); + gizmo_basis = Basis(); + } + } + } else { + List &selection = editor_selection->get_selected_node_list(); + for (List::Element *E = selection.front(); E; E = E->next()) { - Spatial *sp = Object::cast_to(E->get()); - if (!sp) - continue; + Spatial *sp = Object::cast_to(E->get()); + if (!sp) + continue; - SpatialEditorSelectedItem *se = editor_selection->get_node_editor_data(sp); - if (!se) - continue; + SpatialEditorSelectedItem *se = editor_selection->get_node_editor_data(sp); + if (!se) + continue; - Transform xf = se->sp->get_global_gizmo_transform(); + Transform xf = se->sp->get_global_gizmo_transform(); - if (first) { - center.position = xf.origin; - first = false; - if (local_gizmo_coords) { - gizmo_basis = xf.basis; - gizmo_basis.orthonormalize(); + if (first) { + center.position = xf.origin; + first = false; + if (local_gizmo_coords) { + gizmo_basis = xf.basis; + gizmo_basis.orthonormalize(); + } + } else { + center.expand_to(xf.origin); + gizmo_basis = Basis(); } - } else { - center.expand_to(xf.origin); - gizmo_basis = Basis(); } } Vector3 pcenter = center.position + center.size * 0.5; - gizmo.visible = !first; gizmo.transform.origin = pcenter; gizmo.transform.basis = gizmo_basis; + gizmo.visible = !first; for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) { viewports[i]->update_transform_gizmo_view(); @@ -4880,12 +4909,15 @@ void SpatialEditor::_menu_item_pressed(int p_option) { case MENU_TOOL_MOVE: case MENU_TOOL_ROTATE: case MENU_TOOL_SCALE: - case MENU_TOOL_LIST_SELECT: { - - for (int i = 0; i < TOOL_MAX; i++) + case MENU_TOOL_LIST_SELECT: + case MENU_TOOL_EXTERNAL: { + for (int i = 0; i < TOOL_MAX; i++) { tool_button[i]->set_pressed(i == p_option); + } tool_mode = (ToolMode)p_option; + clear_externals(); update_transform_gizmo(); + emit_signal("change_tool_mode"); } break; case MENU_TRANSFORM_CONFIGURE_SNAP: { @@ -6233,6 +6265,7 @@ void SpatialEditor::_bind_methods() { ADD_SIGNAL(MethodInfo("transform_key_request")); ADD_SIGNAL(MethodInfo("item_lock_status_changed")); ADD_SIGNAL(MethodInfo("item_group_status_changed")); + ADD_SIGNAL(MethodInfo("change_tool_mode")); } void SpatialEditor::clear() { @@ -6326,6 +6359,10 @@ SpatialEditor::SpatialEditor(EditorNode *p_editor) { tool_button[TOOL_MODE_SCALE]->connect("pressed", this, "_menu_item_pressed", button_binds); tool_button[TOOL_MODE_SCALE]->set_shortcut(ED_SHORTCUT("spatial_editor/tool_scale", TTR("Scale Mode"), KEY_R)); + tool_button[TOOL_MODE_EXTERNAL] = memnew(ToolButton); + button_binds.write[0] = MENU_TOOL_EXTERNAL; + tool_button[TOOL_MODE_EXTERNAL]->connect("pressed", this, "_menu_item_pressed", button_binds); + hbc_menu->add_child(memnew(VSeparator)); tool_button[TOOL_MODE_LIST_SELECT] = memnew(ToolButton); @@ -6625,6 +6662,10 @@ SpatialEditor::~SpatialEditor() { memdelete(preview_node); } +void SpatialEditor::set_tool_mode(ToolMode p_tool_mode) { + _menu_item_pressed(p_tool_mode); +} + void SpatialEditorPlugin::make_visible(bool p_visible) { if (p_visible) { diff --git a/editor/plugins/spatial_editor_plugin.h b/editor/plugins/spatial_editor_plugin.h index 4644144a58..351063ed47 100644 --- a/editor/plugins/spatial_editor_plugin.h +++ b/editor/plugins/spatial_editor_plugin.h @@ -233,6 +233,40 @@ public: FREELOOK_FULLY_AXIS_LOCKED, }; + enum TransformMode { + TRANSFORM_NONE, + TRANSFORM_ROTATE, + TRANSFORM_TRANSLATE, + TRANSFORM_SCALE + }; + + enum TransformPlane { + TRANSFORM_VIEW, + TRANSFORM_X_AXIS, + TRANSFORM_Y_AXIS, + TRANSFORM_Z_AXIS, + TRANSFORM_YZ, + TRANSFORM_XZ, + TRANSFORM_XY, + }; + + struct EditData { + TransformMode mode; + TransformPlane plane; + Transform original; + Vector3 click_ray; + Vector3 click_ray_pos; + Vector3 center; + Vector3 orig_gizmo_pos; + int edited_gizmo; + Point2 mouse_pos; + bool snap; + Ref gizmo; + int gizmo_handle; + Variant gizmo_initial_value; + Vector3 gizmo_initial_pos; + }; + private: int index; String name; @@ -292,14 +326,11 @@ private: void _select(Node *p_node, bool p_append, bool p_single); ObjectID _select_ray(const Point2 &p_pos, bool p_append, bool &r_includes_current, int *r_gizmo_handle = NULL, bool p_alt_select = false); void _find_items_at_pos(const Point2 &p_pos, bool &r_includes_current, Vector<_RayResult> &results, bool p_alt_select = false); - Vector3 _get_ray_pos(const Vector2 &p_pos) const; - Vector3 _get_ray(const Vector2 &p_pos) const; Point2 _point_to_screen(const Vector3 &p_point); Transform _get_camera_transform() const; int get_selected_count() const; Vector3 _get_camera_position() const; - Vector3 _get_camera_normal() const; Vector3 _get_screen_to_space(const Vector3 &p_vector3); void _select_region(); @@ -333,39 +364,8 @@ private: NAVIGATION_ORBIT, NAVIGATION_LOOK }; - enum TransformMode { - TRANSFORM_NONE, - TRANSFORM_ROTATE, - TRANSFORM_TRANSLATE, - TRANSFORM_SCALE - }; - enum TransformPlane { - TRANSFORM_VIEW, - TRANSFORM_X_AXIS, - TRANSFORM_Y_AXIS, - TRANSFORM_Z_AXIS, - TRANSFORM_YZ, - TRANSFORM_XZ, - TRANSFORM_XY, - }; - - struct EditData { - TransformMode mode; - TransformPlane plane; - Transform original; - Vector3 click_ray; - Vector3 click_ray_pos; - Vector3 center; - Vector3 orig_gizmo_pos; - int edited_gizmo; - Point2 mouse_pos; - bool snap; - Ref gizmo; - int gizmo_handle; - Variant gizmo_initial_value; - Vector3 gizmo_initial_pos; - } _edit; + EditData _edit; struct Cursor { @@ -451,6 +451,8 @@ public: void update_surface() { surface->update(); } void update_transform_gizmo_view(); + ViewportContainer *get_viewport_container() const { return viewport_container; } + void set_can_preview(Camera *p_preview); void set_state(const Dictionary &p_state); Dictionary get_state() const; @@ -466,6 +468,11 @@ public: Viewport *get_viewport_node() { return viewport; } Camera *get_camera() { return camera; } // return the default camera object. + float get_gizmo_scale() { return gizmo_scale; }; + + Vector3 get_camera_normal() const; + Vector3 get_ray_pos(const Vector2 &p_pos) const; + Vector3 get_ray(const Vector2 &p_pos) const; SpatialEditorViewport(SpatialEditor *p_spatial_editor, EditorNode *p_editor, int p_index); }; @@ -545,6 +552,7 @@ public: TOOL_MODE_MOVE, TOOL_MODE_ROTATE, TOOL_MODE_SCALE, + TOOL_MODE_EXTERNAL, TOOL_MODE_LIST_SELECT, TOOL_LOCK_SELECTED, TOOL_UNLOCK_SELECTED, @@ -559,7 +567,15 @@ public: TOOL_OPT_USE_SNAP, TOOL_OPT_OVERRIDE_CAMERA, TOOL_OPT_MAX + }; + + enum ExternalToolMode { + EX_TOOL_MODE_SELECT, + EX_TOOL_MODE_MOVE, + EX_TOOL_MODE_ROTATE, + EX_TOOL_MODE_SCALE, + EX_TOOL_MAX }; private: @@ -574,6 +590,7 @@ private: ///// ToolMode tool_mode; + ExternalToolMode external_tool_mode = EX_TOOL_MODE_SELECT; VisualServer::ScenarioDebugMode scenario_debug; @@ -626,6 +643,7 @@ private: MENU_TOOL_MOVE, MENU_TOOL_ROTATE, MENU_TOOL_SCALE, + MENU_TOOL_EXTERNAL, MENU_TOOL_LIST_SELECT, MENU_TOOL_LOCAL_COORDS, MENU_TOOL_USE_SNAP, @@ -728,6 +746,8 @@ private: void _refresh_menu_icons(); + Vector externals; + protected: void _notification(int p_what); //void _gui_input(InputEvent p_event); @@ -748,7 +768,11 @@ public: Transform get_gizmo_transform() const { return gizmo.transform; } bool is_gizmo_visible() const { return gizmo.visible; } + void set_tool_mode(ToolMode p_tool_mode); ToolMode get_tool_mode() const { return tool_mode; } + bool is_tool_external() const { return tool_mode == TOOL_MODE_EXTERNAL; } + ExternalToolMode get_external_tool_mode() const { return external_tool_mode; } + void set_external_tool_mode(ExternalToolMode p_external_tool_mode) { external_tool_mode = p_external_tool_mode; } bool are_local_coords_enabled() const { return tool_option_button[SpatialEditor::TOOL_OPT_LOCAL_COORDS]->is_pressed(); } bool is_snap_enabled() const { return snap_enabled ^ snap_key_enabled; } float get_translate_snap() const; @@ -800,6 +824,11 @@ public: void edit(Spatial *p_spatial); void clear(); + void append_to_externals(Transform p_transform) { externals.push_back(p_transform); } + void append_array_to_externals(Vector p_transforms) { externals.append_array(p_transforms); } + void clear_externals() { externals.clear(); } + Vector get_externals() const { return externals; } + SpatialEditor(EditorNode *p_editor); ~SpatialEditor(); }; diff --git a/editor/spatial_editor_gizmos.cpp b/editor/spatial_editor_gizmos.cpp index c60952e5a6..ddecd103f4 100644 --- a/editor/spatial_editor_gizmos.cpp +++ b/editor/spatial_editor_gizmos.cpp @@ -32,6 +32,7 @@ #include "core/math/geometry.h" #include "core/math/quick_hull.h" +#include "editor/plugins/skeleton_editor_plugin.h" #include "scene/3d/audio_stream_player_3d.h" #include "scene/3d/baked_lightmap.h" #include "scene/3d/collision_polygon.h" @@ -1611,8 +1612,35 @@ void Position3DSpatialGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) { SkeletonSpatialGizmoPlugin::SkeletonSpatialGizmoPlugin() { - Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/skeleton", Color(1, 0.8, 0.4)); - create_material("skeleton_material", gizmo_color); + skeleton_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/skeleton", Color(1, 0.8, 0.4)); + selected_bone_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/selected_bone", Color(1, 0, 0)); + bone_axis_length = EDITOR_DEF("editors/3d_gizmos/gizmo_settings/bone_axis_length", (float)0.015); + create_material("skeleton_material", skeleton_color); + selected_mat = Ref(memnew(ShaderMaterial)); + selected_sh = Ref(memnew(Shader)); + selected_sh->set_code(" \ + shader_type spatial; \ + render_mode unshaded; \ + uniform vec4 albedo : hint_color = vec4(1,1,1,1); \ + 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, -POSITION.w, 0.998); \ + } \ + void fragment() { \ + vec2 base_uv = UV; \ + vec4 albedo_tex = texture(texture_albedo,base_uv); \ + albedo_tex *= COLOR; \ + if (albedo.a * albedo_tex.a < 0.5) { discard; } \ + ALBEDO = albedo.rgb * albedo_tex.rgb; \ + } \ + "); + selected_mat->set_shader(selected_sh); } bool SkeletonSpatialGizmoPlugin::has_gizmo(Spatial *p_spatial) { @@ -1633,7 +1661,12 @@ void SkeletonSpatialGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) { p_gizmo->clear(); - Ref material = get_material("skeleton_material", p_gizmo); + Ref material; + if (p_gizmo->is_selected()) { + material = selected_mat; + } else { + material = get_material("skeleton_material", p_gizmo); + } Ref surface_tool(memnew(SurfaceTool)); @@ -1654,12 +1687,15 @@ void SkeletonSpatialGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) { weights.write[0] = 1; + Color bone_color; AABB aabb; - Color bonecolor = Color(1.0, 0.4, 0.4, 0.3); - Color rootcolor = Color(0.4, 1.0, 0.4, 0.1); - for (int i_bone = 0; i_bone < skel->get_bone_count(); i_bone++) { + if (skel->get_bone_parent(i_bone) == skel->get_selected_bone()) { + bone_color = selected_bone_color; + } else { + bone_color = skeleton_color; + } int i = skel->get_process_order(i_bone); @@ -1687,17 +1723,33 @@ void SkeletonSpatialGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) { Vector3 first; Vector3 points[4]; int pointidx = 0; + Color axis_color[3]; + axis_color[0] = Color(1,0,0); + axis_color[1] = Color(0,1,0); + axis_color[2] = Color(0,0,1); for (int j = 0; j < 3; j++) { - bones.write[0] = parent; - surface_tool->add_bones(bones); - surface_tool->add_weights(weights); - surface_tool->add_color(rootcolor); - surface_tool->add_vertex(v0 - grests[parent].basis[j].normalized() * dist * 0.05); - surface_tool->add_bones(bones); - surface_tool->add_weights(weights); - surface_tool->add_color(rootcolor); - surface_tool->add_vertex(v0 + grests[parent].basis[j].normalized() * dist * 0.05); + if (p_gizmo->is_selected()) { + bones.write[0] = i; + surface_tool->add_bones(bones); + surface_tool->add_weights(weights); + surface_tool->add_color(axis_color[j]); + surface_tool->add_vertex(v1); + surface_tool->add_bones(bones); + surface_tool->add_weights(weights); + surface_tool->add_color(axis_color[j]); + surface_tool->add_vertex(v1 + grests[i].basis[j].normalized() * bone_axis_length); + } else { + bones.write[0] = i; + surface_tool->add_bones(bones); + surface_tool->add_weights(weights); + surface_tool->add_color(axis_color[j]); + surface_tool->add_vertex(v1 - grests[i].basis[j].normalized() * dist * 0.05); + surface_tool->add_bones(bones); + surface_tool->add_weights(weights); + surface_tool->add_color(axis_color[j]); + surface_tool->add_vertex(v1 + grests[i].basis[j].normalized() * dist * 0.05); + } if (j == closest) continue; @@ -1720,22 +1772,22 @@ void SkeletonSpatialGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) { bones.write[0] = parent; surface_tool->add_bones(bones); surface_tool->add_weights(weights); - surface_tool->add_color(bonecolor); + surface_tool->add_color(bone_color); surface_tool->add_vertex(v0); surface_tool->add_bones(bones); surface_tool->add_weights(weights); - surface_tool->add_color(bonecolor); + surface_tool->add_color(bone_color); surface_tool->add_vertex(point); bones.write[0] = parent; surface_tool->add_bones(bones); surface_tool->add_weights(weights); - surface_tool->add_color(bonecolor); + surface_tool->add_color(bone_color); surface_tool->add_vertex(point); bones.write[0] = i; surface_tool->add_bones(bones); surface_tool->add_weights(weights); - surface_tool->add_color(bonecolor); + surface_tool->add_color(bone_color); surface_tool->add_vertex(v1); points[pointidx++] = point; } @@ -1747,11 +1799,11 @@ void SkeletonSpatialGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) { bones.write[0] = parent; surface_tool->add_bones(bones); surface_tool->add_weights(weights); - surface_tool->add_color(bonecolor); + surface_tool->add_color(bone_color); surface_tool->add_vertex(points[j]); surface_tool->add_bones(bones); surface_tool->add_weights(weights); - surface_tool->add_color(bonecolor); + surface_tool->add_color(bone_color); surface_tool->add_vertex(points[(j + 1) % 4]); } diff --git a/editor/spatial_editor_gizmos.h b/editor/spatial_editor_gizmos.h index 58598d1ff3..36070116d9 100644 --- a/editor/spatial_editor_gizmos.h +++ b/editor/spatial_editor_gizmos.h @@ -137,6 +137,11 @@ public: class SkeletonSpatialGizmoPlugin : public EditorSpatialGizmoPlugin { GDCLASS(SkeletonSpatialGizmoPlugin, EditorSpatialGizmoPlugin); + Color skeleton_color = Color(1, 0.8, 0.4); + Color selected_bone_color = Color(1, 0, 0); + float bone_axis_length = 0.015; + Ref selected_mat; + Ref selected_sh; public: bool has_gizmo(Spatial *p_spatial); diff --git a/modules/gridmap/grid_map_editor_plugin.h b/modules/gridmap/grid_map_editor_plugin.h index 941f3c52f4..6de4061ca9 100644 --- a/modules/gridmap/grid_map_editor_plugin.h +++ b/modules/gridmap/grid_map_editor_plugin.h @@ -261,7 +261,7 @@ protected: void _notification(int p_what); public: - virtual bool forward_spatial_gui_input(Camera *p_camera, const Ref &p_event) { return grid_map_editor->forward_spatial_input_event(p_camera, p_event); } + virtual bool forward_spatial_gui_input(int p_index, Camera *p_camera, const Ref &p_event) { return grid_map_editor->forward_spatial_input_event(p_camera, p_event); } virtual String get_name() const { return "GridMap"; } bool has_main_screen() const { return false; } virtual void edit(Object *p_object); diff --git a/scene/3d/skeleton.cpp b/scene/3d/skeleton.cpp index ec6d4dceef..2c77bec64a 100644 --- a/scene/3d/skeleton.cpp +++ b/scene/3d/skeleton.cpp @@ -158,17 +158,17 @@ bool Skeleton::_get(const StringName &p_path, Variant &r_ret) const { return true; } -void Skeleton::_get_property_list(List *p_list) const { +void Skeleton::_get_property_list(List *p_list) const { for (int i = 0; i < bones.size(); i++) { String prep = "bones/" + itos(i) + "/"; - p_list->push_back(PropertyInfo(Variant::STRING, prep + "name")); - p_list->push_back(PropertyInfo(Variant::INT, prep + "parent", PROPERTY_HINT_RANGE, "-1," + itos(bones.size() - 1) + ",1")); - p_list->push_back(PropertyInfo(Variant::TRANSFORM, prep + "rest")); - p_list->push_back(PropertyInfo(Variant::BOOL, prep + "enabled")); + p_list->push_back(PropertyInfo(Variant::STRING, prep + "name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + p_list->push_back(PropertyInfo(Variant::INT, prep + "parent", PROPERTY_HINT_RANGE, "-1," + itos(bones.size() - 1) + ",1", PROPERTY_USAGE_NOEDITOR)); + p_list->push_back(PropertyInfo(Variant::TRANSFORM, prep + "rest", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); + p_list->push_back(PropertyInfo(Variant::BOOL, prep + "enabled", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); p_list->push_back(PropertyInfo(Variant::TRANSFORM, prep + "pose", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); - p_list->push_back(PropertyInfo(Variant::ARRAY, prep + "bound_children")); + p_list->push_back(PropertyInfo(Variant::ARRAY, prep + "bound_children", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR)); } } @@ -375,7 +375,11 @@ void Skeleton::_notification(int p_what) { } dirty = false; - emit_signal("skeleton_updated"); + +#ifdef TOOLS_ENABLED + emit_signal("pose_updated"); +#endif // TOOLS_ENABLED + } break; } } @@ -624,7 +628,6 @@ int Skeleton::get_process_order(int p_idx) { } void Skeleton::localize_rests() { - _update_process_order(); for (int i = bones.size() - 1; i >= 0; i--) { @@ -849,6 +852,16 @@ Ref Skeleton::register_skin(const Ref &p_skin) { return skin_ref; } +void Skeleton::set_selected_bone(int p_bone) { + selected_bone = p_bone; + update_gizmo(); + return; +} + +int Skeleton::get_selected_bone() const { + return selected_bone; +} + void Skeleton::_bind_methods() { ClassDB::bind_method(D_METHOD("add_bone", "name"), &Skeleton::add_bone); @@ -897,7 +910,10 @@ void Skeleton::_bind_methods() { #endif // _3D_DISABLED +#ifdef TOOLS_ENABLED + ADD_SIGNAL(MethodInfo("pose_updated")); ADD_SIGNAL(MethodInfo("skeleton_updated")); +#endif // TOOLS_ENABLED BIND_CONSTANT(NOTIFICATION_UPDATE_SKELETON); } @@ -916,3 +932,8 @@ Skeleton::~Skeleton() { E->get()->skeleton_node = nullptr; } } + +Vector Skeleton::get_bone_process_order() { + _update_process_order(); + return process_order; +} \ No newline at end of file diff --git a/scene/3d/skeleton.h b/scene/3d/skeleton.h index fb5d1367d4..73df15ec98 100644 --- a/scene/3d/skeleton.h +++ b/scene/3d/skeleton.h @@ -144,6 +144,8 @@ private: void _update_process_order(); + int selected_bone = -1; + protected: bool _get(const StringName &p_path, Variant &r_ret) const; bool _set(const StringName &p_path, const Variant &p_value); @@ -200,9 +202,13 @@ public: void localize_rests(); // used for loaders and tools int get_process_order(int p_idx); + Vector get_bone_process_order(); Ref register_skin(const Ref &p_skin); + void set_selected_bone(int p_bone); + int get_selected_bone() const; + #ifndef _3D_DISABLED // Physical bone API -- 2.30.1