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