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