Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/scene/debugger/runtime_node_select.cpp
20934 views
1
/**************************************************************************/
2
/* runtime_node_select.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
#ifdef DEBUG_ENABLED
32
33
#include "runtime_node_select.h"
34
35
#include "core/config/project_settings.h"
36
#include "core/debugger/debugger_marshalls.h"
37
#include "core/debugger/engine_debugger.h"
38
#include "core/input/input.h"
39
#include "core/math/geometry_3d.h"
40
#include "scene/2d/camera_2d.h"
41
#include "scene/debugger/scene_debugger_object.h"
42
#include "scene/gui/popup_menu.h"
43
#include "scene/main/canvas_layer.h"
44
#include "scene/theme/theme_db.h"
45
46
#ifndef PHYSICS_2D_DISABLED
47
#include "scene/2d/physics/collision_object_2d.h"
48
#include "scene/2d/physics/collision_polygon_2d.h"
49
#include "scene/2d/physics/collision_shape_2d.h"
50
#endif // PHYSICS_2D_DISABLED
51
52
#ifndef _3D_DISABLED
53
#include "scene/3d/camera_3d.h"
54
#ifndef PHYSICS_3D_DISABLED
55
#include "scene/3d/physics/collision_object_3d.h"
56
#include "scene/3d/physics/collision_shape_3d.h"
57
#endif // PHYSICS_3D_DISABLED
58
#include "scene/3d/visual_instance_3d.h"
59
#include "scene/resources/3d/convex_polygon_shape_3d.h"
60
#include "scene/resources/surface_tool.h"
61
#endif // _3D_DISABLED
62
63
RuntimeNodeSelect *RuntimeNodeSelect::get_singleton() {
64
return singleton;
65
}
66
67
RuntimeNodeSelect::~RuntimeNodeSelect() {
68
if (selection_list && !selection_list->is_visible()) {
69
memdelete(selection_list);
70
}
71
72
if (draw_canvas.is_valid()) {
73
RS::get_singleton()->free_rid(sel_drag_ci);
74
RS::get_singleton()->free_rid(sbox_2d_ci);
75
RS::get_singleton()->free_rid(draw_canvas);
76
}
77
}
78
79
void RuntimeNodeSelect::_setup(const Dictionary &p_settings) {
80
Window *root = SceneTree::get_singleton()->get_root();
81
ERR_FAIL_COND(root->is_connected(SceneStringName(window_input), callable_mp(this, &RuntimeNodeSelect::_root_window_input)));
82
83
root->connect(SceneStringName(window_input), callable_mp(this, &RuntimeNodeSelect::_root_window_input));
84
root->connect("size_changed", callable_mp(this, &RuntimeNodeSelect::_queue_selection_update), CONNECT_DEFERRED);
85
86
max_selection = p_settings.get("debugger/max_node_selection", 1);
87
88
panner.instantiate();
89
panner->set_callbacks(callable_mp(this, &RuntimeNodeSelect::_pan_callback), callable_mp(this, &RuntimeNodeSelect::_zoom_callback));
90
91
ViewPanner::ControlScheme panning_scheme = (ViewPanner::ControlScheme)p_settings.get("editors/panning/2d_editor_panning_scheme", 0).operator int();
92
bool simple_panning = p_settings.get("editors/panning/simple_panning", false);
93
int pan_speed = p_settings.get("editors/panning/2d_editor_pan_speed", 20);
94
Array keys = p_settings.get("canvas_item_editor/pan_view", Array()).operator Array();
95
panner->setup(panning_scheme, DebuggerMarshalls::deserialize_key_shortcut(keys), simple_panning);
96
panner->setup_warped_panning(root, p_settings.get("editors/panning/warped_mouse_panning", true));
97
panner->set_scroll_speed(pan_speed);
98
99
sel_2d_grab_dist = p_settings.get("editors/polygon_editor/point_grab_radius", 0);
100
sel_2d_scale = MAX(1, Math::ceil(2.0 / (float)GLOBAL_GET("display/window/stretch/scale")));
101
102
selection_area_fill = p_settings.get("box_selection_fill_color", Color());
103
selection_area_outline = p_settings.get("box_selection_stroke_color", Color());
104
105
draw_canvas = RS::get_singleton()->canvas_create();
106
sel_drag_ci = RS::get_singleton()->canvas_item_create();
107
108
/// 2D Selection Box Generation
109
110
sbox_2d_ci = RS::get_singleton()->canvas_item_create();
111
RS::get_singleton()->viewport_attach_canvas(root->get_viewport_rid(), draw_canvas);
112
RS::get_singleton()->canvas_item_set_parent(sel_drag_ci, draw_canvas);
113
RS::get_singleton()->canvas_item_set_parent(sbox_2d_ci, draw_canvas);
114
115
#ifndef _3D_DISABLED
116
cursor = Cursor();
117
118
camera_fov = p_settings.get("editors/3d/default_fov", 70);
119
camera_znear = p_settings.get("editors/3d/default_z_near", 0.05);
120
camera_zfar = p_settings.get("editors/3d/default_z_far", 4'000);
121
122
invert_x_axis = p_settings.get("editors/3d/navigation/invert_x_axis", false);
123
invert_y_axis = p_settings.get("editors/3d/navigation/invert_y_axis", false);
124
warped_mouse_panning_3d = p_settings.get("editors/3d/navigation/warped_mouse_panning", true);
125
126
freelook_base_speed = p_settings.get("editors/3d/freelook/freelook_base_speed", 5);
127
freelook_sensitivity = Math::deg_to_rad((real_t)p_settings.get("editors/3d/freelook/freelook_sensitivity", 0.25));
128
orbit_sensitivity = Math::deg_to_rad((real_t)p_settings.get("editors/3d/navigation_feel/orbit_sensitivity", 0.004));
129
translation_sensitivity = p_settings.get("editors/3d/navigation_feel/translation_sensitivity", 1);
130
131
/// 3D Selection Box Generation
132
// Copied from the Node3DEditor implementation.
133
134
sbox_3d_color = p_settings.get("editors/3d/selection_box_color", Color());
135
136
// Use two AABBs to create the illusion of a slightly thicker line.
137
AABB aabb(Vector3(), Vector3(1, 1, 1));
138
139
// Create a x-ray (visible through solid surfaces) and standard version of the selection box.
140
// Both will be drawn at the same position, but with different opacity.
141
// This lets the user see where the selection is while still having a sense of depth.
142
Ref<SurfaceTool> st = memnew(SurfaceTool);
143
Ref<SurfaceTool> st_xray = memnew(SurfaceTool);
144
145
st->begin(Mesh::PRIMITIVE_LINES);
146
st_xray->begin(Mesh::PRIMITIVE_LINES);
147
for (int i = 0; i < 12; i++) {
148
Vector3 a, b;
149
aabb.get_edge(i, a, b);
150
151
st->add_vertex(a);
152
st->add_vertex(b);
153
st_xray->add_vertex(a);
154
st_xray->add_vertex(b);
155
}
156
157
Ref<StandardMaterial3D> mat = memnew(StandardMaterial3D);
158
mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
159
mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
160
mat->set_albedo(sbox_3d_color);
161
mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
162
st->set_material(mat);
163
sbox_3d_mesh = st->commit();
164
165
Ref<StandardMaterial3D> mat_xray = memnew(StandardMaterial3D);
166
mat_xray->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
167
mat_xray->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
168
mat_xray->set_flag(StandardMaterial3D::FLAG_DISABLE_DEPTH_TEST, true);
169
mat_xray->set_albedo(sbox_3d_color * Color(1, 1, 1, 0.15));
170
mat_xray->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
171
st_xray->set_material(mat_xray);
172
sbox_3d_mesh_xray = st_xray->commit();
173
#endif // _3D_DISABLED
174
175
SceneTree::get_singleton()->connect("process_frame", callable_mp(this, &RuntimeNodeSelect::_process_frame));
176
SceneTree::get_singleton()->connect("physics_frame", callable_mp(this, &RuntimeNodeSelect::_physics_frame));
177
178
// This function will be called before the root enters the tree at first when the Game view is passing its settings to
179
// the debugger, so queue the update for after it enters.
180
root->connect(SceneStringName(tree_entered), callable_mp(this, &RuntimeNodeSelect::_update_input_state), Object::CONNECT_ONE_SHOT);
181
}
182
183
void RuntimeNodeSelect::_node_set_type(NodeType p_type) {
184
node_select_type = p_type;
185
_update_input_state();
186
}
187
188
void RuntimeNodeSelect::_select_set_mode(SelectMode p_mode) {
189
node_select_mode = p_mode;
190
}
191
192
void RuntimeNodeSelect::_set_camera_override_enabled(bool p_enabled) {
193
camera_override = p_enabled;
194
195
if (camera_first_override) {
196
_reset_camera_2d();
197
#ifndef _3D_DISABLED
198
_reset_camera_3d();
199
#endif // _3D_DISABLED
200
201
camera_first_override = false;
202
} else if (p_enabled) {
203
_update_view_2d();
204
205
#ifndef _3D_DISABLED
206
Window *root = SceneTree::get_singleton()->get_root();
207
ERR_FAIL_COND(!root->is_camera_3d_override_enabled());
208
Camera3D *override_camera = root->get_override_camera_3d();
209
override_camera->set_transform(_get_cursor_transform());
210
override_camera->set_perspective(camera_fov * cursor.fov_scale, camera_znear, camera_zfar);
211
#endif // _3D_DISABLED
212
}
213
}
214
215
void RuntimeNodeSelect::_root_window_input(const Ref<InputEvent> &p_event) {
216
Window *root = SceneTree::get_singleton()->get_root();
217
if (node_select_type == NODE_TYPE_NONE || (selection_list && selection_list->is_visible())) {
218
// Workaround for platforms that don't allow subwindows.
219
if (selection_list && selection_list->is_visible() && selection_list->is_embedded()) {
220
root->set_disable_input_override(false);
221
selection_list->push_input(p_event);
222
callable_mp(root->get_viewport(), &Viewport::set_disable_input_override).call_deferred(true);
223
}
224
225
return;
226
}
227
228
bool is_dragging_camera = false;
229
if (camera_override) {
230
if (node_select_type == NODE_TYPE_2D) {
231
is_dragging_camera = panner->gui_input(p_event, Rect2(Vector2(), root->get_visible_rect().get_size()));
232
#ifndef _3D_DISABLED
233
} else if (node_select_type == NODE_TYPE_3D && selection_drag_state == SELECTION_DRAG_NONE) {
234
if (_handle_3d_input(p_event)) {
235
return;
236
}
237
#endif // _3D_DISABLED
238
}
239
}
240
241
Ref<InputEventMouseButton> b = p_event;
242
243
if (selection_drag_state == SELECTION_DRAG_MOVE) {
244
Ref<InputEventMouseMotion> m = p_event;
245
if (m.is_valid()) {
246
_update_selection_drag(root->get_screen_transform().affine_inverse().xform(m->get_position()));
247
return;
248
} else if (b.is_valid()) {
249
// Account for actions like zooming.
250
_update_selection_drag(root->get_screen_transform().affine_inverse().xform(b->get_position()));
251
}
252
}
253
254
if (b.is_null()) {
255
return;
256
}
257
258
// Ignore mouse wheel inputs.
259
if (b->get_button_index() != MouseButton::LEFT && b->get_button_index() != MouseButton::RIGHT) {
260
return;
261
}
262
263
if (selection_drag_state == SELECTION_DRAG_MOVE && !b->is_pressed() && b->get_button_index() == MouseButton::LEFT) {
264
selection_drag_state = SELECTION_DRAG_END;
265
selection_drag_area = selection_drag_area.abs();
266
_update_selection_drag();
267
268
// Trigger a selection in the position on release.
269
if (multi_shortcut_pressed) {
270
selection_position = root->get_screen_transform().affine_inverse().xform(b->get_position());
271
}
272
}
273
274
if (!is_dragging_camera && b->is_pressed()) {
275
multi_shortcut_pressed = b->is_shift_pressed();
276
list_shortcut_pressed = node_select_mode == SELECT_MODE_SINGLE && b->get_button_index() == MouseButton::RIGHT && b->is_alt_pressed();
277
if (list_shortcut_pressed || b->get_button_index() == MouseButton::LEFT) {
278
selection_position = root->get_screen_transform().affine_inverse().xform(b->get_position());
279
}
280
}
281
}
282
283
void RuntimeNodeSelect::_items_popup_index_pressed(int p_index, PopupMenu *p_popup) {
284
Object *obj = p_popup->get_item_metadata(p_index).get_validated_object();
285
if (obj) {
286
Vector<Node *> node;
287
node.append(Object::cast_to<Node>(obj));
288
_send_ids(node);
289
}
290
}
291
292
void RuntimeNodeSelect::_update_input_state() {
293
SceneTree *scene_tree = SceneTree::get_singleton();
294
// This function can be called at the very beginning, when the root hasn't entered the tree yet.
295
// So check first to avoid a crash.
296
if (!scene_tree->get_root()->is_inside_tree()) {
297
return;
298
}
299
300
bool disable_input = scene_tree->is_suspended() || node_select_type != RuntimeNodeSelect::NODE_TYPE_NONE;
301
Input::get_singleton()->set_disable_input(disable_input);
302
Input::get_singleton()->set_mouse_mode_override_enabled(disable_input);
303
scene_tree->get_root()->set_disable_input_override(disable_input);
304
}
305
306
void RuntimeNodeSelect::_process_frame() {
307
#ifndef _3D_DISABLED
308
if (camera_freelook) {
309
Transform3D transform = _get_cursor_transform();
310
Vector3 forward = transform.basis.xform(Vector3(0, 0, -1));
311
const Vector3 right = transform.basis.xform(Vector3(1, 0, 0));
312
Vector3 up = transform.basis.xform(Vector3(0, 1, 0));
313
314
Vector3 direction;
315
316
Input *input = Input::get_singleton();
317
bool was_input_disabled = input->is_input_disabled();
318
if (was_input_disabled) {
319
input->set_disable_input(false);
320
}
321
322
if (input->is_physical_key_pressed(Key::A)) {
323
direction -= right;
324
}
325
if (input->is_physical_key_pressed(Key::D)) {
326
direction += right;
327
}
328
if (input->is_physical_key_pressed(Key::W)) {
329
direction += forward;
330
}
331
if (input->is_physical_key_pressed(Key::S)) {
332
direction -= forward;
333
}
334
if (input->is_physical_key_pressed(Key::E)) {
335
direction += up;
336
}
337
if (input->is_physical_key_pressed(Key::Q)) {
338
direction -= up;
339
}
340
341
real_t speed = freelook_base_speed;
342
if (input->is_physical_key_pressed(Key::SHIFT)) {
343
speed *= 3.0;
344
}
345
if (input->is_physical_key_pressed(Key::ALT)) {
346
speed *= 0.333333;
347
}
348
349
if (was_input_disabled) {
350
input->set_disable_input(true);
351
}
352
353
if (direction != Vector3()) {
354
Window *root = SceneTree::get_singleton()->get_root();
355
ERR_FAIL_COND(!root->is_camera_3d_override_enabled());
356
357
// Calculate the process time manually, as the time scale is frozen.
358
const double process_time = (1.0 / Engine::get_singleton()->get_frames_per_second()) * Engine::get_singleton()->get_unfrozen_time_scale();
359
const Vector3 motion = direction * speed * process_time;
360
cursor.pos += motion;
361
cursor.eye_pos += motion;
362
363
root->get_override_camera_3d()->set_transform(_get_cursor_transform());
364
}
365
}
366
#endif // _3D_DISABLED
367
368
if (selection_update_queued || !SceneTree::get_singleton()->is_suspended()) {
369
selection_update_queued = false;
370
if (has_selection) {
371
_update_selection();
372
}
373
}
374
}
375
376
void RuntimeNodeSelect::_physics_frame() {
377
if (selection_drag_state != SELECTION_DRAG_END && (selection_drag_state == SELECTION_DRAG_MOVE || Math::is_inf(selection_position.x))) {
378
return;
379
}
380
381
Window *root = SceneTree::get_singleton()->get_root();
382
bool selection_drag_valid = selection_drag_state == SELECTION_DRAG_END && selection_drag_area.get_area() > SELECTION_MIN_AREA;
383
Vector<SelectResult> items;
384
385
if (node_select_type == NODE_TYPE_2D) {
386
if (selection_drag_valid) {
387
for (int i = 0; i < root->get_child_count(); i++) {
388
_find_canvas_items_at_rect(selection_drag_area, root->get_child(i), items);
389
}
390
} else if (!Math::is_inf(selection_position.x)) {
391
for (int i = 0; i < root->get_child_count(); i++) {
392
_find_canvas_items_at_pos(selection_position, root->get_child(i), items);
393
}
394
}
395
396
#ifndef _3D_DISABLED
397
} else if (node_select_type == NODE_TYPE_3D) {
398
if (selection_drag_valid) {
399
_find_3d_items_at_rect(selection_drag_area, items);
400
} else {
401
_find_3d_items_at_pos(selection_position, items);
402
}
403
#endif // _3D_DISABLED
404
}
405
406
if ((prefer_group_selection || avoid_locked_nodes) && !list_shortcut_pressed && node_select_mode == SELECT_MODE_SINGLE) {
407
for (int i = 0; i < items.size(); i++) {
408
Node *node = items[i].item;
409
Node *final_node = node;
410
real_t order = items[i].order;
411
412
// Replace the node by the group if grouped.
413
if (prefer_group_selection) {
414
while (node && node != root) {
415
if (node->has_meta("_edit_group_")) {
416
final_node = node;
417
418
if (Object::cast_to<CanvasItem>(final_node)) {
419
CanvasItem *ci_tmp = Object::cast_to<CanvasItem>(final_node);
420
order = ci_tmp->get_effective_z_index() + ci_tmp->get_canvas_layer();
421
#ifndef _3D_DISABLED
422
} else if (Object::cast_to<Node3D>(final_node)) {
423
Node3D *node3d_tmp = Object::cast_to<Node3D>(final_node);
424
Camera3D *camera = root->get_camera_3d();
425
Vector3 pos = camera->project_ray_origin(selection_position);
426
order = -pos.distance_to(node3d_tmp->get_global_transform().origin);
427
#endif // _3D_DISABLED
428
}
429
}
430
node = node->get_parent();
431
}
432
}
433
434
// Filter out locked nodes.
435
if (avoid_locked_nodes && final_node->get_meta("_edit_lock_", false)) {
436
items.remove_at(i);
437
i--;
438
continue;
439
}
440
441
items.write[i].item = final_node;
442
items.write[i].order = order;
443
}
444
}
445
446
// Remove possible duplicates.
447
for (int i = 0; i < items.size(); i++) {
448
Node *item = items[i].item;
449
for (int j = 0; j < i; j++) {
450
if (items[j].item == item) {
451
items.remove_at(i);
452
i--;
453
454
break;
455
}
456
}
457
}
458
459
items.sort();
460
461
switch (selection_drag_state) {
462
case SELECTION_DRAG_END: {
463
selection_position = Point2(Math::INF, Math::INF);
464
selection_drag_state = SELECTION_DRAG_NONE;
465
466
if (selection_drag_area.get_area() > SELECTION_MIN_AREA) {
467
if (!items.is_empty()) {
468
Vector<Node *> nodes;
469
for (const SelectResult item : items) {
470
nodes.append(item.item);
471
}
472
_send_ids(nodes, false);
473
}
474
475
_update_selection_drag();
476
return;
477
}
478
479
_update_selection_drag();
480
} break;
481
482
case SELECTION_DRAG_NONE: {
483
if (node_select_mode == SELECT_MODE_LIST) {
484
break;
485
}
486
487
if (multi_shortcut_pressed) {
488
// Allow forcing box selection when an item was clicked.
489
selection_drag_state = SELECTION_DRAG_MOVE;
490
} else if (items.is_empty()) {
491
#ifdef _3D_DISABLED
492
if (!selected_ci_nodes.is_empty()) {
493
#else
494
if (!selected_ci_nodes.is_empty() || !selected_3d_nodes.is_empty()) {
495
#endif // _3D_DISABLED
496
EngineDebugger::get_singleton()->send_message("remote_nothing_selected", Array());
497
_clear_selection();
498
}
499
500
selection_drag_state = SELECTION_DRAG_MOVE;
501
} else {
502
break;
503
}
504
505
[[fallthrough]];
506
}
507
508
case SELECTION_DRAG_MOVE: {
509
selection_drag_area.position = selection_position;
510
511
// Stop selection on click, so it can happen on release if the selection area doesn't pass the threshold.
512
if (multi_shortcut_pressed) {
513
return;
514
}
515
}
516
}
517
518
if (items.is_empty()) {
519
selection_position = Point2(Math::INF, Math::INF);
520
return;
521
}
522
if ((!list_shortcut_pressed && node_select_mode == SELECT_MODE_SINGLE) || items.size() == 1) {
523
selection_position = Point2(Math::INF, Math::INF);
524
525
Vector<Node *> node;
526
node.append(items[0].item);
527
_send_ids(node);
528
529
return;
530
}
531
532
if (!selection_list && (list_shortcut_pressed || node_select_mode == SELECT_MODE_LIST)) {
533
_open_selection_list(items, selection_position);
534
}
535
536
selection_position = Point2(Math::INF, Math::INF);
537
}
538
539
void RuntimeNodeSelect::_send_ids(const Vector<Node *> &p_picked_nodes, bool p_invert_new_selections) {
540
ERR_FAIL_COND(p_picked_nodes.is_empty());
541
542
Vector<Node *> picked_nodes = p_picked_nodes;
543
Array message;
544
545
if (!multi_shortcut_pressed) {
546
if (picked_nodes.size() > max_selection) {
547
picked_nodes.resize(max_selection);
548
EngineDebugger::get_singleton()->send_message("show_selection_limit_warning", Array());
549
}
550
551
for (const Node *node : picked_nodes) {
552
SceneDebuggerObject obj(node->get_instance_id());
553
Array arr;
554
obj.serialize(arr);
555
message.append(arr);
556
}
557
558
EngineDebugger::get_singleton()->send_message("remote_objects_selected", message);
559
_set_selected_nodes(picked_nodes);
560
561
return;
562
}
563
564
LocalVector<Node *> nodes;
565
LocalVector<ObjectID> ids;
566
for (Node *node : picked_nodes) {
567
ObjectID id = node->get_instance_id();
568
if (CanvasItem *ci = Object::cast_to<CanvasItem>(node)) {
569
if (selected_ci_nodes.has(id)) {
570
if (p_invert_new_selections) {
571
selected_ci_nodes.erase(id);
572
}
573
} else {
574
ids.push_back(id);
575
nodes.push_back(ci);
576
}
577
} else {
578
#ifndef _3D_DISABLED
579
if (Node3D *node3d = Object::cast_to<Node3D>(node)) {
580
if (selected_3d_nodes.has(id)) {
581
if (p_invert_new_selections) {
582
selected_3d_nodes.erase(id);
583
}
584
} else {
585
ids.push_back(id);
586
nodes.push_back(node3d);
587
}
588
}
589
#endif // _3D_DISABLED
590
}
591
}
592
593
uint32_t limit = max_selection - selected_ci_nodes.size();
594
#ifndef _3D_DISABLED
595
limit -= selected_3d_nodes.size();
596
#endif // _3D_DISABLED
597
if (ids.size() > limit) {
598
ids.resize(limit);
599
nodes.resize(limit);
600
EngineDebugger::get_singleton()->send_message("show_selection_limit_warning", Array());
601
}
602
603
for (ObjectID id : selected_ci_nodes) {
604
ids.push_back(id);
605
nodes.push_back(ObjectDB::get_instance<Node>(id));
606
}
607
#ifndef _3D_DISABLED
608
for (const KeyValue<ObjectID, Ref<SelectionBox3D>> &KV : selected_3d_nodes) {
609
ids.push_back(KV.key);
610
nodes.push_back(ObjectDB::get_instance<Node>(KV.key));
611
}
612
#endif // _3D_DISABLED
613
614
if (ids.is_empty()) {
615
EngineDebugger::get_singleton()->send_message("remote_nothing_selected", message);
616
} else {
617
for (const ObjectID &id : ids) {
618
SceneDebuggerObject obj(id);
619
Array arr;
620
obj.serialize(arr);
621
message.append(arr);
622
}
623
624
EngineDebugger::get_singleton()->send_message("remote_objects_selected", message);
625
}
626
627
_set_selected_nodes(Vector<Node *>(nodes));
628
}
629
630
void RuntimeNodeSelect::_set_selected_nodes(const Vector<Node *> &p_nodes) {
631
if (p_nodes.is_empty()) {
632
_clear_selection();
633
return;
634
}
635
636
bool changed = false;
637
LocalVector<ObjectID> nodes_ci;
638
#ifndef _3D_DISABLED
639
HashMap<ObjectID, Ref<SelectionBox3D>> nodes_3d;
640
#endif // _3D_DISABLED
641
642
for (Node *node : p_nodes) {
643
ObjectID id = node->get_instance_id();
644
if (Object::cast_to<CanvasItem>(node)) {
645
if (!changed || !selected_ci_nodes.has(id)) {
646
changed = true;
647
}
648
649
nodes_ci.push_back(id);
650
} else {
651
#ifndef _3D_DISABLED
652
Node3D *node_3d = Object::cast_to<Node3D>(node);
653
if (!node_3d || !node_3d->is_inside_world()) {
654
continue;
655
}
656
657
if (!changed || !selected_3d_nodes.has(id)) {
658
changed = true;
659
}
660
661
if (selected_3d_nodes.has(id)) {
662
// Assign an already available visual instance.
663
nodes_3d[id] = selected_3d_nodes.get(id);
664
continue;
665
}
666
667
if (sbox_3d_mesh.is_null() || sbox_3d_mesh_xray.is_null()) {
668
continue;
669
}
670
671
Ref<SelectionBox3D> sb;
672
sb.instantiate();
673
nodes_3d[id] = sb;
674
675
RID scenario = node_3d->get_world_3d()->get_scenario();
676
677
sb->instance = RS::get_singleton()->instance_create2(sbox_3d_mesh->get_rid(), scenario);
678
sb->instance_ofs = RS::get_singleton()->instance_create2(sbox_3d_mesh->get_rid(), scenario);
679
RS::get_singleton()->instance_geometry_set_cast_shadows_setting(sb->instance, RS::SHADOW_CASTING_SETTING_OFF);
680
RS::get_singleton()->instance_geometry_set_cast_shadows_setting(sb->instance_ofs, RS::SHADOW_CASTING_SETTING_OFF);
681
RS::get_singleton()->instance_geometry_set_flag(sb->instance, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);
682
RS::get_singleton()->instance_geometry_set_flag(sb->instance, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);
683
RS::get_singleton()->instance_geometry_set_flag(sb->instance_ofs, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);
684
RS::get_singleton()->instance_geometry_set_flag(sb->instance_ofs, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);
685
686
sb->instance_xray = RS::get_singleton()->instance_create2(sbox_3d_mesh_xray->get_rid(), scenario);
687
sb->instance_xray_ofs = RS::get_singleton()->instance_create2(sbox_3d_mesh_xray->get_rid(), scenario);
688
RS::get_singleton()->instance_geometry_set_cast_shadows_setting(sb->instance_xray, RS::SHADOW_CASTING_SETTING_OFF);
689
RS::get_singleton()->instance_geometry_set_cast_shadows_setting(sb->instance_xray_ofs, RS::SHADOW_CASTING_SETTING_OFF);
690
RS::get_singleton()->instance_geometry_set_flag(sb->instance_xray, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);
691
RS::get_singleton()->instance_geometry_set_flag(sb->instance_xray, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);
692
RS::get_singleton()->instance_geometry_set_flag(sb->instance_xray_ofs, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);
693
RS::get_singleton()->instance_geometry_set_flag(sb->instance_xray_ofs, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);
694
#endif // _3D_DISABLED
695
}
696
}
697
698
#ifdef _3D_DISABLED
699
if (!changed && nodes_ci.size() == selected_ci_nodes.size()) {
700
return;
701
}
702
#else
703
if (!changed && nodes_ci.size() == selected_ci_nodes.size() && nodes_3d.size() == selected_3d_nodes.size()) {
704
return;
705
}
706
#endif // _3D_DISABLED
707
708
_clear_selection();
709
selected_ci_nodes = nodes_ci;
710
has_selection = !nodes_ci.is_empty();
711
712
#ifndef _3D_DISABLED
713
if (!nodes_3d.is_empty()) {
714
selected_3d_nodes = nodes_3d;
715
has_selection = true;
716
}
717
#endif // _3D_DISABLED
718
719
_queue_selection_update();
720
}
721
722
void RuntimeNodeSelect::_queue_selection_update() {
723
if (has_selection && selection_visible) {
724
if (SceneTree::get_singleton()->is_suspended()) {
725
_update_selection();
726
} else {
727
selection_update_queued = true;
728
}
729
}
730
}
731
732
void RuntimeNodeSelect::_update_selection() {
733
RS::get_singleton()->canvas_item_clear(sbox_2d_ci);
734
RS::get_singleton()->canvas_item_set_visible(sbox_2d_ci, selection_visible);
735
736
for (LocalVector<ObjectID>::Iterator E = selected_ci_nodes.begin(); E != selected_ci_nodes.end(); ++E) {
737
ObjectID id = *E;
738
CanvasItem *ci = ObjectDB::get_instance<CanvasItem>(id);
739
if (!ci) {
740
selected_ci_nodes.erase(id);
741
--E;
742
continue;
743
}
744
745
if (!ci->is_inside_tree()) {
746
continue;
747
}
748
749
Transform2D xform = ci->get_global_transform_with_canvas();
750
751
// Fallback.
752
Rect2 rect = Rect2(Vector2(), Vector2(10, 10));
753
754
if (ci->_edit_use_rect()) {
755
rect = ci->_edit_get_rect();
756
} else {
757
#ifndef PHYSICS_2D_DISABLED
758
CollisionShape2D *collision_shape = Object::cast_to<CollisionShape2D>(ci);
759
if (collision_shape) {
760
Ref<Shape2D> shape = collision_shape->get_shape();
761
if (shape.is_valid()) {
762
rect = shape->get_rect();
763
}
764
}
765
#endif // PHYSICS_2D_DISABLED
766
}
767
768
const Vector2 endpoints[4] = {
769
xform.xform(rect.position),
770
xform.xform(rect.position + Point2(rect.size.x, 0)),
771
xform.xform(rect.position + rect.size),
772
xform.xform(rect.position + Point2(0, rect.size.y))
773
};
774
775
const Color selection_color_2d = Color(1, 0.6, 0.4, 0.7);
776
for (int i = 0; i < 4; i++) {
777
RS::get_singleton()->canvas_item_add_line(sbox_2d_ci, endpoints[i], endpoints[(i + 1) % 4], selection_color_2d, sel_2d_scale);
778
}
779
}
780
781
#ifndef _3D_DISABLED
782
for (HashMap<ObjectID, Ref<SelectionBox3D>>::ConstIterator KV = selected_3d_nodes.begin(); KV != selected_3d_nodes.end(); ++KV) {
783
ObjectID id = KV->key;
784
Node3D *node_3d = ObjectDB::get_instance<Node3D>(id);
785
if (!node_3d) {
786
selected_3d_nodes.erase(id);
787
--KV;
788
continue;
789
}
790
791
if (!node_3d->is_inside_tree()) {
792
continue;
793
}
794
795
// Fallback.
796
AABB bounds(Vector3(-0.5, -0.5, -0.5), Vector3(1, 1, 1));
797
798
VisualInstance3D *visual_instance = Object::cast_to<VisualInstance3D>(node_3d);
799
if (visual_instance) {
800
bounds = visual_instance->get_aabb();
801
} else {
802
#ifndef PHYSICS_3D_DISABLED
803
CollisionShape3D *collision_shape = Object::cast_to<CollisionShape3D>(node_3d);
804
if (collision_shape) {
805
Ref<Shape3D> shape = collision_shape->get_shape();
806
if (shape.is_valid()) {
807
bounds = shape->get_debug_mesh()->get_aabb();
808
}
809
}
810
#endif // PHYSICS_3D_DISABLED
811
}
812
813
Transform3D xform_to_top_level_parent_space = node_3d->get_global_transform().affine_inverse() * node_3d->get_global_transform();
814
bounds = xform_to_top_level_parent_space.xform(bounds);
815
Transform3D t = node_3d->get_global_transform();
816
817
Ref<SelectionBox3D> sb = KV->value;
818
if (t == sb->transform && bounds == sb->bounds) {
819
continue; // Nothing changed.
820
}
821
sb->transform = t;
822
sb->bounds = bounds;
823
824
Transform3D t_offset = t;
825
826
// Apply AABB scaling before item's global transform.
827
{
828
const Vector3 offset(0.005, 0.005, 0.005);
829
Basis aabb_s;
830
aabb_s.scale(bounds.size + offset);
831
t.translate_local(bounds.position - offset / 2);
832
t.basis = t.basis * aabb_s;
833
}
834
{
835
const Vector3 offset(0.01, 0.01, 0.01);
836
Basis aabb_s;
837
aabb_s.scale(bounds.size + offset);
838
t_offset.translate_local(bounds.position - offset / 2);
839
t_offset.basis = t_offset.basis * aabb_s;
840
}
841
842
RS::get_singleton()->instance_set_visible(sb->instance, selection_visible);
843
RS::get_singleton()->instance_set_visible(sb->instance_ofs, selection_visible);
844
RS::get_singleton()->instance_set_visible(sb->instance_xray, selection_visible);
845
RS::get_singleton()->instance_set_visible(sb->instance_xray_ofs, selection_visible);
846
847
RS::get_singleton()->instance_set_transform(sb->instance, t);
848
RS::get_singleton()->instance_set_transform(sb->instance_ofs, t_offset);
849
RS::get_singleton()->instance_set_transform(sb->instance_xray, t);
850
RS::get_singleton()->instance_set_transform(sb->instance_xray_ofs, t_offset);
851
}
852
#endif // _3D_DISABLED
853
}
854
855
void RuntimeNodeSelect::_clear_selection() {
856
selected_ci_nodes.clear();
857
if (draw_canvas.is_valid()) {
858
RS::get_singleton()->canvas_item_clear(sbox_2d_ci);
859
}
860
861
#ifndef _3D_DISABLED
862
selected_3d_nodes.clear();
863
#endif // _3D_DISABLED
864
865
has_selection = false;
866
}
867
868
void RuntimeNodeSelect::_update_selection_drag(const Point2 &p_end_pos) {
869
RS::get_singleton()->canvas_item_clear(sel_drag_ci);
870
871
if (selection_drag_state != SELECTION_DRAG_MOVE) {
872
return;
873
}
874
875
selection_drag_area.size = p_end_pos - selection_drag_area.position;
876
877
if (selection_drag_state == SELECTION_DRAG_END) {
878
return;
879
}
880
881
Rect2 selection_drawing = selection_drag_area.abs();
882
int thickness = 1;
883
884
const Vector2 endpoints[4] = {
885
selection_drawing.position,
886
selection_drawing.position + Point2(selection_drawing.size.x, 0),
887
selection_drawing.position + selection_drawing.size,
888
selection_drawing.position + Point2(0, selection_drawing.size.y)
889
};
890
891
// Draw fill.
892
RS::get_singleton()->canvas_item_add_rect(sel_drag_ci, selection_drawing, selection_area_fill);
893
// Draw outline.
894
for (int i = 0; i < 4; i++) {
895
RS::get_singleton()->canvas_item_add_line(sel_drag_ci, endpoints[i], endpoints[(i + 1) % 4], selection_area_outline, thickness);
896
}
897
}
898
899
void RuntimeNodeSelect::_open_selection_list(const Vector<SelectResult> &p_items, const Point2 &p_pos) {
900
Window *root = SceneTree::get_singleton()->get_root();
901
902
selection_list = memnew(PopupMenu);
903
selection_list->set_theme(ThemeDB::get_singleton()->get_default_theme());
904
selection_list->set_auto_translate_mode(Node::AUTO_TRANSLATE_MODE_DISABLED);
905
selection_list->set_force_native(true);
906
selection_list->connect("index_pressed", callable_mp(this, &RuntimeNodeSelect::_items_popup_index_pressed).bind(selection_list));
907
selection_list->connect("popup_hide", callable_mp(this, &RuntimeNodeSelect::_close_selection_list));
908
909
root->add_child(selection_list);
910
911
for (const SelectResult &I : p_items) {
912
int locked = 0;
913
if (I.item->get_meta("_edit_lock_", false)) {
914
locked = 1;
915
} else {
916
Node *scene = SceneTree::get_singleton()->get_root();
917
Node *node = I.item;
918
919
while (node && node != scene->get_parent()) {
920
if (node->has_meta("_edit_group_")) {
921
locked = 2;
922
}
923
node = node->get_parent();
924
}
925
}
926
927
String suffix;
928
if (locked == 1) {
929
suffix = " (" + RTR("Locked") + ")";
930
} else if (locked == 2) {
931
suffix = " (" + RTR("Grouped") + ")";
932
}
933
934
selection_list->add_item((String)I.item->get_name() + suffix);
935
selection_list->set_item_metadata(-1, I.item);
936
}
937
938
selection_list->set_position(selection_list->is_embedded() ? p_pos : (Input::get_singleton()->get_mouse_position() + root->get_position()));
939
selection_list->reset_size();
940
selection_list->popup();
941
942
selection_list->set_content_scale_factor(1);
943
selection_list->set_min_size(selection_list->get_contents_minimum_size());
944
selection_list->reset_size();
945
946
// FIXME: Ugly hack that stops the popup from hiding when the button is released.
947
selection_list->call_deferred(SNAME("set_position"), selection_list->get_position() + Point2(1, 0));
948
}
949
950
void RuntimeNodeSelect::_close_selection_list() {
951
selection_list->queue_free();
952
selection_list = nullptr;
953
}
954
955
void RuntimeNodeSelect::_set_selection_visible(bool p_visible) {
956
selection_visible = p_visible;
957
958
if (has_selection) {
959
_update_selection();
960
}
961
}
962
963
void RuntimeNodeSelect::_set_avoid_locked(bool p_enabled) {
964
avoid_locked_nodes = p_enabled;
965
}
966
967
void RuntimeNodeSelect::_set_prefer_group(bool p_enabled) {
968
prefer_group_selection = p_enabled;
969
}
970
971
// Copied and trimmed from the CanvasItemEditor implementation.
972
void 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) {
973
if (!p_node || Object::cast_to<Viewport>(p_node)) {
974
return;
975
}
976
977
CanvasItem *ci = Object::cast_to<CanvasItem>(p_node);
978
for (int i = p_node->get_child_count() - 1; i >= 0; i--) {
979
if (ci) {
980
if (!ci->is_set_as_top_level()) {
981
_find_canvas_items_at_pos(p_pos, p_node->get_child(i), r_items, p_parent_xform * ci->get_transform(), p_canvas_xform);
982
} else {
983
_find_canvas_items_at_pos(p_pos, p_node->get_child(i), r_items, ci->get_transform(), p_canvas_xform);
984
}
985
} else {
986
CanvasLayer *cl = Object::cast_to<CanvasLayer>(p_node);
987
_find_canvas_items_at_pos(p_pos, p_node->get_child(i), r_items, Transform2D(), cl ? cl->get_transform() : p_canvas_xform);
988
}
989
}
990
991
if (!ci || !ci->is_visible_in_tree()) {
992
return;
993
}
994
995
Transform2D xform = p_canvas_xform;
996
if (!ci->is_set_as_top_level()) {
997
xform *= p_parent_xform;
998
}
999
1000
Window *root = SceneTree::get_singleton()->get_root();
1001
Point2 pos;
1002
1003
// Cameras don't affect `CanvasLayer`s.
1004
if (!ci->get_canvas_layer_node() || ci->get_canvas_layer_node()->is_following_viewport()) {
1005
pos = root->get_canvas_transform().affine_inverse().xform(p_pos);
1006
} else {
1007
pos = p_pos;
1008
}
1009
1010
xform = (xform * ci->get_transform()).affine_inverse();
1011
const real_t local_grab_distance = xform.basis_xform(Vector2(sel_2d_grab_dist, 0)).length() / view_2d_zoom;
1012
if (ci->_edit_is_selected_on_click(xform.xform(pos), local_grab_distance)) {
1013
SelectResult res;
1014
res.item = ci;
1015
res.order = ci->get_effective_z_index() + ci->get_canvas_layer();
1016
r_items.push_back(res);
1017
1018
#ifndef PHYSICS_2D_DISABLED
1019
// If it's a shape, get the collision object it's from.
1020
// FIXME: If the collision object has multiple shapes, only the topmost will be above it in the list.
1021
if (Object::cast_to<CollisionShape2D>(ci) || Object::cast_to<CollisionPolygon2D>(ci)) {
1022
CollisionObject2D *collision_object = Object::cast_to<CollisionObject2D>(ci->get_parent());
1023
if (collision_object) {
1024
SelectResult res_col;
1025
res_col.item = ci->get_parent();
1026
res_col.order = collision_object->get_z_index() + ci->get_canvas_layer();
1027
r_items.push_back(res_col);
1028
}
1029
}
1030
#endif // PHYSICS_2D_DISABLED
1031
}
1032
}
1033
1034
// Copied and trimmed from the CanvasItemEditor implementation.
1035
void 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) {
1036
if (!p_node || Object::cast_to<Viewport>(p_node)) {
1037
return;
1038
}
1039
1040
CanvasItem *ci = Object::cast_to<CanvasItem>(p_node);
1041
for (int i = p_node->get_child_count() - 1; i >= 0; i--) {
1042
if (ci) {
1043
if (!ci->is_set_as_top_level()) {
1044
_find_canvas_items_at_rect(p_rect, p_node->get_child(i), r_items, p_parent_xform * ci->get_transform(), p_canvas_xform);
1045
} else {
1046
_find_canvas_items_at_rect(p_rect, p_node->get_child(i), r_items, ci->get_transform(), p_canvas_xform);
1047
}
1048
} else {
1049
CanvasLayer *cl = Object::cast_to<CanvasLayer>(p_node);
1050
_find_canvas_items_at_rect(p_rect, p_node->get_child(i), r_items, Transform2D(), cl ? cl->get_transform() : p_canvas_xform);
1051
}
1052
}
1053
1054
if (!ci || !ci->is_visible_in_tree()) {
1055
return;
1056
}
1057
1058
Transform2D xform = p_canvas_xform;
1059
if (!ci->is_set_as_top_level()) {
1060
xform *= p_parent_xform;
1061
}
1062
1063
Window *root = SceneTree::get_singleton()->get_root();
1064
Rect2 rect;
1065
// Cameras don't affect `CanvasLayer`s.
1066
if (!ci->get_canvas_layer_node() || ci->get_canvas_layer_node()->is_following_viewport()) {
1067
rect = root->get_canvas_transform().affine_inverse().xform(p_rect);
1068
} else {
1069
rect = p_rect;
1070
}
1071
rect = (xform * ci->get_transform()).affine_inverse().xform(rect);
1072
1073
bool selected = false;
1074
if (ci->_edit_use_rect()) {
1075
Rect2 ci_rect = ci->_edit_get_rect();
1076
if (rect.has_point(ci_rect.position) &&
1077
rect.has_point(ci_rect.position + Vector2(ci_rect.size.x, 0)) &&
1078
rect.has_point(ci_rect.position + Vector2(ci_rect.size.x, ci_rect.size.y)) &&
1079
rect.has_point(ci_rect.position + Vector2(0, ci_rect.size.y))) {
1080
selected = true;
1081
}
1082
} else if (rect.has_point(Point2())) {
1083
selected = true;
1084
}
1085
1086
if (selected) {
1087
SelectResult res;
1088
res.item = ci;
1089
res.order = ci->get_effective_z_index() + ci->get_canvas_layer();
1090
r_items.push_back(res);
1091
}
1092
}
1093
1094
void RuntimeNodeSelect::_pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event) {
1095
Vector2 scroll = SceneTree::get_singleton()->get_root()->get_screen_transform().affine_inverse().xform(p_scroll_vec);
1096
view_2d_offset.x -= scroll.x / view_2d_zoom;
1097
view_2d_offset.y -= scroll.y / view_2d_zoom;
1098
1099
_update_view_2d();
1100
}
1101
1102
// A very shallow copy of the same function inside CanvasItemEditor.
1103
void RuntimeNodeSelect::_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event) {
1104
real_t prev_zoom = view_2d_zoom;
1105
view_2d_zoom = CLAMP(view_2d_zoom * p_zoom_factor, VIEW_2D_MIN_ZOOM, VIEW_2D_MAX_ZOOM);
1106
1107
Vector2 pos = SceneTree::get_singleton()->get_root()->get_screen_transform().affine_inverse().xform(p_origin);
1108
view_2d_offset += pos / prev_zoom - pos / view_2d_zoom;
1109
1110
// We want to align in-scene pixels to screen pixels, this prevents blurry rendering
1111
// of small details (texts, lines).
1112
// This correction adds a jitter movement when zooming, so we correct only when the
1113
// zoom factor is an integer. (in the other cases, all pixels won't be aligned anyway)
1114
const real_t closest_zoom_factor = Math::round(view_2d_zoom);
1115
if (Math::is_zero_approx(view_2d_zoom - closest_zoom_factor)) {
1116
// Make sure scene pixel at view_offset is aligned on a screen pixel.
1117
Vector2 view_offset_int = view_2d_offset.floor();
1118
Vector2 view_offset_frac = view_2d_offset - view_offset_int;
1119
view_2d_offset = view_offset_int + (view_offset_frac * closest_zoom_factor).round() / closest_zoom_factor;
1120
}
1121
1122
_update_view_2d();
1123
}
1124
1125
void RuntimeNodeSelect::_reset_camera_2d() {
1126
camera_first_override = true;
1127
Window *root = SceneTree::get_singleton()->get_root();
1128
Camera2D *game_camera = root->is_camera_2d_override_enabled() ? root->get_overridden_camera_2d() : root->get_camera_2d();
1129
if (game_camera) {
1130
// Ideally we should be using Camera2D::get_camera_transform() but it's not so this hack will have to do for now.
1131
view_2d_offset = game_camera->get_camera_screen_center() - (0.5 * root->get_visible_rect().size);
1132
} else {
1133
view_2d_offset = Vector2();
1134
}
1135
1136
view_2d_zoom = 1;
1137
1138
if (root->is_camera_2d_override_enabled()) {
1139
_update_view_2d();
1140
}
1141
}
1142
1143
void RuntimeNodeSelect::_update_view_2d() {
1144
Window *root = SceneTree::get_singleton()->get_root();
1145
ERR_FAIL_COND(!root->is_camera_2d_override_enabled());
1146
1147
Camera2D *override_camera = root->get_override_camera_2d();
1148
override_camera->set_anchor_mode(Camera2D::ANCHOR_MODE_FIXED_TOP_LEFT);
1149
override_camera->set_zoom(Vector2(view_2d_zoom, view_2d_zoom));
1150
override_camera->set_offset(view_2d_offset);
1151
1152
_queue_selection_update();
1153
}
1154
1155
#ifndef _3D_DISABLED
1156
void RuntimeNodeSelect::_find_3d_items_at_pos(const Point2 &p_pos, Vector<SelectResult> &r_items) {
1157
Window *root = SceneTree::get_singleton()->get_root();
1158
1159
Vector3 ray, pos, to;
1160
Camera3D *camera = root->get_camera_3d();
1161
if (!camera) {
1162
return;
1163
}
1164
1165
ray = camera->project_ray_normal(p_pos);
1166
pos = camera->project_ray_origin(p_pos);
1167
to = pos + ray * camera->get_far();
1168
1169
#ifndef PHYSICS_3D_DISABLED
1170
// Start with physical objects.
1171
PhysicsDirectSpaceState3D *ss = root->get_world_3d()->get_direct_space_state();
1172
PhysicsDirectSpaceState3D::RayResult result;
1173
HashSet<RID> excluded;
1174
PhysicsDirectSpaceState3D::RayParameters ray_params;
1175
ray_params.from = pos;
1176
ray_params.to = to;
1177
ray_params.collide_with_areas = true;
1178
while (true) {
1179
ray_params.exclude = excluded;
1180
if (ss->intersect_ray(ray_params, result)) {
1181
SelectResult res;
1182
res.item = Object::cast_to<Node>(result.collider);
1183
res.order = -pos.distance_to(result.position);
1184
1185
// Fetch collision shapes.
1186
CollisionObject3D *collision = Object::cast_to<CollisionObject3D>(result.collider);
1187
if (collision) {
1188
List<uint32_t> owners;
1189
collision->get_shape_owners(&owners);
1190
for (uint32_t &I : owners) {
1191
SelectResult res_shape;
1192
res_shape.item = Object::cast_to<Node>(collision->shape_owner_get_owner(I));
1193
res_shape.order = res.order;
1194
r_items.push_back(res_shape);
1195
}
1196
}
1197
1198
r_items.push_back(res);
1199
1200
excluded.insert(result.rid);
1201
} else {
1202
break;
1203
}
1204
}
1205
#endif // PHYSICS_3D_DISABLED
1206
1207
// Then go for the meshes.
1208
Vector<ObjectID> items = RS::get_singleton()->instances_cull_ray(pos, to, root->get_world_3d()->get_scenario());
1209
for (int i = 0; i < items.size(); i++) {
1210
Object *obj = ObjectDB::get_instance(items[i]);
1211
1212
GeometryInstance3D *geo_instance = Object::cast_to<GeometryInstance3D>(obj);
1213
if (geo_instance) {
1214
Ref<TriangleMesh> mesh_collision = geo_instance->generate_triangle_mesh();
1215
1216
if (mesh_collision.is_valid()) {
1217
Transform3D gt = geo_instance->get_global_transform();
1218
Transform3D ai = gt.affine_inverse();
1219
Vector3 point, normal;
1220
if (mesh_collision->intersect_ray(ai.xform(pos), ai.basis.xform(ray).normalized(), point, normal)) {
1221
SelectResult res;
1222
res.item = Object::cast_to<Node>(obj);
1223
res.order = -pos.distance_to(gt.xform(point));
1224
r_items.push_back(res);
1225
1226
continue;
1227
}
1228
}
1229
}
1230
1231
items.remove_at(i);
1232
i--;
1233
}
1234
}
1235
1236
void RuntimeNodeSelect::_find_3d_items_at_rect(const Rect2 &p_rect, Vector<SelectResult> &r_items) {
1237
Window *root = SceneTree::get_singleton()->get_root();
1238
Camera3D *camera = root->get_camera_3d();
1239
if (!camera) {
1240
return;
1241
}
1242
1243
Vector3 cam_pos = camera->get_global_position();
1244
Vector3 dist_pos = camera->project_ray_origin(p_rect.position + p_rect.size / 2);
1245
1246
real_t znear = camera->get_near();
1247
real_t zfar = camera->get_far();
1248
real_t zofs = MAX(0.0, 5.0 - znear);
1249
1250
const Point2 pos_end = p_rect.position + p_rect.size;
1251
Vector3 box[4] = {
1252
Vector3(
1253
MIN(p_rect.position.x, pos_end.x),
1254
MIN(p_rect.position.y, pos_end.y),
1255
zofs),
1256
Vector3(
1257
MAX(p_rect.position.x, pos_end.x),
1258
MIN(p_rect.position.y, pos_end.y),
1259
zofs),
1260
Vector3(
1261
MAX(p_rect.position.x, pos_end.x),
1262
MAX(p_rect.position.y, pos_end.y),
1263
zofs),
1264
Vector3(
1265
MIN(p_rect.position.x, pos_end.x),
1266
MAX(p_rect.position.y, pos_end.y),
1267
zofs)
1268
};
1269
1270
Vector<Plane> frustum;
1271
for (int i = 0; i < 4; i++) {
1272
Vector3 a = _get_screen_to_space(box[i]);
1273
Vector3 b = _get_screen_to_space(box[(i + 1) % 4]);
1274
frustum.push_back(Plane(a, b, cam_pos));
1275
}
1276
1277
// Get the camera normal.
1278
Plane near_plane = Plane(camera->get_global_transform().basis.get_column(2), cam_pos);
1279
1280
near_plane.d -= znear;
1281
frustum.push_back(near_plane);
1282
1283
Plane far_plane = -near_plane;
1284
far_plane.d += zfar;
1285
frustum.push_back(far_plane);
1286
1287
// Keep track of the currently listed nodes, so repeats can be ignored.
1288
HashSet<Node *> node_list;
1289
1290
#ifndef PHYSICS_3D_DISABLED
1291
Vector<Vector3> points = Geometry3D::compute_convex_mesh_points(&frustum[0], frustum.size());
1292
Ref<ConvexPolygonShape3D> shape;
1293
shape.instantiate();
1294
shape->set_points(points);
1295
1296
// Start with physical objects.
1297
PhysicsDirectSpaceState3D *ss = root->get_world_3d()->get_direct_space_state();
1298
PhysicsDirectSpaceState3D::ShapeResult results[32];
1299
PhysicsDirectSpaceState3D::ShapeParameters shape_params;
1300
shape_params.shape_rid = shape->get_rid();
1301
shape_params.collide_with_areas = true;
1302
const int num_hits = ss->intersect_shape(shape_params, results, 32);
1303
for (int i = 0; i < num_hits; i++) {
1304
const PhysicsDirectSpaceState3D::ShapeResult &result = results[i];
1305
SelectResult res;
1306
res.item = Object::cast_to<Node>(result.collider);
1307
res.order = -dist_pos.distance_to(Object::cast_to<Node3D>(res.item)->get_global_transform().origin);
1308
1309
// Fetch collision shapes.
1310
CollisionObject3D *collision = Object::cast_to<CollisionObject3D>(result.collider);
1311
if (collision) {
1312
List<uint32_t> owners;
1313
collision->get_shape_owners(&owners);
1314
for (uint32_t &I : owners) {
1315
SelectResult res_shape;
1316
res_shape.item = Object::cast_to<Node>(collision->shape_owner_get_owner(I));
1317
if (!node_list.has(res_shape.item)) {
1318
node_list.insert(res_shape.item);
1319
res_shape.order = res.order;
1320
r_items.push_back(res_shape);
1321
}
1322
}
1323
}
1324
1325
if (!node_list.has(res.item)) {
1326
node_list.insert(res.item);
1327
r_items.push_back(res);
1328
}
1329
}
1330
#endif // PHYSICS_3D_DISABLED
1331
1332
// Then go for the meshes.
1333
Vector<ObjectID> items = RS::get_singleton()->instances_cull_convex(frustum, root->get_world_3d()->get_scenario());
1334
for (int i = 0; i < items.size(); i++) {
1335
Object *obj = ObjectDB::get_instance(items[i]);
1336
GeometryInstance3D *geo_instance = Object::cast_to<GeometryInstance3D>(obj);
1337
if (geo_instance) {
1338
Ref<TriangleMesh> mesh_collision = geo_instance->generate_triangle_mesh();
1339
1340
if (mesh_collision.is_valid()) {
1341
Transform3D gt = geo_instance->get_global_transform();
1342
Vector3 mesh_scale = gt.get_basis().get_scale();
1343
gt.orthonormalize();
1344
1345
Transform3D it = gt.affine_inverse();
1346
1347
Vector<Plane> transformed_frustum;
1348
int plane_count = frustum.size();
1349
transformed_frustum.resize(plane_count);
1350
1351
for (int j = 0; j < plane_count; j++) {
1352
transformed_frustum.write[j] = it.xform(frustum[j]);
1353
}
1354
Vector<Vector3> convex_points = Geometry3D::compute_convex_mesh_points(transformed_frustum.ptr(), plane_count);
1355
if (mesh_collision->inside_convex_shape(transformed_frustum.ptr(), transformed_frustum.size(), convex_points.ptr(), convex_points.size(), mesh_scale)) {
1356
SelectResult res;
1357
res.item = Object::cast_to<Node>(obj);
1358
if (!node_list.has(res.item)) {
1359
node_list.insert(res.item);
1360
res.order = -dist_pos.distance_to(gt.origin);
1361
r_items.push_back(res);
1362
}
1363
1364
continue;
1365
}
1366
}
1367
}
1368
1369
items.remove_at(i);
1370
i--;
1371
}
1372
}
1373
1374
Vector3 RuntimeNodeSelect::_get_screen_to_space(const Vector3 &p_vector3) {
1375
Window *root = SceneTree::get_singleton()->get_root();
1376
Camera3D *camera = root->get_camera_3d();
1377
1378
Transform3D camera_transform = camera->get_camera_transform();
1379
Size2 size = root->get_size();
1380
real_t znear = camera->get_near();
1381
Projection cm = Projection::create_perspective(camera->get_fov(), size.aspect(), znear + p_vector3.z, camera->get_far());
1382
Vector2 screen_he = cm.get_viewport_half_extents();
1383
return 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)));
1384
}
1385
1386
bool RuntimeNodeSelect::_handle_3d_input(const Ref<InputEvent> &p_event) {
1387
Ref<InputEventMouseButton> b = p_event;
1388
if (b.is_valid()) {
1389
const real_t zoom_factor = 1.08 * b->get_factor();
1390
switch (b->get_button_index()) {
1391
case MouseButton::WHEEL_UP: {
1392
if (!camera_freelook) {
1393
_cursor_scale_distance(1.0 / zoom_factor);
1394
} else {
1395
_scale_freelook_speed(zoom_factor);
1396
}
1397
1398
return true;
1399
} break;
1400
case MouseButton::WHEEL_DOWN: {
1401
if (!camera_freelook) {
1402
_cursor_scale_distance(zoom_factor);
1403
} else {
1404
_scale_freelook_speed(1.0 / zoom_factor);
1405
}
1406
1407
return true;
1408
} break;
1409
case MouseButton::RIGHT: {
1410
_set_camera_freelook_enabled(b->is_pressed());
1411
return true;
1412
} break;
1413
default: {
1414
}
1415
}
1416
}
1417
1418
Ref<InputEventMouseMotion> m = p_event;
1419
if (m.is_valid()) {
1420
if (camera_freelook) {
1421
_cursor_look(m);
1422
} else if (m->get_button_mask().has_flag(MouseButtonMask::MIDDLE)) {
1423
if (m->is_shift_pressed()) {
1424
_cursor_pan(m);
1425
} else {
1426
_cursor_orbit(m);
1427
}
1428
}
1429
1430
return true;
1431
}
1432
1433
Ref<InputEventKey> k = p_event;
1434
if (k.is_valid()) {
1435
if (k->get_physical_keycode() == Key::ESCAPE) {
1436
_set_camera_freelook_enabled(false);
1437
return true;
1438
} else if (k->is_ctrl_pressed()) {
1439
switch (k->get_physical_keycode()) {
1440
case Key::EQUAL: {
1441
ERR_FAIL_COND_V(!SceneTree::get_singleton()->get_root()->is_camera_3d_override_enabled(), false);
1442
cursor.fov_scale = CLAMP(cursor.fov_scale - 0.05, CAMERA_MIN_FOV_SCALE, CAMERA_MAX_FOV_SCALE);
1443
SceneTree::get_singleton()->get_root()->get_override_camera_3d()->set_perspective(camera_fov * cursor.fov_scale, camera_znear, camera_zfar);
1444
1445
return true;
1446
} break;
1447
case Key::MINUS: {
1448
ERR_FAIL_COND_V(!SceneTree::get_singleton()->get_root()->is_camera_3d_override_enabled(), false);
1449
cursor.fov_scale = CLAMP(cursor.fov_scale + 0.05, CAMERA_MIN_FOV_SCALE, CAMERA_MAX_FOV_SCALE);
1450
SceneTree::get_singleton()->get_root()->get_override_camera_3d()->set_perspective(camera_fov * cursor.fov_scale, camera_znear, camera_zfar);
1451
1452
return true;
1453
} break;
1454
case Key::KEY_0: {
1455
ERR_FAIL_COND_V(!SceneTree::get_singleton()->get_root()->is_camera_3d_override_enabled(), false);
1456
cursor.fov_scale = 1;
1457
SceneTree::get_singleton()->get_root()->get_override_camera_3d()->set_perspective(camera_fov, camera_znear, camera_zfar);
1458
1459
return true;
1460
} break;
1461
default: {
1462
}
1463
}
1464
}
1465
}
1466
1467
// TODO: Handle magnify and pan input gestures.
1468
1469
return false;
1470
}
1471
1472
void RuntimeNodeSelect::_set_camera_freelook_enabled(bool p_enabled) {
1473
camera_freelook = p_enabled;
1474
1475
if (p_enabled) {
1476
// Make sure eye_pos is synced, because freelook referential is eye pos rather than orbit pos
1477
Vector3 forward = _get_cursor_transform().basis.xform(Vector3(0, 0, -1));
1478
cursor.eye_pos = cursor.pos - cursor.distance * forward;
1479
1480
previous_mouse_position = SceneTree::get_singleton()->get_root()->get_mouse_position();
1481
1482
// Hide mouse like in an FPS (warping doesn't work).
1483
Input::get_singleton()->set_mouse_mode_override(Input::MouseMode::MOUSE_MODE_CAPTURED);
1484
1485
} else {
1486
// Restore mouse.
1487
Input::get_singleton()->set_mouse_mode_override(Input::MouseMode::MOUSE_MODE_VISIBLE);
1488
1489
// Restore the previous mouse position when leaving freelook mode.
1490
// This is done because leaving `Input.MOUSE_MODE_CAPTURED` will center the cursor
1491
// due to OS limitations.
1492
Input::get_singleton()->warp_mouse(previous_mouse_position);
1493
}
1494
}
1495
1496
void RuntimeNodeSelect::_cursor_scale_distance(real_t p_scale) {
1497
ERR_FAIL_COND(!SceneTree::get_singleton()->get_root()->is_camera_3d_override_enabled());
1498
real_t min_distance = MAX(camera_znear * 4, VIEW_3D_MIN_ZOOM);
1499
real_t max_distance = MIN(camera_zfar / 4, VIEW_3D_MAX_ZOOM);
1500
cursor.distance = CLAMP(cursor.distance * p_scale, min_distance, max_distance);
1501
1502
SceneTree::get_singleton()->get_root()->get_override_camera_3d()->set_transform(_get_cursor_transform());
1503
}
1504
1505
void RuntimeNodeSelect::_scale_freelook_speed(real_t p_scale) {
1506
real_t min_speed = MAX(camera_znear * 4, VIEW_3D_MIN_ZOOM);
1507
real_t max_speed = MIN(camera_zfar / 4, VIEW_3D_MAX_ZOOM);
1508
if (unlikely(min_speed > max_speed)) {
1509
freelook_base_speed = (min_speed + max_speed) / 2;
1510
} else {
1511
freelook_base_speed = CLAMP(freelook_base_speed * p_scale, min_speed, max_speed);
1512
}
1513
}
1514
1515
void RuntimeNodeSelect::_cursor_look(Ref<InputEventWithModifiers> p_event) {
1516
Window *root = SceneTree::get_singleton()->get_root();
1517
ERR_FAIL_COND(!root->is_camera_3d_override_enabled());
1518
1519
const Vector2 relative = _get_warped_mouse_motion(p_event, Rect2(Vector2(), root->get_size()));
1520
const Transform3D prev_camera_transform = _get_cursor_transform();
1521
1522
if (invert_y_axis) {
1523
cursor.x_rot -= relative.y * freelook_sensitivity;
1524
} else {
1525
cursor.x_rot += relative.y * freelook_sensitivity;
1526
}
1527
// Clamp the Y rotation to roughly -90..90 degrees so the user can't look upside-down and end up disoriented.
1528
cursor.x_rot = CLAMP(cursor.x_rot, -1.57, 1.57);
1529
1530
cursor.y_rot += relative.x * freelook_sensitivity;
1531
1532
// Look is like the opposite of Orbit: the focus point rotates around the camera.
1533
Transform3D camera_transform = _get_cursor_transform();
1534
Vector3 pos = camera_transform.xform(Vector3(0, 0, 0));
1535
Vector3 prev_pos = prev_camera_transform.xform(Vector3(0, 0, 0));
1536
Vector3 diff = prev_pos - pos;
1537
cursor.pos += diff;
1538
1539
root->get_override_camera_3d()->set_transform(_get_cursor_transform());
1540
}
1541
1542
void RuntimeNodeSelect::_cursor_pan(Ref<InputEventWithModifiers> p_event) {
1543
Window *root = SceneTree::get_singleton()->get_root();
1544
ERR_FAIL_COND(!root->is_camera_3d_override_enabled());
1545
1546
// Reduce all sides of the area by 1, so warping works when windows are maximized/fullscreen.
1547
const Vector2 relative = _get_warped_mouse_motion(p_event, Rect2(Vector2(1, 1), root->get_size() - Vector2(2, 2)));
1548
const real_t pan_speed = translation_sensitivity / 150.0;
1549
1550
Transform3D camera_transform;
1551
camera_transform.translate_local(cursor.pos);
1552
camera_transform.basis.rotate(Vector3(1, 0, 0), -cursor.x_rot);
1553
camera_transform.basis.rotate(Vector3(0, 1, 0), -cursor.y_rot);
1554
1555
Vector3 translation(1 * -relative.x * pan_speed, relative.y * pan_speed, 0);
1556
translation *= cursor.distance / 4;
1557
camera_transform.translate_local(translation);
1558
cursor.pos = camera_transform.origin;
1559
1560
root->get_override_camera_3d()->set_transform(_get_cursor_transform());
1561
}
1562
1563
void RuntimeNodeSelect::_cursor_orbit(Ref<InputEventWithModifiers> p_event) {
1564
Window *root = SceneTree::get_singleton()->get_root();
1565
ERR_FAIL_COND(!root->is_camera_3d_override_enabled());
1566
1567
// Reduce all sides of the area by 1, so warping works when windows are maximized/fullscreen.
1568
const Vector2 relative = _get_warped_mouse_motion(p_event, Rect2(Vector2(1, 1), root->get_size() - Vector2(2, 2)));
1569
1570
if (invert_y_axis) {
1571
cursor.x_rot -= relative.y * orbit_sensitivity;
1572
} else {
1573
cursor.x_rot += relative.y * orbit_sensitivity;
1574
}
1575
// Clamp the Y rotation to roughly -90..90 degrees so the user can't look upside-down and end up disoriented.
1576
cursor.x_rot = CLAMP(cursor.x_rot, -1.57, 1.57);
1577
1578
if (invert_x_axis) {
1579
cursor.y_rot -= relative.x * orbit_sensitivity;
1580
} else {
1581
cursor.y_rot += relative.x * orbit_sensitivity;
1582
}
1583
1584
root->get_override_camera_3d()->set_transform(_get_cursor_transform());
1585
}
1586
1587
Point2 RuntimeNodeSelect::_get_warped_mouse_motion(const Ref<InputEventMouseMotion> &p_event, Rect2 p_area) const {
1588
ERR_FAIL_COND_V(p_event.is_null(), Point2());
1589
1590
if (warped_mouse_panning_3d) {
1591
return Input::get_singleton()->warp_mouse_motion(p_event, p_area);
1592
}
1593
1594
return p_event->get_relative();
1595
}
1596
1597
Transform3D RuntimeNodeSelect::_get_cursor_transform() {
1598
Transform3D camera_transform;
1599
camera_transform.translate_local(cursor.pos);
1600
camera_transform.basis.rotate(Vector3(1, 0, 0), -cursor.x_rot);
1601
camera_transform.basis.rotate(Vector3(0, 1, 0), -cursor.y_rot);
1602
camera_transform.translate_local(0, 0, cursor.distance);
1603
1604
return camera_transform;
1605
}
1606
1607
void RuntimeNodeSelect::_reset_camera_3d() {
1608
camera_first_override = true;
1609
1610
cursor = Cursor();
1611
Window *root = SceneTree::get_singleton()->get_root();
1612
Camera3D *game_camera = root->is_camera_3d_override_enabled() ? root->get_overridden_camera_3d() : root->get_camera_3d();
1613
if (game_camera) {
1614
Transform3D transform = game_camera->get_camera_transform();
1615
transform.translate_local(0, 0, -cursor.distance);
1616
cursor.pos = transform.origin;
1617
1618
cursor.x_rot = -game_camera->get_global_rotation().x;
1619
cursor.y_rot = -game_camera->get_global_rotation().y;
1620
1621
cursor.fov_scale = CLAMP(game_camera->get_fov() / camera_fov, CAMERA_MIN_FOV_SCALE, CAMERA_MAX_FOV_SCALE);
1622
} else {
1623
cursor.fov_scale = 1.0;
1624
}
1625
1626
if (root->is_camera_3d_override_enabled()) {
1627
Camera3D *override_camera = root->get_override_camera_3d();
1628
override_camera->set_transform(_get_cursor_transform());
1629
override_camera->set_perspective(camera_fov * cursor.fov_scale, camera_znear, camera_zfar);
1630
}
1631
}
1632
#endif // _3D_DISABLED
1633
1634
#endif // DEBUG_ENABLED
1635
1636