Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/editor/scene/3d/node_3d_editor_plugin.cpp
9902 views
1
/**************************************************************************/
2
/* node_3d_editor_plugin.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
#include "node_3d_editor_plugin.h"
32
33
#include "core/config/project_settings.h"
34
#include "core/input/input.h"
35
#include "core/input/input_map.h"
36
#include "core/math/math_funcs.h"
37
#include "core/math/projection.h"
38
#include "core/os/keyboard.h"
39
#include "editor/animation/animation_player_editor_plugin.h"
40
#include "editor/debugger/editor_debugger_node.h"
41
#include "editor/docks/scene_tree_dock.h"
42
#include "editor/editor_main_screen.h"
43
#include "editor/editor_node.h"
44
#include "editor/editor_string_names.h"
45
#include "editor/editor_undo_redo_manager.h"
46
#include "editor/gui/editor_spin_slider.h"
47
#include "editor/run/editor_run_bar.h"
48
#include "editor/scene/3d/gizmos/audio_listener_3d_gizmo_plugin.h"
49
#include "editor/scene/3d/gizmos/audio_stream_player_3d_gizmo_plugin.h"
50
#include "editor/scene/3d/gizmos/camera_3d_gizmo_plugin.h"
51
#include "editor/scene/3d/gizmos/cpu_particles_3d_gizmo_plugin.h"
52
#include "editor/scene/3d/gizmos/decal_gizmo_plugin.h"
53
#include "editor/scene/3d/gizmos/fog_volume_gizmo_plugin.h"
54
#include "editor/scene/3d/gizmos/geometry_instance_3d_gizmo_plugin.h"
55
#include "editor/scene/3d/gizmos/gpu_particles_3d_gizmo_plugin.h"
56
#include "editor/scene/3d/gizmos/gpu_particles_collision_3d_gizmo_plugin.h"
57
#include "editor/scene/3d/gizmos/label_3d_gizmo_plugin.h"
58
#include "editor/scene/3d/gizmos/light_3d_gizmo_plugin.h"
59
#include "editor/scene/3d/gizmos/lightmap_gi_gizmo_plugin.h"
60
#include "editor/scene/3d/gizmos/lightmap_probe_gizmo_plugin.h"
61
#include "editor/scene/3d/gizmos/marker_3d_gizmo_plugin.h"
62
#include "editor/scene/3d/gizmos/mesh_instance_3d_gizmo_plugin.h"
63
#include "editor/scene/3d/gizmos/occluder_instance_3d_gizmo_plugin.h"
64
#include "editor/scene/3d/gizmos/particles_3d_emission_shape_gizmo_plugin.h"
65
#include "editor/scene/3d/gizmos/physics/collision_object_3d_gizmo_plugin.h"
66
#include "editor/scene/3d/gizmos/physics/collision_polygon_3d_gizmo_plugin.h"
67
#include "editor/scene/3d/gizmos/physics/collision_shape_3d_gizmo_plugin.h"
68
#include "editor/scene/3d/gizmos/physics/joint_3d_gizmo_plugin.h"
69
#include "editor/scene/3d/gizmos/physics/physics_bone_3d_gizmo_plugin.h"
70
#include "editor/scene/3d/gizmos/physics/ray_cast_3d_gizmo_plugin.h"
71
#include "editor/scene/3d/gizmos/physics/shape_cast_3d_gizmo_plugin.h"
72
#include "editor/scene/3d/gizmos/physics/soft_body_3d_gizmo_plugin.h"
73
#include "editor/scene/3d/gizmos/physics/spring_arm_3d_gizmo_plugin.h"
74
#include "editor/scene/3d/gizmos/physics/vehicle_body_3d_gizmo_plugin.h"
75
#include "editor/scene/3d/gizmos/reflection_probe_gizmo_plugin.h"
76
#include "editor/scene/3d/gizmos/spring_bone_3d_gizmo_plugin.h"
77
#include "editor/scene/3d/gizmos/sprite_base_3d_gizmo_plugin.h"
78
#include "editor/scene/3d/gizmos/visible_on_screen_notifier_3d_gizmo_plugin.h"
79
#include "editor/scene/3d/gizmos/voxel_gi_gizmo_plugin.h"
80
#include "editor/scene/3d/node_3d_editor_gizmos.h"
81
#include "editor/settings/editor_settings.h"
82
#include "editor/translations/editor_translation_preview_button.h"
83
#include "editor/translations/editor_translation_preview_menu.h"
84
#include "scene/3d/audio_stream_player_3d.h"
85
#include "scene/3d/camera_3d.h"
86
#include "scene/3d/decal.h"
87
#include "scene/3d/light_3d.h"
88
#include "scene/3d/mesh_instance_3d.h"
89
#include "scene/3d/physics/collision_shape_3d.h"
90
#include "scene/3d/physics/physics_body_3d.h"
91
#include "scene/3d/sprite_3d.h"
92
#include "scene/3d/visual_instance_3d.h"
93
#include "scene/3d/world_environment.h"
94
#include "scene/gui/center_container.h"
95
#include "scene/gui/color_picker.h"
96
#include "scene/gui/flow_container.h"
97
#include "scene/gui/separator.h"
98
#include "scene/gui/split_container.h"
99
#include "scene/gui/subviewport_container.h"
100
#include "scene/resources/3d/sky_material.h"
101
#include "scene/resources/packed_scene.h"
102
#include "scene/resources/surface_tool.h"
103
104
constexpr real_t DISTANCE_DEFAULT = 4;
105
106
constexpr real_t GIZMO_ARROW_SIZE = 0.35;
107
constexpr real_t GIZMO_RING_HALF_WIDTH = 0.1;
108
constexpr real_t GIZMO_PLANE_SIZE = 0.2;
109
constexpr real_t GIZMO_PLANE_DST = 0.3;
110
constexpr real_t GIZMO_CIRCLE_SIZE = 1.1;
111
constexpr real_t GIZMO_SCALE_OFFSET = GIZMO_CIRCLE_SIZE + 0.3;
112
constexpr real_t GIZMO_ARROW_OFFSET = GIZMO_CIRCLE_SIZE + 0.3;
113
114
constexpr real_t ZOOM_FREELOOK_MIN = 0.01;
115
constexpr real_t ZOOM_FREELOOK_MULTIPLIER = 1.08;
116
constexpr real_t ZOOM_FREELOOK_INDICATOR_DELAY_S = 1.5;
117
118
#ifdef REAL_T_IS_DOUBLE
119
constexpr double ZOOM_FREELOOK_MAX = 1'000'000'000'000;
120
#else
121
constexpr float ZOOM_FREELOOK_MAX = 10'000;
122
#endif
123
124
constexpr real_t MIN_Z = 0.01;
125
constexpr real_t MAX_Z = 1000000.0;
126
127
constexpr real_t MIN_FOV = 0.01;
128
constexpr real_t MAX_FOV = 179;
129
130
void ViewportNavigationControl::_notification(int p_what) {
131
switch (p_what) {
132
case NOTIFICATION_DRAW: {
133
if (viewport != nullptr) {
134
_draw();
135
_update_navigation();
136
}
137
} break;
138
139
case NOTIFICATION_MOUSE_ENTER: {
140
hovered = true;
141
queue_redraw();
142
} break;
143
144
case NOTIFICATION_MOUSE_EXIT: {
145
hovered = false;
146
queue_redraw();
147
} break;
148
}
149
}
150
151
void ViewportNavigationControl::_draw() {
152
if (nav_mode == Node3DEditorViewport::NAVIGATION_NONE) {
153
return;
154
}
155
156
Vector2 center = get_size() / 2.0;
157
float radius = get_size().x / 2.0;
158
159
const bool focused = focused_index != -1;
160
draw_circle(center, radius, Color(0.5, 0.5, 0.5, focused || hovered ? 0.35 : 0.15));
161
162
const Color c = focused ? Color(0.9, 0.9, 0.9, 0.9) : Color(0.5, 0.5, 0.5, 0.25);
163
164
Vector2 circle_pos = focused ? center.move_toward(focused_pos, radius) : center;
165
166
draw_circle(circle_pos, AXIS_CIRCLE_RADIUS, c);
167
draw_circle(circle_pos, AXIS_CIRCLE_RADIUS * 0.8, c.darkened(0.4));
168
}
169
170
void ViewportNavigationControl::_process_click(int p_index, Vector2 p_position, bool p_pressed) {
171
hovered = false;
172
queue_redraw();
173
174
if (focused_index != -1 && focused_index != p_index) {
175
return;
176
}
177
if (p_pressed) {
178
if (p_position.distance_to(get_size() / 2.0) < get_size().x / 2.0) {
179
focused_pos = p_position;
180
focused_index = p_index;
181
queue_redraw();
182
}
183
} else {
184
focused_index = -1;
185
if (Input::get_singleton()->get_mouse_mode() == Input::MOUSE_MODE_CAPTURED) {
186
Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE);
187
Input::get_singleton()->warp_mouse(focused_mouse_start);
188
}
189
}
190
}
191
192
void ViewportNavigationControl::_process_drag(int p_index, Vector2 p_position, Vector2 p_relative_position) {
193
if (focused_index == p_index) {
194
if (Input::get_singleton()->get_mouse_mode() == Input::MOUSE_MODE_VISIBLE) {
195
Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_CAPTURED);
196
focused_mouse_start = p_position;
197
}
198
focused_pos += p_relative_position;
199
queue_redraw();
200
}
201
}
202
203
void ViewportNavigationControl::gui_input(const Ref<InputEvent> &p_event) {
204
// Mouse events
205
const Ref<InputEventMouseButton> mouse_button = p_event;
206
if (mouse_button.is_valid() && mouse_button->get_button_index() == MouseButton::LEFT) {
207
_process_click(100, mouse_button->get_position(), mouse_button->is_pressed());
208
}
209
210
const Ref<InputEventMouseMotion> mouse_motion = p_event;
211
if (mouse_motion.is_valid()) {
212
_process_drag(100, mouse_motion->get_global_position(), viewport->_get_warped_mouse_motion(mouse_motion));
213
}
214
215
// Touch events
216
const Ref<InputEventScreenTouch> screen_touch = p_event;
217
if (screen_touch.is_valid()) {
218
_process_click(screen_touch->get_index(), screen_touch->get_position(), screen_touch->is_pressed());
219
}
220
221
const Ref<InputEventScreenDrag> screen_drag = p_event;
222
if (screen_drag.is_valid()) {
223
_process_drag(screen_drag->get_index(), screen_drag->get_position(), screen_drag->get_relative());
224
}
225
}
226
227
void ViewportNavigationControl::_update_navigation() {
228
if (focused_index == -1) {
229
return;
230
}
231
232
Vector2 delta = focused_pos - (get_size() / 2.0);
233
Vector2 delta_normalized = delta.normalized();
234
switch (nav_mode) {
235
case Node3DEditorViewport::NavigationMode::NAVIGATION_MOVE: {
236
real_t speed_multiplier = MIN(delta.length() / (get_size().x * 100.0), 3.0);
237
real_t speed = viewport->freelook_speed * speed_multiplier;
238
239
const Node3DEditorViewport::FreelookNavigationScheme navigation_scheme = (Node3DEditorViewport::FreelookNavigationScheme)EDITOR_GET("editors/3d/freelook/freelook_navigation_scheme").operator int();
240
241
Vector3 forward;
242
if (navigation_scheme == Node3DEditorViewport::FreelookNavigationScheme::FREELOOK_FULLY_AXIS_LOCKED) {
243
// Forward/backward keys will always go straight forward/backward, never moving on the Y axis.
244
forward = Vector3(0, 0, delta_normalized.y).rotated(Vector3(0, 1, 0), viewport->camera->get_rotation().y);
245
} else {
246
// Forward/backward keys will be relative to the camera pitch.
247
forward = viewport->camera->get_transform().basis.xform(Vector3(0, 0, delta_normalized.y));
248
}
249
250
const Vector3 right = viewport->camera->get_transform().basis.xform(Vector3(delta_normalized.x, 0, 0));
251
252
const Vector3 direction = forward + right;
253
const Vector3 motion = direction * speed;
254
viewport->cursor.pos += motion;
255
viewport->cursor.eye_pos += motion;
256
} break;
257
258
case Node3DEditorViewport::NavigationMode::NAVIGATION_LOOK: {
259
real_t speed_multiplier = MIN(delta.length() / (get_size().x * 2.5), 3.0);
260
real_t speed = viewport->freelook_speed * speed_multiplier;
261
viewport->_nav_look(nullptr, delta_normalized * speed);
262
} break;
263
264
case Node3DEditorViewport::NAVIGATION_PAN: {
265
real_t speed_multiplier = MIN(delta.length() / (get_size().x), 3.0);
266
real_t speed = viewport->freelook_speed * speed_multiplier;
267
viewport->_nav_pan(nullptr, -delta_normalized * speed);
268
} break;
269
case Node3DEditorViewport::NAVIGATION_ZOOM: {
270
real_t speed_multiplier = MIN(delta.length() / (get_size().x), 3.0);
271
real_t speed = viewport->freelook_speed * speed_multiplier;
272
viewport->_nav_zoom(nullptr, delta_normalized * speed);
273
} break;
274
case Node3DEditorViewport::NAVIGATION_ORBIT: {
275
real_t speed_multiplier = MIN(delta.length() / (get_size().x), 3.0);
276
real_t speed = viewport->freelook_speed * speed_multiplier;
277
viewport->_nav_orbit(nullptr, delta_normalized * speed);
278
} break;
279
case Node3DEditorViewport::NAVIGATION_NONE: {
280
} break;
281
}
282
}
283
284
void ViewportNavigationControl::set_navigation_mode(Node3DEditorViewport::NavigationMode p_nav_mode) {
285
nav_mode = p_nav_mode;
286
}
287
288
void ViewportNavigationControl::set_viewport(Node3DEditorViewport *p_viewport) {
289
viewport = p_viewport;
290
}
291
292
void ViewportRotationControl::_notification(int p_what) {
293
switch (p_what) {
294
case NOTIFICATION_ENTER_TREE: {
295
axis_menu_options.clear();
296
axis_menu_options.push_back(Node3DEditorViewport::VIEW_RIGHT);
297
axis_menu_options.push_back(Node3DEditorViewport::VIEW_TOP);
298
axis_menu_options.push_back(Node3DEditorViewport::VIEW_FRONT);
299
axis_menu_options.push_back(Node3DEditorViewport::VIEW_LEFT);
300
axis_menu_options.push_back(Node3DEditorViewport::VIEW_BOTTOM);
301
axis_menu_options.push_back(Node3DEditorViewport::VIEW_REAR);
302
303
axis_colors.clear();
304
axis_colors.push_back(get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor)));
305
axis_colors.push_back(get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor)));
306
axis_colors.push_back(get_theme_color(SNAME("axis_z_color"), EditorStringName(Editor)));
307
queue_redraw();
308
} break;
309
310
case NOTIFICATION_DRAW: {
311
if (viewport != nullptr) {
312
_draw();
313
}
314
} break;
315
316
case NOTIFICATION_MOUSE_EXIT: {
317
focused_axis = -2;
318
queue_redraw();
319
} break;
320
321
case NOTIFICATION_WM_WINDOW_FOCUS_OUT: {
322
gizmo_activated = false;
323
} break;
324
}
325
}
326
327
void ViewportRotationControl::_draw() {
328
const Vector2 center = get_size() / 2.0;
329
const real_t radius = get_size().x / 2.0;
330
331
if (focused_axis > -2 || orbiting_index != -1) {
332
draw_circle(center, radius, Color(0.5, 0.5, 0.5, 0.25), true, -1.0, true);
333
}
334
335
Vector<Axis2D> axis_to_draw;
336
_get_sorted_axis(axis_to_draw);
337
for (int i = 0; i < axis_to_draw.size(); ++i) {
338
_draw_axis(axis_to_draw[i]);
339
}
340
}
341
342
void ViewportRotationControl::_draw_axis(const Axis2D &p_axis) {
343
const bool focused = focused_axis == p_axis.axis;
344
const bool positive = p_axis.axis < 3;
345
const int direction = p_axis.axis % 3;
346
347
const Color axis_color = axis_colors[direction];
348
const double min_alpha = 0.35;
349
const double alpha = focused ? 1.0 : Math::remap((p_axis.z_axis + 1.0) / 2.0, 0, 0.5, min_alpha, 1.0);
350
const Color c = focused ? Color(axis_color.lightened(0.75), 1.0) : Color(axis_color, alpha);
351
352
if (positive) {
353
// Draw axis lines for the positive axes.
354
const Vector2 center = get_size() / 2.0;
355
const Vector2 diff = p_axis.screen_point - center;
356
const float line_length = MAX(diff.length() - AXIS_CIRCLE_RADIUS - 0.5 * EDSCALE, 0);
357
358
draw_line(center + diff.limit_length(0.5 * EDSCALE), center + diff.limit_length(line_length), c, 1.5 * EDSCALE, true);
359
360
draw_circle(p_axis.screen_point, AXIS_CIRCLE_RADIUS, c, true, -1.0, true);
361
362
// Draw the axis letter for the positive axes.
363
const String axis_name = direction == 0 ? "X" : (direction == 1 ? "Y" : "Z");
364
const Ref<Font> &font = get_theme_font(SNAME("rotation_control"), EditorStringName(EditorFonts));
365
const int font_size = get_theme_font_size(SNAME("rotation_control_size"), EditorStringName(EditorFonts));
366
const Size2 char_size = font->get_char_size(axis_name[0], font_size);
367
const Vector2 char_offset = Vector2(-char_size.width / 2.0, char_size.height * 0.25);
368
draw_char(font, p_axis.screen_point + char_offset, axis_name, font_size, Color(0.0, 0.0, 0.0, alpha * 0.6));
369
} else {
370
// Draw an outline around the negative axes.
371
draw_circle(p_axis.screen_point, AXIS_CIRCLE_RADIUS, c, true, -1.0, true);
372
draw_circle(p_axis.screen_point, AXIS_CIRCLE_RADIUS * 0.8, c.darkened(0.4), true, -1.0, true);
373
}
374
}
375
376
void ViewportRotationControl::_get_sorted_axis(Vector<Axis2D> &r_axis) {
377
const Vector2 center = get_size() / 2.0;
378
const real_t radius = get_size().x / 2.0 - AXIS_CIRCLE_RADIUS - 2.0 * EDSCALE;
379
const Basis camera_basis = viewport->to_camera_transform(viewport->cursor).get_basis().inverse();
380
381
for (int i = 0; i < 3; ++i) {
382
Vector3 axis_3d = camera_basis.get_column(i);
383
Vector2 axis_vector = Vector2(axis_3d.x, -axis_3d.y) * radius;
384
385
if (Math::abs(axis_3d.z) <= 1.0) {
386
Axis2D pos_axis;
387
pos_axis.axis = i;
388
pos_axis.screen_point = center + axis_vector;
389
pos_axis.z_axis = axis_3d.z;
390
r_axis.push_back(pos_axis);
391
392
Axis2D neg_axis;
393
neg_axis.axis = i + 3;
394
neg_axis.screen_point = center - axis_vector;
395
neg_axis.z_axis = -axis_3d.z;
396
r_axis.push_back(neg_axis);
397
} else {
398
// Special case when the camera is aligned with one axis
399
Axis2D axis;
400
axis.axis = i + (axis_3d.z <= 0 ? 0 : 3);
401
axis.screen_point = center;
402
axis.z_axis = 1.0;
403
r_axis.push_back(axis);
404
}
405
}
406
407
r_axis.sort_custom<Axis2DCompare>();
408
}
409
410
void ViewportRotationControl::_process_click(int p_index, Vector2 p_position, bool p_pressed) {
411
if (orbiting_index != -1 && orbiting_index != p_index) {
412
return;
413
}
414
if (p_pressed) {
415
if (p_position.distance_to(get_size() / 2.0) < get_size().x / 2.0) {
416
orbiting_index = p_index;
417
}
418
} else {
419
if (focused_axis > -1 && gizmo_activated) {
420
viewport->_menu_option(axis_menu_options[focused_axis]);
421
_update_focus();
422
}
423
orbiting_index = -1;
424
if (Input::get_singleton()->get_mouse_mode() == Input::MOUSE_MODE_CAPTURED) {
425
Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE);
426
Input::get_singleton()->warp_mouse(orbiting_mouse_start);
427
}
428
}
429
}
430
431
void ViewportRotationControl::_process_drag(Ref<InputEventWithModifiers> p_event, int p_index, Vector2 p_position, Vector2 p_relative_position) {
432
Point2 mouse_pos = get_local_mouse_position();
433
const bool movement_threshold_passed = original_mouse_pos.distance_to(mouse_pos) > 4 * EDSCALE;
434
if (orbiting_index == p_index && gizmo_activated && movement_threshold_passed) {
435
if (Input::get_singleton()->get_mouse_mode() == Input::MOUSE_MODE_VISIBLE) {
436
Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_CAPTURED);
437
orbiting_mouse_start = p_position;
438
viewport->previous_cursor = viewport->cursor;
439
}
440
viewport->_nav_orbit(p_event, p_relative_position);
441
focused_axis = -1;
442
} else {
443
_update_focus();
444
}
445
}
446
447
void ViewportRotationControl::gui_input(const Ref<InputEvent> &p_event) {
448
ERR_FAIL_COND(p_event.is_null());
449
450
// Key events
451
const Ref<InputEventKey> k = p_event;
452
453
if (k.is_valid() && k->is_action_pressed(SNAME("ui_cancel"), false, true)) {
454
if (Input::get_singleton()->get_mouse_mode() == Input::MOUSE_MODE_CAPTURED) {
455
Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE);
456
Input::get_singleton()->warp_mouse(orbiting_mouse_start);
457
viewport->cursor = viewport->previous_cursor;
458
gizmo_activated = false;
459
}
460
}
461
462
// Mouse events
463
const Ref<InputEventMouseButton> mb = p_event;
464
if (mb.is_valid()) {
465
if (mb->get_button_index() == MouseButton::LEFT) {
466
_process_click(100, mb->get_position(), mb->is_pressed());
467
if (mb->is_pressed()) {
468
gizmo_activated = true;
469
original_mouse_pos = get_local_mouse_position();
470
grab_focus();
471
}
472
} else if (mb->get_button_index() == MouseButton::RIGHT) {
473
if (Input::get_singleton()->get_mouse_mode() == Input::MOUSE_MODE_CAPTURED) {
474
Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE);
475
Input::get_singleton()->warp_mouse(orbiting_mouse_start);
476
viewport->cursor = viewport->previous_cursor;
477
gizmo_activated = false;
478
}
479
}
480
}
481
482
const Ref<InputEventMouseMotion> mm = p_event;
483
if (mm.is_valid()) {
484
_process_drag(mm, 100, mm->get_global_position(), viewport->_get_warped_mouse_motion(mm));
485
}
486
487
// Touch events
488
const Ref<InputEventScreenTouch> screen_touch = p_event;
489
if (screen_touch.is_valid()) {
490
_process_click(screen_touch->get_index(), screen_touch->get_position(), screen_touch->is_pressed());
491
}
492
493
const Ref<InputEventScreenDrag> screen_drag = p_event;
494
if (screen_drag.is_valid()) {
495
_process_drag(screen_drag, screen_drag->get_index(), screen_drag->get_position(), screen_drag->get_relative());
496
}
497
}
498
499
void ViewportRotationControl::_update_focus() {
500
int original_focus = focused_axis;
501
focused_axis = -2;
502
Vector2 mouse_pos = get_local_mouse_position();
503
504
if (mouse_pos.distance_to(get_size() / 2.0) < get_size().x / 2.0) {
505
focused_axis = -1;
506
}
507
508
Vector<Axis2D> axes;
509
_get_sorted_axis(axes);
510
511
for (int i = 0; i < axes.size(); i++) {
512
const Axis2D &axis = axes[i];
513
if (mouse_pos.distance_to(axis.screen_point) < AXIS_CIRCLE_RADIUS) {
514
focused_axis = axis.axis;
515
}
516
}
517
518
if (focused_axis != original_focus) {
519
queue_redraw();
520
}
521
}
522
523
void ViewportRotationControl::set_viewport(Node3DEditorViewport *p_viewport) {
524
viewport = p_viewport;
525
}
526
527
void Node3DEditorViewport::_view_settings_confirmed(real_t p_interp_delta) {
528
// Set FOV override multiplier back to the default, so that the FOV
529
// setting specified in the View menu is correctly applied.
530
cursor.fov_scale = 1.0;
531
532
_update_camera(p_interp_delta);
533
}
534
535
void Node3DEditorViewport::_update_navigation_controls_visibility() {
536
bool show_viewport_rotation_gizmo = EDITOR_GET("editors/3d/navigation/show_viewport_rotation_gizmo") && (!previewing_cinema && !previewing_camera);
537
rotation_control->set_visible(show_viewport_rotation_gizmo);
538
539
bool show_viewport_navigation_gizmo = EDITOR_GET("editors/3d/navigation/show_viewport_navigation_gizmo") && (!previewing_cinema && !previewing_camera);
540
position_control->set_visible(show_viewport_navigation_gizmo);
541
look_control->set_visible(show_viewport_navigation_gizmo);
542
}
543
544
void Node3DEditorViewport::_update_camera(real_t p_interp_delta) {
545
bool is_orthogonal = camera->get_projection() == Camera3D::PROJECTION_ORTHOGONAL;
546
547
Cursor old_camera_cursor = camera_cursor;
548
camera_cursor = cursor;
549
550
if (p_interp_delta > 0) {
551
//-------
552
// Perform smoothing
553
554
if (is_freelook_active()) {
555
// Higher inertia should increase "lag" (lerp with factor between 0 and 1)
556
// Inertia of zero should produce instant movement (lerp with factor of 1) in this case it returns a really high value and gets clamped to 1.
557
const real_t inertia = EDITOR_GET("editors/3d/freelook/freelook_inertia");
558
real_t factor = (1.0 / inertia) * p_interp_delta;
559
560
// We interpolate a different point here, because in freelook mode the focus point (cursor.pos) orbits around eye_pos
561
camera_cursor.eye_pos = old_camera_cursor.eye_pos.lerp(cursor.eye_pos, CLAMP(factor, 0, 1));
562
563
const real_t orbit_inertia = EDITOR_GET("editors/3d/navigation_feel/orbit_inertia");
564
camera_cursor.x_rot = Math::lerp(old_camera_cursor.x_rot, cursor.x_rot, MIN(1.f, p_interp_delta * (1 / orbit_inertia)));
565
camera_cursor.y_rot = Math::lerp(old_camera_cursor.y_rot, cursor.y_rot, MIN(1.f, p_interp_delta * (1 / orbit_inertia)));
566
567
if (Math::abs(camera_cursor.x_rot - cursor.x_rot) < 0.1) {
568
camera_cursor.x_rot = cursor.x_rot;
569
}
570
571
if (Math::abs(camera_cursor.y_rot - cursor.y_rot) < 0.1) {
572
camera_cursor.y_rot = cursor.y_rot;
573
}
574
575
Vector3 forward = to_camera_transform(camera_cursor).basis.xform(Vector3(0, 0, -1));
576
camera_cursor.pos = camera_cursor.eye_pos + forward * camera_cursor.distance;
577
578
} else {
579
const real_t orbit_inertia = EDITOR_GET("editors/3d/navigation_feel/orbit_inertia");
580
const real_t translation_inertia = EDITOR_GET("editors/3d/navigation_feel/translation_inertia");
581
const real_t zoom_inertia = EDITOR_GET("editors/3d/navigation_feel/zoom_inertia");
582
583
camera_cursor.x_rot = Math::lerp(old_camera_cursor.x_rot, cursor.x_rot, MIN(1.f, p_interp_delta * (1 / orbit_inertia)));
584
camera_cursor.y_rot = Math::lerp(old_camera_cursor.y_rot, cursor.y_rot, MIN(1.f, p_interp_delta * (1 / orbit_inertia)));
585
586
if (Math::abs(camera_cursor.x_rot - cursor.x_rot) < 0.1) {
587
camera_cursor.x_rot = cursor.x_rot;
588
}
589
590
if (Math::abs(camera_cursor.y_rot - cursor.y_rot) < 0.1) {
591
camera_cursor.y_rot = cursor.y_rot;
592
}
593
594
camera_cursor.pos = old_camera_cursor.pos.lerp(cursor.pos, MIN(1.f, p_interp_delta * (1 / translation_inertia)));
595
camera_cursor.distance = Math::lerp(old_camera_cursor.distance, cursor.distance, MIN((real_t)1.0, p_interp_delta * (1 / zoom_inertia)));
596
}
597
}
598
599
//-------
600
// Apply camera transform
601
602
real_t tolerance = 0.001;
603
bool equal = true;
604
if (!Math::is_equal_approx(old_camera_cursor.x_rot, camera_cursor.x_rot, tolerance) || !Math::is_equal_approx(old_camera_cursor.y_rot, camera_cursor.y_rot, tolerance)) {
605
equal = false;
606
} else if (!old_camera_cursor.pos.is_equal_approx(camera_cursor.pos)) {
607
equal = false;
608
} else if (!Math::is_equal_approx(old_camera_cursor.distance, camera_cursor.distance, tolerance)) {
609
equal = false;
610
} else if (!Math::is_equal_approx(old_camera_cursor.fov_scale, camera_cursor.fov_scale, tolerance)) {
611
equal = false;
612
}
613
614
if (!equal || p_interp_delta == 0 || is_orthogonal != orthogonal) {
615
last_camera_transform = to_camera_transform(camera_cursor);
616
camera->set_global_transform(last_camera_transform);
617
618
if (orthogonal) {
619
float half_fov = Math::deg_to_rad(get_fov()) / 2.0;
620
float height = 2.0 * cursor.distance * Math::tan(half_fov);
621
camera->set_orthogonal(height, get_znear(), get_zfar());
622
} else {
623
camera->set_perspective(get_fov(), get_znear(), get_zfar());
624
}
625
626
update_transform_gizmo_view();
627
rotation_control->queue_redraw();
628
position_control->queue_redraw();
629
look_control->queue_redraw();
630
spatial_editor->update_grid();
631
}
632
}
633
634
Transform3D Node3DEditorViewport::to_camera_transform(const Cursor &p_cursor) const {
635
Transform3D camera_transform;
636
camera_transform.translate_local(p_cursor.pos);
637
camera_transform.basis.rotate(Vector3(1, 0, 0), -p_cursor.x_rot);
638
camera_transform.basis.rotate(Vector3(0, 1, 0), -p_cursor.y_rot);
639
640
if (orthogonal) {
641
camera_transform.translate_local(0, 0, (get_zfar() - get_znear()) / 2.0);
642
} else {
643
camera_transform.translate_local(0, 0, p_cursor.distance);
644
}
645
646
return camera_transform;
647
}
648
649
int Node3DEditorViewport::get_selected_count() const {
650
const HashMap<Node *, Object *> &selection = editor_selection->get_selection();
651
652
int count = 0;
653
654
for (const KeyValue<Node *, Object *> &E : selection) {
655
Node3D *sp = Object::cast_to<Node3D>(E.key);
656
if (!sp) {
657
continue;
658
}
659
660
Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);
661
if (!se) {
662
continue;
663
}
664
665
count++;
666
}
667
668
return count;
669
}
670
671
void Node3DEditorViewport::cancel_transform() {
672
const List<Node *> &selection = editor_selection->get_top_selected_node_list();
673
674
for (Node *E : selection) {
675
Node3D *sp = Object::cast_to<Node3D>(E);
676
if (!sp) {
677
continue;
678
}
679
680
Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);
681
if (!se) {
682
continue;
683
}
684
685
if (se && se->gizmo.is_valid()) {
686
Vector<int> ids;
687
Vector<Transform3D> restore;
688
689
for (const KeyValue<int, Transform3D> &GE : se->subgizmos) {
690
ids.push_back(GE.key);
691
restore.push_back(GE.value);
692
}
693
694
se->gizmo->commit_subgizmos(ids, restore, true);
695
}
696
697
sp->set_global_transform(se->original);
698
}
699
700
collision_reposition = false;
701
finish_transform();
702
set_message(TTRC("Transform Aborted."), 3);
703
}
704
705
void Node3DEditorViewport::_update_shrink() {
706
bool shrink = view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_HALF_RESOLUTION));
707
subviewport_container->set_stretch_shrink(shrink ? 2 : 1);
708
subviewport_container->set_texture_filter(shrink ? TEXTURE_FILTER_NEAREST : TEXTURE_FILTER_PARENT_NODE);
709
}
710
711
float Node3DEditorViewport::get_znear() const {
712
return CLAMP(spatial_editor->get_znear(), MIN_Z, MAX_Z);
713
}
714
715
float Node3DEditorViewport::get_zfar() const {
716
return CLAMP(spatial_editor->get_zfar(), MIN_Z, MAX_Z);
717
}
718
719
float Node3DEditorViewport::get_fov() const {
720
return CLAMP(spatial_editor->get_fov() * cursor.fov_scale, MIN_FOV, MAX_FOV);
721
}
722
723
Transform3D Node3DEditorViewport::_get_camera_transform() const {
724
return camera->get_global_transform();
725
}
726
727
Vector3 Node3DEditorViewport::_get_camera_position() const {
728
return _get_camera_transform().origin;
729
}
730
731
Point2 Node3DEditorViewport::point_to_screen(const Vector3 &p_point) {
732
return camera->unproject_position(p_point) * subviewport_container->get_stretch_shrink();
733
}
734
735
Vector3 Node3DEditorViewport::get_ray_pos(const Vector2 &p_pos) const {
736
return camera->project_ray_origin(p_pos / subviewport_container->get_stretch_shrink());
737
}
738
739
Vector3 Node3DEditorViewport::_get_camera_normal() const {
740
return -_get_camera_transform().basis.get_column(2);
741
}
742
743
Vector3 Node3DEditorViewport::get_ray(const Vector2 &p_pos) const {
744
return camera->project_ray_normal(p_pos / subviewport_container->get_stretch_shrink());
745
}
746
747
void Node3DEditorViewport::_clear_selected() {
748
_edit.gizmo = Ref<EditorNode3DGizmo>();
749
_edit.gizmo_handle = -1;
750
_edit.gizmo_handle_secondary = false;
751
_edit.gizmo_initial_value = Variant();
752
753
Node3D *selected = spatial_editor->get_single_selected_node();
754
Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr;
755
756
if (se && se->gizmo.is_valid()) {
757
se->subgizmos.clear();
758
se->gizmo->redraw();
759
se->gizmo.unref();
760
spatial_editor->update_transform_gizmo();
761
} else {
762
editor_selection->clear();
763
Node3DEditor::get_singleton()->edit(nullptr);
764
}
765
}
766
767
void Node3DEditorViewport::_select_clicked(bool p_allow_locked) {
768
Node *node = ObjectDB::get_instance<Node3D>(clicked);
769
Node3D *selected = Object::cast_to<Node3D>(node);
770
clicked = ObjectID();
771
772
if (!selected) {
773
return;
774
}
775
776
Node *edited_scene = EditorNode::get_singleton()->get_edited_scene();
777
778
// Prevent selection of nodes not owned by the edited scene.
779
while (node && node != edited_scene->get_parent()) {
780
Node *node_owner = node->get_owner();
781
if (node_owner == edited_scene || node == edited_scene || (node_owner != nullptr && edited_scene->is_editable_instance(node_owner))) {
782
break;
783
}
784
node = node->get_parent();
785
selected = Object::cast_to<Node3D>(node);
786
}
787
788
if (!p_allow_locked) {
789
// Replace the node by the group if grouped
790
while (node && node != edited_scene->get_parent()) {
791
Node3D *selected_tmp = Object::cast_to<Node3D>(node);
792
if (selected_tmp && node->has_meta("_edit_group_")) {
793
selected = selected_tmp;
794
}
795
node = node->get_parent();
796
}
797
}
798
799
if (p_allow_locked || (selected != nullptr && !_is_node_locked(selected))) {
800
if (clicked_wants_append) {
801
const List<Node *> &top_node_list = editor_selection->get_top_selected_node_list();
802
const Node *active_node = top_node_list.is_empty() ? nullptr : top_node_list.back()->get();
803
if (editor_selection->is_selected(selected)) {
804
editor_selection->remove_node(selected);
805
if (selected != active_node) {
806
editor_selection->add_node(selected);
807
}
808
} else {
809
editor_selection->add_node(selected);
810
}
811
} else {
812
if (!editor_selection->is_selected(selected)) {
813
editor_selection->clear();
814
editor_selection->add_node(selected);
815
EditorNode::get_singleton()->edit_node(selected);
816
}
817
}
818
819
const List<Node *> &top_node_list = editor_selection->get_top_selected_node_list();
820
if (top_node_list.size() == 1) {
821
EditorNode::get_singleton()->edit_node(top_node_list.front()->get());
822
}
823
}
824
}
825
826
ObjectID Node3DEditorViewport::_select_ray(const Point2 &p_pos) const {
827
Vector3 ray = get_ray(p_pos);
828
Vector3 pos = get_ray_pos(p_pos);
829
Vector2 shrinked_pos = p_pos / subviewport_container->get_stretch_shrink();
830
831
if (viewport->get_debug_draw() == Viewport::DEBUG_DRAW_SDFGI_PROBES) {
832
RS::get_singleton()->sdfgi_set_debug_probe_select(pos, ray);
833
}
834
835
HashSet<Ref<EditorNode3DGizmo>> found_gizmos;
836
837
Node *edited_scene = get_tree()->get_edited_scene_root();
838
ObjectID closest;
839
Node *item = nullptr;
840
float closest_dist = 1e20;
841
842
Vector<Node3D *> nodes_with_gizmos = Node3DEditor::get_singleton()->gizmo_bvh_ray_query(pos, pos + ray * camera->get_far());
843
844
for (Node3D *spat : nodes_with_gizmos) {
845
if (!spat || _is_node_locked(spat)) {
846
continue;
847
}
848
849
Vector<Ref<Node3DGizmo>> gizmos = spat->get_gizmos();
850
851
for (int j = 0; j < gizmos.size(); j++) {
852
Ref<EditorNode3DGizmo> seg = gizmos[j];
853
854
if (seg.is_null() || found_gizmos.has(seg)) {
855
continue;
856
}
857
858
found_gizmos.insert(seg);
859
Vector3 point;
860
Vector3 normal;
861
862
bool inters = seg->intersect_ray(camera, shrinked_pos, point, normal);
863
864
if (!inters) {
865
continue;
866
}
867
868
const real_t dist = pos.distance_to(point);
869
870
if (dist < 0) {
871
continue;
872
}
873
874
if (dist < closest_dist) {
875
item = Object::cast_to<Node>(spat);
876
if (item != edited_scene) {
877
item = edited_scene->get_deepest_editable_node(item);
878
}
879
880
closest = item->get_instance_id();
881
closest_dist = dist;
882
}
883
}
884
}
885
886
if (!item) {
887
return ObjectID();
888
}
889
890
return closest;
891
}
892
893
void Node3DEditorViewport::_find_items_at_pos(const Point2 &p_pos, Vector<_RayResult> &r_results, bool p_include_locked_nodes) {
894
Vector3 ray = get_ray(p_pos);
895
Vector3 pos = get_ray_pos(p_pos);
896
897
Vector<Node3D *> nodes_with_gizmos = Node3DEditor::get_singleton()->gizmo_bvh_ray_query(pos, pos + ray * camera->get_far());
898
899
HashSet<Node3D *> found_nodes;
900
901
for (Node3D *spat : nodes_with_gizmos) {
902
if (!spat) {
903
continue;
904
}
905
906
if (found_nodes.has(spat)) {
907
continue;
908
}
909
910
if (!p_include_locked_nodes && _is_node_locked(spat)) {
911
continue;
912
}
913
914
Vector<Ref<Node3DGizmo>> gizmos = spat->get_gizmos();
915
for (int j = 0; j < gizmos.size(); j++) {
916
Ref<EditorNode3DGizmo> seg = gizmos[j];
917
918
if (seg.is_null()) {
919
continue;
920
}
921
922
Vector3 point;
923
Vector3 normal;
924
925
bool inters = seg->intersect_ray(camera, p_pos, point, normal);
926
927
if (!inters) {
928
continue;
929
}
930
931
const real_t dist = pos.distance_to(point);
932
933
if (dist < 0) {
934
continue;
935
}
936
937
found_nodes.insert(spat);
938
939
_RayResult res;
940
res.item = spat;
941
res.depth = dist;
942
r_results.push_back(res);
943
break;
944
}
945
}
946
947
r_results.sort();
948
}
949
950
Vector3 Node3DEditorViewport::_get_screen_to_space(const Vector3 &p_vector3) {
951
Projection cm;
952
if (orthogonal) {
953
cm.set_orthogonal(camera->get_size(), get_size().aspect(), get_znear() + p_vector3.z, get_zfar());
954
} else {
955
cm.set_perspective(get_fov(), get_size().aspect(), get_znear() + p_vector3.z, get_zfar());
956
}
957
Vector2 screen_he = cm.get_viewport_half_extents();
958
959
Transform3D camera_transform;
960
camera_transform.translate_local(cursor.pos);
961
camera_transform.basis.rotate(Vector3(1, 0, 0), -cursor.x_rot);
962
camera_transform.basis.rotate(Vector3(0, 1, 0), -cursor.y_rot);
963
camera_transform.translate_local(0, 0, cursor.distance);
964
965
return camera_transform.xform(Vector3(((p_vector3.x / get_size().width) * 2.0 - 1.0) * screen_he.x, ((1.0 - (p_vector3.y / get_size().height)) * 2.0 - 1.0) * screen_he.y, -(get_znear() + p_vector3.z)));
966
}
967
968
void Node3DEditorViewport::_select_region() {
969
if (cursor.region_begin == cursor.region_end) {
970
if (!clicked_wants_append) {
971
_clear_selected();
972
}
973
return; //nothing really
974
}
975
976
const real_t z_offset = MAX(0.0, 5.0 - get_znear());
977
978
Vector3 box[4] = {
979
Vector3(
980
MIN(cursor.region_begin.x, cursor.region_end.x),
981
MIN(cursor.region_begin.y, cursor.region_end.y),
982
z_offset),
983
Vector3(
984
MAX(cursor.region_begin.x, cursor.region_end.x),
985
MIN(cursor.region_begin.y, cursor.region_end.y),
986
z_offset),
987
Vector3(
988
MAX(cursor.region_begin.x, cursor.region_end.x),
989
MAX(cursor.region_begin.y, cursor.region_end.y),
990
z_offset),
991
Vector3(
992
MIN(cursor.region_begin.x, cursor.region_end.x),
993
MAX(cursor.region_begin.y, cursor.region_end.y),
994
z_offset)
995
};
996
997
Vector<Plane> frustum;
998
999
Vector3 cam_pos = _get_camera_position();
1000
1001
for (int i = 0; i < 4; i++) {
1002
Vector3 a = _get_screen_to_space(box[i]);
1003
Vector3 b = _get_screen_to_space(box[(i + 1) % 4]);
1004
if (orthogonal) {
1005
frustum.push_back(Plane((a - b).normalized(), a));
1006
} else {
1007
frustum.push_back(Plane(a, b, cam_pos));
1008
}
1009
}
1010
1011
Plane near_plane = Plane(-_get_camera_normal(), cam_pos);
1012
near_plane.d -= get_znear();
1013
frustum.push_back(near_plane);
1014
1015
Plane far_plane = -near_plane;
1016
far_plane.d += get_zfar();
1017
frustum.push_back(far_plane);
1018
1019
if (spatial_editor->get_single_selected_node()) {
1020
Node3D *single_selected = spatial_editor->get_single_selected_node();
1021
Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(single_selected);
1022
1023
if (se) {
1024
Ref<EditorNode3DGizmo> old_gizmo;
1025
if (!clicked_wants_append) {
1026
se->subgizmos.clear();
1027
old_gizmo = se->gizmo;
1028
se->gizmo.unref();
1029
}
1030
1031
bool found_subgizmos = false;
1032
Vector<Ref<Node3DGizmo>> gizmos = single_selected->get_gizmos();
1033
for (int j = 0; j < gizmos.size(); j++) {
1034
Ref<EditorNode3DGizmo> seg = gizmos[j];
1035
if (seg.is_null()) {
1036
continue;
1037
}
1038
1039
if (se->gizmo.is_valid() && se->gizmo != seg) {
1040
continue;
1041
}
1042
1043
Vector<int> subgizmos = seg->subgizmos_intersect_frustum(camera, frustum);
1044
if (!subgizmos.is_empty()) {
1045
se->gizmo = seg;
1046
for (int i = 0; i < subgizmos.size(); i++) {
1047
int subgizmo_id = subgizmos[i];
1048
if (!se->subgizmos.has(subgizmo_id)) {
1049
se->subgizmos.insert(subgizmo_id, se->gizmo->get_subgizmo_transform(subgizmo_id));
1050
}
1051
}
1052
found_subgizmos = true;
1053
break;
1054
}
1055
}
1056
1057
if (!clicked_wants_append || found_subgizmos) {
1058
if (se->gizmo.is_valid()) {
1059
se->gizmo->redraw();
1060
}
1061
1062
if (old_gizmo != se->gizmo && old_gizmo.is_valid()) {
1063
old_gizmo->redraw();
1064
}
1065
1066
spatial_editor->update_transform_gizmo();
1067
}
1068
1069
if (found_subgizmos) {
1070
return;
1071
}
1072
}
1073
}
1074
1075
if (!clicked_wants_append) {
1076
_clear_selected();
1077
}
1078
1079
Vector<Node3D *> nodes_with_gizmos = Node3DEditor::get_singleton()->gizmo_bvh_frustum_query(frustum);
1080
HashSet<Node3D *> found_nodes;
1081
Vector<Node *> selected;
1082
1083
Node *edited_scene = get_tree()->get_edited_scene_root();
1084
if (edited_scene == nullptr) {
1085
return;
1086
}
1087
1088
for (Node3D *sp : nodes_with_gizmos) {
1089
if (!sp || _is_node_locked(sp)) {
1090
continue;
1091
}
1092
1093
if (found_nodes.has(sp)) {
1094
continue;
1095
}
1096
found_nodes.insert(sp);
1097
1098
Node *node = Object::cast_to<Node>(sp);
1099
1100
// Selection requires that the node is the edited scene or its descendant, and has an owner.
1101
if (node != edited_scene) {
1102
if (!node->get_owner() || !edited_scene->is_ancestor_of(node)) {
1103
continue;
1104
}
1105
node = edited_scene->get_deepest_editable_node(node);
1106
while (node != edited_scene) {
1107
Node *node_owner = node->get_owner();
1108
if (node_owner == edited_scene || (node_owner != nullptr && edited_scene->is_editable_instance(node_owner))) {
1109
break;
1110
}
1111
node = node->get_parent();
1112
}
1113
}
1114
1115
// Replace the node by the group if grouped
1116
if (node->is_class("Node3D")) {
1117
Node3D *sel = Object::cast_to<Node3D>(node);
1118
while (node && node != EditorNode::get_singleton()->get_edited_scene()->get_parent()) {
1119
Node3D *selected_tmp = Object::cast_to<Node3D>(node);
1120
if (selected_tmp && node->has_meta("_edit_group_")) {
1121
sel = selected_tmp;
1122
}
1123
node = node->get_parent();
1124
}
1125
node = sel;
1126
}
1127
1128
if (_is_node_locked(node)) {
1129
continue;
1130
}
1131
1132
Vector<Ref<Node3DGizmo>> gizmos = sp->get_gizmos();
1133
for (int j = 0; j < gizmos.size(); j++) {
1134
Ref<EditorNode3DGizmo> seg = gizmos[j];
1135
if (seg.is_null()) {
1136
continue;
1137
}
1138
1139
if (seg->intersect_frustum(camera, frustum)) {
1140
selected.push_back(node);
1141
}
1142
}
1143
}
1144
1145
for (int i = 0; i < selected.size(); i++) {
1146
if (!editor_selection->is_selected(selected[i])) {
1147
editor_selection->add_node(selected[i]);
1148
}
1149
}
1150
1151
const List<Node *> &top_node_list = editor_selection->get_top_selected_node_list();
1152
if (top_node_list.size() == 1) {
1153
EditorNode::get_singleton()->edit_node(top_node_list.front()->get());
1154
}
1155
}
1156
1157
void Node3DEditorViewport::_update_name() {
1158
String name;
1159
1160
switch (view_type) {
1161
case VIEW_TYPE_USER: {
1162
if (orthogonal) {
1163
name = TTR("Orthogonal");
1164
} else {
1165
name = TTR("Perspective");
1166
}
1167
} break;
1168
case VIEW_TYPE_TOP: {
1169
if (orthogonal) {
1170
name = TTR("Top Orthogonal");
1171
} else {
1172
name = TTR("Top Perspective");
1173
}
1174
} break;
1175
case VIEW_TYPE_BOTTOM: {
1176
if (orthogonal) {
1177
name = TTR("Bottom Orthogonal");
1178
} else {
1179
name = TTR("Bottom Perspective");
1180
}
1181
} break;
1182
case VIEW_TYPE_LEFT: {
1183
if (orthogonal) {
1184
name = TTR("Left Orthogonal");
1185
} else {
1186
name = TTR("Left Perspective");
1187
}
1188
} break;
1189
case VIEW_TYPE_RIGHT: {
1190
if (orthogonal) {
1191
name = TTR("Right Orthogonal");
1192
} else {
1193
name = TTR("Right Perspective");
1194
}
1195
} break;
1196
case VIEW_TYPE_FRONT: {
1197
if (orthogonal) {
1198
name = TTR("Front Orthogonal");
1199
} else {
1200
name = TTR("Front Perspective");
1201
}
1202
} break;
1203
case VIEW_TYPE_REAR: {
1204
if (orthogonal) {
1205
name = TTR("Rear Orthogonal");
1206
} else {
1207
name = TTR("Rear Perspective");
1208
}
1209
} break;
1210
}
1211
1212
if (auto_orthogonal) {
1213
// TRANSLATORS: This will be appended to the view name when Auto Orthogonal is enabled.
1214
name += " " + TTR("[auto]");
1215
}
1216
1217
view_display_menu->set_text(name);
1218
view_display_menu->reset_size();
1219
}
1220
1221
void Node3DEditorViewport::_compute_edit(const Point2 &p_point) {
1222
_edit.original_local = spatial_editor->are_local_coords_enabled();
1223
_edit.click_ray = get_ray(p_point);
1224
_edit.click_ray_pos = get_ray_pos(p_point);
1225
_edit.plane = TRANSFORM_VIEW;
1226
spatial_editor->update_transform_gizmo();
1227
_edit.center = spatial_editor->get_gizmo_transform().origin;
1228
1229
Node3D *selected = spatial_editor->get_single_selected_node();
1230
Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr;
1231
1232
if (se && se->gizmo.is_valid()) {
1233
for (const KeyValue<int, Transform3D> &E : se->subgizmos) {
1234
int subgizmo_id = E.key;
1235
se->subgizmos[subgizmo_id] = se->gizmo->get_subgizmo_transform(subgizmo_id);
1236
}
1237
se->original_local = selected->get_transform();
1238
se->original = selected->get_global_transform();
1239
} else {
1240
const List<Node *> &selection = editor_selection->get_top_selected_node_list();
1241
1242
for (Node *E : selection) {
1243
Node3D *sp = Object::cast_to<Node3D>(E);
1244
if (!sp) {
1245
continue;
1246
}
1247
1248
Node3DEditorSelectedItem *sel_item = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);
1249
1250
if (!sel_item) {
1251
continue;
1252
}
1253
1254
sel_item->original_local = sel_item->sp->get_local_gizmo_transform();
1255
sel_item->original = sel_item->sp->get_global_gizmo_transform();
1256
}
1257
}
1258
}
1259
1260
static Key _get_key_modifier_setting(const String &p_property) {
1261
switch (EDITOR_GET(p_property).operator int()) {
1262
case 0:
1263
return Key::NONE;
1264
case 1:
1265
return Key::SHIFT;
1266
case 2:
1267
return Key::ALT;
1268
case 3:
1269
return Key::META;
1270
case 4:
1271
return Key::CTRL;
1272
}
1273
return Key::NONE;
1274
}
1275
1276
static Key _get_key_modifier(Ref<InputEventWithModifiers> e) {
1277
if (e->is_shift_pressed()) {
1278
return Key::SHIFT;
1279
}
1280
if (e->is_alt_pressed()) {
1281
return Key::ALT;
1282
}
1283
if (e->is_ctrl_pressed()) {
1284
return Key::CTRL;
1285
}
1286
if (e->is_meta_pressed()) {
1287
return Key::META;
1288
}
1289
return Key::NONE;
1290
}
1291
1292
bool Node3DEditorViewport::_transform_gizmo_select(const Vector2 &p_screenpos, bool p_highlight_only) {
1293
if (!spatial_editor->is_gizmo_visible()) {
1294
return false;
1295
}
1296
if (get_selected_count() == 0) {
1297
if (p_highlight_only) {
1298
spatial_editor->select_gizmo_highlight_axis(-1);
1299
}
1300
return false;
1301
}
1302
1303
Vector3 ray_pos = get_ray_pos(p_screenpos);
1304
Vector3 ray = get_ray(p_screenpos);
1305
1306
Transform3D gt = spatial_editor->get_gizmo_transform();
1307
1308
if (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_MOVE) {
1309
int col_axis = -1;
1310
real_t col_d = 1e20;
1311
1312
for (int i = 0; i < 3; i++) {
1313
const Vector3 grabber_pos = gt.origin + gt.basis.get_column(i).normalized() * gizmo_scale * (GIZMO_ARROW_OFFSET + (GIZMO_ARROW_SIZE * 0.5));
1314
const real_t grabber_radius = gizmo_scale * GIZMO_ARROW_SIZE;
1315
1316
Vector3 r;
1317
1318
if (Geometry3D::segment_intersects_sphere(ray_pos, ray_pos + ray * MAX_Z, grabber_pos, grabber_radius, &r)) {
1319
const real_t d = r.distance_to(ray_pos);
1320
if (d < col_d) {
1321
col_d = d;
1322
col_axis = i;
1323
}
1324
}
1325
}
1326
1327
bool is_plane_translate = false;
1328
// plane select
1329
if (col_axis == -1) {
1330
col_d = 1e20;
1331
1332
for (int i = 0; i < 3; i++) {
1333
Vector3 ivec2 = gt.basis.get_column((i + 1) % 3).normalized();
1334
Vector3 ivec3 = gt.basis.get_column((i + 2) % 3).normalized();
1335
1336
// Allow some tolerance to make the plane easier to click,
1337
// even if the click is actually slightly outside the plane.
1338
const Vector3 grabber_pos = gt.origin + (ivec2 + ivec3) * gizmo_scale * (GIZMO_PLANE_SIZE + GIZMO_PLANE_DST * 0.6667);
1339
1340
Vector3 r;
1341
Plane plane(gt.basis.get_column(i).normalized(), gt.origin);
1342
1343
if (plane.intersects_ray(ray_pos, ray, &r)) {
1344
const real_t dist = r.distance_to(grabber_pos);
1345
// Allow some tolerance to make the plane easier to click,
1346
// even if the click is actually slightly outside the plane.
1347
if (dist < (gizmo_scale * GIZMO_PLANE_SIZE * 1.5)) {
1348
const real_t d = ray_pos.distance_to(r);
1349
if (d < col_d) {
1350
col_d = d;
1351
col_axis = i;
1352
1353
is_plane_translate = true;
1354
}
1355
}
1356
}
1357
}
1358
}
1359
1360
if (col_axis != -1) {
1361
if (p_highlight_only) {
1362
spatial_editor->select_gizmo_highlight_axis(col_axis + (is_plane_translate ? 6 : 0));
1363
1364
} else {
1365
//handle plane translate
1366
_edit.mode = TRANSFORM_TRANSLATE;
1367
_compute_edit(p_screenpos);
1368
_edit.plane = TransformPlane(TRANSFORM_X_AXIS + col_axis + (is_plane_translate ? 3 : 0));
1369
}
1370
return true;
1371
}
1372
}
1373
1374
if (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE) {
1375
int col_axis = -1;
1376
1377
Vector3 hit_position;
1378
Vector3 hit_normal;
1379
real_t ray_length = gt.origin.distance_to(ray_pos) + (GIZMO_CIRCLE_SIZE * gizmo_scale) * 4.0f;
1380
if (Geometry3D::segment_intersects_sphere(ray_pos, ray_pos + ray * ray_length, gt.origin, gizmo_scale * (GIZMO_CIRCLE_SIZE), &hit_position, &hit_normal)) {
1381
if (hit_normal.dot(_get_camera_normal()) < 0.05) {
1382
hit_position = gt.xform_inv(hit_position).abs();
1383
int min_axis = hit_position.min_axis_index();
1384
if (hit_position[min_axis] < gizmo_scale * GIZMO_RING_HALF_WIDTH) {
1385
col_axis = min_axis;
1386
}
1387
}
1388
}
1389
1390
if (col_axis == -1) {
1391
float col_d = 1e20;
1392
1393
for (int i = 0; i < 3; i++) {
1394
Plane plane(gt.basis.get_column(i).normalized(), gt.origin);
1395
Vector3 r;
1396
if (!plane.intersects_ray(ray_pos, ray, &r)) {
1397
continue;
1398
}
1399
1400
const real_t dist = r.distance_to(gt.origin);
1401
const Vector3 r_dir = (r - gt.origin).normalized();
1402
1403
if (_get_camera_normal().dot(r_dir) <= 0.005) {
1404
if (dist > gizmo_scale * (GIZMO_CIRCLE_SIZE - GIZMO_RING_HALF_WIDTH) && dist < gizmo_scale * (GIZMO_CIRCLE_SIZE + GIZMO_RING_HALF_WIDTH)) {
1405
const real_t d = ray_pos.distance_to(r);
1406
if (d < col_d) {
1407
col_d = d;
1408
col_axis = i;
1409
}
1410
}
1411
}
1412
}
1413
}
1414
1415
if (col_axis != -1) {
1416
if (p_highlight_only) {
1417
spatial_editor->select_gizmo_highlight_axis(col_axis + 3);
1418
} else {
1419
//handle rotate
1420
_edit.mode = TRANSFORM_ROTATE;
1421
_compute_edit(p_screenpos);
1422
_edit.plane = TransformPlane(TRANSFORM_X_AXIS + col_axis);
1423
}
1424
return true;
1425
}
1426
}
1427
1428
if (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SCALE) {
1429
int col_axis = -1;
1430
float col_d = 1e20;
1431
1432
for (int i = 0; i < 3; i++) {
1433
const Vector3 grabber_pos = gt.origin + gt.basis.get_column(i).normalized() * gizmo_scale * GIZMO_SCALE_OFFSET;
1434
const real_t grabber_radius = gizmo_scale * GIZMO_ARROW_SIZE;
1435
1436
Vector3 r;
1437
1438
if (Geometry3D::segment_intersects_sphere(ray_pos, ray_pos + ray * MAX_Z, grabber_pos, grabber_radius, &r)) {
1439
const real_t d = r.distance_to(ray_pos);
1440
if (d < col_d) {
1441
col_d = d;
1442
col_axis = i;
1443
}
1444
}
1445
}
1446
1447
bool is_plane_scale = false;
1448
// plane select
1449
if (col_axis == -1) {
1450
col_d = 1e20;
1451
1452
for (int i = 0; i < 3; i++) {
1453
const Vector3 ivec2 = gt.basis.get_column((i + 1) % 3).normalized();
1454
const Vector3 ivec3 = gt.basis.get_column((i + 2) % 3).normalized();
1455
1456
// Allow some tolerance to make the plane easier to click,
1457
// even if the click is actually slightly outside the plane.
1458
const Vector3 grabber_pos = gt.origin + (ivec2 + ivec3) * gizmo_scale * (GIZMO_PLANE_SIZE + GIZMO_PLANE_DST * 0.6667);
1459
1460
Vector3 r;
1461
Plane plane(gt.basis.get_column(i).normalized(), gt.origin);
1462
1463
if (plane.intersects_ray(ray_pos, ray, &r)) {
1464
const real_t dist = r.distance_to(grabber_pos);
1465
// Allow some tolerance to make the plane easier to click,
1466
// even if the click is actually slightly outside the plane.
1467
if (dist < (gizmo_scale * GIZMO_PLANE_SIZE * 1.5)) {
1468
const real_t d = ray_pos.distance_to(r);
1469
if (d < col_d) {
1470
col_d = d;
1471
col_axis = i;
1472
1473
is_plane_scale = true;
1474
}
1475
}
1476
}
1477
}
1478
}
1479
1480
if (col_axis != -1) {
1481
if (p_highlight_only) {
1482
spatial_editor->select_gizmo_highlight_axis(col_axis + (is_plane_scale ? 12 : 9));
1483
1484
} else {
1485
//handle scale
1486
_edit.mode = TRANSFORM_SCALE;
1487
_compute_edit(p_screenpos);
1488
_edit.plane = TransformPlane(TRANSFORM_X_AXIS + col_axis + (is_plane_scale ? 3 : 0));
1489
}
1490
return true;
1491
}
1492
}
1493
1494
if (p_highlight_only) {
1495
spatial_editor->select_gizmo_highlight_axis(-1);
1496
}
1497
1498
return false;
1499
}
1500
1501
void Node3DEditorViewport::_transform_gizmo_apply(Node3D *p_node, const Transform3D &p_transform, bool p_local) {
1502
if (p_transform.basis.determinant() == 0) {
1503
return;
1504
}
1505
1506
if (p_local) {
1507
p_node->set_transform(p_transform);
1508
} else {
1509
p_node->set_global_transform(p_transform);
1510
}
1511
}
1512
1513
Transform3D Node3DEditorViewport::_compute_transform(TransformMode p_mode, const Transform3D &p_original, const Transform3D &p_original_local, Vector3 p_motion, double p_extra, bool p_local, bool p_orthogonal) {
1514
switch (p_mode) {
1515
case TRANSFORM_SCALE: {
1516
if (_edit.snap || spatial_editor->is_snap_enabled()) {
1517
p_motion.snapf(p_extra);
1518
}
1519
Transform3D s;
1520
if (p_local) {
1521
s.basis = p_original_local.basis.scaled_local(p_motion + Vector3(1, 1, 1));
1522
s.origin = p_original_local.origin;
1523
} else {
1524
s.basis.scale(p_motion + Vector3(1, 1, 1));
1525
Transform3D base = Transform3D(Basis(), _edit.center);
1526
s = base * (s * (base.inverse() * p_original));
1527
1528
// Recalculate orthogonalized scale without moving origin.
1529
if (p_orthogonal) {
1530
s.basis = p_original.basis.scaled_orthogonal(p_motion + Vector3(1, 1, 1));
1531
}
1532
}
1533
1534
return s;
1535
}
1536
case TRANSFORM_TRANSLATE: {
1537
if (_edit.snap || spatial_editor->is_snap_enabled()) {
1538
p_motion.snapf(p_extra);
1539
}
1540
1541
if (p_local) {
1542
return p_original_local.translated_local(p_motion);
1543
}
1544
1545
return p_original.translated(p_motion);
1546
}
1547
case TRANSFORM_ROTATE: {
1548
Transform3D r;
1549
1550
if (p_local) {
1551
Vector3 axis = p_original_local.basis.xform(p_motion);
1552
r.basis = Basis(axis.normalized(), p_extra) * p_original_local.basis;
1553
r.origin = p_original_local.origin;
1554
} else {
1555
Basis local = p_original.basis * p_original_local.basis.inverse();
1556
Vector3 axis = local.xform_inv(p_motion);
1557
r.basis = local * Basis(axis.normalized(), p_extra) * p_original_local.basis;
1558
r.origin = Basis(p_motion, p_extra).xform(p_original.origin - _edit.center) + _edit.center;
1559
}
1560
1561
return r;
1562
}
1563
default: {
1564
ERR_FAIL_V_MSG(Transform3D(), "Invalid mode in '_compute_transform'");
1565
}
1566
}
1567
}
1568
1569
void Node3DEditorViewport::_surface_mouse_enter() {
1570
if (Input::get_singleton()->get_mouse_mode() == Input::MOUSE_MODE_CAPTURED) {
1571
return;
1572
}
1573
1574
if (!surface->has_focus() && (!get_viewport()->gui_get_focus_owner() || !get_viewport()->gui_get_focus_owner()->is_text_field())) {
1575
surface->grab_focus();
1576
}
1577
}
1578
1579
void Node3DEditorViewport::_surface_mouse_exit() {
1580
_remove_preview_node();
1581
_reset_preview_material();
1582
_remove_preview_material();
1583
}
1584
1585
void Node3DEditorViewport::_surface_focus_enter() {
1586
view_display_menu->set_disable_shortcuts(false);
1587
}
1588
1589
void Node3DEditorViewport::_surface_focus_exit() {
1590
view_display_menu->set_disable_shortcuts(true);
1591
}
1592
1593
bool Node3DEditorViewport::_is_node_locked(const Node *p_node) const {
1594
return p_node->get_meta("_edit_lock_", false);
1595
}
1596
1597
void Node3DEditorViewport::_list_select(Ref<InputEventMouseButton> b) {
1598
Vector<_RayResult> potential_selection_results;
1599
_find_items_at_pos(b->get_position(), potential_selection_results, b->is_alt_pressed());
1600
1601
Node *edited_scene = EditorNode::get_singleton()->get_edited_scene();
1602
1603
// Filter to a list of nodes which include either the edited scene or nodes directly owned by the edited scene.
1604
// If a node has an invalid owner, recursively check their parents until a valid node is found.
1605
for (int i = 0; i < potential_selection_results.size(); i++) {
1606
Node3D *node = potential_selection_results[i].item;
1607
while (true) {
1608
if (node == nullptr || node == edited_scene->get_parent()) {
1609
break;
1610
} else {
1611
Node *node_owner = node->get_owner();
1612
if (node == edited_scene || node_owner == edited_scene || (node_owner != nullptr && edited_scene->is_editable_instance(node_owner))) {
1613
if (!selection_results.has(node)) {
1614
selection_results.append(node);
1615
}
1616
break;
1617
}
1618
}
1619
node = Object::cast_to<Node3D>(node->get_parent());
1620
}
1621
}
1622
1623
clicked_wants_append = b->is_shift_pressed();
1624
1625
if (selection_results.size() == 1) {
1626
clicked = selection_results[0]->get_instance_id();
1627
selection_results.clear();
1628
1629
if (clicked.is_valid()) {
1630
_select_clicked(b->is_alt_pressed());
1631
}
1632
} else if (!selection_results.is_empty()) {
1633
NodePath root_path = get_tree()->get_edited_scene_root()->get_path();
1634
StringName root_name = root_path.get_name(root_path.get_name_count() - 1);
1635
1636
for (int i = 0; i < selection_results.size(); i++) {
1637
Node3D *spat = selection_results[i];
1638
1639
Ref<Texture2D> icon = EditorNode::get_singleton()->get_object_icon(spat, "Node");
1640
1641
String node_path = "/" + root_name + "/" + String(root_path.rel_path_to(spat->get_path()));
1642
1643
int locked = 0;
1644
if (_is_node_locked(spat)) {
1645
locked = 1;
1646
} else {
1647
Node *ed_scene = EditorNode::get_singleton()->get_edited_scene();
1648
Node *node = spat;
1649
1650
while (node && node != ed_scene->get_parent()) {
1651
Node3D *selected_tmp = Object::cast_to<Node3D>(node);
1652
if (selected_tmp && node->has_meta("_edit_group_")) {
1653
locked = 2;
1654
}
1655
node = node->get_parent();
1656
}
1657
}
1658
1659
String suffix;
1660
if (locked == 1) {
1661
suffix = " (" + TTR("Locked") + ")";
1662
} else if (locked == 2) {
1663
suffix = " (" + TTR("Grouped") + ")";
1664
}
1665
selection_menu->add_item((String)spat->get_name() + suffix);
1666
selection_menu->set_item_icon(i, icon);
1667
selection_menu->set_item_metadata(i, node_path);
1668
selection_menu->set_item_tooltip(i, String(spat->get_name()) + "\nType: " + spat->get_class() + "\nPath: " + node_path);
1669
}
1670
1671
selection_results_menu = selection_results;
1672
selection_menu->set_position(get_screen_position() + b->get_position());
1673
selection_menu->reset_size();
1674
selection_menu->popup();
1675
}
1676
}
1677
1678
// Helper function to redirect mouse events to the active freelook viewport
1679
static bool _redirect_freelook_input(const Ref<InputEvent> &p_event, Node3DEditorViewport *p_exclude_viewport = nullptr) {
1680
if (Input::get_singleton()->get_mouse_mode() != Input::MOUSE_MODE_CAPTURED) {
1681
return false;
1682
}
1683
1684
Node3DEditor *editor = Node3DEditor::get_singleton();
1685
if (!editor->get_freelook_viewport()) {
1686
return false;
1687
}
1688
1689
Node3DEditorViewport *freelook_vp = editor->get_freelook_viewport();
1690
if (freelook_vp == p_exclude_viewport) {
1691
return false;
1692
}
1693
1694
Ref<InputEventMouse> mouse_event = p_event;
1695
if (!mouse_event.is_valid()) {
1696
return false;
1697
}
1698
1699
Control *target_surface = freelook_vp->get_surface();
1700
1701
target_surface->emit_signal(SceneStringName(gui_input), p_event);
1702
return true;
1703
}
1704
1705
// This is only active during instant transforms,
1706
// to capture and wrap mouse events outside the control.
1707
void Node3DEditorViewport::input(const Ref<InputEvent> &p_event) {
1708
ERR_FAIL_COND(!_edit.instant);
1709
Ref<InputEventMouseMotion> m = p_event;
1710
1711
if (m.is_valid()) {
1712
_edit.mouse_pos += _get_warped_mouse_motion(p_event);
1713
update_transform(_get_key_modifier(m) == Key::SHIFT);
1714
}
1715
}
1716
1717
void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
1718
if (previewing || get_viewport()->gui_get_drag_data()) {
1719
return; //do NONE
1720
}
1721
1722
if (_redirect_freelook_input(p_event, this)) {
1723
return;
1724
}
1725
1726
EditorPlugin::AfterGUIInput after = EditorPlugin::AFTER_GUI_INPUT_PASS;
1727
{
1728
EditorNode *en = EditorNode::get_singleton();
1729
EditorPluginList *force_input_forwarding_list = en->get_editor_plugins_force_input_forwarding();
1730
if (!force_input_forwarding_list->is_empty()) {
1731
EditorPlugin::AfterGUIInput discard = force_input_forwarding_list->forward_3d_gui_input(camera, p_event, true);
1732
if (discard == EditorPlugin::AFTER_GUI_INPUT_STOP) {
1733
return;
1734
}
1735
if (discard == EditorPlugin::AFTER_GUI_INPUT_CUSTOM) {
1736
after = EditorPlugin::AFTER_GUI_INPUT_CUSTOM;
1737
}
1738
}
1739
}
1740
{
1741
EditorNode *en = EditorNode::get_singleton();
1742
EditorPluginList *over_plugin_list = en->get_editor_plugins_over();
1743
if (!over_plugin_list->is_empty()) {
1744
EditorPlugin::AfterGUIInput discard = over_plugin_list->forward_3d_gui_input(camera, p_event, false);
1745
if (discard == EditorPlugin::AFTER_GUI_INPUT_STOP) {
1746
return;
1747
}
1748
if (discard == EditorPlugin::AFTER_GUI_INPUT_CUSTOM) {
1749
after = EditorPlugin::AFTER_GUI_INPUT_CUSTOM;
1750
}
1751
}
1752
}
1753
1754
Ref<InputEventMouseButton> b = p_event;
1755
1756
if (b.is_valid()) {
1757
emit_signal(SNAME("clicked"));
1758
1759
ViewportNavMouseButton orbit_mouse_preference = (ViewportNavMouseButton)EDITOR_GET("editors/3d/navigation/orbit_mouse_button").operator int();
1760
ViewportNavMouseButton pan_mouse_preference = (ViewportNavMouseButton)EDITOR_GET("editors/3d/navigation/pan_mouse_button").operator int();
1761
ViewportNavMouseButton zoom_mouse_preference = (ViewportNavMouseButton)EDITOR_GET("editors/3d/navigation/zoom_mouse_button").operator int();
1762
1763
const real_t zoom_factor = 1 + (ZOOM_FREELOOK_MULTIPLIER - 1) * b->get_factor();
1764
switch (b->get_button_index()) {
1765
case MouseButton::WHEEL_UP: {
1766
if (is_freelook_active()) {
1767
scale_freelook_speed(zoom_factor);
1768
} else {
1769
scale_cursor_distance(1.0 / zoom_factor);
1770
}
1771
} break;
1772
case MouseButton::WHEEL_DOWN: {
1773
if (is_freelook_active()) {
1774
scale_freelook_speed(1.0 / zoom_factor);
1775
} else {
1776
scale_cursor_distance(zoom_factor);
1777
}
1778
} break;
1779
case MouseButton::RIGHT: {
1780
if (b->is_pressed()) {
1781
if (_edit.gizmo.is_valid()) {
1782
// Restore.
1783
_edit.gizmo->commit_handle(_edit.gizmo_handle, _edit.gizmo_handle_secondary, _edit.gizmo_initial_value, true);
1784
_edit.gizmo = Ref<EditorNode3DGizmo>();
1785
}
1786
1787
if (_edit.mode == TRANSFORM_NONE) {
1788
if (orbit_mouse_preference == NAVIGATION_RIGHT_MOUSE && _is_nav_modifier_pressed("spatial_editor/viewport_orbit_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_orbit_modifier_2")) {
1789
break;
1790
} else if (pan_mouse_preference == NAVIGATION_RIGHT_MOUSE && _is_nav_modifier_pressed("spatial_editor/viewport_pan_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_pan_modifier_2")) {
1791
break;
1792
} else if (zoom_mouse_preference == NAVIGATION_RIGHT_MOUSE && _is_nav_modifier_pressed("spatial_editor/viewport_zoom_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_zoom_modifier_2")) {
1793
break;
1794
}
1795
}
1796
1797
if (b->is_alt_pressed()) {
1798
_list_select(b);
1799
return;
1800
}
1801
1802
if (_edit.mode != TRANSFORM_NONE) {
1803
cancel_transform();
1804
break;
1805
}
1806
1807
const Key mod = _get_key_modifier(b);
1808
if (!orthogonal) {
1809
if (mod == _get_key_modifier_setting("editors/3d/freelook/freelook_activation_modifier")) {
1810
set_freelook_active(true);
1811
}
1812
}
1813
} else {
1814
set_freelook_active(false);
1815
}
1816
1817
if (freelook_active && !surface->has_focus()) {
1818
// Focus usually doesn't trigger on right-click, but in case of freelook it should,
1819
// otherwise using keyboard navigation would misbehave
1820
surface->grab_focus();
1821
}
1822
1823
} break;
1824
case MouseButton::MIDDLE: {
1825
if (b->is_pressed() && _edit.mode != TRANSFORM_NONE) {
1826
if (orbit_mouse_preference == NAVIGATION_MIDDLE_MOUSE && _is_nav_modifier_pressed("spatial_editor/viewport_orbit_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_orbit_modifier_2")) {
1827
break;
1828
} else if (pan_mouse_preference == NAVIGATION_MIDDLE_MOUSE && _is_nav_modifier_pressed("spatial_editor/viewport_pan_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_pan_modifier_2")) {
1829
break;
1830
} else if (zoom_mouse_preference == NAVIGATION_MIDDLE_MOUSE && _is_nav_modifier_pressed("spatial_editor/viewport_zoom_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_zoom_modifier_2")) {
1831
break;
1832
}
1833
1834
switch (_edit.plane) {
1835
case TRANSFORM_VIEW: {
1836
_edit.plane = TRANSFORM_X_AXIS;
1837
set_message(TTR("X-Axis Transform."), 2);
1838
view_type = VIEW_TYPE_USER;
1839
_update_name();
1840
} break;
1841
case TRANSFORM_X_AXIS: {
1842
_edit.plane = TRANSFORM_Y_AXIS;
1843
set_message(TTR("Y-Axis Transform."), 2);
1844
1845
} break;
1846
case TRANSFORM_Y_AXIS: {
1847
_edit.plane = TRANSFORM_Z_AXIS;
1848
set_message(TTR("Z-Axis Transform."), 2);
1849
1850
} break;
1851
case TRANSFORM_Z_AXIS: {
1852
_edit.plane = TRANSFORM_VIEW;
1853
// TRANSLATORS: This refers to the transform of the view plane.
1854
set_message(TTR("View Plane Transform."), 2);
1855
1856
} break;
1857
case TRANSFORM_YZ:
1858
case TRANSFORM_XZ:
1859
case TRANSFORM_XY: {
1860
} break;
1861
}
1862
}
1863
} break;
1864
case MouseButton::LEFT: {
1865
if (b->is_pressed()) {
1866
clicked_wants_append = b->is_shift_pressed();
1867
1868
if (_edit.mode != TRANSFORM_NONE && (_edit.instant || collision_reposition)) {
1869
commit_transform();
1870
break; // just commit the edit, stop processing the event so we don't deselect the object
1871
}
1872
if (orbit_mouse_preference == NAVIGATION_LEFT_MOUSE && _is_nav_modifier_pressed("spatial_editor/viewport_orbit_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_orbit_modifier_2")) {
1873
break;
1874
} else if (pan_mouse_preference == NAVIGATION_LEFT_MOUSE && _is_nav_modifier_pressed("spatial_editor/viewport_pan_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_pan_modifier_2")) {
1875
break;
1876
} else if (zoom_mouse_preference == NAVIGATION_LEFT_MOUSE && _is_nav_modifier_pressed("spatial_editor/viewport_zoom_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_zoom_modifier_2")) {
1877
break;
1878
}
1879
1880
if (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_RULER) {
1881
EditorNode::get_singleton()->get_scene_root()->add_child(ruler);
1882
collision_reposition = true;
1883
break;
1884
}
1885
1886
if (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_LIST_SELECT) {
1887
_list_select(b);
1888
break;
1889
}
1890
1891
_edit.mouse_pos = b->get_position();
1892
_edit.original_mouse_pos = b->get_position();
1893
_edit.snap = spatial_editor->is_snap_enabled();
1894
_edit.mode = TRANSFORM_NONE;
1895
_edit.original = spatial_editor->get_gizmo_transform(); // To prevent to break when flipping with scale.
1896
1897
bool can_select_gizmos = spatial_editor->get_single_selected_node();
1898
1899
{
1900
int idx = view_display_menu->get_popup()->get_item_index(VIEW_GIZMOS);
1901
int idx2 = view_display_menu->get_popup()->get_item_index(VIEW_TRANSFORM_GIZMO);
1902
can_select_gizmos = can_select_gizmos && view_display_menu->get_popup()->is_item_checked(idx);
1903
transform_gizmo_visible = view_display_menu->get_popup()->is_item_checked(idx2);
1904
}
1905
1906
// Gizmo handles
1907
if (can_select_gizmos) {
1908
Vector<Ref<Node3DGizmo>> gizmos = spatial_editor->get_single_selected_node()->get_gizmos();
1909
1910
bool intersected_handle = false;
1911
for (int i = 0; i < gizmos.size(); i++) {
1912
Ref<EditorNode3DGizmo> seg = gizmos[i];
1913
1914
if (seg.is_null()) {
1915
continue;
1916
}
1917
1918
int gizmo_handle = -1;
1919
bool gizmo_secondary = false;
1920
seg->handles_intersect_ray(camera, _edit.mouse_pos, b->is_shift_pressed(), gizmo_handle, gizmo_secondary);
1921
if (gizmo_handle != -1) {
1922
_edit.gizmo = seg;
1923
seg->begin_handle_action(gizmo_handle, gizmo_secondary);
1924
_edit.gizmo_handle = gizmo_handle;
1925
_edit.gizmo_handle_secondary = gizmo_secondary;
1926
_edit.gizmo_initial_value = seg->get_handle_value(gizmo_handle, gizmo_secondary);
1927
intersected_handle = true;
1928
break;
1929
}
1930
}
1931
1932
if (intersected_handle) {
1933
break;
1934
}
1935
}
1936
1937
// Transform gizmo
1938
if (transform_gizmo_visible && _transform_gizmo_select(_edit.mouse_pos)) {
1939
break;
1940
}
1941
1942
// Subgizmos
1943
if (can_select_gizmos) {
1944
Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(spatial_editor->get_single_selected_node());
1945
Vector<Ref<Node3DGizmo>> gizmos = spatial_editor->get_single_selected_node()->get_gizmos();
1946
1947
bool intersected_subgizmo = false;
1948
for (int i = 0; i < gizmos.size(); i++) {
1949
Ref<EditorNode3DGizmo> seg = gizmos[i];
1950
1951
if (seg.is_null()) {
1952
continue;
1953
}
1954
1955
int subgizmo_id = seg->subgizmos_intersect_ray(camera, _edit.mouse_pos);
1956
if (subgizmo_id != -1) {
1957
ERR_CONTINUE(!se);
1958
if (b->is_shift_pressed()) {
1959
if (se->subgizmos.has(subgizmo_id)) {
1960
se->subgizmos.erase(subgizmo_id);
1961
} else {
1962
se->subgizmos.insert(subgizmo_id, seg->get_subgizmo_transform(subgizmo_id));
1963
}
1964
} else {
1965
se->subgizmos.clear();
1966
se->subgizmos.insert(subgizmo_id, seg->get_subgizmo_transform(subgizmo_id));
1967
}
1968
1969
if (se->subgizmos.is_empty()) {
1970
se->gizmo = Ref<EditorNode3DGizmo>();
1971
} else {
1972
se->gizmo = seg;
1973
}
1974
1975
seg->redraw();
1976
spatial_editor->update_transform_gizmo();
1977
intersected_subgizmo = true;
1978
break;
1979
}
1980
}
1981
1982
if (intersected_subgizmo) {
1983
break;
1984
}
1985
}
1986
1987
clicked = ObjectID();
1988
1989
bool node_selected = get_selected_count() > 0;
1990
1991
if (after != EditorPlugin::AFTER_GUI_INPUT_CUSTOM) {
1992
// Single item selection.
1993
clicked = _select_ray(b->get_position());
1994
1995
if (clicked.is_valid() && !editor_selection->is_selected(ObjectDB::get_instance<Node>(clicked))) {
1996
if (!node_selected || (!b->is_alt_pressed() && !(spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT && b->is_command_or_control_pressed()))) {
1997
selection_in_progress = true;
1998
break;
1999
}
2000
}
2001
2002
if (clicked.is_null()) {
2003
if (node_selected) {
2004
TransformMode mode = TRANSFORM_NONE;
2005
2006
if (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT) {
2007
if (b->is_command_or_control_pressed()) {
2008
mode = TRANSFORM_ROTATE;
2009
} else if (b->is_alt_pressed()) {
2010
mode = TRANSFORM_TRANSLATE;
2011
}
2012
} else if (b->is_alt_pressed()) {
2013
switch (spatial_editor->get_tool_mode()) {
2014
case Node3DEditor::TOOL_MODE_ROTATE:
2015
mode = TRANSFORM_ROTATE;
2016
break;
2017
case Node3DEditor::TOOL_MODE_MOVE:
2018
mode = TRANSFORM_TRANSLATE;
2019
break;
2020
case Node3DEditor::TOOL_MODE_SCALE:
2021
mode = TRANSFORM_SCALE;
2022
break;
2023
default:
2024
break;
2025
}
2026
}
2027
2028
if (mode != TRANSFORM_NONE) {
2029
begin_transform(mode, false);
2030
break;
2031
}
2032
}
2033
2034
// Default to region select.
2035
cursor.region_select = true;
2036
cursor.region_begin = b->get_position();
2037
cursor.region_end = b->get_position();
2038
break;
2039
}
2040
}
2041
2042
if (clicked.is_valid() && !clicked_wants_append) {
2043
bool is_clicked_node_selected = editor_selection->is_selected(ObjectDB::get_instance<Node>(clicked));
2044
TransformMode mode = TRANSFORM_NONE;
2045
2046
switch (spatial_editor->get_tool_mode()) {
2047
case Node3DEditor::TOOL_MODE_SELECT:
2048
if (b->is_command_or_control_pressed() && node_selected) {
2049
mode = TRANSFORM_ROTATE;
2050
} else if (b->is_alt_pressed() && node_selected) {
2051
mode = TRANSFORM_TRANSLATE;
2052
} else if (is_clicked_node_selected) {
2053
mode = TRANSFORM_TRANSLATE;
2054
}
2055
break;
2056
case Node3DEditor::TOOL_MODE_ROTATE:
2057
if (is_clicked_node_selected || (b->is_alt_pressed() && node_selected)) {
2058
mode = TRANSFORM_ROTATE;
2059
}
2060
break;
2061
case Node3DEditor::TOOL_MODE_MOVE:
2062
if (is_clicked_node_selected || (b->is_alt_pressed() && node_selected)) {
2063
mode = TRANSFORM_TRANSLATE;
2064
}
2065
break;
2066
case Node3DEditor::TOOL_MODE_SCALE:
2067
if (is_clicked_node_selected || (b->is_alt_pressed() && node_selected)) {
2068
mode = TRANSFORM_SCALE;
2069
}
2070
break;
2071
default:
2072
break;
2073
}
2074
2075
if (mode != TRANSFORM_NONE) {
2076
begin_transform(mode, false);
2077
break;
2078
}
2079
}
2080
2081
surface->queue_redraw();
2082
} else {
2083
if (ruler->is_inside_tree()) {
2084
EditorNode::get_singleton()->get_scene_root()->remove_child(ruler);
2085
ruler_start_point->set_visible(false);
2086
ruler_end_point->set_visible(false);
2087
ruler_label->set_visible(false);
2088
collision_reposition = false;
2089
break;
2090
}
2091
2092
if (_edit.gizmo.is_valid()) {
2093
// Certain gizmo plugins should be able to commit handles without dragging them.
2094
if (_edit.original_mouse_pos != _edit.mouse_pos || _edit.gizmo->get_plugin()->can_commit_handle_on_click()) {
2095
_edit.gizmo->commit_handle(_edit.gizmo_handle, _edit.gizmo_handle_secondary, _edit.gizmo_initial_value, false);
2096
}
2097
2098
spatial_editor->get_single_selected_node()->update_gizmos();
2099
_edit.gizmo = Ref<EditorNode3DGizmo>();
2100
break;
2101
}
2102
2103
if (after != EditorPlugin::AFTER_GUI_INPUT_CUSTOM) {
2104
selection_in_progress = false;
2105
2106
if (clicked.is_valid() && _edit.mode == TRANSFORM_NONE) {
2107
_select_clicked(false);
2108
}
2109
2110
if (cursor.region_select) {
2111
_select_region();
2112
cursor.region_select = false;
2113
surface->queue_redraw();
2114
}
2115
}
2116
2117
if (!_edit.instant && _edit.mode != TRANSFORM_NONE) {
2118
Node3D *selected = spatial_editor->get_single_selected_node();
2119
Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr;
2120
2121
if (se && se->gizmo.is_valid()) {
2122
Vector<int> ids;
2123
Vector<Transform3D> restore;
2124
2125
for (const KeyValue<int, Transform3D> &GE : se->subgizmos) {
2126
ids.push_back(GE.key);
2127
restore.push_back(GE.value);
2128
}
2129
2130
se->gizmo->commit_subgizmos(ids, restore, false);
2131
} else {
2132
if (_edit.original_mouse_pos != _edit.mouse_pos) {
2133
commit_transform();
2134
}
2135
}
2136
_edit.mode = TRANSFORM_NONE;
2137
set_message("");
2138
spatial_editor->update_transform_gizmo();
2139
}
2140
}
2141
2142
} break;
2143
default:
2144
break;
2145
}
2146
}
2147
2148
ViewportNavMouseButton orbit_mouse_preference = (ViewportNavMouseButton)EDITOR_GET("editors/3d/navigation/orbit_mouse_button").operator int();
2149
ViewportNavMouseButton pan_mouse_preference = (ViewportNavMouseButton)EDITOR_GET("editors/3d/navigation/pan_mouse_button").operator int();
2150
ViewportNavMouseButton zoom_mouse_preference = (ViewportNavMouseButton)EDITOR_GET("editors/3d/navigation/zoom_mouse_button").operator int();
2151
bool orbit_mod_pressed = _is_nav_modifier_pressed("spatial_editor/viewport_orbit_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_orbit_modifier_2");
2152
bool pan_mod_pressed = _is_nav_modifier_pressed("spatial_editor/viewport_pan_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_pan_modifier_2");
2153
bool zoom_mod_pressed = _is_nav_modifier_pressed("spatial_editor/viewport_zoom_modifier_1") && _is_nav_modifier_pressed("spatial_editor/viewport_zoom_modifier_2");
2154
int orbit_mod_input_count = _get_shortcut_input_count("spatial_editor/viewport_orbit_modifier_1") + _get_shortcut_input_count("spatial_editor/viewport_orbit_modifier_2");
2155
int pan_mod_input_count = _get_shortcut_input_count("spatial_editor/viewport_pan_modifier_1") + _get_shortcut_input_count("spatial_editor/viewport_pan_modifier_2");
2156
int zoom_mod_input_count = _get_shortcut_input_count("spatial_editor/viewport_zoom_modifier_1") + _get_shortcut_input_count("spatial_editor/viewport_zoom_modifier_2");
2157
bool orbit_not_empty = !_is_shortcut_empty("spatial_editor/viewport_orbit_modifier_1") || !_is_shortcut_empty("spatial_editor/viewport_orbit_modifier_2");
2158
bool pan_not_empty = !_is_shortcut_empty("spatial_editor/viewport_pan_modifier_1") || !_is_shortcut_empty("spatial_editor/viewport_pan_modifier_2");
2159
bool zoom_not_empty = !_is_shortcut_empty("spatial_editor/viewport_zoom_modifier_1") || !_is_shortcut_empty("spatial_editor/viewport_zoom_modifier_2");
2160
Vector<ShortcutCheckSet> shortcut_check_sets;
2161
shortcut_check_sets.push_back(ShortcutCheckSet(orbit_mod_pressed, orbit_not_empty, orbit_mod_input_count, orbit_mouse_preference, NAVIGATION_ORBIT));
2162
shortcut_check_sets.push_back(ShortcutCheckSet(pan_mod_pressed, pan_not_empty, pan_mod_input_count, pan_mouse_preference, NAVIGATION_PAN));
2163
shortcut_check_sets.push_back(ShortcutCheckSet(zoom_mod_pressed, zoom_not_empty, zoom_mod_input_count, zoom_mouse_preference, NAVIGATION_ZOOM));
2164
shortcut_check_sets.sort_custom<ShortcutCheckSetComparator>();
2165
2166
Ref<InputEventMouseMotion> m = p_event;
2167
2168
// Instant transforms process mouse motion in input() to handle wrapping.
2169
if (m.is_valid() && !_edit.instant) {
2170
_edit.mouse_pos = m->get_position();
2171
2172
if (spatial_editor->get_single_selected_node()) {
2173
Vector<Ref<Node3DGizmo>> gizmos = spatial_editor->get_single_selected_node()->get_gizmos();
2174
2175
Ref<EditorNode3DGizmo> found_gizmo;
2176
int found_handle = -1;
2177
bool found_handle_secondary = false;
2178
2179
for (int i = 0; i < gizmos.size(); i++) {
2180
Ref<EditorNode3DGizmo> seg = gizmos[i];
2181
if (seg.is_null()) {
2182
continue;
2183
}
2184
2185
seg->handles_intersect_ray(camera, _edit.mouse_pos, false, found_handle, found_handle_secondary);
2186
2187
if (found_handle != -1) {
2188
found_gizmo = seg;
2189
break;
2190
}
2191
}
2192
2193
if (found_gizmo.is_valid()) {
2194
spatial_editor->select_gizmo_highlight_axis(-1);
2195
}
2196
2197
bool current_hover_handle_secondary = false;
2198
int current_hover_handle = spatial_editor->get_current_hover_gizmo_handle(current_hover_handle_secondary);
2199
if (found_gizmo != spatial_editor->get_current_hover_gizmo() || found_handle != current_hover_handle || found_handle_secondary != current_hover_handle_secondary) {
2200
spatial_editor->set_current_hover_gizmo(found_gizmo);
2201
spatial_editor->set_current_hover_gizmo_handle(found_handle, found_handle_secondary);
2202
spatial_editor->get_single_selected_node()->update_gizmos();
2203
}
2204
}
2205
2206
if (transform_gizmo_visible && spatial_editor->get_current_hover_gizmo().is_null() && !m->get_button_mask().has_flag(MouseButtonMask::LEFT) && _edit.gizmo.is_null()) {
2207
_transform_gizmo_select(_edit.mouse_pos, true);
2208
}
2209
2210
NavigationMode nav_mode = NAVIGATION_NONE;
2211
2212
if (_edit.gizmo.is_valid()) {
2213
_edit.gizmo->set_handle(_edit.gizmo_handle, _edit.gizmo_handle_secondary, camera, m->get_position());
2214
Variant v = _edit.gizmo->get_handle_value(_edit.gizmo_handle, _edit.gizmo_handle_secondary);
2215
String n = _edit.gizmo->get_handle_name(_edit.gizmo_handle, _edit.gizmo_handle_secondary);
2216
set_message(n + ": " + String(v));
2217
2218
} else if (m->get_button_mask().has_flag(MouseButtonMask::LEFT)) {
2219
NavigationMode change_nav_from_shortcut = _get_nav_mode_from_shortcut_check(NAVIGATION_LEFT_MOUSE, shortcut_check_sets, false);
2220
if (change_nav_from_shortcut != NAVIGATION_NONE) {
2221
nav_mode = change_nav_from_shortcut;
2222
} else {
2223
const bool movement_threshold_passed = _edit.original_mouse_pos.distance_to(_edit.mouse_pos) > 8 * EDSCALE;
2224
2225
if ((selection_in_progress || clicked_wants_append) && movement_threshold_passed && clicked.is_valid()) {
2226
cursor.region_select = true;
2227
cursor.region_begin = _edit.original_mouse_pos;
2228
clicked = ObjectID();
2229
}
2230
2231
if (cursor.region_select) {
2232
cursor.region_end = m->get_position();
2233
surface->queue_redraw();
2234
return;
2235
}
2236
2237
if (clicked.is_valid() && movement_threshold_passed && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_MOVE)) {
2238
bool is_select_mode = (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT);
2239
bool is_clicked_selected = editor_selection->is_selected(ObjectDB::get_instance<Node>(clicked));
2240
2241
if (_edit.mode == TRANSFORM_NONE && (is_select_mode || is_clicked_selected)) {
2242
_compute_edit(_edit.original_mouse_pos);
2243
clicked = ObjectID();
2244
_edit.mode = TRANSFORM_TRANSLATE;
2245
}
2246
}
2247
2248
if (_edit.mode == TRANSFORM_NONE || _edit.numeric_input != 0 || _edit.numeric_next_decimal != 0) {
2249
return;
2250
}
2251
2252
if (!selection_in_progress) {
2253
update_transform(_get_key_modifier(m) == Key::SHIFT);
2254
}
2255
}
2256
} else if (m->get_button_mask().has_flag(MouseButtonMask::RIGHT) || freelook_active) {
2257
NavigationMode change_nav_from_shortcut = _get_nav_mode_from_shortcut_check(NAVIGATION_RIGHT_MOUSE, shortcut_check_sets, false);
2258
if (m->get_button_mask().has_flag(MouseButtonMask::RIGHT) && change_nav_from_shortcut != NAVIGATION_NONE) {
2259
nav_mode = change_nav_from_shortcut;
2260
} else if (freelook_active) {
2261
nav_mode = NAVIGATION_LOOK;
2262
} else if (orthogonal) {
2263
nav_mode = NAVIGATION_PAN;
2264
}
2265
2266
} else if (m->get_button_mask().has_flag(MouseButtonMask::MIDDLE)) {
2267
NavigationMode change_nav_from_shortcut = _get_nav_mode_from_shortcut_check(NAVIGATION_MIDDLE_MOUSE, shortcut_check_sets, false);
2268
if (change_nav_from_shortcut != NAVIGATION_NONE) {
2269
nav_mode = change_nav_from_shortcut;
2270
}
2271
2272
} else if (m->get_button_mask().has_flag(MouseButtonMask::MB_XBUTTON1)) {
2273
NavigationMode change_nav_from_shortcut = _get_nav_mode_from_shortcut_check(NAVIGATION_MOUSE_4, shortcut_check_sets, false);
2274
if (change_nav_from_shortcut != NAVIGATION_NONE) {
2275
nav_mode = change_nav_from_shortcut;
2276
}
2277
2278
} else if (m->get_button_mask().has_flag(MouseButtonMask::MB_XBUTTON2)) {
2279
NavigationMode change_nav_from_shortcut = _get_nav_mode_from_shortcut_check(NAVIGATION_MOUSE_5, shortcut_check_sets, false);
2280
if (change_nav_from_shortcut != NAVIGATION_NONE) {
2281
nav_mode = change_nav_from_shortcut;
2282
}
2283
2284
} else if (EDITOR_GET("editors/3d/navigation/emulate_3_button_mouse")) {
2285
// Handle trackpad (no external mouse) use case
2286
NavigationMode change_nav_from_shortcut = _get_nav_mode_from_shortcut_check(NAVIGATION_LEFT_MOUSE, shortcut_check_sets, true);
2287
if (change_nav_from_shortcut != NAVIGATION_NONE) {
2288
nav_mode = change_nav_from_shortcut;
2289
}
2290
}
2291
2292
switch (nav_mode) {
2293
case NAVIGATION_PAN: {
2294
_nav_pan(m, _get_warped_mouse_motion(m));
2295
2296
} break;
2297
2298
case NAVIGATION_ZOOM: {
2299
_nav_zoom(m, m->get_relative());
2300
2301
} break;
2302
2303
case NAVIGATION_ORBIT: {
2304
_nav_orbit(m, _get_warped_mouse_motion(m));
2305
2306
} break;
2307
2308
case NAVIGATION_LOOK: {
2309
_nav_look(m, _get_warped_mouse_motion(m));
2310
2311
} break;
2312
2313
default: {
2314
}
2315
}
2316
}
2317
2318
Ref<InputEventMagnifyGesture> magnify_gesture = p_event;
2319
if (magnify_gesture.is_valid()) {
2320
if (is_freelook_active()) {
2321
scale_freelook_speed(magnify_gesture->get_factor());
2322
} else {
2323
scale_cursor_distance(1.0 / magnify_gesture->get_factor());
2324
}
2325
}
2326
2327
Ref<InputEventPanGesture> pan_gesture = p_event;
2328
if (pan_gesture.is_valid()) {
2329
NavigationMode nav_mode = NAVIGATION_NONE;
2330
2331
for (const ShortcutCheckSet &shortcut_check_set : shortcut_check_sets) {
2332
if (shortcut_check_set.mod_pressed) {
2333
nav_mode = shortcut_check_set.result_nav_mode;
2334
break;
2335
}
2336
}
2337
2338
switch (nav_mode) {
2339
case NAVIGATION_PAN: {
2340
_nav_pan(pan_gesture, -pan_gesture->get_delta());
2341
2342
} break;
2343
2344
case NAVIGATION_ZOOM: {
2345
_nav_zoom(pan_gesture, pan_gesture->get_delta());
2346
2347
} break;
2348
2349
case NAVIGATION_ORBIT: {
2350
_nav_orbit(pan_gesture, -pan_gesture->get_delta());
2351
2352
} break;
2353
2354
case NAVIGATION_LOOK: {
2355
_nav_look(pan_gesture, pan_gesture->get_delta());
2356
2357
} break;
2358
2359
default: {
2360
}
2361
}
2362
}
2363
2364
Ref<InputEventKey> k = p_event;
2365
2366
if (k.is_valid()) {
2367
if (!k->is_pressed()) {
2368
return;
2369
}
2370
2371
if (_edit.instant) {
2372
// In a Blender-style transform, numbers set the magnitude of the transform.
2373
// E.g. pressing g4.5x means "translate 4.5 units along the X axis".
2374
bool processed = true;
2375
Key keycode = k->get_keycode();
2376
Key physical_keycode = k->get_physical_keycode();
2377
2378
// Use physical keycode for main keyboard numbers (for non-QWERTY layouts like AZERTY)
2379
// but regular keycode for numpad numbers.
2380
if ((physical_keycode >= Key::KEY_0 && physical_keycode <= Key::KEY_9) || (keycode >= Key::KP_0 && keycode <= Key::KP_9)) {
2381
uint32_t value;
2382
if (physical_keycode >= Key::KEY_0 && physical_keycode <= Key::KEY_9) {
2383
value = uint32_t(physical_keycode - Key::KEY_0);
2384
} else {
2385
value = uint32_t(keycode - Key::KP_0);
2386
}
2387
2388
if (_edit.numeric_next_decimal < 0) {
2389
_edit.numeric_input = _edit.numeric_input + value * Math::pow(10.0, _edit.numeric_next_decimal--);
2390
} else {
2391
_edit.numeric_input = _edit.numeric_input * 10 + value;
2392
}
2393
update_transform_numeric();
2394
} else if (keycode == Key::MINUS || keycode == Key::KP_SUBTRACT) {
2395
_edit.numeric_negate = !_edit.numeric_negate;
2396
update_transform_numeric();
2397
} else if (keycode == Key::PERIOD || physical_keycode == Key::KP_PERIOD) {
2398
// Use physical keycode for KP_PERIOD to ensure numpad period works consistently
2399
// across different keyboard layouts (like nordic keyboards).
2400
if (_edit.numeric_next_decimal == 0) {
2401
_edit.numeric_next_decimal = -1;
2402
}
2403
} else if (keycode == Key::ENTER || keycode == Key::KP_ENTER || keycode == Key::SPACE) {
2404
commit_transform();
2405
} else {
2406
processed = false;
2407
}
2408
2409
if (processed) {
2410
// Ignore mouse inputs once we receive a numeric input.
2411
set_process_input(false);
2412
accept_event();
2413
return;
2414
}
2415
}
2416
2417
if (EDITOR_GET("editors/3d/navigation/emulate_numpad")) {
2418
const Key code = k->get_physical_keycode();
2419
if (code >= Key::KEY_0 && code <= Key::KEY_9) {
2420
k->set_keycode(code - Key::KEY_0 + Key::KP_0);
2421
}
2422
}
2423
2424
if (_edit.mode == TRANSFORM_NONE) {
2425
if (_edit.gizmo.is_null() && is_freelook_active() && k->get_keycode() == Key::ESCAPE) {
2426
set_freelook_active(false);
2427
return;
2428
}
2429
2430
if (_edit.gizmo.is_valid() && (k->get_keycode() == Key::ESCAPE || k->get_keycode() == Key::BACKSPACE)) {
2431
// Restore.
2432
_edit.gizmo->commit_handle(_edit.gizmo_handle, _edit.gizmo_handle_secondary, _edit.gizmo_initial_value, true);
2433
_edit.gizmo = Ref<EditorNode3DGizmo>();
2434
}
2435
if (k->get_keycode() == Key::ESCAPE && !cursor.region_select && !k->is_echo()) {
2436
_clear_selected();
2437
return;
2438
}
2439
} else {
2440
// We're actively transforming, handle keys specially
2441
TransformPlane new_plane = TRANSFORM_VIEW;
2442
if (ED_IS_SHORTCUT("spatial_editor/lock_transform_x", p_event)) {
2443
new_plane = TRANSFORM_X_AXIS;
2444
} else if (ED_IS_SHORTCUT("spatial_editor/lock_transform_y", p_event)) {
2445
new_plane = TRANSFORM_Y_AXIS;
2446
} else if (ED_IS_SHORTCUT("spatial_editor/lock_transform_z", p_event)) {
2447
new_plane = TRANSFORM_Z_AXIS;
2448
} else if (_edit.mode != TRANSFORM_ROTATE) { // rotating on a plane doesn't make sense
2449
if (ED_IS_SHORTCUT("spatial_editor/lock_transform_yz", p_event)) {
2450
new_plane = TRANSFORM_YZ;
2451
} else if (ED_IS_SHORTCUT("spatial_editor/lock_transform_xz", p_event)) {
2452
new_plane = TRANSFORM_XZ;
2453
} else if (ED_IS_SHORTCUT("spatial_editor/lock_transform_xy", p_event)) {
2454
new_plane = TRANSFORM_XY;
2455
}
2456
}
2457
2458
if (new_plane != TRANSFORM_VIEW) {
2459
if (new_plane != _edit.plane) {
2460
// lock me once and get a global constraint
2461
_edit.plane = new_plane;
2462
spatial_editor->set_local_coords_enabled(false);
2463
} else if (!spatial_editor->are_local_coords_enabled()) {
2464
// lock me twice and get a local constraint
2465
spatial_editor->set_local_coords_enabled(true);
2466
} else {
2467
// lock me thrice and we're back where we started
2468
_edit.plane = TRANSFORM_VIEW;
2469
spatial_editor->set_local_coords_enabled(false);
2470
}
2471
if (_edit.numeric_input != 0 || _edit.numeric_next_decimal != 0) {
2472
update_transform_numeric();
2473
} else {
2474
update_transform(Input::get_singleton()->is_key_pressed(Key::SHIFT));
2475
}
2476
accept_event();
2477
return;
2478
}
2479
}
2480
if (ED_IS_SHORTCUT("spatial_editor/snap", p_event)) {
2481
if (_edit.mode != TRANSFORM_NONE) {
2482
_edit.snap = !_edit.snap;
2483
}
2484
}
2485
if (ED_IS_SHORTCUT("spatial_editor/bottom_view", p_event)) {
2486
_menu_option(VIEW_BOTTOM);
2487
}
2488
if (ED_IS_SHORTCUT("spatial_editor/top_view", p_event)) {
2489
_menu_option(VIEW_TOP);
2490
}
2491
if (ED_IS_SHORTCUT("spatial_editor/rear_view", p_event)) {
2492
_menu_option(VIEW_REAR);
2493
}
2494
if (ED_IS_SHORTCUT("spatial_editor/front_view", p_event)) {
2495
_menu_option(VIEW_FRONT);
2496
}
2497
if (ED_IS_SHORTCUT("spatial_editor/left_view", p_event)) {
2498
_menu_option(VIEW_LEFT);
2499
}
2500
if (ED_IS_SHORTCUT("spatial_editor/right_view", p_event)) {
2501
_menu_option(VIEW_RIGHT);
2502
}
2503
if (ED_IS_SHORTCUT("spatial_editor/orbit_view_down", p_event)) {
2504
// Clamp rotation to roughly -90..90 degrees so the user can't look upside-down and end up disoriented.
2505
cursor.x_rot = CLAMP(cursor.x_rot - Math::PI / 12.0, -1.57, 1.57);
2506
view_type = VIEW_TYPE_USER;
2507
_update_name();
2508
}
2509
if (ED_IS_SHORTCUT("spatial_editor/orbit_view_up", p_event)) {
2510
// Clamp rotation to roughly -90..90 degrees so the user can't look upside-down and end up disoriented.
2511
cursor.x_rot = CLAMP(cursor.x_rot + Math::PI / 12.0, -1.57, 1.57);
2512
view_type = VIEW_TYPE_USER;
2513
_update_name();
2514
}
2515
if (ED_IS_SHORTCUT("spatial_editor/orbit_view_right", p_event)) {
2516
cursor.y_rot -= Math::PI / 12.0;
2517
view_type = VIEW_TYPE_USER;
2518
_update_name();
2519
}
2520
if (ED_IS_SHORTCUT("spatial_editor/orbit_view_left", p_event)) {
2521
cursor.y_rot += Math::PI / 12.0;
2522
view_type = VIEW_TYPE_USER;
2523
_update_name();
2524
}
2525
if (ED_IS_SHORTCUT("spatial_editor/orbit_view_180", p_event)) {
2526
cursor.y_rot += Math::PI;
2527
view_type = VIEW_TYPE_USER;
2528
_update_name();
2529
}
2530
if (ED_IS_SHORTCUT("spatial_editor/focus_origin", p_event)) {
2531
_menu_option(VIEW_CENTER_TO_ORIGIN);
2532
}
2533
if (ED_IS_SHORTCUT("spatial_editor/focus_selection", p_event)) {
2534
_menu_option(VIEW_CENTER_TO_SELECTION);
2535
}
2536
if (ED_IS_SHORTCUT("spatial_editor/align_transform_with_view", p_event)) {
2537
_menu_option(VIEW_ALIGN_TRANSFORM_WITH_VIEW);
2538
}
2539
if (ED_IS_SHORTCUT("spatial_editor/align_rotation_with_view", p_event)) {
2540
_menu_option(VIEW_ALIGN_ROTATION_WITH_VIEW);
2541
}
2542
if (ED_IS_SHORTCUT("spatial_editor/insert_anim_key", p_event)) {
2543
if (!get_selected_count() || _edit.mode != TRANSFORM_NONE) {
2544
return;
2545
}
2546
2547
if (!AnimationPlayerEditor::get_singleton()->get_track_editor()->has_keying()) {
2548
set_message(TTR("Keying is disabled (no key inserted)."));
2549
return;
2550
}
2551
2552
const List<Node *> &selection = editor_selection->get_top_selected_node_list();
2553
2554
for (Node *E : selection) {
2555
Node3D *sp = Object::cast_to<Node3D>(E);
2556
if (!sp) {
2557
continue;
2558
}
2559
2560
spatial_editor->emit_signal(SNAME("transform_key_request"), sp, "", sp->get_transform());
2561
}
2562
2563
set_message(TTR("Animation Key Inserted."));
2564
}
2565
if (ED_IS_SHORTCUT("spatial_editor/cancel_transform", p_event) && _edit.mode != TRANSFORM_NONE) {
2566
cancel_transform();
2567
}
2568
if (!is_freelook_active() && !k->is_echo()) {
2569
if (ED_IS_SHORTCUT("spatial_editor/instant_translate", p_event) && (_edit.mode != TRANSFORM_TRANSLATE || collision_reposition)) {
2570
if (_edit.mode == TRANSFORM_NONE) {
2571
begin_transform(TRANSFORM_TRANSLATE, true);
2572
} else if (_edit.instant || collision_reposition) {
2573
commit_transform();
2574
begin_transform(TRANSFORM_TRANSLATE, true);
2575
}
2576
}
2577
if (ED_IS_SHORTCUT("spatial_editor/instant_rotate", p_event) && _edit.mode != TRANSFORM_ROTATE) {
2578
if (_edit.mode == TRANSFORM_NONE) {
2579
begin_transform(TRANSFORM_ROTATE, true);
2580
} else if (_edit.instant || collision_reposition) {
2581
commit_transform();
2582
begin_transform(TRANSFORM_ROTATE, true);
2583
}
2584
}
2585
if (ED_IS_SHORTCUT("spatial_editor/instant_scale", p_event) && _edit.mode != TRANSFORM_SCALE) {
2586
if (_edit.mode == TRANSFORM_NONE) {
2587
begin_transform(TRANSFORM_SCALE, true);
2588
} else if (_edit.instant || collision_reposition) {
2589
commit_transform();
2590
begin_transform(TRANSFORM_SCALE, true);
2591
}
2592
}
2593
if (ED_IS_SHORTCUT("spatial_editor/collision_reposition", p_event) && editor_selection->get_top_selected_node_list().size() == 1 && !collision_reposition) {
2594
if (_edit.mode == TRANSFORM_NONE || _edit.instant) {
2595
if (_edit.mode == TRANSFORM_NONE) {
2596
_compute_edit(_edit.mouse_pos);
2597
} else {
2598
commit_transform();
2599
_compute_edit(_edit.mouse_pos);
2600
}
2601
_edit.mode = TRANSFORM_TRANSLATE;
2602
collision_reposition = true;
2603
}
2604
}
2605
}
2606
2607
// Freelook doesn't work in orthogonal mode.
2608
if (!orthogonal && ED_IS_SHORTCUT("spatial_editor/freelook_toggle", p_event)) {
2609
set_freelook_active(!is_freelook_active());
2610
2611
} else if (k->get_keycode() == Key::ESCAPE) {
2612
set_freelook_active(false);
2613
}
2614
2615
if (k->get_keycode() == Key::SPACE) {
2616
if (!k->is_pressed()) {
2617
emit_signal(SNAME("toggle_maximize_view"), this);
2618
}
2619
}
2620
2621
if (ED_IS_SHORTCUT("spatial_editor/decrease_fov", p_event)) {
2622
scale_fov(-0.05);
2623
}
2624
2625
if (ED_IS_SHORTCUT("spatial_editor/increase_fov", p_event)) {
2626
scale_fov(0.05);
2627
}
2628
2629
if (ED_IS_SHORTCUT("spatial_editor/reset_fov", p_event)) {
2630
reset_fov();
2631
}
2632
}
2633
2634
// freelook uses most of the useful shortcuts, like save, so its ok
2635
// to consider freelook active as end of the line for future events.
2636
if (freelook_active) {
2637
accept_event();
2638
}
2639
}
2640
2641
int Node3DEditorViewport::_get_shortcut_input_count(const String &p_name) {
2642
Ref<Shortcut> check_shortcut = ED_GET_SHORTCUT(p_name);
2643
2644
ERR_FAIL_COND_V_MSG(check_shortcut.is_null(), 0, "The Shortcut was null, possible name mismatch.");
2645
2646
return check_shortcut->get_events().size();
2647
}
2648
2649
Node3DEditorViewport::NavigationMode Node3DEditorViewport::_get_nav_mode_from_shortcut_check(ViewportNavMouseButton p_mouse_button, Vector<ShortcutCheckSet> p_shortcut_check_sets, bool p_use_not_empty) {
2650
if (p_use_not_empty) {
2651
for (const ShortcutCheckSet &shortcut_check_set : p_shortcut_check_sets) {
2652
if (shortcut_check_set.mod_pressed && shortcut_check_set.shortcut_not_empty) {
2653
return shortcut_check_set.result_nav_mode;
2654
}
2655
}
2656
} else {
2657
for (const ShortcutCheckSet &shortcut_check_set : p_shortcut_check_sets) {
2658
if (shortcut_check_set.mouse_preference == p_mouse_button && shortcut_check_set.mod_pressed) {
2659
return shortcut_check_set.result_nav_mode;
2660
}
2661
}
2662
}
2663
2664
return NAVIGATION_NONE;
2665
}
2666
2667
void Node3DEditorViewport::_nav_pan(Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative) {
2668
const NavigationScheme nav_scheme = (NavigationScheme)EDITOR_GET("editors/3d/navigation/navigation_scheme").operator int();
2669
const real_t translation_sensitivity = EDITOR_GET("editors/3d/navigation_feel/translation_sensitivity");
2670
2671
real_t pan_speed = translation_sensitivity / 150.0;
2672
if (p_event.is_valid() && nav_scheme == NAVIGATION_MAYA && p_event->is_shift_pressed()) {
2673
pan_speed *= 10;
2674
}
2675
2676
Transform3D camera_transform;
2677
2678
camera_transform.translate_local(cursor.pos);
2679
camera_transform.basis.rotate(Vector3(1, 0, 0), -cursor.x_rot);
2680
camera_transform.basis.rotate(Vector3(0, 1, 0), -cursor.y_rot);
2681
const bool invert_x_axis = EDITOR_GET("editors/3d/navigation/invert_x_axis");
2682
const bool invert_y_axis = EDITOR_GET("editors/3d/navigation/invert_y_axis");
2683
Vector3 translation(
2684
(invert_x_axis ? -1 : 1) * -p_relative.x * pan_speed,
2685
(invert_y_axis ? -1 : 1) * p_relative.y * pan_speed,
2686
0);
2687
translation *= cursor.distance / DISTANCE_DEFAULT;
2688
camera_transform.translate_local(translation);
2689
cursor.pos = camera_transform.origin;
2690
}
2691
2692
void Node3DEditorViewport::_nav_zoom(Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative) {
2693
const NavigationScheme nav_scheme = (NavigationScheme)EDITOR_GET("editors/3d/navigation/navigation_scheme").operator int();
2694
2695
real_t zoom_speed = 1 / 80.0;
2696
if (p_event.is_valid() && nav_scheme == NAVIGATION_MAYA && p_event->is_shift_pressed()) {
2697
zoom_speed *= 10;
2698
}
2699
2700
NavigationZoomStyle zoom_style = (NavigationZoomStyle)EDITOR_GET("editors/3d/navigation/zoom_style").operator int();
2701
if (zoom_style == NAVIGATION_ZOOM_HORIZONTAL) {
2702
if (p_relative.x > 0) {
2703
scale_cursor_distance(1 - p_relative.x * zoom_speed);
2704
} else if (p_relative.x < 0) {
2705
scale_cursor_distance(1.0 / (1 + p_relative.x * zoom_speed));
2706
}
2707
} else {
2708
if (p_relative.y > 0) {
2709
scale_cursor_distance(1 + p_relative.y * zoom_speed);
2710
} else if (p_relative.y < 0) {
2711
scale_cursor_distance(1.0 / (1 - p_relative.y * zoom_speed));
2712
}
2713
}
2714
}
2715
2716
void Node3DEditorViewport::_nav_orbit(Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative) {
2717
if (lock_rotation) {
2718
_nav_pan(p_event, p_relative);
2719
return;
2720
}
2721
2722
if (orthogonal && auto_orthogonal) {
2723
_menu_option(VIEW_PERSPECTIVE);
2724
}
2725
2726
const real_t degrees_per_pixel = EDITOR_GET("editors/3d/navigation_feel/orbit_sensitivity");
2727
const real_t radians_per_pixel = Math::deg_to_rad(degrees_per_pixel);
2728
const bool invert_y_axis = EDITOR_GET("editors/3d/navigation/invert_y_axis");
2729
const bool invert_x_axis = EDITOR_GET("editors/3d/navigation/invert_x_axis");
2730
2731
if (invert_y_axis) {
2732
cursor.x_rot -= p_relative.y * radians_per_pixel;
2733
} else {
2734
cursor.x_rot += p_relative.y * radians_per_pixel;
2735
}
2736
// Clamp the Y rotation to roughly -90..90 degrees so the user can't look upside-down and end up disoriented.
2737
cursor.x_rot = CLAMP(cursor.x_rot, -1.57, 1.57);
2738
2739
if (invert_x_axis) {
2740
cursor.y_rot -= p_relative.x * radians_per_pixel;
2741
} else {
2742
cursor.y_rot += p_relative.x * radians_per_pixel;
2743
}
2744
view_type = VIEW_TYPE_USER;
2745
_update_name();
2746
}
2747
2748
void Node3DEditorViewport::_nav_look(Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative) {
2749
if (orthogonal) {
2750
_nav_pan(p_event, p_relative);
2751
return;
2752
}
2753
2754
if (orthogonal && auto_orthogonal) {
2755
_menu_option(VIEW_PERSPECTIVE);
2756
}
2757
2758
// Scale mouse sensitivity with camera FOV scale when zoomed in to make it easier to point at things.
2759
const real_t degrees_per_pixel = real_t(EDITOR_GET("editors/3d/freelook/freelook_sensitivity")) * MIN(1.0, cursor.fov_scale);
2760
const real_t radians_per_pixel = Math::deg_to_rad(degrees_per_pixel);
2761
const bool invert_y_axis = EDITOR_GET("editors/3d/navigation/invert_y_axis");
2762
2763
// Note: do NOT assume the camera has the "current" transform, because it is interpolated and may have "lag".
2764
const Transform3D prev_camera_transform = to_camera_transform(cursor);
2765
2766
if (invert_y_axis) {
2767
cursor.x_rot -= p_relative.y * radians_per_pixel;
2768
} else {
2769
cursor.x_rot += p_relative.y * radians_per_pixel;
2770
}
2771
// Clamp the Y rotation to roughly -90..90 degrees so the user can't look upside-down and end up disoriented.
2772
cursor.x_rot = CLAMP(cursor.x_rot, -1.57, 1.57);
2773
2774
cursor.y_rot += p_relative.x * radians_per_pixel;
2775
2776
// Look is like the opposite of Orbit: the focus point rotates around the camera
2777
Transform3D camera_transform = to_camera_transform(cursor);
2778
Vector3 pos = camera_transform.xform(Vector3(0, 0, 0));
2779
Vector3 prev_pos = prev_camera_transform.xform(Vector3(0, 0, 0));
2780
Vector3 diff = prev_pos - pos;
2781
cursor.pos += diff;
2782
2783
view_type = VIEW_TYPE_USER;
2784
_update_name();
2785
}
2786
2787
void Node3DEditorViewport::set_freelook_active(bool active_now) {
2788
if (!freelook_active && active_now) {
2789
// Sync camera cursor to cursor to "cut" interpolation jumps due to changing referential
2790
cursor = camera_cursor;
2791
2792
// Make sure eye_pos is synced, because freelook referential is eye pos rather than orbit pos
2793
Vector3 forward = to_camera_transform(cursor).basis.xform(Vector3(0, 0, -1));
2794
cursor.eye_pos = cursor.pos - cursor.distance * forward;
2795
// Also sync the camera cursor, otherwise switching to freelook will be trippy if inertia is active
2796
camera_cursor.eye_pos = cursor.eye_pos;
2797
2798
if (EDITOR_GET("editors/3d/freelook/freelook_speed_zoom_link")) {
2799
// Re-adjust freelook speed from the current zoom level
2800
real_t base_speed = EDITOR_GET("editors/3d/freelook/freelook_base_speed");
2801
freelook_speed = base_speed * cursor.distance;
2802
}
2803
2804
previous_mouse_position = get_local_mouse_position();
2805
2806
spatial_editor->set_freelook_viewport(this);
2807
2808
// Hide mouse like in an FPS (warping doesn't work)
2809
Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_CAPTURED);
2810
2811
} else if (freelook_active && !active_now) {
2812
// Sync camera cursor to cursor to "cut" interpolation jumps due to changing referential
2813
cursor = camera_cursor;
2814
2815
spatial_editor->set_freelook_viewport(nullptr);
2816
2817
// Restore mouse
2818
Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE);
2819
2820
// Restore the previous mouse position when leaving freelook mode.
2821
// This is done because leaving `Input.MOUSE_MODE_CAPTURED` will center the cursor
2822
// due to OS limitations.
2823
warp_mouse(previous_mouse_position);
2824
}
2825
2826
freelook_active = active_now;
2827
}
2828
2829
void Node3DEditorViewport::scale_fov(real_t p_fov_offset) {
2830
cursor.fov_scale = CLAMP(cursor.fov_scale + p_fov_offset, 0.1, 2.5);
2831
surface->queue_redraw();
2832
}
2833
2834
void Node3DEditorViewport::reset_fov() {
2835
cursor.fov_scale = 1.0;
2836
surface->queue_redraw();
2837
}
2838
2839
void Node3DEditorViewport::scale_cursor_distance(real_t scale) {
2840
real_t min_distance = MAX(camera->get_near() * 4, ZOOM_FREELOOK_MIN);
2841
real_t max_distance = MIN(camera->get_far() / 4, ZOOM_FREELOOK_MAX);
2842
if (unlikely(min_distance > max_distance)) {
2843
cursor.distance = (min_distance + max_distance) / 2;
2844
} else {
2845
cursor.distance = CLAMP(cursor.distance * scale, min_distance, max_distance);
2846
}
2847
2848
if (cursor.distance == max_distance || cursor.distance == min_distance) {
2849
zoom_failed_attempts_count++;
2850
} else {
2851
zoom_failed_attempts_count = 0;
2852
}
2853
2854
zoom_indicator_delay = ZOOM_FREELOOK_INDICATOR_DELAY_S;
2855
surface->queue_redraw();
2856
}
2857
2858
void Node3DEditorViewport::scale_freelook_speed(real_t scale) {
2859
real_t min_speed = MAX(camera->get_near() * 4, ZOOM_FREELOOK_MIN);
2860
real_t max_speed = MIN(camera->get_far() / 4, ZOOM_FREELOOK_MAX);
2861
if (unlikely(min_speed > max_speed)) {
2862
freelook_speed = (min_speed + max_speed) / 2;
2863
} else {
2864
freelook_speed = CLAMP(freelook_speed * scale, min_speed, max_speed);
2865
}
2866
2867
zoom_indicator_delay = ZOOM_FREELOOK_INDICATOR_DELAY_S;
2868
surface->queue_redraw();
2869
}
2870
2871
bool Node3DEditorViewport::_is_nav_modifier_pressed(const String &p_name) {
2872
return _is_shortcut_empty(p_name) || Input::get_singleton()->is_action_pressed(p_name);
2873
}
2874
2875
bool Node3DEditorViewport::_is_shortcut_empty(const String &p_name) {
2876
Ref<Shortcut> check_shortcut = ED_GET_SHORTCUT(p_name);
2877
2878
ERR_FAIL_COND_V_MSG(check_shortcut.is_null(), true, "The Shortcut was null, possible name mismatch.");
2879
2880
return check_shortcut->get_events().is_empty();
2881
}
2882
2883
Point2 Node3DEditorViewport::_get_warped_mouse_motion(const Ref<InputEventMouseMotion> &p_ev_mouse_motion) const {
2884
Point2 relative;
2885
if (bool(EDITOR_GET("editors/3d/navigation/warped_mouse_panning"))) {
2886
relative = Input::get_singleton()->warp_mouse_motion(p_ev_mouse_motion, surface->get_global_rect());
2887
} else {
2888
relative = p_ev_mouse_motion->get_relative();
2889
}
2890
return relative;
2891
}
2892
2893
void Node3DEditorViewport::_update_freelook(real_t delta) {
2894
if (!is_freelook_active()) {
2895
return;
2896
}
2897
2898
const FreelookNavigationScheme navigation_scheme = (FreelookNavigationScheme)EDITOR_GET("editors/3d/freelook/freelook_navigation_scheme").operator int();
2899
2900
Vector3 forward;
2901
if (navigation_scheme == FREELOOK_FULLY_AXIS_LOCKED) {
2902
// Forward/backward keys will always go straight forward/backward, never moving on the Y axis.
2903
forward = Vector3(0, 0, -1).rotated(Vector3(0, 1, 0), camera->get_rotation().y);
2904
} else {
2905
// Forward/backward keys will be relative to the camera pitch.
2906
forward = camera->get_transform().basis.xform(Vector3(0, 0, -1));
2907
}
2908
2909
const Vector3 right = camera->get_transform().basis.xform(Vector3(1, 0, 0));
2910
2911
Vector3 up;
2912
if (navigation_scheme == FREELOOK_PARTIALLY_AXIS_LOCKED || navigation_scheme == FREELOOK_FULLY_AXIS_LOCKED) {
2913
// Up/down keys will always go up/down regardless of camera pitch.
2914
up = Vector3(0, 1, 0);
2915
} else {
2916
// Up/down keys will be relative to the camera pitch.
2917
up = camera->get_transform().basis.xform(Vector3(0, 1, 0));
2918
}
2919
2920
Vector3 direction;
2921
2922
// Use actions from the inputmap, as this is the only way to reliably detect input in this method.
2923
// See #54469 for more discussion and explanation.
2924
Input *inp = Input::get_singleton();
2925
if (inp->is_action_pressed("spatial_editor/freelook_left")) {
2926
direction -= right;
2927
}
2928
if (inp->is_action_pressed("spatial_editor/freelook_right")) {
2929
direction += right;
2930
}
2931
if (inp->is_action_pressed("spatial_editor/freelook_forward")) {
2932
direction += forward;
2933
}
2934
if (inp->is_action_pressed("spatial_editor/freelook_backwards")) {
2935
direction -= forward;
2936
}
2937
if (inp->is_action_pressed("spatial_editor/freelook_up")) {
2938
direction += up;
2939
}
2940
if (inp->is_action_pressed("spatial_editor/freelook_down")) {
2941
direction -= up;
2942
}
2943
2944
real_t speed = freelook_speed;
2945
2946
if (inp->is_action_pressed("spatial_editor/freelook_speed_modifier")) {
2947
speed *= 3.0;
2948
}
2949
if (inp->is_action_pressed("spatial_editor/freelook_slow_modifier")) {
2950
speed *= 0.333333;
2951
}
2952
2953
const Vector3 motion = direction * speed * delta;
2954
cursor.pos += motion;
2955
cursor.eye_pos += motion;
2956
}
2957
2958
void Node3DEditorViewport::set_message(const String &p_message, float p_time) {
2959
message = p_message;
2960
message_time = p_time;
2961
}
2962
2963
void Node3DEditorPlugin::edited_scene_changed() {
2964
for (uint32_t i = 0; i < Node3DEditor::VIEWPORTS_COUNT; i++) {
2965
Node3DEditorViewport *viewport = Node3DEditor::get_singleton()->get_editor_viewport(i);
2966
if (viewport->is_visible()) {
2967
viewport->notification(Control::NOTIFICATION_VISIBILITY_CHANGED);
2968
}
2969
}
2970
}
2971
2972
void Node3DEditorViewport::_project_settings_changed() {
2973
// Update shadow atlas if changed.
2974
int shadowmap_size = GLOBAL_GET("rendering/lights_and_shadows/positional_shadow/atlas_size");
2975
bool shadowmap_16_bits = GLOBAL_GET("rendering/lights_and_shadows/positional_shadow/atlas_16_bits");
2976
int atlas_q0 = GLOBAL_GET("rendering/lights_and_shadows/positional_shadow/atlas_quadrant_0_subdiv");
2977
int atlas_q1 = GLOBAL_GET("rendering/lights_and_shadows/positional_shadow/atlas_quadrant_1_subdiv");
2978
int atlas_q2 = GLOBAL_GET("rendering/lights_and_shadows/positional_shadow/atlas_quadrant_2_subdiv");
2979
int atlas_q3 = GLOBAL_GET("rendering/lights_and_shadows/positional_shadow/atlas_quadrant_3_subdiv");
2980
2981
viewport->set_positional_shadow_atlas_size(shadowmap_size);
2982
viewport->set_positional_shadow_atlas_16_bits(shadowmap_16_bits);
2983
viewport->set_positional_shadow_atlas_quadrant_subdiv(0, Viewport::PositionalShadowAtlasQuadrantSubdiv(atlas_q0));
2984
viewport->set_positional_shadow_atlas_quadrant_subdiv(1, Viewport::PositionalShadowAtlasQuadrantSubdiv(atlas_q1));
2985
viewport->set_positional_shadow_atlas_quadrant_subdiv(2, Viewport::PositionalShadowAtlasQuadrantSubdiv(atlas_q2));
2986
viewport->set_positional_shadow_atlas_quadrant_subdiv(3, Viewport::PositionalShadowAtlasQuadrantSubdiv(atlas_q3));
2987
2988
_update_shrink();
2989
2990
// Update MSAA, screen-space AA and debanding if changed
2991
2992
const int msaa_mode = GLOBAL_GET("rendering/anti_aliasing/quality/msaa_3d");
2993
viewport->set_msaa_3d(Viewport::MSAA(msaa_mode));
2994
const int ssaa_mode = GLOBAL_GET("rendering/anti_aliasing/quality/screen_space_aa");
2995
viewport->set_screen_space_aa(Viewport::ScreenSpaceAA(ssaa_mode));
2996
const bool use_taa = GLOBAL_GET("rendering/anti_aliasing/quality/use_taa");
2997
viewport->set_use_taa(use_taa);
2998
2999
const bool transparent_background = GLOBAL_GET("rendering/viewport/transparent_background");
3000
viewport->set_transparent_background(transparent_background);
3001
3002
const bool use_hdr_2d = GLOBAL_GET("rendering/viewport/hdr_2d");
3003
viewport->set_use_hdr_2d(use_hdr_2d);
3004
3005
const bool use_debanding = GLOBAL_GET("rendering/anti_aliasing/quality/use_debanding");
3006
viewport->set_use_debanding(use_debanding);
3007
3008
const bool use_occlusion_culling = GLOBAL_GET("rendering/occlusion_culling/use_occlusion_culling");
3009
viewport->set_use_occlusion_culling(use_occlusion_culling);
3010
3011
const float mesh_lod_threshold = GLOBAL_GET("rendering/mesh_lod/lod_change/threshold_pixels");
3012
viewport->set_mesh_lod_threshold(mesh_lod_threshold);
3013
3014
const Viewport::Scaling3DMode scaling_3d_mode = Viewport::Scaling3DMode(int(GLOBAL_GET("rendering/scaling_3d/mode")));
3015
viewport->set_scaling_3d_mode(scaling_3d_mode);
3016
3017
const float scaling_3d_scale = GLOBAL_GET("rendering/scaling_3d/scale");
3018
viewport->set_scaling_3d_scale(scaling_3d_scale);
3019
3020
const float fsr_sharpness = GLOBAL_GET("rendering/scaling_3d/fsr_sharpness");
3021
viewport->set_fsr_sharpness(fsr_sharpness);
3022
3023
const float texture_mipmap_bias = GLOBAL_GET("rendering/textures/default_filters/texture_mipmap_bias");
3024
viewport->set_texture_mipmap_bias(texture_mipmap_bias);
3025
3026
const Viewport::AnisotropicFiltering anisotropic_filtering_level = Viewport::AnisotropicFiltering(int(GLOBAL_GET("rendering/textures/default_filters/anisotropic_filtering_level")));
3027
viewport->set_anisotropic_filtering_level(anisotropic_filtering_level);
3028
}
3029
3030
static void override_button_stylebox(Button *p_button, const Ref<StyleBox> p_stylebox) {
3031
p_button->begin_bulk_theme_override();
3032
p_button->add_theme_style_override(CoreStringName(normal), p_stylebox);
3033
p_button->add_theme_style_override("normal_mirrored", p_stylebox);
3034
p_button->add_theme_style_override(SceneStringName(hover), p_stylebox);
3035
p_button->add_theme_style_override("hover_mirrored", p_stylebox);
3036
p_button->add_theme_style_override("hover_pressed", p_stylebox);
3037
p_button->add_theme_style_override("hover_pressed_mirrored", p_stylebox);
3038
p_button->add_theme_style_override(SceneStringName(pressed), p_stylebox);
3039
p_button->add_theme_style_override("pressed_mirrored", p_stylebox);
3040
p_button->add_theme_style_override("focus", p_stylebox);
3041
p_button->add_theme_style_override("focus_mirrored", p_stylebox);
3042
p_button->add_theme_style_override("disabled", p_stylebox);
3043
p_button->add_theme_style_override("disabled_mirrored", p_stylebox);
3044
p_button->end_bulk_theme_override();
3045
}
3046
3047
void Node3DEditorViewport::_notification(int p_what) {
3048
switch (p_what) {
3049
case NOTIFICATION_TRANSLATION_CHANGED: {
3050
_update_name();
3051
_update_centered_labels();
3052
message_time = MIN(message_time, 0.001); // Make it disappear.
3053
3054
Key key = (OS::get_singleton()->has_feature("macos") || OS::get_singleton()->has_feature("web_macos") || OS::get_singleton()->has_feature("web_ios")) ? Key::META : Key::CTRL;
3055
preview_material_label_desc->set_text(vformat(TTR("Drag and drop to override the material of any geometry node.\nHold %s when dropping to override a specific surface."), find_keycode_name(key)));
3056
3057
const int item_count = display_submenu->get_item_count();
3058
for (int i = 0; i < item_count; i++) {
3059
const Array item_data = display_submenu->get_item_metadata(i);
3060
if (item_data.is_empty()) {
3061
continue;
3062
}
3063
3064
SupportedRenderingMethods rendering_methods = item_data[0];
3065
String base_tooltip = item_data[1];
3066
3067
bool disabled = false;
3068
String disabled_tooltip;
3069
switch (rendering_methods) {
3070
case SupportedRenderingMethods::ALL:
3071
break;
3072
case SupportedRenderingMethods::FORWARD_PLUS_MOBILE:
3073
disabled = OS::get_singleton()->get_current_rendering_method() == "gl_compatibility";
3074
disabled_tooltip = TTR("This debug draw mode is not supported when using the Compatibility rendering method.");
3075
break;
3076
case SupportedRenderingMethods::FORWARD_PLUS:
3077
disabled = OS::get_singleton()->get_current_rendering_method() == "gl_compatibility" || OS::get_singleton()->get_current_rendering_method() == "mobile";
3078
disabled_tooltip = TTR("This debug draw mode is not supported when using the Mobile or Compatibility rendering methods.");
3079
break;
3080
}
3081
3082
display_submenu->set_item_disabled(i, disabled);
3083
String tooltip = TTR(base_tooltip);
3084
if (disabled) {
3085
if (tooltip.is_empty()) {
3086
tooltip = disabled_tooltip;
3087
} else {
3088
tooltip += "\n\n" + disabled_tooltip;
3089
}
3090
}
3091
display_submenu->set_item_tooltip(i, tooltip);
3092
}
3093
} break;
3094
3095
case NOTIFICATION_READY: {
3096
ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &Node3DEditorViewport::_project_settings_changed));
3097
} break;
3098
3099
case NOTIFICATION_VISIBILITY_CHANGED: {
3100
bool vp_visible = is_visible_in_tree();
3101
3102
set_process(vp_visible);
3103
set_physics_process(vp_visible);
3104
3105
if (vp_visible) {
3106
orthogonal = view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_ORTHOGONAL));
3107
_update_name();
3108
_update_camera(0);
3109
} else {
3110
set_freelook_active(false);
3111
}
3112
callable_mp(this, &Node3DEditorViewport::update_transform_gizmo_view).call_deferred();
3113
} break;
3114
3115
case NOTIFICATION_RESIZED: {
3116
callable_mp(this, &Node3DEditorViewport::update_transform_gizmo_view).call_deferred();
3117
} break;
3118
3119
case NOTIFICATION_PROCESS: {
3120
if (ruler->is_inside_tree()) {
3121
Vector3 start_pos = ruler_start_point->get_global_position();
3122
Vector3 end_pos = ruler_end_point->get_global_position();
3123
3124
geometry->clear_surfaces();
3125
geometry->surface_begin(Mesh::PRIMITIVE_LINES);
3126
geometry->surface_add_vertex(start_pos);
3127
geometry->surface_add_vertex(end_pos);
3128
geometry->surface_end();
3129
3130
float distance = start_pos.distance_to(end_pos);
3131
ruler_label->set_text(TS->format_number(vformat("%.3f m", distance)));
3132
3133
Vector3 center = (start_pos + end_pos) / 2;
3134
Vector2 screen_position = camera->unproject_position(center) - (ruler_label->get_custom_minimum_size() / 2);
3135
ruler_label->set_position(screen_position);
3136
}
3137
3138
real_t delta = get_process_delta_time();
3139
3140
if (zoom_indicator_delay > 0) {
3141
zoom_indicator_delay -= delta;
3142
if (zoom_indicator_delay <= 0) {
3143
surface->queue_redraw();
3144
zoom_limit_label->hide();
3145
}
3146
}
3147
3148
_update_navigation_controls_visibility();
3149
_update_freelook(delta);
3150
3151
Node *scene_root = SceneTreeDock::get_singleton()->get_editor_data()->get_edited_scene_root();
3152
if (previewing_cinema && scene_root != nullptr) {
3153
Camera3D *cam = scene_root->get_viewport()->get_camera_3d();
3154
if (cam != nullptr && cam != previewing) {
3155
//then switch the viewport's camera to the scene's viewport camera
3156
if (previewing != nullptr) {
3157
previewing->disconnect(SceneStringName(tree_exited), callable_mp(this, &Node3DEditorViewport::_preview_exited_scene));
3158
previewing->disconnect(CoreStringName(property_list_changed), callable_mp(this, &Node3DEditorViewport::_preview_camera_property_changed));
3159
}
3160
previewing = cam;
3161
previewing->connect(SceneStringName(tree_exited), callable_mp(this, &Node3DEditorViewport::_preview_exited_scene));
3162
previewing->connect(CoreStringName(property_list_changed), callable_mp(this, &Node3DEditorViewport::_preview_camera_property_changed));
3163
RS::get_singleton()->viewport_attach_camera(viewport->get_viewport_rid(), cam->get_camera());
3164
surface->queue_redraw();
3165
}
3166
}
3167
3168
if (_camera_moved_externally()) {
3169
// If camera moved after this plugin last set it, presumably a tool script has moved it, accept the new camera transform as the cursor position.
3170
_apply_camera_transform_to_cursor();
3171
_update_camera(0);
3172
} else {
3173
_update_camera(delta);
3174
}
3175
3176
const HashMap<Node *, Object *> &selection = editor_selection->get_selection();
3177
3178
bool changed = false;
3179
bool exist = false;
3180
3181
for (const KeyValue<Node *, Object *> &E : selection) {
3182
Node3D *sp = Object::cast_to<Node3D>(E.key);
3183
if (!sp) {
3184
continue;
3185
}
3186
3187
Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);
3188
if (!se) {
3189
continue;
3190
}
3191
3192
Transform3D t = sp->get_global_gizmo_transform();
3193
if (!t.is_finite()) {
3194
continue;
3195
}
3196
AABB new_aabb = _calculate_spatial_bounds(sp);
3197
3198
exist = true;
3199
if (se->last_xform == t && se->aabb == new_aabb && !se->last_xform_dirty) {
3200
continue;
3201
}
3202
changed = true;
3203
se->last_xform_dirty = false;
3204
se->last_xform = t;
3205
3206
se->aabb = new_aabb;
3207
3208
Transform3D t_offset = t;
3209
3210
// apply AABB scaling before item's global transform
3211
{
3212
const Vector3 offset(0.005, 0.005, 0.005);
3213
Basis aabb_s;
3214
aabb_s.scale(se->aabb.size + offset);
3215
t.translate_local(se->aabb.position - offset / 2);
3216
t.basis = t.basis * aabb_s;
3217
}
3218
{
3219
const Vector3 offset(0.01, 0.01, 0.01);
3220
Basis aabb_s;
3221
aabb_s.scale(se->aabb.size + offset);
3222
t_offset.translate_local(se->aabb.position - offset / 2);
3223
t_offset.basis = t_offset.basis * aabb_s;
3224
}
3225
3226
RenderingServer::get_singleton()->instance_set_transform(se->sbox_instance, t);
3227
RenderingServer::get_singleton()->instance_set_transform(se->sbox_instance_offset, t_offset);
3228
RenderingServer::get_singleton()->instance_set_transform(se->sbox_instance_xray, t);
3229
RenderingServer::get_singleton()->instance_set_transform(se->sbox_instance_xray_offset, t_offset);
3230
}
3231
3232
if (changed || (spatial_editor->is_gizmo_visible() && !exist)) {
3233
spatial_editor->update_transform_gizmo();
3234
}
3235
3236
if (message_time > 0) {
3237
if (message != last_message) {
3238
surface->queue_redraw();
3239
last_message = message;
3240
}
3241
3242
message_time -= get_process_delta_time();
3243
if (message_time < 0) {
3244
surface->queue_redraw();
3245
}
3246
}
3247
3248
bool show_info = view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_INFORMATION));
3249
if (show_info != info_panel->is_visible()) {
3250
info_panel->set_visible(show_info);
3251
}
3252
3253
Camera3D *current_camera;
3254
3255
if (previewing) {
3256
current_camera = previewing;
3257
} else {
3258
current_camera = camera;
3259
}
3260
3261
if (show_info) {
3262
const String viewport_size = vformat(U"%d × %d", viewport->get_size().x, viewport->get_size().y);
3263
String text;
3264
text += vformat(TTR("X: %s\n"), rtos(current_camera->get_position().x).pad_decimals(1));
3265
text += vformat(TTR("Y: %s\n"), rtos(current_camera->get_position().y).pad_decimals(1));
3266
text += vformat(TTR("Z: %s\n"), rtos(current_camera->get_position().z).pad_decimals(1));
3267
text += "\n";
3268
text += vformat(
3269
TTR("Size: %s (%.1fMP)\n"),
3270
viewport_size,
3271
viewport->get_size().x * viewport->get_size().y * 0.000001);
3272
3273
text += "\n";
3274
text += vformat(TTR("Objects: %d\n"), viewport->get_render_info(Viewport::RENDER_INFO_TYPE_VISIBLE, Viewport::RENDER_INFO_OBJECTS_IN_FRAME));
3275
text += vformat(TTR("Primitives: %d\n"), viewport->get_render_info(Viewport::RENDER_INFO_TYPE_VISIBLE, Viewport::RENDER_INFO_PRIMITIVES_IN_FRAME));
3276
text += vformat(TTR("Draw Calls: %d"), viewport->get_render_info(Viewport::RENDER_INFO_TYPE_VISIBLE, Viewport::RENDER_INFO_DRAW_CALLS_IN_FRAME));
3277
3278
info_label->set_text(text);
3279
}
3280
3281
// FPS Counter.
3282
bool show_fps = view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_FRAME_TIME));
3283
3284
if (show_fps != frame_time_panel->is_visible()) {
3285
frame_time_panel->set_visible(show_fps);
3286
RS::get_singleton()->viewport_set_measure_render_time(viewport->get_viewport_rid(), show_fps);
3287
for (int i = 0; i < FRAME_TIME_HISTORY; i++) {
3288
// Initialize to 120 FPS, so that the initial estimation until we get enough data is always reasonable.
3289
cpu_time_history[i] = 8.333333;
3290
gpu_time_history[i] = 8.333333;
3291
}
3292
cpu_time_history_index = 0;
3293
gpu_time_history_index = 0;
3294
}
3295
if (show_fps) {
3296
cpu_time_history[cpu_time_history_index] = RS::get_singleton()->viewport_get_measured_render_time_cpu(viewport->get_viewport_rid());
3297
cpu_time_history_index = (cpu_time_history_index + 1) % FRAME_TIME_HISTORY;
3298
double cpu_time = 0.0;
3299
for (int i = 0; i < FRAME_TIME_HISTORY; i++) {
3300
cpu_time += cpu_time_history[i];
3301
}
3302
cpu_time /= FRAME_TIME_HISTORY;
3303
// Prevent unrealistically low values.
3304
cpu_time = MAX(0.01, cpu_time);
3305
3306
gpu_time_history[gpu_time_history_index] = RS::get_singleton()->viewport_get_measured_render_time_gpu(viewport->get_viewport_rid());
3307
gpu_time_history_index = (gpu_time_history_index + 1) % FRAME_TIME_HISTORY;
3308
double gpu_time = 0.0;
3309
for (int i = 0; i < FRAME_TIME_HISTORY; i++) {
3310
gpu_time += gpu_time_history[i];
3311
}
3312
gpu_time /= FRAME_TIME_HISTORY;
3313
// Prevent division by zero for the FPS counter (and unrealistically low values).
3314
// This limits the reported FPS to 100000.
3315
gpu_time = MAX(0.01, gpu_time);
3316
3317
// Color labels depending on performance level ("good" = green, "OK" = yellow, "bad" = red).
3318
// Middle point is at 15 ms.
3319
cpu_time_label->set_text(vformat(TTR("CPU Time: %s ms"), rtos(cpu_time).pad_decimals(2)));
3320
cpu_time_label->add_theme_color_override(
3321
SceneStringName(font_color),
3322
frame_time_gradient->get_color_at_offset(
3323
Math::remap(cpu_time, 0, 30, 0, 1)));
3324
3325
gpu_time_label->set_text(vformat(TTR("GPU Time: %s ms"), rtos(gpu_time).pad_decimals(2)));
3326
// Middle point is at 15 ms.
3327
gpu_time_label->add_theme_color_override(
3328
SceneStringName(font_color),
3329
frame_time_gradient->get_color_at_offset(
3330
Math::remap(gpu_time, 0, 30, 0, 1)));
3331
3332
const double fps = 1000.0 / gpu_time;
3333
fps_label->set_text(vformat(TTR("FPS: %d"), fps));
3334
// Middle point is at 60 FPS.
3335
fps_label->add_theme_color_override(
3336
SceneStringName(font_color),
3337
frame_time_gradient->get_color_at_offset(
3338
Math::remap(fps, 110, 10, 0, 1)));
3339
}
3340
3341
if (lock_rotation) {
3342
float locked_half_width = locked_label->get_size().width / 2.0f;
3343
locked_label->set_anchor_and_offset(SIDE_LEFT, 0.5f, -locked_half_width);
3344
}
3345
} break;
3346
3347
case NOTIFICATION_PHYSICS_PROCESS: {
3348
if (collision_reposition) {
3349
Node3D *selected_node = nullptr;
3350
3351
if (ruler->is_inside_tree()) {
3352
if (ruler_start_point->is_visible()) {
3353
selected_node = ruler_end_point;
3354
} else {
3355
selected_node = ruler_start_point;
3356
}
3357
} else {
3358
const List<Node *> &selection = editor_selection->get_top_selected_node_list();
3359
if (selection.size() == 1) {
3360
selected_node = Object::cast_to<Node3D>(selection.front()->get());
3361
}
3362
}
3363
3364
if (selected_node) {
3365
if (!ruler->is_inside_tree()) {
3366
double snap = EDITOR_GET("interface/inspector/default_float_step");
3367
int snap_step_decimals = Math::range_step_decimals(snap);
3368
set_message(TTR("Translating:") + " (" + String::num(selected_node->get_global_position().x, snap_step_decimals) + ", " +
3369
String::num(selected_node->get_global_position().y, snap_step_decimals) + ", " + String::num(selected_node->get_global_position().z, snap_step_decimals) + ")");
3370
}
3371
3372
selected_node->set_global_position(spatial_editor->snap_point(_get_instance_position(_edit.mouse_pos, selected_node)));
3373
3374
if (ruler->is_inside_tree() && !ruler_start_point->is_visible()) {
3375
ruler_end_point->set_global_position(ruler_start_point->get_global_position());
3376
ruler_start_point->set_visible(true);
3377
ruler_end_point->set_visible(true);
3378
ruler_label->set_visible(true);
3379
}
3380
}
3381
}
3382
3383
if (!update_preview_node) {
3384
return;
3385
}
3386
if (preview_node->is_inside_tree()) {
3387
preview_node_pos = spatial_editor->snap_point(_get_instance_position(preview_node_viewport_pos, preview_node));
3388
double snap = EDITOR_GET("interface/inspector/default_float_step");
3389
int snap_step_decimals = Math::range_step_decimals(snap);
3390
set_message(TTR("Instantiating:") + " (" + String::num(preview_node_pos.x, snap_step_decimals) + ", " +
3391
String::num(preview_node_pos.y, snap_step_decimals) + ", " + String::num(preview_node_pos.z, snap_step_decimals) + ")");
3392
Transform3D preview_gl_transform = Transform3D(Basis(), preview_node_pos);
3393
preview_node->set_global_transform(preview_gl_transform);
3394
if (!preview_node->is_visible()) {
3395
preview_node->show();
3396
}
3397
}
3398
update_preview_node = false;
3399
} break;
3400
3401
case NOTIFICATION_APPLICATION_FOCUS_OUT:
3402
case NOTIFICATION_WM_WINDOW_FOCUS_OUT: {
3403
set_freelook_active(false);
3404
cursor.region_select = false;
3405
surface->queue_redraw();
3406
} break;
3407
3408
case NOTIFICATION_ENTER_TREE: {
3409
surface->connect(SceneStringName(draw), callable_mp(this, &Node3DEditorViewport::_draw));
3410
surface->connect(SceneStringName(gui_input), callable_mp(this, &Node3DEditorViewport::_sinput));
3411
surface->connect(SceneStringName(mouse_entered), callable_mp(this, &Node3DEditorViewport::_surface_mouse_enter));
3412
surface->connect(SceneStringName(mouse_exited), callable_mp(this, &Node3DEditorViewport::_surface_mouse_exit));
3413
surface->connect(SceneStringName(focus_entered), callable_mp(this, &Node3DEditorViewport::_surface_focus_enter));
3414
surface->connect(SceneStringName(focus_exited), callable_mp(this, &Node3DEditorViewport::_surface_focus_exit));
3415
3416
_init_gizmo_instance(index);
3417
} break;
3418
3419
case NOTIFICATION_EXIT_TREE: {
3420
_finish_gizmo_instances();
3421
} break;
3422
3423
case NOTIFICATION_THEME_CHANGED: {
3424
_update_centered_labels();
3425
3426
view_display_menu->set_button_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl")));
3427
preview_camera->set_button_icon(get_editor_theme_icon(SNAME("Camera3D")));
3428
Control *gui_base = EditorNode::get_singleton()->get_gui_base();
3429
3430
const Ref<StyleBox> &information_3d_stylebox = gui_base->get_theme_stylebox(SNAME("Information3dViewport"), EditorStringName(EditorStyles));
3431
3432
override_button_stylebox(view_display_menu, information_3d_stylebox);
3433
override_button_stylebox(translation_preview_button, information_3d_stylebox);
3434
override_button_stylebox(preview_camera, information_3d_stylebox);
3435
3436
frame_time_gradient->set_color(0, get_theme_color(SNAME("success_color"), EditorStringName(Editor)));
3437
frame_time_gradient->set_color(1, get_theme_color(SNAME("warning_color"), EditorStringName(Editor)));
3438
frame_time_gradient->set_color(2, get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
3439
3440
info_panel->add_theme_style_override(SceneStringName(panel), information_3d_stylebox);
3441
3442
frame_time_panel->add_theme_style_override(SceneStringName(panel), information_3d_stylebox);
3443
// Set a minimum width to prevent the width from changing all the time
3444
// when numbers vary rapidly. This minimum width is set based on a
3445
// GPU time of 999.99 ms in the current editor language.
3446
const float min_width = get_theme_font(SNAME("main"), EditorStringName(EditorFonts))->get_string_size(vformat(TTR("GPU Time: %s ms"), 999.99)).x;
3447
frame_time_panel->set_custom_minimum_size(Size2(min_width, 0) * EDSCALE);
3448
frame_time_vbox->add_theme_constant_override("separation", Math::round(-1 * EDSCALE));
3449
3450
cinema_label->add_theme_style_override(CoreStringName(normal), information_3d_stylebox);
3451
locked_label->add_theme_style_override(CoreStringName(normal), information_3d_stylebox);
3452
3453
ruler_label->add_theme_color_override(SceneStringName(font_color), Color(1.0, 0.9, 0.0, 1.0));
3454
ruler_label->add_theme_color_override("font_outline_color", Color(0.0, 0.0, 0.0, 1.0));
3455
ruler_label->add_theme_constant_override("outline_size", 4 * EDSCALE);
3456
ruler_label->add_theme_font_size_override(SceneStringName(font_size), 15 * EDSCALE);
3457
ruler_label->add_theme_font_override(SceneStringName(font), get_theme_font(SNAME("bold"), EditorStringName(EditorFonts)));
3458
} break;
3459
3460
case NOTIFICATION_DRAG_END: {
3461
// Clear preview material when dropped outside applicable object.
3462
if (spatial_editor->get_preview_material().is_valid() && !is_drag_successful()) {
3463
_reset_preview_material();
3464
_remove_preview_material();
3465
} else {
3466
_remove_preview_node();
3467
}
3468
} break;
3469
}
3470
}
3471
3472
static void draw_indicator_bar(Control &p_surface, real_t p_fill, const Ref<Texture2D> p_icon, const Ref<Font> p_font, int p_font_size, const String &p_text, const Color &p_color) {
3473
// Adjust bar size from control height
3474
const Vector2 surface_size = p_surface.get_size();
3475
const real_t h = surface_size.y / 2.0;
3476
const real_t y = (surface_size.y - h) / 2.0;
3477
3478
const Rect2 r(10 * EDSCALE, y, 6 * EDSCALE, h);
3479
const real_t sy = r.size.y * p_fill;
3480
3481
// Note: because this bar appears over the viewport, it has to stay readable for any background color
3482
// Draw both neutral dark and bright colors to account this
3483
p_surface.draw_rect(r, p_color * Color(1, 1, 1, 0.2));
3484
p_surface.draw_rect(Rect2(r.position.x, r.position.y + r.size.y - sy, r.size.x, sy), p_color * Color(1, 1, 1, 0.6));
3485
p_surface.draw_rect(r.grow(1), Color(0, 0, 0, 0.7), false, Math::round(EDSCALE));
3486
3487
const Vector2 icon_size = p_icon->get_size();
3488
const Vector2 icon_pos = Vector2(r.position.x - (icon_size.x - r.size.x) / 2, r.position.y + r.size.y + 2 * EDSCALE);
3489
p_surface.draw_texture(p_icon, icon_pos, p_color);
3490
3491
// Draw text below the bar (for speed/zoom information).
3492
p_surface.draw_string_outline(p_font, Vector2(icon_pos.x, icon_pos.y + icon_size.y + 16 * EDSCALE), p_text, HORIZONTAL_ALIGNMENT_LEFT, -1.f, p_font_size, Math::round(2 * EDSCALE), Color(0, 0, 0));
3493
p_surface.draw_string(p_font, Vector2(icon_pos.x, icon_pos.y + icon_size.y + 16 * EDSCALE), p_text, HORIZONTAL_ALIGNMENT_LEFT, -1.f, p_font_size, p_color);
3494
}
3495
3496
void Node3DEditorViewport::_draw() {
3497
EditorPluginList *over_plugin_list = EditorNode::get_singleton()->get_editor_plugins_over();
3498
if (!over_plugin_list->is_empty()) {
3499
over_plugin_list->forward_3d_draw_over_viewport(surface);
3500
}
3501
3502
EditorPluginList *force_over_plugin_list = EditorNode::get_singleton()->get_editor_plugins_force_over();
3503
if (!force_over_plugin_list->is_empty()) {
3504
force_over_plugin_list->forward_3d_force_draw_over_viewport(surface);
3505
}
3506
3507
if (surface->has_focus() || rotation_control->has_focus()) {
3508
Size2 size = surface->get_size();
3509
Rect2 r = Rect2(Point2(), size);
3510
get_theme_stylebox(SNAME("FocusViewport"), EditorStringName(EditorStyles))->draw(surface->get_canvas_item(), r);
3511
}
3512
3513
if (cursor.region_select) {
3514
const Rect2 selection_rect = Rect2(cursor.region_begin, cursor.region_end - cursor.region_begin);
3515
3516
surface->draw_rect(
3517
selection_rect,
3518
get_theme_color(SNAME("box_selection_fill_color"), EditorStringName(Editor)));
3519
3520
surface->draw_rect(
3521
selection_rect,
3522
get_theme_color(SNAME("box_selection_stroke_color"), EditorStringName(Editor)),
3523
false,
3524
Math::round(EDSCALE));
3525
}
3526
3527
RID ci = surface->get_canvas_item();
3528
3529
if (message_time > 0) {
3530
Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("Label"));
3531
int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("Label"));
3532
Point2 msgpos = Point2(10 * EDSCALE, get_size().y - 14 * EDSCALE);
3533
font->draw_string(ci, msgpos + Point2(1, 1), message, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color(0, 0, 0, 0.8));
3534
font->draw_string(ci, msgpos + Point2(-1, -1), message, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color(0, 0, 0, 0.8));
3535
font->draw_string(ci, msgpos, message, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color(1, 1, 1, 1));
3536
}
3537
3538
if (_edit.mode == TRANSFORM_ROTATE && _edit.show_rotation_line) {
3539
Point2 center = point_to_screen(_edit.center);
3540
3541
Color handle_color;
3542
switch (_edit.plane) {
3543
case TRANSFORM_X_AXIS:
3544
handle_color = get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor));
3545
break;
3546
case TRANSFORM_Y_AXIS:
3547
handle_color = get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor));
3548
break;
3549
case TRANSFORM_Z_AXIS:
3550
handle_color = get_theme_color(SNAME("axis_z_color"), EditorStringName(Editor));
3551
break;
3552
default:
3553
handle_color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
3554
break;
3555
}
3556
handle_color = handle_color.from_hsv(handle_color.get_h(), 0.25, 1.0, 1);
3557
3558
RenderingServer::get_singleton()->canvas_item_add_line(
3559
ci,
3560
_edit.mouse_pos,
3561
center,
3562
handle_color,
3563
Math::round(2 * EDSCALE));
3564
}
3565
if (previewing) {
3566
Size2 ss = Size2(GLOBAL_GET("display/window/size/viewport_width"), GLOBAL_GET("display/window/size/viewport_height"));
3567
float aspect = ss.aspect();
3568
Size2 s = get_size();
3569
3570
Rect2 draw_rect;
3571
3572
switch (previewing->get_keep_aspect_mode()) {
3573
case Camera3D::KEEP_WIDTH: {
3574
draw_rect.size = Size2(s.width, s.width / aspect);
3575
draw_rect.position.x = 0;
3576
draw_rect.position.y = (s.height - draw_rect.size.y) * 0.5;
3577
3578
} break;
3579
case Camera3D::KEEP_HEIGHT: {
3580
draw_rect.size = Size2(s.height * aspect, s.height);
3581
draw_rect.position.y = 0;
3582
draw_rect.position.x = (s.width - draw_rect.size.x) * 0.5;
3583
3584
} break;
3585
}
3586
3587
draw_rect = Rect2(Vector2(), s).intersection(draw_rect);
3588
3589
surface->draw_rect(draw_rect, Color(0.6, 0.6, 0.1, 0.5), false, Math::round(2 * EDSCALE));
3590
3591
} else {
3592
if (zoom_indicator_delay > 0.0) {
3593
if (is_freelook_active()) {
3594
// Show speed
3595
3596
real_t min_speed = MAX(camera->get_near() * 4, ZOOM_FREELOOK_MIN);
3597
real_t max_speed = MIN(camera->get_far() / 4, ZOOM_FREELOOK_MAX);
3598
real_t scale_length = (max_speed - min_speed);
3599
3600
if (!Math::is_zero_approx(scale_length)) {
3601
real_t logscale_t = 1.0 - Math::log1p(freelook_speed - min_speed) / Math::log1p(scale_length);
3602
3603
// Display the freelook speed to help the user get a better sense of scale.
3604
const int precision = freelook_speed < 1.0 ? 2 : 1;
3605
draw_indicator_bar(
3606
*surface,
3607
1.0 - logscale_t,
3608
get_editor_theme_icon(SNAME("ViewportSpeed")),
3609
get_theme_font(SceneStringName(font), SNAME("Label")),
3610
get_theme_font_size(SceneStringName(font_size), SNAME("Label")),
3611
vformat("%s m/s", String::num(freelook_speed).pad_decimals(precision)),
3612
Color(1.0, 0.95, 0.7));
3613
}
3614
3615
} else {
3616
// Show zoom
3617
zoom_limit_label->set_visible(zoom_failed_attempts_count > 15);
3618
3619
real_t min_distance = MAX(camera->get_near() * 4, ZOOM_FREELOOK_MIN);
3620
real_t max_distance = MIN(camera->get_far() / 4, ZOOM_FREELOOK_MAX);
3621
real_t scale_length = (max_distance - min_distance);
3622
3623
if (!Math::is_zero_approx(scale_length)) {
3624
real_t logscale_t = 1.0 - Math::log1p(cursor.distance - min_distance) / Math::log1p(scale_length);
3625
3626
// Display the zoom center distance to help the user get a better sense of scale.
3627
const int precision = cursor.distance < 1.0 ? 2 : 1;
3628
draw_indicator_bar(
3629
*surface,
3630
logscale_t,
3631
get_editor_theme_icon(SNAME("ViewportZoom")),
3632
get_theme_font(SceneStringName(font), SNAME("Label")),
3633
get_theme_font_size(SceneStringName(font_size), SNAME("Label")),
3634
vformat("%s m", String::num(cursor.distance).pad_decimals(precision)),
3635
Color(0.7, 0.95, 1.0));
3636
}
3637
}
3638
}
3639
}
3640
}
3641
3642
bool Node3DEditorViewport::_camera_moved_externally() {
3643
Transform3D t = camera->get_global_transform();
3644
return !t.is_equal_approx(last_camera_transform);
3645
}
3646
3647
void Node3DEditorViewport::_apply_camera_transform_to_cursor() {
3648
// Effectively the reverse of to_camera_transform, use camera transform to set cursor position and rotation.
3649
const Transform3D camera_transform = camera->get_camera_transform();
3650
const Basis basis = camera_transform.basis;
3651
3652
real_t distance;
3653
if (orthogonal) {
3654
distance = (get_zfar() - get_znear()) / 2.0;
3655
} else {
3656
distance = cursor.distance;
3657
}
3658
3659
cursor.pos = camera_transform.origin - basis.get_column(2) * distance;
3660
3661
cursor.x_rot = -camera_transform.basis.get_euler().x;
3662
cursor.y_rot = -camera_transform.basis.get_euler().y;
3663
}
3664
3665
void Node3DEditorViewport::_menu_option(int p_option) {
3666
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
3667
switch (p_option) {
3668
case VIEW_TOP: {
3669
cursor.y_rot = 0;
3670
cursor.x_rot = Math::PI / 2.0;
3671
set_message(TTR("Top View."), 2);
3672
view_type = VIEW_TYPE_TOP;
3673
_set_auto_orthogonal();
3674
_update_name();
3675
3676
} break;
3677
case VIEW_BOTTOM: {
3678
cursor.y_rot = 0;
3679
cursor.x_rot = -Math::PI / 2.0;
3680
set_message(TTR("Bottom View."), 2);
3681
view_type = VIEW_TYPE_BOTTOM;
3682
_set_auto_orthogonal();
3683
_update_name();
3684
3685
} break;
3686
case VIEW_LEFT: {
3687
cursor.x_rot = 0;
3688
cursor.y_rot = Math::PI / 2.0;
3689
set_message(TTR("Left View."), 2);
3690
view_type = VIEW_TYPE_LEFT;
3691
_set_auto_orthogonal();
3692
_update_name();
3693
3694
} break;
3695
case VIEW_RIGHT: {
3696
cursor.x_rot = 0;
3697
cursor.y_rot = -Math::PI / 2.0;
3698
set_message(TTR("Right View."), 2);
3699
view_type = VIEW_TYPE_RIGHT;
3700
_set_auto_orthogonal();
3701
_update_name();
3702
3703
} break;
3704
case VIEW_FRONT: {
3705
cursor.x_rot = 0;
3706
cursor.y_rot = 0;
3707
set_message(TTR("Front View."), 2);
3708
view_type = VIEW_TYPE_FRONT;
3709
_set_auto_orthogonal();
3710
_update_name();
3711
3712
} break;
3713
case VIEW_REAR: {
3714
cursor.x_rot = 0;
3715
cursor.y_rot = Math::PI;
3716
set_message(TTR("Rear View."), 2);
3717
view_type = VIEW_TYPE_REAR;
3718
_set_auto_orthogonal();
3719
_update_name();
3720
3721
} break;
3722
case VIEW_CENTER_TO_ORIGIN: {
3723
cursor.pos = Vector3(0, 0, 0);
3724
3725
} break;
3726
case VIEW_CENTER_TO_SELECTION: {
3727
focus_selection();
3728
3729
} break;
3730
case VIEW_ALIGN_TRANSFORM_WITH_VIEW: {
3731
if (!get_selected_count()) {
3732
break;
3733
}
3734
3735
Transform3D camera_transform = camera->get_global_transform();
3736
3737
const List<Node *> &selection = editor_selection->get_top_selected_node_list();
3738
3739
undo_redo->create_action(TTR("Align Transform with View"));
3740
for (Node *E : selection) {
3741
Node3D *sp = Object::cast_to<Node3D>(E);
3742
if (!sp) {
3743
continue;
3744
}
3745
3746
Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);
3747
if (!se) {
3748
continue;
3749
}
3750
3751
Transform3D xform = camera_transform;
3752
if (orthogonal) {
3753
Vector3 offset = camera_transform.basis.xform(Vector3(0, 0, cursor.distance));
3754
xform.origin = cursor.pos + offset;
3755
} else {
3756
xform.scale_basis(sp->get_scale());
3757
}
3758
3759
if (Object::cast_to<Decal>(E)) {
3760
// Adjust rotation to match Decal's default orientation.
3761
// This makes the decal "look" in the same direction as the camera,
3762
// rather than pointing down relative to the camera orientation.
3763
xform.basis.rotate_local(Vector3(1, 0, 0), Math::TAU * 0.25);
3764
}
3765
3766
Node3D *parent = sp->get_parent_node_3d();
3767
Transform3D local_xform = parent ? parent->get_global_transform().affine_inverse() * xform : xform;
3768
undo_redo->add_do_method(sp, "set_transform", local_xform);
3769
undo_redo->add_undo_method(sp, "set_transform", sp->get_local_gizmo_transform());
3770
}
3771
undo_redo->commit_action();
3772
3773
} break;
3774
case VIEW_ALIGN_ROTATION_WITH_VIEW: {
3775
if (!get_selected_count()) {
3776
break;
3777
}
3778
3779
Transform3D camera_transform = camera->get_global_transform();
3780
3781
const List<Node *> &selection = editor_selection->get_top_selected_node_list();
3782
3783
undo_redo->create_action(TTR("Align Rotation with View"));
3784
for (Node *E : selection) {
3785
Node3D *sp = Object::cast_to<Node3D>(E);
3786
if (!sp) {
3787
continue;
3788
}
3789
3790
Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);
3791
if (!se) {
3792
continue;
3793
}
3794
3795
Basis basis = camera_transform.basis;
3796
3797
if (Object::cast_to<Decal>(E)) {
3798
// Adjust rotation to match Decal's default orientation.
3799
// This makes the decal "look" in the same direction as the camera,
3800
// rather than pointing down relative to the camera orientation.
3801
basis.rotate_local(Vector3(1, 0, 0), Math::TAU * 0.25);
3802
}
3803
3804
undo_redo->add_do_method(sp, "set_rotation", basis.get_euler_normalized());
3805
undo_redo->add_undo_method(sp, "set_rotation", sp->get_rotation());
3806
}
3807
undo_redo->commit_action();
3808
3809
} break;
3810
case VIEW_ENVIRONMENT: {
3811
int idx = view_display_menu->get_popup()->get_item_index(VIEW_ENVIRONMENT);
3812
bool current = view_display_menu->get_popup()->is_item_checked(idx);
3813
current = !current;
3814
if (current) {
3815
camera->set_environment(Ref<Resource>());
3816
} else {
3817
camera->set_environment(Node3DEditor::get_singleton()->get_viewport_environment());
3818
}
3819
3820
view_display_menu->get_popup()->set_item_checked(idx, current);
3821
3822
} break;
3823
case VIEW_PERSPECTIVE: {
3824
view_display_menu->get_popup()->set_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_PERSPECTIVE), true);
3825
view_display_menu->get_popup()->set_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_ORTHOGONAL), false);
3826
orthogonal = false;
3827
auto_orthogonal = false;
3828
callable_mp(this, &Node3DEditorViewport::update_transform_gizmo_view).call_deferred();
3829
_update_camera(0);
3830
_update_name();
3831
3832
} break;
3833
case VIEW_ORTHOGONAL: {
3834
view_display_menu->get_popup()->set_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_PERSPECTIVE), false);
3835
view_display_menu->get_popup()->set_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_ORTHOGONAL), true);
3836
orthogonal = true;
3837
auto_orthogonal = false;
3838
callable_mp(this, &Node3DEditorViewport::update_transform_gizmo_view).call_deferred();
3839
_update_camera(0);
3840
_update_name();
3841
} break;
3842
case VIEW_SWITCH_PERSPECTIVE_ORTHOGONAL: {
3843
_menu_option(orthogonal ? VIEW_PERSPECTIVE : VIEW_ORTHOGONAL);
3844
3845
} break;
3846
case VIEW_AUTO_ORTHOGONAL: {
3847
int idx = view_display_menu->get_popup()->get_item_index(VIEW_AUTO_ORTHOGONAL);
3848
bool current = view_display_menu->get_popup()->is_item_checked(idx);
3849
current = !current;
3850
view_display_menu->get_popup()->set_item_checked(idx, current);
3851
if (auto_orthogonal) {
3852
auto_orthogonal = false;
3853
_update_name();
3854
}
3855
} break;
3856
case VIEW_LOCK_ROTATION: {
3857
int idx = view_display_menu->get_popup()->get_item_index(VIEW_LOCK_ROTATION);
3858
bool current = view_display_menu->get_popup()->is_item_checked(idx);
3859
_set_lock_view_rotation(!current);
3860
3861
} break;
3862
case VIEW_AUDIO_LISTENER: {
3863
int idx = view_display_menu->get_popup()->get_item_index(VIEW_AUDIO_LISTENER);
3864
bool current = view_display_menu->get_popup()->is_item_checked(idx);
3865
current = !current;
3866
viewport->set_as_audio_listener_3d(current);
3867
view_display_menu->get_popup()->set_item_checked(idx, current);
3868
3869
} break;
3870
case VIEW_AUDIO_DOPPLER: {
3871
int idx = view_display_menu->get_popup()->get_item_index(VIEW_AUDIO_DOPPLER);
3872
bool current = view_display_menu->get_popup()->is_item_checked(idx);
3873
current = !current;
3874
camera->set_doppler_tracking(current ? Camera3D::DOPPLER_TRACKING_IDLE_STEP : Camera3D::DOPPLER_TRACKING_DISABLED);
3875
view_display_menu->get_popup()->set_item_checked(idx, current);
3876
3877
} break;
3878
case VIEW_CINEMATIC_PREVIEW: {
3879
int idx = view_display_menu->get_popup()->get_item_index(VIEW_CINEMATIC_PREVIEW);
3880
bool current = view_display_menu->get_popup()->is_item_checked(idx);
3881
current = !current;
3882
view_display_menu->get_popup()->set_item_checked(idx, current);
3883
previewing_cinema = true;
3884
_toggle_cinema_preview(current);
3885
3886
cinema_label->set_visible(current);
3887
_update_centered_labels();
3888
surface->queue_redraw();
3889
3890
if (current) {
3891
preview_camera->hide();
3892
} else {
3893
if (previewing != nullptr) {
3894
preview_camera->show();
3895
}
3896
}
3897
} break;
3898
case VIEW_GIZMOS: {
3899
int idx = view_display_menu->get_popup()->get_item_index(VIEW_GIZMOS);
3900
bool current = view_display_menu->get_popup()->is_item_checked(idx);
3901
current = !current;
3902
uint32_t layers = camera->get_cull_mask();
3903
layers &= ~(1 << GIZMO_EDIT_LAYER);
3904
if (current) {
3905
layers |= (1 << GIZMO_EDIT_LAYER);
3906
}
3907
camera->set_cull_mask(layers);
3908
view_display_menu->get_popup()->set_item_checked(idx, current);
3909
3910
} break;
3911
case VIEW_TRANSFORM_GIZMO: {
3912
int idx = view_display_menu->get_popup()->get_item_index(VIEW_TRANSFORM_GIZMO);
3913
bool current = view_display_menu->get_popup()->is_item_checked(idx);
3914
current = !current;
3915
transform_gizmo_visible = current;
3916
3917
spatial_editor->update_transform_gizmo();
3918
view_display_menu->get_popup()->set_item_checked(idx, current);
3919
} break;
3920
case VIEW_HALF_RESOLUTION: {
3921
int idx = view_display_menu->get_popup()->get_item_index(VIEW_HALF_RESOLUTION);
3922
bool current = view_display_menu->get_popup()->is_item_checked(idx);
3923
view_display_menu->get_popup()->set_item_checked(idx, !current);
3924
_update_shrink();
3925
} break;
3926
case VIEW_INFORMATION: {
3927
int idx = view_display_menu->get_popup()->get_item_index(VIEW_INFORMATION);
3928
bool current = view_display_menu->get_popup()->is_item_checked(idx);
3929
view_display_menu->get_popup()->set_item_checked(idx, !current);
3930
3931
} break;
3932
case VIEW_FRAME_TIME: {
3933
int idx = view_display_menu->get_popup()->get_item_index(VIEW_FRAME_TIME);
3934
bool current = view_display_menu->get_popup()->is_item_checked(idx);
3935
view_display_menu->get_popup()->set_item_checked(idx, !current);
3936
} break;
3937
case VIEW_GRID: {
3938
int idx = view_display_menu->get_popup()->get_item_index(VIEW_GRID);
3939
bool current = view_display_menu->get_popup()->is_item_checked(idx);
3940
current = !current;
3941
uint32_t layers = camera->get_cull_mask();
3942
layers &= ~(1 << GIZMO_GRID_LAYER);
3943
if (current) {
3944
layers |= (1 << GIZMO_GRID_LAYER);
3945
}
3946
camera->set_cull_mask(layers);
3947
view_display_menu->get_popup()->set_item_checked(idx, current);
3948
} break;
3949
case VIEW_DISPLAY_NORMAL:
3950
case VIEW_DISPLAY_WIREFRAME:
3951
case VIEW_DISPLAY_OVERDRAW:
3952
case VIEW_DISPLAY_UNSHADED:
3953
case VIEW_DISPLAY_LIGHTING:
3954
case VIEW_DISPLAY_NORMAL_BUFFER:
3955
case VIEW_DISPLAY_DEBUG_SHADOW_ATLAS:
3956
case VIEW_DISPLAY_DEBUG_DIRECTIONAL_SHADOW_ATLAS:
3957
case VIEW_DISPLAY_DEBUG_VOXEL_GI_ALBEDO:
3958
case VIEW_DISPLAY_DEBUG_VOXEL_GI_LIGHTING:
3959
case VIEW_DISPLAY_DEBUG_VOXEL_GI_EMISSION:
3960
case VIEW_DISPLAY_DEBUG_SCENE_LUMINANCE:
3961
case VIEW_DISPLAY_DEBUG_SSAO:
3962
case VIEW_DISPLAY_DEBUG_SSIL:
3963
case VIEW_DISPLAY_DEBUG_PSSM_SPLITS:
3964
case VIEW_DISPLAY_DEBUG_DECAL_ATLAS:
3965
case VIEW_DISPLAY_DEBUG_SDFGI:
3966
case VIEW_DISPLAY_DEBUG_SDFGI_PROBES:
3967
case VIEW_DISPLAY_DEBUG_GI_BUFFER:
3968
case VIEW_DISPLAY_DEBUG_DISABLE_LOD:
3969
case VIEW_DISPLAY_DEBUG_CLUSTER_OMNI_LIGHTS:
3970
case VIEW_DISPLAY_DEBUG_CLUSTER_SPOT_LIGHTS:
3971
case VIEW_DISPLAY_DEBUG_CLUSTER_DECALS:
3972
case VIEW_DISPLAY_DEBUG_CLUSTER_REFLECTION_PROBES:
3973
case VIEW_DISPLAY_DEBUG_OCCLUDERS:
3974
case VIEW_DISPLAY_MOTION_VECTORS:
3975
case VIEW_DISPLAY_INTERNAL_BUFFER: {
3976
static const int display_options[] = {
3977
VIEW_DISPLAY_NORMAL,
3978
VIEW_DISPLAY_WIREFRAME,
3979
VIEW_DISPLAY_OVERDRAW,
3980
VIEW_DISPLAY_UNSHADED,
3981
VIEW_DISPLAY_LIGHTING,
3982
VIEW_DISPLAY_NORMAL_BUFFER,
3983
VIEW_DISPLAY_DEBUG_SHADOW_ATLAS,
3984
VIEW_DISPLAY_DEBUG_DIRECTIONAL_SHADOW_ATLAS,
3985
VIEW_DISPLAY_DEBUG_VOXEL_GI_ALBEDO,
3986
VIEW_DISPLAY_DEBUG_VOXEL_GI_LIGHTING,
3987
VIEW_DISPLAY_DEBUG_VOXEL_GI_EMISSION,
3988
VIEW_DISPLAY_DEBUG_SCENE_LUMINANCE,
3989
VIEW_DISPLAY_DEBUG_SSAO,
3990
VIEW_DISPLAY_DEBUG_SSIL,
3991
VIEW_DISPLAY_DEBUG_GI_BUFFER,
3992
VIEW_DISPLAY_DEBUG_DISABLE_LOD,
3993
VIEW_DISPLAY_DEBUG_PSSM_SPLITS,
3994
VIEW_DISPLAY_DEBUG_DECAL_ATLAS,
3995
VIEW_DISPLAY_DEBUG_SDFGI,
3996
VIEW_DISPLAY_DEBUG_SDFGI_PROBES,
3997
VIEW_DISPLAY_DEBUG_CLUSTER_OMNI_LIGHTS,
3998
VIEW_DISPLAY_DEBUG_CLUSTER_SPOT_LIGHTS,
3999
VIEW_DISPLAY_DEBUG_CLUSTER_DECALS,
4000
VIEW_DISPLAY_DEBUG_CLUSTER_REFLECTION_PROBES,
4001
VIEW_DISPLAY_DEBUG_OCCLUDERS,
4002
VIEW_DISPLAY_MOTION_VECTORS,
4003
VIEW_DISPLAY_INTERNAL_BUFFER,
4004
VIEW_MAX
4005
};
4006
static const Viewport::DebugDraw debug_draw_modes[] = {
4007
Viewport::DEBUG_DRAW_DISABLED,
4008
Viewport::DEBUG_DRAW_WIREFRAME,
4009
Viewport::DEBUG_DRAW_OVERDRAW,
4010
Viewport::DEBUG_DRAW_UNSHADED,
4011
Viewport::DEBUG_DRAW_LIGHTING,
4012
Viewport::DEBUG_DRAW_NORMAL_BUFFER,
4013
Viewport::DEBUG_DRAW_SHADOW_ATLAS,
4014
Viewport::DEBUG_DRAW_DIRECTIONAL_SHADOW_ATLAS,
4015
Viewport::DEBUG_DRAW_VOXEL_GI_ALBEDO,
4016
Viewport::DEBUG_DRAW_VOXEL_GI_LIGHTING,
4017
Viewport::DEBUG_DRAW_VOXEL_GI_EMISSION,
4018
Viewport::DEBUG_DRAW_SCENE_LUMINANCE,
4019
Viewport::DEBUG_DRAW_SSAO,
4020
Viewport::DEBUG_DRAW_SSIL,
4021
Viewport::DEBUG_DRAW_GI_BUFFER,
4022
Viewport::DEBUG_DRAW_DISABLE_LOD,
4023
Viewport::DEBUG_DRAW_PSSM_SPLITS,
4024
Viewport::DEBUG_DRAW_DECAL_ATLAS,
4025
Viewport::DEBUG_DRAW_SDFGI,
4026
Viewport::DEBUG_DRAW_SDFGI_PROBES,
4027
Viewport::DEBUG_DRAW_CLUSTER_OMNI_LIGHTS,
4028
Viewport::DEBUG_DRAW_CLUSTER_SPOT_LIGHTS,
4029
Viewport::DEBUG_DRAW_CLUSTER_DECALS,
4030
Viewport::DEBUG_DRAW_CLUSTER_REFLECTION_PROBES,
4031
Viewport::DEBUG_DRAW_OCCLUDERS,
4032
Viewport::DEBUG_DRAW_MOTION_VECTORS,
4033
Viewport::DEBUG_DRAW_INTERNAL_BUFFER,
4034
};
4035
4036
for (int idx = 0; display_options[idx] != VIEW_MAX; idx++) {
4037
int id = display_options[idx];
4038
int item_idx = view_display_menu->get_popup()->get_item_index(id);
4039
if (item_idx != -1) {
4040
view_display_menu->get_popup()->set_item_checked(item_idx, id == p_option);
4041
}
4042
item_idx = display_submenu->get_item_index(id);
4043
if (item_idx != -1) {
4044
display_submenu->set_item_checked(item_idx, id == p_option);
4045
}
4046
4047
if (id == p_option) {
4048
viewport->set_debug_draw(debug_draw_modes[idx]);
4049
}
4050
}
4051
} break;
4052
}
4053
}
4054
4055
void Node3DEditorViewport::_set_auto_orthogonal() {
4056
if (!orthogonal && view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_AUTO_ORTHOGONAL))) {
4057
_menu_option(VIEW_ORTHOGONAL);
4058
auto_orthogonal = true;
4059
}
4060
}
4061
4062
void Node3DEditorViewport::_preview_exited_scene() {
4063
preview_camera->disconnect(SceneStringName(toggled), callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview));
4064
preview_camera->set_pressed(false);
4065
_toggle_camera_preview(false);
4066
preview_camera->connect(SceneStringName(toggled), callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview));
4067
view_display_menu->show();
4068
}
4069
4070
void Node3DEditorViewport::_preview_camera_property_changed() {
4071
if (previewing) {
4072
surface->queue_redraw();
4073
}
4074
}
4075
4076
void Node3DEditorViewport::_update_centered_labels() {
4077
if (cinema_label->is_visible()) {
4078
cinema_label->reset_size();
4079
float cinema_half_width = cinema_label->get_size().width / 2.0f;
4080
cinema_label->set_anchor_and_offset(SIDE_LEFT, 0.5f, -cinema_half_width);
4081
}
4082
}
4083
4084
void Node3DEditorViewport::_init_gizmo_instance(int p_idx) {
4085
uint32_t layer = 1 << (GIZMO_BASE_LAYER + p_idx);
4086
4087
for (int i = 0; i < 3; i++) {
4088
move_gizmo_instance[i] = RS::get_singleton()->instance_create();
4089
RS::get_singleton()->instance_set_base(move_gizmo_instance[i], spatial_editor->get_move_gizmo(i)->get_rid());
4090
RS::get_singleton()->instance_set_scenario(move_gizmo_instance[i], get_tree()->get_root()->get_world_3d()->get_scenario());
4091
RS::get_singleton()->instance_set_visible(move_gizmo_instance[i], false);
4092
RS::get_singleton()->instance_geometry_set_cast_shadows_setting(move_gizmo_instance[i], RS::SHADOW_CASTING_SETTING_OFF);
4093
RS::get_singleton()->instance_set_layer_mask(move_gizmo_instance[i], layer);
4094
RS::get_singleton()->instance_geometry_set_flag(move_gizmo_instance[i], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);
4095
RS::get_singleton()->instance_geometry_set_flag(move_gizmo_instance[i], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);
4096
4097
move_plane_gizmo_instance[i] = RS::get_singleton()->instance_create();
4098
RS::get_singleton()->instance_set_base(move_plane_gizmo_instance[i], spatial_editor->get_move_plane_gizmo(i)->get_rid());
4099
RS::get_singleton()->instance_set_scenario(move_plane_gizmo_instance[i], get_tree()->get_root()->get_world_3d()->get_scenario());
4100
RS::get_singleton()->instance_set_visible(move_plane_gizmo_instance[i], false);
4101
RS::get_singleton()->instance_geometry_set_cast_shadows_setting(move_plane_gizmo_instance[i], RS::SHADOW_CASTING_SETTING_OFF);
4102
RS::get_singleton()->instance_set_layer_mask(move_plane_gizmo_instance[i], layer);
4103
RS::get_singleton()->instance_geometry_set_flag(move_plane_gizmo_instance[i], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);
4104
RS::get_singleton()->instance_geometry_set_flag(move_plane_gizmo_instance[i], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);
4105
4106
rotate_gizmo_instance[i] = RS::get_singleton()->instance_create();
4107
RS::get_singleton()->instance_set_base(rotate_gizmo_instance[i], spatial_editor->get_rotate_gizmo(i)->get_rid());
4108
RS::get_singleton()->instance_set_scenario(rotate_gizmo_instance[i], get_tree()->get_root()->get_world_3d()->get_scenario());
4109
RS::get_singleton()->instance_set_visible(rotate_gizmo_instance[i], false);
4110
RS::get_singleton()->instance_geometry_set_cast_shadows_setting(rotate_gizmo_instance[i], RS::SHADOW_CASTING_SETTING_OFF);
4111
RS::get_singleton()->instance_set_layer_mask(rotate_gizmo_instance[i], layer);
4112
RS::get_singleton()->instance_geometry_set_flag(rotate_gizmo_instance[i], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);
4113
RS::get_singleton()->instance_geometry_set_flag(rotate_gizmo_instance[i], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);
4114
4115
scale_gizmo_instance[i] = RS::get_singleton()->instance_create();
4116
RS::get_singleton()->instance_set_base(scale_gizmo_instance[i], spatial_editor->get_scale_gizmo(i)->get_rid());
4117
RS::get_singleton()->instance_set_scenario(scale_gizmo_instance[i], get_tree()->get_root()->get_world_3d()->get_scenario());
4118
RS::get_singleton()->instance_set_visible(scale_gizmo_instance[i], false);
4119
RS::get_singleton()->instance_geometry_set_cast_shadows_setting(scale_gizmo_instance[i], RS::SHADOW_CASTING_SETTING_OFF);
4120
RS::get_singleton()->instance_set_layer_mask(scale_gizmo_instance[i], layer);
4121
RS::get_singleton()->instance_geometry_set_flag(scale_gizmo_instance[i], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);
4122
RS::get_singleton()->instance_geometry_set_flag(scale_gizmo_instance[i], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);
4123
4124
scale_plane_gizmo_instance[i] = RS::get_singleton()->instance_create();
4125
RS::get_singleton()->instance_set_base(scale_plane_gizmo_instance[i], spatial_editor->get_scale_plane_gizmo(i)->get_rid());
4126
RS::get_singleton()->instance_set_scenario(scale_plane_gizmo_instance[i], get_tree()->get_root()->get_world_3d()->get_scenario());
4127
RS::get_singleton()->instance_set_visible(scale_plane_gizmo_instance[i], false);
4128
RS::get_singleton()->instance_geometry_set_cast_shadows_setting(scale_plane_gizmo_instance[i], RS::SHADOW_CASTING_SETTING_OFF);
4129
RS::get_singleton()->instance_set_layer_mask(scale_plane_gizmo_instance[i], layer);
4130
RS::get_singleton()->instance_geometry_set_flag(scale_plane_gizmo_instance[i], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);
4131
RS::get_singleton()->instance_geometry_set_flag(scale_plane_gizmo_instance[i], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);
4132
4133
axis_gizmo_instance[i] = RS::get_singleton()->instance_create();
4134
RS::get_singleton()->instance_set_base(axis_gizmo_instance[i], spatial_editor->get_axis_gizmo(i)->get_rid());
4135
RS::get_singleton()->instance_set_scenario(axis_gizmo_instance[i], get_tree()->get_root()->get_world_3d()->get_scenario());
4136
RS::get_singleton()->instance_set_visible(axis_gizmo_instance[i], true);
4137
RS::get_singleton()->instance_geometry_set_cast_shadows_setting(axis_gizmo_instance[i], RS::SHADOW_CASTING_SETTING_OFF);
4138
RS::get_singleton()->instance_set_layer_mask(axis_gizmo_instance[i], layer);
4139
RS::get_singleton()->instance_geometry_set_flag(axis_gizmo_instance[i], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);
4140
RS::get_singleton()->instance_geometry_set_flag(axis_gizmo_instance[i], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);
4141
}
4142
4143
// Rotation white outline
4144
rotate_gizmo_instance[3] = RS::get_singleton()->instance_create();
4145
RS::get_singleton()->instance_set_base(rotate_gizmo_instance[3], spatial_editor->get_rotate_gizmo(3)->get_rid());
4146
RS::get_singleton()->instance_set_scenario(rotate_gizmo_instance[3], get_tree()->get_root()->get_world_3d()->get_scenario());
4147
RS::get_singleton()->instance_set_visible(rotate_gizmo_instance[3], false);
4148
RS::get_singleton()->instance_geometry_set_cast_shadows_setting(rotate_gizmo_instance[3], RS::SHADOW_CASTING_SETTING_OFF);
4149
RS::get_singleton()->instance_set_layer_mask(rotate_gizmo_instance[3], layer);
4150
RS::get_singleton()->instance_geometry_set_flag(rotate_gizmo_instance[3], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);
4151
RS::get_singleton()->instance_geometry_set_flag(rotate_gizmo_instance[3], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);
4152
}
4153
4154
void Node3DEditorViewport::_finish_gizmo_instances() {
4155
ERR_FAIL_NULL(RenderingServer::get_singleton());
4156
for (int i = 0; i < 3; i++) {
4157
RS::get_singleton()->free(move_gizmo_instance[i]);
4158
RS::get_singleton()->free(move_plane_gizmo_instance[i]);
4159
RS::get_singleton()->free(rotate_gizmo_instance[i]);
4160
RS::get_singleton()->free(scale_gizmo_instance[i]);
4161
RS::get_singleton()->free(scale_plane_gizmo_instance[i]);
4162
RS::get_singleton()->free(axis_gizmo_instance[i]);
4163
}
4164
// Rotation white outline
4165
RS::get_singleton()->free(rotate_gizmo_instance[3]);
4166
}
4167
4168
void Node3DEditorViewport::_toggle_camera_preview(bool p_activate) {
4169
ERR_FAIL_COND(p_activate && !preview);
4170
ERR_FAIL_COND(!p_activate && !previewing);
4171
4172
previewing_camera = p_activate;
4173
_update_navigation_controls_visibility();
4174
4175
if (!p_activate) {
4176
previewing->disconnect(SceneStringName(tree_exiting), callable_mp(this, &Node3DEditorViewport::_preview_exited_scene));
4177
previewing->disconnect(CoreStringName(property_list_changed), callable_mp(this, &Node3DEditorViewport::_preview_camera_property_changed));
4178
previewing = nullptr;
4179
RS::get_singleton()->viewport_attach_camera(viewport->get_viewport_rid(), camera->get_camera()); //restore
4180
if (!preview) {
4181
preview_camera->hide();
4182
}
4183
surface->queue_redraw();
4184
4185
} else {
4186
previewing = preview;
4187
previewing->connect(SceneStringName(tree_exiting), callable_mp(this, &Node3DEditorViewport::_preview_exited_scene));
4188
previewing->connect(CoreStringName(property_list_changed), callable_mp(this, &Node3DEditorViewport::_preview_camera_property_changed));
4189
RS::get_singleton()->viewport_attach_camera(viewport->get_viewport_rid(), preview->get_camera()); //replace
4190
surface->queue_redraw();
4191
}
4192
}
4193
4194
void Node3DEditorViewport::_toggle_cinema_preview(bool p_activate) {
4195
previewing_cinema = p_activate;
4196
_update_navigation_controls_visibility();
4197
4198
if (!previewing_cinema) {
4199
if (previewing != nullptr) {
4200
previewing->disconnect(SceneStringName(tree_exited), callable_mp(this, &Node3DEditorViewport::_preview_exited_scene));
4201
previewing->disconnect(CoreStringName(property_list_changed), callable_mp(this, &Node3DEditorViewport::_preview_camera_property_changed));
4202
}
4203
4204
previewing = nullptr;
4205
RS::get_singleton()->viewport_attach_camera(viewport->get_viewport_rid(), camera->get_camera()); //restore
4206
preview_camera->set_pressed(false);
4207
if (!preview) {
4208
preview_camera->hide();
4209
} else {
4210
preview_camera->show();
4211
}
4212
view_display_menu->show();
4213
surface->queue_redraw();
4214
}
4215
}
4216
4217
void Node3DEditorViewport::_selection_result_pressed(int p_result) {
4218
if (selection_results_menu.size() <= p_result) {
4219
return;
4220
}
4221
4222
clicked = selection_results_menu[p_result]->get_instance_id();
4223
4224
if (clicked.is_valid()) {
4225
_select_clicked(true);
4226
}
4227
4228
selection_results_menu.clear();
4229
}
4230
4231
void Node3DEditorViewport::_selection_menu_hide() {
4232
selection_results.clear();
4233
selection_menu->clear();
4234
selection_menu->reset_size();
4235
}
4236
4237
void Node3DEditorViewport::set_can_preview(Camera3D *p_preview) {
4238
preview = p_preview;
4239
4240
if (!preview_camera->is_pressed() && !previewing_cinema) {
4241
preview_camera->set_visible(p_preview);
4242
}
4243
}
4244
4245
void Node3DEditorViewport::update_transform_gizmo_view() {
4246
if (!is_visible_in_tree()) {
4247
return;
4248
}
4249
4250
Transform3D xform = spatial_editor->get_gizmo_transform();
4251
4252
Transform3D camera_xform = camera->get_transform();
4253
4254
if (xform.origin.is_equal_approx(camera_xform.origin)) {
4255
for (int i = 0; i < 3; i++) {
4256
RenderingServer::get_singleton()->instance_set_visible(move_gizmo_instance[i], false);
4257
RenderingServer::get_singleton()->instance_set_visible(move_plane_gizmo_instance[i], false);
4258
RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[i], false);
4259
RenderingServer::get_singleton()->instance_set_visible(scale_gizmo_instance[i], false);
4260
RenderingServer::get_singleton()->instance_set_visible(scale_plane_gizmo_instance[i], false);
4261
RenderingServer::get_singleton()->instance_set_visible(axis_gizmo_instance[i], false);
4262
}
4263
// Rotation white outline
4264
RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[3], false);
4265
return;
4266
}
4267
4268
const Vector3 camz = -camera_xform.get_basis().get_column(2).normalized();
4269
const Vector3 camy = -camera_xform.get_basis().get_column(1).normalized();
4270
const Plane p = Plane(camz, camera_xform.origin);
4271
const real_t gizmo_d = MAX(Math::abs(p.distance_to(xform.origin)), CMP_EPSILON);
4272
const real_t d0 = camera->unproject_position(camera_xform.origin + camz * gizmo_d).y;
4273
const real_t d1 = camera->unproject_position(camera_xform.origin + camz * gizmo_d + camy).y;
4274
const real_t dd = MAX(Math::abs(d0 - d1), CMP_EPSILON);
4275
4276
const real_t gizmo_size = EDITOR_GET("editors/3d/manipulator_gizmo_size");
4277
// At low viewport heights, multiply the gizmo scale based on the viewport height.
4278
// This prevents the gizmo from growing very large and going outside the viewport.
4279
const int viewport_base_height = 400 * MAX(1, EDSCALE);
4280
gizmo_scale =
4281
(gizmo_size / Math::abs(dd)) * MAX(1, EDSCALE) *
4282
MIN(viewport_base_height, subviewport_container->get_size().height) / viewport_base_height /
4283
subviewport_container->get_stretch_shrink();
4284
Vector3 scale = Vector3(1, 1, 1) * gizmo_scale;
4285
4286
// if the determinant is zero, we should disable the gizmo from being rendered
4287
// this prevents supplying bad values to the renderer and then having to filter it out again
4288
if (xform.basis.determinant() == 0) {
4289
for (int i = 0; i < 3; i++) {
4290
RenderingServer::get_singleton()->instance_set_visible(move_gizmo_instance[i], false);
4291
RenderingServer::get_singleton()->instance_set_visible(move_plane_gizmo_instance[i], false);
4292
RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[i], false);
4293
RenderingServer::get_singleton()->instance_set_visible(scale_gizmo_instance[i], false);
4294
RenderingServer::get_singleton()->instance_set_visible(scale_plane_gizmo_instance[i], false);
4295
}
4296
// Rotation white outline
4297
RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[3], false);
4298
return;
4299
}
4300
4301
bool show_gizmo = spatial_editor->is_gizmo_visible() && !_edit.instant && transform_gizmo_visible && !collision_reposition;
4302
for (int i = 0; i < 3; i++) {
4303
Transform3D axis_angle;
4304
if (xform.basis.get_column(i).normalized().dot(xform.basis.get_column((i + 1) % 3).normalized()) < 1.0) {
4305
axis_angle = axis_angle.looking_at(xform.basis.get_column(i).normalized(), xform.basis.get_column((i + 1) % 3).normalized());
4306
}
4307
axis_angle.basis.scale(scale);
4308
axis_angle.origin = xform.origin;
4309
RenderingServer::get_singleton()->instance_set_transform(move_gizmo_instance[i], axis_angle);
4310
RenderingServer::get_singleton()->instance_set_visible(move_gizmo_instance[i], show_gizmo && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_MOVE));
4311
RenderingServer::get_singleton()->instance_set_transform(move_plane_gizmo_instance[i], axis_angle);
4312
RenderingServer::get_singleton()->instance_set_visible(move_plane_gizmo_instance[i], show_gizmo && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_MOVE));
4313
RenderingServer::get_singleton()->instance_set_transform(rotate_gizmo_instance[i], axis_angle);
4314
RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[i], show_gizmo && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE));
4315
RenderingServer::get_singleton()->instance_set_transform(scale_gizmo_instance[i], axis_angle);
4316
RenderingServer::get_singleton()->instance_set_visible(scale_gizmo_instance[i], show_gizmo && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SCALE));
4317
RenderingServer::get_singleton()->instance_set_transform(scale_plane_gizmo_instance[i], axis_angle);
4318
RenderingServer::get_singleton()->instance_set_visible(scale_plane_gizmo_instance[i], show_gizmo && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SCALE));
4319
RenderingServer::get_singleton()->instance_set_transform(axis_gizmo_instance[i], xform);
4320
}
4321
4322
bool show_axes = spatial_editor->is_gizmo_visible() && _edit.mode != TRANSFORM_NONE;
4323
RenderingServer *rs = RenderingServer::get_singleton();
4324
rs->instance_set_visible(axis_gizmo_instance[0], show_axes && (_edit.plane == TRANSFORM_X_AXIS || _edit.plane == TRANSFORM_XY || _edit.plane == TRANSFORM_XZ));
4325
rs->instance_set_visible(axis_gizmo_instance[1], show_axes && (_edit.plane == TRANSFORM_Y_AXIS || _edit.plane == TRANSFORM_XY || _edit.plane == TRANSFORM_YZ));
4326
rs->instance_set_visible(axis_gizmo_instance[2], show_axes && (_edit.plane == TRANSFORM_Z_AXIS || _edit.plane == TRANSFORM_XZ || _edit.plane == TRANSFORM_YZ));
4327
4328
// Rotation white outline
4329
xform.orthonormalize();
4330
xform.basis.scale(scale);
4331
RenderingServer::get_singleton()->instance_set_transform(rotate_gizmo_instance[3], xform);
4332
RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[3], spatial_editor->is_gizmo_visible() && !_edit.instant && transform_gizmo_visible && !collision_reposition && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE));
4333
}
4334
4335
void Node3DEditorViewport::set_state(const Dictionary &p_state) {
4336
if (p_state.has("position")) {
4337
cursor.pos = p_state["position"];
4338
}
4339
if (p_state.has("x_rotation")) {
4340
cursor.x_rot = p_state["x_rotation"];
4341
}
4342
if (p_state.has("y_rotation")) {
4343
cursor.y_rot = p_state["y_rotation"];
4344
}
4345
if (p_state.has("distance")) {
4346
cursor.distance = p_state["distance"];
4347
}
4348
if (p_state.has("orthogonal")) {
4349
bool orth = p_state["orthogonal"];
4350
_menu_option(orth ? VIEW_ORTHOGONAL : VIEW_PERSPECTIVE);
4351
}
4352
if (p_state.has("view_type")) {
4353
view_type = ViewType(p_state["view_type"].operator int());
4354
_update_name();
4355
}
4356
if (p_state.has("auto_orthogonal")) {
4357
auto_orthogonal = p_state["auto_orthogonal"];
4358
_update_name();
4359
}
4360
if (p_state.has("auto_orthogonal_enabled")) {
4361
bool enabled = p_state["auto_orthogonal_enabled"];
4362
view_display_menu->get_popup()->set_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_AUTO_ORTHOGONAL), enabled);
4363
}
4364
if (p_state.has("display_mode")) {
4365
int display = p_state["display_mode"];
4366
4367
int idx = view_display_menu->get_popup()->get_item_index(display);
4368
if (idx != -1 && !view_display_menu->get_popup()->is_item_checked(idx)) {
4369
_menu_option(display);
4370
} else {
4371
idx = display_submenu->get_item_index(display);
4372
if (idx != -1 && !display_submenu->is_item_checked(idx)) {
4373
_menu_option(display);
4374
}
4375
}
4376
}
4377
if (p_state.has("lock_rotation")) {
4378
_set_lock_view_rotation(p_state["lock_rotation"]);
4379
}
4380
if (p_state.has("use_environment")) {
4381
bool env = p_state["use_environment"];
4382
4383
if (env != camera->get_environment().is_valid()) {
4384
_menu_option(VIEW_ENVIRONMENT);
4385
}
4386
}
4387
if (p_state.has("listener")) {
4388
bool listener = p_state["listener"];
4389
4390
int idx = view_display_menu->get_popup()->get_item_index(VIEW_AUDIO_LISTENER);
4391
viewport->set_as_audio_listener_3d(listener);
4392
view_display_menu->get_popup()->set_item_checked(idx, listener);
4393
}
4394
if (p_state.has("doppler")) {
4395
bool doppler = p_state["doppler"];
4396
4397
int idx = view_display_menu->get_popup()->get_item_index(VIEW_AUDIO_DOPPLER);
4398
camera->set_doppler_tracking(doppler ? Camera3D::DOPPLER_TRACKING_IDLE_STEP : Camera3D::DOPPLER_TRACKING_DISABLED);
4399
view_display_menu->get_popup()->set_item_checked(idx, doppler);
4400
}
4401
if (p_state.has("gizmos")) {
4402
bool gizmos = p_state["gizmos"];
4403
4404
int idx = view_display_menu->get_popup()->get_item_index(VIEW_GIZMOS);
4405
if (view_display_menu->get_popup()->is_item_checked(idx) != gizmos) {
4406
_menu_option(VIEW_GIZMOS);
4407
}
4408
}
4409
if (p_state.has("transform_gizmo")) {
4410
bool transform_gizmo = p_state["transform_gizmo"];
4411
4412
int idx = view_display_menu->get_popup()->get_item_index(VIEW_TRANSFORM_GIZMO);
4413
if (view_display_menu->get_popup()->is_item_checked(idx) != transform_gizmo) {
4414
_menu_option(VIEW_TRANSFORM_GIZMO);
4415
}
4416
}
4417
if (p_state.has("grid")) {
4418
bool grid = p_state["grid"];
4419
4420
int idx = view_display_menu->get_popup()->get_item_index(VIEW_GRID);
4421
if (view_display_menu->get_popup()->is_item_checked(idx) != grid) {
4422
_menu_option(VIEW_GRID);
4423
}
4424
}
4425
if (p_state.has("information")) {
4426
bool information = p_state["information"];
4427
4428
int idx = view_display_menu->get_popup()->get_item_index(VIEW_INFORMATION);
4429
if (view_display_menu->get_popup()->is_item_checked(idx) != information) {
4430
_menu_option(VIEW_INFORMATION);
4431
}
4432
}
4433
if (p_state.has("frame_time")) {
4434
bool fps = p_state["frame_time"];
4435
4436
int idx = view_display_menu->get_popup()->get_item_index(VIEW_FRAME_TIME);
4437
if (view_display_menu->get_popup()->is_item_checked(idx) != fps) {
4438
_menu_option(VIEW_FRAME_TIME);
4439
}
4440
}
4441
if (p_state.has("half_res")) {
4442
bool half_res = p_state["half_res"];
4443
4444
int idx = view_display_menu->get_popup()->get_item_index(VIEW_HALF_RESOLUTION);
4445
view_display_menu->get_popup()->set_item_checked(idx, half_res);
4446
_update_shrink();
4447
}
4448
if (p_state.has("cinematic_preview")) {
4449
previewing_cinema = p_state["cinematic_preview"];
4450
4451
int idx = view_display_menu->get_popup()->get_item_index(VIEW_CINEMATIC_PREVIEW);
4452
view_display_menu->get_popup()->set_item_checked(idx, previewing_cinema);
4453
4454
cinema_label->set_visible(previewing_cinema);
4455
if (previewing_cinema) {
4456
_update_centered_labels();
4457
surface->queue_redraw();
4458
}
4459
}
4460
4461
if (preview_camera->is_connected(SceneStringName(toggled), callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview))) {
4462
preview_camera->disconnect(SceneStringName(toggled), callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview));
4463
}
4464
if (p_state.has("previewing")) {
4465
Node *pv = EditorNode::get_singleton()->get_edited_scene()->get_node(p_state["previewing"]);
4466
if (Object::cast_to<Camera3D>(pv)) {
4467
previewing = Object::cast_to<Camera3D>(pv);
4468
previewing->connect(SceneStringName(tree_exiting), callable_mp(this, &Node3DEditorViewport::_preview_exited_scene));
4469
previewing->connect(CoreStringName(property_list_changed), callable_mp(this, &Node3DEditorViewport::_preview_camera_property_changed));
4470
RS::get_singleton()->viewport_attach_camera(viewport->get_viewport_rid(), previewing->get_camera()); //replace
4471
surface->queue_redraw();
4472
previewing_camera = true;
4473
preview_camera->set_pressed(true);
4474
preview_camera->show();
4475
}
4476
}
4477
preview_camera->connect(SceneStringName(toggled), callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview));
4478
}
4479
4480
Dictionary Node3DEditorViewport::get_state() const {
4481
Dictionary d;
4482
d["position"] = cursor.pos;
4483
d["x_rotation"] = cursor.x_rot;
4484
d["y_rotation"] = cursor.y_rot;
4485
d["distance"] = cursor.distance;
4486
d["use_environment"] = camera->get_environment().is_valid();
4487
d["orthogonal"] = camera->get_projection() == Camera3D::PROJECTION_ORTHOGONAL;
4488
d["view_type"] = view_type;
4489
d["auto_orthogonal"] = auto_orthogonal;
4490
d["auto_orthogonal_enabled"] = view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_AUTO_ORTHOGONAL));
4491
4492
// Find selected display mode.
4493
int display_mode = VIEW_DISPLAY_NORMAL;
4494
for (int i = VIEW_DISPLAY_NORMAL; i < VIEW_DISPLAY_ADVANCED; i++) {
4495
if (view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(i))) {
4496
display_mode = i;
4497
break;
4498
}
4499
}
4500
for (int i = VIEW_DISPLAY_ADVANCED + 1; i < VIEW_DISPLAY_MAX; i++) {
4501
if (display_submenu->is_item_checked(display_submenu->get_item_index(i))) {
4502
display_mode = i;
4503
break;
4504
}
4505
}
4506
d["display_mode"] = display_mode;
4507
4508
d["listener"] = viewport->is_audio_listener_3d();
4509
d["doppler"] = view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_AUDIO_DOPPLER));
4510
d["gizmos"] = view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_GIZMOS));
4511
d["transform_gizmo"] = view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_TRANSFORM_GIZMO));
4512
d["grid"] = view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_GRID));
4513
d["information"] = view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_INFORMATION));
4514
d["frame_time"] = view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_FRAME_TIME));
4515
d["half_res"] = subviewport_container->get_stretch_shrink() > 1;
4516
d["cinematic_preview"] = view_display_menu->get_popup()->is_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_CINEMATIC_PREVIEW));
4517
if (previewing) {
4518
d["previewing"] = EditorNode::get_singleton()->get_edited_scene()->get_path_to(previewing);
4519
}
4520
d["lock_rotation"] = lock_rotation;
4521
4522
return d;
4523
}
4524
4525
void Node3DEditorViewport::_bind_methods() {
4526
ADD_SIGNAL(MethodInfo("toggle_maximize_view", PropertyInfo(Variant::OBJECT, "viewport")));
4527
ADD_SIGNAL(MethodInfo("clicked"));
4528
}
4529
4530
void Node3DEditorViewport::reset() {
4531
orthogonal = false;
4532
auto_orthogonal = false;
4533
lock_rotation = false;
4534
message_time = 0;
4535
message = "";
4536
last_message = "";
4537
view_type = VIEW_TYPE_USER;
4538
4539
cursor = Cursor();
4540
_update_name();
4541
}
4542
4543
void Node3DEditorViewport::focus_selection() {
4544
Vector3 center;
4545
int count = 0;
4546
4547
const List<Node *> &selection = editor_selection->get_top_selected_node_list();
4548
4549
for (Node *node : selection) {
4550
Node3D *node_3d = Object::cast_to<Node3D>(node);
4551
if (!node_3d) {
4552
continue;
4553
}
4554
4555
Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(node_3d);
4556
if (!se) {
4557
continue;
4558
}
4559
4560
if (se->gizmo.is_valid()) {
4561
for (const KeyValue<int, Transform3D> &GE : se->subgizmos) {
4562
const Vector3 pos = se->gizmo->get_subgizmo_transform(GE.key).origin;
4563
if (pos.is_finite()) {
4564
center += pos;
4565
count++;
4566
}
4567
}
4568
}
4569
const Vector3 pos = node_3d->get_global_gizmo_transform().origin;
4570
if (pos.is_finite()) {
4571
center += pos;
4572
count++;
4573
}
4574
}
4575
4576
if (count > 1) {
4577
center /= count;
4578
}
4579
4580
cursor.pos = center;
4581
}
4582
4583
void Node3DEditorViewport::assign_pending_data_pointers(Node3D *p_preview_node, AABB *p_preview_bounds, AcceptDialog *p_accept) {
4584
preview_node = p_preview_node;
4585
preview_bounds = p_preview_bounds;
4586
accept = p_accept;
4587
}
4588
4589
void _insert_rid_recursive(Node *node, HashSet<RID> &rids) {
4590
CollisionObject3D *co = Object::cast_to<CollisionObject3D>(node);
4591
4592
if (co) {
4593
rids.insert(co->get_rid());
4594
} else if (node->is_class("CSGShape3D")) { // HACK: We should avoid referencing module logic.
4595
rids.insert(node->call("_get_root_collision_instance"));
4596
}
4597
4598
for (int i = 0; i < node->get_child_count(); i++) {
4599
Node *child = node->get_child(i);
4600
_insert_rid_recursive(child, rids);
4601
}
4602
}
4603
4604
Vector3 Node3DEditorViewport::_get_instance_position(const Point2 &p_pos, Node3D *p_node) const {
4605
const float MAX_DISTANCE = 50.0;
4606
const float FALLBACK_DISTANCE = 5.0;
4607
4608
Vector3 world_ray = get_ray(p_pos);
4609
Vector3 world_pos = get_ray_pos(p_pos);
4610
4611
PhysicsDirectSpaceState3D *ss = get_tree()->get_root()->get_world_3d()->get_direct_space_state();
4612
4613
HashSet<RID> rids;
4614
4615
if (!preview_node->is_inside_tree() && !ruler->is_inside_tree()) {
4616
const List<Node *> &selection = editor_selection->get_top_selected_node_list();
4617
4618
Node3D *first_selected_node = Object::cast_to<Node3D>(selection.front()->get());
4619
4620
Array children = first_selected_node->get_children();
4621
4622
if (first_selected_node) {
4623
_insert_rid_recursive(first_selected_node, rids);
4624
}
4625
}
4626
4627
PhysicsDirectSpaceState3D::RayParameters ray_params;
4628
ray_params.exclude = rids;
4629
ray_params.from = world_pos;
4630
ray_params.to = world_pos + world_ray * camera->get_far();
4631
4632
PhysicsDirectSpaceState3D::RayResult result;
4633
if (ss->intersect_ray(ray_params, result) && (preview_node->get_child_count() > 0 || !preview_node->is_inside_tree())) {
4634
// Calculate an offset for the `p_node` such that the its bounding box is on top of and touching the contact surface's plane.
4635
4636
// Use the Gram-Schmidt process to get an orthonormal Basis aligned with the surface normal.
4637
const Vector3 bb_basis_x = result.normal;
4638
Vector3 bb_basis_y = Vector3(0, 1, 0);
4639
bb_basis_y = bb_basis_y - bb_basis_y.project(bb_basis_x);
4640
if (bb_basis_y.is_zero_approx()) {
4641
bb_basis_y = Vector3(0, 0, 1);
4642
bb_basis_y = bb_basis_y - bb_basis_y.project(bb_basis_x);
4643
}
4644
bb_basis_y = bb_basis_y.normalized();
4645
const Vector3 bb_basis_z = bb_basis_x.cross(bb_basis_y);
4646
const Basis bb_basis = Basis(bb_basis_x, bb_basis_y, bb_basis_z);
4647
4648
// This normal-aligned Basis allows us to create an AABB that can fit on the surface plane as snugly as possible.
4649
const Transform3D bb_transform = Transform3D(bb_basis, p_node->get_transform().origin);
4650
const AABB p_node_bb = _calculate_spatial_bounds(p_node, true, &bb_transform);
4651
// The x-axis's alignment with the surface normal also makes it trivial to get the distance from `p_node`'s origin at (0, 0, 0) to the correct AABB face.
4652
const float offset_distance = -p_node_bb.position.x;
4653
4654
// `result_offset` is in global space.
4655
const Vector3 result_offset = result.position + result.normal * offset_distance;
4656
4657
return result_offset;
4658
}
4659
4660
const bool is_orthogonal = camera->get_projection() == Camera3D::PROJECTION_ORTHOGONAL;
4661
4662
// The XZ plane.
4663
Vector3 intersection;
4664
Plane plane(Vector3(0, 1, 0));
4665
if (plane.intersects_ray(world_pos, world_ray, &intersection)) {
4666
if (is_orthogonal || world_pos.distance_to(intersection) <= MAX_DISTANCE) {
4667
return intersection;
4668
}
4669
}
4670
4671
// Plane facing the camera using fallback distance.
4672
if (is_orthogonal) {
4673
plane = Plane(world_ray, cursor.pos - world_ray * (cursor.distance - FALLBACK_DISTANCE));
4674
} else {
4675
plane = Plane(world_ray, world_pos + world_ray * FALLBACK_DISTANCE);
4676
}
4677
if (plane.intersects_ray(world_pos, world_ray, &intersection)) {
4678
return intersection;
4679
}
4680
4681
// Not likely, but just in case...
4682
return world_pos + world_ray * FALLBACK_DISTANCE;
4683
}
4684
4685
AABB Node3DEditorViewport::_calculate_spatial_bounds(const Node3D *p_parent, bool p_omit_top_level, const Transform3D *p_bounds_orientation) {
4686
if (!p_parent) {
4687
return AABB(Vector3(-0.2, -0.2, -0.2), Vector3(0.4, 0.4, 0.4));
4688
}
4689
const Transform3D parent_transform = p_parent->get_global_transform();
4690
if (!parent_transform.is_finite()) {
4691
return AABB();
4692
}
4693
AABB bounds;
4694
4695
Transform3D bounds_orientation;
4696
Transform3D xform_to_top_level_parent_space;
4697
if (p_bounds_orientation) {
4698
bounds_orientation = *p_bounds_orientation;
4699
xform_to_top_level_parent_space = bounds_orientation.affine_inverse() * parent_transform;
4700
} else {
4701
bounds_orientation = parent_transform;
4702
}
4703
4704
const VisualInstance3D *visual_instance = Object::cast_to<VisualInstance3D>(p_parent);
4705
if (visual_instance) {
4706
bounds = visual_instance->get_aabb();
4707
} else {
4708
bounds = AABB();
4709
}
4710
bounds = xform_to_top_level_parent_space.xform(bounds);
4711
4712
for (int i = 0; i < p_parent->get_child_count(); i++) {
4713
const Node3D *child = Object::cast_to<Node3D>(p_parent->get_child(i));
4714
if (child && !(p_omit_top_level && child->is_set_as_top_level())) {
4715
const AABB child_bounds = _calculate_spatial_bounds(child, p_omit_top_level, &bounds_orientation);
4716
bounds.merge_with(child_bounds);
4717
}
4718
}
4719
4720
return bounds;
4721
}
4722
4723
Node *Node3DEditorViewport::_sanitize_preview_node(Node *p_node) const {
4724
Node3D *node_3d = Object::cast_to<Node3D>(p_node);
4725
if (node_3d == nullptr) {
4726
Node3D *replacement_node = memnew(Node3D);
4727
replacement_node->set_name(p_node->get_name());
4728
p_node->replace_by(replacement_node);
4729
memdelete(p_node);
4730
p_node = replacement_node;
4731
} else {
4732
VisualInstance3D *visual_instance = Object::cast_to<VisualInstance3D>(node_3d);
4733
if (visual_instance == nullptr) {
4734
Node3D *replacement_node = memnew(Node3D);
4735
replacement_node->set_name(node_3d->get_name());
4736
replacement_node->set_visible(node_3d->is_visible());
4737
replacement_node->set_transform(node_3d->get_transform());
4738
replacement_node->set_rotation_edit_mode(node_3d->get_rotation_edit_mode());
4739
replacement_node->set_rotation_order(node_3d->get_rotation_order());
4740
replacement_node->set_as_top_level(node_3d->is_set_as_top_level());
4741
p_node->replace_by(replacement_node);
4742
memdelete(p_node);
4743
p_node = replacement_node;
4744
}
4745
}
4746
4747
for (int i = 0; i < p_node->get_child_count(); i++) {
4748
_sanitize_preview_node(p_node->get_child(i));
4749
}
4750
4751
return p_node;
4752
}
4753
4754
void Node3DEditorViewport::_create_preview_node(const Vector<String> &files) const {
4755
bool add_preview = false;
4756
for (const String &path : files) {
4757
Ref<Resource> res = ResourceLoader::load(path);
4758
ERR_CONTINUE(res.is_null());
4759
4760
Ref<PackedScene> scene = res;
4761
if (scene.is_valid()) {
4762
Node *instance = scene->instantiate();
4763
if (instance) {
4764
instance = _sanitize_preview_node(instance);
4765
preview_node->add_child(instance);
4766
Node3D *node_3d = Object::cast_to<Node3D>(instance);
4767
if (node_3d) {
4768
node_3d->set_as_top_level(false);
4769
}
4770
}
4771
add_preview = true;
4772
}
4773
4774
Ref<Mesh> mesh = res;
4775
if (mesh.is_valid()) {
4776
MeshInstance3D *mesh_instance = memnew(MeshInstance3D);
4777
mesh_instance->set_mesh(mesh);
4778
preview_node->add_child(mesh_instance);
4779
add_preview = true;
4780
}
4781
4782
Ref<AudioStream> audio = res;
4783
if (audio.is_valid()) {
4784
Sprite3D *sprite = memnew(Sprite3D);
4785
sprite->set_texture(get_editor_theme_icon(SNAME("Gizmo3DSamplePlayer")));
4786
sprite->set_billboard_mode(StandardMaterial3D::BILLBOARD_ENABLED);
4787
sprite->set_pixel_size(0.005);
4788
preview_node->add_child(sprite);
4789
add_preview = true;
4790
}
4791
}
4792
if (add_preview) {
4793
EditorNode::get_singleton()->get_scene_root()->add_child(preview_node);
4794
}
4795
4796
*preview_bounds = _calculate_spatial_bounds(preview_node);
4797
}
4798
4799
void Node3DEditorViewport::_remove_preview_node() {
4800
set_message("");
4801
if (preview_node->get_parent()) {
4802
for (int i = preview_node->get_child_count() - 1; i >= 0; i--) {
4803
Node *node = preview_node->get_child(i);
4804
node->queue_free();
4805
preview_node->remove_child(node);
4806
}
4807
EditorNode::get_singleton()->get_scene_root()->remove_child(preview_node);
4808
}
4809
}
4810
4811
bool Node3DEditorViewport::_apply_preview_material(ObjectID p_target, const Point2 &p_point) const {
4812
_reset_preview_material();
4813
4814
if (p_target.is_null()) {
4815
return false;
4816
}
4817
4818
spatial_editor->set_preview_material_target(p_target);
4819
4820
Object *target_inst = ObjectDB::get_instance(p_target);
4821
4822
bool is_ctrl = Input::get_singleton()->is_key_pressed(Key::CTRL);
4823
4824
MeshInstance3D *mesh_instance = Object::cast_to<MeshInstance3D>(target_inst);
4825
if (is_ctrl && mesh_instance) {
4826
Ref<Mesh> mesh = mesh_instance->get_mesh();
4827
int surface_count = mesh->get_surface_count();
4828
4829
Vector3 world_ray = get_ray(p_point);
4830
Vector3 world_pos = get_ray_pos(p_point);
4831
4832
int closest_surface = -1;
4833
float closest_dist = 1e20;
4834
4835
Transform3D gt = mesh_instance->get_global_transform();
4836
4837
Transform3D ai = gt.affine_inverse();
4838
Vector3 xform_ray = ai.basis.xform(world_ray).normalized();
4839
Vector3 xform_pos = ai.xform(world_pos);
4840
4841
for (int surface_idx = 0; surface_idx < surface_count; surface_idx++) {
4842
Ref<TriangleMesh> surface_mesh = mesh->generate_surface_triangle_mesh(surface_idx);
4843
4844
Vector3 rpos, rnorm;
4845
if (surface_mesh->intersect_ray(xform_pos, xform_ray, rpos, rnorm)) {
4846
Vector3 hitpos = gt.xform(rpos);
4847
4848
const real_t dist = world_pos.distance_to(hitpos);
4849
4850
if (dist < 0) {
4851
continue;
4852
}
4853
4854
if (dist < closest_dist) {
4855
closest_surface = surface_idx;
4856
closest_dist = dist;
4857
}
4858
}
4859
}
4860
4861
if (closest_surface == -1) {
4862
return false;
4863
}
4864
4865
spatial_editor->set_preview_material_surface(closest_surface);
4866
spatial_editor->set_preview_reset_material(mesh_instance->get_surface_override_material(closest_surface));
4867
mesh_instance->set_surface_override_material(closest_surface, spatial_editor->get_preview_material());
4868
4869
return true;
4870
}
4871
4872
GeometryInstance3D *geometry_instance = Object::cast_to<GeometryInstance3D>(target_inst);
4873
if (geometry_instance) {
4874
spatial_editor->set_preview_material_surface(-1);
4875
spatial_editor->set_preview_reset_material(geometry_instance->get_material_override());
4876
geometry_instance->set_material_override(spatial_editor->get_preview_material());
4877
return true;
4878
}
4879
4880
return false;
4881
}
4882
4883
void Node3DEditorViewport::_reset_preview_material() const {
4884
ObjectID last_target = spatial_editor->get_preview_material_target();
4885
if (last_target.is_null()) {
4886
return;
4887
}
4888
Object *last_target_inst = ObjectDB::get_instance(last_target);
4889
4890
MeshInstance3D *mesh_instance = Object::cast_to<MeshInstance3D>(last_target_inst);
4891
GeometryInstance3D *geometry_instance = Object::cast_to<GeometryInstance3D>(last_target_inst);
4892
if (mesh_instance && spatial_editor->get_preview_material_surface() != -1) {
4893
mesh_instance->set_surface_override_material(spatial_editor->get_preview_material_surface(), spatial_editor->get_preview_reset_material());
4894
} else if (geometry_instance) {
4895
geometry_instance->set_material_override(spatial_editor->get_preview_reset_material());
4896
}
4897
}
4898
4899
void Node3DEditorViewport::_remove_preview_material() {
4900
preview_material_label->hide();
4901
preview_material_label_desc->hide();
4902
4903
spatial_editor->set_preview_material(Ref<Material>());
4904
spatial_editor->set_preview_reset_material(Ref<Material>());
4905
spatial_editor->set_preview_material_target(ObjectID());
4906
spatial_editor->set_preview_material_surface(-1);
4907
}
4908
4909
bool Node3DEditorViewport::_cyclical_dependency_exists(const String &p_target_scene_path, Node *p_desired_node) const {
4910
if (p_desired_node->get_scene_file_path() == p_target_scene_path) {
4911
return true;
4912
}
4913
4914
int childCount = p_desired_node->get_child_count();
4915
for (int i = 0; i < childCount; i++) {
4916
Node *child = p_desired_node->get_child(i);
4917
if (_cyclical_dependency_exists(p_target_scene_path, child)) {
4918
return true;
4919
}
4920
}
4921
return false;
4922
}
4923
4924
bool Node3DEditorViewport::_create_instance(Node *p_parent, const String &p_path, const Point2 &p_point) {
4925
Ref<Resource> res = ResourceLoader::load(p_path);
4926
ERR_FAIL_COND_V(res.is_null(), false);
4927
4928
Ref<PackedScene> scene = res;
4929
Ref<Mesh> mesh = res;
4930
4931
Node *instantiated_scene = nullptr;
4932
4933
if (mesh.is_valid() || scene.is_valid()) {
4934
if (mesh.is_valid()) {
4935
MeshInstance3D *mesh_instance = memnew(MeshInstance3D);
4936
mesh_instance->set_mesh(mesh);
4937
4938
// Adjust casing according to project setting. The file name is expected to be in snake_case, but will work for others.
4939
const String &node_name = Node::adjust_name_casing(p_path.get_file().get_basename());
4940
if (!node_name.is_empty()) {
4941
mesh_instance->set_name(node_name);
4942
}
4943
4944
instantiated_scene = mesh_instance;
4945
} else {
4946
if (scene.is_null()) { // invalid scene
4947
return false;
4948
} else {
4949
instantiated_scene = scene->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE);
4950
}
4951
}
4952
}
4953
4954
if (instantiated_scene == nullptr) {
4955
return false;
4956
}
4957
4958
if (!EditorNode::get_singleton()->get_edited_scene()->get_scene_file_path().is_empty()) { // Cyclic instantiation.
4959
if (_cyclical_dependency_exists(EditorNode::get_singleton()->get_edited_scene()->get_scene_file_path(), instantiated_scene)) {
4960
memdelete(instantiated_scene);
4961
return false;
4962
}
4963
}
4964
4965
if (scene.is_valid()) {
4966
instantiated_scene->set_scene_file_path(ProjectSettings::get_singleton()->localize_path(p_path));
4967
}
4968
4969
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
4970
undo_redo->add_do_method(p_parent, "add_child", instantiated_scene, true);
4971
undo_redo->add_do_method(instantiated_scene, "set_owner", EditorNode::get_singleton()->get_edited_scene());
4972
undo_redo->add_do_reference(instantiated_scene);
4973
undo_redo->add_undo_method(p_parent, "remove_child", instantiated_scene);
4974
undo_redo->add_do_method(editor_selection, "add_node", instantiated_scene);
4975
4976
String new_name = p_parent->validate_child_name(instantiated_scene);
4977
EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton();
4978
undo_redo->add_do_method(ed, "live_debug_instantiate_node", EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent), p_path, new_name);
4979
undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent)) + "/" + new_name));
4980
4981
Node3D *node3d = Object::cast_to<Node3D>(instantiated_scene);
4982
if (node3d) {
4983
Transform3D parent_tf;
4984
Node3D *parent_node3d = Object::cast_to<Node3D>(p_parent);
4985
if (parent_node3d) {
4986
parent_tf = parent_node3d->get_global_gizmo_transform();
4987
}
4988
4989
Transform3D new_tf = node3d->get_transform();
4990
if (node3d->is_set_as_top_level()) {
4991
new_tf.origin += preview_node_pos;
4992
} else {
4993
new_tf.origin = parent_tf.affine_inverse().xform(preview_node_pos + node3d->get_position());
4994
new_tf.basis = parent_tf.affine_inverse().basis * new_tf.basis;
4995
}
4996
4997
undo_redo->add_do_method(instantiated_scene, "set_transform", new_tf);
4998
}
4999
5000
return true;
5001
}
5002
5003
bool Node3DEditorViewport::_create_audio_node(Node *p_parent, const String &p_path, const Point2 &p_point) {
5004
Ref<AudioStream> audio = ResourceLoader::load(p_path);
5005
ERR_FAIL_COND_V(audio.is_null(), false);
5006
5007
AudioStreamPlayer3D *audio_player = memnew(AudioStreamPlayer3D);
5008
audio_player->set_stream(audio);
5009
5010
// Adjust casing according to project setting. The file name is expected to be in snake_case, but will work for others.
5011
const String &node_name = Node::adjust_name_casing(p_path.get_file().get_basename());
5012
if (!node_name.is_empty()) {
5013
audio_player->set_name(node_name);
5014
}
5015
5016
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
5017
undo_redo->add_do_method(p_parent, "add_child", audio_player, true);
5018
undo_redo->add_do_method(audio_player, "set_owner", EditorNode::get_singleton()->get_edited_scene());
5019
undo_redo->add_do_reference(audio_player);
5020
undo_redo->add_undo_method(p_parent, "remove_child", audio_player);
5021
undo_redo->add_do_method(editor_selection, "add_node", audio_player);
5022
5023
const String new_name = p_parent->validate_child_name(audio_player);
5024
EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton();
5025
undo_redo->add_do_method(ed, "live_debug_create_node", EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent), audio_player->get_class(), new_name);
5026
undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent)) + "/" + new_name));
5027
5028
Transform3D parent_tf;
5029
Node3D *parent_node3d = Object::cast_to<Node3D>(p_parent);
5030
if (parent_node3d) {
5031
parent_tf = parent_node3d->get_global_gizmo_transform();
5032
}
5033
5034
Transform3D new_tf = audio_player->get_transform();
5035
new_tf.origin = parent_tf.affine_inverse().xform(preview_node_pos + audio_player->get_position());
5036
new_tf.basis = parent_tf.affine_inverse().basis * new_tf.basis;
5037
5038
undo_redo->add_do_method(audio_player, "set_transform", new_tf);
5039
5040
return true;
5041
}
5042
5043
void Node3DEditorViewport::_perform_drop_data() {
5044
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
5045
if (spatial_editor->get_preview_material_target().is_valid()) {
5046
GeometryInstance3D *geometry_instance = ObjectDB::get_instance<GeometryInstance3D>(spatial_editor->get_preview_material_target());
5047
MeshInstance3D *mesh_instance = ObjectDB::get_instance<MeshInstance3D>(spatial_editor->get_preview_material_target());
5048
if (mesh_instance && spatial_editor->get_preview_material_surface() != -1) {
5049
undo_redo->create_action(vformat(TTR("Set Surface %d Override Material"), spatial_editor->get_preview_material_surface()));
5050
undo_redo->add_do_method(geometry_instance, "set_surface_override_material", spatial_editor->get_preview_material_surface(), spatial_editor->get_preview_material());
5051
undo_redo->add_undo_method(geometry_instance, "set_surface_override_material", spatial_editor->get_preview_material_surface(), spatial_editor->get_preview_reset_material());
5052
undo_redo->commit_action();
5053
} else if (geometry_instance) {
5054
undo_redo->create_action(TTR("Set Material Override"));
5055
undo_redo->add_do_method(geometry_instance, "set_material_override", spatial_editor->get_preview_material());
5056
undo_redo->add_undo_method(geometry_instance, "set_material_override", spatial_editor->get_preview_reset_material());
5057
undo_redo->commit_action();
5058
}
5059
5060
_remove_preview_material();
5061
return;
5062
}
5063
5064
_remove_preview_node();
5065
5066
PackedStringArray error_files;
5067
5068
undo_redo->create_action(TTR("Create Node"), UndoRedo::MERGE_DISABLE, target_node);
5069
undo_redo->add_do_method(editor_selection, "clear");
5070
5071
for (int i = 0; i < selected_files.size(); i++) {
5072
String path = selected_files[i];
5073
Ref<Resource> res = ResourceLoader::load(path);
5074
if (res.is_null()) {
5075
continue;
5076
}
5077
5078
Ref<PackedScene> scene = res;
5079
Ref<Mesh> mesh = res;
5080
if (mesh.is_valid() || scene.is_valid()) {
5081
if (!_create_instance(target_node, path, drop_pos)) {
5082
error_files.push_back(path.get_file());
5083
}
5084
}
5085
5086
Ref<AudioStream> audio = res;
5087
if (audio.is_valid()) {
5088
if (!_create_audio_node(target_node, path, drop_pos)) {
5089
error_files.push_back(path.get_file());
5090
}
5091
}
5092
}
5093
5094
undo_redo->commit_action();
5095
5096
if (error_files.size() > 0) {
5097
accept->set_text(vformat(TTR("Error instantiating scene from %s."), String(", ").join(error_files)));
5098
accept->popup_centered();
5099
}
5100
}
5101
5102
bool Node3DEditorViewport::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {
5103
if (p_point == Vector2(Math::INF, Math::INF)) {
5104
return false;
5105
}
5106
preview_node_viewport_pos = p_point;
5107
5108
bool can_instantiate = false;
5109
bool is_cyclical_dep = false;
5110
String error_file;
5111
5112
if (!preview_node->is_inside_tree() && spatial_editor->get_preview_material().is_null()) {
5113
Dictionary d = p_data;
5114
if (d.has("type") && (String(d["type"]) == "files")) {
5115
Vector<String> files = d["files"];
5116
5117
// Track whether a type other than PackedScene is valid to stop checking them and only
5118
// continue to check if the rest of the scenes are valid (don't have cyclic dependencies).
5119
bool is_other_valid = false;
5120
// Check if at least one of the dragged files is a mesh, material, texture or scene.
5121
for (int i = 0; i < files.size(); i++) {
5122
const String &res_type = ResourceLoader::get_resource_type(files[i]);
5123
bool is_scene = ClassDB::is_parent_class(res_type, "PackedScene");
5124
bool is_mesh = ClassDB::is_parent_class(res_type, "Mesh");
5125
bool is_material = ClassDB::is_parent_class(res_type, "Material");
5126
bool is_texture = ClassDB::is_parent_class(res_type, "Texture");
5127
bool is_audio = ClassDB::is_parent_class(res_type, "AudioStream");
5128
5129
if (is_mesh || is_scene || is_material || is_texture || is_audio) {
5130
Ref<Resource> res = ResourceLoader::load(files[i]);
5131
if (res.is_null()) {
5132
continue;
5133
}
5134
Ref<PackedScene> scn = res;
5135
Ref<Mesh> mesh = res;
5136
Ref<Material> mat = res;
5137
Ref<Texture2D> tex = res;
5138
Ref<AudioStream> audio = res;
5139
if (scn.is_valid()) {
5140
Node *instantiated_scene = scn->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE);
5141
if (!instantiated_scene) {
5142
continue;
5143
}
5144
Node *edited_scene = EditorNode::get_singleton()->get_edited_scene();
5145
if (edited_scene && !edited_scene->get_scene_file_path().is_empty() && _cyclical_dependency_exists(edited_scene->get_scene_file_path(), instantiated_scene)) {
5146
memdelete(instantiated_scene);
5147
can_instantiate = false;
5148
is_cyclical_dep = true;
5149
error_file = files[i].get_file();
5150
break;
5151
}
5152
memdelete(instantiated_scene);
5153
} else if (!is_other_valid && mat.is_valid()) {
5154
Ref<BaseMaterial3D> base_mat = res;
5155
Ref<ShaderMaterial> shader_mat = res;
5156
5157
if (base_mat.is_null() && shader_mat.is_null()) {
5158
continue;
5159
}
5160
5161
spatial_editor->set_preview_material(mat);
5162
is_other_valid = true;
5163
continue;
5164
} else if (!is_other_valid && mesh.is_valid()) {
5165
// Let the mesh pass.
5166
is_other_valid = true;
5167
} else if (!is_other_valid && tex.is_valid()) {
5168
Ref<StandardMaterial3D> new_mat = memnew(StandardMaterial3D);
5169
new_mat->set_texture(BaseMaterial3D::TEXTURE_ALBEDO, tex);
5170
5171
spatial_editor->set_preview_material(new_mat);
5172
is_other_valid = true;
5173
continue;
5174
} else if (!is_other_valid && audio.is_valid()) {
5175
is_other_valid = true;
5176
} else {
5177
continue;
5178
}
5179
can_instantiate = true;
5180
}
5181
}
5182
if (can_instantiate) {
5183
_create_preview_node(files);
5184
preview_node->hide();
5185
}
5186
}
5187
} else {
5188
if (preview_node->is_inside_tree()) {
5189
can_instantiate = true;
5190
}
5191
}
5192
5193
if (is_cyclical_dep) {
5194
set_message(vformat(TTR("Can't instantiate: %s."), vformat(TTR("Circular dependency found at %s"), error_file)));
5195
return false;
5196
}
5197
5198
if (can_instantiate) {
5199
update_preview_node = true;
5200
return true;
5201
}
5202
5203
if (spatial_editor->get_preview_material().is_valid()) {
5204
preview_material_label->show();
5205
preview_material_label_desc->show();
5206
5207
ObjectID new_preview_material_target = _select_ray(p_point);
5208
return _apply_preview_material(new_preview_material_target, p_point);
5209
}
5210
5211
return false;
5212
}
5213
5214
void Node3DEditorViewport::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {
5215
if (!can_drop_data_fw(p_point, p_data, p_from)) {
5216
return;
5217
}
5218
5219
bool is_shift = Input::get_singleton()->is_key_pressed(Key::SHIFT);
5220
bool is_alt = Input::get_singleton()->is_key_pressed(Key::ALT);
5221
5222
selected_files.clear();
5223
Dictionary d = p_data;
5224
if (d.has("type") && String(d["type"]) == "files") {
5225
selected_files = d["files"];
5226
}
5227
5228
List<Node *> selected_nodes = EditorNode::get_singleton()->get_editor_selection()->get_top_selected_node_list();
5229
Node *root_node = EditorNode::get_singleton()->get_edited_scene();
5230
if (selected_nodes.size() > 0) {
5231
Node *selected_node = selected_nodes.front()->get();
5232
if (is_alt) {
5233
target_node = root_node;
5234
} else if (is_shift) {
5235
target_node = selected_node;
5236
} else { // Default behavior.
5237
target_node = (selected_node != root_node) ? selected_node->get_parent() : root_node;
5238
}
5239
} else {
5240
if (root_node) {
5241
target_node = root_node;
5242
} else {
5243
// Create a root node so we can add child nodes to it.
5244
SceneTreeDock::get_singleton()->add_root_node(memnew(Node3D));
5245
target_node = get_tree()->get_edited_scene_root();
5246
}
5247
}
5248
5249
drop_pos = p_point;
5250
5251
_perform_drop_data();
5252
}
5253
5254
void Node3DEditorViewport::begin_transform(TransformMode p_mode, bool instant) {
5255
if (get_selected_count() > 0) {
5256
_edit.mode = p_mode;
5257
_compute_edit(_edit.mouse_pos);
5258
_edit.instant = instant;
5259
_edit.snap = spatial_editor->is_snap_enabled();
5260
update_transform_gizmo_view();
5261
set_process_input(instant);
5262
}
5263
}
5264
5265
// Apply the current transform operation.
5266
void Node3DEditorViewport::commit_transform() {
5267
ERR_FAIL_COND(_edit.mode == TRANSFORM_NONE);
5268
static const char *_transform_name[4] = {
5269
TTRC("None"),
5270
TTRC("Rotate"),
5271
// TRANSLATORS: This refers to the movement that changes the position of an object.
5272
TTRC("Translate"),
5273
TTRC("Scale"),
5274
};
5275
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
5276
undo_redo->create_action(_transform_name[_edit.mode]);
5277
5278
const List<Node *> &selection = editor_selection->get_top_selected_node_list();
5279
5280
for (Node *E : selection) {
5281
Node3D *sp = Object::cast_to<Node3D>(E);
5282
if (!sp) {
5283
continue;
5284
}
5285
5286
Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);
5287
if (!se) {
5288
continue;
5289
}
5290
5291
undo_redo->add_do_method(sp, "set_transform", sp->get_local_gizmo_transform());
5292
undo_redo->add_undo_method(sp, "set_transform", se->original_local);
5293
}
5294
undo_redo->commit_action();
5295
5296
collision_reposition = false;
5297
finish_transform();
5298
set_message("");
5299
}
5300
5301
void Node3DEditorViewport::apply_transform(Vector3 p_motion, double p_snap) {
5302
bool local_coords = (spatial_editor->are_local_coords_enabled() && _edit.plane != TRANSFORM_VIEW);
5303
const List<Node *> &selection = editor_selection->get_top_selected_node_list();
5304
for (Node *E : selection) {
5305
Node3D *sp = Object::cast_to<Node3D>(E);
5306
if (!sp) {
5307
continue;
5308
}
5309
5310
Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);
5311
if (!se) {
5312
continue;
5313
}
5314
5315
if (sp->has_meta("_edit_lock_") && !spatial_editor->is_gizmo_visible()) {
5316
continue;
5317
}
5318
5319
if (se->gizmo.is_valid()) {
5320
for (KeyValue<int, Transform3D> &GE : se->subgizmos) {
5321
Transform3D xform = GE.value;
5322
Transform3D new_xform = _compute_transform(_edit.mode, se->original * xform, xform, p_motion, p_snap, local_coords, _edit.plane != TRANSFORM_VIEW); // Force orthogonal with subgizmo.
5323
if (!local_coords) {
5324
new_xform = se->original.affine_inverse() * new_xform;
5325
}
5326
se->gizmo->set_subgizmo_transform(GE.key, new_xform);
5327
}
5328
} else {
5329
Transform3D new_xform = _compute_transform(_edit.mode, se->original, se->original_local, p_motion, p_snap, local_coords, sp->get_rotation_edit_mode() != Node3D::ROTATION_EDIT_MODE_BASIS && _edit.plane != TRANSFORM_VIEW);
5330
_transform_gizmo_apply(se->sp, new_xform, local_coords);
5331
}
5332
}
5333
5334
spatial_editor->update_transform_gizmo();
5335
surface->queue_redraw();
5336
}
5337
5338
// Update the current transform operation in response to an input.
5339
void Node3DEditorViewport::update_transform(bool p_shift) {
5340
Vector3 ray_pos = get_ray_pos(_edit.mouse_pos);
5341
Vector3 ray = get_ray(_edit.mouse_pos);
5342
double snap = EDITOR_GET("interface/inspector/default_float_step");
5343
int snap_step_decimals = Math::range_step_decimals(snap);
5344
5345
switch (_edit.mode) {
5346
case TRANSFORM_SCALE: {
5347
Vector3 motion_mask;
5348
Plane plane;
5349
bool plane_mv = false;
5350
5351
switch (_edit.plane) {
5352
case TRANSFORM_VIEW:
5353
motion_mask = Vector3(0, 0, 0);
5354
plane = Plane(_get_camera_normal(), _edit.center);
5355
break;
5356
case TRANSFORM_X_AXIS:
5357
motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(0).normalized();
5358
plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center);
5359
break;
5360
case TRANSFORM_Y_AXIS:
5361
motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(1).normalized();
5362
plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center);
5363
break;
5364
case TRANSFORM_Z_AXIS:
5365
motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(2).normalized();
5366
plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center);
5367
break;
5368
case TRANSFORM_YZ:
5369
motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(2).normalized() + spatial_editor->get_gizmo_transform().basis.get_column(1).normalized();
5370
plane = Plane(spatial_editor->get_gizmo_transform().basis.get_column(0).normalized(), _edit.center);
5371
plane_mv = true;
5372
break;
5373
case TRANSFORM_XZ:
5374
motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(2).normalized() + spatial_editor->get_gizmo_transform().basis.get_column(0).normalized();
5375
plane = Plane(spatial_editor->get_gizmo_transform().basis.get_column(1).normalized(), _edit.center);
5376
plane_mv = true;
5377
break;
5378
case TRANSFORM_XY:
5379
motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(0).normalized() + spatial_editor->get_gizmo_transform().basis.get_column(1).normalized();
5380
plane = Plane(spatial_editor->get_gizmo_transform().basis.get_column(2).normalized(), _edit.center);
5381
plane_mv = true;
5382
break;
5383
}
5384
5385
Vector3 intersection;
5386
if (!plane.intersects_ray(ray_pos, ray, &intersection)) {
5387
break;
5388
}
5389
5390
Vector3 click;
5391
if (!plane.intersects_ray(_edit.click_ray_pos, _edit.click_ray, &click)) {
5392
break;
5393
}
5394
5395
Vector3 motion = intersection - click;
5396
if (_edit.plane != TRANSFORM_VIEW) {
5397
if (!plane_mv) {
5398
motion = motion_mask.dot(motion) * motion_mask;
5399
5400
} else {
5401
// Alternative planar scaling mode
5402
if (p_shift) {
5403
motion = motion_mask.dot(motion) * motion_mask;
5404
}
5405
}
5406
5407
} else {
5408
const real_t center_click_dist = click.distance_to(_edit.center);
5409
const real_t center_inters_dist = intersection.distance_to(_edit.center);
5410
if (center_click_dist == 0) {
5411
break;
5412
}
5413
5414
const real_t scale = center_inters_dist - center_click_dist;
5415
motion = Vector3(scale, scale, scale);
5416
}
5417
5418
motion /= click.distance_to(_edit.center);
5419
5420
// Disable local transformation for TRANSFORM_VIEW
5421
bool local_coords = (spatial_editor->are_local_coords_enabled() && _edit.plane != TRANSFORM_VIEW);
5422
5423
if (_edit.snap || spatial_editor->is_snap_enabled()) {
5424
snap = spatial_editor->get_scale_snap() / 100;
5425
}
5426
Vector3 motion_snapped = motion;
5427
motion_snapped.snapf(snap);
5428
// This might not be necessary anymore after issue #288 is solved (in 4.0?).
5429
// TRANSLATORS: Refers to changing the scale of a node in the 3D editor.
5430
set_message(TTR("Scaling:") + " (" + String::num(motion_snapped.x, snap_step_decimals) + ", " +
5431
String::num(motion_snapped.y, snap_step_decimals) + ", " + String::num(motion_snapped.z, snap_step_decimals) + ")");
5432
if (local_coords) {
5433
// TODO: needed?
5434
motion = _edit.original.basis.inverse().xform(motion);
5435
}
5436
5437
apply_transform(motion, snap);
5438
} break;
5439
5440
case TRANSFORM_TRANSLATE: {
5441
Vector3 motion_mask;
5442
Plane plane;
5443
bool plane_mv = false;
5444
5445
switch (_edit.plane) {
5446
case TRANSFORM_VIEW:
5447
plane = Plane(_get_camera_normal(), _edit.center);
5448
break;
5449
case TRANSFORM_X_AXIS:
5450
motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(0).normalized();
5451
plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center);
5452
break;
5453
case TRANSFORM_Y_AXIS:
5454
motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(1).normalized();
5455
plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center);
5456
break;
5457
case TRANSFORM_Z_AXIS:
5458
motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(2).normalized();
5459
plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center);
5460
break;
5461
case TRANSFORM_YZ:
5462
plane = Plane(spatial_editor->get_gizmo_transform().basis.get_column(0).normalized(), _edit.center);
5463
plane_mv = true;
5464
break;
5465
case TRANSFORM_XZ:
5466
plane = Plane(spatial_editor->get_gizmo_transform().basis.get_column(1).normalized(), _edit.center);
5467
plane_mv = true;
5468
break;
5469
case TRANSFORM_XY:
5470
plane = Plane(spatial_editor->get_gizmo_transform().basis.get_column(2).normalized(), _edit.center);
5471
plane_mv = true;
5472
break;
5473
}
5474
5475
Vector3 intersection;
5476
if (!plane.intersects_ray(ray_pos, ray, &intersection)) {
5477
break;
5478
}
5479
5480
Vector3 click;
5481
if (!plane.intersects_ray(_edit.click_ray_pos, _edit.click_ray, &click)) {
5482
break;
5483
}
5484
5485
Vector3 motion = intersection - click;
5486
if (_edit.plane != TRANSFORM_VIEW) {
5487
if (!plane_mv) {
5488
motion = motion_mask.dot(motion) * motion_mask;
5489
}
5490
}
5491
5492
// Disable local transformation for TRANSFORM_VIEW
5493
bool local_coords = (spatial_editor->are_local_coords_enabled() && _edit.plane != TRANSFORM_VIEW);
5494
5495
if (_edit.snap || spatial_editor->is_snap_enabled()) {
5496
snap = spatial_editor->get_translate_snap();
5497
}
5498
Vector3 motion_snapped = motion;
5499
motion_snapped.snapf(snap);
5500
// TRANSLATORS: Refers to changing the position of a node in the 3D editor.
5501
set_message(TTR("Translating:") + " (" + String::num(motion_snapped.x, snap_step_decimals) + ", " +
5502
String::num(motion_snapped.y, snap_step_decimals) + ", " + String::num(motion_snapped.z, snap_step_decimals) + ")");
5503
if (local_coords) {
5504
motion = spatial_editor->get_gizmo_transform().basis.inverse().xform(motion);
5505
}
5506
5507
apply_transform(motion, snap);
5508
} break;
5509
5510
case TRANSFORM_ROTATE: {
5511
Plane plane;
5512
if (camera->get_projection() == Camera3D::PROJECTION_PERSPECTIVE) {
5513
Vector3 cam_to_obj = _edit.center - _get_camera_position();
5514
if (!cam_to_obj.is_zero_approx()) {
5515
plane = Plane(cam_to_obj.normalized(), _edit.center);
5516
} else {
5517
plane = Plane(_get_camera_normal(), _edit.center);
5518
}
5519
} else {
5520
plane = Plane(_get_camera_normal(), _edit.center);
5521
}
5522
5523
Vector3 local_axis;
5524
Vector3 global_axis;
5525
switch (_edit.plane) {
5526
case TRANSFORM_VIEW:
5527
// local_axis unused
5528
global_axis = plane.normal;
5529
break;
5530
case TRANSFORM_X_AXIS:
5531
local_axis = Vector3(1, 0, 0);
5532
break;
5533
case TRANSFORM_Y_AXIS:
5534
local_axis = Vector3(0, 1, 0);
5535
break;
5536
case TRANSFORM_Z_AXIS:
5537
local_axis = Vector3(0, 0, 1);
5538
break;
5539
case TRANSFORM_YZ:
5540
case TRANSFORM_XZ:
5541
case TRANSFORM_XY:
5542
break;
5543
}
5544
5545
if (_edit.plane != TRANSFORM_VIEW) {
5546
global_axis = spatial_editor->get_gizmo_transform().basis.xform(local_axis).normalized();
5547
}
5548
5549
Vector3 intersection;
5550
if (!plane.intersects_ray(ray_pos, ray, &intersection)) {
5551
break;
5552
}
5553
5554
Vector3 click;
5555
if (!plane.intersects_ray(_edit.click_ray_pos, _edit.click_ray, &click)) {
5556
break;
5557
}
5558
5559
static const float orthogonal_threshold = Math::cos(Math::deg_to_rad(85.0f));
5560
bool axis_is_orthogonal = Math::abs(plane.normal.dot(global_axis)) < orthogonal_threshold;
5561
5562
double angle = 0.0f;
5563
if (axis_is_orthogonal) {
5564
_edit.show_rotation_line = false;
5565
Vector3 projection_axis = plane.normal.cross(global_axis);
5566
Vector3 delta = intersection - click;
5567
float projection = delta.dot(projection_axis);
5568
angle = (projection * (Math::PI / 2.0f)) / (gizmo_scale * GIZMO_CIRCLE_SIZE);
5569
} else {
5570
_edit.show_rotation_line = true;
5571
Vector3 click_axis = (click - _edit.center).normalized();
5572
Vector3 current_axis = (intersection - _edit.center).normalized();
5573
angle = click_axis.signed_angle_to(current_axis, global_axis);
5574
}
5575
5576
if (_edit.snap || spatial_editor->is_snap_enabled()) {
5577
snap = spatial_editor->get_rotate_snap();
5578
}
5579
angle = Math::snapped(Math::rad_to_deg(angle), snap);
5580
set_message(vformat(TTR("Rotating %s degrees."), String::num(angle, snap_step_decimals)));
5581
angle = Math::deg_to_rad(angle);
5582
5583
bool local_coords = (spatial_editor->are_local_coords_enabled() && _edit.plane != TRANSFORM_VIEW); // Disable local transformation for TRANSFORM_VIEW
5584
5585
Vector3 compute_axis = local_coords ? local_axis : global_axis;
5586
apply_transform(compute_axis, angle);
5587
} break;
5588
default: {
5589
}
5590
}
5591
}
5592
5593
void Node3DEditorViewport::update_transform_numeric() {
5594
Vector3 motion;
5595
switch (_edit.plane) {
5596
case TRANSFORM_VIEW: {
5597
switch (_edit.mode) {
5598
case TRANSFORM_TRANSLATE:
5599
motion = Vector3(1, 0, 0);
5600
break;
5601
case TRANSFORM_ROTATE:
5602
motion = spatial_editor->get_gizmo_transform().basis.xform_inv(_get_camera_normal()).normalized();
5603
break;
5604
case TRANSFORM_SCALE:
5605
motion = Vector3(1, 1, 1);
5606
break;
5607
case TRANSFORM_NONE:
5608
ERR_FAIL_MSG("_edit.mode cannot be TRANSFORM_NONE in update_transform_numeric.");
5609
}
5610
break;
5611
}
5612
case TRANSFORM_X_AXIS:
5613
motion = Vector3(1, 0, 0);
5614
break;
5615
case TRANSFORM_Y_AXIS:
5616
motion = Vector3(0, 1, 0);
5617
break;
5618
case TRANSFORM_Z_AXIS:
5619
motion = Vector3(0, 0, 1);
5620
break;
5621
case TRANSFORM_XY:
5622
motion = Vector3(1, 1, 0);
5623
break;
5624
case TRANSFORM_XZ:
5625
motion = Vector3(1, 0, 1);
5626
break;
5627
case TRANSFORM_YZ:
5628
motion = Vector3(0, 1, 1);
5629
break;
5630
}
5631
5632
double value = _edit.numeric_input * (_edit.numeric_negate ? -1 : 1);
5633
double extra = 0.0;
5634
switch (_edit.mode) {
5635
case TRANSFORM_TRANSLATE:
5636
motion *= value;
5637
set_message(vformat(TTR("Translating %s."), motion));
5638
break;
5639
case TRANSFORM_ROTATE:
5640
extra = Math::deg_to_rad(value);
5641
set_message(vformat(TTR("Rotating %f degrees."), value));
5642
break;
5643
case TRANSFORM_SCALE:
5644
// To halve the size of an object in Blender, you scale it by 0.5.
5645
// Doing the same in Godot is considered scaling it by -0.5.
5646
motion *= (value - 1.0);
5647
set_message(vformat(TTR("Scaling %s."), motion));
5648
break;
5649
case TRANSFORM_NONE:
5650
ERR_FAIL_MSG("_edit.mode cannot be TRANSFORM_NONE in update_transform_numeric.");
5651
}
5652
5653
apply_transform(motion, extra);
5654
}
5655
5656
// Perform cleanup after a transform operation is committed or canceled.
5657
void Node3DEditorViewport::finish_transform() {
5658
_edit.mode = TRANSFORM_NONE;
5659
_edit.instant = false;
5660
_edit.numeric_input = 0;
5661
_edit.numeric_next_decimal = 0;
5662
_edit.numeric_negate = false;
5663
spatial_editor->set_local_coords_enabled(_edit.original_local);
5664
spatial_editor->update_transform_gizmo();
5665
surface->queue_redraw();
5666
set_process_input(false);
5667
clicked = ObjectID();
5668
}
5669
5670
// Register a shortcut and also add it as an input action with the same events.
5671
void Node3DEditorViewport::register_shortcut_action(const String &p_path, const String &p_name, Key p_keycode, bool p_physical) {
5672
Ref<Shortcut> sc = ED_SHORTCUT(p_path, p_name, p_keycode, p_physical);
5673
shortcut_changed_callback(sc, p_path);
5674
// Connect to the change event on the shortcut so the input binding can be updated.
5675
sc->connect_changed(callable_mp(this, &Node3DEditorViewport::shortcut_changed_callback).bind(sc, p_path));
5676
}
5677
5678
// Update the action in the InputMap to the provided shortcut events.
5679
void Node3DEditorViewport::shortcut_changed_callback(const Ref<Shortcut> p_shortcut, const String &p_shortcut_path) {
5680
InputMap *im = InputMap::get_singleton();
5681
if (im->has_action(p_shortcut_path)) {
5682
im->action_erase_events(p_shortcut_path);
5683
} else {
5684
im->add_action(p_shortcut_path);
5685
}
5686
5687
for (int i = 0; i < p_shortcut->get_events().size(); i++) {
5688
im->action_add_event(p_shortcut_path, p_shortcut->get_events()[i]);
5689
}
5690
}
5691
5692
void Node3DEditorViewport::_set_lock_view_rotation(bool p_lock_rotation) {
5693
lock_rotation = p_lock_rotation;
5694
int idx = view_display_menu->get_popup()->get_item_index(VIEW_LOCK_ROTATION);
5695
view_display_menu->get_popup()->set_item_checked(idx, p_lock_rotation);
5696
if (p_lock_rotation) {
5697
locked_label->show();
5698
} else {
5699
locked_label->hide();
5700
}
5701
}
5702
5703
void Node3DEditorViewport::_add_advanced_debug_draw_mode_item(PopupMenu *p_popup, const String &p_name, int p_value, SupportedRenderingMethods p_rendering_methods, const String &p_tooltip) {
5704
display_submenu->add_radio_check_item(p_name, p_value);
5705
Array item_data = { p_rendering_methods, p_tooltip };
5706
display_submenu->set_item_metadata(-1, item_data); // Tooltip is assigned in NOTIFICATION_TRANSLATION_CHANGED.
5707
}
5708
5709
Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p_index) {
5710
cpu_time_history_index = 0;
5711
gpu_time_history_index = 0;
5712
5713
_edit.mode = TRANSFORM_NONE;
5714
_edit.plane = TRANSFORM_VIEW;
5715
_edit.snap = true;
5716
_edit.show_rotation_line = true;
5717
_edit.instant = false;
5718
_edit.gizmo_handle = -1;
5719
_edit.gizmo_handle_secondary = false;
5720
5721
index = p_index;
5722
editor_selection = EditorNode::get_singleton()->get_editor_selection();
5723
5724
orthogonal = false;
5725
auto_orthogonal = false;
5726
lock_rotation = false;
5727
message_time = 0;
5728
zoom_indicator_delay = 0.0;
5729
5730
spatial_editor = p_spatial_editor;
5731
SubViewportContainer *c = memnew(SubViewportContainer);
5732
subviewport_container = c;
5733
c->set_stretch(true);
5734
add_child(c);
5735
c->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
5736
viewport = memnew(SubViewport);
5737
viewport->set_disable_input(true);
5738
5739
c->add_child(viewport);
5740
surface = memnew(Control);
5741
SET_DRAG_FORWARDING_CD(surface, Node3DEditorViewport);
5742
add_child(surface);
5743
surface->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
5744
surface->set_clip_contents(true);
5745
camera = memnew(Camera3D);
5746
camera->set_disable_gizmos(true);
5747
camera->set_cull_mask(((1 << 20) - 1) | (1 << (GIZMO_BASE_LAYER + p_index)) | (1 << GIZMO_EDIT_LAYER) | (1 << GIZMO_GRID_LAYER) | (1 << MISC_TOOL_LAYER));
5748
viewport->add_child(camera);
5749
camera->make_current();
5750
surface->set_focus_mode(FOCUS_ALL);
5751
5752
VBoxContainer *vbox = memnew(VBoxContainer);
5753
surface->add_child(vbox);
5754
vbox->set_offset(SIDE_LEFT, 10 * EDSCALE);
5755
vbox->set_offset(SIDE_TOP, 10 * EDSCALE);
5756
5757
HBoxContainer *hbox = memnew(HBoxContainer);
5758
vbox->add_child(hbox);
5759
5760
view_display_menu = memnew(MenuButton);
5761
view_display_menu->set_flat(false);
5762
view_display_menu->set_h_size_flags(0);
5763
view_display_menu->set_shortcut_context(this);
5764
view_display_menu->set_accessibility_name(TTRC("View"));
5765
view_display_menu->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
5766
view_display_menu->get_popup()->set_auto_translate_mode(AUTO_TRANSLATE_MODE_ALWAYS);
5767
hbox->add_child(view_display_menu);
5768
5769
view_display_menu->get_popup()->set_hide_on_checkable_item_selection(false);
5770
5771
view_display_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/top_view"), VIEW_TOP);
5772
view_display_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/bottom_view"), VIEW_BOTTOM);
5773
view_display_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/left_view"), VIEW_LEFT);
5774
view_display_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/right_view"), VIEW_RIGHT);
5775
view_display_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/front_view"), VIEW_FRONT);
5776
view_display_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/rear_view"), VIEW_REAR);
5777
view_display_menu->get_popup()->add_separator();
5778
view_display_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/switch_perspective_orthogonal"), VIEW_SWITCH_PERSPECTIVE_ORTHOGONAL);
5779
view_display_menu->get_popup()->add_radio_check_item(TTRC("Perspective"), VIEW_PERSPECTIVE);
5780
view_display_menu->get_popup()->add_radio_check_item(TTRC("Orthogonal"), VIEW_ORTHOGONAL);
5781
view_display_menu->get_popup()->set_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_PERSPECTIVE), true);
5782
view_display_menu->get_popup()->add_check_item(TTRC("Auto Orthogonal Enabled"), VIEW_AUTO_ORTHOGONAL);
5783
view_display_menu->get_popup()->set_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_AUTO_ORTHOGONAL), true);
5784
view_display_menu->get_popup()->add_separator();
5785
view_display_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_lock_rotation", TTRC("Lock View Rotation")), VIEW_LOCK_ROTATION);
5786
view_display_menu->get_popup()->add_separator();
5787
// TRANSLATORS: "Normal" as in "normal life", not "normal vector".
5788
view_display_menu->get_popup()->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_normal", TTRC("Display Normal")), VIEW_DISPLAY_NORMAL);
5789
view_display_menu->get_popup()->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_wireframe", TTRC("Display Wireframe")), VIEW_DISPLAY_WIREFRAME);
5790
view_display_menu->get_popup()->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_overdraw", TTRC("Display Overdraw")), VIEW_DISPLAY_OVERDRAW);
5791
view_display_menu->get_popup()->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_lighting", TTRC("Display Lighting")), VIEW_DISPLAY_LIGHTING);
5792
view_display_menu->get_popup()->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_unshaded", TTRC("Display Unshaded")), VIEW_DISPLAY_UNSHADED);
5793
view_display_menu->get_popup()->set_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_DISPLAY_NORMAL), true);
5794
5795
display_submenu = memnew(PopupMenu);
5796
display_submenu->set_hide_on_checkable_item_selection(false);
5797
_add_advanced_debug_draw_mode_item(display_submenu, TTRC("Directional Shadow Splits"), VIEW_DISPLAY_DEBUG_PSSM_SPLITS, SupportedRenderingMethods::FORWARD_PLUS_MOBILE,
5798
TTRC("Displays directional shadow splits in different colors to make adjusting split thresholds easier. \nRed: 1st split (closest to the camera), Green: 2nd split, Blue: 3rd split, Yellow: 4th split (furthest from the camera)"));
5799
display_submenu->add_separator();
5800
// TRANSLATORS: "Normal" as in "normal vector", not "normal life".
5801
_add_advanced_debug_draw_mode_item(display_submenu, TTRC("Normal Buffer"), VIEW_DISPLAY_NORMAL_BUFFER, SupportedRenderingMethods::FORWARD_PLUS);
5802
display_submenu->add_separator();
5803
_add_advanced_debug_draw_mode_item(display_submenu, TTRC("Shadow Atlas"), VIEW_DISPLAY_DEBUG_SHADOW_ATLAS, SupportedRenderingMethods::ALL,
5804
TTRC("Displays the shadow atlas used for positional (omni/spot) shadow mapping.\nRequires a visible OmniLight3D or SpotLight3D node with shadows enabled to have a visible effect."));
5805
_add_advanced_debug_draw_mode_item(display_submenu, TTRC("Directional Shadow Map"), VIEW_DISPLAY_DEBUG_DIRECTIONAL_SHADOW_ATLAS, SupportedRenderingMethods::ALL,
5806
TTRC("Displays the shadow map used for directional shadow mapping.\nRequires a visible DirectionalLight3D node with shadows enabled to have a visible effect."));
5807
display_submenu->add_separator();
5808
_add_advanced_debug_draw_mode_item(display_submenu, TTRC("Decal Atlas"), VIEW_DISPLAY_DEBUG_DECAL_ATLAS, SupportedRenderingMethods::FORWARD_PLUS_MOBILE);
5809
display_submenu->add_separator();
5810
_add_advanced_debug_draw_mode_item(display_submenu, TTRC("VoxelGI Lighting"), VIEW_DISPLAY_DEBUG_VOXEL_GI_LIGHTING, SupportedRenderingMethods::FORWARD_PLUS,
5811
TTRC("Requires a visible VoxelGI node that has been baked to have a visible effect."));
5812
_add_advanced_debug_draw_mode_item(display_submenu, TTRC("VoxelGI Albedo"), VIEW_DISPLAY_DEBUG_VOXEL_GI_ALBEDO, SupportedRenderingMethods::FORWARD_PLUS,
5813
TTRC("Requires a visible VoxelGI node that has been baked to have a visible effect."));
5814
_add_advanced_debug_draw_mode_item(display_submenu, TTRC("VoxelGI Emission"), VIEW_DISPLAY_DEBUG_VOXEL_GI_EMISSION, SupportedRenderingMethods::FORWARD_PLUS,
5815
TTRC("Requires a visible VoxelGI node that has been baked to have a visible effect."));
5816
display_submenu->add_separator();
5817
_add_advanced_debug_draw_mode_item(display_submenu, TTRC("SDFGI Cascades"), VIEW_DISPLAY_DEBUG_SDFGI, SupportedRenderingMethods::FORWARD_PLUS,
5818
TTRC("Requires SDFGI to be enabled in Environment to have a visible effect."));
5819
_add_advanced_debug_draw_mode_item(display_submenu, TTRC("SDFGI Probes"), VIEW_DISPLAY_DEBUG_SDFGI_PROBES, SupportedRenderingMethods::FORWARD_PLUS,
5820
TTRC("Requires SDFGI to be enabled in Environment to have a visible effect."));
5821
display_submenu->add_separator();
5822
_add_advanced_debug_draw_mode_item(display_submenu, TTRC("Scene Luminance"), VIEW_DISPLAY_DEBUG_SCENE_LUMINANCE, SupportedRenderingMethods::FORWARD_PLUS_MOBILE,
5823
TTRC("Displays the scene luminance computed from the 3D buffer. This is used for Auto Exposure calculation.\nRequires Auto Exposure to be enabled in CameraAttributes to have a visible effect."));
5824
display_submenu->add_separator();
5825
_add_advanced_debug_draw_mode_item(display_submenu, TTRC("SSAO"), VIEW_DISPLAY_DEBUG_SSAO, SupportedRenderingMethods::FORWARD_PLUS,
5826
TTRC("Displays the screen-space ambient occlusion buffer. Requires SSAO to be enabled in Environment to have a visible effect."));
5827
_add_advanced_debug_draw_mode_item(display_submenu, TTRC("SSIL"), VIEW_DISPLAY_DEBUG_SSIL, SupportedRenderingMethods::FORWARD_PLUS,
5828
TTRC("Displays the screen-space indirect lighting buffer. Requires SSIL to be enabled in Environment to have a visible effect."));
5829
display_submenu->add_separator();
5830
_add_advanced_debug_draw_mode_item(display_submenu, TTRC("VoxelGI/SDFGI Buffer"), VIEW_DISPLAY_DEBUG_GI_BUFFER, SupportedRenderingMethods::FORWARD_PLUS,
5831
TTRC("Requires SDFGI or VoxelGI to be enabled to have a visible effect."));
5832
display_submenu->add_separator();
5833
_add_advanced_debug_draw_mode_item(display_submenu, TTRC("Disable Mesh LOD"), VIEW_DISPLAY_DEBUG_DISABLE_LOD, SupportedRenderingMethods::ALL,
5834
TTRC("Renders all meshes with their highest level of detail regardless of their distance from the camera."));
5835
display_submenu->add_separator();
5836
_add_advanced_debug_draw_mode_item(display_submenu, TTRC("OmniLight3D Cluster"), VIEW_DISPLAY_DEBUG_CLUSTER_OMNI_LIGHTS, SupportedRenderingMethods::FORWARD_PLUS,
5837
TTRC("Highlights tiles of pixels that are affected by at least one OmniLight3D."));
5838
_add_advanced_debug_draw_mode_item(display_submenu, TTRC("SpotLight3D Cluster"), VIEW_DISPLAY_DEBUG_CLUSTER_SPOT_LIGHTS, SupportedRenderingMethods::FORWARD_PLUS,
5839
TTRC("Highlights tiles of pixels that are affected by at least one SpotLight3D."));
5840
_add_advanced_debug_draw_mode_item(display_submenu, TTRC("Decal Cluster"), VIEW_DISPLAY_DEBUG_CLUSTER_DECALS, SupportedRenderingMethods::FORWARD_PLUS,
5841
TTRC("Highlights tiles of pixels that are affected by at least one Decal."));
5842
_add_advanced_debug_draw_mode_item(display_submenu, TTRC("ReflectionProbe Cluster"), VIEW_DISPLAY_DEBUG_CLUSTER_REFLECTION_PROBES, SupportedRenderingMethods::FORWARD_PLUS,
5843
TTRC("Highlights tiles of pixels that are affected by at least one ReflectionProbe."));
5844
_add_advanced_debug_draw_mode_item(display_submenu, TTRC("Occlusion Culling Buffer"), VIEW_DISPLAY_DEBUG_OCCLUDERS, SupportedRenderingMethods::FORWARD_PLUS_MOBILE,
5845
TTRC("Represents occluders with black pixels. Requires occlusion culling to be enabled to have a visible effect."));
5846
_add_advanced_debug_draw_mode_item(display_submenu, TTRC("Motion Vectors"), VIEW_DISPLAY_MOTION_VECTORS, SupportedRenderingMethods::FORWARD_PLUS,
5847
TTRC("Represents motion vectors with colored lines in the direction of motion. Gray dots represent areas with no per-pixel motion."));
5848
_add_advanced_debug_draw_mode_item(display_submenu, TTRC("Internal Buffer"), VIEW_DISPLAY_INTERNAL_BUFFER, SupportedRenderingMethods::FORWARD_PLUS_MOBILE,
5849
TTRC("Shows the scene rendered in linear colorspace before any tonemapping or post-processing."));
5850
view_display_menu->get_popup()->add_submenu_node_item(TTRC("Display Advanced..."), display_submenu, VIEW_DISPLAY_ADVANCED);
5851
5852
view_display_menu->get_popup()->add_separator();
5853
view_display_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_environment", TTRC("View Environment")), VIEW_ENVIRONMENT);
5854
view_display_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_gizmos", TTRC("View Gizmos")), VIEW_GIZMOS);
5855
view_display_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_transform_gizmo", TTRC("View Transform Gizmo")), VIEW_TRANSFORM_GIZMO);
5856
view_display_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_grid_lines", TTRC("View Grid")), VIEW_GRID);
5857
view_display_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_information", TTRC("View Information")), VIEW_INFORMATION);
5858
view_display_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_fps", TTRC("View Frame Time")), VIEW_FRAME_TIME);
5859
view_display_menu->get_popup()->set_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_ENVIRONMENT), true);
5860
view_display_menu->get_popup()->add_separator();
5861
view_display_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_half_resolution", TTRC("Half Resolution")), VIEW_HALF_RESOLUTION);
5862
view_display_menu->get_popup()->add_separator();
5863
view_display_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_audio_listener", TTRC("Audio Listener")), VIEW_AUDIO_LISTENER);
5864
view_display_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_audio_doppler", TTRC("Enable Doppler")), VIEW_AUDIO_DOPPLER);
5865
view_display_menu->get_popup()->set_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_GIZMOS), true);
5866
view_display_menu->get_popup()->set_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_TRANSFORM_GIZMO), true);
5867
view_display_menu->get_popup()->set_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_GRID), true);
5868
5869
view_display_menu->get_popup()->add_separator();
5870
view_display_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_cinematic_preview", TTRC("Cinematic Preview")), VIEW_CINEMATIC_PREVIEW);
5871
5872
view_display_menu->get_popup()->add_separator();
5873
view_display_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/focus_origin"), VIEW_CENTER_TO_ORIGIN);
5874
view_display_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/focus_selection"), VIEW_CENTER_TO_SELECTION);
5875
view_display_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/align_transform_with_view"), VIEW_ALIGN_TRANSFORM_WITH_VIEW);
5876
view_display_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/align_rotation_with_view"), VIEW_ALIGN_ROTATION_WITH_VIEW);
5877
view_display_menu->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &Node3DEditorViewport::_menu_option));
5878
display_submenu->connect(SceneStringName(id_pressed), callable_mp(this, &Node3DEditorViewport::_menu_option));
5879
view_display_menu->set_disable_shortcuts(true);
5880
5881
// Registering with Key::NONE intentionally creates an empty Array.
5882
register_shortcut_action("spatial_editor/viewport_orbit_modifier_1", TTRC("Viewport Orbit Modifier 1"), Key::NONE);
5883
register_shortcut_action("spatial_editor/viewport_orbit_modifier_2", TTRC("Viewport Orbit Modifier 2"), Key::NONE);
5884
register_shortcut_action("spatial_editor/viewport_pan_modifier_1", TTRC("Viewport Pan Modifier 1"), Key::SHIFT);
5885
register_shortcut_action("spatial_editor/viewport_pan_modifier_2", TTRC("Viewport Pan Modifier 2"), Key::NONE);
5886
register_shortcut_action("spatial_editor/viewport_zoom_modifier_1", TTRC("Viewport Zoom Modifier 1"), Key::SHIFT);
5887
register_shortcut_action("spatial_editor/viewport_zoom_modifier_2", TTRC("Viewport Zoom Modifier 2"), Key::CTRL);
5888
5889
register_shortcut_action("spatial_editor/freelook_left", TTRC("Freelook Left"), Key::A, true);
5890
register_shortcut_action("spatial_editor/freelook_right", TTRC("Freelook Right"), Key::D, true);
5891
register_shortcut_action("spatial_editor/freelook_forward", TTRC("Freelook Forward"), Key::W, true);
5892
register_shortcut_action("spatial_editor/freelook_backwards", TTRC("Freelook Backwards"), Key::S, true);
5893
register_shortcut_action("spatial_editor/freelook_up", TTRC("Freelook Up"), Key::E, true);
5894
register_shortcut_action("spatial_editor/freelook_down", TTRC("Freelook Down"), Key::Q, true);
5895
register_shortcut_action("spatial_editor/freelook_speed_modifier", TTRC("Freelook Speed Modifier"), Key::SHIFT);
5896
register_shortcut_action("spatial_editor/freelook_slow_modifier", TTRC("Freelook Slow Modifier"), Key::ALT);
5897
5898
ED_SHORTCUT("spatial_editor/lock_transform_x", TTRC("Lock Transformation to X axis"), Key::X);
5899
ED_SHORTCUT("spatial_editor/lock_transform_y", TTRC("Lock Transformation to Y axis"), Key::Y);
5900
ED_SHORTCUT("spatial_editor/lock_transform_z", TTRC("Lock Transformation to Z axis"), Key::Z);
5901
ED_SHORTCUT("spatial_editor/lock_transform_yz", TTRC("Lock Transformation to YZ plane"), KeyModifierMask::SHIFT | Key::X);
5902
ED_SHORTCUT("spatial_editor/lock_transform_xz", TTRC("Lock Transformation to XZ plane"), KeyModifierMask::SHIFT | Key::Y);
5903
ED_SHORTCUT("spatial_editor/lock_transform_xy", TTRC("Lock Transformation to XY plane"), KeyModifierMask::SHIFT | Key::Z);
5904
ED_SHORTCUT("spatial_editor/cancel_transform", TTRC("Cancel Transformation"), Key::ESCAPE);
5905
ED_SHORTCUT("spatial_editor/instant_translate", TTRC("Begin Translate Transformation"));
5906
ED_SHORTCUT("spatial_editor/instant_rotate", TTRC("Begin Rotate Transformation"));
5907
ED_SHORTCUT("spatial_editor/instant_scale", TTRC("Begin Scale Transformation"));
5908
ED_SHORTCUT("spatial_editor/collision_reposition", TTRC("Reposition Using Collisions"), KeyModifierMask::SHIFT | Key::G);
5909
5910
translation_preview_button = memnew(EditorTranslationPreviewButton);
5911
hbox->add_child(translation_preview_button);
5912
5913
preview_camera = memnew(CheckBox);
5914
preview_camera->set_text(TTRC("Preview"));
5915
// Using Control even on macOS to avoid conflict with Quick Open shortcut.
5916
preview_camera->set_shortcut(ED_SHORTCUT("spatial_editor/toggle_camera_preview", TTRC("Toggle Camera Preview"), KeyModifierMask::CTRL | Key::P));
5917
vbox->add_child(preview_camera);
5918
preview_camera->set_h_size_flags(0);
5919
preview_camera->hide();
5920
preview_camera->connect(SceneStringName(toggled), callable_mp(this, &Node3DEditorViewport::_toggle_camera_preview));
5921
previewing = nullptr;
5922
gizmo_scale = 1.0;
5923
5924
preview_node = nullptr;
5925
5926
bottom_center_vbox = memnew(VBoxContainer);
5927
bottom_center_vbox->set_anchors_preset(LayoutPreset::PRESET_CENTER);
5928
bottom_center_vbox->set_anchor_and_offset(SIDE_TOP, ANCHOR_END, -20 * EDSCALE);
5929
bottom_center_vbox->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, -10 * EDSCALE);
5930
bottom_center_vbox->set_h_grow_direction(GROW_DIRECTION_BOTH);
5931
bottom_center_vbox->set_v_grow_direction(GROW_DIRECTION_BEGIN);
5932
surface->add_child(bottom_center_vbox);
5933
5934
info_panel = memnew(PanelContainer);
5935
info_panel->set_anchor_and_offset(SIDE_LEFT, ANCHOR_END, -90 * EDSCALE);
5936
info_panel->set_anchor_and_offset(SIDE_TOP, ANCHOR_END, -90 * EDSCALE);
5937
info_panel->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_END, -10 * EDSCALE);
5938
info_panel->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, -10 * EDSCALE);
5939
info_panel->set_h_grow_direction(GROW_DIRECTION_BEGIN);
5940
info_panel->set_v_grow_direction(GROW_DIRECTION_BEGIN);
5941
info_panel->set_mouse_filter(MOUSE_FILTER_IGNORE);
5942
surface->add_child(info_panel);
5943
info_panel->hide();
5944
5945
info_label = memnew(Label);
5946
info_label->set_focus_mode(FOCUS_ACCESSIBILITY);
5947
info_panel->add_child(info_label);
5948
5949
cinema_label = memnew(Label);
5950
cinema_label->set_anchor_and_offset(SIDE_TOP, ANCHOR_BEGIN, 10 * EDSCALE);
5951
cinema_label->set_h_grow_direction(GROW_DIRECTION_END);
5952
cinema_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
5953
cinema_label->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
5954
surface->add_child(cinema_label);
5955
cinema_label->set_text(TTRC("Cinematic Preview"));
5956
cinema_label->hide();
5957
previewing_cinema = false;
5958
5959
locked_label = memnew(Label);
5960
locked_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
5961
locked_label->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
5962
locked_label->set_h_size_flags(SIZE_SHRINK_CENTER);
5963
bottom_center_vbox->add_child(locked_label);
5964
locked_label->set_text(TTRC("View Rotation Locked"));
5965
locked_label->hide();
5966
5967
zoom_limit_label = memnew(Label);
5968
zoom_limit_label->set_text(TTRC("To zoom further, change the camera's clipping planes (View -> Settings...)"));
5969
zoom_limit_label->set_name("ZoomLimitMessageLabel");
5970
zoom_limit_label->add_theme_color_override(SceneStringName(font_color), Color(1, 1, 1, 1));
5971
zoom_limit_label->hide();
5972
bottom_center_vbox->add_child(zoom_limit_label);
5973
5974
preview_material_label = memnew(Label);
5975
preview_material_label->set_anchors_and_offsets_preset(LayoutPreset::PRESET_BOTTOM_LEFT);
5976
preview_material_label->set_offset(Side::SIDE_TOP, -70 * EDSCALE);
5977
preview_material_label->set_text(TTRC("Overriding material..."));
5978
preview_material_label->add_theme_color_override(SceneStringName(font_color), Color(1, 1, 1, 1));
5979
preview_material_label->hide();
5980
surface->add_child(preview_material_label);
5981
5982
preview_material_label_desc = memnew(Label);
5983
preview_material_label_desc->set_focus_mode(FOCUS_ACCESSIBILITY);
5984
preview_material_label_desc->set_anchors_and_offsets_preset(LayoutPreset::PRESET_BOTTOM_LEFT);
5985
preview_material_label_desc->set_offset(Side::SIDE_TOP, -50 * EDSCALE);
5986
preview_material_label_desc->add_theme_color_override(SceneStringName(font_color), Color(0.8, 0.8, 0.8, 1));
5987
preview_material_label_desc->add_theme_constant_override("line_spacing", 0);
5988
preview_material_label_desc->hide();
5989
surface->add_child(preview_material_label_desc);
5990
5991
frame_time_gradient = memnew(Gradient);
5992
// The color is set when the theme changes.
5993
frame_time_gradient->add_point(0.5, Color());
5994
5995
top_right_vbox = memnew(VBoxContainer);
5996
top_right_vbox->add_theme_constant_override("separation", 10.0 * EDSCALE);
5997
top_right_vbox->set_anchors_and_offsets_preset(PRESET_TOP_RIGHT, PRESET_MODE_MINSIZE, 10.0 * EDSCALE);
5998
top_right_vbox->set_h_grow_direction(GROW_DIRECTION_BEGIN);
5999
6000
const int navigation_control_size = 150;
6001
6002
position_control = memnew(ViewportNavigationControl);
6003
position_control->set_navigation_mode(Node3DEditorViewport::NAVIGATION_MOVE);
6004
position_control->set_custom_minimum_size(Size2(navigation_control_size, navigation_control_size) * EDSCALE);
6005
position_control->set_h_size_flags(SIZE_SHRINK_END);
6006
position_control->set_anchor_and_offset(SIDE_LEFT, ANCHOR_BEGIN, 0);
6007
position_control->set_anchor_and_offset(SIDE_TOP, ANCHOR_END, -navigation_control_size * EDSCALE);
6008
position_control->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_BEGIN, navigation_control_size * EDSCALE);
6009
position_control->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, 0);
6010
position_control->set_viewport(this);
6011
surface->add_child(position_control);
6012
6013
look_control = memnew(ViewportNavigationControl);
6014
look_control->set_navigation_mode(Node3DEditorViewport::NAVIGATION_LOOK);
6015
look_control->set_custom_minimum_size(Size2(navigation_control_size, navigation_control_size) * EDSCALE);
6016
look_control->set_h_size_flags(SIZE_SHRINK_END);
6017
look_control->set_anchor_and_offset(SIDE_LEFT, ANCHOR_END, -navigation_control_size * EDSCALE);
6018
look_control->set_anchor_and_offset(SIDE_TOP, ANCHOR_END, -navigation_control_size * EDSCALE);
6019
look_control->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_END, 0);
6020
look_control->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, 0);
6021
look_control->set_viewport(this);
6022
surface->add_child(look_control);
6023
6024
rotation_control = memnew(ViewportRotationControl);
6025
rotation_control->set_custom_minimum_size(Size2(80, 80) * EDSCALE);
6026
rotation_control->set_h_size_flags(SIZE_SHRINK_END);
6027
rotation_control->set_viewport(this);
6028
rotation_control->set_focus_mode(FOCUS_CLICK);
6029
top_right_vbox->add_child(rotation_control);
6030
6031
frame_time_panel = memnew(PanelContainer);
6032
frame_time_panel->set_mouse_filter(MOUSE_FILTER_IGNORE);
6033
top_right_vbox->add_child(frame_time_panel);
6034
frame_time_panel->hide();
6035
6036
frame_time_vbox = memnew(VBoxContainer);
6037
frame_time_panel->add_child(frame_time_vbox);
6038
6039
// Individual Labels are used to allow coloring each label with its own color.
6040
cpu_time_label = memnew(Label);
6041
frame_time_vbox->add_child(cpu_time_label);
6042
6043
gpu_time_label = memnew(Label);
6044
frame_time_vbox->add_child(gpu_time_label);
6045
6046
fps_label = memnew(Label);
6047
frame_time_vbox->add_child(fps_label);
6048
6049
surface->add_child(top_right_vbox);
6050
6051
accept = nullptr;
6052
6053
freelook_active = false;
6054
freelook_speed = EDITOR_GET("editors/3d/freelook/freelook_base_speed");
6055
6056
selection_menu = memnew(PopupMenu);
6057
add_child(selection_menu);
6058
selection_menu->set_min_size(Size2(100, 0) * EDSCALE);
6059
selection_menu->connect(SceneStringName(id_pressed), callable_mp(this, &Node3DEditorViewport::_selection_result_pressed));
6060
selection_menu->connect("popup_hide", callable_mp(this, &Node3DEditorViewport::_selection_menu_hide));
6061
6062
if (p_index == 0) {
6063
view_display_menu->get_popup()->set_item_checked(view_display_menu->get_popup()->get_item_index(VIEW_AUDIO_LISTENER), true);
6064
viewport->set_as_audio_listener_3d(true);
6065
}
6066
6067
ruler = memnew(Node);
6068
6069
ruler_start_point = memnew(Node3D);
6070
ruler_start_point->set_visible(false);
6071
6072
ruler_end_point = memnew(Node3D);
6073
ruler_end_point->set_visible(false);
6074
6075
ruler_material.instantiate();
6076
ruler_material->set_albedo(Color(1.0, 0.9, 0.0, 1.0));
6077
ruler_material->set_flag(BaseMaterial3D::FLAG_DISABLE_FOG, true);
6078
ruler_material->set_shading_mode(BaseMaterial3D::SHADING_MODE_UNSHADED);
6079
ruler_material->set_depth_draw_mode(BaseMaterial3D::DEPTH_DRAW_DISABLED);
6080
6081
ruler_material_xray.instantiate();
6082
ruler_material_xray->set_albedo(Color(1.0, 0.9, 0.0, 0.15));
6083
ruler_material_xray->set_flag(BaseMaterial3D::FLAG_DISABLE_FOG, true);
6084
ruler_material_xray->set_shading_mode(BaseMaterial3D::SHADING_MODE_UNSHADED);
6085
ruler_material_xray->set_flag(BaseMaterial3D::FLAG_DISABLE_DEPTH_TEST, true);
6086
ruler_material_xray->set_transparency(BaseMaterial3D::TRANSPARENCY_ALPHA);
6087
ruler_material_xray->set_render_priority(BaseMaterial3D::RENDER_PRIORITY_MAX);
6088
6089
geometry.instantiate();
6090
6091
ruler_line = memnew(MeshInstance3D);
6092
ruler_line->set_mesh(geometry);
6093
ruler_line->set_material_override(ruler_material);
6094
6095
ruler_line_xray = memnew(MeshInstance3D);
6096
ruler_line_xray->set_mesh(geometry);
6097
ruler_line_xray->set_material_override(ruler_material_xray);
6098
6099
ruler_label = memnew(Label);
6100
ruler_label->set_visible(false);
6101
6102
ruler->add_child(ruler_start_point);
6103
ruler->add_child(ruler_end_point);
6104
ruler->add_child(ruler_line);
6105
ruler->add_child(ruler_line_xray);
6106
6107
viewport->add_child(ruler_label);
6108
6109
view_type = VIEW_TYPE_USER;
6110
_update_name();
6111
6112
EditorSettings::get_singleton()->connect("settings_changed", callable_mp(this, &Node3DEditorViewport::update_transform_gizmo_view));
6113
}
6114
6115
Node3DEditorViewport::~Node3DEditorViewport() {
6116
memdelete(ruler);
6117
memdelete(frame_time_gradient);
6118
}
6119
6120
//////////////////////////////////////////////////////////////
6121
6122
void Node3DEditorViewportContainer::gui_input(const Ref<InputEvent> &p_event) {
6123
ERR_FAIL_COND(p_event.is_null());
6124
6125
if (_redirect_freelook_input(p_event)) {
6126
return;
6127
}
6128
6129
Ref<InputEventMouseButton> mb = p_event;
6130
6131
if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT) {
6132
if (mb->is_pressed()) {
6133
Vector2 size = get_size();
6134
6135
int h_sep = get_theme_constant(SNAME("separation"), SNAME("HSplitContainer"));
6136
int v_sep = get_theme_constant(SNAME("separation"), SNAME("VSplitContainer"));
6137
6138
int mid_w = size.width * ratio_h;
6139
int mid_h = size.height * ratio_v;
6140
6141
dragging_h = mb->get_position().x > (mid_w - h_sep / 2) && mb->get_position().x < (mid_w + h_sep / 2);
6142
dragging_v = mb->get_position().y > (mid_h - v_sep / 2) && mb->get_position().y < (mid_h + v_sep / 2);
6143
6144
drag_begin_pos = mb->get_position();
6145
drag_begin_ratio.x = ratio_h;
6146
drag_begin_ratio.y = ratio_v;
6147
6148
switch (view) {
6149
case VIEW_USE_1_VIEWPORT: {
6150
dragging_h = false;
6151
dragging_v = false;
6152
6153
} break;
6154
case VIEW_USE_2_VIEWPORTS: {
6155
dragging_h = false;
6156
6157
} break;
6158
case VIEW_USE_2_VIEWPORTS_ALT: {
6159
dragging_v = false;
6160
6161
} break;
6162
case VIEW_USE_3_VIEWPORTS:
6163
case VIEW_USE_3_VIEWPORTS_ALT:
6164
case VIEW_USE_4_VIEWPORTS: {
6165
// Do nothing.
6166
6167
} break;
6168
}
6169
} else {
6170
dragging_h = false;
6171
dragging_v = false;
6172
}
6173
}
6174
6175
Ref<InputEventMouseMotion> mm = p_event;
6176
6177
if (mm.is_valid()) {
6178
if (view == VIEW_USE_3_VIEWPORTS || view == VIEW_USE_3_VIEWPORTS_ALT || view == VIEW_USE_4_VIEWPORTS) {
6179
Vector2 size = get_size();
6180
6181
int h_sep = get_theme_constant(SNAME("separation"), SNAME("HSplitContainer"));
6182
int v_sep = get_theme_constant(SNAME("separation"), SNAME("VSplitContainer"));
6183
6184
int mid_w = size.width * ratio_h;
6185
int mid_h = size.height * ratio_v;
6186
6187
bool was_hovering_h = hovering_h;
6188
bool was_hovering_v = hovering_v;
6189
hovering_h = mm->get_position().x > (mid_w - h_sep / 2) && mm->get_position().x < (mid_w + h_sep / 2);
6190
hovering_v = mm->get_position().y > (mid_h - v_sep / 2) && mm->get_position().y < (mid_h + v_sep / 2);
6191
6192
if (was_hovering_h != hovering_h || was_hovering_v != hovering_v) {
6193
queue_redraw();
6194
}
6195
}
6196
6197
if (dragging_h) {
6198
real_t new_ratio = drag_begin_ratio.x + (mm->get_position().x - drag_begin_pos.x) / get_size().width;
6199
new_ratio = CLAMP(new_ratio, 40 / get_size().width, (get_size().width - 40) / get_size().width);
6200
ratio_h = new_ratio;
6201
queue_sort();
6202
queue_redraw();
6203
}
6204
if (dragging_v) {
6205
real_t new_ratio = drag_begin_ratio.y + (mm->get_position().y - drag_begin_pos.y) / get_size().height;
6206
new_ratio = CLAMP(new_ratio, 40 / get_size().height, (get_size().height - 40) / get_size().height);
6207
ratio_v = new_ratio;
6208
queue_sort();
6209
queue_redraw();
6210
}
6211
}
6212
}
6213
6214
void Node3DEditorViewportContainer::_notification(int p_what) {
6215
switch (p_what) {
6216
case NOTIFICATION_MOUSE_ENTER:
6217
case NOTIFICATION_MOUSE_EXIT: {
6218
mouseover = (p_what == NOTIFICATION_MOUSE_ENTER);
6219
queue_redraw();
6220
} break;
6221
6222
case NOTIFICATION_DRAW: {
6223
if (mouseover && Input::get_singleton()->get_mouse_mode() != Input::MOUSE_MODE_CAPTURED) {
6224
Ref<Texture2D> h_grabber = get_theme_icon(SNAME("grabber"), SNAME("HSplitContainer"));
6225
Ref<Texture2D> v_grabber = get_theme_icon(SNAME("grabber"), SNAME("VSplitContainer"));
6226
6227
Ref<Texture2D> hdiag_grabber = get_editor_theme_icon(SNAME("GuiViewportHdiagsplitter"));
6228
Ref<Texture2D> vdiag_grabber = get_editor_theme_icon(SNAME("GuiViewportVdiagsplitter"));
6229
Ref<Texture2D> vh_grabber = get_editor_theme_icon(SNAME("GuiViewportVhsplitter"));
6230
6231
Vector2 size = get_size();
6232
6233
int h_sep = get_theme_constant(SNAME("separation"), SNAME("HSplitContainer"));
6234
6235
int v_sep = get_theme_constant(SNAME("separation"), SNAME("VSplitContainer"));
6236
6237
int mid_w = size.width * ratio_h;
6238
int mid_h = size.height * ratio_v;
6239
6240
int size_left = mid_w - h_sep / 2;
6241
int size_bottom = size.height - mid_h - v_sep / 2;
6242
6243
switch (view) {
6244
case VIEW_USE_1_VIEWPORT: {
6245
// Nothing to show.
6246
6247
} break;
6248
case VIEW_USE_2_VIEWPORTS: {
6249
draw_texture(v_grabber, Vector2((size.width - v_grabber->get_width()) / 2, mid_h - v_grabber->get_height() / 2));
6250
set_default_cursor_shape(CURSOR_VSPLIT);
6251
6252
} break;
6253
case VIEW_USE_2_VIEWPORTS_ALT: {
6254
draw_texture(h_grabber, Vector2(mid_w - h_grabber->get_width() / 2, (size.height - h_grabber->get_height()) / 2));
6255
set_default_cursor_shape(CURSOR_HSPLIT);
6256
6257
} break;
6258
case VIEW_USE_3_VIEWPORTS: {
6259
if ((hovering_v && hovering_h && !dragging_v && !dragging_h) || (dragging_v && dragging_h)) {
6260
draw_texture(hdiag_grabber, Vector2(mid_w - hdiag_grabber->get_width() / 2, mid_h - v_grabber->get_height() / 4));
6261
set_default_cursor_shape(CURSOR_DRAG);
6262
} else if ((hovering_v && !dragging_h) || dragging_v) {
6263
draw_texture(v_grabber, Vector2((size.width - v_grabber->get_width()) / 2, mid_h - v_grabber->get_height() / 2));
6264
set_default_cursor_shape(CURSOR_VSPLIT);
6265
} else if (hovering_h || dragging_h) {
6266
draw_texture(h_grabber, Vector2(mid_w - h_grabber->get_width() / 2, mid_h + v_grabber->get_height() / 2 + (size_bottom - h_grabber->get_height()) / 2));
6267
set_default_cursor_shape(CURSOR_HSPLIT);
6268
}
6269
6270
} break;
6271
case VIEW_USE_3_VIEWPORTS_ALT: {
6272
if ((hovering_v && hovering_h && !dragging_v && !dragging_h) || (dragging_v && dragging_h)) {
6273
draw_texture(vdiag_grabber, Vector2(mid_w - vdiag_grabber->get_width() + v_grabber->get_height() / 4, mid_h - vdiag_grabber->get_height() / 2));
6274
set_default_cursor_shape(CURSOR_DRAG);
6275
} else if ((hovering_v && !dragging_h) || dragging_v) {
6276
draw_texture(v_grabber, Vector2((size_left - v_grabber->get_width()) / 2, mid_h - v_grabber->get_height() / 2));
6277
set_default_cursor_shape(CURSOR_VSPLIT);
6278
} else if (hovering_h || dragging_h) {
6279
draw_texture(h_grabber, Vector2(mid_w - h_grabber->get_width() / 2, (size.height - h_grabber->get_height()) / 2));
6280
set_default_cursor_shape(CURSOR_HSPLIT);
6281
}
6282
6283
} break;
6284
case VIEW_USE_4_VIEWPORTS: {
6285
Vector2 half(mid_w, mid_h);
6286
if ((hovering_v && hovering_h && !dragging_v && !dragging_h) || (dragging_v && dragging_h)) {
6287
draw_texture(vh_grabber, half - vh_grabber->get_size() / 2.0);
6288
set_default_cursor_shape(CURSOR_DRAG);
6289
} else if ((hovering_v && !dragging_h) || dragging_v) {
6290
draw_texture(v_grabber, half - v_grabber->get_size() / 2.0);
6291
set_default_cursor_shape(CURSOR_VSPLIT);
6292
} else if (hovering_h || dragging_h) {
6293
draw_texture(h_grabber, half - h_grabber->get_size() / 2.0);
6294
set_default_cursor_shape(CURSOR_HSPLIT);
6295
}
6296
6297
} break;
6298
}
6299
}
6300
} break;
6301
6302
case NOTIFICATION_SORT_CHILDREN: {
6303
Node3DEditorViewport *viewports[4];
6304
int vc = 0;
6305
for (int i = 0; i < get_child_count(); i++) {
6306
viewports[vc] = Object::cast_to<Node3DEditorViewport>(get_child(i));
6307
if (viewports[vc]) {
6308
vc++;
6309
}
6310
}
6311
6312
ERR_FAIL_COND(vc != 4);
6313
6314
Size2 size = get_size();
6315
6316
if (size.x < 10 || size.y < 10) {
6317
for (int i = 0; i < 4; i++) {
6318
viewports[i]->hide();
6319
}
6320
return;
6321
}
6322
int h_sep = get_theme_constant(SNAME("separation"), SNAME("HSplitContainer"));
6323
6324
int v_sep = get_theme_constant(SNAME("separation"), SNAME("VSplitContainer"));
6325
6326
int mid_w = size.width * ratio_h;
6327
int mid_h = size.height * ratio_v;
6328
6329
int size_left = mid_w - h_sep / 2;
6330
int size_right = size.width - mid_w - h_sep / 2;
6331
6332
int size_top = mid_h - v_sep / 2;
6333
int size_bottom = size.height - mid_h - v_sep / 2;
6334
6335
switch (view) {
6336
case VIEW_USE_1_VIEWPORT: {
6337
viewports[0]->show();
6338
for (int i = 1; i < 4; i++) {
6339
viewports[i]->hide();
6340
}
6341
6342
fit_child_in_rect(viewports[0], Rect2(Vector2(), size));
6343
6344
} break;
6345
case VIEW_USE_2_VIEWPORTS: {
6346
for (int i = 0; i < 4; i++) {
6347
if (i == 1 || i == 3) {
6348
viewports[i]->hide();
6349
} else {
6350
viewports[i]->show();
6351
}
6352
}
6353
6354
fit_child_in_rect(viewports[0], Rect2(Vector2(), Vector2(size.width, size_top)));
6355
fit_child_in_rect(viewports[2], Rect2(Vector2(0, mid_h + v_sep / 2), Vector2(size.width, size_bottom)));
6356
6357
} break;
6358
case VIEW_USE_2_VIEWPORTS_ALT: {
6359
for (int i = 0; i < 4; i++) {
6360
if (i == 1 || i == 3) {
6361
viewports[i]->hide();
6362
} else {
6363
viewports[i]->show();
6364
}
6365
}
6366
fit_child_in_rect(viewports[0], Rect2(Vector2(), Vector2(size_left, size.height)));
6367
fit_child_in_rect(viewports[2], Rect2(Vector2(mid_w + h_sep / 2, 0), Vector2(size_right, size.height)));
6368
6369
} break;
6370
case VIEW_USE_3_VIEWPORTS: {
6371
for (int i = 0; i < 4; i++) {
6372
if (i == 1) {
6373
viewports[i]->hide();
6374
} else {
6375
viewports[i]->show();
6376
}
6377
}
6378
6379
fit_child_in_rect(viewports[0], Rect2(Vector2(), Vector2(size.width, size_top)));
6380
fit_child_in_rect(viewports[2], Rect2(Vector2(0, mid_h + v_sep / 2), Vector2(size_left, size_bottom)));
6381
fit_child_in_rect(viewports[3], Rect2(Vector2(mid_w + h_sep / 2, mid_h + v_sep / 2), Vector2(size_right, size_bottom)));
6382
6383
} break;
6384
case VIEW_USE_3_VIEWPORTS_ALT: {
6385
for (int i = 0; i < 4; i++) {
6386
if (i == 1) {
6387
viewports[i]->hide();
6388
} else {
6389
viewports[i]->show();
6390
}
6391
}
6392
6393
fit_child_in_rect(viewports[0], Rect2(Vector2(), Vector2(size_left, size_top)));
6394
fit_child_in_rect(viewports[2], Rect2(Vector2(0, mid_h + v_sep / 2), Vector2(size_left, size_bottom)));
6395
fit_child_in_rect(viewports[3], Rect2(Vector2(mid_w + h_sep / 2, 0), Vector2(size_right, size.height)));
6396
6397
} break;
6398
case VIEW_USE_4_VIEWPORTS: {
6399
for (int i = 0; i < 4; i++) {
6400
viewports[i]->show();
6401
}
6402
6403
fit_child_in_rect(viewports[0], Rect2(Vector2(), Vector2(size_left, size_top)));
6404
fit_child_in_rect(viewports[1], Rect2(Vector2(mid_w + h_sep / 2, 0), Vector2(size_right, size_top)));
6405
fit_child_in_rect(viewports[2], Rect2(Vector2(0, mid_h + v_sep / 2), Vector2(size_left, size_bottom)));
6406
fit_child_in_rect(viewports[3], Rect2(Vector2(mid_w + h_sep / 2, mid_h + v_sep / 2), Vector2(size_right, size_bottom)));
6407
6408
} break;
6409
}
6410
} break;
6411
}
6412
}
6413
6414
void Node3DEditorViewportContainer::set_view(View p_view) {
6415
view = p_view;
6416
queue_sort();
6417
}
6418
6419
Node3DEditorViewportContainer::View Node3DEditorViewportContainer::get_view() {
6420
return view;
6421
}
6422
6423
Node3DEditorViewportContainer::Node3DEditorViewportContainer() {
6424
set_clip_contents(true);
6425
view = VIEW_USE_1_VIEWPORT;
6426
mouseover = false;
6427
ratio_h = 0.5;
6428
ratio_v = 0.5;
6429
hovering_v = false;
6430
hovering_h = false;
6431
dragging_v = false;
6432
dragging_h = false;
6433
}
6434
6435
///////////////////////////////////////////////////////////////////
6436
6437
Node3DEditor *Node3DEditor::singleton = nullptr;
6438
6439
Node3DEditorSelectedItem::~Node3DEditorSelectedItem() {
6440
ERR_FAIL_NULL(RenderingServer::get_singleton());
6441
if (sbox_instance.is_valid()) {
6442
RenderingServer::get_singleton()->free(sbox_instance);
6443
}
6444
if (sbox_instance_offset.is_valid()) {
6445
RenderingServer::get_singleton()->free(sbox_instance_offset);
6446
}
6447
if (sbox_instance_xray.is_valid()) {
6448
RenderingServer::get_singleton()->free(sbox_instance_xray);
6449
}
6450
if (sbox_instance_xray_offset.is_valid()) {
6451
RenderingServer::get_singleton()->free(sbox_instance_xray_offset);
6452
}
6453
}
6454
6455
void Node3DEditor::select_gizmo_highlight_axis(int p_axis) {
6456
for (int i = 0; i < 3; i++) {
6457
move_gizmo[i]->surface_set_material(0, i == p_axis ? gizmo_color_hl[i] : gizmo_color[i]);
6458
move_plane_gizmo[i]->surface_set_material(0, (i + 6) == p_axis ? plane_gizmo_color_hl[i] : plane_gizmo_color[i]);
6459
rotate_gizmo[i]->surface_set_material(0, (i + 3) == p_axis ? rotate_gizmo_color_hl[i] : rotate_gizmo_color[i]);
6460
scale_gizmo[i]->surface_set_material(0, (i + 9) == p_axis ? gizmo_color_hl[i] : gizmo_color[i]);
6461
scale_plane_gizmo[i]->surface_set_material(0, (i + 12) == p_axis ? plane_gizmo_color_hl[i] : plane_gizmo_color[i]);
6462
}
6463
}
6464
6465
void Node3DEditor::update_transform_gizmo() {
6466
int count = 0;
6467
bool local_gizmo_coords = are_local_coords_enabled();
6468
6469
Vector3 gizmo_center;
6470
Basis gizmo_basis;
6471
6472
Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr;
6473
6474
if (se && se->gizmo.is_valid()) {
6475
for (const KeyValue<int, Transform3D> &E : se->subgizmos) {
6476
Transform3D xf = se->sp->get_global_transform() * se->gizmo->get_subgizmo_transform(E.key);
6477
if (!xf.is_finite()) {
6478
continue;
6479
}
6480
gizmo_center += xf.origin;
6481
if ((unsigned int)count == se->subgizmos.size() - 1 && local_gizmo_coords) {
6482
gizmo_basis = xf.basis;
6483
}
6484
count++;
6485
}
6486
} else {
6487
const List<Node *> &selection = editor_selection->get_top_selected_node_list();
6488
for (Node *E : selection) {
6489
Node3D *sp = Object::cast_to<Node3D>(E);
6490
if (!sp) {
6491
continue;
6492
}
6493
6494
if (sp->has_meta("_edit_lock_")) {
6495
continue;
6496
}
6497
6498
Node3DEditorSelectedItem *sel_item = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);
6499
if (!sel_item) {
6500
continue;
6501
}
6502
6503
Transform3D xf = sel_item->sp->get_global_transform();
6504
if (!xf.is_finite()) {
6505
continue;
6506
}
6507
gizmo_center += xf.origin;
6508
if (count == selection.size() - 1 && local_gizmo_coords) {
6509
gizmo_basis = xf.basis;
6510
}
6511
count++;
6512
}
6513
}
6514
6515
gizmo.visible = count > 0;
6516
gizmo.transform.origin = (count > 0) ? gizmo_center / count : Vector3();
6517
gizmo.transform.basis = gizmo_basis;
6518
6519
for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) {
6520
viewports[i]->update_transform_gizmo_view();
6521
}
6522
}
6523
6524
void _update_all_gizmos(Node *p_node) {
6525
for (int i = p_node->get_child_count() - 1; 0 <= i; --i) {
6526
Node3D *spatial_node = Object::cast_to<Node3D>(p_node->get_child(i));
6527
if (spatial_node) {
6528
spatial_node->update_gizmos();
6529
}
6530
6531
_update_all_gizmos(p_node->get_child(i));
6532
}
6533
}
6534
6535
void Node3DEditor::update_all_gizmos(Node *p_node) {
6536
if (!p_node && is_inside_tree()) {
6537
p_node = get_tree()->get_edited_scene_root();
6538
}
6539
6540
if (!p_node) {
6541
// No edited scene, so nothing to update.
6542
return;
6543
}
6544
_update_all_gizmos(p_node);
6545
}
6546
6547
Object *Node3DEditor::_get_editor_data(Object *p_what) {
6548
Node3D *sp = Object::cast_to<Node3D>(p_what);
6549
if (!sp) {
6550
return nullptr;
6551
}
6552
6553
Node3DEditorSelectedItem *si = memnew(Node3DEditorSelectedItem);
6554
6555
si->sp = sp;
6556
si->sbox_instance = RenderingServer::get_singleton()->instance_create2(
6557
selection_box->get_rid(),
6558
sp->get_world_3d()->get_scenario());
6559
si->sbox_instance_offset = RenderingServer::get_singleton()->instance_create2(
6560
selection_box->get_rid(),
6561
sp->get_world_3d()->get_scenario());
6562
RS::get_singleton()->instance_geometry_set_cast_shadows_setting(
6563
si->sbox_instance,
6564
RS::SHADOW_CASTING_SETTING_OFF);
6565
RS::get_singleton()->instance_geometry_set_cast_shadows_setting(
6566
si->sbox_instance_offset,
6567
RS::SHADOW_CASTING_SETTING_OFF);
6568
// Use the Edit layer to hide the selection box when View Gizmos is disabled, since it is a bit distracting.
6569
// It's still possible to approximately guess what is selected by looking at the manipulation gizmo position.
6570
RS::get_singleton()->instance_set_layer_mask(si->sbox_instance, 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER);
6571
RS::get_singleton()->instance_set_layer_mask(si->sbox_instance_offset, 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER);
6572
RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);
6573
RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);
6574
RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance_offset, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);
6575
RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance_offset, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);
6576
si->sbox_instance_xray = RenderingServer::get_singleton()->instance_create2(
6577
selection_box_xray->get_rid(),
6578
sp->get_world_3d()->get_scenario());
6579
si->sbox_instance_xray_offset = RenderingServer::get_singleton()->instance_create2(
6580
selection_box_xray->get_rid(),
6581
sp->get_world_3d()->get_scenario());
6582
RS::get_singleton()->instance_geometry_set_cast_shadows_setting(
6583
si->sbox_instance_xray,
6584
RS::SHADOW_CASTING_SETTING_OFF);
6585
RS::get_singleton()->instance_geometry_set_cast_shadows_setting(
6586
si->sbox_instance_xray_offset,
6587
RS::SHADOW_CASTING_SETTING_OFF);
6588
// Use the Edit layer to hide the selection box when View Gizmos is disabled, since it is a bit distracting.
6589
// It's still possible to approximately guess what is selected by looking at the manipulation gizmo position.
6590
RS::get_singleton()->instance_set_layer_mask(si->sbox_instance_xray, 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER);
6591
RS::get_singleton()->instance_set_layer_mask(si->sbox_instance_xray_offset, 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER);
6592
RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance_xray, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);
6593
RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance_xray, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);
6594
RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance_xray_offset, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);
6595
RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance_xray_offset, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);
6596
6597
return si;
6598
}
6599
6600
void Node3DEditor::_generate_selection_boxes() {
6601
// Use two AABBs to create the illusion of a slightly thicker line.
6602
AABB aabb(Vector3(), Vector3(1, 1, 1));
6603
6604
// Create a x-ray (visible through solid surfaces) and standard version of the selection box.
6605
// Both will be drawn at the same position, but with different opacity.
6606
// This lets the user see where the selection is while still having a sense of depth.
6607
Ref<SurfaceTool> st = memnew(SurfaceTool);
6608
Ref<SurfaceTool> st_xray = memnew(SurfaceTool);
6609
Ref<SurfaceTool> active_st = memnew(SurfaceTool);
6610
Ref<SurfaceTool> active_st_xray = memnew(SurfaceTool);
6611
6612
st->begin(Mesh::PRIMITIVE_LINES);
6613
st_xray->begin(Mesh::PRIMITIVE_LINES);
6614
active_st->begin(Mesh::PRIMITIVE_LINES);
6615
active_st_xray->begin(Mesh::PRIMITIVE_LINES);
6616
for (int i = 0; i < 12; i++) {
6617
Vector3 a, b;
6618
aabb.get_edge(i, a, b);
6619
6620
st->add_vertex(a);
6621
st->add_vertex(b);
6622
active_st->add_vertex(a);
6623
active_st->add_vertex(b);
6624
st_xray->add_vertex(a);
6625
st_xray->add_vertex(b);
6626
active_st_xray->add_vertex(a);
6627
active_st_xray->add_vertex(b);
6628
}
6629
6630
const Color selection_box_color = EDITOR_GET("editors/3d/selection_box_color");
6631
const Color active_selection_box_color = EDITOR_GET("editors/3d/active_selection_box_color");
6632
6633
selection_box_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
6634
selection_box_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
6635
selection_box_mat->set_albedo(selection_box_color);
6636
selection_box_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
6637
st->set_material(selection_box_mat);
6638
selection_box = st->commit();
6639
6640
selection_box_mat_xray->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
6641
selection_box_mat_xray->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
6642
selection_box_mat_xray->set_flag(StandardMaterial3D::FLAG_DISABLE_DEPTH_TEST, true);
6643
selection_box_mat_xray->set_albedo(selection_box_color * Color(1, 1, 1, 0.15));
6644
selection_box_mat_xray->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
6645
st_xray->set_material(selection_box_mat_xray);
6646
selection_box_xray = st_xray->commit();
6647
6648
active_selection_box_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
6649
active_selection_box_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
6650
active_selection_box_mat->set_albedo(active_selection_box_color);
6651
active_selection_box_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
6652
active_st->set_material(active_selection_box_mat);
6653
active_selection_box = active_st->commit();
6654
6655
active_selection_box_mat_xray->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
6656
active_selection_box_mat_xray->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
6657
active_selection_box_mat_xray->set_flag(StandardMaterial3D::FLAG_DISABLE_DEPTH_TEST, true);
6658
active_selection_box_mat_xray->set_albedo(active_selection_box_color * Color(1, 1, 1, 0.15));
6659
active_selection_box_mat_xray->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
6660
active_st_xray->set_material(active_selection_box_mat_xray);
6661
active_selection_box_xray = active_st_xray->commit();
6662
}
6663
6664
Dictionary Node3DEditor::get_state() const {
6665
Dictionary d;
6666
6667
d["snap_enabled"] = snap_enabled;
6668
d["translate_snap"] = snap_translate_value;
6669
d["rotate_snap"] = snap_rotate_value;
6670
d["scale_snap"] = snap_scale_value;
6671
6672
d["local_coords"] = tool_option_button[TOOL_OPT_LOCAL_COORDS]->is_pressed();
6673
6674
int vc = 0;
6675
if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT))) {
6676
vc = 1;
6677
} else if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS))) {
6678
vc = 2;
6679
} else if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS))) {
6680
vc = 3;
6681
} else if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS))) {
6682
vc = 4;
6683
} else if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT))) {
6684
vc = 5;
6685
} else if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT))) {
6686
vc = 6;
6687
}
6688
6689
d["viewport_mode"] = vc;
6690
Array vpdata;
6691
for (int i = 0; i < 4; i++) {
6692
vpdata.push_back(viewports[i]->get_state());
6693
}
6694
6695
d["viewports"] = vpdata;
6696
6697
d["show_grid"] = view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_GRID));
6698
d["show_origin"] = view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_ORIGIN));
6699
d["fov"] = get_fov();
6700
d["znear"] = get_znear();
6701
d["zfar"] = get_zfar();
6702
6703
Dictionary gizmos_status;
6704
for (int i = 0; i < gizmo_plugins_by_name.size(); i++) {
6705
if (!gizmo_plugins_by_name[i]->can_be_hidden()) {
6706
continue;
6707
}
6708
int state = gizmos_menu->get_item_state(gizmos_menu->get_item_index(i));
6709
String name = gizmo_plugins_by_name[i]->get_gizmo_name();
6710
gizmos_status[name] = state;
6711
}
6712
6713
d["gizmos_status"] = gizmos_status;
6714
{
6715
Dictionary pd;
6716
6717
pd["sun_rotation"] = sun_rotation;
6718
6719
pd["environ_sky_color"] = environ_sky_color->get_pick_color();
6720
pd["environ_ground_color"] = environ_ground_color->get_pick_color();
6721
pd["environ_energy"] = environ_energy->get_value();
6722
pd["environ_glow_enabled"] = environ_glow_button->is_pressed();
6723
pd["environ_tonemap_enabled"] = environ_tonemap_button->is_pressed();
6724
pd["environ_ao_enabled"] = environ_ao_button->is_pressed();
6725
pd["environ_gi_enabled"] = environ_gi_button->is_pressed();
6726
pd["sun_shadow_max_distance"] = sun_shadow_max_distance->get_value();
6727
6728
pd["sun_color"] = sun_color->get_pick_color();
6729
pd["sun_energy"] = sun_energy->get_value();
6730
6731
pd["sun_enabled"] = sun_button->is_pressed();
6732
pd["environ_enabled"] = environ_button->is_pressed();
6733
6734
d["preview_sun_env"] = pd;
6735
}
6736
6737
return d;
6738
}
6739
6740
void Node3DEditor::set_state(const Dictionary &p_state) {
6741
Dictionary d = p_state;
6742
6743
if (d.has("snap_enabled")) {
6744
snap_enabled = d["snap_enabled"];
6745
tool_option_button[TOOL_OPT_USE_SNAP]->set_pressed(d["snap_enabled"]);
6746
}
6747
6748
if (d.has("translate_snap")) {
6749
snap_translate_value = d["translate_snap"];
6750
}
6751
6752
if (d.has("rotate_snap")) {
6753
snap_rotate_value = d["rotate_snap"];
6754
}
6755
6756
if (d.has("scale_snap")) {
6757
snap_scale_value = d["scale_snap"];
6758
}
6759
6760
_snap_update();
6761
6762
if (d.has("local_coords")) {
6763
tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_pressed(d["local_coords"]);
6764
update_transform_gizmo();
6765
}
6766
6767
if (d.has("viewport_mode")) {
6768
int vc = d["viewport_mode"];
6769
6770
if (vc == 1) {
6771
_menu_item_pressed(MENU_VIEW_USE_1_VIEWPORT);
6772
} else if (vc == 2) {
6773
_menu_item_pressed(MENU_VIEW_USE_2_VIEWPORTS);
6774
} else if (vc == 3) {
6775
_menu_item_pressed(MENU_VIEW_USE_3_VIEWPORTS);
6776
} else if (vc == 4) {
6777
_menu_item_pressed(MENU_VIEW_USE_4_VIEWPORTS);
6778
} else if (vc == 5) {
6779
_menu_item_pressed(MENU_VIEW_USE_2_VIEWPORTS_ALT);
6780
} else if (vc == 6) {
6781
_menu_item_pressed(MENU_VIEW_USE_3_VIEWPORTS_ALT);
6782
}
6783
}
6784
6785
if (d.has("viewports")) {
6786
Array vp = d["viewports"];
6787
uint32_t vp_size = static_cast<uint32_t>(vp.size());
6788
if (vp_size > VIEWPORTS_COUNT) {
6789
WARN_PRINT("Ignoring superfluous viewport settings from spatial editor state.");
6790
vp_size = VIEWPORTS_COUNT;
6791
}
6792
6793
for (uint32_t i = 0; i < vp_size; i++) {
6794
viewports[i]->set_state(vp[i]);
6795
}
6796
}
6797
6798
if (d.has("zfar")) {
6799
settings_zfar->set_value(double(d["zfar"]));
6800
}
6801
if (d.has("znear")) {
6802
settings_znear->set_value(double(d["znear"]));
6803
}
6804
if (d.has("fov")) {
6805
settings_fov->set_value(double(d["fov"]));
6806
}
6807
if (d.has("show_grid")) {
6808
bool use = d["show_grid"];
6809
6810
if (use != view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_GRID))) {
6811
_menu_item_pressed(MENU_VIEW_GRID);
6812
}
6813
}
6814
6815
if (d.has("show_origin")) {
6816
bool use = d["show_origin"];
6817
6818
if (use != view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_ORIGIN))) {
6819
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_ORIGIN), use);
6820
RenderingServer::get_singleton()->instance_set_visible(origin_instance, use);
6821
}
6822
}
6823
6824
if (d.has("gizmos_status")) {
6825
Dictionary gizmos_status = d["gizmos_status"];
6826
6827
for (int j = 0; j < gizmo_plugins_by_name.size(); ++j) {
6828
if (!gizmo_plugins_by_name[j]->can_be_hidden()) {
6829
continue;
6830
}
6831
int state = EditorNode3DGizmoPlugin::VISIBLE;
6832
for (const KeyValue<Variant, Variant> &kv : gizmos_status) {
6833
if (gizmo_plugins_by_name.write[j]->get_gizmo_name() == String(kv.key)) {
6834
state = kv.value;
6835
break;
6836
}
6837
}
6838
6839
gizmo_plugins_by_name.write[j]->set_state(state);
6840
}
6841
_update_gizmos_menu();
6842
}
6843
6844
if (d.has("preview_sun_env")) {
6845
sun_environ_updating = true;
6846
Dictionary pd = d["preview_sun_env"];
6847
sun_rotation = pd["sun_rotation"];
6848
6849
environ_sky_color->set_pick_color(pd["environ_sky_color"]);
6850
environ_ground_color->set_pick_color(pd["environ_ground_color"]);
6851
environ_energy->set_value_no_signal(pd["environ_energy"]);
6852
environ_glow_button->set_pressed_no_signal(pd["environ_glow_enabled"]);
6853
environ_tonemap_button->set_pressed_no_signal(pd["environ_tonemap_enabled"]);
6854
environ_ao_button->set_pressed_no_signal(pd["environ_ao_enabled"]);
6855
environ_gi_button->set_pressed_no_signal(pd["environ_gi_enabled"]);
6856
sun_shadow_max_distance->set_value_no_signal(pd["sun_shadow_max_distance"]);
6857
6858
sun_color->set_pick_color(pd["sun_color"]);
6859
sun_energy->set_value_no_signal(pd["sun_energy"]);
6860
6861
sun_button->set_pressed(pd["sun_enabled"]);
6862
environ_button->set_pressed(pd["environ_enabled"]);
6863
6864
sun_environ_updating = false;
6865
6866
_preview_settings_changed();
6867
_update_preview_environment();
6868
} else {
6869
_load_default_preview_settings();
6870
sun_button->set_pressed(true);
6871
environ_button->set_pressed(true);
6872
_preview_settings_changed();
6873
_update_preview_environment();
6874
}
6875
}
6876
6877
void Node3DEditor::edit(Node3D *p_spatial) {
6878
if (p_spatial != selected) {
6879
if (selected) {
6880
Vector<Ref<Node3DGizmo>> gizmos = selected->get_gizmos();
6881
for (int i = 0; i < gizmos.size(); i++) {
6882
Ref<EditorNode3DGizmo> seg = gizmos[i];
6883
if (seg.is_null()) {
6884
continue;
6885
}
6886
seg->set_selected(false);
6887
}
6888
6889
Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected);
6890
if (se) {
6891
se->gizmo.unref();
6892
se->subgizmos.clear();
6893
}
6894
6895
selected->update_gizmos();
6896
}
6897
6898
selected = p_spatial;
6899
current_hover_gizmo = Ref<EditorNode3DGizmo>();
6900
current_hover_gizmo_handle = -1;
6901
current_hover_gizmo_handle_secondary = false;
6902
6903
if (selected) {
6904
Vector<Ref<Node3DGizmo>> gizmos = selected->get_gizmos();
6905
for (int i = 0; i < gizmos.size(); i++) {
6906
Ref<EditorNode3DGizmo> seg = gizmos[i];
6907
if (seg.is_null()) {
6908
continue;
6909
}
6910
seg->set_selected(true);
6911
}
6912
selected->update_gizmos();
6913
}
6914
}
6915
}
6916
6917
void Node3DEditor::_snap_changed() {
6918
snap_translate_value = snap_translate->get_text().to_float();
6919
snap_rotate_value = snap_rotate->get_text().to_float();
6920
snap_scale_value = snap_scale->get_text().to_float();
6921
6922
EditorSettings::get_singleton()->set_project_metadata("3d_editor", "snap_translate_value", snap_translate_value);
6923
EditorSettings::get_singleton()->set_project_metadata("3d_editor", "snap_rotate_value", snap_rotate_value);
6924
EditorSettings::get_singleton()->set_project_metadata("3d_editor", "snap_scale_value", snap_scale_value);
6925
}
6926
6927
void Node3DEditor::_snap_update() {
6928
double snap = EDITOR_GET("interface/inspector/default_float_step");
6929
int snap_step_decimals = Math::range_step_decimals(snap);
6930
6931
snap_translate->set_text(String::num(snap_translate_value, snap_step_decimals));
6932
snap_rotate->set_text(String::num(snap_rotate_value, snap_step_decimals));
6933
snap_scale->set_text(String::num(snap_scale_value, snap_step_decimals));
6934
}
6935
6936
void Node3DEditor::_xform_dialog_action() {
6937
Transform3D t;
6938
//translation
6939
Vector3 scale;
6940
Vector3 rotate;
6941
Vector3 translate;
6942
6943
for (int i = 0; i < 3; i++) {
6944
translate[i] = xform_translate[i]->get_text().to_float();
6945
rotate[i] = Math::deg_to_rad(xform_rotate[i]->get_text().to_float());
6946
scale[i] = xform_scale[i]->get_text().to_float();
6947
}
6948
6949
t.basis.scale(scale);
6950
t.basis.rotate(rotate);
6951
t.origin = translate;
6952
6953
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
6954
undo_redo->create_action(TTR("XForm Dialog"));
6955
6956
const List<Node *> &selection = editor_selection->get_top_selected_node_list();
6957
6958
for (Node *E : selection) {
6959
Node3D *sp = Object::cast_to<Node3D>(E);
6960
if (!sp) {
6961
continue;
6962
}
6963
6964
Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);
6965
if (!se) {
6966
continue;
6967
}
6968
6969
bool post = xform_type->get_selected() > 0;
6970
6971
Transform3D tr = sp->get_global_gizmo_transform();
6972
if (post) {
6973
tr = tr * t;
6974
} else {
6975
tr.basis = t.basis * tr.basis;
6976
tr.origin += t.origin;
6977
}
6978
6979
Node3D *parent = sp->get_parent_node_3d();
6980
Transform3D local_tr = parent ? parent->get_global_transform().affine_inverse() * tr : tr;
6981
undo_redo->add_do_method(sp, "set_transform", local_tr);
6982
undo_redo->add_undo_method(sp, "set_transform", sp->get_transform());
6983
}
6984
undo_redo->commit_action();
6985
}
6986
6987
void Node3DEditor::_menu_item_toggled(bool pressed, int p_option) {
6988
switch (p_option) {
6989
case MENU_TOOL_LOCAL_COORDS: {
6990
tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_pressed(pressed);
6991
update_transform_gizmo();
6992
} break;
6993
6994
case MENU_TOOL_USE_SNAP: {
6995
tool_option_button[TOOL_OPT_USE_SNAP]->set_pressed(pressed);
6996
snap_enabled = pressed;
6997
} break;
6998
}
6999
}
7000
7001
void Node3DEditor::_menu_gizmo_toggled(int p_option) {
7002
const int idx = gizmos_menu->get_item_index(p_option);
7003
gizmos_menu->toggle_item_multistate(idx);
7004
7005
// Change icon
7006
const int state = gizmos_menu->get_item_state(idx);
7007
switch (state) {
7008
case EditorNode3DGizmoPlugin::VISIBLE:
7009
gizmos_menu->set_item_icon(idx, get_editor_theme_icon(SNAME("GuiVisibilityVisible")));
7010
break;
7011
case EditorNode3DGizmoPlugin::ON_TOP:
7012
gizmos_menu->set_item_icon(idx, get_editor_theme_icon(SNAME("GuiVisibilityXray")));
7013
break;
7014
case EditorNode3DGizmoPlugin::HIDDEN:
7015
gizmos_menu->set_item_icon(idx, get_editor_theme_icon(SNAME("GuiVisibilityHidden")));
7016
break;
7017
}
7018
7019
gizmo_plugins_by_name.write[p_option]->set_state(state);
7020
7021
update_all_gizmos();
7022
}
7023
7024
void Node3DEditor::_menu_item_pressed(int p_option) {
7025
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
7026
switch (p_option) {
7027
case MENU_TOOL_SELECT:
7028
case MENU_TOOL_MOVE:
7029
case MENU_TOOL_ROTATE:
7030
case MENU_TOOL_SCALE:
7031
case MENU_TOOL_LIST_SELECT: {
7032
for (int i = 0; i < TOOL_MAX; i++) {
7033
tool_button[i]->set_pressed(i == p_option);
7034
}
7035
tool_mode = (ToolMode)p_option;
7036
update_transform_gizmo();
7037
7038
} break;
7039
case MENU_TRANSFORM_CONFIGURE_SNAP: {
7040
snap_dialog->popup_centered(Size2(200, 180));
7041
} break;
7042
case MENU_TRANSFORM_DIALOG: {
7043
for (int i = 0; i < 3; i++) {
7044
xform_translate[i]->set_text("0");
7045
xform_rotate[i]->set_text("0");
7046
xform_scale[i]->set_text("1");
7047
}
7048
7049
xform_dialog->popup_centered(Size2(320, 240) * EDSCALE);
7050
7051
} break;
7052
case MENU_VIEW_USE_1_VIEWPORT: {
7053
viewport_base->set_view(Node3DEditorViewportContainer::VIEW_USE_1_VIEWPORT);
7054
if (last_used_viewport > 0) {
7055
last_used_viewport = 0;
7056
}
7057
7058
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), true);
7059
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), false);
7060
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS), false);
7061
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS), false);
7062
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT), false);
7063
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT), false);
7064
7065
} break;
7066
case MENU_VIEW_USE_2_VIEWPORTS: {
7067
viewport_base->set_view(Node3DEditorViewportContainer::VIEW_USE_2_VIEWPORTS);
7068
if (last_used_viewport > 1) {
7069
last_used_viewport = 0;
7070
}
7071
7072
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), false);
7073
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), true);
7074
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS), false);
7075
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS), false);
7076
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT), false);
7077
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT), false);
7078
7079
} break;
7080
case MENU_VIEW_USE_2_VIEWPORTS_ALT: {
7081
viewport_base->set_view(Node3DEditorViewportContainer::VIEW_USE_2_VIEWPORTS_ALT);
7082
if (last_used_viewport > 1) {
7083
last_used_viewport = 0;
7084
}
7085
7086
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), false);
7087
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), false);
7088
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS), false);
7089
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS), false);
7090
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT), true);
7091
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT), false);
7092
7093
} break;
7094
case MENU_VIEW_USE_3_VIEWPORTS: {
7095
viewport_base->set_view(Node3DEditorViewportContainer::VIEW_USE_3_VIEWPORTS);
7096
if (last_used_viewport > 2) {
7097
last_used_viewport = 0;
7098
}
7099
7100
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), false);
7101
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), false);
7102
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS), true);
7103
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS), false);
7104
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT), false);
7105
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT), false);
7106
7107
} break;
7108
case MENU_VIEW_USE_3_VIEWPORTS_ALT: {
7109
viewport_base->set_view(Node3DEditorViewportContainer::VIEW_USE_3_VIEWPORTS_ALT);
7110
if (last_used_viewport > 2) {
7111
last_used_viewport = 0;
7112
}
7113
7114
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), false);
7115
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), false);
7116
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS), false);
7117
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS), false);
7118
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT), false);
7119
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT), true);
7120
7121
} break;
7122
case MENU_VIEW_USE_4_VIEWPORTS: {
7123
viewport_base->set_view(Node3DEditorViewportContainer::VIEW_USE_4_VIEWPORTS);
7124
7125
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), false);
7126
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), false);
7127
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS), false);
7128
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS), true);
7129
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT), false);
7130
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT), false);
7131
7132
} break;
7133
case MENU_VIEW_ORIGIN: {
7134
bool is_checked = view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(p_option));
7135
7136
origin_enabled = !is_checked;
7137
RenderingServer::get_singleton()->instance_set_visible(origin_instance, origin_enabled);
7138
// Update the grid since its appearance depends on whether the origin is enabled
7139
_finish_grid();
7140
_init_grid();
7141
7142
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(p_option), origin_enabled);
7143
} break;
7144
case MENU_VIEW_GRID: {
7145
bool is_checked = view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(p_option));
7146
7147
grid_enabled = !is_checked;
7148
7149
for (int i = 0; i < 3; ++i) {
7150
if (grid_enable[i]) {
7151
grid_visible[i] = grid_enabled;
7152
}
7153
}
7154
_finish_grid();
7155
_init_grid();
7156
7157
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(p_option), grid_enabled);
7158
7159
} break;
7160
case MENU_VIEW_CAMERA_SETTINGS: {
7161
settings_dialog->popup_centered(settings_vbc->get_combined_minimum_size() + Size2(50, 50));
7162
} break;
7163
case MENU_SNAP_TO_FLOOR: {
7164
snap_selected_nodes_to_floor();
7165
} break;
7166
case MENU_LOCK_SELECTED: {
7167
undo_redo->create_action(TTR("Lock Selected"));
7168
7169
const List<Node *> &selection = editor_selection->get_top_selected_node_list();
7170
7171
for (Node *E : selection) {
7172
Node3D *spatial = Object::cast_to<Node3D>(E);
7173
if (!spatial || !spatial->is_inside_tree()) {
7174
continue;
7175
}
7176
7177
undo_redo->add_do_method(spatial, "set_meta", "_edit_lock_", true);
7178
undo_redo->add_undo_method(spatial, "remove_meta", "_edit_lock_");
7179
undo_redo->add_do_method(this, "emit_signal", "item_lock_status_changed");
7180
undo_redo->add_undo_method(this, "emit_signal", "item_lock_status_changed");
7181
}
7182
7183
undo_redo->add_do_method(this, "_refresh_menu_icons");
7184
undo_redo->add_undo_method(this, "_refresh_menu_icons");
7185
undo_redo->commit_action();
7186
} break;
7187
case MENU_UNLOCK_SELECTED: {
7188
undo_redo->create_action(TTR("Unlock Selected"));
7189
7190
const List<Node *> &selection = editor_selection->get_top_selected_node_list();
7191
7192
for (Node *E : selection) {
7193
Node3D *spatial = Object::cast_to<Node3D>(E);
7194
if (!spatial || !spatial->is_inside_tree()) {
7195
continue;
7196
}
7197
7198
undo_redo->add_do_method(spatial, "remove_meta", "_edit_lock_");
7199
undo_redo->add_undo_method(spatial, "set_meta", "_edit_lock_", true);
7200
undo_redo->add_do_method(this, "emit_signal", "item_lock_status_changed");
7201
undo_redo->add_undo_method(this, "emit_signal", "item_lock_status_changed");
7202
}
7203
7204
undo_redo->add_do_method(this, "_refresh_menu_icons");
7205
undo_redo->add_undo_method(this, "_refresh_menu_icons");
7206
undo_redo->commit_action();
7207
} break;
7208
case MENU_GROUP_SELECTED: {
7209
undo_redo->create_action(TTR("Group Selected"));
7210
7211
const List<Node *> &selection = editor_selection->get_top_selected_node_list();
7212
7213
for (Node *E : selection) {
7214
Node3D *spatial = Object::cast_to<Node3D>(E);
7215
if (!spatial || !spatial->is_inside_tree()) {
7216
continue;
7217
}
7218
7219
undo_redo->add_do_method(spatial, "set_meta", "_edit_group_", true);
7220
undo_redo->add_undo_method(spatial, "remove_meta", "_edit_group_");
7221
undo_redo->add_do_method(this, "emit_signal", "item_group_status_changed");
7222
undo_redo->add_undo_method(this, "emit_signal", "item_group_status_changed");
7223
}
7224
7225
undo_redo->add_do_method(this, "_refresh_menu_icons");
7226
undo_redo->add_undo_method(this, "_refresh_menu_icons");
7227
undo_redo->commit_action();
7228
} break;
7229
case MENU_UNGROUP_SELECTED: {
7230
undo_redo->create_action(TTR("Ungroup Selected"));
7231
const List<Node *> &selection = editor_selection->get_top_selected_node_list();
7232
7233
for (Node *E : selection) {
7234
Node3D *spatial = Object::cast_to<Node3D>(E);
7235
if (!spatial || !spatial->is_inside_tree()) {
7236
continue;
7237
}
7238
7239
undo_redo->add_do_method(spatial, "remove_meta", "_edit_group_");
7240
undo_redo->add_undo_method(spatial, "set_meta", "_edit_group_", true);
7241
undo_redo->add_do_method(this, "emit_signal", "item_group_status_changed");
7242
undo_redo->add_undo_method(this, "emit_signal", "item_group_status_changed");
7243
}
7244
7245
undo_redo->add_do_method(this, "_refresh_menu_icons");
7246
undo_redo->add_undo_method(this, "_refresh_menu_icons");
7247
undo_redo->commit_action();
7248
} break;
7249
case MENU_RULER: {
7250
for (int i = 0; i < TOOL_MAX; i++) {
7251
tool_button[i]->set_pressed(i == p_option);
7252
}
7253
tool_button[TOOL_RULER]->set_pressed(true);
7254
tool_mode = ToolMode::TOOL_RULER;
7255
update_transform_gizmo();
7256
} break;
7257
}
7258
}
7259
7260
void Node3DEditor::_init_indicators() {
7261
{
7262
origin_enabled = true;
7263
grid_enabled = true;
7264
7265
Ref<Shader> origin_shader = memnew(Shader);
7266
origin_shader->set_code(R"(
7267
// 3D editor origin line shader.
7268
7269
shader_type spatial;
7270
render_mode blend_mix, cull_disabled, unshaded, fog_disabled;
7271
7272
void vertex() {
7273
vec3 point_a = MODEL_MATRIX[3].xyz;
7274
// Encoded in scale.
7275
vec3 point_b = vec3(MODEL_MATRIX[0].x, MODEL_MATRIX[1].y, MODEL_MATRIX[2].z);
7276
7277
// Points are already in world space, so no need for MODEL_MATRIX anymore.
7278
vec4 clip_a = PROJECTION_MATRIX * (VIEW_MATRIX * vec4(point_a, 1.0));
7279
vec4 clip_b = PROJECTION_MATRIX * (VIEW_MATRIX * vec4(point_b, 1.0));
7280
7281
vec2 screen_a = VIEWPORT_SIZE * (0.5 * clip_a.xy / clip_a.w + 0.5);
7282
vec2 screen_b = VIEWPORT_SIZE * (0.5 * clip_b.xy / clip_b.w + 0.5);
7283
7284
vec2 x_basis = normalize(screen_b - screen_a);
7285
vec2 y_basis = vec2(-x_basis.y, x_basis.x);
7286
7287
float width = 3.0;
7288
vec2 screen_point_a = screen_a + width * (VERTEX.x * x_basis + VERTEX.y * y_basis);
7289
vec2 screen_point_b = screen_b + width * (VERTEX.x * x_basis + VERTEX.y * y_basis);
7290
vec2 screen_point_final = mix(screen_point_a, screen_point_b, VERTEX.z);
7291
7292
vec4 clip_final = mix(clip_a, clip_b, VERTEX.z);
7293
7294
POSITION = vec4(clip_final.w * ((2.0 * screen_point_final) / VIEWPORT_SIZE - 1.0), clip_final.z, clip_final.w);
7295
UV = VERTEX.yz * clip_final.w;
7296
7297
if (!OUTPUT_IS_SRGB) {
7298
COLOR.rgb = mix(pow((COLOR.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)), COLOR.rgb * (1.0 / 12.92), lessThan(COLOR.rgb, vec3(0.04045)));
7299
}
7300
}
7301
7302
void fragment() {
7303
// Multiply by 0.5 since UV is actually UV is [-1, 1].
7304
float line_width = fwidth(UV.x * 0.5);
7305
float line_uv = abs(UV.x * 0.5);
7306
float line = smoothstep(line_width * 1.0, line_width * 0.25, line_uv);
7307
7308
ALBEDO = COLOR.rgb;
7309
ALPHA *= COLOR.a * line;
7310
}
7311
)");
7312
7313
origin_mat.instantiate();
7314
origin_mat->set_shader(origin_shader);
7315
7316
Vector<Vector3> origin_points;
7317
origin_points.resize(6);
7318
7319
origin_points.set(0, Vector3(0.0, -0.5, 0.0));
7320
origin_points.set(1, Vector3(0.0, -0.5, 1.0));
7321
origin_points.set(2, Vector3(0.0, 0.5, 1.0));
7322
7323
origin_points.set(3, Vector3(0.0, -0.5, 0.0));
7324
origin_points.set(4, Vector3(0.0, 0.5, 1.0));
7325
origin_points.set(5, Vector3(0.0, 0.5, 0.0));
7326
7327
Array d;
7328
d.resize(RS::ARRAY_MAX);
7329
d[RenderingServer::ARRAY_VERTEX] = origin_points;
7330
7331
origin_mesh = RenderingServer::get_singleton()->mesh_create();
7332
7333
RenderingServer::get_singleton()->mesh_add_surface_from_arrays(origin_mesh, RenderingServer::PRIMITIVE_TRIANGLES, d);
7334
RenderingServer::get_singleton()->mesh_surface_set_material(origin_mesh, 0, origin_mat->get_rid());
7335
7336
origin_multimesh = RenderingServer::get_singleton()->multimesh_create();
7337
RenderingServer::get_singleton()->multimesh_set_mesh(origin_multimesh, origin_mesh);
7338
RenderingServer::get_singleton()->multimesh_allocate_data(origin_multimesh, 12, RS::MultimeshTransformFormat::MULTIMESH_TRANSFORM_3D, true, false);
7339
RenderingServer::get_singleton()->multimesh_set_visible_instances(origin_multimesh, -1);
7340
7341
LocalVector<float> distances;
7342
distances.resize(5);
7343
distances[0] = -1000000.0;
7344
distances[1] = -1000.0;
7345
distances[2] = 0.0;
7346
distances[3] = 1000.0;
7347
distances[4] = 1000000.0;
7348
7349
for (int i = 0; i < 3; i++) {
7350
Color origin_color;
7351
switch (i) {
7352
case 0:
7353
origin_color = get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor));
7354
break;
7355
case 1:
7356
origin_color = get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor));
7357
break;
7358
case 2:
7359
origin_color = get_theme_color(SNAME("axis_z_color"), EditorStringName(Editor));
7360
break;
7361
default:
7362
origin_color = Color();
7363
break;
7364
}
7365
7366
Vector3 axis;
7367
axis[i] = 1;
7368
7369
for (int j = 0; j < 4; j++) {
7370
Transform3D t = Transform3D();
7371
if (distances[j] > 0.0) {
7372
t = t.scaled(axis * distances[j + 1]);
7373
t = t.translated(axis * distances[j]);
7374
} else {
7375
t = t.scaled(axis * distances[j]);
7376
t = t.translated(axis * distances[j + 1]);
7377
}
7378
RenderingServer::get_singleton()->multimesh_instance_set_transform(origin_multimesh, i * 4 + j, t);
7379
RenderingServer::get_singleton()->multimesh_instance_set_color(origin_multimesh, i * 4 + j, origin_color);
7380
}
7381
}
7382
7383
origin_instance = RenderingServer::get_singleton()->instance_create2(origin_multimesh, get_tree()->get_root()->get_world_3d()->get_scenario());
7384
RS::get_singleton()->instance_set_layer_mask(origin_instance, 1 << Node3DEditorViewport::GIZMO_GRID_LAYER);
7385
RS::get_singleton()->instance_geometry_set_flag(origin_instance, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);
7386
RS::get_singleton()->instance_geometry_set_flag(origin_instance, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);
7387
7388
RenderingServer::get_singleton()->instance_geometry_set_cast_shadows_setting(origin_instance, RS::SHADOW_CASTING_SETTING_OFF);
7389
7390
Ref<Shader> grid_shader = memnew(Shader);
7391
grid_shader->set_code(R"(
7392
// 3D editor grid shader.
7393
7394
shader_type spatial;
7395
7396
render_mode unshaded, fog_disabled;
7397
7398
uniform bool orthogonal;
7399
uniform float grid_size;
7400
7401
void vertex() {
7402
// From FLAG_SRGB_VERTEX_COLOR.
7403
if (!OUTPUT_IS_SRGB) {
7404
COLOR.rgb = mix(pow((COLOR.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)), COLOR.rgb * (1.0 / 12.92), lessThan(COLOR.rgb, vec3(0.04045)));
7405
}
7406
}
7407
7408
void fragment() {
7409
ALBEDO = COLOR.rgb;
7410
vec3 dir = orthogonal ? -vec3(0, 0, 1) : VIEW;
7411
float angle_fade = abs(dot(dir, NORMAL));
7412
angle_fade = smoothstep(0.05, 0.2, angle_fade);
7413
7414
vec3 world_pos = (INV_VIEW_MATRIX * vec4(VERTEX, 1.0)).xyz;
7415
vec3 world_normal = (INV_VIEW_MATRIX * vec4(NORMAL, 0.0)).xyz;
7416
vec3 camera_world_pos = INV_VIEW_MATRIX[3].xyz;
7417
vec3 camera_world_pos_on_plane = camera_world_pos * (1.0 - world_normal);
7418
float dist_fade = 1.0 - (distance(world_pos, camera_world_pos_on_plane) / grid_size);
7419
dist_fade = smoothstep(0.02, 0.3, dist_fade);
7420
7421
ALPHA = COLOR.a * dist_fade * angle_fade;
7422
}
7423
)");
7424
7425
for (int i = 0; i < 3; i++) {
7426
grid_mat[i].instantiate();
7427
grid_mat[i]->set_shader(grid_shader);
7428
}
7429
7430
grid_enable[0] = EDITOR_GET("editors/3d/grid_xy_plane");
7431
grid_enable[1] = EDITOR_GET("editors/3d/grid_yz_plane");
7432
grid_enable[2] = EDITOR_GET("editors/3d/grid_xz_plane");
7433
grid_visible[0] = grid_enable[0];
7434
grid_visible[1] = grid_enable[1];
7435
grid_visible[2] = grid_enable[2];
7436
7437
_init_grid();
7438
}
7439
7440
{
7441
//move gizmo
7442
7443
// Inverted zxy.
7444
Vector3 ivec = Vector3(0, 0, -1);
7445
Vector3 nivec = Vector3(-1, -1, 0);
7446
Vector3 ivec2 = Vector3(-1, 0, 0);
7447
Vector3 ivec3 = Vector3(0, -1, 0);
7448
7449
for (int i = 0; i < 3; i++) {
7450
Color col;
7451
switch (i) {
7452
case 0:
7453
col = get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor));
7454
break;
7455
case 1:
7456
col = get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor));
7457
break;
7458
case 2:
7459
col = get_theme_color(SNAME("axis_z_color"), EditorStringName(Editor));
7460
break;
7461
default:
7462
col = Color();
7463
break;
7464
}
7465
7466
col.a = EDITOR_GET("editors/3d/manipulator_gizmo_opacity");
7467
7468
move_gizmo[i].instantiate();
7469
move_plane_gizmo[i].instantiate();
7470
rotate_gizmo[i].instantiate();
7471
scale_gizmo[i].instantiate();
7472
scale_plane_gizmo[i].instantiate();
7473
axis_gizmo[i].instantiate();
7474
7475
Ref<StandardMaterial3D> mat = memnew(StandardMaterial3D);
7476
mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
7477
mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
7478
mat->set_on_top_of_alpha();
7479
mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
7480
mat->set_albedo(col);
7481
gizmo_color[i] = mat;
7482
7483
Ref<StandardMaterial3D> mat_hl = mat->duplicate();
7484
const Color albedo = col.from_hsv(col.get_h(), 0.25, 1.0, 1);
7485
mat_hl->set_albedo(albedo);
7486
gizmo_color_hl[i] = mat_hl;
7487
7488
//translate
7489
{
7490
Ref<SurfaceTool> surftool = memnew(SurfaceTool);
7491
surftool->begin(Mesh::PRIMITIVE_TRIANGLES);
7492
7493
// Arrow profile
7494
const int arrow_points = 5;
7495
Vector3 arrow[5] = {
7496
nivec * 0.0 + ivec * 0.0,
7497
nivec * 0.01 + ivec * 0.0,
7498
nivec * 0.01 + ivec * GIZMO_ARROW_OFFSET,
7499
nivec * 0.065 + ivec * GIZMO_ARROW_OFFSET,
7500
nivec * 0.0 + ivec * (GIZMO_ARROW_OFFSET + GIZMO_ARROW_SIZE),
7501
};
7502
7503
int arrow_sides = 16;
7504
7505
const real_t arrow_sides_step = Math::TAU / arrow_sides;
7506
for (int k = 0; k < arrow_sides; k++) {
7507
Basis ma(ivec, k * arrow_sides_step);
7508
Basis mb(ivec, (k + 1) * arrow_sides_step);
7509
7510
for (int j = 0; j < arrow_points - 1; j++) {
7511
Vector3 points[4] = {
7512
ma.xform(arrow[j]),
7513
mb.xform(arrow[j]),
7514
mb.xform(arrow[j + 1]),
7515
ma.xform(arrow[j + 1]),
7516
};
7517
surftool->add_vertex(points[0]);
7518
surftool->add_vertex(points[1]);
7519
surftool->add_vertex(points[2]);
7520
7521
surftool->add_vertex(points[0]);
7522
surftool->add_vertex(points[2]);
7523
surftool->add_vertex(points[3]);
7524
}
7525
}
7526
7527
surftool->set_material(mat);
7528
surftool->commit(move_gizmo[i]);
7529
}
7530
7531
// Plane Translation
7532
{
7533
Ref<SurfaceTool> surftool = memnew(SurfaceTool);
7534
surftool->begin(Mesh::PRIMITIVE_TRIANGLES);
7535
7536
Vector3 vec = ivec2 - ivec3;
7537
Vector3 plane[4] = {
7538
vec * GIZMO_PLANE_DST,
7539
vec * GIZMO_PLANE_DST + ivec2 * GIZMO_PLANE_SIZE,
7540
vec * (GIZMO_PLANE_DST + GIZMO_PLANE_SIZE),
7541
vec * GIZMO_PLANE_DST - ivec3 * GIZMO_PLANE_SIZE
7542
};
7543
7544
Basis ma(ivec, Math::PI / 2);
7545
7546
Vector3 points[4] = {
7547
ma.xform(plane[0]),
7548
ma.xform(plane[1]),
7549
ma.xform(plane[2]),
7550
ma.xform(plane[3]),
7551
};
7552
surftool->add_vertex(points[0]);
7553
surftool->add_vertex(points[1]);
7554
surftool->add_vertex(points[2]);
7555
7556
surftool->add_vertex(points[0]);
7557
surftool->add_vertex(points[2]);
7558
surftool->add_vertex(points[3]);
7559
7560
Ref<StandardMaterial3D> plane_mat = memnew(StandardMaterial3D);
7561
plane_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
7562
plane_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
7563
plane_mat->set_on_top_of_alpha();
7564
plane_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
7565
plane_mat->set_cull_mode(StandardMaterial3D::CULL_DISABLED);
7566
plane_mat->set_albedo(col);
7567
plane_gizmo_color[i] = plane_mat; // needed, so we can draw planes from both sides
7568
surftool->set_material(plane_mat);
7569
surftool->commit(move_plane_gizmo[i]);
7570
7571
Ref<StandardMaterial3D> plane_mat_hl = plane_mat->duplicate();
7572
plane_mat_hl->set_albedo(albedo);
7573
plane_gizmo_color_hl[i] = plane_mat_hl; // needed, so we can draw planes from both sides
7574
}
7575
7576
// Rotate
7577
{
7578
Ref<SurfaceTool> surftool = memnew(SurfaceTool);
7579
surftool->begin(Mesh::PRIMITIVE_TRIANGLES);
7580
7581
int n = 128; // number of circle segments
7582
int m = 3; // number of thickness segments
7583
7584
real_t step = Math::TAU / n;
7585
for (int j = 0; j < n; ++j) {
7586
Basis basis = Basis(ivec, j * step);
7587
7588
Vector3 vertex = basis.xform(ivec2 * GIZMO_CIRCLE_SIZE);
7589
7590
for (int k = 0; k < m; ++k) {
7591
Vector2 ofs = Vector2(Math::cos((Math::TAU * k) / m), Math::sin((Math::TAU * k) / m));
7592
Vector3 normal = ivec * ofs.x + ivec2 * ofs.y;
7593
7594
surftool->set_normal(basis.xform(normal));
7595
surftool->add_vertex(vertex);
7596
}
7597
}
7598
7599
for (int j = 0; j < n; ++j) {
7600
for (int k = 0; k < m; ++k) {
7601
int current_ring = j * m;
7602
int next_ring = ((j + 1) % n) * m;
7603
int current_segment = k;
7604
int next_segment = (k + 1) % m;
7605
7606
surftool->add_index(current_ring + next_segment);
7607
surftool->add_index(current_ring + current_segment);
7608
surftool->add_index(next_ring + current_segment);
7609
7610
surftool->add_index(next_ring + current_segment);
7611
surftool->add_index(next_ring + next_segment);
7612
surftool->add_index(current_ring + next_segment);
7613
}
7614
}
7615
7616
Ref<Shader> rotate_shader = memnew(Shader);
7617
7618
rotate_shader->set_code(R"(
7619
// 3D editor rotation manipulator gizmo shader.
7620
7621
shader_type spatial;
7622
7623
render_mode unshaded, depth_test_disabled, fog_disabled;
7624
7625
uniform vec4 albedo;
7626
7627
mat3 orthonormalize(mat3 m) {
7628
vec3 x = normalize(m[0]);
7629
vec3 y = normalize(m[1] - x * dot(x, m[1]));
7630
vec3 z = m[2] - x * dot(x, m[2]);
7631
z = normalize(z - y * (dot(y, m[2])));
7632
return mat3(x,y,z);
7633
}
7634
7635
void vertex() {
7636
mat3 mv = orthonormalize(mat3(MODELVIEW_MATRIX));
7637
vec3 n = mv * VERTEX;
7638
float orientation = dot(vec3(0.0, 0.0, -1.0), n);
7639
if (orientation <= 0.005) {
7640
VERTEX += NORMAL * 0.02;
7641
}
7642
}
7643
7644
void fragment() {
7645
ALBEDO = albedo.rgb;
7646
ALPHA = albedo.a;
7647
}
7648
)");
7649
7650
Ref<ShaderMaterial> rotate_mat = memnew(ShaderMaterial);
7651
rotate_mat->set_render_priority(Material::RENDER_PRIORITY_MAX);
7652
rotate_mat->set_shader(rotate_shader);
7653
rotate_mat->set_shader_parameter("albedo", col);
7654
rotate_gizmo_color[i] = rotate_mat;
7655
7656
Array arrays = surftool->commit_to_arrays();
7657
rotate_gizmo[i]->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, arrays);
7658
rotate_gizmo[i]->surface_set_material(0, rotate_mat);
7659
7660
Ref<ShaderMaterial> rotate_mat_hl = rotate_mat->duplicate();
7661
rotate_mat_hl->set_shader_parameter("albedo", albedo);
7662
rotate_gizmo_color_hl[i] = rotate_mat_hl;
7663
7664
if (i == 2) { // Rotation white outline
7665
Ref<ShaderMaterial> border_mat = rotate_mat->duplicate();
7666
7667
Ref<Shader> border_shader = memnew(Shader);
7668
border_shader->set_code(R"(
7669
// 3D editor rotation manipulator gizmo shader (white outline).
7670
7671
shader_type spatial;
7672
7673
render_mode unshaded, depth_test_disabled, fog_disabled;
7674
7675
uniform vec4 albedo;
7676
7677
mat3 orthonormalize(mat3 m) {
7678
vec3 x = normalize(m[0]);
7679
vec3 y = normalize(m[1] - x * dot(x, m[1]));
7680
vec3 z = m[2] - x * dot(x, m[2]);
7681
z = normalize(z - y * (dot(y, m[2])));
7682
return mat3(x, y, z);
7683
}
7684
7685
void vertex() {
7686
mat3 mv = orthonormalize(mat3(MODELVIEW_MATRIX));
7687
mv = inverse(mv);
7688
VERTEX += NORMAL * 0.008;
7689
vec3 camera_dir_local = mv * vec3(0.0, 0.0, 1.0);
7690
vec3 camera_up_local = mv * vec3(0.0, 1.0, 0.0);
7691
mat3 rotation_matrix = mat3(cross(camera_dir_local, camera_up_local), camera_up_local, camera_dir_local);
7692
VERTEX = rotation_matrix * VERTEX;
7693
}
7694
7695
void fragment() {
7696
ALBEDO = albedo.rgb;
7697
ALPHA = albedo.a;
7698
}
7699
)");
7700
7701
border_mat->set_shader(border_shader);
7702
border_mat->set_shader_parameter("albedo", Color(0.75, 0.75, 0.75, col.a / 3.0));
7703
7704
rotate_gizmo[3].instantiate();
7705
rotate_gizmo[3]->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, arrays);
7706
rotate_gizmo[3]->surface_set_material(0, border_mat);
7707
}
7708
}
7709
7710
// Scale
7711
{
7712
Ref<SurfaceTool> surftool = memnew(SurfaceTool);
7713
surftool->begin(Mesh::PRIMITIVE_TRIANGLES);
7714
7715
// Cube arrow profile
7716
const int arrow_points = 6;
7717
Vector3 arrow[6] = {
7718
nivec * 0.0 + ivec * 0.0,
7719
nivec * 0.01 + ivec * 0.0,
7720
nivec * 0.01 + ivec * 1.0 * GIZMO_SCALE_OFFSET,
7721
nivec * 0.07 + ivec * 1.0 * GIZMO_SCALE_OFFSET,
7722
nivec * 0.07 + ivec * 1.11 * GIZMO_SCALE_OFFSET,
7723
nivec * 0.0 + ivec * 1.11 * GIZMO_SCALE_OFFSET,
7724
};
7725
7726
int arrow_sides = 4;
7727
7728
const real_t arrow_sides_step = Math::TAU / arrow_sides;
7729
for (int k = 0; k < 4; k++) {
7730
Basis ma(ivec, k * arrow_sides_step);
7731
Basis mb(ivec, (k + 1) * arrow_sides_step);
7732
7733
for (int j = 0; j < arrow_points - 1; j++) {
7734
Vector3 points[4] = {
7735
ma.xform(arrow[j]),
7736
mb.xform(arrow[j]),
7737
mb.xform(arrow[j + 1]),
7738
ma.xform(arrow[j + 1]),
7739
};
7740
surftool->add_vertex(points[0]);
7741
surftool->add_vertex(points[1]);
7742
surftool->add_vertex(points[2]);
7743
7744
surftool->add_vertex(points[0]);
7745
surftool->add_vertex(points[2]);
7746
surftool->add_vertex(points[3]);
7747
}
7748
}
7749
7750
surftool->set_material(mat);
7751
surftool->commit(scale_gizmo[i]);
7752
}
7753
7754
// Plane Scale
7755
{
7756
Ref<SurfaceTool> surftool = memnew(SurfaceTool);
7757
surftool->begin(Mesh::PRIMITIVE_TRIANGLES);
7758
7759
Vector3 vec = ivec2 - ivec3;
7760
Vector3 plane[4] = {
7761
vec * GIZMO_PLANE_DST,
7762
vec * GIZMO_PLANE_DST + ivec2 * GIZMO_PLANE_SIZE,
7763
vec * (GIZMO_PLANE_DST + GIZMO_PLANE_SIZE),
7764
vec * GIZMO_PLANE_DST - ivec3 * GIZMO_PLANE_SIZE
7765
};
7766
7767
Basis ma(ivec, Math::PI / 2);
7768
7769
Vector3 points[4] = {
7770
ma.xform(plane[0]),
7771
ma.xform(plane[1]),
7772
ma.xform(plane[2]),
7773
ma.xform(plane[3]),
7774
};
7775
surftool->add_vertex(points[0]);
7776
surftool->add_vertex(points[1]);
7777
surftool->add_vertex(points[2]);
7778
7779
surftool->add_vertex(points[0]);
7780
surftool->add_vertex(points[2]);
7781
surftool->add_vertex(points[3]);
7782
7783
Ref<StandardMaterial3D> plane_mat = memnew(StandardMaterial3D);
7784
plane_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
7785
plane_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
7786
plane_mat->set_on_top_of_alpha();
7787
plane_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
7788
plane_mat->set_cull_mode(StandardMaterial3D::CULL_DISABLED);
7789
plane_mat->set_albedo(col);
7790
plane_gizmo_color[i] = plane_mat; // needed, so we can draw planes from both sides
7791
surftool->set_material(plane_mat);
7792
surftool->commit(scale_plane_gizmo[i]);
7793
7794
Ref<StandardMaterial3D> plane_mat_hl = plane_mat->duplicate();
7795
plane_mat_hl->set_albedo(col.from_hsv(col.get_h(), 0.25, 1.0, 1));
7796
plane_gizmo_color_hl[i] = plane_mat_hl; // needed, so we can draw planes from both sides
7797
}
7798
7799
// Lines to visualize transforms locked to an axis/plane
7800
{
7801
Ref<SurfaceTool> surftool = memnew(SurfaceTool);
7802
surftool->begin(Mesh::PRIMITIVE_LINE_STRIP);
7803
7804
Vector3 vec;
7805
vec[i] = 1;
7806
7807
// line extending through infinity(ish)
7808
surftool->add_vertex(vec * -1048576);
7809
surftool->add_vertex(Vector3());
7810
surftool->add_vertex(vec * 1048576);
7811
surftool->set_material(mat_hl);
7812
surftool->commit(axis_gizmo[i]);
7813
}
7814
}
7815
}
7816
7817
_generate_selection_boxes();
7818
}
7819
7820
void Node3DEditor::_update_gizmos_menu() {
7821
gizmos_menu->clear();
7822
7823
for (int i = 0; i < gizmo_plugins_by_name.size(); ++i) {
7824
if (!gizmo_plugins_by_name[i]->can_be_hidden()) {
7825
continue;
7826
}
7827
String plugin_name = gizmo_plugins_by_name[i]->get_gizmo_name();
7828
const int plugin_state = gizmo_plugins_by_name[i]->get_state();
7829
gizmos_menu->add_multistate_item(plugin_name, 3, plugin_state, i);
7830
const int idx = gizmos_menu->get_item_index(i);
7831
gizmos_menu->set_item_tooltip(
7832
idx,
7833
TTR("Click to toggle between visibility states.\n\nOpen eye: Gizmo is visible.\nClosed eye: Gizmo is hidden.\nHalf-open eye: Gizmo is also visible through opaque surfaces (\"x-ray\")."));
7834
switch (plugin_state) {
7835
case EditorNode3DGizmoPlugin::VISIBLE:
7836
gizmos_menu->set_item_icon(idx, get_editor_theme_icon(SNAME("GuiVisibilityVisible")));
7837
break;
7838
case EditorNode3DGizmoPlugin::ON_TOP:
7839
gizmos_menu->set_item_icon(idx, get_editor_theme_icon(SNAME("GuiVisibilityXray")));
7840
break;
7841
case EditorNode3DGizmoPlugin::HIDDEN:
7842
gizmos_menu->set_item_icon(idx, get_editor_theme_icon(SNAME("GuiVisibilityHidden")));
7843
break;
7844
}
7845
}
7846
}
7847
7848
void Node3DEditor::_update_gizmos_menu_theme() {
7849
for (int i = 0; i < gizmo_plugins_by_name.size(); ++i) {
7850
if (!gizmo_plugins_by_name[i]->can_be_hidden()) {
7851
continue;
7852
}
7853
const int plugin_state = gizmo_plugins_by_name[i]->get_state();
7854
const int idx = gizmos_menu->get_item_index(i);
7855
switch (plugin_state) {
7856
case EditorNode3DGizmoPlugin::VISIBLE:
7857
gizmos_menu->set_item_icon(idx, get_editor_theme_icon(SNAME("GuiVisibilityVisible")));
7858
break;
7859
case EditorNode3DGizmoPlugin::ON_TOP:
7860
gizmos_menu->set_item_icon(idx, get_editor_theme_icon(SNAME("GuiVisibilityXray")));
7861
break;
7862
case EditorNode3DGizmoPlugin::HIDDEN:
7863
gizmos_menu->set_item_icon(idx, get_editor_theme_icon(SNAME("GuiVisibilityHidden")));
7864
break;
7865
}
7866
}
7867
}
7868
7869
void Node3DEditor::_init_grid() {
7870
if (!grid_enabled) {
7871
return;
7872
}
7873
Camera3D *camera = get_editor_viewport(0)->camera;
7874
Vector3 camera_position = camera->get_position();
7875
if (camera_position == Vector3()) {
7876
return; // Camera3D is invalid, don't draw the grid.
7877
}
7878
7879
bool orthogonal = camera->get_projection() == Camera3D::PROJECTION_ORTHOGONAL;
7880
7881
static LocalVector<Color> grid_colors[3];
7882
static LocalVector<Vector3> grid_points[3];
7883
static LocalVector<Vector3> grid_normals[3];
7884
7885
for (uint32_t n = 0; n < 3; n++) {
7886
grid_colors[n].clear();
7887
grid_points[n].clear();
7888
grid_normals[n].clear();
7889
}
7890
7891
Color primary_grid_color = EDITOR_GET("editors/3d/primary_grid_color");
7892
Color secondary_grid_color = EDITOR_GET("editors/3d/secondary_grid_color");
7893
int grid_size = EDITOR_GET("editors/3d/grid_size");
7894
int primary_grid_steps = EDITOR_GET("editors/3d/primary_grid_steps");
7895
7896
// Which grid planes are enabled? Which should we generate?
7897
grid_enable[0] = grid_visible[0] = orthogonal || EDITOR_GET("editors/3d/grid_xy_plane");
7898
grid_enable[1] = grid_visible[1] = orthogonal || EDITOR_GET("editors/3d/grid_yz_plane");
7899
grid_enable[2] = grid_visible[2] = orthogonal || EDITOR_GET("editors/3d/grid_xz_plane");
7900
7901
// Offsets division_level for bigger or smaller grids.
7902
// Default value is -0.2. -1.0 gives Blender-like behavior, 0.5 gives huge grids.
7903
real_t division_level_bias = EDITOR_GET("editors/3d/grid_division_level_bias");
7904
// Default largest grid size is 8^2 (default value is 2) when primary_grid_steps is 8 (64m apart, so primary grid lines are 512m apart).
7905
int division_level_max = EDITOR_GET("editors/3d/grid_division_level_max");
7906
// Default smallest grid size is 8^0 (default value is 0) when primary_grid_steps is 8.
7907
int division_level_min = EDITOR_GET("editors/3d/grid_division_level_min");
7908
ERR_FAIL_COND_MSG(division_level_max < division_level_min, "The 3D grid's maximum division level cannot be lower than its minimum division level.");
7909
7910
if (primary_grid_steps != 10) { // Log10 of 10 is 1.
7911
// Change of base rule, divide by ln(10).
7912
real_t div = Math::log((real_t)primary_grid_steps) / (real_t)2.302585092994045901094;
7913
// Truncation (towards zero) is intentional.
7914
division_level_max = (int)(division_level_max / div);
7915
division_level_min = (int)(division_level_min / div);
7916
}
7917
7918
for (int a = 0; a < 3; a++) {
7919
if (!grid_enable[a]) {
7920
continue; // If this grid plane is disabled, skip generation.
7921
}
7922
int b = (a + 1) % 3;
7923
int c = (a + 2) % 3;
7924
7925
Vector3 normal;
7926
normal[c] = 1.0;
7927
7928
real_t camera_distance = Math::abs(camera_position[c]);
7929
7930
if (orthogonal) {
7931
camera_distance = camera->get_size() / 2.0;
7932
Vector3 camera_direction = -camera->get_global_transform().get_basis().get_column(2);
7933
Plane grid_plane = Plane(normal);
7934
Vector3 intersection;
7935
if (grid_plane.intersects_ray(camera_position, camera_direction, &intersection)) {
7936
camera_position = intersection;
7937
}
7938
}
7939
7940
real_t division_level = Math::log(Math::abs(camera_distance)) / Math::log((double)primary_grid_steps) + division_level_bias;
7941
7942
real_t clamped_division_level = CLAMP(division_level, division_level_min, division_level_max);
7943
real_t division_level_floored = Math::floor(clamped_division_level);
7944
real_t division_level_decimals = clamped_division_level - division_level_floored;
7945
7946
real_t small_step_size = Math::pow(primary_grid_steps, division_level_floored);
7947
real_t large_step_size = small_step_size * primary_grid_steps;
7948
real_t center_a = large_step_size * (int)(camera_position[a] / large_step_size);
7949
real_t center_b = large_step_size * (int)(camera_position[b] / large_step_size);
7950
7951
real_t bgn_a = center_a - grid_size * small_step_size;
7952
real_t end_a = center_a + grid_size * small_step_size;
7953
real_t bgn_b = center_b - grid_size * small_step_size;
7954
real_t end_b = center_b + grid_size * small_step_size;
7955
7956
real_t fade_size = Math::pow(primary_grid_steps, division_level - 1.0);
7957
real_t min_fade_size = Math::pow(primary_grid_steps, float(division_level_min));
7958
real_t max_fade_size = Math::pow(primary_grid_steps, float(division_level_max));
7959
fade_size = CLAMP(fade_size, min_fade_size, max_fade_size);
7960
7961
real_t grid_fade_size = (grid_size - primary_grid_steps) * fade_size;
7962
grid_mat[c]->set_shader_parameter("grid_size", grid_fade_size);
7963
grid_mat[c]->set_shader_parameter("orthogonal", orthogonal);
7964
7965
LocalVector<Vector3> &ref_grid = grid_points[c];
7966
LocalVector<Vector3> &ref_grid_normals = grid_normals[c];
7967
LocalVector<Color> &ref_grid_colors = grid_colors[c];
7968
7969
// Count our elements same as code below it.
7970
int expected_size = 0;
7971
for (int i = -grid_size; i <= grid_size; i++) {
7972
const real_t position_a = center_a + i * small_step_size;
7973
const real_t position_b = center_b + i * small_step_size;
7974
7975
// Don't draw lines over the origin if it's enabled.
7976
if (!(origin_enabled && Math::is_zero_approx(position_a))) {
7977
expected_size += 2;
7978
}
7979
7980
if (!(origin_enabled && Math::is_zero_approx(position_b))) {
7981
expected_size += 2;
7982
}
7983
}
7984
7985
int idx = 0;
7986
ref_grid.resize(expected_size);
7987
ref_grid_normals.resize(expected_size);
7988
ref_grid_colors.resize(expected_size);
7989
7990
// In each iteration of this loop, draw one line in each direction (so two lines per loop, in each if statement).
7991
for (int i = -grid_size; i <= grid_size; i++) {
7992
Color line_color;
7993
// Is this a primary line? Set the appropriate color.
7994
if (i % primary_grid_steps == 0) {
7995
line_color = primary_grid_color.lerp(secondary_grid_color, division_level_decimals);
7996
} else {
7997
line_color = secondary_grid_color;
7998
line_color.a = line_color.a * (1 - division_level_decimals);
7999
}
8000
8001
real_t position_a = center_a + i * small_step_size;
8002
real_t position_b = center_b + i * small_step_size;
8003
8004
// Don't draw lines over the origin if it's enabled.
8005
if (!(origin_enabled && Math::is_zero_approx(position_a))) {
8006
Vector3 line_bgn;
8007
Vector3 line_end;
8008
line_bgn[a] = position_a;
8009
line_end[a] = position_a;
8010
line_bgn[b] = bgn_b;
8011
line_end[b] = end_b;
8012
ref_grid[idx] = line_bgn;
8013
ref_grid[idx + 1] = line_end;
8014
ref_grid_colors[idx] = line_color;
8015
ref_grid_colors[idx + 1] = line_color;
8016
ref_grid_normals[idx] = normal;
8017
ref_grid_normals[idx + 1] = normal;
8018
idx += 2;
8019
}
8020
8021
if (!(origin_enabled && Math::is_zero_approx(position_b))) {
8022
Vector3 line_bgn;
8023
Vector3 line_end;
8024
line_bgn[b] = position_b;
8025
line_end[b] = position_b;
8026
line_bgn[a] = bgn_a;
8027
line_end[a] = end_a;
8028
ref_grid[idx] = line_bgn;
8029
ref_grid[idx + 1] = line_end;
8030
ref_grid_colors[idx] = line_color;
8031
ref_grid_colors[idx + 1] = line_color;
8032
ref_grid_normals[idx] = normal;
8033
ref_grid_normals[idx + 1] = normal;
8034
idx += 2;
8035
}
8036
}
8037
8038
// Create a mesh from the pushed vector points and colors.
8039
grid[c] = RenderingServer::get_singleton()->mesh_create();
8040
Array d;
8041
d.resize(RS::ARRAY_MAX);
8042
d[RenderingServer::ARRAY_VERTEX] = (Vector<Vector3>)grid_points[c];
8043
d[RenderingServer::ARRAY_COLOR] = (Vector<Color>)grid_colors[c];
8044
d[RenderingServer::ARRAY_NORMAL] = (Vector<Vector3>)grid_normals[c];
8045
RenderingServer::get_singleton()->mesh_add_surface_from_arrays(grid[c], RenderingServer::PRIMITIVE_LINES, d);
8046
RenderingServer::get_singleton()->mesh_surface_set_material(grid[c], 0, grid_mat[c]->get_rid());
8047
grid_instance[c] = RenderingServer::get_singleton()->instance_create2(grid[c], get_tree()->get_root()->get_world_3d()->get_scenario());
8048
8049
// Yes, the end of this line is supposed to be a.
8050
RenderingServer::get_singleton()->instance_set_visible(grid_instance[c], grid_visible[a]);
8051
RenderingServer::get_singleton()->instance_geometry_set_cast_shadows_setting(grid_instance[c], RS::SHADOW_CASTING_SETTING_OFF);
8052
RS::get_singleton()->instance_set_layer_mask(grid_instance[c], 1 << Node3DEditorViewport::GIZMO_GRID_LAYER);
8053
RS::get_singleton()->instance_geometry_set_flag(grid_instance[c], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);
8054
RS::get_singleton()->instance_geometry_set_flag(grid_instance[c], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false);
8055
}
8056
}
8057
8058
void Node3DEditor::_finish_indicators() {
8059
RenderingServer::get_singleton()->free(origin_instance);
8060
RenderingServer::get_singleton()->free(origin_multimesh);
8061
RenderingServer::get_singleton()->free(origin_mesh);
8062
8063
_finish_grid();
8064
}
8065
8066
void Node3DEditor::_finish_grid() {
8067
for (int i = 0; i < 3; i++) {
8068
RenderingServer::get_singleton()->free(grid_instance[i]);
8069
RenderingServer::get_singleton()->free(grid[i]);
8070
}
8071
}
8072
8073
void Node3DEditor::update_grid() {
8074
const Camera3D::ProjectionType current_projection = viewports[0]->camera->get_projection();
8075
8076
if (current_projection != grid_camera_last_update_perspective) {
8077
grid_init_draw = false; // redraw
8078
grid_camera_last_update_perspective = current_projection;
8079
}
8080
8081
// Gets a orthogonal or perspective position correctly (for the grid comparison)
8082
const Vector3 camera_position = get_editor_viewport(0)->camera->get_position();
8083
8084
if (!grid_init_draw || grid_camera_last_update_position.distance_squared_to(camera_position) >= 100.0f) {
8085
_finish_grid();
8086
_init_grid();
8087
grid_init_draw = true;
8088
grid_camera_last_update_position = camera_position;
8089
}
8090
}
8091
8092
void Node3DEditor::_selection_changed() {
8093
_refresh_menu_icons();
8094
8095
const HashMap<Node *, Object *> &selection = editor_selection->get_selection();
8096
8097
for (const KeyValue<Node *, Object *> &E : selection) {
8098
Node3D *sp = Object::cast_to<Node3D>(E.key);
8099
if (!sp) {
8100
continue;
8101
}
8102
8103
Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);
8104
if (!se) {
8105
continue;
8106
}
8107
8108
if (sp == editor_selection->get_top_selected_node_list().back()->get()) {
8109
RenderingServer::get_singleton()->instance_set_base(se->sbox_instance, active_selection_box->get_rid());
8110
RenderingServer::get_singleton()->instance_set_base(se->sbox_instance_xray, active_selection_box_xray->get_rid());
8111
RenderingServer::get_singleton()->instance_set_base(se->sbox_instance_offset, active_selection_box->get_rid());
8112
RenderingServer::get_singleton()->instance_set_base(se->sbox_instance_xray_offset, active_selection_box_xray->get_rid());
8113
} else {
8114
RenderingServer::get_singleton()->instance_set_base(se->sbox_instance, selection_box->get_rid());
8115
RenderingServer::get_singleton()->instance_set_base(se->sbox_instance_xray, selection_box_xray->get_rid());
8116
RenderingServer::get_singleton()->instance_set_base(se->sbox_instance_offset, selection_box->get_rid());
8117
RenderingServer::get_singleton()->instance_set_base(se->sbox_instance_xray_offset, selection_box_xray->get_rid());
8118
}
8119
}
8120
8121
if (selected && editor_selection->get_top_selected_node_list().size() != 1) {
8122
Vector<Ref<Node3DGizmo>> gizmos = selected->get_gizmos();
8123
for (int i = 0; i < gizmos.size(); i++) {
8124
Ref<EditorNode3DGizmo> seg = gizmos[i];
8125
if (seg.is_null()) {
8126
continue;
8127
}
8128
seg->set_selected(false);
8129
}
8130
8131
Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected);
8132
if (se) {
8133
se->gizmo.unref();
8134
se->subgizmos.clear();
8135
}
8136
selected->update_gizmos();
8137
selected = nullptr;
8138
}
8139
8140
// Ensure gizmo updates are performed when the selection changes
8141
// outside of the 3D view (see GH-106713).
8142
if (!is_visible()) {
8143
const List<Node *> &top_selected = editor_selection->get_top_selected_node_list();
8144
if (top_selected.size() == 1) {
8145
Node3D *new_selected = Object::cast_to<Node3D>(top_selected.back()->get());
8146
if (new_selected != selected) {
8147
gizmos_dirty = true;
8148
}
8149
}
8150
}
8151
8152
update_transform_gizmo();
8153
}
8154
8155
void Node3DEditor::refresh_dirty_gizmos() {
8156
if (!gizmos_dirty) {
8157
return;
8158
}
8159
8160
const List<Node *> &top_selected = editor_selection->get_top_selected_node_list();
8161
if (top_selected.size() == 1) {
8162
Node3D *new_selected = Object::cast_to<Node3D>(top_selected.back()->get());
8163
if (new_selected != selected) {
8164
edit(new_selected);
8165
}
8166
}
8167
gizmos_dirty = false;
8168
}
8169
8170
void Node3DEditor::_refresh_menu_icons() {
8171
bool all_locked = true;
8172
bool all_grouped = true;
8173
bool has_node3d_item = false;
8174
8175
const List<Node *> &selection = editor_selection->get_top_selected_node_list();
8176
8177
if (selection.is_empty()) {
8178
all_locked = false;
8179
all_grouped = false;
8180
} else {
8181
for (Node *E : selection) {
8182
Node3D *node = Object::cast_to<Node3D>(E);
8183
if (node) {
8184
if (all_locked && !node->has_meta("_edit_lock_")) {
8185
all_locked = false;
8186
}
8187
if (all_grouped && !node->has_meta("_edit_group_")) {
8188
all_grouped = false;
8189
}
8190
has_node3d_item = true;
8191
}
8192
if (!all_locked && !all_grouped) {
8193
break;
8194
}
8195
}
8196
}
8197
8198
all_locked = all_locked && has_node3d_item;
8199
all_grouped = all_grouped && has_node3d_item;
8200
8201
tool_button[TOOL_LOCK_SELECTED]->set_visible(!all_locked);
8202
tool_button[TOOL_LOCK_SELECTED]->set_disabled(!has_node3d_item);
8203
tool_button[TOOL_UNLOCK_SELECTED]->set_visible(all_locked);
8204
tool_button[TOOL_UNLOCK_SELECTED]->set_disabled(!has_node3d_item);
8205
8206
tool_button[TOOL_GROUP_SELECTED]->set_visible(!all_grouped);
8207
tool_button[TOOL_GROUP_SELECTED]->set_disabled(!has_node3d_item);
8208
tool_button[TOOL_UNGROUP_SELECTED]->set_visible(all_grouped);
8209
tool_button[TOOL_UNGROUP_SELECTED]->set_disabled(!has_node3d_item);
8210
}
8211
8212
template <typename T>
8213
HashSet<T *> _get_child_nodes(Node *parent_node) {
8214
HashSet<T *> nodes = HashSet<T *>();
8215
T *node = Node::cast_to<T>(parent_node);
8216
if (node) {
8217
nodes.insert(node);
8218
}
8219
8220
for (int i = 0; i < parent_node->get_child_count(); i++) {
8221
Node *child_node = parent_node->get_child(i);
8222
HashSet<T *> child_nodes = _get_child_nodes<T>(child_node);
8223
for (T *I : child_nodes) {
8224
nodes.insert(I);
8225
}
8226
}
8227
8228
return nodes;
8229
}
8230
8231
HashSet<RID> _get_physics_bodies_rid(Node *node) {
8232
HashSet<RID> rids = HashSet<RID>();
8233
PhysicsBody3D *pb = Node::cast_to<PhysicsBody3D>(node);
8234
if (pb) {
8235
rids.insert(pb->get_rid());
8236
}
8237
HashSet<PhysicsBody3D *> child_nodes = _get_child_nodes<PhysicsBody3D>(node);
8238
for (const PhysicsBody3D *I : child_nodes) {
8239
rids.insert(I->get_rid());
8240
}
8241
8242
return rids;
8243
}
8244
8245
void Node3DEditor::snap_selected_nodes_to_floor() {
8246
do_snap_selected_nodes_to_floor = true;
8247
}
8248
8249
void Node3DEditor::_snap_selected_nodes_to_floor() {
8250
const List<Node *> &selection = editor_selection->get_top_selected_node_list();
8251
Dictionary snap_data;
8252
8253
for (Node *E : selection) {
8254
Node3D *sp = Object::cast_to<Node3D>(E);
8255
if (sp) {
8256
Vector3 from;
8257
Vector3 position_offset;
8258
8259
// Priorities for snapping to floor are CollisionShapes, VisualInstances and then origin
8260
HashSet<VisualInstance3D *> vi = _get_child_nodes<VisualInstance3D>(sp);
8261
HashSet<CollisionShape3D *> cs = _get_child_nodes<CollisionShape3D>(sp);
8262
bool found_valid_shape = false;
8263
8264
if (cs.size()) {
8265
AABB aabb;
8266
HashSet<CollisionShape3D *>::Iterator I = cs.begin();
8267
if ((*I)->get_shape().is_valid()) {
8268
CollisionShape3D *collision_shape = *cs.begin();
8269
aabb = collision_shape->get_global_transform().xform(collision_shape->get_shape()->get_debug_mesh()->get_aabb());
8270
found_valid_shape = true;
8271
}
8272
8273
for (++I; I; ++I) {
8274
CollisionShape3D *col_shape = *I;
8275
if (col_shape->get_shape().is_valid()) {
8276
aabb.merge_with(col_shape->get_global_transform().xform(col_shape->get_shape()->get_debug_mesh()->get_aabb()));
8277
found_valid_shape = true;
8278
}
8279
}
8280
if (found_valid_shape) {
8281
Vector3 size = aabb.size * Vector3(0.5, 0.0, 0.5);
8282
from = aabb.position + size;
8283
position_offset.y = from.y - sp->get_global_transform().origin.y;
8284
}
8285
}
8286
if (!found_valid_shape && vi.size()) {
8287
VisualInstance3D *begin = *vi.begin();
8288
AABB aabb = begin->get_global_transform().xform(begin->get_aabb());
8289
for (const VisualInstance3D *I : vi) {
8290
aabb.merge_with(I->get_global_transform().xform(I->get_aabb()));
8291
}
8292
Vector3 size = aabb.size * Vector3(0.5, 0.0, 0.5);
8293
from = aabb.position + size;
8294
position_offset.y = from.y - sp->get_global_transform().origin.y;
8295
} else if (!found_valid_shape) {
8296
from = sp->get_global_transform().origin;
8297
}
8298
8299
// We add a bit of margin to the from position to avoid it from snapping
8300
// when the spatial is already on a floor and there's another floor under
8301
// it
8302
from = from + Vector3(0.0, 1, 0.0);
8303
8304
Dictionary d;
8305
8306
d["from"] = from;
8307
d["position_offset"] = position_offset;
8308
snap_data[sp] = d;
8309
}
8310
}
8311
8312
PhysicsDirectSpaceState3D *ss = get_tree()->get_root()->get_world_3d()->get_direct_space_state();
8313
PhysicsDirectSpaceState3D::RayResult result;
8314
8315
// The maximum height an object can travel to be snapped
8316
const float max_snap_height = 500.0;
8317
8318
// Will be set to `true` if at least one node from the selection was successfully snapped
8319
bool snapped_to_floor = false;
8320
8321
if (!snap_data.is_empty()) {
8322
// For snapping to be performed, there must be solid geometry under at least one of the selected nodes.
8323
// We need to check this before snapping to register the undo/redo action only if needed.
8324
for (const KeyValue<Variant, Variant> &kv : snap_data) {
8325
Node *node = Object::cast_to<Node>(kv.key);
8326
Node3D *sp = Object::cast_to<Node3D>(node);
8327
Dictionary d = kv.value;
8328
Vector3 from = d["from"];
8329
Vector3 to = from - Vector3(0.0, max_snap_height, 0.0);
8330
HashSet<RID> excluded = _get_physics_bodies_rid(sp);
8331
8332
PhysicsDirectSpaceState3D::RayParameters ray_params;
8333
ray_params.from = from;
8334
ray_params.to = to;
8335
ray_params.exclude = excluded;
8336
8337
if (ss->intersect_ray(ray_params, result)) {
8338
snapped_to_floor = true;
8339
}
8340
}
8341
8342
if (snapped_to_floor) {
8343
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
8344
undo_redo->create_action(TTR("Snap Nodes to Floor"));
8345
8346
// Perform snapping if at least one node can be snapped
8347
for (const KeyValue<Variant, Variant> &kv : snap_data) {
8348
Node *node = Object::cast_to<Node>(kv.key);
8349
Node3D *sp = Object::cast_to<Node3D>(node);
8350
Dictionary d = kv.value;
8351
Vector3 from = d["from"];
8352
Vector3 to = from - Vector3(0.0, max_snap_height, 0.0);
8353
HashSet<RID> excluded = _get_physics_bodies_rid(sp);
8354
8355
PhysicsDirectSpaceState3D::RayParameters ray_params;
8356
ray_params.from = from;
8357
ray_params.to = to;
8358
ray_params.exclude = excluded;
8359
8360
if (ss->intersect_ray(ray_params, result)) {
8361
Vector3 position_offset = d["position_offset"];
8362
Transform3D new_transform = sp->get_global_transform();
8363
8364
new_transform.origin.y = result.position.y;
8365
new_transform.origin = new_transform.origin - position_offset;
8366
8367
Node3D *parent = sp->get_parent_node_3d();
8368
Transform3D new_local_xform = parent ? parent->get_global_transform().affine_inverse() * new_transform : new_transform;
8369
undo_redo->add_do_method(sp, "set_transform", new_local_xform);
8370
undo_redo->add_undo_method(sp, "set_transform", sp->get_transform());
8371
}
8372
}
8373
8374
undo_redo->commit_action();
8375
} else {
8376
EditorNode::get_singleton()->show_warning(TTR("Couldn't find a solid floor to snap the selection to."));
8377
}
8378
}
8379
}
8380
8381
void Node3DEditor::shortcut_input(const Ref<InputEvent> &p_event) {
8382
ERR_FAIL_COND(p_event.is_null());
8383
8384
if (!is_visible_in_tree()) {
8385
return;
8386
}
8387
8388
snap_key_enabled = Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL);
8389
}
8390
8391
void Node3DEditor::_sun_environ_settings_pressed() {
8392
Vector2 pos = sun_environ_settings->get_screen_position() + sun_environ_settings->get_size();
8393
sun_environ_popup->set_position(pos - Vector2(sun_environ_popup->get_contents_minimum_size().width / 2, 0));
8394
sun_environ_popup->reset_size();
8395
sun_environ_popup->popup();
8396
// Grabbing the focus is required for Shift modifier checking to be functional
8397
// (when the Add sun/environment buttons are pressed).
8398
sun_environ_popup->grab_focus();
8399
}
8400
8401
void Node3DEditor::_add_sun_to_scene(bool p_already_added_environment) {
8402
sun_environ_popup->hide();
8403
8404
if (!p_already_added_environment && world_env_count == 0 && Input::get_singleton()->is_key_pressed(Key::SHIFT)) {
8405
// Prevent infinite feedback loop between the sun and environment methods.
8406
_add_environment_to_scene(true);
8407
}
8408
8409
Node *base = get_tree()->get_edited_scene_root();
8410
if (!base) {
8411
// Create a root node so we can add child nodes to it.
8412
SceneTreeDock::get_singleton()->add_root_node(memnew(Node3D));
8413
base = get_tree()->get_edited_scene_root();
8414
}
8415
ERR_FAIL_NULL(base);
8416
Node *new_sun = preview_sun->duplicate();
8417
8418
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
8419
undo_redo->create_action(TTR("Add Preview Sun to Scene"));
8420
undo_redo->add_do_method(base, "add_child", new_sun, true);
8421
// Move to the beginning of the scene tree since more "global" nodes
8422
// generally look better when placed at the top.
8423
undo_redo->add_do_method(base, "move_child", new_sun, 0);
8424
undo_redo->add_do_method(new_sun, "set_owner", base);
8425
undo_redo->add_undo_method(base, "remove_child", new_sun);
8426
undo_redo->add_do_reference(new_sun);
8427
undo_redo->commit_action();
8428
}
8429
8430
void Node3DEditor::_add_environment_to_scene(bool p_already_added_sun) {
8431
sun_environ_popup->hide();
8432
8433
if (!p_already_added_sun && directional_light_count == 0 && Input::get_singleton()->is_key_pressed(Key::SHIFT)) {
8434
// Prevent infinite feedback loop between the sun and environment methods.
8435
_add_sun_to_scene(true);
8436
}
8437
8438
Node *base = get_tree()->get_edited_scene_root();
8439
if (!base) {
8440
// Create a root node so we can add child nodes to it.
8441
SceneTreeDock::get_singleton()->add_root_node(memnew(Node3D));
8442
base = get_tree()->get_edited_scene_root();
8443
}
8444
ERR_FAIL_NULL(base);
8445
8446
WorldEnvironment *new_env = memnew(WorldEnvironment);
8447
new_env->set_environment(preview_environment->get_environment()->duplicate(true));
8448
if (GLOBAL_GET("rendering/lights_and_shadows/use_physical_light_units")) {
8449
new_env->set_camera_attributes(preview_environment->get_camera_attributes()->duplicate(true));
8450
}
8451
8452
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
8453
undo_redo->create_action(TTR("Add Preview Environment to Scene"));
8454
undo_redo->add_do_method(base, "add_child", new_env, true);
8455
// Move to the beginning of the scene tree since more "global" nodes
8456
// generally look better when placed at the top.
8457
undo_redo->add_do_method(base, "move_child", new_env, 0);
8458
undo_redo->add_do_method(new_env, "set_owner", base);
8459
undo_redo->add_undo_method(base, "remove_child", new_env);
8460
undo_redo->add_do_reference(new_env);
8461
undo_redo->commit_action();
8462
}
8463
8464
void Node3DEditor::_update_theme() {
8465
tool_button[TOOL_MODE_SELECT]->set_button_icon(get_editor_theme_icon(SNAME("ToolSelect")));
8466
tool_button[TOOL_MODE_MOVE]->set_button_icon(get_editor_theme_icon(SNAME("ToolMove")));
8467
tool_button[TOOL_MODE_ROTATE]->set_button_icon(get_editor_theme_icon(SNAME("ToolRotate")));
8468
tool_button[TOOL_MODE_SCALE]->set_button_icon(get_editor_theme_icon(SNAME("ToolScale")));
8469
tool_button[TOOL_MODE_LIST_SELECT]->set_button_icon(get_editor_theme_icon(SNAME("ListSelect")));
8470
tool_button[TOOL_LOCK_SELECTED]->set_button_icon(get_editor_theme_icon(SNAME("Lock")));
8471
tool_button[TOOL_UNLOCK_SELECTED]->set_button_icon(get_editor_theme_icon(SNAME("Unlock")));
8472
tool_button[TOOL_GROUP_SELECTED]->set_button_icon(get_editor_theme_icon(SNAME("Group")));
8473
tool_button[TOOL_UNGROUP_SELECTED]->set_button_icon(get_editor_theme_icon(SNAME("Ungroup")));
8474
tool_button[TOOL_RULER]->set_button_icon(get_editor_theme_icon(SNAME("Ruler")));
8475
8476
tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_button_icon(get_editor_theme_icon(SNAME("Object")));
8477
tool_option_button[TOOL_OPT_USE_SNAP]->set_button_icon(get_editor_theme_icon(SNAME("Snap")));
8478
8479
view_layout_menu->get_popup()->set_item_icon(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), get_editor_theme_icon(SNAME("Panels1")));
8480
view_layout_menu->get_popup()->set_item_icon(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), get_editor_theme_icon(SNAME("Panels2")));
8481
view_layout_menu->get_popup()->set_item_icon(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT), get_editor_theme_icon(SNAME("Panels2Alt")));
8482
view_layout_menu->get_popup()->set_item_icon(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS), get_editor_theme_icon(SNAME("Panels3")));
8483
view_layout_menu->get_popup()->set_item_icon(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT), get_editor_theme_icon(SNAME("Panels3Alt")));
8484
view_layout_menu->get_popup()->set_item_icon(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS), get_editor_theme_icon(SNAME("Panels4")));
8485
8486
sun_button->set_button_icon(get_editor_theme_icon(SNAME("PreviewSun")));
8487
environ_button->set_button_icon(get_editor_theme_icon(SNAME("PreviewEnvironment")));
8488
sun_environ_settings->set_button_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl")));
8489
8490
sun_title->add_theme_font_override(SceneStringName(font), get_theme_font(SNAME("title_font"), SNAME("Window")));
8491
environ_title->add_theme_font_override(SceneStringName(font), get_theme_font(SNAME("title_font"), SNAME("Window")));
8492
8493
sun_color->set_custom_minimum_size(Size2(0, get_theme_constant(SNAME("color_picker_button_height"), EditorStringName(Editor))));
8494
environ_sky_color->set_custom_minimum_size(Size2(0, get_theme_constant(SNAME("color_picker_button_height"), EditorStringName(Editor))));
8495
environ_ground_color->set_custom_minimum_size(Size2(0, get_theme_constant(SNAME("color_picker_button_height"), EditorStringName(Editor))));
8496
8497
context_toolbar_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("ContextualToolbar"), EditorStringName(EditorStyles)));
8498
}
8499
8500
void Node3DEditor::_notification(int p_what) {
8501
switch (p_what) {
8502
case NOTIFICATION_TRANSLATION_CHANGED: {
8503
tool_button[TOOL_MODE_SELECT]->set_tooltip_text(keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL) + TTR("Drag: Rotate selected node around pivot.") + "\n" + TTR("Alt+RMB: Show list of all nodes at position clicked, including locked.") + "\n" + TTR("(Available in all modes.)"));
8504
tool_button[TOOL_MODE_MOVE]->set_tooltip_text(keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL) + TTR("Drag: Use snap.") + "\n" + TTR("Alt+RMB: Show list of all nodes at position clicked, including locked."));
8505
tool_button[TOOL_MODE_ROTATE]->set_tooltip_text(keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL) + TTR("Drag: Use snap.") + "\n" + TTR("Alt+RMB: Show list of all nodes at position clicked, including locked."));
8506
tool_button[TOOL_MODE_SCALE]->set_tooltip_text(keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL) + TTR("Drag: Use snap.") + "\n" + TTR("Alt+RMB: Show list of all nodes at position clicked, including locked."));
8507
_update_gizmos_menu();
8508
} break;
8509
8510
case NOTIFICATION_READY: {
8511
_menu_item_pressed(MENU_VIEW_USE_1_VIEWPORT);
8512
8513
_refresh_menu_icons();
8514
8515
get_tree()->connect("node_removed", callable_mp(this, &Node3DEditor::_node_removed));
8516
get_tree()->connect("node_added", callable_mp(this, &Node3DEditor::_node_added));
8517
SceneTreeDock::get_singleton()->get_tree_editor()->connect("node_changed", callable_mp(this, &Node3DEditor::_refresh_menu_icons));
8518
editor_selection->connect("selection_changed", callable_mp(this, &Node3DEditor::_selection_changed));
8519
8520
_update_preview_environment();
8521
8522
sun_state->set_custom_minimum_size(sun_vb->get_combined_minimum_size());
8523
environ_state->set_custom_minimum_size(environ_vb->get_combined_minimum_size());
8524
8525
ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &Node3DEditor::update_all_gizmos).bind(Variant()));
8526
} break;
8527
8528
case NOTIFICATION_ACCESSIBILITY_UPDATE: {
8529
RID ae = get_accessibility_element();
8530
ERR_FAIL_COND(ae.is_null());
8531
8532
//TODO
8533
DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_STATIC_TEXT);
8534
DisplayServer::get_singleton()->accessibility_update_set_value(ae, TTR(vformat("The %s is not accessible at this time.", "3D editor")));
8535
} break;
8536
8537
case NOTIFICATION_ENTER_TREE: {
8538
_update_theme();
8539
_register_all_gizmos();
8540
_init_indicators();
8541
update_all_gizmos();
8542
} break;
8543
8544
case NOTIFICATION_EXIT_TREE: {
8545
_finish_indicators();
8546
} break;
8547
8548
case NOTIFICATION_THEME_CHANGED: {
8549
_update_theme();
8550
_update_gizmos_menu_theme();
8551
sun_title->add_theme_font_override(SceneStringName(font), get_theme_font(SNAME("title_font"), SNAME("Window")));
8552
environ_title->add_theme_font_override(SceneStringName(font), get_theme_font(SNAME("title_font"), SNAME("Window")));
8553
} break;
8554
8555
case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
8556
if (EditorSettings::get_singleton()->check_changed_settings_in_group("editors/3d")) {
8557
const Color selection_box_color = EDITOR_GET("editors/3d/selection_box_color");
8558
const Color active_selection_box_color = EDITOR_GET("editors/3d/active_selection_box_color");
8559
8560
if (selection_box_color != selection_box_mat->get_albedo()) {
8561
selection_box_mat->set_albedo(selection_box_color);
8562
selection_box_mat_xray->set_albedo(selection_box_color * Color(1, 1, 1, 0.15));
8563
}
8564
8565
if (active_selection_box_color != active_selection_box_mat->get_albedo()) {
8566
active_selection_box_mat->set_albedo(active_selection_box_color);
8567
active_selection_box_mat_xray->set_albedo(active_selection_box_color * Color(1, 1, 1, 0.15));
8568
}
8569
8570
// Update grid color by rebuilding grid.
8571
_finish_grid();
8572
_init_grid();
8573
}
8574
} break;
8575
8576
case NOTIFICATION_PHYSICS_PROCESS: {
8577
if (do_snap_selected_nodes_to_floor) {
8578
_snap_selected_nodes_to_floor();
8579
do_snap_selected_nodes_to_floor = false;
8580
}
8581
}
8582
}
8583
}
8584
8585
bool Node3DEditor::is_subgizmo_selected(int p_id) {
8586
Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr;
8587
if (se) {
8588
return se->subgizmos.has(p_id);
8589
}
8590
return false;
8591
}
8592
8593
bool Node3DEditor::is_current_selected_gizmo(const EditorNode3DGizmo *p_gizmo) {
8594
Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr;
8595
if (se) {
8596
return se->gizmo == p_gizmo;
8597
}
8598
return false;
8599
}
8600
8601
Vector<int> Node3DEditor::get_subgizmo_selection() {
8602
Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr;
8603
8604
Vector<int> ret;
8605
if (se) {
8606
for (const KeyValue<int, Transform3D> &E : se->subgizmos) {
8607
ret.push_back(E.key);
8608
}
8609
}
8610
return ret;
8611
}
8612
8613
void Node3DEditor::clear_subgizmo_selection(Object *p_obj) {
8614
_clear_subgizmo_selection(p_obj);
8615
}
8616
8617
void Node3DEditor::add_control_to_menu_panel(Control *p_control) {
8618
ERR_FAIL_NULL(p_control);
8619
ERR_FAIL_COND(p_control->get_parent());
8620
8621
VSeparator *sep = memnew(VSeparator);
8622
context_toolbar_hbox->add_child(sep);
8623
context_toolbar_hbox->add_child(p_control);
8624
context_toolbar_separators[p_control] = sep;
8625
8626
p_control->connect(SceneStringName(visibility_changed), callable_mp(this, &Node3DEditor::_update_context_toolbar));
8627
8628
_update_context_toolbar();
8629
}
8630
8631
void Node3DEditor::remove_control_from_menu_panel(Control *p_control) {
8632
ERR_FAIL_NULL(p_control);
8633
ERR_FAIL_COND(p_control->get_parent() != context_toolbar_hbox);
8634
8635
p_control->disconnect(SceneStringName(visibility_changed), callable_mp(this, &Node3DEditor::_update_context_toolbar));
8636
8637
VSeparator *sep = context_toolbar_separators[p_control];
8638
context_toolbar_hbox->remove_child(sep);
8639
context_toolbar_hbox->remove_child(p_control);
8640
context_toolbar_separators.erase(p_control);
8641
memdelete(sep);
8642
8643
_update_context_toolbar();
8644
}
8645
8646
void Node3DEditor::_update_context_toolbar() {
8647
bool has_visible = false;
8648
bool first_visible = false;
8649
8650
for (int i = 0; i < context_toolbar_hbox->get_child_count(); i++) {
8651
Control *child = Object::cast_to<Control>(context_toolbar_hbox->get_child(i));
8652
if (!child || !context_toolbar_separators.has(child)) {
8653
continue;
8654
}
8655
if (child->is_visible()) {
8656
first_visible = !has_visible;
8657
has_visible = true;
8658
}
8659
8660
VSeparator *sep = context_toolbar_separators[child];
8661
sep->set_visible(!first_visible && child->is_visible());
8662
}
8663
8664
context_toolbar_panel->set_visible(has_visible);
8665
}
8666
8667
void Node3DEditor::set_can_preview(Camera3D *p_preview) {
8668
for (int i = 0; i < 4; i++) {
8669
viewports[i]->set_can_preview(p_preview);
8670
}
8671
}
8672
8673
VSplitContainer *Node3DEditor::get_shader_split() {
8674
return shader_split;
8675
}
8676
8677
Node3DEditorViewport *Node3DEditor::get_last_used_viewport() {
8678
return viewports[last_used_viewport];
8679
}
8680
8681
void Node3DEditor::add_control_to_left_panel(Control *p_control) {
8682
left_panel_split->add_child(p_control);
8683
left_panel_split->move_child(p_control, 0);
8684
}
8685
8686
void Node3DEditor::add_control_to_right_panel(Control *p_control) {
8687
right_panel_split->add_child(p_control);
8688
right_panel_split->move_child(p_control, 1);
8689
}
8690
8691
void Node3DEditor::remove_control_from_left_panel(Control *p_control) {
8692
left_panel_split->remove_child(p_control);
8693
}
8694
8695
void Node3DEditor::remove_control_from_right_panel(Control *p_control) {
8696
right_panel_split->remove_child(p_control);
8697
}
8698
8699
void Node3DEditor::move_control_to_left_panel(Control *p_control) {
8700
ERR_FAIL_NULL(p_control);
8701
if (p_control->get_parent() == left_panel_split) {
8702
return;
8703
}
8704
8705
ERR_FAIL_COND(p_control->get_parent() != right_panel_split);
8706
right_panel_split->remove_child(p_control);
8707
8708
add_control_to_left_panel(p_control);
8709
}
8710
8711
void Node3DEditor::move_control_to_right_panel(Control *p_control) {
8712
ERR_FAIL_NULL(p_control);
8713
if (p_control->get_parent() == right_panel_split) {
8714
return;
8715
}
8716
8717
ERR_FAIL_COND(p_control->get_parent() != left_panel_split);
8718
left_panel_split->remove_child(p_control);
8719
8720
add_control_to_right_panel(p_control);
8721
}
8722
8723
void Node3DEditor::_request_gizmo(Object *p_obj) {
8724
Node3D *sp = Object::cast_to<Node3D>(p_obj);
8725
if (!sp) {
8726
return;
8727
}
8728
8729
bool is_selected = (sp == selected);
8730
8731
Node *edited_scene = EditorNode::get_singleton()->get_edited_scene();
8732
if (edited_scene && (sp == edited_scene || (sp->get_owner() && edited_scene->is_ancestor_of(sp)))) {
8733
for (int i = 0; i < gizmo_plugins_by_priority.size(); ++i) {
8734
Ref<EditorNode3DGizmo> seg = gizmo_plugins_by_priority.write[i]->get_gizmo(sp);
8735
8736
if (seg.is_valid()) {
8737
sp->add_gizmo(seg);
8738
8739
if (is_selected != seg->is_selected()) {
8740
seg->set_selected(is_selected);
8741
}
8742
}
8743
}
8744
if (!sp->get_gizmos().is_empty()) {
8745
sp->update_gizmos();
8746
}
8747
}
8748
}
8749
8750
void Node3DEditor::_request_gizmo_for_id(ObjectID p_id) {
8751
Node3D *node = ObjectDB::get_instance<Node3D>(p_id);
8752
if (node) {
8753
_request_gizmo(node);
8754
}
8755
}
8756
8757
void Node3DEditor::_set_subgizmo_selection(Object *p_obj, Ref<Node3DGizmo> p_gizmo, int p_id, Transform3D p_transform) {
8758
if (p_id == -1) {
8759
_clear_subgizmo_selection(p_obj);
8760
return;
8761
}
8762
8763
Node3D *sp = nullptr;
8764
if (p_obj) {
8765
sp = Object::cast_to<Node3D>(p_obj);
8766
} else {
8767
sp = selected;
8768
}
8769
8770
if (!sp) {
8771
return;
8772
}
8773
8774
Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);
8775
if (se) {
8776
se->subgizmos.clear();
8777
se->subgizmos.insert(p_id, p_transform);
8778
se->gizmo = p_gizmo;
8779
sp->update_gizmos();
8780
update_transform_gizmo();
8781
}
8782
}
8783
8784
void Node3DEditor::_clear_subgizmo_selection(Object *p_obj) {
8785
Node3D *sp = nullptr;
8786
if (p_obj) {
8787
sp = Object::cast_to<Node3D>(p_obj);
8788
} else {
8789
sp = selected;
8790
}
8791
8792
if (!sp) {
8793
return;
8794
}
8795
8796
Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);
8797
if (se) {
8798
se->subgizmos.clear();
8799
se->gizmo.unref();
8800
sp->update_gizmos();
8801
update_transform_gizmo();
8802
}
8803
}
8804
8805
void Node3DEditor::_toggle_maximize_view(Object *p_viewport) {
8806
if (!p_viewport) {
8807
return;
8808
}
8809
Node3DEditorViewport *current_viewport = Object::cast_to<Node3DEditorViewport>(p_viewport);
8810
if (!current_viewport) {
8811
return;
8812
}
8813
8814
int index = -1;
8815
bool maximized = false;
8816
for (int i = 0; i < 4; i++) {
8817
if (viewports[i] == current_viewport) {
8818
index = i;
8819
if (current_viewport->get_global_rect() == viewport_base->get_global_rect()) {
8820
maximized = true;
8821
}
8822
break;
8823
}
8824
}
8825
if (index == -1) {
8826
return;
8827
}
8828
8829
if (!maximized) {
8830
for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) {
8831
if (i == (uint32_t)index) {
8832
viewports[i]->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
8833
} else {
8834
viewports[i]->hide();
8835
}
8836
}
8837
} else {
8838
for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) {
8839
viewports[i]->show();
8840
}
8841
8842
if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT))) {
8843
_menu_item_pressed(MENU_VIEW_USE_1_VIEWPORT);
8844
} else if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS))) {
8845
_menu_item_pressed(MENU_VIEW_USE_2_VIEWPORTS);
8846
} else if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT))) {
8847
_menu_item_pressed(MENU_VIEW_USE_2_VIEWPORTS_ALT);
8848
} else if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS))) {
8849
_menu_item_pressed(MENU_VIEW_USE_3_VIEWPORTS);
8850
} else if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT))) {
8851
_menu_item_pressed(MENU_VIEW_USE_3_VIEWPORTS_ALT);
8852
} else if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS))) {
8853
_menu_item_pressed(MENU_VIEW_USE_4_VIEWPORTS);
8854
}
8855
}
8856
}
8857
8858
void Node3DEditor::_viewport_clicked(int p_viewport_idx) {
8859
last_used_viewport = p_viewport_idx;
8860
}
8861
8862
void Node3DEditor::_node_added(Node *p_node) {
8863
if (EditorNode::get_singleton()->get_scene_root()->is_ancestor_of(p_node)) {
8864
if (Object::cast_to<WorldEnvironment>(p_node)) {
8865
world_env_count++;
8866
if (world_env_count == 1) {
8867
_update_preview_environment();
8868
}
8869
} else if (Object::cast_to<DirectionalLight3D>(p_node)) {
8870
directional_light_count++;
8871
if (directional_light_count == 1) {
8872
_update_preview_environment();
8873
}
8874
}
8875
}
8876
}
8877
8878
void Node3DEditor::_node_removed(Node *p_node) {
8879
if (EditorNode::get_singleton()->get_scene_root()->is_ancestor_of(p_node)) {
8880
if (Object::cast_to<WorldEnvironment>(p_node)) {
8881
world_env_count--;
8882
if (world_env_count == 0) {
8883
_update_preview_environment();
8884
}
8885
} else if (Object::cast_to<DirectionalLight3D>(p_node)) {
8886
directional_light_count--;
8887
if (directional_light_count == 0) {
8888
_update_preview_environment();
8889
}
8890
}
8891
}
8892
8893
if (p_node == selected) {
8894
Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected);
8895
if (se) {
8896
se->gizmo.unref();
8897
se->subgizmos.clear();
8898
}
8899
selected = nullptr;
8900
update_transform_gizmo();
8901
}
8902
}
8903
8904
void Node3DEditor::_register_all_gizmos() {
8905
add_gizmo_plugin(Ref<Camera3DGizmoPlugin>(memnew(Camera3DGizmoPlugin)));
8906
add_gizmo_plugin(Ref<Light3DGizmoPlugin>(memnew(Light3DGizmoPlugin)));
8907
add_gizmo_plugin(Ref<AudioStreamPlayer3DGizmoPlugin>(memnew(AudioStreamPlayer3DGizmoPlugin)));
8908
add_gizmo_plugin(Ref<AudioListener3DGizmoPlugin>(memnew(AudioListener3DGizmoPlugin)));
8909
add_gizmo_plugin(Ref<MeshInstance3DGizmoPlugin>(memnew(MeshInstance3DGizmoPlugin)));
8910
add_gizmo_plugin(Ref<OccluderInstance3DGizmoPlugin>(memnew(OccluderInstance3DGizmoPlugin)));
8911
add_gizmo_plugin(Ref<SoftBody3DGizmoPlugin>(memnew(SoftBody3DGizmoPlugin)));
8912
add_gizmo_plugin(Ref<SpriteBase3DGizmoPlugin>(memnew(SpriteBase3DGizmoPlugin)));
8913
add_gizmo_plugin(Ref<Label3DGizmoPlugin>(memnew(Label3DGizmoPlugin)));
8914
add_gizmo_plugin(Ref<GeometryInstance3DGizmoPlugin>(memnew(GeometryInstance3DGizmoPlugin)));
8915
add_gizmo_plugin(Ref<Marker3DGizmoPlugin>(memnew(Marker3DGizmoPlugin)));
8916
add_gizmo_plugin(Ref<RayCast3DGizmoPlugin>(memnew(RayCast3DGizmoPlugin)));
8917
add_gizmo_plugin(Ref<ShapeCast3DGizmoPlugin>(memnew(ShapeCast3DGizmoPlugin)));
8918
add_gizmo_plugin(Ref<SpringArm3DGizmoPlugin>(memnew(SpringArm3DGizmoPlugin)));
8919
add_gizmo_plugin(Ref<SpringBoneCollision3DGizmoPlugin>(memnew(SpringBoneCollision3DGizmoPlugin)));
8920
add_gizmo_plugin(Ref<SpringBoneSimulator3DGizmoPlugin>(memnew(SpringBoneSimulator3DGizmoPlugin)));
8921
add_gizmo_plugin(Ref<VehicleWheel3DGizmoPlugin>(memnew(VehicleWheel3DGizmoPlugin)));
8922
add_gizmo_plugin(Ref<VisibleOnScreenNotifier3DGizmoPlugin>(memnew(VisibleOnScreenNotifier3DGizmoPlugin)));
8923
add_gizmo_plugin(Ref<GPUParticles3DGizmoPlugin>(memnew(GPUParticles3DGizmoPlugin)));
8924
add_gizmo_plugin(Ref<GPUParticlesCollision3DGizmoPlugin>(memnew(GPUParticlesCollision3DGizmoPlugin)));
8925
add_gizmo_plugin(Ref<Particles3DEmissionShapeGizmoPlugin>(memnew(Particles3DEmissionShapeGizmoPlugin)));
8926
add_gizmo_plugin(Ref<CPUParticles3DGizmoPlugin>(memnew(CPUParticles3DGizmoPlugin)));
8927
add_gizmo_plugin(Ref<ReflectionProbeGizmoPlugin>(memnew(ReflectionProbeGizmoPlugin)));
8928
add_gizmo_plugin(Ref<DecalGizmoPlugin>(memnew(DecalGizmoPlugin)));
8929
add_gizmo_plugin(Ref<VoxelGIGizmoPlugin>(memnew(VoxelGIGizmoPlugin)));
8930
add_gizmo_plugin(Ref<LightmapGIGizmoPlugin>(memnew(LightmapGIGizmoPlugin)));
8931
add_gizmo_plugin(Ref<LightmapProbeGizmoPlugin>(memnew(LightmapProbeGizmoPlugin)));
8932
add_gizmo_plugin(Ref<CollisionObject3DGizmoPlugin>(memnew(CollisionObject3DGizmoPlugin)));
8933
add_gizmo_plugin(Ref<CollisionShape3DGizmoPlugin>(memnew(CollisionShape3DGizmoPlugin)));
8934
add_gizmo_plugin(Ref<CollisionPolygon3DGizmoPlugin>(memnew(CollisionPolygon3DGizmoPlugin)));
8935
add_gizmo_plugin(Ref<Joint3DGizmoPlugin>(memnew(Joint3DGizmoPlugin)));
8936
add_gizmo_plugin(Ref<PhysicalBone3DGizmoPlugin>(memnew(PhysicalBone3DGizmoPlugin)));
8937
add_gizmo_plugin(Ref<FogVolumeGizmoPlugin>(memnew(FogVolumeGizmoPlugin)));
8938
}
8939
8940
void Node3DEditor::_bind_methods() {
8941
ClassDB::bind_method("_get_editor_data", &Node3DEditor::_get_editor_data);
8942
ClassDB::bind_method("_request_gizmo", &Node3DEditor::_request_gizmo);
8943
ClassDB::bind_method("_request_gizmo_for_id", &Node3DEditor::_request_gizmo_for_id);
8944
ClassDB::bind_method("_set_subgizmo_selection", &Node3DEditor::_set_subgizmo_selection);
8945
ClassDB::bind_method("_clear_subgizmo_selection", &Node3DEditor::_clear_subgizmo_selection);
8946
ClassDB::bind_method("_refresh_menu_icons", &Node3DEditor::_refresh_menu_icons);
8947
ClassDB::bind_method("_preview_settings_changed", &Node3DEditor::_preview_settings_changed);
8948
8949
ClassDB::bind_method("update_all_gizmos", &Node3DEditor::update_all_gizmos);
8950
ClassDB::bind_method("update_transform_gizmo", &Node3DEditor::update_transform_gizmo);
8951
8952
ADD_SIGNAL(MethodInfo("transform_key_request"));
8953
ADD_SIGNAL(MethodInfo("item_lock_status_changed"));
8954
ADD_SIGNAL(MethodInfo("item_group_status_changed"));
8955
}
8956
8957
void Node3DEditor::clear() {
8958
settings_fov->set_value(EDITOR_GET("editors/3d/default_fov"));
8959
settings_znear->set_value(EDITOR_GET("editors/3d/default_z_near"));
8960
settings_zfar->set_value(EDITOR_GET("editors/3d/default_z_far"));
8961
8962
snap_translate_value = EditorSettings::get_singleton()->get_project_metadata("3d_editor", "snap_translate_value", 1);
8963
snap_rotate_value = EditorSettings::get_singleton()->get_project_metadata("3d_editor", "snap_rotate_value", 15);
8964
snap_scale_value = EditorSettings::get_singleton()->get_project_metadata("3d_editor", "snap_scale_value", 10);
8965
_snap_update();
8966
8967
for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) {
8968
viewports[i]->reset();
8969
}
8970
8971
if (origin_instance.is_valid()) {
8972
RenderingServer::get_singleton()->instance_set_visible(origin_instance, true);
8973
}
8974
8975
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_ORIGIN), true);
8976
for (int i = 0; i < 3; ++i) {
8977
if (grid_enable[i]) {
8978
grid_visible[i] = true;
8979
}
8980
}
8981
8982
for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) {
8983
viewports[i]->view_display_menu->get_popup()->set_item_checked(viewports[i]->view_display_menu->get_popup()->get_item_index(Node3DEditorViewport::VIEW_AUDIO_LISTENER), i == 0);
8984
viewports[i]->viewport->set_as_audio_listener_3d(i == 0);
8985
}
8986
8987
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_GRID), true);
8988
grid_enabled = true;
8989
grid_init_draw = false;
8990
}
8991
8992
void Node3DEditor::_sun_direction_draw() {
8993
sun_direction->draw_rect(Rect2(Vector2(), sun_direction->get_size()), Color(1, 1, 1, 1));
8994
Vector3 z_axis = preview_sun->get_transform().basis.get_column(Vector3::AXIS_Z);
8995
z_axis = get_editor_viewport(0)->camera->get_camera_transform().basis.xform_inv(z_axis);
8996
sun_direction_material->set_shader_parameter("sun_direction", Vector3(z_axis.x, -z_axis.y, z_axis.z));
8997
Color color = sun_color->get_pick_color() * sun_energy->get_value();
8998
sun_direction_material->set_shader_parameter("sun_color", Vector3(color.r, color.g, color.b));
8999
}
9000
9001
void Node3DEditor::_preview_settings_changed() {
9002
if (sun_environ_updating) {
9003
return;
9004
}
9005
9006
{ // preview sun
9007
sun_rotation.x = Math::deg_to_rad(-sun_angle_altitude->get_value());
9008
sun_rotation.y = Math::deg_to_rad(180.0 - sun_angle_azimuth->get_value());
9009
Transform3D t;
9010
t.basis = Basis::from_euler(Vector3(sun_rotation.x, sun_rotation.y, 0));
9011
preview_sun->set_transform(t);
9012
sun_direction->queue_redraw();
9013
preview_sun->set_param(Light3D::PARAM_ENERGY, sun_energy->get_value());
9014
preview_sun->set_param(Light3D::PARAM_SHADOW_MAX_DISTANCE, sun_shadow_max_distance->get_value());
9015
preview_sun->set_color(sun_color->get_pick_color());
9016
}
9017
9018
{ //preview env
9019
sky_material->set_energy_multiplier(environ_energy->get_value());
9020
Color hz_color = environ_sky_color->get_pick_color().lerp(environ_ground_color->get_pick_color(), 0.5);
9021
float hz_lum = hz_color.get_luminance() * 3.333;
9022
hz_color = hz_color.lerp(Color(hz_lum, hz_lum, hz_lum), 0.5);
9023
sky_material->set_sky_top_color(environ_sky_color->get_pick_color());
9024
sky_material->set_sky_horizon_color(hz_color);
9025
sky_material->set_ground_bottom_color(environ_ground_color->get_pick_color());
9026
sky_material->set_ground_horizon_color(hz_color);
9027
9028
environment->set_ssao_enabled(environ_ao_button->is_pressed());
9029
environment->set_glow_enabled(environ_glow_button->is_pressed());
9030
environment->set_sdfgi_enabled(environ_gi_button->is_pressed());
9031
environment->set_tonemapper(environ_tonemap_button->is_pressed() ? Environment::TONE_MAPPER_FILMIC : Environment::TONE_MAPPER_LINEAR);
9032
}
9033
}
9034
9035
void Node3DEditor::_load_default_preview_settings() {
9036
sun_environ_updating = true;
9037
9038
// These default rotations place the preview sun at an angular altitude
9039
// of 60 degrees (must be negative) and an azimuth of 30 degrees clockwise
9040
// from north (or 150 CCW from south), from north east, facing south west.
9041
// On any not-tidally-locked planet, a sun would have an angular altitude
9042
// of 60 degrees as the average of all points on the sphere at noon.
9043
// The azimuth choice is arbitrary, but ideally shouldn't be on an axis.
9044
sun_rotation = Vector2(-Math::deg_to_rad(60.0), Math::deg_to_rad(150.0));
9045
9046
sun_angle_altitude->set_value_no_signal(-Math::rad_to_deg(sun_rotation.x));
9047
sun_angle_azimuth->set_value_no_signal(180.0 - Math::rad_to_deg(sun_rotation.y));
9048
sun_direction->queue_redraw();
9049
environ_sky_color->set_pick_color(Color(0.385, 0.454, 0.55));
9050
environ_ground_color->set_pick_color(Color(0.2, 0.169, 0.133));
9051
environ_energy->set_value_no_signal(1.0);
9052
if (OS::get_singleton()->get_current_rendering_method() != "gl_compatibility" && OS::get_singleton()->get_current_rendering_method() != "dummy") {
9053
environ_glow_button->set_pressed_no_signal(true);
9054
}
9055
environ_tonemap_button->set_pressed_no_signal(true);
9056
environ_ao_button->set_pressed_no_signal(false);
9057
environ_gi_button->set_pressed_no_signal(false);
9058
sun_shadow_max_distance->set_value_no_signal(100);
9059
9060
sun_color->set_pick_color(Color(1, 1, 1));
9061
sun_energy->set_value_no_signal(1.0);
9062
9063
sun_environ_updating = false;
9064
}
9065
9066
void Node3DEditor::_update_preview_environment() {
9067
bool disable_light = directional_light_count > 0 || !sun_button->is_pressed();
9068
9069
sun_button->set_disabled(directional_light_count > 0);
9070
9071
if (disable_light) {
9072
if (preview_sun->get_parent()) {
9073
preview_sun->get_parent()->remove_child(preview_sun);
9074
sun_state->show();
9075
sun_vb->hide();
9076
preview_sun_dangling = true;
9077
}
9078
9079
if (directional_light_count > 0) {
9080
sun_state->set_text(TTRC("Scene contains\nDirectionalLight3D.\nPreview disabled."));
9081
} else {
9082
sun_state->set_text(TTRC("Preview disabled."));
9083
}
9084
9085
} else {
9086
if (!preview_sun->get_parent()) {
9087
add_child(preview_sun, true);
9088
sun_state->hide();
9089
sun_vb->show();
9090
preview_sun_dangling = false;
9091
}
9092
}
9093
9094
sun_angle_altitude->set_value_no_signal(-Math::rad_to_deg(sun_rotation.x));
9095
sun_angle_azimuth->set_value_no_signal(180.0 - Math::rad_to_deg(sun_rotation.y));
9096
9097
bool disable_env = world_env_count > 0 || !environ_button->is_pressed();
9098
9099
environ_button->set_disabled(world_env_count > 0);
9100
9101
if (disable_env) {
9102
if (preview_environment->get_parent()) {
9103
preview_environment->get_parent()->remove_child(preview_environment);
9104
environ_state->show();
9105
environ_vb->hide();
9106
preview_env_dangling = true;
9107
}
9108
if (world_env_count > 0) {
9109
environ_state->set_text(TTRC("Scene contains\nWorldEnvironment.\nPreview disabled."));
9110
} else {
9111
environ_state->set_text(TTRC("Preview disabled."));
9112
}
9113
9114
} else {
9115
if (!preview_environment->get_parent()) {
9116
add_child(preview_environment);
9117
environ_state->hide();
9118
environ_vb->show();
9119
preview_env_dangling = false;
9120
}
9121
}
9122
}
9123
9124
void Node3DEditor::_sun_direction_input(const Ref<InputEvent> &p_event) {
9125
Ref<InputEventMouseMotion> mm = p_event;
9126
if (mm.is_valid() && mm->get_button_mask().has_flag(MouseButtonMask::LEFT)) {
9127
sun_rotation.x += mm->get_relative().y * (0.02 * EDSCALE);
9128
sun_rotation.y -= mm->get_relative().x * (0.02 * EDSCALE);
9129
sun_rotation.x = CLAMP(sun_rotation.x, -Math::TAU / 4, Math::TAU / 4);
9130
9131
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
9132
undo_redo->create_action(TTR("Set Preview Sun Direction"), UndoRedo::MergeMode::MERGE_ENDS);
9133
undo_redo->add_do_method(sun_angle_altitude, "set_value_no_signal", -Math::rad_to_deg(sun_rotation.x));
9134
undo_redo->add_undo_method(sun_angle_altitude, "set_value_no_signal", sun_angle_altitude->get_value());
9135
undo_redo->add_do_method(sun_angle_azimuth, "set_value_no_signal", 180.0 - Math::rad_to_deg(sun_rotation.y));
9136
undo_redo->add_undo_method(sun_angle_azimuth, "set_value_no_signal", sun_angle_azimuth->get_value());
9137
undo_redo->add_do_method(this, "_preview_settings_changed");
9138
undo_redo->add_undo_method(this, "_preview_settings_changed");
9139
undo_redo->commit_action();
9140
}
9141
}
9142
9143
void Node3DEditor::_sun_direction_set_altitude(float p_altitude) {
9144
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
9145
undo_redo->create_action(TTR("Set Preview Sun Altitude"), UndoRedo::MergeMode::MERGE_ENDS);
9146
undo_redo->add_do_method(sun_angle_altitude, "set_value_no_signal", p_altitude);
9147
undo_redo->add_undo_method(sun_angle_altitude, "set_value_no_signal", -Math::rad_to_deg(sun_rotation.x));
9148
undo_redo->add_do_method(this, "_preview_settings_changed");
9149
undo_redo->add_undo_method(this, "_preview_settings_changed");
9150
undo_redo->commit_action();
9151
}
9152
9153
void Node3DEditor::_sun_direction_set_azimuth(float p_azimuth) {
9154
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
9155
undo_redo->create_action(TTR("Set Preview Sun Azimuth"), UndoRedo::MergeMode::MERGE_ENDS);
9156
undo_redo->add_do_method(sun_angle_azimuth, "set_value_no_signal", p_azimuth);
9157
undo_redo->add_undo_method(sun_angle_azimuth, "set_value_no_signal", 180.0 - Math::rad_to_deg(sun_rotation.y));
9158
undo_redo->add_do_method(this, "_preview_settings_changed");
9159
undo_redo->add_undo_method(this, "_preview_settings_changed");
9160
undo_redo->commit_action();
9161
}
9162
9163
void Node3DEditor::_sun_set_color(const Color &p_color) {
9164
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
9165
undo_redo->create_action(TTR("Set Preview Sun Color"), UndoRedo::MergeMode::MERGE_ENDS);
9166
undo_redo->add_do_method(sun_color, "set_pick_color", p_color);
9167
undo_redo->add_undo_method(sun_color, "set_pick_color", preview_sun->get_color());
9168
undo_redo->add_do_method(this, "_preview_settings_changed");
9169
undo_redo->add_undo_method(this, "_preview_settings_changed");
9170
undo_redo->commit_action();
9171
}
9172
9173
void Node3DEditor::_sun_set_energy(float p_energy) {
9174
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
9175
undo_redo->create_action(TTR("Set Preview Sun Energy"), UndoRedo::MergeMode::MERGE_ENDS);
9176
undo_redo->add_do_method(sun_energy, "set_value_no_signal", p_energy);
9177
undo_redo->add_undo_method(sun_energy, "set_value_no_signal", preview_sun->get_param(Light3D::PARAM_ENERGY));
9178
undo_redo->add_do_method(this, "_preview_settings_changed");
9179
undo_redo->add_undo_method(this, "_preview_settings_changed");
9180
undo_redo->commit_action();
9181
}
9182
9183
void Node3DEditor::_sun_set_shadow_max_distance(float p_shadow_max_distance) {
9184
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
9185
undo_redo->create_action(TTR("Set Preview Sun Max Shadow Distance"), UndoRedo::MergeMode::MERGE_ENDS);
9186
undo_redo->add_do_method(sun_shadow_max_distance, "set_value_no_signal", p_shadow_max_distance);
9187
undo_redo->add_undo_method(sun_shadow_max_distance, "set_value_no_signal", preview_sun->get_param(Light3D::PARAM_SHADOW_MAX_DISTANCE));
9188
undo_redo->add_do_method(this, "_preview_settings_changed");
9189
undo_redo->add_undo_method(this, "_preview_settings_changed");
9190
undo_redo->commit_action();
9191
}
9192
9193
void Node3DEditor::_environ_set_sky_color(const Color &p_color) {
9194
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
9195
undo_redo->create_action(TTR("Set Preview Environment Sky Color"), UndoRedo::MergeMode::MERGE_ENDS);
9196
undo_redo->add_do_method(environ_sky_color, "set_pick_color", p_color);
9197
undo_redo->add_undo_method(environ_sky_color, "set_pick_color", sky_material->get_sky_top_color());
9198
undo_redo->add_do_method(this, "_preview_settings_changed");
9199
undo_redo->add_undo_method(this, "_preview_settings_changed");
9200
undo_redo->commit_action();
9201
}
9202
9203
void Node3DEditor::_environ_set_ground_color(const Color &p_color) {
9204
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
9205
undo_redo->create_action(TTR("Set Preview Environment Ground Color"), UndoRedo::MergeMode::MERGE_ENDS);
9206
undo_redo->add_do_method(environ_ground_color, "set_pick_color", p_color);
9207
undo_redo->add_undo_method(environ_ground_color, "set_pick_color", sky_material->get_ground_bottom_color());
9208
undo_redo->add_do_method(this, "_preview_settings_changed");
9209
undo_redo->add_undo_method(this, "_preview_settings_changed");
9210
undo_redo->commit_action();
9211
}
9212
9213
void Node3DEditor::_environ_set_sky_energy(float p_energy) {
9214
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
9215
undo_redo->create_action(TTR("Set Preview Environment Energy"), UndoRedo::MergeMode::MERGE_ENDS);
9216
undo_redo->add_do_method(environ_energy, "set_value_no_signal", p_energy);
9217
undo_redo->add_undo_method(environ_energy, "set_value_no_signal", sky_material->get_energy_multiplier());
9218
undo_redo->add_do_method(this, "_preview_settings_changed");
9219
undo_redo->add_undo_method(this, "_preview_settings_changed");
9220
undo_redo->commit_action();
9221
}
9222
9223
void Node3DEditor::_environ_set_ao() {
9224
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
9225
undo_redo->create_action(TTR("Set Preview Environment Ambient Occlusion"));
9226
undo_redo->add_do_method(environ_ao_button, "set_pressed", environ_ao_button->is_pressed());
9227
undo_redo->add_undo_method(environ_ao_button, "set_pressed", !environ_ao_button->is_pressed());
9228
undo_redo->add_do_method(this, "_preview_settings_changed");
9229
undo_redo->add_undo_method(this, "_preview_settings_changed");
9230
undo_redo->commit_action();
9231
}
9232
9233
void Node3DEditor::_environ_set_glow() {
9234
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
9235
undo_redo->create_action(TTR("Set Preview Environment Glow"));
9236
undo_redo->add_do_method(environ_glow_button, "set_pressed", environ_glow_button->is_pressed());
9237
undo_redo->add_undo_method(environ_glow_button, "set_pressed", !environ_glow_button->is_pressed());
9238
undo_redo->add_do_method(this, "_preview_settings_changed");
9239
undo_redo->add_undo_method(this, "_preview_settings_changed");
9240
undo_redo->commit_action();
9241
}
9242
9243
void Node3DEditor::_environ_set_tonemap() {
9244
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
9245
undo_redo->create_action(TTR("Set Preview Environment Tonemap"));
9246
undo_redo->add_do_method(environ_tonemap_button, "set_pressed", environ_tonemap_button->is_pressed());
9247
undo_redo->add_undo_method(environ_tonemap_button, "set_pressed", !environ_tonemap_button->is_pressed());
9248
undo_redo->add_do_method(this, "_preview_settings_changed");
9249
undo_redo->add_undo_method(this, "_preview_settings_changed");
9250
undo_redo->commit_action();
9251
}
9252
9253
void Node3DEditor::_environ_set_gi() {
9254
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
9255
undo_redo->create_action(TTR("Set Preview Environment Global Illumination"));
9256
undo_redo->add_do_method(environ_gi_button, "set_pressed", environ_gi_button->is_pressed());
9257
undo_redo->add_undo_method(environ_gi_button, "set_pressed", !environ_gi_button->is_pressed());
9258
undo_redo->add_do_method(this, "_preview_settings_changed");
9259
undo_redo->add_undo_method(this, "_preview_settings_changed");
9260
undo_redo->commit_action();
9261
}
9262
9263
void Node3DEditor::PreviewSunEnvPopup::shortcut_input(const Ref<InputEvent> &p_event) {
9264
const Ref<InputEventKey> k = p_event;
9265
if (k.is_valid() && k->is_pressed()) {
9266
bool handled = false;
9267
9268
if (ED_IS_SHORTCUT("ui_undo", p_event)) {
9269
EditorNode::get_singleton()->undo();
9270
handled = true;
9271
}
9272
9273
if (ED_IS_SHORTCUT("ui_redo", p_event)) {
9274
EditorNode::get_singleton()->redo();
9275
handled = true;
9276
}
9277
9278
if (handled) {
9279
set_input_as_handled();
9280
}
9281
}
9282
}
9283
9284
Node3DEditor::Node3DEditor() {
9285
gizmo.visible = true;
9286
gizmo.scale = 1.0;
9287
9288
viewport_environment.instantiate();
9289
VBoxContainer *vbc = this;
9290
9291
custom_camera = nullptr;
9292
ERR_FAIL_COND_MSG(singleton != nullptr, "A Node3DEditor singleton already exists.");
9293
singleton = this;
9294
editor_selection = EditorNode::get_singleton()->get_editor_selection();
9295
editor_selection->add_editor_plugin(this);
9296
9297
snap_enabled = false;
9298
snap_key_enabled = false;
9299
tool_mode = TOOL_MODE_SELECT;
9300
9301
// Add some margin to the sides for better aesthetics.
9302
// This prevents the first button's hover/pressed effect from "touching" the panel's border,
9303
// which looks ugly.
9304
MarginContainer *toolbar_margin = memnew(MarginContainer);
9305
toolbar_margin->add_theme_constant_override("margin_left", 4 * EDSCALE);
9306
toolbar_margin->add_theme_constant_override("margin_right", 4 * EDSCALE);
9307
vbc->add_child(toolbar_margin);
9308
9309
// A fluid container for all toolbars.
9310
HFlowContainer *main_flow = memnew(HFlowContainer);
9311
toolbar_margin->add_child(main_flow);
9312
9313
// Main toolbars.
9314
HBoxContainer *main_menu_hbox = memnew(HBoxContainer);
9315
main_flow->add_child(main_menu_hbox);
9316
9317
String sct;
9318
9319
tool_button[TOOL_MODE_SELECT] = memnew(Button);
9320
main_menu_hbox->add_child(tool_button[TOOL_MODE_SELECT]);
9321
tool_button[TOOL_MODE_SELECT]->set_toggle_mode(true);
9322
tool_button[TOOL_MODE_SELECT]->set_tooltip_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
9323
tool_button[TOOL_MODE_SELECT]->set_theme_type_variation(SceneStringName(FlatButton));
9324
tool_button[TOOL_MODE_SELECT]->set_pressed(true);
9325
tool_button[TOOL_MODE_SELECT]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_TOOL_SELECT));
9326
tool_button[TOOL_MODE_SELECT]->set_shortcut(ED_SHORTCUT("spatial_editor/tool_select", TTRC("Select Mode"), Key::Q, true));
9327
tool_button[TOOL_MODE_SELECT]->set_shortcut_context(this);
9328
tool_button[TOOL_MODE_SELECT]->set_accessibility_name(TTRC("Select Mode"));
9329
main_menu_hbox->add_child(memnew(VSeparator));
9330
9331
tool_button[TOOL_MODE_MOVE] = memnew(Button);
9332
main_menu_hbox->add_child(tool_button[TOOL_MODE_MOVE]);
9333
tool_button[TOOL_MODE_MOVE]->set_toggle_mode(true);
9334
tool_button[TOOL_MODE_MOVE]->set_tooltip_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
9335
tool_button[TOOL_MODE_MOVE]->set_theme_type_variation(SceneStringName(FlatButton));
9336
9337
tool_button[TOOL_MODE_MOVE]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_TOOL_MOVE));
9338
tool_button[TOOL_MODE_MOVE]->set_shortcut(ED_SHORTCUT("spatial_editor/tool_move", TTRC("Move Mode"), Key::W, true));
9339
tool_button[TOOL_MODE_MOVE]->set_shortcut_context(this);
9340
tool_button[TOOL_MODE_MOVE]->set_accessibility_name(TTRC("Move Mode"));
9341
9342
tool_button[TOOL_MODE_ROTATE] = memnew(Button);
9343
main_menu_hbox->add_child(tool_button[TOOL_MODE_ROTATE]);
9344
tool_button[TOOL_MODE_ROTATE]->set_toggle_mode(true);
9345
tool_button[TOOL_MODE_ROTATE]->set_tooltip_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
9346
tool_button[TOOL_MODE_ROTATE]->set_theme_type_variation(SceneStringName(FlatButton));
9347
tool_button[TOOL_MODE_ROTATE]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_TOOL_ROTATE));
9348
tool_button[TOOL_MODE_ROTATE]->set_shortcut(ED_SHORTCUT("spatial_editor/tool_rotate", TTRC("Rotate Mode"), Key::E, true));
9349
tool_button[TOOL_MODE_ROTATE]->set_shortcut_context(this);
9350
tool_button[TOOL_MODE_ROTATE]->set_accessibility_name(TTRC("Rotate Mode"));
9351
9352
tool_button[TOOL_MODE_SCALE] = memnew(Button);
9353
main_menu_hbox->add_child(tool_button[TOOL_MODE_SCALE]);
9354
tool_button[TOOL_MODE_SCALE]->set_toggle_mode(true);
9355
tool_button[TOOL_MODE_SCALE]->set_tooltip_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
9356
tool_button[TOOL_MODE_SCALE]->set_theme_type_variation(SceneStringName(FlatButton));
9357
tool_button[TOOL_MODE_SCALE]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_TOOL_SCALE));
9358
tool_button[TOOL_MODE_SCALE]->set_shortcut(ED_SHORTCUT("spatial_editor/tool_scale", TTRC("Scale Mode"), Key::R, true));
9359
tool_button[TOOL_MODE_SCALE]->set_shortcut_context(this);
9360
tool_button[TOOL_MODE_SCALE]->set_accessibility_name(TTRC("Scale Mode"));
9361
9362
main_menu_hbox->add_child(memnew(VSeparator));
9363
9364
tool_button[TOOL_MODE_LIST_SELECT] = memnew(Button);
9365
main_menu_hbox->add_child(tool_button[TOOL_MODE_LIST_SELECT]);
9366
tool_button[TOOL_MODE_LIST_SELECT]->set_toggle_mode(true);
9367
tool_button[TOOL_MODE_LIST_SELECT]->set_theme_type_variation(SceneStringName(FlatButton));
9368
tool_button[TOOL_MODE_LIST_SELECT]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_TOOL_LIST_SELECT));
9369
tool_button[TOOL_MODE_LIST_SELECT]->set_tooltip_text(TTRC("Show list of selectable nodes at position clicked."));
9370
9371
tool_button[TOOL_LOCK_SELECTED] = memnew(Button);
9372
main_menu_hbox->add_child(tool_button[TOOL_LOCK_SELECTED]);
9373
tool_button[TOOL_LOCK_SELECTED]->set_theme_type_variation(SceneStringName(FlatButton));
9374
tool_button[TOOL_LOCK_SELECTED]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_LOCK_SELECTED));
9375
tool_button[TOOL_LOCK_SELECTED]->set_tooltip_text(TTRC("Lock selected node, preventing selection and movement."));
9376
// Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused.
9377
tool_button[TOOL_LOCK_SELECTED]->set_shortcut(ED_GET_SHORTCUT("editor/lock_selected_nodes"));
9378
tool_button[TOOL_LOCK_SELECTED]->set_accessibility_name(TTRC("Lock"));
9379
9380
tool_button[TOOL_UNLOCK_SELECTED] = memnew(Button);
9381
main_menu_hbox->add_child(tool_button[TOOL_UNLOCK_SELECTED]);
9382
tool_button[TOOL_UNLOCK_SELECTED]->set_theme_type_variation(SceneStringName(FlatButton));
9383
tool_button[TOOL_UNLOCK_SELECTED]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_UNLOCK_SELECTED));
9384
tool_button[TOOL_UNLOCK_SELECTED]->set_tooltip_text(TTRC("Unlock selected node, allowing selection and movement."));
9385
// Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused.
9386
tool_button[TOOL_UNLOCK_SELECTED]->set_shortcut(ED_GET_SHORTCUT("editor/unlock_selected_nodes"));
9387
tool_button[TOOL_UNLOCK_SELECTED]->set_accessibility_name(TTRC("Unlock"));
9388
9389
tool_button[TOOL_GROUP_SELECTED] = memnew(Button);
9390
main_menu_hbox->add_child(tool_button[TOOL_GROUP_SELECTED]);
9391
tool_button[TOOL_GROUP_SELECTED]->set_theme_type_variation(SceneStringName(FlatButton));
9392
tool_button[TOOL_GROUP_SELECTED]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_GROUP_SELECTED));
9393
tool_button[TOOL_GROUP_SELECTED]->set_tooltip_text(TTRC("Groups the selected node with its children. This selects the parent when any child node is clicked in 2D and 3D view."));
9394
// Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused.
9395
tool_button[TOOL_GROUP_SELECTED]->set_shortcut(ED_GET_SHORTCUT("editor/group_selected_nodes"));
9396
tool_button[TOOL_GROUP_SELECTED]->set_accessibility_name(TTRC("Group"));
9397
9398
tool_button[TOOL_UNGROUP_SELECTED] = memnew(Button);
9399
main_menu_hbox->add_child(tool_button[TOOL_UNGROUP_SELECTED]);
9400
tool_button[TOOL_UNGROUP_SELECTED]->set_theme_type_variation(SceneStringName(FlatButton));
9401
tool_button[TOOL_UNGROUP_SELECTED]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_UNGROUP_SELECTED));
9402
tool_button[TOOL_UNGROUP_SELECTED]->set_tooltip_text(TTRC("Ungroups the selected node from its children. Child nodes will be individual items in 2D and 3D view."));
9403
// Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused.
9404
tool_button[TOOL_UNGROUP_SELECTED]->set_shortcut(ED_GET_SHORTCUT("editor/ungroup_selected_nodes"));
9405
tool_button[TOOL_UNGROUP_SELECTED]->set_accessibility_name(TTRC("Ungroup"));
9406
9407
tool_button[TOOL_RULER] = memnew(Button);
9408
main_menu_hbox->add_child(tool_button[TOOL_RULER]);
9409
tool_button[TOOL_RULER]->set_toggle_mode(true);
9410
tool_button[TOOL_RULER]->set_theme_type_variation("FlatButton");
9411
tool_button[TOOL_RULER]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_RULER));
9412
tool_button[TOOL_RULER]->set_tooltip_text(TTRC("LMB+Drag: Measure the distance between two points in 3D space."));
9413
// Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused.
9414
tool_button[TOOL_RULER]->set_shortcut(ED_SHORTCUT("spatial_editor/measure", TTRC("Ruler Mode"), Key::M));
9415
tool_button[TOOL_RULER]->set_accessibility_name(TTRC("Ruler Mode"));
9416
9417
main_menu_hbox->add_child(memnew(VSeparator));
9418
9419
tool_option_button[TOOL_OPT_LOCAL_COORDS] = memnew(Button);
9420
main_menu_hbox->add_child(tool_option_button[TOOL_OPT_LOCAL_COORDS]);
9421
tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_toggle_mode(true);
9422
tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_theme_type_variation(SceneStringName(FlatButton));
9423
tool_option_button[TOOL_OPT_LOCAL_COORDS]->connect(SceneStringName(toggled), callable_mp(this, &Node3DEditor::_menu_item_toggled).bind(MENU_TOOL_LOCAL_COORDS));
9424
tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_shortcut(ED_SHORTCUT("spatial_editor/local_coords", TTRC("Use Local Space"), Key::T));
9425
tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_shortcut_context(this);
9426
tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_accessibility_name(TTRC("Use Local Space"));
9427
9428
tool_option_button[TOOL_OPT_USE_SNAP] = memnew(Button);
9429
main_menu_hbox->add_child(tool_option_button[TOOL_OPT_USE_SNAP]);
9430
tool_option_button[TOOL_OPT_USE_SNAP]->set_toggle_mode(true);
9431
tool_option_button[TOOL_OPT_USE_SNAP]->set_theme_type_variation(SceneStringName(FlatButton));
9432
tool_option_button[TOOL_OPT_USE_SNAP]->connect(SceneStringName(toggled), callable_mp(this, &Node3DEditor::_menu_item_toggled).bind(MENU_TOOL_USE_SNAP));
9433
tool_option_button[TOOL_OPT_USE_SNAP]->set_shortcut(ED_SHORTCUT("spatial_editor/snap", TTRC("Use Snap"), Key::Y));
9434
tool_option_button[TOOL_OPT_USE_SNAP]->set_shortcut_context(this);
9435
tool_option_button[TOOL_OPT_USE_SNAP]->set_accessibility_name(TTRC("Use Snap"));
9436
9437
main_menu_hbox->add_child(memnew(VSeparator));
9438
sun_button = memnew(Button);
9439
sun_button->set_tooltip_text(TTRC("Toggle preview sunlight.\nIf a DirectionalLight3D node is added to the scene, preview sunlight is disabled."));
9440
sun_button->set_toggle_mode(true);
9441
sun_button->set_accessibility_name(TTRC("Toggle preview sunlight."));
9442
sun_button->set_theme_type_variation(SceneStringName(FlatButton));
9443
sun_button->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_update_preview_environment), CONNECT_DEFERRED);
9444
// Preview is enabled by default - ensure this applies on editor startup when there is no state yet.
9445
sun_button->set_pressed(true);
9446
9447
main_menu_hbox->add_child(sun_button);
9448
9449
environ_button = memnew(Button);
9450
environ_button->set_tooltip_text(TTRC("Toggle preview environment.\nIf a WorldEnvironment node is added to the scene, preview environment is disabled."));
9451
environ_button->set_toggle_mode(true);
9452
environ_button->set_accessibility_name(TTRC("Toggle preview environment."));
9453
environ_button->set_theme_type_variation(SceneStringName(FlatButton));
9454
environ_button->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_update_preview_environment), CONNECT_DEFERRED);
9455
// Preview is enabled by default - ensure this applies on editor startup when there is no state yet.
9456
environ_button->set_pressed(true);
9457
9458
main_menu_hbox->add_child(environ_button);
9459
9460
sun_environ_settings = memnew(Button);
9461
sun_environ_settings->set_tooltip_text(TTRC("Edit Sun and Environment settings."));
9462
sun_environ_settings->set_theme_type_variation(SceneStringName(FlatButton));
9463
sun_environ_settings->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_sun_environ_settings_pressed));
9464
9465
main_menu_hbox->add_child(sun_environ_settings);
9466
9467
main_menu_hbox->add_child(memnew(VSeparator));
9468
9469
// Drag and drop support;
9470
preview_node = memnew(Node3D);
9471
preview_bounds = AABB();
9472
9473
ED_SHORTCUT("spatial_editor/bottom_view", TTRC("Bottom View"), KeyModifierMask::ALT + Key::KP_7);
9474
ED_SHORTCUT("spatial_editor/top_view", TTRC("Top View"), Key::KP_7);
9475
ED_SHORTCUT("spatial_editor/rear_view", TTRC("Rear View"), KeyModifierMask::ALT + Key::KP_1);
9476
ED_SHORTCUT("spatial_editor/front_view", TTRC("Front View"), Key::KP_1);
9477
ED_SHORTCUT("spatial_editor/left_view", TTRC("Left View"), KeyModifierMask::ALT + Key::KP_3);
9478
ED_SHORTCUT("spatial_editor/right_view", TTRC("Right View"), Key::KP_3);
9479
ED_SHORTCUT("spatial_editor/orbit_view_down", TTRC("Orbit View Down"), Key::KP_2);
9480
ED_SHORTCUT("spatial_editor/orbit_view_left", TTRC("Orbit View Left"), Key::KP_4);
9481
ED_SHORTCUT("spatial_editor/orbit_view_right", TTRC("Orbit View Right"), Key::KP_6);
9482
ED_SHORTCUT("spatial_editor/orbit_view_up", TTRC("Orbit View Up"), Key::KP_8);
9483
ED_SHORTCUT("spatial_editor/orbit_view_180", TTRC("Orbit View 180"), Key::KP_9);
9484
ED_SHORTCUT("spatial_editor/switch_perspective_orthogonal", TTRC("Switch Perspective/Orthogonal View"), Key::KP_5);
9485
ED_SHORTCUT("spatial_editor/insert_anim_key", TTRC("Insert Animation Key"), Key::K);
9486
ED_SHORTCUT("spatial_editor/focus_origin", TTRC("Focus Origin"), Key::O);
9487
ED_SHORTCUT("spatial_editor/focus_selection", TTRC("Focus Selection"), Key::F);
9488
ED_SHORTCUT_ARRAY("spatial_editor/align_transform_with_view", TTRC("Align Transform with View"),
9489
{ int32_t(KeyModifierMask::ALT | KeyModifierMask::CTRL | Key::KP_0),
9490
int32_t(KeyModifierMask::ALT | KeyModifierMask::CTRL | Key::M),
9491
int32_t(KeyModifierMask::ALT | KeyModifierMask::CTRL | Key::G) });
9492
ED_SHORTCUT_OVERRIDE_ARRAY("spatial_editor/align_transform_with_view", "macos",
9493
{ int32_t(KeyModifierMask::ALT | KeyModifierMask::META | Key::KP_0),
9494
int32_t(KeyModifierMask::ALT | KeyModifierMask::META | Key::G) });
9495
ED_SHORTCUT("spatial_editor/align_rotation_with_view", TTRC("Align Rotation with View"), KeyModifierMask::ALT + KeyModifierMask::CMD_OR_CTRL + Key::F);
9496
ED_SHORTCUT("spatial_editor/freelook_toggle", TTRC("Toggle Freelook"), KeyModifierMask::SHIFT + Key::F);
9497
ED_SHORTCUT("spatial_editor/decrease_fov", TTRC("Decrease Field of View"), KeyModifierMask::CMD_OR_CTRL + Key::EQUAL); // Usually direct access key for `KEY_PLUS`.
9498
ED_SHORTCUT("spatial_editor/increase_fov", TTRC("Increase Field of View"), KeyModifierMask::CMD_OR_CTRL + Key::MINUS);
9499
ED_SHORTCUT("spatial_editor/reset_fov", TTRC("Reset Field of View to Default"), KeyModifierMask::CMD_OR_CTRL + Key::KEY_0);
9500
9501
PopupMenu *p;
9502
9503
transform_menu = memnew(MenuButton);
9504
transform_menu->set_flat(false);
9505
transform_menu->set_theme_type_variation("FlatMenuButton");
9506
transform_menu->set_text(TTRC("Transform"));
9507
transform_menu->set_switch_on_hover(true);
9508
transform_menu->set_shortcut_context(this);
9509
main_menu_hbox->add_child(transform_menu);
9510
9511
p = transform_menu->get_popup();
9512
p->add_shortcut(ED_SHORTCUT("spatial_editor/snap_to_floor", TTRC("Snap Object to Floor"), Key::PAGEDOWN), MENU_SNAP_TO_FLOOR);
9513
p->add_shortcut(ED_SHORTCUT("spatial_editor/transform_dialog", TTRC("Transform Dialog...")), MENU_TRANSFORM_DIALOG);
9514
9515
p->add_separator();
9516
p->add_shortcut(ED_SHORTCUT("spatial_editor/configure_snap", TTRC("Configure Snap...")), MENU_TRANSFORM_CONFIGURE_SNAP);
9517
9518
p->connect(SceneStringName(id_pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed));
9519
9520
view_layout_menu = memnew(MenuButton);
9521
view_layout_menu->set_flat(false);
9522
view_layout_menu->set_theme_type_variation("FlatMenuButton");
9523
// TRANSLATORS: Noun, name of the 2D/3D View menus.
9524
view_layout_menu->set_text(TTRC("View"));
9525
view_layout_menu->set_switch_on_hover(true);
9526
view_layout_menu->set_shortcut_context(this);
9527
main_menu_hbox->add_child(view_layout_menu);
9528
9529
main_menu_hbox->add_child(memnew(VSeparator));
9530
9531
context_toolbar_panel = memnew(PanelContainer);
9532
context_toolbar_hbox = memnew(HBoxContainer);
9533
context_toolbar_panel->add_child(context_toolbar_hbox);
9534
main_flow->add_child(context_toolbar_panel);
9535
9536
// Get the view menu popup and have it stay open when a checkable item is selected
9537
p = view_layout_menu->get_popup();
9538
p->set_hide_on_checkable_item_selection(false);
9539
9540
accept = memnew(AcceptDialog);
9541
EditorNode::get_singleton()->get_gui_base()->add_child(accept);
9542
9543
p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/1_viewport", TTRC("1 Viewport"), KeyModifierMask::CMD_OR_CTRL + Key::KEY_1), MENU_VIEW_USE_1_VIEWPORT);
9544
p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/2_viewports", TTRC("2 Viewports"), KeyModifierMask::CMD_OR_CTRL + Key::KEY_2), MENU_VIEW_USE_2_VIEWPORTS);
9545
p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/2_viewports_alt", TTRC("2 Viewports (Alt)"), KeyModifierMask::ALT + KeyModifierMask::CMD_OR_CTRL + Key::KEY_2), MENU_VIEW_USE_2_VIEWPORTS_ALT);
9546
p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/3_viewports", TTRC("3 Viewports"), KeyModifierMask::CMD_OR_CTRL + Key::KEY_3), MENU_VIEW_USE_3_VIEWPORTS);
9547
p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/3_viewports_alt", TTRC("3 Viewports (Alt)"), KeyModifierMask::ALT + KeyModifierMask::CMD_OR_CTRL + Key::KEY_3), MENU_VIEW_USE_3_VIEWPORTS_ALT);
9548
p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/4_viewports", TTRC("4 Viewports"), KeyModifierMask::CMD_OR_CTRL + Key::KEY_4), MENU_VIEW_USE_4_VIEWPORTS);
9549
p->add_separator();
9550
9551
gizmos_menu = memnew(PopupMenu);
9552
gizmos_menu->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
9553
gizmos_menu->set_hide_on_checkable_item_selection(false);
9554
p->add_submenu_node_item(TTRC("Gizmos"), gizmos_menu);
9555
gizmos_menu->connect(SceneStringName(id_pressed), callable_mp(this, &Node3DEditor::_menu_gizmo_toggled));
9556
9557
p->add_separator();
9558
p->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_origin", TTRC("View Origin")), MENU_VIEW_ORIGIN);
9559
p->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_grid", TTRC("View Grid"), Key::NUMBERSIGN), MENU_VIEW_GRID);
9560
9561
p->add_separator();
9562
p->add_submenu_node_item(TTRC("Preview Translation"), memnew(EditorTranslationPreviewMenu));
9563
9564
p->add_separator();
9565
p->add_shortcut(ED_SHORTCUT("spatial_editor/settings", TTRC("Settings...")), MENU_VIEW_CAMERA_SETTINGS);
9566
9567
p->set_item_checked(p->get_item_index(MENU_VIEW_ORIGIN), true);
9568
p->set_item_checked(p->get_item_index(MENU_VIEW_GRID), true);
9569
9570
p->connect(SceneStringName(id_pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed));
9571
9572
/* REST OF MENU */
9573
9574
left_panel_split = memnew(HSplitContainer);
9575
left_panel_split->set_v_size_flags(SIZE_EXPAND_FILL);
9576
vbc->add_child(left_panel_split);
9577
9578
right_panel_split = memnew(HSplitContainer);
9579
right_panel_split->set_v_size_flags(SIZE_EXPAND_FILL);
9580
left_panel_split->add_child(right_panel_split);
9581
9582
shader_split = memnew(VSplitContainer);
9583
shader_split->set_h_size_flags(SIZE_EXPAND_FILL);
9584
right_panel_split->add_child(shader_split);
9585
viewport_base = memnew(Node3DEditorViewportContainer);
9586
shader_split->add_child(viewport_base);
9587
viewport_base->set_v_size_flags(SIZE_EXPAND_FILL);
9588
for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) {
9589
viewports[i] = memnew(Node3DEditorViewport(this, i));
9590
viewports[i]->connect("toggle_maximize_view", callable_mp(this, &Node3DEditor::_toggle_maximize_view));
9591
viewports[i]->connect("clicked", callable_mp(this, &Node3DEditor::_viewport_clicked).bind(i));
9592
viewports[i]->assign_pending_data_pointers(preview_node, &preview_bounds, accept);
9593
viewport_base->add_child(viewports[i]);
9594
}
9595
9596
/* SNAP DIALOG */
9597
9598
snap_dialog = memnew(ConfirmationDialog);
9599
snap_dialog->set_title(TTRC("Snap Settings"));
9600
add_child(snap_dialog);
9601
snap_dialog->connect(SceneStringName(confirmed), callable_mp(this, &Node3DEditor::_snap_changed));
9602
snap_dialog->get_cancel_button()->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_snap_update));
9603
9604
VBoxContainer *snap_dialog_vbc = memnew(VBoxContainer);
9605
snap_dialog->add_child(snap_dialog_vbc);
9606
9607
snap_translate = memnew(LineEdit);
9608
snap_translate->set_select_all_on_focus(true);
9609
snap_translate->set_accessibility_name(TTRC("Translate Snap:"));
9610
snap_dialog_vbc->add_margin_child(TTRC("Translate Snap:"), snap_translate);
9611
9612
snap_rotate = memnew(LineEdit);
9613
snap_rotate->set_select_all_on_focus(true);
9614
snap_rotate->set_accessibility_name(TTRC("Rotate Snap (deg.):"));
9615
snap_dialog_vbc->add_margin_child(TTRC("Rotate Snap (deg.):"), snap_rotate);
9616
9617
snap_scale = memnew(LineEdit);
9618
snap_scale->set_select_all_on_focus(true);
9619
snap_scale->set_accessibility_name(TTRC("Scale Snap (%):"));
9620
snap_dialog_vbc->add_margin_child(TTRC("Scale Snap (%):"), snap_scale);
9621
9622
/* SETTINGS DIALOG */
9623
9624
settings_dialog = memnew(ConfirmationDialog);
9625
settings_dialog->set_title(TTRC("Viewport Settings"));
9626
add_child(settings_dialog);
9627
settings_vbc = memnew(VBoxContainer);
9628
settings_vbc->set_custom_minimum_size(Size2(200, 0) * EDSCALE);
9629
settings_dialog->add_child(settings_vbc);
9630
9631
settings_fov = memnew(SpinBox);
9632
settings_fov->set_max(MAX_FOV);
9633
settings_fov->set_min(MIN_FOV);
9634
settings_fov->set_step(0.1);
9635
settings_fov->set_value(EDITOR_GET("editors/3d/default_fov"));
9636
settings_fov->set_select_all_on_focus(true);
9637
settings_fov->set_tooltip_text(TTRC("FOV is defined as a vertical value, as the editor camera always uses the Keep Height aspect mode."));
9638
settings_fov->set_accessibility_name(TTRC("Perspective VFOV (deg.):"));
9639
settings_vbc->add_margin_child(TTRC("Perspective VFOV (deg.):"), settings_fov);
9640
9641
settings_znear = memnew(SpinBox);
9642
settings_znear->set_max(MAX_Z);
9643
settings_znear->set_min(MIN_Z);
9644
settings_znear->set_step(0.01);
9645
settings_znear->set_accessibility_name(TTRC("View Z-Near:"));
9646
settings_znear->set_value(EDITOR_GET("editors/3d/default_z_near"));
9647
settings_znear->set_select_all_on_focus(true);
9648
settings_vbc->add_margin_child(TTRC("View Z-Near:"), settings_znear);
9649
9650
settings_zfar = memnew(SpinBox);
9651
settings_zfar->set_max(MAX_Z);
9652
settings_zfar->set_min(MIN_Z);
9653
settings_zfar->set_step(0.1);
9654
settings_zfar->set_accessibility_name(TTRC("View Z-Far:"));
9655
settings_zfar->set_value(EDITOR_GET("editors/3d/default_z_far"));
9656
settings_zfar->set_select_all_on_focus(true);
9657
settings_vbc->add_margin_child(TTRC("View Z-Far:"), settings_zfar);
9658
9659
for (uint32_t i = 0; i < VIEWPORTS_COUNT; ++i) {
9660
settings_dialog->connect(SceneStringName(confirmed), callable_mp(viewports[i], &Node3DEditorViewport::_view_settings_confirmed).bind(0.0));
9661
}
9662
9663
/* XFORM DIALOG */
9664
9665
xform_dialog = memnew(ConfirmationDialog);
9666
xform_dialog->set_title(TTRC("Transform Change"));
9667
add_child(xform_dialog);
9668
9669
VBoxContainer *xform_vbc = memnew(VBoxContainer);
9670
xform_dialog->add_child(xform_vbc);
9671
9672
Label *l = memnew(Label);
9673
l->set_text(TTRC("Translate:"));
9674
xform_vbc->add_child(l);
9675
9676
HBoxContainer *xform_hbc = memnew(HBoxContainer);
9677
xform_vbc->add_child(xform_hbc);
9678
9679
for (int i = 0; i < 3; i++) {
9680
xform_translate[i] = memnew(LineEdit);
9681
xform_translate[i]->set_h_size_flags(SIZE_EXPAND_FILL);
9682
xform_translate[i]->set_select_all_on_focus(true);
9683
xform_hbc->add_child(xform_translate[i]);
9684
}
9685
9686
l = memnew(Label);
9687
l->set_text(TTRC("Rotate (deg.):"));
9688
xform_vbc->add_child(l);
9689
9690
xform_hbc = memnew(HBoxContainer);
9691
xform_vbc->add_child(xform_hbc);
9692
9693
for (int i = 0; i < 3; i++) {
9694
xform_rotate[i] = memnew(LineEdit);
9695
xform_rotate[i]->set_h_size_flags(SIZE_EXPAND_FILL);
9696
xform_rotate[i]->set_select_all_on_focus(true);
9697
xform_hbc->add_child(xform_rotate[i]);
9698
}
9699
9700
l = memnew(Label);
9701
l->set_text(TTRC("Scale (ratio):"));
9702
xform_vbc->add_child(l);
9703
9704
xform_hbc = memnew(HBoxContainer);
9705
xform_vbc->add_child(xform_hbc);
9706
9707
for (int i = 0; i < 3; i++) {
9708
xform_scale[i] = memnew(LineEdit);
9709
xform_scale[i]->set_h_size_flags(SIZE_EXPAND_FILL);
9710
xform_scale[i]->set_select_all_on_focus(true);
9711
xform_hbc->add_child(xform_scale[i]);
9712
}
9713
9714
l = memnew(Label);
9715
l->set_text(TTRC("Transform Type"));
9716
xform_vbc->add_child(l);
9717
9718
xform_type = memnew(OptionButton);
9719
xform_type->set_h_size_flags(SIZE_EXPAND_FILL);
9720
xform_type->set_accessibility_name(TTRC("Transform Type"));
9721
xform_type->add_item(TTRC("Pre"));
9722
xform_type->add_item(TTRC("Post"));
9723
xform_vbc->add_child(xform_type);
9724
9725
xform_dialog->connect(SceneStringName(confirmed), callable_mp(this, &Node3DEditor::_xform_dialog_action));
9726
9727
selected = nullptr;
9728
9729
set_process_shortcut_input(true);
9730
add_to_group(SceneStringName(_spatial_editor_group));
9731
9732
current_hover_gizmo_handle = -1;
9733
current_hover_gizmo_handle_secondary = false;
9734
{
9735
// Sun/preview environment popup.
9736
sun_environ_popup = memnew(PreviewSunEnvPopup);
9737
add_child(sun_environ_popup);
9738
9739
HBoxContainer *sun_environ_hb = memnew(HBoxContainer);
9740
9741
sun_environ_popup->add_child(sun_environ_hb);
9742
9743
sun_vb = memnew(VBoxContainer);
9744
sun_environ_hb->add_child(sun_vb);
9745
sun_vb->set_custom_minimum_size(Size2(200 * EDSCALE, 0));
9746
sun_vb->hide();
9747
9748
sun_title = memnew(Label);
9749
sun_title->set_theme_type_variation("HeaderMedium");
9750
sun_vb->add_child(sun_title);
9751
sun_title->set_text(TTRC("Preview Sun"));
9752
sun_title->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
9753
9754
CenterContainer *sun_direction_center = memnew(CenterContainer);
9755
sun_direction = memnew(Control);
9756
sun_direction->set_custom_minimum_size(Size2(128, 128) * EDSCALE);
9757
sun_direction_center->add_child(sun_direction);
9758
sun_vb->add_margin_child(TTRC("Sun Direction"), sun_direction_center);
9759
sun_direction->connect(SceneStringName(gui_input), callable_mp(this, &Node3DEditor::_sun_direction_input));
9760
sun_direction->connect(SceneStringName(draw), callable_mp(this, &Node3DEditor::_sun_direction_draw));
9761
sun_direction->set_default_cursor_shape(CURSOR_MOVE);
9762
9763
sun_direction_shader.instantiate();
9764
sun_direction_shader->set_code(R"(
9765
// 3D editor Preview Sun direction shader.
9766
9767
shader_type canvas_item;
9768
9769
uniform vec3 sun_direction;
9770
uniform vec3 sun_color;
9771
9772
void fragment() {
9773
vec3 n;
9774
n.xy = UV * 2.0 - 1.0;
9775
n.z = sqrt(max(0.0, 1.0 - dot(n.xy, n.xy)));
9776
COLOR.rgb = dot(n, sun_direction) * sun_color;
9777
COLOR.a = 1.0 - smoothstep(0.99, 1.0, length(n.xy));
9778
}
9779
)");
9780
sun_direction_material.instantiate();
9781
sun_direction_material->set_shader(sun_direction_shader);
9782
sun_direction_material->set_shader_parameter("sun_direction", Vector3(0, 0, 1));
9783
sun_direction_material->set_shader_parameter("sun_color", Vector3(1, 1, 1));
9784
sun_direction->set_material(sun_direction_material);
9785
9786
HBoxContainer *sun_angle_hbox = memnew(HBoxContainer);
9787
sun_angle_hbox->set_h_size_flags(SIZE_EXPAND_FILL);
9788
VBoxContainer *sun_angle_altitude_vbox = memnew(VBoxContainer);
9789
sun_angle_altitude_vbox->set_h_size_flags(SIZE_EXPAND_FILL);
9790
Label *sun_angle_altitude_label = memnew(Label);
9791
sun_angle_altitude_label->set_text(TTRC("Angular Altitude"));
9792
sun_angle_altitude_vbox->add_child(sun_angle_altitude_label);
9793
sun_angle_altitude = memnew(EditorSpinSlider);
9794
sun_angle_altitude->set_suffix(U"\u00B0");
9795
sun_angle_altitude->set_max(90);
9796
sun_angle_altitude->set_min(-90);
9797
sun_angle_altitude->set_step(0.1);
9798
sun_angle_altitude->connect(SceneStringName(value_changed), callable_mp(this, &Node3DEditor::_sun_direction_set_altitude));
9799
sun_angle_altitude_vbox->add_child(sun_angle_altitude);
9800
sun_angle_hbox->add_child(sun_angle_altitude_vbox);
9801
VBoxContainer *sun_angle_azimuth_vbox = memnew(VBoxContainer);
9802
sun_angle_azimuth_vbox->set_h_size_flags(SIZE_EXPAND_FILL);
9803
sun_angle_azimuth_vbox->set_custom_minimum_size(Vector2(100, 0));
9804
Label *sun_angle_azimuth_label = memnew(Label);
9805
sun_angle_azimuth_label->set_text(TTRC("Azimuth"));
9806
sun_angle_azimuth_vbox->add_child(sun_angle_azimuth_label);
9807
sun_angle_azimuth = memnew(EditorSpinSlider);
9808
sun_angle_azimuth->set_suffix(U"\u00B0");
9809
sun_angle_azimuth->set_max(180);
9810
sun_angle_azimuth->set_min(-180);
9811
sun_angle_azimuth->set_step(0.1);
9812
sun_angle_azimuth->set_allow_greater(true);
9813
sun_angle_azimuth->set_allow_lesser(true);
9814
sun_angle_azimuth->connect(SceneStringName(value_changed), callable_mp(this, &Node3DEditor::_sun_direction_set_azimuth));
9815
sun_angle_azimuth_vbox->add_child(sun_angle_azimuth);
9816
sun_angle_hbox->add_child(sun_angle_azimuth_vbox);
9817
sun_angle_hbox->add_theme_constant_override("separation", 10);
9818
sun_vb->add_child(sun_angle_hbox);
9819
9820
sun_color = memnew(ColorPickerButton);
9821
sun_color->set_edit_alpha(false);
9822
sun_vb->add_margin_child(TTRC("Sun Color"), sun_color);
9823
sun_color->connect("color_changed", callable_mp(this, &Node3DEditor::_sun_set_color));
9824
sun_color->get_popup()->connect("about_to_popup", callable_mp(EditorNode::get_singleton(), &EditorNode::setup_color_picker).bind(sun_color->get_picker()));
9825
9826
sun_energy = memnew(EditorSpinSlider);
9827
sun_energy->set_max(64.0);
9828
sun_energy->set_min(0);
9829
sun_energy->set_step(0.05);
9830
sun_vb->add_margin_child(TTRC("Sun Energy"), sun_energy);
9831
sun_energy->connect(SceneStringName(value_changed), callable_mp(this, &Node3DEditor::_sun_set_energy));
9832
9833
sun_shadow_max_distance = memnew(EditorSpinSlider);
9834
sun_vb->add_margin_child(TTRC("Shadow Max Distance"), sun_shadow_max_distance);
9835
sun_shadow_max_distance->connect(SceneStringName(value_changed), callable_mp(this, &Node3DEditor::_sun_set_shadow_max_distance));
9836
sun_shadow_max_distance->set_min(1);
9837
sun_shadow_max_distance->set_max(4096);
9838
9839
sun_add_to_scene = memnew(Button);
9840
sun_add_to_scene->set_text(TTRC("Add Sun to Scene"));
9841
sun_add_to_scene->set_tooltip_text(TTRC("Adds a DirectionalLight3D node matching the preview sun settings to the current scene.\nHold Shift while clicking to also add the preview environment to the current scene."));
9842
sun_add_to_scene->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_add_sun_to_scene).bind(false));
9843
sun_vb->add_spacer();
9844
sun_vb->add_child(sun_add_to_scene);
9845
9846
sun_state = memnew(Label);
9847
sun_environ_hb->add_child(sun_state);
9848
sun_state->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
9849
sun_state->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
9850
sun_state->set_h_size_flags(SIZE_EXPAND_FILL);
9851
9852
VSeparator *sc = memnew(VSeparator);
9853
sc->set_custom_minimum_size(Size2(10 * EDSCALE, 0));
9854
sc->set_v_size_flags(SIZE_EXPAND_FILL);
9855
sun_environ_hb->add_child(sc);
9856
9857
environ_vb = memnew(VBoxContainer);
9858
sun_environ_hb->add_child(environ_vb);
9859
environ_vb->set_custom_minimum_size(Size2(200 * EDSCALE, 0));
9860
environ_vb->hide();
9861
9862
environ_title = memnew(Label);
9863
environ_title->set_theme_type_variation("HeaderMedium");
9864
9865
environ_vb->add_child(environ_title);
9866
environ_title->set_text(TTRC("Preview Environment"));
9867
environ_title->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
9868
9869
environ_sky_color = memnew(ColorPickerButton);
9870
environ_sky_color->set_edit_alpha(false);
9871
environ_sky_color->connect("color_changed", callable_mp(this, &Node3DEditor::_environ_set_sky_color));
9872
environ_sky_color->get_popup()->connect("about_to_popup", callable_mp(EditorNode::get_singleton(), &EditorNode::setup_color_picker).bind(environ_sky_color->get_picker()));
9873
environ_vb->add_margin_child(TTRC("Sky Color"), environ_sky_color);
9874
environ_ground_color = memnew(ColorPickerButton);
9875
environ_ground_color->connect("color_changed", callable_mp(this, &Node3DEditor::_environ_set_ground_color));
9876
environ_ground_color->set_edit_alpha(false);
9877
environ_ground_color->get_popup()->connect("about_to_popup", callable_mp(EditorNode::get_singleton(), &EditorNode::setup_color_picker).bind(environ_ground_color->get_picker()));
9878
environ_vb->add_margin_child(TTRC("Ground Color"), environ_ground_color);
9879
environ_energy = memnew(EditorSpinSlider);
9880
environ_energy->set_max(8.0);
9881
environ_energy->set_min(0);
9882
environ_energy->set_step(0.05);
9883
environ_energy->connect(SceneStringName(value_changed), callable_mp(this, &Node3DEditor::_environ_set_sky_energy));
9884
environ_vb->add_margin_child(TTRC("Sky Energy"), environ_energy);
9885
HBoxContainer *fx_vb = memnew(HBoxContainer);
9886
fx_vb->set_h_size_flags(SIZE_EXPAND_FILL);
9887
9888
environ_ao_button = memnew(Button);
9889
environ_ao_button->set_text(TTRC("AO"));
9890
environ_ao_button->set_h_size_flags(SIZE_EXPAND_FILL);
9891
environ_ao_button->set_toggle_mode(true);
9892
environ_ao_button->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_environ_set_ao), CONNECT_DEFERRED);
9893
fx_vb->add_child(environ_ao_button);
9894
environ_glow_button = memnew(Button);
9895
environ_glow_button->set_text(TTRC("Glow"));
9896
environ_glow_button->set_h_size_flags(SIZE_EXPAND_FILL);
9897
environ_glow_button->set_toggle_mode(true);
9898
environ_glow_button->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_environ_set_glow), CONNECT_DEFERRED);
9899
fx_vb->add_child(environ_glow_button);
9900
environ_tonemap_button = memnew(Button);
9901
environ_tonemap_button->set_text(TTRC("Tonemap"));
9902
environ_tonemap_button->set_h_size_flags(SIZE_EXPAND_FILL);
9903
environ_tonemap_button->set_toggle_mode(true);
9904
environ_tonemap_button->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_environ_set_tonemap), CONNECT_DEFERRED);
9905
fx_vb->add_child(environ_tonemap_button);
9906
environ_gi_button = memnew(Button);
9907
environ_gi_button->set_text(TTRC("GI"));
9908
environ_gi_button->set_h_size_flags(SIZE_EXPAND_FILL);
9909
environ_gi_button->set_toggle_mode(true);
9910
environ_gi_button->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_environ_set_gi), CONNECT_DEFERRED);
9911
fx_vb->add_child(environ_gi_button);
9912
environ_vb->add_margin_child(TTRC("Post Process"), fx_vb);
9913
9914
environ_add_to_scene = memnew(Button);
9915
environ_add_to_scene->set_text(TTRC("Add Environment to Scene"));
9916
environ_add_to_scene->set_tooltip_text(TTRC("Adds a WorldEnvironment node matching the preview environment settings to the current scene.\nHold Shift while clicking to also add the preview sun to the current scene."));
9917
environ_add_to_scene->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_add_environment_to_scene).bind(false));
9918
environ_vb->add_spacer();
9919
environ_vb->add_child(environ_add_to_scene);
9920
9921
environ_state = memnew(Label);
9922
sun_environ_hb->add_child(environ_state);
9923
environ_state->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
9924
environ_state->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
9925
environ_state->set_h_size_flags(SIZE_EXPAND_FILL);
9926
9927
preview_sun = memnew(DirectionalLight3D);
9928
preview_sun->set_shadow(true);
9929
preview_sun->set_shadow_mode(DirectionalLight3D::SHADOW_PARALLEL_4_SPLITS);
9930
preview_environment = memnew(WorldEnvironment);
9931
environment.instantiate();
9932
preview_environment->set_environment(environment);
9933
if (GLOBAL_GET("rendering/lights_and_shadows/use_physical_light_units")) {
9934
camera_attributes.instantiate();
9935
preview_environment->set_camera_attributes(camera_attributes);
9936
}
9937
Ref<Sky> sky;
9938
sky.instantiate();
9939
sky_material.instantiate();
9940
sky->set_material(sky_material);
9941
environment->set_sky(sky);
9942
environment->set_background(Environment::BG_SKY);
9943
9944
sun_environ_popup->set_process_shortcut_input(true);
9945
9946
_load_default_preview_settings();
9947
_preview_settings_changed();
9948
}
9949
clear(); // Make sure values are initialized. Will call _snap_update() for us.
9950
}
9951
Node3DEditor::~Node3DEditor() {
9952
singleton = nullptr;
9953
memdelete(preview_node);
9954
if (preview_sun_dangling && preview_sun) {
9955
memdelete(preview_sun);
9956
}
9957
if (preview_env_dangling && preview_environment) {
9958
memdelete(preview_environment);
9959
}
9960
}
9961
9962
void Node3DEditorPlugin::make_visible(bool p_visible) {
9963
if (p_visible) {
9964
spatial_editor->show();
9965
spatial_editor->set_process(true);
9966
spatial_editor->set_physics_process(true);
9967
spatial_editor->refresh_dirty_gizmos();
9968
} else {
9969
spatial_editor->hide();
9970
spatial_editor->set_process(false);
9971
spatial_editor->set_physics_process(false);
9972
}
9973
}
9974
9975
void Node3DEditorPlugin::edit(Object *p_object) {
9976
spatial_editor->edit(Object::cast_to<Node3D>(p_object));
9977
}
9978
9979
bool Node3DEditorPlugin::handles(Object *p_object) const {
9980
return p_object->is_class("Node3D");
9981
}
9982
9983
Dictionary Node3DEditorPlugin::get_state() const {
9984
return spatial_editor->get_state();
9985
}
9986
9987
void Node3DEditorPlugin::set_state(const Dictionary &p_state) {
9988
spatial_editor->set_state(p_state);
9989
}
9990
9991
Size2i Node3DEditor::get_camera_viewport_size(Camera3D *p_camera) {
9992
Viewport *viewport = p_camera->get_viewport();
9993
9994
Window *window = Object::cast_to<Window>(viewport);
9995
if (window) {
9996
return window->get_size();
9997
}
9998
9999
SubViewport *sub_viewport = Object::cast_to<SubViewport>(viewport);
10000
ERR_FAIL_NULL_V(sub_viewport, Size2i());
10001
10002
if (sub_viewport == EditorNode::get_singleton()->get_scene_root()) {
10003
return Size2(GLOBAL_GET("display/window/size/viewport_width"), GLOBAL_GET("display/window/size/viewport_height"));
10004
}
10005
10006
return sub_viewport->get_size();
10007
}
10008
10009
Vector3 Node3DEditor::snap_point(Vector3 p_target, Vector3 p_start) const {
10010
if (is_snap_enabled()) {
10011
real_t snap = get_translate_snap();
10012
p_target.snapf(snap);
10013
}
10014
return p_target;
10015
}
10016
10017
bool Node3DEditor::is_gizmo_visible() const {
10018
if (selected) {
10019
return gizmo.visible && selected->is_transform_gizmo_visible();
10020
}
10021
return gizmo.visible;
10022
}
10023
10024
real_t Node3DEditor::get_translate_snap() const {
10025
real_t snap_value = snap_translate_value;
10026
if (Input::get_singleton()->is_key_pressed(Key::SHIFT)) {
10027
snap_value /= 10.0f;
10028
}
10029
return snap_value;
10030
}
10031
10032
real_t Node3DEditor::get_rotate_snap() const {
10033
real_t snap_value = snap_rotate_value;
10034
if (Input::get_singleton()->is_key_pressed(Key::SHIFT)) {
10035
snap_value /= 3.0f;
10036
}
10037
return snap_value;
10038
}
10039
10040
real_t Node3DEditor::get_scale_snap() const {
10041
real_t snap_value = snap_scale_value;
10042
if (Input::get_singleton()->is_key_pressed(Key::SHIFT)) {
10043
snap_value /= 2.0f;
10044
}
10045
return snap_value;
10046
}
10047
10048
struct _GizmoPluginPriorityComparator {
10049
bool operator()(const Ref<EditorNode3DGizmoPlugin> &p_a, const Ref<EditorNode3DGizmoPlugin> &p_b) const {
10050
if (p_a->get_priority() == p_b->get_priority()) {
10051
return p_a->get_gizmo_name() < p_b->get_gizmo_name();
10052
}
10053
return p_a->get_priority() > p_b->get_priority();
10054
}
10055
};
10056
10057
struct _GizmoPluginNameComparator {
10058
bool operator()(const Ref<EditorNode3DGizmoPlugin> &p_a, const Ref<EditorNode3DGizmoPlugin> &p_b) const {
10059
return p_a->get_gizmo_name() < p_b->get_gizmo_name();
10060
}
10061
};
10062
10063
void Node3DEditor::add_gizmo_plugin(Ref<EditorNode3DGizmoPlugin> p_plugin) {
10064
ERR_FAIL_COND(p_plugin.is_null());
10065
10066
gizmo_plugins_by_priority.push_back(p_plugin);
10067
gizmo_plugins_by_priority.sort_custom<_GizmoPluginPriorityComparator>();
10068
10069
gizmo_plugins_by_name.push_back(p_plugin);
10070
gizmo_plugins_by_name.sort_custom<_GizmoPluginNameComparator>();
10071
10072
_update_gizmos_menu();
10073
}
10074
10075
void Node3DEditor::remove_gizmo_plugin(Ref<EditorNode3DGizmoPlugin> p_plugin) {
10076
gizmo_plugins_by_priority.erase(p_plugin);
10077
gizmo_plugins_by_name.erase(p_plugin);
10078
_update_gizmos_menu();
10079
}
10080
10081
DynamicBVH::ID Node3DEditor::insert_gizmo_bvh_node(Node3D *p_node, const AABB &p_aabb) {
10082
return gizmo_bvh.insert(p_aabb, p_node);
10083
}
10084
10085
void Node3DEditor::update_gizmo_bvh_node(DynamicBVH::ID p_id, const AABB &p_aabb) {
10086
gizmo_bvh.update(p_id, p_aabb);
10087
gizmo_bvh.optimize_incremental(1);
10088
}
10089
10090
void Node3DEditor::remove_gizmo_bvh_node(DynamicBVH::ID p_id) {
10091
gizmo_bvh.remove(p_id);
10092
}
10093
10094
Vector<Node3D *> Node3DEditor::gizmo_bvh_ray_query(const Vector3 &p_ray_start, const Vector3 &p_ray_end) {
10095
struct Result {
10096
Vector<Node3D *> nodes;
10097
bool operator()(void *p_data) {
10098
nodes.append((Node3D *)p_data);
10099
return false;
10100
}
10101
} result;
10102
10103
gizmo_bvh.ray_query(p_ray_start, p_ray_end, result);
10104
10105
return result.nodes;
10106
}
10107
10108
Vector<Node3D *> Node3DEditor::gizmo_bvh_frustum_query(const Vector<Plane> &p_frustum) {
10109
Vector<Vector3> points = Geometry3D::compute_convex_mesh_points(&p_frustum[0], p_frustum.size());
10110
10111
struct Result {
10112
Vector<Node3D *> nodes;
10113
bool operator()(void *p_data) {
10114
nodes.append((Node3D *)p_data);
10115
return false;
10116
}
10117
} result;
10118
10119
gizmo_bvh.convex_query(p_frustum.ptr(), p_frustum.size(), points.ptr(), points.size(), result);
10120
10121
return result.nodes;
10122
}
10123
10124
Node3DEditorPlugin::Node3DEditorPlugin() {
10125
spatial_editor = memnew(Node3DEditor);
10126
spatial_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL);
10127
EditorNode::get_singleton()->get_editor_main_screen()->get_control()->add_child(spatial_editor);
10128
10129
spatial_editor->hide();
10130
}
10131
10132