Path: blob/master/editor/scene/3d/node_3d_editor_plugin.cpp
9902 views
/**************************************************************************/1/* node_3d_editor_plugin.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "node_3d_editor_plugin.h"3132#include "core/config/project_settings.h"33#include "core/input/input.h"34#include "core/input/input_map.h"35#include "core/math/math_funcs.h"36#include "core/math/projection.h"37#include "core/os/keyboard.h"38#include "editor/animation/animation_player_editor_plugin.h"39#include "editor/debugger/editor_debugger_node.h"40#include "editor/docks/scene_tree_dock.h"41#include "editor/editor_main_screen.h"42#include "editor/editor_node.h"43#include "editor/editor_string_names.h"44#include "editor/editor_undo_redo_manager.h"45#include "editor/gui/editor_spin_slider.h"46#include "editor/run/editor_run_bar.h"47#include "editor/scene/3d/gizmos/audio_listener_3d_gizmo_plugin.h"48#include "editor/scene/3d/gizmos/audio_stream_player_3d_gizmo_plugin.h"49#include "editor/scene/3d/gizmos/camera_3d_gizmo_plugin.h"50#include "editor/scene/3d/gizmos/cpu_particles_3d_gizmo_plugin.h"51#include "editor/scene/3d/gizmos/decal_gizmo_plugin.h"52#include "editor/scene/3d/gizmos/fog_volume_gizmo_plugin.h"53#include "editor/scene/3d/gizmos/geometry_instance_3d_gizmo_plugin.h"54#include "editor/scene/3d/gizmos/gpu_particles_3d_gizmo_plugin.h"55#include "editor/scene/3d/gizmos/gpu_particles_collision_3d_gizmo_plugin.h"56#include "editor/scene/3d/gizmos/label_3d_gizmo_plugin.h"57#include "editor/scene/3d/gizmos/light_3d_gizmo_plugin.h"58#include "editor/scene/3d/gizmos/lightmap_gi_gizmo_plugin.h"59#include "editor/scene/3d/gizmos/lightmap_probe_gizmo_plugin.h"60#include "editor/scene/3d/gizmos/marker_3d_gizmo_plugin.h"61#include "editor/scene/3d/gizmos/mesh_instance_3d_gizmo_plugin.h"62#include "editor/scene/3d/gizmos/occluder_instance_3d_gizmo_plugin.h"63#include "editor/scene/3d/gizmos/particles_3d_emission_shape_gizmo_plugin.h"64#include "editor/scene/3d/gizmos/physics/collision_object_3d_gizmo_plugin.h"65#include "editor/scene/3d/gizmos/physics/collision_polygon_3d_gizmo_plugin.h"66#include "editor/scene/3d/gizmos/physics/collision_shape_3d_gizmo_plugin.h"67#include "editor/scene/3d/gizmos/physics/joint_3d_gizmo_plugin.h"68#include "editor/scene/3d/gizmos/physics/physics_bone_3d_gizmo_plugin.h"69#include "editor/scene/3d/gizmos/physics/ray_cast_3d_gizmo_plugin.h"70#include "editor/scene/3d/gizmos/physics/shape_cast_3d_gizmo_plugin.h"71#include "editor/scene/3d/gizmos/physics/soft_body_3d_gizmo_plugin.h"72#include "editor/scene/3d/gizmos/physics/spring_arm_3d_gizmo_plugin.h"73#include "editor/scene/3d/gizmos/physics/vehicle_body_3d_gizmo_plugin.h"74#include "editor/scene/3d/gizmos/reflection_probe_gizmo_plugin.h"75#include "editor/scene/3d/gizmos/spring_bone_3d_gizmo_plugin.h"76#include "editor/scene/3d/gizmos/sprite_base_3d_gizmo_plugin.h"77#include "editor/scene/3d/gizmos/visible_on_screen_notifier_3d_gizmo_plugin.h"78#include "editor/scene/3d/gizmos/voxel_gi_gizmo_plugin.h"79#include "editor/scene/3d/node_3d_editor_gizmos.h"80#include "editor/settings/editor_settings.h"81#include "editor/translations/editor_translation_preview_button.h"82#include "editor/translations/editor_translation_preview_menu.h"83#include "scene/3d/audio_stream_player_3d.h"84#include "scene/3d/camera_3d.h"85#include "scene/3d/decal.h"86#include "scene/3d/light_3d.h"87#include "scene/3d/mesh_instance_3d.h"88#include "scene/3d/physics/collision_shape_3d.h"89#include "scene/3d/physics/physics_body_3d.h"90#include "scene/3d/sprite_3d.h"91#include "scene/3d/visual_instance_3d.h"92#include "scene/3d/world_environment.h"93#include "scene/gui/center_container.h"94#include "scene/gui/color_picker.h"95#include "scene/gui/flow_container.h"96#include "scene/gui/separator.h"97#include "scene/gui/split_container.h"98#include "scene/gui/subviewport_container.h"99#include "scene/resources/3d/sky_material.h"100#include "scene/resources/packed_scene.h"101#include "scene/resources/surface_tool.h"102103constexpr real_t DISTANCE_DEFAULT = 4;104105constexpr real_t GIZMO_ARROW_SIZE = 0.35;106constexpr real_t GIZMO_RING_HALF_WIDTH = 0.1;107constexpr real_t GIZMO_PLANE_SIZE = 0.2;108constexpr real_t GIZMO_PLANE_DST = 0.3;109constexpr real_t GIZMO_CIRCLE_SIZE = 1.1;110constexpr real_t GIZMO_SCALE_OFFSET = GIZMO_CIRCLE_SIZE + 0.3;111constexpr real_t GIZMO_ARROW_OFFSET = GIZMO_CIRCLE_SIZE + 0.3;112113constexpr real_t ZOOM_FREELOOK_MIN = 0.01;114constexpr real_t ZOOM_FREELOOK_MULTIPLIER = 1.08;115constexpr real_t ZOOM_FREELOOK_INDICATOR_DELAY_S = 1.5;116117#ifdef REAL_T_IS_DOUBLE118constexpr double ZOOM_FREELOOK_MAX = 1'000'000'000'000;119#else120constexpr float ZOOM_FREELOOK_MAX = 10'000;121#endif122123constexpr real_t MIN_Z = 0.01;124constexpr real_t MAX_Z = 1000000.0;125126constexpr real_t MIN_FOV = 0.01;127constexpr real_t MAX_FOV = 179;128129void ViewportNavigationControl::_notification(int p_what) {130switch (p_what) {131case NOTIFICATION_DRAW: {132if (viewport != nullptr) {133_draw();134_update_navigation();135}136} break;137138case NOTIFICATION_MOUSE_ENTER: {139hovered = true;140queue_redraw();141} break;142143case NOTIFICATION_MOUSE_EXIT: {144hovered = false;145queue_redraw();146} break;147}148}149150void ViewportNavigationControl::_draw() {151if (nav_mode == Node3DEditorViewport::NAVIGATION_NONE) {152return;153}154155Vector2 center = get_size() / 2.0;156float radius = get_size().x / 2.0;157158const bool focused = focused_index != -1;159draw_circle(center, radius, Color(0.5, 0.5, 0.5, focused || hovered ? 0.35 : 0.15));160161const Color c = focused ? Color(0.9, 0.9, 0.9, 0.9) : Color(0.5, 0.5, 0.5, 0.25);162163Vector2 circle_pos = focused ? center.move_toward(focused_pos, radius) : center;164165draw_circle(circle_pos, AXIS_CIRCLE_RADIUS, c);166draw_circle(circle_pos, AXIS_CIRCLE_RADIUS * 0.8, c.darkened(0.4));167}168169void ViewportNavigationControl::_process_click(int p_index, Vector2 p_position, bool p_pressed) {170hovered = false;171queue_redraw();172173if (focused_index != -1 && focused_index != p_index) {174return;175}176if (p_pressed) {177if (p_position.distance_to(get_size() / 2.0) < get_size().x / 2.0) {178focused_pos = p_position;179focused_index = p_index;180queue_redraw();181}182} else {183focused_index = -1;184if (Input::get_singleton()->get_mouse_mode() == Input::MOUSE_MODE_CAPTURED) {185Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE);186Input::get_singleton()->warp_mouse(focused_mouse_start);187}188}189}190191void ViewportNavigationControl::_process_drag(int p_index, Vector2 p_position, Vector2 p_relative_position) {192if (focused_index == p_index) {193if (Input::get_singleton()->get_mouse_mode() == Input::MOUSE_MODE_VISIBLE) {194Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_CAPTURED);195focused_mouse_start = p_position;196}197focused_pos += p_relative_position;198queue_redraw();199}200}201202void ViewportNavigationControl::gui_input(const Ref<InputEvent> &p_event) {203// Mouse events204const Ref<InputEventMouseButton> mouse_button = p_event;205if (mouse_button.is_valid() && mouse_button->get_button_index() == MouseButton::LEFT) {206_process_click(100, mouse_button->get_position(), mouse_button->is_pressed());207}208209const Ref<InputEventMouseMotion> mouse_motion = p_event;210if (mouse_motion.is_valid()) {211_process_drag(100, mouse_motion->get_global_position(), viewport->_get_warped_mouse_motion(mouse_motion));212}213214// Touch events215const Ref<InputEventScreenTouch> screen_touch = p_event;216if (screen_touch.is_valid()) {217_process_click(screen_touch->get_index(), screen_touch->get_position(), screen_touch->is_pressed());218}219220const Ref<InputEventScreenDrag> screen_drag = p_event;221if (screen_drag.is_valid()) {222_process_drag(screen_drag->get_index(), screen_drag->get_position(), screen_drag->get_relative());223}224}225226void ViewportNavigationControl::_update_navigation() {227if (focused_index == -1) {228return;229}230231Vector2 delta = focused_pos - (get_size() / 2.0);232Vector2 delta_normalized = delta.normalized();233switch (nav_mode) {234case Node3DEditorViewport::NavigationMode::NAVIGATION_MOVE: {235real_t speed_multiplier = MIN(delta.length() / (get_size().x * 100.0), 3.0);236real_t speed = viewport->freelook_speed * speed_multiplier;237238const Node3DEditorViewport::FreelookNavigationScheme navigation_scheme = (Node3DEditorViewport::FreelookNavigationScheme)EDITOR_GET("editors/3d/freelook/freelook_navigation_scheme").operator int();239240Vector3 forward;241if (navigation_scheme == Node3DEditorViewport::FreelookNavigationScheme::FREELOOK_FULLY_AXIS_LOCKED) {242// Forward/backward keys will always go straight forward/backward, never moving on the Y axis.243forward = Vector3(0, 0, delta_normalized.y).rotated(Vector3(0, 1, 0), viewport->camera->get_rotation().y);244} else {245// Forward/backward keys will be relative to the camera pitch.246forward = viewport->camera->get_transform().basis.xform(Vector3(0, 0, delta_normalized.y));247}248249const Vector3 right = viewport->camera->get_transform().basis.xform(Vector3(delta_normalized.x, 0, 0));250251const Vector3 direction = forward + right;252const Vector3 motion = direction * speed;253viewport->cursor.pos += motion;254viewport->cursor.eye_pos += motion;255} break;256257case Node3DEditorViewport::NavigationMode::NAVIGATION_LOOK: {258real_t speed_multiplier = MIN(delta.length() / (get_size().x * 2.5), 3.0);259real_t speed = viewport->freelook_speed * speed_multiplier;260viewport->_nav_look(nullptr, delta_normalized * speed);261} break;262263case Node3DEditorViewport::NAVIGATION_PAN: {264real_t speed_multiplier = MIN(delta.length() / (get_size().x), 3.0);265real_t speed = viewport->freelook_speed * speed_multiplier;266viewport->_nav_pan(nullptr, -delta_normalized * speed);267} break;268case Node3DEditorViewport::NAVIGATION_ZOOM: {269real_t speed_multiplier = MIN(delta.length() / (get_size().x), 3.0);270real_t speed = viewport->freelook_speed * speed_multiplier;271viewport->_nav_zoom(nullptr, delta_normalized * speed);272} break;273case Node3DEditorViewport::NAVIGATION_ORBIT: {274real_t speed_multiplier = MIN(delta.length() / (get_size().x), 3.0);275real_t speed = viewport->freelook_speed * speed_multiplier;276viewport->_nav_orbit(nullptr, delta_normalized * speed);277} break;278case Node3DEditorViewport::NAVIGATION_NONE: {279} break;280}281}282283void ViewportNavigationControl::set_navigation_mode(Node3DEditorViewport::NavigationMode p_nav_mode) {284nav_mode = p_nav_mode;285}286287void ViewportNavigationControl::set_viewport(Node3DEditorViewport *p_viewport) {288viewport = p_viewport;289}290291void ViewportRotationControl::_notification(int p_what) {292switch (p_what) {293case NOTIFICATION_ENTER_TREE: {294axis_menu_options.clear();295axis_menu_options.push_back(Node3DEditorViewport::VIEW_RIGHT);296axis_menu_options.push_back(Node3DEditorViewport::VIEW_TOP);297axis_menu_options.push_back(Node3DEditorViewport::VIEW_FRONT);298axis_menu_options.push_back(Node3DEditorViewport::VIEW_LEFT);299axis_menu_options.push_back(Node3DEditorViewport::VIEW_BOTTOM);300axis_menu_options.push_back(Node3DEditorViewport::VIEW_REAR);301302axis_colors.clear();303axis_colors.push_back(get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor)));304axis_colors.push_back(get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor)));305axis_colors.push_back(get_theme_color(SNAME("axis_z_color"), EditorStringName(Editor)));306queue_redraw();307} break;308309case NOTIFICATION_DRAW: {310if (viewport != nullptr) {311_draw();312}313} break;314315case NOTIFICATION_MOUSE_EXIT: {316focused_axis = -2;317queue_redraw();318} break;319320case NOTIFICATION_WM_WINDOW_FOCUS_OUT: {321gizmo_activated = false;322} break;323}324}325326void ViewportRotationControl::_draw() {327const Vector2 center = get_size() / 2.0;328const real_t radius = get_size().x / 2.0;329330if (focused_axis > -2 || orbiting_index != -1) {331draw_circle(center, radius, Color(0.5, 0.5, 0.5, 0.25), true, -1.0, true);332}333334Vector<Axis2D> axis_to_draw;335_get_sorted_axis(axis_to_draw);336for (int i = 0; i < axis_to_draw.size(); ++i) {337_draw_axis(axis_to_draw[i]);338}339}340341void ViewportRotationControl::_draw_axis(const Axis2D &p_axis) {342const bool focused = focused_axis == p_axis.axis;343const bool positive = p_axis.axis < 3;344const int direction = p_axis.axis % 3;345346const Color axis_color = axis_colors[direction];347const double min_alpha = 0.35;348const double alpha = focused ? 1.0 : Math::remap((p_axis.z_axis + 1.0) / 2.0, 0, 0.5, min_alpha, 1.0);349const Color c = focused ? Color(axis_color.lightened(0.75), 1.0) : Color(axis_color, alpha);350351if (positive) {352// Draw axis lines for the positive axes.353const Vector2 center = get_size() / 2.0;354const Vector2 diff = p_axis.screen_point - center;355const float line_length = MAX(diff.length() - AXIS_CIRCLE_RADIUS - 0.5 * EDSCALE, 0);356357draw_line(center + diff.limit_length(0.5 * EDSCALE), center + diff.limit_length(line_length), c, 1.5 * EDSCALE, true);358359draw_circle(p_axis.screen_point, AXIS_CIRCLE_RADIUS, c, true, -1.0, true);360361// Draw the axis letter for the positive axes.362const String axis_name = direction == 0 ? "X" : (direction == 1 ? "Y" : "Z");363const Ref<Font> &font = get_theme_font(SNAME("rotation_control"), EditorStringName(EditorFonts));364const int font_size = get_theme_font_size(SNAME("rotation_control_size"), EditorStringName(EditorFonts));365const Size2 char_size = font->get_char_size(axis_name[0], font_size);366const Vector2 char_offset = Vector2(-char_size.width / 2.0, char_size.height * 0.25);367draw_char(font, p_axis.screen_point + char_offset, axis_name, font_size, Color(0.0, 0.0, 0.0, alpha * 0.6));368} else {369// Draw an outline around the negative axes.370draw_circle(p_axis.screen_point, AXIS_CIRCLE_RADIUS, c, true, -1.0, true);371draw_circle(p_axis.screen_point, AXIS_CIRCLE_RADIUS * 0.8, c.darkened(0.4), true, -1.0, true);372}373}374375void ViewportRotationControl::_get_sorted_axis(Vector<Axis2D> &r_axis) {376const Vector2 center = get_size() / 2.0;377const real_t radius = get_size().x / 2.0 - AXIS_CIRCLE_RADIUS - 2.0 * EDSCALE;378const Basis camera_basis = viewport->to_camera_transform(viewport->cursor).get_basis().inverse();379380for (int i = 0; i < 3; ++i) {381Vector3 axis_3d = camera_basis.get_column(i);382Vector2 axis_vector = Vector2(axis_3d.x, -axis_3d.y) * radius;383384if (Math::abs(axis_3d.z) <= 1.0) {385Axis2D pos_axis;386pos_axis.axis = i;387pos_axis.screen_point = center + axis_vector;388pos_axis.z_axis = axis_3d.z;389r_axis.push_back(pos_axis);390391Axis2D neg_axis;392neg_axis.axis = i + 3;393neg_axis.screen_point = center - axis_vector;394neg_axis.z_axis = -axis_3d.z;395r_axis.push_back(neg_axis);396} else {397// Special case when the camera is aligned with one axis398Axis2D axis;399axis.axis = i + (axis_3d.z <= 0 ? 0 : 3);400axis.screen_point = center;401axis.z_axis = 1.0;402r_axis.push_back(axis);403}404}405406r_axis.sort_custom<Axis2DCompare>();407}408409void ViewportRotationControl::_process_click(int p_index, Vector2 p_position, bool p_pressed) {410if (orbiting_index != -1 && orbiting_index != p_index) {411return;412}413if (p_pressed) {414if (p_position.distance_to(get_size() / 2.0) < get_size().x / 2.0) {415orbiting_index = p_index;416}417} else {418if (focused_axis > -1 && gizmo_activated) {419viewport->_menu_option(axis_menu_options[focused_axis]);420_update_focus();421}422orbiting_index = -1;423if (Input::get_singleton()->get_mouse_mode() == Input::MOUSE_MODE_CAPTURED) {424Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE);425Input::get_singleton()->warp_mouse(orbiting_mouse_start);426}427}428}429430void ViewportRotationControl::_process_drag(Ref<InputEventWithModifiers> p_event, int p_index, Vector2 p_position, Vector2 p_relative_position) {431Point2 mouse_pos = get_local_mouse_position();432const bool movement_threshold_passed = original_mouse_pos.distance_to(mouse_pos) > 4 * EDSCALE;433if (orbiting_index == p_index && gizmo_activated && movement_threshold_passed) {434if (Input::get_singleton()->get_mouse_mode() == Input::MOUSE_MODE_VISIBLE) {435Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_CAPTURED);436orbiting_mouse_start = p_position;437viewport->previous_cursor = viewport->cursor;438}439viewport->_nav_orbit(p_event, p_relative_position);440focused_axis = -1;441} else {442_update_focus();443}444}445446void ViewportRotationControl::gui_input(const Ref<InputEvent> &p_event) {447ERR_FAIL_COND(p_event.is_null());448449// Key events450const Ref<InputEventKey> k = p_event;451452if (k.is_valid() && k->is_action_pressed(SNAME("ui_cancel"), false, true)) {453if (Input::get_singleton()->get_mouse_mode() == Input::MOUSE_MODE_CAPTURED) {454Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE);455Input::get_singleton()->warp_mouse(orbiting_mouse_start);456viewport->cursor = viewport->previous_cursor;457gizmo_activated = false;458}459}460461// Mouse events462const Ref<InputEventMouseButton> mb = p_event;463if (mb.is_valid()) {464if (mb->get_button_index() == MouseButton::LEFT) {465_process_click(100, mb->get_position(), mb->is_pressed());466if (mb->is_pressed()) {467gizmo_activated = true;468original_mouse_pos = get_local_mouse_position();469grab_focus();470}471} else if (mb->get_button_index() == MouseButton::RIGHT) {472if (Input::get_singleton()->get_mouse_mode() == Input::MOUSE_MODE_CAPTURED) {473Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE);474Input::get_singleton()->warp_mouse(orbiting_mouse_start);475viewport->cursor = viewport->previous_cursor;476gizmo_activated = false;477}478}479}480481const Ref<InputEventMouseMotion> mm = p_event;482if (mm.is_valid()) {483_process_drag(mm, 100, mm->get_global_position(), viewport->_get_warped_mouse_motion(mm));484}485486// Touch events487const Ref<InputEventScreenTouch> screen_touch = p_event;488if (screen_touch.is_valid()) {489_process_click(screen_touch->get_index(), screen_touch->get_position(), screen_touch->is_pressed());490}491492const Ref<InputEventScreenDrag> screen_drag = p_event;493if (screen_drag.is_valid()) {494_process_drag(screen_drag, screen_drag->get_index(), screen_drag->get_position(), screen_drag->get_relative());495}496}497498void ViewportRotationControl::_update_focus() {499int original_focus = focused_axis;500focused_axis = -2;501Vector2 mouse_pos = get_local_mouse_position();502503if (mouse_pos.distance_to(get_size() / 2.0) < get_size().x / 2.0) {504focused_axis = -1;505}506507Vector<Axis2D> axes;508_get_sorted_axis(axes);509510for (int i = 0; i < axes.size(); i++) {511const Axis2D &axis = axes[i];512if (mouse_pos.distance_to(axis.screen_point) < AXIS_CIRCLE_RADIUS) {513focused_axis = axis.axis;514}515}516517if (focused_axis != original_focus) {518queue_redraw();519}520}521522void ViewportRotationControl::set_viewport(Node3DEditorViewport *p_viewport) {523viewport = p_viewport;524}525526void Node3DEditorViewport::_view_settings_confirmed(real_t p_interp_delta) {527// Set FOV override multiplier back to the default, so that the FOV528// setting specified in the View menu is correctly applied.529cursor.fov_scale = 1.0;530531_update_camera(p_interp_delta);532}533534void Node3DEditorViewport::_update_navigation_controls_visibility() {535bool show_viewport_rotation_gizmo = EDITOR_GET("editors/3d/navigation/show_viewport_rotation_gizmo") && (!previewing_cinema && !previewing_camera);536rotation_control->set_visible(show_viewport_rotation_gizmo);537538bool show_viewport_navigation_gizmo = EDITOR_GET("editors/3d/navigation/show_viewport_navigation_gizmo") && (!previewing_cinema && !previewing_camera);539position_control->set_visible(show_viewport_navigation_gizmo);540look_control->set_visible(show_viewport_navigation_gizmo);541}542543void Node3DEditorViewport::_update_camera(real_t p_interp_delta) {544bool is_orthogonal = camera->get_projection() == Camera3D::PROJECTION_ORTHOGONAL;545546Cursor old_camera_cursor = camera_cursor;547camera_cursor = cursor;548549if (p_interp_delta > 0) {550//-------551// Perform smoothing552553if (is_freelook_active()) {554// Higher inertia should increase "lag" (lerp with factor between 0 and 1)555// Inertia of zero should produce instant movement (lerp with factor of 1) in this case it returns a really high value and gets clamped to 1.556const real_t inertia = EDITOR_GET("editors/3d/freelook/freelook_inertia");557real_t factor = (1.0 / inertia) * p_interp_delta;558559// We interpolate a different point here, because in freelook mode the focus point (cursor.pos) orbits around eye_pos560camera_cursor.eye_pos = old_camera_cursor.eye_pos.lerp(cursor.eye_pos, CLAMP(factor, 0, 1));561562const real_t orbit_inertia = EDITOR_GET("editors/3d/navigation_feel/orbit_inertia");563camera_cursor.x_rot = Math::lerp(old_camera_cursor.x_rot, cursor.x_rot, MIN(1.f, p_interp_delta * (1 / orbit_inertia)));564camera_cursor.y_rot = Math::lerp(old_camera_cursor.y_rot, cursor.y_rot, MIN(1.f, p_interp_delta * (1 / orbit_inertia)));565566if (Math::abs(camera_cursor.x_rot - cursor.x_rot) < 0.1) {567camera_cursor.x_rot = cursor.x_rot;568}569570if (Math::abs(camera_cursor.y_rot - cursor.y_rot) < 0.1) {571camera_cursor.y_rot = cursor.y_rot;572}573574Vector3 forward = to_camera_transform(camera_cursor).basis.xform(Vector3(0, 0, -1));575camera_cursor.pos = camera_cursor.eye_pos + forward * camera_cursor.distance;576577} else {578const real_t orbit_inertia = EDITOR_GET("editors/3d/navigation_feel/orbit_inertia");579const real_t translation_inertia = EDITOR_GET("editors/3d/navigation_feel/translation_inertia");580const real_t zoom_inertia = EDITOR_GET("editors/3d/navigation_feel/zoom_inertia");581582camera_cursor.x_rot = Math::lerp(old_camera_cursor.x_rot, cursor.x_rot, MIN(1.f, p_interp_delta * (1 / orbit_inertia)));583camera_cursor.y_rot = Math::lerp(old_camera_cursor.y_rot, cursor.y_rot, MIN(1.f, p_interp_delta * (1 / orbit_inertia)));584585if (Math::abs(camera_cursor.x_rot - cursor.x_rot) < 0.1) {586camera_cursor.x_rot = cursor.x_rot;587}588589if (Math::abs(camera_cursor.y_rot - cursor.y_rot) < 0.1) {590camera_cursor.y_rot = cursor.y_rot;591}592593camera_cursor.pos = old_camera_cursor.pos.lerp(cursor.pos, MIN(1.f, p_interp_delta * (1 / translation_inertia)));594camera_cursor.distance = Math::lerp(old_camera_cursor.distance, cursor.distance, MIN((real_t)1.0, p_interp_delta * (1 / zoom_inertia)));595}596}597598//-------599// Apply camera transform600601real_t tolerance = 0.001;602bool equal = true;603if (!Math::is_equal_approx(old_camera_cursor.x_rot, camera_cursor.x_rot, tolerance) || !Math::is_equal_approx(old_camera_cursor.y_rot, camera_cursor.y_rot, tolerance)) {604equal = false;605} else if (!old_camera_cursor.pos.is_equal_approx(camera_cursor.pos)) {606equal = false;607} else if (!Math::is_equal_approx(old_camera_cursor.distance, camera_cursor.distance, tolerance)) {608equal = false;609} else if (!Math::is_equal_approx(old_camera_cursor.fov_scale, camera_cursor.fov_scale, tolerance)) {610equal = false;611}612613if (!equal || p_interp_delta == 0 || is_orthogonal != orthogonal) {614last_camera_transform = to_camera_transform(camera_cursor);615camera->set_global_transform(last_camera_transform);616617if (orthogonal) {618float half_fov = Math::deg_to_rad(get_fov()) / 2.0;619float height = 2.0 * cursor.distance * Math::tan(half_fov);620camera->set_orthogonal(height, get_znear(), get_zfar());621} else {622camera->set_perspective(get_fov(), get_znear(), get_zfar());623}624625update_transform_gizmo_view();626rotation_control->queue_redraw();627position_control->queue_redraw();628look_control->queue_redraw();629spatial_editor->update_grid();630}631}632633Transform3D Node3DEditorViewport::to_camera_transform(const Cursor &p_cursor) const {634Transform3D camera_transform;635camera_transform.translate_local(p_cursor.pos);636camera_transform.basis.rotate(Vector3(1, 0, 0), -p_cursor.x_rot);637camera_transform.basis.rotate(Vector3(0, 1, 0), -p_cursor.y_rot);638639if (orthogonal) {640camera_transform.translate_local(0, 0, (get_zfar() - get_znear()) / 2.0);641} else {642camera_transform.translate_local(0, 0, p_cursor.distance);643}644645return camera_transform;646}647648int Node3DEditorViewport::get_selected_count() const {649const HashMap<Node *, Object *> &selection = editor_selection->get_selection();650651int count = 0;652653for (const KeyValue<Node *, Object *> &E : selection) {654Node3D *sp = Object::cast_to<Node3D>(E.key);655if (!sp) {656continue;657}658659Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);660if (!se) {661continue;662}663664count++;665}666667return count;668}669670void Node3DEditorViewport::cancel_transform() {671const List<Node *> &selection = editor_selection->get_top_selected_node_list();672673for (Node *E : selection) {674Node3D *sp = Object::cast_to<Node3D>(E);675if (!sp) {676continue;677}678679Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);680if (!se) {681continue;682}683684if (se && se->gizmo.is_valid()) {685Vector<int> ids;686Vector<Transform3D> restore;687688for (const KeyValue<int, Transform3D> &GE : se->subgizmos) {689ids.push_back(GE.key);690restore.push_back(GE.value);691}692693se->gizmo->commit_subgizmos(ids, restore, true);694}695696sp->set_global_transform(se->original);697}698699collision_reposition = false;700finish_transform();701set_message(TTRC("Transform Aborted."), 3);702}703704void Node3DEditorViewport::_update_shrink() {705bool shrink = view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_HALF_RESOLUTION));706subviewport_container->set_stretch_shrink(shrink ? 2 : 1);707subviewport_container->set_texture_filter(shrink ? TEXTURE_FILTER_NEAREST : TEXTURE_FILTER_PARENT_NODE);708}709710float Node3DEditorViewport::get_znear() const {711return CLAMP(spatial_editor->get_znear(), MIN_Z, MAX_Z);712}713714float Node3DEditorViewport::get_zfar() const {715return CLAMP(spatial_editor->get_zfar(), MIN_Z, MAX_Z);716}717718float Node3DEditorViewport::get_fov() const {719return CLAMP(spatial_editor->get_fov() * cursor.fov_scale, MIN_FOV, MAX_FOV);720}721722Transform3D Node3DEditorViewport::_get_camera_transform() const {723return camera->get_global_transform();724}725726Vector3 Node3DEditorViewport::_get_camera_position() const {727return _get_camera_transform().origin;728}729730Point2 Node3DEditorViewport::point_to_screen(const Vector3 &p_point) {731return camera->unproject_position(p_point) * subviewport_container->get_stretch_shrink();732}733734Vector3 Node3DEditorViewport::get_ray_pos(const Vector2 &p_pos) const {735return camera->project_ray_origin(p_pos / subviewport_container->get_stretch_shrink());736}737738Vector3 Node3DEditorViewport::_get_camera_normal() const {739return -_get_camera_transform().basis.get_column(2);740}741742Vector3 Node3DEditorViewport::get_ray(const Vector2 &p_pos) const {743return camera->project_ray_normal(p_pos / subviewport_container->get_stretch_shrink());744}745746void Node3DEditorViewport::_clear_selected() {747_edit.gizmo = Ref<EditorNode3DGizmo>();748_edit.gizmo_handle = -1;749_edit.gizmo_handle_secondary = false;750_edit.gizmo_initial_value = Variant();751752Node3D *selected = spatial_editor->get_single_selected_node();753Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr;754755if (se && se->gizmo.is_valid()) {756se->subgizmos.clear();757se->gizmo->redraw();758se->gizmo.unref();759spatial_editor->update_transform_gizmo();760} else {761editor_selection->clear();762Node3DEditor::get_singleton()->edit(nullptr);763}764}765766void Node3DEditorViewport::_select_clicked(bool p_allow_locked) {767Node *node = ObjectDB::get_instance<Node3D>(clicked);768Node3D *selected = Object::cast_to<Node3D>(node);769clicked = ObjectID();770771if (!selected) {772return;773}774775Node *edited_scene = EditorNode::get_singleton()->get_edited_scene();776777// Prevent selection of nodes not owned by the edited scene.778while (node && node != edited_scene->get_parent()) {779Node *node_owner = node->get_owner();780if (node_owner == edited_scene || node == edited_scene || (node_owner != nullptr && edited_scene->is_editable_instance(node_owner))) {781break;782}783node = node->get_parent();784selected = Object::cast_to<Node3D>(node);785}786787if (!p_allow_locked) {788// Replace the node by the group if grouped789while (node && node != edited_scene->get_parent()) {790Node3D *selected_tmp = Object::cast_to<Node3D>(node);791if (selected_tmp && node->has_meta("_edit_group_")) {792selected = selected_tmp;793}794node = node->get_parent();795}796}797798if (p_allow_locked || (selected != nullptr && !_is_node_locked(selected))) {799if (clicked_wants_append) {800const List<Node *> &top_node_list = editor_selection->get_top_selected_node_list();801const Node *active_node = top_node_list.is_empty() ? nullptr : top_node_list.back()->get();802if (editor_selection->is_selected(selected)) {803editor_selection->remove_node(selected);804if (selected != active_node) {805editor_selection->add_node(selected);806}807} else {808editor_selection->add_node(selected);809}810} else {811if (!editor_selection->is_selected(selected)) {812editor_selection->clear();813editor_selection->add_node(selected);814EditorNode::get_singleton()->edit_node(selected);815}816}817818const List<Node *> &top_node_list = editor_selection->get_top_selected_node_list();819if (top_node_list.size() == 1) {820EditorNode::get_singleton()->edit_node(top_node_list.front()->get());821}822}823}824825ObjectID Node3DEditorViewport::_select_ray(const Point2 &p_pos) const {826Vector3 ray = get_ray(p_pos);827Vector3 pos = get_ray_pos(p_pos);828Vector2 shrinked_pos = p_pos / subviewport_container->get_stretch_shrink();829830if (viewport->get_debug_draw() == Viewport::DEBUG_DRAW_SDFGI_PROBES) {831RS::get_singleton()->sdfgi_set_debug_probe_select(pos, ray);832}833834HashSet<Ref<EditorNode3DGizmo>> found_gizmos;835836Node *edited_scene = get_tree()->get_edited_scene_root();837ObjectID closest;838Node *item = nullptr;839float closest_dist = 1e20;840841Vector<Node3D *> nodes_with_gizmos = Node3DEditor::get_singleton()->gizmo_bvh_ray_query(pos, pos + ray * camera->get_far());842843for (Node3D *spat : nodes_with_gizmos) {844if (!spat || _is_node_locked(spat)) {845continue;846}847848Vector<Ref<Node3DGizmo>> gizmos = spat->get_gizmos();849850for (int j = 0; j < gizmos.size(); j++) {851Ref<EditorNode3DGizmo> seg = gizmos[j];852853if (seg.is_null() || found_gizmos.has(seg)) {854continue;855}856857found_gizmos.insert(seg);858Vector3 point;859Vector3 normal;860861bool inters = seg->intersect_ray(camera, shrinked_pos, point, normal);862863if (!inters) {864continue;865}866867const real_t dist = pos.distance_to(point);868869if (dist < 0) {870continue;871}872873if (dist < closest_dist) {874item = Object::cast_to<Node>(spat);875if (item != edited_scene) {876item = edited_scene->get_deepest_editable_node(item);877}878879closest = item->get_instance_id();880closest_dist = dist;881}882}883}884885if (!item) {886return ObjectID();887}888889return closest;890}891892void Node3DEditorViewport::_find_items_at_pos(const Point2 &p_pos, Vector<_RayResult> &r_results, bool p_include_locked_nodes) {893Vector3 ray = get_ray(p_pos);894Vector3 pos = get_ray_pos(p_pos);895896Vector<Node3D *> nodes_with_gizmos = Node3DEditor::get_singleton()->gizmo_bvh_ray_query(pos, pos + ray * camera->get_far());897898HashSet<Node3D *> found_nodes;899900for (Node3D *spat : nodes_with_gizmos) {901if (!spat) {902continue;903}904905if (found_nodes.has(spat)) {906continue;907}908909if (!p_include_locked_nodes && _is_node_locked(spat)) {910continue;911}912913Vector<Ref<Node3DGizmo>> gizmos = spat->get_gizmos();914for (int j = 0; j < gizmos.size(); j++) {915Ref<EditorNode3DGizmo> seg = gizmos[j];916917if (seg.is_null()) {918continue;919}920921Vector3 point;922Vector3 normal;923924bool inters = seg->intersect_ray(camera, p_pos, point, normal);925926if (!inters) {927continue;928}929930const real_t dist = pos.distance_to(point);931932if (dist < 0) {933continue;934}935936found_nodes.insert(spat);937938_RayResult res;939res.item = spat;940res.depth = dist;941r_results.push_back(res);942break;943}944}945946r_results.sort();947}948949Vector3 Node3DEditorViewport::_get_screen_to_space(const Vector3 &p_vector3) {950Projection cm;951if (orthogonal) {952cm.set_orthogonal(camera->get_size(), get_size().aspect(), get_znear() + p_vector3.z, get_zfar());953} else {954cm.set_perspective(get_fov(), get_size().aspect(), get_znear() + p_vector3.z, get_zfar());955}956Vector2 screen_he = cm.get_viewport_half_extents();957958Transform3D camera_transform;959camera_transform.translate_local(cursor.pos);960camera_transform.basis.rotate(Vector3(1, 0, 0), -cursor.x_rot);961camera_transform.basis.rotate(Vector3(0, 1, 0), -cursor.y_rot);962camera_transform.translate_local(0, 0, cursor.distance);963964return camera_transform.xform(Vector3(((p_vector3.x / get_size().width) * 2.0 - 1.0) * screen_he.x, ((1.0 - (p_vector3.y / get_size().height)) * 2.0 - 1.0) * screen_he.y, -(get_znear() + p_vector3.z)));965}966967void Node3DEditorViewport::_select_region() {968if (cursor.region_begin == cursor.region_end) {969if (!clicked_wants_append) {970_clear_selected();971}972return; //nothing really973}974975const real_t z_offset = MAX(0.0, 5.0 - get_znear());976977Vector3 box[4] = {978Vector3(979MIN(cursor.region_begin.x, cursor.region_end.x),980MIN(cursor.region_begin.y, cursor.region_end.y),981z_offset),982Vector3(983MAX(cursor.region_begin.x, cursor.region_end.x),984MIN(cursor.region_begin.y, cursor.region_end.y),985z_offset),986Vector3(987MAX(cursor.region_begin.x, cursor.region_end.x),988MAX(cursor.region_begin.y, cursor.region_end.y),989z_offset),990Vector3(991MIN(cursor.region_begin.x, cursor.region_end.x),992MAX(cursor.region_begin.y, cursor.region_end.y),993z_offset)994};995996Vector<Plane> frustum;997998Vector3 cam_pos = _get_camera_position();9991000for (int i = 0; i < 4; i++) {1001Vector3 a = _get_screen_to_space(box[i]);1002Vector3 b = _get_screen_to_space(box[(i + 1) % 4]);1003if (orthogonal) {1004frustum.push_back(Plane((a - b).normalized(), a));1005} else {1006frustum.push_back(Plane(a, b, cam_pos));1007}1008}10091010Plane near_plane = Plane(-_get_camera_normal(), cam_pos);1011near_plane.d -= get_znear();1012frustum.push_back(near_plane);10131014Plane far_plane = -near_plane;1015far_plane.d += get_zfar();1016frustum.push_back(far_plane);10171018if (spatial_editor->get_single_selected_node()) {1019Node3D *single_selected = spatial_editor->get_single_selected_node();1020Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(single_selected);10211022if (se) {1023Ref<EditorNode3DGizmo> old_gizmo;1024if (!clicked_wants_append) {1025se->subgizmos.clear();1026old_gizmo = se->gizmo;1027se->gizmo.unref();1028}10291030bool found_subgizmos = false;1031Vector<Ref<Node3DGizmo>> gizmos = single_selected->get_gizmos();1032for (int j = 0; j < gizmos.size(); j++) {1033Ref<EditorNode3DGizmo> seg = gizmos[j];1034if (seg.is_null()) {1035continue;1036}10371038if (se->gizmo.is_valid() && se->gizmo != seg) {1039continue;1040}10411042Vector<int> subgizmos = seg->subgizmos_intersect_frustum(camera, frustum);1043if (!subgizmos.is_empty()) {1044se->gizmo = seg;1045for (int i = 0; i < subgizmos.size(); i++) {1046int subgizmo_id = subgizmos[i];1047if (!se->subgizmos.has(subgizmo_id)) {1048se->subgizmos.insert(subgizmo_id, se->gizmo->get_subgizmo_transform(subgizmo_id));1049}1050}1051found_subgizmos = true;1052break;1053}1054}10551056if (!clicked_wants_append || found_subgizmos) {1057if (se->gizmo.is_valid()) {1058se->gizmo->redraw();1059}10601061if (old_gizmo != se->gizmo && old_gizmo.is_valid()) {1062old_gizmo->redraw();1063}10641065spatial_editor->update_transform_gizmo();1066}10671068if (found_subgizmos) {1069return;1070}1071}1072}10731074if (!clicked_wants_append) {1075_clear_selected();1076}10771078Vector<Node3D *> nodes_with_gizmos = Node3DEditor::get_singleton()->gizmo_bvh_frustum_query(frustum);1079HashSet<Node3D *> found_nodes;1080Vector<Node *> selected;10811082Node *edited_scene = get_tree()->get_edited_scene_root();1083if (edited_scene == nullptr) {1084return;1085}10861087for (Node3D *sp : nodes_with_gizmos) {1088if (!sp || _is_node_locked(sp)) {1089continue;1090}10911092if (found_nodes.has(sp)) {1093continue;1094}1095found_nodes.insert(sp);10961097Node *node = Object::cast_to<Node>(sp);10981099// Selection requires that the node is the edited scene or its descendant, and has an owner.1100if (node != edited_scene) {1101if (!node->get_owner() || !edited_scene->is_ancestor_of(node)) {1102continue;1103}1104node = edited_scene->get_deepest_editable_node(node);1105while (node != edited_scene) {1106Node *node_owner = node->get_owner();1107if (node_owner == edited_scene || (node_owner != nullptr && edited_scene->is_editable_instance(node_owner))) {1108break;1109}1110node = node->get_parent();1111}1112}11131114// Replace the node by the group if grouped1115if (node->is_class("Node3D")) {1116Node3D *sel = Object::cast_to<Node3D>(node);1117while (node && node != EditorNode::get_singleton()->get_edited_scene()->get_parent()) {1118Node3D *selected_tmp = Object::cast_to<Node3D>(node);1119if (selected_tmp && node->has_meta("_edit_group_")) {1120sel = selected_tmp;1121}1122node = node->get_parent();1123}1124node = sel;1125}11261127if (_is_node_locked(node)) {1128continue;1129}11301131Vector<Ref<Node3DGizmo>> gizmos = sp->get_gizmos();1132for (int j = 0; j < gizmos.size(); j++) {1133Ref<EditorNode3DGizmo> seg = gizmos[j];1134if (seg.is_null()) {1135continue;1136}11371138if (seg->intersect_frustum(camera, frustum)) {1139selected.push_back(node);1140}1141}1142}11431144for (int i = 0; i < selected.size(); i++) {1145if (!editor_selection->is_selected(selected[i])) {1146editor_selection->add_node(selected[i]);1147}1148}11491150const List<Node *> &top_node_list = editor_selection->get_top_selected_node_list();1151if (top_node_list.size() == 1) {1152EditorNode::get_singleton()->edit_node(top_node_list.front()->get());1153}1154}11551156void Node3DEditorViewport::_update_name() {1157String name;11581159switch (view_type) {1160case VIEW_TYPE_USER: {1161if (orthogonal) {1162name = TTR("Orthogonal");1163} else {1164name = TTR("Perspective");1165}1166} break;1167case VIEW_TYPE_TOP: {1168if (orthogonal) {1169name = TTR("Top Orthogonal");1170} else {1171name = TTR("Top Perspective");1172}1173} break;1174case VIEW_TYPE_BOTTOM: {1175if (orthogonal) {1176name = TTR("Bottom Orthogonal");1177} else {1178name = TTR("Bottom Perspective");1179}1180} break;1181case VIEW_TYPE_LEFT: {1182if (orthogonal) {1183name = TTR("Left Orthogonal");1184} else {1185name = TTR("Left Perspective");1186}1187} break;1188case VIEW_TYPE_RIGHT: {1189if (orthogonal) {1190name = TTR("Right Orthogonal");1191} else {1192name = TTR("Right Perspective");1193}1194} break;1195case VIEW_TYPE_FRONT: {1196if (orthogonal) {1197name = TTR("Front Orthogonal");1198} else {1199name = TTR("Front Perspective");1200}1201} break;1202case VIEW_TYPE_REAR: {1203if (orthogonal) {1204name = TTR("Rear Orthogonal");1205} else {1206name = TTR("Rear Perspective");1207}1208} break;1209}12101211if (auto_orthogonal) {1212// TRANSLATORS: This will be appended to the view name when Auto Orthogonal is enabled.1213name += " " + TTR("[auto]");1214}12151216view_display_menu->set_text(name);1217view_display_menu->reset_size();1218}12191220void Node3DEditorViewport::_compute_edit(const Point2 &p_point) {1221_edit.original_local = spatial_editor->are_local_coords_enabled();1222_edit.click_ray = get_ray(p_point);1223_edit.click_ray_pos = get_ray_pos(p_point);1224_edit.plane = TRANSFORM_VIEW;1225spatial_editor->update_transform_gizmo();1226_edit.center = spatial_editor->get_gizmo_transform().origin;12271228Node3D *selected = spatial_editor->get_single_selected_node();1229Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr;12301231if (se && se->gizmo.is_valid()) {1232for (const KeyValue<int, Transform3D> &E : se->subgizmos) {1233int subgizmo_id = E.key;1234se->subgizmos[subgizmo_id] = se->gizmo->get_subgizmo_transform(subgizmo_id);1235}1236se->original_local = selected->get_transform();1237se->original = selected->get_global_transform();1238} else {1239const List<Node *> &selection = editor_selection->get_top_selected_node_list();12401241for (Node *E : selection) {1242Node3D *sp = Object::cast_to<Node3D>(E);1243if (!sp) {1244continue;1245}12461247Node3DEditorSelectedItem *sel_item = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);12481249if (!sel_item) {1250continue;1251}12521253sel_item->original_local = sel_item->sp->get_local_gizmo_transform();1254sel_item->original = sel_item->sp->get_global_gizmo_transform();1255}1256}1257}12581259static Key _get_key_modifier_setting(const String &p_property) {1260switch (EDITOR_GET(p_property).operator int()) {1261case 0:1262return Key::NONE;1263case 1:1264return Key::SHIFT;1265case 2:1266return Key::ALT;1267case 3:1268return Key::META;1269case 4:1270return Key::CTRL;1271}1272return Key::NONE;1273}12741275static Key _get_key_modifier(Ref<InputEventWithModifiers> e) {1276if (e->is_shift_pressed()) {1277return Key::SHIFT;1278}1279if (e->is_alt_pressed()) {1280return Key::ALT;1281}1282if (e->is_ctrl_pressed()) {1283return Key::CTRL;1284}1285if (e->is_meta_pressed()) {1286return Key::META;1287}1288return Key::NONE;1289}12901291bool Node3DEditorViewport::_transform_gizmo_select(const Vector2 &p_screenpos, bool p_highlight_only) {1292if (!spatial_editor->is_gizmo_visible()) {1293return false;1294}1295if (get_selected_count() == 0) {1296if (p_highlight_only) {1297spatial_editor->select_gizmo_highlight_axis(-1);1298}1299return false;1300}13011302Vector3 ray_pos = get_ray_pos(p_screenpos);1303Vector3 ray = get_ray(p_screenpos);13041305Transform3D gt = spatial_editor->get_gizmo_transform();13061307if (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_MOVE) {1308int col_axis = -1;1309real_t col_d = 1e20;13101311for (int i = 0; i < 3; i++) {1312const Vector3 grabber_pos = gt.origin + gt.basis.get_column(i).normalized() * gizmo_scale * (GIZMO_ARROW_OFFSET + (GIZMO_ARROW_SIZE * 0.5));1313const real_t grabber_radius = gizmo_scale * GIZMO_ARROW_SIZE;13141315Vector3 r;13161317if (Geometry3D::segment_intersects_sphere(ray_pos, ray_pos + ray * MAX_Z, grabber_pos, grabber_radius, &r)) {1318const real_t d = r.distance_to(ray_pos);1319if (d < col_d) {1320col_d = d;1321col_axis = i;1322}1323}1324}13251326bool is_plane_translate = false;1327// plane select1328if (col_axis == -1) {1329col_d = 1e20;13301331for (int i = 0; i < 3; i++) {1332Vector3 ivec2 = gt.basis.get_column((i + 1) % 3).normalized();1333Vector3 ivec3 = gt.basis.get_column((i + 2) % 3).normalized();13341335// Allow some tolerance to make the plane easier to click,1336// even if the click is actually slightly outside the plane.1337const Vector3 grabber_pos = gt.origin + (ivec2 + ivec3) * gizmo_scale * (GIZMO_PLANE_SIZE + GIZMO_PLANE_DST * 0.6667);13381339Vector3 r;1340Plane plane(gt.basis.get_column(i).normalized(), gt.origin);13411342if (plane.intersects_ray(ray_pos, ray, &r)) {1343const real_t dist = r.distance_to(grabber_pos);1344// Allow some tolerance to make the plane easier to click,1345// even if the click is actually slightly outside the plane.1346if (dist < (gizmo_scale * GIZMO_PLANE_SIZE * 1.5)) {1347const real_t d = ray_pos.distance_to(r);1348if (d < col_d) {1349col_d = d;1350col_axis = i;13511352is_plane_translate = true;1353}1354}1355}1356}1357}13581359if (col_axis != -1) {1360if (p_highlight_only) {1361spatial_editor->select_gizmo_highlight_axis(col_axis + (is_plane_translate ? 6 : 0));13621363} else {1364//handle plane translate1365_edit.mode = TRANSFORM_TRANSLATE;1366_compute_edit(p_screenpos);1367_edit.plane = TransformPlane(TRANSFORM_X_AXIS + col_axis + (is_plane_translate ? 3 : 0));1368}1369return true;1370}1371}13721373if (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE) {1374int col_axis = -1;13751376Vector3 hit_position;1377Vector3 hit_normal;1378real_t ray_length = gt.origin.distance_to(ray_pos) + (GIZMO_CIRCLE_SIZE * gizmo_scale) * 4.0f;1379if (Geometry3D::segment_intersects_sphere(ray_pos, ray_pos + ray * ray_length, gt.origin, gizmo_scale * (GIZMO_CIRCLE_SIZE), &hit_position, &hit_normal)) {1380if (hit_normal.dot(_get_camera_normal()) < 0.05) {1381hit_position = gt.xform_inv(hit_position).abs();1382int min_axis = hit_position.min_axis_index();1383if (hit_position[min_axis] < gizmo_scale * GIZMO_RING_HALF_WIDTH) {1384col_axis = min_axis;1385}1386}1387}13881389if (col_axis == -1) {1390float col_d = 1e20;13911392for (int i = 0; i < 3; i++) {1393Plane plane(gt.basis.get_column(i).normalized(), gt.origin);1394Vector3 r;1395if (!plane.intersects_ray(ray_pos, ray, &r)) {1396continue;1397}13981399const real_t dist = r.distance_to(gt.origin);1400const Vector3 r_dir = (r - gt.origin).normalized();14011402if (_get_camera_normal().dot(r_dir) <= 0.005) {1403if (dist > gizmo_scale * (GIZMO_CIRCLE_SIZE - GIZMO_RING_HALF_WIDTH) && dist < gizmo_scale * (GIZMO_CIRCLE_SIZE + GIZMO_RING_HALF_WIDTH)) {1404const real_t d = ray_pos.distance_to(r);1405if (d < col_d) {1406col_d = d;1407col_axis = i;1408}1409}1410}1411}1412}14131414if (col_axis != -1) {1415if (p_highlight_only) {1416spatial_editor->select_gizmo_highlight_axis(col_axis + 3);1417} else {1418//handle rotate1419_edit.mode = TRANSFORM_ROTATE;1420_compute_edit(p_screenpos);1421_edit.plane = TransformPlane(TRANSFORM_X_AXIS + col_axis);1422}1423return true;1424}1425}14261427if (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SCALE) {1428int col_axis = -1;1429float col_d = 1e20;14301431for (int i = 0; i < 3; i++) {1432const Vector3 grabber_pos = gt.origin + gt.basis.get_column(i).normalized() * gizmo_scale * GIZMO_SCALE_OFFSET;1433const real_t grabber_radius = gizmo_scale * GIZMO_ARROW_SIZE;14341435Vector3 r;14361437if (Geometry3D::segment_intersects_sphere(ray_pos, ray_pos + ray * MAX_Z, grabber_pos, grabber_radius, &r)) {1438const real_t d = r.distance_to(ray_pos);1439if (d < col_d) {1440col_d = d;1441col_axis = i;1442}1443}1444}14451446bool is_plane_scale = false;1447// plane select1448if (col_axis == -1) {1449col_d = 1e20;14501451for (int i = 0; i < 3; i++) {1452const Vector3 ivec2 = gt.basis.get_column((i + 1) % 3).normalized();1453const Vector3 ivec3 = gt.basis.get_column((i + 2) % 3).normalized();14541455// Allow some tolerance to make the plane easier to click,1456// even if the click is actually slightly outside the plane.1457const Vector3 grabber_pos = gt.origin + (ivec2 + ivec3) * gizmo_scale * (GIZMO_PLANE_SIZE + GIZMO_PLANE_DST * 0.6667);14581459Vector3 r;1460Plane plane(gt.basis.get_column(i).normalized(), gt.origin);14611462if (plane.intersects_ray(ray_pos, ray, &r)) {1463const real_t dist = r.distance_to(grabber_pos);1464// Allow some tolerance to make the plane easier to click,1465// even if the click is actually slightly outside the plane.1466if (dist < (gizmo_scale * GIZMO_PLANE_SIZE * 1.5)) {1467const real_t d = ray_pos.distance_to(r);1468if (d < col_d) {1469col_d = d;1470col_axis = i;14711472is_plane_scale = true;1473}1474}1475}1476}1477}14781479if (col_axis != -1) {1480if (p_highlight_only) {1481spatial_editor->select_gizmo_highlight_axis(col_axis + (is_plane_scale ? 12 : 9));14821483} else {1484//handle scale1485_edit.mode = TRANSFORM_SCALE;1486_compute_edit(p_screenpos);1487_edit.plane = TransformPlane(TRANSFORM_X_AXIS + col_axis + (is_plane_scale ? 3 : 0));1488}1489return true;1490}1491}14921493if (p_highlight_only) {1494spatial_editor->select_gizmo_highlight_axis(-1);1495}14961497return false;1498}14991500void Node3DEditorViewport::_transform_gizmo_apply(Node3D *p_node, const Transform3D &p_transform, bool p_local) {1501if (p_transform.basis.determinant() == 0) {1502return;1503}15041505if (p_local) {1506p_node->set_transform(p_transform);1507} else {1508p_node->set_global_transform(p_transform);1509}1510}15111512Transform3D Node3DEditorViewport::_compute_transform(TransformMode p_mode, const Transform3D &p_original, const Transform3D &p_original_local, Vector3 p_motion, double p_extra, bool p_local, bool p_orthogonal) {1513switch (p_mode) {1514case TRANSFORM_SCALE: {1515if (_edit.snap || spatial_editor->is_snap_enabled()) {1516p_motion.snapf(p_extra);1517}1518Transform3D s;1519if (p_local) {1520s.basis = p_original_local.basis.scaled_local(p_motion + Vector3(1, 1, 1));1521s.origin = p_original_local.origin;1522} else {1523s.basis.scale(p_motion + Vector3(1, 1, 1));1524Transform3D base = Transform3D(Basis(), _edit.center);1525s = base * (s * (base.inverse() * p_original));15261527// Recalculate orthogonalized scale without moving origin.1528if (p_orthogonal) {1529s.basis = p_original.basis.scaled_orthogonal(p_motion + Vector3(1, 1, 1));1530}1531}15321533return s;1534}1535case TRANSFORM_TRANSLATE: {1536if (_edit.snap || spatial_editor->is_snap_enabled()) {1537p_motion.snapf(p_extra);1538}15391540if (p_local) {1541return p_original_local.translated_local(p_motion);1542}15431544return p_original.translated(p_motion);1545}1546case TRANSFORM_ROTATE: {1547Transform3D r;15481549if (p_local) {1550Vector3 axis = p_original_local.basis.xform(p_motion);1551r.basis = Basis(axis.normalized(), p_extra) * p_original_local.basis;1552r.origin = p_original_local.origin;1553} else {1554Basis local = p_original.basis * p_original_local.basis.inverse();1555Vector3 axis = local.xform_inv(p_motion);1556r.basis = local * Basis(axis.normalized(), p_extra) * p_original_local.basis;1557r.origin = Basis(p_motion, p_extra).xform(p_original.origin - _edit.center) + _edit.center;1558}15591560return r;1561}1562default: {1563ERR_FAIL_V_MSG(Transform3D(), "Invalid mode in '_compute_transform'");1564}1565}1566}15671568void Node3DEditorViewport::_surface_mouse_enter() {1569if (Input::get_singleton()->get_mouse_mode() == Input::MOUSE_MODE_CAPTURED) {1570return;1571}15721573if (!surface->has_focus() && (!get_viewport()->gui_get_focus_owner() || !get_viewport()->gui_get_focus_owner()->is_text_field())) {1574surface->grab_focus();1575}1576}15771578void Node3DEditorViewport::_surface_mouse_exit() {1579_remove_preview_node();1580_reset_preview_material();1581_remove_preview_material();1582}15831584void Node3DEditorViewport::_surface_focus_enter() {1585view_display_menu->set_disable_shortcuts(false);1586}15871588void Node3DEditorViewport::_surface_focus_exit() {1589view_display_menu->set_disable_shortcuts(true);1590}15911592bool Node3DEditorViewport::_is_node_locked(const Node *p_node) const {1593return p_node->get_meta("_edit_lock_", false);1594}15951596void Node3DEditorViewport::_list_select(Ref<InputEventMouseButton> b) {1597Vector<_RayResult> potential_selection_results;1598_find_items_at_pos(b->get_position(), potential_selection_results, b->is_alt_pressed());15991600Node *edited_scene = EditorNode::get_singleton()->get_edited_scene();16011602// Filter to a list of nodes which include either the edited scene or nodes directly owned by the edited scene.1603// If a node has an invalid owner, recursively check their parents until a valid node is found.1604for (int i = 0; i < potential_selection_results.size(); i++) {1605Node3D *node = potential_selection_results[i].item;1606while (true) {1607if (node == nullptr || node == edited_scene->get_parent()) {1608break;1609} else {1610Node *node_owner = node->get_owner();1611if (node == edited_scene || node_owner == edited_scene || (node_owner != nullptr && edited_scene->is_editable_instance(node_owner))) {1612if (!selection_results.has(node)) {1613selection_results.append(node);1614}1615break;1616}1617}1618node = Object::cast_to<Node3D>(node->get_parent());1619}1620}16211622clicked_wants_append = b->is_shift_pressed();16231624if (selection_results.size() == 1) {1625clicked = selection_results[0]->get_instance_id();1626selection_results.clear();16271628if (clicked.is_valid()) {1629_select_clicked(b->is_alt_pressed());1630}1631} else if (!selection_results.is_empty()) {1632NodePath root_path = get_tree()->get_edited_scene_root()->get_path();1633StringName root_name = root_path.get_name(root_path.get_name_count() - 1);16341635for (int i = 0; i < selection_results.size(); i++) {1636Node3D *spat = selection_results[i];16371638Ref<Texture2D> icon = EditorNode::get_singleton()->get_object_icon(spat, "Node");16391640String node_path = "/" + root_name + "/" + String(root_path.rel_path_to(spat->get_path()));16411642int locked = 0;1643if (_is_node_locked(spat)) {1644locked = 1;1645} else {1646Node *ed_scene = EditorNode::get_singleton()->get_edited_scene();1647Node *node = spat;16481649while (node && node != ed_scene->get_parent()) {1650Node3D *selected_tmp = Object::cast_to<Node3D>(node);1651if (selected_tmp && node->has_meta("_edit_group_")) {1652locked = 2;1653}1654node = node->get_parent();1655}1656}16571658String suffix;1659if (locked == 1) {1660suffix = " (" + TTR("Locked") + ")";1661} else if (locked == 2) {1662suffix = " (" + TTR("Grouped") + ")";1663}1664selection_menu->add_item((String)spat->get_name() + suffix);1665selection_menu->set_item_icon(i, icon);1666selection_menu->set_item_metadata(i, node_path);1667selection_menu->set_item_tooltip(i, String(spat->get_name()) + "\nType: " + spat->get_class() + "\nPath: " + node_path);1668}16691670selection_results_menu = selection_results;1671selection_menu->set_position(get_screen_position() + b->get_position());1672selection_menu->reset_size();1673selection_menu->popup();1674}1675}16761677// Helper function to redirect mouse events to the active freelook viewport1678static bool _redirect_freelook_input(const Ref<InputEvent> &p_event, Node3DEditorViewport *p_exclude_viewport = nullptr) {1679if (Input::get_singleton()->get_mouse_mode() != Input::MOUSE_MODE_CAPTURED) {1680return false;1681}16821683Node3DEditor *editor = Node3DEditor::get_singleton();1684if (!editor->get_freelook_viewport()) {1685return false;1686}16871688Node3DEditorViewport *freelook_vp = editor->get_freelook_viewport();1689if (freelook_vp == p_exclude_viewport) {1690return false;1691}16921693Ref<InputEventMouse> mouse_event = p_event;1694if (!mouse_event.is_valid()) {1695return false;1696}16971698Control *target_surface = freelook_vp->get_surface();16991700target_surface->emit_signal(SceneStringName(gui_input), p_event);1701return true;1702}17031704// This is only active during instant transforms,1705// to capture and wrap mouse events outside the control.1706void Node3DEditorViewport::input(const Ref<InputEvent> &p_event) {1707ERR_FAIL_COND(!_edit.instant);1708Ref<InputEventMouseMotion> m = p_event;17091710if (m.is_valid()) {1711_edit.mouse_pos += _get_warped_mouse_motion(p_event);1712update_transform(_get_key_modifier(m) == Key::SHIFT);1713}1714}17151716void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {1717if (previewing || get_viewport()->gui_get_drag_data()) {1718return; //do NONE1719}17201721if (_redirect_freelook_input(p_event, this)) {1722return;1723}17241725EditorPlugin::AfterGUIInput after = EditorPlugin::AFTER_GUI_INPUT_PASS;1726{1727EditorNode *en = EditorNode::get_singleton();1728EditorPluginList *force_input_forwarding_list = en->get_editor_plugins_force_input_forwarding();1729if (!force_input_forwarding_list->is_empty()) {1730EditorPlugin::AfterGUIInput discard = force_input_forwarding_list->forward_3d_gui_input(camera, p_event, true);1731if (discard == EditorPlugin::AFTER_GUI_INPUT_STOP) {1732return;1733}1734if (discard == EditorPlugin::AFTER_GUI_INPUT_CUSTOM) {1735after = EditorPlugin::AFTER_GUI_INPUT_CUSTOM;1736}1737}1738}1739{1740EditorNode *en = EditorNode::get_singleton();1741EditorPluginList *over_plugin_list = en->get_editor_plugins_over();1742if (!over_plugin_list->is_empty()) {1743EditorPlugin::AfterGUIInput discard = over_plugin_list->forward_3d_gui_input(camera, p_event, false);1744if (discard == EditorPlugin::AFTER_GUI_INPUT_STOP) {1745return;1746}1747if (discard == EditorPlugin::AFTER_GUI_INPUT_CUSTOM) {1748after = EditorPlugin::AFTER_GUI_INPUT_CUSTOM;1749}1750}1751}17521753Ref<InputEventMouseButton> b = p_event;17541755if (b.is_valid()) {1756emit_signal(SNAME("clicked"));17571758ViewportNavMouseButton orbit_mouse_preference = (ViewportNavMouseButton)EDITOR_GET("editors/3d/navigation/orbit_mouse_button").operator int();1759ViewportNavMouseButton pan_mouse_preference = (ViewportNavMouseButton)EDITOR_GET("editors/3d/navigation/pan_mouse_button").operator int();1760ViewportNavMouseButton zoom_mouse_preference = (ViewportNavMouseButton)EDITOR_GET("editors/3d/navigation/zoom_mouse_button").operator int();17611762const real_t zoom_factor = 1 + (ZOOM_FREELOOK_MULTIPLIER - 1) * b->get_factor();1763switch (b->get_button_index()) {1764case MouseButton::WHEEL_UP: {1765if (is_freelook_active()) {1766scale_freelook_speed(zoom_factor);1767} else {1768scale_cursor_distance(1.0 / zoom_factor);1769}1770} break;1771case MouseButton::WHEEL_DOWN: {1772if (is_freelook_active()) {1773scale_freelook_speed(1.0 / zoom_factor);1774} else {1775scale_cursor_distance(zoom_factor);1776}1777} break;1778case MouseButton::RIGHT: {1779if (b->is_pressed()) {1780if (_edit.gizmo.is_valid()) {1781// Restore.1782_edit.gizmo->commit_handle(_edit.gizmo_handle, _edit.gizmo_handle_secondary, _edit.gizmo_initial_value, true);1783_edit.gizmo = Ref<EditorNode3DGizmo>();1784}17851786if (_edit.mode == TRANSFORM_NONE) {1787if (orbit_mouse_preference == NAVIGATION_RIGHT_MOUSE && _is_nav_modifier_pressed("spatial_editor/viewport_orbit_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_orbit_modifier_2")) {1788break;1789} else if (pan_mouse_preference == NAVIGATION_RIGHT_MOUSE && _is_nav_modifier_pressed("spatial_editor/viewport_pan_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_pan_modifier_2")) {1790break;1791} else if (zoom_mouse_preference == NAVIGATION_RIGHT_MOUSE && _is_nav_modifier_pressed("spatial_editor/viewport_zoom_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_zoom_modifier_2")) {1792break;1793}1794}17951796if (b->is_alt_pressed()) {1797_list_select(b);1798return;1799}18001801if (_edit.mode != TRANSFORM_NONE) {1802cancel_transform();1803break;1804}18051806const Key mod = _get_key_modifier(b);1807if (!orthogonal) {1808if (mod == _get_key_modifier_setting("editors/3d/freelook/freelook_activation_modifier")) {1809set_freelook_active(true);1810}1811}1812} else {1813set_freelook_active(false);1814}18151816if (freelook_active && !surface->has_focus()) {1817// Focus usually doesn't trigger on right-click, but in case of freelook it should,1818// otherwise using keyboard navigation would misbehave1819surface->grab_focus();1820}18211822} break;1823case MouseButton::MIDDLE: {1824if (b->is_pressed() && _edit.mode != TRANSFORM_NONE) {1825if (orbit_mouse_preference == NAVIGATION_MIDDLE_MOUSE && _is_nav_modifier_pressed("spatial_editor/viewport_orbit_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_orbit_modifier_2")) {1826break;1827} else if (pan_mouse_preference == NAVIGATION_MIDDLE_MOUSE && _is_nav_modifier_pressed("spatial_editor/viewport_pan_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_pan_modifier_2")) {1828break;1829} else if (zoom_mouse_preference == NAVIGATION_MIDDLE_MOUSE && _is_nav_modifier_pressed("spatial_editor/viewport_zoom_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_zoom_modifier_2")) {1830break;1831}18321833switch (_edit.plane) {1834case TRANSFORM_VIEW: {1835_edit.plane = TRANSFORM_X_AXIS;1836set_message(TTR("X-Axis Transform."), 2);1837view_type = VIEW_TYPE_USER;1838_update_name();1839} break;1840case TRANSFORM_X_AXIS: {1841_edit.plane = TRANSFORM_Y_AXIS;1842set_message(TTR("Y-Axis Transform."), 2);18431844} break;1845case TRANSFORM_Y_AXIS: {1846_edit.plane = TRANSFORM_Z_AXIS;1847set_message(TTR("Z-Axis Transform."), 2);18481849} break;1850case TRANSFORM_Z_AXIS: {1851_edit.plane = TRANSFORM_VIEW;1852// TRANSLATORS: This refers to the transform of the view plane.1853set_message(TTR("View Plane Transform."), 2);18541855} break;1856case TRANSFORM_YZ:1857case TRANSFORM_XZ:1858case TRANSFORM_XY: {1859} break;1860}1861}1862} break;1863case MouseButton::LEFT: {1864if (b->is_pressed()) {1865clicked_wants_append = b->is_shift_pressed();18661867if (_edit.mode != TRANSFORM_NONE && (_edit.instant || collision_reposition)) {1868commit_transform();1869break; // just commit the edit, stop processing the event so we don't deselect the object1870}1871if (orbit_mouse_preference == NAVIGATION_LEFT_MOUSE && _is_nav_modifier_pressed("spatial_editor/viewport_orbit_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_orbit_modifier_2")) {1872break;1873} else if (pan_mouse_preference == NAVIGATION_LEFT_MOUSE && _is_nav_modifier_pressed("spatial_editor/viewport_pan_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_pan_modifier_2")) {1874break;1875} else if (zoom_mouse_preference == NAVIGATION_LEFT_MOUSE && _is_nav_modifier_pressed("spatial_editor/viewport_zoom_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_zoom_modifier_2")) {1876break;1877}18781879if (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_RULER) {1880EditorNode::get_singleton()->get_scene_root()->add_child(ruler);1881collision_reposition = true;1882break;1883}18841885if (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_LIST_SELECT) {1886_list_select(b);1887break;1888}18891890_edit.mouse_pos = b->get_position();1891_edit.original_mouse_pos = b->get_position();1892_edit.snap = spatial_editor->is_snap_enabled();1893_edit.mode = TRANSFORM_NONE;1894_edit.original = spatial_editor->get_gizmo_transform(); // To prevent to break when flipping with scale.18951896bool can_select_gizmos = spatial_editor->get_single_selected_node();18971898{1899int idx = view_display_menu->get_popup()->get_item_index(VIEW_GIZMOS);1900int idx2 = view_display_menu->get_popup()->get_item_index(VIEW_TRANSFORM_GIZMO);1901can_select_gizmos = can_select_gizmos && view_display_menu->get_popup()->is_item_checked(idx);1902transform_gizmo_visible = view_display_menu->get_popup()->is_item_checked(idx2);1903}19041905// Gizmo handles1906if (can_select_gizmos) {1907Vector<Ref<Node3DGizmo>> gizmos = spatial_editor->get_single_selected_node()->get_gizmos();19081909bool intersected_handle = false;1910for (int i = 0; i < gizmos.size(); i++) {1911Ref<EditorNode3DGizmo> seg = gizmos[i];19121913if (seg.is_null()) {1914continue;1915}19161917int gizmo_handle = -1;1918bool gizmo_secondary = false;1919seg->handles_intersect_ray(camera, _edit.mouse_pos, b->is_shift_pressed(), gizmo_handle, gizmo_secondary);1920if (gizmo_handle != -1) {1921_edit.gizmo = seg;1922seg->begin_handle_action(gizmo_handle, gizmo_secondary);1923_edit.gizmo_handle = gizmo_handle;1924_edit.gizmo_handle_secondary = gizmo_secondary;1925_edit.gizmo_initial_value = seg->get_handle_value(gizmo_handle, gizmo_secondary);1926intersected_handle = true;1927break;1928}1929}19301931if (intersected_handle) {1932break;1933}1934}19351936// Transform gizmo1937if (transform_gizmo_visible && _transform_gizmo_select(_edit.mouse_pos)) {1938break;1939}19401941// Subgizmos1942if (can_select_gizmos) {1943Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(spatial_editor->get_single_selected_node());1944Vector<Ref<Node3DGizmo>> gizmos = spatial_editor->get_single_selected_node()->get_gizmos();19451946bool intersected_subgizmo = false;1947for (int i = 0; i < gizmos.size(); i++) {1948Ref<EditorNode3DGizmo> seg = gizmos[i];19491950if (seg.is_null()) {1951continue;1952}19531954int subgizmo_id = seg->subgizmos_intersect_ray(camera, _edit.mouse_pos);1955if (subgizmo_id != -1) {1956ERR_CONTINUE(!se);1957if (b->is_shift_pressed()) {1958if (se->subgizmos.has(subgizmo_id)) {1959se->subgizmos.erase(subgizmo_id);1960} else {1961se->subgizmos.insert(subgizmo_id, seg->get_subgizmo_transform(subgizmo_id));1962}1963} else {1964se->subgizmos.clear();1965se->subgizmos.insert(subgizmo_id, seg->get_subgizmo_transform(subgizmo_id));1966}19671968if (se->subgizmos.is_empty()) {1969se->gizmo = Ref<EditorNode3DGizmo>();1970} else {1971se->gizmo = seg;1972}19731974seg->redraw();1975spatial_editor->update_transform_gizmo();1976intersected_subgizmo = true;1977break;1978}1979}19801981if (intersected_subgizmo) {1982break;1983}1984}19851986clicked = ObjectID();19871988bool node_selected = get_selected_count() > 0;19891990if (after != EditorPlugin::AFTER_GUI_INPUT_CUSTOM) {1991// Single item selection.1992clicked = _select_ray(b->get_position());19931994if (clicked.is_valid() && !editor_selection->is_selected(ObjectDB::get_instance<Node>(clicked))) {1995if (!node_selected || (!b->is_alt_pressed() && !(spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT && b->is_command_or_control_pressed()))) {1996selection_in_progress = true;1997break;1998}1999}20002001if (clicked.is_null()) {2002if (node_selected) {2003TransformMode mode = TRANSFORM_NONE;20042005if (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT) {2006if (b->is_command_or_control_pressed()) {2007mode = TRANSFORM_ROTATE;2008} else if (b->is_alt_pressed()) {2009mode = TRANSFORM_TRANSLATE;2010}2011} else if (b->is_alt_pressed()) {2012switch (spatial_editor->get_tool_mode()) {2013case Node3DEditor::TOOL_MODE_ROTATE:2014mode = TRANSFORM_ROTATE;2015break;2016case Node3DEditor::TOOL_MODE_MOVE:2017mode = TRANSFORM_TRANSLATE;2018break;2019case Node3DEditor::TOOL_MODE_SCALE:2020mode = TRANSFORM_SCALE;2021break;2022default:2023break;2024}2025}20262027if (mode != TRANSFORM_NONE) {2028begin_transform(mode, false);2029break;2030}2031}20322033// Default to region select.2034cursor.region_select = true;2035cursor.region_begin = b->get_position();2036cursor.region_end = b->get_position();2037break;2038}2039}20402041if (clicked.is_valid() && !clicked_wants_append) {2042bool is_clicked_node_selected = editor_selection->is_selected(ObjectDB::get_instance<Node>(clicked));2043TransformMode mode = TRANSFORM_NONE;20442045switch (spatial_editor->get_tool_mode()) {2046case Node3DEditor::TOOL_MODE_SELECT:2047if (b->is_command_or_control_pressed() && node_selected) {2048mode = TRANSFORM_ROTATE;2049} else if (b->is_alt_pressed() && node_selected) {2050mode = TRANSFORM_TRANSLATE;2051} else if (is_clicked_node_selected) {2052mode = TRANSFORM_TRANSLATE;2053}2054break;2055case Node3DEditor::TOOL_MODE_ROTATE:2056if (is_clicked_node_selected || (b->is_alt_pressed() && node_selected)) {2057mode = TRANSFORM_ROTATE;2058}2059break;2060case Node3DEditor::TOOL_MODE_MOVE:2061if (is_clicked_node_selected || (b->is_alt_pressed() && node_selected)) {2062mode = TRANSFORM_TRANSLATE;2063}2064break;2065case Node3DEditor::TOOL_MODE_SCALE:2066if (is_clicked_node_selected || (b->is_alt_pressed() && node_selected)) {2067mode = TRANSFORM_SCALE;2068}2069break;2070default:2071break;2072}20732074if (mode != TRANSFORM_NONE) {2075begin_transform(mode, false);2076break;2077}2078}20792080surface->queue_redraw();2081} else {2082if (ruler->is_inside_tree()) {2083EditorNode::get_singleton()->get_scene_root()->remove_child(ruler);2084ruler_start_point->set_visible(false);2085ruler_end_point->set_visible(false);2086ruler_label->set_visible(false);2087collision_reposition = false;2088break;2089}20902091if (_edit.gizmo.is_valid()) {2092// Certain gizmo plugins should be able to commit handles without dragging them.2093if (_edit.original_mouse_pos != _edit.mouse_pos || _edit.gizmo->get_plugin()->can_commit_handle_on_click()) {2094_edit.gizmo->commit_handle(_edit.gizmo_handle, _edit.gizmo_handle_secondary, _edit.gizmo_initial_value, false);2095}20962097spatial_editor->get_single_selected_node()->update_gizmos();2098_edit.gizmo = Ref<EditorNode3DGizmo>();2099break;2100}21012102if (after != EditorPlugin::AFTER_GUI_INPUT_CUSTOM) {2103selection_in_progress = false;21042105if (clicked.is_valid() && _edit.mode == TRANSFORM_NONE) {2106_select_clicked(false);2107}21082109if (cursor.region_select) {2110_select_region();2111cursor.region_select = false;2112surface->queue_redraw();2113}2114}21152116if (!_edit.instant && _edit.mode != TRANSFORM_NONE) {2117Node3D *selected = spatial_editor->get_single_selected_node();2118Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr;21192120if (se && se->gizmo.is_valid()) {2121Vector<int> ids;2122Vector<Transform3D> restore;21232124for (const KeyValue<int, Transform3D> &GE : se->subgizmos) {2125ids.push_back(GE.key);2126restore.push_back(GE.value);2127}21282129se->gizmo->commit_subgizmos(ids, restore, false);2130} else {2131if (_edit.original_mouse_pos != _edit.mouse_pos) {2132commit_transform();2133}2134}2135_edit.mode = TRANSFORM_NONE;2136set_message("");2137spatial_editor->update_transform_gizmo();2138}2139}21402141} break;2142default:2143break;2144}2145}21462147ViewportNavMouseButton orbit_mouse_preference = (ViewportNavMouseButton)EDITOR_GET("editors/3d/navigation/orbit_mouse_button").operator int();2148ViewportNavMouseButton pan_mouse_preference = (ViewportNavMouseButton)EDITOR_GET("editors/3d/navigation/pan_mouse_button").operator int();2149ViewportNavMouseButton zoom_mouse_preference = (ViewportNavMouseButton)EDITOR_GET("editors/3d/navigation/zoom_mouse_button").operator int();2150bool orbit_mod_pressed = _is_nav_modifier_pressed("spatial_editor/viewport_orbit_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_orbit_modifier_2");2151bool pan_mod_pressed = _is_nav_modifier_pressed("spatial_editor/viewport_pan_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_pan_modifier_2");2152bool zoom_mod_pressed = _is_nav_modifier_pressed("spatial_editor/viewport_zoom_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_zoom_modifier_2");2153int orbit_mod_input_count = _get_shortcut_input_count("spatial_editor/viewport_orbit_modifier_1") + _get_shortcut_input_count("spatial_editor/viewport_orbit_modifier_2");2154int pan_mod_input_count = _get_shortcut_input_count("spatial_editor/viewport_pan_modifier_1") + _get_shortcut_input_count("spatial_editor/viewport_pan_modifier_2");2155int zoom_mod_input_count = _get_shortcut_input_count("spatial_editor/viewport_zoom_modifier_1") + _get_shortcut_input_count("spatial_editor/viewport_zoom_modifier_2");2156bool orbit_not_empty = !_is_shortcut_empty("spatial_editor/viewport_orbit_modifier_1") || !_is_shortcut_empty("spatial_editor/viewport_orbit_modifier_2");2157bool pan_not_empty = !_is_shortcut_empty("spatial_editor/viewport_pan_modifier_1") || !_is_shortcut_empty("spatial_editor/viewport_pan_modifier_2");2158bool zoom_not_empty = !_is_shortcut_empty("spatial_editor/viewport_zoom_modifier_1") || !_is_shortcut_empty("spatial_editor/viewport_zoom_modifier_2");2159Vector<ShortcutCheckSet> shortcut_check_sets;2160shortcut_check_sets.push_back(ShortcutCheckSet(orbit_mod_pressed, orbit_not_empty, orbit_mod_input_count, orbit_mouse_preference, NAVIGATION_ORBIT));2161shortcut_check_sets.push_back(ShortcutCheckSet(pan_mod_pressed, pan_not_empty, pan_mod_input_count, pan_mouse_preference, NAVIGATION_PAN));2162shortcut_check_sets.push_back(ShortcutCheckSet(zoom_mod_pressed, zoom_not_empty, zoom_mod_input_count, zoom_mouse_preference, NAVIGATION_ZOOM));2163shortcut_check_sets.sort_custom<ShortcutCheckSetComparator>();21642165Ref<InputEventMouseMotion> m = p_event;21662167// Instant transforms process mouse motion in input() to handle wrapping.2168if (m.is_valid() && !_edit.instant) {2169_edit.mouse_pos = m->get_position();21702171if (spatial_editor->get_single_selected_node()) {2172Vector<Ref<Node3DGizmo>> gizmos = spatial_editor->get_single_selected_node()->get_gizmos();21732174Ref<EditorNode3DGizmo> found_gizmo;2175int found_handle = -1;2176bool found_handle_secondary = false;21772178for (int i = 0; i < gizmos.size(); i++) {2179Ref<EditorNode3DGizmo> seg = gizmos[i];2180if (seg.is_null()) {2181continue;2182}21832184seg->handles_intersect_ray(camera, _edit.mouse_pos, false, found_handle, found_handle_secondary);21852186if (found_handle != -1) {2187found_gizmo = seg;2188break;2189}2190}21912192if (found_gizmo.is_valid()) {2193spatial_editor->select_gizmo_highlight_axis(-1);2194}21952196bool current_hover_handle_secondary = false;2197int current_hover_handle = spatial_editor->get_current_hover_gizmo_handle(current_hover_handle_secondary);2198if (found_gizmo != spatial_editor->get_current_hover_gizmo() || found_handle != current_hover_handle || found_handle_secondary != current_hover_handle_secondary) {2199spatial_editor->set_current_hover_gizmo(found_gizmo);2200spatial_editor->set_current_hover_gizmo_handle(found_handle, found_handle_secondary);2201spatial_editor->get_single_selected_node()->update_gizmos();2202}2203}22042205if (transform_gizmo_visible && spatial_editor->get_current_hover_gizmo().is_null() && !m->get_button_mask().has_flag(MouseButtonMask::LEFT) && _edit.gizmo.is_null()) {2206_transform_gizmo_select(_edit.mouse_pos, true);2207}22082209NavigationMode nav_mode = NAVIGATION_NONE;22102211if (_edit.gizmo.is_valid()) {2212_edit.gizmo->set_handle(_edit.gizmo_handle, _edit.gizmo_handle_secondary, camera, m->get_position());2213Variant v = _edit.gizmo->get_handle_value(_edit.gizmo_handle, _edit.gizmo_handle_secondary);2214String n = _edit.gizmo->get_handle_name(_edit.gizmo_handle, _edit.gizmo_handle_secondary);2215set_message(n + ": " + String(v));22162217} else if (m->get_button_mask().has_flag(MouseButtonMask::LEFT)) {2218NavigationMode change_nav_from_shortcut = _get_nav_mode_from_shortcut_check(NAVIGATION_LEFT_MOUSE, shortcut_check_sets, false);2219if (change_nav_from_shortcut != NAVIGATION_NONE) {2220nav_mode = change_nav_from_shortcut;2221} else {2222const bool movement_threshold_passed = _edit.original_mouse_pos.distance_to(_edit.mouse_pos) > 8 * EDSCALE;22232224if ((selection_in_progress || clicked_wants_append) && movement_threshold_passed && clicked.is_valid()) {2225cursor.region_select = true;2226cursor.region_begin = _edit.original_mouse_pos;2227clicked = ObjectID();2228}22292230if (cursor.region_select) {2231cursor.region_end = m->get_position();2232surface->queue_redraw();2233return;2234}22352236if (clicked.is_valid() && movement_threshold_passed && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_MOVE)) {2237bool is_select_mode = (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT);2238bool is_clicked_selected = editor_selection->is_selected(ObjectDB::get_instance<Node>(clicked));22392240if (_edit.mode == TRANSFORM_NONE && (is_select_mode || is_clicked_selected)) {2241_compute_edit(_edit.original_mouse_pos);2242clicked = ObjectID();2243_edit.mode = TRANSFORM_TRANSLATE;2244}2245}22462247if (_edit.mode == TRANSFORM_NONE || _edit.numeric_input != 0 || _edit.numeric_next_decimal != 0) {2248return;2249}22502251if (!selection_in_progress) {2252update_transform(_get_key_modifier(m) == Key::SHIFT);2253}2254}2255} else if (m->get_button_mask().has_flag(MouseButtonMask::RIGHT) || freelook_active) {2256NavigationMode change_nav_from_shortcut = _get_nav_mode_from_shortcut_check(NAVIGATION_RIGHT_MOUSE, shortcut_check_sets, false);2257if (m->get_button_mask().has_flag(MouseButtonMask::RIGHT) && change_nav_from_shortcut != NAVIGATION_NONE) {2258nav_mode = change_nav_from_shortcut;2259} else if (freelook_active) {2260nav_mode = NAVIGATION_LOOK;2261} else if (orthogonal) {2262nav_mode = NAVIGATION_PAN;2263}22642265} else if (m->get_button_mask().has_flag(MouseButtonMask::MIDDLE)) {2266NavigationMode change_nav_from_shortcut = _get_nav_mode_from_shortcut_check(NAVIGATION_MIDDLE_MOUSE, shortcut_check_sets, false);2267if (change_nav_from_shortcut != NAVIGATION_NONE) {2268nav_mode = change_nav_from_shortcut;2269}22702271} else if (m->get_button_mask().has_flag(MouseButtonMask::MB_XBUTTON1)) {2272NavigationMode change_nav_from_shortcut = _get_nav_mode_from_shortcut_check(NAVIGATION_MOUSE_4, shortcut_check_sets, false);2273if (change_nav_from_shortcut != NAVIGATION_NONE) {2274nav_mode = change_nav_from_shortcut;2275}22762277} else if (m->get_button_mask().has_flag(MouseButtonMask::MB_XBUTTON2)) {2278NavigationMode change_nav_from_shortcut = _get_nav_mode_from_shortcut_check(NAVIGATION_MOUSE_5, shortcut_check_sets, false);2279if (change_nav_from_shortcut != NAVIGATION_NONE) {2280nav_mode = change_nav_from_shortcut;2281}22822283} else if (EDITOR_GET("editors/3d/navigation/emulate_3_button_mouse")) {2284// Handle trackpad (no external mouse) use case2285NavigationMode change_nav_from_shortcut = _get_nav_mode_from_shortcut_check(NAVIGATION_LEFT_MOUSE, shortcut_check_sets, true);2286if (change_nav_from_shortcut != NAVIGATION_NONE) {2287nav_mode = change_nav_from_shortcut;2288}2289}22902291switch (nav_mode) {2292case NAVIGATION_PAN: {2293_nav_pan(m, _get_warped_mouse_motion(m));22942295} break;22962297case NAVIGATION_ZOOM: {2298_nav_zoom(m, m->get_relative());22992300} break;23012302case NAVIGATION_ORBIT: {2303_nav_orbit(m, _get_warped_mouse_motion(m));23042305} break;23062307case NAVIGATION_LOOK: {2308_nav_look(m, _get_warped_mouse_motion(m));23092310} break;23112312default: {2313}2314}2315}23162317Ref<InputEventMagnifyGesture> magnify_gesture = p_event;2318if (magnify_gesture.is_valid()) {2319if (is_freelook_active()) {2320scale_freelook_speed(magnify_gesture->get_factor());2321} else {2322scale_cursor_distance(1.0 / magnify_gesture->get_factor());2323}2324}23252326Ref<InputEventPanGesture> pan_gesture = p_event;2327if (pan_gesture.is_valid()) {2328NavigationMode nav_mode = NAVIGATION_NONE;23292330for (const ShortcutCheckSet &shortcut_check_set : shortcut_check_sets) {2331if (shortcut_check_set.mod_pressed) {2332nav_mode = shortcut_check_set.result_nav_mode;2333break;2334}2335}23362337switch (nav_mode) {2338case NAVIGATION_PAN: {2339_nav_pan(pan_gesture, -pan_gesture->get_delta());23402341} break;23422343case NAVIGATION_ZOOM: {2344_nav_zoom(pan_gesture, pan_gesture->get_delta());23452346} break;23472348case NAVIGATION_ORBIT: {2349_nav_orbit(pan_gesture, -pan_gesture->get_delta());23502351} break;23522353case NAVIGATION_LOOK: {2354_nav_look(pan_gesture, pan_gesture->get_delta());23552356} break;23572358default: {2359}2360}2361}23622363Ref<InputEventKey> k = p_event;23642365if (k.is_valid()) {2366if (!k->is_pressed()) {2367return;2368}23692370if (_edit.instant) {2371// In a Blender-style transform, numbers set the magnitude of the transform.2372// E.g. pressing g4.5x means "translate 4.5 units along the X axis".2373bool processed = true;2374Key keycode = k->get_keycode();2375Key physical_keycode = k->get_physical_keycode();23762377// Use physical keycode for main keyboard numbers (for non-QWERTY layouts like AZERTY)2378// but regular keycode for numpad numbers.2379if ((physical_keycode >= Key::KEY_0 && physical_keycode <= Key::KEY_9) || (keycode >= Key::KP_0 && keycode <= Key::KP_9)) {2380uint32_t value;2381if (physical_keycode >= Key::KEY_0 && physical_keycode <= Key::KEY_9) {2382value = uint32_t(physical_keycode - Key::KEY_0);2383} else {2384value = uint32_t(keycode - Key::KP_0);2385}23862387if (_edit.numeric_next_decimal < 0) {2388_edit.numeric_input = _edit.numeric_input + value * Math::pow(10.0, _edit.numeric_next_decimal--);2389} else {2390_edit.numeric_input = _edit.numeric_input * 10 + value;2391}2392update_transform_numeric();2393} else if (keycode == Key::MINUS || keycode == Key::KP_SUBTRACT) {2394_edit.numeric_negate = !_edit.numeric_negate;2395update_transform_numeric();2396} else if (keycode == Key::PERIOD || physical_keycode == Key::KP_PERIOD) {2397// Use physical keycode for KP_PERIOD to ensure numpad period works consistently2398// across different keyboard layouts (like nordic keyboards).2399if (_edit.numeric_next_decimal == 0) {2400_edit.numeric_next_decimal = -1;2401}2402} else if (keycode == Key::ENTER || keycode == Key::KP_ENTER || keycode == Key::SPACE) {2403commit_transform();2404} else {2405processed = false;2406}24072408if (processed) {2409// Ignore mouse inputs once we receive a numeric input.2410set_process_input(false);2411accept_event();2412return;2413}2414}24152416if (EDITOR_GET("editors/3d/navigation/emulate_numpad")) {2417const Key code = k->get_physical_keycode();2418if (code >= Key::KEY_0 && code <= Key::KEY_9) {2419k->set_keycode(code - Key::KEY_0 + Key::KP_0);2420}2421}24222423if (_edit.mode == TRANSFORM_NONE) {2424if (_edit.gizmo.is_null() && is_freelook_active() && k->get_keycode() == Key::ESCAPE) {2425set_freelook_active(false);2426return;2427}24282429if (_edit.gizmo.is_valid() && (k->get_keycode() == Key::ESCAPE || k->get_keycode() == Key::BACKSPACE)) {2430// Restore.2431_edit.gizmo->commit_handle(_edit.gizmo_handle, _edit.gizmo_handle_secondary, _edit.gizmo_initial_value, true);2432_edit.gizmo = Ref<EditorNode3DGizmo>();2433}2434if (k->get_keycode() == Key::ESCAPE && !cursor.region_select && !k->is_echo()) {2435_clear_selected();2436return;2437}2438} else {2439// We're actively transforming, handle keys specially2440TransformPlane new_plane = TRANSFORM_VIEW;2441if (ED_IS_SHORTCUT("spatial_editor/lock_transform_x", p_event)) {2442new_plane = TRANSFORM_X_AXIS;2443} else if (ED_IS_SHORTCUT("spatial_editor/lock_transform_y", p_event)) {2444new_plane = TRANSFORM_Y_AXIS;2445} else if (ED_IS_SHORTCUT("spatial_editor/lock_transform_z", p_event)) {2446new_plane = TRANSFORM_Z_AXIS;2447} else if (_edit.mode != TRANSFORM_ROTATE) { // rotating on a plane doesn't make sense2448if (ED_IS_SHORTCUT("spatial_editor/lock_transform_yz", p_event)) {2449new_plane = TRANSFORM_YZ;2450} else if (ED_IS_SHORTCUT("spatial_editor/lock_transform_xz", p_event)) {2451new_plane = TRANSFORM_XZ;2452} else if (ED_IS_SHORTCUT("spatial_editor/lock_transform_xy", p_event)) {2453new_plane = TRANSFORM_XY;2454}2455}24562457if (new_plane != TRANSFORM_VIEW) {2458if (new_plane != _edit.plane) {2459// lock me once and get a global constraint2460_edit.plane = new_plane;2461spatial_editor->set_local_coords_enabled(false);2462} else if (!spatial_editor->are_local_coords_enabled()) {2463// lock me twice and get a local constraint2464spatial_editor->set_local_coords_enabled(true);2465} else {2466// lock me thrice and we're back where we started2467_edit.plane = TRANSFORM_VIEW;2468spatial_editor->set_local_coords_enabled(false);2469}2470if (_edit.numeric_input != 0 || _edit.numeric_next_decimal != 0) {2471update_transform_numeric();2472} else {2473update_transform(Input::get_singleton()->is_key_pressed(Key::SHIFT));2474}2475accept_event();2476return;2477}2478}2479if (ED_IS_SHORTCUT("spatial_editor/snap", p_event)) {2480if (_edit.mode != TRANSFORM_NONE) {2481_edit.snap = !_edit.snap;2482}2483}2484if (ED_IS_SHORTCUT("spatial_editor/bottom_view", p_event)) {2485_menu_option(VIEW_BOTTOM);2486}2487if (ED_IS_SHORTCUT("spatial_editor/top_view", p_event)) {2488_menu_option(VIEW_TOP);2489}2490if (ED_IS_SHORTCUT("spatial_editor/rear_view", p_event)) {2491_menu_option(VIEW_REAR);2492}2493if (ED_IS_SHORTCUT("spatial_editor/front_view", p_event)) {2494_menu_option(VIEW_FRONT);2495}2496if (ED_IS_SHORTCUT("spatial_editor/left_view", p_event)) {2497_menu_option(VIEW_LEFT);2498}2499if (ED_IS_SHORTCUT("spatial_editor/right_view", p_event)) {2500_menu_option(VIEW_RIGHT);2501}2502if (ED_IS_SHORTCUT("spatial_editor/orbit_view_down", p_event)) {2503// Clamp rotation to roughly -90..90 degrees so the user can't look upside-down and end up disoriented.2504cursor.x_rot = CLAMP(cursor.x_rot - Math::PI / 12.0, -1.57, 1.57);2505view_type = VIEW_TYPE_USER;2506_update_name();2507}2508if (ED_IS_SHORTCUT("spatial_editor/orbit_view_up", p_event)) {2509// Clamp rotation to roughly -90..90 degrees so the user can't look upside-down and end up disoriented.2510cursor.x_rot = CLAMP(cursor.x_rot + Math::PI / 12.0, -1.57, 1.57);2511view_type = VIEW_TYPE_USER;2512_update_name();2513}2514if (ED_IS_SHORTCUT("spatial_editor/orbit_view_right", p_event)) {2515cursor.y_rot -= Math::PI / 12.0;2516view_type = VIEW_TYPE_USER;2517_update_name();2518}2519if (ED_IS_SHORTCUT("spatial_editor/orbit_view_left", p_event)) {2520cursor.y_rot += Math::PI / 12.0;2521view_type = VIEW_TYPE_USER;2522_update_name();2523}2524if (ED_IS_SHORTCUT("spatial_editor/orbit_view_180", p_event)) {2525cursor.y_rot += Math::PI;2526view_type = VIEW_TYPE_USER;2527_update_name();2528}2529if (ED_IS_SHORTCUT("spatial_editor/focus_origin", p_event)) {2530_menu_option(VIEW_CENTER_TO_ORIGIN);2531}2532if (ED_IS_SHORTCUT("spatial_editor/focus_selection", p_event)) {2533_menu_option(VIEW_CENTER_TO_SELECTION);2534}2535if (ED_IS_SHORTCUT("spatial_editor/align_transform_with_view", p_event)) {2536_menu_option(VIEW_ALIGN_TRANSFORM_WITH_VIEW);2537}2538if (ED_IS_SHORTCUT("spatial_editor/align_rotation_with_view", p_event)) {2539_menu_option(VIEW_ALIGN_ROTATION_WITH_VIEW);2540}2541if (ED_IS_SHORTCUT("spatial_editor/insert_anim_key", p_event)) {2542if (!get_selected_count() || _edit.mode != TRANSFORM_NONE) {2543return;2544}25452546if (!AnimationPlayerEditor::get_singleton()->get_track_editor()->has_keying()) {2547set_message(TTR("Keying is disabled (no key inserted)."));2548return;2549}25502551const List<Node *> &selection = editor_selection->get_top_selected_node_list();25522553for (Node *E : selection) {2554Node3D *sp = Object::cast_to<Node3D>(E);2555if (!sp) {2556continue;2557}25582559spatial_editor->emit_signal(SNAME("transform_key_request"), sp, "", sp->get_transform());2560}25612562set_message(TTR("Animation Key Inserted."));2563}2564if (ED_IS_SHORTCUT("spatial_editor/cancel_transform", p_event) && _edit.mode != TRANSFORM_NONE) {2565cancel_transform();2566}2567if (!is_freelook_active() && !k->is_echo()) {2568if (ED_IS_SHORTCUT("spatial_editor/instant_translate", p_event) && (_edit.mode != TRANSFORM_TRANSLATE || collision_reposition)) {2569if (_edit.mode == TRANSFORM_NONE) {2570begin_transform(TRANSFORM_TRANSLATE, true);2571} else if (_edit.instant || collision_reposition) {2572commit_transform();2573begin_transform(TRANSFORM_TRANSLATE, true);2574}2575}2576if (ED_IS_SHORTCUT("spatial_editor/instant_rotate", p_event) && _edit.mode != TRANSFORM_ROTATE) {2577if (_edit.mode == TRANSFORM_NONE) {2578begin_transform(TRANSFORM_ROTATE, true);2579} else if (_edit.instant || collision_reposition) {2580commit_transform();2581begin_transform(TRANSFORM_ROTATE, true);2582}2583}2584if (ED_IS_SHORTCUT("spatial_editor/instant_scale", p_event) && _edit.mode != TRANSFORM_SCALE) {2585if (_edit.mode == TRANSFORM_NONE) {2586begin_transform(TRANSFORM_SCALE, true);2587} else if (_edit.instant || collision_reposition) {2588commit_transform();2589begin_transform(TRANSFORM_SCALE, true);2590}2591}2592if (ED_IS_SHORTCUT("spatial_editor/collision_reposition", p_event) && editor_selection->get_top_selected_node_list().size() == 1 && !collision_reposition) {2593if (_edit.mode == TRANSFORM_NONE || _edit.instant) {2594if (_edit.mode == TRANSFORM_NONE) {2595_compute_edit(_edit.mouse_pos);2596} else {2597commit_transform();2598_compute_edit(_edit.mouse_pos);2599}2600_edit.mode = TRANSFORM_TRANSLATE;2601collision_reposition = true;2602}2603}2604}26052606// Freelook doesn't work in orthogonal mode.2607if (!orthogonal && ED_IS_SHORTCUT("spatial_editor/freelook_toggle", p_event)) {2608set_freelook_active(!is_freelook_active());26092610} else if (k->get_keycode() == Key::ESCAPE) {2611set_freelook_active(false);2612}26132614if (k->get_keycode() == Key::SPACE) {2615if (!k->is_pressed()) {2616emit_signal(SNAME("toggle_maximize_view"), this);2617}2618}26192620if (ED_IS_SHORTCUT("spatial_editor/decrease_fov", p_event)) {2621scale_fov(-0.05);2622}26232624if (ED_IS_SHORTCUT("spatial_editor/increase_fov", p_event)) {2625scale_fov(0.05);2626}26272628if (ED_IS_SHORTCUT("spatial_editor/reset_fov", p_event)) {2629reset_fov();2630}2631}26322633// freelook uses most of the useful shortcuts, like save, so its ok2634// to consider freelook active as end of the line for future events.2635if (freelook_active) {2636accept_event();2637}2638}26392640int Node3DEditorViewport::_get_shortcut_input_count(const String &p_name) {2641Ref<Shortcut> check_shortcut = ED_GET_SHORTCUT(p_name);26422643ERR_FAIL_COND_V_MSG(check_shortcut.is_null(), 0, "The Shortcut was null, possible name mismatch.");26442645return check_shortcut->get_events().size();2646}26472648Node3DEditorViewport::NavigationMode Node3DEditorViewport::_get_nav_mode_from_shortcut_check(ViewportNavMouseButton p_mouse_button, Vector<ShortcutCheckSet> p_shortcut_check_sets, bool p_use_not_empty) {2649if (p_use_not_empty) {2650for (const ShortcutCheckSet &shortcut_check_set : p_shortcut_check_sets) {2651if (shortcut_check_set.mod_pressed && shortcut_check_set.shortcut_not_empty) {2652return shortcut_check_set.result_nav_mode;2653}2654}2655} else {2656for (const ShortcutCheckSet &shortcut_check_set : p_shortcut_check_sets) {2657if (shortcut_check_set.mouse_preference == p_mouse_button && shortcut_check_set.mod_pressed) {2658return shortcut_check_set.result_nav_mode;2659}2660}2661}26622663return NAVIGATION_NONE;2664}26652666void Node3DEditorViewport::_nav_pan(Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative) {2667const NavigationScheme nav_scheme = (NavigationScheme)EDITOR_GET("editors/3d/navigation/navigation_scheme").operator int();2668const real_t translation_sensitivity = EDITOR_GET("editors/3d/navigation_feel/translation_sensitivity");26692670real_t pan_speed = translation_sensitivity / 150.0;2671if (p_event.is_valid() && nav_scheme == NAVIGATION_MAYA && p_event->is_shift_pressed()) {2672pan_speed *= 10;2673}26742675Transform3D camera_transform;26762677camera_transform.translate_local(cursor.pos);2678camera_transform.basis.rotate(Vector3(1, 0, 0), -cursor.x_rot);2679camera_transform.basis.rotate(Vector3(0, 1, 0), -cursor.y_rot);2680const bool invert_x_axis = EDITOR_GET("editors/3d/navigation/invert_x_axis");2681const bool invert_y_axis = EDITOR_GET("editors/3d/navigation/invert_y_axis");2682Vector3 translation(2683(invert_x_axis ? -1 : 1) * -p_relative.x * pan_speed,2684(invert_y_axis ? -1 : 1) * p_relative.y * pan_speed,26850);2686translation *= cursor.distance / DISTANCE_DEFAULT;2687camera_transform.translate_local(translation);2688cursor.pos = camera_transform.origin;2689}26902691void Node3DEditorViewport::_nav_zoom(Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative) {2692const NavigationScheme nav_scheme = (NavigationScheme)EDITOR_GET("editors/3d/navigation/navigation_scheme").operator int();26932694real_t zoom_speed = 1 / 80.0;2695if (p_event.is_valid() && nav_scheme == NAVIGATION_MAYA && p_event->is_shift_pressed()) {2696zoom_speed *= 10;2697}26982699NavigationZoomStyle zoom_style = (NavigationZoomStyle)EDITOR_GET("editors/3d/navigation/zoom_style").operator int();2700if (zoom_style == NAVIGATION_ZOOM_HORIZONTAL) {2701if (p_relative.x > 0) {2702scale_cursor_distance(1 - p_relative.x * zoom_speed);2703} else if (p_relative.x < 0) {2704scale_cursor_distance(1.0 / (1 + p_relative.x * zoom_speed));2705}2706} else {2707if (p_relative.y > 0) {2708scale_cursor_distance(1 + p_relative.y * zoom_speed);2709} else if (p_relative.y < 0) {2710scale_cursor_distance(1.0 / (1 - p_relative.y * zoom_speed));2711}2712}2713}27142715void Node3DEditorViewport::_nav_orbit(Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative) {2716if (lock_rotation) {2717_nav_pan(p_event, p_relative);2718return;2719}27202721if (orthogonal && auto_orthogonal) {2722_menu_option(VIEW_PERSPECTIVE);2723}27242725const real_t degrees_per_pixel = EDITOR_GET("editors/3d/navigation_feel/orbit_sensitivity");2726const real_t radians_per_pixel = Math::deg_to_rad(degrees_per_pixel);2727const bool invert_y_axis = EDITOR_GET("editors/3d/navigation/invert_y_axis");2728const bool invert_x_axis = EDITOR_GET("editors/3d/navigation/invert_x_axis");27292730if (invert_y_axis) {2731cursor.x_rot -= p_relative.y * radians_per_pixel;2732} else {2733cursor.x_rot += p_relative.y * radians_per_pixel;2734}2735// Clamp the Y rotation to roughly -90..90 degrees so the user can't look upside-down and end up disoriented.2736cursor.x_rot = CLAMP(cursor.x_rot, -1.57, 1.57);27372738if (invert_x_axis) {2739cursor.y_rot -= p_relative.x * radians_per_pixel;2740} else {2741cursor.y_rot += p_relative.x * radians_per_pixel;2742}2743view_type = VIEW_TYPE_USER;2744_update_name();2745}27462747void Node3DEditorViewport::_nav_look(Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative) {2748if (orthogonal) {2749_nav_pan(p_event, p_relative);2750return;2751}27522753if (orthogonal && auto_orthogonal) {2754_menu_option(VIEW_PERSPECTIVE);2755}27562757// Scale mouse sensitivity with camera FOV scale when zoomed in to make it easier to point at things.2758const real_t degrees_per_pixel = real_t(EDITOR_GET("editors/3d/freelook/freelook_sensitivity")) * MIN(1.0, cursor.fov_scale);2759const real_t radians_per_pixel = Math::deg_to_rad(degrees_per_pixel);2760const bool invert_y_axis = EDITOR_GET("editors/3d/navigation/invert_y_axis");27612762// Note: do NOT assume the camera has the "current" transform, because it is interpolated and may have "lag".2763const Transform3D prev_camera_transform = to_camera_transform(cursor);27642765if (invert_y_axis) {2766cursor.x_rot -= p_relative.y * radians_per_pixel;2767} else {2768cursor.x_rot += p_relative.y * radians_per_pixel;2769}2770// Clamp the Y rotation to roughly -90..90 degrees so the user can't look upside-down and end up disoriented.2771cursor.x_rot = CLAMP(cursor.x_rot, -1.57, 1.57);27722773cursor.y_rot += p_relative.x * radians_per_pixel;27742775// Look is like the opposite of Orbit: the focus point rotates around the camera2776Transform3D camera_transform = to_camera_transform(cursor);2777Vector3 pos = camera_transform.xform(Vector3(0, 0, 0));2778Vector3 prev_pos = prev_camera_transform.xform(Vector3(0, 0, 0));2779Vector3 diff = prev_pos - pos;2780cursor.pos += diff;27812782view_type = VIEW_TYPE_USER;2783_update_name();2784}27852786void Node3DEditorViewport::set_freelook_active(bool active_now) {2787if (!freelook_active && active_now) {2788// Sync camera cursor to cursor to "cut" interpolation jumps due to changing referential2789cursor = camera_cursor;27902791// Make sure eye_pos is synced, because freelook referential is eye pos rather than orbit pos2792Vector3 forward = to_camera_transform(cursor).basis.xform(Vector3(0, 0, -1));2793cursor.eye_pos = cursor.pos - cursor.distance * forward;2794// Also sync the camera cursor, otherwise switching to freelook will be trippy if inertia is active2795camera_cursor.eye_pos = cursor.eye_pos;27962797if (EDITOR_GET("editors/3d/freelook/freelook_speed_zoom_link")) {2798// Re-adjust freelook speed from the current zoom level2799real_t base_speed = EDITOR_GET("editors/3d/freelook/freelook_base_speed");2800freelook_speed = base_speed * cursor.distance;2801}28022803previous_mouse_position = get_local_mouse_position();28042805spatial_editor->set_freelook_viewport(this);28062807// Hide mouse like in an FPS (warping doesn't work)2808Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_CAPTURED);28092810} else if (freelook_active && !active_now) {2811// Sync camera cursor to cursor to "cut" interpolation jumps due to changing referential2812cursor = camera_cursor;28132814spatial_editor->set_freelook_viewport(nullptr);28152816// Restore mouse2817Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE);28182819// Restore the previous mouse position when leaving freelook mode.2820// This is done because leaving `Input.MOUSE_MODE_CAPTURED` will center the cursor2821// due to OS limitations.2822warp_mouse(previous_mouse_position);2823}28242825freelook_active = active_now;2826}28272828void Node3DEditorViewport::scale_fov(real_t p_fov_offset) {2829cursor.fov_scale = CLAMP(cursor.fov_scale + p_fov_offset, 0.1, 2.5);2830surface->queue_redraw();2831}28322833void Node3DEditorViewport::reset_fov() {2834cursor.fov_scale = 1.0;2835surface->queue_redraw();2836}28372838void Node3DEditorViewport::scale_cursor_distance(real_t scale) {2839real_t min_distance = MAX(camera->get_near() * 4, ZOOM_FREELOOK_MIN);2840real_t max_distance = MIN(camera->get_far() / 4, ZOOM_FREELOOK_MAX);2841if (unlikely(min_distance > max_distance)) {2842cursor.distance = (min_distance + max_distance) / 2;2843} else {2844cursor.distance = CLAMP(cursor.distance * scale, min_distance, max_distance);2845}28462847if (cursor.distance == max_distance || cursor.distance == min_distance) {2848zoom_failed_attempts_count++;2849} else {2850zoom_failed_attempts_count = 0;2851}28522853zoom_indicator_delay = ZOOM_FREELOOK_INDICATOR_DELAY_S;2854surface->queue_redraw();2855}28562857void Node3DEditorViewport::scale_freelook_speed(real_t scale) {2858real_t min_speed = MAX(camera->get_near() * 4, ZOOM_FREELOOK_MIN);2859real_t max_speed = MIN(camera->get_far() / 4, ZOOM_FREELOOK_MAX);2860if (unlikely(min_speed > max_speed)) {2861freelook_speed = (min_speed + max_speed) / 2;2862} else {2863freelook_speed = CLAMP(freelook_speed * scale, min_speed, max_speed);2864}28652866zoom_indicator_delay = ZOOM_FREELOOK_INDICATOR_DELAY_S;2867surface->queue_redraw();2868}28692870bool Node3DEditorViewport::_is_nav_modifier_pressed(const String &p_name) {2871return _is_shortcut_empty(p_name) || Input::get_singleton()->is_action_pressed(p_name);2872}28732874bool Node3DEditorViewport::_is_shortcut_empty(const String &p_name) {2875Ref<Shortcut> check_shortcut = ED_GET_SHORTCUT(p_name);28762877ERR_FAIL_COND_V_MSG(check_shortcut.is_null(), true, "The Shortcut was null, possible name mismatch.");28782879return check_shortcut->get_events().is_empty();2880}28812882Point2 Node3DEditorViewport::_get_warped_mouse_motion(const Ref<InputEventMouseMotion> &p_ev_mouse_motion) const {2883Point2 relative;2884if (bool(EDITOR_GET("editors/3d/navigation/warped_mouse_panning"))) {2885relative = Input::get_singleton()->warp_mouse_motion(p_ev_mouse_motion, surface->get_global_rect());2886} else {2887relative = p_ev_mouse_motion->get_relative();2888}2889return relative;2890}28912892void Node3DEditorViewport::_update_freelook(real_t delta) {2893if (!is_freelook_active()) {2894return;2895}28962897const FreelookNavigationScheme navigation_scheme = (FreelookNavigationScheme)EDITOR_GET("editors/3d/freelook/freelook_navigation_scheme").operator int();28982899Vector3 forward;2900if (navigation_scheme == FREELOOK_FULLY_AXIS_LOCKED) {2901// Forward/backward keys will always go straight forward/backward, never moving on the Y axis.2902forward = Vector3(0, 0, -1).rotated(Vector3(0, 1, 0), camera->get_rotation().y);2903} else {2904// Forward/backward keys will be relative to the camera pitch.2905forward = camera->get_transform().basis.xform(Vector3(0, 0, -1));2906}29072908const Vector3 right = camera->get_transform().basis.xform(Vector3(1, 0, 0));29092910Vector3 up;2911if (navigation_scheme == FREELOOK_PARTIALLY_AXIS_LOCKED || navigation_scheme == FREELOOK_FULLY_AXIS_LOCKED) {2912// Up/down keys will always go up/down regardless of camera pitch.2913up = Vector3(0, 1, 0);2914} else {2915// Up/down keys will be relative to the camera pitch.2916up = camera->get_transform().basis.xform(Vector3(0, 1, 0));2917}29182919Vector3 direction;29202921// Use actions from the inputmap, as this is the only way to reliably detect input in this method.2922// See #54469 for more discussion and explanation.2923Input *inp = Input::get_singleton();2924if (inp->is_action_pressed("spatial_editor/freelook_left")) {2925direction -= right;2926}2927if (inp->is_action_pressed("spatial_editor/freelook_right")) {2928direction += right;2929}2930if (inp->is_action_pressed("spatial_editor/freelook_forward")) {2931direction += forward;2932}2933if (inp->is_action_pressed("spatial_editor/freelook_backwards")) {2934direction -= forward;2935}2936if (inp->is_action_pressed("spatial_editor/freelook_up")) {2937direction += up;2938}2939if (inp->is_action_pressed("spatial_editor/freelook_down")) {2940direction -= up;2941}29422943real_t speed = freelook_speed;29442945if (inp->is_action_pressed("spatial_editor/freelook_speed_modifier")) {2946speed *= 3.0;2947}2948if (inp->is_action_pressed("spatial_editor/freelook_slow_modifier")) {2949speed *= 0.333333;2950}29512952const Vector3 motion = direction * speed * delta;2953cursor.pos += motion;2954cursor.eye_pos += motion;2955}29562957void Node3DEditorViewport::set_message(const String &p_message, float p_time) {2958message = p_message;2959message_time = p_time;2960}29612962void Node3DEditorPlugin::edited_scene_changed() {2963for (uint32_t i = 0; i < Node3DEditor::VIEWPORTS_COUNT; i++) {2964Node3DEditorViewport *viewport = Node3DEditor::get_singleton()->get_editor_viewport(i);2965if (viewport->is_visible()) {2966viewport->notification(Control::NOTIFICATION_VISIBILITY_CHANGED);2967}2968}2969}29702971void Node3DEditorViewport::_project_settings_changed() {2972// Update shadow atlas if changed.2973int shadowmap_size = GLOBAL_GET("rendering/lights_and_shadows/positional_shadow/atlas_size");2974bool shadowmap_16_bits = GLOBAL_GET("rendering/lights_and_shadows/positional_shadow/atlas_16_bits");2975int atlas_q0 = GLOBAL_GET("rendering/lights_and_shadows/positional_shadow/atlas_quadrant_0_subdiv");2976int atlas_q1 = GLOBAL_GET("rendering/lights_and_shadows/positional_shadow/atlas_quadrant_1_subdiv");2977int atlas_q2 = GLOBAL_GET("rendering/lights_and_shadows/positional_shadow/atlas_quadrant_2_subdiv");2978int atlas_q3 = GLOBAL_GET("rendering/lights_and_shadows/positional_shadow/atlas_quadrant_3_subdiv");29792980viewport->set_positional_shadow_atlas_size(shadowmap_size);2981viewport->set_positional_shadow_atlas_16_bits(shadowmap_16_bits);2982viewport->set_positional_shadow_atlas_quadrant_subdiv(0, Viewport::PositionalShadowAtlasQuadrantSubdiv(atlas_q0));2983viewport->set_positional_shadow_atlas_quadrant_subdiv(1, Viewport::PositionalShadowAtlasQuadrantSubdiv(atlas_q1));2984viewport->set_positional_shadow_atlas_quadrant_subdiv(2, Viewport::PositionalShadowAtlasQuadrantSubdiv(atlas_q2));2985viewport->set_positional_shadow_atlas_quadrant_subdiv(3, Viewport::PositionalShadowAtlasQuadrantSubdiv(atlas_q3));29862987_update_shrink();29882989// Update MSAA, screen-space AA and debanding if changed29902991const int msaa_mode = GLOBAL_GET("rendering/anti_aliasing/quality/msaa_3d");2992viewport->set_msaa_3d(Viewport::MSAA(msaa_mode));2993const int ssaa_mode = GLOBAL_GET("rendering/anti_aliasing/quality/screen_space_aa");2994viewport->set_screen_space_aa(Viewport::ScreenSpaceAA(ssaa_mode));2995const bool use_taa = GLOBAL_GET("rendering/anti_aliasing/quality/use_taa");2996viewport->set_use_taa(use_taa);29972998const bool transparent_background = GLOBAL_GET("rendering/viewport/transparent_background");2999viewport->set_transparent_background(transparent_background);30003001const bool use_hdr_2d = GLOBAL_GET("rendering/viewport/hdr_2d");3002viewport->set_use_hdr_2d(use_hdr_2d);30033004const bool use_debanding = GLOBAL_GET("rendering/anti_aliasing/quality/use_debanding");3005viewport->set_use_debanding(use_debanding);30063007const bool use_occlusion_culling = GLOBAL_GET("rendering/occlusion_culling/use_occlusion_culling");3008viewport->set_use_occlusion_culling(use_occlusion_culling);30093010const float mesh_lod_threshold = GLOBAL_GET("rendering/mesh_lod/lod_change/threshold_pixels");3011viewport->set_mesh_lod_threshold(mesh_lod_threshold);30123013const Viewport::Scaling3DMode scaling_3d_mode = Viewport::Scaling3DMode(int(GLOBAL_GET("rendering/scaling_3d/mode")));3014viewport->set_scaling_3d_mode(scaling_3d_mode);30153016const float scaling_3d_scale = GLOBAL_GET("rendering/scaling_3d/scale");3017viewport->set_scaling_3d_scale(scaling_3d_scale);30183019const float fsr_sharpness = GLOBAL_GET("rendering/scaling_3d/fsr_sharpness");3020viewport->set_fsr_sharpness(fsr_sharpness);30213022const float texture_mipmap_bias = GLOBAL_GET("rendering/textures/default_filters/texture_mipmap_bias");3023viewport->set_texture_mipmap_bias(texture_mipmap_bias);30243025const Viewport::AnisotropicFiltering anisotropic_filtering_level = Viewport::AnisotropicFiltering(int(GLOBAL_GET("rendering/textures/default_filters/anisotropic_filtering_level")));3026viewport->set_anisotropic_filtering_level(anisotropic_filtering_level);3027}30283029static void override_button_stylebox(Button *p_button, const Ref<StyleBox> p_stylebox) {3030p_button->begin_bulk_theme_override();3031p_button->add_theme_style_override(CoreStringName(normal), p_stylebox);3032p_button->add_theme_style_override("normal_mirrored", p_stylebox);3033p_button->add_theme_style_override(SceneStringName(hover), p_stylebox);3034p_button->add_theme_style_override("hover_mirrored", p_stylebox);3035p_button->add_theme_style_override("hover_pressed", p_stylebox);3036p_button->add_theme_style_override("hover_pressed_mirrored", p_stylebox);3037p_button->add_theme_style_override(SceneStringName(pressed), p_stylebox);3038p_button->add_theme_style_override("pressed_mirrored", p_stylebox);3039p_button->add_theme_style_override("focus", p_stylebox);3040p_button->add_theme_style_override("focus_mirrored", p_stylebox);3041p_button->add_theme_style_override("disabled", p_stylebox);3042p_button->add_theme_style_override("disabled_mirrored", p_stylebox);3043p_button->end_bulk_theme_override();3044}30453046void Node3DEditorViewport::_notification(int p_what) {3047switch (p_what) {3048case NOTIFICATION_TRANSLATION_CHANGED: {3049_update_name();3050_update_centered_labels();3051message_time = MIN(message_time, 0.001); // Make it disappear.30523053Key key = (OS::get_singleton()->has_feature("macos") || OS::get_singleton()->has_feature("web_macos") || OS::get_singleton()->has_feature("web_ios")) ? Key::META : Key::CTRL;3054preview_material_label_desc->set_text(vformat(TTR("Drag and drop to override the material of any geometry node.\nHold %s when dropping to override a specific surface."), find_keycode_name(key)));30553056const int item_count = display_submenu->get_item_count();3057for (int i = 0; i < item_count; i++) {3058const Array item_data = display_submenu->get_item_metadata(i);3059if (item_data.is_empty()) {3060continue;3061}30623063SupportedRenderingMethods rendering_methods = item_data[0];3064String base_tooltip = item_data[1];30653066bool disabled = false;3067String disabled_tooltip;3068switch (rendering_methods) {3069case SupportedRenderingMethods::ALL:3070break;3071case SupportedRenderingMethods::FORWARD_PLUS_MOBILE:3072disabled = OS::get_singleton()->get_current_rendering_method() == "gl_compatibility";3073disabled_tooltip = TTR("This debug draw mode is not supported when using the Compatibility rendering method.");3074break;3075case SupportedRenderingMethods::FORWARD_PLUS:3076disabled = OS::get_singleton()->get_current_rendering_method() == "gl_compatibility" || OS::get_singleton()->get_current_rendering_method() == "mobile";3077disabled_tooltip = TTR("This debug draw mode is not supported when using the Mobile or Compatibility rendering methods.");3078break;3079}30803081display_submenu->set_item_disabled(i, disabled);3082String tooltip = TTR(base_tooltip);3083if (disabled) {3084if (tooltip.is_empty()) {3085tooltip = disabled_tooltip;3086} else {3087tooltip += "\n\n" + disabled_tooltip;3088}3089}3090display_submenu->set_item_tooltip(i, tooltip);3091}3092} break;30933094case NOTIFICATION_READY: {3095ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &Node3DEditorViewport::_project_settings_changed));3096} break;30973098case NOTIFICATION_VISIBILITY_CHANGED: {3099bool vp_visible = is_visible_in_tree();31003101set_process(vp_visible);3102set_physics_process(vp_visible);31033104if (vp_visible) {3105orthogonal = view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_ORTHOGONAL));3106_update_name();3107_update_camera(0);3108} else {3109set_freelook_active(false);3110}3111callable_mp(this, &Node3DEditorViewport::update_transform_gizmo_view).call_deferred();3112} break;31133114case NOTIFICATION_RESIZED: {3115callable_mp(this, &Node3DEditorViewport::update_transform_gizmo_view).call_deferred();3116} break;31173118case NOTIFICATION_PROCESS: {3119if (ruler->is_inside_tree()) {3120Vector3 start_pos = ruler_start_point->get_global_position();3121Vector3 end_pos = ruler_end_point->get_global_position();31223123geometry->clear_surfaces();3124geometry->surface_begin(Mesh::PRIMITIVE_LINES);3125geometry->surface_add_vertex(start_pos);3126geometry->surface_add_vertex(end_pos);3127geometry->surface_end();31283129float distance = start_pos.distance_to(end_pos);3130ruler_label->set_text(TS->format_number(vformat("%.3f m", distance)));31313132Vector3 center = (start_pos + end_pos) / 2;3133Vector2 screen_position = camera->unproject_position(center) - (ruler_label->get_custom_minimum_size() / 2);3134ruler_label->set_position(screen_position);3135}31363137real_t delta = get_process_delta_time();31383139if (zoom_indicator_delay > 0) {3140zoom_indicator_delay -= delta;3141if (zoom_indicator_delay <= 0) {3142surface->queue_redraw();3143zoom_limit_label->hide();3144}3145}31463147_update_navigation_controls_visibility();3148_update_freelook(delta);31493150Node *scene_root = SceneTreeDock::get_singleton()->get_editor_data()->get_edited_scene_root();3151if (previewing_cinema && scene_root != nullptr) {3152Camera3D *cam = scene_root->get_viewport()->get_camera_3d();3153if (cam != nullptr && cam != previewing) {3154//then switch the viewport's camera to the scene's viewport camera3155if (previewing != nullptr) {3156previewing->disconnect(SceneStringName(tree_exited), callable_mp(this, &Node3DEditorViewport::_preview_exited_scene));3157previewing->disconnect(CoreStringName(property_list_changed), callable_mp(this, &Node3DEditorViewport::_preview_camera_property_changed));3158}3159previewing = cam;3160previewing->connect(SceneStringName(tree_exited), callable_mp(this, &Node3DEditorViewport::_preview_exited_scene));3161previewing->connect(CoreStringName(property_list_changed), callable_mp(this, &Node3DEditorViewport::_preview_camera_property_changed));3162RS::get_singleton()->viewport_attach_camera(viewport->get_viewport_rid(), cam->get_camera());3163surface->queue_redraw();3164}3165}31663167if (_camera_moved_externally()) {3168// If camera moved after this plugin last set it, presumably a tool script has moved it, accept the new camera transform as the cursor position.3169_apply_camera_transform_to_cursor();3170_update_camera(0);3171} else {3172_update_camera(delta);3173}31743175const HashMap<Node *, Object *> &selection = editor_selection->get_selection();31763177bool changed = false;3178bool exist = false;31793180for (const KeyValue<Node *, Object *> &E : selection) {3181Node3D *sp = Object::cast_to<Node3D>(E.key);3182if (!sp) {3183continue;3184}31853186Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);3187if (!se) {3188continue;3189}31903191Transform3D t = sp->get_global_gizmo_transform();3192if (!t.is_finite()) {3193continue;3194}3195AABB new_aabb = _calculate_spatial_bounds(sp);31963197exist = true;3198if (se->last_xform == t && se->aabb == new_aabb && !se->last_xform_dirty) {3199continue;3200}3201changed = true;3202se->last_xform_dirty = false;3203se->last_xform = t;32043205se->aabb = new_aabb;32063207Transform3D t_offset = t;32083209// apply AABB scaling before item's global transform3210{3211const Vector3 offset(0.005, 0.005, 0.005);3212Basis aabb_s;3213aabb_s.scale(se->aabb.size + offset);3214t.translate_local(se->aabb.position - offset / 2);3215t.basis = t.basis * aabb_s;3216}3217{3218const Vector3 offset(0.01, 0.01, 0.01);3219Basis aabb_s;3220aabb_s.scale(se->aabb.size + offset);3221t_offset.translate_local(se->aabb.position - offset / 2);3222t_offset.basis = t_offset.basis * aabb_s;3223}32243225RenderingServer::get_singleton()->instance_set_transform(se->sbox_instance, t);3226RenderingServer::get_singleton()->instance_set_transform(se->sbox_instance_offset, t_offset);3227RenderingServer::get_singleton()->instance_set_transform(se->sbox_instance_xray, t);3228RenderingServer::get_singleton()->instance_set_transform(se->sbox_instance_xray_offset, t_offset);3229}32303231if (changed || (spatial_editor->is_gizmo_visible() && !exist)) {3232spatial_editor->update_transform_gizmo();3233}32343235if (message_time > 0) {3236if (message != last_message) {3237surface->queue_redraw();3238last_message = message;3239}32403241message_time -= get_process_delta_time();3242if (message_time < 0) {3243surface->queue_redraw();3244}3245}32463247bool show_info = view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_INFORMATION));3248if (show_info != info_panel->is_visible()) {3249info_panel->set_visible(show_info);3250}32513252Camera3D *current_camera;32533254if (previewing) {3255current_camera = previewing;3256} else {3257current_camera = camera;3258}32593260if (show_info) {3261const String viewport_size = vformat(U"%d × %d", viewport->get_size().x, viewport->get_size().y);3262String text;3263text += vformat(TTR("X: %s\n"), rtos(current_camera->get_position().x).pad_decimals(1));3264text += vformat(TTR("Y: %s\n"), rtos(current_camera->get_position().y).pad_decimals(1));3265text += vformat(TTR("Z: %s\n"), rtos(current_camera->get_position().z).pad_decimals(1));3266text += "\n";3267text += vformat(3268TTR("Size: %s (%.1fMP)\n"),3269viewport_size,3270viewport->get_size().x * viewport->get_size().y * 0.000001);32713272text += "\n";3273text += vformat(TTR("Objects: %d\n"), viewport->get_render_info(Viewport::RENDER_INFO_TYPE_VISIBLE, Viewport::RENDER_INFO_OBJECTS_IN_FRAME));3274text += vformat(TTR("Primitives: %d\n"), viewport->get_render_info(Viewport::RENDER_INFO_TYPE_VISIBLE, Viewport::RENDER_INFO_PRIMITIVES_IN_FRAME));3275text += vformat(TTR("Draw Calls: %d"), viewport->get_render_info(Viewport::RENDER_INFO_TYPE_VISIBLE, Viewport::RENDER_INFO_DRAW_CALLS_IN_FRAME));32763277info_label->set_text(text);3278}32793280// FPS Counter.3281bool show_fps = view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_FRAME_TIME));32823283if (show_fps != frame_time_panel->is_visible()) {3284frame_time_panel->set_visible(show_fps);3285RS::get_singleton()->viewport_set_measure_render_time(viewport->get_viewport_rid(), show_fps);3286for (int i = 0; i < FRAME_TIME_HISTORY; i++) {3287// Initialize to 120 FPS, so that the initial estimation until we get enough data is always reasonable.3288cpu_time_history[i] = 8.333333;3289gpu_time_history[i] = 8.333333;3290}3291cpu_time_history_index = 0;3292gpu_time_history_index = 0;3293}3294if (show_fps) {3295cpu_time_history[cpu_time_history_index] = RS::get_singleton()->viewport_get_measured_render_time_cpu(viewport->get_viewport_rid());3296cpu_time_history_index = (cpu_time_history_index + 1) % FRAME_TIME_HISTORY;3297double cpu_time = 0.0;3298for (int i = 0; i < FRAME_TIME_HISTORY; i++) {3299cpu_time += cpu_time_history[i];3300}3301cpu_time /= FRAME_TIME_HISTORY;3302// Prevent unrealistically low values.3303cpu_time = MAX(0.01, cpu_time);33043305gpu_time_history[gpu_time_history_index] = RS::get_singleton()->viewport_get_measured_render_time_gpu(viewport->get_viewport_rid());3306gpu_time_history_index = (gpu_time_history_index + 1) % FRAME_TIME_HISTORY;3307double gpu_time = 0.0;3308for (int i = 0; i < FRAME_TIME_HISTORY; i++) {3309gpu_time += gpu_time_history[i];3310}3311gpu_time /= FRAME_TIME_HISTORY;3312// Prevent division by zero for the FPS counter (and unrealistically low values).3313// This limits the reported FPS to 100000.3314gpu_time = MAX(0.01, gpu_time);33153316// Color labels depending on performance level ("good" = green, "OK" = yellow, "bad" = red).3317// Middle point is at 15 ms.3318cpu_time_label->set_text(vformat(TTR("CPU Time: %s ms"), rtos(cpu_time).pad_decimals(2)));3319cpu_time_label->add_theme_color_override(3320SceneStringName(font_color),3321frame_time_gradient->get_color_at_offset(3322Math::remap(cpu_time, 0, 30, 0, 1)));33233324gpu_time_label->set_text(vformat(TTR("GPU Time: %s ms"), rtos(gpu_time).pad_decimals(2)));3325// Middle point is at 15 ms.3326gpu_time_label->add_theme_color_override(3327SceneStringName(font_color),3328frame_time_gradient->get_color_at_offset(3329Math::remap(gpu_time, 0, 30, 0, 1)));33303331const double fps = 1000.0 / gpu_time;3332fps_label->set_text(vformat(TTR("FPS: %d"), fps));3333// Middle point is at 60 FPS.3334fps_label->add_theme_color_override(3335SceneStringName(font_color),3336frame_time_gradient->get_color_at_offset(3337Math::remap(fps, 110, 10, 0, 1)));3338}33393340if (lock_rotation) {3341float locked_half_width = locked_label->get_size().width / 2.0f;3342locked_label->set_anchor_and_offset(SIDE_LEFT, 0.5f, -locked_half_width);3343}3344} break;33453346case NOTIFICATION_PHYSICS_PROCESS: {3347if (collision_reposition) {3348Node3D *selected_node = nullptr;33493350if (ruler->is_inside_tree()) {3351if (ruler_start_point->is_visible()) {3352selected_node = ruler_end_point;3353} else {3354selected_node = ruler_start_point;3355}3356} else {3357const List<Node *> &selection = editor_selection->get_top_selected_node_list();3358if (selection.size() == 1) {3359selected_node = Object::cast_to<Node3D>(selection.front()->get());3360}3361}33623363if (selected_node) {3364if (!ruler->is_inside_tree()) {3365double snap = EDITOR_GET("interface/inspector/default_float_step");3366int snap_step_decimals = Math::range_step_decimals(snap);3367set_message(TTR("Translating:") + " (" + String::num(selected_node->get_global_position().x, snap_step_decimals) + ", " +3368String::num(selected_node->get_global_position().y, snap_step_decimals) + ", " + String::num(selected_node->get_global_position().z, snap_step_decimals) + ")");3369}33703371selected_node->set_global_position(spatial_editor->snap_point(_get_instance_position(_edit.mouse_pos, selected_node)));33723373if (ruler->is_inside_tree() && !ruler_start_point->is_visible()) {3374ruler_end_point->set_global_position(ruler_start_point->get_global_position());3375ruler_start_point->set_visible(true);3376ruler_end_point->set_visible(true);3377ruler_label->set_visible(true);3378}3379}3380}33813382if (!update_preview_node) {3383return;3384}3385if (preview_node->is_inside_tree()) {3386preview_node_pos = spatial_editor->snap_point(_get_instance_position(preview_node_viewport_pos, preview_node));3387double snap = EDITOR_GET("interface/inspector/default_float_step");3388int snap_step_decimals = Math::range_step_decimals(snap);3389set_message(TTR("Instantiating:") + " (" + String::num(preview_node_pos.x, snap_step_decimals) + ", " +3390String::num(preview_node_pos.y, snap_step_decimals) + ", " + String::num(preview_node_pos.z, snap_step_decimals) + ")");3391Transform3D preview_gl_transform = Transform3D(Basis(), preview_node_pos);3392preview_node->set_global_transform(preview_gl_transform);3393if (!preview_node->is_visible()) {3394preview_node->show();3395}3396}3397update_preview_node = false;3398} break;33993400case NOTIFICATION_APPLICATION_FOCUS_OUT:3401case NOTIFICATION_WM_WINDOW_FOCUS_OUT: {3402set_freelook_active(false);3403cursor.region_select = false;3404surface->queue_redraw();3405} break;34063407case NOTIFICATION_ENTER_TREE: {3408surface->connect(SceneStringName(draw), callable_mp(this, &Node3DEditorViewport::_draw));3409surface->connect(SceneStringName(gui_input), callable_mp(this, &Node3DEditorViewport::_sinput));3410surface->connect(SceneStringName(mouse_entered), callable_mp(this, &Node3DEditorViewport::_surface_mouse_enter));3411surface->connect(SceneStringName(mouse_exited), callable_mp(this, &Node3DEditorViewport::_surface_mouse_exit));3412surface->connect(SceneStringName(focus_entered), callable_mp(this, &Node3DEditorViewport::_surface_focus_enter));3413surface->connect(SceneStringName(focus_exited), callable_mp(this, &Node3DEditorViewport::_surface_focus_exit));34143415_init_gizmo_instance(index);3416} break;34173418case NOTIFICATION_EXIT_TREE: {3419_finish_gizmo_instances();3420} break;34213422case NOTIFICATION_THEME_CHANGED: {3423_update_centered_labels();34243425view_display_menu->set_button_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl")));3426preview_camera->set_button_icon(get_editor_theme_icon(SNAME("Camera3D")));3427Control *gui_base = EditorNode::get_singleton()->get_gui_base();34283429const Ref<StyleBox> &information_3d_stylebox = gui_base->get_theme_stylebox(SNAME("Information3dViewport"), EditorStringName(EditorStyles));34303431override_button_stylebox(view_display_menu, information_3d_stylebox);3432override_button_stylebox(translation_preview_button, information_3d_stylebox);3433override_button_stylebox(preview_camera, information_3d_stylebox);34343435frame_time_gradient->set_color(0, get_theme_color(SNAME("success_color"), EditorStringName(Editor)));3436frame_time_gradient->set_color(1, get_theme_color(SNAME("warning_color"), EditorStringName(Editor)));3437frame_time_gradient->set_color(2, get_theme_color(SNAME("error_color"), EditorStringName(Editor)));34383439info_panel->add_theme_style_override(SceneStringName(panel), information_3d_stylebox);34403441frame_time_panel->add_theme_style_override(SceneStringName(panel), information_3d_stylebox);3442// Set a minimum width to prevent the width from changing all the time3443// when numbers vary rapidly. This minimum width is set based on a3444// GPU time of 999.99 ms in the current editor language.3445const float min_width = get_theme_font(SNAME("main"), EditorStringName(EditorFonts))->get_string_size(vformat(TTR("GPU Time: %s ms"), 999.99)).x;3446frame_time_panel->set_custom_minimum_size(Size2(min_width, 0) * EDSCALE);3447frame_time_vbox->add_theme_constant_override("separation", Math::round(-1 * EDSCALE));34483449cinema_label->add_theme_style_override(CoreStringName(normal), information_3d_stylebox);3450locked_label->add_theme_style_override(CoreStringName(normal), information_3d_stylebox);34513452ruler_label->add_theme_color_override(SceneStringName(font_color), Color(1.0, 0.9, 0.0, 1.0));3453ruler_label->add_theme_color_override("font_outline_color", Color(0.0, 0.0, 0.0, 1.0));3454ruler_label->add_theme_constant_override("outline_size", 4 * EDSCALE);3455ruler_label->add_theme_font_size_override(SceneStringName(font_size), 15 * EDSCALE);3456ruler_label->add_theme_font_override(SceneStringName(font), get_theme_font(SNAME("bold"), EditorStringName(EditorFonts)));3457} break;34583459case NOTIFICATION_DRAG_END: {3460// Clear preview material when dropped outside applicable object.3461if (spatial_editor->get_preview_material().is_valid() && !is_drag_successful()) {3462_reset_preview_material();3463_remove_preview_material();3464} else {3465_remove_preview_node();3466}3467} break;3468}3469}34703471static void draw_indicator_bar(Control &p_surface, real_t p_fill, const Ref<Texture2D> p_icon, const Ref<Font> p_font, int p_font_size, const String &p_text, const Color &p_color) {3472// Adjust bar size from control height3473const Vector2 surface_size = p_surface.get_size();3474const real_t h = surface_size.y / 2.0;3475const real_t y = (surface_size.y - h) / 2.0;34763477const Rect2 r(10 * EDSCALE, y, 6 * EDSCALE, h);3478const real_t sy = r.size.y * p_fill;34793480// Note: because this bar appears over the viewport, it has to stay readable for any background color3481// Draw both neutral dark and bright colors to account this3482p_surface.draw_rect(r, p_color * Color(1, 1, 1, 0.2));3483p_surface.draw_rect(Rect2(r.position.x, r.position.y + r.size.y - sy, r.size.x, sy), p_color * Color(1, 1, 1, 0.6));3484p_surface.draw_rect(r.grow(1), Color(0, 0, 0, 0.7), false, Math::round(EDSCALE));34853486const Vector2 icon_size = p_icon->get_size();3487const Vector2 icon_pos = Vector2(r.position.x - (icon_size.x - r.size.x) / 2, r.position.y + r.size.y + 2 * EDSCALE);3488p_surface.draw_texture(p_icon, icon_pos, p_color);34893490// Draw text below the bar (for speed/zoom information).3491p_surface.draw_string_outline(p_font, Vector2(icon_pos.x, icon_pos.y + icon_size.y + 16 * EDSCALE), p_text, HORIZONTAL_ALIGNMENT_LEFT, -1.f, p_font_size, Math::round(2 * EDSCALE), Color(0, 0, 0));3492p_surface.draw_string(p_font, Vector2(icon_pos.x, icon_pos.y + icon_size.y + 16 * EDSCALE), p_text, HORIZONTAL_ALIGNMENT_LEFT, -1.f, p_font_size, p_color);3493}34943495void Node3DEditorViewport::_draw() {3496EditorPluginList *over_plugin_list = EditorNode::get_singleton()->get_editor_plugins_over();3497if (!over_plugin_list->is_empty()) {3498over_plugin_list->forward_3d_draw_over_viewport(surface);3499}35003501EditorPluginList *force_over_plugin_list = EditorNode::get_singleton()->get_editor_plugins_force_over();3502if (!force_over_plugin_list->is_empty()) {3503force_over_plugin_list->forward_3d_force_draw_over_viewport(surface);3504}35053506if (surface->has_focus() || rotation_control->has_focus()) {3507Size2 size = surface->get_size();3508Rect2 r = Rect2(Point2(), size);3509get_theme_stylebox(SNAME("FocusViewport"), EditorStringName(EditorStyles))->draw(surface->get_canvas_item(), r);3510}35113512if (cursor.region_select) {3513const Rect2 selection_rect = Rect2(cursor.region_begin, cursor.region_end - cursor.region_begin);35143515surface->draw_rect(3516selection_rect,3517get_theme_color(SNAME("box_selection_fill_color"), EditorStringName(Editor)));35183519surface->draw_rect(3520selection_rect,3521get_theme_color(SNAME("box_selection_stroke_color"), EditorStringName(Editor)),3522false,3523Math::round(EDSCALE));3524}35253526RID ci = surface->get_canvas_item();35273528if (message_time > 0) {3529Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("Label"));3530int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("Label"));3531Point2 msgpos = Point2(10 * EDSCALE, get_size().y - 14 * EDSCALE);3532font->draw_string(ci, msgpos + Point2(1, 1), message, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color(0, 0, 0, 0.8));3533font->draw_string(ci, msgpos + Point2(-1, -1), message, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color(0, 0, 0, 0.8));3534font->draw_string(ci, msgpos, message, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color(1, 1, 1, 1));3535}35363537if (_edit.mode == TRANSFORM_ROTATE && _edit.show_rotation_line) {3538Point2 center = point_to_screen(_edit.center);35393540Color handle_color;3541switch (_edit.plane) {3542case TRANSFORM_X_AXIS:3543handle_color = get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor));3544break;3545case TRANSFORM_Y_AXIS:3546handle_color = get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor));3547break;3548case TRANSFORM_Z_AXIS:3549handle_color = get_theme_color(SNAME("axis_z_color"), EditorStringName(Editor));3550break;3551default:3552handle_color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));3553break;3554}3555handle_color = handle_color.from_hsv(handle_color.get_h(), 0.25, 1.0, 1);35563557RenderingServer::get_singleton()->canvas_item_add_line(3558ci,3559_edit.mouse_pos,3560center,3561handle_color,3562Math::round(2 * EDSCALE));3563}3564if (previewing) {3565Size2 ss = Size2(GLOBAL_GET("display/window/size/viewport_width"), GLOBAL_GET("display/window/size/viewport_height"));3566float aspect = ss.aspect();3567Size2 s = get_size();35683569Rect2 draw_rect;35703571switch (previewing->get_keep_aspect_mode()) {3572case Camera3D::KEEP_WIDTH: {3573draw_rect.size = Size2(s.width, s.width / aspect);3574draw_rect.position.x = 0;3575draw_rect.position.y = (s.height - draw_rect.size.y) * 0.5;35763577} break;3578case Camera3D::KEEP_HEIGHT: {3579draw_rect.size = Size2(s.height * aspect, s.height);3580draw_rect.position.y = 0;3581draw_rect.position.x = (s.width - draw_rect.size.x) * 0.5;35823583} break;3584}35853586draw_rect = Rect2(Vector2(), s).intersection(draw_rect);35873588surface->draw_rect(draw_rect, Color(0.6, 0.6, 0.1, 0.5), false, Math::round(2 * EDSCALE));35893590} else {3591if (zoom_indicator_delay > 0.0) {3592if (is_freelook_active()) {3593// Show speed35943595real_t min_speed = MAX(camera->get_near() * 4, ZOOM_FREELOOK_MIN);3596real_t max_speed = MIN(camera->get_far() / 4, ZOOM_FREELOOK_MAX);3597real_t scale_length = (max_speed - min_speed);35983599if (!Math::is_zero_approx(scale_length)) {3600real_t logscale_t = 1.0 - Math::log1p(freelook_speed - min_speed) / Math::log1p(scale_length);36013602// Display the freelook speed to help the user get a better sense of scale.3603const int precision = freelook_speed < 1.0 ? 2 : 1;3604draw_indicator_bar(3605*surface,36061.0 - logscale_t,3607get_editor_theme_icon(SNAME("ViewportSpeed")),3608get_theme_font(SceneStringName(font), SNAME("Label")),3609get_theme_font_size(SceneStringName(font_size), SNAME("Label")),3610vformat("%s m/s", String::num(freelook_speed).pad_decimals(precision)),3611Color(1.0, 0.95, 0.7));3612}36133614} else {3615// Show zoom3616zoom_limit_label->set_visible(zoom_failed_attempts_count > 15);36173618real_t min_distance = MAX(camera->get_near() * 4, ZOOM_FREELOOK_MIN);3619real_t max_distance = MIN(camera->get_far() / 4, ZOOM_FREELOOK_MAX);3620real_t scale_length = (max_distance - min_distance);36213622if (!Math::is_zero_approx(scale_length)) {3623real_t logscale_t = 1.0 - Math::log1p(cursor.distance - min_distance) / Math::log1p(scale_length);36243625// Display the zoom center distance to help the user get a better sense of scale.3626const int precision = cursor.distance < 1.0 ? 2 : 1;3627draw_indicator_bar(3628*surface,3629logscale_t,3630get_editor_theme_icon(SNAME("ViewportZoom")),3631get_theme_font(SceneStringName(font), SNAME("Label")),3632get_theme_font_size(SceneStringName(font_size), SNAME("Label")),3633vformat("%s m", String::num(cursor.distance).pad_decimals(precision)),3634Color(0.7, 0.95, 1.0));3635}3636}3637}3638}3639}36403641bool Node3DEditorViewport::_camera_moved_externally() {3642Transform3D t = camera->get_global_transform();3643return !t.is_equal_approx(last_camera_transform);3644}36453646void Node3DEditorViewport::_apply_camera_transform_to_cursor() {3647// Effectively the reverse of to_camera_transform, use camera transform to set cursor position and rotation.3648const Transform3D camera_transform = camera->get_camera_transform();3649const Basis basis = camera_transform.basis;36503651real_t distance;3652if (orthogonal) {3653distance = (get_zfar() - get_znear()) / 2.0;3654} else {3655distance = cursor.distance;3656}36573658cursor.pos = camera_transform.origin - basis.get_column(2) * distance;36593660cursor.x_rot = -camera_transform.basis.get_euler().x;3661cursor.y_rot = -camera_transform.basis.get_euler().y;3662}36633664void Node3DEditorViewport::_menu_option(int p_option) {3665EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();3666switch (p_option) {3667case VIEW_TOP: {3668cursor.y_rot = 0;3669cursor.x_rot = Math::PI / 2.0;3670set_message(TTR("Top View."), 2);3671view_type = VIEW_TYPE_TOP;3672_set_auto_orthogonal();3673_update_name();36743675} break;3676case VIEW_BOTTOM: {3677cursor.y_rot = 0;3678cursor.x_rot = -Math::PI / 2.0;3679set_message(TTR("Bottom View."), 2);3680view_type = VIEW_TYPE_BOTTOM;3681_set_auto_orthogonal();3682_update_name();36833684} break;3685case VIEW_LEFT: {3686cursor.x_rot = 0;3687cursor.y_rot = Math::PI / 2.0;3688set_message(TTR("Left View."), 2);3689view_type = VIEW_TYPE_LEFT;3690_set_auto_orthogonal();3691_update_name();36923693} break;3694case VIEW_RIGHT: {3695cursor.x_rot = 0;3696cursor.y_rot = -Math::PI / 2.0;3697set_message(TTR("Right View."), 2);3698view_type = VIEW_TYPE_RIGHT;3699_set_auto_orthogonal();3700_update_name();37013702} break;3703case VIEW_FRONT: {3704cursor.x_rot = 0;3705cursor.y_rot = 0;3706set_message(TTR("Front View."), 2);3707view_type = VIEW_TYPE_FRONT;3708_set_auto_orthogonal();3709_update_name();37103711} break;3712case VIEW_REAR: {3713cursor.x_rot = 0;3714cursor.y_rot = Math::PI;3715set_message(TTR("Rear View."), 2);3716view_type = VIEW_TYPE_REAR;3717_set_auto_orthogonal();3718_update_name();37193720} break;3721case VIEW_CENTER_TO_ORIGIN: {3722cursor.pos = Vector3(0, 0, 0);37233724} break;3725case VIEW_CENTER_TO_SELECTION: {3726focus_selection();37273728} break;3729case VIEW_ALIGN_TRANSFORM_WITH_VIEW: {3730if (!get_selected_count()) {3731break;3732}37333734Transform3D camera_transform = camera->get_global_transform();37353736const List<Node *> &selection = editor_selection->get_top_selected_node_list();37373738undo_redo->create_action(TTR("Align Transform with View"));3739for (Node *E : selection) {3740Node3D *sp = Object::cast_to<Node3D>(E);3741if (!sp) {3742continue;3743}37443745Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);3746if (!se) {3747continue;3748}37493750Transform3D xform = camera_transform;3751if (orthogonal) {3752Vector3 offset = camera_transform.basis.xform(Vector3(0, 0, cursor.distance));3753xform.origin = cursor.pos + offset;3754} else {3755xform.scale_basis(sp->get_scale());3756}37573758if (Object::cast_to<Decal>(E)) {3759// Adjust rotation to match Decal's default orientation.3760// This makes the decal "look" in the same direction as the camera,3761// rather than pointing down relative to the camera orientation.3762xform.basis.rotate_local(Vector3(1, 0, 0), Math::TAU * 0.25);3763}37643765Node3D *parent = sp->get_parent_node_3d();3766Transform3D local_xform = parent ? parent->get_global_transform().affine_inverse() * xform : xform;3767undo_redo->add_do_method(sp, "set_transform", local_xform);3768undo_redo->add_undo_method(sp, "set_transform", sp->get_local_gizmo_transform());3769}3770undo_redo->commit_action();37713772} break;3773case VIEW_ALIGN_ROTATION_WITH_VIEW: {3774if (!get_selected_count()) {3775break;3776}37773778Transform3D camera_transform = camera->get_global_transform();37793780const List<Node *> &selection = editor_selection->get_top_selected_node_list();37813782undo_redo->create_action(TTR("Align Rotation with View"));3783for (Node *E : selection) {3784Node3D *sp = Object::cast_to<Node3D>(E);3785if (!sp) {3786continue;3787}37883789Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);3790if (!se) {3791continue;3792}37933794Basis basis = camera_transform.basis;37953796if (Object::cast_to<Decal>(E)) {3797// Adjust rotation to match Decal's default orientation.3798// This makes the decal "look" in the same direction as the camera,3799// rather than pointing down relative to the camera orientation.3800basis.rotate_local(Vector3(1, 0, 0), Math::TAU * 0.25);3801}38023803undo_redo->add_do_method(sp, "set_rotation", basis.get_euler_normalized());3804undo_redo->add_undo_method(sp, "set_rotation", sp->get_rotation());3805}3806undo_redo->commit_action();38073808} break;3809case VIEW_ENVIRONMENT: {3810int idx = view_display_menu->get_popup()->get_item_index(VIEW_ENVIRONMENT);3811bool current = view_display_menu->get_popup()->is_item_checked(idx);3812current = !current;3813if (current) {3814camera->set_environment(Ref<Resource>());3815} else {3816camera->set_environment(Node3DEditor::get_singleton()->get_viewport_environment());3817}38183819view_display_menu->get_popup()->set_item_checked(idx, current);38203821} break;3822case VIEW_PERSPECTIVE: {3823view_display_menu->get_popup()->set_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_PERSPECTIVE), true);3824view_display_menu->get_popup()->set_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_ORTHOGONAL), false);3825orthogonal = false;3826auto_orthogonal = false;3827callable_mp(this, &Node3DEditorViewport::update_transform_gizmo_view).call_deferred();3828_update_camera(0);3829_update_name();38303831} break;3832case VIEW_ORTHOGONAL: {3833view_display_menu->get_popup()->set_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_PERSPECTIVE), false);3834view_display_menu->get_popup()->set_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_ORTHOGONAL), true);3835orthogonal = true;3836auto_orthogonal = false;3837callable_mp(this, &Node3DEditorViewport::update_transform_gizmo_view).call_deferred();3838_update_camera(0);3839_update_name();3840} break;3841case VIEW_SWITCH_PERSPECTIVE_ORTHOGONAL: {3842_menu_option(orthogonal ? VIEW_PERSPECTIVE : VIEW_ORTHOGONAL);38433844} break;3845case VIEW_AUTO_ORTHOGONAL: {3846int idx = view_display_menu->get_popup()->get_item_index(VIEW_AUTO_ORTHOGONAL);3847bool current = view_display_menu->get_popup()->is_item_checked(idx);3848current = !current;3849view_display_menu->get_popup()->set_item_checked(idx, current);3850if (auto_orthogonal) {3851auto_orthogonal = false;3852_update_name();3853}3854} break;3855case VIEW_LOCK_ROTATION: {3856int idx = view_display_menu->get_popup()->get_item_index(VIEW_LOCK_ROTATION);3857bool current = view_display_menu->get_popup()->is_item_checked(idx);3858_set_lock_view_rotation(!current);38593860} break;3861case VIEW_AUDIO_LISTENER: {3862int idx = view_display_menu->get_popup()->get_item_index(VIEW_AUDIO_LISTENER);3863bool current = view_display_menu->get_popup()->is_item_checked(idx);3864current = !current;3865viewport->set_as_audio_listener_3d(current);3866view_display_menu->get_popup()->set_item_checked(idx, current);38673868} break;3869case VIEW_AUDIO_DOPPLER: {3870int idx = view_display_menu->get_popup()->get_item_index(VIEW_AUDIO_DOPPLER);3871bool current = view_display_menu->get_popup()->is_item_checked(idx);3872current = !current;3873camera->set_doppler_tracking(current ? Camera3D::DOPPLER_TRACKING_IDLE_STEP : Camera3D::DOPPLER_TRACKING_DISABLED);3874view_display_menu->get_popup()->set_item_checked(idx, current);38753876} break;3877case VIEW_CINEMATIC_PREVIEW: {3878int idx = view_display_menu->get_popup()->get_item_index(VIEW_CINEMATIC_PREVIEW);3879bool current = view_display_menu->get_popup()->is_item_checked(idx);3880current = !current;3881view_display_menu->get_popup()->set_item_checked(idx, current);3882previewing_cinema = true;3883_toggle_cinema_preview(current);38843885cinema_label->set_visible(current);3886_update_centered_labels();3887surface->queue_redraw();38883889if (current) {3890preview_camera->hide();3891} else {3892if (previewing != nullptr) {3893preview_camera->show();3894}3895}3896} break;3897case VIEW_GIZMOS: {3898int idx = view_display_menu->get_popup()->get_item_index(VIEW_GIZMOS);3899bool current = view_display_menu->get_popup()->is_item_checked(idx);3900current = !current;3901uint32_t layers = camera->get_cull_mask();3902layers &= ~(1 << GIZMO_EDIT_LAYER);3903if (current) {3904layers |= (1 << GIZMO_EDIT_LAYER);3905}3906camera->set_cull_mask(layers);3907view_display_menu->get_popup()->set_item_checked(idx, current);39083909} break;3910case VIEW_TRANSFORM_GIZMO: {3911int idx = view_display_menu->get_popup()->get_item_index(VIEW_TRANSFORM_GIZMO);3912bool current = view_display_menu->get_popup()->is_item_checked(idx);3913current = !current;3914transform_gizmo_visible = current;39153916spatial_editor->update_transform_gizmo();3917view_display_menu->get_popup()->set_item_checked(idx, current);3918} break;3919case VIEW_HALF_RESOLUTION: {3920int idx = view_display_menu->get_popup()->get_item_index(VIEW_HALF_RESOLUTION);3921bool current = view_display_menu->get_popup()->is_item_checked(idx);3922view_display_menu->get_popup()->set_item_checked(idx, !current);3923_update_shrink();3924} break;3925case VIEW_INFORMATION: {3926int idx = view_display_menu->get_popup()->get_item_index(VIEW_INFORMATION);3927bool current = view_display_menu->get_popup()->is_item_checked(idx);3928view_display_menu->get_popup()->set_item_checked(idx, !current);39293930} break;3931case VIEW_FRAME_TIME: {3932int idx = view_display_menu->get_popup()->get_item_index(VIEW_FRAME_TIME);3933bool current = view_display_menu->get_popup()->is_item_checked(idx);3934view_display_menu->get_popup()->set_item_checked(idx, !current);3935} break;3936case VIEW_GRID: {3937int idx = view_display_menu->get_popup()->get_item_index(VIEW_GRID);3938bool current = view_display_menu->get_popup()->is_item_checked(idx);3939current = !current;3940uint32_t layers = camera->get_cull_mask();3941layers &= ~(1 << GIZMO_GRID_LAYER);3942if (current) {3943layers |= (1 << GIZMO_GRID_LAYER);3944}3945camera->set_cull_mask(layers);3946view_display_menu->get_popup()->set_item_checked(idx, current);3947} break;3948case VIEW_DISPLAY_NORMAL:3949case VIEW_DISPLAY_WIREFRAME:3950case VIEW_DISPLAY_OVERDRAW:3951case VIEW_DISPLAY_UNSHADED:3952case VIEW_DISPLAY_LIGHTING:3953case VIEW_DISPLAY_NORMAL_BUFFER:3954case VIEW_DISPLAY_DEBUG_SHADOW_ATLAS:3955case VIEW_DISPLAY_DEBUG_DIRECTIONAL_SHADOW_ATLAS:3956case VIEW_DISPLAY_DEBUG_VOXEL_GI_ALBEDO:3957case VIEW_DISPLAY_DEBUG_VOXEL_GI_LIGHTING:3958case VIEW_DISPLAY_DEBUG_VOXEL_GI_EMISSION:3959case VIEW_DISPLAY_DEBUG_SCENE_LUMINANCE:3960case VIEW_DISPLAY_DEBUG_SSAO:3961case VIEW_DISPLAY_DEBUG_SSIL:3962case VIEW_DISPLAY_DEBUG_PSSM_SPLITS:3963case VIEW_DISPLAY_DEBUG_DECAL_ATLAS:3964case VIEW_DISPLAY_DEBUG_SDFGI:3965case VIEW_DISPLAY_DEBUG_SDFGI_PROBES:3966case VIEW_DISPLAY_DEBUG_GI_BUFFER:3967case VIEW_DISPLAY_DEBUG_DISABLE_LOD:3968case VIEW_DISPLAY_DEBUG_CLUSTER_OMNI_LIGHTS:3969case VIEW_DISPLAY_DEBUG_CLUSTER_SPOT_LIGHTS:3970case VIEW_DISPLAY_DEBUG_CLUSTER_DECALS:3971case VIEW_DISPLAY_DEBUG_CLUSTER_REFLECTION_PROBES:3972case VIEW_DISPLAY_DEBUG_OCCLUDERS:3973case VIEW_DISPLAY_MOTION_VECTORS:3974case VIEW_DISPLAY_INTERNAL_BUFFER: {3975static const int display_options[] = {3976VIEW_DISPLAY_NORMAL,3977VIEW_DISPLAY_WIREFRAME,3978VIEW_DISPLAY_OVERDRAW,3979VIEW_DISPLAY_UNSHADED,3980VIEW_DISPLAY_LIGHTING,3981VIEW_DISPLAY_NORMAL_BUFFER,3982VIEW_DISPLAY_DEBUG_SHADOW_ATLAS,3983VIEW_DISPLAY_DEBUG_DIRECTIONAL_SHADOW_ATLAS,3984VIEW_DISPLAY_DEBUG_VOXEL_GI_ALBEDO,3985VIEW_DISPLAY_DEBUG_VOXEL_GI_LIGHTING,3986VIEW_DISPLAY_DEBUG_VOXEL_GI_EMISSION,3987VIEW_DISPLAY_DEBUG_SCENE_LUMINANCE,3988VIEW_DISPLAY_DEBUG_SSAO,3989VIEW_DISPLAY_DEBUG_SSIL,3990VIEW_DISPLAY_DEBUG_GI_BUFFER,3991VIEW_DISPLAY_DEBUG_DISABLE_LOD,3992VIEW_DISPLAY_DEBUG_PSSM_SPLITS,3993VIEW_DISPLAY_DEBUG_DECAL_ATLAS,3994VIEW_DISPLAY_DEBUG_SDFGI,3995VIEW_DISPLAY_DEBUG_SDFGI_PROBES,3996VIEW_DISPLAY_DEBUG_CLUSTER_OMNI_LIGHTS,3997VIEW_DISPLAY_DEBUG_CLUSTER_SPOT_LIGHTS,3998VIEW_DISPLAY_DEBUG_CLUSTER_DECALS,3999VIEW_DISPLAY_DEBUG_CLUSTER_REFLECTION_PROBES,4000VIEW_DISPLAY_DEBUG_OCCLUDERS,4001VIEW_DISPLAY_MOTION_VECTORS,4002VIEW_DISPLAY_INTERNAL_BUFFER,4003VIEW_MAX4004};4005static const Viewport::DebugDraw debug_draw_modes[] = {4006Viewport::DEBUG_DRAW_DISABLED,4007Viewport::DEBUG_DRAW_WIREFRAME,4008Viewport::DEBUG_DRAW_OVERDRAW,4009Viewport::DEBUG_DRAW_UNSHADED,4010Viewport::DEBUG_DRAW_LIGHTING,4011Viewport::DEBUG_DRAW_NORMAL_BUFFER,4012Viewport::DEBUG_DRAW_SHADOW_ATLAS,4013Viewport::DEBUG_DRAW_DIRECTIONAL_SHADOW_ATLAS,4014Viewport::DEBUG_DRAW_VOXEL_GI_ALBEDO,4015Viewport::DEBUG_DRAW_VOXEL_GI_LIGHTING,4016Viewport::DEBUG_DRAW_VOXEL_GI_EMISSION,4017Viewport::DEBUG_DRAW_SCENE_LUMINANCE,4018Viewport::DEBUG_DRAW_SSAO,4019Viewport::DEBUG_DRAW_SSIL,4020Viewport::DEBUG_DRAW_GI_BUFFER,4021Viewport::DEBUG_DRAW_DISABLE_LOD,4022Viewport::DEBUG_DRAW_PSSM_SPLITS,4023Viewport::DEBUG_DRAW_DECAL_ATLAS,4024Viewport::DEBUG_DRAW_SDFGI,4025Viewport::DEBUG_DRAW_SDFGI_PROBES,4026Viewport::DEBUG_DRAW_CLUSTER_OMNI_LIGHTS,4027Viewport::DEBUG_DRAW_CLUSTER_SPOT_LIGHTS,4028Viewport::DEBUG_DRAW_CLUSTER_DECALS,4029Viewport::DEBUG_DRAW_CLUSTER_REFLECTION_PROBES,4030Viewport::DEBUG_DRAW_OCCLUDERS,4031Viewport::DEBUG_DRAW_MOTION_VECTORS,4032Viewport::DEBUG_DRAW_INTERNAL_BUFFER,4033};40344035for (int idx = 0; display_options[idx] != VIEW_MAX; idx++) {4036int id = display_options[idx];4037int item_idx = view_display_menu->get_popup()->get_item_index(id);4038if (item_idx != -1) {4039view_display_menu->get_popup()->set_item_checked(item_idx, id == p_option);4040}4041item_idx = display_submenu->get_item_index(id);4042if (item_idx != -1) {4043display_submenu->set_item_checked(item_idx, id == p_option);4044}40454046if (id == p_option) {4047viewport->set_debug_draw(debug_draw_modes[idx]);4048}4049}4050} break;4051}4052}40534054void Node3DEditorViewport::_set_auto_orthogonal() {4055if (!orthogonal && view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_AUTO_ORTHOGONAL))) {4056_menu_option(VIEW_ORTHOGONAL);4057auto_orthogonal = true;4058}4059}40604061void Node3DEditorViewport::_preview_exited_scene() {4062preview_camera->disconnect(SceneStringName(toggled), callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview));4063preview_camera->set_pressed(false);4064_toggle_camera_preview(false);4065preview_camera->connect(SceneStringName(toggled), callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview));4066view_display_menu->show();4067}40684069void Node3DEditorViewport::_preview_camera_property_changed() {4070if (previewing) {4071surface->queue_redraw();4072}4073}40744075void Node3DEditorViewport::_update_centered_labels() {4076if (cinema_label->is_visible()) {4077cinema_label->reset_size();4078float cinema_half_width = cinema_label->get_size().width / 2.0f;4079cinema_label->set_anchor_and_offset(SIDE_LEFT, 0.5f, -cinema_half_width);4080}4081}40824083void Node3DEditorViewport::_init_gizmo_instance(int p_idx) {4084uint32_t layer = 1 << (GIZMO_BASE_LAYER + p_idx);40854086for (int i = 0; i < 3; i++) {4087move_gizmo_instance[i] = RS::get_singleton()->instance_create();4088RS::get_singleton()->instance_set_base(move_gizmo_instance[i], spatial_editor->get_move_gizmo(i)->get_rid());4089RS::get_singleton()->instance_set_scenario(move_gizmo_instance[i], get_tree()->get_root()->get_world_3d()->get_scenario());4090RS::get_singleton()->instance_set_visible(move_gizmo_instance[i], false);4091RS::get_singleton()->instance_geometry_set_cast_shadows_setting(move_gizmo_instance[i], RS::SHADOW_CASTING_SETTING_OFF);4092RS::get_singleton()->instance_set_layer_mask(move_gizmo_instance[i], layer);4093RS::get_singleton()->instance_geometry_set_flag(move_gizmo_instance[i], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);4094RS::get_singleton()->instance_geometry_set_flag(move_gizmo_instance[i], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);40954096move_plane_gizmo_instance[i] = RS::get_singleton()->instance_create();4097RS::get_singleton()->instance_set_base(move_plane_gizmo_instance[i], spatial_editor->get_move_plane_gizmo(i)->get_rid());4098RS::get_singleton()->instance_set_scenario(move_plane_gizmo_instance[i], get_tree()->get_root()->get_world_3d()->get_scenario());4099RS::get_singleton()->instance_set_visible(move_plane_gizmo_instance[i], false);4100RS::get_singleton()->instance_geometry_set_cast_shadows_setting(move_plane_gizmo_instance[i], RS::SHADOW_CASTING_SETTING_OFF);4101RS::get_singleton()->instance_set_layer_mask(move_plane_gizmo_instance[i], layer);4102RS::get_singleton()->instance_geometry_set_flag(move_plane_gizmo_instance[i], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);4103RS::get_singleton()->instance_geometry_set_flag(move_plane_gizmo_instance[i], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);41044105rotate_gizmo_instance[i] = RS::get_singleton()->instance_create();4106RS::get_singleton()->instance_set_base(rotate_gizmo_instance[i], spatial_editor->get_rotate_gizmo(i)->get_rid());4107RS::get_singleton()->instance_set_scenario(rotate_gizmo_instance[i], get_tree()->get_root()->get_world_3d()->get_scenario());4108RS::get_singleton()->instance_set_visible(rotate_gizmo_instance[i], false);4109RS::get_singleton()->instance_geometry_set_cast_shadows_setting(rotate_gizmo_instance[i], RS::SHADOW_CASTING_SETTING_OFF);4110RS::get_singleton()->instance_set_layer_mask(rotate_gizmo_instance[i], layer);4111RS::get_singleton()->instance_geometry_set_flag(rotate_gizmo_instance[i], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);4112RS::get_singleton()->instance_geometry_set_flag(rotate_gizmo_instance[i], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);41134114scale_gizmo_instance[i] = RS::get_singleton()->instance_create();4115RS::get_singleton()->instance_set_base(scale_gizmo_instance[i], spatial_editor->get_scale_gizmo(i)->get_rid());4116RS::get_singleton()->instance_set_scenario(scale_gizmo_instance[i], get_tree()->get_root()->get_world_3d()->get_scenario());4117RS::get_singleton()->instance_set_visible(scale_gizmo_instance[i], false);4118RS::get_singleton()->instance_geometry_set_cast_shadows_setting(scale_gizmo_instance[i], RS::SHADOW_CASTING_SETTING_OFF);4119RS::get_singleton()->instance_set_layer_mask(scale_gizmo_instance[i], layer);4120RS::get_singleton()->instance_geometry_set_flag(scale_gizmo_instance[i], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);4121RS::get_singleton()->instance_geometry_set_flag(scale_gizmo_instance[i], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);41224123scale_plane_gizmo_instance[i] = RS::get_singleton()->instance_create();4124RS::get_singleton()->instance_set_base(scale_plane_gizmo_instance[i], spatial_editor->get_scale_plane_gizmo(i)->get_rid());4125RS::get_singleton()->instance_set_scenario(scale_plane_gizmo_instance[i], get_tree()->get_root()->get_world_3d()->get_scenario());4126RS::get_singleton()->instance_set_visible(scale_plane_gizmo_instance[i], false);4127RS::get_singleton()->instance_geometry_set_cast_shadows_setting(scale_plane_gizmo_instance[i], RS::SHADOW_CASTING_SETTING_OFF);4128RS::get_singleton()->instance_set_layer_mask(scale_plane_gizmo_instance[i], layer);4129RS::get_singleton()->instance_geometry_set_flag(scale_plane_gizmo_instance[i], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);4130RS::get_singleton()->instance_geometry_set_flag(scale_plane_gizmo_instance[i], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);41314132axis_gizmo_instance[i] = RS::get_singleton()->instance_create();4133RS::get_singleton()->instance_set_base(axis_gizmo_instance[i], spatial_editor->get_axis_gizmo(i)->get_rid());4134RS::get_singleton()->instance_set_scenario(axis_gizmo_instance[i], get_tree()->get_root()->get_world_3d()->get_scenario());4135RS::get_singleton()->instance_set_visible(axis_gizmo_instance[i], true);4136RS::get_singleton()->instance_geometry_set_cast_shadows_setting(axis_gizmo_instance[i], RS::SHADOW_CASTING_SETTING_OFF);4137RS::get_singleton()->instance_set_layer_mask(axis_gizmo_instance[i], layer);4138RS::get_singleton()->instance_geometry_set_flag(axis_gizmo_instance[i], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);4139RS::get_singleton()->instance_geometry_set_flag(axis_gizmo_instance[i], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);4140}41414142// Rotation white outline4143rotate_gizmo_instance[3] = RS::get_singleton()->instance_create();4144RS::get_singleton()->instance_set_base(rotate_gizmo_instance[3], spatial_editor->get_rotate_gizmo(3)->get_rid());4145RS::get_singleton()->instance_set_scenario(rotate_gizmo_instance[3], get_tree()->get_root()->get_world_3d()->get_scenario());4146RS::get_singleton()->instance_set_visible(rotate_gizmo_instance[3], false);4147RS::get_singleton()->instance_geometry_set_cast_shadows_setting(rotate_gizmo_instance[3], RS::SHADOW_CASTING_SETTING_OFF);4148RS::get_singleton()->instance_set_layer_mask(rotate_gizmo_instance[3], layer);4149RS::get_singleton()->instance_geometry_set_flag(rotate_gizmo_instance[3], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);4150RS::get_singleton()->instance_geometry_set_flag(rotate_gizmo_instance[3], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);4151}41524153void Node3DEditorViewport::_finish_gizmo_instances() {4154ERR_FAIL_NULL(RenderingServer::get_singleton());4155for (int i = 0; i < 3; i++) {4156RS::get_singleton()->free(move_gizmo_instance[i]);4157RS::get_singleton()->free(move_plane_gizmo_instance[i]);4158RS::get_singleton()->free(rotate_gizmo_instance[i]);4159RS::get_singleton()->free(scale_gizmo_instance[i]);4160RS::get_singleton()->free(scale_plane_gizmo_instance[i]);4161RS::get_singleton()->free(axis_gizmo_instance[i]);4162}4163// Rotation white outline4164RS::get_singleton()->free(rotate_gizmo_instance[3]);4165}41664167void Node3DEditorViewport::_toggle_camera_preview(bool p_activate) {4168ERR_FAIL_COND(p_activate && !preview);4169ERR_FAIL_COND(!p_activate && !previewing);41704171previewing_camera = p_activate;4172_update_navigation_controls_visibility();41734174if (!p_activate) {4175previewing->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Node3DEditorViewport::_preview_exited_scene));4176previewing->disconnect(CoreStringName(property_list_changed), callable_mp(this, &Node3DEditorViewport::_preview_camera_property_changed));4177previewing = nullptr;4178RS::get_singleton()->viewport_attach_camera(viewport->get_viewport_rid(), camera->get_camera()); //restore4179if (!preview) {4180preview_camera->hide();4181}4182surface->queue_redraw();41834184} else {4185previewing = preview;4186previewing->connect(SceneStringName(tree_exiting), callable_mp(this, &Node3DEditorViewport::_preview_exited_scene));4187previewing->connect(CoreStringName(property_list_changed), callable_mp(this, &Node3DEditorViewport::_preview_camera_property_changed));4188RS::get_singleton()->viewport_attach_camera(viewport->get_viewport_rid(), preview->get_camera()); //replace4189surface->queue_redraw();4190}4191}41924193void Node3DEditorViewport::_toggle_cinema_preview(bool p_activate) {4194previewing_cinema = p_activate;4195_update_navigation_controls_visibility();41964197if (!previewing_cinema) {4198if (previewing != nullptr) {4199previewing->disconnect(SceneStringName(tree_exited), callable_mp(this, &Node3DEditorViewport::_preview_exited_scene));4200previewing->disconnect(CoreStringName(property_list_changed), callable_mp(this, &Node3DEditorViewport::_preview_camera_property_changed));4201}42024203previewing = nullptr;4204RS::get_singleton()->viewport_attach_camera(viewport->get_viewport_rid(), camera->get_camera()); //restore4205preview_camera->set_pressed(false);4206if (!preview) {4207preview_camera->hide();4208} else {4209preview_camera->show();4210}4211view_display_menu->show();4212surface->queue_redraw();4213}4214}42154216void Node3DEditorViewport::_selection_result_pressed(int p_result) {4217if (selection_results_menu.size() <= p_result) {4218return;4219}42204221clicked = selection_results_menu[p_result]->get_instance_id();42224223if (clicked.is_valid()) {4224_select_clicked(true);4225}42264227selection_results_menu.clear();4228}42294230void Node3DEditorViewport::_selection_menu_hide() {4231selection_results.clear();4232selection_menu->clear();4233selection_menu->reset_size();4234}42354236void Node3DEditorViewport::set_can_preview(Camera3D *p_preview) {4237preview = p_preview;42384239if (!preview_camera->is_pressed() && !previewing_cinema) {4240preview_camera->set_visible(p_preview);4241}4242}42434244void Node3DEditorViewport::update_transform_gizmo_view() {4245if (!is_visible_in_tree()) {4246return;4247}42484249Transform3D xform = spatial_editor->get_gizmo_transform();42504251Transform3D camera_xform = camera->get_transform();42524253if (xform.origin.is_equal_approx(camera_xform.origin)) {4254for (int i = 0; i < 3; i++) {4255RenderingServer::get_singleton()->instance_set_visible(move_gizmo_instance[i], false);4256RenderingServer::get_singleton()->instance_set_visible(move_plane_gizmo_instance[i], false);4257RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[i], false);4258RenderingServer::get_singleton()->instance_set_visible(scale_gizmo_instance[i], false);4259RenderingServer::get_singleton()->instance_set_visible(scale_plane_gizmo_instance[i], false);4260RenderingServer::get_singleton()->instance_set_visible(axis_gizmo_instance[i], false);4261}4262// Rotation white outline4263RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[3], false);4264return;4265}42664267const Vector3 camz = -camera_xform.get_basis().get_column(2).normalized();4268const Vector3 camy = -camera_xform.get_basis().get_column(1).normalized();4269const Plane p = Plane(camz, camera_xform.origin);4270const real_t gizmo_d = MAX(Math::abs(p.distance_to(xform.origin)), CMP_EPSILON);4271const real_t d0 = camera->unproject_position(camera_xform.origin + camz * gizmo_d).y;4272const real_t d1 = camera->unproject_position(camera_xform.origin + camz * gizmo_d + camy).y;4273const real_t dd = MAX(Math::abs(d0 - d1), CMP_EPSILON);42744275const real_t gizmo_size = EDITOR_GET("editors/3d/manipulator_gizmo_size");4276// At low viewport heights, multiply the gizmo scale based on the viewport height.4277// This prevents the gizmo from growing very large and going outside the viewport.4278const int viewport_base_height = 400 * MAX(1, EDSCALE);4279gizmo_scale =4280(gizmo_size / Math::abs(dd)) * MAX(1, EDSCALE) *4281MIN(viewport_base_height, subviewport_container->get_size().height) / viewport_base_height /4282subviewport_container->get_stretch_shrink();4283Vector3 scale = Vector3(1, 1, 1) * gizmo_scale;42844285// if the determinant is zero, we should disable the gizmo from being rendered4286// this prevents supplying bad values to the renderer and then having to filter it out again4287if (xform.basis.determinant() == 0) {4288for (int i = 0; i < 3; i++) {4289RenderingServer::get_singleton()->instance_set_visible(move_gizmo_instance[i], false);4290RenderingServer::get_singleton()->instance_set_visible(move_plane_gizmo_instance[i], false);4291RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[i], false);4292RenderingServer::get_singleton()->instance_set_visible(scale_gizmo_instance[i], false);4293RenderingServer::get_singleton()->instance_set_visible(scale_plane_gizmo_instance[i], false);4294}4295// Rotation white outline4296RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[3], false);4297return;4298}42994300bool show_gizmo = spatial_editor->is_gizmo_visible() && !_edit.instant && transform_gizmo_visible && !collision_reposition;4301for (int i = 0; i < 3; i++) {4302Transform3D axis_angle;4303if (xform.basis.get_column(i).normalized().dot(xform.basis.get_column((i + 1) % 3).normalized()) < 1.0) {4304axis_angle = axis_angle.looking_at(xform.basis.get_column(i).normalized(), xform.basis.get_column((i + 1) % 3).normalized());4305}4306axis_angle.basis.scale(scale);4307axis_angle.origin = xform.origin;4308RenderingServer::get_singleton()->instance_set_transform(move_gizmo_instance[i], axis_angle);4309RenderingServer::get_singleton()->instance_set_visible(move_gizmo_instance[i], show_gizmo && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_MOVE));4310RenderingServer::get_singleton()->instance_set_transform(move_plane_gizmo_instance[i], axis_angle);4311RenderingServer::get_singleton()->instance_set_visible(move_plane_gizmo_instance[i], show_gizmo && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_MOVE));4312RenderingServer::get_singleton()->instance_set_transform(rotate_gizmo_instance[i], axis_angle);4313RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[i], show_gizmo && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE));4314RenderingServer::get_singleton()->instance_set_transform(scale_gizmo_instance[i], axis_angle);4315RenderingServer::get_singleton()->instance_set_visible(scale_gizmo_instance[i], show_gizmo && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SCALE));4316RenderingServer::get_singleton()->instance_set_transform(scale_plane_gizmo_instance[i], axis_angle);4317RenderingServer::get_singleton()->instance_set_visible(scale_plane_gizmo_instance[i], show_gizmo && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SCALE));4318RenderingServer::get_singleton()->instance_set_transform(axis_gizmo_instance[i], xform);4319}43204321bool show_axes = spatial_editor->is_gizmo_visible() && _edit.mode != TRANSFORM_NONE;4322RenderingServer *rs = RenderingServer::get_singleton();4323rs->instance_set_visible(axis_gizmo_instance[0], show_axes && (_edit.plane == TRANSFORM_X_AXIS || _edit.plane == TRANSFORM_XY || _edit.plane == TRANSFORM_XZ));4324rs->instance_set_visible(axis_gizmo_instance[1], show_axes && (_edit.plane == TRANSFORM_Y_AXIS || _edit.plane == TRANSFORM_XY || _edit.plane == TRANSFORM_YZ));4325rs->instance_set_visible(axis_gizmo_instance[2], show_axes && (_edit.plane == TRANSFORM_Z_AXIS || _edit.plane == TRANSFORM_XZ || _edit.plane == TRANSFORM_YZ));43264327// Rotation white outline4328xform.orthonormalize();4329xform.basis.scale(scale);4330RenderingServer::get_singleton()->instance_set_transform(rotate_gizmo_instance[3], xform);4331RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[3], spatial_editor->is_gizmo_visible() && !_edit.instant && transform_gizmo_visible && !collision_reposition && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE));4332}43334334void Node3DEditorViewport::set_state(const Dictionary &p_state) {4335if (p_state.has("position")) {4336cursor.pos = p_state["position"];4337}4338if (p_state.has("x_rotation")) {4339cursor.x_rot = p_state["x_rotation"];4340}4341if (p_state.has("y_rotation")) {4342cursor.y_rot = p_state["y_rotation"];4343}4344if (p_state.has("distance")) {4345cursor.distance = p_state["distance"];4346}4347if (p_state.has("orthogonal")) {4348bool orth = p_state["orthogonal"];4349_menu_option(orth ? VIEW_ORTHOGONAL : VIEW_PERSPECTIVE);4350}4351if (p_state.has("view_type")) {4352view_type = ViewType(p_state["view_type"].operator int());4353_update_name();4354}4355if (p_state.has("auto_orthogonal")) {4356auto_orthogonal = p_state["auto_orthogonal"];4357_update_name();4358}4359if (p_state.has("auto_orthogonal_enabled")) {4360bool enabled = p_state["auto_orthogonal_enabled"];4361view_display_menu->get_popup()->set_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_AUTO_ORTHOGONAL), enabled);4362}4363if (p_state.has("display_mode")) {4364int display = p_state["display_mode"];43654366int idx = view_display_menu->get_popup()->get_item_index(display);4367if (idx != -1 && !view_display_menu->get_popup()->is_item_checked(idx)) {4368_menu_option(display);4369} else {4370idx = display_submenu->get_item_index(display);4371if (idx != -1 && !display_submenu->is_item_checked(idx)) {4372_menu_option(display);4373}4374}4375}4376if (p_state.has("lock_rotation")) {4377_set_lock_view_rotation(p_state["lock_rotation"]);4378}4379if (p_state.has("use_environment")) {4380bool env = p_state["use_environment"];43814382if (env != camera->get_environment().is_valid()) {4383_menu_option(VIEW_ENVIRONMENT);4384}4385}4386if (p_state.has("listener")) {4387bool listener = p_state["listener"];43884389int idx = view_display_menu->get_popup()->get_item_index(VIEW_AUDIO_LISTENER);4390viewport->set_as_audio_listener_3d(listener);4391view_display_menu->get_popup()->set_item_checked(idx, listener);4392}4393if (p_state.has("doppler")) {4394bool doppler = p_state["doppler"];43954396int idx = view_display_menu->get_popup()->get_item_index(VIEW_AUDIO_DOPPLER);4397camera->set_doppler_tracking(doppler ? Camera3D::DOPPLER_TRACKING_IDLE_STEP : Camera3D::DOPPLER_TRACKING_DISABLED);4398view_display_menu->get_popup()->set_item_checked(idx, doppler);4399}4400if (p_state.has("gizmos")) {4401bool gizmos = p_state["gizmos"];44024403int idx = view_display_menu->get_popup()->get_item_index(VIEW_GIZMOS);4404if (view_display_menu->get_popup()->is_item_checked(idx) != gizmos) {4405_menu_option(VIEW_GIZMOS);4406}4407}4408if (p_state.has("transform_gizmo")) {4409bool transform_gizmo = p_state["transform_gizmo"];44104411int idx = view_display_menu->get_popup()->get_item_index(VIEW_TRANSFORM_GIZMO);4412if (view_display_menu->get_popup()->is_item_checked(idx) != transform_gizmo) {4413_menu_option(VIEW_TRANSFORM_GIZMO);4414}4415}4416if (p_state.has("grid")) {4417bool grid = p_state["grid"];44184419int idx = view_display_menu->get_popup()->get_item_index(VIEW_GRID);4420if (view_display_menu->get_popup()->is_item_checked(idx) != grid) {4421_menu_option(VIEW_GRID);4422}4423}4424if (p_state.has("information")) {4425bool information = p_state["information"];44264427int idx = view_display_menu->get_popup()->get_item_index(VIEW_INFORMATION);4428if (view_display_menu->get_popup()->is_item_checked(idx) != information) {4429_menu_option(VIEW_INFORMATION);4430}4431}4432if (p_state.has("frame_time")) {4433bool fps = p_state["frame_time"];44344435int idx = view_display_menu->get_popup()->get_item_index(VIEW_FRAME_TIME);4436if (view_display_menu->get_popup()->is_item_checked(idx) != fps) {4437_menu_option(VIEW_FRAME_TIME);4438}4439}4440if (p_state.has("half_res")) {4441bool half_res = p_state["half_res"];44424443int idx = view_display_menu->get_popup()->get_item_index(VIEW_HALF_RESOLUTION);4444view_display_menu->get_popup()->set_item_checked(idx, half_res);4445_update_shrink();4446}4447if (p_state.has("cinematic_preview")) {4448previewing_cinema = p_state["cinematic_preview"];44494450int idx = view_display_menu->get_popup()->get_item_index(VIEW_CINEMATIC_PREVIEW);4451view_display_menu->get_popup()->set_item_checked(idx, previewing_cinema);44524453cinema_label->set_visible(previewing_cinema);4454if (previewing_cinema) {4455_update_centered_labels();4456surface->queue_redraw();4457}4458}44594460if (preview_camera->is_connected(SceneStringName(toggled), callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview))) {4461preview_camera->disconnect(SceneStringName(toggled), callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview));4462}4463if (p_state.has("previewing")) {4464Node *pv = EditorNode::get_singleton()->get_edited_scene()->get_node(p_state["previewing"]);4465if (Object::cast_to<Camera3D>(pv)) {4466previewing = Object::cast_to<Camera3D>(pv);4467previewing->connect(SceneStringName(tree_exiting), callable_mp(this, &Node3DEditorViewport::_preview_exited_scene));4468previewing->connect(CoreStringName(property_list_changed), callable_mp(this, &Node3DEditorViewport::_preview_camera_property_changed));4469RS::get_singleton()->viewport_attach_camera(viewport->get_viewport_rid(), previewing->get_camera()); //replace4470surface->queue_redraw();4471previewing_camera = true;4472preview_camera->set_pressed(true);4473preview_camera->show();4474}4475}4476preview_camera->connect(SceneStringName(toggled), callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview));4477}44784479Dictionary Node3DEditorViewport::get_state() const {4480Dictionary d;4481d["position"] = cursor.pos;4482d["x_rotation"] = cursor.x_rot;4483d["y_rotation"] = cursor.y_rot;4484d["distance"] = cursor.distance;4485d["use_environment"] = camera->get_environment().is_valid();4486d["orthogonal"] = camera->get_projection() == Camera3D::PROJECTION_ORTHOGONAL;4487d["view_type"] = view_type;4488d["auto_orthogonal"] = auto_orthogonal;4489d["auto_orthogonal_enabled"] = view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_AUTO_ORTHOGONAL));44904491// Find selected display mode.4492int display_mode = VIEW_DISPLAY_NORMAL;4493for (int i = VIEW_DISPLAY_NORMAL; i < VIEW_DISPLAY_ADVANCED; i++) {4494if (view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(i))) {4495display_mode = i;4496break;4497}4498}4499for (int i = VIEW_DISPLAY_ADVANCED + 1; i < VIEW_DISPLAY_MAX; i++) {4500if (display_submenu->is_item_checked(display_submenu->get_item_index(i))) {4501display_mode = i;4502break;4503}4504}4505d["display_mode"] = display_mode;45064507d["listener"] = viewport->is_audio_listener_3d();4508d["doppler"] = view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_AUDIO_DOPPLER));4509d["gizmos"] = view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_GIZMOS));4510d["transform_gizmo"] = view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_TRANSFORM_GIZMO));4511d["grid"] = view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_GRID));4512d["information"] = view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_INFORMATION));4513d["frame_time"] = view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_FRAME_TIME));4514d["half_res"] = subviewport_container->get_stretch_shrink() > 1;4515d["cinematic_preview"] = view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_CINEMATIC_PREVIEW));4516if (previewing) {4517d["previewing"] = EditorNode::get_singleton()->get_edited_scene()->get_path_to(previewing);4518}4519d["lock_rotation"] = lock_rotation;45204521return d;4522}45234524void Node3DEditorViewport::_bind_methods() {4525ADD_SIGNAL(MethodInfo("toggle_maximize_view", PropertyInfo(Variant::OBJECT, "viewport")));4526ADD_SIGNAL(MethodInfo("clicked"));4527}45284529void Node3DEditorViewport::reset() {4530orthogonal = false;4531auto_orthogonal = false;4532lock_rotation = false;4533message_time = 0;4534message = "";4535last_message = "";4536view_type = VIEW_TYPE_USER;45374538cursor = Cursor();4539_update_name();4540}45414542void Node3DEditorViewport::focus_selection() {4543Vector3 center;4544int count = 0;45454546const List<Node *> &selection = editor_selection->get_top_selected_node_list();45474548for (Node *node : selection) {4549Node3D *node_3d = Object::cast_to<Node3D>(node);4550if (!node_3d) {4551continue;4552}45534554Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(node_3d);4555if (!se) {4556continue;4557}45584559if (se->gizmo.is_valid()) {4560for (const KeyValue<int, Transform3D> &GE : se->subgizmos) {4561const Vector3 pos = se->gizmo->get_subgizmo_transform(GE.key).origin;4562if (pos.is_finite()) {4563center += pos;4564count++;4565}4566}4567}4568const Vector3 pos = node_3d->get_global_gizmo_transform().origin;4569if (pos.is_finite()) {4570center += pos;4571count++;4572}4573}45744575if (count > 1) {4576center /= count;4577}45784579cursor.pos = center;4580}45814582void Node3DEditorViewport::assign_pending_data_pointers(Node3D *p_preview_node, AABB *p_preview_bounds, AcceptDialog *p_accept) {4583preview_node = p_preview_node;4584preview_bounds = p_preview_bounds;4585accept = p_accept;4586}45874588void _insert_rid_recursive(Node *node, HashSet<RID> &rids) {4589CollisionObject3D *co = Object::cast_to<CollisionObject3D>(node);45904591if (co) {4592rids.insert(co->get_rid());4593} else if (node->is_class("CSGShape3D")) { // HACK: We should avoid referencing module logic.4594rids.insert(node->call("_get_root_collision_instance"));4595}45964597for (int i = 0; i < node->get_child_count(); i++) {4598Node *child = node->get_child(i);4599_insert_rid_recursive(child, rids);4600}4601}46024603Vector3 Node3DEditorViewport::_get_instance_position(const Point2 &p_pos, Node3D *p_node) const {4604const float MAX_DISTANCE = 50.0;4605const float FALLBACK_DISTANCE = 5.0;46064607Vector3 world_ray = get_ray(p_pos);4608Vector3 world_pos = get_ray_pos(p_pos);46094610PhysicsDirectSpaceState3D *ss = get_tree()->get_root()->get_world_3d()->get_direct_space_state();46114612HashSet<RID> rids;46134614if (!preview_node->is_inside_tree() && !ruler->is_inside_tree()) {4615const List<Node *> &selection = editor_selection->get_top_selected_node_list();46164617Node3D *first_selected_node = Object::cast_to<Node3D>(selection.front()->get());46184619Array children = first_selected_node->get_children();46204621if (first_selected_node) {4622_insert_rid_recursive(first_selected_node, rids);4623}4624}46254626PhysicsDirectSpaceState3D::RayParameters ray_params;4627ray_params.exclude = rids;4628ray_params.from = world_pos;4629ray_params.to = world_pos + world_ray * camera->get_far();46304631PhysicsDirectSpaceState3D::RayResult result;4632if (ss->intersect_ray(ray_params, result) && (preview_node->get_child_count() > 0 || !preview_node->is_inside_tree())) {4633// Calculate an offset for the `p_node` such that the its bounding box is on top of and touching the contact surface's plane.46344635// Use the Gram-Schmidt process to get an orthonormal Basis aligned with the surface normal.4636const Vector3 bb_basis_x = result.normal;4637Vector3 bb_basis_y = Vector3(0, 1, 0);4638bb_basis_y = bb_basis_y - bb_basis_y.project(bb_basis_x);4639if (bb_basis_y.is_zero_approx()) {4640bb_basis_y = Vector3(0, 0, 1);4641bb_basis_y = bb_basis_y - bb_basis_y.project(bb_basis_x);4642}4643bb_basis_y = bb_basis_y.normalized();4644const Vector3 bb_basis_z = bb_basis_x.cross(bb_basis_y);4645const Basis bb_basis = Basis(bb_basis_x, bb_basis_y, bb_basis_z);46464647// This normal-aligned Basis allows us to create an AABB that can fit on the surface plane as snugly as possible.4648const Transform3D bb_transform = Transform3D(bb_basis, p_node->get_transform().origin);4649const AABB p_node_bb = _calculate_spatial_bounds(p_node, true, &bb_transform);4650// The x-axis's alignment with the surface normal also makes it trivial to get the distance from `p_node`'s origin at (0, 0, 0) to the correct AABB face.4651const float offset_distance = -p_node_bb.position.x;46524653// `result_offset` is in global space.4654const Vector3 result_offset = result.position + result.normal * offset_distance;46554656return result_offset;4657}46584659const bool is_orthogonal = camera->get_projection() == Camera3D::PROJECTION_ORTHOGONAL;46604661// The XZ plane.4662Vector3 intersection;4663Plane plane(Vector3(0, 1, 0));4664if (plane.intersects_ray(world_pos, world_ray, &intersection)) {4665if (is_orthogonal || world_pos.distance_to(intersection) <= MAX_DISTANCE) {4666return intersection;4667}4668}46694670// Plane facing the camera using fallback distance.4671if (is_orthogonal) {4672plane = Plane(world_ray, cursor.pos - world_ray * (cursor.distance - FALLBACK_DISTANCE));4673} else {4674plane = Plane(world_ray, world_pos + world_ray * FALLBACK_DISTANCE);4675}4676if (plane.intersects_ray(world_pos, world_ray, &intersection)) {4677return intersection;4678}46794680// Not likely, but just in case...4681return world_pos + world_ray * FALLBACK_DISTANCE;4682}46834684AABB Node3DEditorViewport::_calculate_spatial_bounds(const Node3D *p_parent, bool p_omit_top_level, const Transform3D *p_bounds_orientation) {4685if (!p_parent) {4686return AABB(Vector3(-0.2, -0.2, -0.2), Vector3(0.4, 0.4, 0.4));4687}4688const Transform3D parent_transform = p_parent->get_global_transform();4689if (!parent_transform.is_finite()) {4690return AABB();4691}4692AABB bounds;46934694Transform3D bounds_orientation;4695Transform3D xform_to_top_level_parent_space;4696if (p_bounds_orientation) {4697bounds_orientation = *p_bounds_orientation;4698xform_to_top_level_parent_space = bounds_orientation.affine_inverse() * parent_transform;4699} else {4700bounds_orientation = parent_transform;4701}47024703const VisualInstance3D *visual_instance = Object::cast_to<VisualInstance3D>(p_parent);4704if (visual_instance) {4705bounds = visual_instance->get_aabb();4706} else {4707bounds = AABB();4708}4709bounds = xform_to_top_level_parent_space.xform(bounds);47104711for (int i = 0; i < p_parent->get_child_count(); i++) {4712const Node3D *child = Object::cast_to<Node3D>(p_parent->get_child(i));4713if (child && !(p_omit_top_level && child->is_set_as_top_level())) {4714const AABB child_bounds = _calculate_spatial_bounds(child, p_omit_top_level, &bounds_orientation);4715bounds.merge_with(child_bounds);4716}4717}47184719return bounds;4720}47214722Node *Node3DEditorViewport::_sanitize_preview_node(Node *p_node) const {4723Node3D *node_3d = Object::cast_to<Node3D>(p_node);4724if (node_3d == nullptr) {4725Node3D *replacement_node = memnew(Node3D);4726replacement_node->set_name(p_node->get_name());4727p_node->replace_by(replacement_node);4728memdelete(p_node);4729p_node = replacement_node;4730} else {4731VisualInstance3D *visual_instance = Object::cast_to<VisualInstance3D>(node_3d);4732if (visual_instance == nullptr) {4733Node3D *replacement_node = memnew(Node3D);4734replacement_node->set_name(node_3d->get_name());4735replacement_node->set_visible(node_3d->is_visible());4736replacement_node->set_transform(node_3d->get_transform());4737replacement_node->set_rotation_edit_mode(node_3d->get_rotation_edit_mode());4738replacement_node->set_rotation_order(node_3d->get_rotation_order());4739replacement_node->set_as_top_level(node_3d->is_set_as_top_level());4740p_node->replace_by(replacement_node);4741memdelete(p_node);4742p_node = replacement_node;4743}4744}47454746for (int i = 0; i < p_node->get_child_count(); i++) {4747_sanitize_preview_node(p_node->get_child(i));4748}47494750return p_node;4751}47524753void Node3DEditorViewport::_create_preview_node(const Vector<String> &files) const {4754bool add_preview = false;4755for (const String &path : files) {4756Ref<Resource> res = ResourceLoader::load(path);4757ERR_CONTINUE(res.is_null());47584759Ref<PackedScene> scene = res;4760if (scene.is_valid()) {4761Node *instance = scene->instantiate();4762if (instance) {4763instance = _sanitize_preview_node(instance);4764preview_node->add_child(instance);4765Node3D *node_3d = Object::cast_to<Node3D>(instance);4766if (node_3d) {4767node_3d->set_as_top_level(false);4768}4769}4770add_preview = true;4771}47724773Ref<Mesh> mesh = res;4774if (mesh.is_valid()) {4775MeshInstance3D *mesh_instance = memnew(MeshInstance3D);4776mesh_instance->set_mesh(mesh);4777preview_node->add_child(mesh_instance);4778add_preview = true;4779}47804781Ref<AudioStream> audio = res;4782if (audio.is_valid()) {4783Sprite3D *sprite = memnew(Sprite3D);4784sprite->set_texture(get_editor_theme_icon(SNAME("Gizmo3DSamplePlayer")));4785sprite->set_billboard_mode(StandardMaterial3D::BILLBOARD_ENABLED);4786sprite->set_pixel_size(0.005);4787preview_node->add_child(sprite);4788add_preview = true;4789}4790}4791if (add_preview) {4792EditorNode::get_singleton()->get_scene_root()->add_child(preview_node);4793}47944795*preview_bounds = _calculate_spatial_bounds(preview_node);4796}47974798void Node3DEditorViewport::_remove_preview_node() {4799set_message("");4800if (preview_node->get_parent()) {4801for (int i = preview_node->get_child_count() - 1; i >= 0; i--) {4802Node *node = preview_node->get_child(i);4803node->queue_free();4804preview_node->remove_child(node);4805}4806EditorNode::get_singleton()->get_scene_root()->remove_child(preview_node);4807}4808}48094810bool Node3DEditorViewport::_apply_preview_material(ObjectID p_target, const Point2 &p_point) const {4811_reset_preview_material();48124813if (p_target.is_null()) {4814return false;4815}48164817spatial_editor->set_preview_material_target(p_target);48184819Object *target_inst = ObjectDB::get_instance(p_target);48204821bool is_ctrl = Input::get_singleton()->is_key_pressed(Key::CTRL);48224823MeshInstance3D *mesh_instance = Object::cast_to<MeshInstance3D>(target_inst);4824if (is_ctrl && mesh_instance) {4825Ref<Mesh> mesh = mesh_instance->get_mesh();4826int surface_count = mesh->get_surface_count();48274828Vector3 world_ray = get_ray(p_point);4829Vector3 world_pos = get_ray_pos(p_point);48304831int closest_surface = -1;4832float closest_dist = 1e20;48334834Transform3D gt = mesh_instance->get_global_transform();48354836Transform3D ai = gt.affine_inverse();4837Vector3 xform_ray = ai.basis.xform(world_ray).normalized();4838Vector3 xform_pos = ai.xform(world_pos);48394840for (int surface_idx = 0; surface_idx < surface_count; surface_idx++) {4841Ref<TriangleMesh> surface_mesh = mesh->generate_surface_triangle_mesh(surface_idx);48424843Vector3 rpos, rnorm;4844if (surface_mesh->intersect_ray(xform_pos, xform_ray, rpos, rnorm)) {4845Vector3 hitpos = gt.xform(rpos);48464847const real_t dist = world_pos.distance_to(hitpos);48484849if (dist < 0) {4850continue;4851}48524853if (dist < closest_dist) {4854closest_surface = surface_idx;4855closest_dist = dist;4856}4857}4858}48594860if (closest_surface == -1) {4861return false;4862}48634864spatial_editor->set_preview_material_surface(closest_surface);4865spatial_editor->set_preview_reset_material(mesh_instance->get_surface_override_material(closest_surface));4866mesh_instance->set_surface_override_material(closest_surface, spatial_editor->get_preview_material());48674868return true;4869}48704871GeometryInstance3D *geometry_instance = Object::cast_to<GeometryInstance3D>(target_inst);4872if (geometry_instance) {4873spatial_editor->set_preview_material_surface(-1);4874spatial_editor->set_preview_reset_material(geometry_instance->get_material_override());4875geometry_instance->set_material_override(spatial_editor->get_preview_material());4876return true;4877}48784879return false;4880}48814882void Node3DEditorViewport::_reset_preview_material() const {4883ObjectID last_target = spatial_editor->get_preview_material_target();4884if (last_target.is_null()) {4885return;4886}4887Object *last_target_inst = ObjectDB::get_instance(last_target);48884889MeshInstance3D *mesh_instance = Object::cast_to<MeshInstance3D>(last_target_inst);4890GeometryInstance3D *geometry_instance = Object::cast_to<GeometryInstance3D>(last_target_inst);4891if (mesh_instance && spatial_editor->get_preview_material_surface() != -1) {4892mesh_instance->set_surface_override_material(spatial_editor->get_preview_material_surface(), spatial_editor->get_preview_reset_material());4893} else if (geometry_instance) {4894geometry_instance->set_material_override(spatial_editor->get_preview_reset_material());4895}4896}48974898void Node3DEditorViewport::_remove_preview_material() {4899preview_material_label->hide();4900preview_material_label_desc->hide();49014902spatial_editor->set_preview_material(Ref<Material>());4903spatial_editor->set_preview_reset_material(Ref<Material>());4904spatial_editor->set_preview_material_target(ObjectID());4905spatial_editor->set_preview_material_surface(-1);4906}49074908bool Node3DEditorViewport::_cyclical_dependency_exists(const String &p_target_scene_path, Node *p_desired_node) const {4909if (p_desired_node->get_scene_file_path() == p_target_scene_path) {4910return true;4911}49124913int childCount = p_desired_node->get_child_count();4914for (int i = 0; i < childCount; i++) {4915Node *child = p_desired_node->get_child(i);4916if (_cyclical_dependency_exists(p_target_scene_path, child)) {4917return true;4918}4919}4920return false;4921}49224923bool Node3DEditorViewport::_create_instance(Node *p_parent, const String &p_path, const Point2 &p_point) {4924Ref<Resource> res = ResourceLoader::load(p_path);4925ERR_FAIL_COND_V(res.is_null(), false);49264927Ref<PackedScene> scene = res;4928Ref<Mesh> mesh = res;49294930Node *instantiated_scene = nullptr;49314932if (mesh.is_valid() || scene.is_valid()) {4933if (mesh.is_valid()) {4934MeshInstance3D *mesh_instance = memnew(MeshInstance3D);4935mesh_instance->set_mesh(mesh);49364937// Adjust casing according to project setting. The file name is expected to be in snake_case, but will work for others.4938const String &node_name = Node::adjust_name_casing(p_path.get_file().get_basename());4939if (!node_name.is_empty()) {4940mesh_instance->set_name(node_name);4941}49424943instantiated_scene = mesh_instance;4944} else {4945if (scene.is_null()) { // invalid scene4946return false;4947} else {4948instantiated_scene = scene->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE);4949}4950}4951}49524953if (instantiated_scene == nullptr) {4954return false;4955}49564957if (!EditorNode::get_singleton()->get_edited_scene()->get_scene_file_path().is_empty()) { // Cyclic instantiation.4958if (_cyclical_dependency_exists(EditorNode::get_singleton()->get_edited_scene()->get_scene_file_path(), instantiated_scene)) {4959memdelete(instantiated_scene);4960return false;4961}4962}49634964if (scene.is_valid()) {4965instantiated_scene->set_scene_file_path(ProjectSettings::get_singleton()->localize_path(p_path));4966}49674968EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();4969undo_redo->add_do_method(p_parent, "add_child", instantiated_scene, true);4970undo_redo->add_do_method(instantiated_scene, "set_owner", EditorNode::get_singleton()->get_edited_scene());4971undo_redo->add_do_reference(instantiated_scene);4972undo_redo->add_undo_method(p_parent, "remove_child", instantiated_scene);4973undo_redo->add_do_method(editor_selection, "add_node", instantiated_scene);49744975String new_name = p_parent->validate_child_name(instantiated_scene);4976EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton();4977undo_redo->add_do_method(ed, "live_debug_instantiate_node", EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent), p_path, new_name);4978undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent)) + "/" + new_name));49794980Node3D *node3d = Object::cast_to<Node3D>(instantiated_scene);4981if (node3d) {4982Transform3D parent_tf;4983Node3D *parent_node3d = Object::cast_to<Node3D>(p_parent);4984if (parent_node3d) {4985parent_tf = parent_node3d->get_global_gizmo_transform();4986}49874988Transform3D new_tf = node3d->get_transform();4989if (node3d->is_set_as_top_level()) {4990new_tf.origin += preview_node_pos;4991} else {4992new_tf.origin = parent_tf.affine_inverse().xform(preview_node_pos + node3d->get_position());4993new_tf.basis = parent_tf.affine_inverse().basis * new_tf.basis;4994}49954996undo_redo->add_do_method(instantiated_scene, "set_transform", new_tf);4997}49984999return true;5000}50015002bool Node3DEditorViewport::_create_audio_node(Node *p_parent, const String &p_path, const Point2 &p_point) {5003Ref<AudioStream> audio = ResourceLoader::load(p_path);5004ERR_FAIL_COND_V(audio.is_null(), false);50055006AudioStreamPlayer3D *audio_player = memnew(AudioStreamPlayer3D);5007audio_player->set_stream(audio);50085009// Adjust casing according to project setting. The file name is expected to be in snake_case, but will work for others.5010const String &node_name = Node::adjust_name_casing(p_path.get_file().get_basename());5011if (!node_name.is_empty()) {5012audio_player->set_name(node_name);5013}50145015EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();5016undo_redo->add_do_method(p_parent, "add_child", audio_player, true);5017undo_redo->add_do_method(audio_player, "set_owner", EditorNode::get_singleton()->get_edited_scene());5018undo_redo->add_do_reference(audio_player);5019undo_redo->add_undo_method(p_parent, "remove_child", audio_player);5020undo_redo->add_do_method(editor_selection, "add_node", audio_player);50215022const String new_name = p_parent->validate_child_name(audio_player);5023EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton();5024undo_redo->add_do_method(ed, "live_debug_create_node", EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent), audio_player->get_class(), new_name);5025undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent)) + "/" + new_name));50265027Transform3D parent_tf;5028Node3D *parent_node3d = Object::cast_to<Node3D>(p_parent);5029if (parent_node3d) {5030parent_tf = parent_node3d->get_global_gizmo_transform();5031}50325033Transform3D new_tf = audio_player->get_transform();5034new_tf.origin = parent_tf.affine_inverse().xform(preview_node_pos + audio_player->get_position());5035new_tf.basis = parent_tf.affine_inverse().basis * new_tf.basis;50365037undo_redo->add_do_method(audio_player, "set_transform", new_tf);50385039return true;5040}50415042void Node3DEditorViewport::_perform_drop_data() {5043EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();5044if (spatial_editor->get_preview_material_target().is_valid()) {5045GeometryInstance3D *geometry_instance = ObjectDB::get_instance<GeometryInstance3D>(spatial_editor->get_preview_material_target());5046MeshInstance3D *mesh_instance = ObjectDB::get_instance<MeshInstance3D>(spatial_editor->get_preview_material_target());5047if (mesh_instance && spatial_editor->get_preview_material_surface() != -1) {5048undo_redo->create_action(vformat(TTR("Set Surface %d Override Material"), spatial_editor->get_preview_material_surface()));5049undo_redo->add_do_method(geometry_instance, "set_surface_override_material", spatial_editor->get_preview_material_surface(), spatial_editor->get_preview_material());5050undo_redo->add_undo_method(geometry_instance, "set_surface_override_material", spatial_editor->get_preview_material_surface(), spatial_editor->get_preview_reset_material());5051undo_redo->commit_action();5052} else if (geometry_instance) {5053undo_redo->create_action(TTR("Set Material Override"));5054undo_redo->add_do_method(geometry_instance, "set_material_override", spatial_editor->get_preview_material());5055undo_redo->add_undo_method(geometry_instance, "set_material_override", spatial_editor->get_preview_reset_material());5056undo_redo->commit_action();5057}50585059_remove_preview_material();5060return;5061}50625063_remove_preview_node();50645065PackedStringArray error_files;50665067undo_redo->create_action(TTR("Create Node"), UndoRedo::MERGE_DISABLE, target_node);5068undo_redo->add_do_method(editor_selection, "clear");50695070for (int i = 0; i < selected_files.size(); i++) {5071String path = selected_files[i];5072Ref<Resource> res = ResourceLoader::load(path);5073if (res.is_null()) {5074continue;5075}50765077Ref<PackedScene> scene = res;5078Ref<Mesh> mesh = res;5079if (mesh.is_valid() || scene.is_valid()) {5080if (!_create_instance(target_node, path, drop_pos)) {5081error_files.push_back(path.get_file());5082}5083}50845085Ref<AudioStream> audio = res;5086if (audio.is_valid()) {5087if (!_create_audio_node(target_node, path, drop_pos)) {5088error_files.push_back(path.get_file());5089}5090}5091}50925093undo_redo->commit_action();50945095if (error_files.size() > 0) {5096accept->set_text(vformat(TTR("Error instantiating scene from %s."), String(", ").join(error_files)));5097accept->popup_centered();5098}5099}51005101bool Node3DEditorViewport::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {5102if (p_point == Vector2(Math::INF, Math::INF)) {5103return false;5104}5105preview_node_viewport_pos = p_point;51065107bool can_instantiate = false;5108bool is_cyclical_dep = false;5109String error_file;51105111if (!preview_node->is_inside_tree() && spatial_editor->get_preview_material().is_null()) {5112Dictionary d = p_data;5113if (d.has("type") && (String(d["type"]) == "files")) {5114Vector<String> files = d["files"];51155116// Track whether a type other than PackedScene is valid to stop checking them and only5117// continue to check if the rest of the scenes are valid (don't have cyclic dependencies).5118bool is_other_valid = false;5119// Check if at least one of the dragged files is a mesh, material, texture or scene.5120for (int i = 0; i < files.size(); i++) {5121const String &res_type = ResourceLoader::get_resource_type(files[i]);5122bool is_scene = ClassDB::is_parent_class(res_type, "PackedScene");5123bool is_mesh = ClassDB::is_parent_class(res_type, "Mesh");5124bool is_material = ClassDB::is_parent_class(res_type, "Material");5125bool is_texture = ClassDB::is_parent_class(res_type, "Texture");5126bool is_audio = ClassDB::is_parent_class(res_type, "AudioStream");51275128if (is_mesh || is_scene || is_material || is_texture || is_audio) {5129Ref<Resource> res = ResourceLoader::load(files[i]);5130if (res.is_null()) {5131continue;5132}5133Ref<PackedScene> scn = res;5134Ref<Mesh> mesh = res;5135Ref<Material> mat = res;5136Ref<Texture2D> tex = res;5137Ref<AudioStream> audio = res;5138if (scn.is_valid()) {5139Node *instantiated_scene = scn->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE);5140if (!instantiated_scene) {5141continue;5142}5143Node *edited_scene = EditorNode::get_singleton()->get_edited_scene();5144if (edited_scene && !edited_scene->get_scene_file_path().is_empty() && _cyclical_dependency_exists(edited_scene->get_scene_file_path(), instantiated_scene)) {5145memdelete(instantiated_scene);5146can_instantiate = false;5147is_cyclical_dep = true;5148error_file = files[i].get_file();5149break;5150}5151memdelete(instantiated_scene);5152} else if (!is_other_valid && mat.is_valid()) {5153Ref<BaseMaterial3D> base_mat = res;5154Ref<ShaderMaterial> shader_mat = res;51555156if (base_mat.is_null() && shader_mat.is_null()) {5157continue;5158}51595160spatial_editor->set_preview_material(mat);5161is_other_valid = true;5162continue;5163} else if (!is_other_valid && mesh.is_valid()) {5164// Let the mesh pass.5165is_other_valid = true;5166} else if (!is_other_valid && tex.is_valid()) {5167Ref<StandardMaterial3D> new_mat = memnew(StandardMaterial3D);5168new_mat->set_texture(BaseMaterial3D::TEXTURE_ALBEDO, tex);51695170spatial_editor->set_preview_material(new_mat);5171is_other_valid = true;5172continue;5173} else if (!is_other_valid && audio.is_valid()) {5174is_other_valid = true;5175} else {5176continue;5177}5178can_instantiate = true;5179}5180}5181if (can_instantiate) {5182_create_preview_node(files);5183preview_node->hide();5184}5185}5186} else {5187if (preview_node->is_inside_tree()) {5188can_instantiate = true;5189}5190}51915192if (is_cyclical_dep) {5193set_message(vformat(TTR("Can't instantiate: %s."), vformat(TTR("Circular dependency found at %s"), error_file)));5194return false;5195}51965197if (can_instantiate) {5198update_preview_node = true;5199return true;5200}52015202if (spatial_editor->get_preview_material().is_valid()) {5203preview_material_label->show();5204preview_material_label_desc->show();52055206ObjectID new_preview_material_target = _select_ray(p_point);5207return _apply_preview_material(new_preview_material_target, p_point);5208}52095210return false;5211}52125213void Node3DEditorViewport::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {5214if (!can_drop_data_fw(p_point, p_data, p_from)) {5215return;5216}52175218bool is_shift = Input::get_singleton()->is_key_pressed(Key::SHIFT);5219bool is_alt = Input::get_singleton()->is_key_pressed(Key::ALT);52205221selected_files.clear();5222Dictionary d = p_data;5223if (d.has("type") && String(d["type"]) == "files") {5224selected_files = d["files"];5225}52265227List<Node *> selected_nodes = EditorNode::get_singleton()->get_editor_selection()->get_top_selected_node_list();5228Node *root_node = EditorNode::get_singleton()->get_edited_scene();5229if (selected_nodes.size() > 0) {5230Node *selected_node = selected_nodes.front()->get();5231if (is_alt) {5232target_node = root_node;5233} else if (is_shift) {5234target_node = selected_node;5235} else { // Default behavior.5236target_node = (selected_node != root_node) ? selected_node->get_parent() : root_node;5237}5238} else {5239if (root_node) {5240target_node = root_node;5241} else {5242// Create a root node so we can add child nodes to it.5243SceneTreeDock::get_singleton()->add_root_node(memnew(Node3D));5244target_node = get_tree()->get_edited_scene_root();5245}5246}52475248drop_pos = p_point;52495250_perform_drop_data();5251}52525253void Node3DEditorViewport::begin_transform(TransformMode p_mode, bool instant) {5254if (get_selected_count() > 0) {5255_edit.mode = p_mode;5256_compute_edit(_edit.mouse_pos);5257_edit.instant = instant;5258_edit.snap = spatial_editor->is_snap_enabled();5259update_transform_gizmo_view();5260set_process_input(instant);5261}5262}52635264// Apply the current transform operation.5265void Node3DEditorViewport::commit_transform() {5266ERR_FAIL_COND(_edit.mode == TRANSFORM_NONE);5267static const char *_transform_name[4] = {5268TTRC("None"),5269TTRC("Rotate"),5270// TRANSLATORS: This refers to the movement that changes the position of an object.5271TTRC("Translate"),5272TTRC("Scale"),5273};5274EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();5275undo_redo->create_action(_transform_name[_edit.mode]);52765277const List<Node *> &selection = editor_selection->get_top_selected_node_list();52785279for (Node *E : selection) {5280Node3D *sp = Object::cast_to<Node3D>(E);5281if (!sp) {5282continue;5283}52845285Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);5286if (!se) {5287continue;5288}52895290undo_redo->add_do_method(sp, "set_transform", sp->get_local_gizmo_transform());5291undo_redo->add_undo_method(sp, "set_transform", se->original_local);5292}5293undo_redo->commit_action();52945295collision_reposition = false;5296finish_transform();5297set_message("");5298}52995300void Node3DEditorViewport::apply_transform(Vector3 p_motion, double p_snap) {5301bool local_coords = (spatial_editor->are_local_coords_enabled() && _edit.plane != TRANSFORM_VIEW);5302const List<Node *> &selection = editor_selection->get_top_selected_node_list();5303for (Node *E : selection) {5304Node3D *sp = Object::cast_to<Node3D>(E);5305if (!sp) {5306continue;5307}53085309Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);5310if (!se) {5311continue;5312}53135314if (sp->has_meta("_edit_lock_") && !spatial_editor->is_gizmo_visible()) {5315continue;5316}53175318if (se->gizmo.is_valid()) {5319for (KeyValue<int, Transform3D> &GE : se->subgizmos) {5320Transform3D xform = GE.value;5321Transform3D new_xform = _compute_transform(_edit.mode, se->original * xform, xform, p_motion, p_snap, local_coords, _edit.plane != TRANSFORM_VIEW); // Force orthogonal with subgizmo.5322if (!local_coords) {5323new_xform = se->original.affine_inverse() * new_xform;5324}5325se->gizmo->set_subgizmo_transform(GE.key, new_xform);5326}5327} else {5328Transform3D new_xform = _compute_transform(_edit.mode, se->original, se->original_local, p_motion, p_snap, local_coords, sp->get_rotation_edit_mode() != Node3D::ROTATION_EDIT_MODE_BASIS && _edit.plane != TRANSFORM_VIEW);5329_transform_gizmo_apply(se->sp, new_xform, local_coords);5330}5331}53325333spatial_editor->update_transform_gizmo();5334surface->queue_redraw();5335}53365337// Update the current transform operation in response to an input.5338void Node3DEditorViewport::update_transform(bool p_shift) {5339Vector3 ray_pos = get_ray_pos(_edit.mouse_pos);5340Vector3 ray = get_ray(_edit.mouse_pos);5341double snap = EDITOR_GET("interface/inspector/default_float_step");5342int snap_step_decimals = Math::range_step_decimals(snap);53435344switch (_edit.mode) {5345case TRANSFORM_SCALE: {5346Vector3 motion_mask;5347Plane plane;5348bool plane_mv = false;53495350switch (_edit.plane) {5351case TRANSFORM_VIEW:5352motion_mask = Vector3(0, 0, 0);5353plane = Plane(_get_camera_normal(), _edit.center);5354break;5355case TRANSFORM_X_AXIS:5356motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(0).normalized();5357plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center);5358break;5359case TRANSFORM_Y_AXIS:5360motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(1).normalized();5361plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center);5362break;5363case TRANSFORM_Z_AXIS:5364motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(2).normalized();5365plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center);5366break;5367case TRANSFORM_YZ:5368motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(2).normalized() + spatial_editor->get_gizmo_transform().basis.get_column(1).normalized();5369plane = Plane(spatial_editor->get_gizmo_transform().basis.get_column(0).normalized(), _edit.center);5370plane_mv = true;5371break;5372case TRANSFORM_XZ:5373motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(2).normalized() + spatial_editor->get_gizmo_transform().basis.get_column(0).normalized();5374plane = Plane(spatial_editor->get_gizmo_transform().basis.get_column(1).normalized(), _edit.center);5375plane_mv = true;5376break;5377case TRANSFORM_XY:5378motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(0).normalized() + spatial_editor->get_gizmo_transform().basis.get_column(1).normalized();5379plane = Plane(spatial_editor->get_gizmo_transform().basis.get_column(2).normalized(), _edit.center);5380plane_mv = true;5381break;5382}53835384Vector3 intersection;5385if (!plane.intersects_ray(ray_pos, ray, &intersection)) {5386break;5387}53885389Vector3 click;5390if (!plane.intersects_ray(_edit.click_ray_pos, _edit.click_ray, &click)) {5391break;5392}53935394Vector3 motion = intersection - click;5395if (_edit.plane != TRANSFORM_VIEW) {5396if (!plane_mv) {5397motion = motion_mask.dot(motion) * motion_mask;53985399} else {5400// Alternative planar scaling mode5401if (p_shift) {5402motion = motion_mask.dot(motion) * motion_mask;5403}5404}54055406} else {5407const real_t center_click_dist = click.distance_to(_edit.center);5408const real_t center_inters_dist = intersection.distance_to(_edit.center);5409if (center_click_dist == 0) {5410break;5411}54125413const real_t scale = center_inters_dist - center_click_dist;5414motion = Vector3(scale, scale, scale);5415}54165417motion /= click.distance_to(_edit.center);54185419// Disable local transformation for TRANSFORM_VIEW5420bool local_coords = (spatial_editor->are_local_coords_enabled() && _edit.plane != TRANSFORM_VIEW);54215422if (_edit.snap || spatial_editor->is_snap_enabled()) {5423snap = spatial_editor->get_scale_snap() / 100;5424}5425Vector3 motion_snapped = motion;5426motion_snapped.snapf(snap);5427// This might not be necessary anymore after issue #288 is solved (in 4.0?).5428// TRANSLATORS: Refers to changing the scale of a node in the 3D editor.5429set_message(TTR("Scaling:") + " (" + String::num(motion_snapped.x, snap_step_decimals) + ", " +5430String::num(motion_snapped.y, snap_step_decimals) + ", " + String::num(motion_snapped.z, snap_step_decimals) + ")");5431if (local_coords) {5432// TODO: needed?5433motion = _edit.original.basis.inverse().xform(motion);5434}54355436apply_transform(motion, snap);5437} break;54385439case TRANSFORM_TRANSLATE: {5440Vector3 motion_mask;5441Plane plane;5442bool plane_mv = false;54435444switch (_edit.plane) {5445case TRANSFORM_VIEW:5446plane = Plane(_get_camera_normal(), _edit.center);5447break;5448case TRANSFORM_X_AXIS:5449motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(0).normalized();5450plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center);5451break;5452case TRANSFORM_Y_AXIS:5453motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(1).normalized();5454plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center);5455break;5456case TRANSFORM_Z_AXIS:5457motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(2).normalized();5458plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center);5459break;5460case TRANSFORM_YZ:5461plane = Plane(spatial_editor->get_gizmo_transform().basis.get_column(0).normalized(), _edit.center);5462plane_mv = true;5463break;5464case TRANSFORM_XZ:5465plane = Plane(spatial_editor->get_gizmo_transform().basis.get_column(1).normalized(), _edit.center);5466plane_mv = true;5467break;5468case TRANSFORM_XY:5469plane = Plane(spatial_editor->get_gizmo_transform().basis.get_column(2).normalized(), _edit.center);5470plane_mv = true;5471break;5472}54735474Vector3 intersection;5475if (!plane.intersects_ray(ray_pos, ray, &intersection)) {5476break;5477}54785479Vector3 click;5480if (!plane.intersects_ray(_edit.click_ray_pos, _edit.click_ray, &click)) {5481break;5482}54835484Vector3 motion = intersection - click;5485if (_edit.plane != TRANSFORM_VIEW) {5486if (!plane_mv) {5487motion = motion_mask.dot(motion) * motion_mask;5488}5489}54905491// Disable local transformation for TRANSFORM_VIEW5492bool local_coords = (spatial_editor->are_local_coords_enabled() && _edit.plane != TRANSFORM_VIEW);54935494if (_edit.snap || spatial_editor->is_snap_enabled()) {5495snap = spatial_editor->get_translate_snap();5496}5497Vector3 motion_snapped = motion;5498motion_snapped.snapf(snap);5499// TRANSLATORS: Refers to changing the position of a node in the 3D editor.5500set_message(TTR("Translating:") + " (" + String::num(motion_snapped.x, snap_step_decimals) + ", " +5501String::num(motion_snapped.y, snap_step_decimals) + ", " + String::num(motion_snapped.z, snap_step_decimals) + ")");5502if (local_coords) {5503motion = spatial_editor->get_gizmo_transform().basis.inverse().xform(motion);5504}55055506apply_transform(motion, snap);5507} break;55085509case TRANSFORM_ROTATE: {5510Plane plane;5511if (camera->get_projection() == Camera3D::PROJECTION_PERSPECTIVE) {5512Vector3 cam_to_obj = _edit.center - _get_camera_position();5513if (!cam_to_obj.is_zero_approx()) {5514plane = Plane(cam_to_obj.normalized(), _edit.center);5515} else {5516plane = Plane(_get_camera_normal(), _edit.center);5517}5518} else {5519plane = Plane(_get_camera_normal(), _edit.center);5520}55215522Vector3 local_axis;5523Vector3 global_axis;5524switch (_edit.plane) {5525case TRANSFORM_VIEW:5526// local_axis unused5527global_axis = plane.normal;5528break;5529case TRANSFORM_X_AXIS:5530local_axis = Vector3(1, 0, 0);5531break;5532case TRANSFORM_Y_AXIS:5533local_axis = Vector3(0, 1, 0);5534break;5535case TRANSFORM_Z_AXIS:5536local_axis = Vector3(0, 0, 1);5537break;5538case TRANSFORM_YZ:5539case TRANSFORM_XZ:5540case TRANSFORM_XY:5541break;5542}55435544if (_edit.plane != TRANSFORM_VIEW) {5545global_axis = spatial_editor->get_gizmo_transform().basis.xform(local_axis).normalized();5546}55475548Vector3 intersection;5549if (!plane.intersects_ray(ray_pos, ray, &intersection)) {5550break;5551}55525553Vector3 click;5554if (!plane.intersects_ray(_edit.click_ray_pos, _edit.click_ray, &click)) {5555break;5556}55575558static const float orthogonal_threshold = Math::cos(Math::deg_to_rad(85.0f));5559bool axis_is_orthogonal = Math::abs(plane.normal.dot(global_axis)) < orthogonal_threshold;55605561double angle = 0.0f;5562if (axis_is_orthogonal) {5563_edit.show_rotation_line = false;5564Vector3 projection_axis = plane.normal.cross(global_axis);5565Vector3 delta = intersection - click;5566float projection = delta.dot(projection_axis);5567angle = (projection * (Math::PI / 2.0f)) / (gizmo_scale * GIZMO_CIRCLE_SIZE);5568} else {5569_edit.show_rotation_line = true;5570Vector3 click_axis = (click - _edit.center).normalized();5571Vector3 current_axis = (intersection - _edit.center).normalized();5572angle = click_axis.signed_angle_to(current_axis, global_axis);5573}55745575if (_edit.snap || spatial_editor->is_snap_enabled()) {5576snap = spatial_editor->get_rotate_snap();5577}5578angle = Math::snapped(Math::rad_to_deg(angle), snap);5579set_message(vformat(TTR("Rotating %s degrees."), String::num(angle, snap_step_decimals)));5580angle = Math::deg_to_rad(angle);55815582bool local_coords = (spatial_editor->are_local_coords_enabled() && _edit.plane != TRANSFORM_VIEW); // Disable local transformation for TRANSFORM_VIEW55835584Vector3 compute_axis = local_coords ? local_axis : global_axis;5585apply_transform(compute_axis, angle);5586} break;5587default: {5588}5589}5590}55915592void Node3DEditorViewport::update_transform_numeric() {5593Vector3 motion;5594switch (_edit.plane) {5595case TRANSFORM_VIEW: {5596switch (_edit.mode) {5597case TRANSFORM_TRANSLATE:5598motion = Vector3(1, 0, 0);5599break;5600case TRANSFORM_ROTATE:5601motion = spatial_editor->get_gizmo_transform().basis.xform_inv(_get_camera_normal()).normalized();5602break;5603case TRANSFORM_SCALE:5604motion = Vector3(1, 1, 1);5605break;5606case TRANSFORM_NONE:5607ERR_FAIL_MSG("_edit.mode cannot be TRANSFORM_NONE in update_transform_numeric.");5608}5609break;5610}5611case TRANSFORM_X_AXIS:5612motion = Vector3(1, 0, 0);5613break;5614case TRANSFORM_Y_AXIS:5615motion = Vector3(0, 1, 0);5616break;5617case TRANSFORM_Z_AXIS:5618motion = Vector3(0, 0, 1);5619break;5620case TRANSFORM_XY:5621motion = Vector3(1, 1, 0);5622break;5623case TRANSFORM_XZ:5624motion = Vector3(1, 0, 1);5625break;5626case TRANSFORM_YZ:5627motion = Vector3(0, 1, 1);5628break;5629}56305631double value = _edit.numeric_input * (_edit.numeric_negate ? -1 : 1);5632double extra = 0.0;5633switch (_edit.mode) {5634case TRANSFORM_TRANSLATE:5635motion *= value;5636set_message(vformat(TTR("Translating %s."), motion));5637break;5638case TRANSFORM_ROTATE:5639extra = Math::deg_to_rad(value);5640set_message(vformat(TTR("Rotating %f degrees."), value));5641break;5642case TRANSFORM_SCALE:5643// To halve the size of an object in Blender, you scale it by 0.5.5644// Doing the same in Godot is considered scaling it by -0.5.5645motion *= (value - 1.0);5646set_message(vformat(TTR("Scaling %s."), motion));5647break;5648case TRANSFORM_NONE:5649ERR_FAIL_MSG("_edit.mode cannot be TRANSFORM_NONE in update_transform_numeric.");5650}56515652apply_transform(motion, extra);5653}56545655// Perform cleanup after a transform operation is committed or canceled.5656void Node3DEditorViewport::finish_transform() {5657_edit.mode = TRANSFORM_NONE;5658_edit.instant = false;5659_edit.numeric_input = 0;5660_edit.numeric_next_decimal = 0;5661_edit.numeric_negate = false;5662spatial_editor->set_local_coords_enabled(_edit.original_local);5663spatial_editor->update_transform_gizmo();5664surface->queue_redraw();5665set_process_input(false);5666clicked = ObjectID();5667}56685669// Register a shortcut and also add it as an input action with the same events.5670void Node3DEditorViewport::register_shortcut_action(const String &p_path, const String &p_name, Key p_keycode, bool p_physical) {5671Ref<Shortcut> sc = ED_SHORTCUT(p_path, p_name, p_keycode, p_physical);5672shortcut_changed_callback(sc, p_path);5673// Connect to the change event on the shortcut so the input binding can be updated.5674sc->connect_changed(callable_mp(this, &Node3DEditorViewport::shortcut_changed_callback).bind(sc, p_path));5675}56765677// Update the action in the InputMap to the provided shortcut events.5678void Node3DEditorViewport::shortcut_changed_callback(const Ref<Shortcut> p_shortcut, const String &p_shortcut_path) {5679InputMap *im = InputMap::get_singleton();5680if (im->has_action(p_shortcut_path)) {5681im->action_erase_events(p_shortcut_path);5682} else {5683im->add_action(p_shortcut_path);5684}56855686for (int i = 0; i < p_shortcut->get_events().size(); i++) {5687im->action_add_event(p_shortcut_path, p_shortcut->get_events()[i]);5688}5689}56905691void Node3DEditorViewport::_set_lock_view_rotation(bool p_lock_rotation) {5692lock_rotation = p_lock_rotation;5693int idx = view_display_menu->get_popup()->get_item_index(VIEW_LOCK_ROTATION);5694view_display_menu->get_popup()->set_item_checked(idx, p_lock_rotation);5695if (p_lock_rotation) {5696locked_label->show();5697} else {5698locked_label->hide();5699}5700}57015702void Node3DEditorViewport::_add_advanced_debug_draw_mode_item(PopupMenu *p_popup, const String &p_name, int p_value, SupportedRenderingMethods p_rendering_methods, const String &p_tooltip) {5703display_submenu->add_radio_check_item(p_name, p_value);5704Array item_data = { p_rendering_methods, p_tooltip };5705display_submenu->set_item_metadata(-1, item_data); // Tooltip is assigned in NOTIFICATION_TRANSLATION_CHANGED.5706}57075708Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p_index) {5709cpu_time_history_index = 0;5710gpu_time_history_index = 0;57115712_edit.mode = TRANSFORM_NONE;5713_edit.plane = TRANSFORM_VIEW;5714_edit.snap = true;5715_edit.show_rotation_line = true;5716_edit.instant = false;5717_edit.gizmo_handle = -1;5718_edit.gizmo_handle_secondary = false;57195720index = p_index;5721editor_selection = EditorNode::get_singleton()->get_editor_selection();57225723orthogonal = false;5724auto_orthogonal = false;5725lock_rotation = false;5726message_time = 0;5727zoom_indicator_delay = 0.0;57285729spatial_editor = p_spatial_editor;5730SubViewportContainer *c = memnew(SubViewportContainer);5731subviewport_container = c;5732c->set_stretch(true);5733add_child(c);5734c->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);5735viewport = memnew(SubViewport);5736viewport->set_disable_input(true);57375738c->add_child(viewport);5739surface = memnew(Control);5740SET_DRAG_FORWARDING_CD(surface, Node3DEditorViewport);5741add_child(surface);5742surface->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);5743surface->set_clip_contents(true);5744camera = memnew(Camera3D);5745camera->set_disable_gizmos(true);5746camera->set_cull_mask(((1 << 20) - 1) | (1 << (GIZMO_BASE_LAYER + p_index)) | (1 << GIZMO_EDIT_LAYER) | (1 << GIZMO_GRID_LAYER) | (1 << MISC_TOOL_LAYER));5747viewport->add_child(camera);5748camera->make_current();5749surface->set_focus_mode(FOCUS_ALL);57505751VBoxContainer *vbox = memnew(VBoxContainer);5752surface->add_child(vbox);5753vbox->set_offset(SIDE_LEFT, 10 * EDSCALE);5754vbox->set_offset(SIDE_TOP, 10 * EDSCALE);57555756HBoxContainer *hbox = memnew(HBoxContainer);5757vbox->add_child(hbox);57585759view_display_menu = memnew(MenuButton);5760view_display_menu->set_flat(false);5761view_display_menu->set_h_size_flags(0);5762view_display_menu->set_shortcut_context(this);5763view_display_menu->set_accessibility_name(TTRC("View"));5764view_display_menu->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);5765view_display_menu->get_popup()->set_auto_translate_mode(AUTO_TRANSLATE_MODE_ALWAYS);5766hbox->add_child(view_display_menu);57675768view_display_menu->get_popup()->set_hide_on_checkable_item_selection(false);57695770view_display_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/top_view"), VIEW_TOP);5771view_display_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/bottom_view"), VIEW_BOTTOM);5772view_display_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/left_view"), VIEW_LEFT);5773view_display_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/right_view"), VIEW_RIGHT);5774view_display_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/front_view"), VIEW_FRONT);5775view_display_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/rear_view"), VIEW_REAR);5776view_display_menu->get_popup()->add_separator();5777view_display_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/switch_perspective_orthogonal"), VIEW_SWITCH_PERSPECTIVE_ORTHOGONAL);5778view_display_menu->get_popup()->add_radio_check_item(TTRC("Perspective"), VIEW_PERSPECTIVE);5779view_display_menu->get_popup()->add_radio_check_item(TTRC("Orthogonal"), VIEW_ORTHOGONAL);5780view_display_menu->get_popup()->set_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_PERSPECTIVE), true);5781view_display_menu->get_popup()->add_check_item(TTRC("Auto Orthogonal Enabled"), VIEW_AUTO_ORTHOGONAL);5782view_display_menu->get_popup()->set_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_AUTO_ORTHOGONAL), true);5783view_display_menu->get_popup()->add_separator();5784view_display_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_lock_rotation", TTRC("Lock View Rotation")), VIEW_LOCK_ROTATION);5785view_display_menu->get_popup()->add_separator();5786// TRANSLATORS: "Normal" as in "normal life", not "normal vector".5787view_display_menu->get_popup()->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_normal", TTRC("Display Normal")), VIEW_DISPLAY_NORMAL);5788view_display_menu->get_popup()->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_wireframe", TTRC("Display Wireframe")), VIEW_DISPLAY_WIREFRAME);5789view_display_menu->get_popup()->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_overdraw", TTRC("Display Overdraw")), VIEW_DISPLAY_OVERDRAW);5790view_display_menu->get_popup()->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_lighting", TTRC("Display Lighting")), VIEW_DISPLAY_LIGHTING);5791view_display_menu->get_popup()->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_unshaded", TTRC("Display Unshaded")), VIEW_DISPLAY_UNSHADED);5792view_display_menu->get_popup()->set_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_DISPLAY_NORMAL), true);57935794display_submenu = memnew(PopupMenu);5795display_submenu->set_hide_on_checkable_item_selection(false);5796_add_advanced_debug_draw_mode_item(display_submenu, TTRC("Directional Shadow Splits"), VIEW_DISPLAY_DEBUG_PSSM_SPLITS, SupportedRenderingMethods::FORWARD_PLUS_MOBILE,5797TTRC("Displays directional shadow splits in different colors to make adjusting split thresholds easier. \nRed: 1st split (closest to the camera), Green: 2nd split, Blue: 3rd split, Yellow: 4th split (furthest from the camera)"));5798display_submenu->add_separator();5799// TRANSLATORS: "Normal" as in "normal vector", not "normal life".5800_add_advanced_debug_draw_mode_item(display_submenu, TTRC("Normal Buffer"), VIEW_DISPLAY_NORMAL_BUFFER, SupportedRenderingMethods::FORWARD_PLUS);5801display_submenu->add_separator();5802_add_advanced_debug_draw_mode_item(display_submenu, TTRC("Shadow Atlas"), VIEW_DISPLAY_DEBUG_SHADOW_ATLAS, SupportedRenderingMethods::ALL,5803TTRC("Displays the shadow atlas used for positional (omni/spot) shadow mapping.\nRequires a visible OmniLight3D or SpotLight3D node with shadows enabled to have a visible effect."));5804_add_advanced_debug_draw_mode_item(display_submenu, TTRC("Directional Shadow Map"), VIEW_DISPLAY_DEBUG_DIRECTIONAL_SHADOW_ATLAS, SupportedRenderingMethods::ALL,5805TTRC("Displays the shadow map used for directional shadow mapping.\nRequires a visible DirectionalLight3D node with shadows enabled to have a visible effect."));5806display_submenu->add_separator();5807_add_advanced_debug_draw_mode_item(display_submenu, TTRC("Decal Atlas"), VIEW_DISPLAY_DEBUG_DECAL_ATLAS, SupportedRenderingMethods::FORWARD_PLUS_MOBILE);5808display_submenu->add_separator();5809_add_advanced_debug_draw_mode_item(display_submenu, TTRC("VoxelGI Lighting"), VIEW_DISPLAY_DEBUG_VOXEL_GI_LIGHTING, SupportedRenderingMethods::FORWARD_PLUS,5810TTRC("Requires a visible VoxelGI node that has been baked to have a visible effect."));5811_add_advanced_debug_draw_mode_item(display_submenu, TTRC("VoxelGI Albedo"), VIEW_DISPLAY_DEBUG_VOXEL_GI_ALBEDO, SupportedRenderingMethods::FORWARD_PLUS,5812TTRC("Requires a visible VoxelGI node that has been baked to have a visible effect."));5813_add_advanced_debug_draw_mode_item(display_submenu, TTRC("VoxelGI Emission"), VIEW_DISPLAY_DEBUG_VOXEL_GI_EMISSION, SupportedRenderingMethods::FORWARD_PLUS,5814TTRC("Requires a visible VoxelGI node that has been baked to have a visible effect."));5815display_submenu->add_separator();5816_add_advanced_debug_draw_mode_item(display_submenu, TTRC("SDFGI Cascades"), VIEW_DISPLAY_DEBUG_SDFGI, SupportedRenderingMethods::FORWARD_PLUS,5817TTRC("Requires SDFGI to be enabled in Environment to have a visible effect."));5818_add_advanced_debug_draw_mode_item(display_submenu, TTRC("SDFGI Probes"), VIEW_DISPLAY_DEBUG_SDFGI_PROBES, SupportedRenderingMethods::FORWARD_PLUS,5819TTRC("Requires SDFGI to be enabled in Environment to have a visible effect."));5820display_submenu->add_separator();5821_add_advanced_debug_draw_mode_item(display_submenu, TTRC("Scene Luminance"), VIEW_DISPLAY_DEBUG_SCENE_LUMINANCE, SupportedRenderingMethods::FORWARD_PLUS_MOBILE,5822TTRC("Displays the scene luminance computed from the 3D buffer. This is used for Auto Exposure calculation.\nRequires Auto Exposure to be enabled in CameraAttributes to have a visible effect."));5823display_submenu->add_separator();5824_add_advanced_debug_draw_mode_item(display_submenu, TTRC("SSAO"), VIEW_DISPLAY_DEBUG_SSAO, SupportedRenderingMethods::FORWARD_PLUS,5825TTRC("Displays the screen-space ambient occlusion buffer. Requires SSAO to be enabled in Environment to have a visible effect."));5826_add_advanced_debug_draw_mode_item(display_submenu, TTRC("SSIL"), VIEW_DISPLAY_DEBUG_SSIL, SupportedRenderingMethods::FORWARD_PLUS,5827TTRC("Displays the screen-space indirect lighting buffer. Requires SSIL to be enabled in Environment to have a visible effect."));5828display_submenu->add_separator();5829_add_advanced_debug_draw_mode_item(display_submenu, TTRC("VoxelGI/SDFGI Buffer"), VIEW_DISPLAY_DEBUG_GI_BUFFER, SupportedRenderingMethods::FORWARD_PLUS,5830TTRC("Requires SDFGI or VoxelGI to be enabled to have a visible effect."));5831display_submenu->add_separator();5832_add_advanced_debug_draw_mode_item(display_submenu, TTRC("Disable Mesh LOD"), VIEW_DISPLAY_DEBUG_DISABLE_LOD, SupportedRenderingMethods::ALL,5833TTRC("Renders all meshes with their highest level of detail regardless of their distance from the camera."));5834display_submenu->add_separator();5835_add_advanced_debug_draw_mode_item(display_submenu, TTRC("OmniLight3D Cluster"), VIEW_DISPLAY_DEBUG_CLUSTER_OMNI_LIGHTS, SupportedRenderingMethods::FORWARD_PLUS,5836TTRC("Highlights tiles of pixels that are affected by at least one OmniLight3D."));5837_add_advanced_debug_draw_mode_item(display_submenu, TTRC("SpotLight3D Cluster"), VIEW_DISPLAY_DEBUG_CLUSTER_SPOT_LIGHTS, SupportedRenderingMethods::FORWARD_PLUS,5838TTRC("Highlights tiles of pixels that are affected by at least one SpotLight3D."));5839_add_advanced_debug_draw_mode_item(display_submenu, TTRC("Decal Cluster"), VIEW_DISPLAY_DEBUG_CLUSTER_DECALS, SupportedRenderingMethods::FORWARD_PLUS,5840TTRC("Highlights tiles of pixels that are affected by at least one Decal."));5841_add_advanced_debug_draw_mode_item(display_submenu, TTRC("ReflectionProbe Cluster"), VIEW_DISPLAY_DEBUG_CLUSTER_REFLECTION_PROBES, SupportedRenderingMethods::FORWARD_PLUS,5842TTRC("Highlights tiles of pixels that are affected by at least one ReflectionProbe."));5843_add_advanced_debug_draw_mode_item(display_submenu, TTRC("Occlusion Culling Buffer"), VIEW_DISPLAY_DEBUG_OCCLUDERS, SupportedRenderingMethods::FORWARD_PLUS_MOBILE,5844TTRC("Represents occluders with black pixels. Requires occlusion culling to be enabled to have a visible effect."));5845_add_advanced_debug_draw_mode_item(display_submenu, TTRC("Motion Vectors"), VIEW_DISPLAY_MOTION_VECTORS, SupportedRenderingMethods::FORWARD_PLUS,5846TTRC("Represents motion vectors with colored lines in the direction of motion. Gray dots represent areas with no per-pixel motion."));5847_add_advanced_debug_draw_mode_item(display_submenu, TTRC("Internal Buffer"), VIEW_DISPLAY_INTERNAL_BUFFER, SupportedRenderingMethods::FORWARD_PLUS_MOBILE,5848TTRC("Shows the scene rendered in linear colorspace before any tonemapping or post-processing."));5849view_display_menu->get_popup()->add_submenu_node_item(TTRC("Display Advanced..."), display_submenu, VIEW_DISPLAY_ADVANCED);58505851view_display_menu->get_popup()->add_separator();5852view_display_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_environment", TTRC("View Environment")), VIEW_ENVIRONMENT);5853view_display_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_gizmos", TTRC("View Gizmos")), VIEW_GIZMOS);5854view_display_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_transform_gizmo", TTRC("View Transform Gizmo")), VIEW_TRANSFORM_GIZMO);5855view_display_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_grid_lines", TTRC("View Grid")), VIEW_GRID);5856view_display_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_information", TTRC("View Information")), VIEW_INFORMATION);5857view_display_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_fps", TTRC("View Frame Time")), VIEW_FRAME_TIME);5858view_display_menu->get_popup()->set_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_ENVIRONMENT), true);5859view_display_menu->get_popup()->add_separator();5860view_display_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_half_resolution", TTRC("Half Resolution")), VIEW_HALF_RESOLUTION);5861view_display_menu->get_popup()->add_separator();5862view_display_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_audio_listener", TTRC("Audio Listener")), VIEW_AUDIO_LISTENER);5863view_display_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_audio_doppler", TTRC("Enable Doppler")), VIEW_AUDIO_DOPPLER);5864view_display_menu->get_popup()->set_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_GIZMOS), true);5865view_display_menu->get_popup()->set_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_TRANSFORM_GIZMO), true);5866view_display_menu->get_popup()->set_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_GRID), true);58675868view_display_menu->get_popup()->add_separator();5869view_display_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_cinematic_preview", TTRC("Cinematic Preview")), VIEW_CINEMATIC_PREVIEW);58705871view_display_menu->get_popup()->add_separator();5872view_display_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/focus_origin"), VIEW_CENTER_TO_ORIGIN);5873view_display_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/focus_selection"), VIEW_CENTER_TO_SELECTION);5874view_display_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/align_transform_with_view"), VIEW_ALIGN_TRANSFORM_WITH_VIEW);5875view_display_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/align_rotation_with_view"), VIEW_ALIGN_ROTATION_WITH_VIEW);5876view_display_menu->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &Node3DEditorViewport::_menu_option));5877display_submenu->connect(SceneStringName(id_pressed), callable_mp(this, &Node3DEditorViewport::_menu_option));5878view_display_menu->set_disable_shortcuts(true);58795880// Registering with Key::NONE intentionally creates an empty Array.5881register_shortcut_action("spatial_editor/viewport_orbit_modifier_1", TTRC("Viewport Orbit Modifier 1"), Key::NONE);5882register_shortcut_action("spatial_editor/viewport_orbit_modifier_2", TTRC("Viewport Orbit Modifier 2"), Key::NONE);5883register_shortcut_action("spatial_editor/viewport_pan_modifier_1", TTRC("Viewport Pan Modifier 1"), Key::SHIFT);5884register_shortcut_action("spatial_editor/viewport_pan_modifier_2", TTRC("Viewport Pan Modifier 2"), Key::NONE);5885register_shortcut_action("spatial_editor/viewport_zoom_modifier_1", TTRC("Viewport Zoom Modifier 1"), Key::SHIFT);5886register_shortcut_action("spatial_editor/viewport_zoom_modifier_2", TTRC("Viewport Zoom Modifier 2"), Key::CTRL);58875888register_shortcut_action("spatial_editor/freelook_left", TTRC("Freelook Left"), Key::A, true);5889register_shortcut_action("spatial_editor/freelook_right", TTRC("Freelook Right"), Key::D, true);5890register_shortcut_action("spatial_editor/freelook_forward", TTRC("Freelook Forward"), Key::W, true);5891register_shortcut_action("spatial_editor/freelook_backwards", TTRC("Freelook Backwards"), Key::S, true);5892register_shortcut_action("spatial_editor/freelook_up", TTRC("Freelook Up"), Key::E, true);5893register_shortcut_action("spatial_editor/freelook_down", TTRC("Freelook Down"), Key::Q, true);5894register_shortcut_action("spatial_editor/freelook_speed_modifier", TTRC("Freelook Speed Modifier"), Key::SHIFT);5895register_shortcut_action("spatial_editor/freelook_slow_modifier", TTRC("Freelook Slow Modifier"), Key::ALT);58965897ED_SHORTCUT("spatial_editor/lock_transform_x", TTRC("Lock Transformation to X axis"), Key::X);5898ED_SHORTCUT("spatial_editor/lock_transform_y", TTRC("Lock Transformation to Y axis"), Key::Y);5899ED_SHORTCUT("spatial_editor/lock_transform_z", TTRC("Lock Transformation to Z axis"), Key::Z);5900ED_SHORTCUT("spatial_editor/lock_transform_yz", TTRC("Lock Transformation to YZ plane"), KeyModifierMask::SHIFT | Key::X);5901ED_SHORTCUT("spatial_editor/lock_transform_xz", TTRC("Lock Transformation to XZ plane"), KeyModifierMask::SHIFT | Key::Y);5902ED_SHORTCUT("spatial_editor/lock_transform_xy", TTRC("Lock Transformation to XY plane"), KeyModifierMask::SHIFT | Key::Z);5903ED_SHORTCUT("spatial_editor/cancel_transform", TTRC("Cancel Transformation"), Key::ESCAPE);5904ED_SHORTCUT("spatial_editor/instant_translate", TTRC("Begin Translate Transformation"));5905ED_SHORTCUT("spatial_editor/instant_rotate", TTRC("Begin Rotate Transformation"));5906ED_SHORTCUT("spatial_editor/instant_scale", TTRC("Begin Scale Transformation"));5907ED_SHORTCUT("spatial_editor/collision_reposition", TTRC("Reposition Using Collisions"), KeyModifierMask::SHIFT | Key::G);59085909translation_preview_button = memnew(EditorTranslationPreviewButton);5910hbox->add_child(translation_preview_button);59115912preview_camera = memnew(CheckBox);5913preview_camera->set_text(TTRC("Preview"));5914// Using Control even on macOS to avoid conflict with Quick Open shortcut.5915preview_camera->set_shortcut(ED_SHORTCUT("spatial_editor/toggle_camera_preview", TTRC("Toggle Camera Preview"), KeyModifierMask::CTRL | Key::P));5916vbox->add_child(preview_camera);5917preview_camera->set_h_size_flags(0);5918preview_camera->hide();5919preview_camera->connect(SceneStringName(toggled), callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview));5920previewing = nullptr;5921gizmo_scale = 1.0;59225923preview_node = nullptr;59245925bottom_center_vbox = memnew(VBoxContainer);5926bottom_center_vbox->set_anchors_preset(LayoutPreset::PRESET_CENTER);5927bottom_center_vbox->set_anchor_and_offset(SIDE_TOP, ANCHOR_END, -20 * EDSCALE);5928bottom_center_vbox->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, -10 * EDSCALE);5929bottom_center_vbox->set_h_grow_direction(GROW_DIRECTION_BOTH);5930bottom_center_vbox->set_v_grow_direction(GROW_DIRECTION_BEGIN);5931surface->add_child(bottom_center_vbox);59325933info_panel = memnew(PanelContainer);5934info_panel->set_anchor_and_offset(SIDE_LEFT, ANCHOR_END, -90 * EDSCALE);5935info_panel->set_anchor_and_offset(SIDE_TOP, ANCHOR_END, -90 * EDSCALE);5936info_panel->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_END, -10 * EDSCALE);5937info_panel->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, -10 * EDSCALE);5938info_panel->set_h_grow_direction(GROW_DIRECTION_BEGIN);5939info_panel->set_v_grow_direction(GROW_DIRECTION_BEGIN);5940info_panel->set_mouse_filter(MOUSE_FILTER_IGNORE);5941surface->add_child(info_panel);5942info_panel->hide();59435944info_label = memnew(Label);5945info_label->set_focus_mode(FOCUS_ACCESSIBILITY);5946info_panel->add_child(info_label);59475948cinema_label = memnew(Label);5949cinema_label->set_anchor_and_offset(SIDE_TOP, ANCHOR_BEGIN, 10 * EDSCALE);5950cinema_label->set_h_grow_direction(GROW_DIRECTION_END);5951cinema_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);5952cinema_label->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);5953surface->add_child(cinema_label);5954cinema_label->set_text(TTRC("Cinematic Preview"));5955cinema_label->hide();5956previewing_cinema = false;59575958locked_label = memnew(Label);5959locked_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);5960locked_label->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);5961locked_label->set_h_size_flags(SIZE_SHRINK_CENTER);5962bottom_center_vbox->add_child(locked_label);5963locked_label->set_text(TTRC("View Rotation Locked"));5964locked_label->hide();59655966zoom_limit_label = memnew(Label);5967zoom_limit_label->set_text(TTRC("To zoom further, change the camera's clipping planes (View -> Settings...)"));5968zoom_limit_label->set_name("ZoomLimitMessageLabel");5969zoom_limit_label->add_theme_color_override(SceneStringName(font_color), Color(1, 1, 1, 1));5970zoom_limit_label->hide();5971bottom_center_vbox->add_child(zoom_limit_label);59725973preview_material_label = memnew(Label);5974preview_material_label->set_anchors_and_offsets_preset(LayoutPreset::PRESET_BOTTOM_LEFT);5975preview_material_label->set_offset(Side::SIDE_TOP, -70 * EDSCALE);5976preview_material_label->set_text(TTRC("Overriding material..."));5977preview_material_label->add_theme_color_override(SceneStringName(font_color), Color(1, 1, 1, 1));5978preview_material_label->hide();5979surface->add_child(preview_material_label);59805981preview_material_label_desc = memnew(Label);5982preview_material_label_desc->set_focus_mode(FOCUS_ACCESSIBILITY);5983preview_material_label_desc->set_anchors_and_offsets_preset(LayoutPreset::PRESET_BOTTOM_LEFT);5984preview_material_label_desc->set_offset(Side::SIDE_TOP, -50 * EDSCALE);5985preview_material_label_desc->add_theme_color_override(SceneStringName(font_color), Color(0.8, 0.8, 0.8, 1));5986preview_material_label_desc->add_theme_constant_override("line_spacing", 0);5987preview_material_label_desc->hide();5988surface->add_child(preview_material_label_desc);59895990frame_time_gradient = memnew(Gradient);5991// The color is set when the theme changes.5992frame_time_gradient->add_point(0.5, Color());59935994top_right_vbox = memnew(VBoxContainer);5995top_right_vbox->add_theme_constant_override("separation", 10.0 * EDSCALE);5996top_right_vbox->set_anchors_and_offsets_preset(PRESET_TOP_RIGHT, PRESET_MODE_MINSIZE, 10.0 * EDSCALE);5997top_right_vbox->set_h_grow_direction(GROW_DIRECTION_BEGIN);59985999const int navigation_control_size = 150;60006001position_control = memnew(ViewportNavigationControl);6002position_control->set_navigation_mode(Node3DEditorViewport::NAVIGATION_MOVE);6003position_control->set_custom_minimum_size(Size2(navigation_control_size, navigation_control_size) * EDSCALE);6004position_control->set_h_size_flags(SIZE_SHRINK_END);6005position_control->set_anchor_and_offset(SIDE_LEFT, ANCHOR_BEGIN, 0);6006position_control->set_anchor_and_offset(SIDE_TOP, ANCHOR_END, -navigation_control_size * EDSCALE);6007position_control->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_BEGIN, navigation_control_size * EDSCALE);6008position_control->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, 0);6009position_control->set_viewport(this);6010surface->add_child(position_control);60116012look_control = memnew(ViewportNavigationControl);6013look_control->set_navigation_mode(Node3DEditorViewport::NAVIGATION_LOOK);6014look_control->set_custom_minimum_size(Size2(navigation_control_size, navigation_control_size) * EDSCALE);6015look_control->set_h_size_flags(SIZE_SHRINK_END);6016look_control->set_anchor_and_offset(SIDE_LEFT, ANCHOR_END, -navigation_control_size * EDSCALE);6017look_control->set_anchor_and_offset(SIDE_TOP, ANCHOR_END, -navigation_control_size * EDSCALE);6018look_control->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_END, 0);6019look_control->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, 0);6020look_control->set_viewport(this);6021surface->add_child(look_control);60226023rotation_control = memnew(ViewportRotationControl);6024rotation_control->set_custom_minimum_size(Size2(80, 80) * EDSCALE);6025rotation_control->set_h_size_flags(SIZE_SHRINK_END);6026rotation_control->set_viewport(this);6027rotation_control->set_focus_mode(FOCUS_CLICK);6028top_right_vbox->add_child(rotation_control);60296030frame_time_panel = memnew(PanelContainer);6031frame_time_panel->set_mouse_filter(MOUSE_FILTER_IGNORE);6032top_right_vbox->add_child(frame_time_panel);6033frame_time_panel->hide();60346035frame_time_vbox = memnew(VBoxContainer);6036frame_time_panel->add_child(frame_time_vbox);60376038// Individual Labels are used to allow coloring each label with its own color.6039cpu_time_label = memnew(Label);6040frame_time_vbox->add_child(cpu_time_label);60416042gpu_time_label = memnew(Label);6043frame_time_vbox->add_child(gpu_time_label);60446045fps_label = memnew(Label);6046frame_time_vbox->add_child(fps_label);60476048surface->add_child(top_right_vbox);60496050accept = nullptr;60516052freelook_active = false;6053freelook_speed = EDITOR_GET("editors/3d/freelook/freelook_base_speed");60546055selection_menu = memnew(PopupMenu);6056add_child(selection_menu);6057selection_menu->set_min_size(Size2(100, 0) * EDSCALE);6058selection_menu->connect(SceneStringName(id_pressed), callable_mp(this, &Node3DEditorViewport::_selection_result_pressed));6059selection_menu->connect("popup_hide", callable_mp(this, &Node3DEditorViewport::_selection_menu_hide));60606061if (p_index == 0) {6062view_display_menu->get_popup()->set_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_AUDIO_LISTENER), true);6063viewport->set_as_audio_listener_3d(true);6064}60656066ruler = memnew(Node);60676068ruler_start_point = memnew(Node3D);6069ruler_start_point->set_visible(false);60706071ruler_end_point = memnew(Node3D);6072ruler_end_point->set_visible(false);60736074ruler_material.instantiate();6075ruler_material->set_albedo(Color(1.0, 0.9, 0.0, 1.0));6076ruler_material->set_flag(BaseMaterial3D::FLAG_DISABLE_FOG, true);6077ruler_material->set_shading_mode(BaseMaterial3D::SHADING_MODE_UNSHADED);6078ruler_material->set_depth_draw_mode(BaseMaterial3D::DEPTH_DRAW_DISABLED);60796080ruler_material_xray.instantiate();6081ruler_material_xray->set_albedo(Color(1.0, 0.9, 0.0, 0.15));6082ruler_material_xray->set_flag(BaseMaterial3D::FLAG_DISABLE_FOG, true);6083ruler_material_xray->set_shading_mode(BaseMaterial3D::SHADING_MODE_UNSHADED);6084ruler_material_xray->set_flag(BaseMaterial3D::FLAG_DISABLE_DEPTH_TEST, true);6085ruler_material_xray->set_transparency(BaseMaterial3D::TRANSPARENCY_ALPHA);6086ruler_material_xray->set_render_priority(BaseMaterial3D::RENDER_PRIORITY_MAX);60876088geometry.instantiate();60896090ruler_line = memnew(MeshInstance3D);6091ruler_line->set_mesh(geometry);6092ruler_line->set_material_override(ruler_material);60936094ruler_line_xray = memnew(MeshInstance3D);6095ruler_line_xray->set_mesh(geometry);6096ruler_line_xray->set_material_override(ruler_material_xray);60976098ruler_label = memnew(Label);6099ruler_label->set_visible(false);61006101ruler->add_child(ruler_start_point);6102ruler->add_child(ruler_end_point);6103ruler->add_child(ruler_line);6104ruler->add_child(ruler_line_xray);61056106viewport->add_child(ruler_label);61076108view_type = VIEW_TYPE_USER;6109_update_name();61106111EditorSettings::get_singleton()->connect("settings_changed", callable_mp(this, &Node3DEditorViewport::update_transform_gizmo_view));6112}61136114Node3DEditorViewport::~Node3DEditorViewport() {6115memdelete(ruler);6116memdelete(frame_time_gradient);6117}61186119//////////////////////////////////////////////////////////////61206121void Node3DEditorViewportContainer::gui_input(const Ref<InputEvent> &p_event) {6122ERR_FAIL_COND(p_event.is_null());61236124if (_redirect_freelook_input(p_event)) {6125return;6126}61276128Ref<InputEventMouseButton> mb = p_event;61296130if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT) {6131if (mb->is_pressed()) {6132Vector2 size = get_size();61336134int h_sep = get_theme_constant(SNAME("separation"), SNAME("HSplitContainer"));6135int v_sep = get_theme_constant(SNAME("separation"), SNAME("VSplitContainer"));61366137int mid_w = size.width * ratio_h;6138int mid_h = size.height * ratio_v;61396140dragging_h = mb->get_position().x > (mid_w - h_sep / 2) && mb->get_position().x < (mid_w + h_sep / 2);6141dragging_v = mb->get_position().y > (mid_h - v_sep / 2) && mb->get_position().y < (mid_h + v_sep / 2);61426143drag_begin_pos = mb->get_position();6144drag_begin_ratio.x = ratio_h;6145drag_begin_ratio.y = ratio_v;61466147switch (view) {6148case VIEW_USE_1_VIEWPORT: {6149dragging_h = false;6150dragging_v = false;61516152} break;6153case VIEW_USE_2_VIEWPORTS: {6154dragging_h = false;61556156} break;6157case VIEW_USE_2_VIEWPORTS_ALT: {6158dragging_v = false;61596160} break;6161case VIEW_USE_3_VIEWPORTS:6162case VIEW_USE_3_VIEWPORTS_ALT:6163case VIEW_USE_4_VIEWPORTS: {6164// Do nothing.61656166} break;6167}6168} else {6169dragging_h = false;6170dragging_v = false;6171}6172}61736174Ref<InputEventMouseMotion> mm = p_event;61756176if (mm.is_valid()) {6177if (view == VIEW_USE_3_VIEWPORTS || view == VIEW_USE_3_VIEWPORTS_ALT || view == VIEW_USE_4_VIEWPORTS) {6178Vector2 size = get_size();61796180int h_sep = get_theme_constant(SNAME("separation"), SNAME("HSplitContainer"));6181int v_sep = get_theme_constant(SNAME("separation"), SNAME("VSplitContainer"));61826183int mid_w = size.width * ratio_h;6184int mid_h = size.height * ratio_v;61856186bool was_hovering_h = hovering_h;6187bool was_hovering_v = hovering_v;6188hovering_h = mm->get_position().x > (mid_w - h_sep / 2) && mm->get_position().x < (mid_w + h_sep / 2);6189hovering_v = mm->get_position().y > (mid_h - v_sep / 2) && mm->get_position().y < (mid_h + v_sep / 2);61906191if (was_hovering_h != hovering_h || was_hovering_v != hovering_v) {6192queue_redraw();6193}6194}61956196if (dragging_h) {6197real_t new_ratio = drag_begin_ratio.x + (mm->get_position().x - drag_begin_pos.x) / get_size().width;6198new_ratio = CLAMP(new_ratio, 40 / get_size().width, (get_size().width - 40) / get_size().width);6199ratio_h = new_ratio;6200queue_sort();6201queue_redraw();6202}6203if (dragging_v) {6204real_t new_ratio = drag_begin_ratio.y + (mm->get_position().y - drag_begin_pos.y) / get_size().height;6205new_ratio = CLAMP(new_ratio, 40 / get_size().height, (get_size().height - 40) / get_size().height);6206ratio_v = new_ratio;6207queue_sort();6208queue_redraw();6209}6210}6211}62126213void Node3DEditorViewportContainer::_notification(int p_what) {6214switch (p_what) {6215case NOTIFICATION_MOUSE_ENTER:6216case NOTIFICATION_MOUSE_EXIT: {6217mouseover = (p_what == NOTIFICATION_MOUSE_ENTER);6218queue_redraw();6219} break;62206221case NOTIFICATION_DRAW: {6222if (mouseover && Input::get_singleton()->get_mouse_mode() != Input::MOUSE_MODE_CAPTURED) {6223Ref<Texture2D> h_grabber = get_theme_icon(SNAME("grabber"), SNAME("HSplitContainer"));6224Ref<Texture2D> v_grabber = get_theme_icon(SNAME("grabber"), SNAME("VSplitContainer"));62256226Ref<Texture2D> hdiag_grabber = get_editor_theme_icon(SNAME("GuiViewportHdiagsplitter"));6227Ref<Texture2D> vdiag_grabber = get_editor_theme_icon(SNAME("GuiViewportVdiagsplitter"));6228Ref<Texture2D> vh_grabber = get_editor_theme_icon(SNAME("GuiViewportVhsplitter"));62296230Vector2 size = get_size();62316232int h_sep = get_theme_constant(SNAME("separation"), SNAME("HSplitContainer"));62336234int v_sep = get_theme_constant(SNAME("separation"), SNAME("VSplitContainer"));62356236int mid_w = size.width * ratio_h;6237int mid_h = size.height * ratio_v;62386239int size_left = mid_w - h_sep / 2;6240int size_bottom = size.height - mid_h - v_sep / 2;62416242switch (view) {6243case VIEW_USE_1_VIEWPORT: {6244// Nothing to show.62456246} break;6247case VIEW_USE_2_VIEWPORTS: {6248draw_texture(v_grabber, Vector2((size.width - v_grabber->get_width()) / 2, mid_h - v_grabber->get_height() / 2));6249set_default_cursor_shape(CURSOR_VSPLIT);62506251} break;6252case VIEW_USE_2_VIEWPORTS_ALT: {6253draw_texture(h_grabber, Vector2(mid_w - h_grabber->get_width() / 2, (size.height - h_grabber->get_height()) / 2));6254set_default_cursor_shape(CURSOR_HSPLIT);62556256} break;6257case VIEW_USE_3_VIEWPORTS: {6258if ((hovering_v && hovering_h && !dragging_v && !dragging_h) || (dragging_v && dragging_h)) {6259draw_texture(hdiag_grabber, Vector2(mid_w - hdiag_grabber->get_width() / 2, mid_h - v_grabber->get_height() / 4));6260set_default_cursor_shape(CURSOR_DRAG);6261} else if ((hovering_v && !dragging_h) || dragging_v) {6262draw_texture(v_grabber, Vector2((size.width - v_grabber->get_width()) / 2, mid_h - v_grabber->get_height() / 2));6263set_default_cursor_shape(CURSOR_VSPLIT);6264} else if (hovering_h || dragging_h) {6265draw_texture(h_grabber, Vector2(mid_w - h_grabber->get_width() / 2, mid_h + v_grabber->get_height() / 2 + (size_bottom - h_grabber->get_height()) / 2));6266set_default_cursor_shape(CURSOR_HSPLIT);6267}62686269} break;6270case VIEW_USE_3_VIEWPORTS_ALT: {6271if ((hovering_v && hovering_h && !dragging_v && !dragging_h) || (dragging_v && dragging_h)) {6272draw_texture(vdiag_grabber, Vector2(mid_w - vdiag_grabber->get_width() + v_grabber->get_height() / 4, mid_h - vdiag_grabber->get_height() / 2));6273set_default_cursor_shape(CURSOR_DRAG);6274} else if ((hovering_v && !dragging_h) || dragging_v) {6275draw_texture(v_grabber, Vector2((size_left - v_grabber->get_width()) / 2, mid_h - v_grabber->get_height() / 2));6276set_default_cursor_shape(CURSOR_VSPLIT);6277} else if (hovering_h || dragging_h) {6278draw_texture(h_grabber, Vector2(mid_w - h_grabber->get_width() / 2, (size.height - h_grabber->get_height()) / 2));6279set_default_cursor_shape(CURSOR_HSPLIT);6280}62816282} break;6283case VIEW_USE_4_VIEWPORTS: {6284Vector2 half(mid_w, mid_h);6285if ((hovering_v && hovering_h && !dragging_v && !dragging_h) || (dragging_v && dragging_h)) {6286draw_texture(vh_grabber, half - vh_grabber->get_size() / 2.0);6287set_default_cursor_shape(CURSOR_DRAG);6288} else if ((hovering_v && !dragging_h) || dragging_v) {6289draw_texture(v_grabber, half - v_grabber->get_size() / 2.0);6290set_default_cursor_shape(CURSOR_VSPLIT);6291} else if (hovering_h || dragging_h) {6292draw_texture(h_grabber, half - h_grabber->get_size() / 2.0);6293set_default_cursor_shape(CURSOR_HSPLIT);6294}62956296} break;6297}6298}6299} break;63006301case NOTIFICATION_SORT_CHILDREN: {6302Node3DEditorViewport *viewports[4];6303int vc = 0;6304for (int i = 0; i < get_child_count(); i++) {6305viewports[vc] = Object::cast_to<Node3DEditorViewport>(get_child(i));6306if (viewports[vc]) {6307vc++;6308}6309}63106311ERR_FAIL_COND(vc != 4);63126313Size2 size = get_size();63146315if (size.x < 10 || size.y < 10) {6316for (int i = 0; i < 4; i++) {6317viewports[i]->hide();6318}6319return;6320}6321int h_sep = get_theme_constant(SNAME("separation"), SNAME("HSplitContainer"));63226323int v_sep = get_theme_constant(SNAME("separation"), SNAME("VSplitContainer"));63246325int mid_w = size.width * ratio_h;6326int mid_h = size.height * ratio_v;63276328int size_left = mid_w - h_sep / 2;6329int size_right = size.width - mid_w - h_sep / 2;63306331int size_top = mid_h - v_sep / 2;6332int size_bottom = size.height - mid_h - v_sep / 2;63336334switch (view) {6335case VIEW_USE_1_VIEWPORT: {6336viewports[0]->show();6337for (int i = 1; i < 4; i++) {6338viewports[i]->hide();6339}63406341fit_child_in_rect(viewports[0], Rect2(Vector2(), size));63426343} break;6344case VIEW_USE_2_VIEWPORTS: {6345for (int i = 0; i < 4; i++) {6346if (i == 1 || i == 3) {6347viewports[i]->hide();6348} else {6349viewports[i]->show();6350}6351}63526353fit_child_in_rect(viewports[0], Rect2(Vector2(), Vector2(size.width, size_top)));6354fit_child_in_rect(viewports[2], Rect2(Vector2(0, mid_h + v_sep / 2), Vector2(size.width, size_bottom)));63556356} break;6357case VIEW_USE_2_VIEWPORTS_ALT: {6358for (int i = 0; i < 4; i++) {6359if (i == 1 || i == 3) {6360viewports[i]->hide();6361} else {6362viewports[i]->show();6363}6364}6365fit_child_in_rect(viewports[0], Rect2(Vector2(), Vector2(size_left, size.height)));6366fit_child_in_rect(viewports[2], Rect2(Vector2(mid_w + h_sep / 2, 0), Vector2(size_right, size.height)));63676368} break;6369case VIEW_USE_3_VIEWPORTS: {6370for (int i = 0; i < 4; i++) {6371if (i == 1) {6372viewports[i]->hide();6373} else {6374viewports[i]->show();6375}6376}63776378fit_child_in_rect(viewports[0], Rect2(Vector2(), Vector2(size.width, size_top)));6379fit_child_in_rect(viewports[2], Rect2(Vector2(0, mid_h + v_sep / 2), Vector2(size_left, size_bottom)));6380fit_child_in_rect(viewports[3], Rect2(Vector2(mid_w + h_sep / 2, mid_h + v_sep / 2), Vector2(size_right, size_bottom)));63816382} break;6383case VIEW_USE_3_VIEWPORTS_ALT: {6384for (int i = 0; i < 4; i++) {6385if (i == 1) {6386viewports[i]->hide();6387} else {6388viewports[i]->show();6389}6390}63916392fit_child_in_rect(viewports[0], Rect2(Vector2(), Vector2(size_left, size_top)));6393fit_child_in_rect(viewports[2], Rect2(Vector2(0, mid_h + v_sep / 2), Vector2(size_left, size_bottom)));6394fit_child_in_rect(viewports[3], Rect2(Vector2(mid_w + h_sep / 2, 0), Vector2(size_right, size.height)));63956396} break;6397case VIEW_USE_4_VIEWPORTS: {6398for (int i = 0; i < 4; i++) {6399viewports[i]->show();6400}64016402fit_child_in_rect(viewports[0], Rect2(Vector2(), Vector2(size_left, size_top)));6403fit_child_in_rect(viewports[1], Rect2(Vector2(mid_w + h_sep / 2, 0), Vector2(size_right, size_top)));6404fit_child_in_rect(viewports[2], Rect2(Vector2(0, mid_h + v_sep / 2), Vector2(size_left, size_bottom)));6405fit_child_in_rect(viewports[3], Rect2(Vector2(mid_w + h_sep / 2, mid_h + v_sep / 2), Vector2(size_right, size_bottom)));64066407} break;6408}6409} break;6410}6411}64126413void Node3DEditorViewportContainer::set_view(View p_view) {6414view = p_view;6415queue_sort();6416}64176418Node3DEditorViewportContainer::View Node3DEditorViewportContainer::get_view() {6419return view;6420}64216422Node3DEditorViewportContainer::Node3DEditorViewportContainer() {6423set_clip_contents(true);6424view = VIEW_USE_1_VIEWPORT;6425mouseover = false;6426ratio_h = 0.5;6427ratio_v = 0.5;6428hovering_v = false;6429hovering_h = false;6430dragging_v = false;6431dragging_h = false;6432}64336434///////////////////////////////////////////////////////////////////64356436Node3DEditor *Node3DEditor::singleton = nullptr;64376438Node3DEditorSelectedItem::~Node3DEditorSelectedItem() {6439ERR_FAIL_NULL(RenderingServer::get_singleton());6440if (sbox_instance.is_valid()) {6441RenderingServer::get_singleton()->free(sbox_instance);6442}6443if (sbox_instance_offset.is_valid()) {6444RenderingServer::get_singleton()->free(sbox_instance_offset);6445}6446if (sbox_instance_xray.is_valid()) {6447RenderingServer::get_singleton()->free(sbox_instance_xray);6448}6449if (sbox_instance_xray_offset.is_valid()) {6450RenderingServer::get_singleton()->free(sbox_instance_xray_offset);6451}6452}64536454void Node3DEditor::select_gizmo_highlight_axis(int p_axis) {6455for (int i = 0; i < 3; i++) {6456move_gizmo[i]->surface_set_material(0, i == p_axis ? gizmo_color_hl[i] : gizmo_color[i]);6457move_plane_gizmo[i]->surface_set_material(0, (i + 6) == p_axis ? plane_gizmo_color_hl[i] : plane_gizmo_color[i]);6458rotate_gizmo[i]->surface_set_material(0, (i + 3) == p_axis ? rotate_gizmo_color_hl[i] : rotate_gizmo_color[i]);6459scale_gizmo[i]->surface_set_material(0, (i + 9) == p_axis ? gizmo_color_hl[i] : gizmo_color[i]);6460scale_plane_gizmo[i]->surface_set_material(0, (i + 12) == p_axis ? plane_gizmo_color_hl[i] : plane_gizmo_color[i]);6461}6462}64636464void Node3DEditor::update_transform_gizmo() {6465int count = 0;6466bool local_gizmo_coords = are_local_coords_enabled();64676468Vector3 gizmo_center;6469Basis gizmo_basis;64706471Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr;64726473if (se && se->gizmo.is_valid()) {6474for (const KeyValue<int, Transform3D> &E : se->subgizmos) {6475Transform3D xf = se->sp->get_global_transform() * se->gizmo->get_subgizmo_transform(E.key);6476if (!xf.is_finite()) {6477continue;6478}6479gizmo_center += xf.origin;6480if ((unsigned int)count == se->subgizmos.size() - 1 && local_gizmo_coords) {6481gizmo_basis = xf.basis;6482}6483count++;6484}6485} else {6486const List<Node *> &selection = editor_selection->get_top_selected_node_list();6487for (Node *E : selection) {6488Node3D *sp = Object::cast_to<Node3D>(E);6489if (!sp) {6490continue;6491}64926493if (sp->has_meta("_edit_lock_")) {6494continue;6495}64966497Node3DEditorSelectedItem *sel_item = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);6498if (!sel_item) {6499continue;6500}65016502Transform3D xf = sel_item->sp->get_global_transform();6503if (!xf.is_finite()) {6504continue;6505}6506gizmo_center += xf.origin;6507if (count == selection.size() - 1 && local_gizmo_coords) {6508gizmo_basis = xf.basis;6509}6510count++;6511}6512}65136514gizmo.visible = count > 0;6515gizmo.transform.origin = (count > 0) ? gizmo_center / count : Vector3();6516gizmo.transform.basis = gizmo_basis;65176518for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) {6519viewports[i]->update_transform_gizmo_view();6520}6521}65226523void _update_all_gizmos(Node *p_node) {6524for (int i = p_node->get_child_count() - 1; 0 <= i; --i) {6525Node3D *spatial_node = Object::cast_to<Node3D>(p_node->get_child(i));6526if (spatial_node) {6527spatial_node->update_gizmos();6528}65296530_update_all_gizmos(p_node->get_child(i));6531}6532}65336534void Node3DEditor::update_all_gizmos(Node *p_node) {6535if (!p_node && is_inside_tree()) {6536p_node = get_tree()->get_edited_scene_root();6537}65386539if (!p_node) {6540// No edited scene, so nothing to update.6541return;6542}6543_update_all_gizmos(p_node);6544}65456546Object *Node3DEditor::_get_editor_data(Object *p_what) {6547Node3D *sp = Object::cast_to<Node3D>(p_what);6548if (!sp) {6549return nullptr;6550}65516552Node3DEditorSelectedItem *si = memnew(Node3DEditorSelectedItem);65536554si->sp = sp;6555si->sbox_instance = RenderingServer::get_singleton()->instance_create2(6556selection_box->get_rid(),6557sp->get_world_3d()->get_scenario());6558si->sbox_instance_offset = RenderingServer::get_singleton()->instance_create2(6559selection_box->get_rid(),6560sp->get_world_3d()->get_scenario());6561RS::get_singleton()->instance_geometry_set_cast_shadows_setting(6562si->sbox_instance,6563RS::SHADOW_CASTING_SETTING_OFF);6564RS::get_singleton()->instance_geometry_set_cast_shadows_setting(6565si->sbox_instance_offset,6566RS::SHADOW_CASTING_SETTING_OFF);6567// Use the Edit layer to hide the selection box when View Gizmos is disabled, since it is a bit distracting.6568// It's still possible to approximately guess what is selected by looking at the manipulation gizmo position.6569RS::get_singleton()->instance_set_layer_mask(si->sbox_instance, 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER);6570RS::get_singleton()->instance_set_layer_mask(si->sbox_instance_offset, 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER);6571RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);6572RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);6573RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance_offset, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);6574RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance_offset, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);6575si->sbox_instance_xray = RenderingServer::get_singleton()->instance_create2(6576selection_box_xray->get_rid(),6577sp->get_world_3d()->get_scenario());6578si->sbox_instance_xray_offset = RenderingServer::get_singleton()->instance_create2(6579selection_box_xray->get_rid(),6580sp->get_world_3d()->get_scenario());6581RS::get_singleton()->instance_geometry_set_cast_shadows_setting(6582si->sbox_instance_xray,6583RS::SHADOW_CASTING_SETTING_OFF);6584RS::get_singleton()->instance_geometry_set_cast_shadows_setting(6585si->sbox_instance_xray_offset,6586RS::SHADOW_CASTING_SETTING_OFF);6587// Use the Edit layer to hide the selection box when View Gizmos is disabled, since it is a bit distracting.6588// It's still possible to approximately guess what is selected by looking at the manipulation gizmo position.6589RS::get_singleton()->instance_set_layer_mask(si->sbox_instance_xray, 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER);6590RS::get_singleton()->instance_set_layer_mask(si->sbox_instance_xray_offset, 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER);6591RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance_xray, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);6592RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance_xray, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);6593RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance_xray_offset, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);6594RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance_xray_offset, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);65956596return si;6597}65986599void Node3DEditor::_generate_selection_boxes() {6600// Use two AABBs to create the illusion of a slightly thicker line.6601AABB aabb(Vector3(), Vector3(1, 1, 1));66026603// Create a x-ray (visible through solid surfaces) and standard version of the selection box.6604// Both will be drawn at the same position, but with different opacity.6605// This lets the user see where the selection is while still having a sense of depth.6606Ref<SurfaceTool> st = memnew(SurfaceTool);6607Ref<SurfaceTool> st_xray = memnew(SurfaceTool);6608Ref<SurfaceTool> active_st = memnew(SurfaceTool);6609Ref<SurfaceTool> active_st_xray = memnew(SurfaceTool);66106611st->begin(Mesh::PRIMITIVE_LINES);6612st_xray->begin(Mesh::PRIMITIVE_LINES);6613active_st->begin(Mesh::PRIMITIVE_LINES);6614active_st_xray->begin(Mesh::PRIMITIVE_LINES);6615for (int i = 0; i < 12; i++) {6616Vector3 a, b;6617aabb.get_edge(i, a, b);66186619st->add_vertex(a);6620st->add_vertex(b);6621active_st->add_vertex(a);6622active_st->add_vertex(b);6623st_xray->add_vertex(a);6624st_xray->add_vertex(b);6625active_st_xray->add_vertex(a);6626active_st_xray->add_vertex(b);6627}66286629const Color selection_box_color = EDITOR_GET("editors/3d/selection_box_color");6630const Color active_selection_box_color = EDITOR_GET("editors/3d/active_selection_box_color");66316632selection_box_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);6633selection_box_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);6634selection_box_mat->set_albedo(selection_box_color);6635selection_box_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);6636st->set_material(selection_box_mat);6637selection_box = st->commit();66386639selection_box_mat_xray->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);6640selection_box_mat_xray->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);6641selection_box_mat_xray->set_flag(StandardMaterial3D::FLAG_DISABLE_DEPTH_TEST, true);6642selection_box_mat_xray->set_albedo(selection_box_color * Color(1, 1, 1, 0.15));6643selection_box_mat_xray->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);6644st_xray->set_material(selection_box_mat_xray);6645selection_box_xray = st_xray->commit();66466647active_selection_box_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);6648active_selection_box_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);6649active_selection_box_mat->set_albedo(active_selection_box_color);6650active_selection_box_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);6651active_st->set_material(active_selection_box_mat);6652active_selection_box = active_st->commit();66536654active_selection_box_mat_xray->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);6655active_selection_box_mat_xray->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);6656active_selection_box_mat_xray->set_flag(StandardMaterial3D::FLAG_DISABLE_DEPTH_TEST, true);6657active_selection_box_mat_xray->set_albedo(active_selection_box_color * Color(1, 1, 1, 0.15));6658active_selection_box_mat_xray->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);6659active_st_xray->set_material(active_selection_box_mat_xray);6660active_selection_box_xray = active_st_xray->commit();6661}66626663Dictionary Node3DEditor::get_state() const {6664Dictionary d;66656666d["snap_enabled"] = snap_enabled;6667d["translate_snap"] = snap_translate_value;6668d["rotate_snap"] = snap_rotate_value;6669d["scale_snap"] = snap_scale_value;66706671d["local_coords"] = tool_option_button[TOOL_OPT_LOCAL_COORDS]->is_pressed();66726673int vc = 0;6674if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT))) {6675vc = 1;6676} else if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS))) {6677vc = 2;6678} else if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS))) {6679vc = 3;6680} else if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS))) {6681vc = 4;6682} else if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT))) {6683vc = 5;6684} else if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT))) {6685vc = 6;6686}66876688d["viewport_mode"] = vc;6689Array vpdata;6690for (int i = 0; i < 4; i++) {6691vpdata.push_back(viewports[i]->get_state());6692}66936694d["viewports"] = vpdata;66956696d["show_grid"] = view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_GRID));6697d["show_origin"] = view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_ORIGIN));6698d["fov"] = get_fov();6699d["znear"] = get_znear();6700d["zfar"] = get_zfar();67016702Dictionary gizmos_status;6703for (int i = 0; i < gizmo_plugins_by_name.size(); i++) {6704if (!gizmo_plugins_by_name[i]->can_be_hidden()) {6705continue;6706}6707int state = gizmos_menu->get_item_state(gizmos_menu->get_item_index(i));6708String name = gizmo_plugins_by_name[i]->get_gizmo_name();6709gizmos_status[name] = state;6710}67116712d["gizmos_status"] = gizmos_status;6713{6714Dictionary pd;67156716pd["sun_rotation"] = sun_rotation;67176718pd["environ_sky_color"] = environ_sky_color->get_pick_color();6719pd["environ_ground_color"] = environ_ground_color->get_pick_color();6720pd["environ_energy"] = environ_energy->get_value();6721pd["environ_glow_enabled"] = environ_glow_button->is_pressed();6722pd["environ_tonemap_enabled"] = environ_tonemap_button->is_pressed();6723pd["environ_ao_enabled"] = environ_ao_button->is_pressed();6724pd["environ_gi_enabled"] = environ_gi_button->is_pressed();6725pd["sun_shadow_max_distance"] = sun_shadow_max_distance->get_value();67266727pd["sun_color"] = sun_color->get_pick_color();6728pd["sun_energy"] = sun_energy->get_value();67296730pd["sun_enabled"] = sun_button->is_pressed();6731pd["environ_enabled"] = environ_button->is_pressed();67326733d["preview_sun_env"] = pd;6734}67356736return d;6737}67386739void Node3DEditor::set_state(const Dictionary &p_state) {6740Dictionary d = p_state;67416742if (d.has("snap_enabled")) {6743snap_enabled = d["snap_enabled"];6744tool_option_button[TOOL_OPT_USE_SNAP]->set_pressed(d["snap_enabled"]);6745}67466747if (d.has("translate_snap")) {6748snap_translate_value = d["translate_snap"];6749}67506751if (d.has("rotate_snap")) {6752snap_rotate_value = d["rotate_snap"];6753}67546755if (d.has("scale_snap")) {6756snap_scale_value = d["scale_snap"];6757}67586759_snap_update();67606761if (d.has("local_coords")) {6762tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_pressed(d["local_coords"]);6763update_transform_gizmo();6764}67656766if (d.has("viewport_mode")) {6767int vc = d["viewport_mode"];67686769if (vc == 1) {6770_menu_item_pressed(MENU_VIEW_USE_1_VIEWPORT);6771} else if (vc == 2) {6772_menu_item_pressed(MENU_VIEW_USE_2_VIEWPORTS);6773} else if (vc == 3) {6774_menu_item_pressed(MENU_VIEW_USE_3_VIEWPORTS);6775} else if (vc == 4) {6776_menu_item_pressed(MENU_VIEW_USE_4_VIEWPORTS);6777} else if (vc == 5) {6778_menu_item_pressed(MENU_VIEW_USE_2_VIEWPORTS_ALT);6779} else if (vc == 6) {6780_menu_item_pressed(MENU_VIEW_USE_3_VIEWPORTS_ALT);6781}6782}67836784if (d.has("viewports")) {6785Array vp = d["viewports"];6786uint32_t vp_size = static_cast<uint32_t>(vp.size());6787if (vp_size > VIEWPORTS_COUNT) {6788WARN_PRINT("Ignoring superfluous viewport settings from spatial editor state.");6789vp_size = VIEWPORTS_COUNT;6790}67916792for (uint32_t i = 0; i < vp_size; i++) {6793viewports[i]->set_state(vp[i]);6794}6795}67966797if (d.has("zfar")) {6798settings_zfar->set_value(double(d["zfar"]));6799}6800if (d.has("znear")) {6801settings_znear->set_value(double(d["znear"]));6802}6803if (d.has("fov")) {6804settings_fov->set_value(double(d["fov"]));6805}6806if (d.has("show_grid")) {6807bool use = d["show_grid"];68086809if (use != view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_GRID))) {6810_menu_item_pressed(MENU_VIEW_GRID);6811}6812}68136814if (d.has("show_origin")) {6815bool use = d["show_origin"];68166817if (use != view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_ORIGIN))) {6818view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_ORIGIN), use);6819RenderingServer::get_singleton()->instance_set_visible(origin_instance, use);6820}6821}68226823if (d.has("gizmos_status")) {6824Dictionary gizmos_status = d["gizmos_status"];68256826for (int j = 0; j < gizmo_plugins_by_name.size(); ++j) {6827if (!gizmo_plugins_by_name[j]->can_be_hidden()) {6828continue;6829}6830int state = EditorNode3DGizmoPlugin::VISIBLE;6831for (const KeyValue<Variant, Variant> &kv : gizmos_status) {6832if (gizmo_plugins_by_name.write[j]->get_gizmo_name() == String(kv.key)) {6833state = kv.value;6834break;6835}6836}68376838gizmo_plugins_by_name.write[j]->set_state(state);6839}6840_update_gizmos_menu();6841}68426843if (d.has("preview_sun_env")) {6844sun_environ_updating = true;6845Dictionary pd = d["preview_sun_env"];6846sun_rotation = pd["sun_rotation"];68476848environ_sky_color->set_pick_color(pd["environ_sky_color"]);6849environ_ground_color->set_pick_color(pd["environ_ground_color"]);6850environ_energy->set_value_no_signal(pd["environ_energy"]);6851environ_glow_button->set_pressed_no_signal(pd["environ_glow_enabled"]);6852environ_tonemap_button->set_pressed_no_signal(pd["environ_tonemap_enabled"]);6853environ_ao_button->set_pressed_no_signal(pd["environ_ao_enabled"]);6854environ_gi_button->set_pressed_no_signal(pd["environ_gi_enabled"]);6855sun_shadow_max_distance->set_value_no_signal(pd["sun_shadow_max_distance"]);68566857sun_color->set_pick_color(pd["sun_color"]);6858sun_energy->set_value_no_signal(pd["sun_energy"]);68596860sun_button->set_pressed(pd["sun_enabled"]);6861environ_button->set_pressed(pd["environ_enabled"]);68626863sun_environ_updating = false;68646865_preview_settings_changed();6866_update_preview_environment();6867} else {6868_load_default_preview_settings();6869sun_button->set_pressed(true);6870environ_button->set_pressed(true);6871_preview_settings_changed();6872_update_preview_environment();6873}6874}68756876void Node3DEditor::edit(Node3D *p_spatial) {6877if (p_spatial != selected) {6878if (selected) {6879Vector<Ref<Node3DGizmo>> gizmos = selected->get_gizmos();6880for (int i = 0; i < gizmos.size(); i++) {6881Ref<EditorNode3DGizmo> seg = gizmos[i];6882if (seg.is_null()) {6883continue;6884}6885seg->set_selected(false);6886}68876888Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected);6889if (se) {6890se->gizmo.unref();6891se->subgizmos.clear();6892}68936894selected->update_gizmos();6895}68966897selected = p_spatial;6898current_hover_gizmo = Ref<EditorNode3DGizmo>();6899current_hover_gizmo_handle = -1;6900current_hover_gizmo_handle_secondary = false;69016902if (selected) {6903Vector<Ref<Node3DGizmo>> gizmos = selected->get_gizmos();6904for (int i = 0; i < gizmos.size(); i++) {6905Ref<EditorNode3DGizmo> seg = gizmos[i];6906if (seg.is_null()) {6907continue;6908}6909seg->set_selected(true);6910}6911selected->update_gizmos();6912}6913}6914}69156916void Node3DEditor::_snap_changed() {6917snap_translate_value = snap_translate->get_text().to_float();6918snap_rotate_value = snap_rotate->get_text().to_float();6919snap_scale_value = snap_scale->get_text().to_float();69206921EditorSettings::get_singleton()->set_project_metadata("3d_editor", "snap_translate_value", snap_translate_value);6922EditorSettings::get_singleton()->set_project_metadata("3d_editor", "snap_rotate_value", snap_rotate_value);6923EditorSettings::get_singleton()->set_project_metadata("3d_editor", "snap_scale_value", snap_scale_value);6924}69256926void Node3DEditor::_snap_update() {6927double snap = EDITOR_GET("interface/inspector/default_float_step");6928int snap_step_decimals = Math::range_step_decimals(snap);69296930snap_translate->set_text(String::num(snap_translate_value, snap_step_decimals));6931snap_rotate->set_text(String::num(snap_rotate_value, snap_step_decimals));6932snap_scale->set_text(String::num(snap_scale_value, snap_step_decimals));6933}69346935void Node3DEditor::_xform_dialog_action() {6936Transform3D t;6937//translation6938Vector3 scale;6939Vector3 rotate;6940Vector3 translate;69416942for (int i = 0; i < 3; i++) {6943translate[i] = xform_translate[i]->get_text().to_float();6944rotate[i] = Math::deg_to_rad(xform_rotate[i]->get_text().to_float());6945scale[i] = xform_scale[i]->get_text().to_float();6946}69476948t.basis.scale(scale);6949t.basis.rotate(rotate);6950t.origin = translate;69516952EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();6953undo_redo->create_action(TTR("XForm Dialog"));69546955const List<Node *> &selection = editor_selection->get_top_selected_node_list();69566957for (Node *E : selection) {6958Node3D *sp = Object::cast_to<Node3D>(E);6959if (!sp) {6960continue;6961}69626963Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);6964if (!se) {6965continue;6966}69676968bool post = xform_type->get_selected() > 0;69696970Transform3D tr = sp->get_global_gizmo_transform();6971if (post) {6972tr = tr * t;6973} else {6974tr.basis = t.basis * tr.basis;6975tr.origin += t.origin;6976}69776978Node3D *parent = sp->get_parent_node_3d();6979Transform3D local_tr = parent ? parent->get_global_transform().affine_inverse() * tr : tr;6980undo_redo->add_do_method(sp, "set_transform", local_tr);6981undo_redo->add_undo_method(sp, "set_transform", sp->get_transform());6982}6983undo_redo->commit_action();6984}69856986void Node3DEditor::_menu_item_toggled(bool pressed, int p_option) {6987switch (p_option) {6988case MENU_TOOL_LOCAL_COORDS: {6989tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_pressed(pressed);6990update_transform_gizmo();6991} break;69926993case MENU_TOOL_USE_SNAP: {6994tool_option_button[TOOL_OPT_USE_SNAP]->set_pressed(pressed);6995snap_enabled = pressed;6996} break;6997}6998}69997000void Node3DEditor::_menu_gizmo_toggled(int p_option) {7001const int idx = gizmos_menu->get_item_index(p_option);7002gizmos_menu->toggle_item_multistate(idx);70037004// Change icon7005const int state = gizmos_menu->get_item_state(idx);7006switch (state) {7007case EditorNode3DGizmoPlugin::VISIBLE:7008gizmos_menu->set_item_icon(idx, get_editor_theme_icon(SNAME("GuiVisibilityVisible")));7009break;7010case EditorNode3DGizmoPlugin::ON_TOP:7011gizmos_menu->set_item_icon(idx, get_editor_theme_icon(SNAME("GuiVisibilityXray")));7012break;7013case EditorNode3DGizmoPlugin::HIDDEN:7014gizmos_menu->set_item_icon(idx, get_editor_theme_icon(SNAME("GuiVisibilityHidden")));7015break;7016}70177018gizmo_plugins_by_name.write[p_option]->set_state(state);70197020update_all_gizmos();7021}70227023void Node3DEditor::_menu_item_pressed(int p_option) {7024EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();7025switch (p_option) {7026case MENU_TOOL_SELECT:7027case MENU_TOOL_MOVE:7028case MENU_TOOL_ROTATE:7029case MENU_TOOL_SCALE:7030case MENU_TOOL_LIST_SELECT: {7031for (int i = 0; i < TOOL_MAX; i++) {7032tool_button[i]->set_pressed(i == p_option);7033}7034tool_mode = (ToolMode)p_option;7035update_transform_gizmo();70367037} break;7038case MENU_TRANSFORM_CONFIGURE_SNAP: {7039snap_dialog->popup_centered(Size2(200, 180));7040} break;7041case MENU_TRANSFORM_DIALOG: {7042for (int i = 0; i < 3; i++) {7043xform_translate[i]->set_text("0");7044xform_rotate[i]->set_text("0");7045xform_scale[i]->set_text("1");7046}70477048xform_dialog->popup_centered(Size2(320, 240) * EDSCALE);70497050} break;7051case MENU_VIEW_USE_1_VIEWPORT: {7052viewport_base->set_view(Node3DEditorViewportContainer::VIEW_USE_1_VIEWPORT);7053if (last_used_viewport > 0) {7054last_used_viewport = 0;7055}70567057view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), true);7058view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), false);7059view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS), false);7060view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS), false);7061view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT), false);7062view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT), false);70637064} break;7065case MENU_VIEW_USE_2_VIEWPORTS: {7066viewport_base->set_view(Node3DEditorViewportContainer::VIEW_USE_2_VIEWPORTS);7067if (last_used_viewport > 1) {7068last_used_viewport = 0;7069}70707071view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), false);7072view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), true);7073view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS), false);7074view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS), false);7075view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT), false);7076view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT), false);70777078} break;7079case MENU_VIEW_USE_2_VIEWPORTS_ALT: {7080viewport_base->set_view(Node3DEditorViewportContainer::VIEW_USE_2_VIEWPORTS_ALT);7081if (last_used_viewport > 1) {7082last_used_viewport = 0;7083}70847085view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), false);7086view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), false);7087view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS), false);7088view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS), false);7089view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT), true);7090view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT), false);70917092} break;7093case MENU_VIEW_USE_3_VIEWPORTS: {7094viewport_base->set_view(Node3DEditorViewportContainer::VIEW_USE_3_VIEWPORTS);7095if (last_used_viewport > 2) {7096last_used_viewport = 0;7097}70987099view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), false);7100view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), false);7101view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS), true);7102view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS), false);7103view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT), false);7104view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT), false);71057106} break;7107case MENU_VIEW_USE_3_VIEWPORTS_ALT: {7108viewport_base->set_view(Node3DEditorViewportContainer::VIEW_USE_3_VIEWPORTS_ALT);7109if (last_used_viewport > 2) {7110last_used_viewport = 0;7111}71127113view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), false);7114view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), false);7115view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS), false);7116view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS), false);7117view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT), false);7118view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT), true);71197120} break;7121case MENU_VIEW_USE_4_VIEWPORTS: {7122viewport_base->set_view(Node3DEditorViewportContainer::VIEW_USE_4_VIEWPORTS);71237124view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), false);7125view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), false);7126view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS), false);7127view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS), true);7128view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT), false);7129view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT), false);71307131} break;7132case MENU_VIEW_ORIGIN: {7133bool is_checked = view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(p_option));71347135origin_enabled = !is_checked;7136RenderingServer::get_singleton()->instance_set_visible(origin_instance, origin_enabled);7137// Update the grid since its appearance depends on whether the origin is enabled7138_finish_grid();7139_init_grid();71407141view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(p_option), origin_enabled);7142} break;7143case MENU_VIEW_GRID: {7144bool is_checked = view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(p_option));71457146grid_enabled = !is_checked;71477148for (int i = 0; i < 3; ++i) {7149if (grid_enable[i]) {7150grid_visible[i] = grid_enabled;7151}7152}7153_finish_grid();7154_init_grid();71557156view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(p_option), grid_enabled);71577158} break;7159case MENU_VIEW_CAMERA_SETTINGS: {7160settings_dialog->popup_centered(settings_vbc->get_combined_minimum_size() + Size2(50, 50));7161} break;7162case MENU_SNAP_TO_FLOOR: {7163snap_selected_nodes_to_floor();7164} break;7165case MENU_LOCK_SELECTED: {7166undo_redo->create_action(TTR("Lock Selected"));71677168const List<Node *> &selection = editor_selection->get_top_selected_node_list();71697170for (Node *E : selection) {7171Node3D *spatial = Object::cast_to<Node3D>(E);7172if (!spatial || !spatial->is_inside_tree()) {7173continue;7174}71757176undo_redo->add_do_method(spatial, "set_meta", "_edit_lock_", true);7177undo_redo->add_undo_method(spatial, "remove_meta", "_edit_lock_");7178undo_redo->add_do_method(this, "emit_signal", "item_lock_status_changed");7179undo_redo->add_undo_method(this, "emit_signal", "item_lock_status_changed");7180}71817182undo_redo->add_do_method(this, "_refresh_menu_icons");7183undo_redo->add_undo_method(this, "_refresh_menu_icons");7184undo_redo->commit_action();7185} break;7186case MENU_UNLOCK_SELECTED: {7187undo_redo->create_action(TTR("Unlock Selected"));71887189const List<Node *> &selection = editor_selection->get_top_selected_node_list();71907191for (Node *E : selection) {7192Node3D *spatial = Object::cast_to<Node3D>(E);7193if (!spatial || !spatial->is_inside_tree()) {7194continue;7195}71967197undo_redo->add_do_method(spatial, "remove_meta", "_edit_lock_");7198undo_redo->add_undo_method(spatial, "set_meta", "_edit_lock_", true);7199undo_redo->add_do_method(this, "emit_signal", "item_lock_status_changed");7200undo_redo->add_undo_method(this, "emit_signal", "item_lock_status_changed");7201}72027203undo_redo->add_do_method(this, "_refresh_menu_icons");7204undo_redo->add_undo_method(this, "_refresh_menu_icons");7205undo_redo->commit_action();7206} break;7207case MENU_GROUP_SELECTED: {7208undo_redo->create_action(TTR("Group Selected"));72097210const List<Node *> &selection = editor_selection->get_top_selected_node_list();72117212for (Node *E : selection) {7213Node3D *spatial = Object::cast_to<Node3D>(E);7214if (!spatial || !spatial->is_inside_tree()) {7215continue;7216}72177218undo_redo->add_do_method(spatial, "set_meta", "_edit_group_", true);7219undo_redo->add_undo_method(spatial, "remove_meta", "_edit_group_");7220undo_redo->add_do_method(this, "emit_signal", "item_group_status_changed");7221undo_redo->add_undo_method(this, "emit_signal", "item_group_status_changed");7222}72237224undo_redo->add_do_method(this, "_refresh_menu_icons");7225undo_redo->add_undo_method(this, "_refresh_menu_icons");7226undo_redo->commit_action();7227} break;7228case MENU_UNGROUP_SELECTED: {7229undo_redo->create_action(TTR("Ungroup Selected"));7230const List<Node *> &selection = editor_selection->get_top_selected_node_list();72317232for (Node *E : selection) {7233Node3D *spatial = Object::cast_to<Node3D>(E);7234if (!spatial || !spatial->is_inside_tree()) {7235continue;7236}72377238undo_redo->add_do_method(spatial, "remove_meta", "_edit_group_");7239undo_redo->add_undo_method(spatial, "set_meta", "_edit_group_", true);7240undo_redo->add_do_method(this, "emit_signal", "item_group_status_changed");7241undo_redo->add_undo_method(this, "emit_signal", "item_group_status_changed");7242}72437244undo_redo->add_do_method(this, "_refresh_menu_icons");7245undo_redo->add_undo_method(this, "_refresh_menu_icons");7246undo_redo->commit_action();7247} break;7248case MENU_RULER: {7249for (int i = 0; i < TOOL_MAX; i++) {7250tool_button[i]->set_pressed(i == p_option);7251}7252tool_button[TOOL_RULER]->set_pressed(true);7253tool_mode = ToolMode::TOOL_RULER;7254update_transform_gizmo();7255} break;7256}7257}72587259void Node3DEditor::_init_indicators() {7260{7261origin_enabled = true;7262grid_enabled = true;72637264Ref<Shader> origin_shader = memnew(Shader);7265origin_shader->set_code(R"(7266// 3D editor origin line shader.72677268shader_type spatial;7269render_mode blend_mix, cull_disabled, unshaded, fog_disabled;72707271void vertex() {7272vec3 point_a = MODEL_MATRIX[3].xyz;7273// Encoded in scale.7274vec3 point_b = vec3(MODEL_MATRIX[0].x, MODEL_MATRIX[1].y, MODEL_MATRIX[2].z);72757276// Points are already in world space, so no need for MODEL_MATRIX anymore.7277vec4 clip_a = PROJECTION_MATRIX * (VIEW_MATRIX * vec4(point_a, 1.0));7278vec4 clip_b = PROJECTION_MATRIX * (VIEW_MATRIX * vec4(point_b, 1.0));72797280vec2 screen_a = VIEWPORT_SIZE * (0.5 * clip_a.xy / clip_a.w + 0.5);7281vec2 screen_b = VIEWPORT_SIZE * (0.5 * clip_b.xy / clip_b.w + 0.5);72827283vec2 x_basis = normalize(screen_b - screen_a);7284vec2 y_basis = vec2(-x_basis.y, x_basis.x);72857286float width = 3.0;7287vec2 screen_point_a = screen_a + width * (VERTEX.x * x_basis + VERTEX.y * y_basis);7288vec2 screen_point_b = screen_b + width * (VERTEX.x * x_basis + VERTEX.y * y_basis);7289vec2 screen_point_final = mix(screen_point_a, screen_point_b, VERTEX.z);72907291vec4 clip_final = mix(clip_a, clip_b, VERTEX.z);72927293POSITION = vec4(clip_final.w * ((2.0 * screen_point_final) / VIEWPORT_SIZE - 1.0), clip_final.z, clip_final.w);7294UV = VERTEX.yz * clip_final.w;72957296if (!OUTPUT_IS_SRGB) {7297COLOR.rgb = mix(pow((COLOR.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)), COLOR.rgb * (1.0 / 12.92), lessThan(COLOR.rgb, vec3(0.04045)));7298}7299}73007301void fragment() {7302// Multiply by 0.5 since UV is actually UV is [-1, 1].7303float line_width = fwidth(UV.x * 0.5);7304float line_uv = abs(UV.x * 0.5);7305float line = smoothstep(line_width * 1.0, line_width * 0.25, line_uv);73067307ALBEDO = COLOR.rgb;7308ALPHA *= COLOR.a * line;7309}7310)");73117312origin_mat.instantiate();7313origin_mat->set_shader(origin_shader);73147315Vector<Vector3> origin_points;7316origin_points.resize(6);73177318origin_points.set(0, Vector3(0.0, -0.5, 0.0));7319origin_points.set(1, Vector3(0.0, -0.5, 1.0));7320origin_points.set(2, Vector3(0.0, 0.5, 1.0));73217322origin_points.set(3, Vector3(0.0, -0.5, 0.0));7323origin_points.set(4, Vector3(0.0, 0.5, 1.0));7324origin_points.set(5, Vector3(0.0, 0.5, 0.0));73257326Array d;7327d.resize(RS::ARRAY_MAX);7328d[RenderingServer::ARRAY_VERTEX] = origin_points;73297330origin_mesh = RenderingServer::get_singleton()->mesh_create();73317332RenderingServer::get_singleton()->mesh_add_surface_from_arrays(origin_mesh, RenderingServer::PRIMITIVE_TRIANGLES, d);7333RenderingServer::get_singleton()->mesh_surface_set_material(origin_mesh, 0, origin_mat->get_rid());73347335origin_multimesh = RenderingServer::get_singleton()->multimesh_create();7336RenderingServer::get_singleton()->multimesh_set_mesh(origin_multimesh, origin_mesh);7337RenderingServer::get_singleton()->multimesh_allocate_data(origin_multimesh, 12, RS::MultimeshTransformFormat::MULTIMESH_TRANSFORM_3D, true, false);7338RenderingServer::get_singleton()->multimesh_set_visible_instances(origin_multimesh, -1);73397340LocalVector<float> distances;7341distances.resize(5);7342distances[0] = -1000000.0;7343distances[1] = -1000.0;7344distances[2] = 0.0;7345distances[3] = 1000.0;7346distances[4] = 1000000.0;73477348for (int i = 0; i < 3; i++) {7349Color origin_color;7350switch (i) {7351case 0:7352origin_color = get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor));7353break;7354case 1:7355origin_color = get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor));7356break;7357case 2:7358origin_color = get_theme_color(SNAME("axis_z_color"), EditorStringName(Editor));7359break;7360default:7361origin_color = Color();7362break;7363}73647365Vector3 axis;7366axis[i] = 1;73677368for (int j = 0; j < 4; j++) {7369Transform3D t = Transform3D();7370if (distances[j] > 0.0) {7371t = t.scaled(axis * distances[j + 1]);7372t = t.translated(axis * distances[j]);7373} else {7374t = t.scaled(axis * distances[j]);7375t = t.translated(axis * distances[j + 1]);7376}7377RenderingServer::get_singleton()->multimesh_instance_set_transform(origin_multimesh, i * 4 + j, t);7378RenderingServer::get_singleton()->multimesh_instance_set_color(origin_multimesh, i * 4 + j, origin_color);7379}7380}73817382origin_instance = RenderingServer::get_singleton()->instance_create2(origin_multimesh, get_tree()->get_root()->get_world_3d()->get_scenario());7383RS::get_singleton()->instance_set_layer_mask(origin_instance, 1 << Node3DEditorViewport::GIZMO_GRID_LAYER);7384RS::get_singleton()->instance_geometry_set_flag(origin_instance, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);7385RS::get_singleton()->instance_geometry_set_flag(origin_instance, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);73867387RenderingServer::get_singleton()->instance_geometry_set_cast_shadows_setting(origin_instance, RS::SHADOW_CASTING_SETTING_OFF);73887389Ref<Shader> grid_shader = memnew(Shader);7390grid_shader->set_code(R"(7391// 3D editor grid shader.73927393shader_type spatial;73947395render_mode unshaded, fog_disabled;73967397uniform bool orthogonal;7398uniform float grid_size;73997400void vertex() {7401// From FLAG_SRGB_VERTEX_COLOR.7402if (!OUTPUT_IS_SRGB) {7403COLOR.rgb = mix(pow((COLOR.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)), COLOR.rgb * (1.0 / 12.92), lessThan(COLOR.rgb, vec3(0.04045)));7404}7405}74067407void fragment() {7408ALBEDO = COLOR.rgb;7409vec3 dir = orthogonal ? -vec3(0, 0, 1) : VIEW;7410float angle_fade = abs(dot(dir, NORMAL));7411angle_fade = smoothstep(0.05, 0.2, angle_fade);74127413vec3 world_pos = (INV_VIEW_MATRIX * vec4(VERTEX, 1.0)).xyz;7414vec3 world_normal = (INV_VIEW_MATRIX * vec4(NORMAL, 0.0)).xyz;7415vec3 camera_world_pos = INV_VIEW_MATRIX[3].xyz;7416vec3 camera_world_pos_on_plane = camera_world_pos * (1.0 - world_normal);7417float dist_fade = 1.0 - (distance(world_pos, camera_world_pos_on_plane) / grid_size);7418dist_fade = smoothstep(0.02, 0.3, dist_fade);74197420ALPHA = COLOR.a * dist_fade * angle_fade;7421}7422)");74237424for (int i = 0; i < 3; i++) {7425grid_mat[i].instantiate();7426grid_mat[i]->set_shader(grid_shader);7427}74287429grid_enable[0] = EDITOR_GET("editors/3d/grid_xy_plane");7430grid_enable[1] = EDITOR_GET("editors/3d/grid_yz_plane");7431grid_enable[2] = EDITOR_GET("editors/3d/grid_xz_plane");7432grid_visible[0] = grid_enable[0];7433grid_visible[1] = grid_enable[1];7434grid_visible[2] = grid_enable[2];74357436_init_grid();7437}74387439{7440//move gizmo74417442// Inverted zxy.7443Vector3 ivec = Vector3(0, 0, -1);7444Vector3 nivec = Vector3(-1, -1, 0);7445Vector3 ivec2 = Vector3(-1, 0, 0);7446Vector3 ivec3 = Vector3(0, -1, 0);74477448for (int i = 0; i < 3; i++) {7449Color col;7450switch (i) {7451case 0:7452col = get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor));7453break;7454case 1:7455col = get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor));7456break;7457case 2:7458col = get_theme_color(SNAME("axis_z_color"), EditorStringName(Editor));7459break;7460default:7461col = Color();7462break;7463}74647465col.a = EDITOR_GET("editors/3d/manipulator_gizmo_opacity");74667467move_gizmo[i].instantiate();7468move_plane_gizmo[i].instantiate();7469rotate_gizmo[i].instantiate();7470scale_gizmo[i].instantiate();7471scale_plane_gizmo[i].instantiate();7472axis_gizmo[i].instantiate();74737474Ref<StandardMaterial3D> mat = memnew(StandardMaterial3D);7475mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);7476mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);7477mat->set_on_top_of_alpha();7478mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);7479mat->set_albedo(col);7480gizmo_color[i] = mat;74817482Ref<StandardMaterial3D> mat_hl = mat->duplicate();7483const Color albedo = col.from_hsv(col.get_h(), 0.25, 1.0, 1);7484mat_hl->set_albedo(albedo);7485gizmo_color_hl[i] = mat_hl;74867487//translate7488{7489Ref<SurfaceTool> surftool = memnew(SurfaceTool);7490surftool->begin(Mesh::PRIMITIVE_TRIANGLES);74917492// Arrow profile7493const int arrow_points = 5;7494Vector3 arrow[5] = {7495nivec * 0.0 + ivec * 0.0,7496nivec * 0.01 + ivec * 0.0,7497nivec * 0.01 + ivec * GIZMO_ARROW_OFFSET,7498nivec * 0.065 + ivec * GIZMO_ARROW_OFFSET,7499nivec * 0.0 + ivec * (GIZMO_ARROW_OFFSET + GIZMO_ARROW_SIZE),7500};75017502int arrow_sides = 16;75037504const real_t arrow_sides_step = Math::TAU / arrow_sides;7505for (int k = 0; k < arrow_sides; k++) {7506Basis ma(ivec, k * arrow_sides_step);7507Basis mb(ivec, (k + 1) * arrow_sides_step);75087509for (int j = 0; j < arrow_points - 1; j++) {7510Vector3 points[4] = {7511ma.xform(arrow[j]),7512mb.xform(arrow[j]),7513mb.xform(arrow[j + 1]),7514ma.xform(arrow[j + 1]),7515};7516surftool->add_vertex(points[0]);7517surftool->add_vertex(points[1]);7518surftool->add_vertex(points[2]);75197520surftool->add_vertex(points[0]);7521surftool->add_vertex(points[2]);7522surftool->add_vertex(points[3]);7523}7524}75257526surftool->set_material(mat);7527surftool->commit(move_gizmo[i]);7528}75297530// Plane Translation7531{7532Ref<SurfaceTool> surftool = memnew(SurfaceTool);7533surftool->begin(Mesh::PRIMITIVE_TRIANGLES);75347535Vector3 vec = ivec2 - ivec3;7536Vector3 plane[4] = {7537vec * GIZMO_PLANE_DST,7538vec * GIZMO_PLANE_DST + ivec2 * GIZMO_PLANE_SIZE,7539vec * (GIZMO_PLANE_DST + GIZMO_PLANE_SIZE),7540vec * GIZMO_PLANE_DST - ivec3 * GIZMO_PLANE_SIZE7541};75427543Basis ma(ivec, Math::PI / 2);75447545Vector3 points[4] = {7546ma.xform(plane[0]),7547ma.xform(plane[1]),7548ma.xform(plane[2]),7549ma.xform(plane[3]),7550};7551surftool->add_vertex(points[0]);7552surftool->add_vertex(points[1]);7553surftool->add_vertex(points[2]);75547555surftool->add_vertex(points[0]);7556surftool->add_vertex(points[2]);7557surftool->add_vertex(points[3]);75587559Ref<StandardMaterial3D> plane_mat = memnew(StandardMaterial3D);7560plane_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);7561plane_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);7562plane_mat->set_on_top_of_alpha();7563plane_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);7564plane_mat->set_cull_mode(StandardMaterial3D::CULL_DISABLED);7565plane_mat->set_albedo(col);7566plane_gizmo_color[i] = plane_mat; // needed, so we can draw planes from both sides7567surftool->set_material(plane_mat);7568surftool->commit(move_plane_gizmo[i]);75697570Ref<StandardMaterial3D> plane_mat_hl = plane_mat->duplicate();7571plane_mat_hl->set_albedo(albedo);7572plane_gizmo_color_hl[i] = plane_mat_hl; // needed, so we can draw planes from both sides7573}75747575// Rotate7576{7577Ref<SurfaceTool> surftool = memnew(SurfaceTool);7578surftool->begin(Mesh::PRIMITIVE_TRIANGLES);75797580int n = 128; // number of circle segments7581int m = 3; // number of thickness segments75827583real_t step = Math::TAU / n;7584for (int j = 0; j < n; ++j) {7585Basis basis = Basis(ivec, j * step);75867587Vector3 vertex = basis.xform(ivec2 * GIZMO_CIRCLE_SIZE);75887589for (int k = 0; k < m; ++k) {7590Vector2 ofs = Vector2(Math::cos((Math::TAU * k) / m), Math::sin((Math::TAU * k) / m));7591Vector3 normal = ivec * ofs.x + ivec2 * ofs.y;75927593surftool->set_normal(basis.xform(normal));7594surftool->add_vertex(vertex);7595}7596}75977598for (int j = 0; j < n; ++j) {7599for (int k = 0; k < m; ++k) {7600int current_ring = j * m;7601int next_ring = ((j + 1) % n) * m;7602int current_segment = k;7603int next_segment = (k + 1) % m;76047605surftool->add_index(current_ring + next_segment);7606surftool->add_index(current_ring + current_segment);7607surftool->add_index(next_ring + current_segment);76087609surftool->add_index(next_ring + current_segment);7610surftool->add_index(next_ring + next_segment);7611surftool->add_index(current_ring + next_segment);7612}7613}76147615Ref<Shader> rotate_shader = memnew(Shader);76167617rotate_shader->set_code(R"(7618// 3D editor rotation manipulator gizmo shader.76197620shader_type spatial;76217622render_mode unshaded, depth_test_disabled, fog_disabled;76237624uniform vec4 albedo;76257626mat3 orthonormalize(mat3 m) {7627vec3 x = normalize(m[0]);7628vec3 y = normalize(m[1] - x * dot(x, m[1]));7629vec3 z = m[2] - x * dot(x, m[2]);7630z = normalize(z - y * (dot(y, m[2])));7631return mat3(x,y,z);7632}76337634void vertex() {7635mat3 mv = orthonormalize(mat3(MODELVIEW_MATRIX));7636vec3 n = mv * VERTEX;7637float orientation = dot(vec3(0.0, 0.0, -1.0), n);7638if (orientation <= 0.005) {7639VERTEX += NORMAL * 0.02;7640}7641}76427643void fragment() {7644ALBEDO = albedo.rgb;7645ALPHA = albedo.a;7646}7647)");76487649Ref<ShaderMaterial> rotate_mat = memnew(ShaderMaterial);7650rotate_mat->set_render_priority(Material::RENDER_PRIORITY_MAX);7651rotate_mat->set_shader(rotate_shader);7652rotate_mat->set_shader_parameter("albedo", col);7653rotate_gizmo_color[i] = rotate_mat;76547655Array arrays = surftool->commit_to_arrays();7656rotate_gizmo[i]->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, arrays);7657rotate_gizmo[i]->surface_set_material(0, rotate_mat);76587659Ref<ShaderMaterial> rotate_mat_hl = rotate_mat->duplicate();7660rotate_mat_hl->set_shader_parameter("albedo", albedo);7661rotate_gizmo_color_hl[i] = rotate_mat_hl;76627663if (i == 2) { // Rotation white outline7664Ref<ShaderMaterial> border_mat = rotate_mat->duplicate();76657666Ref<Shader> border_shader = memnew(Shader);7667border_shader->set_code(R"(7668// 3D editor rotation manipulator gizmo shader (white outline).76697670shader_type spatial;76717672render_mode unshaded, depth_test_disabled, fog_disabled;76737674uniform vec4 albedo;76757676mat3 orthonormalize(mat3 m) {7677vec3 x = normalize(m[0]);7678vec3 y = normalize(m[1] - x * dot(x, m[1]));7679vec3 z = m[2] - x * dot(x, m[2]);7680z = normalize(z - y * (dot(y, m[2])));7681return mat3(x, y, z);7682}76837684void vertex() {7685mat3 mv = orthonormalize(mat3(MODELVIEW_MATRIX));7686mv = inverse(mv);7687VERTEX += NORMAL * 0.008;7688vec3 camera_dir_local = mv * vec3(0.0, 0.0, 1.0);7689vec3 camera_up_local = mv * vec3(0.0, 1.0, 0.0);7690mat3 rotation_matrix = mat3(cross(camera_dir_local, camera_up_local), camera_up_local, camera_dir_local);7691VERTEX = rotation_matrix * VERTEX;7692}76937694void fragment() {7695ALBEDO = albedo.rgb;7696ALPHA = albedo.a;7697}7698)");76997700border_mat->set_shader(border_shader);7701border_mat->set_shader_parameter("albedo", Color(0.75, 0.75, 0.75, col.a / 3.0));77027703rotate_gizmo[3].instantiate();7704rotate_gizmo[3]->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, arrays);7705rotate_gizmo[3]->surface_set_material(0, border_mat);7706}7707}77087709// Scale7710{7711Ref<SurfaceTool> surftool = memnew(SurfaceTool);7712surftool->begin(Mesh::PRIMITIVE_TRIANGLES);77137714// Cube arrow profile7715const int arrow_points = 6;7716Vector3 arrow[6] = {7717nivec * 0.0 + ivec * 0.0,7718nivec * 0.01 + ivec * 0.0,7719nivec * 0.01 + ivec * 1.0 * GIZMO_SCALE_OFFSET,7720nivec * 0.07 + ivec * 1.0 * GIZMO_SCALE_OFFSET,7721nivec * 0.07 + ivec * 1.11 * GIZMO_SCALE_OFFSET,7722nivec * 0.0 + ivec * 1.11 * GIZMO_SCALE_OFFSET,7723};77247725int arrow_sides = 4;77267727const real_t arrow_sides_step = Math::TAU / arrow_sides;7728for (int k = 0; k < 4; k++) {7729Basis ma(ivec, k * arrow_sides_step);7730Basis mb(ivec, (k + 1) * arrow_sides_step);77317732for (int j = 0; j < arrow_points - 1; j++) {7733Vector3 points[4] = {7734ma.xform(arrow[j]),7735mb.xform(arrow[j]),7736mb.xform(arrow[j + 1]),7737ma.xform(arrow[j + 1]),7738};7739surftool->add_vertex(points[0]);7740surftool->add_vertex(points[1]);7741surftool->add_vertex(points[2]);77427743surftool->add_vertex(points[0]);7744surftool->add_vertex(points[2]);7745surftool->add_vertex(points[3]);7746}7747}77487749surftool->set_material(mat);7750surftool->commit(scale_gizmo[i]);7751}77527753// Plane Scale7754{7755Ref<SurfaceTool> surftool = memnew(SurfaceTool);7756surftool->begin(Mesh::PRIMITIVE_TRIANGLES);77577758Vector3 vec = ivec2 - ivec3;7759Vector3 plane[4] = {7760vec * GIZMO_PLANE_DST,7761vec * GIZMO_PLANE_DST + ivec2 * GIZMO_PLANE_SIZE,7762vec * (GIZMO_PLANE_DST + GIZMO_PLANE_SIZE),7763vec * GIZMO_PLANE_DST - ivec3 * GIZMO_PLANE_SIZE7764};77657766Basis ma(ivec, Math::PI / 2);77677768Vector3 points[4] = {7769ma.xform(plane[0]),7770ma.xform(plane[1]),7771ma.xform(plane[2]),7772ma.xform(plane[3]),7773};7774surftool->add_vertex(points[0]);7775surftool->add_vertex(points[1]);7776surftool->add_vertex(points[2]);77777778surftool->add_vertex(points[0]);7779surftool->add_vertex(points[2]);7780surftool->add_vertex(points[3]);77817782Ref<StandardMaterial3D> plane_mat = memnew(StandardMaterial3D);7783plane_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);7784plane_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);7785plane_mat->set_on_top_of_alpha();7786plane_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);7787plane_mat->set_cull_mode(StandardMaterial3D::CULL_DISABLED);7788plane_mat->set_albedo(col);7789plane_gizmo_color[i] = plane_mat; // needed, so we can draw planes from both sides7790surftool->set_material(plane_mat);7791surftool->commit(scale_plane_gizmo[i]);77927793Ref<StandardMaterial3D> plane_mat_hl = plane_mat->duplicate();7794plane_mat_hl->set_albedo(col.from_hsv(col.get_h(), 0.25, 1.0, 1));7795plane_gizmo_color_hl[i] = plane_mat_hl; // needed, so we can draw planes from both sides7796}77977798// Lines to visualize transforms locked to an axis/plane7799{7800Ref<SurfaceTool> surftool = memnew(SurfaceTool);7801surftool->begin(Mesh::PRIMITIVE_LINE_STRIP);78027803Vector3 vec;7804vec[i] = 1;78057806// line extending through infinity(ish)7807surftool->add_vertex(vec * -1048576);7808surftool->add_vertex(Vector3());7809surftool->add_vertex(vec * 1048576);7810surftool->set_material(mat_hl);7811surftool->commit(axis_gizmo[i]);7812}7813}7814}78157816_generate_selection_boxes();7817}78187819void Node3DEditor::_update_gizmos_menu() {7820gizmos_menu->clear();78217822for (int i = 0; i < gizmo_plugins_by_name.size(); ++i) {7823if (!gizmo_plugins_by_name[i]->can_be_hidden()) {7824continue;7825}7826String plugin_name = gizmo_plugins_by_name[i]->get_gizmo_name();7827const int plugin_state = gizmo_plugins_by_name[i]->get_state();7828gizmos_menu->add_multistate_item(plugin_name, 3, plugin_state, i);7829const int idx = gizmos_menu->get_item_index(i);7830gizmos_menu->set_item_tooltip(7831idx,7832TTR("Click to toggle between visibility states.\n\nOpen eye: Gizmo is visible.\nClosed eye: Gizmo is hidden.\nHalf-open eye: Gizmo is also visible through opaque surfaces (\"x-ray\")."));7833switch (plugin_state) {7834case EditorNode3DGizmoPlugin::VISIBLE:7835gizmos_menu->set_item_icon(idx, get_editor_theme_icon(SNAME("GuiVisibilityVisible")));7836break;7837case EditorNode3DGizmoPlugin::ON_TOP:7838gizmos_menu->set_item_icon(idx, get_editor_theme_icon(SNAME("GuiVisibilityXray")));7839break;7840case EditorNode3DGizmoPlugin::HIDDEN:7841gizmos_menu->set_item_icon(idx, get_editor_theme_icon(SNAME("GuiVisibilityHidden")));7842break;7843}7844}7845}78467847void Node3DEditor::_update_gizmos_menu_theme() {7848for (int i = 0; i < gizmo_plugins_by_name.size(); ++i) {7849if (!gizmo_plugins_by_name[i]->can_be_hidden()) {7850continue;7851}7852const int plugin_state = gizmo_plugins_by_name[i]->get_state();7853const int idx = gizmos_menu->get_item_index(i);7854switch (plugin_state) {7855case EditorNode3DGizmoPlugin::VISIBLE:7856gizmos_menu->set_item_icon(idx, get_editor_theme_icon(SNAME("GuiVisibilityVisible")));7857break;7858case EditorNode3DGizmoPlugin::ON_TOP:7859gizmos_menu->set_item_icon(idx, get_editor_theme_icon(SNAME("GuiVisibilityXray")));7860break;7861case EditorNode3DGizmoPlugin::HIDDEN:7862gizmos_menu->set_item_icon(idx, get_editor_theme_icon(SNAME("GuiVisibilityHidden")));7863break;7864}7865}7866}78677868void Node3DEditor::_init_grid() {7869if (!grid_enabled) {7870return;7871}7872Camera3D *camera = get_editor_viewport(0)->camera;7873Vector3 camera_position = camera->get_position();7874if (camera_position == Vector3()) {7875return; // Camera3D is invalid, don't draw the grid.7876}78777878bool orthogonal = camera->get_projection() == Camera3D::PROJECTION_ORTHOGONAL;78797880static LocalVector<Color> grid_colors[3];7881static LocalVector<Vector3> grid_points[3];7882static LocalVector<Vector3> grid_normals[3];78837884for (uint32_t n = 0; n < 3; n++) {7885grid_colors[n].clear();7886grid_points[n].clear();7887grid_normals[n].clear();7888}78897890Color primary_grid_color = EDITOR_GET("editors/3d/primary_grid_color");7891Color secondary_grid_color = EDITOR_GET("editors/3d/secondary_grid_color");7892int grid_size = EDITOR_GET("editors/3d/grid_size");7893int primary_grid_steps = EDITOR_GET("editors/3d/primary_grid_steps");78947895// Which grid planes are enabled? Which should we generate?7896grid_enable[0] = grid_visible[0] = orthogonal || EDITOR_GET("editors/3d/grid_xy_plane");7897grid_enable[1] = grid_visible[1] = orthogonal || EDITOR_GET("editors/3d/grid_yz_plane");7898grid_enable[2] = grid_visible[2] = orthogonal || EDITOR_GET("editors/3d/grid_xz_plane");78997900// Offsets division_level for bigger or smaller grids.7901// Default value is -0.2. -1.0 gives Blender-like behavior, 0.5 gives huge grids.7902real_t division_level_bias = EDITOR_GET("editors/3d/grid_division_level_bias");7903// Default largest grid size is 8^2 (default value is 2) when primary_grid_steps is 8 (64m apart, so primary grid lines are 512m apart).7904int division_level_max = EDITOR_GET("editors/3d/grid_division_level_max");7905// Default smallest grid size is 8^0 (default value is 0) when primary_grid_steps is 8.7906int division_level_min = EDITOR_GET("editors/3d/grid_division_level_min");7907ERR_FAIL_COND_MSG(division_level_max < division_level_min, "The 3D grid's maximum division level cannot be lower than its minimum division level.");79087909if (primary_grid_steps != 10) { // Log10 of 10 is 1.7910// Change of base rule, divide by ln(10).7911real_t div = Math::log((real_t)primary_grid_steps) / (real_t)2.302585092994045901094;7912// Truncation (towards zero) is intentional.7913division_level_max = (int)(division_level_max / div);7914division_level_min = (int)(division_level_min / div);7915}79167917for (int a = 0; a < 3; a++) {7918if (!grid_enable[a]) {7919continue; // If this grid plane is disabled, skip generation.7920}7921int b = (a + 1) % 3;7922int c = (a + 2) % 3;79237924Vector3 normal;7925normal[c] = 1.0;79267927real_t camera_distance = Math::abs(camera_position[c]);79287929if (orthogonal) {7930camera_distance = camera->get_size() / 2.0;7931Vector3 camera_direction = -camera->get_global_transform().get_basis().get_column(2);7932Plane grid_plane = Plane(normal);7933Vector3 intersection;7934if (grid_plane.intersects_ray(camera_position, camera_direction, &intersection)) {7935camera_position = intersection;7936}7937}79387939real_t division_level = Math::log(Math::abs(camera_distance)) / Math::log((double)primary_grid_steps) + division_level_bias;79407941real_t clamped_division_level = CLAMP(division_level, division_level_min, division_level_max);7942real_t division_level_floored = Math::floor(clamped_division_level);7943real_t division_level_decimals = clamped_division_level - division_level_floored;79447945real_t small_step_size = Math::pow(primary_grid_steps, division_level_floored);7946real_t large_step_size = small_step_size * primary_grid_steps;7947real_t center_a = large_step_size * (int)(camera_position[a] / large_step_size);7948real_t center_b = large_step_size * (int)(camera_position[b] / large_step_size);79497950real_t bgn_a = center_a - grid_size * small_step_size;7951real_t end_a = center_a + grid_size * small_step_size;7952real_t bgn_b = center_b - grid_size * small_step_size;7953real_t end_b = center_b + grid_size * small_step_size;79547955real_t fade_size = Math::pow(primary_grid_steps, division_level - 1.0);7956real_t min_fade_size = Math::pow(primary_grid_steps, float(division_level_min));7957real_t max_fade_size = Math::pow(primary_grid_steps, float(division_level_max));7958fade_size = CLAMP(fade_size, min_fade_size, max_fade_size);79597960real_t grid_fade_size = (grid_size - primary_grid_steps) * fade_size;7961grid_mat[c]->set_shader_parameter("grid_size", grid_fade_size);7962grid_mat[c]->set_shader_parameter("orthogonal", orthogonal);79637964LocalVector<Vector3> &ref_grid = grid_points[c];7965LocalVector<Vector3> &ref_grid_normals = grid_normals[c];7966LocalVector<Color> &ref_grid_colors = grid_colors[c];79677968// Count our elements same as code below it.7969int expected_size = 0;7970for (int i = -grid_size; i <= grid_size; i++) {7971const real_t position_a = center_a + i * small_step_size;7972const real_t position_b = center_b + i * small_step_size;79737974// Don't draw lines over the origin if it's enabled.7975if (!(origin_enabled && Math::is_zero_approx(position_a))) {7976expected_size += 2;7977}79787979if (!(origin_enabled && Math::is_zero_approx(position_b))) {7980expected_size += 2;7981}7982}79837984int idx = 0;7985ref_grid.resize(expected_size);7986ref_grid_normals.resize(expected_size);7987ref_grid_colors.resize(expected_size);79887989// In each iteration of this loop, draw one line in each direction (so two lines per loop, in each if statement).7990for (int i = -grid_size; i <= grid_size; i++) {7991Color line_color;7992// Is this a primary line? Set the appropriate color.7993if (i % primary_grid_steps == 0) {7994line_color = primary_grid_color.lerp(secondary_grid_color, division_level_decimals);7995} else {7996line_color = secondary_grid_color;7997line_color.a = line_color.a * (1 - division_level_decimals);7998}79998000real_t position_a = center_a + i * small_step_size;8001real_t position_b = center_b + i * small_step_size;80028003// Don't draw lines over the origin if it's enabled.8004if (!(origin_enabled && Math::is_zero_approx(position_a))) {8005Vector3 line_bgn;8006Vector3 line_end;8007line_bgn[a] = position_a;8008line_end[a] = position_a;8009line_bgn[b] = bgn_b;8010line_end[b] = end_b;8011ref_grid[idx] = line_bgn;8012ref_grid[idx + 1] = line_end;8013ref_grid_colors[idx] = line_color;8014ref_grid_colors[idx + 1] = line_color;8015ref_grid_normals[idx] = normal;8016ref_grid_normals[idx + 1] = normal;8017idx += 2;8018}80198020if (!(origin_enabled && Math::is_zero_approx(position_b))) {8021Vector3 line_bgn;8022Vector3 line_end;8023line_bgn[b] = position_b;8024line_end[b] = position_b;8025line_bgn[a] = bgn_a;8026line_end[a] = end_a;8027ref_grid[idx] = line_bgn;8028ref_grid[idx + 1] = line_end;8029ref_grid_colors[idx] = line_color;8030ref_grid_colors[idx + 1] = line_color;8031ref_grid_normals[idx] = normal;8032ref_grid_normals[idx + 1] = normal;8033idx += 2;8034}8035}80368037// Create a mesh from the pushed vector points and colors.8038grid[c] = RenderingServer::get_singleton()->mesh_create();8039Array d;8040d.resize(RS::ARRAY_MAX);8041d[RenderingServer::ARRAY_VERTEX] = (Vector<Vector3>)grid_points[c];8042d[RenderingServer::ARRAY_COLOR] = (Vector<Color>)grid_colors[c];8043d[RenderingServer::ARRAY_NORMAL] = (Vector<Vector3>)grid_normals[c];8044RenderingServer::get_singleton()->mesh_add_surface_from_arrays(grid[c], RenderingServer::PRIMITIVE_LINES, d);8045RenderingServer::get_singleton()->mesh_surface_set_material(grid[c], 0, grid_mat[c]->get_rid());8046grid_instance[c] = RenderingServer::get_singleton()->instance_create2(grid[c], get_tree()->get_root()->get_world_3d()->get_scenario());80478048// Yes, the end of this line is supposed to be a.8049RenderingServer::get_singleton()->instance_set_visible(grid_instance[c], grid_visible[a]);8050RenderingServer::get_singleton()->instance_geometry_set_cast_shadows_setting(grid_instance[c], RS::SHADOW_CASTING_SETTING_OFF);8051RS::get_singleton()->instance_set_layer_mask(grid_instance[c], 1 << Node3DEditorViewport::GIZMO_GRID_LAYER);8052RS::get_singleton()->instance_geometry_set_flag(grid_instance[c], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);8053RS::get_singleton()->instance_geometry_set_flag(grid_instance[c], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);8054}8055}80568057void Node3DEditor::_finish_indicators() {8058RenderingServer::get_singleton()->free(origin_instance);8059RenderingServer::get_singleton()->free(origin_multimesh);8060RenderingServer::get_singleton()->free(origin_mesh);80618062_finish_grid();8063}80648065void Node3DEditor::_finish_grid() {8066for (int i = 0; i < 3; i++) {8067RenderingServer::get_singleton()->free(grid_instance[i]);8068RenderingServer::get_singleton()->free(grid[i]);8069}8070}80718072void Node3DEditor::update_grid() {8073const Camera3D::ProjectionType current_projection = viewports[0]->camera->get_projection();80748075if (current_projection != grid_camera_last_update_perspective) {8076grid_init_draw = false; // redraw8077grid_camera_last_update_perspective = current_projection;8078}80798080// Gets a orthogonal or perspective position correctly (for the grid comparison)8081const Vector3 camera_position = get_editor_viewport(0)->camera->get_position();80828083if (!grid_init_draw || grid_camera_last_update_position.distance_squared_to(camera_position) >= 100.0f) {8084_finish_grid();8085_init_grid();8086grid_init_draw = true;8087grid_camera_last_update_position = camera_position;8088}8089}80908091void Node3DEditor::_selection_changed() {8092_refresh_menu_icons();80938094const HashMap<Node *, Object *> &selection = editor_selection->get_selection();80958096for (const KeyValue<Node *, Object *> &E : selection) {8097Node3D *sp = Object::cast_to<Node3D>(E.key);8098if (!sp) {8099continue;8100}81018102Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);8103if (!se) {8104continue;8105}81068107if (sp == editor_selection->get_top_selected_node_list().back()->get()) {8108RenderingServer::get_singleton()->instance_set_base(se->sbox_instance, active_selection_box->get_rid());8109RenderingServer::get_singleton()->instance_set_base(se->sbox_instance_xray, active_selection_box_xray->get_rid());8110RenderingServer::get_singleton()->instance_set_base(se->sbox_instance_offset, active_selection_box->get_rid());8111RenderingServer::get_singleton()->instance_set_base(se->sbox_instance_xray_offset, active_selection_box_xray->get_rid());8112} else {8113RenderingServer::get_singleton()->instance_set_base(se->sbox_instance, selection_box->get_rid());8114RenderingServer::get_singleton()->instance_set_base(se->sbox_instance_xray, selection_box_xray->get_rid());8115RenderingServer::get_singleton()->instance_set_base(se->sbox_instance_offset, selection_box->get_rid());8116RenderingServer::get_singleton()->instance_set_base(se->sbox_instance_xray_offset, selection_box_xray->get_rid());8117}8118}81198120if (selected && editor_selection->get_top_selected_node_list().size() != 1) {8121Vector<Ref<Node3DGizmo>> gizmos = selected->get_gizmos();8122for (int i = 0; i < gizmos.size(); i++) {8123Ref<EditorNode3DGizmo> seg = gizmos[i];8124if (seg.is_null()) {8125continue;8126}8127seg->set_selected(false);8128}81298130Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected);8131if (se) {8132se->gizmo.unref();8133se->subgizmos.clear();8134}8135selected->update_gizmos();8136selected = nullptr;8137}81388139// Ensure gizmo updates are performed when the selection changes8140// outside of the 3D view (see GH-106713).8141if (!is_visible()) {8142const List<Node *> &top_selected = editor_selection->get_top_selected_node_list();8143if (top_selected.size() == 1) {8144Node3D *new_selected = Object::cast_to<Node3D>(top_selected.back()->get());8145if (new_selected != selected) {8146gizmos_dirty = true;8147}8148}8149}81508151update_transform_gizmo();8152}81538154void Node3DEditor::refresh_dirty_gizmos() {8155if (!gizmos_dirty) {8156return;8157}81588159const List<Node *> &top_selected = editor_selection->get_top_selected_node_list();8160if (top_selected.size() == 1) {8161Node3D *new_selected = Object::cast_to<Node3D>(top_selected.back()->get());8162if (new_selected != selected) {8163edit(new_selected);8164}8165}8166gizmos_dirty = false;8167}81688169void Node3DEditor::_refresh_menu_icons() {8170bool all_locked = true;8171bool all_grouped = true;8172bool has_node3d_item = false;81738174const List<Node *> &selection = editor_selection->get_top_selected_node_list();81758176if (selection.is_empty()) {8177all_locked = false;8178all_grouped = false;8179} else {8180for (Node *E : selection) {8181Node3D *node = Object::cast_to<Node3D>(E);8182if (node) {8183if (all_locked && !node->has_meta("_edit_lock_")) {8184all_locked = false;8185}8186if (all_grouped && !node->has_meta("_edit_group_")) {8187all_grouped = false;8188}8189has_node3d_item = true;8190}8191if (!all_locked && !all_grouped) {8192break;8193}8194}8195}81968197all_locked = all_locked && has_node3d_item;8198all_grouped = all_grouped && has_node3d_item;81998200tool_button[TOOL_LOCK_SELECTED]->set_visible(!all_locked);8201tool_button[TOOL_LOCK_SELECTED]->set_disabled(!has_node3d_item);8202tool_button[TOOL_UNLOCK_SELECTED]->set_visible(all_locked);8203tool_button[TOOL_UNLOCK_SELECTED]->set_disabled(!has_node3d_item);82048205tool_button[TOOL_GROUP_SELECTED]->set_visible(!all_grouped);8206tool_button[TOOL_GROUP_SELECTED]->set_disabled(!has_node3d_item);8207tool_button[TOOL_UNGROUP_SELECTED]->set_visible(all_grouped);8208tool_button[TOOL_UNGROUP_SELECTED]->set_disabled(!has_node3d_item);8209}82108211template <typename T>8212HashSet<T *> _get_child_nodes(Node *parent_node) {8213HashSet<T *> nodes = HashSet<T *>();8214T *node = Node::cast_to<T>(parent_node);8215if (node) {8216nodes.insert(node);8217}82188219for (int i = 0; i < parent_node->get_child_count(); i++) {8220Node *child_node = parent_node->get_child(i);8221HashSet<T *> child_nodes = _get_child_nodes<T>(child_node);8222for (T *I : child_nodes) {8223nodes.insert(I);8224}8225}82268227return nodes;8228}82298230HashSet<RID> _get_physics_bodies_rid(Node *node) {8231HashSet<RID> rids = HashSet<RID>();8232PhysicsBody3D *pb = Node::cast_to<PhysicsBody3D>(node);8233if (pb) {8234rids.insert(pb->get_rid());8235}8236HashSet<PhysicsBody3D *> child_nodes = _get_child_nodes<PhysicsBody3D>(node);8237for (const PhysicsBody3D *I : child_nodes) {8238rids.insert(I->get_rid());8239}82408241return rids;8242}82438244void Node3DEditor::snap_selected_nodes_to_floor() {8245do_snap_selected_nodes_to_floor = true;8246}82478248void Node3DEditor::_snap_selected_nodes_to_floor() {8249const List<Node *> &selection = editor_selection->get_top_selected_node_list();8250Dictionary snap_data;82518252for (Node *E : selection) {8253Node3D *sp = Object::cast_to<Node3D>(E);8254if (sp) {8255Vector3 from;8256Vector3 position_offset;82578258// Priorities for snapping to floor are CollisionShapes, VisualInstances and then origin8259HashSet<VisualInstance3D *> vi = _get_child_nodes<VisualInstance3D>(sp);8260HashSet<CollisionShape3D *> cs = _get_child_nodes<CollisionShape3D>(sp);8261bool found_valid_shape = false;82628263if (cs.size()) {8264AABB aabb;8265HashSet<CollisionShape3D *>::Iterator I = cs.begin();8266if ((*I)->get_shape().is_valid()) {8267CollisionShape3D *collision_shape = *cs.begin();8268aabb = collision_shape->get_global_transform().xform(collision_shape->get_shape()->get_debug_mesh()->get_aabb());8269found_valid_shape = true;8270}82718272for (++I; I; ++I) {8273CollisionShape3D *col_shape = *I;8274if (col_shape->get_shape().is_valid()) {8275aabb.merge_with(col_shape->get_global_transform().xform(col_shape->get_shape()->get_debug_mesh()->get_aabb()));8276found_valid_shape = true;8277}8278}8279if (found_valid_shape) {8280Vector3 size = aabb.size * Vector3(0.5, 0.0, 0.5);8281from = aabb.position + size;8282position_offset.y = from.y - sp->get_global_transform().origin.y;8283}8284}8285if (!found_valid_shape && vi.size()) {8286VisualInstance3D *begin = *vi.begin();8287AABB aabb = begin->get_global_transform().xform(begin->get_aabb());8288for (const VisualInstance3D *I : vi) {8289aabb.merge_with(I->get_global_transform().xform(I->get_aabb()));8290}8291Vector3 size = aabb.size * Vector3(0.5, 0.0, 0.5);8292from = aabb.position + size;8293position_offset.y = from.y - sp->get_global_transform().origin.y;8294} else if (!found_valid_shape) {8295from = sp->get_global_transform().origin;8296}82978298// We add a bit of margin to the from position to avoid it from snapping8299// when the spatial is already on a floor and there's another floor under8300// it8301from = from + Vector3(0.0, 1, 0.0);83028303Dictionary d;83048305d["from"] = from;8306d["position_offset"] = position_offset;8307snap_data[sp] = d;8308}8309}83108311PhysicsDirectSpaceState3D *ss = get_tree()->get_root()->get_world_3d()->get_direct_space_state();8312PhysicsDirectSpaceState3D::RayResult result;83138314// The maximum height an object can travel to be snapped8315const float max_snap_height = 500.0;83168317// Will be set to `true` if at least one node from the selection was successfully snapped8318bool snapped_to_floor = false;83198320if (!snap_data.is_empty()) {8321// For snapping to be performed, there must be solid geometry under at least one of the selected nodes.8322// We need to check this before snapping to register the undo/redo action only if needed.8323for (const KeyValue<Variant, Variant> &kv : snap_data) {8324Node *node = Object::cast_to<Node>(kv.key);8325Node3D *sp = Object::cast_to<Node3D>(node);8326Dictionary d = kv.value;8327Vector3 from = d["from"];8328Vector3 to = from - Vector3(0.0, max_snap_height, 0.0);8329HashSet<RID> excluded = _get_physics_bodies_rid(sp);83308331PhysicsDirectSpaceState3D::RayParameters ray_params;8332ray_params.from = from;8333ray_params.to = to;8334ray_params.exclude = excluded;83358336if (ss->intersect_ray(ray_params, result)) {8337snapped_to_floor = true;8338}8339}83408341if (snapped_to_floor) {8342EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();8343undo_redo->create_action(TTR("Snap Nodes to Floor"));83448345// Perform snapping if at least one node can be snapped8346for (const KeyValue<Variant, Variant> &kv : snap_data) {8347Node *node = Object::cast_to<Node>(kv.key);8348Node3D *sp = Object::cast_to<Node3D>(node);8349Dictionary d = kv.value;8350Vector3 from = d["from"];8351Vector3 to = from - Vector3(0.0, max_snap_height, 0.0);8352HashSet<RID> excluded = _get_physics_bodies_rid(sp);83538354PhysicsDirectSpaceState3D::RayParameters ray_params;8355ray_params.from = from;8356ray_params.to = to;8357ray_params.exclude = excluded;83588359if (ss->intersect_ray(ray_params, result)) {8360Vector3 position_offset = d["position_offset"];8361Transform3D new_transform = sp->get_global_transform();83628363new_transform.origin.y = result.position.y;8364new_transform.origin = new_transform.origin - position_offset;83658366Node3D *parent = sp->get_parent_node_3d();8367Transform3D new_local_xform = parent ? parent->get_global_transform().affine_inverse() * new_transform : new_transform;8368undo_redo->add_do_method(sp, "set_transform", new_local_xform);8369undo_redo->add_undo_method(sp, "set_transform", sp->get_transform());8370}8371}83728373undo_redo->commit_action();8374} else {8375EditorNode::get_singleton()->show_warning(TTR("Couldn't find a solid floor to snap the selection to."));8376}8377}8378}83798380void Node3DEditor::shortcut_input(const Ref<InputEvent> &p_event) {8381ERR_FAIL_COND(p_event.is_null());83828383if (!is_visible_in_tree()) {8384return;8385}83868387snap_key_enabled = Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL);8388}83898390void Node3DEditor::_sun_environ_settings_pressed() {8391Vector2 pos = sun_environ_settings->get_screen_position() + sun_environ_settings->get_size();8392sun_environ_popup->set_position(pos - Vector2(sun_environ_popup->get_contents_minimum_size().width / 2, 0));8393sun_environ_popup->reset_size();8394sun_environ_popup->popup();8395// Grabbing the focus is required for Shift modifier checking to be functional8396// (when the Add sun/environment buttons are pressed).8397sun_environ_popup->grab_focus();8398}83998400void Node3DEditor::_add_sun_to_scene(bool p_already_added_environment) {8401sun_environ_popup->hide();84028403if (!p_already_added_environment && world_env_count == 0 && Input::get_singleton()->is_key_pressed(Key::SHIFT)) {8404// Prevent infinite feedback loop between the sun and environment methods.8405_add_environment_to_scene(true);8406}84078408Node *base = get_tree()->get_edited_scene_root();8409if (!base) {8410// Create a root node so we can add child nodes to it.8411SceneTreeDock::get_singleton()->add_root_node(memnew(Node3D));8412base = get_tree()->get_edited_scene_root();8413}8414ERR_FAIL_NULL(base);8415Node *new_sun = preview_sun->duplicate();84168417EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();8418undo_redo->create_action(TTR("Add Preview Sun to Scene"));8419undo_redo->add_do_method(base, "add_child", new_sun, true);8420// Move to the beginning of the scene tree since more "global" nodes8421// generally look better when placed at the top.8422undo_redo->add_do_method(base, "move_child", new_sun, 0);8423undo_redo->add_do_method(new_sun, "set_owner", base);8424undo_redo->add_undo_method(base, "remove_child", new_sun);8425undo_redo->add_do_reference(new_sun);8426undo_redo->commit_action();8427}84288429void Node3DEditor::_add_environment_to_scene(bool p_already_added_sun) {8430sun_environ_popup->hide();84318432if (!p_already_added_sun && directional_light_count == 0 && Input::get_singleton()->is_key_pressed(Key::SHIFT)) {8433// Prevent infinite feedback loop between the sun and environment methods.8434_add_sun_to_scene(true);8435}84368437Node *base = get_tree()->get_edited_scene_root();8438if (!base) {8439// Create a root node so we can add child nodes to it.8440SceneTreeDock::get_singleton()->add_root_node(memnew(Node3D));8441base = get_tree()->get_edited_scene_root();8442}8443ERR_FAIL_NULL(base);84448445WorldEnvironment *new_env = memnew(WorldEnvironment);8446new_env->set_environment(preview_environment->get_environment()->duplicate(true));8447if (GLOBAL_GET("rendering/lights_and_shadows/use_physical_light_units")) {8448new_env->set_camera_attributes(preview_environment->get_camera_attributes()->duplicate(true));8449}84508451EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();8452undo_redo->create_action(TTR("Add Preview Environment to Scene"));8453undo_redo->add_do_method(base, "add_child", new_env, true);8454// Move to the beginning of the scene tree since more "global" nodes8455// generally look better when placed at the top.8456undo_redo->add_do_method(base, "move_child", new_env, 0);8457undo_redo->add_do_method(new_env, "set_owner", base);8458undo_redo->add_undo_method(base, "remove_child", new_env);8459undo_redo->add_do_reference(new_env);8460undo_redo->commit_action();8461}84628463void Node3DEditor::_update_theme() {8464tool_button[TOOL_MODE_SELECT]->set_button_icon(get_editor_theme_icon(SNAME("ToolSelect")));8465tool_button[TOOL_MODE_MOVE]->set_button_icon(get_editor_theme_icon(SNAME("ToolMove")));8466tool_button[TOOL_MODE_ROTATE]->set_button_icon(get_editor_theme_icon(SNAME("ToolRotate")));8467tool_button[TOOL_MODE_SCALE]->set_button_icon(get_editor_theme_icon(SNAME("ToolScale")));8468tool_button[TOOL_MODE_LIST_SELECT]->set_button_icon(get_editor_theme_icon(SNAME("ListSelect")));8469tool_button[TOOL_LOCK_SELECTED]->set_button_icon(get_editor_theme_icon(SNAME("Lock")));8470tool_button[TOOL_UNLOCK_SELECTED]->set_button_icon(get_editor_theme_icon(SNAME("Unlock")));8471tool_button[TOOL_GROUP_SELECTED]->set_button_icon(get_editor_theme_icon(SNAME("Group")));8472tool_button[TOOL_UNGROUP_SELECTED]->set_button_icon(get_editor_theme_icon(SNAME("Ungroup")));8473tool_button[TOOL_RULER]->set_button_icon(get_editor_theme_icon(SNAME("Ruler")));84748475tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_button_icon(get_editor_theme_icon(SNAME("Object")));8476tool_option_button[TOOL_OPT_USE_SNAP]->set_button_icon(get_editor_theme_icon(SNAME("Snap")));84778478view_layout_menu->get_popup()->set_item_icon(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), get_editor_theme_icon(SNAME("Panels1")));8479view_layout_menu->get_popup()->set_item_icon(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), get_editor_theme_icon(SNAME("Panels2")));8480view_layout_menu->get_popup()->set_item_icon(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT), get_editor_theme_icon(SNAME("Panels2Alt")));8481view_layout_menu->get_popup()->set_item_icon(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS), get_editor_theme_icon(SNAME("Panels3")));8482view_layout_menu->get_popup()->set_item_icon(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT), get_editor_theme_icon(SNAME("Panels3Alt")));8483view_layout_menu->get_popup()->set_item_icon(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS), get_editor_theme_icon(SNAME("Panels4")));84848485sun_button->set_button_icon(get_editor_theme_icon(SNAME("PreviewSun")));8486environ_button->set_button_icon(get_editor_theme_icon(SNAME("PreviewEnvironment")));8487sun_environ_settings->set_button_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl")));84888489sun_title->add_theme_font_override(SceneStringName(font), get_theme_font(SNAME("title_font"), SNAME("Window")));8490environ_title->add_theme_font_override(SceneStringName(font), get_theme_font(SNAME("title_font"), SNAME("Window")));84918492sun_color->set_custom_minimum_size(Size2(0, get_theme_constant(SNAME("color_picker_button_height"), EditorStringName(Editor))));8493environ_sky_color->set_custom_minimum_size(Size2(0, get_theme_constant(SNAME("color_picker_button_height"), EditorStringName(Editor))));8494environ_ground_color->set_custom_minimum_size(Size2(0, get_theme_constant(SNAME("color_picker_button_height"), EditorStringName(Editor))));84958496context_toolbar_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("ContextualToolbar"), EditorStringName(EditorStyles)));8497}84988499void Node3DEditor::_notification(int p_what) {8500switch (p_what) {8501case NOTIFICATION_TRANSLATION_CHANGED: {8502tool_button[TOOL_MODE_SELECT]->set_tooltip_text(keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL) + TTR("Drag: Rotate selected node around pivot.") + "\n" + TTR("Alt+RMB: Show list of all nodes at position clicked, including locked.") + "\n" + TTR("(Available in all modes.)"));8503tool_button[TOOL_MODE_MOVE]->set_tooltip_text(keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL) + TTR("Drag: Use snap.") + "\n" + TTR("Alt+RMB: Show list of all nodes at position clicked, including locked."));8504tool_button[TOOL_MODE_ROTATE]->set_tooltip_text(keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL) + TTR("Drag: Use snap.") + "\n" + TTR("Alt+RMB: Show list of all nodes at position clicked, including locked."));8505tool_button[TOOL_MODE_SCALE]->set_tooltip_text(keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL) + TTR("Drag: Use snap.") + "\n" + TTR("Alt+RMB: Show list of all nodes at position clicked, including locked."));8506_update_gizmos_menu();8507} break;85088509case NOTIFICATION_READY: {8510_menu_item_pressed(MENU_VIEW_USE_1_VIEWPORT);85118512_refresh_menu_icons();85138514get_tree()->connect("node_removed", callable_mp(this, &Node3DEditor::_node_removed));8515get_tree()->connect("node_added", callable_mp(this, &Node3DEditor::_node_added));8516SceneTreeDock::get_singleton()->get_tree_editor()->connect("node_changed", callable_mp(this, &Node3DEditor::_refresh_menu_icons));8517editor_selection->connect("selection_changed", callable_mp(this, &Node3DEditor::_selection_changed));85188519_update_preview_environment();85208521sun_state->set_custom_minimum_size(sun_vb->get_combined_minimum_size());8522environ_state->set_custom_minimum_size(environ_vb->get_combined_minimum_size());85238524ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &Node3DEditor::update_all_gizmos).bind(Variant()));8525} break;85268527case NOTIFICATION_ACCESSIBILITY_UPDATE: {8528RID ae = get_accessibility_element();8529ERR_FAIL_COND(ae.is_null());85308531//TODO8532DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_STATIC_TEXT);8533DisplayServer::get_singleton()->accessibility_update_set_value(ae, TTR(vformat("The %s is not accessible at this time.", "3D editor")));8534} break;85358536case NOTIFICATION_ENTER_TREE: {8537_update_theme();8538_register_all_gizmos();8539_init_indicators();8540update_all_gizmos();8541} break;85428543case NOTIFICATION_EXIT_TREE: {8544_finish_indicators();8545} break;85468547case NOTIFICATION_THEME_CHANGED: {8548_update_theme();8549_update_gizmos_menu_theme();8550sun_title->add_theme_font_override(SceneStringName(font), get_theme_font(SNAME("title_font"), SNAME("Window")));8551environ_title->add_theme_font_override(SceneStringName(font), get_theme_font(SNAME("title_font"), SNAME("Window")));8552} break;85538554case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {8555if (EditorSettings::get_singleton()->check_changed_settings_in_group("editors/3d")) {8556const Color selection_box_color = EDITOR_GET("editors/3d/selection_box_color");8557const Color active_selection_box_color = EDITOR_GET("editors/3d/active_selection_box_color");85588559if (selection_box_color != selection_box_mat->get_albedo()) {8560selection_box_mat->set_albedo(selection_box_color);8561selection_box_mat_xray->set_albedo(selection_box_color * Color(1, 1, 1, 0.15));8562}85638564if (active_selection_box_color != active_selection_box_mat->get_albedo()) {8565active_selection_box_mat->set_albedo(active_selection_box_color);8566active_selection_box_mat_xray->set_albedo(active_selection_box_color * Color(1, 1, 1, 0.15));8567}85688569// Update grid color by rebuilding grid.8570_finish_grid();8571_init_grid();8572}8573} break;85748575case NOTIFICATION_PHYSICS_PROCESS: {8576if (do_snap_selected_nodes_to_floor) {8577_snap_selected_nodes_to_floor();8578do_snap_selected_nodes_to_floor = false;8579}8580}8581}8582}85838584bool Node3DEditor::is_subgizmo_selected(int p_id) {8585Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr;8586if (se) {8587return se->subgizmos.has(p_id);8588}8589return false;8590}85918592bool Node3DEditor::is_current_selected_gizmo(const EditorNode3DGizmo *p_gizmo) {8593Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr;8594if (se) {8595return se->gizmo == p_gizmo;8596}8597return false;8598}85998600Vector<int> Node3DEditor::get_subgizmo_selection() {8601Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr;86028603Vector<int> ret;8604if (se) {8605for (const KeyValue<int, Transform3D> &E : se->subgizmos) {8606ret.push_back(E.key);8607}8608}8609return ret;8610}86118612void Node3DEditor::clear_subgizmo_selection(Object *p_obj) {8613_clear_subgizmo_selection(p_obj);8614}86158616void Node3DEditor::add_control_to_menu_panel(Control *p_control) {8617ERR_FAIL_NULL(p_control);8618ERR_FAIL_COND(p_control->get_parent());86198620VSeparator *sep = memnew(VSeparator);8621context_toolbar_hbox->add_child(sep);8622context_toolbar_hbox->add_child(p_control);8623context_toolbar_separators[p_control] = sep;86248625p_control->connect(SceneStringName(visibility_changed), callable_mp(this, &Node3DEditor::_update_context_toolbar));86268627_update_context_toolbar();8628}86298630void Node3DEditor::remove_control_from_menu_panel(Control *p_control) {8631ERR_FAIL_NULL(p_control);8632ERR_FAIL_COND(p_control->get_parent() != context_toolbar_hbox);86338634p_control->disconnect(SceneStringName(visibility_changed), callable_mp(this, &Node3DEditor::_update_context_toolbar));86358636VSeparator *sep = context_toolbar_separators[p_control];8637context_toolbar_hbox->remove_child(sep);8638context_toolbar_hbox->remove_child(p_control);8639context_toolbar_separators.erase(p_control);8640memdelete(sep);86418642_update_context_toolbar();8643}86448645void Node3DEditor::_update_context_toolbar() {8646bool has_visible = false;8647bool first_visible = false;86488649for (int i = 0; i < context_toolbar_hbox->get_child_count(); i++) {8650Control *child = Object::cast_to<Control>(context_toolbar_hbox->get_child(i));8651if (!child || !context_toolbar_separators.has(child)) {8652continue;8653}8654if (child->is_visible()) {8655first_visible = !has_visible;8656has_visible = true;8657}86588659VSeparator *sep = context_toolbar_separators[child];8660sep->set_visible(!first_visible && child->is_visible());8661}86628663context_toolbar_panel->set_visible(has_visible);8664}86658666void Node3DEditor::set_can_preview(Camera3D *p_preview) {8667for (int i = 0; i < 4; i++) {8668viewports[i]->set_can_preview(p_preview);8669}8670}86718672VSplitContainer *Node3DEditor::get_shader_split() {8673return shader_split;8674}86758676Node3DEditorViewport *Node3DEditor::get_last_used_viewport() {8677return viewports[last_used_viewport];8678}86798680void Node3DEditor::add_control_to_left_panel(Control *p_control) {8681left_panel_split->add_child(p_control);8682left_panel_split->move_child(p_control, 0);8683}86848685void Node3DEditor::add_control_to_right_panel(Control *p_control) {8686right_panel_split->add_child(p_control);8687right_panel_split->move_child(p_control, 1);8688}86898690void Node3DEditor::remove_control_from_left_panel(Control *p_control) {8691left_panel_split->remove_child(p_control);8692}86938694void Node3DEditor::remove_control_from_right_panel(Control *p_control) {8695right_panel_split->remove_child(p_control);8696}86978698void Node3DEditor::move_control_to_left_panel(Control *p_control) {8699ERR_FAIL_NULL(p_control);8700if (p_control->get_parent() == left_panel_split) {8701return;8702}87038704ERR_FAIL_COND(p_control->get_parent() != right_panel_split);8705right_panel_split->remove_child(p_control);87068707add_control_to_left_panel(p_control);8708}87098710void Node3DEditor::move_control_to_right_panel(Control *p_control) {8711ERR_FAIL_NULL(p_control);8712if (p_control->get_parent() == right_panel_split) {8713return;8714}87158716ERR_FAIL_COND(p_control->get_parent() != left_panel_split);8717left_panel_split->remove_child(p_control);87188719add_control_to_right_panel(p_control);8720}87218722void Node3DEditor::_request_gizmo(Object *p_obj) {8723Node3D *sp = Object::cast_to<Node3D>(p_obj);8724if (!sp) {8725return;8726}87278728bool is_selected = (sp == selected);87298730Node *edited_scene = EditorNode::get_singleton()->get_edited_scene();8731if (edited_scene && (sp == edited_scene || (sp->get_owner() && edited_scene->is_ancestor_of(sp)))) {8732for (int i = 0; i < gizmo_plugins_by_priority.size(); ++i) {8733Ref<EditorNode3DGizmo> seg = gizmo_plugins_by_priority.write[i]->get_gizmo(sp);87348735if (seg.is_valid()) {8736sp->add_gizmo(seg);87378738if (is_selected != seg->is_selected()) {8739seg->set_selected(is_selected);8740}8741}8742}8743if (!sp->get_gizmos().is_empty()) {8744sp->update_gizmos();8745}8746}8747}87488749void Node3DEditor::_request_gizmo_for_id(ObjectID p_id) {8750Node3D *node = ObjectDB::get_instance<Node3D>(p_id);8751if (node) {8752_request_gizmo(node);8753}8754}87558756void Node3DEditor::_set_subgizmo_selection(Object *p_obj, Ref<Node3DGizmo> p_gizmo, int p_id, Transform3D p_transform) {8757if (p_id == -1) {8758_clear_subgizmo_selection(p_obj);8759return;8760}87618762Node3D *sp = nullptr;8763if (p_obj) {8764sp = Object::cast_to<Node3D>(p_obj);8765} else {8766sp = selected;8767}87688769if (!sp) {8770return;8771}87728773Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);8774if (se) {8775se->subgizmos.clear();8776se->subgizmos.insert(p_id, p_transform);8777se->gizmo = p_gizmo;8778sp->update_gizmos();8779update_transform_gizmo();8780}8781}87828783void Node3DEditor::_clear_subgizmo_selection(Object *p_obj) {8784Node3D *sp = nullptr;8785if (p_obj) {8786sp = Object::cast_to<Node3D>(p_obj);8787} else {8788sp = selected;8789}87908791if (!sp) {8792return;8793}87948795Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);8796if (se) {8797se->subgizmos.clear();8798se->gizmo.unref();8799sp->update_gizmos();8800update_transform_gizmo();8801}8802}88038804void Node3DEditor::_toggle_maximize_view(Object *p_viewport) {8805if (!p_viewport) {8806return;8807}8808Node3DEditorViewport *current_viewport = Object::cast_to<Node3DEditorViewport>(p_viewport);8809if (!current_viewport) {8810return;8811}88128813int index = -1;8814bool maximized = false;8815for (int i = 0; i < 4; i++) {8816if (viewports[i] == current_viewport) {8817index = i;8818if (current_viewport->get_global_rect() == viewport_base->get_global_rect()) {8819maximized = true;8820}8821break;8822}8823}8824if (index == -1) {8825return;8826}88278828if (!maximized) {8829for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) {8830if (i == (uint32_t)index) {8831viewports[i]->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);8832} else {8833viewports[i]->hide();8834}8835}8836} else {8837for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) {8838viewports[i]->show();8839}88408841if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT))) {8842_menu_item_pressed(MENU_VIEW_USE_1_VIEWPORT);8843} else if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS))) {8844_menu_item_pressed(MENU_VIEW_USE_2_VIEWPORTS);8845} else if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT))) {8846_menu_item_pressed(MENU_VIEW_USE_2_VIEWPORTS_ALT);8847} else if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS))) {8848_menu_item_pressed(MENU_VIEW_USE_3_VIEWPORTS);8849} else if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT))) {8850_menu_item_pressed(MENU_VIEW_USE_3_VIEWPORTS_ALT);8851} else if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS))) {8852_menu_item_pressed(MENU_VIEW_USE_4_VIEWPORTS);8853}8854}8855}88568857void Node3DEditor::_viewport_clicked(int p_viewport_idx) {8858last_used_viewport = p_viewport_idx;8859}88608861void Node3DEditor::_node_added(Node *p_node) {8862if (EditorNode::get_singleton()->get_scene_root()->is_ancestor_of(p_node)) {8863if (Object::cast_to<WorldEnvironment>(p_node)) {8864world_env_count++;8865if (world_env_count == 1) {8866_update_preview_environment();8867}8868} else if (Object::cast_to<DirectionalLight3D>(p_node)) {8869directional_light_count++;8870if (directional_light_count == 1) {8871_update_preview_environment();8872}8873}8874}8875}88768877void Node3DEditor::_node_removed(Node *p_node) {8878if (EditorNode::get_singleton()->get_scene_root()->is_ancestor_of(p_node)) {8879if (Object::cast_to<WorldEnvironment>(p_node)) {8880world_env_count--;8881if (world_env_count == 0) {8882_update_preview_environment();8883}8884} else if (Object::cast_to<DirectionalLight3D>(p_node)) {8885directional_light_count--;8886if (directional_light_count == 0) {8887_update_preview_environment();8888}8889}8890}88918892if (p_node == selected) {8893Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected);8894if (se) {8895se->gizmo.unref();8896se->subgizmos.clear();8897}8898selected = nullptr;8899update_transform_gizmo();8900}8901}89028903void Node3DEditor::_register_all_gizmos() {8904add_gizmo_plugin(Ref<Camera3DGizmoPlugin>(memnew(Camera3DGizmoPlugin)));8905add_gizmo_plugin(Ref<Light3DGizmoPlugin>(memnew(Light3DGizmoPlugin)));8906add_gizmo_plugin(Ref<AudioStreamPlayer3DGizmoPlugin>(memnew(AudioStreamPlayer3DGizmoPlugin)));8907add_gizmo_plugin(Ref<AudioListener3DGizmoPlugin>(memnew(AudioListener3DGizmoPlugin)));8908add_gizmo_plugin(Ref<MeshInstance3DGizmoPlugin>(memnew(MeshInstance3DGizmoPlugin)));8909add_gizmo_plugin(Ref<OccluderInstance3DGizmoPlugin>(memnew(OccluderInstance3DGizmoPlugin)));8910add_gizmo_plugin(Ref<SoftBody3DGizmoPlugin>(memnew(SoftBody3DGizmoPlugin)));8911add_gizmo_plugin(Ref<SpriteBase3DGizmoPlugin>(memnew(SpriteBase3DGizmoPlugin)));8912add_gizmo_plugin(Ref<Label3DGizmoPlugin>(memnew(Label3DGizmoPlugin)));8913add_gizmo_plugin(Ref<GeometryInstance3DGizmoPlugin>(memnew(GeometryInstance3DGizmoPlugin)));8914add_gizmo_plugin(Ref<Marker3DGizmoPlugin>(memnew(Marker3DGizmoPlugin)));8915add_gizmo_plugin(Ref<RayCast3DGizmoPlugin>(memnew(RayCast3DGizmoPlugin)));8916add_gizmo_plugin(Ref<ShapeCast3DGizmoPlugin>(memnew(ShapeCast3DGizmoPlugin)));8917add_gizmo_plugin(Ref<SpringArm3DGizmoPlugin>(memnew(SpringArm3DGizmoPlugin)));8918add_gizmo_plugin(Ref<SpringBoneCollision3DGizmoPlugin>(memnew(SpringBoneCollision3DGizmoPlugin)));8919add_gizmo_plugin(Ref<SpringBoneSimulator3DGizmoPlugin>(memnew(SpringBoneSimulator3DGizmoPlugin)));8920add_gizmo_plugin(Ref<VehicleWheel3DGizmoPlugin>(memnew(VehicleWheel3DGizmoPlugin)));8921add_gizmo_plugin(Ref<VisibleOnScreenNotifier3DGizmoPlugin>(memnew(VisibleOnScreenNotifier3DGizmoPlugin)));8922add_gizmo_plugin(Ref<GPUParticles3DGizmoPlugin>(memnew(GPUParticles3DGizmoPlugin)));8923add_gizmo_plugin(Ref<GPUParticlesCollision3DGizmoPlugin>(memnew(GPUParticlesCollision3DGizmoPlugin)));8924add_gizmo_plugin(Ref<Particles3DEmissionShapeGizmoPlugin>(memnew(Particles3DEmissionShapeGizmoPlugin)));8925add_gizmo_plugin(Ref<CPUParticles3DGizmoPlugin>(memnew(CPUParticles3DGizmoPlugin)));8926add_gizmo_plugin(Ref<ReflectionProbeGizmoPlugin>(memnew(ReflectionProbeGizmoPlugin)));8927add_gizmo_plugin(Ref<DecalGizmoPlugin>(memnew(DecalGizmoPlugin)));8928add_gizmo_plugin(Ref<VoxelGIGizmoPlugin>(memnew(VoxelGIGizmoPlugin)));8929add_gizmo_plugin(Ref<LightmapGIGizmoPlugin>(memnew(LightmapGIGizmoPlugin)));8930add_gizmo_plugin(Ref<LightmapProbeGizmoPlugin>(memnew(LightmapProbeGizmoPlugin)));8931add_gizmo_plugin(Ref<CollisionObject3DGizmoPlugin>(memnew(CollisionObject3DGizmoPlugin)));8932add_gizmo_plugin(Ref<CollisionShape3DGizmoPlugin>(memnew(CollisionShape3DGizmoPlugin)));8933add_gizmo_plugin(Ref<CollisionPolygon3DGizmoPlugin>(memnew(CollisionPolygon3DGizmoPlugin)));8934add_gizmo_plugin(Ref<Joint3DGizmoPlugin>(memnew(Joint3DGizmoPlugin)));8935add_gizmo_plugin(Ref<PhysicalBone3DGizmoPlugin>(memnew(PhysicalBone3DGizmoPlugin)));8936add_gizmo_plugin(Ref<FogVolumeGizmoPlugin>(memnew(FogVolumeGizmoPlugin)));8937}89388939void Node3DEditor::_bind_methods() {8940ClassDB::bind_method("_get_editor_data", &Node3DEditor::_get_editor_data);8941ClassDB::bind_method("_request_gizmo", &Node3DEditor::_request_gizmo);8942ClassDB::bind_method("_request_gizmo_for_id", &Node3DEditor::_request_gizmo_for_id);8943ClassDB::bind_method("_set_subgizmo_selection", &Node3DEditor::_set_subgizmo_selection);8944ClassDB::bind_method("_clear_subgizmo_selection", &Node3DEditor::_clear_subgizmo_selection);8945ClassDB::bind_method("_refresh_menu_icons", &Node3DEditor::_refresh_menu_icons);8946ClassDB::bind_method("_preview_settings_changed", &Node3DEditor::_preview_settings_changed);89478948ClassDB::bind_method("update_all_gizmos", &Node3DEditor::update_all_gizmos);8949ClassDB::bind_method("update_transform_gizmo", &Node3DEditor::update_transform_gizmo);89508951ADD_SIGNAL(MethodInfo("transform_key_request"));8952ADD_SIGNAL(MethodInfo("item_lock_status_changed"));8953ADD_SIGNAL(MethodInfo("item_group_status_changed"));8954}89558956void Node3DEditor::clear() {8957settings_fov->set_value(EDITOR_GET("editors/3d/default_fov"));8958settings_znear->set_value(EDITOR_GET("editors/3d/default_z_near"));8959settings_zfar->set_value(EDITOR_GET("editors/3d/default_z_far"));89608961snap_translate_value = EditorSettings::get_singleton()->get_project_metadata("3d_editor", "snap_translate_value", 1);8962snap_rotate_value = EditorSettings::get_singleton()->get_project_metadata("3d_editor", "snap_rotate_value", 15);8963snap_scale_value = EditorSettings::get_singleton()->get_project_metadata("3d_editor", "snap_scale_value", 10);8964_snap_update();89658966for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) {8967viewports[i]->reset();8968}89698970if (origin_instance.is_valid()) {8971RenderingServer::get_singleton()->instance_set_visible(origin_instance, true);8972}89738974view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_ORIGIN), true);8975for (int i = 0; i < 3; ++i) {8976if (grid_enable[i]) {8977grid_visible[i] = true;8978}8979}89808981for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) {8982viewports[i]->view_display_menu->get_popup()->set_item_checked(viewports[i]->view_display_menu->get_popup()->get_item_index(Node3DEditorViewport::VIEW_AUDIO_LISTENER), i == 0);8983viewports[i]->viewport->set_as_audio_listener_3d(i == 0);8984}89858986view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_GRID), true);8987grid_enabled = true;8988grid_init_draw = false;8989}89908991void Node3DEditor::_sun_direction_draw() {8992sun_direction->draw_rect(Rect2(Vector2(), sun_direction->get_size()), Color(1, 1, 1, 1));8993Vector3 z_axis = preview_sun->get_transform().basis.get_column(Vector3::AXIS_Z);8994z_axis = get_editor_viewport(0)->camera->get_camera_transform().basis.xform_inv(z_axis);8995sun_direction_material->set_shader_parameter("sun_direction", Vector3(z_axis.x, -z_axis.y, z_axis.z));8996Color color = sun_color->get_pick_color() * sun_energy->get_value();8997sun_direction_material->set_shader_parameter("sun_color", Vector3(color.r, color.g, color.b));8998}89999000void Node3DEditor::_preview_settings_changed() {9001if (sun_environ_updating) {9002return;9003}90049005{ // preview sun9006sun_rotation.x = Math::deg_to_rad(-sun_angle_altitude->get_value());9007sun_rotation.y = Math::deg_to_rad(180.0 - sun_angle_azimuth->get_value());9008Transform3D t;9009t.basis = Basis::from_euler(Vector3(sun_rotation.x, sun_rotation.y, 0));9010preview_sun->set_transform(t);9011sun_direction->queue_redraw();9012preview_sun->set_param(Light3D::PARAM_ENERGY, sun_energy->get_value());9013preview_sun->set_param(Light3D::PARAM_SHADOW_MAX_DISTANCE, sun_shadow_max_distance->get_value());9014preview_sun->set_color(sun_color->get_pick_color());9015}90169017{ //preview env9018sky_material->set_energy_multiplier(environ_energy->get_value());9019Color hz_color = environ_sky_color->get_pick_color().lerp(environ_ground_color->get_pick_color(), 0.5);9020float hz_lum = hz_color.get_luminance() * 3.333;9021hz_color = hz_color.lerp(Color(hz_lum, hz_lum, hz_lum), 0.5);9022sky_material->set_sky_top_color(environ_sky_color->get_pick_color());9023sky_material->set_sky_horizon_color(hz_color);9024sky_material->set_ground_bottom_color(environ_ground_color->get_pick_color());9025sky_material->set_ground_horizon_color(hz_color);90269027environment->set_ssao_enabled(environ_ao_button->is_pressed());9028environment->set_glow_enabled(environ_glow_button->is_pressed());9029environment->set_sdfgi_enabled(environ_gi_button->is_pressed());9030environment->set_tonemapper(environ_tonemap_button->is_pressed() ? Environment::TONE_MAPPER_FILMIC : Environment::TONE_MAPPER_LINEAR);9031}9032}90339034void Node3DEditor::_load_default_preview_settings() {9035sun_environ_updating = true;90369037// These default rotations place the preview sun at an angular altitude9038// of 60 degrees (must be negative) and an azimuth of 30 degrees clockwise9039// from north (or 150 CCW from south), from north east, facing south west.9040// On any not-tidally-locked planet, a sun would have an angular altitude9041// of 60 degrees as the average of all points on the sphere at noon.9042// The azimuth choice is arbitrary, but ideally shouldn't be on an axis.9043sun_rotation = Vector2(-Math::deg_to_rad(60.0), Math::deg_to_rad(150.0));90449045sun_angle_altitude->set_value_no_signal(-Math::rad_to_deg(sun_rotation.x));9046sun_angle_azimuth->set_value_no_signal(180.0 - Math::rad_to_deg(sun_rotation.y));9047sun_direction->queue_redraw();9048environ_sky_color->set_pick_color(Color(0.385, 0.454, 0.55));9049environ_ground_color->set_pick_color(Color(0.2, 0.169, 0.133));9050environ_energy->set_value_no_signal(1.0);9051if (OS::get_singleton()->get_current_rendering_method() != "gl_compatibility" && OS::get_singleton()->get_current_rendering_method() != "dummy") {9052environ_glow_button->set_pressed_no_signal(true);9053}9054environ_tonemap_button->set_pressed_no_signal(true);9055environ_ao_button->set_pressed_no_signal(false);9056environ_gi_button->set_pressed_no_signal(false);9057sun_shadow_max_distance->set_value_no_signal(100);90589059sun_color->set_pick_color(Color(1, 1, 1));9060sun_energy->set_value_no_signal(1.0);90619062sun_environ_updating = false;9063}90649065void Node3DEditor::_update_preview_environment() {9066bool disable_light = directional_light_count > 0 || !sun_button->is_pressed();90679068sun_button->set_disabled(directional_light_count > 0);90699070if (disable_light) {9071if (preview_sun->get_parent()) {9072preview_sun->get_parent()->remove_child(preview_sun);9073sun_state->show();9074sun_vb->hide();9075preview_sun_dangling = true;9076}90779078if (directional_light_count > 0) {9079sun_state->set_text(TTRC("Scene contains\nDirectionalLight3D.\nPreview disabled."));9080} else {9081sun_state->set_text(TTRC("Preview disabled."));9082}90839084} else {9085if (!preview_sun->get_parent()) {9086add_child(preview_sun, true);9087sun_state->hide();9088sun_vb->show();9089preview_sun_dangling = false;9090}9091}90929093sun_angle_altitude->set_value_no_signal(-Math::rad_to_deg(sun_rotation.x));9094sun_angle_azimuth->set_value_no_signal(180.0 - Math::rad_to_deg(sun_rotation.y));90959096bool disable_env = world_env_count > 0 || !environ_button->is_pressed();90979098environ_button->set_disabled(world_env_count > 0);90999100if (disable_env) {9101if (preview_environment->get_parent()) {9102preview_environment->get_parent()->remove_child(preview_environment);9103environ_state->show();9104environ_vb->hide();9105preview_env_dangling = true;9106}9107if (world_env_count > 0) {9108environ_state->set_text(TTRC("Scene contains\nWorldEnvironment.\nPreview disabled."));9109} else {9110environ_state->set_text(TTRC("Preview disabled."));9111}91129113} else {9114if (!preview_environment->get_parent()) {9115add_child(preview_environment);9116environ_state->hide();9117environ_vb->show();9118preview_env_dangling = false;9119}9120}9121}91229123void Node3DEditor::_sun_direction_input(const Ref<InputEvent> &p_event) {9124Ref<InputEventMouseMotion> mm = p_event;9125if (mm.is_valid() && mm->get_button_mask().has_flag(MouseButtonMask::LEFT)) {9126sun_rotation.x += mm->get_relative().y * (0.02 * EDSCALE);9127sun_rotation.y -= mm->get_relative().x * (0.02 * EDSCALE);9128sun_rotation.x = CLAMP(sun_rotation.x, -Math::TAU / 4, Math::TAU / 4);91299130EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();9131undo_redo->create_action(TTR("Set Preview Sun Direction"), UndoRedo::MergeMode::MERGE_ENDS);9132undo_redo->add_do_method(sun_angle_altitude, "set_value_no_signal", -Math::rad_to_deg(sun_rotation.x));9133undo_redo->add_undo_method(sun_angle_altitude, "set_value_no_signal", sun_angle_altitude->get_value());9134undo_redo->add_do_method(sun_angle_azimuth, "set_value_no_signal", 180.0 - Math::rad_to_deg(sun_rotation.y));9135undo_redo->add_undo_method(sun_angle_azimuth, "set_value_no_signal", sun_angle_azimuth->get_value());9136undo_redo->add_do_method(this, "_preview_settings_changed");9137undo_redo->add_undo_method(this, "_preview_settings_changed");9138undo_redo->commit_action();9139}9140}91419142void Node3DEditor::_sun_direction_set_altitude(float p_altitude) {9143EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();9144undo_redo->create_action(TTR("Set Preview Sun Altitude"), UndoRedo::MergeMode::MERGE_ENDS);9145undo_redo->add_do_method(sun_angle_altitude, "set_value_no_signal", p_altitude);9146undo_redo->add_undo_method(sun_angle_altitude, "set_value_no_signal", -Math::rad_to_deg(sun_rotation.x));9147undo_redo->add_do_method(this, "_preview_settings_changed");9148undo_redo->add_undo_method(this, "_preview_settings_changed");9149undo_redo->commit_action();9150}91519152void Node3DEditor::_sun_direction_set_azimuth(float p_azimuth) {9153EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();9154undo_redo->create_action(TTR("Set Preview Sun Azimuth"), UndoRedo::MergeMode::MERGE_ENDS);9155undo_redo->add_do_method(sun_angle_azimuth, "set_value_no_signal", p_azimuth);9156undo_redo->add_undo_method(sun_angle_azimuth, "set_value_no_signal", 180.0 - Math::rad_to_deg(sun_rotation.y));9157undo_redo->add_do_method(this, "_preview_settings_changed");9158undo_redo->add_undo_method(this, "_preview_settings_changed");9159undo_redo->commit_action();9160}91619162void Node3DEditor::_sun_set_color(const Color &p_color) {9163EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();9164undo_redo->create_action(TTR("Set Preview Sun Color"), UndoRedo::MergeMode::MERGE_ENDS);9165undo_redo->add_do_method(sun_color, "set_pick_color", p_color);9166undo_redo->add_undo_method(sun_color, "set_pick_color", preview_sun->get_color());9167undo_redo->add_do_method(this, "_preview_settings_changed");9168undo_redo->add_undo_method(this, "_preview_settings_changed");9169undo_redo->commit_action();9170}91719172void Node3DEditor::_sun_set_energy(float p_energy) {9173EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();9174undo_redo->create_action(TTR("Set Preview Sun Energy"), UndoRedo::MergeMode::MERGE_ENDS);9175undo_redo->add_do_method(sun_energy, "set_value_no_signal", p_energy);9176undo_redo->add_undo_method(sun_energy, "set_value_no_signal", preview_sun->get_param(Light3D::PARAM_ENERGY));9177undo_redo->add_do_method(this, "_preview_settings_changed");9178undo_redo->add_undo_method(this, "_preview_settings_changed");9179undo_redo->commit_action();9180}91819182void Node3DEditor::_sun_set_shadow_max_distance(float p_shadow_max_distance) {9183EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();9184undo_redo->create_action(TTR("Set Preview Sun Max Shadow Distance"), UndoRedo::MergeMode::MERGE_ENDS);9185undo_redo->add_do_method(sun_shadow_max_distance, "set_value_no_signal", p_shadow_max_distance);9186undo_redo->add_undo_method(sun_shadow_max_distance, "set_value_no_signal", preview_sun->get_param(Light3D::PARAM_SHADOW_MAX_DISTANCE));9187undo_redo->add_do_method(this, "_preview_settings_changed");9188undo_redo->add_undo_method(this, "_preview_settings_changed");9189undo_redo->commit_action();9190}91919192void Node3DEditor::_environ_set_sky_color(const Color &p_color) {9193EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();9194undo_redo->create_action(TTR("Set Preview Environment Sky Color"), UndoRedo::MergeMode::MERGE_ENDS);9195undo_redo->add_do_method(environ_sky_color, "set_pick_color", p_color);9196undo_redo->add_undo_method(environ_sky_color, "set_pick_color", sky_material->get_sky_top_color());9197undo_redo->add_do_method(this, "_preview_settings_changed");9198undo_redo->add_undo_method(this, "_preview_settings_changed");9199undo_redo->commit_action();9200}92019202void Node3DEditor::_environ_set_ground_color(const Color &p_color) {9203EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();9204undo_redo->create_action(TTR("Set Preview Environment Ground Color"), UndoRedo::MergeMode::MERGE_ENDS);9205undo_redo->add_do_method(environ_ground_color, "set_pick_color", p_color);9206undo_redo->add_undo_method(environ_ground_color, "set_pick_color", sky_material->get_ground_bottom_color());9207undo_redo->add_do_method(this, "_preview_settings_changed");9208undo_redo->add_undo_method(this, "_preview_settings_changed");9209undo_redo->commit_action();9210}92119212void Node3DEditor::_environ_set_sky_energy(float p_energy) {9213EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();9214undo_redo->create_action(TTR("Set Preview Environment Energy"), UndoRedo::MergeMode::MERGE_ENDS);9215undo_redo->add_do_method(environ_energy, "set_value_no_signal", p_energy);9216undo_redo->add_undo_method(environ_energy, "set_value_no_signal", sky_material->get_energy_multiplier());9217undo_redo->add_do_method(this, "_preview_settings_changed");9218undo_redo->add_undo_method(this, "_preview_settings_changed");9219undo_redo->commit_action();9220}92219222void Node3DEditor::_environ_set_ao() {9223EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();9224undo_redo->create_action(TTR("Set Preview Environment Ambient Occlusion"));9225undo_redo->add_do_method(environ_ao_button, "set_pressed", environ_ao_button->is_pressed());9226undo_redo->add_undo_method(environ_ao_button, "set_pressed", !environ_ao_button->is_pressed());9227undo_redo->add_do_method(this, "_preview_settings_changed");9228undo_redo->add_undo_method(this, "_preview_settings_changed");9229undo_redo->commit_action();9230}92319232void Node3DEditor::_environ_set_glow() {9233EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();9234undo_redo->create_action(TTR("Set Preview Environment Glow"));9235undo_redo->add_do_method(environ_glow_button, "set_pressed", environ_glow_button->is_pressed());9236undo_redo->add_undo_method(environ_glow_button, "set_pressed", !environ_glow_button->is_pressed());9237undo_redo->add_do_method(this, "_preview_settings_changed");9238undo_redo->add_undo_method(this, "_preview_settings_changed");9239undo_redo->commit_action();9240}92419242void Node3DEditor::_environ_set_tonemap() {9243EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();9244undo_redo->create_action(TTR("Set Preview Environment Tonemap"));9245undo_redo->add_do_method(environ_tonemap_button, "set_pressed", environ_tonemap_button->is_pressed());9246undo_redo->add_undo_method(environ_tonemap_button, "set_pressed", !environ_tonemap_button->is_pressed());9247undo_redo->add_do_method(this, "_preview_settings_changed");9248undo_redo->add_undo_method(this, "_preview_settings_changed");9249undo_redo->commit_action();9250}92519252void Node3DEditor::_environ_set_gi() {9253EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();9254undo_redo->create_action(TTR("Set Preview Environment Global Illumination"));9255undo_redo->add_do_method(environ_gi_button, "set_pressed", environ_gi_button->is_pressed());9256undo_redo->add_undo_method(environ_gi_button, "set_pressed", !environ_gi_button->is_pressed());9257undo_redo->add_do_method(this, "_preview_settings_changed");9258undo_redo->add_undo_method(this, "_preview_settings_changed");9259undo_redo->commit_action();9260}92619262void Node3DEditor::PreviewSunEnvPopup::shortcut_input(const Ref<InputEvent> &p_event) {9263const Ref<InputEventKey> k = p_event;9264if (k.is_valid() && k->is_pressed()) {9265bool handled = false;92669267if (ED_IS_SHORTCUT("ui_undo", p_event)) {9268EditorNode::get_singleton()->undo();9269handled = true;9270}92719272if (ED_IS_SHORTCUT("ui_redo", p_event)) {9273EditorNode::get_singleton()->redo();9274handled = true;9275}92769277if (handled) {9278set_input_as_handled();9279}9280}9281}92829283Node3DEditor::Node3DEditor() {9284gizmo.visible = true;9285gizmo.scale = 1.0;92869287viewport_environment.instantiate();9288VBoxContainer *vbc = this;92899290custom_camera = nullptr;9291ERR_FAIL_COND_MSG(singleton != nullptr, "A Node3DEditor singleton already exists.");9292singleton = this;9293editor_selection = EditorNode::get_singleton()->get_editor_selection();9294editor_selection->add_editor_plugin(this);92959296snap_enabled = false;9297snap_key_enabled = false;9298tool_mode = TOOL_MODE_SELECT;92999300// Add some margin to the sides for better aesthetics.9301// This prevents the first button's hover/pressed effect from "touching" the panel's border,9302// which looks ugly.9303MarginContainer *toolbar_margin = memnew(MarginContainer);9304toolbar_margin->add_theme_constant_override("margin_left", 4 * EDSCALE);9305toolbar_margin->add_theme_constant_override("margin_right", 4 * EDSCALE);9306vbc->add_child(toolbar_margin);93079308// A fluid container for all toolbars.9309HFlowContainer *main_flow = memnew(HFlowContainer);9310toolbar_margin->add_child(main_flow);93119312// Main toolbars.9313HBoxContainer *main_menu_hbox = memnew(HBoxContainer);9314main_flow->add_child(main_menu_hbox);93159316String sct;93179318tool_button[TOOL_MODE_SELECT] = memnew(Button);9319main_menu_hbox->add_child(tool_button[TOOL_MODE_SELECT]);9320tool_button[TOOL_MODE_SELECT]->set_toggle_mode(true);9321tool_button[TOOL_MODE_SELECT]->set_tooltip_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);9322tool_button[TOOL_MODE_SELECT]->set_theme_type_variation(SceneStringName(FlatButton));9323tool_button[TOOL_MODE_SELECT]->set_pressed(true);9324tool_button[TOOL_MODE_SELECT]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_TOOL_SELECT));9325tool_button[TOOL_MODE_SELECT]->set_shortcut(ED_SHORTCUT("spatial_editor/tool_select", TTRC("Select Mode"), Key::Q, true));9326tool_button[TOOL_MODE_SELECT]->set_shortcut_context(this);9327tool_button[TOOL_MODE_SELECT]->set_accessibility_name(TTRC("Select Mode"));9328main_menu_hbox->add_child(memnew(VSeparator));93299330tool_button[TOOL_MODE_MOVE] = memnew(Button);9331main_menu_hbox->add_child(tool_button[TOOL_MODE_MOVE]);9332tool_button[TOOL_MODE_MOVE]->set_toggle_mode(true);9333tool_button[TOOL_MODE_MOVE]->set_tooltip_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);9334tool_button[TOOL_MODE_MOVE]->set_theme_type_variation(SceneStringName(FlatButton));93359336tool_button[TOOL_MODE_MOVE]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_TOOL_MOVE));9337tool_button[TOOL_MODE_MOVE]->set_shortcut(ED_SHORTCUT("spatial_editor/tool_move", TTRC("Move Mode"), Key::W, true));9338tool_button[TOOL_MODE_MOVE]->set_shortcut_context(this);9339tool_button[TOOL_MODE_MOVE]->set_accessibility_name(TTRC("Move Mode"));93409341tool_button[TOOL_MODE_ROTATE] = memnew(Button);9342main_menu_hbox->add_child(tool_button[TOOL_MODE_ROTATE]);9343tool_button[TOOL_MODE_ROTATE]->set_toggle_mode(true);9344tool_button[TOOL_MODE_ROTATE]->set_tooltip_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);9345tool_button[TOOL_MODE_ROTATE]->set_theme_type_variation(SceneStringName(FlatButton));9346tool_button[TOOL_MODE_ROTATE]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_TOOL_ROTATE));9347tool_button[TOOL_MODE_ROTATE]->set_shortcut(ED_SHORTCUT("spatial_editor/tool_rotate", TTRC("Rotate Mode"), Key::E, true));9348tool_button[TOOL_MODE_ROTATE]->set_shortcut_context(this);9349tool_button[TOOL_MODE_ROTATE]->set_accessibility_name(TTRC("Rotate Mode"));93509351tool_button[TOOL_MODE_SCALE] = memnew(Button);9352main_menu_hbox->add_child(tool_button[TOOL_MODE_SCALE]);9353tool_button[TOOL_MODE_SCALE]->set_toggle_mode(true);9354tool_button[TOOL_MODE_SCALE]->set_tooltip_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);9355tool_button[TOOL_MODE_SCALE]->set_theme_type_variation(SceneStringName(FlatButton));9356tool_button[TOOL_MODE_SCALE]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_TOOL_SCALE));9357tool_button[TOOL_MODE_SCALE]->set_shortcut(ED_SHORTCUT("spatial_editor/tool_scale", TTRC("Scale Mode"), Key::R, true));9358tool_button[TOOL_MODE_SCALE]->set_shortcut_context(this);9359tool_button[TOOL_MODE_SCALE]->set_accessibility_name(TTRC("Scale Mode"));93609361main_menu_hbox->add_child(memnew(VSeparator));93629363tool_button[TOOL_MODE_LIST_SELECT] = memnew(Button);9364main_menu_hbox->add_child(tool_button[TOOL_MODE_LIST_SELECT]);9365tool_button[TOOL_MODE_LIST_SELECT]->set_toggle_mode(true);9366tool_button[TOOL_MODE_LIST_SELECT]->set_theme_type_variation(SceneStringName(FlatButton));9367tool_button[TOOL_MODE_LIST_SELECT]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_TOOL_LIST_SELECT));9368tool_button[TOOL_MODE_LIST_SELECT]->set_tooltip_text(TTRC("Show list of selectable nodes at position clicked."));93699370tool_button[TOOL_LOCK_SELECTED] = memnew(Button);9371main_menu_hbox->add_child(tool_button[TOOL_LOCK_SELECTED]);9372tool_button[TOOL_LOCK_SELECTED]->set_theme_type_variation(SceneStringName(FlatButton));9373tool_button[TOOL_LOCK_SELECTED]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_LOCK_SELECTED));9374tool_button[TOOL_LOCK_SELECTED]->set_tooltip_text(TTRC("Lock selected node, preventing selection and movement."));9375// Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused.9376tool_button[TOOL_LOCK_SELECTED]->set_shortcut(ED_GET_SHORTCUT("editor/lock_selected_nodes"));9377tool_button[TOOL_LOCK_SELECTED]->set_accessibility_name(TTRC("Lock"));93789379tool_button[TOOL_UNLOCK_SELECTED] = memnew(Button);9380main_menu_hbox->add_child(tool_button[TOOL_UNLOCK_SELECTED]);9381tool_button[TOOL_UNLOCK_SELECTED]->set_theme_type_variation(SceneStringName(FlatButton));9382tool_button[TOOL_UNLOCK_SELECTED]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_UNLOCK_SELECTED));9383tool_button[TOOL_UNLOCK_SELECTED]->set_tooltip_text(TTRC("Unlock selected node, allowing selection and movement."));9384// Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused.9385tool_button[TOOL_UNLOCK_SELECTED]->set_shortcut(ED_GET_SHORTCUT("editor/unlock_selected_nodes"));9386tool_button[TOOL_UNLOCK_SELECTED]->set_accessibility_name(TTRC("Unlock"));93879388tool_button[TOOL_GROUP_SELECTED] = memnew(Button);9389main_menu_hbox->add_child(tool_button[TOOL_GROUP_SELECTED]);9390tool_button[TOOL_GROUP_SELECTED]->set_theme_type_variation(SceneStringName(FlatButton));9391tool_button[TOOL_GROUP_SELECTED]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_GROUP_SELECTED));9392tool_button[TOOL_GROUP_SELECTED]->set_tooltip_text(TTRC("Groups the selected node with its children. This selects the parent when any child node is clicked in 2D and 3D view."));9393// Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused.9394tool_button[TOOL_GROUP_SELECTED]->set_shortcut(ED_GET_SHORTCUT("editor/group_selected_nodes"));9395tool_button[TOOL_GROUP_SELECTED]->set_accessibility_name(TTRC("Group"));93969397tool_button[TOOL_UNGROUP_SELECTED] = memnew(Button);9398main_menu_hbox->add_child(tool_button[TOOL_UNGROUP_SELECTED]);9399tool_button[TOOL_UNGROUP_SELECTED]->set_theme_type_variation(SceneStringName(FlatButton));9400tool_button[TOOL_UNGROUP_SELECTED]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_UNGROUP_SELECTED));9401tool_button[TOOL_UNGROUP_SELECTED]->set_tooltip_text(TTRC("Ungroups the selected node from its children. Child nodes will be individual items in 2D and 3D view."));9402// Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused.9403tool_button[TOOL_UNGROUP_SELECTED]->set_shortcut(ED_GET_SHORTCUT("editor/ungroup_selected_nodes"));9404tool_button[TOOL_UNGROUP_SELECTED]->set_accessibility_name(TTRC("Ungroup"));94059406tool_button[TOOL_RULER] = memnew(Button);9407main_menu_hbox->add_child(tool_button[TOOL_RULER]);9408tool_button[TOOL_RULER]->set_toggle_mode(true);9409tool_button[TOOL_RULER]->set_theme_type_variation("FlatButton");9410tool_button[TOOL_RULER]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_RULER));9411tool_button[TOOL_RULER]->set_tooltip_text(TTRC("LMB+Drag: Measure the distance between two points in 3D space."));9412// Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused.9413tool_button[TOOL_RULER]->set_shortcut(ED_SHORTCUT("spatial_editor/measure", TTRC("Ruler Mode"), Key::M));9414tool_button[TOOL_RULER]->set_accessibility_name(TTRC("Ruler Mode"));94159416main_menu_hbox->add_child(memnew(VSeparator));94179418tool_option_button[TOOL_OPT_LOCAL_COORDS] = memnew(Button);9419main_menu_hbox->add_child(tool_option_button[TOOL_OPT_LOCAL_COORDS]);9420tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_toggle_mode(true);9421tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_theme_type_variation(SceneStringName(FlatButton));9422tool_option_button[TOOL_OPT_LOCAL_COORDS]->connect(SceneStringName(toggled), callable_mp(this, &Node3DEditor::_menu_item_toggled).bind(MENU_TOOL_LOCAL_COORDS));9423tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_shortcut(ED_SHORTCUT("spatial_editor/local_coords", TTRC("Use Local Space"), Key::T));9424tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_shortcut_context(this);9425tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_accessibility_name(TTRC("Use Local Space"));94269427tool_option_button[TOOL_OPT_USE_SNAP] = memnew(Button);9428main_menu_hbox->add_child(tool_option_button[TOOL_OPT_USE_SNAP]);9429tool_option_button[TOOL_OPT_USE_SNAP]->set_toggle_mode(true);9430tool_option_button[TOOL_OPT_USE_SNAP]->set_theme_type_variation(SceneStringName(FlatButton));9431tool_option_button[TOOL_OPT_USE_SNAP]->connect(SceneStringName(toggled), callable_mp(this, &Node3DEditor::_menu_item_toggled).bind(MENU_TOOL_USE_SNAP));9432tool_option_button[TOOL_OPT_USE_SNAP]->set_shortcut(ED_SHORTCUT("spatial_editor/snap", TTRC("Use Snap"), Key::Y));9433tool_option_button[TOOL_OPT_USE_SNAP]->set_shortcut_context(this);9434tool_option_button[TOOL_OPT_USE_SNAP]->set_accessibility_name(TTRC("Use Snap"));94359436main_menu_hbox->add_child(memnew(VSeparator));9437sun_button = memnew(Button);9438sun_button->set_tooltip_text(TTRC("Toggle preview sunlight.\nIf a DirectionalLight3D node is added to the scene, preview sunlight is disabled."));9439sun_button->set_toggle_mode(true);9440sun_button->set_accessibility_name(TTRC("Toggle preview sunlight."));9441sun_button->set_theme_type_variation(SceneStringName(FlatButton));9442sun_button->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_update_preview_environment), CONNECT_DEFERRED);9443// Preview is enabled by default - ensure this applies on editor startup when there is no state yet.9444sun_button->set_pressed(true);94459446main_menu_hbox->add_child(sun_button);94479448environ_button = memnew(Button);9449environ_button->set_tooltip_text(TTRC("Toggle preview environment.\nIf a WorldEnvironment node is added to the scene, preview environment is disabled."));9450environ_button->set_toggle_mode(true);9451environ_button->set_accessibility_name(TTRC("Toggle preview environment."));9452environ_button->set_theme_type_variation(SceneStringName(FlatButton));9453environ_button->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_update_preview_environment), CONNECT_DEFERRED);9454// Preview is enabled by default - ensure this applies on editor startup when there is no state yet.9455environ_button->set_pressed(true);94569457main_menu_hbox->add_child(environ_button);94589459sun_environ_settings = memnew(Button);9460sun_environ_settings->set_tooltip_text(TTRC("Edit Sun and Environment settings."));9461sun_environ_settings->set_theme_type_variation(SceneStringName(FlatButton));9462sun_environ_settings->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_sun_environ_settings_pressed));94639464main_menu_hbox->add_child(sun_environ_settings);94659466main_menu_hbox->add_child(memnew(VSeparator));94679468// Drag and drop support;9469preview_node = memnew(Node3D);9470preview_bounds = AABB();94719472ED_SHORTCUT("spatial_editor/bottom_view", TTRC("Bottom View"), KeyModifierMask::ALT + Key::KP_7);9473ED_SHORTCUT("spatial_editor/top_view", TTRC("Top View"), Key::KP_7);9474ED_SHORTCUT("spatial_editor/rear_view", TTRC("Rear View"), KeyModifierMask::ALT + Key::KP_1);9475ED_SHORTCUT("spatial_editor/front_view", TTRC("Front View"), Key::KP_1);9476ED_SHORTCUT("spatial_editor/left_view", TTRC("Left View"), KeyModifierMask::ALT + Key::KP_3);9477ED_SHORTCUT("spatial_editor/right_view", TTRC("Right View"), Key::KP_3);9478ED_SHORTCUT("spatial_editor/orbit_view_down", TTRC("Orbit View Down"), Key::KP_2);9479ED_SHORTCUT("spatial_editor/orbit_view_left", TTRC("Orbit View Left"), Key::KP_4);9480ED_SHORTCUT("spatial_editor/orbit_view_right", TTRC("Orbit View Right"), Key::KP_6);9481ED_SHORTCUT("spatial_editor/orbit_view_up", TTRC("Orbit View Up"), Key::KP_8);9482ED_SHORTCUT("spatial_editor/orbit_view_180", TTRC("Orbit View 180"), Key::KP_9);9483ED_SHORTCUT("spatial_editor/switch_perspective_orthogonal", TTRC("Switch Perspective/Orthogonal View"), Key::KP_5);9484ED_SHORTCUT("spatial_editor/insert_anim_key", TTRC("Insert Animation Key"), Key::K);9485ED_SHORTCUT("spatial_editor/focus_origin", TTRC("Focus Origin"), Key::O);9486ED_SHORTCUT("spatial_editor/focus_selection", TTRC("Focus Selection"), Key::F);9487ED_SHORTCUT_ARRAY("spatial_editor/align_transform_with_view", TTRC("Align Transform with View"),9488{ int32_t(KeyModifierMask::ALT | KeyModifierMask::CTRL | Key::KP_0),9489int32_t(KeyModifierMask::ALT | KeyModifierMask::CTRL | Key::M),9490int32_t(KeyModifierMask::ALT | KeyModifierMask::CTRL | Key::G) });9491ED_SHORTCUT_OVERRIDE_ARRAY("spatial_editor/align_transform_with_view", "macos",9492{ int32_t(KeyModifierMask::ALT | KeyModifierMask::META | Key::KP_0),9493int32_t(KeyModifierMask::ALT | KeyModifierMask::META | Key::G) });9494ED_SHORTCUT("spatial_editor/align_rotation_with_view", TTRC("Align Rotation with View"), KeyModifierMask::ALT + KeyModifierMask::CMD_OR_CTRL + Key::F);9495ED_SHORTCUT("spatial_editor/freelook_toggle", TTRC("Toggle Freelook"), KeyModifierMask::SHIFT + Key::F);9496ED_SHORTCUT("spatial_editor/decrease_fov", TTRC("Decrease Field of View"), KeyModifierMask::CMD_OR_CTRL + Key::EQUAL); // Usually direct access key for `KEY_PLUS`.9497ED_SHORTCUT("spatial_editor/increase_fov", TTRC("Increase Field of View"), KeyModifierMask::CMD_OR_CTRL + Key::MINUS);9498ED_SHORTCUT("spatial_editor/reset_fov", TTRC("Reset Field of View to Default"), KeyModifierMask::CMD_OR_CTRL + Key::KEY_0);94999500PopupMenu *p;95019502transform_menu = memnew(MenuButton);9503transform_menu->set_flat(false);9504transform_menu->set_theme_type_variation("FlatMenuButton");9505transform_menu->set_text(TTRC("Transform"));9506transform_menu->set_switch_on_hover(true);9507transform_menu->set_shortcut_context(this);9508main_menu_hbox->add_child(transform_menu);95099510p = transform_menu->get_popup();9511p->add_shortcut(ED_SHORTCUT("spatial_editor/snap_to_floor", TTRC("Snap Object to Floor"), Key::PAGEDOWN), MENU_SNAP_TO_FLOOR);9512p->add_shortcut(ED_SHORTCUT("spatial_editor/transform_dialog", TTRC("Transform Dialog...")), MENU_TRANSFORM_DIALOG);95139514p->add_separator();9515p->add_shortcut(ED_SHORTCUT("spatial_editor/configure_snap", TTRC("Configure Snap...")), MENU_TRANSFORM_CONFIGURE_SNAP);95169517p->connect(SceneStringName(id_pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed));95189519view_layout_menu = memnew(MenuButton);9520view_layout_menu->set_flat(false);9521view_layout_menu->set_theme_type_variation("FlatMenuButton");9522// TRANSLATORS: Noun, name of the 2D/3D View menus.9523view_layout_menu->set_text(TTRC("View"));9524view_layout_menu->set_switch_on_hover(true);9525view_layout_menu->set_shortcut_context(this);9526main_menu_hbox->add_child(view_layout_menu);95279528main_menu_hbox->add_child(memnew(VSeparator));95299530context_toolbar_panel = memnew(PanelContainer);9531context_toolbar_hbox = memnew(HBoxContainer);9532context_toolbar_panel->add_child(context_toolbar_hbox);9533main_flow->add_child(context_toolbar_panel);95349535// Get the view menu popup and have it stay open when a checkable item is selected9536p = view_layout_menu->get_popup();9537p->set_hide_on_checkable_item_selection(false);95389539accept = memnew(AcceptDialog);9540EditorNode::get_singleton()->get_gui_base()->add_child(accept);95419542p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/1_viewport", TTRC("1 Viewport"), KeyModifierMask::CMD_OR_CTRL + Key::KEY_1), MENU_VIEW_USE_1_VIEWPORT);9543p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/2_viewports", TTRC("2 Viewports"), KeyModifierMask::CMD_OR_CTRL + Key::KEY_2), MENU_VIEW_USE_2_VIEWPORTS);9544p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/2_viewports_alt", TTRC("2 Viewports (Alt)"), KeyModifierMask::ALT + KeyModifierMask::CMD_OR_CTRL + Key::KEY_2), MENU_VIEW_USE_2_VIEWPORTS_ALT);9545p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/3_viewports", TTRC("3 Viewports"), KeyModifierMask::CMD_OR_CTRL + Key::KEY_3), MENU_VIEW_USE_3_VIEWPORTS);9546p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/3_viewports_alt", TTRC("3 Viewports (Alt)"), KeyModifierMask::ALT + KeyModifierMask::CMD_OR_CTRL + Key::KEY_3), MENU_VIEW_USE_3_VIEWPORTS_ALT);9547p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/4_viewports", TTRC("4 Viewports"), KeyModifierMask::CMD_OR_CTRL + Key::KEY_4), MENU_VIEW_USE_4_VIEWPORTS);9548p->add_separator();95499550gizmos_menu = memnew(PopupMenu);9551gizmos_menu->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);9552gizmos_menu->set_hide_on_checkable_item_selection(false);9553p->add_submenu_node_item(TTRC("Gizmos"), gizmos_menu);9554gizmos_menu->connect(SceneStringName(id_pressed), callable_mp(this, &Node3DEditor::_menu_gizmo_toggled));95559556p->add_separator();9557p->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_origin", TTRC("View Origin")), MENU_VIEW_ORIGIN);9558p->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_grid", TTRC("View Grid"), Key::NUMBERSIGN), MENU_VIEW_GRID);95599560p->add_separator();9561p->add_submenu_node_item(TTRC("Preview Translation"), memnew(EditorTranslationPreviewMenu));95629563p->add_separator();9564p->add_shortcut(ED_SHORTCUT("spatial_editor/settings", TTRC("Settings...")), MENU_VIEW_CAMERA_SETTINGS);95659566p->set_item_checked(p->get_item_index(MENU_VIEW_ORIGIN), true);9567p->set_item_checked(p->get_item_index(MENU_VIEW_GRID), true);95689569p->connect(SceneStringName(id_pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed));95709571/* REST OF MENU */95729573left_panel_split = memnew(HSplitContainer);9574left_panel_split->set_v_size_flags(SIZE_EXPAND_FILL);9575vbc->add_child(left_panel_split);95769577right_panel_split = memnew(HSplitContainer);9578right_panel_split->set_v_size_flags(SIZE_EXPAND_FILL);9579left_panel_split->add_child(right_panel_split);95809581shader_split = memnew(VSplitContainer);9582shader_split->set_h_size_flags(SIZE_EXPAND_FILL);9583right_panel_split->add_child(shader_split);9584viewport_base = memnew(Node3DEditorViewportContainer);9585shader_split->add_child(viewport_base);9586viewport_base->set_v_size_flags(SIZE_EXPAND_FILL);9587for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) {9588viewports[i] = memnew(Node3DEditorViewport(this, i));9589viewports[i]->connect("toggle_maximize_view", callable_mp(this, &Node3DEditor::_toggle_maximize_view));9590viewports[i]->connect("clicked", callable_mp(this, &Node3DEditor::_viewport_clicked).bind(i));9591viewports[i]->assign_pending_data_pointers(preview_node, &preview_bounds, accept);9592viewport_base->add_child(viewports[i]);9593}95949595/* SNAP DIALOG */95969597snap_dialog = memnew(ConfirmationDialog);9598snap_dialog->set_title(TTRC("Snap Settings"));9599add_child(snap_dialog);9600snap_dialog->connect(SceneStringName(confirmed), callable_mp(this, &Node3DEditor::_snap_changed));9601snap_dialog->get_cancel_button()->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_snap_update));96029603VBoxContainer *snap_dialog_vbc = memnew(VBoxContainer);9604snap_dialog->add_child(snap_dialog_vbc);96059606snap_translate = memnew(LineEdit);9607snap_translate->set_select_all_on_focus(true);9608snap_translate->set_accessibility_name(TTRC("Translate Snap:"));9609snap_dialog_vbc->add_margin_child(TTRC("Translate Snap:"), snap_translate);96109611snap_rotate = memnew(LineEdit);9612snap_rotate->set_select_all_on_focus(true);9613snap_rotate->set_accessibility_name(TTRC("Rotate Snap (deg.):"));9614snap_dialog_vbc->add_margin_child(TTRC("Rotate Snap (deg.):"), snap_rotate);96159616snap_scale = memnew(LineEdit);9617snap_scale->set_select_all_on_focus(true);9618snap_scale->set_accessibility_name(TTRC("Scale Snap (%):"));9619snap_dialog_vbc->add_margin_child(TTRC("Scale Snap (%):"), snap_scale);96209621/* SETTINGS DIALOG */96229623settings_dialog = memnew(ConfirmationDialog);9624settings_dialog->set_title(TTRC("Viewport Settings"));9625add_child(settings_dialog);9626settings_vbc = memnew(VBoxContainer);9627settings_vbc->set_custom_minimum_size(Size2(200, 0) * EDSCALE);9628settings_dialog->add_child(settings_vbc);96299630settings_fov = memnew(SpinBox);9631settings_fov->set_max(MAX_FOV);9632settings_fov->set_min(MIN_FOV);9633settings_fov->set_step(0.1);9634settings_fov->set_value(EDITOR_GET("editors/3d/default_fov"));9635settings_fov->set_select_all_on_focus(true);9636settings_fov->set_tooltip_text(TTRC("FOV is defined as a vertical value, as the editor camera always uses the Keep Height aspect mode."));9637settings_fov->set_accessibility_name(TTRC("Perspective VFOV (deg.):"));9638settings_vbc->add_margin_child(TTRC("Perspective VFOV (deg.):"), settings_fov);96399640settings_znear = memnew(SpinBox);9641settings_znear->set_max(MAX_Z);9642settings_znear->set_min(MIN_Z);9643settings_znear->set_step(0.01);9644settings_znear->set_accessibility_name(TTRC("View Z-Near:"));9645settings_znear->set_value(EDITOR_GET("editors/3d/default_z_near"));9646settings_znear->set_select_all_on_focus(true);9647settings_vbc->add_margin_child(TTRC("View Z-Near:"), settings_znear);96489649settings_zfar = memnew(SpinBox);9650settings_zfar->set_max(MAX_Z);9651settings_zfar->set_min(MIN_Z);9652settings_zfar->set_step(0.1);9653settings_zfar->set_accessibility_name(TTRC("View Z-Far:"));9654settings_zfar->set_value(EDITOR_GET("editors/3d/default_z_far"));9655settings_zfar->set_select_all_on_focus(true);9656settings_vbc->add_margin_child(TTRC("View Z-Far:"), settings_zfar);96579658for (uint32_t i = 0; i < VIEWPORTS_COUNT; ++i) {9659settings_dialog->connect(SceneStringName(confirmed), callable_mp(viewports[i], &Node3DEditorViewport::_view_settings_confirmed).bind(0.0));9660}96619662/* XFORM DIALOG */96639664xform_dialog = memnew(ConfirmationDialog);9665xform_dialog->set_title(TTRC("Transform Change"));9666add_child(xform_dialog);96679668VBoxContainer *xform_vbc = memnew(VBoxContainer);9669xform_dialog->add_child(xform_vbc);96709671Label *l = memnew(Label);9672l->set_text(TTRC("Translate:"));9673xform_vbc->add_child(l);96749675HBoxContainer *xform_hbc = memnew(HBoxContainer);9676xform_vbc->add_child(xform_hbc);96779678for (int i = 0; i < 3; i++) {9679xform_translate[i] = memnew(LineEdit);9680xform_translate[i]->set_h_size_flags(SIZE_EXPAND_FILL);9681xform_translate[i]->set_select_all_on_focus(true);9682xform_hbc->add_child(xform_translate[i]);9683}96849685l = memnew(Label);9686l->set_text(TTRC("Rotate (deg.):"));9687xform_vbc->add_child(l);96889689xform_hbc = memnew(HBoxContainer);9690xform_vbc->add_child(xform_hbc);96919692for (int i = 0; i < 3; i++) {9693xform_rotate[i] = memnew(LineEdit);9694xform_rotate[i]->set_h_size_flags(SIZE_EXPAND_FILL);9695xform_rotate[i]->set_select_all_on_focus(true);9696xform_hbc->add_child(xform_rotate[i]);9697}96989699l = memnew(Label);9700l->set_text(TTRC("Scale (ratio):"));9701xform_vbc->add_child(l);97029703xform_hbc = memnew(HBoxContainer);9704xform_vbc->add_child(xform_hbc);97059706for (int i = 0; i < 3; i++) {9707xform_scale[i] = memnew(LineEdit);9708xform_scale[i]->set_h_size_flags(SIZE_EXPAND_FILL);9709xform_scale[i]->set_select_all_on_focus(true);9710xform_hbc->add_child(xform_scale[i]);9711}97129713l = memnew(Label);9714l->set_text(TTRC("Transform Type"));9715xform_vbc->add_child(l);97169717xform_type = memnew(OptionButton);9718xform_type->set_h_size_flags(SIZE_EXPAND_FILL);9719xform_type->set_accessibility_name(TTRC("Transform Type"));9720xform_type->add_item(TTRC("Pre"));9721xform_type->add_item(TTRC("Post"));9722xform_vbc->add_child(xform_type);97239724xform_dialog->connect(SceneStringName(confirmed), callable_mp(this, &Node3DEditor::_xform_dialog_action));97259726selected = nullptr;97279728set_process_shortcut_input(true);9729add_to_group(SceneStringName(_spatial_editor_group));97309731current_hover_gizmo_handle = -1;9732current_hover_gizmo_handle_secondary = false;9733{9734// Sun/preview environment popup.9735sun_environ_popup = memnew(PreviewSunEnvPopup);9736add_child(sun_environ_popup);97379738HBoxContainer *sun_environ_hb = memnew(HBoxContainer);97399740sun_environ_popup->add_child(sun_environ_hb);97419742sun_vb = memnew(VBoxContainer);9743sun_environ_hb->add_child(sun_vb);9744sun_vb->set_custom_minimum_size(Size2(200 * EDSCALE, 0));9745sun_vb->hide();97469747sun_title = memnew(Label);9748sun_title->set_theme_type_variation("HeaderMedium");9749sun_vb->add_child(sun_title);9750sun_title->set_text(TTRC("Preview Sun"));9751sun_title->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);97529753CenterContainer *sun_direction_center = memnew(CenterContainer);9754sun_direction = memnew(Control);9755sun_direction->set_custom_minimum_size(Size2(128, 128) * EDSCALE);9756sun_direction_center->add_child(sun_direction);9757sun_vb->add_margin_child(TTRC("Sun Direction"), sun_direction_center);9758sun_direction->connect(SceneStringName(gui_input), callable_mp(this, &Node3DEditor::_sun_direction_input));9759sun_direction->connect(SceneStringName(draw), callable_mp(this, &Node3DEditor::_sun_direction_draw));9760sun_direction->set_default_cursor_shape(CURSOR_MOVE);97619762sun_direction_shader.instantiate();9763sun_direction_shader->set_code(R"(9764// 3D editor Preview Sun direction shader.97659766shader_type canvas_item;97679768uniform vec3 sun_direction;9769uniform vec3 sun_color;97709771void fragment() {9772vec3 n;9773n.xy = UV * 2.0 - 1.0;9774n.z = sqrt(max(0.0, 1.0 - dot(n.xy, n.xy)));9775COLOR.rgb = dot(n, sun_direction) * sun_color;9776COLOR.a = 1.0 - smoothstep(0.99, 1.0, length(n.xy));9777}9778)");9779sun_direction_material.instantiate();9780sun_direction_material->set_shader(sun_direction_shader);9781sun_direction_material->set_shader_parameter("sun_direction", Vector3(0, 0, 1));9782sun_direction_material->set_shader_parameter("sun_color", Vector3(1, 1, 1));9783sun_direction->set_material(sun_direction_material);97849785HBoxContainer *sun_angle_hbox = memnew(HBoxContainer);9786sun_angle_hbox->set_h_size_flags(SIZE_EXPAND_FILL);9787VBoxContainer *sun_angle_altitude_vbox = memnew(VBoxContainer);9788sun_angle_altitude_vbox->set_h_size_flags(SIZE_EXPAND_FILL);9789Label *sun_angle_altitude_label = memnew(Label);9790sun_angle_altitude_label->set_text(TTRC("Angular Altitude"));9791sun_angle_altitude_vbox->add_child(sun_angle_altitude_label);9792sun_angle_altitude = memnew(EditorSpinSlider);9793sun_angle_altitude->set_suffix(U"\u00B0");9794sun_angle_altitude->set_max(90);9795sun_angle_altitude->set_min(-90);9796sun_angle_altitude->set_step(0.1);9797sun_angle_altitude->connect(SceneStringName(value_changed), callable_mp(this, &Node3DEditor::_sun_direction_set_altitude));9798sun_angle_altitude_vbox->add_child(sun_angle_altitude);9799sun_angle_hbox->add_child(sun_angle_altitude_vbox);9800VBoxContainer *sun_angle_azimuth_vbox = memnew(VBoxContainer);9801sun_angle_azimuth_vbox->set_h_size_flags(SIZE_EXPAND_FILL);9802sun_angle_azimuth_vbox->set_custom_minimum_size(Vector2(100, 0));9803Label *sun_angle_azimuth_label = memnew(Label);9804sun_angle_azimuth_label->set_text(TTRC("Azimuth"));9805sun_angle_azimuth_vbox->add_child(sun_angle_azimuth_label);9806sun_angle_azimuth = memnew(EditorSpinSlider);9807sun_angle_azimuth->set_suffix(U"\u00B0");9808sun_angle_azimuth->set_max(180);9809sun_angle_azimuth->set_min(-180);9810sun_angle_azimuth->set_step(0.1);9811sun_angle_azimuth->set_allow_greater(true);9812sun_angle_azimuth->set_allow_lesser(true);9813sun_angle_azimuth->connect(SceneStringName(value_changed), callable_mp(this, &Node3DEditor::_sun_direction_set_azimuth));9814sun_angle_azimuth_vbox->add_child(sun_angle_azimuth);9815sun_angle_hbox->add_child(sun_angle_azimuth_vbox);9816sun_angle_hbox->add_theme_constant_override("separation", 10);9817sun_vb->add_child(sun_angle_hbox);98189819sun_color = memnew(ColorPickerButton);9820sun_color->set_edit_alpha(false);9821sun_vb->add_margin_child(TTRC("Sun Color"), sun_color);9822sun_color->connect("color_changed", callable_mp(this, &Node3DEditor::_sun_set_color));9823sun_color->get_popup()->connect("about_to_popup", callable_mp(EditorNode::get_singleton(), &EditorNode::setup_color_picker).bind(sun_color->get_picker()));98249825sun_energy = memnew(EditorSpinSlider);9826sun_energy->set_max(64.0);9827sun_energy->set_min(0);9828sun_energy->set_step(0.05);9829sun_vb->add_margin_child(TTRC("Sun Energy"), sun_energy);9830sun_energy->connect(SceneStringName(value_changed), callable_mp(this, &Node3DEditor::_sun_set_energy));98319832sun_shadow_max_distance = memnew(EditorSpinSlider);9833sun_vb->add_margin_child(TTRC("Shadow Max Distance"), sun_shadow_max_distance);9834sun_shadow_max_distance->connect(SceneStringName(value_changed), callable_mp(this, &Node3DEditor::_sun_set_shadow_max_distance));9835sun_shadow_max_distance->set_min(1);9836sun_shadow_max_distance->set_max(4096);98379838sun_add_to_scene = memnew(Button);9839sun_add_to_scene->set_text(TTRC("Add Sun to Scene"));9840sun_add_to_scene->set_tooltip_text(TTRC("Adds a DirectionalLight3D node matching the preview sun settings to the current scene.\nHold Shift while clicking to also add the preview environment to the current scene."));9841sun_add_to_scene->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_add_sun_to_scene).bind(false));9842sun_vb->add_spacer();9843sun_vb->add_child(sun_add_to_scene);98449845sun_state = memnew(Label);9846sun_environ_hb->add_child(sun_state);9847sun_state->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);9848sun_state->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);9849sun_state->set_h_size_flags(SIZE_EXPAND_FILL);98509851VSeparator *sc = memnew(VSeparator);9852sc->set_custom_minimum_size(Size2(10 * EDSCALE, 0));9853sc->set_v_size_flags(SIZE_EXPAND_FILL);9854sun_environ_hb->add_child(sc);98559856environ_vb = memnew(VBoxContainer);9857sun_environ_hb->add_child(environ_vb);9858environ_vb->set_custom_minimum_size(Size2(200 * EDSCALE, 0));9859environ_vb->hide();98609861environ_title = memnew(Label);9862environ_title->set_theme_type_variation("HeaderMedium");98639864environ_vb->add_child(environ_title);9865environ_title->set_text(TTRC("Preview Environment"));9866environ_title->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);98679868environ_sky_color = memnew(ColorPickerButton);9869environ_sky_color->set_edit_alpha(false);9870environ_sky_color->connect("color_changed", callable_mp(this, &Node3DEditor::_environ_set_sky_color));9871environ_sky_color->get_popup()->connect("about_to_popup", callable_mp(EditorNode::get_singleton(), &EditorNode::setup_color_picker).bind(environ_sky_color->get_picker()));9872environ_vb->add_margin_child(TTRC("Sky Color"), environ_sky_color);9873environ_ground_color = memnew(ColorPickerButton);9874environ_ground_color->connect("color_changed", callable_mp(this, &Node3DEditor::_environ_set_ground_color));9875environ_ground_color->set_edit_alpha(false);9876environ_ground_color->get_popup()->connect("about_to_popup", callable_mp(EditorNode::get_singleton(), &EditorNode::setup_color_picker).bind(environ_ground_color->get_picker()));9877environ_vb->add_margin_child(TTRC("Ground Color"), environ_ground_color);9878environ_energy = memnew(EditorSpinSlider);9879environ_energy->set_max(8.0);9880environ_energy->set_min(0);9881environ_energy->set_step(0.05);9882environ_energy->connect(SceneStringName(value_changed), callable_mp(this, &Node3DEditor::_environ_set_sky_energy));9883environ_vb->add_margin_child(TTRC("Sky Energy"), environ_energy);9884HBoxContainer *fx_vb = memnew(HBoxContainer);9885fx_vb->set_h_size_flags(SIZE_EXPAND_FILL);98869887environ_ao_button = memnew(Button);9888environ_ao_button->set_text(TTRC("AO"));9889environ_ao_button->set_h_size_flags(SIZE_EXPAND_FILL);9890environ_ao_button->set_toggle_mode(true);9891environ_ao_button->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_environ_set_ao), CONNECT_DEFERRED);9892fx_vb->add_child(environ_ao_button);9893environ_glow_button = memnew(Button);9894environ_glow_button->set_text(TTRC("Glow"));9895environ_glow_button->set_h_size_flags(SIZE_EXPAND_FILL);9896environ_glow_button->set_toggle_mode(true);9897environ_glow_button->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_environ_set_glow), CONNECT_DEFERRED);9898fx_vb->add_child(environ_glow_button);9899environ_tonemap_button = memnew(Button);9900environ_tonemap_button->set_text(TTRC("Tonemap"));9901environ_tonemap_button->set_h_size_flags(SIZE_EXPAND_FILL);9902environ_tonemap_button->set_toggle_mode(true);9903environ_tonemap_button->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_environ_set_tonemap), CONNECT_DEFERRED);9904fx_vb->add_child(environ_tonemap_button);9905environ_gi_button = memnew(Button);9906environ_gi_button->set_text(TTRC("GI"));9907environ_gi_button->set_h_size_flags(SIZE_EXPAND_FILL);9908environ_gi_button->set_toggle_mode(true);9909environ_gi_button->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_environ_set_gi), CONNECT_DEFERRED);9910fx_vb->add_child(environ_gi_button);9911environ_vb->add_margin_child(TTRC("Post Process"), fx_vb);99129913environ_add_to_scene = memnew(Button);9914environ_add_to_scene->set_text(TTRC("Add Environment to Scene"));9915environ_add_to_scene->set_tooltip_text(TTRC("Adds a WorldEnvironment node matching the preview environment settings to the current scene.\nHold Shift while clicking to also add the preview sun to the current scene."));9916environ_add_to_scene->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_add_environment_to_scene).bind(false));9917environ_vb->add_spacer();9918environ_vb->add_child(environ_add_to_scene);99199920environ_state = memnew(Label);9921sun_environ_hb->add_child(environ_state);9922environ_state->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);9923environ_state->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);9924environ_state->set_h_size_flags(SIZE_EXPAND_FILL);99259926preview_sun = memnew(DirectionalLight3D);9927preview_sun->set_shadow(true);9928preview_sun->set_shadow_mode(DirectionalLight3D::SHADOW_PARALLEL_4_SPLITS);9929preview_environment = memnew(WorldEnvironment);9930environment.instantiate();9931preview_environment->set_environment(environment);9932if (GLOBAL_GET("rendering/lights_and_shadows/use_physical_light_units")) {9933camera_attributes.instantiate();9934preview_environment->set_camera_attributes(camera_attributes);9935}9936Ref<Sky> sky;9937sky.instantiate();9938sky_material.instantiate();9939sky->set_material(sky_material);9940environment->set_sky(sky);9941environment->set_background(Environment::BG_SKY);99429943sun_environ_popup->set_process_shortcut_input(true);99449945_load_default_preview_settings();9946_preview_settings_changed();9947}9948clear(); // Make sure values are initialized. Will call _snap_update() for us.9949}9950Node3DEditor::~Node3DEditor() {9951singleton = nullptr;9952memdelete(preview_node);9953if (preview_sun_dangling && preview_sun) {9954memdelete(preview_sun);9955}9956if (preview_env_dangling && preview_environment) {9957memdelete(preview_environment);9958}9959}99609961void Node3DEditorPlugin::make_visible(bool p_visible) {9962if (p_visible) {9963spatial_editor->show();9964spatial_editor->set_process(true);9965spatial_editor->set_physics_process(true);9966spatial_editor->refresh_dirty_gizmos();9967} else {9968spatial_editor->hide();9969spatial_editor->set_process(false);9970spatial_editor->set_physics_process(false);9971}9972}99739974void Node3DEditorPlugin::edit(Object *p_object) {9975spatial_editor->edit(Object::cast_to<Node3D>(p_object));9976}99779978bool Node3DEditorPlugin::handles(Object *p_object) const {9979return p_object->is_class("Node3D");9980}99819982Dictionary Node3DEditorPlugin::get_state() const {9983return spatial_editor->get_state();9984}99859986void Node3DEditorPlugin::set_state(const Dictionary &p_state) {9987spatial_editor->set_state(p_state);9988}99899990Size2i Node3DEditor::get_camera_viewport_size(Camera3D *p_camera) {9991Viewport *viewport = p_camera->get_viewport();99929993Window *window = Object::cast_to<Window>(viewport);9994if (window) {9995return window->get_size();9996}99979998SubViewport *sub_viewport = Object::cast_to<SubViewport>(viewport);9999ERR_FAIL_NULL_V(sub_viewport, Size2i());1000010001if (sub_viewport == EditorNode::get_singleton()->get_scene_root()) {10002return Size2(GLOBAL_GET("display/window/size/viewport_width"), GLOBAL_GET("display/window/size/viewport_height"));10003}1000410005return sub_viewport->get_size();10006}1000710008Vector3 Node3DEditor::snap_point(Vector3 p_target, Vector3 p_start) const {10009if (is_snap_enabled()) {10010real_t snap = get_translate_snap();10011p_target.snapf(snap);10012}10013return p_target;10014}1001510016bool Node3DEditor::is_gizmo_visible() const {10017if (selected) {10018return gizmo.visible && selected->is_transform_gizmo_visible();10019}10020return gizmo.visible;10021}1002210023real_t Node3DEditor::get_translate_snap() const {10024real_t snap_value = snap_translate_value;10025if (Input::get_singleton()->is_key_pressed(Key::SHIFT)) {10026snap_value /= 10.0f;10027}10028return snap_value;10029}1003010031real_t Node3DEditor::get_rotate_snap() const {10032real_t snap_value = snap_rotate_value;10033if (Input::get_singleton()->is_key_pressed(Key::SHIFT)) {10034snap_value /= 3.0f;10035}10036return snap_value;10037}1003810039real_t Node3DEditor::get_scale_snap() const {10040real_t snap_value = snap_scale_value;10041if (Input::get_singleton()->is_key_pressed(Key::SHIFT)) {10042snap_value /= 2.0f;10043}10044return snap_value;10045}1004610047struct _GizmoPluginPriorityComparator {10048bool operator()(const Ref<EditorNode3DGizmoPlugin> &p_a, const Ref<EditorNode3DGizmoPlugin> &p_b) const {10049if (p_a->get_priority() == p_b->get_priority()) {10050return p_a->get_gizmo_name() < p_b->get_gizmo_name();10051}10052return p_a->get_priority() > p_b->get_priority();10053}10054};1005510056struct _GizmoPluginNameComparator {10057bool operator()(const Ref<EditorNode3DGizmoPlugin> &p_a, const Ref<EditorNode3DGizmoPlugin> &p_b) const {10058return p_a->get_gizmo_name() < p_b->get_gizmo_name();10059}10060};1006110062void Node3DEditor::add_gizmo_plugin(Ref<EditorNode3DGizmoPlugin> p_plugin) {10063ERR_FAIL_COND(p_plugin.is_null());1006410065gizmo_plugins_by_priority.push_back(p_plugin);10066gizmo_plugins_by_priority.sort_custom<_GizmoPluginPriorityComparator>();1006710068gizmo_plugins_by_name.push_back(p_plugin);10069gizmo_plugins_by_name.sort_custom<_GizmoPluginNameComparator>();1007010071_update_gizmos_menu();10072}1007310074void Node3DEditor::remove_gizmo_plugin(Ref<EditorNode3DGizmoPlugin> p_plugin) {10075gizmo_plugins_by_priority.erase(p_plugin);10076gizmo_plugins_by_name.erase(p_plugin);10077_update_gizmos_menu();10078}1007910080DynamicBVH::ID Node3DEditor::insert_gizmo_bvh_node(Node3D *p_node, const AABB &p_aabb) {10081return gizmo_bvh.insert(p_aabb, p_node);10082}1008310084void Node3DEditor::update_gizmo_bvh_node(DynamicBVH::ID p_id, const AABB &p_aabb) {10085gizmo_bvh.update(p_id, p_aabb);10086gizmo_bvh.optimize_incremental(1);10087}1008810089void Node3DEditor::remove_gizmo_bvh_node(DynamicBVH::ID p_id) {10090gizmo_bvh.remove(p_id);10091}1009210093Vector<Node3D *> Node3DEditor::gizmo_bvh_ray_query(const Vector3 &p_ray_start, const Vector3 &p_ray_end) {10094struct Result {10095Vector<Node3D *> nodes;10096bool operator()(void *p_data) {10097nodes.append((Node3D *)p_data);10098return false;10099}10100} result;1010110102gizmo_bvh.ray_query(p_ray_start, p_ray_end, result);1010310104return result.nodes;10105}1010610107Vector<Node3D *> Node3DEditor::gizmo_bvh_frustum_query(const Vector<Plane> &p_frustum) {10108Vector<Vector3> points = Geometry3D::compute_convex_mesh_points(&p_frustum[0], p_frustum.size());1010910110struct Result {10111Vector<Node3D *> nodes;10112bool operator()(void *p_data) {10113nodes.append((Node3D *)p_data);10114return false;10115}10116} result;1011710118gizmo_bvh.convex_query(p_frustum.ptr(), p_frustum.size(), points.ptr(), points.size(), result);1011910120return result.nodes;10121}1012210123Node3DEditorPlugin::Node3DEditorPlugin() {10124spatial_editor = memnew(Node3DEditor);10125spatial_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL);10126EditorNode::get_singleton()->get_editor_main_screen()->get_control()->add_child(spatial_editor);1012710128spatial_editor->hide();10129}101301013110132