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