godot/editor/plugins/spatial_editor_plugin.cpp
JFonS 9828c6bc86 Keep mouse inside 3D viewport rotation widget
Hide and keep the mouse in place when the user oribts the scene via the 3D
rotation widget.

(cherry picked from commit 051f02a3a0)
2020-05-11 11:01:13 +02:00

6728 lines
228 KiB
C++

/*************************************************************************/
/* spatial_editor_plugin.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* 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 "spatial_editor_plugin.h"
#include "core/math/camera_matrix.h"
#include "core/os/input.h"
#include "core/os/keyboard.h"
#include "core/print_string.h"
#include "core/project_settings.h"
#include "core/sort_array.h"
#include "editor/editor_node.h"
#include "editor/editor_scale.h"
#include "editor/editor_settings.h"
#include "editor/plugins/animation_player_editor_plugin.h"
#include "editor/plugins/script_editor_plugin.h"
#include "editor/script_editor_debugger.h"
#include "editor/spatial_editor_gizmos.h"
#include "scene/3d/camera.h"
#include "scene/3d/collision_shape.h"
#include "scene/3d/mesh_instance.h"
#include "scene/3d/physics_body.h"
#include "scene/3d/visual_instance.h"
#include "scene/gui/viewport_container.h"
#include "scene/resources/packed_scene.h"
#include "scene/resources/surface_tool.h"
#define DISTANCE_DEFAULT 4
#define GIZMO_ARROW_SIZE 0.35
#define GIZMO_RING_HALF_WIDTH 0.1
#define GIZMO_SCALE_DEFAULT 0.15
#define GIZMO_PLANE_SIZE 0.2
#define GIZMO_PLANE_DST 0.3
#define GIZMO_CIRCLE_SIZE 1.1
#define GIZMO_SCALE_OFFSET (GIZMO_CIRCLE_SIZE + 0.3)
#define GIZMO_ARROW_OFFSET (GIZMO_CIRCLE_SIZE + 0.3)
#define ZOOM_MIN_DISTANCE 0.001
#define ZOOM_MULTIPLIER 1.08
#define ZOOM_INDICATOR_DELAY_S 1.5
#define FREELOOK_MIN_SPEED 0.01
#define FREELOOK_SPEED_MULTIPLIER 1.08
#define MIN_Z 0.01
#define MAX_Z 1000000.0
#define MIN_FOV 0.01
#define MAX_FOV 179
void ViewportRotationControl::_notification(int p_what) {
if (p_what == NOTIFICATION_ENTER_TREE) {
axis_menu_options.clear();
axis_menu_options.push_back(SpatialEditorViewport::VIEW_RIGHT);
axis_menu_options.push_back(SpatialEditorViewport::VIEW_TOP);
axis_menu_options.push_back(SpatialEditorViewport::VIEW_FRONT);
axis_menu_options.push_back(SpatialEditorViewport::VIEW_LEFT);
axis_menu_options.push_back(SpatialEditorViewport::VIEW_BOTTOM);
axis_menu_options.push_back(SpatialEditorViewport::VIEW_REAR);
axis_colors.clear();
axis_colors.push_back(get_color("axis_x_color", "Editor"));
axis_colors.push_back(get_color("axis_y_color", "Editor"));
axis_colors.push_back(get_color("axis_z_color", "Editor"));
update();
if (!is_connected("mouse_exited", this, "_on_mouse_exited")) {
connect("mouse_exited", this, "_on_mouse_exited");
}
}
if (p_what == NOTIFICATION_DRAW && viewport != nullptr) {
_draw();
}
}
void ViewportRotationControl::_draw() {
Vector2i center = get_size() / 2.0;
float radius = get_size().x / 2.0;
if (focused_axis > -2 || orbiting) {
draw_circle(center, radius, Color(0.5, 0.5, 0.5, 0.25));
}
Vector<Axis2D> axis_to_draw;
_get_sorted_axis(axis_to_draw);
for (int i = 0; i < axis_to_draw.size(); ++i) {
_draw_axis(axis_to_draw[i]);
}
}
void ViewportRotationControl::_draw_axis(const Axis2D &p_axis) {
bool focused = focused_axis == p_axis.axis;
bool positive = p_axis.axis < 3;
bool front = (Math::abs(p_axis.z_axis) <= 0.001 && positive) || p_axis.z_axis > 0.001;
int direction = p_axis.axis % 3;
Color axis_color = axis_colors[direction];
if (!front) {
axis_color = axis_color.darkened(0.4);
}
Color c = focused ? Color(0.9, 0.9, 0.9) : axis_color;
if (positive) {
Vector2i center = get_size() / 2.0;
draw_line(center, p_axis.screen_point, c, 1.5 * EDSCALE, true);
}
if (front) {
String axis_name = direction == 0 ? "X" : (direction == 1 ? "Y" : "Z");
draw_circle(p_axis.screen_point, AXIS_CIRCLE_RADIUS, c);
draw_char(get_font("rotation_control", "EditorFonts"), p_axis.screen_point + Vector2(-4.0, 5.0) * EDSCALE, axis_name, "", Color(0.3, 0.3, 0.3));
} else {
draw_circle(p_axis.screen_point, AXIS_CIRCLE_RADIUS * (0.55 + (0.2 * (1.0 + p_axis.z_axis))), c);
}
}
void ViewportRotationControl::_get_sorted_axis(Vector<Axis2D> &r_axis) {
Vector2i center = get_size() / 2.0;
float radius = get_size().x / 2.0;
float axis_radius = radius - AXIS_CIRCLE_RADIUS - 2.0 * EDSCALE;
Basis camera_basis = viewport->to_camera_transform(viewport->cursor).get_basis().inverse();
for (int i = 0; i < 3; ++i) {
Vector3 axis_3d = camera_basis.get_axis(i);
Vector2i axis_vector = Vector2(axis_3d.x, -axis_3d.y) * axis_radius;
if (Math::abs(axis_3d.z) < 1.0) {
Axis2D pos_axis;
pos_axis.axis = i;
pos_axis.screen_point = center + axis_vector;
pos_axis.z_axis = axis_3d.z;
r_axis.push_back(pos_axis);
Axis2D neg_axis;
neg_axis.axis = i + 3;
neg_axis.screen_point = center - axis_vector;
neg_axis.z_axis = -axis_3d.z;
r_axis.push_back(neg_axis);
} else {
// Special case when the camera is aligned with one axis
Axis2D axis;
axis.axis = i + (axis_3d.z < 0 ? 0 : 3);
axis.screen_point = center;
axis.z_axis = 1.0;
r_axis.push_back(axis);
}
}
r_axis.sort_custom<Axis2DCompare>();
}
void ViewportRotationControl::_gui_input(Ref<InputEvent> p_event) {
const Ref<InputEventMouseButton> mb = p_event;
if (mb.is_valid() && mb->get_button_index() == BUTTON_LEFT) {
Vector2 pos = mb->get_position();
if (mb->is_pressed()) {
if (pos.distance_to(get_size() / 2.0) < get_size().x / 2.0) {
orbiting = true;
}
} else {
if (focused_axis > -1) {
viewport->_menu_option(axis_menu_options[focused_axis]);
_update_focus();
}
orbiting = false;
if (Input::get_singleton()->get_mouse_mode() == Input::MOUSE_MODE_CAPTURED) {
Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE);
Input::get_singleton()->warp_mouse_position(orbiting_mouse_start);
}
}
}
const Ref<InputEventMouseMotion> mm = p_event;
if (mm.is_valid()) {
if (orbiting) {
if (Input::get_singleton()->get_mouse_mode() == Input::MOUSE_MODE_VISIBLE) {
Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_CAPTURED);
orbiting_mouse_start = mm->get_global_position();
}
viewport->_nav_orbit(mm, viewport->_get_warped_mouse_motion(mm));
focused_axis = -1;
} else {
_update_focus();
}
}
}
void ViewportRotationControl::_update_focus() {
int original_focus = focused_axis;
focused_axis = -2;
Vector2 mouse_pos = get_local_mouse_position();
if (mouse_pos.distance_to(get_size() / 2.0) < get_size().x / 2.0) {
focused_axis = -1;
}
Vector<Axis2D> axes;
_get_sorted_axis(axes);
for (int i = 0; i < axes.size(); i++) {
const Axis2D &axis = axes[i];
if (mouse_pos.distance_to(axis.screen_point) < AXIS_CIRCLE_RADIUS) {
focused_axis = axis.axis;
}
}
if (focused_axis != original_focus) {
update();
}
}
void ViewportRotationControl::_on_mouse_exited() {
focused_axis = -2;
update();
}
void ViewportRotationControl::set_viewport(SpatialEditorViewport *p_viewport) {
viewport = p_viewport;
}
void ViewportRotationControl::_bind_methods() {
ClassDB::bind_method(D_METHOD("_gui_input"), &ViewportRotationControl::_gui_input);
ClassDB::bind_method(D_METHOD("_on_mouse_exited"), &ViewportRotationControl::_on_mouse_exited);
}
void SpatialEditorViewport::_update_camera(float p_interp_delta) {
bool is_orthogonal = camera->get_projection() == Camera::PROJECTION_ORTHOGONAL;
Cursor old_camera_cursor = camera_cursor;
camera_cursor = cursor;
if (p_interp_delta > 0) {
//-------
// Perform smoothing
if (is_freelook_active()) {
// Higher inertia should increase "lag" (lerp with factor between 0 and 1)
// Inertia of zero should produce instant movement (lerp with factor of 1) in this case it returns a really high value and gets clamped to 1.
real_t inertia = EDITOR_GET("editors/3d/freelook/freelook_inertia");
inertia = MAX(0.001, inertia);
real_t factor = (1.0 / inertia) * p_interp_delta;
// We interpolate a different point here, because in freelook mode the focus point (cursor.pos) orbits around eye_pos
camera_cursor.eye_pos = old_camera_cursor.eye_pos.linear_interpolate(cursor.eye_pos, CLAMP(factor, 0, 1));
float orbit_inertia = EDITOR_GET("editors/3d/navigation_feel/orbit_inertia");
orbit_inertia = MAX(0.0001, orbit_inertia);
camera_cursor.x_rot = Math::lerp(old_camera_cursor.x_rot, cursor.x_rot, MIN(1.f, p_interp_delta * (1 / orbit_inertia)));
camera_cursor.y_rot = Math::lerp(old_camera_cursor.y_rot, cursor.y_rot, MIN(1.f, p_interp_delta * (1 / orbit_inertia)));
if (Math::abs(camera_cursor.x_rot - cursor.x_rot) < 0.1) {
camera_cursor.x_rot = cursor.x_rot;
}
if (Math::abs(camera_cursor.y_rot - cursor.y_rot) < 0.1) {
camera_cursor.y_rot = cursor.y_rot;
}
Vector3 forward = to_camera_transform(camera_cursor).basis.xform(Vector3(0, 0, -1));
camera_cursor.pos = camera_cursor.eye_pos + forward * camera_cursor.distance;
} else {
//when not being manipulated, move softly
float free_orbit_inertia = EDITOR_GET("editors/3d/navigation_feel/orbit_inertia");
float free_translation_inertia = EDITOR_GET("editors/3d/navigation_feel/translation_inertia");
//when being manipulated, move more quickly
float manip_orbit_inertia = EDITOR_GET("editors/3d/navigation_feel/manipulation_orbit_inertia");
float manip_translation_inertia = EDITOR_GET("editors/3d/navigation_feel/manipulation_translation_inertia");
float zoom_inertia = EDITOR_GET("editors/3d/navigation_feel/zoom_inertia");
//determine if being manipulated
bool manipulated = Input::get_singleton()->get_mouse_button_mask() & (2 | 4);
manipulated |= Input::get_singleton()->is_key_pressed(KEY_SHIFT);
manipulated |= Input::get_singleton()->is_key_pressed(KEY_ALT);
manipulated |= Input::get_singleton()->is_key_pressed(KEY_CONTROL);
float orbit_inertia = MAX(0.00001, manipulated ? manip_orbit_inertia : free_orbit_inertia);
float translation_inertia = MAX(0.0001, manipulated ? manip_translation_inertia : free_translation_inertia);
zoom_inertia = MAX(0.0001, zoom_inertia);
camera_cursor.x_rot = Math::lerp(old_camera_cursor.x_rot, cursor.x_rot, MIN(1.f, p_interp_delta * (1 / orbit_inertia)));
camera_cursor.y_rot = Math::lerp(old_camera_cursor.y_rot, cursor.y_rot, MIN(1.f, p_interp_delta * (1 / orbit_inertia)));
if (Math::abs(camera_cursor.x_rot - cursor.x_rot) < 0.1) {
camera_cursor.x_rot = cursor.x_rot;
}
if (Math::abs(camera_cursor.y_rot - cursor.y_rot) < 0.1) {
camera_cursor.y_rot = cursor.y_rot;
}
camera_cursor.pos = old_camera_cursor.pos.linear_interpolate(cursor.pos, MIN(1.f, p_interp_delta * (1 / translation_inertia)));
camera_cursor.distance = Math::lerp(old_camera_cursor.distance, cursor.distance, MIN(1.f, p_interp_delta * (1 / zoom_inertia)));
}
}
//-------
// Apply camera transform
float tolerance = 0.001;
bool equal = true;
if (Math::abs(old_camera_cursor.x_rot - camera_cursor.x_rot) > tolerance || Math::abs(old_camera_cursor.y_rot - camera_cursor.y_rot) > tolerance) {
equal = false;
}
if (equal && old_camera_cursor.pos.distance_squared_to(camera_cursor.pos) > tolerance * tolerance) {
equal = false;
}
if (equal && Math::abs(old_camera_cursor.distance - camera_cursor.distance) > tolerance) {
equal = false;
}
if (!equal || p_interp_delta == 0 || is_freelook_active() || is_orthogonal != orthogonal) {
camera->set_global_transform(to_camera_transform(camera_cursor));
if (orthogonal) {
float half_fov = Math::deg2rad(get_fov()) / 2.0;
float height = 2.0 * cursor.distance * Math::tan(half_fov);
camera->set_orthogonal(height, get_znear(), get_zfar());
} else {
camera->set_perspective(get_fov(), get_znear(), get_zfar());
}
update_transform_gizmo_view();
rotation_control->update();
}
}
Transform SpatialEditorViewport::to_camera_transform(const Cursor &p_cursor) const {
Transform camera_transform;
camera_transform.translate(p_cursor.pos);
camera_transform.basis.rotate(Vector3(1, 0, 0), -p_cursor.x_rot);
camera_transform.basis.rotate(Vector3(0, 1, 0), -p_cursor.y_rot);
if (orthogonal)
camera_transform.translate(0, 0, (get_zfar() - get_znear()) / 2.0);
else
camera_transform.translate(0, 0, p_cursor.distance);
return camera_transform;
}
int SpatialEditorViewport::get_selected_count() const {
Map<Node *, Object *> &selection = editor_selection->get_selection();
int count = 0;
for (Map<Node *, Object *>::Element *E = selection.front(); E; E = E->next()) {
Spatial *sp = Object::cast_to<Spatial>(E->key());
if (!sp)
continue;
SpatialEditorSelectedItem *se = editor_selection->get_node_editor_data<SpatialEditorSelectedItem>(sp);
if (!se)
continue;
count++;
}
return count;
}
float SpatialEditorViewport::get_znear() const {
return CLAMP(spatial_editor->get_znear(), MIN_Z, MAX_Z);
}
float SpatialEditorViewport::get_zfar() const {
return CLAMP(spatial_editor->get_zfar(), MIN_Z, MAX_Z);
}
float SpatialEditorViewport::get_fov() const {
return CLAMP(spatial_editor->get_fov(), MIN_FOV, MAX_FOV);
}
Transform SpatialEditorViewport::_get_camera_transform() const {
return camera->get_global_transform();
}
Vector3 SpatialEditorViewport::_get_camera_position() const {
return _get_camera_transform().origin;
}
Point2 SpatialEditorViewport::_point_to_screen(const Vector3 &p_point) {
return camera->unproject_position(p_point) * viewport_container->get_stretch_shrink();
}
Vector3 SpatialEditorViewport::_get_ray_pos(const Vector2 &p_pos) const {
return camera->project_ray_origin(p_pos / viewport_container->get_stretch_shrink());
}
Vector3 SpatialEditorViewport::_get_camera_normal() const {
return -_get_camera_transform().basis.get_axis(2);
}
Vector3 SpatialEditorViewport::_get_ray(const Vector2 &p_pos) const {
return camera->project_ray_normal(p_pos / viewport_container->get_stretch_shrink());
}
void SpatialEditorViewport::_clear_selected() {
editor_selection->clear();
}
void SpatialEditorViewport::_select_clicked(bool p_append, bool p_single) {
if (!clicked)
return;
Node *node = Object::cast_to<Node>(ObjectDB::get_instance(clicked));
Spatial *selected = Object::cast_to<Spatial>(node);
if (!selected)
return;
// Replace the node by the group if grouped
while (node && node != editor->get_edited_scene()->get_parent()) {
Spatial *selected_tmp = Object::cast_to<Spatial>(node);
if (selected_tmp && node->has_meta("_edit_group_")) {
selected = selected_tmp;
}
node = node->get_parent();
}
if (!_is_node_locked(selected))
_select(selected, clicked_wants_append, true);
}
void SpatialEditorViewport::_select(Node *p_node, bool p_append, bool p_single) {
if (!p_append) {
editor_selection->clear();
}
if (editor_selection->is_selected(p_node)) {
//erase
editor_selection->remove_node(p_node);
} else {
editor_selection->add_node(p_node);
}
if (p_single) {
if (Engine::get_singleton()->is_editor_hint())
editor->call("edit_node", p_node);
}
}
ObjectID SpatialEditorViewport::_select_ray(const Point2 &p_pos, bool p_append, bool &r_includes_current, int *r_gizmo_handle, bool p_alt_select) {
if (r_gizmo_handle)
*r_gizmo_handle = -1;
Vector3 ray = _get_ray(p_pos);
Vector3 pos = _get_ray_pos(p_pos);
Vector2 shrinked_pos = p_pos / viewport_container->get_stretch_shrink();
Vector<ObjectID> instances = VisualServer::get_singleton()->instances_cull_ray(pos, ray, get_tree()->get_root()->get_world()->get_scenario());
Set<Ref<EditorSpatialGizmo> > found_gizmos;
Node *edited_scene = get_tree()->get_edited_scene_root();
ObjectID closest = 0;
Node *item = NULL;
float closest_dist = 1e20;
int selected_handle = -1;
for (int i = 0; i < instances.size(); i++) {
Spatial *spat = Object::cast_to<Spatial>(ObjectDB::get_instance(instances[i]));
if (!spat)
continue;
Ref<EditorSpatialGizmo> seg = spat->get_gizmo();
if ((!seg.is_valid()) || found_gizmos.has(seg)) {
continue;
}
found_gizmos.insert(seg);
Vector3 point;
Vector3 normal;
int handle = -1;
bool inters = seg->intersect_ray(camera, shrinked_pos, point, normal, &handle, p_alt_select);
if (!inters)
continue;
float dist = pos.distance_to(point);
if (dist < 0)
continue;
if (dist < closest_dist) {
item = Object::cast_to<Node>(spat);
while (item->get_owner() && item->get_owner() != edited_scene && !edited_scene->is_editable_instance(item->get_owner())) {
item = item->get_owner();
}
closest = item->get_instance_id();
closest_dist = dist;
selected_handle = handle;
}
}
if (!item)
return 0;
if (!editor_selection->is_selected(item) || (r_gizmo_handle && selected_handle >= 0)) {
if (r_gizmo_handle)
*r_gizmo_handle = selected_handle;
}
return closest;
}
void SpatialEditorViewport::_find_items_at_pos(const Point2 &p_pos, bool &r_includes_current, Vector<_RayResult> &results, bool p_alt_select) {
Vector3 ray = _get_ray(p_pos);
Vector3 pos = _get_ray_pos(p_pos);
Vector<ObjectID> instances = VisualServer::get_singleton()->instances_cull_ray(pos, ray, get_tree()->get_root()->get_world()->get_scenario());
Set<Ref<EditorSpatialGizmo> > found_gizmos;
r_includes_current = false;
for (int i = 0; i < instances.size(); i++) {
Spatial *spat = Object::cast_to<Spatial>(ObjectDB::get_instance(instances[i]));
if (!spat)
continue;
Ref<EditorSpatialGizmo> seg = spat->get_gizmo();
if (!seg.is_valid())
continue;
if (found_gizmos.has(seg))
continue;
found_gizmos.insert(seg);
Vector3 point;
Vector3 normal;
int handle = -1;
bool inters = seg->intersect_ray(camera, p_pos, point, normal, NULL, p_alt_select);
if (!inters)
continue;
float dist = pos.distance_to(point);
if (dist < 0)
continue;
if (editor_selection->is_selected(spat))
r_includes_current = true;
_RayResult res;
res.item = spat;
res.depth = dist;
res.handle = handle;
results.push_back(res);
}
if (results.empty())
return;
results.sort();
}
Vector3 SpatialEditorViewport::_get_screen_to_space(const Vector3 &p_vector3) {
CameraMatrix cm;
if (orthogonal) {
cm.set_orthogonal(camera->get_size(), get_size().aspect(), get_znear() + p_vector3.z, get_zfar());
} else {
cm.set_perspective(get_fov(), get_size().aspect(), get_znear() + p_vector3.z, get_zfar());
}
Vector2 screen_he = cm.get_viewport_half_extents();
Transform camera_transform;
camera_transform.translate(cursor.pos);
camera_transform.basis.rotate(Vector3(1, 0, 0), -cursor.x_rot);
camera_transform.basis.rotate(Vector3(0, 1, 0), -cursor.y_rot);
camera_transform.translate(0, 0, cursor.distance);
return camera_transform.xform(Vector3(((p_vector3.x / get_size().width) * 2.0 - 1.0) * screen_he.x, ((1.0 - (p_vector3.y / get_size().height)) * 2.0 - 1.0) * screen_he.y, -(get_znear() + p_vector3.z)));
}
void SpatialEditorViewport::_select_region() {
if (cursor.region_begin == cursor.region_end)
return; //nothing really
float z_offset = MAX(0.0, 5.0 - get_znear());
Vector3 box[4] = {
Vector3(
MIN(cursor.region_begin.x, cursor.region_end.x),
MIN(cursor.region_begin.y, cursor.region_end.y),
z_offset),
Vector3(
MAX(cursor.region_begin.x, cursor.region_end.x),
MIN(cursor.region_begin.y, cursor.region_end.y),
z_offset),
Vector3(
MAX(cursor.region_begin.x, cursor.region_end.x),
MAX(cursor.region_begin.y, cursor.region_end.y),
z_offset),
Vector3(
MIN(cursor.region_begin.x, cursor.region_end.x),
MAX(cursor.region_begin.y, cursor.region_end.y),
z_offset)
};
Vector<Plane> frustum;
Vector3 cam_pos = _get_camera_position();
for (int i = 0; i < 4; i++) {
Vector3 a = _get_screen_to_space(box[i]);
Vector3 b = _get_screen_to_space(box[(i + 1) % 4]);
if (orthogonal) {
frustum.push_back(Plane(a, (a - b).normalized()));
} else {
frustum.push_back(Plane(a, b, cam_pos));
}
}
Plane near(cam_pos, -_get_camera_normal());
near.d -= get_znear();
frustum.push_back(near);
Plane far = -near;
far.d += get_zfar();
frustum.push_back(far);
Vector<ObjectID> instances = VisualServer::get_singleton()->instances_cull_convex(frustum, get_tree()->get_root()->get_world()->get_scenario());
Vector<Node *> selected;
Node *edited_scene = get_tree()->get_edited_scene_root();
for (int i = 0; i < instances.size(); i++) {
Spatial *sp = Object::cast_to<Spatial>(ObjectDB::get_instance(instances[i]));
if (!sp || _is_node_locked(sp))
continue;
Node *item = Object::cast_to<Node>(sp);
while (item->get_owner() && item->get_owner() != edited_scene && !edited_scene->is_editable_instance(item->get_owner())) {
item = item->get_owner();
}
// Replace the node by the group if grouped
if (item->is_class("Spatial")) {
Spatial *sel = Object::cast_to<Spatial>(item);
while (item && item != editor->get_edited_scene()->get_parent()) {
Spatial *selected_tmp = Object::cast_to<Spatial>(item);
if (selected_tmp && item->has_meta("_edit_group_")) {
sel = selected_tmp;
}
item = item->get_parent();
}
item = sel;
}
if (selected.find(item) != -1) continue;
if (_is_node_locked(item)) continue;
Ref<EditorSpatialGizmo> seg = sp->get_gizmo();
if (!seg.is_valid())
continue;
if (seg->intersect_frustum(camera, frustum)) {
selected.push_back(item);
}
}
bool single = selected.size() == 1;
for (int i = 0; i < selected.size(); i++) {
_select(selected[i], true, single);
}
}
void SpatialEditorViewport::_update_name() {
String view_mode = orthogonal ? TTR("Orthogonal") : TTR("Perspective");
if (auto_orthogonal) {
view_mode += " [auto]";
}
if (name != "")
view_menu->set_text(name + " " + view_mode);
else
view_menu->set_text(view_mode);
view_menu->set_size(Vector2(0, 0)); // resets the button size
}
void SpatialEditorViewport::_compute_edit(const Point2 &p_point) {
_edit.click_ray = _get_ray(Vector2(p_point.x, p_point.y));
_edit.click_ray_pos = _get_ray_pos(Vector2(p_point.x, p_point.y));
_edit.plane = TRANSFORM_VIEW;
spatial_editor->update_transform_gizmo();
_edit.center = spatial_editor->get_gizmo_transform().origin;
List<Node *> &selection = editor_selection->get_selected_node_list();
for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
Spatial *sp = Object::cast_to<Spatial>(E->get());
if (!sp)
continue;
SpatialEditorSelectedItem *se = editor_selection->get_node_editor_data<SpatialEditorSelectedItem>(sp);
if (!se)
continue;
se->original = se->sp->get_global_gizmo_transform();
se->original_local = se->sp->get_local_gizmo_transform();
}
}
static int _get_key_modifier_setting(const String &p_property) {
switch (EditorSettings::get_singleton()->get(p_property).operator int()) {
case 0: return 0;
case 1: return KEY_SHIFT;
case 2: return KEY_ALT;
case 3: return KEY_META;
case 4: return KEY_CONTROL;
}
return 0;
}
static int _get_key_modifier(Ref<InputEventWithModifiers> e) {
if (e->get_shift())
return KEY_SHIFT;
if (e->get_alt())
return KEY_ALT;
if (e->get_control())
return KEY_CONTROL;
if (e->get_metakey())
return KEY_META;
return 0;
}
bool SpatialEditorViewport::_gizmo_select(const Vector2 &p_screenpos, bool p_highlight_only) {
if (!spatial_editor->is_gizmo_visible())
return false;
if (get_selected_count() == 0) {
if (p_highlight_only)
spatial_editor->select_gizmo_highlight_axis(-1);
return false;
}
Vector3 ray_pos = _get_ray_pos(Vector2(p_screenpos.x, p_screenpos.y));
Vector3 ray = _get_ray(Vector2(p_screenpos.x, p_screenpos.y));
Transform gt = spatial_editor->get_gizmo_transform();
float gs = gizmo_scale;
if (spatial_editor->get_tool_mode() == SpatialEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == SpatialEditor::TOOL_MODE_MOVE) {
int col_axis = -1;
float col_d = 1e20;
for (int i = 0; i < 3; i++) {
Vector3 grabber_pos = gt.origin + gt.basis.get_axis(i) * gs * (GIZMO_ARROW_OFFSET + (GIZMO_ARROW_SIZE * 0.5));
float grabber_radius = gs * GIZMO_ARROW_SIZE;
Vector3 r;
if (Geometry::segment_intersects_sphere(ray_pos, ray_pos + ray * MAX_Z, grabber_pos, grabber_radius, &r)) {
float d = r.distance_to(ray_pos);
if (d < col_d) {
col_d = d;
col_axis = i;
}
}
}
bool is_plane_translate = false;
// plane select
if (col_axis == -1) {
col_d = 1e20;
for (int i = 0; i < 3; i++) {
Vector3 ivec2 = gt.basis.get_axis((i + 1) % 3).normalized();
Vector3 ivec3 = gt.basis.get_axis((i + 2) % 3).normalized();
Vector3 grabber_pos = gt.origin + (ivec2 + ivec3) * gs * (GIZMO_PLANE_SIZE + GIZMO_PLANE_DST);
Vector3 r;
Plane plane(gt.origin, gt.basis.get_axis(i).normalized());
if (plane.intersects_ray(ray_pos, ray, &r)) {
float dist = r.distance_to(grabber_pos);
if (dist < (gs * GIZMO_PLANE_SIZE)) {
float d = ray_pos.distance_to(r);
if (d < col_d) {
col_d = d;
col_axis = i;
is_plane_translate = true;
}
}
}
}
}
if (col_axis != -1) {
if (p_highlight_only) {
spatial_editor->select_gizmo_highlight_axis(col_axis + (is_plane_translate ? 6 : 0));
} else {
//handle plane translate
_edit.mode = TRANSFORM_TRANSLATE;
_compute_edit(Point2(p_screenpos.x, p_screenpos.y));
_edit.plane = TransformPlane(TRANSFORM_X_AXIS + col_axis + (is_plane_translate ? 3 : 0));
}
return true;
}
}
if (spatial_editor->get_tool_mode() == SpatialEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == SpatialEditor::TOOL_MODE_ROTATE) {
int col_axis = -1;
float col_d = 1e20;
for (int i = 0; i < 3; i++) {
Plane plane(gt.origin, gt.basis.get_axis(i).normalized());
Vector3 r;
if (!plane.intersects_ray(ray_pos, ray, &r))
continue;
float dist = r.distance_to(gt.origin);
if (dist > gs * (GIZMO_CIRCLE_SIZE - GIZMO_RING_HALF_WIDTH) && dist < gs * (GIZMO_CIRCLE_SIZE + GIZMO_RING_HALF_WIDTH)) {
float d = ray_pos.distance_to(r);
if (d < col_d) {
col_d = d;
col_axis = i;
}
}
}
if (col_axis != -1) {
if (p_highlight_only) {
spatial_editor->select_gizmo_highlight_axis(col_axis + 3);
} else {
//handle rotate
_edit.mode = TRANSFORM_ROTATE;
_compute_edit(Point2(p_screenpos.x, p_screenpos.y));
_edit.plane = TransformPlane(TRANSFORM_X_AXIS + col_axis);
}
return true;
}
}
if (spatial_editor->get_tool_mode() == SpatialEditor::TOOL_MODE_SCALE) {
int col_axis = -1;
float col_d = 1e20;
for (int i = 0; i < 3; i++) {
Vector3 grabber_pos = gt.origin + gt.basis.get_axis(i) * gs * GIZMO_SCALE_OFFSET;
float grabber_radius = gs * GIZMO_ARROW_SIZE;
Vector3 r;
if (Geometry::segment_intersects_sphere(ray_pos, ray_pos + ray * MAX_Z, grabber_pos, grabber_radius, &r)) {
float d = r.distance_to(ray_pos);
if (d < col_d) {
col_d = d;
col_axis = i;
}
}
}
bool is_plane_scale = false;
// plane select
if (col_axis == -1) {
col_d = 1e20;
for (int i = 0; i < 3; i++) {
Vector3 ivec2 = gt.basis.get_axis((i + 1) % 3).normalized();
Vector3 ivec3 = gt.basis.get_axis((i + 2) % 3).normalized();
Vector3 grabber_pos = gt.origin + (ivec2 + ivec3) * gs * (GIZMO_PLANE_SIZE + GIZMO_PLANE_DST);
Vector3 r;
Plane plane(gt.origin, gt.basis.get_axis(i).normalized());
if (plane.intersects_ray(ray_pos, ray, &r)) {
float dist = r.distance_to(grabber_pos);
if (dist < (gs * GIZMO_PLANE_SIZE)) {
float d = ray_pos.distance_to(r);
if (d < col_d) {
col_d = d;
col_axis = i;
is_plane_scale = true;
}
}
}
}
}
if (col_axis != -1) {
if (p_highlight_only) {
spatial_editor->select_gizmo_highlight_axis(col_axis + (is_plane_scale ? 12 : 9));
} else {
//handle scale
_edit.mode = TRANSFORM_SCALE;
_compute_edit(Point2(p_screenpos.x, p_screenpos.y));
_edit.plane = TransformPlane(TRANSFORM_X_AXIS + col_axis + (is_plane_scale ? 3 : 0));
}
return true;
}
}
if (p_highlight_only)
spatial_editor->select_gizmo_highlight_axis(-1);
return false;
}
void SpatialEditorViewport::_surface_mouse_enter() {
if (!surface->has_focus() && (!get_focus_owner() || !get_focus_owner()->is_text_field()))
surface->grab_focus();
}
void SpatialEditorViewport::_surface_mouse_exit() {
_remove_preview();
}
void SpatialEditorViewport::_surface_focus_enter() {
view_menu->set_disable_shortcuts(false);
}
void SpatialEditorViewport::_surface_focus_exit() {
view_menu->set_disable_shortcuts(true);
}
bool SpatialEditorViewport ::_is_node_locked(const Node *p_node) {
return p_node->has_meta("_edit_lock_") && p_node->get_meta("_edit_lock_");
}
void SpatialEditorViewport::_list_select(Ref<InputEventMouseButton> b) {
_find_items_at_pos(b->get_position(), clicked_includes_current, selection_results, b->get_shift());
Node *scene = editor->get_edited_scene();
for (int i = 0; i < selection_results.size(); i++) {
Spatial *item = selection_results[i].item;
if (item != scene && item->get_owner() != scene && !scene->is_editable_instance(item->get_owner())) {
//invalid result
selection_results.remove(i);
i--;
}
}
clicked_wants_append = b->get_shift();
if (selection_results.size() == 1) {
clicked = selection_results[0].item->get_instance_id();
selection_results.clear();
if (clicked) {
_select_clicked(clicked_wants_append, true);
clicked = 0;
}
} else if (!selection_results.empty()) {
NodePath root_path = get_tree()->get_edited_scene_root()->get_path();
StringName root_name = root_path.get_name(root_path.get_name_count() - 1);
for (int i = 0; i < selection_results.size(); i++) {
Spatial *spat = selection_results[i].item;
Ref<Texture> icon = EditorNode::get_singleton()->get_object_icon(spat, "Node");
String node_path = "/" + root_name + "/" + root_path.rel_path_to(spat->get_path());
selection_menu->add_item(spat->get_name());
selection_menu->set_item_icon(i, icon);
selection_menu->set_item_metadata(i, node_path);
selection_menu->set_item_tooltip(i, String(spat->get_name()) + "\nType: " + spat->get_class() + "\nPath: " + node_path);
}
selection_menu->set_global_position(b->get_global_position());
selection_menu->popup();
}
}
void SpatialEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
if (previewing)
return; //do NONE
{
EditorNode *en = editor;
EditorPluginList *force_input_forwarding_list = en->get_editor_plugins_force_input_forwarding();
if (!force_input_forwarding_list->empty()) {
bool discard = force_input_forwarding_list->forward_spatial_gui_input(camera, p_event, true);
if (discard)
return;
}
}
{
EditorNode *en = editor;
EditorPluginList *over_plugin_list = en->get_editor_plugins_over();
if (!over_plugin_list->empty()) {
bool discard = over_plugin_list->forward_spatial_gui_input(camera, p_event, false);
if (discard)
return;
}
}
Ref<InputEventMouseButton> b = p_event;
if (b.is_valid()) {
emit_signal("clicked", this);
float zoom_factor = 1 + (ZOOM_MULTIPLIER - 1) * b->get_factor();
switch (b->get_button_index()) {
case BUTTON_WHEEL_UP: {
if (is_freelook_active())
scale_freelook_speed(zoom_factor);
else
scale_cursor_distance(1.0 / zoom_factor);
} break;
case BUTTON_WHEEL_DOWN: {
if (is_freelook_active())
scale_freelook_speed(1.0 / zoom_factor);
else
scale_cursor_distance(zoom_factor);
} break;
case BUTTON_RIGHT: {
NavigationScheme nav_scheme = (NavigationScheme)EditorSettings::get_singleton()->get("editors/3d/navigation/navigation_scheme").operator int();
if (b->is_pressed() && _edit.gizmo.is_valid()) {
//restore
_edit.gizmo->commit_handle(_edit.gizmo_handle, _edit.gizmo_initial_value, true);
_edit.gizmo = Ref<EditorSpatialGizmo>();
}
if (_edit.mode == TRANSFORM_NONE && b->is_pressed()) {
if (b->get_alt()) {
if (nav_scheme == NAVIGATION_MAYA)
break;
_list_select(b);
return;
}
}
if (_edit.mode != TRANSFORM_NONE && b->is_pressed()) {
//cancel motion
_edit.mode = TRANSFORM_NONE;
List<Node *> &selection = editor_selection->get_selected_node_list();
for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
Spatial *sp = Object::cast_to<Spatial>(E->get());
if (!sp)
continue;
SpatialEditorSelectedItem *se = editor_selection->get_node_editor_data<SpatialEditorSelectedItem>(sp);
if (!se)
continue;
sp->set_global_transform(se->original);
}
surface->update();
set_message(TTR("Transform Aborted."), 3);
}
if (b->is_pressed()) {
const int mod = _get_key_modifier(b);
if (!orthogonal) {
if (mod == _get_key_modifier_setting("editors/3d/freelook/freelook_activation_modifier")) {
set_freelook_active(true);
}
}
} else {
set_freelook_active(false);
}
if (freelook_active && !surface->has_focus()) {
// Focus usually doesn't trigger on right-click, but in case of freelook it should,
// otherwise using keyboard navigation would misbehave
surface->grab_focus();
}
} break;
case BUTTON_MIDDLE: {
if (b->is_pressed() && _edit.mode != TRANSFORM_NONE) {
switch (_edit.plane) {
case TRANSFORM_VIEW: {
_edit.plane = TRANSFORM_X_AXIS;
set_message(TTR("X-Axis Transform."), 2);
name = "";
_update_name();
} break;
case TRANSFORM_X_AXIS: {
_edit.plane = TRANSFORM_Y_AXIS;
set_message(TTR("Y-Axis Transform."), 2);
} break;
case TRANSFORM_Y_AXIS: {
_edit.plane = TRANSFORM_Z_AXIS;
set_message(TTR("Z-Axis Transform."), 2);
} break;
case TRANSFORM_Z_AXIS: {
_edit.plane = TRANSFORM_VIEW;
set_message(TTR("View Plane Transform."), 2);
} break;
case TRANSFORM_YZ:
case TRANSFORM_XZ:
case TRANSFORM_XY: {
} break;
}
}
} break;
case BUTTON_LEFT: {
if (b->is_pressed()) {
NavigationScheme nav_scheme = (NavigationScheme)EditorSettings::get_singleton()->get("editors/3d/navigation/navigation_scheme").operator int();
if ((nav_scheme == NAVIGATION_MAYA || nav_scheme == NAVIGATION_MODO) && b->get_alt()) {
break;
}
if (spatial_editor->get_tool_mode() == SpatialEditor::TOOL_MODE_LIST_SELECT) {
_list_select(b);
break;
}
_edit.mouse_pos = b->get_position();
_edit.snap = spatial_editor->is_snap_enabled();
_edit.mode = TRANSFORM_NONE;
//gizmo has priority over everything
bool can_select_gizmos = true;
{
int idx = view_menu->get_popup()->get_item_index(VIEW_GIZMOS);
can_select_gizmos = view_menu->get_popup()->is_item_checked(idx);
}
if (can_select_gizmos && spatial_editor->get_selected()) {
Ref<EditorSpatialGizmo> seg = spatial_editor->get_selected()->get_gizmo();
if (seg.is_valid()) {
int handle = -1;
Vector3 point;
Vector3 normal;
bool inters = seg->intersect_ray(camera, _edit.mouse_pos, point, normal, &handle, b->get_shift());
if (inters && handle != -1) {
_edit.gizmo = seg;
_edit.gizmo_handle = handle;
_edit.gizmo_initial_value = seg->get_handle_value(handle);
break;
}
}
}
if (_gizmo_select(_edit.mouse_pos))
break;
clicked = 0;
clicked_includes_current = false;
if ((spatial_editor->get_tool_mode() == SpatialEditor::TOOL_MODE_SELECT && b->get_control()) || spatial_editor->get_tool_mode() == SpatialEditor::TOOL_MODE_ROTATE) {
/* HANDLE ROTATION */
if (get_selected_count() == 0)
break; //bye
//handle rotate
_edit.mode = TRANSFORM_ROTATE;
_compute_edit(b->get_position());
break;
}
if (spatial_editor->get_tool_mode() == SpatialEditor::TOOL_MODE_MOVE) {
if (get_selected_count() == 0)
break; //bye
//handle translate
_edit.mode = TRANSFORM_TRANSLATE;
_compute_edit(b->get_position());
break;
}
if (spatial_editor->get_tool_mode() == SpatialEditor::TOOL_MODE_SCALE) {
if (get_selected_count() == 0)
break; //bye
//handle scale
_edit.mode = TRANSFORM_SCALE;
_compute_edit(b->get_position());
break;
}
// todo scale
int gizmo_handle = -1;
clicked = _select_ray(b->get_position(), b->get_shift(), clicked_includes_current, &gizmo_handle, b->get_shift());
//clicking is always deferred to either move or release
clicked_wants_append = b->get_shift();
if (!clicked) {
if (!clicked_wants_append)
_clear_selected();
//default to regionselect
cursor.region_select = true;
cursor.region_begin = b->get_position();
cursor.region_end = b->get_position();
}
if (clicked && gizmo_handle >= 0) {
Spatial *spa = Object::cast_to<Spatial>(ObjectDB::get_instance(clicked));
if (spa) {
Ref<EditorSpatialGizmo> seg = spa->get_gizmo();
if (seg.is_valid()) {
_edit.gizmo = seg;
_edit.gizmo_handle = gizmo_handle;
_edit.gizmo_initial_value = seg->get_handle_value(gizmo_handle);
break;
}
}
}
surface->update();
} else {
if (_edit.gizmo.is_valid()) {
_edit.gizmo->commit_handle(_edit.gizmo_handle, _edit.gizmo_initial_value, false);
_edit.gizmo = Ref<EditorSpatialGizmo>();
break;
}
if (clicked) {
_select_clicked(clicked_wants_append, true);
// Processing was deferred.
clicked = 0;
}
if (cursor.region_select) {
if (!clicked_wants_append) _clear_selected();
_select_region();
cursor.region_select = false;
surface->update();
}
if (_edit.mode != TRANSFORM_NONE) {
static const char *_transform_name[4] = { "None", "Rotate", "Translate", "Scale" };
undo_redo->create_action(_transform_name[_edit.mode]);
List<Node *> &selection = editor_selection->get_selected_node_list();
for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
Spatial *sp = Object::cast_to<Spatial>(E->get());
if (!sp)
continue;
SpatialEditorSelectedItem *se = editor_selection->get_node_editor_data<SpatialEditorSelectedItem>(sp);
if (!se)
continue;
undo_redo->add_do_method(sp, "set_global_transform", sp->get_global_gizmo_transform());
undo_redo->add_undo_method(sp, "set_global_transform", se->original);
}
undo_redo->commit_action();
_edit.mode = TRANSFORM_NONE;
set_message("");
}
surface->update();
}
} break;
}
}
Ref<InputEventMouseMotion> m = p_event;
if (m.is_valid()) {
_edit.mouse_pos = m->get_position();
if (spatial_editor->get_selected()) {
Ref<EditorSpatialGizmo> seg = spatial_editor->get_selected()->get_gizmo();
if (seg.is_valid()) {
int selected_handle = -1;
int handle = -1;
Vector3 point;
Vector3 normal;
bool inters = seg->intersect_ray(camera, _edit.mouse_pos, point, normal, &handle, false);
if (inters && handle != -1) {
selected_handle = handle;
}
if (selected_handle != spatial_editor->get_over_gizmo_handle()) {
spatial_editor->set_over_gizmo_handle(selected_handle);
spatial_editor->get_selected()->update_gizmo();
if (selected_handle != -1)
spatial_editor->select_gizmo_highlight_axis(-1);
}
}
}
if (spatial_editor->get_over_gizmo_handle() == -1 && !(m->get_button_mask() & 1) && !_edit.gizmo.is_valid()) {
_gizmo_select(_edit.mouse_pos, true);
}
NavigationScheme nav_scheme = (NavigationScheme)EditorSettings::get_singleton()->get("editors/3d/navigation/navigation_scheme").operator int();
NavigationMode nav_mode = NAVIGATION_NONE;
if (_edit.gizmo.is_valid()) {
_edit.gizmo->set_handle(_edit.gizmo_handle, camera, m->get_position());
Variant v = _edit.gizmo->get_handle_value(_edit.gizmo_handle);
String n = _edit.gizmo->get_handle_name(_edit.gizmo_handle);
set_message(n + ": " + String(v));
} else if (m->get_button_mask() & BUTTON_MASK_LEFT) {
if (nav_scheme == NAVIGATION_MAYA && m->get_alt()) {
nav_mode = NAVIGATION_ORBIT;
} else if (nav_scheme == NAVIGATION_MODO && m->get_alt() && m->get_shift()) {
nav_mode = NAVIGATION_PAN;
} else if (nav_scheme == NAVIGATION_MODO && m->get_alt() && m->get_control()) {
nav_mode = NAVIGATION_ZOOM;
} else if (nav_scheme == NAVIGATION_MODO && m->get_alt()) {
nav_mode = NAVIGATION_ORBIT;
} else {
if (clicked) {
if (!clicked_includes_current) {
_select_clicked(clicked_wants_append, true);
// Processing was deferred.
}
_compute_edit(_edit.mouse_pos);
clicked = 0;
_edit.mode = TRANSFORM_TRANSLATE;
}
if (cursor.region_select) {
cursor.region_end = m->get_position();
surface->update();
return;
}
if (_edit.mode == TRANSFORM_NONE)
return;
Vector3 ray_pos = _get_ray_pos(m->get_position());
Vector3 ray = _get_ray(m->get_position());
float snap = EDITOR_GET("interface/inspector/default_float_step");
int snap_step_decimals = Math::range_step_decimals(snap);
switch (_edit.mode) {
case TRANSFORM_SCALE: {
Vector3 motion_mask;
Plane plane;
bool plane_mv = false;
switch (_edit.plane) {
case TRANSFORM_VIEW:
motion_mask = Vector3(0, 0, 0);
plane = Plane(_edit.center, _get_camera_normal());
break;
case TRANSFORM_X_AXIS:
motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(0);
plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized());
break;
case TRANSFORM_Y_AXIS:
motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(1);
plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized());
break;
case TRANSFORM_Z_AXIS:
motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(2);
plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized());
break;
case TRANSFORM_YZ:
motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(2) + spatial_editor->get_gizmo_transform().basis.get_axis(1);
plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(0));
plane_mv = true;
break;
case TRANSFORM_XZ:
motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(2) + spatial_editor->get_gizmo_transform().basis.get_axis(0);
plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(1));
plane_mv = true;
break;
case TRANSFORM_XY:
motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(0) + spatial_editor->get_gizmo_transform().basis.get_axis(1);
plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(2));
plane_mv = true;
break;
}
Vector3 intersection;
if (!plane.intersects_ray(ray_pos, ray, &intersection))
break;
Vector3 click;
if (!plane.intersects_ray(_edit.click_ray_pos, _edit.click_ray, &click))
break;
Vector3 motion = intersection - click;
if (_edit.plane != TRANSFORM_VIEW) {
if (!plane_mv) {
motion = motion_mask.dot(motion) * motion_mask;
} else {
// Alternative planar scaling mode
if (_get_key_modifier(m) != KEY_SHIFT) {
motion = motion_mask.dot(motion) * motion_mask;
}
}
} else {
float center_click_dist = click.distance_to(_edit.center);
float center_inters_dist = intersection.distance_to(_edit.center);
if (center_click_dist == 0)
break;
float scale = center_inters_dist - center_click_dist;
motion = Vector3(scale, scale, scale);
}
List<Node *> &selection = editor_selection->get_selected_node_list();
// Disable local transformation for TRANSFORM_VIEW
bool local_coords = (spatial_editor->are_local_coords_enabled() && _edit.plane != TRANSFORM_VIEW);
if (_edit.snap || spatial_editor->is_snap_enabled()) {
snap = spatial_editor->get_scale_snap() / 100;
}
Vector3 motion_snapped = motion;
motion_snapped.snap(Vector3(snap, snap, snap));
// This might not be necessary anymore after issue #288 is solved (in 4.0?).
set_message(TTR("Scaling: ") + "(" + String::num(motion_snapped.x, snap_step_decimals) + ", " +
String::num(motion_snapped.y, snap_step_decimals) + ", " + String::num(motion_snapped.z, snap_step_decimals) + ")");
for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
Spatial *sp = Object::cast_to<Spatial>(E->get());
if (!sp) {
continue;
}
SpatialEditorSelectedItem *se = editor_selection->get_node_editor_data<SpatialEditorSelectedItem>(sp);
if (!se) {
continue;
}
if (sp->has_meta("_edit_lock_")) {
continue;
}
Transform original = se->original;
Transform original_local = se->original_local;
Transform base = Transform(Basis(), _edit.center);
Transform t;
Vector3 local_scale;
if (local_coords) {
Basis g = original.basis.orthonormalized();
Vector3 local_motion = g.inverse().xform(motion);
if (_edit.snap || spatial_editor->is_snap_enabled()) {
local_motion.snap(Vector3(snap, snap, snap));
}
local_scale = original_local.basis.get_scale() * (local_motion + Vector3(1, 1, 1));
// Prevent scaling to 0 it would break the gizmo
Basis check = original_local.basis;
check.scale(local_scale);
if (check.determinant() != 0) {
// Apply scale
sp->set_scale(local_scale);
}
} else {
if (_edit.snap || spatial_editor->is_snap_enabled()) {
motion.snap(Vector3(snap, snap, snap));
}
Transform r;
r.basis.scale(motion + Vector3(1, 1, 1));
t = base * (r * (base.inverse() * original));
// Apply scale
sp->set_global_transform(t);
}
}
surface->update();
} break;
case TRANSFORM_TRANSLATE: {
Vector3 motion_mask;
Plane plane;
bool plane_mv = false;
switch (_edit.plane) {
case TRANSFORM_VIEW:
plane = Plane(_edit.center, _get_camera_normal());
break;
case TRANSFORM_X_AXIS:
motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(0);
plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized());
break;
case TRANSFORM_Y_AXIS:
motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(1);
plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized());
break;
case TRANSFORM_Z_AXIS:
motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(2);
plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized());
break;
case TRANSFORM_YZ:
plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(0));
plane_mv = true;
break;
case TRANSFORM_XZ:
plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(1));
plane_mv = true;
break;
case TRANSFORM_XY:
plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(2));
plane_mv = true;
break;
}
Vector3 intersection;
if (!plane.intersects_ray(ray_pos, ray, &intersection))
break;
Vector3 click;
if (!plane.intersects_ray(_edit.click_ray_pos, _edit.click_ray, &click))
break;
Vector3 motion = intersection - click;
if (_edit.plane != TRANSFORM_VIEW) {
if (!plane_mv) {
motion = motion_mask.dot(motion) * motion_mask;
}
}
List<Node *> &selection = editor_selection->get_selected_node_list();
// Disable local transformation for TRANSFORM_VIEW
bool local_coords = (spatial_editor->are_local_coords_enabled() && _edit.plane != TRANSFORM_VIEW);
if (_edit.snap || spatial_editor->is_snap_enabled()) {
snap = spatial_editor->get_translate_snap();
}
Vector3 motion_snapped = motion;
motion_snapped.snap(Vector3(snap, snap, snap));
set_message(TTR("Translating: ") + "(" + String::num(motion_snapped.x, snap_step_decimals) + ", " +
String::num(motion_snapped.y, snap_step_decimals) + ", " + String::num(motion_snapped.z, snap_step_decimals) + ")");
for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
Spatial *sp = Object::cast_to<Spatial>(E->get());
if (!sp) {
continue;
}
SpatialEditorSelectedItem *se = editor_selection->get_node_editor_data<SpatialEditorSelectedItem>(sp);
if (!se) {
continue;
}
if (sp->has_meta("_edit_lock_")) {
continue;
}
Transform original = se->original;
Transform t;
if (local_coords) {
if (_edit.snap || spatial_editor->is_snap_enabled()) {
Basis g = original.basis.orthonormalized();
Vector3 local_motion = g.inverse().xform(motion);
local_motion.snap(Vector3(snap, snap, snap));
motion = g.xform(local_motion);
}
} else {
if (_edit.snap || spatial_editor->is_snap_enabled()) {
motion.snap(Vector3(snap, snap, snap));
}
}
// Apply translation
t = original;
t.origin += motion;
sp->set_global_transform(t);
}
surface->update();
} break;
case TRANSFORM_ROTATE: {
Plane plane;
Vector3 axis;
switch (_edit.plane) {
case TRANSFORM_VIEW:
plane = Plane(_edit.center, _get_camera_normal());
break;
case TRANSFORM_X_AXIS:
plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(0));
axis = Vector3(1, 0, 0);
break;
case TRANSFORM_Y_AXIS:
plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(1));
axis = Vector3(0, 1, 0);
break;
case TRANSFORM_Z_AXIS:
plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(2));
axis = Vector3(0, 0, 1);
break;
case TRANSFORM_YZ:
case TRANSFORM_XZ:
case TRANSFORM_XY:
break;
}
Vector3 intersection;
if (!plane.intersects_ray(ray_pos, ray, &intersection))
break;
Vector3 click;
if (!plane.intersects_ray(_edit.click_ray_pos, _edit.click_ray, &click))
break;
Vector3 y_axis = (click - _edit.center).normalized();
Vector3 x_axis = plane.normal.cross(y_axis).normalized();
float angle = Math::atan2(x_axis.dot(intersection - _edit.center), y_axis.dot(intersection - _edit.center));
if (_edit.snap || spatial_editor->is_snap_enabled()) {
snap = spatial_editor->get_rotate_snap();
}
angle = Math::rad2deg(angle) + snap * 0.5; //else it won't reach +180
angle -= Math::fmod(angle, snap);
set_message(vformat(TTR("Rotating %s degrees."), String::num(angle, snap_step_decimals)));
angle = Math::deg2rad(angle);
List<Node *> &selection = editor_selection->get_selected_node_list();
bool local_coords = (spatial_editor->are_local_coords_enabled() && _edit.plane != TRANSFORM_VIEW); // Disable local transformation for TRANSFORM_VIEW
for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
Spatial *sp = Object::cast_to<Spatial>(E->get());
if (!sp)
continue;
SpatialEditorSelectedItem *se = editor_selection->get_node_editor_data<SpatialEditorSelectedItem>(sp);
if (!se)
continue;
if (sp->has_meta("_edit_lock_")) {
continue;
}
Transform t;
if (local_coords) {
Transform original_local = se->original_local;
Basis rot = Basis(axis, angle);
t.basis = original_local.get_basis().orthonormalized() * rot;
t.origin = original_local.origin;
// Apply rotation
sp->set_transform(t);
sp->set_scale(original_local.basis.get_scale()); // re-apply original scale
} else {
Transform original = se->original;
Transform r;
Transform base = Transform(Basis(), _edit.center);
r.basis.rotate(plane.normal, angle);
t = base * r * base.inverse() * original;
// Apply rotation
sp->set_global_transform(t);
}
}
surface->update();
} break;
default: {
}
}
}
} else if ((m->get_button_mask() & BUTTON_MASK_RIGHT) || freelook_active) {
if (nav_scheme == NAVIGATION_MAYA && m->get_alt()) {
nav_mode = NAVIGATION_ZOOM;
} else if (freelook_active) {
nav_mode = NAVIGATION_LOOK;
} else if (orthogonal) {
nav_mode = NAVIGATION_PAN;
}
} else if (m->get_button_mask() & BUTTON_MASK_MIDDLE) {
if (nav_scheme == NAVIGATION_GODOT) {
const int mod = _get_key_modifier(m);
if (mod == _get_key_modifier_setting("editors/3d/navigation/pan_modifier")) {
nav_mode = NAVIGATION_PAN;
} else if (mod == _get_key_modifier_setting("editors/3d/navigation/zoom_modifier")) {
nav_mode = NAVIGATION_ZOOM;
} else if (mod == KEY_ALT || mod == _get_key_modifier_setting("editors/3d/navigation/orbit_modifier")) {
// Always allow Alt as a modifier to better support graphic tablets.
nav_mode = NAVIGATION_ORBIT;
}
} else if (nav_scheme == NAVIGATION_MAYA) {
if (m->get_alt())
nav_mode = NAVIGATION_PAN;
}
} else if (EditorSettings::get_singleton()->get("editors/3d/navigation/emulate_3_button_mouse")) {
// Handle trackpad (no external mouse) use case
const int mod = _get_key_modifier(m);
if (mod) {
if (mod == _get_key_modifier_setting("editors/3d/navigation/pan_modifier")) {
nav_mode = NAVIGATION_PAN;
} else if (mod == _get_key_modifier_setting("editors/3d/navigation/zoom_modifier")) {
nav_mode = NAVIGATION_ZOOM;
} else if (mod == KEY_ALT || mod == _get_key_modifier_setting("editors/3d/navigation/orbit_modifier")) {
// Always allow Alt as a modifier to better support graphic tablets.
nav_mode = NAVIGATION_ORBIT;
}
}
}
switch (nav_mode) {
case NAVIGATION_PAN: {
_nav_pan(m, _get_warped_mouse_motion(m));
} break;
case NAVIGATION_ZOOM: {
_nav_zoom(m, m->get_relative());
} break;
case NAVIGATION_ORBIT: {
_nav_orbit(m, _get_warped_mouse_motion(m));
} break;
case NAVIGATION_LOOK: {
_nav_look(m, _get_warped_mouse_motion(m));
} break;
default: {
}
}
}
Ref<InputEventMagnifyGesture> magnify_gesture = p_event;
if (magnify_gesture.is_valid()) {
if (is_freelook_active())
scale_freelook_speed(magnify_gesture->get_factor());
else
scale_cursor_distance(1.0 / magnify_gesture->get_factor());
}
Ref<InputEventPanGesture> pan_gesture = p_event;
if (pan_gesture.is_valid()) {
NavigationScheme nav_scheme = (NavigationScheme)EditorSettings::get_singleton()->get("editors/3d/navigation/navigation_scheme").operator int();
NavigationMode nav_mode = NAVIGATION_NONE;
if (nav_scheme == NAVIGATION_GODOT) {
const int mod = _get_key_modifier(pan_gesture);
if (mod == _get_key_modifier_setting("editors/3d/navigation/pan_modifier")) {
nav_mode = NAVIGATION_PAN;
} else if (mod == _get_key_modifier_setting("editors/3d/navigation/zoom_modifier")) {
nav_mode = NAVIGATION_ZOOM;
} else if (mod == KEY_ALT || mod == _get_key_modifier_setting("editors/3d/navigation/orbit_modifier")) {
// Always allow Alt as a modifier to better support graphic tablets.
nav_mode = NAVIGATION_ORBIT;
}
} else if (nav_scheme == NAVIGATION_MAYA) {
if (pan_gesture->get_alt())
nav_mode = NAVIGATION_PAN;
}
switch (nav_mode) {
case NAVIGATION_PAN: {
_nav_pan(m, pan_gesture->get_delta());
} break;
case NAVIGATION_ZOOM: {
_nav_zoom(m, pan_gesture->get_delta());
} break;
case NAVIGATION_ORBIT: {
_nav_orbit(m, pan_gesture->get_delta());
} break;
case NAVIGATION_LOOK: {
_nav_look(m, pan_gesture->get_delta());
} break;
default: {
}
}
}
Ref<InputEventKey> k = p_event;
if (k.is_valid()) {
if (!k->is_pressed())
return;
if (ED_IS_SHORTCUT("spatial_editor/snap", p_event)) {
if (_edit.mode != TRANSFORM_NONE) {
_edit.snap = !_edit.snap;
}
}
if (ED_IS_SHORTCUT("spatial_editor/bottom_view", p_event)) {
_menu_option(VIEW_BOTTOM);
}
if (ED_IS_SHORTCUT("spatial_editor/top_view", p_event)) {
_menu_option(VIEW_TOP);
}
if (ED_IS_SHORTCUT("spatial_editor/rear_view", p_event)) {
_menu_option(VIEW_REAR);
}
if (ED_IS_SHORTCUT("spatial_editor/front_view", p_event)) {
_menu_option(VIEW_FRONT);
}
if (ED_IS_SHORTCUT("spatial_editor/left_view", p_event)) {
_menu_option(VIEW_LEFT);
}
if (ED_IS_SHORTCUT("spatial_editor/right_view", p_event)) {
_menu_option(VIEW_RIGHT);
}
if (ED_IS_SHORTCUT("spatial_editor/focus_origin", p_event)) {
_menu_option(VIEW_CENTER_TO_ORIGIN);
}
if (ED_IS_SHORTCUT("spatial_editor/focus_selection", p_event)) {
_menu_option(VIEW_CENTER_TO_SELECTION);
}
// Orthgonal mode doesn't work in freelook.
if (!freelook_active && ED_IS_SHORTCUT("spatial_editor/switch_perspective_orthogonal", p_event)) {
_menu_option(orthogonal ? VIEW_PERSPECTIVE : VIEW_ORTHOGONAL);
}
if (ED_IS_SHORTCUT("spatial_editor/align_transform_with_view", p_event)) {
_menu_option(VIEW_ALIGN_TRANSFORM_WITH_VIEW);
}
if (ED_IS_SHORTCUT("spatial_editor/align_rotation_with_view", p_event)) {
_menu_option(VIEW_ALIGN_ROTATION_WITH_VIEW);
}
if (ED_IS_SHORTCUT("spatial_editor/insert_anim_key", p_event)) {
if (!get_selected_count() || _edit.mode != TRANSFORM_NONE)
return;
if (!AnimationPlayerEditor::singleton->get_track_editor()->has_keying()) {
set_message(TTR("Keying is disabled (no key inserted)."));
return;
}
List<Node *> &selection = editor_selection->get_selected_node_list();
for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
Spatial *sp = Object::cast_to<Spatial>(E->get());
if (!sp)
continue;
spatial_editor->emit_signal("transform_key_request", sp, "", sp->get_transform());
}
set_message(TTR("Animation Key Inserted."));
}
// Freelook doesn't work in orthogonal mode.
if (!orthogonal && ED_IS_SHORTCUT("spatial_editor/freelook_toggle", p_event)) {
set_freelook_active(!is_freelook_active());
} else if (k->get_scancode() == KEY_ESCAPE) {
set_freelook_active(false);
}
if (k->get_scancode() == KEY_SPACE) {
if (!k->is_pressed()) emit_signal("toggle_maximize_view", this);
}
}
// freelook uses most of the useful shortcuts, like save, so its ok
// to consider freelook active as end of the line for future events.
if (freelook_active)
accept_event();
}
void SpatialEditorViewport::_nav_pan(Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative) {
const NavigationScheme nav_scheme = (NavigationScheme)EditorSettings::get_singleton()->get("editors/3d/navigation/navigation_scheme").operator int();
real_t pan_speed = 1 / 150.0;
int pan_speed_modifier = 10;
if (nav_scheme == NAVIGATION_MAYA && p_event->get_shift())
pan_speed *= pan_speed_modifier;
Transform camera_transform;
camera_transform.translate(cursor.pos);
camera_transform.basis.rotate(Vector3(1, 0, 0), -cursor.x_rot);
camera_transform.basis.rotate(Vector3(0, 1, 0), -cursor.y_rot);
Vector3 translation(-p_relative.x * pan_speed, p_relative.y * pan_speed, 0);
translation *= cursor.distance / DISTANCE_DEFAULT;
camera_transform.translate(translation);
cursor.pos = camera_transform.origin;
}
void SpatialEditorViewport::_nav_zoom(Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative) {
const NavigationScheme nav_scheme = (NavigationScheme)EditorSettings::get_singleton()->get("editors/3d/navigation/navigation_scheme").operator int();
real_t zoom_speed = 1 / 80.0;
int zoom_speed_modifier = 10;
if (nav_scheme == NAVIGATION_MAYA && p_event->get_shift())
zoom_speed *= zoom_speed_modifier;
NavigationZoomStyle zoom_style = (NavigationZoomStyle)EditorSettings::get_singleton()->get("editors/3d/navigation/zoom_style").operator int();
if (zoom_style == NAVIGATION_ZOOM_HORIZONTAL) {
if (p_relative.x > 0)
scale_cursor_distance(1 - p_relative.x * zoom_speed);
else if (p_relative.x < 0)
scale_cursor_distance(1.0 / (1 + p_relative.x * zoom_speed));
} else {
if (p_relative.y > 0)
scale_cursor_distance(1 + p_relative.y * zoom_speed);
else if (p_relative.y < 0)
scale_cursor_distance(1.0 / (1 - p_relative.y * zoom_speed));
}
}
void SpatialEditorViewport::_nav_orbit(Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative) {
if (lock_rotation) {
_nav_pan(p_event, p_relative);
return;
}
if (orthogonal && auto_orthogonal) {
_menu_option(VIEW_PERSPECTIVE);
}
real_t degrees_per_pixel = EditorSettings::get_singleton()->get("editors/3d/navigation_feel/orbit_sensitivity");
real_t radians_per_pixel = Math::deg2rad(degrees_per_pixel);
bool invert_y_axis = EditorSettings::get_singleton()->get("editors/3d/navigation/invert_y_axis");
if (invert_y_axis) {
cursor.x_rot -= p_relative.y * radians_per_pixel;
} else {
cursor.x_rot += p_relative.y * radians_per_pixel;
}
cursor.y_rot += p_relative.x * radians_per_pixel;
if (cursor.x_rot > Math_PI / 2.0)
cursor.x_rot = Math_PI / 2.0;
if (cursor.x_rot < -Math_PI / 2.0)
cursor.x_rot = -Math_PI / 2.0;
name = "";
_update_name();
}
void SpatialEditorViewport::_nav_look(Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative) {
if (orthogonal) {
_nav_pan(p_event, p_relative);
return;
}
if (orthogonal && auto_orthogonal) {
_menu_option(VIEW_PERSPECTIVE);
}
real_t degrees_per_pixel = EditorSettings::get_singleton()->get("editors/3d/navigation_feel/orbit_sensitivity");
real_t radians_per_pixel = Math::deg2rad(degrees_per_pixel);
bool invert_y_axis = EditorSettings::get_singleton()->get("editors/3d/navigation/invert_y_axis");
// Note: do NOT assume the camera has the "current" transform, because it is interpolated and may have "lag".
Transform prev_camera_transform = to_camera_transform(cursor);
if (invert_y_axis) {
cursor.x_rot -= p_relative.y * radians_per_pixel;
} else {
cursor.x_rot += p_relative.y * radians_per_pixel;
}
cursor.y_rot += p_relative.x * radians_per_pixel;
if (cursor.x_rot > Math_PI / 2.0)
cursor.x_rot = Math_PI / 2.0;
if (cursor.x_rot < -Math_PI / 2.0)
cursor.x_rot = -Math_PI / 2.0;
// Look is like the opposite of Orbit: the focus point rotates around the camera
Transform camera_transform = to_camera_transform(cursor);
Vector3 pos = camera_transform.xform(Vector3(0, 0, 0));
Vector3 prev_pos = prev_camera_transform.xform(Vector3(0, 0, 0));
Vector3 diff = prev_pos - pos;
cursor.pos += diff;
name = "";
_update_name();
}
void SpatialEditorViewport::set_freelook_active(bool active_now) {
if (!freelook_active && active_now) {
// Sync camera cursor to cursor to "cut" interpolation jumps due to changing referential
cursor = camera_cursor;
// Make sure eye_pos is synced, because freelook referential is eye pos rather than orbit pos
Vector3 forward = to_camera_transform(cursor).basis.xform(Vector3(0, 0, -1));
cursor.eye_pos = cursor.pos - cursor.distance * forward;
// Also sync the camera cursor, otherwise switching to freelook will be trippy if inertia is active
camera_cursor.eye_pos = cursor.eye_pos;
if (EditorSettings::get_singleton()->get("editors/3d/freelook/freelook_speed_zoom_link")) {
// Re-adjust freelook speed from the current zoom level
real_t base_speed = EditorSettings::get_singleton()->get("editors/3d/freelook/freelook_base_speed");
freelook_speed = base_speed * cursor.distance;
}
// Hide mouse like in an FPS (warping doesn't work)
Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_CAPTURED);
} else if (freelook_active && !active_now) {
// Sync camera cursor to cursor to "cut" interpolation jumps due to changing referential
cursor = camera_cursor;
// Restore mouse
Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE);
}
freelook_active = active_now;
}
void SpatialEditorViewport::scale_cursor_distance(real_t scale) {
// Prevents zero distance which would short-circuit any scaling
if (cursor.distance < ZOOM_MIN_DISTANCE)
cursor.distance = ZOOM_MIN_DISTANCE;
cursor.distance *= scale;
if (cursor.distance < ZOOM_MIN_DISTANCE)
cursor.distance = ZOOM_MIN_DISTANCE;
zoom_indicator_delay = ZOOM_INDICATOR_DELAY_S;
surface->update();
}
void SpatialEditorViewport::scale_freelook_speed(real_t scale) {
// Prevents zero distance which would short-circuit any scaling
if (freelook_speed < FREELOOK_MIN_SPEED)
freelook_speed = FREELOOK_MIN_SPEED;
freelook_speed *= scale;
if (freelook_speed < FREELOOK_MIN_SPEED)
freelook_speed = FREELOOK_MIN_SPEED;
zoom_indicator_delay = ZOOM_INDICATOR_DELAY_S;
surface->update();
}
Point2i SpatialEditorViewport::_get_warped_mouse_motion(const Ref<InputEventMouseMotion> &p_ev_mouse_motion) const {
Point2i relative;
if (bool(EDITOR_DEF("editors/3d/navigation/warped_mouse_panning", false))) {
relative = Input::get_singleton()->warp_mouse_motion(p_ev_mouse_motion, surface->get_global_rect());
} else {
relative = p_ev_mouse_motion->get_relative();
}
return relative;
}
static bool is_shortcut_pressed(const String &p_path) {
Ref<ShortCut> shortcut = ED_GET_SHORTCUT(p_path);
if (shortcut.is_null()) {
return false;
}
InputEventKey *k = Object::cast_to<InputEventKey>(shortcut->get_shortcut().ptr());
if (k == NULL) {
return false;
}
const Input &input = *Input::get_singleton();
int scancode = k->get_scancode();
return input.is_key_pressed(scancode);
}
void SpatialEditorViewport::_update_freelook(real_t delta) {
if (!is_freelook_active()) {
return;
}
const FreelookNavigationScheme navigation_scheme = (FreelookNavigationScheme)EditorSettings::get_singleton()->get("editors/3d/freelook/freelook_navigation_scheme").operator int();
Vector3 forward;
if (navigation_scheme == FREELOOK_FULLY_AXIS_LOCKED) {
// Forward/backward keys will always go straight forward/backward, never moving on the Y axis.
forward = Vector3(0, 0, -1).rotated(Vector3(0, 1, 0), camera->get_rotation().y);
} else {
// Forward/backward keys will be relative to the camera pitch.
forward = camera->get_transform().basis.xform(Vector3(0, 0, -1));
}
const Vector3 right = camera->get_transform().basis.xform(Vector3(1, 0, 0));
Vector3 up;
if (navigation_scheme == FREELOOK_PARTIALLY_AXIS_LOCKED || navigation_scheme == FREELOOK_FULLY_AXIS_LOCKED) {
// Up/down keys will always go up/down regardless of camera pitch.
up = Vector3(0, 1, 0);
} else {
// Up/down keys will be relative to the camera pitch.
up = camera->get_transform().basis.xform(Vector3(0, 1, 0));
}
Vector3 direction;
if (is_shortcut_pressed("spatial_editor/freelook_left")) {
direction -= right;
}
if (is_shortcut_pressed("spatial_editor/freelook_right")) {
direction += right;
}
if (is_shortcut_pressed("spatial_editor/freelook_forward")) {
direction += forward;
}
if (is_shortcut_pressed("spatial_editor/freelook_backwards")) {
direction -= forward;
}
if (is_shortcut_pressed("spatial_editor/freelook_up")) {
direction += up;
}
if (is_shortcut_pressed("spatial_editor/freelook_down")) {
direction -= up;
}
real_t speed = freelook_speed;
if (is_shortcut_pressed("spatial_editor/freelook_speed_modifier")) {
speed *= 3.0;
}
if (is_shortcut_pressed("spatial_editor/freelook_slow_modifier")) {
speed *= 0.333333;
}
const Vector3 motion = direction * speed * delta;
cursor.pos += motion;
cursor.eye_pos += motion;
}
void SpatialEditorViewport::set_message(String p_message, float p_time) {
message = p_message;
message_time = p_time;
}
void SpatialEditorPlugin::edited_scene_changed() {
for (uint32_t i = 0; i < SpatialEditor::VIEWPORTS_COUNT; i++) {
SpatialEditorViewport *viewport = SpatialEditor::get_singleton()->get_editor_viewport(i);
if (viewport->is_visible()) {
viewport->notification(Control::NOTIFICATION_VISIBILITY_CHANGED);
}
}
}
void SpatialEditorViewport::_notification(int p_what) {
if (p_what == NOTIFICATION_VISIBILITY_CHANGED) {
bool visible = is_visible_in_tree();
set_process(visible);
if (visible) {
orthogonal = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_ORTHOGONAL));
_update_name();
_update_camera(0);
} else {
set_freelook_active(false);
}
call_deferred("update_transform_gizmo_view");
rotation_control->set_visible(EditorSettings::get_singleton()->get("editors/3d/navigation/show_viewport_rotation_gizmo"));
}
if (p_what == NOTIFICATION_RESIZED) {
call_deferred("update_transform_gizmo_view");
}
if (p_what == NOTIFICATION_READY) {
// The crosshair icon doesn't depend on the editor theme.
crosshair->set_texture(get_icon("Crosshair", "EditorIcons"));
// Set the anchors and margins after changing the icon to ensure it's centered correctly.
crosshair->set_anchors_and_margins_preset(PRESET_CENTER);
}
if (p_what == NOTIFICATION_PROCESS) {
real_t delta = get_process_delta_time();
if (zoom_indicator_delay > 0) {
zoom_indicator_delay -= delta;
if (zoom_indicator_delay <= 0) {
surface->update();
}
}
_update_freelook(delta);
Node *scene_root = editor->get_scene_tree_dock()->get_editor_data()->get_edited_scene_root();
if (previewing_cinema && scene_root != NULL) {
Camera *cam = scene_root->get_viewport()->get_camera();
if (cam != NULL && cam != previewing) {
//then switch the viewport's camera to the scene's viewport camera
if (previewing != NULL) {
previewing->disconnect("tree_exited", this, "_preview_exited_scene");
}
previewing = cam;
previewing->connect("tree_exited", this, "_preview_exited_scene");
VS::get_singleton()->viewport_attach_camera(viewport->get_viewport_rid(), cam->get_camera());
surface->update();
}
}
_update_camera(delta);
Map<Node *, Object *> &selection = editor_selection->get_selection();
bool changed = false;
bool exist = false;
for (Map<Node *, Object *>::Element *E = selection.front(); E; E = E->next()) {
Spatial *sp = Object::cast_to<Spatial>(E->key());
if (!sp)
continue;
SpatialEditorSelectedItem *se = editor_selection->get_node_editor_data<SpatialEditorSelectedItem>(sp);
if (!se)
continue;
Transform t = sp->get_global_gizmo_transform();
exist = true;
if (se->last_xform == t && !se->last_xform_dirty)
continue;
changed = true;
se->last_xform_dirty = false;
se->last_xform = t;
VisualInstance *vi = Object::cast_to<VisualInstance>(sp);
se->aabb = vi ? vi->get_aabb() : _calculate_spatial_bounds(sp);
t.translate(se->aabb.position);
// apply AABB scaling before item's global transform
Basis aabb_s;
aabb_s.scale(se->aabb.size);
t.basis = t.basis * aabb_s;
VisualServer::get_singleton()->instance_set_transform(se->sbox_instance, t);
}
if (changed || (spatial_editor->is_gizmo_visible() && !exist)) {
spatial_editor->update_transform_gizmo();
}
if (message_time > 0) {
if (message != last_message) {
surface->update();
last_message = message;
}
message_time -= get_physics_process_delta_time();
if (message_time < 0)
surface->update();
}
//update shadow atlas if changed
int shadowmap_size = ProjectSettings::get_singleton()->get("rendering/quality/shadow_atlas/size");
int atlas_q0 = ProjectSettings::get_singleton()->get("rendering/quality/shadow_atlas/quadrant_0_subdiv");
int atlas_q1 = ProjectSettings::get_singleton()->get("rendering/quality/shadow_atlas/quadrant_1_subdiv");
int atlas_q2 = ProjectSettings::get_singleton()->get("rendering/quality/shadow_atlas/quadrant_2_subdiv");
int atlas_q3 = ProjectSettings::get_singleton()->get("rendering/quality/shadow_atlas/quadrant_3_subdiv");
viewport->set_shadow_atlas_size(shadowmap_size);
viewport->set_shadow_atlas_quadrant_subdiv(0, Viewport::ShadowAtlasQuadrantSubdiv(atlas_q0));
viewport->set_shadow_atlas_quadrant_subdiv(1, Viewport::ShadowAtlasQuadrantSubdiv(atlas_q1));
viewport->set_shadow_atlas_quadrant_subdiv(2, Viewport::ShadowAtlasQuadrantSubdiv(atlas_q2));
viewport->set_shadow_atlas_quadrant_subdiv(3, Viewport::ShadowAtlasQuadrantSubdiv(atlas_q3));
bool shrink = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_HALF_RESOLUTION));
if (shrink != (viewport_container->get_stretch_shrink() > 1)) {
viewport_container->set_stretch_shrink(shrink ? 2 : 1);
}
//update msaa if changed
int msaa_mode = ProjectSettings::get_singleton()->get("rendering/quality/filters/msaa");
viewport->set_msaa(Viewport::MSAA(msaa_mode));
bool hdr = ProjectSettings::get_singleton()->get("rendering/quality/depth/hdr");
viewport->set_hdr(hdr);
bool show_info = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_INFORMATION));
info_label->set_visible(show_info);
Camera *current_camera;
if (previewing) {
current_camera = previewing;
} else {
current_camera = camera;
}
// Display the crosshair only while freelooking. Hide it otherwise,
// as the crosshair can be distracting.
crosshair->set_visible(freelook_active);
if (show_info) {
String text;
text += "X: " + rtos(current_camera->get_translation().x).pad_decimals(1) + "\n";
text += "Y: " + rtos(current_camera->get_translation().y).pad_decimals(1) + "\n";
text += "Z: " + rtos(current_camera->get_translation().z).pad_decimals(1) + "\n";
text += TTR("Pitch") + ": " + itos(Math::round(current_camera->get_rotation_degrees().x)) + "\n";
text += TTR("Yaw") + ": " + itos(Math::round(current_camera->get_rotation_degrees().y)) + "\n\n";
text += TTR("Objects Drawn") + ": " + itos(viewport->get_render_info(Viewport::RENDER_INFO_OBJECTS_IN_FRAME)) + "\n";
text += TTR("Material Changes") + ": " + itos(viewport->get_render_info(Viewport::RENDER_INFO_MATERIAL_CHANGES_IN_FRAME)) + "\n";
text += TTR("Shader Changes") + ": " + itos(viewport->get_render_info(Viewport::RENDER_INFO_SHADER_CHANGES_IN_FRAME)) + "\n";
text += TTR("Surface Changes") + ": " + itos(viewport->get_render_info(Viewport::RENDER_INFO_SURFACE_CHANGES_IN_FRAME)) + "\n";
text += TTR("Draw Calls") + ": " + itos(viewport->get_render_info(Viewport::RENDER_INFO_DRAW_CALLS_IN_FRAME)) + "\n";
text += TTR("Vertices") + ": " + itos(viewport->get_render_info(Viewport::RENDER_INFO_VERTICES_IN_FRAME));
info_label->set_text(text);
}
// FPS Counter.
bool show_fps = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_FPS));
fps_label->set_visible(show_fps);
if (show_fps) {
String text;
const float temp_fps = Engine::get_singleton()->get_frames_per_second();
text += TTR(vformat("FPS: %d (%s ms)", temp_fps, String::num(1000.0f / temp_fps, 2)));
fps_label->set_text(text);
}
bool show_cinema = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_CINEMATIC_PREVIEW));
cinema_label->set_visible(show_cinema);
if (show_cinema) {
float cinema_half_width = cinema_label->get_size().width / 2.0f;
cinema_label->set_anchor_and_margin(MARGIN_LEFT, 0.5f, -cinema_half_width);
}
if (lock_rotation) {
float locked_half_width = locked_label->get_size().width / 2.0f;
locked_label->set_anchor_and_margin(MARGIN_LEFT, 0.5f, -locked_half_width);
}
}
if (p_what == NOTIFICATION_ENTER_TREE) {
surface->connect("draw", this, "_draw");
surface->connect("gui_input", this, "_sinput");
surface->connect("mouse_entered", this, "_surface_mouse_enter");
surface->connect("mouse_exited", this, "_surface_mouse_exit");
surface->connect("focus_entered", this, "_surface_focus_enter");
surface->connect("focus_exited", this, "_surface_focus_exit");
_init_gizmo_instance(index);
}
if (p_what == NOTIFICATION_EXIT_TREE) {
_finish_gizmo_instances();
}
if (p_what == NOTIFICATION_THEME_CHANGED) {
view_menu->set_icon(get_icon("GuiTabMenu", "EditorIcons"));
preview_camera->set_icon(get_icon("Camera", "EditorIcons"));
view_menu->add_style_override("normal", editor->get_gui_base()->get_stylebox("Information3dViewport", "EditorStyles"));
view_menu->add_style_override("hover", editor->get_gui_base()->get_stylebox("Information3dViewport", "EditorStyles"));
view_menu->add_style_override("pressed", editor->get_gui_base()->get_stylebox("Information3dViewport", "EditorStyles"));
view_menu->add_style_override("focus", editor->get_gui_base()->get_stylebox("Information3dViewport", "EditorStyles"));
view_menu->add_style_override("disabled", editor->get_gui_base()->get_stylebox("Information3dViewport", "EditorStyles"));
preview_camera->add_style_override("normal", editor->get_gui_base()->get_stylebox("Information3dViewport", "EditorStyles"));
preview_camera->add_style_override("hover", editor->get_gui_base()->get_stylebox("Information3dViewport", "EditorStyles"));
preview_camera->add_style_override("pressed", editor->get_gui_base()->get_stylebox("Information3dViewport", "EditorStyles"));
preview_camera->add_style_override("focus", editor->get_gui_base()->get_stylebox("Information3dViewport", "EditorStyles"));
preview_camera->add_style_override("disabled", editor->get_gui_base()->get_stylebox("Information3dViewport", "EditorStyles"));
info_label->add_style_override("normal", editor->get_gui_base()->get_stylebox("Information3dViewport", "EditorStyles"));
fps_label->add_style_override("normal", editor->get_gui_base()->get_stylebox("Information3dViewport", "EditorStyles"));
cinema_label->add_style_override("normal", editor->get_gui_base()->get_stylebox("Information3dViewport", "EditorStyles"));
locked_label->add_style_override("normal", editor->get_gui_base()->get_stylebox("Information3dViewport", "EditorStyles"));
}
}
static void draw_indicator_bar(Control &surface, real_t fill, Ref<Texture> icon) {
// Adjust bar size from control height
Vector2 surface_size = surface.get_size();
real_t h = surface_size.y / 2.0;
real_t y = (surface_size.y - h) / 2.0;
Rect2 r(10, y, 6, h);
real_t sy = r.size.y * fill;
// Note: because this bar appears over the viewport, it has to stay readable for any background color
// Draw both neutral dark and bright colors to account this
surface.draw_rect(r, Color(1, 1, 1, 0.2));
surface.draw_rect(Rect2(r.position.x, r.position.y + r.size.y - sy, r.size.x, sy), Color(1, 1, 1, 0.6));
surface.draw_rect(r.grow(1), Color(0, 0, 0, 0.7), false, Math::round(EDSCALE));
Vector2 icon_size = icon->get_size();
Vector2 icon_pos = Vector2(r.position.x - (icon_size.x - r.size.x) / 2, r.position.y + r.size.y + 2);
surface.draw_texture(icon, icon_pos);
}
void SpatialEditorViewport::_draw() {
EditorPluginList *over_plugin_list = EditorNode::get_singleton()->get_editor_plugins_over();
if (!over_plugin_list->empty()) {
over_plugin_list->forward_spatial_draw_over_viewport(surface);
}
EditorPluginList *force_over_plugin_list = editor->get_editor_plugins_force_over();
if (!force_over_plugin_list->empty()) {
force_over_plugin_list->forward_spatial_force_draw_over_viewport(surface);
}
if (surface->has_focus()) {
Size2 size = surface->get_size();
Rect2 r = Rect2(Point2(), size);
get_stylebox("Focus", "EditorStyles")->draw(surface->get_canvas_item(), r);
}
if (cursor.region_select) {
const Rect2 selection_rect = Rect2(cursor.region_begin, cursor.region_end - cursor.region_begin);
surface->draw_rect(
selection_rect,
get_color("box_selection_fill_color", "Editor"));
surface->draw_rect(
selection_rect,
get_color("box_selection_stroke_color", "Editor"),
false,
Math::round(EDSCALE));
}
RID ci = surface->get_canvas_item();
if (message_time > 0) {
Ref<Font> font = get_font("font", "Label");
Point2 msgpos = Point2(5, get_size().y - 20);
font->draw(ci, msgpos + Point2(1, 1), message, Color(0, 0, 0, 0.8));
font->draw(ci, msgpos + Point2(-1, -1), message, Color(0, 0, 0, 0.8));
font->draw(ci, msgpos, message, Color(1, 1, 1, 1));
}
if (_edit.mode == TRANSFORM_ROTATE) {
Point2 center = _point_to_screen(_edit.center);
VisualServer::get_singleton()->canvas_item_add_line(
ci,
_edit.mouse_pos,
center,
get_color("accent_color", "Editor") * Color(1, 1, 1, 0.6),
Math::round(2 * EDSCALE),
true);
}
if (previewing) {
Size2 ss = Size2(ProjectSettings::get_singleton()->get("display/window/size/width"), ProjectSettings::get_singleton()->get("display/window/size/height"));
float aspect = ss.aspect();
Size2 s = get_size();
Rect2 draw_rect;
switch (previewing->get_keep_aspect_mode()) {
case Camera::KEEP_WIDTH: {
draw_rect.size = Size2(s.width, s.width / aspect);
draw_rect.position.x = 0;
draw_rect.position.y = (s.height - draw_rect.size.y) * 0.5;
} break;
case Camera::KEEP_HEIGHT: {
draw_rect.size = Size2(s.height * aspect, s.height);
draw_rect.position.y = 0;
draw_rect.position.x = (s.width - draw_rect.size.x) * 0.5;
} break;
}
draw_rect = Rect2(Vector2(), s).clip(draw_rect);
surface->draw_rect(draw_rect, Color(0.6, 0.6, 0.1, 0.5), false, Math::round(2 * EDSCALE));
} else {
if (zoom_indicator_delay > 0.0) {
if (is_freelook_active()) {
// Show speed
real_t min_speed = FREELOOK_MIN_SPEED;
real_t max_speed = camera->get_zfar();
real_t scale_length = (max_speed - min_speed);
if (!Math::is_zero_approx(scale_length)) {
real_t logscale_t = 1.0 - Math::log(1 + freelook_speed - min_speed) / Math::log(1 + scale_length);
// There is no real maximum speed so that factor can become negative,
// Let's make it look asymptotic instead (will decrease slower and slower).
if (logscale_t < 0.25)
logscale_t = 0.25 * Math::exp(4.0 * logscale_t - 1.0);
draw_indicator_bar(*surface, 1.0 - logscale_t, get_icon("ViewportSpeed", "EditorIcons"));
}
} else {
// Show zoom
real_t min_distance = ZOOM_MIN_DISTANCE; // TODO Why not pick znear to limit zoom?
real_t max_distance = camera->get_zfar();
real_t scale_length = (max_distance - min_distance);
if (!Math::is_zero_approx(scale_length)) {
real_t logscale_t = 1.0 - Math::log(1 + cursor.distance - min_distance) / Math::log(1 + scale_length);
// There is no real maximum distance so that factor can become negative,
// Let's make it look asymptotic instead (will decrease slower and slower).
if (logscale_t < 0.25)
logscale_t = 0.25 * Math::exp(4.0 * logscale_t - 1.0);
draw_indicator_bar(*surface, logscale_t, get_icon("ViewportZoom", "EditorIcons"));
}
}
}
}
}
void SpatialEditorViewport::_menu_option(int p_option) {
switch (p_option) {
case VIEW_TOP: {
cursor.y_rot = 0;
cursor.x_rot = Math_PI / 2.0;
set_message(TTR("Top View."), 2);
name = TTR("Top");
_set_auto_orthogonal();
_update_name();
} break;
case VIEW_BOTTOM: {
cursor.y_rot = 0;
cursor.x_rot = -Math_PI / 2.0;
set_message(TTR("Bottom View."), 2);
name = TTR("Bottom");
_set_auto_orthogonal();
_update_name();
} break;
case VIEW_LEFT: {
cursor.x_rot = 0;
cursor.y_rot = Math_PI / 2.0;
set_message(TTR("Left View."), 2);
name = TTR("Left");
_set_auto_orthogonal();
_update_name();
} break;
case VIEW_RIGHT: {
cursor.x_rot = 0;
cursor.y_rot = -Math_PI / 2.0;
set_message(TTR("Right View."), 2);
name = TTR("Right");
_set_auto_orthogonal();
_update_name();
} break;
case VIEW_FRONT: {
cursor.x_rot = 0;
cursor.y_rot = 0;
set_message(TTR("Front View."), 2);
name = TTR("Front");
_set_auto_orthogonal();
_update_name();
} break;
case VIEW_REAR: {
cursor.x_rot = 0;
cursor.y_rot = Math_PI;
set_message(TTR("Rear View."), 2);
name = TTR("Rear");
_set_auto_orthogonal();
_update_name();
} break;
case VIEW_CENTER_TO_ORIGIN: {
cursor.pos = Vector3(0, 0, 0);
} break;
case VIEW_CENTER_TO_SELECTION: {
focus_selection();
} break;
case VIEW_ALIGN_TRANSFORM_WITH_VIEW: {
if (!get_selected_count())
break;
Transform camera_transform = camera->get_global_transform();
List<Node *> &selection = editor_selection->get_selected_node_list();
undo_redo->create_action(TTR("Align Transform with View"));
for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
Spatial *sp = Object::cast_to<Spatial>(E->get());
if (!sp)
continue;
SpatialEditorSelectedItem *se = editor_selection->get_node_editor_data<SpatialEditorSelectedItem>(sp);
if (!se)
continue;
Transform xform;
if (orthogonal) {
xform = sp->get_global_transform();
xform.basis.set_euler(camera_transform.basis.get_euler());
} else {
xform = camera_transform;
xform.scale_basis(sp->get_scale());
}
undo_redo->add_do_method(sp, "set_global_transform", xform);
undo_redo->add_undo_method(sp, "set_global_transform", sp->get_global_gizmo_transform());
}
undo_redo->commit_action();
} break;
case VIEW_ALIGN_ROTATION_WITH_VIEW: {
if (!get_selected_count())
break;
Transform camera_transform = camera->get_global_transform();
List<Node *> &selection = editor_selection->get_selected_node_list();
undo_redo->create_action(TTR("Align Rotation with View"));
for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
Spatial *sp = Object::cast_to<Spatial>(E->get());
if (!sp)
continue;
SpatialEditorSelectedItem *se = editor_selection->get_node_editor_data<SpatialEditorSelectedItem>(sp);
if (!se)
continue;
undo_redo->add_do_method(sp, "set_rotation", camera_transform.basis.get_rotation());
undo_redo->add_undo_method(sp, "set_rotation", sp->get_rotation());
}
undo_redo->commit_action();
} break;
case VIEW_ENVIRONMENT: {
int idx = view_menu->get_popup()->get_item_index(VIEW_ENVIRONMENT);
bool current = view_menu->get_popup()->is_item_checked(idx);
current = !current;
if (current) {
camera->set_environment(RES());
} else {
camera->set_environment(SpatialEditor::get_singleton()->get_viewport_environment());
}
view_menu->get_popup()->set_item_checked(idx, current);
} break;
case VIEW_PERSPECTIVE: {
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_PERSPECTIVE), true);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_ORTHOGONAL), false);
orthogonal = false;
auto_orthogonal = false;
call_deferred("update_transform_gizmo_view");
_update_name();
} break;
case VIEW_ORTHOGONAL: {
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_PERSPECTIVE), false);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_ORTHOGONAL), true);
orthogonal = true;
auto_orthogonal = false;
call_deferred("update_transform_gizmo_view");
_update_name();
} break;
case VIEW_AUTO_ORTHOGONAL: {
int idx = view_menu->get_popup()->get_item_index(VIEW_AUTO_ORTHOGONAL);
bool current = view_menu->get_popup()->is_item_checked(idx);
current = !current;
view_menu->get_popup()->set_item_checked(idx, current);
if (auto_orthogonal) {
auto_orthogonal = false;
_update_name();
}
} break;
case VIEW_LOCK_ROTATION: {
int idx = view_menu->get_popup()->get_item_index(VIEW_LOCK_ROTATION);
bool current = view_menu->get_popup()->is_item_checked(idx);
lock_rotation = !current;
view_menu->get_popup()->set_item_checked(idx, !current);
if (lock_rotation) {
locked_label->show();
} else {
locked_label->hide();
}
} break;
case VIEW_AUDIO_LISTENER: {
int idx = view_menu->get_popup()->get_item_index(VIEW_AUDIO_LISTENER);
bool current = view_menu->get_popup()->is_item_checked(idx);
current = !current;
viewport->set_as_audio_listener(current);
view_menu->get_popup()->set_item_checked(idx, current);
} break;
case VIEW_AUDIO_DOPPLER: {
int idx = view_menu->get_popup()->get_item_index(VIEW_AUDIO_DOPPLER);
bool current = view_menu->get_popup()->is_item_checked(idx);
current = !current;
camera->set_doppler_tracking(current ? Camera::DOPPLER_TRACKING_IDLE_STEP : Camera::DOPPLER_TRACKING_DISABLED);
view_menu->get_popup()->set_item_checked(idx, current);
} break;
case VIEW_CINEMATIC_PREVIEW: {
int idx = view_menu->get_popup()->get_item_index(VIEW_CINEMATIC_PREVIEW);
bool current = view_menu->get_popup()->is_item_checked(idx);
current = !current;
view_menu->get_popup()->set_item_checked(idx, current);
previewing_cinema = true;
_toggle_cinema_preview(current);
if (current) {
preview_camera->hide();
} else {
if (previewing != NULL)
preview_camera->show();
}
} break;
case VIEW_GIZMOS: {
int idx = view_menu->get_popup()->get_item_index(VIEW_GIZMOS);
bool current = view_menu->get_popup()->is_item_checked(idx);
current = !current;
if (current)
camera->set_cull_mask(((1 << 20) - 1) | (1 << (GIZMO_BASE_LAYER + index)) | (1 << GIZMO_EDIT_LAYER) | (1 << GIZMO_GRID_LAYER));
else
camera->set_cull_mask(((1 << 20) - 1) | (1 << (GIZMO_BASE_LAYER + index)) | (1 << GIZMO_GRID_LAYER));
view_menu->get_popup()->set_item_checked(idx, current);
} break;
case VIEW_HALF_RESOLUTION: {
int idx = view_menu->get_popup()->get_item_index(VIEW_HALF_RESOLUTION);
bool current = view_menu->get_popup()->is_item_checked(idx);
current = !current;
view_menu->get_popup()->set_item_checked(idx, current);
} break;
case VIEW_INFORMATION: {
int idx = view_menu->get_popup()->get_item_index(VIEW_INFORMATION);
bool current = view_menu->get_popup()->is_item_checked(idx);
view_menu->get_popup()->set_item_checked(idx, !current);
} break;
case VIEW_FPS: {
int idx = view_menu->get_popup()->get_item_index(VIEW_FPS);
bool current = view_menu->get_popup()->is_item_checked(idx);
view_menu->get_popup()->set_item_checked(idx, !current);
} break;
case VIEW_DISPLAY_NORMAL: {
viewport->set_debug_draw(Viewport::DEBUG_DRAW_DISABLED);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_DISPLAY_NORMAL), true);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_DISPLAY_WIREFRAME), false);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_DISPLAY_OVERDRAW), false);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_DISPLAY_SHADELESS), false);
} break;
case VIEW_DISPLAY_WIREFRAME: {
viewport->set_debug_draw(Viewport::DEBUG_DRAW_WIREFRAME);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_DISPLAY_NORMAL), false);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_DISPLAY_WIREFRAME), true);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_DISPLAY_OVERDRAW), false);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_DISPLAY_SHADELESS), false);
} break;
case VIEW_DISPLAY_OVERDRAW: {
viewport->set_debug_draw(Viewport::DEBUG_DRAW_OVERDRAW);
VisualServer::get_singleton()->scenario_set_debug(get_tree()->get_root()->get_world()->get_scenario(), VisualServer::SCENARIO_DEBUG_OVERDRAW);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_DISPLAY_NORMAL), false);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_DISPLAY_WIREFRAME), false);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_DISPLAY_OVERDRAW), true);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_DISPLAY_SHADELESS), false);
} break;
case VIEW_DISPLAY_SHADELESS: {
viewport->set_debug_draw(Viewport::DEBUG_DRAW_UNSHADED);
VisualServer::get_singleton()->scenario_set_debug(get_tree()->get_root()->get_world()->get_scenario(), VisualServer::SCENARIO_DEBUG_SHADELESS);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_DISPLAY_NORMAL), false);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_DISPLAY_WIREFRAME), false);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_DISPLAY_OVERDRAW), false);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_DISPLAY_SHADELESS), true);
} break;
}
}
void SpatialEditorViewport::_set_auto_orthogonal() {
if (!orthogonal && view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_AUTO_ORTHOGONAL))) {
_menu_option(VIEW_ORTHOGONAL);
auto_orthogonal = true;
}
}
void SpatialEditorViewport::_preview_exited_scene() {
preview_camera->disconnect("toggled", this, "_toggle_camera_preview");
preview_camera->set_pressed(false);
_toggle_camera_preview(false);
preview_camera->connect("toggled", this, "_toggle_camera_preview");
view_menu->show();
}
void SpatialEditorViewport::_init_gizmo_instance(int p_idx) {
uint32_t layer = 1 << (GIZMO_BASE_LAYER + p_idx);
for (int i = 0; i < 3; i++) {
move_gizmo_instance[i] = VS::get_singleton()->instance_create();
VS::get_singleton()->instance_set_base(move_gizmo_instance[i], spatial_editor->get_move_gizmo(i)->get_rid());
VS::get_singleton()->instance_set_scenario(move_gizmo_instance[i], get_tree()->get_root()->get_world()->get_scenario());
VS::get_singleton()->instance_set_visible(move_gizmo_instance[i], false);
VS::get_singleton()->instance_geometry_set_cast_shadows_setting(move_gizmo_instance[i], VS::SHADOW_CASTING_SETTING_OFF);
VS::get_singleton()->instance_set_layer_mask(move_gizmo_instance[i], layer);
move_plane_gizmo_instance[i] = VS::get_singleton()->instance_create();
VS::get_singleton()->instance_set_base(move_plane_gizmo_instance[i], spatial_editor->get_move_plane_gizmo(i)->get_rid());
VS::get_singleton()->instance_set_scenario(move_plane_gizmo_instance[i], get_tree()->get_root()->get_world()->get_scenario());
VS::get_singleton()->instance_set_visible(move_plane_gizmo_instance[i], false);
VS::get_singleton()->instance_geometry_set_cast_shadows_setting(move_plane_gizmo_instance[i], VS::SHADOW_CASTING_SETTING_OFF);
VS::get_singleton()->instance_set_layer_mask(move_plane_gizmo_instance[i], layer);
rotate_gizmo_instance[i] = VS::get_singleton()->instance_create();
VS::get_singleton()->instance_set_base(rotate_gizmo_instance[i], spatial_editor->get_rotate_gizmo(i)->get_rid());
VS::get_singleton()->instance_set_scenario(rotate_gizmo_instance[i], get_tree()->get_root()->get_world()->get_scenario());
VS::get_singleton()->instance_set_visible(rotate_gizmo_instance[i], false);
VS::get_singleton()->instance_geometry_set_cast_shadows_setting(rotate_gizmo_instance[i], VS::SHADOW_CASTING_SETTING_OFF);
VS::get_singleton()->instance_set_layer_mask(rotate_gizmo_instance[i], layer);
scale_gizmo_instance[i] = VS::get_singleton()->instance_create();
VS::get_singleton()->instance_set_base(scale_gizmo_instance[i], spatial_editor->get_scale_gizmo(i)->get_rid());
VS::get_singleton()->instance_set_scenario(scale_gizmo_instance[i], get_tree()->get_root()->get_world()->get_scenario());
VS::get_singleton()->instance_set_visible(scale_gizmo_instance[i], false);
VS::get_singleton()->instance_geometry_set_cast_shadows_setting(scale_gizmo_instance[i], VS::SHADOW_CASTING_SETTING_OFF);
VS::get_singleton()->instance_set_layer_mask(scale_gizmo_instance[i], layer);
scale_plane_gizmo_instance[i] = VS::get_singleton()->instance_create();
VS::get_singleton()->instance_set_base(scale_plane_gizmo_instance[i], spatial_editor->get_scale_plane_gizmo(i)->get_rid());
VS::get_singleton()->instance_set_scenario(scale_plane_gizmo_instance[i], get_tree()->get_root()->get_world()->get_scenario());
VS::get_singleton()->instance_set_visible(scale_plane_gizmo_instance[i], false);
VS::get_singleton()->instance_geometry_set_cast_shadows_setting(scale_plane_gizmo_instance[i], VS::SHADOW_CASTING_SETTING_OFF);
VS::get_singleton()->instance_set_layer_mask(scale_plane_gizmo_instance[i], layer);
}
}
void SpatialEditorViewport::_finish_gizmo_instances() {
for (int i = 0; i < 3; i++) {
VS::get_singleton()->free(move_gizmo_instance[i]);
VS::get_singleton()->free(move_plane_gizmo_instance[i]);
VS::get_singleton()->free(rotate_gizmo_instance[i]);
VS::get_singleton()->free(scale_gizmo_instance[i]);
VS::get_singleton()->free(scale_plane_gizmo_instance[i]);
}
}
void SpatialEditorViewport::_toggle_camera_preview(bool p_activate) {
ERR_FAIL_COND(p_activate && !preview);
ERR_FAIL_COND(!p_activate && !previewing);
rotation_control->set_visible(!p_activate);
if (!p_activate) {
previewing->disconnect("tree_exiting", this, "_preview_exited_scene");
previewing = NULL;
VS::get_singleton()->viewport_attach_camera(viewport->get_viewport_rid(), camera->get_camera()); //restore
if (!preview)
preview_camera->hide();
view_menu->set_disabled(false);
surface->update();
} else {
previewing = preview;
previewing->connect("tree_exiting", this, "_preview_exited_scene");
VS::get_singleton()->viewport_attach_camera(viewport->get_viewport_rid(), preview->get_camera()); //replace
view_menu->set_disabled(true);
surface->update();
}
}
void SpatialEditorViewport::_toggle_cinema_preview(bool p_activate) {
previewing_cinema = p_activate;
if (!previewing_cinema) {
if (previewing != NULL)
previewing->disconnect("tree_exited", this, "_preview_exited_scene");
previewing = NULL;
VS::get_singleton()->viewport_attach_camera(viewport->get_viewport_rid(), camera->get_camera()); //restore
preview_camera->set_pressed(false);
if (!preview) {
preview_camera->hide();
} else {
preview_camera->show();
}
view_menu->show();
surface->update();
}
}
void SpatialEditorViewport::_selection_result_pressed(int p_result) {
if (selection_results.size() <= p_result)
return;
clicked = selection_results[p_result].item->get_instance_id();
if (clicked) {
_select_clicked(clicked_wants_append, true);
clicked = 0;
}
}
void SpatialEditorViewport::_selection_menu_hide() {
selection_results.clear();
selection_menu->clear();
selection_menu->set_size(Vector2(0, 0));
}
void SpatialEditorViewport::set_can_preview(Camera *p_preview) {
preview = p_preview;
if (!preview_camera->is_pressed() && !previewing_cinema)
preview_camera->set_visible(p_preview);
}
void SpatialEditorViewport::update_transform_gizmo_view() {
if (!is_visible_in_tree())
return;
Transform xform = spatial_editor->get_gizmo_transform();
Transform camera_xform = camera->get_transform();
if (xform.origin.distance_squared_to(camera_xform.origin) < 0.01) {
for (int i = 0; i < 3; i++) {
VisualServer::get_singleton()->instance_set_visible(move_gizmo_instance[i], false);
VisualServer::get_singleton()->instance_set_visible(move_plane_gizmo_instance[i], false);
VisualServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[i], false);
VisualServer::get_singleton()->instance_set_visible(scale_gizmo_instance[i], false);
VisualServer::get_singleton()->instance_set_visible(scale_plane_gizmo_instance[i], false);
}
return;
}
Vector3 camz = -camera_xform.get_basis().get_axis(2).normalized();
Vector3 camy = -camera_xform.get_basis().get_axis(1).normalized();
Plane p(camera_xform.origin, camz);
float gizmo_d = Math::abs(p.distance_to(xform.origin));
float d0 = camera->unproject_position(camera_xform.origin + camz * gizmo_d).y;
float d1 = camera->unproject_position(camera_xform.origin + camz * gizmo_d + camy).y;
float dd = Math::abs(d0 - d1);
if (dd == 0)
dd = 0.0001;
float gizmo_size = EditorSettings::get_singleton()->get("editors/3d/manipulator_gizmo_size");
// At low viewport heights, multiply the gizmo scale based on the viewport height.
// This prevents the gizmo from growing very large and going outside the viewport.
const int viewport_base_height = 400 * MAX(1, EDSCALE);
gizmo_scale =
(gizmo_size / Math::abs(dd)) * MAX(1, EDSCALE) *
MIN(viewport_base_height, viewport_container->get_size().height) / viewport_base_height /
viewport_container->get_stretch_shrink();
Vector3 scale = Vector3(1, 1, 1) * gizmo_scale;
xform.basis.scale(scale);
for (int i = 0; i < 3; i++) {
VisualServer::get_singleton()->instance_set_transform(move_gizmo_instance[i], xform);
VisualServer::get_singleton()->instance_set_visible(move_gizmo_instance[i], spatial_editor->is_gizmo_visible() && (spatial_editor->get_tool_mode() == SpatialEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == SpatialEditor::TOOL_MODE_MOVE));
VisualServer::get_singleton()->instance_set_transform(move_plane_gizmo_instance[i], xform);
VisualServer::get_singleton()->instance_set_visible(move_plane_gizmo_instance[i], spatial_editor->is_gizmo_visible() && (spatial_editor->get_tool_mode() == SpatialEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == SpatialEditor::TOOL_MODE_MOVE));
VisualServer::get_singleton()->instance_set_transform(rotate_gizmo_instance[i], xform);
VisualServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[i], spatial_editor->is_gizmo_visible() && (spatial_editor->get_tool_mode() == SpatialEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == SpatialEditor::TOOL_MODE_ROTATE));
VisualServer::get_singleton()->instance_set_transform(scale_gizmo_instance[i], xform);
VisualServer::get_singleton()->instance_set_visible(scale_gizmo_instance[i], spatial_editor->is_gizmo_visible() && (spatial_editor->get_tool_mode() == SpatialEditor::TOOL_MODE_SCALE));
VisualServer::get_singleton()->instance_set_transform(scale_plane_gizmo_instance[i], xform);
VisualServer::get_singleton()->instance_set_visible(scale_plane_gizmo_instance[i], spatial_editor->is_gizmo_visible() && (spatial_editor->get_tool_mode() == SpatialEditor::TOOL_MODE_SCALE));
}
}
void SpatialEditorViewport::set_state(const Dictionary &p_state) {
if (p_state.has("position"))
cursor.pos = p_state["position"];
if (p_state.has("x_rotation"))
cursor.x_rot = p_state["x_rotation"];
if (p_state.has("y_rotation"))
cursor.y_rot = p_state["y_rotation"];
if (p_state.has("distance"))
cursor.distance = p_state["distance"];
if (p_state.has("use_orthogonal")) {
bool orth = p_state["use_orthogonal"];
if (orth)
_menu_option(VIEW_ORTHOGONAL);
else
_menu_option(VIEW_PERSPECTIVE);
}
if (p_state.has("view_name")) {
name = p_state["view_name"];
_update_name();
}
if (p_state.has("auto_orthogonal")) {
auto_orthogonal = p_state["auto_orthogonal"];
_update_name();
}
if (p_state.has("auto_orthogonal_enabled")) {
bool enabled = p_state["auto_orthogonal_enabled"];
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_AUTO_ORTHOGONAL), enabled);
}
if (p_state.has("display_mode")) {
int display = p_state["display_mode"];
int idx = view_menu->get_popup()->get_item_index(display);
if (!view_menu->get_popup()->is_item_checked(idx))
_menu_option(display);
}
if (p_state.has("lock_rotation")) {
lock_rotation = p_state["lock_rotation"];
int idx = view_menu->get_popup()->get_item_index(VIEW_LOCK_ROTATION);
view_menu->get_popup()->set_item_checked(idx, lock_rotation);
}
if (p_state.has("use_environment")) {
bool env = p_state["use_environment"];
if (env != camera->get_environment().is_valid())
_menu_option(VIEW_ENVIRONMENT);
}
if (p_state.has("listener")) {
bool listener = p_state["listener"];
int idx = view_menu->get_popup()->get_item_index(VIEW_AUDIO_LISTENER);
viewport->set_as_audio_listener(listener);
view_menu->get_popup()->set_item_checked(idx, listener);
}
if (p_state.has("doppler")) {
bool doppler = p_state["doppler"];
int idx = view_menu->get_popup()->get_item_index(VIEW_AUDIO_DOPPLER);
camera->set_doppler_tracking(doppler ? Camera::DOPPLER_TRACKING_IDLE_STEP : Camera::DOPPLER_TRACKING_DISABLED);
view_menu->get_popup()->set_item_checked(idx, doppler);
}
if (p_state.has("gizmos")) {
bool gizmos = p_state["gizmos"];
int idx = view_menu->get_popup()->get_item_index(VIEW_GIZMOS);
if (view_menu->get_popup()->is_item_checked(idx) != gizmos)
_menu_option(VIEW_GIZMOS);
}
if (p_state.has("information")) {
bool information = p_state["information"];
int idx = view_menu->get_popup()->get_item_index(VIEW_INFORMATION);
if (view_menu->get_popup()->is_item_checked(idx) != information)
_menu_option(VIEW_INFORMATION);
}
if (p_state.has("fps")) {
bool fps = p_state["fps"];
int idx = view_menu->get_popup()->get_item_index(VIEW_FPS);
if (view_menu->get_popup()->is_item_checked(idx) != fps)
_menu_option(VIEW_FPS);
}
if (p_state.has("half_res")) {
bool half_res = p_state["half_res"];
int idx = view_menu->get_popup()->get_item_index(VIEW_HALF_RESOLUTION);
view_menu->get_popup()->set_item_checked(idx, half_res);
}
if (p_state.has("cinematic_preview")) {
previewing_cinema = p_state["cinematic_preview"];
int idx = view_menu->get_popup()->get_item_index(VIEW_CINEMATIC_PREVIEW);
view_menu->get_popup()->set_item_checked(idx, previewing_cinema);
}
if (preview_camera->is_connected("toggled", this, "_toggle_camera_preview")) {
preview_camera->disconnect("toggled", this, "_toggle_camera_preview");
}
if (p_state.has("previewing")) {
Node *pv = EditorNode::get_singleton()->get_edited_scene()->get_node(p_state["previewing"]);
if (Object::cast_to<Camera>(pv)) {
previewing = Object::cast_to<Camera>(pv);
previewing->connect("tree_exiting", this, "_preview_exited_scene");
VS::get_singleton()->viewport_attach_camera(viewport->get_viewport_rid(), previewing->get_camera()); //replace
view_menu->set_disabled(true);
surface->update();
preview_camera->set_pressed(true);
preview_camera->show();
}
}
preview_camera->connect("toggled", this, "_toggle_camera_preview");
}
Dictionary SpatialEditorViewport::get_state() const {
Dictionary d;
d["position"] = cursor.pos;
d["x_rotation"] = cursor.x_rot;
d["y_rotation"] = cursor.y_rot;
d["distance"] = cursor.distance;
d["use_environment"] = camera->get_environment().is_valid();
d["use_orthogonal"] = camera->get_projection() == Camera::PROJECTION_ORTHOGONAL;
d["view_name"] = name;
d["auto_orthogonal"] = auto_orthogonal;
d["auto_orthogonal_enabled"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_AUTO_ORTHOGONAL));
if (view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_DISPLAY_NORMAL)))
d["display_mode"] = VIEW_DISPLAY_NORMAL;
else if (view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_DISPLAY_WIREFRAME)))
d["display_mode"] = VIEW_DISPLAY_WIREFRAME;
else if (view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_DISPLAY_OVERDRAW)))
d["display_mode"] = VIEW_DISPLAY_OVERDRAW;
else if (view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_DISPLAY_SHADELESS)))
d["display_mode"] = VIEW_DISPLAY_SHADELESS;
d["listener"] = viewport->is_audio_listener();
d["doppler"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_AUDIO_DOPPLER));
d["gizmos"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_GIZMOS));
d["information"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_INFORMATION));
d["fps"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_FPS));
d["half_res"] = viewport_container->get_stretch_shrink() > 1;
d["cinematic_preview"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_CINEMATIC_PREVIEW));
if (previewing)
d["previewing"] = EditorNode::get_singleton()->get_edited_scene()->get_path_to(previewing);
if (lock_rotation)
d["lock_rotation"] = lock_rotation;
return d;
}
void SpatialEditorViewport::_bind_methods() {
ClassDB::bind_method(D_METHOD("_draw"), &SpatialEditorViewport::_draw);
ClassDB::bind_method(D_METHOD("_surface_mouse_enter"), &SpatialEditorViewport::_surface_mouse_enter);
ClassDB::bind_method(D_METHOD("_surface_mouse_exit"), &SpatialEditorViewport::_surface_mouse_exit);
ClassDB::bind_method(D_METHOD("_surface_focus_enter"), &SpatialEditorViewport::_surface_focus_enter);
ClassDB::bind_method(D_METHOD("_surface_focus_exit"), &SpatialEditorViewport::_surface_focus_exit);
ClassDB::bind_method(D_METHOD("_sinput"), &SpatialEditorViewport::_sinput);
ClassDB::bind_method(D_METHOD("_menu_option"), &SpatialEditorViewport::_menu_option);
ClassDB::bind_method(D_METHOD("_toggle_camera_preview"), &SpatialEditorViewport::_toggle_camera_preview);
ClassDB::bind_method(D_METHOD("_preview_exited_scene"), &SpatialEditorViewport::_preview_exited_scene);
ClassDB::bind_method(D_METHOD("_update_camera"), &SpatialEditorViewport::_update_camera);
ClassDB::bind_method(D_METHOD("update_transform_gizmo_view"), &SpatialEditorViewport::update_transform_gizmo_view);
ClassDB::bind_method(D_METHOD("_selection_result_pressed"), &SpatialEditorViewport::_selection_result_pressed);
ClassDB::bind_method(D_METHOD("_selection_menu_hide"), &SpatialEditorViewport::_selection_menu_hide);
ClassDB::bind_method(D_METHOD("can_drop_data_fw"), &SpatialEditorViewport::can_drop_data_fw);
ClassDB::bind_method(D_METHOD("drop_data_fw"), &SpatialEditorViewport::drop_data_fw);
ADD_SIGNAL(MethodInfo("toggle_maximize_view", PropertyInfo(Variant::OBJECT, "viewport")));
ADD_SIGNAL(MethodInfo("clicked", PropertyInfo(Variant::OBJECT, "viewport")));
}
void SpatialEditorViewport::reset() {
orthogonal = false;
auto_orthogonal = false;
lock_rotation = false;
message_time = 0;
message = "";
last_message = "";
name = "";
cursor.x_rot = 0.5;
cursor.y_rot = 0.5;
cursor.distance = 4;
cursor.region_select = false;
cursor.pos = Vector3();
_update_name();
}
void SpatialEditorViewport::focus_selection() {
if (!get_selected_count())
return;
Vector3 center;
int count = 0;
List<Node *> &selection = editor_selection->get_selected_node_list();
for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
Spatial *sp = Object::cast_to<Spatial>(E->get());
if (!sp)
continue;
SpatialEditorSelectedItem *se = editor_selection->get_node_editor_data<SpatialEditorSelectedItem>(sp);
if (!se)
continue;
center += sp->get_global_gizmo_transform().origin;
count++;
}
if (count != 0) {
center /= float(count);
}
cursor.pos = center;
}
void SpatialEditorViewport::assign_pending_data_pointers(Spatial *p_preview_node, AABB *p_preview_bounds, AcceptDialog *p_accept) {
preview_node = p_preview_node;
preview_bounds = p_preview_bounds;
accept = p_accept;
}
Vector3 SpatialEditorViewport::_get_instance_position(const Point2 &p_pos) const {
const float MAX_DISTANCE = 10;
Vector3 world_ray = _get_ray(p_pos);
Vector3 world_pos = _get_ray_pos(p_pos);
Vector<ObjectID> instances = VisualServer::get_singleton()->instances_cull_ray(world_pos, world_ray, get_tree()->get_root()->get_world()->get_scenario());
Set<Ref<EditorSpatialGizmo> > found_gizmos;
float closest_dist = MAX_DISTANCE;
Vector3 point = world_pos + world_ray * MAX_DISTANCE;
Vector3 normal = Vector3(0.0, 0.0, 0.0);
for (int i = 0; i < instances.size(); i++) {
MeshInstance *mesh_instance = Object::cast_to<MeshInstance>(ObjectDB::get_instance(instances[i]));
if (!mesh_instance)
continue;
Ref<EditorSpatialGizmo> seg = mesh_instance->get_gizmo();
if ((!seg.is_valid()) || found_gizmos.has(seg)) {
continue;
}
found_gizmos.insert(seg);
Vector3 hit_point;
Vector3 hit_normal;
bool inters = seg->intersect_ray(camera, p_pos, hit_point, hit_normal, NULL, false);
if (!inters)
continue;
float dist = world_pos.distance_to(hit_point);
if (dist < 0)
continue;
if (dist < closest_dist) {
closest_dist = dist;
point = hit_point;
normal = hit_normal;
}
}
Vector3 offset = Vector3();
for (int i = 0; i < 3; i++) {
if (normal[i] > 0.0)
offset[i] = (preview_bounds->get_size()[i] - (preview_bounds->get_size()[i] + preview_bounds->get_position()[i]));
else if (normal[i] < 0.0)
offset[i] = -(preview_bounds->get_size()[i] + preview_bounds->get_position()[i]);
}
return point + offset;
}
AABB SpatialEditorViewport::_calculate_spatial_bounds(const Spatial *p_parent, bool p_exclude_toplevel_transform) {
AABB bounds;
const MeshInstance *mesh_instance = Object::cast_to<MeshInstance>(p_parent);
if (mesh_instance) {
bounds = mesh_instance->get_aabb();
}
for (int i = 0; i < p_parent->get_child_count(); i++) {
Spatial *child = Object::cast_to<Spatial>(p_parent->get_child(i));
if (child) {
AABB child_bounds = _calculate_spatial_bounds(child, false);
if (bounds.size == Vector3() && p_parent->get_class_name() == StringName("Spatial")) {
bounds = child_bounds;
} else {
bounds.merge_with(child_bounds);
}
}
}
if (bounds.size == Vector3() && p_parent->get_class_name() != StringName("Spatial")) {
bounds = AABB(Vector3(-0.2, -0.2, -0.2), Vector3(0.4, 0.4, 0.4));
}
if (!p_exclude_toplevel_transform) {
bounds = p_parent->get_transform().xform(bounds);
}
return bounds;
}
void SpatialEditorViewport::_create_preview(const Vector<String> &files) const {
for (int i = 0; i < files.size(); i++) {
String path = files[i];
RES res = ResourceLoader::load(path);
ERR_CONTINUE(res.is_null());
Ref<PackedScene> scene = Ref<PackedScene>(Object::cast_to<PackedScene>(*res));
Ref<Mesh> mesh = Ref<Mesh>(Object::cast_to<Mesh>(*res));
if (mesh != NULL || scene != NULL) {
if (mesh != NULL) {
MeshInstance *mesh_instance = memnew(MeshInstance);
mesh_instance->set_mesh(mesh);
preview_node->add_child(mesh_instance);
} else {
if (scene.is_valid()) {
Node *instance = scene->instance();
if (instance) {
preview_node->add_child(instance);
}
}
}
editor->get_scene_root()->add_child(preview_node);
}
}
*preview_bounds = _calculate_spatial_bounds(preview_node);
}
void SpatialEditorViewport::_remove_preview() {
if (preview_node->get_parent()) {
for (int i = preview_node->get_child_count() - 1; i >= 0; i--) {
Node *node = preview_node->get_child(i);
node->queue_delete();
preview_node->remove_child(node);
}
editor->get_scene_root()->remove_child(preview_node);
}
}
bool SpatialEditorViewport::_cyclical_dependency_exists(const String &p_target_scene_path, Node *p_desired_node) {
if (p_desired_node->get_filename() == p_target_scene_path) {
return true;
}
int childCount = p_desired_node->get_child_count();
for (int i = 0; i < childCount; i++) {
Node *child = p_desired_node->get_child(i);
if (_cyclical_dependency_exists(p_target_scene_path, child)) {
return true;
}
}
return false;
}
bool SpatialEditorViewport::_create_instance(Node *parent, String &path, const Point2 &p_point) {
RES res = ResourceLoader::load(path);
ERR_FAIL_COND_V(res.is_null(), false);
Ref<PackedScene> scene = Ref<PackedScene>(Object::cast_to<PackedScene>(*res));
Ref<Mesh> mesh = Ref<Mesh>(Object::cast_to<Mesh>(*res));
Node *instanced_scene = NULL;
if (mesh != NULL || scene != NULL) {
if (mesh != NULL) {
MeshInstance *mesh_instance = memnew(MeshInstance);
mesh_instance->set_mesh(mesh);
mesh_instance->set_name(path.get_file().get_basename());
instanced_scene = mesh_instance;
} else {
if (!scene.is_valid()) { // invalid scene
return false;
} else {
instanced_scene = scene->instance(PackedScene::GEN_EDIT_STATE_INSTANCE);
}
}
}
if (instanced_scene == NULL) {
return false;
}
if (editor->get_edited_scene()->get_filename() != "") { // cyclical instancing
if (_cyclical_dependency_exists(editor->get_edited_scene()->get_filename(), instanced_scene)) {
memdelete(instanced_scene);
return false;
}
}
if (scene != NULL) {
instanced_scene->set_filename(ProjectSettings::get_singleton()->localize_path(path));
}
editor_data->get_undo_redo().add_do_method(parent, "add_child", instanced_scene);
editor_data->get_undo_redo().add_do_method(instanced_scene, "set_owner", editor->get_edited_scene());
editor_data->get_undo_redo().add_do_reference(instanced_scene);
editor_data->get_undo_redo().add_undo_method(parent, "remove_child", instanced_scene);
String new_name = parent->validate_child_name(instanced_scene);
ScriptEditorDebugger *sed = ScriptEditor::get_singleton()->get_debugger();
editor_data->get_undo_redo().add_do_method(sed, "live_debug_instance_node", editor->get_edited_scene()->get_path_to(parent), path, new_name);
editor_data->get_undo_redo().add_undo_method(sed, "live_debug_remove_node", NodePath(String(editor->get_edited_scene()->get_path_to(parent)) + "/" + new_name));
Transform global_transform;
Spatial *parent_spatial = Object::cast_to<Spatial>(parent);
if (parent_spatial)
global_transform = parent_spatial->get_global_gizmo_transform();
global_transform.origin = spatial_editor->snap_point(_get_instance_position(p_point));
editor_data->get_undo_redo().add_do_method(instanced_scene, "set_global_transform", global_transform);
return true;
}
void SpatialEditorViewport::_perform_drop_data() {
_remove_preview();
Vector<String> error_files;
editor_data->get_undo_redo().create_action(TTR("Create Node"));
for (int i = 0; i < selected_files.size(); i++) {
String path = selected_files[i];
RES res = ResourceLoader::load(path);
if (res.is_null()) {
continue;
}
Ref<PackedScene> scene = Ref<PackedScene>(Object::cast_to<PackedScene>(*res));
Ref<Mesh> mesh = Ref<Mesh>(Object::cast_to<Mesh>(*res));
if (mesh != NULL || scene != NULL) {
bool success = _create_instance(target_node, path, drop_pos);
if (!success) {
error_files.push_back(path);
}
}
}
editor_data->get_undo_redo().commit_action();
if (error_files.size() > 0) {
String files_str;
for (int i = 0; i < error_files.size(); i++) {
files_str += error_files[i].get_file().get_basename() + ",";
}
files_str = files_str.substr(0, files_str.length() - 1);
accept->set_text(vformat(TTR("Error instancing scene from %s"), files_str.c_str()));
accept->popup_centered_minsize();
}
}
bool SpatialEditorViewport::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
bool can_instance = false;
if (!preview_node->is_inside_tree()) {
Dictionary d = p_data;
if (d.has("type") && (String(d["type"]) == "files")) {
Vector<String> files = d["files"];
List<String> scene_extensions;
ResourceLoader::get_recognized_extensions_for_type("PackedScene", &scene_extensions);
List<String> mesh_extensions;
ResourceLoader::get_recognized_extensions_for_type("Mesh", &mesh_extensions);
for (int i = 0; i < files.size(); i++) {
if (mesh_extensions.find(files[i].get_extension()) || scene_extensions.find(files[i].get_extension())) {
RES res = ResourceLoader::load(files[i]);
if (res.is_null()) {
continue;
}
String type = res->get_class();
if (type == "PackedScene") {
Ref<PackedScene> sdata = ResourceLoader::load(files[i]);
Node *instanced_scene = sdata->instance(PackedScene::GEN_EDIT_STATE_INSTANCE);
if (!instanced_scene) {
continue;
}
memdelete(instanced_scene);
} else if (type == "Mesh" || type == "ArrayMesh" || type == "PrimitiveMesh") {
Ref<Mesh> mesh = ResourceLoader::load(files[i]);
if (!mesh.is_valid()) {
continue;
}
} else {
continue;
}
can_instance = true;
break;
}
}
if (can_instance) {
_create_preview(files);
}
}
} else {
can_instance = true;
}
if (can_instance) {
Transform global_transform = Transform(Basis(), _get_instance_position(p_point));
preview_node->set_global_transform(global_transform);
}
return can_instance;
}
void SpatialEditorViewport::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {
if (!can_drop_data_fw(p_point, p_data, p_from))
return;
bool is_shift = Input::get_singleton()->is_key_pressed(KEY_SHIFT);
selected_files.clear();
Dictionary d = p_data;
if (d.has("type") && String(d["type"]) == "files") {
selected_files = d["files"];
}
List<Node *> list = editor->get_editor_selection()->get_selected_node_list();
if (list.size() == 0) {
Node *root_node = editor->get_edited_scene();
if (root_node) {
list.push_back(root_node);
} else {
accept->set_text(TTR("No parent to instance a child at."));
accept->popup_centered_minsize();
_remove_preview();
return;
}
}
if (list.size() != 1) {
accept->set_text(TTR("This operation requires a single selected node."));
accept->popup_centered_minsize();
_remove_preview();
return;
}
target_node = list[0];
if (is_shift && target_node != editor->get_edited_scene()) {
target_node = target_node->get_parent();
}
drop_pos = p_point;
_perform_drop_data();
}
SpatialEditorViewport::SpatialEditorViewport(SpatialEditor *p_spatial_editor, EditorNode *p_editor, int p_index) {
_edit.mode = TRANSFORM_NONE;
_edit.plane = TRANSFORM_VIEW;
_edit.edited_gizmo = 0;
_edit.snap = 1;
_edit.gizmo_handle = 0;
index = p_index;
editor = p_editor;
editor_data = editor->get_scene_tree_dock()->get_editor_data();
editor_selection = editor->get_editor_selection();
undo_redo = editor->get_undo_redo();
clicked = 0;
clicked_includes_current = false;
orthogonal = false;
auto_orthogonal = false;
lock_rotation = false;
message_time = 0;
zoom_indicator_delay = 0.0;
spatial_editor = p_spatial_editor;
ViewportContainer *c = memnew(ViewportContainer);
viewport_container = c;
c->set_stretch(true);
add_child(c);
c->set_anchors_and_margins_preset(Control::PRESET_WIDE);
viewport = memnew(Viewport);
viewport->set_disable_input(true);
c->add_child(viewport);
surface = memnew(Control);
surface->set_drag_forwarding(this);
add_child(surface);
surface->set_anchors_and_margins_preset(Control::PRESET_WIDE);
surface->set_clip_contents(true);
camera = memnew(Camera);
camera->set_disable_gizmo(true);
camera->set_cull_mask(((1 << 20) - 1) | (1 << (GIZMO_BASE_LAYER + p_index)) | (1 << GIZMO_EDIT_LAYER) | (1 << GIZMO_GRID_LAYER));
viewport->add_child(camera);
camera->make_current();
surface->set_focus_mode(FOCUS_ALL);
crosshair = memnew(TextureRect);
crosshair->set_mouse_filter(MOUSE_FILTER_IGNORE);
surface->add_child(crosshair);
VBoxContainer *vbox = memnew(VBoxContainer);
surface->add_child(vbox);
vbox->set_position(Point2(10, 10) * EDSCALE);
view_menu = memnew(MenuButton);
view_menu->set_flat(false);
vbox->add_child(view_menu);
view_menu->set_h_size_flags(0);
view_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/top_view"), VIEW_TOP);
view_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/bottom_view"), VIEW_BOTTOM);
view_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/left_view"), VIEW_LEFT);
view_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/right_view"), VIEW_RIGHT);
view_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/front_view"), VIEW_FRONT);
view_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/rear_view"), VIEW_REAR);
view_menu->get_popup()->add_separator();
view_menu->get_popup()->add_radio_check_item(TTR("Perspective") + " (" + ED_GET_SHORTCUT("spatial_editor/switch_perspective_orthogonal")->get_as_text() + ")", VIEW_PERSPECTIVE);
view_menu->get_popup()->add_radio_check_item(TTR("Orthogonal") + " (" + ED_GET_SHORTCUT("spatial_editor/switch_perspective_orthogonal")->get_as_text() + ")", VIEW_ORTHOGONAL);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_PERSPECTIVE), true);
view_menu->get_popup()->add_check_item(TTR("Auto Orthogonal Enabled"), VIEW_AUTO_ORTHOGONAL);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_AUTO_ORTHOGONAL), true);
view_menu->get_popup()->add_separator();
view_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_lock_rotation", TTR("Lock View Rotation")), VIEW_LOCK_ROTATION);
view_menu->get_popup()->add_separator();
view_menu->get_popup()->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_normal", TTR("Display Normal")), VIEW_DISPLAY_NORMAL);
view_menu->get_popup()->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_wireframe", TTR("Display Wireframe")), VIEW_DISPLAY_WIREFRAME);
view_menu->get_popup()->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_overdraw", TTR("Display Overdraw")), VIEW_DISPLAY_OVERDRAW);
view_menu->get_popup()->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_unshaded", TTR("Display Unshaded")), VIEW_DISPLAY_SHADELESS);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_DISPLAY_NORMAL), true);
view_menu->get_popup()->add_separator();
view_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_environment", TTR("View Environment")), VIEW_ENVIRONMENT);
view_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_gizmos", TTR("View Gizmos")), VIEW_GIZMOS);
view_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_information", TTR("View Information")), VIEW_INFORMATION);
view_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_fps", TTR("View FPS")), VIEW_FPS);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_ENVIRONMENT), true);
view_menu->get_popup()->add_separator();
view_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_half_resolution", TTR("Half Resolution")), VIEW_HALF_RESOLUTION);
view_menu->get_popup()->add_separator();
view_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_audio_listener", TTR("Audio Listener")), VIEW_AUDIO_LISTENER);
view_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_audio_doppler", TTR("Enable Doppler")), VIEW_AUDIO_DOPPLER);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_GIZMOS), true);
view_menu->get_popup()->add_separator();
view_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_cinematic_preview", TTR("Cinematic Preview")), VIEW_CINEMATIC_PREVIEW);
view_menu->get_popup()->add_separator();
view_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/focus_origin"), VIEW_CENTER_TO_ORIGIN);
view_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/focus_selection"), VIEW_CENTER_TO_SELECTION);
view_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/align_transform_with_view"), VIEW_ALIGN_TRANSFORM_WITH_VIEW);
view_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/align_rotation_with_view"), VIEW_ALIGN_ROTATION_WITH_VIEW);
view_menu->get_popup()->connect("id_pressed", this, "_menu_option");
view_menu->set_disable_shortcuts(true);
if (OS::get_singleton()->get_current_video_driver() == OS::VIDEO_DRIVER_GLES2) {
// Alternate display modes only work when using the GLES3 renderer; make this explicit.
const int normal_idx = view_menu->get_popup()->get_item_index(VIEW_DISPLAY_NORMAL);
const int wireframe_idx = view_menu->get_popup()->get_item_index(VIEW_DISPLAY_WIREFRAME);
const int overdraw_idx = view_menu->get_popup()->get_item_index(VIEW_DISPLAY_OVERDRAW);
const int shadeless_idx = view_menu->get_popup()->get_item_index(VIEW_DISPLAY_SHADELESS);
const String unsupported_tooltip = TTR("Not available when using the GLES2 renderer.");
view_menu->get_popup()->set_item_disabled(normal_idx, true);
view_menu->get_popup()->set_item_tooltip(normal_idx, unsupported_tooltip);
view_menu->get_popup()->set_item_disabled(wireframe_idx, true);
view_menu->get_popup()->set_item_tooltip(wireframe_idx, unsupported_tooltip);
view_menu->get_popup()->set_item_disabled(overdraw_idx, true);
view_menu->get_popup()->set_item_tooltip(overdraw_idx, unsupported_tooltip);
view_menu->get_popup()->set_item_disabled(shadeless_idx, true);
view_menu->get_popup()->set_item_tooltip(shadeless_idx, unsupported_tooltip);
}
ED_SHORTCUT("spatial_editor/freelook_left", TTR("Freelook Left"), KEY_A);
ED_SHORTCUT("spatial_editor/freelook_right", TTR("Freelook Right"), KEY_D);
ED_SHORTCUT("spatial_editor/freelook_forward", TTR("Freelook Forward"), KEY_W);
ED_SHORTCUT("spatial_editor/freelook_backwards", TTR("Freelook Backwards"), KEY_S);
ED_SHORTCUT("spatial_editor/freelook_up", TTR("Freelook Up"), KEY_E);
ED_SHORTCUT("spatial_editor/freelook_down", TTR("Freelook Down"), KEY_Q);
ED_SHORTCUT("spatial_editor/freelook_speed_modifier", TTR("Freelook Speed Modifier"), KEY_SHIFT);
ED_SHORTCUT("spatial_editor/freelook_slow_modifier", TTR("Freelook Slow Modifier"), KEY_ALT);
preview_camera = memnew(CheckBox);
preview_camera->set_text(TTR("Preview"));
vbox->add_child(preview_camera);
preview_camera->set_h_size_flags(0);
preview_camera->hide();
preview_camera->connect("toggled", this, "_toggle_camera_preview");
previewing = NULL;
gizmo_scale = 1.0;
preview_node = NULL;
info_label = memnew(Label);
info_label->set_anchor_and_margin(MARGIN_LEFT, ANCHOR_END, -90 * EDSCALE);
info_label->set_anchor_and_margin(MARGIN_TOP, ANCHOR_END, -90 * EDSCALE);
info_label->set_anchor_and_margin(MARGIN_RIGHT, ANCHOR_END, -10 * EDSCALE);
info_label->set_anchor_and_margin(MARGIN_BOTTOM, ANCHOR_END, -10 * EDSCALE);
info_label->set_h_grow_direction(GROW_DIRECTION_BEGIN);
info_label->set_v_grow_direction(GROW_DIRECTION_BEGIN);
surface->add_child(info_label);
info_label->hide();
cinema_label = memnew(Label);
cinema_label->set_anchor_and_margin(MARGIN_TOP, ANCHOR_BEGIN, 10 * EDSCALE);
cinema_label->set_h_grow_direction(GROW_DIRECTION_END);
cinema_label->set_align(Label::ALIGN_CENTER);
surface->add_child(cinema_label);
cinema_label->set_text(TTR("Cinematic Preview"));
cinema_label->hide();
previewing_cinema = false;
locked_label = memnew(Label);
locked_label->set_anchor_and_margin(MARGIN_TOP, ANCHOR_END, -20 * EDSCALE);
locked_label->set_anchor_and_margin(MARGIN_BOTTOM, ANCHOR_END, -10 * EDSCALE);
locked_label->set_h_grow_direction(GROW_DIRECTION_END);
locked_label->set_v_grow_direction(GROW_DIRECTION_BEGIN);
locked_label->set_align(Label::ALIGN_CENTER);
surface->add_child(locked_label);
locked_label->set_text(TTR("View Rotation Locked"));
locked_label->hide();
top_right_vbox = memnew(VBoxContainer);
top_right_vbox->set_anchors_and_margins_preset(PRESET_TOP_RIGHT, PRESET_MODE_MINSIZE, 2.0 * EDSCALE);
top_right_vbox->set_h_grow_direction(GROW_DIRECTION_BEGIN);
rotation_control = memnew(ViewportRotationControl);
rotation_control->set_custom_minimum_size(Size2(80, 80) * EDSCALE);
rotation_control->set_h_size_flags(SIZE_SHRINK_END);
rotation_control->set_viewport(this);
top_right_vbox->add_child(rotation_control);
fps_label = memnew(Label);
fps_label->set_anchor_and_margin(MARGIN_LEFT, ANCHOR_END, -90 * EDSCALE);
fps_label->set_anchor_and_margin(MARGIN_TOP, ANCHOR_BEGIN, 10 * EDSCALE);
fps_label->set_anchor_and_margin(MARGIN_RIGHT, ANCHOR_END, -10 * EDSCALE);
fps_label->set_h_grow_direction(GROW_DIRECTION_BEGIN);
fps_label->set_tooltip(TTR("Note: The FPS value displayed is the editor's framerate.\nIt cannot be used as a reliable indication of in-game performance."));
fps_label->set_mouse_filter(MOUSE_FILTER_PASS); // Otherwise tooltip doesn't show.
top_right_vbox->add_child(fps_label);
fps_label->hide();
surface->add_child(top_right_vbox);
accept = NULL;
freelook_active = false;
freelook_speed = EditorSettings::get_singleton()->get("editors/3d/freelook/freelook_base_speed");
selection_menu = memnew(PopupMenu);
add_child(selection_menu);
selection_menu->set_custom_minimum_size(Size2(100, 0) * EDSCALE);
selection_menu->connect("id_pressed", this, "_selection_result_pressed");
selection_menu->connect("popup_hide", this, "_selection_menu_hide");
if (p_index == 0) {
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_AUDIO_LISTENER), true);
viewport->set_as_audio_listener(true);
}
name = "";
_update_name();
EditorSettings::get_singleton()->connect("settings_changed", this, "update_transform_gizmo_view");
}
//////////////////////////////////////////////////////////////
void SpatialEditorViewportContainer::_gui_input(const Ref<InputEvent> &p_event) {
Ref<InputEventMouseButton> mb = p_event;
if (mb.is_valid() && mb->get_button_index() == BUTTON_LEFT) {
if (mb->is_pressed()) {
Vector2 size = get_size();
int h_sep = get_constant("separation", "HSplitContainer");
int v_sep = get_constant("separation", "VSplitContainer");
int mid_w = size.width * ratio_h;
int mid_h = size.height * ratio_v;
dragging_h = mb->get_position().x > (mid_w - h_sep / 2) && mb->get_position().x < (mid_w + h_sep / 2);
dragging_v = mb->get_position().y > (mid_h - v_sep / 2) && mb->get_position().y < (mid_h + v_sep / 2);
drag_begin_pos = mb->get_position();
drag_begin_ratio.x = ratio_h;
drag_begin_ratio.y = ratio_v;
switch (view) {
case VIEW_USE_1_VIEWPORT: {
dragging_h = false;
dragging_v = false;
} break;
case VIEW_USE_2_VIEWPORTS: {
dragging_h = false;
} break;
case VIEW_USE_2_VIEWPORTS_ALT: {
dragging_v = false;
} break;
case VIEW_USE_3_VIEWPORTS:
case VIEW_USE_3_VIEWPORTS_ALT:
case VIEW_USE_4_VIEWPORTS: {
// Do nothing.
} break;
}
} else {
dragging_h = false;
dragging_v = false;
}
}
Ref<InputEventMouseMotion> mm = p_event;
if (mm.is_valid()) {
if (view == VIEW_USE_3_VIEWPORTS || view == VIEW_USE_3_VIEWPORTS_ALT || view == VIEW_USE_4_VIEWPORTS) {
Vector2 size = get_size();
int h_sep = get_constant("separation", "HSplitContainer");
int v_sep = get_constant("separation", "VSplitContainer");
int mid_w = size.width * ratio_h;
int mid_h = size.height * ratio_v;
bool was_hovering_h = hovering_h;
bool was_hovering_v = hovering_v;
hovering_h = mm->get_position().x > (mid_w - h_sep / 2) && mm->get_position().x < (mid_w + h_sep / 2);
hovering_v = mm->get_position().y > (mid_h - v_sep / 2) && mm->get_position().y < (mid_h + v_sep / 2);
if (was_hovering_h != hovering_h || was_hovering_v != hovering_v) {
update();
}
}
if (dragging_h) {
float new_ratio = drag_begin_ratio.x + (mm->get_position().x - drag_begin_pos.x) / get_size().width;
new_ratio = CLAMP(new_ratio, 40 / get_size().width, (get_size().width - 40) / get_size().width);
ratio_h = new_ratio;
queue_sort();
update();
}
if (dragging_v) {
float new_ratio = drag_begin_ratio.y + (mm->get_position().y - drag_begin_pos.y) / get_size().height;
new_ratio = CLAMP(new_ratio, 40 / get_size().height, (get_size().height - 40) / get_size().height);
ratio_v = new_ratio;
queue_sort();
update();
}
}
}
void SpatialEditorViewportContainer::_notification(int p_what) {
if (p_what == NOTIFICATION_MOUSE_ENTER || p_what == NOTIFICATION_MOUSE_EXIT) {
mouseover = (p_what == NOTIFICATION_MOUSE_ENTER);
update();
}
if (p_what == NOTIFICATION_DRAW && mouseover) {
Ref<Texture> h_grabber = get_icon("grabber", "HSplitContainer");
Ref<Texture> v_grabber = get_icon("grabber", "VSplitContainer");
Ref<Texture> hdiag_grabber = get_icon("GuiViewportHdiagsplitter", "EditorIcons");
Ref<Texture> vdiag_grabber = get_icon("GuiViewportVdiagsplitter", "EditorIcons");
Ref<Texture> vh_grabber = get_icon("GuiViewportVhsplitter", "EditorIcons");
Vector2 size = get_size();
int h_sep = get_constant("separation", "HSplitContainer");
int v_sep = get_constant("separation", "VSplitContainer");
int mid_w = size.width * ratio_h;
int mid_h = size.height * ratio_v;
int size_left = mid_w - h_sep / 2;
int size_bottom = size.height - mid_h - v_sep / 2;
switch (view) {
case VIEW_USE_1_VIEWPORT: {
// Nothing to show.
} break;
case VIEW_USE_2_VIEWPORTS: {
draw_texture(v_grabber, Vector2((size.width - v_grabber->get_width()) / 2, mid_h - v_grabber->get_height() / 2));
set_default_cursor_shape(CURSOR_VSPLIT);
} break;
case VIEW_USE_2_VIEWPORTS_ALT: {
draw_texture(h_grabber, Vector2(mid_w - h_grabber->get_width() / 2, (size.height - h_grabber->get_height()) / 2));
set_default_cursor_shape(CURSOR_HSPLIT);
} break;
case VIEW_USE_3_VIEWPORTS: {
if ((hovering_v && hovering_h && !dragging_v && !dragging_h) || (dragging_v && dragging_h)) {
draw_texture(hdiag_grabber, Vector2(mid_w - hdiag_grabber->get_width() / 2, mid_h - v_grabber->get_height() / 4));
set_default_cursor_shape(CURSOR_DRAG);
} else if ((hovering_v && !dragging_h) || dragging_v) {
draw_texture(v_grabber, Vector2((size.width - v_grabber->get_width()) / 2, mid_h - v_grabber->get_height() / 2));
set_default_cursor_shape(CURSOR_VSPLIT);
} else if (hovering_h || dragging_h) {
draw_texture(h_grabber, Vector2(mid_w - h_grabber->get_width() / 2, mid_h + v_grabber->get_height() / 2 + (size_bottom - h_grabber->get_height()) / 2));
set_default_cursor_shape(CURSOR_HSPLIT);
}
} break;
case VIEW_USE_3_VIEWPORTS_ALT: {
if ((hovering_v && hovering_h && !dragging_v && !dragging_h) || (dragging_v && dragging_h)) {
draw_texture(vdiag_grabber, Vector2(mid_w - vdiag_grabber->get_width() + v_grabber->get_height() / 4, mid_h - vdiag_grabber->get_height() / 2));
set_default_cursor_shape(CURSOR_DRAG);
} else if ((hovering_v && !dragging_h) || dragging_v) {
draw_texture(v_grabber, Vector2((size_left - v_grabber->get_width()) / 2, mid_h - v_grabber->get_height() / 2));
set_default_cursor_shape(CURSOR_VSPLIT);
} else if (hovering_h || dragging_h) {
draw_texture(h_grabber, Vector2(mid_w - h_grabber->get_width() / 2, (size.height - h_grabber->get_height()) / 2));
set_default_cursor_shape(CURSOR_HSPLIT);
}
} break;
case VIEW_USE_4_VIEWPORTS: {
Vector2 half(mid_w, mid_h);
if ((hovering_v && hovering_h && !dragging_v && !dragging_h) || (dragging_v && dragging_h)) {
draw_texture(vh_grabber, half - vh_grabber->get_size() / 2.0);
set_default_cursor_shape(CURSOR_DRAG);
} else if ((hovering_v && !dragging_h) || dragging_v) {
draw_texture(v_grabber, half - v_grabber->get_size() / 2.0);
set_default_cursor_shape(CURSOR_VSPLIT);
} else if (hovering_h || dragging_h) {
draw_texture(h_grabber, half - h_grabber->get_size() / 2.0);
set_default_cursor_shape(CURSOR_HSPLIT);
}
} break;
}
}
if (p_what == NOTIFICATION_SORT_CHILDREN) {
SpatialEditorViewport *viewports[4];
int vc = 0;
for (int i = 0; i < get_child_count(); i++) {
viewports[vc] = Object::cast_to<SpatialEditorViewport>(get_child(i));
if (viewports[vc]) {
vc++;
}
}
ERR_FAIL_COND(vc != 4);
Size2 size = get_size();
if (size.x < 10 || size.y < 10) {
for (int i = 0; i < 4; i++) {
viewports[i]->hide();
}
return;
}
int h_sep = get_constant("separation", "HSplitContainer");
int v_sep = get_constant("separation", "VSplitContainer");
int mid_w = size.width * ratio_h;
int mid_h = size.height * ratio_v;
int size_left = mid_w - h_sep / 2;
int size_right = size.width - mid_w - h_sep / 2;
int size_top = mid_h - v_sep / 2;
int size_bottom = size.height - mid_h - v_sep / 2;
switch (view) {
case VIEW_USE_1_VIEWPORT: {
viewports[0]->show();
for (int i = 1; i < 4; i++) {
viewports[i]->hide();
}
fit_child_in_rect(viewports[0], Rect2(Vector2(), size));
} break;
case VIEW_USE_2_VIEWPORTS: {
for (int i = 0; i < 4; i++) {
if (i == 1 || i == 3)
viewports[i]->hide();
else
viewports[i]->show();
}
fit_child_in_rect(viewports[0], Rect2(Vector2(), Vector2(size.width, size_top)));
fit_child_in_rect(viewports[2], Rect2(Vector2(0, mid_h + v_sep / 2), Vector2(size.width, size_bottom)));
} break;
case VIEW_USE_2_VIEWPORTS_ALT: {
for (int i = 0; i < 4; i++) {
if (i == 1 || i == 3)
viewports[i]->hide();
else
viewports[i]->show();
}
fit_child_in_rect(viewports[0], Rect2(Vector2(), Vector2(size_left, size.height)));
fit_child_in_rect(viewports[2], Rect2(Vector2(mid_w + h_sep / 2, 0), Vector2(size_right, size.height)));
} break;
case VIEW_USE_3_VIEWPORTS: {
for (int i = 0; i < 4; i++) {
if (i == 1)
viewports[i]->hide();
else
viewports[i]->show();
}
fit_child_in_rect(viewports[0], Rect2(Vector2(), Vector2(size.width, size_top)));
fit_child_in_rect(viewports[2], Rect2(Vector2(0, mid_h + v_sep / 2), Vector2(size_left, size_bottom)));
fit_child_in_rect(viewports[3], Rect2(Vector2(mid_w + h_sep / 2, mid_h + v_sep / 2), Vector2(size_right, size_bottom)));
} break;
case VIEW_USE_3_VIEWPORTS_ALT: {
for (int i = 0; i < 4; i++) {
if (i == 1)
viewports[i]->hide();
else
viewports[i]->show();
}
fit_child_in_rect(viewports[0], Rect2(Vector2(), Vector2(size_left, size_top)));
fit_child_in_rect(viewports[2], Rect2(Vector2(0, mid_h + v_sep / 2), Vector2(size_left, size_bottom)));
fit_child_in_rect(viewports[3], Rect2(Vector2(mid_w + h_sep / 2, 0), Vector2(size_right, size.height)));
} break;
case VIEW_USE_4_VIEWPORTS: {
for (int i = 0; i < 4; i++) {
viewports[i]->show();
}
fit_child_in_rect(viewports[0], Rect2(Vector2(), Vector2(size_left, size_top)));
fit_child_in_rect(viewports[1], Rect2(Vector2(mid_w + h_sep / 2, 0), Vector2(size_right, size_top)));
fit_child_in_rect(viewports[2], Rect2(Vector2(0, mid_h + v_sep / 2), Vector2(size_left, size_bottom)));
fit_child_in_rect(viewports[3], Rect2(Vector2(mid_w + h_sep / 2, mid_h + v_sep / 2), Vector2(size_right, size_bottom)));
} break;
}
}
}
void SpatialEditorViewportContainer::set_view(View p_view) {
view = p_view;
queue_sort();
}
SpatialEditorViewportContainer::View SpatialEditorViewportContainer::get_view() {
return view;
}
void SpatialEditorViewportContainer::_bind_methods() {
ClassDB::bind_method("_gui_input", &SpatialEditorViewportContainer::_gui_input);
}
SpatialEditorViewportContainer::SpatialEditorViewportContainer() {
set_clip_contents(true);
view = VIEW_USE_1_VIEWPORT;
mouseover = false;
ratio_h = 0.5;
ratio_v = 0.5;
hovering_v = false;
hovering_h = false;
dragging_v = false;
dragging_h = false;
}
///////////////////////////////////////////////////////////////////
SpatialEditor *SpatialEditor::singleton = NULL;
SpatialEditorSelectedItem::~SpatialEditorSelectedItem() {
if (sbox_instance.is_valid())
VisualServer::get_singleton()->free(sbox_instance);
}
void SpatialEditor::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]);
rotate_gizmo[i]->surface_set_material(0, (i + 3) == p_axis ? gizmo_color_hl[i] : 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]);
}
}
void SpatialEditor::update_transform_gizmo() {
List<Node *> &selection = editor_selection->get_selected_node_list();
AABB center;
bool first = true;
Basis gizmo_basis;
bool local_gizmo_coords = are_local_coords_enabled();
for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
Spatial *sp = Object::cast_to<Spatial>(E->get());
if (!sp)
continue;
SpatialEditorSelectedItem *se = editor_selection->get_node_editor_data<SpatialEditorSelectedItem>(sp);
if (!se)
continue;
Transform xf = se->sp->get_global_gizmo_transform();
if (first) {
center.position = xf.origin;
first = false;
if (local_gizmo_coords) {
gizmo_basis = xf.basis;
gizmo_basis.orthonormalize();
}
} else {
center.expand_to(xf.origin);
gizmo_basis = Basis();
}
}
Vector3 pcenter = center.position + center.size * 0.5;
gizmo.visible = !first;
gizmo.transform.origin = pcenter;
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) {
Spatial *spatial_node = Object::cast_to<Spatial>(p_node->get_child(i));
if (spatial_node) {
spatial_node->update_gizmo();
}
_update_all_gizmos(p_node->get_child(i));
}
}
void SpatialEditor::update_all_gizmos(Node *p_node) {
if (!p_node) {
p_node = SceneTree::get_singleton()->get_root();
}
_update_all_gizmos(p_node);
}
Object *SpatialEditor::_get_editor_data(Object *p_what) {
Spatial *sp = Object::cast_to<Spatial>(p_what);
if (!sp)
return NULL;
SpatialEditorSelectedItem *si = memnew(SpatialEditorSelectedItem);
si->sp = sp;
si->sbox_instance = VisualServer::get_singleton()->instance_create2(selection_box->get_rid(), sp->get_world()->get_scenario());
VS::get_singleton()->instance_geometry_set_cast_shadows_setting(si->sbox_instance, VS::SHADOW_CASTING_SETTING_OFF);
return si;
}
void SpatialEditor::_generate_selection_box() {
AABB aabb(Vector3(), Vector3(1, 1, 1));
aabb.grow_by(aabb.get_longest_axis_size() / 20.0);
Ref<SurfaceTool> st = memnew(SurfaceTool);
st->begin(Mesh::PRIMITIVE_LINES);
for (int i = 0; i < 12; i++) {
Vector3 a, b;
aabb.get_edge(i, a, b);
st->add_color(Color(1.0, 1.0, 0.8, 0.8));
st->add_vertex(a);
st->add_color(Color(1.0, 1.0, 0.8, 0.4));
st->add_vertex(a.linear_interpolate(b, 0.2));
st->add_color(Color(1.0, 1.0, 0.8, 0.4));
st->add_vertex(a.linear_interpolate(b, 0.8));
st->add_color(Color(1.0, 1.0, 0.8, 0.8));
st->add_vertex(b);
}
Ref<SpatialMaterial> mat = memnew(SpatialMaterial);
mat->set_flag(SpatialMaterial::FLAG_UNSHADED, true);
mat->set_albedo(Color(1, 1, 1));
mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true);
mat->set_flag(SpatialMaterial::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
mat->set_flag(SpatialMaterial::FLAG_SRGB_VERTEX_COLOR, true);
st->set_material(mat);
selection_box = st->commit();
}
Dictionary SpatialEditor::get_state() const {
Dictionary d;
d["snap_enabled"] = snap_enabled;
d["translate_snap"] = get_translate_snap();
d["rotate_snap"] = get_rotate_snap();
d["scale_snap"] = get_scale_snap();
d["local_coords"] = tool_option_button[TOOL_OPT_LOCAL_COORDS]->is_pressed();
int vc = 0;
if (view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT)))
vc = 1;
else if (view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS)))
vc = 2;
else if (view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS)))
vc = 3;
else if (view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS)))
vc = 4;
else if (view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT)))
vc = 5;
else if (view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT)))
vc = 6;
d["viewport_mode"] = vc;
Array vpdata;
for (int i = 0; i < 4; i++) {
vpdata.push_back(viewports[i]->get_state());
}
d["viewports"] = vpdata;
d["show_grid"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_GRID));
d["show_origin"] = view_menu->get_popup()->is_item_checked(view_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_name();
gizmos_status[name] = state;
}
d["gizmos_status"] = gizmos_status;
return d;
}
void SpatialEditor::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("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("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("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("zfar"))
settings_zfar->set_value(float(d["zfar"]));
if (d.has("znear"))
settings_znear->set_value(float(d["znear"]));
if (d.has("fov"))
settings_fov->set_value(float(d["fov"]));
if (d.has("show_grid")) {
bool use = d["show_grid"];
if (use != view_menu->get_popup()->is_item_checked(view_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_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_ORIGIN))) {
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_ORIGIN), use);
VisualServer::get_singleton()->instance_set_visible(origin_instance, use);
}
}
if (d.has("gizmos_status")) {
Dictionary gizmos_status = d["gizmos_status"];
List<Variant> keys;
gizmos_status.get_key_list(&keys);
for (int j = 0; j < gizmo_plugins_by_name.size(); ++j) {
if (!gizmo_plugins_by_name[j]->can_be_hidden()) continue;
int state = EditorSpatialGizmoPlugin::VISIBLE;
for (int i = 0; i < keys.size(); i++) {
if (gizmo_plugins_by_name.write[j]->get_name() == keys[i]) {
state = gizmos_status[keys[i]];
break;
}
}
gizmo_plugins_by_name.write[j]->set_state(state);
}
_update_gizmos_menu();
}
}
void SpatialEditor::edit(Spatial *p_spatial) {
if (p_spatial != selected) {
if (selected) {
Ref<EditorSpatialGizmo> seg = selected->get_gizmo();
if (seg.is_valid()) {
seg->set_selected(false);
selected->update_gizmo();
}
}
selected = p_spatial;
over_gizmo_handle = -1;
if (selected) {
Ref<EditorSpatialGizmo> seg = selected->get_gizmo();
if (seg.is_valid()) {
seg->set_selected(true);
selected->update_gizmo();
}
}
}
}
void SpatialEditor::_snap_changed() {
snap_translate_value = snap_translate->get_text().to_double();
snap_rotate_value = snap_rotate->get_text().to_double();
snap_scale_value = snap_scale->get_text().to_double();
}
void SpatialEditor::_snap_update() {
snap_translate->set_text(String::num(snap_translate_value));
snap_rotate->set_text(String::num(snap_rotate_value));
snap_scale->set_text(String::num(snap_scale_value));
}
void SpatialEditor::_xform_dialog_action() {
Transform t;
//translation
Vector3 scale;
Vector3 rotate;
Vector3 translate;
for (int i = 0; i < 3; i++) {
translate[i] = xform_translate[i]->get_text().to_double();
rotate[i] = Math::deg2rad(xform_rotate[i]->get_text().to_double());
scale[i] = xform_scale[i]->get_text().to_double();
}
t.basis.scale(scale);
t.basis.rotate(rotate);
t.origin = translate;
undo_redo->create_action(TTR("XForm Dialog"));
List<Node *> &selection = editor_selection->get_selected_node_list();
for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
Spatial *sp = Object::cast_to<Spatial>(E->get());
if (!sp)
continue;
SpatialEditorSelectedItem *se = editor_selection->get_node_editor_data<SpatialEditorSelectedItem>(sp);
if (!se)
continue;
bool post = xform_type->get_selected() > 0;
Transform tr = sp->get_global_gizmo_transform();
if (post)
tr = tr * t;
else {
tr.basis = t.basis * tr.basis;
tr.origin += t.origin;
}
undo_redo->add_do_method(sp, "set_global_transform", tr);
undo_redo->add_undo_method(sp, "set_global_transform", sp->get_global_gizmo_transform());
}
undo_redo->commit_action();
}
void SpatialEditor::_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_OVERRIDE_CAMERA: {
ScriptEditorDebugger *const debugger = ScriptEditor::get_singleton()->get_debugger();
if (pressed) {
using Override = ScriptEditorDebugger::CameraOverride;
debugger->set_camera_override((Override)(Override::OVERRIDE_3D_1 + camera_override_viewport_id));
} else {
debugger->set_camera_override(ScriptEditorDebugger::OVERRIDE_NONE);
}
} break;
}
}
void SpatialEditor::_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 EditorSpatialGizmoPlugin::VISIBLE:
gizmos_menu->set_item_icon(idx, view_menu->get_popup()->get_icon("visibility_visible"));
break;
case EditorSpatialGizmoPlugin::ON_TOP:
gizmos_menu->set_item_icon(idx, view_menu->get_popup()->get_icon("visibility_xray"));
break;
case EditorSpatialGizmoPlugin::HIDDEN:
gizmos_menu->set_item_icon(idx, view_menu->get_popup()->get_icon("visibility_hidden"));
break;
}
gizmo_plugins_by_name.write[p_option]->set_state(state);
update_all_gizmos();
}
void SpatialEditor::_update_camera_override_button(bool p_game_running) {
Button *const button = tool_option_button[TOOL_OPT_OVERRIDE_CAMERA];
if (p_game_running) {
button->set_disabled(false);
button->set_tooltip(TTR("Game Camera Override\nNo game instance running."));
} else {
button->set_disabled(true);
button->set_pressed(false);
button->set_tooltip(TTR("Game Camera Override\nOverrides game camera with editor viewport camera."));
}
}
void SpatialEditor::_update_camera_override_viewport(Object *p_viewport) {
SpatialEditorViewport *current_viewport = Object::cast_to<SpatialEditorViewport>(p_viewport);
if (!current_viewport)
return;
ScriptEditorDebugger *const debugger = ScriptEditor::get_singleton()->get_debugger();
camera_override_viewport_id = current_viewport->index;
if (debugger->get_camera_override() >= ScriptEditorDebugger::OVERRIDE_3D_1) {
using Override = ScriptEditorDebugger::CameraOverride;
debugger->set_camera_override((Override)(Override::OVERRIDE_3D_1 + camera_override_viewport_id));
}
}
void SpatialEditor::_menu_item_pressed(int p_option) {
switch (p_option) {
case MENU_TOOL_SELECT:
case MENU_TOOL_MOVE:
case MENU_TOOL_ROTATE:
case MENU_TOOL_SCALE:
case MENU_TOOL_LIST_SELECT: {
for (int i = 0; i < TOOL_MAX; i++)
tool_button[i]->set_pressed(i == p_option);
tool_mode = (ToolMode)p_option;
update_transform_gizmo();
} break;
case MENU_TRANSFORM_CONFIGURE_SNAP: {
snap_dialog->popup_centered(Size2(200, 180));
} 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(SpatialEditorViewportContainer::VIEW_USE_1_VIEWPORT);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), true);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), false);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS), false);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS), false);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT), false);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT), false);
} break;
case MENU_VIEW_USE_2_VIEWPORTS: {
viewport_base->set_view(SpatialEditorViewportContainer::VIEW_USE_2_VIEWPORTS);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), false);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), true);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS), false);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS), false);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT), false);
view_menu->get_popup()->set_item_checked(view_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(SpatialEditorViewportContainer::VIEW_USE_2_VIEWPORTS_ALT);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), false);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), false);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS), false);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS), false);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT), true);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT), false);
} break;
case MENU_VIEW_USE_3_VIEWPORTS: {
viewport_base->set_view(SpatialEditorViewportContainer::VIEW_USE_3_VIEWPORTS);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), false);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), false);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS), true);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS), false);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT), false);
view_menu->get_popup()->set_item_checked(view_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(SpatialEditorViewportContainer::VIEW_USE_3_VIEWPORTS_ALT);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), false);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), false);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS), false);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS), false);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT), false);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT), true);
} break;
case MENU_VIEW_USE_4_VIEWPORTS: {
viewport_base->set_view(SpatialEditorViewportContainer::VIEW_USE_4_VIEWPORTS);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), false);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), false);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS), false);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS), true);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT), false);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT), false);
} break;
case MENU_VIEW_ORIGIN: {
bool is_checked = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(p_option));
origin_enabled = !is_checked;
VisualServer::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_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(p_option), origin_enabled);
} break;
case MENU_VIEW_GRID: {
bool is_checked = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(p_option));
grid_enabled = !is_checked;
for (int i = 0; i < 3; ++i) {
if (grid_enable[i]) {
VisualServer::get_singleton()->instance_set_visible(grid_instance[i], grid_enabled);
grid_visible[i] = grid_enabled;
}
}
view_menu->get_popup()->set_item_checked(view_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"));
List<Node *> &selection = editor_selection->get_selected_node_list();
for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
Spatial *spatial = Object::cast_to<Spatial>(E->get());
if (!spatial || !spatial->is_visible_in_tree())
continue;
if (spatial->get_viewport() != EditorNode::get_singleton()->get_scene_root())
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", Variant());
undo_redo->add_undo_method(this, "_refresh_menu_icons", Variant());
undo_redo->commit_action();
} break;
case MENU_UNLOCK_SELECTED: {
undo_redo->create_action(TTR("Unlock Selected"));
List<Node *> &selection = editor_selection->get_selected_node_list();
for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
Spatial *spatial = Object::cast_to<Spatial>(E->get());
if (!spatial || !spatial->is_visible_in_tree())
continue;
if (spatial->get_viewport() != EditorNode::get_singleton()->get_scene_root())
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", Variant());
undo_redo->add_undo_method(this, "_refresh_menu_icons", Variant());
undo_redo->commit_action();
} break;
case MENU_GROUP_SELECTED: {
undo_redo->create_action(TTR("Group Selected"));
List<Node *> &selection = editor_selection->get_selected_node_list();
for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
Spatial *spatial = Object::cast_to<Spatial>(E->get());
if (!spatial || !spatial->is_visible_in_tree())
continue;
if (spatial->get_viewport() != EditorNode::get_singleton()->get_scene_root())
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", Variant());
undo_redo->add_undo_method(this, "_refresh_menu_icons", Variant());
undo_redo->commit_action();
} break;
case MENU_UNGROUP_SELECTED: {
undo_redo->create_action(TTR("Ungroup Selected"));
List<Node *> &selection = editor_selection->get_selected_node_list();
for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
Spatial *spatial = Object::cast_to<Spatial>(E->get());
if (!spatial || !spatial->is_visible_in_tree())
continue;
if (spatial->get_viewport() != EditorNode::get_singleton()->get_scene_root())
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", Variant());
undo_redo->add_undo_method(this, "_refresh_menu_icons", Variant());
undo_redo->commit_action();
} break;
}
}
void SpatialEditor::_init_indicators() {
{
origin_enabled = true;
grid_enabled = true;
indicator_mat.instance();
indicator_mat->set_flag(SpatialMaterial::FLAG_UNSHADED, true);
indicator_mat->set_flag(SpatialMaterial::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
indicator_mat->set_flag(SpatialMaterial::FLAG_SRGB_VERTEX_COLOR, true);
Vector<Color> origin_colors;
Vector<Vector3> origin_points;
for (int i = 0; i < 3; i++) {
Vector3 axis;
axis[i] = 1;
Color origin_color;
switch (i) {
case 0:
origin_color = get_color("axis_x_color", "Editor");
break;
case 1:
origin_color = get_color("axis_y_color", "Editor");
break;
case 2:
origin_color = get_color("axis_z_color", "Editor");
break;
default:
origin_color = Color();
break;
}
grid_enable[i] = false;
grid_visible[i] = false;
origin_colors.push_back(origin_color);
origin_colors.push_back(origin_color);
origin_points.push_back(axis * 4096);
origin_points.push_back(axis * -4096);
}
grid_enable[1] = true;
grid_visible[1] = true;
_init_grid();
origin = VisualServer::get_singleton()->mesh_create();
Array d;
d.resize(VS::ARRAY_MAX);
d[VisualServer::ARRAY_VERTEX] = origin_points;
d[VisualServer::ARRAY_COLOR] = origin_colors;
VisualServer::get_singleton()->mesh_add_surface_from_arrays(origin, VisualServer::PRIMITIVE_LINES, d);
VisualServer::get_singleton()->mesh_surface_set_material(origin, 0, indicator_mat->get_rid());
origin_instance = VisualServer::get_singleton()->instance_create2(origin, get_tree()->get_root()->get_world()->get_scenario());
VS::get_singleton()->instance_set_layer_mask(origin_instance, 1 << SpatialEditorViewport::GIZMO_GRID_LAYER);
VisualServer::get_singleton()->instance_geometry_set_cast_shadows_setting(origin_instance, VS::SHADOW_CASTING_SETTING_OFF);
}
{
//move gizmo
for (int i = 0; i < 3; i++) {
Color col;
switch (i) {
case 0:
col = get_color("axis_x_color", "Editor");
break;
case 1:
col = get_color("axis_y_color", "Editor");
break;
case 2:
col = get_color("axis_z_color", "Editor");
break;
default:
col = Color();
break;
}
col.a = EditorSettings::get_singleton()->get("editors/3d/manipulator_gizmo_opacity");
move_gizmo[i] = Ref<ArrayMesh>(memnew(ArrayMesh));
move_plane_gizmo[i] = Ref<ArrayMesh>(memnew(ArrayMesh));
rotate_gizmo[i] = Ref<ArrayMesh>(memnew(ArrayMesh));
scale_gizmo[i] = Ref<ArrayMesh>(memnew(ArrayMesh));
scale_plane_gizmo[i] = Ref<ArrayMesh>(memnew(ArrayMesh));
Ref<SpatialMaterial> mat = memnew(SpatialMaterial);
mat->set_flag(SpatialMaterial::FLAG_UNSHADED, true);
mat->set_on_top_of_alpha();
mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true);
mat->set_albedo(col);
gizmo_color[i] = mat;
Ref<SpatialMaterial> mat_hl = mat->duplicate();
mat_hl->set_albedo(Color(col.r, col.g, col.b, 1.0));
gizmo_color_hl[i] = mat_hl;
Vector3 ivec;
ivec[i] = 1;
Vector3 nivec;
nivec[(i + 1) % 3] = 1;
nivec[(i + 2) % 3] = 1;
Vector3 ivec2;
ivec2[(i + 1) % 3] = 1;
Vector3 ivec3;
ivec3[(i + 2) % 3] = 1;
//translate
{
Ref<SurfaceTool> surftool = memnew(SurfaceTool);
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;
for (int k = 0; k < arrow_sides; k++) {
Basis ma(ivec, Math_PI * 2 * float(k) / arrow_sides);
Basis mb(ivec, Math_PI * 2 * float(k + 1) / arrow_sides);
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 = memnew(SurfaceTool);
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<SpatialMaterial> plane_mat = memnew(SpatialMaterial);
plane_mat->set_flag(SpatialMaterial::FLAG_UNSHADED, true);
plane_mat->set_on_top_of_alpha();
plane_mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true);
plane_mat->set_cull_mode(SpatialMaterial::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<SpatialMaterial> plane_mat_hl = plane_mat->duplicate();
plane_mat_hl->set_albedo(Color(col.r, col.g, col.b, 1.0));
plane_gizmo_color_hl[i] = plane_mat_hl; // needed, so we can draw planes from both sides
}
// Rotate
{
Ref<SurfaceTool> surftool = memnew(SurfaceTool);
surftool->begin(Mesh::PRIMITIVE_TRIANGLES);
Vector3 circle[5] = {
ivec * 0.02 + ivec2 * 0.02 + ivec2 * GIZMO_CIRCLE_SIZE,
ivec * -0.02 + ivec2 * 0.02 + ivec2 * GIZMO_CIRCLE_SIZE,
ivec * -0.02 + ivec2 * -0.02 + ivec2 * GIZMO_CIRCLE_SIZE,
ivec * 0.02 + ivec2 * -0.02 + ivec2 * GIZMO_CIRCLE_SIZE,
ivec * 0.02 + ivec2 * 0.02 + ivec2 * GIZMO_CIRCLE_SIZE,
};
for (int k = 0; k < 64; k++) {
Basis ma(ivec, Math_PI * 2 * float(k) / 64);
Basis mb(ivec, Math_PI * 2 * float(k + 1) / 64);
for (int j = 0; j < 4; j++) {
Vector3 points[4] = {
ma.xform(circle[j]),
mb.xform(circle[j]),
mb.xform(circle[j + 1]),
ma.xform(circle[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(rotate_gizmo[i]);
}
// Scale
{
Ref<SurfaceTool> surftool = memnew(SurfaceTool);
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;
for (int k = 0; k < 4; k++) {
Basis ma(ivec, Math_PI * 2 * float(k) / arrow_sides);
Basis mb(ivec, Math_PI * 2 * float(k + 1) / arrow_sides);
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 = memnew(SurfaceTool);
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<SpatialMaterial> plane_mat = memnew(SpatialMaterial);
plane_mat->set_flag(SpatialMaterial::FLAG_UNSHADED, true);
plane_mat->set_on_top_of_alpha();
plane_mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true);
plane_mat->set_cull_mode(SpatialMaterial::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<SpatialMaterial> plane_mat_hl = plane_mat->duplicate();
plane_mat_hl->set_albedo(Color(col.r, col.g, col.b, 1.0));
plane_gizmo_color_hl[i] = plane_mat_hl; // needed, so we can draw planes from both sides
}
}
}
_generate_selection_box();
}
void SpatialEditor::_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_name();
const int plugin_state = gizmo_plugins_by_name[i]->get_state();
gizmos_menu->add_multistate_item(TTR(plugin_name), 3, plugin_state, i);
const int idx = gizmos_menu->get_item_index(i);
switch (plugin_state) {
case EditorSpatialGizmoPlugin::VISIBLE:
gizmos_menu->set_item_icon(idx, gizmos_menu->get_icon("visibility_visible"));
break;
case EditorSpatialGizmoPlugin::ON_TOP:
gizmos_menu->set_item_icon(idx, gizmos_menu->get_icon("visibility_xray"));
break;
case EditorSpatialGizmoPlugin::HIDDEN:
gizmos_menu->set_item_icon(idx, gizmos_menu->get_icon("visibility_hidden"));
break;
}
}
}
void SpatialEditor::_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 EditorSpatialGizmoPlugin::VISIBLE:
gizmos_menu->set_item_icon(idx, gizmos_menu->get_icon("visibility_visible"));
break;
case EditorSpatialGizmoPlugin::ON_TOP:
gizmos_menu->set_item_icon(idx, gizmos_menu->get_icon("visibility_xray"));
break;
case EditorSpatialGizmoPlugin::HIDDEN:
gizmos_menu->set_item_icon(idx, gizmos_menu->get_icon("visibility_hidden"));
break;
}
}
}
void SpatialEditor::_init_grid() {
PoolVector<Color> grid_colors[3];
PoolVector<Vector3> grid_points[3];
Color primary_grid_color = EditorSettings::get_singleton()->get("editors/3d/primary_grid_color");
Color secondary_grid_color = EditorSettings::get_singleton()->get("editors/3d/secondary_grid_color");
int grid_size = EditorSettings::get_singleton()->get("editors/3d/grid_size");
int primary_grid_steps = EditorSettings::get_singleton()->get("editors/3d/primary_grid_steps");
for (int i = 0; i < 3; i++) {
Vector3 axis;
axis[i] = 1;
Vector3 axis_n1;
axis_n1[(i + 1) % 3] = 1;
Vector3 axis_n2;
axis_n2[(i + 2) % 3] = 1;
for (int j = -grid_size; j <= grid_size; j++) {
Vector3 p1 = axis_n1 * j + axis_n2 * -grid_size;
Vector3 p1_dest = p1 * (-axis_n2 + axis_n1);
Vector3 p2 = axis_n2 * j + axis_n1 * -grid_size;
Vector3 p2_dest = p2 * (-axis_n1 + axis_n2);
Color line_color = secondary_grid_color;
if (origin_enabled && j == 0) {
// Don't draw the center lines of the grid if the origin is enabled
// The origin would overlap the grid lines in this case, causing flickering
continue;
} else if (j % primary_grid_steps == 0) {
line_color = primary_grid_color;
}
grid_points[i].push_back(p1);
grid_points[i].push_back(p1_dest);
grid_colors[i].push_back(line_color);
grid_colors[i].push_back(line_color);
grid_points[i].push_back(p2);
grid_points[i].push_back(p2_dest);
grid_colors[i].push_back(line_color);
grid_colors[i].push_back(line_color);
}
grid[i] = VisualServer::get_singleton()->mesh_create();
Array d;
d.resize(VS::ARRAY_MAX);
d[VisualServer::ARRAY_VERTEX] = grid_points[i];
d[VisualServer::ARRAY_COLOR] = grid_colors[i];
VisualServer::get_singleton()->mesh_add_surface_from_arrays(grid[i], VisualServer::PRIMITIVE_LINES, d);
VisualServer::get_singleton()->mesh_surface_set_material(grid[i], 0, indicator_mat->get_rid());
grid_instance[i] = VisualServer::get_singleton()->instance_create2(grid[i], get_tree()->get_root()->get_world()->get_scenario());
VisualServer::get_singleton()->instance_set_visible(grid_instance[i], grid_visible[i]);
VisualServer::get_singleton()->instance_geometry_set_cast_shadows_setting(grid_instance[i], VS::SHADOW_CASTING_SETTING_OFF);
VS::get_singleton()->instance_set_layer_mask(grid_instance[i], 1 << SpatialEditorViewport::GIZMO_GRID_LAYER);
}
}
void SpatialEditor::_finish_indicators() {
VisualServer::get_singleton()->free(origin_instance);
VisualServer::get_singleton()->free(origin);
_finish_grid();
}
void SpatialEditor::_finish_grid() {
for (int i = 0; i < 3; i++) {
VisualServer::get_singleton()->free(grid_instance[i]);
VisualServer::get_singleton()->free(grid[i]);
}
}
bool SpatialEditor::is_any_freelook_active() const {
for (unsigned int i = 0; i < VIEWPORTS_COUNT; ++i) {
if (viewports[i]->is_freelook_active())
return true;
}
return false;
}
void SpatialEditor::_refresh_menu_icons() {
bool all_locked = true;
bool all_grouped = true;
List<Node *> &selection = editor_selection->get_selected_node_list();
if (selection.empty()) {
all_locked = false;
all_grouped = false;
} else {
for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
if (Object::cast_to<Spatial>(E->get()) && !Object::cast_to<Spatial>(E->get())->has_meta("_edit_lock_")) {
all_locked = false;
break;
}
}
for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
if (Object::cast_to<Spatial>(E->get()) && !Object::cast_to<Spatial>(E->get())->has_meta("_edit_group_")) {
all_grouped = false;
break;
}
}
}
tool_button[TOOL_LOCK_SELECTED]->set_visible(!all_locked);
tool_button[TOOL_LOCK_SELECTED]->set_disabled(selection.empty());
tool_button[TOOL_UNLOCK_SELECTED]->set_visible(all_locked);
tool_button[TOOL_GROUP_SELECTED]->set_visible(!all_grouped);
tool_button[TOOL_GROUP_SELECTED]->set_disabled(selection.empty());
tool_button[TOOL_UNGROUP_SELECTED]->set_visible(all_grouped);
}
template <typename T>
Set<T *> _get_child_nodes(Node *parent_node) {
Set<T *> nodes = Set<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);
Set<T *> child_nodes = _get_child_nodes<T>(child_node);
for (typename Set<T *>::Element *I = child_nodes.front(); I; I = I->next()) {
nodes.insert(I->get());
}
}
return nodes;
}
Set<RID> _get_physics_bodies_rid(Node *node) {
Set<RID> rids = Set<RID>();
PhysicsBody *pb = Node::cast_to<PhysicsBody>(node);
if (pb) {
rids.insert(pb->get_rid());
}
Set<PhysicsBody *> child_nodes = _get_child_nodes<PhysicsBody>(node);
for (Set<PhysicsBody *>::Element *I = child_nodes.front(); I; I = I->next()) {
rids.insert(I->get()->get_rid());
}
return rids;
}
void SpatialEditor::snap_selected_nodes_to_floor() {
List<Node *> &selection = editor_selection->get_selected_node_list();
Dictionary snap_data;
for (List<Node *>::Element *E = selection.front(); E; E = E->next()) {
Spatial *sp = Object::cast_to<Spatial>(E->get());
if (sp) {
Vector3 from = Vector3();
Vector3 position_offset = Vector3();
// Priorities for snapping to floor are CollisionShapes, VisualInstances and then origin
Set<VisualInstance *> vi = _get_child_nodes<VisualInstance>(sp);
Set<CollisionShape *> cs = _get_child_nodes<CollisionShape>(sp);
if (cs.size()) {
AABB aabb = sp->get_global_transform().xform(cs.front()->get()->get_shape()->get_debug_mesh()->get_aabb());
for (Set<CollisionShape *>::Element *I = cs.front(); I; I = I->next()) {
aabb.merge_with(sp->get_global_transform().xform(I->get()->get_shape()->get_debug_mesh()->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 (vi.size()) {
AABB aabb = vi.front()->get()->get_transformed_aabb();
for (Set<VisualInstance *>::Element *I = vi.front(); I; I = I->next()) {
aabb.merge_with(I->get()->get_transformed_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 {
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, 0.2, 0.0);
Dictionary d;
d["from"] = from;
d["position_offset"] = position_offset;
snap_data[sp] = d;
}
}
PhysicsDirectSpaceState *ss = get_tree()->get_root()->get_world()->get_direct_space_state();
PhysicsDirectSpaceState::RayResult result;
Array keys = snap_data.keys();
// The maximum height an object can travel to be snapped
const float max_snap_height = 20.0;
// Will be set to `true` if at least one node from the selection was successfully snapped
bool snapped_to_floor = false;
if (keys.size()) {
// 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 (int i = 0; i < keys.size(); i++) {
Node *node = keys[i];
Spatial *sp = Object::cast_to<Spatial>(node);
Dictionary d = snap_data[node];
Vector3 from = d["from"];
Vector3 to = from - Vector3(0.0, max_snap_height, 0.0);
Set<RID> excluded = _get_physics_bodies_rid(sp);
if (ss->intersect_ray(from, to, result, excluded)) {
snapped_to_floor = true;
}
}
if (snapped_to_floor) {
undo_redo->create_action(TTR("Snap Nodes To Floor"));
// Perform snapping if at least one node can be snapped
for (int i = 0; i < keys.size(); i++) {
Node *node = keys[i];
Spatial *sp = Object::cast_to<Spatial>(node);
Dictionary d = snap_data[node];
Vector3 from = d["from"];
Vector3 to = from - Vector3(0.0, max_snap_height, 0.0);
Set<RID> excluded = _get_physics_bodies_rid(sp);
if (ss->intersect_ray(from, to, result, excluded)) {
Vector3 position_offset = d["position_offset"];
Transform new_transform = sp->get_global_transform();
new_transform.origin.y = result.position.y;
new_transform.origin = new_transform.origin - position_offset;
undo_redo->add_do_method(sp, "set_global_transform", new_transform);
undo_redo->add_undo_method(sp, "set_global_transform", sp->get_global_transform());
}
}
undo_redo->commit_action();
} else {
EditorNode::get_singleton()->show_warning(TTR("Couldn't find a solid floor to snap the selection to."));
}
}
}
void SpatialEditor::_unhandled_key_input(Ref<InputEvent> p_event) {
if (!is_visible_in_tree() || get_viewport()->gui_has_modal_stack())
return;
snap_key_enabled = Input::get_singleton()->is_key_pressed(KEY_CONTROL);
}
void SpatialEditor::_notification(int p_what) {
if (p_what == NOTIFICATION_READY) {
tool_button[SpatialEditor::TOOL_MODE_SELECT]->set_icon(get_icon("ToolSelect", "EditorIcons"));
tool_button[SpatialEditor::TOOL_MODE_MOVE]->set_icon(get_icon("ToolMove", "EditorIcons"));
tool_button[SpatialEditor::TOOL_MODE_ROTATE]->set_icon(get_icon("ToolRotate", "EditorIcons"));
tool_button[SpatialEditor::TOOL_MODE_SCALE]->set_icon(get_icon("ToolScale", "EditorIcons"));
tool_button[SpatialEditor::TOOL_MODE_LIST_SELECT]->set_icon(get_icon("ListSelect", "EditorIcons"));
tool_button[SpatialEditor::TOOL_LOCK_SELECTED]->set_icon(get_icon("Lock", "EditorIcons"));
tool_button[SpatialEditor::TOOL_UNLOCK_SELECTED]->set_icon(get_icon("Unlock", "EditorIcons"));
tool_button[SpatialEditor::TOOL_GROUP_SELECTED]->set_icon(get_icon("Group", "EditorIcons"));
tool_button[SpatialEditor::TOOL_UNGROUP_SELECTED]->set_icon(get_icon("Ungroup", "EditorIcons"));
tool_option_button[SpatialEditor::TOOL_OPT_LOCAL_COORDS]->set_icon(get_icon("Object", "EditorIcons"));
tool_option_button[SpatialEditor::TOOL_OPT_USE_SNAP]->set_icon(get_icon("Snap", "EditorIcons"));
tool_option_button[SpatialEditor::TOOL_OPT_OVERRIDE_CAMERA]->set_icon(get_icon("Camera", "EditorIcons"));
view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), get_icon("Panels1", "EditorIcons"));
view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), get_icon("Panels2", "EditorIcons"));
view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT), get_icon("Panels2Alt", "EditorIcons"));
view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS), get_icon("Panels3", "EditorIcons"));
view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT), get_icon("Panels3Alt", "EditorIcons"));
view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS), get_icon("Panels4", "EditorIcons"));
_menu_item_pressed(MENU_VIEW_USE_1_VIEWPORT);
_refresh_menu_icons();
get_tree()->connect("node_removed", this, "_node_removed");
EditorNode::get_singleton()->get_scene_tree_dock()->get_tree_editor()->connect("node_changed", this, "_refresh_menu_icons");
editor_selection->connect("selection_changed", this, "_refresh_menu_icons");
editor->connect("stop_pressed", this, "_update_camera_override_button", make_binds(false));
editor->connect("play_pressed", this, "_update_camera_override_button", make_binds(true));
} else if (p_what == NOTIFICATION_ENTER_TREE) {
_register_all_gizmos();
_update_gizmos_menu();
_init_indicators();
} else if (p_what == NOTIFICATION_THEME_CHANGED) {
_update_gizmos_menu_theme();
} else if (p_what == NOTIFICATION_EXIT_TREE) {
_finish_indicators();
} else if (p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) {
tool_button[SpatialEditor::TOOL_MODE_SELECT]->set_icon(get_icon("ToolSelect", "EditorIcons"));
tool_button[SpatialEditor::TOOL_MODE_MOVE]->set_icon(get_icon("ToolMove", "EditorIcons"));
tool_button[SpatialEditor::TOOL_MODE_ROTATE]->set_icon(get_icon("ToolRotate", "EditorIcons"));
tool_button[SpatialEditor::TOOL_MODE_SCALE]->set_icon(get_icon("ToolScale", "EditorIcons"));
tool_button[SpatialEditor::TOOL_MODE_LIST_SELECT]->set_icon(get_icon("ListSelect", "EditorIcons"));
tool_button[SpatialEditor::TOOL_LOCK_SELECTED]->set_icon(get_icon("Lock", "EditorIcons"));
tool_button[SpatialEditor::TOOL_UNLOCK_SELECTED]->set_icon(get_icon("Unlock", "EditorIcons"));
tool_button[SpatialEditor::TOOL_GROUP_SELECTED]->set_icon(get_icon("Group", "EditorIcons"));
tool_button[SpatialEditor::TOOL_UNGROUP_SELECTED]->set_icon(get_icon("Ungroup", "EditorIcons"));
tool_option_button[SpatialEditor::TOOL_OPT_LOCAL_COORDS]->set_icon(get_icon("Object", "EditorIcons"));
tool_option_button[SpatialEditor::TOOL_OPT_USE_SNAP]->set_icon(get_icon("Snap", "EditorIcons"));
view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), get_icon("Panels1", "EditorIcons"));
view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), get_icon("Panels2", "EditorIcons"));
view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT), get_icon("Panels2Alt", "EditorIcons"));
view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS), get_icon("Panels3", "EditorIcons"));
view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT), get_icon("Panels3Alt", "EditorIcons"));
view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS), get_icon("Panels4", "EditorIcons"));
// Update grid color by rebuilding grid.
_finish_grid();
_init_grid();
} else if (p_what == NOTIFICATION_VISIBILITY_CHANGED) {
if (!is_visible() && tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->is_pressed()) {
ScriptEditorDebugger *debugger = ScriptEditor::get_singleton()->get_debugger();
debugger->set_camera_override(ScriptEditorDebugger::OVERRIDE_NONE);
tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->set_pressed(false);
}
}
}
void SpatialEditor::add_control_to_menu_panel(Control *p_control) {
hbc_menu->add_child(p_control);
}
void SpatialEditor::remove_control_from_menu_panel(Control *p_control) {
hbc_menu->remove_child(p_control);
}
void SpatialEditor::set_can_preview(Camera *p_preview) {
for (int i = 0; i < 4; i++) {
viewports[i]->set_can_preview(p_preview);
}
}
VSplitContainer *SpatialEditor::get_shader_split() {
return shader_split;
}
HSplitContainer *SpatialEditor::get_palette_split() {
return palette_split;
}
void SpatialEditor::_request_gizmo(Object *p_obj) {
Spatial *sp = Object::cast_to<Spatial>(p_obj);
if (!sp)
return;
if (editor->get_edited_scene() && (sp == editor->get_edited_scene() || (sp->get_owner() && editor->get_edited_scene()->is_a_parent_of(sp)))) {
Ref<EditorSpatialGizmo> seg;
for (int i = 0; i < gizmo_plugins_by_priority.size(); ++i) {
seg = gizmo_plugins_by_priority.write[i]->get_gizmo(sp);
if (seg.is_valid()) {
sp->set_gizmo(seg);
if (sp == selected) {
seg->set_selected(true);
selected->update_gizmo();
}
break;
}
}
}
}
void SpatialEditor::_toggle_maximize_view(Object *p_viewport) {
if (!p_viewport) return;
SpatialEditorViewport *current_viewport = Object::cast_to<SpatialEditorViewport>(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_margins_preset(Control::PRESET_WIDE);
else
viewports[i]->hide();
}
} else {
for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++)
viewports[i]->show();
if (view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT)))
_menu_item_pressed(MENU_VIEW_USE_1_VIEWPORT);
else if (view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS)))
_menu_item_pressed(MENU_VIEW_USE_2_VIEWPORTS);
else if (view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS_ALT)))
_menu_item_pressed(MENU_VIEW_USE_2_VIEWPORTS_ALT);
else if (view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS)))
_menu_item_pressed(MENU_VIEW_USE_3_VIEWPORTS);
else if (view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_3_VIEWPORTS_ALT)))
_menu_item_pressed(MENU_VIEW_USE_3_VIEWPORTS_ALT);
else if (view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_4_VIEWPORTS)))
_menu_item_pressed(MENU_VIEW_USE_4_VIEWPORTS);
}
}
void SpatialEditor::_node_removed(Node *p_node) {
if (p_node == selected)
selected = NULL;
}
void SpatialEditor::_register_all_gizmos() {
add_gizmo_plugin(Ref<CameraSpatialGizmoPlugin>(memnew(CameraSpatialGizmoPlugin)));
add_gizmo_plugin(Ref<LightSpatialGizmoPlugin>(memnew(LightSpatialGizmoPlugin)));
add_gizmo_plugin(Ref<AudioStreamPlayer3DSpatialGizmoPlugin>(memnew(AudioStreamPlayer3DSpatialGizmoPlugin)));
add_gizmo_plugin(Ref<MeshInstanceSpatialGizmoPlugin>(memnew(MeshInstanceSpatialGizmoPlugin)));
add_gizmo_plugin(Ref<SoftBodySpatialGizmoPlugin>(memnew(SoftBodySpatialGizmoPlugin)));
add_gizmo_plugin(Ref<Sprite3DSpatialGizmoPlugin>(memnew(Sprite3DSpatialGizmoPlugin)));
add_gizmo_plugin(Ref<SkeletonSpatialGizmoPlugin>(memnew(SkeletonSpatialGizmoPlugin)));
add_gizmo_plugin(Ref<Position3DSpatialGizmoPlugin>(memnew(Position3DSpatialGizmoPlugin)));
add_gizmo_plugin(Ref<RayCastSpatialGizmoPlugin>(memnew(RayCastSpatialGizmoPlugin)));
add_gizmo_plugin(Ref<SpringArmSpatialGizmoPlugin>(memnew(SpringArmSpatialGizmoPlugin)));
add_gizmo_plugin(Ref<VehicleWheelSpatialGizmoPlugin>(memnew(VehicleWheelSpatialGizmoPlugin)));
add_gizmo_plugin(Ref<VisibilityNotifierGizmoPlugin>(memnew(VisibilityNotifierGizmoPlugin)));
add_gizmo_plugin(Ref<ParticlesGizmoPlugin>(memnew(ParticlesGizmoPlugin)));
add_gizmo_plugin(Ref<CPUParticlesGizmoPlugin>(memnew(CPUParticlesGizmoPlugin)));
add_gizmo_plugin(Ref<ReflectionProbeGizmoPlugin>(memnew(ReflectionProbeGizmoPlugin)));
add_gizmo_plugin(Ref<GIProbeGizmoPlugin>(memnew(GIProbeGizmoPlugin)));
add_gizmo_plugin(Ref<BakedIndirectLightGizmoPlugin>(memnew(BakedIndirectLightGizmoPlugin)));
add_gizmo_plugin(Ref<CollisionShapeSpatialGizmoPlugin>(memnew(CollisionShapeSpatialGizmoPlugin)));
add_gizmo_plugin(Ref<CollisionPolygonSpatialGizmoPlugin>(memnew(CollisionPolygonSpatialGizmoPlugin)));
add_gizmo_plugin(Ref<NavigationMeshSpatialGizmoPlugin>(memnew(NavigationMeshSpatialGizmoPlugin)));
add_gizmo_plugin(Ref<JointSpatialGizmoPlugin>(memnew(JointSpatialGizmoPlugin)));
add_gizmo_plugin(Ref<PhysicalBoneSpatialGizmoPlugin>(memnew(PhysicalBoneSpatialGizmoPlugin)));
}
void SpatialEditor::_bind_methods() {
ClassDB::bind_method("_unhandled_key_input", &SpatialEditor::_unhandled_key_input);
ClassDB::bind_method("_node_removed", &SpatialEditor::_node_removed);
ClassDB::bind_method("_menu_item_pressed", &SpatialEditor::_menu_item_pressed);
ClassDB::bind_method("_menu_gizmo_toggled", &SpatialEditor::_menu_gizmo_toggled);
ClassDB::bind_method("_menu_item_toggled", &SpatialEditor::_menu_item_toggled);
ClassDB::bind_method("_xform_dialog_action", &SpatialEditor::_xform_dialog_action);
ClassDB::bind_method("_get_editor_data", &SpatialEditor::_get_editor_data);
ClassDB::bind_method("_request_gizmo", &SpatialEditor::_request_gizmo);
ClassDB::bind_method("_toggle_maximize_view", &SpatialEditor::_toggle_maximize_view);
ClassDB::bind_method("_refresh_menu_icons", &SpatialEditor::_refresh_menu_icons);
ClassDB::bind_method("_update_camera_override_button", &SpatialEditor::_update_camera_override_button);
ClassDB::bind_method("_update_camera_override_viewport", &SpatialEditor::_update_camera_override_viewport);
ClassDB::bind_method("_snap_changed", &SpatialEditor::_snap_changed);
ClassDB::bind_method("_snap_update", &SpatialEditor::_snap_update);
ADD_SIGNAL(MethodInfo("transform_key_request"));
ADD_SIGNAL(MethodInfo("item_lock_status_changed"));
ADD_SIGNAL(MethodInfo("item_group_status_changed"));
}
void SpatialEditor::clear() {
settings_fov->set_value(EDITOR_DEF("editors/3d/default_fov", 70.0));
settings_znear->set_value(EDITOR_DEF("editors/3d/default_z_near", 0.05));
settings_zfar->set_value(EDITOR_DEF("editors/3d/default_z_far", 1500.0));
for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) {
viewports[i]->reset();
}
VisualServer::get_singleton()->instance_set_visible(origin_instance, true);
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_ORIGIN), true);
for (int i = 0; i < 3; ++i) {
if (grid_enable[i]) {
VisualServer::get_singleton()->instance_set_visible(grid_instance[i], true);
grid_visible[i] = true;
}
}
for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) {
viewports[i]->view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(SpatialEditorViewport::VIEW_AUDIO_LISTENER), i == 0);
viewports[i]->viewport->set_as_audio_listener(i == 0);
}
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_GRID), true);
}
SpatialEditor::SpatialEditor(EditorNode *p_editor) {
gizmo.visible = true;
gizmo.scale = 1.0;
viewport_environment = Ref<Environment>(memnew(Environment));
undo_redo = p_editor->get_undo_redo();
VBoxContainer *vbc = this;
custom_camera = NULL;
singleton = this;
editor = p_editor;
editor_selection = editor->get_editor_selection();
editor_selection->add_editor_plugin(this);
snap_enabled = false;
snap_key_enabled = false;
tool_mode = TOOL_MODE_SELECT;
camera_override_viewport_id = 0;
hbc_menu = memnew(HBoxContainer);
vbc->add_child(hbc_menu);
Vector<Variant> button_binds;
button_binds.resize(1);
String sct;
tool_button[TOOL_MODE_SELECT] = memnew(ToolButton);
hbc_menu->add_child(tool_button[TOOL_MODE_SELECT]);
tool_button[TOOL_MODE_SELECT]->set_toggle_mode(true);
tool_button[TOOL_MODE_SELECT]->set_flat(true);
tool_button[TOOL_MODE_SELECT]->set_pressed(true);
button_binds.write[0] = MENU_TOOL_SELECT;
tool_button[TOOL_MODE_SELECT]->connect("pressed", this, "_menu_item_pressed", button_binds);
tool_button[TOOL_MODE_SELECT]->set_shortcut(ED_SHORTCUT("spatial_editor/tool_select", TTR("Select Mode"), KEY_Q));
tool_button[TOOL_MODE_SELECT]->set_tooltip(keycode_get_string(KEY_MASK_CMD) + TTR("Drag: Rotate\nAlt+Drag: Move\nAlt+RMB: Depth list selection"));
hbc_menu->add_child(memnew(VSeparator));
tool_button[TOOL_MODE_MOVE] = memnew(ToolButton);
hbc_menu->add_child(tool_button[TOOL_MODE_MOVE]);
tool_button[TOOL_MODE_MOVE]->set_toggle_mode(true);
tool_button[TOOL_MODE_MOVE]->set_flat(true);
button_binds.write[0] = MENU_TOOL_MOVE;
tool_button[TOOL_MODE_MOVE]->connect("pressed", this, "_menu_item_pressed", button_binds);
tool_button[TOOL_MODE_MOVE]->set_shortcut(ED_SHORTCUT("spatial_editor/tool_move", TTR("Move Mode"), KEY_W));
tool_button[TOOL_MODE_ROTATE] = memnew(ToolButton);
hbc_menu->add_child(tool_button[TOOL_MODE_ROTATE]);
tool_button[TOOL_MODE_ROTATE]->set_toggle_mode(true);
tool_button[TOOL_MODE_ROTATE]->set_flat(true);
button_binds.write[0] = MENU_TOOL_ROTATE;
tool_button[TOOL_MODE_ROTATE]->connect("pressed", this, "_menu_item_pressed", button_binds);
tool_button[TOOL_MODE_ROTATE]->set_shortcut(ED_SHORTCUT("spatial_editor/tool_rotate", TTR("Rotate Mode"), KEY_E));
tool_button[TOOL_MODE_SCALE] = memnew(ToolButton);
hbc_menu->add_child(tool_button[TOOL_MODE_SCALE]);
tool_button[TOOL_MODE_SCALE]->set_toggle_mode(true);
tool_button[TOOL_MODE_SCALE]->set_flat(true);
button_binds.write[0] = MENU_TOOL_SCALE;
tool_button[TOOL_MODE_SCALE]->connect("pressed", this, "_menu_item_pressed", button_binds);
tool_button[TOOL_MODE_SCALE]->set_shortcut(ED_SHORTCUT("spatial_editor/tool_scale", TTR("Scale Mode"), KEY_R));
hbc_menu->add_child(memnew(VSeparator));
tool_button[TOOL_MODE_LIST_SELECT] = memnew(ToolButton);
hbc_menu->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_flat(true);
button_binds.write[0] = MENU_TOOL_LIST_SELECT;
tool_button[TOOL_MODE_LIST_SELECT]->connect("pressed", this, "_menu_item_pressed", button_binds);
tool_button[TOOL_MODE_LIST_SELECT]->set_tooltip(TTR("Show a list of all objects at the position clicked\n(same as Alt+RMB in select mode)."));
tool_button[TOOL_LOCK_SELECTED] = memnew(ToolButton);
hbc_menu->add_child(tool_button[TOOL_LOCK_SELECTED]);
button_binds.write[0] = MENU_LOCK_SELECTED;
tool_button[TOOL_LOCK_SELECTED]->connect("pressed", this, "_menu_item_pressed", button_binds);
tool_button[TOOL_LOCK_SELECTED]->set_tooltip(TTR("Lock the selected object in place (can't be moved)."));
tool_button[TOOL_UNLOCK_SELECTED] = memnew(ToolButton);
hbc_menu->add_child(tool_button[TOOL_UNLOCK_SELECTED]);
button_binds.write[0] = MENU_UNLOCK_SELECTED;
tool_button[TOOL_UNLOCK_SELECTED]->connect("pressed", this, "_menu_item_pressed", button_binds);
tool_button[TOOL_UNLOCK_SELECTED]->set_tooltip(TTR("Unlock the selected object (can be moved)."));
tool_button[TOOL_GROUP_SELECTED] = memnew(ToolButton);
hbc_menu->add_child(tool_button[TOOL_GROUP_SELECTED]);
button_binds.write[0] = MENU_GROUP_SELECTED;
tool_button[TOOL_GROUP_SELECTED]->connect("pressed", this, "_menu_item_pressed", button_binds);
tool_button[TOOL_GROUP_SELECTED]->set_tooltip(TTR("Makes sure the object's children are not selectable."));
tool_button[TOOL_UNGROUP_SELECTED] = memnew(ToolButton);
hbc_menu->add_child(tool_button[TOOL_UNGROUP_SELECTED]);
button_binds.write[0] = MENU_UNGROUP_SELECTED;
tool_button[TOOL_UNGROUP_SELECTED]->connect("pressed", this, "_menu_item_pressed", button_binds);
tool_button[TOOL_UNGROUP_SELECTED]->set_tooltip(TTR("Restores the object's children's ability to be selected."));
hbc_menu->add_child(memnew(VSeparator));
tool_option_button[TOOL_OPT_LOCAL_COORDS] = memnew(ToolButton);
hbc_menu->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_flat(true);
button_binds.write[0] = MENU_TOOL_LOCAL_COORDS;
tool_option_button[TOOL_OPT_LOCAL_COORDS]->connect("toggled", this, "_menu_item_toggled", button_binds);
tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_shortcut(ED_SHORTCUT("spatial_editor/local_coords", TTR("Use Local Space"), KEY_T));
tool_option_button[TOOL_OPT_USE_SNAP] = memnew(ToolButton);
hbc_menu->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_flat(true);
button_binds.write[0] = MENU_TOOL_USE_SNAP;
tool_option_button[TOOL_OPT_USE_SNAP]->connect("toggled", this, "_menu_item_toggled", button_binds);
tool_option_button[TOOL_OPT_USE_SNAP]->set_shortcut(ED_SHORTCUT("spatial_editor/snap", TTR("Use Snap"), KEY_Y));
hbc_menu->add_child(memnew(VSeparator));
tool_option_button[TOOL_OPT_OVERRIDE_CAMERA] = memnew(ToolButton);
hbc_menu->add_child(tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]);
tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->set_toggle_mode(true);
tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->set_flat(true);
tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->set_disabled(true);
button_binds.write[0] = MENU_TOOL_OVERRIDE_CAMERA;
tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->connect("toggled", this, "_menu_item_toggled", button_binds);
_update_camera_override_button(false);
hbc_menu->add_child(memnew(VSeparator));
// Drag and drop support;
preview_node = memnew(Spatial);
preview_bounds = AABB();
ED_SHORTCUT("spatial_editor/bottom_view", TTR("Bottom View"), KEY_MASK_ALT + KEY_KP_7);
ED_SHORTCUT("spatial_editor/top_view", TTR("Top View"), KEY_KP_7);
ED_SHORTCUT("spatial_editor/rear_view", TTR("Rear View"), KEY_MASK_ALT + KEY_KP_1);
ED_SHORTCUT("spatial_editor/front_view", TTR("Front View"), KEY_KP_1);
ED_SHORTCUT("spatial_editor/left_view", TTR("Left View"), KEY_MASK_ALT + KEY_KP_3);
ED_SHORTCUT("spatial_editor/right_view", TTR("Right View"), KEY_KP_3);
ED_SHORTCUT("spatial_editor/switch_perspective_orthogonal", TTR("Switch Perspective/Orthogonal View"), KEY_KP_5);
ED_SHORTCUT("spatial_editor/insert_anim_key", TTR("Insert Animation Key"), KEY_K);
ED_SHORTCUT("spatial_editor/focus_origin", TTR("Focus Origin"), KEY_O);
ED_SHORTCUT("spatial_editor/focus_selection", TTR("Focus Selection"), KEY_F);
ED_SHORTCUT("spatial_editor/align_transform_with_view", TTR("Align Transform with View"), KEY_MASK_ALT + KEY_MASK_CMD + KEY_M);
ED_SHORTCUT("spatial_editor/align_rotation_with_view", TTR("Align Rotation with View"), KEY_MASK_ALT + KEY_MASK_CMD + KEY_F);
ED_SHORTCUT("spatial_editor/freelook_toggle", TTR("Toggle Freelook"), KEY_MASK_SHIFT + KEY_F);
PopupMenu *p;
transform_menu = memnew(MenuButton);
transform_menu->set_text(TTR("Transform"));
transform_menu->set_switch_on_hover(true);
hbc_menu->add_child(transform_menu);
p = transform_menu->get_popup();
p->add_shortcut(ED_SHORTCUT("spatial_editor/snap_to_floor", TTR("Snap Object to Floor"), KEY_PAGEDOWN), MENU_SNAP_TO_FLOOR);
p->add_shortcut(ED_SHORTCUT("spatial_editor/transform_dialog", TTR("Transform Dialog...")), MENU_TRANSFORM_DIALOG);
p->add_separator();
p->add_shortcut(ED_SHORTCUT("spatial_editor/configure_snap", TTR("Configure Snap...")), MENU_TRANSFORM_CONFIGURE_SNAP);
p->connect("id_pressed", this, "_menu_item_pressed");
view_menu = memnew(MenuButton);
view_menu->set_text(TTR("View"));
view_menu->set_switch_on_hover(true);
hbc_menu->add_child(view_menu);
p = view_menu->get_popup();
accept = memnew(AcceptDialog);
editor->get_gui_base()->add_child(accept);
p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/1_viewport", TTR("1 Viewport"), KEY_MASK_CMD + KEY_1), MENU_VIEW_USE_1_VIEWPORT);
p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/2_viewports", TTR("2 Viewports"), KEY_MASK_CMD + KEY_2), MENU_VIEW_USE_2_VIEWPORTS);
p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/2_viewports_alt", TTR("2 Viewports (Alt)"), KEY_MASK_ALT + KEY_MASK_CMD + KEY_2), MENU_VIEW_USE_2_VIEWPORTS_ALT);
p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/3_viewports", TTR("3 Viewports"), KEY_MASK_CMD + KEY_3), MENU_VIEW_USE_3_VIEWPORTS);
p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/3_viewports_alt", TTR("3 Viewports (Alt)"), KEY_MASK_ALT + KEY_MASK_CMD + KEY_3), MENU_VIEW_USE_3_VIEWPORTS_ALT);
p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/4_viewports", TTR("4 Viewports"), KEY_MASK_CMD + KEY_4), MENU_VIEW_USE_4_VIEWPORTS);
p->add_separator();
p->add_submenu_item(TTR("Gizmos"), "GizmosMenu");
p->add_separator();
p->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_origin", TTR("View Origin")), MENU_VIEW_ORIGIN);
p->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_grid", TTR("View Grid")), MENU_VIEW_GRID);
p->add_separator();
p->add_shortcut(ED_SHORTCUT("spatial_editor/settings", TTR("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("id_pressed", this, "_menu_item_pressed");
gizmos_menu = memnew(PopupMenu);
p->add_child(gizmos_menu);
gizmos_menu->set_name("GizmosMenu");
gizmos_menu->set_hide_on_checkable_item_selection(false);
gizmos_menu->connect("id_pressed", this, "_menu_gizmo_toggled");
/* REST OF MENU */
palette_split = memnew(HSplitContainer);
palette_split->set_v_size_flags(SIZE_EXPAND_FILL);
vbc->add_child(palette_split);
shader_split = memnew(VSplitContainer);
shader_split->set_h_size_flags(SIZE_EXPAND_FILL);
palette_split->add_child(shader_split);
viewport_base = memnew(SpatialEditorViewportContainer);
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(SpatialEditorViewport(this, editor, i));
viewports[i]->connect("toggle_maximize_view", this, "_toggle_maximize_view");
viewports[i]->connect("clicked", this, "_update_camera_override_viewport");
viewports[i]->assign_pending_data_pointers(preview_node, &preview_bounds, accept);
viewport_base->add_child(viewports[i]);
}
/* SNAP DIALOG */
snap_translate_value = 1;
snap_rotate_value = 15;
snap_scale_value = 10;
snap_dialog = memnew(ConfirmationDialog);
snap_dialog->set_title(TTR("Snap Settings"));
add_child(snap_dialog);
snap_dialog->connect("confirmed", this, "_snap_changed");
snap_dialog->get_cancel()->connect("pressed", this, "_snap_update");
VBoxContainer *snap_dialog_vbc = memnew(VBoxContainer);
snap_dialog->add_child(snap_dialog_vbc);
snap_translate = memnew(LineEdit);
snap_dialog_vbc->add_margin_child(TTR("Translate Snap:"), snap_translate);
snap_rotate = memnew(LineEdit);
snap_dialog_vbc->add_margin_child(TTR("Rotate Snap (deg.):"), snap_rotate);
snap_scale = memnew(LineEdit);
snap_dialog_vbc->add_margin_child(TTR("Scale Snap (%):"), snap_scale);
_snap_update();
/* SETTINGS DIALOG */
settings_dialog = memnew(ConfirmationDialog);
settings_dialog->set_title(TTR("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.01);
settings_fov->set_value(EDITOR_DEF("editors/3d/default_fov", 70.0));
settings_vbc->add_margin_child(TTR("Perspective FOV (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_value(EDITOR_DEF("editors/3d/default_z_near", 0.05));
settings_vbc->add_margin_child(TTR("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.01);
settings_zfar->set_value(EDITOR_DEF("editors/3d/default_z_far", 1500));
settings_vbc->add_margin_child(TTR("View Z-Far:"), settings_zfar);
for (uint32_t i = 0; i < VIEWPORTS_COUNT; ++i) {
settings_dialog->connect("confirmed", viewports[i], "_update_camera", varray(0.0));
}
/* XFORM DIALOG */
xform_dialog = memnew(ConfirmationDialog);
xform_dialog->set_title(TTR("Transform Change"));
add_child(xform_dialog);
VBoxContainer *xform_vbc = memnew(VBoxContainer);
xform_dialog->add_child(xform_vbc);
Label *l = memnew(Label);
l->set_text(TTR("Translate:"));
xform_vbc->add_child(l);
HBoxContainer *xform_hbc = memnew(HBoxContainer);
xform_vbc->add_child(xform_hbc);
for (int i = 0; i < 3; i++) {
xform_translate[i] = memnew(LineEdit);
xform_translate[i]->set_h_size_flags(SIZE_EXPAND_FILL);
xform_hbc->add_child(xform_translate[i]);
}
l = memnew(Label);
l->set_text(TTR("Rotate (deg.):"));
xform_vbc->add_child(l);
xform_hbc = memnew(HBoxContainer);
xform_vbc->add_child(xform_hbc);
for (int i = 0; i < 3; i++) {
xform_rotate[i] = memnew(LineEdit);
xform_rotate[i]->set_h_size_flags(SIZE_EXPAND_FILL);
xform_hbc->add_child(xform_rotate[i]);
}
l = memnew(Label);
l->set_text(TTR("Scale (ratio):"));
xform_vbc->add_child(l);
xform_hbc = memnew(HBoxContainer);
xform_vbc->add_child(xform_hbc);
for (int i = 0; i < 3; i++) {
xform_scale[i] = memnew(LineEdit);
xform_scale[i]->set_h_size_flags(SIZE_EXPAND_FILL);
xform_hbc->add_child(xform_scale[i]);
}
l = memnew(Label);
l->set_text(TTR("Transform Type"));
xform_vbc->add_child(l);
xform_type = memnew(OptionButton);
xform_type->set_h_size_flags(SIZE_EXPAND_FILL);
xform_type->add_item(TTR("Pre"));
xform_type->add_item(TTR("Post"));
xform_vbc->add_child(xform_type);
xform_dialog->connect("confirmed", this, "_xform_dialog_action");
scenario_debug = VisualServer::SCENARIO_DEBUG_DISABLED;
selected = NULL;
set_process_unhandled_key_input(true);
add_to_group("_spatial_editor_group");
EDITOR_DEF("editors/3d/manipulator_gizmo_size", 80);
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "editors/3d/manipulator_gizmo_size", PROPERTY_HINT_RANGE, "16,1024,1"));
EDITOR_DEF("editors/3d/manipulator_gizmo_opacity", 0.4);
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::REAL, "editors/3d/manipulator_gizmo_opacity", PROPERTY_HINT_RANGE, "0,1,0.01"));
EDITOR_DEF("editors/3d/navigation/show_viewport_rotation_gizmo", true);
over_gizmo_handle = -1;
}
SpatialEditor::~SpatialEditor() {
memdelete(preview_node);
}
void SpatialEditorPlugin::make_visible(bool p_visible) {
if (p_visible) {
spatial_editor->show();
spatial_editor->set_process(true);
} else {
spatial_editor->hide();
spatial_editor->set_process(false);
}
}
void SpatialEditorPlugin::edit(Object *p_object) {
spatial_editor->edit(Object::cast_to<Spatial>(p_object));
}
bool SpatialEditorPlugin::handles(Object *p_object) const {
return p_object->is_class("Spatial");
}
Dictionary SpatialEditorPlugin::get_state() const {
return spatial_editor->get_state();
}
void SpatialEditorPlugin::set_state(const Dictionary &p_state) {
spatial_editor->set_state(p_state);
}
void SpatialEditor::snap_cursor_to_plane(const Plane &p_plane) {
//cursor.pos=p_plane.project(cursor.pos);
}
Vector3 SpatialEditor::snap_point(Vector3 p_target, Vector3 p_start) const {
if (is_snap_enabled()) {
p_target.x = Math::snap_scalar(0.0, get_translate_snap(), p_target.x);
p_target.y = Math::snap_scalar(0.0, get_translate_snap(), p_target.y);
p_target.z = Math::snap_scalar(0.0, get_translate_snap(), p_target.z);
}
return p_target;
}
float SpatialEditor::get_translate_snap() const {
float snap_value;
if (Input::get_singleton()->is_key_pressed(KEY_SHIFT)) {
snap_value = snap_translate->get_text().to_double() / 10.0;
} else {
snap_value = snap_translate->get_text().to_double();
}
return snap_value;
}
float SpatialEditor::get_rotate_snap() const {
float snap_value;
if (Input::get_singleton()->is_key_pressed(KEY_SHIFT)) {
snap_value = snap_rotate->get_text().to_double() / 3.0;
} else {
snap_value = snap_rotate->get_text().to_double();
}
return snap_value;
}
float SpatialEditor::get_scale_snap() const {
float snap_value;
if (Input::get_singleton()->is_key_pressed(KEY_SHIFT)) {
snap_value = snap_scale->get_text().to_double() / 2.0;
} else {
snap_value = snap_scale->get_text().to_double();
}
return snap_value;
}
void SpatialEditorPlugin::_bind_methods() {
ClassDB::bind_method("snap_cursor_to_plane", &SpatialEditorPlugin::snap_cursor_to_plane);
}
void SpatialEditorPlugin::snap_cursor_to_plane(const Plane &p_plane) {
spatial_editor->snap_cursor_to_plane(p_plane);
}
struct _GizmoPluginPriorityComparator {
bool operator()(const Ref<EditorSpatialGizmoPlugin> &p_a, const Ref<EditorSpatialGizmoPlugin> &p_b) const {
if (p_a->get_priority() == p_b->get_priority()) {
return p_a->get_name() < p_b->get_name();
}
return p_a->get_priority() > p_b->get_priority();
}
};
struct _GizmoPluginNameComparator {
bool operator()(const Ref<EditorSpatialGizmoPlugin> &p_a, const Ref<EditorSpatialGizmoPlugin> &p_b) const {
return p_a->get_name() < p_b->get_name();
}
};
void SpatialEditor::add_gizmo_plugin(Ref<EditorSpatialGizmoPlugin> p_plugin) {
ERR_FAIL_NULL(p_plugin.ptr());
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();
SpatialEditor::get_singleton()->update_all_gizmos();
}
void SpatialEditor::remove_gizmo_plugin(Ref<EditorSpatialGizmoPlugin> p_plugin) {
gizmo_plugins_by_priority.erase(p_plugin);
gizmo_plugins_by_name.erase(p_plugin);
_update_gizmos_menu();
}
SpatialEditorPlugin::SpatialEditorPlugin(EditorNode *p_node) {
editor = p_node;
spatial_editor = memnew(SpatialEditor(p_node));
spatial_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL);
editor->get_viewport()->add_child(spatial_editor);
spatial_editor->hide();
spatial_editor->connect("transform_key_request", editor->get_inspector_dock(), "_transform_keyed");
}
SpatialEditorPlugin::~SpatialEditorPlugin() {
}
void EditorSpatialGizmoPlugin::create_material(const String &p_name, const Color &p_color, bool p_billboard, bool p_on_top, bool p_use_vertex_color) {
Color instanced_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/instanced", Color(0.7, 0.7, 0.7, 0.6));
Vector<Ref<SpatialMaterial> > mats;
for (int i = 0; i < 4; i++) {
bool selected = i % 2 == 1;
bool instanced = i < 2;
Ref<SpatialMaterial> material = Ref<SpatialMaterial>(memnew(SpatialMaterial));
Color color = instanced ? instanced_color : p_color;
if (!selected) {
color.a *= 0.3;
}
material->set_albedo(color);
material->set_flag(SpatialMaterial::FLAG_UNSHADED, true);
material->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true);
material->set_render_priority(SpatialMaterial::RENDER_PRIORITY_MIN + 1);
if (p_use_vertex_color) {
material->set_flag(SpatialMaterial::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
material->set_flag(SpatialMaterial::FLAG_SRGB_VERTEX_COLOR, true);
}
if (p_billboard) {
material->set_billboard_mode(SpatialMaterial::BILLBOARD_ENABLED);
}
if (p_on_top && selected) {
material->set_on_top_of_alpha();
}
mats.push_back(material);
}
materials[p_name] = mats;
}
void EditorSpatialGizmoPlugin::create_icon_material(const String &p_name, const Ref<Texture> &p_texture, bool p_on_top, const Color &p_albedo) {
Color instanced_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/instanced", Color(0.7, 0.7, 0.7, 0.6));
Vector<Ref<SpatialMaterial> > icons;
for (int i = 0; i < 4; i++) {
bool selected = i % 2 == 1;
bool instanced = i < 2;
Ref<SpatialMaterial> icon = Ref<SpatialMaterial>(memnew(SpatialMaterial));
Color color = instanced ? instanced_color : p_albedo;
if (!selected) {
color.a *= 0.85;
}
icon->set_albedo(color);
icon->set_flag(SpatialMaterial::FLAG_UNSHADED, true);
icon->set_flag(SpatialMaterial::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
icon->set_flag(SpatialMaterial::FLAG_SRGB_VERTEX_COLOR, true);
icon->set_cull_mode(SpatialMaterial::CULL_DISABLED);
icon->set_depth_draw_mode(SpatialMaterial::DEPTH_DRAW_DISABLED);
icon->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true);
icon->set_texture(SpatialMaterial::TEXTURE_ALBEDO, p_texture);
icon->set_flag(SpatialMaterial::FLAG_FIXED_SIZE, true);
icon->set_billboard_mode(SpatialMaterial::BILLBOARD_ENABLED);
icon->set_render_priority(SpatialMaterial::RENDER_PRIORITY_MIN);
if (p_on_top && selected) {
icon->set_on_top_of_alpha();
}
icons.push_back(icon);
}
materials[p_name] = icons;
}
void EditorSpatialGizmoPlugin::create_handle_material(const String &p_name, bool p_billboard) {
Ref<SpatialMaterial> handle_material = Ref<SpatialMaterial>(memnew(SpatialMaterial));
handle_material->set_flag(SpatialMaterial::FLAG_UNSHADED, true);
handle_material->set_flag(SpatialMaterial::FLAG_USE_POINT_SIZE, true);
Ref<Texture> handle_t = SpatialEditor::get_singleton()->get_icon("Editor3DHandle", "EditorIcons");
handle_material->set_point_size(handle_t->get_width());
handle_material->set_texture(SpatialMaterial::TEXTURE_ALBEDO, handle_t);
handle_material->set_albedo(Color(1, 1, 1));
handle_material->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true);
handle_material->set_flag(SpatialMaterial::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
handle_material->set_flag(SpatialMaterial::FLAG_SRGB_VERTEX_COLOR, true);
handle_material->set_on_top_of_alpha();
if (p_billboard) {
handle_material->set_billboard_mode(SpatialMaterial::BILLBOARD_ENABLED);
handle_material->set_on_top_of_alpha();
}
materials[p_name] = Vector<Ref<SpatialMaterial> >();
materials[p_name].push_back(handle_material);
}
void EditorSpatialGizmoPlugin::add_material(const String &p_name, Ref<SpatialMaterial> p_material) {
materials[p_name] = Vector<Ref<SpatialMaterial> >();
materials[p_name].push_back(p_material);
}
Ref<SpatialMaterial> EditorSpatialGizmoPlugin::get_material(const String &p_name, const Ref<EditorSpatialGizmo> &p_gizmo) {
ERR_FAIL_COND_V(!materials.has(p_name), Ref<SpatialMaterial>());
ERR_FAIL_COND_V(materials[p_name].size() == 0, Ref<SpatialMaterial>());
if (p_gizmo.is_null() || materials[p_name].size() == 1) return materials[p_name][0];
int index = (p_gizmo->is_selected() ? 1 : 0) + (p_gizmo->is_editable() ? 2 : 0);
Ref<SpatialMaterial> mat = materials[p_name][index];
if (current_state == ON_TOP && p_gizmo->is_selected()) {
mat->set_flag(SpatialMaterial::FLAG_DISABLE_DEPTH_TEST, true);
} else {
mat->set_flag(SpatialMaterial::FLAG_DISABLE_DEPTH_TEST, false);
}
return mat;
}
String EditorSpatialGizmoPlugin::get_name() const {
if (get_script_instance() && get_script_instance()->has_method("get_name")) {
return get_script_instance()->call("get_name");
}
return TTR("Nameless gizmo");
}
int EditorSpatialGizmoPlugin::get_priority() const {
if (get_script_instance() && get_script_instance()->has_method("get_priority")) {
return get_script_instance()->call("get_priority");
}
return 0;
}
Ref<EditorSpatialGizmo> EditorSpatialGizmoPlugin::get_gizmo(Spatial *p_spatial) {
if (get_script_instance() && get_script_instance()->has_method("get_gizmo")) {
return get_script_instance()->call("get_gizmo", p_spatial);
}
Ref<EditorSpatialGizmo> ref = create_gizmo(p_spatial);
if (ref.is_null()) return ref;
ref->set_plugin(this);
ref->set_spatial_node(p_spatial);
ref->set_hidden(current_state == HIDDEN);
current_gizmos.push_back(ref.ptr());
return ref;
}
void EditorSpatialGizmoPlugin::_bind_methods() {
#define GIZMO_REF PropertyInfo(Variant::OBJECT, "gizmo", PROPERTY_HINT_RESOURCE_TYPE, "EditorSpatialGizmo")
BIND_VMETHOD(MethodInfo(Variant::BOOL, "has_gizmo", PropertyInfo(Variant::OBJECT, "spatial", PROPERTY_HINT_RESOURCE_TYPE, "Spatial")));
BIND_VMETHOD(MethodInfo(GIZMO_REF, "create_gizmo", PropertyInfo(Variant::OBJECT, "spatial", PROPERTY_HINT_RESOURCE_TYPE, "Spatial")));
ClassDB::bind_method(D_METHOD("create_material", "name", "color", "billboard", "on_top", "use_vertex_color"), &EditorSpatialGizmoPlugin::create_material, DEFVAL(false), DEFVAL(false), DEFVAL(false));
ClassDB::bind_method(D_METHOD("create_icon_material", "name", "texture", "on_top", "color"), &EditorSpatialGizmoPlugin::create_icon_material, DEFVAL(false), DEFVAL(Color(1, 1, 1, 1)));
ClassDB::bind_method(D_METHOD("create_handle_material", "name", "billboard"), &EditorSpatialGizmoPlugin::create_handle_material, DEFVAL(false));
ClassDB::bind_method(D_METHOD("add_material", "name", "material"), &EditorSpatialGizmoPlugin::add_material);
ClassDB::bind_method(D_METHOD("get_material", "name", "gizmo"), &EditorSpatialGizmoPlugin::get_material); //, DEFVAL(Ref<EditorSpatialGizmo>()));
BIND_VMETHOD(MethodInfo(Variant::STRING, "get_name"));
BIND_VMETHOD(MethodInfo(Variant::STRING, "get_priority"));
BIND_VMETHOD(MethodInfo(Variant::BOOL, "can_be_hidden"));
BIND_VMETHOD(MethodInfo(Variant::BOOL, "is_selectable_when_hidden"));
BIND_VMETHOD(MethodInfo("redraw", GIZMO_REF));
BIND_VMETHOD(MethodInfo(Variant::STRING, "get_handle_name", GIZMO_REF, PropertyInfo(Variant::INT, "index")));
MethodInfo hvget(Variant::NIL, "get_handle_value", GIZMO_REF, PropertyInfo(Variant::INT, "index"));
hvget.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
BIND_VMETHOD(hvget);
BIND_VMETHOD(MethodInfo("set_handle", GIZMO_REF, PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::OBJECT, "camera", PROPERTY_HINT_RESOURCE_TYPE, "Camera"), PropertyInfo(Variant::VECTOR2, "point")));
MethodInfo cm = MethodInfo("commit_handle", GIZMO_REF, PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::NIL, "restore"), PropertyInfo(Variant::BOOL, "cancel"));
cm.default_arguments.push_back(false);
BIND_VMETHOD(cm);
BIND_VMETHOD(MethodInfo(Variant::BOOL, "is_handle_highlighted", GIZMO_REF, PropertyInfo(Variant::INT, "index")));
#undef GIZMO_REF
}
bool EditorSpatialGizmoPlugin::has_gizmo(Spatial *p_spatial) {
if (get_script_instance() && get_script_instance()->has_method("has_gizmo")) {
return get_script_instance()->call("has_gizmo", p_spatial);
}
return false;
}
Ref<EditorSpatialGizmo> EditorSpatialGizmoPlugin::create_gizmo(Spatial *p_spatial) {
if (get_script_instance() && get_script_instance()->has_method("create_gizmo")) {
return get_script_instance()->call("create_gizmo", p_spatial);
}
Ref<EditorSpatialGizmo> ref;
if (has_gizmo(p_spatial)) ref.instance();
return ref;
}
bool EditorSpatialGizmoPlugin::can_be_hidden() const {
if (get_script_instance() && get_script_instance()->has_method("can_be_hidden")) {
return get_script_instance()->call("can_be_hidden");
}
return true;
}
bool EditorSpatialGizmoPlugin::is_selectable_when_hidden() const {
if (get_script_instance() && get_script_instance()->has_method("is_selectable_when_hidden")) {
return get_script_instance()->call("is_selectable_when_hidden");
}
return false;
}
void EditorSpatialGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) {
if (get_script_instance() && get_script_instance()->has_method("redraw")) {
Ref<EditorSpatialGizmo> ref(p_gizmo);
get_script_instance()->call("redraw", ref);
}
}
String EditorSpatialGizmoPlugin::get_handle_name(const EditorSpatialGizmo *p_gizmo, int p_idx) const {
if (get_script_instance() && get_script_instance()->has_method("get_handle_name")) {
return get_script_instance()->call("get_handle_name", p_gizmo, p_idx);
}
return "";
}
Variant EditorSpatialGizmoPlugin::get_handle_value(EditorSpatialGizmo *p_gizmo, int p_idx) const {
if (get_script_instance() && get_script_instance()->has_method("get_handle_value")) {
return get_script_instance()->call("get_handle_value", p_gizmo, p_idx);
}
return Variant();
}
void EditorSpatialGizmoPlugin::set_handle(EditorSpatialGizmo *p_gizmo, int p_idx, Camera *p_camera, const Point2 &p_point) {
if (get_script_instance() && get_script_instance()->has_method("set_handle")) {
get_script_instance()->call("set_handle", p_gizmo, p_idx, p_camera, p_point);
}
}
void EditorSpatialGizmoPlugin::commit_handle(EditorSpatialGizmo *p_gizmo, int p_idx, const Variant &p_restore, bool p_cancel) {
if (get_script_instance() && get_script_instance()->has_method("commit_handle")) {
get_script_instance()->call("commit_handle", p_gizmo, p_idx, p_restore, p_cancel);
}
}
bool EditorSpatialGizmoPlugin::is_handle_highlighted(const EditorSpatialGizmo *p_gizmo, int p_idx) const {
if (get_script_instance() && get_script_instance()->has_method("is_handle_highlighted")) {
return get_script_instance()->call("is_handle_highlighted", p_gizmo, p_idx);
}
return false;
}
void EditorSpatialGizmoPlugin::set_state(int p_state) {
current_state = p_state;
for (int i = 0; i < current_gizmos.size(); ++i) {
current_gizmos[i]->set_hidden(current_state == HIDDEN);
}
}
int EditorSpatialGizmoPlugin::get_state() const {
return current_state;
}
void EditorSpatialGizmoPlugin::unregister_gizmo(EditorSpatialGizmo *p_gizmo) {
current_gizmos.erase(p_gizmo);
}
EditorSpatialGizmoPlugin::EditorSpatialGizmoPlugin() {
current_state = VISIBLE;
}
EditorSpatialGizmoPlugin::~EditorSpatialGizmoPlugin() {
for (int i = 0; i < current_gizmos.size(); ++i) {
current_gizmos[i]->set_plugin(NULL);
current_gizmos[i]->get_spatial_node()->set_gizmo(NULL);
}
if (SpatialEditor::get_singleton()) {
SpatialEditor::get_singleton()->update_all_gizmos();
}
}