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