Merge pull request #115957 from YeldhamDev/cursor_manipulator

Add `View3DController` for editor 3D view manipulation
This commit is contained in:
Thaddeus Crews 2026-03-06 12:40:06 -06:00
commit 42c771d160
No known key found for this signature in database
GPG key ID: 8C6E5FEB5FC03CCC
9 changed files with 1609 additions and 1378 deletions

View file

@ -74,17 +74,52 @@ void GameViewDebugger::_session_started(Ref<EditorDebuggerSession> p_session) {
settings["canvas_item_editor/pan_view"] = DebuggerMarshalls::serialize_key_shortcut(ED_GET_SHORTCUT("canvas_item_editor/pan_view"));
settings["box_selection_fill_color"] = EditorNode::get_singleton()->get_editor_theme()->get_color(SNAME("box_selection_fill_color"), EditorStringName(Editor));
settings["box_selection_stroke_color"] = EditorNode::get_singleton()->get_editor_theme()->get_color(SNAME("box_selection_stroke_color"), EditorStringName(Editor));
#ifndef _3D_DISABLED
settings["editors/3d/default_fov"] = EDITOR_GET("editors/3d/default_fov");
settings["editors/3d/default_z_near"] = EDITOR_GET("editors/3d/default_z_near");
settings["editors/3d/default_z_far"] = EDITOR_GET("editors/3d/default_z_far");
settings["editors/3d/navigation/invert_x_axis"] = EDITOR_GET("editors/3d/navigation/invert_x_axis");
settings["editors/3d/navigation/invert_y_axis"] = EDITOR_GET("editors/3d/navigation/invert_y_axis");
settings["editors/3d/navigation/warped_mouse_panning"] = EDITOR_GET("editors/3d/navigation/warped_mouse_panning");
settings["editors/3d/navigation/pan_mouse_button"] = EDITOR_GET("editors/3d/navigation/pan_mouse_button");
settings["editors/3d/freelook/freelook_navigation_scheme"] = EDITOR_GET("editors/3d/freelook/freelook_navigation_scheme");
settings["editors/3d/freelook/freelook_base_speed"] = EDITOR_GET("editors/3d/freelook/freelook_base_speed");
settings["editors/3d/freelook/freelook_sensitivity"] = EDITOR_GET("editors/3d/freelook/freelook_sensitivity");
settings["editors/3d/freelook/freelook_inertia"] = EDITOR_GET("editors/3d/freelook/freelook_inertia");
settings["editors/3d/freelook/freelook_speed_zoom_link"] = EDITOR_GET("editors/3d/freelook/freelook_speed_zoom_link");
settings["editors/3d/freelook/freelook_invert_y_axis"] = EDITOR_GET("editors/3d/freelook/freelook_invert_y_axis");
settings["editors/3d/navigation/orbit_mouse_button"] = EDITOR_GET("editors/3d/navigation/orbit_mouse_button");
settings["editors/3d/navigation_feel/orbit_sensitivity"] = EDITOR_GET("editors/3d/navigation_feel/orbit_sensitivity");
settings["editors/3d/navigation_feel/orbit_inertia"] = EDITOR_GET("editors/3d/navigation_feel/orbit_inertia");
settings["editors/3d/navigation/zoom_style"] = EDITOR_GET("editors/3d/navigation/zoom_style");
settings["editors/3d/navigation_feel/zoom_inertia"] = EDITOR_GET("editors/3d/navigation_feel/zoom_inertia");
settings["editors/3d/navigation/zoom_mouse_button"] = EDITOR_GET("editors/3d/navigation/zoom_mouse_button");
settings["editors/3d/navigation_feel/translation_sensitivity"] = EDITOR_GET("editors/3d/navigation_feel/translation_sensitivity");
settings["editors/3d/navigation_feel/translation_inertia"] = EDITOR_GET("editors/3d/navigation_feel/translation_inertia");
settings["editors/3d/navigation_feel/angle_snap_threshold"] = EDITOR_GET("editors/3d/navigation_feel/angle_snap_threshold");
settings["editors/3d/navigation/emulate_3_button_mouse"] = EDITOR_GET("editors/3d/navigation/emulate_3_button_mouse");
settings["editors/3d/navigation/emulate_numpad"] = EDITOR_GET("editors/3d/navigation/emulate_numpad");
settings["editors/3d/selection_box_color"] = EDITOR_GET("editors/3d/selection_box_color");
settings["spatial_editor/decrease_fov"] = DebuggerMarshalls::serialize_key_shortcut(ED_GET_SHORTCUT("spatial_editor/decrease_fov"));
settings["spatial_editor/increase_fov"] = DebuggerMarshalls::serialize_key_shortcut(ED_GET_SHORTCUT("spatial_editor/increase_fov"));
settings["spatial_editor/reset_fov"] = DebuggerMarshalls::serialize_key_shortcut(ED_GET_SHORTCUT("spatial_editor/reset_fov"));
settings["spatial_editor/viewport_pan_modifier_1"] = DebuggerMarshalls::serialize_key_shortcut(ED_GET_SHORTCUT("spatial_editor/viewport_pan_modifier_1"));
settings["spatial_editor/viewport_pan_modifier_2"] = DebuggerMarshalls::serialize_key_shortcut(ED_GET_SHORTCUT("spatial_editor/viewport_pan_modifier_2"));
settings["spatial_editor/viewport_orbit_modifier_1"] = DebuggerMarshalls::serialize_key_shortcut(ED_GET_SHORTCUT("spatial_editor/viewport_orbit_modifier_1"));
settings["spatial_editor/viewport_orbit_modifier_2"] = DebuggerMarshalls::serialize_key_shortcut(ED_GET_SHORTCUT("spatial_editor/viewport_orbit_modifier_2"));
settings["spatial_editor/viewport_orbit_snap_modifier_1"] = DebuggerMarshalls::serialize_key_shortcut(ED_GET_SHORTCUT("spatial_editor/viewport_orbit_snap_modifier_1"));
settings["spatial_editor/viewport_orbit_snap_modifier_2"] = DebuggerMarshalls::serialize_key_shortcut(ED_GET_SHORTCUT("spatial_editor/viewport_orbit_snap_modifier_2"));
settings["spatial_editor/viewport_zoom_modifier_1"] = DebuggerMarshalls::serialize_key_shortcut(ED_GET_SHORTCUT("spatial_editor/viewport_zoom_modifier_1"));
settings["spatial_editor/viewport_zoom_modifier_2"] = DebuggerMarshalls::serialize_key_shortcut(ED_GET_SHORTCUT("spatial_editor/viewport_zoom_modifier_2"));
settings["spatial_editor/freelook_forward"] = DebuggerMarshalls::serialize_key_shortcut(ED_GET_SHORTCUT("spatial_editor/freelook_forward"));
settings["spatial_editor/freelook_backwards"] = DebuggerMarshalls::serialize_key_shortcut(ED_GET_SHORTCUT("spatial_editor/freelook_backwards"));
settings["spatial_editor/freelook_left"] = DebuggerMarshalls::serialize_key_shortcut(ED_GET_SHORTCUT("spatial_editor/freelook_left"));
settings["spatial_editor/freelook_right"] = DebuggerMarshalls::serialize_key_shortcut(ED_GET_SHORTCUT("spatial_editor/freelook_right"));
settings["spatial_editor/freelook_up"] = DebuggerMarshalls::serialize_key_shortcut(ED_GET_SHORTCUT("spatial_editor/freelook_up"));
settings["spatial_editor/freelook_down"] = DebuggerMarshalls::serialize_key_shortcut(ED_GET_SHORTCUT("spatial_editor/freelook_down"));
settings["spatial_editor/freelook_speed_modifier"] = DebuggerMarshalls::serialize_key_shortcut(ED_GET_SHORTCUT("spatial_editor/freelook_speed_modifier"));
settings["spatial_editor/freelook_slow_modifier"] = DebuggerMarshalls::serialize_key_shortcut(ED_GET_SHORTCUT("spatial_editor/freelook_slow_modifier"));
#endif // _3D_DISABLED
Array setup_data;
setup_data.append(settings);

File diff suppressed because it is too large Load diff

View file

@ -34,6 +34,7 @@
#include "editor/plugins/editor_plugin.h"
#include "editor/scene/3d/node_3d_editor_gizmos.h"
#include "editor/themes/editor_scale.h"
#include "scene/debugger/view_3d_controller.h"
#include "scene/gui/box_container.h"
#include "scene/gui/button.h"
#include "scene/gui/spin_box.h"
@ -173,16 +174,6 @@ class Node3DEditorViewport : public Control {
VIEW_MAX
};
enum ViewType {
VIEW_TYPE_USER,
VIEW_TYPE_TOP,
VIEW_TYPE_BOTTOM,
VIEW_TYPE_LEFT,
VIEW_TYPE_RIGHT,
VIEW_TYPE_FRONT,
VIEW_TYPE_REAR,
};
public:
static constexpr int32_t GIZMO_BASE_LAYER = 27;
static constexpr int32_t GIZMO_EDIT_LAYER = 26;
@ -191,28 +182,6 @@ public:
static constexpr int32_t FRAME_TIME_HISTORY = 20;
enum NavigationScheme {
NAVIGATION_GODOT = 0,
NAVIGATION_MAYA = 1,
NAVIGATION_MODO = 2,
NAVIGATION_CUSTOM = 3,
NAVIGATION_TABLET = 4,
};
enum FreelookNavigationScheme {
FREELOOK_DEFAULT,
FREELOOK_PARTIALLY_AXIS_LOCKED,
FREELOOK_FULLY_AXIS_LOCKED,
};
enum ViewportNavMouseButton {
NAVIGATION_LEFT_MOUSE,
NAVIGATION_MIDDLE_MOUSE,
NAVIGATION_RIGHT_MOUSE,
NAVIGATION_MOUSE_4,
NAVIGATION_MOUSE_5,
};
private:
double cpu_time_history[FRAME_TIME_HISTORY];
int cpu_time_history_index;
@ -230,9 +199,7 @@ private:
Ref<StandardMaterial3D> ruler_material_xray;
int index;
ViewType view_type;
void _menu_option(int p_option);
void _set_auto_orthogonal();
Node3D *preview_node = nullptr;
bool update_preview_node = false;
Point2 preview_node_viewport_pos;
@ -260,17 +227,10 @@ private:
SubViewport *viewport = nullptr;
Camera3D *camera = nullptr;
bool transforming = false;
bool orthogonal;
bool auto_orthogonal;
bool lock_rotation;
bool transform_gizmo_visible = true;
bool collision_reposition = false;
real_t gizmo_scale;
bool freelook_active;
real_t freelook_speed;
Vector2 previous_mouse_position;
PanelContainer *info_panel = nullptr;
Label *info_label = nullptr;
Label *cinema_label = nullptr;
@ -298,6 +258,8 @@ private:
_FORCE_INLINE_ bool operator<(const _RayResult &p_rr) const { return depth < p_rr.depth; }
};
void _view_state_changed();
void _update_name();
void _compute_edit(const Point2 &p_point);
void _clear_selected();
@ -319,14 +281,8 @@ private:
bool _transform_gizmo_select(const Vector2 &p_screenpos, bool p_highlight_only = false);
void _transform_gizmo_apply(Node3D *p_node, const Transform3D &p_transform, bool p_local);
void _nav_pan(Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative);
void _nav_zoom(Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative);
void _nav_orbit(Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative);
void _nav_look(Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative);
bool _is_shortcut_empty(const String &p_name);
bool _is_nav_modifier_pressed(const String &p_name);
int _get_shortcut_input_count(const String &p_name);
float get_znear() const;
float get_zfar() const;
@ -342,19 +298,6 @@ private:
PopupMenu *selection_menu = nullptr;
enum NavigationZoomStyle {
NAVIGATION_ZOOM_VERTICAL,
NAVIGATION_ZOOM_HORIZONTAL
};
enum NavigationMode {
NAVIGATION_NONE,
NAVIGATION_PAN,
NAVIGATION_ZOOM,
NAVIGATION_ORBIT,
NAVIGATION_LOOK,
NAVIGATION_MOVE
};
enum TransformMode {
TRANSFORM_NONE,
TRANSFORM_ROTATE,
@ -413,59 +356,15 @@ private:
bool gizmo_initiated = false;
} _edit;
struct Cursor {
Vector3 pos;
real_t x_rot, y_rot, distance, fov_scale;
real_t unsnapped_x_rot, unsnapped_y_rot;
Vector3 eye_pos; // Used in freelook mode
bool region_select;
Point2 region_begin, region_end;
Ref<View3DController> view_3d_controller;
void _update_view_3d_controller(bool p_update_all = true);
Cursor() {
// These rotations place the camera in +X +Y +Z, aka south east, facing north west.
x_rot = 0.5;
y_rot = -0.5;
unsnapped_x_rot = x_rot;
unsnapped_y_rot = y_rot;
distance = 4;
fov_scale = 1.0;
region_select = false;
}
};
// Viewport camera supports movement smoothing,
// so one cursor is the real cursor, while the other can be an interpolated version.
Cursor cursor; // Immediate cursor
Cursor camera_cursor; // That one may be interpolated (don't modify this one except for smoothing purposes)
Cursor previous_cursor; // Storing previous cursor state for canceling purposes
void _cursor_interpolated();
void scale_fov(real_t p_fov_offset);
void reset_fov();
void scale_cursor_distance(real_t scale);
void _freelook_changed();
void _freelook_speed_scaled();
struct ShortcutCheckSet {
bool mod_pressed = false;
bool shortcut_not_empty = true;
int input_count = 0;
ViewportNavMouseButton mouse_preference = NAVIGATION_LEFT_MOUSE;
NavigationMode result_nav_mode = NAVIGATION_NONE;
ShortcutCheckSet() {}
ShortcutCheckSet(bool p_mod_pressed, bool p_shortcut_not_empty, int p_input_count, const ViewportNavMouseButton &p_mouse_preference, const NavigationMode &p_result_nav_mode) :
mod_pressed(p_mod_pressed), shortcut_not_empty(p_shortcut_not_empty), input_count(p_input_count), mouse_preference(p_mouse_preference), result_nav_mode(p_result_nav_mode) {
}
};
struct ShortcutCheckSetComparator {
_FORCE_INLINE_ bool operator()(const ShortcutCheckSet &A, const ShortcutCheckSet &B) const {
return A.input_count > B.input_count;
}
};
NavigationMode _get_nav_mode_from_shortcut_check(ViewportNavMouseButton p_mouse_button, Vector<ShortcutCheckSet> p_shortcut_check_sets, bool p_use_not_empty);
void set_freelook_active(bool active_now);
void scale_freelook_speed(real_t scale);
View3DController::Cursor previous_cursor; // Storing previous cursor state for canceling purposes.
real_t zoom_indicator_delay;
int zoom_failed_attempts_count = 0;
@ -480,9 +379,7 @@ private:
void set_message(const String &p_message, float p_time = 5);
void _view_settings_confirmed(real_t p_interp_delta);
void _update_camera(real_t p_interp_delta);
void _update_navigation_controls_visibility();
Transform3D to_camera_transform(const Cursor &p_cursor) const;
void _draw();
// These allow tool scripts to set the 3D cursor location by updating the camera transform.
@ -497,7 +394,6 @@ private:
void input(const Ref<InputEvent> &p_event) override;
void _sinput(const Ref<InputEvent> &p_event);
void _update_freelook(real_t delta);
Node3DEditor *spatial_editor = nullptr;
Camera3D *previewing = nullptr;
@ -518,7 +414,6 @@ private:
void _selection_result_pressed(int);
void _selection_menu_hide();
void _list_select(Ref<InputEventMouseButton> b);
Point2 _get_warped_mouse_motion(const Ref<InputEventMouseMotion> &p_ev_mouse_motion) const;
Vector3 _get_instance_position(const Point2 &p_pos, Node3D *p_node) const;
static AABB _calculate_spatial_bounds(const Node3D *p_parent, bool p_omit_top_level = false, const Transform3D *p_bounds_orientation = nullptr);
@ -578,7 +473,6 @@ public:
void set_state(const Dictionary &p_state);
Dictionary get_state() const;
void reset();
bool is_freelook_active() const { return freelook_active; }
Vector3 get_ray_pos(const Vector2 &p_pos) const;
Vector3 get_ray(const Vector2 &p_pos) const;
@ -743,9 +637,9 @@ private:
DynamicBVH gizmo_bvh;
real_t snap_translate_value;
real_t snap_rotate_value;
real_t snap_scale_value;
real_t snap_translate_value = 0;
real_t snap_rotate_value = 0;
real_t snap_scale_value = 0;
Ref<ArrayMesh> active_selection_box_xray;
Ref<ArrayMesh> active_selection_box;
@ -1133,7 +1027,7 @@ class ViewportNavigationControl : public Control {
Vector2 focused_pos;
bool hovered = false;
int focused_index = -1;
Node3DEditorViewport::NavigationMode nav_mode = Node3DEditorViewport::NavigationMode::NAVIGATION_NONE;
View3DController::NavigationMode nav_mode = View3DController::NavigationMode::NAV_MODE_NONE;
const float AXIS_CIRCLE_RADIUS = 30.0f * EDSCALE;
@ -1146,6 +1040,6 @@ protected:
void _update_navigation();
public:
void set_navigation_mode(Node3DEditorViewport::NavigationMode p_nav_mode);
void set_navigation_mode(View3DController::NavigationMode p_nav_mode);
void set_viewport(Node3DEditorViewport *p_viewport);
};

View file

@ -50,6 +50,7 @@
#include "editor/settings/input_event_configuration_dialog.h"
#include "editor/themes/editor_scale.h"
#include "editor/themes/editor_theme_manager.h"
#include "scene/debugger/view_3d_controller.h"
#include "scene/gui/check_button.h"
#include "scene/gui/panel_container.h"
#include "scene/gui/tab_container.h"
@ -86,7 +87,7 @@ void EditorSettingsDialog::_settings_property_edited() {
} else if (full_name.begins_with("editors/visual_editors/connection_colors") || full_name.begins_with("editors/visual_editors/category_colors")) {
EditorSettings::get_singleton()->set_manually("editors/visual_editors/color_theme", "Custom");
} else if (full_name == "editors/3d/navigation/orbit_mouse_button" || full_name == "editors/3d/navigation/pan_mouse_button" || full_name == "editors/3d/navigation/zoom_mouse_button" || full_name == "editors/3d/navigation/emulate_3_button_mouse") {
EditorSettings::get_singleton()->set_manually("editors/3d/navigation/navigation_scheme", (int)Node3DEditorViewport::NAVIGATION_CUSTOM);
EditorSettings::get_singleton()->set_manually("editors/3d/navigation/navigation_scheme", (int)View3DController::NAV_SCHEME_CUSTOM);
} else if (full_name == "editors/3d/navigation/navigation_scheme") {
update_navigation_preset();
_update_shortcuts();
@ -94,10 +95,10 @@ void EditorSettingsDialog::_settings_property_edited() {
}
void EditorSettingsDialog::update_navigation_preset() {
Node3DEditorViewport::NavigationScheme nav_scheme = (Node3DEditorViewport::NavigationScheme)EDITOR_GET("editors/3d/navigation/navigation_scheme").operator int();
Node3DEditorViewport::ViewportNavMouseButton set_orbit_mouse_button = Node3DEditorViewport::NAVIGATION_LEFT_MOUSE;
Node3DEditorViewport::ViewportNavMouseButton set_pan_mouse_button = Node3DEditorViewport::NAVIGATION_LEFT_MOUSE;
Node3DEditorViewport::ViewportNavMouseButton set_zoom_mouse_button = Node3DEditorViewport::NAVIGATION_LEFT_MOUSE;
View3DController::NavigationScheme nav_scheme = (View3DController::NavigationScheme)EDITOR_GET("editors/3d/navigation/navigation_scheme").operator int();
View3DController::NavigationMouseButton set_orbit_mouse_button = View3DController::NAV_MOUSE_BUTTON_LEFT;
View3DController::NavigationMouseButton set_pan_mouse_button = View3DController::NAV_MOUSE_BUTTON_LEFT;
View3DController::NavigationMouseButton set_zoom_mouse_button = View3DController::NAV_MOUSE_BUTTON_LEFT;
bool set_3_button_mouse = false;
Ref<InputEventKey> orbit_mod_key_1;
Ref<InputEventKey> orbit_mod_key_2;
@ -109,11 +110,11 @@ void EditorSettingsDialog::update_navigation_preset() {
Ref<InputEventKey> orbit_snap_mod_key_2;
bool set_preset = false;
if (nav_scheme == Node3DEditorViewport::NAVIGATION_GODOT) {
if (nav_scheme == View3DController::NAV_SCHEME_GODOT) {
set_preset = true;
set_orbit_mouse_button = Node3DEditorViewport::NAVIGATION_MIDDLE_MOUSE;
set_pan_mouse_button = Node3DEditorViewport::NAVIGATION_MIDDLE_MOUSE;
set_zoom_mouse_button = Node3DEditorViewport::NAVIGATION_MIDDLE_MOUSE;
set_orbit_mouse_button = View3DController::NAV_MOUSE_BUTTON_MIDDLE;
set_pan_mouse_button = View3DController::NAV_MOUSE_BUTTON_MIDDLE;
set_zoom_mouse_button = View3DController::NAV_MOUSE_BUTTON_MIDDLE;
set_3_button_mouse = false;
orbit_mod_key_1 = InputEventKey::create_reference(Key::NONE);
orbit_mod_key_2 = InputEventKey::create_reference(Key::NONE);
@ -123,11 +124,11 @@ void EditorSettingsDialog::update_navigation_preset() {
zoom_mod_key_2 = InputEventKey::create_reference(Key::NONE);
orbit_snap_mod_key_1 = InputEventKey::create_reference(Key::ALT);
orbit_snap_mod_key_2 = InputEventKey::create_reference(Key::NONE);
} else if (nav_scheme == Node3DEditorViewport::NAVIGATION_MAYA) {
} else if (nav_scheme == View3DController::NAV_SCHEME_MAYA) {
set_preset = true;
set_orbit_mouse_button = Node3DEditorViewport::NAVIGATION_LEFT_MOUSE;
set_pan_mouse_button = Node3DEditorViewport::NAVIGATION_MIDDLE_MOUSE;
set_zoom_mouse_button = Node3DEditorViewport::NAVIGATION_RIGHT_MOUSE;
set_orbit_mouse_button = View3DController::NAV_MOUSE_BUTTON_LEFT;
set_pan_mouse_button = View3DController::NAV_MOUSE_BUTTON_MIDDLE;
set_zoom_mouse_button = View3DController::NAV_MOUSE_BUTTON_RIGHT;
set_3_button_mouse = false;
orbit_mod_key_1 = InputEventKey::create_reference(Key::ALT);
orbit_mod_key_2 = InputEventKey::create_reference(Key::NONE);
@ -137,11 +138,11 @@ void EditorSettingsDialog::update_navigation_preset() {
zoom_mod_key_2 = InputEventKey::create_reference(Key::NONE);
orbit_snap_mod_key_1 = InputEventKey::create_reference(Key::NONE);
orbit_snap_mod_key_2 = InputEventKey::create_reference(Key::NONE);
} else if (nav_scheme == Node3DEditorViewport::NAVIGATION_MODO) {
} else if (nav_scheme == View3DController::NAV_SCHEME_MODO) {
set_preset = true;
set_orbit_mouse_button = Node3DEditorViewport::NAVIGATION_LEFT_MOUSE;
set_pan_mouse_button = Node3DEditorViewport::NAVIGATION_LEFT_MOUSE;
set_zoom_mouse_button = Node3DEditorViewport::NAVIGATION_LEFT_MOUSE;
set_orbit_mouse_button = View3DController::NAV_MOUSE_BUTTON_LEFT;
set_pan_mouse_button = View3DController::NAV_MOUSE_BUTTON_LEFT;
set_zoom_mouse_button = View3DController::NAV_MOUSE_BUTTON_LEFT;
set_3_button_mouse = false;
orbit_mod_key_1 = InputEventKey::create_reference(Key::ALT);
orbit_mod_key_2 = InputEventKey::create_reference(Key::NONE);
@ -151,11 +152,11 @@ void EditorSettingsDialog::update_navigation_preset() {
zoom_mod_key_2 = InputEventKey::create_reference(Key::CTRL);
orbit_snap_mod_key_1 = InputEventKey::create_reference(Key::NONE);
orbit_snap_mod_key_2 = InputEventKey::create_reference(Key::NONE);
} else if (nav_scheme == Node3DEditorViewport::NAVIGATION_TABLET) {
} else if (nav_scheme == View3DController::NAV_SCHEME_TABLET) {
set_preset = true;
set_orbit_mouse_button = Node3DEditorViewport::NAVIGATION_MIDDLE_MOUSE;
set_pan_mouse_button = Node3DEditorViewport::NAVIGATION_MIDDLE_MOUSE;
set_zoom_mouse_button = Node3DEditorViewport::NAVIGATION_MIDDLE_MOUSE;
set_orbit_mouse_button = View3DController::NAV_MOUSE_BUTTON_MIDDLE;
set_pan_mouse_button = View3DController::NAV_MOUSE_BUTTON_MIDDLE;
set_zoom_mouse_button = View3DController::NAV_MOUSE_BUTTON_MIDDLE;
set_3_button_mouse = true;
orbit_mod_key_1 = InputEventKey::create_reference(Key::ALT);
orbit_mod_key_2 = InputEventKey::create_reference(Key::NONE);
@ -428,7 +429,7 @@ void EditorSettingsDialog::_update_shortcut_events(const String &p_path, const A
bool path_is_pan_mod = p_path == "spatial_editor/viewport_pan_modifier_1" || p_path == "spatial_editor/viewport_pan_modifier_2";
bool path_is_zoom_mod = p_path == "spatial_editor/viewport_zoom_modifier_1" || p_path == "spatial_editor/viewport_zoom_modifier_2";
if (path_is_orbit_mod || path_is_pan_mod || path_is_zoom_mod) {
EditorSettings::get_singleton()->set_manually("editors/3d/navigation/navigation_scheme", (int)Node3DEditorViewport::NAVIGATION_CUSTOM);
EditorSettings::get_singleton()->set_manually("editors/3d/navigation/navigation_scheme", (int)View3DController::NAV_SCHEME_CUSTOM);
}
}

View file

@ -47,13 +47,13 @@
#include "editor/settings/editor_settings.h"
#include "editor/themes/editor_scale.h"
#include "scene/3d/camera_3d.h"
#include "scene/debugger/view_3d_controller.h"
#include "scene/gui/dialogs.h"
#include "scene/gui/item_list.h"
#include "scene/gui/label.h"
#include "scene/gui/margin_container.h"
#include "scene/gui/menu_button.h"
#include "scene/gui/separator.h"
#include "scene/gui/slider.h"
#include "scene/gui/spin_box.h"
#include "scene/main/scene_tree.h"
#include "scene/main/window.h"
@ -861,8 +861,8 @@ EditorPlugin::AfterGUIInput GridMapEditor::forward_spatial_input_event(Camera3D
if (mb->is_pressed()) {
if (mb->get_button_index() == MouseButton::LEFT) {
Node3DEditorViewport::NavigationScheme nav_scheme = (Node3DEditorViewport::NavigationScheme)EDITOR_GET("editors/3d/navigation/navigation_scheme").operator int();
if ((nav_scheme == Node3DEditorViewport::NAVIGATION_MAYA || nav_scheme == Node3DEditorViewport::NAVIGATION_MODO) && mb->is_alt_pressed()) {
View3DController::NavigationScheme nav_scheme = (View3DController::NavigationScheme)EDITOR_GET("editors/3d/navigation/navigation_scheme").operator int();
if ((nav_scheme == View3DController::NAV_SCHEME_MAYA || nav_scheme == View3DController::NAV_SCHEME_MODO) && mb->is_alt_pressed()) {
return EditorPlugin::AFTER_GUI_INPUT_PASS;
}

View file

@ -43,6 +43,7 @@
#include "scene/2d/camera_2d.h"
#include "scene/debugger/scene_debugger_object.h"
#include "scene/gui/popup_menu.h"
#include "scene/gui/view_panner.h"
#include "scene/main/canvas_layer.h"
#include "scene/main/scene_tree.h"
#include "scene/resources/mesh.h"
@ -91,6 +92,8 @@ void RuntimeNodeSelect::_setup(const Dictionary &p_settings) {
max_selection = p_settings.get("debugger/max_node_selection", 1);
// Panner Setup
panner.instantiate();
panner->set_callbacks(callable_mp(this, &RuntimeNodeSelect::_pan_callback), callable_mp(this, &RuntimeNodeSelect::_zoom_callback));
@ -119,20 +122,79 @@ void RuntimeNodeSelect::_setup(const Dictionary &p_settings) {
RS::get_singleton()->canvas_item_set_parent(sbox_2d_ci, draw_canvas);
#ifndef _3D_DISABLED
cursor = Cursor();
camera_fov = p_settings.get("editors/3d/default_fov", 70);
camera_znear = p_settings.get("editors/3d/default_z_near", 0.05);
camera_zfar = p_settings.get("editors/3d/default_z_far", 4'000);
invert_x_axis = p_settings.get("editors/3d/navigation/invert_x_axis", false);
invert_y_axis = p_settings.get("editors/3d/navigation/invert_y_axis", false);
warped_mouse_panning_3d = p_settings.get("editors/3d/navigation/warped_mouse_panning", true);
// View3DController Setup
freelook_base_speed = p_settings.get("editors/3d/freelook/freelook_base_speed", 5);
freelook_sensitivity = Math::deg_to_rad((real_t)p_settings.get("editors/3d/freelook/freelook_sensitivity", 0.25));
orbit_sensitivity = Math::deg_to_rad((real_t)p_settings.get("editors/3d/navigation_feel/orbit_sensitivity", 0.004));
translation_sensitivity = p_settings.get("editors/3d/navigation_feel/translation_sensitivity", 1);
view_3d_controller.instantiate();
view_3d_controller->set_freelook_scheme((View3DController::FreelookScheme)p_settings.get("editors/3d/freelook/freelook_navigation_scheme", View3DController::FREELOOK_DEFAULT).operator int());
view_3d_controller->set_freelook_base_speed(p_settings.get("editors/3d/freelook/freelook_base_speed", 5));
view_3d_controller->set_freelook_sensitivity(p_settings.get("editors/3d/freelook/freelook_sensitivity", 0.25));
view_3d_controller->set_freelook_inertia(p_settings.get("editors/3d/freelook/freelook_inertia", 0));
view_3d_controller->set_freelook_speed_zoom_link(p_settings.get("editors/3d/freelook/freelook_speed_zoom_link", false));
view_3d_controller->set_freelook_invert_y_axis(p_settings.get("editors/3d/freelook/freelook_invert_y_axis", false));
view_3d_controller->set_translation_sensitivity(p_settings.get("editors/3d/navigation_feel/translation_sensitivity", 1));
view_3d_controller->set_translation_inertia(p_settings.get("editors/3d/navigation_feel/translation_inertia", 0));
view_3d_controller->set_pan_mouse_button(p_settings.get("editors/3d/navigation/pan_mouse_button", View3DController::NAV_MOUSE_BUTTON_MIDDLE));
view_3d_controller->set_orbit_mouse_button(p_settings.get("editors/3d/navigation/orbit_mouse_button", View3DController::NAV_MOUSE_BUTTON_MIDDLE));
view_3d_controller->set_orbit_sensitivity(p_settings.get("editors/3d/navigation_feel/orbit_sensitivity", 0.004));
view_3d_controller->set_orbit_inertia(p_settings.get("editors/3d/navigation_feel/orbit_inertia", 0));
view_3d_controller->set_zoom_style(p_settings.get("editors/3d/navigation/zoom_style", View3DController::ZOOM_VERTICAL));
view_3d_controller->set_zoom_inertia(p_settings.get("editors/3d/navigation_feel/zoom_inertia", 0));
view_3d_controller->set_zoom_mouse_button(p_settings.get("editors/3d/navigation/zoom_mouse_button", View3DController::NAV_MOUSE_BUTTON_MIDDLE));
view_3d_controller->set_angle_snap_threshold(p_settings.get("editors/3d/navigation_feel/angle_snap_threshold", 10));
view_3d_controller->set_emulate_3_button_mouse(p_settings.get("editors/3d/navigation/emulate_3_button_mouse", false));
view_3d_controller->set_emulate_numpad(p_settings.get("editors/3d/navigation/emulate_numpad", true));
view_3d_controller->set_z_near(camera_znear);
view_3d_controller->set_z_far(camera_zfar);
view_3d_controller->set_invert_x_axis(p_settings.get("editors/3d/navigation/invert_x_axis", false));
view_3d_controller->set_invert_x_axis(p_settings.get("editors/3d/navigation/invert_y_axis", false));
view_3d_controller->set_warped_mouse_panning(p_settings.get("editors/3d/navigation/warped_mouse_panning", true));
view_3d_controller->connect("fov_scaled", callable_mp(this, &RuntimeNodeSelect::_fov_scaled));
view_3d_controller->connect("cursor_interpolated", callable_mp(this, &RuntimeNodeSelect::_cursor_interpolated));
#define SET_SHORTCUT(p_name, p_setting) \
{ \
Ref<Shortcut> shortcut = DebuggerMarshalls::deserialize_key_shortcut(p_settings.get(p_setting, Array()).operator Array()); \
if (shortcut.is_valid()) { \
view_3d_controller->set_shortcut(p_name, shortcut); \
} \
}
SET_SHORTCUT(View3DController::SHORTCUT_FOV_DECREASE, "spatial_editor/decrease_fov");
SET_SHORTCUT(View3DController::SHORTCUT_FOV_INCREASE, "spatial_editor/increase_fov");
SET_SHORTCUT(View3DController::SHORTCUT_FOV_RESET, "spatial_editor/reset_fov");
SET_SHORTCUT(View3DController::SHORTCUT_PAN_MOD_1, "spatial_editor/viewport_pan_modifier_1");
SET_SHORTCUT(View3DController::SHORTCUT_PAN_MOD_2, "spatial_editor/viewport_pan_modifier_2");
SET_SHORTCUT(View3DController::SHORTCUT_ORBIT_MOD_1, "spatial_editor/viewport_orbit_modifier_1");
SET_SHORTCUT(View3DController::SHORTCUT_ORBIT_MOD_2, "spatial_editor/viewport_orbit_modifier_2");
SET_SHORTCUT(View3DController::SHORTCUT_ORBIT_SNAP_MOD_1, "spatial_editor/viewport_orbit_snap_modifier_1");
SET_SHORTCUT(View3DController::SHORTCUT_ORBIT_SNAP_MOD_2, "spatial_editor/viewport_orbit_snap_modifier_2");
SET_SHORTCUT(View3DController::SHORTCUT_ZOOM_MOD_1, "spatial_editor/viewport_zoom_modifier_1");
SET_SHORTCUT(View3DController::SHORTCUT_ZOOM_MOD_2, "spatial_editor/viewport_zoom_modifier_2");
SET_SHORTCUT(View3DController::SHORTCUT_FREELOOK_FORWARD, "spatial_editor/freelook_forward");
SET_SHORTCUT(View3DController::SHORTCUT_FREELOOK_BACKWARDS, "spatial_editor/freelook_backwards");
SET_SHORTCUT(View3DController::SHORTCUT_FREELOOK_LEFT, "spatial_editor/freelook_left");
SET_SHORTCUT(View3DController::SHORTCUT_FREELOOK_RIGHT, "spatial_editor/freelook_right");
SET_SHORTCUT(View3DController::SHORTCUT_FREELOOK_UP, "spatial_editor/freelook_up");
SET_SHORTCUT(View3DController::SHORTCUT_FREELOOK_DOWN, "spatial_editor/freelook_down");
SET_SHORTCUT(View3DController::SHORTCUT_FREELOOK_SPEED_MOD, "spatial_editor/freelook_speed_modifier");
SET_SHORTCUT(View3DController::SHORTCUT_FREELOOK_SLOW_MOD, "spatial_editor/freelook_slow_modifier");
#undef SET_SHORTCUT
/// 3D Selection Box Generation
// Copied from the Node3DEditor implementation.
@ -212,8 +274,8 @@ void RuntimeNodeSelect::_set_camera_override_enabled(bool p_enabled) {
Window *root = SceneTree::get_singleton()->get_root();
ERR_FAIL_COND(!root->is_camera_3d_override_enabled());
Camera3D *override_camera = root->get_override_camera_3d();
override_camera->set_transform(_get_cursor_transform());
override_camera->set_perspective(camera_fov * cursor.fov_scale, camera_znear, camera_zfar);
override_camera->set_transform(view_3d_controller->to_camera_transform());
override_camera->set_perspective(camera_fov * view_3d_controller->cursor.fov_scale, camera_znear, camera_zfar);
#endif // _3D_DISABLED
}
}
@ -311,64 +373,24 @@ void RuntimeNodeSelect::_update_input_state() {
void RuntimeNodeSelect::_process_frame() {
#ifndef _3D_DISABLED
if (camera_freelook) {
Transform3D transform = _get_cursor_transform();
Vector3 forward = transform.basis.xform(Vector3(0, 0, -1));
const Vector3 right = transform.basis.xform(Vector3(1, 0, 0));
Vector3 up = transform.basis.xform(Vector3(0, 1, 0));
Vector3 direction;
// Calculate the process time manually, as the time scale can be frozen.
const double process_time = (1.0 / Engine::get_singleton()->get_frames_per_second()) * Engine::get_singleton()->get_unfrozen_time_scale();
if (view_3d_controller->is_freelook_enabled()) {
Input *input = Input::get_singleton();
bool was_input_disabled = input->is_input_disabled();
if (was_input_disabled) {
input->set_disable_input(false);
}
if (input->is_physical_key_pressed(Key::A)) {
direction -= right;
}
if (input->is_physical_key_pressed(Key::D)) {
direction += right;
}
if (input->is_physical_key_pressed(Key::W)) {
direction += forward;
}
if (input->is_physical_key_pressed(Key::S)) {
direction -= forward;
}
if (input->is_physical_key_pressed(Key::E)) {
direction += up;
}
if (input->is_physical_key_pressed(Key::Q)) {
direction -= up;
}
real_t speed = freelook_base_speed;
if (input->is_physical_key_pressed(Key::SHIFT)) {
speed *= 3.0;
}
if (input->is_physical_key_pressed(Key::ALT)) {
speed *= 0.333333;
}
view_3d_controller->update_freelook(process_time);
if (was_input_disabled) {
input->set_disable_input(true);
}
if (direction != Vector3()) {
Window *root = SceneTree::get_singleton()->get_root();
ERR_FAIL_COND(!root->is_camera_3d_override_enabled());
// Calculate the process time manually, as the time scale is frozen.
const double process_time = (1.0 / Engine::get_singleton()->get_frames_per_second()) * Engine::get_singleton()->get_unfrozen_time_scale();
const Vector3 motion = direction * speed * process_time;
cursor.pos += motion;
cursor.eye_pos += motion;
root->get_override_camera_3d()->set_transform(_get_cursor_transform());
}
}
view_3d_controller->update_camera(process_time);
#endif // _3D_DISABLED
if (selection_update_queued || !SceneTree::get_singleton()->is_suspended()) {
@ -1390,231 +1412,58 @@ Vector3 RuntimeNodeSelect::_get_screen_to_space(const Vector3 &p_vector3) {
return camera_transform.xform(Vector3(((p_vector3.x / size.width) * 2.0 - 1.0) * screen_he.x, ((1.0 - (p_vector3.y / size.height)) * 2.0 - 1.0) * screen_he.y, -(znear + p_vector3.z)));
}
void RuntimeNodeSelect::_fov_scaled() {
SceneTree::get_singleton()->get_root()->get_override_camera_3d()->set_perspective(camera_fov * view_3d_controller->cursor.fov_scale, camera_znear, camera_zfar);
}
void RuntimeNodeSelect::_cursor_interpolated() {
Window *root = SceneTree::get_singleton()->get_root();
ERR_FAIL_COND(!root->is_camera_3d_override_enabled());
root->get_override_camera_3d()->set_transform(view_3d_controller->interp_to_camera_transform());
}
bool RuntimeNodeSelect::_handle_3d_input(const Ref<InputEvent> &p_event) {
Ref<InputEventMouseButton> b = p_event;
if (b.is_valid()) {
const real_t zoom_factor = 1.08 * b->get_factor();
switch (b->get_button_index()) {
case MouseButton::WHEEL_UP: {
if (!camera_freelook) {
_cursor_scale_distance(1.0 / zoom_factor);
} else {
_scale_freelook_speed(zoom_factor);
}
return true;
} break;
case MouseButton::WHEEL_DOWN: {
if (!camera_freelook) {
_cursor_scale_distance(zoom_factor);
} else {
_scale_freelook_speed(1.0 / zoom_factor);
}
return true;
} break;
case MouseButton::RIGHT: {
_set_camera_freelook_enabled(b->is_pressed());
return true;
} break;
default: {
}
}
if (b.is_valid() && b->get_button_index() == MouseButton::RIGHT) {
view_3d_controller->set_freelook_enabled(b->is_pressed());
return true;
}
Ref<InputEventMouseMotion> m = p_event;
if (m.is_valid()) {
if (camera_freelook) {
_cursor_look(m);
} else if (m->get_button_mask().has_flag(MouseButtonMask::MIDDLE)) {
if (m->is_shift_pressed()) {
_cursor_pan(m);
} else {
_cursor_orbit(m);
}
}
Window *root = SceneTree::get_singleton()->get_root();
ERR_FAIL_COND_V(!root->is_camera_3d_override_enabled(), true);
Input *input = Input::get_singleton();
bool was_input_disabled = input->is_input_disabled();
if (was_input_disabled) {
input->set_disable_input(false);
}
// Reduce all sides of the area by 1, so warping works when windows are maximized/fullscreen.
bool view_3d_input_received = view_3d_controller->gui_input(p_event, Rect2(Vector2(1, 1), root->get_size() - Vector2(2, 2)));
if (was_input_disabled) {
input->set_disable_input(true);
}
if (view_3d_input_received) {
root->get_override_camera_3d()->set_transform(view_3d_controller->interp_to_camera_transform());
return true;
}
Ref<InputEventKey> k = p_event;
if (k.is_valid()) {
if (k->get_physical_keycode() == Key::ESCAPE) {
_set_camera_freelook_enabled(false);
return true;
} else if (k->is_ctrl_pressed()) {
switch (k->get_physical_keycode()) {
case Key::EQUAL: {
ERR_FAIL_COND_V(!SceneTree::get_singleton()->get_root()->is_camera_3d_override_enabled(), false);
cursor.fov_scale = CLAMP(cursor.fov_scale - 0.05, CAMERA_MIN_FOV_SCALE, CAMERA_MAX_FOV_SCALE);
SceneTree::get_singleton()->get_root()->get_override_camera_3d()->set_perspective(camera_fov * cursor.fov_scale, camera_znear, camera_zfar);
return true;
} break;
case Key::MINUS: {
ERR_FAIL_COND_V(!SceneTree::get_singleton()->get_root()->is_camera_3d_override_enabled(), false);
cursor.fov_scale = CLAMP(cursor.fov_scale + 0.05, CAMERA_MIN_FOV_SCALE, CAMERA_MAX_FOV_SCALE);
SceneTree::get_singleton()->get_root()->get_override_camera_3d()->set_perspective(camera_fov * cursor.fov_scale, camera_znear, camera_zfar);
return true;
} break;
case Key::KEY_0: {
ERR_FAIL_COND_V(!SceneTree::get_singleton()->get_root()->is_camera_3d_override_enabled(), false);
cursor.fov_scale = 1;
SceneTree::get_singleton()->get_root()->get_override_camera_3d()->set_perspective(camera_fov, camera_znear, camera_zfar);
return true;
} break;
default: {
}
}
}
if (k.is_valid() && k->get_physical_keycode() == Key::ESCAPE) {
view_3d_controller->set_freelook_enabled(false);
return true;
}
// TODO: Handle magnify and pan input gestures.
return false;
}
void RuntimeNodeSelect::_set_camera_freelook_enabled(bool p_enabled) {
camera_freelook = p_enabled;
if (p_enabled) {
// Make sure eye_pos is synced, because freelook referential is eye pos rather than orbit pos
Vector3 forward = _get_cursor_transform().basis.xform(Vector3(0, 0, -1));
cursor.eye_pos = cursor.pos - cursor.distance * forward;
previous_mouse_position = SceneTree::get_singleton()->get_root()->get_mouse_position();
// Hide mouse like in an FPS (warping doesn't work).
Input::get_singleton()->set_mouse_mode_override(Input::MouseMode::MOUSE_MODE_CAPTURED);
} else {
// Restore mouse.
Input::get_singleton()->set_mouse_mode_override(Input::MouseMode::MOUSE_MODE_VISIBLE);
// Restore the previous mouse position when leaving freelook mode.
// This is done because leaving `Input.MOUSE_MODE_CAPTURED` will center the cursor
// due to OS limitations.
Input::get_singleton()->warp_mouse(previous_mouse_position);
}
}
void RuntimeNodeSelect::_cursor_scale_distance(real_t p_scale) {
ERR_FAIL_COND(!SceneTree::get_singleton()->get_root()->is_camera_3d_override_enabled());
real_t min_distance = MAX(camera_znear * 4, VIEW_3D_MIN_ZOOM);
real_t max_distance = MIN(camera_zfar / 4, VIEW_3D_MAX_ZOOM);
cursor.distance = CLAMP(cursor.distance * p_scale, min_distance, max_distance);
SceneTree::get_singleton()->get_root()->get_override_camera_3d()->set_transform(_get_cursor_transform());
}
void RuntimeNodeSelect::_scale_freelook_speed(real_t p_scale) {
real_t min_speed = MAX(camera_znear * 4, VIEW_3D_MIN_ZOOM);
real_t max_speed = MIN(camera_zfar / 4, VIEW_3D_MAX_ZOOM);
if (unlikely(min_speed > max_speed)) {
freelook_base_speed = (min_speed + max_speed) / 2;
} else {
freelook_base_speed = CLAMP(freelook_base_speed * p_scale, min_speed, max_speed);
}
}
void RuntimeNodeSelect::_cursor_look(Ref<InputEventWithModifiers> p_event) {
Window *root = SceneTree::get_singleton()->get_root();
ERR_FAIL_COND(!root->is_camera_3d_override_enabled());
const Vector2 relative = _get_warped_mouse_motion(p_event, Rect2(Vector2(), root->get_size()));
const Transform3D prev_camera_transform = _get_cursor_transform();
if (invert_y_axis) {
cursor.x_rot -= relative.y * freelook_sensitivity;
} else {
cursor.x_rot += relative.y * freelook_sensitivity;
}
// Clamp the Y rotation to roughly -90..90 degrees so the user can't look upside-down and end up disoriented.
cursor.x_rot = CLAMP(cursor.x_rot, -1.57, 1.57);
cursor.y_rot += relative.x * freelook_sensitivity;
// Look is like the opposite of Orbit: the focus point rotates around the camera.
Transform3D camera_transform = _get_cursor_transform();
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;
root->get_override_camera_3d()->set_transform(_get_cursor_transform());
}
void RuntimeNodeSelect::_cursor_pan(Ref<InputEventWithModifiers> p_event) {
Window *root = SceneTree::get_singleton()->get_root();
ERR_FAIL_COND(!root->is_camera_3d_override_enabled());
// Reduce all sides of the area by 1, so warping works when windows are maximized/fullscreen.
const Vector2 relative = _get_warped_mouse_motion(p_event, Rect2(Vector2(1, 1), root->get_size() - Vector2(2, 2)));
const real_t pan_speed = translation_sensitivity / 150.0;
Transform3D camera_transform;
camera_transform.translate_local(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(1 * -relative.x * pan_speed, relative.y * pan_speed, 0);
translation *= cursor.distance / 4;
camera_transform.translate_local(translation);
cursor.pos = camera_transform.origin;
root->get_override_camera_3d()->set_transform(_get_cursor_transform());
}
void RuntimeNodeSelect::_cursor_orbit(Ref<InputEventWithModifiers> p_event) {
Window *root = SceneTree::get_singleton()->get_root();
ERR_FAIL_COND(!root->is_camera_3d_override_enabled());
// Reduce all sides of the area by 1, so warping works when windows are maximized/fullscreen.
const Vector2 relative = _get_warped_mouse_motion(p_event, Rect2(Vector2(1, 1), root->get_size() - Vector2(2, 2)));
if (invert_y_axis) {
cursor.x_rot -= relative.y * orbit_sensitivity;
} else {
cursor.x_rot += relative.y * orbit_sensitivity;
}
// Clamp the Y rotation to roughly -90..90 degrees so the user can't look upside-down and end up disoriented.
cursor.x_rot = CLAMP(cursor.x_rot, -1.57, 1.57);
if (invert_x_axis) {
cursor.y_rot -= relative.x * orbit_sensitivity;
} else {
cursor.y_rot += relative.x * orbit_sensitivity;
}
root->get_override_camera_3d()->set_transform(_get_cursor_transform());
}
Point2 RuntimeNodeSelect::_get_warped_mouse_motion(const Ref<InputEventMouseMotion> &p_event, Rect2 p_area) const {
ERR_FAIL_COND_V(p_event.is_null(), Point2());
if (warped_mouse_panning_3d) {
return Input::get_singleton()->warp_mouse_motion(p_event, p_area);
}
return p_event->get_relative();
}
Transform3D RuntimeNodeSelect::_get_cursor_transform() {
Transform3D camera_transform;
camera_transform.translate_local(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_local(0, 0, cursor.distance);
return camera_transform;
}
void RuntimeNodeSelect::_reset_camera_3d() {
camera_first_override = true;
cursor = Cursor();
View3DController::Cursor cursor;
Window *root = SceneTree::get_singleton()->get_root();
Camera3D *game_camera = root->is_camera_3d_override_enabled() ? root->get_overridden_camera_3d() : root->get_camera_3d();
if (game_camera) {
@ -1624,15 +1473,18 @@ void RuntimeNodeSelect::_reset_camera_3d() {
cursor.x_rot = -game_camera->get_global_rotation().x;
cursor.y_rot = -game_camera->get_global_rotation().y;
cursor.unsnapped_x_rot = cursor.x_rot;
cursor.unsnapped_y_rot = cursor.y_rot;
cursor.fov_scale = CLAMP(game_camera->get_fov() / camera_fov, CAMERA_MIN_FOV_SCALE, CAMERA_MAX_FOV_SCALE);
} else {
cursor.fov_scale = 1.0;
cursor.fov_scale = CLAMP(game_camera->get_fov() / camera_fov, View3DControllerConsts::CAMERA_MIN_FOV_SCALE, View3DControllerConsts::CAMERA_MAX_FOV_SCALE);
}
view_3d_controller->cursor = cursor;
if (root->is_camera_3d_override_enabled()) {
view_3d_controller->update_camera();
Camera3D *override_camera = root->get_override_camera_3d();
override_camera->set_transform(_get_cursor_transform());
override_camera->set_transform(view_3d_controller->to_camera_transform());
override_camera->set_perspective(camera_fov * cursor.fov_scale, camera_znear, camera_zfar);
}
}

View file

@ -32,14 +32,17 @@
#ifdef DEBUG_ENABLED
#include "core/object/object.h"
#include "scene/gui/view_panner.h"
#ifndef _3D_DISABLED
#include "scene/debugger/view_3d_controller.h"
#include "scene/resources/mesh.h"
#endif // _3D_DISABLED
class InputEvent;
class InputEventMouseMotion;
class InputEventWithModifiers;
class Node;
class PopupMenu;
class ViewPanner;
#ifndef _3D_DISABLED
class ArrayMesh;
@ -119,49 +122,12 @@ private:
RID sbox_2d_ci;
#ifndef _3D_DISABLED
struct Cursor {
Vector3 pos;
real_t x_rot, y_rot, distance, fov_scale;
Vector3 eye_pos; // Used in freelook mode.
Cursor() {
// These rotations place the camera in +X +Y +Z, aka south east, facing north west.
x_rot = 0.5;
y_rot = -0.5;
distance = 4;
fov_scale = 1.0;
}
};
Cursor cursor;
// Values taken from Node3DEditor.
const float VIEW_3D_MIN_ZOOM = 0.01;
#ifdef REAL_T_IS_DOUBLE
const double VIEW_3D_MAX_ZOOM = 1'000'000'000'000;
#else
const float VIEW_3D_MAX_ZOOM = 10'000;
#endif // REAL_T_IS_DOUBLE
const float CAMERA_MIN_FOV_SCALE = 0.1;
const float CAMERA_MAX_FOV_SCALE = 2.5;
bool camera_freelook = false;
Ref<View3DController> view_3d_controller;
real_t camera_fov = 0;
real_t camera_znear = 0;
real_t camera_zfar = 0;
bool invert_x_axis = false;
bool invert_y_axis = false;
bool warped_mouse_panning_3d = false;
real_t freelook_base_speed = 0;
real_t freelook_sensitivity = 0;
real_t orbit_sensitivity = 0;
real_t translation_sensitivity = 0;
Vector2 previous_mouse_position;
struct SelectionBox3D : public RefCounted {
RID instance;
RID instance_ofs;
@ -223,15 +189,10 @@ private:
void _find_3d_items_at_rect(const Rect2 &p_rect, Vector<SelectResult> &r_items);
Vector3 _get_screen_to_space(const Vector3 &p_vector3);
void _fov_scaled();
void _cursor_interpolated();
bool _handle_3d_input(const Ref<InputEvent> &p_event);
void _set_camera_freelook_enabled(bool p_enabled);
void _cursor_scale_distance(real_t p_scale);
void _scale_freelook_speed(real_t p_scale);
void _cursor_look(Ref<InputEventWithModifiers> p_event);
void _cursor_pan(Ref<InputEventWithModifiers> p_event);
void _cursor_orbit(Ref<InputEventWithModifiers> p_event);
Point2 _get_warped_mouse_motion(const Ref<InputEventMouseMotion> &p_event, Rect2 p_border) const;
Transform3D _get_cursor_transform();
void _reset_camera_3d();
#endif // _3D_DISABLED

View file

@ -0,0 +1,763 @@
/**************************************************************************/
/* view_3d_controller.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef _3D_DISABLED
#include "view_3d_controller.h"
#include "core/config/engine.h"
#include "core/input/input.h"
#include "core/input/shortcut.h"
#include "core/object/class_db.h"
#include "scene/main/scene_tree.h"
#include "scene/main/window.h"
using namespace View3DControllerConsts;
Transform3D View3DController::_to_camera_transform(const Cursor &p_cursor) const {
Transform3D camera_transform;
camera_transform.translate_local(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_local(0, 0, (zfar - znear) / 2.0);
} else {
camera_transform.translate_local(0, 0, p_cursor.distance);
}
return camera_transform;
}
bool View3DController::_is_shortcut_pressed(const ShortcutName p_name, const bool p_true_if_null) {
Ref<Shortcut> shortcut = inputs[p_name];
if (shortcut.is_null()) {
return p_true_if_null;
}
const Array shortcuts = shortcut->get_events();
Ref<InputEventKey> k;
if (shortcuts.size() > 0) {
k = shortcuts.front();
}
if (k.is_null()) {
return p_true_if_null;
}
#define EMULATE_NUMPAD_KEY(p_code) \
(emulate_numpad && p_code >= Key::KEY_0 && p_code <= Key::KEY_9 ? p_code - Key::KEY_0 + Key::KP_0 : p_code)
if (k->get_physical_keycode() == Key::NONE) {
return Input::get_singleton()->is_key_pressed(EMULATE_NUMPAD_KEY(k->get_keycode()));
}
return Input::get_singleton()->is_physical_key_pressed(EMULATE_NUMPAD_KEY(k->get_physical_keycode()));
#undef EMULATE_NUMPAD_KEY
}
bool View3DController::_is_shortcut_empty(const ShortcutName p_name) {
Ref<Shortcut> shortcut = inputs[p_name];
if (shortcut.is_null()) {
return true;
}
const Array shortcuts = shortcut->get_events();
Ref<InputEventKey> k;
if (shortcuts.size() > 0) {
k = shortcuts.front();
}
return k.is_null();
}
View3DController::NavigationMode View3DController::_get_nav_mode_from_shortcuts(NavigationMouseButton p_mouse_button, const Vector<ShortcutCheck> &p_shortcut_checks, bool p_not_empty) {
if (p_not_empty) {
for (const ShortcutCheck &shortcut_check : p_shortcut_checks) {
if (shortcut_check.mod_pressed && shortcut_check.not_empty) {
return shortcut_check.result_nav_mode;
}
}
} else {
for (const ShortcutCheck &shortcut_check : p_shortcut_checks) {
if (shortcut_check.mouse_preference == p_mouse_button && shortcut_check.mod_pressed) {
return shortcut_check.result_nav_mode;
}
}
}
return NAV_MODE_NONE;
}
bool View3DController::gui_input(const Ref<InputEvent> &p_event, const Rect2 &p_surface_rect) {
Ref<InputEventMouseButton> b = p_event;
if (b.is_valid()) {
const real_t zoom_factor = 1 + (ZOOM_FREELOOK_MULTIPLIER - 1) * b->get_factor();
switch (b->get_button_index()) {
case MouseButton::WHEEL_UP: {
if (freelook) {
scale_freelook_speed(zoom_factor);
} else {
scale_cursor_distance(1.0 / zoom_factor);
}
} break;
case MouseButton::WHEEL_DOWN: {
if (freelook) {
scale_freelook_speed(1.0 / zoom_factor);
} else {
scale_cursor_distance(zoom_factor);
}
} break;
default: {
return false;
}
}
return true;
}
Vector<ShortcutCheck> shortcut_checks;
if (Input::get_singleton()->get_mouse_mode() != Input::MouseMode::MOUSE_MODE_CAPTURED) {
#define GET_SHORTCUT_COUNT(p_name) (inputs[p_name].is_null() ? 0 : inputs[p_name]->get_events().size())
bool orbit_mod_pressed = _is_shortcut_pressed(SHORTCUT_ORBIT_MOD_1, true) && _is_shortcut_pressed(SHORTCUT_ORBIT_MOD_2, true);
bool pan_mod_pressed = _is_shortcut_pressed(SHORTCUT_PAN_MOD_1, true) && _is_shortcut_pressed(SHORTCUT_PAN_MOD_2, true);
bool zoom_mod_pressed = _is_shortcut_pressed(SHORTCUT_ZOOM_MOD_1, true) && _is_shortcut_pressed(SHORTCUT_ZOOM_MOD_2, true);
int orbit_mod_input_count = GET_SHORTCUT_COUNT(SHORTCUT_ORBIT_MOD_1) + GET_SHORTCUT_COUNT(SHORTCUT_ORBIT_MOD_2);
int pan_mod_input_count = GET_SHORTCUT_COUNT(SHORTCUT_PAN_MOD_1) + GET_SHORTCUT_COUNT(SHORTCUT_PAN_MOD_2);
int zoom_mod_input_count = GET_SHORTCUT_COUNT(SHORTCUT_ZOOM_MOD_1) + GET_SHORTCUT_COUNT(SHORTCUT_ZOOM_MOD_2);
bool orbit_not_empty = !_is_shortcut_empty(SHORTCUT_ORBIT_MOD_1) || !_is_shortcut_empty(SHORTCUT_ORBIT_MOD_2);
bool pan_not_empty = !_is_shortcut_empty(SHORTCUT_PAN_MOD_2) || !_is_shortcut_empty(SHORTCUT_PAN_MOD_2);
bool zoom_not_empty = !_is_shortcut_empty(SHORTCUT_ZOOM_MOD_1) || !_is_shortcut_empty(SHORTCUT_ZOOM_MOD_2);
shortcut_checks.push_back(ShortcutCheck(orbit_mod_pressed, orbit_not_empty, orbit_mod_input_count, orbit_mouse_button, NAV_MODE_ORBIT));
shortcut_checks.push_back(ShortcutCheck(pan_mod_pressed, pan_not_empty, pan_mod_input_count, pan_mouse_button, NAV_MODE_PAN));
shortcut_checks.push_back(ShortcutCheck(zoom_mod_pressed, zoom_not_empty, zoom_mod_input_count, zoom_mouse_button, NAV_MODE_ZOOM));
shortcut_checks.sort_custom<ShortcutCheckSetComparator>();
#undef GET_SHORTCUT_COUNT
}
Ref<InputEventMouseMotion> m = p_event;
if (m.is_valid()) {
NavigationMode nav_mode = NAV_MODE_NONE;
if (m->get_button_mask().has_flag(MouseButtonMask::LEFT)) {
NavigationMode change_nav_from_shortcut = _get_nav_mode_from_shortcuts(NAV_MOUSE_BUTTON_LEFT, shortcut_checks, false);
if (change_nav_from_shortcut != NAV_MODE_NONE) {
nav_mode = change_nav_from_shortcut;
}
} else if (freelook || m->get_button_mask().has_flag(MouseButtonMask::RIGHT)) {
NavigationMode change_nav_from_shortcut = _get_nav_mode_from_shortcuts(NAV_MOUSE_BUTTON_RIGHT, shortcut_checks, false);
if (m->get_button_mask().has_flag(MouseButtonMask::RIGHT) && change_nav_from_shortcut != NAV_MODE_NONE) {
nav_mode = change_nav_from_shortcut;
} else if (freelook) {
nav_mode = NAV_MODE_LOOK;
} else if (orthogonal) {
nav_mode = NAV_MODE_PAN;
}
} else if (m->get_button_mask().has_flag(MouseButtonMask::MIDDLE)) {
NavigationMode change_nav_from_shortcut = _get_nav_mode_from_shortcuts(NAV_MOUSE_BUTTON_MIDDLE, shortcut_checks, false);
if (change_nav_from_shortcut != NAV_MODE_NONE) {
nav_mode = change_nav_from_shortcut;
}
} else if (m->get_button_mask().has_flag(MouseButtonMask::MB_XBUTTON1)) {
NavigationMode change_nav_from_shortcut = _get_nav_mode_from_shortcuts(NAV_MOUSE_BUTTON_4, shortcut_checks, false);
if (change_nav_from_shortcut != NAV_MODE_NONE) {
nav_mode = change_nav_from_shortcut;
}
} else if (m->get_button_mask().has_flag(MouseButtonMask::MB_XBUTTON2)) {
NavigationMode change_nav_from_shortcut = _get_nav_mode_from_shortcuts(NAV_MOUSE_BUTTON_5, shortcut_checks, false);
if (change_nav_from_shortcut != NAV_MODE_NONE) {
nav_mode = change_nav_from_shortcut;
}
} else if (emulate_3_button_mouse) {
// Handle trackpad (no external mouse) use case.
NavigationMode change_nav_from_shortcut = _get_nav_mode_from_shortcuts(NAV_MOUSE_BUTTON_LEFT, shortcut_checks, true);
if (change_nav_from_shortcut != NAV_MODE_NONE) {
nav_mode = change_nav_from_shortcut;
}
}
switch (nav_mode) {
case NAV_MODE_PAN: {
cursor_pan(m, get_warped_mouse_motion(m, p_surface_rect));
} break;
case NAV_MODE_ZOOM: {
cursor_zoom(m, m->get_relative());
} break;
case NAV_MODE_ORBIT: {
cursor_orbit(m, get_warped_mouse_motion(m, p_surface_rect));
} break;
case NAV_MODE_LOOK: {
cursor_look(m, get_warped_mouse_motion(m, p_surface_rect));
} break;
default: {
return false;
}
}
return true;
}
Ref<InputEventMagnifyGesture> magnify_gesture = p_event;
if (magnify_gesture.is_valid()) {
if (freelook) {
scale_freelook_speed(magnify_gesture->get_factor());
} else {
scale_cursor_distance(1.0 / magnify_gesture->get_factor());
}
return true;
}
Ref<InputEventPanGesture> pan_gesture = p_event;
if (pan_gesture.is_valid()) {
NavigationMode nav_mode = NAV_MODE_NONE;
for (const ShortcutCheck &shortcut_check_set : shortcut_checks) {
if (shortcut_check_set.mod_pressed) {
nav_mode = shortcut_check_set.result_nav_mode;
break;
}
}
switch (nav_mode) {
case NAV_MODE_PAN: {
cursor_pan(pan_gesture, -pan_gesture->get_delta());
} break;
case NAV_MODE_ZOOM: {
cursor_zoom(pan_gesture, pan_gesture->get_delta());
} break;
case NAV_MODE_ORBIT: {
cursor_orbit(pan_gesture, -pan_gesture->get_delta());
} break;
case NAV_MODE_LOOK: {
cursor_look(pan_gesture, pan_gesture->get_delta());
} break;
default: {
return false;
}
}
return true;
}
bool pressed = false;
float old_fov_scale = cursor.fov_scale;
if (_is_shortcut_pressed(SHORTCUT_FOV_DECREASE)) {
cursor.fov_scale = CLAMP(cursor.fov_scale - 0.05, CAMERA_MIN_FOV_SCALE, CAMERA_MAX_FOV_SCALE);
pressed = true;
}
if (_is_shortcut_pressed(SHORTCUT_FOV_INCREASE)) {
cursor.fov_scale = CLAMP(cursor.fov_scale + 0.05, CAMERA_MIN_FOV_SCALE, CAMERA_MAX_FOV_SCALE);
pressed = true;
}
if (_is_shortcut_pressed(SHORTCUT_FOV_RESET)) {
cursor.fov_scale = 1;
pressed = true;
}
if (old_fov_scale != cursor.fov_scale) {
emit_signal(SNAME("fov_scaled"));
}
return pressed;
}
void View3DController::cursor_pan(const Ref<InputEventWithModifiers> &p_event, const Vector2 &p_relative) {
float pan_speed = translation_sensitivity / 150.0;
if (p_event.is_valid() && navigation_scheme == NAV_SCHEME_MAYA && p_event->is_shift_pressed()) {
pan_speed *= 10;
}
Transform3D camera_transform;
camera_transform.translate_local(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(
(invert_x_axis ? -1 : 1) * -p_relative.x * pan_speed,
(invert_y_axis ? -1 : 1) * p_relative.y * pan_speed,
0);
translation *= cursor.distance / DISTANCE_DEFAULT;
camera_transform.translate_local(translation);
cursor.pos = camera_transform.origin;
emit_signal(SNAME("cursor_panned"));
}
void View3DController::cursor_orbit(const Ref<InputEventWithModifiers> &p_event, const Vector2 &p_relative) {
if (lock_rotation) {
cursor_pan(p_event, p_relative);
return;
}
const float radians_per_pixel = Math::deg_to_rad(orbit_sensitivity);
cursor.unsnapped_x_rot += p_relative.y * radians_per_pixel * (invert_y_axis ? -1 : 1);
cursor.unsnapped_x_rot = CLAMP(cursor.unsnapped_x_rot, -1.57, 1.57);
cursor.unsnapped_y_rot += p_relative.x * radians_per_pixel * (invert_x_axis ? -1 : 1);
cursor.x_rot = cursor.unsnapped_x_rot;
cursor.y_rot = cursor.unsnapped_y_rot;
ViewType new_view_type = VIEW_TYPE_USER;
bool snap_modifier_configured = !_is_shortcut_empty(SHORTCUT_ORBIT_SNAP_MOD_1) || !_is_shortcut_empty(SHORTCUT_ORBIT_SNAP_MOD_2);
if (snap_modifier_configured && _is_shortcut_pressed(SHORTCUT_ORBIT_SNAP_MOD_1, true) && _is_shortcut_pressed(SHORTCUT_ORBIT_SNAP_MOD_2, true)) {
const float snap_angle = Math::deg_to_rad(45.0);
const float snap_threshold = Math::deg_to_rad(angle_snap_threshold);
float x_rot_snapped = Math::snapped(cursor.unsnapped_x_rot, snap_angle);
float y_rot_snapped = Math::snapped(cursor.unsnapped_y_rot, snap_angle);
float x_dist = Math::abs(cursor.unsnapped_x_rot - x_rot_snapped);
float y_dist = Math::abs(cursor.unsnapped_y_rot - y_rot_snapped);
if (x_dist < snap_threshold && y_dist < snap_threshold) {
cursor.x_rot = x_rot_snapped;
cursor.y_rot = y_rot_snapped;
float y_rot_wrapped = Math::wrapf(y_rot_snapped, (float)-Math::PI, (float)Math::PI);
if (Math::abs(x_rot_snapped) < snap_threshold) {
// Only switch to ortho for 90-degree views.
if (Math::abs(y_rot_wrapped) < snap_threshold) {
new_view_type = VIEW_TYPE_FRONT;
} else if (Math::abs(Math::abs(y_rot_wrapped) - Math::PI) < snap_threshold) {
new_view_type = VIEW_TYPE_REAR;
} else if (Math::abs(y_rot_wrapped - Math::PI / 2.0) < snap_threshold) {
new_view_type = VIEW_TYPE_LEFT;
} else if (Math::abs(y_rot_wrapped + Math::PI / 2.0) < snap_threshold) {
new_view_type = VIEW_TYPE_RIGHT;
}
} else if (Math::abs(Math::abs(x_rot_snapped) - Math::PI / 2.0) < snap_threshold) {
if (Math::abs(y_rot_wrapped) < snap_threshold ||
Math::abs(Math::abs(y_rot_wrapped) - Math::PI) < snap_threshold ||
Math::abs(y_rot_wrapped - Math::PI / 2.0) < snap_threshold ||
Math::abs(y_rot_wrapped + Math::PI / 2.0) < snap_threshold) {
new_view_type = x_rot_snapped > 0 ? VIEW_TYPE_TOP : VIEW_TYPE_BOTTOM;
}
}
}
}
set_view_type(new_view_type);
}
void View3DController::cursor_look(const Ref<InputEventWithModifiers> &p_event, const Vector2 &p_relative) {
if (orthogonal) {
cursor_pan(p_event, p_relative);
return;
}
// Scale mouse sensitivity with camera FOV scale when zoomed in to make it easier to point at things.
const float degrees_per_pixel = freelook_sensitivity * MIN(1.0, cursor.fov_scale);
const float radians_per_pixel = Math::deg_to_rad(degrees_per_pixel);
// Note: do NOT assume the camera has the "current" transform, because it is interpolated and may have "lag".
const Transform3D prev_camera_transform = to_camera_transform();
if (freelook_invert_y_axis) {
cursor.x_rot -= p_relative.y * radians_per_pixel;
} else {
cursor.x_rot += p_relative.y * radians_per_pixel;
}
// Clamp the Y rotation to roughly -90..90 degrees so the user can't look upside-down and end up disoriented.
cursor.x_rot = CLAMP(cursor.x_rot, -1.57, 1.57);
cursor.unsnapped_x_rot = cursor.x_rot;
cursor.y_rot += p_relative.x * radians_per_pixel;
cursor.unsnapped_y_rot = cursor.y_rot;
// Look is like the opposite of Orbit: the focus point rotates around the camera
Transform3D camera_transform = to_camera_transform();
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;
set_view_type(VIEW_TYPE_USER);
}
void View3DController::cursor_zoom(const Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative) {
float zoom_speed = 1 / 80.0;
if (p_event.is_valid() && navigation_scheme == NAV_SCHEME_MAYA && p_event->is_shift_pressed()) {
zoom_speed *= 10;
}
if (zoom_style == 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 View3DController::update_camera(const real_t p_delta) {
View3DController::Cursor old_camera_cursor = cursor_interp;
cursor_interp = cursor;
bool equal = true;
if (p_delta > 0) {
// Perform smoothing.
if (freelook) {
// 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.
float factor = (1.0 / freelook_inertia) * p_delta;
// We interpolate a different point here, because in freelook mode the focus point (cursor.pos) orbits around eye_pos
cursor_interp.eye_pos = old_camera_cursor.eye_pos.lerp(cursor.eye_pos, CLAMP(factor, 0, 1));
}
cursor_interp.x_rot = Math::lerp(old_camera_cursor.x_rot, cursor.x_rot, MIN(1.f, p_delta * (1 / orbit_inertia)));
cursor_interp.y_rot = Math::lerp(old_camera_cursor.y_rot, cursor.y_rot, MIN(1.f, p_delta * (1 / orbit_inertia)));
if (Math::abs(cursor_interp.x_rot - cursor.x_rot) < 0.1) {
cursor_interp.x_rot = cursor.x_rot;
}
if (Math::abs(cursor_interp.y_rot - cursor.y_rot) < 0.1) {
cursor_interp.y_rot = cursor.y_rot;
}
if (freelook) {
Vector3 forward = _to_camera_transform(cursor_interp).basis.xform(Vector3(0, 0, -1));
cursor_interp.pos = cursor_interp.eye_pos + forward * cursor_interp.distance;
} else {
cursor_interp.pos = old_camera_cursor.pos.lerp(cursor.pos, MIN(1.f, p_delta * (1 / translation_inertia)));
cursor_interp.distance = Math::lerp(old_camera_cursor.distance, cursor.distance, MIN((float)1.0, p_delta * (1 / zoom_inertia)));
}
// Apply camera transform.
const real_t tolerance = 0.001;
if (!Math::is_equal_approx(old_camera_cursor.x_rot, cursor_interp.x_rot, tolerance) || !Math::is_equal_approx(old_camera_cursor.y_rot, cursor_interp.y_rot, tolerance)) {
equal = false;
} else if (!old_camera_cursor.pos.is_equal_approx(cursor_interp.pos)) {
equal = false;
} else if (!Math::is_equal_approx(old_camera_cursor.distance, cursor_interp.distance, tolerance)) {
equal = false;
} else if (!Math::is_equal_approx(old_camera_cursor.fov_scale, cursor_interp.fov_scale, tolerance)) {
equal = false;
}
}
if (p_delta == 0 || !equal) {
emit_signal(SNAME("cursor_interpolated"));
}
}
void View3DController::update_freelook(const float p_delta) {
if (!freelook) {
return;
}
const Transform3D camera_transform = to_camera_transform();
Vector3 forward;
if (freelook_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_transform.get_basis().get_euler().y);
} else {
// Forward/backward keys will be relative to the camera pitch.
forward = camera_transform.basis.xform(Vector3(0, 0, -1));
}
const Vector3 right = camera_transform.basis.xform(Vector3(1, 0, 0));
Vector3 up;
if (freelook_scheme == View3DController::FREELOOK_PARTIALLY_AXIS_LOCKED || freelook_scheme == View3DController::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_transform.basis.xform(Vector3(0, 1, 0));
}
Vector3 direction;
if (_is_shortcut_pressed(SHORTCUT_FREELOOK_LEFT)) {
direction -= right;
}
if (_is_shortcut_pressed(SHORTCUT_FREELOOK_RIGHT)) {
direction += right;
}
if (_is_shortcut_pressed(SHORTCUT_FREELOOK_FORWARD)) {
direction += forward;
}
if (_is_shortcut_pressed(SHORTCUT_FREELOOK_BACKWARDS)) {
direction -= forward;
}
if (_is_shortcut_pressed(SHORTCUT_FREELOOK_UP)) {
direction += up;
}
if (_is_shortcut_pressed(SHORTCUT_FREELOOK_DOWN)) {
direction -= up;
}
real_t speed = freelook_speed;
if (_is_shortcut_pressed(SHORTCUT_FREELOOK_SPEED_MOD)) {
speed *= 3.0;
}
if (_is_shortcut_pressed(SHORTCUT_FREELOOK_SLOW_MOD)) {
speed *= 0.333333;
}
const Vector3 motion = direction * speed * p_delta;
cursor.pos += motion;
cursor.eye_pos += motion;
}
void View3DController::scale_freelook_speed(const float p_scale) {
float min_speed = MAX(znear * 4, ZOOM_FREELOOK_MIN);
float max_speed = MIN(zfar / 4, ZOOM_FREELOOK_MAX);
if (unlikely(min_speed > max_speed)) {
freelook_speed = (min_speed + max_speed) / 2;
} else {
freelook_speed = CLAMP(freelook_speed * p_scale, min_speed, max_speed);
}
emit_signal(SNAME("freelook_speed_scaled"));
}
void View3DController::scale_cursor_distance(const float p_scale) {
float min_distance = MAX(znear * 4, ZOOM_FREELOOK_MIN);
float max_distance = MIN(zfar / 4, ZOOM_FREELOOK_MAX);
if (unlikely(min_distance > max_distance)) {
cursor.distance = (min_distance + max_distance) / 2;
} else {
cursor.distance = CLAMP(cursor.distance * p_scale, min_distance, max_distance);
}
if (cursor.distance == max_distance || cursor.distance == min_distance) {
zoom_failed_attempts_count++;
} else {
zoom_failed_attempts_count = 0;
}
}
void View3DController::set_shortcut(const ShortcutName p_name, const Ref<Shortcut> &p_shortcut) {
ERR_FAIL_INDEX(0, SHORTCUT_MAX);
ERR_FAIL_COND(p_shortcut.is_null());
inputs[p_name] = p_shortcut;
}
void View3DController::set_view_type(const ViewType p_view) {
ViewType view_type_old = view_type;
OrthogonalMode orthogonal_old = orthogonal;
view_type = p_view;
if (view_type != VIEW_TYPE_USER) {
if (auto_orthogonal_allowed && orthogonal != ORTHOGONAL_ENABLED) {
orthogonal = ORTHOGONAL_AUTO;
}
} else if (orthogonal == ORTHOGONAL_AUTO) {
orthogonal = ORTHOGONAL_DISABLED;
}
if (view_type_old != view_type || orthogonal_old != orthogonal) {
emit_signal(SNAME("view_state_changed"));
}
}
String View3DController::get_view_type_name() const {
String name;
switch (view_type) {
case VIEW_TYPE_USER: {
if (orthogonal) {
name = RTR("Orthogonal");
} else {
name = RTR("Perspective");
}
} break;
case VIEW_TYPE_TOP: {
if (orthogonal) {
name = RTR("Top Orthogonal");
} else {
name = RTR("Top Perspective");
}
} break;
case VIEW_TYPE_BOTTOM: {
if (orthogonal) {
name = RTR("Bottom Orthogonal");
} else {
name = RTR("Bottom Perspective");
}
} break;
case VIEW_TYPE_LEFT: {
if (orthogonal) {
name = RTR("Left Orthogonal");
} else {
name = RTR("Left Perspective");
}
} break;
case VIEW_TYPE_RIGHT: {
if (orthogonal) {
name = RTR("Right Orthogonal");
} else {
name = RTR("Right Perspective");
}
} break;
case VIEW_TYPE_FRONT: {
if (orthogonal) {
name = RTR("Front Orthogonal");
} else {
name = RTR("Front Perspective");
}
} break;
case VIEW_TYPE_REAR: {
if (orthogonal) {
name = RTR("Rear Orthogonal");
} else {
name = RTR("Rear Perspective");
}
} break;
}
if (orthogonal == ORTHOGONAL_AUTO) {
// TRANSLATORS: This will be appended to the view name when Auto Orthogonal is enabled.
name += " " + RTR("[auto]");
}
return name;
}
void View3DController::set_freelook_enabled(const bool p_enabled) {
if (freelook == p_enabled) {
return;
}
freelook = p_enabled;
if (freelook) {
// Sync interpolated cursor to cursor to "cut" interpolation jumps due to changing referential.
cursor = cursor_interp;
// Make sure eye_pos is synced, because freelook referential is eye pos rather than orbit pos.
Vector3 forward = to_camera_transform().basis.xform(Vector3(0, 0, -1));
cursor.eye_pos = cursor.pos - cursor.distance * forward;
// Also sync the interpolated cursor's eye_pos, otherwise switching to freelook will be trippy if inertia is active.
cursor_interp.eye_pos = cursor.eye_pos;
if (freelook_speed_zoom_link) {
// Re-adjust freelook speed from the current zoom level.
freelook_speed = freelook_base_speed * cursor.distance;
}
previous_mouse_position = SceneTree::get_singleton()->get_root()->get_mouse_position();
// Hide mouse like in an FPS (warping doesn't work).
if (Engine::get_singleton()->is_editor_hint()) {
Input::get_singleton()->set_mouse_mode(Input::MouseMode::MOUSE_MODE_CAPTURED);
} else {
Input::get_singleton()->set_mouse_mode_override(Input::MouseMode::MOUSE_MODE_CAPTURED);
}
} else {
// Restore mouse.
if (Engine::get_singleton()->is_editor_hint()) {
Input::get_singleton()->set_mouse_mode(Input::MouseMode::MOUSE_MODE_VISIBLE);
} else {
Input::get_singleton()->set_mouse_mode_override(Input::MouseMode::MOUSE_MODE_VISIBLE);
}
// Restore the previous mouse position when leaving freelook mode.
// This is done because leaving `Input.MOUSE_MODE_CAPTURED` will center the cursor
// due to OS limitations.
Input::get_singleton()->warp_mouse(previous_mouse_position);
}
emit_signal(SNAME("freelook_changed"));
}
void View3DController::set_freelook_base_speed(const float p_speed) {
freelook_base_speed = p_speed;
freelook_speed = p_speed;
}
void View3DController::force_auto_orthogonal() {
if (auto_orthogonal_allowed) {
orthogonal = ORTHOGONAL_AUTO;
}
}
void View3DController::set_auto_orthogonal_allowed(const bool p_enabled) {
auto_orthogonal_allowed = p_enabled;
if (!p_enabled && orthogonal == ORTHOGONAL_AUTO) {
orthogonal = ORTHOGONAL_ENABLED;
}
}
Point2 View3DController::get_warped_mouse_motion(const Ref<InputEventMouseMotion> &p_event, const Rect2 &p_surface_rect) const {
if (warped_mouse_panning) {
return Input::get_singleton()->warp_mouse_motion(p_event, p_surface_rect);
}
return p_event->get_relative();
}
void View3DController::_bind_methods() {
ADD_SIGNAL(MethodInfo("view_state_changed"));
ADD_SIGNAL(MethodInfo("fov_scaled"));
ADD_SIGNAL(MethodInfo("freelook_changed"));
ADD_SIGNAL(MethodInfo("freelook_speed_scaled"));
ADD_SIGNAL(MethodInfo("cursor_panned"));
ADD_SIGNAL(MethodInfo("cursor_interpolated"));
}
#endif // _3D_DISABLED

View file

@ -0,0 +1,326 @@
/**************************************************************************/
/* view_3d_controller.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#ifndef _3D_DISABLED
#include "core/object/ref_counted.h"
namespace View3DControllerConsts {
constexpr float DISTANCE_DEFAULT = 4;
constexpr float ZOOM_FREELOOK_MULTIPLIER = 1.08;
constexpr float ZOOM_FREELOOK_MIN = 0.01;
#ifdef REAL_T_IS_DOUBLE
constexpr double ZOOM_FREELOOK_MAX = 1'000'000'000'000;
#else
constexpr float ZOOM_FREELOOK_MAX = 10'000;
#endif
constexpr float CAMERA_MIN_FOV_SCALE = 0.1;
constexpr float CAMERA_MAX_FOV_SCALE = 2.5;
} //namespace View3DControllerConsts
class InputEvent;
class InputEventMouseMotion;
class InputEventWithModifiers;
class Shortcut;
class View3DController : public RefCounted {
GDCLASS(View3DController, RefCounted);
public:
enum NavigationMode {
NAV_MODE_NONE,
NAV_MODE_PAN,
NAV_MODE_ZOOM,
NAV_MODE_ORBIT,
NAV_MODE_LOOK,
NAV_MODE_MOVE,
};
enum NavigationScheme {
NAV_SCHEME_GODOT,
NAV_SCHEME_MAYA,
NAV_SCHEME_MODO,
NAV_SCHEME_CUSTOM,
NAV_SCHEME_TABLET,
};
enum NavigationMouseButton {
NAV_MOUSE_BUTTON_LEFT,
NAV_MOUSE_BUTTON_MIDDLE,
NAV_MOUSE_BUTTON_RIGHT,
NAV_MOUSE_BUTTON_4,
NAV_MOUSE_BUTTON_5,
};
enum ZoomStyle {
ZOOM_VERTICAL,
ZOOM_HORIZONTAL,
};
enum FreelookScheme {
FREELOOK_DEFAULT,
FREELOOK_PARTIALLY_AXIS_LOCKED,
FREELOOK_FULLY_AXIS_LOCKED,
};
enum ViewType {
VIEW_TYPE_USER,
VIEW_TYPE_TOP,
VIEW_TYPE_BOTTOM,
VIEW_TYPE_LEFT,
VIEW_TYPE_RIGHT,
VIEW_TYPE_FRONT,
VIEW_TYPE_REAR,
};
enum OrthogonalMode {
ORTHOGONAL_DISABLED,
ORTHOGONAL_ENABLED,
ORTHOGONAL_AUTO,
};
enum ShortcutName {
SHORTCUT_FOV_INCREASE,
SHORTCUT_FOV_DECREASE,
SHORTCUT_FOV_RESET,
SHORTCUT_PAN_MOD_1,
SHORTCUT_PAN_MOD_2,
SHORTCUT_ORBIT_MOD_1,
SHORTCUT_ORBIT_MOD_2,
SHORTCUT_ORBIT_SNAP_MOD_1,
SHORTCUT_ORBIT_SNAP_MOD_2,
SHORTCUT_ZOOM_MOD_1,
SHORTCUT_ZOOM_MOD_2,
SHORTCUT_FREELOOK_FORWARD,
SHORTCUT_FREELOOK_BACKWARDS,
SHORTCUT_FREELOOK_LEFT,
SHORTCUT_FREELOOK_RIGHT,
SHORTCUT_FREELOOK_UP,
SHORTCUT_FREELOOK_DOWN,
SHORTCUT_FREELOOK_SPEED_MOD,
SHORTCUT_FREELOOK_SLOW_MOD,
SHORTCUT_MAX,
};
struct Cursor {
Vector3 pos;
real_t x_rot;
real_t y_rot;
real_t distance;
real_t fov_scale;
real_t unsnapped_x_rot;
real_t unsnapped_y_rot;
Vector3 eye_pos; // Used for freelook.
// TODO: These variables are not related to cursor manipulation, and specific
// to Node3DEditorPlugin. So remove them in the future.
bool region_select;
Point2 region_begin;
Point2 region_end;
Cursor() {
// These rotations place the camera in +X +Y +Z, aka south east, facing north west.
x_rot = 0.5;
y_rot = -0.5;
unsnapped_x_rot = x_rot;
unsnapped_y_rot = y_rot;
distance = 4;
fov_scale = 1.0;
region_select = false;
}
};
// Viewport camera supports movement smoothing,
// so one cursor is the real cursor, while the other can be an interpolated version.
Cursor cursor; // Immediate cursor.
private:
Cursor cursor_interp; // That one may be interpolated (don't modify this one except for smoothing purposes).
protected:
static void _bind_methods();
private:
HashMap<int, Ref<Shortcut>> inputs;
NavigationScheme navigation_scheme = NAV_SCHEME_GODOT;
ViewType view_type = VIEW_TYPE_USER;
NavigationMouseButton pan_mouse_button = NAV_MOUSE_BUTTON_MIDDLE;
NavigationMouseButton orbit_mouse_button = NAV_MOUSE_BUTTON_MIDDLE;
float orbit_sensitivity = 0;
float orbit_inertia = 0;
bool freelook = false;
FreelookScheme freelook_scheme = FREELOOK_DEFAULT;
float freelook_speed = 0;
float freelook_base_speed = 0;
float freelook_speed_zoom_link = 0;
float freelook_sensitivity = 0;
float freelook_inertia = 0;
bool freelook_invert_y_axis = false;
ZoomStyle zoom_style = ZOOM_VERTICAL;
NavigationMouseButton zoom_mouse_button = NAV_MOUSE_BUTTON_MIDDLE;
float zoom_inertia = 0;
int zoom_failed_attempts_count = 0;
float translation_sensitivity = 0;
float translation_inertia = 0;
float angle_snap_threshold = 0;
bool warped_mouse_panning = false;
bool emulate_3_button_mouse = false;
bool emulate_numpad = true;
OrthogonalMode orthogonal = ORTHOGONAL_DISABLED;
bool auto_orthogonal_allowed = false;
bool lock_rotation = false;
float znear = 0;
float zfar = 0;
bool invert_x_axis = false;
bool invert_y_axis = false;
Point2 previous_mouse_position;
Transform3D _to_camera_transform(const Cursor &p_cursor) const;
struct ShortcutCheck {
bool mod_pressed = false;
bool not_empty = true;
int input_count = 0;
NavigationMouseButton mouse_preference = NAV_MOUSE_BUTTON_LEFT;
NavigationMode result_nav_mode = NAV_MODE_NONE;
ShortcutCheck(bool p_mod_pressed, bool p_not_empty, int p_input_count, const NavigationMouseButton &p_mouse_preference, const NavigationMode &p_result_nav_mode) :
mod_pressed(p_mod_pressed), not_empty(p_not_empty), input_count(p_input_count), mouse_preference(p_mouse_preference), result_nav_mode(p_result_nav_mode) {
}
};
struct ShortcutCheckSetComparator {
_FORCE_INLINE_ bool operator()(const ShortcutCheck &A, const ShortcutCheck &B) const {
return A.input_count > B.input_count;
}
};
bool _is_shortcut_pressed(const ShortcutName p_name, const bool p_true_if_null = false);
bool _is_shortcut_empty(const ShortcutName p_name);
NavigationMode _get_nav_mode_from_shortcuts(NavigationMouseButton p_mouse_button, const Vector<ShortcutCheck> &p_shortcut_checks, bool p_not_empty);
public:
bool gui_input(const Ref<InputEvent> &p_event, const Rect2 &p_surface_rect);
void cursor_pan(const Ref<InputEventWithModifiers> &p_event, const Vector2 &p_relative);
void cursor_look(const Ref<InputEventWithModifiers> &p_event, const Vector2 &p_relative);
void cursor_orbit(const Ref<InputEventWithModifiers> &p_event, const Vector2 &p_relative);
void cursor_zoom(const Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative);
void update_camera(const real_t p_delta = 0);
void update_freelook(const float p_delta);
void scale_freelook_speed(const float p_scale);
void scale_cursor_distance(const float p_scale);
inline Transform3D to_camera_transform() const { return _to_camera_transform(cursor); }
inline Transform3D interp_to_camera_transform() const { return _to_camera_transform(cursor_interp); }
void set_shortcut(const ShortcutName p_name, const Ref<Shortcut> &p_shortcut);
void set_navigation_scheme(const NavigationScheme p_scheme) { navigation_scheme = p_scheme; }
void set_view_type(const ViewType p_view);
ViewType get_view_type() const { return view_type; }
String get_view_type_name() const;
void set_pan_mouse_button(const NavigationMouseButton p_button) { pan_mouse_button = p_button; }
void set_orbit_sensitivity(const float p_sensitivity) { orbit_sensitivity = p_sensitivity; }
void set_orbit_inertia(const float p_inertia) { orbit_inertia = p_inertia; }
void set_orbit_mouse_button(const NavigationMouseButton p_button) { orbit_mouse_button = p_button; }
void set_freelook_enabled(const bool p_enabled);
bool is_freelook_enabled() const { return freelook; }
void set_freelook_scheme(FreelookScheme p_scheme) { freelook_scheme = p_scheme; }
FreelookScheme get_freelook_scheme() const { return freelook_scheme; }
void set_freelook_base_speed(const float p_speed);
float get_freelook_speed() const { return freelook_speed; }
void set_freelook_sensitivity(const float p_sensitivity) { freelook_sensitivity = p_sensitivity; }
void set_freelook_inertia(const float p_inertia) { freelook_inertia = p_inertia; }
void set_freelook_speed_zoom_link(const bool p_enabled) { freelook_speed_zoom_link = p_enabled; }
void set_freelook_invert_y_axis(const bool p_enabled) { freelook_invert_y_axis = p_enabled; }
void set_zoom_style(ZoomStyle p_style) { zoom_style = p_style; }
void set_zoom_inertia(const float p_inertia) { zoom_inertia = p_inertia; }
void set_zoom_mouse_button(const NavigationMouseButton p_button) { zoom_mouse_button = p_button; }
void set_translation_sensitivity(const float p_sensitivity) { translation_sensitivity = p_sensitivity; }
void set_translation_inertia(const float p_inertia) { translation_inertia = p_inertia; }
void set_angle_snap_threshold(const float p_threshold) { angle_snap_threshold = p_threshold; }
void set_emulate_3_button_mouse(const bool p_enabled) { emulate_3_button_mouse = p_enabled; }
void set_emulate_numpad(const bool p_enabled) { emulate_numpad = p_enabled; }
void set_orthogonal(const bool p_enabled) { orthogonal = p_enabled ? ORTHOGONAL_ENABLED : ORTHOGONAL_DISABLED; }
bool is_orthogonal() const { return orthogonal != ORTHOGONAL_DISABLED; }
OrthogonalMode get_orthogonal_mode() const { return orthogonal; }
void force_auto_orthogonal();
void set_auto_orthogonal_allowed(const bool p_enabled);
void set_lock_rotation(const bool p_locked) { lock_rotation = p_locked; }
bool is_locking_rotation() { return lock_rotation; }
void set_z_near(const float p_near) { znear = p_near; }
void set_z_far(const float p_far) { zfar = p_far; }
void set_invert_x_axis(const bool p_invert) { invert_x_axis = p_invert; }
void set_invert_y_axis(const bool p_invert) { invert_y_axis = p_invert; }
void set_warped_mouse_panning(const bool p_enabled) { warped_mouse_panning = p_enabled; }
Point2 get_warped_mouse_motion(const Ref<InputEventMouseMotion> &p_event, const Rect2 &p_surface_rect) const;
int get_zoom_failed_attempts_count() const { return zoom_failed_attempts_count; }
};
#endif // _3D_DISABLED