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