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