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