eb00a8546d
Splits `Node3DEditorViewport` into a dedicated file. The main `Node3DEditorPlugin` file was one if the biggest files of the entire engine. It was getting way too big to be viewed, parsed and displayed effectively (e.g. GitHub, certain IDEs). While still big after this PR it at least splits it more or less in half. - Moves `Node3DEditorViewport` and related helper classes to its own file `node_3d_editor_viewport` file. - Moves the (shared) constexpr to its own file `node_3d_editor_constants` file. - Forward declares many more classes. - Removes unnecessary includes. - Fixes classes that needed the actual Node3DViewport class.
4127 lines
167 KiB
C++
4127 lines
167 KiB
C++
/**************************************************************************/
|
|
/* node_3d_editor_plugin.cpp */
|
|
/**************************************************************************/
|
|
/* This file is part of: */
|
|
/* GODOT ENGINE */
|
|
/* https://godotengine.org */
|
|
/**************************************************************************/
|
|
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
|
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
|
/* */
|
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
|
/* a copy of this software and associated documentation files (the */
|
|
/* "Software"), to deal in the Software without restriction, including */
|
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
|
/* the following conditions: */
|
|
/* */
|
|
/* The above copyright notice and this permission notice shall be */
|
|
/* included in all copies or substantial portions of the Software. */
|
|
/* */
|
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
/**************************************************************************/
|
|
|
|
#include "node_3d_editor_plugin.h"
|
|
|
|
#include "core/config/project_settings.h"
|
|
#include "core/input/input.h"
|
|
#include "core/math/geometry_3d.h"
|
|
#include "core/math/math_funcs.h"
|
|
#include "core/math/projection.h"
|
|
#include "core/object/callable_mp.h"
|
|
#include "core/object/class_db.h"
|
|
#include "core/os/keyboard.h"
|
|
#include "core/os/os.h"
|
|
#include "editor/debugger/editor_debugger_node.h"
|
|
#include "editor/docks/scene_tree_dock.h"
|
|
#include "editor/editor_main_screen.h"
|
|
#include "editor/editor_node.h"
|
|
#include "editor/editor_string_names.h"
|
|
#include "editor/editor_undo_redo_manager.h"
|
|
#include "editor/gui/editor_spin_slider.h"
|
|
#include "editor/plugins/editor_plugin_list.h"
|
|
#include "editor/run/editor_run_bar.h"
|
|
#include "editor/scene/3d/gizmos/audio_listener_3d_gizmo_plugin.h"
|
|
#include "editor/scene/3d/gizmos/audio_stream_player_3d_gizmo_plugin.h"
|
|
#include "editor/scene/3d/gizmos/camera_3d_gizmo_plugin.h"
|
|
#include "editor/scene/3d/gizmos/chain_ik_3d_gizmo_plugin.h"
|
|
#include "editor/scene/3d/gizmos/cpu_particles_3d_gizmo_plugin.h"
|
|
#include "editor/scene/3d/gizmos/decal_gizmo_plugin.h"
|
|
#include "editor/scene/3d/gizmos/fog_volume_gizmo_plugin.h"
|
|
#include "editor/scene/3d/gizmos/geometry_instance_3d_gizmo_plugin.h"
|
|
#include "editor/scene/3d/gizmos/gpu_particles_3d_gizmo_plugin.h"
|
|
#include "editor/scene/3d/gizmos/gpu_particles_collision_3d_gizmo_plugin.h"
|
|
#include "editor/scene/3d/gizmos/label_3d_gizmo_plugin.h"
|
|
#include "editor/scene/3d/gizmos/light_3d_gizmo_plugin.h"
|
|
#include "editor/scene/3d/gizmos/lightmap_gi_gizmo_plugin.h"
|
|
#include "editor/scene/3d/gizmos/lightmap_probe_gizmo_plugin.h"
|
|
#include "editor/scene/3d/gizmos/marker_3d_gizmo_plugin.h"
|
|
#include "editor/scene/3d/gizmos/mesh_instance_3d_gizmo_plugin.h"
|
|
#include "editor/scene/3d/gizmos/occluder_instance_3d_gizmo_plugin.h"
|
|
#include "editor/scene/3d/gizmos/particles_3d_emission_shape_gizmo_plugin.h"
|
|
#include "editor/scene/3d/gizmos/physics/collision_object_3d_gizmo_plugin.h"
|
|
#include "editor/scene/3d/gizmos/physics/collision_polygon_3d_gizmo_plugin.h"
|
|
#include "editor/scene/3d/gizmos/physics/collision_shape_3d_gizmo_plugin.h"
|
|
#include "editor/scene/3d/gizmos/physics/joint_3d_gizmo_plugin.h"
|
|
#include "editor/scene/3d/gizmos/physics/physics_bone_3d_gizmo_plugin.h"
|
|
#include "editor/scene/3d/gizmos/physics/ray_cast_3d_gizmo_plugin.h"
|
|
#include "editor/scene/3d/gizmos/physics/shape_cast_3d_gizmo_plugin.h"
|
|
#include "editor/scene/3d/gizmos/physics/soft_body_3d_gizmo_plugin.h"
|
|
#include "editor/scene/3d/gizmos/physics/spring_arm_3d_gizmo_plugin.h"
|
|
#include "editor/scene/3d/gizmos/physics/vehicle_body_3d_gizmo_plugin.h"
|
|
#include "editor/scene/3d/gizmos/reflection_probe_gizmo_plugin.h"
|
|
#include "editor/scene/3d/gizmos/spring_bone_3d_gizmo_plugin.h"
|
|
#include "editor/scene/3d/gizmos/sprite_base_3d_gizmo_plugin.h"
|
|
#include "editor/scene/3d/gizmos/two_bone_ik_3d_gizmo_plugin.h"
|
|
#include "editor/scene/3d/gizmos/visible_on_screen_notifier_3d_gizmo_plugin.h"
|
|
#include "editor/scene/3d/gizmos/voxel_gi_gizmo_plugin.h"
|
|
#include "editor/scene/3d/node_3d_editor_constants.h"
|
|
#include "editor/scene/3d/node_3d_editor_gizmos.h"
|
|
#include "editor/scene/3d/node_3d_editor_viewport.h"
|
|
#include "editor/settings/editor_settings.h"
|
|
#include "editor/translations/editor_translation_preview_menu.h"
|
|
#include "scene/3d/camera_3d.h"
|
|
#include "scene/3d/light_3d.h"
|
|
#include "scene/3d/physics/collision_shape_3d.h"
|
|
#include "scene/3d/physics/physics_body_3d.h"
|
|
#include "scene/3d/world_environment.h"
|
|
#include "scene/gui/button.h"
|
|
#include "scene/gui/center_container.h"
|
|
#include "scene/gui/color_picker.h"
|
|
#include "scene/gui/flow_container.h"
|
|
#include "scene/gui/menu_button.h"
|
|
#include "scene/gui/rich_text_label.h"
|
|
#include "scene/gui/separator.h"
|
|
#include "scene/gui/spin_box.h"
|
|
#include "scene/gui/split_container.h"
|
|
#include "scene/main/scene_tree.h"
|
|
#include "scene/resources/3d/sky_material.h"
|
|
#include "scene/resources/sky.h"
|
|
#include "scene/resources/surface_tool.h"
|
|
#include "servers/rendering/rendering_server.h"
|
|
|
|
using namespace Node3DEditorConstants;
|
|
|
|
///////////////////////////////////////////////////////////////////
|
|
|
|
Node3DEditor *Node3DEditor::singleton = nullptr;
|
|
|
|
void Node3DEditor::select_gizmo_highlight_axis(int p_axis) {
|
|
for (int i = 0; i < 3; i++) {
|
|
move_gizmo[i]->surface_set_material(0, i == p_axis ? gizmo_color_hl[i] : gizmo_color[i]);
|
|
move_plane_gizmo[i]->surface_set_material(0, (i + 6) == p_axis ? plane_gizmo_color_hl[i] : plane_gizmo_color[i]);
|
|
scale_gizmo[i]->surface_set_material(0, (i + 9) == p_axis ? gizmo_color_hl[i] : gizmo_color[i]);
|
|
scale_plane_gizmo[i]->surface_set_material(0, (i + 12) == p_axis ? plane_gizmo_color_hl[i] : plane_gizmo_color[i]);
|
|
}
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
bool highlight;
|
|
if (i == 3) {
|
|
highlight = (p_axis == GIZMO_HIGHLIGHT_AXIS_VIEW_ROTATION);
|
|
} else {
|
|
highlight = (i + 3) == p_axis;
|
|
}
|
|
rotate_gizmo[i]->surface_set_material(0, highlight ? rotate_gizmo_color_hl[i] : rotate_gizmo_color[i]);
|
|
}
|
|
|
|
bool highlight_trackball = (p_axis == GIZMO_HIGHLIGHT_AXIS_TRACKBALL);
|
|
trackball_sphere_gizmo->surface_set_material(0, highlight_trackball ? trackball_sphere_material_hl : trackball_sphere_material);
|
|
}
|
|
|
|
void Node3DEditor::update_transform_gizmo() {
|
|
int count = 0;
|
|
bool local_gizmo_coords = are_local_coords_enabled();
|
|
|
|
Vector3 gizmo_center;
|
|
Basis gizmo_basis;
|
|
|
|
Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr;
|
|
|
|
if (se && se->gizmo.is_valid()) {
|
|
for (const KeyValue<int, Transform3D> &E : se->subgizmos) {
|
|
Transform3D xf = se->sp->get_global_transform() * se->gizmo->get_subgizmo_transform(E.key);
|
|
if (!xf.is_finite()) {
|
|
continue;
|
|
}
|
|
gizmo_center += xf.origin;
|
|
if ((unsigned int)count == se->subgizmos.size() - 1 && local_gizmo_coords) {
|
|
gizmo_basis = xf.basis;
|
|
}
|
|
count++;
|
|
}
|
|
} else {
|
|
const List<Node *> &selection = editor_selection->get_top_selected_node_list();
|
|
for (Node *E : selection) {
|
|
Node3D *sp = Object::cast_to<Node3D>(E);
|
|
if (!sp) {
|
|
continue;
|
|
}
|
|
|
|
if (sp->has_meta("_edit_lock_")) {
|
|
continue;
|
|
}
|
|
|
|
Node3DEditorSelectedItem *sel_item = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);
|
|
if (!sel_item) {
|
|
continue;
|
|
}
|
|
|
|
Transform3D xf = sel_item->sp->get_global_transform();
|
|
if (!xf.is_finite()) {
|
|
continue;
|
|
}
|
|
gizmo_center += xf.origin;
|
|
if (count == selection.size() - 1 && local_gizmo_coords) {
|
|
gizmo_basis = xf.basis;
|
|
}
|
|
count++;
|
|
}
|
|
}
|
|
|
|
gizmo.visible = count > 0;
|
|
gizmo.transform.origin = (count > 0) ? gizmo_center / count : Vector3();
|
|
gizmo.transform.basis = gizmo_basis;
|
|
|
|
for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) {
|
|
viewports[i]->update_transform_gizmo_view();
|
|
}
|
|
}
|
|
|
|
void _update_all_gizmos(Node *p_node) {
|
|
for (int i = p_node->get_child_count() - 1; 0 <= i; --i) {
|
|
Node3D *spatial_node = Object::cast_to<Node3D>(p_node->get_child(i));
|
|
if (spatial_node) {
|
|
spatial_node->update_gizmos();
|
|
}
|
|
|
|
_update_all_gizmos(p_node->get_child(i));
|
|
}
|
|
}
|
|
|
|
void Node3DEditor::update_all_gizmos(Node *p_node) {
|
|
if (!p_node && is_inside_tree()) {
|
|
p_node = get_tree()->get_edited_scene_root();
|
|
}
|
|
|
|
if (!p_node) {
|
|
// No edited scene, so nothing to update.
|
|
return;
|
|
}
|
|
_update_all_gizmos(p_node);
|
|
}
|
|
|
|
Object *Node3DEditor::_get_editor_data(Object *p_what) {
|
|
Node3D *sp = Object::cast_to<Node3D>(p_what);
|
|
if (!sp) {
|
|
return nullptr;
|
|
}
|
|
|
|
Node3DEditorSelectedItem *si = memnew(Node3DEditorSelectedItem);
|
|
|
|
si->sp = sp;
|
|
si->sbox_instance = RenderingServer::get_singleton()->instance_create2(
|
|
selection_box->get_rid(),
|
|
sp->get_world_3d()->get_scenario());
|
|
si->sbox_instance_offset = RenderingServer::get_singleton()->instance_create2(
|
|
selection_box->get_rid(),
|
|
sp->get_world_3d()->get_scenario());
|
|
RS::get_singleton()->instance_geometry_set_cast_shadows_setting(
|
|
si->sbox_instance,
|
|
RSE::SHADOW_CASTING_SETTING_OFF);
|
|
RS::get_singleton()->instance_geometry_set_cast_shadows_setting(
|
|
si->sbox_instance_offset,
|
|
RSE::SHADOW_CASTING_SETTING_OFF);
|
|
// Use the Edit layer to hide the selection box when View Gizmos is disabled, since it is a bit distracting.
|
|
// It's still possible to approximately guess what is selected by looking at the manipulation gizmo position.
|
|
RS::get_singleton()->instance_set_layer_mask(si->sbox_instance, 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER);
|
|
RS::get_singleton()->instance_set_layer_mask(si->sbox_instance_offset, 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER);
|
|
RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance, RSE::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);
|
|
RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance, RSE::INSTANCE_FLAG_USE_BAKED_LIGHT, false);
|
|
RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance_offset, RSE::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);
|
|
RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance_offset, RSE::INSTANCE_FLAG_USE_BAKED_LIGHT, false);
|
|
si->sbox_instance_xray = RenderingServer::get_singleton()->instance_create2(
|
|
selection_box_xray->get_rid(),
|
|
sp->get_world_3d()->get_scenario());
|
|
si->sbox_instance_xray_offset = RenderingServer::get_singleton()->instance_create2(
|
|
selection_box_xray->get_rid(),
|
|
sp->get_world_3d()->get_scenario());
|
|
RS::get_singleton()->instance_geometry_set_cast_shadows_setting(
|
|
si->sbox_instance_xray,
|
|
RSE::SHADOW_CASTING_SETTING_OFF);
|
|
RS::get_singleton()->instance_geometry_set_cast_shadows_setting(
|
|
si->sbox_instance_xray_offset,
|
|
RSE::SHADOW_CASTING_SETTING_OFF);
|
|
// Use the Edit layer to hide the selection box when View Gizmos is disabled, since it is a bit distracting.
|
|
// It's still possible to approximately guess what is selected by looking at the manipulation gizmo position.
|
|
RS::get_singleton()->instance_set_layer_mask(si->sbox_instance_xray, 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER);
|
|
RS::get_singleton()->instance_set_layer_mask(si->sbox_instance_xray_offset, 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER);
|
|
RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance_xray, RSE::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);
|
|
RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance_xray, RSE::INSTANCE_FLAG_USE_BAKED_LIGHT, false);
|
|
RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance_xray_offset, RSE::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);
|
|
RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance_xray_offset, RSE::INSTANCE_FLAG_USE_BAKED_LIGHT, false);
|
|
|
|
return si;
|
|
}
|
|
|
|
void Node3DEditor::_generate_selection_boxes() {
|
|
// Use two AABBs to create the illusion of a slightly thicker line.
|
|
AABB aabb(Vector3(), Vector3(1, 1, 1));
|
|
|
|
// Create a x-ray (visible through solid surfaces) and standard version of the selection box.
|
|
// Both will be drawn at the same position, but with different opacity.
|
|
// This lets the user see where the selection is while still having a sense of depth.
|
|
Ref<SurfaceTool> st = memnew(SurfaceTool);
|
|
Ref<SurfaceTool> st_xray = memnew(SurfaceTool);
|
|
Ref<SurfaceTool> active_st = memnew(SurfaceTool);
|
|
Ref<SurfaceTool> active_st_xray = memnew(SurfaceTool);
|
|
|
|
st->begin(Mesh::PRIMITIVE_LINES);
|
|
st_xray->begin(Mesh::PRIMITIVE_LINES);
|
|
active_st->begin(Mesh::PRIMITIVE_LINES);
|
|
active_st_xray->begin(Mesh::PRIMITIVE_LINES);
|
|
for (int i = 0; i < 12; i++) {
|
|
Vector3 a, b;
|
|
aabb.get_edge(i, a, b);
|
|
|
|
st->add_vertex(a);
|
|
st->add_vertex(b);
|
|
active_st->add_vertex(a);
|
|
active_st->add_vertex(b);
|
|
st_xray->add_vertex(a);
|
|
st_xray->add_vertex(b);
|
|
active_st_xray->add_vertex(a);
|
|
active_st_xray->add_vertex(b);
|
|
}
|
|
|
|
const Color selection_box_color = EDITOR_GET("editors/3d/selection_box_color");
|
|
const Color active_selection_box_color = EDITOR_GET("editors/3d/active_selection_box_color");
|
|
|
|
selection_box_mat.instantiate();
|
|
selection_box_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
|
|
selection_box_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
|
|
selection_box_mat->set_albedo(selection_box_color);
|
|
selection_box_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
|
|
st->set_material(selection_box_mat);
|
|
selection_box = st->commit();
|
|
|
|
selection_box_mat_xray.instantiate();
|
|
selection_box_mat_xray->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
|
|
selection_box_mat_xray->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
|
|
selection_box_mat_xray->set_flag(StandardMaterial3D::FLAG_DISABLE_DEPTH_TEST, true);
|
|
selection_box_mat_xray->set_albedo(selection_box_color * Color(1, 1, 1, 0.15));
|
|
selection_box_mat_xray->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
|
|
st_xray->set_material(selection_box_mat_xray);
|
|
selection_box_xray = st_xray->commit();
|
|
|
|
active_selection_box_mat.instantiate();
|
|
active_selection_box_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
|
|
active_selection_box_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
|
|
active_selection_box_mat->set_albedo(active_selection_box_color);
|
|
active_selection_box_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
|
|
active_st->set_material(active_selection_box_mat);
|
|
active_selection_box = active_st->commit();
|
|
|
|
active_selection_box_mat_xray.instantiate();
|
|
active_selection_box_mat_xray->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
|
|
active_selection_box_mat_xray->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
|
|
active_selection_box_mat_xray->set_flag(StandardMaterial3D::FLAG_DISABLE_DEPTH_TEST, true);
|
|
active_selection_box_mat_xray->set_albedo(active_selection_box_color * Color(1, 1, 1, 0.15));
|
|
active_selection_box_mat_xray->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
|
|
active_st_xray->set_material(active_selection_box_mat_xray);
|
|
active_selection_box_xray = active_st_xray->commit();
|
|
}
|
|
|
|
Dictionary Node3DEditor::get_state() const {
|
|
Dictionary d;
|
|
|
|
d["snap_enabled"] = snap_enabled;
|
|
d["trackball_enabled"] = trackball_enabled;
|
|
d["translate_snap"] = snap_translate_value;
|
|
d["rotate_snap"] = snap_rotate_value;
|
|
d["scale_snap"] = snap_scale_value;
|
|
|
|
d["local_coords"] = tool_option_button[TOOL_OPT_LOCAL_COORDS]->is_pressed();
|
|
d["preserve_children_transform"] = tool_option_button[TOOL_OPT_PRESERVE_CHILDREN_TRANSFORM]->is_pressed();
|
|
|
|
int vc = 0;
|
|
if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT))) {
|
|
vc = 1;
|
|
} else if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS))) {
|
|
vc = 2;
|
|
} else if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS))) {
|
|
vc = 3;
|
|
} else if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS))) {
|
|
vc = 4;
|
|
} else if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT))) {
|
|
vc = 5;
|
|
} else if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT))) {
|
|
vc = 6;
|
|
}
|
|
|
|
d["viewport_mode"] = vc;
|
|
d["viewport_splits"] = viewport_base->get_split_state();
|
|
Array vpdata;
|
|
for (int i = 0; i < 4; i++) {
|
|
vpdata.push_back(viewports[i]->get_state());
|
|
}
|
|
|
|
d["viewports"] = vpdata;
|
|
|
|
d["vertex_snap_origin_mode"] = vertex_snap_origin_mode;
|
|
d["vertex_snap_use_collision"] = vertex_snap_use_collision;
|
|
d["show_grid"] = view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_GRID));
|
|
d["show_origin"] = view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_ORIGIN));
|
|
d["fov"] = get_fov();
|
|
d["znear"] = get_znear();
|
|
d["zfar"] = get_zfar();
|
|
|
|
Dictionary gizmos_status;
|
|
for (int i = 0; i < gizmo_plugins_by_name.size(); i++) {
|
|
if (!gizmo_plugins_by_name[i]->can_be_hidden()) {
|
|
continue;
|
|
}
|
|
int state = gizmos_menu->get_item_state(gizmos_menu->get_item_index(i));
|
|
String name = gizmo_plugins_by_name[i]->get_gizmo_name();
|
|
gizmos_status[name] = state;
|
|
}
|
|
|
|
d["gizmos_status"] = gizmos_status;
|
|
{
|
|
Dictionary pd;
|
|
|
|
pd["sun_rotation"] = sun_rotation;
|
|
|
|
pd["environ_sky_color"] = environ_sky_color->get_pick_color();
|
|
pd["environ_ground_color"] = environ_ground_color->get_pick_color();
|
|
pd["environ_energy"] = environ_energy->get_value();
|
|
pd["environ_glow_enabled"] = environ_glow_button->is_pressed();
|
|
pd["environ_tonemap_enabled"] = environ_tonemap_button->is_pressed();
|
|
pd["environ_ao_enabled"] = environ_ao_button->is_pressed();
|
|
pd["environ_gi_enabled"] = environ_gi_button->is_pressed();
|
|
pd["sun_shadow_max_distance"] = sun_shadow_max_distance->get_value();
|
|
|
|
pd["sun_color"] = sun_color->get_pick_color();
|
|
pd["sun_energy"] = sun_energy->get_value();
|
|
|
|
pd["sun_enabled"] = sun_button->is_pressed();
|
|
pd["environ_enabled"] = environ_button->is_pressed();
|
|
|
|
d["preview_sun_env"] = pd;
|
|
}
|
|
|
|
return d;
|
|
}
|
|
|
|
void Node3DEditor::set_state(const Dictionary &p_state) {
|
|
Dictionary d = p_state;
|
|
|
|
if (d.has("snap_enabled")) {
|
|
snap_enabled = d["snap_enabled"];
|
|
tool_option_button[TOOL_OPT_USE_SNAP]->set_pressed(d["snap_enabled"]);
|
|
}
|
|
|
|
if (d.has("trackball_enabled")) {
|
|
trackball_enabled = d["trackball_enabled"];
|
|
tool_option_button[TOOL_OPT_USE_TRACKBALL]->set_pressed(d["trackball_enabled"]);
|
|
}
|
|
|
|
if (d.has("translate_snap")) {
|
|
snap_translate_value = d["translate_snap"];
|
|
}
|
|
|
|
if (d.has("rotate_snap")) {
|
|
snap_rotate_value = d["rotate_snap"];
|
|
}
|
|
|
|
if (d.has("scale_snap")) {
|
|
snap_scale_value = d["scale_snap"];
|
|
}
|
|
|
|
_snap_update();
|
|
|
|
if (d.has("preserve_children_transform")) {
|
|
tool_option_button[TOOL_OPT_PRESERVE_CHILDREN_TRANSFORM]->set_pressed(d["preserve_children_transform"]);
|
|
}
|
|
|
|
if (d.has("vertex_snap_origin_mode")) {
|
|
vertex_snap_origin_mode = d["vertex_snap_origin_mode"];
|
|
int idx_vertex = transform_menu->get_popup()->get_item_index(MENU_VERTEX_SNAP_BASE_VERTEX);
|
|
int idx_origin = transform_menu->get_popup()->get_item_index(MENU_VERTEX_SNAP_BASE_ORIGIN);
|
|
transform_menu->get_popup()->set_item_checked(idx_vertex, !vertex_snap_origin_mode);
|
|
transform_menu->get_popup()->set_item_checked(idx_origin, vertex_snap_origin_mode);
|
|
}
|
|
|
|
if (d.has("vertex_snap_use_collision")) {
|
|
vertex_snap_use_collision = d["vertex_snap_use_collision"];
|
|
int idx_mesh = transform_menu->get_popup()->get_item_index(MENU_VERTEX_SNAP_SOURCE_MESH);
|
|
int idx_collision = transform_menu->get_popup()->get_item_index(MENU_VERTEX_SNAP_SOURCE_COLLISION);
|
|
transform_menu->get_popup()->set_item_checked(idx_mesh, !vertex_snap_use_collision);
|
|
transform_menu->get_popup()->set_item_checked(idx_collision, vertex_snap_use_collision);
|
|
}
|
|
|
|
if (d.has("local_coords")) {
|
|
tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_pressed(d["local_coords"]);
|
|
update_transform_gizmo();
|
|
}
|
|
|
|
if (d.has("viewport_mode")) {
|
|
int vc = d["viewport_mode"];
|
|
|
|
if (vc == 1) {
|
|
_menu_item_pressed(MENU_VIEW_USE_1_VIEWPORT);
|
|
} else if (vc == 2) {
|
|
_menu_item_pressed(MENU_VIEW_USE_2_VIEWPORTS);
|
|
} else if (vc == 3) {
|
|
_menu_item_pressed(MENU_VIEW_USE_3_VIEWPORTS);
|
|
} else if (vc == 4) {
|
|
_menu_item_pressed(MENU_VIEW_USE_4_VIEWPORTS);
|
|
} else if (vc == 5) {
|
|
_menu_item_pressed(MENU_VIEW_USE_2_VIEWPORTS_ALT);
|
|
} else if (vc == 6) {
|
|
_menu_item_pressed(MENU_VIEW_USE_3_VIEWPORTS_ALT);
|
|
}
|
|
}
|
|
|
|
if (d.has("zfar")) {
|
|
settings_zfar->set_value(double(d["zfar"]));
|
|
}
|
|
if (d.has("znear")) {
|
|
settings_znear->set_value(double(d["znear"]));
|
|
}
|
|
if (d.has("fov")) {
|
|
settings_fov->set_value(double(d["fov"]));
|
|
}
|
|
if (d.has("viewport_splits")) {
|
|
viewport_base->set_split_state(d["viewport_splits"]);
|
|
}
|
|
|
|
if (d.has("viewports")) {
|
|
Array vp = d["viewports"];
|
|
uint32_t vp_size = static_cast<uint32_t>(vp.size());
|
|
if (vp_size > VIEWPORTS_COUNT) {
|
|
WARN_PRINT("Ignoring superfluous viewport settings from spatial editor state.");
|
|
vp_size = VIEWPORTS_COUNT;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < vp_size; i++) {
|
|
viewports[i]->set_state(vp[i]);
|
|
}
|
|
}
|
|
|
|
if (d.has("show_grid")) {
|
|
bool use = d["show_grid"];
|
|
|
|
if (use != view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_GRID))) {
|
|
_menu_item_pressed(MENU_VIEW_GRID);
|
|
}
|
|
}
|
|
|
|
if (d.has("show_origin")) {
|
|
bool use = d["show_origin"];
|
|
|
|
if (use != view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_ORIGIN))) {
|
|
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_ORIGIN), use);
|
|
RenderingServer::get_singleton()->instance_set_visible(origin_instance, use);
|
|
}
|
|
}
|
|
|
|
if (d.has("gizmos_status")) {
|
|
Dictionary gizmos_status = d["gizmos_status"];
|
|
|
|
for (int j = 0; j < gizmo_plugins_by_name.size(); ++j) {
|
|
if (!gizmo_plugins_by_name[j]->can_be_hidden()) {
|
|
continue;
|
|
}
|
|
int state = EditorNode3DGizmoPlugin::VISIBLE;
|
|
for (const KeyValue<Variant, Variant> &kv : gizmos_status) {
|
|
if (gizmo_plugins_by_name.write[j]->get_gizmo_name() == String(kv.key)) {
|
|
state = kv.value;
|
|
break;
|
|
}
|
|
}
|
|
|
|
gizmo_plugins_by_name.write[j]->set_state(state);
|
|
}
|
|
_update_gizmos_menu();
|
|
}
|
|
|
|
if (d.has("preview_sun_env")) {
|
|
sun_environ_updating = true;
|
|
Dictionary pd = d["preview_sun_env"];
|
|
sun_rotation = pd["sun_rotation"];
|
|
|
|
environ_sky_color->set_pick_color(pd["environ_sky_color"]);
|
|
environ_ground_color->set_pick_color(pd["environ_ground_color"]);
|
|
environ_energy->set_value_no_signal(pd["environ_energy"]);
|
|
environ_glow_button->set_pressed_no_signal(pd["environ_glow_enabled"]);
|
|
environ_tonemap_button->set_pressed_no_signal(pd["environ_tonemap_enabled"]);
|
|
environ_ao_button->set_pressed_no_signal(pd["environ_ao_enabled"]);
|
|
environ_gi_button->set_pressed_no_signal(pd["environ_gi_enabled"]);
|
|
sun_shadow_max_distance->set_value_no_signal(pd["sun_shadow_max_distance"]);
|
|
|
|
sun_color->set_pick_color(pd["sun_color"]);
|
|
sun_energy->set_value_no_signal(pd["sun_energy"]);
|
|
|
|
sun_button->set_pressed(pd["sun_enabled"]);
|
|
environ_button->set_pressed(pd["environ_enabled"]);
|
|
|
|
sun_environ_updating = false;
|
|
|
|
_preview_settings_changed();
|
|
_update_preview_environment();
|
|
} else {
|
|
_load_default_preview_settings();
|
|
sun_button->set_pressed(true);
|
|
environ_button->set_pressed(true);
|
|
_preview_settings_changed();
|
|
_update_preview_environment();
|
|
}
|
|
}
|
|
|
|
void Node3DEditor::edit(Node3D *p_spatial) {
|
|
if (p_spatial != selected) {
|
|
if (selected) {
|
|
Vector<Ref<Node3DGizmo>> gizmos = selected->get_gizmos();
|
|
for (int i = 0; i < gizmos.size(); i++) {
|
|
Ref<EditorNode3DGizmo> seg = gizmos[i];
|
|
if (seg.is_null()) {
|
|
continue;
|
|
}
|
|
seg->set_selected(false);
|
|
}
|
|
|
|
Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected);
|
|
if (se) {
|
|
se->gizmo.unref();
|
|
se->subgizmos.clear();
|
|
}
|
|
|
|
selected->update_gizmos();
|
|
}
|
|
|
|
selected = p_spatial;
|
|
current_hover_gizmo = Ref<EditorNode3DGizmo>();
|
|
current_hover_gizmo_handle = -1;
|
|
current_hover_gizmo_handle_secondary = false;
|
|
|
|
if (selected) {
|
|
Vector<Ref<Node3DGizmo>> gizmos = selected->get_gizmos();
|
|
for (int i = 0; i < gizmos.size(); i++) {
|
|
Ref<EditorNode3DGizmo> seg = gizmos[i];
|
|
if (seg.is_null()) {
|
|
continue;
|
|
}
|
|
seg->set_selected(true);
|
|
}
|
|
selected->update_gizmos();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Node3DEditor::_snap_changed() {
|
|
snap_translate_value = snap_translate->get_value();
|
|
snap_rotate_value = snap_rotate->get_value();
|
|
snap_scale_value = snap_scale->get_value();
|
|
|
|
EditorSettings::get_singleton()->set_project_metadata("3d_editor", "snap_translate_value", snap_translate_value);
|
|
EditorSettings::get_singleton()->set_project_metadata("3d_editor", "snap_rotate_value", snap_rotate_value);
|
|
EditorSettings::get_singleton()->set_project_metadata("3d_editor", "snap_scale_value", snap_scale_value);
|
|
}
|
|
|
|
void Node3DEditor::_snap_update() {
|
|
snap_translate->set_value(snap_translate_value);
|
|
snap_rotate->set_value(snap_rotate_value);
|
|
snap_scale->set_value(snap_scale_value);
|
|
}
|
|
|
|
void Node3DEditor::_update_vertex_snap_tooltips() {
|
|
String snap_key = ED_GET_SHORTCUT("spatial_editor/vertex_snap")->get_as_text();
|
|
PopupMenu *p = transform_menu->get_popup();
|
|
p->set_item_tooltip(p->get_item_index(MENU_VERTEX_SNAP_BASE_VERTEX),
|
|
vformat(TTR("Hold %s to highlight a vertex on the currently selected node,\nthen drag to move the node and snap it to vertices on neighboring nodes.\n\nFor nodes without a vertex-based representation,\nSnap Origin to Vertex is always used instead."), snap_key));
|
|
p->set_item_tooltip(p->get_item_index(MENU_VERTEX_SNAP_BASE_ORIGIN),
|
|
vformat(TTR("Hold %s to highlight another node's vertex,\nthen click to teleport the selected node to the highlighted vertex."), snap_key));
|
|
p->set_item_tooltip(p->get_item_index(MENU_VERTEX_SNAP_SOURCE_MESH),
|
|
TTR("Snap to vertices of visual meshes.\nHold Shift while vertex snapping to temporarily snap to collision shapes instead."));
|
|
p->set_item_tooltip(p->get_item_index(MENU_VERTEX_SNAP_SOURCE_COLLISION),
|
|
TTR("Snap to vertices of collision shapes.\nHold Shift while vertex snapping to temporarily snap to mesh vertices instead."));
|
|
}
|
|
|
|
void Node3DEditor::_xform_dialog_action() {
|
|
Transform3D t;
|
|
//translation
|
|
Vector3 scale;
|
|
Vector3 rotate;
|
|
Vector3 translate;
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
translate[i] = xform_translate[i]->get_text().to_float();
|
|
rotate[i] = Math::deg_to_rad(xform_rotate[i]->get_text().to_float());
|
|
scale[i] = xform_scale[i]->get_text().to_float();
|
|
}
|
|
|
|
t.basis.scale(scale);
|
|
t.basis.rotate(rotate);
|
|
t.origin = translate;
|
|
|
|
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
|
undo_redo->create_action(TTR("XForm Dialog"));
|
|
|
|
const List<Node *> &selection = editor_selection->get_top_selected_node_list();
|
|
|
|
for (Node *E : selection) {
|
|
Node3D *sp = Object::cast_to<Node3D>(E);
|
|
if (!sp) {
|
|
continue;
|
|
}
|
|
|
|
Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);
|
|
if (!se) {
|
|
continue;
|
|
}
|
|
|
|
bool post = xform_type->get_selected() > 0;
|
|
|
|
Transform3D tr = sp->get_global_gizmo_transform();
|
|
if (post) {
|
|
tr = tr * t;
|
|
} else {
|
|
tr.basis = t.basis * tr.basis;
|
|
tr.origin += t.origin;
|
|
}
|
|
|
|
Node3D *parent = sp->get_parent_node_3d();
|
|
Transform3D local_tr = parent ? parent->get_global_transform().affine_inverse() * tr : tr;
|
|
undo_redo->add_do_method(sp, "set_transform", local_tr);
|
|
undo_redo->add_undo_method(sp, "set_transform", sp->get_transform());
|
|
}
|
|
undo_redo->commit_action();
|
|
}
|
|
|
|
void Node3DEditor::_menu_item_toggled(bool pressed, int p_option) {
|
|
switch (p_option) {
|
|
case MENU_TOOL_LOCAL_COORDS: {
|
|
tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_pressed(pressed);
|
|
update_transform_gizmo();
|
|
} break;
|
|
|
|
case MENU_TOOL_USE_SNAP: {
|
|
tool_option_button[TOOL_OPT_USE_SNAP]->set_pressed(pressed);
|
|
snap_enabled = pressed;
|
|
} break;
|
|
|
|
case MENU_TOOL_USE_TRACKBALL: {
|
|
tool_option_button[TOOL_OPT_USE_TRACKBALL]->set_pressed(pressed);
|
|
trackball_enabled = pressed;
|
|
for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) {
|
|
viewports[i]->update_transform_gizmo_highlight();
|
|
}
|
|
} break;
|
|
|
|
case MENU_TOOL_PRESERVE_CHILDREN_TRANSFORM: {
|
|
tool_option_button[TOOL_OPT_PRESERVE_CHILDREN_TRANSFORM]->set_pressed(pressed);
|
|
if (pressed) {
|
|
EditorNode::get_editor_data().add_undo_redo_inspector_hook_callback(callable_mp(this, &Node3DEditor::_undo_redo_inspector_callback));
|
|
} else {
|
|
EditorNode::get_editor_data().remove_undo_redo_inspector_hook_callback(callable_mp(this, &Node3DEditor::_undo_redo_inspector_callback));
|
|
}
|
|
} break;
|
|
}
|
|
}
|
|
|
|
void Node3DEditor::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, const String &p_property, const Variant &p_new_value) {
|
|
Node3D *node = Object::cast_to<Node3D>(p_edited);
|
|
if (!node) {
|
|
return;
|
|
}
|
|
|
|
static const char *transform_properties[] = { "position", "rotation", "scale", "quaternion", "basis", "transform", nullptr };
|
|
bool is_transform_prop = false;
|
|
for (int i = 0; transform_properties[i]; i++) {
|
|
if (p_property == transform_properties[i]) {
|
|
is_transform_prop = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!is_transform_prop) {
|
|
return;
|
|
}
|
|
|
|
EditorUndoRedoManager *undo_redo = Object::cast_to<EditorUndoRedoManager>(p_undo_redo);
|
|
ERR_FAIL_NULL(undo_redo);
|
|
|
|
int child_count = node->get_child_count();
|
|
for (int i = 0; i < child_count; i++) {
|
|
Node3D *child = Object::cast_to<Node3D>(node->get_child(i));
|
|
if (child) {
|
|
Transform3D child_global = child->get_global_transform();
|
|
undo_redo->add_do_method(child, "set_global_transform", child_global);
|
|
undo_redo->add_undo_method(child, "set_global_transform", child_global);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Node3DEditor::_menu_gizmo_toggled(int p_option) {
|
|
const int idx = gizmos_menu->get_item_index(p_option);
|
|
gizmos_menu->toggle_item_multistate(idx);
|
|
|
|
// Change icon
|
|
const int state = gizmos_menu->get_item_state(idx);
|
|
switch (state) {
|
|
case EditorNode3DGizmoPlugin::VISIBLE:
|
|
gizmos_menu->set_item_icon(idx, get_editor_theme_icon(SNAME("GuiVisibilityVisible")));
|
|
break;
|
|
case EditorNode3DGizmoPlugin::ON_TOP:
|
|
gizmos_menu->set_item_icon(idx, get_editor_theme_icon(SNAME("GuiVisibilityXray")));
|
|
break;
|
|
case EditorNode3DGizmoPlugin::HIDDEN:
|
|
gizmos_menu->set_item_icon(idx, get_editor_theme_icon(SNAME("GuiVisibilityHidden")));
|
|
break;
|
|
}
|
|
|
|
gizmo_plugins_by_name.write[p_option]->set_state(state);
|
|
|
|
update_all_gizmos();
|
|
}
|
|
|
|
void Node3DEditor::_menu_item_pressed(int p_option) {
|
|
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
|
switch (p_option) {
|
|
case MENU_TOOL_TRANSFORM:
|
|
case MENU_TOOL_MOVE:
|
|
case MENU_TOOL_ROTATE:
|
|
case MENU_TOOL_SCALE:
|
|
case MENU_TOOL_SELECT:
|
|
case MENU_TOOL_LIST_SELECT: {
|
|
for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) {
|
|
if (viewports[i]->_edit.mode != Node3DEditorViewport::TRANSFORM_NONE) {
|
|
viewports[i]->commit_transform();
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < TOOL_MAX; i++) {
|
|
tool_button[i]->set_pressed(i == p_option);
|
|
}
|
|
tool_mode = (ToolMode)p_option;
|
|
update_transform_gizmo();
|
|
|
|
for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) {
|
|
viewports[i]->update_transform_gizmo_highlight();
|
|
}
|
|
} break;
|
|
case MENU_TRANSFORM_CONFIGURE_SNAP: {
|
|
snap_dialog->popup_centered(Size2(200, 180));
|
|
} break;
|
|
case MENU_VERTEX_SNAP_BASE_VERTEX: {
|
|
vertex_snap_origin_mode = false;
|
|
int idx_vertex = transform_menu->get_popup()->get_item_index(MENU_VERTEX_SNAP_BASE_VERTEX);
|
|
int idx_origin = transform_menu->get_popup()->get_item_index(MENU_VERTEX_SNAP_BASE_ORIGIN);
|
|
transform_menu->get_popup()->set_item_checked(idx_vertex, true);
|
|
transform_menu->get_popup()->set_item_checked(idx_origin, false);
|
|
} break;
|
|
case MENU_VERTEX_SNAP_BASE_ORIGIN: {
|
|
vertex_snap_origin_mode = true;
|
|
int idx_vertex = transform_menu->get_popup()->get_item_index(MENU_VERTEX_SNAP_BASE_VERTEX);
|
|
int idx_origin = transform_menu->get_popup()->get_item_index(MENU_VERTEX_SNAP_BASE_ORIGIN);
|
|
transform_menu->get_popup()->set_item_checked(idx_vertex, false);
|
|
transform_menu->get_popup()->set_item_checked(idx_origin, true);
|
|
} break;
|
|
case MENU_VERTEX_SNAP_SOURCE_MESH: {
|
|
vertex_snap_use_collision = false;
|
|
int idx_mesh = transform_menu->get_popup()->get_item_index(MENU_VERTEX_SNAP_SOURCE_MESH);
|
|
int idx_collision = transform_menu->get_popup()->get_item_index(MENU_VERTEX_SNAP_SOURCE_COLLISION);
|
|
transform_menu->get_popup()->set_item_checked(idx_mesh, true);
|
|
transform_menu->get_popup()->set_item_checked(idx_collision, false);
|
|
} break;
|
|
case MENU_VERTEX_SNAP_SOURCE_COLLISION: {
|
|
vertex_snap_use_collision = true;
|
|
int idx_mesh = transform_menu->get_popup()->get_item_index(MENU_VERTEX_SNAP_SOURCE_MESH);
|
|
int idx_collision = transform_menu->get_popup()->get_item_index(MENU_VERTEX_SNAP_SOURCE_COLLISION);
|
|
transform_menu->get_popup()->set_item_checked(idx_mesh, false);
|
|
transform_menu->get_popup()->set_item_checked(idx_collision, true);
|
|
} break;
|
|
case MENU_TRANSFORM_DIALOG: {
|
|
for (int i = 0; i < 3; i++) {
|
|
xform_translate[i]->set_text("0");
|
|
xform_rotate[i]->set_text("0");
|
|
xform_scale[i]->set_text("1");
|
|
}
|
|
|
|
xform_dialog->popup_centered(Size2(320, 240) * EDSCALE);
|
|
|
|
} break;
|
|
case MENU_VIEW_USE_1_VIEWPORT: {
|
|
viewport_base->set_view(Node3DEditorViewportContainer::VIEW_USE_1_VIEWPORT);
|
|
if (last_used_viewport > 0) {
|
|
last_used_viewport = 0;
|
|
}
|
|
|
|
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), true);
|
|
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), false);
|
|
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS), false);
|
|
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS), false);
|
|
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT), false);
|
|
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT), false);
|
|
|
|
} break;
|
|
case MENU_VIEW_USE_2_VIEWPORTS: {
|
|
viewport_base->set_view(Node3DEditorViewportContainer::VIEW_USE_2_VIEWPORTS);
|
|
if (last_used_viewport > 1) {
|
|
last_used_viewport = 0;
|
|
}
|
|
|
|
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), false);
|
|
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), true);
|
|
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS), false);
|
|
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS), false);
|
|
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT), false);
|
|
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT), false);
|
|
|
|
} break;
|
|
case MENU_VIEW_USE_2_VIEWPORTS_ALT: {
|
|
viewport_base->set_view(Node3DEditorViewportContainer::VIEW_USE_2_VIEWPORTS_ALT);
|
|
if (last_used_viewport > 1) {
|
|
last_used_viewport = 0;
|
|
}
|
|
|
|
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), false);
|
|
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), false);
|
|
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS), false);
|
|
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS), false);
|
|
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT), true);
|
|
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT), false);
|
|
|
|
} break;
|
|
case MENU_VIEW_USE_3_VIEWPORTS: {
|
|
viewport_base->set_view(Node3DEditorViewportContainer::VIEW_USE_3_VIEWPORTS);
|
|
if (last_used_viewport > 2) {
|
|
last_used_viewport = 0;
|
|
}
|
|
|
|
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), false);
|
|
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), false);
|
|
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS), true);
|
|
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS), false);
|
|
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT), false);
|
|
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT), false);
|
|
|
|
} break;
|
|
case MENU_VIEW_USE_3_VIEWPORTS_ALT: {
|
|
viewport_base->set_view(Node3DEditorViewportContainer::VIEW_USE_3_VIEWPORTS_ALT);
|
|
if (last_used_viewport > 2) {
|
|
last_used_viewport = 0;
|
|
}
|
|
|
|
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), false);
|
|
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), false);
|
|
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS), false);
|
|
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS), false);
|
|
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT), false);
|
|
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT), true);
|
|
|
|
} break;
|
|
case MENU_VIEW_USE_4_VIEWPORTS: {
|
|
viewport_base->set_view(Node3DEditorViewportContainer::VIEW_USE_4_VIEWPORTS);
|
|
|
|
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), false);
|
|
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), false);
|
|
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS), false);
|
|
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS), true);
|
|
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT), false);
|
|
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT), false);
|
|
|
|
} break;
|
|
case MENU_VIEW_ORIGIN: {
|
|
bool is_checked = view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(p_option));
|
|
|
|
origin_enabled = !is_checked;
|
|
RenderingServer::get_singleton()->instance_set_visible(origin_instance, origin_enabled);
|
|
// Update the grid since its appearance depends on whether the origin is enabled
|
|
_finish_grid();
|
|
_init_grid();
|
|
|
|
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(p_option), origin_enabled);
|
|
} break;
|
|
case MENU_VIEW_GRID: {
|
|
bool is_checked = view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(p_option));
|
|
|
|
grid_enabled = !is_checked;
|
|
|
|
for (int i = 0; i < 3; ++i) {
|
|
if (grid_enable[i]) {
|
|
grid_visible[i] = grid_enabled;
|
|
}
|
|
}
|
|
_finish_grid();
|
|
_init_grid();
|
|
|
|
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(p_option), grid_enabled);
|
|
|
|
} break;
|
|
case MENU_VIEW_CAMERA_SETTINGS: {
|
|
settings_dialog->popup_centered(settings_vbc->get_combined_minimum_size() + Size2(50, 50));
|
|
} break;
|
|
case MENU_SNAP_TO_FLOOR: {
|
|
snap_selected_nodes_to_floor();
|
|
} break;
|
|
case MENU_LOCK_SELECTED: {
|
|
undo_redo->create_action(TTR("Lock Selected"));
|
|
|
|
const List<Node *> &selection = editor_selection->get_top_selected_node_list();
|
|
|
|
for (Node *E : selection) {
|
|
Node3D *spatial = Object::cast_to<Node3D>(E);
|
|
if (!spatial || !spatial->is_inside_tree()) {
|
|
continue;
|
|
}
|
|
|
|
undo_redo->add_do_method(spatial, "set_meta", "_edit_lock_", true);
|
|
undo_redo->add_undo_method(spatial, "remove_meta", "_edit_lock_");
|
|
undo_redo->add_do_method(this, "emit_signal", "item_lock_status_changed");
|
|
undo_redo->add_undo_method(this, "emit_signal", "item_lock_status_changed");
|
|
}
|
|
|
|
undo_redo->add_do_method(this, "_refresh_menu_icons");
|
|
undo_redo->add_undo_method(this, "_refresh_menu_icons");
|
|
undo_redo->add_do_method(this, "update_transform_gizmo");
|
|
undo_redo->add_undo_method(this, "update_transform_gizmo");
|
|
undo_redo->commit_action();
|
|
} break;
|
|
case MENU_UNLOCK_SELECTED: {
|
|
undo_redo->create_action(TTR("Unlock Selected"));
|
|
|
|
const List<Node *> &selection = editor_selection->get_top_selected_node_list();
|
|
|
|
for (Node *E : selection) {
|
|
Node3D *spatial = Object::cast_to<Node3D>(E);
|
|
if (!spatial || !spatial->is_inside_tree()) {
|
|
continue;
|
|
}
|
|
|
|
undo_redo->add_do_method(spatial, "remove_meta", "_edit_lock_");
|
|
undo_redo->add_undo_method(spatial, "set_meta", "_edit_lock_", true);
|
|
undo_redo->add_do_method(this, "emit_signal", "item_lock_status_changed");
|
|
undo_redo->add_undo_method(this, "emit_signal", "item_lock_status_changed");
|
|
}
|
|
|
|
undo_redo->add_do_method(this, "_refresh_menu_icons");
|
|
undo_redo->add_undo_method(this, "_refresh_menu_icons");
|
|
undo_redo->add_do_method(this, "update_transform_gizmo");
|
|
undo_redo->add_undo_method(this, "update_transform_gizmo");
|
|
undo_redo->commit_action();
|
|
} break;
|
|
case MENU_GROUP_SELECTED: {
|
|
undo_redo->create_action(TTR("Group Selected"));
|
|
|
|
const List<Node *> &selection = editor_selection->get_top_selected_node_list();
|
|
|
|
for (Node *E : selection) {
|
|
Node3D *spatial = Object::cast_to<Node3D>(E);
|
|
if (!spatial || !spatial->is_inside_tree()) {
|
|
continue;
|
|
}
|
|
|
|
undo_redo->add_do_method(spatial, "set_meta", "_edit_group_", true);
|
|
undo_redo->add_undo_method(spatial, "remove_meta", "_edit_group_");
|
|
undo_redo->add_do_method(this, "emit_signal", "item_group_status_changed");
|
|
undo_redo->add_undo_method(this, "emit_signal", "item_group_status_changed");
|
|
}
|
|
|
|
undo_redo->add_do_method(this, "_refresh_menu_icons");
|
|
undo_redo->add_undo_method(this, "_refresh_menu_icons");
|
|
undo_redo->commit_action();
|
|
} break;
|
|
case MENU_UNGROUP_SELECTED: {
|
|
undo_redo->create_action(TTR("Ungroup Selected"));
|
|
const List<Node *> &selection = editor_selection->get_top_selected_node_list();
|
|
|
|
for (Node *E : selection) {
|
|
Node3D *spatial = Object::cast_to<Node3D>(E);
|
|
if (!spatial || !spatial->is_inside_tree()) {
|
|
continue;
|
|
}
|
|
|
|
undo_redo->add_do_method(spatial, "remove_meta", "_edit_group_");
|
|
undo_redo->add_undo_method(spatial, "set_meta", "_edit_group_", true);
|
|
undo_redo->add_do_method(this, "emit_signal", "item_group_status_changed");
|
|
undo_redo->add_undo_method(this, "emit_signal", "item_group_status_changed");
|
|
}
|
|
|
|
undo_redo->add_do_method(this, "_refresh_menu_icons");
|
|
undo_redo->add_undo_method(this, "_refresh_menu_icons");
|
|
undo_redo->commit_action();
|
|
} break;
|
|
case MENU_RULER: {
|
|
for (int i = 0; i < TOOL_MAX; i++) {
|
|
tool_button[i]->set_pressed(i == p_option);
|
|
}
|
|
tool_button[TOOL_RULER]->set_pressed(true);
|
|
tool_mode = ToolMode::TOOL_RULER;
|
|
update_transform_gizmo();
|
|
} break;
|
|
}
|
|
}
|
|
|
|
void Node3DEditor::_init_indicators() {
|
|
{
|
|
origin_enabled = true;
|
|
grid_enabled = true;
|
|
|
|
Ref<Shader> origin_shader = memnew(Shader);
|
|
origin_shader->set_code(R"(
|
|
// 3D editor origin line shader.
|
|
|
|
shader_type spatial;
|
|
render_mode blend_mix, cull_disabled, unshaded, fog_disabled;
|
|
|
|
void vertex() {
|
|
vec3 point_a = MODEL_MATRIX[3].xyz;
|
|
// Encoded in scale.
|
|
vec3 point_b = vec3(MODEL_MATRIX[0].x, MODEL_MATRIX[1].y, MODEL_MATRIX[2].z);
|
|
|
|
// Points are already in world space, so no need for MODEL_MATRIX anymore.
|
|
vec4 clip_a = PROJECTION_MATRIX * (VIEW_MATRIX * vec4(point_a, 1.0));
|
|
vec4 clip_b = PROJECTION_MATRIX * (VIEW_MATRIX * vec4(point_b, 1.0));
|
|
|
|
vec2 screen_a = VIEWPORT_SIZE * (0.5 * clip_a.xy / clip_a.w + 0.5);
|
|
vec2 screen_b = VIEWPORT_SIZE * (0.5 * clip_b.xy / clip_b.w + 0.5);
|
|
|
|
vec2 x_basis = normalize(screen_b - screen_a);
|
|
vec2 y_basis = vec2(-x_basis.y, x_basis.x);
|
|
|
|
float width = 3.0;
|
|
vec2 screen_point_a = screen_a + width * (VERTEX.x * x_basis + VERTEX.y * y_basis);
|
|
vec2 screen_point_b = screen_b + width * (VERTEX.x * x_basis + VERTEX.y * y_basis);
|
|
vec2 screen_point_final = mix(screen_point_a, screen_point_b, VERTEX.z);
|
|
|
|
vec4 clip_final = mix(clip_a, clip_b, VERTEX.z);
|
|
|
|
POSITION = vec4(clip_final.w * ((2.0 * screen_point_final) / VIEWPORT_SIZE - 1.0), clip_final.z, clip_final.w);
|
|
UV = VERTEX.yz * clip_final.w;
|
|
|
|
if (!OUTPUT_IS_SRGB) {
|
|
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)));
|
|
}
|
|
}
|
|
|
|
void fragment() {
|
|
// Multiply by 0.5 since UV is actually UV is [-1, 1].
|
|
float line_width = fwidth(UV.x * 0.5);
|
|
float line_uv = abs(UV.x * 0.5);
|
|
float line = smoothstep(line_width * 1.0, line_width * 0.25, line_uv);
|
|
|
|
ALBEDO = COLOR.rgb;
|
|
ALPHA *= COLOR.a * line;
|
|
}
|
|
)");
|
|
|
|
origin_mat.instantiate();
|
|
origin_mat->set_shader(origin_shader);
|
|
|
|
Vector<Vector3> origin_points;
|
|
origin_points.resize(6);
|
|
|
|
origin_points.set(0, Vector3(0.0, -0.5, 0.0));
|
|
origin_points.set(1, Vector3(0.0, -0.5, 1.0));
|
|
origin_points.set(2, Vector3(0.0, 0.5, 1.0));
|
|
|
|
origin_points.set(3, Vector3(0.0, -0.5, 0.0));
|
|
origin_points.set(4, Vector3(0.0, 0.5, 1.0));
|
|
origin_points.set(5, Vector3(0.0, 0.5, 0.0));
|
|
|
|
Array d;
|
|
d.resize(RSE::ARRAY_MAX);
|
|
d[RSE::ARRAY_VERTEX] = origin_points;
|
|
|
|
origin_mesh = RenderingServer::get_singleton()->mesh_create();
|
|
|
|
RenderingServer::get_singleton()->mesh_add_surface_from_arrays(origin_mesh, RSE::PRIMITIVE_TRIANGLES, d);
|
|
RenderingServer::get_singleton()->mesh_surface_set_material(origin_mesh, 0, origin_mat->get_rid());
|
|
|
|
origin_multimesh = RenderingServer::get_singleton()->multimesh_create();
|
|
RenderingServer::get_singleton()->multimesh_set_mesh(origin_multimesh, origin_mesh);
|
|
RenderingServer::get_singleton()->multimesh_allocate_data(origin_multimesh, 12, RSE::MultimeshTransformFormat::MULTIMESH_TRANSFORM_3D, true, false);
|
|
RenderingServer::get_singleton()->multimesh_set_visible_instances(origin_multimesh, -1);
|
|
|
|
LocalVector<float> distances;
|
|
distances.resize(5);
|
|
distances[0] = -1000000.0;
|
|
distances[1] = -1000.0;
|
|
distances[2] = 0.0;
|
|
distances[3] = 1000.0;
|
|
distances[4] = 1000000.0;
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
Color origin_color;
|
|
switch (i) {
|
|
case 0:
|
|
origin_color = get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor));
|
|
break;
|
|
case 1:
|
|
origin_color = get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor));
|
|
break;
|
|
case 2:
|
|
origin_color = get_theme_color(SNAME("axis_z_color"), EditorStringName(Editor));
|
|
break;
|
|
default:
|
|
origin_color = Color();
|
|
break;
|
|
}
|
|
|
|
Vector3 axis;
|
|
axis[i] = 1;
|
|
|
|
for (int j = 0; j < 4; j++) {
|
|
Transform3D t = Transform3D();
|
|
if (distances[j] > 0.0) {
|
|
t = t.scaled(axis * distances[j + 1]);
|
|
t = t.translated(axis * distances[j]);
|
|
} else {
|
|
t = t.scaled(axis * distances[j]);
|
|
t = t.translated(axis * distances[j + 1]);
|
|
}
|
|
RenderingServer::get_singleton()->multimesh_instance_set_transform(origin_multimesh, i * 4 + j, t);
|
|
RenderingServer::get_singleton()->multimesh_instance_set_color(origin_multimesh, i * 4 + j, origin_color);
|
|
}
|
|
}
|
|
|
|
origin_instance = RenderingServer::get_singleton()->instance_create2(origin_multimesh, get_tree()->get_root()->get_world_3d()->get_scenario());
|
|
RS::get_singleton()->instance_set_layer_mask(origin_instance, 1 << Node3DEditorViewport::GIZMO_GRID_LAYER);
|
|
RS::get_singleton()->instance_geometry_set_flag(origin_instance, RSE::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);
|
|
RS::get_singleton()->instance_geometry_set_flag(origin_instance, RSE::INSTANCE_FLAG_USE_BAKED_LIGHT, false);
|
|
|
|
RenderingServer::get_singleton()->instance_geometry_set_cast_shadows_setting(origin_instance, RSE::SHADOW_CASTING_SETTING_OFF);
|
|
|
|
Ref<Shader> grid_shader = memnew(Shader);
|
|
grid_shader->set_code(R"(
|
|
// 3D editor grid shader.
|
|
|
|
shader_type spatial;
|
|
|
|
render_mode unshaded, fog_disabled;
|
|
|
|
uniform bool orthogonal;
|
|
uniform float grid_size;
|
|
|
|
void vertex() {
|
|
// From FLAG_SRGB_VERTEX_COLOR.
|
|
if (!OUTPUT_IS_SRGB) {
|
|
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)));
|
|
}
|
|
}
|
|
|
|
void fragment() {
|
|
ALBEDO = COLOR.rgb;
|
|
vec3 dir = orthogonal ? -vec3(0, 0, 1) : VIEW;
|
|
float angle_fade = abs(dot(dir, NORMAL));
|
|
angle_fade = smoothstep(0.05, 0.2, angle_fade);
|
|
|
|
vec3 world_pos = (INV_VIEW_MATRIX * vec4(VERTEX, 1.0)).xyz;
|
|
vec3 world_normal = (INV_VIEW_MATRIX * vec4(NORMAL, 0.0)).xyz;
|
|
vec3 camera_world_pos = INV_VIEW_MATRIX[3].xyz;
|
|
vec3 camera_world_pos_on_plane = camera_world_pos * (1.0 - world_normal);
|
|
float dist_fade = 1.0 - (distance(world_pos, camera_world_pos_on_plane) / grid_size);
|
|
dist_fade = smoothstep(0.02, 0.3, dist_fade);
|
|
|
|
ALPHA = COLOR.a * dist_fade * angle_fade;
|
|
}
|
|
)");
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
grid_mat[i].instantiate();
|
|
grid_mat[i]->set_shader(grid_shader);
|
|
}
|
|
|
|
grid_enable[0] = EDITOR_GET("editors/3d/grid_xy_plane");
|
|
grid_enable[1] = EDITOR_GET("editors/3d/grid_yz_plane");
|
|
grid_enable[2] = EDITOR_GET("editors/3d/grid_xz_plane");
|
|
grid_visible[0] = grid_enable[0];
|
|
grid_visible[1] = grid_enable[1];
|
|
grid_visible[2] = grid_enable[2];
|
|
|
|
_init_grid();
|
|
}
|
|
|
|
{
|
|
//move gizmo
|
|
|
|
// Inverted zxy.
|
|
Vector3 ivec = Vector3(0, 0, -1);
|
|
Vector3 nivec = Vector3(-1, -1, 0);
|
|
Vector3 ivec2 = Vector3(-1, 0, 0);
|
|
Vector3 ivec3 = Vector3(0, -1, 0);
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
Color col;
|
|
switch (i) {
|
|
case 0:
|
|
col = get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor));
|
|
break;
|
|
case 1:
|
|
col = get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor));
|
|
break;
|
|
case 2:
|
|
col = get_theme_color(SNAME("axis_z_color"), EditorStringName(Editor));
|
|
break;
|
|
case 3:
|
|
col = get_theme_color(SNAME("axis_view_plane_color"), EditorStringName(Editor));
|
|
break;
|
|
default:
|
|
col = Color();
|
|
break;
|
|
}
|
|
|
|
col.a = col.a * (float)EDITOR_GET("editors/3d/manipulator_gizmo_opacity");
|
|
|
|
if (i < 3) {
|
|
move_gizmo[i].instantiate();
|
|
move_plane_gizmo[i].instantiate();
|
|
scale_gizmo[i].instantiate();
|
|
scale_plane_gizmo[i].instantiate();
|
|
axis_gizmo[i].instantiate();
|
|
}
|
|
|
|
rotate_gizmo[i].instantiate();
|
|
|
|
const Color albedo = col.from_hsv(col.get_h(), col.get_s() * 0.25, 1.0, 1);
|
|
|
|
Ref<StandardMaterial3D> mat;
|
|
Ref<StandardMaterial3D> mat_hl;
|
|
|
|
if (i < 3) {
|
|
// Only create standard materials for X, Y, Z axes (move/scale gizmos).
|
|
mat.instantiate();
|
|
mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
|
|
mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
|
|
mat->set_on_top_of_alpha();
|
|
mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
|
|
mat->set_albedo(col);
|
|
gizmo_color[i] = mat;
|
|
|
|
mat_hl = mat->duplicate();
|
|
mat_hl->set_albedo(albedo);
|
|
gizmo_color_hl[i] = mat_hl;
|
|
}
|
|
|
|
// Only create translate gizmo for X, Y, Z axes (not view rotation).
|
|
if (i < 3) {
|
|
//translate
|
|
{
|
|
Ref<SurfaceTool> surftool;
|
|
surftool.instantiate();
|
|
surftool->begin(Mesh::PRIMITIVE_TRIANGLES);
|
|
|
|
// Arrow profile
|
|
const int arrow_points = 5;
|
|
Vector3 arrow[5] = {
|
|
nivec * 0.0 + ivec * 0.0,
|
|
nivec * 0.01 + ivec * 0.0,
|
|
nivec * 0.01 + ivec * GIZMO_ARROW_OFFSET,
|
|
nivec * 0.065 + ivec * GIZMO_ARROW_OFFSET,
|
|
nivec * 0.0 + ivec * (GIZMO_ARROW_OFFSET + GIZMO_ARROW_SIZE),
|
|
};
|
|
|
|
int arrow_sides = 16;
|
|
|
|
const real_t arrow_sides_step = Math::TAU / arrow_sides;
|
|
for (int k = 0; k < arrow_sides; k++) {
|
|
Basis ma(ivec, k * arrow_sides_step);
|
|
Basis mb(ivec, (k + 1) * arrow_sides_step);
|
|
|
|
for (int j = 0; j < arrow_points - 1; j++) {
|
|
Vector3 points[4] = {
|
|
ma.xform(arrow[j]),
|
|
mb.xform(arrow[j]),
|
|
mb.xform(arrow[j + 1]),
|
|
ma.xform(arrow[j + 1]),
|
|
};
|
|
surftool->add_vertex(points[0]);
|
|
surftool->add_vertex(points[1]);
|
|
surftool->add_vertex(points[2]);
|
|
|
|
surftool->add_vertex(points[0]);
|
|
surftool->add_vertex(points[2]);
|
|
surftool->add_vertex(points[3]);
|
|
}
|
|
}
|
|
|
|
surftool->set_material(mat);
|
|
surftool->commit(move_gizmo[i]);
|
|
}
|
|
|
|
// Plane Translation
|
|
{
|
|
Ref<SurfaceTool> surftool;
|
|
surftool.instantiate();
|
|
surftool->begin(Mesh::PRIMITIVE_TRIANGLES);
|
|
|
|
Vector3 vec = ivec2 - ivec3;
|
|
Vector3 plane[4] = {
|
|
vec * GIZMO_PLANE_DST,
|
|
vec * GIZMO_PLANE_DST + ivec2 * GIZMO_PLANE_SIZE,
|
|
vec * (GIZMO_PLANE_DST + GIZMO_PLANE_SIZE),
|
|
vec * GIZMO_PLANE_DST - ivec3 * GIZMO_PLANE_SIZE
|
|
};
|
|
|
|
Basis ma(ivec, Math::PI / 2);
|
|
|
|
Vector3 points[4] = {
|
|
ma.xform(plane[0]),
|
|
ma.xform(plane[1]),
|
|
ma.xform(plane[2]),
|
|
ma.xform(plane[3]),
|
|
};
|
|
surftool->add_vertex(points[0]);
|
|
surftool->add_vertex(points[1]);
|
|
surftool->add_vertex(points[2]);
|
|
|
|
surftool->add_vertex(points[0]);
|
|
surftool->add_vertex(points[2]);
|
|
surftool->add_vertex(points[3]);
|
|
|
|
Ref<StandardMaterial3D> plane_mat;
|
|
plane_mat.instantiate();
|
|
plane_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
|
|
plane_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
|
|
plane_mat->set_on_top_of_alpha();
|
|
plane_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
|
|
plane_mat->set_cull_mode(StandardMaterial3D::CULL_DISABLED);
|
|
plane_mat->set_albedo(col);
|
|
plane_gizmo_color[i] = plane_mat; // Needed, so we can draw planes from both sides.
|
|
surftool->set_material(plane_mat);
|
|
surftool->commit(move_plane_gizmo[i]);
|
|
|
|
Ref<StandardMaterial3D> plane_mat_hl = plane_mat->duplicate();
|
|
plane_mat_hl->set_albedo(albedo);
|
|
plane_gizmo_color_hl[i] = plane_mat_hl; // Needed, so we can draw planes from both sides.
|
|
}
|
|
}
|
|
|
|
// Rotate - for all 4 indices (X, Y, Z, and view rotation).
|
|
{
|
|
Ref<SurfaceTool> surftool;
|
|
surftool.instantiate();
|
|
surftool->begin(Mesh::PRIMITIVE_TRIANGLES);
|
|
|
|
int n = 128; // number of circle segments
|
|
int m = 3; // number of thickness segments
|
|
|
|
real_t step = Math::TAU / n;
|
|
for (int j = 0; j < n; ++j) {
|
|
Basis basis = Basis(ivec, j * step);
|
|
|
|
Vector3 vertex = basis.xform(ivec2 * GIZMO_CIRCLE_SIZE);
|
|
|
|
for (int k = 0; k < m; ++k) {
|
|
Vector2 ofs = Vector2(Math::cos((Math::TAU * k) / m), Math::sin((Math::TAU * k) / m));
|
|
Vector3 normal = ivec * ofs.x + ivec2 * ofs.y;
|
|
|
|
surftool->set_normal(basis.xform(normal));
|
|
surftool->add_vertex(vertex);
|
|
}
|
|
}
|
|
|
|
for (int j = 0; j < n; ++j) {
|
|
for (int k = 0; k < m; ++k) {
|
|
int current_ring = j * m;
|
|
int next_ring = ((j + 1) % n) * m;
|
|
int current_segment = k;
|
|
int next_segment = (k + 1) % m;
|
|
|
|
surftool->add_index(current_ring + next_segment);
|
|
surftool->add_index(current_ring + current_segment);
|
|
surftool->add_index(next_ring + current_segment);
|
|
|
|
surftool->add_index(next_ring + current_segment);
|
|
surftool->add_index(next_ring + next_segment);
|
|
surftool->add_index(current_ring + next_segment);
|
|
}
|
|
}
|
|
|
|
Ref<Shader> rotate_shader = memnew(Shader);
|
|
|
|
// Use special shader for view rotation (index 3) with camera-relative transformation.
|
|
if (i == 3) {
|
|
rotate_shader->set_code(R"(
|
|
|
|
shader_type spatial;
|
|
|
|
render_mode unshaded, depth_test_disabled, fog_disabled;
|
|
|
|
uniform vec4 albedo;
|
|
|
|
mat3 orthonormalize(mat3 m) {
|
|
vec3 x = normalize(m[0]);
|
|
vec3 y = normalize(m[1] - x * dot(x, m[1]));
|
|
vec3 z = m[2] - x * dot(x, m[2]);
|
|
z = normalize(z - y * (dot(y, m[2])));
|
|
return mat3(x,y,z);
|
|
}
|
|
|
|
void vertex() {
|
|
mat3 mv = orthonormalize(mat3(MODELVIEW_MATRIX));
|
|
mv = inverse(mv);
|
|
VERTEX += NORMAL * 0.008;
|
|
vec3 camera_dir_local = mv * vec3(0.0, 0.0, 1.0);
|
|
vec3 camera_up_local = mv * vec3(0.0, 1.0, 0.0);
|
|
mat3 rotation_matrix = mat3(cross(camera_dir_local, camera_up_local), camera_up_local, camera_dir_local);
|
|
VERTEX = rotation_matrix * VERTEX;
|
|
}
|
|
|
|
void fragment() {
|
|
ALBEDO = albedo.rgb;
|
|
ALPHA = albedo.a;
|
|
}
|
|
)");
|
|
} else {
|
|
// Standard shader for X, Y, Z rotation gizmos.
|
|
rotate_shader->set_code(R"(
|
|
// 3D editor rotation manipulator gizmo shader.
|
|
|
|
shader_type spatial;
|
|
|
|
render_mode unshaded, depth_test_disabled, fog_disabled;
|
|
|
|
uniform vec4 albedo;
|
|
|
|
mat3 orthonormalize(mat3 m) {
|
|
vec3 x = normalize(m[0]);
|
|
vec3 y = normalize(m[1] - x * dot(x, m[1]));
|
|
vec3 z = m[2] - x * dot(x, m[2]);
|
|
z = normalize(z - y * (dot(y, m[2])));
|
|
return mat3(x, y, z);
|
|
}
|
|
|
|
void vertex() {
|
|
mat3 mv = orthonormalize(mat3(MODELVIEW_MATRIX));
|
|
vec3 n = mv * VERTEX;
|
|
float orientation = dot(vec3(0.0, 0.0, -1.0), n);
|
|
if (orientation <= 0.005) {
|
|
VERTEX += NORMAL * 0.02;
|
|
}
|
|
}
|
|
|
|
void fragment() {
|
|
ALBEDO = albedo.rgb;
|
|
ALPHA = albedo.a;
|
|
}
|
|
)");
|
|
}
|
|
|
|
Ref<ShaderMaterial> rotate_mat;
|
|
rotate_mat.instantiate();
|
|
rotate_mat->set_render_priority(Material::RENDER_PRIORITY_MAX);
|
|
rotate_mat->set_shader(rotate_shader);
|
|
rotate_mat->set_shader_parameter("albedo", col);
|
|
rotate_gizmo_color[i] = rotate_mat;
|
|
|
|
Array arrays = surftool->commit_to_arrays();
|
|
rotate_gizmo[i]->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, arrays);
|
|
rotate_gizmo[i]->surface_set_material(0, rotate_mat);
|
|
|
|
Ref<ShaderMaterial> rotate_mat_hl = rotate_mat->duplicate();
|
|
rotate_mat_hl->set_shader_parameter("albedo", albedo);
|
|
rotate_gizmo_color_hl[i] = rotate_mat_hl;
|
|
}
|
|
|
|
// Only create scale gizmo for X, Y, Z axes (not view rotation).
|
|
if (i < 3) {
|
|
// Scale.
|
|
{
|
|
Ref<SurfaceTool> surftool;
|
|
surftool.instantiate();
|
|
surftool->begin(Mesh::PRIMITIVE_TRIANGLES);
|
|
|
|
// Cube arrow profile.
|
|
const int arrow_points = 6;
|
|
Vector3 arrow[6] = {
|
|
nivec * 0.0 + ivec * 0.0,
|
|
nivec * 0.01 + ivec * 0.0,
|
|
nivec * 0.01 + ivec * 1.0 * GIZMO_SCALE_OFFSET,
|
|
nivec * 0.07 + ivec * 1.0 * GIZMO_SCALE_OFFSET,
|
|
nivec * 0.07 + ivec * 1.11 * GIZMO_SCALE_OFFSET,
|
|
nivec * 0.0 + ivec * 1.11 * GIZMO_SCALE_OFFSET,
|
|
};
|
|
|
|
int arrow_sides = 4;
|
|
|
|
const real_t arrow_sides_step = Math::TAU / arrow_sides;
|
|
for (int k = 0; k < 4; k++) {
|
|
Basis ma(ivec, k * arrow_sides_step);
|
|
Basis mb(ivec, (k + 1) * arrow_sides_step);
|
|
|
|
for (int j = 0; j < arrow_points - 1; j++) {
|
|
Vector3 points[4] = {
|
|
ma.xform(arrow[j]),
|
|
mb.xform(arrow[j]),
|
|
mb.xform(arrow[j + 1]),
|
|
ma.xform(arrow[j + 1]),
|
|
};
|
|
surftool->add_vertex(points[0]);
|
|
surftool->add_vertex(points[1]);
|
|
surftool->add_vertex(points[2]);
|
|
|
|
surftool->add_vertex(points[0]);
|
|
surftool->add_vertex(points[2]);
|
|
surftool->add_vertex(points[3]);
|
|
}
|
|
}
|
|
|
|
surftool->set_material(mat);
|
|
surftool->commit(scale_gizmo[i]);
|
|
}
|
|
|
|
// Plane Scale.
|
|
{
|
|
Ref<SurfaceTool> surftool;
|
|
surftool.instantiate();
|
|
surftool->begin(Mesh::PRIMITIVE_TRIANGLES);
|
|
|
|
Vector3 vec = ivec2 - ivec3;
|
|
Vector3 plane[4] = {
|
|
vec * GIZMO_PLANE_DST,
|
|
vec * GIZMO_PLANE_DST + ivec2 * GIZMO_PLANE_SIZE,
|
|
vec * (GIZMO_PLANE_DST + GIZMO_PLANE_SIZE),
|
|
vec * GIZMO_PLANE_DST - ivec3 * GIZMO_PLANE_SIZE
|
|
};
|
|
|
|
Basis ma(ivec, Math::PI / 2);
|
|
|
|
Vector3 points[4] = {
|
|
ma.xform(plane[0]),
|
|
ma.xform(plane[1]),
|
|
ma.xform(plane[2]),
|
|
ma.xform(plane[3]),
|
|
};
|
|
surftool->add_vertex(points[0]);
|
|
surftool->add_vertex(points[1]);
|
|
surftool->add_vertex(points[2]);
|
|
|
|
surftool->add_vertex(points[0]);
|
|
surftool->add_vertex(points[2]);
|
|
surftool->add_vertex(points[3]);
|
|
|
|
Ref<StandardMaterial3D> plane_mat;
|
|
plane_mat.instantiate();
|
|
plane_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
|
|
plane_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
|
|
plane_mat->set_on_top_of_alpha();
|
|
plane_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
|
|
plane_mat->set_cull_mode(StandardMaterial3D::CULL_DISABLED);
|
|
plane_mat->set_albedo(col);
|
|
plane_gizmo_color[i] = plane_mat; // needed, so we can draw planes from both sides.
|
|
surftool->set_material(plane_mat);
|
|
surftool->commit(scale_plane_gizmo[i]);
|
|
|
|
Ref<StandardMaterial3D> plane_mat_hl = plane_mat->duplicate();
|
|
plane_mat_hl->set_albedo(col.from_hsv(col.get_h(), col.get_s() * 0.25, 1.0, 1));
|
|
plane_gizmo_color_hl[i] = plane_mat_hl; // needed, so we can draw planes from both sides.
|
|
}
|
|
|
|
// Lines to visualize transforms locked to an axis/plane.
|
|
{
|
|
Ref<SurfaceTool> surftool;
|
|
surftool.instantiate();
|
|
surftool->begin(Mesh::PRIMITIVE_LINE_STRIP);
|
|
|
|
Vector3 vec;
|
|
vec[i] = 1;
|
|
|
|
// Line extending through like infinity.
|
|
surftool->add_vertex(vec * -1048576);
|
|
surftool->add_vertex(Vector3());
|
|
surftool->add_vertex(vec * 1048576);
|
|
surftool->set_material(mat_hl);
|
|
surftool->commit(axis_gizmo[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create trackball sphere
|
|
{
|
|
trackball_sphere_gizmo.instantiate();
|
|
Ref<SurfaceTool> surftool;
|
|
surftool.instantiate();
|
|
surftool->begin(Mesh::PRIMITIVE_TRIANGLES);
|
|
|
|
const int sphere_rings = TRACKBALL_SPHERE_RINGS;
|
|
const int sphere_sectors = TRACKBALL_SPHERE_SECTORS;
|
|
const real_t sphere_radius = GIZMO_CIRCLE_SIZE;
|
|
|
|
for (int r = 0; r <= sphere_rings; ++r) {
|
|
for (int s = 0; s <= sphere_sectors; ++s) {
|
|
real_t ring_angle = Math::PI * r / sphere_rings;
|
|
real_t sector_angle = 2.0 * Math::PI * s / sphere_sectors;
|
|
|
|
Vector3 vertex(
|
|
sphere_radius * Math::sin(ring_angle) * Math::cos(sector_angle),
|
|
sphere_radius * Math::cos(ring_angle),
|
|
sphere_radius * Math::sin(ring_angle) * Math::sin(sector_angle));
|
|
|
|
surftool->set_normal(vertex.normalized());
|
|
surftool->add_vertex(vertex);
|
|
}
|
|
}
|
|
|
|
for (int r = 0; r < sphere_rings; ++r) {
|
|
for (int s = 0; s < sphere_sectors; ++s) {
|
|
int current = r * (sphere_sectors + 1) + s;
|
|
int next = current + sphere_sectors + 1;
|
|
|
|
surftool->add_index(current);
|
|
surftool->add_index(next);
|
|
surftool->add_index(current + 1);
|
|
|
|
surftool->add_index(current + 1);
|
|
surftool->add_index(next);
|
|
surftool->add_index(next + 1);
|
|
}
|
|
}
|
|
|
|
trackball_sphere_material.instantiate();
|
|
trackball_sphere_material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
|
|
trackball_sphere_material->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true);
|
|
trackball_sphere_material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
|
|
trackball_sphere_material->set_cull_mode(StandardMaterial3D::CULL_DISABLED);
|
|
trackball_sphere_material->set_albedo(Color(1.0, 1.0, 1.0, 0.0));
|
|
trackball_sphere_material->set_flag(StandardMaterial3D::FLAG_DISABLE_DEPTH_TEST, true);
|
|
|
|
trackball_sphere_material_hl = trackball_sphere_material->duplicate();
|
|
trackball_sphere_material_hl->set_albedo(Color(1.0, 1.0, 1.0, TRACKBALL_HIGHLIGHT_ALPHA));
|
|
|
|
surftool->set_material(trackball_sphere_material);
|
|
surftool->commit(trackball_sphere_gizmo);
|
|
}
|
|
|
|
_generate_selection_boxes();
|
|
}
|
|
|
|
void Node3DEditor::_update_gizmos_menu() {
|
|
gizmos_menu->clear();
|
|
|
|
for (int i = 0; i < gizmo_plugins_by_name.size(); ++i) {
|
|
if (!gizmo_plugins_by_name[i]->can_be_hidden()) {
|
|
continue;
|
|
}
|
|
String plugin_name = gizmo_plugins_by_name[i]->get_gizmo_name();
|
|
const int plugin_state = gizmo_plugins_by_name[i]->get_state();
|
|
gizmos_menu->add_multistate_item(plugin_name, 3, plugin_state, i);
|
|
const int idx = gizmos_menu->get_item_index(i);
|
|
gizmos_menu->set_item_tooltip(
|
|
idx,
|
|
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\")."));
|
|
switch (plugin_state) {
|
|
case EditorNode3DGizmoPlugin::VISIBLE:
|
|
gizmos_menu->set_item_icon(idx, get_editor_theme_icon(SNAME("GuiVisibilityVisible")));
|
|
break;
|
|
case EditorNode3DGizmoPlugin::ON_TOP:
|
|
gizmos_menu->set_item_icon(idx, get_editor_theme_icon(SNAME("GuiVisibilityXray")));
|
|
break;
|
|
case EditorNode3DGizmoPlugin::HIDDEN:
|
|
gizmos_menu->set_item_icon(idx, get_editor_theme_icon(SNAME("GuiVisibilityHidden")));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Node3DEditor::_update_gizmos_menu_theme() {
|
|
for (int i = 0; i < gizmo_plugins_by_name.size(); ++i) {
|
|
if (!gizmo_plugins_by_name[i]->can_be_hidden()) {
|
|
continue;
|
|
}
|
|
const int plugin_state = gizmo_plugins_by_name[i]->get_state();
|
|
const int idx = gizmos_menu->get_item_index(i);
|
|
switch (plugin_state) {
|
|
case EditorNode3DGizmoPlugin::VISIBLE:
|
|
gizmos_menu->set_item_icon(idx, get_editor_theme_icon(SNAME("GuiVisibilityVisible")));
|
|
break;
|
|
case EditorNode3DGizmoPlugin::ON_TOP:
|
|
gizmos_menu->set_item_icon(idx, get_editor_theme_icon(SNAME("GuiVisibilityXray")));
|
|
break;
|
|
case EditorNode3DGizmoPlugin::HIDDEN:
|
|
gizmos_menu->set_item_icon(idx, get_editor_theme_icon(SNAME("GuiVisibilityHidden")));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Node3DEditor::_init_grid() {
|
|
if (!grid_enabled) {
|
|
return;
|
|
}
|
|
Camera3D *camera = get_editor_viewport(0)->camera;
|
|
Vector3 camera_position = camera->get_position();
|
|
if (camera_position == Vector3()) {
|
|
return; // Camera3D is invalid, don't draw the grid.
|
|
}
|
|
|
|
bool orthogonal = camera->get_projection() == Camera3D::PROJECTION_ORTHOGONAL;
|
|
|
|
static LocalVector<Color> grid_colors[3];
|
|
static LocalVector<Vector3> grid_points[3];
|
|
static LocalVector<Vector3> grid_normals[3];
|
|
|
|
for (uint32_t n = 0; n < 3; n++) {
|
|
grid_colors[n].clear();
|
|
grid_points[n].clear();
|
|
grid_normals[n].clear();
|
|
}
|
|
|
|
Color primary_grid_color = EDITOR_GET("editors/3d/primary_grid_color");
|
|
Color secondary_grid_color = EDITOR_GET("editors/3d/secondary_grid_color");
|
|
int grid_size = EDITOR_GET("editors/3d/grid_size");
|
|
int primary_grid_steps = EDITOR_GET("editors/3d/primary_grid_steps");
|
|
|
|
// Which grid planes are enabled? Which should we generate?
|
|
grid_enable[0] = grid_visible[0] = orthogonal || EDITOR_GET("editors/3d/grid_xy_plane");
|
|
grid_enable[1] = grid_visible[1] = orthogonal || EDITOR_GET("editors/3d/grid_yz_plane");
|
|
grid_enable[2] = grid_visible[2] = orthogonal || EDITOR_GET("editors/3d/grid_xz_plane");
|
|
|
|
// Offsets division_level for bigger or smaller grids.
|
|
// Default value is -0.2. -1.0 gives Blender-like behavior, 0.5 gives huge grids.
|
|
real_t division_level_bias = EDITOR_GET("editors/3d/grid_division_level_bias");
|
|
// 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).
|
|
int division_level_max = EDITOR_GET("editors/3d/grid_division_level_max");
|
|
// Default smallest grid size is 8^0 (default value is 0) when primary_grid_steps is 8.
|
|
int division_level_min = EDITOR_GET("editors/3d/grid_division_level_min");
|
|
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.");
|
|
|
|
if (primary_grid_steps != 10) { // Log10 of 10 is 1.
|
|
// Change of base rule, divide by ln(10).
|
|
real_t div = Math::log((real_t)primary_grid_steps) / (real_t)2.302585092994045901094;
|
|
// Truncation (towards zero) is intentional.
|
|
division_level_max = (int)(division_level_max / div);
|
|
division_level_min = (int)(division_level_min / div);
|
|
}
|
|
|
|
for (int a = 0; a < 3; a++) {
|
|
if (!grid_enable[a]) {
|
|
continue; // If this grid plane is disabled, skip generation.
|
|
}
|
|
int b = (a + 1) % 3;
|
|
int c = (a + 2) % 3;
|
|
|
|
Vector3 normal;
|
|
normal[c] = 1.0;
|
|
|
|
real_t camera_distance = Math::abs(camera_position[c]);
|
|
|
|
if (orthogonal) {
|
|
camera_distance = camera->get_size() / 2.0;
|
|
Vector3 camera_direction = -camera->get_global_transform().get_basis().get_column(2);
|
|
Plane grid_plane = Plane(normal);
|
|
Vector3 intersection;
|
|
if (grid_plane.intersects_ray(camera_position, camera_direction, &intersection)) {
|
|
camera_position = intersection;
|
|
}
|
|
}
|
|
|
|
real_t division_level = Math::log(Math::abs(camera_distance)) / Math::log((double)primary_grid_steps) + division_level_bias;
|
|
|
|
real_t clamped_division_level = CLAMP(division_level, division_level_min, division_level_max);
|
|
real_t division_level_floored = Math::floor(clamped_division_level);
|
|
real_t division_level_decimals = clamped_division_level - division_level_floored;
|
|
|
|
real_t small_step_size = Math::pow(primary_grid_steps, division_level_floored);
|
|
real_t large_step_size = small_step_size * primary_grid_steps;
|
|
real_t center_a = large_step_size * (int)(camera_position[a] / large_step_size);
|
|
real_t center_b = large_step_size * (int)(camera_position[b] / large_step_size);
|
|
|
|
real_t bgn_a = center_a - grid_size * small_step_size;
|
|
real_t end_a = center_a + grid_size * small_step_size;
|
|
real_t bgn_b = center_b - grid_size * small_step_size;
|
|
real_t end_b = center_b + grid_size * small_step_size;
|
|
|
|
real_t fade_size = Math::pow(primary_grid_steps, division_level - 1.0);
|
|
real_t min_fade_size = Math::pow(primary_grid_steps, float(division_level_min));
|
|
real_t max_fade_size = Math::pow(primary_grid_steps, float(division_level_max));
|
|
fade_size = CLAMP(fade_size, min_fade_size, max_fade_size);
|
|
|
|
real_t grid_fade_size = (grid_size - primary_grid_steps) * fade_size;
|
|
grid_mat[c]->set_shader_parameter("grid_size", grid_fade_size);
|
|
grid_mat[c]->set_shader_parameter("orthogonal", orthogonal);
|
|
|
|
LocalVector<Vector3> &ref_grid = grid_points[c];
|
|
LocalVector<Vector3> &ref_grid_normals = grid_normals[c];
|
|
LocalVector<Color> &ref_grid_colors = grid_colors[c];
|
|
|
|
// Count our elements same as code below it.
|
|
int expected_size = 0;
|
|
for (int i = -grid_size; i <= grid_size; i++) {
|
|
const real_t position_a = center_a + i * small_step_size;
|
|
const real_t position_b = center_b + i * small_step_size;
|
|
|
|
// Don't draw lines over the origin if it's enabled.
|
|
if (!(origin_enabled && Math::is_zero_approx(position_a))) {
|
|
expected_size += 2;
|
|
}
|
|
|
|
if (!(origin_enabled && Math::is_zero_approx(position_b))) {
|
|
expected_size += 2;
|
|
}
|
|
}
|
|
|
|
int idx = 0;
|
|
ref_grid.resize(expected_size);
|
|
ref_grid_normals.resize(expected_size);
|
|
ref_grid_colors.resize(expected_size);
|
|
|
|
// In each iteration of this loop, draw one line in each direction (so two lines per loop, in each if statement).
|
|
for (int i = -grid_size; i <= grid_size; i++) {
|
|
Color line_color;
|
|
// Is this a primary line? Set the appropriate color.
|
|
if (i % primary_grid_steps == 0) {
|
|
line_color = primary_grid_color.lerp(secondary_grid_color, division_level_decimals);
|
|
} else {
|
|
line_color = secondary_grid_color;
|
|
line_color.a = line_color.a * (1 - division_level_decimals);
|
|
}
|
|
|
|
real_t position_a = center_a + i * small_step_size;
|
|
real_t position_b = center_b + i * small_step_size;
|
|
|
|
// Don't draw lines over the origin if it's enabled.
|
|
if (!(origin_enabled && Math::is_zero_approx(position_a))) {
|
|
Vector3 line_bgn;
|
|
Vector3 line_end;
|
|
line_bgn[a] = position_a;
|
|
line_end[a] = position_a;
|
|
line_bgn[b] = bgn_b;
|
|
line_end[b] = end_b;
|
|
ref_grid[idx] = line_bgn;
|
|
ref_grid[idx + 1] = line_end;
|
|
ref_grid_colors[idx] = line_color;
|
|
ref_grid_colors[idx + 1] = line_color;
|
|
ref_grid_normals[idx] = normal;
|
|
ref_grid_normals[idx + 1] = normal;
|
|
idx += 2;
|
|
}
|
|
|
|
if (!(origin_enabled && Math::is_zero_approx(position_b))) {
|
|
Vector3 line_bgn;
|
|
Vector3 line_end;
|
|
line_bgn[b] = position_b;
|
|
line_end[b] = position_b;
|
|
line_bgn[a] = bgn_a;
|
|
line_end[a] = end_a;
|
|
ref_grid[idx] = line_bgn;
|
|
ref_grid[idx + 1] = line_end;
|
|
ref_grid_colors[idx] = line_color;
|
|
ref_grid_colors[idx + 1] = line_color;
|
|
ref_grid_normals[idx] = normal;
|
|
ref_grid_normals[idx + 1] = normal;
|
|
idx += 2;
|
|
}
|
|
}
|
|
|
|
// Create a mesh from the pushed vector points and colors.
|
|
grid[c] = RenderingServer::get_singleton()->mesh_create();
|
|
Array d;
|
|
d.resize(RSE::ARRAY_MAX);
|
|
d[RSE::ARRAY_VERTEX] = (Vector<Vector3>)grid_points[c];
|
|
d[RSE::ARRAY_COLOR] = (Vector<Color>)grid_colors[c];
|
|
d[RSE::ARRAY_NORMAL] = (Vector<Vector3>)grid_normals[c];
|
|
RenderingServer::get_singleton()->mesh_add_surface_from_arrays(grid[c], RSE::PRIMITIVE_LINES, d);
|
|
RenderingServer::get_singleton()->mesh_surface_set_material(grid[c], 0, grid_mat[c]->get_rid());
|
|
grid_instance[c] = RenderingServer::get_singleton()->instance_create2(grid[c], get_tree()->get_root()->get_world_3d()->get_scenario());
|
|
|
|
// Yes, the end of this line is supposed to be a.
|
|
RenderingServer::get_singleton()->instance_set_visible(grid_instance[c], grid_visible[a]);
|
|
RenderingServer::get_singleton()->instance_geometry_set_cast_shadows_setting(grid_instance[c], RSE::SHADOW_CASTING_SETTING_OFF);
|
|
RS::get_singleton()->instance_set_layer_mask(grid_instance[c], 1 << Node3DEditorViewport::GIZMO_GRID_LAYER);
|
|
RS::get_singleton()->instance_geometry_set_flag(grid_instance[c], RSE::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true);
|
|
RS::get_singleton()->instance_geometry_set_flag(grid_instance[c], RSE::INSTANCE_FLAG_USE_BAKED_LIGHT, false);
|
|
}
|
|
}
|
|
|
|
void Node3DEditor::_finish_indicators() {
|
|
RenderingServer::get_singleton()->free_rid(origin_instance);
|
|
RenderingServer::get_singleton()->free_rid(origin_multimesh);
|
|
RenderingServer::get_singleton()->free_rid(origin_mesh);
|
|
|
|
_finish_grid();
|
|
}
|
|
|
|
void Node3DEditor::_finish_grid() {
|
|
for (int i = 0; i < 3; i++) {
|
|
RenderingServer::get_singleton()->free_rid(grid_instance[i]);
|
|
RenderingServer::get_singleton()->free_rid(grid[i]);
|
|
}
|
|
}
|
|
|
|
void Node3DEditor::update_gizmo_opacity() {
|
|
if (!origin_instance.is_valid()) {
|
|
return;
|
|
}
|
|
|
|
const float opacity = EDITOR_GET("editors/3d/manipulator_gizmo_opacity");
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
Color col = gizmo_color[i]->get_albedo();
|
|
col.a = opacity;
|
|
gizmo_color[i]->set_albedo(col);
|
|
|
|
col = gizmo_color_hl[i]->get_albedo();
|
|
col.a = 1.0;
|
|
gizmo_color_hl[i]->set_albedo(col);
|
|
|
|
col = plane_gizmo_color[i]->get_albedo();
|
|
col.a = opacity;
|
|
plane_gizmo_color[i]->set_albedo(col);
|
|
|
|
col = plane_gizmo_color_hl[i]->get_albedo();
|
|
col.a = 1.0;
|
|
plane_gizmo_color_hl[i]->set_albedo(col);
|
|
}
|
|
}
|
|
|
|
void Node3DEditor::update_grid() {
|
|
const Camera3D::ProjectionType current_projection = viewports[0]->camera->get_projection();
|
|
|
|
if (current_projection != grid_camera_last_update_perspective) {
|
|
grid_init_draw = false; // redraw
|
|
grid_camera_last_update_perspective = current_projection;
|
|
}
|
|
|
|
// Gets a orthogonal or perspective position correctly (for the grid comparison)
|
|
const Vector3 camera_position = get_editor_viewport(0)->camera->get_position();
|
|
|
|
if (!grid_init_draw || grid_camera_last_update_position.distance_squared_to(camera_position) >= 100.0f) {
|
|
_finish_grid();
|
|
_init_grid();
|
|
grid_init_draw = true;
|
|
grid_camera_last_update_position = camera_position;
|
|
}
|
|
}
|
|
|
|
void Node3DEditor::_selection_changed() {
|
|
_refresh_menu_icons();
|
|
|
|
const HashMap<ObjectID, Object *> &selection = editor_selection->get_selection();
|
|
|
|
for (const KeyValue<ObjectID, Object *> &E : selection) {
|
|
Node3D *sp = ObjectDB::get_instance<Node3D>(E.key);
|
|
if (!sp) {
|
|
continue;
|
|
}
|
|
|
|
Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);
|
|
if (!se) {
|
|
continue;
|
|
}
|
|
|
|
if (sp == editor_selection->get_top_selected_node_list().back()->get()) {
|
|
RenderingServer::get_singleton()->instance_set_base(se->sbox_instance, active_selection_box->get_rid());
|
|
RenderingServer::get_singleton()->instance_set_base(se->sbox_instance_xray, active_selection_box_xray->get_rid());
|
|
RenderingServer::get_singleton()->instance_set_base(se->sbox_instance_offset, active_selection_box->get_rid());
|
|
RenderingServer::get_singleton()->instance_set_base(se->sbox_instance_xray_offset, active_selection_box_xray->get_rid());
|
|
} else {
|
|
RenderingServer::get_singleton()->instance_set_base(se->sbox_instance, selection_box->get_rid());
|
|
RenderingServer::get_singleton()->instance_set_base(se->sbox_instance_xray, selection_box_xray->get_rid());
|
|
RenderingServer::get_singleton()->instance_set_base(se->sbox_instance_offset, selection_box->get_rid());
|
|
RenderingServer::get_singleton()->instance_set_base(se->sbox_instance_xray_offset, selection_box_xray->get_rid());
|
|
}
|
|
}
|
|
|
|
if (selected && editor_selection->get_top_selected_node_list().size() != 1) {
|
|
Vector<Ref<Node3DGizmo>> gizmos = selected->get_gizmos();
|
|
for (int i = 0; i < gizmos.size(); i++) {
|
|
Ref<EditorNode3DGizmo> seg = gizmos[i];
|
|
if (seg.is_null()) {
|
|
continue;
|
|
}
|
|
seg->set_selected(false);
|
|
}
|
|
|
|
Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected);
|
|
if (se) {
|
|
se->gizmo.unref();
|
|
se->subgizmos.clear();
|
|
}
|
|
selected->update_gizmos();
|
|
selected = nullptr;
|
|
}
|
|
|
|
// Ensure gizmo updates are performed when the selection changes
|
|
// outside of the 3D view (see GH-106713).
|
|
if (!is_visible()) {
|
|
const List<Node *> &top_selected = editor_selection->get_top_selected_node_list();
|
|
if (top_selected.size() == 1) {
|
|
Node3D *new_selected = Object::cast_to<Node3D>(top_selected.back()->get());
|
|
if (new_selected != selected) {
|
|
gizmos_dirty = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
update_transform_gizmo();
|
|
}
|
|
|
|
void Node3DEditor::refresh_dirty_gizmos() {
|
|
if (!gizmos_dirty) {
|
|
return;
|
|
}
|
|
|
|
const List<Node *> &top_selected = editor_selection->get_top_selected_node_list();
|
|
if (top_selected.size() == 1) {
|
|
Node3D *new_selected = Object::cast_to<Node3D>(top_selected.back()->get());
|
|
if (new_selected != selected) {
|
|
edit(new_selected);
|
|
}
|
|
}
|
|
gizmos_dirty = false;
|
|
}
|
|
|
|
void Node3DEditor::_refresh_menu_icons() {
|
|
bool all_locked = true;
|
|
bool all_grouped = true;
|
|
bool has_node3d_item = false;
|
|
|
|
const List<Node *> &selection = editor_selection->get_top_selected_node_list();
|
|
|
|
if (selection.is_empty()) {
|
|
all_locked = false;
|
|
all_grouped = false;
|
|
} else {
|
|
for (Node *E : selection) {
|
|
Node3D *node = Object::cast_to<Node3D>(E);
|
|
if (node) {
|
|
if (all_locked && !node->has_meta("_edit_lock_")) {
|
|
all_locked = false;
|
|
}
|
|
if (all_grouped && !node->has_meta("_edit_group_")) {
|
|
all_grouped = false;
|
|
}
|
|
has_node3d_item = true;
|
|
}
|
|
if (!all_locked && !all_grouped) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
all_locked = all_locked && has_node3d_item;
|
|
all_grouped = all_grouped && has_node3d_item;
|
|
|
|
tool_button[TOOL_LOCK_SELECTED]->set_visible(!all_locked);
|
|
tool_button[TOOL_LOCK_SELECTED]->set_disabled(!has_node3d_item);
|
|
tool_button[TOOL_UNLOCK_SELECTED]->set_visible(all_locked);
|
|
tool_button[TOOL_UNLOCK_SELECTED]->set_disabled(!has_node3d_item);
|
|
|
|
tool_button[TOOL_GROUP_SELECTED]->set_visible(!all_grouped);
|
|
tool_button[TOOL_GROUP_SELECTED]->set_disabled(!has_node3d_item);
|
|
tool_button[TOOL_UNGROUP_SELECTED]->set_visible(all_grouped);
|
|
tool_button[TOOL_UNGROUP_SELECTED]->set_disabled(!has_node3d_item);
|
|
}
|
|
|
|
template <typename T>
|
|
HashSet<T *> _get_child_nodes(Node *parent_node) {
|
|
HashSet<T *> nodes = HashSet<T *>();
|
|
T *node = Node::cast_to<T>(parent_node);
|
|
if (node) {
|
|
nodes.insert(node);
|
|
}
|
|
|
|
for (int i = 0; i < parent_node->get_child_count(); i++) {
|
|
Node *child_node = parent_node->get_child(i);
|
|
HashSet<T *> child_nodes = _get_child_nodes<T>(child_node);
|
|
for (T *I : child_nodes) {
|
|
nodes.insert(I);
|
|
}
|
|
}
|
|
|
|
return nodes;
|
|
}
|
|
|
|
HashSet<RID> _get_physics_bodies_rid(Node *node) {
|
|
HashSet<RID> rids = HashSet<RID>();
|
|
PhysicsBody3D *pb = Node::cast_to<PhysicsBody3D>(node);
|
|
if (pb) {
|
|
rids.insert(pb->get_rid());
|
|
}
|
|
HashSet<PhysicsBody3D *> child_nodes = _get_child_nodes<PhysicsBody3D>(node);
|
|
for (const PhysicsBody3D *I : child_nodes) {
|
|
rids.insert(I->get_rid());
|
|
}
|
|
|
|
return rids;
|
|
}
|
|
|
|
void Node3DEditor::snap_selected_nodes_to_floor() {
|
|
do_snap_selected_nodes_to_floor = true;
|
|
}
|
|
|
|
void Node3DEditor::_snap_selected_nodes_to_floor() {
|
|
const List<Node *> &selection = editor_selection->get_top_selected_node_list();
|
|
Dictionary snap_data;
|
|
|
|
for (Node *E : selection) {
|
|
Node3D *sp = Object::cast_to<Node3D>(E);
|
|
if (sp) {
|
|
Vector3 from;
|
|
Vector3 position_offset;
|
|
|
|
// Priorities for snapping to floor are CollisionShapes, VisualInstances and then origin
|
|
HashSet<VisualInstance3D *> vi = _get_child_nodes<VisualInstance3D>(sp);
|
|
HashSet<CollisionShape3D *> cs = _get_child_nodes<CollisionShape3D>(sp);
|
|
bool found_valid_shape = false;
|
|
|
|
if (cs.size()) {
|
|
AABB aabb;
|
|
HashSet<CollisionShape3D *>::Iterator I = cs.begin();
|
|
if ((*I)->get_shape().is_valid()) {
|
|
CollisionShape3D *collision_shape = *cs.begin();
|
|
aabb = collision_shape->get_global_transform().xform(collision_shape->get_shape()->get_debug_mesh()->get_aabb());
|
|
found_valid_shape = true;
|
|
}
|
|
|
|
for (++I; I; ++I) {
|
|
CollisionShape3D *col_shape = *I;
|
|
if (col_shape->get_shape().is_valid()) {
|
|
aabb.merge_with(col_shape->get_global_transform().xform(col_shape->get_shape()->get_debug_mesh()->get_aabb()));
|
|
found_valid_shape = true;
|
|
}
|
|
}
|
|
if (found_valid_shape) {
|
|
Vector3 size = aabb.size * Vector3(0.5, 0.0, 0.5);
|
|
from = aabb.position + size;
|
|
position_offset.y = from.y - sp->get_global_transform().origin.y;
|
|
}
|
|
}
|
|
if (!found_valid_shape && vi.size()) {
|
|
VisualInstance3D *begin = *vi.begin();
|
|
AABB aabb = begin->get_global_transform().xform(begin->get_aabb());
|
|
for (const VisualInstance3D *I : vi) {
|
|
aabb.merge_with(I->get_global_transform().xform(I->get_aabb()));
|
|
}
|
|
Vector3 size = aabb.size * Vector3(0.5, 0.0, 0.5);
|
|
from = aabb.position + size;
|
|
position_offset.y = from.y - sp->get_global_transform().origin.y;
|
|
} else if (!found_valid_shape) {
|
|
from = sp->get_global_transform().origin;
|
|
}
|
|
|
|
// We add a bit of margin to the from position to avoid it from snapping
|
|
// when the spatial is already on a floor and there's another floor under
|
|
// it
|
|
from = from + Vector3(0.0, 1, 0.0);
|
|
|
|
Dictionary d;
|
|
|
|
d["from"] = from;
|
|
d["position_offset"] = position_offset;
|
|
snap_data[sp] = d;
|
|
}
|
|
}
|
|
|
|
PhysicsDirectSpaceState3D *ss = get_tree()->get_root()->get_world_3d()->get_direct_space_state();
|
|
PhysicsDirectSpaceState3D::RayResult result;
|
|
|
|
// The maximum height an object can travel to be snapped
|
|
const float max_snap_height = 500.0;
|
|
|
|
// Will be set to `true` if at least one node from the selection was successfully snapped
|
|
bool snapped_to_floor = false;
|
|
|
|
if (!snap_data.is_empty()) {
|
|
// For snapping to be performed, there must be solid geometry under at least one of the selected nodes.
|
|
// We need to check this before snapping to register the undo/redo action only if needed.
|
|
for (const KeyValue<Variant, Variant> &kv : snap_data) {
|
|
Node *node = Object::cast_to<Node>(kv.key);
|
|
Node3D *sp = Object::cast_to<Node3D>(node);
|
|
Dictionary d = kv.value;
|
|
Vector3 from = d["from"];
|
|
Vector3 to = from - Vector3(0.0, max_snap_height, 0.0);
|
|
HashSet<RID> excluded = _get_physics_bodies_rid(sp);
|
|
|
|
PhysicsDirectSpaceState3D::RayParameters ray_params;
|
|
ray_params.from = from;
|
|
ray_params.to = to;
|
|
ray_params.exclude = excluded;
|
|
|
|
if (ss->intersect_ray(ray_params, result)) {
|
|
snapped_to_floor = true;
|
|
}
|
|
}
|
|
|
|
if (snapped_to_floor) {
|
|
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
|
undo_redo->create_action(TTR("Snap Nodes to Floor"));
|
|
|
|
// Perform snapping if at least one node can be snapped
|
|
for (const KeyValue<Variant, Variant> &kv : snap_data) {
|
|
Node *node = Object::cast_to<Node>(kv.key);
|
|
Node3D *sp = Object::cast_to<Node3D>(node);
|
|
Dictionary d = kv.value;
|
|
Vector3 from = d["from"];
|
|
Vector3 to = from - Vector3(0.0, max_snap_height, 0.0);
|
|
HashSet<RID> excluded = _get_physics_bodies_rid(sp);
|
|
|
|
PhysicsDirectSpaceState3D::RayParameters ray_params;
|
|
ray_params.from = from;
|
|
ray_params.to = to;
|
|
ray_params.exclude = excluded;
|
|
|
|
if (ss->intersect_ray(ray_params, result)) {
|
|
Vector3 position_offset = d["position_offset"];
|
|
Transform3D new_transform = sp->get_global_transform();
|
|
|
|
new_transform.origin.y = result.position.y;
|
|
new_transform.origin = new_transform.origin - position_offset;
|
|
|
|
Node3D *parent = sp->get_parent_node_3d();
|
|
Transform3D new_local_xform = parent ? parent->get_global_transform().affine_inverse() * new_transform : new_transform;
|
|
undo_redo->add_do_method(sp, "set_transform", new_local_xform);
|
|
undo_redo->add_undo_method(sp, "set_transform", sp->get_transform());
|
|
}
|
|
}
|
|
|
|
undo_redo->commit_action();
|
|
} else {
|
|
EditorNode::get_singleton()->show_warning(TTR("Couldn't find a solid floor to snap the selection to."));
|
|
}
|
|
}
|
|
}
|
|
|
|
void Node3DEditor::shortcut_input(const Ref<InputEvent> &p_event) {
|
|
ERR_FAIL_COND(p_event.is_null());
|
|
|
|
if (!is_visible_in_tree()) {
|
|
return;
|
|
}
|
|
|
|
snap_key_enabled = Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL);
|
|
}
|
|
|
|
void Node3DEditor::_sun_environ_settings_pressed() {
|
|
Vector2 pos = sun_environ_settings->get_screen_position() + sun_environ_settings->get_size();
|
|
sun_environ_popup->set_position(pos - Vector2(sun_environ_popup->get_contents_minimum_size().width / 2, 0));
|
|
sun_environ_popup->reset_size();
|
|
sun_environ_popup->popup();
|
|
// Grabbing the focus is required for Shift modifier checking to be functional
|
|
// (when the Add sun/environment buttons are pressed).
|
|
sun_environ_popup->grab_focus();
|
|
}
|
|
|
|
void Node3DEditor::_add_sun_to_scene(bool p_already_added_environment) {
|
|
sun_environ_popup->hide();
|
|
|
|
if (!p_already_added_environment && world_env_count == 0 && Input::get_singleton()->is_key_pressed(Key::SHIFT)) {
|
|
// Prevent infinite feedback loop between the sun and environment methods.
|
|
_add_environment_to_scene(true);
|
|
}
|
|
|
|
Node *base = get_tree()->get_edited_scene_root();
|
|
if (!base) {
|
|
// Create a root node so we can add child nodes to it.
|
|
SceneTreeDock::get_singleton()->add_root_node(memnew(Node3D));
|
|
base = get_tree()->get_edited_scene_root();
|
|
}
|
|
ERR_FAIL_NULL(base);
|
|
Node *new_sun = preview_sun->duplicate();
|
|
|
|
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
|
undo_redo->create_action(TTR("Add Preview Sun to Scene"));
|
|
undo_redo->add_do_method(base, "add_child", new_sun, true);
|
|
// Move to the beginning of the scene tree since more "global" nodes
|
|
// generally look better when placed at the top.
|
|
undo_redo->add_do_method(base, "move_child", new_sun, 0);
|
|
undo_redo->add_do_method(new_sun, "set_owner", base);
|
|
undo_redo->add_undo_method(base, "remove_child", new_sun);
|
|
undo_redo->add_do_reference(new_sun);
|
|
undo_redo->commit_action();
|
|
}
|
|
|
|
void Node3DEditor::_add_environment_to_scene(bool p_already_added_sun) {
|
|
sun_environ_popup->hide();
|
|
|
|
if (!p_already_added_sun && directional_light_count == 0 && Input::get_singleton()->is_key_pressed(Key::SHIFT)) {
|
|
// Prevent infinite feedback loop between the sun and environment methods.
|
|
_add_sun_to_scene(true);
|
|
}
|
|
|
|
Node *base = get_tree()->get_edited_scene_root();
|
|
if (!base) {
|
|
// Create a root node so we can add child nodes to it.
|
|
SceneTreeDock::get_singleton()->add_root_node(memnew(Node3D));
|
|
base = get_tree()->get_edited_scene_root();
|
|
}
|
|
ERR_FAIL_NULL(base);
|
|
|
|
WorldEnvironment *new_env = memnew(WorldEnvironment);
|
|
new_env->set_environment(preview_environment->get_environment()->duplicate(true));
|
|
if (GLOBAL_GET("rendering/lights_and_shadows/use_physical_light_units")) {
|
|
new_env->set_camera_attributes(preview_environment->get_camera_attributes()->duplicate(true));
|
|
}
|
|
|
|
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
|
undo_redo->create_action(TTR("Add Preview Environment to Scene"));
|
|
undo_redo->add_do_method(base, "add_child", new_env, true);
|
|
// Move to the beginning of the scene tree since more "global" nodes
|
|
// generally look better when placed at the top.
|
|
undo_redo->add_do_method(base, "move_child", new_env, 0);
|
|
undo_redo->add_do_method(new_env, "set_owner", base);
|
|
undo_redo->add_undo_method(base, "remove_child", new_env);
|
|
undo_redo->add_do_reference(new_env);
|
|
undo_redo->commit_action();
|
|
}
|
|
|
|
void Node3DEditor::_update_theme() {
|
|
tool_button[TOOL_MODE_TRANSFORM]->set_button_icon(get_editor_theme_icon(SNAME("ToolTransform")));
|
|
tool_button[TOOL_MODE_MOVE]->set_button_icon(get_editor_theme_icon(SNAME("ToolMove")));
|
|
tool_button[TOOL_MODE_ROTATE]->set_button_icon(get_editor_theme_icon(SNAME("ToolRotate")));
|
|
tool_button[TOOL_MODE_SCALE]->set_button_icon(get_editor_theme_icon(SNAME("ToolScale")));
|
|
tool_button[TOOL_MODE_SELECT]->set_button_icon(get_editor_theme_icon(SNAME("ToolSelect")));
|
|
tool_button[TOOL_MODE_LIST_SELECT]->set_button_icon(get_editor_theme_icon(SNAME("ListSelect")));
|
|
tool_button[TOOL_LOCK_SELECTED]->set_button_icon(get_editor_theme_icon(SNAME("Lock")));
|
|
tool_button[TOOL_UNLOCK_SELECTED]->set_button_icon(get_editor_theme_icon(SNAME("Unlock")));
|
|
tool_button[TOOL_GROUP_SELECTED]->set_button_icon(get_editor_theme_icon(SNAME("Group")));
|
|
tool_button[TOOL_UNGROUP_SELECTED]->set_button_icon(get_editor_theme_icon(SNAME("Ungroup")));
|
|
tool_button[TOOL_RULER]->set_button_icon(get_editor_theme_icon(SNAME("Ruler")));
|
|
|
|
tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_button_icon(get_editor_theme_icon(SNAME("Object")));
|
|
tool_option_button[TOOL_OPT_USE_SNAP]->set_button_icon(get_editor_theme_icon(SNAME("Snap")));
|
|
tool_option_button[TOOL_OPT_USE_TRACKBALL]->set_button_icon(get_editor_theme_icon(SNAME("Trackball")));
|
|
tool_option_button[TOOL_OPT_PRESERVE_CHILDREN_TRANSFORM]->set_button_icon(get_editor_theme_icon(SNAME("Pin")));
|
|
|
|
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")));
|
|
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")));
|
|
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")));
|
|
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")));
|
|
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")));
|
|
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")));
|
|
|
|
sun_button->set_button_icon(get_editor_theme_icon(SNAME("PreviewSun")));
|
|
environ_button->set_button_icon(get_editor_theme_icon(SNAME("PreviewEnvironment")));
|
|
sun_environ_settings->set_button_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl")));
|
|
|
|
sun_title->add_theme_font_override(SceneStringName(font), get_theme_font(SNAME("title_font"), SNAME("Window")));
|
|
environ_title->add_theme_font_override(SceneStringName(font), get_theme_font(SNAME("title_font"), SNAME("Window")));
|
|
|
|
sun_color->set_custom_minimum_size(Size2(0, get_theme_constant(SNAME("inspector_property_height"), EditorStringName(Editor))));
|
|
environ_sky_color->set_custom_minimum_size(Size2(0, get_theme_constant(SNAME("inspector_property_height"), EditorStringName(Editor))));
|
|
environ_ground_color->set_custom_minimum_size(Size2(0, get_theme_constant(SNAME("inspector_property_height"), EditorStringName(Editor))));
|
|
|
|
context_toolbar_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("ContextualToolbar"), EditorStringName(EditorStyles)));
|
|
}
|
|
|
|
void Node3DEditor::_notification(int p_what) {
|
|
switch (p_what) {
|
|
case NOTIFICATION_TRANSLATION_CHANGED: {
|
|
const String show_list_tooltip = TTR("Alt+RMB: Show list of all nodes at position clicked, including locked.");
|
|
tool_button[TOOL_MODE_TRANSFORM]->set_tooltip_text(vformat(TTR("%s+Drag: Rotate selected node around pivot."), keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL)) + "\n" + show_list_tooltip);
|
|
tool_button[TOOL_MODE_MOVE]->set_tooltip_text(vformat(TTR("%s+Drag: Use snap."), keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL)) + "\n" + show_list_tooltip);
|
|
tool_button[TOOL_MODE_ROTATE]->set_tooltip_text(vformat(TTR("%s+Drag: Use snap."), keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL)) + "\n" + show_list_tooltip);
|
|
tool_button[TOOL_MODE_SCALE]->set_tooltip_text(vformat(TTR("%s+Drag: Use snap."), keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL)) + "\n" + show_list_tooltip);
|
|
tool_button[TOOL_MODE_SELECT]->set_tooltip_text(show_list_tooltip);
|
|
tool_button[TOOL_MODE_LIST_SELECT]->set_tooltip_text(TTR("Show list of selectable nodes at position clicked.") + "\n" + show_list_tooltip);
|
|
tool_button[TOOL_RULER]->set_tooltip_text(TTR("LMB+Drag: Measure the distance between two points in 3D space.") + "\n" + show_list_tooltip);
|
|
_update_gizmos_menu();
|
|
_update_vertex_snap_tooltips();
|
|
} break;
|
|
|
|
case NOTIFICATION_READY: {
|
|
_menu_item_pressed(MENU_VIEW_USE_1_VIEWPORT);
|
|
|
|
_refresh_menu_icons();
|
|
|
|
get_tree()->connect("node_removed", callable_mp(this, &Node3DEditor::_node_removed));
|
|
get_tree()->connect("node_added", callable_mp(this, &Node3DEditor::_node_added));
|
|
SceneTreeDock::get_singleton()->get_tree_editor()->connect("node_changed", callable_mp(this, &Node3DEditor::_refresh_menu_icons));
|
|
editor_selection->connect("selection_changed", callable_mp(this, &Node3DEditor::_selection_changed));
|
|
|
|
_update_preview_environment();
|
|
|
|
sun_state->set_custom_minimum_size(sun_vb->get_combined_minimum_size());
|
|
environ_state->set_custom_minimum_size(environ_vb->get_combined_minimum_size());
|
|
|
|
ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &Node3DEditor::update_all_gizmos).bind(Variant()));
|
|
} break;
|
|
|
|
case NOTIFICATION_ENTER_TREE: {
|
|
_update_theme();
|
|
_register_all_gizmos();
|
|
_init_indicators();
|
|
update_all_gizmos();
|
|
} break;
|
|
|
|
case NOTIFICATION_EXIT_TREE: {
|
|
_finish_indicators();
|
|
} break;
|
|
|
|
case NOTIFICATION_THEME_CHANGED: {
|
|
_update_theme();
|
|
_update_gizmos_menu_theme();
|
|
sun_title->add_theme_font_override(SceneStringName(font), get_theme_font(SNAME("title_font"), SNAME("Window")));
|
|
environ_title->add_theme_font_override(SceneStringName(font), get_theme_font(SNAME("title_font"), SNAME("Window")));
|
|
} break;
|
|
|
|
case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
|
|
if (EditorSettings::get_singleton()->check_changed_settings_in_group("editors/3d")) {
|
|
const Color selection_box_color = EDITOR_GET("editors/3d/selection_box_color");
|
|
const Color active_selection_box_color = EDITOR_GET("editors/3d/active_selection_box_color");
|
|
|
|
if (selection_box_color != selection_box_mat->get_albedo()) {
|
|
selection_box_mat->set_albedo(selection_box_color);
|
|
selection_box_mat_xray->set_albedo(selection_box_color * Color(1, 1, 1, 0.15));
|
|
}
|
|
|
|
if (active_selection_box_color != active_selection_box_mat->get_albedo()) {
|
|
active_selection_box_mat->set_albedo(active_selection_box_color);
|
|
active_selection_box_mat_xray->set_albedo(active_selection_box_color * Color(1, 1, 1, 0.15));
|
|
}
|
|
|
|
gizmo_view_rotation_scale = GIZMO_CIRCLE_SIZE * (float)EDITOR_GET("editors/3d/view_plane_rotation_gizmo_scale");
|
|
|
|
// Update grid color by rebuilding grid.
|
|
_finish_grid();
|
|
_init_grid();
|
|
|
|
for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) {
|
|
viewports[i]->update_transform_gizmo_view();
|
|
}
|
|
update_gizmo_opacity();
|
|
}
|
|
if (EditorSettings::get_singleton()->check_changed_settings_in_group("editors/3d_gizmos/gizmo_settings")) {
|
|
CollisionShape3DGizmoPlugin::set_show_only_when_selected(EDITOR_GET("editors/3d_gizmos/gizmo_settings/show_collision_shapes_only_when_selected"));
|
|
update_all_gizmos();
|
|
}
|
|
_update_vertex_snap_tooltips();
|
|
} break;
|
|
|
|
case NOTIFICATION_PHYSICS_PROCESS: {
|
|
if (do_snap_selected_nodes_to_floor) {
|
|
_snap_selected_nodes_to_floor();
|
|
do_snap_selected_nodes_to_floor = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Node3DEditor::is_subgizmo_selected(int p_id) {
|
|
Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr;
|
|
if (se) {
|
|
return se->subgizmos.has(p_id);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Node3DEditor::is_current_selected_gizmo(const EditorNode3DGizmo *p_gizmo) {
|
|
Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr;
|
|
if (se) {
|
|
return se->gizmo == p_gizmo;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Vector<int> Node3DEditor::get_subgizmo_selection() {
|
|
Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr;
|
|
|
|
Vector<int> ret;
|
|
if (se) {
|
|
for (const KeyValue<int, Transform3D> &E : se->subgizmos) {
|
|
ret.push_back(E.key);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void Node3DEditor::clear_subgizmo_selection(Object *p_obj) {
|
|
_clear_subgizmo_selection(p_obj);
|
|
}
|
|
|
|
void Node3DEditor::add_control_to_menu_panel(Control *p_control) {
|
|
ERR_FAIL_NULL(p_control);
|
|
ERR_FAIL_COND(p_control->get_parent());
|
|
|
|
VSeparator *sep = memnew(VSeparator);
|
|
context_toolbar_hbox->add_child(sep);
|
|
context_toolbar_hbox->add_child(p_control);
|
|
context_toolbar_separators[p_control] = sep;
|
|
|
|
p_control->connect(SceneStringName(visibility_changed), callable_mp(this, &Node3DEditor::_update_context_toolbar));
|
|
|
|
_update_context_toolbar();
|
|
}
|
|
|
|
void Node3DEditor::remove_control_from_menu_panel(Control *p_control) {
|
|
ERR_FAIL_NULL(p_control);
|
|
ERR_FAIL_COND(p_control->get_parent() != context_toolbar_hbox);
|
|
|
|
p_control->disconnect(SceneStringName(visibility_changed), callable_mp(this, &Node3DEditor::_update_context_toolbar));
|
|
|
|
VSeparator *sep = context_toolbar_separators[p_control];
|
|
context_toolbar_hbox->remove_child(sep);
|
|
context_toolbar_hbox->remove_child(p_control);
|
|
context_toolbar_separators.erase(p_control);
|
|
memdelete(sep);
|
|
|
|
_update_context_toolbar();
|
|
}
|
|
|
|
void Node3DEditor::_update_context_toolbar() {
|
|
bool has_visible = false;
|
|
bool first_visible = false;
|
|
|
|
for (int i = 0; i < context_toolbar_hbox->get_child_count(); i++) {
|
|
Control *child = Object::cast_to<Control>(context_toolbar_hbox->get_child(i));
|
|
if (!child || !context_toolbar_separators.has(child)) {
|
|
continue;
|
|
}
|
|
if (child->is_visible()) {
|
|
first_visible = !has_visible;
|
|
has_visible = true;
|
|
}
|
|
|
|
VSeparator *sep = context_toolbar_separators[child];
|
|
sep->set_visible(!first_visible && child->is_visible());
|
|
}
|
|
|
|
context_toolbar_panel->set_visible(has_visible);
|
|
}
|
|
|
|
void Node3DEditor::set_can_preview(Camera3D *p_preview) {
|
|
for (int i = 0; i < 4; i++) {
|
|
viewports[i]->set_can_preview(p_preview);
|
|
}
|
|
|
|
viewports[last_used_viewport]->switch_preview_camera(p_preview);
|
|
}
|
|
|
|
VSplitContainer *Node3DEditor::get_shader_split() {
|
|
return shader_split;
|
|
}
|
|
|
|
Node3DEditorViewport *Node3DEditor::get_last_used_viewport() {
|
|
return viewports[last_used_viewport];
|
|
}
|
|
|
|
void Node3DEditor::set_freelook_viewport(Node3DEditorViewport *p_viewport) {
|
|
freelook_viewport = p_viewport;
|
|
}
|
|
|
|
Node3DEditorViewport *Node3DEditor::get_freelook_viewport() const {
|
|
return freelook_viewport;
|
|
}
|
|
|
|
void Node3DEditor::add_control_to_left_panel(Control *p_control) {
|
|
left_panel_split->add_child(p_control);
|
|
left_panel_split->move_child(p_control, 0);
|
|
}
|
|
|
|
void Node3DEditor::add_control_to_right_panel(Control *p_control) {
|
|
right_panel_split->add_child(p_control);
|
|
right_panel_split->move_child(p_control, 1);
|
|
}
|
|
|
|
void Node3DEditor::remove_control_from_left_panel(Control *p_control) {
|
|
left_panel_split->remove_child(p_control);
|
|
}
|
|
|
|
void Node3DEditor::remove_control_from_right_panel(Control *p_control) {
|
|
right_panel_split->remove_child(p_control);
|
|
}
|
|
|
|
void Node3DEditor::move_control_to_left_panel(Control *p_control) {
|
|
ERR_FAIL_NULL(p_control);
|
|
if (p_control->get_parent() == left_panel_split) {
|
|
return;
|
|
}
|
|
|
|
ERR_FAIL_COND(p_control->get_parent() != right_panel_split);
|
|
right_panel_split->remove_child(p_control);
|
|
|
|
add_control_to_left_panel(p_control);
|
|
}
|
|
|
|
void Node3DEditor::move_control_to_right_panel(Control *p_control) {
|
|
ERR_FAIL_NULL(p_control);
|
|
if (p_control->get_parent() == right_panel_split) {
|
|
return;
|
|
}
|
|
|
|
ERR_FAIL_COND(p_control->get_parent() != left_panel_split);
|
|
left_panel_split->remove_child(p_control);
|
|
|
|
add_control_to_right_panel(p_control);
|
|
}
|
|
|
|
void Node3DEditor::_request_gizmo(Object *p_obj) {
|
|
Node3D *sp = Object::cast_to<Node3D>(p_obj);
|
|
if (!sp) {
|
|
return;
|
|
}
|
|
|
|
bool is_selected = (sp == selected);
|
|
|
|
Node *edited_scene = EditorNode::get_singleton()->get_edited_scene();
|
|
if (edited_scene && (sp == edited_scene || (sp->get_owner() && edited_scene->is_ancestor_of(sp)))) {
|
|
for (int i = 0; i < gizmo_plugins_by_priority.size(); ++i) {
|
|
Ref<EditorNode3DGizmo> seg = gizmo_plugins_by_priority.write[i]->get_gizmo(sp);
|
|
|
|
if (seg.is_valid()) {
|
|
sp->add_gizmo(seg);
|
|
|
|
if (is_selected != seg->is_selected()) {
|
|
seg->set_selected(is_selected);
|
|
}
|
|
}
|
|
}
|
|
if (!sp->get_gizmos().is_empty()) {
|
|
sp->update_gizmos();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Node3DEditor::_request_gizmo_for_id(ObjectID p_id) {
|
|
Node3D *node = ObjectDB::get_instance<Node3D>(p_id);
|
|
if (node) {
|
|
_request_gizmo(node);
|
|
}
|
|
}
|
|
|
|
void Node3DEditor::_set_subgizmo_selection(Object *p_obj, Ref<Node3DGizmo> p_gizmo, int p_id, Transform3D p_transform) {
|
|
if (p_id == -1) {
|
|
_clear_subgizmo_selection(p_obj);
|
|
return;
|
|
}
|
|
|
|
Node3D *sp = nullptr;
|
|
if (p_obj) {
|
|
sp = Object::cast_to<Node3D>(p_obj);
|
|
} else {
|
|
sp = selected;
|
|
}
|
|
|
|
if (!sp) {
|
|
return;
|
|
}
|
|
|
|
Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);
|
|
if (se) {
|
|
se->subgizmos.clear();
|
|
se->subgizmos.insert(p_id, p_transform);
|
|
se->gizmo = p_gizmo;
|
|
sp->update_gizmos();
|
|
update_transform_gizmo();
|
|
}
|
|
}
|
|
|
|
void Node3DEditor::_clear_subgizmo_selection(Object *p_obj) {
|
|
Node3D *sp = nullptr;
|
|
if (p_obj) {
|
|
sp = Object::cast_to<Node3D>(p_obj);
|
|
} else {
|
|
sp = selected;
|
|
}
|
|
|
|
if (!sp) {
|
|
return;
|
|
}
|
|
|
|
Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);
|
|
if (se) {
|
|
se->subgizmos.clear();
|
|
se->gizmo.unref();
|
|
sp->update_gizmos();
|
|
update_transform_gizmo();
|
|
}
|
|
}
|
|
|
|
void Node3DEditor::_toggle_maximize_view(Object *p_viewport) {
|
|
if (!p_viewport) {
|
|
return;
|
|
}
|
|
Node3DEditorViewport *current_viewport = Object::cast_to<Node3DEditorViewport>(p_viewport);
|
|
if (!current_viewport) {
|
|
return;
|
|
}
|
|
|
|
int index = -1;
|
|
bool maximized = false;
|
|
for (int i = 0; i < 4; i++) {
|
|
if (viewports[i] == current_viewport) {
|
|
index = i;
|
|
if (current_viewport->get_global_rect() == viewport_base->get_global_rect()) {
|
|
maximized = true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (index == -1) {
|
|
return;
|
|
}
|
|
|
|
if (!maximized) {
|
|
for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) {
|
|
if (i == (uint32_t)index) {
|
|
viewports[i]->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
|
|
} else {
|
|
viewports[i]->hide();
|
|
}
|
|
}
|
|
} else {
|
|
for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) {
|
|
viewports[i]->show();
|
|
}
|
|
|
|
if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT))) {
|
|
_menu_item_pressed(MENU_VIEW_USE_1_VIEWPORT);
|
|
} else if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS))) {
|
|
_menu_item_pressed(MENU_VIEW_USE_2_VIEWPORTS);
|
|
} else if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT))) {
|
|
_menu_item_pressed(MENU_VIEW_USE_2_VIEWPORTS_ALT);
|
|
} else if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS))) {
|
|
_menu_item_pressed(MENU_VIEW_USE_3_VIEWPORTS);
|
|
} else if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT))) {
|
|
_menu_item_pressed(MENU_VIEW_USE_3_VIEWPORTS_ALT);
|
|
} else if (view_layout_menu->get_popup()->is_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS))) {
|
|
_menu_item_pressed(MENU_VIEW_USE_4_VIEWPORTS);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Node3DEditor::_viewport_clicked(int p_viewport_idx) {
|
|
last_used_viewport = p_viewport_idx;
|
|
}
|
|
|
|
void Node3DEditor::_node_added(Node *p_node) {
|
|
if (EditorNode::get_singleton()->get_scene_root()->is_ancestor_of(p_node)) {
|
|
if (Object::cast_to<WorldEnvironment>(p_node)) {
|
|
world_env_count++;
|
|
if (world_env_count == 1) {
|
|
_update_preview_environment();
|
|
}
|
|
} else if (Object::cast_to<DirectionalLight3D>(p_node)) {
|
|
directional_light_count++;
|
|
if (directional_light_count == 1) {
|
|
_update_preview_environment();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Node3DEditor::_node_removed(Node *p_node) {
|
|
if (EditorNode::get_singleton()->get_scene_root()->is_ancestor_of(p_node)) {
|
|
if (Object::cast_to<WorldEnvironment>(p_node)) {
|
|
world_env_count--;
|
|
if (world_env_count == 0) {
|
|
_update_preview_environment();
|
|
}
|
|
} else if (Object::cast_to<DirectionalLight3D>(p_node)) {
|
|
directional_light_count--;
|
|
if (directional_light_count == 0) {
|
|
_update_preview_environment();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (p_node == selected) {
|
|
Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected);
|
|
if (se) {
|
|
se->gizmo.unref();
|
|
se->subgizmos.clear();
|
|
}
|
|
selected = nullptr;
|
|
update_transform_gizmo();
|
|
}
|
|
}
|
|
|
|
void Node3DEditor::_register_all_gizmos() {
|
|
add_gizmo_plugin(Ref<Camera3DGizmoPlugin>(memnew(Camera3DGizmoPlugin)));
|
|
add_gizmo_plugin(Ref<Light3DGizmoPlugin>(memnew(Light3DGizmoPlugin)));
|
|
add_gizmo_plugin(Ref<AudioStreamPlayer3DGizmoPlugin>(memnew(AudioStreamPlayer3DGizmoPlugin)));
|
|
add_gizmo_plugin(Ref<AudioListener3DGizmoPlugin>(memnew(AudioListener3DGizmoPlugin)));
|
|
add_gizmo_plugin(Ref<MeshInstance3DGizmoPlugin>(memnew(MeshInstance3DGizmoPlugin)));
|
|
add_gizmo_plugin(Ref<OccluderInstance3DGizmoPlugin>(memnew(OccluderInstance3DGizmoPlugin)));
|
|
add_gizmo_plugin(Ref<SoftBody3DGizmoPlugin>(memnew(SoftBody3DGizmoPlugin)));
|
|
add_gizmo_plugin(Ref<SpriteBase3DGizmoPlugin>(memnew(SpriteBase3DGizmoPlugin)));
|
|
add_gizmo_plugin(Ref<Label3DGizmoPlugin>(memnew(Label3DGizmoPlugin)));
|
|
add_gizmo_plugin(Ref<GeometryInstance3DGizmoPlugin>(memnew(GeometryInstance3DGizmoPlugin)));
|
|
add_gizmo_plugin(Ref<Marker3DGizmoPlugin>(memnew(Marker3DGizmoPlugin)));
|
|
add_gizmo_plugin(Ref<RayCast3DGizmoPlugin>(memnew(RayCast3DGizmoPlugin)));
|
|
add_gizmo_plugin(Ref<ShapeCast3DGizmoPlugin>(memnew(ShapeCast3DGizmoPlugin)));
|
|
add_gizmo_plugin(Ref<SpringArm3DGizmoPlugin>(memnew(SpringArm3DGizmoPlugin)));
|
|
add_gizmo_plugin(Ref<SpringBoneCollision3DGizmoPlugin>(memnew(SpringBoneCollision3DGizmoPlugin)));
|
|
add_gizmo_plugin(Ref<SpringBoneSimulator3DGizmoPlugin>(memnew(SpringBoneSimulator3DGizmoPlugin)));
|
|
add_gizmo_plugin(Ref<VehicleWheel3DGizmoPlugin>(memnew(VehicleWheel3DGizmoPlugin)));
|
|
add_gizmo_plugin(Ref<VisibleOnScreenNotifier3DGizmoPlugin>(memnew(VisibleOnScreenNotifier3DGizmoPlugin)));
|
|
add_gizmo_plugin(Ref<GPUParticles3DGizmoPlugin>(memnew(GPUParticles3DGizmoPlugin)));
|
|
add_gizmo_plugin(Ref<GPUParticlesCollision3DGizmoPlugin>(memnew(GPUParticlesCollision3DGizmoPlugin)));
|
|
add_gizmo_plugin(Ref<Particles3DEmissionShapeGizmoPlugin>(memnew(Particles3DEmissionShapeGizmoPlugin)));
|
|
add_gizmo_plugin(Ref<CPUParticles3DGizmoPlugin>(memnew(CPUParticles3DGizmoPlugin)));
|
|
add_gizmo_plugin(Ref<ReflectionProbeGizmoPlugin>(memnew(ReflectionProbeGizmoPlugin)));
|
|
add_gizmo_plugin(Ref<DecalGizmoPlugin>(memnew(DecalGizmoPlugin)));
|
|
add_gizmo_plugin(Ref<VoxelGIGizmoPlugin>(memnew(VoxelGIGizmoPlugin)));
|
|
add_gizmo_plugin(Ref<LightmapGIGizmoPlugin>(memnew(LightmapGIGizmoPlugin)));
|
|
add_gizmo_plugin(Ref<LightmapProbeGizmoPlugin>(memnew(LightmapProbeGizmoPlugin)));
|
|
add_gizmo_plugin(Ref<CollisionObject3DGizmoPlugin>(memnew(CollisionObject3DGizmoPlugin)));
|
|
add_gizmo_plugin(Ref<CollisionShape3DGizmoPlugin>(memnew(CollisionShape3DGizmoPlugin)));
|
|
add_gizmo_plugin(Ref<CollisionPolygon3DGizmoPlugin>(memnew(CollisionPolygon3DGizmoPlugin)));
|
|
add_gizmo_plugin(Ref<Joint3DGizmoPlugin>(memnew(Joint3DGizmoPlugin)));
|
|
add_gizmo_plugin(Ref<PhysicalBone3DGizmoPlugin>(memnew(PhysicalBone3DGizmoPlugin)));
|
|
add_gizmo_plugin(Ref<FogVolumeGizmoPlugin>(memnew(FogVolumeGizmoPlugin)));
|
|
add_gizmo_plugin(Ref<TwoBoneIK3DGizmoPlugin>(memnew(TwoBoneIK3DGizmoPlugin)));
|
|
add_gizmo_plugin(Ref<ChainIK3DGizmoPlugin>(memnew(ChainIK3DGizmoPlugin)));
|
|
}
|
|
|
|
void Node3DEditor::_bind_methods() {
|
|
ClassDB::bind_method("_get_editor_data", &Node3DEditor::_get_editor_data);
|
|
ClassDB::bind_method("_request_gizmo", &Node3DEditor::_request_gizmo);
|
|
ClassDB::bind_method("_request_gizmo_for_id", &Node3DEditor::_request_gizmo_for_id);
|
|
ClassDB::bind_method("_set_subgizmo_selection", &Node3DEditor::_set_subgizmo_selection);
|
|
ClassDB::bind_method("_clear_subgizmo_selection", &Node3DEditor::_clear_subgizmo_selection);
|
|
ClassDB::bind_method("_refresh_menu_icons", &Node3DEditor::_refresh_menu_icons);
|
|
ClassDB::bind_method("_preview_settings_changed", &Node3DEditor::_preview_settings_changed);
|
|
|
|
ClassDB::bind_method("update_all_gizmos", &Node3DEditor::update_all_gizmos);
|
|
ClassDB::bind_method("update_transform_gizmo", &Node3DEditor::update_transform_gizmo);
|
|
|
|
ADD_SIGNAL(MethodInfo("transform_key_request"));
|
|
ADD_SIGNAL(MethodInfo("item_lock_status_changed"));
|
|
ADD_SIGNAL(MethodInfo("item_group_status_changed"));
|
|
}
|
|
|
|
void Node3DEditor::clear() {
|
|
settings_fov->set_value(EDITOR_GET("editors/3d/default_fov"));
|
|
settings_znear->set_value(EDITOR_GET("editors/3d/default_z_near"));
|
|
settings_zfar->set_value(EDITOR_GET("editors/3d/default_z_far"));
|
|
|
|
snap_translate_value = EditorSettings::get_singleton()->get_project_metadata("3d_editor", "snap_translate_value", 1);
|
|
snap_rotate_value = EditorSettings::get_singleton()->get_project_metadata("3d_editor", "snap_rotate_value", 15);
|
|
snap_scale_value = EditorSettings::get_singleton()->get_project_metadata("3d_editor", "snap_scale_value", 10);
|
|
_snap_update();
|
|
|
|
for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) {
|
|
viewports[i]->reset();
|
|
}
|
|
|
|
if (origin_instance.is_valid()) {
|
|
RenderingServer::get_singleton()->instance_set_visible(origin_instance, true);
|
|
}
|
|
|
|
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_ORIGIN), true);
|
|
for (int i = 0; i < 3; ++i) {
|
|
if (grid_enable[i]) {
|
|
grid_visible[i] = true;
|
|
}
|
|
}
|
|
|
|
for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) {
|
|
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);
|
|
viewports[i]->viewport->set_as_audio_listener_3d(i == 0);
|
|
}
|
|
|
|
view_layout_menu->get_popup()->set_item_checked(view_layout_menu->get_popup()->get_item_index(MENU_VIEW_GRID), true);
|
|
grid_enabled = true;
|
|
grid_init_draw = false;
|
|
}
|
|
|
|
void Node3DEditor::_sun_direction_draw() {
|
|
sun_direction->draw_rect(Rect2(Vector2(), sun_direction->get_size()), Color(1, 1, 1, 1));
|
|
Vector3 z_axis = preview_sun->get_transform().basis.get_column(Vector3::AXIS_Z);
|
|
z_axis = get_editor_viewport(0)->camera->get_camera_transform().basis.xform_inv(z_axis);
|
|
sun_direction_material->set_shader_parameter("sun_direction", Vector3(z_axis.x, -z_axis.y, z_axis.z));
|
|
Color color = sun_color->get_pick_color() * sun_energy->get_value();
|
|
sun_direction_material->set_shader_parameter("sun_color", Vector3(color.r, color.g, color.b));
|
|
}
|
|
|
|
void Node3DEditor::_preview_settings_changed() {
|
|
if (sun_environ_updating) {
|
|
return;
|
|
}
|
|
|
|
{ // preview sun
|
|
sun_rotation.x = Math::deg_to_rad(-sun_angle_altitude->get_value());
|
|
sun_rotation.y = Math::deg_to_rad(180.0 - sun_angle_azimuth->get_value());
|
|
Transform3D t;
|
|
t.basis = Basis::from_euler(Vector3(sun_rotation.x, sun_rotation.y, 0));
|
|
preview_sun->set_transform(t);
|
|
sun_direction->queue_redraw();
|
|
preview_sun->set_param(Light3D::PARAM_ENERGY, sun_energy->get_value());
|
|
preview_sun->set_param(Light3D::PARAM_SHADOW_MAX_DISTANCE, sun_shadow_max_distance->get_value());
|
|
preview_sun->set_color(sun_color->get_pick_color());
|
|
}
|
|
|
|
{ //preview env
|
|
sky_material->set_energy_multiplier(environ_energy->get_value());
|
|
Color hz_color = environ_sky_color->get_pick_color().lerp(environ_ground_color->get_pick_color(), 0.5);
|
|
float hz_lum = hz_color.get_luminance() * 3.333;
|
|
hz_color = hz_color.lerp(Color(hz_lum, hz_lum, hz_lum), 0.5);
|
|
sky_material->set_sky_top_color(environ_sky_color->get_pick_color());
|
|
sky_material->set_sky_horizon_color(hz_color);
|
|
sky_material->set_ground_bottom_color(environ_ground_color->get_pick_color());
|
|
sky_material->set_ground_horizon_color(hz_color);
|
|
|
|
environment->set_ssao_enabled(environ_ao_button->is_pressed());
|
|
environment->set_glow_enabled(environ_glow_button->is_pressed());
|
|
environment->set_sdfgi_enabled(environ_gi_button->is_pressed());
|
|
environment->set_tonemapper(environ_tonemap_button->is_pressed() ? Environment::TONE_MAPPER_FILMIC : Environment::TONE_MAPPER_LINEAR);
|
|
}
|
|
}
|
|
|
|
void Node3DEditor::_load_default_preview_settings() {
|
|
sun_environ_updating = true;
|
|
|
|
// These default rotations place the preview sun at an angular altitude
|
|
// of 60 degrees (must be negative) and an azimuth of 30 degrees clockwise
|
|
// from north (or 150 CCW from south), from north east, facing south west.
|
|
// On any not-tidally-locked planet, a sun would have an angular altitude
|
|
// of 60 degrees as the average of all points on the sphere at noon.
|
|
// The azimuth choice is arbitrary, but ideally shouldn't be on an axis.
|
|
sun_rotation = Vector2(-Math::deg_to_rad(60.0), Math::deg_to_rad(150.0));
|
|
|
|
sun_angle_altitude->set_value_no_signal(-Math::rad_to_deg(sun_rotation.x));
|
|
sun_angle_azimuth->set_value_no_signal(180.0 - Math::rad_to_deg(sun_rotation.y));
|
|
sun_direction->queue_redraw();
|
|
environ_sky_color->set_pick_color(Color(0.385, 0.454, 0.55));
|
|
environ_ground_color->set_pick_color(Color(0.2, 0.169, 0.133));
|
|
environ_energy->set_value_no_signal(1.0);
|
|
if (OS::get_singleton()->get_current_rendering_method() != "gl_compatibility" && OS::get_singleton()->get_current_rendering_method() != "dummy") {
|
|
environ_glow_button->set_pressed_no_signal(true);
|
|
}
|
|
environ_tonemap_button->set_pressed_no_signal(false);
|
|
environ_ao_button->set_pressed_no_signal(false);
|
|
environ_gi_button->set_pressed_no_signal(false);
|
|
sun_shadow_max_distance->set_value_no_signal(100);
|
|
|
|
sun_color->set_pick_color(Color(1, 1, 1));
|
|
sun_energy->set_value_no_signal(1.0);
|
|
|
|
sun_environ_updating = false;
|
|
}
|
|
|
|
void Node3DEditor::_update_preview_environment() {
|
|
bool disable_light = directional_light_count > 0 || !sun_button->is_pressed();
|
|
|
|
sun_button->set_disabled(directional_light_count > 0);
|
|
|
|
if (disable_light) {
|
|
if (preview_sun->get_parent()) {
|
|
preview_sun->get_parent()->remove_child(preview_sun);
|
|
sun_state->show();
|
|
sun_vb->hide();
|
|
preview_sun_dangling = true;
|
|
}
|
|
|
|
if (directional_light_count > 0) {
|
|
sun_state->set_text(TTRC("Scene contains\nDirectionalLight3D.\nPreview disabled."));
|
|
} else {
|
|
sun_state->set_text(TTRC("Preview disabled."));
|
|
}
|
|
|
|
} else {
|
|
if (!preview_sun->get_parent()) {
|
|
add_child(preview_sun, true);
|
|
sun_state->hide();
|
|
sun_vb->show();
|
|
preview_sun_dangling = false;
|
|
}
|
|
}
|
|
|
|
sun_angle_altitude->set_value_no_signal(-Math::rad_to_deg(sun_rotation.x));
|
|
sun_angle_azimuth->set_value_no_signal(180.0 - Math::rad_to_deg(sun_rotation.y));
|
|
|
|
bool disable_env = world_env_count > 0 || !environ_button->is_pressed();
|
|
|
|
environ_button->set_disabled(world_env_count > 0);
|
|
|
|
if (disable_env) {
|
|
if (preview_environment->get_parent()) {
|
|
preview_environment->get_parent()->remove_child(preview_environment);
|
|
environ_state->show();
|
|
environ_vb->hide();
|
|
preview_env_dangling = true;
|
|
}
|
|
if (world_env_count > 0) {
|
|
environ_state->set_text(TTRC("Scene contains\nWorldEnvironment.\nPreview disabled."));
|
|
} else {
|
|
environ_state->set_text(TTRC("Preview disabled."));
|
|
}
|
|
|
|
} else {
|
|
if (!preview_environment->get_parent()) {
|
|
add_child(preview_environment);
|
|
environ_state->hide();
|
|
environ_vb->show();
|
|
preview_env_dangling = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Node3DEditor::_sun_direction_input(const Ref<InputEvent> &p_event) {
|
|
Ref<InputEventMouseMotion> mm = p_event;
|
|
if (mm.is_valid() && mm->get_button_mask().has_flag(MouseButtonMask::LEFT)) {
|
|
sun_rotation.x += mm->get_relative().y * (0.02 * EDSCALE);
|
|
sun_rotation.y -= mm->get_relative().x * (0.02 * EDSCALE);
|
|
sun_rotation.x = CLAMP(sun_rotation.x, -Math::TAU / 4, Math::TAU / 4);
|
|
|
|
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
|
undo_redo->create_action(TTR("Set Preview Sun Direction"), UndoRedo::MergeMode::MERGE_ENDS);
|
|
undo_redo->add_do_method(sun_angle_altitude, "set_value_no_signal", -Math::rad_to_deg(sun_rotation.x));
|
|
undo_redo->add_undo_method(sun_angle_altitude, "set_value_no_signal", sun_angle_altitude->get_value());
|
|
undo_redo->add_do_method(sun_angle_azimuth, "set_value_no_signal", 180.0 - Math::rad_to_deg(sun_rotation.y));
|
|
undo_redo->add_undo_method(sun_angle_azimuth, "set_value_no_signal", sun_angle_azimuth->get_value());
|
|
undo_redo->add_do_method(this, "_preview_settings_changed");
|
|
undo_redo->add_undo_method(this, "_preview_settings_changed");
|
|
undo_redo->commit_action();
|
|
}
|
|
}
|
|
|
|
void Node3DEditor::_sun_direction_set_altitude(float p_altitude) {
|
|
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
|
undo_redo->create_action(TTR("Set Preview Sun Altitude"), UndoRedo::MergeMode::MERGE_ENDS);
|
|
undo_redo->add_do_method(sun_angle_altitude, "set_value_no_signal", p_altitude);
|
|
undo_redo->add_undo_method(sun_angle_altitude, "set_value_no_signal", -Math::rad_to_deg(sun_rotation.x));
|
|
undo_redo->add_do_method(this, "_preview_settings_changed");
|
|
undo_redo->add_undo_method(this, "_preview_settings_changed");
|
|
undo_redo->commit_action();
|
|
}
|
|
|
|
void Node3DEditor::_sun_direction_set_azimuth(float p_azimuth) {
|
|
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
|
undo_redo->create_action(TTR("Set Preview Sun Azimuth"), UndoRedo::MergeMode::MERGE_ENDS);
|
|
undo_redo->add_do_method(sun_angle_azimuth, "set_value_no_signal", p_azimuth);
|
|
undo_redo->add_undo_method(sun_angle_azimuth, "set_value_no_signal", 180.0 - Math::rad_to_deg(sun_rotation.y));
|
|
undo_redo->add_do_method(this, "_preview_settings_changed");
|
|
undo_redo->add_undo_method(this, "_preview_settings_changed");
|
|
undo_redo->commit_action();
|
|
}
|
|
|
|
void Node3DEditor::_sun_set_color(const Color &p_color) {
|
|
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
|
undo_redo->create_action(TTR("Set Preview Sun Color"), UndoRedo::MergeMode::MERGE_ENDS);
|
|
undo_redo->add_do_method(sun_color, "set_pick_color", p_color);
|
|
undo_redo->add_undo_method(sun_color, "set_pick_color", preview_sun->get_color());
|
|
undo_redo->add_do_method(this, "_preview_settings_changed");
|
|
undo_redo->add_undo_method(this, "_preview_settings_changed");
|
|
undo_redo->commit_action();
|
|
}
|
|
|
|
void Node3DEditor::_sun_set_energy(float p_energy) {
|
|
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
|
undo_redo->create_action(TTR("Set Preview Sun Energy"), UndoRedo::MergeMode::MERGE_ENDS);
|
|
undo_redo->add_do_method(sun_energy, "set_value_no_signal", p_energy);
|
|
undo_redo->add_undo_method(sun_energy, "set_value_no_signal", preview_sun->get_param(Light3D::PARAM_ENERGY));
|
|
undo_redo->add_do_method(this, "_preview_settings_changed");
|
|
undo_redo->add_undo_method(this, "_preview_settings_changed");
|
|
undo_redo->commit_action();
|
|
}
|
|
|
|
void Node3DEditor::_sun_set_shadow_max_distance(float p_shadow_max_distance) {
|
|
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
|
undo_redo->create_action(TTR("Set Preview Sun Max Shadow Distance"), UndoRedo::MergeMode::MERGE_ENDS);
|
|
undo_redo->add_do_method(sun_shadow_max_distance, "set_value_no_signal", p_shadow_max_distance);
|
|
undo_redo->add_undo_method(sun_shadow_max_distance, "set_value_no_signal", preview_sun->get_param(Light3D::PARAM_SHADOW_MAX_DISTANCE));
|
|
undo_redo->add_do_method(this, "_preview_settings_changed");
|
|
undo_redo->add_undo_method(this, "_preview_settings_changed");
|
|
undo_redo->commit_action();
|
|
}
|
|
|
|
void Node3DEditor::_environ_set_sky_color(const Color &p_color) {
|
|
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
|
undo_redo->create_action(TTR("Set Preview Environment Sky Color"), UndoRedo::MergeMode::MERGE_ENDS);
|
|
undo_redo->add_do_method(environ_sky_color, "set_pick_color", p_color);
|
|
undo_redo->add_undo_method(environ_sky_color, "set_pick_color", sky_material->get_sky_top_color());
|
|
undo_redo->add_do_method(this, "_preview_settings_changed");
|
|
undo_redo->add_undo_method(this, "_preview_settings_changed");
|
|
undo_redo->commit_action();
|
|
}
|
|
|
|
void Node3DEditor::_environ_set_ground_color(const Color &p_color) {
|
|
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
|
undo_redo->create_action(TTR("Set Preview Environment Ground Color"), UndoRedo::MergeMode::MERGE_ENDS);
|
|
undo_redo->add_do_method(environ_ground_color, "set_pick_color", p_color);
|
|
undo_redo->add_undo_method(environ_ground_color, "set_pick_color", sky_material->get_ground_bottom_color());
|
|
undo_redo->add_do_method(this, "_preview_settings_changed");
|
|
undo_redo->add_undo_method(this, "_preview_settings_changed");
|
|
undo_redo->commit_action();
|
|
}
|
|
|
|
void Node3DEditor::_environ_set_sky_energy(float p_energy) {
|
|
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
|
undo_redo->create_action(TTR("Set Preview Environment Energy"), UndoRedo::MergeMode::MERGE_ENDS);
|
|
undo_redo->add_do_method(environ_energy, "set_value_no_signal", p_energy);
|
|
undo_redo->add_undo_method(environ_energy, "set_value_no_signal", sky_material->get_energy_multiplier());
|
|
undo_redo->add_do_method(this, "_preview_settings_changed");
|
|
undo_redo->add_undo_method(this, "_preview_settings_changed");
|
|
undo_redo->commit_action();
|
|
}
|
|
|
|
void Node3DEditor::_environ_set_ao() {
|
|
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
|
undo_redo->create_action(TTR("Set Preview Environment Ambient Occlusion"));
|
|
undo_redo->add_do_method(environ_ao_button, "set_pressed", environ_ao_button->is_pressed());
|
|
undo_redo->add_undo_method(environ_ao_button, "set_pressed", !environ_ao_button->is_pressed());
|
|
undo_redo->add_do_method(this, "_preview_settings_changed");
|
|
undo_redo->add_undo_method(this, "_preview_settings_changed");
|
|
undo_redo->commit_action();
|
|
}
|
|
|
|
void Node3DEditor::_environ_set_glow() {
|
|
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
|
undo_redo->create_action(TTR("Set Preview Environment Glow"));
|
|
undo_redo->add_do_method(environ_glow_button, "set_pressed", environ_glow_button->is_pressed());
|
|
undo_redo->add_undo_method(environ_glow_button, "set_pressed", !environ_glow_button->is_pressed());
|
|
undo_redo->add_do_method(this, "_preview_settings_changed");
|
|
undo_redo->add_undo_method(this, "_preview_settings_changed");
|
|
undo_redo->commit_action();
|
|
}
|
|
|
|
void Node3DEditor::_environ_set_tonemap() {
|
|
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
|
undo_redo->create_action(TTR("Set Preview Environment Tonemap"));
|
|
undo_redo->add_do_method(environ_tonemap_button, "set_pressed", environ_tonemap_button->is_pressed());
|
|
undo_redo->add_undo_method(environ_tonemap_button, "set_pressed", !environ_tonemap_button->is_pressed());
|
|
undo_redo->add_do_method(this, "_preview_settings_changed");
|
|
undo_redo->add_undo_method(this, "_preview_settings_changed");
|
|
undo_redo->commit_action();
|
|
}
|
|
|
|
void Node3DEditor::_environ_set_gi() {
|
|
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
|
undo_redo->create_action(TTR("Set Preview Environment Global Illumination"));
|
|
undo_redo->add_do_method(environ_gi_button, "set_pressed", environ_gi_button->is_pressed());
|
|
undo_redo->add_undo_method(environ_gi_button, "set_pressed", !environ_gi_button->is_pressed());
|
|
undo_redo->add_do_method(this, "_preview_settings_changed");
|
|
undo_redo->add_undo_method(this, "_preview_settings_changed");
|
|
undo_redo->commit_action();
|
|
}
|
|
|
|
void Node3DEditor::PreviewSunEnvPopup::shortcut_input(const Ref<InputEvent> &p_event) {
|
|
const Ref<InputEventKey> k = p_event;
|
|
if (k.is_valid() && k->is_pressed()) {
|
|
bool handled = false;
|
|
|
|
if (ED_IS_SHORTCUT("ui_undo", p_event)) {
|
|
EditorNode::get_singleton()->undo();
|
|
handled = true;
|
|
}
|
|
|
|
if (ED_IS_SHORTCUT("ui_redo", p_event)) {
|
|
EditorNode::get_singleton()->redo();
|
|
handled = true;
|
|
}
|
|
|
|
if (handled) {
|
|
set_input_as_handled();
|
|
}
|
|
}
|
|
}
|
|
|
|
Node3DEditor::Node3DEditor() {
|
|
gizmo.visible = true;
|
|
gizmo.scale = 1.0;
|
|
gizmo_view_rotation_scale = GIZMO_CIRCLE_SIZE * (float)EDITOR_GET("editors/3d/view_plane_rotation_gizmo_scale");
|
|
|
|
viewport_environment.instantiate();
|
|
VBoxContainer *vbc = this;
|
|
|
|
ERR_FAIL_COND_MSG(singleton != nullptr, "A Node3DEditor singleton already exists.");
|
|
singleton = this;
|
|
editor_selection = EditorNode::get_singleton()->get_editor_selection();
|
|
editor_selection->add_editor_plugin(this);
|
|
|
|
MarginContainer *toolbar_margin = memnew(MarginContainer);
|
|
toolbar_margin->set_theme_type_variation("MainToolBarMargin");
|
|
vbc->add_child(toolbar_margin);
|
|
|
|
// A fluid container for all toolbars.
|
|
HFlowContainer *main_flow = memnew(HFlowContainer);
|
|
toolbar_margin->add_child(main_flow);
|
|
|
|
// Main toolbars.
|
|
HBoxContainer *main_menu_hbox = memnew(HBoxContainer);
|
|
main_flow->add_child(main_menu_hbox);
|
|
|
|
String sct;
|
|
|
|
tool_button[TOOL_MODE_TRANSFORM] = memnew(Button);
|
|
main_menu_hbox->add_child(tool_button[TOOL_MODE_TRANSFORM]);
|
|
tool_button[TOOL_MODE_TRANSFORM]->set_toggle_mode(true);
|
|
tool_button[TOOL_MODE_TRANSFORM]->set_theme_type_variation(SceneStringName(FlatButton));
|
|
tool_button[TOOL_MODE_TRANSFORM]->set_pressed(true);
|
|
tool_button[TOOL_MODE_TRANSFORM]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_TOOL_TRANSFORM));
|
|
tool_button[TOOL_MODE_TRANSFORM]->set_shortcut(ED_SHORTCUT("spatial_editor/tool_transform", TTRC("Transform Mode"), Key::Q, true));
|
|
tool_button[TOOL_MODE_TRANSFORM]->set_shortcut_context(this);
|
|
tool_button[TOOL_MODE_TRANSFORM]->set_accessibility_name(TTRC("Transform Mode"));
|
|
|
|
tool_button[TOOL_MODE_MOVE] = memnew(Button);
|
|
main_menu_hbox->add_child(tool_button[TOOL_MODE_MOVE]);
|
|
tool_button[TOOL_MODE_MOVE]->set_toggle_mode(true);
|
|
tool_button[TOOL_MODE_MOVE]->set_tooltip_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
|
tool_button[TOOL_MODE_MOVE]->set_theme_type_variation(SceneStringName(FlatButton));
|
|
|
|
tool_button[TOOL_MODE_MOVE]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_TOOL_MOVE));
|
|
tool_button[TOOL_MODE_MOVE]->set_shortcut(ED_SHORTCUT("spatial_editor/tool_move", TTRC("Move Mode"), Key::W, true));
|
|
tool_button[TOOL_MODE_MOVE]->set_shortcut_context(this);
|
|
tool_button[TOOL_MODE_MOVE]->set_accessibility_name(TTRC("Move Mode"));
|
|
|
|
tool_button[TOOL_MODE_ROTATE] = memnew(Button);
|
|
main_menu_hbox->add_child(tool_button[TOOL_MODE_ROTATE]);
|
|
tool_button[TOOL_MODE_ROTATE]->set_toggle_mode(true);
|
|
tool_button[TOOL_MODE_ROTATE]->set_tooltip_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
|
tool_button[TOOL_MODE_ROTATE]->set_theme_type_variation(SceneStringName(FlatButton));
|
|
tool_button[TOOL_MODE_ROTATE]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_TOOL_ROTATE));
|
|
tool_button[TOOL_MODE_ROTATE]->set_shortcut(ED_SHORTCUT("spatial_editor/tool_rotate", TTRC("Rotate Mode"), Key::E, true));
|
|
tool_button[TOOL_MODE_ROTATE]->set_shortcut_context(this);
|
|
tool_button[TOOL_MODE_ROTATE]->set_accessibility_name(TTRC("Rotate Mode"));
|
|
|
|
tool_button[TOOL_MODE_SCALE] = memnew(Button);
|
|
main_menu_hbox->add_child(tool_button[TOOL_MODE_SCALE]);
|
|
tool_button[TOOL_MODE_SCALE]->set_toggle_mode(true);
|
|
tool_button[TOOL_MODE_SCALE]->set_tooltip_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
|
tool_button[TOOL_MODE_SCALE]->set_theme_type_variation(SceneStringName(FlatButton));
|
|
tool_button[TOOL_MODE_SCALE]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_TOOL_SCALE));
|
|
tool_button[TOOL_MODE_SCALE]->set_shortcut(ED_SHORTCUT("spatial_editor/tool_scale", TTRC("Scale Mode"), Key::R, true));
|
|
tool_button[TOOL_MODE_SCALE]->set_shortcut_context(this);
|
|
tool_button[TOOL_MODE_SCALE]->set_accessibility_name(TTRC("Scale Mode"));
|
|
|
|
tool_button[TOOL_MODE_SELECT] = memnew(Button);
|
|
main_menu_hbox->add_child(tool_button[TOOL_MODE_SELECT]);
|
|
tool_button[TOOL_MODE_SELECT]->set_toggle_mode(true);
|
|
tool_button[TOOL_MODE_SELECT]->set_tooltip_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
|
tool_button[TOOL_MODE_SELECT]->set_theme_type_variation(SceneStringName(FlatButton));
|
|
tool_button[TOOL_MODE_SELECT]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_TOOL_SELECT));
|
|
tool_button[TOOL_MODE_SELECT]->set_shortcut(ED_SHORTCUT("spatial_editor/tool_select", TTRC("Select Mode"), Key::V, true));
|
|
tool_button[TOOL_MODE_SELECT]->set_shortcut_context(this);
|
|
tool_button[TOOL_MODE_SELECT]->set_accessibility_name(TTRC("Select Mode"));
|
|
|
|
main_menu_hbox->add_child(memnew(VSeparator));
|
|
|
|
tool_button[TOOL_MODE_LIST_SELECT] = memnew(Button);
|
|
main_menu_hbox->add_child(tool_button[TOOL_MODE_LIST_SELECT]);
|
|
tool_button[TOOL_MODE_LIST_SELECT]->set_toggle_mode(true);
|
|
tool_button[TOOL_MODE_LIST_SELECT]->set_theme_type_variation(SceneStringName(FlatButton));
|
|
tool_button[TOOL_MODE_LIST_SELECT]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_TOOL_LIST_SELECT));
|
|
tool_button[TOOL_MODE_LIST_SELECT]->set_tooltip_text(TTR("Show list of selectable nodes at position clicked.") + "\n" + TTR("Alt+RMB: Show list of all nodes at position clicked, including locked."));
|
|
tool_button[TOOL_MODE_LIST_SELECT]->set_accessibility_name(TTRC("Show List of Selectable Nodes"));
|
|
|
|
tool_button[TOOL_LOCK_SELECTED] = memnew(Button);
|
|
main_menu_hbox->add_child(tool_button[TOOL_LOCK_SELECTED]);
|
|
tool_button[TOOL_LOCK_SELECTED]->set_theme_type_variation(SceneStringName(FlatButton));
|
|
tool_button[TOOL_LOCK_SELECTED]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_LOCK_SELECTED));
|
|
tool_button[TOOL_LOCK_SELECTED]->set_tooltip_text(TTRC("Lock selected node, preventing selection and movement."));
|
|
// Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused.
|
|
tool_button[TOOL_LOCK_SELECTED]->set_shortcut(ED_GET_SHORTCUT("editor/lock_selected_nodes"));
|
|
tool_button[TOOL_LOCK_SELECTED]->set_accessibility_name(TTRC("Lock"));
|
|
|
|
tool_button[TOOL_UNLOCK_SELECTED] = memnew(Button);
|
|
main_menu_hbox->add_child(tool_button[TOOL_UNLOCK_SELECTED]);
|
|
tool_button[TOOL_UNLOCK_SELECTED]->set_theme_type_variation(SceneStringName(FlatButton));
|
|
tool_button[TOOL_UNLOCK_SELECTED]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_UNLOCK_SELECTED));
|
|
tool_button[TOOL_UNLOCK_SELECTED]->set_tooltip_text(TTRC("Unlock selected node, allowing selection and movement."));
|
|
// Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused.
|
|
tool_button[TOOL_UNLOCK_SELECTED]->set_shortcut(ED_GET_SHORTCUT("editor/unlock_selected_nodes"));
|
|
tool_button[TOOL_UNLOCK_SELECTED]->set_accessibility_name(TTRC("Unlock"));
|
|
|
|
tool_button[TOOL_GROUP_SELECTED] = memnew(Button);
|
|
main_menu_hbox->add_child(tool_button[TOOL_GROUP_SELECTED]);
|
|
tool_button[TOOL_GROUP_SELECTED]->set_theme_type_variation(SceneStringName(FlatButton));
|
|
tool_button[TOOL_GROUP_SELECTED]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_GROUP_SELECTED));
|
|
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."));
|
|
// Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused.
|
|
tool_button[TOOL_GROUP_SELECTED]->set_shortcut(ED_GET_SHORTCUT("editor/group_selected_nodes"));
|
|
tool_button[TOOL_GROUP_SELECTED]->set_accessibility_name(TTRC("Group"));
|
|
|
|
tool_button[TOOL_UNGROUP_SELECTED] = memnew(Button);
|
|
main_menu_hbox->add_child(tool_button[TOOL_UNGROUP_SELECTED]);
|
|
tool_button[TOOL_UNGROUP_SELECTED]->set_theme_type_variation(SceneStringName(FlatButton));
|
|
tool_button[TOOL_UNGROUP_SELECTED]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_UNGROUP_SELECTED));
|
|
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."));
|
|
// Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused.
|
|
tool_button[TOOL_UNGROUP_SELECTED]->set_shortcut(ED_GET_SHORTCUT("editor/ungroup_selected_nodes"));
|
|
tool_button[TOOL_UNGROUP_SELECTED]->set_accessibility_name(TTRC("Ungroup"));
|
|
|
|
tool_button[TOOL_RULER] = memnew(Button);
|
|
main_menu_hbox->add_child(tool_button[TOOL_RULER]);
|
|
tool_button[TOOL_RULER]->set_toggle_mode(true);
|
|
tool_button[TOOL_RULER]->set_theme_type_variation("FlatButton");
|
|
tool_button[TOOL_RULER]->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed).bind(MENU_RULER));
|
|
tool_button[TOOL_RULER]->set_tooltip_text(TTRC("LMB+Drag: Measure distance between two points.\nShift+LMB+Drag: Show component measurements."));
|
|
// Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused.
|
|
tool_button[TOOL_RULER]->set_shortcut(ED_SHORTCUT("spatial_editor/measure", TTRC("Ruler Mode"), Key::M));
|
|
tool_button[TOOL_RULER]->set_accessibility_name(TTRC("Ruler Mode"));
|
|
|
|
main_menu_hbox->add_child(memnew(VSeparator));
|
|
|
|
tool_option_button[TOOL_OPT_LOCAL_COORDS] = memnew(Button);
|
|
main_menu_hbox->add_child(tool_option_button[TOOL_OPT_LOCAL_COORDS]);
|
|
tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_toggle_mode(true);
|
|
tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_theme_type_variation(SceneStringName(FlatButton));
|
|
tool_option_button[TOOL_OPT_LOCAL_COORDS]->connect(SceneStringName(toggled), callable_mp(this, &Node3DEditor::_menu_item_toggled).bind(MENU_TOOL_LOCAL_COORDS));
|
|
tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_shortcut(ED_SHORTCUT("spatial_editor/local_coords", TTRC("Use Local Space"), Key::T));
|
|
tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_shortcut_context(this);
|
|
tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_accessibility_name(TTRC("Use Local Space"));
|
|
|
|
tool_option_button[TOOL_OPT_USE_SNAP] = memnew(Button);
|
|
main_menu_hbox->add_child(tool_option_button[TOOL_OPT_USE_SNAP]);
|
|
tool_option_button[TOOL_OPT_USE_SNAP]->set_toggle_mode(true);
|
|
tool_option_button[TOOL_OPT_USE_SNAP]->set_theme_type_variation(SceneStringName(FlatButton));
|
|
tool_option_button[TOOL_OPT_USE_SNAP]->connect(SceneStringName(toggled), callable_mp(this, &Node3DEditor::_menu_item_toggled).bind(MENU_TOOL_USE_SNAP));
|
|
tool_option_button[TOOL_OPT_USE_SNAP]->set_shortcut(ED_SHORTCUT("spatial_editor/snap", TTRC("Use Snap"), Key::Y));
|
|
tool_option_button[TOOL_OPT_USE_SNAP]->set_shortcut_context(this);
|
|
tool_option_button[TOOL_OPT_USE_SNAP]->set_accessibility_name(TTRC("Use Snap"));
|
|
|
|
tool_option_button[TOOL_OPT_USE_TRACKBALL] = memnew(Button);
|
|
main_menu_hbox->add_child(tool_option_button[TOOL_OPT_USE_TRACKBALL]);
|
|
tool_option_button[TOOL_OPT_USE_TRACKBALL]->set_toggle_mode(true);
|
|
tool_option_button[TOOL_OPT_USE_TRACKBALL]->set_theme_type_variation(SceneStringName(FlatButton));
|
|
tool_option_button[TOOL_OPT_USE_TRACKBALL]->connect(SceneStringName(toggled), callable_mp(this, &Node3DEditor::_menu_item_toggled).bind(MENU_TOOL_USE_TRACKBALL));
|
|
tool_option_button[TOOL_OPT_USE_TRACKBALL]->set_shortcut(ED_SHORTCUT("spatial_editor/trackball", TTRC("Use Trackball"), Key::U));
|
|
tool_option_button[TOOL_OPT_USE_TRACKBALL]->set_shortcut_context(this);
|
|
tool_option_button[TOOL_OPT_USE_TRACKBALL]->set_accessibility_name(TTRC("Use Trackball"));
|
|
|
|
tool_option_button[TOOL_OPT_PRESERVE_CHILDREN_TRANSFORM] = memnew(Button);
|
|
main_menu_hbox->add_child(tool_option_button[TOOL_OPT_PRESERVE_CHILDREN_TRANSFORM]);
|
|
tool_option_button[TOOL_OPT_PRESERVE_CHILDREN_TRANSFORM]->set_toggle_mode(true);
|
|
tool_option_button[TOOL_OPT_PRESERVE_CHILDREN_TRANSFORM]->set_theme_type_variation(SceneStringName(FlatButton));
|
|
tool_option_button[TOOL_OPT_PRESERVE_CHILDREN_TRANSFORM]->connect(SceneStringName(toggled), callable_mp(this, &Node3DEditor::_menu_item_toggled).bind(MENU_TOOL_PRESERVE_CHILDREN_TRANSFORM));
|
|
tool_option_button[TOOL_OPT_PRESERVE_CHILDREN_TRANSFORM]->set_shortcut(ED_SHORTCUT("spatial_editor/preserve_children_transform", TTRC("Preserve Children Transform"), Key::P));
|
|
tool_option_button[TOOL_OPT_PRESERVE_CHILDREN_TRANSFORM]->set_shortcut_context(this);
|
|
tool_option_button[TOOL_OPT_PRESERVE_CHILDREN_TRANSFORM]->set_accessibility_name(TTRC("Preserve Children Transform"));
|
|
tool_option_button[TOOL_OPT_PRESERVE_CHILDREN_TRANSFORM]->set_tooltip_text(TTRC("When enabled, transforming a node will preserve the global transform of its children.\nThis also applies when editing transform properties in the Inspector."));
|
|
|
|
main_menu_hbox->add_child(memnew(VSeparator));
|
|
sun_button = memnew(Button);
|
|
sun_button->set_tooltip_text(TTRC("Toggle preview sunlight.\nIf a DirectionalLight3D node is added to the scene, preview sunlight is disabled."));
|
|
sun_button->set_toggle_mode(true);
|
|
sun_button->set_accessibility_name(TTRC("Toggle preview sunlight."));
|
|
sun_button->set_theme_type_variation(SceneStringName(FlatButton));
|
|
sun_button->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_update_preview_environment), CONNECT_DEFERRED);
|
|
// Preview is enabled by default - ensure this applies on editor startup when there is no state yet.
|
|
sun_button->set_pressed(true);
|
|
|
|
main_menu_hbox->add_child(sun_button);
|
|
|
|
environ_button = memnew(Button);
|
|
environ_button->set_tooltip_text(TTRC("Toggle preview environment.\nIf a WorldEnvironment node is added to the scene, preview environment is disabled."));
|
|
environ_button->set_toggle_mode(true);
|
|
environ_button->set_accessibility_name(TTRC("Toggle preview environment."));
|
|
environ_button->set_theme_type_variation(SceneStringName(FlatButton));
|
|
environ_button->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_update_preview_environment), CONNECT_DEFERRED);
|
|
// Preview is enabled by default - ensure this applies on editor startup when there is no state yet.
|
|
environ_button->set_pressed(true);
|
|
|
|
main_menu_hbox->add_child(environ_button);
|
|
|
|
sun_environ_settings = memnew(Button);
|
|
sun_environ_settings->set_tooltip_text(TTRC("Edit Sun and Environment settings."));
|
|
sun_environ_settings->set_theme_type_variation(SceneStringName(FlatButton));
|
|
sun_environ_settings->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_sun_environ_settings_pressed));
|
|
|
|
main_menu_hbox->add_child(sun_environ_settings);
|
|
|
|
main_menu_hbox->add_child(memnew(VSeparator));
|
|
|
|
// Drag and drop support;
|
|
preview_node = memnew(Node3D);
|
|
preview_bounds = AABB();
|
|
|
|
ED_SHORTCUT("spatial_editor/bottom_view", TTRC("Bottom View"), KeyModifierMask::ALT + Key::KP_7);
|
|
ED_SHORTCUT("spatial_editor/top_view", TTRC("Top View"), Key::KP_7);
|
|
ED_SHORTCUT("spatial_editor/rear_view", TTRC("Rear View"), KeyModifierMask::ALT + Key::KP_1);
|
|
ED_SHORTCUT("spatial_editor/front_view", TTRC("Front View"), Key::KP_1);
|
|
ED_SHORTCUT("spatial_editor/left_view", TTRC("Left View"), KeyModifierMask::ALT + Key::KP_3);
|
|
ED_SHORTCUT("spatial_editor/right_view", TTRC("Right View"), Key::KP_3);
|
|
ED_SHORTCUT("spatial_editor/orbit_view_down", TTRC("Orbit View Down"), Key::KP_2);
|
|
ED_SHORTCUT("spatial_editor/orbit_view_left", TTRC("Orbit View Left"), Key::KP_4);
|
|
ED_SHORTCUT("spatial_editor/orbit_view_right", TTRC("Orbit View Right"), Key::KP_6);
|
|
ED_SHORTCUT("spatial_editor/orbit_view_up", TTRC("Orbit View Up"), Key::KP_8);
|
|
ED_SHORTCUT("spatial_editor/orbit_view_180", TTRC("Orbit View 180"), Key::KP_9);
|
|
ED_SHORTCUT("spatial_editor/switch_perspective_orthogonal", TTRC("Switch Perspective/Orthogonal View"), Key::KP_5);
|
|
ED_SHORTCUT("spatial_editor/insert_anim_key", TTRC("Insert Animation Key"), Key::K);
|
|
ED_SHORTCUT("spatial_editor/focus_origin", TTRC("Focus Origin"), Key::O);
|
|
ED_SHORTCUT("spatial_editor/focus_selection", TTRC("Focus Selection"), Key::F);
|
|
ED_SHORTCUT_ARRAY("spatial_editor/align_transform_with_view", TTRC("Align Transform with View"),
|
|
{ int32_t(KeyModifierMask::ALT | KeyModifierMask::CTRL | Key::KP_0),
|
|
int32_t(KeyModifierMask::ALT | KeyModifierMask::CTRL | Key::M),
|
|
int32_t(KeyModifierMask::ALT | KeyModifierMask::CTRL | Key::G) });
|
|
ED_SHORTCUT_OVERRIDE_ARRAY("spatial_editor/align_transform_with_view", "macos",
|
|
{ int32_t(KeyModifierMask::ALT | KeyModifierMask::META | Key::KP_0),
|
|
int32_t(KeyModifierMask::ALT | KeyModifierMask::META | Key::G) });
|
|
ED_SHORTCUT("spatial_editor/align_rotation_with_view", TTRC("Align Rotation with View"), KeyModifierMask::ALT + KeyModifierMask::CMD_OR_CTRL + Key::F);
|
|
ED_SHORTCUT("spatial_editor/freelook_toggle", TTRC("Toggle Freelook"), KeyModifierMask::SHIFT + Key::F);
|
|
ED_SHORTCUT("spatial_editor/decrease_fov", TTRC("Decrease Field of View"), KeyModifierMask::CMD_OR_CTRL + Key::EQUAL); // Usually direct access key for `KEY_PLUS`.
|
|
ED_SHORTCUT("spatial_editor/increase_fov", TTRC("Increase Field of View"), KeyModifierMask::CMD_OR_CTRL + Key::MINUS);
|
|
ED_SHORTCUT("spatial_editor/reset_fov", TTRC("Reset Field of View to Default"), KeyModifierMask::CMD_OR_CTRL + Key::KEY_0);
|
|
|
|
PopupMenu *p;
|
|
|
|
transform_menu = memnew(MenuButton);
|
|
transform_menu->set_flat(false);
|
|
transform_menu->set_theme_type_variation("FlatMenuButton");
|
|
transform_menu->set_text(TTRC("Transform"));
|
|
transform_menu->set_switch_on_hover(true);
|
|
transform_menu->set_shortcut_context(this);
|
|
main_menu_hbox->add_child(transform_menu);
|
|
|
|
p = transform_menu->get_popup();
|
|
p->add_shortcut(ED_SHORTCUT("spatial_editor/snap_to_floor", TTRC("Snap Object to Floor"), Key::PAGEDOWN), MENU_SNAP_TO_FLOOR);
|
|
p->add_shortcut(ED_SHORTCUT("spatial_editor/transform_dialog", TTRC("Transform Dialog...")), MENU_TRANSFORM_DIALOG);
|
|
|
|
p->add_separator();
|
|
ED_SHORTCUT("spatial_editor/vertex_snap", TTRC("Vertex Snap"), Key::B);
|
|
p->add_radio_check_item(TTRC("Snap Vertex to Vertex"), MENU_VERTEX_SNAP_BASE_VERTEX);
|
|
p->set_item_checked(p->get_item_index(MENU_VERTEX_SNAP_BASE_VERTEX), true);
|
|
p->add_radio_check_item(TTRC("Snap Origin to Vertex"), MENU_VERTEX_SNAP_BASE_ORIGIN);
|
|
|
|
p->add_separator();
|
|
p->add_radio_check_item(TTRC("Snap to Mesh Vertices"), MENU_VERTEX_SNAP_SOURCE_MESH);
|
|
p->set_item_checked(p->get_item_index(MENU_VERTEX_SNAP_SOURCE_MESH), true);
|
|
p->add_radio_check_item(TTRC("Snap to Collision Vertices"), MENU_VERTEX_SNAP_SOURCE_COLLISION);
|
|
_update_vertex_snap_tooltips();
|
|
|
|
p->add_separator();
|
|
p->add_shortcut(ED_SHORTCUT("spatial_editor/configure_snap", TTRC("Configure Snap...")), MENU_TRANSFORM_CONFIGURE_SNAP);
|
|
|
|
p->connect(SceneStringName(id_pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed));
|
|
|
|
view_layout_menu = memnew(MenuButton);
|
|
view_layout_menu->set_flat(false);
|
|
view_layout_menu->set_theme_type_variation("FlatMenuButton");
|
|
// TRANSLATORS: Noun, name of the 2D/3D View menus.
|
|
view_layout_menu->set_text(TTRC("View"));
|
|
view_layout_menu->set_switch_on_hover(true);
|
|
view_layout_menu->set_shortcut_context(this);
|
|
main_menu_hbox->add_child(view_layout_menu);
|
|
|
|
main_menu_hbox->add_child(memnew(VSeparator));
|
|
|
|
context_toolbar_panel = memnew(PanelContainer);
|
|
context_toolbar_hbox = memnew(HBoxContainer);
|
|
context_toolbar_panel->add_child(context_toolbar_hbox);
|
|
main_flow->add_child(context_toolbar_panel);
|
|
|
|
// Get the view menu popup and have it stay open when a checkable item is selected
|
|
p = view_layout_menu->get_popup();
|
|
p->set_hide_on_checkable_item_selection(false);
|
|
|
|
accept = memnew(AcceptDialog);
|
|
EditorNode::get_singleton()->get_gui_base()->add_child(accept);
|
|
|
|
p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/1_viewport", TTRC("1 Viewport"), KeyModifierMask::CMD_OR_CTRL + Key::KEY_1, true), MENU_VIEW_USE_1_VIEWPORT);
|
|
p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/2_viewports", TTRC("2 Viewports"), KeyModifierMask::CMD_OR_CTRL + Key::KEY_2, true), MENU_VIEW_USE_2_VIEWPORTS);
|
|
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, true), MENU_VIEW_USE_2_VIEWPORTS_ALT);
|
|
p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/3_viewports", TTRC("3 Viewports"), KeyModifierMask::CMD_OR_CTRL + Key::KEY_3, true), MENU_VIEW_USE_3_VIEWPORTS);
|
|
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, true), MENU_VIEW_USE_3_VIEWPORTS_ALT);
|
|
p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/4_viewports", TTRC("4 Viewports"), KeyModifierMask::CMD_OR_CTRL + Key::KEY_4, true), MENU_VIEW_USE_4_VIEWPORTS);
|
|
p->add_separator();
|
|
|
|
gizmos_menu = memnew(PopupMenu);
|
|
gizmos_menu->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
|
gizmos_menu->set_hide_on_checkable_item_selection(false);
|
|
p->add_submenu_node_item(TTRC("Gizmos"), gizmos_menu);
|
|
gizmos_menu->connect(SceneStringName(id_pressed), callable_mp(this, &Node3DEditor::_menu_gizmo_toggled));
|
|
|
|
p->add_separator();
|
|
p->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_origin", TTRC("View Origin")), MENU_VIEW_ORIGIN);
|
|
p->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_grid", TTRC("View Grid"), Key::NUMBERSIGN), MENU_VIEW_GRID);
|
|
|
|
p->add_separator();
|
|
p->add_submenu_node_item(TTRC("Preview Translation"), memnew(EditorTranslationPreviewMenu));
|
|
|
|
p->add_separator();
|
|
p->add_shortcut(ED_SHORTCUT("spatial_editor/settings", TTRC("Settings...")), MENU_VIEW_CAMERA_SETTINGS);
|
|
|
|
p->set_item_checked(p->get_item_index(MENU_VIEW_ORIGIN), true);
|
|
p->set_item_checked(p->get_item_index(MENU_VIEW_GRID), true);
|
|
|
|
p->connect(SceneStringName(id_pressed), callable_mp(this, &Node3DEditor::_menu_item_pressed));
|
|
|
|
/* REST OF MENU */
|
|
|
|
left_panel_split = memnew(HSplitContainer);
|
|
left_panel_split->set_v_size_flags(SIZE_EXPAND_FILL);
|
|
vbc->add_child(left_panel_split);
|
|
|
|
right_panel_split = memnew(HSplitContainer);
|
|
right_panel_split->set_v_size_flags(SIZE_EXPAND_FILL);
|
|
left_panel_split->add_child(right_panel_split);
|
|
|
|
shader_split = memnew(VSplitContainer);
|
|
shader_split->set_h_size_flags(SIZE_EXPAND_FILL);
|
|
right_panel_split->add_child(shader_split);
|
|
viewport_base = memnew(Node3DEditorViewportContainer);
|
|
shader_split->add_child(viewport_base);
|
|
viewport_base->set_v_size_flags(SIZE_EXPAND_FILL);
|
|
for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) {
|
|
viewports[i] = memnew(Node3DEditorViewport(this, i));
|
|
viewports[i]->connect("toggle_maximize_view", callable_mp(this, &Node3DEditor::_toggle_maximize_view));
|
|
viewports[i]->connect("clicked", callable_mp(this, &Node3DEditor::_viewport_clicked).bind(i));
|
|
viewports[i]->assign_pending_data_pointers(preview_node, &preview_bounds, accept);
|
|
viewports[i]->set_h_size_flags(SIZE_EXPAND_FILL);
|
|
viewports[i]->set_v_size_flags(SIZE_EXPAND_FILL);
|
|
viewports[i]->set_custom_minimum_size(Size2(39, 39));
|
|
viewport_base->add_viewport(viewports[i], i);
|
|
}
|
|
|
|
/* SNAP DIALOG */
|
|
|
|
snap_dialog = memnew(ConfirmationDialog);
|
|
snap_dialog->set_title(TTRC("Snap Settings"));
|
|
add_child(snap_dialog);
|
|
snap_dialog->connect(SceneStringName(confirmed), callable_mp(this, &Node3DEditor::_snap_changed));
|
|
snap_dialog->get_cancel_button()->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_snap_update));
|
|
|
|
VBoxContainer *snap_dialog_vbc = memnew(VBoxContainer);
|
|
snap_dialog->add_child(snap_dialog_vbc);
|
|
|
|
snap_translate = memnew(EditorSpinSlider);
|
|
snap_translate->set_min(0.0);
|
|
snap_translate->set_step(0.001);
|
|
snap_translate->set_max(10.0);
|
|
snap_translate->set_suffix("m");
|
|
snap_translate->set_allow_greater(true);
|
|
snap_translate->set_accessibility_name(TTRC("Translate Snap"));
|
|
snap_dialog_vbc->add_margin_child(TTR("Translate Snap:"), snap_translate);
|
|
|
|
snap_rotate = memnew(EditorSpinSlider);
|
|
snap_rotate->set_min(0.0);
|
|
snap_rotate->set_step(0.1);
|
|
snap_rotate->set_max(360);
|
|
snap_rotate->set_suffix(U"°");
|
|
snap_rotate->set_accessibility_name(TTRC("Rotate Snap"));
|
|
snap_dialog_vbc->add_margin_child(TTR("Rotate Snap:"), snap_rotate);
|
|
|
|
snap_scale = memnew(EditorSpinSlider);
|
|
snap_scale->set_min(0.0);
|
|
snap_scale->set_step(1.0);
|
|
snap_scale->set_max(100);
|
|
snap_scale->set_suffix("%");
|
|
snap_scale->set_accessibility_name(TTRC("Scale Snap"));
|
|
snap_dialog_vbc->add_margin_child(TTR("Scale Snap:"), snap_scale);
|
|
|
|
/* SETTINGS DIALOG */
|
|
|
|
settings_dialog = memnew(ConfirmationDialog);
|
|
settings_dialog->set_title(TTRC("Viewport Settings"));
|
|
add_child(settings_dialog);
|
|
settings_vbc = memnew(VBoxContainer);
|
|
settings_vbc->set_custom_minimum_size(Size2(200, 0) * EDSCALE);
|
|
settings_dialog->add_child(settings_vbc);
|
|
|
|
settings_fov = memnew(SpinBox);
|
|
settings_fov->set_max(MAX_FOV);
|
|
settings_fov->set_min(MIN_FOV);
|
|
settings_fov->set_step(0.1);
|
|
settings_fov->set_value(EDITOR_GET("editors/3d/default_fov"));
|
|
settings_fov->set_select_all_on_focus(true);
|
|
settings_fov->set_tooltip_text(TTRC("FOV is defined as a vertical value, as the editor camera always uses the Keep Height aspect mode."));
|
|
settings_fov->set_accessibility_name(TTRC("Perspective VFOV (deg.):"));
|
|
settings_vbc->add_margin_child(TTRC("Perspective VFOV (deg.):"), settings_fov);
|
|
|
|
settings_znear = memnew(SpinBox);
|
|
settings_znear->set_max(MAX_Z);
|
|
settings_znear->set_min(MIN_Z);
|
|
settings_znear->set_step(0.01);
|
|
settings_znear->set_accessibility_name(TTRC("View Z-Near:"));
|
|
settings_znear->set_value(EDITOR_GET("editors/3d/default_z_near"));
|
|
settings_znear->set_select_all_on_focus(true);
|
|
settings_vbc->add_margin_child(TTRC("View Z-Near:"), settings_znear);
|
|
|
|
settings_zfar = memnew(SpinBox);
|
|
settings_zfar->set_max(MAX_Z);
|
|
settings_zfar->set_min(MIN_Z);
|
|
settings_zfar->set_step(0.1);
|
|
settings_zfar->set_accessibility_name(TTRC("View Z-Far:"));
|
|
settings_zfar->set_value(EDITOR_GET("editors/3d/default_z_far"));
|
|
settings_zfar->set_select_all_on_focus(true);
|
|
settings_vbc->add_margin_child(TTRC("View Z-Far:"), settings_zfar);
|
|
|
|
for (uint32_t i = 0; i < VIEWPORTS_COUNT; ++i) {
|
|
settings_dialog->connect(SceneStringName(confirmed), callable_mp(viewports[i], &Node3DEditorViewport::_view_settings_confirmed).bind(0.0));
|
|
}
|
|
|
|
/* XFORM DIALOG */
|
|
|
|
xform_dialog = memnew(ConfirmationDialog);
|
|
xform_dialog->set_title(TTRC("Transform Change"));
|
|
add_child(xform_dialog);
|
|
|
|
VBoxContainer *xform_vbc = memnew(VBoxContainer);
|
|
xform_dialog->add_child(xform_vbc);
|
|
|
|
HBoxContainer *translate_hb = memnew(HBoxContainer);
|
|
xform_vbc->add_margin_child(TTRC("Translate:"), translate_hb);
|
|
HBoxContainer *rotate_hb = memnew(HBoxContainer);
|
|
xform_vbc->add_margin_child(TTRC("Rotate (deg.):"), rotate_hb);
|
|
HBoxContainer *scale_hb = memnew(HBoxContainer);
|
|
xform_vbc->add_margin_child(TTRC("Scale (ratio):"), scale_hb);
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
xform_translate[i] = memnew(LineEdit);
|
|
xform_translate[i]->set_h_size_flags(SIZE_EXPAND_FILL);
|
|
xform_translate[i]->set_select_all_on_focus(true);
|
|
translate_hb->add_child(xform_translate[i]);
|
|
|
|
xform_rotate[i] = memnew(LineEdit);
|
|
xform_rotate[i]->set_h_size_flags(SIZE_EXPAND_FILL);
|
|
xform_rotate[i]->set_select_all_on_focus(true);
|
|
rotate_hb->add_child(xform_rotate[i]);
|
|
|
|
xform_scale[i] = memnew(LineEdit);
|
|
xform_scale[i]->set_h_size_flags(SIZE_EXPAND_FILL);
|
|
xform_scale[i]->set_select_all_on_focus(true);
|
|
scale_hb->add_child(xform_scale[i]);
|
|
}
|
|
|
|
xform_type = memnew(OptionButton);
|
|
xform_type->set_h_size_flags(SIZE_EXPAND_FILL);
|
|
xform_type->set_accessibility_name(TTRC("Transform Type"));
|
|
xform_type->add_item(TTRC("Pre"));
|
|
xform_type->add_item(TTRC("Post"));
|
|
xform_vbc->add_margin_child(TTRC("Transform Type"), xform_type);
|
|
|
|
xform_dialog->connect(SceneStringName(confirmed), callable_mp(this, &Node3DEditor::_xform_dialog_action));
|
|
|
|
selected = nullptr;
|
|
|
|
set_process_shortcut_input(true);
|
|
add_to_group(SceneStringName(_spatial_editor_group));
|
|
|
|
current_hover_gizmo_handle = -1;
|
|
current_hover_gizmo_handle_secondary = false;
|
|
{
|
|
// Sun/preview environment popup.
|
|
sun_environ_popup = memnew(PreviewSunEnvPopup);
|
|
add_child(sun_environ_popup);
|
|
|
|
HBoxContainer *sun_environ_hb = memnew(HBoxContainer);
|
|
|
|
sun_environ_popup->add_child(sun_environ_hb);
|
|
|
|
sun_vb = memnew(VBoxContainer);
|
|
sun_environ_hb->add_child(sun_vb);
|
|
sun_vb->set_custom_minimum_size(Size2(200 * EDSCALE, 0));
|
|
sun_vb->hide();
|
|
|
|
sun_title = memnew(Label);
|
|
sun_title->set_theme_type_variation("HeaderMedium");
|
|
sun_vb->add_child(sun_title);
|
|
sun_title->set_text(TTRC("Preview Sun"));
|
|
sun_title->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
|
|
|
|
CenterContainer *sun_direction_center = memnew(CenterContainer);
|
|
sun_direction = memnew(Control);
|
|
sun_direction->set_custom_minimum_size(Size2(128, 128) * EDSCALE);
|
|
sun_direction_center->add_child(sun_direction);
|
|
sun_vb->add_margin_child(TTRC("Sun Direction"), sun_direction_center);
|
|
sun_direction->connect(SceneStringName(gui_input), callable_mp(this, &Node3DEditor::_sun_direction_input));
|
|
sun_direction->connect(SceneStringName(draw), callable_mp(this, &Node3DEditor::_sun_direction_draw));
|
|
sun_direction->set_default_cursor_shape(CURSOR_MOVE);
|
|
|
|
sun_direction_shader.instantiate();
|
|
sun_direction_shader->set_code(R"(
|
|
// 3D editor Preview Sun direction shader.
|
|
|
|
shader_type canvas_item;
|
|
|
|
uniform vec3 sun_direction;
|
|
uniform vec3 sun_color;
|
|
|
|
void fragment() {
|
|
vec3 n;
|
|
n.xy = UV * 2.0 - 1.0;
|
|
n.z = sqrt(max(0.0, 1.0 - dot(n.xy, n.xy)));
|
|
COLOR.rgb = dot(n, sun_direction) * sun_color;
|
|
COLOR.a = 1.0 - smoothstep(0.99, 1.0, length(n.xy));
|
|
}
|
|
)");
|
|
sun_direction_material.instantiate();
|
|
sun_direction_material->set_shader(sun_direction_shader);
|
|
sun_direction_material->set_shader_parameter("sun_direction", Vector3(0, 0, 1));
|
|
sun_direction_material->set_shader_parameter("sun_color", Vector3(1, 1, 1));
|
|
sun_direction->set_material(sun_direction_material);
|
|
|
|
HBoxContainer *sun_angle_hbox = memnew(HBoxContainer);
|
|
sun_angle_hbox->set_h_size_flags(SIZE_EXPAND_FILL);
|
|
VBoxContainer *sun_angle_altitude_vbox = memnew(VBoxContainer);
|
|
sun_angle_altitude_vbox->set_h_size_flags(SIZE_EXPAND_FILL);
|
|
Label *sun_angle_altitude_label = memnew(Label);
|
|
sun_angle_altitude_label->set_text(TTRC("Angular Altitude"));
|
|
sun_angle_altitude_vbox->add_child(sun_angle_altitude_label);
|
|
sun_angle_altitude = memnew(EditorSpinSlider);
|
|
sun_angle_altitude->set_suffix(U"\u00B0");
|
|
sun_angle_altitude->set_max(90);
|
|
sun_angle_altitude->set_min(-90);
|
|
sun_angle_altitude->set_step(0.1);
|
|
sun_angle_altitude->connect(SceneStringName(value_changed), callable_mp(this, &Node3DEditor::_sun_direction_set_altitude));
|
|
sun_angle_altitude_vbox->add_child(sun_angle_altitude);
|
|
sun_angle_hbox->add_child(sun_angle_altitude_vbox);
|
|
VBoxContainer *sun_angle_azimuth_vbox = memnew(VBoxContainer);
|
|
sun_angle_azimuth_vbox->set_h_size_flags(SIZE_EXPAND_FILL);
|
|
sun_angle_azimuth_vbox->set_custom_minimum_size(Vector2(100, 0));
|
|
Label *sun_angle_azimuth_label = memnew(Label);
|
|
sun_angle_azimuth_label->set_text(TTRC("Azimuth"));
|
|
sun_angle_azimuth_vbox->add_child(sun_angle_azimuth_label);
|
|
sun_angle_azimuth = memnew(EditorSpinSlider);
|
|
sun_angle_azimuth->set_suffix(U"\u00B0");
|
|
sun_angle_azimuth->set_max(180);
|
|
sun_angle_azimuth->set_min(-180);
|
|
sun_angle_azimuth->set_step(0.1);
|
|
sun_angle_azimuth->set_allow_greater(true);
|
|
sun_angle_azimuth->set_allow_lesser(true);
|
|
sun_angle_azimuth->connect(SceneStringName(value_changed), callable_mp(this, &Node3DEditor::_sun_direction_set_azimuth));
|
|
sun_angle_azimuth_vbox->add_child(sun_angle_azimuth);
|
|
sun_angle_hbox->add_child(sun_angle_azimuth_vbox);
|
|
sun_angle_hbox->add_theme_constant_override("separation", 10);
|
|
sun_vb->add_child(sun_angle_hbox);
|
|
|
|
sun_color = memnew(ColorPickerButton);
|
|
sun_color->set_edit_alpha(false);
|
|
sun_vb->add_margin_child(TTRC("Sun Color"), sun_color);
|
|
sun_color->connect("color_changed", callable_mp(this, &Node3DEditor::_sun_set_color));
|
|
sun_color->get_popup()->connect("about_to_popup", callable_mp(EditorNode::get_singleton(), &EditorNode::setup_color_picker).bind(sun_color->get_picker()));
|
|
|
|
sun_energy = memnew(EditorSpinSlider);
|
|
sun_energy->set_max(64.0);
|
|
sun_energy->set_min(0);
|
|
sun_energy->set_step(0.05);
|
|
sun_vb->add_margin_child(TTRC("Sun Energy"), sun_energy);
|
|
sun_energy->connect(SceneStringName(value_changed), callable_mp(this, &Node3DEditor::_sun_set_energy));
|
|
|
|
sun_shadow_max_distance = memnew(EditorSpinSlider);
|
|
sun_vb->add_margin_child(TTRC("Shadow Max Distance"), sun_shadow_max_distance);
|
|
sun_shadow_max_distance->connect(SceneStringName(value_changed), callable_mp(this, &Node3DEditor::_sun_set_shadow_max_distance));
|
|
sun_shadow_max_distance->set_min(1);
|
|
sun_shadow_max_distance->set_max(4096);
|
|
|
|
sun_add_to_scene = memnew(Button);
|
|
sun_add_to_scene->set_text(TTRC("Add Sun to Scene"));
|
|
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."));
|
|
sun_add_to_scene->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_add_sun_to_scene).bind(false));
|
|
sun_vb->add_spacer();
|
|
sun_vb->add_child(sun_add_to_scene);
|
|
|
|
sun_state = memnew(Label);
|
|
sun_environ_hb->add_child(sun_state);
|
|
sun_state->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
|
|
sun_state->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
|
|
sun_state->set_h_size_flags(SIZE_EXPAND_FILL);
|
|
|
|
VSeparator *sc = memnew(VSeparator);
|
|
sc->set_custom_minimum_size(Size2(10 * EDSCALE, 0));
|
|
sc->set_v_size_flags(SIZE_EXPAND_FILL);
|
|
sun_environ_hb->add_child(sc);
|
|
|
|
environ_vb = memnew(VBoxContainer);
|
|
sun_environ_hb->add_child(environ_vb);
|
|
environ_vb->set_custom_minimum_size(Size2(200 * EDSCALE, 0));
|
|
environ_vb->hide();
|
|
|
|
environ_title = memnew(Label);
|
|
environ_title->set_theme_type_variation("HeaderMedium");
|
|
|
|
environ_vb->add_child(environ_title);
|
|
environ_title->set_text(TTRC("Preview Environment"));
|
|
environ_title->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
|
|
|
|
environ_sky_color = memnew(ColorPickerButton);
|
|
environ_sky_color->set_edit_alpha(false);
|
|
environ_sky_color->connect("color_changed", callable_mp(this, &Node3DEditor::_environ_set_sky_color));
|
|
environ_sky_color->get_popup()->connect("about_to_popup", callable_mp(EditorNode::get_singleton(), &EditorNode::setup_color_picker).bind(environ_sky_color->get_picker()));
|
|
environ_vb->add_margin_child(TTRC("Sky Color"), environ_sky_color);
|
|
environ_ground_color = memnew(ColorPickerButton);
|
|
environ_ground_color->connect("color_changed", callable_mp(this, &Node3DEditor::_environ_set_ground_color));
|
|
environ_ground_color->set_edit_alpha(false);
|
|
environ_ground_color->get_popup()->connect("about_to_popup", callable_mp(EditorNode::get_singleton(), &EditorNode::setup_color_picker).bind(environ_ground_color->get_picker()));
|
|
environ_vb->add_margin_child(TTRC("Ground Color"), environ_ground_color);
|
|
environ_energy = memnew(EditorSpinSlider);
|
|
environ_energy->set_max(8.0);
|
|
environ_energy->set_min(0);
|
|
environ_energy->set_step(0.05);
|
|
environ_energy->connect(SceneStringName(value_changed), callable_mp(this, &Node3DEditor::_environ_set_sky_energy));
|
|
environ_vb->add_margin_child(TTRC("Sky Energy"), environ_energy);
|
|
HBoxContainer *fx_vb = memnew(HBoxContainer);
|
|
fx_vb->set_h_size_flags(SIZE_EXPAND_FILL);
|
|
|
|
environ_ao_button = memnew(Button);
|
|
environ_ao_button->set_text(TTRC("AO"));
|
|
environ_ao_button->set_h_size_flags(SIZE_EXPAND_FILL);
|
|
environ_ao_button->set_toggle_mode(true);
|
|
environ_ao_button->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_environ_set_ao), CONNECT_DEFERRED);
|
|
fx_vb->add_child(environ_ao_button);
|
|
environ_glow_button = memnew(Button);
|
|
environ_glow_button->set_text(TTRC("Glow"));
|
|
environ_glow_button->set_h_size_flags(SIZE_EXPAND_FILL);
|
|
environ_glow_button->set_toggle_mode(true);
|
|
environ_glow_button->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_environ_set_glow), CONNECT_DEFERRED);
|
|
fx_vb->add_child(environ_glow_button);
|
|
environ_tonemap_button = memnew(Button);
|
|
environ_tonemap_button->set_text(TTRC("Tonemap"));
|
|
environ_tonemap_button->set_h_size_flags(SIZE_EXPAND_FILL);
|
|
environ_tonemap_button->set_toggle_mode(true);
|
|
environ_tonemap_button->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_environ_set_tonemap), CONNECT_DEFERRED);
|
|
fx_vb->add_child(environ_tonemap_button);
|
|
environ_gi_button = memnew(Button);
|
|
environ_gi_button->set_text(TTRC("GI"));
|
|
environ_gi_button->set_h_size_flags(SIZE_EXPAND_FILL);
|
|
environ_gi_button->set_toggle_mode(true);
|
|
environ_gi_button->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_environ_set_gi), CONNECT_DEFERRED);
|
|
fx_vb->add_child(environ_gi_button);
|
|
environ_vb->add_margin_child(TTRC("Post Process"), fx_vb);
|
|
|
|
environ_add_to_scene = memnew(Button);
|
|
environ_add_to_scene->set_text(TTRC("Add Environment to Scene"));
|
|
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."));
|
|
environ_add_to_scene->connect(SceneStringName(pressed), callable_mp(this, &Node3DEditor::_add_environment_to_scene).bind(false));
|
|
environ_vb->add_spacer();
|
|
environ_vb->add_child(environ_add_to_scene);
|
|
|
|
environ_state = memnew(Label);
|
|
sun_environ_hb->add_child(environ_state);
|
|
environ_state->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
|
|
environ_state->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
|
|
environ_state->set_h_size_flags(SIZE_EXPAND_FILL);
|
|
|
|
preview_sun = memnew(DirectionalLight3D);
|
|
preview_sun->set_shadow(true);
|
|
preview_sun->set_shadow_mode(DirectionalLight3D::SHADOW_PARALLEL_4_SPLITS);
|
|
preview_environment = memnew(WorldEnvironment);
|
|
environment.instantiate();
|
|
preview_environment->set_environment(environment);
|
|
if (GLOBAL_GET("rendering/lights_and_shadows/use_physical_light_units")) {
|
|
camera_attributes.instantiate();
|
|
preview_environment->set_camera_attributes(camera_attributes);
|
|
}
|
|
Ref<Sky> sky;
|
|
sky.instantiate();
|
|
sky_material.instantiate();
|
|
sky->set_material(sky_material);
|
|
environment->set_sky(sky);
|
|
environment->set_background(Environment::BG_SKY);
|
|
|
|
sun_environ_popup->set_process_shortcut_input(true);
|
|
|
|
_load_default_preview_settings();
|
|
_preview_settings_changed();
|
|
}
|
|
clear(); // Make sure values are initialized. Will call _snap_update() for us.
|
|
}
|
|
Node3DEditor::~Node3DEditor() {
|
|
singleton = nullptr;
|
|
memdelete(preview_node);
|
|
if (preview_sun_dangling && preview_sun) {
|
|
memdelete(preview_sun);
|
|
}
|
|
if (preview_env_dangling && preview_environment) {
|
|
memdelete(preview_environment);
|
|
}
|
|
}
|
|
|
|
void Node3DEditorPlugin::edited_scene_changed() {
|
|
for (uint32_t i = 0; i < Node3DEditor::VIEWPORTS_COUNT; i++) {
|
|
Node3DEditorViewport *viewport = Node3DEditor::get_singleton()->get_editor_viewport(i);
|
|
if (viewport->is_visible()) {
|
|
viewport->notification(Control::NOTIFICATION_VISIBILITY_CHANGED);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Node3DEditorPlugin::make_visible(bool p_visible) {
|
|
if (p_visible) {
|
|
spatial_editor->show();
|
|
spatial_editor->set_process(true);
|
|
spatial_editor->set_physics_process(true);
|
|
spatial_editor->refresh_dirty_gizmos();
|
|
} else {
|
|
spatial_editor->hide();
|
|
spatial_editor->set_process(false);
|
|
spatial_editor->set_physics_process(false);
|
|
}
|
|
}
|
|
|
|
void Node3DEditorPlugin::edit(Object *p_object) {
|
|
spatial_editor->edit(Object::cast_to<Node3D>(p_object));
|
|
}
|
|
|
|
bool Node3DEditorPlugin::handles(Object *p_object) const {
|
|
return p_object->is_class("Node3D");
|
|
}
|
|
|
|
Dictionary Node3DEditorPlugin::get_state() const {
|
|
return spatial_editor->get_state();
|
|
}
|
|
|
|
void Node3DEditorPlugin::set_state(const Dictionary &p_state) {
|
|
spatial_editor->set_state(p_state);
|
|
}
|
|
|
|
Size2i Node3DEditor::get_camera_viewport_size(Camera3D *p_camera) {
|
|
Viewport *viewport = p_camera->get_viewport();
|
|
|
|
Window *window = Object::cast_to<Window>(viewport);
|
|
if (window) {
|
|
return window->get_size();
|
|
}
|
|
|
|
SubViewport *sub_viewport = Object::cast_to<SubViewport>(viewport);
|
|
ERR_FAIL_NULL_V(sub_viewport, Size2i());
|
|
|
|
if (sub_viewport == EditorNode::get_singleton()->get_scene_root()) {
|
|
return Size2(GLOBAL_GET("display/window/size/viewport_width"), GLOBAL_GET("display/window/size/viewport_height"));
|
|
}
|
|
|
|
return sub_viewport->get_size();
|
|
}
|
|
|
|
Vector3 Node3DEditor::snap_point(Vector3 p_target, Vector3 p_start) const {
|
|
if (is_snap_enabled()) {
|
|
real_t snap = get_translate_snap();
|
|
p_target.snapf(snap);
|
|
}
|
|
return p_target;
|
|
}
|
|
|
|
float Node3DEditor::get_znear() const {
|
|
return settings_znear->get_value();
|
|
}
|
|
|
|
float Node3DEditor::get_zfar() const {
|
|
return settings_zfar->get_value();
|
|
}
|
|
|
|
float Node3DEditor::get_fov() const {
|
|
return settings_fov->get_value();
|
|
}
|
|
|
|
bool Node3DEditor::is_gizmo_visible() const {
|
|
if (selected) {
|
|
return gizmo.visible && selected->is_transform_gizmo_visible();
|
|
}
|
|
return gizmo.visible;
|
|
}
|
|
|
|
bool Node3DEditor::are_local_coords_enabled() const {
|
|
return tool_option_button[Node3DEditor::TOOL_OPT_LOCAL_COORDS]->is_pressed();
|
|
}
|
|
|
|
void Node3DEditor::set_local_coords_enabled(bool on) const {
|
|
tool_option_button[Node3DEditor::TOOL_OPT_LOCAL_COORDS]->set_pressed(on);
|
|
}
|
|
|
|
bool Node3DEditor::is_preserve_children_transform_enabled() const {
|
|
return tool_option_button[Node3DEditor::TOOL_OPT_PRESERVE_CHILDREN_TRANSFORM]->is_pressed();
|
|
}
|
|
|
|
bool Node3DEditor::is_vertex_snap_use_collision() const {
|
|
return vertex_snap_use_collision != Input::get_singleton()->is_key_pressed(Key::SHIFT);
|
|
}
|
|
|
|
real_t Node3DEditor::get_translate_snap() const {
|
|
real_t snap_value = snap_translate_value;
|
|
if (Input::get_singleton()->is_key_pressed(Key::SHIFT)) {
|
|
snap_value /= 10.0f;
|
|
}
|
|
return snap_value;
|
|
}
|
|
|
|
real_t Node3DEditor::get_rotate_snap() const {
|
|
real_t snap_value = snap_rotate_value;
|
|
if (Input::get_singleton()->is_key_pressed(Key::SHIFT)) {
|
|
snap_value /= 3.0f;
|
|
}
|
|
return snap_value;
|
|
}
|
|
|
|
real_t Node3DEditor::get_scale_snap() const {
|
|
real_t snap_value = snap_scale_value;
|
|
if (Input::get_singleton()->is_key_pressed(Key::SHIFT)) {
|
|
snap_value /= 2.0f;
|
|
}
|
|
return snap_value;
|
|
}
|
|
|
|
struct _GizmoPluginPriorityComparator {
|
|
bool operator()(const Ref<EditorNode3DGizmoPlugin> &p_a, const Ref<EditorNode3DGizmoPlugin> &p_b) const {
|
|
if (p_a->get_priority() == p_b->get_priority()) {
|
|
return p_a->get_gizmo_name() < p_b->get_gizmo_name();
|
|
}
|
|
return p_a->get_priority() > p_b->get_priority();
|
|
}
|
|
};
|
|
|
|
struct _GizmoPluginNameComparator {
|
|
bool operator()(const Ref<EditorNode3DGizmoPlugin> &p_a, const Ref<EditorNode3DGizmoPlugin> &p_b) const {
|
|
return p_a->get_gizmo_name() < p_b->get_gizmo_name();
|
|
}
|
|
};
|
|
|
|
void Node3DEditor::add_gizmo_plugin(Ref<EditorNode3DGizmoPlugin> p_plugin) {
|
|
ERR_FAIL_COND(p_plugin.is_null());
|
|
|
|
gizmo_plugins_by_priority.push_back(p_plugin);
|
|
gizmo_plugins_by_priority.sort_custom<_GizmoPluginPriorityComparator>();
|
|
|
|
gizmo_plugins_by_name.push_back(p_plugin);
|
|
gizmo_plugins_by_name.sort_custom<_GizmoPluginNameComparator>();
|
|
|
|
_update_gizmos_menu();
|
|
}
|
|
|
|
void Node3DEditor::remove_gizmo_plugin(Ref<EditorNode3DGizmoPlugin> p_plugin) {
|
|
gizmo_plugins_by_priority.erase(p_plugin);
|
|
gizmo_plugins_by_name.erase(p_plugin);
|
|
_update_gizmos_menu();
|
|
}
|
|
|
|
DynamicBVH::ID Node3DEditor::insert_gizmo_bvh_node(Node3D *p_node, const AABB &p_aabb) {
|
|
return gizmo_bvh.insert(p_aabb, p_node);
|
|
}
|
|
|
|
void Node3DEditor::update_gizmo_bvh_node(DynamicBVH::ID p_id, const AABB &p_aabb) {
|
|
gizmo_bvh.update(p_id, p_aabb);
|
|
gizmo_bvh.optimize_incremental(1);
|
|
}
|
|
|
|
void Node3DEditor::remove_gizmo_bvh_node(DynamicBVH::ID p_id) {
|
|
gizmo_bvh.remove(p_id);
|
|
}
|
|
|
|
Vector<Node3D *> Node3DEditor::gizmo_bvh_ray_query(const Vector3 &p_ray_start, const Vector3 &p_ray_end) {
|
|
struct Result {
|
|
Vector<Node3D *> nodes;
|
|
bool operator()(void *p_data) {
|
|
nodes.append((Node3D *)p_data);
|
|
return false;
|
|
}
|
|
} result;
|
|
|
|
gizmo_bvh.ray_query(p_ray_start, p_ray_end, result);
|
|
|
|
return result.nodes;
|
|
}
|
|
|
|
Vector<Node3D *> Node3DEditor::gizmo_bvh_frustum_query(const Vector<Plane> &p_frustum) {
|
|
Vector<Vector3> points = Geometry3D::compute_convex_mesh_points(&p_frustum[0], p_frustum.size());
|
|
|
|
struct Result {
|
|
Vector<Node3D *> nodes;
|
|
bool operator()(void *p_data) {
|
|
nodes.append((Node3D *)p_data);
|
|
return false;
|
|
}
|
|
} result;
|
|
|
|
gizmo_bvh.convex_query(p_frustum.ptr(), p_frustum.size(), points.ptr(), points.size(), result);
|
|
|
|
return result.nodes;
|
|
}
|
|
|
|
Node3DEditorPlugin::Node3DEditorPlugin() {
|
|
spatial_editor = memnew(Node3DEditor);
|
|
spatial_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
|
EditorNode::get_singleton()->get_editor_main_screen()->get_control()->add_child(spatial_editor);
|
|
|
|
spatial_editor->hide();
|
|
}
|