Cleanup: move operator constants out of DNA
[blender.git] / source / blender / editors / space_view3d / view3d_edit.c
1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  *
16  * The Original Code is Copyright (C) 2008 Blender Foundation.
17  * All rights reserved.
18  */
19
20 /** \file
21  * \ingroup spview3d
22  *
23  * 3D view manipulation/operators.
24  */
25
26 #include <string.h>
27 #include <stdio.h>
28 #include <math.h>
29 #include <float.h>
30
31 #include "DNA_armature_types.h"
32 #include "DNA_camera_types.h"
33 #include "DNA_curve_types.h"
34 #include "DNA_object_types.h"
35 #include "DNA_scene_types.h"
36 #include "DNA_gpencil_types.h"
37
38 #include "MEM_guardedalloc.h"
39
40 #include "BLI_blenlib.h"
41 #include "BLI_math.h"
42 #include "BLI_utildefines.h"
43
44 #include "BKE_action.h"
45 #include "BKE_armature.h"
46 #include "BKE_camera.h"
47 #include "BKE_context.h"
48 #include "BKE_font.h"
49 #include "BKE_gpencil.h"
50 #include "BKE_layer.h"
51 #include "BKE_library.h"
52 #include "BKE_main.h"
53 #include "BKE_object.h"
54 #include "BKE_paint.h"
55 #include "BKE_report.h"
56 #include "BKE_scene.h"
57 #include "BKE_screen.h"
58
59 #include "DEG_depsgraph.h"
60 #include "DEG_depsgraph_query.h"
61
62 #include "WM_api.h"
63 #include "WM_types.h"
64 #include "WM_message.h"
65
66 #include "RNA_access.h"
67 #include "RNA_define.h"
68
69 #include "ED_armature.h"
70 #include "ED_particle.h"
71 #include "ED_screen.h"
72 #include "ED_transform.h"
73 #include "ED_mesh.h"
74 #include "ED_view3d.h"
75 #include "ED_transform_snap_object_context.h"
76
77 #include "UI_resources.h"
78
79 #include "PIL_time.h"
80
81 #include "view3d_intern.h"  /* own include */
82
83
84 enum {
85         HAS_TRANSLATE = (1 << 0),
86         HAS_ROTATE = (1 << 0),
87 };
88
89 /* -------------------------------------------------------------------- */
90 /** \name Generic View Operator Properties
91  * \{ */
92
93 enum eV3D_OpPropFlag {
94         V3D_OP_PROP_MOUSE_CO = (1 << 0),
95         V3D_OP_PROP_DELTA = (1 << 1),
96         V3D_OP_PROP_USE_ALL_REGIONS = (1 << 2),
97         V3D_OP_PROP_USE_MOUSE_INIT = (1 << 3),
98 };
99
100 static void view3d_operator_properties_common(wmOperatorType *ot, const enum eV3D_OpPropFlag flag)
101 {
102         if (flag & V3D_OP_PROP_MOUSE_CO) {
103                 PropertyRNA *prop;
104                 prop = RNA_def_int(ot->srna, "mx", 0, 0, INT_MAX, "Region Position X", "", 0, INT_MAX);
105                 RNA_def_property_flag(prop, PROP_HIDDEN);
106                 prop = RNA_def_int(ot->srna, "my", 0, 0, INT_MAX, "Region Position Y", "", 0, INT_MAX);
107                 RNA_def_property_flag(prop, PROP_HIDDEN);
108         }
109         if (flag & V3D_OP_PROP_DELTA) {
110                 RNA_def_int(ot->srna, "delta", 0, INT_MIN, INT_MAX, "Delta", "", INT_MIN, INT_MAX);
111         }
112         if (flag & V3D_OP_PROP_USE_ALL_REGIONS) {
113                 PropertyRNA *prop;
114                 prop = RNA_def_boolean(ot->srna, "use_all_regions", 0, "All Regions", "View selected for all regions");
115                 RNA_def_property_flag(prop, PROP_SKIP_SAVE);
116         }
117         if (flag & V3D_OP_PROP_USE_MOUSE_INIT) {
118                 /* Disable when view operators are initialized from buttons. */
119                 PropertyRNA *prop;
120                 prop = RNA_def_boolean(ot->srna, "use_mouse_init", true, "Mouse Init", "Use initial mouse position");
121                 RNA_def_property_flag(prop, PROP_SKIP_SAVE | PROP_HIDDEN);
122         }
123 }
124
125 /** \} */
126
127 /* -------------------------------------------------------------------- */
128 /** \name Generic View Operator Custom-Data
129  * \{ */
130
131 typedef struct ViewOpsData {
132         /** Context pointers (assigned by #viewops_data_alloc). */
133         Main *bmain;
134         Scene *scene;
135         ScrArea *sa;
136         ARegion *ar;
137         View3D *v3d;
138         RegionView3D *rv3d;
139         Depsgraph *depsgraph;
140
141         /** Needed for continuous zoom. */
142         wmTimer *timer;
143
144         /** Viewport state on initialization, don't change afterwards. */
145         struct {
146                 float dist;
147                 float camzoom;
148                 float quat[4];
149                 /** #wmEvent.x, y. */
150                 int event_xy[2];
151                 /** Offset to use when #VIEWOPS_FLAG_USE_MOUSE_INIT is not set.
152                  * so we can simulate pressing in the middle of the screen. */
153                 int event_xy_offset[2];
154                 /** #wmEvent.type that triggered the operator. */
155                 int event_type;
156                 float ofs[3];
157                 /** Initial distance to 'ofs'. */
158                 float zfac;
159
160                 /** Trackball rotation only. */
161                 float trackvec[3];
162                 /** Dolly only. */
163                 float mousevec[3];
164         } init;
165
166         /** Previous state (previous modal event handled). */
167         struct {
168                 int event_xy[2];
169                 /** For operators that use time-steps (continuous zoom). */
170                 double time;
171         } prev;
172
173         /** Current state. */
174         struct {
175                 /** Working copy of #RegionView3D.viewquat, needed for rotation calculation
176                  * so we can apply snap to the view-port while keeping the unsnapped rotation
177                  * here to use when snap is disabled and for continued calculation. */
178                 float viewquat[4];
179         } curr;
180
181         float reverse;
182         bool axis_snap;  /* view rotate only */
183
184         /** Use for orbit selection and auto-dist. */
185         float dyn_ofs[3];
186         bool use_dyn_ofs;
187 } ViewOpsData;
188
189 #define TRACKBALLSIZE  (1.1f)
190
191 static void calctrackballvec(const rcti *rect, const int event_xy[2], float vec[3])
192 {
193         const float radius = TRACKBALLSIZE;
194         const float t = radius / (float)M_SQRT2;
195         float x, y, z, d;
196
197         /* normalize x and y */
198         x = BLI_rcti_cent_x(rect) - event_xy[0];
199         x /= (float)(BLI_rcti_size_x(rect) / 4);
200         y = BLI_rcti_cent_y(rect) - event_xy[1];
201         y /= (float)(BLI_rcti_size_y(rect) / 2);
202         d = sqrtf(x * x + y * y);
203         if (d < t) { /* Inside sphere */
204                 z = sqrtf(radius * radius - d * d);
205         }
206         else { /* On hyperbola */
207                 z = t * t / d;
208         }
209
210         vec[0] = x;
211         vec[1] = y;
212         vec[2] = -z;     /* yah yah! */
213 }
214
215 /**
216  * Allocate and fill in context pointers for #ViewOpsData
217  */
218 static void viewops_data_alloc(bContext *C, wmOperator *op)
219 {
220         ViewOpsData *vod = MEM_callocN(sizeof(ViewOpsData), "viewops data");
221
222         /* store data */
223         op->customdata = vod;
224         vod->bmain = CTX_data_main(C);
225         vod->depsgraph = CTX_data_depsgraph(C);
226         vod->scene = CTX_data_scene(C);
227         vod->sa = CTX_wm_area(C);
228         vod->ar = CTX_wm_region(C);
229         vod->v3d = vod->sa->spacedata.first;
230         vod->rv3d = vod->ar->regiondata;
231 }
232
233 void view3d_orbit_apply_dyn_ofs(
234         float r_ofs[3], const float ofs_init[3], const float viewquat_old[4],
235         const float viewquat_new[4], const float dyn_ofs[3])
236 {
237         float q[4];
238         invert_qt_qt_normalized(q, viewquat_old);
239         mul_qt_qtqt(q, q, viewquat_new);
240
241         invert_qt_normalized(q);
242
243         sub_v3_v3v3(r_ofs, ofs_init, dyn_ofs);
244         mul_qt_v3(q, r_ofs);
245         add_v3_v3(r_ofs, dyn_ofs);
246 }
247
248 static bool view3d_orbit_calc_center(bContext *C, float r_dyn_ofs[3])
249 {
250         static float lastofs[3] = {0, 0, 0};
251         bool is_set = false;
252
253         const Depsgraph *depsgraph = CTX_data_depsgraph(C);
254         Scene *scene = CTX_data_scene(C);
255         ViewLayer *view_layer_eval = DEG_get_evaluated_view_layer(depsgraph);
256         View3D *v3d = CTX_wm_view3d(C);
257         Object *ob_act_eval = OBACT(view_layer_eval);
258         Object *ob_act = DEG_get_original_object(ob_act_eval);
259
260         if (ob_act && (ob_act->mode & OB_MODE_ALL_PAINT) &&
261             /* with weight-paint + pose-mode, fall through to using calculateTransformCenter */
262             ((ob_act->mode & OB_MODE_WEIGHT_PAINT) && BKE_object_pose_armature_get(ob_act)) == 0)
263         {
264                 /* in case of sculpting use last average stroke position as a rotation
265                  * center, in other cases it's not clear what rotation center shall be
266                  * so just rotate around object origin
267                  */
268                 if (ob_act->mode & (OB_MODE_SCULPT | OB_MODE_TEXTURE_PAINT | OB_MODE_VERTEX_PAINT | OB_MODE_WEIGHT_PAINT)) {
269                         float stroke[3];
270                         BKE_paint_stroke_get_average(scene, ob_act_eval, stroke);
271                         copy_v3_v3(lastofs, stroke);
272                 }
273                 else {
274                         copy_v3_v3(lastofs, ob_act_eval->obmat[3]);
275                 }
276                 is_set = true;
277         }
278         else if (ob_act && (ob_act->mode & OB_MODE_EDIT) && (ob_act->type == OB_FONT)) {
279                 Curve *cu = ob_act_eval->data;
280                 EditFont *ef = cu->editfont;
281                 int i;
282
283                 zero_v3(lastofs);
284                 for (i = 0; i < 4; i++) {
285                         add_v2_v2(lastofs, ef->textcurs[i]);
286                 }
287                 mul_v2_fl(lastofs, 1.0f / 4.0f);
288
289                 mul_m4_v3(ob_act_eval->obmat, lastofs);
290
291                 is_set = true;
292         }
293         else if (ob_act == NULL || ob_act->mode == OB_MODE_OBJECT) {
294                 /* object mode use boundbox centers */
295                 Base *base_eval;
296                 uint tot = 0;
297                 float select_center[3];
298
299                 zero_v3(select_center);
300                 for (base_eval = FIRSTBASE(view_layer_eval); base_eval; base_eval = base_eval->next) {
301                         if (BASE_SELECTED(v3d, base_eval)) {
302                                 /* use the boundbox if we can */
303                                 Object *ob_eval = base_eval->object;
304
305                                 if (ob_eval->runtime.bb && !(ob_eval->runtime.bb->flag & BOUNDBOX_DIRTY)) {
306                                         float cent[3];
307
308                                         BKE_boundbox_calc_center_aabb(ob_eval->runtime.bb, cent);
309
310                                         mul_m4_v3(ob_eval->obmat, cent);
311                                         add_v3_v3(select_center, cent);
312                                 }
313                                 else {
314                                         add_v3_v3(select_center, ob_eval->obmat[3]);
315                                 }
316                                 tot++;
317                         }
318                 }
319                 if (tot) {
320                         mul_v3_fl(select_center, 1.0f / (float)tot);
321                         copy_v3_v3(lastofs, select_center);
322                         is_set = true;
323                 }
324         }
325         else {
326                 /* If there's no selection, lastofs is unmodified and last value since static */
327                 is_set = calculateTransformCenter(C, V3D_AROUND_CENTER_MEDIAN, lastofs, NULL);
328         }
329
330         copy_v3_v3(r_dyn_ofs, lastofs);
331
332         return is_set;
333 }
334
335 enum eViewOpsFlag {
336         /** When enabled, rotate around the selection. */
337         VIEWOPS_FLAG_ORBIT_SELECT = (1 << 0),
338         /** When enabled, use the depth under the cursor for navigation. */
339         VIEWOPS_FLAG_DEPTH_NAVIGATE = (1 << 1),
340         /**
341          * When enabled run #ED_view3d_persp_ensure this may switch out of
342          * camera view when orbiting or switch from ortho to perspective when auto-persp is enabled.
343          * Some operations don't require this (view zoom/pan or ndof where subtle rotation is common
344          * so we don't want it to trigger auto-perspective). */
345         VIEWOPS_FLAG_PERSP_ENSURE = (1 << 2),
346         /** When set, ignore any options that depend on initial cursor location. */
347         VIEWOPS_FLAG_USE_MOUSE_INIT = (1 << 3),
348 };
349
350 static enum eViewOpsFlag viewops_flag_from_args(bool use_select, bool use_depth)
351 {
352         enum eViewOpsFlag flag = 0;
353         if (use_select) {
354                 flag |= VIEWOPS_FLAG_ORBIT_SELECT;
355         }
356         if (use_depth) {
357                 flag |= VIEWOPS_FLAG_DEPTH_NAVIGATE;
358         }
359
360         return flag;
361 }
362
363 static enum eViewOpsFlag viewops_flag_from_prefs(void)
364 {
365         return viewops_flag_from_args(
366                 (U.uiflag & USER_ORBIT_SELECTION) != 0,
367                 (U.uiflag & USER_DEPTH_NAVIGATE) != 0);
368 }
369
370 /**
371  * Calculate the values for #ViewOpsData
372  */
373 static void viewops_data_create(
374         bContext *C, wmOperator *op, const wmEvent *event,
375         enum eViewOpsFlag viewops_flag)
376 {
377         Depsgraph *depsgraph = CTX_data_depsgraph(C);
378         ViewOpsData *vod = op->customdata;
379         RegionView3D *rv3d = vod->rv3d;
380
381         /* Could do this more nicely. */
382         if ((viewops_flag & VIEWOPS_FLAG_USE_MOUSE_INIT) == 0) {
383                 viewops_flag &= ~VIEWOPS_FLAG_DEPTH_NAVIGATE;
384         }
385
386         /* we need the depth info before changing any viewport options */
387         if (viewops_flag & VIEWOPS_FLAG_DEPTH_NAVIGATE) {
388                 float fallback_depth_pt[3];
389
390                 view3d_operator_needs_opengl(C); /* needed for zbuf drawing */
391
392                 negate_v3_v3(fallback_depth_pt, rv3d->ofs);
393
394                 vod->use_dyn_ofs = ED_view3d_autodist(
395                         depsgraph, vod->ar, vod->v3d,
396                         event->mval, vod->dyn_ofs, true, fallback_depth_pt);
397         }
398         else {
399                 vod->use_dyn_ofs = false;
400         }
401
402         if (viewops_flag & VIEWOPS_FLAG_PERSP_ENSURE) {
403                 if (ED_view3d_persp_ensure(depsgraph, vod->v3d, vod->ar)) {
404                         /* If we're switching from camera view to the perspective one,
405                          * need to tag viewport update, so camera vuew and borders
406                          * are properly updated.
407                          */
408                         ED_region_tag_redraw(vod->ar);
409                 }
410         }
411
412         /* set the view from the camera, if view locking is enabled.
413          * we may want to make this optional but for now its needed always */
414         ED_view3d_camera_lock_init(depsgraph, vod->v3d, vod->rv3d);
415
416         vod->init.dist = rv3d->dist;
417         vod->init.camzoom = rv3d->camzoom;
418         copy_qt_qt(vod->init.quat, rv3d->viewquat);
419         vod->init.event_xy[0] = vod->prev.event_xy[0] = event->x;
420         vod->init.event_xy[1] = vod->prev.event_xy[1] = event->y;
421
422         if (viewops_flag & VIEWOPS_FLAG_USE_MOUSE_INIT) {
423                 vod->init.event_xy_offset[0] = 0;
424                 vod->init.event_xy_offset[1] = 0;
425         }
426         else {
427                 /* Simulate the event starting in the middle of the region. */
428                 vod->init.event_xy_offset[0] = BLI_rcti_cent_x(&vod->ar->winrct) - event->x;
429                 vod->init.event_xy_offset[1] = BLI_rcti_cent_y(&vod->ar->winrct) - event->y;
430         }
431
432         vod->init.event_type = event->type;
433         copy_v3_v3(vod->init.ofs, rv3d->ofs);
434
435         copy_qt_qt(vod->curr.viewquat, rv3d->viewquat);
436
437         if (viewops_flag & VIEWOPS_FLAG_ORBIT_SELECT) {
438                 float ofs[3];
439                 if (view3d_orbit_calc_center(C, ofs) || (vod->use_dyn_ofs == false)) {
440                         vod->use_dyn_ofs = true;
441                         negate_v3_v3(vod->dyn_ofs, ofs);
442                         viewops_flag &= ~VIEWOPS_FLAG_DEPTH_NAVIGATE;
443                 }
444         }
445
446         if (viewops_flag & VIEWOPS_FLAG_DEPTH_NAVIGATE) {
447                 if (vod->use_dyn_ofs) {
448                         if (rv3d->is_persp) {
449                                 float my_origin[3]; /* original G.vd->ofs */
450                                 float my_pivot[3]; /* view */
451                                 float dvec[3];
452
453                                 /* locals for dist correction */
454                                 float mat[3][3];
455                                 float upvec[3];
456
457                                 negate_v3_v3(my_origin, rv3d->ofs);             /* ofs is flipped */
458
459                                 /* Set the dist value to be the distance from this 3d point this means youll
460                                  * always be able to zoom into it and panning wont go bad when dist was zero */
461
462                                 /* remove dist value */
463                                 upvec[0] = upvec[1] = 0;
464                                 upvec[2] = rv3d->dist;
465                                 copy_m3_m4(mat, rv3d->viewinv);
466
467                                 mul_m3_v3(mat, upvec);
468                                 sub_v3_v3v3(my_pivot, rv3d->ofs, upvec);
469                                 negate_v3(my_pivot);                /* ofs is flipped */
470
471                                 /* find a new ofs value that is along the view axis
472                                  * (rather than the mouse location) */
473                                 closest_to_line_v3(dvec, vod->dyn_ofs, my_pivot, my_origin);
474                                 vod->init.dist = rv3d->dist = len_v3v3(my_pivot, dvec);
475
476                                 negate_v3_v3(rv3d->ofs, dvec);
477                         }
478                         else {
479                                 const float mval_ar_mid[2] = {
480                                     (float)vod->ar->winx / 2.0f,
481                                     (float)vod->ar->winy / 2.0f};
482
483                                 ED_view3d_win_to_3d(vod->v3d, vod->ar, vod->dyn_ofs, mval_ar_mid, rv3d->ofs);
484                                 negate_v3(rv3d->ofs);
485                         }
486                         negate_v3(vod->dyn_ofs);
487                         copy_v3_v3(vod->init.ofs, rv3d->ofs);
488                 }
489         }
490
491         /* For dolly */
492         ED_view3d_win_to_vector(vod->ar, (const float[2]){UNPACK2(event->mval)}, vod->init.mousevec);
493
494         {
495                 const int event_xy_offset[2] = {
496                         event->x + vod->init.event_xy_offset[0],
497                         event->y + vod->init.event_xy_offset[1],
498                 };
499                 /* For rotation with trackball rotation. */
500                 calctrackballvec(&vod->ar->winrct, event_xy_offset, vod->init.trackvec);
501         }
502
503         {
504                 float tvec[3];
505                 negate_v3_v3(tvec, rv3d->ofs);
506                 vod->init.zfac = ED_view3d_calc_zfac(rv3d, tvec, NULL);
507         }
508
509         vod->reverse = 1.0f;
510         if (rv3d->persmat[2][1] < 0.0f) {
511                 vod->reverse = -1.0f;
512         }
513
514         rv3d->rflag |= RV3D_NAVIGATING;
515 }
516
517 static void viewops_data_free(bContext *C, wmOperator *op)
518 {
519         ARegion *ar;
520 #if 0
521         Paint *p = BKE_paint_get_active_from_context(C);
522 #endif
523         if (op->customdata) {
524                 ViewOpsData *vod = op->customdata;
525                 ar = vod->ar;
526                 vod->rv3d->rflag &= ~RV3D_NAVIGATING;
527
528                 if (vod->timer) {
529                         WM_event_remove_timer(CTX_wm_manager(C), vod->timer->win, vod->timer);
530                 }
531
532                 MEM_freeN(vod);
533                 op->customdata = NULL;
534         }
535         else {
536                 ar = CTX_wm_region(C);
537         }
538
539 #if 0
540         if (p && (p->flags & PAINT_FAST_NAVIGATE))
541 #endif
542         {
543                 ED_region_tag_redraw(ar);
544         }
545 }
546
547 /** \} */
548
549 /* -------------------------------------------------------------------- */
550 /** \name View Rotate Operator
551  * \{ */
552
553 enum {
554         VIEW_PASS = 0,
555         VIEW_APPLY,
556         VIEW_CONFIRM,
557 };
558
559 /* NOTE: these defines are saved in keymap files, do not change values but just add new ones */
560 enum {
561         VIEW_MODAL_CONFIRM              = 1, /* used for all view operations */
562         VIEWROT_MODAL_AXIS_SNAP_ENABLE  = 2,
563         VIEWROT_MODAL_AXIS_SNAP_DISABLE = 3,
564         VIEWROT_MODAL_SWITCH_ZOOM       = 4,
565         VIEWROT_MODAL_SWITCH_MOVE       = 5,
566         VIEWROT_MODAL_SWITCH_ROTATE     = 6,
567 };
568
569 /* called in transform_ops.c, on each regeneration of keymaps  */
570 void viewrotate_modal_keymap(wmKeyConfig *keyconf)
571 {
572         static const EnumPropertyItem modal_items[] = {
573                 {VIEW_MODAL_CONFIRM,    "CONFIRM", 0, "Confirm", ""},
574
575                 {VIEWROT_MODAL_AXIS_SNAP_ENABLE,    "AXIS_SNAP_ENABLE", 0, "Axis Snap", ""},
576                 {VIEWROT_MODAL_AXIS_SNAP_DISABLE,   "AXIS_SNAP_DISABLE", 0, "Axis Snap (Off)", ""},
577
578                 {VIEWROT_MODAL_SWITCH_ZOOM, "SWITCH_TO_ZOOM", 0, "Switch to Zoom"},
579                 {VIEWROT_MODAL_SWITCH_MOVE, "SWITCH_TO_MOVE", 0, "Switch to Move"},
580
581                 {0, NULL, 0, NULL, NULL},
582         };
583
584         wmKeyMap *keymap = WM_modalkeymap_get(keyconf, "View3D Rotate Modal");
585
586         /* this function is called for each spacetype, only needs to add map once */
587         if (keymap && keymap->modal_items) {
588                 return;
589         }
590
591         keymap = WM_modalkeymap_add(keyconf, "View3D Rotate Modal", modal_items);
592
593         /* disabled mode switching for now, can re-implement better, later on */
594 #if 0
595         WM_modalkeymap_add_item(keymap, LEFTMOUSE, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ZOOM);
596         WM_modalkeymap_add_item(keymap, LEFTCTRLKEY, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ZOOM);
597         WM_modalkeymap_add_item(keymap, LEFTSHIFTKEY, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_MOVE);
598 #endif
599
600         /* assign map to operators */
601         WM_modalkeymap_assign(keymap, "VIEW3D_OT_rotate");
602 }
603
604 static void viewrotate_apply_dyn_ofs(ViewOpsData *vod, const float viewquat_new[4])
605 {
606         if (vod->use_dyn_ofs) {
607                 RegionView3D *rv3d = vod->rv3d;
608                 view3d_orbit_apply_dyn_ofs(rv3d->ofs, vod->init.ofs, vod->init.quat, viewquat_new, vod->dyn_ofs);
609         }
610 }
611
612 static void viewrotate_apply_snap(ViewOpsData *vod)
613 {
614         const float axis_limit = DEG2RADF(45 / 3);
615
616         RegionView3D *rv3d = vod->rv3d;
617
618         float viewquat_inv[4];
619         float zaxis[3] = {0, 0, 1};
620         float zaxis_best[3];
621         int x, y, z;
622         bool found = false;
623
624         invert_qt_qt_normalized(viewquat_inv, vod->curr.viewquat);
625
626         mul_qt_v3(viewquat_inv, zaxis);
627         normalize_v3(zaxis);
628
629
630         for (x = -1; x < 2; x++) {
631                 for (y = -1; y < 2; y++) {
632                         for (z = -1; z < 2; z++) {
633                                 if (x || y || z) {
634                                         float zaxis_test[3] = {x, y, z};
635
636                                         normalize_v3(zaxis_test);
637
638                                         if (angle_normalized_v3v3(zaxis_test, zaxis) < axis_limit) {
639                                                 copy_v3_v3(zaxis_best, zaxis_test);
640                                                 found = true;
641                                         }
642                                 }
643                         }
644                 }
645         }
646
647         if (found) {
648
649                 /* find the best roll */
650                 float quat_roll[4], quat_final[4], quat_best[4], quat_snap[4];
651                 float viewquat_align[4]; /* viewquat aligned to zaxis_best */
652                 float viewquat_align_inv[4]; /* viewquat aligned to zaxis_best */
653                 float best_angle = axis_limit;
654                 int j;
655
656                 /* viewquat_align is the original viewquat aligned to the snapped axis
657                  * for testing roll */
658                 rotation_between_vecs_to_quat(viewquat_align, zaxis_best, zaxis);
659                 normalize_qt(viewquat_align);
660                 mul_qt_qtqt(viewquat_align, vod->curr.viewquat, viewquat_align);
661                 normalize_qt(viewquat_align);
662                 invert_qt_qt_normalized(viewquat_align_inv, viewquat_align);
663
664                 vec_to_quat(quat_snap, zaxis_best, OB_NEGZ, OB_POSY);
665                 normalize_qt(quat_snap);
666                 invert_qt_normalized(quat_snap);
667
668                 /* check if we can find the roll */
669                 found = false;
670
671                 /* find best roll */
672                 for (j = 0; j < 8; j++) {
673                         float angle;
674                         float xaxis1[3] = {1, 0, 0};
675                         float xaxis2[3] = {1, 0, 0};
676                         float quat_final_inv[4];
677
678                         axis_angle_to_quat(quat_roll, zaxis_best, (float)j * DEG2RADF(45.0f));
679                         normalize_qt(quat_roll);
680
681                         mul_qt_qtqt(quat_final, quat_snap, quat_roll);
682                         normalize_qt(quat_final);
683
684                         /* compare 2 vector angles to find the least roll */
685                         invert_qt_qt_normalized(quat_final_inv, quat_final);
686                         mul_qt_v3(viewquat_align_inv, xaxis1);
687                         mul_qt_v3(quat_final_inv, xaxis2);
688                         angle = angle_v3v3(xaxis1, xaxis2);
689
690                         if (angle <= best_angle) {
691                                 found = true;
692                                 best_angle = angle;
693                                 copy_qt_qt(quat_best, quat_final);
694                         }
695                 }
696
697                 if (found) {
698                         /* lock 'quat_best' to an axis view if we can */
699                         rv3d->view = ED_view3d_quat_to_axis_view(quat_best, 0.01f);
700                         if (rv3d->view != RV3D_VIEW_USER) {
701                                 ED_view3d_quat_from_axis_view(rv3d->view, quat_best);
702                         }
703                 }
704                 else {
705                         copy_qt_qt(quat_best, viewquat_align);
706                 }
707
708                 copy_qt_qt(rv3d->viewquat, quat_best);
709
710                 viewrotate_apply_dyn_ofs(vod, rv3d->viewquat);
711         }
712 }
713
714 static void viewrotate_apply(ViewOpsData *vod, const int event_xy[2])
715 {
716         RegionView3D *rv3d = vod->rv3d;
717
718         rv3d->view = RV3D_VIEW_USER; /* need to reset every time because of view snapping */
719
720         if (U.flag & USER_TRACKBALL) {
721                 float axis[3], q1[4], dvec[3], newvec[3];
722                 float angle;
723
724                 {
725                         const int event_xy_offset[2] = {
726                                 event_xy[0] + vod->init.event_xy_offset[0],
727                                 event_xy[1] + vod->init.event_xy_offset[1],
728                         };
729                         calctrackballvec(&vod->ar->winrct, event_xy_offset, newvec);
730                 }
731
732                 sub_v3_v3v3(dvec, newvec, vod->init.trackvec);
733
734                 angle = (len_v3(dvec) / (2.0f * TRACKBALLSIZE)) * (float)M_PI;
735
736                 /* Allow for rotation beyond the interval [-pi, pi] */
737                 angle = angle_wrap_rad(angle);
738
739                 /* This relation is used instead of the actual angle between vectors
740                  * so that the angle of rotation is linearly proportional to
741                  * the distance that the mouse is dragged. */
742
743                 cross_v3_v3v3(axis, vod->init.trackvec, newvec);
744                 axis_angle_to_quat(q1, axis, angle);
745
746                 mul_qt_qtqt(vod->curr.viewquat, q1, vod->init.quat);
747
748                 viewrotate_apply_dyn_ofs(vod, vod->curr.viewquat);
749         }
750         else {
751                 /* New turntable view code by John Aughey */
752                 float quat_local_x[4], quat_global_z[4];
753                 float m[3][3];
754                 float m_inv[3][3];
755                 const float zvec_global[3] = {0.0f, 0.0f, 1.0f};
756                 float xaxis[3];
757
758                 /* Sensitivity will control how fast the viewport rotates.  0.007 was
759                  * obtained experimentally by looking at viewport rotation sensitivities
760                  * on other modeling programs. */
761                 /* Perhaps this should be a configurable user parameter. */
762                 const float sensitivity = 0.007f;
763
764                 /* Get the 3x3 matrix and its inverse from the quaternion */
765                 quat_to_mat3(m, vod->curr.viewquat);
766                 invert_m3_m3(m_inv, m);
767
768                 /* avoid gimble lock */
769 #if 1
770                 if (len_squared_v3v3(zvec_global, m_inv[2]) > 0.001f) {
771                         float fac;
772                         cross_v3_v3v3(xaxis, zvec_global, m_inv[2]);
773                         if (dot_v3v3(xaxis, m_inv[0]) < 0) {
774                                 negate_v3(xaxis);
775                         }
776                         fac = angle_normalized_v3v3(zvec_global, m_inv[2]) / (float)M_PI;
777                         fac = fabsf(fac - 0.5f) * 2;
778                         fac = fac * fac;
779                         interp_v3_v3v3(xaxis, xaxis, m_inv[0], fac);
780                 }
781                 else {
782                         copy_v3_v3(xaxis, m_inv[0]);
783                 }
784 #else
785                 copy_v3_v3(xaxis, m_inv[0]);
786 #endif
787
788                 /* Determine the direction of the x vector (for rotating up and down) */
789                 /* This can likely be computed directly from the quaternion. */
790
791                 /* Perform the up/down rotation */
792                 axis_angle_to_quat(quat_local_x, xaxis, sensitivity * -(event_xy[1] - vod->prev.event_xy[1]));
793                 mul_qt_qtqt(quat_local_x, vod->curr.viewquat, quat_local_x);
794
795                 /* Perform the orbital rotation */
796                 axis_angle_to_quat_single(quat_global_z, 'Z', sensitivity * vod->reverse * (event_xy[0] - vod->prev.event_xy[0]));
797                 mul_qt_qtqt(vod->curr.viewquat, quat_local_x, quat_global_z);
798
799                 viewrotate_apply_dyn_ofs(vod, vod->curr.viewquat);
800         }
801
802         /* avoid precision loss over time */
803         normalize_qt(vod->curr.viewquat);
804
805         /* use a working copy so view rotation locking doesn't overwrite the locked
806          * rotation back into the view we calculate with */
807         copy_qt_qt(rv3d->viewquat, vod->curr.viewquat);
808
809         /* check for view snap,
810          * note: don't apply snap to vod->viewquat so the view wont jam up */
811         if (vod->axis_snap) {
812                 viewrotate_apply_snap(vod);
813         }
814         vod->prev.event_xy[0] = event_xy[0];
815         vod->prev.event_xy[1] = event_xy[1];
816
817         ED_view3d_camera_lock_sync(vod->depsgraph, vod->v3d, rv3d);
818
819         ED_region_tag_redraw(vod->ar);
820 }
821
822 static int viewrotate_modal(bContext *C, wmOperator *op, const wmEvent *event)
823 {
824         ViewOpsData *vod = op->customdata;
825         short event_code = VIEW_PASS;
826         bool use_autokey = false;
827         int ret = OPERATOR_RUNNING_MODAL;
828
829         /* execute the events */
830         if (event->type == MOUSEMOVE) {
831                 event_code = VIEW_APPLY;
832         }
833         else if (event->type == EVT_MODAL_MAP) {
834                 switch (event->val) {
835                         case VIEW_MODAL_CONFIRM:
836                                 event_code = VIEW_CONFIRM;
837                                 break;
838                         case VIEWROT_MODAL_AXIS_SNAP_ENABLE:
839                                 vod->axis_snap = true;
840                                 event_code = VIEW_APPLY;
841                                 break;
842                         case VIEWROT_MODAL_AXIS_SNAP_DISABLE:
843                                 vod->axis_snap = false;
844                                 event_code = VIEW_APPLY;
845                                 break;
846                         case VIEWROT_MODAL_SWITCH_ZOOM:
847                                 WM_operator_name_call(C, "VIEW3D_OT_zoom", WM_OP_INVOKE_DEFAULT, NULL);
848                                 event_code = VIEW_CONFIRM;
849                                 break;
850                         case VIEWROT_MODAL_SWITCH_MOVE:
851                                 WM_operator_name_call(C, "VIEW3D_OT_move", WM_OP_INVOKE_DEFAULT, NULL);
852                                 event_code = VIEW_CONFIRM;
853                                 break;
854                 }
855         }
856         else if (event->type == vod->init.event_type && event->val == KM_RELEASE) {
857                 event_code = VIEW_CONFIRM;
858         }
859
860         if (event_code == VIEW_APPLY) {
861                 viewrotate_apply(vod, &event->x);
862                 if (ED_screen_animation_playing(CTX_wm_manager(C))) {
863                         use_autokey = true;
864                 }
865         }
866         else if (event_code == VIEW_CONFIRM) {
867                 ED_view3d_depth_tag_update(vod->rv3d);
868                 use_autokey = true;
869                 ret = OPERATOR_FINISHED;
870         }
871
872         if (use_autokey) {
873                 ED_view3d_camera_lock_autokey(vod->v3d, vod->rv3d, C, true, true);
874         }
875
876         if (ret & OPERATOR_FINISHED) {
877                 viewops_data_free(C, op);
878         }
879
880         return ret;
881 }
882
883 static int viewrotate_invoke(bContext *C, wmOperator *op, const wmEvent *event)
884 {
885         ViewOpsData *vod;
886
887         const bool use_mouse_init = RNA_boolean_get(op->ptr, "use_mouse_init");
888
889         /* makes op->customdata */
890         viewops_data_alloc(C, op);
891         vod = op->customdata;
892
893         /* poll should check but in some cases fails, see poll func for details */
894         if (vod->rv3d->viewlock & RV3D_LOCKED) {
895                 viewops_data_free(C, op);
896                 return OPERATOR_PASS_THROUGH;
897         }
898
899         ED_view3d_smooth_view_force_finish(C, vod->v3d, vod->ar);
900
901         viewops_data_create(
902                 C, op, event,
903                 viewops_flag_from_prefs() |
904                 VIEWOPS_FLAG_PERSP_ENSURE |
905                 (use_mouse_init ? VIEWOPS_FLAG_USE_MOUSE_INIT : 0));
906
907         if (ELEM(event->type, MOUSEPAN, MOUSEROTATE)) {
908                 /* Rotate direction we keep always same */
909                 int event_xy[2];
910
911                 if (event->type == MOUSEPAN) {
912                         if (U.uiflag2 & USER_TRACKPAD_NATURAL) {
913                                 event_xy[0] = 2 * event->x - event->prevx;
914                                 event_xy[1] = 2 * event->y - event->prevy;
915                         }
916                         else {
917                                 event_xy[0] = event->prevx;
918                                 event_xy[1] = event->prevy;
919                         }
920                 }
921                 else {
922                         /* MOUSEROTATE performs orbital rotation, so y axis delta is set to 0 */
923                         event_xy[0] = event->prevx;
924                         event_xy[1] = event->y;
925                 }
926
927                 viewrotate_apply(vod, event_xy);
928                 ED_view3d_depth_tag_update(vod->rv3d);
929
930                 viewops_data_free(C, op);
931
932                 return OPERATOR_FINISHED;
933         }
934         else {
935                 /* add temp handler */
936                 WM_event_add_modal_handler(C, op);
937
938                 return OPERATOR_RUNNING_MODAL;
939         }
940 }
941
942 /* test for unlocked camera view in quad view */
943 static bool view3d_camera_user_poll(bContext *C)
944 {
945         View3D *v3d;
946         ARegion *ar;
947
948         if (ED_view3d_context_user_region(C, &v3d, &ar)) {
949                 RegionView3D *rv3d = ar->regiondata;
950                 if (rv3d->persp == RV3D_CAMOB) {
951                         return 1;
952                 }
953         }
954
955         return 0;
956 }
957
958 static bool view3d_lock_poll(bContext *C)
959 {
960         View3D *v3d = CTX_wm_view3d(C);
961         if (v3d) {
962                 RegionView3D *rv3d = CTX_wm_region_view3d(C);
963                 if (rv3d) {
964                         return ED_view3d_offset_lock_check(v3d, rv3d);
965                 }
966         }
967         return false;
968 }
969
970 static void viewrotate_cancel(bContext *C, wmOperator *op)
971 {
972         viewops_data_free(C, op);
973 }
974
975 void VIEW3D_OT_rotate(wmOperatorType *ot)
976 {
977         /* identifiers */
978         ot->name = "Rotate View";
979         ot->description = "Rotate the view";
980         ot->idname = "VIEW3D_OT_rotate";
981
982         /* api callbacks */
983         ot->invoke = viewrotate_invoke;
984         ot->modal = viewrotate_modal;
985         ot->poll = ED_operator_region_view3d_active;
986         ot->cancel = viewrotate_cancel;
987
988         /* flags */
989         ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR;
990
991         view3d_operator_properties_common(ot, V3D_OP_PROP_USE_MOUSE_INIT);
992 }
993
994 /** \} */
995
996 /* -------------------------------------------------------------------- */
997 /** \name NDOF Utility Functions
998  * \{ */
999
1000 #ifdef WITH_INPUT_NDOF
1001 #define NDOF_HAS_TRANSLATE ((!ED_view3d_offset_lock_check(v3d, rv3d)) && !is_zero_v3(ndof->tvec))
1002 #define NDOF_HAS_ROTATE    (((rv3d->viewlock & RV3D_LOCKED) == 0) && !is_zero_v3(ndof->rvec))
1003
1004 /**
1005  * \param depth_pt: A point to calculate the depth (in perspective mode)
1006  */
1007 static float view3d_ndof_pan_speed_calc_ex(RegionView3D *rv3d, const float depth_pt[3])
1008 {
1009         float speed = rv3d->pixsize * NDOF_PIXELS_PER_SECOND;
1010
1011         if (rv3d->is_persp) {
1012                 speed *= ED_view3d_calc_zfac(rv3d, depth_pt, NULL);
1013         }
1014
1015         return speed;
1016 }
1017
1018 static float view3d_ndof_pan_speed_calc_from_dist(RegionView3D *rv3d, const float dist)
1019 {
1020         float viewinv[4];
1021         float tvec[3];
1022
1023         BLI_assert(dist >= 0.0f);
1024
1025         copy_v3_fl3(tvec, 0.0f, 0.0f, dist);
1026         /* rv3d->viewinv isn't always valid */
1027 #if 0
1028         mul_mat3_m4_v3(rv3d->viewinv, tvec);
1029 #else
1030         invert_qt_qt_normalized(viewinv, rv3d->viewquat);
1031         mul_qt_v3(viewinv, tvec);
1032 #endif
1033
1034         return view3d_ndof_pan_speed_calc_ex(rv3d, tvec);
1035 }
1036
1037 static float view3d_ndof_pan_speed_calc(RegionView3D *rv3d)
1038 {
1039         float tvec[3];
1040         negate_v3_v3(tvec, rv3d->ofs);
1041
1042         return view3d_ndof_pan_speed_calc_ex(rv3d, tvec);
1043 }
1044
1045 /**
1046  * Zoom and pan in the same function since sometimes zoom is interpreted as dolly (pan forward).
1047  *
1048  * \param has_zoom: zoom, otherwise dolly, often `!rv3d->is_persp` since it doesn't make sense to dolly in ortho.
1049  */
1050 static void view3d_ndof_pan_zoom(
1051         const struct wmNDOFMotionData *ndof, ScrArea *sa, ARegion *ar,
1052         const bool has_translate, const bool has_zoom)
1053 {
1054         RegionView3D *rv3d = ar->regiondata;
1055         float view_inv[4];
1056         float pan_vec[3];
1057
1058         if (has_translate == false && has_zoom == false) {
1059                 return;
1060         }
1061
1062         WM_event_ndof_pan_get(ndof, pan_vec, false);
1063
1064         if (has_zoom) {
1065                 /* zoom with Z */
1066
1067                 /* Zoom!
1068                  * velocity should be proportional to the linear velocity attained by rotational motion of same strength
1069                  * [got that?]
1070                  * proportional to arclength = radius * angle
1071                  */
1072
1073                 pan_vec[2] = 0.0f;
1074
1075                 /* "zoom in" or "translate"? depends on zoom mode in user settings? */
1076                 if (ndof->tvec[2]) {
1077                         float zoom_distance = rv3d->dist * ndof->dt * ndof->tvec[2];
1078
1079                         if (U.ndof_flag & NDOF_ZOOM_INVERT) {
1080                                 zoom_distance = -zoom_distance;
1081                         }
1082
1083                         rv3d->dist += zoom_distance;
1084                 }
1085         }
1086         else {
1087                 /* dolly with Z */
1088
1089                 /* all callers must check */
1090                 if (has_translate) {
1091                         BLI_assert(ED_view3d_offset_lock_check((View3D *)sa->spacedata.first, rv3d) == false);
1092                 }
1093         }
1094
1095         if (has_translate) {
1096                 const float speed = view3d_ndof_pan_speed_calc(rv3d);
1097
1098                 mul_v3_fl(pan_vec, speed * ndof->dt);
1099
1100                 /* transform motion from view to world coordinates */
1101                 invert_qt_qt_normalized(view_inv, rv3d->viewquat);
1102                 mul_qt_v3(view_inv, pan_vec);
1103
1104                 /* move center of view opposite of hand motion (this is camera mode, not object mode) */
1105                 sub_v3_v3(rv3d->ofs, pan_vec);
1106
1107                 if (rv3d->viewlock & RV3D_BOXVIEW) {
1108                         view3d_boxview_sync(sa, ar);
1109                 }
1110         }
1111 }
1112
1113
1114 static void view3d_ndof_orbit(
1115         const struct wmNDOFMotionData *ndof, ScrArea *sa, ARegion *ar,
1116         ViewOpsData *vod, const bool apply_dyn_ofs)
1117 {
1118         View3D *v3d = sa->spacedata.first;
1119         RegionView3D *rv3d = ar->regiondata;
1120
1121         float view_inv[4];
1122
1123         BLI_assert((rv3d->viewlock & RV3D_LOCKED) == 0);
1124
1125         ED_view3d_persp_ensure(vod->depsgraph, v3d, ar);
1126
1127         rv3d->view = RV3D_VIEW_USER;
1128
1129         invert_qt_qt_normalized(view_inv, rv3d->viewquat);
1130
1131         if (U.ndof_flag & NDOF_TURNTABLE) {
1132                 float rot[3];
1133
1134                 /* turntable view code by John Aughey, adapted for 3D mouse by [mce] */
1135                 float angle, quat[4];
1136                 float xvec[3] = {1, 0, 0};
1137
1138                 /* only use XY, ignore Z */
1139                 WM_event_ndof_rotate_get(ndof, rot);
1140
1141                 /* Determine the direction of the x vector (for rotating up and down) */
1142                 mul_qt_v3(view_inv, xvec);
1143
1144                 /* Perform the up/down rotation */
1145                 angle = ndof->dt * rot[0];
1146                 axis_angle_to_quat(quat, xvec, angle);
1147                 mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, quat);
1148
1149                 /* Perform the orbital rotation */
1150                 angle = ndof->dt * rot[1];
1151
1152                 /* update the onscreen doo-dad */
1153                 rv3d->rot_angle = angle;
1154                 rv3d->rot_axis[0] = 0;
1155                 rv3d->rot_axis[1] = 0;
1156                 rv3d->rot_axis[2] = 1;
1157
1158                 axis_angle_to_quat_single(quat, 'Z', angle);
1159                 mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, quat);
1160         }
1161         else {
1162                 float quat[4];
1163                 float axis[3];
1164                 float angle = WM_event_ndof_to_axis_angle(ndof, axis);
1165
1166                 /* transform rotation axis from view to world coordinates */
1167                 mul_qt_v3(view_inv, axis);
1168
1169                 /* update the onscreen doo-dad */
1170                 rv3d->rot_angle = angle;
1171                 copy_v3_v3(rv3d->rot_axis, axis);
1172
1173                 axis_angle_to_quat(quat, axis, angle);
1174
1175                 /* apply rotation */
1176                 mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, quat);
1177         }
1178
1179         if (apply_dyn_ofs) {
1180                 viewrotate_apply_dyn_ofs(vod, rv3d->viewquat);
1181         }
1182 }
1183
1184 /**
1185  * Called from both fly mode and walk mode,
1186  */
1187 void view3d_ndof_fly(
1188         const wmNDOFMotionData *ndof,
1189         View3D *v3d, RegionView3D *rv3d,
1190         const bool use_precision, const short protectflag,
1191         bool *r_has_translate, bool *r_has_rotate)
1192 {
1193         bool has_translate = NDOF_HAS_TRANSLATE;
1194         bool has_rotate = NDOF_HAS_ROTATE;
1195
1196         float view_inv[4];
1197         invert_qt_qt_normalized(view_inv, rv3d->viewquat);
1198
1199         rv3d->rot_angle = 0.0f;  /* disable onscreen rotation doo-dad */
1200
1201         if (has_translate) {
1202                 /* ignore real 'dist' since fly has its own speed settings,
1203                  * also its overwritten at this point. */
1204                 float speed = view3d_ndof_pan_speed_calc_from_dist(rv3d, 1.0f);
1205                 float trans[3], trans_orig_y;
1206
1207                 if (use_precision) {
1208                         speed *= 0.2f;
1209                 }
1210
1211                 WM_event_ndof_pan_get(ndof, trans, false);
1212                 mul_v3_fl(trans, speed * ndof->dt);
1213                 trans_orig_y = trans[1];
1214
1215                 if (U.ndof_flag & NDOF_FLY_HELICOPTER) {
1216                         trans[1] = 0.0f;
1217                 }
1218
1219                 /* transform motion from view to world coordinates */
1220                 mul_qt_v3(view_inv, trans);
1221
1222                 if (U.ndof_flag & NDOF_FLY_HELICOPTER) {
1223                         /* replace world z component with device y (yes it makes sense) */
1224                         trans[2] = trans_orig_y;
1225                 }
1226
1227                 if (rv3d->persp == RV3D_CAMOB) {
1228                         /* respect camera position locks */
1229                         if (protectflag & OB_LOCK_LOCX) {
1230                                 trans[0] = 0.0f;
1231                         }
1232                         if (protectflag & OB_LOCK_LOCY) {
1233                                 trans[1] = 0.0f;
1234                         }
1235                         if (protectflag & OB_LOCK_LOCZ) {
1236                                 trans[2] = 0.0f;
1237                         }
1238                 }
1239
1240                 if (!is_zero_v3(trans)) {
1241                         /* move center of view opposite of hand motion
1242                          * (this is camera mode, not object mode) */
1243                         sub_v3_v3(rv3d->ofs, trans);
1244                         has_translate = true;
1245                 }
1246                 else {
1247                         has_translate = false;
1248                 }
1249         }
1250
1251         if (has_rotate) {
1252                 const float turn_sensitivity = 1.0f;
1253
1254                 float rotation[4];
1255                 float axis[3];
1256                 float angle = turn_sensitivity * WM_event_ndof_to_axis_angle(ndof, axis);
1257
1258                 if (fabsf(angle) > 0.0001f) {
1259                         has_rotate = true;
1260
1261                         if (use_precision) {
1262                                 angle *= 0.2f;
1263                         }
1264
1265                         /* transform rotation axis from view to world coordinates */
1266                         mul_qt_v3(view_inv, axis);
1267
1268                         /* apply rotation to view */
1269                         axis_angle_to_quat(rotation, axis, angle);
1270                         mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, rotation);
1271
1272                         if (U.ndof_flag & NDOF_LOCK_HORIZON) {
1273                                 /* force an upright viewpoint
1274                                  * TODO: make this less... sudden */
1275                                 float view_horizon[3] = {1.0f, 0.0f, 0.0f}; /* view +x */
1276                                 float view_direction[3] = {0.0f, 0.0f, -1.0f}; /* view -z (into screen) */
1277
1278                                 /* find new inverse since viewquat has changed */
1279                                 invert_qt_qt_normalized(view_inv, rv3d->viewquat);
1280                                 /* could apply reverse rotation to existing view_inv to save a few cycles */
1281
1282                                 /* transform view vectors to world coordinates */
1283                                 mul_qt_v3(view_inv, view_horizon);
1284                                 mul_qt_v3(view_inv, view_direction);
1285
1286
1287                                 /* find difference between view & world horizons
1288                                  * true horizon lives in world xy plane, so look only at difference in z */
1289                                 angle = -asinf(view_horizon[2]);
1290
1291                                 /* rotate view so view horizon = world horizon */
1292                                 axis_angle_to_quat(rotation, view_direction, angle);
1293                                 mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, rotation);
1294                         }
1295
1296                         rv3d->view = RV3D_VIEW_USER;
1297                 }
1298                 else {
1299                         has_rotate = false;
1300                 }
1301         }
1302
1303         *r_has_translate = has_translate;
1304         *r_has_rotate    = has_rotate;
1305 }
1306
1307 /** \} */
1308
1309 /* -------------------------------------------------------------------- */
1310 /** \name NDOF Orbit/Translate Operator
1311  * \{ */
1312
1313 static int ndof_orbit_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1314 {
1315         if (event->type != NDOF_MOTION) {
1316                 return OPERATOR_CANCELLED;
1317         }
1318
1319         const Depsgraph *depsgraph = CTX_data_depsgraph(C);
1320         ViewOpsData *vod;
1321         View3D *v3d;
1322         RegionView3D *rv3d;
1323         char xform_flag = 0;
1324
1325         const wmNDOFMotionData *ndof = event->customdata;
1326
1327         viewops_data_alloc(C, op);
1328         viewops_data_create(
1329                 C, op, event,
1330                 viewops_flag_from_args((U.uiflag & USER_ORBIT_SELECTION) != 0, false));
1331         vod = op->customdata;
1332
1333         ED_view3d_smooth_view_force_finish(C, vod->v3d, vod->ar);
1334
1335         v3d = vod->v3d;
1336         rv3d = vod->rv3d;
1337
1338         /* off by default, until changed later this function */
1339         rv3d->rot_angle = 0.0f;
1340
1341         ED_view3d_camera_lock_init_ex(depsgraph, v3d, rv3d, false);
1342
1343         if (ndof->progress != P_FINISHING) {
1344                 const bool has_rotation = NDOF_HAS_ROTATE;
1345                 /* if we can't rotate, fallback to translate (locked axis views) */
1346                 const bool has_translate = NDOF_HAS_TRANSLATE && (rv3d->viewlock & RV3D_LOCKED);
1347                 const bool has_zoom = (ndof->tvec[2] != 0.0f) && !rv3d->is_persp;
1348
1349                 if (has_translate || has_zoom) {
1350                         view3d_ndof_pan_zoom(ndof, vod->sa, vod->ar, has_translate, has_zoom);
1351                         xform_flag |= HAS_TRANSLATE;
1352                 }
1353
1354                 if (has_rotation) {
1355                         view3d_ndof_orbit(ndof, vod->sa, vod->ar, vod, true);
1356                         xform_flag |= HAS_ROTATE;
1357                 }
1358         }
1359
1360         ED_view3d_camera_lock_sync(depsgraph, v3d, rv3d);
1361         if (xform_flag) {
1362                 ED_view3d_camera_lock_autokey(v3d, rv3d, C, xform_flag & HAS_ROTATE, xform_flag & HAS_TRANSLATE);
1363         }
1364
1365         ED_region_tag_redraw(vod->ar);
1366
1367         viewops_data_free(C, op);
1368
1369         return OPERATOR_FINISHED;
1370 }
1371
1372 void VIEW3D_OT_ndof_orbit(struct wmOperatorType *ot)
1373 {
1374         /* identifiers */
1375         ot->name = "NDOF Orbit View";
1376         ot->description = "Orbit the view using the 3D mouse";
1377         ot->idname = "VIEW3D_OT_ndof_orbit";
1378
1379         /* api callbacks */
1380         ot->invoke = ndof_orbit_invoke;
1381         ot->poll = ED_operator_view3d_active;
1382
1383         /* flags */
1384         ot->flag = 0;
1385 }
1386
1387 /** \} */
1388
1389 /* -------------------------------------------------------------------- */
1390 /** \name NDOF Orbit/Zoom Operator
1391  * \{ */
1392
1393 static int ndof_orbit_zoom_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1394 {
1395         if (event->type != NDOF_MOTION) {
1396                 return OPERATOR_CANCELLED;
1397         }
1398
1399         const Depsgraph *depsgraph = CTX_data_depsgraph(C);
1400         ViewOpsData *vod;
1401         View3D *v3d;
1402         RegionView3D *rv3d;
1403         char xform_flag = 0;
1404
1405         const wmNDOFMotionData *ndof = event->customdata;
1406
1407         viewops_data_alloc(C, op);
1408         viewops_data_create(
1409                 C, op, event,
1410                 viewops_flag_from_args((U.uiflag & USER_ORBIT_SELECTION) != 0, false));
1411
1412         vod = op->customdata;
1413
1414         ED_view3d_smooth_view_force_finish(C, vod->v3d, vod->ar);
1415
1416         v3d = vod->v3d;
1417         rv3d = vod->rv3d;
1418
1419         /* off by default, until changed later this function */
1420         rv3d->rot_angle = 0.0f;
1421
1422         ED_view3d_camera_lock_init_ex(depsgraph, v3d, rv3d, false);
1423
1424         if (ndof->progress == P_FINISHING) {
1425                 /* pass */
1426         }
1427         else if ((rv3d->persp == RV3D_ORTHO) && RV3D_VIEW_IS_AXIS(rv3d->view)) {
1428                 /* if we can't rotate, fallback to translate (locked axis views) */
1429                 const bool has_translate = NDOF_HAS_TRANSLATE;
1430                 const bool has_zoom = (ndof->tvec[2] != 0.0f) && ED_view3d_offset_lock_check(v3d, rv3d);
1431
1432                 if (has_translate || has_zoom) {
1433                         view3d_ndof_pan_zoom(ndof, vod->sa, vod->ar, has_translate, true);
1434                         xform_flag |= HAS_TRANSLATE;
1435                 }
1436         }
1437         else if ((U.ndof_flag & NDOF_MODE_ORBIT) ||
1438                  ED_view3d_offset_lock_check(v3d, rv3d))
1439         {
1440                 const bool has_rotation = NDOF_HAS_ROTATE;
1441                 const bool has_zoom = (ndof->tvec[2] != 0.0f);
1442
1443                 if (has_zoom) {
1444                         view3d_ndof_pan_zoom(ndof, vod->sa, vod->ar, false, has_zoom);
1445                         xform_flag |= HAS_TRANSLATE;
1446                 }
1447
1448                 if (has_rotation) {
1449                         view3d_ndof_orbit(ndof, vod->sa, vod->ar, vod, true);
1450                         xform_flag |= HAS_ROTATE;
1451                 }
1452         }
1453         else {  /* free/explore (like fly mode) */
1454                 const bool has_rotation = NDOF_HAS_ROTATE;
1455                 const bool has_translate = NDOF_HAS_TRANSLATE;
1456                 const bool has_zoom = (ndof->tvec[2] != 0.0f) && !rv3d->is_persp;
1457
1458                 float dist_backup;
1459
1460                 if (has_translate || has_zoom) {
1461                         view3d_ndof_pan_zoom(ndof, vod->sa, vod->ar, has_translate, has_zoom);
1462                         xform_flag |= HAS_TRANSLATE;
1463                 }
1464
1465                 dist_backup = rv3d->dist;
1466                 ED_view3d_distance_set(rv3d, 0.0f);
1467
1468                 if (has_rotation) {
1469                         view3d_ndof_orbit(ndof, vod->sa, vod->ar, vod, false);
1470                         xform_flag |= HAS_ROTATE;
1471                 }
1472
1473                 ED_view3d_distance_set(rv3d, dist_backup);
1474         }
1475
1476         ED_view3d_camera_lock_sync(depsgraph, v3d, rv3d);
1477         if (xform_flag) {
1478                 ED_view3d_camera_lock_autokey(v3d, rv3d, C, xform_flag & HAS_ROTATE, xform_flag & HAS_TRANSLATE);
1479         }
1480
1481         ED_region_tag_redraw(vod->ar);
1482
1483         viewops_data_free(C, op);
1484
1485         return OPERATOR_FINISHED;
1486 }
1487
1488 void VIEW3D_OT_ndof_orbit_zoom(struct wmOperatorType *ot)
1489 {
1490         /* identifiers */
1491         ot->name = "NDOF Orbit View with Zoom";
1492         ot->description = "Orbit and zoom the view using the 3D mouse";
1493         ot->idname = "VIEW3D_OT_ndof_orbit_zoom";
1494
1495         /* api callbacks */
1496         ot->invoke = ndof_orbit_zoom_invoke;
1497         ot->poll = ED_operator_view3d_active;
1498
1499         /* flags */
1500         ot->flag = 0;
1501 }
1502
1503 /** \} */
1504
1505 /* -------------------------------------------------------------------- */
1506 /** \name NDOF Pan/Zoom Operator
1507  * \{ */
1508
1509 static int ndof_pan_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
1510 {
1511         if (event->type != NDOF_MOTION) {
1512                 return OPERATOR_CANCELLED;
1513         }
1514
1515         const Depsgraph *depsgraph = CTX_data_depsgraph(C);
1516         View3D *v3d = CTX_wm_view3d(C);
1517         RegionView3D *rv3d = CTX_wm_region_view3d(C);
1518         const wmNDOFMotionData *ndof = event->customdata;
1519         char xform_flag = 0;
1520
1521         const bool has_translate = NDOF_HAS_TRANSLATE;
1522         const bool has_zoom = (ndof->tvec[2] != 0.0f) && !rv3d->is_persp;
1523
1524         /* we're panning here! so erase any leftover rotation from other operators */
1525         rv3d->rot_angle = 0.0f;
1526
1527         if (!(has_translate || has_zoom)) {
1528                 return OPERATOR_CANCELLED;
1529         }
1530
1531         ED_view3d_camera_lock_init_ex(depsgraph, v3d, rv3d, false);
1532
1533         if (ndof->progress != P_FINISHING) {
1534                 ScrArea *sa = CTX_wm_area(C);
1535                 ARegion *ar = CTX_wm_region(C);
1536
1537                 if (has_translate || has_zoom) {
1538                         view3d_ndof_pan_zoom(ndof, sa, ar, has_translate, has_zoom);
1539                         xform_flag |= HAS_TRANSLATE;
1540                 }
1541         }
1542
1543         ED_view3d_camera_lock_sync(depsgraph, v3d, rv3d);
1544         if (xform_flag) {
1545                 ED_view3d_camera_lock_autokey(v3d, rv3d, C, false, xform_flag & HAS_TRANSLATE);
1546         }
1547
1548         ED_region_tag_redraw(CTX_wm_region(C));
1549
1550         return OPERATOR_FINISHED;
1551 }
1552
1553 void VIEW3D_OT_ndof_pan(struct wmOperatorType *ot)
1554 {
1555         /* identifiers */
1556         ot->name = "NDOF Pan View";
1557         ot->description = "Pan the view with the 3D mouse";
1558         ot->idname = "VIEW3D_OT_ndof_pan";
1559
1560         /* api callbacks */
1561         ot->invoke = ndof_pan_invoke;
1562         ot->poll = ED_operator_view3d_active;
1563
1564         /* flags */
1565         ot->flag = 0;
1566 }
1567
1568 /** \} */
1569
1570 /* -------------------------------------------------------------------- */
1571 /** \name NDOF Transform All Operator
1572  * \{ */
1573
1574 /**
1575  * wraps #ndof_orbit_zoom but never restrict to orbit.
1576  */
1577 static int ndof_all_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1578 {
1579         /* weak!, but it works */
1580         const int ndof_flag = U.ndof_flag;
1581         int ret;
1582
1583         U.ndof_flag &= ~NDOF_MODE_ORBIT;
1584
1585         ret = ndof_orbit_zoom_invoke(C, op, event);
1586
1587         U.ndof_flag = ndof_flag;
1588
1589         return ret;
1590 }
1591
1592 void VIEW3D_OT_ndof_all(struct wmOperatorType *ot)
1593 {
1594         /* identifiers */
1595         ot->name = "NDOF Transform View";
1596         ot->description = "Pan and rotate the view with the 3D mouse";
1597         ot->idname = "VIEW3D_OT_ndof_all";
1598
1599         /* api callbacks */
1600         ot->invoke = ndof_all_invoke;
1601         ot->poll = ED_operator_view3d_active;
1602
1603         /* flags */
1604         ot->flag = 0;
1605 }
1606
1607 #endif /* WITH_INPUT_NDOF */
1608
1609 /** \} */
1610
1611 /* -------------------------------------------------------------------- */
1612 /** \name View Move (Pan) Operator
1613  * \{ */
1614
1615 /* NOTE: these defines are saved in keymap files, do not change values but just add new ones */
1616
1617 /* called in transform_ops.c, on each regeneration of keymaps  */
1618 void viewmove_modal_keymap(wmKeyConfig *keyconf)
1619 {
1620         static const EnumPropertyItem modal_items[] = {
1621                 {VIEW_MODAL_CONFIRM,    "CONFIRM", 0, "Confirm", ""},
1622
1623                 {VIEWROT_MODAL_SWITCH_ZOOM, "SWITCH_TO_ZOOM", 0, "Switch to Zoom"},
1624                 {VIEWROT_MODAL_SWITCH_ROTATE, "SWITCH_TO_ROTATE", 0, "Switch to Rotate"},
1625
1626                 {0, NULL, 0, NULL, NULL},
1627         };
1628
1629         wmKeyMap *keymap = WM_modalkeymap_get(keyconf, "View3D Move Modal");
1630
1631         /* this function is called for each spacetype, only needs to add map once */
1632         if (keymap && keymap->modal_items) {
1633                 return;
1634         }
1635
1636         keymap = WM_modalkeymap_add(keyconf, "View3D Move Modal", modal_items);
1637
1638         /* items for modal map */
1639         WM_modalkeymap_add_item(keymap, MIDDLEMOUSE, KM_RELEASE, KM_ANY, 0, VIEW_MODAL_CONFIRM);
1640         WM_modalkeymap_add_item(keymap, ESCKEY, KM_PRESS, KM_ANY, 0, VIEW_MODAL_CONFIRM);
1641
1642         /* disabled mode switching for now, can re-implement better, later on */
1643 #if 0
1644         WM_modalkeymap_add_item(keymap, LEFTMOUSE, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ZOOM);
1645         WM_modalkeymap_add_item(keymap, LEFTCTRLKEY, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ZOOM);
1646         WM_modalkeymap_add_item(keymap, LEFTSHIFTKEY, KM_RELEASE, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ROTATE);
1647 #endif
1648
1649         /* assign map to operators */
1650         WM_modalkeymap_assign(keymap, "VIEW3D_OT_move");
1651 }
1652
1653
1654 static void viewmove_apply(ViewOpsData *vod, int x, int y)
1655 {
1656         if (ED_view3d_offset_lock_check(vod->v3d, vod->rv3d)) {
1657                 vod->rv3d->ofs_lock[0] -= ((vod->prev.event_xy[0] - x) * 2.0f) / (float)vod->ar->winx;
1658                 vod->rv3d->ofs_lock[1] -= ((vod->prev.event_xy[1] - y) * 2.0f) / (float)vod->ar->winy;
1659         }
1660         else if ((vod->rv3d->persp == RV3D_CAMOB) && !ED_view3d_camera_lock_check(vod->v3d, vod->rv3d)) {
1661                 const float zoomfac = BKE_screen_view3d_zoom_to_fac(vod->rv3d->camzoom) * 2.0f;
1662                 vod->rv3d->camdx += (vod->prev.event_xy[0] - x) / (vod->ar->winx * zoomfac);
1663                 vod->rv3d->camdy += (vod->prev.event_xy[1] - y) / (vod->ar->winy * zoomfac);
1664                 CLAMP(vod->rv3d->camdx, -1.0f, 1.0f);
1665                 CLAMP(vod->rv3d->camdy, -1.0f, 1.0f);
1666         }
1667         else {
1668                 float dvec[3];
1669                 float mval_f[2];
1670
1671                 mval_f[0] = x - vod->prev.event_xy[0];
1672                 mval_f[1] = y - vod->prev.event_xy[1];
1673                 ED_view3d_win_to_delta(vod->ar, mval_f, dvec, vod->init.zfac);
1674
1675                 add_v3_v3(vod->rv3d->ofs, dvec);
1676
1677                 if (vod->rv3d->viewlock & RV3D_BOXVIEW) {
1678                         view3d_boxview_sync(vod->sa, vod->ar);
1679                 }
1680         }
1681
1682         vod->prev.event_xy[0] = x;
1683         vod->prev.event_xy[1] = y;
1684
1685         ED_view3d_camera_lock_sync(vod->depsgraph, vod->v3d, vod->rv3d);
1686
1687         ED_region_tag_redraw(vod->ar);
1688 }
1689
1690
1691 static int viewmove_modal(bContext *C, wmOperator *op, const wmEvent *event)
1692 {
1693
1694         ViewOpsData *vod = op->customdata;
1695         short event_code = VIEW_PASS;
1696         bool use_autokey = false;
1697         int ret = OPERATOR_RUNNING_MODAL;
1698
1699         /* execute the events */
1700         if (event->type == MOUSEMOVE) {
1701                 event_code = VIEW_APPLY;
1702         }
1703         else if (event->type == EVT_MODAL_MAP) {
1704                 switch (event->val) {
1705                         case VIEW_MODAL_CONFIRM:
1706                                 event_code = VIEW_CONFIRM;
1707                                 break;
1708                         case VIEWROT_MODAL_SWITCH_ZOOM:
1709                                 WM_operator_name_call(C, "VIEW3D_OT_zoom", WM_OP_INVOKE_DEFAULT, NULL);
1710                                 event_code = VIEW_CONFIRM;
1711                                 break;
1712                         case VIEWROT_MODAL_SWITCH_ROTATE:
1713                                 WM_operator_name_call(C, "VIEW3D_OT_rotate", WM_OP_INVOKE_DEFAULT, NULL);
1714                                 event_code = VIEW_CONFIRM;
1715                                 break;
1716                 }
1717         }
1718         else if (event->type == vod->init.event_type && event->val == KM_RELEASE) {
1719                 event_code = VIEW_CONFIRM;
1720         }
1721
1722         if (event_code == VIEW_APPLY) {
1723                 viewmove_apply(vod, event->x, event->y);
1724                 if (ED_screen_animation_playing(CTX_wm_manager(C))) {
1725                         use_autokey = true;
1726                 }
1727         }
1728         else if (event_code == VIEW_CONFIRM) {
1729                 ED_view3d_depth_tag_update(vod->rv3d);
1730                 use_autokey = true;
1731                 ret = OPERATOR_FINISHED;
1732         }
1733
1734         if (use_autokey) {
1735                 ED_view3d_camera_lock_autokey(vod->v3d, vod->rv3d, C, false, true);
1736         }
1737
1738         if (ret & OPERATOR_FINISHED) {
1739                 viewops_data_free(C, op);
1740         }
1741
1742         return ret;
1743 }
1744
1745 static int viewmove_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1746 {
1747         ViewOpsData *vod;
1748
1749         const bool use_mouse_init = RNA_boolean_get(op->ptr, "use_mouse_init");
1750
1751         /* makes op->customdata */
1752         viewops_data_alloc(C, op);
1753         viewops_data_create(
1754                 C, op, event,
1755                 (viewops_flag_from_prefs() & ~VIEWOPS_FLAG_ORBIT_SELECT) |
1756                 (use_mouse_init ? VIEWOPS_FLAG_USE_MOUSE_INIT : 0));
1757         vod = op->customdata;
1758
1759         ED_view3d_smooth_view_force_finish(C, vod->v3d, vod->ar);
1760
1761         if (event->type == MOUSEPAN) {
1762                 /* invert it, trackpad scroll follows same principle as 2d windows this way */
1763                 viewmove_apply(vod, 2 * event->x - event->prevx, 2 * event->y - event->prevy);
1764                 ED_view3d_depth_tag_update(vod->rv3d);
1765
1766                 viewops_data_free(C, op);
1767
1768                 return OPERATOR_FINISHED;
1769         }
1770         else {
1771                 /* add temp handler */
1772                 WM_event_add_modal_handler(C, op);
1773
1774                 return OPERATOR_RUNNING_MODAL;
1775         }
1776 }
1777
1778 static void viewmove_cancel(bContext *C, wmOperator *op)
1779 {
1780         viewops_data_free(C, op);
1781 }
1782
1783 void VIEW3D_OT_move(wmOperatorType *ot)
1784 {
1785
1786         /* identifiers */
1787         ot->name = "Pan View";
1788         ot->description = "Move the view";
1789         ot->idname = "VIEW3D_OT_move";
1790
1791         /* api callbacks */
1792         ot->invoke = viewmove_invoke;
1793         ot->modal = viewmove_modal;
1794         ot->poll = ED_operator_view3d_active;
1795         ot->cancel = viewmove_cancel;
1796
1797         /* flags */
1798         ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR;
1799
1800         /* properties */
1801         view3d_operator_properties_common(ot, V3D_OP_PROP_USE_MOUSE_INIT);
1802 }
1803
1804 /** \} */
1805
1806 /* -------------------------------------------------------------------- */
1807 /** \name View Zoom Operator
1808  * \{ */
1809
1810 /* viewdolly_modal_keymap has an exact copy of this, apply fixes to both */
1811 /* called in transform_ops.c, on each regeneration of keymaps  */
1812 void viewzoom_modal_keymap(wmKeyConfig *keyconf)
1813 {
1814         static const EnumPropertyItem modal_items[] = {
1815                 {VIEW_MODAL_CONFIRM,    "CONFIRM", 0, "Confirm", ""},
1816
1817                 {VIEWROT_MODAL_SWITCH_ROTATE, "SWITCH_TO_ROTATE", 0, "Switch to Rotate"},
1818                 {VIEWROT_MODAL_SWITCH_MOVE, "SWITCH_TO_MOVE", 0, "Switch to Move"},
1819
1820                 {0, NULL, 0, NULL, NULL},
1821         };
1822
1823         wmKeyMap *keymap = WM_modalkeymap_get(keyconf, "View3D Zoom Modal");
1824
1825         /* this function is called for each spacetype, only needs to add map once */
1826         if (keymap && keymap->modal_items) {
1827                 return;
1828         }
1829
1830         keymap = WM_modalkeymap_add(keyconf, "View3D Zoom Modal", modal_items);
1831
1832         /* disabled mode switching for now, can re-implement better, later on */
1833 #if 0
1834         WM_modalkeymap_add_item(keymap, LEFTMOUSE, KM_RELEASE, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ROTATE);
1835         WM_modalkeymap_add_item(keymap, LEFTCTRLKEY, KM_RELEASE, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ROTATE);
1836         WM_modalkeymap_add_item(keymap, LEFTSHIFTKEY, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_MOVE);
1837 #endif
1838
1839         /* assign map to operators */
1840         WM_modalkeymap_assign(keymap, "VIEW3D_OT_zoom");
1841 }
1842
1843 /**
1844  * \param zoom_xy: Optionally zoom to window location (coords compatible w/ #wmEvent.x, y). Use when not NULL.
1845  */
1846 static void view_zoom_to_window_xy_camera(
1847         Scene *scene, Depsgraph *depsgraph, View3D *v3d,
1848         ARegion *ar, float dfac, const int zoom_xy[2])
1849 {
1850         RegionView3D *rv3d = ar->regiondata;
1851         const float zoomfac = BKE_screen_view3d_zoom_to_fac(rv3d->camzoom);
1852         const float zoomfac_new = clamp_f(zoomfac * (1.0f / dfac), RV3D_CAMZOOM_MIN_FACTOR, RV3D_CAMZOOM_MAX_FACTOR);
1853         const float camzoom_new = BKE_screen_view3d_zoom_from_fac(zoomfac_new);
1854
1855
1856         if (zoom_xy != NULL) {
1857                 float zoomfac_px;
1858                 rctf camera_frame_old;
1859                 rctf camera_frame_new;
1860
1861                 const float pt_src[2] = {zoom_xy[0], zoom_xy[1]};
1862                 float pt_dst[2];
1863                 float delta_px[2];
1864
1865                 ED_view3d_calc_camera_border(scene, depsgraph, ar, v3d, rv3d, &camera_frame_old, false);
1866                 BLI_rctf_translate(&camera_frame_old, ar->winrct.xmin, ar->winrct.ymin);
1867
1868                 rv3d->camzoom = camzoom_new;
1869                 CLAMP(rv3d->camzoom, RV3D_CAMZOOM_MIN, RV3D_CAMZOOM_MAX);
1870
1871                 ED_view3d_calc_camera_border(scene, depsgraph, ar, v3d, rv3d, &camera_frame_new, false);
1872                 BLI_rctf_translate(&camera_frame_new, ar->winrct.xmin, ar->winrct.ymin);
1873
1874                 BLI_rctf_transform_pt_v(&camera_frame_new, &camera_frame_old, pt_dst, pt_src);
1875                 sub_v2_v2v2(delta_px, pt_dst, pt_src);
1876
1877                 /* translate the camera offset using pixel space delta
1878                  * mapped back to the camera (same logic as panning in camera view) */
1879                 zoomfac_px = BKE_screen_view3d_zoom_to_fac(rv3d->camzoom) * 2.0f;
1880
1881                 rv3d->camdx += delta_px[0] / (ar->winx * zoomfac_px);
1882                 rv3d->camdy += delta_px[1] / (ar->winy * zoomfac_px);
1883                 CLAMP(rv3d->camdx, -1.0f, 1.0f);
1884                 CLAMP(rv3d->camdy, -1.0f, 1.0f);
1885         }
1886         else {
1887                 rv3d->camzoom = camzoom_new;
1888                 CLAMP(rv3d->camzoom, RV3D_CAMZOOM_MIN, RV3D_CAMZOOM_MAX);
1889         }
1890 }
1891
1892 /**
1893  * \param zoom_xy: Optionally zoom to window location (coords compatible w/ #wmEvent.x, y). Use when not NULL.
1894  */
1895 static void view_zoom_to_window_xy_3d(ARegion *ar, float dfac, const int zoom_xy[2])
1896 {
1897         RegionView3D *rv3d = ar->regiondata;
1898         const float dist_new = rv3d->dist * dfac;
1899
1900         if (zoom_xy != NULL) {
1901                 float dvec[3];
1902                 float tvec[3];
1903                 float tpos[3];
1904                 float mval_f[2];
1905
1906                 float zfac;
1907
1908                 negate_v3_v3(tpos, rv3d->ofs);
1909
1910                 mval_f[0] = (float)(((zoom_xy[0] - ar->winrct.xmin) * 2) - ar->winx) / 2.0f;
1911                 mval_f[1] = (float)(((zoom_xy[1] - ar->winrct.ymin) * 2) - ar->winy) / 2.0f;
1912
1913                 /* Project cursor position into 3D space */
1914                 zfac = ED_view3d_calc_zfac(rv3d, tpos, NULL);
1915                 ED_view3d_win_to_delta(ar, mval_f, dvec, zfac);
1916
1917                 /* Calculate view target position for dolly */
1918                 add_v3_v3v3(tvec, tpos, dvec);
1919                 negate_v3(tvec);
1920
1921                 /* Offset to target position and dolly */
1922                 copy_v3_v3(rv3d->ofs, tvec);
1923                 rv3d->dist = dist_new;
1924
1925                 /* Calculate final offset */
1926                 madd_v3_v3v3fl(rv3d->ofs, tvec, dvec, dfac);
1927         }
1928         else {
1929                 rv3d->dist = dist_new;
1930         }
1931 }
1932
1933 static float viewzoom_scale_value(
1934         const rcti *winrct,
1935         const short viewzoom,
1936         const bool zoom_invert, const bool zoom_invert_force,
1937         const int xy_curr[2], const int xy_init[2],
1938         const float val, const float val_orig,
1939         double *r_timer_lastdraw)
1940 {
1941         float zfac;
1942
1943         if (viewzoom == USER_ZOOM_CONT) {
1944                 double time = PIL_check_seconds_timer();
1945                 float time_step = (float)(time - *r_timer_lastdraw);
1946                 float fac;
1947
1948                 if (U.uiflag & USER_ZOOM_HORIZ) {
1949                         fac = (float)(xy_init[0] - xy_curr[0]);
1950                 }
1951                 else {
1952                         fac = (float)(xy_init[1] - xy_curr[1]);
1953                 }
1954
1955                 if (zoom_invert != zoom_invert_force) {
1956                         fac = -fac;
1957                 }
1958
1959                 /* oldstyle zoom */
1960                 zfac = 1.0f + ((fac / 20.0f) * time_step);
1961                 *r_timer_lastdraw = time;
1962         }
1963         else if (viewzoom == USER_ZOOM_SCALE) {
1964                 /* method which zooms based on how far you move the mouse */
1965
1966                 const int ctr[2] = {
1967                     BLI_rcti_cent_x(winrct),
1968                     BLI_rcti_cent_y(winrct),
1969                 };
1970                 float len_new = 5 + len_v2v2_int(ctr, xy_curr);
1971                 float len_old = 5 + len_v2v2_int(ctr, xy_init);
1972
1973                 /* intentionally ignore 'zoom_invert' for scale */
1974                 if (zoom_invert_force) {
1975                         SWAP(float, len_new, len_old);
1976                 }
1977
1978                 zfac = val_orig * (len_old / max_ff(len_new, 1.0f)) / val;
1979         }
1980         else {  /* USER_ZOOM_DOLLY */
1981                 float len_new = 5;
1982                 float len_old = 5;
1983
1984                 if (U.uiflag & USER_ZOOM_HORIZ) {
1985                         len_new += (winrct->xmax - (xy_curr[0]));
1986                         len_old += (winrct->xmax - (xy_init[0]));
1987                 }
1988                 else {
1989                         len_new += (winrct->ymax - (xy_curr[1]));
1990                         len_old += (winrct->ymax - (xy_init[1]));
1991                 }
1992
1993                 if (zoom_invert != zoom_invert_force) {
1994                         SWAP(float, len_new, len_old);
1995                 }
1996
1997                 zfac = val_orig * (2.0f * ((len_new / max_ff(len_old, 1.0f)) - 1.0f) + 1.0f) / val;
1998         }
1999
2000
2001         return zfac;
2002 }
2003
2004 static float viewzoom_scale_value_offset(
2005         const rcti *winrct,
2006         const short viewzoom,
2007         const bool zoom_invert, const bool zoom_invert_force,
2008         const int xy_curr[2], const int xy_init[2], const int xy_offset[2],
2009         const float val, const float val_orig,
2010         double *r_timer_lastdraw)
2011 {
2012         const int xy_curr_offset[2] = {
2013                 xy_curr[0] + xy_offset[0],
2014                 xy_curr[1] + xy_offset[1],
2015         };
2016         const int xy_init_offset[2] = {
2017                 xy_init[0] + xy_offset[0],
2018                 xy_init[1] + xy_offset[1],
2019         };
2020         return viewzoom_scale_value(
2021                 winrct, viewzoom, zoom_invert, zoom_invert_force,
2022                 xy_curr_offset, xy_init_offset,
2023                 val, val_orig, r_timer_lastdraw);
2024 }
2025
2026 static void viewzoom_apply_camera(
2027         ViewOpsData *vod, const int xy[2],
2028         const short viewzoom, const bool zoom_invert, const bool zoom_to_pos)
2029 {
2030         float zfac;
2031         float zoomfac_prev = BKE_screen_view3d_zoom_to_fac(vod->init.camzoom) * 2.0f;
2032         float zoomfac =      BKE_screen_view3d_zoom_to_fac(vod->rv3d->camzoom) * 2.0f;
2033
2034         zfac = viewzoom_scale_value_offset(
2035                &vod->ar->winrct, viewzoom, zoom_invert, true,
2036                xy, vod->init.event_xy, vod->init.event_xy_offset,
2037                zoomfac, zoomfac_prev,
2038                &vod->prev.time);
2039
2040         if (zfac != 1.0f && zfac != 0.0f) {
2041                 /* calculate inverted, then invert again (needed because of camera zoom scaling) */
2042                 zfac = 1.0f / zfac;
2043                 view_zoom_to_window_xy_camera(
2044                         vod->scene, vod->depsgraph, vod->v3d,
2045                         vod->ar, zfac, zoom_to_pos ? vod->prev.event_xy : NULL);
2046         }
2047
2048         ED_region_tag_redraw(vod->ar);
2049 }
2050
2051 static void viewzoom_apply_3d(
2052         ViewOpsData *vod, const int xy[2],
2053         const short viewzoom, const bool zoom_invert, const bool zoom_to_pos)
2054 {
2055         float zfac;
2056         float dist_range[2];
2057
2058         ED_view3d_dist_range_get(vod->v3d, dist_range);
2059
2060         zfac = viewzoom_scale_value_offset(
2061                &vod->ar->winrct, viewzoom, zoom_invert, false,
2062                xy, vod->init.event_xy, vod->init.event_xy_offset,
2063                vod->rv3d->dist, vod->init.dist,
2064                &vod->prev.time);
2065
2066         if (zfac != 1.0f) {
2067                 const float zfac_min = dist_range[0] / vod->rv3d->dist;
2068                 const float zfac_max = dist_range[1] / vod->rv3d->dist;
2069                 CLAMP(zfac, zfac_min, zfac_max);
2070
2071                 view_zoom_to_window_xy_3d(
2072                         vod->ar, zfac, zoom_to_pos ? vod->prev.event_xy : NULL);
2073         }
2074
2075         /* these limits were in old code too */
2076         CLAMP(vod->rv3d->dist, dist_range[0], dist_range[1]);
2077
2078         if (vod->rv3d->viewlock & RV3D_BOXVIEW) {
2079                 view3d_boxview_sync(vod->sa, vod->ar);
2080         }
2081
2082         ED_view3d_camera_lock_sync(vod->depsgraph, vod->v3d, vod->rv3d);
2083
2084         ED_region_tag_redraw(vod->ar);
2085 }
2086
2087 static void viewzoom_apply(
2088         ViewOpsData *vod, const int xy[2],
2089         const short viewzoom, const bool zoom_invert, const bool zoom_to_pos)
2090 {
2091         if ((vod->rv3d->persp == RV3D_CAMOB) &&
2092             (vod->rv3d->is_persp && ED_view3d_camera_lock_check(vod->v3d, vod->rv3d)) == 0)
2093         {
2094                 viewzoom_apply_camera(vod, xy, viewzoom, zoom_invert, zoom_to_pos);
2095         }
2096         else {
2097                 viewzoom_apply_3d(vod, xy, viewzoom, zoom_invert, zoom_to_pos);
2098         }
2099 }
2100
2101 static int viewzoom_modal(bContext *C, wmOperator *op, const wmEvent *event)
2102 {
2103         ViewOpsData *vod = op->customdata;
2104         short event_code = VIEW_PASS;
2105         bool use_autokey = false;
2106         int ret = OPERATOR_RUNNING_MODAL;
2107
2108         /* execute the events */
2109         if (event->type == TIMER && event->customdata == vod->timer) {
2110                 /* continuous zoom */
2111                 event_code = VIEW_APPLY;
2112         }
2113         else if (event->type == MOUSEMOVE) {
2114                 event_code = VIEW_APPLY;
2115         }
2116         else if (event->type == EVT_MODAL_MAP) {
2117                 switch (event->val) {
2118                         case VIEW_MODAL_CONFIRM:
2119                                 event_code = VIEW_CONFIRM;
2120                                 break;
2121                         case VIEWROT_MODAL_SWITCH_MOVE:
2122                                 WM_operator_name_call(C, "VIEW3D_OT_move", WM_OP_INVOKE_DEFAULT, NULL);
2123                                 event_code = VIEW_CONFIRM;
2124                                 break;
2125                         case VIEWROT_MODAL_SWITCH_ROTATE:
2126                                 WM_operator_name_call(C, "VIEW3D_OT_rotate", WM_OP_INVOKE_DEFAULT, NULL);
2127                                 event_code = VIEW_CONFIRM;
2128                                 break;
2129                 }
2130         }
2131         else if (event->type == vod->init.event_type && event->val == KM_RELEASE) {
2132                 event_code = VIEW_CONFIRM;
2133         }
2134
2135         if (event_code == VIEW_APPLY) {
2136                 const bool use_mouse_init = RNA_boolean_get(op->ptr, "use_mouse_init");
2137                 viewzoom_apply(
2138                         vod, &event->x, U.viewzoom,
2139                         (U.uiflag & USER_ZOOM_INVERT) != 0,
2140                         (use_mouse_init && (U.uiflag & USER_ZOOM_TO_MOUSEPOS)));
2141                 if (ED_screen_animation_playing(CTX_wm_manager(C))) {
2142                         use_autokey = true;
2143                 }
2144         }
2145         else if (event_code == VIEW_CONFIRM) {
2146                 ED_view3d_depth_tag_update(vod->rv3d);
2147                 use_autokey = true;
2148                 ret = OPERATOR_FINISHED;
2149         }
2150
2151         if (use_autokey) {
2152                 ED_view3d_camera_lock_autokey(vod->v3d, vod->rv3d, C, false, true);
2153         }
2154
2155         if (ret & OPERATOR_FINISHED) {
2156                 viewops_data_free(C, op);
2157         }
2158
2159         return ret;
2160 }
2161
2162 static int viewzoom_exec(bContext *C, wmOperator *op)
2163 {
2164         Depsgraph *depsgraph = CTX_data_depsgraph(C);
2165         Scene *scene = CTX_data_scene(C);
2166         View3D *v3d;
2167         RegionView3D *rv3d;
2168         ScrArea *sa;
2169         ARegion *ar;
2170         bool use_cam_zoom;
2171         float dist_range[2];
2172
2173         const int delta = RNA_int_get(op->ptr, "delta");
2174         const bool use_mouse_init = RNA_boolean_get(op->ptr, "use_mouse_init");
2175
2176         if (op->customdata) {
2177                 ViewOpsData *vod = op->customdata;
2178
2179                 sa = vod->sa;
2180                 ar = vod->ar;
2181         }
2182         else {
2183                 sa = CTX_wm_area(C);
2184                 ar = CTX_wm_region(C);
2185         }
2186
2187         v3d = sa->spacedata.first;
2188         rv3d = ar->regiondata;
2189
2190
2191         use_cam_zoom = (rv3d->persp == RV3D_CAMOB) && !(rv3d->is_persp && ED_view3d_camera_lock_check(v3d, rv3d));
2192
2193         int zoom_xy_buf[2];
2194         const int *zoom_xy = NULL;
2195         if (use_mouse_init && (U.uiflag & USER_ZOOM_TO_MOUSEPOS)) {
2196                 zoom_xy_buf[0] = RNA_struct_property_is_set(op->ptr, "mx") ? RNA_int_get(op->ptr, "mx") : ar->winx / 2;
2197                 zoom_xy_buf[1] = RNA_struct_property_is_set(op->ptr, "my") ? RNA_int_get(op->ptr, "my") : ar->winy / 2;
2198                 zoom_xy = zoom_xy_buf;
2199         }
2200
2201         ED_view3d_dist_range_get(v3d, dist_range);
2202
2203         if (delta < 0) {
2204                 const float step = 1.2f;
2205                 /* this min and max is also in viewmove() */
2206                 if (use_cam_zoom) {
2207                         view_zoom_to_window_xy_camera(scene, depsgraph, v3d, ar, step, zoom_xy);
2208                 }
2209                 else {
2210                         if (rv3d->dist < dist_range[1]) {
2211                                 view_zoom_to_window_xy_3d(ar, step, zoom_xy);
2212                         }
2213                 }
2214         }
2215         else {
2216                 const float step = 1.0f / 1.2f;
2217                 if (use_cam_zoom) {
2218                         view_zoom_to_window_xy_camera(scene, depsgraph, v3d, ar, step, zoom_xy);
2219                 }
2220                 else {
2221                         if (rv3d->dist > dist_range[0]) {
2222                                 view_zoom_to_window_xy_3d(ar, step, zoom_xy);
2223                         }
2224                 }
2225         }
2226
2227         if (rv3d->viewlock & RV3D_BOXVIEW) {
2228                 view3d_boxview_sync(sa, ar);
2229         }
2230
2231         ED_view3d_depth_tag_update(rv3d);
2232
2233         ED_view3d_camera_lock_sync(depsgraph, v3d, rv3d);
2234         ED_view3d_camera_lock_autokey(v3d, rv3d, C, false, true);
2235
2236         ED_region_tag_redraw(ar);
2237
2238         viewops_data_free(C, op);
2239
2240         return OPERATOR_FINISHED;
2241 }
2242
2243 /* viewdolly_invoke() copied this function, changes here may apply there */
2244 static int viewzoom_invoke(bContext *C, wmOperator *op, const wmEvent *event)
2245 {
2246         ViewOpsData *vod;
2247
2248         const bool use_mouse_init = RNA_boolean_get(op->ptr, "use_mouse_init");
2249
2250         /* makes op->customdata */
2251         viewops_data_alloc(C, op);
2252         viewops_data_create(
2253                 C, op, event,
2254                 (viewops_flag_from_prefs() & ~VIEWOPS_FLAG_ORBIT_SELECT) |
2255                 (use_mouse_init ? VIEWOPS_FLAG_USE_MOUSE_INIT : 0));
2256         vod = op->customdata;
2257
2258         ED_view3d_smooth_view_force_finish(C, vod->v3d, vod->ar);
2259
2260         /* if one or the other zoom position aren't set, set from event */
2261         if (!RNA_struct_property_is_set(op->ptr, "mx") || !RNA_struct_property_is_set(op->ptr, "my")) {
2262                 RNA_int_set(op->ptr, "mx", event->x);
2263                 RNA_int_set(op->ptr, "my", event->y);
2264         }
2265
2266         if (RNA_struct_property_is_set(op->ptr, "delta")) {
2267                 viewzoom_exec(C, op);
2268         }
2269         else {
2270                 if (event->type == MOUSEZOOM || event->type == MOUSEPAN) {
2271
2272                         if (U.uiflag & USER_ZOOM_HORIZ) {
2273                                 vod->init.event_xy[0] = vod->prev.event_xy[0] = event->x;
2274                         }
2275                         else {
2276                                 /* Set y move = x move as MOUSEZOOM uses only x axis to pass magnification value */
2277                                 vod->init.event_xy[1] = vod->prev.event_xy[1] = vod->init.event_xy[1] + event->x - event->prevx;
2278                         }
2279                         viewzoom_apply(
2280                                 vod, &event->prevx, USER_ZOOM_DOLLY,
2281                                 (U.uiflag & USER_ZOOM_INVERT) != 0,
2282                                 (use_mouse_init && (U.uiflag & USER_ZOOM_TO_MOUSEPOS)));
2283                         ED_view3d_camera_lock_autokey(vod->v3d, vod->rv3d, C, false, true);
2284
2285                         ED_view3d_depth_tag_update(vod->rv3d);
2286
2287                         viewops_data_free(C, op);
2288                         return OPERATOR_FINISHED;
2289                 }
2290                 else {
2291                         if (U.viewzoom == USER_ZOOM_CONT) {
2292                                 /* needs a timer to continue redrawing */
2293                                 vod->timer = WM_event_add_timer(CTX_wm_manager(C), CTX_wm_window(C), TIMER, 0.01f);
2294                                 vod->prev.time = PIL_check_seconds_timer();
2295                         }
2296
2297                         /* add temp handler */
2298                         WM_event_add_modal_handler(C, op);
2299
2300                         return OPERATOR_RUNNING_MODAL;
2301                 }
2302         }
2303         return OPERATOR_FINISHED;
2304 }
2305
2306 static void viewzoom_cancel(bContext *C, wmOperator *op)
2307 {
2308         viewops_data_free(C, op);
2309 }
2310
2311 void VIEW3D_OT_zoom(wmOperatorType *ot)
2312 {
2313         /* identifiers */
2314         ot->name = "Zoom View";
2315         ot->description = "Zoom in/out in the view";
2316         ot->idname = "VIEW3D_OT_zoom";
2317
2318         /* api callbacks */
2319         ot->invoke = viewzoom_invoke;
2320         ot->exec = viewzoom_exec;
2321         ot->modal = viewzoom_modal;
2322         ot->poll = ED_operator_region_view3d_active;
2323         ot->cancel = viewzoom_cancel;
2324
2325         /* flags */
2326         ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR;
2327
2328         /* properties */
2329         view3d_operator_properties_common(
2330                 ot,
2331                 V3D_OP_PROP_DELTA | V3D_OP_PROP_MOUSE_CO | V3D_OP_PROP_USE_MOUSE_INIT);
2332 }
2333
2334 /** \} */
2335
2336 /* -------------------------------------------------------------------- */
2337 /** \name View Dolly Operator
2338  *
2339  * Like zoom but translates the view offset along the view direction
2340  * which avoids #RegionView3D.dist approaching zero.
2341  * \{ */
2342
2343 /* this is an exact copy of viewzoom_modal_keymap */
2344 /* called in transform_ops.c, on each regeneration of keymaps  */
2345 void viewdolly_modal_keymap(wmKeyConfig *keyconf)
2346 {
2347         static const EnumPropertyItem modal_items[] = {
2348                 {VIEW_MODAL_CONFIRM,    "CONFIRM", 0, "Confirm", ""},
2349
2350                 {VIEWROT_MODAL_SWITCH_ROTATE, "SWITCH_TO_ROTATE", 0, "Switch to Rotate"},
2351                 {VIEWROT_MODAL_SWITCH_MOVE, "SWITCH_TO_MOVE", 0, "Switch to Move"},
2352
2353                 {0, NULL, 0, NULL, NULL},
2354         };
2355
2356         wmKeyMap *keymap = WM_modalkeymap_get(keyconf, "View3D Dolly Modal");
2357
2358         /* this function is called for each spacetype, only needs to add map once */
2359         if (keymap && keymap->modal_items) {
2360                 return;
2361         }
2362
2363         keymap = WM_modalkeymap_add(keyconf, "View3D Dolly Modal", modal_items);
2364
2365         /* disabled mode switching for now, can re-implement better, later on */
2366 #if 0
2367         WM_modalkeymap_add_item(keymap, LEFTMOUSE, KM_RELEASE, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ROTATE);
2368         WM_modalkeymap_add_item(keymap, LEFTCTRLKEY, KM_RELEASE, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ROTATE);
2369         WM_modalkeymap_add_item(keymap, LEFTSHIFTKEY, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_MOVE);
2370 #endif
2371
2372         /* assign map to operators */
2373         WM_modalkeymap_assign(keymap, "VIEW3D_OT_dolly");
2374 }
2375
2376 static bool viewdolly_offset_lock_check(bContext *C, wmOperator *op)
2377 {
2378         View3D *v3d = CTX_wm_view3d(C);
2379         RegionView3D *rv3d = CTX_wm_region_view3d(C);
2380         if (ED_view3d_offset_lock_check(v3d, rv3d)) {
2381                 BKE_report(op->reports, RPT_WARNING, "Cannot dolly when the view offset is locked");
2382                 return true;
2383         }
2384         else {
2385                 return false;
2386         }
2387 }
2388
2389 static void view_dolly_to_vector_3d(ARegion *ar, float orig_ofs[3], float dvec[3], float dfac)
2390 {
2391         RegionView3D *rv3d = ar->regiondata;
2392         madd_v3_v3v3fl(rv3d->ofs, orig_ofs, dvec, -(1.0f - dfac));
2393 }
2394
2395 static void viewdolly_apply(ViewOpsData *vod, const int xy[2], const short zoom_invert)
2396 {
2397         float zfac = 1.0;
2398
2399         {
2400                 float len1, len2;
2401
2402                 if (U.uiflag & USER_ZOOM_HORIZ) {
2403                         len1 = (vod->ar->winrct.xmax - xy[0]) + 5;
2404                         len2 = (vod->ar->winrct.xmax - vod->init.event_xy[0]) + 5;
2405                 }
2406                 else {
2407                         len1 = (vod->ar->winrct.ymax - xy[1]) + 5;
2408                         len2 = (vod->ar->winrct.ymax - vod->init.event_xy[1]) + 5;
2409                 }
2410                 if (zoom_invert) {
2411                         SWAP(float, len1, len2);
2412                 }
2413
2414                 zfac =  1.0f + ((len1 - len2) * 0.01f * vod->rv3d->dist);
2415         }
2416
2417         if (zfac != 1.0f) {
2418                 view_dolly_to_vector_3d(vod->ar, vod->init.ofs, vod->init.mousevec, zfac);
2419         }
2420
2421         if (vod->rv3d->viewlock & RV3D_BOXVIEW) {
2422                 view3d_boxview_sync(vod->sa, vod->ar);
2423         }
2424
2425         ED_view3d_camera_lock_sync(vod->depsgraph, vod->v3d, vod->rv3d);
2426
2427         ED_region_tag_redraw(vod->ar);
2428 }
2429
2430
2431 static int viewdolly_modal(bContext *C, wmOperator *op, const wmEvent *event)
2432 {
2433         ViewOpsData *vod = op->customdata;
2434         short event_code = VIEW_PASS;
2435         bool use_autokey = false;
2436         int ret = OPERATOR_RUNNING_MODAL;
2437
2438         /* execute the events */
2439         if (event->type == MOUSEMOVE) {
2440                 event_code = VIEW_APPLY;
2441         }
2442         else if (event->type == EVT_MODAL_MAP) {
2443                 switch (event->val) {
2444                         case VIEW_MODAL_CONFIRM:
2445                                 event_code = VIEW_CONFIRM;
2446                                 break;
2447                         case VIEWROT_MODAL_SWITCH_MOVE:
2448                                 WM_operator_name_call(C, "VIEW3D_OT_move", WM_OP_INVOKE_DEFAULT, NULL);
2449                                 event_code = VIEW_CONFIRM;
2450                                 break;
2451                         case VIEWROT_MODAL_SWITCH_ROTATE:
2452                                 WM_operator_name_call(C, "VIEW3D_OT_rotate", WM_OP_INVOKE_DEFAULT, NULL);
2453                                 event_code = VIEW_CONFIRM;
2454                                 break;
2455                 }
2456         }
2457         else if (event->type == vod->init.event_type && event->val == KM_RELEASE) {
2458                 event_code = VIEW_CONFIRM;
2459         }
2460
2461         if (event_code == VIEW_APPLY) {
2462                 viewdolly_apply(vod, &event->x, (U.uiflag & USER_ZOOM_INVERT) != 0);
2463                 if (ED_screen_animation_playing(CTX_wm_manager(C))) {
2464                         use_autokey = true;
2465                 }
2466         }
2467         else if (event_code == VIEW_CONFIRM) {
2468                 ED_view3d_depth_tag_update(vod->rv3d);
2469                 use_autokey = true;
2470                 ret = OPERATOR_FINISHED;
2471         }
2472
2473         if (use_autokey) {
2474                 ED_view3d_camera_lock_autokey(vod->v3d, vod->rv3d, C, false, true);
2475         }
2476
2477         if (ret & OPERATOR_FINISHED) {
2478                 viewops_data_free(C, op);
2479         }
2480
2481         return ret;
2482 }
2483
2484 static int viewdolly_exec(bContext *C, wmOperator *op)
2485 {
2486         View3D *v3d;
2487         RegionView3D *rv3d;
2488         ScrArea *sa;
2489         ARegion *ar;
2490         float mousevec[3];
2491
2492         const int delta = RNA_int_get(op->ptr, "delta");
2493
2494         if (op->customdata) {
2495                 ViewOpsData *vod = op->customdata;
2496
2497                 sa = vod->sa;
2498                 ar = vod->ar;
2499                 copy_v3_v3(mousevec, vod->init.mousevec);
2500         }
2501         else {
2502                 sa = CTX_wm_area(C);
2503                 ar = CTX_wm_region(C);
2504                 negate_v3_v3(mousevec, ((RegionView3D *)ar->regiondata)->viewinv[2]);
2505                 normalize_v3(mousevec);
2506         }
2507
2508         v3d = sa->spacedata.first;
2509         rv3d = ar->regiondata;
2510
2511         const bool use_mouse_init = RNA_boolean_get(op->ptr, "use_mouse_init");
2512
2513         /* overwrite the mouse vector with the view direction (zoom into the center) */
2514         if ((use_mouse_init && (U.uiflag & USER_ZOOM_TO_MOUSEPOS)) == 0) {
2515                 normalize_v3_v3(mousevec, rv3d->viewinv[2]);
2516         }
2517
2518         view_dolly_to_vector_3d(ar, rv3d->ofs, mousevec, delta < 0 ? 0.2f : 1.8f);
2519
2520         if (rv3d->viewlock & RV3D_BOXVIEW) {
2521                 view3d_boxview_sync(sa, ar);
2522         }
2523
2524         ED_view3d_depth_tag_update(rv3d);
2525
2526         ED_view3d_camera_lock_sync(CTX_data_depsgraph(C), v3d, rv3d);
2527
2528         ED_region_tag_redraw(ar);
2529
2530         viewops_data_free(C, op);
2531
2532         return OPERATOR_FINISHED;
2533 }
2534
2535 /* copied from viewzoom_invoke(), changes here may apply there */
2536 static int viewdolly_invoke(bContext *C, wmOperator *op, const wmEvent *event)
2537 {
2538         ViewOpsData *vod;
2539
2540         if (viewdolly_offset_lock_check(C, op)) {
2541                 return OPERATOR_CANCELLED;
2542         }
2543
2544         /* makes op->customdata */
2545         viewops_data_alloc(C, op);
2546         vod = op->customdata;
2547
2548         /* poll should check but in some cases fails, see poll func for details */
2549         if (vod->rv3d->viewlock & RV3D_LOCKED) {
2550                 viewops_data_free(C, op);
2551                 return OPERATOR_PASS_THROUGH;
2552         }
2553
2554         ED_view3d_smooth_view_force_finish(C, vod->v3d, vod->ar);
2555
2556         /* needs to run before 'viewops_data_create' so the backup 'rv3d->ofs' is correct */
2557         /* switch from camera view when: */
2558         if (vod->rv3d->persp != RV3D_PERSP) {
2559                 if (vod->rv3d->persp == RV3D_CAMOB) {
2560                         /* ignore rv3d->lpersp because dolly only makes sense in perspective mode */
2561                         const Depsgraph *depsgraph = CTX_data_depsgraph(C);
2562                         ED_view3d_persp_switch_from_camera(depsgraph, vod->v3d, vod->rv3d, RV3D_PERSP);
2563                 }
2564                 else {
2565                         vod->rv3d->persp = RV3D_PERSP;
2566                 }
2567                 ED_region_tag_redraw(vod->ar);
2568         }
2569
2570         const bool use_mouse_init = RNA_boolean_get(op->ptr, "use_mouse_init");
2571
2572         viewops_data_create(
2573                 C, op, event,
2574                 (viewops_flag_from_prefs() & ~VIEWOPS_FLAG_ORBIT_SELECT) |
2575                 (use_mouse_init ? VIEWOPS_FLAG_USE_MOUSE_INIT : 0));
2576
2577
2578         /* if one or the other zoom position aren't set, set from event */
2579         if (!RNA_struct_property_is_set(op->ptr, "mx") || !RNA_struct_property_is_set(op->ptr, "my")) {
2580                 RNA_int_set(op->ptr, "mx", event->x);
2581                 RNA_int_set(op->ptr, "my", event->y);
2582         }
2583
2584         if (RNA_struct_property_is_set(op->ptr, "delta")) {
2585                 viewdolly_exec(C, op);
2586         }
2587         else {
2588                 /* overwrite the mouse vector with the view direction (zoom into the center) */
2589                 if ((use_mouse_init && (U.uiflag & USER_ZOOM_TO_MOUSEPOS)) == 0) {
2590                         negate_v3_v3(vod->init.mousevec, vod->rv3d->viewinv[2]);
2591                         normalize_v3(vod->init.mousevec);
2592                 }
2593
2594                 if (event->type == MOUSEZOOM) {
2595                         /* Bypass Zoom invert flag for track pads (pass false always) */
2596
2597                         if (U.uiflag & USER_ZOOM_HORIZ) {
2598                                 vod->init.event_xy[0] = vod->prev.event_xy[0] = event->x;
2599                         }
2600                         else {
2601                                 /* Set y move = x move as MOUSEZOOM uses only x axis to pass magnification value */
2602                                 vod->init.event_xy[1] = vod->prev.event_xy[1] = vod->init.event_xy[1] + event->x - event->prevx;
2603                         }
2604                         viewdolly_apply(vod, &event->prevx, (U.uiflag & USER_ZOOM_INVERT) == 0);
2605                         ED_view3d_depth_tag_update(vod->rv3d);
2606
2607                         viewops_data_free(C, op);
2608                         return OPERATOR_FINISHED;
2609                 }
2610                 else {
2611                         /* add temp handler */
2612                         WM_event_add_modal_handler(C, op);
2613
2614                         return OPERATOR_RUNNING_MODAL;
2615                 }
2616         }
2617         return OPERATOR_FINISHED;
2618 }
2619
2620 static void viewdolly_cancel(bContext *C, wmOperator *op)
2621 {
2622         viewops_data_free(C, op);
2623 }
2624
2625 void VIEW3D_OT_dolly(wmOperatorType *ot)
2626 {
2627         /* identifiers */
2628         ot->name = "Dolly View";
2629         ot->description = "Dolly in/out in the view";
2630         ot->idname = "VIEW3D_OT_dolly";
2631
2632         /* api callbacks */
2633         ot->invoke = viewdolly_invoke;
2634         ot->exec = viewdolly_exec;
2635         ot->modal = viewdolly_modal;
2636         ot->poll = ED_operator_region_view3d_active;
2637         ot->cancel = viewdolly_cancel;
2638
2639         /* flags */
2640         ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR;
2641
2642         /* properties */
2643         view3d_operator_properties_common(
2644                 ot, V3D_OP_PROP_DELTA | V3D_OP_PROP_MOUSE_CO | V3D_OP_PROP_USE_MOUSE_INIT);
2645 }
2646
2647 /** \} */
2648
2649 /* -------------------------------------------------------------------- */
2650 /** \name View All Operator
2651  *
2652  * Move & Zoom the view to fit all of it's contents.
2653  * \{ */
2654
2655 static bool view3d_object_skip_minmax(
2656         const View3D *v3d, const RegionView3D *rv3d, const Object *ob, const bool skip_camera,
2657         bool *r_only_center)
2658 {
2659         BLI_assert(ob->id.orig_id == NULL);
2660         *r_only_center = false;
2661
2662         if (skip_camera && (ob == v3d->camera)) {
2663                 return true;
2664         }
2665
2666         if ((ob->type == OB_EMPTY) &&
2667             (ob->empty_drawtype == OB_EMPTY_IMAGE) &&
2668             !BKE_object_empty_image_frame_is_visible_in_view3d(ob, rv3d))
2669         {
2670                 *r_only_center = true;
2671                 return false;
2672         }
2673
2674         return false;
2675 }
2676
2677 static void view3d_from_minmax(
2678         bContext *C, View3D *v3d, ARegion *ar,
2679         const float min[3], const float max[3],
2680         bool ok_dist, const int smooth_viewtx)
2681 {
2682         RegionView3D *rv3d = ar->regiondata;
2683         float afm[3];
2684         float size;
2685
2686         ED_view3d_smooth_view_force_finish(C, v3d, ar);
2687
2688         /* SMOOTHVIEW */
2689         float new_ofs[3];
2690         float new_dist;
2691
2692         sub_v3_v3v3(afm, max, min);
2693         size = max_fff(afm[0], afm[1], afm[2]);
2694
2695         if (ok_dist) {
2696                 char persp;
2697
2698                 if (rv3d->is_persp) {
2699                         if (rv3d->persp == RV3D_CAMOB && ED_view3d_camera_lock_check(v3d, rv3d)) {
2700                                 persp = RV3D_CAMOB;
2701                         }
2702                         else {
2703                                 persp = RV3D_PERSP;
2704                         }
2705                 }
2706                 else { /* ortho */
2707                         if (size < 0.0001f) {
2708                                 /* bounding box was a single point so do not zoom */
2709                                 ok_dist = false;
2710                         }
2711                         else {
2712                                 /* adjust zoom so it looks nicer */
2713                                 persp = RV3D_ORTHO;
2714                         }
2715                 }
2716
2717                 if (ok_dist) {
2718                         new_dist = ED_view3d_radius_to_dist(v3d, ar, CTX_data_depsgraph(C), persp, true, (size / 2) * VIEW3D_MARGIN);
2719                         if (rv3d->is_persp) {
2720                                 /* don't zoom closer than the near clipping plane */
2721                                 new_dist = max_ff(new_dist, v3d->clip_start * 1.5f);
2722                         }
2723                 }
2724         }
2725
2726         mid_v3_v3v3(new_ofs, min, max);
2727         negate_v3(new_ofs);
2728
2729         if (rv3d->persp == RV3D_CAMOB && !ED_view3d_camera_lock_check(v3d, rv3d)) {
2730                 rv3d->persp = RV3D_PERSP;
2731                 ED_view3d_smooth_view(
2732                         C, v3d, ar, smooth_viewtx,
2733                         &(const V3D_SmoothParams) {
2734                             .camera_old = v3d->camera, .ofs = new_ofs,
2735                             .dist = ok_dist ? &new_dist : NULL,
2736                         });
2737         }
2738         else {
2739                 ED_view3d_smooth_view(
2740                         C, v3d, ar, smooth_viewtx,
2741                         &(const V3D_SmoothParams) { .ofs = new_ofs, .dist = ok_dist ? &new_dist : NULL, });
2742         }
2743
2744         /* smooth view does viewlock RV3D_BOXVIEW copy */
2745 }
2746
2747 /**
2748  * Same as #view3d_from_minmax but for all regions (except cameras).
2749  */
2750 static void view3d_from_minmax_multi(
2751         bContext *C, View3D *v3d,
2752         const float min[3], const float max[3],
2753         const bool ok_dist, const int smooth_viewtx)
2754 {
2755         ScrArea *sa = CTX_wm_area(C);
2756         ARegion *ar;
2757         for (ar = sa->regionbase.first; ar; ar = ar->next) {
2758                 if (ar->regiontype == RGN_TYPE_WINDOW) {
2759                         RegionView3D *rv3d = ar->regiondata;
2760                         /* when using all regions, don't jump out of camera view,
2761                          * but _do_ allow locked cameras to be moved */
2762                         if ((rv3d->persp != RV3D_CAMOB) || ED_view3d_camera_lock_check(v3d, rv3d)) {
2763                                 view3d_from_minmax(C, v3d, ar, min, max, ok_dist, smooth_viewtx);
2764                         }
2765                 }
2766         }
2767 }
2768
2769 static int view3d_all_exec(bContext *C, wmOperator *op)
2770 {
2771         ARegion *ar = CTX_wm_region(C);
2772         View3D *v3d = CTX_wm_view3d(C);
2773         RegionView3D *rv3d = CTX_wm_region_view3d(C);
2774         Scene *scene = CTX_data_scene(C);
2775         const Depsgraph *depsgraph = CTX_data_depsgraph(C);
2776         ViewLayer *view_layer_eval = DEG_get_evaluated_view_layer(depsgraph);
2777         Base *base_eval;
2778         const bool use_all_regions = RNA_boolean_get(op->ptr, "use_all_regions");
2779         const bool skip_camera = (ED_view3d_camera_lock_check(v3d, ar->regiondata) ||
2780                                   /* any one of the regions may be locked */
2781                                   (use_all_regions && v3d->flag2 & V3D_LOCK_CAMERA));
2782         const bool center = RNA_boolean_get(op->ptr, "center");
2783         const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
2784
2785         float min[3], max[3];
2786         bool changed = false;
2787
2788         if (center) {
2789                 /* in 2.4x this also move the cursor to (0, 0, 0) (with shift+c). */
2790                 View3DCursor *cursor = &scene->cursor;
2791                 zero_v3(min);
2792                 zero_v3(max);
2793                 zero_v3(cursor->location);
2794                 float mat3[3][3];
2795                 unit_m3(mat3);
2796                 BKE_scene_cursor_mat3_to_rot(cursor, mat3, false);
2797         }
2798         else {
2799                 INIT_MINMAX(min, max);
2800         }
2801
2802         for (base_eval = view_layer_eval->object_bases.first; base_eval; base_eval = base_eval->next) {
2803                 if (BASE_VISIBLE(v3d, base_eval)) {
2804                         bool only_center = false;
2805                         Object *ob = DEG_get_original_object(base_eval->object);
2806                         if (view3d_object_skip_minmax(v3d, rv3d, ob, skip_camera, &only_center)) {
2807                                 continue;
2808                         }
2809
2810                         if (only_center) {
2811                                 minmax_v3v3_v3(min, max, base_eval->object->obmat[3]);
2812                         }
2813                         else {
2814                                 BKE_object_minmax(base_eval->object, min, max, false);
2815                         }
2816                         changed = true;
2817                 }
2818         }
2819
2820         if (center) {
2821                 DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE);
2822         }
2823
2824         if (!changed) {
2825                 ED_region_tag_redraw(ar);
2826                 /* TODO - should this be cancel?
2827                  * I think no, because we always move the cursor, with or without
2828                  * object, but in this case there is no change in the scene,
2829                  * only the cursor so I choice a ED_region_tag like
2830                  * view3d_smooth_view do for the center_cursor.
2831                  * See bug #22640
2832                  */
2833                 return OPERATOR_FINISHED;
2834         }
2835
2836         if (use_all_regions) {
2837                 view3d_from_minmax_multi(C, v3d, min, max, true, smooth_viewtx);
2838         }
2839         else {
2840                 view3d_from_minmax(C, v3d, ar, min, max, true, smooth_viewtx);
2841         }
2842
2843         return OPERATOR_FINISHED;
2844 }
2845
2846
2847 void VIEW3D_OT_view_all(wmOperatorType *ot)
2848 {
2849         /* identifiers */
2850         ot->name = "View All";
2851         ot->description = "View all objects in scene";
2852         ot->idname = "VIEW3D_OT_view_all";
2853
2854         /* api callbacks */
2855         ot->exec = view3d_all_exec;
2856         ot->poll = ED_operator_region_view3d_active;
2857
2858         /* flags */
2859         ot->flag = 0;
2860
2861         /* properties */
2862         view3d_operator_properties_common(ot, V3D_OP_PROP_USE_ALL_REGIONS);
2863         RNA_def_boolean(ot->srna, "center", 0, "Center", "");
2864 }
2865
2866 /** \} */
2867
2868 /* -------------------------------------------------------------------- */
2869 /** \name View Selected Operator
2870  *
2871  * Move & Zoom the view to fit selected contents.
2872  * \{ */
2873
2874 /* like a localview without local!, was centerview() in 2.4x */
2875 static int viewselected_exec(bContext *C, wmOperator *op)
2876 {
2877         ARegion *ar = CTX_wm_region(C);
2878         View3D *v3d = CTX_wm_view3d(C);
2879         RegionView3D *rv3d = CTX_wm_region_view3d(C);
2880         Scene *scene = CTX_data_scene(C);
2881         Depsgraph *depsgraph = CTX_data_depsgraph(C);
2882         ViewLayer *view_layer_eval = DEG_get_evaluated_view_layer(depsgraph);
2883         bGPdata *gpd = CTX_data_gpencil_data(C);
2884         const bool is_gp_edit = GPENCIL_ANY_MODE(gpd);
2885         const bool is_face_map = ((is_gp_edit == false) && ar->gizmo_map &&
2886                                   WM_gizmomap_is_any_selected(ar->gizmo_map));
2887         Object *ob_eval = OBACT(view_layer_eval);
2888         Object *obedit = CTX_data_edit_object(C);
2889         float min[3], max[3];
2890         bool ok = false, ok_dist = true;
2891         const bool use_all_regions = RNA_boolean_get(op->ptr, "use_all_regions");
2892         const bool skip_camera = (ED_view3d_camera_lock_check(v3d, ar->regiondata) ||
2893                                   /* any one of the regions may be locked */
2894                                   (use_all_regions && v3d->flag2 & V3D_LOCK_CAMERA));
2895         const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
2896
2897         INIT_MINMAX(min, max);
2898         if (is_face_map) {
2899                 ob_eval = NULL;
2900         }
2901
2902         if (ob_eval && (ob_eval->mode & OB_MODE_WEIGHT_PAINT)) {
2903                 /* hard-coded exception, we look for the one selected armature */
2904                 /* this is weak code this way, we should make a generic
2905                  * active/selection callback interface once... */
2906                 Base *base_eval;
2907                 for (base_eval = view_layer_eval->object_bases.first; base_eval; base_eval = base_eval->next) {
2908                         if (BASE_SELECTED_EDITABLE(v3d, base_eval)) {
2909                                 if (base_eval->object->type == OB_ARMATURE) {
2910                                         if (base_eval->object->mode & OB_MODE_POSE) {
2911                                                 break;
2912                                         }
2913                                 }
2914                         }
2915                 }
2916                 if (base_eval) {
2917                         ob_eval = base_eval->object;
2918                 }
2919         }
2920
2921         if (is_gp_edit) {
2922                 CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
2923                 {
2924                         /* we're only interested in selected points here... */
2925                         if ((gps->flag & GP_STROKE_SELECT) && (gps->flag & GP_STROKE_3DSPACE)) {
2926                                 ok |= BKE_gpencil_stroke_minmax(gps, true, min, max);
2927                         }
2928                 }
2929                 CTX_DATA_END;
2930
2931                 if ((ob_eval) && (ok)) {
2932                         mul_m4_v3(ob_eval->obmat, min);
2933                         mul_m4_v3(ob_eval->obmat, max);
2934                 }
2935         }
2936         else if (is_face_map) {
2937                 ok = WM_gizmomap_minmax(ar->gizmo_map, true, true, min, max);
2938         }
2939         else if (obedit) {
2940                 /* only selected */
2941                 FOREACH_OBJECT_IN_MODE_BEGIN (view_layer_eval, v3d, obedit->type, obedit->mode, ob_eval_iter) {
2942                         ok |= ED_view3d_minmax_verts(ob_eval_iter, min, max);
2943                 }
2944                 FOREACH_OBJECT_IN_MODE_END;
2945         }
2946         else if (ob_eval && (ob_eval->mode & OB_MODE_POSE)) {
2947                 FOREACH_OBJECT_IN_MODE_BEGIN (view_layer_eval, v3d, ob_eval->type, ob_eval->mode, ob_eval_iter) {
2948                         ok |= BKE_pose_minmax(ob_eval_iter, min, max, true, true);
2949                 }
2950                 FOREACH_OBJECT_IN_MODE_END;
2951         }
2952         else if (BKE_paint_select_face_test(ob_eval)) {
2953                 ok = paintface_minmax(ob_eval, min, max);
2954         }
2955         else if (ob_eval && (ob_eval->mode & OB_MODE_PARTICLE_EDIT)) {
2956                 ok = PE_minmax(scene, view_layer_eval, min, max);
2957         }
2958         else if (ob_eval &&
2959                  (ob_eval->mode & (OB_MODE_SCULPT | OB_MODE_VERTEX_PAINT | OB_MODE_WEIGHT_PAINT | OB_MODE_TEXTURE_PAINT)))
2960         {
2961                 BKE_paint_stroke_get_average(scene, ob_eval, min);
2962                 copy_v3_v3(max, min);
2963                 ok = true;
2964                 ok_dist = 0; /* don't zoom */
2965         }
2966         else {
2967                 Base *base_eval;
2968                 for (base_eval = FIRSTBASE(view_layer_eval); base_eval; base_eval = base_eval->next) {
2969                         if (BASE_SELECTED(v3d, base_eval)) {
2970                                 bool only_center = false;
2971                                 Object *ob = DEG_get_original_object(base_eval->object);
2972                                 if (view3d_object_skip_minmax(v3d, rv3d, ob, skip_camera, &only_center)) {
2973                                         continue;
2974                                 }
2975
2976                                 /* account for duplis */
2977                                 if (BKE_object_minmax_dupli(depsgraph, scene, base_eval->object, min, max, false) == 0) {
2978                                         /* use if duplis not found */
2979                                         if (only_center) {
2980                                                 minmax_v3v3_v3(min, max, base_eval->object->obmat[3]);
2981                                         }
2982                                         else {
2983                                                 BKE_object_minmax(base_eval->object, min, max, false);
2984                                         }
2985                                 }
2986
2987                                 ok = 1;
2988                         }
2989                 }
2990         }
2991
2992         if (ok == 0) {
2993                 return OPERATOR_FINISHED;
2994         }
2995
2996         if (use_all_regions) {
2997                 view3d_from_minmax_multi(C, v3d, min, max, ok_dist, smooth_viewtx);
2998         }
2999         else {
3000                 view3d_from_minmax(C, v3d, ar, min, max, ok_dist, smooth_viewtx);
3001         }
3002
3003         return OPERATOR_FINISHED;
3004 }
3005
3006 void VIEW3D_OT_view_selected(wmOperatorType *ot)
3007 {
3008         /* identifiers */
3009         ot->name = "View Selected";
3010         ot->description = "Move the view to the selection center";
3011         ot->idname = "VIEW3D_OT_view_selected";
3012
3013         /* api callbacks */
3014         ot->exec = viewselected_exec;
3015         ot->poll = ED_operator_region_view3d_active;
3016
3017         /* flags */
3018         ot->flag = 0;
3019
3020         /* properties */
3021         view3d_operator_properties_common(ot, V3D_OP_PROP_USE_ALL_REGIONS);
3022 }
3023
3024 /** \} */
3025
3026 /* -------------------------------------------------------------------- */
3027 /** \name View Lock Clear Operator
3028  * \{ */
3029
3030 static int view_lock_clear_exec(bContext *C, wmOperator *UNUSED(op))
3031 {
3032         View3D *v3d = CTX_wm_view3d(C);
3033
3034         if (v3d) {
3035                 ED_view3d_lock_clear(v3d);
3036
3037                 WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, v3d);
3038
3039                 return OPERATOR_FINISHED;
3040         }
3041         else {
3042                 return OPERATOR_CANCELLED;
3043         }
3044 }
3045
3046 void VIEW3D_OT_view_lock_clear(wmOperatorType *ot)
3047 {
3048
3049         /* identifiers */
3050         ot->name = "View Lock Clear";
3051         ot->description = "Clear all view locking";
3052         ot->idname = "VIEW3D_OT_view_lock_clear";
3053
3054         /* api callbacks */
3055         ot->exec = view_lock_clear_exec;
3056         ot->poll = ED_operator_region_view3d_active;
3057
3058         /* flags */
3059         ot->flag = 0;
3060 }
3061
3062 /** \} */
3063
3064 /* -------------------------------------------------------------------- */
3065 /** \name View Lock to Active Operator
3066  * \{ */
3067
3068 static int view_lock_to_active_exec(bContext *C, wmOperator *UNUSED(op))
3069 {
3070         View3D *v3d = CTX_wm_view3d(C);
3071         Object *obact = CTX_data_active_object(C);
3072
3073         if (v3d) {
3074                 ED_view3d_lock_clear(v3d);
3075
3076                 v3d->ob_centre = obact; /* can be NULL */
3077
3078                 if (obact && obact->type == OB_ARMATURE) {
3079                         if (obact->mode & OB_MODE_POSE) {
3080                                 Object *obact_eval = DEG_get_evaluated_object(CTX_data_depsgraph(C), obact);
3081                                 bPoseChannel *pcham_act = BKE_pose_channel_active(obact_eval);
3082                                 if (pcham_act) {
3083                                         BLI_strncpy(v3d->ob_centre_bone, pcham_act->name, sizeof(v3d->ob_centre_bone));
3084                                 }
3085                         }
3086                         else {
3087                                 EditBone *ebone_act = ((bArmature *)obact->data)->act_edbone;
3088                                 if (ebone_act) {
3089                                         BLI_strncpy(v3d->ob_centre_bone, ebone_act->name, sizeof(v3d->ob_centre_bone));
3090                                 }
3091                         }
3092                 }
3093
3094                 WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, v3d);
3095
3096                 return OPERATOR_FINISHED;
3097         }
3098         else {
3099                 return OPERATOR_CANCELLED;
3100         }
3101 }
3102
3103 void VIEW3D_OT_view_lock_to_active(wmOperatorType *ot)
3104 {
3105
3106         /* identifiers */
3107         ot->name = "View Lock to Active";
3108         ot->description = "Lock the view to the active object/bone";
3109         ot->idname = "VIEW3D_OT_view_lock_to_active";
3110
3111         /* api callbacks */
3112         ot->exec = view_lock_to_active_exec;
3113         ot->poll = ED_operator_region_view3d_active;
3114
3115         /* flags */
3116         ot->flag = 0;
3117 }
3118
3119 /** \} */
3120
3121 /* -------------------------------------------------------------------- */
3122 /** \name View Center Cursor Operator
3123  * \{ */
3124
3125 static int viewcenter_cursor_exec(bContext *C, wmOperator *op)
3126 {
3127         View3D *v3d = CTX_wm_view3d(C);
3128         RegionView3D *rv3d = CTX_wm_region_view3d(C);
3129         Scene *scene = CTX_data_scene(C);
3130
3131         if (rv3d) {
3132                 ARegion *ar = CTX_wm_region(C);
3133                 const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
3134
3135                 ED_view3d_smooth_view_force_finish(C, v3d, ar);
3136
3137                 /* non camera center */
3138                 float new_ofs[3];
3139                 negate_v3_v3(new_ofs, scene->cursor.location);
3140                 ED_view3d_smooth_view(
3141                         C, v3d, ar, smooth_viewtx,
3142                         &(const V3D_SmoothParams) {.ofs = new_ofs});
3143
3144                 /* smooth view does viewlock RV3D_BOXVIEW copy */
3145         }
3146
3147         return OPERATOR_FINISHED;
3148 }
3149
3150 void VIEW3D_OT_view_center_cursor(wmOperatorType *ot)
3151 {
3152         /* identifiers */
3153         ot->name = "Center View to Cursor";
3154         ot->description = "Center the view so that the cursor is in the middle of the view";
3155         ot->idname = "VIEW3D_OT_view_center_cursor";
3156
3157         /* api callbacks */
3158         ot->exec = viewcenter_cursor_exec;
3159         ot->poll = ED_operator_view3d_active;
3160
3161         /* flags */
3162         ot->flag = 0;
3163 }
3164
3165 /** \} */
3166
3167 /* -------------------------------------------------------------------- */
3168 /** \name View Center Pick Operator
3169  * \{ */
3170
3171 static int viewcenter_pick_invoke(bContext *C, wmOperator *op, const wmEvent *event)
3172 {
3173         View3D *v3d = CTX_wm_view3d(C);
3174         RegionView3D *rv3d = CTX_wm_region_view3d(C);
3175         ARegion *ar = CTX_wm_region(C);
3176
3177         if (rv3d) {
3178                 struct Depsgraph *depsgraph = CTX_data_depsgraph(C);
3179                 float new_ofs[3];
3180                 const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
3181
3182                 ED_view3d_smooth_view_force_finish(C, v3d, ar);
3183
3184                 view3d_operator_needs_opengl(C);
3185
3186                 if (ED_view3d_autodist(depsgraph, ar, v3d, event->mval, new_ofs, false, NULL)) {
3187                         /* pass */
3188                 }
3189                 else {
3190                         /* fallback to simple pan */
3191                         negate_v3_v3(new_ofs, rv3d->ofs);
3192                         ED_view3d_win_to_3d_int(v3d, ar, new_ofs, event->mval, new_ofs);
3193                 }
3194                 negate_v3(new_ofs);
3195                 ED_view3d_smooth_view(
3196                         C, v3d, ar, smooth_viewtx,
3197                         &(const V3D_SmoothParams) {.ofs = new_ofs});
3198         }
3199
3200         return OPERATOR_FINISHED;
3201 }
3202
3203 void VIEW3D_OT_view_center_pick(wmOperatorType *ot)
3204 {
3205         /* identifiers */
3206         ot->name = "Center View to Mouse";
3207         ot->description = "Center the view to the Z-depth position under the mouse cursor";
3208         ot->idname = "VIEW3D_OT_view_center_pick";
3209
3210         /* api callbacks */
3211         ot->invoke = viewcenter_pick_invoke;
3212         ot->poll = ED_operator_view3d_active;
3213
3214         /* flags */
3215         ot->flag = 0;
3216 }
3217
3218 /** \} */
3219
3220 /* -------------------------------------------------------------------- */
3221 /** \name View Camera Center Operator
3222  * \{ */
3223
3224 static int view3d_center_camera_exec(bContext *C, wmOperator *UNUSED(op))
3225 {
3226         Depsgraph *depsgraph = CTX_data_depsgraph(C);
3227         Scene *scene = CTX_data_scene(C);
3228         float xfac, yfac;
3229         float size[2];
3230
3231         View3D *v3d;
3232         ARegion *ar;
3233         RegionView3D *rv3d;
3234
3235         /* no NULL check is needed, poll checks */
3236         ED_view3d_context_user_region(C, &v3d, &ar);
3237         rv3d = ar->regiondata;
3238