Cleanup: correct wireframe xray flag name
[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 vec[3])
189 {
190   const float radius = TRACKBALLSIZE;
191   const float t = radius / (float)M_SQRT2;
192   float x, y, z, d;
193
194   /* normalize x and y */
195   x = BLI_rcti_cent_x(rect) - event_xy[0];
196   x /= (float)(BLI_rcti_size_x(rect) / 4);
197   y = BLI_rcti_cent_y(rect) - event_xy[1];
198   y /= (float)(BLI_rcti_size_y(rect) / 2);
199   d = sqrtf(x * x + y * y);
200   if (d < t) { /* Inside sphere */
201     z = sqrtf(radius * radius - d * d);
202   }
203   else { /* On hyperbola */
204     z = t * t / d;
205   }
206
207   vec[0] = x;
208   vec[1] = y;
209   vec[2] = -z; /* yah yah! */
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_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_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_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     /* Allow for rotation beyond the interval [-pi, pi] */
733     angle = angle_wrap_rad(angle);
734
735     /* This relation is used instead of the actual angle between vectors
736      * so that the angle of rotation is linearly proportional to
737      * the distance that the mouse is dragged. */
738
739     cross_v3_v3v3(axis, vod->init.trackvec, newvec);
740     axis_angle_to_quat(q1, axis, angle);
741
742     mul_qt_qtqt(vod->curr.viewquat, q1, vod->init.quat);
743
744     viewrotate_apply_dyn_ofs(vod, vod->curr.viewquat);
745   }
746   else {
747     /* New turntable view code by John Aughey */
748     float quat_local_x[4], quat_global_z[4];
749     float m[3][3];
750     float m_inv[3][3];
751     const float zvec_global[3] = {0.0f, 0.0f, 1.0f};
752     float xaxis[3];
753
754     /* Sensitivity will control how fast the viewport rotates.  0.007 was
755      * obtained experimentally by looking at viewport rotation sensitivities
756      * on other modeling programs. */
757     /* Perhaps this should be a configurable user parameter. */
758     const float sensitivity = 0.007f;
759
760     /* Get the 3x3 matrix and its inverse from the quaternion */
761     quat_to_mat3(m, vod->curr.viewquat);
762     invert_m3_m3(m_inv, m);
763
764     /* avoid gimble lock */
765 #if 1
766     if (len_squared_v3v3(zvec_global, m_inv[2]) > 0.001f) {
767       float fac;
768       cross_v3_v3v3(xaxis, zvec_global, m_inv[2]);
769       if (dot_v3v3(xaxis, m_inv[0]) < 0) {
770         negate_v3(xaxis);
771       }
772       fac = angle_normalized_v3v3(zvec_global, m_inv[2]) / (float)M_PI;
773       fac = fabsf(fac - 0.5f) * 2;
774       fac = fac * fac;
775       interp_v3_v3v3(xaxis, xaxis, m_inv[0], fac);
776     }
777     else {
778       copy_v3_v3(xaxis, m_inv[0]);
779     }
780 #else
781     copy_v3_v3(xaxis, m_inv[0]);
782 #endif
783
784     /* Determine the direction of the x vector (for rotating up and down) */
785     /* This can likely be computed directly from the quaternion. */
786
787     /* Perform the up/down rotation */
788     axis_angle_to_quat(quat_local_x, xaxis, sensitivity * -(event_xy[1] - vod->prev.event_xy[1]));
789     mul_qt_qtqt(quat_local_x, vod->curr.viewquat, quat_local_x);
790
791     /* Perform the orbital rotation */
792     axis_angle_to_quat_single(
793         quat_global_z, 'Z', sensitivity * vod->reverse * (event_xy[0] - vod->prev.event_xy[0]));
794     mul_qt_qtqt(vod->curr.viewquat, quat_local_x, quat_global_z);
795
796     viewrotate_apply_dyn_ofs(vod, vod->curr.viewquat);
797   }
798
799   /* avoid precision loss over time */
800   normalize_qt(vod->curr.viewquat);
801
802   /* use a working copy so view rotation locking doesn't overwrite the locked
803    * rotation back into the view we calculate with */
804   copy_qt_qt(rv3d->viewquat, vod->curr.viewquat);
805
806   /* check for view snap,
807    * note: don't apply snap to vod->viewquat so the view wont jam up */
808   if (vod->axis_snap) {
809     viewrotate_apply_snap(vod);
810   }
811   vod->prev.event_xy[0] = event_xy[0];
812   vod->prev.event_xy[1] = event_xy[1];
813
814   ED_view3d_camera_lock_sync(vod->depsgraph, vod->v3d, rv3d);
815
816   ED_region_tag_redraw(vod->ar);
817 }
818
819 static int viewrotate_modal(bContext *C, wmOperator *op, const wmEvent *event)
820 {
821   ViewOpsData *vod = op->customdata;
822   short event_code = VIEW_PASS;
823   bool use_autokey = false;
824   int ret = OPERATOR_RUNNING_MODAL;
825
826   /* execute the events */
827   if (event->type == MOUSEMOVE) {
828     event_code = VIEW_APPLY;
829   }
830   else if (event->type == EVT_MODAL_MAP) {
831     switch (event->val) {
832       case VIEW_MODAL_CONFIRM:
833         event_code = VIEW_CONFIRM;
834         break;
835       case VIEWROT_MODAL_AXIS_SNAP_ENABLE:
836         vod->axis_snap = true;
837         event_code = VIEW_APPLY;
838         break;
839       case VIEWROT_MODAL_AXIS_SNAP_DISABLE:
840         vod->axis_snap = false;
841         event_code = VIEW_APPLY;
842         break;
843       case VIEWROT_MODAL_SWITCH_ZOOM:
844         WM_operator_name_call(C, "VIEW3D_OT_zoom", WM_OP_INVOKE_DEFAULT, NULL);
845         event_code = VIEW_CONFIRM;
846         break;
847       case VIEWROT_MODAL_SWITCH_MOVE:
848         WM_operator_name_call(C, "VIEW3D_OT_move", WM_OP_INVOKE_DEFAULT, NULL);
849         event_code = VIEW_CONFIRM;
850         break;
851     }
852   }
853   else if (event->type == vod->init.event_type && event->val == KM_RELEASE) {
854     event_code = VIEW_CONFIRM;
855   }
856
857   if (event_code == VIEW_APPLY) {
858     viewrotate_apply(vod, &event->x);
859     if (ED_screen_animation_playing(CTX_wm_manager(C))) {
860       use_autokey = true;
861     }
862   }
863   else if (event_code == VIEW_CONFIRM) {
864     ED_view3d_depth_tag_update(vod->rv3d);
865     use_autokey = true;
866     ret = OPERATOR_FINISHED;
867   }
868
869   if (use_autokey) {
870     ED_view3d_camera_lock_autokey(vod->v3d, vod->rv3d, C, true, true);
871   }
872
873   if (ret & OPERATOR_FINISHED) {
874     viewops_data_free(C, op);
875   }
876
877   return ret;
878 }
879
880 static int viewrotate_invoke(bContext *C, wmOperator *op, const wmEvent *event)
881 {
882   ViewOpsData *vod;
883
884   const bool use_cursor_init = RNA_boolean_get(op->ptr, "use_cursor_init");
885
886   /* makes op->customdata */
887   viewops_data_alloc(C, op);
888   vod = op->customdata;
889
890   /* poll should check but in some cases fails, see poll func for details */
891   if (vod->rv3d->viewlock & RV3D_LOCKED) {
892     viewops_data_free(C, op);
893     return OPERATOR_PASS_THROUGH;
894   }
895
896   ED_view3d_smooth_view_force_finish(C, vod->v3d, vod->ar);
897
898   viewops_data_create(C,
899                       op,
900                       event,
901                       viewops_flag_from_prefs() | VIEWOPS_FLAG_PERSP_ENSURE |
902                           (use_cursor_init ? VIEWOPS_FLAG_USE_MOUSE_INIT : 0));
903
904   if (ELEM(event->type, MOUSEPAN, MOUSEROTATE)) {
905     /* Rotate direction we keep always same */
906     int event_xy[2];
907
908     if (event->type == MOUSEPAN) {
909       if (U.uiflag2 & USER_TRACKPAD_NATURAL) {
910         event_xy[0] = 2 * event->x - event->prevx;
911         event_xy[1] = 2 * event->y - event->prevy;
912       }
913       else {
914         event_xy[0] = event->prevx;
915         event_xy[1] = event->prevy;
916       }
917     }
918     else {
919       /* MOUSEROTATE performs orbital rotation, so y axis delta is set to 0 */
920       event_xy[0] = event->prevx;
921       event_xy[1] = event->y;
922     }
923
924     viewrotate_apply(vod, event_xy);
925     ED_view3d_depth_tag_update(vod->rv3d);
926
927     viewops_data_free(C, op);
928
929     return OPERATOR_FINISHED;
930   }
931   else {
932     /* add temp handler */
933     WM_event_add_modal_handler(C, op);
934
935     return OPERATOR_RUNNING_MODAL;
936   }
937 }
938
939 /* test for unlocked camera view in quad view */
940 static bool view3d_camera_user_poll(bContext *C)
941 {
942   View3D *v3d;
943   ARegion *ar;
944
945   if (ED_view3d_context_user_region(C, &v3d, &ar)) {
946     RegionView3D *rv3d = ar->regiondata;
947     if (rv3d->persp == RV3D_CAMOB) {
948       return 1;
949     }
950   }
951
952   return 0;
953 }
954
955 static bool view3d_lock_poll(bContext *C)
956 {
957   View3D *v3d = CTX_wm_view3d(C);
958   if (v3d) {
959     RegionView3D *rv3d = CTX_wm_region_view3d(C);
960     if (rv3d) {
961       return ED_view3d_offset_lock_check(v3d, rv3d);
962     }
963   }
964   return false;
965 }
966
967 static void viewrotate_cancel(bContext *C, wmOperator *op)
968 {
969   viewops_data_free(C, op);
970 }
971
972 void VIEW3D_OT_rotate(wmOperatorType *ot)
973 {
974   /* identifiers */
975   ot->name = "Rotate View";
976   ot->description = "Rotate the view";
977   ot->idname = "VIEW3D_OT_rotate";
978
979   /* api callbacks */
980   ot->invoke = viewrotate_invoke;
981   ot->modal = viewrotate_modal;
982   ot->poll = ED_operator_region_view3d_active;
983   ot->cancel = viewrotate_cancel;
984
985   /* flags */
986   ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_XY;
987
988   view3d_operator_properties_common(ot, V3D_OP_PROP_USE_MOUSE_INIT);
989 }
990
991 /** \} */
992
993 /* -------------------------------------------------------------------- */
994 /** \name NDOF Utility Functions
995  * \{ */
996
997 #ifdef WITH_INPUT_NDOF
998 #  define NDOF_HAS_TRANSLATE ((!ED_view3d_offset_lock_check(v3d, rv3d)) && !is_zero_v3(ndof->tvec))
999 #  define NDOF_HAS_ROTATE (((rv3d->viewlock & RV3D_LOCKED) == 0) && !is_zero_v3(ndof->rvec))
1000
1001 /**
1002  * \param depth_pt: A point to calculate the depth (in perspective mode)
1003  */
1004 static float view3d_ndof_pan_speed_calc_ex(RegionView3D *rv3d, const float depth_pt[3])
1005 {
1006   float speed = rv3d->pixsize * NDOF_PIXELS_PER_SECOND;
1007
1008   if (rv3d->is_persp) {
1009     speed *= ED_view3d_calc_zfac(rv3d, depth_pt, NULL);
1010   }
1011
1012   return speed;
1013 }
1014
1015 static float view3d_ndof_pan_speed_calc_from_dist(RegionView3D *rv3d, const float dist)
1016 {
1017   float viewinv[4];
1018   float tvec[3];
1019
1020   BLI_assert(dist >= 0.0f);
1021
1022   copy_v3_fl3(tvec, 0.0f, 0.0f, dist);
1023   /* rv3d->viewinv isn't always valid */
1024 #  if 0
1025   mul_mat3_m4_v3(rv3d->viewinv, tvec);
1026 #  else
1027   invert_qt_qt_normalized(viewinv, rv3d->viewquat);
1028   mul_qt_v3(viewinv, tvec);
1029 #  endif
1030
1031   return view3d_ndof_pan_speed_calc_ex(rv3d, tvec);
1032 }
1033
1034 static float view3d_ndof_pan_speed_calc(RegionView3D *rv3d)
1035 {
1036   float tvec[3];
1037   negate_v3_v3(tvec, rv3d->ofs);
1038
1039   return view3d_ndof_pan_speed_calc_ex(rv3d, tvec);
1040 }
1041
1042 /**
1043  * Zoom and pan in the same function since sometimes zoom is interpreted as dolly (pan forward).
1044  *
1045  * \param has_zoom: zoom, otherwise dolly,
1046  * often `!rv3d->is_persp` since it doesn't make sense to dolly in ortho.
1047  */
1048 static void view3d_ndof_pan_zoom(const struct wmNDOFMotionData *ndof,
1049                                  ScrArea *sa,
1050                                  ARegion *ar,
1051                                  const bool has_translate,
1052                                  const bool has_zoom)
1053 {
1054   RegionView3D *rv3d = ar->regiondata;
1055   float view_inv[4];
1056   float pan_vec[3];
1057
1058   if (has_translate == false && has_zoom == false) {
1059     return;
1060   }
1061
1062   WM_event_ndof_pan_get(ndof, pan_vec, false);
1063
1064   if (has_zoom) {
1065     /* zoom with Z */
1066
1067     /* Zoom!
1068      * velocity should be proportional to the linear velocity attained by rotational motion
1069      * of same strength [got that?] proportional to `arclength = radius * angle`.
1070      */
1071
1072     pan_vec[2] = 0.0f;
1073
1074     /* "zoom in" or "translate"? depends on zoom mode in user settings? */
1075     if (ndof->tvec[2]) {
1076       float zoom_distance = rv3d->dist * ndof->dt * ndof->tvec[2];
1077
1078       if (U.ndof_flag & NDOF_ZOOM_INVERT) {
1079         zoom_distance = -zoom_distance;
1080       }
1081
1082       rv3d->dist += zoom_distance;
1083     }
1084   }
1085   else {
1086     /* dolly with Z */
1087
1088     /* all callers must check */
1089     if (has_translate) {
1090       BLI_assert(ED_view3d_offset_lock_check((View3D *)sa->spacedata.first, rv3d) == false);
1091     }
1092   }
1093
1094   if (has_translate) {
1095     const float speed = view3d_ndof_pan_speed_calc(rv3d);
1096
1097     mul_v3_fl(pan_vec, speed * ndof->dt);
1098
1099     /* transform motion from view to world coordinates */
1100     invert_qt_qt_normalized(view_inv, rv3d->viewquat);
1101     mul_qt_v3(view_inv, pan_vec);
1102
1103     /* move center of view opposite of hand motion (this is camera mode, not object mode) */
1104     sub_v3_v3(rv3d->ofs, pan_vec);
1105
1106     if (rv3d->viewlock & RV3D_BOXVIEW) {
1107       view3d_boxview_sync(sa, ar);
1108     }
1109   }
1110 }
1111
1112 static void view3d_ndof_orbit(const struct wmNDOFMotionData *ndof,
1113                               ScrArea *sa,
1114                               ARegion *ar,
1115                               ViewOpsData *vod,
1116                               const bool apply_dyn_ofs)
1117 {
1118   View3D *v3d = sa->spacedata.first;
1119   RegionView3D *rv3d = ar->regiondata;
1120
1121   float view_inv[4];
1122
1123   BLI_assert((rv3d->viewlock & RV3D_LOCKED) == 0);
1124
1125   ED_view3d_persp_ensure(vod->depsgraph, v3d, ar);
1126
1127   rv3d->view = RV3D_VIEW_USER;
1128
1129   invert_qt_qt_normalized(view_inv, rv3d->viewquat);
1130
1131   if (U.ndof_flag & NDOF_TURNTABLE) {
1132     float rot[3];
1133
1134     /* turntable view code by John Aughey, adapted for 3D mouse by [mce] */
1135     float angle, quat[4];
1136     float xvec[3] = {1, 0, 0};
1137
1138     /* only use XY, ignore Z */
1139     WM_event_ndof_rotate_get(ndof, rot);
1140
1141     /* Determine the direction of the x vector (for rotating up and down) */
1142     mul_qt_v3(view_inv, xvec);
1143
1144     /* Perform the up/down rotation */
1145     angle = ndof->dt * rot[0];
1146     axis_angle_to_quat(quat, xvec, angle);
1147     mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, quat);
1148
1149     /* Perform the orbital rotation */
1150     angle = ndof->dt * rot[1];
1151
1152     /* update the onscreen doo-dad */
1153     rv3d->rot_angle = angle;
1154     rv3d->rot_axis[0] = 0;
1155     rv3d->rot_axis[1] = 0;
1156     rv3d->rot_axis[2] = 1;
1157
1158     axis_angle_to_quat_single(quat, 'Z', angle);
1159     mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, quat);
1160   }
1161   else {
1162     float quat[4];
1163     float axis[3];
1164     float angle = WM_event_ndof_to_axis_angle(ndof, axis);
1165
1166     /* transform rotation axis from view to world coordinates */
1167     mul_qt_v3(view_inv, axis);
1168
1169     /* update the onscreen doo-dad */
1170     rv3d->rot_angle = angle;
1171     copy_v3_v3(rv3d->rot_axis, axis);
1172
1173     axis_angle_to_quat(quat, axis, angle);
1174
1175     /* apply rotation */
1176     mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, quat);
1177   }
1178
1179   if (apply_dyn_ofs) {
1180     viewrotate_apply_dyn_ofs(vod, rv3d->viewquat);
1181   }
1182 }
1183
1184 /**
1185  * Called from both fly mode and walk mode,
1186  */
1187 void view3d_ndof_fly(const wmNDOFMotionData *ndof,
1188                      View3D *v3d,
1189                      RegionView3D *rv3d,
1190                      const bool use_precision,
1191                      const short protectflag,
1192                      bool *r_has_translate,
1193                      bool *r_has_rotate)
1194 {
1195   bool has_translate = NDOF_HAS_TRANSLATE;
1196   bool has_rotate = NDOF_HAS_ROTATE;
1197
1198   float view_inv[4];
1199   invert_qt_qt_normalized(view_inv, rv3d->viewquat);
1200
1201   rv3d->rot_angle = 0.0f; /* disable onscreen rotation doo-dad */
1202
1203   if (has_translate) {
1204     /* ignore real 'dist' since fly has its own speed settings,
1205      * also its overwritten at this point. */
1206     float speed = view3d_ndof_pan_speed_calc_from_dist(rv3d, 1.0f);
1207     float trans[3], trans_orig_y;
1208
1209     if (use_precision) {
1210       speed *= 0.2f;
1211     }
1212
1213     WM_event_ndof_pan_get(ndof, trans, false);
1214     mul_v3_fl(trans, speed * ndof->dt);
1215     trans_orig_y = trans[1];
1216
1217     if (U.ndof_flag & NDOF_FLY_HELICOPTER) {
1218       trans[1] = 0.0f;
1219     }
1220
1221     /* transform motion from view to world coordinates */
1222     mul_qt_v3(view_inv, trans);
1223
1224     if (U.ndof_flag & NDOF_FLY_HELICOPTER) {
1225       /* replace world z component with device y (yes it makes sense) */
1226       trans[2] = trans_orig_y;
1227     }
1228
1229     if (rv3d->persp == RV3D_CAMOB) {
1230       /* respect camera position locks */
1231       if (protectflag & OB_LOCK_LOCX) {
1232         trans[0] = 0.0f;
1233       }
1234       if (protectflag & OB_LOCK_LOCY) {
1235         trans[1] = 0.0f;
1236       }
1237       if (protectflag & OB_LOCK_LOCZ) {
1238         trans[2] = 0.0f;
1239       }
1240     }
1241
1242     if (!is_zero_v3(trans)) {
1243       /* move center of view opposite of hand motion
1244        * (this is camera mode, not object mode) */
1245       sub_v3_v3(rv3d->ofs, trans);
1246       has_translate = true;
1247     }
1248     else {
1249       has_translate = false;
1250     }
1251   }
1252
1253   if (has_rotate) {
1254     const float turn_sensitivity = 1.0f;
1255
1256     float rotation[4];
1257     float axis[3];
1258     float angle = turn_sensitivity * WM_event_ndof_to_axis_angle(ndof, axis);
1259
1260     if (fabsf(angle) > 0.0001f) {
1261       has_rotate = true;
1262
1263       if (use_precision) {
1264         angle *= 0.2f;
1265       }
1266
1267       /* transform rotation axis from view to world coordinates */
1268       mul_qt_v3(view_inv, axis);
1269
1270       /* apply rotation to view */
1271       axis_angle_to_quat(rotation, axis, angle);
1272       mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, rotation);
1273
1274       if (U.ndof_flag & NDOF_LOCK_HORIZON) {
1275         /* force an upright viewpoint
1276          * TODO: make this less... sudden */
1277         float view_horizon[3] = {1.0f, 0.0f, 0.0f};    /* view +x */
1278         float view_direction[3] = {0.0f, 0.0f, -1.0f}; /* view -z (into screen) */
1279
1280         /* find new inverse since viewquat has changed */
1281         invert_qt_qt_normalized(view_inv, rv3d->viewquat);
1282         /* could apply reverse rotation to existing view_inv to save a few cycles */
1283
1284         /* transform view vectors to world coordinates */
1285         mul_qt_v3(view_inv, view_horizon);
1286         mul_qt_v3(view_inv, view_direction);
1287
1288         /* find difference between view & world horizons
1289          * true horizon lives in world xy plane, so look only at difference in z */
1290         angle = -asinf(view_horizon[2]);
1291
1292         /* rotate view so view horizon = world horizon */
1293         axis_angle_to_quat(rotation, view_direction, angle);
1294         mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, rotation);
1295       }
1296
1297       rv3d->view = RV3D_VIEW_USER;
1298     }
1299     else {
1300       has_rotate = false;
1301     }
1302   }
1303
1304   *r_has_translate = has_translate;
1305   *r_has_rotate = has_rotate;
1306 }
1307
1308 /** \} */
1309
1310 /* -------------------------------------------------------------------- */
1311 /** \name NDOF Orbit/Translate Operator
1312  * \{ */
1313
1314 static int ndof_orbit_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1315 {
1316   if (event->type != NDOF_MOTION) {
1317     return OPERATOR_CANCELLED;
1318   }
1319
1320   const Depsgraph *depsgraph = CTX_data_depsgraph(C);
1321   ViewOpsData *vod;
1322   View3D *v3d;
1323   RegionView3D *rv3d;
1324   char xform_flag = 0;
1325
1326   const wmNDOFMotionData *ndof = event->customdata;
1327
1328   viewops_data_alloc(C, op);
1329   viewops_data_create(
1330       C, op, event, viewops_flag_from_args((U.uiflag & USER_ORBIT_SELECTION) != 0, false));
1331   vod = op->customdata;
1332
1333   ED_view3d_smooth_view_force_finish(C, vod->v3d, vod->ar);
1334
1335   v3d = vod->v3d;
1336   rv3d = vod->rv3d;
1337
1338   /* off by default, until changed later this function */
1339   rv3d->rot_angle = 0.0f;
1340
1341   ED_view3d_camera_lock_init_ex(depsgraph, v3d, rv3d, false);
1342
1343   if (ndof->progress != P_FINISHING) {
1344     const bool has_rotation = NDOF_HAS_ROTATE;
1345     /* if we can't rotate, fallback to translate (locked axis views) */
1346     const bool has_translate = NDOF_HAS_TRANSLATE && (rv3d->viewlock & RV3D_LOCKED);
1347     const bool has_zoom = (ndof->tvec[2] != 0.0f) && !rv3d->is_persp;
1348
1349     if (has_translate || has_zoom) {
1350       view3d_ndof_pan_zoom(ndof, vod->sa, vod->ar, has_translate, has_zoom);
1351       xform_flag |= HAS_TRANSLATE;
1352     }
1353
1354     if (has_rotation) {
1355       view3d_ndof_orbit(ndof, vod->sa, vod->ar, vod, true);
1356       xform_flag |= HAS_ROTATE;
1357     }
1358   }
1359
1360   ED_view3d_camera_lock_sync(depsgraph, v3d, rv3d);
1361   if (xform_flag) {
1362     ED_view3d_camera_lock_autokey(
1363         v3d, rv3d, C, xform_flag & HAS_ROTATE, xform_flag & HAS_TRANSLATE);
1364   }
1365
1366   ED_region_tag_redraw(vod->ar);
1367
1368   viewops_data_free(C, op);
1369
1370   return OPERATOR_FINISHED;
1371 }
1372
1373 void VIEW3D_OT_ndof_orbit(struct wmOperatorType *ot)
1374 {
1375   /* identifiers */
1376   ot->name = "NDOF Orbit View";
1377   ot->description = "Orbit the view using the 3D mouse";
1378   ot->idname = "VIEW3D_OT_ndof_orbit";
1379
1380   /* api callbacks */
1381   ot->invoke = ndof_orbit_invoke;
1382   ot->poll = ED_operator_view3d_active;
1383
1384   /* flags */
1385   ot->flag = 0;
1386 }
1387
1388 /** \} */
1389
1390 /* -------------------------------------------------------------------- */
1391 /** \name NDOF Orbit/Zoom Operator
1392  * \{ */
1393
1394 static int ndof_orbit_zoom_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1395 {
1396   if (event->type != NDOF_MOTION) {
1397     return OPERATOR_CANCELLED;
1398   }
1399
1400   const Depsgraph *depsgraph = CTX_data_depsgraph(C);
1401   ViewOpsData *vod;
1402   View3D *v3d;
1403   RegionView3D *rv3d;
1404   char xform_flag = 0;
1405
1406   const wmNDOFMotionData *ndof = event->customdata;
1407
1408   viewops_data_alloc(C, op);
1409   viewops_data_create(
1410       C, op, event, viewops_flag_from_args((U.uiflag & USER_ORBIT_SELECTION) != 0, false));
1411
1412   vod = op->customdata;
1413
1414   ED_view3d_smooth_view_force_finish(C, vod->v3d, vod->ar);
1415
1416   v3d = vod->v3d;
1417   rv3d = vod->rv3d;
1418
1419   /* off by default, until changed later this function */
1420   rv3d->rot_angle = 0.0f;
1421
1422   ED_view3d_camera_lock_init_ex(depsgraph, v3d, rv3d, false);
1423
1424   if (ndof->progress == P_FINISHING) {
1425     /* pass */
1426   }
1427   else if ((rv3d->persp == RV3D_ORTHO) && RV3D_VIEW_IS_AXIS(rv3d->view)) {
1428     /* if we can't rotate, fallback to translate (locked axis views) */
1429     const bool has_translate = NDOF_HAS_TRANSLATE;
1430     const bool has_zoom = (ndof->tvec[2] != 0.0f) && ED_view3d_offset_lock_check(v3d, rv3d);
1431
1432     if (has_translate || has_zoom) {
1433       view3d_ndof_pan_zoom(ndof, vod->sa, vod->ar, has_translate, true);
1434       xform_flag |= HAS_TRANSLATE;
1435     }
1436   }
1437   else if ((U.ndof_flag & NDOF_MODE_ORBIT) || ED_view3d_offset_lock_check(v3d, rv3d)) {
1438     const bool has_rotation = NDOF_HAS_ROTATE;
1439     const bool has_zoom = (ndof->tvec[2] != 0.0f);
1440
1441     if (has_zoom) {
1442       view3d_ndof_pan_zoom(ndof, vod->sa, vod->ar, false, has_zoom);
1443       xform_flag |= HAS_TRANSLATE;
1444     }
1445
1446     if (has_rotation) {
1447       view3d_ndof_orbit(ndof, vod->sa, vod->ar, vod, true);
1448       xform_flag |= HAS_ROTATE;
1449     }
1450   }
1451   else { /* free/explore (like fly mode) */
1452     const bool has_rotation = NDOF_HAS_ROTATE;
1453     const bool has_translate = NDOF_HAS_TRANSLATE;
1454     const bool has_zoom = (ndof->tvec[2] != 0.0f) && !rv3d->is_persp;
1455
1456     float dist_backup;
1457
1458     if (has_translate || has_zoom) {
1459       view3d_ndof_pan_zoom(ndof, vod->sa, vod->ar, has_translate, has_zoom);
1460       xform_flag |= HAS_TRANSLATE;
1461     }
1462
1463     dist_backup = rv3d->dist;
1464     ED_view3d_distance_set(rv3d, 0.0f);
1465
1466     if (has_rotation) {
1467       view3d_ndof_orbit(ndof, vod->sa, vod->ar, vod, false);
1468       xform_flag |= HAS_ROTATE;
1469     }
1470
1471     ED_view3d_distance_set(rv3d, dist_backup);
1472   }
1473
1474   ED_view3d_camera_lock_sync(depsgraph, v3d, rv3d);
1475   if (xform_flag) {
1476     ED_view3d_camera_lock_autokey(
1477         v3d, rv3d, C, xform_flag & HAS_ROTATE, xform_flag & HAS_TRANSLATE);
1478   }
1479
1480   ED_region_tag_redraw(vod->ar);
1481
1482   viewops_data_free(C, op);
1483
1484   return OPERATOR_FINISHED;
1485 }
1486
1487 void VIEW3D_OT_ndof_orbit_zoom(struct wmOperatorType *ot)
1488 {
1489   /* identifiers */
1490   ot->name = "NDOF Orbit View with Zoom";
1491   ot->description = "Orbit and zoom the view using the 3D mouse";
1492   ot->idname = "VIEW3D_OT_ndof_orbit_zoom";
1493
1494   /* api callbacks */
1495   ot->invoke = ndof_orbit_zoom_invoke;
1496   ot->poll = ED_operator_view3d_active;
1497
1498   /* flags */
1499   ot->flag = 0;
1500 }
1501
1502 /** \} */
1503
1504 /* -------------------------------------------------------------------- */
1505 /** \name NDOF Pan/Zoom Operator
1506  * \{ */
1507
1508 static int ndof_pan_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
1509 {
1510   if (event->type != NDOF_MOTION) {
1511     return OPERATOR_CANCELLED;
1512   }
1513
1514   const Depsgraph *depsgraph = CTX_data_depsgraph(C);
1515   View3D *v3d = CTX_wm_view3d(C);
1516   RegionView3D *rv3d = CTX_wm_region_view3d(C);
1517   const wmNDOFMotionData *ndof = event->customdata;
1518   char xform_flag = 0;
1519
1520   const bool has_translate = NDOF_HAS_TRANSLATE;
1521   const bool has_zoom = (ndof->tvec[2] != 0.0f) && !rv3d->is_persp;
1522
1523   /* we're panning here! so erase any leftover rotation from other operators */
1524   rv3d->rot_angle = 0.0f;
1525
1526   if (!(has_translate || has_zoom)) {
1527     return OPERATOR_CANCELLED;
1528   }
1529
1530   ED_view3d_camera_lock_init_ex(depsgraph, v3d, rv3d, false);
1531
1532   if (ndof->progress != P_FINISHING) {
1533     ScrArea *sa = CTX_wm_area(C);
1534     ARegion *ar = CTX_wm_region(C);
1535
1536     if (has_translate || has_zoom) {
1537       view3d_ndof_pan_zoom(ndof, sa, ar, has_translate, has_zoom);
1538       xform_flag |= HAS_TRANSLATE;
1539     }
1540   }
1541
1542   ED_view3d_camera_lock_sync(depsgraph, v3d, rv3d);
1543   if (xform_flag) {
1544     ED_view3d_camera_lock_autokey(v3d, rv3d, C, false, xform_flag & HAS_TRANSLATE);
1545   }
1546
1547   ED_region_tag_redraw(CTX_wm_region(C));
1548
1549   return OPERATOR_FINISHED;
1550 }
1551
1552 void VIEW3D_OT_ndof_pan(struct wmOperatorType *ot)
1553 {
1554   /* identifiers */
1555   ot->name = "NDOF Pan View";
1556   ot->description = "Pan the view with the 3D mouse";
1557   ot->idname = "VIEW3D_OT_ndof_pan";
1558
1559   /* api callbacks */
1560   ot->invoke = ndof_pan_invoke;
1561   ot->poll = ED_operator_view3d_active;
1562
1563   /* flags */
1564   ot->flag = 0;
1565 }
1566
1567 /** \} */
1568
1569 /* -------------------------------------------------------------------- */
1570 /** \name NDOF Transform All Operator
1571  * \{ */
1572
1573 /**
1574  * wraps #ndof_orbit_zoom but never restrict to orbit.
1575  */
1576 static int ndof_all_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1577 {
1578   /* weak!, but it works */
1579   const int ndof_flag = U.ndof_flag;
1580   int ret;
1581
1582   U.ndof_flag &= ~NDOF_MODE_ORBIT;
1583
1584   ret = ndof_orbit_zoom_invoke(C, op, event);
1585
1586   U.ndof_flag = ndof_flag;
1587
1588   return ret;
1589 }
1590
1591 void VIEW3D_OT_ndof_all(struct wmOperatorType *ot)
1592 {
1593   /* identifiers */
1594   ot->name = "NDOF Transform View";
1595   ot->description = "Pan and rotate the view with the 3D mouse";
1596   ot->idname = "VIEW3D_OT_ndof_all";
1597
1598   /* api callbacks */
1599   ot->invoke = ndof_all_invoke;
1600   ot->poll = ED_operator_view3d_active;
1601
1602   /* flags */
1603   ot->flag = 0;
1604 }
1605
1606 #endif /* WITH_INPUT_NDOF */
1607
1608 /** \} */
1609
1610 /* -------------------------------------------------------------------- */
1611 /** \name View Move (Pan) Operator
1612  * \{ */
1613
1614 /* NOTE: these defines are saved in keymap files, do not change values but just add new ones */
1615
1616 /* called in transform_ops.c, on each regeneration of keymaps  */
1617 void viewmove_modal_keymap(wmKeyConfig *keyconf)
1618 {
1619   static const EnumPropertyItem modal_items[] = {
1620       {VIEW_MODAL_CONFIRM, "CONFIRM", 0, "Confirm", ""},
1621
1622       {VIEWROT_MODAL_SWITCH_ZOOM, "SWITCH_TO_ZOOM", 0, "Switch to Zoom"},
1623       {VIEWROT_MODAL_SWITCH_ROTATE, "SWITCH_TO_ROTATE", 0, "Switch to Rotate"},
1624
1625       {0, NULL, 0, NULL, NULL},
1626   };
1627
1628   wmKeyMap *keymap = WM_modalkeymap_get(keyconf, "View3D Move Modal");
1629
1630   /* this function is called for each spacetype, only needs to add map once */
1631   if (keymap && keymap->modal_items) {
1632     return;
1633   }
1634
1635   keymap = WM_modalkeymap_add(keyconf, "View3D Move Modal", modal_items);
1636
1637   /* items for modal map */
1638   WM_modalkeymap_add_item(keymap, MIDDLEMOUSE, KM_RELEASE, KM_ANY, 0, VIEW_MODAL_CONFIRM);
1639   WM_modalkeymap_add_item(keymap, ESCKEY, KM_PRESS, KM_ANY, 0, VIEW_MODAL_CONFIRM);
1640
1641   /* disabled mode switching for now, can re-implement better, later on */
1642 #if 0
1643   WM_modalkeymap_add_item(keymap, LEFTMOUSE, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ZOOM);
1644   WM_modalkeymap_add_item(keymap, LEFTCTRLKEY, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ZOOM);
1645   WM_modalkeymap_add_item(
1646       keymap, LEFTSHIFTKEY, KM_RELEASE, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ROTATE);
1647 #endif
1648
1649   /* assign map to operators */
1650   WM_modalkeymap_assign(keymap, "VIEW3D_OT_move");
1651 }
1652
1653 static void viewmove_apply(ViewOpsData *vod, int x, int y)
1654 {
1655   if (ED_view3d_offset_lock_check(vod->v3d, vod->rv3d)) {
1656     vod->rv3d->ofs_lock[0] -= ((vod->prev.event_xy[0] - x) * 2.0f) / (float)vod->ar->winx;
1657     vod->rv3d->ofs_lock[1] -= ((vod->prev.event_xy[1] - y) * 2.0f) / (float)vod->ar->winy;
1658   }
1659   else if ((vod->rv3d->persp == RV3D_CAMOB) && !ED_view3d_camera_lock_check(vod->v3d, vod->rv3d)) {
1660     const float zoomfac = BKE_screen_view3d_zoom_to_fac(vod->rv3d->camzoom) * 2.0f;
1661     vod->rv3d->camdx += (vod->prev.event_xy[0] - x) / (vod->ar->winx * zoomfac);
1662     vod->rv3d->camdy += (vod->prev.event_xy[1] - y) / (vod->ar->winy * zoomfac);
1663     CLAMP(vod->rv3d->camdx, -1.0f, 1.0f);
1664     CLAMP(vod->rv3d->camdy, -1.0f, 1.0f);
1665   }
1666   else {
1667     float dvec[3];
1668     float mval_f[2];
1669
1670     mval_f[0] = x - vod->prev.event_xy[0];
1671     mval_f[1] = y - vod->prev.event_xy[1];
1672     ED_view3d_win_to_delta(vod->ar, mval_f, dvec, vod->init.zfac);
1673
1674     add_v3_v3(vod->rv3d->ofs, dvec);
1675
1676     if (vod->rv3d->viewlock & RV3D_BOXVIEW) {
1677       view3d_boxview_sync(vod->sa, vod->ar);
1678     }
1679   }
1680
1681   vod->prev.event_xy[0] = x;
1682   vod->prev.event_xy[1] = y;
1683
1684   ED_view3d_camera_lock_sync(vod->depsgraph, vod->v3d, vod->rv3d);
1685
1686   ED_region_tag_redraw(vod->ar);
1687 }
1688
1689 static int viewmove_modal(bContext *C, wmOperator *op, const wmEvent *event)
1690 {
1691
1692   ViewOpsData *vod = op->customdata;
1693   short event_code = VIEW_PASS;
1694   bool use_autokey = false;
1695   int ret = OPERATOR_RUNNING_MODAL;
1696
1697   /* execute the events */
1698   if (event->type == MOUSEMOVE) {
1699     event_code = VIEW_APPLY;
1700   }
1701   else if (event->type == EVT_MODAL_MAP) {
1702     switch (event->val) {
1703       case VIEW_MODAL_CONFIRM:
1704         event_code = VIEW_CONFIRM;
1705         break;
1706       case VIEWROT_MODAL_SWITCH_ZOOM:
1707         WM_operator_name_call(C, "VIEW3D_OT_zoom", WM_OP_INVOKE_DEFAULT, NULL);
1708         event_code = VIEW_CONFIRM;
1709         break;
1710       case VIEWROT_MODAL_SWITCH_ROTATE:
1711         WM_operator_name_call(C, "VIEW3D_OT_rotate", WM_OP_INVOKE_DEFAULT, NULL);
1712         event_code = VIEW_CONFIRM;
1713         break;
1714     }
1715   }
1716   else if (event->type == vod->init.event_type && event->val == KM_RELEASE) {
1717     event_code = VIEW_CONFIRM;
1718   }
1719
1720   if (event_code == VIEW_APPLY) {
1721     viewmove_apply(vod, event->x, event->y);
1722     if (ED_screen_animation_playing(CTX_wm_manager(C))) {
1723       use_autokey = true;
1724     }
1725   }
1726   else if (event_code == VIEW_CONFIRM) {
1727     ED_view3d_depth_tag_update(vod->rv3d);
1728     use_autokey = true;
1729     ret = OPERATOR_FINISHED;
1730   }
1731
1732   if (use_autokey) {
1733     ED_view3d_camera_lock_autokey(vod->v3d, vod->rv3d, C, false, true);
1734   }
1735
1736   if (ret & OPERATOR_FINISHED) {
1737     viewops_data_free(C, op);
1738   }
1739
1740   return ret;
1741 }
1742
1743 static int viewmove_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1744 {
1745   ViewOpsData *vod;
1746
1747   const bool use_cursor_init = RNA_boolean_get(op->ptr, "use_cursor_init");
1748
1749   /* makes op->customdata */
1750   viewops_data_alloc(C, op);
1751   viewops_data_create(C,
1752                       op,
1753                       event,
1754                       (viewops_flag_from_prefs() & ~VIEWOPS_FLAG_ORBIT_SELECT) |
1755                           (use_cursor_init ? VIEWOPS_FLAG_USE_MOUSE_INIT : 0));
1756   vod = op->customdata;
1757
1758   ED_view3d_smooth_view_force_finish(C, vod->v3d, vod->ar);
1759
1760   if (event->type == MOUSEPAN) {
1761     /* invert it, trackpad scroll follows same principle as 2d windows this way */
1762     viewmove_apply(vod, 2 * event->x - event->prevx, 2 * event->y - event->prevy);
1763     ED_view3d_depth_tag_update(vod->rv3d);
1764
1765     viewops_data_free(C, op);
1766
1767     return OPERATOR_FINISHED;
1768   }
1769   else {
1770     /* add temp handler */
1771     WM_event_add_modal_handler(C, op);
1772
1773     return OPERATOR_RUNNING_MODAL;
1774   }
1775 }
1776
1777 static void viewmove_cancel(bContext *C, wmOperator *op)
1778 {
1779   viewops_data_free(C, op);
1780 }
1781
1782 void VIEW3D_OT_move(wmOperatorType *ot)
1783 {
1784
1785   /* identifiers */
1786   ot->name = "Pan View";
1787   ot->description = "Move the view";
1788   ot->idname = "VIEW3D_OT_move";
1789
1790   /* api callbacks */
1791   ot->invoke = viewmove_invoke;
1792   ot->modal = viewmove_modal;
1793   ot->poll = ED_operator_region_view3d_active;
1794   ot->cancel = viewmove_cancel;
1795
1796   /* flags */
1797   ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_XY;
1798
1799   /* properties */
1800   view3d_operator_properties_common(ot, V3D_OP_PROP_USE_MOUSE_INIT);
1801 }
1802
1803 /** \} */
1804
1805 /* -------------------------------------------------------------------- */
1806 /** \name View Zoom Operator
1807  * \{ */
1808
1809 /* viewdolly_modal_keymap has an exact copy of this, apply fixes to both */
1810 /* called in transform_ops.c, on each regeneration of keymaps  */
1811 void viewzoom_modal_keymap(wmKeyConfig *keyconf)
1812 {
1813   static const EnumPropertyItem modal_items[] = {
1814       {VIEW_MODAL_CONFIRM, "CONFIRM", 0, "Confirm", ""},
1815
1816       {VIEWROT_MODAL_SWITCH_ROTATE, "SWITCH_TO_ROTATE", 0, "Switch to Rotate"},
1817       {VIEWROT_MODAL_SWITCH_MOVE, "SWITCH_TO_MOVE", 0, "Switch to Move"},
1818
1819       {0, NULL, 0, NULL, NULL},
1820   };
1821
1822   wmKeyMap *keymap = WM_modalkeymap_get(keyconf, "View3D Zoom Modal");
1823
1824   /* this function is called for each spacetype, only needs to add map once */
1825   if (keymap && keymap->modal_items) {
1826     return;
1827   }
1828
1829   keymap = WM_modalkeymap_add(keyconf, "View3D Zoom Modal", modal_items);
1830
1831   /* disabled mode switching for now, can re-implement better, later on */
1832 #if 0
1833   WM_modalkeymap_add_item(keymap, LEFTMOUSE, KM_RELEASE, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ROTATE);
1834   WM_modalkeymap_add_item(keymap, LEFTCTRLKEY, KM_RELEASE, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ROTATE);
1835   WM_modalkeymap_add_item(keymap, LEFTSHIFTKEY, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_MOVE);
1836 #endif
1837
1838   /* assign map to operators */
1839   WM_modalkeymap_assign(keymap, "VIEW3D_OT_zoom");
1840 }
1841
1842 /**
1843  * \param zoom_xy: Optionally zoom to window location
1844  * (coords compatible w/ #wmEvent.x, y). Use when not NULL.
1845  */
1846 static void view_zoom_to_window_xy_camera(
1847     Scene *scene, Depsgraph *depsgraph, View3D *v3d, ARegion *ar, float dfac, const int zoom_xy[2])
1848 {
1849   RegionView3D *rv3d = ar->regiondata;
1850   const float zoomfac = BKE_screen_view3d_zoom_to_fac(rv3d->camzoom);
1851   const float zoomfac_new = clamp_f(
1852       zoomfac * (1.0f / dfac), RV3D_CAMZOOM_MIN_FACTOR, RV3D_CAMZOOM_MAX_FACTOR);
1853   const float camzoom_new = BKE_screen_view3d_zoom_from_fac(zoomfac_new);
1854
1855   if (zoom_xy != NULL) {
1856     float zoomfac_px;
1857     rctf camera_frame_old;
1858     rctf camera_frame_new;
1859
1860     const float pt_src[2] = {zoom_xy[0], zoom_xy[1]};
1861     float pt_dst[2];
1862     float delta_px[2];
1863
1864     ED_view3d_calc_camera_border(scene, depsgraph, ar, v3d, rv3d, &camera_frame_old, false);
1865     BLI_rctf_translate(&camera_frame_old, ar->winrct.xmin, ar->winrct.ymin);
1866
1867     rv3d->camzoom = camzoom_new;
1868     CLAMP(rv3d->camzoom, RV3D_CAMZOOM_MIN, RV3D_CAMZOOM_MAX);
1869
1870     ED_view3d_calc_camera_border(scene, depsgraph, ar, v3d, rv3d, &camera_frame_new, false);
1871     BLI_rctf_translate(&camera_frame_new, ar->winrct.xmin, ar->winrct.ymin);
1872
1873     BLI_rctf_transform_pt_v(&camera_frame_new, &camera_frame_old, pt_dst, pt_src);
1874     sub_v2_v2v2(delta_px, pt_dst, pt_src);
1875
1876     /* translate the camera offset using pixel space delta
1877      * mapped back to the camera (same logic as panning in camera view) */
1878     zoomfac_px = BKE_screen_view3d_zoom_to_fac(rv3d->camzoom) * 2.0f;
1879
1880     rv3d->camdx += delta_px[0] / (ar->winx * zoomfac_px);
1881     rv3d->camdy += delta_px[1] / (ar->winy * zoomfac_px);
1882     CLAMP(rv3d->camdx, -1.0f, 1.0f);
1883     CLAMP(rv3d->camdy, -1.0f, 1.0f);
1884   }
1885   else {
1886     rv3d->camzoom = camzoom_new;
1887     CLAMP(rv3d->camzoom, RV3D_CAMZOOM_MIN, RV3D_CAMZOOM_MAX);
1888   }
1889 }
1890
1891 /**
1892  * \param zoom_xy: Optionally zoom to window location
1893  * (coords compatible w/ #wmEvent.x, y). Use when not NULL.
1894  */
1895 static void view_zoom_to_window_xy_3d(ARegion *ar, float dfac, const int zoom_xy[2])
1896 {
1897   RegionView3D *rv3d = ar->regiondata;
1898   const float dist_new = rv3d->dist * dfac;
1899
1900   if (zoom_xy != NULL) {
1901     float dvec[3];
1902     float tvec[3];
1903     float tpos[3];
1904     float mval_f[2];
1905
1906     float zfac;
1907
1908     negate_v3_v3(tpos, rv3d->ofs);
1909
1910     mval_f[0] = (float)(((zoom_xy[0] - ar->winrct.xmin) * 2) - ar->winx) / 2.0f;
1911     mval_f[1] = (float)(((zoom_xy[1] - ar->winrct.ymin) * 2) - ar->winy) / 2.0f;
1912
1913     /* Project cursor position into 3D space */
1914     zfac = ED_view3d_calc_zfac(rv3d, tpos, NULL);
1915     ED_view3d_win_to_delta(ar, mval_f, dvec, zfac);
1916
1917     /* Calculate view target position for dolly */
1918     add_v3_v3v3(tvec, tpos, dvec);
1919     negate_v3(tvec);
1920
1921     /* Offset to target position and dolly */
1922     copy_v3_v3(rv3d->ofs, tvec);
1923     rv3d->dist = dist_new;
1924
1925     /* Calculate final offset */
1926     madd_v3_v3v3fl(rv3d->ofs, tvec, dvec, dfac);
1927   }
1928   else {
1929     rv3d->dist = dist_new;
1930   }
1931 }
1932
1933 static float viewzoom_scale_value(const rcti *winrct,
1934                                   const short viewzoom,
1935                                   const bool zoom_invert,
1936                                   const bool zoom_invert_force,
1937                                   const int xy_curr[2],
1938                                   const int xy_init[2],
1939                                   const float val,
1940                                   const float val_orig,
1941                                   double *r_timer_lastdraw)
1942 {
1943   float zfac;
1944
1945   if (viewzoom == USER_ZOOM_CONT) {
1946     double time = PIL_check_seconds_timer();
1947     float time_step = (float)(time - *r_timer_lastdraw);
1948     float fac;
1949
1950     if (U.uiflag & USER_ZOOM_HORIZ) {
1951       fac = (float)(xy_init[0] - xy_curr[0]);
1952     }
1953     else {
1954       fac = (float)(xy_init[1] - xy_curr[1]);
1955     }
1956
1957     if (zoom_invert != zoom_invert_force) {
1958       fac = -fac;
1959     }
1960
1961     /* oldstyle zoom */
1962     zfac = 1.0f + ((fac / 20.0f) * time_step);
1963     *r_timer_lastdraw = time;
1964   }
1965   else if (viewzoom == USER_ZOOM_SCALE) {
1966     /* method which zooms based on how far you move the mouse */
1967
1968     const int ctr[2] = {
1969         BLI_rcti_cent_x(winrct),
1970         BLI_rcti_cent_y(winrct),
1971     };
1972     float len_new = 5 + len_v2v2_int(ctr, xy_curr);
1973     float len_old = 5 + len_v2v2_int(ctr, xy_init);
1974
1975     /* intentionally ignore 'zoom_invert' for scale */
1976     if (zoom_invert_force) {
1977       SWAP(float, len_new, len_old);
1978     }
1979
1980     zfac = val_orig * (len_old / max_ff(len_new, 1.0f)) / val;
1981   }
1982   else { /* USER_ZOOM_DOLLY */
1983     float len_new = 5;
1984     float len_old = 5;
1985
1986     if (U.uiflag & USER_ZOOM_HORIZ) {
1987       len_new += (winrct->xmax - (xy_curr[0]));
1988       len_old += (winrct->xmax - (xy_init[0]));
1989     }
1990     else {
1991       len_new += (winrct->ymax - (xy_curr[1]));
1992       len_old += (winrct->ymax - (xy_init[1]));
1993     }
1994
1995     if (zoom_invert != zoom_invert_force) {
1996       SWAP(float, len_new, len_old);
1997     }
1998
1999     zfac = val_orig * (2.0f * ((len_new / max_ff(len_old, 1.0f)) - 1.0f) + 1.0f) / val;
2000   }
2001
2002   return zfac;
2003 }
2004
2005 static float viewzoom_scale_value_offset(const rcti *winrct,
2006                                          const short viewzoom,
2007                                          const bool zoom_invert,
2008                                          const bool zoom_invert_force,
2009                                          const int xy_curr[2],
2010                                          const int xy_init[2],
2011                                          const int xy_offset[2],
2012                                          const float val,
2013                                          const float val_orig,
2014                                          double *r_timer_lastdraw)
2015 {
2016   const int xy_curr_offset[2] = {
2017       xy_curr[0] + xy_offset[0],
2018       xy_curr[1] + xy_offset[1],
2019   };
2020   const int xy_init_offset[2] = {
2021       xy_init[0] + xy_offset[0],
2022       xy_init[1] + xy_offset[1],
2023   };
2024   return viewzoom_scale_value(winrct,
2025                               viewzoom,
2026                               zoom_invert,
2027                               zoom_invert_force,
2028                               xy_curr_offset,
2029                               xy_init_offset,
2030                               val,
2031                               val_orig,
2032                               r_timer_lastdraw);
2033 }
2034
2035 static void viewzoom_apply_camera(ViewOpsData *vod,
2036                                   const int xy[2],
2037                                   const short viewzoom,
2038                                   const bool zoom_invert,
2039                                   const bool zoom_to_pos)
2040 {
2041   float zfac;
2042   float zoomfac_prev = BKE_screen_view3d_zoom_to_fac(vod->init.camzoom) * 2.0f;
2043   float zoomfac = BKE_screen_view3d_zoom_to_fac(vod->rv3d->camzoom) * 2.0f;
2044
2045   zfac = viewzoom_scale_value_offset(&vod->ar->winrct,
2046                                      viewzoom,
2047                                      zoom_invert,
2048                                      true,
2049                                      xy,
2050                                      vod->init.event_xy,
2051                                      vod->init.event_xy_offset,
2052                                      zoomfac,
2053                                      zoomfac_prev,
2054                                      &vod->prev.time);
2055
2056   if (zfac != 1.0f && zfac != 0.0f) {
2057     /* calculate inverted, then invert again (needed because of camera zoom scaling) */
2058     zfac = 1.0f / zfac;
2059     view_zoom_to_window_xy_camera(vod->scene,
2060                                   vod->depsgraph,
2061                                   vod->v3d,
2062                                   vod->ar,
2063                                   zfac,
2064                                   zoom_to_pos ? vod->prev.event_xy : NULL);
2065   }
2066
2067   ED_region_tag_redraw(vod->ar);
2068 }
2069
2070 static void viewzoom_apply_3d(ViewOpsData *vod,
2071                               const int xy[2],
2072                               const short viewzoom,
2073                               const bool zoom_invert,
2074                               const bool zoom_to_pos)
2075 {
2076   float zfac;
2077   float dist_range[2];
2078
2079   ED_view3d_dist_range_get(vod->v3d, dist_range);
2080
2081   zfac = viewzoom_scale_value_offset(&vod->ar->winrct,
2082                                      viewzoom,
2083                                      zoom_invert,
2084                                      false,
2085                                      xy,
2086                                      vod->init.event_xy,
2087                                      vod->init.event_xy_offset,
2088                                      vod->rv3d->dist,
2089                                      vod->init.dist,
2090                                      &vod->prev.time);
2091
2092   if (zfac != 1.0f) {
2093     const float zfac_min = dist_range[0] / vod->rv3d->dist;
2094     const float zfac_max = dist_range[1] / vod->rv3d->dist;
2095     CLAMP(zfac, zfac_min, zfac_max);
2096
2097     view_zoom_to_window_xy_3d(vod->ar, zfac, zoom_to_pos ? vod->prev.event_xy : NULL);
2098   }
2099
2100   /* these limits were in old code too */
2101   CLAMP(vod->rv3d->dist, dist_range[0], dist_range[1]);
2102
2103   if (vod->rv3d->viewlock & RV3D_BOXVIEW) {
2104     view3d_boxview_sync(vod->sa, vod->ar);
2105   }
2106
2107   ED_view3d_camera_lock_sync(vod->depsgraph, vod->v3d, vod->rv3d);
2108
2109   ED_region_tag_redraw(vod->ar);
2110 }
2111
2112 static void viewzoom_apply(ViewOpsData *vod,
2113                            const int xy[2],
2114                            const short viewzoom,
2115                            const bool zoom_invert,
2116                            const bool zoom_to_pos)
2117 {
2118   if ((vod->rv3d->persp == RV3D_CAMOB) &&
2119       (vod->rv3d->is_persp && ED_view3d_camera_lock_check(vod->v3d, vod->rv3d)) == 0) {
2120     viewzoom_apply_camera(vod, xy, viewzoom, zoom_invert, zoom_to_pos);
2121   }
2122   else {
2123     viewzoom_apply_3d(vod, xy, viewzoom, zoom_invert, zoom_to_pos);
2124   }
2125 }
2126
2127 static int viewzoom_modal(bContext *C, wmOperator *op, const wmEvent *event)
2128 {
2129   ViewOpsData *vod = op->customdata;
2130   short event_code = VIEW_PASS;
2131   bool use_autokey = false;
2132   int ret = OPERATOR_RUNNING_MODAL;
2133
2134   /* execute the events */
2135   if (event->type == TIMER && event->customdata == vod->timer) {
2136     /* continuous zoom */
2137     event_code = VIEW_APPLY;
2138   }
2139   else if (event->type == MOUSEMOVE) {
2140     event_code = VIEW_APPLY;
2141   }
2142   else if (event->type == EVT_MODAL_MAP) {
2143     switch (event->val) {
2144       case VIEW_MODAL_CONFIRM:
2145         event_code = VIEW_CONFIRM;
2146         break;
2147       case VIEWROT_MODAL_SWITCH_MOVE:
2148         WM_operator_name_call(C, "VIEW3D_OT_move", WM_OP_INVOKE_DEFAULT, NULL);
2149         event_code = VIEW_CONFIRM;
2150         break;
2151       case VIEWROT_MODAL_SWITCH_ROTATE:
2152         WM_operator_name_call(C, "VIEW3D_OT_rotate", WM_OP_INVOKE_DEFAULT, NULL);
2153         event_code = VIEW_CONFIRM;
2154         break;
2155     }
2156   }
2157   else if (event->type == vod->init.event_type && event->val == KM_RELEASE) {
2158     event_code = VIEW_CONFIRM;
2159   }
2160
2161   if (event_code == VIEW_APPLY) {
2162     const bool use_cursor_init = RNA_boolean_get(op->ptr, "use_cursor_init");
2163     viewzoom_apply(vod,
2164                    &event->x,
2165                    U.viewzoom,
2166                    (U.uiflag & USER_ZOOM_INVERT) != 0,
2167                    (use_cursor_init && (U.uiflag & USER_ZOOM_TO_MOUSEPOS)));
2168     if (ED_screen_animation_playing(CTX_wm_manager(C))) {
2169       use_autokey = true;
2170     }
2171   }
2172   else if (event_code == VIEW_CONFIRM) {
2173     ED_view3d_depth_tag_update(vod->rv3d);
2174     use_autokey = true;
2175     ret = OPERATOR_FINISHED;
2176   }
2177
2178   if (use_autokey) {
2179     ED_view3d_camera_lock_autokey(vod->v3d, vod->rv3d, C, false, true);
2180   }
2181
2182   if (ret & OPERATOR_FINISHED) {
2183     viewops_data_free(C, op);
2184   }
2185
2186   return ret;
2187 }
2188
2189 static int viewzoom_exec(bContext *C, wmOperator *op)
2190 {
2191   Depsgraph *depsgraph = CTX_data_depsgraph(C);
2192   Scene *scene = CTX_data_scene(C);
2193   View3D *v3d;
2194   RegionView3D *rv3d;
2195   ScrArea *sa;
2196   ARegion *ar;
2197   bool use_cam_zoom;
2198   float dist_range[2];
2199
2200   const int delta = RNA_int_get(op->ptr, "delta");
2201   const bool use_cursor_init = RNA_boolean_get(op->ptr, "use_cursor_init");
2202
2203   if (op->customdata) {
2204     ViewOpsData *vod = op->customdata;
2205
2206     sa = vod->sa;
2207     ar = vod->ar;
2208   }
2209   else {
2210     sa = CTX_wm_area(C);
2211     ar = CTX_wm_region(C);
2212   }
2213
2214   v3d = sa->spacedata.first;
2215   rv3d = ar->regiondata;
2216
2217   use_cam_zoom = (rv3d->persp == RV3D_CAMOB) &&
2218                  !(rv3d->is_persp && ED_view3d_camera_lock_check(v3d, rv3d));
2219
2220   int zoom_xy_buf[2];
2221   const int *zoom_xy = NULL;
2222   if (use_cursor_init && (U.uiflag & USER_ZOOM_TO_MOUSEPOS)) {
2223     zoom_xy_buf[0] = RNA_struct_property_is_set(op->ptr, "mx") ? RNA_int_get(op->ptr, "mx") :
2224                                                                  ar->winx / 2;
2225     zoom_xy_buf[1] = RNA_struct_property_is_set(op->ptr, "my") ? RNA_int_get(op->ptr, "my") :
2226                                                                  ar->winy / 2;
2227     zoom_xy = zoom_xy_buf;
2228   }
2229
2230   ED_view3d_dist_range_get(v3d, dist_range);
2231
2232   if (delta < 0) {
2233     const float step = 1.2f;
2234     /* this min and max is also in viewmove() */
2235     if (use_cam_zoom) {
2236       view_zoom_to_window_xy_camera(scene, depsgraph, v3d, ar, step, zoom_xy);
2237     }
2238     else {
2239       if (rv3d->dist < dist_range[1]) {
2240         view_zoom_to_window_xy_3d(ar, step, zoom_xy);
2241       }
2242     }
2243   }
2244   else {
2245     const float step = 1.0f / 1.2f;
2246     if (use_cam_zoom) {
2247       view_zoom_to_window_xy_camera(scene, depsgraph, v3d, ar, step, zoom_xy);
2248     }
2249     else {
2250       if (rv3d->dist > dist_range[0]) {
2251         view_zoom_to_window_xy_3d(ar, step, zoom_xy);
2252       }
2253     }
2254   }
2255
2256   if (rv3d->viewlock & RV3D_BOXVIEW) {
2257     view3d_boxview_sync(sa, ar);
2258   }
2259
2260   ED_view3d_depth_tag_update(rv3d);
2261
2262   ED_view3d_camera_lock_sync(depsgraph, v3d, rv3d);
2263   ED_view3d_camera_lock_autokey(v3d, rv3d, C, false, true);
2264
2265   ED_region_tag_redraw(ar);
2266
2267   viewops_data_free(C, op);
2268
2269   return OPERATOR_FINISHED;
2270 }
2271
2272 /* viewdolly_invoke() copied this function, changes here may apply there */
2273 static int viewzoom_invoke(bContext *C, wmOperator *op, const wmEvent *event)
2274 {
2275   ViewOpsData *vod;
2276
2277   const bool use_cursor_init = RNA_boolean_get(op->ptr, "use_cursor_init");
2278
2279   /* makes op->customdata */
2280   viewops_data_alloc(C, op);
2281   viewops_data_create(C,
2282                       op,
2283                       event,
2284                       (viewops_flag_from_prefs() & ~VIEWOPS_FLAG_ORBIT_SELECT) |
2285                           (use_cursor_init ? VIEWOPS_FLAG_USE_MOUSE_INIT : 0));
2286   vod = op->customdata;
2287
2288   ED_view3d_smooth_view_force_finish(C, vod->v3d, vod->ar);
2289
2290   /* if one or the other zoom position aren't set, set from event */
2291   if (!RNA_struct_property_is_set(op->ptr, "mx") || !RNA_struct_property_is_set(op->ptr, "my")) {
2292     RNA_int_set(op->ptr, "mx", event->x);
2293     RNA_int_set(op->ptr, "my", event->y);
2294   }
2295
2296   if (RNA_struct_property_is_set(op->ptr, "delta")) {
2297     viewzoom_exec(C, op);
2298   }
2299   else {
2300     if (event->type == MOUSEZOOM || event->type == MOUSEPAN) {
2301
2302       if (U.uiflag & USER_ZOOM_HORIZ) {
2303         vod->init.event_xy[0] = vod->prev.event_xy[0] = event->x;
2304       }
2305       else {
2306         /* Set y move = x move as MOUSEZOOM uses only x axis to pass magnification value */
2307         vod->init.event_xy[1] = vod->prev.event_xy[1] = vod->init.event_xy[1] + event->x -
2308                                                         event->prevx;
2309       }
2310       viewzoom_apply(vod,
2311                      &event->prevx,
2312                      USER_ZOOM_DOLLY,
2313                      (U.uiflag & USER_ZOOM_INVERT) != 0,
2314                      (use_cursor_init && (U.uiflag & USER_ZOOM_TO_MOUSEPOS)));
2315       ED_view3d_camera_lock_autokey(vod->v3d, vod->rv3d, C, false, true);
2316
2317       ED_view3d_depth_tag_update(vod->rv3d);
2318
2319       viewops_data_free(C, op);
2320       return OPERATOR_FINISHED;
2321     }
2322     else {
2323       if (U.viewzoom == USER_ZOOM_CONT) {
2324         /* needs a timer to continue redrawing */
2325         vod->timer = WM_event_add_timer(CTX_wm_manager(C), CTX_wm_window(C), TIMER, 0.01f);
2326         vod->prev.time = PIL_check_seconds_timer();
2327       }
2328
2329       /* add temp handler */
2330       WM_event_add_modal_handler(C, op);
2331
2332       return OPERATOR_RUNNING_MODAL;
2333     }
2334   }
2335   return OPERATOR_FINISHED;
2336 }
2337
2338 static void viewzoom_cancel(bContext *C, wmOperator *op)
2339 {
2340   viewops_data_free(C, op);
2341 }
2342
2343 void VIEW3D_OT_zoom(wmOperatorType *ot)
2344 {
2345   /* identifiers */
2346   ot->name = "Zoom View";
2347   ot->description = "Zoom in/out in the view";
2348   ot->idname = "VIEW3D_OT_zoom";
2349
2350   /* api callbacks */
2351   ot->invoke = viewzoom_invoke;
2352   ot->exec = viewzoom_exec;
2353   ot->modal = viewzoom_modal;
2354   ot->poll = ED_operator_region_view3d_active;
2355   ot->cancel = viewzoom_cancel;
2356
2357   /* flags */
2358   ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_XY;
2359
2360   /* properties */
2361   view3d_operator_properties_common(
2362       ot, V3D_OP_PROP_DELTA | V3D_OP_PROP_MOUSE_CO | V3D_OP_PROP_USE_MOUSE_INIT);
2363 }
2364
2365 /** \} */
2366
2367 /* -------------------------------------------------------------------- */
2368 /** \name View Dolly Operator
2369  *
2370  * Like zoom but translates the view offset along the view direction
2371  * which avoids #RegionView3D.dist approaching zero.
2372  * \{ */
2373
2374 /* this is an exact copy of viewzoom_modal_keymap */
2375 /* called in transform_ops.c, on each regeneration of keymaps  */
2376 void viewdolly_modal_keymap(wmKeyConfig *keyconf)
2377 {
2378   static const EnumPropertyItem modal_items[] = {
2379       {VIEW_MODAL_CONFIRM, "CONFIRM", 0, "Confirm", ""},
2380
2381       {VIEWROT_MODAL_SWITCH_ROTATE, "SWITCH_TO_ROTATE", 0, "Switch to Rotate"},
2382       {VIEWROT_MODAL_SWITCH_MOVE, "SWITCH_TO_MOVE", 0, "Switch to Move"},
2383
2384       {0, NULL, 0, NULL, NULL},
2385   };
2386
2387   wmKeyMap *keymap = WM_modalkeymap_get(keyconf, "View3D Dolly Modal");
2388
2389   /* this function is called for each spacetype, only needs to add map once */
2390   if (keymap && keymap->modal_items) {
2391     return;
2392   }
2393
2394   keymap = WM_modalkeymap_add(keyconf, "View3D Dolly Modal", modal_items);
2395
2396   /* disabled mode switching for now, can re-implement better, later on */
2397 #if 0
2398   WM_modalkeymap_add_item(keymap, LEFTMOUSE, KM_RELEASE, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ROTATE);
2399   WM_modalkeymap_add_item(keymap, LEFTCTRLKEY, KM_RELEASE, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ROTATE);
2400   WM_modalkeymap_add_item(keymap, LEFTSHIFTKEY, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_MOVE);
2401 #endif
2402
2403   /* assign map to operators */
2404   WM_modalkeymap_assign(keymap, "VIEW3D_OT_dolly");
2405 }
2406
2407 static bool viewdolly_offset_lock_check(bContext *C, wmOperator *op)
2408 {
2409   View3D *v3d = CTX_wm_view3d(C);
2410   RegionView3D *rv3d = CTX_wm_region_view3d(C);
2411   if (ED_view3d_offset_lock_check(v3d, rv3d)) {
2412     BKE_report(op->reports, RPT_WARNING, "Cannot dolly when the view offset is locked");
2413     return true;
2414   }
2415   else {
2416     return false;
2417   }
2418 }
2419
2420 static void view_dolly_to_vector_3d(ARegion *ar, float orig_ofs[3], float dvec[3], float dfac)
2421 {
2422   RegionView3D *rv3d = ar->regiondata;
2423   madd_v3_v3v3fl(rv3d->ofs, orig_ofs, dvec, -(1.0f - dfac));
2424 }
2425
2426 static void viewdolly_apply(ViewOpsData *vod, const int xy[2], const short zoom_invert)
2427 {
2428   float zfac = 1.0;
2429
2430   {
2431     float len1, len2;
2432
2433     if (U.uiflag & USER_ZOOM_HORIZ) {
2434       len1 = (vod->ar->winrct.xmax - xy[0]) + 5;
2435       len2 = (vod->ar->winrct.xmax - vod->init.event_xy[0]) + 5;
2436     }
2437     else {
2438       len1 = (vod->ar->winrct.ymax - xy[1]) + 5;
2439       len2 = (vod->ar->winrct.ymax - vod->init.event_xy[1]) + 5;
2440     }
2441     if (zoom_invert) {
2442       SWAP(float, len1, len2);
2443     }
2444
2445     zfac = 1.0f + ((len1 - len2) * 0.01f * vod->rv3d->dist);
2446   }
2447
2448   if (zfac != 1.0f) {
2449     view_dolly_to_vector_3d(vod->ar, vod->init.ofs, vod->init.mousevec, zfac);
2450   }
2451
2452   if (vod->rv3d->viewlock & RV3D_BOXVIEW) {
2453     view3d_boxview_sync(vod->sa, vod->ar);
2454   }
2455
2456   ED_view3d_camera_lock_sync(vod->depsgraph, vod->v3d, vod->rv3d);
2457
2458   ED_region_tag_redraw(vod->ar);
2459 }
2460
2461 static int viewdolly_modal(bContext *C, wmOperator *op, const wmEvent *event)
2462 {
2463   ViewOpsData *vod = op->customdata;
2464   short event_code = VIEW_PASS;
2465   bool use_autokey = false;
2466   int ret = OPERATOR_RUNNING_MODAL;
2467
2468   /* execute the events */
2469   if (event->type == MOUSEMOVE) {
2470     event_code = VIEW_APPLY;
2471   }
2472   else if (event->type == EVT_MODAL_MAP) {
2473     switch (event->val) {
2474       case VIEW_MODAL_CONFIRM:
2475         event_code = VIEW_CONFIRM;
2476         break;
2477       case VIEWROT_MODAL_SWITCH_MOVE:
2478         WM_operator_name_call(C, "VIEW3D_OT_move", WM_OP_INVOKE_DEFAULT, NULL);
2479         event_code = VIEW_CONFIRM;
2480         break;
2481       case VIEWROT_MODAL_SWITCH_ROTATE:
2482         WM_operator_name_call(C, "VIEW3D_OT_rotate", WM_OP_INVOKE_DEFAULT, NULL);
2483         event_code = VIEW_CONFIRM;
2484         break;
2485     }
2486   }
2487   else if (event->type == vod->init.event_type && event->val == KM_RELEASE) {
2488     event_code = VIEW_CONFIRM;
2489   }
2490
2491   if (event_code == VIEW_APPLY) {
2492     viewdolly_apply(vod, &event->x, (U.uiflag & USER_ZOOM_INVERT) != 0);
2493     if (ED_screen_animation_playing(CTX_wm_manager(C))) {
2494       use_autokey = true;
2495     }
2496   }
2497   else if (event_code == VIEW_CONFIRM) {
2498     ED_view3d_depth_tag_update(vod->rv3d);
2499     use_autokey = true;
2500     ret = OPERATOR_FINISHED;
2501   }
2502
2503   if (use_autokey) {
2504     ED_view3d_camera_lock_autokey(vod->v3d, vod->rv3d, C, false, true);
2505   }
2506
2507   if (ret & OPERATOR_FINISHED) {
2508     viewops_data_free(C, op);
2509   }
2510
2511   return ret;
2512 }
2513
2514 static int viewdolly_exec(bContext *C, wmOperator *op)
2515 {
2516   View3D *v3d;
2517   RegionView3D *rv3d;
2518   ScrArea *sa;
2519   ARegion *ar;
2520   float mousevec[3];
2521
2522   const int delta = RNA_int_get(op->ptr, "delta");
2523
2524   if (op->customdata) {
2525     ViewOpsData *vod = op->customdata;
2526
2527     sa = vod->sa;
2528     ar = vod->ar;
2529     copy_v3_v3(mousevec, vod->init.mousevec);
2530   }
2531   else {
2532     sa = CTX_wm_area(C);
2533     ar = CTX_wm_region(C);
2534     negate_v3_v3(mousevec, ((RegionView3D *)ar->regiondata)->viewinv[2]);
2535     normalize_v3(mousevec);
2536   }
2537
2538   v3d = sa->spacedata.first;
2539   rv3d = ar->regiondata;
2540
2541   const bool use_cursor_init = RNA_boolean_get(op->ptr, "use_cursor_init");
2542
2543   /* overwrite the mouse vector with the view direction (zoom into the center) */
2544   if ((use_cursor_init && (U.uiflag & USER_ZOOM_TO_MOUSEPOS)) == 0) {
2545     normalize_v3_v3(mousevec, rv3d->viewinv[2]);
2546   }
2547
2548   view_dolly_to_vector_3d(ar, rv3d->ofs, mousevec, delta < 0 ? 0.2f : 1.8f);
2549
2550   if (rv3d->viewlock & RV3D_BOXVIEW) {
2551     view3d_boxview_sync(sa, ar);
2552   }
2553
2554   ED_view3d_depth_tag_update(rv3d);
2555
2556   ED_view3d_camera_lock_sync(CTX_data_depsgraph(C), v3d, rv3d);
2557
2558   ED_region_tag_redraw(ar);
2559
2560   viewops_data_free(C, op);
2561
2562   return OPERATOR_FINISHED;
2563 }
2564
2565 /* copied from viewzoom_invoke(), changes here may apply there */
2566 static int viewdolly_invoke(bContext *C, wmOperator *op, const wmEvent *event)
2567 {
2568   ViewOpsData *vod;
2569
2570   if (viewdolly_offset_lock_check(C, op)) {
2571     return OPERATOR_CANCELLED;
2572   }
2573
2574   /* makes op->customdata */
2575   viewops_data_alloc(C, op);
2576   vod = op->customdata;
2577
2578   /* poll should check but in some cases fails, see poll func for details */
2579   if (vod->rv3d->viewlock & RV3D_LOCKED) {
2580     viewops_data_free(C, op);
2581     return OPERATOR_PASS_THROUGH;
2582   }
2583
2584   ED_view3d_smooth_view_force_finish(C, vod->v3d, vod->ar);
2585
2586   /* needs to run before 'viewops_data_create' so the backup 'rv3d->ofs' is correct */
2587   /* switch from camera view when: */
2588   if (vod->rv3d->persp != RV3D_PERSP) {
2589     if (vod->rv3d->persp == RV3D_CAMOB) {
2590       /* ignore rv3d->lpersp because dolly only makes sense in perspective mode */
2591       const Depsgraph *depsgraph = CTX_data_depsgraph(C);
2592       ED_view3d_persp_switch_from_camera(depsgraph, vod->v3d, vod->rv3d, RV3D_PERSP);
2593     }
2594     else {
2595       vod->rv3d->persp = RV3D_PERSP;
2596     }
2597     ED_region_tag_redraw(vod->ar);
2598   }
2599
2600   const bool use_cursor_init = RNA_boolean_get(op->ptr, "use_cursor_init");
2601
2602   viewops_data_create(C,
2603                       op,
2604                       event,
2605                       (viewops_flag_from_prefs() & ~VIEWOPS_FLAG_ORBIT_SELECT) |
2606                           (use_cursor_init ? VIEWOPS_FLAG_USE_MOUSE_INIT : 0));
2607
2608   /* if one or the other zoom position aren't set, set from event */
2609   if (!RNA_struct_property_is_set(op->ptr, "mx") || !RNA_struct_property_is_set(op->ptr, "my")) {
2610     RNA_int_set(op->ptr, "mx", event->x);
2611     RNA_int_set(op->ptr, "my", event->y);
2612   }
2613
2614   if (RNA_struct_property_is_set(op->ptr, "delta")) {
2615     viewdolly_exec(C, op);
2616   }
2617   else {
2618     /* overwrite the mouse vector with the view direction (zoom into the center) */
2619     if ((use_cursor_init && (U.uiflag & USER_ZOOM_TO_MOUSEPOS)) == 0) {
2620       negate_v3_v3(vod->init.mousevec, vod->rv3d->viewinv[2]);
2621       normalize_v3(vod->init.mousevec);
2622     }
2623
2624     if (event->type == MOUSEZOOM) {
2625       /* Bypass Zoom invert flag for track pads (pass false always) */
2626
2627       if (U.uiflag & USER_ZOOM_HORIZ) {
2628         vod->init.event_xy[0] = vod->prev.event_xy[0] = event->x;
2629       }
2630       else {
2631         /* Set y move = x move as MOUSEZOOM uses only x axis to pass magnification value */
2632         vod->init.event_xy[1] = vod->prev.event_xy[1] = vod->init.event_xy[1] + event->x -
2633                                                         event->prevx;
2634       }
2635       viewdolly_apply(vod, &event->prevx, (U.uiflag & USER_ZOOM_INVERT) == 0);
2636       ED_view3d_depth_tag_update(vod->rv3d);
2637
2638       viewops_data_free(C, op);
2639       return OPERATOR_FINISHED;
2640     }
2641     else {
2642       /* add temp handler */
2643       WM_event_add_modal_handler(C, op);
2644
2645       return OPERATOR_RUNNING_MODAL;
2646     }
2647   }
2648   return OPERATOR_FINISHED;
2649 }
2650
2651 static void viewdolly_cancel(bContext *C, wmOperator *op)
2652 {
2653   viewops_data_free(C, op);
2654 }
2655
2656 void VIEW3D_OT_dolly(wmOperatorType *ot)
2657 {
2658   /* identifiers */
2659   ot->name = "Dolly View";
2660   ot->description = "Dolly in/out in the view";
2661   ot->idname = "VIEW3D_OT_dolly";
2662
2663   /* api callbacks */
2664   ot->invoke = viewdolly_invoke;
2665   ot->exec = viewdolly_exec;
2666   ot->modal = viewdolly_modal;
2667   ot->poll = ED_operator_region_view3d_active;
2668   ot->cancel = viewdolly_cancel;
2669
2670   /* flags */
2671   ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_XY;
2672
2673   /* properties */
2674   view3d_operator_properties_common(
2675       ot, V3D_OP_PROP_DELTA | V3D_OP_PROP_MOUSE_CO | V3D_OP_PROP_USE_MOUSE_INIT);
2676 }
2677
2678 /** \} */
2679
2680 /* -------------------------------------------------------------------- */
2681 /** \name View All Operator
2682  *
2683  * Move & Zoom the view to fit all of it's contents.
2684  * \{ */
2685
2686 static bool view3d_object_skip_minmax(const View3D *v3d,
2687                                       const RegionView3D *rv3d,
2688                                       const Object *ob,
2689                                       const bool skip_camera,
2690                                       bool *r_only_center)
2691 {
2692   BLI_assert(ob->id.orig_id == NULL);
2693   *r_only_center = false;
2694
2695   if (skip_camera && (ob == v3d->camera)) {
2696     return true;
2697   }
2698
2699   if ((ob->type == OB_EMPTY) && (ob->empty_drawtype == OB_EMPTY_IMAGE) &&
2700       !BKE_object_empty_image_frame_is_visible_in_view3d(ob, rv3d)) {
2701     *r_only_center = true;
2702     return false;
2703   }
2704
2705   return false;
2706 }
2707
2708 static void view3d_from_minmax(bContext *C,
2709                                View3D *v3d,
2710                                ARegion *ar,
2711                                const float min[3],
2712                                const float max[3],
2713                                bool ok_dist,
2714                                const int smooth_viewtx)
2715 {
2716   RegionView3D *rv3d = ar->regiondata;
2717   float afm[3];
2718   float size;
2719
2720   ED_view3d_smooth_view_force_finish(C, v3d, ar);
2721
2722   /* SMOOTHVIEW */
2723   float new_ofs[3];
2724   float new_dist;
2725
2726   sub_v3_v3v3(afm, max, min);
2727   size = max_fff(afm[0], afm[1], afm[2]);
2728
2729   if (ok_dist) {
2730     char persp;
2731
2732     if (rv3d->is_persp) {
2733       if (rv3d->persp == RV3D_CAMOB && ED_view3d_camera_lock_check(v3d, rv3d)) {
2734         persp = RV3D_CAMOB;
2735       }
2736       else {
2737         persp = RV3D_PERSP;
2738       }
2739     }
2740     else { /* ortho */
2741       if (size < 0.0001f) {
2742         /* bounding box was a single point so do not zoom */
2743         ok_dist = false;
2744       }
2745       else {
2746         /* adjust zoom so it looks nicer */
2747         persp = RV3D_ORTHO;
2748       }
2749     }
2750
2751     if (ok_dist) {
2752       new_dist = ED_view3d_radius_to_dist(
2753           v3d, ar, CTX_data_depsgraph(C), persp, true, (size / 2) * VIEW3D_MARGIN);
2754       if (rv3d->is_persp) {
2755         /* don't zoom closer than the near clipping plane */
2756         new_dist = max_ff(new_dist, v3d->clip_start * 1.5f);
2757       }
2758     }
2759   }
2760
2761   mid_v3_v3v3(new_ofs, min, max);
2762   negate_v3(new_ofs);
2763
2764   if (rv3d->persp == RV3D_CAMOB && !ED_view3d_camera_lock_check(v3d, rv3d)) {
2765     rv3d->persp = RV3D_PERSP;
2766     ED_view3d_smooth_view(C,
2767                           v3d,
2768                           ar,
2769                           smooth_viewtx,
2770                           &(const V3D_SmoothParams){
2771                               .camera_old = v3d->camera,
2772                               .ofs = new_ofs,
2773                               .dist = ok_dist ? &new_dist : NULL,
2774                           });
2775   }
2776   else {
2777     ED_view3d_smooth_view(C,
2778                           v3d,
2779                           ar,
2780                           smooth_viewtx,
2781                           &(const V3D_SmoothParams){
2782                               .ofs = new_ofs,
2783                               .dist = ok_dist ? &new_dist : NULL,
2784                           });
2785   }
2786
2787   /* smooth view does viewlock RV3D_BOXVIEW copy */
2788 }
2789
2790 /**
2791  * Same as #view3d_from_minmax but for all regions (except cameras).
2792  */
2793 static void view3d_from_minmax_multi(bContext *C,
2794                                      View3D *v3d,
2795                                      const float min[3],
2796                                      const float max[3],
2797                                      const bool ok_dist,
2798                                      const int smooth_viewtx)
2799 {
2800   ScrArea *sa = CTX_wm_area(C);
2801   ARegion *ar;
2802   for (ar = sa->regionbase.first; ar; ar = ar->next) {
2803     if (ar->regiontype == RGN_TYPE_WINDOW) {
2804       RegionView3D *rv3d = ar->regiondata;
2805       /* when using all regions, don't jump out of camera view,
2806        * but _do_ allow locked cameras to be moved */
2807       if ((rv3d->persp != RV3D_CAMOB) || ED_view3d_camera_lock_check(v3d, rv3d)) {
2808         view3d_from_minmax(C, v3d, ar, min, max, ok_dist, smooth_viewtx);
2809       }
2810     }
2811   }
2812 }
2813
2814 static int view3d_all_exec(bContext *C, wmOperator *op)
2815 {
2816   ARegion *ar = CTX_wm_region(C);
2817   View3D *v3d = CTX_wm_view3d(C);
2818   RegionView3D *rv3d = CTX_wm_region_view3d(C);
2819   Scene *scene = CTX_data_scene(C);
2820   const Depsgraph *depsgraph = CTX_data_depsgraph(C);
2821   ViewLayer *view_layer_eval = DEG_get_evaluated_view_layer(depsgraph);
2822   Base *base_eval;
2823   const bool use_all_regions = RNA_boolean_get(op->ptr, "use_all_regions");
2824   const bool skip_camera = (ED_view3d_camera_lock_check(v3d, ar->regiondata) ||
2825                             /* any one of the regions may be locked */
2826                             (use_all_regions && v3d->flag2 & V3D_LOCK_CAMERA));
2827   const bool center = RNA_boolean_get(op->ptr, "center");
2828   const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
2829
2830   float min[3], max[3];
2831   bool changed = false;
2832
2833   if (center) {
2834     /* in 2.4x this also move the cursor to (0, 0, 0) (with shift+c). */
2835     View3DCursor *cursor = &scene->cursor;
2836     zero_v3(min);
2837     zero_v3(max);
2838     zero_v3(cursor->location);
2839     float mat3[3][3];
2840     unit_m3(mat3);
2841     BKE_scene_cursor_mat3_to_rot(cursor, mat3, false);
2842   }
2843   else {
2844     INIT_MINMAX(min, max);
2845   }
2846
2847   for (base_eval = view_layer_eval->object_bases.first; base_eval; base_eval = base_eval->next) {
2848     if (BASE_VISIBLE(v3d, base_eval)) {
2849       bool only_center = false;
2850       Object *ob = DEG_get_original_object(base_eval->object);
2851       if (view3d_object_skip_minmax(v3d, rv3d, ob, skip_camera, &only_center)) {
2852         continue;
2853       }
2854
2855       if (only_center) {
2856         minmax_v3v3_v3(min, max, base_eval->object->obmat[3]);
2857       }
2858       else {
2859         BKE_object_minmax(base_eval->object, min, max, false);
2860       }
2861       changed = true;
2862     }
2863   }
2864
2865   if (center) {
2866     DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE);
2867   }
2868
2869   if (!changed) {
2870     ED_region_tag_redraw(ar);
2871     /* TODO - should this be cancel?
2872      * I think no, because we always move the cursor, with or without
2873      * object, but in this case there is no change in the scene,
2874      * only the cursor so I choice a ED_region_tag like
2875      * view3d_smooth_view do for the center_cursor.
2876      * See bug #22640
2877      */
2878     return OPERATOR_FINISHED;
2879   }
2880
2881   if (use_all_regions) {
2882     view3d_from_minmax_multi(C, v3d, min, max, true, smooth_viewtx);
2883   }
2884   else {
2885     view3d_from_minmax(C, v3d, ar, min, max, true, smooth_viewtx);
2886   }
2887
2888   return OPERATOR_FINISHED;
2889 }
2890
2891 void VIEW3D_OT_view_all(wmOperatorType *ot)
2892 {
2893   /* identifiers */
2894   ot->name = "View All";
2895   ot->description = "View all objects in scene";
2896   ot->idname = "VIEW3D_OT_view_all";
2897
2898   /* api callbacks */
2899   ot->exec = view3d_all_exec;
2900   ot->poll = ED_operator_region_view3d_active;
2901
2902   /* flags */
2903   ot->flag = 0;
2904
2905   /* properties */
2906   view3d_operator_properties_common(ot, V3D_OP_PROP_USE_ALL_REGIONS);
2907   RNA_def_boolean(ot->srna, "center", 0, "Center", "");
2908 }
2909
2910 /** \} */
2911
2912 /* -------------------------------------------------------------------- */
2913 /** \name View Selected Operator
2914  *
2915  * Move & Zoom the view to fit selected contents.
2916  * \{ */
2917
2918 /* like a localview without local!, was centerview() in 2.4x */
2919 static int viewselected_exec(bContext *C, wmOperator *op)
2920 {
2921   ARegion *ar = CTX_wm_region(C);
2922   View3D *v3d = CTX_wm_view3d(C);
2923   RegionView3D *rv3d = CTX_wm_region_view3d(C);
2924   Scene *scene = CTX_data_scene(C);
2925   Depsgraph *depsgraph = CTX_data_depsgraph(C);
2926   ViewLayer *view_layer_eval = DEG_get_evaluated_view_layer(depsgraph);
2927   bGPdata *gpd = CTX_data_gpencil_data(C);
2928   const bool is_gp_edit = GPENCIL_ANY_MODE(gpd);
2929   const bool is_face_map = ((is_gp_edit == false) && ar->gizmo_map &&
2930                             WM_gizmomap_is_any_selected(ar->gizmo_map));
2931   Object *ob_eval = OBACT(view_layer_eval);
2932   Object *obedit = CTX_data_edit_object(C);
2933   float min[3], max[3];
2934   bool ok = false, ok_dist = true;
2935   const bool use_all_regions = RNA_boolean_get(op->ptr, "use_all_regions");
2936   const bool skip_camera = (ED_view3d_camera_lock_check(v3d, ar->regiondata) ||
2937                             /* any one of the regions may be locked */
2938                             (use_all_regions && v3d->flag2 & V3D_LOCK_CAMERA));
2939   const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
2940
2941   INIT_MINMAX(min, max);
2942   if (is_face_map) {
2943     ob_eval = NULL;
2944   }
2945
2946   if (ob_eval && (ob_eval->mode & OB_MODE_WEIGHT_PAINT)) {
2947     /* hard-coded exception, we look for the one selected armature */
2948     /* this is weak code this way, we should make a generic
2949      * active/selection callback interface once... */
2950     Base *base_eval;
2951     for (base_eval = view_layer_eval->object_bases.first; base_eval; base_eval = base_eval->next) {
2952       if (BASE_SELECTED_EDITABLE(v3d, base_eval)) {
2953         if (base_eval->object->type == OB_ARMATURE) {
2954           if (base_eval->object->mode & OB_MODE_POSE) {
2955             break;
2956           }
2957         }
2958       }
2959     }
2960     if (base_eval) {
2961       ob_eval = base_eval->object;
2962     }
2963   }
2964
2965   if (is_gp_edit) {
2966     CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) {
2967       /* we're only interested in selected points here... */
2968       if ((gps->flag & GP_STROKE_SELECT) && (gps->flag & GP_STROKE_3DSPACE)) {
2969         ok |= BKE_gpencil_stroke_minmax(gps, true, min, max);
2970       }
2971     }
2972     CTX_DATA_END;
2973
2974     if ((ob_eval) && (ok)) {
2975       mul_m4_v3(ob_eval->obmat, min);
2976       mul_m4_v3(ob_eval->obmat, max);
2977     }
2978   }
2979   else if (is_face_map) {
2980     ok = WM_gizmomap_minmax(ar->gizmo_map, true, true, min, max);
2981   }
2982   else if (obedit) {
2983     /* only selected */
2984     FOREACH_OBJECT_IN_MODE_BEGIN (view_layer_eval, v3d, obedit->type, obedit->mode, ob_eval_iter) {
2985       ok |= ED_view3d_minmax_verts(ob_eval_iter, min, max);
2986     }
2987     FOREACH_OBJECT_IN_MODE_END;
2988   }
2989   else if (ob_eval && (ob_eval->mode & OB_MODE_POSE)) {
2990     FOREACH_OBJECT_IN_MODE_BEGIN (
2991         view_layer_eval, v3d, ob_eval->type, ob_eval->mode, ob_eval_iter) {
2992       ok |= BKE_pose_minmax(ob_eval_iter, min, max, true, true);
2993     }
2994     FOREACH_OBJECT_IN_MODE_END;
2995   }
2996   else if (BKE_paint_select_face_test(ob_eval)) {
2997     ok = paintface_minmax(ob_eval, min, max);
2998   }
2999   else if (ob_eval && (ob_eval->mode & OB_MODE_PARTICLE_EDIT)) {
3000     ok = PE_minmax(scene, view_layer_eval, min, max);
3001   }
3002   else if (ob_eval && (ob_eval->mode & (OB_MODE_SCULPT | OB_MODE_VERTEX_PAINT |
3003                                         OB_MODE_WEIGHT_PAINT | OB_MODE_TEXTURE_PAINT))) {
3004     BKE_paint_stroke_get_average(scene, ob_eval, min);
3005     copy_v3_v3(max, min);
3006     ok = true;
3007     ok_dist = 0; /* don't zoom */
3008   }
3009   else {
3010     Base *base_eval;
3011     for (base_eval = FIRSTBASE(view_layer_eval); base_eval; base_eval = base_eval->next) {
3012       if (BASE_SELECTED(v3d, base_eval)) {
3013         bool only_center = false;
3014         Object *ob = DEG_get_original_object(base_eval->object);
3015         if (view3d_object_skip_minmax(v3d, rv3d, ob, skip_camera, &only_center)) {
3016           continue;
3017         }
3018
3019         /* account for duplis */
3020         if (BKE_object_minmax_dupli(depsgraph, scene, base_eval->object, min, max, false) == 0) {
3021           /* use if duplis not found */
3022           if (only_center) {
3023             minmax_v3v3_v3(min, max, base_eval->object->obmat[3]);
3024           }
3025           else {
3026             BKE_object_minmax(base_eval->object, min, max, false);
3027           }
3028         }
3029
3030         ok = 1;
3031       }
3032     }
3033   }
3034
3035   if (ok == 0) {
3036     return OPERATOR_FINISHED;
3037   }
3038
3039   if (use_all_regions) {
3040     view3d_from_minmax_multi(C, v3d, min, max, ok_dist, smooth_viewtx);
3041   }
3042   else {
3043     view3d_from_minmax(C, v3d, ar, min, max, ok_dist, smooth_viewtx);
3044   }
3045
3046   return OPERATOR_FINISHED;
3047 }
3048
3049 void VIEW3D_OT_view_selected(wmOperatorType *ot)
3050 {
3051   /* identifiers */
3052   ot->name = "View Selected";
3053   ot->description = "Move the view to the selection center";
3054   ot->idname = "VIEW3D_OT_view_selected";
3055
3056   /* api callbacks */
3057   ot->exec = viewselected_exec;
3058   ot->poll = ED_operator_region_view3d_active;
3059
3060   /* flags */
3061   ot->flag = 0;
3062
3063   /* properties */
3064   view3d_operator_properties_common(ot, V3D_OP_PROP_USE_ALL_REGIONS);
3065 }
3066
3067 /** \} */
3068
3069 /* -------------------------------------------------------------------- */
3070 /** \name View Lock Clear Operator
3071  * \{ */
3072
3073 static int view_lock_clear_exec(bContext *C, wmOperator *UNUSED(op))
3074 {
3075   View3D *v3d = CTX_wm_view3d(C);
3076
3077   if (v3d) {
3078     ED_view3d_lock_clear(v3d);
3079
3080     WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, v3d);
3081
3082     return OPERATOR_FINISHED;
3083   }
3084   else {
3085     return OPERATOR_CANCELLED;
3086   }
3087 }
3088
3089 void VIEW3D_OT_view_lock_clear(wmOperatorType *ot)
3090 {
3091
3092   /* identifiers */
3093   ot->name = "View Lock Clear";
3094   ot->description = "Clear all view locking";
3095   ot->idname = "VIEW3D_OT_view_lock_clear";
3096
3097   /* api callbacks */
3098   ot->exec = view_lock_clear_exec;
3099   ot->poll = ED_operator_region_view3d_active;
3100
3101   /* flags */
3102   ot->flag = 0;
3103 }
3104
3105 /** \} */
3106
3107 /* -------------------------------------------------------------------- */
3108 /** \name View Lock to Active Operator
3109  * \{ */
3110
3111 static int view_lock_to_active_exec(bContext *C, wmOperator *UNUSED(op))
3112 {
3113   View3D *v3d = CTX_wm_view3d(C);
3114   Object *obact = CTX_data_active_object(C);
3115
3116   if (v3d) {
3117     ED_view3d_lock_clear(v3d);
3118
3119     v3d->ob_centre = obact; /* can be NULL */
3120
3121     if (obact && obact->type == OB_ARMATURE) {
3122       if (obact->mode & OB_MODE_POSE) {
3123         Object *obact_eval = DEG_get_evaluated_object(CTX_data_depsgraph(C), obact);
3124         bPoseChannel *pcham_act = BKE_pose_channel_active(obact_eval);
3125         if (pcham_act) {
3126           BLI_strncpy(v3d->ob_centre_bone, pcham_act->name, sizeof(v3d->ob_centre_bone));
3127         }
3128       }
3129       else {
3130         EditBone *ebone_act = ((bArmature *)obact->data)->act_edbone;
3131         if (ebone_act) {
3132           BLI_strncpy(v3d->ob_centre_bone, ebone_act->name, sizeof(v3d->ob_centre_bone));
3133         }
3134       }
3135     }
3136
3137     WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, v3d);
3138
3139     return OPERATOR_FINISHED;
3140   }
3141   else {
3142     return OPERATOR_CANCELLED;
3143   }
3144 }
3145
3146 void VIEW3D_OT_view_lock_to_active(wmOperatorType *ot)
3147 {
3148
3149   /* identifiers */
3150   ot->name = "View Lock to Active";
3151   ot->description = "Lock the view to the active object/bone";
3152   ot->idname = "VIEW3D_OT_view_lock_to_active";
3153
3154   /* api callbacks */
3155   ot->exec = view_lock_to_active_exec;
3156   ot->poll = ED_operator_region_view3d_active;
3157
3158   /* flags */
3159   ot->flag = 0;
3160 }
3161
3162 /** \} */
3163
3164 /* -------------------------------------------------------------------- */
3165 /** \name View Center Cursor Operator
3166  * \{ */
3167
3168 static int viewcenter_cursor_exec(bContext *C, wmOperator *op)
3169 {
3170   View3D *v3d = CTX_wm_view3d(C);
3171   RegionView3D *rv3d = CTX_wm_region_view3d(C);
3172   Scene *scene = CTX_data_scene(C);
3173
3174   if (rv3d) {
3175     ARegion *ar = CTX_wm_region(C);
3176     const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
3177
3178     ED_view3d_smooth_view_force_finish(C, v3d, ar);
3179
3180     /* non camera center */
3181     float new_ofs[3];
3182     negate_v3_v3(new_ofs, scene->cursor.location);
3183     ED_view3d_smooth_view(C, v3d, ar, smooth_viewtx, &(const V3D_SmoothParams){.ofs = new_ofs});
3184
3185     /* smooth view does viewlock RV3D_BOXVIEW copy */
3186   }
3187
3188   return OPERATOR_FINISHED;
3189 }
3190
3191 void VIEW3D_OT_view_center_cursor(wmOperatorType *ot)
3192 {
3193   /* identifiers */
3194   ot->name = "Center View to Cursor";
3195   ot->description = "Center the view so that the cursor is in the middle of the view";
3196   ot->idname = "VIEW3D_OT_view_center_cursor";
3197
3198   /* api callbacks */
3199   ot->exec = viewcenter_cursor_exec;
3200   ot->poll = ED_operator_view3d_active;
3201
3202   /* flags */
3203   ot->flag = 0;
3204 }
3205
3206 /** \} */
3207
3208 /* -------------------------------------------------------------------- */
3209 /** \name View Center Pick Operator
3210  * \{ */
3211
3212 static int viewcenter_pick_invoke(bContext *C, wmOperator *op, const wmEvent *event)
3213 {
3214   View3D *v3d = CTX_wm_view3d(C);
3215   RegionView3D *rv3d = CTX_wm_region_view3d(C);
3216   ARegion *ar = CTX_wm_region(C);
3217
3218   if (rv3d) {
3219     struct Depsgraph *depsgraph = CTX_data_depsgraph(C);
3220     float new_ofs[3];
3221     const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
3222
3223     ED_view3d_smooth_view_force_finish(C, v3d, ar);
3224
3225     view3d_operator_needs_opengl(C);
3226
3227     if (ED_view3d_autodist(depsgraph, ar, v3d, event->mval, new_ofs, false, NULL)) {
3228       /* pass */
3229     }
3230     else {
3231       /* fallback to simple pan */
3232       negate_v3_v3(new_ofs, rv3d->ofs);
3233       ED_view3d_win_to_3d_int(v3d, ar, new_ofs, event->mval, new_ofs);
3234     }
3235     negate_v3(new_ofs);
3236     ED_view3d_smooth_view(C, v3d, ar, smooth_viewtx, &(const V3D_SmoothParams){.ofs = new_ofs});
3237   }
3238
3239   return OPERATOR_FINISHED;
3240 }
3241
3242 void VIEW3D_OT_view_center_pick(wmOperatorType *ot)
3243 {
3244   /* identifiers */
3245   ot->name = "Center View to Mouse";
3246   ot->description = "Center the view to the Z-depth position under the mouse cursor";
3247   ot->idname = "VIEW3D_OT_view_center_pick";
3248
3249   /* api callbacks */
3250   ot->invoke = viewcenter_pick_invoke;
3251   ot->poll = ED_operator_view3d_active;
3252
3253   /* flags */
3254   ot->flag = 0;
3255 }
3256
3257 /** \} */
3258
3259 /* -------------------------------------------------------------------- */
3260 /** \name View Camera Center Operator
3261  * \{ */
3262
3263 static int view3d_center_camera_exec(bContext *C, wmOperator *UNUSED(op))
3264 {
3265   Depsgraph *depsgraph = CTX_data_depsgraph(C);
3266   Scene *scene = CTX_data_scene(C);
3267   float xfac, yfac;
3268   float size[2];
3269
3270   View3D *v3d;
3271   ARegion *ar;
3272   RegionView3D *rv3d;
3273
3274   /* no NULL check is needed, poll checks */
3275   ED_view3d_context_user_region(C, &v3d, &ar);
3276   rv3d = ar->regiondata;
3277
3278   rv3d->camdx = rv3d->camdy = 0.0f;
3279
3280   ED_view3d_calc_camera_border_size(scene, depsgraph, ar, v3d, rv3d, size);
3281
3282   /* 4px is just a little room from the edge of the area */
3283   xfac = (float)ar->winx / (float)(size[0] + 4);
3284   yfac = (float)ar->winy / (float)(size[1] + 4);
3285
3286   rv3d->camzoom = BKE_screen_view3d_zoom_from_fac(min_ff(xfac, yfac));
3287   CLAMP(rv3d->camzoom, RV3D_CAMZOOM_MIN, RV3D_CAMZOOM_MAX);
3288
3289   WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, v3d);
3290
3291   return OPERATOR_FINISHED;
3292 }
3293
3294 void VIEW3D_OT_view_center_camera(wmOperatorType *ot)
3295 {
3296   /* identifiers */
3297   ot->name = "View Camera Center";
3298   ot->description = "Center the camera view";
3299   ot->idname = "VIEW3D_OT_view_center_camera";
3300
3301   /* api callbacks */
3302   ot->exec = view3d_center_camera_exec;
3303   ot->poll = view3d_camera_user_poll;
3304
3305   /* flags */
3306   ot->flag = 0;
3307 }
3308
3309 /** \} */
3310
3311 /* -------------------------------------------------------------------- */
3312 /** \name View Lock Center Operator
3313  * \{ */
3314
3315 static int view3d_center_lock_exec(bContext *C, wmOperator *UNUSED(op))
3316 {
3317   RegionView3D *rv3d = CTX_wm_region_view3d(C);
3318
3319   zero_v2(rv3d->ofs_lock);
3320
3321   WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, CTX_wm_view3d(C));
3322
3323   return OPERATOR_FINISHED;
3324 }
3325
3326 void VIEW3D_OT_view_center_lock(wmOperatorType *ot)
3327 {
3328   /* identifiers */
3329   ot->name = "View Lock Center";
3330   ot->description = "Center the view lock offset";
3331   ot->idname = "VIEW3D_OT_view_center_lock";
3332
3333   /* api callbacks */
3334   ot->exec = view3d_center_lock_exec;
3335   ot->poll = view3d_lock_poll;
3336
3337   /* flags */
3338   ot->flag = 0;
3339 }
3340
3341 /** \} */
3342
3343 /* -------------------------------------------------------------------- */
3344 /** \name Set Render Border Operator
3345  * \{ */
3346
3347 static int render_border_exec(bContext *C, wmOperator *op)
3348 {
3349   View3D *v3d = CTX_wm_view3d(C);
3350   ARegion *ar = CTX_wm_region(C);
3351   RegionView3D *rv3d = ED_view3d_context_rv3d(C);
3352
3353   Scene *scene = CTX_data_scene(C);
3354
3355   rcti rect;
3356   rctf vb, border;
3357
3358   /* get box select values using rna */
3359   WM_operator_properties_border_to_rcti(op, &rect);
3360
3361   /* calculate range */
3362
3363   if (rv3d->persp == RV3D_CAMOB) {
3364     Depsgraph *depsgraph = CTX_data_depsgraph(C);
3365     ED_view3d_calc_camera_border(scene, depsgraph, ar, v3d, rv3d, &vb, false);
3366   }
3367   else {
3368     vb.xmin = 0;
3369     vb.ymin = 0;
3370     vb.xmax = ar->winx;
3371     vb.ymax = ar->winy;
3372   }
3373
3374   border.xmin = ((float)rect.xmin - vb.xmin) / BLI_rctf_size_x(&vb);
3375   border.ymin = ((float)rect.ymin - vb.ymin) / BLI_rctf_size_y(&vb);
3376   border.xmax = ((float)rect.xmax - vb.xmin) / BLI_rctf_size_x(&vb);
3377   border.ymax = ((float)rect.ymax - vb.ymin) / BLI_rctf_size_y(&vb);
3378
3379   /* actually set border */
3380   CLAMP(border.xmin, 0.0f, 1.0f);
3381   CLAMP(border.ymin, 0.0f, 1.0f);
3382   CLAMP(border.xmax, 0.0f, 1.0f);
3383   CLAMP(border.ymax, 0.0f, 1.0f);
3384
3385   if (rv3d->persp == RV3D_CAMOB) {
3386     scene->r.border = border;
3387
3388     WM_event_add_notifier(C, NC_SCENE | ND_RENDER_OPTIONS, NULL);
3389   }
3390   else {
3391     v3d->render_border = border;
3392
3393     WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, NULL);
3394   }
3395
3396   /* drawing a border outside the camera view switches off border rendering */
3397   if ((border.xmin == border.xmax || border.ymin == border.ymax)) {
3398     if (rv3d->persp == RV3D_CAMOB) {
3399       scene->r.mode &= ~R_BORDER;
3400     }
3401     else {
3402       v3d->flag2 &= ~V3D_RENDER_BORDER;
3403     }
3404   }
3405   else {
3406     if (rv3d->persp == RV3D_CAMOB) {
3407       scene->r.mode |= R_BORDER;
3408     }
3409     else {
3410       v3d->flag2 |= V3D_RENDER_BORDER;
3411     }
3412   }
3413
3414   if (rv3d->persp == RV3D_CAMOB) {
3415     DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE);
3416   }
3417   return OPERATOR_FINISHED;
3418 }
3419
3420 void VIEW3D_OT_render_border(wmOperatorType *ot)
3421 {
3422   /* identifiers */
3423   ot->name = "Set Render Region";
3424   ot->description = "Set the boundaries of the border render and enable border render";
3425   ot->idname = "VIEW3D_OT_render_border";
3426
3427   /* api callbacks */
3428   ot->invoke = WM_gesture_box_invoke;
3429   ot->exec = render_border_exec;
3430   ot->modal = WM_gesture_box_modal;
3431   ot->cancel = WM_gesture_box_cancel;
3432
3433   ot->poll = ED_operator_view3d_active;
3434
3435   /* flags */
3436   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3437
3438   /* properties */
3439   WM_operator_properties_border(ot);
3440 }
3441
3442 /** \} */
3443
3444 /* -------------------------------------------------------------------- */
3445 /** \name Clear Render Border Operator
3446  * \{ */
3447
3448 static int clear_render_border_exec(bContext *C, wmOperator *UNUSED(op))
3449 {
3450   View3D *v3d = CTX_wm_view3d(C);
3451   RegionView3D *rv3d = ED_view3d_context_rv3d(C);
3452
3453   Scene *scene = CTX_data_scene(C);
3454   rctf *border = NULL;
3455
3456   if (rv3d->persp == RV3D_CAMOB) {
3457     scene->r.mode &= ~R_BORDER;
3458     border = &scene->r.border;
3459
3460     WM_event_add_notifier(C, NC_SCENE | ND_RENDER_OPTIONS, NULL);
3461   }
3462   else {
3463     v3d->flag2 &= ~V3D_RENDER_BORDER;
3464     border = &v3d->render_border;
3465
3466     WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, NULL);
3467   }
3468
3469   border->xmin = 0.0f;
3470   border->ymin = 0.0f;
3471   border->xmax = 1.0f;
3472   border->ymax = 1.0f;
3473
3474   if (rv3d->persp == RV3D_CAMOB) {
3475     DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE);
3476   }
3477   return OPERATOR_FINISHED;
3478 }
3479
3480 void VIEW3D_OT_clear_render_border(wmOperatorType *ot)
3481 {
3482   /* identifiers */
3483   ot->name = "Clear Render Region";
3484   ot->description = "Clear the boundaries of the border render and disable border render";
3485   ot->idname = "VIEW3D_OT_clear_render_border";
3486
3487   /* api callbacks */
3488   ot->exec = clear_render_border_exec;
3489   ot->poll = ED_operator_view3d_active;
3490
3491   /* flags */
3492   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3493 }
3494
3495 /** \} */
3496
3497 /* -------------------------------------------------------------------- */
3498 /** \name Border Zoom Operator
3499  * \{ */
3500
3501 static int view3d_zoom_border_exec(bContext *C, wmOperator *op)
3502 {
3503   ARegion *ar = CTX_wm_region(C);
3504   View3D *v3d = CTX_wm_view3d(C);
3505   RegionView3D *rv3d = CTX_wm_region_view3d(C);
3506   const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
3507
3508   /* Zooms in on a border drawn by the user */
3509   rcti rect;
3510   float dvec[3], vb[2], xscale, yscale;
3511   float dist_range[2];
3512
3513   /* SMOOTHVIEW */
3514   float new_dist;
3515   float new_ofs[3];
3516
3517   /* ZBuffer depth vars */
3518   float depth_close = FLT_MAX;
3519   float cent[2], p[3];
3520
3521   /* note; otherwise opengl won't work */
3522   view3d_operator_needs_opengl(C);
3523
3524   /* get box select values using rna */
3525   WM_operator_properties_border_to_rcti(op, &rect);
3526
3527   /* check if zooming in/out view */
3528   const bool zoom_in = !RNA_boolean_get(op->ptr, "zoom_out");
3529
3530   ED_view3d_dist_range_get(v3d, dist_range);
3531
3532   /* Get Z Depths, needed for perspective, nice for ortho */
3533   ED_view3d_draw_depth(CTX_data_depsgraph(C), ar, v3d, true);
3534
3535   {
3536     /* avoid allocating the whole depth buffer */
3537     ViewDepths depth_temp = {0};
3538
3539     /* avoid view3d_update_depths() for speed. */
3540     view3d_update_depths_rect(ar, &depth_temp, &rect);
3541
3542     /* find the closest Z pixel */
3543     depth_close = view3d_depth_near(&depth_temp);
3544
3545     MEM_SAFE_FREE(depth_temp.depths);
3546   }
3547
3548   cent[0] = (((float)rect.xmin) + ((float)rect.xmax)) / 2;
3549   cent[1] = (((float)rect.ymin) + ((float)rect.ymax)) / 2;
3550
3551   if (rv3d->is_persp) {
3552     float p_corner[3];
3553
3554     /* no depths to use, we cant do anything! */
3555     if (depth_close == FLT_MAX) {
3556       BKE_report(op->reports, RPT_ERROR, "Depth too large");
3557       return OPERATOR_CANCELLED;
3558     }
3559     /* convert border to 3d coordinates */
3560     if ((!ED_view3d_unproject(ar, cent[0], cent[1], depth_close, p)) ||
3561         (!ED_view3d_unproject(ar, rect.xmin, rect.ymin, depth_close, p_corner))) {
3562       return OPERATOR_CANCELLED;
3563     }
3564
3565     sub_v3_v3v3(dvec, p, p_corner);
3566     negate_v3_v3(new_ofs, p);
3567
3568     new_dist = len_v3(dvec);
3569
3570     /* ignore dist_range min */
3571     dist_range[0] = v3d->clip_start * 1.5f;
3572   }
3573   else { /* othographic */
3574     /* find the current window width and height */
3575     vb[0] = ar->winx;
3576     vb[1] = ar->winy;
3577
3578     new_dist = rv3d->dist;
3579
3580     /* convert the drawn rectangle into 3d space */
3581     if (depth_close != FLT_MAX && ED_view3d_unproject(ar, cent[0], cent[1], depth_close, p)) {
3582       negate_v3_v3(new_ofs, p);
3583     }
3584     else {
3585       float mval_f[2];
3586       float zfac;
3587
3588       /* We cant use the depth, fallback to the old way that dosnt set the center depth */
3589       copy_v3_v3(new_ofs, rv3d->ofs);
3590
3591       {
3592         float tvec[3];
3593         negate_v3_v3(tvec, new_ofs);
3594         zfac = ED_view3d_calc_zfac(rv3d, tvec, NULL);
3595       }
3596
3597       mval_f[0] = (rect.xmin + rect.xmax - vb[0]) / 2.0f;
3598       mval_f[1] = (rect.ymin + rect.ymax - vb[1]) / 2.0f;
3599       ED_view3d_win_to_delta(ar, mval_f, dvec, zfac);
3600       /* center the view to the center of the rectangle */
3601       sub_v3_v3(new_ofs, dvec);
3602     }
3603
3604     /* work out the ratios, so that everything selected fits when we zoom */
3605     xscale = (BLI_rcti_size_x(&rect) / vb[0]);
3606     yscale = (BLI_rcti_size_y(&rect) / vb[1]);
3607     new_dist *= max_ff(xscale, yscale);
3608   }
3609
3610   if (!zoom_in) {
3611     sub_v3_v3v3(dvec, new_ofs, rv3d->ofs);
3612     new_dist = rv3d->dist * (rv3d->dist / new_dist);
3613     add_v3_v3v3(new_ofs, rv3d->ofs, dvec);
3614   }
3615
3616   /* clamp after because we may have been zooming out */
3617   CLAMP(new_dist, dist_range[0], dist_range[1]);
3618
3619   /* TODO(campbell): 'is_camera_lock' not currently working well. */
3620   const bool is_camera_lock = ED_view3d_camera_lock_check(v3d, rv3d);
3621   if ((rv3d->persp == RV3D_CAMOB) && (is_camera_lock == false)) {
3622     Depsgraph *depsgraph = CTX_data_depsgraph(C);
3623     ED_view3d_persp_switch_from_camera(depsgraph, v3d, rv3d, RV3D_PERSP);
3624   }
3625
3626   ED_view3d_smooth_view(C,
3627                         v3d,
3628                         ar,
3629                         smooth_viewtx,
3630                         &(const V3D_SmoothParams){
3631                             .ofs = new_ofs,
3632                             .dist = &new_dist,
3633                         });
3634
3635   if (rv3d->viewlock & RV3D_BOXVIEW) {
3636     view3d_boxview_sync(CTX_wm_area(C), ar);
3637   }
3638
3639   return OPERATOR_FINISHED;
3640 }
3641
3642 void VIEW3D_OT_zoom_border(wmOperatorType *ot)
3643 {
3644   /* identifiers */
3645   ot->name = "Zoom to Border";
3646   ot->description = "Zoom in the view to the nearest object contained in the border";
3647   ot->idname = "VIEW3D_OT_zoom_border";
3648
3649   /* api callbacks */
3650   ot->invoke = WM_gesture_box_invoke;
3651   ot->exec = view3d_zoom_border_exec;
3652   ot->modal = WM_gesture_box_modal;
3653   ot->cancel = WM_gesture_box_cancel;
3654
3655   ot->poll = ED_operator_region_view3d_active;
3656
3657   /* flags */
3658   ot->flag = 0;
3659
3660   /* properties */
3661   WM_operator_properties_gesture_box_zoom(ot);
3662 }
3663
3664 /** \} */
3665
3666 /* -------------------------------------------------------------------- */
3667 /** \name Set Camera Zoom 1:1 Operator
3668  *
3669  * Sets the view to 1:1 camera/render-pixel.
3670  * \{ */
3671
3672 static void view3d_set_1_to_1_viewborder(Scene *scene,
3673                                          Depsgraph *depsgraph,
3674                                &nb