Blender V4.3
view3d_navigate_view_rotate.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
9#include "BKE_context.hh"
10
11#include "BLI_math_matrix.h"
12#include "BLI_math_rotation.h"
13#include "BLI_math_vector.h"
14
15#include "WM_api.hh"
16
17#include "ED_screen.hh"
18
19#include "view3d_intern.hh"
20#include "view3d_navigate.hh" /* own include */
21
22/* -------------------------------------------------------------------- */
27{
28 static const EnumPropertyItem modal_items[] = {
29 {VIEW_MODAL_CANCEL, "CANCEL", 0, "Cancel", ""},
30 {VIEW_MODAL_CONFIRM, "CONFIRM", 0, "Confirm", ""},
31
32 {VIEWROT_MODAL_AXIS_SNAP_ENABLE, "AXIS_SNAP_ENABLE", 0, "Axis Snap", ""},
33 {VIEWROT_MODAL_AXIS_SNAP_DISABLE, "AXIS_SNAP_DISABLE", 0, "Axis Snap (Off)", ""},
34
35 {VIEWROT_MODAL_SWITCH_ZOOM, "SWITCH_TO_ZOOM", 0, "Switch to Zoom"},
36 {VIEWROT_MODAL_SWITCH_MOVE, "SWITCH_TO_MOVE", 0, "Switch to Move"},
37
38 {0, nullptr, 0, nullptr, nullptr},
39 };
40
41 wmKeyMap *keymap = WM_modalkeymap_find(keyconf, "View3D Rotate Modal");
42
43 /* This function is called for each space-type, only needs to add map once. */
44 if (keymap && keymap->modal_items) {
45 return;
46 }
47
48 keymap = WM_modalkeymap_ensure(keyconf, "View3D Rotate Modal", modal_items);
49
50 /* assign map to operators */
51 WM_modalkeymap_assign(keymap, "VIEW3D_OT_rotate");
52}
53
55{
56 const float axis_limit = DEG2RADF(45 / 3);
57
58 RegionView3D *rv3d = vod->rv3d;
59
60 float viewquat_inv[4];
61 float zaxis[3] = {0, 0, 1};
62 float zaxis_best[3];
63 int x, y, z;
64 bool found = false;
65
66 invert_qt_qt_normalized(viewquat_inv, vod->curr.viewquat);
67
68 mul_qt_v3(viewquat_inv, zaxis);
69 normalize_v3(zaxis);
70
71 for (x = -1; x < 2; x++) {
72 for (y = -1; y < 2; y++) {
73 for (z = -1; z < 2; z++) {
74 if (x || y || z) {
75 float zaxis_test[3] = {float(x), float(y), float(z)};
76
77 normalize_v3(zaxis_test);
78
79 if (angle_normalized_v3v3(zaxis_test, zaxis) < axis_limit) {
80 copy_v3_v3(zaxis_best, zaxis_test);
81 found = true;
82 }
83 }
84 }
85 }
86 }
87
88 if (found) {
89
90 /* find the best roll */
91 float quat_roll[4], quat_final[4], quat_best[4], quat_snap[4];
92 float viewquat_align[4]; /* viewquat aligned to zaxis_best */
93 float viewquat_align_inv[4]; /* viewquat aligned to zaxis_best */
94 float best_angle = axis_limit;
95 int j;
96
97 /* viewquat_align is the original viewquat aligned to the snapped axis
98 * for testing roll */
99 rotation_between_vecs_to_quat(viewquat_align, zaxis_best, zaxis);
100 normalize_qt(viewquat_align);
101 mul_qt_qtqt(viewquat_align, vod->curr.viewquat, viewquat_align);
102 normalize_qt(viewquat_align);
103 invert_qt_qt_normalized(viewquat_align_inv, viewquat_align);
104
105 vec_to_quat(quat_snap, zaxis_best, OB_NEGZ, OB_POSY);
106 normalize_qt(quat_snap);
107 invert_qt_normalized(quat_snap);
108
109 /* check if we can find the roll */
110 found = false;
111
112 /* find best roll */
113 for (j = 0; j < 8; j++) {
114 float angle;
115 float xaxis1[3] = {1, 0, 0};
116 float xaxis2[3] = {1, 0, 0};
117 float quat_final_inv[4];
118
119 axis_angle_to_quat(quat_roll, zaxis_best, float(j * DEG2RADF(45.0f)));
120 normalize_qt(quat_roll);
121
122 mul_qt_qtqt(quat_final, quat_snap, quat_roll);
123 normalize_qt(quat_final);
124
125 /* compare 2 vector angles to find the least roll */
126 invert_qt_qt_normalized(quat_final_inv, quat_final);
127 mul_qt_v3(viewquat_align_inv, xaxis1);
128 mul_qt_v3(quat_final_inv, xaxis2);
129 angle = angle_v3v3(xaxis1, xaxis2);
130
131 if (angle <= best_angle) {
132 found = true;
133 best_angle = angle;
134 copy_qt_qt(quat_best, quat_final);
135 }
136 }
137
138 if (found) {
139 /* lock 'quat_best' to an axis view if we can */
141 quat_best, 0.01f, &rv3d->view, &rv3d->view_axis_roll);
142 }
143 else {
144 copy_qt_qt(quat_best, viewquat_align);
145 }
146
147 copy_qt_qt(rv3d->viewquat, quat_best);
148
150
151 if (U.uiflag & USER_AUTOPERSP) {
152 if (RV3D_VIEW_IS_AXIS(rv3d->view)) {
153 if (rv3d->persp == RV3D_PERSP) {
154 rv3d->persp = RV3D_ORTHO;
155 }
156 }
157 }
158 }
159 else if (U.uiflag & USER_AUTOPERSP) {
161 }
162}
163
164static void viewrotate_apply(ViewOpsData *vod, const int event_xy[2])
165{
166 RegionView3D *rv3d = vod->rv3d;
167
168 rv3d->view = RV3D_VIEW_USER; /* need to reset every time because of view snapping */
169
170 if (U.flag & USER_TRACKBALL) {
171 float axis[3], q1[4], dvec[3], newvec[3];
172 float angle;
173
174 {
175 const int event_xy_offset[2] = {
176 event_xy[0] + vod->init.event_xy_offset[0],
177 event_xy[1] + vod->init.event_xy_offset[1],
178 };
179 calctrackballvec(&vod->region->winrct, event_xy_offset, newvec);
180 }
181
182 sub_v3_v3v3(dvec, newvec, vod->init.trackvec);
183
184 angle = (len_v3(dvec) / (2.0f * V3D_OP_TRACKBALLSIZE)) * float(M_PI);
185
186 /* Before applying the sensitivity this is rotating 1:1,
187 * where the cursor would match the surface of a sphere in the view. */
188 angle *= U.view_rotate_sensitivity_trackball;
189
190 /* Allow for rotation beyond the interval [-pi, pi] */
191 angle = angle_wrap_rad(angle);
192
193 /* This relation is used instead of the actual angle between vectors
194 * so that the angle of rotation is linearly proportional to
195 * the distance that the mouse is dragged. */
196
197 cross_v3_v3v3(axis, vod->init.trackvec, newvec);
198 axis_angle_to_quat(q1, axis, angle);
199
200 mul_qt_qtqt(vod->curr.viewquat, q1, vod->init.quat);
201
203 }
204 else {
205 float quat_local_x[4], quat_global_z[4];
206 float m[3][3];
207 float m_inv[3][3];
208 const float zvec_global[3] = {0.0f, 0.0f, 1.0f};
209 float xaxis[3];
210
211 /* Radians per-pixel. */
212 const float sensitivity = U.view_rotate_sensitivity_turntable / UI_SCALE_FAC;
213
214 /* Get the 3x3 matrix and its inverse from the quaternion */
215 quat_to_mat3(m, vod->curr.viewquat);
216 invert_m3_m3(m_inv, m);
217
218 /* Avoid Gimbal Lock
219 *
220 * Even though turn-table mode is in use, this can occur when the user exits the camera view
221 * or when aligning the view to a rotated object.
222 *
223 * We have gimbal lock when the user's view is rotated +/- 90 degrees along the view axis.
224 * In this case the vertical rotation is the same as the sideways turntable motion.
225 * Making it impossible to get out of the gimbal locked state without resetting the view.
226 *
227 * The logic below lets the user exit out of this state without any abrupt 'fix'
228 * which would be disorienting.
229 *
230 * This works by blending two horizons:
231 * - Rotated-horizon: `cross_v3_v3v3(xaxis, zvec_global, m_inv[2])`
232 * When only this is used, this turntable rotation works - but it's side-ways
233 * (as if the entire turn-table has been placed on its side)
234 * While there is no gimbal lock, it's also awkward to use.
235 * - Un-rotated-horizon: `m_inv[0]`
236 * When only this is used, the turntable rotation can have gimbal lock.
237 *
238 * The solution used here is to blend between these two values,
239 * so the severity of the gimbal lock is used to blend the rotated horizon.
240 * Blending isn't essential, it just makes the transition smoother.
241 *
242 * This allows sideways turn-table rotation on a Z axis that isn't world-space Z,
243 * While up-down turntable rotation eventually corrects gimbal lock. */
244#if 1
245 if (len_squared_v3v3(zvec_global, m_inv[2]) > 0.001f) {
246 float fac;
247 cross_v3_v3v3(xaxis, zvec_global, m_inv[2]);
248 if (dot_v3v3(xaxis, m_inv[0]) < 0) {
249 negate_v3(xaxis);
250 }
251 fac = angle_normalized_v3v3(zvec_global, m_inv[2]) / float(M_PI);
252 fac = fabsf(fac - 0.5f) * 2;
253 fac = fac * fac;
254 interp_v3_v3v3(xaxis, xaxis, m_inv[0], fac);
255 }
256 else {
257 copy_v3_v3(xaxis, m_inv[0]);
258 }
259#else
260 copy_v3_v3(xaxis, m_inv[0]);
261#endif
262
263 /* Determine the direction of the x vector (for rotating up and down) */
264 /* This can likely be computed directly from the quaternion. */
265
266 /* Perform the up/down rotation */
267 axis_angle_to_quat(quat_local_x, xaxis, sensitivity * -(event_xy[1] - vod->prev.event_xy[1]));
268 mul_qt_qtqt(quat_local_x, vod->curr.viewquat, quat_local_x);
269
270 /* Perform the orbital rotation */
272 quat_global_z, 'Z', sensitivity * vod->reverse * (event_xy[0] - vod->prev.event_xy[0]));
273 mul_qt_qtqt(vod->curr.viewquat, quat_local_x, quat_global_z);
274
276 }
277
278 /* avoid precision loss over time */
280
281 /* use a working copy so view rotation locking doesn't overwrite the locked
282 * rotation back into the view we calculate with */
283 copy_qt_qt(rv3d->viewquat, vod->curr.viewquat);
284
285 /* Check for view snap,
286 * NOTE: don't apply snap to `vod->viewquat` so the view won't jam up. */
287 if (vod->axis_snap) {
289 }
290 vod->prev.event_xy[0] = event_xy[0];
291 vod->prev.event_xy[1] = event_xy[1];
292
293 ED_view3d_camera_lock_sync(vod->depsgraph, vod->v3d, rv3d);
294
296}
297
299 ViewOpsData *vod,
300 const eV3D_OpEvent event_code,
301 const int xy[2])
302{
303 bool use_autokey = false;
305
306 switch (event_code) {
307 case VIEW_APPLY: {
308 viewrotate_apply(vod, xy);
310 use_autokey = true;
311 }
312 break;
313 }
314 case VIEW_CONFIRM: {
315 use_autokey = true;
317 break;
318 }
319 case VIEW_CANCEL: {
320 vod->state_restore();
322 break;
323 }
324 case VIEW_PASS:
325 break;
326 }
327
328 if (use_autokey) {
329 ED_view3d_camera_lock_autokey(vod->v3d, vod->rv3d, C, true, true);
330 }
331
332 return ret;
333}
334
336 ViewOpsData *vod,
337 const wmEvent *event,
338 PointerRNA * /*ptr*/)
339{
340 if (vod->use_dyn_ofs && (vod->rv3d->is_persp == false)) {
342 }
343
345
346 if (event_code == VIEW_CONFIRM) {
347 /* MOUSEROTATE performs orbital rotation, so y axis delta is set to 0 */
348 const bool is_inverted = (event->flag & WM_EVENT_SCROLL_INVERT) &&
349 (event->type != MOUSEROTATE);
350
351 int m_xy[2];
352 if (is_inverted) {
353 m_xy[0] = 2 * event->xy[0] - event->prev_xy[0];
354 m_xy[1] = 2 * event->xy[1] - event->prev_xy[1];
355 }
356 else {
357 copy_v2_v2_int(m_xy, event->prev_xy);
358 }
359 viewrotate_apply(vod, m_xy);
360 return OPERATOR_FINISHED;
361 }
362
364}
365
366static int viewrotate_invoke(bContext *C, wmOperator *op, const wmEvent *event)
367{
368 return view3d_navigate_invoke_impl(C, op, event, &ViewOpsType_rotate);
369}
370
372{
373 /* identifiers */
374 ot->name = "Rotate View";
375 ot->description = "Rotate the view";
377
378 /* api callbacks */
383
384 /* flags */
386
388}
389
394 /*idname*/ "VIEW3D_OT_rotate",
395 /*poll_fn*/ view3d_rotation_poll,
396 /*init_fn*/ viewrotate_invoke_impl,
397 /*apply_fn*/ viewrotate_modal_impl,
398};
wmWindowManager * CTX_wm_manager(const bContext *C)
#define M_PI
bool invert_m3_m3(float inverse[3][3], const float mat[3][3])
void rotation_between_vecs_to_quat(float q[4], const float v1[3], const float v2[3])
void invert_qt_normalized(float q[4])
void axis_angle_to_quat(float r[4], const float axis[3], float angle)
void quat_to_mat3(float m[3][3], const float q[4])
void axis_angle_to_quat_single(float q[4], char axis, float angle)
void vec_to_quat(float q[4], const float vec[3], short axis, short upflag)
float normalize_qt(float q[4])
#define DEG2RADF(_deg)
void mul_qt_v3(const float q[4], float r[3])
void invert_qt_qt_normalized(float q1[4], const float q2[4])
void mul_qt_qtqt(float q[4], const float a[4], const float b[4])
void copy_qt_qt(float q[4], const float a[4])
float angle_wrap_rad(float angle)
float angle_v3v3(const float a[3], const float b[3]) ATTR_WARN_UNUSED_RESULT
MINLINE float len_squared_v3v3(const float a[3], const float b[3]) ATTR_WARN_UNUSED_RESULT
MINLINE void sub_v3_v3v3(float r[3], const float a[3], const float b[3])
MINLINE void copy_v2_v2_int(int r[2], const int a[2])
MINLINE void copy_v3_v3(float r[3], const float a[3])
MINLINE float dot_v3v3(const float a[3], const float b[3]) ATTR_WARN_UNUSED_RESULT
void interp_v3_v3v3(float r[3], const float a[3], const float b[3], float t)
Definition math_vector.c:36
MINLINE void cross_v3_v3v3(float r[3], const float a[3], const float b[3])
MINLINE void negate_v3(float r[3])
float angle_normalized_v3v3(const float v1[3], const float v2[3]) ATTR_WARN_UNUSED_RESULT
MINLINE float normalize_v3(float n[3])
MINLINE float len_v3(const float a[3]) ATTR_WARN_UNUSED_RESULT
#define ELEM(...)
@ OB_NEGZ
@ OB_POSY
@ USER_AUTOPERSP
@ USER_TRACKBALL
#define UI_SCALE_FAC
#define RV3D_VIEW_IS_AXIS(view)
@ RV3D_PERSP
@ RV3D_ORTHO
@ RV3D_VIEW_USER
@ OPERATOR_RUNNING_MODAL
bScreen * ED_screen_animation_playing(const wmWindowManager *wm)
void ED_region_tag_redraw(ARegion *region)
Definition area.cc:634
bool ED_view3d_camera_lock_sync(const Depsgraph *depsgraph, View3D *v3d, RegionView3D *rv3d)
bool ED_view3d_quat_to_axis_view_and_reset_quat(float quat[4], float epsilon, char *r_view, char *r_view_axis_roll)
bool ED_view3d_camera_lock_autokey(View3D *v3d, RegionView3D *rv3d, bContext *C, bool do_rotate, bool do_translate)
static double angle(const Eigen::Vector3d &v1, const Eigen::Vector3d &v2)
Definition IK_Math.h:125
static void axis_limit(const int axis, const float limits[2], float co[3], float dcut[3])
@ OPTYPE_BLOCKING
Definition WM_types.hh:164
@ OPTYPE_GRAB_CURSOR_XY
Definition WM_types.hh:168
@ WM_EVENT_SCROLL_INVERT
Definition WM_types.hh:643
unsigned int U
Definition btGjkEpa3.h:78
SIMD_FORCE_INLINE const btScalar & z() const
Return the z value.
Definition btQuadWord.h:117
#define fabsf(x)
draw_view in_light_buf[] float
@ VIEW_CONFIRM
Definition image_ops.cc:587
@ VIEW_PASS
Definition image_ops.cc:585
@ VIEW_APPLY
Definition image_ops.cc:586
return ret
struct ViewOpsData::@543 init
char persp_with_auto_persp_applied
bool use_dyn_ofs_ortho_correction
Depsgraph * depsgraph
ARegion * region
struct ViewOpsData::@544 prev
RegionView3D * rv3d
struct ViewOpsData::@545 curr
const char * idname
int prev_xy[2]
Definition WM_types.hh:785
short type
Definition WM_types.hh:722
const void * modal_items
const char * name
Definition WM_types.hh:990
bool(* poll)(bContext *C) ATTR_WARN_UNUSED_RESULT
Definition WM_types.hh:1042
const char * idname
Definition WM_types.hh:992
int(* modal)(bContext *C, wmOperator *op, const wmEvent *event) ATTR_WARN_UNUSED_RESULT
Definition WM_types.hh:1036
int(* invoke)(bContext *C, wmOperator *op, const wmEvent *event) ATTR_WARN_UNUSED_RESULT
Definition WM_types.hh:1022
const char * description
Definition WM_types.hh:996
void(* cancel)(bContext *C, wmOperator *op)
Definition WM_types.hh:1028
void view3d_navigate_cancel_fn(bContext *C, wmOperator *op)
void view3d_operator_properties_common(wmOperatorType *ot, const enum eV3D_OpPropFlag flag)
bool view3d_rotation_poll(bContext *C)
int view3d_navigate_modal_fn(bContext *C, wmOperator *op, const wmEvent *event)
void calctrackballvec(const rcti *rect, const int event_xy[2], float r_dir[3])
int view3d_navigate_invoke_impl(bContext *C, wmOperator *op, const wmEvent *event, const ViewOpsType *nav_type)
void viewrotate_apply_dyn_ofs(ViewOpsData *vod, const float viewquat_new[4])
@ VIEWOPS_FLAG_ORBIT_SELECT
@ VIEWOPS_FLAG_DEPTH_NAVIGATE
@ VIEWOPS_FLAG_PERSP_ENSURE
eV3D_OpEvent
@ VIEW_CANCEL
@ VIEWROT_MODAL_AXIS_SNAP_ENABLE
@ VIEW_MODAL_CANCEL
@ VIEWROT_MODAL_SWITCH_MOVE
@ VIEW_MODAL_CONFIRM
@ VIEWROT_MODAL_SWITCH_ZOOM
@ VIEWROT_MODAL_AXIS_SNAP_DISABLE
@ V3D_OP_PROP_USE_MOUSE_INIT
#define V3D_OP_TRACKBALLSIZE
static void viewrotate_apply(ViewOpsData *vod, const int event_xy[2])
static int viewrotate_invoke(bContext *C, wmOperator *op, const wmEvent *event)
void VIEW3D_OT_rotate(wmOperatorType *ot)
const ViewOpsType ViewOpsType_rotate
void viewrotate_modal_keymap(wmKeyConfig *keyconf)
static int viewrotate_invoke_impl(bContext *, ViewOpsData *vod, const wmEvent *event, PointerRNA *)
static void viewrotate_apply_snap(ViewOpsData *vod)
static int viewrotate_modal_impl(bContext *C, ViewOpsData *vod, const eV3D_OpEvent event_code, const int xy[2])
int xy[2]
Definition wm_draw.cc:170
@ MOUSEPAN
@ MOUSEROTATE
wmOperatorType * ot
Definition wm_files.cc:4125
wmKeyMap * WM_modalkeymap_ensure(wmKeyConfig *keyconf, const char *idname, const EnumPropertyItem *items)
Definition wm_keymap.cc:933
void WM_modalkeymap_assign(wmKeyMap *km, const char *opname)
wmKeyMap * WM_modalkeymap_find(wmKeyConfig *keyconf, const char *idname)
Definition wm_keymap.cc:960