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