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