3D View: preferences for rotate sensitivity
[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 enum {
84   HAS_TRANSLATE = (1 << 0),
85   HAS_ROTATE = (1 << 0),
86 };
87
88 /* -------------------------------------------------------------------- */
89 /** \name Generic View Operator Properties
90  * \{ */
91
92 enum eV3D_OpPropFlag {
93   V3D_OP_PROP_MOUSE_CO = (1 << 0),
94   V3D_OP_PROP_DELTA = (1 << 1),
95   V3D_OP_PROP_USE_ALL_REGIONS = (1 << 2),
96   V3D_OP_PROP_USE_MOUSE_INIT = (1 << 3),
97 };
98
99 static void view3d_operator_properties_common(wmOperatorType *ot, const enum eV3D_OpPropFlag flag)
100 {
101   if (flag & V3D_OP_PROP_MOUSE_CO) {
102     PropertyRNA *prop;
103     prop = RNA_def_int(ot->srna, "mx", 0, 0, INT_MAX, "Region Position X", "", 0, INT_MAX);
104     RNA_def_property_flag(prop, PROP_HIDDEN);
105     prop = RNA_def_int(ot->srna, "my", 0, 0, INT_MAX, "Region Position Y", "", 0, INT_MAX);
106     RNA_def_property_flag(prop, PROP_HIDDEN);
107   }
108   if (flag & V3D_OP_PROP_DELTA) {
109     RNA_def_int(ot->srna, "delta", 0, INT_MIN, INT_MAX, "Delta", "", INT_MIN, INT_MAX);
110   }
111   if (flag & V3D_OP_PROP_USE_ALL_REGIONS) {
112     PropertyRNA *prop;
113     prop = RNA_def_boolean(
114         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     WM_operator_properties_use_cursor_init(ot);
119   }
120 }
121
122 /** \} */
123
124 /* -------------------------------------------------------------------- */
125 /** \name Generic View Operator Custom-Data
126  * \{ */
127
128 typedef struct ViewOpsData {
129   /** Context pointers (assigned by #viewops_data_alloc). */
130   Main *bmain;
131   Scene *scene;
132   ScrArea *sa;
133   ARegion *ar;
134   View3D *v3d;
135   RegionView3D *rv3d;
136   Depsgraph *depsgraph;
137
138   /** Needed for continuous zoom. */
139   wmTimer *timer;
140
141   /** Viewport state on initialization, don't change afterwards. */
142   struct {
143     float dist;
144     float camzoom;
145     float quat[4];
146     /** #wmEvent.x, y. */
147     int event_xy[2];
148     /** Offset to use when #VIEWOPS_FLAG_USE_MOUSE_INIT is not set.
149      * so we can simulate pressing in the middle of the screen. */
150     int event_xy_offset[2];
151     /** #wmEvent.type that triggered the operator. */
152     int event_type;
153     float ofs[3];
154     /** Initial distance to 'ofs'. */
155     float zfac;
156
157     /** Trackball rotation only. */
158     float trackvec[3];
159     /** Dolly only. */
160     float mousevec[3];
161   } init;
162
163   /** Previous state (previous modal event handled). */
164   struct {
165     int event_xy[2];
166     /** For operators that use time-steps (continuous zoom). */
167     double time;
168   } prev;
169
170   /** Current state. */
171   struct {
172     /** Working copy of #RegionView3D.viewquat, needed for rotation calculation
173      * so we can apply snap to the view-port while keeping the unsnapped rotation
174      * here to use when snap is disabled and for continued calculation. */
175     float viewquat[4];
176   } curr;
177
178   float reverse;
179   bool axis_snap; /* view rotate only */
180
181   /** Use for orbit selection and auto-dist. */
182   float dyn_ofs[3];
183   bool use_dyn_ofs;
184 } ViewOpsData;
185
186 #define TRACKBALLSIZE (1.1f)
187
188 static void calctrackballvec(const rcti *rect, const int event_xy[2], float r_dir[3])
189 {
190   const float radius = TRACKBALLSIZE;
191   const float t = radius / (float)M_SQRT2;
192   const float size[2] = {BLI_rcti_size_x(rect), BLI_rcti_size_y(rect)};
193   /* Aspect correct so dragging in a non-square view doesn't squash the,
194    * so diagonal motion rotates diagonally too. */
195   const float size_min = min_ff(size[0], size[1]);
196   const float aspect[2] = {size_min / size[0], size_min / size[1]};
197
198   /* Normalize x and y. */
199   r_dir[0] = (event_xy[0] - BLI_rcti_cent_x(rect)) / ((size[0] * aspect[0]) / 2.0);
200   r_dir[1] = (event_xy[1] - BLI_rcti_cent_x(rect)) / ((size[1] * aspect[1]) / 2.0);
201   const float d = len_v2(r_dir);
202   if (d < t) {
203     /* Inside sphere. */
204     r_dir[2] = sqrtf(SQUARE(radius) - SQUARE(d));
205   }
206   else {
207     /* On hyperbola. */
208     r_dir[2] = SQUARE(t) / d;
209   }
210 }
211
212 /**
213  * Allocate and fill in context pointers for #ViewOpsData
214  */
215 static void viewops_data_alloc(bContext *C, wmOperator *op)
216 {
217   ViewOpsData *vod = MEM_callocN(sizeof(ViewOpsData), "viewops data");
218
219   /* store data */
220   op->customdata = vod;
221   vod->bmain = CTX_data_main(C);
222   vod->depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
223   vod->scene = CTX_data_scene(C);
224   vod->sa = CTX_wm_area(C);
225   vod->ar = CTX_wm_region(C);
226   vod->v3d = vod->sa->spacedata.first;
227   vod->rv3d = vod->ar->regiondata;
228 }
229
230 void view3d_orbit_apply_dyn_ofs(float r_ofs[3],
231                                 const float ofs_init[3],
232                                 const float viewquat_old[4],
233                                 const float viewquat_new[4],
234                                 const float dyn_ofs[3])
235 {
236   float q[4];
237   invert_qt_qt_normalized(q, viewquat_old);
238   mul_qt_qtqt(q, q, viewquat_new);
239
240   invert_qt_normalized(q);
241
242   sub_v3_v3v3(r_ofs, ofs_init, dyn_ofs);
243   mul_qt_v3(q, r_ofs);
244   add_v3_v3(r_ofs, dyn_ofs);
245 }
246
247 static bool view3d_orbit_calc_center(bContext *C, float r_dyn_ofs[3])
248 {
249   static float lastofs[3] = {0, 0, 0};
250   bool is_set = false;
251
252   const Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
253   Scene *scene = CTX_data_scene(C);
254   ViewLayer *view_layer_eval = DEG_get_evaluated_view_layer(depsgraph);
255   View3D *v3d = CTX_wm_view3d(C);
256   Object *ob_act_eval = OBACT(view_layer_eval);
257   Object *ob_act = DEG_get_original_object(ob_act_eval);
258
259   if (ob_act && (ob_act->mode & OB_MODE_ALL_PAINT) &&
260       /* with weight-paint + pose-mode, fall through to using calculateTransformCenter */
261       ((ob_act->mode & OB_MODE_WEIGHT_PAINT) && BKE_object_pose_armature_get(ob_act)) == 0) {
262     /* in case of sculpting use last average stroke position as a rotation
263      * center, in other cases it's not clear what rotation center shall be
264      * so just rotate around object origin
265      */
266     if (ob_act->mode &
267         (OB_MODE_SCULPT | OB_MODE_TEXTURE_PAINT | OB_MODE_VERTEX_PAINT | OB_MODE_WEIGHT_PAINT)) {
268       float stroke[3];
269       BKE_paint_stroke_get_average(scene, ob_act_eval, stroke);
270       copy_v3_v3(lastofs, stroke);
271     }
272     else {
273       copy_v3_v3(lastofs, ob_act_eval->obmat[3]);
274     }
275     is_set = true;
276   }
277   else if (ob_act && (ob_act->mode & OB_MODE_EDIT) && (ob_act->type == OB_FONT)) {
278     Curve *cu = ob_act_eval->data;
279     EditFont *ef = cu->editfont;
280     int i;
281
282     zero_v3(lastofs);
283     for (i = 0; i < 4; i++) {
284       add_v2_v2(lastofs, ef->textcurs[i]);
285     }
286     mul_v2_fl(lastofs, 1.0f / 4.0f);
287
288     mul_m4_v3(ob_act_eval->obmat, lastofs);
289
290     is_set = true;
291   }
292   else if (ob_act == NULL || ob_act->mode == OB_MODE_OBJECT) {
293     /* object mode use boundbox centers */
294     Base *base_eval;
295     uint tot = 0;
296     float select_center[3];
297
298     zero_v3(select_center);
299     for (base_eval = FIRSTBASE(view_layer_eval); base_eval; base_eval = base_eval->next) {
300       if (BASE_SELECTED(v3d, base_eval)) {
301         /* use the boundbox if we can */
302         Object *ob_eval = base_eval->object;
303
304         if (ob_eval->runtime.bb && !(ob_eval->runtime.bb->flag & BOUNDBOX_DIRTY)) {
305           float cent[3];
306
307           BKE_boundbox_calc_center_aabb(ob_eval->runtime.bb, cent);
308
309           mul_m4_v3(ob_eval->obmat, cent);
310           add_v3_v3(select_center, cent);
311         }
312         else {
313           add_v3_v3(select_center, ob_eval->obmat[3]);
314         }
315         tot++;
316       }
317     }
318     if (tot) {
319       mul_v3_fl(select_center, 1.0f / (float)tot);
320       copy_v3_v3(lastofs, select_center);
321       is_set = true;
322     }
323   }
324   else {
325     /* If there's no selection, lastofs is unmodified and last value since static */
326     is_set = calculateTransformCenter(C, V3D_AROUND_CENTER_MEDIAN, lastofs, NULL);
327   }
328
329   copy_v3_v3(r_dyn_ofs, lastofs);
330
331   return is_set;
332 }
333
334 enum eViewOpsFlag {
335   /** When enabled, rotate around the selection. */
336   VIEWOPS_FLAG_ORBIT_SELECT = (1 << 0),
337   /** When enabled, use the depth under the cursor for navigation. */
338   VIEWOPS_FLAG_DEPTH_NAVIGATE = (1 << 1),
339   /**
340    * When enabled run #ED_view3d_persp_ensure this may switch out of
341    * camera view when orbiting or switch from ortho to perspective when auto-persp is enabled.
342    * Some operations don't require this (view zoom/pan or ndof where subtle rotation is common
343    * so we don't want it to trigger auto-perspective). */
344   VIEWOPS_FLAG_PERSP_ENSURE = (1 << 2),
345   /** When set, ignore any options that depend on initial cursor location. */
346   VIEWOPS_FLAG_USE_MOUSE_INIT = (1 << 3),
347 };
348
349 static enum eViewOpsFlag viewops_flag_from_args(bool use_select, bool use_depth)
350 {
351   enum eViewOpsFlag flag = 0;
352   if (use_select) {
353     flag |= VIEWOPS_FLAG_ORBIT_SELECT;
354   }
355   if (use_depth) {
356     flag |= VIEWOPS_FLAG_DEPTH_NAVIGATE;
357   }
358
359   return flag;
360 }
361
362 static enum eViewOpsFlag viewops_flag_from_prefs(void)
363 {
364   return viewops_flag_from_args((U.uiflag & USER_ORBIT_SELECTION) != 0,
365                                 (U.uiflag & USER_DEPTH_NAVIGATE) != 0);
366 }
367
368 /**
369  * Calculate the values for #ViewOpsData
370  */
371 static void viewops_data_create(bContext *C,
372                                 wmOperator *op,
373                                 const wmEvent *event,
374                                 enum eViewOpsFlag viewops_flag)
375 {
376   Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
377   ViewOpsData *vod = op->customdata;
378   RegionView3D *rv3d = vod->rv3d;
379
380   /* Could do this more nicely. */
381   if ((viewops_flag & VIEWOPS_FLAG_USE_MOUSE_INIT) == 0) {
382     viewops_flag &= ~VIEWOPS_FLAG_DEPTH_NAVIGATE;
383   }
384
385   /* we need the depth info before changing any viewport options */
386   if (viewops_flag & VIEWOPS_FLAG_DEPTH_NAVIGATE) {
387     float fallback_depth_pt[3];
388
389     view3d_operator_needs_opengl(C); /* needed for zbuf drawing */
390
391     negate_v3_v3(fallback_depth_pt, rv3d->ofs);
392
393     vod->use_dyn_ofs = ED_view3d_autodist(
394         depsgraph, vod->ar, vod->v3d, event->mval, vod->dyn_ofs, true, fallback_depth_pt);
395   }
396   else {
397     vod->use_dyn_ofs = false;
398   }
399
400   if (viewops_flag & VIEWOPS_FLAG_PERSP_ENSURE) {
401     if (ED_view3d_persp_ensure(depsgraph, vod->v3d, vod->ar)) {
402       /* If we're switching from camera view to the perspective one,
403        * need to tag viewport update, so camera vuew and borders
404        * are properly updated.
405        */
406       ED_region_tag_redraw(vod->ar);
407     }
408   }
409
410   /* set the view from the camera, if view locking is enabled.
411    * we may want to make this optional but for now its needed always */
412   ED_view3d_camera_lock_init(depsgraph, vod->v3d, vod->rv3d);
413
414   vod->init.dist = rv3d->dist;
415   vod->init.camzoom = rv3d->camzoom;
416   copy_qt_qt(vod->init.quat, rv3d->viewquat);
417   vod->init.event_xy[0] = vod->prev.event_xy[0] = event->x;
418   vod->init.event_xy[1] = vod->prev.event_xy[1] = event->y;
419
420   if (viewops_flag & VIEWOPS_FLAG_USE_MOUSE_INIT) {
421     vod->init.event_xy_offset[0] = 0;
422     vod->init.event_xy_offset[1] = 0;
423   }
424   else {
425     /* Simulate the event starting in the middle of the region. */
426     vod->init.event_xy_offset[0] = BLI_rcti_cent_x(&vod->ar->winrct) - event->x;
427     vod->init.event_xy_offset[1] = BLI_rcti_cent_y(&vod->ar->winrct) - event->y;
428   }
429
430   vod->init.event_type = event->type;
431   copy_v3_v3(vod->init.ofs, rv3d->ofs);
432
433   copy_qt_qt(vod->curr.viewquat, rv3d->viewquat);
434
435   if (viewops_flag & VIEWOPS_FLAG_ORBIT_SELECT) {
436     float ofs[3];
437     if (view3d_orbit_calc_center(C, ofs) || (vod->use_dyn_ofs == false)) {
438       vod->use_dyn_ofs = true;
439       negate_v3_v3(vod->dyn_ofs, ofs);
440       viewops_flag &= ~VIEWOPS_FLAG_DEPTH_NAVIGATE;
441     }
442   }
443
444   if (viewops_flag & VIEWOPS_FLAG_DEPTH_NAVIGATE) {
445     if (vod->use_dyn_ofs) {
446       if (rv3d->is_persp) {
447         float my_origin[3]; /* original G.vd->ofs */
448         float my_pivot[3];  /* view */
449         float dvec[3];
450
451         /* locals for dist correction */
452         float mat[3][3];
453         float upvec[3];
454
455         negate_v3_v3(my_origin, rv3d->ofs); /* ofs is flipped */
456
457         /* Set the dist value to be the distance from this 3d point this means youll
458          * always be able to zoom into it and panning wont go bad when dist was zero */
459
460         /* remove dist value */
461         upvec[0] = upvec[1] = 0;
462         upvec[2] = rv3d->dist;
463         copy_m3_m4(mat, rv3d->viewinv);
464
465         mul_m3_v3(mat, upvec);
466         sub_v3_v3v3(my_pivot, rv3d->ofs, upvec);
467         negate_v3(my_pivot); /* ofs is flipped */
468
469         /* find a new ofs value that is along the view axis
470          * (rather than the mouse location) */
471         closest_to_line_v3(dvec, vod->dyn_ofs, my_pivot, my_origin);
472         vod->init.dist = rv3d->dist = len_v3v3(my_pivot, dvec);
473
474         negate_v3_v3(rv3d->ofs, dvec);
475       }
476       else {
477         const float mval_ar_mid[2] = {(float)vod->ar->winx / 2.0f, (float)vod->ar->winy / 2.0f};
478
479         ED_view3d_win_to_3d(vod->v3d, vod->ar, vod->dyn_ofs, mval_ar_mid, rv3d->ofs);
480         negate_v3(rv3d->ofs);
481       }
482       negate_v3(vod->dyn_ofs);
483       copy_v3_v3(vod->init.ofs, rv3d->ofs);
484     }
485   }
486
487   /* For dolly */
488   ED_view3d_win_to_vector(vod->ar, (const float[2]){UNPACK2(event->mval)}, vod->init.mousevec);
489
490   {
491     const int event_xy_offset[2] = {
492         event->x + vod->init.event_xy_offset[0],
493         event->y + vod->init.event_xy_offset[1],
494     };
495     /* For rotation with trackball rotation. */
496     calctrackballvec(&vod->ar->winrct, event_xy_offset, vod->init.trackvec);
497   }
498
499   {
500     float tvec[3];
501     negate_v3_v3(tvec, rv3d->ofs);
502     vod->init.zfac = ED_view3d_calc_zfac(rv3d, tvec, NULL);
503   }
504
505   vod->reverse = 1.0f;
506   if (rv3d->persmat[2][1] < 0.0f) {
507     vod->reverse = -1.0f;
508   }
509
510   rv3d->rflag |= RV3D_NAVIGATING;
511 }
512
513 static void viewops_data_free(bContext *C, wmOperator *op)
514 {
515   ARegion *ar;
516 #if 0
517   Paint *p = BKE_paint_get_active_from_context(C);
518 #endif
519   if (op->customdata) {
520     ViewOpsData *vod = op->customdata;
521     ar = vod->ar;
522     vod->rv3d->rflag &= ~RV3D_NAVIGATING;
523
524     if (vod->timer) {
525       WM_event_remove_timer(CTX_wm_manager(C), vod->timer->win, vod->timer);
526     }
527
528     MEM_freeN(vod);
529     op->customdata = NULL;
530   }
531   else {
532     ar = CTX_wm_region(C);
533   }
534
535 #if 0
536   if (p && (p->flags & PAINT_FAST_NAVIGATE))
537 #endif
538   {
539     ED_region_tag_redraw(ar);
540   }
541 }
542
543 /** \} */
544
545 /* -------------------------------------------------------------------- */
546 /** \name View Rotate Operator
547  * \{ */
548
549 enum {
550   VIEW_PASS = 0,
551   VIEW_APPLY,
552   VIEW_CONFIRM,
553 };
554
555 /* NOTE: these defines are saved in keymap files, do not change values but just add new ones */
556 enum {
557   VIEW_MODAL_CONFIRM = 1, /* used for all view operations */
558   VIEWROT_MODAL_AXIS_SNAP_ENABLE = 2,
559   VIEWROT_MODAL_AXIS_SNAP_DISABLE = 3,
560   VIEWROT_MODAL_SWITCH_ZOOM = 4,
561   VIEWROT_MODAL_SWITCH_MOVE = 5,
562   VIEWROT_MODAL_SWITCH_ROTATE = 6,
563 };
564
565 /* called in transform_ops.c, on each regeneration of keymaps  */
566 void viewrotate_modal_keymap(wmKeyConfig *keyconf)
567 {
568   static const EnumPropertyItem modal_items[] = {
569       {VIEW_MODAL_CONFIRM, "CONFIRM", 0, "Confirm", ""},
570
571       {VIEWROT_MODAL_AXIS_SNAP_ENABLE, "AXIS_SNAP_ENABLE", 0, "Axis Snap", ""},
572       {VIEWROT_MODAL_AXIS_SNAP_DISABLE, "AXIS_SNAP_DISABLE", 0, "Axis Snap (Off)", ""},
573
574       {VIEWROT_MODAL_SWITCH_ZOOM, "SWITCH_TO_ZOOM", 0, "Switch to Zoom"},
575       {VIEWROT_MODAL_SWITCH_MOVE, "SWITCH_TO_MOVE", 0, "Switch to Move"},
576
577       {0, NULL, 0, NULL, NULL},
578   };
579
580   wmKeyMap *keymap = WM_modalkeymap_get(keyconf, "View3D Rotate Modal");
581
582   /* this function is called for each spacetype, only needs to add map once */
583   if (keymap && keymap->modal_items) {
584     return;
585   }
586
587   keymap = WM_modalkeymap_add(keyconf, "View3D Rotate Modal", modal_items);
588
589   /* disabled mode switching for now, can re-implement better, later on */
590 #if 0
591   WM_modalkeymap_add_item(keymap, LEFTMOUSE, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ZOOM);
592   WM_modalkeymap_add_item(keymap, LEFTCTRLKEY, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ZOOM);
593   WM_modalkeymap_add_item(keymap, LEFTSHIFTKEY, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_MOVE);
594 #endif
595
596   /* assign map to operators */
597   WM_modalkeymap_assign(keymap, "VIEW3D_OT_rotate");
598 }
599
600 static void viewrotate_apply_dyn_ofs(ViewOpsData *vod, const float viewquat_new[4])
601 {
602   if (vod->use_dyn_ofs) {
603     RegionView3D *rv3d = vod->rv3d;
604     view3d_orbit_apply_dyn_ofs(
605         rv3d->ofs, vod->init.ofs, vod->init.quat, viewquat_new, vod->dyn_ofs);
606   }
607 }
608
609 static void viewrotate_apply_snap(ViewOpsData *vod)
610 {
611   const float axis_limit = DEG2RADF(45 / 3);
612
613   RegionView3D *rv3d = vod->rv3d;
614
615   float viewquat_inv[4];
616   float zaxis[3] = {0, 0, 1};
617   float zaxis_best[3];
618   int x, y, z;
619   bool found = false;
620
621   invert_qt_qt_normalized(viewquat_inv, vod->curr.viewquat);
622
623   mul_qt_v3(viewquat_inv, zaxis);
624   normalize_v3(zaxis);
625
626   for (x = -1; x < 2; x++) {
627     for (y = -1; y < 2; y++) {
628       for (z = -1; z < 2; z++) {
629         if (x || y || z) {
630           float zaxis_test[3] = {x, y, z};
631
632           normalize_v3(zaxis_test);
633
634           if (angle_normalized_v3v3(zaxis_test, zaxis) < axis_limit) {
635             copy_v3_v3(zaxis_best, zaxis_test);
636             found = true;
637           }
638         }
639       }
640     }
641   }
642
643   if (found) {
644
645     /* find the best roll */
646     float quat_roll[4], quat_final[4], quat_best[4], quat_snap[4];
647     float viewquat_align[4];     /* viewquat aligned to zaxis_best */
648     float viewquat_align_inv[4]; /* viewquat aligned to zaxis_best */
649     float best_angle = axis_limit;
650     int j;
651
652     /* viewquat_align is the original viewquat aligned to the snapped axis
653      * for testing roll */
654     rotation_between_vecs_to_quat(viewquat_align, zaxis_best, zaxis);
655     normalize_qt(viewquat_align);
656     mul_qt_qtqt(viewquat_align, vod->curr.viewquat, viewquat_align);
657     normalize_qt(viewquat_align);
658     invert_qt_qt_normalized(viewquat_align_inv, viewquat_align);
659
660     vec_to_quat(quat_snap, zaxis_best, OB_NEGZ, OB_POSY);
661     normalize_qt(quat_snap);
662     invert_qt_normalized(quat_snap);
663
664     /* check if we can find the roll */
665     found = false;
666
667     /* find best roll */
668     for (j = 0; j < 8; j++) {
669       float angle;
670       float xaxis1[3] = {1, 0, 0};
671       float xaxis2[3] = {1, 0, 0};
672       float quat_final_inv[4];
673
674       axis_angle_to_quat(quat_roll, zaxis_best, (float)j * DEG2RADF(45.0f));
675       normalize_qt(quat_roll);
676
677       mul_qt_qtqt(quat_final, quat_snap, quat_roll);
678       normalize_qt(quat_final);
679
680       /* compare 2 vector angles to find the least roll */
681       invert_qt_qt_normalized(quat_final_inv, quat_final);
682       mul_qt_v3(viewquat_align_inv, xaxis1);
683       mul_qt_v3(quat_final_inv, xaxis2);
684       angle = angle_v3v3(xaxis1, xaxis2);
685
686       if (angle <= best_angle) {
687         found = true;
688         best_angle = angle;
689         copy_qt_qt(quat_best, quat_final);
690       }
691     }
692
693     if (found) {
694       /* lock 'quat_best' to an axis view if we can */
695       rv3d->view = ED_view3d_quat_to_axis_view(quat_best, 0.01f);
696       if (rv3d->view != RV3D_VIEW_USER) {
697         ED_view3d_quat_from_axis_view(rv3d->view, quat_best);
698       }
699     }
700     else {
701       copy_qt_qt(quat_best, viewquat_align);
702     }
703
704     copy_qt_qt(rv3d->viewquat, quat_best);
705
706     viewrotate_apply_dyn_ofs(vod, rv3d->viewquat);
707   }
708 }
709
710 static void viewrotate_apply(ViewOpsData *vod, const int event_xy[2])
711 {
712   RegionView3D *rv3d = vod->rv3d;
713
714   rv3d->view = RV3D_VIEW_USER; /* need to reset every time because of view snapping */
715
716   if (U.flag & USER_TRACKBALL) {
717     float axis[3], q1[4], dvec[3], newvec[3];
718     float angle;
719
720     {
721       const int event_xy_offset[2] = {
722           event_xy[0] + vod->init.event_xy_offset[0],
723           event_xy[1] + vod->init.event_xy_offset[1],
724       };
725       calctrackballvec(&vod->ar->winrct, event_xy_offset, newvec);
726     }
727
728     sub_v3_v3v3(dvec, newvec, vod->init.trackvec);
729
730     angle = (len_v3(dvec) / (2.0f * TRACKBALLSIZE)) * (float)M_PI;
731
732     /* Before applying the sensitivity this is rotating 1:1,
733      * where the cursor would match the surface of a sphere in the view. */
734     angle *= U.view_rotate_sensitivity_trackball;
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     /* Radians per-pixel. */
759     const float sensitivity = U.view_rotate_sensitivity_turntable / U.pixelsize;
760
761     /* Get the 3x3 matrix and its inverse from the quaternion */
762     quat_to_mat3(m, vod->curr.viewquat);
763     invert_m3_m3(m_inv, m);
764
765     /* avoid gimble lock */
766 #if 1
767     if (len_squared_v3v3(zvec_global, m_inv[2]) > 0.001f) {
768       float fac;
769       cross_v3_v3v3(xaxis, zvec_global, m_inv[2]);
770       if (dot_v3v3(xaxis, m_inv[0]) < 0) {
771         negate_v3(xaxis);
772       }
773       fac = angle_normalized_v3v3(zvec_global, m_inv[2]) / (float)M_PI;
774       fac = fabsf(fac - 0.5f) * 2;
775       fac = fac * fac;
776       interp_v3_v3v3(xaxis, xaxis, m_inv[0], fac);
777     }
778     else {
779       copy_v3_v3(xaxis, m_inv[0]);
780     }
781 #else
782     copy_v3_v3(xaxis, m_inv[0]);
783 #endif
784
785     /* Determine the direction of the x vector (for rotating up and down) */
786     /* This can likely be computed directly from the quaternion. */
787
788     /* Perform the up/down rotation */
789     axis_angle_to_quat(quat_local_x, xaxis, sensitivity * -(event_xy[1] - vod->prev.event_xy[1]));
790     mul_qt_qtqt(quat_local_x, vod->curr.viewquat, quat_local_x);
791
792     /* Perform the orbital rotation */
793     axis_angle_to_quat_single(
794         quat_global_z, 'Z', sensitivity * vod->reverse * (event_xy[0] - vod->prev.event_xy[0]));
795     mul_qt_qtqt(vod->curr.viewquat, quat_local_x, quat_global_z);
796
797     viewrotate_apply_dyn_ofs(vod, vod->curr.viewquat);
798   }
799
800   /* avoid precision loss over time */
801   normalize_qt(vod->curr.viewquat);
802
803   /* use a working copy so view rotation locking doesn't overwrite the locked
804    * rotation back into the view we calculate with */
805   copy_qt_qt(rv3d->viewquat, vod->curr.viewquat);
806
807   /* check for view snap,
808    * note: don't apply snap to vod->viewquat so the view wont jam up */
809   if (vod->axis_snap) {
810     viewrotate_apply_snap(vod);
811   }
812   vod->prev.event_xy[0] = event_xy[0];
813   vod->prev.event_xy[1] = event_xy[1];
814
815   ED_view3d_camera_lock_sync(vod->depsgraph, vod->v3d, rv3d);
816
817   ED_region_tag_redraw(vod->ar);
818 }
819
820 static int viewrotate_modal(bContext *C, wmOperator *op, const wmEvent *event)
821 {
822   ViewOpsData *vod = op->customdata;
823   short event_code = VIEW_PASS;
824   bool use_autokey = false;
825   int ret = OPERATOR_RUNNING_MODAL;
826
827   /* execute the events */
828   if (event->type == MOUSEMOVE) {
829     event_code = VIEW_APPLY;
830   }
831   else if (event->type == EVT_MODAL_MAP) {
832     switch (event->val) {
833       case VIEW_MODAL_CONFIRM:
834         event_code = VIEW_CONFIRM;
835         break;
836       case VIEWROT_MODAL_AXIS_SNAP_ENABLE:
837         vod->axis_snap = true;
838         event_code = VIEW_APPLY;
839         break;
840       case VIEWROT_MODAL_AXIS_SNAP_DISABLE:
841         vod->axis_snap = false;
842         event_code = VIEW_APPLY;
843         break;
844       case VIEWROT_MODAL_SWITCH_ZOOM:
845         WM_operator_name_call(C, "VIEW3D_OT_zoom", WM_OP_INVOKE_DEFAULT, NULL);
846         event_code = VIEW_CONFIRM;
847         break;
848       case VIEWROT_MODAL_SWITCH_MOVE:
849         WM_operator_name_call(C, "VIEW3D_OT_move", WM_OP_INVOKE_DEFAULT, NULL);
850         event_code = VIEW_CONFIRM;
851         break;
852     }
853   }
854   else if (event->type == vod->init.event_type && event->val == KM_RELEASE) {
855     event_code = VIEW_CONFIRM;
856   }
857
858   if (event_code == VIEW_APPLY) {
859     viewrotate_apply(vod, &event->x);
860     if (ED_screen_animation_playing(CTX_wm_manager(C))) {
861       use_autokey = true;
862     }
863   }
864   else if (event_code == VIEW_CONFIRM) {
865     ED_view3d_depth_tag_update(vod->rv3d);
866     use_autokey = true;
867     ret = OPERATOR_FINISHED;
868   }
869
870   if (use_autokey) {
871     ED_view3d_camera_lock_autokey(vod->v3d, vod->rv3d, C, true, true);
872   }
873
874   if (ret & OPERATOR_FINISHED) {
875     viewops_data_free(C, op);
876   }
877
878   return ret;
879 }
880
881 static int viewrotate_invoke(bContext *C, wmOperator *op, const wmEvent *event)
882 {
883   ViewOpsData *vod;
884
885   const bool use_cursor_init = RNA_boolean_get(op->ptr, "use_cursor_init");
886
887   /* makes op->customdata */
888   viewops_data_alloc(C, op);
889   vod = op->customdata;
890
891   /* poll should check but in some cases fails, see poll func for details */
892   if (vod->rv3d->viewlock & RV3D_LOCKED) {
893     viewops_data_free(C, op);
894     return OPERATOR_PASS_THROUGH;
895   }
896
897   ED_view3d_smooth_view_force_finish(C, vod->v3d, vod->ar);
898
899   viewops_data_create(C,
900                       op,
901                       event,
902                       viewops_flag_from_prefs() | VIEWOPS_FLAG_PERSP_ENSURE |
903                           (use_cursor_init ? VIEWOPS_FLAG_USE_MOUSE_INIT : 0));
904
905   if (ELEM(event->type, MOUSEPAN, MOUSEROTATE)) {
906     /* Rotate direction we keep always same */
907     int event_xy[2];
908
909     if (event->type == MOUSEPAN) {
910       if (U.uiflag2 & USER_TRACKPAD_NATURAL) {
911         event_xy[0] = 2 * event->x - event->prevx;
912         event_xy[1] = 2 * event->y - event->prevy;
913       }
914       else {
915         event_xy[0] = event->prevx;
916         event_xy[1] = event->prevy;
917       }
918     }
919     else {
920       /* MOUSEROTATE performs orbital rotation, so y axis delta is set to 0 */
921       event_xy[0] = event->prevx;
922       event_xy[1] = event->y;
923     }
924
925     viewrotate_apply(vod, event_xy);
926     ED_view3d_depth_tag_update(vod->rv3d);
927
928     viewops_data_free(C, op);
929
930     return OPERATOR_FINISHED;
931   }
932   else {
933     /* add temp handler */
934     WM_event_add_modal_handler(C, op);
935
936     return OPERATOR_RUNNING_MODAL;
937   }
938 }
939
940 /* test for unlocked camera view in quad view */
941 static bool view3d_camera_user_poll(bContext *C)
942 {
943   View3D *v3d;
944   ARegion *ar;
945
946   if (ED_view3d_context_user_region(C, &v3d, &ar)) {
947     RegionView3D *rv3d = ar->regiondata;
948     if (rv3d->persp == RV3D_CAMOB) {
949       return 1;
950     }
951   }
952
953   return 0;
954 }
955
956 static bool view3d_lock_poll(bContext *C)
957 {
958   View3D *v3d = CTX_wm_view3d(C);
959   if (v3d) {
960     RegionView3D *rv3d = CTX_wm_region_view3d(C);
961     if (rv3d) {
962       return ED_view3d_offset_lock_check(v3d, rv3d);
963     }
964   }
965   return false;
966 }
967
968 static void viewrotate_cancel(bContext *C, wmOperator *op)
969 {
970   viewops_data_free(C, op);
971 }
972
973 void VIEW3D_OT_rotate(wmOperatorType *ot)
974 {
975   /* identifiers */
976   ot->name = "Rotate View";
977   ot->description = "Rotate the view";
978   ot->idname = "VIEW3D_OT_rotate";
979
980   /* api callbacks */
981   ot->invoke = viewrotate_invoke;
982   ot->modal = viewrotate_modal;
983   ot->poll = ED_operator_region_view3d_active;
984   ot->cancel = viewrotate_cancel;
985
986   /* flags */
987   ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_XY;
988
989   view3d_operator_properties_common(ot, V3D_OP_PROP_USE_MOUSE_INIT);
990 }
991
992 /** \} */
993
994 /* -------------------------------------------------------------------- */
995 /** \name NDOF Utility Functions
996  * \{ */
997
998 #ifdef WITH_INPUT_NDOF
999 #  define NDOF_HAS_TRANSLATE ((!ED_view3d_offset_lock_check(v3d, rv3d)) && !is_zero_v3(ndof->tvec))
1000 #  define NDOF_HAS_ROTATE (((rv3d->viewlock & RV3D_LOCKED) == 0) && !is_zero_v3(ndof->rvec))
1001
1002 /**
1003  * \param depth_pt: A point to calculate the depth (in perspective mode)
1004  */
1005 static float view3d_ndof_pan_speed_calc_ex(RegionView3D *rv3d, const float depth_pt[3])
1006 {
1007   float speed = rv3d->pixsize * NDOF_PIXELS_PER_SECOND;
1008
1009   if (rv3d->is_persp) {
1010     speed *= ED_view3d_calc_zfac(rv3d, depth_pt, NULL);
1011   }
1012
1013   return speed;
1014 }
1015
1016 static float view3d_ndof_pan_speed_calc_from_dist(RegionView3D *rv3d, const float dist)
1017 {
1018   float viewinv[4];
1019   float tvec[3];
1020
1021   BLI_assert(dist >= 0.0f);
1022
1023   copy_v3_fl3(tvec, 0.0f, 0.0f, dist);
1024   /* rv3d->viewinv isn't always valid */
1025 #  if 0
1026   mul_mat3_m4_v3(rv3d->viewinv, tvec);
1027 #  else
1028   invert_qt_qt_normalized(viewinv, rv3d->viewquat);
1029   mul_qt_v3(viewinv, tvec);
1030 #  endif
1031
1032   return view3d_ndof_pan_speed_calc_ex(rv3d, tvec);
1033 }
1034
1035 static float view3d_ndof_pan_speed_calc(RegionView3D *rv3d)
1036 {
1037   float tvec[3];
1038   negate_v3_v3(tvec, rv3d->ofs);
1039
1040   return view3d_ndof_pan_speed_calc_ex(rv3d, tvec);
1041 }
1042
1043 /**
1044  * Zoom and pan in the same function since sometimes zoom is interpreted as dolly (pan forward).
1045  *
1046  * \param has_zoom: zoom, otherwise dolly,
1047  * often `!rv3d->is_persp` since it doesn't make sense to dolly in ortho.
1048  */
1049 static void view3d_ndof_pan_zoom(const struct wmNDOFMotionData *ndof,
1050                                  ScrArea *sa,
1051                                  ARegion *ar,
1052                                  const bool has_translate,
1053                                  const bool has_zoom)
1054 {
1055   RegionView3D *rv3d = ar->regiondata;
1056   float view_inv[4];
1057   float pan_vec[3];
1058
1059   if (has_translate == false && has_zoom == false) {
1060     return;
1061   }
1062
1063   WM_event_ndof_pan_get(ndof, pan_vec, false);
1064
1065   if (has_zoom) {
1066     /* zoom with Z */
1067
1068     /* Zoom!
1069      * velocity should be proportional to the linear velocity attained by rotational motion
1070      * of same strength [got that?] 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 static void view3d_ndof_orbit(const struct wmNDOFMotionData *ndof,
1114                               ScrArea *sa,
1115                               ARegion *ar,
1116                               ViewOpsData *vod,
1117                               const bool apply_dyn_ofs)
1118 {
1119   View3D *v3d = sa->spacedata.first;
1120   RegionView3D *rv3d = ar->regiondata;
1121
1122   float view_inv[4];
1123
1124   BLI_assert((rv3d->viewlock & RV3D_LOCKED) == 0);
1125
1126   ED_view3d_persp_ensure(vod->depsgraph, v3d, ar);
1127
1128   rv3d->view = RV3D_VIEW_USER;
1129
1130   invert_qt_qt_normalized(view_inv, rv3d->viewquat);
1131
1132   if (U.ndof_flag & NDOF_TURNTABLE) {
1133     float rot[3];
1134
1135     /* turntable view code by John Aughey, adapted for 3D mouse by [mce] */
1136     float angle, quat[4];
1137     float xvec[3] = {1, 0, 0};
1138
1139     /* only use XY, ignore Z */
1140     WM_event_ndof_rotate_get(ndof, rot);
1141
1142     /* Determine the direction of the x vector (for rotating up and down) */
1143     mul_qt_v3(view_inv, xvec);
1144
1145     /* Perform the up/down rotation */
1146     angle = ndof->dt * rot[0];
1147     axis_angle_to_quat(quat, xvec, angle);
1148     mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, quat);
1149
1150     /* Perform the orbital rotation */
1151     angle = ndof->dt * rot[1];
1152
1153     /* update the onscreen doo-dad */
1154     rv3d->rot_angle = angle;
1155     rv3d->rot_axis[0] = 0;
1156     rv3d->rot_axis[1] = 0;
1157     rv3d->rot_axis[2] = 1;
1158
1159     axis_angle_to_quat_single(quat, 'Z', angle);
1160     mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, quat);
1161   }
1162   else {
1163     float quat[4];
1164     float axis[3];
1165     float angle = WM_event_ndof_to_axis_angle(ndof, axis);
1166
1167     /* transform rotation axis from view to world coordinates */
1168     mul_qt_v3(view_inv, axis);
1169
1170     /* update the onscreen doo-dad */
1171     rv3d->rot_angle = angle;
1172     copy_v3_v3(rv3d->rot_axis, axis);
1173
1174     axis_angle_to_quat(quat, axis, angle);
1175
1176     /* apply rotation */
1177     mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, quat);
1178   }
1179
1180   if (apply_dyn_ofs) {
1181     viewrotate_apply_dyn_ofs(vod, rv3d->viewquat);
1182   }
1183 }
1184
1185 /**
1186  * Called from both fly mode and walk mode,
1187  */
1188 void view3d_ndof_fly(const wmNDOFMotionData *ndof,
1189                      View3D *v3d,
1190                      RegionView3D *rv3d,
1191                      const bool use_precision,
1192                      const short protectflag,
1193                      bool *r_has_translate,
1194                      bool *r_has_rotate)
1195 {
1196   bool has_translate = NDOF_HAS_TRANSLATE;
1197   bool has_rotate = NDOF_HAS_ROTATE;
1198
1199   float view_inv[4];
1200   invert_qt_qt_normalized(view_inv, rv3d->viewquat);
1201
1202   rv3d->rot_angle = 0.0f; /* disable onscreen rotation doo-dad */
1203
1204   if (has_translate) {
1205     /* ignore real 'dist' since fly has its own speed settings,
1206      * also its overwritten at this point. */
1207     float speed = view3d_ndof_pan_speed_calc_from_dist(rv3d, 1.0f);
1208     float trans[3], trans_orig_y;
1209
1210     if (use_precision) {
1211       speed *= 0.2f;
1212     }
1213
1214     WM_event_ndof_pan_get(ndof, trans, false);
1215     mul_v3_fl(trans, speed * ndof->dt);
1216     trans_orig_y = trans[1];
1217
1218     if (U.ndof_flag & NDOF_FLY_HELICOPTER) {
1219       trans[1] = 0.0f;
1220     }
1221
1222     /* transform motion from view to world coordinates */
1223     mul_qt_v3(view_inv, trans);
1224
1225     if (U.ndof_flag & NDOF_FLY_HELICOPTER) {
1226       /* replace world z component with device y (yes it makes sense) */
1227       trans[2] = trans_orig_y;
1228     }
1229
1230     if (rv3d->persp == RV3D_CAMOB) {
1231       /* respect camera position locks */
1232       if (protectflag & OB_LOCK_LOCX) {
1233         trans[0] = 0.0f;
1234       }
1235       if (protectflag & OB_LOCK_LOCY) {
1236         trans[1] = 0.0f;
1237       }
1238       if (protectflag & OB_LOCK_LOCZ) {
1239         trans[2] = 0.0f;
1240       }
1241     }
1242
1243     if (!is_zero_v3(trans)) {
1244       /* move center of view opposite of hand motion
1245        * (this is camera mode, not object mode) */
1246       sub_v3_v3(rv3d->ofs, trans);
1247       has_translate = true;
1248     }
1249     else {
1250       has_translate = false;
1251     }
1252   }
1253
1254   if (has_rotate) {
1255     const float turn_sensitivity = 1.0f;
1256
1257     float rotation[4];
1258     float axis[3];
1259     float angle = turn_sensitivity * WM_event_ndof_to_axis_angle(ndof, axis);
1260
1261     if (fabsf(angle) > 0.0001f) {
1262       has_rotate = true;
1263
1264       if (use_precision) {
1265         angle *= 0.2f;
1266       }
1267
1268       /* transform rotation axis from view to world coordinates */
1269       mul_qt_v3(view_inv, axis);
1270
1271       /* apply rotation to view */
1272       axis_angle_to_quat(rotation, axis, angle);
1273       mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, rotation);
1274
1275       if (U.ndof_flag & NDOF_LOCK_HORIZON) {
1276         /* force an upright viewpoint
1277          * TODO: make this less... sudden */
1278         float view_horizon[3] = {1.0f, 0.0f, 0.0f};    /* view +x */
1279         float view_direction[3] = {0.0f, 0.0f, -1.0f}; /* view -z (into screen) */
1280
1281         /* find new inverse since viewquat has changed */
1282         invert_qt_qt_normalized(view_inv, rv3d->viewquat);
1283         /* could apply reverse rotation to existing view_inv to save a few cycles */
1284
1285         /* transform view vectors to world coordinates */
1286         mul_qt_v3(view_inv, view_horizon);
1287         mul_qt_v3(view_inv, view_direction);
1288
1289         /* find difference between view & world horizons
1290          * true horizon lives in world xy plane, so look only at difference in z */
1291         angle = -asinf(view_horizon[2]);
1292
1293         /* rotate view so view horizon = world horizon */
1294         axis_angle_to_quat(rotation, view_direction, angle);
1295         mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, rotation);
1296       }
1297
1298       rv3d->view = RV3D_VIEW_USER;
1299     }
1300     else {
1301       has_rotate = false;
1302     }
1303   }
1304
1305   *r_has_translate = has_translate;
1306   *r_has_rotate = has_rotate;
1307 }
1308
1309 /** \} */
1310
1311 /* -------------------------------------------------------------------- */
1312 /** \name NDOF Orbit/Translate Operator
1313  * \{ */
1314
1315 static int ndof_orbit_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1316 {
1317   if (event->type != NDOF_MOTION) {
1318     return OPERATOR_CANCELLED;
1319   }
1320
1321   const Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
1322   ViewOpsData *vod;
1323   View3D *v3d;
1324   RegionView3D *rv3d;
1325   char xform_flag = 0;
1326
1327   const wmNDOFMotionData *ndof = event->customdata;
1328
1329   viewops_data_alloc(C, op);
1330   viewops_data_create(
1331       C, op, event, viewops_flag_from_args((U.uiflag & USER_ORBIT_SELECTION) != 0, false));
1332   vod = op->customdata;
1333
1334   ED_view3d_smooth_view_force_finish(C, vod->v3d, vod->ar);
1335
1336   v3d = vod->v3d;
1337   rv3d = vod->rv3d;
1338
1339   /* off by default, until changed later this function */
1340   rv3d->rot_angle = 0.0f;
1341
1342   ED_view3d_camera_lock_init_ex(depsgraph, v3d, rv3d, false);
1343
1344   if (ndof->progress != P_FINISHING) {
1345     const bool has_rotation = NDOF_HAS_ROTATE;
1346     /* if we can't rotate, fallback to translate (locked axis views) */
1347     const bool has_translate = NDOF_HAS_TRANSLATE && (rv3d->viewlock & RV3D_LOCKED);
1348     const bool has_zoom = (ndof->tvec[2] != 0.0f) && !rv3d->is_persp;
1349
1350     if (has_translate || has_zoom) {
1351       view3d_ndof_pan_zoom(ndof, vod->sa, vod->ar, has_translate, has_zoom);
1352       xform_flag |= HAS_TRANSLATE;
1353     }
1354
1355     if (has_rotation) {
1356       view3d_ndof_orbit(ndof, vod->sa, vod->ar, vod, true);
1357       xform_flag |= HAS_ROTATE;
1358     }
1359   }
1360
1361   ED_view3d_camera_lock_sync(depsgraph, v3d, rv3d);
1362   if (xform_flag) {
1363     ED_view3d_camera_lock_autokey(
1364         v3d, rv3d, C, xform_flag & HAS_ROTATE, xform_flag & HAS_TRANSLATE);
1365   }
1366
1367   ED_region_tag_redraw(vod->ar);
1368
1369   viewops_data_free(C, op);
1370
1371   return OPERATOR_FINISHED;
1372 }
1373
1374 void VIEW3D_OT_ndof_orbit(struct wmOperatorType *ot)
1375 {
1376   /* identifiers */
1377   ot->name = "NDOF Orbit View";
1378   ot->description = "Orbit the view using the 3D mouse";
1379   ot->idname = "VIEW3D_OT_ndof_orbit";
1380
1381   /* api callbacks */
1382   ot->invoke = ndof_orbit_invoke;
1383   ot->poll = ED_operator_view3d_active;
1384
1385   /* flags */
1386   ot->flag = 0;
1387 }
1388
1389 /** \} */
1390
1391 /* -------------------------------------------------------------------- */
1392 /** \name NDOF Orbit/Zoom Operator
1393  * \{ */
1394
1395 static int ndof_orbit_zoom_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1396 {
1397   if (event->type != NDOF_MOTION) {
1398     return OPERATOR_CANCELLED;
1399   }
1400
1401   const Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
1402   ViewOpsData *vod;
1403   View3D *v3d;
1404   RegionView3D *rv3d;
1405   char xform_flag = 0;
1406
1407   const wmNDOFMotionData *ndof = event->customdata;
1408
1409   viewops_data_alloc(C, op);
1410   viewops_data_create(
1411       C, op, event, viewops_flag_from_args((U.uiflag & USER_ORBIT_SELECTION) != 0, false));
1412
1413   vod = op->customdata;
1414
1415   ED_view3d_smooth_view_force_finish(C, vod->v3d, vod->ar);
1416
1417   v3d = vod->v3d;
1418   rv3d = vod->rv3d;
1419
1420   /* off by default, until changed later this function */
1421   rv3d->rot_angle = 0.0f;
1422
1423   ED_view3d_camera_lock_init_ex(depsgraph, v3d, rv3d, false);
1424
1425   if (ndof->progress == P_FINISHING) {
1426     /* pass */
1427   }
1428   else if ((rv3d->persp == RV3D_ORTHO) && RV3D_VIEW_IS_AXIS(rv3d->view)) {
1429     /* if we can't rotate, fallback to translate (locked axis views) */
1430     const bool has_translate = NDOF_HAS_TRANSLATE;
1431     const bool has_zoom = (ndof->tvec[2] != 0.0f) && ED_view3d_offset_lock_check(v3d, rv3d);
1432
1433     if (has_translate || has_zoom) {
1434       view3d_ndof_pan_zoom(ndof, vod->sa, vod->ar, has_translate, true);
1435       xform_flag |= HAS_TRANSLATE;
1436     }
1437   }
1438   else if ((U.ndof_flag & NDOF_MODE_ORBIT) || ED_view3d_offset_lock_check(v3d, rv3d)) {
1439     const bool has_rotation = NDOF_HAS_ROTATE;
1440     const bool has_zoom = (ndof->tvec[2] != 0.0f);
1441
1442     if (has_zoom) {
1443       view3d_ndof_pan_zoom(ndof, vod->sa, vod->ar, false, has_zoom);
1444       xform_flag |= HAS_TRANSLATE;
1445     }
1446
1447     if (has_rotation) {
1448       view3d_ndof_orbit(ndof, vod->sa, vod->ar, vod, true);
1449       xform_flag |= HAS_ROTATE;
1450     }
1451   }
1452   else { /* free/explore (like fly mode) */
1453     const bool has_rotation = NDOF_HAS_ROTATE;
1454     const bool has_translate = NDOF_HAS_TRANSLATE;
1455     const bool has_zoom = (ndof->tvec[2] != 0.0f) && !rv3d->is_persp;
1456
1457     float dist_backup;
1458
1459     if (has_translate || has_zoom) {
1460       view3d_ndof_pan_zoom(ndof, vod->sa, vod->ar, has_translate, has_zoom);
1461       xform_flag |= HAS_TRANSLATE;
1462     }
1463
1464     dist_backup = rv3d->dist;
1465     ED_view3d_distance_set(rv3d, 0.0f);
1466
1467     if (has_rotation) {
1468       view3d_ndof_orbit(ndof, vod->sa, vod->ar, vod, false);
1469       xform_flag |= HAS_ROTATE;
1470     }
1471
1472     ED_view3d_distance_set(rv3d, dist_backup);
1473   }
1474
1475   ED_view3d_camera_lock_sync(depsgraph, v3d, rv3d);
1476   if (xform_flag) {
1477     ED_view3d_camera_lock_autokey(
1478         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_ensure_evaluated_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(
1647       keymap, LEFTSHIFTKEY, KM_RELEASE, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ROTATE);
1648 #endif
1649
1650   /* assign map to operators */
1651   WM_modalkeymap_assign(keymap, "VIEW3D_OT_move");
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 static int viewmove_modal(bContext *C, wmOperator *op, const wmEvent *event)
1691 {
1692
1693   ViewOpsData *vod = op->customdata;
1694   short event_code = VIEW_PASS;
1695   bool use_autokey = false;
1696   int ret = OPERATOR_RUNNING_MODAL;
1697
1698   /* execute the events */
1699   if (event->type == MOUSEMOVE) {
1700     event_code = VIEW_APPLY;
1701   }
1702   else if (event->type == EVT_MODAL_MAP) {
1703     switch (event->val) {
1704       case VIEW_MODAL_CONFIRM:
1705         event_code = VIEW_CONFIRM;
1706         break;
1707       case VIEWROT_MODAL_SWITCH_ZOOM:
1708         WM_operator_name_call(C, "VIEW3D_OT_zoom", WM_OP_INVOKE_DEFAULT, NULL);
1709         event_code = VIEW_CONFIRM;
1710         break;
1711       case VIEWROT_MODAL_SWITCH_ROTATE:
1712         WM_operator_name_call(C, "VIEW3D_OT_rotate", WM_OP_INVOKE_DEFAULT, NULL);
1713         event_code = VIEW_CONFIRM;
1714         break;
1715     }
1716   }
1717   else if (event->type == vod->init.event_type && event->val == KM_RELEASE) {
1718     event_code = VIEW_CONFIRM;
1719   }
1720
1721   if (event_code == VIEW_APPLY) {
1722     viewmove_apply(vod, event->x, event->y);
1723     if (ED_screen_animation_playing(CTX_wm_manager(C))) {
1724       use_autokey = true;
1725     }
1726   }
1727   else if (event_code == VIEW_CONFIRM) {
1728     ED_view3d_depth_tag_update(vod->rv3d);
1729     use_autokey = true;
1730     ret = OPERATOR_FINISHED;
1731   }
1732
1733   if (use_autokey) {
1734     ED_view3d_camera_lock_autokey(vod->v3d, vod->rv3d, C, false, true);
1735   }
1736
1737   if (ret & OPERATOR_FINISHED) {
1738     viewops_data_free(C, op);
1739   }
1740
1741   return ret;
1742 }
1743
1744 static int viewmove_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1745 {
1746   ViewOpsData *vod;
1747
1748   const bool use_cursor_init = RNA_boolean_get(op->ptr, "use_cursor_init");
1749
1750   /* makes op->customdata */
1751   viewops_data_alloc(C, op);
1752   viewops_data_create(C,
1753                       op,
1754                       event,
1755                       (viewops_flag_from_prefs() & ~VIEWOPS_FLAG_ORBIT_SELECT) |
1756                           (use_cursor_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_region_view3d_active;
1795   ot->cancel = viewmove_cancel;
1796
1797   /* flags */
1798   ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_XY;
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
1845  * (coords compatible w/ #wmEvent.x, y). Use when not NULL.
1846  */
1847 static void view_zoom_to_window_xy_camera(
1848     Scene *scene, Depsgraph *depsgraph, View3D *v3d, 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(
1853       zoomfac * (1.0f / dfac), RV3D_CAMZOOM_MIN_FACTOR, RV3D_CAMZOOM_MAX_FACTOR);
1854   const float camzoom_new = BKE_screen_view3d_zoom_from_fac(zoomfac_new);
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
1894  * (coords compatible w/ #wmEvent.x, y). Use when not NULL.
1895  */
1896 static void view_zoom_to_window_xy_3d(ARegion *ar, float dfac, const int zoom_xy[2])
1897 {
1898   RegionView3D *rv3d = ar->regiondata;
1899   const float dist_new = rv3d->dist * dfac;
1900
1901   if (zoom_xy != NULL) {
1902     float dvec[3];
1903     float tvec[3];
1904     float tpos[3];
1905     float mval_f[2];
1906
1907     float zfac;
1908
1909     negate_v3_v3(tpos, rv3d->ofs);
1910
1911     mval_f[0] = (float)(((zoom_xy[0] - ar->winrct.xmin) * 2) - ar->winx) / 2.0f;
1912     mval_f[1] = (float)(((zoom_xy[1] - ar->winrct.ymin) * 2) - ar->winy) / 2.0f;
1913
1914     /* Project cursor position into 3D space */
1915     zfac = ED_view3d_calc_zfac(rv3d, tpos, NULL);
1916     ED_view3d_win_to_delta(ar, mval_f, dvec, zfac);
1917
1918     /* Calculate view target position for dolly */
1919     add_v3_v3v3(tvec, tpos, dvec);
1920     negate_v3(tvec);
1921
1922     /* Offset to target position and dolly */
1923     copy_v3_v3(rv3d->ofs, tvec);
1924     rv3d->dist = dist_new;
1925
1926     /* Calculate final offset */
1927     madd_v3_v3v3fl(rv3d->ofs, tvec, dvec, dfac);
1928   }
1929   else {
1930     rv3d->dist = dist_new;
1931   }
1932 }
1933
1934 static float viewzoom_scale_value(const rcti *winrct,
1935                                   const short viewzoom,
1936                                   const bool zoom_invert,
1937                                   const bool zoom_invert_force,
1938                                   const int xy_curr[2],
1939                                   const int xy_init[2],
1940                                   const float val,
1941                                   const float val_orig,
1942                                   double *r_timer_lastdraw)
1943 {
1944   float zfac;
1945
1946   if (viewzoom == USER_ZOOM_CONT) {
1947     double time = PIL_check_seconds_timer();
1948     float time_step = (float)(time - *r_timer_lastdraw);
1949     float fac;
1950
1951     if (U.uiflag & USER_ZOOM_HORIZ) {
1952       fac = (float)(xy_init[0] - xy_curr[0]);
1953     }
1954     else {
1955       fac = (float)(xy_init[1] - xy_curr[1]);
1956     }
1957
1958     if (zoom_invert != zoom_invert_force) {
1959       fac = -fac;
1960     }
1961
1962     /* oldstyle zoom */
1963     zfac = 1.0f + ((fac / 20.0f) * time_step);
1964     *r_timer_lastdraw = time;
1965   }
1966   else if (viewzoom == USER_ZOOM_SCALE) {
1967     /* method which zooms based on how far you move the mouse */
1968
1969     const int ctr[2] = {
1970         BLI_rcti_cent_x(winrct),
1971         BLI_rcti_cent_y(winrct),
1972     };
1973     float len_new = 5 + len_v2v2_int(ctr, xy_curr);
1974     float len_old = 5 + len_v2v2_int(ctr, xy_init);
1975
1976     /* intentionally ignore 'zoom_invert' for scale */
1977     if (zoom_invert_force) {
1978       SWAP(float, len_new, len_old);
1979     }
1980
1981     zfac = val_orig * (len_old / max_ff(len_new, 1.0f)) / val;
1982   }
1983   else { /* USER_ZOOM_DOLLY */
1984     float len_new = 5;
1985     float len_old = 5;
1986
1987     if (U.uiflag & USER_ZOOM_HORIZ) {
1988       len_new += (winrct->xmax - (xy_curr[0]));
1989       len_old += (winrct->xmax - (xy_init[0]));
1990     }
1991     else {
1992       len_new += (winrct->ymax - (xy_curr[1]));
1993       len_old += (winrct->ymax - (xy_init[1]));
1994     }
1995
1996     if (zoom_invert != zoom_invert_force) {
1997       SWAP(float, len_new, len_old);
1998     }
1999
2000     zfac = val_orig * (2.0f * ((len_new / max_ff(len_old, 1.0f)) - 1.0f) + 1.0f) / val;
2001   }
2002
2003   return zfac;
2004 }
2005
2006 static float viewzoom_scale_value_offset(const rcti *winrct,
2007                                          const short viewzoom,
2008                                          const bool zoom_invert,
2009                                          const bool zoom_invert_force,
2010                                          const int xy_curr[2],
2011                                          const int xy_init[2],
2012                                          const int xy_offset[2],
2013                                          const float val,
2014                                          const float val_orig,
2015                                          double *r_timer_lastdraw)
2016 {
2017   const int xy_curr_offset[2] = {
2018       xy_curr[0] + xy_offset[0],
2019       xy_curr[1] + xy_offset[1],
2020   };
2021   const int xy_init_offset[2] = {
2022       xy_init[0] + xy_offset[0],
2023       xy_init[1] + xy_offset[1],
2024   };
2025   return viewzoom_scale_value(winrct,
2026                               viewzoom,
2027                               zoom_invert,
2028                               zoom_invert_force,
2029                               xy_curr_offset,
2030                               xy_init_offset,
2031                               val,
2032                               val_orig,
2033                               r_timer_lastdraw);
2034 }
2035
2036 static void viewzoom_apply_camera(ViewOpsData *vod,
2037                                   const int xy[2],
2038                                   const short viewzoom,
2039                                   const bool zoom_invert,
2040                                   const bool zoom_to_pos)
2041 {
2042   float zfac;
2043   float zoomfac_prev = BKE_screen_view3d_zoom_to_fac(vod->init.camzoom) * 2.0f;
2044   float zoomfac = BKE_screen_view3d_zoom_to_fac(vod->rv3d->camzoom) * 2.0f;
2045
2046   zfac = viewzoom_scale_value_offset(&vod->ar->winrct,
2047                                      viewzoom,
2048                                      zoom_invert,
2049                                      true,
2050                                      xy,
2051                                      vod->init.event_xy,
2052                                      vod->init.event_xy_offset,
2053                                      zoomfac,
2054                                      zoomfac_prev,
2055                                      &vod->prev.time);
2056
2057   if (zfac != 1.0f && zfac != 0.0f) {
2058     /* calculate inverted, then invert again (needed because of camera zoom scaling) */
2059     zfac = 1.0f / zfac;
2060     view_zoom_to_window_xy_camera(vod->scene,
2061                                   vod->depsgraph,
2062                                   vod->v3d,
2063                                   vod->ar,
2064                                   zfac,
2065                                   zoom_to_pos ? vod->prev.event_xy : NULL);
2066   }
2067
2068   ED_region_tag_redraw(vod->ar);
2069 }
2070
2071 static void viewzoom_apply_3d(ViewOpsData *vod,
2072                               const int xy[2],
2073                               const short viewzoom,
2074                               const bool zoom_invert,
2075                               const bool zoom_to_pos)
2076 {
2077   float zfac;
2078   float dist_range[2];
2079
2080   ED_view3d_dist_range_get(vod->v3d, dist_range);
2081
2082   zfac = viewzoom_scale_value_offset(&vod->ar->winrct,
2083                                      viewzoom,
2084                                      zoom_invert,
2085                                      false,
2086                                      xy,
2087                                      vod->init.event_xy,
2088                                      vod->init.event_xy_offset,
2089                                      vod->rv3d->dist,
2090                                      vod->init.dist,
2091                                      &vod->prev.time);
2092
2093   if (zfac != 1.0f) {
2094     const float zfac_min = dist_range[0] / vod->rv3d->dist;
2095     const float zfac_max = dist_range[1] / vod->rv3d->dist;
2096     CLAMP(zfac, zfac_min, zfac_max);
2097
2098     view_zoom_to_window_xy_3d(vod->ar, zfac, zoom_to_pos ? vod->prev.event_xy : NULL);
2099   }
2100
2101   /* these limits were in old code too */
2102   CLAMP(vod->rv3d->dist, dist_range[0], dist_range[1]);
2103
2104   if (vod->rv3d->viewlock & RV3D_BOXVIEW) {
2105     view3d_boxview_sync(vod->sa, vod->ar);
2106   }
2107
2108   ED_view3d_camera_lock_sync(vod->depsgraph, vod->v3d, vod->rv3d);
2109
2110   ED_region_tag_redraw(vod->ar);
2111 }
2112
2113 static void viewzoom_apply(ViewOpsData *vod,
2114                            const int xy[2],
2115                            const short viewzoom,
2116                            const bool zoom_invert,
2117                            const bool zoom_to_pos)
2118 {
2119   if ((vod->rv3d->persp == RV3D_CAMOB) &&
2120       (vod->rv3d->is_persp && ED_view3d_camera_lock_check(vod->v3d, vod->rv3d)) == 0) {
2121     viewzoom_apply_camera(vod, xy, viewzoom, zoom_invert, zoom_to_pos);
2122   }
2123   else {
2124     viewzoom_apply_3d(vod, xy, viewzoom, zoom_invert, zoom_to_pos);
2125   }
2126 }
2127
2128 static int viewzoom_modal(bContext *C, wmOperator *op, const wmEvent *event)
2129 {
2130   ViewOpsData *vod = op->customdata;
2131   short event_code = VIEW_PASS;
2132   bool use_autokey = false;
2133   int ret = OPERATOR_RUNNING_MODAL;
2134
2135   /* execute the events */
2136   if (event->type == TIMER && event->customdata == vod->timer) {
2137     /* continuous zoom */
2138     event_code = VIEW_APPLY;
2139   }
2140   else if (event->type == MOUSEMOVE) {
2141     event_code = VIEW_APPLY;
2142   }
2143   else if (event->type == EVT_MODAL_MAP) {
2144     switch (event->val) {
2145       case VIEW_MODAL_CONFIRM:
2146         event_code = VIEW_CONFIRM;
2147         break;
2148       case VIEWROT_MODAL_SWITCH_MOVE:
2149         WM_operator_name_call(C, "VIEW3D_OT_move", WM_OP_INVOKE_DEFAULT, NULL);
2150         event_code = VIEW_CONFIRM;
2151         break;
2152       case VIEWROT_MODAL_SWITCH_ROTATE:
2153         WM_operator_name_call(C, "VIEW3D_OT_rotate", WM_OP_INVOKE_DEFAULT, NULL);
2154         event_code = VIEW_CONFIRM;
2155         break;
2156     }
2157   }
2158   else if (event->type == vod->init.event_type && event->val == KM_RELEASE) {
2159     event_code = VIEW_CONFIRM;
2160   }
2161
2162   if (event_code == VIEW_APPLY) {
2163     const bool use_cursor_init = RNA_boolean_get(op->ptr, "use_cursor_init");
2164     viewzoom_apply(vod,
2165                    &event->x,
2166                    U.viewzoom,
2167                    (U.uiflag & USER_ZOOM_INVERT) != 0,
2168                    (use_cursor_init && (U.uiflag & USER_ZOOM_TO_MOUSEPOS)));
2169     if (ED_screen_animation_playing(CTX_wm_manager(C))) {
2170       use_autokey = true;
2171     }
2172   }
2173   else if (event_code == VIEW_CONFIRM) {
2174     ED_view3d_depth_tag_update(vod->rv3d);
2175     use_autokey = true;
2176     ret = OPERATOR_FINISHED;
2177   }
2178
2179   if (use_autokey) {
2180     ED_view3d_camera_lock_autokey(vod->v3d, vod->rv3d, C, false, true);
2181   }
2182
2183   if (ret & OPERATOR_FINISHED) {
2184     viewops_data_free(C, op);
2185   }
2186
2187   return ret;
2188 }
2189
2190 static int viewzoom_exec(bContext *C, wmOperator *op)
2191 {
2192   Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
2193   Scene *scene = CTX_data_scene(C);
2194   View3D *v3d;
2195   RegionView3D *rv3d;
2196   ScrArea *sa;
2197   ARegion *ar;
2198   bool use_cam_zoom;
2199   float dist_range[2];
2200
2201   const int delta = RNA_int_get(op->ptr, "delta");
2202   const bool use_cursor_init = RNA_boolean_get(op->ptr, "use_cursor_init");
2203
2204   if (op->customdata) {
2205     ViewOpsData *vod = op->customdata;
2206
2207     sa = vod->sa;
2208     ar = vod->ar;
2209   }
2210   else {
2211     sa = CTX_wm_area(C);
2212     ar = CTX_wm_region(C);
2213   }
2214
2215   v3d = sa->spacedata.first;
2216   rv3d = ar->regiondata;
2217
2218   use_cam_zoom = (rv3d->persp == RV3D_CAMOB) &&
2219                  !(rv3d->is_persp && ED_view3d_camera_lock_check(v3d, rv3d));
2220
2221   int zoom_xy_buf[2];
2222   const int *zoom_xy = NULL;
2223   if (use_cursor_init && (U.uiflag & USER_ZOOM_TO_MOUSEPOS)) {
2224     zoom_xy_buf[0] = RNA_struct_property_is_set(op->ptr, "mx") ? RNA_int_get(op->ptr, "mx") :
2225                                                                  ar->winx / 2;
2226     zoom_xy_buf[1] = RNA_struct_property_is_set(op->ptr, "my") ? RNA_int_get(op->ptr, "my") :
2227                                                                  ar->winy / 2;
2228     zoom_xy = zoom_xy_buf;
2229   }
2230
2231   ED_view3d_dist_range_get(v3d, dist_range);
2232
2233   if (delta < 0) {
2234     const float step = 1.2f;
2235     /* this min and max is also in viewmove() */
2236     if (use_cam_zoom) {
2237       view_zoom_to_window_xy_camera(scene, depsgraph, v3d, ar, step, zoom_xy);
2238     }
2239     else {
2240       if (rv3d->dist < dist_range[1]) {
2241         view_zoom_to_window_xy_3d(ar, step, zoom_xy);
2242       }
2243     }
2244   }
2245   else {
2246     const float step = 1.0f / 1.2f;
2247     if (use_cam_zoom) {
2248       view_zoom_to_window_xy_camera(scene, depsgraph, v3d, ar, step, zoom_xy);
2249     }
2250     else {
2251       if (rv3d->dist > dist_range[0]) {
2252         view_zoom_to_window_xy_3d(ar, step, zoom_xy);
2253       }
2254     }
2255   }
2256
2257   if (rv3d->viewlock & RV3D_BOXVIEW) {
2258     view3d_boxview_sync(sa, ar);
2259   }
2260
2261   ED_view3d_depth_tag_update(rv3d);
2262
2263   ED_view3d_camera_lock_sync(depsgraph, v3d, rv3d);
2264   ED_view3d_camera_lock_autokey(v3d, rv3d, C, false, true);
2265
2266   ED_region_tag_redraw(ar);
2267
2268   viewops_data_free(C, op);
2269
2270   return OPERATOR_FINISHED;
2271 }
2272
2273 /* viewdolly_invoke() copied this function, changes here may apply there */
2274 static int viewzoom_invoke(bContext *C, wmOperator *op, const wmEvent *event)
2275 {
2276   ViewOpsData *vod;
2277
2278   const bool use_cursor_init = RNA_boolean_get(op->ptr, "use_cursor_init");
2279
2280   /* makes op->customdata */
2281   viewops_data_alloc(C, op);
2282   viewops_data_create(C,
2283                       op,
2284                       event,
2285                       (viewops_flag_from_prefs() & ~VIEWOPS_FLAG_ORBIT_SELECT) |
2286                           (use_cursor_init ? VIEWOPS_FLAG_USE_MOUSE_INIT : 0));
2287   vod = op->customdata;
2288
2289   ED_view3d_smooth_view_force_finish(C, vod->v3d, vod->ar);
2290
2291   /* if one or the other zoom position aren't set, set from event */
2292   if (!RNA_struct_property_is_set(op->ptr, "mx") || !RNA_struct_property_is_set(op->ptr, "my")) {
2293     RNA_int_set(op->ptr, "mx", event->x);
2294     RNA_int_set(op->ptr, "my", event->y);
2295   }
2296
2297   if (RNA_struct_property_is_set(op->ptr, "delta")) {
2298     viewzoom_exec(C, op);
2299   }
2300   else {
2301     if (event->type == MOUSEZOOM || event->type == MOUSEPAN) {
2302
2303       if (U.uiflag & USER_ZOOM_HORIZ) {
2304         vod->init.event_xy[0] = vod->prev.event_xy[0] = event->x;
2305       }
2306       else {
2307         /* Set y move = x move as MOUSEZOOM uses only x axis to pass magnification value */
2308         vod->init.event_xy[1] = vod->prev.event_xy[1] = vod->init.event_xy[1] + event->x -
2309                                                         event->prevx;
2310       }
2311       viewzoom_apply(vod,
2312                      &event->prevx,
2313                      USER_ZOOM_DOLLY,
2314                      (U.uiflag & USER_ZOOM_INVERT) != 0,
2315                      (use_cursor_init && (U.uiflag & USER_ZOOM_TO_MOUSEPOS)));
2316       ED_view3d_camera_lock_autokey(vod->v3d, vod->rv3d, C, false, true);
2317
2318       ED_view3d_depth_tag_update(vod->rv3d);
2319
2320       viewops_data_free(C, op);
2321       return OPERATOR_FINISHED;
2322     }
2323     else {
2324       if (U.viewzoom == USER_ZOOM_CONT) {
2325         /* needs a timer to continue redrawing */
2326         vod->timer = WM_event_add_timer(CTX_wm_manager(C), CTX_wm_window(C), TIMER, 0.01f);
2327         vod->prev.time = PIL_check_seconds_timer();
2328       }
2329
2330       /* add temp handler */
2331       WM_event_add_modal_handler(C, op);
2332
2333       return OPERATOR_RUNNING_MODAL;
2334     }
2335   }
2336   return OPERATOR_FINISHED;
2337 }
2338
2339 static void viewzoom_cancel(bContext *C, wmOperator *op)
2340 {
2341   viewops_data_free(C, op);
2342 }
2343
2344 void VIEW3D_OT_zoom(wmOperatorType *ot)
2345 {
2346   /* identifiers */
2347   ot->name = "Zoom View";
2348   ot->description = "Zoom in/out in the view";
2349   ot->idname = "VIEW3D_OT_zoom";
2350
2351   /* api callbacks */
2352   ot->invoke = viewzoom_invoke;
2353   ot->exec = viewzoom_exec;
2354   ot->modal = viewzoom_modal;
2355   ot->poll = ED_operator_region_view3d_active;
2356   ot->cancel = viewzoom_cancel;
2357
2358   /* flags */
2359   ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_XY;
2360
2361   /* properties */
2362   view3d_operator_properties_common(
2363       ot, V3D_OP_PROP_DELTA | V3D_OP_PROP_MOUSE_CO | V3D_OP_PROP_USE_MOUSE_INIT);
2364 }
2365
2366 /** \} */
2367
2368 /* -------------------------------------------------------------------- */
2369 /** \name View Dolly Operator
2370  *
2371  * Like zoom but translates the view offset along the view direction
2372  * which avoids #RegionView3D.dist approaching zero.
2373  * \{ */
2374
2375 /* this is an exact copy of viewzoom_modal_keymap */
2376 /* called in transform_ops.c, on each regeneration of keymaps  */
2377 void viewdolly_modal_keymap(wmKeyConfig *keyconf)
2378 {
2379   static const EnumPropertyItem modal_items[] = {
2380       {VIEW_MODAL_CONFIRM, "CONFIRM", 0, "Confirm", ""},
2381
2382       {VIEWROT_MODAL_SWITCH_ROTATE, "SWITCH_TO_ROTATE", 0, "Switch to Rotate"},
2383       {VIEWROT_MODAL_SWITCH_MOVE, "SWITCH_TO_MOVE", 0, "Switch to Move"},
2384
2385       {0, NULL, 0, NULL, NULL},
2386   };
2387
2388   wmKeyMap *keymap = WM_modalkeymap_get(keyconf, "View3D Dolly Modal");
2389
2390   /* this function is called for each spacetype, only needs to add map once */
2391   if (keymap && keymap->modal_items) {
2392     return;
2393   }
2394
2395   keymap = WM_modalkeymap_add(keyconf, "View3D Dolly Modal", modal_items);
2396
2397   /* disabled mode switching for now, can re-implement better, later on */
2398 #if 0
2399   WM_modalkeymap_add_item(keymap, LEFTMOUSE, KM_RELEASE, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ROTATE);
2400   WM_modalkeymap_add_item(keymap, LEFTCTRLKEY, KM_RELEASE, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ROTATE);
2401   WM_modalkeymap_add_item(keymap, LEFTSHIFTKEY, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_MOVE);
2402 #endif
2403
2404   /* assign map to operators */
2405   WM_modalkeymap_assign(keymap, "VIEW3D_OT_dolly");
2406 }
2407
2408 static bool viewdolly_offset_lock_check(bContext *C, wmOperator *op)
2409 {
2410   View3D *v3d = CTX_wm_view3d(C);
2411   RegionView3D *rv3d = CTX_wm_region_view3d(C);
2412   if (ED_view3d_offset_lock_check(v3d, rv3d)) {
2413     BKE_report(op->reports, RPT_WARNING, "Cannot dolly when the view offset is locked");
2414     return true;
2415   }
2416   else {
2417     return false;
2418   }
2419 }
2420
2421 static void view_dolly_to_vector_3d(ARegion *ar, float orig_ofs[3], float dvec[3], float dfac)
2422 {
2423   RegionView3D *rv3d = ar->regiondata;
2424   madd_v3_v3v3fl(rv3d->ofs, orig_ofs, dvec, -(1.0f - dfac));
2425 }
2426
2427 static void viewdolly_apply(ViewOpsData *vod, const int xy[2], const short zoom_invert)
2428 {
2429   float zfac = 1.0;
2430
2431   {
2432     float len1, len2;
2433
2434     if (U.uiflag & USER_ZOOM_HORIZ) {
2435       len1 = (vod->ar->winrct.xmax - xy[0]) + 5;
2436       len2 = (vod->ar->winrct.xmax - vod->init.event_xy[0]) + 5;
2437     }
2438     else {
2439       len1 = (vod->ar->winrct.ymax - xy[1]) + 5;
2440       len2 = (vod->ar->winrct.ymax - vod->init.event_xy[1]) + 5;
2441     }
2442     if (zoom_invert) {
2443       SWAP(float, len1, len2);
2444     }
2445
2446     zfac = 1.0f + ((len1 - len2) * 0.01f * vod->rv3d->dist);
2447   }
2448
2449   if (zfac != 1.0f) {
2450     view_dolly_to_vector_3d(vod->ar, vod->init.ofs, vod->init.mousevec, zfac);
2451   }
2452
2453   if (vod->rv3d->viewlock & RV3D_BOXVIEW) {
2454     view3d_boxview_sync(vod->sa, vod->ar);
2455   }
2456
2457   ED_view3d_camera_lock_sync(vod->depsgraph, vod->v3d, vod->rv3d);
2458
2459   ED_region_tag_redraw(vod->ar);
2460 }
2461
2462 static int viewdolly_modal(bContext *C, wmOperator *op, const wmEvent *event)
2463 {
2464   ViewOpsData *vod = op->customdata;
2465   short event_code = VIEW_PASS;
2466   bool use_autokey = false;
2467   int ret = OPERATOR_RUNNING_MODAL;
2468
2469   /* execute the events */
2470   if (event->type == MOUSEMOVE) {
2471     event_code = VIEW_APPLY;
2472   }
2473   else if (event->type == EVT_MODAL_MAP) {
2474     switch (event->val) {
2475       case VIEW_MODAL_CONFIRM:
2476         event_code = VIEW_CONFIRM;
2477         break;
2478       case VIEWROT_MODAL_SWITCH_MOVE:
2479         WM_operator_name_call(C, "VIEW3D_OT_move", WM_OP_INVOKE_DEFAULT, NULL);
2480         event_code = VIEW_CONFIRM;
2481         break;
2482       case VIEWROT_MODAL_SWITCH_ROTATE:
2483         WM_operator_name_call(C, "VIEW3D_OT_rotate", WM_OP_INVOKE_DEFAULT, NULL);
2484         event_code = VIEW_CONFIRM;
2485         break;
2486     }
2487   }
2488   else if (event->type == vod->init.event_type && event->val == KM_RELEASE) {
2489     event_code = VIEW_CONFIRM;
2490   }
2491
2492   if (event_code == VIEW_APPLY) {
2493     viewdolly_apply(vod, &event->x, (U.uiflag & USER_ZOOM_INVERT) != 0);
2494     if (ED_screen_animation_playing(CTX_wm_manager(C))) {
2495       use_autokey = true;
2496     }
2497   }
2498   else if (event_code == VIEW_CONFIRM) {
2499     ED_view3d_depth_tag_update(vod->rv3d);
2500     use_autokey = true;
2501     ret = OPERATOR_FINISHED;
2502   }
2503
2504   if (use_autokey) {
2505     ED_view3d_camera_lock_autokey(vod->v3d, vod->rv3d, C, false, true);
2506   }
2507
2508   if (ret & OPERATOR_FINISHED) {
2509     viewops_data_free(C, op);
2510   }
2511
2512   return ret;
2513 }
2514
2515 static int viewdolly_exec(bContext *C, wmOperator *op)
2516 {
2517   View3D *v3d;
2518   RegionView3D *rv3d;
2519   ScrArea *sa;
2520   ARegion *ar;
2521   float mousevec[3];
2522
2523   const int delta = RNA_int_get(op->ptr, "delta");
2524
2525   if (op->customdata) {
2526     ViewOpsData *vod = op->customdata;
2527
2528     sa = vod->sa;
2529     ar = vod->ar;
2530     copy_v3_v3(mousevec, vod->init.mousevec);
2531   }
2532   else {
2533     sa = CTX_wm_area(C);
2534     ar = CTX_wm_region(C);
2535     negate_v3_v3(mousevec, ((RegionView3D *)ar->regiondata)->viewinv[2]);
2536     normalize_v3(mousevec);
2537   }
2538
2539   v3d = sa->spacedata.first;
2540   rv3d = ar->regiondata;
2541
2542   const bool use_cursor_init = RNA_boolean_get(op->ptr, "use_cursor_init");
2543
2544   /* overwrite the mouse vector with the view direction (zoom into the center) */
2545   if ((use_cursor_init && (U.uiflag & USER_ZOOM_TO_MOUSEPOS)) == 0) {
2546     normalize_v3_v3(mousevec, rv3d->viewinv[2]);
2547   }
2548
2549   view_dolly_to_vector_3d(ar, rv3d->ofs, mousevec, delta < 0 ? 0.2f : 1.8f);
2550
2551   if (rv3d->viewlock & RV3D_BOXVIEW) {
2552     view3d_boxview_sync(sa, ar);
2553   }
2554
2555   ED_view3d_depth_tag_update(rv3d);
2556
2557   ED_view3d_camera_lock_sync(CTX_data_ensure_evaluated_depsgraph(C), v3d, rv3d);
2558
2559   ED_region_tag_redraw(ar);
2560
2561   viewops_data_free(C, op);
2562
2563   return OPERATOR_FINISHED;
2564 }
2565
2566 /* copied from viewzoom_invoke(), changes here may apply there */
2567 static int viewdolly_invoke(bContext *C, wmOperator *op, const wmEvent *event)
2568 {
2569   ViewOpsData *vod;
2570
2571   if (viewdolly_offset_lock_check(C, op)) {
2572     return OPERATOR_CANCELLED;
2573   }
2574
2575   /* makes op->customdata */
2576   viewops_data_alloc(C, op);
2577   vod = op->customdata;
2578
2579   /* poll should check but in some cases fails, see poll func for details */
2580   if (vod->rv3d->viewlock & RV3D_LOCKED) {
2581     viewops_data_free(C, op);
2582     return OPERATOR_PASS_THROUGH;
2583   }
2584
2585   ED_view3d_smooth_view_force_finish(C, vod->v3d, vod->ar);
2586
2587   /* needs to run before 'viewops_data_create' so the backup 'rv3d->ofs' is correct */
2588   /* switch from camera view when: */
2589   if (vod->rv3d->persp != RV3D_PERSP) {
2590     if (vod->rv3d->persp == RV3D_CAMOB) {
2591       /* ignore rv3d->lpersp because dolly only makes sense in perspective mode */
2592       const Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
2593       ED_view3d_persp_switch_from_camera(depsgraph, vod->v3d, vod->rv3d, RV3D_PERSP);
2594     }
2595     else {
2596       vod->rv3d->persp = RV3D_PERSP;
2597     }
2598     ED_region_tag_redraw(vod->ar);
2599   }
2600
2601   const bool use_cursor_init = RNA_boolean_get(op->ptr, "use_cursor_init");
2602
2603   viewops_data_create(C,
2604                       op,
2605                       event,
2606                       (viewops_flag_from_prefs() & ~VIEWOPS_FLAG_ORBIT_SELECT) |
2607                           (use_cursor_init ? VIEWOPS_FLAG_USE_MOUSE_INIT : 0));
2608
2609   /* if one or the other zoom position aren't set, set from event */
2610   if (!RNA_struct_property_is_set(op->ptr, "mx") || !RNA_struct_property_is_set(op->ptr, "my")) {
2611     RNA_int_set(op->ptr, "mx", event->x);
2612     RNA_int_set(op->ptr, "my", event->y);
2613   }
2614
2615   if (RNA_struct_property_is_set(op->ptr, "delta")) {
2616     viewdolly_exec(C, op);
2617   }
2618   else {
2619     /* overwrite the mouse vector with the view direction (zoom into the center) */
2620     if ((use_cursor_init && (U.uiflag & USER_ZOOM_TO_MOUSEPOS)) == 0) {
2621       negate_v3_v3(vod->init.mousevec, vod->rv3d->viewinv[2]);
2622       normalize_v3(vod->init.mousevec);
2623     }
2624
2625     if (event->type == MOUSEZOOM) {
2626       /* Bypass Zoom invert flag for track pads (pass false always) */
2627
2628       if (U.uiflag & USER_ZOOM_HORIZ) {
2629         vod->init.event_xy[0] = vod->prev.event_xy[0] = event->x;
2630       }
2631       else {
2632         /* Set y move = x move as MOUSEZOOM uses only x axis to pass magnification value */
2633         vod->init.event_xy[1] = vod->prev.event_xy[1] = vod->init.event_xy[1] + event->x -
2634                                                         event->prevx;
2635       }
2636       viewdolly_apply(vod, &event->prevx, (U.uiflag & USER_ZOOM_INVERT) == 0);
2637       ED_view3d_depth_tag_update(vod->rv3d);
2638
2639       viewops_data_free(C, op);
2640       return OPERATOR_FINISHED;
2641     }
2642     else {
2643       /* add temp handler */
2644       WM_event_add_modal_handler(C, op);
2645
2646       return OPERATOR_RUNNING_MODAL;
2647     }
2648   }
2649   return OPERATOR_FINISHED;
2650 }
2651
2652 static void viewdolly_cancel(bContext *C, wmOperator *op)
2653 {
2654   viewops_data_free(C, op);
2655 }
2656
2657 void VIEW3D_OT_dolly(wmOperatorType *ot)
2658 {
2659   /* identifiers */
2660   ot->name = "Dolly View";
2661   ot->description = "Dolly in/out in the view";
2662   ot->idname = "VIEW3D_OT_dolly";
2663
2664   /* api callbacks */
2665   ot->invoke = viewdolly_invoke;
2666   ot->exec = viewdolly_exec;
2667   ot->modal = viewdolly_modal;
2668   ot->poll = ED_operator_region_view3d_active;
2669   ot->cancel = viewdolly_cancel;
2670
2671   /* flags */
2672   ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_XY;
2673
2674   /* properties */
2675   view3d_operator_properties_common(
2676       ot, V3D_OP_PROP_DELTA | V3D_OP_PROP_MOUSE_CO | V3D_OP_PROP_USE_MOUSE_INIT);
2677 }
2678
2679 /** \} */
2680
2681 /* -------------------------------------------------------------------- */
2682 /** \name View All Operator
2683  *
2684  * Move & Zoom the view to fit all of it's contents.
2685  * \{ */
2686
2687 static bool view3d_object_skip_minmax(const View3D *v3d,
2688                                       const RegionView3D *rv3d,
2689                                       const Object *ob,
2690                                       const bool skip_camera,
2691                                       bool *r_only_center)
2692 {
2693   BLI_assert(ob->id.orig_id == NULL);
2694   *r_only_center = false;
2695
2696   if (skip_camera && (ob == v3d->camera)) {
2697     return true;
2698   }
2699
2700   if ((ob->type == OB_EMPTY) && (ob->empty_drawtype == OB_EMPTY_IMAGE) &&
2701       !BKE_object_empty_image_frame_is_visible_in_view3d(ob, rv3d)) {
2702     *r_only_center = true;
2703     return false;
2704   }
2705
2706   return false;
2707 }
2708
2709 static void view3d_from_minmax(bContext *C,
2710                                View3D *v3d,
2711                                ARegion *ar,
2712                                const float min[3],
2713                                const float max[3],
2714                                bool ok_dist,
2715                                const int smooth_viewtx)
2716 {
2717   RegionView3D *rv3d = ar->regiondata;
2718   float afm[3];
2719   float size;
2720
2721   ED_view3d_smooth_view_force_finish(C, v3d, ar);
2722
2723   /* SMOOTHVIEW */
2724   float new_ofs[3];
2725   float new_dist;
2726
2727   sub_v3_v3v3(afm, max, min);
2728   size = max_fff(afm[0], afm[1], afm[2]);
2729
2730   if (ok_dist) {
2731     char persp;
2732
2733     if (rv3d->is_persp) {
2734       if (rv3d->persp == RV3D_CAMOB && ED_view3d_camera_lock_check(v3d, rv3d)) {
2735         persp = RV3D_CAMOB;
2736       }
2737       else {
2738         persp = RV3D_PERSP;
2739       }
2740     }
2741     else { /* ortho */
2742       if (size < 0.0001f) {
2743         /* bounding box was a single point so do not zoom */
2744         ok_dist = false;
2745       }
2746       else {
2747         /* adjust zoom so it looks nicer */
2748         persp = RV3D_ORTHO;
2749       }
2750     }
2751
2752     if (ok_dist) {
2753       Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
2754       new_dist = ED_view3d_radius_to_dist(
2755           v3d, ar, depsgraph, persp, true, (size / 2) * VIEW3D_MARGIN);
2756       if (rv3d->is_persp) {
2757         /* don't zoom closer than the near clipping plane */
2758         new_dist = max_ff(new_dist, v3d->clip_start * 1.5f);
2759       }
2760     }
2761   }
2762
2763   mid_v3_v3v3(new_ofs, min, max);
2764   negate_v3(new_ofs);
2765
2766   if (rv3d->persp == RV3D_CAMOB && !ED_view3d_camera_lock_check(v3d, rv3d)) {
2767     rv3d->persp = RV3D_PERSP;
2768     ED_view3d_smooth_view(C,
2769                           v3d,
2770                           ar,
2771                           smooth_viewtx,
2772                           &(const V3D_SmoothParams){
2773                               .camera_old = v3d->camera,
2774                               .ofs = new_ofs,
2775                               .dist = ok_dist ? &new_dist : NULL,
2776                           });
2777   }
2778   else {
2779     ED_view3d_smooth_view(C,
2780                           v3d,
2781                           ar,
2782                           smooth_viewtx,
2783                           &(const V3D_SmoothParams){
2784                               .ofs = new_ofs,
2785                               .dist = ok_dist ? &new_dist : NULL,
2786                           });
2787   }
2788
2789   /* smooth view does viewlock RV3D_BOXVIEW copy */
2790 }
2791
2792 /**
2793  * Same as #view3d_from_minmax but for all regions (except cameras).
2794  */
2795 static void view3d_from_minmax_multi(bContext *C,
2796                                      View3D *v3d,
2797                                      const float min[3],
2798                                      const float max[3],
2799                                      const bool ok_dist,
2800                                      const int smooth_viewtx)
2801 {
2802   ScrArea *sa = CTX_wm_area(C);
2803   ARegion *ar;
2804   for (ar = sa->regionbase.first; ar; ar = ar->next) {
2805     if (ar->regiontype == RGN_TYPE_WINDOW) {
2806       RegionView3D *rv3d = ar->regiondata;
2807       /* when using all regions, don't jump out of camera view,
2808        * but _do_ allow locked cameras to be moved */
2809       if ((rv3d->persp != RV3D_CAMOB) || ED_view3d_camera_lock_check(v3d, rv3d)) {
2810         view3d_from_minmax(C, v3d, ar, min, max, ok_dist, smooth_viewtx);
2811       }
2812     }
2813   }
2814 }
2815
2816 static int view3d_all_exec(bContext *C, wmOperator *op)
2817 {
2818   ARegion *ar = CTX_wm_region(C);
2819   View3D *v3d = CTX_wm_view3d(C);
2820   RegionView3D *rv3d = CTX_wm_region_view3d(C);
2821   Scene *scene = CTX_data_scene(C);
2822   const Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
2823   ViewLayer *view_layer_eval = DEG_get_evaluated_view_layer(depsgraph);
2824   Base *base_eval;
2825   const bool use_all_regions = RNA_boolean_get(op->ptr, "use_all_regions");
2826   const bool skip_camera = (ED_view3d_camera_lock_check(v3d, ar->regiondata) ||
2827                             /* any one of the regions may be locked */
2828                             (use_all_regions && v3d->flag2 & V3D_LOCK_CAMERA));
2829   const bool center = RNA_boolean_get(op->ptr, "center");
2830   const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
2831
2832   float min[3], max[3];
2833   bool changed = false;
2834
2835   if (center) {
2836     /* in 2.4x this also move the cursor to (0, 0, 0) (with shift+c). */
2837     View3DCursor *cursor = &scene->cursor;
2838     zero_v3(min);
2839     zero_v3(max);
2840     zero_v3(cursor->location);
2841     float mat3[3][3];
2842     unit_m3(mat3);
2843     BKE_scene_cursor_mat3_to_rot(cursor, mat3, false);
2844   }
2845   else {
2846     INIT_MINMAX(min, max);
2847   }
2848
2849   for (base_eval = view_layer_eval->object_bases.first; base_eval; base_eval = base_eval->next) {
2850     if (BASE_VISIBLE(v3d, base_eval)) {
2851       bool only_center = false;
2852       Object *ob = DEG_get_original_object(base_eval->object);
2853       if (view3d_object_skip_minmax(v3d, rv3d, ob, skip_camera, &only_center)) {
2854         continue;
2855       }
2856
2857       if (only_center) {
2858         minmax_v3v3_v3(min, max, base_eval->object->obmat[3]);
2859       }
2860       else {
2861         BKE_object_minmax(base_eval->object, min, max, false);
2862       }
2863       changed = true;
2864     }
2865   }
2866
2867   if (center) {
2868     DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE);
2869   }
2870
2871   if (!changed) {
2872     ED_region_tag_redraw(ar);
2873     /* TODO - should this be cancel?
2874      * I think no, because we always move the cursor, with or without
2875      * object, but in this case there is no change in the scene,
2876      * only the cursor so I choice a ED_region_tag like
2877      * view3d_smooth_view do for the center_cursor.
2878      * See bug #22640
2879      */
2880     return OPERATOR_FINISHED;
2881   }
2882
2883   if (use_all_regions) {
2884     view3d_from_minmax_multi(C, v3d, min, max, true, smooth_viewtx);
2885   }
2886   else {
2887     view3d_from_minmax(C, v3d, ar, min, max, true, smooth_viewtx);
2888   }
2889
2890   return OPERATOR_FINISHED;
2891 }
2892
2893 void VIEW3D_OT_view_all(wmOperatorType *ot)
2894 {
2895   /* identifiers */
2896   ot->name = "View All";
2897   ot->description = "View all objects in scene";
2898   ot->idname = "VIEW3D_OT_view_all";
2899
2900   /* api callbacks */
2901   ot->exec = view3d_all_exec;
2902   ot->poll = ED_operator_region_view3d_active;
2903
2904   /* flags */
2905   ot->flag = 0;
2906
2907   /* properties */
2908   view3d_operator_properties_common(ot, V3D_OP_PROP_USE_ALL_REGIONS);
2909   RNA_def_boolean(ot->srna, "center", 0, "Center", "");
2910 }
2911
2912 /** \} */
2913
2914 /* -------------------------------------------------------------------- */
2915 /** \name View Selected Operator
2916  *
2917  * Move & Zoom the view to fit selected contents.
2918  * \{ */
2919
2920 /* like a localview without local!, was centerview() in 2.4x */
2921 static int viewselected_exec(bContext *C, wmOperator *op)
2922 {
2923   ARegion *ar = CTX_wm_region(C);
2924   View3D *v3d = CTX_wm_view3d(C);
2925   RegionView3D *rv3d = CTX_wm_region_view3d(C);
2926   Scene *scene = CTX_data_scene(C);
2927   Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
2928   ViewLayer *view_layer_eval = DEG_get_evaluated_view_layer(depsgraph);
2929   Object *ob_eval = OBACT(view_layer_eval);
2930   Object *obedit = CTX_data_edit_object(C);
2931   const bGPdata *gpd_eval = ob_eval && (ob_eval->type == OB_GPENCIL) ? ob_eval->data : NULL;
2932   const bool is_gp_edit = gpd_eval ? GPENCIL_ANY_MODE(gpd_eval) : false;
2933   const bool is_face_map = ((is_gp_edit == false) && ar->gizmo_map &&
2934                             WM_gizmomap_is_any_selected(ar->gizmo_map));
2935   float min[3], max[3];
2936   bool ok = false, ok_dist = true;
2937   const bool use_all_regions = RNA_boolean_get(op->ptr, "use_all_regions");
2938   const bool skip_camera = (ED_view3d_camera_lock_check(v3d, ar->regiondata) ||
2939                             /* any one of the regions may be locked */
2940                             (use_all_regions && v3d->flag2 & V3D_LOCK_CAMERA));
2941   const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
2942
2943   INIT_MINMAX(min, max);
2944   if (is_face_map) {
2945     ob_eval = NULL;
2946   }
2947
2948   if (ob_eval && (ob_eval->mode & OB_MODE_WEIGHT_PAINT)) {
2949     /* hard-coded exception, we look for the one selected armature */
2950     /* this is weak code this way, we should make a generic
2951      * active/selection callback interface once... */
2952     Base *base_eval;
2953     for (base_eval = view_layer_eval->object_bases.first; base_eval; base_eval = base_eval->next) {
2954       if (BASE_SELECTED_EDITABLE(v3d, base_eval)) {
2955         if (base_eval->object->type == OB_ARMATURE) {
2956           if (base_eval->object->mode & OB_MODE_POSE) {
2957             break;
2958           }
2959         }
2960       }
2961     }
2962     if (base_eval) {
2963       ob_eval = base_eval->object;
2964     }
2965   }
2966
2967   if (is_gp_edit) {
2968     CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) {
2969       /* we're only interested in selected points here... */
2970       if ((gps->flag & GP_STROKE_SELECT) && (gps->flag & GP_STROKE_3DSPACE)) {
2971         ok |= BKE_gpencil_stroke_minmax(gps, true, min, max);
2972       }
2973     }
2974     CTX_DATA_END;
2975
2976     if ((ob_eval) && (ok)) {
2977       mul_m4_v3(ob_eval->obmat, min);
2978       mul_m4_v3(ob_eval->obmat, max);
2979     }
2980   }
2981   else if (is_face_map) {
2982     ok = WM_gizmomap_minmax(ar->gizmo_map, true, true, min, max);
2983   }
2984   else if (obedit) {
2985     /* only selected */
2986     FOREACH_OBJECT_IN_MODE_BEGIN (view_layer_eval, v3d, obedit->type, obedit->mode, ob_eval_iter) {
2987       ok |= ED_view3d_minmax_verts(ob_eval_iter, min, max);
2988     }
2989     FOREACH_OBJECT_IN_MODE_END;
2990   }
2991   else if (ob_eval && (ob_eval->mode & OB_MODE_POSE)) {
2992     FOREACH_OBJECT_IN_MODE_BEGIN (
2993         view_layer_eval, v3d, ob_eval->type, ob_eval->mode, ob_eval_iter) {
2994       ok |= BKE_pose_minmax(ob_eval_iter, min, max, true, true);
2995     }
2996     FOREACH_OBJECT_IN_MODE_END;
2997   }
2998   else if (BKE_paint_select_face_test(ob_eval)) {
2999     ok = paintface_minmax(ob_eval, min, max);
3000   }
3001   else if (ob_eval && (ob_eval->mode & OB_MODE_PARTICLE_EDIT)) {
3002     ok = PE_minmax(scene, view_layer_eval, min, max);
3003   }
3004   else if (ob_eval && (ob_eval->mode & (OB_MODE_SCULPT | OB_MODE_VERTEX_PAINT |
3005                                         OB_MODE_WEIGHT_PAINT | OB_MODE_TEXTURE_PAINT))) {
3006     BKE_paint_stroke_get_average(scene, ob_eval, min);
3007     copy_v3_v3(max, min);
3008     ok = true;
3009     ok_dist = 0; /* don't zoom */
3010   }
3011   else {
3012     Base *base_eval;
3013     for (base_eval = FIRSTBASE(view_layer_eval); base_eval; base_eval = base_eval->next) {
3014       if (BASE_SELECTED(v3d, base_eval)) {
3015         bool only_center = false;
3016         Object *ob = DEG_get_original_object(base_eval->object);
3017         if (view3d_object_skip_minmax(v3d, rv3d, ob, skip_camera, &only_center)) {
3018           continue;
3019         }
3020
3021         /* account for duplis */
3022         if (BKE_object_minmax_dupli(depsgraph, scene, base_eval->object, min, max, false) == 0) {
3023           /* use if duplis not found */
3024           if (only_center) {
3025             minmax_v3v3_v3(min, max, base_eval->object->obmat[3]);
3026           }
3027           else {
3028             BKE_object_minmax(base_eval->object, min, max, false);
3029           }
3030         }
3031
3032         ok = 1;
3033       }
3034     }
3035   }
3036
3037   if (ok == 0) {
3038     return OPERATOR_FINISHED;
3039   }
3040
3041   if (use_all_regions) {
3042     view3d_from_minmax_multi(C, v3d, min, max, ok_dist, smooth_viewtx);
3043   }
3044   else {
3045     view3d_from_minmax(C, v3d, ar, min, max, ok_dist, smooth_viewtx);
3046   }
3047
3048   return OPERATOR_FINISHED;
3049 }
3050
3051 void VIEW3D_OT_view_selected(wmOperatorType *ot)
3052 {
3053   /* identifiers */
3054   ot->name = "View Selected";
3055   ot->description = "Move the view to the selection center";
3056   ot->idname = "VIEW3D_OT_view_selected";
3057
3058   /* api callbacks */
3059   ot->exec = viewselected_exec;
3060   ot->poll = ED_operator_region_view3d_active;
3061
3062   /* flags */
3063   ot->flag = 0;
3064
3065   /* properties */
3066   view3d_operator_properties_common(ot, V3D_OP_PROP_USE_ALL_REGIONS);
3067 }
3068
3069 /** \} */
3070
3071 /* -------------------------------------------------------------------- */
3072 /** \name View Lock Clear Operator
3073  * \{ */
3074
3075 static int view_lock_clear_exec(bContext *C, wmOperator *UNUSED(op))
3076 {
3077   View3D *v3d = CTX_wm_view3d(C);
3078
3079   if (v3d) {
3080     ED_view3d_lock_clear(v3d);
3081
3082     WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, v3d);
3083
3084     return OPERATOR_FINISHED;
3085   }
3086   else {
3087     return OPERATOR_CANCELLED;
3088   }
3089 }
3090
3091 void VIEW3D_OT_view_lock_clear(wmOperatorType *ot)
3092 {
3093
3094   /* identifiers */
3095   ot->name = "View Lock Clear";
3096   ot->description = "Clear all view locking";
3097   ot->idname = "VIEW3D_OT_view_lock_clear";
3098
3099   /* api callbacks */
3100   ot->exec = view_lock_clear_exec;
3101   ot->poll = ED_operator_region_view3d_active;
3102
3103   /* flags */
3104   ot->flag = 0;
3105 }
3106
3107 /** \} */
3108
3109 /* -------------------------------------------------------------------- */
3110 /** \name View Lock to Active Operator
3111  * \{ */
3112
3113 static int view_lock_to_active_exec(bContext *C, wmOperator *UNUSED(op))
3114 {
3115   View3D *v3d = CTX_wm_view3d(C);
3116   Object *obact = CTX_data_active_object(C);
3117
3118   if (v3d) {
3119     ED_view3d_lock_clear(v3d);
3120
3121     v3d->ob_centre = obact; /* can be NULL */
3122
3123     if (obact && obact->type == OB_ARMATURE) {
3124       if (obact->mode & OB_MODE_POSE) {
3125         Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
3126         Object *obact_eval = DEG_get_evaluated_object(depsgraph, obact);
3127         bPoseChannel *pcham_act = BKE_pose_channel_active(obact_eval);
3128         if (pcham_act) {
3129           BLI_strncpy(v3d->ob_centre_bone, pcham_act->name, sizeof(v3d->ob_centre_bone));
3130         }
3131       }
3132       else {
3133         EditBone *ebone_act = ((bArmature *)obact->data)->act_edbone;
3134         if (ebone_act) {
3135           BLI_strncpy(v3d->ob_centre_bone, ebone_act->name, sizeof(v3d->ob_centre_bone));
3136         }
3137       }
3138     }
3139
3140     WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, v3d);
3141
3142     return OPERATOR_FINISHED;
3143   }
3144   else {
3145     return OPERATOR_CANCELLED;
3146   }
3147 }
3148
3149 void VIEW3D_OT_view_lock_to_active(wmOperatorType *ot)
3150 {
3151
3152   /* identifiers */
3153   ot->name = "View Lock to Active";
3154   ot->description = "Lock the view to the active object/bone";
3155   ot->idname = "VIEW3D_OT_view_lock_to_active";
3156
3157   /* api callbacks */
3158   ot->exec = view_lock_to_active_exec;
3159   ot->poll = ED_operator_region_view3d_active;
3160
3161   /* flags */
3162   ot->flag = 0;
3163 }
3164
3165 /** \} */
3166
3167 /* -------------------------------------------------------------------- */
3168 /** \name View Center Cursor Operator
3169  * \{ */
3170
3171 static int viewcenter_cursor_exec(bContext *C, wmOperator *op)
3172 {
3173   View3D *v3d = CTX_wm_view3d(C);
3174   RegionView3D *rv3d = CTX_wm_region_view3d(C);
3175   Scene *scene = CTX_data_scene(C);
3176
3177   if (rv3d) {
3178     ARegion *ar = CTX_wm_region(C);
3179     const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
3180
3181     ED_view3d_smooth_view_force_finish(C, v3d, ar);
3182
3183     /* non camera center */
3184     float new_ofs[3];
3185     negate_v3_v3(new_ofs, scene->cursor.location);
3186     ED_view3d_smooth_view(C, v3d, ar, smooth_viewtx, &(const V3D_SmoothParams){.ofs = new_ofs});
3187
3188     /* smooth view does viewlock RV3D_BOXVIEW copy */
3189   }
3190
3191   return OPERATOR_FINISHED;
3192 }
3193
3194 void VIEW3D_OT_view_center_cursor(wmOperatorType *ot)
3195 {
3196   /* identifiers */
3197   ot->name = "Center View to Cursor";
3198   ot->description = "Center the view so that the cursor is in the middle of the view";
3199   ot->idname = "VIEW3D_OT_view_center_cursor";
3200
3201   /* api callbacks */
3202   ot->exec = viewcenter_cursor_exec;
3203   ot->poll = ED_operator_view3d_active;
3204
3205   /* flags */
3206   ot->flag = 0;
3207 }
3208
3209 /** \} */
3210
3211 /* -------------------------------------------------------------------- */
3212 /** \name View Center Pick Operator
3213  * \{ */
3214
3215 static int viewcenter_pick_invoke(bContext *C, wmOperator *op, const wmEvent *event)
3216 {
3217   View3D *v3d = CTX_wm_view3d(C);
3218   RegionView3D *rv3d = CTX_wm_region_view3d(C);
3219   ARegion *ar = CTX_wm_region(C);
3220
3221   if (rv3d) {
3222     struct Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
3223     float new_ofs[3];
3224     const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
3225
3226     ED_view3d_smooth_view_force_finish(C, v3d, ar);
3227
3228     view3d_operator_needs_opengl(C);
3229
3230     if (ED_view3d_autodist(depsgraph, ar, v3d, event->mval, new_ofs, false, NULL)) {
3231       /* pass */
3232     }
3233     else {
3234       /* fallback to simple pan */
3235       negate_v3_v3(new_ofs, rv3d->ofs);
3236       ED_view3d_win_to_3d_int(v3d, ar, new_ofs, event->mval, new_ofs);
3237     }
3238     negate_v3(new_ofs);
3239     ED_view3d_smooth_view(C, v3d, ar, smooth_viewtx, &(const V3D_SmoothParams){.ofs = new_ofs});
3240   }
3241
3242   return OPERATOR_FINISHED;
3243 }
3244
3245 void VIEW3D_OT_view_center_pick(wmOperatorType *ot)
3246 {
3247   /* identifiers */
3248   ot->name = "Center View to Mouse";
3249   ot->description = "Center the view to the Z-depth position under the mouse cursor";
3250   ot->idname = "VIEW3D_OT_view_center_pick";
3251
3252   /* api callbacks */
3253   ot->invoke = viewcenter_pick_invoke;
3254   ot->poll = ED_operator_view3d_active;
3255
3256   /* flags */
3257   ot->flag = 0;
3258 }
3259
3260 /** \} */
3261
3262 /* -------------------------------------------------------------------- */
3263 /** \name View Camera Center Operator
3264  * \{ */
3265
3266 static int view3d_center_camera_exec(bContext *C, wmOperator *UNUSED(op))
3267 {
3268   Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
3269   Scene *scene = CTX_data_scene(C);
3270   float xfac, yfac;
3271   float size[2];
3272
3273   View3D *v3d;
3274   ARegion *ar;
3275   RegionView3D *rv3d;
3276
3277   /* no NULL check is needed, poll checks */
3278   ED_view3d_context_user_region(C, &v3d, &ar);
3279   rv3d = ar->regiondata;
3280
3281   rv3d->camdx = rv3d->camdy = 0.0f;
3282
3283   ED_view3d_calc_camera_border_size(scene, depsgraph, ar, v3d, rv3d, size);
3284
3285   /* 4px is just a little room from the edge of the area */
3286   xfac = (float)ar->winx / (float)(size[0] + 4);
3287   yfac = (float)ar->winy / (float)(size[1] + 4);
3288
3289   rv3d->camzoom = BKE_screen_view3d_zoom_from_fac(min_ff(xfac, yfac));
3290   CLAMP(rv3d->camzoom, RV3D_CAMZOOM_MIN, RV3D_CAMZOOM_MAX);
3291
3292   WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, v3d);
3293
3294   return OPERATOR_FINISHED;
3295 }
3296
3297 void VIEW3D_OT_view_center_camera(wmOperatorType *ot)
3298 {
3299   /* identifiers */
3300   ot->name = "View Camera Center";
3301   ot->description = "Center the camera view";
3302   ot->idname = "VIEW3D_OT_view_center_camera";
3303
3304   /* api callbacks */
3305   ot->exec = view3d_center_camera_exec;
3306   ot->poll = view3d_camera_user_poll;
3307
3308   /* flags */
3309   ot->flag = 0;
3310 }
3311
3312 /** \} */
3313
3314 /* -------------------------------------------------------------------- */
3315 /** \name View Lock Center Operator
3316  * \{ */
3317
3318 static int view3d_center_lock_exec(bContext *C, wmOperator *UNUSED(op))
3319 {
3320   RegionView3D *rv3d = CTX_wm_region_view3d(C);
3321
3322   zero_v2(rv3d->ofs_lock);
3323
3324   WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, CTX_wm_view3d(C));
3325
3326   return OPERATOR_FINISHED;
3327 }
3328
3329 void VIEW3D_OT_view_center_lock(wmOperatorType *ot)
3330 {
3331   /* identifiers */
3332   ot->name = "View Lock Center";
3333   ot->description = "Center the view lock offset";
3334   ot->idname = "VIEW3D_OT_view_center_lock";
3335
3336   /* api callbacks */
3337   ot->exec = view3d_center_lock_exec;
3338   ot->poll = view3d_lock_poll;
3339
3340   /* flags */
3341   ot->flag = 0;
3342 }
3343
3344 /** \} */
3345
3346 /* -------------------------------------------------------------------- */
3347 /** \name Set Render Border Operator
3348  * \{ */
3349
3350 static int render_border_exec(bContext *C, wmOperator *op)
3351 {
3352   View3D *v3d = CTX_wm_view3d(C);
3353   ARegion *ar = CTX_wm_region(C);
3354   RegionView3D *rv3d = ED_view3d_context_rv3d(C);
3355
3356   Scene *scene = CTX_data_scene(C);
3357
3358   rcti rect;
3359   rctf vb, border;
3360
3361   /* get box select values using rna */
3362   WM_operator_properties_border_to_rcti(op, &rect);
3363
3364   /* calculate range */
3365
3366   if (rv3d->persp == RV3D_CAMOB) {
3367     Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
3368     ED_view3d_calc_camera_border(scene, depsgraph, ar, v3d, rv3d, &vb, false);
3369   }
3370   else {
3371     vb.xmin = 0;
3372     vb.ymin = 0;
3373     vb.xmax = ar->winx;
3374     vb.ymax = ar->winy;
3375   }
3376
3377   border.xmin = ((float)rect.xmin - vb.xmin) / BLI_rctf_size_x(&vb);
3378   border.ymin = ((float)rect.ymin - vb.ymin) / BLI_rctf_size_y(&vb);
3379   border.xmax = ((float)rect.xmax - vb.xmin) / BLI_rctf_size_x(&vb);
3380   border.ymax = ((float)rect.ymax - vb.ymin) / BLI_rctf_size_y(&vb);
3381
3382   /* actually set border */
3383   CLAMP(border.xmin, 0.0f, 1.0f);
3384   CLAMP(border.ymin, 0.0f, 1.0f);
3385   CLAMP(border.xmax, 0.0f, 1.0f);
3386   CLAMP(border.ymax, 0.0f, 1.0f);
3387
3388   if (rv3d->persp == RV3D_CAMOB) {
3389     scene->r.border = border;
3390
3391     WM_event_add_notifier(C, NC_SCENE | ND_RENDER_OPTIONS, NULL);
3392   }
3393   else {
3394     v3d->render_border = border;
3395
3396     WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, NULL);
3397   }
3398
3399   /* drawing a border outside the camera view switches off border rendering */
3400   if ((border.xmin == border.xmax || border.ymin == border.ymax)) {
3401     if (rv3d->persp == RV3D_CAMOB) {
3402       scene->r.mode &= ~R_BORDER;
3403     }
3404     else {
3405       v3d->flag2 &= ~V3D_RENDER_BORDER;
3406     }
3407   }
3408   else {
3409     if (rv3d->persp == RV3D_CAMOB) {
3410       scene->r.mode |= R_BORDER;
3411     }
3412     else {
3413       v3d->flag2 |= V3D_RENDER_BORDER;
3414     }
3415   }
3416
3417   if (rv3d->persp == RV3D_CAMOB) {
3418     DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE);
3419   }
3420   return OPERATOR_FINISHED;
3421 }
3422
3423 void VIEW3D_OT_render_border(wmOperatorType *ot)
3424 {
3425   /* identifiers */
3426   ot->name = "Set Render Region";
3427   ot->description = "Set the boundaries of the border render and enable border render";
3428   ot->idname = "VIEW3D_OT_render_border";
3429
3430   /* api callbacks */
3431   ot->invoke = WM_gesture_box_invoke;
3432   ot->exec = render_border_exec;
3433   ot->modal = WM_gesture_box_modal;
3434   ot->cancel = WM_gesture_box_cancel;
3435
3436   ot->poll = ED_operator_view3d_active;
3437
3438   /* flags */
3439   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3440
3441   /* properties */
3442   WM_operator_properties_border(ot);
3443 }
3444
3445 /** \} */
3446
3447 /* -------------------------------------------------------------------- */
3448 /** \name Clear Render Border Operator
3449  * \{ */
3450
3451 static int clear_render_border_exec(bContext *C, wmOperator *UNUSED(op))
3452 {
3453   View3D *v3d = CTX_wm_view3d(C);
3454   RegionView3D *rv3d = ED_view3d_context_rv3d(C);
3455
3456   Scene *scene = CTX_data_scene(C);
3457   rctf *border = NULL;
3458
3459   if (rv3d->persp == RV3D_CAMOB) {
3460     scene->r.mode &= ~R_BORDER;
3461     border = &scene->r.border;
3462
3463     WM_event_add_notifier(C, NC_SCENE | ND_RENDER_OPTIONS, NULL);
3464   }
3465   else {
3466     v3d->flag2 &= ~V3D_RENDER_BORDER;
3467     border = &v3d->render_border;
3468
3469     WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, NULL);
3470   }
3471
3472   border->xmin = 0.0f;
3473   border->ymin = 0.0f;
3474   border->xmax = 1.0f;
3475   border->ymax = 1.0f;
3476
3477   if (rv3d->persp == RV3D_CAMOB) {
3478     DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE);
3479   }
3480   return OPERATOR_FINISHED;
3481 }
3482
3483 void VIEW3D_OT_clear_render_border(wmOperatorType *ot)
3484 {
3485   /* identifiers */
3486   ot->name = "Clear Render Region";
3487   ot->description = "Clear the boundaries of the border render and disable border render";
3488   ot->idname = "VIEW3D_OT_clear_render_border";
3489
3490   /* api callbacks */
3491   ot->exec = clear_render_border_exec;
3492   ot->poll = ED_operator_view3d_active;
3493
3494   /* flags */
3495   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3496 }
3497
3498 /** \} */
3499
3500 /* -------------------------------------------------------------------- */
3501 /** \name Border Zoom Operator
3502  * \{ */
3503
3504 static int view3d_zoom_border_exec(bContext *C, wmOperator *op)
3505 {
3506   ARegion *ar = CTX_wm_region(C);
3507   View3D *v3d = CTX_wm_view3d(C);
3508   RegionView3D *rv3d = CTX_wm_region_view3d(C);
3509   const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
3510
3511   /* Zooms in on a border drawn by the user */
3512   rcti rect;
3513   float dvec[3], vb[2], xscale, yscale;
3514   float dist_range[2];
3515
3516   /* SMOOTHVIEW */
3517   float new_dist;
3518   float new_ofs[3];
3519
3520   /* ZBuffer depth vars */
3521   float depth_close = FLT_MAX;
3522   float cent[2], p[3];
3523
3524   /* note; otherwise opengl won't work */
3525   view3d_operator_needs_opengl(C);
3526
3527   /* get box select values using rna */
3528   WM_operator_properties_border_to_rcti(op, &rect);
3529
3530   /* check if zooming in/out view */
3531   const bool zoom_in = !RNA_boolean_get(op->ptr, "zoom_out");
3532
3533   ED_view3d_dist_range_get(v3d, dist_range);
3534
3535   /* Get Z Depths, needed for perspective, nice for ortho */
3536   ED_view3d_draw_depth(CTX_data_ensure_evaluated_depsgraph(C), ar, v3d, true);
3537
3538   {
3539     /* avoid allocating the whole depth buffer */
3540     ViewDepths depth_temp = {0};
3541
3542     /* avoid view3d_update_depths() for speed. */
3543     view3d_update_depths_rect(ar, &depth_temp, &rect);
3544
3545     /* find the closest Z pixel */
3546     depth_close = view3d_depth_near(&depth_temp);
3547
3548     MEM_SAFE_FREE(depth_temp.depths);
3549   }
3550
3551   cent[0] = (((float)rect.xmin) + ((float)rect.xmax)) / 2;
3552   cent[1] = (((float)rect.ymin) + ((float)rect.ymax)) / 2;
3553
3554   if (rv3d->is_persp) {
3555     float p_corner[3];
3556
3557     /* no depths to use, we cant do anything! */
3558     if (depth_close == FLT_MAX) {
3559       BKE_report(op->reports, RPT_ERROR, "Depth too large");
3560       return OPERATOR_CANCELLED;
3561     }
3562     /* convert border to 3d coordinates */
3563     if ((!ED_view3d_unproject(ar, cent[0], cent[1], depth_close, p)) ||
3564         (!ED_view3d_unproject(ar, rect.xmin, rect.ymin, depth_close, p_corner))) {
3565       return OPERATOR_CANCELLED;
3566     }
3567
3568     sub_v3_v3v3(dvec, p, p_corner);
3569     negate_v3_v3(new_ofs, p);
3570
3571     new_dist = len_v3(dvec);
3572
3573     /* ignore dist_range min */
3574     dist_range[0] = v3d->clip_start * 1.5f;
3575   }
3576   else { /* othographic */
3577     /* find the current window width and height */
3578     vb[0] = ar->winx;
3579     vb[1] = ar->winy;
3580
3581     new_dist = rv3d->dist;
3582
3583     /* convert the drawn rectangle into 3d space */
3584     if (depth_close != FLT_MAX && ED_view3d_unproject(ar, cent[0], cent[1], depth_close, p)) {
3585       negate_v3_v3(new_ofs, p);
3586     }
3587     else {
3588       float mval_f[2];
3589       float zfac;
3590
3591       /* We can't use the depth, fallback to the old way that doesn't set the center depth */
3592       copy_v3_v3(new_ofs, rv3d->ofs);
3593
3594       {
3595         float tvec[3];
3596         negate_v3_v3(tvec, new_ofs);
3597         zfac = ED_view3d_calc_zfac(rv3d, tvec, NULL);
3598       }
3599
3600       mval_f[0] = (rect.xmin + rect.xmax - vb[0]) / 2.0f;
3601       mval_f[1] = (rect.ymin + rect.ymax - vb[1]) / 2.0f;
3602       ED_view3d_win_to_delta(ar, mval_f, dvec, zfac);
3603       /* center the view to the center of the rectangle */
3604       sub_v3_v3(new_ofs, dvec);
3605     }
3606
3607     /* work out the ratios, so that everything selected fits when we zoom */
3608     xscale = (BLI_rcti_size_x(&rect) / vb[0]);
3609     yscale = (BLI_rcti_size_y(&rect) / vb[1]);
3610     new_dist *= max_ff(xscale, yscale);
3611   }
3612
3613   if (!zoom_in) {
3614     sub_v3_v3v3(dvec, new_ofs, rv3d->ofs);
3615     new_dist = rv3d->dist * (rv3d->dist / new_dist);
3616     add_v3_v3v3(new_ofs, rv3d->ofs, dvec);
3617   }
3618
3619   /* clamp after because we may have been zooming out */
3620   CLAMP(new_dist, dist_range[0], dist_range[1]);
3621
3622   /* TODO(campbell): 'is_camera_lock' not currently working well. */
3623   const bool is_camera_lock = ED_view3d_camera_lock_check(v3d, rv3d);
3624   if ((rv3d->persp == RV3D_CAMOB) && (is_camera_lock == false)) {
3625     Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
3626     ED_view3d_persp_switch_from_camera(depsgraph, v3d, rv3d, RV3D_PERSP);
3627   }
3628
3629   ED_view3d_smooth_view(C,
3630                         v3d,
3631                         ar,
3632                         smooth_viewtx,
3633                         &(const V3D_SmoothParams){
3634                             .ofs = new_ofs,
3635                             .dist = &new_dist,
3636                         });
3637
3638   if (rv3d->viewlock & RV3D_BOXVIEW) {
3639     view3d_boxview_sync(CTX_wm_area(C), ar);
3640   }
3641
3642   return OPERATOR_FINISHED;
3643 }
3644
3645 void VIEW3D_OT_zoom_border(wmOperatorType *ot)
3646 {
3647   /* identifiers */
3648   ot->name = "Zoom to Border";
3649   ot->description = "Zoom in the view to the nearest object contained in the border";
3650   ot->idname = "VIEW3D_OT_zoom_border";
3651
3652   /* api callbacks */
3653   ot->invoke = WM_gesture_box_invoke;
3654   ot->exec = view3d_zoom_border_exec;
3655   ot->modal = WM_gesture_box_modal;
3656   ot->cancel = WM_gesture_box_cancel;
3657
3658   ot->poll = ED_operator_region_view3d_active;
3659
3660   /* flags */
3661   ot->flag = 0;
3662
3663   /* properties */
3664   WM_operator_properties_gesture_box_zoom(ot);
3665 }
3666
3667 /** \} */
3668
3669 /* -------------------------------------------------------------------- */
3670 /** \name Set Camera Zoom 1:1 Operator
3671  *
3672  * Sets the view to 1:1 camera/