Path: blob/master/editor/scene/3d/node_3d_editor_plugin.cpp
20929 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/geometry_3d.h"36#include "core/math/math_funcs.h"37#include "core/math/projection.h"38#include "core/os/keyboard.h"39#include "core/string/translation_server.h"40#include "editor/animation/animation_player_editor_plugin.h"41#include "editor/debugger/editor_debugger_node.h"42#include "editor/docks/scene_tree_dock.h"43#include "editor/editor_main_screen.h"44#include "editor/editor_node.h"45#include "editor/editor_string_names.h"46#include "editor/editor_undo_redo_manager.h"47#include "editor/gui/editor_spin_slider.h"48#include "editor/plugins/editor_plugin_list.h"49#include "editor/run/editor_run_bar.h"50#include "editor/scene/3d/gizmos/audio_listener_3d_gizmo_plugin.h"51#include "editor/scene/3d/gizmos/audio_stream_player_3d_gizmo_plugin.h"52#include "editor/scene/3d/gizmos/camera_3d_gizmo_plugin.h"53#include "editor/scene/3d/gizmos/chain_ik_3d_gizmo_plugin.h"54#include "editor/scene/3d/gizmos/cpu_particles_3d_gizmo_plugin.h"55#include "editor/scene/3d/gizmos/decal_gizmo_plugin.h"56#include "editor/scene/3d/gizmos/fog_volume_gizmo_plugin.h"57#include "editor/scene/3d/gizmos/geometry_instance_3d_gizmo_plugin.h"58#include "editor/scene/3d/gizmos/gpu_particles_3d_gizmo_plugin.h"59#include "editor/scene/3d/gizmos/gpu_particles_collision_3d_gizmo_plugin.h"60#include "editor/scene/3d/gizmos/label_3d_gizmo_plugin.h"61#include "editor/scene/3d/gizmos/light_3d_gizmo_plugin.h"62#include "editor/scene/3d/gizmos/lightmap_gi_gizmo_plugin.h"63#include "editor/scene/3d/gizmos/lightmap_probe_gizmo_plugin.h"64#include "editor/scene/3d/gizmos/marker_3d_gizmo_plugin.h"65#include "editor/scene/3d/gizmos/mesh_instance_3d_gizmo_plugin.h"66#include "editor/scene/3d/gizmos/occluder_instance_3d_gizmo_plugin.h"67#include "editor/scene/3d/gizmos/particles_3d_emission_shape_gizmo_plugin.h"68#include "editor/scene/3d/gizmos/physics/collision_object_3d_gizmo_plugin.h"69#include "editor/scene/3d/gizmos/physics/collision_polygon_3d_gizmo_plugin.h"70#include "editor/scene/3d/gizmos/physics/collision_shape_3d_gizmo_plugin.h"71#include "editor/scene/3d/gizmos/physics/joint_3d_gizmo_plugin.h"72#include "editor/scene/3d/gizmos/physics/physics_bone_3d_gizmo_plugin.h"73#include "editor/scene/3d/gizmos/physics/ray_cast_3d_gizmo_plugin.h"74#include "editor/scene/3d/gizmos/physics/shape_cast_3d_gizmo_plugin.h"75#include "editor/scene/3d/gizmos/physics/soft_body_3d_gizmo_plugin.h"76#include "editor/scene/3d/gizmos/physics/spring_arm_3d_gizmo_plugin.h"77#include "editor/scene/3d/gizmos/physics/vehicle_body_3d_gizmo_plugin.h"78#include "editor/scene/3d/gizmos/reflection_probe_gizmo_plugin.h"79#include "editor/scene/3d/gizmos/spring_bone_3d_gizmo_plugin.h"80#include "editor/scene/3d/gizmos/sprite_base_3d_gizmo_plugin.h"81#include "editor/scene/3d/gizmos/two_bone_ik_3d_gizmo_plugin.h"82#include "editor/scene/3d/gizmos/visible_on_screen_notifier_3d_gizmo_plugin.h"83#include "editor/scene/3d/gizmos/voxel_gi_gizmo_plugin.h"84#include "editor/scene/3d/node_3d_editor_gizmos.h"85#include "editor/settings/editor_settings.h"86#include "editor/translations/editor_translation_preview_button.h"87#include "editor/translations/editor_translation_preview_menu.h"88#include "scene/3d/audio_stream_player_3d.h"89#include "scene/3d/camera_3d.h"90#include "scene/3d/decal.h"91#include "scene/3d/light_3d.h"92#include "scene/3d/mesh_instance_3d.h"93#include "scene/3d/physics/collision_shape_3d.h"94#include "scene/3d/physics/physics_body_3d.h"95#include "scene/3d/sprite_3d.h"96#include "scene/3d/visual_instance_3d.h"97#include "scene/3d/world_environment.h"98#include "scene/gui/center_container.h"99#include "scene/gui/color_picker.h"100#include "scene/gui/flow_container.h"101#include "scene/gui/separator.h"102#include "scene/gui/split_container.h"103#include "scene/gui/subviewport_container.h"104#include "scene/resources/3d/sky_material.h"105#include "scene/resources/packed_scene.h"106#include "scene/resources/sky.h"107#include "scene/resources/surface_tool.h"108109constexpr real_t DISTANCE_DEFAULT = 4;110111constexpr real_t GIZMO_ARROW_SIZE = 0.35;112constexpr real_t GIZMO_RING_HALF_WIDTH = 0.1;113constexpr real_t GIZMO_PLANE_SIZE = 0.2;114constexpr real_t GIZMO_PLANE_DST = 0.3;115constexpr real_t GIZMO_CIRCLE_SIZE = 1.1;116constexpr real_t GIZMO_VIEW_ROTATION_SIZE = 1.25;117constexpr real_t GIZMO_SCALE_OFFSET = GIZMO_CIRCLE_SIZE + 0.3;118constexpr real_t GIZMO_ARROW_OFFSET = GIZMO_CIRCLE_SIZE + 0.3;119120constexpr real_t TRACKBALL_SENSITIVITY = 0.005;121constexpr int TRACKBALL_SPHERE_RINGS = 16;122constexpr int TRACKBALL_SPHERE_SECTORS = 32;123constexpr real_t TRACKBALL_HIGHLIGHT_ALPHA = 0.01;124constexpr int GIZMO_HIGHLIGHT_AXIS_VIEW_ROTATION = 15;125constexpr int GIZMO_HIGHLIGHT_AXIS_TRACKBALL = 16;126127constexpr real_t ZOOM_FREELOOK_MIN = 0.01;128constexpr real_t ZOOM_FREELOOK_MULTIPLIER = 1.08;129constexpr real_t ZOOM_FREELOOK_INDICATOR_DELAY_S = 1.5;130131#ifdef REAL_T_IS_DOUBLE132constexpr double ZOOM_FREELOOK_MAX = 1'000'000'000'000;133#else134constexpr float ZOOM_FREELOOK_MAX = 10'000;135#endif136137constexpr real_t MIN_Z = 0.01;138constexpr real_t MAX_Z = 1000000.0;139140constexpr real_t MIN_FOV = 0.01;141constexpr real_t MAX_FOV = 179;142143void ViewportNavigationControl::_notification(int p_what) {144switch (p_what) {145case NOTIFICATION_DRAW: {146if (viewport != nullptr) {147_draw();148_update_navigation();149}150} break;151152case NOTIFICATION_MOUSE_ENTER: {153hovered = true;154queue_redraw();155} break;156157case NOTIFICATION_MOUSE_EXIT: {158hovered = false;159queue_redraw();160} break;161}162}163164void ViewportNavigationControl::_draw() {165if (nav_mode == Node3DEditorViewport::NAVIGATION_NONE) {166return;167}168169Vector2 center = get_size() / 2.0;170float radius = get_size().x / 2.0;171172const bool focused = focused_index != -1;173draw_circle(center, radius, Color(0.5, 0.5, 0.5, focused || hovered ? 0.35 : 0.15));174175const Color c = focused ? Color(0.9, 0.9, 0.9, 0.9) : Color(0.5, 0.5, 0.5, 0.25);176177Vector2 circle_pos = focused ? center.move_toward(focused_pos, radius) : center;178179draw_circle(circle_pos, AXIS_CIRCLE_RADIUS, c);180draw_circle(circle_pos, AXIS_CIRCLE_RADIUS * 0.8, c.darkened(0.4));181}182183void ViewportNavigationControl::_process_click(int p_index, Vector2 p_position, bool p_pressed) {184hovered = false;185queue_redraw();186187if (focused_index != -1 && focused_index != p_index) {188return;189}190if (p_pressed) {191if (p_position.distance_to(get_size() / 2.0) < get_size().x / 2.0) {192focused_pos = p_position;193focused_index = p_index;194queue_redraw();195}196} else {197focused_index = -1;198if (Input::get_singleton()->get_mouse_mode() == Input::MouseMode::MOUSE_MODE_CAPTURED) {199Input::get_singleton()->set_mouse_mode(Input::MouseMode::MOUSE_MODE_VISIBLE);200Input::get_singleton()->warp_mouse(focused_mouse_start);201}202}203}204205void ViewportNavigationControl::_process_drag(int p_index, Vector2 p_position, Vector2 p_relative_position) {206if (focused_index == p_index) {207if (Input::get_singleton()->get_mouse_mode() == Input::MouseMode::MOUSE_MODE_VISIBLE) {208Input::get_singleton()->set_mouse_mode(Input::MouseMode::MOUSE_MODE_CAPTURED);209focused_mouse_start = p_position;210}211focused_pos += p_relative_position;212queue_redraw();213}214}215216void ViewportNavigationControl::gui_input(const Ref<InputEvent> &p_event) {217// Mouse events218const Ref<InputEventMouseButton> mouse_button = p_event;219if (mouse_button.is_valid() && mouse_button->get_button_index() == MouseButton::LEFT) {220_process_click(100, mouse_button->get_position(), mouse_button->is_pressed());221}222223const Ref<InputEventMouseMotion> mouse_motion = p_event;224if (mouse_motion.is_valid()) {225_process_drag(100, mouse_motion->get_global_position(), viewport->_get_warped_mouse_motion(mouse_motion));226}227228// Touch events229const Ref<InputEventScreenTouch> screen_touch = p_event;230if (screen_touch.is_valid()) {231_process_click(screen_touch->get_index(), screen_touch->get_position(), screen_touch->is_pressed());232}233234const Ref<InputEventScreenDrag> screen_drag = p_event;235if (screen_drag.is_valid()) {236_process_drag(screen_drag->get_index(), screen_drag->get_position(), screen_drag->get_relative());237}238}239240void ViewportNavigationControl::_update_navigation() {241if (focused_index == -1) {242return;243}244245Vector2 delta = focused_pos - (get_size() / 2.0);246Vector2 delta_normalized = delta.normalized();247switch (nav_mode) {248case Node3DEditorViewport::NavigationMode::NAVIGATION_MOVE: {249real_t speed_multiplier = MIN(delta.length() / (get_size().x * 100.0), 3.0);250real_t speed = viewport->freelook_speed * speed_multiplier;251252const Node3DEditorViewport::FreelookNavigationScheme navigation_scheme = (Node3DEditorViewport::FreelookNavigationScheme)EDITOR_GET("editors/3d/freelook/freelook_navigation_scheme").operator int();253254Vector3 forward;255if (navigation_scheme == Node3DEditorViewport::FreelookNavigationScheme::FREELOOK_FULLY_AXIS_LOCKED) {256// Forward/backward keys will always go straight forward/backward, never moving on the Y axis.257forward = Vector3(0, 0, delta_normalized.y).rotated(Vector3(0, 1, 0), viewport->camera->get_rotation().y);258} else {259// Forward/backward keys will be relative to the camera pitch.260forward = viewport->camera->get_transform().basis.xform(Vector3(0, 0, delta_normalized.y));261}262263const Vector3 right = viewport->camera->get_transform().basis.xform(Vector3(delta_normalized.x, 0, 0));264265const Vector3 direction = forward + right;266const Vector3 motion = direction * speed;267viewport->cursor.pos += motion;268viewport->cursor.eye_pos += motion;269} break;270271case Node3DEditorViewport::NavigationMode::NAVIGATION_LOOK: {272real_t speed_multiplier = MIN(delta.length() / (get_size().x * 2.5), 3.0);273real_t speed = viewport->freelook_speed * speed_multiplier;274viewport->_nav_look(nullptr, delta_normalized * speed);275} break;276277case Node3DEditorViewport::NAVIGATION_PAN: {278real_t speed_multiplier = MIN(delta.length() / (get_size().x), 3.0);279real_t speed = viewport->freelook_speed * speed_multiplier;280viewport->_nav_pan(nullptr, -delta_normalized * speed);281} break;282case Node3DEditorViewport::NAVIGATION_ZOOM: {283real_t speed_multiplier = MIN(delta.length() / (get_size().x), 3.0);284real_t speed = viewport->freelook_speed * speed_multiplier;285viewport->_nav_zoom(nullptr, delta_normalized * speed);286} break;287case Node3DEditorViewport::NAVIGATION_ORBIT: {288real_t speed_multiplier = MIN(delta.length() / (get_size().x), 3.0);289real_t speed = viewport->freelook_speed * speed_multiplier;290viewport->_nav_orbit(nullptr, delta_normalized * speed);291} break;292case Node3DEditorViewport::NAVIGATION_NONE: {293} break;294}295}296297void ViewportNavigationControl::set_navigation_mode(Node3DEditorViewport::NavigationMode p_nav_mode) {298nav_mode = p_nav_mode;299}300301void ViewportNavigationControl::set_viewport(Node3DEditorViewport *p_viewport) {302viewport = p_viewport;303}304305void ViewportRotationControl::_notification(int p_what) {306switch (p_what) {307case NOTIFICATION_ENTER_TREE: {308axis_menu_options.clear();309axis_menu_options.push_back(Node3DEditorViewport::VIEW_RIGHT);310axis_menu_options.push_back(Node3DEditorViewport::VIEW_TOP);311axis_menu_options.push_back(Node3DEditorViewport::VIEW_FRONT);312axis_menu_options.push_back(Node3DEditorViewport::VIEW_LEFT);313axis_menu_options.push_back(Node3DEditorViewport::VIEW_BOTTOM);314axis_menu_options.push_back(Node3DEditorViewport::VIEW_REAR);315316axis_colors.clear();317axis_colors.push_back(get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor)));318axis_colors.push_back(get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor)));319axis_colors.push_back(get_theme_color(SNAME("axis_z_color"), EditorStringName(Editor)));320queue_redraw();321} break;322323case NOTIFICATION_DRAW: {324if (viewport != nullptr) {325_draw();326}327} break;328329case NOTIFICATION_MOUSE_EXIT: {330focused_axis = -2;331queue_redraw();332} break;333334case NOTIFICATION_WM_WINDOW_FOCUS_OUT: {335gizmo_activated = false;336} break;337}338}339340void ViewportRotationControl::_draw() {341const Vector2 center = get_size() / 2.0;342const real_t radius = get_size().x / 2.0;343344if (focused_axis > -2 || orbiting_index != -1) {345draw_circle(center, radius, Color(0.5, 0.5, 0.5, 0.25), true, -1.0, true);346}347348Vector<Axis2D> axis_to_draw;349_get_sorted_axis(axis_to_draw);350for (int i = 0; i < axis_to_draw.size(); ++i) {351_draw_axis(axis_to_draw[i]);352}353}354355void ViewportRotationControl::_draw_axis(const Axis2D &p_axis) {356const bool focused = focused_axis == p_axis.axis;357const bool positive = p_axis.is_positive;358const int direction = p_axis.axis % 3;359360const Color axis_color = axis_colors[direction];361const double min_alpha = 0.35;362const double alpha = focused ? 1.0 : Math::remap((p_axis.z_axis + 1.0) / 2.0, 0, 0.5, min_alpha, 1.0);363const Color c = focused ? Color(axis_color.lightened(0.25), 1.0) : Color(axis_color, alpha);364365// Highlight positive axis text when hovered.366const Color c_positive_axis = focused ? Color(1.0, 1.0, 1.0, alpha) : Color(0.0, 0.0, 0.0, alpha * 0.6);367368// Highlight negative axis text when hovered, but hide when not focused.369const Color c_negative_axis = focused ? Color(1.0, 1.0, 1.0, alpha) : Color(axis_color, 0);370371if (positive) {372// Draw axis lines for the positive axes.373const Vector2 center = get_size() / 2.0;374const Vector2 diff = p_axis.screen_point - center;375const float line_length = MAX(diff.length() - AXIS_CIRCLE_RADIUS - 0.5 * EDSCALE, 0);376377draw_line(center + diff.limit_length(0.5 * EDSCALE), center + diff.limit_length(line_length), c, 1.5 * EDSCALE, true);378379draw_circle(p_axis.screen_point, AXIS_CIRCLE_RADIUS, c, true, -1.0, true);380381// Draw the axis letter for the positive axes.382const String axis_name = direction == 0 ? "X" : (direction == 1 ? "Y" : "Z");383const Ref<Font> &font = get_theme_font(SNAME("rotation_control"), EditorStringName(EditorFonts));384const int font_size = get_theme_font_size(SNAME("rotation_control_size"), EditorStringName(EditorFonts));385const Size2 char_size = font->get_char_size(axis_name[0], font_size);386const Vector2 char_offset = Vector2(-char_size.width / 2.0, char_size.height * 0.25);387draw_char(font, p_axis.screen_point + char_offset, axis_name, font_size, c_positive_axis);388} else {389// Draw an outline around the negative axes.390draw_circle(p_axis.screen_point, AXIS_CIRCLE_RADIUS, c, true, -1.0, true);391draw_circle(p_axis.screen_point, AXIS_CIRCLE_RADIUS * 0.8, c.darkened(0.4), true, -1.0, true);392393// Draw the text for the negative axes.394const String axis_name = direction == 0 ? "-X" : (direction == 1 ? "-Y" : "-Z");395const Ref<Font> &font = get_theme_font(SNAME("rotation_control"), EditorStringName(EditorFonts));396const int font_size = get_theme_font_size(SNAME("rotation_control_size"), EditorStringName(EditorFonts));397const Size2 string_size = font->get_string_size(axis_name, HORIZONTAL_ALIGNMENT_LEFT, -1.0f, font_size);398const float font_ascent = font->get_ascent(font_size);399const float font_descent = font->get_descent(font_size);400const float string_height = font_ascent + font_descent;401const Vector2 offset(-string_size.width / 2.0, string_height * 0.25);402draw_string(font, p_axis.screen_point + offset, axis_name, HORIZONTAL_ALIGNMENT_LEFT, -1.0f, font_size, c_negative_axis);403}404}405406void ViewportRotationControl::_get_sorted_axis(Vector<Axis2D> &r_axis) {407const Vector2 center = get_size() / 2.0;408const real_t radius = get_size().x / 2.0 - AXIS_CIRCLE_RADIUS - 2.0 * EDSCALE;409const Basis camera_basis = viewport->to_camera_transform(viewport->cursor).get_basis().inverse();410411for (int i = 0; i < 3; ++i) {412Vector3 axis_3d = camera_basis.get_column(i);413Vector2 axis_vector = Vector2(axis_3d.x, -axis_3d.y) * radius;414415if (Math::abs(axis_3d.z) < 1.0) {416Axis2D pos_axis;417pos_axis.axis = i;418pos_axis.screen_point = center + axis_vector;419pos_axis.z_axis = axis_3d.z;420pos_axis.is_positive = true;421r_axis.push_back(pos_axis);422423Axis2D neg_axis;424neg_axis.axis = i + 3;425neg_axis.screen_point = center - axis_vector;426neg_axis.z_axis = -axis_3d.z;427neg_axis.is_positive = false;428r_axis.push_back(neg_axis);429} else {430// Special case when the camera is aligned with one axis.431Axis2D axis;432axis.axis = i + (axis_3d.z <= 0 ? 0 : 3);433axis.screen_point = center;434axis.z_axis = 1.0;435// Invert display style to fix aligned axis rendering.436axis.is_positive = (axis_3d.z > 0);437r_axis.push_back(axis);438}439}440441r_axis.sort_custom<Axis2DCompare>();442}443444void ViewportRotationControl::_process_click(int p_index, Vector2 p_position, bool p_pressed) {445if (orbiting_index != -1 && orbiting_index != p_index) {446return;447}448if (p_pressed) {449if (p_position.distance_to(get_size() / 2.0) < get_size().x / 2.0) {450orbiting_index = p_index;451}452} else {453if (focused_axis > -1 && gizmo_activated) {454viewport->_menu_option(axis_menu_options[focused_axis]);455_update_focus();456}457orbiting_index = -1;458if (Input::get_singleton()->get_mouse_mode() == Input::MouseMode::MOUSE_MODE_CAPTURED) {459Input::get_singleton()->set_mouse_mode(Input::MouseMode::MOUSE_MODE_VISIBLE);460Input::get_singleton()->warp_mouse(orbiting_mouse_start);461}462}463}464465void ViewportRotationControl::_process_drag(Ref<InputEventWithModifiers> p_event, int p_index, Vector2 p_position, Vector2 p_relative_position) {466Point2 mouse_pos = get_local_mouse_position();467const bool movement_threshold_passed = original_mouse_pos.distance_to(mouse_pos) > 4 * EDSCALE;468if (orbiting_index == p_index && gizmo_activated && movement_threshold_passed) {469if (Input::get_singleton()->get_mouse_mode() == Input::MouseMode::MOUSE_MODE_VISIBLE) {470Input::get_singleton()->set_mouse_mode(Input::MouseMode::MOUSE_MODE_CAPTURED);471orbiting_mouse_start = p_position;472viewport->previous_cursor = viewport->cursor;473}474viewport->_nav_orbit(p_event, p_relative_position);475focused_axis = -1;476} else {477_update_focus();478}479}480481void ViewportRotationControl::gui_input(const Ref<InputEvent> &p_event) {482ERR_FAIL_COND(p_event.is_null());483484// Key events485const Ref<InputEventKey> k = p_event;486487if (k.is_valid() && k->is_action_pressed(SNAME("ui_cancel"), false, true)) {488if (Input::get_singleton()->get_mouse_mode() == Input::MouseMode::MOUSE_MODE_CAPTURED) {489Input::get_singleton()->set_mouse_mode(Input::MouseMode::MOUSE_MODE_VISIBLE);490Input::get_singleton()->warp_mouse(orbiting_mouse_start);491viewport->cursor = viewport->previous_cursor;492gizmo_activated = false;493}494}495496// Mouse events497const Ref<InputEventMouseButton> mb = p_event;498if (mb.is_valid()) {499if (mb->get_button_index() == MouseButton::LEFT) {500_process_click(100, mb->get_position(), mb->is_pressed());501if (mb->is_pressed()) {502gizmo_activated = true;503original_mouse_pos = get_local_mouse_position();504grab_focus();505}506} else if (mb->get_button_index() == MouseButton::RIGHT) {507if (Input::get_singleton()->get_mouse_mode() == Input::MouseMode::MOUSE_MODE_CAPTURED) {508Input::get_singleton()->set_mouse_mode(Input::MouseMode::MOUSE_MODE_VISIBLE);509Input::get_singleton()->warp_mouse(orbiting_mouse_start);510viewport->cursor = viewport->previous_cursor;511gizmo_activated = false;512}513}514}515516const Ref<InputEventMouseMotion> mm = p_event;517if (mm.is_valid()) {518_process_drag(mm, 100, mm->get_global_position(), viewport->_get_warped_mouse_motion(mm));519}520521// Touch events522const Ref<InputEventScreenTouch> screen_touch = p_event;523if (screen_touch.is_valid()) {524_process_click(screen_touch->get_index(), screen_touch->get_position(), screen_touch->is_pressed());525}526527const Ref<InputEventScreenDrag> screen_drag = p_event;528if (screen_drag.is_valid()) {529_process_drag(nullptr, screen_drag->get_index(), screen_drag->get_position(), screen_drag->get_relative());530}531}532533void ViewportRotationControl::_update_focus() {534int original_focus = focused_axis;535focused_axis = -2;536Vector2 mouse_pos = get_local_mouse_position();537538if (mouse_pos.distance_to(get_size() / 2.0) < get_size().x / 2.0) {539focused_axis = -1;540}541542Vector<Axis2D> axes;543_get_sorted_axis(axes);544545for (int i = 0; i < axes.size(); i++) {546const Axis2D &axis = axes[i];547if (mouse_pos.distance_to(axis.screen_point) < AXIS_CIRCLE_RADIUS) {548focused_axis = axis.axis;549}550}551552if (focused_axis != original_focus) {553queue_redraw();554}555}556557void ViewportRotationControl::set_viewport(Node3DEditorViewport *p_viewport) {558viewport = p_viewport;559}560561void Node3DEditorViewport::_view_settings_confirmed(real_t p_interp_delta) {562// Set FOV override multiplier back to the default, so that the FOV563// setting specified in the View menu is correctly applied.564cursor.fov_scale = 1.0;565566_update_camera(p_interp_delta);567}568569void Node3DEditorViewport::_update_navigation_controls_visibility() {570bool show_viewport_rotation_gizmo = EDITOR_GET("editors/3d/navigation/show_viewport_rotation_gizmo") && (!previewing_cinema && !previewing_camera);571rotation_control->set_visible(show_viewport_rotation_gizmo);572573bool show_viewport_navigation_gizmo = EDITOR_GET("editors/3d/navigation/show_viewport_navigation_gizmo") && (!previewing_cinema && !previewing_camera);574position_control->set_visible(show_viewport_navigation_gizmo);575look_control->set_visible(show_viewport_navigation_gizmo);576}577578bool Node3DEditorViewport::_is_rotation_arc_visible() const {579return _edit.mode == TRANSFORM_ROTATE && _edit.accumulated_rotation_angle != 0.0 && _edit.gizmo_initiated;580}581582void Node3DEditorViewport::_update_camera(real_t p_interp_delta) {583bool is_orthogonal = camera->get_projection() == Camera3D::PROJECTION_ORTHOGONAL;584585Cursor old_camera_cursor = camera_cursor;586camera_cursor = cursor;587588if (p_interp_delta > 0) {589//-------590// Perform smoothing591592if (is_freelook_active()) {593// Higher inertia should increase "lag" (lerp with factor between 0 and 1)594// 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.595const real_t inertia = EDITOR_GET("editors/3d/freelook/freelook_inertia");596real_t factor = (1.0 / inertia) * p_interp_delta;597598// We interpolate a different point here, because in freelook mode the focus point (cursor.pos) orbits around eye_pos599camera_cursor.eye_pos = old_camera_cursor.eye_pos.lerp(cursor.eye_pos, CLAMP(factor, 0, 1));600601const real_t orbit_inertia = EDITOR_GET("editors/3d/navigation_feel/orbit_inertia");602camera_cursor.x_rot = Math::lerp(old_camera_cursor.x_rot, cursor.x_rot, MIN(1.f, p_interp_delta * (1 / orbit_inertia)));603camera_cursor.y_rot = Math::lerp(old_camera_cursor.y_rot, cursor.y_rot, MIN(1.f, p_interp_delta * (1 / orbit_inertia)));604605if (Math::abs(camera_cursor.x_rot - cursor.x_rot) < 0.1) {606camera_cursor.x_rot = cursor.x_rot;607}608609if (Math::abs(camera_cursor.y_rot - cursor.y_rot) < 0.1) {610camera_cursor.y_rot = cursor.y_rot;611}612613Vector3 forward = to_camera_transform(camera_cursor).basis.xform(Vector3(0, 0, -1));614camera_cursor.pos = camera_cursor.eye_pos + forward * camera_cursor.distance;615616} else {617const real_t orbit_inertia = EDITOR_GET("editors/3d/navigation_feel/orbit_inertia");618const real_t translation_inertia = EDITOR_GET("editors/3d/navigation_feel/translation_inertia");619const real_t zoom_inertia = EDITOR_GET("editors/3d/navigation_feel/zoom_inertia");620621camera_cursor.x_rot = Math::lerp(old_camera_cursor.x_rot, cursor.x_rot, MIN(1.f, p_interp_delta * (1 / orbit_inertia)));622camera_cursor.y_rot = Math::lerp(old_camera_cursor.y_rot, cursor.y_rot, MIN(1.f, p_interp_delta * (1 / orbit_inertia)));623624if (Math::abs(camera_cursor.x_rot - cursor.x_rot) < 0.1) {625camera_cursor.x_rot = cursor.x_rot;626}627628if (Math::abs(camera_cursor.y_rot - cursor.y_rot) < 0.1) {629camera_cursor.y_rot = cursor.y_rot;630}631632camera_cursor.pos = old_camera_cursor.pos.lerp(cursor.pos, MIN(1.f, p_interp_delta * (1 / translation_inertia)));633camera_cursor.distance = Math::lerp(old_camera_cursor.distance, cursor.distance, MIN((real_t)1.0, p_interp_delta * (1 / zoom_inertia)));634}635}636637//-------638// Apply camera transform639640real_t tolerance = 0.001;641bool equal = true;642if (!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)) {643equal = false;644} else if (!old_camera_cursor.pos.is_equal_approx(camera_cursor.pos)) {645equal = false;646} else if (!Math::is_equal_approx(old_camera_cursor.distance, camera_cursor.distance, tolerance)) {647equal = false;648} else if (!Math::is_equal_approx(old_camera_cursor.fov_scale, camera_cursor.fov_scale, tolerance)) {649equal = false;650}651652if (!equal || p_interp_delta == 0 || is_orthogonal != orthogonal) {653last_camera_transform = to_camera_transform(camera_cursor);654camera->set_global_transform(last_camera_transform);655656if (orthogonal) {657float half_fov = Math::deg_to_rad(get_fov()) / 2.0;658float height = 2.0 * cursor.distance * Math::tan(half_fov);659camera->set_orthogonal(height, get_znear(), get_zfar());660} else {661camera->set_perspective(get_fov(), get_znear(), get_zfar());662}663664update_transform_gizmo_view();665rotation_control->queue_redraw();666position_control->queue_redraw();667look_control->queue_redraw();668spatial_editor->update_grid();669}670}671672Transform3D Node3DEditorViewport::to_camera_transform(const Cursor &p_cursor) const {673Transform3D camera_transform;674camera_transform.translate_local(p_cursor.pos);675camera_transform.basis.rotate(Vector3(1, 0, 0), -p_cursor.x_rot);676camera_transform.basis.rotate(Vector3(0, 1, 0), -p_cursor.y_rot);677678if (orthogonal) {679camera_transform.translate_local(0, 0, (get_zfar() - get_znear()) / 2.0);680} else {681camera_transform.translate_local(0, 0, p_cursor.distance);682}683684return camera_transform;685}686687int Node3DEditorViewport::get_selected_count() const {688const HashMap<ObjectID, Object *> &selection = editor_selection->get_selection();689690int count = 0;691692for (const KeyValue<ObjectID, Object *> &E : selection) {693Node3D *sp = ObjectDB::get_instance<Node3D>(E.key);694if (!sp) {695continue;696}697698Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);699if (!se) {700continue;701}702703count++;704}705706return count;707}708709void Node3DEditorViewport::cancel_transform() {710const List<Node *> &selection = editor_selection->get_top_selected_node_list();711712for (Node *E : selection) {713Node3D *sp = Object::cast_to<Node3D>(E);714if (!sp) {715continue;716}717718Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);719if (!se) {720continue;721}722723if (se && se->gizmo.is_valid()) {724Vector<int> ids;725Vector<Transform3D> restore;726727for (const KeyValue<int, Transform3D> &GE : se->subgizmos) {728ids.push_back(GE.key);729restore.push_back(GE.value);730}731732se->gizmo->commit_subgizmos(ids, restore, true);733}734735sp->set_global_transform(se->original);736}737738collision_reposition = false;739finish_transform();740set_message(TTRC("Transform Aborted."), 3);741}742743void Node3DEditorViewport::_update_shrink() {744const float scaling_3d_scale = GLOBAL_GET("rendering/scaling_3d/scale");745const float shrink_factor = view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_HALF_RESOLUTION)) ? 0.5 : 1.0;746viewport->set_scaling_3d_scale(MAX(0.25, scaling_3d_scale * shrink_factor));747}748749float Node3DEditorViewport::get_znear() const {750return CLAMP(spatial_editor->get_znear(), MIN_Z, MAX_Z);751}752753float Node3DEditorViewport::get_zfar() const {754return CLAMP(spatial_editor->get_zfar(), MIN_Z, MAX_Z);755}756757float Node3DEditorViewport::get_fov() const {758return CLAMP(spatial_editor->get_fov() * cursor.fov_scale, MIN_FOV, MAX_FOV);759}760761Transform3D Node3DEditorViewport::_get_camera_transform() const {762return camera->get_global_transform();763}764765Vector3 Node3DEditorViewport::_get_camera_position() const {766return _get_camera_transform().origin;767}768769Point2 Node3DEditorViewport::point_to_screen(const Vector3 &p_point) {770return camera->unproject_position(p_point);771}772773Vector3 Node3DEditorViewport::get_ray_pos(const Vector2 &p_pos) const {774return camera->project_ray_origin(p_pos);775}776777Vector3 Node3DEditorViewport::_get_camera_normal() const {778return -_get_camera_transform().basis.get_column(2);779}780781Vector3 Node3DEditorViewport::get_ray(const Vector2 &p_pos) const {782return camera->project_ray_normal(p_pos);783}784785void Node3DEditorViewport::_clear_selected() {786_edit.gizmo = Ref<EditorNode3DGizmo>();787_edit.gizmo_handle = -1;788_edit.gizmo_handle_secondary = false;789_edit.gizmo_initial_value = Variant();790791Node3D *selected = spatial_editor->get_single_selected_node();792Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr;793794if (se && se->gizmo.is_valid()) {795se->subgizmos.clear();796se->gizmo->redraw();797se->gizmo.unref();798spatial_editor->update_transform_gizmo();799} else {800editor_selection->clear();801Node3DEditor::get_singleton()->edit(nullptr);802}803}804805void Node3DEditorViewport::_select_clicked(bool p_allow_locked) {806Node *node = ObjectDB::get_instance<Node3D>(clicked);807Node3D *selected = Object::cast_to<Node3D>(node);808clicked = ObjectID();809810if (!selected) {811return;812}813814Node *edited_scene = EditorNode::get_singleton()->get_edited_scene();815816// Prevent selection of nodes not owned by the edited scene.817while (node && node != edited_scene->get_parent()) {818Node *node_owner = node->get_owner();819if (node_owner == edited_scene || node == edited_scene || (node_owner != nullptr && edited_scene->is_editable_instance(node_owner))) {820break;821}822node = node->get_parent();823selected = Object::cast_to<Node3D>(node);824}825826if (!p_allow_locked) {827// Replace the node by the group if grouped828while (node && node != edited_scene->get_parent()) {829Node3D *selected_tmp = Object::cast_to<Node3D>(node);830if (selected_tmp && node->has_meta("_edit_group_")) {831selected = selected_tmp;832}833node = node->get_parent();834}835}836837if (p_allow_locked || (selected != nullptr && !_is_node_locked(selected))) {838if (clicked_wants_append) {839const List<Node *> &top_node_list = editor_selection->get_top_selected_node_list();840const Node *active_node = top_node_list.is_empty() ? nullptr : top_node_list.back()->get();841if (editor_selection->is_selected(selected)) {842editor_selection->remove_node(selected);843if (selected != active_node) {844editor_selection->add_node(selected);845}846} else {847editor_selection->add_node(selected);848}849} else {850if (!editor_selection->is_selected(selected)) {851editor_selection->clear();852editor_selection->add_node(selected);853EditorNode::get_singleton()->edit_node(selected);854}855}856857const List<Node *> &top_node_list = editor_selection->get_top_selected_node_list();858if (top_node_list.size() == 1) {859EditorNode::get_singleton()->edit_node(top_node_list.front()->get());860}861}862}863864ObjectID Node3DEditorViewport::_select_ray(const Point2 &p_pos) const {865Vector3 ray = get_ray(p_pos);866Vector3 pos = get_ray_pos(p_pos);867Vector2 shrinked_pos = p_pos;868869if (viewport->get_debug_draw() == Viewport::DEBUG_DRAW_SDFGI_PROBES) {870RS::get_singleton()->sdfgi_set_debug_probe_select(pos, ray);871}872873HashSet<Ref<EditorNode3DGizmo>> found_gizmos;874875Node *edited_scene = get_tree()->get_edited_scene_root();876ObjectID closest;877Node *item = nullptr;878float closest_dist = 1e20;879880Vector<Node3D *> nodes_with_gizmos = Node3DEditor::get_singleton()->gizmo_bvh_ray_query(pos, pos + ray * camera->get_far());881882for (Node3D *spat : nodes_with_gizmos) {883if (!spat || _is_node_locked(spat)) {884continue;885}886887Vector<Ref<Node3DGizmo>> gizmos = spat->get_gizmos();888889for (int j = 0; j < gizmos.size(); j++) {890Ref<EditorNode3DGizmo> seg = gizmos[j];891892if (seg.is_null() || found_gizmos.has(seg)) {893continue;894}895896found_gizmos.insert(seg);897Vector3 point;898Vector3 normal;899900bool inters = seg->intersect_ray(camera, shrinked_pos, point, normal);901902if (!inters) {903continue;904}905906const real_t dist = pos.distance_to(point);907908if (dist < 0) {909continue;910}911912if (dist < closest_dist) {913item = Object::cast_to<Node>(spat);914if (item != edited_scene) {915item = edited_scene->get_deepest_editable_node(item);916}917918closest = item->get_instance_id();919closest_dist = dist;920}921}922}923924if (!item) {925return ObjectID();926}927928return closest;929}930931void Node3DEditorViewport::_find_items_at_pos(const Point2 &p_pos, Vector<_RayResult> &r_results, bool p_include_locked_nodes) {932Vector3 ray = get_ray(p_pos);933Vector3 pos = get_ray_pos(p_pos);934935Vector<Node3D *> nodes_with_gizmos = Node3DEditor::get_singleton()->gizmo_bvh_ray_query(pos, pos + ray * camera->get_far());936937HashSet<Node3D *> found_nodes;938939for (Node3D *spat : nodes_with_gizmos) {940if (!spat) {941continue;942}943944if (found_nodes.has(spat)) {945continue;946}947948if (!p_include_locked_nodes && _is_node_locked(spat)) {949continue;950}951952Vector<Ref<Node3DGizmo>> gizmos = spat->get_gizmos();953for (int j = 0; j < gizmos.size(); j++) {954Ref<EditorNode3DGizmo> seg = gizmos[j];955956if (seg.is_null()) {957continue;958}959960Vector3 point;961Vector3 normal;962963bool inters = seg->intersect_ray(camera, p_pos, point, normal);964965if (!inters) {966continue;967}968969const real_t dist = pos.distance_to(point);970971if (dist < 0) {972continue;973}974975found_nodes.insert(spat);976977_RayResult res;978res.item = spat;979res.depth = dist;980r_results.push_back(res);981break;982}983}984985r_results.sort();986}987988Vector3 Node3DEditorViewport::_get_screen_to_space(const Vector3 &p_vector3) {989Projection cm;990if (orthogonal) {991cm.set_orthogonal(camera->get_size(), get_size().aspect(), get_znear() + p_vector3.z, get_zfar());992} else {993cm.set_perspective(get_fov(), get_size().aspect(), get_znear() + p_vector3.z, get_zfar());994}995Vector2 screen_he = cm.get_viewport_half_extents();996997Transform3D camera_transform;998camera_transform.translate_local(cursor.pos);999camera_transform.basis.rotate(Vector3(1, 0, 0), -cursor.x_rot);1000camera_transform.basis.rotate(Vector3(0, 1, 0), -cursor.y_rot);1001camera_transform.translate_local(0, 0, cursor.distance);10021003return 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)));1004}10051006void Node3DEditorViewport::_select_region() {1007if (cursor.region_begin == cursor.region_end) {1008if (!clicked_wants_append) {1009_clear_selected();1010}1011return; //nothing really1012}10131014const real_t z_offset = MAX(0.0, 5.0 - get_znear());10151016Vector3 box[4] = {1017Vector3(1018MIN(cursor.region_begin.x, cursor.region_end.x),1019MIN(cursor.region_begin.y, cursor.region_end.y),1020z_offset),1021Vector3(1022MAX(cursor.region_begin.x, cursor.region_end.x),1023MIN(cursor.region_begin.y, cursor.region_end.y),1024z_offset),1025Vector3(1026MAX(cursor.region_begin.x, cursor.region_end.x),1027MAX(cursor.region_begin.y, cursor.region_end.y),1028z_offset),1029Vector3(1030MIN(cursor.region_begin.x, cursor.region_end.x),1031MAX(cursor.region_begin.y, cursor.region_end.y),1032z_offset)1033};10341035Vector<Plane> frustum;10361037Vector3 cam_pos = _get_camera_position();10381039for (int i = 0; i < 4; i++) {1040Vector3 a = _get_screen_to_space(box[i]);1041Vector3 b = _get_screen_to_space(box[(i + 1) % 4]);1042if (orthogonal) {1043frustum.push_back(Plane((a - b).normalized(), a));1044} else {1045frustum.push_back(Plane(a, b, cam_pos));1046}1047}10481049Plane near_plane = Plane(-_get_camera_normal(), cam_pos);1050near_plane.d -= get_znear();1051frustum.push_back(near_plane);10521053Plane far_plane = -near_plane;1054far_plane.d += get_zfar();1055frustum.push_back(far_plane);10561057if (spatial_editor->get_single_selected_node()) {1058Node3D *single_selected = spatial_editor->get_single_selected_node();1059Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(single_selected);10601061if (se) {1062Ref<EditorNode3DGizmo> old_gizmo;1063if (!clicked_wants_append) {1064se->subgizmos.clear();1065old_gizmo = se->gizmo;1066se->gizmo.unref();1067}10681069bool found_subgizmos = false;1070Vector<Ref<Node3DGizmo>> gizmos = single_selected->get_gizmos();1071for (int j = 0; j < gizmos.size(); j++) {1072Ref<EditorNode3DGizmo> seg = gizmos[j];1073if (seg.is_null()) {1074continue;1075}10761077if (se->gizmo.is_valid() && se->gizmo != seg) {1078continue;1079}10801081Vector<int> subgizmos = seg->subgizmos_intersect_frustum(camera, frustum);1082if (!subgizmos.is_empty()) {1083se->gizmo = seg;1084for (int i = 0; i < subgizmos.size(); i++) {1085int subgizmo_id = subgizmos[i];1086if (!se->subgizmos.has(subgizmo_id)) {1087se->subgizmos.insert(subgizmo_id, se->gizmo->get_subgizmo_transform(subgizmo_id));1088}1089}1090found_subgizmos = true;1091break;1092}1093}10941095if (!clicked_wants_append || found_subgizmos) {1096if (se->gizmo.is_valid()) {1097se->gizmo->redraw();1098}10991100if (old_gizmo != se->gizmo && old_gizmo.is_valid()) {1101old_gizmo->redraw();1102}11031104spatial_editor->update_transform_gizmo();1105}11061107if (found_subgizmos) {1108return;1109}1110}1111}11121113if (!clicked_wants_append) {1114_clear_selected();1115}11161117Vector<Node3D *> nodes_with_gizmos = Node3DEditor::get_singleton()->gizmo_bvh_frustum_query(frustum);1118HashSet<Node3D *> found_nodes;1119Vector<Node *> selected;11201121Node *edited_scene = get_tree()->get_edited_scene_root();1122if (edited_scene == nullptr) {1123return;1124}11251126for (Node3D *sp : nodes_with_gizmos) {1127if (!sp || _is_node_locked(sp)) {1128continue;1129}11301131if (found_nodes.has(sp)) {1132continue;1133}1134found_nodes.insert(sp);11351136Node *node = Object::cast_to<Node>(sp);11371138// Selection requires that the node is the edited scene or its descendant, and has an owner.1139if (node != edited_scene) {1140if (!node->get_owner() || !edited_scene->is_ancestor_of(node)) {1141continue;1142}1143node = edited_scene->get_deepest_editable_node(node);1144while (node != edited_scene) {1145Node *node_owner = node->get_owner();1146if (node_owner == edited_scene || (node_owner != nullptr && edited_scene->is_editable_instance(node_owner))) {1147break;1148}1149node = node->get_parent();1150}1151}11521153// Replace the node by the group if grouped1154if (node->is_class("Node3D")) {1155Node3D *sel = Object::cast_to<Node3D>(node);1156while (node && node != EditorNode::get_singleton()->get_edited_scene()->get_parent()) {1157Node3D *selected_tmp = Object::cast_to<Node3D>(node);1158if (selected_tmp && node->has_meta("_edit_group_")) {1159sel = selected_tmp;1160}1161node = node->get_parent();1162}1163node = sel;1164}11651166if (_is_node_locked(node)) {1167continue;1168}11691170Vector<Ref<Node3DGizmo>> gizmos = sp->get_gizmos();1171for (int j = 0; j < gizmos.size(); j++) {1172Ref<EditorNode3DGizmo> seg = gizmos[j];1173if (seg.is_null()) {1174continue;1175}11761177if (seg->intersect_frustum(camera, frustum)) {1178selected.push_back(node);1179}1180}1181}11821183for (int i = 0; i < selected.size(); i++) {1184if (!editor_selection->is_selected(selected[i])) {1185editor_selection->add_node(selected[i]);1186}1187}11881189const List<Node *> &top_node_list = editor_selection->get_top_selected_node_list();1190if (top_node_list.size() == 1) {1191EditorNode::get_singleton()->edit_node(top_node_list.front()->get());1192}1193}11941195void Node3DEditorViewport::_update_name() {1196String name;11971198switch (view_type) {1199case VIEW_TYPE_USER: {1200if (orthogonal) {1201name = TTR("Orthogonal");1202} else {1203name = TTR("Perspective");1204}1205} break;1206case VIEW_TYPE_TOP: {1207if (orthogonal) {1208name = TTR("Top Orthogonal");1209} else {1210name = TTR("Top Perspective");1211}1212} break;1213case VIEW_TYPE_BOTTOM: {1214if (orthogonal) {1215name = TTR("Bottom Orthogonal");1216} else {1217name = TTR("Bottom Perspective");1218}1219} break;1220case VIEW_TYPE_LEFT: {1221if (orthogonal) {1222name = TTR("Left Orthogonal");1223} else {1224name = TTR("Left Perspective");1225}1226} break;1227case VIEW_TYPE_RIGHT: {1228if (orthogonal) {1229name = TTR("Right Orthogonal");1230} else {1231name = TTR("Right Perspective");1232}1233} break;1234case VIEW_TYPE_FRONT: {1235if (orthogonal) {1236name = TTR("Front Orthogonal");1237} else {1238name = TTR("Front Perspective");1239}1240} break;1241case VIEW_TYPE_REAR: {1242if (orthogonal) {1243name = TTR("Rear Orthogonal");1244} else {1245name = TTR("Rear Perspective");1246}1247} break;1248}12491250if (orthogonal && auto_orthogonal) {1251// TRANSLATORS: This will be appended to the view name when Auto Orthogonal is enabled.1252name += " " + TTR("[auto]");1253}12541255view_display_menu->set_text(name);1256view_display_menu->reset_size();1257}12581259void Node3DEditorViewport::_compute_edit(const Point2 &p_point) {1260_edit.original_local = spatial_editor->are_local_coords_enabled();1261_edit.click_ray = get_ray(p_point);1262_edit.click_ray_pos = get_ray_pos(p_point);1263_edit.plane = TRANSFORM_VIEW;1264spatial_editor->update_transform_gizmo();1265_edit.center = spatial_editor->get_gizmo_transform().origin;12661267Node3D *selected = spatial_editor->get_single_selected_node();1268Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr;12691270if (se && se->gizmo.is_valid()) {1271for (const KeyValue<int, Transform3D> &E : se->subgizmos) {1272int subgizmo_id = E.key;1273se->subgizmos[subgizmo_id] = se->gizmo->get_subgizmo_transform(subgizmo_id);1274}1275se->original_local = selected->get_transform();1276se->original = selected->get_global_transform();1277} else {1278const List<Node *> &selection = editor_selection->get_top_selected_node_list();12791280for (Node *E : selection) {1281Node3D *sp = Object::cast_to<Node3D>(E);1282if (!sp) {1283continue;1284}12851286Node3DEditorSelectedItem *sel_item = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);12871288if (!sel_item) {1289continue;1290}12911292sel_item->original_local = sel_item->sp->get_local_gizmo_transform();1293sel_item->original = sel_item->sp->get_global_gizmo_transform();1294}1295}1296}12971298static Key _get_key_modifier_setting(const String &p_property) {1299switch (EDITOR_GET(p_property).operator int()) {1300case 0:1301return Key::NONE;1302case 1:1303return Key::SHIFT;1304case 2:1305return Key::ALT;1306case 3:1307return Key::META;1308case 4:1309return Key::CTRL;1310}1311return Key::NONE;1312}13131314static Key _get_key_modifier(Ref<InputEventWithModifiers> e) {1315if (e->is_shift_pressed()) {1316return Key::SHIFT;1317}1318if (e->is_alt_pressed()) {1319return Key::ALT;1320}1321if (e->is_ctrl_pressed()) {1322return Key::CTRL;1323}1324if (e->is_meta_pressed()) {1325return Key::META;1326}1327return Key::NONE;1328}13291330bool Node3DEditorViewport::_transform_gizmo_select(const Vector2 &p_screenpos, bool p_highlight_only) {1331if (!spatial_editor->is_gizmo_visible()) {1332return false;1333}1334if (get_selected_count() == 0) {1335if (p_highlight_only) {1336spatial_editor->select_gizmo_highlight_axis(-1);1337}1338return false;1339}13401341Vector3 ray_pos = get_ray_pos(p_screenpos);1342Vector3 ray = get_ray(p_screenpos);13431344Transform3D gt = spatial_editor->get_gizmo_transform();13451346if (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_TRANSFORM || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_MOVE) {1347int col_axis = -1;1348real_t col_d = 1e20;13491350for (int i = 0; i < 3; i++) {1351const Vector3 grabber_pos = gt.origin + gt.basis.get_column(i).normalized() * gizmo_scale * (GIZMO_ARROW_OFFSET + (GIZMO_ARROW_SIZE * 0.5));1352const real_t grabber_radius = gizmo_scale * GIZMO_ARROW_SIZE;13531354Vector3 r;13551356if (Geometry3D::segment_intersects_sphere(ray_pos, ray_pos + ray * MAX_Z, grabber_pos, grabber_radius, &r)) {1357const real_t d = r.distance_to(ray_pos);1358if (d < col_d) {1359col_d = d;1360col_axis = i;1361}1362}1363}13641365bool is_plane_translate = false;1366// plane select1367if (col_axis == -1) {1368col_d = 1e20;13691370for (int i = 0; i < 3; i++) {1371Vector3 ivec2 = gt.basis.get_column((i + 1) % 3).normalized();1372Vector3 ivec3 = gt.basis.get_column((i + 2) % 3).normalized();13731374// Allow some tolerance to make the plane easier to click,1375// even if the click is actually slightly outside the plane.1376const Vector3 grabber_pos = gt.origin + (ivec2 + ivec3) * gizmo_scale * (GIZMO_PLANE_SIZE + GIZMO_PLANE_DST * 0.6667);13771378Vector3 r;1379Plane plane(gt.basis.get_column(i).normalized(), gt.origin);13801381if (plane.intersects_ray(ray_pos, ray, &r)) {1382const real_t dist = r.distance_to(grabber_pos);1383// Allow some tolerance to make the plane easier to click,1384// even if the click is actually slightly outside the plane.1385if (dist < (gizmo_scale * GIZMO_PLANE_SIZE * 1.5)) {1386const real_t d = ray_pos.distance_to(r);1387if (d < col_d) {1388col_d = d;1389col_axis = i;13901391is_plane_translate = true;1392}1393}1394}1395}1396}13971398if (col_axis != -1) {1399if (p_highlight_only) {1400spatial_editor->select_gizmo_highlight_axis(col_axis + (is_plane_translate ? 6 : 0));14011402} else {1403//handle plane translate1404_edit.mode = TRANSFORM_TRANSLATE;1405_compute_edit(p_screenpos);1406_edit.plane = TransformPlane(TRANSFORM_X_AXIS + col_axis + (is_plane_translate ? 3 : 0));1407}1408return true;1409}1410}14111412if (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_TRANSFORM || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE) {1413int col_axis = -1;1414bool view_rotation_selected = false;1415bool trackball_selected = false;14161417Vector3 hit_position;1418Vector3 hit_normal;1419real_t ray_length = gt.origin.distance_to(ray_pos) + (GIZMO_CIRCLE_SIZE * gizmo_scale) * 4.0f;1420if (Geometry3D::segment_intersects_sphere(ray_pos, ray_pos + ray * ray_length, gt.origin, gizmo_scale * (GIZMO_CIRCLE_SIZE), &hit_position, &hit_normal)) {1421if (hit_normal.dot(_get_camera_normal()) < 0.05) {1422hit_position = gt.xform_inv(hit_position).abs();1423int min_axis = hit_position.min_axis_index();1424if (hit_position[min_axis] < gizmo_scale * GIZMO_RING_HALF_WIDTH) {1425col_axis = min_axis;1426}1427}1428}14291430if (col_axis == -1) {1431float col_d = 1e20;14321433for (int i = 0; i < 3; i++) {1434Plane plane(gt.basis.get_column(i).normalized(), gt.origin);1435Vector3 r;1436if (!plane.intersects_ray(ray_pos, ray, &r)) {1437continue;1438}14391440const real_t dist = r.distance_to(gt.origin);1441const Vector3 r_dir = (r - gt.origin).normalized();14421443if (_get_camera_normal().dot(r_dir) <= 0.005) {1444if (dist > gizmo_scale * (GIZMO_CIRCLE_SIZE - GIZMO_RING_HALF_WIDTH) && dist < gizmo_scale * (GIZMO_CIRCLE_SIZE + GIZMO_RING_HALF_WIDTH)) {1445const real_t d = ray_pos.distance_to(r);1446if (d < col_d) {1447col_d = d;1448col_axis = i;1449}1450}1451}1452}1453}14541455if (col_axis == -1) {1456Vector3 ray_to_center = gt.origin - ray_pos;1457real_t ray_length_to_center = ray_to_center.dot(ray);1458Vector3 closest_point_on_ray = ray_pos + ray * ray_length_to_center;1459real_t distance_ray_to_center = closest_point_on_ray.distance_to(gt.origin);14601461real_t view_rotation_radius = gizmo_scale * GIZMO_VIEW_ROTATION_SIZE;1462real_t circumference_tolerance = gizmo_scale * GIZMO_RING_HALF_WIDTH;14631464if (Math::abs(distance_ray_to_center - view_rotation_radius) < circumference_tolerance &&1465ray_length_to_center > 0) {1466view_rotation_selected = true;1467} else if (spatial_editor->is_trackball_enabled() && distance_ray_to_center < gizmo_scale * (GIZMO_CIRCLE_SIZE - GIZMO_RING_HALF_WIDTH) && ray_length_to_center > 0) {1468trackball_selected = true;1469}1470}14711472if (view_rotation_selected) {1473if (p_highlight_only) {1474spatial_editor->select_gizmo_highlight_axis(GIZMO_HIGHLIGHT_AXIS_VIEW_ROTATION);1475} else {1476_edit.mode = TRANSFORM_ROTATE;1477_compute_edit(p_screenpos);1478_edit.plane = TRANSFORM_VIEW;1479_edit.accumulated_rotation_angle = 0.0;1480_edit.rotation_axis = _get_camera_normal();1481_edit.view_axis_local = spatial_editor->get_gizmo_transform().basis.xform_inv(_get_camera_normal()).normalized();1482_edit.gizmo_initiated = true;1483}1484return true;1485} else if (trackball_selected) {1486if (p_highlight_only) {1487spatial_editor->select_gizmo_highlight_axis(GIZMO_HIGHLIGHT_AXIS_TRACKBALL);1488} else {1489_edit.mode = TRANSFORM_ROTATE;1490_compute_edit(p_screenpos);1491_edit.plane = TRANSFORM_VIEW;1492_edit.is_trackball = true;1493_edit.show_rotation_line = false;1494_edit.accumulated_rotation_angle = 0.0;1495_edit.rotation_axis = _get_camera_normal();1496_edit.gizmo_initiated = true;1497spatial_editor->select_gizmo_highlight_axis(-1);1498}1499return true;1500} else if (col_axis != -1) {1501if (p_highlight_only) {1502spatial_editor->select_gizmo_highlight_axis(col_axis + 3);1503} else {1504//handle axis-specific rotate1505_edit.mode = TRANSFORM_ROTATE;1506_compute_edit(p_screenpos);1507_edit.plane = TransformPlane(TRANSFORM_X_AXIS + col_axis);1508_edit.accumulated_rotation_angle = 0.0;1509_edit.rotation_axis = gt.basis.get_column(col_axis).normalized();1510_edit.gizmo_initiated = true;1511}1512return true;1513}1514}15151516if (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SCALE) {1517int col_axis = -1;1518float col_d = 1e20;15191520for (int i = 0; i < 3; i++) {1521const Vector3 grabber_pos = gt.origin + gt.basis.get_column(i).normalized() * gizmo_scale * GIZMO_SCALE_OFFSET;1522const real_t grabber_radius = gizmo_scale * GIZMO_ARROW_SIZE;15231524Vector3 r;15251526if (Geometry3D::segment_intersects_sphere(ray_pos, ray_pos + ray * MAX_Z, grabber_pos, grabber_radius, &r)) {1527const real_t d = r.distance_to(ray_pos);1528if (d < col_d) {1529col_d = d;1530col_axis = i;1531}1532}1533}15341535bool is_plane_scale = false;1536// plane select1537if (col_axis == -1) {1538col_d = 1e20;15391540for (int i = 0; i < 3; i++) {1541const Vector3 ivec2 = gt.basis.get_column((i + 1) % 3).normalized();1542const Vector3 ivec3 = gt.basis.get_column((i + 2) % 3).normalized();15431544// Allow some tolerance to make the plane easier to click,1545// even if the click is actually slightly outside the plane.1546const Vector3 grabber_pos = gt.origin + (ivec2 + ivec3) * gizmo_scale * (GIZMO_PLANE_SIZE + GIZMO_PLANE_DST * 0.6667);15471548Vector3 r;1549Plane plane(gt.basis.get_column(i).normalized(), gt.origin);15501551if (plane.intersects_ray(ray_pos, ray, &r)) {1552const real_t dist = r.distance_to(grabber_pos);1553// Allow some tolerance to make the plane easier to click,1554// even if the click is actually slightly outside the plane.1555if (dist < (gizmo_scale * GIZMO_PLANE_SIZE * 1.5)) {1556const real_t d = ray_pos.distance_to(r);1557if (d < col_d) {1558col_d = d;1559col_axis = i;15601561is_plane_scale = true;1562}1563}1564}1565}1566}15671568if (col_axis != -1) {1569if (p_highlight_only) {1570spatial_editor->select_gizmo_highlight_axis(col_axis + (is_plane_scale ? 12 : 9));15711572} else {1573//handle scale1574_edit.mode = TRANSFORM_SCALE;1575_compute_edit(p_screenpos);1576_edit.plane = TransformPlane(TRANSFORM_X_AXIS + col_axis + (is_plane_scale ? 3 : 0));1577}1578return true;1579}1580}15811582if (p_highlight_only) {1583spatial_editor->select_gizmo_highlight_axis(-1);1584}15851586return false;1587}15881589void Node3DEditorViewport::_transform_gizmo_apply(Node3D *p_node, const Transform3D &p_transform, bool p_local) {1590if (p_transform.basis.determinant() == 0) {1591return;1592}15931594if (p_local) {1595p_node->set_transform(p_transform);1596} else {1597p_node->set_global_transform(p_transform);1598}1599}16001601Transform3D 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, bool p_view_axis) {1602switch (p_mode) {1603case TRANSFORM_SCALE: {1604if (_edit.snap || spatial_editor->is_snap_enabled()) {1605p_motion.snapf(p_extra);1606}1607Transform3D s;1608if (p_local) {1609s.basis = p_original_local.basis.scaled_local(p_motion + Vector3(1, 1, 1));1610s.origin = p_original_local.origin;1611} else {1612s.basis.scale(p_motion + Vector3(1, 1, 1));1613Transform3D base = Transform3D(Basis(), _edit.center);1614s = base * (s * (base.inverse() * p_original));16151616// Recalculate orthogonalized scale without moving origin.1617if (p_orthogonal) {1618s.basis = p_original.basis.scaled_orthogonal(p_motion + Vector3(1, 1, 1));1619}1620}16211622return s;1623}1624case TRANSFORM_TRANSLATE: {1625if (_edit.snap || spatial_editor->is_snap_enabled()) {1626p_motion.snapf(p_extra);1627}16281629if (p_local) {1630return p_original_local.translated_local(p_motion);1631}16321633return p_original.translated(p_motion);1634}1635case TRANSFORM_ROTATE: {1636Transform3D r;16371638Basis parent_global_basis = p_original.basis * p_original_local.basis.inverse();16391640Vector3 axis;1641if (p_local && !p_view_axis) {1642axis = p_original_local.basis.xform(p_motion);1643} else {1644axis = parent_global_basis.xform_inv(p_motion);1645}16461647if (p_local) {1648r.basis = Basis(axis.normalized(), p_extra) * p_original_local.basis;1649r.origin = p_original_local.origin;1650} else {1651r.basis = parent_global_basis * Basis(axis.normalized(), p_extra) * p_original_local.basis;1652r.origin = Basis(p_motion, p_extra).xform(p_original.origin - _edit.center) + _edit.center;1653}16541655return r;1656}1657default: {1658ERR_FAIL_V_MSG(Transform3D(), "Invalid mode in '_compute_transform'");1659}1660}1661}16621663void Node3DEditorViewport::_reset_transform(TransformType p_type) {1664List<Node *> selection = editor_selection->get_full_selected_node_list();1665if (selection.is_empty()) {1666return;1667}1668EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();1669undo_redo->create_action(TTR("Reset Transform"));1670for (Node *node : selection) {1671Node3D *sp = Object::cast_to<Node3D>(node);1672if (!sp) {1673continue;1674}16751676switch (p_type) {1677case TransformType::POSITION:1678undo_redo->add_undo_method(sp, "set_position", sp->get_position());1679undo_redo->add_do_method(sp, "set_position", Vector3());1680break;1681case TransformType::ROTATION:1682undo_redo->add_undo_method(sp, "set_rotation", sp->get_rotation());1683undo_redo->add_do_method(sp, "set_rotation", Vector3());1684break;1685case TransformType::SCALE:1686undo_redo->add_undo_method(sp, "set_scale", sp->get_scale());1687undo_redo->add_do_method(sp, "set_scale", Vector3(1, 1, 1));1688break;1689}1690}1691undo_redo->commit_action();1692}16931694void Node3DEditorViewport::_surface_mouse_enter() {1695if (Input::get_singleton()->get_mouse_mode() == Input::MouseMode::MOUSE_MODE_CAPTURED) {1696return;1697}16981699if (!surface->has_focus() && (!get_viewport()->gui_get_focus_owner() || !get_viewport()->gui_get_focus_owner()->is_text_field())) {1700surface->grab_focus();1701}1702}17031704void Node3DEditorViewport::_surface_mouse_exit() {1705_remove_preview_node();1706_reset_preview_material();1707_remove_preview_material();1708}17091710void Node3DEditorViewport::_surface_focus_enter() {1711view_display_menu->set_disable_shortcuts(false);1712}17131714void Node3DEditorViewport::_surface_focus_exit() {1715view_display_menu->set_disable_shortcuts(true);1716}17171718bool Node3DEditorViewport::_is_node_locked(const Node *p_node) const {1719return p_node->get_meta("_edit_lock_", false);1720}17211722void Node3DEditorViewport::_list_select(Ref<InputEventMouseButton> b) {1723Vector<_RayResult> potential_selection_results;1724_find_items_at_pos(b->get_position(), potential_selection_results, b->is_alt_pressed());17251726Node *edited_scene = EditorNode::get_singleton()->get_edited_scene();17271728// Filter to a list of nodes which include either the edited scene or nodes directly owned by the edited scene.1729// If a node has an invalid owner, recursively check their parents until a valid node is found.1730for (int i = 0; i < potential_selection_results.size(); i++) {1731Node3D *node = potential_selection_results[i].item;1732while (true) {1733if (node == nullptr || node == edited_scene->get_parent()) {1734break;1735} else {1736Node *node_owner = node->get_owner();1737if (node == edited_scene || node_owner == edited_scene || (node_owner != nullptr && edited_scene->is_editable_instance(node_owner))) {1738if (!selection_results.has(node)) {1739selection_results.append(node);1740}1741break;1742}1743}1744node = Object::cast_to<Node3D>(node->get_parent());1745}1746}17471748clicked_wants_append = b->is_shift_pressed();17491750if (selection_results.size() == 1) {1751clicked = selection_results[0]->get_instance_id();1752selection_results.clear();17531754if (clicked.is_valid()) {1755_select_clicked(b->is_alt_pressed());1756}1757} else if (!selection_results.is_empty()) {1758NodePath root_path = get_tree()->get_edited_scene_root()->get_path();1759StringName root_name = root_path.get_name(root_path.get_name_count() - 1);1760int icon_max_width = EditorNode::get_singleton()->get_editor_theme()->get_constant(SNAME("class_icon_size"), EditorStringName(Editor));17611762for (int i = 0; i < selection_results.size(); i++) {1763Node3D *spat = selection_results[i];17641765Ref<Texture2D> icon = EditorNode::get_singleton()->get_object_icon(spat);17661767String node_path = "/" + root_name + "/" + String(root_path.rel_path_to(spat->get_path()));17681769int locked = 0;1770if (_is_node_locked(spat)) {1771locked = 1;1772} else {1773Node *ed_scene = EditorNode::get_singleton()->get_edited_scene();1774Node *node = spat;17751776while (node && node != ed_scene->get_parent()) {1777Node3D *selected_tmp = Object::cast_to<Node3D>(node);1778if (selected_tmp && node->has_meta("_edit_group_")) {1779locked = 2;1780}1781node = node->get_parent();1782}1783}17841785String suffix;1786if (locked == 1) {1787suffix = " (" + TTR("Locked") + ")";1788} else if (locked == 2) {1789suffix = " (" + TTR("Grouped") + ")";1790}1791selection_menu->add_item((String)spat->get_name() + suffix);1792selection_menu->set_item_icon(i, icon);1793selection_menu->set_item_icon_max_width(i, icon_max_width);1794selection_menu->set_item_metadata(i, node_path);1795selection_menu->set_item_tooltip(i, String(spat->get_name()) + "\nType: " + spat->get_class() + "\nPath: " + node_path);1796}17971798selection_results_menu = selection_results;1799selection_menu->set_position(get_screen_position() + b->get_position());1800selection_menu->reset_size();1801selection_menu->popup();1802}1803}18041805// Helper function to redirect mouse events to the active freelook viewport1806static bool _redirect_freelook_input(const Ref<InputEvent> &p_event, Node3DEditorViewport *p_exclude_viewport = nullptr) {1807if (Input::get_singleton()->get_mouse_mode() != Input::MouseMode::MOUSE_MODE_CAPTURED) {1808return false;1809}18101811Node3DEditor *editor = Node3DEditor::get_singleton();1812if (!editor->get_freelook_viewport()) {1813return false;1814}18151816Node3DEditorViewport *freelook_vp = editor->get_freelook_viewport();1817if (freelook_vp == p_exclude_viewport) {1818return false;1819}18201821Ref<InputEventMouse> mouse_event = p_event;1822if (!mouse_event.is_valid()) {1823return false;1824}18251826Control *target_surface = freelook_vp->get_surface();18271828target_surface->emit_signal(SceneStringName(gui_input), p_event);1829return true;1830}18311832// This is only active during instant transforms,1833// to capture and wrap mouse events outside the control.1834void Node3DEditorViewport::input(const Ref<InputEvent> &p_event) {1835ERR_FAIL_COND(!_edit.instant);1836Ref<InputEventMouseMotion> m = p_event;18371838if (m.is_valid()) {1839_edit.mouse_pos += _get_warped_mouse_motion(p_event);1840update_transform(_get_key_modifier(m) == Key::SHIFT);1841}1842}18431844void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {1845const Ref<InputEventKey> k = p_event;18461847if (k.is_valid() && k->is_pressed() && EDITOR_GET("editors/3d/navigation/emulate_numpad")) {1848const Key code = k->get_physical_keycode();1849if (code >= Key::KEY_0 && code <= Key::KEY_9) {1850k->set_keycode(code - Key::KEY_0 + Key::KP_0);1851}1852}18531854if (previewing || get_viewport()->gui_get_drag_data()) {1855// Disable all input actions when previewing a camera or during drag-and-drop.1856return;1857}18581859if (_redirect_freelook_input(p_event, this)) {1860return;1861}18621863EditorPlugin::AfterGUIInput after = EditorPlugin::AFTER_GUI_INPUT_PASS;1864{1865EditorNode *en = EditorNode::get_singleton();18661867switch (en->get_editor_plugins_force_input_forwarding()->forward_3d_gui_input(camera, p_event, true)) {1868case EditorPlugin::AFTER_GUI_INPUT_PASS: {1869// Continue processing.1870} break;18711872case EditorPlugin::AFTER_GUI_INPUT_STOP: {1873return; // Stop processing.1874} break;18751876case EditorPlugin::AFTER_GUI_INPUT_CUSTOM: {1877after = EditorPlugin::AFTER_GUI_INPUT_CUSTOM;1878} break;1879}18801881switch (en->get_editor_plugins_over()->forward_3d_gui_input(camera, p_event, false)) {1882case EditorPlugin::AFTER_GUI_INPUT_PASS: {1883// Continue processing.1884} break;18851886case EditorPlugin::AFTER_GUI_INPUT_STOP: {1887return; // Stop processing.1888} break;18891890case EditorPlugin::AFTER_GUI_INPUT_CUSTOM: {1891after = EditorPlugin::AFTER_GUI_INPUT_CUSTOM;1892} break;1893}1894}18951896Ref<InputEventMouseButton> b = p_event;18971898if (b.is_valid()) {1899emit_signal(SNAME("clicked"));19001901ViewportNavMouseButton orbit_mouse_preference = (ViewportNavMouseButton)EDITOR_GET("editors/3d/navigation/orbit_mouse_button").operator int();1902ViewportNavMouseButton pan_mouse_preference = (ViewportNavMouseButton)EDITOR_GET("editors/3d/navigation/pan_mouse_button").operator int();1903ViewportNavMouseButton zoom_mouse_preference = (ViewportNavMouseButton)EDITOR_GET("editors/3d/navigation/zoom_mouse_button").operator int();19041905const real_t zoom_factor = 1 + (ZOOM_FREELOOK_MULTIPLIER - 1) * b->get_factor();1906switch (b->get_button_index()) {1907case MouseButton::WHEEL_UP: {1908if (is_freelook_active()) {1909scale_freelook_speed(zoom_factor);1910} else {1911scale_cursor_distance(1.0 / zoom_factor);1912}1913} break;1914case MouseButton::WHEEL_DOWN: {1915if (is_freelook_active()) {1916scale_freelook_speed(1.0 / zoom_factor);1917} else {1918scale_cursor_distance(zoom_factor);1919}1920} break;1921case MouseButton::RIGHT: {1922if (b->is_pressed()) {1923if (_edit.gizmo.is_valid()) {1924// Restore.1925_edit.gizmo->commit_handle(_edit.gizmo_handle, _edit.gizmo_handle_secondary, _edit.gizmo_initial_value, true);1926_edit.gizmo = Ref<EditorNode3DGizmo>();1927}19281929if (_edit.mode == TRANSFORM_NONE) {1930if (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")) {1931break;1932} 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")) {1933break;1934} 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")) {1935break;1936}1937}19381939if (b->is_alt_pressed()) {1940_list_select(b);1941return;1942}19431944if (_edit.mode != TRANSFORM_NONE) {1945cancel_transform();1946break;1947}19481949const Key mod = _get_key_modifier(b);1950if (!orthogonal) {1951if (mod == _get_key_modifier_setting("editors/3d/freelook/freelook_activation_modifier")) {1952set_freelook_active(true);1953}1954}1955} else {1956set_freelook_active(false);1957}19581959if (freelook_active && !surface->has_focus()) {1960// Focus usually doesn't trigger on right-click, but in case of freelook it should,1961// otherwise using keyboard navigation would misbehave1962surface->grab_focus();1963}19641965} break;1966case MouseButton::MIDDLE: {1967if (b->is_pressed() && _edit.mode != TRANSFORM_NONE) {1968if (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")) {1969break;1970} 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")) {1971break;1972} 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")) {1973break;1974}19751976switch (_edit.plane) {1977case TRANSFORM_VIEW: {1978_edit.plane = TRANSFORM_X_AXIS;1979set_message(TTR("X-Axis Transform."), 2);1980view_type = VIEW_TYPE_USER;1981_update_name();1982} break;1983case TRANSFORM_X_AXIS: {1984_edit.plane = TRANSFORM_Y_AXIS;1985set_message(TTR("Y-Axis Transform."), 2);19861987} break;1988case TRANSFORM_Y_AXIS: {1989_edit.plane = TRANSFORM_Z_AXIS;1990set_message(TTR("Z-Axis Transform."), 2);19911992} break;1993case TRANSFORM_Z_AXIS: {1994_edit.plane = TRANSFORM_VIEW;1995// TRANSLATORS: This refers to the transform of the view plane.1996set_message(TTR("View Plane Transform."), 2);19971998} break;1999case TRANSFORM_YZ:2000case TRANSFORM_XZ:2001case TRANSFORM_XY: {2002} break;2003}2004}2005} break;2006case MouseButton::LEFT: {2007if (b->is_pressed()) {2008clicked_wants_append = b->is_shift_pressed();20092010if (_edit.mode != TRANSFORM_NONE && (_edit.instant || collision_reposition)) {2011commit_transform();2012break; // just commit the edit, stop processing the event so we don't deselect the object2013}2014if (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")) {2015break;2016} 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")) {2017break;2018} 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")) {2019break;2020}20212022if (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_RULER) {2023EditorNode::get_singleton()->get_scene_root()->add_child(ruler);2024collision_reposition = true;2025break;2026}20272028if (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_LIST_SELECT) {2029_list_select(b);2030break;2031}20322033_edit.mouse_pos = b->get_position();2034_edit.original_mouse_pos = b->get_position();2035_edit.snap = spatial_editor->is_snap_enabled();2036_edit.mode = TRANSFORM_NONE;2037_edit.original = spatial_editor->get_gizmo_transform(); // To prevent to break when flipping with scale.20382039bool can_select_gizmos = spatial_editor->get_single_selected_node();20402041{2042int idx = view_display_menu->get_popup()->get_item_index(VIEW_GIZMOS);2043int idx2 = view_display_menu->get_popup()->get_item_index(VIEW_TRANSFORM_GIZMO);2044can_select_gizmos = can_select_gizmos && view_display_menu->get_popup()->is_item_checked(idx);2045transform_gizmo_visible = view_display_menu->get_popup()->is_item_checked(idx2);2046}20472048// Gizmo handles2049if (can_select_gizmos) {2050Vector<Ref<Node3DGizmo>> gizmos = spatial_editor->get_single_selected_node()->get_gizmos();20512052bool intersected_handle = false;2053for (int i = 0; i < gizmos.size(); i++) {2054Ref<EditorNode3DGizmo> seg = gizmos[i];20552056if (seg.is_null()) {2057continue;2058}20592060int gizmo_handle = -1;2061bool gizmo_secondary = false;2062seg->handles_intersect_ray(camera, _edit.mouse_pos, b->is_shift_pressed(), gizmo_handle, gizmo_secondary);2063if (gizmo_handle != -1) {2064_edit.gizmo = seg;2065seg->begin_handle_action(gizmo_handle, gizmo_secondary);2066_edit.gizmo_handle = gizmo_handle;2067_edit.gizmo_handle_secondary = gizmo_secondary;2068_edit.gizmo_initial_value = seg->get_handle_value(gizmo_handle, gizmo_secondary);2069intersected_handle = true;2070break;2071}2072}20732074if (intersected_handle) {2075break;2076}2077}20782079// Transform gizmo2080if (transform_gizmo_visible && _transform_gizmo_select(_edit.mouse_pos)) {2081break;2082}20832084// Subgizmos2085if (can_select_gizmos) {2086Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(spatial_editor->get_single_selected_node());2087Vector<Ref<Node3DGizmo>> gizmos = spatial_editor->get_single_selected_node()->get_gizmos();20882089bool intersected_subgizmo = false;2090for (int i = 0; i < gizmos.size(); i++) {2091Ref<EditorNode3DGizmo> seg = gizmos[i];20922093if (seg.is_null()) {2094continue;2095}20962097int subgizmo_id = seg->subgizmos_intersect_ray(camera, _edit.mouse_pos);2098if (subgizmo_id != -1) {2099ERR_CONTINUE(!se);2100if (b->is_shift_pressed()) {2101if (se->subgizmos.has(subgizmo_id)) {2102se->subgizmos.erase(subgizmo_id);2103} else {2104se->subgizmos.insert(subgizmo_id, seg->get_subgizmo_transform(subgizmo_id));2105}2106} else {2107se->subgizmos.clear();2108se->subgizmos.insert(subgizmo_id, seg->get_subgizmo_transform(subgizmo_id));2109}21102111if (se->subgizmos.is_empty()) {2112se->gizmo = Ref<EditorNode3DGizmo>();2113} else {2114se->gizmo = seg;2115}21162117seg->redraw();2118spatial_editor->update_transform_gizmo();2119intersected_subgizmo = true;2120break;2121}2122}21232124if (intersected_subgizmo) {2125break;2126}2127}21282129clicked = ObjectID();21302131bool node_selected = get_selected_count() > 0;21322133if (after != EditorPlugin::AFTER_GUI_INPUT_CUSTOM) {2134// Single item selection.2135clicked = _select_ray(b->get_position());21362137if (clicked.is_valid() && !editor_selection->is_selected(ObjectDB::get_instance<Node>(clicked))) {2138if (!node_selected || (!b->is_alt_pressed() && !(spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_TRANSFORM && b->is_command_or_control_pressed()))) {2139selection_in_progress = true;2140break;2141}2142}21432144if (clicked.is_null()) {2145if (node_selected) {2146TransformMode mode = TRANSFORM_NONE;21472148if (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_TRANSFORM) {2149if (b->is_command_or_control_pressed()) {2150mode = TRANSFORM_ROTATE;2151} else if (b->is_alt_pressed()) {2152mode = TRANSFORM_TRANSLATE;2153}2154} else if (b->is_alt_pressed()) {2155switch (spatial_editor->get_tool_mode()) {2156case Node3DEditor::TOOL_MODE_ROTATE:2157mode = TRANSFORM_ROTATE;2158break;2159case Node3DEditor::TOOL_MODE_MOVE:2160mode = TRANSFORM_TRANSLATE;2161break;2162case Node3DEditor::TOOL_MODE_SCALE:2163mode = TRANSFORM_SCALE;2164break;2165default:2166break;2167}2168}21692170if (mode != TRANSFORM_NONE) {2171begin_transform(mode, false);2172break;2173}2174}21752176// Default to region select.2177cursor.region_select = true;2178cursor.region_begin = b->get_position();2179cursor.region_end = b->get_position();2180break;2181}2182}21832184if (clicked.is_valid() && !clicked_wants_append) {2185bool is_clicked_node_selected = editor_selection->is_selected(ObjectDB::get_instance<Node>(clicked));2186TransformMode mode = TRANSFORM_NONE;21872188switch (spatial_editor->get_tool_mode()) {2189case Node3DEditor::TOOL_MODE_TRANSFORM:2190if (b->is_command_or_control_pressed() && node_selected) {2191mode = TRANSFORM_ROTATE;2192} else if (b->is_alt_pressed() && node_selected) {2193mode = TRANSFORM_TRANSLATE;2194} else if (is_clicked_node_selected) {2195mode = TRANSFORM_TRANSLATE;2196}2197break;2198case Node3DEditor::TOOL_MODE_ROTATE:2199if (is_clicked_node_selected || (b->is_alt_pressed() && node_selected)) {2200mode = TRANSFORM_ROTATE;2201}2202break;2203case Node3DEditor::TOOL_MODE_MOVE:2204if (is_clicked_node_selected || (b->is_alt_pressed() && node_selected)) {2205mode = TRANSFORM_TRANSLATE;2206}2207break;2208case Node3DEditor::TOOL_MODE_SCALE:2209if (is_clicked_node_selected || (b->is_alt_pressed() && node_selected)) {2210mode = TRANSFORM_SCALE;2211}2212break;2213default:2214break;2215}22162217if (mode != TRANSFORM_NONE) {2218begin_transform(mode, false);2219break;2220}2221}22222223surface->queue_redraw();2224} else {2225if (ruler->is_inside_tree()) {2226EditorNode::get_singleton()->get_scene_root()->remove_child(ruler);2227ruler_start_point->set_visible(false);2228ruler_end_point->set_visible(false);2229ruler_label->set_visible(false);2230collision_reposition = false;2231break;2232}22332234if (_edit.gizmo.is_valid()) {2235// Certain gizmo plugins should be able to commit handles without dragging them.2236if (_edit.original_mouse_pos != _edit.mouse_pos || _edit.gizmo->get_plugin()->can_commit_handle_on_click()) {2237_edit.gizmo->commit_handle(_edit.gizmo_handle, _edit.gizmo_handle_secondary, _edit.gizmo_initial_value, false);2238}2239Node3D *selected_node = spatial_editor->get_single_selected_node();2240if (selected_node) {2241selected_node->update_gizmos();2242}2243_edit.gizmo = Ref<EditorNode3DGizmo>();2244break;2245}22462247if (after != EditorPlugin::AFTER_GUI_INPUT_CUSTOM) {2248selection_in_progress = false;22492250if (clicked.is_valid() && _edit.mode == TRANSFORM_NONE) {2251_select_clicked(false);2252} else if (cursor.region_select) {2253_select_region();2254surface->queue_redraw();2255}22562257movement_threshold_passed = false;2258cursor.region_select = false;2259}22602261if (!_edit.instant && _edit.mode != TRANSFORM_NONE) {2262Node3D *selected = spatial_editor->get_single_selected_node();2263Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr;22642265if (se && se->gizmo.is_valid()) {2266Vector<int> ids;2267Vector<Transform3D> restore;22682269for (const KeyValue<int, Transform3D> &GE : se->subgizmos) {2270ids.push_back(GE.key);2271restore.push_back(GE.value);2272}22732274se->gizmo->commit_subgizmos(ids, restore, false);2275} else {2276if (_edit.original_mouse_pos != _edit.mouse_pos) {2277commit_transform();2278}2279}2280_edit.mode = TRANSFORM_NONE;2281set_message("");2282spatial_editor->update_transform_gizmo();2283}2284}22852286} break;2287default:2288break;2289}2290}22912292Vector<ShortcutCheckSet> shortcut_check_sets;22932294if (Input::get_singleton()->get_mouse_mode() != Input::MouseMode::MOUSE_MODE_CAPTURED) {2295ViewportNavMouseButton orbit_mouse_preference = (ViewportNavMouseButton)EDITOR_GET("editors/3d/navigation/orbit_mouse_button").operator int();2296ViewportNavMouseButton pan_mouse_preference = (ViewportNavMouseButton)EDITOR_GET("editors/3d/navigation/pan_mouse_button").operator int();2297ViewportNavMouseButton zoom_mouse_preference = (ViewportNavMouseButton)EDITOR_GET("editors/3d/navigation/zoom_mouse_button").operator int();2298bool orbit_mod_pressed = _is_nav_modifier_pressed("spatial_editor/viewport_orbit_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_orbit_modifier_2");2299bool pan_mod_pressed = _is_nav_modifier_pressed("spatial_editor/viewport_pan_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_pan_modifier_2");2300bool zoom_mod_pressed = _is_nav_modifier_pressed("spatial_editor/viewport_zoom_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_zoom_modifier_2");2301int orbit_mod_input_count = _get_shortcut_input_count("spatial_editor/viewport_orbit_modifier_1") + _get_shortcut_input_count("spatial_editor/viewport_orbit_modifier_2");2302int pan_mod_input_count = _get_shortcut_input_count("spatial_editor/viewport_pan_modifier_1") + _get_shortcut_input_count("spatial_editor/viewport_pan_modifier_2");2303int zoom_mod_input_count = _get_shortcut_input_count("spatial_editor/viewport_zoom_modifier_1") + _get_shortcut_input_count("spatial_editor/viewport_zoom_modifier_2");2304bool orbit_not_empty = !_is_shortcut_empty("spatial_editor/viewport_orbit_modifier_1") || !_is_shortcut_empty("spatial_editor/viewport_orbit_modifier_2");2305bool pan_not_empty = !_is_shortcut_empty("spatial_editor/viewport_pan_modifier_1") || !_is_shortcut_empty("spatial_editor/viewport_pan_modifier_2");2306bool zoom_not_empty = !_is_shortcut_empty("spatial_editor/viewport_zoom_modifier_1") || !_is_shortcut_empty("spatial_editor/viewport_zoom_modifier_2");2307shortcut_check_sets.push_back(ShortcutCheckSet(orbit_mod_pressed, orbit_not_empty, orbit_mod_input_count, orbit_mouse_preference, NAVIGATION_ORBIT));2308shortcut_check_sets.push_back(ShortcutCheckSet(pan_mod_pressed, pan_not_empty, pan_mod_input_count, pan_mouse_preference, NAVIGATION_PAN));2309shortcut_check_sets.push_back(ShortcutCheckSet(zoom_mod_pressed, zoom_not_empty, zoom_mod_input_count, zoom_mouse_preference, NAVIGATION_ZOOM));2310shortcut_check_sets.sort_custom<ShortcutCheckSetComparator>();2311}23122313Ref<InputEventMouseMotion> m = p_event;23142315// Instant transforms process mouse motion in input() to handle wrapping.2316if (m.is_valid() && !_edit.instant) {2317_edit.mouse_pos = m->get_position();23182319if (!freelook_active && spatial_editor->get_single_selected_node()) {2320Vector<Ref<Node3DGizmo>> gizmos = spatial_editor->get_single_selected_node()->get_gizmos();23212322Ref<EditorNode3DGizmo> found_gizmo;2323int found_handle = -1;2324bool found_handle_secondary = false;23252326for (int i = 0; i < gizmos.size(); i++) {2327Ref<EditorNode3DGizmo> seg = gizmos[i];2328if (seg.is_null()) {2329continue;2330}23312332seg->handles_intersect_ray(camera, _edit.mouse_pos, false, found_handle, found_handle_secondary);23332334if (found_handle != -1) {2335found_gizmo = seg;2336break;2337}2338}23392340if (found_gizmo.is_valid()) {2341spatial_editor->select_gizmo_highlight_axis(-1);2342}23432344bool current_hover_handle_secondary = false;2345int current_hover_handle = spatial_editor->get_current_hover_gizmo_handle(current_hover_handle_secondary);2346if (found_gizmo != spatial_editor->get_current_hover_gizmo() || found_handle != current_hover_handle || found_handle_secondary != current_hover_handle_secondary) {2347spatial_editor->set_current_hover_gizmo(found_gizmo);2348spatial_editor->set_current_hover_gizmo_handle(found_handle, found_handle_secondary);2349spatial_editor->get_single_selected_node()->update_gizmos();2350}2351}23522353if (!freelook_active && transform_gizmo_visible && spatial_editor->get_current_hover_gizmo().is_null() && !m->get_button_mask().has_flag(MouseButtonMask::LEFT) && _edit.gizmo.is_null()) {2354_transform_gizmo_select(_edit.mouse_pos, true);2355}23562357NavigationMode nav_mode = NAVIGATION_NONE;23582359if (_edit.gizmo.is_valid()) {2360_edit.gizmo->set_handle(_edit.gizmo_handle, _edit.gizmo_handle_secondary, camera, m->get_position());2361Variant v = _edit.gizmo->get_handle_value(_edit.gizmo_handle, _edit.gizmo_handle_secondary);2362String n = _edit.gizmo->get_handle_name(_edit.gizmo_handle, _edit.gizmo_handle_secondary);2363set_message(n + ": " + String(v));23642365} else if (m->get_button_mask().has_flag(MouseButtonMask::LEFT)) {2366NavigationMode change_nav_from_shortcut = _get_nav_mode_from_shortcut_check(NAVIGATION_LEFT_MOUSE, shortcut_check_sets, false);2367if (change_nav_from_shortcut != NAVIGATION_NONE) {2368nav_mode = change_nav_from_shortcut;2369} else {2370movement_threshold_passed = _edit.original_mouse_pos.distance_to(_edit.mouse_pos) > 8 * EDSCALE;23712372if ((selection_in_progress || clicked_wants_append || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT) && movement_threshold_passed && clicked.is_valid()) {2373cursor.region_select = true;2374cursor.region_begin = _edit.original_mouse_pos;2375clicked = ObjectID();2376}23772378if (cursor.region_select) {2379cursor.region_end = m->get_position();2380surface->queue_redraw();2381return;2382}23832384if (clicked.is_valid() && movement_threshold_passed && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_TRANSFORM || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_MOVE)) {2385bool is_select_mode = (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_TRANSFORM);2386bool is_clicked_selected = editor_selection->is_selected(ObjectDB::get_instance<Node>(clicked));23872388if (_edit.mode == TRANSFORM_NONE && (is_select_mode || is_clicked_selected)) {2389_compute_edit(_edit.original_mouse_pos);2390clicked = ObjectID();2391_edit.mode = TRANSFORM_TRANSLATE;2392}2393}23942395if (_edit.mode == TRANSFORM_NONE || _edit.numeric_input != 0 || _edit.numeric_next_decimal != 0) {2396return;2397}23982399if (!selection_in_progress) {2400update_transform(_get_key_modifier(m) == Key::SHIFT);2401}2402}2403} else if (m->get_button_mask().has_flag(MouseButtonMask::RIGHT) || freelook_active) {2404NavigationMode change_nav_from_shortcut = _get_nav_mode_from_shortcut_check(NAVIGATION_RIGHT_MOUSE, shortcut_check_sets, false);2405if (m->get_button_mask().has_flag(MouseButtonMask::RIGHT) && change_nav_from_shortcut != NAVIGATION_NONE) {2406nav_mode = change_nav_from_shortcut;2407} else if (freelook_active) {2408nav_mode = NAVIGATION_LOOK;2409} else if (orthogonal) {2410nav_mode = NAVIGATION_PAN;2411}24122413} else if (m->get_button_mask().has_flag(MouseButtonMask::MIDDLE)) {2414NavigationMode change_nav_from_shortcut = _get_nav_mode_from_shortcut_check(NAVIGATION_MIDDLE_MOUSE, shortcut_check_sets, false);2415if (change_nav_from_shortcut != NAVIGATION_NONE) {2416nav_mode = change_nav_from_shortcut;2417}24182419} else if (m->get_button_mask().has_flag(MouseButtonMask::MB_XBUTTON1)) {2420NavigationMode change_nav_from_shortcut = _get_nav_mode_from_shortcut_check(NAVIGATION_MOUSE_4, shortcut_check_sets, false);2421if (change_nav_from_shortcut != NAVIGATION_NONE) {2422nav_mode = change_nav_from_shortcut;2423}24242425} else if (m->get_button_mask().has_flag(MouseButtonMask::MB_XBUTTON2)) {2426NavigationMode change_nav_from_shortcut = _get_nav_mode_from_shortcut_check(NAVIGATION_MOUSE_5, shortcut_check_sets, false);2427if (change_nav_from_shortcut != NAVIGATION_NONE) {2428nav_mode = change_nav_from_shortcut;2429}24302431} else if (EDITOR_GET("editors/3d/navigation/emulate_3_button_mouse")) {2432// Handle trackpad (no external mouse) use case2433NavigationMode change_nav_from_shortcut = _get_nav_mode_from_shortcut_check(NAVIGATION_LEFT_MOUSE, shortcut_check_sets, true);2434if (change_nav_from_shortcut != NAVIGATION_NONE) {2435nav_mode = change_nav_from_shortcut;2436}2437}24382439switch (nav_mode) {2440case NAVIGATION_PAN: {2441_nav_pan(m, _get_warped_mouse_motion(m));24422443} break;24442445case NAVIGATION_ZOOM: {2446_nav_zoom(m, m->get_relative());24472448} break;24492450case NAVIGATION_ORBIT: {2451_nav_orbit(m, _get_warped_mouse_motion(m));24522453} break;24542455case NAVIGATION_LOOK: {2456_nav_look(m, _get_warped_mouse_motion(m));24572458} break;24592460default: {2461}2462}2463}24642465Ref<InputEventMagnifyGesture> magnify_gesture = p_event;2466if (magnify_gesture.is_valid()) {2467if (is_freelook_active()) {2468scale_freelook_speed(magnify_gesture->get_factor());2469} else {2470scale_cursor_distance(1.0 / magnify_gesture->get_factor());2471}2472}24732474Ref<InputEventPanGesture> pan_gesture = p_event;2475if (pan_gesture.is_valid()) {2476NavigationMode nav_mode = NAVIGATION_NONE;24772478for (const ShortcutCheckSet &shortcut_check_set : shortcut_check_sets) {2479if (shortcut_check_set.mod_pressed) {2480nav_mode = shortcut_check_set.result_nav_mode;2481break;2482}2483}24842485switch (nav_mode) {2486case NAVIGATION_PAN: {2487_nav_pan(pan_gesture, -pan_gesture->get_delta());24882489} break;24902491case NAVIGATION_ZOOM: {2492_nav_zoom(pan_gesture, pan_gesture->get_delta());24932494} break;24952496case NAVIGATION_ORBIT: {2497_nav_orbit(pan_gesture, -pan_gesture->get_delta());24982499} break;25002501case NAVIGATION_LOOK: {2502_nav_look(pan_gesture, pan_gesture->get_delta());25032504} break;25052506default: {2507}2508}2509}25102511if (k.is_valid()) {2512if (!k->is_pressed()) {2513return;2514}25152516if (_edit.instant) {2517// In a Blender-style transform, numbers set the magnitude of the transform.2518// E.g. pressing g4.5x means "translate 4.5 units along the X axis".2519bool processed = true;2520Key keycode = k->get_keycode();2521Key physical_keycode = k->get_physical_keycode();25222523// Use physical keycode for main keyboard numbers (for non-QWERTY layouts like AZERTY)2524// but regular keycode for numpad numbers.2525if ((physical_keycode >= Key::KEY_0 && physical_keycode <= Key::KEY_9) || (keycode >= Key::KP_0 && keycode <= Key::KP_9)) {2526uint32_t value;2527if (physical_keycode >= Key::KEY_0 && physical_keycode <= Key::KEY_9) {2528value = uint32_t(physical_keycode - Key::KEY_0);2529} else {2530value = uint32_t(keycode - Key::KP_0);2531}25322533if (_edit.numeric_next_decimal < 0) {2534_edit.numeric_input = _edit.numeric_input + value * Math::pow(10.0, _edit.numeric_next_decimal--);2535} else {2536_edit.numeric_input = _edit.numeric_input * 10 + value;2537}2538update_transform_numeric();2539} else if (keycode == Key::MINUS || keycode == Key::KP_SUBTRACT) {2540_edit.numeric_negate = !_edit.numeric_negate;2541update_transform_numeric();2542} else if (keycode == Key::PERIOD || physical_keycode == Key::KP_PERIOD) {2543// Use physical keycode for KP_PERIOD to ensure numpad period works consistently2544// across different keyboard layouts (like nordic keyboards).2545if (_edit.numeric_next_decimal == 0) {2546_edit.numeric_next_decimal = -1;2547}2548} else if (keycode == Key::ENTER || keycode == Key::KP_ENTER || keycode == Key::SPACE) {2549commit_transform();2550} else {2551processed = false;2552}25532554if (processed) {2555// Ignore mouse inputs once we receive a numeric input.2556set_process_input(false);2557accept_event();2558return;2559}2560}25612562Ref<InputEvent> event_mod = p_event;2563if (EDITOR_GET("editors/3d/navigation/emulate_numpad")) {2564const Key code = k->get_physical_keycode();2565if (code >= Key::KEY_0 && code <= Key::KEY_9) {2566event_mod = p_event->duplicate();2567Ref<InputEventKey> k_mod = event_mod;2568k_mod->set_keycode(code - Key::KEY_0 + Key::KP_0);2569}2570}25712572if (_edit.mode == TRANSFORM_NONE) {2573if (_edit.gizmo.is_null() && is_freelook_active() && k->get_keycode() == Key::ESCAPE) {2574set_freelook_active(false);2575return;2576}25772578if (_edit.gizmo.is_valid() && (k->get_keycode() == Key::ESCAPE || k->get_keycode() == Key::BACKSPACE)) {2579// Restore.2580_edit.gizmo->commit_handle(_edit.gizmo_handle, _edit.gizmo_handle_secondary, _edit.gizmo_initial_value, true);2581_edit.gizmo = Ref<EditorNode3DGizmo>();2582}2583if (k->get_keycode() == Key::ESCAPE && !cursor.region_select && !k->is_echo()) {2584_clear_selected();2585return;2586}2587} else {2588// We're actively transforming, handle keys specially2589TransformPlane new_plane = TRANSFORM_VIEW;2590if (ED_IS_SHORTCUT("spatial_editor/lock_transform_x", event_mod)) {2591new_plane = TRANSFORM_X_AXIS;2592} else if (ED_IS_SHORTCUT("spatial_editor/lock_transform_y", event_mod)) {2593new_plane = TRANSFORM_Y_AXIS;2594} else if (ED_IS_SHORTCUT("spatial_editor/lock_transform_z", event_mod)) {2595new_plane = TRANSFORM_Z_AXIS;2596} else if (_edit.mode != TRANSFORM_ROTATE) { // rotating on a plane doesn't make sense2597if (ED_IS_SHORTCUT("spatial_editor/lock_transform_yz", event_mod)) {2598new_plane = TRANSFORM_YZ;2599} else if (ED_IS_SHORTCUT("spatial_editor/lock_transform_xz", event_mod)) {2600new_plane = TRANSFORM_XZ;2601} else if (ED_IS_SHORTCUT("spatial_editor/lock_transform_xy", event_mod)) {2602new_plane = TRANSFORM_XY;2603}2604}26052606if (new_plane != TRANSFORM_VIEW) {2607if (new_plane != _edit.plane) {2608// lock me once and get a global constraint2609_edit.plane = new_plane;2610spatial_editor->set_local_coords_enabled(false);2611} else if (!spatial_editor->are_local_coords_enabled()) {2612// lock me twice and get a local constraint2613spatial_editor->set_local_coords_enabled(true);2614} else {2615// lock me thrice and we're back where we started2616_edit.plane = TRANSFORM_VIEW;2617spatial_editor->set_local_coords_enabled(false);2618}2619if (_edit.numeric_input != 0 || _edit.numeric_next_decimal != 0) {2620update_transform_numeric();2621} else {2622update_transform(Input::get_singleton()->is_key_pressed(Key::SHIFT));2623}2624accept_event();2625return;2626}2627}2628if (ED_IS_SHORTCUT("spatial_editor/snap", event_mod)) {2629if (_edit.mode != TRANSFORM_NONE) {2630_edit.snap = !_edit.snap;2631}2632}2633if (ED_IS_SHORTCUT("spatial_editor/bottom_view", event_mod)) {2634_menu_option(VIEW_BOTTOM);2635}2636if (ED_IS_SHORTCUT("spatial_editor/top_view", event_mod)) {2637_menu_option(VIEW_TOP);2638}2639if (ED_IS_SHORTCUT("spatial_editor/rear_view", event_mod)) {2640_menu_option(VIEW_REAR);2641}2642if (ED_IS_SHORTCUT("spatial_editor/front_view", event_mod)) {2643_menu_option(VIEW_FRONT);2644}2645if (ED_IS_SHORTCUT("spatial_editor/left_view", event_mod)) {2646_menu_option(VIEW_LEFT);2647}2648if (ED_IS_SHORTCUT("spatial_editor/right_view", event_mod)) {2649_menu_option(VIEW_RIGHT);2650}2651if (ED_IS_SHORTCUT("spatial_editor/orbit_view_down", event_mod)) {2652// Clamp rotation to roughly -90..90 degrees so the user can't look upside-down and end up disoriented.2653cursor.x_rot = CLAMP(cursor.x_rot - Math::PI / 12.0, -1.57, 1.57);2654cursor.unsnapped_x_rot = cursor.x_rot;2655view_type = VIEW_TYPE_USER;2656_update_name();2657}2658if (ED_IS_SHORTCUT("spatial_editor/orbit_view_up", event_mod)) {2659// Clamp rotation to roughly -90..90 degrees so the user can't look upside-down and end up disoriented.2660cursor.x_rot = CLAMP(cursor.x_rot + Math::PI / 12.0, -1.57, 1.57);2661cursor.unsnapped_x_rot = cursor.x_rot;2662view_type = VIEW_TYPE_USER;2663_update_name();2664}2665if (ED_IS_SHORTCUT("spatial_editor/orbit_view_right", event_mod)) {2666cursor.y_rot -= Math::PI / 12.0;2667cursor.unsnapped_y_rot = cursor.y_rot;2668view_type = VIEW_TYPE_USER;2669_update_name();2670}2671if (ED_IS_SHORTCUT("spatial_editor/orbit_view_left", event_mod)) {2672cursor.y_rot += Math::PI / 12.0;2673cursor.unsnapped_y_rot = cursor.y_rot;2674view_type = VIEW_TYPE_USER;2675_update_name();2676}2677if (ED_IS_SHORTCUT("spatial_editor/orbit_view_180", event_mod)) {2678cursor.y_rot += Math::PI;2679cursor.unsnapped_y_rot = cursor.y_rot;2680view_type = VIEW_TYPE_USER;2681_update_name();2682}2683if (ED_IS_SHORTCUT("spatial_editor/focus_origin", event_mod)) {2684_menu_option(VIEW_CENTER_TO_ORIGIN);2685}2686if (ED_IS_SHORTCUT("spatial_editor/focus_selection", event_mod)) {2687_menu_option(VIEW_CENTER_TO_SELECTION);2688}2689if (ED_IS_SHORTCUT("spatial_editor/align_transform_with_view", event_mod)) {2690_menu_option(VIEW_ALIGN_TRANSFORM_WITH_VIEW);2691}2692if (ED_IS_SHORTCUT("spatial_editor/align_rotation_with_view", event_mod)) {2693_menu_option(VIEW_ALIGN_ROTATION_WITH_VIEW);2694}2695if (ED_IS_SHORTCUT("spatial_editor/insert_anim_key", event_mod)) {2696if (!get_selected_count() || _edit.mode != TRANSFORM_NONE) {2697return;2698}26992700if (!AnimationPlayerEditor::get_singleton()->get_track_editor()->has_keying()) {2701set_message(TTR("Keying is disabled (no key inserted)."));2702return;2703}27042705const List<Node *> &selection = editor_selection->get_top_selected_node_list();27062707for (Node *E : selection) {2708Node3D *sp = Object::cast_to<Node3D>(E);2709if (!sp) {2710continue;2711}27122713spatial_editor->emit_signal(SNAME("transform_key_request"), sp, "", sp->get_transform());2714}27152716set_message(TTR("Animation Key Inserted."));2717}2718if (ED_IS_SHORTCUT("spatial_editor/cancel_transform", event_mod) && _edit.mode != TRANSFORM_NONE) {2719cancel_transform();2720}2721if (!is_freelook_active() && !k->is_echo()) {2722if (ED_IS_SHORTCUT("spatial_editor/reset_transform_position", event_mod)) {2723_reset_transform(TransformType::POSITION);2724}2725if (ED_IS_SHORTCUT("spatial_editor/reset_transform_rotation", event_mod)) {2726_reset_transform(TransformType::ROTATION);2727}2728if (ED_IS_SHORTCUT("spatial_editor/reset_transform_scale", event_mod)) {2729_reset_transform(TransformType::SCALE);2730}2731if (ED_IS_SHORTCUT("spatial_editor/instant_translate", event_mod) && (_edit.mode != TRANSFORM_TRANSLATE || collision_reposition)) {2732if (_edit.mode == TRANSFORM_NONE) {2733begin_transform(TRANSFORM_TRANSLATE, true);2734} else if (_edit.instant || collision_reposition) {2735commit_transform();2736begin_transform(TRANSFORM_TRANSLATE, true);2737}2738}2739if (ED_IS_SHORTCUT("spatial_editor/instant_rotate", event_mod) && _edit.mode != TRANSFORM_ROTATE) {2740if (_edit.mode == TRANSFORM_NONE) {2741begin_transform(TRANSFORM_ROTATE, true);2742} else if (_edit.instant || collision_reposition) {2743commit_transform();2744begin_transform(TRANSFORM_ROTATE, true);2745}2746}2747if (ED_IS_SHORTCUT("spatial_editor/instant_scale", event_mod) && _edit.mode != TRANSFORM_SCALE) {2748if (_edit.mode == TRANSFORM_NONE) {2749begin_transform(TRANSFORM_SCALE, true);2750} else if (_edit.instant || collision_reposition) {2751commit_transform();2752begin_transform(TRANSFORM_SCALE, true);2753}2754}2755if (ED_IS_SHORTCUT("spatial_editor/collision_reposition", event_mod) && editor_selection->get_top_selected_node_list().size() == 1 && !collision_reposition) {2756if (_edit.mode == TRANSFORM_NONE || _edit.instant) {2757if (_edit.mode == TRANSFORM_NONE) {2758_compute_edit(_edit.mouse_pos);2759} else {2760commit_transform();2761_compute_edit(_edit.mouse_pos);2762}2763_edit.mode = TRANSFORM_TRANSLATE;2764collision_reposition = true;2765}2766}2767}27682769// Freelook doesn't work in orthogonal mode.2770if (!orthogonal && ED_IS_SHORTCUT("spatial_editor/freelook_toggle", event_mod)) {2771set_freelook_active(!is_freelook_active());27722773} else if (k->get_keycode() == Key::ESCAPE) {2774set_freelook_active(false);2775}27762777if (k->get_keycode() == Key::SPACE) {2778if (!k->is_pressed()) {2779emit_signal(SNAME("toggle_maximize_view"), this);2780}2781}27822783if (ED_IS_SHORTCUT("spatial_editor/decrease_fov", event_mod)) {2784scale_fov(-0.05);2785}27862787if (ED_IS_SHORTCUT("spatial_editor/increase_fov", event_mod)) {2788scale_fov(0.05);2789}27902791if (ED_IS_SHORTCUT("spatial_editor/reset_fov", event_mod)) {2792reset_fov();2793}2794}27952796// freelook uses most of the useful shortcuts, like save, so its ok2797// to consider freelook active as end of the line for future events.2798if (freelook_active) {2799accept_event();2800}2801}28022803int Node3DEditorViewport::_get_shortcut_input_count(const String &p_name) {2804Ref<Shortcut> check_shortcut = ED_GET_SHORTCUT(p_name);28052806ERR_FAIL_COND_V_MSG(check_shortcut.is_null(), 0, "The Shortcut was null, possible name mismatch.");28072808return check_shortcut->get_events().size();2809}28102811Node3DEditorViewport::NavigationMode Node3DEditorViewport::_get_nav_mode_from_shortcut_check(ViewportNavMouseButton p_mouse_button, Vector<ShortcutCheckSet> p_shortcut_check_sets, bool p_use_not_empty) {2812if (p_use_not_empty) {2813for (const ShortcutCheckSet &shortcut_check_set : p_shortcut_check_sets) {2814if (shortcut_check_set.mod_pressed && shortcut_check_set.shortcut_not_empty) {2815return shortcut_check_set.result_nav_mode;2816}2817}2818} else {2819for (const ShortcutCheckSet &shortcut_check_set : p_shortcut_check_sets) {2820if (shortcut_check_set.mouse_preference == p_mouse_button && shortcut_check_set.mod_pressed) {2821return shortcut_check_set.result_nav_mode;2822}2823}2824}28252826return NAVIGATION_NONE;2827}28282829void Node3DEditorViewport::_nav_pan(Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative) {2830const NavigationScheme nav_scheme = (NavigationScheme)EDITOR_GET("editors/3d/navigation/navigation_scheme").operator int();2831const real_t translation_sensitivity = EDITOR_GET("editors/3d/navigation_feel/translation_sensitivity");28322833real_t pan_speed = translation_sensitivity / 150.0;2834if (p_event.is_valid() && nav_scheme == NAVIGATION_MAYA && p_event->is_shift_pressed()) {2835pan_speed *= 10;2836}28372838Transform3D camera_transform;28392840camera_transform.translate_local(cursor.pos);2841camera_transform.basis.rotate(Vector3(1, 0, 0), -cursor.x_rot);2842camera_transform.basis.rotate(Vector3(0, 1, 0), -cursor.y_rot);2843const bool invert_x_axis = EDITOR_GET("editors/3d/navigation/invert_x_axis");2844const bool invert_y_axis = EDITOR_GET("editors/3d/navigation/invert_y_axis");2845Vector3 translation(2846(invert_x_axis ? -1 : 1) * -p_relative.x * pan_speed,2847(invert_y_axis ? -1 : 1) * p_relative.y * pan_speed,28480);2849translation *= cursor.distance / DISTANCE_DEFAULT;2850camera_transform.translate_local(translation);2851cursor.pos = camera_transform.origin;2852}28532854void Node3DEditorViewport::_nav_zoom(Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative) {2855const NavigationScheme nav_scheme = (NavigationScheme)EDITOR_GET("editors/3d/navigation/navigation_scheme").operator int();28562857real_t zoom_speed = 1 / 80.0;2858if (p_event.is_valid() && nav_scheme == NAVIGATION_MAYA && p_event->is_shift_pressed()) {2859zoom_speed *= 10;2860}28612862NavigationZoomStyle zoom_style = (NavigationZoomStyle)EDITOR_GET("editors/3d/navigation/zoom_style").operator int();2863if (zoom_style == NAVIGATION_ZOOM_HORIZONTAL) {2864if (p_relative.x > 0) {2865scale_cursor_distance(1 - p_relative.x * zoom_speed);2866} else if (p_relative.x < 0) {2867scale_cursor_distance(1.0 / (1 + p_relative.x * zoom_speed));2868}2869} else {2870if (p_relative.y > 0) {2871scale_cursor_distance(1 + p_relative.y * zoom_speed);2872} else if (p_relative.y < 0) {2873scale_cursor_distance(1.0 / (1 - p_relative.y * zoom_speed));2874}2875}2876}28772878void Node3DEditorViewport::_nav_orbit(Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative) {2879if (lock_rotation) {2880_nav_pan(p_event, p_relative);2881return;2882}28832884const real_t degrees_per_pixel = EDITOR_GET("editors/3d/navigation_feel/orbit_sensitivity");2885const real_t radians_per_pixel = Math::deg_to_rad(degrees_per_pixel);2886const bool invert_y_axis = EDITOR_GET("editors/3d/navigation/invert_y_axis");2887const bool invert_x_axis = EDITOR_GET("editors/3d/navigation/invert_x_axis");28882889cursor.unsnapped_x_rot += p_relative.y * radians_per_pixel * (invert_y_axis ? -1 : 1);2890cursor.unsnapped_x_rot = CLAMP(cursor.unsnapped_x_rot, -1.57, 1.57);2891cursor.unsnapped_y_rot += p_relative.x * radians_per_pixel * (invert_x_axis ? -1 : 1);28922893cursor.x_rot = cursor.unsnapped_x_rot;2894cursor.y_rot = cursor.unsnapped_y_rot;28952896bool snap_modifier_configured = !_is_shortcut_empty("spatial_editor/viewport_orbit_snap_modifier_1") || !_is_shortcut_empty("spatial_editor/viewport_orbit_snap_modifier_2");28972898if (snap_modifier_configured && _is_nav_modifier_pressed("spatial_editor/viewport_orbit_snap_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_orbit_snap_modifier_2")) {2899const real_t snap_angle = Math::deg_to_rad(45.0);2900const real_t snap_threshold = Math::deg_to_rad((real_t)EDITOR_GET("editors/3d/navigation_feel/angle_snap_threshold"));29012902real_t x_rot_snapped = Math::snapped(cursor.unsnapped_x_rot, snap_angle);2903real_t y_rot_snapped = Math::snapped(cursor.unsnapped_y_rot, snap_angle);29042905real_t x_dist = Math::abs(cursor.unsnapped_x_rot - x_rot_snapped);2906real_t y_dist = Math::abs(cursor.unsnapped_y_rot - y_rot_snapped);29072908if (x_dist < snap_threshold && y_dist < snap_threshold) {2909cursor.x_rot = x_rot_snapped;2910cursor.y_rot = y_rot_snapped;29112912real_t y_rot_wrapped = Math::wrapf(y_rot_snapped, (real_t)-Math::PI, (real_t)Math::PI);29132914if (Math::abs(x_rot_snapped) < snap_threshold) {2915if (Math::abs(y_rot_wrapped) < snap_threshold) {2916view_type = VIEW_TYPE_FRONT;2917} else if (Math::abs(Math::abs(y_rot_wrapped) - Math::PI) < snap_threshold) {2918view_type = VIEW_TYPE_REAR;2919} else if (Math::abs(y_rot_wrapped - Math::PI / 2.0) < snap_threshold) {2920view_type = VIEW_TYPE_LEFT;2921} else if (Math::abs(y_rot_wrapped + Math::PI / 2.0) < snap_threshold) {2922view_type = VIEW_TYPE_RIGHT;2923} else {2924// Only switch to ortho for 90-degree views.2925return;2926}2927_set_auto_orthogonal();2928_update_name();2929} else if (Math::abs(Math::abs(x_rot_snapped) - Math::PI / 2.0) < snap_threshold) {2930if (Math::abs(y_rot_wrapped) < snap_threshold ||2931Math::abs(Math::abs(y_rot_wrapped) - Math::PI) < snap_threshold ||2932Math::abs(y_rot_wrapped - Math::PI / 2.0) < snap_threshold ||2933Math::abs(y_rot_wrapped + Math::PI / 2.0) < snap_threshold) {2934view_type = x_rot_snapped > 0 ? VIEW_TYPE_TOP : VIEW_TYPE_BOTTOM;2935_set_auto_orthogonal();2936_update_name();2937}2938}29392940return;2941}2942}29432944view_type = VIEW_TYPE_USER;2945if (orthogonal && auto_orthogonal) {2946_menu_option(VIEW_PERSPECTIVE);2947}2948}29492950void Node3DEditorViewport::_nav_look(Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative) {2951if (orthogonal) {2952_nav_pan(p_event, p_relative);2953return;2954}29552956if (orthogonal && auto_orthogonal) {2957_menu_option(VIEW_PERSPECTIVE);2958}29592960// Scale mouse sensitivity with camera FOV scale when zoomed in to make it easier to point at things.2961const real_t degrees_per_pixel = real_t(EDITOR_GET("editors/3d/freelook/freelook_sensitivity")) * MIN(1.0, cursor.fov_scale);2962const real_t radians_per_pixel = Math::deg_to_rad(degrees_per_pixel);2963const bool invert_y_axis = EDITOR_GET("editors/3d/navigation/invert_y_axis");29642965// Note: do NOT assume the camera has the "current" transform, because it is interpolated and may have "lag".2966const Transform3D prev_camera_transform = to_camera_transform(cursor);29672968if (invert_y_axis) {2969cursor.x_rot -= p_relative.y * radians_per_pixel;2970} else {2971cursor.x_rot += p_relative.y * radians_per_pixel;2972}2973// Clamp the Y rotation to roughly -90..90 degrees so the user can't look upside-down and end up disoriented.2974cursor.x_rot = CLAMP(cursor.x_rot, -1.57, 1.57);2975cursor.unsnapped_x_rot = cursor.x_rot;29762977cursor.y_rot += p_relative.x * radians_per_pixel;2978cursor.unsnapped_y_rot = cursor.y_rot;29792980// Look is like the opposite of Orbit: the focus point rotates around the camera2981Transform3D camera_transform = to_camera_transform(cursor);2982Vector3 pos = camera_transform.xform(Vector3(0, 0, 0));2983Vector3 prev_pos = prev_camera_transform.xform(Vector3(0, 0, 0));2984Vector3 diff = prev_pos - pos;2985cursor.pos += diff;29862987view_type = VIEW_TYPE_USER;2988_update_name();2989}29902991void Node3DEditorViewport::set_freelook_active(bool active_now) {2992if (!freelook_active && active_now) {2993// Sync camera cursor to cursor to "cut" interpolation jumps due to changing referential2994cursor = camera_cursor;29952996// Make sure eye_pos is synced, because freelook referential is eye pos rather than orbit pos2997Vector3 forward = to_camera_transform(cursor).basis.xform(Vector3(0, 0, -1));2998cursor.eye_pos = cursor.pos - cursor.distance * forward;2999// Also sync the camera cursor, otherwise switching to freelook will be trippy if inertia is active3000camera_cursor.eye_pos = cursor.eye_pos;30013002if (EDITOR_GET("editors/3d/freelook/freelook_speed_zoom_link")) {3003// Re-adjust freelook speed from the current zoom level3004real_t base_speed = EDITOR_GET("editors/3d/freelook/freelook_base_speed");3005freelook_speed = base_speed * cursor.distance;3006}30073008previous_mouse_position = get_local_mouse_position();30093010spatial_editor->set_freelook_viewport(this);30113012// Hide mouse like in an FPS (warping doesn't work)3013Input::get_singleton()->set_mouse_mode(Input::MouseMode::MOUSE_MODE_CAPTURED);30143015} else if (freelook_active && !active_now) {3016// Sync camera cursor to cursor to "cut" interpolation jumps due to changing referential3017cursor = camera_cursor;30183019spatial_editor->set_freelook_viewport(nullptr);30203021// Restore mouse3022Input::get_singleton()->set_mouse_mode(Input::MouseMode::MOUSE_MODE_VISIBLE);30233024// Restore the previous mouse position when leaving freelook mode.3025// This is done because leaving `Input.MOUSE_MODE_CAPTURED` will center the cursor3026// due to OS limitations.3027warp_mouse(previous_mouse_position);3028}30293030freelook_active = active_now;3031}30323033void Node3DEditorViewport::scale_fov(real_t p_fov_offset) {3034cursor.fov_scale = CLAMP(cursor.fov_scale + p_fov_offset, 0.1, 2.5);3035surface->queue_redraw();3036}30373038void Node3DEditorViewport::reset_fov() {3039cursor.fov_scale = 1.0;3040surface->queue_redraw();3041}30423043void Node3DEditorViewport::scale_cursor_distance(real_t scale) {3044real_t min_distance = MAX(camera->get_near() * 4, ZOOM_FREELOOK_MIN);3045real_t max_distance = MIN(camera->get_far() / 4, ZOOM_FREELOOK_MAX);3046if (unlikely(min_distance > max_distance)) {3047cursor.distance = (min_distance + max_distance) / 2;3048} else {3049cursor.distance = CLAMP(cursor.distance * scale, min_distance, max_distance);3050}30513052if (cursor.distance == max_distance || cursor.distance == min_distance) {3053zoom_failed_attempts_count++;3054} else {3055zoom_failed_attempts_count = 0;3056}30573058zoom_indicator_delay = ZOOM_FREELOOK_INDICATOR_DELAY_S;3059surface->queue_redraw();3060}30613062void Node3DEditorViewport::scale_freelook_speed(real_t scale) {3063real_t min_speed = MAX(camera->get_near() * 4, ZOOM_FREELOOK_MIN);3064real_t max_speed = MIN(camera->get_far() / 4, ZOOM_FREELOOK_MAX);3065if (unlikely(min_speed > max_speed)) {3066freelook_speed = (min_speed + max_speed) / 2;3067} else {3068freelook_speed = CLAMP(freelook_speed * scale, min_speed, max_speed);3069}30703071zoom_indicator_delay = ZOOM_FREELOOK_INDICATOR_DELAY_S;3072surface->queue_redraw();3073}30743075bool Node3DEditorViewport::_is_nav_modifier_pressed(const String &p_name) {3076return _is_shortcut_empty(p_name) || Input::get_singleton()->is_action_pressed(p_name);3077}30783079bool Node3DEditorViewport::_is_shortcut_empty(const String &p_name) {3080Ref<Shortcut> check_shortcut = ED_GET_SHORTCUT(p_name);30813082ERR_FAIL_COND_V_MSG(check_shortcut.is_null(), true, "The Shortcut was null, possible name mismatch.");30833084return check_shortcut->get_events().is_empty();3085}30863087Point2 Node3DEditorViewport::_get_warped_mouse_motion(const Ref<InputEventMouseMotion> &p_ev_mouse_motion) const {3088Point2 relative;3089if (bool(EDITOR_GET("editors/3d/navigation/warped_mouse_panning"))) {3090relative = Input::get_singleton()->warp_mouse_motion(p_ev_mouse_motion, surface->get_global_rect());3091} else {3092relative = p_ev_mouse_motion->get_relative();3093}3094return relative;3095}30963097void Node3DEditorViewport::_update_freelook(real_t delta) {3098if (!is_freelook_active()) {3099return;3100}31013102const FreelookNavigationScheme navigation_scheme = (FreelookNavigationScheme)EDITOR_GET("editors/3d/freelook/freelook_navigation_scheme").operator int();31033104Vector3 forward;3105if (navigation_scheme == FREELOOK_FULLY_AXIS_LOCKED) {3106// Forward/backward keys will always go straight forward/backward, never moving on the Y axis.3107forward = Vector3(0, 0, -1).rotated(Vector3(0, 1, 0), camera->get_rotation().y);3108} else {3109// Forward/backward keys will be relative to the camera pitch.3110forward = camera->get_transform().basis.xform(Vector3(0, 0, -1));3111}31123113const Vector3 right = camera->get_transform().basis.xform(Vector3(1, 0, 0));31143115Vector3 up;3116if (navigation_scheme == FREELOOK_PARTIALLY_AXIS_LOCKED || navigation_scheme == FREELOOK_FULLY_AXIS_LOCKED) {3117// Up/down keys will always go up/down regardless of camera pitch.3118up = Vector3(0, 1, 0);3119} else {3120// Up/down keys will be relative to the camera pitch.3121up = camera->get_transform().basis.xform(Vector3(0, 1, 0));3122}31233124Vector3 direction;31253126// Use actions from the inputmap, as this is the only way to reliably detect input in this method.3127// See #54469 for more discussion and explanation.3128Input *inp = Input::get_singleton();3129if (inp->is_action_pressed("spatial_editor/freelook_left")) {3130direction -= right;3131}3132if (inp->is_action_pressed("spatial_editor/freelook_right")) {3133direction += right;3134}3135if (inp->is_action_pressed("spatial_editor/freelook_forward")) {3136direction += forward;3137}3138if (inp->is_action_pressed("spatial_editor/freelook_backwards")) {3139direction -= forward;3140}3141if (inp->is_action_pressed("spatial_editor/freelook_up")) {3142direction += up;3143}3144if (inp->is_action_pressed("spatial_editor/freelook_down")) {3145direction -= up;3146}31473148real_t speed = freelook_speed;31493150if (inp->is_action_pressed("spatial_editor/freelook_speed_modifier")) {3151speed *= 3.0;3152}3153if (inp->is_action_pressed("spatial_editor/freelook_slow_modifier")) {3154speed *= 0.333333;3155}31563157const Vector3 motion = direction * speed * delta;3158cursor.pos += motion;3159cursor.eye_pos += motion;3160}31613162void Node3DEditorViewport::set_message(const String &p_message, float p_time) {3163message = p_message;3164message_time = p_time;3165}31663167void Node3DEditorPlugin::edited_scene_changed() {3168for (uint32_t i = 0; i < Node3DEditor::VIEWPORTS_COUNT; i++) {3169Node3DEditorViewport *viewport = Node3DEditor::get_singleton()->get_editor_viewport(i);3170if (viewport->is_visible()) {3171viewport->notification(Control::NOTIFICATION_VISIBILITY_CHANGED);3172}3173}3174}31753176void Node3DEditorViewport::_project_settings_changed() {3177// Update shadow atlas if changed.3178int shadowmap_size = GLOBAL_GET("rendering/lights_and_shadows/positional_shadow/atlas_size");3179bool shadowmap_16_bits = GLOBAL_GET("rendering/lights_and_shadows/positional_shadow/atlas_16_bits");3180int atlas_q0 = GLOBAL_GET("rendering/lights_and_shadows/positional_shadow/atlas_quadrant_0_subdiv");3181int atlas_q1 = GLOBAL_GET("rendering/lights_and_shadows/positional_shadow/atlas_quadrant_1_subdiv");3182int atlas_q2 = GLOBAL_GET("rendering/lights_and_shadows/positional_shadow/atlas_quadrant_2_subdiv");3183int atlas_q3 = GLOBAL_GET("rendering/lights_and_shadows/positional_shadow/atlas_quadrant_3_subdiv");31843185viewport->set_positional_shadow_atlas_size(shadowmap_size);3186viewport->set_positional_shadow_atlas_16_bits(shadowmap_16_bits);3187viewport->set_positional_shadow_atlas_quadrant_subdiv(0, Viewport::PositionalShadowAtlasQuadrantSubdiv(atlas_q0));3188viewport->set_positional_shadow_atlas_quadrant_subdiv(1, Viewport::PositionalShadowAtlasQuadrantSubdiv(atlas_q1));3189viewport->set_positional_shadow_atlas_quadrant_subdiv(2, Viewport::PositionalShadowAtlasQuadrantSubdiv(atlas_q2));3190viewport->set_positional_shadow_atlas_quadrant_subdiv(3, Viewport::PositionalShadowAtlasQuadrantSubdiv(atlas_q3));31913192// Update MSAA, screen-space AA and debanding if changed31933194const int msaa_mode = GLOBAL_GET("rendering/anti_aliasing/quality/msaa_3d");3195viewport->set_msaa_3d(Viewport::MSAA(msaa_mode));3196const int ssaa_mode = GLOBAL_GET("rendering/anti_aliasing/quality/screen_space_aa");3197viewport->set_screen_space_aa(Viewport::ScreenSpaceAA(ssaa_mode));3198const bool use_taa = GLOBAL_GET("rendering/anti_aliasing/quality/use_taa");3199viewport->set_use_taa(use_taa);32003201const bool transparent_background = GLOBAL_GET("rendering/viewport/transparent_background");3202viewport->set_transparent_background(transparent_background);32033204const bool use_hdr_2d = GLOBAL_GET("rendering/viewport/hdr_2d");3205viewport->set_use_hdr_2d(use_hdr_2d);32063207const bool use_debanding = GLOBAL_GET("rendering/anti_aliasing/quality/use_debanding");3208viewport->set_use_debanding(use_debanding);32093210const bool use_occlusion_culling = GLOBAL_GET("rendering/occlusion_culling/use_occlusion_culling");3211viewport->set_use_occlusion_culling(use_occlusion_culling);32123213const float mesh_lod_threshold = GLOBAL_GET("rendering/mesh_lod/lod_change/threshold_pixels");3214viewport->set_mesh_lod_threshold(mesh_lod_threshold);32153216const Viewport::Scaling3DMode scaling_3d_mode = Viewport::Scaling3DMode(int(GLOBAL_GET("rendering/scaling_3d/mode")));3217viewport->set_scaling_3d_mode(scaling_3d_mode);32183219_update_shrink();32203221const float fsr_sharpness = GLOBAL_GET("rendering/scaling_3d/fsr_sharpness");3222viewport->set_fsr_sharpness(fsr_sharpness);32233224const float texture_mipmap_bias = GLOBAL_GET("rendering/textures/default_filters/texture_mipmap_bias");3225viewport->set_texture_mipmap_bias(texture_mipmap_bias);32263227const Viewport::AnisotropicFiltering anisotropic_filtering_level = Viewport::AnisotropicFiltering(int(GLOBAL_GET("rendering/textures/default_filters/anisotropic_filtering_level")));3228viewport->set_anisotropic_filtering_level(anisotropic_filtering_level);3229}32303231static void override_label_colors(Control *p_control) {3232p_control->begin_bulk_theme_override();3233p_control->add_theme_color_override(SceneStringName(font_color), p_control->get_theme_color(SNAME("font_dark_background_color"), EditorStringName(Editor)));3234p_control->add_theme_color_override("font_hover_color", p_control->get_theme_color(SNAME("font_dark_background_hover_color"), EditorStringName(Editor)));3235p_control->add_theme_color_override("font_focus_color", p_control->get_theme_color(SNAME("font_dark_background_focus_color"), EditorStringName(Editor)));3236p_control->add_theme_color_override("font_pressed_color", p_control->get_theme_color(SNAME("font_dark_background_pressed_color"), EditorStringName(Editor)));3237p_control->add_theme_color_override("font_hover_pressed_color", p_control->get_theme_color(SNAME("font_dark_background_hover_pressed_color"), EditorStringName(Editor)));3238p_control->end_bulk_theme_override();3239}32403241static void override_button_stylebox(Button *p_button, const Ref<StyleBox> p_stylebox) {3242p_button->begin_bulk_theme_override();3243p_button->add_theme_style_override(CoreStringName(normal), p_stylebox);3244p_button->add_theme_style_override("normal_mirrored", p_stylebox);3245p_button->add_theme_style_override(SceneStringName(hover), p_stylebox);3246p_button->add_theme_style_override("hover_mirrored", p_stylebox);3247p_button->add_theme_style_override("hover_pressed", p_stylebox);3248p_button->add_theme_style_override("hover_pressed_mirrored", p_stylebox);3249p_button->add_theme_style_override(SceneStringName(pressed), p_stylebox);3250p_button->add_theme_style_override("pressed_mirrored", p_stylebox);3251p_button->add_theme_style_override("focus", p_stylebox);3252p_button->add_theme_style_override("focus_mirrored", p_stylebox);3253p_button->add_theme_style_override("disabled", p_stylebox);3254p_button->add_theme_style_override("disabled_mirrored", p_stylebox);3255p_button->end_bulk_theme_override();3256}32573258void Node3DEditorViewport::_notification(int p_what) {3259switch (p_what) {3260case NOTIFICATION_TRANSLATION_CHANGED: {3261_update_name();3262_update_centered_labels();3263message_time = MIN(message_time, 0.001); // Make it disappear.32643265Key key = OS::prefer_meta_over_ctrl() ? Key::META : Key::CTRL;3266preview_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)));32673268const int item_count = display_submenu->get_item_count();3269for (int i = 0; i < item_count; i++) {3270const Array item_data = display_submenu->get_item_metadata(i);3271if (item_data.is_empty()) {3272continue;3273}32743275SupportedRenderingMethods rendering_methods = item_data[0];3276String base_tooltip = item_data[1];32773278bool disabled = false;3279String disabled_tooltip;3280switch (rendering_methods) {3281case SupportedRenderingMethods::ALL:3282break;3283case SupportedRenderingMethods::FORWARD_PLUS_MOBILE:3284disabled = OS::get_singleton()->get_current_rendering_method() == "gl_compatibility";3285disabled_tooltip = TTR("This debug draw mode is only supported when using the Forward+ or Mobile renderer.");3286break;3287case SupportedRenderingMethods::FORWARD_PLUS:3288disabled = OS::get_singleton()->get_current_rendering_method() == "gl_compatibility" || OS::get_singleton()->get_current_rendering_method() == "mobile";3289disabled_tooltip = TTR("This debug draw mode is only supported when using the Forward+ renderer.");3290break;3291}32923293display_submenu->set_item_disabled(i, disabled);3294String tooltip = TTR(base_tooltip);3295if (disabled) {3296if (tooltip.is_empty()) {3297tooltip = disabled_tooltip;3298} else {3299tooltip += "\n\n" + disabled_tooltip;3300}3301}3302display_submenu->set_item_tooltip(i, tooltip);3303}3304} break;33053306case NOTIFICATION_READY: {3307ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &Node3DEditorViewport::_project_settings_changed));3308_update_navigation_controls_visibility();3309} break;33103311case NOTIFICATION_VISIBILITY_CHANGED: {3312bool vp_visible = is_visible_in_tree();33133314set_process(vp_visible);3315set_physics_process(vp_visible);33163317if (vp_visible) {3318orthogonal = view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_ORTHOGONAL));3319_update_name();3320_update_camera(0);3321} else {3322set_freelook_active(false);3323}3324callable_mp(this, &Node3DEditorViewport::update_transform_gizmo_view).call_deferred();3325} break;33263327case NOTIFICATION_RESIZED: {3328callable_mp(this, &Node3DEditorViewport::update_transform_gizmo_view).call_deferred();3329} break;33303331case NOTIFICATION_PROCESS: {3332if (ruler->is_inside_tree()) {3333Vector3 start_pos = ruler_start_point->get_global_position();3334Vector3 end_pos = ruler_end_point->get_global_position();33353336geometry->clear_surfaces();3337geometry->surface_begin(Mesh::PRIMITIVE_LINES);3338geometry->surface_add_vertex(start_pos);3339geometry->surface_add_vertex(end_pos);3340geometry->surface_end();33413342float distance = start_pos.distance_to(end_pos);3343ruler_label->set_text(TranslationServer::get_singleton()->format_number(vformat("%.3f m", distance), _get_locale()));33443345Vector3 center = (start_pos + end_pos) / 2;3346Vector2 screen_position = camera->unproject_position(center) - (ruler_label->get_custom_minimum_size() / 2);3347ruler_label->set_position(screen_position);3348}33493350real_t delta = get_process_delta_time();33513352if (zoom_indicator_delay > 0) {3353zoom_indicator_delay -= delta;3354if (zoom_indicator_delay <= 0) {3355surface->queue_redraw();3356zoom_limit_label->hide();3357}3358}33593360_update_freelook(delta);33613362Node *scene_root = SceneTreeDock::get_singleton()->get_editor_data()->get_edited_scene_root();3363if (previewing_cinema && scene_root != nullptr) {3364Camera3D *cam = scene_root->get_viewport()->get_camera_3d();3365if (cam != nullptr && cam != previewing) {3366//then switch the viewport's camera to the scene's viewport camera3367if (previewing != nullptr) {3368previewing->disconnect(SceneStringName(tree_exited), callable_mp(this, &Node3DEditorViewport::_preview_exited_scene));3369previewing->disconnect(CoreStringName(property_list_changed), callable_mp(this, &Node3DEditorViewport::_preview_camera_property_changed));3370}3371previewing = cam;3372previewing->connect(SceneStringName(tree_exited), callable_mp(this, &Node3DEditorViewport::_preview_exited_scene));3373previewing->connect(CoreStringName(property_list_changed), callable_mp(this, &Node3DEditorViewport::_preview_camera_property_changed));3374RS::get_singleton()->viewport_attach_camera(viewport->get_viewport_rid(), cam->get_camera());3375surface->queue_redraw();3376}3377}33783379if (_camera_moved_externally()) {3380// 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.3381_apply_camera_transform_to_cursor();3382_update_camera(0);3383} else {3384_update_camera(delta);3385}33863387const HashMap<ObjectID, Object *> &selection = editor_selection->get_selection();33883389bool changed = false;3390bool exist = false;33913392for (const KeyValue<ObjectID, Object *> &E : selection) {3393Node3D *sp = ObjectDB::get_instance<Node3D>(E.key);3394if (!sp) {3395continue;3396}33973398Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);3399if (!se) {3400continue;3401}34023403Transform3D t = sp->get_global_gizmo_transform();3404if (!t.is_finite()) {3405continue;3406}3407AABB new_aabb = _calculate_spatial_bounds(sp);34083409exist = true;3410if (se->last_xform == t && se->aabb == new_aabb && !se->last_xform_dirty) {3411continue;3412}3413changed = true;3414se->last_xform_dirty = false;3415se->last_xform = t;34163417se->aabb = new_aabb;34183419Transform3D t_offset = t;34203421// apply AABB scaling before item's global transform3422{3423const Vector3 offset(0.005, 0.005, 0.005);3424Basis aabb_s;3425aabb_s.scale(se->aabb.size + offset);3426t.translate_local(se->aabb.position - offset / 2);3427t.basis = t.basis * aabb_s;3428}3429{3430const Vector3 offset(0.01, 0.01, 0.01);3431Basis aabb_s;3432aabb_s.scale(se->aabb.size + offset);3433t_offset.translate_local(se->aabb.position - offset / 2);3434t_offset.basis = t_offset.basis * aabb_s;3435}34363437RenderingServer::get_singleton()->instance_set_transform(se->sbox_instance, t);3438RenderingServer::get_singleton()->instance_set_transform(se->sbox_instance_offset, t_offset);3439RenderingServer::get_singleton()->instance_set_transform(se->sbox_instance_xray, t);3440RenderingServer::get_singleton()->instance_set_transform(se->sbox_instance_xray_offset, t_offset);3441}34423443if (changed || (spatial_editor->is_gizmo_visible() && !exist)) {3444spatial_editor->update_transform_gizmo();3445}34463447if (message_time > 0) {3448if (message != last_message) {3449surface->queue_redraw();3450last_message = message;3451}34523453message_time -= get_process_delta_time();3454if (message_time < 0) {3455surface->queue_redraw();3456}3457}34583459bool show_info = view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_INFORMATION));3460if (show_info != info_panel->is_visible()) {3461info_panel->set_visible(show_info);3462}34633464Camera3D *current_camera;34653466if (previewing) {3467current_camera = previewing;3468} else {3469current_camera = camera;3470}34713472if (show_info) {3473const String viewport_size = vformat(U"%d × %d", viewport->get_size().x * viewport->get_scaling_3d_scale(), viewport->get_size().y * viewport->get_scaling_3d_scale());3474String text;3475text += vformat(TTR("X: %s"), rtos(current_camera->get_position().x).pad_decimals(1)) + "\n";3476text += vformat(TTR("Y: %s"), rtos(current_camera->get_position().y).pad_decimals(1)) + "\n";3477text += vformat(TTR("Z: %s"), rtos(current_camera->get_position().z).pad_decimals(1)) + "\n";3478text += "\n";3479text += vformat(3480TTR("Size: %s (%.1fMP)") + "\n",3481viewport_size,3482viewport->get_size().x * viewport->get_size().y * Math::pow(viewport->get_scaling_3d_scale(), 2) * 0.000001);34833484text += "\n";3485text += vformat(TTR("Objects: %d"), viewport->get_render_info(Viewport::RENDER_INFO_TYPE_VISIBLE, Viewport::RENDER_INFO_OBJECTS_IN_FRAME)) + "\n";3486text += vformat(TTR("Primitives: %d"), viewport->get_render_info(Viewport::RENDER_INFO_TYPE_VISIBLE, Viewport::RENDER_INFO_PRIMITIVES_IN_FRAME)) + "\n";3487text += vformat(TTR("Draw Calls: %d"), viewport->get_render_info(Viewport::RENDER_INFO_TYPE_VISIBLE, Viewport::RENDER_INFO_DRAW_CALLS_IN_FRAME));34883489info_label->set_text(text);3490}34913492// FPS Counter.3493bool show_fps = view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_FRAME_TIME));34943495if (show_fps != frame_time_panel->is_visible()) {3496frame_time_panel->set_visible(show_fps);3497RS::get_singleton()->viewport_set_measure_render_time(viewport->get_viewport_rid(), show_fps);3498for (int i = 0; i < FRAME_TIME_HISTORY; i++) {3499// Initialize to 120 FPS, so that the initial estimation until we get enough data is always reasonable.3500cpu_time_history[i] = 8.333333;3501gpu_time_history[i] = 8.333333;3502}3503cpu_time_history_index = 0;3504gpu_time_history_index = 0;3505}3506if (show_fps) {3507cpu_time_history[cpu_time_history_index] = RS::get_singleton()->viewport_get_measured_render_time_cpu(viewport->get_viewport_rid());3508cpu_time_history_index = (cpu_time_history_index + 1) % FRAME_TIME_HISTORY;3509double cpu_time = 0.0;3510for (int i = 0; i < FRAME_TIME_HISTORY; i++) {3511cpu_time += cpu_time_history[i];3512}3513cpu_time /= FRAME_TIME_HISTORY;3514// Prevent unrealistically low values.3515cpu_time = MAX(0.01, cpu_time);35163517gpu_time_history[gpu_time_history_index] = RS::get_singleton()->viewport_get_measured_render_time_gpu(viewport->get_viewport_rid());3518gpu_time_history_index = (gpu_time_history_index + 1) % FRAME_TIME_HISTORY;3519double gpu_time = 0.0;3520for (int i = 0; i < FRAME_TIME_HISTORY; i++) {3521gpu_time += gpu_time_history[i];3522}3523gpu_time /= FRAME_TIME_HISTORY;3524// Prevent division by zero for the FPS counter (and unrealistically low values).3525// This limits the reported FPS to 100000.3526gpu_time = MAX(0.01, gpu_time);35273528// Color labels depending on performance level ("good" = green, "OK" = yellow, "bad" = red).3529// Middle point is at 15 ms.3530cpu_time_label->set_text(vformat(TTR("CPU Time: %s ms"), rtos(cpu_time).pad_decimals(2)));3531cpu_time_label->add_theme_color_override(3532SceneStringName(font_color),3533frame_time_gradient->get_color_at_offset(3534Math::remap(cpu_time, 0, 30, 0, 1)));35353536gpu_time_label->set_text(vformat(TTR("GPU Time: %s ms"), rtos(gpu_time).pad_decimals(2)));3537// Middle point is at 15 ms.3538gpu_time_label->add_theme_color_override(3539SceneStringName(font_color),3540frame_time_gradient->get_color_at_offset(3541Math::remap(gpu_time, 0, 30, 0, 1)));35423543const double fps = 1000.0 / gpu_time;3544fps_label->set_text(vformat(TTR("FPS: %d"), fps));3545// Middle point is at 60 FPS.3546fps_label->add_theme_color_override(3547SceneStringName(font_color),3548frame_time_gradient->get_color_at_offset(3549Math::remap(fps, 110, 10, 0, 1)));3550}3551} break;35523553case NOTIFICATION_PHYSICS_PROCESS: {3554if (collision_reposition) {3555Node3D *selected_node = nullptr;35563557if (ruler->is_inside_tree()) {3558if (ruler_start_point->is_visible()) {3559selected_node = ruler_end_point;3560} else {3561selected_node = ruler_start_point;3562}3563} else {3564const List<Node *> &selection = editor_selection->get_top_selected_node_list();3565if (selection.size() == 1) {3566selected_node = Object::cast_to<Node3D>(selection.front()->get());3567}3568}35693570if (selected_node) {3571if (!ruler->is_inside_tree()) {3572double snap = EDITOR_GET("interface/inspector/default_float_step");3573int snap_step_decimals = Math::range_step_decimals(snap);3574set_message(TTR("Translating:") + " (" + String::num(selected_node->get_global_position().x, snap_step_decimals) + ", " +3575String::num(selected_node->get_global_position().y, snap_step_decimals) + ", " + String::num(selected_node->get_global_position().z, snap_step_decimals) + ")");3576}35773578selected_node->set_global_position(spatial_editor->snap_point(_get_instance_position(_edit.mouse_pos, selected_node)));35793580if (ruler->is_inside_tree() && !ruler_start_point->is_visible()) {3581ruler_end_point->set_global_position(ruler_start_point->get_global_position());3582ruler_start_point->set_visible(true);3583ruler_end_point->set_visible(true);3584ruler_label->set_visible(true);3585}3586}3587}35883589if (!update_preview_node) {3590return;3591}3592if (preview_node->is_inside_tree()) {3593preview_node_pos = spatial_editor->snap_point(_get_instance_position(preview_node_viewport_pos, preview_node));3594double snap = EDITOR_GET("interface/inspector/default_float_step");3595int snap_step_decimals = Math::range_step_decimals(snap);3596set_message(TTR("Instantiating:") + " (" + String::num(preview_node_pos.x, snap_step_decimals) + ", " +3597String::num(preview_node_pos.y, snap_step_decimals) + ", " + String::num(preview_node_pos.z, snap_step_decimals) + ")");3598Transform3D preview_gl_transform = Transform3D(Basis(), preview_node_pos);3599preview_node->set_global_transform(preview_gl_transform);3600if (!preview_node->is_visible()) {3601preview_node->show();3602}3603}3604update_preview_node = false;3605} break;36063607case NOTIFICATION_APPLICATION_FOCUS_OUT:3608case NOTIFICATION_WM_WINDOW_FOCUS_OUT: {3609set_freelook_active(false);3610cursor.region_select = false;3611surface->queue_redraw();36123613// Commit the drag if the window is focused out.3614if (_edit.mode != TRANSFORM_NONE) {3615commit_transform();3616return;3617}36183619if (_edit.gizmo.is_valid()) {3620// Certain gizmo plugins should be able to commit handles without dragging them.3621if (_edit.original_mouse_pos != _edit.mouse_pos || _edit.gizmo->get_plugin()->can_commit_handle_on_click()) {3622_edit.gizmo->commit_handle(_edit.gizmo_handle, _edit.gizmo_handle_secondary, _edit.gizmo_initial_value, false);3623}36243625spatial_editor->get_single_selected_node()->update_gizmos();3626_edit.gizmo = Ref<EditorNode3DGizmo>();3627}3628} break;36293630case NOTIFICATION_ENTER_TREE: {3631surface->connect(SceneStringName(draw), callable_mp(this, &Node3DEditorViewport::_draw));3632surface->connect(SceneStringName(gui_input), callable_mp(this, &Node3DEditorViewport::_sinput));3633surface->connect(SceneStringName(mouse_entered), callable_mp(this, &Node3DEditorViewport::_surface_mouse_enter));3634surface->connect(SceneStringName(mouse_exited), callable_mp(this, &Node3DEditorViewport::_surface_mouse_exit));3635surface->connect(SceneStringName(focus_entered), callable_mp(this, &Node3DEditorViewport::_surface_focus_enter));3636surface->connect(SceneStringName(focus_exited), callable_mp(this, &Node3DEditorViewport::_surface_focus_exit));36373638_init_gizmo_instance(index);3639} break;36403641case NOTIFICATION_EXIT_TREE: {3642_finish_gizmo_instances();3643} break;36443645case NOTIFICATION_THEME_CHANGED: {3646_update_centered_labels();36473648view_display_menu->set_button_icon(get_editor_theme_icon(SNAME("GuiTabMenuHlDarkBackground")));3649preview_camera->set_button_icon(get_editor_theme_icon(SNAME("Camera3DDarkBackground")));3650Control *gui_base = EditorNode::get_singleton()->get_gui_base();36513652const Ref<StyleBox> &information_3d_stylebox = gui_base->get_theme_stylebox(SNAME("Information3dViewport"), EditorStringName(EditorStyles));36533654override_button_stylebox(view_display_menu, information_3d_stylebox);3655override_label_colors(view_display_menu);3656override_button_stylebox(translation_preview_button, information_3d_stylebox);3657override_label_colors(translation_preview_button);3658override_button_stylebox(preview_camera, information_3d_stylebox);3659override_label_colors(preview_camera);36603661frame_time_gradient->set_color(0, get_theme_color(SNAME("success_color_dark_background"), EditorStringName(Editor)));3662frame_time_gradient->set_color(1, get_theme_color(SNAME("warning_color_dark_background"), EditorStringName(Editor)));3663frame_time_gradient->set_color(2, get_theme_color(SNAME("error_color_dark_background"), EditorStringName(Editor)));36643665info_panel->add_theme_style_override(SceneStringName(panel), information_3d_stylebox);3666override_label_colors(info_label);36673668frame_time_panel->add_theme_style_override(SceneStringName(panel), information_3d_stylebox);3669// Set a minimum width to prevent the width from changing all the time3670// when numbers vary rapidly. This minimum width is set based on a3671// GPU time of 999.99 ms in the current editor language.3672const float min_width = get_theme_font(SNAME("main"), EditorStringName(EditorFonts))->get_string_size(vformat(TTR("GPU Time: %s ms"), 999.99)).x;3673frame_time_panel->set_custom_minimum_size(Size2(min_width, 0) * EDSCALE);3674frame_time_vbox->add_theme_constant_override("separation", Math::round(-1 * EDSCALE));36753676cinema_label->add_theme_style_override(CoreStringName(normal), information_3d_stylebox);3677locked_label->add_theme_style_override(CoreStringName(normal), information_3d_stylebox);36783679ruler_label->add_theme_color_override(SceneStringName(font_color), Color(1.0, 0.9, 0.0, 1.0));3680ruler_label->add_theme_color_override("font_outline_color", Color(0.0, 0.0, 0.0, 1.0));3681ruler_label->add_theme_constant_override("outline_size", 4 * EDSCALE);3682ruler_label->add_theme_font_size_override(SceneStringName(font_size), 15 * EDSCALE);3683ruler_label->add_theme_font_override(SceneStringName(font), get_theme_font(SNAME("bold"), EditorStringName(EditorFonts)));3684} break;36853686case NOTIFICATION_DRAG_END: {3687// Clear preview material when dropped outside applicable object.3688if (spatial_editor->get_preview_material().is_valid() && !is_drag_successful()) {3689_reset_preview_material();3690_remove_preview_material();3691} else {3692_remove_preview_node();3693}3694} break;36953696case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {3697if (EditorSettings::get_singleton()->check_changed_settings_in_group("editors/3d")) {3698_update_navigation_controls_visibility();3699}3700} break;3701}3702}37033704static 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) {3705// Adjust bar size from control height3706const Vector2 surface_size = p_surface.get_size();3707const real_t h = surface_size.y / 2.0;3708const real_t y = (surface_size.y - h) / 2.0;37093710const Rect2 r(10 * EDSCALE, y, 6 * EDSCALE, h);3711const real_t sy = r.size.y * p_fill;37123713// Note: because this bar appears over the viewport, it has to stay readable for any background color3714// Draw both neutral dark and bright colors to account this3715p_surface.draw_rect(r, p_color * Color(1, 1, 1, 0.2));3716p_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));3717p_surface.draw_rect(r.grow(1), Color(0, 0, 0, 0.7), false, Math::round(EDSCALE));37183719const Vector2 icon_size = p_icon->get_size();3720const Vector2 icon_pos = Vector2(r.position.x - (icon_size.x - r.size.x) / 2, r.position.y + r.size.y + 2 * EDSCALE);3721p_surface.draw_texture(p_icon, icon_pos, p_color);37223723// Draw text below the bar (for speed/zoom information).3724p_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(4 * EDSCALE), Color(0, 0, 0));3725p_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);3726}37273728void Node3DEditorViewport::_draw() {3729EditorNode::get_singleton()->get_editor_plugins_over()->forward_3d_draw_over_viewport(surface);3730EditorNode::get_singleton()->get_editor_plugins_force_over()->forward_3d_force_draw_over_viewport(surface);37313732if (surface->has_focus() || rotation_control->has_focus()) {3733Size2 size = surface->get_size();3734Rect2 r = Rect2(Point2(), size);3735get_theme_stylebox(SNAME("FocusViewport"), EditorStringName(EditorStyles))->draw(surface->get_canvas_item(), r);3736}37373738if (cursor.region_select && movement_threshold_passed) {3739const Rect2 selection_rect = Rect2(cursor.region_begin, cursor.region_end - cursor.region_begin);37403741surface->draw_rect(3742selection_rect,3743get_theme_color(SNAME("box_selection_fill_color"), EditorStringName(Editor)));37443745surface->draw_rect(3746selection_rect,3747get_theme_color(SNAME("box_selection_stroke_color"), EditorStringName(Editor)),3748false,3749Math::round(EDSCALE));3750}37513752RID ci = surface->get_canvas_item();37533754if (message_time > 0) {3755Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("Label"));3756int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("Label"));3757Point2 msgpos = Point2(10 * EDSCALE, get_size().y - 14 * EDSCALE);3758font->draw_string(ci, msgpos + Point2(1, 1), message, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color(0, 0, 0, 0.8));3759font->draw_string(ci, msgpos + Point2(-1, -1), message, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color(0, 0, 0, 0.8));3760font->draw_string(ci, msgpos, message, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color(1, 1, 1, 1));3761}37623763if (_edit.mode == TRANSFORM_ROTATE) {3764Point2 center = point_to_screen(_edit.center);37653766Color handle_color;3767switch (_edit.plane) {3768case TRANSFORM_VIEW:3769handle_color = Color(1.0, 1.0, 1.0, 1.0);3770break;3771case TRANSFORM_X_AXIS:3772handle_color = get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor));3773break;3774case TRANSFORM_Y_AXIS:3775handle_color = get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor));3776break;3777case TRANSFORM_Z_AXIS:3778handle_color = get_theme_color(SNAME("axis_z_color"), EditorStringName(Editor));3779break;3780default:3781handle_color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));3782break;3783}37843785if (!_edit.is_trackball && _is_rotation_arc_visible() && !_edit.initial_click_vector.is_zero_approx()) {3786Vector3 up = _edit.rotation_axis;3787Vector3 right = _edit.initial_click_vector;37883789right = right - up * up.dot(right);3790right.normalize();3791Vector3 forward = up.cross(right);37923793real_t rotation_radius = (_edit.plane == TRANSFORM_VIEW) ? GIZMO_VIEW_ROTATION_SIZE : GIZMO_CIRCLE_SIZE;37943795const int circle_segments = 64;3796Vector<Point2> circle_points;3797for (int i = 0; i <= circle_segments; i++) {3798float angle = (float(i) / float(circle_segments)) * Math::TAU;3799Vector3 point_3d = _edit.center + gizmo_scale * rotation_radius * (right * Math::cos(angle) + forward * Math::sin(angle));3800Point2 point_2d = point_to_screen(point_3d);3801circle_points.push_back(point_2d);3802}38033804Color circle_color = (_edit.plane == TRANSFORM_VIEW) ? Color(1.0, 1.0, 1.0, 0.8) : handle_color.from_hsv(handle_color.get_h(), 0.6, 1.0, 0.8);3805Vector<Color> circle_colors;3806circle_colors.resize(circle_points.size());3807circle_colors.fill(circle_color);3808RenderingServer::get_singleton()->canvas_item_add_polyline(ci, circle_points, circle_colors, Math::round(2 * EDSCALE), true);38093810const int segments = 64;3811float display_angle = _edit.display_rotation_angle;38123813float abs_angle = Math::abs(display_angle);3814if (abs_angle > Math::TAU) {3815float remainder = Math::fmod((double)abs_angle, Math::TAU);3816remainder = remainder < 0.01 ? Math::TAU : remainder;3817display_angle = SIGN(display_angle) * remainder;3818abs_angle = remainder;3819}38203821int num_segments = MAX(8, int(abs_angle / (Math::TAU / segments) * segments));3822num_segments = MIN(num_segments, segments);38233824Color fill_color = Color(1.0, 1.0, 1.0, 0.2);38253826bool is_counterclockwise = display_angle > 0;3827float start_angle = is_counterclockwise ? 0.0f : display_angle;3828float end_angle = is_counterclockwise ? display_angle : 0.0f;38293830for (int i = 0; i < num_segments; i++) {3831float t1 = float(i) / float(num_segments);3832float t2 = float(i + 1) / float(num_segments);3833float angle1 = Math::lerp(start_angle, end_angle, t1);3834float angle2 = Math::lerp(start_angle, end_angle, t2);38353836Vector3 point1_3d = _edit.center + gizmo_scale * rotation_radius * (right * Math::cos(angle1) + forward * Math::sin(angle1));3837Vector3 point2_3d = _edit.center + gizmo_scale * rotation_radius * (right * Math::cos(angle2) + forward * Math::sin(angle2));38383839Point2 center_2d = center;3840Point2 point1_2d = point_to_screen(point1_3d);3841Point2 point2_2d = point_to_screen(point2_3d);38423843Vector<Point2> triangle_points;3844triangle_points.push_back(center_2d);3845triangle_points.push_back(point1_2d);3846triangle_points.push_back(point2_2d);38473848Vector<Color> triangle_colors;3849triangle_colors.push_back(fill_color);3850triangle_colors.push_back(fill_color);3851triangle_colors.push_back(fill_color);38523853RenderingServer::get_singleton()->canvas_item_add_polygon(ci, triangle_points, triangle_colors);3854}38553856Color edge_color = (_edit.plane == TRANSFORM_VIEW) ? Color(1.0, 1.0, 1.0, 0.7) : handle_color.from_hsv(handle_color.get_h(), 0.8, 1.0, 0.7);38573858Vector3 start_point_3d = _edit.center + gizmo_scale * rotation_radius * right;3859Point2 start_point_2d = point_to_screen(start_point_3d);3860RenderingServer::get_singleton()->canvas_item_add_line(3861ci,3862center,3863start_point_2d,3864edge_color,3865Math::round(2 * EDSCALE),3866true);38673868Vector3 end_point_3d = _edit.center + gizmo_scale * rotation_radius * (right * Math::cos(_edit.accumulated_rotation_angle) + forward * Math::sin(_edit.accumulated_rotation_angle));3869Point2 end_point_2d = point_to_screen(end_point_3d);3870RenderingServer::get_singleton()->canvas_item_add_line(3871ci,3872center,3873end_point_2d,3874edge_color,3875Math::round(2 * EDSCALE),3876true);3877}38783879if (_edit.show_rotation_line) {3880handle_color = handle_color.from_hsv(handle_color.get_h(), 0.25, 1.0, 1);3881RenderingServer::get_singleton()->canvas_item_add_line(3882ci,3883_edit.mouse_pos,3884center,3885handle_color,3886Math::round(2 * EDSCALE));3887}3888}3889if (previewing) {3890Size2 ss = Size2(GLOBAL_GET("display/window/size/viewport_width"), GLOBAL_GET("display/window/size/viewport_height"));3891float aspect = ss.aspect();3892Size2 s = get_size();38933894Rect2 draw_rect;38953896switch (previewing->get_keep_aspect_mode()) {3897case Camera3D::KEEP_WIDTH: {3898draw_rect.size = Size2(s.width, s.width / aspect);3899draw_rect.position.x = 0;3900draw_rect.position.y = (s.height - draw_rect.size.y) * 0.5;39013902} break;3903case Camera3D::KEEP_HEIGHT: {3904draw_rect.size = Size2(s.height * aspect, s.height);3905draw_rect.position.y = 0;3906draw_rect.position.x = (s.width - draw_rect.size.x) * 0.5;39073908} break;3909}39103911draw_rect = Rect2(Vector2(), s).intersection(draw_rect);39123913surface->draw_rect(draw_rect, Color(0.6, 0.6, 0.1, 0.5), false, Math::round(2 * EDSCALE));39143915} else {3916if (zoom_indicator_delay > 0.0) {3917if (is_freelook_active()) {3918// Show speed39193920real_t min_speed = MAX(camera->get_near() * 4, ZOOM_FREELOOK_MIN);3921real_t max_speed = MIN(camera->get_far() / 4, ZOOM_FREELOOK_MAX);3922real_t scale_length = (max_speed - min_speed);39233924if (!Math::is_zero_approx(scale_length)) {3925real_t logscale_t = 1.0 - Math::log1p(freelook_speed - min_speed) / Math::log1p(scale_length);39263927// Display the freelook speed to help the user get a better sense of scale.3928const int precision = freelook_speed < 1.0 ? 2 : 1;3929draw_indicator_bar(3930*surface,39311.0 - logscale_t,3932get_editor_theme_icon(SNAME("ViewportSpeed")),3933get_theme_font("bold", EditorStringName(EditorFonts)),3934get_theme_font_size(SceneStringName(font_size), SNAME("Label")),3935vformat("%s m/s", String::num(freelook_speed).pad_decimals(precision)),3936Color(1.0, 0.95, 0.7));3937}39383939} else {3940// Show zoom3941zoom_limit_label->set_visible(zoom_failed_attempts_count > 15);39423943real_t min_distance = MAX(camera->get_near() * 4, ZOOM_FREELOOK_MIN);3944real_t max_distance = MIN(camera->get_far() / 4, ZOOM_FREELOOK_MAX);3945real_t scale_length = (max_distance - min_distance);39463947if (!Math::is_zero_approx(scale_length)) {3948real_t logscale_t = 1.0 - Math::log1p(cursor.distance - min_distance) / Math::log1p(scale_length);39493950// Display the zoom center distance to help the user get a better sense of scale.3951const int precision = cursor.distance < 1.0 ? 2 : 1;3952draw_indicator_bar(3953*surface,3954logscale_t,3955get_editor_theme_icon(SNAME("ViewportZoom")),3956get_theme_font("bold", EditorStringName(EditorFonts)),3957get_theme_font_size(SceneStringName(font_size), SNAME("Label")),3958vformat("%s m", String::num(cursor.distance).pad_decimals(precision)),3959Color(0.7, 0.95, 1.0));3960}3961}3962}3963}3964}39653966bool Node3DEditorViewport::_camera_moved_externally() {3967Transform3D t = camera->get_global_transform();3968return !t.is_equal_approx(last_camera_transform);3969}39703971void Node3DEditorViewport::_apply_camera_transform_to_cursor() {3972// Effectively the reverse of to_camera_transform, use camera transform to set cursor position and rotation.3973const Transform3D camera_transform = camera->get_camera_transform();3974const Basis basis = camera_transform.basis;39753976real_t distance;3977if (orthogonal) {3978distance = (get_zfar() - get_znear()) / 2.0;3979} else {3980distance = cursor.distance;3981}39823983cursor.pos = camera_transform.origin - basis.get_column(2) * distance;39843985cursor.x_rot = -camera_transform.basis.get_euler().x;3986cursor.y_rot = -camera_transform.basis.get_euler().y;3987cursor.unsnapped_x_rot = cursor.x_rot;3988cursor.unsnapped_y_rot = cursor.y_rot;3989}39903991void Node3DEditorViewport::_menu_option(int p_option) {3992EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();3993switch (p_option) {3994case VIEW_TOP: {3995cursor.y_rot = 0;3996cursor.x_rot = Math::PI / 2.0;3997cursor.unsnapped_y_rot = cursor.y_rot;3998cursor.unsnapped_x_rot = cursor.x_rot;3999set_message(TTR("Top View."), 2);4000view_type = VIEW_TYPE_TOP;4001_set_auto_orthogonal();4002_update_name();40034004} break;4005case VIEW_BOTTOM: {4006cursor.y_rot = 0;4007cursor.x_rot = -Math::PI / 2.0;4008cursor.unsnapped_y_rot = cursor.y_rot;4009cursor.unsnapped_x_rot = cursor.x_rot;4010set_message(TTR("Bottom View."), 2);4011view_type = VIEW_TYPE_BOTTOM;4012_set_auto_orthogonal();4013_update_name();40144015} break;4016case VIEW_LEFT: {4017cursor.x_rot = 0;4018cursor.y_rot = Math::PI / 2.0;4019cursor.unsnapped_x_rot = cursor.x_rot;4020cursor.unsnapped_y_rot = cursor.y_rot;4021set_message(TTR("Left View."), 2);4022view_type = VIEW_TYPE_LEFT;4023_set_auto_orthogonal();4024_update_name();40254026} break;4027case VIEW_RIGHT: {4028cursor.x_rot = 0;4029cursor.y_rot = -Math::PI / 2.0;4030cursor.unsnapped_x_rot = cursor.x_rot;4031cursor.unsnapped_y_rot = cursor.y_rot;4032set_message(TTR("Right View."), 2);4033view_type = VIEW_TYPE_RIGHT;4034_set_auto_orthogonal();4035_update_name();40364037} break;4038case VIEW_FRONT: {4039cursor.x_rot = 0;4040cursor.y_rot = 0;4041cursor.unsnapped_x_rot = cursor.x_rot;4042cursor.unsnapped_y_rot = cursor.y_rot;4043set_message(TTR("Front View."), 2);4044view_type = VIEW_TYPE_FRONT;4045_set_auto_orthogonal();4046_update_name();40474048} break;4049case VIEW_REAR: {4050cursor.x_rot = 0;4051cursor.y_rot = Math::PI;4052cursor.unsnapped_x_rot = cursor.x_rot;4053cursor.unsnapped_y_rot = cursor.y_rot;4054set_message(TTR("Rear View."), 2);4055view_type = VIEW_TYPE_REAR;4056_set_auto_orthogonal();4057_update_name();40584059} break;4060case VIEW_CENTER_TO_ORIGIN: {4061cursor.pos = Vector3(0, 0, 0);40624063} break;4064case VIEW_CENTER_TO_SELECTION: {4065focus_selection();40664067} break;4068case VIEW_ALIGN_TRANSFORM_WITH_VIEW: {4069if (!get_selected_count()) {4070break;4071}40724073Transform3D camera_transform = camera->get_global_transform();40744075const List<Node *> &selection = editor_selection->get_top_selected_node_list();40764077undo_redo->create_action(TTR("Align Transform with View"));4078for (Node *E : selection) {4079Node3D *sp = Object::cast_to<Node3D>(E);4080if (!sp) {4081continue;4082}40834084Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);4085if (!se) {4086continue;4087}40884089Transform3D xform = camera_transform;4090if (orthogonal) {4091Vector3 offset = camera_transform.basis.xform(Vector3(0, 0, cursor.distance));4092xform.origin = cursor.pos + offset;4093} else {4094xform.scale_basis(sp->get_scale());4095}40964097if (Object::cast_to<Decal>(E)) {4098// Adjust rotation to match Decal's default orientation.4099// This makes the decal "look" in the same direction as the camera,4100// rather than pointing down relative to the camera orientation.4101xform.basis.rotate_local(Vector3(1, 0, 0), Math::TAU * 0.25);4102}41034104Node3D *parent = sp->get_parent_node_3d();4105Transform3D local_xform = parent ? parent->get_global_transform().affine_inverse() * xform : xform;4106undo_redo->add_do_method(sp, "set_transform", local_xform);4107undo_redo->add_undo_method(sp, "set_transform", sp->get_local_gizmo_transform());4108}4109undo_redo->commit_action();41104111} break;4112case VIEW_ALIGN_ROTATION_WITH_VIEW: {4113if (!get_selected_count()) {4114break;4115}41164117Transform3D camera_transform = camera->get_global_transform();41184119const List<Node *> &selection = editor_selection->get_top_selected_node_list();41204121undo_redo->create_action(TTR("Align Rotation with View"));4122for (Node *E : selection) {4123Node3D *sp = Object::cast_to<Node3D>(E);4124if (!sp) {4125continue;4126}41274128Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);4129if (!se) {4130continue;4131}41324133Basis basis = camera_transform.basis;41344135if (Object::cast_to<Decal>(E)) {4136// Adjust rotation to match Decal's default orientation.4137// This makes the decal "look" in the same direction as the camera,4138// rather than pointing down relative to the camera orientation.4139basis.rotate_local(Vector3(1, 0, 0), Math::TAU * 0.25);4140}41414142undo_redo->add_do_method(sp, "set_rotation", basis.get_euler_normalized());4143undo_redo->add_undo_method(sp, "set_rotation", sp->get_rotation());4144}4145undo_redo->commit_action();41464147} break;4148case VIEW_ENVIRONMENT: {4149int idx = view_display_menu->get_popup()->get_item_index(VIEW_ENVIRONMENT);4150bool current = view_display_menu->get_popup()->is_item_checked(idx);4151current = !current;4152if (current) {4153camera->set_environment(Ref<Resource>());4154} else {4155camera->set_environment(Node3DEditor::get_singleton()->get_viewport_environment());4156}41574158view_display_menu->get_popup()->set_item_checked(idx, current);41594160} break;4161case VIEW_PERSPECTIVE: {4162view_display_menu->get_popup()->set_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_PERSPECTIVE), true);4163view_display_menu->get_popup()->set_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_ORTHOGONAL), false);4164orthogonal = false;4165auto_orthogonal = false;4166callable_mp(this, &Node3DEditorViewport::update_transform_gizmo_view).call_deferred();4167_update_camera(0);4168_update_name();41694170} break;4171case VIEW_ORTHOGONAL: {4172view_display_menu->get_popup()->set_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_PERSPECTIVE), false);4173view_display_menu->get_popup()->set_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_ORTHOGONAL), true);4174orthogonal = true;4175auto_orthogonal = false;4176callable_mp(this, &Node3DEditorViewport::update_transform_gizmo_view).call_deferred();4177_update_camera(0);4178_update_name();4179} break;4180case VIEW_SWITCH_PERSPECTIVE_ORTHOGONAL: {4181_menu_option(orthogonal ? VIEW_PERSPECTIVE : VIEW_ORTHOGONAL);41824183} break;4184case VIEW_AUTO_ORTHOGONAL: {4185int idx = view_display_menu->get_popup()->get_item_index(VIEW_AUTO_ORTHOGONAL);4186bool current = view_display_menu->get_popup()->is_item_checked(idx);4187current = !current;4188view_display_menu->get_popup()->set_item_checked(idx, current);4189if (auto_orthogonal) {4190auto_orthogonal = false;4191_update_name();4192}4193} break;4194case VIEW_LOCK_ROTATION: {4195int idx = view_display_menu->get_popup()->get_item_index(VIEW_LOCK_ROTATION);4196bool current = view_display_menu->get_popup()->is_item_checked(idx);4197_set_lock_view_rotation(!current);41984199} break;4200case VIEW_AUDIO_LISTENER: {4201int idx = view_display_menu->get_popup()->get_item_index(VIEW_AUDIO_LISTENER);4202bool current = view_display_menu->get_popup()->is_item_checked(idx);4203current = !current;4204viewport->set_as_audio_listener_3d(current);4205view_display_menu->get_popup()->set_item_checked(idx, current);42064207} break;4208case VIEW_AUDIO_DOPPLER: {4209int idx = view_display_menu->get_popup()->get_item_index(VIEW_AUDIO_DOPPLER);4210bool current = view_display_menu->get_popup()->is_item_checked(idx);4211current = !current;4212camera->set_doppler_tracking(current ? Camera3D::DOPPLER_TRACKING_IDLE_STEP : Camera3D::DOPPLER_TRACKING_DISABLED);4213view_display_menu->get_popup()->set_item_checked(idx, current);42144215} break;4216case VIEW_CINEMATIC_PREVIEW: {4217int idx = view_display_menu->get_popup()->get_item_index(VIEW_CINEMATIC_PREVIEW);4218bool current = view_display_menu->get_popup()->is_item_checked(idx);4219current = !current;4220view_display_menu->get_popup()->set_item_checked(idx, current);4221_toggle_cinema_preview(current);42224223cinema_label->set_visible(current);4224_update_centered_labels();4225surface->queue_redraw();42264227if (current) {4228preview_camera->hide();4229} else {4230if (previewing != nullptr) {4231preview_camera->show();4232}4233}4234} break;4235case VIEW_GIZMOS: {4236int idx = view_display_menu->get_popup()->get_item_index(VIEW_GIZMOS);4237bool current = view_display_menu->get_popup()->is_item_checked(idx);4238current = !current;4239uint32_t layers = camera->get_cull_mask();4240layers &= ~(1 << GIZMO_EDIT_LAYER);4241if (current) {4242layers |= (1 << GIZMO_EDIT_LAYER);4243}4244camera->set_cull_mask(layers);4245view_display_menu->get_popup()->set_item_checked(idx, current);42464247} break;4248case VIEW_TRANSFORM_GIZMO: {4249int idx = view_display_menu->get_popup()->get_item_index(VIEW_TRANSFORM_GIZMO);4250bool current = view_display_menu->get_popup()->is_item_checked(idx);4251current = !current;4252transform_gizmo_visible = current;42534254spatial_editor->update_transform_gizmo();4255view_display_menu->get_popup()->set_item_checked(idx, current);4256} break;4257case VIEW_HALF_RESOLUTION: {4258int idx = view_display_menu->get_popup()->get_item_index(VIEW_HALF_RESOLUTION);4259bool current = view_display_menu->get_popup()->is_item_checked(idx);4260view_display_menu->get_popup()->set_item_checked(idx, !current);4261_update_shrink();4262} break;4263case VIEW_INFORMATION: {4264int idx = view_display_menu->get_popup()->get_item_index(VIEW_INFORMATION);4265bool current = view_display_menu->get_popup()->is_item_checked(idx);4266view_display_menu->get_popup()->set_item_checked(idx, !current);42674268} break;4269case VIEW_FRAME_TIME: {4270int idx = view_display_menu->get_popup()->get_item_index(VIEW_FRAME_TIME);4271bool current = view_display_menu->get_popup()->is_item_checked(idx);4272view_display_menu->get_popup()->set_item_checked(idx, !current);4273} break;4274case VIEW_GRID: {4275int idx = view_display_menu->get_popup()->get_item_index(VIEW_GRID);4276bool current = view_display_menu->get_popup()->is_item_checked(idx);4277current = !current;4278uint32_t layers = camera->get_cull_mask();4279layers &= ~(1 << GIZMO_GRID_LAYER);4280if (current) {4281layers |= (1 << GIZMO_GRID_LAYER);4282}4283camera->set_cull_mask(layers);4284view_display_menu->get_popup()->set_item_checked(idx, current);4285} break;4286case VIEW_DISPLAY_NORMAL:4287case VIEW_DISPLAY_WIREFRAME:4288case VIEW_DISPLAY_OVERDRAW:4289case VIEW_DISPLAY_UNSHADED:4290case VIEW_DISPLAY_LIGHTING:4291case VIEW_DISPLAY_NORMAL_BUFFER:4292case VIEW_DISPLAY_DEBUG_SHADOW_ATLAS:4293case VIEW_DISPLAY_DEBUG_DIRECTIONAL_SHADOW_ATLAS:4294case VIEW_DISPLAY_DEBUG_VOXEL_GI_ALBEDO:4295case VIEW_DISPLAY_DEBUG_VOXEL_GI_LIGHTING:4296case VIEW_DISPLAY_DEBUG_VOXEL_GI_EMISSION:4297case VIEW_DISPLAY_DEBUG_SCENE_LUMINANCE:4298case VIEW_DISPLAY_DEBUG_SSAO:4299case VIEW_DISPLAY_DEBUG_SSIL:4300case VIEW_DISPLAY_DEBUG_PSSM_SPLITS:4301case VIEW_DISPLAY_DEBUG_DECAL_ATLAS:4302case VIEW_DISPLAY_DEBUG_SDFGI:4303case VIEW_DISPLAY_DEBUG_SDFGI_PROBES:4304case VIEW_DISPLAY_DEBUG_GI_BUFFER:4305case VIEW_DISPLAY_DEBUG_DISABLE_LOD:4306case VIEW_DISPLAY_DEBUG_CLUSTER_OMNI_LIGHTS:4307case VIEW_DISPLAY_DEBUG_CLUSTER_SPOT_LIGHTS:4308case VIEW_DISPLAY_DEBUG_CLUSTER_DECALS:4309case VIEW_DISPLAY_DEBUG_CLUSTER_REFLECTION_PROBES:4310case VIEW_DISPLAY_DEBUG_OCCLUDERS:4311case VIEW_DISPLAY_MOTION_VECTORS:4312case VIEW_DISPLAY_INTERNAL_BUFFER: {4313static const int display_options[] = {4314VIEW_DISPLAY_NORMAL,4315VIEW_DISPLAY_WIREFRAME,4316VIEW_DISPLAY_OVERDRAW,4317VIEW_DISPLAY_UNSHADED,4318VIEW_DISPLAY_LIGHTING,4319VIEW_DISPLAY_NORMAL_BUFFER,4320VIEW_DISPLAY_DEBUG_SHADOW_ATLAS,4321VIEW_DISPLAY_DEBUG_DIRECTIONAL_SHADOW_ATLAS,4322VIEW_DISPLAY_DEBUG_VOXEL_GI_ALBEDO,4323VIEW_DISPLAY_DEBUG_VOXEL_GI_LIGHTING,4324VIEW_DISPLAY_DEBUG_VOXEL_GI_EMISSION,4325VIEW_DISPLAY_DEBUG_SCENE_LUMINANCE,4326VIEW_DISPLAY_DEBUG_SSAO,4327VIEW_DISPLAY_DEBUG_SSIL,4328VIEW_DISPLAY_DEBUG_GI_BUFFER,4329VIEW_DISPLAY_DEBUG_DISABLE_LOD,4330VIEW_DISPLAY_DEBUG_PSSM_SPLITS,4331VIEW_DISPLAY_DEBUG_DECAL_ATLAS,4332VIEW_DISPLAY_DEBUG_SDFGI,4333VIEW_DISPLAY_DEBUG_SDFGI_PROBES,4334VIEW_DISPLAY_DEBUG_CLUSTER_OMNI_LIGHTS,4335VIEW_DISPLAY_DEBUG_CLUSTER_SPOT_LIGHTS,4336VIEW_DISPLAY_DEBUG_CLUSTER_DECALS,4337VIEW_DISPLAY_DEBUG_CLUSTER_REFLECTION_PROBES,4338VIEW_DISPLAY_DEBUG_OCCLUDERS,4339VIEW_DISPLAY_MOTION_VECTORS,4340VIEW_DISPLAY_INTERNAL_BUFFER,4341VIEW_MAX4342};4343static const Viewport::DebugDraw debug_draw_modes[] = {4344Viewport::DEBUG_DRAW_DISABLED,4345Viewport::DEBUG_DRAW_WIREFRAME,4346Viewport::DEBUG_DRAW_OVERDRAW,4347Viewport::DEBUG_DRAW_UNSHADED,4348Viewport::DEBUG_DRAW_LIGHTING,4349Viewport::DEBUG_DRAW_NORMAL_BUFFER,4350Viewport::DEBUG_DRAW_SHADOW_ATLAS,4351Viewport::DEBUG_DRAW_DIRECTIONAL_SHADOW_ATLAS,4352Viewport::DEBUG_DRAW_VOXEL_GI_ALBEDO,4353Viewport::DEBUG_DRAW_VOXEL_GI_LIGHTING,4354Viewport::DEBUG_DRAW_VOXEL_GI_EMISSION,4355Viewport::DEBUG_DRAW_SCENE_LUMINANCE,4356Viewport::DEBUG_DRAW_SSAO,4357Viewport::DEBUG_DRAW_SSIL,4358Viewport::DEBUG_DRAW_GI_BUFFER,4359Viewport::DEBUG_DRAW_DISABLE_LOD,4360Viewport::DEBUG_DRAW_PSSM_SPLITS,4361Viewport::DEBUG_DRAW_DECAL_ATLAS,4362Viewport::DEBUG_DRAW_SDFGI,4363Viewport::DEBUG_DRAW_SDFGI_PROBES,4364Viewport::DEBUG_DRAW_CLUSTER_OMNI_LIGHTS,4365Viewport::DEBUG_DRAW_CLUSTER_SPOT_LIGHTS,4366Viewport::DEBUG_DRAW_CLUSTER_DECALS,4367Viewport::DEBUG_DRAW_CLUSTER_REFLECTION_PROBES,4368Viewport::DEBUG_DRAW_OCCLUDERS,4369Viewport::DEBUG_DRAW_MOTION_VECTORS,4370Viewport::DEBUG_DRAW_INTERNAL_BUFFER,4371};43724373for (int idx = 0; display_options[idx] != VIEW_MAX; idx++) {4374int id = display_options[idx];4375int item_idx = view_display_menu->get_popup()->get_item_index(id);4376if (item_idx != -1) {4377view_display_menu->get_popup()->set_item_checked(item_idx, id == p_option);4378}4379item_idx = display_submenu->get_item_index(id);4380if (item_idx != -1) {4381display_submenu->set_item_checked(item_idx, id == p_option);4382}43834384if (id == p_option) {4385viewport->set_debug_draw(debug_draw_modes[idx]);4386}4387}4388} break;4389}4390}43914392void Node3DEditorViewport::_set_auto_orthogonal() {4393if (!orthogonal && view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_AUTO_ORTHOGONAL))) {4394_menu_option(VIEW_ORTHOGONAL);4395auto_orthogonal = true;4396}4397}43984399void Node3DEditorViewport::_preview_exited_scene() {4400preview_camera->disconnect(SceneStringName(toggled), callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview));4401preview_camera->set_pressed(false);4402_toggle_camera_preview(false);4403preview_camera->connect(SceneStringName(toggled), callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview));4404view_display_menu->show();4405}44064407void Node3DEditorViewport::_preview_camera_property_changed() {4408if (previewing) {4409surface->queue_redraw();4410}4411}44124413void Node3DEditorViewport::_update_centered_labels() {4414if (cinema_label->is_visible()) {4415cinema_label->reset_size();4416float cinema_half_width = cinema_label->get_size().width / 2.0f;4417cinema_label->set_anchor_and_offset(SIDE_LEFT, 0.5f, -cinema_half_width);4418}44194420if (locked_label->is_visible()) {4421locked_label->reset_size();4422float locked_half_width = locked_label->get_size().width / 2.0f;4423locked_label->set_anchor_and_offset(SIDE_LEFT, 0.5f, -locked_half_width);4424}4425}44264427void Node3DEditorViewport::_init_gizmo_instance(int p_idx) {4428uint32_t layer = 1 << (GIZMO_BASE_LAYER + p_idx);44294430for (int i = 0; i < 3; i++) {4431move_gizmo_instance[i] = RS::get_singleton()->instance_create();4432RS::get_singleton()->instance_set_base(move_gizmo_instance[i], spatial_editor->get_move_gizmo(i)->get_rid());4433RS::get_singleton()->instance_set_scenario(move_gizmo_instance[i], get_tree()->get_root()->get_world_3d()->get_scenario());4434RS::get_singleton()->instance_set_visible(move_gizmo_instance[i], false);4435RS::get_singleton()->instance_geometry_set_cast_shadows_setting(move_gizmo_instance[i], RS::SHADOW_CASTING_SETTING_OFF);4436RS::get_singleton()->instance_set_layer_mask(move_gizmo_instance[i], layer);4437RS::get_singleton()->instance_geometry_set_flag(move_gizmo_instance[i], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);4438RS::get_singleton()->instance_geometry_set_flag(move_gizmo_instance[i], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);44394440move_plane_gizmo_instance[i] = RS::get_singleton()->instance_create();4441RS::get_singleton()->instance_set_base(move_plane_gizmo_instance[i], spatial_editor->get_move_plane_gizmo(i)->get_rid());4442RS::get_singleton()->instance_set_scenario(move_plane_gizmo_instance[i], get_tree()->get_root()->get_world_3d()->get_scenario());4443RS::get_singleton()->instance_set_visible(move_plane_gizmo_instance[i], false);4444RS::get_singleton()->instance_geometry_set_cast_shadows_setting(move_plane_gizmo_instance[i], RS::SHADOW_CASTING_SETTING_OFF);4445RS::get_singleton()->instance_set_layer_mask(move_plane_gizmo_instance[i], layer);4446RS::get_singleton()->instance_geometry_set_flag(move_plane_gizmo_instance[i], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);4447RS::get_singleton()->instance_geometry_set_flag(move_plane_gizmo_instance[i], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);44484449scale_gizmo_instance[i] = RS::get_singleton()->instance_create();4450RS::get_singleton()->instance_set_base(scale_gizmo_instance[i], spatial_editor->get_scale_gizmo(i)->get_rid());4451RS::get_singleton()->instance_set_scenario(scale_gizmo_instance[i], get_tree()->get_root()->get_world_3d()->get_scenario());4452RS::get_singleton()->instance_set_visible(scale_gizmo_instance[i], false);4453RS::get_singleton()->instance_geometry_set_cast_shadows_setting(scale_gizmo_instance[i], RS::SHADOW_CASTING_SETTING_OFF);4454RS::get_singleton()->instance_set_layer_mask(scale_gizmo_instance[i], layer);4455RS::get_singleton()->instance_geometry_set_flag(scale_gizmo_instance[i], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);4456RS::get_singleton()->instance_geometry_set_flag(scale_gizmo_instance[i], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);44574458scale_plane_gizmo_instance[i] = RS::get_singleton()->instance_create();4459RS::get_singleton()->instance_set_base(scale_plane_gizmo_instance[i], spatial_editor->get_scale_plane_gizmo(i)->get_rid());4460RS::get_singleton()->instance_set_scenario(scale_plane_gizmo_instance[i], get_tree()->get_root()->get_world_3d()->get_scenario());4461RS::get_singleton()->instance_set_visible(scale_plane_gizmo_instance[i], false);4462RS::get_singleton()->instance_geometry_set_cast_shadows_setting(scale_plane_gizmo_instance[i], RS::SHADOW_CASTING_SETTING_OFF);4463RS::get_singleton()->instance_set_layer_mask(scale_plane_gizmo_instance[i], layer);4464RS::get_singleton()->instance_geometry_set_flag(scale_plane_gizmo_instance[i], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);4465RS::get_singleton()->instance_geometry_set_flag(scale_plane_gizmo_instance[i], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);44664467axis_gizmo_instance[i] = RS::get_singleton()->instance_create();4468}44694470for (int i = 0; i < 3; i++) {4471RS::get_singleton()->instance_set_base(axis_gizmo_instance[i], spatial_editor->get_axis_gizmo(i)->get_rid());4472RS::get_singleton()->instance_set_scenario(axis_gizmo_instance[i], get_tree()->get_root()->get_world_3d()->get_scenario());4473RS::get_singleton()->instance_set_visible(axis_gizmo_instance[i], true);4474RS::get_singleton()->instance_geometry_set_cast_shadows_setting(axis_gizmo_instance[i], RS::SHADOW_CASTING_SETTING_OFF);4475RS::get_singleton()->instance_set_layer_mask(axis_gizmo_instance[i], layer);4476RS::get_singleton()->instance_geometry_set_flag(axis_gizmo_instance[i], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);4477RS::get_singleton()->instance_geometry_set_flag(axis_gizmo_instance[i], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);4478}44794480for (int i = 0; i < 4; i++) {4481rotate_gizmo_instance[i] = RS::get_singleton()->instance_create();4482RS::get_singleton()->instance_set_base(rotate_gizmo_instance[i], spatial_editor->get_rotate_gizmo(i)->get_rid());4483RS::get_singleton()->instance_set_scenario(rotate_gizmo_instance[i], get_tree()->get_root()->get_world_3d()->get_scenario());4484RS::get_singleton()->instance_set_visible(rotate_gizmo_instance[i], false);4485RS::get_singleton()->instance_geometry_set_cast_shadows_setting(rotate_gizmo_instance[i], RS::SHADOW_CASTING_SETTING_OFF);4486RS::get_singleton()->instance_set_layer_mask(rotate_gizmo_instance[i], layer);4487RS::get_singleton()->instance_geometry_set_flag(rotate_gizmo_instance[i], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);4488RS::get_singleton()->instance_geometry_set_flag(rotate_gizmo_instance[i], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);4489}44904491// Create trackball sphere instance4492trackball_sphere_instance = RS::get_singleton()->instance_create();4493RS::get_singleton()->instance_set_base(trackball_sphere_instance, spatial_editor->get_trackball_sphere_gizmo()->get_rid());4494RS::get_singleton()->instance_set_scenario(trackball_sphere_instance, get_tree()->get_root()->get_world_3d()->get_scenario());4495RS::get_singleton()->instance_set_visible(trackball_sphere_instance, false);4496RS::get_singleton()->instance_geometry_set_cast_shadows_setting(trackball_sphere_instance, RS::SHADOW_CASTING_SETTING_OFF);4497RS::get_singleton()->instance_set_layer_mask(trackball_sphere_instance, layer);4498RS::get_singleton()->instance_geometry_set_flag(trackball_sphere_instance, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);4499RS::get_singleton()->instance_geometry_set_flag(trackball_sphere_instance, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);4500}45014502void Node3DEditorViewport::_finish_gizmo_instances() {4503ERR_FAIL_NULL(RenderingServer::get_singleton());4504for (int i = 0; i < 3; i++) {4505RS::get_singleton()->free_rid(move_gizmo_instance[i]);4506RS::get_singleton()->free_rid(move_plane_gizmo_instance[i]);4507RS::get_singleton()->free_rid(rotate_gizmo_instance[i]);4508RS::get_singleton()->free_rid(scale_gizmo_instance[i]);4509RS::get_singleton()->free_rid(scale_plane_gizmo_instance[i]);4510RS::get_singleton()->free_rid(axis_gizmo_instance[i]);4511}4512// Rotation white outline4513RS::get_singleton()->free_rid(rotate_gizmo_instance[3]);45144515RS::get_singleton()->free_rid(trackball_sphere_instance);4516}45174518void Node3DEditorViewport::_toggle_camera_preview(bool p_activate) {4519ERR_FAIL_COND(p_activate && !preview);4520ERR_FAIL_COND(!p_activate && !previewing);45214522previewing_camera = p_activate;4523_update_navigation_controls_visibility();45244525if (!p_activate) {4526previewing->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Node3DEditorViewport::_preview_exited_scene));4527previewing->disconnect(CoreStringName(property_list_changed), callable_mp(this, &Node3DEditorViewport::_preview_camera_property_changed));4528previewing = nullptr;4529RS::get_singleton()->viewport_attach_camera(viewport->get_viewport_rid(), camera->get_camera()); //restore4530if (!preview) {4531preview_camera->hide();4532}4533surface->queue_redraw();45344535} else {4536previewing = preview;4537previewing->connect(SceneStringName(tree_exiting), callable_mp(this, &Node3DEditorViewport::_preview_exited_scene));4538previewing->connect(CoreStringName(property_list_changed), callable_mp(this, &Node3DEditorViewport::_preview_camera_property_changed));4539RS::get_singleton()->viewport_attach_camera(viewport->get_viewport_rid(), preview->get_camera()); //replace4540surface->queue_redraw();4541}4542}45434544void Node3DEditorViewport::_toggle_cinema_preview(bool p_activate) {4545previewing_cinema = p_activate;4546_update_navigation_controls_visibility();45474548if (!previewing_cinema) {4549if (previewing != nullptr) {4550previewing->disconnect(SceneStringName(tree_exited), callable_mp(this, &Node3DEditorViewport::_preview_exited_scene));4551previewing->disconnect(CoreStringName(property_list_changed), callable_mp(this, &Node3DEditorViewport::_preview_camera_property_changed));4552}45534554previewing = nullptr;4555RS::get_singleton()->viewport_attach_camera(viewport->get_viewport_rid(), camera->get_camera()); //restore4556preview_camera->set_pressed(false);4557if (!preview) {4558preview_camera->hide();4559} else {4560preview_camera->show();4561}4562view_display_menu->show();4563surface->queue_redraw();4564}4565}45664567void Node3DEditorViewport::_selection_result_pressed(int p_result) {4568if (selection_results_menu.size() <= p_result) {4569return;4570}45714572clicked = selection_results_menu[p_result]->get_instance_id();45734574if (clicked.is_valid()) {4575_select_clicked(true);4576}45774578selection_results_menu.clear();4579}45804581void Node3DEditorViewport::_selection_menu_hide() {4582selection_results.clear();4583selection_menu->clear();4584selection_menu->reset_size();4585}45864587void Node3DEditorViewport::set_can_preview(Camera3D *p_preview) {4588preview = p_preview;45894590if (!preview_camera->is_pressed() && !previewing_cinema) {4591preview_camera->set_visible(p_preview);4592}4593}45944595void Node3DEditorViewport::update_transform_gizmo_view() {4596if (!is_visible_in_tree()) {4597return;4598}45994600Transform3D xform = spatial_editor->get_gizmo_transform();46014602Transform3D camera_xform = camera->get_transform();46034604if (xform.origin.is_equal_approx(camera_xform.origin)) {4605for (int i = 0; i < 3; i++) {4606RenderingServer::get_singleton()->instance_set_visible(move_gizmo_instance[i], false);4607RenderingServer::get_singleton()->instance_set_visible(move_plane_gizmo_instance[i], false);4608RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[i], false);4609RenderingServer::get_singleton()->instance_set_visible(scale_gizmo_instance[i], false);4610RenderingServer::get_singleton()->instance_set_visible(scale_plane_gizmo_instance[i], false);4611RenderingServer::get_singleton()->instance_set_visible(axis_gizmo_instance[i], false);4612}4613RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[3], false);4614return;4615}46164617const Vector3 camz = -camera_xform.get_basis().get_column(2).normalized();4618const Vector3 camy = -camera_xform.get_basis().get_column(1).normalized();4619const Plane p = Plane(camz, camera_xform.origin);4620const real_t gizmo_d = MAX(Math::abs(p.distance_to(xform.origin)), CMP_EPSILON);4621const real_t d0 = camera->unproject_position(camera_xform.origin + camz * gizmo_d).y;4622const real_t d1 = camera->unproject_position(camera_xform.origin + camz * gizmo_d + camy).y;4623const real_t dd = MAX(Math::abs(d0 - d1), CMP_EPSILON);46244625const real_t gizmo_size = EDITOR_GET("editors/3d/manipulator_gizmo_size");4626// At low viewport heights, multiply the gizmo scale based on the viewport height.4627// This prevents the gizmo from growing very large and going outside the viewport.4628const int viewport_base_height = 400 * MAX(1, EDSCALE);4629gizmo_scale =4630(gizmo_size / Math::abs(dd)) * MAX(1, EDSCALE) *4631MIN(viewport_base_height, subviewport_container->get_size().height) / viewport_base_height;4632Vector3 scale = Vector3(1, 1, 1) * gizmo_scale;46334634// if the determinant is zero, we should disable the gizmo from being rendered4635// this prevents supplying bad values to the renderer and then having to filter it out again4636if (xform.basis.determinant() == 0) {4637for (int i = 0; i < 3; i++) {4638RenderingServer::get_singleton()->instance_set_visible(move_gizmo_instance[i], false);4639RenderingServer::get_singleton()->instance_set_visible(move_plane_gizmo_instance[i], false);4640RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[i], false);4641RenderingServer::get_singleton()->instance_set_visible(scale_gizmo_instance[i], false);4642RenderingServer::get_singleton()->instance_set_visible(scale_plane_gizmo_instance[i], false);4643}4644RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[3], false);4645return;4646}46474648bool local_coords = spatial_editor->are_local_coords_enabled();4649bool arc_visible = _is_rotation_arc_visible();4650int show_gizmo_flags = EDITOR_GET("editors/3d/show_gizmo_during_rotation");46514652bool keep_gizmo_visible = arc_visible && ((local_coords && (show_gizmo_flags & Node3DEditor::TRANSFORM_MODE_LOCAL)) || (!local_coords && (show_gizmo_flags & Node3DEditor::TRANSFORM_MODE_GLOBAL)));4653bool hide_gizmo_during_rotation = arc_visible && !keep_gizmo_visible;4654bool hide_gizmo_during_trackball = (_edit.mode == TRANSFORM_ROTATE && _edit.is_trackball);46554656int arc_replaces_ring = -1;4657if (keep_gizmo_visible) {4658switch (_edit.plane) {4659case TRANSFORM_X_AXIS:4660arc_replaces_ring = 0;4661break;4662case TRANSFORM_Y_AXIS:4663arc_replaces_ring = 1;4664break;4665case TRANSFORM_Z_AXIS:4666arc_replaces_ring = 2;4667break;4668case TRANSFORM_VIEW:4669arc_replaces_ring = 3;4670break;4671default:4672break;4673}4674}46754676bool show_gizmo = spatial_editor->is_gizmo_visible() && !_edit.instant && transform_gizmo_visible && !collision_reposition && !hide_gizmo_during_rotation && !hide_gizmo_during_trackball;4677bool show_rotate_gizmo = show_gizmo && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_TRANSFORM || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE);46784679for (int i = 0; i < 3; i++) {4680Transform3D axis_angle;4681if (xform.basis.get_column(i).normalized().dot(xform.basis.get_column((i + 1) % 3).normalized()) < 1.0) {4682axis_angle = axis_angle.looking_at(xform.basis.get_column(i).normalized(), xform.basis.get_column((i + 1) % 3).normalized());4683}4684axis_angle.basis.scale(scale);4685axis_angle.origin = xform.origin;4686RenderingServer::get_singleton()->instance_set_transform(move_gizmo_instance[i], axis_angle);4687RenderingServer::get_singleton()->instance_set_visible(move_gizmo_instance[i], show_gizmo && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_TRANSFORM || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_MOVE));4688RenderingServer::get_singleton()->instance_set_transform(move_plane_gizmo_instance[i], axis_angle);4689RenderingServer::get_singleton()->instance_set_visible(move_plane_gizmo_instance[i], show_gizmo && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_TRANSFORM || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_MOVE));4690RenderingServer::get_singleton()->instance_set_transform(rotate_gizmo_instance[i], axis_angle);4691RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[i], show_rotate_gizmo && i != arc_replaces_ring);4692RenderingServer::get_singleton()->instance_set_transform(scale_gizmo_instance[i], axis_angle);4693RenderingServer::get_singleton()->instance_set_visible(scale_gizmo_instance[i], show_gizmo && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SCALE));4694RenderingServer::get_singleton()->instance_set_transform(scale_plane_gizmo_instance[i], axis_angle);4695RenderingServer::get_singleton()->instance_set_visible(scale_plane_gizmo_instance[i], show_gizmo && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SCALE));4696RenderingServer::get_singleton()->instance_set_transform(axis_gizmo_instance[i], xform);4697}46984699Transform3D view_rotation_xform = xform;4700view_rotation_xform.orthonormalize();4701bool shrink_view_ring = arc_replaces_ring >= 0 && arc_replaces_ring < 3;4702Vector3 view_ring_scale = shrink_view_ring ? scale * (GIZMO_CIRCLE_SIZE / GIZMO_VIEW_ROTATION_SIZE) : scale;4703view_rotation_xform.basis.scale(view_ring_scale);4704RenderingServer::get_singleton()->instance_set_transform(rotate_gizmo_instance[3], view_rotation_xform);4705RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[3], show_rotate_gizmo && arc_replaces_ring != 3);47064707bool can_show_trackball = spatial_editor->is_gizmo_visible() && !_edit.instant && transform_gizmo_visible && !collision_reposition && !hide_gizmo_during_rotation;4708bool show_trackball_sphere = can_show_trackball && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_TRANSFORM || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE) && !hide_gizmo_during_trackball;4709RenderingServer::get_singleton()->instance_set_transform(trackball_sphere_instance, view_rotation_xform);4710RenderingServer::get_singleton()->instance_set_visible(trackball_sphere_instance, show_trackball_sphere);47114712bool show_axes = spatial_editor->is_gizmo_visible() && _edit.mode != TRANSFORM_NONE && !hide_gizmo_during_trackball;4713RenderingServer *rs = RenderingServer::get_singleton();4714rs->instance_set_visible(axis_gizmo_instance[0], show_axes && (_edit.plane == TRANSFORM_X_AXIS || _edit.plane == TRANSFORM_XY || _edit.plane == TRANSFORM_XZ));4715rs->instance_set_visible(axis_gizmo_instance[1], show_axes && (_edit.plane == TRANSFORM_Y_AXIS || _edit.plane == TRANSFORM_XY || _edit.plane == TRANSFORM_YZ));4716rs->instance_set_visible(axis_gizmo_instance[2], show_axes && (_edit.plane == TRANSFORM_Z_AXIS || _edit.plane == TRANSFORM_XZ || _edit.plane == TRANSFORM_YZ));4717}47184719void Node3DEditorViewport::set_state(const Dictionary &p_state) {4720if (p_state.has("position")) {4721cursor.pos = p_state["position"];4722}4723if (p_state.has("x_rotation")) {4724cursor.x_rot = p_state["x_rotation"];4725cursor.unsnapped_x_rot = cursor.x_rot;4726}4727if (p_state.has("y_rotation")) {4728cursor.y_rot = p_state["y_rotation"];4729cursor.unsnapped_y_rot = cursor.y_rot;4730}4731if (p_state.has("distance")) {4732cursor.distance = p_state["distance"];4733}4734if (p_state.has("orthogonal")) {4735bool orth = p_state["orthogonal"];4736_menu_option(orth ? VIEW_ORTHOGONAL : VIEW_PERSPECTIVE);4737}4738if (p_state.has("view_type")) {4739view_type = ViewType(p_state["view_type"].operator int());4740_update_name();4741}4742if (p_state.has("auto_orthogonal")) {4743auto_orthogonal = p_state["auto_orthogonal"];4744_update_name();4745}4746if (p_state.has("auto_orthogonal_enabled")) {4747bool enabled = p_state["auto_orthogonal_enabled"];4748view_display_menu->get_popup()->set_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_AUTO_ORTHOGONAL), enabled);4749}4750if (p_state.has("display_mode")) {4751int display = p_state["display_mode"];47524753int idx = view_display_menu->get_popup()->get_item_index(display);4754if (idx != -1 && !view_display_menu->get_popup()->is_item_checked(idx)) {4755_menu_option(display);4756} else {4757idx = display_submenu->get_item_index(display);4758if (idx != -1 && !display_submenu->is_item_checked(idx)) {4759_menu_option(display);4760}4761}4762}4763if (p_state.has("lock_rotation")) {4764_set_lock_view_rotation(p_state["lock_rotation"]);4765}4766if (p_state.has("use_environment")) {4767bool env = p_state["use_environment"];47684769if (env != camera->get_environment().is_valid()) {4770_menu_option(VIEW_ENVIRONMENT);4771}4772}4773if (p_state.has("listener")) {4774bool listener = p_state["listener"];47754776int idx = view_display_menu->get_popup()->get_item_index(VIEW_AUDIO_LISTENER);4777viewport->set_as_audio_listener_3d(listener);4778view_display_menu->get_popup()->set_item_checked(idx, listener);4779}4780if (p_state.has("doppler")) {4781bool doppler = p_state["doppler"];47824783int idx = view_display_menu->get_popup()->get_item_index(VIEW_AUDIO_DOPPLER);4784camera->set_doppler_tracking(doppler ? Camera3D::DOPPLER_TRACKING_IDLE_STEP : Camera3D::DOPPLER_TRACKING_DISABLED);4785view_display_menu->get_popup()->set_item_checked(idx, doppler);4786}4787if (p_state.has("gizmos")) {4788bool gizmos = p_state["gizmos"];47894790int idx = view_display_menu->get_popup()->get_item_index(VIEW_GIZMOS);4791if (view_display_menu->get_popup()->is_item_checked(idx) != gizmos) {4792_menu_option(VIEW_GIZMOS);4793}4794}4795if (p_state.has("transform_gizmo")) {4796bool transform_gizmo = p_state["transform_gizmo"];47974798int idx = view_display_menu->get_popup()->get_item_index(VIEW_TRANSFORM_GIZMO);4799if (view_display_menu->get_popup()->is_item_checked(idx) != transform_gizmo) {4800_menu_option(VIEW_TRANSFORM_GIZMO);4801}4802}4803if (p_state.has("grid")) {4804bool grid = p_state["grid"];48054806int idx = view_display_menu->get_popup()->get_item_index(VIEW_GRID);4807if (view_display_menu->get_popup()->is_item_checked(idx) != grid) {4808_menu_option(VIEW_GRID);4809}4810}4811if (p_state.has("information")) {4812bool information = p_state["information"];48134814int idx = view_display_menu->get_popup()->get_item_index(VIEW_INFORMATION);4815if (view_display_menu->get_popup()->is_item_checked(idx) != information) {4816_menu_option(VIEW_INFORMATION);4817}4818}4819if (p_state.has("frame_time")) {4820bool fps = p_state["frame_time"];48214822int idx = view_display_menu->get_popup()->get_item_index(VIEW_FRAME_TIME);4823if (view_display_menu->get_popup()->is_item_checked(idx) != fps) {4824_menu_option(VIEW_FRAME_TIME);4825}4826}4827if (p_state.has("half_res")) {4828bool half_res = p_state["half_res"];48294830int idx = view_display_menu->get_popup()->get_item_index(VIEW_HALF_RESOLUTION);4831view_display_menu->get_popup()->set_item_checked(idx, half_res);4832_update_shrink();4833}4834if (p_state.has("cinematic_preview")) {4835previewing_cinema = p_state["cinematic_preview"];48364837int idx = view_display_menu->get_popup()->get_item_index(VIEW_CINEMATIC_PREVIEW);4838view_display_menu->get_popup()->set_item_checked(idx, previewing_cinema);48394840cinema_label->set_visible(previewing_cinema);4841if (previewing_cinema) {4842_update_centered_labels();4843surface->queue_redraw();4844}4845}48464847if (preview_camera->is_connected(SceneStringName(toggled), callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview))) {4848preview_camera->disconnect(SceneStringName(toggled), callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview));4849}4850if (p_state.has("previewing")) {4851Node *pv = EditorNode::get_singleton()->get_edited_scene()->get_node(p_state["previewing"]);4852if (Object::cast_to<Camera3D>(pv)) {4853previewing = Object::cast_to<Camera3D>(pv);4854previewing->connect(SceneStringName(tree_exiting), callable_mp(this, &Node3DEditorViewport::_preview_exited_scene));4855previewing->connect(CoreStringName(property_list_changed), callable_mp(this, &Node3DEditorViewport::_preview_camera_property_changed));4856RS::get_singleton()->viewport_attach_camera(viewport->get_viewport_rid(), previewing->get_camera()); //replace4857surface->queue_redraw();4858previewing_camera = true;4859_update_navigation_controls_visibility();4860preview_camera->set_pressed(true);4861preview_camera->show();4862}4863}4864preview_camera->connect(SceneStringName(toggled), callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview));4865}48664867Dictionary Node3DEditorViewport::get_state() const {4868Dictionary d;4869d["position"] = cursor.pos;4870d["x_rotation"] = cursor.x_rot;4871d["y_rotation"] = cursor.y_rot;4872d["distance"] = cursor.distance;4873d["use_environment"] = camera->get_environment().is_valid();4874d["orthogonal"] = camera->get_projection() == Camera3D::PROJECTION_ORTHOGONAL;4875d["view_type"] = view_type;4876d["auto_orthogonal"] = auto_orthogonal;4877d["auto_orthogonal_enabled"] = view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_AUTO_ORTHOGONAL));48784879// Find selected display mode.4880int display_mode = VIEW_DISPLAY_NORMAL;4881for (int i = VIEW_DISPLAY_NORMAL; i < VIEW_DISPLAY_ADVANCED; i++) {4882if (view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(i))) {4883display_mode = i;4884break;4885}4886}4887for (int i = VIEW_DISPLAY_ADVANCED + 1; i < VIEW_DISPLAY_MAX; i++) {4888if (display_submenu->is_item_checked(display_submenu->get_item_index(i))) {4889display_mode = i;4890break;4891}4892}4893d["display_mode"] = display_mode;48944895d["listener"] = viewport->is_audio_listener_3d();4896d["doppler"] = view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_AUDIO_DOPPLER));4897d["gizmos"] = view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_GIZMOS));4898d["transform_gizmo"] = view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_TRANSFORM_GIZMO));4899d["grid"] = view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_GRID));4900d["information"] = view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_INFORMATION));4901d["frame_time"] = view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_FRAME_TIME));4902d["half_res"] = view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_HALF_RESOLUTION));4903d["cinematic_preview"] = view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_CINEMATIC_PREVIEW));4904if (previewing) {4905d["previewing"] = EditorNode::get_singleton()->get_edited_scene()->get_path_to(previewing);4906}4907d["lock_rotation"] = lock_rotation;49084909return d;4910}49114912void Node3DEditorViewport::_bind_methods() {4913ADD_SIGNAL(MethodInfo("toggle_maximize_view", PropertyInfo(Variant::OBJECT, "viewport")));4914ADD_SIGNAL(MethodInfo("clicked"));4915}49164917void Node3DEditorViewport::reset() {4918orthogonal = false;4919auto_orthogonal = false;4920lock_rotation = false;4921message_time = 0;4922message = "";4923last_message = "";4924view_type = VIEW_TYPE_USER;49254926cursor = Cursor();4927_update_name();4928}49294930void Node3DEditorViewport::focus_selection() {4931Vector3 center;4932int count = 0;49334934const List<Node *> &selection = editor_selection->get_top_selected_node_list();49354936for (Node *node : selection) {4937Node3D *node_3d = Object::cast_to<Node3D>(node);4938if (!node_3d) {4939continue;4940}49414942Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(node_3d);4943if (!se) {4944continue;4945}49464947if (se->gizmo.is_valid()) {4948for (const KeyValue<int, Transform3D> &GE : se->subgizmos) {4949const Vector3 pos = se->gizmo->get_subgizmo_transform(GE.key).origin;4950if (pos.is_finite()) {4951center += pos;4952count++;4953}4954}4955}4956const Vector3 pos = node_3d->get_global_gizmo_transform().origin;4957if (pos.is_finite()) {4958center += pos;4959count++;4960}4961}49624963if (count > 1) {4964center /= count;4965}49664967cursor.pos = center;4968}49694970void Node3DEditorViewport::assign_pending_data_pointers(Node3D *p_preview_node, AABB *p_preview_bounds, AcceptDialog *p_accept) {4971preview_node = p_preview_node;4972preview_bounds = p_preview_bounds;4973accept = p_accept;4974}49754976void _insert_rid_recursive(Node *node, HashSet<RID> &rids) {4977CollisionObject3D *co = Object::cast_to<CollisionObject3D>(node);49784979if (co) {4980rids.insert(co->get_rid());4981} else if (node->is_class("CSGShape3D")) { // HACK: We should avoid referencing module logic.4982rids.insert(node->call("_get_root_collision_instance"));4983}49844985for (int i = 0; i < node->get_child_count(); i++) {4986Node *child = node->get_child(i);4987_insert_rid_recursive(child, rids);4988}4989}49904991Vector3 Node3DEditorViewport::_get_instance_position(const Point2 &p_pos, Node3D *p_node) const {4992const float MAX_DISTANCE = 50.0;4993const float FALLBACK_DISTANCE = 5.0;49944995Vector3 world_ray = get_ray(p_pos);4996Vector3 world_pos = get_ray_pos(p_pos);49974998PhysicsDirectSpaceState3D *ss = get_tree()->get_root()->get_world_3d()->get_direct_space_state();49995000HashSet<RID> rids;50015002if (preview_node && preview_node->get_child_count() > 0) {5003_insert_rid_recursive(preview_node, rids);5004} else if (!preview_node->is_inside_tree() && !ruler->is_inside_tree()) {5005const List<Node *> &selection = editor_selection->get_top_selected_node_list();50065007Node3D *first_selected_node = Object::cast_to<Node3D>(selection.front()->get());50085009if (first_selected_node) {5010_insert_rid_recursive(first_selected_node, rids);5011}5012}50135014PhysicsDirectSpaceState3D::RayParameters ray_params;5015ray_params.exclude = rids;5016ray_params.from = world_pos;5017ray_params.to = world_pos + world_ray * camera->get_far();50185019PhysicsDirectSpaceState3D::RayResult result;5020if (ss->intersect_ray(ray_params, result) && (preview_node->get_child_count() > 0 || !preview_node->is_inside_tree())) {5021// Calculate an offset for the `p_node` such that the its bounding box is on top of and touching the contact surface's plane.50225023// Use the Gram-Schmidt process to get an orthonormal Basis aligned with the surface normal.5024const Vector3 bb_basis_x = result.normal;5025Vector3 bb_basis_y = Vector3(0, 1, 0);5026bb_basis_y = bb_basis_y - bb_basis_y.project(bb_basis_x);5027if (bb_basis_y.is_zero_approx()) {5028bb_basis_y = Vector3(0, 0, 1);5029bb_basis_y = bb_basis_y - bb_basis_y.project(bb_basis_x);5030}5031bb_basis_y = bb_basis_y.normalized();5032const Vector3 bb_basis_z = bb_basis_x.cross(bb_basis_y);5033const Basis bb_basis = Basis(bb_basis_x, bb_basis_y, bb_basis_z);50345035// This normal-aligned Basis allows us to create an AABB that can fit on the surface plane as snugly as possible.5036const Transform3D bb_transform = Transform3D(bb_basis, p_node->get_global_transform().origin);5037const AABB p_node_bb = _calculate_spatial_bounds(p_node, true, &bb_transform);5038// 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.5039const float offset_distance = -p_node_bb.position.x;50405041// `result_offset` is in global space.5042const Vector3 result_offset = result.position + result.normal * offset_distance;50435044return result_offset;5045}50465047const bool is_orthogonal = camera->get_projection() == Camera3D::PROJECTION_ORTHOGONAL;50485049// The XZ plane.5050Vector3 intersection;5051Plane plane(Vector3(0, 1, 0));5052if (plane.intersects_ray(world_pos, world_ray, &intersection)) {5053if (is_orthogonal || world_pos.distance_to(intersection) <= MAX_DISTANCE) {5054return intersection;5055}5056}50575058// Plane facing the camera using fallback distance.5059if (is_orthogonal) {5060plane = Plane(world_ray, cursor.pos - world_ray * (cursor.distance - FALLBACK_DISTANCE));5061} else {5062plane = Plane(world_ray, world_pos + world_ray * FALLBACK_DISTANCE);5063}5064if (plane.intersects_ray(world_pos, world_ray, &intersection)) {5065return intersection;5066}50675068// Not likely, but just in case...5069return world_pos + world_ray * FALLBACK_DISTANCE;5070}50715072AABB Node3DEditorViewport::_calculate_spatial_bounds(const Node3D *p_parent, bool p_omit_top_level, const Transform3D *p_bounds_orientation) {5073if (!p_parent) {5074return AABB(Vector3(-0.2, -0.2, -0.2), Vector3(0.4, 0.4, 0.4));5075}5076const Transform3D parent_transform = p_parent->get_global_transform();5077if (!parent_transform.is_finite()) {5078return AABB();5079}5080AABB bounds;50815082Transform3D bounds_orientation;5083Transform3D xform_to_top_level_parent_space;5084if (p_bounds_orientation) {5085bounds_orientation = *p_bounds_orientation;5086xform_to_top_level_parent_space = bounds_orientation.affine_inverse() * parent_transform;5087} else {5088bounds_orientation = parent_transform;5089}50905091const VisualInstance3D *visual_instance = Object::cast_to<VisualInstance3D>(p_parent);5092if (visual_instance) {5093bounds = visual_instance->get_aabb();5094} else {5095bounds = AABB();5096}5097bounds = xform_to_top_level_parent_space.xform(bounds);50985099for (int i = 0; i < p_parent->get_child_count(); i++) {5100const Node3D *child = Object::cast_to<Node3D>(p_parent->get_child(i));5101if (child && !(p_omit_top_level && child->is_set_as_top_level())) {5102const AABB child_bounds = _calculate_spatial_bounds(child, p_omit_top_level, &bounds_orientation);5103bounds.merge_with(child_bounds);5104}5105}51065107return bounds;5108}51095110Node *Node3DEditorViewport::_sanitize_preview_node(Node *p_node) const {5111Node3D *node_3d = Object::cast_to<Node3D>(p_node);5112if (node_3d == nullptr) {5113Node3D *replacement_node = memnew(Node3D);5114replacement_node->set_name(p_node->get_name());5115p_node->replace_by(replacement_node);5116memdelete(p_node);5117p_node = replacement_node;5118} else {5119VisualInstance3D *visual_instance = Object::cast_to<VisualInstance3D>(node_3d);5120if (visual_instance == nullptr) {5121Node3D *replacement_node = memnew(Node3D);5122replacement_node->set_name(node_3d->get_name());5123replacement_node->set_visible(node_3d->is_visible());5124replacement_node->set_transform(node_3d->get_transform());5125replacement_node->set_rotation_edit_mode(node_3d->get_rotation_edit_mode());5126replacement_node->set_rotation_order(node_3d->get_rotation_order());5127replacement_node->set_as_top_level(node_3d->is_set_as_top_level());5128p_node->replace_by(replacement_node);5129memdelete(p_node);5130p_node = replacement_node;5131}5132}51335134for (int i = 0; i < p_node->get_child_count(); i++) {5135_sanitize_preview_node(p_node->get_child(i));5136}51375138return p_node;5139}51405141void Node3DEditorViewport::_create_preview_node(const Vector<String> &files) const {5142bool add_preview = false;5143for (const String &path : files) {5144Ref<Resource> res = ResourceLoader::load(path);5145ERR_CONTINUE(res.is_null());51465147Ref<PackedScene> scene = res;5148if (scene.is_valid()) {5149Node *instance = scene->instantiate();5150if (instance) {5151instance = _sanitize_preview_node(instance);5152preview_node->add_child(instance);5153Node3D *node_3d = Object::cast_to<Node3D>(instance);5154if (node_3d) {5155node_3d->set_as_top_level(false);5156}5157}5158add_preview = true;5159}51605161Ref<Mesh> mesh = res;5162if (mesh.is_valid()) {5163MeshInstance3D *mesh_instance = memnew(MeshInstance3D);5164mesh_instance->set_mesh(mesh);5165preview_node->add_child(mesh_instance);5166add_preview = true;5167}51685169Ref<AudioStream> audio = res;5170if (audio.is_valid()) {5171Sprite3D *sprite = memnew(Sprite3D);5172sprite->set_texture(get_editor_theme_icon(SNAME("Gizmo3DSamplePlayer")));5173sprite->set_billboard_mode(StandardMaterial3D::BILLBOARD_ENABLED);5174sprite->set_pixel_size(0.005);5175preview_node->add_child(sprite);5176add_preview = true;5177}5178}5179if (add_preview) {5180EditorNode::get_singleton()->get_scene_root()->add_child(preview_node);5181}51825183*preview_bounds = _calculate_spatial_bounds(preview_node);5184}51855186void Node3DEditorViewport::_remove_preview_node() {5187set_message("");5188if (preview_node->get_parent()) {5189for (int i = preview_node->get_child_count() - 1; i >= 0; i--) {5190Node *node = preview_node->get_child(i);5191node->queue_free();5192preview_node->remove_child(node);5193}5194EditorNode::get_singleton()->get_scene_root()->remove_child(preview_node);5195}5196}51975198bool Node3DEditorViewport::_apply_preview_material(ObjectID p_target, const Point2 &p_point) const {5199_reset_preview_material();52005201if (p_target.is_null()) {5202return false;5203}52045205spatial_editor->set_preview_material_target(p_target);52065207Object *target_inst = ObjectDB::get_instance(p_target);52085209bool is_ctrl = Input::get_singleton()->is_key_pressed(Key::CTRL);52105211MeshInstance3D *mesh_instance = Object::cast_to<MeshInstance3D>(target_inst);5212if (is_ctrl && mesh_instance) {5213Ref<Mesh> mesh = mesh_instance->get_mesh();5214int surface_count = mesh->get_surface_count();52155216Vector3 world_ray = get_ray(p_point);5217Vector3 world_pos = get_ray_pos(p_point);52185219int closest_surface = -1;5220float closest_dist = 1e20;52215222Transform3D gt = mesh_instance->get_global_transform();52235224Transform3D ai = gt.affine_inverse();5225Vector3 xform_ray = ai.basis.xform(world_ray).normalized();5226Vector3 xform_pos = ai.xform(world_pos);52275228for (int surface_idx = 0; surface_idx < surface_count; surface_idx++) {5229Ref<TriangleMesh> surface_mesh = mesh->generate_surface_triangle_mesh(surface_idx);52305231Vector3 rpos, rnorm;5232if (surface_mesh->intersect_ray(xform_pos, xform_ray, rpos, rnorm)) {5233Vector3 hitpos = gt.xform(rpos);52345235const real_t dist = world_pos.distance_to(hitpos);52365237if (dist < 0) {5238continue;5239}52405241if (dist < closest_dist) {5242closest_surface = surface_idx;5243closest_dist = dist;5244}5245}5246}52475248if (closest_surface == -1) {5249return false;5250}52515252spatial_editor->set_preview_material_surface(closest_surface);5253spatial_editor->set_preview_reset_material(mesh_instance->get_surface_override_material(closest_surface));5254mesh_instance->set_surface_override_material(closest_surface, spatial_editor->get_preview_material());52555256return true;5257}52585259GeometryInstance3D *geometry_instance = Object::cast_to<GeometryInstance3D>(target_inst);5260if (geometry_instance) {5261spatial_editor->set_preview_material_surface(-1);5262spatial_editor->set_preview_reset_material(geometry_instance->get_material_override());5263geometry_instance->set_material_override(spatial_editor->get_preview_material());5264return true;5265}52665267return false;5268}52695270void Node3DEditorViewport::_reset_preview_material() const {5271ObjectID last_target = spatial_editor->get_preview_material_target();5272if (last_target.is_null()) {5273return;5274}5275Object *last_target_inst = ObjectDB::get_instance(last_target);52765277MeshInstance3D *mesh_instance = Object::cast_to<MeshInstance3D>(last_target_inst);5278GeometryInstance3D *geometry_instance = Object::cast_to<GeometryInstance3D>(last_target_inst);5279if (mesh_instance && spatial_editor->get_preview_material_surface() != -1) {5280mesh_instance->set_surface_override_material(spatial_editor->get_preview_material_surface(), spatial_editor->get_preview_reset_material());5281} else if (geometry_instance) {5282geometry_instance->set_material_override(spatial_editor->get_preview_reset_material());5283}5284}52855286void Node3DEditorViewport::_remove_preview_material() {5287preview_material_label->hide();5288preview_material_label_desc->hide();52895290spatial_editor->set_preview_material(Ref<Material>());5291spatial_editor->set_preview_reset_material(Ref<Material>());5292spatial_editor->set_preview_material_target(ObjectID());5293spatial_editor->set_preview_material_surface(-1);5294}52955296bool Node3DEditorViewport::_cyclical_dependency_exists(const String &p_target_scene_path, Node *p_desired_node) const {5297if (p_desired_node->get_scene_file_path() == p_target_scene_path) {5298return true;5299}53005301int childCount = p_desired_node->get_child_count();5302for (int i = 0; i < childCount; i++) {5303Node *child = p_desired_node->get_child(i);5304if (_cyclical_dependency_exists(p_target_scene_path, child)) {5305return true;5306}5307}5308return false;5309}53105311bool Node3DEditorViewport::_create_instance(Node *p_parent, const String &p_path, const Point2 &p_point) {5312Ref<Resource> res = ResourceLoader::load(p_path);5313ERR_FAIL_COND_V(res.is_null(), false);53145315Ref<PackedScene> scene = res;5316Ref<Mesh> mesh = res;53175318Node *instantiated_scene = nullptr;53195320if (mesh.is_valid() || scene.is_valid()) {5321if (mesh.is_valid()) {5322MeshInstance3D *mesh_instance = memnew(MeshInstance3D);5323mesh_instance->set_mesh(mesh);53245325// Adjust casing according to project setting. The file name is expected to be in snake_case, but will work for others.5326const String &node_name = Node::adjust_name_casing(p_path.get_file().get_basename());5327if (!node_name.is_empty()) {5328mesh_instance->set_name(node_name);5329}53305331instantiated_scene = mesh_instance;5332} else {5333if (scene.is_null()) { // invalid scene5334return false;5335} else {5336instantiated_scene = scene->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE);5337}5338}5339}53405341if (instantiated_scene == nullptr) {5342return false;5343}53445345if (!EditorNode::get_singleton()->get_edited_scene()->get_scene_file_path().is_empty()) { // Cyclic instantiation.5346if (_cyclical_dependency_exists(EditorNode::get_singleton()->get_edited_scene()->get_scene_file_path(), instantiated_scene)) {5347memdelete(instantiated_scene);5348return false;5349}5350}53515352if (scene.is_valid()) {5353instantiated_scene->set_scene_file_path(ProjectSettings::get_singleton()->localize_path(p_path));5354}53555356EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();5357undo_redo->add_do_method(p_parent, "add_child", instantiated_scene, true);5358undo_redo->add_do_method(instantiated_scene, "set_owner", EditorNode::get_singleton()->get_edited_scene());5359undo_redo->add_do_reference(instantiated_scene);5360undo_redo->add_undo_method(p_parent, "remove_child", instantiated_scene);5361undo_redo->add_do_method(editor_selection, "add_node", instantiated_scene);53625363String new_name = p_parent->validate_child_name(instantiated_scene);5364EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton();5365undo_redo->add_do_method(ed, "live_debug_instantiate_node", EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent), p_path, new_name);5366undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent)) + "/" + new_name));53675368Node3D *node3d = Object::cast_to<Node3D>(instantiated_scene);5369if (node3d) {5370Transform3D parent_tf;5371Node3D *parent_node3d = Object::cast_to<Node3D>(p_parent);5372if (parent_node3d) {5373parent_tf = parent_node3d->get_global_gizmo_transform();5374}53755376Transform3D new_tf = node3d->get_transform();5377if (node3d->is_set_as_top_level()) {5378new_tf.origin += preview_node_pos;5379} else {5380new_tf.origin = parent_tf.affine_inverse().xform(preview_node_pos + node3d->get_position());5381new_tf.basis = parent_tf.affine_inverse().basis * new_tf.basis;5382}53835384undo_redo->add_do_method(instantiated_scene, "set_transform", new_tf);5385}53865387return true;5388}53895390bool Node3DEditorViewport::_create_audio_node(Node *p_parent, const String &p_path, const Point2 &p_point) {5391Ref<AudioStream> audio = ResourceLoader::load(p_path);5392ERR_FAIL_COND_V(audio.is_null(), false);53935394AudioStreamPlayer3D *audio_player = memnew(AudioStreamPlayer3D);5395audio_player->set_stream(audio);53965397// Adjust casing according to project setting. The file name is expected to be in snake_case, but will work for others.5398const String &node_name = Node::adjust_name_casing(p_path.get_file().get_basename());5399if (!node_name.is_empty()) {5400audio_player->set_name(node_name);5401}54025403EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();5404undo_redo->add_do_method(p_parent, "add_child", audio_player, true);5405undo_redo->add_do_method(audio_player, "set_owner", EditorNode::get_singleton()->get_edited_scene());5406undo_redo->add_do_reference(audio_player);5407undo_redo->add_undo_method(p_parent, "remove_child", audio_player);5408undo_redo->add_do_method(editor_selection, "add_node", audio_player);54095410const String new_name = p_parent->validate_child_name(audio_player);5411EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton();5412undo_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);5413undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent)) + "/" + new_name));54145415Transform3D parent_tf;5416Node3D *parent_node3d = Object::cast_to<Node3D>(p_parent);5417if (parent_node3d) {5418parent_tf = parent_node3d->get_global_gizmo_transform();5419}54205421Transform3D new_tf = audio_player->get_transform();5422new_tf.origin = parent_tf.affine_inverse().xform(preview_node_pos + audio_player->get_position());5423new_tf.basis = parent_tf.affine_inverse().basis * new_tf.basis;54245425undo_redo->add_do_method(audio_player, "set_transform", new_tf);54265427return true;5428}54295430void Node3DEditorViewport::_perform_drop_data() {5431EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();5432if (spatial_editor->get_preview_material_target().is_valid()) {5433GeometryInstance3D *geometry_instance = ObjectDB::get_instance<GeometryInstance3D>(spatial_editor->get_preview_material_target());5434MeshInstance3D *mesh_instance = ObjectDB::get_instance<MeshInstance3D>(spatial_editor->get_preview_material_target());5435if (mesh_instance && spatial_editor->get_preview_material_surface() != -1) {5436undo_redo->create_action(vformat(TTR("Set Surface %d Override Material"), spatial_editor->get_preview_material_surface()));5437undo_redo->add_do_method(geometry_instance, "set_surface_override_material", spatial_editor->get_preview_material_surface(), spatial_editor->get_preview_material());5438undo_redo->add_undo_method(geometry_instance, "set_surface_override_material", spatial_editor->get_preview_material_surface(), spatial_editor->get_preview_reset_material());5439undo_redo->commit_action();5440} else if (geometry_instance) {5441undo_redo->create_action(TTR("Set Material Override"));5442undo_redo->add_do_method(geometry_instance, "set_material_override", spatial_editor->get_preview_material());5443undo_redo->add_undo_method(geometry_instance, "set_material_override", spatial_editor->get_preview_reset_material());5444undo_redo->commit_action();5445}54465447_remove_preview_material();5448return;5449}54505451_remove_preview_node();54525453PackedStringArray error_files;54545455undo_redo->create_action(TTR("Create Node"), UndoRedo::MERGE_DISABLE, target_node);5456undo_redo->add_do_method(editor_selection, "clear");54575458for (int i = 0; i < selected_files.size(); i++) {5459String path = selected_files[i];5460Ref<Resource> res = ResourceLoader::load(path);5461if (res.is_null()) {5462continue;5463}54645465Ref<PackedScene> scene = res;5466Ref<Mesh> mesh = res;5467if (mesh.is_valid() || scene.is_valid()) {5468if (!_create_instance(target_node, path, drop_pos)) {5469error_files.push_back(path.get_file());5470}5471}54725473Ref<AudioStream> audio = res;5474if (audio.is_valid()) {5475if (!_create_audio_node(target_node, path, drop_pos)) {5476error_files.push_back(path.get_file());5477}5478}5479}54805481undo_redo->commit_action();54825483if (error_files.size() > 0) {5484accept->set_text(vformat(TTR("Error instantiating scene from %s."), String(", ").join(error_files)));5485accept->popup_centered();5486}5487}54885489bool Node3DEditorViewport::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {5490if (p_point == Vector2(Math::INF, Math::INF)) {5491return false;5492}5493preview_node_viewport_pos = p_point;54945495bool can_instantiate = false;5496bool is_cyclical_dep = false;5497String error_file;54985499if (!preview_node->is_inside_tree() && spatial_editor->get_preview_material().is_null()) {5500Dictionary d = p_data;5501if (d.has("type") && (String(d["type"]) == "files")) {5502Vector<String> files = d["files"];55035504// Track whether a type other than PackedScene is valid to stop checking them and only5505// continue to check if the rest of the scenes are valid (don't have cyclic dependencies).5506bool is_other_valid = false;5507// Check if at least one of the dragged files is a mesh, material, texture or scene.5508for (int i = 0; i < files.size(); i++) {5509const String &res_type = ResourceLoader::get_resource_type(files[i]);5510bool is_scene = ClassDB::is_parent_class(res_type, "PackedScene");5511bool is_mesh = ClassDB::is_parent_class(res_type, "Mesh");5512bool is_material = ClassDB::is_parent_class(res_type, "Material");5513bool is_texture = ClassDB::is_parent_class(res_type, "Texture");5514bool is_audio = ClassDB::is_parent_class(res_type, "AudioStream");55155516if (is_mesh || is_scene || is_material || is_texture || is_audio) {5517Ref<Resource> res = ResourceLoader::load(files[i]);5518if (res.is_null()) {5519continue;5520}5521Ref<PackedScene> scn = res;5522Ref<Mesh> mesh = res;5523Ref<Material> mat = res;5524Ref<Texture2D> tex = res;5525Ref<AudioStream> audio = res;5526if (scn.is_valid()) {5527Node *instantiated_scene = scn->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE);5528if (!instantiated_scene) {5529continue;5530}5531Node *edited_scene = EditorNode::get_singleton()->get_edited_scene();5532if (edited_scene && !edited_scene->get_scene_file_path().is_empty() && _cyclical_dependency_exists(edited_scene->get_scene_file_path(), instantiated_scene)) {5533memdelete(instantiated_scene);5534can_instantiate = false;5535is_cyclical_dep = true;5536error_file = files[i].get_file();5537break;5538}5539memdelete(instantiated_scene);5540} else if (!is_other_valid && mat.is_valid()) {5541Ref<BaseMaterial3D> base_mat = res;5542Ref<ShaderMaterial> shader_mat = res;55435544if (base_mat.is_null() && shader_mat.is_null()) {5545continue;5546}55475548spatial_editor->set_preview_material(mat);5549is_other_valid = true;5550continue;5551} else if (!is_other_valid && mesh.is_valid()) {5552// Let the mesh pass.5553is_other_valid = true;5554} else if (!is_other_valid && tex.is_valid()) {5555Ref<StandardMaterial3D> new_mat = memnew(StandardMaterial3D);5556new_mat->set_texture(BaseMaterial3D::TEXTURE_ALBEDO, tex);55575558spatial_editor->set_preview_material(new_mat);5559is_other_valid = true;5560continue;5561} else if (!is_other_valid && audio.is_valid()) {5562is_other_valid = true;5563} else {5564continue;5565}5566can_instantiate = true;5567}5568}5569if (can_instantiate) {5570_create_preview_node(files);5571preview_node->hide();5572}5573}5574} else {5575if (preview_node->is_inside_tree()) {5576can_instantiate = true;5577}5578}55795580if (is_cyclical_dep) {5581set_message(vformat(TTR("Can't instantiate: %s."), vformat(TTR("Circular dependency found at %s"), error_file)));5582return false;5583}55845585if (can_instantiate) {5586update_preview_node = true;5587return true;5588}55895590if (spatial_editor->get_preview_material().is_valid()) {5591preview_material_label->show();5592preview_material_label_desc->show();55935594ObjectID new_preview_material_target = _select_ray(p_point);5595return _apply_preview_material(new_preview_material_target, p_point);5596}55975598return false;5599}56005601void Node3DEditorViewport::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {5602if (!can_drop_data_fw(p_point, p_data, p_from)) {5603return;5604}56055606bool is_shift = Input::get_singleton()->is_key_pressed(Key::SHIFT);5607bool is_alt = Input::get_singleton()->is_key_pressed(Key::ALT);56085609selected_files.clear();5610Dictionary d = p_data;5611if (d.has("type") && String(d["type"]) == "files") {5612selected_files = d["files"];5613}56145615const List<Node *> &selected_nodes = EditorNode::get_singleton()->get_editor_selection()->get_top_selected_node_list();5616Node *root_node = EditorNode::get_singleton()->get_edited_scene();5617if (selected_nodes.size() > 0) {5618Node *selected_node = selected_nodes.front()->get();5619if (is_alt) {5620target_node = root_node;5621} else if (is_shift) {5622target_node = selected_node;5623} else { // Default behavior.5624target_node = (selected_node != root_node) ? selected_node->get_parent() : root_node;5625}5626} else {5627if (root_node) {5628target_node = root_node;5629} else {5630// Create a root node so we can add child nodes to it.5631SceneTreeDock::get_singleton()->add_root_node(memnew(Node3D));5632target_node = get_tree()->get_edited_scene_root();5633}5634}56355636drop_pos = p_point;56375638_perform_drop_data();5639}56405641void Node3DEditorViewport::begin_transform(TransformMode p_mode, bool instant) {5642if (get_selected_count() > 0) {5643_edit.mode = p_mode;5644_compute_edit(_edit.mouse_pos);5645_edit.instant = instant;5646_edit.snap = spatial_editor->is_snap_enabled();5647_edit.initial_click_vector = Vector3();5648_edit.previous_rotation_vector = Vector3();5649_edit.accumulated_rotation_angle = 0.0;5650_edit.display_rotation_angle = 0.0;5651_edit.gizmo_initiated = false;5652update_transform_gizmo_view();5653set_process_input(instant);5654}5655}56565657// Apply the current transform operation.5658void Node3DEditorViewport::commit_transform() {5659ERR_FAIL_COND(_edit.mode == TRANSFORM_NONE);5660static const char *_transform_name[4] = {5661TTRC("None"),5662TTRC("Rotate"),5663// TRANSLATORS: This refers to the movement that changes the position of an object.5664TTRC("Translate"),5665TTRC("Scale"),5666};5667EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();5668undo_redo->create_action(_transform_name[_edit.mode]);56695670const List<Node *> &selection = editor_selection->get_top_selected_node_list();56715672for (Node *E : selection) {5673Node3D *sp = Object::cast_to<Node3D>(E);5674if (!sp) {5675continue;5676}56775678Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);5679if (!se) {5680continue;5681}56825683undo_redo->add_do_method(sp, "set_transform", sp->get_local_gizmo_transform());5684undo_redo->add_undo_method(sp, "set_transform", se->original_local);5685}5686undo_redo->commit_action();56875688collision_reposition = false;5689finish_transform();5690set_message("");5691}56925693void Node3DEditorViewport::apply_transform(Vector3 p_motion, double p_snap) {5694// View-plane translate/scale always uses global coords; rotation and axis operations respect local/global preference.5695bool local_coords = spatial_editor->are_local_coords_enabled() &&5696!(_edit.plane == TRANSFORM_VIEW && _edit.mode != TRANSFORM_ROTATE);56975698bool is_global_view_plane = (_edit.plane == TRANSFORM_VIEW) &&5699((_edit.mode != TRANSFORM_ROTATE) || !spatial_editor->are_local_coords_enabled());57005701const List<Node *> &selection = editor_selection->get_top_selected_node_list();5702for (Node *E : selection) {5703Node3D *sp = Object::cast_to<Node3D>(E);5704if (!sp) {5705continue;5706}57075708Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);5709if (!se) {5710continue;5711}57125713if (sp->has_meta("_edit_lock_") && !spatial_editor->is_gizmo_visible()) {5714continue;5715}57165717if (se->gizmo.is_valid()) {5718for (KeyValue<int, Transform3D> &GE : se->subgizmos) {5719Transform3D xform = GE.value;5720Transform3D new_xform = _compute_transform(_edit.mode, se->original * xform, xform, p_motion, p_snap, local_coords, _edit.plane != TRANSFORM_VIEW, is_global_view_plane); // Force orthogonal with subgizmo.5721if (!local_coords) {5722new_xform = se->original.affine_inverse() * new_xform;5723}5724se->gizmo->set_subgizmo_transform(GE.key, new_xform);5725}5726} else {5727Transform3D 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, is_global_view_plane);5728_transform_gizmo_apply(se->sp, new_xform, local_coords);5729}5730}57315732spatial_editor->update_transform_gizmo();5733surface->queue_redraw();5734}57355736// Update the current transform operation in response to an input.5737void Node3DEditorViewport::update_transform(bool p_shift) {5738Vector3 ray_pos = get_ray_pos(_edit.mouse_pos);5739Vector3 ray = get_ray(_edit.mouse_pos);5740double snap = EDITOR_GET("interface/inspector/default_float_step");5741int snap_step_decimals = Math::range_step_decimals(snap);57425743// View-plane translate/scale always uses global coords; rotation and axis operations respect local/global preference.5744bool local_coords = spatial_editor->are_local_coords_enabled() &&5745!(_edit.plane == TRANSFORM_VIEW && _edit.mode != TRANSFORM_ROTATE);57465747switch (_edit.mode) {5748case TRANSFORM_SCALE: {5749Vector3 motion_mask;5750Plane plane;5751bool plane_mv = false;57525753switch (_edit.plane) {5754case TRANSFORM_VIEW:5755motion_mask = Vector3(0, 0, 0);5756plane = Plane(_get_camera_normal(), _edit.center);5757break;5758case TRANSFORM_X_AXIS:5759motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(0).normalized();5760plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center);5761break;5762case TRANSFORM_Y_AXIS:5763motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(1).normalized();5764plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center);5765break;5766case TRANSFORM_Z_AXIS:5767motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(2).normalized();5768plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center);5769break;5770case TRANSFORM_YZ:5771motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(2).normalized() + spatial_editor->get_gizmo_transform().basis.get_column(1).normalized();5772plane = Plane(spatial_editor->get_gizmo_transform().basis.get_column(0).normalized(), _edit.center);5773plane_mv = true;5774break;5775case TRANSFORM_XZ:5776motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(2).normalized() + spatial_editor->get_gizmo_transform().basis.get_column(0).normalized();5777plane = Plane(spatial_editor->get_gizmo_transform().basis.get_column(1).normalized(), _edit.center);5778plane_mv = true;5779break;5780case TRANSFORM_XY:5781motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(0).normalized() + spatial_editor->get_gizmo_transform().basis.get_column(1).normalized();5782plane = Plane(spatial_editor->get_gizmo_transform().basis.get_column(2).normalized(), _edit.center);5783plane_mv = true;5784break;5785}57865787Vector3 intersection;5788if (!plane.intersects_ray(ray_pos, ray, &intersection)) {5789break;5790}57915792Vector3 click;5793if (!plane.intersects_ray(_edit.click_ray_pos, _edit.click_ray, &click)) {5794break;5795}57965797Vector3 motion = intersection - click;5798if (_edit.plane != TRANSFORM_VIEW) {5799if (!plane_mv) {5800motion = motion_mask.dot(motion) * motion_mask;58015802} else {5803// Alternative planar scaling mode5804if (p_shift) {5805motion = motion_mask.dot(motion) * motion_mask;5806}5807}58085809} else {5810const real_t center_click_dist = click.distance_to(_edit.center);5811const real_t center_inters_dist = intersection.distance_to(_edit.center);5812if (center_click_dist == 0) {5813break;5814}58155816const real_t scale = center_inters_dist - center_click_dist;5817motion = Vector3(scale, scale, scale);5818}58195820motion /= click.distance_to(_edit.center);58215822if (_edit.snap || spatial_editor->is_snap_enabled()) {5823snap = spatial_editor->get_scale_snap() / 100;5824}5825Vector3 motion_snapped = motion;5826motion_snapped.snapf(snap);5827// This might not be necessary anymore after issue #288 is solved (in 4.0?).5828// TRANSLATORS: Refers to changing the scale of a node in the 3D editor.5829set_message(TTR("Scaling:") + " (" + String::num(motion_snapped.x, snap_step_decimals) + ", " +5830String::num(motion_snapped.y, snap_step_decimals) + ", " + String::num(motion_snapped.z, snap_step_decimals) + ")");5831if (local_coords) {5832// TODO: needed?5833motion = _edit.original.basis.inverse().xform(motion);5834}58355836apply_transform(motion, snap);5837} break;58385839case TRANSFORM_TRANSLATE: {5840Vector3 motion_mask;5841Plane plane;5842bool plane_mv = false;58435844switch (_edit.plane) {5845case TRANSFORM_VIEW:5846plane = Plane(_get_camera_normal(), _edit.center);5847break;5848case TRANSFORM_X_AXIS:5849motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(0).normalized();5850plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center);5851break;5852case TRANSFORM_Y_AXIS:5853motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(1).normalized();5854plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center);5855break;5856case TRANSFORM_Z_AXIS:5857motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(2).normalized();5858plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center);5859break;5860case TRANSFORM_YZ:5861plane = Plane(spatial_editor->get_gizmo_transform().basis.get_column(0).normalized(), _edit.center);5862plane_mv = true;5863break;5864case TRANSFORM_XZ:5865plane = Plane(spatial_editor->get_gizmo_transform().basis.get_column(1).normalized(), _edit.center);5866plane_mv = true;5867break;5868case TRANSFORM_XY:5869plane = Plane(spatial_editor->get_gizmo_transform().basis.get_column(2).normalized(), _edit.center);5870plane_mv = true;5871break;5872}58735874Vector3 intersection;5875if (!plane.intersects_ray(ray_pos, ray, &intersection)) {5876break;5877}58785879Vector3 click;5880if (!plane.intersects_ray(_edit.click_ray_pos, _edit.click_ray, &click)) {5881break;5882}58835884Vector3 motion = intersection - click;5885if (_edit.plane != TRANSFORM_VIEW) {5886if (!plane_mv) {5887motion = motion_mask.dot(motion) * motion_mask;5888}5889}58905891if (_edit.snap || spatial_editor->is_snap_enabled()) {5892snap = spatial_editor->get_translate_snap();5893}5894Vector3 motion_snapped = motion;5895motion_snapped.snapf(snap);5896// TRANSLATORS: Refers to changing the position of a node in the 3D editor.5897set_message(TTR("Translating:") + " (" + String::num(motion_snapped.x, snap_step_decimals) + ", " +5898String::num(motion_snapped.y, snap_step_decimals) + ", " + String::num(motion_snapped.z, snap_step_decimals) + ")");5899if (local_coords) {5900motion = spatial_editor->get_gizmo_transform().basis.inverse().xform(motion);5901}59025903apply_transform(motion, snap);5904} break;59055906case TRANSFORM_ROTATE: {5907Plane plane;5908if (camera->get_projection() == Camera3D::PROJECTION_PERSPECTIVE) {5909Vector3 cam_to_obj = _edit.center - _get_camera_position();5910if (!cam_to_obj.is_zero_approx()) {5911plane = Plane(cam_to_obj.normalized(), _edit.center);5912} else {5913plane = Plane(_get_camera_normal(), _edit.center);5914}5915} else {5916plane = Plane(_get_camera_normal(), _edit.center);5917}59185919if (_edit.is_trackball) {5920Vector2 motion_delta = _edit.mouse_pos - _edit.original_mouse_pos;5921real_t sensitivity = TRACKBALL_SENSITIVITY * EDSCALE;5922Vector2 rotation_input = motion_delta * sensitivity;59235924Transform3D cam_transform = to_camera_transform(cursor);5925Vector3 cam_right = cam_transform.basis.get_column(0).normalized();5926Vector3 cam_up = cam_transform.basis.get_column(1).normalized();5927Vector3 rotation_axis = cam_up * rotation_input.x + cam_right * rotation_input.y;59285929real_t rotation_angle = rotation_axis.length();5930if (rotation_angle > 0.0f) {5931rotation_axis /= rotation_angle;59325933bool snapping = _edit.snap || spatial_editor->is_snap_enabled();5934if (snapping) {5935double snap_step = spatial_editor->get_rotate_snap();5936double angle_deg = Math::rad_to_deg(rotation_angle);5937angle_deg = Math::snapped(angle_deg, snap_step);5938rotation_angle = Math::deg_to_rad(angle_deg);5939}59405941double angle_deg = Math::rad_to_deg(rotation_angle);5942set_message(vformat(TTR("Rotating %s degrees."), String::num(angle_deg, 2)));59435944apply_transform(rotation_axis, rotation_angle);5945}5946break;5947}59485949Vector3 local_axis;5950Vector3 global_axis;5951switch (_edit.plane) {5952case TRANSFORM_VIEW:5953local_axis = _edit.view_axis_local;5954global_axis = _get_camera_normal();5955break;5956case TRANSFORM_X_AXIS:5957local_axis = Vector3(1, 0, 0);5958break;5959case TRANSFORM_Y_AXIS:5960local_axis = Vector3(0, 1, 0);5961break;5962case TRANSFORM_Z_AXIS:5963local_axis = Vector3(0, 0, 1);5964break;5965case TRANSFORM_YZ:5966case TRANSFORM_XZ:5967case TRANSFORM_XY:5968break;5969}59705971if (_edit.plane != TRANSFORM_VIEW) {5972global_axis = spatial_editor->get_gizmo_transform().basis.xform(local_axis).normalized();5973}59745975Vector3 intersection;5976if (!plane.intersects_ray(ray_pos, ray, &intersection)) {5977break;5978}59795980Vector3 click;5981if (!plane.intersects_ray(_edit.click_ray_pos, _edit.click_ray, &click)) {5982break;5983}59845985Vector3 current_rotation_vector = (intersection - _edit.center).normalized();59865987if (_edit.initial_click_vector == Vector3()) {5988Plane rotation_plane(global_axis, _edit.center);5989Vector3 click_on_rotation_plane;5990if (rotation_plane.intersects_ray(_edit.click_ray_pos, _edit.click_ray, &click_on_rotation_plane)) {5991_edit.initial_click_vector = (click_on_rotation_plane - _edit.center).normalized();5992} else {5993_edit.initial_click_vector = (click - _edit.center).normalized();5994}5995_edit.previous_rotation_vector = current_rotation_vector;5996_edit.accumulated_rotation_angle = 0.0;5997_edit.display_rotation_angle = 0.0;5998}59996000static const float orthogonal_threshold = Math::cos(Math::deg_to_rad(85.0f));6001bool axis_is_orthogonal = Math::abs(plane.normal.dot(global_axis)) < orthogonal_threshold;60026003double angle = 0.0f;6004if (axis_is_orthogonal) {6005_edit.show_rotation_line = false;6006Vector3 projection_axis = plane.normal.cross(global_axis);6007Vector3 delta = intersection - click;6008float projection = delta.dot(projection_axis);6009angle = (projection * (Math::PI / 2.0f)) / (gizmo_scale * GIZMO_CIRCLE_SIZE);6010} else {6011_edit.show_rotation_line = true;6012Vector3 click_axis = (click - _edit.center).normalized();6013angle = click_axis.signed_angle_to(current_rotation_vector, global_axis);6014}60156016if (_edit.previous_rotation_vector != Vector3()) {6017double delta_angle = _edit.previous_rotation_vector.signed_angle_to(current_rotation_vector, global_axis);6018_edit.accumulated_rotation_angle += delta_angle;6019}6020_edit.previous_rotation_vector = current_rotation_vector;60216022bool snapping = _edit.snap || spatial_editor->is_snap_enabled();6023if (snapping) {6024snap = spatial_editor->get_rotate_snap();6025_edit.display_rotation_angle = Math::deg_to_rad(Math::snapped(Math::rad_to_deg(_edit.accumulated_rotation_angle), snap));6026} else {6027_edit.display_rotation_angle = _edit.accumulated_rotation_angle;6028}6029angle = Math::snapped(Math::rad_to_deg(angle), snap);6030set_message(vformat(TTR("Rotating %s degrees."), String::num(angle, snap_step_decimals)));6031angle = Math::deg_to_rad(angle);60326033Vector3 compute_axis = local_coords ? local_axis : global_axis;6034apply_transform(compute_axis, angle);6035} break;6036default: {6037}6038}6039}60406041void Node3DEditorViewport::update_transform_numeric() {6042Vector3 motion;6043switch (_edit.plane) {6044case TRANSFORM_VIEW: {6045switch (_edit.mode) {6046case TRANSFORM_TRANSLATE:6047motion = Vector3(1, 0, 0);6048break;6049case TRANSFORM_ROTATE:6050motion = _edit.view_axis_local;6051break;6052case TRANSFORM_SCALE:6053motion = Vector3(1, 1, 1);6054break;6055case TRANSFORM_NONE:6056ERR_FAIL_MSG("_edit.mode cannot be TRANSFORM_NONE in update_transform_numeric.");6057}6058break;6059}6060case TRANSFORM_X_AXIS:6061motion = Vector3(1, 0, 0);6062break;6063case TRANSFORM_Y_AXIS:6064motion = Vector3(0, 1, 0);6065break;6066case TRANSFORM_Z_AXIS:6067motion = Vector3(0, 0, 1);6068break;6069case TRANSFORM_XY:6070motion = Vector3(1, 1, 0);6071break;6072case TRANSFORM_XZ:6073motion = Vector3(1, 0, 1);6074break;6075case TRANSFORM_YZ:6076motion = Vector3(0, 1, 1);6077break;6078}60796080double value = _edit.numeric_input * (_edit.numeric_negate ? -1 : 1);6081double extra = 0.0;6082switch (_edit.mode) {6083case TRANSFORM_TRANSLATE:6084motion *= value;6085set_message(vformat(TTR("Translating %s."), motion));6086break;6087case TRANSFORM_ROTATE:6088extra = Math::deg_to_rad(value);6089set_message(vformat(TTR("Rotating %f degrees."), value));6090break;6091case TRANSFORM_SCALE:6092// To halve the size of an object in Blender, you scale it by 0.5.6093// Doing the same in Godot is considered scaling it by -0.5.6094motion *= (value - 1.0);6095set_message(vformat(TTR("Scaling %s."), motion));6096break;6097case TRANSFORM_NONE:6098ERR_FAIL_MSG("_edit.mode cannot be TRANSFORM_NONE in update_transform_numeric.");6099}61006101apply_transform(motion, extra);6102}61036104// Perform cleanup after a transform operation is committed or canceled.6105void Node3DEditorViewport::finish_transform() {6106_edit.mode = TRANSFORM_NONE;6107_edit.instant = false;6108_edit.numeric_input = 0;6109_edit.numeric_next_decimal = 0;6110_edit.numeric_negate = false;6111_edit.is_trackball = false;6112_edit.initial_click_vector = Vector3();6113_edit.previous_rotation_vector = Vector3();6114_edit.accumulated_rotation_angle = 0.0;6115_edit.display_rotation_angle = 0.0;6116_edit.gizmo_initiated = false;6117spatial_editor->set_local_coords_enabled(_edit.original_local);6118spatial_editor->update_transform_gizmo();6119surface->queue_redraw();6120set_process_input(false);6121clicked = ObjectID();6122}61236124// Register a shortcut and also add it as an input action with the same events.6125void Node3DEditorViewport::register_shortcut_action(const String &p_path, const String &p_name, Key p_keycode, bool p_physical) {6126Ref<Shortcut> sc = ED_SHORTCUT(p_path, p_name, p_keycode, p_physical);6127shortcut_changed_callback(sc, p_path);6128// Connect to the change event on the shortcut so the input binding can be updated.6129sc->connect_changed(callable_mp(this, &Node3DEditorViewport::shortcut_changed_callback).bind(sc, p_path));6130}61316132// Update the action in the InputMap to the provided shortcut events.6133void Node3DEditorViewport::shortcut_changed_callback(const Ref<Shortcut> p_shortcut, const String &p_shortcut_path) {6134InputMap *im = InputMap::get_singleton();6135if (im->has_action(p_shortcut_path)) {6136im->action_erase_events(p_shortcut_path);6137} else {6138im->add_action(p_shortcut_path);6139}61406141for (int i = 0; i < p_shortcut->get_events().size(); i++) {6142im->action_add_event(p_shortcut_path, p_shortcut->get_events()[i]);6143}6144}61456146void Node3DEditorViewport::_set_lock_view_rotation(bool p_lock_rotation) {6147lock_rotation = p_lock_rotation;6148int idx = view_display_menu->get_popup()->get_item_index(VIEW_LOCK_ROTATION);6149view_display_menu->get_popup()->set_item_checked(idx, p_lock_rotation);6150if (p_lock_rotation) {6151locked_label->show();6152} else {6153locked_label->hide();6154}6155}61566157void 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) {6158display_submenu->add_radio_check_item(p_name, p_value);6159Array item_data = { p_rendering_methods, p_tooltip };6160display_submenu->set_item_metadata(-1, item_data); // Tooltip is assigned in NOTIFICATION_TRANSLATION_CHANGED.6161}61626163Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p_index) {6164cpu_time_history_index = 0;6165gpu_time_history_index = 0;61666167_edit.mode = TRANSFORM_NONE;6168_edit.plane = TRANSFORM_VIEW;6169_edit.snap = true;6170_edit.show_rotation_line = true;6171_edit.instant = false;6172_edit.gizmo_handle = -1;6173_edit.gizmo_handle_secondary = false;61746175index = p_index;6176editor_selection = EditorNode::get_singleton()->get_editor_selection();61776178orthogonal = false;6179auto_orthogonal = false;6180lock_rotation = false;6181message_time = 0;6182zoom_indicator_delay = 0.0;61836184spatial_editor = p_spatial_editor;6185SubViewportContainer *c = memnew(SubViewportContainer);6186subviewport_container = c;6187c->set_stretch(true);6188add_child(c);6189c->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);6190viewport = memnew(SubViewport);6191viewport->set_disable_input(true);61926193c->add_child(viewport);6194surface = memnew(Control);6195SET_DRAG_FORWARDING_CD(surface, Node3DEditorViewport);6196add_child(surface);6197surface->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);6198surface->set_clip_contents(true);6199camera = memnew(Camera3D);6200camera->set_disable_gizmos(true);6201camera->set_cull_mask(((1 << 20) - 1) | (1 << (GIZMO_BASE_LAYER + p_index)) | (1 << GIZMO_EDIT_LAYER) | (1 << GIZMO_GRID_LAYER) | (1 << MISC_TOOL_LAYER));6202viewport->add_child(camera);6203camera->make_current();6204surface->set_focus_mode(FOCUS_ALL);62056206VBoxContainer *vbox = memnew(VBoxContainer);6207surface->add_child(vbox);6208vbox->set_offset(SIDE_LEFT, 10 * EDSCALE);6209vbox->set_offset(SIDE_TOP, 10 * EDSCALE);62106211HBoxContainer *hbox = memnew(HBoxContainer);6212vbox->add_child(hbox);62136214view_display_menu = memnew(MenuButton);6215view_display_menu->set_flat(false);6216view_display_menu->set_h_size_flags(0);6217view_display_menu->set_shortcut_context(this);6218view_display_menu->set_accessibility_name(TTRC("View"));6219view_display_menu->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);6220view_display_menu->get_popup()->set_auto_translate_mode(AUTO_TRANSLATE_MODE_ALWAYS);6221hbox->add_child(view_display_menu);62226223view_display_menu->get_popup()->set_hide_on_checkable_item_selection(false);62246225view_display_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/top_view"), VIEW_TOP);6226view_display_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/bottom_view"), VIEW_BOTTOM);6227view_display_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/left_view"), VIEW_LEFT);6228view_display_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/right_view"), VIEW_RIGHT);6229view_display_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/front_view"), VIEW_FRONT);6230view_display_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/rear_view"), VIEW_REAR);6231view_display_menu->get_popup()->add_separator();6232view_display_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/switch_perspective_orthogonal"), VIEW_SWITCH_PERSPECTIVE_ORTHOGONAL);6233view_display_menu->get_popup()->add_radio_check_item(TTRC("Perspective"), VIEW_PERSPECTIVE);6234view_display_menu->get_popup()->add_radio_check_item(TTRC("Orthogonal"), VIEW_ORTHOGONAL);6235view_display_menu->get_popup()->set_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_PERSPECTIVE), true);6236view_display_menu->get_popup()->add_check_item(TTRC("Auto Orthogonal Enabled"), VIEW_AUTO_ORTHOGONAL);6237view_display_menu->get_popup()->set_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_AUTO_ORTHOGONAL), true);6238view_display_menu->get_popup()->add_separator();6239view_display_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_lock_rotation", TTRC("Lock View Rotation")), VIEW_LOCK_ROTATION);6240view_display_menu->get_popup()->add_separator();6241// TRANSLATORS: "Normal" as in "normal life", not "normal vector".6242view_display_menu->get_popup()->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_normal", TTRC("Display Normal")), VIEW_DISPLAY_NORMAL);6243view_display_menu->get_popup()->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_wireframe", TTRC("Display Wireframe")), VIEW_DISPLAY_WIREFRAME);6244view_display_menu->get_popup()->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_overdraw", TTRC("Display Overdraw")), VIEW_DISPLAY_OVERDRAW);6245view_display_menu->get_popup()->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_lighting", TTRC("Display Lighting")), VIEW_DISPLAY_LIGHTING);6246view_display_menu->get_popup()->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_unshaded", TTRC("Display Unshaded")), VIEW_DISPLAY_UNSHADED);6247view_display_menu->get_popup()->set_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_DISPLAY_NORMAL), true);62486249display_submenu = memnew(PopupMenu);6250display_submenu->set_hide_on_checkable_item_selection(false);6251_add_advanced_debug_draw_mode_item(display_submenu, TTRC("Directional Shadow Splits"), VIEW_DISPLAY_DEBUG_PSSM_SPLITS, SupportedRenderingMethods::FORWARD_PLUS_MOBILE,6252TTRC("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)"));6253display_submenu->add_separator();6254// TRANSLATORS: "Normal" as in "normal vector", not "normal life".6255_add_advanced_debug_draw_mode_item(display_submenu, TTRC("Normal Buffer"), VIEW_DISPLAY_NORMAL_BUFFER, SupportedRenderingMethods::FORWARD_PLUS);6256display_submenu->add_separator();6257_add_advanced_debug_draw_mode_item(display_submenu, TTRC("Shadow Atlas"), VIEW_DISPLAY_DEBUG_SHADOW_ATLAS, SupportedRenderingMethods::ALL,6258TTRC("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."));6259_add_advanced_debug_draw_mode_item(display_submenu, TTRC("Directional Shadow Map"), VIEW_DISPLAY_DEBUG_DIRECTIONAL_SHADOW_ATLAS, SupportedRenderingMethods::ALL,6260TTRC("Displays the shadow map used for directional shadow mapping.\nRequires a visible DirectionalLight3D node with shadows enabled to have a visible effect."));6261display_submenu->add_separator();6262_add_advanced_debug_draw_mode_item(display_submenu, TTRC("Decal Atlas"), VIEW_DISPLAY_DEBUG_DECAL_ATLAS, SupportedRenderingMethods::FORWARD_PLUS_MOBILE);6263display_submenu->add_separator();6264_add_advanced_debug_draw_mode_item(display_submenu, TTRC("VoxelGI Lighting"), VIEW_DISPLAY_DEBUG_VOXEL_GI_LIGHTING, SupportedRenderingMethods::FORWARD_PLUS,6265TTRC("Requires a visible VoxelGI node that has been baked to have a visible effect."));6266_add_advanced_debug_draw_mode_item(display_submenu, TTRC("VoxelGI Albedo"), VIEW_DISPLAY_DEBUG_VOXEL_GI_ALBEDO, SupportedRenderingMethods::FORWARD_PLUS,6267TTRC("Requires a visible VoxelGI node that has been baked to have a visible effect."));6268_add_advanced_debug_draw_mode_item(display_submenu, TTRC("VoxelGI Emission"), VIEW_DISPLAY_DEBUG_VOXEL_GI_EMISSION, SupportedRenderingMethods::FORWARD_PLUS,6269TTRC("Requires a visible VoxelGI node that has been baked to have a visible effect."));6270display_submenu->add_separator();6271_add_advanced_debug_draw_mode_item(display_submenu, TTRC("SDFGI Cascades"), VIEW_DISPLAY_DEBUG_SDFGI, SupportedRenderingMethods::FORWARD_PLUS,6272TTRC("Requires SDFGI to be enabled in Environment to have a visible effect."));6273_add_advanced_debug_draw_mode_item(display_submenu, TTRC("SDFGI Probes"), VIEW_DISPLAY_DEBUG_SDFGI_PROBES, SupportedRenderingMethods::FORWARD_PLUS,6274TTRC("Left-click a SDFGI probe to display its occlusion information (white = not occluded, red = fully occluded).\nRequires SDFGI to be enabled in Environment to have a visible effect."));6275display_submenu->add_separator();6276_add_advanced_debug_draw_mode_item(display_submenu, TTRC("Scene Luminance"), VIEW_DISPLAY_DEBUG_SCENE_LUMINANCE, SupportedRenderingMethods::FORWARD_PLUS_MOBILE,6277TTRC("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."));6278display_submenu->add_separator();6279_add_advanced_debug_draw_mode_item(display_submenu, TTRC("SSAO"), VIEW_DISPLAY_DEBUG_SSAO, SupportedRenderingMethods::FORWARD_PLUS,6280TTRC("Displays the screen-space ambient occlusion buffer. Requires SSAO to be enabled in Environment to have a visible effect."));6281_add_advanced_debug_draw_mode_item(display_submenu, TTRC("SSIL"), VIEW_DISPLAY_DEBUG_SSIL, SupportedRenderingMethods::FORWARD_PLUS,6282TTRC("Displays the screen-space indirect lighting buffer. Requires SSIL to be enabled in Environment to have a visible effect."));6283display_submenu->add_separator();6284_add_advanced_debug_draw_mode_item(display_submenu, TTRC("VoxelGI/SDFGI Buffer"), VIEW_DISPLAY_DEBUG_GI_BUFFER, SupportedRenderingMethods::FORWARD_PLUS,6285TTRC("Requires SDFGI or VoxelGI to be enabled to have a visible effect."));6286display_submenu->add_separator();6287_add_advanced_debug_draw_mode_item(display_submenu, TTRC("Disable Mesh LOD"), VIEW_DISPLAY_DEBUG_DISABLE_LOD, SupportedRenderingMethods::ALL,6288TTRC("Renders all meshes with their highest level of detail regardless of their distance from the camera."));6289display_submenu->add_separator();6290_add_advanced_debug_draw_mode_item(display_submenu, TTRC("OmniLight3D Cluster"), VIEW_DISPLAY_DEBUG_CLUSTER_OMNI_LIGHTS, SupportedRenderingMethods::FORWARD_PLUS,6291TTRC("Highlights tiles of pixels that are affected by at least one OmniLight3D."));6292_add_advanced_debug_draw_mode_item(display_submenu, TTRC("SpotLight3D Cluster"), VIEW_DISPLAY_DEBUG_CLUSTER_SPOT_LIGHTS, SupportedRenderingMethods::FORWARD_PLUS,6293TTRC("Highlights tiles of pixels that are affected by at least one SpotLight3D."));6294_add_advanced_debug_draw_mode_item(display_submenu, TTRC("Decal Cluster"), VIEW_DISPLAY_DEBUG_CLUSTER_DECALS, SupportedRenderingMethods::FORWARD_PLUS,6295TTRC("Highlights tiles of pixels that are affected by at least one Decal."));6296_add_advanced_debug_draw_mode_item(display_submenu, TTRC("ReflectionProbe Cluster"), VIEW_DISPLAY_DEBUG_CLUSTER_REFLECTION_PROBES, SupportedRenderingMethods::FORWARD_PLUS,6297TTRC("Highlights tiles of pixels that are affected by at least one ReflectionProbe."));6298_add_advanced_debug_draw_mode_item(display_submenu, TTRC("Occlusion Culling Buffer"), VIEW_DISPLAY_DEBUG_OCCLUDERS, SupportedRenderingMethods::FORWARD_PLUS_MOBILE,6299TTRC("Represents occluders with black pixels. Requires occlusion culling to be enabled to have a visible effect."));6300_add_advanced_debug_draw_mode_item(display_submenu, TTRC("Motion Vectors"), VIEW_DISPLAY_MOTION_VECTORS, SupportedRenderingMethods::FORWARD_PLUS,6301TTRC("Represents motion vectors with colored lines in the direction of motion. Gray dots represent areas with no per-pixel motion."));6302_add_advanced_debug_draw_mode_item(display_submenu, TTRC("Internal Buffer"), VIEW_DISPLAY_INTERNAL_BUFFER, SupportedRenderingMethods::FORWARD_PLUS_MOBILE,6303TTRC("Shows the scene rendered in linear colorspace before any tonemapping or post-processing."));6304view_display_menu->get_popup()->add_submenu_node_item(TTRC("Display Advanced..."), display_submenu, VIEW_DISPLAY_ADVANCED);63056306view_display_menu->get_popup()->add_separator();6307view_display_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_environment", TTRC("View Environment")), VIEW_ENVIRONMENT);6308view_display_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_gizmos", TTRC("View Gizmos")), VIEW_GIZMOS);6309view_display_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_transform_gizmo", TTRC("View Transform Gizmo")), VIEW_TRANSFORM_GIZMO);6310view_display_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_grid_lines", TTRC("View Grid")), VIEW_GRID);6311view_display_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_information", TTRC("View Information")), VIEW_INFORMATION);6312view_display_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_fps", TTRC("View Frame Time")), VIEW_FRAME_TIME);6313view_display_menu->get_popup()->set_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_ENVIRONMENT), true);6314view_display_menu->get_popup()->add_separator();6315view_display_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_half_resolution", TTRC("Half Resolution")), VIEW_HALF_RESOLUTION);6316view_display_menu->get_popup()->add_separator();6317view_display_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_audio_listener", TTRC("Audio Listener")), VIEW_AUDIO_LISTENER);6318view_display_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_audio_doppler", TTRC("Enable Doppler")), VIEW_AUDIO_DOPPLER);6319view_display_menu->get_popup()->set_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_GIZMOS), true);6320view_display_menu->get_popup()->set_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_TRANSFORM_GIZMO), true);6321view_display_menu->get_popup()->set_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_GRID), true);63226323view_display_menu->get_popup()->add_separator();6324view_display_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_cinematic_preview", TTRC("Cinematic Preview")), VIEW_CINEMATIC_PREVIEW);63256326view_display_menu->get_popup()->add_separator();6327view_display_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/focus_origin"), VIEW_CENTER_TO_ORIGIN);6328view_display_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/focus_selection"), VIEW_CENTER_TO_SELECTION);6329view_display_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/align_transform_with_view"), VIEW_ALIGN_TRANSFORM_WITH_VIEW);6330view_display_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/align_rotation_with_view"), VIEW_ALIGN_ROTATION_WITH_VIEW);6331view_display_menu->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &Node3DEditorViewport::_menu_option));6332display_submenu->connect(SceneStringName(id_pressed), callable_mp(this, &Node3DEditorViewport::_menu_option));6333view_display_menu->set_disable_shortcuts(true);63346335// Registering with Key::NONE intentionally creates an empty Array.6336register_shortcut_action("spatial_editor/viewport_orbit_modifier_1", TTRC("Viewport Orbit Modifier 1"), Key::NONE);6337register_shortcut_action("spatial_editor/viewport_orbit_modifier_2", TTRC("Viewport Orbit Modifier 2"), Key::NONE);6338register_shortcut_action("spatial_editor/viewport_orbit_snap_modifier_1", TTRC("Viewport Orbit Snap Modifier 1"), Key::ALT);6339register_shortcut_action("spatial_editor/viewport_orbit_snap_modifier_2", TTRC("Viewport Orbit Snap Modifier 2"), Key::NONE);6340register_shortcut_action("spatial_editor/viewport_pan_modifier_1", TTRC("Viewport Pan Modifier 1"), Key::SHIFT);6341register_shortcut_action("spatial_editor/viewport_pan_modifier_2", TTRC("Viewport Pan Modifier 2"), Key::NONE);6342register_shortcut_action("spatial_editor/viewport_zoom_modifier_1", TTRC("Viewport Zoom Modifier 1"), Key::CTRL);6343register_shortcut_action("spatial_editor/viewport_zoom_modifier_2", TTRC("Viewport Zoom Modifier 2"), Key::NONE);63446345register_shortcut_action("spatial_editor/freelook_left", TTRC("Freelook Left"), Key::A, true);6346register_shortcut_action("spatial_editor/freelook_right", TTRC("Freelook Right"), Key::D, true);6347register_shortcut_action("spatial_editor/freelook_forward", TTRC("Freelook Forward"), Key::W, true);6348register_shortcut_action("spatial_editor/freelook_backwards", TTRC("Freelook Backwards"), Key::S, true);6349register_shortcut_action("spatial_editor/freelook_up", TTRC("Freelook Up"), Key::E, true);6350register_shortcut_action("spatial_editor/freelook_down", TTRC("Freelook Down"), Key::Q, true);6351register_shortcut_action("spatial_editor/freelook_speed_modifier", TTRC("Freelook Speed Modifier"), Key::SHIFT);6352register_shortcut_action("spatial_editor/freelook_slow_modifier", TTRC("Freelook Slow Modifier"), Key::ALT);63536354ED_SHORTCUT("spatial_editor/lock_transform_x", TTRC("Lock Transformation to X axis"), Key::X);6355ED_SHORTCUT("spatial_editor/lock_transform_y", TTRC("Lock Transformation to Y axis"), Key::Y);6356ED_SHORTCUT("spatial_editor/lock_transform_z", TTRC("Lock Transformation to Z axis"), Key::Z);6357ED_SHORTCUT("spatial_editor/lock_transform_yz", TTRC("Lock Transformation to YZ plane"), KeyModifierMask::SHIFT | Key::X);6358ED_SHORTCUT("spatial_editor/lock_transform_xz", TTRC("Lock Transformation to XZ plane"), KeyModifierMask::SHIFT | Key::Y);6359ED_SHORTCUT("spatial_editor/lock_transform_xy", TTRC("Lock Transformation to XY plane"), KeyModifierMask::SHIFT | Key::Z);6360ED_SHORTCUT("spatial_editor/cancel_transform", TTRC("Cancel Transformation"), Key::ESCAPE);6361ED_SHORTCUT("spatial_editor/instant_translate", TTRC("Begin Translate Transformation"));6362ED_SHORTCUT("spatial_editor/instant_rotate", TTRC("Begin Rotate Transformation"));6363ED_SHORTCUT("spatial_editor/instant_scale", TTRC("Begin Scale Transformation"));6364ED_SHORTCUT("spatial_editor/collision_reposition", TTRC("Reposition Using Collisions"), KeyModifierMask::SHIFT | Key::G);6365ED_SHORTCUT("spatial_editor/reset_transform_position", TTRC("Reset Position"), KeyModifierMask::ALT + Key::W);6366ED_SHORTCUT("spatial_editor/reset_transform_rotation", TTRC("Reset Rotation"), KeyModifierMask::ALT + Key::E);6367ED_SHORTCUT("spatial_editor/reset_transform_scale", TTRC("Reset Scale"), KeyModifierMask::ALT + Key::R);63686369translation_preview_button = memnew(EditorTranslationPreviewButton);6370hbox->add_child(translation_preview_button);63716372preview_camera = memnew(CheckBox);6373preview_camera->set_text(TTRC("Preview"));6374// Using Control even on macOS to avoid conflict with Quick Open shortcut.6375preview_camera->set_shortcut(ED_SHORTCUT("spatial_editor/toggle_camera_preview", TTRC("Toggle Camera Preview"), KeyModifierMask::CTRL | Key::P));6376vbox->add_child(preview_camera);6377preview_camera->set_h_size_flags(0);6378preview_camera->hide();6379preview_camera->connect(SceneStringName(toggled), callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview));6380previewing = nullptr;6381gizmo_scale = 1.0;63826383preview_node = nullptr;63846385bottom_center_vbox = memnew(VBoxContainer);6386bottom_center_vbox->set_anchors_preset(LayoutPreset::PRESET_CENTER);6387bottom_center_vbox->set_anchor_and_offset(SIDE_TOP, ANCHOR_END, -20 * EDSCALE);6388bottom_center_vbox->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, -10 * EDSCALE);6389bottom_center_vbox->set_h_grow_direction(GROW_DIRECTION_BOTH);6390bottom_center_vbox->set_v_grow_direction(GROW_DIRECTION_BEGIN);6391surface->add_child(bottom_center_vbox);63926393info_panel = memnew(PanelContainer);6394info_panel->set_anchor_and_offset(SIDE_LEFT, ANCHOR_END, -90 * EDSCALE);6395info_panel->set_anchor_and_offset(SIDE_TOP, ANCHOR_END, -90 * EDSCALE);6396info_panel->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_END, -10 * EDSCALE);6397info_panel->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, -10 * EDSCALE);6398info_panel->set_h_grow_direction(GROW_DIRECTION_BEGIN);6399info_panel->set_v_grow_direction(GROW_DIRECTION_BEGIN);6400info_panel->set_mouse_filter(MOUSE_FILTER_IGNORE);6401surface->add_child(info_panel);6402info_panel->hide();64036404info_label = memnew(Label);6405info_label->set_focus_mode(FOCUS_ACCESSIBILITY);6406info_panel->add_child(info_label);64076408cinema_label = memnew(Label);6409cinema_label->set_anchor_and_offset(SIDE_TOP, ANCHOR_BEGIN, 10 * EDSCALE);6410cinema_label->set_h_grow_direction(GROW_DIRECTION_END);6411cinema_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);6412cinema_label->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);6413surface->add_child(cinema_label);6414cinema_label->set_text(TTRC("Cinematic Preview"));6415cinema_label->hide();6416previewing_cinema = false;64176418locked_label = memnew(Label);6419locked_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);6420locked_label->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);6421locked_label->set_h_size_flags(SIZE_SHRINK_CENTER);6422bottom_center_vbox->add_child(locked_label);6423locked_label->set_text(TTRC("View Rotation Locked"));6424locked_label->hide();64256426zoom_limit_label = memnew(Label);6427zoom_limit_label->set_text(TTRC(U"To zoom further, change the camera's clipping planes (View → Settings...)"));6428zoom_limit_label->set_name("ZoomLimitMessageLabel");6429zoom_limit_label->add_theme_color_override(SceneStringName(font_color), Color(1, 1, 1, 1));6430zoom_limit_label->hide();6431bottom_center_vbox->add_child(zoom_limit_label);64326433preview_material_label = memnew(Label);6434preview_material_label->set_anchors_and_offsets_preset(LayoutPreset::PRESET_BOTTOM_LEFT);6435preview_material_label->set_offset(Side::SIDE_TOP, -70 * EDSCALE);6436preview_material_label->set_text(TTRC("Overriding material..."));6437preview_material_label->add_theme_color_override(SceneStringName(font_color), Color(1, 1, 1, 1));6438preview_material_label->hide();6439surface->add_child(preview_material_label);64406441preview_material_label_desc = memnew(Label);6442preview_material_label_desc->set_focus_mode(FOCUS_ACCESSIBILITY);6443preview_material_label_desc->set_anchors_and_offsets_preset(LayoutPreset::PRESET_BOTTOM_LEFT);6444preview_material_label_desc->set_offset(Side::SIDE_TOP, -50 * EDSCALE);6445preview_material_label_desc->add_theme_color_override(SceneStringName(font_color), Color(0.8, 0.8, 0.8, 1));6446preview_material_label_desc->add_theme_constant_override("line_spacing", 0);6447preview_material_label_desc->hide();6448surface->add_child(preview_material_label_desc);64496450frame_time_gradient = memnew(Gradient);6451// The color is set when the theme changes.6452frame_time_gradient->add_point(0.5, Color());64536454top_right_vbox = memnew(VBoxContainer);6455top_right_vbox->add_theme_constant_override("separation", 10.0 * EDSCALE);6456top_right_vbox->set_anchors_and_offsets_preset(PRESET_TOP_RIGHT, PRESET_MODE_MINSIZE, 10.0 * EDSCALE);6457top_right_vbox->set_h_grow_direction(GROW_DIRECTION_BEGIN);64586459const int navigation_control_size = 150;64606461position_control = memnew(ViewportNavigationControl);6462position_control->set_navigation_mode(Node3DEditorViewport::NAVIGATION_MOVE);6463position_control->set_custom_minimum_size(Size2(navigation_control_size, navigation_control_size) * EDSCALE);6464position_control->set_h_size_flags(SIZE_SHRINK_END);6465position_control->set_anchor_and_offset(SIDE_LEFT, ANCHOR_BEGIN, 0);6466position_control->set_anchor_and_offset(SIDE_TOP, ANCHOR_END, -navigation_control_size * EDSCALE);6467position_control->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_BEGIN, navigation_control_size * EDSCALE);6468position_control->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, 0);6469position_control->set_viewport(this);6470surface->add_child(position_control);64716472look_control = memnew(ViewportNavigationControl);6473look_control->set_navigation_mode(Node3DEditorViewport::NAVIGATION_LOOK);6474look_control->set_custom_minimum_size(Size2(navigation_control_size, navigation_control_size) * EDSCALE);6475look_control->set_h_size_flags(SIZE_SHRINK_END);6476look_control->set_anchor_and_offset(SIDE_LEFT, ANCHOR_END, -navigation_control_size * EDSCALE);6477look_control->set_anchor_and_offset(SIDE_TOP, ANCHOR_END, -navigation_control_size * EDSCALE);6478look_control->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_END, 0);6479look_control->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, 0);6480look_control->set_viewport(this);6481surface->add_child(look_control);64826483rotation_control = memnew(ViewportRotationControl);6484rotation_control->set_custom_minimum_size(Size2(80, 80) * EDSCALE);6485rotation_control->set_h_size_flags(SIZE_SHRINK_END);6486rotation_control->set_viewport(this);6487rotation_control->set_focus_mode(FOCUS_CLICK);6488top_right_vbox->add_child(rotation_control);64896490frame_time_panel = memnew(PanelContainer);6491frame_time_panel->set_mouse_filter(MOUSE_FILTER_IGNORE);6492top_right_vbox->add_child(frame_time_panel);6493frame_time_panel->hide();64946495frame_time_vbox = memnew(VBoxContainer);6496frame_time_panel->add_child(frame_time_vbox);64976498// Individual Labels are used to allow coloring each label with its own color.6499cpu_time_label = memnew(Label);6500frame_time_vbox->add_child(cpu_time_label);65016502gpu_time_label = memnew(Label);6503frame_time_vbox->add_child(gpu_time_label);65046505fps_label = memnew(Label);6506frame_time_vbox->add_child(fps_label);65076508surface->add_child(top_right_vbox);65096510accept = nullptr;65116512freelook_active = false;6513freelook_speed = EDITOR_GET("editors/3d/freelook/freelook_base_speed");65146515selection_menu = memnew(PopupMenu);6516add_child(selection_menu);6517selection_menu->set_min_size(Size2(100, 0) * EDSCALE);6518selection_menu->connect(SceneStringName(id_pressed), callable_mp(this, &Node3DEditorViewport::_selection_result_pressed));6519selection_menu->connect("popup_hide", callable_mp(this, &Node3DEditorViewport::_selection_menu_hide));65206521if (p_index == 0) {6522view_display_menu->get_popup()->set_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_AUDIO_LISTENER), true);6523viewport->set_as_audio_listener_3d(true);6524}65256526ruler = memnew(Node);65276528ruler_start_point = memnew(Node3D);6529ruler_start_point->set_visible(false);65306531ruler_end_point = memnew(Node3D);6532ruler_end_point->set_visible(false);65336534ruler_material.instantiate();6535ruler_material->set_albedo(Color(1.0, 0.9, 0.0, 1.0));6536ruler_material->set_flag(BaseMaterial3D::FLAG_DISABLE_FOG, true);6537ruler_material->set_shading_mode(BaseMaterial3D::SHADING_MODE_UNSHADED);6538ruler_material->set_depth_draw_mode(BaseMaterial3D::DEPTH_DRAW_DISABLED);65396540ruler_material_xray.instantiate();6541ruler_material_xray->set_albedo(Color(1.0, 0.9, 0.0, 0.15));6542ruler_material_xray->set_flag(BaseMaterial3D::FLAG_DISABLE_FOG, true);6543ruler_material_xray->set_shading_mode(BaseMaterial3D::SHADING_MODE_UNSHADED);6544ruler_material_xray->set_flag(BaseMaterial3D::FLAG_DISABLE_DEPTH_TEST, true);6545ruler_material_xray->set_transparency(BaseMaterial3D::TRANSPARENCY_ALPHA);6546ruler_material_xray->set_render_priority(BaseMaterial3D::RENDER_PRIORITY_MAX);65476548geometry.instantiate();65496550ruler_line = memnew(MeshInstance3D);6551ruler_line->set_mesh(geometry);6552ruler_line->set_material_override(ruler_material);65536554ruler_line_xray = memnew(MeshInstance3D);6555ruler_line_xray->set_mesh(geometry);6556ruler_line_xray->set_material_override(ruler_material_xray);65576558ruler_label = memnew(Label);6559ruler_label->set_visible(false);65606561ruler->add_child(ruler_start_point);6562ruler->add_child(ruler_end_point);6563ruler->add_child(ruler_line);6564ruler->add_child(ruler_line_xray);65656566viewport->add_child(ruler_label);65676568view_type = VIEW_TYPE_USER;6569_update_name();6570}65716572Node3DEditorViewport::~Node3DEditorViewport() {6573memdelete(ruler);6574memdelete(frame_time_gradient);6575}65766577//////////////////////////////////////////////////////////////65786579void Node3DEditorViewportContainer::gui_input(const Ref<InputEvent> &p_event) {6580ERR_FAIL_COND(p_event.is_null());65816582if (_redirect_freelook_input(p_event)) {6583return;6584}65856586Ref<InputEventMouseButton> mb = p_event;65876588if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT) {6589if (mb->is_pressed()) {6590Vector2 size = get_size();65916592int h_sep = get_theme_constant(SNAME("separation"), SNAME("HSplitContainer"));6593int v_sep = get_theme_constant(SNAME("separation"), SNAME("VSplitContainer"));65946595int mid_w = size.width * ratio_h;6596int mid_h = size.height * ratio_v;65976598dragging_h = mb->get_position().x > (mid_w - h_sep / 2) && mb->get_position().x < (mid_w + h_sep / 2);6599dragging_v = mb->get_position().y > (mid_h - v_sep / 2) && mb->get_position().y < (mid_h + v_sep / 2);66006601drag_begin_pos = mb->get_position();6602drag_begin_ratio.x = ratio_h;6603drag_begin_ratio.y = ratio_v;66046605switch (view) {6606case VIEW_USE_1_VIEWPORT: {6607dragging_h = false;6608dragging_v = false;66096610} break;6611case VIEW_USE_2_VIEWPORTS: {6612dragging_h = false;66136614} break;6615case VIEW_USE_2_VIEWPORTS_ALT: {6616dragging_v = false;66176618} break;6619case VIEW_USE_3_VIEWPORTS:6620case VIEW_USE_3_VIEWPORTS_ALT:6621case VIEW_USE_4_VIEWPORTS: {6622// Do nothing.66236624} break;6625}6626} else {6627dragging_h = false;6628dragging_v = false;6629}6630}66316632Ref<InputEventMouseMotion> mm = p_event;66336634if (mm.is_valid()) {6635if (view == VIEW_USE_3_VIEWPORTS || view == VIEW_USE_3_VIEWPORTS_ALT || view == VIEW_USE_4_VIEWPORTS) {6636Vector2 size = get_size();66376638int h_sep = get_theme_constant(SNAME("separation"), SNAME("HSplitContainer"));6639int v_sep = get_theme_constant(SNAME("separation"), SNAME("VSplitContainer"));66406641int mid_w = size.width * ratio_h;6642int mid_h = size.height * ratio_v;66436644bool was_hovering_h = hovering_h;6645bool was_hovering_v = hovering_v;6646hovering_h = mm->get_position().x > (mid_w - h_sep / 2) && mm->get_position().x < (mid_w + h_sep / 2);6647hovering_v = mm->get_position().y > (mid_h - v_sep / 2) && mm->get_position().y < (mid_h + v_sep / 2);66486649if (was_hovering_h != hovering_h || was_hovering_v != hovering_v) {6650queue_redraw();6651}6652}66536654if (dragging_h) {6655real_t new_ratio = drag_begin_ratio.x + (mm->get_position().x - drag_begin_pos.x) / get_size().width;6656new_ratio = CLAMP(new_ratio, 40 / get_size().width, (get_size().width - 40) / get_size().width);6657ratio_h = new_ratio;6658queue_sort();6659queue_redraw();6660}6661if (dragging_v) {6662real_t new_ratio = drag_begin_ratio.y + (mm->get_position().y - drag_begin_pos.y) / get_size().height;6663new_ratio = CLAMP(new_ratio, 40 / get_size().height, (get_size().height - 40) / get_size().height);6664ratio_v = new_ratio;6665queue_sort();6666queue_redraw();6667}6668}6669}66706671void Node3DEditorViewportContainer::_notification(int p_what) {6672switch (p_what) {6673case NOTIFICATION_MOUSE_ENTER:6674case NOTIFICATION_MOUSE_EXIT: {6675mouseover = (p_what == NOTIFICATION_MOUSE_ENTER);6676queue_redraw();6677} break;66786679case NOTIFICATION_DRAW: {6680if (mouseover && Input::get_singleton()->get_mouse_mode() != Input::MouseMode::MOUSE_MODE_CAPTURED) {6681Ref<Texture2D> h_grabber = get_theme_icon(SNAME("grabber"), SNAME("HSplitContainer"));6682Ref<Texture2D> v_grabber = get_theme_icon(SNAME("grabber"), SNAME("VSplitContainer"));66836684Ref<Texture2D> hdiag_grabber = get_editor_theme_icon(SNAME("GuiViewportHdiagsplitter"));6685Ref<Texture2D> vdiag_grabber = get_editor_theme_icon(SNAME("GuiViewportVdiagsplitter"));6686Ref<Texture2D> vh_grabber = get_editor_theme_icon(SNAME("GuiViewportVhsplitter"));66876688Vector2 size = get_size();66896690int h_sep = get_theme_constant(SNAME("separation"), SNAME("HSplitContainer"));66916692int v_sep = get_theme_constant(SNAME("separation"), SNAME("VSplitContainer"));66936694int mid_w = size.width * ratio_h;6695int mid_h = size.height * ratio_v;66966697int size_left = mid_w - h_sep / 2;6698int size_bottom = size.height - mid_h - v_sep / 2;66996700switch (view) {6701case VIEW_USE_1_VIEWPORT: {6702// Nothing to show.67036704} break;6705case VIEW_USE_2_VIEWPORTS: {6706draw_texture(v_grabber, Vector2((size.width - v_grabber->get_width()) / 2, mid_h - v_grabber->get_height() / 2));6707set_default_cursor_shape(CURSOR_VSPLIT);67086709} break;6710case VIEW_USE_2_VIEWPORTS_ALT: {6711draw_texture(h_grabber, Vector2(mid_w - h_grabber->get_width() / 2, (size.height - h_grabber->get_height()) / 2));6712set_default_cursor_shape(CURSOR_HSPLIT);67136714} break;6715case VIEW_USE_3_VIEWPORTS: {6716if ((hovering_v && hovering_h && !dragging_v && !dragging_h) || (dragging_v && dragging_h)) {6717draw_texture(hdiag_grabber, Vector2(mid_w - hdiag_grabber->get_width() / 2, mid_h - v_grabber->get_height() / 4));6718set_default_cursor_shape(CURSOR_DRAG);6719} else if ((hovering_v && !dragging_h) || dragging_v) {6720draw_texture(v_grabber, Vector2((size.width - v_grabber->get_width()) / 2, mid_h - v_grabber->get_height() / 2));6721set_default_cursor_shape(CURSOR_VSPLIT);6722} else if (hovering_h || dragging_h) {6723draw_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));6724set_default_cursor_shape(CURSOR_HSPLIT);6725}67266727} break;6728case VIEW_USE_3_VIEWPORTS_ALT: {6729if ((hovering_v && hovering_h && !dragging_v && !dragging_h) || (dragging_v && dragging_h)) {6730draw_texture(vdiag_grabber, Vector2(mid_w - vdiag_grabber->get_width() + v_grabber->get_height() / 4, mid_h - vdiag_grabber->get_height() / 2));6731set_default_cursor_shape(CURSOR_DRAG);6732} else if ((hovering_v && !dragging_h) || dragging_v) {6733draw_texture(v_grabber, Vector2((size_left - v_grabber->get_width()) / 2, mid_h - v_grabber->get_height() / 2));6734set_default_cursor_shape(CURSOR_VSPLIT);6735} else if (hovering_h || dragging_h) {6736draw_texture(h_grabber, Vector2(mid_w - h_grabber->get_width() / 2, (size.height - h_grabber->get_height()) / 2));6737set_default_cursor_shape(CURSOR_HSPLIT);6738}67396740} break;6741case VIEW_USE_4_VIEWPORTS: {6742Vector2 half(mid_w, mid_h);6743if ((hovering_v && hovering_h && !dragging_v && !dragging_h) || (dragging_v && dragging_h)) {6744draw_texture(vh_grabber, half - vh_grabber->get_size() / 2.0);6745set_default_cursor_shape(CURSOR_DRAG);6746} else if ((hovering_v && !dragging_h) || dragging_v) {6747draw_texture(v_grabber, half - v_grabber->get_size() / 2.0);6748set_default_cursor_shape(CURSOR_VSPLIT);6749} else if (hovering_h || dragging_h) {6750draw_texture(h_grabber, half - h_grabber->get_size() / 2.0);6751set_default_cursor_shape(CURSOR_HSPLIT);6752}67536754} break;6755}6756}6757} break;67586759case NOTIFICATION_SORT_CHILDREN: {6760Node3DEditorViewport *viewports[4];6761int vc = 0;6762for (int i = 0; i < get_child_count(); i++) {6763viewports[vc] = Object::cast_to<Node3DEditorViewport>(get_child(i));6764if (viewports[vc]) {6765vc++;6766}6767}67686769ERR_FAIL_COND(vc != 4);67706771Size2 size = get_size();67726773if (size.x < 10 || size.y < 10) {6774for (int i = 0; i < 4; i++) {6775viewports[i]->hide();6776}6777return;6778}6779int h_sep = get_theme_constant(SNAME("separation"), SNAME("HSplitContainer"));67806781int v_sep = get_theme_constant(SNAME("separation"), SNAME("VSplitContainer"));67826783int mid_w = size.width * ratio_h;6784int mid_h = size.height * ratio_v;67856786int size_left = mid_w - h_sep / 2;6787int size_right = size.width - mid_w - h_sep / 2;67886789int size_top = mid_h - v_sep / 2;6790int size_bottom = size.height - mid_h - v_sep / 2;67916792switch (view) {6793case VIEW_USE_1_VIEWPORT: {6794viewports[0]->show();6795for (int i = 1; i < 4; i++) {6796viewports[i]->hide();6797}67986799fit_child_in_rect(viewports[0], Rect2(Vector2(), size));68006801} break;6802case VIEW_USE_2_VIEWPORTS: {6803for (int i = 0; i < 4; i++) {6804if (i == 1 || i == 3) {6805viewports[i]->hide();6806} else {6807viewports[i]->show();6808}6809}68106811fit_child_in_rect(viewports[0], Rect2(Vector2(), Vector2(size.width, size_top)));6812fit_child_in_rect(viewports[2], Rect2(Vector2(0, mid_h + v_sep / 2), Vector2(size.width, size_bottom)));68136814} break;6815case VIEW_USE_2_VIEWPORTS_ALT: {6816for (int i = 0; i < 4; i++) {6817if (i == 1 || i == 3) {6818viewports[i]->hide();6819} else {6820viewports[i]->show();6821}6822}6823fit_child_in_rect(viewports[0], Rect2(Vector2(), Vector2(size_left, size.height)));6824fit_child_in_rect(viewports[2], Rect2(Vector2(mid_w + h_sep / 2, 0), Vector2(size_right, size.height)));68256826} break;6827case VIEW_USE_3_VIEWPORTS: {6828for (int i = 0; i < 4; i++) {6829if (i == 1) {6830viewports[i]->hide();6831} else {6832viewports[i]->show();6833}6834}68356836fit_child_in_rect(viewports[0], Rect2(Vector2(), Vector2(size.width, size_top)));6837fit_child_in_rect(viewports[2], Rect2(Vector2(0, mid_h + v_sep / 2), Vector2(size_left, size_bottom)));6838fit_child_in_rect(viewports[3], Rect2(Vector2(mid_w + h_sep / 2, mid_h + v_sep / 2), Vector2(size_right, size_bottom)));68396840} break;6841case VIEW_USE_3_VIEWPORTS_ALT: {6842for (int i = 0; i < 4; i++) {6843if (i == 1) {6844viewports[i]->hide();6845} else {6846viewports[i]->show();6847}6848}68496850fit_child_in_rect(viewports[0], Rect2(Vector2(), Vector2(size_left, size_top)));6851fit_child_in_rect(viewports[2], Rect2(Vector2(0, mid_h + v_sep / 2), Vector2(size_left, size_bottom)));6852fit_child_in_rect(viewports[3], Rect2(Vector2(mid_w + h_sep / 2, 0), Vector2(size_right, size.height)));68536854} break;6855case VIEW_USE_4_VIEWPORTS: {6856for (int i = 0; i < 4; i++) {6857viewports[i]->show();6858}68596860fit_child_in_rect(viewports[0], Rect2(Vector2(), Vector2(size_left, size_top)));6861fit_child_in_rect(viewports[1], Rect2(Vector2(mid_w + h_sep / 2, 0), Vector2(size_right, size_top)));6862fit_child_in_rect(viewports[2], Rect2(Vector2(0, mid_h + v_sep / 2), Vector2(size_left, size_bottom)));6863fit_child_in_rect(viewports[3], Rect2(Vector2(mid_w + h_sep / 2, mid_h + v_sep / 2), Vector2(size_right, size_bottom)));68646865} break;6866}6867} break;6868}6869}68706871void Node3DEditorViewportContainer::set_view(View p_view) {6872view = p_view;6873queue_sort();6874}68756876Node3DEditorViewportContainer::View Node3DEditorViewportContainer::get_view() {6877return view;6878}68796880Node3DEditorViewportContainer::Node3DEditorViewportContainer() {6881set_clip_contents(true);6882view = VIEW_USE_1_VIEWPORT;6883mouseover = false;6884ratio_h = 0.5;6885ratio_v = 0.5;6886hovering_v = false;6887hovering_h = false;6888dragging_v = false;6889dragging_h = false;6890}68916892///////////////////////////////////////////////////////////////////68936894Node3DEditor *Node3DEditor::singleton = nullptr;68956896Node3DEditorSelectedItem::~Node3DEditorSelectedItem() {6897ERR_FAIL_NULL(RenderingServer::get_singleton());6898if (sbox_instance.is_valid()) {6899RenderingServer::get_singleton()->free_rid(sbox_instance);6900}6901if (sbox_instance_offset.is_valid()) {6902RenderingServer::get_singleton()->free_rid(sbox_instance_offset);6903}6904if (sbox_instance_xray.is_valid()) {6905RenderingServer::get_singleton()->free_rid(sbox_instance_xray);6906}6907if (sbox_instance_xray_offset.is_valid()) {6908RenderingServer::get_singleton()->free_rid(sbox_instance_xray_offset);6909}6910}69116912void Node3DEditor::select_gizmo_highlight_axis(int p_axis) {6913for (int i = 0; i < 3; i++) {6914move_gizmo[i]->surface_set_material(0, i == p_axis ? gizmo_color_hl[i] : gizmo_color[i]);6915move_plane_gizmo[i]->surface_set_material(0, (i + 6) == p_axis ? plane_gizmo_color_hl[i] : plane_gizmo_color[i]);6916scale_gizmo[i]->surface_set_material(0, (i + 9) == p_axis ? gizmo_color_hl[i] : gizmo_color[i]);6917scale_plane_gizmo[i]->surface_set_material(0, (i + 12) == p_axis ? plane_gizmo_color_hl[i] : plane_gizmo_color[i]);6918}69196920for (int i = 0; i < 4; i++) {6921bool highlight;6922if (i == 3) {6923highlight = (p_axis == GIZMO_HIGHLIGHT_AXIS_VIEW_ROTATION);6924} else {6925highlight = (i + 3) == p_axis;6926}6927rotate_gizmo[i]->surface_set_material(0, highlight ? rotate_gizmo_color_hl[i] : rotate_gizmo_color[i]);6928}69296930bool highlight_trackball = (p_axis == GIZMO_HIGHLIGHT_AXIS_TRACKBALL);6931trackball_sphere_gizmo->surface_set_material(0, highlight_trackball ? trackball_sphere_material_hl : trackball_sphere_material);6932}69336934void Node3DEditor::update_transform_gizmo() {6935int count = 0;6936bool local_gizmo_coords = are_local_coords_enabled();69376938Vector3 gizmo_center;6939Basis gizmo_basis;69406941Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr;69426943if (se && se->gizmo.is_valid()) {6944for (const KeyValue<int, Transform3D> &E : se->subgizmos) {6945Transform3D xf = se->sp->get_global_transform() * se->gizmo->get_subgizmo_transform(E.key);6946if (!xf.is_finite()) {6947continue;6948}6949gizmo_center += xf.origin;6950if ((unsigned int)count == se->subgizmos.size() - 1 && local_gizmo_coords) {6951gizmo_basis = xf.basis;6952}6953count++;6954}6955} else {6956const List<Node *> &selection = editor_selection->get_top_selected_node_list();6957for (Node *E : selection) {6958Node3D *sp = Object::cast_to<Node3D>(E);6959if (!sp) {6960continue;6961}69626963if (sp->has_meta("_edit_lock_")) {6964continue;6965}69666967Node3DEditorSelectedItem *sel_item = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);6968if (!sel_item) {6969continue;6970}69716972Transform3D xf = sel_item->sp->get_global_transform();6973if (!xf.is_finite()) {6974continue;6975}6976gizmo_center += xf.origin;6977if (count == selection.size() - 1 && local_gizmo_coords) {6978gizmo_basis = xf.basis;6979}6980count++;6981}6982}69836984gizmo.visible = count > 0;6985gizmo.transform.origin = (count > 0) ? gizmo_center / count : Vector3();6986gizmo.transform.basis = gizmo_basis;69876988for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) {6989viewports[i]->update_transform_gizmo_view();6990}6991}69926993void _update_all_gizmos(Node *p_node) {6994for (int i = p_node->get_child_count() - 1; 0 <= i; --i) {6995Node3D *spatial_node = Object::cast_to<Node3D>(p_node->get_child(i));6996if (spatial_node) {6997spatial_node->update_gizmos();6998}69997000_update_all_gizmos(p_node->get_child(i));7001}7002}70037004void Node3DEditor::update_all_gizmos(Node *p_node) {7005if (!p_node && is_inside_tree()) {7006p_node = get_tree()->get_edited_scene_root();7007}70087009if (!p_node) {7010// No edited scene, so nothing to update.7011return;7012}7013_update_all_gizmos(p_node);7014}70157016Object *Node3DEditor::_get_editor_data(Object *p_what) {7017Node3D *sp = Object::cast_to<Node3D>(p_what);7018if (!sp) {7019return nullptr;7020}70217022Node3DEditorSelectedItem *si = memnew(Node3DEditorSelectedItem);70237024si->sp = sp;7025si->sbox_instance = RenderingServer::get_singleton()->instance_create2(7026selection_box->get_rid(),7027sp->get_world_3d()->get_scenario());7028si->sbox_instance_offset = RenderingServer::get_singleton()->instance_create2(7029selection_box->get_rid(),7030sp->get_world_3d()->get_scenario());7031RS::get_singleton()->instance_geometry_set_cast_shadows_setting(7032si->sbox_instance,7033RS::SHADOW_CASTING_SETTING_OFF);7034RS::get_singleton()->instance_geometry_set_cast_shadows_setting(7035si->sbox_instance_offset,7036RS::SHADOW_CASTING_SETTING_OFF);7037// Use the Edit layer to hide the selection box when View Gizmos is disabled, since it is a bit distracting.7038// It's still possible to approximately guess what is selected by looking at the manipulation gizmo position.7039RS::get_singleton()->instance_set_layer_mask(si->sbox_instance, 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER);7040RS::get_singleton()->instance_set_layer_mask(si->sbox_instance_offset, 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER);7041RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);7042RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);7043RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance_offset, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);7044RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance_offset, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);7045si->sbox_instance_xray = RenderingServer::get_singleton()->instance_create2(7046selection_box_xray->get_rid(),7047sp->get_world_3d()->get_scenario());7048si->sbox_instance_xray_offset = RenderingServer::get_singleton()->instance_create2(7049selection_box_xray->get_rid(),7050sp->get_world_3d()->get_scenario());7051RS::get_singleton()->instance_geometry_set_cast_shadows_setting(7052si->sbox_instance_xray,7053RS::SHADOW_CASTING_SETTING_OFF);7054RS::get_singleton()->instance_geometry_set_cast_shadows_setting(7055si->sbox_instance_xray_offset,7056RS::SHADOW_CASTING_SETTING_OFF);7057// Use the Edit layer to hide the selection box when View Gizmos is disabled, since it is a bit distracting.7058// It's still possible to approximately guess what is selected by looking at the manipulation gizmo position.7059RS::get_singleton()->instance_set_layer_mask(si->sbox_instance_xray, 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER);7060RS::get_singleton()->instance_set_layer_mask(si->sbox_instance_xray_offset, 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER);7061RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance_xray, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);7062RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance_xray, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);7063RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance_xray_offset, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);7064RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance_xray_offset, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);70657066return si;7067}70687069void Node3DEditor::_generate_selection_boxes() {7070// Use two AABBs to create the illusion of a slightly thicker line.7071AABB aabb(Vector3(), Vector3(1, 1, 1));70727073// Create a x-ray (visible through solid surfaces) and standard version of the selection box.7074// Both will be drawn at the same position, but with different opacity.7075// This lets the user see where the selection is while still having a sense of depth.7076Ref<SurfaceTool> st = memnew(SurfaceTool);7077Ref<SurfaceTool> st_xray = memnew(SurfaceTool);7078Ref<SurfaceTool> active_st = memnew(SurfaceTool);7079Ref<SurfaceTool> active_st_xray = memnew(SurfaceTool);70807081st->begin(Mesh::PRIMITIVE_LINES);7082st_xray->begin(Mesh::PRIMITIVE_LINES);7083active_st->begin(Mesh::PRIMITIVE_LINES);7084active_st_xray->begin(Mesh::PRIMITIVE_LINES);7085for (int i = 0; i < 12; i++) {7086Vector3 a, b;7087aabb.get_edge(i, a, b);70887089st->add_vertex(a);7090st->add_vertex(b);7091active_st->add_vertex(a);7092active_st->add_vertex(b);7093st_xray->add_vertex(a);7094st_xray->add_vertex(b);7095active_st_xray->add_vertex(a);7096active_st_xray->add_vertex(b);7097}70987099const Color selection_box_color = EDITOR_GET("editors/3d/selection_box_color");7100const Color active_selection_box_color = EDITOR_GET("editors/3d/active_selection_box_color");71017102selection_box_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);7103selection_box_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);7104selection_box_mat->set_albedo(selection_box_color);7105selection_box_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);7106st->set_material(selection_box_mat);7107selection_box = st->commit();71087109selection_box_mat_xray->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);7110selection_box_mat_xray->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);7111selection_box_mat_xray->set_flag(StandardMaterial3D::FLAG_DISABLE_DEPTH_TEST, true);7112selection_box_mat_xray->set_albedo(selection_box_color * Color(1, 1, 1, 0.15));7113selection_box_mat_xray->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);7114st_xray->set_material(selection_box_mat_xray);7115selection_box_xray = st_xray->commit();71167117active_selection_box_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);7118active_selection_box_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);7119active_selection_box_mat->set_albedo(active_selection_box_color);7120active_selection_box_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);7121active_st->set_material(active_selection_box_mat);7122active_selection_box = active_st->commit();71237124active_selection_box_mat_xray->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);7125active_selection_box_mat_xray->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);7126active_selection_box_mat_xray->set_flag(StandardMaterial3D::FLAG_DISABLE_DEPTH_TEST, true);7127active_selection_box_mat_xray->set_albedo(active_selection_box_color * Color(1, 1, 1, 0.15));7128active_selection_box_mat_xray->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);7129active_st_xray->set_material(active_selection_box_mat_xray);7130active_selection_box_xray = active_st_xray->commit();7131}71327133Dictionary Node3DEditor::get_state() const {7134Dictionary d;71357136d["snap_enabled"] = snap_enabled;7137d["trackball_enabled"] = trackball_enabled;7138d["translate_snap"] = snap_translate_value;7139d["rotate_snap"] = snap_rotate_value;7140d["scale_snap"] = snap_scale_value;71417142d["local_coords"] = tool_option_button[TOOL_OPT_LOCAL_COORDS]->is_pressed();71437144int vc = 0;7145if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT))) {7146vc = 1;7147} else if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS))) {7148vc = 2;7149} else if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS))) {7150vc = 3;7151} else if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS))) {7152vc = 4;7153} else if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT))) {7154vc = 5;7155} else if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT))) {7156vc = 6;7157}71587159d["viewport_mode"] = vc;7160Array vpdata;7161for (int i = 0; i < 4; i++) {7162vpdata.push_back(viewports[i]->get_state());7163}71647165d["viewports"] = vpdata;71667167d["show_grid"] = view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_GRID));7168d["show_origin"] = view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_ORIGIN));7169d["fov"] = get_fov();7170d["znear"] = get_znear();7171d["zfar"] = get_zfar();71727173Dictionary gizmos_status;7174for (int i = 0; i < gizmo_plugins_by_name.size(); i++) {7175if (!gizmo_plugins_by_name[i]->can_be_hidden()) {7176continue;7177}7178int state = gizmos_menu->get_item_state(gizmos_menu->get_item_index(i));7179String name = gizmo_plugins_by_name[i]->get_gizmo_name();7180gizmos_status[name] = state;7181}71827183d["gizmos_status"] = gizmos_status;7184{7185Dictionary pd;71867187pd["sun_rotation"] = sun_rotation;71887189pd["environ_sky_color"] = environ_sky_color->get_pick_color();7190pd["environ_ground_color"] = environ_ground_color->get_pick_color();7191pd["environ_energy"] = environ_energy->get_value();7192pd["environ_glow_enabled"] = environ_glow_button->is_pressed();7193pd["environ_tonemap_enabled"] = environ_tonemap_button->is_pressed();7194pd["environ_ao_enabled"] = environ_ao_button->is_pressed();7195pd["environ_gi_enabled"] = environ_gi_button->is_pressed();7196pd["sun_shadow_max_distance"] = sun_shadow_max_distance->get_value();71977198pd["sun_color"] = sun_color->get_pick_color();7199pd["sun_energy"] = sun_energy->get_value();72007201pd["sun_enabled"] = sun_button->is_pressed();7202pd["environ_enabled"] = environ_button->is_pressed();72037204d["preview_sun_env"] = pd;7205}72067207return d;7208}72097210void Node3DEditor::set_state(const Dictionary &p_state) {7211Dictionary d = p_state;72127213if (d.has("snap_enabled")) {7214snap_enabled = d["snap_enabled"];7215tool_option_button[TOOL_OPT_USE_SNAP]->set_pressed(d["snap_enabled"]);7216}72177218if (d.has("trackball_enabled")) {7219trackball_enabled = d["trackball_enabled"];7220tool_option_button[TOOL_OPT_USE_TRACKBALL]->set_pressed(d["trackball_enabled"]);7221}72227223if (d.has("translate_snap")) {7224snap_translate_value = d["translate_snap"];7225}72267227if (d.has("rotate_snap")) {7228snap_rotate_value = d["rotate_snap"];7229}72307231if (d.has("scale_snap")) {7232snap_scale_value = d["scale_snap"];7233}72347235_snap_update();72367237if (d.has("local_coords")) {7238tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_pressed(d["local_coords"]);7239update_transform_gizmo();7240}72417242if (d.has("viewport_mode")) {7243int vc = d["viewport_mode"];72447245if (vc == 1) {7246_menu_item_pressed(MENU_VIEW_USE_1_VIEWPORT);7247} else if (vc == 2) {7248_menu_item_pressed(MENU_VIEW_USE_2_VIEWPORTS);7249} else if (vc == 3) {7250_menu_item_pressed(MENU_VIEW_USE_3_VIEWPORTS);7251} else if (vc == 4) {7252_menu_item_pressed(MENU_VIEW_USE_4_VIEWPORTS);7253} else if (vc == 5) {7254_menu_item_pressed(MENU_VIEW_USE_2_VIEWPORTS_ALT);7255} else if (vc == 6) {7256_menu_item_pressed(MENU_VIEW_USE_3_VIEWPORTS_ALT);7257}7258}72597260if (d.has("viewports")) {7261Array vp = d["viewports"];7262uint32_t vp_size = static_cast<uint32_t>(vp.size());7263if (vp_size > VIEWPORTS_COUNT) {7264WARN_PRINT("Ignoring superfluous viewport settings from spatial editor state.");7265vp_size = VIEWPORTS_COUNT;7266}72677268for (uint32_t i = 0; i < vp_size; i++) {7269viewports[i]->set_state(vp[i]);7270}7271}72727273if (d.has("zfar")) {7274settings_zfar->set_value(double(d["zfar"]));7275}7276if (d.has("znear")) {7277settings_znear->set_value(double(d["znear"]));7278}7279if (d.has("fov")) {7280settings_fov->set_value(double(d["fov"]));7281}7282if (d.has("show_grid")) {7283bool use = d["show_grid"];72847285if (use != view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_GRID))) {7286_menu_item_pressed(MENU_VIEW_GRID);7287}7288}72897290if (d.has("show_origin")) {7291bool use = d["show_origin"];72927293if (use != view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_ORIGIN))) {7294view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_ORIGIN), use);7295RenderingServer::get_singleton()->instance_set_visible(origin_instance, use);7296}7297}72987299if (d.has("gizmos_status")) {7300Dictionary gizmos_status = d["gizmos_status"];73017302for (int j = 0; j < gizmo_plugins_by_name.size(); ++j) {7303if (!gizmo_plugins_by_name[j]->can_be_hidden()) {7304continue;7305}7306int state = EditorNode3DGizmoPlugin::VISIBLE;7307for (const KeyValue<Variant, Variant> &kv : gizmos_status) {7308if (gizmo_plugins_by_name.write[j]->get_gizmo_name() == String(kv.key)) {7309state = kv.value;7310break;7311}7312}73137314gizmo_plugins_by_name.write[j]->set_state(state);7315}7316_update_gizmos_menu();7317}73187319if (d.has("preview_sun_env")) {7320sun_environ_updating = true;7321Dictionary pd = d["preview_sun_env"];7322sun_rotation = pd["sun_rotation"];73237324environ_sky_color->set_pick_color(pd["environ_sky_color"]);7325environ_ground_color->set_pick_color(pd["environ_ground_color"]);7326environ_energy->set_value_no_signal(pd["environ_energy"]);7327environ_glow_button->set_pressed_no_signal(pd["environ_glow_enabled"]);7328environ_tonemap_button->set_pressed_no_signal(pd["environ_tonemap_enabled"]);7329environ_ao_button->set_pressed_no_signal(pd["environ_ao_enabled"]);7330environ_gi_button->set_pressed_no_signal(pd["environ_gi_enabled"]);7331sun_shadow_max_distance->set_value_no_signal(pd["sun_shadow_max_distance"]);73327333sun_color->set_pick_color(pd["sun_color"]);7334sun_energy->set_value_no_signal(pd["sun_energy"]);73357336sun_button->set_pressed(pd["sun_enabled"]);7337environ_button->set_pressed(pd["environ_enabled"]);73387339sun_environ_updating = false;73407341_preview_settings_changed();7342_update_preview_environment();7343} else {7344_load_default_preview_settings();7345sun_button->set_pressed(true);7346environ_button->set_pressed(true);7347_preview_settings_changed();7348_update_preview_environment();7349}7350}73517352void Node3DEditor::edit(Node3D *p_spatial) {7353if (p_spatial != selected) {7354if (selected) {7355Vector<Ref<Node3DGizmo>> gizmos = selected->get_gizmos();7356for (int i = 0; i < gizmos.size(); i++) {7357Ref<EditorNode3DGizmo> seg = gizmos[i];7358if (seg.is_null()) {7359continue;7360}7361seg->set_selected(false);7362}73637364Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected);7365if (se) {7366se->gizmo.unref();7367se->subgizmos.clear();7368}73697370selected->update_gizmos();7371}73727373selected = p_spatial;7374current_hover_gizmo = Ref<EditorNode3DGizmo>();7375current_hover_gizmo_handle = -1;7376current_hover_gizmo_handle_secondary = false;73777378if (selected) {7379Vector<Ref<Node3DGizmo>> gizmos = selected->get_gizmos();7380for (int i = 0; i < gizmos.size(); i++) {7381Ref<EditorNode3DGizmo> seg = gizmos[i];7382if (seg.is_null()) {7383continue;7384}7385seg->set_selected(true);7386}7387selected->update_gizmos();7388}7389}7390}73917392void Node3DEditor::_snap_changed() {7393snap_translate_value = snap_translate->get_value();7394snap_rotate_value = snap_rotate->get_value();7395snap_scale_value = snap_scale->get_value();73967397EditorSettings::get_singleton()->set_project_metadata("3d_editor", "snap_translate_value", snap_translate_value);7398EditorSettings::get_singleton()->set_project_metadata("3d_editor", "snap_rotate_value", snap_rotate_value);7399EditorSettings::get_singleton()->set_project_metadata("3d_editor", "snap_scale_value", snap_scale_value);7400}74017402void Node3DEditor::_snap_update() {7403snap_translate->set_value(snap_translate_value);7404snap_rotate->set_value(snap_rotate_value);7405snap_scale->set_value(snap_scale_value);7406}74077408void Node3DEditor::_xform_dialog_action() {7409Transform3D t;7410//translation7411Vector3 scale;7412Vector3 rotate;7413Vector3 translate;74147415for (int i = 0; i < 3; i++) {7416translate[i] = xform_translate[i]->get_text().to_float();7417rotate[i] = Math::deg_to_rad(xform_rotate[i]->get_text().to_float());7418scale[i] = xform_scale[i]->get_text().to_float();7419}74207421t.basis.scale(scale);7422t.basis.rotate(rotate);7423t.origin = translate;74247425EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();7426undo_redo->create_action(TTR("XForm Dialog"));74277428const List<Node *> &selection = editor_selection->get_top_selected_node_list();74297430for (Node *E : selection) {7431Node3D *sp = Object::cast_to<Node3D>(E);7432if (!sp) {7433continue;7434}74357436Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);7437if (!se) {7438continue;7439}74407441bool post = xform_type->get_selected() > 0;74427443Transform3D tr = sp->get_global_gizmo_transform();7444if (post) {7445tr = tr * t;7446} else {7447tr.basis = t.basis * tr.basis;7448tr.origin += t.origin;7449}74507451Node3D *parent = sp->get_parent_node_3d();7452Transform3D local_tr = parent ? parent->get_global_transform().affine_inverse() * tr : tr;7453undo_redo->add_do_method(sp, "set_transform", local_tr);7454undo_redo->add_undo_method(sp, "set_transform", sp->get_transform());7455}7456undo_redo->commit_action();7457}74587459void Node3DEditor::_menu_item_toggled(bool pressed, int p_option) {7460switch (p_option) {7461case MENU_TOOL_LOCAL_COORDS: {7462tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_pressed(pressed);7463update_transform_gizmo();7464} break;74657466case MENU_TOOL_USE_SNAP: {7467tool_option_button[TOOL_OPT_USE_SNAP]->set_pressed(pressed);7468snap_enabled = pressed;7469} break;74707471case MENU_TOOL_USE_TRACKBALL: {7472tool_option_button[TOOL_OPT_USE_TRACKBALL]->set_pressed(pressed);7473trackball_enabled = pressed;7474} break;7475}7476}74777478void Node3DEditor::_menu_gizmo_toggled(int p_option) {7479const int idx = gizmos_menu->get_item_index(p_option);7480gizmos_menu->toggle_item_multistate(idx);74817482// Change icon7483const int state = gizmos_menu->get_item_state(idx);7484switch (state) {7485case EditorNode3DGizmoPlugin::VISIBLE:7486gizmos_menu->set_item_icon(idx, get_editor_theme_icon(SNAME("GuiVisibilityVisible")));7487break;7488case EditorNode3DGizmoPlugin::ON_TOP:7489gizmos_menu->set_item_icon(idx, get_editor_theme_icon(SNAME("GuiVisibilityXray")));7490break;7491case EditorNode3DGizmoPlugin::HIDDEN:7492gizmos_menu->set_item_icon(idx, get_editor_theme_icon(SNAME("GuiVisibilityHidden")));7493break;7494}74957496gizmo_plugins_by_name.write[p_option]->set_state(state);74977498update_all_gizmos();7499}75007501void Node3DEditor::_menu_item_pressed(int p_option) {7502EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();7503switch (p_option) {7504case MENU_TOOL_TRANSFORM:7505case MENU_TOOL_MOVE:7506case MENU_TOOL_ROTATE:7507case MENU_TOOL_SCALE:7508case MENU_TOOL_SELECT:7509case MENU_TOOL_LIST_SELECT: {7510for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) {7511if (viewports[i]->_edit.mode != Node3DEditorViewport::TRANSFORM_NONE) {7512viewports[i]->commit_transform();7513}7514}75157516for (int i = 0; i < TOOL_MAX; i++) {7517tool_button[i]->set_pressed(i == p_option);7518}7519tool_mode = (ToolMode)p_option;7520update_transform_gizmo();75217522} break;7523case MENU_TRANSFORM_CONFIGURE_SNAP: {7524snap_dialog->popup_centered(Size2(200, 180));7525} break;7526case MENU_TRANSFORM_DIALOG: {7527for (int i = 0; i < 3; i++) {7528xform_translate[i]->set_text("0");7529xform_rotate[i]->set_text("0");7530xform_scale[i]->set_text("1");7531}75327533xform_dialog->popup_centered(Size2(320, 240) * EDSCALE);75347535} break;7536case MENU_VIEW_USE_1_VIEWPORT: {7537viewport_base->set_view(Node3DEditorViewportContainer::VIEW_USE_1_VIEWPORT);7538if (last_used_viewport > 0) {7539last_used_viewport = 0;7540}75417542view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), true);7543view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), false);7544view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS), false);7545view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS), false);7546view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT), false);7547view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT), false);75487549} break;7550case MENU_VIEW_USE_2_VIEWPORTS: {7551viewport_base->set_view(Node3DEditorViewportContainer::VIEW_USE_2_VIEWPORTS);7552if (last_used_viewport > 1) {7553last_used_viewport = 0;7554}75557556view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), false);7557view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), true);7558view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS), false);7559view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS), false);7560view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT), false);7561view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT), false);75627563} break;7564case MENU_VIEW_USE_2_VIEWPORTS_ALT: {7565viewport_base->set_view(Node3DEditorViewportContainer::VIEW_USE_2_VIEWPORTS_ALT);7566if (last_used_viewport > 1) {7567last_used_viewport = 0;7568}75697570view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), false);7571view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), false);7572view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS), false);7573view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS), false);7574view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT), true);7575view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT), false);75767577} break;7578case MENU_VIEW_USE_3_VIEWPORTS: {7579viewport_base->set_view(Node3DEditorViewportContainer::VIEW_USE_3_VIEWPORTS);7580if (last_used_viewport > 2) {7581last_used_viewport = 0;7582}75837584view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), false);7585view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), false);7586view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS), true);7587view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS), false);7588view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT), false);7589view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT), false);75907591} break;7592case MENU_VIEW_USE_3_VIEWPORTS_ALT: {7593viewport_base->set_view(Node3DEditorViewportContainer::VIEW_USE_3_VIEWPORTS_ALT);7594if (last_used_viewport > 2) {7595last_used_viewport = 0;7596}75977598view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), false);7599view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), false);7600view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS), false);7601view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS), false);7602view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT), false);7603view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT), true);76047605} break;7606case MENU_VIEW_USE_4_VIEWPORTS: {7607viewport_base->set_view(Node3DEditorViewportContainer::VIEW_USE_4_VIEWPORTS);76087609view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), false);7610view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), false);7611view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS), false);7612view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS), true);7613view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT), false);7614view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT), false);76157616} break;7617case MENU_VIEW_ORIGIN: {7618bool is_checked = view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(p_option));76197620origin_enabled = !is_checked;7621RenderingServer::get_singleton()->instance_set_visible(origin_instance, origin_enabled);7622// Update the grid since its appearance depends on whether the origin is enabled7623_finish_grid();7624_init_grid();76257626view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(p_option), origin_enabled);7627} break;7628case MENU_VIEW_GRID: {7629bool is_checked = view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(p_option));76307631grid_enabled = !is_checked;76327633for (int i = 0; i < 3; ++i) {7634if (grid_enable[i]) {7635grid_visible[i] = grid_enabled;7636}7637}7638_finish_grid();7639_init_grid();76407641view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(p_option), grid_enabled);76427643} break;7644case MENU_VIEW_CAMERA_SETTINGS: {7645settings_dialog->popup_centered(settings_vbc->get_combined_minimum_size() + Size2(50, 50));7646} break;7647case MENU_SNAP_TO_FLOOR: {7648snap_selected_nodes_to_floor();7649} break;7650case MENU_LOCK_SELECTED: {7651undo_redo->create_action(TTR("Lock Selected"));76527653const List<Node *> &selection = editor_selection->get_top_selected_node_list();76547655for (Node *E : selection) {7656Node3D *spatial = Object::cast_to<Node3D>(E);7657if (!spatial || !spatial->is_inside_tree()) {7658continue;7659}76607661undo_redo->add_do_method(spatial, "set_meta", "_edit_lock_", true);7662undo_redo->add_undo_method(spatial, "remove_meta", "_edit_lock_");7663undo_redo->add_do_method(this, "emit_signal", "item_lock_status_changed");7664undo_redo->add_undo_method(this, "emit_signal", "item_lock_status_changed");7665}76667667undo_redo->add_do_method(this, "_refresh_menu_icons");7668undo_redo->add_undo_method(this, "_refresh_menu_icons");7669undo_redo->commit_action();7670} break;7671case MENU_UNLOCK_SELECTED: {7672undo_redo->create_action(TTR("Unlock Selected"));76737674const List<Node *> &selection = editor_selection->get_top_selected_node_list();76757676for (Node *E : selection) {7677Node3D *spatial = Object::cast_to<Node3D>(E);7678if (!spatial || !spatial->is_inside_tree()) {7679continue;7680}76817682undo_redo->add_do_method(spatial, "remove_meta", "_edit_lock_");7683undo_redo->add_undo_method(spatial, "set_meta", "_edit_lock_", true);7684undo_redo->add_do_method(this, "emit_signal", "item_lock_status_changed");7685undo_redo->add_undo_method(this, "emit_signal", "item_lock_status_changed");7686}76877688undo_redo->add_do_method(this, "_refresh_menu_icons");7689undo_redo->add_undo_method(this, "_refresh_menu_icons");7690undo_redo->commit_action();7691} break;7692case MENU_GROUP_SELECTED: {7693undo_redo->create_action(TTR("Group Selected"));76947695const List<Node *> &selection = editor_selection->get_top_selected_node_list();76967697for (Node *E : selection) {7698Node3D *spatial = Object::cast_to<Node3D>(E);7699if (!spatial || !spatial->is_inside_tree()) {7700continue;7701}77027703undo_redo->add_do_method(spatial, "set_meta", "_edit_group_", true);7704undo_redo->add_undo_method(spatial, "remove_meta", "_edit_group_");7705undo_redo->add_do_method(this, "emit_signal", "item_group_status_changed");7706undo_redo->add_undo_method(this, "emit_signal", "item_group_status_changed");7707}77087709undo_redo->add_do_method(this, "_refresh_menu_icons");7710undo_redo->add_undo_method(this, "_refresh_menu_icons");7711undo_redo->commit_action();7712} break;7713case MENU_UNGROUP_SELECTED: {7714undo_redo->create_action(TTR("Ungroup Selected"));7715const List<Node *> &selection = editor_selection->get_top_selected_node_list();77167717for (Node *E : selection) {7718Node3D *spatial = Object::cast_to<Node3D>(E);7719if (!spatial || !spatial->is_inside_tree()) {7720continue;7721}77227723undo_redo->add_do_method(spatial, "remove_meta", "_edit_group_");7724undo_redo->add_undo_method(spatial, "set_meta", "_edit_group_", true);7725undo_redo->add_do_method(this, "emit_signal", "item_group_status_changed");7726undo_redo->add_undo_method(this, "emit_signal", "item_group_status_changed");7727}77287729undo_redo->add_do_method(this, "_refresh_menu_icons");7730undo_redo->add_undo_method(this, "_refresh_menu_icons");7731undo_redo->commit_action();7732} break;7733case MENU_RULER: {7734for (int i = 0; i < TOOL_MAX; i++) {7735tool_button[i]->set_pressed(i == p_option);7736}7737tool_button[TOOL_RULER]->set_pressed(true);7738tool_mode = ToolMode::TOOL_RULER;7739update_transform_gizmo();7740} break;7741}7742}77437744void Node3DEditor::_init_indicators() {7745{7746origin_enabled = true;7747grid_enabled = true;77487749Ref<Shader> origin_shader = memnew(Shader);7750origin_shader->set_code(R"(7751// 3D editor origin line shader.77527753shader_type spatial;7754render_mode blend_mix, cull_disabled, unshaded, fog_disabled;77557756void vertex() {7757vec3 point_a = MODEL_MATRIX[3].xyz;7758// Encoded in scale.7759vec3 point_b = vec3(MODEL_MATRIX[0].x, MODEL_MATRIX[1].y, MODEL_MATRIX[2].z);77607761// Points are already in world space, so no need for MODEL_MATRIX anymore.7762vec4 clip_a = PROJECTION_MATRIX * (VIEW_MATRIX * vec4(point_a, 1.0));7763vec4 clip_b = PROJECTION_MATRIX * (VIEW_MATRIX * vec4(point_b, 1.0));77647765vec2 screen_a = VIEWPORT_SIZE * (0.5 * clip_a.xy / clip_a.w + 0.5);7766vec2 screen_b = VIEWPORT_SIZE * (0.5 * clip_b.xy / clip_b.w + 0.5);77677768vec2 x_basis = normalize(screen_b - screen_a);7769vec2 y_basis = vec2(-x_basis.y, x_basis.x);77707771float width = 3.0;7772vec2 screen_point_a = screen_a + width * (VERTEX.x * x_basis + VERTEX.y * y_basis);7773vec2 screen_point_b = screen_b + width * (VERTEX.x * x_basis + VERTEX.y * y_basis);7774vec2 screen_point_final = mix(screen_point_a, screen_point_b, VERTEX.z);77757776vec4 clip_final = mix(clip_a, clip_b, VERTEX.z);77777778POSITION = vec4(clip_final.w * ((2.0 * screen_point_final) / VIEWPORT_SIZE - 1.0), clip_final.z, clip_final.w);7779UV = VERTEX.yz * clip_final.w;77807781if (!OUTPUT_IS_SRGB) {7782COLOR.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)));7783}7784}77857786void fragment() {7787// Multiply by 0.5 since UV is actually UV is [-1, 1].7788float line_width = fwidth(UV.x * 0.5);7789float line_uv = abs(UV.x * 0.5);7790float line = smoothstep(line_width * 1.0, line_width * 0.25, line_uv);77917792ALBEDO = COLOR.rgb;7793ALPHA *= COLOR.a * line;7794}7795)");77967797origin_mat.instantiate();7798origin_mat->set_shader(origin_shader);77997800Vector<Vector3> origin_points;7801origin_points.resize(6);78027803origin_points.set(0, Vector3(0.0, -0.5, 0.0));7804origin_points.set(1, Vector3(0.0, -0.5, 1.0));7805origin_points.set(2, Vector3(0.0, 0.5, 1.0));78067807origin_points.set(3, Vector3(0.0, -0.5, 0.0));7808origin_points.set(4, Vector3(0.0, 0.5, 1.0));7809origin_points.set(5, Vector3(0.0, 0.5, 0.0));78107811Array d;7812d.resize(RS::ARRAY_MAX);7813d[RenderingServer::ARRAY_VERTEX] = origin_points;78147815origin_mesh = RenderingServer::get_singleton()->mesh_create();78167817RenderingServer::get_singleton()->mesh_add_surface_from_arrays(origin_mesh, RenderingServer::PRIMITIVE_TRIANGLES, d);7818RenderingServer::get_singleton()->mesh_surface_set_material(origin_mesh, 0, origin_mat->get_rid());78197820origin_multimesh = RenderingServer::get_singleton()->multimesh_create();7821RenderingServer::get_singleton()->multimesh_set_mesh(origin_multimesh, origin_mesh);7822RenderingServer::get_singleton()->multimesh_allocate_data(origin_multimesh, 12, RS::MultimeshTransformFormat::MULTIMESH_TRANSFORM_3D, true, false);7823RenderingServer::get_singleton()->multimesh_set_visible_instances(origin_multimesh, -1);78247825LocalVector<float> distances;7826distances.resize(5);7827distances[0] = -1000000.0;7828distances[1] = -1000.0;7829distances[2] = 0.0;7830distances[3] = 1000.0;7831distances[4] = 1000000.0;78327833for (int i = 0; i < 3; i++) {7834Color origin_color;7835switch (i) {7836case 0:7837origin_color = get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor));7838break;7839case 1:7840origin_color = get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor));7841break;7842case 2:7843origin_color = get_theme_color(SNAME("axis_z_color"), EditorStringName(Editor));7844break;7845default:7846origin_color = Color();7847break;7848}78497850Vector3 axis;7851axis[i] = 1;78527853for (int j = 0; j < 4; j++) {7854Transform3D t = Transform3D();7855if (distances[j] > 0.0) {7856t = t.scaled(axis * distances[j + 1]);7857t = t.translated(axis * distances[j]);7858} else {7859t = t.scaled(axis * distances[j]);7860t = t.translated(axis * distances[j + 1]);7861}7862RenderingServer::get_singleton()->multimesh_instance_set_transform(origin_multimesh, i * 4 + j, t);7863RenderingServer::get_singleton()->multimesh_instance_set_color(origin_multimesh, i * 4 + j, origin_color);7864}7865}78667867origin_instance = RenderingServer::get_singleton()->instance_create2(origin_multimesh, get_tree()->get_root()->get_world_3d()->get_scenario());7868RS::get_singleton()->instance_set_layer_mask(origin_instance, 1 << Node3DEditorViewport::GIZMO_GRID_LAYER);7869RS::get_singleton()->instance_geometry_set_flag(origin_instance, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);7870RS::get_singleton()->instance_geometry_set_flag(origin_instance, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);78717872RenderingServer::get_singleton()->instance_geometry_set_cast_shadows_setting(origin_instance, RS::SHADOW_CASTING_SETTING_OFF);78737874Ref<Shader> grid_shader = memnew(Shader);7875grid_shader->set_code(R"(7876// 3D editor grid shader.78777878shader_type spatial;78797880render_mode unshaded, fog_disabled;78817882uniform bool orthogonal;7883uniform float grid_size;78847885void vertex() {7886// From FLAG_SRGB_VERTEX_COLOR.7887if (!OUTPUT_IS_SRGB) {7888COLOR.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)));7889}7890}78917892void fragment() {7893ALBEDO = COLOR.rgb;7894vec3 dir = orthogonal ? -vec3(0, 0, 1) : VIEW;7895float angle_fade = abs(dot(dir, NORMAL));7896angle_fade = smoothstep(0.05, 0.2, angle_fade);78977898vec3 world_pos = (INV_VIEW_MATRIX * vec4(VERTEX, 1.0)).xyz;7899vec3 world_normal = (INV_VIEW_MATRIX * vec4(NORMAL, 0.0)).xyz;7900vec3 camera_world_pos = INV_VIEW_MATRIX[3].xyz;7901vec3 camera_world_pos_on_plane = camera_world_pos * (1.0 - world_normal);7902float dist_fade = 1.0 - (distance(world_pos, camera_world_pos_on_plane) / grid_size);7903dist_fade = smoothstep(0.02, 0.3, dist_fade);79047905ALPHA = COLOR.a * dist_fade * angle_fade;7906}7907)");79087909for (int i = 0; i < 3; i++) {7910grid_mat[i].instantiate();7911grid_mat[i]->set_shader(grid_shader);7912}79137914grid_enable[0] = EDITOR_GET("editors/3d/grid_xy_plane");7915grid_enable[1] = EDITOR_GET("editors/3d/grid_yz_plane");7916grid_enable[2] = EDITOR_GET("editors/3d/grid_xz_plane");7917grid_visible[0] = grid_enable[0];7918grid_visible[1] = grid_enable[1];7919grid_visible[2] = grid_enable[2];79207921_init_grid();7922}79237924{7925//move gizmo79267927// Inverted zxy.7928Vector3 ivec = Vector3(0, 0, -1);7929Vector3 nivec = Vector3(-1, -1, 0);7930Vector3 ivec2 = Vector3(-1, 0, 0);7931Vector3 ivec3 = Vector3(0, -1, 0);79327933for (int i = 0; i < 4; i++) {7934Color col;7935switch (i) {7936case 0:7937col = get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor));7938break;7939case 1:7940col = get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor));7941break;7942case 2:7943col = get_theme_color(SNAME("axis_z_color"), EditorStringName(Editor));7944break;7945case 3:7946col = Color(0.75, 0.75, 0.75, 1.0);7947break;7948default:7949col = Color();7950break;7951}79527953if (i < 3) {7954col.a = EDITOR_GET("editors/3d/manipulator_gizmo_opacity");7955}79567957if (i < 3) {7958move_gizmo[i].instantiate();7959move_plane_gizmo[i].instantiate();7960scale_gizmo[i].instantiate();7961scale_plane_gizmo[i].instantiate();7962axis_gizmo[i].instantiate();7963}79647965rotate_gizmo[i].instantiate();79667967const Color albedo = col.from_hsv(col.get_h(), 0.25, 1.0, 1);79687969Ref<StandardMaterial3D> mat;7970Ref<StandardMaterial3D> mat_hl;79717972if (i < 3) {7973// Only create standard materials for X, Y, Z axes (move/scale gizmos).7974mat.instantiate();7975mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);7976mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);7977mat->set_on_top_of_alpha();7978mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);7979mat->set_albedo(col);7980gizmo_color[i] = mat;79817982mat_hl = mat->duplicate();7983mat_hl->set_albedo(albedo);7984gizmo_color_hl[i] = mat_hl;7985}79867987// Only create translate gizmo for X, Y, Z axes (not view rotation).7988if (i < 3) {7989//translate7990{7991Ref<SurfaceTool> surftool;7992surftool.instantiate();7993surftool->begin(Mesh::PRIMITIVE_TRIANGLES);79947995// Arrow profile7996const int arrow_points = 5;7997Vector3 arrow[5] = {7998nivec * 0.0 + ivec * 0.0,7999nivec * 0.01 + ivec * 0.0,8000nivec * 0.01 + ivec * GIZMO_ARROW_OFFSET,8001nivec * 0.065 + ivec * GIZMO_ARROW_OFFSET,8002nivec * 0.0 + ivec * (GIZMO_ARROW_OFFSET + GIZMO_ARROW_SIZE),8003};80048005int arrow_sides = 16;80068007const real_t arrow_sides_step = Math::TAU / arrow_sides;8008for (int k = 0; k < arrow_sides; k++) {8009Basis ma(ivec, k * arrow_sides_step);8010Basis mb(ivec, (k + 1) * arrow_sides_step);80118012for (int j = 0; j < arrow_points - 1; j++) {8013Vector3 points[4] = {8014ma.xform(arrow[j]),8015mb.xform(arrow[j]),8016mb.xform(arrow[j + 1]),8017ma.xform(arrow[j + 1]),8018};8019surftool->add_vertex(points[0]);8020surftool->add_vertex(points[1]);8021surftool->add_vertex(points[2]);80228023surftool->add_vertex(points[0]);8024surftool->add_vertex(points[2]);8025surftool->add_vertex(points[3]);8026}8027}80288029surftool->set_material(mat);8030surftool->commit(move_gizmo[i]);8031}80328033// Plane Translation8034{8035Ref<SurfaceTool> surftool;8036surftool.instantiate();8037surftool->begin(Mesh::PRIMITIVE_TRIANGLES);80388039Vector3 vec = ivec2 - ivec3;8040Vector3 plane[4] = {8041vec * GIZMO_PLANE_DST,8042vec * GIZMO_PLANE_DST + ivec2 * GIZMO_PLANE_SIZE,8043vec * (GIZMO_PLANE_DST + GIZMO_PLANE_SIZE),8044vec * GIZMO_PLANE_DST - ivec3 * GIZMO_PLANE_SIZE8045};80468047Basis ma(ivec, Math::PI / 2);80488049Vector3 points[4] = {8050ma.xform(plane[0]),8051ma.xform(plane[1]),8052ma.xform(plane[2]),8053ma.xform(plane[3]),8054};8055surftool->add_vertex(points[0]);8056surftool->add_vertex(points[1]);8057surftool->add_vertex(points[2]);80588059surftool->add_vertex(points[0]);8060surftool->add_vertex(points[2]);8061surftool->add_vertex(points[3]);80628063Ref<StandardMaterial3D> plane_mat;8064plane_mat.instantiate();8065plane_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);8066plane_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);8067plane_mat->set_on_top_of_alpha();8068plane_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);8069plane_mat->set_cull_mode(StandardMaterial3D::CULL_DISABLED);8070plane_mat->set_albedo(col);8071plane_gizmo_color[i] = plane_mat; // Needed, so we can draw planes from both sides.8072surftool->set_material(plane_mat);8073surftool->commit(move_plane_gizmo[i]);80748075Ref<StandardMaterial3D> plane_mat_hl = plane_mat->duplicate();8076plane_mat_hl->set_albedo(albedo);8077plane_gizmo_color_hl[i] = plane_mat_hl; // Needed, so we can draw planes from both sides.8078}8079}80808081// Rotate - for all 4 indices (X, Y, Z, and view rotation).8082{8083Ref<SurfaceTool> surftool;8084surftool.instantiate();8085surftool->begin(Mesh::PRIMITIVE_TRIANGLES);80868087int n = 128; // number of circle segments8088int m = 3; // number of thickness segments80898090real_t step = Math::TAU / n;8091for (int j = 0; j < n; ++j) {8092Basis basis = Basis(ivec, j * step);80938094real_t circle_size = (i == 3) ? GIZMO_VIEW_ROTATION_SIZE : GIZMO_CIRCLE_SIZE;8095Vector3 vertex = basis.xform(ivec2 * circle_size);80968097for (int k = 0; k < m; ++k) {8098Vector2 ofs = Vector2(Math::cos((Math::TAU * k) / m), Math::sin((Math::TAU * k) / m));8099Vector3 normal = ivec * ofs.x + ivec2 * ofs.y;81008101surftool->set_normal(basis.xform(normal));8102surftool->add_vertex(vertex);8103}8104}81058106for (int j = 0; j < n; ++j) {8107for (int k = 0; k < m; ++k) {8108int current_ring = j * m;8109int next_ring = ((j + 1) % n) * m;8110int current_segment = k;8111int next_segment = (k + 1) % m;81128113surftool->add_index(current_ring + next_segment);8114surftool->add_index(current_ring + current_segment);8115surftool->add_index(next_ring + current_segment);81168117surftool->add_index(next_ring + current_segment);8118surftool->add_index(next_ring + next_segment);8119surftool->add_index(current_ring + next_segment);8120}8121}81228123Ref<Shader> rotate_shader = memnew(Shader);81248125// Use special shader for view rotation (index 3) with camera-relative transformation.8126if (i == 3) {8127rotate_shader->set_code(R"(81288129shader_type spatial;81308131render_mode unshaded, depth_test_disabled, fog_disabled;81328133uniform vec4 albedo;81348135mat3 orthonormalize(mat3 m) {8136vec3 x = normalize(m[0]);8137vec3 y = normalize(m[1] - x * dot(x, m[1]));8138vec3 z = m[2] - x * dot(x, m[2]);8139z = normalize(z - y * (dot(y, m[2])));8140return mat3(x,y,z);8141}81428143void vertex() {8144mat3 mv = orthonormalize(mat3(MODELVIEW_MATRIX));8145mv = inverse(mv);8146VERTEX += NORMAL * 0.008;8147vec3 camera_dir_local = mv * vec3(0.0, 0.0, 1.0);8148vec3 camera_up_local = mv * vec3(0.0, 1.0, 0.0);8149mat3 rotation_matrix = mat3(cross(camera_dir_local, camera_up_local), camera_up_local, camera_dir_local);8150VERTEX = rotation_matrix * VERTEX;8151}81528153void fragment() {8154ALBEDO = albedo.rgb;8155ALPHA = albedo.a;8156}8157)");8158} else {8159// Standard shader for X, Y, Z rotation gizmos.8160rotate_shader->set_code(R"(8161// 3D editor rotation manipulator gizmo shader.81628163shader_type spatial;81648165render_mode unshaded, depth_test_disabled, fog_disabled;81668167uniform vec4 albedo;81688169mat3 orthonormalize(mat3 m) {8170vec3 x = normalize(m[0]);8171vec3 y = normalize(m[1] - x * dot(x, m[1]));8172vec3 z = m[2] - x * dot(x, m[2]);8173z = normalize(z - y * (dot(y, m[2])));8174return mat3(x, y, z);8175}81768177void vertex() {8178mat3 mv = orthonormalize(mat3(MODELVIEW_MATRIX));8179vec3 n = mv * VERTEX;8180float orientation = dot(vec3(0.0, 0.0, -1.0), n);8181if (orientation <= 0.005) {8182VERTEX += NORMAL * 0.02;8183}8184}81858186void fragment() {8187ALBEDO = albedo.rgb;8188ALPHA = albedo.a;8189}8190)");8191}81928193Ref<ShaderMaterial> rotate_mat;8194rotate_mat.instantiate();8195rotate_mat->set_render_priority(Material::RENDER_PRIORITY_MAX);8196rotate_mat->set_shader(rotate_shader);8197rotate_mat->set_shader_parameter("albedo", col);8198rotate_gizmo_color[i] = rotate_mat;81998200Array arrays = surftool->commit_to_arrays();8201rotate_gizmo[i]->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, arrays);8202rotate_gizmo[i]->surface_set_material(0, rotate_mat);82038204Ref<ShaderMaterial> rotate_mat_hl = rotate_mat->duplicate();8205// For view rotation, use bright white; for axes, use highlight color.8206Color highlight_color = (i == 3) ? Color(1.0, 1.0, 1.0, 1.0) : albedo;8207rotate_mat_hl->set_shader_parameter("albedo", highlight_color);8208rotate_gizmo_color_hl[i] = rotate_mat_hl;8209}82108211// Only create scale gizmo for X, Y, Z axes (not view rotation).8212if (i < 3) {8213// Scale.8214{8215Ref<SurfaceTool> surftool;8216surftool.instantiate();8217surftool->begin(Mesh::PRIMITIVE_TRIANGLES);82188219// Cube arrow profile.8220const int arrow_points = 6;8221Vector3 arrow[6] = {8222nivec * 0.0 + ivec * 0.0,8223nivec * 0.01 + ivec * 0.0,8224nivec * 0.01 + ivec * 1.0 * GIZMO_SCALE_OFFSET,8225nivec * 0.07 + ivec * 1.0 * GIZMO_SCALE_OFFSET,8226nivec * 0.07 + ivec * 1.11 * GIZMO_SCALE_OFFSET,8227nivec * 0.0 + ivec * 1.11 * GIZMO_SCALE_OFFSET,8228};82298230int arrow_sides = 4;82318232const real_t arrow_sides_step = Math::TAU / arrow_sides;8233for (int k = 0; k < 4; k++) {8234Basis ma(ivec, k * arrow_sides_step);8235Basis mb(ivec, (k + 1) * arrow_sides_step);82368237for (int j = 0; j < arrow_points - 1; j++) {8238Vector3 points[4] = {8239ma.xform(arrow[j]),8240mb.xform(arrow[j]),8241mb.xform(arrow[j + 1]),8242ma.xform(arrow[j + 1]),8243};8244surftool->add_vertex(points[0]);8245surftool->add_vertex(points[1]);8246surftool->add_vertex(points[2]);82478248surftool->add_vertex(points[0]);8249surftool->add_vertex(points[2]);8250surftool->add_vertex(points[3]);8251}8252}82538254surftool->set_material(mat);8255surftool->commit(scale_gizmo[i]);8256}82578258// Plane Scale.8259{8260Ref<SurfaceTool> surftool;8261surftool.instantiate();8262surftool->begin(Mesh::PRIMITIVE_TRIANGLES);82638264Vector3 vec = ivec2 - ivec3;8265Vector3 plane[4] = {8266vec * GIZMO_PLANE_DST,8267vec * GIZMO_PLANE_DST + ivec2 * GIZMO_PLANE_SIZE,8268vec * (GIZMO_PLANE_DST + GIZMO_PLANE_SIZE),8269vec * GIZMO_PLANE_DST - ivec3 * GIZMO_PLANE_SIZE8270};82718272Basis ma(ivec, Math::PI / 2);82738274Vector3 points[4] = {8275ma.xform(plane[0]),8276ma.xform(plane[1]),8277ma.xform(plane[2]),8278ma.xform(plane[3]),8279};8280surftool->add_vertex(points[0]);8281surftool->add_vertex(points[1]);8282surftool->add_vertex(points[2]);82838284surftool->add_vertex(points[0]);8285surftool->add_vertex(points[2]);8286surftool->add_vertex(points[3]);82878288Ref<StandardMaterial3D> plane_mat;8289plane_mat.instantiate();8290plane_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);8291plane_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);8292plane_mat->set_on_top_of_alpha();8293plane_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);8294plane_mat->set_cull_mode(StandardMaterial3D::CULL_DISABLED);8295plane_mat->set_albedo(col);8296plane_gizmo_color[i] = plane_mat; // needed, so we can draw planes from both sides.8297surftool->set_material(plane_mat);8298surftool->commit(scale_plane_gizmo[i]);82998300Ref<StandardMaterial3D> plane_mat_hl = plane_mat->duplicate();8301plane_mat_hl->set_albedo(col.from_hsv(col.get_h(), 0.25, 1.0, 1));8302plane_gizmo_color_hl[i] = plane_mat_hl; // needed, so we can draw planes from both sides.8303}83048305// Lines to visualize transforms locked to an axis/plane.8306{8307Ref<SurfaceTool> surftool;8308surftool.instantiate();8309surftool->begin(Mesh::PRIMITIVE_LINE_STRIP);83108311Vector3 vec;8312vec[i] = 1;83138314// Line extending through like infinity.8315surftool->add_vertex(vec * -1048576);8316surftool->add_vertex(Vector3());8317surftool->add_vertex(vec * 1048576);8318surftool->set_material(mat_hl);8319surftool->commit(axis_gizmo[i]);8320}8321}8322}8323}83248325// Create trackball sphere8326{8327trackball_sphere_gizmo.instantiate();8328Ref<SurfaceTool> surftool;8329surftool.instantiate();8330surftool->begin(Mesh::PRIMITIVE_TRIANGLES);83318332const int sphere_rings = TRACKBALL_SPHERE_RINGS;8333const int sphere_sectors = TRACKBALL_SPHERE_SECTORS;8334const real_t sphere_radius = GIZMO_CIRCLE_SIZE;83358336for (int r = 0; r <= sphere_rings; ++r) {8337for (int s = 0; s <= sphere_sectors; ++s) {8338real_t ring_angle = Math::PI * r / sphere_rings;8339real_t sector_angle = 2.0 * Math::PI * s / sphere_sectors;83408341Vector3 vertex(8342sphere_radius * Math::sin(ring_angle) * Math::cos(sector_angle),8343sphere_radius * Math::cos(ring_angle),8344sphere_radius * Math::sin(ring_angle) * Math::sin(sector_angle));83458346surftool->set_normal(vertex.normalized());8347surftool->add_vertex(vertex);8348}8349}83508351for (int r = 0; r < sphere_rings; ++r) {8352for (int s = 0; s < sphere_sectors; ++s) {8353int current = r * (sphere_sectors + 1) + s;8354int next = current + sphere_sectors + 1;83558356surftool->add_index(current);8357surftool->add_index(next);8358surftool->add_index(current + 1);83598360surftool->add_index(current + 1);8361surftool->add_index(next);8362surftool->add_index(next + 1);8363}8364}83658366trackball_sphere_material.instantiate();8367trackball_sphere_material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);8368trackball_sphere_material->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);8369trackball_sphere_material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);8370trackball_sphere_material->set_cull_mode(StandardMaterial3D::CULL_DISABLED);8371trackball_sphere_material->set_albedo(Color(1.0, 1.0, 1.0, 0.0));8372trackball_sphere_material->set_flag(StandardMaterial3D::FLAG_DISABLE_DEPTH_TEST, true);83738374trackball_sphere_material_hl = trackball_sphere_material->duplicate();8375trackball_sphere_material_hl->set_albedo(Color(1.0, 1.0, 1.0, TRACKBALL_HIGHLIGHT_ALPHA));83768377surftool->set_material(trackball_sphere_material);8378surftool->commit(trackball_sphere_gizmo);8379}83808381_generate_selection_boxes();8382}83838384void Node3DEditor::_update_gizmos_menu() {8385gizmos_menu->clear();83868387for (int i = 0; i < gizmo_plugins_by_name.size(); ++i) {8388if (!gizmo_plugins_by_name[i]->can_be_hidden()) {8389continue;8390}8391String plugin_name = gizmo_plugins_by_name[i]->get_gizmo_name();8392const int plugin_state = gizmo_plugins_by_name[i]->get_state();8393gizmos_menu->add_multistate_item(plugin_name, 3, plugin_state, i);8394const int idx = gizmos_menu->get_item_index(i);8395gizmos_menu->set_item_tooltip(8396idx,8397TTR("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\")."));8398switch (plugin_state) {8399case EditorNode3DGizmoPlugin::VISIBLE:8400gizmos_menu->set_item_icon(idx, get_editor_theme_icon(SNAME("GuiVisibilityVisible")));8401break;8402case EditorNode3DGizmoPlugin::ON_TOP:8403gizmos_menu->set_item_icon(idx, get_editor_theme_icon(SNAME("GuiVisibilityXray")));8404break;8405case EditorNode3DGizmoPlugin::HIDDEN:8406gizmos_menu->set_item_icon(idx, get_editor_theme_icon(SNAME("GuiVisibilityHidden")));8407break;8408}8409}8410}84118412void Node3DEditor::_update_gizmos_menu_theme() {8413for (int i = 0; i < gizmo_plugins_by_name.size(); ++i) {8414if (!gizmo_plugins_by_name[i]->can_be_hidden()) {8415continue;8416}8417const int plugin_state = gizmo_plugins_by_name[i]->get_state();8418const int idx = gizmos_menu->get_item_index(i);8419switch (plugin_state) {8420case EditorNode3DGizmoPlugin::VISIBLE:8421gizmos_menu->set_item_icon(idx, get_editor_theme_icon(SNAME("GuiVisibilityVisible")));8422break;8423case EditorNode3DGizmoPlugin::ON_TOP:8424gizmos_menu->set_item_icon(idx, get_editor_theme_icon(SNAME("GuiVisibilityXray")));8425break;8426case EditorNode3DGizmoPlugin::HIDDEN:8427gizmos_menu->set_item_icon(idx, get_editor_theme_icon(SNAME("GuiVisibilityHidden")));8428break;8429}8430}8431}84328433void Node3DEditor::_init_grid() {8434if (!grid_enabled) {8435return;8436}8437Camera3D *camera = get_editor_viewport(0)->camera;8438Vector3 camera_position = camera->get_position();8439if (camera_position == Vector3()) {8440return; // Camera3D is invalid, don't draw the grid.8441}84428443bool orthogonal = camera->get_projection() == Camera3D::PROJECTION_ORTHOGONAL;84448445static LocalVector<Color> grid_colors[3];8446static LocalVector<Vector3> grid_points[3];8447static LocalVector<Vector3> grid_normals[3];84488449for (uint32_t n = 0; n < 3; n++) {8450grid_colors[n].clear();8451grid_points[n].clear();8452grid_normals[n].clear();8453}84548455Color primary_grid_color = EDITOR_GET("editors/3d/primary_grid_color");8456Color secondary_grid_color = EDITOR_GET("editors/3d/secondary_grid_color");8457int grid_size = EDITOR_GET("editors/3d/grid_size");8458int primary_grid_steps = EDITOR_GET("editors/3d/primary_grid_steps");84598460// Which grid planes are enabled? Which should we generate?8461grid_enable[0] = grid_visible[0] = orthogonal || EDITOR_GET("editors/3d/grid_xy_plane");8462grid_enable[1] = grid_visible[1] = orthogonal || EDITOR_GET("editors/3d/grid_yz_plane");8463grid_enable[2] = grid_visible[2] = orthogonal || EDITOR_GET("editors/3d/grid_xz_plane");84648465// Offsets division_level for bigger or smaller grids.8466// Default value is -0.2. -1.0 gives Blender-like behavior, 0.5 gives huge grids.8467real_t division_level_bias = EDITOR_GET("editors/3d/grid_division_level_bias");8468// 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).8469int division_level_max = EDITOR_GET("editors/3d/grid_division_level_max");8470// Default smallest grid size is 8^0 (default value is 0) when primary_grid_steps is 8.8471int division_level_min = EDITOR_GET("editors/3d/grid_division_level_min");8472ERR_FAIL_COND_MSG(division_level_max < division_level_min, "The 3D grid's maximum division level cannot be lower than its minimum division level.");84738474if (primary_grid_steps != 10) { // Log10 of 10 is 1.8475// Change of base rule, divide by ln(10).8476real_t div = Math::log((real_t)primary_grid_steps) / (real_t)2.302585092994045901094;8477// Truncation (towards zero) is intentional.8478division_level_max = (int)(division_level_max / div);8479division_level_min = (int)(division_level_min / div);8480}84818482for (int a = 0; a < 3; a++) {8483if (!grid_enable[a]) {8484continue; // If this grid plane is disabled, skip generation.8485}8486int b = (a + 1) % 3;8487int c = (a + 2) % 3;84888489Vector3 normal;8490normal[c] = 1.0;84918492real_t camera_distance = Math::abs(camera_position[c]);84938494if (orthogonal) {8495camera_distance = camera->get_size() / 2.0;8496Vector3 camera_direction = -camera->get_global_transform().get_basis().get_column(2);8497Plane grid_plane = Plane(normal);8498Vector3 intersection;8499if (grid_plane.intersects_ray(camera_position, camera_direction, &intersection)) {8500camera_position = intersection;8501}8502}85038504real_t division_level = Math::log(Math::abs(camera_distance)) / Math::log((double)primary_grid_steps) + division_level_bias;85058506real_t clamped_division_level = CLAMP(division_level, division_level_min, division_level_max);8507real_t division_level_floored = Math::floor(clamped_division_level);8508real_t division_level_decimals = clamped_division_level - division_level_floored;85098510real_t small_step_size = Math::pow(primary_grid_steps, division_level_floored);8511real_t large_step_size = small_step_size * primary_grid_steps;8512real_t center_a = large_step_size * (int)(camera_position[a] / large_step_size);8513real_t center_b = large_step_size * (int)(camera_position[b] / large_step_size);85148515real_t bgn_a = center_a - grid_size * small_step_size;8516real_t end_a = center_a + grid_size * small_step_size;8517real_t bgn_b = center_b - grid_size * small_step_size;8518real_t end_b = center_b + grid_size * small_step_size;85198520real_t fade_size = Math::pow(primary_grid_steps, division_level - 1.0);8521real_t min_fade_size = Math::pow(primary_grid_steps, float(division_level_min));8522real_t max_fade_size = Math::pow(primary_grid_steps, float(division_level_max));8523fade_size = CLAMP(fade_size, min_fade_size, max_fade_size);85248525real_t grid_fade_size = (grid_size - primary_grid_steps) * fade_size;8526grid_mat[c]->set_shader_parameter("grid_size", grid_fade_size);8527grid_mat[c]->set_shader_parameter("orthogonal", orthogonal);85288529LocalVector<Vector3> &ref_grid = grid_points[c];8530LocalVector<Vector3> &ref_grid_normals = grid_normals[c];8531LocalVector<Color> &ref_grid_colors = grid_colors[c];85328533// Count our elements same as code below it.8534int expected_size = 0;8535for (int i = -grid_size; i <= grid_size; i++) {8536const real_t position_a = center_a + i * small_step_size;8537const real_t position_b = center_b + i * small_step_size;85388539// Don't draw lines over the origin if it's enabled.8540if (!(origin_enabled && Math::is_zero_approx(position_a))) {8541expected_size += 2;8542}85438544if (!(origin_enabled && Math::is_zero_approx(position_b))) {8545expected_size += 2;8546}8547}85488549int idx = 0;8550ref_grid.resize(expected_size);8551ref_grid_normals.resize(expected_size);8552ref_grid_colors.resize(expected_size);85538554// In each iteration of this loop, draw one line in each direction (so two lines per loop, in each if statement).8555for (int i = -grid_size; i <= grid_size; i++) {8556Color line_color;8557// Is this a primary line? Set the appropriate color.8558if (i % primary_grid_steps == 0) {8559line_color = primary_grid_color.lerp(secondary_grid_color, division_level_decimals);8560} else {8561line_color = secondary_grid_color;8562line_color.a = line_color.a * (1 - division_level_decimals);8563}85648565real_t position_a = center_a + i * small_step_size;8566real_t position_b = center_b + i * small_step_size;85678568// Don't draw lines over the origin if it's enabled.8569if (!(origin_enabled && Math::is_zero_approx(position_a))) {8570Vector3 line_bgn;8571Vector3 line_end;8572line_bgn[a] = position_a;8573line_end[a] = position_a;8574line_bgn[b] = bgn_b;8575line_end[b] = end_b;8576ref_grid[idx] = line_bgn;8577ref_grid[idx + 1] = line_end;8578ref_grid_colors[idx] = line_color;8579ref_grid_colors[idx + 1] = line_color;8580ref_grid_normals[idx] = normal;8581ref_grid_normals[idx + 1] = normal;8582idx += 2;8583}85848585if (!(origin_enabled && Math::is_zero_approx(position_b))) {8586Vector3 line_bgn;8587Vector3 line_end;8588line_bgn[b] = position_b;8589line_end[b] = position_b;8590line_bgn[a] = bgn_a;8591line_end[a] = end_a;8592ref_grid[idx] = line_bgn;8593ref_grid[idx + 1] = line_end;8594ref_grid_colors[idx] = line_color;8595ref_grid_colors[idx + 1] = line_color;8596ref_grid_normals[idx] = normal;8597ref_grid_normals[idx + 1] = normal;8598idx += 2;8599}8600}86018602// Create a mesh from the pushed vector points and colors.8603grid[c] = RenderingServer::get_singleton()->mesh_create();8604Array d;8605d.resize(RS::ARRAY_MAX);8606d[RenderingServer::ARRAY_VERTEX] = (Vector<Vector3>)grid_points[c];8607d[RenderingServer::ARRAY_COLOR] = (Vector<Color>)grid_colors[c];8608d[RenderingServer::ARRAY_NORMAL] = (Vector<Vector3>)grid_normals[c];8609RenderingServer::get_singleton()->mesh_add_surface_from_arrays(grid[c], RenderingServer::PRIMITIVE_LINES, d);8610RenderingServer::get_singleton()->mesh_surface_set_material(grid[c], 0, grid_mat[c]->get_rid());8611grid_instance[c] = RenderingServer::get_singleton()->instance_create2(grid[c], get_tree()->get_root()->get_world_3d()->get_scenario());86128613// Yes, the end of this line is supposed to be a.8614RenderingServer::get_singleton()->instance_set_visible(grid_instance[c], grid_visible[a]);8615RenderingServer::get_singleton()->instance_geometry_set_cast_shadows_setting(grid_instance[c], RS::SHADOW_CASTING_SETTING_OFF);8616RS::get_singleton()->instance_set_layer_mask(grid_instance[c], 1 << Node3DEditorViewport::GIZMO_GRID_LAYER);8617RS::get_singleton()->instance_geometry_set_flag(grid_instance[c], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);8618RS::get_singleton()->instance_geometry_set_flag(grid_instance[c], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);8619}8620}86218622void Node3DEditor::_finish_indicators() {8623RenderingServer::get_singleton()->free_rid(origin_instance);8624RenderingServer::get_singleton()->free_rid(origin_multimesh);8625RenderingServer::get_singleton()->free_rid(origin_mesh);86268627_finish_grid();8628}86298630void Node3DEditor::_finish_grid() {8631for (int i = 0; i < 3; i++) {8632RenderingServer::get_singleton()->free_rid(grid_instance[i]);8633RenderingServer::get_singleton()->free_rid(grid[i]);8634}8635}86368637void Node3DEditor::update_gizmo_opacity() {8638if (!origin_instance.is_valid()) {8639return;8640}86418642const float opacity = EDITOR_GET("editors/3d/manipulator_gizmo_opacity");86438644for (int i = 0; i < 3; i++) {8645Color col = gizmo_color[i]->get_albedo();8646col.a = opacity;8647gizmo_color[i]->set_albedo(col);86488649col = gizmo_color_hl[i]->get_albedo();8650col.a = 1.0;8651gizmo_color_hl[i]->set_albedo(col);86528653col = plane_gizmo_color[i]->get_albedo();8654col.a = opacity;8655plane_gizmo_color[i]->set_albedo(col);86568657col = plane_gizmo_color_hl[i]->get_albedo();8658col.a = 1.0;8659plane_gizmo_color_hl[i]->set_albedo(col);8660}8661}86628663void Node3DEditor::update_grid() {8664const Camera3D::ProjectionType current_projection = viewports[0]->camera->get_projection();86658666if (current_projection != grid_camera_last_update_perspective) {8667grid_init_draw = false; // redraw8668grid_camera_last_update_perspective = current_projection;8669}86708671// Gets a orthogonal or perspective position correctly (for the grid comparison)8672const Vector3 camera_position = get_editor_viewport(0)->camera->get_position();86738674if (!grid_init_draw || grid_camera_last_update_position.distance_squared_to(camera_position) >= 100.0f) {8675_finish_grid();8676_init_grid();8677grid_init_draw = true;8678grid_camera_last_update_position = camera_position;8679}8680}86818682void Node3DEditor::_selection_changed() {8683_refresh_menu_icons();86848685const HashMap<ObjectID, Object *> &selection = editor_selection->get_selection();86868687for (const KeyValue<ObjectID, Object *> &E : selection) {8688Node3D *sp = ObjectDB::get_instance<Node3D>(E.key);8689if (!sp) {8690continue;8691}86928693Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);8694if (!se) {8695continue;8696}86978698if (sp == editor_selection->get_top_selected_node_list().back()->get()) {8699RenderingServer::get_singleton()->instance_set_base(se->sbox_instance, active_selection_box->get_rid());8700RenderingServer::get_singleton()->instance_set_base(se->sbox_instance_xray, active_selection_box_xray->get_rid());8701RenderingServer::get_singleton()->instance_set_base(se->sbox_instance_offset, active_selection_box->get_rid());8702RenderingServer::get_singleton()->instance_set_base(se->sbox_instance_xray_offset, active_selection_box_xray->get_rid());8703} else {8704RenderingServer::get_singleton()->instance_set_base(se->sbox_instance, selection_box->get_rid());8705RenderingServer::get_singleton()->instance_set_base(se->sbox_instance_xray, selection_box_xray->get_rid());8706RenderingServer::get_singleton()->instance_set_base(se->sbox_instance_offset, selection_box->get_rid());8707RenderingServer::get_singleton()->instance_set_base(se->sbox_instance_xray_offset, selection_box_xray->get_rid());8708}8709}87108711if (selected && editor_selection->get_top_selected_node_list().size() != 1) {8712Vector<Ref<Node3DGizmo>> gizmos = selected->get_gizmos();8713for (int i = 0; i < gizmos.size(); i++) {8714Ref<EditorNode3DGizmo> seg = gizmos[i];8715if (seg.is_null()) {8716continue;8717}8718seg->set_selected(false);8719}87208721Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected);8722if (se) {8723se->gizmo.unref();8724se->subgizmos.clear();8725}8726selected->update_gizmos();8727selected = nullptr;8728}87298730// Ensure gizmo updates are performed when the selection changes8731// outside of the 3D view (see GH-106713).8732if (!is_visible()) {8733const List<Node *> &top_selected = editor_selection->get_top_selected_node_list();8734if (top_selected.size() == 1) {8735Node3D *new_selected = Object::cast_to<Node3D>(top_selected.back()->get());8736if (new_selected != selected) {8737gizmos_dirty = true;8738}8739}8740}87418742update_transform_gizmo();8743}87448745void Node3DEditor::refresh_dirty_gizmos() {8746if (!gizmos_dirty) {8747return;8748}87498750const List<Node *> &top_selected = editor_selection->get_top_selected_node_list();8751if (top_selected.size() == 1) {8752Node3D *new_selected = Object::cast_to<Node3D>(top_selected.back()->get());8753if (new_selected != selected) {8754edit(new_selected);8755}8756}8757gizmos_dirty = false;8758}87598760void Node3DEditor::_refresh_menu_icons() {8761bool all_locked = true;8762bool all_grouped = true;8763bool has_node3d_item = false;87648765const List<Node *> &selection = editor_selection->get_top_selected_node_list();87668767if (selection.is_empty()) {8768all_locked = false;8769all_grouped = false;8770} else {8771for (Node *E : selection) {8772Node3D *node = Object::cast_to<Node3D>(E);8773if (node) {8774if (all_locked && !node->has_meta("_edit_lock_")) {8775all_locked = false;8776}8777if (all_grouped && !node->has_meta("_edit_group_")) {8778all_grouped = false;8779}8780has_node3d_item = true;8781}8782if (!all_locked && !all_grouped) {8783break;8784}8785}8786}87878788all_locked = all_locked && has_node3d_item;8789all_grouped = all_grouped && has_node3d_item;87908791tool_button[TOOL_LOCK_SELECTED]->set_visible(!all_locked);8792tool_button[TOOL_LOCK_SELECTED]->set_disabled(!has_node3d_item);8793tool_button[TOOL_UNLOCK_SELECTED]->set_visible(all_locked);8794tool_button[TOOL_UNLOCK_SELECTED]->set_disabled(!has_node3d_item);87958796tool_button[TOOL_GROUP_SELECTED]->set_visible(!all_grouped);8797tool_button[TOOL_GROUP_SELECTED]->set_disabled(!has_node3d_item);8798tool_button[TOOL_UNGROUP_SELECTED]->set_visible(all_grouped);8799tool_button[TOOL_UNGROUP_SELECTED]->set_disabled(!has_node3d_item);8800}88018802template <typename T>8803HashSet<T *> _get_child_nodes(Node *parent_node) {8804HashSet<T *> nodes = HashSet<T *>();8805T *node = Node::cast_to<T>(parent_node);8806if (node) {8807nodes.insert(node);8808}88098810for (int i = 0; i < parent_node->get_child_count(); i++) {8811Node *child_node = parent_node->get_child(i);8812HashSet<T *> child_nodes = _get_child_nodes<T>(child_node);8813for (T *I : child_nodes) {8814nodes.insert(I);8815}8816}88178818return nodes;8819}88208821HashSet<RID> _get_physics_bodies_rid(Node *node) {8822HashSet<RID> rids = HashSet<RID>();8823PhysicsBody3D *pb = Node::cast_to<PhysicsBody3D>(node);8824if (pb) {8825rids.insert(pb->get_rid());8826}8827HashSet<PhysicsBody3D *> child_nodes = _get_child_nodes<PhysicsBody3D>(node);8828for (const PhysicsBody3D *I : child_nodes) {8829rids.insert(I->get_rid());8830}88318832return rids;8833}88348835void Node3DEditor::snap_selected_nodes_to_floor() {8836do_snap_selected_nodes_to_floor = true;8837}88388839void Node3DEditor::_snap_selected_nodes_to_floor() {8840const List<Node *> &selection = editor_selection->get_top_selected_node_list();8841Dictionary snap_data;88428843for (Node *E : selection) {8844Node3D *sp = Object::cast_to<Node3D>(E);8845if (sp) {8846Vector3 from;8847Vector3 position_offset;88488849// Priorities for snapping to floor are CollisionShapes, VisualInstances and then origin8850HashSet<VisualInstance3D *> vi = _get_child_nodes<VisualInstance3D>(sp);8851HashSet<CollisionShape3D *> cs = _get_child_nodes<CollisionShape3D>(sp);8852bool found_valid_shape = false;88538854if (cs.size()) {8855AABB aabb;8856HashSet<CollisionShape3D *>::Iterator I = cs.begin();8857if ((*I)->get_shape().is_valid()) {8858CollisionShape3D *collision_shape = *cs.begin();8859aabb = collision_shape->get_global_transform().xform(collision_shape->get_shape()->get_debug_mesh()->get_aabb());8860found_valid_shape = true;8861}88628863for (++I; I; ++I) {8864CollisionShape3D *col_shape = *I;8865if (col_shape->get_shape().is_valid()) {8866aabb.merge_with(col_shape->get_global_transform().xform(col_shape->get_shape()->get_debug_mesh()->get_aabb()));8867found_valid_shape = true;8868}8869}8870if (found_valid_shape) {8871Vector3 size = aabb.size * Vector3(0.5, 0.0, 0.5);8872from = aabb.position + size;8873position_offset.y = from.y - sp->get_global_transform().origin.y;8874}8875}8876if (!found_valid_shape && vi.size()) {8877VisualInstance3D *begin = *vi.begin();8878AABB aabb = begin->get_global_transform().xform(begin->get_aabb());8879for (const VisualInstance3D *I : vi) {8880aabb.merge_with(I->get_global_transform().xform(I->get_aabb()));8881}8882Vector3 size = aabb.size * Vector3(0.5, 0.0, 0.5);8883from = aabb.position + size;8884position_offset.y = from.y - sp->get_global_transform().origin.y;8885} else if (!found_valid_shape) {8886from = sp->get_global_transform().origin;8887}88888889// We add a bit of margin to the from position to avoid it from snapping8890// when the spatial is already on a floor and there's another floor under8891// it8892from = from + Vector3(0.0, 1, 0.0);88938894Dictionary d;88958896d["from"] = from;8897d["position_offset"] = position_offset;8898snap_data[sp] = d;8899}8900}89018902PhysicsDirectSpaceState3D *ss = get_tree()->get_root()->get_world_3d()->get_direct_space_state();8903PhysicsDirectSpaceState3D::RayResult result;89048905// The maximum height an object can travel to be snapped8906const float max_snap_height = 500.0;89078908// Will be set to `true` if at least one node from the selection was successfully snapped8909bool snapped_to_floor = false;89108911if (!snap_data.is_empty()) {8912// For snapping to be performed, there must be solid geometry under at least one of the selected nodes.8913// We need to check this before snapping to register the undo/redo action only if needed.8914for (const KeyValue<Variant, Variant> &kv : snap_data) {8915Node *node = Object::cast_to<Node>(kv.key);8916Node3D *sp = Object::cast_to<Node3D>(node);8917Dictionary d = kv.value;8918Vector3 from = d["from"];8919Vector3 to = from - Vector3(0.0, max_snap_height, 0.0);8920HashSet<RID> excluded = _get_physics_bodies_rid(sp);89218922PhysicsDirectSpaceState3D::RayParameters ray_params;8923ray_params.from = from;8924ray_params.to = to;8925ray_params.exclude = excluded;89268927if (ss->intersect_ray(ray_params, result)) {8928snapped_to_floor = true;8929}8930}89318932if (snapped_to_floor) {8933EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();8934undo_redo->create_action(TTR("Snap Nodes to Floor"));89358936// Perform snapping if at least one node can be snapped8937for (const KeyValue<Variant, Variant> &kv : snap_data) {8938Node *node = Object::cast_to<Node>(kv.key);8939Node3D *sp = Object::cast_to<Node3D>(node);8940Dictionary d = kv.value;8941Vector3 from = d["from"];8942Vector3 to = from - Vector3(0.0, max_snap_height, 0.0);8943HashSet<RID> excluded = _get_physics_bodies_rid(sp);89448945PhysicsDirectSpaceState3D::RayParameters ray_params;8946ray_params.from = from;8947ray_params.to = to;8948ray_params.exclude = excluded;89498950if (ss->intersect_ray(ray_params, result)) {8951Vector3 position_offset = d["position_offset"];8952Transform3D new_transform = sp->get_global_transform();89538954new_transform.origin.y = result.position.y;8955new_transform.origin = new_transform.origin - position_offset;89568957Node3D *parent = sp->get_parent_node_3d();8958Transform3D new_local_xform = parent ? parent->get_global_transform().affine_inverse() * new_transform : new_transform;8959undo_redo->add_do_method(sp, "set_transform", new_local_xform);8960undo_redo->add_undo_method(sp, "set_transform", sp->get_transform());8961}8962}89638964undo_redo->commit_action();8965} else {8966EditorNode::get_singleton()->show_warning(TTR("Couldn't find a solid floor to snap the selection to."));8967}8968}8969}89708971void Node3DEditor::shortcut_input(const Ref<InputEvent> &p_event) {8972ERR_FAIL_COND(p_event.is_null());89738974if (!is_visible_in_tree()) {8975return;8976}89778978snap_key_enabled = Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL);8979}89808981void Node3DEditor::_sun_environ_settings_pressed() {8982Vector2 pos = sun_environ_settings->get_screen_position() + sun_environ_settings->get_size();8983sun_environ_popup->set_position(pos - Vector2(sun_environ_popup->get_contents_minimum_size().width / 2, 0));8984sun_environ_popup->reset_size();8985sun_environ_popup->popup();8986// Grabbing the focus is required for Shift modifier checking to be functional8987// (when the Add sun/environment buttons are pressed).8988sun_environ_popup->grab_focus();8989}89908991void Node3DEditor::_add_sun_to_scene(bool p_already_added_environment) {8992sun_environ_popup->hide();89938994if (!p_already_added_environment && world_env_count == 0 && Input::get_singleton()->is_key_pressed(Key::SHIFT)) {8995// Prevent infinite feedback loop between the sun and environment methods.8996_add_environment_to_scene(true);8997}89988999Node *base = get_tree()->get_edited_scene_root();9000if (!base) {9001// Create a root node so we can add child nodes to it.9002SceneTreeDock::get_singleton()->add_root_node(memnew(Node3D));9003base = get_tree()->get_edited_scene_root();9004}9005ERR_FAIL_NULL(base);9006Node *new_sun = preview_sun->duplicate();90079008EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();9009undo_redo->create_action(TTR("Add Preview Sun to Scene"));9010undo_redo->add_do_method(base, "add_child", new_sun, true);9011// Move to the beginning of the scene tree since more "global" nodes9012// generally look better when placed at the top.9013undo_redo->add_do_method(base, "move_child", new_sun, 0);9014undo_redo->add_do_method(new_sun, "set_owner", base);9015undo_redo->add_undo_method(base, "remove_child", new_sun);9016undo_redo->add_do_reference(new_sun);9017undo_redo->commit_action();9018}90199020void Node3DEditor::_add_environment_to_scene(bool p_already_added_sun) {9021sun_environ_popup->hide();90229023if (!p_already_added_sun && directional_light_count == 0 && Input::get_singleton()->is_key_pressed(Key::SHIFT)) {9024// Prevent infinite feedback loop between the sun and environment methods.9025_add_sun_to_scene(true);9026}90279028Node *base = get_tree()->get_edited_scene_root();9029if (!base) {9030// Create a root node so we can add child nodes to it.9031SceneTreeDock::get_singleton()->add_root_node(memnew(Node3D));9032base = get_tree()->get_edited_scene_root();9033}9034ERR_FAIL_NULL(base);90359036WorldEnvironment *new_env = memnew(WorldEnvironment);9037new_env->set_environment(preview_environment->get_environment()->duplicate(true));9038if (GLOBAL_GET("rendering/lights_and_shadows/use_physical_light_units")) {9039new_env->set_camera_attributes(preview_environment->get_camera_attributes()->duplicate(true));9040}90419042EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();9043undo_redo->create_action(TTR("Add Preview Environment to Scene"));9044undo_redo->add_do_method(base, "add_child", new_env, true);9045// Move to the beginning of the scene tree since more "global" nodes9046// generally look better when placed at the top.9047undo_redo->add_do_method(base, "move_child", new_env, 0);9048undo_redo->add_do_method(new_env, "set_owner", base);9049undo_redo->add_undo_method(base, "remove_child", new_env);9050undo_redo->add_do_reference(new_env);9051undo_redo->commit_action();9052}90539054void Node3DEditor::_update_theme() {9055tool_button[TOOL_MODE_TRANSFORM]->set_button_icon(get_editor_theme_icon(SNAME("ToolTransform")));9056tool_button[TOOL_MODE_MOVE]->set_button_icon(get_editor_theme_icon(SNAME("ToolMove")));9057tool_button[TOOL_MODE_ROTATE]->set_button_icon(get_editor_theme_icon(SNAME("ToolRotate")));9058tool_button[TOOL_MODE_SCALE]->set_button_icon(get_editor_theme_icon(SNAME("ToolScale")));9059tool_button[TOOL_MODE_SELECT]->set_button_icon(get_editor_theme_icon(SNAME("ToolSelect")));9060tool_button[TOOL_MODE_LIST_SELECT]->set_button_icon(get_editor_theme_icon(SNAME("ListSelect")));9061tool_button[TOOL_LOCK_SELECTED]->set_button_icon(get_editor_theme_icon(SNAME("Lock")));9062tool_button[TOOL_UNLOCK_SELECTED]->set_button_icon(get_editor_theme_icon(SNAME("Unlock")));9063tool_button[TOOL_GROUP_SELECTED]->set_button_icon(get_editor_theme_icon(SNAME("Group")));9064tool_button[TOOL_UNGROUP_SELECTED]->set_button_icon(get_editor_theme_icon(SNAME("Ungroup")));9065tool_button[TOOL_RULER]->set_button_icon(get_editor_theme_icon(SNAME("Ruler")));90669067tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_button_icon(get_editor_theme_icon(SNAME("Object")));9068tool_option_button[TOOL_OPT_USE_SNAP]->set_button_icon(get_editor_theme_icon(SNAME("Snap")));9069tool_option_button[TOOL_OPT_USE_TRACKBALL]->set_button_icon(get_editor_theme_icon(SNAME("Trackball")));90709071view_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")));9072view_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")));9073view_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")));9074view_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")));9075view_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")));9076view_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")));90779078sun_button->set_button_icon(get_editor_theme_icon(SNAME("PreviewSun")));9079environ_button->set_button_icon(get_editor_theme_icon(SNAME("PreviewEnvironment")));9080sun_environ_settings->set_button_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl")));90819082sun_title->add_theme_font_override(SceneStringName(font), get_theme_font(SNAME("title_font"), SNAME("Window")));9083environ_title->add_theme_font_override(SceneStringName(font), get_theme_font(SNAME("title_font"), SNAME("Window")));90849085sun_color->set_custom_minimum_size(Size2(0, get_theme_constant(SNAME("inspector_property_height"), EditorStringName(Editor))));9086environ_sky_color->set_custom_minimum_size(Size2(0, get_theme_constant(SNAME("inspector_property_height"), EditorStringName(Editor))));9087environ_ground_color->set_custom_minimum_size(Size2(0, get_theme_constant(SNAME("inspector_property_height"), EditorStringName(Editor))));90889089context_toolbar_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("ContextualToolbar"), EditorStringName(EditorStyles)));9090}90919092void Node3DEditor::_notification(int p_what) {9093switch (p_what) {9094case NOTIFICATION_TRANSLATION_CHANGED: {9095tool_button[TOOL_MODE_TRANSFORM]->set_tooltip_text(vformat(TTR("%s+Drag: Rotate selected node around pivot."), keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL)) + "\n" + TTR("Alt+RMB: Show list of all nodes at position clicked, including locked.") + "\n" + TTR("(Available in all modes.)"));9096tool_button[TOOL_MODE_MOVE]->set_tooltip_text(vformat(TTR("%s+Drag: Use snap."), keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL)) + "\n" + TTR("Alt+RMB: Show list of all nodes at position clicked, including locked."));9097tool_button[TOOL_MODE_ROTATE]->set_tooltip_text(vformat(TTR("%s+Drag: Use snap."), keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL)) + "\n" + TTR("Alt+RMB: Show list of all nodes at position clicked, including locked."));9098tool_button[TOOL_MODE_SCALE]->set_tooltip_text(vformat(TTR("%s+Drag: Use snap."), keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL)) + "\n" + TTR("Alt+RMB: Show list of all nodes at position clicked, including locked."));9099tool_button[TOOL_MODE_SELECT]->set_tooltip_text(TTR("Alt+RMB: Show list of all nodes at position clicked, including locked.") + "\n" + TTR("(Available in all modes.)"));9100_update_gizmos_menu();9101} break;91029103case NOTIFICATION_READY: {9104_menu_item_pressed(MENU_VIEW_USE_1_VIEWPORT);91059106_refresh_menu_icons();91079108get_tree()->connect("node_removed", callable_mp(this, &Node3DEditor::_node_removed));9109get_tree()->connect("node_added", callable_mp(this, &Node3DEditor::_node_added));9110SceneTreeDock::get_singleton()->get_tree_editor()->connect("node_changed", callable_mp(this, &Node3DEditor::_refresh_menu_icons));9111editor_selection->connect("selection_changed", callable_mp(this, &Node3DEditor::_selection_changed));91129113_update_preview_environment();91149115sun_state->set_custom_minimum_size(sun_vb->get_combined_minimum_size());9116environ_state->set_custom_minimum_size(environ_vb->get_combined_minimum_size());91179118ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &Node3DEditor::update_all_gizmos).bind(Variant()));9119} break;91209121case NOTIFICATION_ENTER_TREE: {9122_update_theme();9123_register_all_gizmos();9124_init_indicators();9125update_all_gizmos();9126} break;91279128case NOTIFICATION_EXIT_TREE: {9129_finish_indicators();9130} break;91319132case NOTIFICATION_THEME_CHANGED: {9133_update_theme();9134_update_gizmos_menu_theme();9135sun_title->add_theme_font_override(SceneStringName(font), get_theme_font(SNAME("title_font"), SNAME("Window")));9136environ_title->add_theme_font_override(SceneStringName(font), get_theme_font(SNAME("title_font"), SNAME("Window")));9137} break;91389139case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {9140if (EditorSettings::get_singleton()->check_changed_settings_in_group("editors/3d")) {9141const Color selection_box_color = EDITOR_GET("editors/3d/selection_box_color");9142const Color active_selection_box_color = EDITOR_GET("editors/3d/active_selection_box_color");91439144if (selection_box_color != selection_box_mat->get_albedo()) {9145selection_box_mat->set_albedo(selection_box_color);9146selection_box_mat_xray->set_albedo(selection_box_color * Color(1, 1, 1, 0.15));9147}91489149if (active_selection_box_color != active_selection_box_mat->get_albedo()) {9150active_selection_box_mat->set_albedo(active_selection_box_color);9151active_selection_box_mat_xray->set_albedo(active_selection_box_color * Color(1, 1, 1, 0.15));9152}91539154// Update grid color by rebuilding grid.9155_finish_grid();9156_init_grid();91579158for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) {9159viewports[i]->update_transform_gizmo_view();9160}9161update_gizmo_opacity();9162}9163if (EditorSettings::get_singleton()->check_changed_settings_in_group("editors/3d_gizmos/gizmo_settings")) {9164CollisionShape3DGizmoPlugin::set_show_only_when_selected(EDITOR_GET("editors/3d_gizmos/gizmo_settings/show_collision_shapes_only_when_selected"));9165update_all_gizmos();9166}9167} break;91689169case NOTIFICATION_PHYSICS_PROCESS: {9170if (do_snap_selected_nodes_to_floor) {9171_snap_selected_nodes_to_floor();9172do_snap_selected_nodes_to_floor = false;9173}9174}9175}9176}91779178bool Node3DEditor::is_subgizmo_selected(int p_id) {9179Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr;9180if (se) {9181return se->subgizmos.has(p_id);9182}9183return false;9184}91859186bool Node3DEditor::is_current_selected_gizmo(const EditorNode3DGizmo *p_gizmo) {9187Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr;9188if (se) {9189return se->gizmo == p_gizmo;9190}9191return false;9192}91939194Vector<int> Node3DEditor::get_subgizmo_selection() {9195Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr;91969197Vector<int> ret;9198if (se) {9199for (const KeyValue<int, Transform3D> &E : se->subgizmos) {9200ret.push_back(E.key);9201}9202}9203return ret;9204}92059206void Node3DEditor::clear_subgizmo_selection(Object *p_obj) {9207_clear_subgizmo_selection(p_obj);9208}92099210void Node3DEditor::add_control_to_menu_panel(Control *p_control) {9211ERR_FAIL_NULL(p_control);9212ERR_FAIL_COND(p_control->get_parent());92139214VSeparator *sep = memnew(VSeparator);9215context_toolbar_hbox->add_child(sep);9216context_toolbar_hbox->add_child(p_control);9217context_toolbar_separators[p_control] = sep;92189219p_control->connect(SceneStringName(visibility_changed), callable_mp(this, &Node3DEditor::_update_context_toolbar));92209221_update_context_toolbar();9222}92239224void Node3DEditor::remove_control_from_menu_panel(Control *p_control) {9225ERR_FAIL_NULL(p_control);9226ERR_FAIL_COND(p_control->get_parent() != context_toolbar_hbox);92279228p_control->disconnect(SceneStringName(visibility_changed), callable_mp(this, &Node3DEditor::_update_context_toolbar));92299230VSeparator *sep = context_toolbar_separators[p_control];9231context_toolbar_hbox->remove_child(sep);9232context_toolbar_hbox->remove_child(p_control);9233context_toolbar_separators.erase(p_control);9234memdelete(sep);92359236_update_context_toolbar();9237}92389239void Node3DEditor::_update_context_toolbar() {9240bool has_visible = false;9241bool first_visible = false;92429243for (int i = 0; i < context_toolbar_hbox->get_child_count(); i++) {9244Control *child = Object::cast_to<Control>(context_toolbar_hbox->get_child(i));9245if (!child || !context_toolbar_separators.has(child)) {9246continue;9247}9248if (child->is_visible()) {9249first_visible = !has_visible;9250has_visible = true;9251}92529253VSeparator *sep = context_toolbar_separators[child];9254sep->set_visible(!first_visible && child->is_visible());9255}92569257context_toolbar_panel->set_visible(has_visible);9258}92599260void Node3DEditor::set_can_preview(Camera3D *p_preview) {9261for (int i = 0; i < 4; i++) {9262viewports[i]->set_can_preview(p_preview);9263}9264}92659266VSplitContainer *Node3DEditor::get_shader_split() {9267return shader_split;9268}92699270Node3DEditorViewport *Node3DEditor::get_last_used_viewport() {9271return viewports[last_used_viewport];9272}92739274void Node3DEditor::add_control_to_left_panel(Control *p_control) {9275left_panel_split->add_child(p_control);9276left_panel_split->move_child(p_control, 0);9277}92789279void Node3DEditor::add_control_to_right_panel(Control *p_control) {9280right_panel_split->add_child(p_control);9281right_panel_split->move_child(p_control, 1);9282}92839284void Node3DEditor::remove_control_from_left_panel(Control *p_control) {9285left_panel_split->remove_child(p_control);9286}92879288void Node3DEditor::remove_control_from_right_panel(Control *p_control) {9289right_panel_split->remove_child(p_control);9290}92919292void Node3DEditor::move_control_to_left_panel(Control *p_control) {9293ERR_FAIL_NULL(p_control);9294if (p_control->get_parent() == left_panel_split) {9295return;9296}92979298ERR_FAIL_COND(p_control->get_parent() != right_panel_split);9299right_panel_split->remove_child(p_control);93009301add_control_to_left_panel(p_control);9302}93039304void Node3DEditor::move_control_to_right_panel(Control *p_control) {9305ERR_FAIL_NULL(p_control);9306if (p_control->get_parent() == right_panel_split) {9307return;9308}93099310ERR_FAIL_COND(p_control->get_parent() != left_panel_split);9311left_panel_split->remove_child(p_control);93129313add_control_to_right_panel(p_control);9314}93159316void Node3DEditor::_request_gizmo(Object *p_obj) {9317Node3D *sp = Object::cast_to<Node3D>(p_obj);9318if (!sp) {9319return;9320}93219322bool is_selected = (sp == selected);93239324Node *edited_scene = EditorNode::get_singleton()->get_edited_scene();9325if (edited_scene && (sp == edited_scene || (sp->get_owner() && edited_scene->is_ancestor_of(sp)))) {9326for (int i = 0; i < gizmo_plugins_by_priority.size(); ++i) {9327Ref<EditorNode3DGizmo> seg = gizmo_plugins_by_priority.write[i]->get_gizmo(sp);93289329if (seg.is_valid()) {9330sp->add_gizmo(seg);93319332if (is_selected != seg->is_selected()) {9333seg->set_selected(is_selected);9334}9335}9336}9337if (!sp->get_gizmos().is_empty()) {9338sp->update_gizmos();9339}9340}9341}93429343void Node3DEditor::_request_gizmo_for_id(ObjectID p_id) {9344Node3D *node = ObjectDB::get_instance<Node3D>(p_id);9345if (node) {9346_request_gizmo(node);9347}9348}93499350void Node3DEditor::_set_subgizmo_selection(Object *p_obj, Ref<Node3DGizmo> p_gizmo, int p_id, Transform3D p_transform) {9351if (p_id == -1) {9352_clear_subgizmo_selection(p_obj);9353return;9354}93559356Node3D *sp = nullptr;9357if (p_obj) {9358sp = Object::cast_to<Node3D>(p_obj);9359} else {9360sp = selected;9361}93629363if (!sp) {9364return;9365}93669367Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);9368if (se) {9369se->subgizmos.clear();9370se->subgizmos.insert(p_id, p_transform);9371se->gizmo = p_gizmo;9372sp->update_gizmos();9373update_transform_gizmo();9374}9375}93769377void Node3DEditor::_clear_subgizmo_selection(Object *p_obj) {9378Node3D *sp = nullptr;9379if (p_obj) {9380sp = Object::cast_to<Node3D>(p_obj);9381} else {9382sp = selected;9383}93849385if (!sp) {9386return;9387}93889389Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);9390if (se) {9391se->subgizmos.clear();9392se->gizmo.unref();9393sp->update_gizmos();9394update_transform_gizmo();9395}9396}93979398void Node3DEditor::_toggle_maximize_view(Object *p_viewport) {9399if (!p_viewport) {9400return;9401}9402Node3DEditorViewport *current_viewport = Object::cast_to<Node3DEditorViewport>(p_viewport);9403if (!current_viewport) {9404return;9405}94069407int index = -1;9408bool maximized = false;9409for (int i = 0; i < 4; i++) {9410if (viewports[i] == current_viewport) {9411index = i;9412if (current_viewport->get_global_rect() == viewport_base->get_global_rect()) {9413maximized = true;9414}9415break;9416}9417}9418if (index == -1) {9419return;9420}94219422if (!maximized) {9423for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) {9424if (i == (uint32_t)index) {9425viewports[i]->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);9426} else {9427viewports[i]->hide();9428}9429}9430} else {9431for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) {9432viewports[i]->show();9433}94349435if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT))) {9436_menu_item_pressed(MENU_VIEW_USE_1_VIEWPORT);9437} else if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS))) {9438_menu_item_pressed(MENU_VIEW_USE_2_VIEWPORTS);9439} else if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT))) {9440_menu_item_pressed(MENU_VIEW_USE_2_VIEWPORTS_ALT);9441} else if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS))) {9442_menu_item_pressed(MENU_VIEW_USE_3_VIEWPORTS);9443} else if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT))) {9444_menu_item_pressed(MENU_VIEW_USE_3_VIEWPORTS_ALT);9445} else if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS))) {9446_menu_item_pressed(MENU_VIEW_USE_4_VIEWPORTS);9447}9448}9449}94509451void Node3DEditor::_viewport_clicked(int p_viewport_idx) {9452last_used_viewport = p_viewport_idx;9453}94549455void Node3DEditor::_node_added(Node *p_node) {9456if (EditorNode::get_singleton()->get_scene_root()->is_ancestor_of(p_node)) {9457if (Object::cast_to<WorldEnvironment>(p_node)) {9458world_env_count++;9459if (world_env_count == 1) {9460_update_preview_environment();9461}9462} else if (Object::cast_to<DirectionalLight3D>(p_node)) {9463directional_light_count++;9464if (directional_light_count == 1) {9465_update_preview_environment();9466}9467}9468}9469}94709471void Node3DEditor::_node_removed(Node *p_node) {9472if (EditorNode::get_singleton()->get_scene_root()->is_ancestor_of(p_node)) {9473if (Object::cast_to<WorldEnvironment>(p_node)) {9474world_env_count--;9475if (world_env_count == 0) {9476_update_preview_environment();9477}9478} else if (Object::cast_to<DirectionalLight3D>(p_node)) {9479directional_light_count--;9480if (directional_light_count == 0) {9481_update_preview_environment();9482}9483}9484}94859486if (p_node == selected) {9487Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected);9488if (se) {9489se->gizmo.unref();9490se->subgizmos.clear();9491}9492selected = nullptr;9493update_transform_gizmo();9494}9495}94969497void Node3DEditor::_register_all_gizmos() {9498add_gizmo_plugin(Ref<Camera3DGizmoPlugin>(memnew(Camera3DGizmoPlugin)));9499add_gizmo_plugin(Ref<Light3DGizmoPlugin>(memnew(Light3DGizmoPlugin)));9500add_gizmo_plugin(Ref<AudioStreamPlayer3DGizmoPlugin>(memnew(AudioStreamPlayer3DGizmoPlugin)));9501add_gizmo_plugin(Ref<AudioListener3DGizmoPlugin>(memnew(AudioListener3DGizmoPlugin)));9502add_gizmo_plugin(Ref<MeshInstance3DGizmoPlugin>(memnew(MeshInstance3DGizmoPlugin)));9503add_gizmo_plugin(Ref<OccluderInstance3DGizmoPlugin>(memnew(OccluderInstance3DGizmoPlugin)));9504add_gizmo_plugin(Ref<SoftBody3DGizmoPlugin>(memnew(SoftBody3DGizmoPlugin)));9505add_gizmo_plugin(Ref<SpriteBase3DGizmoPlugin>(memnew(SpriteBase3DGizmoPlugin)));9506add_gizmo_plugin(Ref<Label3DGizmoPlugin>(memnew(Label3DGizmoPlugin)));9507add_gizmo_plugin(Ref<GeometryInstance3DGizmoPlugin>(memnew(GeometryInstance3DGizmoPlugin)));9508add_gizmo_plugin(Ref<Marker3DGizmoPlugin>(memnew(Marker3DGizmoPlugin)));9509add_gizmo_plugin(Ref<RayCast3DGizmoPlugin>(memnew(RayCast3DGizmoPlugin)));9510add_gizmo_plugin(Ref<ShapeCast3DGizmoPlugin>(memnew(ShapeCast3DGizmoPlugin)));9511add_gizmo_plugin(Ref<SpringArm3DGizmoPlugin>(memnew(SpringArm3DGizmoPlugin)));9512add_gizmo_plugin(Ref<SpringBoneCollision3DGizmoPlugin>(memnew(SpringBoneCollision3DGizmoPlugin)));9513add_gizmo_plugin(Ref<SpringBoneSimulator3DGizmoPlugin>(memnew(SpringBoneSimulator3DGizmoPlugin)));9514add_gizmo_plugin(Ref<VehicleWheel3DGizmoPlugin>(memnew(VehicleWheel3DGizmoPlugin)));9515add_gizmo_plugin(Ref<VisibleOnScreenNotifier3DGizmoPlugin>(memnew(VisibleOnScreenNotifier3DGizmoPlugin)));9516add_gizmo_plugin(Ref<GPUParticles3DGizmoPlugin>(memnew(GPUParticles3DGizmoPlugin)));9517add_gizmo_plugin(Ref<GPUParticlesCollision3DGizmoPlugin>(memnew(GPUParticlesCollision3DGizmoPlugin)));9518add_gizmo_plugin(Ref<Particles3DEmissionShapeGizmoPlugin>(memnew(Particles3DEmissionShapeGizmoPlugin)));9519add_gizmo_plugin(Ref<CPUParticles3DGizmoPlugin>(memnew(CPUParticles3DGizmoPlugin)));9520add_gizmo_plugin(Ref<ReflectionProbeGizmoPlugin>(memnew(ReflectionProbeGizmoPlugin)));9521add_gizmo_plugin(Ref<DecalGizmoPlugin>(memnew(DecalGizmoPlugin)));9522add_gizmo_plugin(Ref<VoxelGIGizmoPlugin>(memnew(VoxelGIGizmoPlugin)));9523add_gizmo_plugin(Ref<LightmapGIGizmoPlugin>(memnew(LightmapGIGizmoPlugin)));9524add_gizmo_plugin(Ref<LightmapProbeGizmoPlugin>(memnew(LightmapProbeGizmoPlugin)));9525add_gizmo_plugin(Ref<CollisionObject3DGizmoPlugin>(memnew(CollisionObject3DGizmoPlugin)));9526add_gizmo_plugin(Ref<CollisionShape3DGizmoPlugin>(memnew(CollisionShape3DGizmoPlugin)));9527add_gizmo_plugin(Ref<CollisionPolygon3DGizmoPlugin>(memnew(CollisionPolygon3DGizmoPlugin)));9528add_gizmo_plugin(Ref<Joint3DGizmoPlugin>(memnew(Joint3DGizmoPlugin)));9529add_gizmo_plugin(Ref<PhysicalBone3DGizmoPlugin>(memnew(PhysicalBone3DGizmoPlugin)));9530add_gizmo_plugin(Ref<FogVolumeGizmoPlugin>(memnew(FogVolumeGizmoPlugin)));9531add_gizmo_plugin(Ref<TwoBoneIK3DGizmoPlugin>(memnew(TwoBoneIK3DGizmoPlugin)));9532add_gizmo_plugin(Ref<ChainIK3DGizmoPlugin>(memnew(ChainIK3DGizmoPlugin)));9533}95349535void Node3DEditor::_bind_methods() {9536ClassDB::bind_method("_get_editor_data", &Node3DEditor::_get_editor_data);9537ClassDB::bind_method("_request_gizmo", &Node3DEditor::_request_gizmo);9538ClassDB::bind_method("_request_gizmo_for_id", &Node3DEditor::_request_gizmo_for_id);9539ClassDB::bind_method("_set_subgizmo_selection", &Node3DEditor::_set_subgizmo_selection);9540ClassDB::bind_method("_clear_subgizmo_selection", &Node3DEditor::_clear_subgizmo_selection);9541ClassDB::bind_method("_refresh_menu_icons", &Node3DEditor::_refresh_menu_icons);9542ClassDB::bind_method("_preview_settings_changed", &Node3DEditor::_preview_settings_changed);95439544ClassDB::bind_method("update_all_gizmos", &Node3DEditor::update_all_gizmos);9545ClassDB::bind_method("update_transform_gizmo", &Node3DEditor::update_transform_gizmo);95469547ADD_SIGNAL(MethodInfo("transform_key_request"));9548ADD_SIGNAL(MethodInfo("item_lock_status_changed"));9549ADD_SIGNAL(MethodInfo("item_group_status_changed"));9550}95519552void Node3DEditor::clear() {9553settings_fov->set_value(EDITOR_GET("editors/3d/default_fov"));9554settings_znear->set_value(EDITOR_GET("editors/3d/default_z_near"));9555settings_zfar->set_value(EDITOR_GET("editors/3d/default_z_far"));95569557snap_translate_value = EditorSettings::get_singleton()->get_project_metadata("3d_editor", "snap_translate_value", 1);9558snap_rotate_value = EditorSettings::get_singleton()->get_project_metadata("3d_editor", "snap_rotate_value", 15);9559snap_scale_value = EditorSettings::get_singleton()->get_project_metadata("3d_editor", "snap_scale_value", 10);9560_snap_update();95619562for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) {9563viewports[i]->reset();9564}95659566if (origin_instance.is_valid()) {9567RenderingServer::get_singleton()->instance_set_visible(origin_instance, true);9568}95699570view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_ORIGIN), true);9571for (int i = 0; i < 3; ++i) {9572if (grid_enable[i]) {9573grid_visible[i] = true;9574}9575}95769577for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) {9578viewports[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);9579viewports[i]->viewport->set_as_audio_listener_3d(i == 0);9580}95819582view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_GRID), true);9583grid_enabled = true;9584grid_init_draw = false;9585}95869587void Node3DEditor::_sun_direction_draw() {9588sun_direction->draw_rect(Rect2(Vector2(), sun_direction->get_size()), Color(1, 1, 1, 1));9589Vector3 z_axis = preview_sun->get_transform().basis.get_column(Vector3::AXIS_Z);9590z_axis = get_editor_viewport(0)->camera->get_camera_transform().basis.xform_inv(z_axis);9591sun_direction_material->set_shader_parameter("sun_direction", Vector3(z_axis.x, -z_axis.y, z_axis.z));9592Color color = sun_color->get_pick_color() * sun_energy->get_value();9593sun_direction_material->set_shader_parameter("sun_color", Vector3(color.r, color.g, color.b));9594}95959596void Node3DEditor::_preview_settings_changed() {9597if (sun_environ_updating) {9598return;9599}96009601{ // preview sun9602sun_rotation.x = Math::deg_to_rad(-sun_angle_altitude->get_value());9603sun_rotation.y = Math::deg_to_rad(180.0 - sun_angle_azimuth->get_value());9604Transform3D t;9605t.basis = Basis::from_euler(Vector3(sun_rotation.x, sun_rotation.y, 0));9606preview_sun->set_transform(t);9607sun_direction->queue_redraw();9608preview_sun->set_param(Light3D::PARAM_ENERGY, sun_energy->get_value());9609preview_sun->set_param(Light3D::PARAM_SHADOW_MAX_DISTANCE, sun_shadow_max_distance->get_value());9610preview_sun->set_color(sun_color->get_pick_color());9611}96129613{ //preview env9614sky_material->set_energy_multiplier(environ_energy->get_value());9615Color hz_color = environ_sky_color->get_pick_color().lerp(environ_ground_color->get_pick_color(), 0.5);9616float hz_lum = hz_color.get_luminance() * 3.333;9617hz_color = hz_color.lerp(Color(hz_lum, hz_lum, hz_lum), 0.5);9618sky_material->set_sky_top_color(environ_sky_color->get_pick_color());9619sky_material->set_sky_horizon_color(hz_color);9620sky_material->set_ground_bottom_color(environ_ground_color->get_pick_color());9621sky_material->set_ground_horizon_color(hz_color);96229623environment->set_ssao_enabled(environ_ao_button->is_pressed());9624environment->set_glow_enabled(environ_glow_button->is_pressed());9625environment->set_sdfgi_enabled(environ_gi_button->is_pressed());9626environment->set_tonemapper(environ_tonemap_button->is_pressed() ? Environment::TONE_MAPPER_FILMIC : Environment::TONE_MAPPER_LINEAR);9627}9628}96299630void Node3DEditor::_load_default_preview_settings() {9631sun_environ_updating = true;96329633// These default rotations place the preview sun at an angular altitude9634// of 60 degrees (must be negative) and an azimuth of 30 degrees clockwise9635// from north (or 150 CCW from south), from north east, facing south west.9636// On any not-tidally-locked planet, a sun would have an angular altitude9637// of 60 degrees as the average of all points on the sphere at noon.9638// The azimuth choice is arbitrary, but ideally shouldn't be on an axis.9639sun_rotation = Vector2(-Math::deg_to_rad(60.0), Math::deg_to_rad(150.0));96409641sun_angle_altitude->set_value_no_signal(-Math::rad_to_deg(sun_rotation.x));9642sun_angle_azimuth->set_value_no_signal(180.0 - Math::rad_to_deg(sun_rotation.y));9643sun_direction->queue_redraw();9644environ_sky_color->set_pick_color(Color(0.385, 0.454, 0.55));9645environ_ground_color->set_pick_color(Color(0.2, 0.169, 0.133));9646environ_energy->set_value_no_signal(1.0);9647if (OS::get_singleton()->get_current_rendering_method() != "gl_compatibility" && OS::get_singleton()->get_current_rendering_method() != "dummy") {9648environ_glow_button->set_pressed_no_signal(true);9649}9650environ_tonemap_button->set_pressed_no_signal(true);9651environ_ao_button->set_pressed_no_signal(false);9652environ_gi_button->set_pressed_no_signal(false);9653sun_shadow_max_distance->set_value_no_signal(100);96549655sun_color->set_pick_color(Color(1, 1, 1));9656sun_energy->set_value_no_signal(1.0);96579658sun_environ_updating = false;9659}96609661void Node3DEditor::_update_preview_environment() {9662bool disable_light = directional_light_count > 0 || !sun_button->is_pressed();96639664sun_button->set_disabled(directional_light_count > 0);96659666if (disable_light) {9667if (preview_sun->get_parent()) {9668preview_sun->get_parent()->remove_child(preview_sun);9669sun_state->show();9670sun_vb->hide();9671preview_sun_dangling = true;9672}96739674if (directional_light_count > 0) {9675sun_state->set_text(TTRC("Scene contains\nDirectionalLight3D.\nPreview disabled."));9676} else {9677sun_state->set_text(TTRC("Preview disabled."));9678}96799680} else {9681if (!preview_sun->get_parent()) {9682add_child(preview_sun, true);9683sun_state->hide();9684sun_vb->show();9685preview_sun_dangling = false;9686}9687}96889689sun_angle_altitude->set_value_no_signal(-Math::rad_to_deg(sun_rotation.x));9690sun_angle_azimuth->set_value_no_signal(180.0 - Math::rad_to_deg(sun_rotation.y));96919692bool disable_env = world_env_count > 0 || !environ_button->is_pressed();96939694environ_button->set_disabled(world_env_count > 0);96959696if (disable_env) {9697if (preview_environment->get_parent()) {9698preview_environment->get_parent()->remove_child(preview_environment);9699environ_state->show();9700environ_vb->hide();9701preview_env_dangling = true;9702}9703if (world_env_count > 0) {9704environ_state->set_text(TTRC("Scene contains\nWorldEnvironment.\nPreview disabled."));9705} else {9706environ_state->set_text(TTRC("Preview disabled."));9707}97089709} else {9710if (!preview_environment->get_parent()) {9711add_child(preview_environment);9712environ_state->hide();9713environ_vb->show();9714preview_env_dangling = false;9715}9716}9717}97189719void Node3DEditor::_sun_direction_input(const Ref<InputEvent> &p_event) {9720Ref<InputEventMouseMotion> mm = p_event;9721if (mm.is_valid() && mm->get_button_mask().has_flag(MouseButtonMask::LEFT)) {9722sun_rotation.x += mm->get_relative().y * (0.02 * EDSCALE);9723sun_rotation.y -= mm->get_relative().x * (0.02 * EDSCALE);9724sun_rotation.x = CLAMP(sun_rotation.x, -Math::TAU / 4, Math::TAU / 4);97259726EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();9727undo_redo->create_action(TTR("Set Preview Sun Direction"), UndoRedo::MergeMode::MERGE_ENDS);9728undo_redo->add_do_method(sun_angle_altitude, "set_value_no_signal", -Math::rad_to_deg(sun_rotation.x));9729undo_redo->add_undo_method(sun_angle_altitude, "set_value_no_signal", sun_angle_altitude->get_value());9730undo_redo->add_do_method(sun_angle_azimuth, "set_value_no_signal", 180.0 - Math::rad_to_deg(sun_rotation.y));9731undo_redo->add_undo_method(sun_angle_azimuth, "set_value_no_signal", sun_angle_azimuth->get_value());9732undo_redo->add_do_method(this, "_preview_settings_changed");9733undo_redo->add_undo_method(this, "_preview_settings_changed");9734undo_redo->commit_action();9735}9736}97379738void Node3DEditor::_sun_direction_set_altitude(float p_altitude) {9739EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();9740undo_redo->create_action(TTR("Set Preview Sun Altitude"), UndoRedo::MergeMode::MERGE_ENDS);9741undo_redo->add_do_method(sun_angle_altitude, "set_value_no_signal", p_altitude);9742undo_redo->add_undo_method(sun_angle_altitude, "set_value_no_signal", -Math::rad_to_deg(sun_rotation.x));9743undo_redo->add_do_method(this, "_preview_settings_changed");9744undo_redo->add_undo_method(this, "_preview_settings_changed");9745undo_redo->commit_action();9746}97479748void Node3DEditor::_sun_direction_set_azimuth(float p_azimuth) {9749EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();9750undo_redo->create_action(TTR("Set Preview Sun Azimuth"), UndoRedo::MergeMode::MERGE_ENDS);9751undo_redo->add_do_method(sun_angle_azimuth, "set_value_no_signal", p_azimuth);9752undo_redo->add_undo_method(sun_angle_azimuth, "set_value_no_signal", 180.0 - Math::rad_to_deg(sun_rotation.y));9753undo_redo->add_do_method(this, "_preview_settings_changed");9754undo_redo->add_undo_method(this, "_preview_settings_changed");9755undo_redo->commit_action();9756}97579758void Node3DEditor::_sun_set_color(const Color &p_color) {9759EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();9760undo_redo->create_action(TTR("Set Preview Sun Color"), UndoRedo::MergeMode::MERGE_ENDS);9761undo_redo->add_do_method(sun_color, "set_pick_color", p_color);9762undo_redo->add_undo_method(sun_color, "set_pick_color", preview_sun->get_color());9763undo_redo->add_do_method(this, "_preview_settings_changed");9764undo_redo->add_undo_method(this, "_preview_settings_changed");9765undo_redo->commit_action();9766}97679768void Node3DEditor::_sun_set_energy(float p_energy) {9769EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();9770undo_redo->create_action(TTR("Set Preview Sun Energy"), UndoRedo::MergeMode::MERGE_ENDS);9771undo_redo->add_do_method(sun_energy, "set_value_no_signal", p_energy);9772undo_redo->add_undo_method(sun_energy, "set_value_no_signal", preview_sun->get_param(Light3D::PARAM_ENERGY));9773undo_redo->add_do_method(this, "_preview_settings_changed");9774undo_redo->add_undo_method(this, "_preview_settings_changed");9775undo_redo->commit_action();9776}97779778void Node3DEditor::_sun_set_shadow_max_distance(float p_shadow_max_distance) {9779EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();9780undo_redo->create_action(TTR("Set Preview Sun Max Shadow Distance"), UndoRedo::MergeMode::MERGE_ENDS);9781undo_redo->add_do_method(sun_shadow_max_distance, "set_value_no_signal", p_shadow_max_distance);9782undo_redo->add_undo_method(sun_shadow_max_distance, "set_value_no_signal", preview_sun->get_param(Light3D::PARAM_SHADOW_MAX_DISTANCE));9783undo_redo->add_do_method(this, "_preview_settings_changed");9784undo_redo->add_undo_method(this, "_preview_settings_changed");9785undo_redo->commit_action();9786}97879788void Node3DEditor::_environ_set_sky_color(const Color &p_color) {9789EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();9790undo_redo->create_action(TTR("Set Preview Environment Sky Color"), UndoRedo::MergeMode::MERGE_ENDS);9791undo_redo->add_do_method(environ_sky_color, "set_pick_color", p_color);9792undo_redo->add_undo_method(environ_sky_color, "set_pick_color", sky_material->get_sky_top_color());9793undo_redo->add_do_method(this, "_preview_settings_changed");9794undo_redo->add_undo_method(this, "_preview_settings_changed");9795undo_redo->commit_action();9796}97979798void Node3DEditor::_environ_set_ground_color(const Color &p_color) {9799EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();9800undo_redo->create_action(TTR("Set Preview Environment Ground Color"), UndoRedo::MergeMode::MERGE_ENDS);9801undo_redo->add_do_method(environ_ground_color, "set_pick_color", p_color);9802undo_redo->add_undo_method(environ_ground_color, "set_pick_color", sky_material->get_ground_bottom_color());9803undo_redo->add_do_method(this, "_preview_settings_changed");9804undo_redo->add_undo_method(this, "_preview_settings_changed");9805undo_redo->commit_action();9806}98079808void Node3DEditor::_environ_set_sky_energy(float p_energy) {9809EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();9810undo_redo->create_action(TTR("Set Preview Environment Energy"), UndoRedo::MergeMode::MERGE_ENDS);9811undo_redo->add_do_method(environ_energy, "set_value_no_signal", p_energy);9812undo_redo->add_undo_method(environ_energy, "set_value_no_signal", sky_material->get_energy_multiplier());9813undo_redo->add_do_method(this, "_preview_settings_changed");9814undo_redo->add_undo_method(this, "_preview_settings_changed");9815undo_redo->commit_action();9816}98179818void Node3DEditor::_environ_set_ao() {9819EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();9820undo_redo->create_action(TTR("Set Preview Environment Ambient Occlusion"));9821undo_redo->add_do_method(environ_ao_button, "set_pressed", environ_ao_button->is_pressed());9822undo_redo->add_undo_method(environ_ao_button, "set_pressed", !environ_ao_button->is_pressed());9823undo_redo->add_do_method(this, "_preview_settings_changed");9824undo_redo->add_undo_method(this, "_preview_settings_changed");9825undo_redo->commit_action();9826}98279828void Node3DEditor::_environ_set_glow() {9829EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();9830undo_redo->create_action(TTR("Set Preview Environment Glow"));9831undo_redo->add_do_method(environ_glow_button, "set_pressed", environ_glow_button->is_pressed());9832undo_redo->add_undo_method(environ_glow_button, "set_pressed", !environ_glow_button->is_pressed());9833undo_redo->add_do_method(this, "_preview_settings_changed");9834undo_redo->add_undo_method(this, "_preview_settings_changed");9835undo_redo->commit_action();9836}98379838void Node3DEditor::_environ_set_tonemap() {9839EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();9840undo_redo->create_action(TTR("Set Preview Environment Tonemap"));9841undo_redo->add_do_method(environ_tonemap_button, "set_pressed", environ_tonemap_button->is_pressed());9842undo_redo->add_undo_method(environ_tonemap_button, "set_pressed", !environ_tonemap_button->is_pressed());9843undo_redo->add_do_method(this, "_preview_settings_changed");9844undo_redo->add_undo_method(this, "_preview_settings_changed");9845undo_redo->commit_action();9846}98479848void Node3DEditor::_environ_set_gi() {9849EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();9850undo_redo->create_action(TTR("Set Preview Environment Global Illumination"));9851undo_redo->add_do_method(environ_gi_button, "set_pressed", environ_gi_button->is_pressed());9852undo_redo->add_undo_method(environ_gi_button, "set_pressed", !environ_gi_button->is_pressed());9853undo_redo->add_do_method(this, "_preview_settings_changed");9854undo_redo->add_undo_method(this, "_preview_settings_changed");9855undo_redo->commit_action();9856}98579858void Node3DEditor::PreviewSunEnvPopup::shortcut_input(const Ref<InputEvent> &p_event) {9859const Ref<InputEventKey> k = p_event;9860if (k.is_valid() && k->is_pressed()) {9861bool handled = false;98629863if (ED_IS_SHORTCUT("ui_undo", p_event)) {9864EditorNode::get_singleton()->undo();9865handled = true;9866}98679868if (ED_IS_SHORTCUT("ui_redo", p_event)) {9869EditorNode::get_singleton()->redo();9870handled = true;9871}98729873if (handled) {9874set_input_as_handled();9875}9876}9877}98789879Node3DEditor::Node3DEditor() {9880gizmo.visible = true;9881gizmo.scale = 1.0;98829883viewport_environment.instantiate();9884VBoxContainer *vbc = this;98859886ERR_FAIL_COND_MSG(singleton != nullptr, "A Node3DEditor singleton already exists.");9887singleton = this;9888editor_selection = EditorNode::get_singleton()->get_editor_selection();9889editor_selection->add_editor_plugin(this);98909891MarginContainer *toolbar_margin = memnew(MarginContainer);9892toolbar_margin->set_theme_type_variation("MainToolBarMargin");9893vbc->add_child(toolbar_margin);98949895// A fluid container for all toolbars.9896HFlowContainer *main_flow = memnew(HFlowContainer);9897toolbar_margin->add_child(main_flow);98989899// Main toolbars.9900HBoxContainer *main_menu_hbox = memnew(HBoxContainer);9901main_flow->add_child(main_menu_hbox);99029903String sct;99049905tool_button[TOOL_MODE_TRANSFORM] = memnew(Button);9906main_menu_hbox->add_child(tool_button[TOOL_MODE_TRANSFORM]);9907tool_button[TOOL_MODE_TRANSFORM]->set_toggle_mode(true);9908tool_button[TOOL_MODE_TRANSFORM]->set_theme_type_variation(SceneStringName(FlatButton));9909tool_button[TOOL_MODE_TRANSFORM]->set_pressed(true);9910tool_button[TOOL_MODE_TRANSFORM]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_TOOL_TRANSFORM));9911tool_button[TOOL_MODE_TRANSFORM]->set_shortcut(ED_SHORTCUT("spatial_editor/tool_transform", TTRC("Transform Mode"), Key::Q, true));9912tool_button[TOOL_MODE_TRANSFORM]->set_shortcut_context(this);9913tool_button[TOOL_MODE_TRANSFORM]->set_accessibility_name(TTRC("Transform Mode"));99149915tool_button[TOOL_MODE_MOVE] = memnew(Button);9916main_menu_hbox->add_child(tool_button[TOOL_MODE_MOVE]);9917tool_button[TOOL_MODE_MOVE]->set_toggle_mode(true);9918tool_button[TOOL_MODE_MOVE]->set_tooltip_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);9919tool_button[TOOL_MODE_MOVE]->set_theme_type_variation(SceneStringName(FlatButton));99209921tool_button[TOOL_MODE_MOVE]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_TOOL_MOVE));9922tool_button[TOOL_MODE_MOVE]->set_shortcut(ED_SHORTCUT("spatial_editor/tool_move", TTRC("Move Mode"), Key::W, true));9923tool_button[TOOL_MODE_MOVE]->set_shortcut_context(this);9924tool_button[TOOL_MODE_MOVE]->set_accessibility_name(TTRC("Move Mode"));99259926tool_button[TOOL_MODE_ROTATE] = memnew(Button);9927main_menu_hbox->add_child(tool_button[TOOL_MODE_ROTATE]);9928tool_button[TOOL_MODE_ROTATE]->set_toggle_mode(true);9929tool_button[TOOL_MODE_ROTATE]->set_tooltip_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);9930tool_button[TOOL_MODE_ROTATE]->set_theme_type_variation(SceneStringName(FlatButton));9931tool_button[TOOL_MODE_ROTATE]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_TOOL_ROTATE));9932tool_button[TOOL_MODE_ROTATE]->set_shortcut(ED_SHORTCUT("spatial_editor/tool_rotate", TTRC("Rotate Mode"), Key::E, true));9933tool_button[TOOL_MODE_ROTATE]->set_shortcut_context(this);9934tool_button[TOOL_MODE_ROTATE]->set_accessibility_name(TTRC("Rotate Mode"));99359936tool_button[TOOL_MODE_SCALE] = memnew(Button);9937main_menu_hbox->add_child(tool_button[TOOL_MODE_SCALE]);9938tool_button[TOOL_MODE_SCALE]->set_toggle_mode(true);9939tool_button[TOOL_MODE_SCALE]->set_tooltip_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);9940tool_button[TOOL_MODE_SCALE]->set_theme_type_variation(SceneStringName(FlatButton));9941tool_button[TOOL_MODE_SCALE]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_TOOL_SCALE));9942tool_button[TOOL_MODE_SCALE]->set_shortcut(ED_SHORTCUT("spatial_editor/tool_scale", TTRC("Scale Mode"), Key::R, true));9943tool_button[TOOL_MODE_SCALE]->set_shortcut_context(this);9944tool_button[TOOL_MODE_SCALE]->set_accessibility_name(TTRC("Scale Mode"));99459946tool_button[TOOL_MODE_SELECT] = memnew(Button);9947main_menu_hbox->add_child(tool_button[TOOL_MODE_SELECT]);9948tool_button[TOOL_MODE_SELECT]->set_toggle_mode(true);9949tool_button[TOOL_MODE_SELECT]->set_tooltip_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);9950tool_button[TOOL_MODE_SELECT]->set_theme_type_variation(SceneStringName(FlatButton));9951tool_button[TOOL_MODE_SELECT]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_TOOL_SELECT));9952tool_button[TOOL_MODE_SELECT]->set_shortcut(ED_SHORTCUT("spatial_editor/tool_select", TTRC("Select Mode"), Key::V, true));9953tool_button[TOOL_MODE_SELECT]->set_shortcut_context(this);9954tool_button[TOOL_MODE_SELECT]->set_accessibility_name(TTRC("Select Mode"));99559956main_menu_hbox->add_child(memnew(VSeparator));99579958tool_button[TOOL_MODE_LIST_SELECT] = memnew(Button);9959main_menu_hbox->add_child(tool_button[TOOL_MODE_LIST_SELECT]);9960tool_button[TOOL_MODE_LIST_SELECT]->set_toggle_mode(true);9961tool_button[TOOL_MODE_LIST_SELECT]->set_theme_type_variation(SceneStringName(FlatButton));9962tool_button[TOOL_MODE_LIST_SELECT]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_TOOL_LIST_SELECT));9963tool_button[TOOL_MODE_LIST_SELECT]->set_tooltip_text(TTRC("Show list of selectable nodes at position clicked."));99649965tool_button[TOOL_LOCK_SELECTED] = memnew(Button);9966main_menu_hbox->add_child(tool_button[TOOL_LOCK_SELECTED]);9967tool_button[TOOL_LOCK_SELECTED]->set_theme_type_variation(SceneStringName(FlatButton));9968tool_button[TOOL_LOCK_SELECTED]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_LOCK_SELECTED));9969tool_button[TOOL_LOCK_SELECTED]->set_tooltip_text(TTRC("Lock selected node, preventing selection and movement."));9970// Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused.9971tool_button[TOOL_LOCK_SELECTED]->set_shortcut(ED_GET_SHORTCUT("editor/lock_selected_nodes"));9972tool_button[TOOL_LOCK_SELECTED]->set_accessibility_name(TTRC("Lock"));99739974tool_button[TOOL_UNLOCK_SELECTED] = memnew(Button);9975main_menu_hbox->add_child(tool_button[TOOL_UNLOCK_SELECTED]);9976tool_button[TOOL_UNLOCK_SELECTED]->set_theme_type_variation(SceneStringName(FlatButton));9977tool_button[TOOL_UNLOCK_SELECTED]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_UNLOCK_SELECTED));9978tool_button[TOOL_UNLOCK_SELECTED]->set_tooltip_text(TTRC("Unlock selected node, allowing selection and movement."));9979// Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused.9980tool_button[TOOL_UNLOCK_SELECTED]->set_shortcut(ED_GET_SHORTCUT("editor/unlock_selected_nodes"));9981tool_button[TOOL_UNLOCK_SELECTED]->set_accessibility_name(TTRC("Unlock"));99829983tool_button[TOOL_GROUP_SELECTED] = memnew(Button);9984main_menu_hbox->add_child(tool_button[TOOL_GROUP_SELECTED]);9985tool_button[TOOL_GROUP_SELECTED]->set_theme_type_variation(SceneStringName(FlatButton));9986tool_button[TOOL_GROUP_SELECTED]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_GROUP_SELECTED));9987tool_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."));9988// Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused.9989tool_button[TOOL_GROUP_SELECTED]->set_shortcut(ED_GET_SHORTCUT("editor/group_selected_nodes"));9990tool_button[TOOL_GROUP_SELECTED]->set_accessibility_name(TTRC("Group"));99919992tool_button[TOOL_UNGROUP_SELECTED] = memnew(Button);9993main_menu_hbox->add_child(tool_button[TOOL_UNGROUP_SELECTED]);9994tool_button[TOOL_UNGROUP_SELECTED]->set_theme_type_variation(SceneStringName(FlatButton));9995tool_button[TOOL_UNGROUP_SELECTED]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_UNGROUP_SELECTED));9996tool_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."));9997// Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused.9998tool_button[TOOL_UNGROUP_SELECTED]->set_shortcut(ED_GET_SHORTCUT("editor/ungroup_selected_nodes"));9999tool_button[TOOL_UNGROUP_SELECTED]->set_accessibility_name(TTRC("Ungroup"));1000010001tool_button[TOOL_RULER] = memnew(Button);10002main_menu_hbox->add_child(tool_button[TOOL_RULER]);10003tool_button[TOOL_RULER]->set_toggle_mode(true);10004tool_button[TOOL_RULER]->set_theme_type_variation("FlatButton");10005tool_button[TOOL_RULER]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_RULER));10006tool_button[TOOL_RULER]->set_tooltip_text(TTRC("LMB+Drag: Measure the distance between two points in 3D space."));10007// Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused.10008tool_button[TOOL_RULER]->set_shortcut(ED_SHORTCUT("spatial_editor/measure", TTRC("Ruler Mode"), Key::M));10009tool_button[TOOL_RULER]->set_accessibility_name(TTRC("Ruler Mode"));1001010011main_menu_hbox->add_child(memnew(VSeparator));1001210013tool_option_button[TOOL_OPT_LOCAL_COORDS] = memnew(Button);10014main_menu_hbox->add_child(tool_option_button[TOOL_OPT_LOCAL_COORDS]);10015tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_toggle_mode(true);10016tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_theme_type_variation(SceneStringName(FlatButton));10017tool_option_button[TOOL_OPT_LOCAL_COORDS]->connect(SceneStringName(toggled), callable_mp(this, &Node3DEditor::_menu_item_toggled).bind(MENU_TOOL_LOCAL_COORDS));10018tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_shortcut(ED_SHORTCUT("spatial_editor/local_coords", TTRC("Use Local Space"), Key::T));10019tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_shortcut_context(this);10020tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_accessibility_name(TTRC("Use Local Space"));1002110022tool_option_button[TOOL_OPT_USE_SNAP] = memnew(Button);10023main_menu_hbox->add_child(tool_option_button[TOOL_OPT_USE_SNAP]);10024tool_option_button[TOOL_OPT_USE_SNAP]->set_toggle_mode(true);10025tool_option_button[TOOL_OPT_USE_SNAP]->set_theme_type_variation(SceneStringName(FlatButton));10026tool_option_button[TOOL_OPT_USE_SNAP]->connect(SceneStringName(toggled), callable_mp(this, &Node3DEditor::_menu_item_toggled).bind(MENU_TOOL_USE_SNAP));10027tool_option_button[TOOL_OPT_USE_SNAP]->set_shortcut(ED_SHORTCUT("spatial_editor/snap", TTRC("Use Snap"), Key::Y));10028tool_option_button[TOOL_OPT_USE_SNAP]->set_shortcut_context(this);10029tool_option_button[TOOL_OPT_USE_SNAP]->set_accessibility_name(TTRC("Use Snap"));1003010031tool_option_button[TOOL_OPT_USE_TRACKBALL] = memnew(Button);10032main_menu_hbox->add_child(tool_option_button[TOOL_OPT_USE_TRACKBALL]);10033tool_option_button[TOOL_OPT_USE_TRACKBALL]->set_toggle_mode(true);10034tool_option_button[TOOL_OPT_USE_TRACKBALL]->set_theme_type_variation(SceneStringName(FlatButton));10035tool_option_button[TOOL_OPT_USE_TRACKBALL]->connect(SceneStringName(toggled), callable_mp(this, &Node3DEditor::_menu_item_toggled).bind(MENU_TOOL_USE_TRACKBALL));10036tool_option_button[TOOL_OPT_USE_TRACKBALL]->set_shortcut(ED_SHORTCUT("spatial_editor/trackball", TTRC("Use Trackball"), Key::U));10037tool_option_button[TOOL_OPT_USE_TRACKBALL]->set_shortcut_context(this);10038tool_option_button[TOOL_OPT_USE_TRACKBALL]->set_accessibility_name(TTRC("Use Trackball"));1003910040main_menu_hbox->add_child(memnew(VSeparator));10041sun_button = memnew(Button);10042sun_button->set_tooltip_text(TTRC("Toggle preview sunlight.\nIf a DirectionalLight3D node is added to the scene, preview sunlight is disabled."));10043sun_button->set_toggle_mode(true);10044sun_button->set_accessibility_name(TTRC("Toggle preview sunlight."));10045sun_button->set_theme_type_variation(SceneStringName(FlatButton));10046sun_button->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_update_preview_environment), CONNECT_DEFERRED);10047// Preview is enabled by default - ensure this applies on editor startup when there is no state yet.10048sun_button->set_pressed(true);1004910050main_menu_hbox->add_child(sun_button);1005110052environ_button = memnew(Button);10053environ_button->set_tooltip_text(TTRC("Toggle preview environment.\nIf a WorldEnvironment node is added to the scene, preview environment is disabled."));10054environ_button->set_toggle_mode(true);10055environ_button->set_accessibility_name(TTRC("Toggle preview environment."));10056environ_button->set_theme_type_variation(SceneStringName(FlatButton));10057environ_button->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_update_preview_environment), CONNECT_DEFERRED);10058// Preview is enabled by default - ensure this applies on editor startup when there is no state yet.10059environ_button->set_pressed(true);1006010061main_menu_hbox->add_child(environ_button);1006210063sun_environ_settings = memnew(Button);10064sun_environ_settings->set_tooltip_text(TTRC("Edit Sun and Environment settings."));10065sun_environ_settings->set_theme_type_variation(SceneStringName(FlatButton));10066sun_environ_settings->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_sun_environ_settings_pressed));1006710068main_menu_hbox->add_child(sun_environ_settings);1006910070main_menu_hbox->add_child(memnew(VSeparator));1007110072// Drag and drop support;10073preview_node = memnew(Node3D);10074preview_bounds = AABB();1007510076ED_SHORTCUT("spatial_editor/bottom_view", TTRC("Bottom View"), KeyModifierMask::ALT + Key::KP_7);10077ED_SHORTCUT("spatial_editor/top_view", TTRC("Top View"), Key::KP_7);10078ED_SHORTCUT("spatial_editor/rear_view", TTRC("Rear View"), KeyModifierMask::ALT + Key::KP_1);10079ED_SHORTCUT("spatial_editor/front_view", TTRC("Front View"), Key::KP_1);10080ED_SHORTCUT("spatial_editor/left_view", TTRC("Left View"), KeyModifierMask::ALT + Key::KP_3);10081ED_SHORTCUT("spatial_editor/right_view", TTRC("Right View"), Key::KP_3);10082ED_SHORTCUT("spatial_editor/orbit_view_down", TTRC("Orbit View Down"), Key::KP_2);10083ED_SHORTCUT("spatial_editor/orbit_view_left", TTRC("Orbit View Left"), Key::KP_4);10084ED_SHORTCUT("spatial_editor/orbit_view_right", TTRC("Orbit View Right"), Key::KP_6);10085ED_SHORTCUT("spatial_editor/orbit_view_up", TTRC("Orbit View Up"), Key::KP_8);10086ED_SHORTCUT("spatial_editor/orbit_view_180", TTRC("Orbit View 180"), Key::KP_9);10087ED_SHORTCUT("spatial_editor/switch_perspective_orthogonal", TTRC("Switch Perspective/Orthogonal View"), Key::KP_5);10088ED_SHORTCUT("spatial_editor/insert_anim_key", TTRC("Insert Animation Key"), Key::K);10089ED_SHORTCUT("spatial_editor/focus_origin", TTRC("Focus Origin"), Key::O);10090ED_SHORTCUT("spatial_editor/focus_selection", TTRC("Focus Selection"), Key::F);10091ED_SHORTCUT_ARRAY("spatial_editor/align_transform_with_view", TTRC("Align Transform with View"),10092{ int32_t(KeyModifierMask::ALT | KeyModifierMask::CTRL | Key::KP_0),10093int32_t(KeyModifierMask::ALT | KeyModifierMask::CTRL | Key::M),10094int32_t(KeyModifierMask::ALT | KeyModifierMask::CTRL | Key::G) });10095ED_SHORTCUT_OVERRIDE_ARRAY("spatial_editor/align_transform_with_view", "macos",10096{ int32_t(KeyModifierMask::ALT | KeyModifierMask::META | Key::KP_0),10097int32_t(KeyModifierMask::ALT | KeyModifierMask::META | Key::G) });10098ED_SHORTCUT("spatial_editor/align_rotation_with_view", TTRC("Align Rotation with View"), KeyModifierMask::ALT + KeyModifierMask::CMD_OR_CTRL + Key::F);10099ED_SHORTCUT("spatial_editor/freelook_toggle", TTRC("Toggle Freelook"), KeyModifierMask::SHIFT + Key::F);10100ED_SHORTCUT("spatial_editor/decrease_fov", TTRC("Decrease Field of View"), KeyModifierMask::CMD_OR_CTRL + Key::EQUAL); // Usually direct access key for `KEY_PLUS`.10101ED_SHORTCUT("spatial_editor/increase_fov", TTRC("Increase Field of View"), KeyModifierMask::CMD_OR_CTRL + Key::MINUS);10102ED_SHORTCUT("spatial_editor/reset_fov", TTRC("Reset Field of View to Default"), KeyModifierMask::CMD_OR_CTRL + Key::KEY_0);1010310104PopupMenu *p;1010510106transform_menu = memnew(MenuButton);10107transform_menu->set_flat(false);10108transform_menu->set_theme_type_variation("FlatMenuButton");10109transform_menu->set_text(TTRC("Transform"));10110transform_menu->set_switch_on_hover(true);10111transform_menu->set_shortcut_context(this);10112main_menu_hbox->add_child(transform_menu);1011310114p = transform_menu->get_popup();10115p->add_shortcut(ED_SHORTCUT("spatial_editor/snap_to_floor", TTRC("Snap Object to Floor"), Key::PAGEDOWN), MENU_SNAP_TO_FLOOR);10116p->add_shortcut(ED_SHORTCUT("spatial_editor/transform_dialog", TTRC("Transform Dialog...")), MENU_TRANSFORM_DIALOG);1011710118p->add_separator();10119p->add_shortcut(ED_SHORTCUT("spatial_editor/configure_snap", TTRC("Configure Snap...")), MENU_TRANSFORM_CONFIGURE_SNAP);1012010121p->connect(SceneStringName(id_pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed));1012210123view_layout_menu = memnew(MenuButton);10124view_layout_menu->set_flat(false);10125view_layout_menu->set_theme_type_variation("FlatMenuButton");10126// TRANSLATORS: Noun, name of the 2D/3D View menus.10127view_layout_menu->set_text(TTRC("View"));10128view_layout_menu->set_switch_on_hover(true);10129view_layout_menu->set_shortcut_context(this);10130main_menu_hbox->add_child(view_layout_menu);1013110132main_menu_hbox->add_child(memnew(VSeparator));1013310134context_toolbar_panel = memnew(PanelContainer);10135context_toolbar_hbox = memnew(HBoxContainer);10136context_toolbar_panel->add_child(context_toolbar_hbox);10137main_flow->add_child(context_toolbar_panel);1013810139// Get the view menu popup and have it stay open when a checkable item is selected10140p = view_layout_menu->get_popup();10141p->set_hide_on_checkable_item_selection(false);1014210143accept = memnew(AcceptDialog);10144EditorNode::get_singleton()->get_gui_base()->add_child(accept);1014510146p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/1_viewport", TTRC("1 Viewport"), KeyModifierMask::CMD_OR_CTRL + Key::KEY_1, true), MENU_VIEW_USE_1_VIEWPORT);10147p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/2_viewports", TTRC("2 Viewports"), KeyModifierMask::CMD_OR_CTRL + Key::KEY_2, true), MENU_VIEW_USE_2_VIEWPORTS);10148p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/2_viewports_alt", TTRC("2 Viewports (Alt)"), KeyModifierMask::ALT + KeyModifierMask::CMD_OR_CTRL + Key::KEY_2, true), MENU_VIEW_USE_2_VIEWPORTS_ALT);10149p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/3_viewports", TTRC("3 Viewports"), KeyModifierMask::CMD_OR_CTRL + Key::KEY_3, true), MENU_VIEW_USE_3_VIEWPORTS);10150p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/3_viewports_alt", TTRC("3 Viewports (Alt)"), KeyModifierMask::ALT + KeyModifierMask::CMD_OR_CTRL + Key::KEY_3, true), MENU_VIEW_USE_3_VIEWPORTS_ALT);10151p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/4_viewports", TTRC("4 Viewports"), KeyModifierMask::CMD_OR_CTRL + Key::KEY_4, true), MENU_VIEW_USE_4_VIEWPORTS);10152p->add_separator();1015310154gizmos_menu = memnew(PopupMenu);10155gizmos_menu->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);10156gizmos_menu->set_hide_on_checkable_item_selection(false);10157p->add_submenu_node_item(TTRC("Gizmos"), gizmos_menu);10158gizmos_menu->connect(SceneStringName(id_pressed), callable_mp(this, &Node3DEditor::_menu_gizmo_toggled));1015910160p->add_separator();10161p->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_origin", TTRC("View Origin")), MENU_VIEW_ORIGIN);10162p->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_grid", TTRC("View Grid"), Key::NUMBERSIGN), MENU_VIEW_GRID);1016310164p->add_separator();10165p->add_submenu_node_item(TTRC("Preview Translation"), memnew(EditorTranslationPreviewMenu));1016610167p->add_separator();10168p->add_shortcut(ED_SHORTCUT("spatial_editor/settings", TTRC("Settings...")), MENU_VIEW_CAMERA_SETTINGS);1016910170p->set_item_checked(p->get_item_index(MENU_VIEW_ORIGIN), true);10171p->set_item_checked(p->get_item_index(MENU_VIEW_GRID), true);1017210173p->connect(SceneStringName(id_pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed));1017410175/* REST OF MENU */1017610177left_panel_split = memnew(HSplitContainer);10178left_panel_split->set_v_size_flags(SIZE_EXPAND_FILL);10179vbc->add_child(left_panel_split);1018010181right_panel_split = memnew(HSplitContainer);10182right_panel_split->set_v_size_flags(SIZE_EXPAND_FILL);10183left_panel_split->add_child(right_panel_split);1018410185shader_split = memnew(VSplitContainer);10186shader_split->set_h_size_flags(SIZE_EXPAND_FILL);10187right_panel_split->add_child(shader_split);10188viewport_base = memnew(Node3DEditorViewportContainer);10189shader_split->add_child(viewport_base);10190viewport_base->set_v_size_flags(SIZE_EXPAND_FILL);10191for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) {10192viewports[i] = memnew(Node3DEditorViewport(this, i));10193viewports[i]->connect("toggle_maximize_view", callable_mp(this, &Node3DEditor::_toggle_maximize_view));10194viewports[i]->connect("clicked", callable_mp(this, &Node3DEditor::_viewport_clicked).bind(i));10195viewports[i]->assign_pending_data_pointers(preview_node, &preview_bounds, accept);10196viewport_base->add_child(viewports[i]);10197}1019810199/* SNAP DIALOG */1020010201snap_dialog = memnew(ConfirmationDialog);10202snap_dialog->set_title(TTRC("Snap Settings"));10203add_child(snap_dialog);10204snap_dialog->connect(SceneStringName(confirmed), callable_mp(this, &Node3DEditor::_snap_changed));10205snap_dialog->get_cancel_button()->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_snap_update));1020610207VBoxContainer *snap_dialog_vbc = memnew(VBoxContainer);10208snap_dialog->add_child(snap_dialog_vbc);1020910210snap_translate = memnew(EditorSpinSlider);10211snap_translate->set_min(0.0);10212snap_translate->set_step(0.001);10213snap_translate->set_max(10.0);10214snap_translate->set_suffix("m");10215snap_translate->set_allow_greater(true);10216snap_translate->set_accessibility_name(TTRC("Translate Snap"));10217snap_dialog_vbc->add_margin_child(TTR("Translate Snap:"), snap_translate);1021810219snap_rotate = memnew(EditorSpinSlider);10220snap_rotate->set_min(0.0);10221snap_rotate->set_step(0.1);10222snap_rotate->set_max(360);10223snap_rotate->set_suffix(U"°");10224snap_rotate->set_accessibility_name(TTRC("Rotate Snap"));10225snap_dialog_vbc->add_margin_child(TTR("Rotate Snap:"), snap_rotate);1022610227snap_scale = memnew(EditorSpinSlider);10228snap_scale->set_min(0.0);10229snap_scale->set_step(1.0);10230snap_scale->set_max(100);10231snap_scale->set_suffix("%");10232snap_scale->set_accessibility_name(TTRC("Scale Snap"));10233snap_dialog_vbc->add_margin_child(TTR("Scale Snap:"), snap_scale);1023410235/* SETTINGS DIALOG */1023610237settings_dialog = memnew(ConfirmationDialog);10238settings_dialog->set_title(TTRC("Viewport Settings"));10239add_child(settings_dialog);10240settings_vbc = memnew(VBoxContainer);10241settings_vbc->set_custom_minimum_size(Size2(200, 0) * EDSCALE);10242settings_dialog->add_child(settings_vbc);1024310244settings_fov = memnew(SpinBox);10245settings_fov->set_max(MAX_FOV);10246settings_fov->set_min(MIN_FOV);10247settings_fov->set_step(0.1);10248settings_fov->set_value(EDITOR_GET("editors/3d/default_fov"));10249settings_fov->set_select_all_on_focus(true);10250settings_fov->set_tooltip_text(TTRC("FOV is defined as a vertical value, as the editor camera always uses the Keep Height aspect mode."));10251settings_fov->set_accessibility_name(TTRC("Perspective VFOV (deg.):"));10252settings_vbc->add_margin_child(TTRC("Perspective VFOV (deg.):"), settings_fov);1025310254settings_znear = memnew(SpinBox);10255settings_znear->set_max(MAX_Z);10256settings_znear->set_min(MIN_Z);10257settings_znear->set_step(0.01);10258settings_znear->set_accessibility_name(TTRC("View Z-Near:"));10259settings_znear->set_value(EDITOR_GET("editors/3d/default_z_near"));10260settings_znear->set_select_all_on_focus(true);10261settings_vbc->add_margin_child(TTRC("View Z-Near:"), settings_znear);1026210263settings_zfar = memnew(SpinBox);10264settings_zfar->set_max(MAX_Z);10265settings_zfar->set_min(MIN_Z);10266settings_zfar->set_step(0.1);10267settings_zfar->set_accessibility_name(TTRC("View Z-Far:"));10268settings_zfar->set_value(EDITOR_GET("editors/3d/default_z_far"));10269settings_zfar->set_select_all_on_focus(true);10270settings_vbc->add_margin_child(TTRC("View Z-Far:"), settings_zfar);1027110272for (uint32_t i = 0; i < VIEWPORTS_COUNT; ++i) {10273settings_dialog->connect(SceneStringName(confirmed), callable_mp(viewports[i], &Node3DEditorViewport::_view_settings_confirmed).bind(0.0));10274}1027510276/* XFORM DIALOG */1027710278xform_dialog = memnew(ConfirmationDialog);10279xform_dialog->set_title(TTRC("Transform Change"));10280add_child(xform_dialog);1028110282VBoxContainer *xform_vbc = memnew(VBoxContainer);10283xform_dialog->add_child(xform_vbc);1028410285HBoxContainer *translate_hb = memnew(HBoxContainer);10286xform_vbc->add_margin_child(TTRC("Translate:"), translate_hb);10287HBoxContainer *rotate_hb = memnew(HBoxContainer);10288xform_vbc->add_margin_child(TTRC("Rotate (deg.):"), rotate_hb);10289HBoxContainer *scale_hb = memnew(HBoxContainer);10290xform_vbc->add_margin_child(TTRC("Scale (ratio):"), scale_hb);1029110292for (int i = 0; i < 3; i++) {10293xform_translate[i] = memnew(LineEdit);10294xform_translate[i]->set_h_size_flags(SIZE_EXPAND_FILL);10295xform_translate[i]->set_select_all_on_focus(true);10296translate_hb->add_child(xform_translate[i]);1029710298xform_rotate[i] = memnew(LineEdit);10299xform_rotate[i]->set_h_size_flags(SIZE_EXPAND_FILL);10300xform_rotate[i]->set_select_all_on_focus(true);10301rotate_hb->add_child(xform_rotate[i]);1030210303xform_scale[i] = memnew(LineEdit);10304xform_scale[i]->set_h_size_flags(SIZE_EXPAND_FILL);10305xform_scale[i]->set_select_all_on_focus(true);10306scale_hb->add_child(xform_scale[i]);10307}1030810309xform_type = memnew(OptionButton);10310xform_type->set_h_size_flags(SIZE_EXPAND_FILL);10311xform_type->set_accessibility_name(TTRC("Transform Type"));10312xform_type->add_item(TTRC("Pre"));10313xform_type->add_item(TTRC("Post"));10314xform_vbc->add_margin_child(TTRC("Transform Type"), xform_type);1031510316xform_dialog->connect(SceneStringName(confirmed), callable_mp(this, &Node3DEditor::_xform_dialog_action));1031710318selected = nullptr;1031910320set_process_shortcut_input(true);10321add_to_group(SceneStringName(_spatial_editor_group));1032210323current_hover_gizmo_handle = -1;10324current_hover_gizmo_handle_secondary = false;10325{10326// Sun/preview environment popup.10327sun_environ_popup = memnew(PreviewSunEnvPopup);10328add_child(sun_environ_popup);1032910330HBoxContainer *sun_environ_hb = memnew(HBoxContainer);1033110332sun_environ_popup->add_child(sun_environ_hb);1033310334sun_vb = memnew(VBoxContainer);10335sun_environ_hb->add_child(sun_vb);10336sun_vb->set_custom_minimum_size(Size2(200 * EDSCALE, 0));10337sun_vb->hide();1033810339sun_title = memnew(Label);10340sun_title->set_theme_type_variation("HeaderMedium");10341sun_vb->add_child(sun_title);10342sun_title->set_text(TTRC("Preview Sun"));10343sun_title->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);1034410345CenterContainer *sun_direction_center = memnew(CenterContainer);10346sun_direction = memnew(Control);10347sun_direction->set_custom_minimum_size(Size2(128, 128) * EDSCALE);10348sun_direction_center->add_child(sun_direction);10349sun_vb->add_margin_child(TTRC("Sun Direction"), sun_direction_center);10350sun_direction->connect(SceneStringName(gui_input), callable_mp(this, &Node3DEditor::_sun_direction_input));10351sun_direction->connect(SceneStringName(draw), callable_mp(this, &Node3DEditor::_sun_direction_draw));10352sun_direction->set_default_cursor_shape(CURSOR_MOVE);1035310354sun_direction_shader.instantiate();10355sun_direction_shader->set_code(R"(10356// 3D editor Preview Sun direction shader.1035710358shader_type canvas_item;1035910360uniform vec3 sun_direction;10361uniform vec3 sun_color;1036210363void fragment() {10364vec3 n;10365n.xy = UV * 2.0 - 1.0;10366n.z = sqrt(max(0.0, 1.0 - dot(n.xy, n.xy)));10367COLOR.rgb = dot(n, sun_direction) * sun_color;10368COLOR.a = 1.0 - smoothstep(0.99, 1.0, length(n.xy));10369}10370)");10371sun_direction_material.instantiate();10372sun_direction_material->set_shader(sun_direction_shader);10373sun_direction_material->set_shader_parameter("sun_direction", Vector3(0, 0, 1));10374sun_direction_material->set_shader_parameter("sun_color", Vector3(1, 1, 1));10375sun_direction->set_material(sun_direction_material);1037610377HBoxContainer *sun_angle_hbox = memnew(HBoxContainer);10378sun_angle_hbox->set_h_size_flags(SIZE_EXPAND_FILL);10379VBoxContainer *sun_angle_altitude_vbox = memnew(VBoxContainer);10380sun_angle_altitude_vbox->set_h_size_flags(SIZE_EXPAND_FILL);10381Label *sun_angle_altitude_label = memnew(Label);10382sun_angle_altitude_label->set_text(TTRC("Angular Altitude"));10383sun_angle_altitude_vbox->add_child(sun_angle_altitude_label);10384sun_angle_altitude = memnew(EditorSpinSlider);10385sun_angle_altitude->set_suffix(U"\u00B0");10386sun_angle_altitude->set_max(90);10387sun_angle_altitude->set_min(-90);10388sun_angle_altitude->set_step(0.1);10389sun_angle_altitude->connect(SceneStringName(value_changed), callable_mp(this, &Node3DEditor::_sun_direction_set_altitude));10390sun_angle_altitude_vbox->add_child(sun_angle_altitude);10391sun_angle_hbox->add_child(sun_angle_altitude_vbox);10392VBoxContainer *sun_angle_azimuth_vbox = memnew(VBoxContainer);10393sun_angle_azimuth_vbox->set_h_size_flags(SIZE_EXPAND_FILL);10394sun_angle_azimuth_vbox->set_custom_minimum_size(Vector2(100, 0));10395Label *sun_angle_azimuth_label = memnew(Label);10396sun_angle_azimuth_label->set_text(TTRC("Azimuth"));10397sun_angle_azimuth_vbox->add_child(sun_angle_azimuth_label);10398sun_angle_azimuth = memnew(EditorSpinSlider);10399sun_angle_azimuth->set_suffix(U"\u00B0");10400sun_angle_azimuth->set_max(180);10401sun_angle_azimuth->set_min(-180);10402sun_angle_azimuth->set_step(0.1);10403sun_angle_azimuth->set_allow_greater(true);10404sun_angle_azimuth->set_allow_lesser(true);10405sun_angle_azimuth->connect(SceneStringName(value_changed), callable_mp(this, &Node3DEditor::_sun_direction_set_azimuth));10406sun_angle_azimuth_vbox->add_child(sun_angle_azimuth);10407sun_angle_hbox->add_child(sun_angle_azimuth_vbox);10408sun_angle_hbox->add_theme_constant_override("separation", 10);10409sun_vb->add_child(sun_angle_hbox);1041010411sun_color = memnew(ColorPickerButton);10412sun_color->set_edit_alpha(false);10413sun_vb->add_margin_child(TTRC("Sun Color"), sun_color);10414sun_color->connect("color_changed", callable_mp(this, &Node3DEditor::_sun_set_color));10415sun_color->get_popup()->connect("about_to_popup", callable_mp(EditorNode::get_singleton(), &EditorNode::setup_color_picker).bind(sun_color->get_picker()));1041610417sun_energy = memnew(EditorSpinSlider);10418sun_energy->set_max(64.0);10419sun_energy->set_min(0);10420sun_energy->set_step(0.05);10421sun_vb->add_margin_child(TTRC("Sun Energy"), sun_energy);10422sun_energy->connect(SceneStringName(value_changed), callable_mp(this, &Node3DEditor::_sun_set_energy));1042310424sun_shadow_max_distance = memnew(EditorSpinSlider);10425sun_vb->add_margin_child(TTRC("Shadow Max Distance"), sun_shadow_max_distance);10426sun_shadow_max_distance->connect(SceneStringName(value_changed), callable_mp(this, &Node3DEditor::_sun_set_shadow_max_distance));10427sun_shadow_max_distance->set_min(1);10428sun_shadow_max_distance->set_max(4096);1042910430sun_add_to_scene = memnew(Button);10431sun_add_to_scene->set_text(TTRC("Add Sun to Scene"));10432sun_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."));10433sun_add_to_scene->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_add_sun_to_scene).bind(false));10434sun_vb->add_spacer();10435sun_vb->add_child(sun_add_to_scene);1043610437sun_state = memnew(Label);10438sun_environ_hb->add_child(sun_state);10439sun_state->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);10440sun_state->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);10441sun_state->set_h_size_flags(SIZE_EXPAND_FILL);1044210443VSeparator *sc = memnew(VSeparator);10444sc->set_custom_minimum_size(Size2(10 * EDSCALE, 0));10445sc->set_v_size_flags(SIZE_EXPAND_FILL);10446sun_environ_hb->add_child(sc);1044710448environ_vb = memnew(VBoxContainer);10449sun_environ_hb->add_child(environ_vb);10450environ_vb->set_custom_minimum_size(Size2(200 * EDSCALE, 0));10451environ_vb->hide();1045210453environ_title = memnew(Label);10454environ_title->set_theme_type_variation("HeaderMedium");1045510456environ_vb->add_child(environ_title);10457environ_title->set_text(TTRC("Preview Environment"));10458environ_title->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);1045910460environ_sky_color = memnew(ColorPickerButton);10461environ_sky_color->set_edit_alpha(false);10462environ_sky_color->connect("color_changed", callable_mp(this, &Node3DEditor::_environ_set_sky_color));10463environ_sky_color->get_popup()->connect("about_to_popup", callable_mp(EditorNode::get_singleton(), &EditorNode::setup_color_picker).bind(environ_sky_color->get_picker()));10464environ_vb->add_margin_child(TTRC("Sky Color"), environ_sky_color);10465environ_ground_color = memnew(ColorPickerButton);10466environ_ground_color->connect("color_changed", callable_mp(this, &Node3DEditor::_environ_set_ground_color));10467environ_ground_color->set_edit_alpha(false);10468environ_ground_color->get_popup()->connect("about_to_popup", callable_mp(EditorNode::get_singleton(), &EditorNode::setup_color_picker).bind(environ_ground_color->get_picker()));10469environ_vb->add_margin_child(TTRC("Ground Color"), environ_ground_color);10470environ_energy = memnew(EditorSpinSlider);10471environ_energy->set_max(8.0);10472environ_energy->set_min(0);10473environ_energy->set_step(0.05);10474environ_energy->connect(SceneStringName(value_changed), callable_mp(this, &Node3DEditor::_environ_set_sky_energy));10475environ_vb->add_margin_child(TTRC("Sky Energy"), environ_energy);10476HBoxContainer *fx_vb = memnew(HBoxContainer);10477fx_vb->set_h_size_flags(SIZE_EXPAND_FILL);1047810479environ_ao_button = memnew(Button);10480environ_ao_button->set_text(TTRC("AO"));10481environ_ao_button->set_h_size_flags(SIZE_EXPAND_FILL);10482environ_ao_button->set_toggle_mode(true);10483environ_ao_button->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_environ_set_ao), CONNECT_DEFERRED);10484fx_vb->add_child(environ_ao_button);10485environ_glow_button = memnew(Button);10486environ_glow_button->set_text(TTRC("Glow"));10487environ_glow_button->set_h_size_flags(SIZE_EXPAND_FILL);10488environ_glow_button->set_toggle_mode(true);10489environ_glow_button->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_environ_set_glow), CONNECT_DEFERRED);10490fx_vb->add_child(environ_glow_button);10491environ_tonemap_button = memnew(Button);10492environ_tonemap_button->set_text(TTRC("Tonemap"));10493environ_tonemap_button->set_h_size_flags(SIZE_EXPAND_FILL);10494environ_tonemap_button->set_toggle_mode(true);10495environ_tonemap_button->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_environ_set_tonemap), CONNECT_DEFERRED);10496fx_vb->add_child(environ_tonemap_button);10497environ_gi_button = memnew(Button);10498environ_gi_button->set_text(TTRC("GI"));10499environ_gi_button->set_h_size_flags(SIZE_EXPAND_FILL);10500environ_gi_button->set_toggle_mode(true);10501environ_gi_button->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_environ_set_gi), CONNECT_DEFERRED);10502fx_vb->add_child(environ_gi_button);10503environ_vb->add_margin_child(TTRC("Post Process"), fx_vb);1050410505environ_add_to_scene = memnew(Button);10506environ_add_to_scene->set_text(TTRC("Add Environment to Scene"));10507environ_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."));10508environ_add_to_scene->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_add_environment_to_scene).bind(false));10509environ_vb->add_spacer();10510environ_vb->add_child(environ_add_to_scene);1051110512environ_state = memnew(Label);10513sun_environ_hb->add_child(environ_state);10514environ_state->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);10515environ_state->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);10516environ_state->set_h_size_flags(SIZE_EXPAND_FILL);1051710518preview_sun = memnew(DirectionalLight3D);10519preview_sun->set_shadow(true);10520preview_sun->set_shadow_mode(DirectionalLight3D::SHADOW_PARALLEL_4_SPLITS);10521preview_environment = memnew(WorldEnvironment);10522environment.instantiate();10523preview_environment->set_environment(environment);10524if (GLOBAL_GET("rendering/lights_and_shadows/use_physical_light_units")) {10525camera_attributes.instantiate();10526preview_environment->set_camera_attributes(camera_attributes);10527}10528Ref<Sky> sky;10529sky.instantiate();10530sky_material.instantiate();10531sky->set_material(sky_material);10532environment->set_sky(sky);10533environment->set_background(Environment::BG_SKY);1053410535sun_environ_popup->set_process_shortcut_input(true);1053610537_load_default_preview_settings();10538_preview_settings_changed();10539}10540clear(); // Make sure values are initialized. Will call _snap_update() for us.10541}10542Node3DEditor::~Node3DEditor() {10543singleton = nullptr;10544memdelete(preview_node);10545if (preview_sun_dangling && preview_sun) {10546memdelete(preview_sun);10547}10548if (preview_env_dangling && preview_environment) {10549memdelete(preview_environment);10550}10551}1055210553void Node3DEditorPlugin::make_visible(bool p_visible) {10554if (p_visible) {10555spatial_editor->show();10556spatial_editor->set_process(true);10557spatial_editor->set_physics_process(true);10558spatial_editor->refresh_dirty_gizmos();10559} else {10560spatial_editor->hide();10561spatial_editor->set_process(false);10562spatial_editor->set_physics_process(false);10563}10564}1056510566void Node3DEditorPlugin::edit(Object *p_object) {10567spatial_editor->edit(Object::cast_to<Node3D>(p_object));10568}1056910570bool Node3DEditorPlugin::handles(Object *p_object) const {10571return p_object->is_class("Node3D");10572}1057310574Dictionary Node3DEditorPlugin::get_state() const {10575return spatial_editor->get_state();10576}1057710578void Node3DEditorPlugin::set_state(const Dictionary &p_state) {10579spatial_editor->set_state(p_state);10580}1058110582Size2i Node3DEditor::get_camera_viewport_size(Camera3D *p_camera) {10583Viewport *viewport = p_camera->get_viewport();1058410585Window *window = Object::cast_to<Window>(viewport);10586if (window) {10587return window->get_size();10588}1058910590SubViewport *sub_viewport = Object::cast_to<SubViewport>(viewport);10591ERR_FAIL_NULL_V(sub_viewport, Size2i());1059210593if (sub_viewport == EditorNode::get_singleton()->get_scene_root()) {10594return Size2(GLOBAL_GET("display/window/size/viewport_width"), GLOBAL_GET("display/window/size/viewport_height"));10595}1059610597return sub_viewport->get_size();10598}1059910600Vector3 Node3DEditor::snap_point(Vector3 p_target, Vector3 p_start) const {10601if (is_snap_enabled()) {10602real_t snap = get_translate_snap();10603p_target.snapf(snap);10604}10605return p_target;10606}1060710608bool Node3DEditor::is_gizmo_visible() const {10609if (selected) {10610return gizmo.visible && selected->is_transform_gizmo_visible();10611}10612return gizmo.visible;10613}1061410615real_t Node3DEditor::get_translate_snap() const {10616real_t snap_value = snap_translate_value;10617if (Input::get_singleton()->is_key_pressed(Key::SHIFT)) {10618snap_value /= 10.0f;10619}10620return snap_value;10621}1062210623real_t Node3DEditor::get_rotate_snap() const {10624real_t snap_value = snap_rotate_value;10625if (Input::get_singleton()->is_key_pressed(Key::SHIFT)) {10626snap_value /= 3.0f;10627}10628return snap_value;10629}1063010631real_t Node3DEditor::get_scale_snap() const {10632real_t snap_value = snap_scale_value;10633if (Input::get_singleton()->is_key_pressed(Key::SHIFT)) {10634snap_value /= 2.0f;10635}10636return snap_value;10637}1063810639struct _GizmoPluginPriorityComparator {10640bool operator()(const Ref<EditorNode3DGizmoPlugin> &p_a, const Ref<EditorNode3DGizmoPlugin> &p_b) const {10641if (p_a->get_priority() == p_b->get_priority()) {10642return p_a->get_gizmo_name() < p_b->get_gizmo_name();10643}10644return p_a->get_priority() > p_b->get_priority();10645}10646};1064710648struct _GizmoPluginNameComparator {10649bool operator()(const Ref<EditorNode3DGizmoPlugin> &p_a, const Ref<EditorNode3DGizmoPlugin> &p_b) const {10650return p_a->get_gizmo_name() < p_b->get_gizmo_name();10651}10652};1065310654void Node3DEditor::add_gizmo_plugin(Ref<EditorNode3DGizmoPlugin> p_plugin) {10655ERR_FAIL_COND(p_plugin.is_null());1065610657gizmo_plugins_by_priority.push_back(p_plugin);10658gizmo_plugins_by_priority.sort_custom<_GizmoPluginPriorityComparator>();1065910660gizmo_plugins_by_name.push_back(p_plugin);10661gizmo_plugins_by_name.sort_custom<_GizmoPluginNameComparator>();1066210663_update_gizmos_menu();10664}1066510666void Node3DEditor::remove_gizmo_plugin(Ref<EditorNode3DGizmoPlugin> p_plugin) {10667gizmo_plugins_by_priority.erase(p_plugin);10668gizmo_plugins_by_name.erase(p_plugin);10669_update_gizmos_menu();10670}1067110672DynamicBVH::ID Node3DEditor::insert_gizmo_bvh_node(Node3D *p_node, const AABB &p_aabb) {10673return gizmo_bvh.insert(p_aabb, p_node);10674}1067510676void Node3DEditor::update_gizmo_bvh_node(DynamicBVH::ID p_id, const AABB &p_aabb) {10677gizmo_bvh.update(p_id, p_aabb);10678gizmo_bvh.optimize_incremental(1);10679}1068010681void Node3DEditor::remove_gizmo_bvh_node(DynamicBVH::ID p_id) {10682gizmo_bvh.remove(p_id);10683}1068410685Vector<Node3D *> Node3DEditor::gizmo_bvh_ray_query(const Vector3 &p_ray_start, const Vector3 &p_ray_end) {10686struct Result {10687Vector<Node3D *> nodes;10688bool operator()(void *p_data) {10689nodes.append((Node3D *)p_data);10690return false;10691}10692} result;1069310694gizmo_bvh.ray_query(p_ray_start, p_ray_end, result);1069510696return result.nodes;10697}1069810699Vector<Node3D *> Node3DEditor::gizmo_bvh_frustum_query(const Vector<Plane> &p_frustum) {10700Vector<Vector3> points = Geometry3D::compute_convex_mesh_points(&p_frustum[0], p_frustum.size());1070110702struct Result {10703Vector<Node3D *> nodes;10704bool operator()(void *p_data) {10705nodes.append((Node3D *)p_data);10706return false;10707}10708} result;1070910710gizmo_bvh.convex_query(p_frustum.ptr(), p_frustum.size(), points.ptr(), points.size(), result);1071110712return result.nodes;10713}1071410715Node3DEditorPlugin::Node3DEditorPlugin() {10716spatial_editor = memnew(Node3DEditor);10717spatial_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL);10718EditorNode::get_singleton()->get_editor_main_screen()->get_control()->add_child(spatial_editor);1071910720spatial_editor->hide();10721}107221072310724