@@ -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 }
@@ -2541,27 +2541,32 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
25412541 if (ED_IS_SHORTCUT (" spatial_editor/orbit_view_down" , p_event)) {
25422542 // Clamp rotation to roughly -90..90 degrees so the user can't look upside-down and end up disoriented.
25432543 cursor.x_rot = CLAMP (cursor.x_rot - Math::PI / 12.0 , -1.57 , 1.57 );
2544+ cursor.unsnapped_x_rot = cursor.x_rot ;
25442545 view_type = VIEW_TYPE_USER;
25452546 _update_name ();
25462547 }
25472548 if (ED_IS_SHORTCUT (" spatial_editor/orbit_view_up" , p_event)) {
25482549 // Clamp rotation to roughly -90..90 degrees so the user can't look upside-down and end up disoriented.
25492550 cursor.x_rot = CLAMP (cursor.x_rot + Math::PI / 12.0 , -1.57 , 1.57 );
2551+ cursor.unsnapped_x_rot = cursor.x_rot ;
25502552 view_type = VIEW_TYPE_USER;
25512553 _update_name ();
25522554 }
25532555 if (ED_IS_SHORTCUT (" spatial_editor/orbit_view_right" , p_event)) {
25542556 cursor.y_rot -= Math::PI / 12.0 ;
2557+ cursor.unsnapped_y_rot = cursor.y_rot ;
25552558 view_type = VIEW_TYPE_USER;
25562559 _update_name ();
25572560 }
25582561 if (ED_IS_SHORTCUT (" spatial_editor/orbit_view_left" , p_event)) {
25592562 cursor.y_rot += Math::PI / 12.0 ;
2563+ cursor.unsnapped_y_rot = cursor.y_rot ;
25602564 view_type = VIEW_TYPE_USER;
25612565 _update_name ();
25622566 }
25632567 if (ED_IS_SHORTCUT (" spatial_editor/orbit_view_180" , p_event)) {
25642568 cursor.y_rot += Math::PI;
2569+ cursor.unsnapped_y_rot = cursor.y_rot ;
25652570 view_type = VIEW_TYPE_USER;
25662571 _update_name ();
25672572 }
@@ -2766,30 +2771,69 @@ void Node3DEditorViewport::_nav_orbit(Ref<InputEventWithModifiers> p_event, cons
27662771 return ;
27672772 }
27682773
2769- if (orthogonal && auto_orthogonal) {
2770- _menu_option (VIEW_PERSPECTIVE);
2771- }
2772-
27732774 const real_t degrees_per_pixel = EDITOR_GET (" editors/3d/navigation_feel/orbit_sensitivity" );
27742775 const real_t radians_per_pixel = Math::deg_to_rad (degrees_per_pixel);
27752776 const bool invert_y_axis = EDITOR_GET (" editors/3d/navigation/invert_y_axis" );
27762777 const bool invert_x_axis = EDITOR_GET (" editors/3d/navigation/invert_x_axis" );
27772778
2778- if (invert_y_axis) {
2779- cursor.x_rot -= p_relative.y * radians_per_pixel;
2780- } else {
2781- cursor.x_rot += p_relative.y * radians_per_pixel;
2782- }
2783- // Clamp the Y rotation to roughly -90..90 degrees so the user can't look upside-down and end up disoriented.
2784- cursor.x_rot = CLAMP (cursor.x_rot , -1.57 , 1.57 );
2779+ cursor.unsnapped_x_rot += p_relative.y * radians_per_pixel * (invert_y_axis ? -1 : 1 );
2780+ cursor.unsnapped_x_rot = CLAMP (cursor.unsnapped_x_rot , -1.57 , 1.57 );
2781+ cursor.unsnapped_y_rot += p_relative.x * radians_per_pixel * (invert_x_axis ? -1 : 1 );
27852782
2786- if (invert_x_axis) {
2787- cursor.y_rot -= p_relative.x * radians_per_pixel;
2788- } else {
2789- cursor.y_rot += p_relative.x * radians_per_pixel;
2783+ cursor.x_rot = cursor.unsnapped_x_rot ;
2784+ cursor.y_rot = cursor.unsnapped_y_rot ;
2785+
2786+ if (_is_nav_modifier_pressed (" spatial_editor/viewport_orbit_snap_modifier_1" ) &&
2787+ _is_nav_modifier_pressed (" spatial_editor/viewport_orbit_snap_modifier_2" )) {
2788+ const real_t snap_angle = Math::deg_to_rad (45.0 );
2789+ const real_t snap_threshold = Math::deg_to_rad ((real_t )EDITOR_GET (" editors/3d/navigation_feel/angle_snap_threshold" ));
2790+
2791+ real_t x_rot_snapped = Math::snapped (cursor.unsnapped_x_rot , snap_angle);
2792+ real_t y_rot_snapped = Math::snapped (cursor.unsnapped_y_rot , snap_angle);
2793+
2794+ real_t x_dist = Math::abs (cursor.unsnapped_x_rot - x_rot_snapped);
2795+ real_t y_dist = Math::abs (cursor.unsnapped_y_rot - y_rot_snapped);
2796+
2797+ if (x_dist < snap_threshold && y_dist < snap_threshold) {
2798+ cursor.x_rot = x_rot_snapped;
2799+ cursor.y_rot = y_rot_snapped;
2800+
2801+ real_t y_rot_wrapped = Math::wrapf (y_rot_snapped, (real_t )-Math::PI, (real_t )Math::PI);
2802+
2803+ if (Math::abs (x_rot_snapped) < snap_threshold) {
2804+ if (Math::abs (y_rot_wrapped) < snap_threshold) {
2805+ view_type = VIEW_TYPE_FRONT;
2806+ } else if (Math::abs (Math::abs (y_rot_wrapped) - Math::PI) < snap_threshold) {
2807+ view_type = VIEW_TYPE_REAR;
2808+ } else if (Math::abs (y_rot_wrapped - Math::PI / 2.0 ) < snap_threshold) {
2809+ view_type = VIEW_TYPE_LEFT;
2810+ } else if (Math::abs (y_rot_wrapped + Math::PI / 2.0 ) < snap_threshold) {
2811+ view_type = VIEW_TYPE_RIGHT;
2812+ } else {
2813+ // Only switch to ortho for 90-degree views.
2814+ return ;
2815+ }
2816+ _set_auto_orthogonal ();
2817+ _update_name ();
2818+ } else if (Math::abs (Math::abs (x_rot_snapped) - Math::PI / 2.0 ) < snap_threshold) {
2819+ if (Math::abs (y_rot_wrapped) < snap_threshold ||
2820+ Math::abs (Math::abs (y_rot_wrapped) - Math::PI) < snap_threshold ||
2821+ Math::abs (y_rot_wrapped - Math::PI / 2.0 ) < snap_threshold ||
2822+ Math::abs (y_rot_wrapped + Math::PI / 2.0 ) < snap_threshold) {
2823+ view_type = x_rot_snapped > 0 ? VIEW_TYPE_TOP : VIEW_TYPE_BOTTOM;
2824+ _set_auto_orthogonal ();
2825+ _update_name ();
2826+ }
2827+ }
2828+
2829+ return ;
2830+ }
27902831 }
2832+
27912833 view_type = VIEW_TYPE_USER;
2792- _update_name ();
2834+ if (orthogonal && auto_orthogonal) {
2835+ _menu_option (VIEW_PERSPECTIVE);
2836+ }
27932837}
27942838
27952839void Node3DEditorViewport::_nav_look (Ref<InputEventWithModifiers> p_event, const Vector2 &p_relative) {
@@ -2817,8 +2861,10 @@ void Node3DEditorViewport::_nav_look(Ref<InputEventWithModifiers> p_event, const
28172861 }
28182862 // Clamp the Y rotation to roughly -90..90 degrees so the user can't look upside-down and end up disoriented.
28192863 cursor.x_rot = CLAMP (cursor.x_rot , -1.57 , 1.57 );
2864+ cursor.unsnapped_x_rot = cursor.x_rot ;
28202865
28212866 cursor.y_rot += p_relative.x * radians_per_pixel;
2867+ cursor.unsnapped_y_rot = cursor.y_rot ;
28222868
28232869 // Look is like the opposite of Orbit: the focus point rotates around the camera
28242870 Transform3D camera_transform = to_camera_transform (cursor);
@@ -3803,6 +3849,8 @@ void Node3DEditorViewport::_apply_camera_transform_to_cursor() {
38033849
38043850 cursor.x_rot = -camera_transform.basis .get_euler ().x ;
38053851 cursor.y_rot = -camera_transform.basis .get_euler ().y ;
3852+ cursor.unsnapped_x_rot = cursor.x_rot ;
3853+ cursor.unsnapped_y_rot = cursor.y_rot ;
38063854}
38073855
38083856void Node3DEditorViewport::_menu_option (int p_option) {
@@ -3811,6 +3859,8 @@ void Node3DEditorViewport::_menu_option(int p_option) {
38113859 case VIEW_TOP: {
38123860 cursor.y_rot = 0 ;
38133861 cursor.x_rot = Math::PI / 2.0 ;
3862+ cursor.unsnapped_y_rot = cursor.y_rot ;
3863+ cursor.unsnapped_x_rot = cursor.x_rot ;
38143864 set_message (TTR (" Top View." ), 2 );
38153865 view_type = VIEW_TYPE_TOP;
38163866 _set_auto_orthogonal ();
@@ -3820,6 +3870,8 @@ void Node3DEditorViewport::_menu_option(int p_option) {
38203870 case VIEW_BOTTOM: {
38213871 cursor.y_rot = 0 ;
38223872 cursor.x_rot = -Math::PI / 2.0 ;
3873+ cursor.unsnapped_y_rot = cursor.y_rot ;
3874+ cursor.unsnapped_x_rot = cursor.x_rot ;
38233875 set_message (TTR (" Bottom View." ), 2 );
38243876 view_type = VIEW_TYPE_BOTTOM;
38253877 _set_auto_orthogonal ();
@@ -3829,6 +3881,8 @@ void Node3DEditorViewport::_menu_option(int p_option) {
38293881 case VIEW_LEFT: {
38303882 cursor.x_rot = 0 ;
38313883 cursor.y_rot = Math::PI / 2.0 ;
3884+ cursor.unsnapped_x_rot = cursor.x_rot ;
3885+ cursor.unsnapped_y_rot = cursor.y_rot ;
38323886 set_message (TTR (" Left View." ), 2 );
38333887 view_type = VIEW_TYPE_LEFT;
38343888 _set_auto_orthogonal ();
@@ -3838,6 +3892,8 @@ void Node3DEditorViewport::_menu_option(int p_option) {
38383892 case VIEW_RIGHT: {
38393893 cursor.x_rot = 0 ;
38403894 cursor.y_rot = -Math::PI / 2.0 ;
3895+ cursor.unsnapped_x_rot = cursor.x_rot ;
3896+ cursor.unsnapped_y_rot = cursor.y_rot ;
38413897 set_message (TTR (" Right View." ), 2 );
38423898 view_type = VIEW_TYPE_RIGHT;
38433899 _set_auto_orthogonal ();
@@ -3847,6 +3903,8 @@ void Node3DEditorViewport::_menu_option(int p_option) {
38473903 case VIEW_FRONT: {
38483904 cursor.x_rot = 0 ;
38493905 cursor.y_rot = 0 ;
3906+ cursor.unsnapped_x_rot = cursor.x_rot ;
3907+ cursor.unsnapped_y_rot = cursor.y_rot ;
38503908 set_message (TTR (" Front View." ), 2 );
38513909 view_type = VIEW_TYPE_FRONT;
38523910 _set_auto_orthogonal ();
@@ -3856,6 +3914,8 @@ void Node3DEditorViewport::_menu_option(int p_option) {
38563914 case VIEW_REAR: {
38573915 cursor.x_rot = 0 ;
38583916 cursor.y_rot = Math::PI;
3917+ cursor.unsnapped_x_rot = cursor.x_rot ;
3918+ cursor.unsnapped_y_rot = cursor.y_rot ;
38593919 set_message (TTR (" Rear View." ), 2 );
38603920 view_type = VIEW_TYPE_REAR;
38613921 _set_auto_orthogonal ();
@@ -4485,9 +4545,11 @@ void Node3DEditorViewport::set_state(const Dictionary &p_state) {
44854545 }
44864546 if (p_state.has (" x_rotation" )) {
44874547 cursor.x_rot = p_state[" x_rotation" ];
4548+ cursor.unsnapped_x_rot = cursor.x_rot ;
44884549 }
44894550 if (p_state.has (" y_rotation" )) {
44904551 cursor.y_rot = p_state[" y_rotation" ];
4552+ cursor.unsnapped_y_rot = cursor.y_rot ;
44914553 }
44924554 if (p_state.has (" distance" )) {
44934555 cursor.distance = p_state[" distance" ];
@@ -6056,6 +6118,8 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p
60566118 // Registering with Key::NONE intentionally creates an empty Array.
60576119 register_shortcut_action (" spatial_editor/viewport_orbit_modifier_1" , TTRC (" Viewport Orbit Modifier 1" ), Key::NONE);
60586120 register_shortcut_action (" spatial_editor/viewport_orbit_modifier_2" , TTRC (" Viewport Orbit Modifier 2" ), Key::NONE);
6121+ register_shortcut_action (" spatial_editor/viewport_orbit_snap_modifier_1" , TTRC (" Viewport Orbit Snap Modifier 1" ), Key::ALT);
6122+ register_shortcut_action (" spatial_editor/viewport_orbit_snap_modifier_2" , TTRC (" Viewport Orbit Snap Modifier 2" ), Key::NONE);
60596123 register_shortcut_action (" spatial_editor/viewport_pan_modifier_1" , TTRC (" Viewport Pan Modifier 1" ), Key::SHIFT);
60606124 register_shortcut_action (" spatial_editor/viewport_pan_modifier_2" , TTRC (" Viewport Pan Modifier 2" ), Key::NONE);
60616125 register_shortcut_action (" spatial_editor/viewport_zoom_modifier_1" , TTRC (" Viewport Zoom Modifier 1" ), Key::SHIFT);
0 commit comments