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