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