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