@@ -1213,7 +1213,7 @@ void Node3DEditorViewport::_update_name() {
12131213 } break ;
12141214 }
12151215
1216- if (auto_orthogonal) {
1216+ if (orthogonal && auto_orthogonal) {
12171217 // TRANSLATORS: This will be appended to the view name when Auto Orthogonal is enabled.
12181218 name += " " + TTR (" [auto]" );
12191219 }
@@ -2510,27 +2510,32 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
25102510 if (ED_IS_SHORTCUT (" spatial_editor/orbit_view_down" , p_event)) {
25112511 // Clamp rotation to roughly -90..90 degrees so the user can't look upside-down and end up disoriented.
25122512 cursor.x_rot = CLAMP (cursor.x_rot - Math::PI / 12.0 , -1.57 , 1.57 );
2513+ cursor.unsnapped_x_rot = cursor.x_rot ;
25132514 view_type = VIEW_TYPE_USER;
25142515 _update_name ();
25152516 }
25162517 if (ED_IS_SHORTCUT (" spatial_editor/orbit_view_up" , p_event)) {
25172518 // Clamp rotation to roughly -90..90 degrees so the user can't look upside-down and end up disoriented.
25182519 cursor.x_rot = CLAMP (cursor.x_rot + Math::PI / 12.0 , -1.57 , 1.57 );
2520+ cursor.unsnapped_x_rot = cursor.x_rot ;
25192521 view_type = VIEW_TYPE_USER;
25202522 _update_name ();
25212523 }
25222524 if (ED_IS_SHORTCUT (" spatial_editor/orbit_view_right" , p_event)) {
25232525 cursor.y_rot -= Math::PI / 12.0 ;
2526+ cursor.unsnapped_y_rot = cursor.y_rot ;
25242527 view_type = VIEW_TYPE_USER;
25252528 _update_name ();
25262529 }
25272530 if (ED_IS_SHORTCUT (" spatial_editor/orbit_view_left" , p_event)) {
25282531 cursor.y_rot += Math::PI / 12.0 ;
2532+ cursor.unsnapped_y_rot = cursor.y_rot ;
25292533 view_type = VIEW_TYPE_USER;
25302534 _update_name ();
25312535 }
25322536 if (ED_IS_SHORTCUT (" spatial_editor/orbit_view_180" , p_event)) {
25332537 cursor.y_rot += Math::PI;
2538+ cursor.unsnapped_y_rot = cursor.y_rot ;
25342539 view_type = VIEW_TYPE_USER;
25352540 _update_name ();
25362541 }
@@ -2726,30 +2731,69 @@ void Node3DEditorViewport::_nav_orbit(Ref<InputEventWithModifiers> p_event, cons
27262731 return ;
27272732 }
27282733
2729- if (orthogonal && auto_orthogonal) {
2730- _menu_option (VIEW_PERSPECTIVE);
2731- }
2732-
27332734 const real_t degrees_per_pixel = EDITOR_GET (" editors/3d/navigation_feel/orbit_sensitivity" );
27342735 const real_t radians_per_pixel = Math::deg_to_rad (degrees_per_pixel);
27352736 const bool invert_y_axis = EDITOR_GET (" editors/3d/navigation/invert_y_axis" );
27362737 const bool invert_x_axis = EDITOR_GET (" editors/3d/navigation/invert_x_axis" );
27372738
2738- if (invert_y_axis) {
2739- cursor.x_rot -= p_relative.y * radians_per_pixel;
2740- } else {
2741- cursor.x_rot += p_relative.y * radians_per_pixel;
2742- }
2743- // Clamp the Y rotation to roughly -90..90 degrees so the user can't look upside-down and end up disoriented.
2744- cursor.x_rot = CLAMP (cursor.x_rot , -1.57 , 1.57 );
2739+ cursor.unsnapped_x_rot += p_relative.y * radians_per_pixel * (invert_y_axis ? -1 : 1 );
2740+ cursor.unsnapped_x_rot = CLAMP (cursor.unsnapped_x_rot , -1.57 , 1.57 );
2741+ cursor.unsnapped_y_rot += p_relative.x * radians_per_pixel * (invert_x_axis ? -1 : 1 );
27452742
2746- if (invert_x_axis) {
2747- cursor.y_rot -= p_relative.x * radians_per_pixel;
2748- } else {
2749- cursor.y_rot += p_relative.x * radians_per_pixel;
2743+ cursor.x_rot = cursor.unsnapped_x_rot ;
2744+ cursor.y_rot = cursor.unsnapped_y_rot ;
2745+
2746+ if (_is_nav_modifier_pressed (" spatial_editor/viewport_orbit_snap_modifier_1" ) &&
2747+ _is_nav_modifier_pressed (" spatial_editor/viewport_orbit_snap_modifier_2" )) {
2748+ const real_t snap_angle = Math::deg_to_rad (45.0 );
2749+ const real_t snap_threshold = Math::deg_to_rad ((real_t )EDITOR_GET (" editors/3d/navigation_feel/angle_snap_threshold" ));
2750+
2751+ real_t x_rot_snapped = Math::snapped (cursor.unsnapped_x_rot , snap_angle);
2752+ real_t y_rot_snapped = Math::snapped (cursor.unsnapped_y_rot , snap_angle);
2753+
2754+ real_t x_dist = Math::abs (cursor.unsnapped_x_rot - x_rot_snapped);
2755+ real_t y_dist = Math::abs (cursor.unsnapped_y_rot - y_rot_snapped);
2756+
2757+ if (x_dist < snap_threshold && y_dist < snap_threshold) {
2758+ cursor.x_rot = x_rot_snapped;
2759+ cursor.y_rot = y_rot_snapped;
2760+
2761+ real_t y_rot_wrapped = Math::wrapf (y_rot_snapped, (real_t )-Math::PI, (real_t )Math::PI);
2762+
2763+ if (Math::abs (x_rot_snapped) < snap_threshold) {
2764+ if (Math::abs (y_rot_wrapped) < snap_threshold) {
2765+ view_type = VIEW_TYPE_FRONT;
2766+ } else if (Math::abs (Math::abs (y_rot_wrapped) - Math::PI) < snap_threshold) {
2767+ view_type = VIEW_TYPE_REAR;
2768+ } else if (Math::abs (y_rot_wrapped - Math::PI / 2.0 ) < snap_threshold) {
2769+ view_type = VIEW_TYPE_LEFT;
2770+ } else if (Math::abs (y_rot_wrapped + Math::PI / 2.0 ) < snap_threshold) {
2771+ view_type = VIEW_TYPE_RIGHT;
2772+ } else {
2773+ // Only switch to ortho for 90-degree views.
2774+ return ;
2775+ }
2776+ _set_auto_orthogonal ();
2777+ _update_name ();
2778+ } else if (Math::abs (Math::abs (x_rot_snapped) - Math::PI / 2.0 ) < snap_threshold) {
2779+ if (Math::abs (y_rot_wrapped) < snap_threshold ||
2780+ Math::abs (Math::abs (y_rot_wrapped) - Math::PI) < snap_threshold ||
2781+ Math::abs (y_rot_wrapped - Math::PI / 2.0 ) < snap_threshold ||
2782+ Math::abs (y_rot_wrapped + Math::PI / 2.0 ) < snap_threshold) {
2783+ view_type = x_rot_snapped > 0 ? VIEW_TYPE_TOP : VIEW_TYPE_BOTTOM;
2784+ _set_auto_orthogonal ();
2785+ _update_name ();
2786+ }
2787+ }
2788+
2789+ return ;
2790+ }
27502791 }
2792+
27512793 view_type = VIEW_TYPE_USER;
2752- _update_name ();
2794+ if (orthogonal && auto_orthogonal) {
2795+ _menu_option (VIEW_PERSPECTIVE);
2796+ }
27532797}
27542798
27552799void Node3DEditorViewport::_nav_look (Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative) {
@@ -2777,8 +2821,10 @@ void Node3DEditorViewport::_nav_look(Ref<InputEventWithModifiers> p_event, const
27772821 }
27782822 // Clamp the Y rotation to roughly -90..90 degrees so the user can't look upside-down and end up disoriented.
27792823 cursor.x_rot = CLAMP (cursor.x_rot , -1.57 , 1.57 );
2824+ cursor.unsnapped_x_rot = cursor.x_rot ;
27802825
27812826 cursor.y_rot += p_relative.x * radians_per_pixel;
2827+ cursor.unsnapped_y_rot = cursor.y_rot ;
27822828
27832829 // Look is like the opposite of Orbit: the focus point rotates around the camera
27842830 Transform3D camera_transform = to_camera_transform (cursor);
@@ -3763,6 +3809,8 @@ void Node3DEditorViewport::_apply_camera_transform_to_cursor() {
37633809
37643810 cursor.x_rot = -camera_transform.basis .get_euler ().x ;
37653811 cursor.y_rot = -camera_transform.basis .get_euler ().y ;
3812+ cursor.unsnapped_x_rot = cursor.x_rot ;
3813+ cursor.unsnapped_y_rot = cursor.y_rot ;
37663814}
37673815
37683816void Node3DEditorViewport::_menu_option (int p_option) {
@@ -3771,6 +3819,8 @@ void Node3DEditorViewport::_menu_option(int p_option) {
37713819 case VIEW_TOP: {
37723820 cursor.y_rot = 0 ;
37733821 cursor.x_rot = Math::PI / 2.0 ;
3822+ cursor.unsnapped_y_rot = cursor.y_rot ;
3823+ cursor.unsnapped_x_rot = cursor.x_rot ;
37743824 set_message (TTR (" Top View." ), 2 );
37753825 view_type = VIEW_TYPE_TOP;
37763826 _set_auto_orthogonal ();
@@ -3780,6 +3830,8 @@ void Node3DEditorViewport::_menu_option(int p_option) {
37803830 case VIEW_BOTTOM: {
37813831 cursor.y_rot = 0 ;
37823832 cursor.x_rot = -Math::PI / 2.0 ;
3833+ cursor.unsnapped_y_rot = cursor.y_rot ;
3834+ cursor.unsnapped_x_rot = cursor.x_rot ;
37833835 set_message (TTR (" Bottom View." ), 2 );
37843836 view_type = VIEW_TYPE_BOTTOM;
37853837 _set_auto_orthogonal ();
@@ -3789,6 +3841,8 @@ void Node3DEditorViewport::_menu_option(int p_option) {
37893841 case VIEW_LEFT: {
37903842 cursor.x_rot = 0 ;
37913843 cursor.y_rot = Math::PI / 2.0 ;
3844+ cursor.unsnapped_x_rot = cursor.x_rot ;
3845+ cursor.unsnapped_y_rot = cursor.y_rot ;
37923846 set_message (TTR (" Left View." ), 2 );
37933847 view_type = VIEW_TYPE_LEFT;
37943848 _set_auto_orthogonal ();
@@ -3798,6 +3852,8 @@ void Node3DEditorViewport::_menu_option(int p_option) {
37983852 case VIEW_RIGHT: {
37993853 cursor.x_rot = 0 ;
38003854 cursor.y_rot = -Math::PI / 2.0 ;
3855+ cursor.unsnapped_x_rot = cursor.x_rot ;
3856+ cursor.unsnapped_y_rot = cursor.y_rot ;
38013857 set_message (TTR (" Right View." ), 2 );
38023858 view_type = VIEW_TYPE_RIGHT;
38033859 _set_auto_orthogonal ();
@@ -3807,6 +3863,8 @@ void Node3DEditorViewport::_menu_option(int p_option) {
38073863 case VIEW_FRONT: {
38083864 cursor.x_rot = 0 ;
38093865 cursor.y_rot = 0 ;
3866+ cursor.unsnapped_x_rot = cursor.x_rot ;
3867+ cursor.unsnapped_y_rot = cursor.y_rot ;
38103868 set_message (TTR (" Front View." ), 2 );
38113869 view_type = VIEW_TYPE_FRONT;
38123870 _set_auto_orthogonal ();
@@ -3816,6 +3874,8 @@ void Node3DEditorViewport::_menu_option(int p_option) {
38163874 case VIEW_REAR: {
38173875 cursor.x_rot = 0 ;
38183876 cursor.y_rot = Math::PI;
3877+ cursor.unsnapped_x_rot = cursor.x_rot ;
3878+ cursor.unsnapped_y_rot = cursor.y_rot ;
38193879 set_message (TTR (" Rear View." ), 2 );
38203880 view_type = VIEW_TYPE_REAR;
38213881 _set_auto_orthogonal ();
@@ -4445,9 +4505,11 @@ void Node3DEditorViewport::set_state(const Dictionary &p_state) {
44454505 }
44464506 if (p_state.has (" x_rotation" )) {
44474507 cursor.x_rot = p_state[" x_rotation" ];
4508+ cursor.unsnapped_x_rot = cursor.x_rot ;
44484509 }
44494510 if (p_state.has (" y_rotation" )) {
44504511 cursor.y_rot = p_state[" y_rotation" ];
4512+ cursor.unsnapped_y_rot = cursor.y_rot ;
44514513 }
44524514 if (p_state.has (" distance" )) {
44534515 cursor.distance = p_state[" distance" ];
@@ -6016,6 +6078,8 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p
60166078 // Registering with Key::NONE intentionally creates an empty Array.
60176079 register_shortcut_action (" spatial_editor/viewport_orbit_modifier_1" , TTRC (" Viewport Orbit Modifier 1" ), Key::NONE);
60186080 register_shortcut_action (" spatial_editor/viewport_orbit_modifier_2" , TTRC (" Viewport Orbit Modifier 2" ), Key::NONE);
6081+ register_shortcut_action (" spatial_editor/viewport_orbit_snap_modifier_1" , TTRC (" Viewport Orbit Snap Modifier 1" ), Key::ALT);
6082+ register_shortcut_action (" spatial_editor/viewport_orbit_snap_modifier_2" , TTRC (" Viewport Orbit Snap Modifier 2" ), Key::NONE);
60196083 register_shortcut_action (" spatial_editor/viewport_pan_modifier_1" , TTRC (" Viewport Pan Modifier 1" ), Key::SHIFT);
60206084 register_shortcut_action (" spatial_editor/viewport_pan_modifier_2" , TTRC (" Viewport Pan Modifier 2" ), Key::NONE);
60216085 register_shortcut_action (" spatial_editor/viewport_zoom_modifier_1" , TTRC (" Viewport Zoom Modifier 1" ), Key::SHIFT);
0 commit comments