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