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