summaryrefslogtreecommitdiff
path: root/editor/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'editor/plugins')
-rw-r--r--editor/plugins/animation_blend_space_1d_editor.cpp16
-rw-r--r--editor/plugins/animation_blend_space_2d_editor.cpp16
-rw-r--r--editor/plugins/animation_blend_tree_editor_plugin.cpp84
-rw-r--r--editor/plugins/animation_player_editor_plugin.cpp28
-rw-r--r--editor/plugins/animation_state_machine_editor.cpp40
-rw-r--r--editor/plugins/animation_tree_editor_plugin.cpp4
-rw-r--r--editor/plugins/asset_library_editor_plugin.cpp5
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp368
-rw-r--r--editor/plugins/canvas_item_editor_plugin.h69
-rw-r--r--editor/plugins/collision_shape_2d_editor_plugin.cpp57
-rw-r--r--editor/plugins/collision_shape_2d_editor_plugin.h13
-rw-r--r--editor/plugins/cpu_particles_2d_editor_plugin.cpp4
-rw-r--r--editor/plugins/editor_preview_plugins.cpp10
-rw-r--r--editor/plugins/gpu_particles_2d_editor_plugin.cpp4
-rw-r--r--editor/plugins/material_editor_plugin.cpp40
-rw-r--r--editor/plugins/mesh_instance_3d_editor_plugin.cpp11
-rw-r--r--editor/plugins/mesh_library_editor_plugin.cpp12
-rw-r--r--editor/plugins/node_3d_editor_gizmos.cpp5474
-rw-r--r--editor/plugins/node_3d_editor_gizmos.h635
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp1766
-rw-r--r--editor/plugins/node_3d_editor_plugin.h211
-rw-r--r--editor/plugins/path_3d_editor_plugin.cpp56
-rw-r--r--editor/plugins/path_3d_editor_plugin.h10
-rw-r--r--editor/plugins/polygon_2d_editor_plugin.cpp30
-rw-r--r--editor/plugins/polygon_2d_editor_plugin.h12
-rw-r--r--editor/plugins/resource_preloader_editor_plugin.cpp12
-rw-r--r--editor/plugins/root_motion_editor_plugin.cpp4
-rw-r--r--editor/plugins/script_editor_plugin.cpp36
-rw-r--r--editor/plugins/script_text_editor.cpp69
-rw-r--r--editor/plugins/shader_editor_plugin.cpp20
-rw-r--r--editor/plugins/shader_file_editor_plugin.cpp6
-rw-r--r--editor/plugins/sprite_frames_editor_plugin.cpp26
-rw-r--r--editor/plugins/texture_editor_plugin.cpp37
-rw-r--r--editor/plugins/texture_editor_plugin.h8
-rw-r--r--editor/plugins/texture_region_editor_plugin.cpp27
-rw-r--r--editor/plugins/theme_editor_plugin.cpp212
-rw-r--r--editor/plugins/tiles/atlas_merging_dialog.cpp2
-rw-r--r--editor/plugins/tiles/tile_atlas_view.cpp5
-rw-r--r--editor/plugins/tiles/tile_data_editors.cpp2
-rw-r--r--editor/plugins/tiles/tile_data_editors.h2
-rw-r--r--editor/plugins/tiles/tile_map_editor.cpp694
-rw-r--r--editor/plugins/tiles/tile_map_editor.h35
-rw-r--r--editor/plugins/tiles/tile_proxies_manager_dialog.cpp12
-rw-r--r--editor/plugins/tiles/tile_proxies_manager_dialog.h2
-rw-r--r--editor/plugins/tiles/tile_set_atlas_source_editor.cpp6
-rw-r--r--editor/plugins/version_control_editor_plugin.cpp26
-rw-r--r--editor/plugins/version_control_editor_plugin.h1
-rw-r--r--editor/plugins/visual_shader_editor_plugin.cpp225
48 files changed, 8403 insertions, 2041 deletions
diff --git a/editor/plugins/animation_blend_space_1d_editor.cpp b/editor/plugins/animation_blend_space_1d_editor.cpp
index 9e58332e41..ad2d9866fa 100644
--- a/editor/plugins/animation_blend_space_1d_editor.cpp
+++ b/editor/plugins/animation_blend_space_1d_editor.cpp
@@ -72,22 +72,22 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_gui_input(const Ref<InputEven
List<StringName> names;
ap->get_animation_list(&names);
- for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
- animations_menu->add_icon_item(get_theme_icon(SNAME("Animation"), SNAME("EditorIcons")), E->get());
- animations_to_add.push_back(E->get());
+ for (const StringName &E : names) {
+ animations_menu->add_icon_item(get_theme_icon(SNAME("Animation"), SNAME("EditorIcons")), E);
+ animations_to_add.push_back(E);
}
}
}
- for (List<StringName>::Element *E = classes.front(); E; E = E->next()) {
- String name = String(E->get()).replace_first("AnimationNode", "");
+ for (const StringName &E : classes) {
+ String name = String(E).replace_first("AnimationNode", "");
if (name == "Animation") {
continue;
}
int idx = menu->get_item_count();
menu->add_item(vformat("Add %s", name), idx);
- menu->set_item_metadata(idx, E->get());
+ menu->set_item_metadata(idx, E);
}
Ref<AnimationNode> clipb = EditorSettings::get_singleton()->get_resource_clipboard();
@@ -373,8 +373,8 @@ void AnimationNodeBlendSpace1DEditor::_add_menu_type(int p_index) {
open_file->clear_filters();
List<String> filters;
ResourceLoader::get_recognized_extensions_for_type("AnimationRootNode", &filters);
- for (List<String>::Element *E = filters.front(); E; E = E->next()) {
- open_file->add_filter("*." + E->get());
+ for (const String &E : filters) {
+ open_file->add_filter("*." + E);
}
open_file->popup_file_dialog();
return;
diff --git a/editor/plugins/animation_blend_space_2d_editor.cpp b/editor/plugins/animation_blend_space_2d_editor.cpp
index bbf88bbd8f..49fcac512b 100644
--- a/editor/plugins/animation_blend_space_2d_editor.cpp
+++ b/editor/plugins/animation_blend_space_2d_editor.cpp
@@ -96,21 +96,21 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Ref<InputEven
if (ap) {
List<StringName> names;
ap->get_animation_list(&names);
- for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
- animations_menu->add_icon_item(get_theme_icon(SNAME("Animation"), SNAME("EditorIcons")), E->get());
- animations_to_add.push_back(E->get());
+ for (const StringName &E : names) {
+ animations_menu->add_icon_item(get_theme_icon(SNAME("Animation"), SNAME("EditorIcons")), E);
+ animations_to_add.push_back(E);
}
}
}
- for (List<StringName>::Element *E = classes.front(); E; E = E->next()) {
- String name = String(E->get()).replace_first("AnimationNode", "");
+ for (const StringName &E : classes) {
+ String name = String(E).replace_first("AnimationNode", "");
if (name == "Animation") {
continue; // nope
}
int idx = menu->get_item_count();
menu->add_item(vformat("Add %s", name), idx);
- menu->set_item_metadata(idx, E->get());
+ menu->set_item_metadata(idx, E);
}
Ref<AnimationNode> clipb = EditorSettings::get_singleton()->get_resource_clipboard();
@@ -295,8 +295,8 @@ void AnimationNodeBlendSpace2DEditor::_add_menu_type(int p_index) {
open_file->clear_filters();
List<String> filters;
ResourceLoader::get_recognized_extensions_for_type("AnimationRootNode", &filters);
- for (List<String>::Element *E = filters.front(); E; E = E->next()) {
- open_file->add_filter("*." + E->get());
+ for (const String &E : filters) {
+ open_file->add_filter("*." + E);
}
open_file->popup_file_dialog();
return;
diff --git a/editor/plugins/animation_blend_tree_editor_plugin.cpp b/editor/plugins/animation_blend_tree_editor_plugin.cpp
index 7930bc1e1c..69206daea8 100644
--- a/editor/plugins/animation_blend_tree_editor_plugin.cpp
+++ b/editor/plugins/animation_blend_tree_editor_plugin.cpp
@@ -89,7 +89,7 @@ Size2 AnimationNodeBlendTreeEditor::get_minimum_size() const {
void AnimationNodeBlendTreeEditor::_property_changed(const StringName &p_property, const Variant &p_value, const String &p_field, bool p_changing) {
AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_tree();
updating = true;
- undo_redo->create_action(TTR("Parameter Changed") + ": " + String(p_property), UndoRedo::MERGE_ENDS);
+ undo_redo->create_action(TTR("Parameter Changed:") + " " + String(p_property), UndoRedo::MERGE_ENDS);
undo_redo->add_do_property(tree, p_property, p_value);
undo_redo->add_undo_property(tree, p_property, tree->get(p_property));
undo_redo->add_do_method(this, "_update_graph");
@@ -121,21 +121,21 @@ void AnimationNodeBlendTreeEditor::_update_graph() {
List<StringName> nodes;
blend_tree->get_node_list(&nodes);
- for (List<StringName>::Element *E = nodes.front(); E; E = E->next()) {
+ for (const StringName &E : nodes) {
GraphNode *node = memnew(GraphNode);
graph->add_child(node);
- Ref<AnimationNode> agnode = blend_tree->get_node(E->get());
+ Ref<AnimationNode> agnode = blend_tree->get_node(E);
- node->set_position_offset(blend_tree->get_node_position(E->get()) * EDSCALE);
+ node->set_position_offset(blend_tree->get_node_position(E) * EDSCALE);
node->set_title(agnode->get_caption());
- node->set_name(E->get());
+ node->set_name(E);
int base = 0;
- if (String(E->get()) != "output") {
+ if (String(E) != "output") {
LineEdit *name = memnew(LineEdit);
- name->set_text(E->get());
+ name->set_text(E);
name->set_expand_to_text_length_enabled(true);
node->add_child(name);
node->set_slot(0, false, 0, Color(), true, 0, get_theme_color(SNAME("font_color"), SNAME("Label")));
@@ -143,7 +143,7 @@ void AnimationNodeBlendTreeEditor::_update_graph() {
name->connect("focus_exited", callable_mp(this, &AnimationNodeBlendTreeEditor::_node_renamed_focus_out), varray(name, agnode), CONNECT_DEFERRED);
base = 1;
node->set_show_close_button(true);
- node->connect("close_request", callable_mp(this, &AnimationNodeBlendTreeEditor::_delete_request), varray(E->get()), CONNECT_DEFERRED);
+ node->connect("close_request", callable_mp(this, &AnimationNodeBlendTreeEditor::_delete_request), varray(E), CONNECT_DEFERRED);
}
for (int i = 0; i < agnode->get_input_count(); i++) {
@@ -155,12 +155,12 @@ void AnimationNodeBlendTreeEditor::_update_graph() {
List<PropertyInfo> pinfo;
agnode->get_parameter_list(&pinfo);
- for (List<PropertyInfo>::Element *F = pinfo.front(); F; F = F->next()) {
- if (!(F->get().usage & PROPERTY_USAGE_EDITOR)) {
+ for (const PropertyInfo &F : pinfo) {
+ if (!(F.usage & PROPERTY_USAGE_EDITOR)) {
continue;
}
- String base_path = AnimationTreeEditor::get_singleton()->get_base_path() + String(E->get()) + "/" + F->get().name;
- EditorProperty *prop = EditorInspector::instantiate_property_editor(AnimationTreeEditor::get_singleton()->get_tree(), F->get().type, base_path, F->get().hint, F->get().hint_string, F->get().usage);
+ String base_path = AnimationTreeEditor::get_singleton()->get_base_path() + String(E) + "/" + F.name;
+ EditorProperty *prop = EditorInspector::instantiate_property_editor(AnimationTreeEditor::get_singleton()->get_tree(), F.type, base_path, F.hint, F.hint_string, F.usage);
if (prop) {
prop->set_object_and_property(AnimationTreeEditor::get_singleton()->get_tree(), base_path);
prop->update_property();
@@ -171,7 +171,7 @@ void AnimationNodeBlendTreeEditor::_update_graph() {
}
}
- node->connect("dragged", callable_mp(this, &AnimationNodeBlendTreeEditor::_node_dragged), varray(E->get()));
+ node->connect("dragged", callable_mp(this, &AnimationNodeBlendTreeEditor::_node_dragged), varray(E));
if (AnimationTreeEditor::get_singleton()->can_edit(agnode)) {
node->add_child(memnew(HSeparator));
@@ -179,7 +179,7 @@ void AnimationNodeBlendTreeEditor::_update_graph() {
open_in_editor->set_text(TTR("Open Editor"));
open_in_editor->set_icon(get_theme_icon(SNAME("Edit"), SNAME("EditorIcons")));
node->add_child(open_in_editor);
- open_in_editor->connect("pressed", callable_mp(this, &AnimationNodeBlendTreeEditor::_open_in_editor), varray(E->get()), CONNECT_DEFERRED);
+ open_in_editor->connect("pressed", callable_mp(this, &AnimationNodeBlendTreeEditor::_open_in_editor), varray(E), CONNECT_DEFERRED);
open_in_editor->set_h_size_flags(SIZE_SHRINK_CENTER);
}
@@ -189,7 +189,7 @@ void AnimationNodeBlendTreeEditor::_update_graph() {
edit_filters->set_text(TTR("Edit Filters"));
edit_filters->set_icon(get_theme_icon(SNAME("AnimationFilter"), SNAME("EditorIcons")));
node->add_child(edit_filters);
- edit_filters->connect("pressed", callable_mp(this, &AnimationNodeBlendTreeEditor::_edit_filters), varray(E->get()), CONNECT_DEFERRED);
+ edit_filters->connect("pressed", callable_mp(this, &AnimationNodeBlendTreeEditor::_edit_filters), varray(E), CONNECT_DEFERRED);
edit_filters->set_h_size_flags(SIZE_SHRINK_CENTER);
}
@@ -212,9 +212,9 @@ void AnimationNodeBlendTreeEditor::_update_graph() {
List<StringName> anims;
ap->get_animation_list(&anims);
- for (List<StringName>::Element *F = anims.front(); F; F = F->next()) {
- mb->get_popup()->add_item(F->get());
- options.push_back(F->get());
+ for (const StringName &F : anims) {
+ mb->get_popup()->add_item(F);
+ options.push_back(F);
}
if (ap->has_animation(anim->get_animation())) {
@@ -225,10 +225,10 @@ void AnimationNodeBlendTreeEditor::_update_graph() {
pb->set_percent_visible(false);
pb->set_custom_minimum_size(Vector2(0, 14) * EDSCALE);
- animations[E->get()] = pb;
+ animations[E] = pb;
node->add_child(pb);
- mb->get_popup()->connect("index_pressed", callable_mp(this, &AnimationNodeBlendTreeEditor::_anim_selected), varray(options, E->get()), CONNECT_DEFERRED);
+ mb->get_popup()->connect("index_pressed", callable_mp(this, &AnimationNodeBlendTreeEditor::_anim_selected), varray(options, E), CONNECT_DEFERRED);
}
Ref<StyleBoxFlat> sb = node->get_theme_stylebox(SNAME("frame"), SNAME("GraphNode"));
@@ -246,10 +246,10 @@ void AnimationNodeBlendTreeEditor::_update_graph() {
List<AnimationNodeBlendTree::NodeConnection> connections;
blend_tree->get_node_connections(&connections);
- for (List<AnimationNodeBlendTree::NodeConnection>::Element *E = connections.front(); E; E = E->next()) {
- StringName from = E->get().output_node;
- StringName to = E->get().input_node;
- int to_idx = E->get().input_index;
+ for (const AnimationNodeBlendTree::NodeConnection &E : connections) {
+ StringName from = E.output_node;
+ StringName to = E.input_node;
+ int to_idx = E.input_index;
graph->connect_node(from, 0, to, to_idx);
}
@@ -274,8 +274,8 @@ void AnimationNodeBlendTreeEditor::_add_node(int p_idx) {
open_file->clear_filters();
List<String> filters;
ResourceLoader::get_recognized_extensions_for_type("AnimationNode", &filters);
- for (List<String>::Element *E = filters.front(); E; E = E->next()) {
- open_file->add_filter("*." + E->get());
+ for (const String &E : filters) {
+ open_file->add_filter("*." + E);
}
open_file->popup_file_dialog();
return;
@@ -394,9 +394,9 @@ void AnimationNodeBlendTreeEditor::_delete_request(const String &p_which) {
List<AnimationNodeBlendTree::NodeConnection> conns;
blend_tree->get_node_connections(&conns);
- for (List<AnimationNodeBlendTree::NodeConnection>::Element *E = conns.front(); E; E = E->next()) {
- if (E->get().output_node == p_which || E->get().input_node == p_which) {
- undo_redo->add_undo_method(blend_tree.ptr(), "connect_node", E->get().input_node, E->get().input_index, E->get().output_node);
+ for (const AnimationNodeBlendTree::NodeConnection &E : conns) {
+ if (E.output_node == p_which || E.input_node == p_which) {
+ undo_redo->add_undo_method(blend_tree.ptr(), "connect_node", E.input_node, E.input_index, E.output_node);
}
}
@@ -423,8 +423,8 @@ void AnimationNodeBlendTreeEditor::_delete_nodes_request() {
undo_redo->create_action(TTR("Delete Node(s)"));
- for (List<StringName>::Element *F = to_erase.front(); F; F = F->next()) {
- _delete_request(F->get());
+ for (const StringName &F : to_erase) {
+ _delete_request(F);
}
undo_redo->commit_action();
@@ -517,8 +517,8 @@ bool AnimationNodeBlendTreeEditor::_update_filters(const Ref<AnimationNode> &ano
List<StringName> animations;
player->get_animation_list(&animations);
- for (List<StringName>::Element *E = animations.front(); E; E = E->next()) {
- Ref<Animation> anim = player->get_animation(E->get());
+ for (const StringName &E : animations) {
+ Ref<Animation> anim = player->get_animation(E);
for (int i = 0; i < anim->get_track_count(); i++) {
String track_path = anim->track_get_path(i);
paths.insert(track_path);
@@ -718,13 +718,13 @@ void AnimationNodeBlendTreeEditor::_notification(int p_what) {
List<AnimationNodeBlendTree::NodeConnection> conns;
blend_tree->get_node_connections(&conns);
- for (List<AnimationNodeBlendTree::NodeConnection>::Element *E = conns.front(); E; E = E->next()) {
+ for (const AnimationNodeBlendTree::NodeConnection &E : conns) {
float activity = 0;
- StringName path = AnimationTreeEditor::get_singleton()->get_base_path() + E->get().input_node;
+ StringName path = AnimationTreeEditor::get_singleton()->get_base_path() + E.input_node;
if (AnimationTreeEditor::get_singleton()->get_tree() && !AnimationTreeEditor::get_singleton()->get_tree()->is_state_invalid()) {
- activity = AnimationTreeEditor::get_singleton()->get_tree()->get_connection_activity(path, E->get().input_index);
+ activity = AnimationTreeEditor::get_singleton()->get_tree()->get_connection_activity(path, E.input_index);
}
- graph->set_connection_activity(E->get().output_node, 0, E->get().input_node, E->get().input_index, activity);
+ graph->set_connection_activity(E.output_node, 0, E.input_node, E.input_index, activity);
}
AnimationTree *graph_player = AnimationTreeEditor::get_singleton()->get_tree();
@@ -741,7 +741,7 @@ void AnimationNodeBlendTreeEditor::_notification(int p_what) {
Ref<Animation> anim = player->get_animation(an->get_animation());
if (anim.is_valid()) {
E->get()->set_max(anim->get_length());
- //StringName path = AnimationTreeEditor::get_singleton()->get_base_path() + E->get().input_node;
+ //StringName path = AnimationTreeEditor::get_singleton()->get_base_path() + E.input_node;
StringName time_path = AnimationTreeEditor::get_singleton()->get_base_path() + String(E->key()) + "/time";
E->get()->set_value(AnimationTreeEditor::get_singleton()->get_tree()->get(time_path));
}
@@ -828,10 +828,10 @@ void AnimationNodeBlendTreeEditor::_node_renamed(const String &p_text, Ref<Anima
List<AnimationNodeBlendTree::NodeConnection> connections;
blend_tree->get_node_connections(&connections);
- for (List<AnimationNodeBlendTree::NodeConnection>::Element *E = connections.front(); E; E = E->next()) {
- StringName from = E->get().output_node;
- StringName to = E->get().input_node;
- int to_idx = E->get().input_index;
+ for (const AnimationNodeBlendTree::NodeConnection &E : connections) {
+ StringName from = E.output_node;
+ StringName to = E.input_node;
+ int to_idx = E.input_index;
graph->connect_node(from, 0, to, to_idx);
}
diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp
index cbce5bb3f5..681c3e7195 100644
--- a/editor/plugins/animation_player_editor_plugin.cpp
+++ b/editor/plugins/animation_player_editor_plugin.cpp
@@ -350,8 +350,8 @@ void AnimationPlayerEditor::_animation_load() {
List<String> extensions;
ResourceLoader::get_recognized_extensions_for_type("Animation", &extensions);
- for (List<String>::Element *E = extensions.front(); E; E = E->next()) {
- file->add_filter("*." + E->get() + " ; " + E->get().to_upper());
+ for (const String &E : extensions) {
+ file->add_filter("*." + E + " ; " + E.to_upper());
}
file->popup_file_dialog();
@@ -408,7 +408,8 @@ void AnimationPlayerEditor::_animation_save_as(const Ref<Resource> &p_resource)
if (p_resource->get_name() != "") {
path = p_resource->get_name() + "." + extensions.front()->get().to_lower();
} else {
- path = "new_" + p_resource->get_class().to_lower() + "." + extensions.front()->get().to_lower();
+ String resource_name_snake_case = p_resource->get_class().camelcase_to_underscore();
+ path = "new_" + resource_name_snake_case + "." + extensions.front()->get().to_lower();
}
}
}
@@ -584,8 +585,7 @@ void AnimationPlayerEditor::_animation_blend() {
blend_editor.next->clear();
blend_editor.next->add_item("", i);
- for (List<StringName>::Element *E = anims.front(); E; E = E->next()) {
- String to = E->get();
+ for (const StringName &to : anims) {
TreeItem *blend = blend_editor.tree->create_item(root);
blend->set_editable(0, false);
blend->set_editable(1, true);
@@ -830,20 +830,20 @@ void AnimationPlayerEditor::_update_player() {
}
int active_idx = -1;
- for (List<StringName>::Element *E = animlist.front(); E; E = E->next()) {
+ for (const StringName &E : animlist) {
Ref<Texture2D> icon;
- if (E->get() == player->get_autoplay()) {
- if (E->get() == "RESET") {
+ if (E == player->get_autoplay()) {
+ if (E == "RESET") {
icon = autoplay_reset_icon;
} else {
icon = autoplay_icon;
}
- } else if (E->get() == "RESET") {
+ } else if (E == "RESET") {
icon = reset_icon;
}
- animation->add_icon_item(icon, E->get());
+ animation->add_icon_item(icon, E);
- if (player->get_assigned_animation() == E->get()) {
+ if (player->get_assigned_animation() == E) {
active_idx = animation->get_item_count() - 1;
}
}
@@ -966,9 +966,9 @@ void AnimationPlayerEditor::_animation_duplicate() {
Ref<Animation> new_anim = memnew(Animation);
List<PropertyInfo> plist;
anim->get_property_list(&plist);
- for (List<PropertyInfo>::Element *E = plist.front(); E; E = E->next()) {
- if (E->get().usage & PROPERTY_USAGE_STORAGE) {
- new_anim->set(E->get().name, anim->get(E->get().name));
+ for (const PropertyInfo &E : plist) {
+ if (E.usage & PROPERTY_USAGE_STORAGE) {
+ new_anim->set(E.name, anim->get(E.name));
}
}
new_anim->set_path("");
diff --git a/editor/plugins/animation_state_machine_editor.cpp b/editor/plugins/animation_state_machine_editor.cpp
index 8abe20c3c9..a1f96f21bf 100644
--- a/editor/plugins/animation_state_machine_editor.cpp
+++ b/editor/plugins/animation_state_machine_editor.cpp
@@ -93,21 +93,21 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv
if (ap) {
List<StringName> names;
ap->get_animation_list(&names);
- for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
- animations_menu->add_icon_item(get_theme_icon(SNAME("Animation"), SNAME("EditorIcons")), E->get());
- animations_to_add.push_back(E->get());
+ for (const StringName &E : names) {
+ animations_menu->add_icon_item(get_theme_icon(SNAME("Animation"), SNAME("EditorIcons")), E);
+ animations_to_add.push_back(E);
}
}
}
- for (List<StringName>::Element *E = classes.front(); E; E = E->next()) {
- String name = String(E->get()).replace_first("AnimationNode", "");
+ for (const StringName &E : classes) {
+ String name = String(E).replace_first("AnimationNode", "");
if (name == "Animation") {
continue; // nope
}
int idx = menu->get_item_count();
menu->add_item(vformat("Add %s", name), idx);
- menu->set_item_metadata(idx, E->get());
+ menu->set_item_metadata(idx, E);
}
Ref<AnimationNode> clipb = EditorSettings::get_singleton()->get_resource_clipboard();
@@ -318,24 +318,24 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv
float best_d_x = 1e20;
float best_d_y = 1e20;
- for (List<StringName>::Element *E = nodes.front(); E; E = E->next()) {
- if (E->get() == selected_node) {
+ for (const StringName &E : nodes) {
+ if (E == selected_node) {
continue;
}
- Vector2 npos = state_machine->get_node_position(E->get());
+ Vector2 npos = state_machine->get_node_position(E);
float d_x = ABS(npos.x - cpos.x);
if (d_x < MIN(5, best_d_x)) {
drag_ofs.x -= cpos.x - npos.x;
best_d_x = d_x;
- snap_x = E->get();
+ snap_x = E;
}
float d_y = ABS(npos.y - cpos.y);
if (d_y < MIN(5, best_d_y)) {
drag_ofs.y -= cpos.y - npos.y;
best_d_y = d_y;
- snap_y = E->get();
+ snap_y = E;
}
}
}
@@ -409,8 +409,8 @@ void AnimationNodeStateMachineEditor::_add_menu_type(int p_index) {
open_file->clear_filters();
List<String> filters;
ResourceLoader::get_recognized_extensions_for_type("AnimationRootNode", &filters);
- for (List<String>::Element *E = filters.front(); E; E = E->next()) {
- open_file->add_filter("*." + E->get());
+ for (const String &E : filters) {
+ open_file->add_filter("*." + E);
}
open_file->popup_file_dialog();
return;
@@ -606,11 +606,11 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() {
}
//pre pass nodes so we know the rectangles
- for (List<StringName>::Element *E = nodes.front(); E; E = E->next()) {
- Ref<AnimationNode> anode = state_machine->get_node(E->get());
- String name = E->get();
+ for (const StringName &E : nodes) {
+ Ref<AnimationNode> anode = state_machine->get_node(E);
+ String name = E;
bool needs_editor = EditorNode::get_singleton()->item_has_editor(anode.ptr());
- Ref<StyleBox> sb = E->get() == selected_node ? style_selected : style;
+ Ref<StyleBox> sb = E == selected_node ? style_selected : style;
Size2 s = sb->get_minimum_size();
int strsize = font->get_string_size(name, font_size).width;
@@ -622,8 +622,8 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() {
}
Vector2 offset;
- offset += state_machine->get_node_position(E->get()) * EDSCALE;
- if (selected_node == E->get() && dragging_selected) {
+ offset += state_machine->get_node_position(E) * EDSCALE;
+ if (selected_node == E && dragging_selected) {
offset += drag_ofs;
}
offset -= s / 2;
@@ -633,7 +633,7 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() {
NodeRect nr;
nr.node = Rect2(offset, s);
- nr.node_name = E->get();
+ nr.node_name = E;
scroll_range = scroll_range.merge(nr.node); //merge with range
diff --git a/editor/plugins/animation_tree_editor_plugin.cpp b/editor/plugins/animation_tree_editor_plugin.cpp
index 9c28e023dd..cd84be0c25 100644
--- a/editor/plugins/animation_tree_editor_plugin.cpp
+++ b/editor/plugins/animation_tree_editor_plugin.cpp
@@ -215,8 +215,8 @@ Vector<String> AnimationTreeEditor::get_animation_list() {
List<StringName> anims;
ap->get_animation_list(&anims);
Vector<String> ret;
- for (List<StringName>::Element *E = anims.front(); E; E = E->next()) {
- ret.push_back(E->get());
+ for (const StringName &E : anims) {
+ ret.push_back(E);
}
return ret;
diff --git a/editor/plugins/asset_library_editor_plugin.cpp b/editor/plugins/asset_library_editor_plugin.cpp
index bbc8107cf6..785bab42cf 100644
--- a/editor/plugins/asset_library_editor_plugin.cpp
+++ b/editor/plugins/asset_library_editor_plugin.cpp
@@ -603,12 +603,11 @@ void EditorAssetLibrary::_notification(int p_what) {
void EditorAssetLibrary::_update_repository_options() {
Dictionary default_urls;
- default_urls["godotengine.org"] = "https://godotengine.org/asset-library/api";
- default_urls["localhost"] = "http://127.0.0.1/asset-library/api";
+ default_urls["godotengine.org (Official)"] = "https://godotengine.org/asset-library/api";
Dictionary available_urls = _EDITOR_DEF("asset_library/available_urls", default_urls, true);
repository->clear();
Array keys = available_urls.keys();
- for (int i = 0; i < available_urls.size(); i++) {
+ for (int i = 0; i < keys.size(); i++) {
String key = keys[i];
repository->add_item(key);
repository->set_item_metadata(i, available_urls[key]);
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index d5ef13e426..61d435cde1 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -81,8 +81,8 @@ public:
SnapDialog() {
const int SPIN_BOX_GRID_RANGE = 16384;
const int SPIN_BOX_ROTATION_RANGE = 360;
- const float SPIN_BOX_SCALE_MIN = 0.01f;
- const float SPIN_BOX_SCALE_MAX = 100;
+ const real_t SPIN_BOX_SCALE_MIN = 0.01;
+ const real_t SPIN_BOX_SCALE_MAX = 100;
Label *label;
VBoxContainer *container;
@@ -210,7 +210,7 @@ public:
child_container->add_child(scale_step);
}
- void set_fields(const Point2 p_grid_offset, const Point2 p_grid_step, const int p_primary_grid_steps, const float p_rotation_offset, const float p_rotation_step, const float p_scale_step) {
+ void set_fields(const Point2 p_grid_offset, const Point2 p_grid_step, const int p_primary_grid_steps, const real_t p_rotation_offset, const real_t p_rotation_step, const real_t p_scale_step) {
grid_offset_x->set_value(p_grid_offset.x);
grid_offset_y->set_value(p_grid_offset.y);
grid_step_x->set_value(p_grid_step.x);
@@ -221,7 +221,7 @@ public:
scale_step->set_value(p_scale_step);
}
- void get_fields(Point2 &p_grid_offset, Point2 &p_grid_step, int &p_primary_grid_steps, float &p_rotation_offset, float &p_rotation_step, float &p_scale_step) {
+ void get_fields(Point2 &p_grid_offset, Point2 &p_grid_step, int &p_primary_grid_steps, real_t &p_rotation_offset, real_t &p_rotation_step, real_t &p_scale_step) {
p_grid_offset = Point2(grid_offset_x->get_value(), grid_offset_y->get_value());
p_grid_step = Point2(grid_step_x->get_value(), grid_step_y->get_value());
p_primary_grid_steps = int(primary_grid_steps->get_value());
@@ -249,12 +249,12 @@ bool CanvasItemEditor::_is_node_movable(const Node *p_node, bool p_popup_warning
}
void CanvasItemEditor::_snap_if_closer_float(
- float p_value,
- float &r_current_snap, SnapTarget &r_current_snap_target,
- float p_target_value, SnapTarget p_snap_target,
- float p_radius) {
- float radius = p_radius / zoom;
- float dist = Math::abs(p_value - p_target_value);
+ const real_t p_value,
+ real_t &r_current_snap, SnapTarget &r_current_snap_target,
+ const real_t p_target_value, const SnapTarget p_snap_target,
+ const real_t p_radius) {
+ const real_t radius = p_radius / zoom;
+ const real_t dist = Math::abs(p_value - p_target_value);
if ((p_radius < 0 || dist < radius) && (r_current_snap_target == SNAP_TARGET_NONE || dist < Math::abs(r_current_snap - p_value))) {
r_current_snap = p_target_value;
r_current_snap_target = p_snap_target;
@@ -264,9 +264,9 @@ void CanvasItemEditor::_snap_if_closer_float(
void CanvasItemEditor::_snap_if_closer_point(
Point2 p_value,
Point2 &r_current_snap, SnapTarget (&r_current_snap_target)[2],
- Point2 p_target_value, SnapTarget p_snap_target,
- real_t rotation,
- float p_radius) {
+ Point2 p_target_value, const SnapTarget p_snap_target,
+ const real_t rotation,
+ const real_t p_radius) {
Transform2D rot_trans = Transform2D(rotation, Point2());
p_value = rot_trans.inverse().xform(p_value);
p_target_value = rot_trans.inverse().xform(p_target_value);
@@ -301,8 +301,8 @@ void CanvasItemEditor::_snap_other_nodes(
// Check if the element is in the exception
bool exception = false;
- for (List<const CanvasItem *>::Element *E = p_exceptions.front(); E; E = E->next()) {
- if (E->get() == p_current) {
+ for (const CanvasItem *&E : p_exceptions) {
+ if (E == p_current) {
exception = true;
break;
}
@@ -399,8 +399,8 @@ Point2 CanvasItemEditor::snap_point(Point2 p_target, unsigned int p_modes, unsig
if ((is_snap_active && snap_other_nodes && (p_modes & SNAP_OTHER_NODES)) || (p_forced_modes & SNAP_OTHER_NODES)) {
Transform2D to_snap_transform = Transform2D();
List<const CanvasItem *> exceptions = List<const CanvasItem *>();
- for (List<CanvasItem *>::Element *E = p_other_nodes_exceptions.front(); E; E = E->next()) {
- exceptions.push_back(E->get());
+ for (const CanvasItem *E : p_other_nodes_exceptions) {
+ exceptions.push_back(E);
}
if (p_self_canvas_item) {
exceptions.push_back(p_self_canvas_item);
@@ -459,7 +459,7 @@ Point2 CanvasItemEditor::snap_point(Point2 p_target, unsigned int p_modes, unsig
return output;
}
-float CanvasItemEditor::snap_angle(float p_target, float p_start) const {
+real_t CanvasItemEditor::snap_angle(real_t p_target, real_t p_start) const {
if (((smart_snap_active || snap_rotation) ^ Input::get_singleton()->is_key_pressed(KEY_CTRL)) && snap_rotation_step != 0) {
if (snap_relative) {
return Math::snapped(p_target - snap_rotation_offset, snap_rotation_step) + snap_rotation_offset + (p_start - (int)(p_start / snap_rotation_step) * snap_rotation_step);
@@ -527,8 +527,7 @@ Rect2 CanvasItemEditor::_get_encompassing_rect_from_list(List<CanvasItem *> p_li
Rect2 rect = Rect2(canvas_item->get_global_transform_with_canvas().xform(canvas_item->_edit_get_rect().position + canvas_item->_edit_get_rect().size / 2), Size2());
// Expand with the other ones
- for (List<CanvasItem *>::Element *E = p_list.front(); E; E = E->next()) {
- CanvasItem *canvas_item2 = E->get();
+ for (CanvasItem *canvas_item2 : p_list) {
Transform2D xform = canvas_item2->get_global_transform_with_canvas();
Rect2 current_rect = canvas_item2->_edit_get_rect();
@@ -760,9 +759,9 @@ List<CanvasItem *> CanvasItemEditor::_get_edited_canvas_items(bool retreive_lock
if (remove_canvas_item_if_parent_in_selection) {
List<CanvasItem *> filtered_selection;
- for (List<CanvasItem *>::Element *E = selection.front(); E; E = E->next()) {
- if (!selection.find(E->get()->get_parent())) {
- filtered_selection.push_back(E->get());
+ for (CanvasItem *E : selection) {
+ if (!selection.find(E->get_parent())) {
+ filtered_selection.push_back(E);
}
}
return filtered_selection;
@@ -800,8 +799,7 @@ Vector2 CanvasItemEditor::_position_to_anchor(const Control *p_control, Vector2
}
void CanvasItemEditor::_save_canvas_item_state(List<CanvasItem *> p_canvas_items, bool save_bones) {
- for (List<CanvasItem *>::Element *E = p_canvas_items.front(); E; E = E->next()) {
- CanvasItem *canvas_item = E->get();
+ for (CanvasItem *canvas_item : p_canvas_items) {
CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(canvas_item);
if (se) {
se->undo_state = canvas_item->_edit_get_state();
@@ -816,8 +814,7 @@ void CanvasItemEditor::_save_canvas_item_state(List<CanvasItem *> p_canvas_items
}
void CanvasItemEditor::_restore_canvas_item_state(List<CanvasItem *> p_canvas_items, bool restore_bones) {
- for (List<CanvasItem *>::Element *E = drag_selection.front(); E; E = E->next()) {
- CanvasItem *canvas_item = E->get();
+ for (CanvasItem *canvas_item : drag_selection) {
CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(canvas_item);
canvas_item->_edit_set_state(se->undo_state);
}
@@ -825,8 +822,7 @@ void CanvasItemEditor::_restore_canvas_item_state(List<CanvasItem *> p_canvas_it
void CanvasItemEditor::_commit_canvas_item_state(List<CanvasItem *> p_canvas_items, String action_name, bool commit_bones) {
List<CanvasItem *> modified_canvas_items;
- for (List<CanvasItem *>::Element *E = p_canvas_items.front(); E; E = E->next()) {
- CanvasItem *canvas_item = E->get();
+ for (CanvasItem *canvas_item : p_canvas_items) {
Dictionary old_state = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(canvas_item)->undo_state;
Dictionary new_state = canvas_item->_edit_get_state();
@@ -840,17 +836,16 @@ void CanvasItemEditor::_commit_canvas_item_state(List<CanvasItem *> p_canvas_ite
}
undo_redo->create_action(action_name);
- for (List<CanvasItem *>::Element *E = modified_canvas_items.front(); E; E = E->next()) {
- CanvasItem *canvas_item = E->get();
+ for (CanvasItem *canvas_item : modified_canvas_items) {
CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(canvas_item);
if (se) {
undo_redo->add_do_method(canvas_item, "_edit_set_state", canvas_item->_edit_get_state());
undo_redo->add_undo_method(canvas_item, "_edit_set_state", se->undo_state);
if (commit_bones) {
- for (List<Dictionary>::Element *F = se->pre_drag_bones_undo_state.front(); F; F = F->next()) {
+ for (const Dictionary &F : se->pre_drag_bones_undo_state) {
canvas_item = Object::cast_to<CanvasItem>(canvas_item->get_parent());
undo_redo->add_do_method(canvas_item, "_edit_set_state", canvas_item->_edit_get_state());
- undo_redo->add_undo_method(canvas_item, "_edit_set_state", F->get());
+ undo_redo->add_undo_method(canvas_item, "_edit_set_state", F);
}
}
}
@@ -928,7 +923,7 @@ bool CanvasItemEditor::_gui_input_rulers_and_guides(const Ref<InputEvent> &p_eve
}
// Hover over guides
- float minimum = 1e20;
+ real_t minimum = 1e20;
is_hovering_h_guide = false;
is_hovering_v_guide = false;
@@ -1269,7 +1264,7 @@ bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bo
if (pan_gesture.is_valid() && !p_already_accepted) {
// If ctrl key pressed, then zoom instead of pan.
if (pan_gesture->is_ctrl_pressed()) {
- const float factor = pan_gesture->get_delta().y;
+ const real_t factor = pan_gesture->get_delta().y;
zoom_widget->set_zoom_by_increments(1);
if (factor != 1.f) {
@@ -1304,8 +1299,7 @@ bool CanvasItemEditor::_gui_input_pivot(const Ref<InputEvent> &p_event) {
// Filters the selection with nodes that allow setting the pivot
drag_selection = List<CanvasItem *>();
- for (List<CanvasItem *>::Element *E = selection.front(); E; E = E->next()) {
- CanvasItem *canvas_item = E->get();
+ for (CanvasItem *canvas_item : selection) {
if (canvas_item->_edit_use_pivot()) {
drag_selection.push_back(canvas_item);
}
@@ -1321,8 +1315,7 @@ bool CanvasItemEditor::_gui_input_pivot(const Ref<InputEvent> &p_event) {
} else {
new_pos = snap_point(drag_from, SNAP_OTHER_NODES | SNAP_GRID | SNAP_PIXEL, 0, nullptr, drag_selection);
}
- for (List<CanvasItem *>::Element *E = drag_selection.front(); E; E = E->next()) {
- CanvasItem *canvas_item = E->get();
+ for (CanvasItem *canvas_item : drag_selection) {
canvas_item->_edit_set_pivot(canvas_item->get_global_transform_with_canvas().affine_inverse().xform(new_pos));
}
@@ -1343,8 +1336,7 @@ bool CanvasItemEditor::_gui_input_pivot(const Ref<InputEvent> &p_event) {
} else {
new_pos = snap_point(drag_to, SNAP_OTHER_NODES | SNAP_GRID | SNAP_PIXEL);
}
- for (List<CanvasItem *>::Element *E = drag_selection.front(); E; E = E->next()) {
- CanvasItem *canvas_item = E->get();
+ for (CanvasItem *canvas_item : drag_selection) {
canvas_item->_edit_set_pivot(canvas_item->get_global_transform_with_canvas().affine_inverse().xform(new_pos));
}
return true;
@@ -1387,8 +1379,8 @@ bool CanvasItemEditor::_gui_input_rotate(const Ref<InputEvent> &p_event) {
List<CanvasItem *> selection = _get_edited_canvas_items();
// Remove not movable nodes
- for (List<CanvasItem *>::Element *E = selection.front(); E; E = E->next()) {
- if (!_is_node_movable(E->get(), true)) {
+ for (CanvasItem *E : selection) {
+ if (!_is_node_movable(E, true)) {
selection.erase(E);
}
}
@@ -1414,8 +1406,7 @@ bool CanvasItemEditor::_gui_input_rotate(const Ref<InputEvent> &p_event) {
// Rotate the node
if (m.is_valid()) {
_restore_canvas_item_state(drag_selection);
- for (List<CanvasItem *>::Element *E = drag_selection.front(); E; E = E->next()) {
- CanvasItem *canvas_item = E->get();
+ for (CanvasItem *canvas_item : drag_selection) {
drag_to = transform.affine_inverse().xform(m->get_position());
//Rotate the opposite way if the canvas item's compounded scale has an uneven number of negative elements
bool opposite = (canvas_item->get_global_transform().get_scale().sign().dot(canvas_item->get_transform().get_scale().sign()) == 0);
@@ -1499,9 +1490,9 @@ bool CanvasItemEditor::_gui_input_anchors(const Ref<InputEvent> &p_event) {
anchor_pos[i] = (transform * control->get_global_transform_with_canvas()).xform(_anchor_to_position(control, anchor_pos[i]));
anchor_rects[i] = Rect2(anchor_pos[i], anchor_handle->get_size());
if (control->is_layout_rtl()) {
- anchor_rects[i].position -= anchor_handle->get_size() * Vector2(float(i == 1 || i == 2), float(i <= 1));
+ anchor_rects[i].position -= anchor_handle->get_size() * Vector2(real_t(i == 1 || i == 2), real_t(i <= 1));
} else {
- anchor_rects[i].position -= anchor_handle->get_size() * Vector2(float(i == 0 || i == 3), float(i <= 1));
+ anchor_rects[i].position -= anchor_handle->get_size() * Vector2(real_t(i == 0 || i == 3), real_t(i <= 1));
}
}
@@ -1655,7 +1646,7 @@ bool CanvasItemEditor::_gui_input_resize(const Ref<InputEvent> &p_event) {
};
DragType resize_drag = DRAG_NONE;
- float radius = (select_handle->get_size().width / 2) * 1.5;
+ real_t radius = (select_handle->get_size().width / 2) * 1.5;
for (int i = 0; i < 4; i++) {
int prev = (i + 3) % 4;
@@ -1701,7 +1692,7 @@ bool CanvasItemEditor::_gui_input_resize(const Ref<InputEvent> &p_event) {
bool symmetric = m->is_alt_pressed();
Rect2 local_rect = canvas_item->_edit_get_rect();
- float aspect = local_rect.get_size().y / local_rect.get_size().x;
+ real_t aspect = local_rect.get_size().y / local_rect.get_size().x;
Point2 current_begin = local_rect.get_position();
Point2 current_end = local_rect.get_position() + local_rect.get_size();
Point2 max_begin = (symmetric) ? (current_begin + current_end - canvas_item->_edit_get_minimum_size()) / 2.0 : current_end - canvas_item->_edit_get_minimum_size();
@@ -1892,7 +1883,7 @@ bool CanvasItemEditor::_gui_input_scale(const Ref<InputEvent> &p_event) {
Size2 scale = canvas_item->call("get_scale");
Size2 original_scale = scale;
- float ratio = scale.y / scale.x;
+ real_t ratio = scale.y / scale.x;
if (drag_type == DRAG_SCALE_BOTH) {
Size2 scale_factor = drag_to_local / drag_from_local;
if (uniform) {
@@ -2021,11 +2012,13 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) {
drag_to = transform.affine_inverse().xform(m->get_position());
Point2 previous_pos;
- if (drag_selection.size() == 1) {
- Transform2D xform = drag_selection[0]->get_global_transform_with_canvas() * drag_selection[0]->get_transform().affine_inverse();
- previous_pos = xform.xform(drag_selection[0]->_edit_get_position());
- } else {
- previous_pos = _get_encompassing_rect_from_list(drag_selection).position;
+ if (!drag_selection.is_empty()) {
+ if (drag_selection.size() == 1) {
+ Transform2D xform = drag_selection[0]->get_global_transform_with_canvas() * drag_selection[0]->get_transform().affine_inverse();
+ previous_pos = xform.xform(drag_selection[0]->_edit_get_position());
+ } else {
+ previous_pos = _get_encompassing_rect_from_list(drag_selection).position;
+ }
}
Point2 new_pos = snap_point(previous_pos + (drag_to - drag_from), SNAP_GRID | SNAP_GUIDES | SNAP_PIXEL | SNAP_NODE_PARENT | SNAP_NODE_ANCHORS | SNAP_OTHER_NODES, 0, nullptr, drag_selection);
@@ -2046,8 +2039,7 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) {
}
int index = 0;
- for (List<CanvasItem *>::Element *E = drag_selection.front(); E; E = E->next()) {
- CanvasItem *canvas_item = E->get();
+ for (CanvasItem *canvas_item : drag_selection) {
Transform2D xform = canvas_item->get_global_transform_with_canvas().affine_inverse() * canvas_item->get_transform();
canvas_item->_edit_set_position(canvas_item->_edit_get_position() + xform.xform(new_pos) - xform.xform(previous_pos));
@@ -2162,8 +2154,7 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) {
}
int index = 0;
- for (List<CanvasItem *>::Element *E = drag_selection.front(); E; E = E->next()) {
- CanvasItem *canvas_item = E->get();
+ for (CanvasItem *canvas_item : drag_selection) {
Transform2D xform = canvas_item->get_global_transform_with_canvas().affine_inverse() * canvas_item->get_transform();
canvas_item->_edit_set_position(canvas_item->_edit_get_position() + xform.xform(new_pos) - xform.xform(previous_pos));
@@ -2375,8 +2366,8 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) {
if (selitems.size() == 1 && editor_selection->get_selected_node_list().is_empty()) {
editor->push_item(selitems[0]);
}
- for (List<CanvasItem *>::Element *E = selitems.front(); E; E = E->next()) {
- editor_selection->add_node(E->get());
+ for (CanvasItem *E : selitems) {
+ editor_selection->add_node(E);
}
}
@@ -2550,7 +2541,7 @@ void CanvasItemEditor::_update_cursor() {
List<CanvasItem *> selection = _get_edited_canvas_items();
if (selection.size() == 1) {
- float angle = Math::fposmod((double)selection[0]->get_global_transform_with_canvas().get_rotation(), Math_PI);
+ const double angle = Math::fposmod((double)selection[0]->get_global_transform_with_canvas().get_rotation(), Math_PI);
if (angle > Math_PI * 7.0 / 8.0) {
rotation_array_index = 0;
} else if (angle > Math_PI * 5.0 / 8.0) {
@@ -2656,7 +2647,7 @@ void CanvasItemEditor::_draw_margin_at_position(int p_value, Point2 p_position,
}
}
-void CanvasItemEditor::_draw_percentage_at_position(float p_value, Point2 p_position, Side p_side) {
+void CanvasItemEditor::_draw_percentage_at_position(real_t p_value, Point2 p_position, Side p_side) {
String str = TS->format_number(vformat("%.1f ", p_value * 100.0)) + TS->percent_sign();
if (p_value != 0) {
_draw_text_at_position(p_position, str, p_side);
@@ -2666,7 +2657,7 @@ void CanvasItemEditor::_draw_percentage_at_position(float p_value, Point2 p_posi
void CanvasItemEditor::_draw_focus() {
// Draw the focus around the base viewport
if (viewport->has_focus()) {
- get_theme_stylebox(SNAME("Focus"), SNAME("EditorStyles"))->draw(viewport->get_canvas_item(), Rect2(Point2(), viewport->get_size()));
+ get_theme_stylebox(SNAME("FocusViewport"), SNAME("EditorStyles"))->draw(viewport->get_canvas_item(), Rect2(Point2(), viewport->get_size()));
}
}
@@ -2681,7 +2672,7 @@ void CanvasItemEditor::_draw_guides() {
if (drag_type == DRAG_V_GUIDE && i == dragged_guide_index) {
continue;
}
- float x = xform.xform(Point2(vguides[i], 0)).x;
+ real_t x = xform.xform(Point2(vguides[i], 0)).x;
viewport->draw_line(Point2(x, 0), Point2(x, viewport->get_size().y), guide_color, Math::round(EDSCALE));
}
}
@@ -2692,7 +2683,7 @@ void CanvasItemEditor::_draw_guides() {
if (drag_type == DRAG_H_GUIDE && i == dragged_guide_index) {
continue;
}
- float y = xform.xform(Point2(0, hguides[i])).y;
+ real_t y = xform.xform(Point2(0, hguides[i])).y;
viewport->draw_line(Point2(0, y), Point2(viewport->get_size().x, y), guide_color, Math::round(EDSCALE));
}
}
@@ -2756,7 +2747,7 @@ void CanvasItemEditor::_draw_rulers() {
ruler_transform.scale_basis(Point2(2, 2));
}
} else {
- float basic_rule = 100;
+ real_t basic_rule = 100;
for (int i = 0; basic_rule * zoom > 100; i++) {
basic_rule /= (i % 2) ? 5.0 : 2.0;
}
@@ -2785,7 +2776,7 @@ void CanvasItemEditor::_draw_rulers() {
Point2 position = (transform * ruler_transform * major_subdivide * minor_subdivide).xform(Point2(i, 0)).round();
if (i % (major_subdivision * minor_subdivision) == 0) {
viewport->draw_line(Point2(position.x, 0), Point2(position.x, RULER_WIDTH), graduation_color, Math::round(EDSCALE));
- float val = (ruler_transform * major_subdivide * minor_subdivide).xform(Point2(i, 0)).x;
+ real_t val = (ruler_transform * major_subdivide * minor_subdivide).xform(Point2(i, 0)).x;
viewport->draw_string(font, Point2(position.x + 2, font->get_height(font_size)), TS->format_number(vformat(((int)val == val) ? "%d" : "%.1f", val)), HALIGN_LEFT, -1, font_size, font_color);
} else {
if (i % minor_subdivision == 0) {
@@ -2802,7 +2793,7 @@ void CanvasItemEditor::_draw_rulers() {
Point2 position = (transform * ruler_transform * major_subdivide * minor_subdivide).xform(Point2(0, i)).round();
if (i % (major_subdivision * minor_subdivision) == 0) {
viewport->draw_line(Point2(0, position.y), Point2(RULER_WIDTH, position.y), graduation_color, Math::round(EDSCALE));
- float val = (ruler_transform * major_subdivide * minor_subdivide).xform(Point2(0, i)).y;
+ real_t val = (ruler_transform * major_subdivide * minor_subdivide).xform(Point2(0, i)).y;
Transform2D text_xform = Transform2D(-Math_PI / 2.0, Point2(font->get_height(font_size), position.y - 2));
viewport->draw_set_transform_matrix(viewport->get_transform() * text_xform);
@@ -2932,26 +2923,26 @@ void CanvasItemEditor::_draw_ruler_tool() {
Point2 text_pos = (begin + end) / 2 - Vector2(text_width / 2, text_height / 2);
text_pos.x = CLAMP(text_pos.x, text_width / 2, viewport->get_rect().size.x - text_width * 1.5);
text_pos.y = CLAMP(text_pos.y, text_height * 1.5, viewport->get_rect().size.y - text_height * 1.5);
- viewport->draw_string(font, text_pos, TS->format_number(vformat("%.2f " + TTR("px"), length_vector.length())), HALIGN_LEFT, -1, font_size, font_color, outline_size, outline_color);
+ viewport->draw_string(font, text_pos, TS->format_number(vformat("%.1f px", length_vector.length())), HALIGN_LEFT, -1, font_size, font_color, outline_size, outline_color);
if (draw_secondary_lines) {
- const float horizontal_angle_rad = atan2(length_vector.y, length_vector.x);
- const float vertical_angle_rad = Math_PI / 2.0 - horizontal_angle_rad;
+ const real_t horizontal_angle_rad = atan2(length_vector.y, length_vector.x);
+ const real_t vertical_angle_rad = Math_PI / 2.0 - horizontal_angle_rad;
const int horizontal_angle = round(180 * horizontal_angle_rad / Math_PI);
const int vertical_angle = round(180 * vertical_angle_rad / Math_PI);
Point2 text_pos2 = text_pos;
text_pos2.x = begin.x < text_pos.x ? MIN(text_pos.x - text_width, begin.x - text_width / 2) : MAX(text_pos.x + text_width, begin.x - text_width / 2);
- viewport->draw_string(font, text_pos2, TS->format_number(vformat("%.2f " + TTR("px"), length_vector.y)), HALIGN_LEFT, -1, font_size, font_secondary_color, outline_size, outline_color);
+ viewport->draw_string(font, text_pos2, TS->format_number(vformat("%.1f px", length_vector.y)), HALIGN_LEFT, -1, font_size, font_secondary_color, outline_size, outline_color);
Point2 v_angle_text_pos = Point2();
v_angle_text_pos.x = CLAMP(begin.x - angle_text_width / 2, angle_text_width / 2, viewport->get_rect().size.x - angle_text_width);
v_angle_text_pos.y = begin.y < end.y ? MIN(text_pos2.y - 2 * text_height, begin.y - text_height * 0.5) : MAX(text_pos2.y + text_height * 3, begin.y + text_height * 1.5);
- viewport->draw_string(font, v_angle_text_pos, TS->format_number(vformat("%d " + TTR("deg"), vertical_angle)), HALIGN_LEFT, -1, font_size, font_secondary_color, outline_size, outline_color);
+ viewport->draw_string(font, v_angle_text_pos, TS->format_number(vformat(String::utf8("%d°"), vertical_angle)), HALIGN_LEFT, -1, font_size, font_secondary_color, outline_size, outline_color);
text_pos2 = text_pos;
text_pos2.y = end.y < text_pos.y ? MIN(text_pos.y - text_height * 2, end.y - text_height / 2) : MAX(text_pos.y + text_height * 2, end.y - text_height / 2);
- viewport->draw_string(font, text_pos2, TS->format_number(vformat("%.2f " + TTR("px"), length_vector.x)), HALIGN_LEFT, -1, font_size, font_secondary_color, outline_size, outline_color);
+ viewport->draw_string(font, text_pos2, TS->format_number(vformat("%.1f px", length_vector.x)), HALIGN_LEFT, -1, font_size, font_secondary_color, outline_size, outline_color);
Point2 h_angle_text_pos = Point2();
h_angle_text_pos.x = CLAMP(end.x - angle_text_width / 2, angle_text_width / 2, viewport->get_rect().size.x - angle_text_width);
@@ -2968,32 +2959,32 @@ void CanvasItemEditor::_draw_ruler_tool() {
h_angle_text_pos.y = MIN(text_pos.y - height_multiplier * text_height, MIN(end.y - text_height * 0.5, text_pos2.y - height_multiplier * text_height));
}
}
- viewport->draw_string(font, h_angle_text_pos, TS->format_number(vformat("%d " + TTR("deg"), horizontal_angle)), HALIGN_LEFT, -1, font_size, font_secondary_color, outline_size, outline_color);
+ viewport->draw_string(font, h_angle_text_pos, TS->format_number(vformat(String::utf8("%d°"), horizontal_angle)), HALIGN_LEFT, -1, font_size, font_secondary_color, outline_size, outline_color);
// Angle arcs
int arc_point_count = 8;
- float arc_radius_max_length_percent = 0.1;
- float ruler_length = length_vector.length() * zoom;
- float arc_max_radius = 50.0;
- float arc_line_width = 2.0;
+ real_t arc_radius_max_length_percent = 0.1;
+ real_t ruler_length = length_vector.length() * zoom;
+ real_t arc_max_radius = 50.0;
+ real_t arc_line_width = 2.0;
const Vector2 end_to_begin = (end - begin);
- float arc_1_start_angle =
+ real_t arc_1_start_angle =
end_to_begin.x < 0 ?
(end_to_begin.y < 0 ? 3.0 * Math_PI / 2.0 - vertical_angle_rad : Math_PI / 2.0) :
(end_to_begin.y < 0 ? 3.0 * Math_PI / 2.0 : Math_PI / 2.0 - vertical_angle_rad);
- float arc_1_end_angle = arc_1_start_angle + vertical_angle_rad;
+ real_t arc_1_end_angle = arc_1_start_angle + vertical_angle_rad;
// Constrain arc to triangle height & max size
- float arc_1_radius = MIN(MIN(arc_radius_max_length_percent * ruler_length, ABS(end_to_begin.y)), arc_max_radius);
+ real_t arc_1_radius = MIN(MIN(arc_radius_max_length_percent * ruler_length, ABS(end_to_begin.y)), arc_max_radius);
- float arc_2_start_angle =
+ real_t arc_2_start_angle =
end_to_begin.x < 0 ?
(end_to_begin.y < 0 ? 0.0 : -horizontal_angle_rad) :
(end_to_begin.y < 0 ? Math_PI - horizontal_angle_rad : Math_PI);
- float arc_2_end_angle = arc_2_start_angle + horizontal_angle_rad;
+ real_t arc_2_end_angle = arc_2_start_angle + horizontal_angle_rad;
// Constrain arc to triangle width & max size
- float arc_2_radius = MIN(MIN(arc_radius_max_length_percent * ruler_length, ABS(end_to_begin.x)), arc_max_radius);
+ real_t arc_2_radius = MIN(MIN(arc_radius_max_length_percent * ruler_length, ABS(end_to_begin.x)), arc_max_radius);
viewport->draw_arc(begin, arc_1_radius, arc_1_start_angle, arc_1_end_angle, arc_point_count, ruler_primary_color, Math::round(EDSCALE * arc_line_width));
viewport->draw_arc(end, arc_2_radius, arc_2_start_angle, arc_2_end_angle, arc_point_count, ruler_primary_color, Math::round(EDSCALE * arc_line_width));
@@ -3031,7 +3022,7 @@ void CanvasItemEditor::_draw_control_anchors(Control *control) {
RID ci = viewport->get_canvas_item();
if (tool == TOOL_SELECT && !Object::cast_to<Container>(control->get_parent())) {
// Compute the anchors
- float anchors_values[4];
+ real_t anchors_values[4];
anchors_values[0] = control->get_anchor(SIDE_LEFT);
anchors_values[1] = control->get_anchor(SIDE_TOP);
anchors_values[2] = control->get_anchor(SIDE_RIGHT);
@@ -3070,7 +3061,7 @@ void CanvasItemEditor::_draw_control_helpers(Control *control) {
Color color_base = Color(0.8, 0.8, 0.8, 0.5);
// Compute the anchors
- float anchors_values[4];
+ real_t anchors_values[4];
anchors_values[0] = control->get_anchor(SIDE_LEFT);
anchors_values[1] = control->get_anchor(SIDE_TOP);
anchors_values[2] = control->get_anchor(SIDE_RIGHT);
@@ -3116,7 +3107,7 @@ void CanvasItemEditor::_draw_control_helpers(Control *control) {
Vector2 line_starts[4];
Vector2 line_ends[4];
for (int i = 0; i < 4; i++) {
- float anchor_val = (i >= 2) ? ANCHOR_END - anchors_values[i] : anchors_values[i];
+ real_t anchor_val = (i >= 2) ? ANCHOR_END - anchors_values[i] : anchors_values[i];
line_starts[i] = corners_pos[i].lerp(corners_pos[(i + 1) % 4], anchor_val);
line_ends[i] = corners_pos[(i + 3) % 4].lerp(corners_pos[(i + 2) % 4], anchor_val);
anchor_snapped = anchors_values[i] == 0.0 || anchors_values[i] == 0.5 || anchors_values[i] == 1.0;
@@ -3124,7 +3115,7 @@ void CanvasItemEditor::_draw_control_helpers(Control *control) {
}
// Display the percentages next to the lines
- float percent_val;
+ real_t percent_val;
percent_val = anchors_values[(dragged_anchor + 2) % 4] - anchors_values[dragged_anchor];
percent_val = (dragged_anchor >= 2) ? -percent_val : percent_val;
_draw_percentage_at_position(percent_val, (anchors_pos[dragged_anchor] + anchors_pos[(dragged_anchor + 1) % 4]) / 2, (Side)((dragged_anchor + 1) % 4));
@@ -3143,9 +3134,9 @@ void CanvasItemEditor::_draw_control_helpers(Control *control) {
}
// Draw the margin values and the node width/height when dragging control side
- float ratio = 0.33;
+ const real_t ratio = 0.33;
Transform2D parent_transform = xform * control->get_transform().affine_inverse();
- float node_pos_in_parent[4];
+ real_t node_pos_in_parent[4];
Rect2 parent_rect = control->get_parent_anchorable_rect();
@@ -3248,8 +3239,8 @@ void CanvasItemEditor::_draw_selection() {
List<CanvasItem *> selection = _get_edited_canvas_items(true, false);
bool single = selection.size() == 1;
- for (List<CanvasItem *>::Element *E = selection.front(); E; E = E->next()) {
- CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E->get());
+ for (CanvasItem *E : selection) {
+ CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E);
CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(canvas_item);
bool item_locked = canvas_item->has_meta("_edit_lock_");
@@ -3462,10 +3453,10 @@ void CanvasItemEditor::_draw_straight_line(Point2 p_from, Point2 p_to, Color p_c
points.push_back(Point2(0, to.y));
points.push_back(Point2(viewport_size.x, to.y));
} else {
- float y_for_zero_x = (to.y * from.x - from.y * to.x) / (from.x - to.x);
- float x_for_zero_y = (to.x * from.y - from.x * to.y) / (from.y - to.y);
- float y_for_viewport_x = ((to.y - from.y) * (viewport_size.x - from.x)) / (to.x - from.x) + from.y;
- float x_for_viewport_y = ((to.x - from.x) * (viewport_size.y - from.y)) / (to.y - from.y) + from.x; // faux
+ real_t y_for_zero_x = (to.y * from.x - from.y * to.x) / (from.x - to.x);
+ real_t x_for_zero_y = (to.x * from.y - from.x * to.y) / (from.y - to.y);
+ real_t y_for_viewport_x = ((to.y - from.y) * (viewport_size.x - from.x)) / (to.x - from.x) + from.y;
+ real_t x_for_viewport_y = ((to.x - from.x) * (viewport_size.y - from.y)) / (to.y - from.y) + from.x; // faux
//bool start_set = false;
if (y_for_zero_x >= 0 && y_for_zero_x <= viewport_size.y) {
@@ -3566,9 +3557,9 @@ void CanvasItemEditor::_draw_hover() {
Point2 pos = transform.xform(hovering_results[i].position) - Point2(0, item_size.y) + (Point2(node_icon->get_size().x, -node_icon->get_size().y) / 4);
// Rectify the position to avoid overlapping items
- for (List<Rect2>::Element *E = previous_rects.front(); E; E = E->next()) {
- if (E->get().intersects(Rect2(pos, item_size))) {
- pos.y = E->get().get_position().y - item_size.y;
+ for (const Rect2 &E : previous_rects) {
+ if (E.intersects(Rect2(pos, item_size))) {
+ pos.y = E.get_position().y - item_size.y;
}
}
@@ -3611,7 +3602,7 @@ void CanvasItemEditor::_draw_locks_and_groups(Node *p_node, const Transform2D &p
RID viewport_canvas_item = viewport->get_canvas_item();
if (canvas_item) {
- float offset = 0;
+ real_t offset = 0;
Ref<Texture2D> lock = get_theme_icon(SNAME("LockViewport"), SNAME("EditorIcons"));
if (p_node->has_meta("_edit_lock_") && show_edit_locks) {
@@ -3642,14 +3633,14 @@ void CanvasItemEditor::_draw_viewport() {
all_locked = false;
all_group = false;
} else {
- for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
- if (Object::cast_to<CanvasItem>(E->get()) && !Object::cast_to<CanvasItem>(E->get())->has_meta("_edit_lock_")) {
+ for (Node *E : selection) {
+ if (Object::cast_to<CanvasItem>(E) && !Object::cast_to<CanvasItem>(E)->has_meta("_edit_lock_")) {
all_locked = false;
break;
}
}
- for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
- if (Object::cast_to<CanvasItem>(E->get()) && !Object::cast_to<CanvasItem>(E->get())->has_meta("_edit_group_")) {
+ for (Node *E : selection) {
+ if (Object::cast_to<CanvasItem>(E) && !Object::cast_to<CanvasItem>(E)->has_meta("_edit_group_")) {
all_group = false;
break;
}
@@ -3716,8 +3707,7 @@ void CanvasItemEditor::_notification(int p_what) {
// Update the viewport if the canvas_item changes
List<CanvasItem *> selection = _get_edited_canvas_items(true);
- for (List<CanvasItem *>::Element *E = selection.front(); E; E = E->next()) {
- CanvasItem *canvas_item = E->get();
+ for (CanvasItem *canvas_item : selection) {
CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(canvas_item);
Rect2 rect;
@@ -3736,7 +3726,7 @@ void CanvasItemEditor::_notification(int p_what) {
Control *control = Object::cast_to<Control>(canvas_item);
if (control) {
- float anchors[4];
+ real_t anchors[4];
Vector2 pivot;
pivot = control->get_pivot_offset();
@@ -3865,7 +3855,10 @@ void CanvasItemEditor::_notification(int p_what) {
key_auto_insert_button->add_theme_color_override("icon_pressed_color", key_auto_color.lerp(Color(1, 0, 0), 0.55));
animation_menu->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons")));
+ _update_context_menu_stylebox();
+
presets_menu->set_icon(get_theme_icon(SNAME("ControlLayout"), SNAME("EditorIcons")));
+
PopupMenu *p = presets_menu->get_popup();
p->clear();
@@ -3932,8 +3925,8 @@ void CanvasItemEditor::_selection_changed() {
int nbValidControls = 0;
int nbAnchorsMode = 0;
List<Node *> selection = editor_selection->get_selected_node_list();
- for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
- Control *control = Object::cast_to<Control>(E->get());
+ for (Node *E : selection) {
+ Control *control = Object::cast_to<Control>(E);
if (!control) {
continue;
}
@@ -3966,6 +3959,18 @@ void CanvasItemEditor::edit(CanvasItem *p_canvas_item) {
}
}
+void CanvasItemEditor::_update_context_menu_stylebox() {
+ // This must be called when the theme changes to follow the new accent color.
+ Ref<StyleBoxFlat> context_menu_stylebox = memnew(StyleBoxFlat);
+ const Color accent_color = EditorNode::get_singleton()->get_gui_base()->get_theme_color("accent_color", "Editor");
+ context_menu_stylebox->set_bg_color(accent_color * Color(1, 1, 1, 0.1));
+ // Add an underline to the StyleBox, but prevent its minimum vertical size from changing.
+ context_menu_stylebox->set_border_color(accent_color);
+ context_menu_stylebox->set_border_width(SIDE_BOTTOM, Math::round(2 * EDSCALE));
+ context_menu_stylebox->set_default_margin(SIDE_BOTTOM, 0);
+ context_menu_container->add_theme_style_override("panel", context_menu_stylebox);
+}
+
void CanvasItemEditor::_update_scrollbars() {
updating_scroll = true;
@@ -3998,7 +4003,7 @@ void CanvasItemEditor::_update_scrollbars() {
bool constrain_editor_view = bool(EditorSettings::get_singleton()->get("editors/2d/constrain_editor_view"));
if (canvas_item_rect.size.height <= (local_rect.size.y / zoom)) {
- float centered = -(size.y / 2) / zoom + screen_rect.y / 2;
+ real_t centered = -(size.y / 2) / zoom + screen_rect.y / 2;
if (constrain_editor_view && ABS(centered - previous_update_view_offset.y) < ABS(centered - view_offset.y)) {
view_offset.y = previous_update_view_offset.y;
}
@@ -4019,7 +4024,7 @@ void CanvasItemEditor::_update_scrollbars() {
}
if (canvas_item_rect.size.width <= (local_rect.size.x / zoom)) {
- float centered = -(size.x / 2) / zoom + screen_rect.x / 2;
+ real_t centered = -(size.x / 2) / zoom + screen_rect.x / 2;
if (constrain_editor_view && ABS(centered - previous_update_view_offset.x) < ABS(centered - view_offset.x)) {
view_offset.x = previous_update_view_offset.x;
}
@@ -4086,7 +4091,7 @@ void CanvasItemEditor::_popup_warning_temporarily(Control *p_control, const floa
info_overlay->set_offset(SIDE_LEFT, (show_rulers ? RULER_WIDTH : 0) + 10);
}
-void CanvasItemEditor::_update_scroll(float) {
+void CanvasItemEditor::_update_scroll(real_t) {
if (updating_scroll) {
return;
}
@@ -4101,8 +4106,8 @@ void CanvasItemEditor::_set_anchors_and_offsets_preset(Control::LayoutPreset p_p
undo_redo->create_action(TTR("Change Anchors and Offsets"));
- for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
- Control *control = Object::cast_to<Control>(E->get());
+ for (Node *E : selection) {
+ Control *control = Object::cast_to<Control>(E);
if (control) {
undo_redo->add_do_method(control, "set_anchors_preset", p_preset);
switch (p_preset) {
@@ -4142,8 +4147,8 @@ void CanvasItemEditor::_set_anchors_and_offsets_to_keep_ratio() {
undo_redo->create_action(TTR("Change Anchors and Offsets"));
- for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
- Control *control = Object::cast_to<Control>(E->get());
+ for (Node *E : selection) {
+ Control *control = Object::cast_to<Control>(E);
if (control) {
Point2 top_left_anchor = _position_to_anchor(control, Point2());
Point2 bottom_right_anchor = _position_to_anchor(control, control->get_size());
@@ -4169,8 +4174,8 @@ void CanvasItemEditor::_set_anchors_preset(Control::LayoutPreset p_preset) {
List<Node *> selection = editor_selection->get_selected_node_list();
undo_redo->create_action(TTR("Change Anchors"));
- for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
- Control *control = Object::cast_to<Control>(E->get());
+ for (Node *E : selection) {
+ Control *control = Object::cast_to<Control>(E);
if (control) {
undo_redo->add_do_method(control, "set_anchors_preset", p_preset);
undo_redo->add_undo_method(control, "_edit_set_state", control->_edit_get_state());
@@ -4180,14 +4185,14 @@ void CanvasItemEditor::_set_anchors_preset(Control::LayoutPreset p_preset) {
undo_redo->commit_action();
}
-void CanvasItemEditor::_zoom_on_position(float p_zoom, Point2 p_position) {
+void CanvasItemEditor::_zoom_on_position(real_t p_zoom, Point2 p_position) {
p_zoom = CLAMP(p_zoom, MIN_ZOOM, MAX_ZOOM);
if (p_zoom == zoom) {
return;
}
- float prev_zoom = zoom;
+ real_t prev_zoom = zoom;
zoom = p_zoom;
view_offset += p_position / prev_zoom - p_position / zoom;
@@ -4196,7 +4201,7 @@ void CanvasItemEditor::_zoom_on_position(float p_zoom, Point2 p_position) {
// in small details (texts, lines).
// This correction adds a jitter movement when zooming, so we correct only when the
// zoom factor is an integer. (in the other cases, all pixels won't be aligned anyway)
- float closest_zoom_factor = Math::round(zoom);
+ const real_t closest_zoom_factor = Math::round(zoom);
if (Math::is_zero_approx(zoom - closest_zoom_factor)) {
// make sure scene pixel at view_offset is aligned on a screen pixel
Vector2 view_offset_int = view_offset.floor();
@@ -4208,7 +4213,7 @@ void CanvasItemEditor::_zoom_on_position(float p_zoom, Point2 p_position) {
update_viewport();
}
-void CanvasItemEditor::_update_zoom(float p_zoom) {
+void CanvasItemEditor::_update_zoom(real_t p_zoom) {
_zoom_on_position(p_zoom, viewport_scrollable->get_size() / 2.0);
}
@@ -4295,15 +4300,15 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation,
}
if (has_chain && ik_chain.size()) {
- for (List<Node2D *>::Element *F = ik_chain.front(); F; F = F->next()) {
+ for (Node2D *&F : ik_chain) {
if (key_pos) {
- AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F->get(), "position", F->get()->get_position(), p_on_existing);
+ AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F, "position", F->get_position(), p_on_existing);
}
if (key_rot) {
- AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F->get(), "rotation", F->get()->get_rotation(), p_on_existing);
+ AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F, "rotation", F->get_rotation(), p_on_existing);
}
if (key_scale) {
- AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F->get(), "scale", F->get()->get_scale(), p_on_existing);
+ AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F, "scale", F->get_scale(), p_on_existing);
}
}
}
@@ -4327,8 +4332,8 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation,
void CanvasItemEditor::_button_toggle_anchor_mode(bool p_status) {
List<CanvasItem *> selection = _get_edited_canvas_items(false, false);
- for (List<CanvasItem *>::Element *E = selection.front(); E; E = E->next()) {
- Control *control = Object::cast_to<Control>(E->get());
+ for (CanvasItem *E : selection) {
+ Control *control = Object::cast_to<Control>(E);
if (!control || Object::cast_to<Container>(control->get_parent())) {
continue;
}
@@ -4441,13 +4446,13 @@ void CanvasItemEditor::_popup_callback(int p_op) {
} break;
case SKELETON_SHOW_BONES: {
List<Node *> selection = editor_selection->get_selected_node_list();
- for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
+ for (Node *E : selection) {
// Add children nodes so they are processed
- for (int child = 0; child < E->get()->get_child_count(); child++) {
- selection.push_back(E->get()->get_child(child));
+ for (int child = 0; child < E->get_child_count(); child++) {
+ selection.push_back(E->get_child(child));
}
- Bone2D *bone_2d = Object::cast_to<Bone2D>(E->get());
+ Bone2D *bone_2d = Object::cast_to<Bone2D>(E);
if (!bone_2d || !bone_2d->is_inside_tree()) {
continue;
}
@@ -4477,8 +4482,8 @@ void CanvasItemEditor::_popup_callback(int p_op) {
undo_redo->create_action(TTR("Lock Selected"));
List<Node *> selection = editor_selection->get_selected_node_list();
- for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
- CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E->get());
+ for (Node *E : selection) {
+ CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E);
if (!canvas_item || !canvas_item->is_inside_tree()) {
continue;
}
@@ -4499,8 +4504,8 @@ void CanvasItemEditor::_popup_callback(int p_op) {
undo_redo->create_action(TTR("Unlock Selected"));
List<Node *> selection = editor_selection->get_selected_node_list();
- for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
- CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E->get());
+ for (Node *E : selection) {
+ CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E);
if (!canvas_item || !canvas_item->is_inside_tree()) {
continue;
}
@@ -4521,8 +4526,8 @@ void CanvasItemEditor::_popup_callback(int p_op) {
undo_redo->create_action(TTR("Group Selected"));
List<Node *> selection = editor_selection->get_selected_node_list();
- for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
- CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E->get());
+ for (Node *E : selection) {
+ CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E);
if (!canvas_item || !canvas_item->is_inside_tree()) {
continue;
}
@@ -4543,8 +4548,8 @@ void CanvasItemEditor::_popup_callback(int p_op) {
undo_redo->create_action(TTR("Ungroup Selected"));
List<Node *> selection = editor_selection->get_selected_node_list();
- for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
- CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E->get());
+ for (Node *E : selection) {
+ CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E);
if (!canvas_item || !canvas_item->is_inside_tree()) {
continue;
}
@@ -4711,14 +4716,14 @@ void CanvasItemEditor::_popup_callback(int p_op) {
}
undo_redo->create_action(TTR("Paste Pose"));
- for (List<PoseClipboard>::Element *E = pose_clipboard.front(); E; E = E->next()) {
- Node2D *n2d = Object::cast_to<Node2D>(ObjectDB::get_instance(E->get().id));
+ for (const PoseClipboard &E : pose_clipboard) {
+ Node2D *n2d = Object::cast_to<Node2D>(ObjectDB::get_instance(E.id));
if (!n2d) {
continue;
}
- undo_redo->add_do_method(n2d, "set_position", E->get().pos);
- undo_redo->add_do_method(n2d, "set_rotation", E->get().rot);
- undo_redo->add_do_method(n2d, "set_scale", E->get().scale);
+ undo_redo->add_do_method(n2d, "set_position", E.pos);
+ undo_redo->add_do_method(n2d, "set_rotation", E.rot);
+ undo_redo->add_do_method(n2d, "set_scale", E.scale);
undo_redo->add_undo_method(n2d, "set_position", n2d->get_position());
undo_redo->add_undo_method(n2d, "set_rotation", n2d->get_rotation());
undo_redo->add_undo_method(n2d, "set_scale", n2d->get_scale());
@@ -4893,8 +4898,8 @@ void CanvasItemEditor::_focus_selection(int p_op) {
} else { // VIEW_FRAME_TO_SELECTION
if (rect.size.x > CMP_EPSILON && rect.size.y > CMP_EPSILON) {
- float scale_x = viewport->get_size().x / rect.size.x;
- float scale_y = viewport->get_size().y / rect.size.y;
+ real_t scale_x = viewport->get_size().x / rect.size.x;
+ real_t scale_y = viewport->get_size().y / rect.size.y;
zoom = scale_x < scale_y ? scale_x : scale_y;
zoom *= 0.90;
viewport->update();
@@ -4959,7 +4964,7 @@ void CanvasItemEditor::set_state(const Dictionary &p_state) {
if (state.has("zoom")) {
// Compensate the editor scale, so that the editor scale can be changed
// and the zoom level will still be the same (relative to the editor scale).
- zoom = float(p_state["zoom"]) * MAX(1, EDSCALE);
+ zoom = real_t(p_state["zoom"]) * MAX(1, EDSCALE);
zoom_widget->set_zoom(zoom);
}
@@ -5139,11 +5144,11 @@ void CanvasItemEditor::remove_control_from_info_overlay(Control *p_control) {
void CanvasItemEditor::add_control_to_menu_panel(Control *p_control) {
ERR_FAIL_COND(!p_control);
- hb->add_child(p_control);
+ hbc_context_menu->add_child(p_control);
}
void CanvasItemEditor::remove_control_from_menu_panel(Control *p_control) {
- hb->remove_child(p_control);
+ hbc_context_menu->remove_child(p_control);
}
HSplitContainer *CanvasItemEditor::get_palette_split() {
@@ -5325,7 +5330,7 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) {
select_button->set_pressed(true);
select_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/select_mode", TTR("Select Mode"), KEY_Q));
select_button->set_shortcut_context(this);
- select_button->set_tooltip(keycode_get_string(KEY_MASK_CMD) + TTR("Drag: Rotate") + "\n" + TTR("Alt+Drag: Move") + "\n" + TTR("Press 'v' to Change Pivot, 'Shift+v' to Drag Pivot (while moving).") + "\n" + TTR("Alt+RMB: Show list of all nodes at position clicked, including locked."));
+ select_button->set_tooltip(keycode_get_string(KEY_MASK_CMD) + TTR("Drag: Rotate selected node around pivot.") + "\n" + TTR("Alt+Drag: Move selected node.") + "\n" + TTR("V: Set selected node's pivot position.") + "\n" + TTR("Alt+RMB: Show list of all nodes at position clicked, including locked.") + "\n" + keycode_get_string(KEY_MASK_CMD) + TTR("RMB: Add node at position clicked."));
hb->add_child(memnew(VSeparator));
@@ -5528,10 +5533,21 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) {
p->add_separator();
p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/preview_canvas_scale", TTR("Preview Canvas Scale"), KEY_MASK_SHIFT | KEY_MASK_CMD | KEY_P), PREVIEW_CANVAS_SCALE);
+ hb->add_child(memnew(VSeparator));
+
+ context_menu_container = memnew(PanelContainer);
+ hbc_context_menu = memnew(HBoxContainer);
+ context_menu_container->add_child(hbc_context_menu);
+ // Use a custom stylebox to make contextual menu items stand out from the rest.
+ // This helps with editor usability as contextual menu items change when selecting nodes,
+ // even though it may not be immediately obvious at first.
+ hb->add_child(context_menu_container);
+ _update_context_menu_stylebox();
+
presets_menu = memnew(MenuButton);
presets_menu->set_shortcut_context(this);
presets_menu->set_text(TTR("Layout"));
- hb->add_child(presets_menu);
+ hbc_context_menu->add_child(presets_menu);
presets_menu->hide();
presets_menu->set_switch_on_hover(true);
@@ -5545,17 +5561,18 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) {
anchor_mode_button = memnew(Button);
anchor_mode_button->set_flat(true);
- hb->add_child(anchor_mode_button);
+ hbc_context_menu->add_child(anchor_mode_button);
anchor_mode_button->set_toggle_mode(true);
anchor_mode_button->hide();
anchor_mode_button->connect("toggled", callable_mp(this, &CanvasItemEditor::_button_toggle_anchor_mode));
animation_hb = memnew(HBoxContainer);
- hb->add_child(animation_hb);
+ hbc_context_menu->add_child(animation_hb);
animation_hb->add_child(memnew(VSeparator));
animation_hb->hide();
key_loc_button = memnew(Button);
+ key_loc_button->set_flat(true);
key_loc_button->set_toggle_mode(true);
key_loc_button->set_pressed(true);
key_loc_button->set_focus_mode(FOCUS_NONE);
@@ -5564,6 +5581,7 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) {
animation_hb->add_child(key_loc_button);
key_rot_button = memnew(Button);
+ key_rot_button->set_flat(true);
key_rot_button->set_toggle_mode(true);
key_rot_button->set_pressed(true);
key_rot_button->set_focus_mode(FOCUS_NONE);
@@ -5572,6 +5590,7 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) {
animation_hb->add_child(key_rot_button);
key_scale_button = memnew(Button);
+ key_scale_button->set_flat(true);
key_scale_button->set_toggle_mode(true);
key_scale_button->set_focus_mode(FOCUS_NONE);
key_scale_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback), varray(ANIM_INSERT_SCALE));
@@ -5579,6 +5598,7 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) {
animation_hb->add_child(key_scale_button);
key_insert_button = memnew(Button);
+ key_insert_button->set_flat(true);
key_insert_button->set_focus_mode(FOCUS_NONE);
key_insert_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback), varray(ANIM_INSERT_KEY));
key_insert_button->set_tooltip(TTR("Insert keys (based on mask)."));
@@ -5745,11 +5765,14 @@ void CanvasItemEditorViewport::_create_preview(const Vector<String> &files) cons
preview_node->add_child(sprite);
label->show();
label_desc->show();
+ label_desc->set_text(TTR("Drag and drop to add as child of current scene's root node.\nHold Ctrl when dropping to add as child of selected node.\nHold Shift when dropping to add as sibling of selected node.\nHold Alt when dropping to add as a different node type."));
} else {
if (scene.is_valid()) {
Node *instance = scene->instantiate();
if (instance) {
preview_node->add_child(instance);
+ label_desc->show();
+ label_desc->set_text(TTR("Drag and drop to add as child of current scene's root node.\nHold Ctrl when dropping to add as child of selected node.\nHold Shift when dropping to add as sibling of selected node."));
}
}
}
@@ -5819,14 +5842,14 @@ void CanvasItemEditorViewport::_create_nodes(Node *parent, Node *child, String &
String property = "texture";
List<PropertyInfo> props;
child->get_property_list(&props);
- for (const List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
- if (E->get().name == "config/texture") { // Particles2D
+ for (const PropertyInfo &E : props) {
+ if (E.name == "config/texture") { // Particles2D
property = "config/texture";
break;
- } else if (E->get().name == "texture/texture") { // Polygon2D
+ } else if (E.name == "texture/texture") { // Polygon2D
property = "texture/texture";
break;
- } else if (E->get().name == "normal") { // TouchScreenButton
+ } else if (E.name == "normal") { // TouchScreenButton
property = "normal";
break;
}
@@ -6048,6 +6071,7 @@ bool CanvasItemEditorViewport::_only_packed_scenes_selected() const {
void CanvasItemEditorViewport::drop_data(const Point2 &p_point, const Variant &p_data) {
bool is_shift = Input::get_singleton()->is_key_pressed(KEY_SHIFT);
+ bool is_ctrl = Input::get_singleton()->is_key_pressed(KEY_CTRL);
bool is_alt = Input::get_singleton()->is_key_pressed(KEY_ALT);
selected_files.clear();
@@ -6059,24 +6083,25 @@ void CanvasItemEditorViewport::drop_data(const Point2 &p_point, const Variant &p
return;
}
- List<Node *> list = editor->get_editor_selection()->get_selected_node_list();
- if (list.size() == 0) {
- Node *root_node = editor->get_edited_scene();
+ List<Node *> selected_nodes = editor->get_editor_selection()->get_selected_node_list();
+ Node *root_node = editor->get_edited_scene();
+ if (selected_nodes.size() > 0) {
+ Node *selected_node = selected_nodes[0];
+ target_node = root_node;
+ if (is_ctrl) {
+ target_node = selected_node;
+ } else if (is_shift && selected_node != root_node) {
+ target_node = selected_node->get_parent();
+ }
+ } else {
if (root_node) {
- list.push_back(root_node);
+ target_node = root_node;
} else {
drop_pos = p_point;
target_node = nullptr;
}
}
- if (list.size() > 0) {
- target_node = list[0];
- if (is_shift && target_node != editor->get_edited_scene()) {
- target_node = target_node->get_parent();
- }
- }
-
drop_pos = p_point;
if (is_alt && !_only_packed_scenes_selected()) {
@@ -6157,7 +6182,6 @@ CanvasItemEditorViewport::CanvasItemEditorViewport(EditorNode *p_node, CanvasIte
canvas_item_editor->get_controls_container()->add_child(label);
label_desc = memnew(Label);
- label_desc->set_text(TTR("Drag & drop + Shift : Add node as sibling\nDrag & drop + Alt : Change node type"));
label_desc->add_theme_color_override("font_color", Color(0.6f, 0.6f, 0.6f, 1));
label_desc->add_theme_color_override("font_shadow_color", Color(0.2f, 0.2f, 0.2f, 1));
label_desc->add_theme_constant_override("shadow_as_outline", 1 * EDSCALE);
diff --git a/editor/plugins/canvas_item_editor_plugin.h b/editor/plugins/canvas_item_editor_plugin.h
index 7b64d0cb5d..1f59dfbc49 100644
--- a/editor/plugins/canvas_item_editor_plugin.h
+++ b/editor/plugins/canvas_item_editor_plugin.h
@@ -48,10 +48,10 @@ class CanvasItemEditorSelectedItem : public Object {
public:
Transform2D prev_xform;
- float prev_rot = 0;
+ real_t prev_rot = 0;
Rect2 prev_rect;
Vector2 prev_pivot;
- float prev_anchors[4] = { 0.0f };
+ real_t prev_anchors[4] = { (real_t)0.0 };
Transform2D pre_drag_xform;
Rect2 pre_drag_rect;
@@ -230,6 +230,10 @@ private:
HScrollBar *h_scroll;
VScrollBar *v_scroll;
HBoxContainer *hb;
+ // Used for secondary menu items which are displayed depending on the currently selected node
+ // (such as MeshInstance's "Mesh" menu).
+ PanelContainer *context_menu_container;
+ HBoxContainer *hbc_context_menu;
Map<Control *, Timer *> popup_temporarily_timers;
@@ -246,7 +250,7 @@ private:
bool show_edit_locks;
bool show_transformation_gizmos;
- float zoom;
+ real_t zoom;
Point2 view_offset;
Point2 previous_update_view_offset;
@@ -258,9 +262,9 @@ private:
int primary_grid_steps;
int grid_step_multiplier;
- float snap_rotation_step;
- float snap_rotation_offset;
- float snap_scale_step;
+ real_t snap_rotation_step;
+ real_t snap_rotation_offset;
+ real_t snap_scale_step;
bool smart_snap_active;
bool grid_snap_active;
@@ -288,7 +292,7 @@ private:
struct _SelectResult {
CanvasItem *item = nullptr;
- float z_index = 0;
+ real_t z_index = 0;
bool has_z = true;
_FORCE_INLINE_ bool operator<(const _SelectResult &p_rr) const {
return has_z && p_rr.has_z ? p_rr.z_index < z_index : p_rr.has_z;
@@ -305,7 +309,7 @@ private:
struct BoneList {
Transform2D xform;
- float length = 0.f;
+ real_t length = 0;
uint64_t last_pass = 0;
};
@@ -328,7 +332,7 @@ private:
struct PoseClipboard {
Vector2 pos;
Vector2 scale;
- float rot = 0;
+ real_t rot = 0;
ObjectID id;
};
List<PoseClipboard> pose_clipboard;
@@ -428,7 +432,7 @@ private:
void _popup_callback(int p_op);
bool updating_scroll;
- void _update_scroll(float);
+ void _update_scroll(real_t);
void _update_scrollbars();
void _append_canvas_item(CanvasItem *p_item);
void _snap_changed();
@@ -455,7 +459,7 @@ private:
void _draw_text_at_position(Point2 p_position, String p_string, Side p_side);
void _draw_margin_at_position(int p_value, Point2 p_position, Side p_side);
- void _draw_percentage_at_position(float p_value, Point2 p_position, Side p_side);
+ void _draw_percentage_at_position(real_t p_value, Point2 p_position, Side p_side);
void _draw_straight_line(Point2 p_from, Point2 p_to, Color p_color);
void _draw_smart_snapping();
@@ -497,16 +501,16 @@ private:
SnapTarget snap_target[2];
Transform2D snap_transform;
void _snap_if_closer_float(
- float p_value,
- float &r_current_snap, SnapTarget &r_current_snap_target,
- float p_target_value, SnapTarget p_snap_target,
- float p_radius = 10.0);
+ const real_t p_value,
+ real_t &r_current_snap, SnapTarget &r_current_snap_target,
+ const real_t p_target_value, const SnapTarget p_snap_target,
+ const real_t p_radius = 10.0);
void _snap_if_closer_point(
Point2 p_value,
Point2 &r_current_snap, SnapTarget (&r_current_snap_target)[2],
- Point2 p_target_value, SnapTarget p_snap_target,
- real_t rotation = 0.0,
- float p_radius = 10.0);
+ Point2 p_target_value, const SnapTarget p_snap_target,
+ const real_t rotation = 0.0,
+ const real_t p_radius = 10.0);
void _snap_other_nodes(
const Point2 p_value,
const Transform2D p_transform_to_snap,
@@ -523,8 +527,8 @@ private:
VBoxContainer *controls_vb;
EditorZoomWidget *zoom_widget;
- void _update_zoom(float p_zoom);
- void _zoom_on_position(float p_zoom, Point2 p_position = Point2());
+ void _update_zoom(real_t p_zoom);
+ void _zoom_on_position(real_t p_zoom, Point2 p_position = Point2());
void _button_toggle_smart_snap(bool p_status);
void _button_toggle_grid_snap(bool p_status);
void _button_override_camera(bool p_pressed);
@@ -535,6 +539,7 @@ private:
HSplitContainer *palette_split;
VSplitContainer *bottom_split;
+ void _update_context_menu_stylebox();
void _popup_warning_temporarily(Control *p_control, const float p_duration);
void _popup_warning_depop(Control *p_control);
@@ -552,28 +557,6 @@ protected:
HBoxContainer *get_panel_hb() { return hb; }
- struct compare_items_x {
- bool operator()(const CanvasItem *a, const CanvasItem *b) const {
- return a->get_global_transform().elements[2].x < b->get_global_transform().elements[2].x;
- }
- };
-
- struct compare_items_y {
- bool operator()(const CanvasItem *a, const CanvasItem *b) const {
- return a->get_global_transform().elements[2].y < b->get_global_transform().elements[2].y;
- }
- };
-
- struct proj_vector2_x {
- float get(const Vector2 &v) { return v.x; }
- void set(Vector2 &v, float f) { v.x = f; }
- };
-
- struct proj_vector2_y {
- float get(const Vector2 &v) { return v.y; }
- void set(Vector2 &v, float f) { v.y = f; }
- };
-
template <class P, class C>
void space_selected_items();
@@ -594,7 +577,7 @@ public:
};
Point2 snap_point(Point2 p_target, unsigned int p_modes = SNAP_DEFAULT, unsigned int p_forced_modes = 0, const CanvasItem *p_self_canvas_item = nullptr, List<CanvasItem *> p_other_nodes_exceptions = List<CanvasItem *>());
- float snap_angle(float p_target, float p_start = 0) const;
+ real_t snap_angle(real_t p_target, real_t p_start = 0) const;
Transform2D get_canvas_transform() const { return transform; }
diff --git a/editor/plugins/collision_shape_2d_editor_plugin.cpp b/editor/plugins/collision_shape_2d_editor_plugin.cpp
index 45adbe29de..2c2adc2672 100644
--- a/editor/plugins/collision_shape_2d_editor_plugin.cpp
+++ b/editor/plugins/collision_shape_2d_editor_plugin.cpp
@@ -31,6 +31,7 @@
#include "collision_shape_2d_editor_plugin.h"
#include "canvas_item_editor_plugin.h"
+#include "core/os/keyboard.h"
#include "scene/resources/capsule_shape_2d.h"
#include "scene/resources/circle_shape_2d.h"
#include "scene/resources/concave_polygon_shape_2d.h"
@@ -97,7 +98,7 @@ Variant CollisionShape2DEditor::get_handle_value(int idx) const {
case RECTANGLE_SHAPE: {
Ref<RectangleShape2D> rect = node->get_shape();
- if (idx < 3) {
+ if (idx < 8) {
return rect->get_size().abs();
}
@@ -176,16 +177,26 @@ void CollisionShape2DEditor::set_handle(int idx, Point2 &p_point) {
} break;
case RECTANGLE_SHAPE: {
- if (idx < 3) {
+ if (idx < 8) {
Ref<RectangleShape2D> rect = node->get_shape();
+ Vector2 size = (Point2)original;
- Vector2 size = rect->get_size();
- if (idx == 2) {
- size = p_point * 2;
+ if (RECT_HANDLES[idx].x != 0) {
+ size.x = p_point.x * RECT_HANDLES[idx].x * 2;
+ }
+ if (RECT_HANDLES[idx].y != 0) {
+ size.y = p_point.y * RECT_HANDLES[idx].y * 2;
+ }
+
+ if (Input::get_singleton()->is_key_pressed(KEY_ALT)) {
+ rect->set_size(size.abs());
+ node->set_global_position(original_transform.get_origin());
} else {
- size[idx] = p_point[idx] * 2;
+ rect->set_size(((Point2)original + (size - (Point2)original) * 0.5).abs());
+ Point2 pos = original_transform.affine_inverse().xform(original_transform.get_origin());
+ pos += (size - (Point2)original) * 0.5 * RECT_HANDLES[idx] * 0.5;
+ node->set_global_position(original_transform.xform(pos));
}
- rect->set_size(size.abs());
canvas_item_editor->update_viewport();
}
@@ -280,8 +291,10 @@ void CollisionShape2DEditor::commit_handle(int idx, Variant &p_org) {
Ref<RectangleShape2D> rect = node->get_shape();
undo_redo->add_do_method(rect.ptr(), "set_size", rect->get_size());
+ undo_redo->add_do_method(node, "set_global_transform", node->get_global_transform());
undo_redo->add_do_method(canvas_item_editor, "update_viewport");
undo_redo->add_undo_method(rect.ptr(), "set_size", p_org);
+ undo_redo->add_undo_method(node, "set_global_transform", original_transform);
undo_redo->add_undo_method(canvas_item_editor, "update_viewport");
} break;
@@ -342,6 +355,8 @@ bool CollisionShape2DEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_e
}
original = get_handle_value(edit_handle);
+ original_transform = node->get_global_transform();
+ last_point = original;
pressed = true;
return true;
@@ -369,13 +384,26 @@ bool CollisionShape2DEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_e
}
Vector2 cpoint = canvas_item_editor->snap_point(canvas_item_editor->get_canvas_transform().affine_inverse().xform(mm->get_position()));
- cpoint = node->get_global_transform().affine_inverse().xform(cpoint);
+ cpoint = original_transform.affine_inverse().xform(cpoint);
+ last_point = cpoint;
set_handle(edit_handle, cpoint);
return true;
}
+ Ref<InputEventKey> k = p_event;
+
+ if (k.is_valid()) {
+ if (edit_handle == -1 || !pressed || k->is_echo()) {
+ return false;
+ }
+
+ if (shape_type == RECTANGLE_SHAPE && k->get_keycode() == KEY_ALT) {
+ set_handle(edit_handle, last_point); // Update handle when Alt key is toggled.
+ }
+ }
+
return false;
}
@@ -492,15 +520,12 @@ void CollisionShape2DEditor::forward_canvas_draw_over_viewport(Control *p_overla
case RECTANGLE_SHAPE: {
Ref<RectangleShape2D> shape = node->get_shape();
- handles.resize(3);
+ handles.resize(8);
Vector2 ext = shape->get_size() / 2;
- handles.write[0] = Point2(ext.x, 0);
- handles.write[1] = Point2(0, ext.y);
- handles.write[2] = Point2(ext.x, ext.y);
-
- p_overlay->draw_texture(h, gt.xform(handles[0]) - size);
- p_overlay->draw_texture(h, gt.xform(handles[1]) - size);
- p_overlay->draw_texture(h, gt.xform(handles[2]) - size);
+ for (int i = 0; i < handles.size(); i++) {
+ handles.write[i] = RECT_HANDLES[i] * ext;
+ p_overlay->draw_texture(h, gt.xform(handles[i]) - size);
+ }
} break;
diff --git a/editor/plugins/collision_shape_2d_editor_plugin.h b/editor/plugins/collision_shape_2d_editor_plugin.h
index 054db1a61b..7db6bd22aa 100644
--- a/editor/plugins/collision_shape_2d_editor_plugin.h
+++ b/editor/plugins/collision_shape_2d_editor_plugin.h
@@ -52,6 +52,17 @@ class CollisionShape2DEditor : public Control {
SEGMENT_SHAPE
};
+ const Point2 RECT_HANDLES[8] = {
+ Point2(1, 0),
+ Point2(1, 1),
+ Point2(0, 1),
+ Point2(-1, 1),
+ Point2(-1, 0),
+ Point2(-1, -1),
+ Point2(0, -1),
+ Point2(1, -1),
+ };
+
EditorNode *editor;
UndoRedo *undo_redo;
CanvasItemEditor *canvas_item_editor;
@@ -63,6 +74,8 @@ class CollisionShape2DEditor : public Control {
int edit_handle;
bool pressed;
Variant original;
+ Transform2D original_transform;
+ Point2 last_point;
Variant get_handle_value(int idx) const;
void set_handle(int idx, Point2 &p_point);
diff --git a/editor/plugins/cpu_particles_2d_editor_plugin.cpp b/editor/plugins/cpu_particles_2d_editor_plugin.cpp
index c4deb296fb..6f246c1661 100644
--- a/editor/plugins/cpu_particles_2d_editor_plugin.cpp
+++ b/editor/plugins/cpu_particles_2d_editor_plugin.cpp
@@ -253,8 +253,8 @@ CPUParticles2DEditorPlugin::CPUParticles2DEditorPlugin(EditorNode *p_node) {
file = memnew(EditorFileDialog);
List<String> ext;
ImageLoader::get_recognized_extensions(&ext);
- for (List<String>::Element *E = ext.front(); E; E = E->next()) {
- file->add_filter("*." + E->get() + "; " + E->get().to_upper());
+ for (const String &E : ext) {
+ file->add_filter("*." + E + "; " + E.to_upper());
}
file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
toolbar->add_child(file);
diff --git a/editor/plugins/editor_preview_plugins.cpp b/editor/plugins/editor_preview_plugins.cpp
index a233d66d82..d7618b973e 100644
--- a/editor/plugins/editor_preview_plugins.cpp
+++ b/editor/plugins/editor_preview_plugins.cpp
@@ -490,11 +490,11 @@ Ref<Texture2D> EditorScriptPreviewPlugin::generate(const RES &p_from, const Size
Set<String> control_flow_keywords;
Set<String> keywords;
- for (List<String>::Element *E = kwors.front(); E; E = E->next()) {
- if (scr->get_language()->is_control_flow_keyword(E->get())) {
- control_flow_keywords.insert(E->get());
+ for (const String &E : kwors) {
+ if (scr->get_language()->is_control_flow_keyword(E)) {
+ control_flow_keywords.insert(E);
} else {
- keywords.insert(E->get());
+ keywords.insert(E);
}
}
@@ -724,7 +724,7 @@ Ref<Texture2D> EditorMeshPreviewPlugin::generate(const RES &p_from, const Size2
xform.basis = Basis().rotated(Vector3(0, 1, 0), -Math_PI * 0.125);
xform.basis = Basis().rotated(Vector3(1, 0, 0), Math_PI * 0.125) * xform.basis;
AABB rot_aabb = xform.xform(aabb);
- float m = MAX(rot_aabb.size.x, rot_aabb.size.y) * 0.5;
+ real_t m = MAX(rot_aabb.size.x, rot_aabb.size.y) * 0.5;
if (m == 0) {
return Ref<Texture2D>();
}
diff --git a/editor/plugins/gpu_particles_2d_editor_plugin.cpp b/editor/plugins/gpu_particles_2d_editor_plugin.cpp
index cc5793a1f9..5184e837ce 100644
--- a/editor/plugins/gpu_particles_2d_editor_plugin.cpp
+++ b/editor/plugins/gpu_particles_2d_editor_plugin.cpp
@@ -361,8 +361,8 @@ GPUParticles2DEditorPlugin::GPUParticles2DEditorPlugin(EditorNode *p_node) {
file = memnew(EditorFileDialog);
List<String> ext;
ImageLoader::get_recognized_extensions(&ext);
- for (List<String>::Element *E = ext.front(); E; E = E->next()) {
- file->add_filter("*." + E->get() + "; " + E->get().to_upper());
+ for (const String &E : ext) {
+ file->add_filter("*." + E + "; " + E.to_upper());
}
file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
toolbar->add_child(file);
diff --git a/editor/plugins/material_editor_plugin.cpp b/editor/plugins/material_editor_plugin.cpp
index 7105d351b0..94966d4fe6 100644
--- a/editor/plugins/material_editor_plugin.cpp
+++ b/editor/plugins/material_editor_plugin.cpp
@@ -265,15 +265,15 @@ Ref<Resource> StandardMaterial3DConversionPlugin::convert(const Ref<Resource> &p
List<PropertyInfo> params;
RS::get_singleton()->shader_get_param_list(mat->get_shader_rid(), &params);
- for (List<PropertyInfo>::Element *E = params.front(); E; E = E->next()) {
+ for (const PropertyInfo &E : params) {
// Texture parameter has to be treated specially since StandardMaterial3D saved it
// as RID but ShaderMaterial needs Texture itself
- Ref<Texture2D> texture = mat->get_texture_by_name(E->get().name);
+ Ref<Texture2D> texture = mat->get_texture_by_name(E.name);
if (texture.is_valid()) {
- smat->set_shader_param(E->get().name, texture);
+ smat->set_shader_param(E.name, texture);
} else {
- Variant value = RS::get_singleton()->material_get_param(mat->get_rid(), E->get().name);
- smat->set_shader_param(E->get().name, value);
+ Variant value = RS::get_singleton()->material_get_param(mat->get_rid(), E.name);
+ smat->set_shader_param(E.name, value);
}
}
@@ -309,9 +309,9 @@ Ref<Resource> ParticlesMaterialConversionPlugin::convert(const Ref<Resource> &p_
List<PropertyInfo> params;
RS::get_singleton()->shader_get_param_list(mat->get_shader_rid(), &params);
- for (List<PropertyInfo>::Element *E = params.front(); E; E = E->next()) {
- Variant value = RS::get_singleton()->material_get_param(mat->get_rid(), E->get().name);
- smat->set_shader_param(E->get().name, value);
+ for (const PropertyInfo &E : params) {
+ Variant value = RS::get_singleton()->material_get_param(mat->get_rid(), E.name);
+ smat->set_shader_param(E.name, value);
}
smat->set_render_priority(mat->get_render_priority());
@@ -346,9 +346,9 @@ Ref<Resource> CanvasItemMaterialConversionPlugin::convert(const Ref<Resource> &p
List<PropertyInfo> params;
RS::get_singleton()->shader_get_param_list(mat->get_shader_rid(), &params);
- for (List<PropertyInfo>::Element *E = params.front(); E; E = E->next()) {
- Variant value = RS::get_singleton()->material_get_param(mat->get_rid(), E->get().name);
- smat->set_shader_param(E->get().name, value);
+ for (const PropertyInfo &E : params) {
+ Variant value = RS::get_singleton()->material_get_param(mat->get_rid(), E.name);
+ smat->set_shader_param(E.name, value);
}
smat->set_render_priority(mat->get_render_priority());
@@ -383,9 +383,9 @@ Ref<Resource> ProceduralSkyMaterialConversionPlugin::convert(const Ref<Resource>
List<PropertyInfo> params;
RS::get_singleton()->shader_get_param_list(mat->get_shader_rid(), &params);
- for (List<PropertyInfo>::Element *E = params.front(); E; E = E->next()) {
- Variant value = RS::get_singleton()->material_get_param(mat->get_rid(), E->get().name);
- smat->set_shader_param(E->get().name, value);
+ for (const PropertyInfo &E : params) {
+ Variant value = RS::get_singleton()->material_get_param(mat->get_rid(), E.name);
+ smat->set_shader_param(E.name, value);
}
smat->set_render_priority(mat->get_render_priority());
@@ -420,9 +420,9 @@ Ref<Resource> PanoramaSkyMaterialConversionPlugin::convert(const Ref<Resource> &
List<PropertyInfo> params;
RS::get_singleton()->shader_get_param_list(mat->get_shader_rid(), &params);
- for (List<PropertyInfo>::Element *E = params.front(); E; E = E->next()) {
- Variant value = RS::get_singleton()->material_get_param(mat->get_rid(), E->get().name);
- smat->set_shader_param(E->get().name, value);
+ for (const PropertyInfo &E : params) {
+ Variant value = RS::get_singleton()->material_get_param(mat->get_rid(), E.name);
+ smat->set_shader_param(E.name, value);
}
smat->set_render_priority(mat->get_render_priority());
@@ -457,9 +457,9 @@ Ref<Resource> PhysicalSkyMaterialConversionPlugin::convert(const Ref<Resource> &
List<PropertyInfo> params;
RS::get_singleton()->shader_get_param_list(mat->get_shader_rid(), &params);
- for (List<PropertyInfo>::Element *E = params.front(); E; E = E->next()) {
- Variant value = RS::get_singleton()->material_get_param(mat->get_rid(), E->get().name);
- smat->set_shader_param(E->get().name, value);
+ for (const PropertyInfo &E : params) {
+ Variant value = RS::get_singleton()->material_get_param(mat->get_rid(), E.name);
+ smat->set_shader_param(E.name, value);
}
smat->set_render_priority(mat->get_render_priority());
diff --git a/editor/plugins/mesh_instance_3d_editor_plugin.cpp b/editor/plugins/mesh_instance_3d_editor_plugin.cpp
index 91415f4883..9a2b222f21 100644
--- a/editor/plugins/mesh_instance_3d_editor_plugin.cpp
+++ b/editor/plugins/mesh_instance_3d_editor_plugin.cpp
@@ -90,8 +90,8 @@ void MeshInstance3DEditor::_menu_option(int p_option) {
ur->create_action(TTR("Create Static Trimesh Body"));
- for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
- MeshInstance3D *instance = Object::cast_to<MeshInstance3D>(E->get());
+ for (Node *E : selection) {
+ MeshInstance3D *instance = Object::cast_to<MeshInstance3D>(E);
if (!instance) {
continue;
}
@@ -332,7 +332,7 @@ void MeshInstance3DEditor::_create_uv_lines(int p_layer) {
Vector<Vector2> uv = a[p_layer == 0 ? Mesh::ARRAY_TEX_UV : Mesh::ARRAY_TEX_UV2];
if (uv.size() == 0) {
- err_dialog->set_text(TTR("Model has no UV in this layer"));
+ err_dialog->set_text(vformat(TTR("Mesh has no UV in layer %d."), p_layer + 1));
err_dialog->popup_centered();
return;
}
@@ -382,9 +382,10 @@ void MeshInstance3DEditor::_debug_uv_draw() {
}
debug_uv->set_clip_contents(true);
- debug_uv->draw_rect(Rect2(Vector2(), debug_uv->get_size()), Color(0.2, 0.2, 0.0));
+ debug_uv->draw_rect(Rect2(Vector2(), debug_uv->get_size()), get_theme_color(SNAME("dark_color_3"), SNAME("Editor")));
debug_uv->draw_set_transform(Vector2(), 0, debug_uv->get_size());
- debug_uv->draw_multiline(uv_lines, Color(1.0, 0.8, 0.7));
+ // Use a translucent color to allow overlapping triangles to be visible.
+ debug_uv->draw_multiline(uv_lines, get_theme_color(SNAME("mono_color"), SNAME("Editor")) * Color(1, 1, 1, 0.5), Math::round(EDSCALE));
}
void MeshInstance3DEditor::_create_outline_mesh() {
diff --git a/editor/plugins/mesh_library_editor_plugin.cpp b/editor/plugins/mesh_library_editor_plugin.cpp
index 15b6a0f3a0..b3f92c9d95 100644
--- a/editor/plugins/mesh_library_editor_plugin.cpp
+++ b/editor/plugins/mesh_library_editor_plugin.cpp
@@ -122,23 +122,23 @@ void MeshLibraryEditor::_import_scene(Node *p_scene, Ref<MeshLibrary> p_library,
List<uint32_t> shapes;
sb->get_shape_owners(&shapes);
- for (List<uint32_t>::Element *E = shapes.front(); E; E = E->next()) {
- if (sb->is_shape_owner_disabled(E->get())) {
+ for (uint32_t &E : shapes) {
+ if (sb->is_shape_owner_disabled(E)) {
continue;
}
- //Transform3D shape_transform = sb->shape_owner_get_transform(E->get());
+ //Transform3D shape_transform = sb->shape_owner_get_transform(E);
//shape_transform.set_origin(shape_transform.get_origin() - phys_offset);
- for (int k = 0; k < sb->shape_owner_get_shape_count(E->get()); k++) {
- Ref<Shape3D> collision = sb->shape_owner_get_shape(E->get(), k);
+ for (int k = 0; k < sb->shape_owner_get_shape_count(E); k++) {
+ Ref<Shape3D> collision = sb->shape_owner_get_shape(E, k);
if (!collision.is_valid()) {
continue;
}
MeshLibrary::ShapeData shape_data;
shape_data.shape = collision;
- shape_data.local_transform = sb->get_transform() * sb->shape_owner_get_transform(E->get());
+ shape_data.local_transform = sb->get_transform() * sb->shape_owner_get_transform(E);
collisions.push_back(shape_data);
}
}
diff --git a/editor/plugins/node_3d_editor_gizmos.cpp b/editor/plugins/node_3d_editor_gizmos.cpp
new file mode 100644
index 0000000000..46e15d97bf
--- /dev/null
+++ b/editor/plugins/node_3d_editor_gizmos.cpp
@@ -0,0 +1,5474 @@
+/*************************************************************************/
+/* node_3d_editor_gizmos.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "node_3d_editor_gizmos.h"
+
+#include "core/math/convex_hull.h"
+#include "core/math/geometry_2d.h"
+#include "core/math/geometry_3d.h"
+#include "editor/plugins/node_3d_editor_plugin.h"
+#include "scene/3d/audio_stream_player_3d.h"
+#include "scene/3d/camera_3d.h"
+#include "scene/3d/collision_polygon_3d.h"
+#include "scene/3d/collision_shape_3d.h"
+#include "scene/3d/cpu_particles_3d.h"
+#include "scene/3d/decal.h"
+#include "scene/3d/gpu_particles_3d.h"
+#include "scene/3d/gpu_particles_collision_3d.h"
+#include "scene/3d/light_3d.h"
+#include "scene/3d/lightmap_gi.h"
+#include "scene/3d/lightmap_probe.h"
+#include "scene/3d/listener_3d.h"
+#include "scene/3d/mesh_instance_3d.h"
+#include "scene/3d/navigation_region_3d.h"
+#include "scene/3d/occluder_instance_3d.h"
+#include "scene/3d/physics_joint_3d.h"
+#include "scene/3d/position_3d.h"
+#include "scene/3d/ray_cast_3d.h"
+#include "scene/3d/reflection_probe.h"
+#include "scene/3d/soft_body_3d.h"
+#include "scene/3d/spring_arm_3d.h"
+#include "scene/3d/sprite_3d.h"
+#include "scene/3d/vehicle_body_3d.h"
+#include "scene/3d/visible_on_screen_notifier_3d.h"
+#include "scene/3d/voxel_gi.h"
+#include "scene/resources/box_shape_3d.h"
+#include "scene/resources/capsule_shape_3d.h"
+#include "scene/resources/concave_polygon_shape_3d.h"
+#include "scene/resources/convex_polygon_shape_3d.h"
+#include "scene/resources/cylinder_shape_3d.h"
+#include "scene/resources/height_map_shape_3d.h"
+#include "scene/resources/primitive_meshes.h"
+#include "scene/resources/ray_shape_3d.h"
+#include "scene/resources/sphere_shape_3d.h"
+#include "scene/resources/surface_tool.h"
+#include "scene/resources/world_margin_shape_3d.h"
+
+#define HANDLE_HALF_SIZE 9.5
+
+bool EditorNode3DGizmo::is_editable() const {
+ ERR_FAIL_COND_V(!spatial_node, false);
+ Node *edited_root = spatial_node->get_tree()->get_edited_scene_root();
+ if (spatial_node == edited_root) {
+ return true;
+ }
+ if (spatial_node->get_owner() == edited_root) {
+ return true;
+ }
+
+ if (edited_root->is_editable_instance(spatial_node->get_owner())) {
+ return true;
+ }
+
+ return false;
+}
+
+void EditorNode3DGizmo::clear() {
+ for (int i = 0; i < instances.size(); i++) {
+ if (instances[i].instance.is_valid()) {
+ RS::get_singleton()->free(instances[i].instance);
+ }
+ }
+
+ billboard_handle = false;
+ collision_segments.clear();
+ collision_mesh = Ref<TriangleMesh>();
+ instances.clear();
+ handles.clear();
+ secondary_handles.clear();
+}
+
+void EditorNode3DGizmo::redraw() {
+ if (get_script_instance() && get_script_instance()->has_method("_redraw")) {
+ get_script_instance()->call("_redraw");
+ } else {
+ ERR_FAIL_COND(!gizmo_plugin);
+ gizmo_plugin->redraw(this);
+ }
+
+ if (Node3DEditor::get_singleton()->is_current_selected_gizmo(this)) {
+ Node3DEditor::get_singleton()->update_transform_gizmo();
+ }
+}
+
+String EditorNode3DGizmo::get_handle_name(int p_id) const {
+ if (get_script_instance() && get_script_instance()->has_method("_get_handle_name")) {
+ return get_script_instance()->call("_get_handle_name", p_id);
+ }
+
+ ERR_FAIL_COND_V(!gizmo_plugin, "");
+ return gizmo_plugin->get_handle_name(this, p_id);
+}
+
+bool EditorNode3DGizmo::is_handle_highlighted(int p_id) const {
+ if (get_script_instance() && get_script_instance()->has_method("_is_handle_highlighted")) {
+ return get_script_instance()->call("_is_handle_highlighted", p_id);
+ }
+
+ ERR_FAIL_COND_V(!gizmo_plugin, false);
+ return gizmo_plugin->is_handle_highlighted(this, p_id);
+}
+
+Variant EditorNode3DGizmo::get_handle_value(int p_id) const {
+ if (get_script_instance() && get_script_instance()->has_method("_get_handle_value")) {
+ return get_script_instance()->call("_get_handle_value", p_id);
+ }
+
+ ERR_FAIL_COND_V(!gizmo_plugin, Variant());
+ return gizmo_plugin->get_handle_value(this, p_id);
+}
+
+void EditorNode3DGizmo::set_handle(int p_id, Camera3D *p_camera, const Point2 &p_point) const {
+ if (get_script_instance() && get_script_instance()->has_method("_set_handle")) {
+ get_script_instance()->call("_set_handle", p_id, p_camera, p_point);
+ return;
+ }
+
+ ERR_FAIL_COND(!gizmo_plugin);
+ gizmo_plugin->set_handle(this, p_id, p_camera, p_point);
+}
+
+void EditorNode3DGizmo::commit_handle(int p_id, const Variant &p_restore, bool p_cancel) const {
+ if (get_script_instance() && get_script_instance()->has_method("_commit_handle")) {
+ get_script_instance()->call("_commit_handle", p_id, p_restore, p_cancel);
+ return;
+ }
+
+ ERR_FAIL_COND(!gizmo_plugin);
+ gizmo_plugin->commit_handle(this, p_id, p_restore, p_cancel);
+}
+
+int EditorNode3DGizmo::subgizmos_intersect_ray(Camera3D *p_camera, const Vector2 &p_point) const {
+ if (get_script_instance() && get_script_instance()->has_method("_subgizmos_intersect_ray")) {
+ return get_script_instance()->call("_subgizmos_intersect_ray", p_camera, p_point);
+ }
+
+ ERR_FAIL_COND_V(!gizmo_plugin, -1);
+ return gizmo_plugin->subgizmos_intersect_ray(this, p_camera, p_point);
+}
+
+Vector<int> EditorNode3DGizmo::subgizmos_intersect_frustum(const Camera3D *p_camera, const Vector<Plane> &p_frustum) const {
+ if (get_script_instance() && get_script_instance()->has_method("_subgizmos_intersect_frustum")) {
+ Array frustum;
+ for (int i = 0; i < p_frustum.size(); i++) {
+ frustum[i] = p_frustum[i];
+ }
+ return get_script_instance()->call("_subgizmos_intersect_frustum", p_camera, frustum);
+ }
+
+ ERR_FAIL_COND_V(!gizmo_plugin, Vector<int>());
+ return gizmo_plugin->subgizmos_intersect_frustum(this, p_camera, p_frustum);
+}
+
+Transform3D EditorNode3DGizmo::get_subgizmo_transform(int p_id) const {
+ if (get_script_instance() && get_script_instance()->has_method("_get_subgizmo_transform")) {
+ return get_script_instance()->call("_get_subgizmo_transform", p_id);
+ }
+
+ ERR_FAIL_COND_V(!gizmo_plugin, Transform3D());
+ return gizmo_plugin->get_subgizmo_transform(this, p_id);
+}
+
+void EditorNode3DGizmo::set_subgizmo_transform(int p_id, Transform3D p_transform) const {
+ if (get_script_instance() && get_script_instance()->has_method("_set_subgizmo_transform")) {
+ get_script_instance()->call("_set_subgizmo_transform", p_id, p_transform);
+ return;
+ }
+
+ ERR_FAIL_COND(!gizmo_plugin);
+ gizmo_plugin->set_subgizmo_transform(this, p_id, p_transform);
+}
+
+void EditorNode3DGizmo::commit_subgizmos(const Vector<int> &p_ids, const Vector<Transform3D> &p_restore, bool p_cancel) const {
+ if (get_script_instance() && get_script_instance()->has_method("_commit_subgizmos")) {
+ Array ids;
+ for (int i = 0; i < p_ids.size(); i++) {
+ ids[i] = p_ids[i];
+ }
+
+ Array restore;
+ for (int i = 0; i < p_restore.size(); i++) {
+ restore[i] = p_restore[i];
+ }
+
+ get_script_instance()->call("_commit_subgizmos", ids, restore, p_cancel);
+ return;
+ }
+
+ ERR_FAIL_COND(!gizmo_plugin);
+ gizmo_plugin->commit_subgizmos(this, p_ids, p_restore, p_cancel);
+}
+
+void EditorNode3DGizmo::set_spatial_node(Node3D *p_node) {
+ ERR_FAIL_NULL(p_node);
+ spatial_node = p_node;
+}
+
+void EditorNode3DGizmo::Instance::create_instance(Node3D *p_base, bool p_hidden) {
+ instance = RS::get_singleton()->instance_create2(mesh->get_rid(), p_base->get_world_3d()->get_scenario());
+ RS::get_singleton()->instance_attach_object_instance_id(instance, p_base->get_instance_id());
+ if (skin_reference.is_valid()) {
+ RS::get_singleton()->instance_attach_skeleton(instance, skin_reference->get_skeleton());
+ }
+ if (extra_margin) {
+ RS::get_singleton()->instance_set_extra_visibility_margin(instance, 1);
+ }
+ RS::get_singleton()->instance_geometry_set_cast_shadows_setting(instance, RS::SHADOW_CASTING_SETTING_OFF);
+ int layer = p_hidden ? 0 : 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER;
+ RS::get_singleton()->instance_set_layer_mask(instance, layer); //gizmos are 26
+ RS::get_singleton()->instance_geometry_set_flag(instance, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);
+}
+
+void EditorNode3DGizmo::add_mesh(const Ref<ArrayMesh> &p_mesh, const Ref<Material> &p_material, const Transform3D &p_xform, const Ref<SkinReference> &p_skin_reference) {
+ ERR_FAIL_COND(!spatial_node);
+ Instance ins;
+
+ ins.mesh = p_mesh;
+ ins.skin_reference = p_skin_reference;
+ ins.material = p_material;
+ ins.xform = p_xform;
+ if (valid) {
+ ins.create_instance(spatial_node, hidden);
+ RS::get_singleton()->instance_set_transform(ins.instance, spatial_node->get_global_transform() * ins.xform);
+ if (ins.material.is_valid()) {
+ RS::get_singleton()->instance_geometry_set_material_override(ins.instance, p_material->get_rid());
+ }
+ }
+
+ instances.push_back(ins);
+}
+
+void EditorNode3DGizmo::add_lines(const Vector<Vector3> &p_lines, const Ref<Material> &p_material, bool p_billboard, const Color &p_modulate) {
+ add_vertices(p_lines, p_material, Mesh::PRIMITIVE_LINES, p_billboard, p_modulate);
+}
+
+void EditorNode3DGizmo::add_vertices(const Vector<Vector3> &p_vertices, const Ref<Material> &p_material, Mesh::PrimitiveType p_primitive_type, bool p_billboard, const Color &p_modulate) {
+ if (p_vertices.is_empty()) {
+ return;
+ }
+
+ ERR_FAIL_COND(!spatial_node);
+ Instance ins;
+
+ Ref<ArrayMesh> mesh = memnew(ArrayMesh);
+ Array a;
+ a.resize(Mesh::ARRAY_MAX);
+
+ a[Mesh::ARRAY_VERTEX] = p_vertices;
+
+ Vector<Color> color;
+ color.resize(p_vertices.size());
+ {
+ Color *w = color.ptrw();
+ for (int i = 0; i < p_vertices.size(); i++) {
+ if (is_selected()) {
+ w[i] = Color(1, 1, 1, 0.8) * p_modulate;
+ } else {
+ w[i] = Color(1, 1, 1, 0.2) * p_modulate;
+ }
+ }
+ }
+
+ a[Mesh::ARRAY_COLOR] = color;
+
+ mesh->add_surface_from_arrays(p_primitive_type, a);
+ mesh->surface_set_material(0, p_material);
+
+ if (p_billboard) {
+ float md = 0;
+ for (int i = 0; i < p_vertices.size(); i++) {
+ md = MAX(0, p_vertices[i].length());
+ }
+ if (md) {
+ mesh->set_custom_aabb(AABB(Vector3(-md, -md, -md), Vector3(md, md, md) * 2.0));
+ }
+ }
+
+ ins.mesh = mesh;
+ if (valid) {
+ ins.create_instance(spatial_node, hidden);
+ RS::get_singleton()->instance_set_transform(ins.instance, spatial_node->get_global_transform());
+ }
+
+ instances.push_back(ins);
+}
+
+void EditorNode3DGizmo::add_unscaled_billboard(const Ref<Material> &p_material, real_t p_scale, const Color &p_modulate) {
+ ERR_FAIL_COND(!spatial_node);
+ Instance ins;
+
+ Vector<Vector3> vs;
+ Vector<Vector2> uv;
+ Vector<Color> colors;
+
+ vs.push_back(Vector3(-p_scale, p_scale, 0));
+ vs.push_back(Vector3(p_scale, p_scale, 0));
+ vs.push_back(Vector3(p_scale, -p_scale, 0));
+ vs.push_back(Vector3(-p_scale, -p_scale, 0));
+
+ uv.push_back(Vector2(0, 0));
+ uv.push_back(Vector2(1, 0));
+ uv.push_back(Vector2(1, 1));
+ uv.push_back(Vector2(0, 1));
+
+ colors.push_back(p_modulate);
+ colors.push_back(p_modulate);
+ colors.push_back(p_modulate);
+ colors.push_back(p_modulate);
+
+ Ref<ArrayMesh> mesh = memnew(ArrayMesh);
+ Array a;
+ a.resize(Mesh::ARRAY_MAX);
+ a[Mesh::ARRAY_VERTEX] = vs;
+ a[Mesh::ARRAY_TEX_UV] = uv;
+ Vector<int> indices;
+ indices.push_back(0);
+ indices.push_back(1);
+ indices.push_back(2);
+ indices.push_back(0);
+ indices.push_back(2);
+ indices.push_back(3);
+ a[Mesh::ARRAY_INDEX] = indices;
+ a[Mesh::ARRAY_COLOR] = colors;
+ mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, a);
+ mesh->surface_set_material(0, p_material);
+
+ float md = 0;
+ for (int i = 0; i < vs.size(); i++) {
+ md = MAX(0, vs[i].length());
+ }
+ if (md) {
+ mesh->set_custom_aabb(AABB(Vector3(-md, -md, -md), Vector3(md, md, md) * 2.0));
+ }
+
+ selectable_icon_size = p_scale;
+ mesh->set_custom_aabb(AABB(Vector3(-selectable_icon_size, -selectable_icon_size, -selectable_icon_size) * 100.0f, Vector3(selectable_icon_size, selectable_icon_size, selectable_icon_size) * 200.0f));
+
+ ins.mesh = mesh;
+ if (valid) {
+ ins.create_instance(spatial_node, hidden);
+ RS::get_singleton()->instance_set_transform(ins.instance, spatial_node->get_global_transform());
+ }
+
+ selectable_icon_size = p_scale;
+
+ instances.push_back(ins);
+}
+
+void EditorNode3DGizmo::add_collision_triangles(const Ref<TriangleMesh> &p_tmesh) {
+ collision_mesh = p_tmesh;
+}
+
+void EditorNode3DGizmo::add_collision_segments(const Vector<Vector3> &p_lines) {
+ int from = collision_segments.size();
+ collision_segments.resize(from + p_lines.size());
+ for (int i = 0; i < p_lines.size(); i++) {
+ collision_segments.write[from + i] = p_lines[i];
+ }
+}
+
+void EditorNode3DGizmo::add_handles(const Vector<Vector3> &p_handles, const Ref<Material> &p_material, const Vector<int> &p_ids, bool p_billboard, bool p_secondary) {
+ billboard_handle = p_billboard;
+
+ if (!is_selected() || !is_editable()) {
+ return;
+ }
+
+ ERR_FAIL_COND(!spatial_node);
+
+ if (p_ids.is_empty()) {
+ ERR_FAIL_COND_MSG((!handles.is_empty() && !handle_ids.is_empty()) || (!secondary_handles.is_empty() && !secondary_handle_ids.is_empty()), "Fail");
+ } else {
+ ERR_FAIL_COND_MSG(handles.size() != handle_ids.size() || secondary_handles.size() != secondary_handle_ids.size(), "Fail");
+ }
+
+ bool is_current_hover_gizmo = Node3DEditor::get_singleton()->get_current_hover_gizmo() == this;
+ int current_hover_handle = Node3DEditor::get_singleton()->get_current_hover_gizmo_handle();
+
+ Instance ins;
+ Ref<ArrayMesh> mesh = memnew(ArrayMesh);
+
+ Array a;
+ a.resize(RS::ARRAY_MAX);
+ a[RS::ARRAY_VERTEX] = p_handles;
+ Vector<Color> colors;
+ {
+ colors.resize(p_handles.size());
+ Color *w = colors.ptrw();
+ for (int i = 0; i < p_handles.size(); i++) {
+ Color col(1, 1, 1, 1);
+ if (is_handle_highlighted(i)) {
+ col = Color(0, 0, 1, 0.9);
+ }
+
+ int id = p_ids.is_empty() ? i : p_ids[i];
+ if (!is_current_hover_gizmo || current_hover_handle != id) {
+ col.a = 0.8;
+ }
+
+ w[i] = col;
+ }
+ }
+ a[RS::ARRAY_COLOR] = colors;
+ mesh->add_surface_from_arrays(Mesh::PRIMITIVE_POINTS, a);
+ mesh->surface_set_material(0, p_material);
+
+ if (p_billboard) {
+ float md = 0;
+ for (int i = 0; i < p_handles.size(); i++) {
+ md = MAX(0, p_handles[i].length());
+ }
+ if (md) {
+ mesh->set_custom_aabb(AABB(Vector3(-md, -md, -md), Vector3(md, md, md) * 2.0));
+ }
+ }
+
+ ins.mesh = mesh;
+ ins.extra_margin = true;
+ if (valid) {
+ ins.create_instance(spatial_node, hidden);
+ RS::get_singleton()->instance_set_transform(ins.instance, spatial_node->get_global_transform());
+ }
+ instances.push_back(ins);
+
+ Vector<Vector3> &h = p_secondary ? secondary_handles : handles;
+ int current_size = h.size();
+ h.resize(current_size + p_handles.size());
+ for (int i = 0; i < p_handles.size(); i++) {
+ h.write[current_size + i] = p_handles[i];
+ }
+
+ if (!p_ids.is_empty()) {
+ Vector<int> &ids = p_secondary ? secondary_handle_ids : handle_ids;
+ current_size = ids.size();
+ ids.resize(current_size + p_ids.size());
+ for (int i = 0; i < p_ids.size(); i++) {
+ ids.write[current_size + i] = p_ids[i];
+ }
+ }
+}
+
+void EditorNode3DGizmo::add_solid_box(Ref<Material> &p_material, Vector3 p_size, Vector3 p_position, const Transform3D &p_xform) {
+ ERR_FAIL_COND(!spatial_node);
+
+ BoxMesh box_mesh;
+ box_mesh.set_size(p_size);
+
+ Array arrays = box_mesh.surface_get_arrays(0);
+ PackedVector3Array vertex = arrays[RS::ARRAY_VERTEX];
+ Vector3 *w = vertex.ptrw();
+
+ for (int i = 0; i < vertex.size(); ++i) {
+ w[i] += p_position;
+ }
+
+ arrays[RS::ARRAY_VERTEX] = vertex;
+
+ Ref<ArrayMesh> m = memnew(ArrayMesh);
+ m->add_surface_from_arrays(box_mesh.surface_get_primitive_type(0), arrays);
+ add_mesh(m, p_material, p_xform);
+}
+
+bool EditorNode3DGizmo::intersect_frustum(const Camera3D *p_camera, const Vector<Plane> &p_frustum) {
+ ERR_FAIL_COND_V(!spatial_node, false);
+ ERR_FAIL_COND_V(!valid, false);
+
+ if (hidden && !gizmo_plugin->is_selectable_when_hidden()) {
+ return false;
+ }
+
+ if (selectable_icon_size > 0.0f) {
+ Vector3 origin = spatial_node->get_global_transform().get_origin();
+
+ const Plane *p = p_frustum.ptr();
+ int fc = p_frustum.size();
+
+ bool any_out = false;
+
+ for (int j = 0; j < fc; j++) {
+ if (p[j].is_point_over(origin)) {
+ any_out = true;
+ break;
+ }
+ }
+
+ return !any_out;
+ }
+
+ if (collision_segments.size()) {
+ const Plane *p = p_frustum.ptr();
+ int fc = p_frustum.size();
+
+ int vc = collision_segments.size();
+ const Vector3 *vptr = collision_segments.ptr();
+ Transform3D t = spatial_node->get_global_transform();
+
+ bool any_out = false;
+ for (int j = 0; j < fc; j++) {
+ for (int i = 0; i < vc; i++) {
+ Vector3 v = t.xform(vptr[i]);
+ if (p[j].is_point_over(v)) {
+ any_out = true;
+ break;
+ }
+ }
+ if (any_out) {
+ break;
+ }
+ }
+
+ if (!any_out) {
+ return true;
+ }
+ }
+
+ if (collision_mesh.is_valid()) {
+ Transform3D t = spatial_node->get_global_transform();
+
+ Vector3 mesh_scale = t.get_basis().get_scale();
+ t.orthonormalize();
+
+ Transform3D it = t.affine_inverse();
+
+ Vector<Plane> transformed_frustum;
+ int plane_count = p_frustum.size();
+ transformed_frustum.resize(plane_count);
+
+ for (int i = 0; i < plane_count; i++) {
+ transformed_frustum.write[i] = it.xform(p_frustum[i]);
+ }
+
+ Vector<Vector3> convex_points = Geometry3D::compute_convex_mesh_points(transformed_frustum.ptr(), plane_count);
+ if (collision_mesh->inside_convex_shape(transformed_frustum.ptr(), plane_count, convex_points.ptr(), convex_points.size(), mesh_scale)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void EditorNode3DGizmo::handles_intersect_ray(Camera3D *p_camera, const Vector2 &p_point, bool p_shift_pressed, int &r_id) {
+ r_id = -1;
+
+ ERR_FAIL_COND(!spatial_node);
+ ERR_FAIL_COND(!valid);
+
+ if (hidden) {
+ return;
+ }
+
+ Transform3D camera_xform = p_camera->get_global_transform();
+ Transform3D t = spatial_node->get_global_transform();
+ if (billboard_handle) {
+ t.set_look_at(t.origin, t.origin - camera_xform.basis.get_axis(2), camera_xform.basis.get_axis(1));
+ }
+
+ float min_d = 1e20;
+
+ for (int i = 0; i < secondary_handles.size(); i++) {
+ Vector3 hpos = t.xform(secondary_handles[i]);
+ Vector2 p = p_camera->unproject_position(hpos);
+
+ if (p.distance_to(p_point) < HANDLE_HALF_SIZE) {
+ real_t dp = p_camera->get_transform().origin.distance_to(hpos);
+ if (dp < min_d) {
+ min_d = dp;
+ if (secondary_handle_ids.is_empty()) {
+ r_id = i;
+ } else {
+ r_id = secondary_handle_ids[i];
+ }
+ }
+ }
+ }
+
+ if (r_id != -1 && p_shift_pressed) {
+ return;
+ }
+
+ min_d = 1e20;
+
+ for (int i = 0; i < handles.size(); i++) {
+ Vector3 hpos = t.xform(handles[i]);
+ Vector2 p = p_camera->unproject_position(hpos);
+
+ if (p.distance_to(p_point) < HANDLE_HALF_SIZE) {
+ real_t dp = p_camera->get_transform().origin.distance_to(hpos);
+ if (dp < min_d) {
+ min_d = dp;
+ if (handle_ids.is_empty()) {
+ r_id = i;
+ } else {
+ r_id = handle_ids[i];
+ }
+ }
+ }
+ }
+}
+
+bool EditorNode3DGizmo::intersect_ray(Camera3D *p_camera, const Point2 &p_point, Vector3 &r_pos, Vector3 &r_normal) {
+ ERR_FAIL_COND_V(!spatial_node, false);
+ ERR_FAIL_COND_V(!valid, false);
+
+ if (hidden && !gizmo_plugin->is_selectable_when_hidden()) {
+ return false;
+ }
+
+ if (selectable_icon_size > 0.0f) {
+ Transform3D t = spatial_node->get_global_transform();
+ Vector3 camera_position = p_camera->get_camera_transform().origin;
+ if (!camera_position.is_equal_approx(t.origin)) {
+ t.set_look_at(t.origin, camera_position);
+ }
+
+ float scale = t.origin.distance_to(p_camera->get_camera_transform().origin);
+
+ if (p_camera->get_projection() == Camera3D::PROJECTION_ORTHOGONAL) {
+ float aspect = p_camera->get_viewport()->get_visible_rect().size.aspect();
+ float size = p_camera->get_size();
+ scale = size / aspect;
+ }
+
+ Point2 center = p_camera->unproject_position(t.origin);
+
+ Transform3D orig_camera_transform = p_camera->get_camera_transform();
+
+ if (!orig_camera_transform.origin.is_equal_approx(t.origin) &&
+ ABS(orig_camera_transform.basis.get_axis(Vector3::AXIS_Z).dot(Vector3(0, 1, 0))) < 0.99) {
+ p_camera->look_at(t.origin);
+ }
+
+ Vector3 c0 = t.xform(Vector3(selectable_icon_size, selectable_icon_size, 0) * scale);
+ Vector3 c1 = t.xform(Vector3(-selectable_icon_size, -selectable_icon_size, 0) * scale);
+
+ Point2 p0 = p_camera->unproject_position(c0);
+ Point2 p1 = p_camera->unproject_position(c1);
+
+ p_camera->set_global_transform(orig_camera_transform);
+
+ Rect2 rect(p0, (p1 - p0).abs());
+
+ rect.set_position(center - rect.get_size() / 2.0);
+
+ if (rect.has_point(p_point)) {
+ r_pos = t.origin;
+ r_normal = -p_camera->project_ray_normal(p_point);
+ return true;
+ }
+ }
+
+ if (collision_segments.size()) {
+ Plane camp(p_camera->get_transform().origin, (-p_camera->get_transform().basis.get_axis(2)).normalized());
+
+ int vc = collision_segments.size();
+ const Vector3 *vptr = collision_segments.ptr();
+ Transform3D t = spatial_node->get_global_transform();
+ if (billboard_handle) {
+ t.set_look_at(t.origin, t.origin - p_camera->get_transform().basis.get_axis(2), p_camera->get_transform().basis.get_axis(1));
+ }
+
+ Vector3 cp;
+ float cpd = 1e20;
+
+ for (int i = 0; i < vc / 2; i++) {
+ Vector3 a = t.xform(vptr[i * 2 + 0]);
+ Vector3 b = t.xform(vptr[i * 2 + 1]);
+ Vector2 s[2];
+ s[0] = p_camera->unproject_position(a);
+ s[1] = p_camera->unproject_position(b);
+
+ Vector2 p = Geometry2D::get_closest_point_to_segment(p_point, s);
+
+ float pd = p.distance_to(p_point);
+
+ if (pd < cpd) {
+ float d = s[0].distance_to(s[1]);
+ Vector3 tcp;
+ if (d > 0) {
+ float d2 = s[0].distance_to(p) / d;
+ tcp = a + (b - a) * d2;
+
+ } else {
+ tcp = a;
+ }
+
+ if (camp.distance_to(tcp) < p_camera->get_near()) {
+ continue;
+ }
+ cp = tcp;
+ cpd = pd;
+ }
+ }
+
+ if (cpd < 8) {
+ r_pos = cp;
+ r_normal = -p_camera->project_ray_normal(p_point);
+ return true;
+ }
+ }
+
+ if (collision_mesh.is_valid()) {
+ Transform3D gt = spatial_node->get_global_transform();
+
+ if (billboard_handle) {
+ gt.set_look_at(gt.origin, gt.origin - p_camera->get_transform().basis.get_axis(2), p_camera->get_transform().basis.get_axis(1));
+ }
+
+ Transform3D ai = gt.affine_inverse();
+ Vector3 ray_from = ai.xform(p_camera->project_ray_origin(p_point));
+ Vector3 ray_dir = ai.basis.xform(p_camera->project_ray_normal(p_point)).normalized();
+ Vector3 rpos, rnorm;
+
+ if (collision_mesh->intersect_ray(ray_from, ray_dir, rpos, rnorm)) {
+ r_pos = gt.xform(rpos);
+ r_normal = gt.basis.xform(rnorm).normalized();
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool EditorNode3DGizmo::is_subgizmo_selected(int p_id) const {
+ Node3DEditor *ed = Node3DEditor::get_singleton();
+ ERR_FAIL_COND_V(!ed, false);
+ return ed->is_current_selected_gizmo(this) && ed->is_subgizmo_selected(p_id);
+}
+
+Vector<int> EditorNode3DGizmo::get_subgizmo_selection() const {
+ Vector<int> ret;
+
+ Node3DEditor *ed = Node3DEditor::get_singleton();
+ ERR_FAIL_COND_V(!ed, ret);
+
+ if (ed->is_current_selected_gizmo(this)) {
+ ret = ed->get_subgizmo_selection();
+ }
+
+ return ret;
+}
+
+void EditorNode3DGizmo::create() {
+ ERR_FAIL_COND(!spatial_node);
+ ERR_FAIL_COND(valid);
+ valid = true;
+
+ for (int i = 0; i < instances.size(); i++) {
+ instances.write[i].create_instance(spatial_node, hidden);
+ }
+
+ transform();
+}
+
+void EditorNode3DGizmo::transform() {
+ ERR_FAIL_COND(!spatial_node);
+ ERR_FAIL_COND(!valid);
+ for (int i = 0; i < instances.size(); i++) {
+ RS::get_singleton()->instance_set_transform(instances[i].instance, spatial_node->get_global_transform() * instances[i].xform);
+ }
+}
+
+void EditorNode3DGizmo::free() {
+ ERR_FAIL_COND(!spatial_node);
+ ERR_FAIL_COND(!valid);
+
+ for (int i = 0; i < instances.size(); i++) {
+ if (instances[i].instance.is_valid()) {
+ RS::get_singleton()->free(instances[i].instance);
+ }
+ instances.write[i].instance = RID();
+ }
+
+ clear();
+
+ valid = false;
+}
+
+void EditorNode3DGizmo::set_hidden(bool p_hidden) {
+ hidden = p_hidden;
+ int layer = hidden ? 0 : 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER;
+ for (int i = 0; i < instances.size(); ++i) {
+ RS::get_singleton()->instance_set_layer_mask(instances[i].instance, layer);
+ }
+}
+
+void EditorNode3DGizmo::set_plugin(EditorNode3DGizmoPlugin *p_plugin) {
+ gizmo_plugin = p_plugin;
+}
+
+void EditorNode3DGizmo::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("add_lines", "lines", "material", "billboard", "modulate"), &EditorNode3DGizmo::add_lines, DEFVAL(false), DEFVAL(Color(1, 1, 1)));
+ ClassDB::bind_method(D_METHOD("add_mesh", "mesh", "material", "transform", "skeleton"), &EditorNode3DGizmo::add_mesh, DEFVAL(Variant()), DEFVAL(Transform3D()), DEFVAL(Ref<SkinReference>()));
+ ClassDB::bind_method(D_METHOD("add_collision_segments", "segments"), &EditorNode3DGizmo::add_collision_segments);
+ ClassDB::bind_method(D_METHOD("add_collision_triangles", "triangles"), &EditorNode3DGizmo::add_collision_triangles);
+ ClassDB::bind_method(D_METHOD("add_unscaled_billboard", "material", "default_scale", "modulate"), &EditorNode3DGizmo::add_unscaled_billboard, DEFVAL(1), DEFVAL(Color(1, 1, 1)));
+ ClassDB::bind_method(D_METHOD("add_handles", "handles", "material", "ids", "billboard", "secondary"), &EditorNode3DGizmo::add_handles, DEFVAL(false), DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("set_spatial_node", "node"), &EditorNode3DGizmo::_set_spatial_node);
+ ClassDB::bind_method(D_METHOD("get_spatial_node"), &EditorNode3DGizmo::get_spatial_node);
+ ClassDB::bind_method(D_METHOD("get_plugin"), &EditorNode3DGizmo::get_plugin);
+ ClassDB::bind_method(D_METHOD("clear"), &EditorNode3DGizmo::clear);
+ ClassDB::bind_method(D_METHOD("set_hidden", "hidden"), &EditorNode3DGizmo::set_hidden);
+ ClassDB::bind_method(D_METHOD("is_subgizmo_selected"), &EditorNode3DGizmo::is_subgizmo_selected);
+ ClassDB::bind_method(D_METHOD("get_subgizmo_selection"), &EditorNode3DGizmo::get_subgizmo_selection);
+
+ BIND_VMETHOD(MethodInfo("_redraw"));
+ BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_handle_name", PropertyInfo(Variant::INT, "id")));
+ BIND_VMETHOD(MethodInfo(Variant::BOOL, "_is_handle_highlighted", PropertyInfo(Variant::INT, "id")));
+
+ MethodInfo hvget(Variant::NIL, "_get_handle_value", PropertyInfo(Variant::INT, "id"));
+ hvget.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
+ BIND_VMETHOD(hvget);
+
+ BIND_VMETHOD(MethodInfo("_set_handle", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::OBJECT, "camera", PROPERTY_HINT_RESOURCE_TYPE, "Camera3D"), PropertyInfo(Variant::VECTOR2, "point")));
+ MethodInfo cm = MethodInfo("_commit_handle", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::NIL, "restore"), PropertyInfo(Variant::BOOL, "cancel"));
+ cm.default_arguments.push_back(false);
+ BIND_VMETHOD(cm);
+
+ BIND_VMETHOD(MethodInfo(Variant::INT, "_subgizmos_intersect_ray", PropertyInfo(Variant::OBJECT, "camera", PROPERTY_HINT_RESOURCE_TYPE, "Camera3D"), PropertyInfo(Variant::VECTOR2, "point")));
+ BIND_VMETHOD(MethodInfo(Variant::PACKED_INT32_ARRAY, "_subgizmos_intersect_frustum", PropertyInfo(Variant::OBJECT, "camera", PROPERTY_HINT_RESOURCE_TYPE, "Camera3D"), PropertyInfo(Variant::ARRAY, "frustum")));
+ BIND_VMETHOD(MethodInfo(Variant::TRANSFORM3D, "_get_subgizmo_transform", PropertyInfo(Variant::INT, "id")));
+ BIND_VMETHOD(MethodInfo("_set_subgizmo_transform", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::TRANSFORM3D, "transform")));
+ MethodInfo cs = MethodInfo("_commit_subgizmos", PropertyInfo(Variant::PACKED_INT32_ARRAY, "ids"), PropertyInfo(Variant::ARRAY, "restore"), PropertyInfo(Variant::BOOL, "cancel"));
+ cs.default_arguments.push_back(false);
+ BIND_VMETHOD(cs);
+}
+
+EditorNode3DGizmo::EditorNode3DGizmo() {
+ valid = false;
+ billboard_handle = false;
+ hidden = false;
+ selected = false;
+ spatial_node = nullptr;
+ gizmo_plugin = nullptr;
+ selectable_icon_size = -1.0f;
+}
+
+EditorNode3DGizmo::~EditorNode3DGizmo() {
+ if (gizmo_plugin != nullptr) {
+ gizmo_plugin->unregister_gizmo(this);
+ }
+ clear();
+}
+
+/////
+
+void EditorNode3DGizmoPlugin::create_material(const String &p_name, const Color &p_color, bool p_billboard, bool p_on_top, bool p_use_vertex_color) {
+ Color instantiated_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/instantiated", Color(0.7, 0.7, 0.7, 0.6));
+
+ Vector<Ref<StandardMaterial3D>> mats;
+
+ for (int i = 0; i < 4; i++) {
+ bool selected = i % 2 == 1;
+ bool instantiated = i < 2;
+
+ Ref<StandardMaterial3D> material = Ref<StandardMaterial3D>(memnew(StandardMaterial3D));
+
+ Color color = instantiated ? instantiated_color : p_color;
+
+ if (!selected) {
+ color.a *= 0.3;
+ }
+
+ material->set_albedo(color);
+ material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
+ material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
+ material->set_render_priority(StandardMaterial3D::RENDER_PRIORITY_MIN + 1);
+ material->set_cull_mode(StandardMaterial3D::CULL_DISABLED);
+
+ if (p_use_vertex_color) {
+ material->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
+ material->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true);
+ }
+
+ if (p_billboard) {
+ material->set_billboard_mode(StandardMaterial3D::BILLBOARD_ENABLED);
+ }
+
+ if (p_on_top && selected) {
+ material->set_on_top_of_alpha();
+ }
+
+ mats.push_back(material);
+ }
+
+ materials[p_name] = mats;
+}
+
+void EditorNode3DGizmoPlugin::create_icon_material(const String &p_name, const Ref<Texture2D> &p_texture, bool p_on_top, const Color &p_albedo) {
+ Color instantiated_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/instantiated", Color(0.7, 0.7, 0.7, 0.6));
+
+ Vector<Ref<StandardMaterial3D>> icons;
+
+ for (int i = 0; i < 4; i++) {
+ bool selected = i % 2 == 1;
+ bool instantiated = i < 2;
+
+ Ref<StandardMaterial3D> icon = Ref<StandardMaterial3D>(memnew(StandardMaterial3D));
+
+ Color color = instantiated ? instantiated_color : p_albedo;
+
+ if (!selected) {
+ color.a *= 0.85;
+ }
+
+ icon->set_albedo(color);
+
+ icon->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
+ icon->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
+ icon->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true);
+ icon->set_cull_mode(StandardMaterial3D::CULL_DISABLED);
+ icon->set_depth_draw_mode(StandardMaterial3D::DEPTH_DRAW_DISABLED);
+ icon->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
+ icon->set_texture(StandardMaterial3D::TEXTURE_ALBEDO, p_texture);
+ icon->set_flag(StandardMaterial3D::FLAG_FIXED_SIZE, true);
+ icon->set_billboard_mode(StandardMaterial3D::BILLBOARD_ENABLED);
+ icon->set_render_priority(StandardMaterial3D::RENDER_PRIORITY_MIN);
+
+ if (p_on_top && selected) {
+ icon->set_on_top_of_alpha();
+ }
+
+ icons.push_back(icon);
+ }
+
+ materials[p_name] = icons;
+}
+
+void EditorNode3DGizmoPlugin::create_handle_material(const String &p_name, bool p_billboard, const Ref<Texture2D> &p_icon) {
+ Ref<StandardMaterial3D> handle_material = Ref<StandardMaterial3D>(memnew(StandardMaterial3D));
+
+ handle_material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
+ handle_material->set_flag(StandardMaterial3D::FLAG_USE_POINT_SIZE, true);
+ Ref<Texture2D> handle_t = p_icon != nullptr ? p_icon : Node3DEditor::get_singleton()->get_theme_icon(SNAME("Editor3DHandle"), SNAME("EditorIcons"));
+ handle_material->set_point_size(handle_t->get_width());
+ handle_material->set_texture(StandardMaterial3D::TEXTURE_ALBEDO, handle_t);
+ handle_material->set_albedo(Color(1, 1, 1));
+ handle_material->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
+ handle_material->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true);
+ handle_material->set_on_top_of_alpha();
+ if (p_billboard) {
+ handle_material->set_billboard_mode(StandardMaterial3D::BILLBOARD_ENABLED);
+ handle_material->set_on_top_of_alpha();
+ }
+ handle_material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
+
+ materials[p_name] = Vector<Ref<StandardMaterial3D>>();
+ materials[p_name].push_back(handle_material);
+}
+
+void EditorNode3DGizmoPlugin::add_material(const String &p_name, Ref<StandardMaterial3D> p_material) {
+ materials[p_name] = Vector<Ref<StandardMaterial3D>>();
+ materials[p_name].push_back(p_material);
+}
+
+Ref<StandardMaterial3D> EditorNode3DGizmoPlugin::get_material(const String &p_name, const Ref<EditorNode3DGizmo> &p_gizmo) {
+ ERR_FAIL_COND_V(!materials.has(p_name), Ref<StandardMaterial3D>());
+ ERR_FAIL_COND_V(materials[p_name].size() == 0, Ref<StandardMaterial3D>());
+
+ if (p_gizmo.is_null() || materials[p_name].size() == 1) {
+ return materials[p_name][0];
+ }
+
+ int index = (p_gizmo->is_selected() ? 1 : 0) + (p_gizmo->is_editable() ? 2 : 0);
+
+ Ref<StandardMaterial3D> mat = materials[p_name][index];
+
+ if (current_state == ON_TOP && p_gizmo->is_selected()) {
+ mat->set_flag(StandardMaterial3D::FLAG_DISABLE_DEPTH_TEST, true);
+ } else {
+ mat->set_flag(StandardMaterial3D::FLAG_DISABLE_DEPTH_TEST, false);
+ }
+
+ return mat;
+}
+
+String EditorNode3DGizmoPlugin::get_gizmo_name() const {
+ if (get_script_instance() && get_script_instance()->has_method("_get_gizmo_name")) {
+ return get_script_instance()->call("_get_gizmo_name");
+ }
+ return TTR("Nameless gizmo");
+}
+
+int EditorNode3DGizmoPlugin::get_priority() const {
+ if (get_script_instance() && get_script_instance()->has_method("_get_priority")) {
+ return get_script_instance()->call("_get_priority");
+ }
+ return 0;
+}
+
+Ref<EditorNode3DGizmo> EditorNode3DGizmoPlugin::get_gizmo(Node3D *p_spatial) {
+ if (get_script_instance() && get_script_instance()->has_method("_get_gizmo")) {
+ return get_script_instance()->call("_get_gizmo", p_spatial);
+ }
+
+ Ref<EditorNode3DGizmo> ref = create_gizmo(p_spatial);
+
+ if (ref.is_null()) {
+ return ref;
+ }
+
+ ref->set_plugin(this);
+ ref->set_spatial_node(p_spatial);
+ ref->set_hidden(current_state == HIDDEN);
+
+ current_gizmos.push_back(ref.ptr());
+ return ref;
+}
+
+void EditorNode3DGizmoPlugin::_bind_methods() {
+#define GIZMO_REF PropertyInfo(Variant::OBJECT, "gizmo", PROPERTY_HINT_RESOURCE_TYPE, "EditorNode3DGizmo")
+
+ BIND_VMETHOD(MethodInfo(Variant::BOOL, "_has_gizmo", PropertyInfo(Variant::OBJECT, "spatial", PROPERTY_HINT_RESOURCE_TYPE, "Node3D")));
+ BIND_VMETHOD(MethodInfo(GIZMO_REF, "_create_gizmo", PropertyInfo(Variant::OBJECT, "spatial", PROPERTY_HINT_RESOURCE_TYPE, "Node3D")));
+
+ ClassDB::bind_method(D_METHOD("create_material", "name", "color", "billboard", "on_top", "use_vertex_color"), &EditorNode3DGizmoPlugin::create_material, DEFVAL(false), DEFVAL(false), DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("create_icon_material", "name", "texture", "on_top", "color"), &EditorNode3DGizmoPlugin::create_icon_material, DEFVAL(false), DEFVAL(Color(1, 1, 1, 1)));
+ ClassDB::bind_method(D_METHOD("create_handle_material", "name", "billboard", "texture"), &EditorNode3DGizmoPlugin::create_handle_material, DEFVAL(false), DEFVAL(Variant()));
+ ClassDB::bind_method(D_METHOD("add_material", "name", "material"), &EditorNode3DGizmoPlugin::add_material);
+
+ ClassDB::bind_method(D_METHOD("get_material", "name", "gizmo"), &EditorNode3DGizmoPlugin::get_material, DEFVAL(Ref<EditorNode3DGizmo>()));
+
+ BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_gizmo_name"));
+ BIND_VMETHOD(MethodInfo(Variant::INT, "_get_priority"));
+ BIND_VMETHOD(MethodInfo(Variant::BOOL, "_can_be_hidden"));
+ BIND_VMETHOD(MethodInfo(Variant::BOOL, "_is_selectable_when_hidden"));
+
+ BIND_VMETHOD(MethodInfo("_redraw", GIZMO_REF));
+ BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_handle_name", GIZMO_REF, PropertyInfo(Variant::INT, "id")));
+ BIND_VMETHOD(MethodInfo(Variant::BOOL, "_is_handle_highlighted", GIZMO_REF, PropertyInfo(Variant::INT, "id")));
+
+ MethodInfo hvget(Variant::NIL, "_get_handle_value", GIZMO_REF, PropertyInfo(Variant::INT, "id"));
+ hvget.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
+ BIND_VMETHOD(hvget);
+
+ BIND_VMETHOD(MethodInfo("_set_handle", GIZMO_REF, PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::OBJECT, "camera", PROPERTY_HINT_RESOURCE_TYPE, "Camera3D"), PropertyInfo(Variant::VECTOR2, "point")));
+ MethodInfo cm = MethodInfo("_commit_handle", GIZMO_REF, PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::NIL, "restore"), PropertyInfo(Variant::BOOL, "cancel"));
+ cm.default_arguments.push_back(false);
+ BIND_VMETHOD(cm);
+
+ BIND_VMETHOD(MethodInfo(Variant::INT, "_subgizmos_intersect_ray", GIZMO_REF, PropertyInfo(Variant::OBJECT, "camera", PROPERTY_HINT_RESOURCE_TYPE, "Camera3D"), PropertyInfo(Variant::VECTOR2, "point")));
+ BIND_VMETHOD(MethodInfo(Variant::PACKED_INT32_ARRAY, "_subgizmos_intersect_frustum", GIZMO_REF, PropertyInfo(Variant::OBJECT, "camera", PROPERTY_HINT_RESOURCE_TYPE, "Camera3D"), PropertyInfo(Variant::ARRAY, "frustum")));
+ BIND_VMETHOD(MethodInfo(Variant::TRANSFORM3D, "_get_subgizmo_transform", GIZMO_REF, PropertyInfo(Variant::INT, "id")));
+ BIND_VMETHOD(MethodInfo("_set_subgizmo_transform", GIZMO_REF, PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::TRANSFORM3D, "transform")));
+ MethodInfo cs = MethodInfo("_commit_subgizmos", GIZMO_REF, PropertyInfo(Variant::PACKED_INT32_ARRAY, "ids"), PropertyInfo(Variant::ARRAY, "restore"), PropertyInfo(Variant::BOOL, "cancel"));
+ cs.default_arguments.push_back(false);
+ BIND_VMETHOD(cs);
+
+#undef GIZMO_REF
+}
+
+bool EditorNode3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ if (get_script_instance() && get_script_instance()->has_method("_has_gizmo")) {
+ return get_script_instance()->call("_has_gizmo", p_spatial);
+ }
+ return false;
+}
+
+Ref<EditorNode3DGizmo> EditorNode3DGizmoPlugin::create_gizmo(Node3D *p_spatial) {
+ if (get_script_instance() && get_script_instance()->has_method("_create_gizmo")) {
+ return get_script_instance()->call("_create_gizmo", p_spatial);
+ }
+
+ Ref<EditorNode3DGizmo> ref;
+ if (has_gizmo(p_spatial)) {
+ ref.instantiate();
+ }
+ return ref;
+}
+
+bool EditorNode3DGizmoPlugin::can_be_hidden() const {
+ if (get_script_instance() && get_script_instance()->has_method("_can_be_hidden")) {
+ return get_script_instance()->call("_can_be_hidden");
+ }
+ return true;
+}
+
+bool EditorNode3DGizmoPlugin::is_selectable_when_hidden() const {
+ if (get_script_instance() && get_script_instance()->has_method("_is_selectable_when_hidden")) {
+ return get_script_instance()->call("_is_selectable_when_hidden");
+ }
+ return false;
+}
+
+void EditorNode3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ if (get_script_instance() && get_script_instance()->has_method("_redraw")) {
+ Ref<EditorNode3DGizmo> ref(p_gizmo);
+ get_script_instance()->call("_redraw", ref);
+ }
+}
+
+bool EditorNode3DGizmoPlugin::is_handle_highlighted(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ if (get_script_instance() && get_script_instance()->has_method("_is_handle_highlighted")) {
+ return get_script_instance()->call("_is_handle_highlighted", p_gizmo, p_id);
+ }
+ return false;
+}
+
+String EditorNode3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ if (get_script_instance() && get_script_instance()->has_method("_get_handle_name")) {
+ return get_script_instance()->call("_get_handle_name", p_gizmo, p_id);
+ }
+ return "";
+}
+
+Variant EditorNode3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ if (get_script_instance() && get_script_instance()->has_method("_get_handle_value")) {
+ return get_script_instance()->call("_get_handle_value", p_gizmo, p_id);
+ }
+ return Variant();
+}
+
+void EditorNode3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const {
+ if (get_script_instance() && get_script_instance()->has_method("_set_handle")) {
+ get_script_instance()->call("_set_handle", p_gizmo, p_id, p_camera, p_point);
+ }
+}
+
+void EditorNode3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) const {
+ if (get_script_instance() && get_script_instance()->has_method("_commit_handle")) {
+ get_script_instance()->call("_commit_handle", p_gizmo, p_id, p_restore, p_cancel);
+ }
+}
+
+int EditorNode3DGizmoPlugin::subgizmos_intersect_ray(const EditorNode3DGizmo *p_gizmo, Camera3D *p_camera, const Vector2 &p_point) const {
+ if (get_script_instance() && get_script_instance()->has_method("_subgizmos_intersect_ray")) {
+ return get_script_instance()->call("_subgizmos_intersect_ray", p_camera, p_point);
+ }
+ return -1;
+}
+
+Vector<int> EditorNode3DGizmoPlugin::subgizmos_intersect_frustum(const EditorNode3DGizmo *p_gizmo, const Camera3D *p_camera, const Vector<Plane> &p_frustum) const {
+ if (get_script_instance() && get_script_instance()->has_method("_subgizmos_intersect_frustum")) {
+ Array frustum;
+ for (int i = 0; i < p_frustum.size(); i++) {
+ frustum[i] = p_frustum[i];
+ }
+ return get_script_instance()->call("_subgizmos_intersect_frustum", p_camera, frustum);
+ }
+
+ return Vector<int>();
+}
+
+Transform3D EditorNode3DGizmoPlugin::get_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ if (get_script_instance() && get_script_instance()->has_method("_get_subgizmo_transform")) {
+ return get_script_instance()->call("_get_subgizmo_transform", p_id);
+ }
+
+ return Transform3D();
+}
+
+void EditorNode3DGizmoPlugin::set_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id, Transform3D p_transform) const {
+ if (get_script_instance() && get_script_instance()->has_method("_set_subgizmo_transform")) {
+ get_script_instance()->call("_set_subgizmo_transform", p_id, p_transform);
+ }
+}
+
+void EditorNode3DGizmoPlugin::commit_subgizmos(const EditorNode3DGizmo *p_gizmo, const Vector<int> &p_ids, const Vector<Transform3D> &p_restore, bool p_cancel) const {
+ if (get_script_instance() && get_script_instance()->has_method("_commit_subgizmos")) {
+ Array ids;
+ for (int i = 0; i < p_ids.size(); i++) {
+ ids[i] = p_ids[i];
+ }
+
+ Array restore;
+ for (int i = 0; i < p_restore.size(); i++) {
+ restore[i] = p_restore[i];
+ }
+
+ get_script_instance()->call("_commit_subgizmos", ids, restore, p_cancel);
+ }
+}
+
+void EditorNode3DGizmoPlugin::set_state(int p_state) {
+ current_state = p_state;
+ for (int i = 0; i < current_gizmos.size(); ++i) {
+ current_gizmos[i]->set_hidden(current_state == HIDDEN);
+ }
+}
+
+int EditorNode3DGizmoPlugin::get_state() const {
+ return current_state;
+}
+
+void EditorNode3DGizmoPlugin::unregister_gizmo(EditorNode3DGizmo *p_gizmo) {
+ current_gizmos.erase(p_gizmo);
+}
+
+EditorNode3DGizmoPlugin::EditorNode3DGizmoPlugin() {
+ current_state = VISIBLE;
+}
+
+EditorNode3DGizmoPlugin::~EditorNode3DGizmoPlugin() {
+ for (int i = 0; i < current_gizmos.size(); ++i) {
+ current_gizmos[i]->set_plugin(nullptr);
+ current_gizmos[i]->get_spatial_node()->remove_gizmo(current_gizmos[i]);
+ }
+ if (Node3DEditor::get_singleton()) {
+ Node3DEditor::get_singleton()->update_all_gizmos();
+ }
+}
+
+//// light gizmo
+
+Light3DGizmoPlugin::Light3DGizmoPlugin() {
+ // Enable vertex colors for the materials below as the gizmo color depends on the light color.
+ create_material("lines_primary", Color(1, 1, 1), false, false, true);
+ create_material("lines_secondary", Color(1, 1, 1, 0.35), false, false, true);
+ create_material("lines_billboard", Color(1, 1, 1), true, false, true);
+
+ create_icon_material("light_directional_icon", Node3DEditor::get_singleton()->get_theme_icon(SNAME("GizmoDirectionalLight"), SNAME("EditorIcons")));
+ create_icon_material("light_omni_icon", Node3DEditor::get_singleton()->get_theme_icon(SNAME("GizmoLight"), SNAME("EditorIcons")));
+ create_icon_material("light_spot_icon", Node3DEditor::get_singleton()->get_theme_icon(SNAME("GizmoSpotLight"), SNAME("EditorIcons")));
+
+ create_handle_material("handles");
+ create_handle_material("handles_billboard", true);
+}
+
+bool Light3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<Light3D>(p_spatial) != nullptr;
+}
+
+String Light3DGizmoPlugin::get_gizmo_name() const {
+ return "Light3D";
+}
+
+int Light3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+String Light3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ if (p_id == 0) {
+ return "Radius";
+ } else {
+ return "Aperture";
+ }
+}
+
+Variant Light3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ Light3D *light = Object::cast_to<Light3D>(p_gizmo->get_spatial_node());
+ if (p_id == 0) {
+ return light->get_param(Light3D::PARAM_RANGE);
+ }
+ if (p_id == 1) {
+ return light->get_param(Light3D::PARAM_SPOT_ANGLE);
+ }
+
+ return Variant();
+}
+
+static float _find_closest_angle_to_half_pi_arc(const Vector3 &p_from, const Vector3 &p_to, float p_arc_radius, const Transform3D &p_arc_xform) {
+ //bleh, discrete is simpler
+ static const int arc_test_points = 64;
+ float min_d = 1e20;
+ Vector3 min_p;
+
+ for (int i = 0; i < arc_test_points; i++) {
+ float a = i * Math_PI * 0.5 / arc_test_points;
+ float an = (i + 1) * Math_PI * 0.5 / arc_test_points;
+ Vector3 p = Vector3(Math::cos(a), 0, -Math::sin(a)) * p_arc_radius;
+ Vector3 n = Vector3(Math::cos(an), 0, -Math::sin(an)) * p_arc_radius;
+
+ Vector3 ra, rb;
+ Geometry3D::get_closest_points_between_segments(p, n, p_from, p_to, ra, rb);
+
+ float d = ra.distance_to(rb);
+ if (d < min_d) {
+ min_d = d;
+ min_p = ra;
+ }
+ }
+
+ //min_p = p_arc_xform.affine_inverse().xform(min_p);
+ float a = (Math_PI * 0.5) - Vector2(min_p.x, -min_p.z).angle();
+ return Math::rad2deg(a);
+}
+
+void Light3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const {
+ Light3D *light = Object::cast_to<Light3D>(p_gizmo->get_spatial_node());
+ Transform3D gt = light->get_global_transform();
+ Transform3D gi = gt.affine_inverse();
+
+ Vector3 ray_from = p_camera->project_ray_origin(p_point);
+ Vector3 ray_dir = p_camera->project_ray_normal(p_point);
+
+ Vector3 s[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 4096) };
+ if (p_id == 0) {
+ if (Object::cast_to<SpotLight3D>(light)) {
+ Vector3 ra, rb;
+ Geometry3D::get_closest_points_between_segments(Vector3(), Vector3(0, 0, -4096), s[0], s[1], ra, rb);
+
+ float d = -ra.z;
+ if (Node3DEditor::get_singleton()->is_snap_enabled()) {
+ d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
+ }
+
+ if (d <= 0) { // Equal is here for negative zero.
+ d = 0;
+ }
+
+ light->set_param(Light3D::PARAM_RANGE, d);
+ } else if (Object::cast_to<OmniLight3D>(light)) {
+ Plane cp = Plane(gt.origin, p_camera->get_transform().basis.get_axis(2));
+
+ Vector3 inters;
+ if (cp.intersects_ray(ray_from, ray_dir, &inters)) {
+ float r = inters.distance_to(gt.origin);
+ if (Node3DEditor::get_singleton()->is_snap_enabled()) {
+ r = Math::snapped(r, Node3DEditor::get_singleton()->get_translate_snap());
+ }
+
+ light->set_param(Light3D::PARAM_RANGE, r);
+ }
+ }
+
+ } else if (p_id == 1) {
+ float a = _find_closest_angle_to_half_pi_arc(s[0], s[1], light->get_param(Light3D::PARAM_RANGE), gt);
+ light->set_param(Light3D::PARAM_SPOT_ANGLE, CLAMP(a, 0.01, 89.99));
+ }
+}
+
+void Light3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) const {
+ Light3D *light = Object::cast_to<Light3D>(p_gizmo->get_spatial_node());
+ if (p_cancel) {
+ light->set_param(p_id == 0 ? Light3D::PARAM_RANGE : Light3D::PARAM_SPOT_ANGLE, p_restore);
+
+ } else if (p_id == 0) {
+ UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
+ ur->create_action(TTR("Change Light Radius"));
+ ur->add_do_method(light, "set_param", Light3D::PARAM_RANGE, light->get_param(Light3D::PARAM_RANGE));
+ ur->add_undo_method(light, "set_param", Light3D::PARAM_RANGE, p_restore);
+ ur->commit_action();
+ } else if (p_id == 1) {
+ UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
+ ur->create_action(TTR("Change Light Radius"));
+ ur->add_do_method(light, "set_param", Light3D::PARAM_SPOT_ANGLE, light->get_param(Light3D::PARAM_SPOT_ANGLE));
+ ur->add_undo_method(light, "set_param", Light3D::PARAM_SPOT_ANGLE, p_restore);
+ ur->commit_action();
+ }
+}
+
+void Light3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ Light3D *light = Object::cast_to<Light3D>(p_gizmo->get_spatial_node());
+
+ Color color = light->get_color();
+ // Make the gizmo color as bright as possible for better visibility
+ color.set_hsv(color.get_h(), color.get_s(), 1);
+
+ p_gizmo->clear();
+
+ if (Object::cast_to<DirectionalLight3D>(light)) {
+ Ref<Material> material = get_material("lines_primary", p_gizmo);
+ Ref<Material> icon = get_material("light_directional_icon", p_gizmo);
+
+ const int arrow_points = 7;
+ const float arrow_length = 1.5;
+
+ Vector3 arrow[arrow_points] = {
+ Vector3(0, 0, -1),
+ Vector3(0, 0.8, 0),
+ Vector3(0, 0.3, 0),
+ Vector3(0, 0.3, arrow_length),
+ Vector3(0, -0.3, arrow_length),
+ Vector3(0, -0.3, 0),
+ Vector3(0, -0.8, 0)
+ };
+
+ int arrow_sides = 2;
+
+ Vector<Vector3> lines;
+
+ for (int i = 0; i < arrow_sides; i++) {
+ for (int j = 0; j < arrow_points; j++) {
+ Basis ma(Vector3(0, 0, 1), Math_PI * i / arrow_sides);
+
+ Vector3 v1 = arrow[j] - Vector3(0, 0, arrow_length);
+ Vector3 v2 = arrow[(j + 1) % arrow_points] - Vector3(0, 0, arrow_length);
+
+ lines.push_back(ma.xform(v1));
+ lines.push_back(ma.xform(v2));
+ }
+ }
+
+ p_gizmo->add_lines(lines, material, false, color);
+ p_gizmo->add_unscaled_billboard(icon, 0.05, color);
+ }
+
+ if (Object::cast_to<OmniLight3D>(light)) {
+ // Use both a billboard circle and 3 non-billboard circles for a better sphere-like representation
+ const Ref<Material> lines_material = get_material("lines_secondary", p_gizmo);
+ const Ref<Material> lines_billboard_material = get_material("lines_billboard", p_gizmo);
+ const Ref<Material> icon = get_material("light_omni_icon", p_gizmo);
+
+ OmniLight3D *on = Object::cast_to<OmniLight3D>(light);
+ const float r = on->get_param(Light3D::PARAM_RANGE);
+ Vector<Vector3> points;
+ Vector<Vector3> points_billboard;
+
+ for (int i = 0; i < 120; i++) {
+ // Create a circle
+ const float ra = Math::deg2rad((float)(i * 3));
+ const float rb = Math::deg2rad((float)((i + 1) * 3));
+ const Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * r;
+ const Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * r;
+
+ // Draw axis-aligned circles
+ points.push_back(Vector3(a.x, 0, a.y));
+ points.push_back(Vector3(b.x, 0, b.y));
+ points.push_back(Vector3(0, a.x, a.y));
+ points.push_back(Vector3(0, b.x, b.y));
+ points.push_back(Vector3(a.x, a.y, 0));
+ points.push_back(Vector3(b.x, b.y, 0));
+
+ // Draw a billboarded circle
+ points_billboard.push_back(Vector3(a.x, a.y, 0));
+ points_billboard.push_back(Vector3(b.x, b.y, 0));
+ }
+
+ p_gizmo->add_lines(points, lines_material, true, color);
+ p_gizmo->add_lines(points_billboard, lines_billboard_material, true, color);
+ p_gizmo->add_unscaled_billboard(icon, 0.05, color);
+
+ Vector<Vector3> handles;
+ handles.push_back(Vector3(r, 0, 0));
+ p_gizmo->add_handles(handles, get_material("handles_billboard"), Vector<int>(), true);
+ }
+
+ if (Object::cast_to<SpotLight3D>(light)) {
+ const Ref<Material> material_primary = get_material("lines_primary", p_gizmo);
+ const Ref<Material> material_secondary = get_material("lines_secondary", p_gizmo);
+ const Ref<Material> icon = get_material("light_spot_icon", p_gizmo);
+
+ Vector<Vector3> points_primary;
+ Vector<Vector3> points_secondary;
+ SpotLight3D *sl = Object::cast_to<SpotLight3D>(light);
+
+ float r = sl->get_param(Light3D::PARAM_RANGE);
+ float w = r * Math::sin(Math::deg2rad(sl->get_param(Light3D::PARAM_SPOT_ANGLE)));
+ float d = r * Math::cos(Math::deg2rad(sl->get_param(Light3D::PARAM_SPOT_ANGLE)));
+
+ for (int i = 0; i < 120; i++) {
+ // Draw a circle
+ const float ra = Math::deg2rad((float)(i * 3));
+ const float rb = Math::deg2rad((float)((i + 1) * 3));
+ const Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * w;
+ const Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * w;
+
+ points_primary.push_back(Vector3(a.x, a.y, -d));
+ points_primary.push_back(Vector3(b.x, b.y, -d));
+
+ if (i % 15 == 0) {
+ // Draw 8 lines from the cone origin to the sides of the circle
+ points_secondary.push_back(Vector3(a.x, a.y, -d));
+ points_secondary.push_back(Vector3());
+ }
+ }
+
+ points_primary.push_back(Vector3(0, 0, -r));
+ points_primary.push_back(Vector3());
+
+ p_gizmo->add_lines(points_primary, material_primary, false, color);
+ p_gizmo->add_lines(points_secondary, material_secondary, false, color);
+
+ Vector<Vector3> handles;
+ handles.push_back(Vector3(0, 0, -r));
+ handles.push_back(Vector3(w, 0, -d));
+
+ p_gizmo->add_handles(handles, get_material("handles"));
+ p_gizmo->add_unscaled_billboard(icon, 0.05, color);
+ }
+}
+
+//////
+
+//// player gizmo
+AudioStreamPlayer3DGizmoPlugin::AudioStreamPlayer3DGizmoPlugin() {
+ Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/stream_player_3d", Color(0.4, 0.8, 1));
+
+ create_icon_material("stream_player_3d_icon", Node3DEditor::get_singleton()->get_theme_icon(SNAME("Gizmo3DSamplePlayer"), SNAME("EditorIcons")));
+ create_material("stream_player_3d_material_primary", gizmo_color);
+ create_material("stream_player_3d_material_secondary", gizmo_color * Color(1, 1, 1, 0.35));
+ create_handle_material("handles");
+}
+
+bool AudioStreamPlayer3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<AudioStreamPlayer3D>(p_spatial) != nullptr;
+}
+
+String AudioStreamPlayer3DGizmoPlugin::get_gizmo_name() const {
+ return "AudioStreamPlayer3D";
+}
+
+int AudioStreamPlayer3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+String AudioStreamPlayer3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ return "Emission Radius";
+}
+
+Variant AudioStreamPlayer3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ AudioStreamPlayer3D *player = Object::cast_to<AudioStreamPlayer3D>(p_gizmo->get_spatial_node());
+ return player->get_emission_angle();
+}
+
+void AudioStreamPlayer3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const {
+ AudioStreamPlayer3D *player = Object::cast_to<AudioStreamPlayer3D>(p_gizmo->get_spatial_node());
+
+ Transform3D gt = player->get_global_transform();
+ Transform3D gi = gt.affine_inverse();
+
+ Vector3 ray_from = p_camera->project_ray_origin(p_point);
+ Vector3 ray_dir = p_camera->project_ray_normal(p_point);
+ Vector3 ray_to = ray_from + ray_dir * 4096;
+
+ ray_from = gi.xform(ray_from);
+ ray_to = gi.xform(ray_to);
+
+ float closest_dist = 1e20;
+ float closest_angle = 1e20;
+
+ for (int i = 0; i < 180; i++) {
+ float a = Math::deg2rad((float)i);
+ float an = Math::deg2rad((float)(i + 1));
+
+ Vector3 from(Math::sin(a), 0, -Math::cos(a));
+ Vector3 to(Math::sin(an), 0, -Math::cos(an));
+
+ Vector3 r1, r2;
+ Geometry3D::get_closest_points_between_segments(from, to, ray_from, ray_to, r1, r2);
+ float d = r1.distance_to(r2);
+ if (d < closest_dist) {
+ closest_dist = d;
+ closest_angle = i;
+ }
+ }
+
+ if (closest_angle < 91) {
+ player->set_emission_angle(closest_angle);
+ }
+}
+
+void AudioStreamPlayer3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) const {
+ AudioStreamPlayer3D *player = Object::cast_to<AudioStreamPlayer3D>(p_gizmo->get_spatial_node());
+
+ if (p_cancel) {
+ player->set_emission_angle(p_restore);
+
+ } else {
+ UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
+ ur->create_action(TTR("Change AudioStreamPlayer3D Emission Angle"));
+ ur->add_do_method(player, "set_emission_angle", player->get_emission_angle());
+ ur->add_undo_method(player, "set_emission_angle", p_restore);
+ ur->commit_action();
+ }
+}
+
+void AudioStreamPlayer3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ const AudioStreamPlayer3D *player = Object::cast_to<AudioStreamPlayer3D>(p_gizmo->get_spatial_node());
+
+ p_gizmo->clear();
+
+ const Ref<Material> icon = get_material("stream_player_3d_icon", p_gizmo);
+
+ if (player->is_emission_angle_enabled()) {
+ const float pc = player->get_emission_angle();
+ const float ofs = -Math::cos(Math::deg2rad(pc));
+ const float radius = Math::sin(Math::deg2rad(pc));
+
+ Vector<Vector3> points_primary;
+ points_primary.resize(200);
+
+ real_t step = Math_TAU / 100.0;
+ for (int i = 0; i < 100; i++) {
+ const float a = i * step;
+ const float an = (i + 1) * step;
+
+ const Vector3 from(Math::sin(a) * radius, Math::cos(a) * radius, ofs);
+ const Vector3 to(Math::sin(an) * radius, Math::cos(an) * radius, ofs);
+
+ points_primary.write[i * 2 + 0] = from;
+ points_primary.write[i * 2 + 1] = to;
+ }
+
+ const Ref<Material> material_primary = get_material("stream_player_3d_material_primary", p_gizmo);
+ p_gizmo->add_lines(points_primary, material_primary);
+
+ Vector<Vector3> points_secondary;
+ points_secondary.resize(16);
+
+ for (int i = 0; i < 8; i++) {
+ const float a = i * (Math_TAU / 8.0);
+ const Vector3 from(Math::sin(a) * radius, Math::cos(a) * radius, ofs);
+
+ points_secondary.write[i * 2 + 0] = from;
+ points_secondary.write[i * 2 + 1] = Vector3();
+ }
+
+ const Ref<Material> material_secondary = get_material("stream_player_3d_material_secondary", p_gizmo);
+ p_gizmo->add_lines(points_secondary, material_secondary);
+
+ Vector<Vector3> handles;
+ const float ha = Math::deg2rad(player->get_emission_angle());
+ handles.push_back(Vector3(Math::sin(ha), 0, -Math::cos(ha)));
+ p_gizmo->add_handles(handles, get_material("handles"));
+ }
+
+ p_gizmo->add_unscaled_billboard(icon, 0.05);
+}
+
+//////
+
+Camera3DGizmoPlugin::Camera3DGizmoPlugin() {
+ Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/camera", Color(0.8, 0.4, 0.8));
+
+ create_material("camera_material", gizmo_color);
+ create_handle_material("handles");
+}
+
+bool Camera3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<Camera3D>(p_spatial) != nullptr;
+}
+
+String Camera3DGizmoPlugin::get_gizmo_name() const {
+ return "Camera3D";
+}
+
+int Camera3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+String Camera3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_spatial_node());
+
+ if (camera->get_projection() == Camera3D::PROJECTION_PERSPECTIVE) {
+ return "FOV";
+ } else {
+ return "Size";
+ }
+}
+
+Variant Camera3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_spatial_node());
+
+ if (camera->get_projection() == Camera3D::PROJECTION_PERSPECTIVE) {
+ return camera->get_fov();
+ } else {
+ return camera->get_size();
+ }
+}
+
+void Camera3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const {
+ Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_spatial_node());
+
+ Transform3D gt = camera->get_global_transform();
+ Transform3D gi = gt.affine_inverse();
+
+ Vector3 ray_from = p_camera->project_ray_origin(p_point);
+ Vector3 ray_dir = p_camera->project_ray_normal(p_point);
+
+ Vector3 s[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 4096) };
+
+ if (camera->get_projection() == Camera3D::PROJECTION_PERSPECTIVE) {
+ Transform3D gt2 = camera->get_global_transform();
+ float a = _find_closest_angle_to_half_pi_arc(s[0], s[1], 1.0, gt2);
+ camera->set("fov", CLAMP(a * 2.0, 1, 179));
+ } else {
+ Vector3 ra, rb;
+ Geometry3D::get_closest_points_between_segments(Vector3(0, 0, -1), Vector3(4096, 0, -1), s[0], s[1], ra, rb);
+ float d = ra.x * 2.0;
+ if (Node3DEditor::get_singleton()->is_snap_enabled()) {
+ d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
+ }
+
+ d = CLAMP(d, 0.1, 16384);
+
+ camera->set("size", d);
+ }
+}
+
+void Camera3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) const {
+ Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_spatial_node());
+
+ if (camera->get_projection() == Camera3D::PROJECTION_PERSPECTIVE) {
+ if (p_cancel) {
+ camera->set("fov", p_restore);
+ } else {
+ UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
+ ur->create_action(TTR("Change Camera FOV"));
+ ur->add_do_property(camera, "fov", camera->get_fov());
+ ur->add_undo_property(camera, "fov", p_restore);
+ ur->commit_action();
+ }
+
+ } else {
+ if (p_cancel) {
+ camera->set("size", p_restore);
+ } else {
+ UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
+ ur->create_action(TTR("Change Camera Size"));
+ ur->add_do_property(camera, "size", camera->get_size());
+ ur->add_undo_property(camera, "size", p_restore);
+ ur->commit_action();
+ }
+ }
+}
+
+void Camera3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_spatial_node());
+
+ p_gizmo->clear();
+
+ Vector<Vector3> lines;
+ Vector<Vector3> handles;
+
+ Ref<Material> material = get_material("camera_material", p_gizmo);
+
+#define ADD_TRIANGLE(m_a, m_b, m_c) \
+ { \
+ lines.push_back(m_a); \
+ lines.push_back(m_b); \
+ lines.push_back(m_b); \
+ lines.push_back(m_c); \
+ lines.push_back(m_c); \
+ lines.push_back(m_a); \
+ }
+
+#define ADD_QUAD(m_a, m_b, m_c, m_d) \
+ { \
+ lines.push_back(m_a); \
+ lines.push_back(m_b); \
+ lines.push_back(m_b); \
+ lines.push_back(m_c); \
+ lines.push_back(m_c); \
+ lines.push_back(m_d); \
+ lines.push_back(m_d); \
+ lines.push_back(m_a); \
+ }
+
+ switch (camera->get_projection()) {
+ case Camera3D::PROJECTION_PERSPECTIVE: {
+ // The real FOV is halved for accurate representation
+ float fov = camera->get_fov() / 2.0;
+
+ Vector3 side = Vector3(Math::sin(Math::deg2rad(fov)), 0, -Math::cos(Math::deg2rad(fov)));
+ Vector3 nside = side;
+ nside.x = -nside.x;
+ Vector3 up = Vector3(0, side.x, 0);
+
+ ADD_TRIANGLE(Vector3(), side + up, side - up);
+ ADD_TRIANGLE(Vector3(), nside + up, nside - up);
+ ADD_TRIANGLE(Vector3(), side + up, nside + up);
+ ADD_TRIANGLE(Vector3(), side - up, nside - up);
+
+ handles.push_back(side);
+ side.x *= 0.25;
+ nside.x *= 0.25;
+ Vector3 tup(0, up.y * 3 / 2, side.z);
+ ADD_TRIANGLE(tup, side + up, nside + up);
+
+ } break;
+ case Camera3D::PROJECTION_ORTHOGONAL: {
+ float size = camera->get_size();
+
+ float hsize = size * 0.5;
+ Vector3 right(hsize, 0, 0);
+ Vector3 up(0, hsize, 0);
+ Vector3 back(0, 0, -1.0);
+ Vector3 front(0, 0, 0);
+
+ ADD_QUAD(-up - right, -up + right, up + right, up - right);
+ ADD_QUAD(-up - right + back, -up + right + back, up + right + back, up - right + back);
+ ADD_QUAD(up + right, up + right + back, up - right + back, up - right);
+ ADD_QUAD(-up + right, -up + right + back, -up - right + back, -up - right);
+
+ handles.push_back(right + back);
+
+ right.x *= 0.25;
+ Vector3 tup(0, up.y * 3 / 2, back.z);
+ ADD_TRIANGLE(tup, right + up + back, -right + up + back);
+
+ } break;
+ case Camera3D::PROJECTION_FRUSTUM: {
+ float hsize = camera->get_size() / 2.0;
+
+ Vector3 side = Vector3(hsize, 0, -camera->get_near()).normalized();
+ Vector3 nside = side;
+ nside.x = -nside.x;
+ Vector3 up = Vector3(0, side.x, 0);
+ Vector3 offset = Vector3(camera->get_frustum_offset().x, camera->get_frustum_offset().y, 0.0);
+
+ ADD_TRIANGLE(Vector3(), side + up + offset, side - up + offset);
+ ADD_TRIANGLE(Vector3(), nside + up + offset, nside - up + offset);
+ ADD_TRIANGLE(Vector3(), side + up + offset, nside + up + offset);
+ ADD_TRIANGLE(Vector3(), side - up + offset, nside - up + offset);
+
+ side.x *= 0.25;
+ nside.x *= 0.25;
+ Vector3 tup(0, up.y * 3 / 2, side.z);
+ ADD_TRIANGLE(tup + offset, side + up + offset, nside + up + offset);
+ }
+ }
+
+#undef ADD_TRIANGLE
+#undef ADD_QUAD
+
+ p_gizmo->add_lines(lines, material);
+ p_gizmo->add_handles(handles, get_material("handles"));
+
+ ClippedCamera3D *clipcam = Object::cast_to<ClippedCamera3D>(camera);
+ if (clipcam) {
+ Node3D *parent = Object::cast_to<Node3D>(camera->get_parent());
+ if (!parent) {
+ return;
+ }
+ Vector3 cam_normal = -camera->get_global_transform().basis.get_axis(Vector3::AXIS_Z).normalized();
+ Vector3 cam_x = camera->get_global_transform().basis.get_axis(Vector3::AXIS_X).normalized();
+ Vector3 cam_y = camera->get_global_transform().basis.get_axis(Vector3::AXIS_Y).normalized();
+ Vector3 cam_pos = camera->get_global_transform().origin;
+ Vector3 parent_pos = parent->get_global_transform().origin;
+
+ Plane parent_plane(parent_pos, cam_normal);
+ Vector3 ray_from = parent_plane.project(cam_pos);
+
+ lines.clear();
+ lines.push_back(ray_from + cam_x * 0.5 + cam_y * 0.5);
+ lines.push_back(ray_from + cam_x * 0.5 + cam_y * -0.5);
+
+ lines.push_back(ray_from + cam_x * 0.5 + cam_y * -0.5);
+ lines.push_back(ray_from + cam_x * -0.5 + cam_y * -0.5);
+
+ lines.push_back(ray_from + cam_x * -0.5 + cam_y * -0.5);
+ lines.push_back(ray_from + cam_x * -0.5 + cam_y * 0.5);
+
+ lines.push_back(ray_from + cam_x * -0.5 + cam_y * 0.5);
+ lines.push_back(ray_from + cam_x * 0.5 + cam_y * 0.5);
+
+ if (parent_plane.distance_to(cam_pos) < 0) {
+ lines.push_back(ray_from);
+ lines.push_back(cam_pos);
+ }
+
+ Transform3D local = camera->get_global_transform().affine_inverse();
+ for (int i = 0; i < lines.size(); i++) {
+ lines.write[i] = local.xform(lines[i]);
+ }
+
+ p_gizmo->add_lines(lines, material);
+ }
+}
+
+//////
+
+MeshInstance3DGizmoPlugin::MeshInstance3DGizmoPlugin() {
+}
+
+bool MeshInstance3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<MeshInstance3D>(p_spatial) != nullptr && Object::cast_to<SoftBody3D>(p_spatial) == nullptr;
+}
+
+String MeshInstance3DGizmoPlugin::get_gizmo_name() const {
+ return "MeshInstance3D";
+}
+
+int MeshInstance3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+bool MeshInstance3DGizmoPlugin::can_be_hidden() const {
+ return false;
+}
+
+void MeshInstance3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ MeshInstance3D *mesh = Object::cast_to<MeshInstance3D>(p_gizmo->get_spatial_node());
+
+ p_gizmo->clear();
+
+ Ref<Mesh> m = mesh->get_mesh();
+
+ if (!m.is_valid()) {
+ return; //none
+ }
+
+ Ref<TriangleMesh> tm = m->generate_triangle_mesh();
+ if (tm.is_valid()) {
+ p_gizmo->add_collision_triangles(tm);
+ }
+}
+
+/////
+
+OccluderInstance3DGizmoPlugin::OccluderInstance3DGizmoPlugin() {
+ create_material("line_material", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/occluder", Color(0.8, 0.5, 1)));
+}
+
+bool OccluderInstance3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<OccluderInstance3D>(p_spatial) != nullptr;
+}
+
+String OccluderInstance3DGizmoPlugin::get_gizmo_name() const {
+ return "OccluderInstance3D";
+}
+
+int OccluderInstance3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+void OccluderInstance3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ OccluderInstance3D *occluder_instance = Object::cast_to<OccluderInstance3D>(p_gizmo->get_spatial_node());
+
+ p_gizmo->clear();
+
+ Ref<Occluder3D> o = occluder_instance->get_occluder();
+
+ if (!o.is_valid()) {
+ return;
+ }
+
+ Vector<Vector3> lines = o->get_debug_lines();
+ if (!lines.is_empty()) {
+ Ref<Material> material = get_material("line_material", p_gizmo);
+ p_gizmo->add_lines(lines, material);
+ p_gizmo->add_collision_segments(lines);
+ }
+}
+
+/////
+
+Sprite3DGizmoPlugin::Sprite3DGizmoPlugin() {
+}
+
+bool Sprite3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<Sprite3D>(p_spatial) != nullptr;
+}
+
+String Sprite3DGizmoPlugin::get_gizmo_name() const {
+ return "Sprite3D";
+}
+
+int Sprite3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+bool Sprite3DGizmoPlugin::can_be_hidden() const {
+ return false;
+}
+
+void Sprite3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ Sprite3D *sprite = Object::cast_to<Sprite3D>(p_gizmo->get_spatial_node());
+
+ p_gizmo->clear();
+
+ Ref<TriangleMesh> tm = sprite->generate_triangle_mesh();
+ if (tm.is_valid()) {
+ p_gizmo->add_collision_triangles(tm);
+ }
+}
+
+///
+
+Position3DGizmoPlugin::Position3DGizmoPlugin() {
+ pos3d_mesh = Ref<ArrayMesh>(memnew(ArrayMesh));
+ cursor_points = Vector<Vector3>();
+
+ Vector<Color> cursor_colors;
+ const float cs = 0.25;
+ // Add more points to create a "hard stop" in the color gradient.
+ cursor_points.push_back(Vector3(+cs, 0, 0));
+ cursor_points.push_back(Vector3());
+ cursor_points.push_back(Vector3());
+ cursor_points.push_back(Vector3(-cs, 0, 0));
+
+ cursor_points.push_back(Vector3(0, +cs, 0));
+ cursor_points.push_back(Vector3());
+ cursor_points.push_back(Vector3());
+ cursor_points.push_back(Vector3(0, -cs, 0));
+
+ cursor_points.push_back(Vector3(0, 0, +cs));
+ cursor_points.push_back(Vector3());
+ cursor_points.push_back(Vector3());
+ cursor_points.push_back(Vector3(0, 0, -cs));
+
+ // Use the axis color which is brighter for the positive axis.
+ // Use a darkened axis color for the negative axis.
+ // This makes it possible to see in which direction the Position3D node is rotated
+ // (which can be important depending on how it's used).
+ const Color color_x = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("axis_x_color"), SNAME("Editor"));
+ cursor_colors.push_back(color_x);
+ cursor_colors.push_back(color_x);
+ // FIXME: Use less strong darkening factor once GH-48573 is fixed.
+ // The current darkening factor compensates for lines being too bright in the 3D editor.
+ cursor_colors.push_back(color_x.lerp(Color(0, 0, 0), 0.75));
+ cursor_colors.push_back(color_x.lerp(Color(0, 0, 0), 0.75));
+
+ const Color color_y = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("axis_y_color"), SNAME("Editor"));
+ cursor_colors.push_back(color_y);
+ cursor_colors.push_back(color_y);
+ cursor_colors.push_back(color_y.lerp(Color(0, 0, 0), 0.75));
+ cursor_colors.push_back(color_y.lerp(Color(0, 0, 0), 0.75));
+
+ const Color color_z = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("axis_z_color"), SNAME("Editor"));
+ cursor_colors.push_back(color_z);
+ cursor_colors.push_back(color_z);
+ cursor_colors.push_back(color_z.lerp(Color(0, 0, 0), 0.75));
+ cursor_colors.push_back(color_z.lerp(Color(0, 0, 0), 0.75));
+
+ Ref<StandardMaterial3D> mat = memnew(StandardMaterial3D);
+ mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
+ mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
+ mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true);
+ mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
+
+ Array d;
+ d.resize(RS::ARRAY_MAX);
+ d[Mesh::ARRAY_VERTEX] = cursor_points;
+ d[Mesh::ARRAY_COLOR] = cursor_colors;
+ pos3d_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_LINES, d);
+ pos3d_mesh->surface_set_material(0, mat);
+}
+
+bool Position3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<Position3D>(p_spatial) != nullptr;
+}
+
+String Position3DGizmoPlugin::get_gizmo_name() const {
+ return "Position3D";
+}
+
+int Position3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+void Position3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ p_gizmo->clear();
+ p_gizmo->add_mesh(pos3d_mesh);
+ p_gizmo->add_collision_segments(cursor_points);
+}
+
+/////
+
+Skeleton3DGizmoPlugin::Skeleton3DGizmoPlugin() {
+ Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/skeleton", Color(1, 0.8, 0.4));
+ create_material("skeleton_material", gizmo_color);
+}
+
+bool Skeleton3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<Skeleton3D>(p_spatial) != nullptr;
+}
+
+String Skeleton3DGizmoPlugin::get_gizmo_name() const {
+ return "Skeleton3D";
+}
+
+int Skeleton3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ Skeleton3D *skel = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node());
+
+ p_gizmo->clear();
+
+ Ref<Material> material = get_material("skeleton_material", p_gizmo);
+
+ Ref<SurfaceTool> surface_tool(memnew(SurfaceTool));
+
+ surface_tool->begin(Mesh::PRIMITIVE_LINES);
+ surface_tool->set_material(material);
+ Vector<Transform3D> grests;
+ grests.resize(skel->get_bone_count());
+
+ Vector<int> bones;
+ Vector<float> weights;
+ bones.resize(4);
+ weights.resize(4);
+
+ for (int i = 0; i < 4; i++) {
+ bones.write[i] = 0;
+ weights.write[i] = 0;
+ }
+
+ weights.write[0] = 1;
+
+ AABB aabb;
+
+ Color bonecolor = Color(1.0, 0.4, 0.4, 0.3);
+ Color rootcolor = Color(0.4, 1.0, 0.4, 0.1);
+
+ for (int i_bone = 0; i_bone < skel->get_bone_count(); i_bone++) {
+ int i = skel->get_process_order(i_bone);
+
+ int parent = skel->get_bone_parent(i);
+
+ if (parent >= 0) {
+ grests.write[i] = grests[parent] * skel->get_bone_rest(i);
+
+ Vector3 v0 = grests[parent].origin;
+ Vector3 v1 = grests[i].origin;
+ Vector3 d = (v1 - v0).normalized();
+ float dist = v0.distance_to(v1);
+
+ //find closest axis
+ int closest = -1;
+ float closest_d = 0.0;
+
+ for (int j = 0; j < 3; j++) {
+ float dp = Math::abs(grests[parent].basis[j].normalized().dot(d));
+ if (j == 0 || dp > closest_d) {
+ closest = j;
+ }
+ }
+
+ //find closest other
+ Vector3 first;
+ Vector3 points[4];
+ int pointidx = 0;
+ for (int j = 0; j < 3; j++) {
+ bones.write[0] = parent;
+ surface_tool->set_bones(bones);
+ surface_tool->set_weights(weights);
+ surface_tool->set_color(rootcolor);
+ surface_tool->add_vertex(v0 - grests[parent].basis[j].normalized() * dist * 0.05);
+ surface_tool->set_bones(bones);
+ surface_tool->set_weights(weights);
+ surface_tool->set_color(rootcolor);
+ surface_tool->add_vertex(v0 + grests[parent].basis[j].normalized() * dist * 0.05);
+
+ if (j == closest) {
+ continue;
+ }
+
+ Vector3 axis;
+ if (first == Vector3()) {
+ axis = d.cross(d.cross(grests[parent].basis[j])).normalized();
+ first = axis;
+ } else {
+ axis = d.cross(first).normalized();
+ }
+
+ for (int k = 0; k < 2; k++) {
+ if (k == 1) {
+ axis = -axis;
+ }
+ Vector3 point = v0 + d * dist * 0.2;
+ point += axis * dist * 0.1;
+
+ bones.write[0] = parent;
+ surface_tool->set_bones(bones);
+ surface_tool->set_weights(weights);
+ surface_tool->set_color(bonecolor);
+ surface_tool->add_vertex(v0);
+ surface_tool->set_bones(bones);
+ surface_tool->set_weights(weights);
+ surface_tool->set_color(bonecolor);
+ surface_tool->add_vertex(point);
+
+ bones.write[0] = parent;
+ surface_tool->set_bones(bones);
+ surface_tool->set_weights(weights);
+ surface_tool->set_color(bonecolor);
+ surface_tool->add_vertex(point);
+ bones.write[0] = i;
+ surface_tool->set_bones(bones);
+ surface_tool->set_weights(weights);
+ surface_tool->set_color(bonecolor);
+ surface_tool->add_vertex(v1);
+ points[pointidx++] = point;
+ }
+ }
+
+ SWAP(points[1], points[2]);
+ for (int j = 0; j < 4; j++) {
+ bones.write[0] = parent;
+ surface_tool->set_bones(bones);
+ surface_tool->set_weights(weights);
+ surface_tool->set_color(bonecolor);
+ surface_tool->add_vertex(points[j]);
+ surface_tool->set_bones(bones);
+ surface_tool->set_weights(weights);
+ surface_tool->set_color(bonecolor);
+ surface_tool->add_vertex(points[(j + 1) % 4]);
+ }
+ } else {
+ grests.write[i] = skel->get_bone_rest(i);
+ bones.write[0] = i;
+ }
+ }
+
+ Ref<ArrayMesh> m = surface_tool->commit();
+ p_gizmo->add_mesh(m, Ref<Material>(), Transform3D(), skel->register_skin(Ref<Skin>()));
+}
+
+////
+
+PhysicalBone3DGizmoPlugin::PhysicalBone3DGizmoPlugin() {
+ create_material("joint_material", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/joint", Color(0.5, 0.8, 1)));
+}
+
+bool PhysicalBone3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<PhysicalBone3D>(p_spatial) != nullptr;
+}
+
+String PhysicalBone3DGizmoPlugin::get_gizmo_name() const {
+ return "PhysicalBone3D";
+}
+
+int PhysicalBone3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+void PhysicalBone3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ p_gizmo->clear();
+
+ PhysicalBone3D *physical_bone = Object::cast_to<PhysicalBone3D>(p_gizmo->get_spatial_node());
+
+ if (!physical_bone) {
+ return;
+ }
+
+ Skeleton3D *sk(physical_bone->find_skeleton_parent());
+ if (!sk) {
+ return;
+ }
+
+ PhysicalBone3D *pb(sk->get_physical_bone(physical_bone->get_bone_id()));
+ if (!pb) {
+ return;
+ }
+
+ PhysicalBone3D *pbp(sk->get_physical_bone_parent(physical_bone->get_bone_id()));
+ if (!pbp) {
+ return;
+ }
+
+ Vector<Vector3> points;
+
+ switch (physical_bone->get_joint_type()) {
+ case PhysicalBone3D::JOINT_TYPE_PIN: {
+ Joint3DGizmoPlugin::CreatePinJointGizmo(physical_bone->get_joint_offset(), points);
+ } break;
+ case PhysicalBone3D::JOINT_TYPE_CONE: {
+ const PhysicalBone3D::ConeJointData *cjd(static_cast<const PhysicalBone3D::ConeJointData *>(physical_bone->get_joint_data()));
+ Joint3DGizmoPlugin::CreateConeTwistJointGizmo(
+ physical_bone->get_joint_offset(),
+ physical_bone->get_global_transform() * physical_bone->get_joint_offset(),
+ pb->get_global_transform(),
+ pbp->get_global_transform(),
+ cjd->swing_span,
+ cjd->twist_span,
+ &points,
+ &points);
+ } break;
+ case PhysicalBone3D::JOINT_TYPE_HINGE: {
+ const PhysicalBone3D::HingeJointData *hjd(static_cast<const PhysicalBone3D::HingeJointData *>(physical_bone->get_joint_data()));
+ Joint3DGizmoPlugin::CreateHingeJointGizmo(
+ physical_bone->get_joint_offset(),
+ physical_bone->get_global_transform() * physical_bone->get_joint_offset(),
+ pb->get_global_transform(),
+ pbp->get_global_transform(),
+ hjd->angular_limit_lower,
+ hjd->angular_limit_upper,
+ hjd->angular_limit_enabled,
+ points,
+ &points,
+ &points);
+ } break;
+ case PhysicalBone3D::JOINT_TYPE_SLIDER: {
+ const PhysicalBone3D::SliderJointData *sjd(static_cast<const PhysicalBone3D::SliderJointData *>(physical_bone->get_joint_data()));
+ Joint3DGizmoPlugin::CreateSliderJointGizmo(
+ physical_bone->get_joint_offset(),
+ physical_bone->get_global_transform() * physical_bone->get_joint_offset(),
+ pb->get_global_transform(),
+ pbp->get_global_transform(),
+ sjd->angular_limit_lower,
+ sjd->angular_limit_upper,
+ sjd->linear_limit_lower,
+ sjd->linear_limit_upper,
+ points,
+ &points,
+ &points);
+ } break;
+ case PhysicalBone3D::JOINT_TYPE_6DOF: {
+ const PhysicalBone3D::SixDOFJointData *sdofjd(static_cast<const PhysicalBone3D::SixDOFJointData *>(physical_bone->get_joint_data()));
+ Joint3DGizmoPlugin::CreateGeneric6DOFJointGizmo(
+ physical_bone->get_joint_offset(),
+
+ physical_bone->get_global_transform() * physical_bone->get_joint_offset(),
+ pb->get_global_transform(),
+ pbp->get_global_transform(),
+
+ sdofjd->axis_data[0].angular_limit_lower,
+ sdofjd->axis_data[0].angular_limit_upper,
+ sdofjd->axis_data[0].linear_limit_lower,
+ sdofjd->axis_data[0].linear_limit_upper,
+ sdofjd->axis_data[0].angular_limit_enabled,
+ sdofjd->axis_data[0].linear_limit_enabled,
+
+ sdofjd->axis_data[1].angular_limit_lower,
+ sdofjd->axis_data[1].angular_limit_upper,
+ sdofjd->axis_data[1].linear_limit_lower,
+ sdofjd->axis_data[1].linear_limit_upper,
+ sdofjd->axis_data[1].angular_limit_enabled,
+ sdofjd->axis_data[1].linear_limit_enabled,
+
+ sdofjd->axis_data[2].angular_limit_lower,
+ sdofjd->axis_data[2].angular_limit_upper,
+ sdofjd->axis_data[2].linear_limit_lower,
+ sdofjd->axis_data[2].linear_limit_upper,
+ sdofjd->axis_data[2].angular_limit_enabled,
+ sdofjd->axis_data[2].linear_limit_enabled,
+
+ points,
+ &points,
+ &points);
+ } break;
+ default:
+ return;
+ }
+
+ Ref<Material> material = get_material("joint_material", p_gizmo);
+
+ p_gizmo->add_collision_segments(points);
+ p_gizmo->add_lines(points, material);
+}
+
+/////
+
+RayCast3DGizmoPlugin::RayCast3DGizmoPlugin() {
+ const Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1));
+ create_material("shape_material", gizmo_color);
+ const float gizmo_value = gizmo_color.get_v();
+ const Color gizmo_color_disabled = Color(gizmo_value, gizmo_value, gizmo_value, 0.65);
+ create_material("shape_material_disabled", gizmo_color_disabled);
+}
+
+bool RayCast3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<RayCast3D>(p_spatial) != nullptr;
+}
+
+String RayCast3DGizmoPlugin::get_gizmo_name() const {
+ return "RayCast3D";
+}
+
+int RayCast3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+void RayCast3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ RayCast3D *raycast = Object::cast_to<RayCast3D>(p_gizmo->get_spatial_node());
+
+ p_gizmo->clear();
+
+ const Ref<StandardMaterial3D> material = raycast->is_enabled() ? raycast->get_debug_material() : get_material("shape_material_disabled");
+
+ p_gizmo->add_lines(raycast->get_debug_line_vertices(), material);
+
+ if (raycast->get_debug_shape_thickness() > 1) {
+ p_gizmo->add_vertices(raycast->get_debug_shape_vertices(), material, Mesh::PRIMITIVE_TRIANGLE_STRIP);
+ }
+
+ p_gizmo->add_collision_segments(raycast->get_debug_line_vertices());
+}
+
+/////
+
+void SpringArm3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ SpringArm3D *spring_arm = Object::cast_to<SpringArm3D>(p_gizmo->get_spatial_node());
+
+ p_gizmo->clear();
+
+ Vector<Vector3> lines;
+
+ lines.push_back(Vector3());
+ lines.push_back(Vector3(0, 0, 1.0) * spring_arm->get_length());
+
+ Ref<StandardMaterial3D> material = get_material("shape_material", p_gizmo);
+
+ p_gizmo->add_lines(lines, material);
+ p_gizmo->add_collision_segments(lines);
+}
+
+SpringArm3DGizmoPlugin::SpringArm3DGizmoPlugin() {
+ Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1));
+ create_material("shape_material", gizmo_color);
+}
+
+bool SpringArm3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<SpringArm3D>(p_spatial) != nullptr;
+}
+
+String SpringArm3DGizmoPlugin::get_gizmo_name() const {
+ return "SpringArm3D";
+}
+
+int SpringArm3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+/////
+
+VehicleWheel3DGizmoPlugin::VehicleWheel3DGizmoPlugin() {
+ Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1));
+ create_material("shape_material", gizmo_color);
+}
+
+bool VehicleWheel3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<VehicleWheel3D>(p_spatial) != nullptr;
+}
+
+String VehicleWheel3DGizmoPlugin::get_gizmo_name() const {
+ return "VehicleWheel3D";
+}
+
+int VehicleWheel3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+void VehicleWheel3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ VehicleWheel3D *car_wheel = Object::cast_to<VehicleWheel3D>(p_gizmo->get_spatial_node());
+
+ p_gizmo->clear();
+
+ Vector<Vector3> points;
+
+ float r = car_wheel->get_radius();
+ const int skip = 10;
+ for (int i = 0; i <= 360; i += skip) {
+ float ra = Math::deg2rad((float)i);
+ float rb = Math::deg2rad((float)i + skip);
+ Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * r;
+ Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * r;
+
+ points.push_back(Vector3(0, a.x, a.y));
+ points.push_back(Vector3(0, b.x, b.y));
+
+ const int springsec = 4;
+
+ for (int j = 0; j < springsec; j++) {
+ float t = car_wheel->get_suspension_rest_length() * 5;
+ points.push_back(Vector3(a.x, i / 360.0 * t / springsec + j * (t / springsec), a.y) * 0.2);
+ points.push_back(Vector3(b.x, (i + skip) / 360.0 * t / springsec + j * (t / springsec), b.y) * 0.2);
+ }
+ }
+
+ //travel
+ points.push_back(Vector3(0, 0, 0));
+ points.push_back(Vector3(0, car_wheel->get_suspension_rest_length(), 0));
+
+ //axis
+ points.push_back(Vector3(r * 0.2, car_wheel->get_suspension_rest_length(), 0));
+ points.push_back(Vector3(-r * 0.2, car_wheel->get_suspension_rest_length(), 0));
+ //axis
+ points.push_back(Vector3(r * 0.2, 0, 0));
+ points.push_back(Vector3(-r * 0.2, 0, 0));
+
+ //forward line
+ points.push_back(Vector3(0, -r, 0));
+ points.push_back(Vector3(0, -r, r * 2));
+ points.push_back(Vector3(0, -r, r * 2));
+ points.push_back(Vector3(r * 2 * 0.2, -r, r * 2 * 0.8));
+ points.push_back(Vector3(0, -r, r * 2));
+ points.push_back(Vector3(-r * 2 * 0.2, -r, r * 2 * 0.8));
+
+ Ref<Material> material = get_material("shape_material", p_gizmo);
+
+ p_gizmo->add_lines(points, material);
+ p_gizmo->add_collision_segments(points);
+}
+
+///////////
+
+SoftBody3DGizmoPlugin::SoftBody3DGizmoPlugin() {
+ Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1));
+ create_material("shape_material", gizmo_color);
+ create_handle_material("handles");
+}
+
+bool SoftBody3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<SoftBody3D>(p_spatial) != nullptr;
+}
+
+String SoftBody3DGizmoPlugin::get_gizmo_name() const {
+ return "SoftBody3D";
+}
+
+int SoftBody3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+bool SoftBody3DGizmoPlugin::is_selectable_when_hidden() const {
+ return true;
+}
+
+void SoftBody3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ SoftBody3D *soft_body = Object::cast_to<SoftBody3D>(p_gizmo->get_spatial_node());
+
+ p_gizmo->clear();
+
+ if (!soft_body || soft_body->get_mesh().is_null()) {
+ return;
+ }
+
+ // find mesh
+
+ Vector<Vector3> lines;
+
+ soft_body->get_mesh()->generate_debug_mesh_lines(lines);
+
+ if (!lines.size()) {
+ return;
+ }
+
+ Ref<TriangleMesh> tm = soft_body->get_mesh()->generate_triangle_mesh();
+
+ Vector<Vector3> points;
+ for (int i = 0; i < soft_body->get_mesh()->get_surface_count(); i++) {
+ Array arrays = soft_body->get_mesh()->surface_get_arrays(i);
+ ERR_CONTINUE(arrays.is_empty());
+
+ const Vector<Vector3> &vertices = arrays[Mesh::ARRAY_VERTEX];
+ points.append_array(vertices);
+ }
+
+ Ref<Material> material = get_material("shape_material", p_gizmo);
+
+ p_gizmo->add_lines(lines, material);
+ p_gizmo->add_handles(points, get_material("handles"));
+ p_gizmo->add_collision_triangles(tm);
+}
+
+String SoftBody3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ return "SoftBody3D pin point";
+}
+
+Variant SoftBody3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ SoftBody3D *soft_body = Object::cast_to<SoftBody3D>(p_gizmo->get_spatial_node());
+ return Variant(soft_body->is_point_pinned(p_id));
+}
+
+void SoftBody3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) const {
+ SoftBody3D *soft_body = Object::cast_to<SoftBody3D>(p_gizmo->get_spatial_node());
+ soft_body->pin_point_toggle(p_id);
+}
+
+bool SoftBody3DGizmoPlugin::is_handle_highlighted(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ SoftBody3D *soft_body = Object::cast_to<SoftBody3D>(p_gizmo->get_spatial_node());
+ return soft_body->is_point_pinned(p_id);
+}
+
+///////////
+
+VisibleOnScreenNotifier3DGizmoPlugin::VisibleOnScreenNotifier3DGizmoPlugin() {
+ Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/visibility_notifier", Color(0.8, 0.5, 0.7));
+ create_material("visibility_notifier_material", gizmo_color);
+ gizmo_color.a = 0.1;
+ create_material("visibility_notifier_solid_material", gizmo_color);
+ create_handle_material("handles");
+}
+
+bool VisibleOnScreenNotifier3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<VisibleOnScreenNotifier3D>(p_spatial) != nullptr;
+}
+
+String VisibleOnScreenNotifier3DGizmoPlugin::get_gizmo_name() const {
+ return "VisibleOnScreenNotifier3D";
+}
+
+int VisibleOnScreenNotifier3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+String VisibleOnScreenNotifier3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ switch (p_id) {
+ case 0:
+ return "Size X";
+ case 1:
+ return "Size Y";
+ case 2:
+ return "Size Z";
+ case 3:
+ return "Pos X";
+ case 4:
+ return "Pos Y";
+ case 5:
+ return "Pos Z";
+ }
+
+ return "";
+}
+
+Variant VisibleOnScreenNotifier3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ VisibleOnScreenNotifier3D *notifier = Object::cast_to<VisibleOnScreenNotifier3D>(p_gizmo->get_spatial_node());
+ return notifier->get_aabb();
+}
+
+void VisibleOnScreenNotifier3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const {
+ VisibleOnScreenNotifier3D *notifier = Object::cast_to<VisibleOnScreenNotifier3D>(p_gizmo->get_spatial_node());
+
+ Transform3D gt = notifier->get_global_transform();
+
+ Transform3D gi = gt.affine_inverse();
+
+ bool move = p_id >= 3;
+ p_id = p_id % 3;
+
+ AABB aabb = notifier->get_aabb();
+ Vector3 ray_from = p_camera->project_ray_origin(p_point);
+ Vector3 ray_dir = p_camera->project_ray_normal(p_point);
+
+ Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 4096) };
+
+ Vector3 ofs = aabb.position + aabb.size * 0.5;
+
+ Vector3 axis;
+ axis[p_id] = 1.0;
+
+ if (move) {
+ Vector3 ra, rb;
+ Geometry3D::get_closest_points_between_segments(ofs - axis * 4096, ofs + axis * 4096, sg[0], sg[1], ra, rb);
+
+ float d = ra[p_id];
+ if (Node3DEditor::get_singleton()->is_snap_enabled()) {
+ d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
+ }
+
+ aabb.position[p_id] = d - 1.0 - aabb.size[p_id] * 0.5;
+ notifier->set_aabb(aabb);
+
+ } else {
+ Vector3 ra, rb;
+ Geometry3D::get_closest_points_between_segments(ofs, ofs + axis * 4096, sg[0], sg[1], ra, rb);
+
+ float d = ra[p_id] - ofs[p_id];
+ if (Node3DEditor::get_singleton()->is_snap_enabled()) {
+ d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
+ }
+
+ if (d < 0.001) {
+ d = 0.001;
+ }
+ //resize
+ aabb.position[p_id] = (aabb.position[p_id] + aabb.size[p_id] * 0.5) - d;
+ aabb.size[p_id] = d * 2;
+ notifier->set_aabb(aabb);
+ }
+}
+
+void VisibleOnScreenNotifier3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) const {
+ VisibleOnScreenNotifier3D *notifier = Object::cast_to<VisibleOnScreenNotifier3D>(p_gizmo->get_spatial_node());
+
+ if (p_cancel) {
+ notifier->set_aabb(p_restore);
+ return;
+ }
+
+ UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
+ ur->create_action(TTR("Change Notifier AABB"));
+ ur->add_do_method(notifier, "set_aabb", notifier->get_aabb());
+ ur->add_undo_method(notifier, "set_aabb", p_restore);
+ ur->commit_action();
+}
+
+void VisibleOnScreenNotifier3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ VisibleOnScreenNotifier3D *notifier = Object::cast_to<VisibleOnScreenNotifier3D>(p_gizmo->get_spatial_node());
+
+ p_gizmo->clear();
+
+ Vector<Vector3> lines;
+ AABB aabb = notifier->get_aabb();
+
+ for (int i = 0; i < 12; i++) {
+ Vector3 a, b;
+ aabb.get_edge(i, a, b);
+ lines.push_back(a);
+ lines.push_back(b);
+ }
+
+ Vector<Vector3> handles;
+
+ for (int i = 0; i < 3; i++) {
+ Vector3 ax;
+ ax[i] = aabb.position[i] + aabb.size[i];
+ ax[(i + 1) % 3] = aabb.position[(i + 1) % 3] + aabb.size[(i + 1) % 3] * 0.5;
+ ax[(i + 2) % 3] = aabb.position[(i + 2) % 3] + aabb.size[(i + 2) % 3] * 0.5;
+ handles.push_back(ax);
+ }
+
+ Vector3 center = aabb.position + aabb.size * 0.5;
+ for (int i = 0; i < 3; i++) {
+ Vector3 ax;
+ ax[i] = 1.0;
+ handles.push_back(center + ax);
+ lines.push_back(center);
+ lines.push_back(center + ax);
+ }
+
+ Ref<Material> material = get_material("visibility_notifier_material", p_gizmo);
+
+ p_gizmo->add_lines(lines, material);
+ p_gizmo->add_collision_segments(lines);
+
+ if (p_gizmo->is_selected()) {
+ Ref<Material> solid_material = get_material("visibility_notifier_solid_material", p_gizmo);
+ p_gizmo->add_solid_box(solid_material, aabb.get_size(), aabb.get_position() + aabb.get_size() / 2.0);
+ }
+
+ p_gizmo->add_handles(handles, get_material("handles"));
+}
+
+////
+
+CPUParticles3DGizmoPlugin::CPUParticles3DGizmoPlugin() {
+ create_icon_material("particles_icon", Node3DEditor::get_singleton()->get_theme_icon(SNAME("GizmoCPUParticles3D"), SNAME("EditorIcons")));
+}
+
+bool CPUParticles3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<CPUParticles3D>(p_spatial) != nullptr;
+}
+
+String CPUParticles3DGizmoPlugin::get_gizmo_name() const {
+ return "CPUParticles3D";
+}
+
+int CPUParticles3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+bool CPUParticles3DGizmoPlugin::is_selectable_when_hidden() const {
+ return true;
+}
+
+void CPUParticles3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ Ref<Material> icon = get_material("particles_icon", p_gizmo);
+ p_gizmo->add_unscaled_billboard(icon, 0.05);
+}
+
+////
+
+GPUParticles3DGizmoPlugin::GPUParticles3DGizmoPlugin() {
+ Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/particles", Color(0.8, 0.7, 0.4));
+ create_material("particles_material", gizmo_color);
+ gizmo_color.a = 0.1;
+ create_material("particles_solid_material", gizmo_color);
+ create_icon_material("particles_icon", Node3DEditor::get_singleton()->get_theme_icon(SNAME("GizmoGPUParticles3D"), SNAME("EditorIcons")));
+ create_handle_material("handles");
+}
+
+bool GPUParticles3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<GPUParticles3D>(p_spatial) != nullptr;
+}
+
+String GPUParticles3DGizmoPlugin::get_gizmo_name() const {
+ return "GPUParticles3D";
+}
+
+int GPUParticles3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+bool GPUParticles3DGizmoPlugin::is_selectable_when_hidden() const {
+ return true;
+}
+
+String GPUParticles3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ switch (p_id) {
+ case 0:
+ return "Size X";
+ case 1:
+ return "Size Y";
+ case 2:
+ return "Size Z";
+ case 3:
+ return "Pos X";
+ case 4:
+ return "Pos Y";
+ case 5:
+ return "Pos Z";
+ }
+
+ return "";
+}
+
+Variant GPUParticles3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(p_gizmo->get_spatial_node());
+ return particles->get_visibility_aabb();
+}
+
+void GPUParticles3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const {
+ GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(p_gizmo->get_spatial_node());
+
+ Transform3D gt = particles->get_global_transform();
+ Transform3D gi = gt.affine_inverse();
+
+ bool move = p_id >= 3;
+ p_id = p_id % 3;
+
+ AABB aabb = particles->get_visibility_aabb();
+ Vector3 ray_from = p_camera->project_ray_origin(p_point);
+ Vector3 ray_dir = p_camera->project_ray_normal(p_point);
+
+ Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 4096) };
+
+ Vector3 ofs = aabb.position + aabb.size * 0.5;
+
+ Vector3 axis;
+ axis[p_id] = 1.0;
+
+ if (move) {
+ Vector3 ra, rb;
+ Geometry3D::get_closest_points_between_segments(ofs - axis * 4096, ofs + axis * 4096, sg[0], sg[1], ra, rb);
+
+ float d = ra[p_id];
+ if (Node3DEditor::get_singleton()->is_snap_enabled()) {
+ d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
+ }
+
+ aabb.position[p_id] = d - 1.0 - aabb.size[p_id] * 0.5;
+ particles->set_visibility_aabb(aabb);
+
+ } else {
+ Vector3 ra, rb;
+ Geometry3D::get_closest_points_between_segments(ofs, ofs + axis * 4096, sg[0], sg[1], ra, rb);
+
+ float d = ra[p_id] - ofs[p_id];
+ if (Node3DEditor::get_singleton()->is_snap_enabled()) {
+ d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
+ }
+
+ if (d < 0.001) {
+ d = 0.001;
+ }
+ //resize
+ aabb.position[p_id] = (aabb.position[p_id] + aabb.size[p_id] * 0.5) - d;
+ aabb.size[p_id] = d * 2;
+ particles->set_visibility_aabb(aabb);
+ }
+}
+
+void GPUParticles3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) const {
+ GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(p_gizmo->get_spatial_node());
+
+ if (p_cancel) {
+ particles->set_visibility_aabb(p_restore);
+ return;
+ }
+
+ UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
+ ur->create_action(TTR("Change Particles AABB"));
+ ur->add_do_method(particles, "set_visibility_aabb", particles->get_visibility_aabb());
+ ur->add_undo_method(particles, "set_visibility_aabb", p_restore);
+ ur->commit_action();
+}
+
+void GPUParticles3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(p_gizmo->get_spatial_node());
+
+ p_gizmo->clear();
+
+ Vector<Vector3> lines;
+ AABB aabb = particles->get_visibility_aabb();
+
+ for (int i = 0; i < 12; i++) {
+ Vector3 a, b;
+ aabb.get_edge(i, a, b);
+ lines.push_back(a);
+ lines.push_back(b);
+ }
+
+ Vector<Vector3> handles;
+
+ for (int i = 0; i < 3; i++) {
+ Vector3 ax;
+ ax[i] = aabb.position[i] + aabb.size[i];
+ ax[(i + 1) % 3] = aabb.position[(i + 1) % 3] + aabb.size[(i + 1) % 3] * 0.5;
+ ax[(i + 2) % 3] = aabb.position[(i + 2) % 3] + aabb.size[(i + 2) % 3] * 0.5;
+ handles.push_back(ax);
+ }
+
+ Vector3 center = aabb.position + aabb.size * 0.5;
+ for (int i = 0; i < 3; i++) {
+ Vector3 ax;
+ ax[i] = 1.0;
+ handles.push_back(center + ax);
+ lines.push_back(center);
+ lines.push_back(center + ax);
+ }
+
+ Ref<Material> material = get_material("particles_material", p_gizmo);
+ Ref<Material> icon = get_material("particles_icon", p_gizmo);
+
+ p_gizmo->add_lines(lines, material);
+
+ if (p_gizmo->is_selected()) {
+ Ref<Material> solid_material = get_material("particles_solid_material", p_gizmo);
+ p_gizmo->add_solid_box(solid_material, aabb.get_size(), aabb.get_position() + aabb.get_size() / 2.0);
+ }
+
+ p_gizmo->add_handles(handles, get_material("handles"));
+ p_gizmo->add_unscaled_billboard(icon, 0.05);
+}
+
+////
+
+GPUParticlesCollision3DGizmoPlugin::GPUParticlesCollision3DGizmoPlugin() {
+ Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/particle_collision", Color(0.5, 0.7, 1));
+ create_material("shape_material", gizmo_color);
+ gizmo_color.a = 0.15;
+ create_material("shape_material_internal", gizmo_color);
+
+ create_handle_material("handles");
+}
+
+bool GPUParticlesCollision3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return (Object::cast_to<GPUParticlesCollision3D>(p_spatial) != nullptr) || (Object::cast_to<GPUParticlesAttractor3D>(p_spatial) != nullptr);
+}
+
+String GPUParticlesCollision3DGizmoPlugin::get_gizmo_name() const {
+ return "GPUParticlesCollision3D";
+}
+
+int GPUParticlesCollision3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+String GPUParticlesCollision3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ const Node3D *cs = p_gizmo->get_spatial_node();
+
+ if (Object::cast_to<GPUParticlesCollisionSphere>(cs) || Object::cast_to<GPUParticlesAttractorSphere>(cs)) {
+ return "Radius";
+ }
+
+ if (Object::cast_to<GPUParticlesCollisionBox>(cs) || Object::cast_to<GPUParticlesAttractorBox>(cs) || Object::cast_to<GPUParticlesAttractorVectorField>(cs) || Object::cast_to<GPUParticlesCollisionSDF>(cs) || Object::cast_to<GPUParticlesCollisionHeightField>(cs)) {
+ return "Extents";
+ }
+
+ return "";
+}
+
+Variant GPUParticlesCollision3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ const Node3D *cs = p_gizmo->get_spatial_node();
+
+ if (Object::cast_to<GPUParticlesCollisionSphere>(cs) || Object::cast_to<GPUParticlesAttractorSphere>(cs)) {
+ return p_gizmo->get_spatial_node()->call("get_radius");
+ }
+
+ if (Object::cast_to<GPUParticlesCollisionBox>(cs) || Object::cast_to<GPUParticlesAttractorBox>(cs) || Object::cast_to<GPUParticlesAttractorVectorField>(cs) || Object::cast_to<GPUParticlesCollisionSDF>(cs) || Object::cast_to<GPUParticlesCollisionHeightField>(cs)) {
+ return Vector3(p_gizmo->get_spatial_node()->call("get_extents"));
+ }
+
+ return Variant();
+}
+
+void GPUParticlesCollision3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const {
+ Node3D *sn = p_gizmo->get_spatial_node();
+
+ Transform3D gt = sn->get_global_transform();
+ Transform3D gi = gt.affine_inverse();
+
+ Vector3 ray_from = p_camera->project_ray_origin(p_point);
+ Vector3 ray_dir = p_camera->project_ray_normal(p_point);
+
+ Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 4096) };
+
+ if (Object::cast_to<GPUParticlesCollisionSphere>(sn) || Object::cast_to<GPUParticlesAttractorSphere>(sn)) {
+ Vector3 ra, rb;
+ Geometry3D::get_closest_points_between_segments(Vector3(), Vector3(4096, 0, 0), sg[0], sg[1], ra, rb);
+ float d = ra.x;
+ if (Node3DEditor::get_singleton()->is_snap_enabled()) {
+ d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
+ }
+
+ if (d < 0.001) {
+ d = 0.001;
+ }
+
+ sn->call("set_radius", d);
+ }
+
+ if (Object::cast_to<GPUParticlesCollisionBox>(sn) || Object::cast_to<GPUParticlesAttractorBox>(sn) || Object::cast_to<GPUParticlesAttractorVectorField>(sn) || Object::cast_to<GPUParticlesCollisionSDF>(sn) || Object::cast_to<GPUParticlesCollisionHeightField>(sn)) {
+ Vector3 axis;
+ axis[p_id] = 1.0;
+ Vector3 ra, rb;
+ Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb);
+ float d = ra[p_id];
+ if (Node3DEditor::get_singleton()->is_snap_enabled()) {
+ d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
+ }
+
+ if (d < 0.001) {
+ d = 0.001;
+ }
+
+ Vector3 he = sn->call("get_extents");
+ he[p_id] = d;
+ sn->call("set_extents", he);
+ }
+}
+
+void GPUParticlesCollision3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) const {
+ Node3D *sn = p_gizmo->get_spatial_node();
+
+ if (Object::cast_to<GPUParticlesCollisionSphere>(sn) || Object::cast_to<GPUParticlesAttractorSphere>(sn)) {
+ if (p_cancel) {
+ sn->call("set_radius", p_restore);
+ return;
+ }
+
+ UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
+ ur->create_action(TTR("Change Radius"));
+ ur->add_do_method(sn, "set_radius", sn->call("get_radius"));
+ ur->add_undo_method(sn, "set_radius", p_restore);
+ ur->commit_action();
+ }
+
+ if (Object::cast_to<GPUParticlesCollisionBox>(sn) || Object::cast_to<GPUParticlesAttractorBox>(sn) || Object::cast_to<GPUParticlesAttractorVectorField>(sn) || Object::cast_to<GPUParticlesCollisionSDF>(sn) || Object::cast_to<GPUParticlesCollisionHeightField>(sn)) {
+ if (p_cancel) {
+ sn->call("set_extents", p_restore);
+ return;
+ }
+
+ UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
+ ur->create_action(TTR("Change Box Shape Extents"));
+ ur->add_do_method(sn, "set_extents", sn->call("get_extents"));
+ ur->add_undo_method(sn, "set_extents", p_restore);
+ ur->commit_action();
+ }
+}
+
+void GPUParticlesCollision3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ Node3D *cs = p_gizmo->get_spatial_node();
+
+ print_line("redraw request " + itos(cs != nullptr));
+ p_gizmo->clear();
+
+ const Ref<Material> material =
+ get_material("shape_material", p_gizmo);
+ const Ref<Material> material_internal =
+ get_material("shape_material_internal", p_gizmo);
+
+ Ref<Material> handles_material = get_material("handles");
+
+ if (Object::cast_to<GPUParticlesCollisionSphere>(cs) || Object::cast_to<GPUParticlesAttractorSphere>(cs)) {
+ float r = cs->call("get_radius");
+
+ Vector<Vector3> points;
+
+ for (int i = 0; i <= 360; i++) {
+ float ra = Math::deg2rad((float)i);
+ float rb = Math::deg2rad((float)i + 1);
+ Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * r;
+ Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * r;
+
+ points.push_back(Vector3(a.x, 0, a.y));
+ points.push_back(Vector3(b.x, 0, b.y));
+ points.push_back(Vector3(0, a.x, a.y));
+ points.push_back(Vector3(0, b.x, b.y));
+ points.push_back(Vector3(a.x, a.y, 0));
+ points.push_back(Vector3(b.x, b.y, 0));
+ }
+
+ Vector<Vector3> collision_segments;
+
+ for (int i = 0; i < 64; i++) {
+ float ra = i * (Math_TAU / 64.0);
+ float rb = (i + 1) * (Math_TAU / 64.0);
+ Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * r;
+ Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * r;
+
+ collision_segments.push_back(Vector3(a.x, 0, a.y));
+ collision_segments.push_back(Vector3(b.x, 0, b.y));
+ collision_segments.push_back(Vector3(0, a.x, a.y));
+ collision_segments.push_back(Vector3(0, b.x, b.y));
+ collision_segments.push_back(Vector3(a.x, a.y, 0));
+ collision_segments.push_back(Vector3(b.x, b.y, 0));
+ }
+
+ p_gizmo->add_lines(points, material);
+ p_gizmo->add_collision_segments(collision_segments);
+ Vector<Vector3> handles;
+ handles.push_back(Vector3(r, 0, 0));
+ p_gizmo->add_handles(handles, handles_material);
+ }
+
+ if (Object::cast_to<GPUParticlesCollisionBox>(cs) || Object::cast_to<GPUParticlesAttractorBox>(cs) || Object::cast_to<GPUParticlesAttractorVectorField>(cs) || Object::cast_to<GPUParticlesCollisionSDF>(cs) || Object::cast_to<GPUParticlesCollisionHeightField>(cs)) {
+ Vector<Vector3> lines;
+ AABB aabb;
+ aabb.position = -cs->call("get_extents").operator Vector3();
+ aabb.size = aabb.position * -2;
+
+ for (int i = 0; i < 12; i++) {
+ Vector3 a, b;
+ aabb.get_edge(i, a, b);
+ lines.push_back(a);
+ lines.push_back(b);
+ }
+
+ Vector<Vector3> handles;
+
+ for (int i = 0; i < 3; i++) {
+ Vector3 ax;
+ ax[i] = cs->call("get_extents").operator Vector3()[i];
+ handles.push_back(ax);
+ }
+
+ p_gizmo->add_lines(lines, material);
+ p_gizmo->add_collision_segments(lines);
+ p_gizmo->add_handles(handles, handles_material);
+
+ GPUParticlesCollisionSDF *col_sdf = Object::cast_to<GPUParticlesCollisionSDF>(cs);
+ if (col_sdf) {
+ static const int subdivs[GPUParticlesCollisionSDF::RESOLUTION_MAX] = { 16, 32, 64, 128, 256, 512 };
+ int subdiv = subdivs[col_sdf->get_resolution()];
+ float cell_size = aabb.get_longest_axis_size() / subdiv;
+
+ lines.clear();
+
+ for (int i = 1; i < subdiv; i++) {
+ for (int j = 0; j < 3; j++) {
+ if (cell_size * i > aabb.size[j]) {
+ continue;
+ }
+
+ Vector2 dir;
+ dir[j] = 1.0;
+ Vector2 ta, tb;
+ int j_n1 = (j + 1) % 3;
+ int j_n2 = (j + 2) % 3;
+ ta[j_n1] = 1.0;
+ tb[j_n2] = 1.0;
+
+ for (int k = 0; k < 4; k++) {
+ Vector3 from = aabb.position, to = aabb.position;
+ from[j] += cell_size * i;
+ to[j] += cell_size * i;
+
+ if (k & 1) {
+ to[j_n1] += aabb.size[j_n1];
+ } else {
+ to[j_n2] += aabb.size[j_n2];
+ }
+
+ if (k & 2) {
+ from[j_n1] += aabb.size[j_n1];
+ from[j_n2] += aabb.size[j_n2];
+ }
+
+ lines.push_back(from);
+ lines.push_back(to);
+ }
+ }
+ }
+
+ p_gizmo->add_lines(lines, material_internal);
+ }
+ }
+}
+
+/////
+
+////
+
+ReflectionProbeGizmoPlugin::ReflectionProbeGizmoPlugin() {
+ Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/reflection_probe", Color(0.6, 1, 0.5));
+
+ create_material("reflection_probe_material", gizmo_color);
+
+ gizmo_color.a = 0.5;
+ create_material("reflection_internal_material", gizmo_color);
+
+ gizmo_color.a = 0.1;
+ create_material("reflection_probe_solid_material", gizmo_color);
+
+ create_icon_material("reflection_probe_icon", Node3DEditor::get_singleton()->get_theme_icon(SNAME("GizmoReflectionProbe"), SNAME("EditorIcons")));
+ create_handle_material("handles");
+}
+
+bool ReflectionProbeGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<ReflectionProbe>(p_spatial) != nullptr;
+}
+
+String ReflectionProbeGizmoPlugin::get_gizmo_name() const {
+ return "ReflectionProbe";
+}
+
+int ReflectionProbeGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+String ReflectionProbeGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ switch (p_id) {
+ case 0:
+ return "Extents X";
+ case 1:
+ return "Extents Y";
+ case 2:
+ return "Extents Z";
+ case 3:
+ return "Origin X";
+ case 4:
+ return "Origin Y";
+ case 5:
+ return "Origin Z";
+ }
+
+ return "";
+}
+
+Variant ReflectionProbeGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ ReflectionProbe *probe = Object::cast_to<ReflectionProbe>(p_gizmo->get_spatial_node());
+ return AABB(probe->get_extents(), probe->get_origin_offset());
+}
+
+void ReflectionProbeGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const {
+ ReflectionProbe *probe = Object::cast_to<ReflectionProbe>(p_gizmo->get_spatial_node());
+ Transform3D gt = probe->get_global_transform();
+
+ Transform3D gi = gt.affine_inverse();
+
+ if (p_id < 3) {
+ Vector3 extents = probe->get_extents();
+
+ Vector3 ray_from = p_camera->project_ray_origin(p_point);
+ Vector3 ray_dir = p_camera->project_ray_normal(p_point);
+
+ Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 16384) };
+
+ Vector3 axis;
+ axis[p_id] = 1.0;
+
+ Vector3 ra, rb;
+ Geometry3D::get_closest_points_between_segments(Vector3(), axis * 16384, sg[0], sg[1], ra, rb);
+ float d = ra[p_id];
+ if (Node3DEditor::get_singleton()->is_snap_enabled()) {
+ d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
+ }
+
+ if (d < 0.001) {
+ d = 0.001;
+ }
+
+ extents[p_id] = d;
+ probe->set_extents(extents);
+ } else {
+ p_id -= 3;
+
+ Vector3 origin = probe->get_origin_offset();
+ origin[p_id] = 0;
+
+ Vector3 ray_from = p_camera->project_ray_origin(p_point);
+ Vector3 ray_dir = p_camera->project_ray_normal(p_point);
+
+ Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 16384) };
+
+ Vector3 axis;
+ axis[p_id] = 1.0;
+
+ Vector3 ra, rb;
+ Geometry3D::get_closest_points_between_segments(origin - axis * 16384, origin + axis * 16384, sg[0], sg[1], ra, rb);
+ // Adjust the actual position to account for the gizmo handle position
+ float d = ra[p_id] + 0.25;
+ if (Node3DEditor::get_singleton()->is_snap_enabled()) {
+ d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
+ }
+
+ origin[p_id] = d;
+ probe->set_origin_offset(origin);
+ }
+}
+
+void ReflectionProbeGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) const {
+ ReflectionProbe *probe = Object::cast_to<ReflectionProbe>(p_gizmo->get_spatial_node());
+
+ AABB restore = p_restore;
+
+ if (p_cancel) {
+ probe->set_extents(restore.position);
+ probe->set_origin_offset(restore.size);
+ return;
+ }
+
+ UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
+ ur->create_action(TTR("Change Probe Extents"));
+ ur->add_do_method(probe, "set_extents", probe->get_extents());
+ ur->add_do_method(probe, "set_origin_offset", probe->get_origin_offset());
+ ur->add_undo_method(probe, "set_extents", restore.position);
+ ur->add_undo_method(probe, "set_origin_offset", restore.size);
+ ur->commit_action();
+}
+
+void ReflectionProbeGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ ReflectionProbe *probe = Object::cast_to<ReflectionProbe>(p_gizmo->get_spatial_node());
+
+ p_gizmo->clear();
+
+ Vector<Vector3> lines;
+ Vector<Vector3> internal_lines;
+ Vector3 extents = probe->get_extents();
+
+ AABB aabb;
+ aabb.position = -extents;
+ aabb.size = extents * 2;
+
+ for (int i = 0; i < 12; i++) {
+ Vector3 a, b;
+ aabb.get_edge(i, a, b);
+ lines.push_back(a);
+ lines.push_back(b);
+ }
+
+ for (int i = 0; i < 8; i++) {
+ Vector3 ep = aabb.get_endpoint(i);
+ internal_lines.push_back(probe->get_origin_offset());
+ internal_lines.push_back(ep);
+ }
+
+ Vector<Vector3> handles;
+
+ for (int i = 0; i < 3; i++) {
+ Vector3 ax;
+ ax[i] = aabb.position[i] + aabb.size[i];
+ handles.push_back(ax);
+ }
+
+ for (int i = 0; i < 3; i++) {
+ Vector3 orig_handle = probe->get_origin_offset();
+ orig_handle[i] -= 0.25;
+ lines.push_back(orig_handle);
+ handles.push_back(orig_handle);
+
+ orig_handle[i] += 0.5;
+ lines.push_back(orig_handle);
+ }
+
+ Ref<Material> material = get_material("reflection_probe_material", p_gizmo);
+ Ref<Material> material_internal = get_material("reflection_internal_material", p_gizmo);
+ Ref<Material> icon = get_material("reflection_probe_icon", p_gizmo);
+
+ p_gizmo->add_lines(lines, material);
+ p_gizmo->add_lines(internal_lines, material_internal);
+
+ if (p_gizmo->is_selected()) {
+ Ref<Material> solid_material = get_material("reflection_probe_solid_material", p_gizmo);
+ p_gizmo->add_solid_box(solid_material, probe->get_extents() * 2.0);
+ }
+
+ p_gizmo->add_unscaled_billboard(icon, 0.05);
+ p_gizmo->add_handles(handles, get_material("handles"));
+}
+
+///////////////////////////////
+
+////
+
+DecalGizmoPlugin::DecalGizmoPlugin() {
+ Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/decal", Color(0.6, 0.5, 1.0));
+
+ create_material("decal_material", gizmo_color);
+
+ create_handle_material("handles");
+}
+
+bool DecalGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<Decal>(p_spatial) != nullptr;
+}
+
+String DecalGizmoPlugin::get_gizmo_name() const {
+ return "Decal";
+}
+
+int DecalGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+String DecalGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ switch (p_id) {
+ case 0:
+ return "Extents X";
+ case 1:
+ return "Extents Y";
+ case 2:
+ return "Extents Z";
+ }
+
+ return "";
+}
+
+Variant DecalGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ Decal *decal = Object::cast_to<Decal>(p_gizmo->get_spatial_node());
+ return decal->get_extents();
+}
+
+void DecalGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const {
+ Decal *decal = Object::cast_to<Decal>(p_gizmo->get_spatial_node());
+ Transform3D gt = decal->get_global_transform();
+
+ Transform3D gi = gt.affine_inverse();
+
+ Vector3 extents = decal->get_extents();
+
+ Vector3 ray_from = p_camera->project_ray_origin(p_point);
+ Vector3 ray_dir = p_camera->project_ray_normal(p_point);
+
+ Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 16384) };
+
+ Vector3 axis;
+ axis[p_id] = 1.0;
+
+ Vector3 ra, rb;
+ Geometry3D::get_closest_points_between_segments(Vector3(), axis * 16384, sg[0], sg[1], ra, rb);
+ float d = ra[p_id];
+ if (Node3DEditor::get_singleton()->is_snap_enabled()) {
+ d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
+ }
+
+ if (d < 0.001) {
+ d = 0.001;
+ }
+
+ extents[p_id] = d;
+ decal->set_extents(extents);
+}
+
+void DecalGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) const {
+ Decal *decal = Object::cast_to<Decal>(p_gizmo->get_spatial_node());
+
+ Vector3 restore = p_restore;
+
+ if (p_cancel) {
+ decal->set_extents(restore);
+ return;
+ }
+
+ UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
+ ur->create_action(TTR("Change Decal Extents"));
+ ur->add_do_method(decal, "set_extents", decal->get_extents());
+ ur->add_undo_method(decal, "set_extents", restore);
+ ur->commit_action();
+}
+
+void DecalGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ Decal *decal = Object::cast_to<Decal>(p_gizmo->get_spatial_node());
+
+ p_gizmo->clear();
+
+ Vector<Vector3> lines;
+ Vector3 extents = decal->get_extents();
+
+ AABB aabb;
+ aabb.position = -extents;
+ aabb.size = extents * 2;
+
+ for (int i = 0; i < 12; i++) {
+ Vector3 a, b;
+ aabb.get_edge(i, a, b);
+ if (a.y == b.y) {
+ lines.push_back(a);
+ lines.push_back(b);
+ } else {
+ Vector3 ah = a.lerp(b, 0.2);
+ lines.push_back(a);
+ lines.push_back(ah);
+ Vector3 bh = b.lerp(a, 0.2);
+ lines.push_back(b);
+ lines.push_back(bh);
+ }
+ }
+
+ lines.push_back(Vector3(0, extents.y, 0));
+ lines.push_back(Vector3(0, extents.y * 1.2, 0));
+
+ Vector<Vector3> handles;
+
+ for (int i = 0; i < 3; i++) {
+ Vector3 ax;
+ ax[i] = aabb.position[i] + aabb.size[i];
+ handles.push_back(ax);
+ }
+
+ Ref<Material> material = get_material("decal_material", p_gizmo);
+
+ p_gizmo->add_lines(lines, material);
+ p_gizmo->add_handles(handles, get_material("handles"));
+}
+
+///////////////////////////////
+VoxelGIGizmoPlugin::VoxelGIGizmoPlugin() {
+ Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/voxel_gi", Color(0.5, 1, 0.6));
+
+ create_material("voxel_gi_material", gizmo_color);
+
+ // This gizmo draws a lot of lines. Use a low opacity to make it not too intrusive.
+ gizmo_color.a = 0.1;
+ create_material("voxel_gi_internal_material", gizmo_color);
+
+ gizmo_color.a = 0.05;
+ create_material("voxel_gi_solid_material", gizmo_color);
+
+ create_icon_material("voxel_gi_icon", Node3DEditor::get_singleton()->get_theme_icon(SNAME("GizmoVoxelGI"), SNAME("EditorIcons")));
+ create_handle_material("handles");
+}
+
+bool VoxelGIGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<VoxelGI>(p_spatial) != nullptr;
+}
+
+String VoxelGIGizmoPlugin::get_gizmo_name() const {
+ return "VoxelGI";
+}
+
+int VoxelGIGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+String VoxelGIGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ switch (p_id) {
+ case 0:
+ return "Extents X";
+ case 1:
+ return "Extents Y";
+ case 2:
+ return "Extents Z";
+ }
+
+ return "";
+}
+
+Variant VoxelGIGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ VoxelGI *probe = Object::cast_to<VoxelGI>(p_gizmo->get_spatial_node());
+ return probe->get_extents();
+}
+
+void VoxelGIGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const {
+ VoxelGI *probe = Object::cast_to<VoxelGI>(p_gizmo->get_spatial_node());
+
+ Transform3D gt = probe->get_global_transform();
+ Transform3D gi = gt.affine_inverse();
+
+ Vector3 extents = probe->get_extents();
+
+ Vector3 ray_from = p_camera->project_ray_origin(p_point);
+ Vector3 ray_dir = p_camera->project_ray_normal(p_point);
+
+ Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 16384) };
+
+ Vector3 axis;
+ axis[p_id] = 1.0;
+
+ Vector3 ra, rb;
+ Geometry3D::get_closest_points_between_segments(Vector3(), axis * 16384, sg[0], sg[1], ra, rb);
+ float d = ra[p_id];
+ if (Node3DEditor::get_singleton()->is_snap_enabled()) {
+ d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
+ }
+
+ if (d < 0.001) {
+ d = 0.001;
+ }
+
+ extents[p_id] = d;
+ probe->set_extents(extents);
+}
+
+void VoxelGIGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) const {
+ VoxelGI *probe = Object::cast_to<VoxelGI>(p_gizmo->get_spatial_node());
+
+ Vector3 restore = p_restore;
+
+ if (p_cancel) {
+ probe->set_extents(restore);
+ return;
+ }
+
+ UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
+ ur->create_action(TTR("Change Probe Extents"));
+ ur->add_do_method(probe, "set_extents", probe->get_extents());
+ ur->add_undo_method(probe, "set_extents", restore);
+ ur->commit_action();
+}
+
+void VoxelGIGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ VoxelGI *probe = Object::cast_to<VoxelGI>(p_gizmo->get_spatial_node());
+
+ Ref<Material> material = get_material("voxel_gi_material", p_gizmo);
+ Ref<Material> icon = get_material("voxel_gi_icon", p_gizmo);
+ Ref<Material> material_internal = get_material("voxel_gi_internal_material", p_gizmo);
+
+ p_gizmo->clear();
+
+ Vector<Vector3> lines;
+ Vector3 extents = probe->get_extents();
+
+ static const int subdivs[VoxelGI::SUBDIV_MAX] = { 64, 128, 256, 512 };
+
+ AABB aabb = AABB(-extents, extents * 2);
+ int subdiv = subdivs[probe->get_subdiv()];
+ float cell_size = aabb.get_longest_axis_size() / subdiv;
+
+ for (int i = 0; i < 12; i++) {
+ Vector3 a, b;
+ aabb.get_edge(i, a, b);
+ lines.push_back(a);
+ lines.push_back(b);
+ }
+
+ p_gizmo->add_lines(lines, material);
+
+ lines.clear();
+
+ for (int i = 1; i < subdiv; i++) {
+ for (int j = 0; j < 3; j++) {
+ if (cell_size * i > aabb.size[j]) {
+ continue;
+ }
+
+ Vector2 dir;
+ dir[j] = 1.0;
+ Vector2 ta, tb;
+ int j_n1 = (j + 1) % 3;
+ int j_n2 = (j + 2) % 3;
+ ta[j_n1] = 1.0;
+ tb[j_n2] = 1.0;
+
+ for (int k = 0; k < 4; k++) {
+ Vector3 from = aabb.position, to = aabb.position;
+ from[j] += cell_size * i;
+ to[j] += cell_size * i;
+
+ if (k & 1) {
+ to[j_n1] += aabb.size[j_n1];
+ } else {
+ to[j_n2] += aabb.size[j_n2];
+ }
+
+ if (k & 2) {
+ from[j_n1] += aabb.size[j_n1];
+ from[j_n2] += aabb.size[j_n2];
+ }
+
+ lines.push_back(from);
+ lines.push_back(to);
+ }
+ }
+ }
+
+ p_gizmo->add_lines(lines, material_internal);
+
+ Vector<Vector3> handles;
+
+ for (int i = 0; i < 3; i++) {
+ Vector3 ax;
+ ax[i] = aabb.position[i] + aabb.size[i];
+ handles.push_back(ax);
+ }
+
+ if (p_gizmo->is_selected()) {
+ Ref<Material> solid_material = get_material("voxel_gi_solid_material", p_gizmo);
+ p_gizmo->add_solid_box(solid_material, aabb.get_size());
+ }
+
+ p_gizmo->add_unscaled_billboard(icon, 0.05);
+ p_gizmo->add_handles(handles, get_material("handles"));
+}
+
+////
+
+LightmapGIGizmoPlugin::LightmapGIGizmoPlugin() {
+ Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/lightmap_lines", Color(0.5, 0.6, 1));
+
+ gizmo_color.a = 0.1;
+ create_material("lightmap_lines", gizmo_color);
+
+ Ref<StandardMaterial3D> mat = memnew(StandardMaterial3D);
+ mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
+ mat->set_cull_mode(StandardMaterial3D::CULL_DISABLED);
+ mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
+ mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, false);
+
+ add_material("lightmap_probe_material", mat);
+
+ create_icon_material("baked_indirect_light_icon", Node3DEditor::get_singleton()->get_theme_icon(SNAME("GizmoLightmapGI"), SNAME("EditorIcons")));
+}
+
+String LightmapGIGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ return "";
+}
+
+Variant LightmapGIGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ return Variant();
+}
+
+void LightmapGIGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const {
+}
+
+void LightmapGIGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) const {
+}
+
+bool LightmapGIGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<LightmapGI>(p_spatial) != nullptr;
+}
+
+String LightmapGIGizmoPlugin::get_gizmo_name() const {
+ return "LightmapGI";
+}
+
+int LightmapGIGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+void LightmapGIGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ Ref<Material> icon = get_material("baked_indirect_light_icon", p_gizmo);
+ LightmapGI *baker = Object::cast_to<LightmapGI>(p_gizmo->get_spatial_node());
+ Ref<LightmapGIData> data = baker->get_light_data();
+
+ p_gizmo->add_unscaled_billboard(icon, 0.05);
+
+ if (data.is_null()) {
+ return;
+ }
+
+ Ref<Material> material_lines = get_material("lightmap_lines", p_gizmo);
+ Ref<Material> material_probes = get_material("lightmap_probe_material", p_gizmo);
+
+ p_gizmo->clear();
+
+ Vector<Vector3> lines;
+ Set<Vector2i> lines_found;
+
+ Vector<Vector3> points = data->get_capture_points();
+ if (points.size() == 0) {
+ return;
+ }
+ Vector<Color> sh = data->get_capture_sh();
+ if (sh.size() != points.size() * 9) {
+ return;
+ }
+
+ Vector<int> tetrahedrons = data->get_capture_tetrahedra();
+
+ for (int i = 0; i < tetrahedrons.size(); i += 4) {
+ for (int j = 0; j < 4; j++) {
+ for (int k = j + 1; k < 4; k++) {
+ Vector2i pair;
+ pair.x = tetrahedrons[i + j];
+ pair.y = tetrahedrons[i + k];
+
+ if (pair.y < pair.x) {
+ SWAP(pair.x, pair.y);
+ }
+ if (lines_found.has(pair)) {
+ continue;
+ }
+ lines_found.insert(pair);
+ lines.push_back(points[pair.x]);
+ lines.push_back(points[pair.y]);
+ }
+ }
+ }
+
+ p_gizmo->add_lines(lines, material_lines);
+
+ int stack_count = 8;
+ int sector_count = 16;
+
+ float sector_step = (Math_PI * 2.0) / sector_count;
+ float stack_step = Math_PI / stack_count;
+
+ Vector<Vector3> vertices;
+ Vector<Color> colors;
+ Vector<int> indices;
+ float radius = 0.3;
+
+ for (int p = 0; p < points.size(); p++) {
+ int vertex_base = vertices.size();
+ Vector3 sh_col[9];
+ for (int i = 0; i < 9; i++) {
+ sh_col[i].x = sh[p * 9 + i].r;
+ sh_col[i].y = sh[p * 9 + i].g;
+ sh_col[i].z = sh[p * 9 + i].b;
+ }
+
+ for (int i = 0; i <= stack_count; ++i) {
+ float stack_angle = Math_PI / 2 - i * stack_step; // starting from pi/2 to -pi/2
+ float xy = radius * Math::cos(stack_angle); // r * cos(u)
+ float z = radius * Math::sin(stack_angle); // r * sin(u)
+
+ // add (sector_count+1) vertices per stack
+ // the first and last vertices have same position and normal, but different tex coords
+ for (int j = 0; j <= sector_count; ++j) {
+ float sector_angle = j * sector_step; // starting from 0 to 2pi
+
+ // vertex position (x, y, z)
+ float x = xy * Math::cos(sector_angle); // r * cos(u) * cos(v)
+ float y = xy * Math::sin(sector_angle); // r * cos(u) * sin(v)
+
+ Vector3 n = Vector3(x, z, y);
+ vertices.push_back(points[p] + n);
+ n.normalize();
+
+ const float c1 = 0.429043;
+ const float c2 = 0.511664;
+ const float c3 = 0.743125;
+ const float c4 = 0.886227;
+ const float c5 = 0.247708;
+ Vector3 light = (c1 * sh_col[8] * (n.x * n.x - n.y * n.y) +
+ c3 * sh_col[6] * n.z * n.z +
+ c4 * sh_col[0] -
+ c5 * sh_col[6] +
+ 2.0 * c1 * sh_col[4] * n.x * n.y +
+ 2.0 * c1 * sh_col[7] * n.x * n.z +
+ 2.0 * c1 * sh_col[5] * n.y * n.z +
+ 2.0 * c2 * sh_col[3] * n.x +
+ 2.0 * c2 * sh_col[1] * n.y +
+ 2.0 * c2 * sh_col[2] * n.z);
+
+ colors.push_back(Color(light.x, light.y, light.z, 1));
+ }
+ }
+
+ for (int i = 0; i < stack_count; ++i) {
+ int k1 = i * (sector_count + 1); // beginning of current stack
+ int k2 = k1 + sector_count + 1; // beginning of next stack
+
+ for (int j = 0; j < sector_count; ++j, ++k1, ++k2) {
+ // 2 triangles per sector excluding first and last stacks
+ // k1 => k2 => k1+1
+ if (i != 0) {
+ indices.push_back(vertex_base + k1);
+ indices.push_back(vertex_base + k2);
+ indices.push_back(vertex_base + k1 + 1);
+ }
+
+ // k1+1 => k2 => k2+1
+ if (i != (stack_count - 1)) {
+ indices.push_back(vertex_base + k1 + 1);
+ indices.push_back(vertex_base + k2);
+ indices.push_back(vertex_base + k2 + 1);
+ }
+ }
+ }
+ }
+
+ Array array;
+ array.resize(RS::ARRAY_MAX);
+ array[RS::ARRAY_VERTEX] = vertices;
+ array[RS::ARRAY_INDEX] = indices;
+ array[RS::ARRAY_COLOR] = colors;
+
+ Ref<ArrayMesh> mesh;
+ mesh.instantiate();
+ mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, array, Array(), Dictionary(), 0); //no compression
+ mesh->surface_set_material(0, material_probes);
+
+ p_gizmo->add_mesh(mesh);
+}
+
+/////////
+
+LightmapProbeGizmoPlugin::LightmapProbeGizmoPlugin() {
+ Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/lightprobe_lines", Color(0.5, 0.6, 1));
+
+ gizmo_color.a = 0.3;
+ create_material("lightprobe_lines", gizmo_color);
+}
+
+String LightmapProbeGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ return "";
+}
+
+Variant LightmapProbeGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ return Variant();
+}
+
+void LightmapProbeGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const {
+}
+
+void LightmapProbeGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) const {
+}
+
+bool LightmapProbeGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<LightmapProbe>(p_spatial) != nullptr;
+}
+
+String LightmapProbeGizmoPlugin::get_gizmo_name() const {
+ return "LightmapProbe";
+}
+
+int LightmapProbeGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+void LightmapProbeGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ Ref<Material> material_lines = get_material("lightprobe_lines", p_gizmo);
+
+ p_gizmo->clear();
+
+ Vector<Vector3> lines;
+
+ int stack_count = 8;
+ int sector_count = 16;
+
+ float sector_step = (Math_PI * 2.0) / sector_count;
+ float stack_step = Math_PI / stack_count;
+
+ Vector<Vector3> vertices;
+ float radius = 0.2;
+
+ for (int i = 0; i <= stack_count; ++i) {
+ float stack_angle = Math_PI / 2 - i * stack_step; // starting from pi/2 to -pi/2
+ float xy = radius * Math::cos(stack_angle); // r * cos(u)
+ float z = radius * Math::sin(stack_angle); // r * sin(u)
+
+ // add (sector_count+1) vertices per stack
+ // the first and last vertices have same position and normal, but different tex coords
+ for (int j = 0; j <= sector_count; ++j) {
+ float sector_angle = j * sector_step; // starting from 0 to 2pi
+
+ // vertex position (x, y, z)
+ float x = xy * Math::cos(sector_angle); // r * cos(u) * cos(v)
+ float y = xy * Math::sin(sector_angle); // r * cos(u) * sin(v)
+
+ Vector3 n = Vector3(x, z, y);
+ vertices.push_back(n);
+ }
+ }
+
+ for (int i = 0; i < stack_count; ++i) {
+ int k1 = i * (sector_count + 1); // beginning of current stack
+ int k2 = k1 + sector_count + 1; // beginning of next stack
+
+ for (int j = 0; j < sector_count; ++j, ++k1, ++k2) {
+ // 2 triangles per sector excluding first and last stacks
+ // k1 => k2 => k1+1
+ if (i != 0) {
+ lines.push_back(vertices[k1]);
+ lines.push_back(vertices[k2]);
+ lines.push_back(vertices[k1]);
+ lines.push_back(vertices[k1 + 1]);
+ }
+
+ if (i != (stack_count - 1)) {
+ lines.push_back(vertices[k1 + 1]);
+ lines.push_back(vertices[k2]);
+ lines.push_back(vertices[k2]);
+ lines.push_back(vertices[k2 + 1]);
+ }
+ }
+ }
+
+ p_gizmo->add_lines(lines, material_lines);
+}
+
+////
+
+CollisionObject3DGizmoPlugin::CollisionObject3DGizmoPlugin() {
+ const Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1));
+ create_material("shape_material", gizmo_color);
+ const float gizmo_value = gizmo_color.get_v();
+ const Color gizmo_color_disabled = Color(gizmo_value, gizmo_value, gizmo_value, 0.65);
+ create_material("shape_material_disabled", gizmo_color_disabled);
+}
+
+bool CollisionObject3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<CollisionObject3D>(p_spatial) != nullptr;
+}
+
+String CollisionObject3DGizmoPlugin::get_gizmo_name() const {
+ return "CollisionObject3D";
+}
+
+int CollisionObject3DGizmoPlugin::get_priority() const {
+ return -2;
+}
+
+void CollisionObject3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ CollisionObject3D *co = Object::cast_to<CollisionObject3D>(p_gizmo->get_spatial_node());
+
+ p_gizmo->clear();
+
+ List<uint32_t> owners;
+ co->get_shape_owners(&owners);
+ for (uint32_t &owner_id : owners) {
+ Transform3D xform = co->shape_owner_get_transform(owner_id);
+ Object *owner = co->shape_owner_get_owner(owner_id);
+ // Exclude CollisionShape3D and CollisionPolygon3D as they have their gizmo.
+ if (!Object::cast_to<CollisionShape3D>(owner) && !Object::cast_to<CollisionPolygon3D>(owner)) {
+ Ref<Material> material = get_material(!co->is_shape_owner_disabled(owner_id) ? "shape_material" : "shape_material_disabled", p_gizmo);
+ for (int shape_id = 0; shape_id < co->shape_owner_get_shape_count(owner_id); shape_id++) {
+ Ref<Shape3D> s = co->shape_owner_get_shape(owner_id, shape_id);
+ if (s.is_null()) {
+ continue;
+ }
+ SurfaceTool st;
+ st.append_from(s->get_debug_mesh(), 0, xform);
+
+ p_gizmo->add_mesh(st.commit(), material);
+ p_gizmo->add_collision_segments(s->get_debug_mesh_lines());
+ }
+ }
+ }
+}
+
+////
+
+CollisionShape3DGizmoPlugin::CollisionShape3DGizmoPlugin() {
+ const Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1));
+ create_material("shape_material", gizmo_color);
+ const float gizmo_value = gizmo_color.get_v();
+ const Color gizmo_color_disabled = Color(gizmo_value, gizmo_value, gizmo_value, 0.65);
+ create_material("shape_material_disabled", gizmo_color_disabled);
+ create_handle_material("handles");
+}
+
+bool CollisionShape3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<CollisionShape3D>(p_spatial) != nullptr;
+}
+
+String CollisionShape3DGizmoPlugin::get_gizmo_name() const {
+ return "CollisionShape3D";
+}
+
+int CollisionShape3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+String CollisionShape3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ const CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_spatial_node());
+
+ Ref<Shape3D> s = cs->get_shape();
+ if (s.is_null()) {
+ return "";
+ }
+
+ if (Object::cast_to<SphereShape3D>(*s)) {
+ return "Radius";
+ }
+
+ if (Object::cast_to<BoxShape3D>(*s)) {
+ return "Size";
+ }
+
+ if (Object::cast_to<CapsuleShape3D>(*s)) {
+ return p_id == 0 ? "Radius" : "Height";
+ }
+
+ if (Object::cast_to<CylinderShape3D>(*s)) {
+ return p_id == 0 ? "Radius" : "Height";
+ }
+
+ if (Object::cast_to<RayShape3D>(*s)) {
+ return "Length";
+ }
+
+ return "";
+}
+
+Variant CollisionShape3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_spatial_node());
+
+ Ref<Shape3D> s = cs->get_shape();
+ if (s.is_null()) {
+ return Variant();
+ }
+
+ if (Object::cast_to<SphereShape3D>(*s)) {
+ Ref<SphereShape3D> ss = s;
+ return ss->get_radius();
+ }
+
+ if (Object::cast_to<BoxShape3D>(*s)) {
+ Ref<BoxShape3D> bs = s;
+ return bs->get_size();
+ }
+
+ if (Object::cast_to<CapsuleShape3D>(*s)) {
+ Ref<CapsuleShape3D> cs2 = s;
+ return p_id == 0 ? cs2->get_radius() : cs2->get_height();
+ }
+
+ if (Object::cast_to<CylinderShape3D>(*s)) {
+ Ref<CylinderShape3D> cs2 = s;
+ return p_id == 0 ? cs2->get_radius() : cs2->get_height();
+ }
+
+ if (Object::cast_to<RayShape3D>(*s)) {
+ Ref<RayShape3D> cs2 = s;
+ return cs2->get_length();
+ }
+
+ return Variant();
+}
+
+void CollisionShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const {
+ CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_spatial_node());
+
+ Ref<Shape3D> s = cs->get_shape();
+ if (s.is_null()) {
+ return;
+ }
+
+ Transform3D gt = cs->get_global_transform();
+ Transform3D gi = gt.affine_inverse();
+
+ Vector3 ray_from = p_camera->project_ray_origin(p_point);
+ Vector3 ray_dir = p_camera->project_ray_normal(p_point);
+
+ Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 4096) };
+
+ if (Object::cast_to<SphereShape3D>(*s)) {
+ Ref<SphereShape3D> ss = s;
+ Vector3 ra, rb;
+ Geometry3D::get_closest_points_between_segments(Vector3(), Vector3(4096, 0, 0), sg[0], sg[1], ra, rb);
+ float d = ra.x;
+ if (Node3DEditor::get_singleton()->is_snap_enabled()) {
+ d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
+ }
+
+ if (d < 0.001) {
+ d = 0.001;
+ }
+
+ ss->set_radius(d);
+ }
+
+ if (Object::cast_to<RayShape3D>(*s)) {
+ Ref<RayShape3D> rs = s;
+ Vector3 ra, rb;
+ Geometry3D::get_closest_points_between_segments(Vector3(), Vector3(0, 0, 4096), sg[0], sg[1], ra, rb);
+ float d = ra.z;
+ if (Node3DEditor::get_singleton()->is_snap_enabled()) {
+ d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
+ }
+
+ if (d < 0.001) {
+ d = 0.001;
+ }
+
+ rs->set_length(d);
+ }
+
+ if (Object::cast_to<BoxShape3D>(*s)) {
+ Vector3 axis;
+ axis[p_id] = 1.0;
+ Ref<BoxShape3D> bs = s;
+ Vector3 ra, rb;
+ Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb);
+ float d = ra[p_id];
+ if (Node3DEditor::get_singleton()->is_snap_enabled()) {
+ d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
+ }
+
+ if (d < 0.001) {
+ d = 0.001;
+ }
+
+ Vector3 he = bs->get_size();
+ he[p_id] = d * 2;
+ bs->set_size(he);
+ }
+
+ if (Object::cast_to<CapsuleShape3D>(*s)) {
+ Vector3 axis;
+ axis[p_id == 0 ? 0 : 2] = 1.0;
+ Ref<CapsuleShape3D> cs2 = s;
+ Vector3 ra, rb;
+ Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb);
+ float d = axis.dot(ra);
+ if (p_id == 1) {
+ d -= cs2->get_radius();
+ }
+
+ if (Node3DEditor::get_singleton()->is_snap_enabled()) {
+ d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
+ }
+
+ if (d < 0.001) {
+ d = 0.001;
+ }
+
+ if (p_id == 0) {
+ cs2->set_radius(d);
+ } else if (p_id == 1) {
+ cs2->set_height(d * 2.0);
+ }
+ }
+
+ if (Object::cast_to<CylinderShape3D>(*s)) {
+ Vector3 axis;
+ axis[p_id == 0 ? 0 : 1] = 1.0;
+ Ref<CylinderShape3D> cs2 = s;
+ Vector3 ra, rb;
+ Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb);
+ float d = axis.dot(ra);
+ if (Node3DEditor::get_singleton()->is_snap_enabled()) {
+ d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap());
+ }
+
+ if (d < 0.001) {
+ d = 0.001;
+ }
+
+ if (p_id == 0) {
+ cs2->set_radius(d);
+ } else if (p_id == 1) {
+ cs2->set_height(d * 2.0);
+ }
+ }
+}
+
+void CollisionShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) const {
+ CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_spatial_node());
+
+ Ref<Shape3D> s = cs->get_shape();
+ if (s.is_null()) {
+ return;
+ }
+
+ if (Object::cast_to<SphereShape3D>(*s)) {
+ Ref<SphereShape3D> ss = s;
+ if (p_cancel) {
+ ss->set_radius(p_restore);
+ return;
+ }
+
+ UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
+ ur->create_action(TTR("Change Sphere Shape Radius"));
+ ur->add_do_method(ss.ptr(), "set_radius", ss->get_radius());
+ ur->add_undo_method(ss.ptr(), "set_radius", p_restore);
+ ur->commit_action();
+ }
+
+ if (Object::cast_to<BoxShape3D>(*s)) {
+ Ref<BoxShape3D> ss = s;
+ if (p_cancel) {
+ ss->set_size(p_restore);
+ return;
+ }
+
+ UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
+ ur->create_action(TTR("Change Box Shape Size"));
+ ur->add_do_method(ss.ptr(), "set_size", ss->get_size());
+ ur->add_undo_method(ss.ptr(), "set_size", p_restore);
+ ur->commit_action();
+ }
+
+ if (Object::cast_to<CapsuleShape3D>(*s)) {
+ Ref<CapsuleShape3D> ss = s;
+ if (p_cancel) {
+ if (p_id == 0) {
+ ss->set_radius(p_restore);
+ } else {
+ ss->set_height(p_restore);
+ }
+ return;
+ }
+
+ UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
+ if (p_id == 0) {
+ ur->create_action(TTR("Change Capsule Shape Radius"));
+ ur->add_do_method(ss.ptr(), "set_radius", ss->get_radius());
+ ur->add_undo_method(ss.ptr(), "set_radius", p_restore);
+ } else {
+ ur->create_action(TTR("Change Capsule Shape Height"));
+ ur->add_do_method(ss.ptr(), "set_height", ss->get_height());
+ ur->add_undo_method(ss.ptr(), "set_height", p_restore);
+ }
+
+ ur->commit_action();
+ }
+
+ if (Object::cast_to<CylinderShape3D>(*s)) {
+ Ref<CylinderShape3D> ss = s;
+ if (p_cancel) {
+ if (p_id == 0) {
+ ss->set_radius(p_restore);
+ } else {
+ ss->set_height(p_restore);
+ }
+ return;
+ }
+
+ UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
+ if (p_id == 0) {
+ ur->create_action(TTR("Change Cylinder Shape Radius"));
+ ur->add_do_method(ss.ptr(), "set_radius", ss->get_radius());
+ ur->add_undo_method(ss.ptr(), "set_radius", p_restore);
+ } else {
+ ur->create_action(
+ ///
+
+ ////////
+ TTR("Change Cylinder Shape Height"));
+ ur->add_do_method(ss.ptr(), "set_height", ss->get_height());
+ ur->add_undo_method(ss.ptr(), "set_height", p_restore);
+ }
+
+ ur->commit_action();
+ }
+
+ if (Object::cast_to<RayShape3D>(*s)) {
+ Ref<RayShape3D> ss = s;
+ if (p_cancel) {
+ ss->set_length(p_restore);
+ return;
+ }
+
+ UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
+ ur->create_action(TTR("Change Ray Shape Length"));
+ ur->add_do_method(ss.ptr(), "set_length", ss->get_length());
+ ur->add_undo_method(ss.ptr(), "set_length", p_restore);
+ ur->commit_action();
+ }
+}
+
+void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_spatial_node());
+
+ p_gizmo->clear();
+
+ Ref<Shape3D> s = cs->get_shape();
+ if (s.is_null()) {
+ return;
+ }
+
+ const Ref<Material> material =
+ get_material(!cs->is_disabled() ? "shape_material" : "shape_material_disabled", p_gizmo);
+ Ref<Material> handles_material = get_material("handles");
+
+ if (Object::cast_to<SphereShape3D>(*s)) {
+ Ref<SphereShape3D> sp = s;
+ float r = sp->get_radius();
+
+ Vector<Vector3> points;
+
+ for (int i = 0; i <= 360; i++) {
+ float ra = Math::deg2rad((float)i);
+ float rb = Math::deg2rad((float)i + 1);
+ Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * r;
+ Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * r;
+
+ points.push_back(Vector3(a.x, 0, a.y));
+ points.push_back(Vector3(b.x, 0, b.y));
+ points.push_back(Vector3(0, a.x, a.y));
+ points.push_back(Vector3(0, b.x, b.y));
+ points.push_back(Vector3(a.x, a.y, 0));
+ points.push_back(Vector3(b.x, b.y, 0));
+ }
+
+ Vector<Vector3> collision_segments;
+
+ for (int i = 0; i < 64; i++) {
+ float ra = i * (Math_TAU / 64.0);
+ float rb = (i + 1) * (Math_TAU / 64.0);
+ Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * r;
+ Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * r;
+
+ collision_segments.push_back(Vector3(a.x, 0, a.y));
+ collision_segments.push_back(Vector3(b.x, 0, b.y));
+ collision_segments.push_back(Vector3(0, a.x, a.y));
+ collision_segments.push_back(Vector3(0, b.x, b.y));
+ collision_segments.push_back(Vector3(a.x, a.y, 0));
+ collision_segments.push_back(Vector3(b.x, b.y, 0));
+ }
+
+ p_gizmo->add_lines(points, material);
+ p_gizmo->add_collision_segments(collision_segments);
+ Vector<Vector3> handles;
+ handles.push_back(Vector3(r, 0, 0));
+ p_gizmo->add_handles(handles, handles_material);
+ }
+
+ if (Object::cast_to<BoxShape3D>(*s)) {
+ Ref<BoxShape3D> bs = s;
+ Vector<Vector3> lines;
+ AABB aabb;
+ aabb.position = -bs->get_size() / 2;
+ aabb.size = bs->get_size();
+
+ for (int i = 0; i < 12; i++) {
+ Vector3 a, b;
+ aabb.get_edge(i, a, b);
+ lines.push_back(a);
+ lines.push_back(b);
+ }
+
+ Vector<Vector3> handles;
+
+ for (int i = 0; i < 3; i++) {
+ Vector3 ax;
+ ax[i] = bs->get_size()[i] / 2;
+ handles.push_back(ax);
+ }
+
+ p_gizmo->add_lines(lines, material);
+ p_gizmo->add_collision_segments(lines);
+ p_gizmo->add_handles(handles, handles_material);
+ }
+
+ if (Object::cast_to<CapsuleShape3D>(*s)) {
+ Ref<CapsuleShape3D> cs2 = s;
+ float radius = cs2->get_radius();
+ float height = cs2->get_height();
+
+ Vector<Vector3> points;
+
+ Vector3 d(0, height * 0.5, 0);
+ for (int i = 0; i < 360; i++) {
+ float ra = Math::deg2rad((float)i);
+ float rb = Math::deg2rad((float)i + 1);
+ Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * radius;
+ Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * radius;
+
+ points.push_back(Vector3(a.x, 0, a.y) + d);
+ points.push_back(Vector3(b.x, 0, b.y) + d);
+
+ points.push_back(Vector3(a.x, 0, a.y) - d);
+ points.push_back(Vector3(b.x, 0, b.y) - d);
+
+ if (i % 90 == 0) {
+ points.push_back(Vector3(a.x, 0, a.y) + d);
+ points.push_back(Vector3(a.x, 0, a.y) - d);
+ }
+
+ Vector3 dud = i < 180 ? d : -d;
+
+ points.push_back(Vector3(0, a.x, a.y) + dud);
+ points.push_back(Vector3(0, b.x, b.y) + dud);
+ points.push_back(Vector3(a.y, a.x, 0) + dud);
+ points.push_back(Vector3(b.y, b.x, 0) + dud);
+ }
+
+ p_gizmo->add_lines(points, material);
+
+ Vector<Vector3> collision_segments;
+
+ for (int i = 0; i < 64; i++) {
+ float ra = i * (Math_TAU / 64.0);
+ float rb = (i + 1) * (Math_TAU / 64.0);
+ Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * radius;
+ Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * radius;
+
+ collision_segments.push_back(Vector3(a.x, 0, a.y) + d);
+ collision_segments.push_back(Vector3(b.x, 0, b.y) + d);
+
+ collision_segments.push_back(Vector3(a.x, 0, a.y) - d);
+ collision_segments.push_back(Vector3(b.x, 0, b.y) - d);
+
+ if (i % 16 == 0) {
+ collision_segments.push_back(Vector3(a.x, 0, a.y) + d);
+ collision_segments.push_back(Vector3(a.x, 0, a.y) - d);
+ }
+
+ Vector3 dud = i < 32 ? d : -d;
+
+ collision_segments.push_back(Vector3(0, a.x, a.y) + dud);
+ collision_segments.push_back(Vector3(0, b.x, b.y) + dud);
+ collision_segments.push_back(Vector3(a.y, a.x, 0) + dud);
+ collision_segments.push_back(Vector3(b.y, b.x, 0) + dud);
+ }
+
+ p_gizmo->add_collision_segments(collision_segments);
+
+ Vector<Vector3> handles;
+ handles.push_back(Vector3(cs2->get_radius(), 0, 0));
+ handles.push_back(Vector3(0, cs2->get_height() * 0.5 + cs2->get_radius(), 0));
+ p_gizmo->add_handles(handles, handles_material);
+ }
+
+ if (Object::cast_to<CylinderShape3D>(*s)) {
+ Ref<CylinderShape3D> cs2 = s;
+ float radius = cs2->get_radius();
+ float height = cs2->get_height();
+
+ Vector<Vector3> points;
+
+ Vector3 d(0, height * 0.5, 0);
+ for (int i = 0; i < 360; i++) {
+ float ra = Math::deg2rad((float)i);
+ float rb = Math::deg2rad((float)i + 1);
+ Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * radius;
+ Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * radius;
+
+ points.push_back(Vector3(a.x, 0, a.y) + d);
+ points.push_back(Vector3(b.x, 0, b.y) + d);
+
+ points.push_back(Vector3(a.x, 0, a.y) - d);
+ points.push_back(Vector3(b.x, 0, b.y) - d);
+
+ if (i % 90 == 0) {
+ points.push_back(Vector3(a.x, 0, a.y) + d);
+ points.push_back(Vector3(a.x, 0, a.y) - d);
+ }
+ }
+
+ p_gizmo->add_lines(points, material);
+
+ Vector<Vector3> collision_segments;
+
+ for (int i = 0; i < 64; i++) {
+ float ra = i * (Math_TAU / 64.0);
+ float rb = (i + 1) * (Math_TAU / 64.0);
+ Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * radius;
+ Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * radius;
+
+ collision_segments.push_back(Vector3(a.x, 0, a.y) + d);
+ collision_segments.push_back(Vector3(b.x, 0, b.y) + d);
+
+ collision_segments.push_back(Vector3(a.x, 0, a.y) - d);
+ collision_segments.push_back(Vector3(b.x, 0, b.y) - d);
+
+ if (i % 16 == 0) {
+ collision_segments.push_back(Vector3(a.x, 0, a.y) + d);
+ collision_segments.push_back(Vector3(a.x, 0, a.y) - d);
+ }
+ }
+
+ p_gizmo->add_collision_segments(collision_segments);
+
+ Vector<Vector3> handles;
+ handles.push_back(Vector3(cs2->get_radius(), 0, 0));
+ handles.push_back(Vector3(0, cs2->get_height() * 0.5, 0));
+ p_gizmo->add_handles(handles, handles_material);
+ }
+
+ if (Object::cast_to<WorldMarginShape3D>(*s)) {
+ Ref<WorldMarginShape3D> ps = s;
+ Plane p = ps->get_plane();
+ Vector<Vector3> points;
+
+ Vector3 n1 = p.get_any_perpendicular_normal();
+ Vector3 n2 = p.normal.cross(n1).normalized();
+
+ Vector3 pface[4] = {
+ p.normal * p.d + n1 * 10.0 + n2 * 10.0,
+ p.normal * p.d + n1 * 10.0 + n2 * -10.0,
+ p.normal * p.d + n1 * -10.0 + n2 * -10.0,
+ p.normal * p.d + n1 * -10.0 + n2 * 10.0,
+ };
+
+ points.push_back(pface[0]);
+ points.push_back(pface[1]);
+ points.push_back(pface[1]);
+ points.push_back(pface[2]);
+ points.push_back(pface[2]);
+ points.push_back(pface[3]);
+ points.push_back(pface[3]);
+ points.push_back(pface[0]);
+ points.push_back(p.normal * p.d);
+ points.push_back(p.normal * p.d + p.normal * 3);
+
+ p_gizmo->add_lines(points, material);
+ p_gizmo->add_collision_segments(points);
+ }
+
+ if (Object::cast_to<ConvexPolygonShape3D>(*s)) {
+ Vector<Vector3> points = Object::cast_to<ConvexPolygonShape3D>(*s)->get_points();
+
+ if (points.size() > 3) {
+ Vector<Vector3> varr = Variant(points);
+ Geometry3D::MeshData md;
+ Error err = ConvexHullComputer::convex_hull(varr, md);
+ if (err == OK) {
+ Vector<Vector3> points2;
+ points2.resize(md.edges.size() * 2);
+ for (int i = 0; i < md.edges.size(); i++) {
+ points2.write[i * 2 + 0] = md.vertices[md.edges[i].a];
+ points2.write[i * 2 + 1] = md.vertices[md.edges[i].b];
+ }
+
+ p_gizmo->add_lines(points2, material);
+ p_gizmo->add_collision_segments(points2);
+ }
+ }
+ }
+
+ if (Object::cast_to<ConcavePolygonShape3D>(*s)) {
+ Ref<ConcavePolygonShape3D> cs2 = s;
+ Ref<ArrayMesh> mesh = cs2->get_debug_mesh();
+ p_gizmo->add_mesh(mesh, material);
+ p_gizmo->add_collision_segments(cs2->get_debug_mesh_lines());
+ }
+
+ if (Object::cast_to<RayShape3D>(*s)) {
+ Ref<RayShape3D> rs = s;
+
+ Vector<Vector3> points;
+ points.push_back(Vector3());
+ points.push_back(Vector3(0, 0, rs->get_length()));
+ p_gizmo->add_lines(points, material);
+ p_gizmo->add_collision_segments(points);
+ Vector<Vector3> handles;
+ handles.push_back(Vector3(0, 0, rs->get_length()));
+ p_gizmo->add_handles(handles, handles_material);
+ }
+
+ if (Object::cast_to<HeightMapShape3D>(*s)) {
+ Ref<HeightMapShape3D> hms = s;
+
+ Ref<ArrayMesh> mesh = hms->get_debug_mesh();
+ p_gizmo->add_mesh(mesh, material);
+ }
+}
+
+/////
+
+CollisionPolygon3DGizmoPlugin::CollisionPolygon3DGizmoPlugin() {
+ const Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1));
+ create_material("shape_material", gizmo_color);
+ const float gizmo_value = gizmo_color.get_v();
+ const Color gizmo_color_disabled = Color(gizmo_value, gizmo_value, gizmo_value, 0.65);
+ create_material("shape_material_disabled", gizmo_color_disabled);
+}
+
+bool CollisionPolygon3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<CollisionPolygon3D>(p_spatial) != nullptr;
+}
+
+String CollisionPolygon3DGizmoPlugin::get_gizmo_name() const {
+ return "CollisionPolygon3D";
+}
+
+int CollisionPolygon3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+void CollisionPolygon3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ CollisionPolygon3D *polygon = Object::cast_to<CollisionPolygon3D>(p_gizmo->get_spatial_node());
+
+ p_gizmo->clear();
+
+ Vector<Vector2> points = polygon->get_polygon();
+ float depth = polygon->get_depth() * 0.5;
+
+ Vector<Vector3> lines;
+ for (int i = 0; i < points.size(); i++) {
+ int n = (i + 1) % points.size();
+ lines.push_back(Vector3(points[i].x, points[i].y, depth));
+ lines.push_back(Vector3(points[n].x, points[n].y, depth));
+ lines.push_back(Vector3(points[i].x, points[i].y, -depth));
+ lines.push_back(Vector3(points[n].x, points[n].y, -depth));
+ lines.push_back(Vector3(points[i].x, points[i].y, depth));
+ lines.push_back(Vector3(points[i].x, points[i].y, -depth));
+ }
+
+ const Ref<Material> material =
+ get_material(!polygon->is_disabled() ? "shape_material" : "shape_material_disabled", p_gizmo);
+
+ p_gizmo->add_lines(lines, material);
+ p_gizmo->add_collision_segments(lines);
+}
+
+////
+
+NavigationRegion3DGizmoPlugin::NavigationRegion3DGizmoPlugin() {
+ create_material("navigation_edge_material", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/navigation_edge", Color(0.5, 1, 1)));
+ create_material("navigation_edge_material_disabled", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/navigation_edge_disabled", Color(0.7, 0.7, 0.7)));
+ create_material("navigation_solid_material", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/navigation_solid", Color(0.5, 1, 1, 0.4)));
+ create_material("navigation_solid_material_disabled", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/navigation_solid_disabled", Color(0.7, 0.7, 0.7, 0.4)));
+}
+
+bool NavigationRegion3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<NavigationRegion3D>(p_spatial) != nullptr;
+}
+
+String NavigationRegion3DGizmoPlugin::get_gizmo_name() const {
+ return "NavigationRegion3D";
+}
+
+int NavigationRegion3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+void NavigationRegion3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ NavigationRegion3D *navmesh = Object::cast_to<NavigationRegion3D>(p_gizmo->get_spatial_node());
+
+ Ref<Material> edge_material = get_material("navigation_edge_material", p_gizmo);
+ Ref<Material> edge_material_disabled = get_material("navigation_edge_material_disabled", p_gizmo);
+ Ref<Material> solid_material = get_material("navigation_solid_material", p_gizmo);
+ Ref<Material> solid_material_disabled = get_material("navigation_solid_material_disabled", p_gizmo);
+
+ p_gizmo->clear();
+ Ref<NavigationMesh> navmeshie = navmesh->get_navigation_mesh();
+ if (navmeshie.is_null()) {
+ return;
+ }
+
+ Vector<Vector3> vertices = navmeshie->get_vertices();
+ const Vector3 *vr = vertices.ptr();
+ List<Face3> faces;
+ for (int i = 0; i < navmeshie->get_polygon_count(); i++) {
+ Vector<int> p = navmeshie->get_polygon(i);
+
+ for (int j = 2; j < p.size(); j++) {
+ Face3 f;
+ f.vertex[0] = vr[p[0]];
+ f.vertex[1] = vr[p[j - 1]];
+ f.vertex[2] = vr[p[j]];
+
+ faces.push_back(f);
+ }
+ }
+
+ if (faces.is_empty()) {
+ return;
+ }
+
+ Map<_EdgeKey, bool> edge_map;
+ Vector<Vector3> tmeshfaces;
+ tmeshfaces.resize(faces.size() * 3);
+
+ {
+ Vector3 *tw = tmeshfaces.ptrw();
+ int tidx = 0;
+
+ for (const Face3 &f : faces) {
+ for (int j = 0; j < 3; j++) {
+ tw[tidx++] = f.vertex[j];
+ _EdgeKey ek;
+ ek.from = f.vertex[j].snapped(Vector3(CMP_EPSILON, CMP_EPSILON, CMP_EPSILON));
+ ek.to = f.vertex[(j + 1) % 3].snapped(Vector3(CMP_EPSILON, CMP_EPSILON, CMP_EPSILON));
+ if (ek.from < ek.to) {
+ SWAP(ek.from, ek.to);
+ }
+
+ Map<_EdgeKey, bool>::Element *F = edge_map.find(ek);
+
+ if (F) {
+ F->get() = false;
+
+ } else {
+ edge_map[ek] = true;
+ }
+ }
+ }
+ }
+ Vector<Vector3> lines;
+
+ for (Map<_EdgeKey, bool>::Element *E = edge_map.front(); E; E = E->next()) {
+ if (E->get()) {
+ lines.push_back(E->key().from);
+ lines.push_back(E->key().to);
+ }
+ }
+
+ Ref<TriangleMesh> tmesh = memnew(TriangleMesh);
+ tmesh->create(tmeshfaces);
+
+ if (lines.size()) {
+ p_gizmo->add_lines(lines, navmesh->is_enabled() ? edge_material : edge_material_disabled);
+ }
+ p_gizmo->add_collision_triangles(tmesh);
+ Ref<ArrayMesh> m = memnew(ArrayMesh);
+ Array a;
+ a.resize(Mesh::ARRAY_MAX);
+ a[0] = tmeshfaces;
+ m->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, a);
+ m->surface_set_material(0, navmesh->is_enabled() ? solid_material : solid_material_disabled);
+ p_gizmo->add_mesh(m);
+ p_gizmo->add_collision_segments(lines);
+}
+
+//////
+
+#define BODY_A_RADIUS 0.25
+#define BODY_B_RADIUS 0.27
+
+Basis JointGizmosDrawer::look_body(const Transform3D &p_joint_transform, const Transform3D &p_body_transform) {
+ const Vector3 &p_eye(p_joint_transform.origin);
+ const Vector3 &p_target(p_body_transform.origin);
+
+ Vector3 v_x, v_y, v_z;
+
+ // Look the body with X
+ v_x = p_target - p_eye;
+ v_x.normalize();
+
+ v_z = v_x.cross(Vector3(0, 1, 0));
+ v_z.normalize();
+
+ v_y = v_z.cross(v_x);
+ v_y.normalize();
+
+ Basis base;
+ base.set(v_x, v_y, v_z);
+
+ // Absorb current joint transform
+ base = p_joint_transform.basis.inverse() * base;
+
+ return base;
+}
+
+Basis JointGizmosDrawer::look_body_toward(Vector3::Axis p_axis, const Transform3D &joint_transform, const Transform3D &body_transform) {
+ switch (p_axis) {
+ case Vector3::AXIS_X:
+ return look_body_toward_x(joint_transform, body_transform);
+ case Vector3::AXIS_Y:
+ return look_body_toward_y(joint_transform, body_transform);
+ case Vector3::AXIS_Z:
+ return look_body_toward_z(joint_transform, body_transform);
+ default:
+ return Basis();
+ }
+}
+
+Basis JointGizmosDrawer::look_body_toward_x(const Transform3D &p_joint_transform, const Transform3D &p_body_transform) {
+ const Vector3 &p_eye(p_joint_transform.origin);
+ const Vector3 &p_target(p_body_transform.origin);
+
+ const Vector3 p_front(p_joint_transform.basis.get_axis(0));
+
+ Vector3 v_x, v_y, v_z;
+
+ // Look the body with X
+ v_x = p_target - p_eye;
+ v_x.normalize();
+
+ v_y = p_front.cross(v_x);
+ v_y.normalize();
+
+ v_z = v_y.cross(p_front);
+ v_z.normalize();
+
+ // Clamp X to FRONT axis
+ v_x = p_front;
+ v_x.normalize();
+
+ Basis base;
+ base.set(v_x, v_y, v_z);
+
+ // Absorb current joint transform
+ base = p_joint_transform.basis.inverse() * base;
+
+ return base;
+}
+
+Basis JointGizmosDrawer::look_body_toward_y(const Transform3D &p_joint_transform, const Transform3D &p_body_transform) {
+ const Vector3 &p_eye(p_joint_transform.origin);
+ const Vector3 &p_target(p_body_transform.origin);
+
+ const Vector3 p_up(p_joint_transform.basis.get_axis(1));
+
+ Vector3 v_x, v_y, v_z;
+
+ // Look the body with X
+ v_x = p_target - p_eye;
+ v_x.normalize();
+
+ v_z = v_x.cross(p_up);
+ v_z.normalize();
+
+ v_x = p_up.cross(v_z);
+ v_x.normalize();
+
+ // Clamp Y to UP axis
+ v_y = p_up;
+ v_y.normalize();
+
+ Basis base;
+ base.set(v_x, v_y, v_z);
+
+ // Absorb current joint transform
+ base = p_joint_transform.basis.inverse() * base;
+
+ return base;
+}
+
+Basis JointGizmosDrawer::look_body_toward_z(const Transform3D &p_joint_transform, const Transform3D &p_body_transform) {
+ const Vector3 &p_eye(p_joint_transform.origin);
+ const Vector3 &p_target(p_body_transform.origin);
+
+ const Vector3 p_lateral(p_joint_transform.basis.get_axis(2));
+
+ Vector3 v_x, v_y, v_z;
+
+ // Look the body with X
+ v_x = p_target - p_eye;
+ v_x.normalize();
+
+ v_z = p_lateral;
+ v_z.normalize();
+
+ v_y = v_z.cross(v_x);
+ v_y.normalize();
+
+ // Clamp X to Z axis
+ v_x = v_y.cross(v_z);
+ v_x.normalize();
+
+ Basis base;
+ base.set(v_x, v_y, v_z);
+
+ // Absorb current joint transform
+ base = p_joint_transform.basis.inverse() * base;
+
+ return base;
+}
+
+void JointGizmosDrawer::draw_circle(Vector3::Axis p_axis, real_t p_radius, const Transform3D &p_offset, const Basis &p_base, real_t p_limit_lower, real_t p_limit_upper, Vector<Vector3> &r_points, bool p_inverse) {
+ if (p_limit_lower == p_limit_upper) {
+ r_points.push_back(p_offset.translated(Vector3()).origin);
+ r_points.push_back(p_offset.translated(p_base.xform(Vector3(0.5, 0, 0))).origin);
+
+ } else {
+ if (p_limit_lower > p_limit_upper) {
+ p_limit_lower = -Math_PI;
+ p_limit_upper = Math_PI;
+ }
+
+ const int points = 32;
+
+ for (int i = 0; i < points; i++) {
+ real_t s = p_limit_lower + i * (p_limit_upper - p_limit_lower) / points;
+ real_t n = p_limit_lower + (i + 1) * (p_limit_upper - p_limit_lower) / points;
+
+ Vector3 from;
+ Vector3 to;
+ switch (p_axis) {
+ case Vector3::AXIS_X:
+ if (p_inverse) {
+ from = p_base.xform(Vector3(0, Math::sin(s), Math::cos(s))) * p_radius;
+ to = p_base.xform(Vector3(0, Math::sin(n), Math::cos(n))) * p_radius;
+ } else {
+ from = p_base.xform(Vector3(0, -Math::sin(s), Math::cos(s))) * p_radius;
+ to = p_base.xform(Vector3(0, -Math::sin(n), Math::cos(n))) * p_radius;
+ }
+ break;
+ case Vector3::AXIS_Y:
+ if (p_inverse) {
+ from = p_base.xform(Vector3(Math::cos(s), 0, -Math::sin(s))) * p_radius;
+ to = p_base.xform(Vector3(Math::cos(n), 0, -Math::sin(n))) * p_radius;
+ } else {
+ from = p_base.xform(Vector3(Math::cos(s), 0, Math::sin(s))) * p_radius;
+ to = p_base.xform(Vector3(Math::cos(n), 0, Math::sin(n))) * p_radius;
+ }
+ break;
+ case Vector3::AXIS_Z:
+ from = p_base.xform(Vector3(Math::cos(s), Math::sin(s), 0)) * p_radius;
+ to = p_base.xform(Vector3(Math::cos(n), Math::sin(n), 0)) * p_radius;
+ break;
+ }
+
+ if (i == points - 1) {
+ r_points.push_back(p_offset.translated(to).origin);
+ r_points.push_back(p_offset.translated(Vector3()).origin);
+ }
+ if (i == 0) {
+ r_points.push_back(p_offset.translated(from).origin);
+ r_points.push_back(p_offset.translated(Vector3()).origin);
+ }
+
+ r_points.push_back(p_offset.translated(from).origin);
+ r_points.push_back(p_offset.translated(to).origin);
+ }
+
+ r_points.push_back(p_offset.translated(Vector3(0, p_radius * 1.5, 0)).origin);
+ r_points.push_back(p_offset.translated(Vector3()).origin);
+ }
+}
+
+void JointGizmosDrawer::draw_cone(const Transform3D &p_offset, const Basis &p_base, real_t p_swing, real_t p_twist, Vector<Vector3> &r_points) {
+ float r = 1.0;
+ float w = r * Math::sin(p_swing);
+ float d = r * Math::cos(p_swing);
+
+ //swing
+ for (int i = 0; i < 360; i += 10) {
+ float ra = Math::deg2rad((float)i);
+ float rb = Math::deg2rad((float)i + 10);
+ Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * w;
+ Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * w;
+
+ r_points.push_back(p_offset.translated(p_base.xform(Vector3(d, a.x, a.y))).origin);
+ r_points.push_back(p_offset.translated(p_base.xform(Vector3(d, b.x, b.y))).origin);
+
+ if (i % 90 == 0) {
+ r_points.push_back(p_offset.translated(p_base.xform(Vector3(d, a.x, a.y))).origin);
+ r_points.push_back(p_offset.translated(p_base.xform(Vector3())).origin);
+ }
+ }
+
+ r_points.push_back(p_offset.translated(p_base.xform(Vector3())).origin);
+ r_points.push_back(p_offset.translated(p_base.xform(Vector3(1, 0, 0))).origin);
+
+ /// Twist
+ float ts = Math::rad2deg(p_twist);
+ ts = MIN(ts, 720);
+
+ for (int i = 0; i < int(ts); i += 5) {
+ float ra = Math::deg2rad((float)i);
+ float rb = Math::deg2rad((float)i + 5);
+ float c = i / 720.0;
+ float cn = (i + 5) / 720.0;
+ Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * w * c;
+ Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * w * cn;
+
+ r_points.push_back(p_offset.translated(p_base.xform(Vector3(c, a.x, a.y))).origin);
+ r_points.push_back(p_offset.translated(p_base.xform(Vector3(cn, b.x, b.y))).origin);
+ }
+}
+
+////
+
+Joint3DGizmoPlugin::Joint3DGizmoPlugin() {
+ create_material("joint_material", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/joint", Color(0.5, 0.8, 1)));
+ create_material("joint_body_a_material", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/joint_body_a", Color(0.6, 0.8, 1)));
+ create_material("joint_body_b_material", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/joint_body_b", Color(0.6, 0.9, 1)));
+
+ update_timer = memnew(Timer);
+ update_timer->set_name("JointGizmoUpdateTimer");
+ update_timer->set_wait_time(1.0 / 120.0);
+ update_timer->connect("timeout", callable_mp(this, &Joint3DGizmoPlugin::incremental_update_gizmos));
+ update_timer->set_autostart(true);
+ EditorNode::get_singleton()->call_deferred(SNAME("add_child"), update_timer);
+}
+
+void Joint3DGizmoPlugin::incremental_update_gizmos() {
+ if (!current_gizmos.is_empty()) {
+ update_idx++;
+ update_idx = update_idx % current_gizmos.size();
+ redraw(current_gizmos[update_idx]);
+ }
+}
+
+bool Joint3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return Object::cast_to<Joint3D>(p_spatial) != nullptr;
+}
+
+String Joint3DGizmoPlugin::get_gizmo_name() const {
+ return "Joint3D";
+}
+
+int Joint3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+void Joint3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ Joint3D *joint = Object::cast_to<Joint3D>(p_gizmo->get_spatial_node());
+
+ p_gizmo->clear();
+
+ Node3D *node_body_a = nullptr;
+ if (!joint->get_node_a().is_empty()) {
+ node_body_a = Object::cast_to<Node3D>(joint->get_node(joint->get_node_a()));
+ }
+
+ Node3D *node_body_b = nullptr;
+ if (!joint->get_node_b().is_empty()) {
+ node_body_b = Object::cast_to<Node3D>(joint->get_node(joint->get_node_b()));
+ }
+
+ if (!node_body_a && !node_body_b) {
+ return;
+ }
+
+ Ref<Material> common_material = get_material("joint_material", p_gizmo);
+ Ref<Material> body_a_material = get_material("joint_body_a_material", p_gizmo);
+ Ref<Material> body_b_material = get_material("joint_body_b_material", p_gizmo);
+
+ Vector<Vector3> points;
+ Vector<Vector3> body_a_points;
+ Vector<Vector3> body_b_points;
+
+ if (Object::cast_to<PinJoint3D>(joint)) {
+ CreatePinJointGizmo(Transform3D(), points);
+ p_gizmo->add_collision_segments(points);
+ p_gizmo->add_lines(points, common_material);
+ }
+
+ HingeJoint3D *hinge = Object::cast_to<HingeJoint3D>(joint);
+ if (hinge) {
+ CreateHingeJointGizmo(
+ Transform3D(),
+ hinge->get_global_transform(),
+ node_body_a ? node_body_a->get_global_transform() : Transform3D(),
+ node_body_b ? node_body_b->get_global_transform() : Transform3D(),
+ hinge->get_param(HingeJoint3D::PARAM_LIMIT_LOWER),
+ hinge->get_param(HingeJoint3D::PARAM_LIMIT_UPPER),
+ hinge->get_flag(HingeJoint3D::FLAG_USE_LIMIT),
+ points,
+ node_body_a ? &body_a_points : nullptr,
+ node_body_b ? &body_b_points : nullptr);
+
+ p_gizmo->add_collision_segments(points);
+ p_gizmo->add_collision_segments(body_a_points);
+ p_gizmo->add_collision_segments(body_b_points);
+
+ p_gizmo->add_lines(points, common_material);
+ p_gizmo->add_lines(body_a_points, body_a_material);
+ p_gizmo->add_lines(body_b_points, body_b_material);
+ }
+
+ SliderJoint3D *slider = Object::cast_to<SliderJoint3D>(joint);
+ if (slider) {
+ CreateSliderJointGizmo(
+ Transform3D(),
+ slider->get_global_transform(),
+ node_body_a ? node_body_a->get_global_transform() : Transform3D(),
+ node_body_b ? node_body_b->get_global_transform() : Transform3D(),
+ slider->get_param(SliderJoint3D::PARAM_ANGULAR_LIMIT_LOWER),
+ slider->get_param(SliderJoint3D::PARAM_ANGULAR_LIMIT_UPPER),
+ slider->get_param(SliderJoint3D::PARAM_LINEAR_LIMIT_LOWER),
+ slider->get_param(SliderJoint3D::PARAM_LINEAR_LIMIT_UPPER),
+ points,
+ node_body_a ? &body_a_points : nullptr,
+ node_body_b ? &body_b_points : nullptr);
+
+ p_gizmo->add_collision_segments(points);
+ p_gizmo->add_collision_segments(body_a_points);
+ p_gizmo->add_collision_segments(body_b_points);
+
+ p_gizmo->add_lines(points, common_material);
+ p_gizmo->add_lines(body_a_points, body_a_material);
+ p_gizmo->add_lines(body_b_points, body_b_material);
+ }
+
+ ConeTwistJoint3D *cone = Object::cast_to<ConeTwistJoint3D>(joint);
+ if (cone) {
+ CreateConeTwistJointGizmo(
+ Transform3D(),
+ cone->get_global_transform(),
+ node_body_a ? node_body_a->get_global_transform() : Transform3D(),
+ node_body_b ? node_body_b->get_global_transform() : Transform3D(),
+ cone->get_param(ConeTwistJoint3D::PARAM_SWING_SPAN),
+ cone->get_param(ConeTwistJoint3D::PARAM_TWIST_SPAN),
+ node_body_a ? &body_a_points : nullptr,
+ node_body_b ? &body_b_points : nullptr);
+
+ p_gizmo->add_collision_segments(body_a_points);
+ p_gizmo->add_collision_segments(body_b_points);
+
+ p_gizmo->add_lines(body_a_points, body_a_material);
+ p_gizmo->add_lines(body_b_points, body_b_material);
+ }
+
+ Generic6DOFJoint3D *gen = Object::cast_to<Generic6DOFJoint3D>(joint);
+ if (gen) {
+ CreateGeneric6DOFJointGizmo(
+ Transform3D(),
+ gen->get_global_transform(),
+ node_body_a ? node_body_a->get_global_transform() : Transform3D(),
+ node_body_b ? node_body_b->get_global_transform() : Transform3D(),
+
+ gen->get_param_x(Generic6DOFJoint3D::PARAM_ANGULAR_LOWER_LIMIT),
+ gen->get_param_x(Generic6DOFJoint3D::PARAM_ANGULAR_UPPER_LIMIT),
+ gen->get_param_x(Generic6DOFJoint3D::PARAM_LINEAR_LOWER_LIMIT),
+ gen->get_param_x(Generic6DOFJoint3D::PARAM_LINEAR_UPPER_LIMIT),
+ gen->get_flag_x(Generic6DOFJoint3D::FLAG_ENABLE_ANGULAR_LIMIT),
+ gen->get_flag_x(Generic6DOFJoint3D::FLAG_ENABLE_LINEAR_LIMIT),
+
+ gen->get_param_y(Generic6DOFJoint3D::PARAM_ANGULAR_LOWER_LIMIT),
+ gen->get_param_y(Generic6DOFJoint3D::PARAM_ANGULAR_UPPER_LIMIT),
+ gen->get_param_y(Generic6DOFJoint3D::PARAM_LINEAR_LOWER_LIMIT),
+ gen->get_param_y(Generic6DOFJoint3D::PARAM_LINEAR_UPPER_LIMIT),
+ gen->get_flag_y(Generic6DOFJoint3D::FLAG_ENABLE_ANGULAR_LIMIT),
+ gen->get_flag_y(Generic6DOFJoint3D::FLAG_ENABLE_LINEAR_LIMIT),
+
+ gen->get_param_z(Generic6DOFJoint3D::PARAM_ANGULAR_LOWER_LIMIT),
+ gen->get_param_z(Generic6DOFJoint3D::PARAM_ANGULAR_UPPER_LIMIT),
+ gen->get_param_z(Generic6DOFJoint3D::PARAM_LINEAR_LOWER_LIMIT),
+ gen->get_param_z(Generic6DOFJoint3D::PARAM_LINEAR_UPPER_LIMIT),
+ gen->get_flag_z(Generic6DOFJoint3D::FLAG_ENABLE_ANGULAR_LIMIT),
+ gen->get_flag_z(Generic6DOFJoint3D::FLAG_ENABLE_LINEAR_LIMIT),
+
+ points,
+ node_body_a ? &body_a_points : nullptr,
+ node_body_a ? &body_b_points : nullptr);
+
+ p_gizmo->add_collision_segments(points);
+ p_gizmo->add_collision_segments(body_a_points);
+ p_gizmo->add_collision_segments(body_b_points);
+
+ p_gizmo->add_lines(points, common_material);
+ p_gizmo->add_lines(body_a_points, body_a_material);
+ p_gizmo->add_lines(body_b_points, body_b_material);
+ }
+}
+
+void Joint3DGizmoPlugin::CreatePinJointGizmo(const Transform3D &p_offset, Vector<Vector3> &r_cursor_points) {
+ float cs = 0.25;
+
+ r_cursor_points.push_back(p_offset.translated(Vector3(+cs, 0, 0)).origin);
+ r_cursor_points.push_back(p_offset.translated(Vector3(-cs, 0, 0)).origin);
+ r_cursor_points.push_back(p_offset.translated(Vector3(0, +cs, 0)).origin);
+ r_cursor_points.push_back(p_offset.translated(Vector3(0, -cs, 0)).origin);
+ r_cursor_points.push_back(p_offset.translated(Vector3(0, 0, +cs)).origin);
+ r_cursor_points.push_back(p_offset.translated(Vector3(0, 0, -cs)).origin);
+}
+
+void Joint3DGizmoPlugin::CreateHingeJointGizmo(const Transform3D &p_offset, const Transform3D &p_trs_joint, const Transform3D &p_trs_body_a, const Transform3D &p_trs_body_b, real_t p_limit_lower, real_t p_limit_upper, bool p_use_limit, Vector<Vector3> &r_common_points, Vector<Vector3> *r_body_a_points, Vector<Vector3> *r_body_b_points) {
+ r_common_points.push_back(p_offset.translated(Vector3(0, 0, 0.5)).origin);
+ r_common_points.push_back(p_offset.translated(Vector3(0, 0, -0.5)).origin);
+
+ if (!p_use_limit) {
+ p_limit_upper = -1;
+ p_limit_lower = 0;
+ }
+
+ if (r_body_a_points) {
+ JointGizmosDrawer::draw_circle(Vector3::AXIS_Z,
+ BODY_A_RADIUS,
+ p_offset,
+ JointGizmosDrawer::look_body_toward_z(p_trs_joint, p_trs_body_a),
+ p_limit_lower,
+ p_limit_upper,
+ *r_body_a_points);
+ }
+
+ if (r_body_b_points) {
+ JointGizmosDrawer::draw_circle(Vector3::AXIS_Z,
+ BODY_B_RADIUS,
+ p_offset,
+ JointGizmosDrawer::look_body_toward_z(p_trs_joint, p_trs_body_b),
+ p_limit_lower,
+ p_limit_upper,
+ *r_body_b_points);
+ }
+}
+
+void Joint3DGizmoPlugin::CreateSliderJointGizmo(const Transform3D &p_offset, const Transform3D &p_trs_joint, const Transform3D &p_trs_body_a, const Transform3D &p_trs_body_b, real_t p_angular_limit_lower, real_t p_angular_limit_upper, real_t p_linear_limit_lower, real_t p_linear_limit_upper, Vector<Vector3> &r_points, Vector<Vector3> *r_body_a_points, Vector<Vector3> *r_body_b_points) {
+ p_linear_limit_lower = -p_linear_limit_lower;
+ p_linear_limit_upper = -p_linear_limit_upper;
+
+ float cs = 0.25;
+ r_points.push_back(p_offset.translated(Vector3(0, 0, 0.5)).origin);
+ r_points.push_back(p_offset.translated(Vector3(0, 0, -0.5)).origin);
+
+ if (p_linear_limit_lower >= p_linear_limit_upper) {
+ r_points.push_back(p_offset.translated(Vector3(p_linear_limit_upper, 0, 0)).origin);
+ r_points.push_back(p_offset.translated(Vector3(p_linear_limit_lower, 0, 0)).origin);
+
+ r_points.push_back(p_offset.translated(Vector3(p_linear_limit_upper, -cs, -cs)).origin);
+ r_points.push_back(p_offset.translated(Vector3(p_linear_limit_upper, -cs, cs)).origin);
+ r_points.push_back(p_offset.translated(Vector3(p_linear_limit_upper, -cs, cs)).origin);
+ r_points.push_back(p_offset.translated(Vector3(p_linear_limit_upper, cs, cs)).origin);
+ r_points.push_back(p_offset.translated(Vector3(p_linear_limit_upper, cs, cs)).origin);
+ r_points.push_back(p_offset.translated(Vector3(p_linear_limit_upper, cs, -cs)).origin);
+ r_points.push_back(p_offset.translated(Vector3(p_linear_limit_upper, cs, -cs)).origin);
+ r_points.push_back(p_offset.translated(Vector3(p_linear_limit_upper, -cs, -cs)).origin);
+
+ r_points.push_back(p_offset.translated(Vector3(p_linear_limit_lower, -cs, -cs)).origin);
+ r_points.push_back(p_offset.translated(Vector3(p_linear_limit_lower, -cs, cs)).origin);
+ r_points.push_back(p_offset.translated(Vector3(p_linear_limit_lower, -cs, cs)).origin);
+ r_points.push_back(p_offset.translated(Vector3(p_linear_limit_lower, cs, cs)).origin);
+ r_points.push_back(p_offset.translated(Vector3(p_linear_limit_lower, cs, cs)).origin);
+ r_points.push_back(p_offset.translated(Vector3(p_linear_limit_lower, cs, -cs)).origin);
+ r_points.push_back(p_offset.translated(Vector3(p_linear_limit_lower, cs, -cs)).origin);
+ r_points.push_back(p_offset.translated(Vector3(p_linear_limit_lower, -cs, -cs)).origin);
+
+ } else {
+ r_points.push_back(p_offset.translated(Vector3(+cs * 2, 0, 0)).origin);
+ r_points.push_back(p_offset.translated(Vector3(-cs * 2, 0, 0)).origin);
+ }
+
+ if (r_body_a_points) {
+ JointGizmosDrawer::draw_circle(
+ Vector3::AXIS_X,
+ BODY_A_RADIUS,
+ p_offset,
+ JointGizmosDrawer::look_body_toward(Vector3::AXIS_X, p_trs_joint, p_trs_body_a),
+ p_angular_limit_lower,
+ p_angular_limit_upper,
+ *r_body_a_points);
+ }
+
+ if (r_body_b_points) {
+ JointGizmosDrawer::draw_circle(
+ Vector3::AXIS_X,
+ BODY_B_RADIUS,
+ p_offset,
+ JointGizmosDrawer::look_body_toward(Vector3::AXIS_X, p_trs_joint, p_trs_body_b),
+ p_angular_limit_lower,
+ p_angular_limit_upper,
+ *r_body_b_points,
+ true);
+ }
+}
+
+void Joint3DGizmoPlugin::CreateConeTwistJointGizmo(const Transform3D &p_offset, const Transform3D &p_trs_joint, const Transform3D &p_trs_body_a, const Transform3D &p_trs_body_b, real_t p_swing, real_t p_twist, Vector<Vector3> *r_body_a_points, Vector<Vector3> *r_body_b_points) {
+ if (r_body_a_points) {
+ JointGizmosDrawer::draw_cone(
+ p_offset,
+ JointGizmosDrawer::look_body(p_trs_joint, p_trs_body_a),
+ p_swing,
+ p_twist,
+ *r_body_a_points);
+ }
+
+ if (r_body_b_points) {
+ JointGizmosDrawer::draw_cone(
+ p_offset,
+ JointGizmosDrawer::look_body(p_trs_joint, p_trs_body_b),
+ p_swing,
+ p_twist,
+ *r_body_b_points);
+ }
+}
+
+void Joint3DGizmoPlugin::CreateGeneric6DOFJointGizmo(
+ const Transform3D &p_offset,
+ const Transform3D &p_trs_joint,
+ const Transform3D &p_trs_body_a,
+ const Transform3D &p_trs_body_b,
+ real_t p_angular_limit_lower_x,
+ real_t p_angular_limit_upper_x,
+ real_t p_linear_limit_lower_x,
+ real_t p_linear_limit_upper_x,
+ bool p_enable_angular_limit_x,
+ bool p_enable_linear_limit_x,
+ real_t p_angular_limit_lower_y,
+ real_t p_angular_limit_upper_y,
+ real_t p_linear_limit_lower_y,
+ real_t p_linear_limit_upper_y,
+ bool p_enable_angular_limit_y,
+ bool p_enable_linear_limit_y,
+ real_t p_angular_limit_lower_z,
+ real_t p_angular_limit_upper_z,
+ real_t p_linear_limit_lower_z,
+ real_t p_linear_limit_upper_z,
+ bool p_enable_angular_limit_z,
+ bool p_enable_linear_limit_z,
+ Vector<Vector3> &r_points,
+ Vector<Vector3> *r_body_a_points,
+ Vector<Vector3> *r_body_b_points) {
+ float cs = 0.25;
+
+ for (int ax = 0; ax < 3; ax++) {
+ float ll = 0;
+ float ul = 0;
+ float lll = 0;
+ float lul = 0;
+
+ int a1 = 0;
+ int a2 = 0;
+ int a3 = 0;
+ bool enable_ang = false;
+ bool enable_lin = false;
+
+ switch (ax) {
+ case 0:
+ ll = p_angular_limit_lower_x;
+ ul = p_angular_limit_upper_x;
+ lll = -p_linear_limit_lower_x;
+ lul = -p_linear_limit_upper_x;
+ enable_ang = p_enable_angular_limit_x;
+ enable_lin = p_enable_linear_limit_x;
+ a1 = 0;
+ a2 = 1;
+ a3 = 2;
+ break;
+ case 1:
+ ll = p_angular_limit_lower_y;
+ ul = p_angular_limit_upper_y;
+ lll = -p_linear_limit_lower_y;
+ lul = -p_linear_limit_upper_y;
+ enable_ang = p_enable_angular_limit_y;
+ enable_lin = p_enable_linear_limit_y;
+ a1 = 1;
+ a2 = 2;
+ a3 = 0;
+ break;
+ case 2:
+ ll = p_angular_limit_lower_z;
+ ul = p_angular_limit_upper_z;
+ lll = -p_linear_limit_lower_z;
+ lul = -p_linear_limit_upper_z;
+ enable_ang = p_enable_angular_limit_z;
+ enable_lin = p_enable_linear_limit_z;
+ a1 = 2;
+ a2 = 0;
+ a3 = 1;
+ break;
+ }
+
+#define ADD_VTX(x, y, z) \
+ { \
+ Vector3 v; \
+ v[a1] = (x); \
+ v[a2] = (y); \
+ v[a3] = (z); \
+ r_points.push_back(p_offset.translated(v).origin); \
+ }
+
+ if (enable_lin && lll >= lul) {
+ ADD_VTX(lul, 0, 0);
+ ADD_VTX(lll, 0, 0);
+
+ ADD_VTX(lul, -cs, -cs);
+ ADD_VTX(lul, -cs, cs);
+ ADD_VTX(lul, -cs, cs);
+ ADD_VTX(lul, cs, cs);
+ ADD_VTX(lul, cs, cs);
+ ADD_VTX(lul, cs, -cs);
+ ADD_VTX(lul, cs, -cs);
+ ADD_VTX(lul, -cs, -cs);
+
+ ADD_VTX(lll, -cs, -cs);
+ ADD_VTX(lll, -cs, cs);
+ ADD_VTX(lll, -cs, cs);
+ ADD_VTX(lll, cs, cs);
+ ADD_VTX(lll, cs, cs);
+ ADD_VTX(lll, cs, -cs);
+ ADD_VTX(lll, cs, -cs);
+ ADD_VTX(lll, -cs, -cs);
+
+ } else {
+ ADD_VTX(+cs * 2, 0, 0);
+ ADD_VTX(-cs * 2, 0, 0);
+ }
+
+ if (!enable_ang) {
+ ll = 0;
+ ul = -1;
+ }
+
+ if (r_body_a_points) {
+ JointGizmosDrawer::draw_circle(
+ static_cast<Vector3::Axis>(ax),
+ BODY_A_RADIUS,
+ p_offset,
+ JointGizmosDrawer::look_body_toward(static_cast<Vector3::Axis>(ax), p_trs_joint, p_trs_body_a),
+ ll,
+ ul,
+ *r_body_a_points,
+ true);
+ }
+
+ if (r_body_b_points) {
+ JointGizmosDrawer::draw_circle(
+ static_cast<Vector3::Axis>(ax),
+ BODY_B_RADIUS,
+ p_offset,
+ JointGizmosDrawer::look_body_toward(static_cast<Vector3::Axis>(ax), p_trs_joint, p_trs_body_b),
+ ll,
+ ul,
+ *r_body_b_points);
+ }
+ }
+
+#undef ADD_VTX
+}
diff --git a/editor/plugins/node_3d_editor_gizmos.h b/editor/plugins/node_3d_editor_gizmos.h
new file mode 100644
index 0000000000..f303a61607
--- /dev/null
+++ b/editor/plugins/node_3d_editor_gizmos.h
@@ -0,0 +1,635 @@
+/*************************************************************************/
+/* node_3d_editor_gizmos.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef NODE_3D_EDITOR_GIZMOS_H
+#define NODE_3D_EDITOR_GIZMOS_H
+
+#include "core/templates/ordered_hash_map.h"
+#include "scene/3d/node_3d.h"
+#include "scene/3d/skeleton_3d.h"
+
+class Camera3D;
+class Timer;
+class EditorNode3DGizmoPlugin;
+
+class EditorNode3DGizmo : public Node3DGizmo {
+ GDCLASS(EditorNode3DGizmo, Node3DGizmo);
+
+ struct Instance {
+ RID instance;
+ Ref<ArrayMesh> mesh;
+ Ref<Material> material;
+ Ref<SkinReference> skin_reference;
+ bool extra_margin = false;
+ Transform3D xform;
+
+ void create_instance(Node3D *p_base, bool p_hidden = false);
+ };
+
+ bool selected;
+
+ Vector<Vector3> collision_segments;
+ Ref<TriangleMesh> collision_mesh;
+
+ Vector<Vector3> handles;
+ Vector<int> handle_ids;
+ Vector<Vector3> secondary_handles;
+ Vector<int> secondary_handle_ids;
+
+ float selectable_icon_size;
+ bool billboard_handle;
+
+ bool valid;
+ bool hidden;
+ Vector<Instance> instances;
+ Node3D *spatial_node;
+
+ void _set_spatial_node(Node *p_node) { set_spatial_node(Object::cast_to<Node3D>(p_node)); }
+
+protected:
+ static void _bind_methods();
+
+ EditorNode3DGizmoPlugin *gizmo_plugin;
+
+public:
+ void add_lines(const Vector<Vector3> &p_lines, const Ref<Material> &p_material, bool p_billboard = false, const Color &p_modulate = Color(1, 1, 1));
+ void add_vertices(const Vector<Vector3> &p_vertices, const Ref<Material> &p_material, Mesh::PrimitiveType p_primitive_type, bool p_billboard = false, const Color &p_modulate = Color(1, 1, 1));
+ void add_mesh(const Ref<ArrayMesh> &p_mesh, const Ref<Material> &p_material = Ref<Material>(), const Transform3D &p_xform = Transform3D(), const Ref<SkinReference> &p_skin_reference = Ref<SkinReference>());
+ void add_collision_segments(const Vector<Vector3> &p_lines);
+ void add_collision_triangles(const Ref<TriangleMesh> &p_tmesh);
+ void add_unscaled_billboard(const Ref<Material> &p_material, real_t p_scale = 1, const Color &p_modulate = Color(1, 1, 1));
+ void add_handles(const Vector<Vector3> &p_handles, const Ref<Material> &p_material, const Vector<int> &p_ids = Vector<int>(), bool p_billboard = false, bool p_secondary = false);
+ void add_solid_box(Ref<Material> &p_material, Vector3 p_size, Vector3 p_position = Vector3(), const Transform3D &p_xform = Transform3D());
+
+ virtual bool is_handle_highlighted(int p_id) const;
+ virtual String get_handle_name(int p_id) const;
+ virtual Variant get_handle_value(int p_id) const;
+ virtual void set_handle(int p_id, Camera3D *p_camera, const Point2 &p_point) const;
+ virtual void commit_handle(int p_id, const Variant &p_restore, bool p_cancel = false) const;
+
+ virtual int subgizmos_intersect_ray(Camera3D *p_camera, const Vector2 &p_point) const;
+ virtual Vector<int> subgizmos_intersect_frustum(const Camera3D *p_camera, const Vector<Plane> &p_frustum) const;
+ virtual Transform3D get_subgizmo_transform(int p_id) const;
+ virtual void set_subgizmo_transform(int p_id, Transform3D p_transform) const;
+ virtual void commit_subgizmos(const Vector<int> &p_ids, const Vector<Transform3D> &p_restore, bool p_cancel = false) const;
+
+ void set_selected(bool p_selected) { selected = p_selected; }
+ bool is_selected() const { return selected; }
+
+ void set_spatial_node(Node3D *p_node);
+ Node3D *get_spatial_node() const { return spatial_node; }
+ Ref<EditorNode3DGizmoPlugin> get_plugin() const { return gizmo_plugin; }
+ bool intersect_frustum(const Camera3D *p_camera, const Vector<Plane> &p_frustum);
+ void handles_intersect_ray(Camera3D *p_camera, const Vector2 &p_point, bool p_shift_pressed, int &r_id);
+ bool intersect_ray(Camera3D *p_camera, const Point2 &p_point, Vector3 &r_pos, Vector3 &r_normal);
+ bool is_subgizmo_selected(int p_id) const;
+ Vector<int> get_subgizmo_selection() const;
+
+ virtual void clear() override;
+ virtual void create() override;
+ virtual void transform() override;
+ virtual void redraw() override;
+ virtual void free() override;
+
+ virtual bool is_editable() const;
+
+ void set_hidden(bool p_hidden);
+ void set_plugin(EditorNode3DGizmoPlugin *p_plugin);
+
+ EditorNode3DGizmo();
+ ~EditorNode3DGizmo();
+};
+
+class EditorNode3DGizmoPlugin : public Resource {
+ GDCLASS(EditorNode3DGizmoPlugin, Resource);
+
+public:
+ static const int VISIBLE = 0;
+ static const int HIDDEN = 1;
+ static const int ON_TOP = 2;
+
+protected:
+ int current_state;
+ List<EditorNode3DGizmo *> current_gizmos;
+ HashMap<String, Vector<Ref<StandardMaterial3D>>> materials;
+
+ static void _bind_methods();
+ virtual bool has_gizmo(Node3D *p_spatial);
+ virtual Ref<EditorNode3DGizmo> create_gizmo(Node3D *p_spatial);
+
+public:
+ void create_material(const String &p_name, const Color &p_color, bool p_billboard = false, bool p_on_top = false, bool p_use_vertex_color = false);
+ void create_icon_material(const String &p_name, const Ref<Texture2D> &p_texture, bool p_on_top = false, const Color &p_albedo = Color(1, 1, 1, 1));
+ void create_handle_material(const String &p_name, bool p_billboard = false, const Ref<Texture2D> &p_texture = nullptr);
+ void add_material(const String &p_name, Ref<StandardMaterial3D> p_material);
+
+ Ref<StandardMaterial3D> get_material(const String &p_name, const Ref<EditorNode3DGizmo> &p_gizmo = Ref<EditorNode3DGizmo>());
+
+ virtual String get_gizmo_name() const;
+ virtual int get_priority() const;
+ virtual bool can_be_hidden() const;
+ virtual bool is_selectable_when_hidden() const;
+
+ virtual void redraw(EditorNode3DGizmo *p_gizmo);
+ virtual bool is_handle_highlighted(const EditorNode3DGizmo *p_gizmo, int p_id) const;
+ virtual String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const;
+ virtual Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const;
+ virtual void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const;
+ virtual void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) const;
+
+ virtual int subgizmos_intersect_ray(const EditorNode3DGizmo *p_gizmo, Camera3D *p_camera, const Vector2 &p_point) const;
+ virtual Vector<int> subgizmos_intersect_frustum(const EditorNode3DGizmo *p_gizmo, const Camera3D *p_camera, const Vector<Plane> &p_frustum) const;
+ virtual Transform3D get_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id) const;
+ virtual void set_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id, Transform3D p_transform) const;
+ virtual void commit_subgizmos(const EditorNode3DGizmo *p_gizmo, const Vector<int> &p_ids, const Vector<Transform3D> &p_restore, bool p_cancel = false) const;
+
+ Ref<EditorNode3DGizmo> get_gizmo(Node3D *p_spatial);
+ void set_state(int p_state);
+ int get_state() const;
+ void unregister_gizmo(EditorNode3DGizmo *p_gizmo);
+
+ EditorNode3DGizmoPlugin();
+ virtual ~EditorNode3DGizmoPlugin();
+};
+
+class Light3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(Light3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+
+ String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const override;
+ void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ Light3DGizmoPlugin();
+};
+
+class AudioStreamPlayer3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(AudioStreamPlayer3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+
+ String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const override;
+ void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ AudioStreamPlayer3DGizmoPlugin();
+};
+
+class Camera3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(Camera3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+
+ String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const override;
+ void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ Camera3DGizmoPlugin();
+};
+
+class MeshInstance3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(MeshInstance3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ bool can_be_hidden() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ MeshInstance3DGizmoPlugin();
+};
+
+class OccluderInstance3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(OccluderInstance3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ OccluderInstance3DGizmoPlugin();
+};
+
+class Sprite3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(Sprite3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ bool can_be_hidden() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ Sprite3DGizmoPlugin();
+};
+
+class Position3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(Position3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+ Ref<ArrayMesh> pos3d_mesh;
+ Vector<Vector3> cursor_points;
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ Position3DGizmoPlugin();
+};
+
+class Skeleton3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(Skeleton3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ Skeleton3DGizmoPlugin();
+};
+
+class PhysicalBone3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(PhysicalBone3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ PhysicalBone3DGizmoPlugin();
+};
+
+class RayCast3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(RayCast3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ RayCast3DGizmoPlugin();
+};
+
+class SpringArm3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(SpringArm3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ SpringArm3DGizmoPlugin();
+};
+
+class VehicleWheel3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(VehicleWheel3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ VehicleWheel3DGizmoPlugin();
+};
+
+class SoftBody3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(SoftBody3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ bool is_selectable_when_hidden() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) const override;
+ bool is_handle_highlighted(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+
+ SoftBody3DGizmoPlugin();
+};
+
+class VisibleOnScreenNotifier3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(VisibleOnScreenNotifier3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const override;
+ void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) const override;
+
+ VisibleOnScreenNotifier3DGizmoPlugin();
+};
+
+class CPUParticles3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(CPUParticles3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ bool is_selectable_when_hidden() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+ CPUParticles3DGizmoPlugin();
+};
+
+class GPUParticles3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(GPUParticles3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ bool is_selectable_when_hidden() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const override;
+ void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) const override;
+
+ GPUParticles3DGizmoPlugin();
+};
+
+class GPUParticlesCollision3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(GPUParticlesCollision3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const override;
+ void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) const override;
+
+ GPUParticlesCollision3DGizmoPlugin();
+};
+
+class ReflectionProbeGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(ReflectionProbeGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const override;
+ void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) const override;
+
+ ReflectionProbeGizmoPlugin();
+};
+
+class DecalGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(DecalGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const override;
+ void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) const override;
+
+ DecalGizmoPlugin();
+};
+
+class VoxelGIGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(VoxelGIGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const override;
+ void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) const override;
+
+ VoxelGIGizmoPlugin();
+};
+
+class LightmapGIGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(LightmapGIGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const override;
+ void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) const override;
+
+ LightmapGIGizmoPlugin();
+};
+
+class LightmapProbeGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(LightmapProbeGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const override;
+ void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) const override;
+
+ LightmapProbeGizmoPlugin();
+};
+
+class CollisionObject3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(CollisionObject3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ CollisionObject3DGizmoPlugin();
+};
+
+class CollisionShape3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(CollisionShape3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) const override;
+ void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) const override;
+
+ CollisionShape3DGizmoPlugin();
+};
+
+class CollisionPolygon3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(CollisionPolygon3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+ CollisionPolygon3DGizmoPlugin();
+};
+
+class NavigationRegion3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(NavigationRegion3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+ struct _EdgeKey {
+ Vector3 from;
+ Vector3 to;
+
+ bool operator<(const _EdgeKey &p_with) const { return from == p_with.from ? to < p_with.to : from < p_with.from; }
+ };
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ NavigationRegion3DGizmoPlugin();
+};
+
+class JointGizmosDrawer {
+public:
+ static Basis look_body(const Transform3D &p_joint_transform, const Transform3D &p_body_transform);
+ static Basis look_body_toward(Vector3::Axis p_axis, const Transform3D &joint_transform, const Transform3D &body_transform);
+ static Basis look_body_toward_x(const Transform3D &p_joint_transform, const Transform3D &p_body_transform);
+ static Basis look_body_toward_y(const Transform3D &p_joint_transform, const Transform3D &p_body_transform);
+ /// Special function just used for physics joints, it returns a basis constrained toward Joint Z axis
+ /// with axis X and Y that are looking toward the body and oriented toward up
+ static Basis look_body_toward_z(const Transform3D &p_joint_transform, const Transform3D &p_body_transform);
+
+ // Draw circle around p_axis
+ static void draw_circle(Vector3::Axis p_axis, real_t p_radius, const Transform3D &p_offset, const Basis &p_base, real_t p_limit_lower, real_t p_limit_upper, Vector<Vector3> &r_points, bool p_inverse = false);
+ static void draw_cone(const Transform3D &p_offset, const Basis &p_base, real_t p_swing, real_t p_twist, Vector<Vector3> &r_points);
+};
+
+class Joint3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(Joint3DGizmoPlugin, EditorNode3DGizmoPlugin);
+
+ Timer *update_timer;
+ uint64_t update_idx = 0;
+
+ void incremental_update_gizmos();
+
+public:
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ int get_priority() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+
+ static void CreatePinJointGizmo(const Transform3D &p_offset, Vector<Vector3> &r_cursor_points);
+ static void CreateHingeJointGizmo(const Transform3D &p_offset, const Transform3D &p_trs_joint, const Transform3D &p_trs_body_a, const Transform3D &p_trs_body_b, real_t p_limit_lower, real_t p_limit_upper, bool p_use_limit, Vector<Vector3> &r_common_points, Vector<Vector3> *r_body_a_points, Vector<Vector3> *r_body_b_points);
+ static void CreateSliderJointGizmo(const Transform3D &p_offset, const Transform3D &p_trs_joint, const Transform3D &p_trs_body_a, const Transform3D &p_trs_body_b, real_t p_angular_limit_lower, real_t p_angular_limit_upper, real_t p_linear_limit_lower, real_t p_linear_limit_upper, Vector<Vector3> &r_points, Vector<Vector3> *r_body_a_points, Vector<Vector3> *r_body_b_points);
+ static void CreateConeTwistJointGizmo(const Transform3D &p_offset, const Transform3D &p_trs_joint, const Transform3D &p_trs_body_a, const Transform3D &p_trs_body_b, real_t p_swing, real_t p_twist, Vector<Vector3> *r_body_a_points, Vector<Vector3> *r_body_b_points);
+ static void CreateGeneric6DOFJointGizmo(
+ const Transform3D &p_offset,
+ const Transform3D &p_trs_joint,
+ const Transform3D &p_trs_body_a,
+ const Transform3D &p_trs_body_b,
+ real_t p_angular_limit_lower_x,
+ real_t p_angular_limit_upper_x,
+ real_t p_linear_limit_lower_x,
+ real_t p_linear_limit_upper_x,
+ bool p_enable_angular_limit_x,
+ bool p_enable_linear_limit_x,
+ real_t p_angular_limit_lower_y,
+ real_t p_angular_limit_upper_y,
+ real_t p_linear_limit_lower_y,
+ real_t p_linear_limit_upper_y,
+ bool p_enable_angular_limit_y,
+ bool p_enable_linear_limit_y,
+ real_t p_angular_limit_lower_z,
+ real_t p_angular_limit_upper_z,
+ real_t p_linear_limit_lower_z,
+ real_t p_linear_limit_upper_z,
+ bool p_enable_angular_limit_z,
+ bool p_enable_linear_limit_z,
+ Vector<Vector3> &r_points,
+ Vector<Vector3> *r_body_a_points,
+ Vector<Vector3> *r_body_b_points);
+
+ Joint3DGizmoPlugin();
+};
+
+#endif // NODE_3D_EDITOR_GIZMOS_H
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index 7290a39463..c342d30012 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -35,20 +35,20 @@
#include "core/math/camera_matrix.h"
#include "core/math/math_funcs.h"
#include "core/os/keyboard.h"
-#include "core/string/print_string.h"
#include "core/templates/sort_array.h"
#include "editor/debugger/editor_debugger_node.h"
#include "editor/editor_node.h"
-#include "editor/editor_scale.h"
#include "editor/editor_settings.h"
-#include "editor/node_3d_editor_gizmos.h"
#include "editor/plugins/animation_player_editor_plugin.h"
+#include "editor/plugins/node_3d_editor_gizmos.h"
#include "editor/plugins/script_editor_plugin.h"
#include "scene/3d/camera_3d.h"
#include "scene/3d/collision_shape_3d.h"
+#include "scene/3d/light_3d.h"
#include "scene/3d/mesh_instance_3d.h"
#include "scene/3d/physics_body_3d.h"
#include "scene/3d/visual_instance_3d.h"
+#include "scene/3d/world_environment.h"
#include "scene/gui/center_container.h"
#include "scene/gui/subviewport_container.h"
#include "scene/resources/packed_scene.h"
@@ -58,7 +58,6 @@
#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
@@ -108,8 +107,8 @@ void ViewportRotationControl::_notification(int p_what) {
}
void ViewportRotationControl::_draw() {
- Vector2i center = get_size() / 2.0;
- float radius = get_size().x / 2.0;
+ const Vector2i center = get_size() / 2.0;
+ const real_t radius = get_size().x / 2.0;
if (focused_axis > -2 || orbiting) {
draw_circle(center, radius, Color(0.5, 0.5, 0.5, 0.25));
@@ -150,15 +149,13 @@ void ViewportRotationControl::_draw_axis(const Axis2D &p_axis) {
}
void ViewportRotationControl::_get_sorted_axis(Vector<Axis2D> &r_axis) {
- Vector2i center = get_size() / 2.0;
- float radius = get_size().x / 2.0;
-
- float axis_radius = radius - AXIS_CIRCLE_RADIUS - 2.0 * EDSCALE;
- Basis camera_basis = viewport->to_camera_transform(viewport->cursor).get_basis().inverse();
+ const Vector2i center = get_size() / 2.0;
+ const real_t radius = get_size().x / 2.0 - AXIS_CIRCLE_RADIUS - 2.0 * EDSCALE;
+ const Basis camera_basis = viewport->to_camera_transform(viewport->cursor).get_basis().inverse();
for (int i = 0; i < 3; ++i) {
Vector3 axis_3d = camera_basis.get_axis(i);
- Vector2i axis_vector = Vector2(axis_3d.x, -axis_3d.y) * axis_radius;
+ Vector2i axis_vector = Vector2(axis_3d.x, -axis_3d.y) * radius;
if (Math::abs(axis_3d.z) < 1.0) {
Axis2D pos_axis;
@@ -260,7 +257,7 @@ void ViewportRotationControl::_bind_methods() {
ClassDB::bind_method(D_METHOD("_gui_input"), &ViewportRotationControl::_gui_input);
}
-void Node3DEditorViewport::_update_camera(float p_interp_delta) {
+void Node3DEditorViewport::_update_camera(real_t p_interp_delta) {
bool is_orthogonal = camera->get_projection() == Camera3D::PROJECTION_ORTHOGONAL;
Cursor old_camera_cursor = camera_cursor;
@@ -280,7 +277,7 @@ void Node3DEditorViewport::_update_camera(float p_interp_delta) {
// We interpolate a different point here, because in freelook mode the focus point (cursor.pos) orbits around eye_pos
camera_cursor.eye_pos = old_camera_cursor.eye_pos.lerp(cursor.eye_pos, CLAMP(factor, 0, 1));
- float orbit_inertia = EDITOR_GET("editors/3d/navigation_feel/orbit_inertia");
+ real_t orbit_inertia = EDITOR_GET("editors/3d/navigation_feel/orbit_inertia");
orbit_inertia = MAX(0.0001, orbit_inertia);
camera_cursor.x_rot = Math::lerp(old_camera_cursor.x_rot, cursor.x_rot, MIN(1.f, p_interp_delta * (1 / orbit_inertia)));
camera_cursor.y_rot = Math::lerp(old_camera_cursor.y_rot, cursor.y_rot, MIN(1.f, p_interp_delta * (1 / orbit_inertia)));
@@ -298,13 +295,13 @@ void Node3DEditorViewport::_update_camera(float p_interp_delta) {
} else {
//when not being manipulated, move softly
- float free_orbit_inertia = EDITOR_GET("editors/3d/navigation_feel/orbit_inertia");
- float free_translation_inertia = EDITOR_GET("editors/3d/navigation_feel/translation_inertia");
+ real_t free_orbit_inertia = EDITOR_GET("editors/3d/navigation_feel/orbit_inertia");
+ real_t free_translation_inertia = EDITOR_GET("editors/3d/navigation_feel/translation_inertia");
//when being manipulated, move more quickly
- float manip_orbit_inertia = EDITOR_GET("editors/3d/navigation_feel/manipulation_orbit_inertia");
- float manip_translation_inertia = EDITOR_GET("editors/3d/navigation_feel/manipulation_translation_inertia");
+ real_t manip_orbit_inertia = EDITOR_GET("editors/3d/navigation_feel/manipulation_orbit_inertia");
+ real_t manip_translation_inertia = EDITOR_GET("editors/3d/navigation_feel/manipulation_translation_inertia");
- float zoom_inertia = EDITOR_GET("editors/3d/navigation_feel/zoom_inertia");
+ real_t zoom_inertia = EDITOR_GET("editors/3d/navigation_feel/zoom_inertia");
//determine if being manipulated
bool manipulated = Input::get_singleton()->get_mouse_button_mask() & (2 | 4);
@@ -312,8 +309,8 @@ void Node3DEditorViewport::_update_camera(float p_interp_delta) {
manipulated |= Input::get_singleton()->is_key_pressed(KEY_ALT);
manipulated |= Input::get_singleton()->is_key_pressed(KEY_CTRL);
- float orbit_inertia = MAX(0.00001, manipulated ? manip_orbit_inertia : free_orbit_inertia);
- float translation_inertia = MAX(0.0001, manipulated ? manip_translation_inertia : free_translation_inertia);
+ real_t orbit_inertia = MAX(0.00001, manipulated ? manip_orbit_inertia : free_orbit_inertia);
+ real_t translation_inertia = MAX(0.0001, manipulated ? manip_translation_inertia : free_translation_inertia);
zoom_inertia = MAX(0.0001, zoom_inertia);
camera_cursor.x_rot = Math::lerp(old_camera_cursor.x_rot, cursor.x_rot, MIN(1.f, p_interp_delta * (1 / orbit_inertia)));
@@ -328,7 +325,7 @@ void Node3DEditorViewport::_update_camera(float p_interp_delta) {
}
camera_cursor.pos = old_camera_cursor.pos.lerp(cursor.pos, MIN(1.f, p_interp_delta * (1 / translation_inertia)));
- camera_cursor.distance = Math::lerp(old_camera_cursor.distance, cursor.distance, MIN(1.f, p_interp_delta * (1 / zoom_inertia)));
+ camera_cursor.distance = Math::lerp(old_camera_cursor.distance, cursor.distance, MIN((real_t)1.0, p_interp_delta * (1 / zoom_inertia)));
}
}
@@ -436,16 +433,29 @@ Vector3 Node3DEditorViewport::_get_ray(const Vector2 &p_pos) const {
}
void Node3DEditorViewport::_clear_selected() {
- editor_selection->clear();
-}
-
-void Node3DEditorViewport::_select_clicked(bool p_append, bool p_single, bool p_allow_locked) {
- if (clicked.is_null()) {
- return;
+ _edit.gizmo = Ref<EditorNode3DGizmo>();
+ _edit.gizmo_handle = -1;
+ _edit.gizmo_initial_value = Variant();
+
+ Node3D *selected = spatial_editor->get_single_selected_node();
+ Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr;
+
+ if (se && se->gizmo.is_valid()) {
+ se->subgizmos.clear();
+ se->gizmo->redraw();
+ se->gizmo.unref();
+ spatial_editor->update_transform_gizmo();
+ } else {
+ editor_selection->clear();
+ Node3DEditor::get_singleton()->edit(nullptr);
}
+}
- Node *node = Object::cast_to<Node>(ObjectDB::get_instance(clicked));
+void Node3DEditorViewport::_select_clicked(bool p_allow_locked) {
+ Node *node = Object::cast_to<Node3D>(ObjectDB::get_instance(clicked));
Node3D *selected = Object::cast_to<Node3D>(node);
+ clicked = ObjectID();
+
if (!selected) {
return;
}
@@ -462,43 +472,27 @@ void Node3DEditorViewport::_select_clicked(bool p_append, bool p_single, bool p_
}
if (p_allow_locked || !_is_node_locked(selected)) {
- _select(selected, clicked_wants_append, true);
- }
-}
-
-void Node3DEditorViewport::_select(Node *p_node, bool p_append, bool p_single) {
- // Add or remove a single node from the selection
- if (p_append && p_single) {
- if (editor_selection->is_selected(p_node)) {
- // Already in the selection, remove it from the selected nodes
- editor_selection->remove_node(p_node);
+ if (clicked_wants_append) {
+ if (editor_selection->is_selected(selected)) {
+ editor_selection->remove_node(selected);
+ } else {
+ editor_selection->add_node(selected);
+ }
} else {
- // Add the item to the selection
- editor_selection->add_node(p_node);
- }
- } else if (p_append && !p_single) {
- // Add the item to the selection
- editor_selection->add_node(p_node);
- } else {
- // No append; single select
- editor_selection->clear();
- editor_selection->add_node(p_node);
- // Reselect
- if (Engine::get_singleton()->is_editor_hint()) {
- editor->call("edit_node", p_node);
+ if (!editor_selection->is_selected(selected)) {
+ editor_selection->clear();
+ editor_selection->add_node(selected);
+ editor->edit_node(selected);
+ }
}
- }
- if (editor_selection->get_selected_node_list().size() == 1) {
- editor->push_item(editor_selection->get_selected_node_list()[0]);
+ if (editor_selection->get_selected_node_list().size() == 1) {
+ editor->edit_node(editor_selection->get_selected_node_list()[0]);
+ }
}
}
-ObjectID Node3DEditorViewport::_select_ray(const Point2 &p_pos, bool p_append, bool &r_includes_current, int *r_gizmo_handle, bool p_alt_select) {
- if (r_gizmo_handle) {
- *r_gizmo_handle = -1;
- }
-
+ObjectID Node3DEditorViewport::_select_ray(const Point2 &p_pos) {
Vector3 ray = _get_ray(p_pos);
Vector3 pos = _get_ray_pos(p_pos);
Vector2 shrinked_pos = p_pos / subviewport_container->get_stretch_shrink();
@@ -514,7 +508,6 @@ ObjectID Node3DEditorViewport::_select_ray(const Point2 &p_pos, bool p_append, b
ObjectID closest;
Node *item = nullptr;
float closest_dist = 1e20;
- int selected_handle = -1;
for (int i = 0; i < instances.size(); i++) {
Node3D *spat = Object::cast_to<Node3D>(ObjectDB::get_instance(instances[i]));
@@ -523,38 +516,40 @@ ObjectID Node3DEditorViewport::_select_ray(const Point2 &p_pos, bool p_append, b
continue;
}
- Ref<EditorNode3DGizmo> seg = spat->get_gizmo();
+ Vector<Ref<Node3DGizmo>> gizmos = spat->get_gizmos();
- if ((!seg.is_valid()) || found_gizmos.has(seg)) {
- continue;
- }
+ for (int j = 0; j < gizmos.size(); j++) {
+ Ref<EditorNode3DGizmo> seg = gizmos[j];
- found_gizmos.insert(seg);
- Vector3 point;
- Vector3 normal;
+ if ((!seg.is_valid()) || found_gizmos.has(seg)) {
+ continue;
+ }
- int handle = -1;
- bool inters = seg->intersect_ray(camera, shrinked_pos, point, normal, &handle, p_alt_select);
+ found_gizmos.insert(seg);
+ Vector3 point;
+ Vector3 normal;
- if (!inters) {
- continue;
- }
+ bool inters = seg->intersect_ray(camera, shrinked_pos, point, normal);
- float dist = pos.distance_to(point);
+ if (!inters) {
+ continue;
+ }
- if (dist < 0) {
- continue;
- }
+ const real_t dist = pos.distance_to(point);
- if (dist < closest_dist) {
- item = Object::cast_to<Node>(spat);
- if (item != edited_scene) {
- item = edited_scene->get_deepest_editable_node(item);
+ if (dist < 0) {
+ continue;
}
- closest = item->get_instance_id();
- closest_dist = dist;
- selected_handle = handle;
+ if (dist < closest_dist) {
+ item = Object::cast_to<Node>(spat);
+ if (item != edited_scene) {
+ item = edited_scene->get_deepest_editable_node(item);
+ }
+
+ closest = item->get_instance_id();
+ closest_dist = dist;
+ }
}
}
@@ -562,23 +557,15 @@ ObjectID Node3DEditorViewport::_select_ray(const Point2 &p_pos, bool p_append, b
return ObjectID();
}
- if (!editor_selection->is_selected(item) || (r_gizmo_handle && selected_handle >= 0)) {
- if (r_gizmo_handle) {
- *r_gizmo_handle = selected_handle;
- }
- }
-
return closest;
}
-void Node3DEditorViewport::_find_items_at_pos(const Point2 &p_pos, bool &r_includes_current, Vector<_RayResult> &results, bool p_alt_select, bool p_include_locked_nodes) {
+void Node3DEditorViewport::_find_items_at_pos(const Point2 &p_pos, Vector<_RayResult> &r_results, bool p_include_locked_nodes) {
Vector3 ray = _get_ray(p_pos);
Vector3 pos = _get_ray_pos(p_pos);
Vector<ObjectID> instances = RenderingServer::get_singleton()->instances_cull_ray(pos, ray, get_tree()->get_root()->get_world_3d()->get_scenario());
- Set<Ref<EditorNode3DGizmo>> found_gizmos;
-
- r_includes_current = false;
+ Set<Node3D *> found_nodes;
for (int i = 0; i < instances.size(); i++) {
Node3D *spat = Object::cast_to<Node3D>(ObjectDB::get_instance(instances[i]));
@@ -587,53 +574,48 @@ void Node3DEditorViewport::_find_items_at_pos(const Point2 &p_pos, bool &r_inclu
continue;
}
- Ref<EditorNode3DGizmo> seg = spat->get_gizmo();
-
- if (!seg.is_valid()) {
+ if (found_nodes.has(spat)) {
continue;
}
- if (found_gizmos.has(seg)) {
+ if (!p_include_locked_nodes && _is_node_locked(spat)) {
continue;
}
- found_gizmos.insert(seg);
- Vector3 point;
- Vector3 normal;
+ Vector<Ref<Node3DGizmo>> gizmos = spat->get_gizmos();
+ for (int j = 0; j < gizmos.size(); j++) {
+ Ref<EditorNode3DGizmo> seg = gizmos[j];
- int handle = -1;
- bool inters = seg->intersect_ray(camera, p_pos, point, normal, nullptr, p_alt_select);
+ if (!seg.is_valid()) {
+ continue;
+ }
- if (!inters) {
- continue;
- }
+ Vector3 point;
+ Vector3 normal;
- float dist = pos.distance_to(point);
+ bool inters = seg->intersect_ray(camera, p_pos, point, normal);
- if (dist < 0) {
- continue;
- }
+ if (!inters) {
+ continue;
+ }
- if (!p_include_locked_nodes && _is_node_locked(spat)) {
- continue;
- }
+ const real_t dist = pos.distance_to(point);
- if (editor_selection->is_selected(spat)) {
- r_includes_current = true;
- }
+ if (dist < 0) {
+ continue;
+ }
- _RayResult res;
- res.item = spat;
- res.depth = dist;
- res.handle = handle;
- results.push_back(res);
- }
+ found_nodes.insert(spat);
- if (results.is_empty()) {
- return;
+ _RayResult res;
+ res.item = spat;
+ res.depth = dist;
+ r_results.push_back(res);
+ break;
+ }
}
- results.sort();
+ r_results.sort();
}
Vector3 Node3DEditorViewport::_get_screen_to_space(const Vector3 &p_vector3) {
@@ -656,10 +638,13 @@ Vector3 Node3DEditorViewport::_get_screen_to_space(const Vector3 &p_vector3) {
void Node3DEditorViewport::_select_region() {
if (cursor.region_begin == cursor.region_end) {
+ if (!clicked_wants_append) {
+ _clear_selected();
+ }
return; //nothing really
}
- float z_offset = MAX(0.0, 5.0 - get_znear());
+ const real_t z_offset = MAX(0.0, 5.0 - get_znear());
Vector3 box[4] = {
Vector3(
@@ -702,7 +687,66 @@ void Node3DEditorViewport::_select_region() {
far.d += get_zfar();
frustum.push_back(far);
+ if (spatial_editor->get_single_selected_node()) {
+ Node3D *single_selected = spatial_editor->get_single_selected_node();
+ Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(single_selected);
+
+ Ref<EditorNode3DGizmo> old_gizmo;
+ if (!clicked_wants_append) {
+ se->subgizmos.clear();
+ old_gizmo = se->gizmo;
+ se->gizmo.unref();
+ }
+
+ bool found_subgizmos = false;
+ Vector<Ref<Node3DGizmo>> gizmos = single_selected->get_gizmos();
+ for (int j = 0; j < gizmos.size(); j++) {
+ Ref<EditorNode3DGizmo> seg = gizmos[j];
+ if (!seg.is_valid()) {
+ continue;
+ }
+
+ if (se->gizmo.is_valid() && se->gizmo != seg) {
+ continue;
+ }
+
+ Vector<int> subgizmos = seg->subgizmos_intersect_frustum(camera, frustum);
+ if (!subgizmos.is_empty()) {
+ se->gizmo = seg;
+ for (int i = 0; i < subgizmos.size(); i++) {
+ int subgizmo_id = subgizmos[i];
+ if (!se->subgizmos.has(subgizmo_id)) {
+ se->subgizmos.insert(subgizmo_id, se->gizmo->get_subgizmo_transform(subgizmo_id));
+ }
+ }
+ found_subgizmos = true;
+ break;
+ }
+ }
+
+ if (!clicked_wants_append || found_subgizmos) {
+ if (se->gizmo.is_valid()) {
+ se->gizmo->redraw();
+ }
+
+ if (old_gizmo != se->gizmo && old_gizmo.is_valid()) {
+ old_gizmo->redraw();
+ }
+
+ spatial_editor->update_transform_gizmo();
+ }
+
+ if (found_subgizmos) {
+ return;
+ }
+ }
+
+ if (!clicked_wants_append) {
+ _clear_selected();
+ }
+
Vector<ObjectID> instances = RenderingServer::get_singleton()->instances_cull_convex(frustum, get_tree()->get_root()->get_world_3d()->get_scenario());
+ Set<Node3D *> found_nodes;
Vector<Node *> selected;
Node *edited_scene = get_tree()->get_edited_scene_root();
@@ -713,6 +757,12 @@ void Node3DEditorViewport::_select_region() {
continue;
}
+ if (found_nodes.has(sp)) {
+ continue;
+ }
+
+ found_nodes.insert(sp);
+
Node *item = Object::cast_to<Node>(sp);
if (item != edited_scene) {
item = edited_scene->get_deepest_editable_node(item);
@@ -731,28 +781,31 @@ void Node3DEditorViewport::_select_region() {
item = sel;
}
- if (selected.find(item) != -1) {
- continue;
- }
-
if (_is_node_locked(item)) {
continue;
}
- Ref<EditorNode3DGizmo> seg = sp->get_gizmo();
+ Vector<Ref<Node3DGizmo>> gizmos = sp->get_gizmos();
+ for (int j = 0; j < gizmos.size(); j++) {
+ Ref<EditorNode3DGizmo> seg = gizmos[j];
+ if (!seg.is_valid()) {
+ continue;
+ }
- if (!seg.is_valid()) {
- continue;
+ if (seg->intersect_frustum(camera, frustum)) {
+ selected.push_back(item);
+ }
}
+ }
- if (seg->intersect_frustum(camera, frustum)) {
- selected.push_back(item);
+ for (int i = 0; i < selected.size(); i++) {
+ if (!editor_selection->is_selected(selected[i])) {
+ editor_selection->add_node(selected[i]);
}
}
- bool single = selected.size() == 1;
- for (int i = 0; i < selected.size(); i++) {
- _select(selected[i], true, single);
+ if (editor_selection->get_selected_node_list().size() == 1) {
+ editor->edit_node(editor_selection->get_selected_node_list()[0]);
}
}
@@ -779,21 +832,34 @@ void Node3DEditorViewport::_compute_edit(const Point2 &p_point) {
spatial_editor->update_transform_gizmo();
_edit.center = spatial_editor->get_gizmo_transform().origin;
- List<Node *> &selection = editor_selection->get_selected_node_list();
+ Node3D *selected = spatial_editor->get_single_selected_node();
+ Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr;
- for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
- Node3D *sp = Object::cast_to<Node3D>(E->get());
- if (!sp) {
- continue;
+ if (se && se->gizmo.is_valid()) {
+ for (Map<int, Transform3D>::Element *E = se->subgizmos.front(); E; E = E->next()) {
+ int subgizmo_id = E->key();
+ se->subgizmos[subgizmo_id] = se->gizmo->get_subgizmo_transform(subgizmo_id);
}
+ se->original_local = selected->get_transform();
+ se->original = selected->get_global_transform();
+ } else {
+ List<Node *> &selection = editor_selection->get_selected_node_list();
- Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);
- if (!se) {
- continue;
- }
+ for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
+ Node3D *sp = Object::cast_to<Node3D>(E->get());
+ if (!sp) {
+ continue;
+ }
+
+ Node3DEditorSelectedItem *sel_item = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);
+
+ if (!sel_item) {
+ continue;
+ }
- se->original = se->sp->get_global_gizmo_transform();
- se->original_local = se->sp->get_local_gizmo_transform();
+ sel_item->original_local = sel_item->sp->get_local_gizmo_transform();
+ sel_item->original = sel_item->sp->get_global_gizmo_transform();
+ }
}
}
@@ -829,7 +895,7 @@ static int _get_key_modifier(Ref<InputEventWithModifiers> e) {
return 0;
}
-bool Node3DEditorViewport::_gizmo_select(const Vector2 &p_screenpos, bool p_highlight_only) {
+bool Node3DEditorViewport::_transform_gizmo_select(const Vector2 &p_screenpos, bool p_highlight_only) {
if (!spatial_editor->is_gizmo_visible()) {
return false;
}
@@ -844,20 +910,19 @@ bool Node3DEditorViewport::_gizmo_select(const Vector2 &p_screenpos, bool p_high
Vector3 ray = _get_ray(Vector2(p_screenpos.x, p_screenpos.y));
Transform3D gt = spatial_editor->get_gizmo_transform();
- float gs = gizmo_scale;
if (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_MOVE) {
int col_axis = -1;
- float col_d = 1e20;
+ real_t 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;
+ const Vector3 grabber_pos = gt.origin + gt.basis.get_axis(i) * gizmo_scale * (GIZMO_ARROW_OFFSET + (GIZMO_ARROW_SIZE * 0.5));
+ const real_t grabber_radius = gizmo_scale * GIZMO_ARROW_SIZE;
Vector3 r;
if (Geometry3D::segment_intersects_sphere(ray_pos, ray_pos + ray * MAX_Z, grabber_pos, grabber_radius, &r)) {
- float d = r.distance_to(ray_pos);
+ const real_t d = r.distance_to(ray_pos);
if (d < col_d) {
col_d = d;
col_axis = i;
@@ -874,15 +939,19 @@ bool Node3DEditorViewport::_gizmo_select(const Vector2 &p_screenpos, bool p_high
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);
+ // Allow some tolerance to make the plane easier to click,
+ // even if the click is actually slightly outside the plane.
+ const Vector3 grabber_pos = gt.origin + (ivec2 + ivec3) * gizmo_scale * (GIZMO_PLANE_SIZE + GIZMO_PLANE_DST * 0.6667);
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);
+ const real_t dist = r.distance_to(grabber_pos);
+ // Allow some tolerance to make the plane easier to click,
+ // even if the click is actually slightly outside the plane.
+ if (dist < (gizmo_scale * GIZMO_PLANE_SIZE * 1.5)) {
+ const real_t d = ray_pos.distance_to(r);
if (d < col_d) {
col_d = d;
col_axis = i;
@@ -919,12 +988,12 @@ bool Node3DEditorViewport::_gizmo_select(const Vector2 &p_screenpos, bool p_high
continue;
}
- float dist = r.distance_to(gt.origin);
- Vector3 r_dir = (r - gt.origin).normalized();
+ const real_t dist = r.distance_to(gt.origin);
+ const Vector3 r_dir = (r - gt.origin).normalized();
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 (dist > gizmo_scale * (GIZMO_CIRCLE_SIZE - GIZMO_RING_HALF_WIDTH) && dist < gizmo_scale * (GIZMO_CIRCLE_SIZE + GIZMO_RING_HALF_WIDTH)) {
+ const real_t d = ray_pos.distance_to(r);
if (d < col_d) {
col_d = d;
col_axis = i;
@@ -951,13 +1020,13 @@ bool Node3DEditorViewport::_gizmo_select(const Vector2 &p_screenpos, bool p_high
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;
+ const Vector3 grabber_pos = gt.origin + gt.basis.get_axis(i) * gizmo_scale * GIZMO_SCALE_OFFSET;
+ const real_t grabber_radius = gizmo_scale * GIZMO_ARROW_SIZE;
Vector3 r;
if (Geometry3D::segment_intersects_sphere(ray_pos, ray_pos + ray * MAX_Z, grabber_pos, grabber_radius, &r)) {
- float d = r.distance_to(ray_pos);
+ const real_t d = r.distance_to(ray_pos);
if (d < col_d) {
col_d = d;
col_axis = i;
@@ -971,18 +1040,22 @@ bool Node3DEditorViewport::_gizmo_select(const Vector2 &p_screenpos, bool p_high
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();
+ const Vector3 ivec2 = gt.basis.get_axis((i + 1) % 3).normalized();
+ const Vector3 ivec3 = gt.basis.get_axis((i + 2) % 3).normalized();
- Vector3 grabber_pos = gt.origin + (ivec2 + ivec3) * gs * (GIZMO_PLANE_SIZE + GIZMO_PLANE_DST);
+ // Allow some tolerance to make the plane easier to click,
+ // even if the click is actually slightly outside the plane.
+ const Vector3 grabber_pos = gt.origin + (ivec2 + ivec3) * gizmo_scale * (GIZMO_PLANE_SIZE + GIZMO_PLANE_DST * 0.6667);
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);
+ const real_t dist = r.distance_to(grabber_pos);
+ // Allow some tolerance to make the plane easier to click,
+ // even if the click is actually slightly outside the plane.
+ if (dist < (gizmo_scale * GIZMO_PLANE_SIZE * 1.5)) {
+ const real_t d = ray_pos.distance_to(r);
if (d < col_d) {
col_d = d;
col_axis = i;
@@ -1015,6 +1088,88 @@ bool Node3DEditorViewport::_gizmo_select(const Vector2 &p_screenpos, bool p_high
return false;
}
+void Node3DEditorViewport::_transform_gizmo_apply(Node3D *p_node, const Transform3D &p_transform, bool p_local) {
+ if (p_transform.basis.determinant() == 0) {
+ return;
+ }
+
+ if (p_local) {
+ p_node->set_transform(p_transform);
+ } else {
+ p_node->set_global_transform(p_transform);
+ }
+}
+
+Transform3D Node3DEditorViewport::_compute_transform(TransformMode p_mode, const Transform3D &p_original, const Transform3D &p_original_local, Vector3 p_motion, double p_extra, bool p_local) {
+ switch (p_mode) {
+ case TRANSFORM_SCALE: {
+ if (p_local) {
+ Basis g = p_original.basis.orthonormalized();
+ Vector3 local_motion = g.inverse().xform(p_motion);
+
+ if (_edit.snap || spatial_editor->is_snap_enabled()) {
+ local_motion.snap(Vector3(p_extra, p_extra, p_extra));
+ }
+
+ Vector3 local_scale = p_original_local.basis.get_scale() * (local_motion + Vector3(1, 1, 1));
+ Transform3D local_t = p_original_local;
+ local_t.basis.set_euler_scale(p_original_local.basis.get_rotation_euler(), local_scale);
+ return local_t;
+ } else {
+ Transform3D base = Transform3D(Basis(), _edit.center);
+ if (_edit.snap || spatial_editor->is_snap_enabled()) {
+ p_motion.snap(Vector3(p_extra, p_extra, p_extra));
+ }
+
+ Transform3D r;
+ r.basis.scale(p_motion + Vector3(1, 1, 1));
+ return base * (r * (base.inverse() * p_original));
+ }
+ }
+ case TRANSFORM_TRANSLATE: {
+ if (p_local) {
+ if (_edit.snap || spatial_editor->is_snap_enabled()) {
+ Basis g = p_original.basis.orthonormalized();
+ Vector3 local_motion = g.inverse().xform(p_motion);
+ local_motion.snap(Vector3(p_extra, p_extra, p_extra));
+
+ p_motion = g.xform(local_motion);
+ }
+
+ } else {
+ if (_edit.snap || spatial_editor->is_snap_enabled()) {
+ p_motion.snap(Vector3(p_extra, p_extra, p_extra));
+ }
+ }
+
+ // Apply translation
+ Transform3D t = p_original;
+ t.origin += p_motion;
+ return t;
+ }
+ case TRANSFORM_ROTATE: {
+ if (p_local) {
+ Basis rot = Basis(p_motion, p_extra);
+
+ Vector3 scale = p_original_local.basis.get_scale();
+ Vector3 euler = (p_original_local.get_basis().orthonormalized() * rot).get_euler();
+ Transform3D t;
+ t.basis.set_euler_scale(euler, scale);
+ t.origin = p_original_local.origin;
+ return t;
+ } else {
+ Transform3D r;
+ r.basis.rotate(p_motion, p_extra);
+ Transform3D base = Transform3D(Basis(), _edit.center);
+ return base * r * base.inverse() * p_original;
+ }
+ }
+ default: {
+ ERR_FAIL_V_MSG(Transform3D(), "Invalid mode in '_compute_transform'");
+ }
+ }
+}
+
void Node3DEditorViewport::_surface_mouse_enter() {
if (!surface->has_focus() && (!get_focus_owner() || !get_focus_owner()->is_text_field())) {
surface->grab_focus();
@@ -1038,7 +1193,7 @@ bool Node3DEditorViewport ::_is_node_locked(const Node *p_node) {
}
void Node3DEditorViewport::_list_select(Ref<InputEventMouseButton> b) {
- _find_items_at_pos(b->get_position(), clicked_includes_current, selection_results, b->is_shift_pressed(), spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT);
+ _find_items_at_pos(b->get_position(), selection_results, spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT);
Node *scene = editor->get_edited_scene();
@@ -1058,10 +1213,8 @@ void Node3DEditorViewport::_list_select(Ref<InputEventMouseButton> b) {
selection_results.clear();
if (clicked.is_valid()) {
- _select_clicked(clicked_wants_append, true, spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT);
- clicked = ObjectID();
+ _select_clicked(spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT);
}
-
} else if (!selection_results.is_empty()) {
NodePath root_path = get_tree()->get_edited_scene_root()->get_path();
StringName root_name = root_path.get_name(root_path.get_name_count() - 1);
@@ -1137,7 +1290,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
if (b.is_valid()) {
emit_signal(SNAME("clicked"), this);
- float zoom_factor = 1 + (ZOOM_FREELOOK_MULTIPLIER - 1) * b->get_factor();
+ const real_t zoom_factor = 1 + (ZOOM_FREELOOK_MULTIPLIER - 1) * b->get_factor();
switch (b->get_button_index()) {
case MOUSE_BUTTON_WHEEL_UP: {
if (is_freelook_active()) {
@@ -1179,8 +1332,8 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
List<Node *> &selection = editor_selection->get_selected_node_list();
- for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
- Node3D *sp = Object::cast_to<Node3D>(E->get());
+ for (Node *E : selection) {
+ Node3D *sp = Object::cast_to<Node3D>(E);
if (!sp) {
continue;
}
@@ -1190,7 +1343,20 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
continue;
}
- sp->set_global_transform(se->original);
+ if (se->gizmo.is_valid()) {
+ Vector<int> ids;
+ Vector<Transform3D> restore;
+
+ for (Map<int, Transform3D>::Element *GE = se->subgizmos.front(); GE; GE = GE->next()) {
+ ids.push_back(GE->key());
+ restore.push_back(GE->value());
+ }
+
+ se->gizmo->commit_subgizmos(ids, restore, true);
+ spatial_editor->update_transform_gizmo();
+ } else {
+ sp->set_global_transform(se->original);
+ }
}
surface->update();
set_message(TTR("Transform Aborted."), 3);
@@ -1262,37 +1428,92 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
_edit.snap = spatial_editor->is_snap_enabled();
_edit.mode = TRANSFORM_NONE;
- //gizmo has priority over everything
-
- bool can_select_gizmos = true;
+ bool can_select_gizmos = spatial_editor->get_single_selected_node();
{
int idx = view_menu->get_popup()->get_item_index(VIEW_GIZMOS);
- can_select_gizmos = view_menu->get_popup()->is_item_checked(idx);
+ can_select_gizmos = can_select_gizmos && view_menu->get_popup()->is_item_checked(idx);
}
- if (can_select_gizmos && spatial_editor->get_selected()) {
- Ref<EditorNode3DGizmo> seg = spatial_editor->get_selected()->get_gizmo();
- if (seg.is_valid()) {
- int handle = -1;
- Vector3 point;
- Vector3 normal;
- bool inters = seg->intersect_ray(camera, _edit.mouse_pos, point, normal, &handle, b->is_shift_pressed());
- if (inters && handle != -1) {
+ // Gizmo handles
+ if (can_select_gizmos) {
+ Vector<Ref<Node3DGizmo>> gizmos = spatial_editor->get_single_selected_node()->get_gizmos();
+
+ bool intersected_handle = false;
+ for (int i = 0; i < gizmos.size(); i++) {
+ Ref<EditorNode3DGizmo> seg = gizmos[i];
+
+ if ((!seg.is_valid())) {
+ continue;
+ }
+
+ int gizmo_handle = -1;
+ seg->handles_intersect_ray(camera, _edit.mouse_pos, b->is_shift_pressed(), gizmo_handle);
+ if (gizmo_handle != -1) {
_edit.gizmo = seg;
- _edit.gizmo_handle = handle;
- _edit.gizmo_initial_value = seg->get_handle_value(handle);
+ _edit.gizmo_handle = gizmo_handle;
+ _edit.gizmo_initial_value = seg->get_handle_value(gizmo_handle);
+ intersected_handle = true;
break;
}
}
+
+ if (intersected_handle) {
+ break;
+ }
}
- if (_gizmo_select(_edit.mouse_pos)) {
+ // Transform gizmo
+ if (_transform_gizmo_select(_edit.mouse_pos)) {
break;
}
+ // Subgizmos
+ if (can_select_gizmos) {
+ Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(spatial_editor->get_single_selected_node());
+ Vector<Ref<Node3DGizmo>> gizmos = spatial_editor->get_single_selected_node()->get_gizmos();
+
+ bool intersected_subgizmo = false;
+ for (int i = 0; i < gizmos.size(); i++) {
+ Ref<EditorNode3DGizmo> seg = gizmos[i];
+
+ if ((!seg.is_valid())) {
+ continue;
+ }
+
+ int subgizmo_id = seg->subgizmos_intersect_ray(camera, _edit.mouse_pos);
+ if (subgizmo_id != -1) {
+ ERR_CONTINUE(!se);
+ if (b->is_shift_pressed()) {
+ if (se->subgizmos.has(subgizmo_id)) {
+ se->subgizmos.erase(subgizmo_id);
+ } else {
+ se->subgizmos.insert(subgizmo_id, seg->get_subgizmo_transform(subgizmo_id));
+ }
+ } else {
+ se->subgizmos.clear();
+ se->subgizmos.insert(subgizmo_id, seg->get_subgizmo_transform(subgizmo_id));
+ }
+
+ if (se->subgizmos.is_empty()) {
+ se->gizmo = Ref<EditorNode3DGizmo>();
+ } else {
+ se->gizmo = seg;
+ }
+
+ seg->redraw();
+ spatial_editor->update_transform_gizmo();
+ intersected_subgizmo = true;
+ break;
+ }
+ }
+
+ if (intersected_subgizmo) {
+ break;
+ }
+ }
+
clicked = ObjectID();
- clicked_includes_current = false;
if ((spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT && b->is_command_pressed()) || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE) {
/* HANDLE ROTATION */
@@ -1325,40 +1546,19 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
break;
}
- // todo scale
-
- int gizmo_handle = -1;
-
- clicked = _select_ray(b->get_position(), b->is_shift_pressed(), clicked_includes_current, &gizmo_handle, b->is_shift_pressed());
+ clicked = _select_ray(b->get_position());
//clicking is always deferred to either move or release
clicked_wants_append = b->is_shift_pressed();
if (clicked.is_null()) {
- if (!clicked_wants_append) {
- _clear_selected();
- }
-
//default to regionselect
cursor.region_select = true;
cursor.region_begin = b->get_position();
cursor.region_end = b->get_position();
}
- if (clicked.is_valid() && gizmo_handle >= 0) {
- Node3D *spa = Object::cast_to<Node3D>(ObjectDB::get_instance(clicked));
- if (spa) {
- Ref<EditorNode3DGizmo> seg = spa->get_gizmo();
- if (seg.is_valid()) {
- _edit.gizmo = seg;
- _edit.gizmo_handle = gizmo_handle;
- _edit.gizmo_initial_value = seg->get_handle_value(gizmo_handle);
- break;
- }
- }
- }
-
surface->update();
} else {
if (_edit.gizmo.is_valid()) {
@@ -1366,47 +1566,63 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
_edit.gizmo = Ref<EditorNode3DGizmo>();
break;
}
+
if (clicked.is_valid()) {
- _select_clicked(clicked_wants_append, true);
- // Processing was deferred.
- clicked = ObjectID();
+ _select_clicked(false);
}
if (cursor.region_select) {
- if (!clicked_wants_append) {
- _clear_selected();
- }
-
_select_region();
cursor.region_select = false;
surface->update();
}
if (_edit.mode != TRANSFORM_NONE) {
- static const char *_transform_name[4] = { "None", "Rotate", "Translate", "Scale" };
- undo_redo->create_action(_transform_name[_edit.mode]);
+ Node3D *selected = spatial_editor->get_single_selected_node();
+ Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr;
- List<Node *> &selection = editor_selection->get_selected_node_list();
+ if (se && se->gizmo.is_valid()) {
+ Vector<int> ids;
+ Vector<Transform3D> restore;
- for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
- Node3D *sp = Object::cast_to<Node3D>(E->get());
- if (!sp) {
- continue;
+ for (Map<int, Transform3D>::Element *GE = se->subgizmos.front(); GE; GE = GE->next()) {
+ ids.push_back(GE->key());
+ restore.push_back(GE->value());
}
- Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);
- if (!se) {
- continue;
- }
+ se->gizmo->commit_subgizmos(ids, restore, false);
+ spatial_editor->update_transform_gizmo();
+ } else {
+ static const char *_transform_name[4] = {
+ TTRC("None"),
+ TTRC("Rotate"),
+ // TRANSLATORS: This refers to the movement that changes the position of an object.
+ TTRC("Translate"),
+ TTRC("Scale"),
+ };
+ undo_redo->create_action(TTRGET(_transform_name[_edit.mode]));
+
+ List<Node *> &selection = editor_selection->get_selected_node_list();
+
+ for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
+ Node3D *sp = Object::cast_to<Node3D>(E->get());
+ if (!sp) {
+ continue;
+ }
- undo_redo->add_do_method(sp, "set_global_transform", sp->get_global_gizmo_transform());
- undo_redo->add_undo_method(sp, "set_global_transform", se->original);
+ Node3DEditorSelectedItem *sel_item = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);
+ if (!sel_item) {
+ continue;
+ }
+
+ undo_redo->add_do_method(sp, "set_global_transform", sp->get_global_gizmo_transform());
+ undo_redo->add_undo_method(sp, "set_global_transform", sel_item->original);
+ }
+ undo_redo->commit_action();
}
- undo_redo->commit_action();
_edit.mode = TRANSFORM_NONE;
set_message("");
}
-
surface->update();
}
@@ -1421,31 +1637,39 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
if (m.is_valid()) {
_edit.mouse_pos = m->get_position();
- if (spatial_editor->get_selected()) {
- Ref<EditorNode3DGizmo> seg = spatial_editor->get_selected()->get_gizmo();
- if (seg.is_valid()) {
- int selected_handle = -1;
-
- int handle = -1;
- Vector3 point;
- Vector3 normal;
- bool inters = seg->intersect_ray(camera, _edit.mouse_pos, point, normal, &handle, false);
- if (inters && handle != -1) {
- selected_handle = handle;
+ if (spatial_editor->get_single_selected_node()) {
+ Vector<Ref<Node3DGizmo>> gizmos = spatial_editor->get_single_selected_node()->get_gizmos();
+
+ Ref<EditorNode3DGizmo> found_gizmo;
+ int found_handle = -1;
+
+ for (int i = 0; i < gizmos.size(); i++) {
+ Ref<EditorNode3DGizmo> seg = gizmos[i];
+ if (!seg.is_valid()) {
+ continue;
}
- if (selected_handle != spatial_editor->get_over_gizmo_handle()) {
- spatial_editor->set_over_gizmo_handle(selected_handle);
- spatial_editor->get_selected()->update_gizmo();
- if (selected_handle != -1) {
- spatial_editor->select_gizmo_highlight_axis(-1);
- }
+ seg->handles_intersect_ray(camera, _edit.mouse_pos, false, found_handle);
+
+ if (found_handle != -1) {
+ found_gizmo = seg;
+ break;
}
}
+
+ if (found_gizmo.is_valid()) {
+ spatial_editor->select_gizmo_highlight_axis(-1);
+ }
+
+ if (found_gizmo != spatial_editor->get_current_hover_gizmo() || found_handle != spatial_editor->get_current_hover_gizmo_handle()) {
+ spatial_editor->set_current_hover_gizmo(found_gizmo);
+ spatial_editor->set_current_hover_gizmo_handle(found_handle);
+ spatial_editor->get_single_selected_node()->update_gizmos();
+ }
}
- if (spatial_editor->get_over_gizmo_handle() == -1 && !(m->get_button_mask() & 1) && !_edit.gizmo.is_valid()) {
- _gizmo_select(_edit.mouse_pos, true);
+ if (spatial_editor->get_current_hover_gizmo().is_null() && !(m->get_button_mask() & 1) && !_edit.gizmo.is_valid()) {
+ _transform_gizmo_select(_edit.mouse_pos, true);
}
NavigationScheme nav_scheme = (NavigationScheme)EditorSettings::get_singleton()->get("editors/3d/navigation/navigation_scheme").operator int();
@@ -1469,11 +1693,6 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
} else {
const bool movement_threshold_passed = _edit.original_mouse_pos.distance_to(_edit.mouse_pos) > 8 * EDSCALE;
if (clicked.is_valid() && movement_threshold_passed) {
- if (!clicked_includes_current) {
- _select_clicked(clicked_wants_append, true);
- // Processing was deferred.
- }
-
_compute_edit(_edit.mouse_pos);
clicked = ObjectID();
@@ -1558,18 +1777,16 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
}
} else {
- float center_click_dist = click.distance_to(_edit.center);
- float center_inters_dist = intersection.distance_to(_edit.center);
+ const real_t center_click_dist = click.distance_to(_edit.center);
+ const real_t center_inters_dist = intersection.distance_to(_edit.center);
if (center_click_dist == 0) {
break;
}
- float scale = center_inters_dist - center_click_dist;
+ const real_t scale = center_inters_dist - center_click_dist;
motion = Vector3(scale, scale, scale);
}
- List<Node *> &selection = editor_selection->get_selected_node_list();
-
// Disable local transformation for TRANSFORM_VIEW
bool local_coords = (spatial_editor->are_local_coords_enabled() && _edit.plane != TRANSFORM_VIEW);
@@ -1582,8 +1799,9 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
set_message(TTR("Scaling: ") + "(" + String::num(motion_snapped.x, snap_step_decimals) + ", " +
String::num(motion_snapped.y, snap_step_decimals) + ", " + String::num(motion_snapped.z, snap_step_decimals) + ")");
- for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
- Node3D *sp = Object::cast_to<Node3D>(E->get());
+ List<Node *> &selection = editor_selection->get_selected_node_list();
+ for (Node *E : selection) {
+ Node3D *sp = Object::cast_to<Node3D>(E);
if (!sp) {
continue;
}
@@ -1597,44 +1815,22 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
continue;
}
- Transform3D original = se->original;
- Transform3D original_local = se->original_local;
- Transform3D base = Transform3D(Basis(), _edit.center);
- Transform3D t;
- Vector3 local_scale;
-
- if (local_coords) {
- Basis g = original.basis.orthonormalized();
- Vector3 local_motion = g.inverse().xform(motion);
-
- if (_edit.snap || spatial_editor->is_snap_enabled()) {
- local_motion.snap(Vector3(snap, snap, snap));
- }
-
- local_scale = original_local.basis.get_scale() * (local_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) {
- // Apply scale
- sp->set_scale(local_scale);
+ if (se->gizmo.is_valid()) {
+ for (Map<int, Transform3D>::Element *GE = se->subgizmos.front(); GE; GE = GE->next()) {
+ Transform3D xform = GE->get();
+ Transform3D new_xform = _compute_transform(TRANSFORM_SCALE, se->original * xform, xform, motion, snap, local_coords);
+ if (!local_coords) {
+ new_xform = se->original.affine_inverse() * new_xform;
+ }
+ se->gizmo->set_subgizmo_transform(GE->key(), new_xform);
}
-
} else {
- if (_edit.snap || spatial_editor->is_snap_enabled()) {
- motion.snap(Vector3(snap, snap, snap));
- }
-
- Transform3D r;
- r.basis.scale(motion + Vector3(1, 1, 1));
- t = base * (r * (base.inverse() * original));
-
- // Apply scale
- sp->set_global_transform(t);
+ Transform3D new_xform = _compute_transform(TRANSFORM_SCALE, se->original, se->original_local, motion, snap, local_coords);
+ _transform_gizmo_apply(se->sp, new_xform, local_coords);
}
}
+ spatial_editor->update_transform_gizmo();
surface->update();
} break;
@@ -1691,8 +1887,6 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
}
}
- List<Node *> &selection = editor_selection->get_selected_node_list();
-
// Disable local transformation for TRANSFORM_VIEW
bool local_coords = (spatial_editor->are_local_coords_enabled() && _edit.plane != TRANSFORM_VIEW);
@@ -1704,8 +1898,9 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
set_message(TTR("Translating: ") + "(" + String::num(motion_snapped.x, snap_step_decimals) + ", " +
String::num(motion_snapped.y, snap_step_decimals) + ", " + String::num(motion_snapped.z, snap_step_decimals) + ")");
- for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
- Node3D *sp = Object::cast_to<Node3D>(E->get());
+ List<Node *> &selection = editor_selection->get_selected_node_list();
+ for (Node *E : selection) {
+ Node3D *sp = Object::cast_to<Node3D>(E);
if (!sp) {
continue;
}
@@ -1719,30 +1914,20 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
continue;
}
- Transform3D original = se->original;
- Transform3D t;
-
- if (local_coords) {
- if (_edit.snap || spatial_editor->is_snap_enabled()) {
- Basis g = original.basis.orthonormalized();
- Vector3 local_motion = g.inverse().xform(motion);
- local_motion.snap(Vector3(snap, snap, snap));
-
- motion = g.xform(local_motion);
+ if (se->gizmo.is_valid()) {
+ for (Map<int, Transform3D>::Element *GE = se->subgizmos.front(); GE; GE = GE->next()) {
+ Transform3D xform = GE->get();
+ Transform3D new_xform = _compute_transform(TRANSFORM_TRANSLATE, se->original * xform, xform, motion, snap, local_coords);
+ new_xform = se->original.affine_inverse() * new_xform;
+ se->gizmo->set_subgizmo_transform(GE->key(), new_xform);
}
-
} else {
- if (_edit.snap || spatial_editor->is_snap_enabled()) {
- motion.snap(Vector3(snap, snap, snap));
- }
+ Transform3D new_xform = _compute_transform(TRANSFORM_TRANSLATE, se->original, se->original_local, motion, snap, local_coords);
+ _transform_gizmo_apply(se->sp, new_xform, false);
}
-
- // Apply translation
- t = original;
- t.origin += motion;
- sp->set_global_transform(t);
}
+ spatial_editor->update_transform_gizmo();
surface->update();
} break;
@@ -1796,12 +1981,11 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
set_message(vformat(TTR("Rotating %s degrees."), String::num(angle, snap_step_decimals)));
angle = Math::deg2rad(angle);
- List<Node *> &selection = editor_selection->get_selected_node_list();
-
bool local_coords = (spatial_editor->are_local_coords_enabled() && _edit.plane != TRANSFORM_VIEW); // Disable local transformation for TRANSFORM_VIEW
- for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
- Node3D *sp = Object::cast_to<Node3D>(E->get());
+ List<Node *> &selection = editor_selection->get_selected_node_list();
+ for (Node *E : selection) {
+ Node3D *sp = Object::cast_to<Node3D>(E);
if (!sp) {
continue;
}
@@ -1815,32 +1999,24 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
continue;
}
- Transform3D t;
-
- if (local_coords) {
- Transform3D original_local = se->original_local;
- Basis rot = Basis(axis, angle);
-
- t.basis = original_local.get_basis().orthonormalized() * rot;
- t.origin = original_local.origin;
-
- // Apply rotation
- sp->set_transform(t);
- sp->set_scale(original_local.basis.get_scale()); // re-apply original scale
+ Vector3 compute_axis = local_coords ? axis : plane.normal;
+ if (se->gizmo.is_valid()) {
+ for (Map<int, Transform3D>::Element *GE = se->subgizmos.front(); GE; GE = GE->next()) {
+ Transform3D xform = GE->get();
+ Transform3D new_xform = _compute_transform(TRANSFORM_ROTATE, se->original * xform, xform, compute_axis, angle, local_coords);
+ if (!local_coords) {
+ new_xform = se->original.affine_inverse() * new_xform;
+ }
+ se->gizmo->set_subgizmo_transform(GE->key(), new_xform);
+ }
} else {
- Transform3D original = se->original;
- Transform3D r;
- Transform3D base = Transform3D(Basis(), _edit.center);
-
- r.basis.rotate(plane.normal, angle);
- t = base * r * base.inverse() * original;
-
- // Apply rotation
- sp->set_global_transform(t);
+ Transform3D new_xform = _compute_transform(TRANSFORM_ROTATE, se->original, se->original_local, compute_axis, angle, local_coords);
+ _transform_gizmo_apply(se->sp, new_xform, local_coords);
}
}
+ spatial_editor->update_transform_gizmo();
surface->update();
} break;
@@ -2039,8 +2215,8 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
List<Node *> &selection = editor_selection->get_selected_node_list();
- for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
- Node3D *sp = Object::cast_to<Node3D>(E->get());
+ for (Node *E : selection) {
+ Node3D *sp = Object::cast_to<Node3D>(E);
if (!sp) {
continue;
}
@@ -2698,7 +2874,7 @@ void Node3DEditorViewport::_draw() {
if (surface->has_focus()) {
Size2 size = surface->get_size();
Rect2 r = Rect2(Point2(), size);
- get_theme_stylebox(SNAME("Focus"), SNAME("EditorStyles"))->draw(surface->get_canvas_item(), r);
+ get_theme_stylebox(SNAME("FocusViewport"), SNAME("EditorStyles"))->draw(surface->get_canvas_item(), r);
}
if (cursor.region_select) {
@@ -2744,9 +2920,7 @@ void Node3DEditorViewport::_draw() {
handle_color = get_theme_color(SNAME("accent_color"), SNAME("Editor"));
break;
}
- handle_color.a = 1.0;
- const float brightness = 1.3;
- handle_color *= Color(brightness, brightness, brightness);
+ handle_color = handle_color.from_hsv(handle_color.get_h(), 0.25, 1.0, 1);
RenderingServer::get_singleton()->canvas_item_add_line(
ci,
@@ -2905,8 +3079,8 @@ void Node3DEditorViewport::_menu_option(int p_option) {
undo_redo->create_action(TTR("Align Transform with View"));
- for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
- Node3D *sp = Object::cast_to<Node3D>(E->get());
+ for (Node *E : selection) {
+ Node3D *sp = Object::cast_to<Node3D>(E);
if (!sp) {
continue;
}
@@ -2941,8 +3115,8 @@ void Node3DEditorViewport::_menu_option(int p_option) {
List<Node *> &selection = editor_selection->get_selected_node_list();
undo_redo->create_action(TTR("Align Rotation with View"));
- for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
- Node3D *sp = Object::cast_to<Node3D>(E->get());
+ for (Node *E : selection) {
+ Node3D *sp = Object::cast_to<Node3D>(E);
if (!sp) {
continue;
}
@@ -3310,8 +3484,7 @@ void Node3DEditorViewport::_selection_result_pressed(int p_result) {
clicked = selection_results[p_result].item->get_instance_id();
if (clicked.is_valid()) {
- _select_clicked(clicked_wants_append, true, spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT);
- clicked = ObjectID();
+ _select_clicked(spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT);
}
}
@@ -3351,18 +3524,15 @@ void Node3DEditorViewport::update_transform_gizmo_view() {
return;
}
- Vector3 camz = -camera_xform.get_basis().get_axis(2).normalized();
- Vector3 camy = -camera_xform.get_basis().get_axis(1).normalized();
- Plane p(camera_xform.origin, camz);
- float gizmo_d = MAX(Math::abs(p.distance_to(xform.origin)), CMP_EPSILON);
- float d0 = camera->unproject_position(camera_xform.origin + camz * gizmo_d).y;
- float d1 = camera->unproject_position(camera_xform.origin + camz * gizmo_d + camy).y;
- float dd = Math::abs(d0 - d1);
- if (dd == 0) {
- dd = 0.0001;
- }
+ const Vector3 camz = -camera_xform.get_basis().get_axis(2).normalized();
+ const Vector3 camy = -camera_xform.get_basis().get_axis(1).normalized();
+ const Plane p(camera_xform.origin, camz);
+ const real_t gizmo_d = MAX(Math::abs(p.distance_to(xform.origin)), CMP_EPSILON);
+ const real_t d0 = camera->unproject_position(camera_xform.origin + camz * gizmo_d).y;
+ const real_t d1 = camera->unproject_position(camera_xform.origin + camz * gizmo_d + camy).y;
+ const real_t dd = MAX(Math::abs(d0 - d1), CMP_EPSILON);
- float gizmo_size = EditorSettings::get_singleton()->get("editors/3d/manipulator_gizmo_size");
+ const real_t gizmo_size = EditorSettings::get_singleton()->get("editors/3d/manipulator_gizmo_size");
// At low viewport heights, multiply the gizmo scale based on the viewport height.
// This prevents the gizmo from growing very large and going outside the viewport.
const int viewport_base_height = 400 * MAX(1, EDSCALE);
@@ -3595,8 +3765,8 @@ void Node3DEditorViewport::focus_selection() {
List<Node *> &selection = editor_selection->get_selected_node_list();
- for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
- Node3D *sp = Object::cast_to<Node3D>(E->get());
+ for (Node *E : selection) {
+ Node3D *sp = Object::cast_to<Node3D>(E);
if (!sp) {
continue;
}
@@ -3606,12 +3776,19 @@ void Node3DEditorViewport::focus_selection() {
continue;
}
+ if (se->gizmo.is_valid()) {
+ for (Map<int, Transform3D>::Element *GE = se->subgizmos.front(); GE; GE = GE->next()) {
+ center += se->gizmo->get_subgizmo_transform(GE->key()).origin;
+ count++;
+ }
+ }
+
center += sp->get_global_gizmo_transform().origin;
count++;
}
if (count != 0) {
- center /= float(count);
+ center /= count;
}
cursor.pos = center;
@@ -3629,58 +3806,16 @@ Vector3 Node3DEditorViewport::_get_instance_position(const Point2 &p_pos) const
Vector3 world_ray = _get_ray(p_pos);
Vector3 world_pos = _get_ray_pos(p_pos);
- Vector<ObjectID> instances = RenderingServer::get_singleton()->instances_cull_ray(world_pos, world_ray, get_tree()->get_root()->get_world_3d()->get_scenario());
- Set<Ref<EditorNode3DGizmo>> found_gizmos;
-
- float closest_dist = MAX_DISTANCE;
-
Vector3 point = world_pos + world_ray * MAX_DISTANCE;
- Vector3 normal = Vector3(0.0, 0.0, 0.0);
- for (int i = 0; i < instances.size(); i++) {
- MeshInstance3D *mesh_instance = Object::cast_to<MeshInstance3D>(ObjectDB::get_instance(instances[i]));
-
- if (!mesh_instance) {
- continue;
- }
-
- Ref<EditorNode3DGizmo> seg = mesh_instance->get_gizmo();
-
- if ((!seg.is_valid()) || found_gizmos.has(seg)) {
- continue;
- }
-
- found_gizmos.insert(seg);
-
- Vector3 hit_point;
- Vector3 hit_normal;
- bool inters = seg->intersect_ray(camera, p_pos, hit_point, hit_normal, nullptr, false);
-
- if (!inters) {
- continue;
- }
-
- float dist = world_pos.distance_to(hit_point);
-
- if (dist < 0) {
- continue;
- }
+ PhysicsDirectSpaceState3D *ss = get_tree()->get_root()->get_world_3d()->get_direct_space_state();
+ PhysicsDirectSpaceState3D::RayResult result;
- if (dist < closest_dist) {
- closest_dist = dist;
- point = hit_point;
- normal = hit_normal;
- }
+ if (ss->intersect_ray(world_pos, world_pos + world_ray * MAX_DISTANCE, result)) {
+ point = result.position;
}
- Vector3 offset = Vector3();
- for (int i = 0; i < 3; i++) {
- if (normal[i] > 0.0) {
- offset[i] = (preview_bounds->get_size()[i] - (preview_bounds->get_size()[i] + preview_bounds->get_position()[i]));
- } else if (normal[i] < 0.0) {
- offset[i] = -(preview_bounds->get_size()[i] + preview_bounds->get_position()[i]);
- }
- }
- return point + offset;
+
+ return point;
}
AABB Node3DEditorViewport::_calculate_spatial_bounds(const Node3D *p_parent, bool p_exclude_top_level_transform) {
@@ -3931,6 +4066,7 @@ void Node3DEditorViewport::drop_data_fw(const Point2 &p_point, const Variant &p_
}
bool is_shift = Input::get_singleton()->is_key_pressed(KEY_SHIFT);
+ bool is_ctrl = Input::get_singleton()->is_key_pressed(KEY_CTRL);
selected_files.clear();
Dictionary d = p_data;
@@ -3938,29 +4074,32 @@ void Node3DEditorViewport::drop_data_fw(const Point2 &p_point, const Variant &p_
selected_files = d["files"];
}
- List<Node *> list = editor->get_editor_selection()->get_selected_node_list();
- if (list.size() == 0) {
- Node *root_node = editor->get_edited_scene();
+ List<Node *> selected_nodes = editor->get_editor_selection()->get_selected_node_list();
+ Node *root_node = editor->get_edited_scene();
+ if (selected_nodes.size() == 1) {
+ Node *selected_node = selected_nodes[0];
+ target_node = root_node;
+ if (is_ctrl) {
+ target_node = selected_node;
+ } else if (is_shift && selected_node != root_node) {
+ target_node = selected_node->get_parent();
+ }
+ } else if (selected_nodes.size() == 0) {
if (root_node) {
- list.push_back(root_node);
+ target_node = root_node;
} else {
- accept->set_text(TTR("No parent to instance a child at."));
+ accept->set_text(TTR("Cannot drag and drop into scene with no root node."));
accept->popup_centered();
_remove_preview();
return;
}
- }
- if (list.size() != 1) {
- accept->set_text(TTR("This operation requires a single selected node."));
+ } else {
+ accept->set_text(TTR("Cannot drag and drop into multiple selected nodes."));
accept->popup_centered();
_remove_preview();
return;
}
- target_node = list[0];
- if (is_shift && target_node != editor->get_edited_scene()) {
- target_node = target_node->get_parent();
- }
drop_pos = p_point;
_perform_drop_data();
@@ -3972,9 +4111,8 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, Edito
_edit.mode = TRANSFORM_NONE;
_edit.plane = TRANSFORM_VIEW;
- _edit.edited_gizmo = 0;
_edit.snap = true;
- _edit.gizmo_handle = 0;
+ _edit.gizmo_handle = -1;
index = p_index;
editor = p_editor;
@@ -3982,7 +4120,6 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, Edito
editor_selection = editor->get_editor_selection();
undo_redo = editor->get_undo_redo();
- clicked_includes_current = false;
orthogonal = false;
auto_orthogonal = false;
lock_rotation = false;
@@ -4005,7 +4142,7 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, Edito
surface->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
surface->set_clip_contents(true);
camera = memnew(Camera3D);
- camera->set_disable_gizmo(true);
+ camera->set_disable_gizmos(true);
camera->set_cull_mask(((1 << 20) - 1) | (1 << (GIZMO_BASE_LAYER + p_index)) | (1 << GIZMO_EDIT_LAYER) | (1 << GIZMO_GRID_LAYER) | (1 << MISC_TOOL_LAYER));
viewport->add_child(camera);
camera->make_current();
@@ -4134,6 +4271,7 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, Edito
preview_camera = memnew(CheckBox);
preview_camera->set_text(TTR("Preview"));
+ preview_camera->set_shortcut(ED_SHORTCUT("spatial_editor/toggle_camera_preview", TTR("Toggle Camera Preview"), KEY_MASK_CMD | KEY_P));
vbox->add_child(preview_camera);
preview_camera->set_h_size_flags(0);
preview_camera->hide();
@@ -4314,14 +4452,14 @@ void Node3DEditorViewportContainer::_gui_input(const Ref<InputEvent> &p_event) {
}
if (dragging_h) {
- float new_ratio = drag_begin_ratio.x + (mm->get_position().x - drag_begin_pos.x) / get_size().width;
+ real_t new_ratio = drag_begin_ratio.x + (mm->get_position().x - drag_begin_pos.x) / get_size().width;
new_ratio = CLAMP(new_ratio, 40 / get_size().width, (get_size().width - 40) / get_size().width);
ratio_h = new_ratio;
queue_sort();
update();
}
if (dragging_v) {
- float new_ratio = drag_begin_ratio.y + (mm->get_position().y - drag_begin_pos.y) / get_size().height;
+ real_t new_ratio = drag_begin_ratio.y + (mm->get_position().y - drag_begin_pos.y) / get_size().height;
new_ratio = CLAMP(new_ratio, 40 / get_size().height, (get_size().height - 40) / get_size().height);
ratio_v = new_ratio;
queue_sort();
@@ -4574,43 +4712,54 @@ void Node3DEditor::select_gizmo_highlight_axis(int p_axis) {
}
void Node3DEditor::update_transform_gizmo() {
- List<Node *> &selection = editor_selection->get_selected_node_list();
- AABB center;
- bool first = true;
+ int count = 0;
+ bool local_gizmo_coords = are_local_coords_enabled();
+ Vector3 gizmo_center;
Basis gizmo_basis;
- bool local_gizmo_coords = are_local_coords_enabled();
- for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
- Node3D *sp = Object::cast_to<Node3D>(E->get());
- if (!sp) {
- continue;
- }
+ Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr;
- Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);
- if (!se) {
- continue;
+ if (se && se->gizmo.is_valid()) {
+ for (Map<int, Transform3D>::Element *E = se->subgizmos.front(); E; E = E->next()) {
+ Transform3D xf = se->sp->get_global_transform() * se->gizmo->get_subgizmo_transform(E->key());
+ gizmo_center += xf.origin;
+ if (count == 0 && local_gizmo_coords) {
+ gizmo_basis = xf.basis;
+ gizmo_basis.orthonormalize();
+ }
+ count++;
}
+ } else {
+ List<Node *> &selection = editor_selection->get_selected_node_list();
+ for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
+ Node3D *sp = Object::cast_to<Node3D>(E->get());
+ if (!sp) {
+ continue;
+ }
+
+ if (sp->has_meta("_edit_lock_")) {
+ continue;
+ }
- Transform3D xf = se->sp->get_global_gizmo_transform();
+ Node3DEditorSelectedItem *sel_item = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);
+ if (!sel_item) {
+ continue;
+ }
- if (first) {
- center.position = xf.origin;
- first = false;
- if (local_gizmo_coords) {
+ Transform3D xf = sel_item->sp->get_global_transform();
+ gizmo_center += xf.origin;
+ if (count == 0 && local_gizmo_coords) {
gizmo_basis = xf.basis;
gizmo_basis.orthonormalize();
}
- } else {
- center.expand_to(xf.origin);
- gizmo_basis = Basis();
+ count++;
}
}
- Vector3 pcenter = center.position + center.size * 0.5;
- gizmo.visible = !first;
- gizmo.transform.origin = pcenter;
- gizmo.transform.basis = gizmo_basis;
+ gizmo.visible = count > 0;
+ gizmo.transform.origin = (count > 0) ? gizmo_center / count : Vector3();
+ gizmo.transform.basis = (count == 1) ? gizmo_basis : Basis();
for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) {
viewports[i]->update_transform_gizmo_view();
@@ -4621,7 +4770,7 @@ void _update_all_gizmos(Node *p_node) {
for (int i = p_node->get_child_count() - 1; 0 <= i; --i) {
Node3D *spatial_node = Object::cast_to<Node3D>(p_node->get_child(i));
if (spatial_node) {
- spatial_node->update_gizmo();
+ spatial_node->update_gizmos();
}
_update_all_gizmos(p_node->get_child(i));
@@ -4655,7 +4804,9 @@ Object *Node3DEditor::_get_editor_data(Object *p_what) {
RS::get_singleton()->instance_geometry_set_cast_shadows_setting(
si->sbox_instance,
RS::SHADOW_CASTING_SETTING_OFF);
- RS::get_singleton()->instance_set_layer_mask(si->sbox_instance, 1 << Node3DEditorViewport::MISC_TOOL_LAYER);
+ // Use the Edit layer to hide the selection box when View Gizmos is disabled, since it is a bit distracting.
+ // It's still possible to approximately guess what is selected by looking at the manipulation gizmo position.
+ RS::get_singleton()->instance_set_layer_mask(si->sbox_instance, 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER);
RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);
si->sbox_instance_xray = RenderingServer::get_singleton()->instance_create2(
selection_box_xray->get_rid(),
@@ -4663,7 +4814,9 @@ Object *Node3DEditor::_get_editor_data(Object *p_what) {
RS::get_singleton()->instance_geometry_set_cast_shadows_setting(
si->sbox_instance_xray,
RS::SHADOW_CASTING_SETTING_OFF);
- RS::get_singleton()->instance_set_layer_mask(si->sbox_instance_xray, 1 << Node3DEditorViewport::MISC_TOOL_LAYER);
+ // Use the Edit layer to hide the selection box when View Gizmos is disabled, since it is a bit distracting.
+ // It's still possible to approximately guess what is selected by looking at the manipulation gizmo position.
+ RS::get_singleton()->instance_set_layer_mask(si->sbox_instance_xray, 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER);
RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);
return si;
@@ -4857,13 +5010,13 @@ void Node3DEditor::set_state(const Dictionary &p_state) {
}
if (d.has("zfar")) {
- settings_zfar->set_value(float(d["zfar"]));
+ settings_zfar->set_value(double(d["zfar"]));
}
if (d.has("znear")) {
- settings_znear->set_value(float(d["znear"]));
+ settings_znear->set_value(double(d["znear"]));
}
if (d.has("fov")) {
- settings_fov->set_value(float(d["fov"]));
+ settings_fov->set_value(double(d["fov"]));
}
if (d.has("show_grid")) {
bool use = d["show_grid"];
@@ -4940,22 +5093,38 @@ void Node3DEditor::set_state(const Dictionary &p_state) {
void Node3DEditor::edit(Node3D *p_spatial) {
if (p_spatial != selected) {
if (selected) {
- Ref<EditorNode3DGizmo> seg = selected->get_gizmo();
- if (seg.is_valid()) {
+ Vector<Ref<Node3DGizmo>> gizmos = selected->get_gizmos();
+ for (int i = 0; i < gizmos.size(); i++) {
+ Ref<EditorNode3DGizmo> seg = gizmos[i];
+ if (!seg.is_valid()) {
+ continue;
+ }
seg->set_selected(false);
- selected->update_gizmo();
}
+
+ Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected);
+ if (se) {
+ se->gizmo.unref();
+ se->subgizmos.clear();
+ }
+
+ selected->update_gizmos();
}
selected = p_spatial;
- over_gizmo_handle = -1;
+ current_hover_gizmo = Ref<EditorNode3DGizmo>();
+ current_hover_gizmo_handle = -1;
if (selected) {
- Ref<EditorNode3DGizmo> seg = selected->get_gizmo();
- if (seg.is_valid()) {
+ Vector<Ref<Node3DGizmo>> gizmos = selected->get_gizmos();
+ for (int i = 0; i < gizmos.size(); i++) {
+ Ref<EditorNode3DGizmo> seg = gizmos[i];
+ if (!seg.is_valid()) {
+ continue;
+ }
seg->set_selected(true);
- selected->update_gizmo();
}
+ selected->update_gizmos();
}
}
}
@@ -4993,8 +5162,8 @@ void Node3DEditor::_xform_dialog_action() {
List<Node *> &selection = editor_selection->get_selected_node_list();
- for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
- Node3D *sp = Object::cast_to<Node3D>(E->get());
+ for (Node *E : selection) {
+ Node3D *sp = Object::cast_to<Node3D>(E);
if (!sp) {
continue;
}
@@ -5230,8 +5399,8 @@ void Node3DEditor::_menu_item_pressed(int p_option) {
List<Node *> &selection = editor_selection->get_selected_node_list();
- for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
- Node3D *spatial = Object::cast_to<Node3D>(E->get());
+ for (Node *E : selection) {
+ Node3D *spatial = Object::cast_to<Node3D>(E);
if (!spatial || !spatial->is_inside_tree()) {
continue;
}
@@ -5255,8 +5424,8 @@ void Node3DEditor::_menu_item_pressed(int p_option) {
List<Node *> &selection = editor_selection->get_selected_node_list();
- for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
- Node3D *spatial = Object::cast_to<Node3D>(E->get());
+ for (Node *E : selection) {
+ Node3D *spatial = Object::cast_to<Node3D>(E);
if (!spatial || !spatial->is_inside_tree()) {
continue;
}
@@ -5280,8 +5449,8 @@ void Node3DEditor::_menu_item_pressed(int p_option) {
List<Node *> &selection = editor_selection->get_selected_node_list();
- for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
- Node3D *spatial = Object::cast_to<Node3D>(E->get());
+ for (Node *E : selection) {
+ Node3D *spatial = Object::cast_to<Node3D>(E);
if (!spatial || !spatial->is_inside_tree()) {
continue;
}
@@ -5304,8 +5473,8 @@ void Node3DEditor::_menu_item_pressed(int p_option) {
undo_redo->create_action(TTR("Ungroup Selected"));
List<Node *> &selection = editor_selection->get_selected_node_list();
- for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
- Node3D *spatial = Object::cast_to<Node3D>(E->get());
+ for (Node *E : selection) {
+ Node3D *spatial = Object::cast_to<Node3D>(E);
if (!spatial || !spatial->is_inside_tree()) {
continue;
}
@@ -5479,8 +5648,7 @@ void fragment() {
gizmo_color[i] = mat;
Ref<StandardMaterial3D> mat_hl = mat->duplicate();
- const float brightness = 1.3;
- const Color albedo = Color(col.r * brightness, col.g * brightness, col.b * brightness);
+ const Color albedo = col.from_hsv(col.get_h(), 0.25, 1.0, 1);
mat_hl->set_albedo(albedo);
gizmo_color_hl[i] = mat_hl;
@@ -5587,7 +5755,7 @@ void fragment() {
surftool->begin(Mesh::PRIMITIVE_TRIANGLES);
int n = 128; // number of circle segments
- int m = 6; // number of thickness segments
+ int m = 3; // number of thickness segments
real_t step = Math_TAU / n;
for (int j = 0; j < n; ++j) {
@@ -5795,7 +5963,7 @@ void fragment() {
surftool->commit(scale_plane_gizmo[i]);
Ref<StandardMaterial3D> plane_mat_hl = plane_mat->duplicate();
- plane_mat_hl->set_albedo(Color(col.r * 1.3, col.g * 1.3, col.b * 1.3));
+ plane_mat_hl->set_albedo(col.from_hsv(col.get_h(), 0.25, 1.0, 1));
plane_gizmo_color_hl[i] = plane_mat_hl; // needed, so we can draw planes from both sides
}
}
@@ -5804,6 +5972,18 @@ void fragment() {
_generate_selection_boxes();
}
+void Node3DEditor::_update_context_menu_stylebox() {
+ // This must be called when the theme changes to follow the new accent color.
+ Ref<StyleBoxFlat> context_menu_stylebox = memnew(StyleBoxFlat);
+ const Color accent_color = EditorNode::get_singleton()->get_gui_base()->get_theme_color("accent_color", "Editor");
+ context_menu_stylebox->set_bg_color(accent_color * Color(1, 1, 1, 0.1));
+ // Add an underline to the StyleBox, but prevent its minimum vertical size from changing.
+ context_menu_stylebox->set_border_color(accent_color);
+ context_menu_stylebox->set_border_width(SIDE_BOTTOM, Math::round(2 * EDSCALE));
+ context_menu_stylebox->set_default_margin(SIDE_BOTTOM, 0);
+ context_menu_container->add_theme_style_override("panel", context_menu_stylebox);
+}
+
void Node3DEditor::_update_gizmos_menu() {
gizmos_menu->clear();
@@ -6027,13 +6207,27 @@ void Node3DEditor::update_grid() {
_init_grid();
}
-bool Node3DEditor::is_any_freelook_active() const {
- for (unsigned int i = 0; i < VIEWPORTS_COUNT; ++i) {
- if (viewports[i]->is_freelook_active()) {
- return true;
+void Node3DEditor::_selection_changed() {
+ _refresh_menu_icons();
+ if (selected && editor_selection->get_selected_node_list().size() != 1) {
+ Vector<Ref<Node3DGizmo>> gizmos = selected->get_gizmos();
+ for (int i = 0; i < gizmos.size(); i++) {
+ Ref<EditorNode3DGizmo> seg = gizmos[i];
+ if (!seg.is_valid()) {
+ continue;
+ }
+ seg->set_selected(false);
}
+
+ Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected);
+ if (se) {
+ se->gizmo.unref();
+ se->subgizmos.clear();
+ }
+ selected->update_gizmos();
+ selected = nullptr;
}
- return false;
+ update_transform_gizmo();
}
void Node3DEditor::_refresh_menu_icons() {
@@ -6046,14 +6240,14 @@ void Node3DEditor::_refresh_menu_icons() {
all_locked = false;
all_grouped = false;
} else {
- for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
- if (Object::cast_to<Node3D>(E->get()) && !Object::cast_to<Node3D>(E->get())->has_meta("_edit_lock_")) {
+ for (Node *E : selection) {
+ if (Object::cast_to<Node3D>(E) && !Object::cast_to<Node3D>(E)->has_meta("_edit_lock_")) {
all_locked = false;
break;
}
}
- for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
- if (Object::cast_to<Node3D>(E->get()) && !Object::cast_to<Node3D>(E->get())->has_meta("_edit_group_")) {
+ for (Node *E : selection) {
+ if (Object::cast_to<Node3D>(E) && !Object::cast_to<Node3D>(E)->has_meta("_edit_group_")) {
all_grouped = false;
break;
}
@@ -6106,8 +6300,8 @@ void Node3DEditor::snap_selected_nodes_to_floor() {
List<Node *> &selection = editor_selection->get_selected_node_list();
Dictionary snap_data;
- for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
- Node3D *sp = Object::cast_to<Node3D>(E->get());
+ for (Node *E : selection) {
+ Node3D *sp = Object::cast_to<Node3D>(E);
if (sp) {
Vector3 from = Vector3();
Vector3 position_offset = Vector3();
@@ -6295,101 +6489,122 @@ void Node3DEditor::_add_environment_to_scene(bool p_already_added_sun) {
undo_redo->commit_action();
}
+void Node3DEditor::_update_theme() {
+ tool_button[Node3DEditor::TOOL_MODE_SELECT]->set_icon(get_theme_icon(SNAME("ToolSelect"), SNAME("EditorIcons")));
+ tool_button[Node3DEditor::TOOL_MODE_MOVE]->set_icon(get_theme_icon(SNAME("ToolMove"), SNAME("EditorIcons")));
+ tool_button[Node3DEditor::TOOL_MODE_ROTATE]->set_icon(get_theme_icon(SNAME("ToolRotate"), SNAME("EditorIcons")));
+ tool_button[Node3DEditor::TOOL_MODE_SCALE]->set_icon(get_theme_icon(SNAME("ToolScale"), SNAME("EditorIcons")));
+ tool_button[Node3DEditor::TOOL_MODE_LIST_SELECT]->set_icon(get_theme_icon(SNAME("ListSelect"), SNAME("EditorIcons")));
+ tool_button[Node3DEditor::TOOL_LOCK_SELECTED]->set_icon(get_theme_icon(SNAME("Lock"), SNAME("EditorIcons")));
+ tool_button[Node3DEditor::TOOL_UNLOCK_SELECTED]->set_icon(get_theme_icon(SNAME("Unlock"), SNAME("EditorIcons")));
+ tool_button[Node3DEditor::TOOL_GROUP_SELECTED]->set_icon(get_theme_icon(SNAME("Group"), SNAME("EditorIcons")));
+ tool_button[Node3DEditor::TOOL_UNGROUP_SELECTED]->set_icon(get_theme_icon(SNAME("Ungroup"), SNAME("EditorIcons")));
+
+ tool_option_button[Node3DEditor::TOOL_OPT_LOCAL_COORDS]->set_icon(get_theme_icon(SNAME("Object"), SNAME("EditorIcons")));
+ tool_option_button[Node3DEditor::TOOL_OPT_USE_SNAP]->set_icon(get_theme_icon(SNAME("Snap"), SNAME("EditorIcons")));
+ tool_option_button[Node3DEditor::TOOL_OPT_OVERRIDE_CAMERA]->set_icon(get_theme_icon(SNAME("Camera3D"), SNAME("EditorIcons")));
+
+ view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), get_theme_icon(SNAME("Panels1"), SNAME("EditorIcons")));
+ view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), get_theme_icon(SNAME("Panels2"), SNAME("EditorIcons")));
+ view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT), get_theme_icon(SNAME("Panels2Alt"), SNAME("EditorIcons")));
+ view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS), get_theme_icon(SNAME("Panels3"), SNAME("EditorIcons")));
+ view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT), get_theme_icon(SNAME("Panels3Alt"), SNAME("EditorIcons")));
+ view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS), get_theme_icon(SNAME("Panels4"), SNAME("EditorIcons")));
+
+ sun_button->set_icon(get_theme_icon(SNAME("DirectionalLight3D"), SNAME("EditorIcons")));
+ environ_button->set_icon(get_theme_icon(SNAME("WorldEnvironment"), SNAME("EditorIcons")));
+ sun_environ_settings->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons")));
+
+ sun_title->add_theme_font_override("font", get_theme_font(SNAME("title_font"), SNAME("Window")));
+ environ_title->add_theme_font_override("font", get_theme_font(SNAME("title_font"), SNAME("Window")));
+}
+
void Node3DEditor::_notification(int p_what) {
- if (p_what == NOTIFICATION_READY) {
- tool_button[Node3DEditor::TOOL_MODE_SELECT]->set_icon(get_theme_icon(SNAME("ToolSelect"), SNAME("EditorIcons")));
- tool_button[Node3DEditor::TOOL_MODE_MOVE]->set_icon(get_theme_icon(SNAME("ToolMove"), SNAME("EditorIcons")));
- tool_button[Node3DEditor::TOOL_MODE_ROTATE]->set_icon(get_theme_icon(SNAME("ToolRotate"), SNAME("EditorIcons")));
- tool_button[Node3DEditor::TOOL_MODE_SCALE]->set_icon(get_theme_icon(SNAME("ToolScale"), SNAME("EditorIcons")));
- tool_button[Node3DEditor::TOOL_MODE_LIST_SELECT]->set_icon(get_theme_icon(SNAME("ListSelect"), SNAME("EditorIcons")));
- tool_button[Node3DEditor::TOOL_LOCK_SELECTED]->set_icon(get_theme_icon(SNAME("Lock"), SNAME("EditorIcons")));
- tool_button[Node3DEditor::TOOL_UNLOCK_SELECTED]->set_icon(get_theme_icon(SNAME("Unlock"), SNAME("EditorIcons")));
- tool_button[Node3DEditor::TOOL_GROUP_SELECTED]->set_icon(get_theme_icon(SNAME("Group"), SNAME("EditorIcons")));
- tool_button[Node3DEditor::TOOL_UNGROUP_SELECTED]->set_icon(get_theme_icon(SNAME("Ungroup"), SNAME("EditorIcons")));
-
- tool_option_button[Node3DEditor::TOOL_OPT_LOCAL_COORDS]->set_icon(get_theme_icon(SNAME("Object"), SNAME("EditorIcons")));
- tool_option_button[Node3DEditor::TOOL_OPT_USE_SNAP]->set_icon(get_theme_icon(SNAME("Snap"), SNAME("EditorIcons")));
- tool_option_button[Node3DEditor::TOOL_OPT_OVERRIDE_CAMERA]->set_icon(get_theme_icon(SNAME("Camera3D"), SNAME("EditorIcons")));
-
- view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), get_theme_icon(SNAME("Panels1"), SNAME("EditorIcons")));
- view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), get_theme_icon(SNAME("Panels2"), SNAME("EditorIcons")));
- view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT), get_theme_icon(SNAME("Panels2Alt"), SNAME("EditorIcons")));
- view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS), get_theme_icon(SNAME("Panels3"), SNAME("EditorIcons")));
- view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT), get_theme_icon(SNAME("Panels3Alt"), SNAME("EditorIcons")));
- view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS), get_theme_icon(SNAME("Panels4"), SNAME("EditorIcons")));
-
- _menu_item_pressed(MENU_VIEW_USE_1_VIEWPORT);
-
- _refresh_menu_icons();
-
- get_tree()->connect("node_removed", callable_mp(this, &Node3DEditor::_node_removed));
- get_tree()->connect("node_added", callable_mp(this, &Node3DEditor::_node_added));
- EditorNode::get_singleton()->get_scene_tree_dock()->get_tree_editor()->connect("node_changed", callable_mp(this, &Node3DEditor::_refresh_menu_icons));
- editor_selection->connect("selection_changed", callable_mp(this, &Node3DEditor::_refresh_menu_icons));
-
- editor->connect("stop_pressed", callable_mp(this, &Node3DEditor::_update_camera_override_button), make_binds(false));
- editor->connect("play_pressed", callable_mp(this, &Node3DEditor::_update_camera_override_button), make_binds(true));
-
- sun_button->set_icon(get_theme_icon(SNAME("DirectionalLight3D"), SNAME("EditorIcons")));
- environ_button->set_icon(get_theme_icon(SNAME("WorldEnvironment"), SNAME("EditorIcons")));
- sun_environ_settings->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons")));
+ switch (p_what) {
+ case NOTIFICATION_READY: {
+ _menu_item_pressed(MENU_VIEW_USE_1_VIEWPORT);
- _update_preview_environment();
- sun_title->add_theme_font_override("font", get_theme_font(SNAME("title_font"), SNAME("Window")));
- environ_title->add_theme_font_override("font", get_theme_font(SNAME("title_font"), SNAME("Window")));
+ _refresh_menu_icons();
- sun_state->set_custom_minimum_size(sun_vb->get_combined_minimum_size());
- environ_state->set_custom_minimum_size(environ_vb->get_combined_minimum_size());
- } else if (p_what == NOTIFICATION_ENTER_TREE) {
- _register_all_gizmos();
- _update_gizmos_menu();
- _init_indicators();
- } else if (p_what == NOTIFICATION_THEME_CHANGED) {
- _update_gizmos_menu_theme();
- sun_title->add_theme_font_override("font", get_theme_font(SNAME("title_font"), SNAME("Window")));
- environ_title->add_theme_font_override("font", get_theme_font(SNAME("title_font"), SNAME("Window")));
- } else if (p_what == NOTIFICATION_EXIT_TREE) {
- _finish_indicators();
- } else if (p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) {
- tool_button[Node3DEditor::TOOL_MODE_SELECT]->set_icon(get_theme_icon(SNAME("ToolSelect"), SNAME("EditorIcons")));
- tool_button[Node3DEditor::TOOL_MODE_MOVE]->set_icon(get_theme_icon(SNAME("ToolMove"), SNAME("EditorIcons")));
- tool_button[Node3DEditor::TOOL_MODE_ROTATE]->set_icon(get_theme_icon(SNAME("ToolRotate"), SNAME("EditorIcons")));
- tool_button[Node3DEditor::TOOL_MODE_SCALE]->set_icon(get_theme_icon(SNAME("ToolScale"), SNAME("EditorIcons")));
- tool_button[Node3DEditor::TOOL_MODE_LIST_SELECT]->set_icon(get_theme_icon(SNAME("ListSelect"), SNAME("EditorIcons")));
- tool_button[Node3DEditor::TOOL_LOCK_SELECTED]->set_icon(get_theme_icon(SNAME("Lock"), SNAME("EditorIcons")));
- tool_button[Node3DEditor::TOOL_UNLOCK_SELECTED]->set_icon(get_theme_icon(SNAME("Unlock"), SNAME("EditorIcons")));
- tool_button[Node3DEditor::TOOL_GROUP_SELECTED]->set_icon(get_theme_icon(SNAME("Group"), SNAME("EditorIcons")));
- tool_button[Node3DEditor::TOOL_UNGROUP_SELECTED]->set_icon(get_theme_icon(SNAME("Ungroup"), SNAME("EditorIcons")));
-
- tool_option_button[Node3DEditor::TOOL_OPT_LOCAL_COORDS]->set_icon(get_theme_icon(SNAME("Object"), SNAME("EditorIcons")));
- tool_option_button[Node3DEditor::TOOL_OPT_USE_SNAP]->set_icon(get_theme_icon(SNAME("Snap"), SNAME("EditorIcons")));
-
- view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), get_theme_icon(SNAME("Panels1"), SNAME("EditorIcons")));
- view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), get_theme_icon(SNAME("Panels2"), SNAME("EditorIcons")));
- view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT), get_theme_icon(SNAME("Panels2Alt"), SNAME("EditorIcons")));
- view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS), get_theme_icon(SNAME("Panels3"), SNAME("EditorIcons")));
- view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT), get_theme_icon(SNAME("Panels3Alt"), SNAME("EditorIcons")));
- view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS), get_theme_icon(SNAME("Panels4"), SNAME("EditorIcons")));
-
- // Update grid color by rebuilding grid.
- _finish_grid();
- _init_grid();
- } else if (p_what == NOTIFICATION_VISIBILITY_CHANGED) {
- if (!is_visible() && tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->is_pressed()) {
- EditorDebuggerNode *debugger = EditorDebuggerNode::get_singleton();
+ get_tree()->connect("node_removed", callable_mp(this, &Node3DEditor::_node_removed));
+ get_tree()->connect("node_added", callable_mp(this, &Node3DEditor::_node_added));
+ EditorNode::get_singleton()->get_scene_tree_dock()->get_tree_editor()->connect("node_changed", callable_mp(this, &Node3DEditor::_refresh_menu_icons));
+ editor_selection->connect("selection_changed", callable_mp(this, &Node3DEditor::_selection_changed));
+
+ editor->connect("stop_pressed", callable_mp(this, &Node3DEditor::_update_camera_override_button), make_binds(false));
+ editor->connect("play_pressed", callable_mp(this, &Node3DEditor::_update_camera_override_button), make_binds(true));
+
+ _update_preview_environment();
+
+ sun_state->set_custom_minimum_size(sun_vb->get_combined_minimum_size());
+ environ_state->set_custom_minimum_size(environ_vb->get_combined_minimum_size());
+ } break;
+ case NOTIFICATION_ENTER_TREE: {
+ _update_theme();
+ _register_all_gizmos();
+ _update_gizmos_menu();
+ _init_indicators();
+ } break;
+ case NOTIFICATION_EXIT_TREE: {
+ _finish_indicators();
+ } break;
+ case NOTIFICATION_THEME_CHANGED: {
+ _update_theme();
+ _update_gizmos_menu_theme();
+ _update_context_menu_stylebox();
+ sun_title->add_theme_font_override("font", get_theme_font(SNAME("title_font"), SNAME("Window")));
+ environ_title->add_theme_font_override("font", get_theme_font(SNAME("title_font"), SNAME("Window")));
+ } break;
+ case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
+ // Update grid color by rebuilding grid.
+ _finish_grid();
+ _init_grid();
+ } break;
+ case NOTIFICATION_VISIBILITY_CHANGED: {
+ if (!is_visible() && tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->is_pressed()) {
+ EditorDebuggerNode *debugger = EditorDebuggerNode::get_singleton();
- debugger->set_camera_override(EditorDebuggerNode::OVERRIDE_NONE);
- tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->set_pressed(false);
+ debugger->set_camera_override(EditorDebuggerNode::OVERRIDE_NONE);
+ tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->set_pressed(false);
+ }
+ } break;
+ }
+}
+
+bool Node3DEditor::is_subgizmo_selected(int p_id) {
+ Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr;
+ if (se) {
+ return se->subgizmos.has(p_id);
+ }
+ return false;
+}
+
+bool Node3DEditor::is_current_selected_gizmo(const EditorNode3DGizmo *p_gizmo) {
+ Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr;
+ if (se) {
+ return se->gizmo == p_gizmo;
+ }
+ return false;
+}
+
+Vector<int> Node3DEditor::get_subgizmo_selection() {
+ Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr;
+
+ Vector<int> ret;
+ if (se) {
+ for (Map<int, Transform3D>::Element *E = se->subgizmos.front(); E; E = E->next()) {
+ ret.push_back(E->key());
}
}
+ return ret;
}
void Node3DEditor::add_control_to_menu_panel(Control *p_control) {
- hbc_menu->add_child(p_control);
+ hbc_context_menu->add_child(p_control);
}
void Node3DEditor::remove_control_from_menu_panel(Control *p_control) {
- hbc_menu->remove_child(p_control);
+ hbc_context_menu->remove_child(p_control);
}
void Node3DEditor::set_can_preview(Camera3D *p_preview) {
@@ -6411,23 +6626,43 @@ void Node3DEditor::_request_gizmo(Object *p_obj) {
if (!sp) {
return;
}
- if (editor->get_edited_scene() && (sp == editor->get_edited_scene() || (sp->get_owner() && editor->get_edited_scene()->is_ancestor_of(sp)))) {
- Ref<EditorNode3DGizmo> seg;
+ bool is_selected = (sp == selected);
+
+ if (editor->get_edited_scene() && (sp == editor->get_edited_scene() || (sp->get_owner() && editor->get_edited_scene()->is_ancestor_of(sp)))) {
for (int i = 0; i < gizmo_plugins_by_priority.size(); ++i) {
- seg = gizmo_plugins_by_priority.write[i]->get_gizmo(sp);
+ Ref<EditorNode3DGizmo> seg = gizmo_plugins_by_priority.write[i]->get_gizmo(sp);
if (seg.is_valid()) {
- sp->set_gizmo(seg);
+ sp->add_gizmo(seg);
- if (sp == selected) {
- seg->set_selected(true);
- selected->update_gizmo();
+ if (is_selected != seg->is_selected()) {
+ seg->set_selected(is_selected);
}
-
- break;
}
}
+ sp->update_gizmos();
+ }
+}
+
+void Node3DEditor::_clear_subgizmo_selection(Object *p_obj) {
+ Node3D *sp = nullptr;
+ if (p_obj) {
+ sp = Object::cast_to<Node3D>(p_obj);
+ } else {
+ sp = selected;
+ }
+
+ if (!sp) {
+ return;
+ }
+
+ Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);
+ if (se) {
+ se->subgizmos.clear();
+ se->gizmo.unref();
+ sp->update_gizmos();
+ update_transform_gizmo();
}
}
@@ -6516,7 +6751,13 @@ void Node3DEditor::_node_removed(Node *p_node) {
}
if (p_node == selected) {
+ Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected);
+ if (se) {
+ se->gizmo.unref();
+ se->subgizmos.clear();
+ }
selected = nullptr;
+ update_transform_gizmo();
}
}
@@ -6554,6 +6795,7 @@ void Node3DEditor::_bind_methods() {
ClassDB::bind_method("_unhandled_key_input", &Node3DEditor::_unhandled_key_input);
ClassDB::bind_method("_get_editor_data", &Node3DEditor::_get_editor_data);
ClassDB::bind_method("_request_gizmo", &Node3DEditor::_request_gizmo);
+ ClassDB::bind_method("_clear_subgizmo_selection", &Node3DEditor::_clear_subgizmo_selection);
ClassDB::bind_method("_refresh_menu_icons", &Node3DEditor::_refresh_menu_icons);
ADD_SIGNAL(MethodInfo("transform_key_request"));
@@ -6769,8 +7011,7 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) {
tool_button[TOOL_MODE_SELECT]->connect("pressed", callable_mp(this, &Node3DEditor::_menu_item_pressed), button_binds);
tool_button[TOOL_MODE_SELECT]->set_shortcut(ED_SHORTCUT("spatial_editor/tool_select", TTR("Select Mode"), KEY_Q));
tool_button[TOOL_MODE_SELECT]->set_shortcut_context(this);
- tool_button[TOOL_MODE_SELECT]->set_tooltip(keycode_get_string(KEY_MASK_CMD) + TTR("Drag: Rotate\nAlt+Drag: Move\nAlt+RMB: Show list of all nodes at position clicked, including locked."));
-
+ tool_button[TOOL_MODE_SELECT]->set_tooltip(keycode_get_string(KEY_MASK_CMD) + TTR("Drag: Rotate selected node around pivot.") + "\n" + TTR("Alt+RMB: Show list of all nodes at position clicked, including locked."));
hbc_menu->add_child(memnew(VSeparator));
tool_button[TOOL_MODE_MOVE] = memnew(Button);
@@ -6946,6 +7187,17 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) {
view_menu->set_shortcut_context(this);
hbc_menu->add_child(view_menu);
+ hbc_menu->add_child(memnew(VSeparator));
+
+ context_menu_container = memnew(PanelContainer);
+ hbc_context_menu = memnew(HBoxContainer);
+ context_menu_container->add_child(hbc_context_menu);
+ // Use a custom stylebox to make contextual menu items stand out from the rest.
+ // This helps with editor usability as contextual menu items change when selecting nodes,
+ // even though it may not be immediately obvious at first.
+ hbc_menu->add_child(context_menu_container);
+ _update_context_menu_stylebox();
+
p = view_menu->get_popup();
accept = memnew(AcceptDialog);
@@ -7130,7 +7382,7 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) {
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::FLOAT, "editors/3d/manipulator_gizmo_opacity", PROPERTY_HINT_RANGE, "0,1,0.01"));
EDITOR_DEF("editors/3d/navigation/show_viewport_rotation_gizmo", true);
- over_gizmo_handle = -1;
+ current_hover_gizmo_handle = -1;
{
//sun popup
@@ -7365,8 +7617,8 @@ Vector3 Node3DEditor::snap_point(Vector3 p_target, Vector3 p_start) const {
return p_target;
}
-float Node3DEditor::get_translate_snap() const {
- float snap_value;
+double Node3DEditor::get_translate_snap() const {
+ double snap_value;
if (Input::get_singleton()->is_key_pressed(KEY_SHIFT)) {
snap_value = snap_translate->get_text().to_float() / 10.0;
} else {
@@ -7376,8 +7628,8 @@ float Node3DEditor::get_translate_snap() const {
return snap_value;
}
-float Node3DEditor::get_rotate_snap() const {
- float snap_value;
+double Node3DEditor::get_rotate_snap() const {
+ double snap_value;
if (Input::get_singleton()->is_key_pressed(KEY_SHIFT)) {
snap_value = snap_rotate->get_text().to_float() / 3.0;
} else {
@@ -7387,8 +7639,8 @@ float Node3DEditor::get_rotate_snap() const {
return snap_value;
}
-float Node3DEditor::get_scale_snap() const {
- float snap_value;
+double Node3DEditor::get_scale_snap() const {
+ double snap_value;
if (Input::get_singleton()->is_key_pressed(KEY_SHIFT)) {
snap_value = snap_scale->get_text().to_float() / 2.0;
} else {
@@ -7444,303 +7696,3 @@ Node3DEditorPlugin::Node3DEditorPlugin(EditorNode *p_node) {
Node3DEditorPlugin::~Node3DEditorPlugin() {
}
-
-void EditorNode3DGizmoPlugin::create_material(const String &p_name, const Color &p_color, bool p_billboard, bool p_on_top, bool p_use_vertex_color) {
- Color instantiated_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/instantiated", Color(0.7, 0.7, 0.7, 0.6));
-
- Vector<Ref<StandardMaterial3D>> mats;
-
- for (int i = 0; i < 4; i++) {
- bool selected = i % 2 == 1;
- bool instantiated = i < 2;
-
- Ref<StandardMaterial3D> material = Ref<StandardMaterial3D>(memnew(StandardMaterial3D));
-
- Color color = instantiated ? instantiated_color : p_color;
-
- if (!selected) {
- color.a *= 0.3;
- }
-
- material->set_albedo(color);
- material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
- material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
- material->set_render_priority(StandardMaterial3D::RENDER_PRIORITY_MIN + 1);
- material->set_cull_mode(StandardMaterial3D::CULL_DISABLED);
-
- if (p_use_vertex_color) {
- material->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
- material->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true);
- }
-
- if (p_billboard) {
- material->set_billboard_mode(StandardMaterial3D::BILLBOARD_ENABLED);
- }
-
- if (p_on_top && selected) {
- material->set_on_top_of_alpha();
- }
-
- mats.push_back(material);
- }
-
- materials[p_name] = mats;
-}
-
-void EditorNode3DGizmoPlugin::create_icon_material(const String &p_name, const Ref<Texture2D> &p_texture, bool p_on_top, const Color &p_albedo) {
- Color instantiated_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/instantiated", Color(0.7, 0.7, 0.7, 0.6));
-
- Vector<Ref<StandardMaterial3D>> icons;
-
- for (int i = 0; i < 4; i++) {
- bool selected = i % 2 == 1;
- bool instantiated = i < 2;
-
- Ref<StandardMaterial3D> icon = Ref<StandardMaterial3D>(memnew(StandardMaterial3D));
-
- Color color = instantiated ? instantiated_color : p_albedo;
-
- if (!selected) {
- color.a *= 0.85;
- }
-
- icon->set_albedo(color);
-
- icon->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
- icon->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
- icon->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true);
- icon->set_cull_mode(StandardMaterial3D::CULL_DISABLED);
- icon->set_depth_draw_mode(StandardMaterial3D::DEPTH_DRAW_DISABLED);
- icon->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
- icon->set_texture(StandardMaterial3D::TEXTURE_ALBEDO, p_texture);
- icon->set_flag(StandardMaterial3D::FLAG_FIXED_SIZE, true);
- icon->set_billboard_mode(StandardMaterial3D::BILLBOARD_ENABLED);
- icon->set_render_priority(StandardMaterial3D::RENDER_PRIORITY_MIN);
-
- if (p_on_top && selected) {
- icon->set_on_top_of_alpha();
- }
-
- icons.push_back(icon);
- }
-
- materials[p_name] = icons;
-}
-
-void EditorNode3DGizmoPlugin::create_handle_material(const String &p_name, bool p_billboard, const Ref<Texture2D> &p_icon) {
- Ref<StandardMaterial3D> handle_material = Ref<StandardMaterial3D>(memnew(StandardMaterial3D));
-
- handle_material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
- handle_material->set_flag(StandardMaterial3D::FLAG_USE_POINT_SIZE, true);
- Ref<Texture2D> handle_t = p_icon != nullptr ? p_icon : Node3DEditor::get_singleton()->get_theme_icon(SNAME("Editor3DHandle"), SNAME("EditorIcons"));
- handle_material->set_point_size(handle_t->get_width());
- handle_material->set_texture(StandardMaterial3D::TEXTURE_ALBEDO, handle_t);
- handle_material->set_albedo(Color(1, 1, 1));
- handle_material->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
- handle_material->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true);
- handle_material->set_on_top_of_alpha();
- if (p_billboard) {
- handle_material->set_billboard_mode(StandardMaterial3D::BILLBOARD_ENABLED);
- handle_material->set_on_top_of_alpha();
- }
- handle_material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
-
- materials[p_name] = Vector<Ref<StandardMaterial3D>>();
- materials[p_name].push_back(handle_material);
-}
-
-void EditorNode3DGizmoPlugin::add_material(const String &p_name, Ref<StandardMaterial3D> p_material) {
- materials[p_name] = Vector<Ref<StandardMaterial3D>>();
- materials[p_name].push_back(p_material);
-}
-
-Ref<StandardMaterial3D> EditorNode3DGizmoPlugin::get_material(const String &p_name, const Ref<EditorNode3DGizmo> &p_gizmo) {
- ERR_FAIL_COND_V(!materials.has(p_name), Ref<StandardMaterial3D>());
- ERR_FAIL_COND_V(materials[p_name].size() == 0, Ref<StandardMaterial3D>());
-
- if (p_gizmo.is_null() || materials[p_name].size() == 1) {
- return materials[p_name][0];
- }
-
- int index = (p_gizmo->is_selected() ? 1 : 0) + (p_gizmo->is_editable() ? 2 : 0);
-
- Ref<StandardMaterial3D> mat = materials[p_name][index];
-
- if (current_state == ON_TOP && p_gizmo->is_selected()) {
- mat->set_flag(StandardMaterial3D::FLAG_DISABLE_DEPTH_TEST, true);
- } else {
- mat->set_flag(StandardMaterial3D::FLAG_DISABLE_DEPTH_TEST, false);
- }
-
- return mat;
-}
-
-String EditorNode3DGizmoPlugin::get_gizmo_name() const {
- if (get_script_instance() && get_script_instance()->has_method("_get_gizmo_name")) {
- return get_script_instance()->call("_get_gizmo_name");
- }
- return TTR("Nameless gizmo");
-}
-
-int EditorNode3DGizmoPlugin::get_priority() const {
- if (get_script_instance() && get_script_instance()->has_method("_get_priority")) {
- return get_script_instance()->call("_get_priority");
- }
- return 0;
-}
-
-Ref<EditorNode3DGizmo> EditorNode3DGizmoPlugin::get_gizmo(Node3D *p_spatial) {
- if (get_script_instance() && get_script_instance()->has_method("get_gizmo")) {
- return get_script_instance()->call("get_gizmo", p_spatial);
- }
-
- Ref<EditorNode3DGizmo> ref = create_gizmo(p_spatial);
-
- if (ref.is_null()) {
- return ref;
- }
-
- ref->set_plugin(this);
- ref->set_spatial_node(p_spatial);
- ref->set_hidden(current_state == HIDDEN);
-
- current_gizmos.push_back(ref.ptr());
- return ref;
-}
-
-void EditorNode3DGizmoPlugin::_bind_methods() {
-#define GIZMO_REF PropertyInfo(Variant::OBJECT, "gizmo", PROPERTY_HINT_RESOURCE_TYPE, "EditorNode3DGizmo")
-
- BIND_VMETHOD(MethodInfo(Variant::BOOL, "_has_gizmo", PropertyInfo(Variant::OBJECT, "spatial", PROPERTY_HINT_RESOURCE_TYPE, "Node3D")));
- BIND_VMETHOD(MethodInfo(GIZMO_REF, "_create_gizmo", PropertyInfo(Variant::OBJECT, "spatial", PROPERTY_HINT_RESOURCE_TYPE, "Node3D")));
-
- ClassDB::bind_method(D_METHOD("create_material", "name", "color", "billboard", "on_top", "use_vertex_color"), &EditorNode3DGizmoPlugin::create_material, DEFVAL(false), DEFVAL(false), DEFVAL(false));
- ClassDB::bind_method(D_METHOD("create_icon_material", "name", "texture", "on_top", "color"), &EditorNode3DGizmoPlugin::create_icon_material, DEFVAL(false), DEFVAL(Color(1, 1, 1, 1)));
- ClassDB::bind_method(D_METHOD("create_handle_material", "name", "billboard", "texture"), &EditorNode3DGizmoPlugin::create_handle_material, DEFVAL(false), DEFVAL(Variant()));
- ClassDB::bind_method(D_METHOD("add_material", "name", "material"), &EditorNode3DGizmoPlugin::add_material);
-
- ClassDB::bind_method(D_METHOD("get_material", "name", "gizmo"), &EditorNode3DGizmoPlugin::get_material, DEFVAL(Ref<EditorNode3DGizmo>()));
-
- BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_gizmo_name"));
- BIND_VMETHOD(MethodInfo(Variant::INT, "_get_priority"));
- BIND_VMETHOD(MethodInfo(Variant::BOOL, "_can_be_hidden"));
- BIND_VMETHOD(MethodInfo(Variant::BOOL, "_is_selectable_when_hidden"));
-
- BIND_VMETHOD(MethodInfo("_redraw", GIZMO_REF));
- BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_handle_name", GIZMO_REF, PropertyInfo(Variant::INT, "index")));
-
- MethodInfo hvget(Variant::NIL, "_get_handle_value", GIZMO_REF, PropertyInfo(Variant::INT, "index"));
- hvget.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
- BIND_VMETHOD(hvget);
-
- BIND_VMETHOD(MethodInfo("_set_handle", GIZMO_REF, PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::OBJECT, "camera", PROPERTY_HINT_RESOURCE_TYPE, "Camera3D"), PropertyInfo(Variant::VECTOR2, "point")));
- MethodInfo cm = MethodInfo("_commit_handle", GIZMO_REF, PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::NIL, "restore"), PropertyInfo(Variant::BOOL, "cancel"));
- cm.default_arguments.push_back(false);
- BIND_VMETHOD(cm);
-
- BIND_VMETHOD(MethodInfo(Variant::BOOL, "_is_handle_highlighted", GIZMO_REF, PropertyInfo(Variant::INT, "index")));
-
-#undef GIZMO_REF
-}
-
-bool EditorNode3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
- if (get_script_instance() && get_script_instance()->has_method("_has_gizmo")) {
- return get_script_instance()->call("_has_gizmo", p_spatial);
- }
- return false;
-}
-
-Ref<EditorNode3DGizmo> EditorNode3DGizmoPlugin::create_gizmo(Node3D *p_spatial) {
- if (get_script_instance() && get_script_instance()->has_method("_create_gizmo")) {
- return get_script_instance()->call("_create_gizmo", p_spatial);
- }
-
- Ref<EditorNode3DGizmo> ref;
- if (has_gizmo(p_spatial)) {
- ref.instantiate();
- }
- return ref;
-}
-
-bool EditorNode3DGizmoPlugin::can_be_hidden() const {
- if (get_script_instance() && get_script_instance()->has_method("_can_be_hidden")) {
- return get_script_instance()->call("_can_be_hidden");
- }
- return true;
-}
-
-bool EditorNode3DGizmoPlugin::is_selectable_when_hidden() const {
- if (get_script_instance() && get_script_instance()->has_method("_is_selectable_when_hidden")) {
- return get_script_instance()->call("_is_selectable_when_hidden");
- }
- return false;
-}
-
-void EditorNode3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
- if (get_script_instance() && get_script_instance()->has_method("_redraw")) {
- Ref<EditorNode3DGizmo> ref(p_gizmo);
- get_script_instance()->call("_redraw", ref);
- }
-}
-
-String EditorNode3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_idx) const {
- if (get_script_instance() && get_script_instance()->has_method("_get_handle_name")) {
- return get_script_instance()->call("_get_handle_name", p_gizmo, p_idx);
- }
- return "";
-}
-
-Variant EditorNode3DGizmoPlugin::get_handle_value(EditorNode3DGizmo *p_gizmo, int p_idx) const {
- if (get_script_instance() && get_script_instance()->has_method("_get_handle_value")) {
- return get_script_instance()->call("_get_handle_value", p_gizmo, p_idx);
- }
- return Variant();
-}
-
-void EditorNode3DGizmoPlugin::set_handle(EditorNode3DGizmo *p_gizmo, int p_idx, Camera3D *p_camera, const Point2 &p_point) {
- if (get_script_instance() && get_script_instance()->has_method("_set_handle")) {
- get_script_instance()->call("_set_handle", p_gizmo, p_idx, p_camera, p_point);
- }
-}
-
-void EditorNode3DGizmoPlugin::commit_handle(EditorNode3DGizmo *p_gizmo, int p_idx, const Variant &p_restore, bool p_cancel) {
- if (get_script_instance() && get_script_instance()->has_method("_commit_handle")) {
- get_script_instance()->call("_commit_handle", p_gizmo, p_idx, p_restore, p_cancel);
- }
-}
-
-bool EditorNode3DGizmoPlugin::is_handle_highlighted(const EditorNode3DGizmo *p_gizmo, int p_idx) const {
- if (get_script_instance() && get_script_instance()->has_method("_is_handle_highlighted")) {
- return get_script_instance()->call("_is_handle_highlighted", p_gizmo, p_idx);
- }
- return false;
-}
-
-void EditorNode3DGizmoPlugin::set_state(int p_state) {
- current_state = p_state;
- for (int i = 0; i < current_gizmos.size(); ++i) {
- current_gizmos[i]->set_hidden(current_state == HIDDEN);
- }
-}
-
-int EditorNode3DGizmoPlugin::get_state() const {
- return current_state;
-}
-
-void EditorNode3DGizmoPlugin::unregister_gizmo(EditorNode3DGizmo *p_gizmo) {
- current_gizmos.erase(p_gizmo);
-}
-
-EditorNode3DGizmoPlugin::EditorNode3DGizmoPlugin() {
- current_state = VISIBLE;
-}
-
-EditorNode3DGizmoPlugin::~EditorNode3DGizmoPlugin() {
- for (int i = 0; i < current_gizmos.size(); ++i) {
- current_gizmos[i]->set_plugin(nullptr);
- current_gizmos[i]->get_spatial_node()->set_gizmo(nullptr);
- }
- if (Node3DEditor::get_singleton()) {
- Node3DEditor::get_singleton()->update_all_gizmos();
- }
-}
diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h
index cc65cb53ca..cb2edbcbc1 100644
--- a/editor/plugins/node_3d_editor_plugin.h
+++ b/editor/plugins/node_3d_editor_plugin.h
@@ -34,6 +34,7 @@
#include "editor/editor_node.h"
#include "editor/editor_plugin.h"
#include "editor/editor_scale.h"
+#include "editor/plugins/node_3d_editor_gizmos.h"
#include "scene/3d/light_3d.h"
#include "scene/3d/visual_instance_3d.h"
#include "scene/3d/world_environment.h"
@@ -43,96 +44,10 @@
class Camera3D;
class Node3DEditor;
-class EditorNode3DGizmoPlugin;
class Node3DEditorViewport;
class SubViewportContainer;
-
-class EditorNode3DGizmo : public Node3DGizmo {
- GDCLASS(EditorNode3DGizmo, Node3DGizmo);
-
- bool selected;
- bool instantiated;
-
-public:
- void set_selected(bool p_selected) { selected = p_selected; }
- bool is_selected() const { return selected; }
-
- struct Instance {
- RID instance;
- Ref<ArrayMesh> mesh;
- Ref<Material> material;
- Ref<SkinReference> skin_reference;
- RID skeleton;
- bool billboard = false;
- bool unscaled = false;
- bool can_intersect = false;
- bool extra_margin = false;
-
- void create_instance(Node3D *p_base, bool p_hidden = false);
- };
-
- Vector<Vector3> collision_segments;
- Ref<TriangleMesh> collision_mesh;
-
- struct Handle {
- Vector3 pos;
- bool billboard = false;
- };
-
- Vector<Vector3> handles;
- Vector<Vector3> secondary_handles;
- float selectable_icon_size;
- bool billboard_handle;
-
- bool valid;
- bool hidden;
- Node3D *base;
- Vector<Instance> instances;
- Node3D *spatial_node;
- EditorNode3DGizmoPlugin *gizmo_plugin;
-
- void _set_spatial_node(Node *p_node) { set_spatial_node(Object::cast_to<Node3D>(p_node)); }
-
-protected:
- static void _bind_methods();
-
-public:
- void add_lines(const Vector<Vector3> &p_lines, const Ref<Material> &p_material, bool p_billboard = false, const Color &p_modulate = Color(1, 1, 1));
- void add_vertices(const Vector<Vector3> &p_vertices, const Ref<Material> &p_material, Mesh::PrimitiveType p_primitive_type, bool p_billboard = false, const Color &p_modulate = Color(1, 1, 1));
- void add_mesh(const Ref<ArrayMesh> &p_mesh, bool p_billboard = false, const Ref<SkinReference> &p_skin_reference = Ref<SkinReference>(), const Ref<Material> &p_material = Ref<Material>());
- void add_collision_segments(const Vector<Vector3> &p_lines);
- void add_collision_triangles(const Ref<TriangleMesh> &p_tmesh);
- void add_unscaled_billboard(const Ref<Material> &p_material, float p_scale = 1, const Color &p_modulate = Color(1, 1, 1));
- void add_handles(const Vector<Vector3> &p_handles, const Ref<Material> &p_material, bool p_billboard = false, bool p_secondary = false);
- void add_solid_box(Ref<Material> &p_material, Vector3 p_size, Vector3 p_position = Vector3());
-
- virtual bool is_handle_highlighted(int p_idx) const;
- virtual String get_handle_name(int p_idx) const;
- virtual Variant get_handle_value(int p_idx);
- virtual void set_handle(int p_idx, Camera3D *p_camera, const Point2 &p_point);
- virtual void commit_handle(int p_idx, const Variant &p_restore, bool p_cancel = false);
-
- void set_spatial_node(Node3D *p_node);
- Node3D *get_spatial_node() const { return spatial_node; }
- Ref<EditorNode3DGizmoPlugin> get_plugin() const { return gizmo_plugin; }
- Vector3 get_handle_pos(int p_idx) const;
- bool intersect_frustum(const Camera3D *p_camera, const Vector<Plane> &p_frustum);
- bool intersect_ray(Camera3D *p_camera, const Point2 &p_point, Vector3 &r_pos, Vector3 &r_normal, int *r_gizmo_handle = nullptr, bool p_sec_first = false);
-
- virtual void clear() override;
- virtual void create() override;
- virtual void transform() override;
- virtual void redraw() override;
- virtual void free() override;
-
- virtual bool is_editable() const;
-
- void set_hidden(bool p_hidden);
- void set_plugin(EditorNode3DGizmoPlugin *p_plugin);
-
- EditorNode3DGizmo();
- ~EditorNode3DGizmo();
-};
+class DirectionalLight3D;
+class WorldEnvironment;
class ViewportRotationControl : public Control {
GDCLASS(ViewportRotationControl, Control);
@@ -286,7 +201,7 @@ private:
bool orthogonal;
bool auto_orthogonal;
bool lock_rotation;
- float gizmo_scale;
+ real_t gizmo_scale;
bool freelook_active;
real_t freelook_speed;
@@ -306,18 +221,16 @@ private:
struct _RayResult {
Node3D *item = nullptr;
- float depth = 0;
- int handle = 0;
+ real_t depth = 0;
_FORCE_INLINE_ bool operator<(const _RayResult &p_rr) const { return depth < p_rr.depth; }
};
void _update_name();
void _compute_edit(const Point2 &p_point);
void _clear_selected();
- void _select_clicked(bool p_append, bool p_single, bool p_allow_locked = false);
- 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 = nullptr, 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, bool p_include_locked_nodes = false);
+ void _select_clicked(bool p_allow_locked);
+ ObjectID _select_ray(const Point2 &p_pos);
+ void _find_items_at_pos(const Point2 &p_pos, Vector<_RayResult> &r_results, bool p_include_locked);
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);
@@ -329,7 +242,8 @@ private:
Vector3 _get_screen_to_space(const Vector3 &p_vector3);
void _select_region();
- bool _gizmo_select(const Vector2 &p_screenpos, bool p_highlight_only = false);
+ bool _transform_gizmo_select(const Vector2 &p_screenpos, bool p_highlight_only = false);
+ void _transform_gizmo_apply(Node3D *p_node, const Transform3D &p_transform, bool p_local);
void _nav_pan(Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative);
void _nav_zoom(Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative);
@@ -342,7 +256,6 @@ private:
ObjectID clicked;
Vector<_RayResult> selection_results;
- bool clicked_includes_current;
bool clicked_wants_append;
PopupMenu *selection_menu;
@@ -383,20 +296,17 @@ private:
Vector3 click_ray;
Vector3 click_ray_pos;
Vector3 center;
- Vector3 orig_gizmo_pos;
- int edited_gizmo = 0;
Point2 mouse_pos;
Point2 original_mouse_pos;
bool snap = false;
Ref<EditorNode3DGizmo> gizmo;
int gizmo_handle = 0;
Variant gizmo_initial_value;
- Vector3 gizmo_initial_pos;
} _edit;
struct Cursor {
Vector3 pos;
- float x_rot, y_rot, distance;
+ real_t x_rot, y_rot, distance;
Vector3 eye_pos; // Used in freelook mode
bool region_select;
Point2 region_begin, region_end;
@@ -430,8 +340,7 @@ private:
void set_message(String p_message, float p_time = 5);
- //
- void _update_camera(float p_interp_delta);
+ void _update_camera(real_t p_interp_delta);
Transform3D to_camera_transform(const Cursor &p_cursor) const;
void _draw();
@@ -472,6 +381,8 @@ private:
void _project_settings_changed();
+ Transform3D _compute_transform(TransformMode p_mode, const Transform3D &p_original, const Transform3D &p_original_local, Vector3 p_motion, double p_extra, bool p_local);
+
protected:
void _notification(int p_what);
static void _bind_methods();
@@ -512,6 +423,8 @@ public:
Node3D *sp;
RID sbox_instance;
RID sbox_instance_xray;
+ Ref<EditorNode3DGizmo> gizmo;
+ Map<int, Transform3D> subgizmos; // map ID -> initial transform
Node3DEditorSelectedItem() {
sp = nullptr;
@@ -536,8 +449,8 @@ public:
private:
View view;
bool mouseover;
- float ratio_h;
- float ratio_v;
+ real_t ratio_h;
+ real_t ratio_v;
bool hovering_v;
bool hovering_h;
@@ -617,10 +530,12 @@ private:
Ref<StandardMaterial3D> plane_gizmo_color_hl[3];
Ref<ShaderMaterial> rotate_gizmo_color_hl[3];
- int over_gizmo_handle;
- float snap_translate_value;
- float snap_rotate_value;
- float snap_scale_value;
+ Ref<Node3DGizmo> current_hover_gizmo;
+ int current_hover_gizmo_handle;
+
+ real_t snap_translate_value;
+ real_t snap_rotate_value;
+ real_t snap_scale_value;
Ref<ArrayMesh> selection_box_xray;
Ref<ArrayMesh> selection_box;
@@ -638,7 +553,7 @@ private:
struct Gizmo {
bool visible = false;
- float scale = 0;
+ real_t scale = 0;
Transform3D transform;
} gizmo;
@@ -688,7 +603,6 @@ private:
LineEdit *snap_translate;
LineEdit *snap_rotate;
LineEdit *snap_scale;
- PanelContainer *menu_panel;
LineEdit *xform_translate[3];
LineEdit *xform_rotate[3];
@@ -710,6 +624,10 @@ private:
void _update_camera_override_viewport(Object *p_viewport);
HBoxContainer *hbc_menu;
+ // Used for secondary menu items which are displayed depending on the currently selected node
+ // (such as MeshInstance's "Mesh" menu).
+ PanelContainer *context_menu_container;
+ HBoxContainer *hbc_context_menu;
void _generate_selection_boxes();
UndoRedo *undo_redo;
@@ -717,6 +635,7 @@ private:
int camera_override_viewport_id;
void _init_indicators();
+ void _update_context_menu_stylebox();
void _update_gizmos_menu();
void _update_gizmos_menu_theme();
void _init_grid();
@@ -734,6 +653,7 @@ private:
Node3D *selected;
void _request_gizmo(Object *p_obj);
+ void _clear_subgizmo_selection(Object *p_obj = nullptr);
static Node3DEditor *singleton;
@@ -746,8 +666,7 @@ private:
Node3DEditor();
- bool is_any_freelook_active() const;
-
+ void _selection_changed();
void _refresh_menu_icons();
// Preview Sun and Environment
@@ -808,6 +727,8 @@ private:
void _add_sun_to_scene(bool p_already_added_environment = false);
void _add_environment_to_scene(bool p_already_added_sun = false);
+ void _update_theme();
+
protected:
void _notification(int p_what);
//void _gui_input(InputEvent p_event);
@@ -830,9 +751,9 @@ public:
ToolMode get_tool_mode() const { return tool_mode; }
bool are_local_coords_enabled() const { return tool_option_button[Node3DEditor::TOOL_OPT_LOCAL_COORDS]->is_pressed(); }
bool is_snap_enabled() const { return snap_enabled ^ snap_key_enabled; }
- float get_translate_snap() const;
- float get_rotate_snap() const;
- float get_scale_snap() const;
+ double get_translate_snap() const;
+ double get_rotate_snap() const;
+ double get_scale_snap() const;
Ref<ArrayMesh> get_move_gizmo(int idx) const { return move_gizmo[idx]; }
Ref<ArrayMesh> get_move_plane_gizmo(int idx) const { return move_plane_gizmo[idx]; }
@@ -861,10 +782,16 @@ public:
VSplitContainer *get_shader_split();
HSplitContainer *get_palette_split();
- Node3D *get_selected() { return selected; }
+ Node3D *get_single_selected_node() { return selected; }
+ bool is_current_selected_gizmo(const EditorNode3DGizmo *p_gizmo);
+ bool is_subgizmo_selected(int p_id);
+ Vector<int> get_subgizmo_selection();
+
+ Ref<EditorNode3DGizmo> get_current_hover_gizmo() const { return current_hover_gizmo; }
+ void set_current_hover_gizmo(Ref<EditorNode3DGizmo> p_gizmo) { current_hover_gizmo = p_gizmo; }
- int get_over_gizmo_handle() const { return over_gizmo_handle; }
- void set_over_gizmo_handle(int idx) { over_gizmo_handle = idx; }
+ void set_current_hover_gizmo_handle(int p_id) { current_hover_gizmo_handle = p_id; }
+ int get_current_hover_gizmo_handle() const { return current_hover_gizmo_handle; }
void set_can_preview(Camera3D *p_preview);
@@ -907,50 +834,4 @@ public:
~Node3DEditorPlugin();
};
-class EditorNode3DGizmoPlugin : public Resource {
- GDCLASS(EditorNode3DGizmoPlugin, Resource);
-
-public:
- static const int VISIBLE = 0;
- static const int HIDDEN = 1;
- static const int ON_TOP = 2;
-
-protected:
- int current_state;
- List<EditorNode3DGizmo *> current_gizmos;
- HashMap<String, Vector<Ref<StandardMaterial3D>>> materials;
-
- static void _bind_methods();
- virtual bool has_gizmo(Node3D *p_spatial);
- virtual Ref<EditorNode3DGizmo> create_gizmo(Node3D *p_spatial);
-
-public:
- void create_material(const String &p_name, const Color &p_color, bool p_billboard = false, bool p_on_top = false, bool p_use_vertex_color = false);
- void create_icon_material(const String &p_name, const Ref<Texture2D> &p_texture, bool p_on_top = false, const Color &p_albedo = Color(1, 1, 1, 1));
- void create_handle_material(const String &p_name, bool p_billboard = false, const Ref<Texture2D> &p_texture = nullptr);
- void add_material(const String &p_name, Ref<StandardMaterial3D> p_material);
-
- Ref<StandardMaterial3D> get_material(const String &p_name, const Ref<EditorNode3DGizmo> &p_gizmo = Ref<EditorNode3DGizmo>());
-
- virtual String get_gizmo_name() const;
- virtual int get_priority() const;
- virtual bool can_be_hidden() const;
- virtual bool is_selectable_when_hidden() const;
-
- virtual void redraw(EditorNode3DGizmo *p_gizmo);
- virtual String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_idx) const;
- virtual Variant get_handle_value(EditorNode3DGizmo *p_gizmo, int p_idx) const;
- virtual void set_handle(EditorNode3DGizmo *p_gizmo, int p_idx, Camera3D *p_camera, const Point2 &p_point);
- virtual void commit_handle(EditorNode3DGizmo *p_gizmo, int p_idx, const Variant &p_restore, bool p_cancel = false);
- virtual bool is_handle_highlighted(const EditorNode3DGizmo *p_gizmo, int p_idx) const;
-
- Ref<EditorNode3DGizmo> get_gizmo(Node3D *p_spatial);
- void set_state(int p_state);
- int get_state() const;
- void unregister_gizmo(EditorNode3DGizmo *p_gizmo);
-
- EditorNode3DGizmoPlugin();
- virtual ~EditorNode3DGizmoPlugin();
-};
-
#endif // NODE_3D_EDITOR_PLUGIN_H
diff --git a/editor/plugins/path_3d_editor_plugin.cpp b/editor/plugins/path_3d_editor_plugin.cpp
index 0a95986fe2..63b89aea35 100644
--- a/editor/plugins/path_3d_editor_plugin.cpp
+++ b/editor/plugins/path_3d_editor_plugin.cpp
@@ -36,20 +36,20 @@
#include "node_3d_editor_plugin.h"
#include "scene/resources/curve.h"
-String Path3DGizmo::get_handle_name(int p_idx) const {
+String Path3DGizmo::get_handle_name(int p_id) const {
Ref<Curve3D> c = path->get_curve();
if (c.is_null()) {
return "";
}
- if (p_idx < c->get_point_count()) {
- return TTR("Curve Point #") + itos(p_idx);
+ if (p_id < c->get_point_count()) {
+ return TTR("Curve Point #") + itos(p_id);
}
- p_idx = p_idx - c->get_point_count() + 1;
+ p_id = p_id - c->get_point_count() + 1;
- int idx = p_idx / 2;
- int t = p_idx % 2;
+ int idx = p_id / 2;
+ int t = p_id % 2;
String n = TTR("Curve Point #") + itos(idx);
if (t == 0) {
n += " In";
@@ -60,21 +60,21 @@ String Path3DGizmo::get_handle_name(int p_idx) const {
return n;
}
-Variant Path3DGizmo::get_handle_value(int p_idx) {
+Variant Path3DGizmo::get_handle_value(int p_id) const {
Ref<Curve3D> c = path->get_curve();
if (c.is_null()) {
return Variant();
}
- if (p_idx < c->get_point_count()) {
- original = c->get_point_position(p_idx);
+ if (p_id < c->get_point_count()) {
+ original = c->get_point_position(p_id);
return original;
}
- p_idx = p_idx - c->get_point_count() + 1;
+ p_id = p_id - c->get_point_count() + 1;
- int idx = p_idx / 2;
- int t = p_idx % 2;
+ int idx = p_id / 2;
+ int t = p_id % 2;
Vector3 ofs;
if (t == 0) {
@@ -88,7 +88,7 @@ Variant Path3DGizmo::get_handle_value(int p_idx) {
return ofs;
}
-void Path3DGizmo::set_handle(int p_idx, Camera3D *p_camera, const Point2 &p_point) {
+void Path3DGizmo::set_handle(int p_id, Camera3D *p_camera, const Point2 &p_point) const {
Ref<Curve3D> c = path->get_curve();
if (c.is_null()) {
return;
@@ -100,7 +100,7 @@ void Path3DGizmo::set_handle(int p_idx, Camera3D *p_camera, const Point2 &p_poin
Vector3 ray_dir = p_camera->project_ray_normal(p_point);
// Setting curve point positions
- if (p_idx < c->get_point_count()) {
+ if (p_id < c->get_point_count()) {
Plane p(gt.xform(original), p_camera->get_transform().basis.get_axis(2));
Vector3 inters;
@@ -112,16 +112,16 @@ void Path3DGizmo::set_handle(int p_idx, Camera3D *p_camera, const Point2 &p_poin
}
Vector3 local = gi.xform(inters);
- c->set_point_position(p_idx, local);
+ c->set_point_position(p_id, local);
}
return;
}
- p_idx = p_idx - c->get_point_count() + 1;
+ p_id = p_id - c->get_point_count() + 1;
- int idx = p_idx / 2;
- int t = p_idx % 2;
+ int idx = p_id / 2;
+ int t = p_id % 2;
Vector3 base = c->get_point_position(idx);
@@ -157,7 +157,7 @@ void Path3DGizmo::set_handle(int p_idx, Camera3D *p_camera, const Point2 &p_poin
}
}
-void Path3DGizmo::commit_handle(int p_idx, const Variant &p_restore, bool p_cancel) {
+void Path3DGizmo::commit_handle(int p_id, const Variant &p_restore, bool p_cancel) const {
Ref<Curve3D> c = path->get_curve();
if (c.is_null()) {
return;
@@ -165,27 +165,27 @@ void Path3DGizmo::commit_handle(int p_idx, const Variant &p_restore, bool p_canc
UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo();
- if (p_idx < c->get_point_count()) {
+ if (p_id < c->get_point_count()) {
if (p_cancel) {
- c->set_point_position(p_idx, p_restore);
+ c->set_point_position(p_id, p_restore);
return;
}
ur->create_action(TTR("Set Curve Point Position"));
- ur->add_do_method(c.ptr(), "set_point_position", p_idx, c->get_point_position(p_idx));
- ur->add_undo_method(c.ptr(), "set_point_position", p_idx, p_restore);
+ ur->add_do_method(c.ptr(), "set_point_position", p_id, c->get_point_position(p_id));
+ ur->add_undo_method(c.ptr(), "set_point_position", p_id, p_restore);
ur->commit_action();
return;
}
- p_idx = p_idx - c->get_point_count() + 1;
+ p_id = p_id - c->get_point_count() + 1;
- int idx = p_idx / 2;
- int t = p_idx % 2;
+ int idx = p_id / 2;
+ int t = p_id % 2;
if (t == 0) {
if (p_cancel) {
- c->set_point_in(p_idx, p_restore);
+ c->set_point_in(p_id, p_restore);
return;
}
@@ -282,7 +282,7 @@ void Path3DGizmo::redraw() {
add_handles(handles, handles_material);
}
if (sec_handles.size()) {
- add_handles(sec_handles, sec_handles_material, false, true);
+ add_handles(sec_handles, sec_handles_material, Vector<int>(), false, true);
}
}
}
diff --git a/editor/plugins/path_3d_editor_plugin.h b/editor/plugins/path_3d_editor_plugin.h
index 13870d7591..5902500526 100644
--- a/editor/plugins/path_3d_editor_plugin.h
+++ b/editor/plugins/path_3d_editor_plugin.h
@@ -31,7 +31,9 @@
#ifndef PATH_EDITOR_PLUGIN_H
#define PATH_EDITOR_PLUGIN_H
-#include "editor/node_3d_editor_gizmos.h"
+#include "editor/editor_plugin.h"
+#include "editor/plugins/node_3d_editor_gizmos.h"
+#include "scene/3d/camera_3d.h"
#include "scene/3d/path_3d.h"
class Path3DGizmo : public EditorNode3DGizmo {
@@ -44,9 +46,9 @@ class Path3DGizmo : public EditorNode3DGizmo {
public:
virtual String get_handle_name(int p_idx) const override;
- virtual Variant get_handle_value(int p_idx) override;
- virtual void set_handle(int p_idx, Camera3D *p_camera, const Point2 &p_point) override;
- virtual void commit_handle(int p_idx, const Variant &p_restore, bool p_cancel = false) override;
+ virtual Variant get_handle_value(int p_id) const override;
+ virtual void set_handle(int p_id, Camera3D *p_camera, const Point2 &p_point) const override;
+ virtual void commit_handle(int p_id, const Variant &p_restore, bool p_cancel = false) const override;
virtual void redraw() override;
Path3DGizmo(Path3D *p_path = nullptr);
diff --git a/editor/plugins/polygon_2d_editor_plugin.cpp b/editor/plugins/polygon_2d_editor_plugin.cpp
index b420372eff..9377395418 100644
--- a/editor/plugins/polygon_2d_editor_plugin.cpp
+++ b/editor/plugins/polygon_2d_editor_plugin.cpp
@@ -400,25 +400,25 @@ void Polygon2DEditor::_set_show_grid(bool p_show) {
uv_edit_draw->update();
}
-void Polygon2DEditor::_set_snap_off_x(float p_val) {
+void Polygon2DEditor::_set_snap_off_x(real_t p_val) {
snap_offset.x = p_val;
EditorSettings::get_singleton()->set_project_metadata("polygon_2d_uv_editor", "snap_offset", snap_offset);
uv_edit_draw->update();
}
-void Polygon2DEditor::_set_snap_off_y(float p_val) {
+void Polygon2DEditor::_set_snap_off_y(real_t p_val) {
snap_offset.y = p_val;
EditorSettings::get_singleton()->set_project_metadata("polygon_2d_uv_editor", "snap_offset", snap_offset);
uv_edit_draw->update();
}
-void Polygon2DEditor::_set_snap_step_x(float p_val) {
+void Polygon2DEditor::_set_snap_step_x(real_t p_val) {
snap_step.x = p_val;
EditorSettings::get_singleton()->set_project_metadata("polygon_2d_uv_editor", "snap_step", snap_step);
uv_edit_draw->update();
}
-void Polygon2DEditor::_set_snap_step_y(float p_val) {
+void Polygon2DEditor::_set_snap_step_y(real_t p_val) {
snap_step.y = p_val;
EditorSettings::get_singleton()->set_project_metadata("polygon_2d_uv_editor", "snap_step", snap_step);
uv_edit_draw->update();
@@ -569,11 +569,11 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) {
}
int closest = -1;
- float closest_dist = 1e20;
+ real_t closest_dist = 1e20;
for (int i = points_prev.size() - internal_vertices; i < points_prev.size(); i++) {
Vector2 tuv = mtx.xform(uv_create_poly_prev[i]);
- float dist = tuv.distance_to(Vector2(mb->get_position().x, mb->get_position().y));
+ real_t dist = tuv.distance_to(Vector2(mb->get_position().x, mb->get_position().y));
if (dist < 8 && dist < closest_dist) {
closest = i;
closest_dist = dist;
@@ -639,11 +639,11 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) {
if (uv_move_current == UV_MODE_ADD_POLYGON) {
int closest = -1;
- float closest_dist = 1e20;
+ real_t closest_dist = 1e20;
for (int i = 0; i < points_prev.size(); i++) {
Vector2 tuv = mtx.xform(points_prev[i]);
- float dist = tuv.distance_to(Vector2(mb->get_position().x, mb->get_position().y));
+ real_t dist = tuv.distance_to(Vector2(mb->get_position().x, mb->get_position().y));
if (dist < 8 && dist < closest_dist) {
closest = i;
closest_dist = dist;
@@ -825,7 +825,7 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) {
}
center /= uv_new.size();
- float angle = (uv_drag_from - mtx.xform(center)).normalized().angle_to((uv_drag_to - mtx.xform(center)).normalized());
+ real_t angle = (uv_drag_from - mtx.xform(center)).normalized().angle_to((uv_drag_to - mtx.xform(center)).normalized());
for (int i = 0; i < uv_new.size(); i++) {
Vector2 rel = points_prev[i] - center;
@@ -848,13 +848,13 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) {
}
center /= uv_new.size();
- float from_dist = uv_drag_from.distance_to(mtx.xform(center));
- float to_dist = uv_drag_to.distance_to(mtx.xform(center));
+ real_t from_dist = uv_drag_from.distance_to(mtx.xform(center));
+ real_t to_dist = uv_drag_to.distance_to(mtx.xform(center));
if (from_dist < 2) {
break;
}
- float scale = to_dist / from_dist;
+ real_t scale = to_dist / from_dist;
for (int i = 0; i < uv_new.size(); i++) {
Vector2 rel = points_prev[i] - center;
@@ -881,8 +881,8 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) {
{
int pc = painted_weights.size();
- float amount = bone_paint_strength->get_value();
- float radius = bone_paint_radius->get_value() * EDSCALE;
+ real_t amount = bone_paint_strength->get_value();
+ real_t radius = bone_paint_radius->get_value() * EDSCALE;
if (uv_mode == UV_MODE_CLEAR_WEIGHT) {
amount = -amount;
@@ -925,7 +925,7 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) {
}
}
-void Polygon2DEditor::_uv_scroll_changed(float) {
+void Polygon2DEditor::_uv_scroll_changed(real_t) {
if (updating_uv_scroll) {
return;
}
diff --git a/editor/plugins/polygon_2d_editor_plugin.h b/editor/plugins/polygon_2d_editor_plugin.h
index af3b2f5aef..cbe7ecf360 100644
--- a/editor/plugins/polygon_2d_editor_plugin.h
+++ b/editor/plugins/polygon_2d_editor_plugin.h
@@ -95,7 +95,7 @@ class Polygon2DEditor : public AbstractPolygon2DEditor {
void _update_bone_list();
Vector2 uv_draw_ofs;
- float uv_draw_zoom;
+ real_t uv_draw_zoom;
Vector<Vector2> points_prev;
Vector<Vector2> uv_create_uv_prev;
Vector<Vector2> uv_create_poly_prev;
@@ -127,17 +127,17 @@ class Polygon2DEditor : public AbstractPolygon2DEditor {
void _cancel_editing();
void _update_polygon_editing_state();
- void _uv_scroll_changed(float);
+ void _uv_scroll_changed(real_t);
void _uv_input(const Ref<InputEvent> &p_input);
void _uv_draw();
void _uv_mode(int p_mode);
void _set_use_snap(bool p_use);
void _set_show_grid(bool p_show);
- void _set_snap_off_x(float p_val);
- void _set_snap_off_y(float p_val);
- void _set_snap_step_x(float p_val);
- void _set_snap_step_y(float p_val);
+ void _set_snap_off_x(real_t p_val);
+ void _set_snap_off_y(real_t p_val);
+ void _set_snap_step_x(real_t p_val);
+ void _set_snap_step_y(real_t p_val);
void _uv_edit_mode_select(int p_mode);
void _uv_edit_popup_hide();
diff --git a/editor/plugins/resource_preloader_editor_plugin.cpp b/editor/plugins/resource_preloader_editor_plugin.cpp
index e099c8b5fd..cbea2405b8 100644
--- a/editor/plugins/resource_preloader_editor_plugin.cpp
+++ b/editor/plugins/resource_preloader_editor_plugin.cpp
@@ -181,21 +181,21 @@ void ResourcePreloaderEditor::_update_library() {
preloader->get_resource_list(&rnames);
List<String> names;
- for (List<StringName>::Element *E = rnames.front(); E; E = E->next()) {
- names.push_back(E->get());
+ for (const StringName &E : rnames) {
+ names.push_back(E);
}
names.sort();
- for (List<String>::Element *E = names.front(); E; E = E->next()) {
+ for (const String &E : names) {
TreeItem *ti = tree->create_item(root);
ti->set_cell_mode(0, TreeItem::CELL_MODE_STRING);
ti->set_editable(0, true);
ti->set_selectable(0, true);
- ti->set_text(0, E->get());
- ti->set_metadata(0, E->get());
+ ti->set_text(0, E);
+ ti->set_metadata(0, E);
- RES r = preloader->get_resource(E->get());
+ RES r = preloader->get_resource(E);
ERR_CONTINUE(r.is_null());
diff --git a/editor/plugins/root_motion_editor_plugin.cpp b/editor/plugins/root_motion_editor_plugin.cpp
index cea76f5233..ed91f174d1 100644
--- a/editor/plugins/root_motion_editor_plugin.cpp
+++ b/editor/plugins/root_motion_editor_plugin.cpp
@@ -70,8 +70,8 @@ void EditorPropertyRootMotion::_node_assign() {
List<StringName> animations;
player->get_animation_list(&animations);
- for (List<StringName>::Element *E = animations.front(); E; E = E->next()) {
- Ref<Animation> anim = player->get_animation(E->get());
+ for (const StringName &E : animations) {
+ Ref<Animation> anim = player->get_animation(E);
for (int i = 0; i < anim->get_track_count(); i++) {
paths.insert(anim->track_get_path(i));
}
diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp
index 1851ffdc34..668a15da37 100644
--- a/editor/plugins/script_editor_plugin.cpp
+++ b/editor/plugins/script_editor_plugin.cpp
@@ -103,8 +103,8 @@ void EditorStandardSyntaxHighlighter::_update_cache() {
const Color type_color = EDITOR_GET("text_editor/highlighting/engine_type_color");
List<StringName> types;
ClassDB::get_class_list(&types);
- for (List<StringName>::Element *E = types.front(); E; E = E->next()) {
- String n = E->get();
+ for (const StringName &E : types) {
+ String n = E;
if (n.begins_with("_")) {
n = n.substr(1, n.length());
}
@@ -115,8 +115,8 @@ void EditorStandardSyntaxHighlighter::_update_cache() {
const Color usertype_color = EDITOR_GET("text_editor/highlighting/user_type_color");
List<StringName> global_classes;
ScriptServer::get_global_class_list(&global_classes);
- for (List<StringName>::Element *E = global_classes.front(); E; E = E->next()) {
- highlighter->add_keyword_color(E->get(), usertype_color);
+ for (const StringName &E : global_classes) {
+ highlighter->add_keyword_color(E, usertype_color);
}
/* Autoloads. */
@@ -134,8 +134,8 @@ void EditorStandardSyntaxHighlighter::_update_cache() {
const Color basetype_color = EDITOR_GET("text_editor/highlighting/base_type_color");
List<String> core_types;
script->get_language()->get_core_type_words(&core_types);
- for (List<String>::Element *E = core_types.front(); E; E = E->next()) {
- highlighter->add_keyword_color(E->get(), basetype_color);
+ for (const String &E : core_types) {
+ highlighter->add_keyword_color(E, basetype_color);
}
/* Reserved words. */
@@ -143,11 +143,11 @@ void EditorStandardSyntaxHighlighter::_update_cache() {
const Color control_flow_keyword_color = EDITOR_GET("text_editor/highlighting/control_flow_keyword_color");
List<String> keywords;
script->get_language()->get_reserved_words(&keywords);
- for (List<String>::Element *E = keywords.front(); E; E = E->next()) {
- if (script->get_language()->is_control_flow_keyword(E->get())) {
- highlighter->add_keyword_color(E->get(), control_flow_keyword_color);
+ for (const String &E : keywords) {
+ if (script->get_language()->is_control_flow_keyword(E)) {
+ highlighter->add_keyword_color(E, control_flow_keyword_color);
} else {
- highlighter->add_keyword_color(E->get(), keyword_color);
+ highlighter->add_keyword_color(E, keyword_color);
}
}
@@ -157,9 +157,9 @@ void EditorStandardSyntaxHighlighter::_update_cache() {
if (instance_base != StringName()) {
List<PropertyInfo> plist;
ClassDB::get_property_list(instance_base, &plist);
- for (List<PropertyInfo>::Element *E = plist.front(); E; E = E->next()) {
- String name = E->get().name;
- if (E->get().usage & PROPERTY_USAGE_CATEGORY || E->get().usage & PROPERTY_USAGE_GROUP || E->get().usage & PROPERTY_USAGE_SUBGROUP) {
+ for (const PropertyInfo &E : plist) {
+ String name = E.name;
+ if (E.usage & PROPERTY_USAGE_CATEGORY || E.usage & PROPERTY_USAGE_GROUP || E.usage & PROPERTY_USAGE_SUBGROUP) {
continue;
}
if (name.find("/") != -1) {
@@ -170,8 +170,8 @@ void EditorStandardSyntaxHighlighter::_update_cache() {
List<String> clist;
ClassDB::get_integer_constant_list(instance_base, &clist);
- for (List<String>::Element *E = clist.front(); E; E = E->next()) {
- highlighter->add_member_keyword_color(E->get(), member_variable_color);
+ for (const String &E : clist) {
+ highlighter->add_member_keyword_color(E, member_variable_color);
}
}
@@ -179,8 +179,7 @@ void EditorStandardSyntaxHighlighter::_update_cache() {
const Color comment_color = EDITOR_GET("text_editor/highlighting/comment_color");
List<String> comments;
script->get_language()->get_comment_delimiters(&comments);
- for (List<String>::Element *E = comments.front(); E; E = E->next()) {
- String comment = E->get();
+ for (const String &comment : comments) {
String beg = comment.get_slice(" ", 0);
String end = comment.get_slice_count(" ") > 1 ? comment.get_slice(" ", 1) : String();
highlighter->add_color_region(beg, end, comment_color, end == "");
@@ -190,8 +189,7 @@ void EditorStandardSyntaxHighlighter::_update_cache() {
const Color string_color = EDITOR_GET("text_editor/highlighting/string_color");
List<String> strings;
script->get_language()->get_string_delimiters(&strings);
- for (List<String>::Element *E = strings.front(); E; E = E->next()) {
- String string = E->get();
+ for (const String &string : strings) {
String beg = string.get_slice(" ", 0);
String end = string.get_slice_count(" ") > 1 ? string.get_slice(" ", 1) : String();
highlighter->add_color_region(beg, end, string_color, end == "");
diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp
index 1a6dfa273e..5bbe56a800 100644
--- a/editor/plugins/script_text_editor.cpp
+++ b/editor/plugins/script_text_editor.cpp
@@ -50,9 +50,7 @@ void ConnectionInfoDialog::popup_connections(String p_method, Vector<Node *> p_n
List<Connection> all_connections;
p_nodes[i]->get_signals_connected_to_this(&all_connections);
- for (List<Connection>::Element *E = all_connections.front(); E; E = E->next()) {
- Connection connection = E->get();
-
+ for (const Connection &connection : all_connections) {
if (connection.callable.get_method() != p_method) {
continue;
}
@@ -116,8 +114,8 @@ Vector<String> ScriptTextEditor::get_functions() {
if (script->get_language()->validate(text, script->get_path(), &fnc)) {
//if valid rewrite functions to latest
functions.clear();
- for (List<String>::Element *E = fnc.front(); E; E = E->next()) {
- functions.push_back(E->get());
+ for (const String &E : fnc) {
+ functions.push_back(E);
}
}
@@ -203,21 +201,29 @@ void ScriptTextEditor::_set_theme_for_script() {
List<String> strings;
script->get_language()->get_string_delimiters(&strings);
text_edit->clear_string_delimiters();
- for (List<String>::Element *E = strings.front(); E; E = E->next()) {
- String string = E->get();
+ for (const String &string : strings) {
String beg = string.get_slice(" ", 0);
String end = string.get_slice_count(" ") > 1 ? string.get_slice(" ", 1) : String();
- text_edit->add_string_delimiter(beg, end, end == "");
+ if (!text_edit->has_string_delimiter(beg)) {
+ text_edit->add_string_delimiter(beg, end, end == "");
+ }
+
+ if (!end.is_empty() && !text_edit->has_auto_brace_completion_open_key(beg)) {
+ text_edit->add_auto_brace_completion_pair(beg, end);
+ }
}
List<String> comments;
script->get_language()->get_comment_delimiters(&comments);
text_edit->clear_comment_delimiters();
- for (List<String>::Element *E = comments.front(); E; E = E->next()) {
- String comment = E->get();
+ for (const String &comment : comments) {
String beg = comment.get_slice(" ", 0);
String end = comment.get_slice_count(" ") > 1 ? comment.get_slice(" ", 1) : String();
text_edit->add_comment_delimiter(beg, end, end == "");
+
+ if (!end.is_empty() && !text_edit->has_auto_brace_completion_open_key(beg)) {
+ text_edit->add_auto_brace_completion_pair(beg, end);
+ }
}
}
@@ -417,8 +423,8 @@ void ScriptTextEditor::_validate_script() {
}
functions.clear();
- for (List<String>::Element *E = fnc.front(); E; E = E->next()) {
- functions.push_back(E->get());
+ for (const String &E : fnc) {
+ functions.push_back(E);
}
script_is_valid = true;
}
@@ -432,9 +438,7 @@ void ScriptTextEditor::_validate_script() {
Node *base = get_tree()->get_edited_scene_root();
if (base && missing_connections.size() > 0) {
warnings_panel->push_table(1);
- for (List<Connection>::Element *E = missing_connections.front(); E; E = E->next()) {
- Connection connection = E->get();
-
+ for (const Connection &connection : missing_connections) {
String base_path = base->get_name();
String source_path = base == connection.signal.get_object() ? base_path : base_path + "/" + base->get_path_to(Object::cast_to<Node>(connection.signal.get_object()));
String target_path = base == connection.callable.get_object() ? base_path : base_path + "/" + base->get_path_to(Object::cast_to<Node>(connection.callable.get_object()));
@@ -456,9 +460,7 @@ void ScriptTextEditor::_validate_script() {
// Add script warnings.
warnings_panel->push_table(3);
- for (List<ScriptLanguage::Warning>::Element *E = warnings.front(); E; E = E->next()) {
- ScriptLanguage::Warning w = E->get();
-
+ for (const ScriptLanguage::Warning &w : warnings) {
Dictionary ignore_meta;
ignore_meta["line"] = w.start_line;
ignore_meta["code"] = w.string_code.to_lower();
@@ -488,9 +490,7 @@ void ScriptTextEditor::_validate_script() {
errors_panel->clear();
errors_panel->push_table(2);
- for (List<ScriptLanguage::ScriptError>::Element *E = errors.front(); E; E = E->next()) {
- ScriptLanguage::ScriptError err = E->get();
-
+ for (const ScriptLanguage::ScriptError &err : errors) {
errors_panel->push_cell();
errors_panel->push_meta(err.line - 1);
errors_panel->push_color(warnings_panel->get_theme_color(SNAME("error_color"), SNAME("Editor")));
@@ -511,8 +511,8 @@ void ScriptTextEditor::_validate_script() {
if (errors.is_empty()) {
te->set_line_background_color(i, Color(0, 0, 0, 0));
} else {
- for (List<ScriptLanguage::ScriptError>::Element *E = errors.front(); E; E = E->next()) {
- bool error_line = i == E->get().line - 1;
+ for (const ScriptLanguage::ScriptError &E : errors) {
+ bool error_line = i == E.line - 1;
te->set_line_background_color(i, error_line ? marked_line_color : Color(0, 0, 0, 0));
if (error_line) {
break;
@@ -752,7 +752,7 @@ void ScriptTextEditor::_lookup_symbol(const String &p_symbol, int p_row, int p_c
EditorNode::get_singleton()->load_resource(p_symbol);
}
- } else if (script->get_language()->lookup_code(code_editor->get_text_editor()->get_text_for_lookup_completion(), p_symbol, script->get_path(), base, result) == OK) {
+ } else if (script->get_language()->lookup_code(code_editor->get_text_editor()->get_text_for_symbol_lookup(), p_symbol, script->get_path(), base, result) == OK) {
_goto_line(p_row);
result.class_name = result.class_name.trim_prefix("_");
@@ -855,18 +855,17 @@ void ScriptTextEditor::_validate_symbol(const String &p_symbol) {
}
ScriptLanguage::LookupResult result;
- if (ScriptServer::is_global_class(p_symbol) || p_symbol.is_resource_file() || script->get_language()->lookup_code(code_editor->get_text_editor()->get_text_for_lookup_completion(), p_symbol, script->get_path(), base, result) == OK || (ProjectSettings::get_singleton()->has_autoload(p_symbol) && ProjectSettings::get_singleton()->get_autoload(p_symbol).is_singleton)) {
- text_edit->set_highlighted_word(p_symbol);
+ if (ScriptServer::is_global_class(p_symbol) || p_symbol.is_resource_file() || script->get_language()->lookup_code(code_editor->get_text_editor()->get_text_for_symbol_lookup(), p_symbol, script->get_path(), base, result) == OK || (ProjectSettings::get_singleton()->has_autoload(p_symbol) && ProjectSettings::get_singleton()->get_autoload(p_symbol).is_singleton)) {
+ text_edit->set_symbol_lookup_word_as_valid(true);
} else if (p_symbol.is_rel_path()) {
String path = _get_absolute_path(p_symbol);
if (FileAccess::exists(path)) {
- text_edit->set_highlighted_word(p_symbol);
+ text_edit->set_symbol_lookup_word_as_valid(true);
} else {
- text_edit->set_highlighted_word(String());
+ text_edit->set_symbol_lookup_word_as_valid(false);
}
-
} else {
- text_edit->set_highlighted_word(String());
+ text_edit->set_symbol_lookup_word_as_valid(false);
}
}
@@ -910,8 +909,7 @@ void ScriptTextEditor::_update_connected_methods() {
List<Connection> connections;
nodes[i]->get_signals_connected_to_this(&connections);
- for (List<Connection>::Element *E = connections.front(); E; E = E->next()) {
- Connection connection = E->get();
+ for (const Connection &connection : connections) {
if (!(connection.flags & CONNECT_PERSIST)) {
continue;
}
@@ -1286,8 +1284,7 @@ void ScriptTextEditor::_edit_option_toggle_inline_comment() {
List<String> comment_delimiters;
script->get_language()->get_comment_delimiters(&comment_delimiters);
- for (List<String>::Element *E = comment_delimiters.front(); E; E = E->next()) {
- String script_delimiter = E->get();
+ for (const String &script_delimiter : comment_delimiters) {
if (script_delimiter.find(" ") == -1) {
delimiter = script_delimiter;
break;
@@ -1556,7 +1553,7 @@ void ScriptTextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) {
base = _find_node_for_script(base, base, script);
}
ScriptLanguage::LookupResult result;
- if (script->get_language()->lookup_code(code_editor->get_text_editor()->get_text_for_lookup_completion(), word_at_pos, script->get_path(), base, result) == OK) {
+ if (script->get_language()->lookup_code(code_editor->get_text_editor()->get_text_for_symbol_lookup(), word_at_pos, script->get_path(), base, result) == OK) {
open_docs = true;
}
}
@@ -1841,7 +1838,7 @@ ScriptTextEditor::ScriptTextEditor() {
code_editor->get_text_editor()->set_code_hint_draw_below(EditorSettings::get_singleton()->get("text_editor/completion/put_callhint_tooltip_below_current_line"));
- code_editor->get_text_editor()->set_select_identifiers_on_hover(true);
+ code_editor->get_text_editor()->set_symbol_lookup_on_click_enabled(true);
code_editor->get_text_editor()->set_context_menu_enabled(false);
context_menu = memnew(PopupMenu);
diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp
index 0d65dec87b..95973f9dfd 100644
--- a/editor/plugins/shader_editor_plugin.cpp
+++ b/editor/plugins/shader_editor_plugin.cpp
@@ -118,11 +118,11 @@ void ShaderTextEditor::_load_theme_settings() {
const Color keyword_color = EDITOR_GET("text_editor/highlighting/keyword_color");
const Color control_flow_keyword_color = EDITOR_GET("text_editor/highlighting/control_flow_keyword_color");
- for (List<String>::Element *E = keywords.front(); E; E = E->next()) {
- if (ShaderLanguage::is_control_flow_keyword(E->get())) {
- syntax_highlighter->add_keyword_color(E->get(), control_flow_keyword_color);
+ for (const String &E : keywords) {
+ if (ShaderLanguage::is_control_flow_keyword(E)) {
+ syntax_highlighter->add_keyword_color(E, control_flow_keyword_color);
} else {
- syntax_highlighter->add_keyword_color(E->get(), keyword_color);
+ syntax_highlighter->add_keyword_color(E, keyword_color);
}
}
@@ -144,8 +144,8 @@ void ShaderTextEditor::_load_theme_settings() {
const Color member_variable_color = EDITOR_GET("text_editor/highlighting/member_variable_color");
- for (List<String>::Element *E = built_ins.front(); E; E = E->next()) {
- syntax_highlighter->add_keyword_color(E->get(), member_variable_color);
+ for (const String &E : built_ins) {
+ syntax_highlighter->add_keyword_color(E, member_variable_color);
}
// Colorize comments.
@@ -158,6 +158,10 @@ void ShaderTextEditor::_load_theme_settings() {
text_editor->add_comment_delimiter("/*", "*/", false);
text_editor->add_comment_delimiter("//", "", true);
+ if (!text_editor->has_auto_brace_completion_open_key("/*")) {
+ text_editor->add_auto_brace_completion_pair("/*", "*/");
+ }
+
if (warnings_panel) {
// Warnings panel
warnings_panel->add_theme_font_override("normal_font", EditorNode::get_singleton()->get_gui_base()->get_theme_font(SNAME("main"), SNAME("EditorFonts")));
@@ -659,7 +663,7 @@ ShaderEditor::ShaderEditor(EditorNode *p_node) {
shader_editor->get_text_editor()->set_code_hint_draw_below(EditorSettings::get_singleton()->get("text_editor/completion/put_callhint_tooltip_below_current_line"));
- shader_editor->get_text_editor()->set_select_identifiers_on_hover(true);
+ shader_editor->get_text_editor()->set_symbol_lookup_on_click_enabled(true);
shader_editor->get_text_editor()->set_context_menu_enabled(false);
shader_editor->get_text_editor()->connect("gui_input", callable_mp(this, &ShaderEditor::_text_edit_gui_input));
@@ -770,7 +774,7 @@ ShaderEditor::ShaderEditor(EditorNode *p_node) {
disk_changed->add_child(vbc);
Label *dl = memnew(Label);
- dl->set_text(TTR("This shader has been modified on on disk.\nWhat action should be taken?"));
+ dl->set_text(TTR("This shader has been modified on disk.\nWhat action should be taken?"));
vbc->add_child(dl);
disk_changed->connect("confirmed", callable_mp(this, &ShaderEditor::_reload_shader_from_disk));
diff --git a/editor/plugins/shader_file_editor_plugin.cpp b/editor/plugins/shader_file_editor_plugin.cpp
index 66d2b36be1..1e62261244 100644
--- a/editor/plugins/shader_file_editor_plugin.cpp
+++ b/editor/plugins/shader_file_editor_plugin.cpp
@@ -55,7 +55,7 @@ void ShaderFileEditor::_version_selected(int p_option) {
RD::ShaderStage stage = RD::SHADER_STAGE_MAX;
int first_found = -1;
- Ref<RDShaderBytecode> bytecode = shader_file->get_bytecode(version_txt);
+ Ref<RDShaderSPIRV> bytecode = shader_file->get_spirv(version_txt);
ERR_FAIL_COND(bytecode.is_null());
for (int i = 0; i < RD::SHADER_STAGE_MAX; i++) {
@@ -142,7 +142,7 @@ void ShaderFileEditor::_update_options() {
Ref<Texture2D> icon;
- Ref<RDShaderBytecode> bytecode = shader_file->get_bytecode(version_list[i]);
+ Ref<RDShaderSPIRV> bytecode = shader_file->get_spirv(version_list[i]);
ERR_FAIL_COND(bytecode.is_null());
bool failed = false;
@@ -175,7 +175,7 @@ void ShaderFileEditor::_update_options() {
return;
}
- Ref<RDShaderBytecode> bytecode = shader_file->get_bytecode(current_version);
+ Ref<RDShaderSPIRV> bytecode = shader_file->get_spirv(current_version);
ERR_FAIL_COND(bytecode.is_null());
int first_valid = -1;
int current = -1;
diff --git a/editor/plugins/sprite_frames_editor_plugin.cpp b/editor/plugins/sprite_frames_editor_plugin.cpp
index 9bb8fbacbb..42f7d23da2 100644
--- a/editor/plugins/sprite_frames_editor_plugin.cpp
+++ b/editor/plugins/sprite_frames_editor_plugin.cpp
@@ -365,8 +365,8 @@ void SpriteFramesEditor::_file_load_request(const Vector<String> &p_path, int p_
int count = 0;
- for (List<Ref<Texture2D>>::Element *E = resources.front(); E; E = E->next()) {
- undo_redo->add_do_method(frames, "add_frame", edited_anim, E->get(), p_at_pos == -1 ? -1 : p_at_pos + count);
+ for (const Ref<Texture2D> &E : resources) {
+ undo_redo->add_do_method(frames, "add_frame", edited_anim, E, p_at_pos == -1 ? -1 : p_at_pos + count);
undo_redo->add_undo_method(frames, "remove_frame", edited_anim, p_at_pos == -1 ? fc : p_at_pos);
count++;
}
@@ -624,10 +624,10 @@ void SpriteFramesEditor::_animation_name_edited() {
undo_redo->add_do_method(frames, "rename_animation", edited_anim, name);
undo_redo->add_undo_method(frames, "rename_animation", name, edited_anim);
- for (List<Node *>::Element *E = nodes.front(); E; E = E->next()) {
- String current = E->get()->call("get_animation");
- undo_redo->add_do_method(E->get(), "set_animation", name);
- undo_redo->add_undo_method(E->get(), "set_animation", edited_anim);
+ for (Node *E : nodes) {
+ String current = E->call("get_animation");
+ undo_redo->add_do_method(E, "set_animation", name);
+ undo_redo->add_undo_method(E, "set_animation", edited_anim);
}
undo_redo->add_do_method(this, "_update_library");
@@ -655,10 +655,10 @@ void SpriteFramesEditor::_animation_add() {
undo_redo->add_do_method(this, "_update_library");
undo_redo->add_undo_method(this, "_update_library");
- for (List<Node *>::Element *E = nodes.front(); E; E = E->next()) {
- String current = E->get()->call("get_animation");
- undo_redo->add_do_method(E->get(), "set_animation", name);
- undo_redo->add_undo_method(E->get(), "set_animation", current);
+ for (Node *E : nodes) {
+ String current = E->call("get_animation");
+ undo_redo->add_do_method(E, "set_animation", name);
+ undo_redo->add_undo_method(E, "set_animation", current);
}
edited_anim = name;
@@ -788,8 +788,8 @@ void SpriteFramesEditor::_update_library(bool p_skip_selector) {
anim_names.sort_custom<StringName::AlphCompare>();
- for (List<StringName>::Element *E = anim_names.front(); E; E = E->next()) {
- String name = E->get();
+ for (const StringName &E : anim_names) {
+ String name = E;
TreeItem *it = animations->create_item(anim_root);
@@ -798,7 +798,7 @@ void SpriteFramesEditor::_update_library(bool p_skip_selector) {
it->set_text(0, name);
it->set_editable(0, true);
- if (E->get() == edited_anim) {
+ if (E == edited_anim) {
it->select(0);
}
}
diff --git a/editor/plugins/texture_editor_plugin.cpp b/editor/plugins/texture_editor_plugin.cpp
index 4029d6785c..44db06bcfd 100644
--- a/editor/plugins/texture_editor_plugin.cpp
+++ b/editor/plugins/texture_editor_plugin.cpp
@@ -36,9 +36,30 @@ TextureRect *TexturePreview::get_texture_display() {
return texture_display;
}
+void TexturePreview::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE:
+ case NOTIFICATION_THEME_CHANGED: {
+ if (!is_inside_tree()) {
+ // TODO: This is a workaround because `NOTIFICATION_THEME_CHANGED`
+ // is getting called for some reason when the `TexturePreview` is
+ // getting destroyed, which causes `get_theme_font()` to return `nullptr`.
+ // See https://github.com/godotengine/godot/issues/50743.
+ break;
+ }
+
+ if (metadata_label) {
+ Ref<Font> metadata_label_font = get_theme_font(SNAME("expression"), SNAME("EditorFonts"));
+ metadata_label->add_theme_font_override("font", metadata_label_font);
+ }
+
+ checkerboard->set_texture(get_theme_icon(SNAME("Checkerboard"), SNAME("EditorIcons")));
+ } break;
+ }
+}
+
TexturePreview::TexturePreview(Ref<Texture2D> p_texture, bool p_show_metadata) {
- TextureRect *checkerboard = memnew(TextureRect);
- checkerboard->set_texture(get_theme_icon("Checkerboard", "EditorIcons"));
+ checkerboard = memnew(TextureRect);
checkerboard->set_stretch_mode(TextureRect::STRETCH_TILE);
checkerboard->set_texture_repeat(CanvasItem::TEXTURE_REPEAT_ENABLED);
checkerboard->set_custom_minimum_size(Size2(0.0, 256.0) * EDSCALE);
@@ -52,7 +73,7 @@ TexturePreview::TexturePreview(Ref<Texture2D> p_texture, bool p_show_metadata) {
add_child(texture_display);
if (p_show_metadata) {
- Label *metadata_label = memnew(Label);
+ metadata_label = memnew(Label);
String format;
if (Object::cast_to<ImageTexture>(*p_texture)) {
@@ -65,16 +86,14 @@ TexturePreview::TexturePreview(Ref<Texture2D> p_texture, bool p_show_metadata) {
metadata_label->set_text(itos(p_texture->get_width()) + "x" + itos(p_texture->get_height()) + " " + format);
- metadata_label->add_theme_font_size_override("font_size", 16 * EDSCALE);
- metadata_label->add_theme_color_override("font_outline_color", Color::named("black"));
- metadata_label->add_theme_constant_override("outline_size", 2 * EDSCALE);
- Ref<Font> metadata_label_font = get_theme_font("expression", "EditorFonts");
- metadata_label->add_theme_font_override("font", metadata_label_font);
-
// It's okay that these colors are static since the grid color is static too.
metadata_label->add_theme_color_override("font_color", Color::named("white"));
metadata_label->add_theme_color_override("font_color_shadow", Color::named("black"));
+ metadata_label->add_theme_font_size_override("font_size", 16 * EDSCALE);
+ metadata_label->add_theme_color_override("font_outline_color", Color::named("black"));
+ metadata_label->add_theme_constant_override("outline_size", 2 * EDSCALE);
+
metadata_label->add_theme_constant_override("shadow_as_outline", 1);
metadata_label->set_h_size_flags(Control::SIZE_SHRINK_END);
metadata_label->set_v_size_flags(Control::SIZE_SHRINK_END);
diff --git a/editor/plugins/texture_editor_plugin.h b/editor/plugins/texture_editor_plugin.h
index 165090be93..36a5513ea6 100644
--- a/editor/plugins/texture_editor_plugin.h
+++ b/editor/plugins/texture_editor_plugin.h
@@ -39,7 +39,13 @@ class TexturePreview : public MarginContainer {
GDCLASS(TexturePreview, MarginContainer);
private:
- TextureRect *texture_display;
+ TextureRect *texture_display = nullptr;
+
+ TextureRect *checkerboard = nullptr;
+ Label *metadata_label = nullptr;
+
+protected:
+ void _notification(int p_what);
public:
TextureRect *get_texture_display();
diff --git a/editor/plugins/texture_region_editor_plugin.cpp b/editor/plugins/texture_region_editor_plugin.cpp
index e323f1571d..b277f2ab42 100644
--- a/editor/plugins/texture_region_editor_plugin.cpp
+++ b/editor/plugins/texture_region_editor_plugin.cpp
@@ -144,8 +144,7 @@ void TextureRegionEditor::_region_draw() {
}
}
} else if (snap_mode == SNAP_AUTOSLICE) {
- for (List<Rect2>::Element *E = autoslice_cache.front(); E; E = E->next()) {
- Rect2 r = E->get();
+ for (const Rect2 &r : autoslice_cache) {
Vector2 endpoints[4] = {
mtx.basis_xform(r.position),
mtx.basis_xform(r.position + Vector2(r.size.x, 0)),
@@ -328,9 +327,9 @@ void TextureRegionEditor::_region_input(const Ref<InputEvent> &p_input) {
}
if (edited_margin < 0 && snap_mode == SNAP_AUTOSLICE) {
Vector2 point = mtx.affine_inverse().xform(Vector2(mb->get_position().x, mb->get_position().y));
- for (List<Rect2>::Element *E = autoslice_cache.front(); E; E = E->next()) {
- if (E->get().has_point(point)) {
- rect = E->get();
+ for (const Rect2 &E : autoslice_cache) {
+ if (E.has_point(point)) {
+ rect = E;
if (Input::get_singleton()->is_key_pressed(KEY_CTRL) && !(Input::get_singleton()->is_key_pressed(KEY_SHIFT | KEY_ALT))) {
Rect2 r;
if (node_sprite) {
@@ -749,12 +748,12 @@ void TextureRegionEditor::_update_autoslice() {
for (int x = 0; x < texture->get_width(); x++) {
if (texture->is_pixel_opaque(x, y)) {
bool found = false;
- for (List<Rect2>::Element *E = autoslice_cache.front(); E; E = E->next()) {
- Rect2 grown = E->get().grow(1.5);
+ for (Rect2 &E : autoslice_cache) {
+ Rect2 grown = E.grow(1.5);
if (grown.has_point(Point2(x, y))) {
- E->get().expand_to(Point2(x, y));
- E->get().expand_to(Point2(x + 1, y + 1));
- x = E->get().position.x + E->get().size.x - 1;
+ E.expand_to(Point2(x, y));
+ E.expand_to(Point2(x + 1, y + 1));
+ x = E.position.x + E.size.x - 1;
bool merged = true;
while (merged) {
merged = false;
@@ -764,12 +763,12 @@ void TextureRegionEditor::_update_autoslice() {
autoslice_cache.erase(F->prev());
queue_erase = false;
}
- if (F == E) {
+ if (F->get() == E) {
continue;
}
- if (E->get().grow(1).intersects(F->get())) {
- E->get().expand_to(F->get().position);
- E->get().expand_to(F->get().position + F->get().size);
+ if (E.grow(1).intersects(F->get())) {
+ E.expand_to(F->get().position);
+ E.expand_to(F->get().position + F->get().size);
if (F->prev()) {
F = F->prev();
autoslice_cache.erase(F->next());
diff --git a/editor/plugins/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp
index 9338ea6ddd..0929a629f8 100644
--- a/editor/plugins/theme_editor_plugin.cpp
+++ b/editor/plugins/theme_editor_plugin.cpp
@@ -65,8 +65,8 @@ void ThemeItemImportTree::_update_items_tree() {
tree_icon_items.clear();
tree_stylebox_items.clear();
- for (List<StringName>::Element *E = types.front(); E; E = E->next()) {
- String type_name = (String)E->get();
+ for (const StringName &E : types) {
+ String type_name = (String)E;
TreeItem *type_node = import_items_tree->create_item(root);
type_node->set_meta("_can_be_imported", false);
@@ -89,12 +89,12 @@ void ThemeItemImportTree::_update_items_tree() {
names.clear();
filtered_names.clear();
- base_theme->get_theme_item_list(dt, E->get(), &names);
+ base_theme->get_theme_item_list(dt, E, &names);
bool data_type_has_filtered_items = false;
- for (List<StringName>::Element *F = names.front(); F; F = F->next()) {
- String item_name = (String)F->get();
+ for (const StringName &F : names) {
+ String item_name = (String)F;
bool is_item_matching_filter = (item_name.findn(filter_text) > -1);
if (!filter_text.is_empty() && !is_matching_filter && !is_item_matching_filter) {
continue;
@@ -105,7 +105,7 @@ void ThemeItemImportTree::_update_items_tree() {
has_filtered_items = true;
data_type_has_filtered_items = true;
}
- filtered_names.push_back(F->get());
+ filtered_names.push_back(F);
}
if (filtered_names.size() == 0) {
@@ -182,10 +182,10 @@ void ThemeItemImportTree::_update_items_tree() {
bool data_type_any_checked_with_data = false;
filtered_names.sort_custom<StringName::AlphCompare>();
- for (List<StringName>::Element *F = filtered_names.front(); F; F = F->next()) {
+ for (const StringName &F : filtered_names) {
TreeItem *item_node = import_items_tree->create_item(data_type_node);
item_node->set_meta("_can_be_imported", true);
- item_node->set_text(0, F->get());
+ item_node->set_text(0, F);
item_node->set_cell_mode(IMPORT_ITEM, TreeItem::CELL_MODE_CHECK);
item_node->set_checked(IMPORT_ITEM, false);
item_node->set_editable(IMPORT_ITEM, true);
@@ -754,7 +754,7 @@ void ThemeItemImportTree::_import_selected() {
return;
}
- // Prevent changes from immediatelly being reported while the operation is still ongoing.
+ // Prevent changes from immediately being reported while the operation is still ongoing.
edited_theme->_freeze_change_propagation();
ProgressDialog::get_singleton()->add_task("import_theme_items", TTR("Importing Theme Items"), selected_items.size() + 2);
@@ -1236,16 +1236,16 @@ void ThemeItemEditorDialog::_update_edit_types() {
bool item_reselected = false;
edit_type_list->clear();
int e_idx = 0;
- for (List<StringName>::Element *E = theme_types.front(); E; E = E->next()) {
+ for (const StringName &E : theme_types) {
Ref<Texture2D> item_icon;
- if (E->get() == "") {
+ if (E == "") {
item_icon = get_theme_icon(SNAME("NodeDisabled"), SNAME("EditorIcons"));
} else {
- item_icon = EditorNode::get_singleton()->get_class_icon(E->get(), "NodeDisabled");
+ item_icon = EditorNode::get_singleton()->get_class_icon(E, "NodeDisabled");
}
- edit_type_list->add_item(E->get(), item_icon);
+ edit_type_list->add_item(E, item_icon);
- if (E->get() == edited_item_type) {
+ if (E == edited_item_type) {
edit_type_list->select(e_idx);
item_reselected = true;
}
@@ -1318,9 +1318,9 @@ void ThemeItemEditorDialog::_update_edit_item_tree(String p_item_type) {
color_root->add_button(0, get_theme_icon(SNAME("Clear"), SNAME("EditorIcons")), ITEMS_TREE_REMOVE_DATA_TYPE, false, TTR("Remove All Color Items"));
names.sort_custom<StringName::AlphCompare>();
- for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
+ for (const StringName &E : names) {
TreeItem *item = edit_items_tree->create_item(color_root);
- item->set_text(0, E->get());
+ item->set_text(0, E);
item->add_button(0, get_theme_icon(SNAME("Edit"), SNAME("EditorIcons")), ITEMS_TREE_RENAME_ITEM, false, TTR("Rename Item"));
item->add_button(0, get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")), ITEMS_TREE_REMOVE_ITEM, false, TTR("Remove Item"));
}
@@ -1339,9 +1339,9 @@ void ThemeItemEditorDialog::_update_edit_item_tree(String p_item_type) {
constant_root->add_button(0, get_theme_icon(SNAME("Clear"), SNAME("EditorIcons")), ITEMS_TREE_REMOVE_DATA_TYPE, false, TTR("Remove All Constant Items"));
names.sort_custom<StringName::AlphCompare>();
- for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
+ for (const StringName &E : names) {
TreeItem *item = edit_items_tree->create_item(constant_root);
- item->set_text(0, E->get());
+ item->set_text(0, E);
item->add_button(0, get_theme_icon(SNAME("Edit"), SNAME("EditorIcons")), ITEMS_TREE_RENAME_ITEM, false, TTR("Rename Item"));
item->add_button(0, get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")), ITEMS_TREE_REMOVE_ITEM, false, TTR("Remove Item"));
}
@@ -1360,9 +1360,9 @@ void ThemeItemEditorDialog::_update_edit_item_tree(String p_item_type) {
font_root->add_button(0, get_theme_icon(SNAME("Clear"), SNAME("EditorIcons")), ITEMS_TREE_REMOVE_DATA_TYPE, false, TTR("Remove All Font Items"));
names.sort_custom<StringName::AlphCompare>();
- for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
+ for (const StringName &E : names) {
TreeItem *item = edit_items_tree->create_item(font_root);
- item->set_text(0, E->get());
+ item->set_text(0, E);
item->add_button(0, get_theme_icon(SNAME("Edit"), SNAME("EditorIcons")), ITEMS_TREE_RENAME_ITEM, false, TTR("Rename Item"));
item->add_button(0, get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")), ITEMS_TREE_REMOVE_ITEM, false, TTR("Remove Item"));
}
@@ -1381,9 +1381,9 @@ void ThemeItemEditorDialog::_update_edit_item_tree(String p_item_type) {
font_size_root->add_button(0, get_theme_icon(SNAME("Clear"), SNAME("EditorIcons")), ITEMS_TREE_REMOVE_DATA_TYPE, false, TTR("Remove All Font Size Items"));
names.sort_custom<StringName::AlphCompare>();
- for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
+ for (const StringName &E : names) {
TreeItem *item = edit_items_tree->create_item(font_size_root);
- item->set_text(0, E->get());
+ item->set_text(0, E);
item->add_button(0, get_theme_icon(SNAME("Edit"), SNAME("EditorIcons")), ITEMS_TREE_RENAME_ITEM, false, TTR("Rename Item"));
item->add_button(0, get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")), ITEMS_TREE_REMOVE_ITEM, false, TTR("Remove Item"));
}
@@ -1402,9 +1402,9 @@ void ThemeItemEditorDialog::_update_edit_item_tree(String p_item_type) {
icon_root->add_button(0, get_theme_icon(SNAME("Clear"), SNAME("EditorIcons")), ITEMS_TREE_REMOVE_DATA_TYPE, false, TTR("Remove All Icon Items"));
names.sort_custom<StringName::AlphCompare>();
- for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
+ for (const StringName &E : names) {
TreeItem *item = edit_items_tree->create_item(icon_root);
- item->set_text(0, E->get());
+ item->set_text(0, E);
item->add_button(0, get_theme_icon(SNAME("Edit"), SNAME("EditorIcons")), ITEMS_TREE_RENAME_ITEM, false, TTR("Rename Item"));
item->add_button(0, get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")), ITEMS_TREE_REMOVE_ITEM, false, TTR("Remove Item"));
}
@@ -1423,9 +1423,9 @@ void ThemeItemEditorDialog::_update_edit_item_tree(String p_item_type) {
stylebox_root->add_button(0, get_theme_icon(SNAME("Clear"), SNAME("EditorIcons")), ITEMS_TREE_REMOVE_DATA_TYPE, false, TTR("Remove All StyleBox Items"));
names.sort_custom<StringName::AlphCompare>();
- for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
+ for (const StringName &E : names) {
TreeItem *item = edit_items_tree->create_item(stylebox_root);
- item->set_text(0, E->get());
+ item->set_text(0, E);
item->add_button(0, get_theme_icon(SNAME("Edit"), SNAME("EditorIcons")), ITEMS_TREE_RENAME_ITEM, false, TTR("Rename Item"));
item->add_button(0, get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")), ITEMS_TREE_REMOVE_ITEM, false, TTR("Remove Item"));
}
@@ -1503,12 +1503,12 @@ void ThemeItemEditorDialog::_add_theme_item(Theme::DataType p_data_type, String
void ThemeItemEditorDialog::_remove_data_type_items(Theme::DataType p_data_type, String p_item_type) {
List<StringName> names;
- // Prevent changes from immediatelly being reported while the operation is still ongoing.
+ // Prevent changes from immediately being reported while the operation is still ongoing.
edited_theme->_freeze_change_propagation();
edited_theme->get_theme_item_list(p_data_type, p_item_type, &names);
- for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
- edited_theme->clear_theme_item(p_data_type, E->get(), p_item_type);
+ for (const StringName &E : names) {
+ edited_theme->clear_theme_item(p_data_type, E, p_item_type);
}
// Allow changes to be reported now that the operation is finished.
@@ -1518,7 +1518,7 @@ void ThemeItemEditorDialog::_remove_data_type_items(Theme::DataType p_data_type,
void ThemeItemEditorDialog::_remove_class_items() {
List<StringName> names;
- // Prevent changes from immediatelly being reported while the operation is still ongoing.
+ // Prevent changes from immediately being reported while the operation is still ongoing.
edited_theme->_freeze_change_propagation();
for (int dt = 0; dt < Theme::DATA_TYPE_MAX; dt++) {
@@ -1526,9 +1526,9 @@ void ThemeItemEditorDialog::_remove_class_items() {
names.clear();
Theme::get_default()->get_theme_item_list(data_type, edited_item_type, &names);
- for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
- if (edited_theme->has_theme_item_nocheck(data_type, E->get(), edited_item_type)) {
- edited_theme->clear_theme_item(data_type, E->get(), edited_item_type);
+ for (const StringName &E : names) {
+ if (edited_theme->has_theme_item_nocheck(data_type, E, edited_item_type)) {
+ edited_theme->clear_theme_item(data_type, E, edited_item_type);
}
}
}
@@ -1542,7 +1542,7 @@ void ThemeItemEditorDialog::_remove_class_items() {
void ThemeItemEditorDialog::_remove_custom_items() {
List<StringName> names;
- // Prevent changes from immediatelly being reported while the operation is still ongoing.
+ // Prevent changes from immediately being reported while the operation is still ongoing.
edited_theme->_freeze_change_propagation();
for (int dt = 0; dt < Theme::DATA_TYPE_MAX; dt++) {
@@ -1550,9 +1550,9 @@ void ThemeItemEditorDialog::_remove_custom_items() {
names.clear();
edited_theme->get_theme_item_list(data_type, edited_item_type, &names);
- for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
- if (!Theme::get_default()->has_theme_item_nocheck(data_type, E->get(), edited_item_type)) {
- edited_theme->clear_theme_item(data_type, E->get(), edited_item_type);
+ for (const StringName &E : names) {
+ if (!Theme::get_default()->has_theme_item_nocheck(data_type, E, edited_item_type)) {
+ edited_theme->clear_theme_item(data_type, E, edited_item_type);
}
}
}
@@ -1566,7 +1566,7 @@ void ThemeItemEditorDialog::_remove_custom_items() {
void ThemeItemEditorDialog::_remove_all_items() {
List<StringName> names;
- // Prevent changes from immediatelly being reported while the operation is still ongoing.
+ // Prevent changes from immediately being reported while the operation is still ongoing.
edited_theme->_freeze_change_propagation();
for (int dt = 0; dt < Theme::DATA_TYPE_MAX; dt++) {
@@ -1574,8 +1574,8 @@ void ThemeItemEditorDialog::_remove_all_items() {
names.clear();
edited_theme->get_theme_item_list(data_type, edited_item_type, &names);
- for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
- edited_theme->clear_theme_item(data_type, E->get(), edited_item_type);
+ for (const StringName &E : names) {
+ edited_theme->clear_theme_item(data_type, E, edited_item_type);
}
}
@@ -1927,8 +1927,8 @@ ThemeItemEditorDialog::ThemeItemEditorDialog() {
import_another_theme_dialog->set_title(TTR("Select Another Theme Resource:"));
List<String> ext;
ResourceLoader::get_recognized_extensions_for_type("Theme", &ext);
- for (List<String>::Element *E = ext.front(); E; E = E->next()) {
- import_another_theme_dialog->add_filter("*." + E->get() + "; Theme Resource");
+ for (const String &E : ext) {
+ import_another_theme_dialog->add_filter("*." + E + "; Theme Resource");
}
import_another_file_hb->add_child(import_another_theme_dialog);
import_another_theme_dialog->connect("file_selected", callable_mp(this, &ThemeItemEditorDialog::_select_another_theme_cbk));
@@ -1969,26 +1969,26 @@ void ThemeTypeDialog::_update_add_type_options(const String &p_filter) {
names.sort_custom<StringName::AlphCompare>();
Vector<StringName> unique_names;
- for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
+ for (const StringName &E : names) {
// Filter out undesired values.
- if (!p_filter.is_subsequence_ofi(String(E->get()))) {
+ if (!p_filter.is_subsequence_ofi(String(E))) {
continue;
}
// Skip duplicate values.
- if (unique_names.has(E->get())) {
+ if (unique_names.has(E)) {
continue;
}
- unique_names.append(E->get());
+ unique_names.append(E);
Ref<Texture2D> item_icon;
- if (E->get() == "") {
+ if (E == "") {
item_icon = get_theme_icon(SNAME("NodeDisabled"), SNAME("EditorIcons"));
} else {
- item_icon = EditorNode::get_singleton()->get_class_icon(E->get(), "NodeDisabled");
+ item_icon = EditorNode::get_singleton()->get_class_icon(E, "NodeDisabled");
}
- add_type_options->add_item(E->get(), item_icon);
+ add_type_options->add_item(E, item_icon);
}
}
@@ -2132,16 +2132,16 @@ void ThemeTypeEditor::_update_type_list() {
bool item_reselected = false;
int e_idx = 0;
- for (List<StringName>::Element *E = theme_types.front(); E; E = E->next()) {
+ for (const StringName &E : theme_types) {
Ref<Texture2D> item_icon;
- if (E->get() == "") {
+ if (E == "") {
item_icon = get_theme_icon(SNAME("NodeDisabled"), SNAME("EditorIcons"));
} else {
- item_icon = EditorNode::get_singleton()->get_class_icon(E->get(), "NodeDisabled");
+ item_icon = EditorNode::get_singleton()->get_class_icon(E, "NodeDisabled");
}
- theme_type_list->add_icon_item(item_icon, E->get());
+ theme_type_list->add_icon_item(item_icon, E);
- if (E->get() == edited_type) {
+ if (E == edited_type) {
theme_type_list->select(e_idx);
item_reselected = true;
}
@@ -2182,8 +2182,8 @@ OrderedHashMap<StringName, bool> ThemeTypeEditor::_get_type_items(String p_type_
(Theme::get_default().operator->()->*get_list_func)(default_type, &names);
names.sort_custom<StringName::AlphCompare>();
- for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
- items[E->get()] = false;
+ for (const StringName &E : names) {
+ items[E] = false;
}
}
@@ -2191,8 +2191,8 @@ OrderedHashMap<StringName, bool> ThemeTypeEditor::_get_type_items(String p_type_
names.clear();
(edited_theme.operator->()->*get_list_func)(p_type_name, &names);
names.sort_custom<StringName::AlphCompare>();
- for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
- items[E->get()] = true;
+ for (const StringName &E : names) {
+ items[E] = true;
}
}
@@ -2203,8 +2203,8 @@ OrderedHashMap<StringName, bool> ThemeTypeEditor::_get_type_items(String p_type_
keys.sort_custom<StringName::AlphCompare>();
OrderedHashMap<StringName, bool> ordered_items;
- for (List<StringName>::Element *E = keys.front(); E; E = E->next()) {
- ordered_items[E->get()] = items[E->get()];
+ for (const StringName &E : keys) {
+ ordered_items[E] = items[E];
}
return ordered_items;
@@ -2574,60 +2574,60 @@ void ThemeTypeEditor::_add_default_type_items() {
}
updating = true;
- // Prevent changes from immediatelly being reported while the operation is still ongoing.
+ // Prevent changes from immediately being reported while the operation is still ongoing.
edited_theme->_freeze_change_propagation();
{
names.clear();
Theme::get_default()->get_icon_list(default_type, &names);
- for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
- if (!edited_theme->has_icon(E->get(), edited_type)) {
- edited_theme->set_icon(E->get(), edited_type, Ref<Texture2D>());
+ for (const StringName &E : names) {
+ if (!edited_theme->has_icon(E, edited_type)) {
+ edited_theme->set_icon(E, edited_type, Ref<Texture2D>());
}
}
}
{
names.clear();
Theme::get_default()->get_stylebox_list(default_type, &names);
- for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
- if (!edited_theme->has_stylebox(E->get(), edited_type)) {
- edited_theme->set_stylebox(E->get(), edited_type, Ref<StyleBox>());
+ for (const StringName &E : names) {
+ if (!edited_theme->has_stylebox(E, edited_type)) {
+ edited_theme->set_stylebox(E, edited_type, Ref<StyleBox>());
}
}
}
{
names.clear();
Theme::get_default()->get_font_list(default_type, &names);
- for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
- if (!edited_theme->has_font(E->get(), edited_type)) {
- edited_theme->set_font(E->get(), edited_type, Ref<Font>());
+ for (const StringName &E : names) {
+ if (!edited_theme->has_font(E, edited_type)) {
+ edited_theme->set_font(E, edited_type, Ref<Font>());
}
}
}
{
names.clear();
Theme::get_default()->get_font_size_list(default_type, &names);
- for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
- if (!edited_theme->has_font_size(E->get(), edited_type)) {
- edited_theme->set_font_size(E->get(), edited_type, Theme::get_default()->get_font_size(E->get(), default_type));
+ for (const StringName &E : names) {
+ if (!edited_theme->has_font_size(E, edited_type)) {
+ edited_theme->set_font_size(E, edited_type, Theme::get_default()->get_font_size(E, default_type));
}
}
}
{
names.clear();
Theme::get_default()->get_color_list(default_type, &names);
- for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
- if (!edited_theme->has_color(E->get(), edited_type)) {
- edited_theme->set_color(E->get(), edited_type, Theme::get_default()->get_color(E->get(), default_type));
+ for (const StringName &E : names) {
+ if (!edited_theme->has_color(E, edited_type)) {
+ edited_theme->set_color(E, edited_type, Theme::get_default()->get_color(E, default_type));
}
}
}
{
names.clear();
Theme::get_default()->get_constant_list(default_type, &names);
- for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
- if (!edited_theme->has_constant(E->get(), edited_type)) {
- edited_theme->set_constant(E->get(), edited_type, Theme::get_default()->get_constant(E->get(), default_type));
+ for (const StringName &E : names) {
+ if (!edited_theme->has_constant(E, edited_type)) {
+ edited_theme->set_constant(E, edited_type, Theme::get_default()->get_constant(E, default_type));
}
}
}
@@ -2876,18 +2876,18 @@ void ThemeTypeEditor::_update_stylebox_from_leading() {
return;
}
- // Prevent changes from immediatelly being reported while the operation is still ongoing.
+ // Prevent changes from immediately being reported while the operation is still ongoing.
edited_theme->_freeze_change_propagation();
List<StringName> names;
edited_theme->get_stylebox_list(edited_type, &names);
List<Ref<StyleBox>> styleboxes;
- for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
- if (E->get() == leading_stylebox.item_name) {
+ for (const StringName &E : names) {
+ if (E == leading_stylebox.item_name) {
continue;
}
- Ref<StyleBox> sb = edited_theme->get_stylebox(E->get(), edited_type);
+ Ref<StyleBox> sb = edited_theme->get_stylebox(E, edited_type);
if (sb->get_class() == leading_stylebox.stylebox->get_class()) {
styleboxes.push_back(sb);
}
@@ -2895,20 +2895,20 @@ void ThemeTypeEditor::_update_stylebox_from_leading() {
List<PropertyInfo> props;
leading_stylebox.stylebox->get_property_list(&props);
- for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
- if (!(E->get().usage & PROPERTY_USAGE_STORAGE)) {
+ for (const PropertyInfo &E : props) {
+ if (!(E.usage & PROPERTY_USAGE_STORAGE)) {
continue;
}
- Variant value = leading_stylebox.stylebox->get(E->get().name);
- Variant ref_value = leading_stylebox.ref_stylebox->get(E->get().name);
+ Variant value = leading_stylebox.stylebox->get(E.name);
+ Variant ref_value = leading_stylebox.ref_stylebox->get(E.name);
if (value == ref_value) {
continue;
}
- for (List<Ref<StyleBox>>::Element *F = styleboxes.front(); F; F = F->next()) {
- Ref<StyleBox> sb = F->get();
- sb->set(E->get().name, value);
+ for (const Ref<StyleBox> &F : styleboxes) {
+ Ref<StyleBox> sb = F;
+ sb->set(E.name, value);
}
}
@@ -3112,7 +3112,7 @@ void ThemeEditor::edit(const Ref<Theme> &p_theme) {
preview_tab->set_preview_theme(p_theme);
}
- theme_name->set_text(TTR("Theme") + ": " + theme->get_path().get_file());
+ theme_name->set_text(TTR("Theme:") + " " + theme->get_path().get_file());
}
Ref<Theme> ThemeEditor::get_edited_theme() {
@@ -3230,7 +3230,7 @@ ThemeEditor::ThemeEditor() {
add_child(top_menu);
theme_name = memnew(Label);
- theme_name->set_text(TTR("Theme") + ": ");
+ theme_name->set_text(TTR("Theme:"));
theme_name->set_theme_type_variation("HeaderSmall");
top_menu->add_child(theme_name);
@@ -3301,8 +3301,8 @@ ThemeEditor::ThemeEditor() {
preview_scene_dialog->set_title(TTR("Select UI Scene:"));
List<String> ext;
ResourceLoader::get_recognized_extensions_for_type("PackedScene", &ext);
- for (List<String>::Element *E = ext.front(); E; E = E->next()) {
- preview_scene_dialog->add_filter("*." + E->get() + "; Scene");
+ for (const String &E : ext) {
+ preview_scene_dialog->add_filter("*." + E + "; Scene");
}
main_hs->add_child(preview_scene_dialog);
preview_scene_dialog->connect("file_selected", callable_mp(this, &ThemeEditor::_preview_scene_dialog_cbk));
@@ -3343,12 +3343,12 @@ bool ThemeEditorPlugin::handles(Object *p_node) const {
List<StringName> names;
edited_theme->get_font_type_list(&types);
- for (List<StringName>::Element *E = types.front(); E; E = E->next()) {
+ for (const StringName &E : types) {
names.clear();
- edited_theme->get_font_list(E->get(), &names);
+ edited_theme->get_font_list(E, &names);
- for (List<StringName>::Element *F = names.front(); F; F = F->next()) {
- if (font_item == edited_theme->get_font(F->get(), E->get())) {
+ for (const StringName &F : names) {
+ if (font_item == edited_theme->get_font(F, E)) {
belongs_to_theme = true;
break;
}
@@ -3360,12 +3360,12 @@ bool ThemeEditorPlugin::handles(Object *p_node) const {
List<StringName> names;
edited_theme->get_stylebox_type_list(&types);
- for (List<StringName>::Element *E = types.front(); E; E = E->next()) {
+ for (const StringName &E : types) {
names.clear();
- edited_theme->get_stylebox_list(E->get(), &names);
+ edited_theme->get_stylebox_list(E, &names);
- for (List<StringName>::Element *F = names.front(); F; F = F->next()) {
- if (stylebox_item == edited_theme->get_stylebox(F->get(), E->get())) {
+ for (const StringName &F : names) {
+ if (stylebox_item == edited_theme->get_stylebox(F, E)) {
belongs_to_theme = true;
break;
}
@@ -3377,12 +3377,12 @@ bool ThemeEditorPlugin::handles(Object *p_node) const {
List<StringName> names;
edited_theme->get_icon_type_list(&types);
- for (List<StringName>::Element *E = types.front(); E; E = E->next()) {
+ for (const StringName &E : types) {
names.clear();
- edited_theme->get_icon_list(E->get(), &names);
+ edited_theme->get_icon_list(E, &names);
- for (List<StringName>::Element *F = names.front(); F; F = F->next()) {
- if (icon_item == edited_theme->get_icon(F->get(), E->get())) {
+ for (const StringName &F : names) {
+ if (icon_item == edited_theme->get_icon(F, E)) {
belongs_to_theme = true;
break;
}
diff --git a/editor/plugins/tiles/atlas_merging_dialog.cpp b/editor/plugins/tiles/atlas_merging_dialog.cpp
index bbafc7802b..d54906c98c 100644
--- a/editor/plugins/tiles/atlas_merging_dialog.cpp
+++ b/editor/plugins/tiles/atlas_merging_dialog.cpp
@@ -154,7 +154,7 @@ void AtlasMergingDialog::_merge_confirmed(String p_path) {
Ref<Texture2D> new_texture_resource = ResourceLoader::load(p_path, "Texture2D");
merged->set_texture(new_texture_resource);
- undo_redo->create_action("Merge TileSetAtlasSource");
+ undo_redo->create_action(TTR("Merge TileSetAtlasSource"));
int next_id = tile_set->get_next_source_id();
undo_redo->add_do_method(*tile_set, "add_source", merged, next_id);
undo_redo->add_undo_method(*tile_set, "remove_source", next_id);
diff --git a/editor/plugins/tiles/tile_atlas_view.cpp b/editor/plugins/tiles/tile_atlas_view.cpp
index 84e40e2ffa..13f04cb804 100644
--- a/editor/plugins/tiles/tile_atlas_view.cpp
+++ b/editor/plugins/tiles/tile_atlas_view.cpp
@@ -32,6 +32,7 @@
#include "core/input/input.h"
#include "core/os/keyboard.h"
+#include "scene/2d/tile_map.h"
#include "scene/gui/box_container.h"
#include "scene/gui/label.h"
#include "scene/gui/panel.h"
@@ -259,7 +260,7 @@ void TileAtlasView::_draw_base_tiles() {
Vector2i offset_pos = (margins + (atlas_coords * texture_region_size) + tile_set_atlas_source->get_tile_texture_region(atlas_coords).size / 2 + tile_set_atlas_source->get_tile_effective_texture_offset(atlas_coords, 0));
// Draw the tile.
- TileSetPluginAtlasRendering::draw_tile(base_tiles_draw->get_canvas_item(), offset_pos, tile_set, source_id, atlas_coords, 0);
+ TileMap::draw_tile(base_tiles_draw->get_canvas_item(), offset_pos, tile_set, source_id, atlas_coords, 0);
}
}
}
@@ -375,7 +376,7 @@ void TileAtlasView::_draw_alternatives() {
}
// Draw the tile.
- TileSetPluginAtlasRendering::draw_tile(alternatives_draw->get_canvas_item(), offset_pos, tile_set, source_id, atlas_coords, alternative_id);
+ TileMap::draw_tile(alternatives_draw->get_canvas_item(), offset_pos, tile_set, source_id, atlas_coords, alternative_id);
// Increment the x position.
current_pos.x += transposed ? texture_region.size.y : texture_region.size.x;
diff --git a/editor/plugins/tiles/tile_data_editors.cpp b/editor/plugins/tiles/tile_data_editors.cpp
index dd2922286f..bab55df65a 100644
--- a/editor/plugins/tiles/tile_data_editors.cpp
+++ b/editor/plugins/tiles/tile_data_editors.cpp
@@ -1244,7 +1244,7 @@ void TileDataCollisionEditor::_polygons_changed() {
}
}
- // Remove uneeded properties and their editors.
+ // Remove unneeded properties and their editors.
for (int i = polygon_editor->get_polygon_count(); dummy_object->has_dummy_property(vformat("polygon_%d_one_way", i)); i++) {
dummy_object->remove_dummy_property(vformat("polygon_%d_one_way", i));
}
diff --git a/editor/plugins/tiles/tile_data_editors.h b/editor/plugins/tiles/tile_data_editors.h
index 781f26cc02..99998dc779 100644
--- a/editor/plugins/tiles/tile_data_editors.h
+++ b/editor/plugins/tiles/tile_data_editors.h
@@ -36,10 +36,10 @@
#include "editor/editor_node.h"
#include "editor/editor_properties.h"
+#include "scene/2d/tile_map.h"
#include "scene/gui/box_container.h"
#include "scene/gui/control.h"
#include "scene/gui/label.h"
-#include "scene/resources/tile_set.h"
class TileDataEditor : public VBoxContainer {
GDCLASS(TileDataEditor, VBoxContainer);
diff --git a/editor/plugins/tiles/tile_map_editor.cpp b/editor/plugins/tiles/tile_map_editor.cpp
index 44822bec3a..77084f551a 100644
--- a/editor/plugins/tiles/tile_map_editor.cpp
+++ b/editor/plugins/tiles/tile_map_editor.cpp
@@ -377,6 +377,11 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p
return false;
}
+ if (tile_map_layer < 0) {
+ return false;
+ }
+ ERR_FAIL_INDEX_V(tile_map_layer, tile_map->get_layers_count(), false);
+
Ref<TileSet> tile_set = tile_map->get_tileset();
if (!tile_set.is_valid()) {
return false;
@@ -391,7 +396,7 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p
for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) {
coords_array.push_back(E->get());
}
- tile_map_clipboard = tile_map->get_pattern(coords_array);
+ tile_map_clipboard = tile_map->get_pattern(tile_map_layer, coords_array);
}
if (ED_IS_SHORTCUT("tiles_editor/cut", p_event)) {
@@ -399,8 +404,8 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p
if (!tile_map_selection.is_empty()) {
undo_redo->create_action(TTR("Delete tiles"));
for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) {
- undo_redo->add_do_method(tile_map, "set_cell", E->get(), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE);
- undo_redo->add_undo_method(tile_map, "set_cell", E->get(), tile_map->get_cell_source_id(E->get()), tile_map->get_cell_atlas_coords(E->get()), tile_map->get_cell_alternative_tile(E->get()));
+ undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E->get(), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE);
+ undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E->get(), tile_map->get_cell_source_id(tile_map_layer, E->get()), tile_map->get_cell_atlas_coords(tile_map_layer, E->get()), tile_map->get_cell_alternative_tile(tile_map_layer, E->get()));
}
undo_redo->add_undo_method(this, "_set_tile_map_selection", _get_tile_map_selection());
tile_map_selection.clear();
@@ -430,8 +435,8 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p
if (!tile_map_selection.is_empty()) {
undo_redo->create_action(TTR("Delete tiles"));
for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) {
- undo_redo->add_do_method(tile_map, "set_cell", E->get(), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE);
- undo_redo->add_undo_method(tile_map, "set_cell", E->get(), tile_map->get_cell_source_id(E->get()), tile_map->get_cell_atlas_coords(E->get()), tile_map->get_cell_alternative_tile(E->get()));
+ undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E->get(), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE);
+ undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E->get(), tile_map->get_cell_source_id(tile_map_layer, E->get()), tile_map->get_cell_atlas_coords(tile_map_layer, E->get()), tile_map->get_cell_alternative_tile(tile_map_layer, E->get()));
}
undo_redo->add_undo_method(this, "_set_tile_map_selection", _get_tile_map_selection());
tile_map_selection.clear();
@@ -456,9 +461,9 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p
}
Vector2i coords = E->key();
if (!drag_modified.has(coords)) {
- drag_modified.insert(coords, tile_map->get_cell(coords));
+ drag_modified.insert(coords, tile_map->get_cell(tile_map_layer, coords));
}
- tile_map->set_cell(coords, E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
+ tile_map->set_cell(tile_map_layer, coords, E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
}
} break;
case DRAG_TYPE_BUCKET: {
@@ -472,9 +477,9 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p
}
Vector2i coords = E->key();
if (!drag_modified.has(coords)) {
- drag_modified.insert(coords, tile_map->get_cell(coords));
+ drag_modified.insert(coords, tile_map->get_cell(tile_map_layer, coords));
}
- tile_map->set_cell(coords, E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
+ tile_map->set_cell(tile_map_layer, coords, E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
}
}
}
@@ -497,7 +502,9 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p
if (mb->get_button_index() == MOUSE_BUTTON_LEFT) {
if (mb->is_pressed()) {
// Pressed
- if (tool_buttons_group->get_pressed_button() == select_tool_button) {
+ if (drag_type == DRAG_TYPE_CLIPBOARD_PASTE) {
+ // Do nothing.
+ } else if (tool_buttons_group->get_pressed_button() == select_tool_button) {
drag_start_mouse_pos = mpos;
if (tile_map_selection.has(tile_map->world_to_map(drag_start_mouse_pos)) && !mb->is_shift_pressed()) {
// Move the selection
@@ -505,8 +512,8 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p
drag_modified.clear();
for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) {
Vector2i coords = E->get();
- drag_modified.insert(coords, tile_map->get_cell(coords));
- tile_map->set_cell(coords, TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE);
+ drag_modified.insert(coords, tile_map->get_cell(tile_map_layer, coords));
+ tile_map->set_cell(tile_map_layer, coords, TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE);
}
} else {
// Select tiles
@@ -530,9 +537,9 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p
}
Vector2i coords = E->key();
if (!drag_modified.has(coords)) {
- drag_modified.insert(coords, tile_map->get_cell(coords));
+ drag_modified.insert(coords, tile_map->get_cell(tile_map_layer, coords));
}
- tile_map->set_cell(coords, E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
+ tile_map->set_cell(tile_map_layer, coords, E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
}
} else if (tool_buttons_group->get_pressed_button() == line_tool_button) {
drag_type = DRAG_TYPE_LINE;
@@ -556,9 +563,9 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p
}
Vector2i coords = E->key();
if (!drag_modified.has(coords)) {
- drag_modified.insert(coords, tile_map->get_cell(coords));
+ drag_modified.insert(coords, tile_map->get_cell(tile_map_layer, coords));
}
- tile_map->set_cell(coords, E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
+ tile_map->set_cell(tile_map_layer, coords, E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
}
}
}
@@ -587,6 +594,11 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over
return;
}
+ if (tile_map_layer < 0) {
+ return;
+ }
+ ERR_FAIL_INDEX(tile_map_layer, tile_map->get_layers_count());
+
Ref<TileSet> tile_set = tile_map->get_tileset();
if (!tile_set.is_valid()) {
return;
@@ -623,7 +635,7 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over
for (int x = rect.position.x; x < rect.get_end().x; x++) {
for (int y = rect.position.y; y < rect.get_end().y; y++) {
Vector2i coords = Vector2i(x, y);
- if (tile_map->get_cell_source_id(coords) != TileSet::INVALID_SOURCE) {
+ if (tile_map->get_cell_source_id(tile_map_layer, coords) != TileSet::INVALID_SOURCE) {
Rect2 cell_region = xform.xform(Rect2(tile_map->map_to_world(coords) - tile_shape_size / 2, tile_shape_size));
tile_set->draw_tile_shape(p_overlay, cell_region, Color(1.0, 1.0, 1.0), false);
}
@@ -637,7 +649,7 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over
for (int x = rect.position.x; x < rect.get_end().x; x++) {
for (int y = rect.position.y; y < rect.get_end().y; y++) {
Vector2i coords = Vector2i(x, y);
- if (tile_map->get_cell_source_id(coords) != TileSet::INVALID_SOURCE) {
+ if (tile_map->get_cell_source_id(tile_map_layer, coords) != TileSet::INVALID_SOURCE) {
to_draw.insert(coords);
}
}
@@ -914,6 +926,8 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_rect(Vector2i p_start
TileMapPattern erase_pattern;
erase_pattern.set_cell(Vector2i(0, 0), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE);
TileMapPattern *pattern = erase_button->is_pressed() ? &erase_pattern : selection_pattern;
+ Map<Vector2i, TileMapCell> err_output;
+ ERR_FAIL_COND_V(pattern->is_empty(), err_output);
// Compute the offset to align things to the bottom or right.
bool aligned_right = p_end_cell.x < p_start_cell.x;
@@ -956,6 +970,12 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i
return Map<Vector2i, TileMapCell>();
}
+ if (tile_map_layer < 0) {
+ return Map<Vector2i, TileMapCell>();
+ }
+ Map<Vector2i, TileMapCell> output;
+ ERR_FAIL_INDEX_V(tile_map_layer, tile_map->get_layers_count(), output);
+
Ref<TileSet> tile_set = tile_map->get_tileset();
if (!tile_set.is_valid()) {
return Map<Vector2i, TileMapCell>();
@@ -966,9 +986,8 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i
erase_pattern.set_cell(Vector2i(0, 0), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE);
TileMapPattern *pattern = erase_button->is_pressed() ? &erase_pattern : selection_pattern;
- Map<Vector2i, TileMapCell> output;
if (!pattern->is_empty()) {
- TileMapCell source = tile_map->get_cell(p_coords);
+ TileMapCell source = tile_map->get_cell(tile_map_layer, p_coords);
// If we are filling empty tiles, compute the tilemap boundaries.
Rect2i boundaries;
@@ -985,9 +1004,9 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i
Vector2i coords = to_check.back()->get();
to_check.pop_back();
if (!already_checked.has(coords)) {
- if (source.source_id == tile_map->get_cell_source_id(coords) &&
- source.get_atlas_coords() == tile_map->get_cell_atlas_coords(coords) &&
- source.alternative_tile == tile_map->get_cell_alternative_tile(coords) &&
+ if (source.source_id == tile_map->get_cell_source_id(tile_map_layer, coords) &&
+ source.get_atlas_coords() == tile_map->get_cell_atlas_coords(tile_map_layer, coords) &&
+ source.alternative_tile == tile_map->get_cell_alternative_tile(tile_map_layer, coords) &&
(source.source_id != TileSet::INVALID_SOURCE || boundaries.has_point(coords))) {
if (!erase_button->is_pressed() && random_tile_checkbox->is_pressed()) {
// Paint a random tile.
@@ -1027,13 +1046,13 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i
}
}
} else {
- to_check = tile_map->get_used_cells();
+ to_check = tile_map->get_used_cells(tile_map_layer);
}
for (int i = 0; i < to_check.size(); i++) {
Vector2i coords = to_check[i];
- if (source.source_id == tile_map->get_cell_source_id(coords) &&
- source.get_atlas_coords() == tile_map->get_cell_atlas_coords(coords) &&
- source.alternative_tile == tile_map->get_cell_alternative_tile(coords) &&
+ if (source.source_id == tile_map->get_cell_source_id(tile_map_layer, coords) &&
+ source.get_atlas_coords() == tile_map->get_cell_atlas_coords(tile_map_layer, coords) &&
+ source.alternative_tile == tile_map->get_cell_alternative_tile(tile_map_layer, coords) &&
(source.source_id != TileSet::INVALID_SOURCE || boundaries.has_point(coords))) {
if (!erase_button->is_pressed() && random_tile_checkbox->is_pressed()) {
// Paint a random tile.
@@ -1066,6 +1085,11 @@ void TileMapEditorTilesPlugin::_stop_dragging() {
return;
}
+ if (tile_map_layer < 0) {
+ return;
+ }
+ ERR_FAIL_INDEX(tile_map_layer, tile_map->get_layers_count());
+
Ref<TileSet> tile_set = tile_map->get_tileset();
if (!tile_set.is_valid()) {
return;
@@ -1091,7 +1115,7 @@ void TileMapEditorTilesPlugin::_stop_dragging() {
tile_map_selection.erase(coords);
}
} else {
- if (tile_map->get_cell_source_id(coords) != TileSet::INVALID_SOURCE) {
+ if (tile_map->get_cell_source_id(tile_map_layer, coords) != TileSet::INVALID_SOURCE) {
tile_map_selection.insert(coords);
}
}
@@ -1123,7 +1147,7 @@ void TileMapEditorTilesPlugin::_stop_dragging() {
coords = tile_map->map_pattern(top_left, selection_used_cells[i], selection_pattern);
cells_undo[coords] = TileMapCell(drag_modified[coords].source_id, drag_modified[coords].get_atlas_coords(), drag_modified[coords].alternative_tile);
coords = tile_map->map_pattern(top_left + offset, selection_used_cells[i], selection_pattern);
- cells_undo[coords] = TileMapCell(tile_map->get_cell_source_id(coords), tile_map->get_cell_atlas_coords(coords), tile_map->get_cell_alternative_tile(coords));
+ cells_undo[coords] = TileMapCell(tile_map->get_cell_source_id(tile_map_layer, coords), tile_map->get_cell_atlas_coords(tile_map_layer, coords), tile_map->get_cell_alternative_tile(tile_map_layer, coords));
}
Map<Vector2i, TileMapCell> cells_do;
@@ -1138,10 +1162,10 @@ void TileMapEditorTilesPlugin::_stop_dragging() {
undo_redo->create_action(TTR("Move tiles"));
// Move the tiles.
for (Map<Vector2i, TileMapCell>::Element *E = cells_do.front(); E; E = E->next()) {
- undo_redo->add_do_method(tile_map, "set_cell", E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
+ undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
}
for (Map<Vector2i, TileMapCell>::Element *E = cells_undo.front(); E; E = E->next()) {
- undo_redo->add_undo_method(tile_map, "set_cell", E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
+ undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
}
// Update the selection.
@@ -1162,12 +1186,12 @@ void TileMapEditorTilesPlugin::_stop_dragging() {
for (int x = rect.position.x; x < rect.get_end().x; x++) {
for (int y = rect.position.y; y < rect.get_end().y; y++) {
Vector2i coords = Vector2i(x, y);
- if (tile_map->get_cell_source_id(coords) != TileSet::INVALID_SOURCE) {
+ if (tile_map->get_cell_source_id(tile_map_layer, coords) != TileSet::INVALID_SOURCE) {
coords_array.push_back(coords);
}
}
}
- selection_pattern = tile_map->get_pattern(coords_array);
+ selection_pattern = tile_map->get_pattern(tile_map_layer, coords_array);
if (!selection_pattern->is_empty()) {
_update_tileset_selection_from_selection_pattern();
} else {
@@ -1178,8 +1202,8 @@ void TileMapEditorTilesPlugin::_stop_dragging() {
case DRAG_TYPE_PAINT: {
undo_redo->create_action(TTR("Paint tiles"));
for (Map<Vector2i, TileMapCell>::Element *E = drag_modified.front(); E; E = E->next()) {
- undo_redo->add_do_method(tile_map, "set_cell", E->key(), tile_map->get_cell_source_id(E->key()), tile_map->get_cell_atlas_coords(E->key()), tile_map->get_cell_alternative_tile(E->key()));
- undo_redo->add_undo_method(tile_map, "set_cell", E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
+ undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E->key(), tile_map->get_cell_source_id(tile_map_layer, E->key()), tile_map->get_cell_atlas_coords(tile_map_layer, E->key()), tile_map->get_cell_alternative_tile(tile_map_layer, E->key()));
+ undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
}
undo_redo->commit_action(false);
} break;
@@ -1190,8 +1214,8 @@ void TileMapEditorTilesPlugin::_stop_dragging() {
if (!erase_button->is_pressed() && E->get().source_id == TileSet::INVALID_SOURCE) {
continue;
}
- undo_redo->add_do_method(tile_map, "set_cell", E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
- undo_redo->add_undo_method(tile_map, "set_cell", E->key(), tile_map->get_cell_source_id(E->key()), tile_map->get_cell_atlas_coords(E->key()), tile_map->get_cell_alternative_tile(E->key()));
+ undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
+ undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E->key(), tile_map->get_cell_source_id(tile_map_layer, E->key()), tile_map->get_cell_atlas_coords(tile_map_layer, E->key()), tile_map->get_cell_alternative_tile(tile_map_layer, E->key()));
}
undo_redo->commit_action();
} break;
@@ -1202,19 +1226,16 @@ void TileMapEditorTilesPlugin::_stop_dragging() {
if (!erase_button->is_pressed() && E->get().source_id == TileSet::INVALID_SOURCE) {
continue;
}
- undo_redo->add_do_method(tile_map, "set_cell", E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
- undo_redo->add_undo_method(tile_map, "set_cell", E->key(), tile_map->get_cell_source_id(E->key()), tile_map->get_cell_atlas_coords(E->key()), tile_map->get_cell_alternative_tile(E->key()));
+ undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
+ undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E->key(), tile_map->get_cell_source_id(tile_map_layer, E->key()), tile_map->get_cell_atlas_coords(tile_map_layer, E->key()), tile_map->get_cell_alternative_tile(tile_map_layer, E->key()));
}
undo_redo->commit_action();
} break;
case DRAG_TYPE_BUCKET: {
undo_redo->create_action(TTR("Paint tiles"));
for (Map<Vector2i, TileMapCell>::Element *E = drag_modified.front(); E; E = E->next()) {
- if (!erase_button->is_pressed() && E->get().source_id == TileSet::INVALID_SOURCE) {
- continue;
- }
- undo_redo->add_do_method(tile_map, "set_cell", E->key(), tile_map->get_cell_source_id(E->key()), tile_map->get_cell_atlas_coords(E->key()), tile_map->get_cell_alternative_tile(E->key()));
- undo_redo->add_undo_method(tile_map, "set_cell", E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
+ undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E->key(), tile_map->get_cell_source_id(tile_map_layer, E->key()), tile_map->get_cell_atlas_coords(tile_map_layer, E->key()), tile_map->get_cell_alternative_tile(tile_map_layer, E->key()));
+ undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
}
undo_redo->commit_action(false);
} break;
@@ -1224,8 +1245,8 @@ void TileMapEditorTilesPlugin::_stop_dragging() {
TypedArray<Vector2i> used_cells = tile_map_clipboard->get_used_cells();
for (int i = 0; i < used_cells.size(); i++) {
Vector2i coords = tile_map->map_pattern(tile_map->world_to_map(mpos - mouse_offset), used_cells[i], tile_map_clipboard);
- undo_redo->add_do_method(tile_map, "set_cell", coords, tile_map_clipboard->get_cell_source_id(used_cells[i]), tile_map_clipboard->get_cell_atlas_coords(used_cells[i]), tile_map_clipboard->get_cell_alternative_tile(used_cells[i]));
- undo_redo->add_undo_method(tile_map, "set_cell", coords, tile_map->get_cell_source_id(coords), tile_map->get_cell_atlas_coords(coords), tile_map->get_cell_alternative_tile(coords));
+ undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, coords, tile_map_clipboard->get_cell_source_id(used_cells[i]), tile_map_clipboard->get_cell_atlas_coords(used_cells[i]), tile_map_clipboard->get_cell_alternative_tile(used_cells[i]));
+ undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, coords, tile_map->get_cell_source_id(tile_map_layer, coords), tile_map->get_cell_atlas_coords(tile_map_layer, coords), tile_map->get_cell_alternative_tile(tile_map_layer, coords));
}
undo_redo->commit_action();
} break;
@@ -1304,13 +1325,15 @@ void TileMapEditorTilesPlugin::_update_selection_pattern_from_tilemap_selection(
return;
}
+ ERR_FAIL_INDEX(tile_map_layer, tile_map->get_layers_count());
+
memdelete(selection_pattern);
TypedArray<Vector2i> coords_array;
for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) {
coords_array.push_back(E->get());
}
- selection_pattern = tile_map->get_pattern(coords_array);
+ selection_pattern = tile_map->get_pattern(tile_map_layer, coords_array);
}
void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_selection() {
@@ -1347,8 +1370,7 @@ void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_selection(
TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source);
if (atlas_source) {
// Organize using coordinates.
- for (List<const TileMapCell *>::Element *E_cell = E_source->get().front(); E_cell; E_cell = E_cell->next()) {
- const TileMapCell *current = E_cell->get();
+ for (const TileMapCell *current : E_source->get()) {
if (current->alternative_tile == 0) {
organized_pattern[current->get_atlas_coords()] = current;
} else {
@@ -1367,8 +1389,8 @@ void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_selection(
}
} else {
// Add everything unorganized.
- for (List<const TileMapCell *>::Element *E_cell = E_source->get().front(); E_cell; E_cell = E_cell->next()) {
- unorganized.push_back(E_cell->get());
+ for (const TileMapCell *cell : E_source->get()) {
+ unorganized.push_back(cell);
}
}
@@ -1378,8 +1400,8 @@ void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_selection(
}
Vector2i organized_size = selection_pattern->get_size();
int unorganized_index = 0;
- for (List<const TileMapCell *>::Element *E_cell = unorganized.front(); E_cell; E_cell = E_cell->next()) {
- selection_pattern->set_cell(Vector2(organized_size.x + unorganized_index, vertical_offset), E_cell->get()->source_id, E_cell->get()->get_atlas_coords(), E_cell->get()->alternative_tile);
+ for (const TileMapCell *cell : unorganized) {
+ selection_pattern->set_cell(Vector2(organized_size.x + unorganized_index, vertical_offset), cell->source_id, cell->get_atlas_coords(), cell->alternative_tile);
unorganized_index++;
}
vertical_offset += MAX(organized_size.y, 1);
@@ -1714,13 +1736,19 @@ TypedArray<Vector2i> TileMapEditorTilesPlugin::_get_tile_map_selection() const {
return output;
}
-void TileMapEditorTilesPlugin::edit(ObjectID p_tile_map_id) {
- tile_map_id = p_tile_map_id;
+void TileMapEditorTilesPlugin::edit(ObjectID p_tile_map_id, int p_tile_map_layer) {
+ _stop_dragging(); // Avoids staying in a wrong drag state.
- // Clear the selection.
- tile_set_selection.clear();
- tile_map_selection.clear();
- selection_pattern->clear();
+ if (tile_map_id != p_tile_map_id) {
+ tile_map_id = p_tile_map_id;
+
+ // Clear the selection.
+ tile_set_selection.clear();
+ tile_map_selection.clear();
+ selection_pattern->clear();
+ }
+
+ tile_map_layer = p_tile_map_layer;
}
void TileMapEditorTilesPlugin::_bind_methods() {
@@ -1759,7 +1787,7 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() {
paint_tool_button->set_flat(true);
paint_tool_button->set_toggle_mode(true);
paint_tool_button->set_button_group(tool_buttons_group);
- paint_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/paint_tool", "Paint", KEY_E));
+ paint_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/paint_tool", "Paint", KEY_D));
paint_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTilesPlugin::_update_toolbar));
tilemap_tiles_tools_buttons->add_child(paint_tool_button);
@@ -2302,6 +2330,7 @@ Set<TileMapEditorTerrainsPlugin::Constraint> TileMapEditorTerrainsPlugin::_get_c
}
ERR_FAIL_INDEX_V(p_terrain_set, tile_set->get_terrain_sets_count(), Set<Constraint>());
+ ERR_FAIL_INDEX_V(tile_map_layer, tile_map->get_layers_count(), Set<Constraint>());
// Build a set of dummy constraints get the constrained points.
Set<Constraint> dummy_constraints;
@@ -2325,7 +2354,7 @@ Set<TileMapEditorTerrainsPlugin::Constraint> TileMapEditorTerrainsPlugin::_get_c
Map<Vector2i, TileSet::CellNeighbor> overlapping_terrain_bits = c.get_overlapping_coords_and_peering_bits();
for (Map<Vector2i, TileSet::CellNeighbor>::Element *E_overlapping = overlapping_terrain_bits.front(); E_overlapping; E_overlapping = E_overlapping->next()) {
if (!p_to_replace.has(E_overlapping->key())) {
- TileMapCell neighbor_cell = tile_map->get_cell(E_overlapping->key());
+ TileMapCell neighbor_cell = tile_map->get_cell(tile_map_layer, E_overlapping->key());
TileData *neighbor_tile_data = nullptr;
if (terrain_tiles.has(neighbor_cell) && terrain_tiles[neighbor_cell]->get_terrain_set() == p_terrain_set) {
neighbor_tile_data = terrain_tiles[neighbor_cell];
@@ -2632,6 +2661,90 @@ Map<Vector2i, TileMapCell> TileMapEditorTerrainsPlugin::_draw_terrains(const Map
return output;
}
+void TileMapEditorTerrainsPlugin::_stop_dragging() {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return;
+ }
+
+ Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform();
+ Vector2 mpos = xform.affine_inverse().xform(CanvasItemEditor::get_singleton()->get_viewport_control()->get_local_mouse_position());
+
+ switch (drag_type) {
+ case DRAG_TYPE_PICK: {
+ Vector2i coords = tile_map->world_to_map(mpos);
+ TileMapCell tile = tile_map->get_cell(tile_map_layer, coords);
+
+ if (terrain_tiles.has(tile)) {
+ Array terrains_tile_pattern = _build_terrains_tile_pattern(terrain_tiles[tile]);
+
+ // Find the tree item for the right terrain set.
+ bool need_tree_item_switch = true;
+ TreeItem *tree_item = terrains_tree->get_selected();
+ if (tree_item) {
+ Dictionary metadata_dict = tree_item->get_metadata(0);
+ if (metadata_dict.has("terrain_set") && metadata_dict.has("terrain_id")) {
+ int terrain_set = metadata_dict["terrain_set"];
+ int terrain_id = metadata_dict["terrain_id"];
+ if (per_terrain_terrains_tile_patterns[terrain_set][terrain_id].has(terrains_tile_pattern)) {
+ need_tree_item_switch = false;
+ }
+ }
+ }
+
+ if (need_tree_item_switch) {
+ for (tree_item = terrains_tree->get_root()->get_first_child(); tree_item; tree_item = tree_item->get_next_visible()) {
+ Dictionary metadata_dict = tree_item->get_metadata(0);
+ if (metadata_dict.has("terrain_set") && metadata_dict.has("terrain_id")) {
+ int terrain_set = metadata_dict["terrain_set"];
+ int terrain_id = metadata_dict["terrain_id"];
+ if (per_terrain_terrains_tile_patterns[terrain_set][terrain_id].has(terrains_tile_pattern)) {
+ // Found
+ tree_item->select(0);
+ _update_tiles_list();
+ break;
+ }
+ }
+ }
+ }
+
+ // Find the list item for the given tile.
+ if (tree_item) {
+ for (int i = 0; i < terrains_tile_list->get_item_count(); i++) {
+ Dictionary metadata_dict = terrains_tile_list->get_item_metadata(i);
+ TerrainsTilePattern in_meta_terrains_tile_pattern = metadata_dict["terrains_tile_pattern"];
+ bool equals = true;
+ for (int j = 0; j < terrains_tile_pattern.size(); j++) {
+ if (terrains_tile_pattern[j] != in_meta_terrains_tile_pattern[j]) {
+ equals = false;
+ break;
+ }
+ }
+ if (equals) {
+ terrains_tile_list->select(i);
+ break;
+ }
+ }
+ } else {
+ ERR_PRINT("Terrain tile not found.");
+ }
+ }
+ picker_button->set_pressed(false);
+ } break;
+ case DRAG_TYPE_PAINT: {
+ undo_redo->create_action(TTR("Paint terrain"));
+ for (Map<Vector2i, TileMapCell>::Element *E = drag_modified.front(); E; E = E->next()) {
+ undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E->key(), tile_map->get_cell_source_id(tile_map_layer, E->key()), tile_map->get_cell_atlas_coords(tile_map_layer, E->key()), tile_map->get_cell_alternative_tile(tile_map_layer, E->key()));
+ undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
+ }
+ undo_redo->commit_action(false);
+ } break;
+ default:
+ break;
+ }
+ drag_type = DRAG_TYPE_NONE;
+}
+
bool TileMapEditorTerrainsPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p_event) {
if (!is_visible_in_tree()) {
// If the bottom editor is not visible, we ignore inputs.
@@ -2652,6 +2765,11 @@ bool TileMapEditorTerrainsPlugin::forward_canvas_gui_input(const Ref<InputEvent>
return false;
}
+ if (tile_map_layer < 0) {
+ return false;
+ }
+ ERR_FAIL_COND_V(tile_map_layer >= tile_map->get_layers_count(), false);
+
// Get the selected terrain.
TerrainsTilePattern selected_terrains_tile_pattern;
int selected_terrain_set = -1;
@@ -2693,9 +2811,9 @@ bool TileMapEditorTerrainsPlugin::forward_canvas_gui_input(const Ref<InputEvent>
Map<Vector2i, TileMapCell> modified = _draw_terrains(to_draw, selected_terrain_set);
for (Map<Vector2i, TileMapCell>::Element *E = modified.front(); E; E = E->next()) {
if (!drag_modified.has(E->key())) {
- drag_modified[E->key()] = tile_map->get_cell(E->key());
+ drag_modified[E->key()] = tile_map->get_cell(tile_map_layer, E->key());
}
- tile_map->set_cell(E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
+ tile_map->set_cell(tile_map_layer, E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
}
}
} break;
@@ -2731,86 +2849,14 @@ bool TileMapEditorTerrainsPlugin::forward_canvas_gui_input(const Ref<InputEvent>
Map<Vector2i, TileMapCell> to_draw = _draw_terrains(terrains_to_draw, selected_terrain_set);
for (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) {
- drag_modified[E->key()] = tile_map->get_cell(E->key());
- tile_map->set_cell(E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
+ drag_modified[E->key()] = tile_map->get_cell(tile_map_layer, E->key());
+ tile_map->set_cell(tile_map_layer, E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
}
}
}
} else {
// Released
- switch (drag_type) {
- case DRAG_TYPE_PICK: {
- Vector2i coords = tile_map->world_to_map(mpos);
- TileMapCell tile = tile_map->get_cell(coords);
-
- if (terrain_tiles.has(tile)) {
- Array terrains_tile_pattern = _build_terrains_tile_pattern(terrain_tiles[tile]);
-
- // Find the tree item for the right terrain set.
- bool need_tree_item_switch = true;
- TreeItem *tree_item = terrains_tree->get_selected();
- if (tree_item) {
- Dictionary metadata_dict = tree_item->get_metadata(0);
- if (metadata_dict.has("terrain_set") && metadata_dict.has("terrain_id")) {
- int terrain_set = metadata_dict["terrain_set"];
- int terrain_id = metadata_dict["terrain_id"];
- if (per_terrain_terrains_tile_patterns[terrain_set][terrain_id].has(terrains_tile_pattern)) {
- need_tree_item_switch = false;
- }
- }
- }
-
- if (need_tree_item_switch) {
- for (tree_item = terrains_tree->get_root()->get_first_child(); tree_item; tree_item = tree_item->get_next_visible()) {
- Dictionary metadata_dict = tree_item->get_metadata(0);
- if (metadata_dict.has("terrain_set") && metadata_dict.has("terrain_id")) {
- int terrain_set = metadata_dict["terrain_set"];
- int terrain_id = metadata_dict["terrain_id"];
- if (per_terrain_terrains_tile_patterns[terrain_set][terrain_id].has(terrains_tile_pattern)) {
- // Found
- tree_item->select(0);
- _update_tiles_list();
- break;
- }
- }
- }
- }
-
- // Find the list item for the given tile.
- if (tree_item) {
- for (int i = 0; i < terrains_tile_list->get_item_count(); i++) {
- Dictionary metadata_dict = terrains_tile_list->get_item_metadata(i);
- TerrainsTilePattern in_meta_terrains_tile_pattern = metadata_dict["terrains_tile_pattern"];
- bool equals = true;
- for (int j = 0; j < terrains_tile_pattern.size(); j++) {
- if (terrains_tile_pattern[j] != in_meta_terrains_tile_pattern[j]) {
- equals = false;
- break;
- }
- }
- if (equals) {
- terrains_tile_list->select(i);
- break;
- }
- }
- } else {
- ERR_PRINT("Terrain tile not found.");
- }
- }
- picker_button->set_pressed(false);
- } break;
- case DRAG_TYPE_PAINT: {
- undo_redo->create_action(TTR("Paint terrain"));
- for (Map<Vector2i, TileMapCell>::Element *E = drag_modified.front(); E; E = E->next()) {
- undo_redo->add_do_method(tile_map, "set_cell", E->key(), tile_map->get_cell_source_id(E->key()), tile_map->get_cell_atlas_coords(E->key()), tile_map->get_cell_alternative_tile(E->key()));
- undo_redo->add_undo_method(tile_map, "set_cell", E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile);
- }
- undo_redo->commit_action(false);
- } break;
- default:
- break;
- }
- drag_type = DRAG_TYPE_NONE;
+ _stop_dragging();
}
CanvasItemEditor::get_singleton()->update_viewport();
@@ -3083,8 +3129,12 @@ void TileMapEditorTerrainsPlugin::_update_tiles_list() {
}
}
-void TileMapEditorTerrainsPlugin::edit(ObjectID p_tile_map_id) {
+void TileMapEditorTerrainsPlugin::edit(ObjectID p_tile_map_id, int p_tile_map_layer) {
+ _stop_dragging(); // Avoids staying in a wrong drag state.
+
tile_map_id = p_tile_map_id;
+ tile_map_layer = p_tile_map_layer;
+
_update_terrains_cache();
_update_terrains_tree();
_update_tiles_list();
@@ -3169,10 +3219,12 @@ void TileMapEditor::_notification(int p_what) {
advanced_menu_button->set_icon(get_theme_icon(SNAME("Tools"), SNAME("EditorIcons")));
toggle_grid_button->set_icon(get_theme_icon(SNAME("Grid"), SNAME("EditorIcons")));
toggle_grid_button->set_pressed(EditorSettings::get_singleton()->get("editors/tiles_editor/display_grid"));
+ toogle_highlight_selected_layer_button->set_icon(get_theme_icon(SNAME("TileMapHighlightSelected"), SNAME("EditorIcons")));
break;
case NOTIFICATION_INTERNAL_PROCESS:
if (is_visible_in_tree() && tileset_changed_needs_update) {
_update_bottom_panel();
+ _update_layers_selection();
tile_map_editor_plugins[tabs->get_current_tab()]->tile_set_changed();
CanvasItemEditor::get_singleton()->update_viewport();
tileset_changed_needs_update = false;
@@ -3181,6 +3233,16 @@ void TileMapEditor::_notification(int p_what) {
case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED:
toggle_grid_button->set_pressed(EditorSettings::get_singleton()->get("editors/tiles_editor/display_grid"));
break;
+ case NOTIFICATION_VISIBILITY_CHANGED:
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (tile_map) {
+ if (is_visible_in_tree()) {
+ tile_map->set_selected_layer(tile_map_layer);
+ } else {
+ tile_map->set_selected_layer(-1);
+ }
+ }
+ break;
}
}
@@ -3188,6 +3250,63 @@ void TileMapEditor::_on_grid_toggled(bool p_pressed) {
EditorSettings::get_singleton()->set("editors/tiles_editor/display_grid", p_pressed);
}
+void TileMapEditor::_layers_selection_button_draw() {
+ if (!has_theme_icon(SNAME("arrow"), SNAME("OptionButton"))) {
+ return;
+ }
+
+ RID ci = layers_selection_button->get_canvas_item();
+ Ref<Texture2D> arrow = Control::get_theme_icon(SNAME("arrow"), SNAME("OptionButton"));
+
+ Color clr = Color(1, 1, 1);
+ if (get_theme_constant(SNAME("modulate_arrow"))) {
+ switch (layers_selection_button->get_draw_mode()) {
+ case BaseButton::DRAW_PRESSED:
+ clr = get_theme_color(SNAME("font_pressed_color"));
+ break;
+ case BaseButton::DRAW_HOVER:
+ clr = get_theme_color(SNAME("font_hover_color"));
+ break;
+ case BaseButton::DRAW_DISABLED:
+ clr = get_theme_color(SNAME("font_disabled_color"));
+ break;
+ default:
+ clr = get_theme_color(SNAME("font_color"));
+ }
+ }
+
+ Size2 size = layers_selection_button->get_size();
+
+ Point2 ofs;
+ if (is_layout_rtl()) {
+ ofs = Point2(get_theme_constant(SNAME("arrow_margin"), SNAME("OptionButton")), int(Math::abs((size.height - arrow->get_height()) / 2)));
+ } else {
+ ofs = Point2(size.width - arrow->get_width() - get_theme_constant(SNAME("arrow_margin"), SNAME("OptionButton")), int(Math::abs((size.height - arrow->get_height()) / 2)));
+ }
+ Rect2 dst_rect = Rect2(ofs, arrow->get_size());
+ if (!layers_selection_button->is_pressed()) {
+ dst_rect.size = -dst_rect.size;
+ }
+ arrow->draw_rect(ci, dst_rect, false, clr);
+}
+
+void TileMapEditor::_layers_selection_button_pressed() {
+ if (!layers_selection_popup->is_visible()) {
+ Size2 size = layers_selection_popup->get_contents_minimum_size();
+ size.x = MAX(size.x, layers_selection_button->get_size().x);
+ layers_selection_popup->set_position(layers_selection_button->get_screen_position() - Size2(0, size.y * get_global_transform().get_scale().y));
+ layers_selection_popup->set_size(size);
+ layers_selection_popup->popup();
+ } else {
+ layers_selection_popup->hide();
+ }
+}
+
+void TileMapEditor::_layers_selection_id_pressed(int p_id) {
+ tile_map_layer = p_id;
+ _update_layers_selection();
+}
+
void TileMapEditor::_advanced_menu_button_id_pressed(int p_id) {
TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
if (!tile_map) {
@@ -3201,18 +3320,20 @@ void TileMapEditor::_advanced_menu_button_id_pressed(int p_id) {
if (p_id == 0) { // Replace Tile Proxies
undo_redo->create_action(TTR("Replace Tiles with Proxies"));
- TypedArray<Vector2i> used_cells = tile_map->get_used_cells();
- for (int i = 0; i < used_cells.size(); i++) {
- Vector2i cell_coords = used_cells[i];
- TileMapCell from = tile_map->get_cell(cell_coords);
- Array to_array = tile_set->map_tile_proxy(from.source_id, from.get_atlas_coords(), from.alternative_tile);
- TileMapCell to;
- to.source_id = to_array[0];
- to.set_atlas_coords(to_array[1]);
- to.alternative_tile = to_array[2];
- if (from != to) {
- undo_redo->add_do_method(tile_map, "set_cell", cell_coords, to.source_id, to.get_atlas_coords(), to.alternative_tile);
- undo_redo->add_undo_method(tile_map, "set_cell", cell_coords, from.source_id, from.get_atlas_coords(), from.alternative_tile);
+ for (int layer_index = 0; layer_index < tile_map->get_layers_count(); layer_index++) {
+ TypedArray<Vector2i> used_cells = tile_map->get_used_cells(layer_index);
+ for (int i = 0; i < used_cells.size(); i++) {
+ Vector2i cell_coords = used_cells[i];
+ TileMapCell from = tile_map->get_cell(layer_index, cell_coords);
+ Array to_array = tile_set->map_tile_proxy(from.source_id, from.get_atlas_coords(), from.alternative_tile);
+ TileMapCell to;
+ to.source_id = to_array[0];
+ to.set_atlas_coords(to_array[1]);
+ to.alternative_tile = to_array[2];
+ if (from != to) {
+ undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, cell_coords, to.source_id, to.get_atlas_coords(), to.alternative_tile);
+ undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, cell_coords, from.source_id, from.get_atlas_coords(), from.alternative_tile);
+ }
}
}
undo_redo->commit_action();
@@ -3318,7 +3439,7 @@ void TileMapEditor::_tile_map_changed() {
void TileMapEditor::_tab_changed(int p_tab_id) {
// Make the plugin edit the correct tilemap.
- tile_map_editor_plugins[tabs->get_current_tab()]->edit(tile_map_id);
+ tile_map_editor_plugins[tabs->get_current_tab()]->edit(tile_map_id, tile_map_layer);
// Update toolbar.
for (int i = 0; i < tile_map_editor_plugins.size(); i++) {
@@ -3342,7 +3463,129 @@ void TileMapEditor::_tab_changed(int p_tab_id) {
CanvasItemEditor::get_singleton()->update_viewport();
}
+void TileMapEditor::_layers_select_next_or_previous(bool p_next) {
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return;
+ }
+
+ if (tile_map->get_layers_count() < 1) {
+ return;
+ }
+
+ if (tile_map_layer < 0) {
+ tile_map_layer = 0;
+ }
+
+ int inc = p_next ? 1 : -1;
+ int origin_layer = tile_map_layer;
+ tile_map_layer = Math::posmod((tile_map_layer + inc), tile_map->get_layers_count());
+ while (tile_map_layer != origin_layer) {
+ if (tile_map->is_layer_enabled(tile_map_layer)) {
+ break;
+ }
+ tile_map_layer = Math::posmod((tile_map_layer + inc), tile_map->get_layers_count());
+ }
+
+ _update_layers_selection();
+}
+
+void TileMapEditor::_update_layers_selection() {
+ layers_selection_popup->clear();
+
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (!tile_map) {
+ return;
+ }
+
+ // Update the selected layer.
+ if (is_visible_in_tree() && tile_map->get_layers_count() >= 1) {
+ tile_map_layer = CLAMP(tile_map_layer, 0, tile_map->get_layers_count() - 1);
+
+ // Search for an enabled layer if the current one is not.
+ int origin_layer = tile_map_layer;
+ while (tile_map_layer >= 0 && !tile_map->is_layer_enabled(tile_map_layer)) {
+ tile_map_layer--;
+ }
+ if (tile_map_layer < 0) {
+ tile_map_layer = origin_layer;
+ while (tile_map_layer < tile_map->get_layers_count() && !tile_map->is_layer_enabled(tile_map_layer)) {
+ tile_map_layer++;
+ }
+ }
+ if (tile_map_layer >= tile_map->get_layers_count()) {
+ tile_map_layer = -1;
+ }
+ } else {
+ tile_map_layer = -1;
+ }
+ tile_map->set_selected_layer(toogle_highlight_selected_layer_button->is_pressed() ? tile_map_layer : -1);
+
+ // Build the list of layers.
+ for (int i = 0; i < tile_map->get_layers_count(); i++) {
+ String name = tile_map->get_layer_name(i);
+ layers_selection_popup->add_item(name.is_empty() ? vformat(TTR("Layer #%d"), i) : name, i);
+ layers_selection_popup->set_item_as_radio_checkable(i, true);
+ layers_selection_popup->set_item_disabled(i, !tile_map->is_layer_enabled(i));
+ layers_selection_popup->set_item_checked(i, i == tile_map_layer);
+ }
+
+ // Update the button label.
+ if (tile_map_layer >= 0) {
+ layers_selection_button->set_text(layers_selection_popup->get_item_text(tile_map_layer));
+ } else {
+ layers_selection_button->set_text(TTR("Select a layer"));
+ }
+
+ // Set button minimum width.
+ Size2 min_button_size = Size2(layers_selection_popup->get_contents_minimum_size().x, 0);
+ if (has_theme_icon(SNAME("arrow"), SNAME("OptionButton"))) {
+ Ref<Texture2D> arrow = Control::get_theme_icon(SNAME("arrow"), SNAME("OptionButton"));
+ min_button_size.x += arrow->get_size().x;
+ }
+ layers_selection_button->set_custom_minimum_size(min_button_size);
+ layers_selection_button->update();
+
+ tile_map_editor_plugins[tabs->get_current_tab()]->edit(tile_map_id, tile_map_layer);
+}
+
+void TileMapEditor::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value) {
+ UndoRedo *undo_redo = Object::cast_to<UndoRedo>(p_undo_redo);
+ ERR_FAIL_COND(!undo_redo);
+
+ TileMap *tile_map = Object::cast_to<TileMap>(p_edited);
+ if (tile_map) {
+ if (p_property == "layers_count") {
+ int new_layers_count = (int)p_new_value;
+ if (new_layers_count < tile_map->get_layers_count()) {
+ List<PropertyInfo> property_list;
+ tile_map->get_property_list(&property_list);
+
+ for (PropertyInfo property_info : property_list) {
+ Vector<String> components = String(property_info.name).split("/", true, 2);
+ if (components.size() == 2 && components[0].begins_with("layer_") && components[0].trim_prefix("layer_").is_valid_int()) {
+ int index = components[0].trim_prefix("layer_").to_int();
+ if (index >= new_layers_count) {
+ undo_redo->add_undo_property(tile_map, property_info.name, tile_map->get(property_info.name));
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
bool TileMapEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_event) {
+ if (ED_IS_SHORTCUT("tiles_editor/select_next_layer", p_event) && p_event->is_pressed()) {
+ _layers_select_next_or_previous(true);
+ return true;
+ }
+
+ if (ED_IS_SHORTCUT("tiles_editor/select_previous_layer", p_event) && p_event->is_pressed()) {
+ _layers_select_next_or_previous(false);
+ return true;
+ }
+
return tile_map_editor_plugins[tabs->get_current_tab()]->forward_canvas_gui_input(p_event);
}
@@ -3366,49 +3609,52 @@ void TileMapEditor::forward_canvas_draw_over_viewport(Control *p_overlay) {
Vector2i tile_shape_size = tile_set->get_tile_size();
// Draw tiles with invalid IDs in the grid.
- TypedArray<Vector2i> used_cells = tile_map->get_used_cells();
- for (int i = 0; i < used_cells.size(); i++) {
- Vector2i coords = used_cells[i];
- int tile_source_id = tile_map->get_cell_source_id(coords);
- if (tile_source_id >= 0) {
- Vector2i tile_atlas_coords = tile_map->get_cell_atlas_coords(coords);
- int tile_alternative_tile = tile_map->get_cell_alternative_tile(coords);
-
- TileSetSource *source = nullptr;
- if (tile_set->has_source(tile_source_id)) {
- source = *tile_set->get_source(tile_source_id);
- }
-
- if (!source || !source->has_tile(tile_atlas_coords) || !source->has_alternative_tile(tile_atlas_coords, tile_alternative_tile)) {
- // Generate a random color from the hashed values of the tiles.
- Array a = tile_set->map_tile_proxy(tile_source_id, tile_atlas_coords, tile_alternative_tile);
- if (int(a[0]) == tile_source_id && Vector2i(a[1]) == tile_atlas_coords && int(a[2]) == tile_alternative_tile) {
- // Only display the pattern if we have no proxy tile.
- Array to_hash;
- to_hash.push_back(tile_source_id);
- to_hash.push_back(tile_atlas_coords);
- to_hash.push_back(tile_alternative_tile);
- uint32_t hash = RandomPCG(to_hash.hash()).rand();
-
- Color color;
- color = color.from_hsv(
- (float)((hash >> 24) & 0xFF) / 256.0,
- Math::lerp(0.5, 1.0, (float)((hash >> 16) & 0xFF) / 256.0),
- Math::lerp(0.5, 1.0, (float)((hash >> 8) & 0xFF) / 256.0),
- 0.8);
-
- // Draw the scaled tile.
- Rect2 cell_region = xform.xform(Rect2(tile_map->map_to_world(coords) - Vector2(tile_shape_size) / 2, Vector2(tile_shape_size)));
- tile_set->draw_tile_shape(p_overlay, cell_region, color, true, warning_pattern_texture);
+ if (tile_map_layer >= 0) {
+ ERR_FAIL_COND(tile_map_layer >= tile_map->get_layers_count());
+ TypedArray<Vector2i> used_cells = tile_map->get_used_cells(tile_map_layer);
+ for (int i = 0; i < used_cells.size(); i++) {
+ Vector2i coords = used_cells[i];
+ int tile_source_id = tile_map->get_cell_source_id(tile_map_layer, coords);
+ if (tile_source_id >= 0) {
+ Vector2i tile_atlas_coords = tile_map->get_cell_atlas_coords(tile_map_layer, coords);
+ int tile_alternative_tile = tile_map->get_cell_alternative_tile(tile_map_layer, coords);
+
+ TileSetSource *source = nullptr;
+ if (tile_set->has_source(tile_source_id)) {
+ source = *tile_set->get_source(tile_source_id);
}
- // Draw the warning icon.
- int min_axis = missing_tile_texture->get_size().min_axis();
- Vector2 icon_size;
- icon_size[min_axis] = tile_set->get_tile_size()[min_axis] / 3;
- icon_size[(min_axis + 1) % 2] = (icon_size[min_axis] * missing_tile_texture->get_size()[(min_axis + 1) % 2] / missing_tile_texture->get_size()[min_axis]);
- Rect2 rect = Rect2(xform.xform(tile_map->map_to_world(coords)) - (icon_size * xform.get_scale() / 2), icon_size * xform.get_scale());
- p_overlay->draw_texture_rect(missing_tile_texture, rect);
+ if (!source || !source->has_tile(tile_atlas_coords) || !source->has_alternative_tile(tile_atlas_coords, tile_alternative_tile)) {
+ // Generate a random color from the hashed values of the tiles.
+ Array a = tile_set->map_tile_proxy(tile_source_id, tile_atlas_coords, tile_alternative_tile);
+ if (int(a[0]) == tile_source_id && Vector2i(a[1]) == tile_atlas_coords && int(a[2]) == tile_alternative_tile) {
+ // Only display the pattern if we have no proxy tile.
+ Array to_hash;
+ to_hash.push_back(tile_source_id);
+ to_hash.push_back(tile_atlas_coords);
+ to_hash.push_back(tile_alternative_tile);
+ uint32_t hash = RandomPCG(to_hash.hash()).rand();
+
+ Color color;
+ color = color.from_hsv(
+ (float)((hash >> 24) & 0xFF) / 256.0,
+ Math::lerp(0.5, 1.0, (float)((hash >> 16) & 0xFF) / 256.0),
+ Math::lerp(0.5, 1.0, (float)((hash >> 8) & 0xFF) / 256.0),
+ 0.8);
+
+ // Draw the scaled tile.
+ Rect2 cell_region = xform.xform(Rect2(tile_map->map_to_world(coords) - Vector2(tile_shape_size) / 2, Vector2(tile_shape_size)));
+ tile_set->draw_tile_shape(p_overlay, cell_region, color, true, warning_pattern_texture);
+ }
+
+ // Draw the warning icon.
+ int min_axis = missing_tile_texture->get_size().min_axis();
+ Vector2 icon_size;
+ icon_size[min_axis] = tile_set->get_tile_size()[min_axis] / 3;
+ icon_size[(min_axis + 1) % 2] = (icon_size[min_axis] * missing_tile_texture->get_size()[(min_axis + 1) % 2] / missing_tile_texture->get_size()[min_axis]);
+ Rect2 rect = Rect2(xform.xform(tile_map->map_to_world(coords)) - (icon_size * xform.get_scale() / 2), icon_size * xform.get_scale());
+ p_overlay->draw_texture_rect(missing_tile_texture, rect);
+ }
}
}
}
@@ -3479,14 +3725,19 @@ void TileMapEditor::edit(TileMap *p_tile_map) {
return;
}
- // Disconnect to changes.
TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
if (tile_map) {
+ // Unselect layer if we are changing tile_map.
+ if (tile_map != p_tile_map) {
+ tile_map->set_selected_layer(-1);
+ }
+
+ // Disconnect to changes.
tile_map->disconnect("changed", callable_mp(this, &TileMapEditor::_tile_map_changed));
}
- // Change the edited object.
if (p_tile_map) {
+ // Change the edited object.
tile_map_id = p_tile_map->get_instance_id();
tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
// Connect to changes.
@@ -3497,8 +3748,10 @@ void TileMapEditor::edit(TileMap *p_tile_map) {
tile_map_id = ObjectID();
}
+ _update_layers_selection();
+
// Call the plugins.
- tile_map_editor_plugins[tabs->get_current_tab()]->edit(tile_map_id);
+ tile_map_editor_plugins[tabs->get_current_tab()]->edit(tile_map_id, tile_map_layer);
_tile_map_changed();
}
@@ -3506,6 +3759,10 @@ void TileMapEditor::edit(TileMap *p_tile_map) {
TileMapEditor::TileMapEditor() {
set_process_internal(true);
+ // Shortcuts.
+ ED_SHORTCUT("tiles_editor/select_next_layer", TTR("Select Next Tile Map Layer"), KEY_PAGEUP);
+ ED_SHORTCUT("tiles_editor/select_previous_layer", TTR("Select Previous Tile Map Layer"), KEY_PAGEDOWN);
+
// TileMap editor plugins
tile_map_editor_plugins.push_back(memnew(TileMapEditorTilesPlugin));
tile_map_editor_plugins.push_back(memnew(TileMapEditorTerrainsPlugin));
@@ -3519,18 +3776,46 @@ TileMapEditor::TileMapEditor() {
tabs->connect("tab_changed", callable_mp(this, &TileMapEditor::_tab_changed));
// --- TileMap toolbar ---
- tilemap_toolbar = memnew(HBoxContainer);
- tilemap_toolbar->set_h_size_flags(SIZE_EXPAND_FILL);
- tilemap_toolbar->add_child(tabs);
+ tile_map_toolbar = memnew(HBoxContainer);
+ tile_map_toolbar->set_h_size_flags(SIZE_EXPAND_FILL);
+
+ // Tabs.
+ tile_map_toolbar->add_child(tabs);
+
+ // Tabs toolbars.
for (int i = 0; i < tile_map_editor_plugins.size(); i++) {
tile_map_editor_plugins[i]->get_toolbar()->hide();
- tilemap_toolbar->add_child(tile_map_editor_plugins[i]->get_toolbar());
+ tile_map_toolbar->add_child(tile_map_editor_plugins[i]->get_toolbar());
}
// Wide empty separation control.
Control *h_empty_space = memnew(Control);
h_empty_space->set_h_size_flags(SIZE_EXPAND_FILL);
- tilemap_toolbar->add_child(h_empty_space);
+ tile_map_toolbar->add_child(h_empty_space);
+
+ // Layer selector.
+ layers_selection_popup = memnew(PopupMenu);
+ layers_selection_popup->connect("id_pressed", callable_mp(this, &TileMapEditor::_layers_selection_id_pressed));
+ layers_selection_popup->set_close_on_parent_focus(false);
+
+ layers_selection_button = memnew(Button);
+ layers_selection_button->set_toggle_mode(true);
+ layers_selection_button->connect("draw", callable_mp(this, &TileMapEditor::_layers_selection_button_draw));
+ layers_selection_button->connect("pressed", callable_mp(this, &TileMapEditor::_layers_selection_button_pressed));
+ layers_selection_button->connect("hidden", callable_mp((Window *)layers_selection_popup, &Popup::hide));
+ layers_selection_button->set_tooltip(TTR("Tile Map Layer"));
+ layers_selection_button->add_child(layers_selection_popup);
+ tile_map_toolbar->add_child(layers_selection_button);
+
+ toogle_highlight_selected_layer_button = memnew(Button);
+ toogle_highlight_selected_layer_button->set_flat(true);
+ toogle_highlight_selected_layer_button->set_toggle_mode(true);
+ toogle_highlight_selected_layer_button->set_pressed(true);
+ toogle_highlight_selected_layer_button->connect("pressed", callable_mp(this, &TileMapEditor::_update_layers_selection));
+ toogle_highlight_selected_layer_button->set_tooltip(TTR("Highlight Selected TileMap Layer"));
+ tile_map_toolbar->add_child(toogle_highlight_selected_layer_button);
+
+ tile_map_toolbar->add_child(memnew(VSeparator));
// Grid toggle.
toggle_grid_button = memnew(Button);
@@ -3538,14 +3823,14 @@ TileMapEditor::TileMapEditor() {
toggle_grid_button->set_toggle_mode(true);
toggle_grid_button->set_tooltip(TTR("Toggle grid visibility."));
toggle_grid_button->connect("toggled", callable_mp(this, &TileMapEditor::_on_grid_toggled));
- tilemap_toolbar->add_child(toggle_grid_button);
+ tile_map_toolbar->add_child(toggle_grid_button);
// Advanced settings menu button.
advanced_menu_button = memnew(MenuButton);
advanced_menu_button->set_flat(true);
advanced_menu_button->get_popup()->add_item(TTR("Automatically Replace Tiles with Proxies"));
advanced_menu_button->get_popup()->connect("id_pressed", callable_mp(this, &TileMapEditor::_advanced_menu_button_id_pressed));
- tilemap_toolbar->add_child(advanced_menu_button);
+ tile_map_toolbar->add_child(advanced_menu_button);
missing_tileset_label = memnew(Label);
missing_tileset_label->set_text(TTR("The edited TileMap node has no TileSet resource."));
@@ -3564,6 +3849,9 @@ TileMapEditor::TileMapEditor() {
}
_tab_changed(0);
+
+ // Registers UndoRedo inspector callback.
+ EditorNode::get_singleton()->get_editor_data().add_undo_redo_inspector_hook_callback(callable_mp(this, &TileMapEditor::_undo_redo_inspector_callback));
}
TileMapEditor::~TileMapEditor() {
diff --git a/editor/plugins/tiles/tile_map_editor.h b/editor/plugins/tiles/tile_map_editor.h
index 236774a06b..6e2f2ce2ba 100644
--- a/editor/plugins/tiles/tile_map_editor.h
+++ b/editor/plugins/tiles/tile_map_editor.h
@@ -47,7 +47,7 @@ public:
virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) { return false; };
virtual void forward_canvas_draw_over_viewport(Control *p_overlay){};
virtual void tile_set_changed(){};
- virtual void edit(ObjectID p_tile_map_id){};
+ virtual void edit(ObjectID p_tile_map_id, int p_tile_map_layer){};
};
class TileMapEditorTilesPlugin : public TileMapEditorPlugin {
@@ -56,7 +56,8 @@ class TileMapEditorTilesPlugin : public TileMapEditorPlugin {
private:
UndoRedo *undo_redo = EditorNode::get_undo_redo();
ObjectID tile_map_id;
- virtual void edit(ObjectID p_tile_map_id) override;
+ int tile_map_layer = -1;
+ virtual void edit(ObjectID p_tile_map_id, int p_tile_map_layer) override;
///// Toolbar /////
HBoxContainer *toolbar;
@@ -185,7 +186,8 @@ class TileMapEditorTerrainsPlugin : public TileMapEditorPlugin {
private:
UndoRedo *undo_redo = EditorNode::get_undo_redo();
ObjectID tile_map_id;
- virtual void edit(ObjectID p_tile_map_id) override;
+ int tile_map_layer = -1;
+ virtual void edit(ObjectID p_tile_map_id, int p_tile_map_layer) override;
// Toolbar.
HBoxContainer *toolbar;
@@ -258,9 +260,9 @@ private:
Map<Vector2i, TerrainsTilePattern> _wave_function_collapse(const Set<Vector2i> &p_to_replace, int p_terrain_set, const Set<TileMapEditorTerrainsPlugin::Constraint> p_constraints) const;
TileMapCell _get_random_tile_from_pattern(int p_terrain_set, TerrainsTilePattern p_terrain_tile_pattern) const;
Map<Vector2i, TileMapCell> _draw_terrains(const Map<Vector2i, TerrainsTilePattern> &p_to_paint, int p_terrain_set) const;
+ void _stop_dragging();
// Cached data.
-
TerrainsTilePattern _build_terrains_tile_pattern(TileData *p_tile_data);
LocalVector<Map<TerrainsTilePattern, Set<TileMapCell>>> per_terrain_terrains_tile_patterns_tiles;
LocalVector<LocalVector<Set<TerrainsTilePattern>>> per_terrain_terrains_tile_patterns;
@@ -300,12 +302,20 @@ private:
UndoRedo *undo_redo = EditorNode::get_undo_redo();
bool tileset_changed_needs_update = false;
ObjectID tile_map_id;
+ int tile_map_layer = -1;
// Vector to keep plugins.
Vector<TileMapEditorPlugin *> tile_map_editor_plugins;
// Toolbar.
- HBoxContainer *tilemap_toolbar;
+ HBoxContainer *tile_map_toolbar;
+
+ PopupMenu *layers_selection_popup;
+ Button *layers_selection_button;
+ Button *toogle_highlight_selected_layer_button;
+ void _layers_selection_button_draw();
+ void _layers_selection_button_pressed();
+ void _layers_selection_id_pressed(int p_id);
Button *toggle_grid_button;
void _on_grid_toggled(bool p_pressed);
@@ -313,19 +323,26 @@ private:
MenuButton *advanced_menu_button;
void _advanced_menu_button_id_pressed(int p_id);
- // Bottom panel
+ // Bottom panel.
Label *missing_tileset_label;
Tabs *tabs;
void _update_bottom_panel();
- // TileMap
+ // TileMap.
Ref<Texture2D> missing_tile_texture;
Ref<Texture2D> warning_pattern_texture;
- // CallBack
+ // CallBack.
void _tile_map_changed();
void _tab_changed(int p_tab_changed);
+ // Updates.
+ void _layers_select_next_or_previous(bool p_next);
+ void _update_layers_selection();
+
+ // Inspector undo/redo callback.
+ void _undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value);
+
protected:
void _notification(int p_what);
void _draw_shape(Control *p_control, Rect2 p_region, TileSet::TileShape p_shape, TileSet::TileOffsetAxis p_offset_axis, Color p_color);
@@ -335,7 +352,7 @@ public:
void forward_canvas_draw_over_viewport(Control *p_overlay);
void edit(TileMap *p_tile_map);
- Control *get_toolbar() { return tilemap_toolbar; };
+ Control *get_toolbar() { return tile_map_toolbar; };
TileMapEditor();
~TileMapEditor();
diff --git a/editor/plugins/tiles/tile_proxies_manager_dialog.cpp b/editor/plugins/tiles/tile_proxies_manager_dialog.cpp
index e5e7efa531..9e47a44b34 100644
--- a/editor/plugins/tiles/tile_proxies_manager_dialog.cpp
+++ b/editor/plugins/tiles/tile_proxies_manager_dialog.cpp
@@ -47,7 +47,7 @@ void TileProxiesManagerDialog::_menu_id_pressed(int p_id) {
}
void TileProxiesManagerDialog::_delete_selected_bindings() {
- undo_redo->create_action("Remove Tile Proxies");
+ undo_redo->create_action(TTR("Remove Tile Proxies"));
Vector<int> source_level_selected = source_level_list->get_selected_items();
for (int i = 0; i < source_level_selected.size(); i++) {
@@ -151,7 +151,7 @@ void TileProxiesManagerDialog::_add_button_pressed() {
Vector2i to_coords = to.get_atlas_coords();
if (from_coords.x >= 0 && from_coords.y >= 0 && to_coords.x >= 0 && to_coords.y >= 0) {
if (from.alternative_tile != TileSetSource::INVALID_TILE_ALTERNATIVE && to.alternative_tile != TileSetSource::INVALID_TILE_ALTERNATIVE) {
- undo_redo->create_action("Create Alternative-level Tile Proxy");
+ undo_redo->create_action(TTR("Create Alternative-level Tile Proxy"));
undo_redo->add_do_method(*tile_set, "set_alternative_level_tile_proxy", from.source_id, from.get_atlas_coords(), from.alternative_tile, to.source_id, to.get_atlas_coords(), to.alternative_tile);
if (tile_set->has_alternative_level_tile_proxy(from.source_id, from.get_atlas_coords(), from.alternative_tile)) {
Array a = tile_set->get_alternative_level_tile_proxy(from.source_id, from.get_atlas_coords(), from.alternative_tile);
@@ -160,7 +160,7 @@ void TileProxiesManagerDialog::_add_button_pressed() {
undo_redo->add_undo_method(*tile_set, "remove_alternative_level_tile_proxy", from.source_id, from.get_atlas_coords(), from.alternative_tile);
}
} else {
- undo_redo->create_action("Create Coords-level Tile Proxy");
+ undo_redo->create_action(TTR("Create Coords-level Tile Proxy"));
undo_redo->add_do_method(*tile_set, "set_coords_level_tile_proxy", from.source_id, from.get_atlas_coords(), to.source_id, to.get_atlas_coords());
if (tile_set->has_coords_level_tile_proxy(from.source_id, from.get_atlas_coords())) {
Array a = tile_set->get_coords_level_tile_proxy(from.source_id, from.get_atlas_coords());
@@ -170,7 +170,7 @@ void TileProxiesManagerDialog::_add_button_pressed() {
}
}
} else {
- undo_redo->create_action("Create source-level Tile Proxy");
+ undo_redo->create_action(TTR("Create source-level Tile Proxy"));
undo_redo->add_do_method(*tile_set, "set_source_level_tile_proxy", from.source_id, to.source_id);
if (tile_set->has_source_level_tile_proxy(from.source_id)) {
undo_redo->add_undo_method(*tile_set, "set_source_level_tile_proxy", to.source_id, tile_set->get_source_level_tile_proxy(from.source_id));
@@ -186,7 +186,7 @@ void TileProxiesManagerDialog::_add_button_pressed() {
}
void TileProxiesManagerDialog::_clear_invalid_button_pressed() {
- undo_redo->create_action("Delete All Invalid Tile Proxies");
+ undo_redo->create_action(TTR("Delete All Invalid Tile Proxies"));
undo_redo->add_do_method(*tile_set, "cleanup_invalid_tile_proxies");
@@ -213,7 +213,7 @@ void TileProxiesManagerDialog::_clear_invalid_button_pressed() {
}
void TileProxiesManagerDialog::_clear_all_button_pressed() {
- undo_redo->create_action("Delete All Tile Proxies");
+ undo_redo->create_action(TTR("Delete All Tile Proxies"));
undo_redo->add_do_method(*tile_set, "clear_tile_proxies");
diff --git a/editor/plugins/tiles/tile_proxies_manager_dialog.h b/editor/plugins/tiles/tile_proxies_manager_dialog.h
index f6898e960b..6849be2cd6 100644
--- a/editor/plugins/tiles/tile_proxies_manager_dialog.h
+++ b/editor/plugins/tiles/tile_proxies_manager_dialog.h
@@ -34,9 +34,9 @@
#include "editor/editor_node.h"
#include "editor/editor_properties.h"
+#include "scene/2d/tile_map.h"
#include "scene/gui/dialogs.h"
#include "scene/gui/item_list.h"
-#include "scene/resources/tile_set.h"
class TileProxiesManagerDialog : public ConfirmationDialog {
GDCLASS(TileProxiesManagerDialog, ConfirmationDialog);
diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp
index de910dfd32..432f48fa85 100644
--- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp
+++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp
@@ -303,9 +303,9 @@ void TileSetAtlasSourceEditor::AtlasTileProxyObject::_get_property_list(List<Pro
}
// Add only properties that are common to all tiles.
- for (List<PLData *>::Element *E = data_list.front(); E; E = E->next()) {
- if (E->get()->uses == tiles.size()) {
- p_list->push_back(E->get()->property_info);
+ for (const PLData *E : data_list) {
+ if (E->uses == tiles.size()) {
+ p_list->push_back(E->property_info);
}
}
}
diff --git a/editor/plugins/version_control_editor_plugin.cpp b/editor/plugins/version_control_editor_plugin.cpp
index 5804f54649..a9a36427db 100644
--- a/editor/plugins/version_control_editor_plugin.cpp
+++ b/editor/plugins/version_control_editor_plugin.cpp
@@ -31,6 +31,7 @@
#include "version_control_editor_plugin.h"
#include "core/object/script_language.h"
+#include "core/os/keyboard.h"
#include "editor/editor_file_system.h"
#include "editor/editor_node.h"
#include "editor/editor_scale.h"
@@ -292,6 +293,29 @@ void VersionControlEditorPlugin::_update_commit_button() {
commit_button->set_disabled(commit_message->get_text().strip_edges() == "");
}
+void VersionControlEditorPlugin::_commit_message_gui_input(const Ref<InputEvent> &p_event) {
+ if (!commit_message->has_focus()) {
+ return;
+ }
+ if (commit_message->get_text().strip_edges().is_empty()) {
+ // Do not allow empty commit messages.
+ return;
+ }
+ const Ref<InputEventKey> k = p_event;
+
+ if (k.is_valid() && k->is_pressed()) {
+ if (ED_IS_SHORTCUT("version_control/commit", p_event)) {
+ if (staged_files_count == 0) {
+ // Stage all files only when no files were previously staged.
+ _stage_all();
+ }
+ _send_commit_msg();
+ commit_message->accept_event();
+ return;
+ }
+ }
+}
+
void VersionControlEditorPlugin::register_editor() {
if (!EditorVCSInterface::get_singleton()) {
EditorNode::get_singleton()->add_control_to_dock(EditorNode::DOCK_SLOT_RIGHT_UL, version_commit_dock);
@@ -462,7 +486,9 @@ VersionControlEditorPlugin::VersionControlEditorPlugin() {
commit_message->set_custom_minimum_size(Size2(200, 100));
commit_message->set_wrap_enabled(true);
commit_message->connect("text_changed", callable_mp(this, &VersionControlEditorPlugin::_update_commit_button));
+ commit_message->connect("gui_input", callable_mp(this, &VersionControlEditorPlugin::_commit_message_gui_input));
commit_box_vbc->add_child(commit_message);
+ ED_SHORTCUT("version_control/commit", TTR("Commit"), KEY_MASK_CMD | KEY_ENTER);
commit_button = memnew(Button);
commit_button->set_text(TTR("Commit Changes"));
diff --git a/editor/plugins/version_control_editor_plugin.h b/editor/plugins/version_control_editor_plugin.h
index 7d7c66a7ee..d2ba63c86c 100644
--- a/editor/plugins/version_control_editor_plugin.h
+++ b/editor/plugins/version_control_editor_plugin.h
@@ -111,6 +111,7 @@ private:
void _update_stage_status();
void _update_commit_status();
void _update_commit_button();
+ void _commit_message_gui_input(const Ref<InputEvent> &p_event);
friend class EditorVCSInterface;
diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp
index 4ac22d0e3b..1acdb4f8b9 100644
--- a/editor/plugins/visual_shader_editor_plugin.cpp
+++ b/editor/plugins/visual_shader_editor_plugin.cpp
@@ -35,7 +35,6 @@
#include "core/io/resource_loader.h"
#include "core/math/math_defs.h"
#include "core/os/keyboard.h"
-#include "core/version.h"
#include "editor/editor_log.h"
#include "editor/editor_properties.h"
#include "editor/editor_scale.h"
@@ -666,8 +665,8 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) {
if (valid_left) {
name_left = vsnode->get_input_port_name(i);
port_left = vsnode->get_input_port_type(i);
- for (List<VisualShader::Connection>::Element *E = connections.front(); E; E = E->next()) {
- if (E->get().to_node == p_id && E->get().to_port == j) {
+ for (const VisualShader::Connection &E : connections) {
+ if (E.to_node == p_id && E.to_port == j) {
port_left_used = true;
}
}
@@ -899,11 +898,11 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) {
expression_box->set_syntax_highlighter(expression_syntax_highlighter);
expression_box->add_theme_color_override("background_color", background_color);
- for (List<String>::Element *E = VisualShaderEditor::get_singleton()->keyword_list.front(); E; E = E->next()) {
- if (ShaderLanguage::is_control_flow_keyword(E->get())) {
- expression_syntax_highlighter->add_keyword_color(E->get(), control_flow_keyword_color);
+ for (const String &E : VisualShaderEditor::get_singleton()->keyword_list) {
+ if (ShaderLanguage::is_control_flow_keyword(E)) {
+ expression_syntax_highlighter->add_keyword_color(E, control_flow_keyword_color);
} else {
- expression_syntax_highlighter->add_keyword_color(E->get(), keyword_color);
+ expression_syntax_highlighter->add_keyword_color(E, keyword_color);
}
}
@@ -921,6 +920,10 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) {
expression_box->add_comment_delimiter("/*", "*/", false);
expression_box->add_comment_delimiter("//", "", true);
+ if (!expression_box->has_auto_brace_completion_open_key("/*")) {
+ expression_box->add_auto_brace_completion_pair("/*", "*/");
+ }
+
expression_box->set_text(expression);
expression_box->set_context_menu_enabled(false);
expression_box->set_draw_line_numbers(true);
@@ -961,7 +964,7 @@ void VisualShaderGraphPlugin::connect_nodes(VisualShader::Type p_type, int p_fro
void VisualShaderGraphPlugin::disconnect_nodes(VisualShader::Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port) {
if (visual_shader->get_shader_type() == p_type) {
VisualShaderEditor::get_singleton()->graph->disconnect_node(itos(p_from_node), p_from_port, itos(p_to_node), p_to_port);
- for (List<VisualShader::Connection>::Element *E = connections.front(); E; E = E->next()) {
+ for (const List<VisualShader::Connection>::Element *E = connections.front(); E; E = E->next()) {
if (E->get().from_node == p_from_node && E->get().from_port == p_from_port && E->get().to_node == p_to_node && E->get().to_port == p_to_port) {
connections.erase(E);
break;
@@ -995,9 +998,23 @@ void VisualShaderEditor::edit(VisualShader *p_visual_shader) {
visual_shader->connect("changed", callable_mp(this, &VisualShaderEditor::_update_preview));
}
#ifndef DISABLE_DEPRECATED
- String version = VERSION_BRANCH;
- if (visual_shader->get_version() != version) {
- visual_shader->update_version(version);
+ Dictionary engine_version = Engine::get_singleton()->get_version_info();
+ static Array components;
+ if (components.is_empty()) {
+ components.push_back("major");
+ components.push_back("minor");
+ }
+ const Dictionary vs_version = visual_shader->get_engine_version();
+ if (!vs_version.has_all(components)) {
+ visual_shader->update_engine_version(engine_version);
+ } else {
+ for (int i = 0; i < components.size(); i++) {
+ if (vs_version[components[i]] != engine_version[components[i]]) {
+ visual_shader->update_engine_version(engine_version);
+ print_line(vformat(TTR("The shader (\"%s\") has been updated to correspond Godot %s.%s version."), visual_shader->get_path(), engine_version["major"], engine_version["minor"]));
+ break;
+ }
+ }
}
#endif
visual_shader->set_graph_offset(graph->get_scroll_ofs() / EDSCALE);
@@ -1470,11 +1487,11 @@ void VisualShaderEditor::_update_graph() {
graph_plugin->make_dirty(false);
- for (List<VisualShader::Connection>::Element *E = connections.front(); E; E = E->next()) {
- int from = E->get().from_node;
- int from_idx = E->get().from_port;
- int to = E->get().to_node;
- int to_idx = E->get().to_port;
+ for (const VisualShader::Connection &E : connections) {
+ int from = E.from_node;
+ int from_idx = E.from_port;
+ int to = E.to_node;
+ int to_idx = E.to_port;
graph->connect_node(itos(from), from_idx, itos(to), to_idx);
}
@@ -1634,11 +1651,11 @@ void VisualShaderEditor::_expand_output_port(int p_node, int p_port, bool p_expa
List<VisualShader::Connection> conns;
visual_shader->get_node_connections(type, &conns);
- for (List<VisualShader::Connection>::Element *E = conns.front(); E; E = E->next()) {
- int from_node = E->get().from_node;
- int from_port = E->get().from_port;
- int to_node = E->get().to_node;
- int to_port = E->get().to_port;
+ for (const VisualShader::Connection &E : conns) {
+ int from_node = E.from_node;
+ int from_port = E.from_port;
+ int to_node = E.to_node;
+ int to_port = E.to_port;
if (from_node == p_node) {
if (p_expand) {
@@ -1708,11 +1725,11 @@ void VisualShaderEditor::_remove_input_port(int p_node, int p_port) {
List<VisualShader::Connection> conns;
visual_shader->get_node_connections(type, &conns);
- for (List<VisualShader::Connection>::Element *E = conns.front(); E; E = E->next()) {
- int from_node = E->get().from_node;
- int from_port = E->get().from_port;
- int to_node = E->get().to_node;
- int to_port = E->get().to_port;
+ for (const VisualShader::Connection &E : conns) {
+ int from_node = E.from_node;
+ int from_port = E.from_port;
+ int to_node = E.to_node;
+ int to_port = E.to_port;
if (to_node == p_node) {
if (to_port == p_port) {
@@ -1757,11 +1774,11 @@ void VisualShaderEditor::_remove_output_port(int p_node, int p_port) {
List<VisualShader::Connection> conns;
visual_shader->get_node_connections(type, &conns);
- for (List<VisualShader::Connection>::Element *E = conns.front(); E; E = E->next()) {
- int from_node = E->get().from_node;
- int from_port = E->get().from_port;
- int to_node = E->get().to_node;
- int to_port = E->get().to_port;
+ for (const VisualShader::Connection &E : conns) {
+ int from_node = E.from_node;
+ int from_port = E.from_port;
+ int to_node = E.to_node;
+ int to_port = E.to_port;
if (from_node == p_node) {
if (from_port == p_port) {
@@ -2518,11 +2535,11 @@ void VisualShaderEditor::_nodes_dragged() {
undo_redo->create_action(TTR("Node(s) Moved"));
- for (List<DragOp>::Element *E = drag_buffer.front(); E; E = E->next()) {
- undo_redo->add_do_method(visual_shader.ptr(), "set_node_position", E->get().type, E->get().node, E->get().to);
- undo_redo->add_undo_method(visual_shader.ptr(), "set_node_position", E->get().type, E->get().node, E->get().from);
- undo_redo->add_do_method(graph_plugin.ptr(), "set_node_position", E->get().type, E->get().node, E->get().to);
- undo_redo->add_undo_method(graph_plugin.ptr(), "set_node_position", E->get().type, E->get().node, E->get().from);
+ for (const DragOp &E : drag_buffer) {
+ undo_redo->add_do_method(visual_shader.ptr(), "set_node_position", E.type, E.node, E.to);
+ undo_redo->add_undo_method(visual_shader.ptr(), "set_node_position", E.type, E.node, E.from);
+ undo_redo->add_do_method(graph_plugin.ptr(), "set_node_position", E.type, E.node, E.to);
+ undo_redo->add_undo_method(graph_plugin.ptr(), "set_node_position", E.type, E.node, E.from);
}
drag_buffer.clear();
@@ -2544,12 +2561,12 @@ void VisualShaderEditor::_connection_request(const String &p_from, int p_from_in
List<VisualShader::Connection> conns;
visual_shader->get_node_connections(type, &conns);
- for (List<VisualShader::Connection>::Element *E = conns.front(); E; E = E->next()) {
- if (E->get().to_node == to && E->get().to_port == p_to_index) {
- undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port);
- undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes", type, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port);
- undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port);
- undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port);
+ for (const VisualShader::Connection &E : conns) {
+ if (E.to_node == to && E.to_port == p_to_index) {
+ undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port);
+ undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port);
+ undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port);
+ undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port);
}
}
@@ -2597,22 +2614,22 @@ void VisualShaderEditor::_delete_nodes(int p_type, const List<int> &p_nodes) {
List<VisualShader::Connection> conns;
visual_shader->get_node_connections(type, &conns);
- for (const List<int>::Element *F = p_nodes.front(); F; F = F->next()) {
- for (List<VisualShader::Connection>::Element *E = conns.front(); E; E = E->next()) {
- if (E->get().from_node == F->get() || E->get().to_node == F->get()) {
- undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port);
+ for (const int &F : p_nodes) {
+ for (const VisualShader::Connection &E : conns) {
+ if (E.from_node == F || E.to_node == F) {
+ undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port);
}
}
}
Set<String> uniform_names;
- for (const List<int>::Element *F = p_nodes.front(); F; F = F->next()) {
- Ref<VisualShaderNode> node = visual_shader->get_node(type, F->get());
+ for (const int &F : p_nodes) {
+ Ref<VisualShaderNode> node = visual_shader->get_node(type, F);
- undo_redo->add_do_method(visual_shader.ptr(), "remove_node", type, F->get());
- undo_redo->add_undo_method(visual_shader.ptr(), "add_node", type, node, visual_shader->get_node_position(type, F->get()), F->get());
- undo_redo->add_undo_method(graph_plugin.ptr(), "add_node", type, F->get());
+ undo_redo->add_do_method(visual_shader.ptr(), "remove_node", type, F);
+ undo_redo->add_undo_method(visual_shader.ptr(), "add_node", type, node, visual_shader->get_node_position(type, F), F);
+ undo_redo->add_undo_method(graph_plugin.ptr(), "add_node", type, F);
undo_redo->add_do_method(this, "_clear_buffer");
undo_redo->add_undo_method(this, "_clear_buffer");
@@ -2638,28 +2655,28 @@ void VisualShaderEditor::_delete_nodes(int p_type, const List<int> &p_nodes) {
}
List<VisualShader::Connection> used_conns;
- for (const List<int>::Element *F = p_nodes.front(); F; F = F->next()) {
- for (List<VisualShader::Connection>::Element *E = conns.front(); E; E = E->next()) {
- if (E->get().from_node == F->get() || E->get().to_node == F->get()) {
+ for (const int &F : p_nodes) {
+ for (const VisualShader::Connection &E : conns) {
+ if (E.from_node == F || E.to_node == F) {
bool cancel = false;
for (List<VisualShader::Connection>::Element *R = used_conns.front(); R; R = R->next()) {
- if (R->get().from_node == E->get().from_node && R->get().from_port == E->get().from_port && R->get().to_node == E->get().to_node && R->get().to_port == E->get().to_port) {
+ if (R->get().from_node == E.from_node && R->get().from_port == E.from_port && R->get().to_node == E.to_node && R->get().to_port == E.to_port) {
cancel = true; // to avoid ERR_ALREADY_EXISTS warning
break;
}
}
if (!cancel) {
- undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes", type, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port);
- undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port);
- used_conns.push_back(E->get());
+ undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port);
+ undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port);
+ used_conns.push_back(E);
}
}
}
}
// delete nodes from the graph
- for (const List<int>::Element *F = p_nodes.front(); F; F = F->next()) {
- undo_redo->add_do_method(graph_plugin.ptr(), "remove_node", type, F->get());
+ for (const int &F : p_nodes) {
+ undo_redo->add_do_method(graph_plugin.ptr(), "remove_node", type, F);
}
// update uniform refs if any uniform has been deleted
@@ -3094,11 +3111,11 @@ void VisualShaderEditor::_notification(int p_what) {
preview_text->add_theme_color_override("background_color", background_color);
- for (List<String>::Element *E = keyword_list.front(); E; E = E->next()) {
- if (ShaderLanguage::is_control_flow_keyword(E->get())) {
- syntax_highlighter->add_keyword_color(E->get(), control_flow_keyword_color);
+ for (const String &E : keyword_list) {
+ if (ShaderLanguage::is_control_flow_keyword(E)) {
+ syntax_highlighter->add_keyword_color(E, control_flow_keyword_color);
} else {
- syntax_highlighter->add_keyword_color(E->get(), keyword_color);
+ syntax_highlighter->add_keyword_color(E, keyword_color);
}
}
@@ -3206,9 +3223,9 @@ void VisualShaderEditor::_dup_paste_nodes(int p_type, int p_pasted_type, List<in
Map<int, int> connection_remap;
Set<int> unsupported_set;
- for (List<int>::Element *E = r_nodes.front(); E; E = E->next()) {
- connection_remap[E->get()] = id_from;
- Ref<VisualShaderNode> node = visual_shader->get_node(pasted_type, E->get());
+ for (int &E : r_nodes) {
+ connection_remap[E] = id_from;
+ Ref<VisualShaderNode> node = visual_shader->get_node(pasted_type, E);
bool unsupported = false;
for (int i = 0; i < add_options.size(); i++) {
@@ -3220,13 +3237,13 @@ void VisualShaderEditor::_dup_paste_nodes(int p_type, int p_pasted_type, List<in
}
}
if (unsupported) {
- unsupported_set.insert(E->get());
+ unsupported_set.insert(E);
continue;
}
Ref<VisualShaderNode> dupli = node->duplicate();
- undo_redo->add_do_method(visual_shader.ptr(), "add_node", type, dupli, visual_shader->get_node_position(pasted_type, E->get()) + p_offset, id_from);
+ undo_redo->add_do_method(visual_shader.ptr(), "add_node", type, dupli, visual_shader->get_node_position(pasted_type, E) + p_offset, id_from);
undo_redo->add_do_method(graph_plugin.ptr(), "add_node", type, id_from);
// duplicate size, inputs and outputs if node is group
@@ -3249,19 +3266,19 @@ void VisualShaderEditor::_dup_paste_nodes(int p_type, int p_pasted_type, List<in
List<VisualShader::Connection> conns;
visual_shader->get_node_connections(pasted_type, &conns);
- for (List<VisualShader::Connection>::Element *E = conns.front(); E; E = E->next()) {
- if (unsupported_set.has(E->get().from_node) || unsupported_set.has(E->get().to_node)) {
+ for (const VisualShader::Connection &E : conns) {
+ if (unsupported_set.has(E.from_node) || unsupported_set.has(E.to_node)) {
continue;
}
- if (connection_remap.has(E->get().from_node) && connection_remap.has(E->get().to_node)) {
- undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes", type, connection_remap[E->get().from_node], E->get().from_port, connection_remap[E->get().to_node], E->get().to_port);
- undo_redo->add_do_method(graph_plugin.ptr(), "connect_nodes", type, connection_remap[E->get().from_node], E->get().from_port, connection_remap[E->get().to_node], E->get().to_port);
- undo_redo->add_undo_method(graph_plugin.ptr(), "disconnect_nodes", type, connection_remap[E->get().from_node], E->get().from_port, connection_remap[E->get().to_node], E->get().to_port);
+ if (connection_remap.has(E.from_node) && connection_remap.has(E.to_node)) {
+ undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes", type, connection_remap[E.from_node], E.from_port, connection_remap[E.to_node], E.to_port);
+ undo_redo->add_do_method(graph_plugin.ptr(), "connect_nodes", type, connection_remap[E.from_node], E.from_port, connection_remap[E.to_node], E.to_port);
+ undo_redo->add_undo_method(graph_plugin.ptr(), "disconnect_nodes", type, connection_remap[E.from_node], E.from_port, connection_remap[E.to_node], E.to_port);
}
}
id_from = base_id;
- for (List<int>::Element *E = r_nodes.front(); E; E = E->next()) {
+ for (int i = 0; i < r_nodes.size(); i++) {
undo_redo->add_undo_method(visual_shader.ptr(), "remove_node", type, id_from);
undo_redo->add_undo_method(graph_plugin.ptr(), "remove_node", type, id_from);
id_from++;
@@ -3399,17 +3416,17 @@ void VisualShaderEditor::_input_select_item(Ref<VisualShaderNodeInput> p_input,
if (type_changed) {
List<VisualShader::Connection> conns;
visual_shader->get_node_connections(type, &conns);
- for (List<VisualShader::Connection>::Element *E = conns.front(); E; E = E->next()) {
- if (E->get().from_node == id) {
- if (visual_shader->is_port_types_compatible(p_input->get_input_type_by_name(p_name), visual_shader->get_node(type, E->get().to_node)->get_input_port_type(E->get().to_port))) {
- undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes", type, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port);
- undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes", type, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port);
+ for (const VisualShader::Connection &E : conns) {
+ if (E.from_node == id) {
+ if (visual_shader->is_port_types_compatible(p_input->get_input_type_by_name(p_name), visual_shader->get_node(type, E.to_node)->get_input_port_type(E.to_port))) {
+ undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port);
+ undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port);
continue;
}
- undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port);
- undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes", type, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port);
- undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port);
- undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port);
+ undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port);
+ undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port);
+ undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port);
+ undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port);
}
}
}
@@ -3445,15 +3462,15 @@ void VisualShaderEditor::_uniform_select_item(Ref<VisualShaderNodeUniformRef> p_
if (type_changed) {
List<VisualShader::Connection> conns;
visual_shader->get_node_connections(type, &conns);
- for (List<VisualShader::Connection>::Element *E = conns.front(); E; E = E->next()) {
- if (E->get().from_node == id) {
- if (visual_shader->is_port_types_compatible(p_uniform_ref->get_uniform_type_by_name(p_name), visual_shader->get_node(type, E->get().to_node)->get_input_port_type(E->get().to_port))) {
+ for (const VisualShader::Connection &E : conns) {
+ if (E.from_node == id) {
+ if (visual_shader->is_port_types_compatible(p_uniform_ref->get_uniform_type_by_name(p_name), visual_shader->get_node(type, E.to_node)->get_input_port_type(E.to_port))) {
continue;
}
- undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port);
- undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes", type, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port);
- undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port);
- undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port);
+ undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port);
+ undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port);
+ undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port);
+ undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port);
}
}
}
@@ -4697,7 +4714,7 @@ public:
UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo();
updating = true;
- undo_redo->create_action(TTR("Edit Visual Property") + ": " + p_property, UndoRedo::MERGE_ENDS);
+ undo_redo->create_action(TTR("Edit Visual Property:") + " " + p_property, UndoRedo::MERGE_ENDS);
undo_redo->add_do_property(node.ptr(), p_property, p_value);
undo_redo->add_undo_property(node.ptr(), p_property, node->get(p_property));
@@ -4825,10 +4842,10 @@ Control *VisualShaderNodePluginDefault::create_editor(const Ref<Resource> &p_par
Vector<PropertyInfo> pinfo;
- for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
+ for (const PropertyInfo &E : props) {
for (int i = 0; i < properties.size(); i++) {
- if (E->get().name == String(properties[i])) {
- pinfo.push_back(E->get());
+ if (E.name == String(properties[i])) {
+ pinfo.push_back(E);
}
}
}
@@ -4894,9 +4911,9 @@ void EditorPropertyShaderMode::_option_selected(int p_which) {
VisualShader::Type type = VisualShader::Type(i);
List<VisualShader::Connection> conns;
visual_shader->get_node_connections(type, &conns);
- for (List<VisualShader::Connection>::Element *E = conns.front(); E; E = E->next()) {
- if (E->get().to_node == VisualShader::NODE_ID_OUTPUT) {
- undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes", type, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port);
+ for (const VisualShader::Connection &E : conns) {
+ if (E.to_node == VisualShader::NODE_ID_OUTPUT) {
+ undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port);
}
}
}
@@ -4918,9 +4935,9 @@ void EditorPropertyShaderMode::_option_selected(int p_which) {
List<PropertyInfo> props;
visual_shader->get_property_list(&props);
- for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
- if (E->get().name.begins_with("flags/") || E->get().name.begins_with("modes/")) {
- undo_redo->add_undo_property(visual_shader.ptr(), E->get().name, visual_shader->get(E->get().name));
+ for (const PropertyInfo &E : props) {
+ if (E.name.begins_with("flags/") || E.name.begins_with("modes/")) {
+ undo_redo->add_undo_property(visual_shader.ptr(), E.name, visual_shader->get(E.name));
}
}
@@ -5024,8 +5041,8 @@ void VisualShaderNodePortPreview::_shader_changed() {
if (src_mat && src_mat->get_shader().is_valid()) {
List<PropertyInfo> params;
src_mat->get_shader()->get_param_list(&params);
- for (List<PropertyInfo>::Element *E = params.front(); E; E = E->next()) {
- material->set(E->get().name, src_mat->get(E->get().name));
+ for (const PropertyInfo &E : params) {
+ material->set(E.name, src_mat->get(E.name));
}
}
}