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