Path: blob/master/scene/debugger/view_3d_controller.cpp
45997 views
/**************************************************************************/1/* view_3d_controller.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#ifndef _3D_DISABLED3132#include "view_3d_controller.h"3334#include "core/config/engine.h"35#include "core/input/input.h"36#include "core/input/shortcut.h"37#include "core/object/class_db.h" // IWYU pragma: keep. `ADD_SIGNAL` macro.38#include "scene/main/scene_tree.h"39#include "scene/main/window.h"4041using namespace View3DControllerConsts;4243Transform3D View3DController::_to_camera_transform(const Cursor &p_cursor) const {44Transform3D camera_transform;45camera_transform.translate_local(p_cursor.pos);46camera_transform.basis.rotate(Vector3(1, 0, 0), -p_cursor.x_rot);47camera_transform.basis.rotate(Vector3(0, 1, 0), -p_cursor.y_rot);4849if (orthogonal) {50camera_transform.translate_local(0, 0, (zfar - znear) / 2.0);51} else {52camera_transform.translate_local(0, 0, p_cursor.distance);53}5455return camera_transform;56}5758bool View3DController::_is_shortcut_pressed(const ShortcutName p_name, const bool p_true_if_null) {59Ref<Shortcut> shortcut = inputs[p_name];60if (shortcut.is_null()) {61return p_true_if_null;62}6364const Array shortcuts = shortcut->get_events();65Ref<InputEventKey> k;66if (shortcuts.size() > 0) {67k = shortcuts.front();68}6970if (k.is_null()) {71return p_true_if_null;72}7374#define EMULATE_NUMPAD_KEY(p_code) \75(emulate_numpad && p_code >= Key::KEY_0 && p_code <= Key::KEY_9 ? p_code - Key::KEY_0 + Key::KP_0 : p_code)7677if (k->get_physical_keycode() == Key::NONE) {78return Input::get_singleton()->is_key_pressed(EMULATE_NUMPAD_KEY(k->get_keycode()));79}8081return Input::get_singleton()->is_physical_key_pressed(EMULATE_NUMPAD_KEY(k->get_physical_keycode()));8283#undef EMULATE_NUMPAD_KEY84}8586bool View3DController::_is_shortcut_empty(const ShortcutName p_name) {87Ref<Shortcut> shortcut = inputs[p_name];88if (shortcut.is_null()) {89return true;90}9192const Array shortcuts = shortcut->get_events();93Ref<InputEventKey> k;94if (shortcuts.size() > 0) {95k = shortcuts.front();96}9798return k.is_null();99}100101View3DController::NavigationMode View3DController::_get_nav_mode_from_shortcuts(NavigationMouseButton p_mouse_button, const Vector<ShortcutCheck> &p_shortcut_checks, bool p_not_empty) {102if (p_not_empty) {103for (const ShortcutCheck &shortcut_check : p_shortcut_checks) {104if (shortcut_check.mod_pressed && shortcut_check.not_empty) {105return shortcut_check.result_nav_mode;106}107}108} else {109for (const ShortcutCheck &shortcut_check : p_shortcut_checks) {110if (shortcut_check.mouse_preference == p_mouse_button && shortcut_check.mod_pressed) {111return shortcut_check.result_nav_mode;112}113}114}115116return NAV_MODE_NONE;117}118119bool View3DController::gui_input(const Ref<InputEvent> &p_event, const Rect2 &p_surface_rect) {120Ref<InputEventMouseButton> b = p_event;121if (b.is_valid()) {122if (b->get_button_index() == MouseButton::RIGHT && b->is_pressed() && navigating) {123cancel_navigation();124return true;125}126127const real_t zoom_factor = 1 + (ZOOM_FREELOOK_MULTIPLIER - 1) * b->get_factor();128switch (b->get_button_index()) {129case MouseButton::WHEEL_UP: {130if (freelook) {131scale_freelook_speed(zoom_factor);132} else {133scale_cursor_distance(1.0 / zoom_factor);134}135} break;136case MouseButton::WHEEL_DOWN: {137if (freelook) {138scale_freelook_speed(1.0 / zoom_factor);139} else {140scale_cursor_distance(zoom_factor);141}142} break;143default: {144return false;145}146}147148return true;149}150151Vector<ShortcutCheck> shortcut_checks;152153if (Input::get_singleton()->get_mouse_mode() != Input::MouseMode::MOUSE_MODE_CAPTURED) {154#define GET_SHORTCUT_COUNT(p_name) (inputs[p_name].is_null() ? 0 : inputs[p_name]->get_events().size())155156bool orbit_mod_pressed = _is_shortcut_pressed(SHORTCUT_ORBIT_MOD_1, true) && _is_shortcut_pressed(SHORTCUT_ORBIT_MOD_2, true);157bool pan_mod_pressed = _is_shortcut_pressed(SHORTCUT_PAN_MOD_1, true) && _is_shortcut_pressed(SHORTCUT_PAN_MOD_2, true);158bool zoom_mod_pressed = _is_shortcut_pressed(SHORTCUT_ZOOM_MOD_1, true) && _is_shortcut_pressed(SHORTCUT_ZOOM_MOD_2, true);159int orbit_mod_input_count = GET_SHORTCUT_COUNT(SHORTCUT_ORBIT_MOD_1) + GET_SHORTCUT_COUNT(SHORTCUT_ORBIT_MOD_2);160int pan_mod_input_count = GET_SHORTCUT_COUNT(SHORTCUT_PAN_MOD_1) + GET_SHORTCUT_COUNT(SHORTCUT_PAN_MOD_2);161int zoom_mod_input_count = GET_SHORTCUT_COUNT(SHORTCUT_ZOOM_MOD_1) + GET_SHORTCUT_COUNT(SHORTCUT_ZOOM_MOD_2);162bool orbit_not_empty = !_is_shortcut_empty(SHORTCUT_ORBIT_MOD_1) || !_is_shortcut_empty(SHORTCUT_ORBIT_MOD_2);163bool pan_not_empty = !_is_shortcut_empty(SHORTCUT_PAN_MOD_2) || !_is_shortcut_empty(SHORTCUT_PAN_MOD_2);164bool zoom_not_empty = !_is_shortcut_empty(SHORTCUT_ZOOM_MOD_1) || !_is_shortcut_empty(SHORTCUT_ZOOM_MOD_2);165shortcut_checks.push_back(ShortcutCheck(orbit_mod_pressed, orbit_not_empty, orbit_mod_input_count, orbit_mouse_button, NAV_MODE_ORBIT));166shortcut_checks.push_back(ShortcutCheck(pan_mod_pressed, pan_not_empty, pan_mod_input_count, pan_mouse_button, NAV_MODE_PAN));167shortcut_checks.push_back(ShortcutCheck(zoom_mod_pressed, zoom_not_empty, zoom_mod_input_count, zoom_mouse_button, NAV_MODE_ZOOM));168shortcut_checks.sort_custom<ShortcutCheckSetComparator>();169170#undef GET_SHORTCUT_COUNT171}172173Ref<InputEventMouseMotion> m = p_event;174if (m.is_valid()) {175if (m->get_button_mask() == MouseButtonMask::NONE) {176navigation_cancelled = false;177}178179if (navigation_cancelled) {180return false;181}182183if (!navigating) {184previous_cursor = cursor;185}186187NavigationMode nav_mode = NAV_MODE_NONE;188189if (m->get_button_mask().has_flag(MouseButtonMask::LEFT)) {190NavigationMode change_nav_from_shortcut = _get_nav_mode_from_shortcuts(NAV_MOUSE_BUTTON_LEFT, shortcut_checks, false);191if (change_nav_from_shortcut != NAV_MODE_NONE) {192nav_mode = change_nav_from_shortcut;193}194} else if (freelook || m->get_button_mask().has_flag(MouseButtonMask::RIGHT)) {195NavigationMode change_nav_from_shortcut = _get_nav_mode_from_shortcuts(NAV_MOUSE_BUTTON_RIGHT, shortcut_checks, false);196if (m->get_button_mask().has_flag(MouseButtonMask::RIGHT) && change_nav_from_shortcut != NAV_MODE_NONE) {197nav_mode = change_nav_from_shortcut;198} else if (freelook) {199nav_mode = NAV_MODE_LOOK;200} else if (orthogonal) {201nav_mode = NAV_MODE_PAN;202}203204} else if (m->get_button_mask().has_flag(MouseButtonMask::MIDDLE)) {205NavigationMode change_nav_from_shortcut = _get_nav_mode_from_shortcuts(NAV_MOUSE_BUTTON_MIDDLE, shortcut_checks, false);206if (change_nav_from_shortcut != NAV_MODE_NONE) {207nav_mode = change_nav_from_shortcut;208}209210} else if (m->get_button_mask().has_flag(MouseButtonMask::MB_XBUTTON1)) {211NavigationMode change_nav_from_shortcut = _get_nav_mode_from_shortcuts(NAV_MOUSE_BUTTON_4, shortcut_checks, false);212if (change_nav_from_shortcut != NAV_MODE_NONE) {213nav_mode = change_nav_from_shortcut;214}215216} else if (m->get_button_mask().has_flag(MouseButtonMask::MB_XBUTTON2)) {217NavigationMode change_nav_from_shortcut = _get_nav_mode_from_shortcuts(NAV_MOUSE_BUTTON_5, shortcut_checks, false);218if (change_nav_from_shortcut != NAV_MODE_NONE) {219nav_mode = change_nav_from_shortcut;220}221222} else if (emulate_3_button_mouse) {223// Handle trackpad (no external mouse) use case.224NavigationMode change_nav_from_shortcut = _get_nav_mode_from_shortcuts(NAV_MOUSE_BUTTON_LEFT, shortcut_checks, true);225if (change_nav_from_shortcut != NAV_MODE_NONE) {226nav_mode = change_nav_from_shortcut;227}228}229230switch (nav_mode) {231case NAV_MODE_PAN: {232cursor_pan(m, get_warped_mouse_motion(m, p_surface_rect));233} break;234235case NAV_MODE_ZOOM: {236cursor_zoom(m, m->get_relative());237} break;238239case NAV_MODE_ORBIT: {240cursor_orbit(m, get_warped_mouse_motion(m, p_surface_rect));241} break;242243case NAV_MODE_LOOK: {244cursor_look(m, get_warped_mouse_motion(m, p_surface_rect));245} break;246247default: {248navigating = false;249return false;250}251}252253if (!freelook) {254navigating = true;255}256return true;257}258259Ref<InputEventMagnifyGesture> magnify_gesture = p_event;260if (magnify_gesture.is_valid()) {261if (freelook) {262scale_freelook_speed(magnify_gesture->get_factor());263} else {264scale_cursor_distance(1.0 / magnify_gesture->get_factor());265}266267return true;268}269270Ref<InputEventPanGesture> pan_gesture = p_event;271if (pan_gesture.is_valid()) {272NavigationMode nav_mode = NAV_MODE_NONE;273274for (const ShortcutCheck &shortcut_check_set : shortcut_checks) {275if (shortcut_check_set.mod_pressed) {276nav_mode = shortcut_check_set.result_nav_mode;277break;278}279}280281switch (nav_mode) {282case NAV_MODE_PAN: {283cursor_pan(pan_gesture, -pan_gesture->get_delta());284} break;285286case NAV_MODE_ZOOM: {287cursor_zoom(pan_gesture, pan_gesture->get_delta());288} break;289290case NAV_MODE_ORBIT: {291cursor_orbit(pan_gesture, -pan_gesture->get_delta());292} break;293294case NAV_MODE_LOOK: {295cursor_look(pan_gesture, pan_gesture->get_delta());296} break;297298default: {299return false;300}301}302303return true;304}305306Ref<InputEventKey> k = p_event;307if (k.is_valid() && k->is_pressed() && !k->is_echo() && k->get_keycode() == Key::ESCAPE && navigating) {308cancel_navigation();309return true;310}311312bool pressed = false;313float old_fov_scale = cursor.fov_scale;314315if (_is_shortcut_pressed(SHORTCUT_FOV_DECREASE)) {316cursor.fov_scale = CLAMP(cursor.fov_scale - 0.05, CAMERA_MIN_FOV_SCALE, CAMERA_MAX_FOV_SCALE);317pressed = true;318}319if (_is_shortcut_pressed(SHORTCUT_FOV_INCREASE)) {320cursor.fov_scale = CLAMP(cursor.fov_scale + 0.05, CAMERA_MIN_FOV_SCALE, CAMERA_MAX_FOV_SCALE);321pressed = true;322}323if (_is_shortcut_pressed(SHORTCUT_FOV_RESET)) {324cursor.fov_scale = 1;325pressed = true;326}327328if (old_fov_scale != cursor.fov_scale) {329emit_signal(SNAME("fov_scaled"));330}331332return pressed;333}334335void View3DController::cancel_navigation() {336navigating = false;337navigation_cancelled = true;338cursor = previous_cursor;339}340341void View3DController::cursor_pan(const Ref<InputEventWithModifiers> &p_event, const Vector2 &p_relative) {342float pan_speed = translation_sensitivity / 150.0;343if (p_event.is_valid() && navigation_scheme == NAV_SCHEME_MAYA && p_event->is_shift_pressed()) {344pan_speed *= 10;345}346347Transform3D camera_transform;348349camera_transform.translate_local(cursor.pos);350camera_transform.basis.rotate(Vector3(1, 0, 0), -cursor.x_rot);351camera_transform.basis.rotate(Vector3(0, 1, 0), -cursor.y_rot);352Vector3 translation(353(invert_x_axis ? -1 : 1) * -p_relative.x * pan_speed,354(invert_y_axis ? -1 : 1) * p_relative.y * pan_speed,3550);356translation *= cursor.distance / DISTANCE_DEFAULT;357camera_transform.translate_local(translation);358cursor.pos = camera_transform.origin;359360emit_signal(SNAME("cursor_panned"));361}362363void View3DController::cursor_orbit(const Ref<InputEventWithModifiers> &p_event, const Vector2 &p_relative) {364if (lock_rotation) {365cursor_pan(p_event, p_relative);366return;367}368369const float radians_per_pixel = Math::deg_to_rad(orbit_sensitivity);370371cursor.unsnapped_x_rot += p_relative.y * radians_per_pixel * (invert_y_axis ? -1 : 1);372cursor.unsnapped_x_rot = CLAMP(cursor.unsnapped_x_rot, -1.57, 1.57);373cursor.unsnapped_y_rot += p_relative.x * radians_per_pixel * (invert_x_axis ? -1 : 1);374375cursor.x_rot = cursor.unsnapped_x_rot;376cursor.y_rot = cursor.unsnapped_y_rot;377378ViewType new_view_type = VIEW_TYPE_USER;379380bool snap_modifier_configured = !_is_shortcut_empty(SHORTCUT_ORBIT_SNAP_MOD_1) || !_is_shortcut_empty(SHORTCUT_ORBIT_SNAP_MOD_2);381if (snap_modifier_configured && _is_shortcut_pressed(SHORTCUT_ORBIT_SNAP_MOD_1, true) && _is_shortcut_pressed(SHORTCUT_ORBIT_SNAP_MOD_2, true)) {382const float snap_angle = Math::deg_to_rad(45.0);383const float snap_threshold = Math::deg_to_rad(angle_snap_threshold);384385float x_rot_snapped = Math::snapped(cursor.unsnapped_x_rot, snap_angle);386float y_rot_snapped = Math::snapped(cursor.unsnapped_y_rot, snap_angle);387388float x_dist = Math::abs(cursor.unsnapped_x_rot - x_rot_snapped);389float y_dist = Math::abs(cursor.unsnapped_y_rot - y_rot_snapped);390391if (x_dist < snap_threshold && y_dist < snap_threshold) {392cursor.x_rot = x_rot_snapped;393cursor.y_rot = y_rot_snapped;394395float y_rot_wrapped = Math::wrapf(y_rot_snapped, (float)-Math::PI, (float)Math::PI);396397if (Math::abs(x_rot_snapped) < snap_threshold) {398// Only switch to ortho for 90-degree views.399if (Math::abs(y_rot_wrapped) < snap_threshold) {400new_view_type = VIEW_TYPE_FRONT;401} else if (Math::abs(Math::abs(y_rot_wrapped) - Math::PI) < snap_threshold) {402new_view_type = VIEW_TYPE_REAR;403} else if (Math::abs(y_rot_wrapped - Math::PI / 2.0) < snap_threshold) {404new_view_type = VIEW_TYPE_LEFT;405} else if (Math::abs(y_rot_wrapped + Math::PI / 2.0) < snap_threshold) {406new_view_type = VIEW_TYPE_RIGHT;407}408409} else if (Math::abs(Math::abs(x_rot_snapped) - Math::PI / 2.0) < snap_threshold) {410if (Math::abs(y_rot_wrapped) < snap_threshold ||411Math::abs(Math::abs(y_rot_wrapped) - Math::PI) < snap_threshold ||412Math::abs(y_rot_wrapped - Math::PI / 2.0) < snap_threshold ||413Math::abs(y_rot_wrapped + Math::PI / 2.0) < snap_threshold) {414new_view_type = x_rot_snapped > 0 ? VIEW_TYPE_TOP : VIEW_TYPE_BOTTOM;415}416}417}418}419420set_view_type(new_view_type);421}422423void View3DController::cursor_look(const Ref<InputEventWithModifiers> &p_event, const Vector2 &p_relative) {424if (orthogonal) {425cursor_pan(p_event, p_relative);426return;427}428429// Scale mouse sensitivity with camera FOV scale when zoomed in to make it easier to point at things.430const float degrees_per_pixel = freelook_sensitivity * MIN(1.0, cursor.fov_scale);431const float radians_per_pixel = Math::deg_to_rad(degrees_per_pixel);432433// Note: do NOT assume the camera has the "current" transform, because it is interpolated and may have "lag".434const Transform3D prev_camera_transform = to_camera_transform();435436if (freelook_invert_y_axis) {437cursor.x_rot -= p_relative.y * radians_per_pixel;438} else {439cursor.x_rot += p_relative.y * radians_per_pixel;440}441442// Clamp the Y rotation to roughly -90..90 degrees so the user can't look upside-down and end up disoriented.443cursor.x_rot = CLAMP(cursor.x_rot, -1.57, 1.57);444cursor.unsnapped_x_rot = cursor.x_rot;445446cursor.y_rot += p_relative.x * radians_per_pixel;447cursor.unsnapped_y_rot = cursor.y_rot;448449// Look is like the opposite of Orbit: the focus point rotates around the camera450Transform3D camera_transform = to_camera_transform();451Vector3 pos = camera_transform.xform(Vector3(0, 0, 0));452Vector3 prev_pos = prev_camera_transform.xform(Vector3(0, 0, 0));453Vector3 diff = prev_pos - pos;454cursor.pos += diff;455456set_view_type(VIEW_TYPE_USER);457}458459void View3DController::cursor_zoom(const Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative) {460float zoom_speed = 1 / 80.0;461if (p_event.is_valid() && navigation_scheme == NAV_SCHEME_MAYA && p_event->is_shift_pressed()) {462zoom_speed *= 10;463}464465if (zoom_style == ZOOM_HORIZONTAL) {466if (p_relative.x > 0) {467scale_cursor_distance(1 - p_relative.x * zoom_speed);468} else if (p_relative.x < 0) {469scale_cursor_distance(1.0 / (1 + p_relative.x * zoom_speed));470}471} else {472if (p_relative.y > 0) {473scale_cursor_distance(1 + p_relative.y * zoom_speed);474} else if (p_relative.y < 0) {475scale_cursor_distance(1.0 / (1 - p_relative.y * zoom_speed));476}477}478}479480void View3DController::update_camera(const real_t p_delta) {481View3DController::Cursor old_camera_cursor = cursor_interp;482cursor_interp = cursor;483bool equal = true;484485if (p_delta > 0) {486// Perform smoothing.487488if (freelook) {489// Higher inertia should increase "lag" (lerp with factor between 0 and 1).490// 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.491float factor = (1.0 / freelook_inertia) * p_delta;492493// We interpolate a different point here, because in freelook mode the focus point (cursor.pos) orbits around eye_pos494cursor_interp.eye_pos = old_camera_cursor.eye_pos.lerp(cursor.eye_pos, CLAMP(factor, 0, 1));495}496497cursor_interp.x_rot = Math::lerp(old_camera_cursor.x_rot, cursor.x_rot, MIN(1.f, p_delta * (1 / orbit_inertia)));498cursor_interp.y_rot = Math::lerp(old_camera_cursor.y_rot, cursor.y_rot, MIN(1.f, p_delta * (1 / orbit_inertia)));499500if (Math::abs(cursor_interp.x_rot - cursor.x_rot) < 0.1) {501cursor_interp.x_rot = cursor.x_rot;502}503if (Math::abs(cursor_interp.y_rot - cursor.y_rot) < 0.1) {504cursor_interp.y_rot = cursor.y_rot;505}506507if (freelook) {508Vector3 forward = _to_camera_transform(cursor_interp).basis.xform(Vector3(0, 0, -1));509cursor_interp.pos = cursor_interp.eye_pos + forward * cursor_interp.distance;510} else {511cursor_interp.pos = old_camera_cursor.pos.lerp(cursor.pos, MIN(1.f, p_delta * (1 / translation_inertia)));512cursor_interp.distance = Math::lerp(old_camera_cursor.distance, cursor.distance, MIN((float)1.0, p_delta * (1 / zoom_inertia)));513}514515// Apply camera transform.516517const real_t tolerance = 0.001;518if (!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)) {519equal = false;520} else if (!old_camera_cursor.pos.is_equal_approx(cursor_interp.pos)) {521equal = false;522} else if (!Math::is_equal_approx(old_camera_cursor.distance, cursor_interp.distance, tolerance)) {523equal = false;524} else if (!Math::is_equal_approx(old_camera_cursor.fov_scale, cursor_interp.fov_scale, tolerance)) {525equal = false;526}527}528529if (p_delta == 0 || !equal) {530emit_signal(SNAME("cursor_interpolated"));531}532}533534void View3DController::update_freelook(const float p_delta) {535if (!freelook) {536return;537}538539const Transform3D camera_transform = to_camera_transform();540541Vector3 forward;542if (freelook_scheme == FREELOOK_FULLY_AXIS_LOCKED) {543// Forward/backward keys will always go straight forward/backward, never moving on the Y axis.544forward = Vector3(0, 0, -1).rotated(Vector3(0, 1, 0), camera_transform.get_basis().get_euler().y);545} else {546// Forward/backward keys will be relative to the camera pitch.547forward = camera_transform.basis.xform(Vector3(0, 0, -1));548}549550const Vector3 right = camera_transform.basis.xform(Vector3(1, 0, 0));551552Vector3 up;553if (freelook_scheme == View3DController::FREELOOK_PARTIALLY_AXIS_LOCKED || freelook_scheme == View3DController::FREELOOK_FULLY_AXIS_LOCKED) {554// Up/down keys will always go up/down regardless of camera pitch.555up = Vector3(0, 1, 0);556} else {557// Up/down keys will be relative to the camera pitch.558up = camera_transform.basis.xform(Vector3(0, 1, 0));559}560561Vector3 direction;562if (_is_shortcut_pressed(SHORTCUT_FREELOOK_LEFT)) {563direction -= right;564}565if (_is_shortcut_pressed(SHORTCUT_FREELOOK_RIGHT)) {566direction += right;567}568if (_is_shortcut_pressed(SHORTCUT_FREELOOK_FORWARD)) {569direction += forward;570}571if (_is_shortcut_pressed(SHORTCUT_FREELOOK_BACKWARDS)) {572direction -= forward;573}574if (_is_shortcut_pressed(SHORTCUT_FREELOOK_UP)) {575direction += up;576}577if (_is_shortcut_pressed(SHORTCUT_FREELOOK_DOWN)) {578direction -= up;579}580581real_t speed = freelook_speed;582583if (_is_shortcut_pressed(SHORTCUT_FREELOOK_SPEED_MOD)) {584speed *= 3.0;585}586if (_is_shortcut_pressed(SHORTCUT_FREELOOK_SLOW_MOD)) {587speed *= 0.333333;588}589590const Vector3 motion = direction * speed * p_delta;591cursor.pos += motion;592cursor.eye_pos += motion;593}594595void View3DController::scale_freelook_speed(const float p_scale) {596float min_speed = MAX(znear * 4, ZOOM_FREELOOK_MIN);597float max_speed = MIN(zfar / 4, ZOOM_FREELOOK_MAX);598if (unlikely(min_speed > max_speed)) {599freelook_speed = (min_speed + max_speed) / 2;600} else {601freelook_speed = CLAMP(freelook_speed * p_scale, min_speed, max_speed);602}603604emit_signal(SNAME("freelook_speed_scaled"));605}606607void View3DController::scale_cursor_distance(const float p_scale) {608float min_distance = MAX(znear * 4, ZOOM_FREELOOK_MIN);609float max_distance = MIN(zfar / 4, ZOOM_FREELOOK_MAX);610if (unlikely(min_distance > max_distance)) {611cursor.distance = (min_distance + max_distance) / 2;612} else {613cursor.distance = CLAMP(cursor.distance * p_scale, min_distance, max_distance);614}615616if (cursor.distance == max_distance || cursor.distance == min_distance) {617zoom_failed_attempts_count++;618} else {619zoom_failed_attempts_count = 0;620}621622emit_signal(SNAME("cursor_distance_scaled"));623}624625void View3DController::set_shortcut(const ShortcutName p_name, const Ref<Shortcut> &p_shortcut) {626ERR_FAIL_INDEX(0, SHORTCUT_MAX);627ERR_FAIL_COND(p_shortcut.is_null());628inputs[p_name] = p_shortcut;629}630631void View3DController::set_view_type(const ViewType p_view) {632ViewType view_type_old = view_type;633OrthogonalMode orthogonal_old = orthogonal;634635view_type = p_view;636if (view_type != VIEW_TYPE_USER) {637if (auto_orthogonal_allowed && orthogonal != ORTHOGONAL_ENABLED) {638orthogonal = ORTHOGONAL_AUTO;639}640} else if (orthogonal == ORTHOGONAL_AUTO) {641orthogonal = ORTHOGONAL_DISABLED;642}643644if (view_type_old != view_type || orthogonal_old != orthogonal) {645emit_signal(SNAME("view_state_changed"));646}647}648649String View3DController::get_view_type_name() const {650String name;651652switch (view_type) {653case VIEW_TYPE_USER: {654if (orthogonal) {655name = RTR("Orthogonal");656} else {657name = RTR("Perspective");658}659} break;660case VIEW_TYPE_TOP: {661if (orthogonal) {662name = RTR("Top Orthogonal");663} else {664name = RTR("Top Perspective");665}666} break;667case VIEW_TYPE_BOTTOM: {668if (orthogonal) {669name = RTR("Bottom Orthogonal");670} else {671name = RTR("Bottom Perspective");672}673} break;674case VIEW_TYPE_LEFT: {675if (orthogonal) {676name = RTR("Left Orthogonal");677} else {678name = RTR("Left Perspective");679}680} break;681case VIEW_TYPE_RIGHT: {682if (orthogonal) {683name = RTR("Right Orthogonal");684} else {685name = RTR("Right Perspective");686}687} break;688case VIEW_TYPE_FRONT: {689if (orthogonal) {690name = RTR("Front Orthogonal");691} else {692name = RTR("Front Perspective");693}694} break;695case VIEW_TYPE_REAR: {696if (orthogonal) {697name = RTR("Rear Orthogonal");698} else {699name = RTR("Rear Perspective");700}701} break;702}703704if (orthogonal == ORTHOGONAL_AUTO) {705// TRANSLATORS: This will be appended to the view name when Auto Orthogonal is enabled.706name += " " + RTR("[auto]");707}708709return name;710}711712void View3DController::set_freelook_enabled(const bool p_enabled) {713if (freelook == p_enabled) {714return;715}716717freelook = p_enabled;718719// Sync interpolated cursor to cursor to "cut" interpolation jumps due to changing referential.720cursor = cursor_interp;721722if (freelook) {723// Make sure eye_pos is synced, because freelook referential is eye pos rather than orbit pos.724Vector3 forward = to_camera_transform().basis.xform(Vector3(0, 0, -1));725cursor.eye_pos = cursor.pos - cursor.distance * forward;726// Also sync the interpolated cursor's eye_pos, otherwise switching to freelook will be trippy if inertia is active.727cursor_interp.eye_pos = cursor.eye_pos;728729if (freelook_speed_zoom_link) {730// Re-adjust freelook speed from the current zoom level.731freelook_speed = freelook_base_speed * cursor.distance;732}733734previous_mouse_position = SceneTree::get_singleton()->get_root()->get_mouse_position();735736// Hide mouse like in an FPS (warping doesn't work).737if (Engine::get_singleton()->is_editor_hint()) {738Input::get_singleton()->set_mouse_mode(Input::MouseMode::MOUSE_MODE_CAPTURED);739} else {740Input::get_singleton()->set_mouse_mode_override(Input::MouseMode::MOUSE_MODE_CAPTURED);741}742} else {743// Restore mouse.744if (Engine::get_singleton()->is_editor_hint()) {745Input::get_singleton()->set_mouse_mode(Input::MouseMode::MOUSE_MODE_VISIBLE);746} else {747Input::get_singleton()->set_mouse_mode_override(Input::MouseMode::MOUSE_MODE_VISIBLE);748}749750// Restore the previous mouse position when leaving freelook mode.751// This is done because leaving `Input.MOUSE_MODE_CAPTURED` will center the cursor752// due to OS limitations.753Input::get_singleton()->warp_mouse(previous_mouse_position);754}755756emit_signal(SNAME("freelook_changed"));757}758759void View3DController::set_freelook_base_speed(const float p_speed) {760freelook_base_speed = p_speed;761freelook_speed = p_speed;762}763764void View3DController::force_auto_orthogonal() {765if (auto_orthogonal_allowed) {766orthogonal = ORTHOGONAL_AUTO;767}768}769770void View3DController::set_auto_orthogonal_allowed(const bool p_enabled) {771auto_orthogonal_allowed = p_enabled;772773if (!p_enabled && orthogonal == ORTHOGONAL_AUTO) {774orthogonal = ORTHOGONAL_ENABLED;775}776}777778Point2 View3DController::get_warped_mouse_motion(const Ref<InputEventMouseMotion> &p_event, const Rect2 &p_surface_rect) const {779if (warped_mouse_panning) {780return Input::get_singleton()->warp_mouse_motion(p_event, p_surface_rect);781}782783return p_event->get_relative();784}785786void View3DController::_bind_methods() {787ADD_SIGNAL(MethodInfo("view_state_changed"));788789ADD_SIGNAL(MethodInfo("fov_scaled"));790791ADD_SIGNAL(MethodInfo("freelook_changed"));792ADD_SIGNAL(MethodInfo("freelook_speed_scaled"));793794ADD_SIGNAL(MethodInfo("cursor_panned"));795ADD_SIGNAL(MethodInfo("cursor_interpolated"));796ADD_SIGNAL(MethodInfo("cursor_distance_scaled"));797}798799#endif // _3D_DISABLED800801802