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