Path: blob/master/scene/debugger/runtime_node_select.cpp
20934 views
/**************************************************************************/1/* runtime_node_select.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#ifdef DEBUG_ENABLED3132#include "runtime_node_select.h"3334#include "core/config/project_settings.h"35#include "core/debugger/debugger_marshalls.h"36#include "core/debugger/engine_debugger.h"37#include "core/input/input.h"38#include "core/math/geometry_3d.h"39#include "scene/2d/camera_2d.h"40#include "scene/debugger/scene_debugger_object.h"41#include "scene/gui/popup_menu.h"42#include "scene/main/canvas_layer.h"43#include "scene/theme/theme_db.h"4445#ifndef PHYSICS_2D_DISABLED46#include "scene/2d/physics/collision_object_2d.h"47#include "scene/2d/physics/collision_polygon_2d.h"48#include "scene/2d/physics/collision_shape_2d.h"49#endif // PHYSICS_2D_DISABLED5051#ifndef _3D_DISABLED52#include "scene/3d/camera_3d.h"53#ifndef PHYSICS_3D_DISABLED54#include "scene/3d/physics/collision_object_3d.h"55#include "scene/3d/physics/collision_shape_3d.h"56#endif // PHYSICS_3D_DISABLED57#include "scene/3d/visual_instance_3d.h"58#include "scene/resources/3d/convex_polygon_shape_3d.h"59#include "scene/resources/surface_tool.h"60#endif // _3D_DISABLED6162RuntimeNodeSelect *RuntimeNodeSelect::get_singleton() {63return singleton;64}6566RuntimeNodeSelect::~RuntimeNodeSelect() {67if (selection_list && !selection_list->is_visible()) {68memdelete(selection_list);69}7071if (draw_canvas.is_valid()) {72RS::get_singleton()->free_rid(sel_drag_ci);73RS::get_singleton()->free_rid(sbox_2d_ci);74RS::get_singleton()->free_rid(draw_canvas);75}76}7778void RuntimeNodeSelect::_setup(const Dictionary &p_settings) {79Window *root = SceneTree::get_singleton()->get_root();80ERR_FAIL_COND(root->is_connected(SceneStringName(window_input), callable_mp(this, &RuntimeNodeSelect::_root_window_input)));8182root->connect(SceneStringName(window_input), callable_mp(this, &RuntimeNodeSelect::_root_window_input));83root->connect("size_changed", callable_mp(this, &RuntimeNodeSelect::_queue_selection_update), CONNECT_DEFERRED);8485max_selection = p_settings.get("debugger/max_node_selection", 1);8687panner.instantiate();88panner->set_callbacks(callable_mp(this, &RuntimeNodeSelect::_pan_callback), callable_mp(this, &RuntimeNodeSelect::_zoom_callback));8990ViewPanner::ControlScheme panning_scheme = (ViewPanner::ControlScheme)p_settings.get("editors/panning/2d_editor_panning_scheme", 0).operator int();91bool simple_panning = p_settings.get("editors/panning/simple_panning", false);92int pan_speed = p_settings.get("editors/panning/2d_editor_pan_speed", 20);93Array keys = p_settings.get("canvas_item_editor/pan_view", Array()).operator Array();94panner->setup(panning_scheme, DebuggerMarshalls::deserialize_key_shortcut(keys), simple_panning);95panner->setup_warped_panning(root, p_settings.get("editors/panning/warped_mouse_panning", true));96panner->set_scroll_speed(pan_speed);9798sel_2d_grab_dist = p_settings.get("editors/polygon_editor/point_grab_radius", 0);99sel_2d_scale = MAX(1, Math::ceil(2.0 / (float)GLOBAL_GET("display/window/stretch/scale")));100101selection_area_fill = p_settings.get("box_selection_fill_color", Color());102selection_area_outline = p_settings.get("box_selection_stroke_color", Color());103104draw_canvas = RS::get_singleton()->canvas_create();105sel_drag_ci = RS::get_singleton()->canvas_item_create();106107/// 2D Selection Box Generation108109sbox_2d_ci = RS::get_singleton()->canvas_item_create();110RS::get_singleton()->viewport_attach_canvas(root->get_viewport_rid(), draw_canvas);111RS::get_singleton()->canvas_item_set_parent(sel_drag_ci, draw_canvas);112RS::get_singleton()->canvas_item_set_parent(sbox_2d_ci, draw_canvas);113114#ifndef _3D_DISABLED115cursor = Cursor();116117camera_fov = p_settings.get("editors/3d/default_fov", 70);118camera_znear = p_settings.get("editors/3d/default_z_near", 0.05);119camera_zfar = p_settings.get("editors/3d/default_z_far", 4'000);120121invert_x_axis = p_settings.get("editors/3d/navigation/invert_x_axis", false);122invert_y_axis = p_settings.get("editors/3d/navigation/invert_y_axis", false);123warped_mouse_panning_3d = p_settings.get("editors/3d/navigation/warped_mouse_panning", true);124125freelook_base_speed = p_settings.get("editors/3d/freelook/freelook_base_speed", 5);126freelook_sensitivity = Math::deg_to_rad((real_t)p_settings.get("editors/3d/freelook/freelook_sensitivity", 0.25));127orbit_sensitivity = Math::deg_to_rad((real_t)p_settings.get("editors/3d/navigation_feel/orbit_sensitivity", 0.004));128translation_sensitivity = p_settings.get("editors/3d/navigation_feel/translation_sensitivity", 1);129130/// 3D Selection Box Generation131// Copied from the Node3DEditor implementation.132133sbox_3d_color = p_settings.get("editors/3d/selection_box_color", Color());134135// Use two AABBs to create the illusion of a slightly thicker line.136AABB aabb(Vector3(), Vector3(1, 1, 1));137138// Create a x-ray (visible through solid surfaces) and standard version of the selection box.139// Both will be drawn at the same position, but with different opacity.140// This lets the user see where the selection is while still having a sense of depth.141Ref<SurfaceTool> st = memnew(SurfaceTool);142Ref<SurfaceTool> st_xray = memnew(SurfaceTool);143144st->begin(Mesh::PRIMITIVE_LINES);145st_xray->begin(Mesh::PRIMITIVE_LINES);146for (int i = 0; i < 12; i++) {147Vector3 a, b;148aabb.get_edge(i, a, b);149150st->add_vertex(a);151st->add_vertex(b);152st_xray->add_vertex(a);153st_xray->add_vertex(b);154}155156Ref<StandardMaterial3D> mat = memnew(StandardMaterial3D);157mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);158mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);159mat->set_albedo(sbox_3d_color);160mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);161st->set_material(mat);162sbox_3d_mesh = st->commit();163164Ref<StandardMaterial3D> mat_xray = memnew(StandardMaterial3D);165mat_xray->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);166mat_xray->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);167mat_xray->set_flag(StandardMaterial3D::FLAG_DISABLE_DEPTH_TEST, true);168mat_xray->set_albedo(sbox_3d_color * Color(1, 1, 1, 0.15));169mat_xray->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);170st_xray->set_material(mat_xray);171sbox_3d_mesh_xray = st_xray->commit();172#endif // _3D_DISABLED173174SceneTree::get_singleton()->connect("process_frame", callable_mp(this, &RuntimeNodeSelect::_process_frame));175SceneTree::get_singleton()->connect("physics_frame", callable_mp(this, &RuntimeNodeSelect::_physics_frame));176177// This function will be called before the root enters the tree at first when the Game view is passing its settings to178// the debugger, so queue the update for after it enters.179root->connect(SceneStringName(tree_entered), callable_mp(this, &RuntimeNodeSelect::_update_input_state), Object::CONNECT_ONE_SHOT);180}181182void RuntimeNodeSelect::_node_set_type(NodeType p_type) {183node_select_type = p_type;184_update_input_state();185}186187void RuntimeNodeSelect::_select_set_mode(SelectMode p_mode) {188node_select_mode = p_mode;189}190191void RuntimeNodeSelect::_set_camera_override_enabled(bool p_enabled) {192camera_override = p_enabled;193194if (camera_first_override) {195_reset_camera_2d();196#ifndef _3D_DISABLED197_reset_camera_3d();198#endif // _3D_DISABLED199200camera_first_override = false;201} else if (p_enabled) {202_update_view_2d();203204#ifndef _3D_DISABLED205Window *root = SceneTree::get_singleton()->get_root();206ERR_FAIL_COND(!root->is_camera_3d_override_enabled());207Camera3D *override_camera = root->get_override_camera_3d();208override_camera->set_transform(_get_cursor_transform());209override_camera->set_perspective(camera_fov * cursor.fov_scale, camera_znear, camera_zfar);210#endif // _3D_DISABLED211}212}213214void RuntimeNodeSelect::_root_window_input(const Ref<InputEvent> &p_event) {215Window *root = SceneTree::get_singleton()->get_root();216if (node_select_type == NODE_TYPE_NONE || (selection_list && selection_list->is_visible())) {217// Workaround for platforms that don't allow subwindows.218if (selection_list && selection_list->is_visible() && selection_list->is_embedded()) {219root->set_disable_input_override(false);220selection_list->push_input(p_event);221callable_mp(root->get_viewport(), &Viewport::set_disable_input_override).call_deferred(true);222}223224return;225}226227bool is_dragging_camera = false;228if (camera_override) {229if (node_select_type == NODE_TYPE_2D) {230is_dragging_camera = panner->gui_input(p_event, Rect2(Vector2(), root->get_visible_rect().get_size()));231#ifndef _3D_DISABLED232} else if (node_select_type == NODE_TYPE_3D && selection_drag_state == SELECTION_DRAG_NONE) {233if (_handle_3d_input(p_event)) {234return;235}236#endif // _3D_DISABLED237}238}239240Ref<InputEventMouseButton> b = p_event;241242if (selection_drag_state == SELECTION_DRAG_MOVE) {243Ref<InputEventMouseMotion> m = p_event;244if (m.is_valid()) {245_update_selection_drag(root->get_screen_transform().affine_inverse().xform(m->get_position()));246return;247} else if (b.is_valid()) {248// Account for actions like zooming.249_update_selection_drag(root->get_screen_transform().affine_inverse().xform(b->get_position()));250}251}252253if (b.is_null()) {254return;255}256257// Ignore mouse wheel inputs.258if (b->get_button_index() != MouseButton::LEFT && b->get_button_index() != MouseButton::RIGHT) {259return;260}261262if (selection_drag_state == SELECTION_DRAG_MOVE && !b->is_pressed() && b->get_button_index() == MouseButton::LEFT) {263selection_drag_state = SELECTION_DRAG_END;264selection_drag_area = selection_drag_area.abs();265_update_selection_drag();266267// Trigger a selection in the position on release.268if (multi_shortcut_pressed) {269selection_position = root->get_screen_transform().affine_inverse().xform(b->get_position());270}271}272273if (!is_dragging_camera && b->is_pressed()) {274multi_shortcut_pressed = b->is_shift_pressed();275list_shortcut_pressed = node_select_mode == SELECT_MODE_SINGLE && b->get_button_index() == MouseButton::RIGHT && b->is_alt_pressed();276if (list_shortcut_pressed || b->get_button_index() == MouseButton::LEFT) {277selection_position = root->get_screen_transform().affine_inverse().xform(b->get_position());278}279}280}281282void RuntimeNodeSelect::_items_popup_index_pressed(int p_index, PopupMenu *p_popup) {283Object *obj = p_popup->get_item_metadata(p_index).get_validated_object();284if (obj) {285Vector<Node *> node;286node.append(Object::cast_to<Node>(obj));287_send_ids(node);288}289}290291void RuntimeNodeSelect::_update_input_state() {292SceneTree *scene_tree = SceneTree::get_singleton();293// This function can be called at the very beginning, when the root hasn't entered the tree yet.294// So check first to avoid a crash.295if (!scene_tree->get_root()->is_inside_tree()) {296return;297}298299bool disable_input = scene_tree->is_suspended() || node_select_type != RuntimeNodeSelect::NODE_TYPE_NONE;300Input::get_singleton()->set_disable_input(disable_input);301Input::get_singleton()->set_mouse_mode_override_enabled(disable_input);302scene_tree->get_root()->set_disable_input_override(disable_input);303}304305void RuntimeNodeSelect::_process_frame() {306#ifndef _3D_DISABLED307if (camera_freelook) {308Transform3D transform = _get_cursor_transform();309Vector3 forward = transform.basis.xform(Vector3(0, 0, -1));310const Vector3 right = transform.basis.xform(Vector3(1, 0, 0));311Vector3 up = transform.basis.xform(Vector3(0, 1, 0));312313Vector3 direction;314315Input *input = Input::get_singleton();316bool was_input_disabled = input->is_input_disabled();317if (was_input_disabled) {318input->set_disable_input(false);319}320321if (input->is_physical_key_pressed(Key::A)) {322direction -= right;323}324if (input->is_physical_key_pressed(Key::D)) {325direction += right;326}327if (input->is_physical_key_pressed(Key::W)) {328direction += forward;329}330if (input->is_physical_key_pressed(Key::S)) {331direction -= forward;332}333if (input->is_physical_key_pressed(Key::E)) {334direction += up;335}336if (input->is_physical_key_pressed(Key::Q)) {337direction -= up;338}339340real_t speed = freelook_base_speed;341if (input->is_physical_key_pressed(Key::SHIFT)) {342speed *= 3.0;343}344if (input->is_physical_key_pressed(Key::ALT)) {345speed *= 0.333333;346}347348if (was_input_disabled) {349input->set_disable_input(true);350}351352if (direction != Vector3()) {353Window *root = SceneTree::get_singleton()->get_root();354ERR_FAIL_COND(!root->is_camera_3d_override_enabled());355356// Calculate the process time manually, as the time scale is frozen.357const double process_time = (1.0 / Engine::get_singleton()->get_frames_per_second()) * Engine::get_singleton()->get_unfrozen_time_scale();358const Vector3 motion = direction * speed * process_time;359cursor.pos += motion;360cursor.eye_pos += motion;361362root->get_override_camera_3d()->set_transform(_get_cursor_transform());363}364}365#endif // _3D_DISABLED366367if (selection_update_queued || !SceneTree::get_singleton()->is_suspended()) {368selection_update_queued = false;369if (has_selection) {370_update_selection();371}372}373}374375void RuntimeNodeSelect::_physics_frame() {376if (selection_drag_state != SELECTION_DRAG_END && (selection_drag_state == SELECTION_DRAG_MOVE || Math::is_inf(selection_position.x))) {377return;378}379380Window *root = SceneTree::get_singleton()->get_root();381bool selection_drag_valid = selection_drag_state == SELECTION_DRAG_END && selection_drag_area.get_area() > SELECTION_MIN_AREA;382Vector<SelectResult> items;383384if (node_select_type == NODE_TYPE_2D) {385if (selection_drag_valid) {386for (int i = 0; i < root->get_child_count(); i++) {387_find_canvas_items_at_rect(selection_drag_area, root->get_child(i), items);388}389} else if (!Math::is_inf(selection_position.x)) {390for (int i = 0; i < root->get_child_count(); i++) {391_find_canvas_items_at_pos(selection_position, root->get_child(i), items);392}393}394395#ifndef _3D_DISABLED396} else if (node_select_type == NODE_TYPE_3D) {397if (selection_drag_valid) {398_find_3d_items_at_rect(selection_drag_area, items);399} else {400_find_3d_items_at_pos(selection_position, items);401}402#endif // _3D_DISABLED403}404405if ((prefer_group_selection || avoid_locked_nodes) && !list_shortcut_pressed && node_select_mode == SELECT_MODE_SINGLE) {406for (int i = 0; i < items.size(); i++) {407Node *node = items[i].item;408Node *final_node = node;409real_t order = items[i].order;410411// Replace the node by the group if grouped.412if (prefer_group_selection) {413while (node && node != root) {414if (node->has_meta("_edit_group_")) {415final_node = node;416417if (Object::cast_to<CanvasItem>(final_node)) {418CanvasItem *ci_tmp = Object::cast_to<CanvasItem>(final_node);419order = ci_tmp->get_effective_z_index() + ci_tmp->get_canvas_layer();420#ifndef _3D_DISABLED421} else if (Object::cast_to<Node3D>(final_node)) {422Node3D *node3d_tmp = Object::cast_to<Node3D>(final_node);423Camera3D *camera = root->get_camera_3d();424Vector3 pos = camera->project_ray_origin(selection_position);425order = -pos.distance_to(node3d_tmp->get_global_transform().origin);426#endif // _3D_DISABLED427}428}429node = node->get_parent();430}431}432433// Filter out locked nodes.434if (avoid_locked_nodes && final_node->get_meta("_edit_lock_", false)) {435items.remove_at(i);436i--;437continue;438}439440items.write[i].item = final_node;441items.write[i].order = order;442}443}444445// Remove possible duplicates.446for (int i = 0; i < items.size(); i++) {447Node *item = items[i].item;448for (int j = 0; j < i; j++) {449if (items[j].item == item) {450items.remove_at(i);451i--;452453break;454}455}456}457458items.sort();459460switch (selection_drag_state) {461case SELECTION_DRAG_END: {462selection_position = Point2(Math::INF, Math::INF);463selection_drag_state = SELECTION_DRAG_NONE;464465if (selection_drag_area.get_area() > SELECTION_MIN_AREA) {466if (!items.is_empty()) {467Vector<Node *> nodes;468for (const SelectResult item : items) {469nodes.append(item.item);470}471_send_ids(nodes, false);472}473474_update_selection_drag();475return;476}477478_update_selection_drag();479} break;480481case SELECTION_DRAG_NONE: {482if (node_select_mode == SELECT_MODE_LIST) {483break;484}485486if (multi_shortcut_pressed) {487// Allow forcing box selection when an item was clicked.488selection_drag_state = SELECTION_DRAG_MOVE;489} else if (items.is_empty()) {490#ifdef _3D_DISABLED491if (!selected_ci_nodes.is_empty()) {492#else493if (!selected_ci_nodes.is_empty() || !selected_3d_nodes.is_empty()) {494#endif // _3D_DISABLED495EngineDebugger::get_singleton()->send_message("remote_nothing_selected", Array());496_clear_selection();497}498499selection_drag_state = SELECTION_DRAG_MOVE;500} else {501break;502}503504[[fallthrough]];505}506507case SELECTION_DRAG_MOVE: {508selection_drag_area.position = selection_position;509510// Stop selection on click, so it can happen on release if the selection area doesn't pass the threshold.511if (multi_shortcut_pressed) {512return;513}514}515}516517if (items.is_empty()) {518selection_position = Point2(Math::INF, Math::INF);519return;520}521if ((!list_shortcut_pressed && node_select_mode == SELECT_MODE_SINGLE) || items.size() == 1) {522selection_position = Point2(Math::INF, Math::INF);523524Vector<Node *> node;525node.append(items[0].item);526_send_ids(node);527528return;529}530531if (!selection_list && (list_shortcut_pressed || node_select_mode == SELECT_MODE_LIST)) {532_open_selection_list(items, selection_position);533}534535selection_position = Point2(Math::INF, Math::INF);536}537538void RuntimeNodeSelect::_send_ids(const Vector<Node *> &p_picked_nodes, bool p_invert_new_selections) {539ERR_FAIL_COND(p_picked_nodes.is_empty());540541Vector<Node *> picked_nodes = p_picked_nodes;542Array message;543544if (!multi_shortcut_pressed) {545if (picked_nodes.size() > max_selection) {546picked_nodes.resize(max_selection);547EngineDebugger::get_singleton()->send_message("show_selection_limit_warning", Array());548}549550for (const Node *node : picked_nodes) {551SceneDebuggerObject obj(node->get_instance_id());552Array arr;553obj.serialize(arr);554message.append(arr);555}556557EngineDebugger::get_singleton()->send_message("remote_objects_selected", message);558_set_selected_nodes(picked_nodes);559560return;561}562563LocalVector<Node *> nodes;564LocalVector<ObjectID> ids;565for (Node *node : picked_nodes) {566ObjectID id = node->get_instance_id();567if (CanvasItem *ci = Object::cast_to<CanvasItem>(node)) {568if (selected_ci_nodes.has(id)) {569if (p_invert_new_selections) {570selected_ci_nodes.erase(id);571}572} else {573ids.push_back(id);574nodes.push_back(ci);575}576} else {577#ifndef _3D_DISABLED578if (Node3D *node3d = Object::cast_to<Node3D>(node)) {579if (selected_3d_nodes.has(id)) {580if (p_invert_new_selections) {581selected_3d_nodes.erase(id);582}583} else {584ids.push_back(id);585nodes.push_back(node3d);586}587}588#endif // _3D_DISABLED589}590}591592uint32_t limit = max_selection - selected_ci_nodes.size();593#ifndef _3D_DISABLED594limit -= selected_3d_nodes.size();595#endif // _3D_DISABLED596if (ids.size() > limit) {597ids.resize(limit);598nodes.resize(limit);599EngineDebugger::get_singleton()->send_message("show_selection_limit_warning", Array());600}601602for (ObjectID id : selected_ci_nodes) {603ids.push_back(id);604nodes.push_back(ObjectDB::get_instance<Node>(id));605}606#ifndef _3D_DISABLED607for (const KeyValue<ObjectID, Ref<SelectionBox3D>> &KV : selected_3d_nodes) {608ids.push_back(KV.key);609nodes.push_back(ObjectDB::get_instance<Node>(KV.key));610}611#endif // _3D_DISABLED612613if (ids.is_empty()) {614EngineDebugger::get_singleton()->send_message("remote_nothing_selected", message);615} else {616for (const ObjectID &id : ids) {617SceneDebuggerObject obj(id);618Array arr;619obj.serialize(arr);620message.append(arr);621}622623EngineDebugger::get_singleton()->send_message("remote_objects_selected", message);624}625626_set_selected_nodes(Vector<Node *>(nodes));627}628629void RuntimeNodeSelect::_set_selected_nodes(const Vector<Node *> &p_nodes) {630if (p_nodes.is_empty()) {631_clear_selection();632return;633}634635bool changed = false;636LocalVector<ObjectID> nodes_ci;637#ifndef _3D_DISABLED638HashMap<ObjectID, Ref<SelectionBox3D>> nodes_3d;639#endif // _3D_DISABLED640641for (Node *node : p_nodes) {642ObjectID id = node->get_instance_id();643if (Object::cast_to<CanvasItem>(node)) {644if (!changed || !selected_ci_nodes.has(id)) {645changed = true;646}647648nodes_ci.push_back(id);649} else {650#ifndef _3D_DISABLED651Node3D *node_3d = Object::cast_to<Node3D>(node);652if (!node_3d || !node_3d->is_inside_world()) {653continue;654}655656if (!changed || !selected_3d_nodes.has(id)) {657changed = true;658}659660if (selected_3d_nodes.has(id)) {661// Assign an already available visual instance.662nodes_3d[id] = selected_3d_nodes.get(id);663continue;664}665666if (sbox_3d_mesh.is_null() || sbox_3d_mesh_xray.is_null()) {667continue;668}669670Ref<SelectionBox3D> sb;671sb.instantiate();672nodes_3d[id] = sb;673674RID scenario = node_3d->get_world_3d()->get_scenario();675676sb->instance = RS::get_singleton()->instance_create2(sbox_3d_mesh->get_rid(), scenario);677sb->instance_ofs = RS::get_singleton()->instance_create2(sbox_3d_mesh->get_rid(), scenario);678RS::get_singleton()->instance_geometry_set_cast_shadows_setting(sb->instance, RS::SHADOW_CASTING_SETTING_OFF);679RS::get_singleton()->instance_geometry_set_cast_shadows_setting(sb->instance_ofs, RS::SHADOW_CASTING_SETTING_OFF);680RS::get_singleton()->instance_geometry_set_flag(sb->instance, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);681RS::get_singleton()->instance_geometry_set_flag(sb->instance, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);682RS::get_singleton()->instance_geometry_set_flag(sb->instance_ofs, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);683RS::get_singleton()->instance_geometry_set_flag(sb->instance_ofs, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);684685sb->instance_xray = RS::get_singleton()->instance_create2(sbox_3d_mesh_xray->get_rid(), scenario);686sb->instance_xray_ofs = RS::get_singleton()->instance_create2(sbox_3d_mesh_xray->get_rid(), scenario);687RS::get_singleton()->instance_geometry_set_cast_shadows_setting(sb->instance_xray, RS::SHADOW_CASTING_SETTING_OFF);688RS::get_singleton()->instance_geometry_set_cast_shadows_setting(sb->instance_xray_ofs, RS::SHADOW_CASTING_SETTING_OFF);689RS::get_singleton()->instance_geometry_set_flag(sb->instance_xray, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);690RS::get_singleton()->instance_geometry_set_flag(sb->instance_xray, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);691RS::get_singleton()->instance_geometry_set_flag(sb->instance_xray_ofs, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);692RS::get_singleton()->instance_geometry_set_flag(sb->instance_xray_ofs, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);693#endif // _3D_DISABLED694}695}696697#ifdef _3D_DISABLED698if (!changed && nodes_ci.size() == selected_ci_nodes.size()) {699return;700}701#else702if (!changed && nodes_ci.size() == selected_ci_nodes.size() && nodes_3d.size() == selected_3d_nodes.size()) {703return;704}705#endif // _3D_DISABLED706707_clear_selection();708selected_ci_nodes = nodes_ci;709has_selection = !nodes_ci.is_empty();710711#ifndef _3D_DISABLED712if (!nodes_3d.is_empty()) {713selected_3d_nodes = nodes_3d;714has_selection = true;715}716#endif // _3D_DISABLED717718_queue_selection_update();719}720721void RuntimeNodeSelect::_queue_selection_update() {722if (has_selection && selection_visible) {723if (SceneTree::get_singleton()->is_suspended()) {724_update_selection();725} else {726selection_update_queued = true;727}728}729}730731void RuntimeNodeSelect::_update_selection() {732RS::get_singleton()->canvas_item_clear(sbox_2d_ci);733RS::get_singleton()->canvas_item_set_visible(sbox_2d_ci, selection_visible);734735for (LocalVector<ObjectID>::Iterator E = selected_ci_nodes.begin(); E != selected_ci_nodes.end(); ++E) {736ObjectID id = *E;737CanvasItem *ci = ObjectDB::get_instance<CanvasItem>(id);738if (!ci) {739selected_ci_nodes.erase(id);740--E;741continue;742}743744if (!ci->is_inside_tree()) {745continue;746}747748Transform2D xform = ci->get_global_transform_with_canvas();749750// Fallback.751Rect2 rect = Rect2(Vector2(), Vector2(10, 10));752753if (ci->_edit_use_rect()) {754rect = ci->_edit_get_rect();755} else {756#ifndef PHYSICS_2D_DISABLED757CollisionShape2D *collision_shape = Object::cast_to<CollisionShape2D>(ci);758if (collision_shape) {759Ref<Shape2D> shape = collision_shape->get_shape();760if (shape.is_valid()) {761rect = shape->get_rect();762}763}764#endif // PHYSICS_2D_DISABLED765}766767const Vector2 endpoints[4] = {768xform.xform(rect.position),769xform.xform(rect.position + Point2(rect.size.x, 0)),770xform.xform(rect.position + rect.size),771xform.xform(rect.position + Point2(0, rect.size.y))772};773774const Color selection_color_2d = Color(1, 0.6, 0.4, 0.7);775for (int i = 0; i < 4; i++) {776RS::get_singleton()->canvas_item_add_line(sbox_2d_ci, endpoints[i], endpoints[(i + 1) % 4], selection_color_2d, sel_2d_scale);777}778}779780#ifndef _3D_DISABLED781for (HashMap<ObjectID, Ref<SelectionBox3D>>::ConstIterator KV = selected_3d_nodes.begin(); KV != selected_3d_nodes.end(); ++KV) {782ObjectID id = KV->key;783Node3D *node_3d = ObjectDB::get_instance<Node3D>(id);784if (!node_3d) {785selected_3d_nodes.erase(id);786--KV;787continue;788}789790if (!node_3d->is_inside_tree()) {791continue;792}793794// Fallback.795AABB bounds(Vector3(-0.5, -0.5, -0.5), Vector3(1, 1, 1));796797VisualInstance3D *visual_instance = Object::cast_to<VisualInstance3D>(node_3d);798if (visual_instance) {799bounds = visual_instance->get_aabb();800} else {801#ifndef PHYSICS_3D_DISABLED802CollisionShape3D *collision_shape = Object::cast_to<CollisionShape3D>(node_3d);803if (collision_shape) {804Ref<Shape3D> shape = collision_shape->get_shape();805if (shape.is_valid()) {806bounds = shape->get_debug_mesh()->get_aabb();807}808}809#endif // PHYSICS_3D_DISABLED810}811812Transform3D xform_to_top_level_parent_space = node_3d->get_global_transform().affine_inverse() * node_3d->get_global_transform();813bounds = xform_to_top_level_parent_space.xform(bounds);814Transform3D t = node_3d->get_global_transform();815816Ref<SelectionBox3D> sb = KV->value;817if (t == sb->transform && bounds == sb->bounds) {818continue; // Nothing changed.819}820sb->transform = t;821sb->bounds = bounds;822823Transform3D t_offset = t;824825// Apply AABB scaling before item's global transform.826{827const Vector3 offset(0.005, 0.005, 0.005);828Basis aabb_s;829aabb_s.scale(bounds.size + offset);830t.translate_local(bounds.position - offset / 2);831t.basis = t.basis * aabb_s;832}833{834const Vector3 offset(0.01, 0.01, 0.01);835Basis aabb_s;836aabb_s.scale(bounds.size + offset);837t_offset.translate_local(bounds.position - offset / 2);838t_offset.basis = t_offset.basis * aabb_s;839}840841RS::get_singleton()->instance_set_visible(sb->instance, selection_visible);842RS::get_singleton()->instance_set_visible(sb->instance_ofs, selection_visible);843RS::get_singleton()->instance_set_visible(sb->instance_xray, selection_visible);844RS::get_singleton()->instance_set_visible(sb->instance_xray_ofs, selection_visible);845846RS::get_singleton()->instance_set_transform(sb->instance, t);847RS::get_singleton()->instance_set_transform(sb->instance_ofs, t_offset);848RS::get_singleton()->instance_set_transform(sb->instance_xray, t);849RS::get_singleton()->instance_set_transform(sb->instance_xray_ofs, t_offset);850}851#endif // _3D_DISABLED852}853854void RuntimeNodeSelect::_clear_selection() {855selected_ci_nodes.clear();856if (draw_canvas.is_valid()) {857RS::get_singleton()->canvas_item_clear(sbox_2d_ci);858}859860#ifndef _3D_DISABLED861selected_3d_nodes.clear();862#endif // _3D_DISABLED863864has_selection = false;865}866867void RuntimeNodeSelect::_update_selection_drag(const Point2 &p_end_pos) {868RS::get_singleton()->canvas_item_clear(sel_drag_ci);869870if (selection_drag_state != SELECTION_DRAG_MOVE) {871return;872}873874selection_drag_area.size = p_end_pos - selection_drag_area.position;875876if (selection_drag_state == SELECTION_DRAG_END) {877return;878}879880Rect2 selection_drawing = selection_drag_area.abs();881int thickness = 1;882883const Vector2 endpoints[4] = {884selection_drawing.position,885selection_drawing.position + Point2(selection_drawing.size.x, 0),886selection_drawing.position + selection_drawing.size,887selection_drawing.position + Point2(0, selection_drawing.size.y)888};889890// Draw fill.891RS::get_singleton()->canvas_item_add_rect(sel_drag_ci, selection_drawing, selection_area_fill);892// Draw outline.893for (int i = 0; i < 4; i++) {894RS::get_singleton()->canvas_item_add_line(sel_drag_ci, endpoints[i], endpoints[(i + 1) % 4], selection_area_outline, thickness);895}896}897898void RuntimeNodeSelect::_open_selection_list(const Vector<SelectResult> &p_items, const Point2 &p_pos) {899Window *root = SceneTree::get_singleton()->get_root();900901selection_list = memnew(PopupMenu);902selection_list->set_theme(ThemeDB::get_singleton()->get_default_theme());903selection_list->set_auto_translate_mode(Node::AUTO_TRANSLATE_MODE_DISABLED);904selection_list->set_force_native(true);905selection_list->connect("index_pressed", callable_mp(this, &RuntimeNodeSelect::_items_popup_index_pressed).bind(selection_list));906selection_list->connect("popup_hide", callable_mp(this, &RuntimeNodeSelect::_close_selection_list));907908root->add_child(selection_list);909910for (const SelectResult &I : p_items) {911int locked = 0;912if (I.item->get_meta("_edit_lock_", false)) {913locked = 1;914} else {915Node *scene = SceneTree::get_singleton()->get_root();916Node *node = I.item;917918while (node && node != scene->get_parent()) {919if (node->has_meta("_edit_group_")) {920locked = 2;921}922node = node->get_parent();923}924}925926String suffix;927if (locked == 1) {928suffix = " (" + RTR("Locked") + ")";929} else if (locked == 2) {930suffix = " (" + RTR("Grouped") + ")";931}932933selection_list->add_item((String)I.item->get_name() + suffix);934selection_list->set_item_metadata(-1, I.item);935}936937selection_list->set_position(selection_list->is_embedded() ? p_pos : (Input::get_singleton()->get_mouse_position() + root->get_position()));938selection_list->reset_size();939selection_list->popup();940941selection_list->set_content_scale_factor(1);942selection_list->set_min_size(selection_list->get_contents_minimum_size());943selection_list->reset_size();944945// FIXME: Ugly hack that stops the popup from hiding when the button is released.946selection_list->call_deferred(SNAME("set_position"), selection_list->get_position() + Point2(1, 0));947}948949void RuntimeNodeSelect::_close_selection_list() {950selection_list->queue_free();951selection_list = nullptr;952}953954void RuntimeNodeSelect::_set_selection_visible(bool p_visible) {955selection_visible = p_visible;956957if (has_selection) {958_update_selection();959}960}961962void RuntimeNodeSelect::_set_avoid_locked(bool p_enabled) {963avoid_locked_nodes = p_enabled;964}965966void RuntimeNodeSelect::_set_prefer_group(bool p_enabled) {967prefer_group_selection = p_enabled;968}969970// Copied and trimmed from the CanvasItemEditor implementation.971void RuntimeNodeSelect::_find_canvas_items_at_pos(const Point2 &p_pos, Node *p_node, Vector<SelectResult> &r_items, const Transform2D &p_parent_xform, const Transform2D &p_canvas_xform) {972if (!p_node || Object::cast_to<Viewport>(p_node)) {973return;974}975976CanvasItem *ci = Object::cast_to<CanvasItem>(p_node);977for (int i = p_node->get_child_count() - 1; i >= 0; i--) {978if (ci) {979if (!ci->is_set_as_top_level()) {980_find_canvas_items_at_pos(p_pos, p_node->get_child(i), r_items, p_parent_xform * ci->get_transform(), p_canvas_xform);981} else {982_find_canvas_items_at_pos(p_pos, p_node->get_child(i), r_items, ci->get_transform(), p_canvas_xform);983}984} else {985CanvasLayer *cl = Object::cast_to<CanvasLayer>(p_node);986_find_canvas_items_at_pos(p_pos, p_node->get_child(i), r_items, Transform2D(), cl ? cl->get_transform() : p_canvas_xform);987}988}989990if (!ci || !ci->is_visible_in_tree()) {991return;992}993994Transform2D xform = p_canvas_xform;995if (!ci->is_set_as_top_level()) {996xform *= p_parent_xform;997}998999Window *root = SceneTree::get_singleton()->get_root();1000Point2 pos;10011002// Cameras don't affect `CanvasLayer`s.1003if (!ci->get_canvas_layer_node() || ci->get_canvas_layer_node()->is_following_viewport()) {1004pos = root->get_canvas_transform().affine_inverse().xform(p_pos);1005} else {1006pos = p_pos;1007}10081009xform = (xform * ci->get_transform()).affine_inverse();1010const real_t local_grab_distance = xform.basis_xform(Vector2(sel_2d_grab_dist, 0)).length() / view_2d_zoom;1011if (ci->_edit_is_selected_on_click(xform.xform(pos), local_grab_distance)) {1012SelectResult res;1013res.item = ci;1014res.order = ci->get_effective_z_index() + ci->get_canvas_layer();1015r_items.push_back(res);10161017#ifndef PHYSICS_2D_DISABLED1018// If it's a shape, get the collision object it's from.1019// FIXME: If the collision object has multiple shapes, only the topmost will be above it in the list.1020if (Object::cast_to<CollisionShape2D>(ci) || Object::cast_to<CollisionPolygon2D>(ci)) {1021CollisionObject2D *collision_object = Object::cast_to<CollisionObject2D>(ci->get_parent());1022if (collision_object) {1023SelectResult res_col;1024res_col.item = ci->get_parent();1025res_col.order = collision_object->get_z_index() + ci->get_canvas_layer();1026r_items.push_back(res_col);1027}1028}1029#endif // PHYSICS_2D_DISABLED1030}1031}10321033// Copied and trimmed from the CanvasItemEditor implementation.1034void RuntimeNodeSelect::_find_canvas_items_at_rect(const Rect2 &p_rect, Node *p_node, Vector<SelectResult> &r_items, const Transform2D &p_parent_xform, const Transform2D &p_canvas_xform) {1035if (!p_node || Object::cast_to<Viewport>(p_node)) {1036return;1037}10381039CanvasItem *ci = Object::cast_to<CanvasItem>(p_node);1040for (int i = p_node->get_child_count() - 1; i >= 0; i--) {1041if (ci) {1042if (!ci->is_set_as_top_level()) {1043_find_canvas_items_at_rect(p_rect, p_node->get_child(i), r_items, p_parent_xform * ci->get_transform(), p_canvas_xform);1044} else {1045_find_canvas_items_at_rect(p_rect, p_node->get_child(i), r_items, ci->get_transform(), p_canvas_xform);1046}1047} else {1048CanvasLayer *cl = Object::cast_to<CanvasLayer>(p_node);1049_find_canvas_items_at_rect(p_rect, p_node->get_child(i), r_items, Transform2D(), cl ? cl->get_transform() : p_canvas_xform);1050}1051}10521053if (!ci || !ci->is_visible_in_tree()) {1054return;1055}10561057Transform2D xform = p_canvas_xform;1058if (!ci->is_set_as_top_level()) {1059xform *= p_parent_xform;1060}10611062Window *root = SceneTree::get_singleton()->get_root();1063Rect2 rect;1064// Cameras don't affect `CanvasLayer`s.1065if (!ci->get_canvas_layer_node() || ci->get_canvas_layer_node()->is_following_viewport()) {1066rect = root->get_canvas_transform().affine_inverse().xform(p_rect);1067} else {1068rect = p_rect;1069}1070rect = (xform * ci->get_transform()).affine_inverse().xform(rect);10711072bool selected = false;1073if (ci->_edit_use_rect()) {1074Rect2 ci_rect = ci->_edit_get_rect();1075if (rect.has_point(ci_rect.position) &&1076rect.has_point(ci_rect.position + Vector2(ci_rect.size.x, 0)) &&1077rect.has_point(ci_rect.position + Vector2(ci_rect.size.x, ci_rect.size.y)) &&1078rect.has_point(ci_rect.position + Vector2(0, ci_rect.size.y))) {1079selected = true;1080}1081} else if (rect.has_point(Point2())) {1082selected = true;1083}10841085if (selected) {1086SelectResult res;1087res.item = ci;1088res.order = ci->get_effective_z_index() + ci->get_canvas_layer();1089r_items.push_back(res);1090}1091}10921093void RuntimeNodeSelect::_pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event) {1094Vector2 scroll = SceneTree::get_singleton()->get_root()->get_screen_transform().affine_inverse().xform(p_scroll_vec);1095view_2d_offset.x -= scroll.x / view_2d_zoom;1096view_2d_offset.y -= scroll.y / view_2d_zoom;10971098_update_view_2d();1099}11001101// A very shallow copy of the same function inside CanvasItemEditor.1102void RuntimeNodeSelect::_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event) {1103real_t prev_zoom = view_2d_zoom;1104view_2d_zoom = CLAMP(view_2d_zoom * p_zoom_factor, VIEW_2D_MIN_ZOOM, VIEW_2D_MAX_ZOOM);11051106Vector2 pos = SceneTree::get_singleton()->get_root()->get_screen_transform().affine_inverse().xform(p_origin);1107view_2d_offset += pos / prev_zoom - pos / view_2d_zoom;11081109// We want to align in-scene pixels to screen pixels, this prevents blurry rendering1110// of small details (texts, lines).1111// This correction adds a jitter movement when zooming, so we correct only when the1112// zoom factor is an integer. (in the other cases, all pixels won't be aligned anyway)1113const real_t closest_zoom_factor = Math::round(view_2d_zoom);1114if (Math::is_zero_approx(view_2d_zoom - closest_zoom_factor)) {1115// Make sure scene pixel at view_offset is aligned on a screen pixel.1116Vector2 view_offset_int = view_2d_offset.floor();1117Vector2 view_offset_frac = view_2d_offset - view_offset_int;1118view_2d_offset = view_offset_int + (view_offset_frac * closest_zoom_factor).round() / closest_zoom_factor;1119}11201121_update_view_2d();1122}11231124void RuntimeNodeSelect::_reset_camera_2d() {1125camera_first_override = true;1126Window *root = SceneTree::get_singleton()->get_root();1127Camera2D *game_camera = root->is_camera_2d_override_enabled() ? root->get_overridden_camera_2d() : root->get_camera_2d();1128if (game_camera) {1129// Ideally we should be using Camera2D::get_camera_transform() but it's not so this hack will have to do for now.1130view_2d_offset = game_camera->get_camera_screen_center() - (0.5 * root->get_visible_rect().size);1131} else {1132view_2d_offset = Vector2();1133}11341135view_2d_zoom = 1;11361137if (root->is_camera_2d_override_enabled()) {1138_update_view_2d();1139}1140}11411142void RuntimeNodeSelect::_update_view_2d() {1143Window *root = SceneTree::get_singleton()->get_root();1144ERR_FAIL_COND(!root->is_camera_2d_override_enabled());11451146Camera2D *override_camera = root->get_override_camera_2d();1147override_camera->set_anchor_mode(Camera2D::ANCHOR_MODE_FIXED_TOP_LEFT);1148override_camera->set_zoom(Vector2(view_2d_zoom, view_2d_zoom));1149override_camera->set_offset(view_2d_offset);11501151_queue_selection_update();1152}11531154#ifndef _3D_DISABLED1155void RuntimeNodeSelect::_find_3d_items_at_pos(const Point2 &p_pos, Vector<SelectResult> &r_items) {1156Window *root = SceneTree::get_singleton()->get_root();11571158Vector3 ray, pos, to;1159Camera3D *camera = root->get_camera_3d();1160if (!camera) {1161return;1162}11631164ray = camera->project_ray_normal(p_pos);1165pos = camera->project_ray_origin(p_pos);1166to = pos + ray * camera->get_far();11671168#ifndef PHYSICS_3D_DISABLED1169// Start with physical objects.1170PhysicsDirectSpaceState3D *ss = root->get_world_3d()->get_direct_space_state();1171PhysicsDirectSpaceState3D::RayResult result;1172HashSet<RID> excluded;1173PhysicsDirectSpaceState3D::RayParameters ray_params;1174ray_params.from = pos;1175ray_params.to = to;1176ray_params.collide_with_areas = true;1177while (true) {1178ray_params.exclude = excluded;1179if (ss->intersect_ray(ray_params, result)) {1180SelectResult res;1181res.item = Object::cast_to<Node>(result.collider);1182res.order = -pos.distance_to(result.position);11831184// Fetch collision shapes.1185CollisionObject3D *collision = Object::cast_to<CollisionObject3D>(result.collider);1186if (collision) {1187List<uint32_t> owners;1188collision->get_shape_owners(&owners);1189for (uint32_t &I : owners) {1190SelectResult res_shape;1191res_shape.item = Object::cast_to<Node>(collision->shape_owner_get_owner(I));1192res_shape.order = res.order;1193r_items.push_back(res_shape);1194}1195}11961197r_items.push_back(res);11981199excluded.insert(result.rid);1200} else {1201break;1202}1203}1204#endif // PHYSICS_3D_DISABLED12051206// Then go for the meshes.1207Vector<ObjectID> items = RS::get_singleton()->instances_cull_ray(pos, to, root->get_world_3d()->get_scenario());1208for (int i = 0; i < items.size(); i++) {1209Object *obj = ObjectDB::get_instance(items[i]);12101211GeometryInstance3D *geo_instance = Object::cast_to<GeometryInstance3D>(obj);1212if (geo_instance) {1213Ref<TriangleMesh> mesh_collision = geo_instance->generate_triangle_mesh();12141215if (mesh_collision.is_valid()) {1216Transform3D gt = geo_instance->get_global_transform();1217Transform3D ai = gt.affine_inverse();1218Vector3 point, normal;1219if (mesh_collision->intersect_ray(ai.xform(pos), ai.basis.xform(ray).normalized(), point, normal)) {1220SelectResult res;1221res.item = Object::cast_to<Node>(obj);1222res.order = -pos.distance_to(gt.xform(point));1223r_items.push_back(res);12241225continue;1226}1227}1228}12291230items.remove_at(i);1231i--;1232}1233}12341235void RuntimeNodeSelect::_find_3d_items_at_rect(const Rect2 &p_rect, Vector<SelectResult> &r_items) {1236Window *root = SceneTree::get_singleton()->get_root();1237Camera3D *camera = root->get_camera_3d();1238if (!camera) {1239return;1240}12411242Vector3 cam_pos = camera->get_global_position();1243Vector3 dist_pos = camera->project_ray_origin(p_rect.position + p_rect.size / 2);12441245real_t znear = camera->get_near();1246real_t zfar = camera->get_far();1247real_t zofs = MAX(0.0, 5.0 - znear);12481249const Point2 pos_end = p_rect.position + p_rect.size;1250Vector3 box[4] = {1251Vector3(1252MIN(p_rect.position.x, pos_end.x),1253MIN(p_rect.position.y, pos_end.y),1254zofs),1255Vector3(1256MAX(p_rect.position.x, pos_end.x),1257MIN(p_rect.position.y, pos_end.y),1258zofs),1259Vector3(1260MAX(p_rect.position.x, pos_end.x),1261MAX(p_rect.position.y, pos_end.y),1262zofs),1263Vector3(1264MIN(p_rect.position.x, pos_end.x),1265MAX(p_rect.position.y, pos_end.y),1266zofs)1267};12681269Vector<Plane> frustum;1270for (int i = 0; i < 4; i++) {1271Vector3 a = _get_screen_to_space(box[i]);1272Vector3 b = _get_screen_to_space(box[(i + 1) % 4]);1273frustum.push_back(Plane(a, b, cam_pos));1274}12751276// Get the camera normal.1277Plane near_plane = Plane(camera->get_global_transform().basis.get_column(2), cam_pos);12781279near_plane.d -= znear;1280frustum.push_back(near_plane);12811282Plane far_plane = -near_plane;1283far_plane.d += zfar;1284frustum.push_back(far_plane);12851286// Keep track of the currently listed nodes, so repeats can be ignored.1287HashSet<Node *> node_list;12881289#ifndef PHYSICS_3D_DISABLED1290Vector<Vector3> points = Geometry3D::compute_convex_mesh_points(&frustum[0], frustum.size());1291Ref<ConvexPolygonShape3D> shape;1292shape.instantiate();1293shape->set_points(points);12941295// Start with physical objects.1296PhysicsDirectSpaceState3D *ss = root->get_world_3d()->get_direct_space_state();1297PhysicsDirectSpaceState3D::ShapeResult results[32];1298PhysicsDirectSpaceState3D::ShapeParameters shape_params;1299shape_params.shape_rid = shape->get_rid();1300shape_params.collide_with_areas = true;1301const int num_hits = ss->intersect_shape(shape_params, results, 32);1302for (int i = 0; i < num_hits; i++) {1303const PhysicsDirectSpaceState3D::ShapeResult &result = results[i];1304SelectResult res;1305res.item = Object::cast_to<Node>(result.collider);1306res.order = -dist_pos.distance_to(Object::cast_to<Node3D>(res.item)->get_global_transform().origin);13071308// Fetch collision shapes.1309CollisionObject3D *collision = Object::cast_to<CollisionObject3D>(result.collider);1310if (collision) {1311List<uint32_t> owners;1312collision->get_shape_owners(&owners);1313for (uint32_t &I : owners) {1314SelectResult res_shape;1315res_shape.item = Object::cast_to<Node>(collision->shape_owner_get_owner(I));1316if (!node_list.has(res_shape.item)) {1317node_list.insert(res_shape.item);1318res_shape.order = res.order;1319r_items.push_back(res_shape);1320}1321}1322}13231324if (!node_list.has(res.item)) {1325node_list.insert(res.item);1326r_items.push_back(res);1327}1328}1329#endif // PHYSICS_3D_DISABLED13301331// Then go for the meshes.1332Vector<ObjectID> items = RS::get_singleton()->instances_cull_convex(frustum, root->get_world_3d()->get_scenario());1333for (int i = 0; i < items.size(); i++) {1334Object *obj = ObjectDB::get_instance(items[i]);1335GeometryInstance3D *geo_instance = Object::cast_to<GeometryInstance3D>(obj);1336if (geo_instance) {1337Ref<TriangleMesh> mesh_collision = geo_instance->generate_triangle_mesh();13381339if (mesh_collision.is_valid()) {1340Transform3D gt = geo_instance->get_global_transform();1341Vector3 mesh_scale = gt.get_basis().get_scale();1342gt.orthonormalize();13431344Transform3D it = gt.affine_inverse();13451346Vector<Plane> transformed_frustum;1347int plane_count = frustum.size();1348transformed_frustum.resize(plane_count);13491350for (int j = 0; j < plane_count; j++) {1351transformed_frustum.write[j] = it.xform(frustum[j]);1352}1353Vector<Vector3> convex_points = Geometry3D::compute_convex_mesh_points(transformed_frustum.ptr(), plane_count);1354if (mesh_collision->inside_convex_shape(transformed_frustum.ptr(), transformed_frustum.size(), convex_points.ptr(), convex_points.size(), mesh_scale)) {1355SelectResult res;1356res.item = Object::cast_to<Node>(obj);1357if (!node_list.has(res.item)) {1358node_list.insert(res.item);1359res.order = -dist_pos.distance_to(gt.origin);1360r_items.push_back(res);1361}13621363continue;1364}1365}1366}13671368items.remove_at(i);1369i--;1370}1371}13721373Vector3 RuntimeNodeSelect::_get_screen_to_space(const Vector3 &p_vector3) {1374Window *root = SceneTree::get_singleton()->get_root();1375Camera3D *camera = root->get_camera_3d();13761377Transform3D camera_transform = camera->get_camera_transform();1378Size2 size = root->get_size();1379real_t znear = camera->get_near();1380Projection cm = Projection::create_perspective(camera->get_fov(), size.aspect(), znear + p_vector3.z, camera->get_far());1381Vector2 screen_he = cm.get_viewport_half_extents();1382return camera_transform.xform(Vector3(((p_vector3.x / size.width) * 2.0 - 1.0) * screen_he.x, ((1.0 - (p_vector3.y / size.height)) * 2.0 - 1.0) * screen_he.y, -(znear + p_vector3.z)));1383}13841385bool RuntimeNodeSelect::_handle_3d_input(const Ref<InputEvent> &p_event) {1386Ref<InputEventMouseButton> b = p_event;1387if (b.is_valid()) {1388const real_t zoom_factor = 1.08 * b->get_factor();1389switch (b->get_button_index()) {1390case MouseButton::WHEEL_UP: {1391if (!camera_freelook) {1392_cursor_scale_distance(1.0 / zoom_factor);1393} else {1394_scale_freelook_speed(zoom_factor);1395}13961397return true;1398} break;1399case MouseButton::WHEEL_DOWN: {1400if (!camera_freelook) {1401_cursor_scale_distance(zoom_factor);1402} else {1403_scale_freelook_speed(1.0 / zoom_factor);1404}14051406return true;1407} break;1408case MouseButton::RIGHT: {1409_set_camera_freelook_enabled(b->is_pressed());1410return true;1411} break;1412default: {1413}1414}1415}14161417Ref<InputEventMouseMotion> m = p_event;1418if (m.is_valid()) {1419if (camera_freelook) {1420_cursor_look(m);1421} else if (m->get_button_mask().has_flag(MouseButtonMask::MIDDLE)) {1422if (m->is_shift_pressed()) {1423_cursor_pan(m);1424} else {1425_cursor_orbit(m);1426}1427}14281429return true;1430}14311432Ref<InputEventKey> k = p_event;1433if (k.is_valid()) {1434if (k->get_physical_keycode() == Key::ESCAPE) {1435_set_camera_freelook_enabled(false);1436return true;1437} else if (k->is_ctrl_pressed()) {1438switch (k->get_physical_keycode()) {1439case Key::EQUAL: {1440ERR_FAIL_COND_V(!SceneTree::get_singleton()->get_root()->is_camera_3d_override_enabled(), false);1441cursor.fov_scale = CLAMP(cursor.fov_scale - 0.05, CAMERA_MIN_FOV_SCALE, CAMERA_MAX_FOV_SCALE);1442SceneTree::get_singleton()->get_root()->get_override_camera_3d()->set_perspective(camera_fov * cursor.fov_scale, camera_znear, camera_zfar);14431444return true;1445} break;1446case Key::MINUS: {1447ERR_FAIL_COND_V(!SceneTree::get_singleton()->get_root()->is_camera_3d_override_enabled(), false);1448cursor.fov_scale = CLAMP(cursor.fov_scale + 0.05, CAMERA_MIN_FOV_SCALE, CAMERA_MAX_FOV_SCALE);1449SceneTree::get_singleton()->get_root()->get_override_camera_3d()->set_perspective(camera_fov * cursor.fov_scale, camera_znear, camera_zfar);14501451return true;1452} break;1453case Key::KEY_0: {1454ERR_FAIL_COND_V(!SceneTree::get_singleton()->get_root()->is_camera_3d_override_enabled(), false);1455cursor.fov_scale = 1;1456SceneTree::get_singleton()->get_root()->get_override_camera_3d()->set_perspective(camera_fov, camera_znear, camera_zfar);14571458return true;1459} break;1460default: {1461}1462}1463}1464}14651466// TODO: Handle magnify and pan input gestures.14671468return false;1469}14701471void RuntimeNodeSelect::_set_camera_freelook_enabled(bool p_enabled) {1472camera_freelook = p_enabled;14731474if (p_enabled) {1475// Make sure eye_pos is synced, because freelook referential is eye pos rather than orbit pos1476Vector3 forward = _get_cursor_transform().basis.xform(Vector3(0, 0, -1));1477cursor.eye_pos = cursor.pos - cursor.distance * forward;14781479previous_mouse_position = SceneTree::get_singleton()->get_root()->get_mouse_position();14801481// Hide mouse like in an FPS (warping doesn't work).1482Input::get_singleton()->set_mouse_mode_override(Input::MouseMode::MOUSE_MODE_CAPTURED);14831484} else {1485// Restore mouse.1486Input::get_singleton()->set_mouse_mode_override(Input::MouseMode::MOUSE_MODE_VISIBLE);14871488// Restore the previous mouse position when leaving freelook mode.1489// This is done because leaving `Input.MOUSE_MODE_CAPTURED` will center the cursor1490// due to OS limitations.1491Input::get_singleton()->warp_mouse(previous_mouse_position);1492}1493}14941495void RuntimeNodeSelect::_cursor_scale_distance(real_t p_scale) {1496ERR_FAIL_COND(!SceneTree::get_singleton()->get_root()->is_camera_3d_override_enabled());1497real_t min_distance = MAX(camera_znear * 4, VIEW_3D_MIN_ZOOM);1498real_t max_distance = MIN(camera_zfar / 4, VIEW_3D_MAX_ZOOM);1499cursor.distance = CLAMP(cursor.distance * p_scale, min_distance, max_distance);15001501SceneTree::get_singleton()->get_root()->get_override_camera_3d()->set_transform(_get_cursor_transform());1502}15031504void RuntimeNodeSelect::_scale_freelook_speed(real_t p_scale) {1505real_t min_speed = MAX(camera_znear * 4, VIEW_3D_MIN_ZOOM);1506real_t max_speed = MIN(camera_zfar / 4, VIEW_3D_MAX_ZOOM);1507if (unlikely(min_speed > max_speed)) {1508freelook_base_speed = (min_speed + max_speed) / 2;1509} else {1510freelook_base_speed = CLAMP(freelook_base_speed * p_scale, min_speed, max_speed);1511}1512}15131514void RuntimeNodeSelect::_cursor_look(Ref<InputEventWithModifiers> p_event) {1515Window *root = SceneTree::get_singleton()->get_root();1516ERR_FAIL_COND(!root->is_camera_3d_override_enabled());15171518const Vector2 relative = _get_warped_mouse_motion(p_event, Rect2(Vector2(), root->get_size()));1519const Transform3D prev_camera_transform = _get_cursor_transform();15201521if (invert_y_axis) {1522cursor.x_rot -= relative.y * freelook_sensitivity;1523} else {1524cursor.x_rot += relative.y * freelook_sensitivity;1525}1526// Clamp the Y rotation to roughly -90..90 degrees so the user can't look upside-down and end up disoriented.1527cursor.x_rot = CLAMP(cursor.x_rot, -1.57, 1.57);15281529cursor.y_rot += relative.x * freelook_sensitivity;15301531// Look is like the opposite of Orbit: the focus point rotates around the camera.1532Transform3D camera_transform = _get_cursor_transform();1533Vector3 pos = camera_transform.xform(Vector3(0, 0, 0));1534Vector3 prev_pos = prev_camera_transform.xform(Vector3(0, 0, 0));1535Vector3 diff = prev_pos - pos;1536cursor.pos += diff;15371538root->get_override_camera_3d()->set_transform(_get_cursor_transform());1539}15401541void RuntimeNodeSelect::_cursor_pan(Ref<InputEventWithModifiers> p_event) {1542Window *root = SceneTree::get_singleton()->get_root();1543ERR_FAIL_COND(!root->is_camera_3d_override_enabled());15441545// Reduce all sides of the area by 1, so warping works when windows are maximized/fullscreen.1546const Vector2 relative = _get_warped_mouse_motion(p_event, Rect2(Vector2(1, 1), root->get_size() - Vector2(2, 2)));1547const real_t pan_speed = translation_sensitivity / 150.0;15481549Transform3D camera_transform;1550camera_transform.translate_local(cursor.pos);1551camera_transform.basis.rotate(Vector3(1, 0, 0), -cursor.x_rot);1552camera_transform.basis.rotate(Vector3(0, 1, 0), -cursor.y_rot);15531554Vector3 translation(1 * -relative.x * pan_speed, relative.y * pan_speed, 0);1555translation *= cursor.distance / 4;1556camera_transform.translate_local(translation);1557cursor.pos = camera_transform.origin;15581559root->get_override_camera_3d()->set_transform(_get_cursor_transform());1560}15611562void RuntimeNodeSelect::_cursor_orbit(Ref<InputEventWithModifiers> p_event) {1563Window *root = SceneTree::get_singleton()->get_root();1564ERR_FAIL_COND(!root->is_camera_3d_override_enabled());15651566// Reduce all sides of the area by 1, so warping works when windows are maximized/fullscreen.1567const Vector2 relative = _get_warped_mouse_motion(p_event, Rect2(Vector2(1, 1), root->get_size() - Vector2(2, 2)));15681569if (invert_y_axis) {1570cursor.x_rot -= relative.y * orbit_sensitivity;1571} else {1572cursor.x_rot += relative.y * orbit_sensitivity;1573}1574// Clamp the Y rotation to roughly -90..90 degrees so the user can't look upside-down and end up disoriented.1575cursor.x_rot = CLAMP(cursor.x_rot, -1.57, 1.57);15761577if (invert_x_axis) {1578cursor.y_rot -= relative.x * orbit_sensitivity;1579} else {1580cursor.y_rot += relative.x * orbit_sensitivity;1581}15821583root->get_override_camera_3d()->set_transform(_get_cursor_transform());1584}15851586Point2 RuntimeNodeSelect::_get_warped_mouse_motion(const Ref<InputEventMouseMotion> &p_event, Rect2 p_area) const {1587ERR_FAIL_COND_V(p_event.is_null(), Point2());15881589if (warped_mouse_panning_3d) {1590return Input::get_singleton()->warp_mouse_motion(p_event, p_area);1591}15921593return p_event->get_relative();1594}15951596Transform3D RuntimeNodeSelect::_get_cursor_transform() {1597Transform3D camera_transform;1598camera_transform.translate_local(cursor.pos);1599camera_transform.basis.rotate(Vector3(1, 0, 0), -cursor.x_rot);1600camera_transform.basis.rotate(Vector3(0, 1, 0), -cursor.y_rot);1601camera_transform.translate_local(0, 0, cursor.distance);16021603return camera_transform;1604}16051606void RuntimeNodeSelect::_reset_camera_3d() {1607camera_first_override = true;16081609cursor = Cursor();1610Window *root = SceneTree::get_singleton()->get_root();1611Camera3D *game_camera = root->is_camera_3d_override_enabled() ? root->get_overridden_camera_3d() : root->get_camera_3d();1612if (game_camera) {1613Transform3D transform = game_camera->get_camera_transform();1614transform.translate_local(0, 0, -cursor.distance);1615cursor.pos = transform.origin;16161617cursor.x_rot = -game_camera->get_global_rotation().x;1618cursor.y_rot = -game_camera->get_global_rotation().y;16191620cursor.fov_scale = CLAMP(game_camera->get_fov() / camera_fov, CAMERA_MIN_FOV_SCALE, CAMERA_MAX_FOV_SCALE);1621} else {1622cursor.fov_scale = 1.0;1623}16241625if (root->is_camera_3d_override_enabled()) {1626Camera3D *override_camera = root->get_override_camera_3d();1627override_camera->set_transform(_get_cursor_transform());1628override_camera->set_perspective(camera_fov * cursor.fov_scale, camera_znear, camera_zfar);1629}1630}1631#endif // _3D_DISABLED16321633#endif // DEBUG_ENABLED163416351636