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