1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  *
16  * The Original Code is Copyright (C) 2008 Blender Foundation.
17  * All rights reserved.
18  */
19 
20 /** \file
21  * \ingroup spview3d
22  *
23  * 3D view manipulation/operators.
24  */
25 
26 #include <float.h>
27 #include <math.h>
28 #include <stdio.h>
29 #include <string.h>
30 
31 #include "DNA_armature_types.h"
32 #include "DNA_camera_types.h"
33 #include "DNA_curve_types.h"
34 #include "DNA_gpencil_types.h"
35 #include "DNA_object_types.h"
36 #include "DNA_scene_types.h"
37 
38 #include "MEM_guardedalloc.h"
39 
40 #include "BLI_blenlib.h"
41 #include "BLI_math.h"
42 #include "BLI_utildefines.h"
43 
44 #include "BKE_action.h"
45 #include "BKE_armature.h"
46 #include "BKE_camera.h"
47 #include "BKE_context.h"
48 #include "BKE_font.h"
49 #include "BKE_gpencil_geom.h"
50 #include "BKE_layer.h"
51 #include "BKE_lib_id.h"
52 #include "BKE_main.h"
53 #include "BKE_object.h"
54 #include "BKE_paint.h"
55 #include "BKE_report.h"
56 #include "BKE_scene.h"
57 #include "BKE_screen.h"
58 
59 #include "DEG_depsgraph.h"
60 #include "DEG_depsgraph_query.h"
61 
62 #include "WM_api.h"
63 #include "WM_message.h"
64 #include "WM_types.h"
65 
66 #include "RNA_access.h"
67 #include "RNA_define.h"
68 
69 #include "ED_armature.h"
70 #include "ED_mesh.h"
71 #include "ED_particle.h"
72 #include "ED_screen.h"
73 #include "ED_transform.h"
74 #include "ED_transform_snap_object_context.h"
75 #include "ED_view3d.h"
76 
77 #include "UI_resources.h"
78 
79 #include "PIL_time.h"
80 
81 #include "view3d_intern.h" /* own include */
82 
83 enum {
84   HAS_TRANSLATE = (1 << 0),
85   HAS_ROTATE = (1 << 0),
86 };
87 
88 /* test for unlocked camera view in quad view */
view3d_camera_user_poll(bContext * C)89 static bool view3d_camera_user_poll(bContext *C)
90 {
91   View3D *v3d;
92   ARegion *region;
93 
94   if (ED_view3d_context_user_region(C, &v3d, &region)) {
95     RegionView3D *rv3d = region->regiondata;
96     if ((rv3d->persp == RV3D_CAMOB) && !(RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ANY_TRANSFORM)) {
97       return 1;
98     }
99   }
100 
101   return 0;
102 }
103 
view3d_lock_poll(bContext * C)104 static bool view3d_lock_poll(bContext *C)
105 {
106   View3D *v3d = CTX_wm_view3d(C);
107   if (v3d) {
108     RegionView3D *rv3d = CTX_wm_region_view3d(C);
109     if (rv3d) {
110       return ED_view3d_offset_lock_check(v3d, rv3d);
111     }
112   }
113   return false;
114 }
115 
view3d_pan_poll(bContext * C)116 static bool view3d_pan_poll(bContext *C)
117 {
118   if (ED_operator_region_view3d_active(C)) {
119     const RegionView3D *rv3d = CTX_wm_region_view3d(C);
120     return !(RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_LOCATION);
121   }
122   return false;
123 }
124 
view3d_zoom_or_dolly_poll(bContext * C)125 static bool view3d_zoom_or_dolly_poll(bContext *C)
126 {
127   if (ED_operator_region_view3d_active(C)) {
128     const RegionView3D *rv3d = CTX_wm_region_view3d(C);
129     return !(RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ZOOM_AND_DOLLY);
130   }
131   return false;
132 }
133 
134 /* -------------------------------------------------------------------- */
135 /** \name Generic View Operator Properties
136  * \{ */
137 
138 enum eV3D_OpPropFlag {
139   V3D_OP_PROP_MOUSE_CO = (1 << 0),
140   V3D_OP_PROP_DELTA = (1 << 1),
141   V3D_OP_PROP_USE_ALL_REGIONS = (1 << 2),
142   V3D_OP_PROP_USE_MOUSE_INIT = (1 << 3),
143 };
144 
view3d_operator_properties_common(wmOperatorType * ot,const enum eV3D_OpPropFlag flag)145 static void view3d_operator_properties_common(wmOperatorType *ot, const enum eV3D_OpPropFlag flag)
146 {
147   if (flag & V3D_OP_PROP_MOUSE_CO) {
148     PropertyRNA *prop;
149     prop = RNA_def_int(ot->srna, "mx", 0, 0, INT_MAX, "Region Position X", "", 0, INT_MAX);
150     RNA_def_property_flag(prop, PROP_HIDDEN);
151     prop = RNA_def_int(ot->srna, "my", 0, 0, INT_MAX, "Region Position Y", "", 0, INT_MAX);
152     RNA_def_property_flag(prop, PROP_HIDDEN);
153   }
154   if (flag & V3D_OP_PROP_DELTA) {
155     RNA_def_int(ot->srna, "delta", 0, INT_MIN, INT_MAX, "Delta", "", INT_MIN, INT_MAX);
156   }
157   if (flag & V3D_OP_PROP_USE_ALL_REGIONS) {
158     PropertyRNA *prop;
159     prop = RNA_def_boolean(
160         ot->srna, "use_all_regions", 0, "All Regions", "View selected for all regions");
161     RNA_def_property_flag(prop, PROP_SKIP_SAVE);
162   }
163   if (flag & V3D_OP_PROP_USE_MOUSE_INIT) {
164     WM_operator_properties_use_cursor_init(ot);
165   }
166 }
167 
168 /** \} */
169 
170 /* -------------------------------------------------------------------- */
171 /** \name Generic View Operator Custom-Data
172  * \{ */
173 
174 typedef struct ViewOpsData {
175   /** Context pointers (assigned by #viewops_data_alloc). */
176   Main *bmain;
177   Scene *scene;
178   ScrArea *area;
179   ARegion *region;
180   View3D *v3d;
181   RegionView3D *rv3d;
182   Depsgraph *depsgraph;
183 
184   /** Needed for continuous zoom. */
185   wmTimer *timer;
186 
187   /** Viewport state on initialization, don't change afterwards. */
188   struct {
189     float dist;
190     float camzoom;
191     float quat[4];
192     /** #wmEvent.x, y. */
193     int event_xy[2];
194     /** Offset to use when #VIEWOPS_FLAG_USE_MOUSE_INIT is not set.
195      * so we can simulate pressing in the middle of the screen. */
196     int event_xy_offset[2];
197     /** #wmEvent.type that triggered the operator. */
198     int event_type;
199     float ofs[3];
200     /** Initial distance to 'ofs'. */
201     float zfac;
202 
203     /** Trackball rotation only. */
204     float trackvec[3];
205     /** Dolly only. */
206     float mousevec[3];
207 
208     /**
209      * #RegionView3D.persp set after auto-perspective is applied.
210      * If we want the value before running the operator, add a separate member.
211      */
212     char persp;
213   } init;
214 
215   /** Previous state (previous modal event handled). */
216   struct {
217     int event_xy[2];
218     /** For operators that use time-steps (continuous zoom). */
219     double time;
220   } prev;
221 
222   /** Current state. */
223   struct {
224     /** Working copy of #RegionView3D.viewquat, needed for rotation calculation
225      * so we can apply snap to the view-port while keeping the unsnapped rotation
226      * here to use when snap is disabled and for continued calculation. */
227     float viewquat[4];
228   } curr;
229 
230   float reverse;
231   bool axis_snap; /* view rotate only */
232 
233   /** Use for orbit selection and auto-dist. */
234   float dyn_ofs[3];
235   bool use_dyn_ofs;
236 } ViewOpsData;
237 
238 /**
239  * Size of the sphere being dragged for trackball rotation within the view bounds.
240  * also affects speed (smaller is faster).
241  */
242 #define TRACKBALLSIZE (1.1f)
243 
calctrackballvec(const rcti * rect,const int event_xy[2],float r_dir[3])244 static void calctrackballvec(const rcti *rect, const int event_xy[2], float r_dir[3])
245 {
246   const float radius = TRACKBALLSIZE;
247   const float t = radius / (float)M_SQRT2;
248   const float size[2] = {BLI_rcti_size_x(rect), BLI_rcti_size_y(rect)};
249   /* Aspect correct so dragging in a non-square view doesn't squash the direction.
250    * So diagonal motion rotates the same direction the cursor is moving. */
251   const float size_min = min_ff(size[0], size[1]);
252   const float aspect[2] = {size_min / size[0], size_min / size[1]};
253 
254   /* Normalize x and y. */
255   r_dir[0] = (event_xy[0] - BLI_rcti_cent_x(rect)) / ((size[0] * aspect[0]) / 2.0);
256   r_dir[1] = (event_xy[1] - BLI_rcti_cent_y(rect)) / ((size[1] * aspect[1]) / 2.0);
257   const float d = len_v2(r_dir);
258   if (d < t) {
259     /* Inside sphere. */
260     r_dir[2] = sqrtf(square_f(radius) - square_f(d));
261   }
262   else {
263     /* On hyperbola. */
264     r_dir[2] = square_f(t) / d;
265   }
266 }
267 
268 /**
269  * Allocate and fill in context pointers for #ViewOpsData
270  */
viewops_data_alloc(bContext * C,wmOperator * op)271 static void viewops_data_alloc(bContext *C, wmOperator *op)
272 {
273   ViewOpsData *vod = MEM_callocN(sizeof(ViewOpsData), "viewops data");
274 
275   /* store data */
276   op->customdata = vod;
277   vod->bmain = CTX_data_main(C);
278   vod->depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
279   vod->scene = CTX_data_scene(C);
280   vod->area = CTX_wm_area(C);
281   vod->region = CTX_wm_region(C);
282   vod->v3d = vod->area->spacedata.first;
283   vod->rv3d = vod->region->regiondata;
284 }
285 
view3d_orbit_apply_dyn_ofs(float r_ofs[3],const float ofs_old[3],const float viewquat_old[4],const float viewquat_new[4],const float dyn_ofs[3])286 void view3d_orbit_apply_dyn_ofs(float r_ofs[3],
287                                 const float ofs_old[3],
288                                 const float viewquat_old[4],
289                                 const float viewquat_new[4],
290                                 const float dyn_ofs[3])
291 {
292   float q[4];
293   invert_qt_qt_normalized(q, viewquat_old);
294   mul_qt_qtqt(q, q, viewquat_new);
295 
296   invert_qt_normalized(q);
297 
298   sub_v3_v3v3(r_ofs, ofs_old, dyn_ofs);
299   mul_qt_v3(q, r_ofs);
300   add_v3_v3(r_ofs, dyn_ofs);
301 }
302 
view3d_orbit_calc_center(bContext * C,float r_dyn_ofs[3])303 static bool view3d_orbit_calc_center(bContext *C, float r_dyn_ofs[3])
304 {
305   static float lastofs[3] = {0, 0, 0};
306   bool is_set = false;
307 
308   const Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
309   Scene *scene = CTX_data_scene(C);
310   ViewLayer *view_layer_eval = DEG_get_evaluated_view_layer(depsgraph);
311   View3D *v3d = CTX_wm_view3d(C);
312   Object *ob_act_eval = OBACT(view_layer_eval);
313   Object *ob_act = DEG_get_original_object(ob_act_eval);
314 
315   if (ob_act && (ob_act->mode & OB_MODE_ALL_PAINT) &&
316       /* with weight-paint + pose-mode, fall through to using calculateTransformCenter */
317       ((ob_act->mode & OB_MODE_WEIGHT_PAINT) && BKE_object_pose_armature_get(ob_act)) == 0) {
318     /* in case of sculpting use last average stroke position as a rotation
319      * center, in other cases it's not clear what rotation center shall be
320      * so just rotate around object origin
321      */
322     if (ob_act->mode &
323         (OB_MODE_SCULPT | OB_MODE_TEXTURE_PAINT | OB_MODE_VERTEX_PAINT | OB_MODE_WEIGHT_PAINT)) {
324       float stroke[3];
325       BKE_paint_stroke_get_average(scene, ob_act_eval, stroke);
326       copy_v3_v3(lastofs, stroke);
327     }
328     else {
329       copy_v3_v3(lastofs, ob_act_eval->obmat[3]);
330     }
331     is_set = true;
332   }
333   else if (ob_act && (ob_act->mode & OB_MODE_EDIT) && (ob_act->type == OB_FONT)) {
334     Curve *cu = ob_act_eval->data;
335     EditFont *ef = cu->editfont;
336 
337     zero_v3(lastofs);
338     for (int i = 0; i < 4; i++) {
339       add_v2_v2(lastofs, ef->textcurs[i]);
340     }
341     mul_v2_fl(lastofs, 1.0f / 4.0f);
342 
343     mul_m4_v3(ob_act_eval->obmat, lastofs);
344 
345     is_set = true;
346   }
347   else if (ob_act == NULL || ob_act->mode == OB_MODE_OBJECT) {
348     /* object mode use boundbox centers */
349     Base *base_eval;
350     uint tot = 0;
351     float select_center[3];
352 
353     zero_v3(select_center);
354     for (base_eval = FIRSTBASE(view_layer_eval); base_eval; base_eval = base_eval->next) {
355       if (BASE_SELECTED(v3d, base_eval)) {
356         /* use the boundbox if we can */
357         Object *ob_eval = base_eval->object;
358 
359         if (ob_eval->runtime.bb && !(ob_eval->runtime.bb->flag & BOUNDBOX_DIRTY)) {
360           float cent[3];
361 
362           BKE_boundbox_calc_center_aabb(ob_eval->runtime.bb, cent);
363 
364           mul_m4_v3(ob_eval->obmat, cent);
365           add_v3_v3(select_center, cent);
366         }
367         else {
368           add_v3_v3(select_center, ob_eval->obmat[3]);
369         }
370         tot++;
371       }
372     }
373     if (tot) {
374       mul_v3_fl(select_center, 1.0f / (float)tot);
375       copy_v3_v3(lastofs, select_center);
376       is_set = true;
377     }
378   }
379   else {
380     /* If there's no selection, lastofs is unmodified and last value since static */
381     is_set = calculateTransformCenter(C, V3D_AROUND_CENTER_MEDIAN, lastofs, NULL);
382   }
383 
384   copy_v3_v3(r_dyn_ofs, lastofs);
385 
386   return is_set;
387 }
388 
389 enum eViewOpsFlag {
390   /** When enabled, rotate around the selection. */
391   VIEWOPS_FLAG_ORBIT_SELECT = (1 << 0),
392   /** When enabled, use the depth under the cursor for navigation. */
393   VIEWOPS_FLAG_DEPTH_NAVIGATE = (1 << 1),
394   /**
395    * When enabled run #ED_view3d_persp_ensure this may switch out of
396    * camera view when orbiting or switch from ortho to perspective when auto-persp is enabled.
397    * Some operations don't require this (view zoom/pan or ndof where subtle rotation is common
398    * so we don't want it to trigger auto-perspective). */
399   VIEWOPS_FLAG_PERSP_ENSURE = (1 << 2),
400   /** When set, ignore any options that depend on initial cursor location. */
401   VIEWOPS_FLAG_USE_MOUSE_INIT = (1 << 3),
402 };
403 
viewops_flag_from_args(bool use_select,bool use_depth)404 static enum eViewOpsFlag viewops_flag_from_args(bool use_select, bool use_depth)
405 {
406   enum eViewOpsFlag flag = 0;
407   if (use_select) {
408     flag |= VIEWOPS_FLAG_ORBIT_SELECT;
409   }
410   if (use_depth) {
411     flag |= VIEWOPS_FLAG_DEPTH_NAVIGATE;
412   }
413 
414   return flag;
415 }
416 
viewops_flag_from_prefs(void)417 static enum eViewOpsFlag viewops_flag_from_prefs(void)
418 {
419   return viewops_flag_from_args((U.uiflag & USER_ORBIT_SELECTION) != 0,
420                                 (U.uiflag & USER_DEPTH_NAVIGATE) != 0);
421 }
422 
423 /**
424  * Calculate the values for #ViewOpsData
425  */
viewops_data_create(bContext * C,wmOperator * op,const wmEvent * event,enum eViewOpsFlag viewops_flag)426 static void viewops_data_create(bContext *C,
427                                 wmOperator *op,
428                                 const wmEvent *event,
429                                 enum eViewOpsFlag viewops_flag)
430 {
431   Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
432   ViewOpsData *vod = op->customdata;
433   RegionView3D *rv3d = vod->rv3d;
434 
435   /* Could do this more nicely. */
436   if ((viewops_flag & VIEWOPS_FLAG_USE_MOUSE_INIT) == 0) {
437     viewops_flag &= ~VIEWOPS_FLAG_DEPTH_NAVIGATE;
438   }
439 
440   /* we need the depth info before changing any viewport options */
441   if (viewops_flag & VIEWOPS_FLAG_DEPTH_NAVIGATE) {
442     float fallback_depth_pt[3];
443 
444     view3d_operator_needs_opengl(C); /* needed for zbuf drawing */
445 
446     negate_v3_v3(fallback_depth_pt, rv3d->ofs);
447 
448     vod->use_dyn_ofs = ED_view3d_autodist(
449         depsgraph, vod->region, vod->v3d, event->mval, vod->dyn_ofs, true, fallback_depth_pt);
450   }
451   else {
452     vod->use_dyn_ofs = false;
453   }
454 
455   if (viewops_flag & VIEWOPS_FLAG_PERSP_ENSURE) {
456     if (ED_view3d_persp_ensure(depsgraph, vod->v3d, vod->region)) {
457       /* If we're switching from camera view to the perspective one,
458        * need to tag viewport update, so camera view and borders are properly updated. */
459       ED_region_tag_redraw(vod->region);
460     }
461   }
462 
463   /* set the view from the camera, if view locking is enabled.
464    * we may want to make this optional but for now its needed always */
465   ED_view3d_camera_lock_init(depsgraph, vod->v3d, vod->rv3d);
466 
467   vod->init.persp = rv3d->persp;
468   vod->init.dist = rv3d->dist;
469   vod->init.camzoom = rv3d->camzoom;
470   copy_qt_qt(vod->init.quat, rv3d->viewquat);
471   vod->init.event_xy[0] = vod->prev.event_xy[0] = event->x;
472   vod->init.event_xy[1] = vod->prev.event_xy[1] = event->y;
473 
474   if (viewops_flag & VIEWOPS_FLAG_USE_MOUSE_INIT) {
475     vod->init.event_xy_offset[0] = 0;
476     vod->init.event_xy_offset[1] = 0;
477   }
478   else {
479     /* Simulate the event starting in the middle of the region. */
480     vod->init.event_xy_offset[0] = BLI_rcti_cent_x(&vod->region->winrct) - event->x;
481     vod->init.event_xy_offset[1] = BLI_rcti_cent_y(&vod->region->winrct) - event->y;
482   }
483 
484   vod->init.event_type = event->type;
485   copy_v3_v3(vod->init.ofs, rv3d->ofs);
486 
487   copy_qt_qt(vod->curr.viewquat, rv3d->viewquat);
488 
489   if (viewops_flag & VIEWOPS_FLAG_ORBIT_SELECT) {
490     float ofs[3];
491     if (view3d_orbit_calc_center(C, ofs) || (vod->use_dyn_ofs == false)) {
492       vod->use_dyn_ofs = true;
493       negate_v3_v3(vod->dyn_ofs, ofs);
494       viewops_flag &= ~VIEWOPS_FLAG_DEPTH_NAVIGATE;
495     }
496   }
497 
498   if (viewops_flag & VIEWOPS_FLAG_DEPTH_NAVIGATE) {
499     if (vod->use_dyn_ofs) {
500       if (rv3d->is_persp) {
501         float my_origin[3]; /* original G.vd->ofs */
502         float my_pivot[3];  /* view */
503         float dvec[3];
504 
505         /* locals for dist correction */
506         float mat[3][3];
507         float upvec[3];
508 
509         negate_v3_v3(my_origin, rv3d->ofs); /* ofs is flipped */
510 
511         /* Set the dist value to be the distance from this 3d point this means you'll
512          * always be able to zoom into it and panning wont go bad when dist was zero. */
513 
514         /* remove dist value */
515         upvec[0] = upvec[1] = 0;
516         upvec[2] = rv3d->dist;
517         copy_m3_m4(mat, rv3d->viewinv);
518 
519         mul_m3_v3(mat, upvec);
520         sub_v3_v3v3(my_pivot, rv3d->ofs, upvec);
521         negate_v3(my_pivot); /* ofs is flipped */
522 
523         /* find a new ofs value that is along the view axis
524          * (rather than the mouse location) */
525         closest_to_line_v3(dvec, vod->dyn_ofs, my_pivot, my_origin);
526         vod->init.dist = rv3d->dist = len_v3v3(my_pivot, dvec);
527 
528         negate_v3_v3(rv3d->ofs, dvec);
529       }
530       else {
531         const float mval_region_mid[2] = {(float)vod->region->winx / 2.0f,
532                                           (float)vod->region->winy / 2.0f};
533 
534         ED_view3d_win_to_3d(vod->v3d, vod->region, vod->dyn_ofs, mval_region_mid, rv3d->ofs);
535         negate_v3(rv3d->ofs);
536       }
537       negate_v3(vod->dyn_ofs);
538       copy_v3_v3(vod->init.ofs, rv3d->ofs);
539     }
540   }
541 
542   /* For dolly */
543   ED_view3d_win_to_vector(vod->region, (const float[2]){UNPACK2(event->mval)}, vod->init.mousevec);
544 
545   {
546     const int event_xy_offset[2] = {
547         event->x + vod->init.event_xy_offset[0],
548         event->y + vod->init.event_xy_offset[1],
549     };
550     /* For rotation with trackball rotation. */
551     calctrackballvec(&vod->region->winrct, event_xy_offset, vod->init.trackvec);
552   }
553 
554   {
555     float tvec[3];
556     negate_v3_v3(tvec, rv3d->ofs);
557     vod->init.zfac = ED_view3d_calc_zfac(rv3d, tvec, NULL);
558   }
559 
560   vod->reverse = 1.0f;
561   if (rv3d->persmat[2][1] < 0.0f) {
562     vod->reverse = -1.0f;
563   }
564 
565   rv3d->rflag |= RV3D_NAVIGATING;
566 }
567 
viewops_data_free(bContext * C,wmOperator * op)568 static void viewops_data_free(bContext *C, wmOperator *op)
569 {
570   ARegion *region;
571   if (op->customdata) {
572     ViewOpsData *vod = op->customdata;
573     region = vod->region;
574     vod->rv3d->rflag &= ~RV3D_NAVIGATING;
575 
576     if (vod->timer) {
577       WM_event_remove_timer(CTX_wm_manager(C), vod->timer->win, vod->timer);
578     }
579 
580     MEM_freeN(vod);
581     op->customdata = NULL;
582   }
583   else {
584     region = CTX_wm_region(C);
585   }
586 
587   /* Need to redraw because drawing code uses RV3D_NAVIGATING to draw
588    * faster while navigation operator runs. */
589   ED_region_tag_redraw(region);
590 }
591 
592 /** \} */
593 
594 /* -------------------------------------------------------------------- */
595 /** \name View Rotate Operator
596  * \{ */
597 
598 enum {
599   VIEW_PASS = 0,
600   VIEW_APPLY,
601   VIEW_CONFIRM,
602 };
603 
604 /* NOTE: these defines are saved in keymap files, do not change values but just add new ones */
605 enum {
606   VIEW_MODAL_CONFIRM = 1, /* used for all view operations */
607   VIEWROT_MODAL_AXIS_SNAP_ENABLE = 2,
608   VIEWROT_MODAL_AXIS_SNAP_DISABLE = 3,
609   VIEWROT_MODAL_SWITCH_ZOOM = 4,
610   VIEWROT_MODAL_SWITCH_MOVE = 5,
611   VIEWROT_MODAL_SWITCH_ROTATE = 6,
612 };
613 
614 /* called in transform_ops.c, on each regeneration of keymaps  */
viewrotate_modal_keymap(wmKeyConfig * keyconf)615 void viewrotate_modal_keymap(wmKeyConfig *keyconf)
616 {
617   static const EnumPropertyItem modal_items[] = {
618       {VIEW_MODAL_CONFIRM, "CONFIRM", 0, "Confirm", ""},
619 
620       {VIEWROT_MODAL_AXIS_SNAP_ENABLE, "AXIS_SNAP_ENABLE", 0, "Axis Snap", ""},
621       {VIEWROT_MODAL_AXIS_SNAP_DISABLE, "AXIS_SNAP_DISABLE", 0, "Axis Snap (Off)", ""},
622 
623       {VIEWROT_MODAL_SWITCH_ZOOM, "SWITCH_TO_ZOOM", 0, "Switch to Zoom"},
624       {VIEWROT_MODAL_SWITCH_MOVE, "SWITCH_TO_MOVE", 0, "Switch to Move"},
625 
626       {0, NULL, 0, NULL, NULL},
627   };
628 
629   wmKeyMap *keymap = WM_modalkeymap_find(keyconf, "View3D Rotate Modal");
630 
631   /* this function is called for each spacetype, only needs to add map once */
632   if (keymap && keymap->modal_items) {
633     return;
634   }
635 
636   keymap = WM_modalkeymap_ensure(keyconf, "View3D Rotate Modal", modal_items);
637 
638   /* disabled mode switching for now, can re-implement better, later on */
639 #if 0
640   WM_modalkeymap_add_item(keymap, LEFTMOUSE, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ZOOM);
641   WM_modalkeymap_add_item(keymap, LEFTCTRLKEY, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ZOOM);
642   WM_modalkeymap_add_item(keymap, LEFTSHIFTKEY, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_MOVE);
643 #endif
644 
645   /* assign map to operators */
646   WM_modalkeymap_assign(keymap, "VIEW3D_OT_rotate");
647 }
648 
viewrotate_apply_dyn_ofs(ViewOpsData * vod,const float viewquat_new[4])649 static void viewrotate_apply_dyn_ofs(ViewOpsData *vod, const float viewquat_new[4])
650 {
651   if (vod->use_dyn_ofs) {
652     RegionView3D *rv3d = vod->rv3d;
653     view3d_orbit_apply_dyn_ofs(
654         rv3d->ofs, vod->init.ofs, vod->init.quat, viewquat_new, vod->dyn_ofs);
655   }
656 }
657 
viewrotate_apply_snap(ViewOpsData * vod)658 static void viewrotate_apply_snap(ViewOpsData *vod)
659 {
660   const float axis_limit = DEG2RADF(45 / 3);
661 
662   RegionView3D *rv3d = vod->rv3d;
663 
664   float viewquat_inv[4];
665   float zaxis[3] = {0, 0, 1};
666   float zaxis_best[3];
667   int x, y, z;
668   bool found = false;
669   bool is_axis_aligned = false;
670 
671   invert_qt_qt_normalized(viewquat_inv, vod->curr.viewquat);
672 
673   mul_qt_v3(viewquat_inv, zaxis);
674   normalize_v3(zaxis);
675 
676   for (x = -1; x < 2; x++) {
677     for (y = -1; y < 2; y++) {
678       for (z = -1; z < 2; z++) {
679         if (x || y || z) {
680           float zaxis_test[3] = {x, y, z};
681 
682           normalize_v3(zaxis_test);
683 
684           if (angle_normalized_v3v3(zaxis_test, zaxis) < axis_limit) {
685             copy_v3_v3(zaxis_best, zaxis_test);
686             found = true;
687 
688             if (abs(x) + abs(y) + abs(z) == 1) {
689               is_axis_aligned = true;
690             }
691           }
692         }
693       }
694     }
695   }
696 
697   if (found) {
698 
699     /* find the best roll */
700     float quat_roll[4], quat_final[4], quat_best[4], quat_snap[4];
701     float viewquat_align[4];     /* viewquat aligned to zaxis_best */
702     float viewquat_align_inv[4]; /* viewquat aligned to zaxis_best */
703     float best_angle = axis_limit;
704     int j;
705 
706     /* viewquat_align is the original viewquat aligned to the snapped axis
707      * for testing roll */
708     rotation_between_vecs_to_quat(viewquat_align, zaxis_best, zaxis);
709     normalize_qt(viewquat_align);
710     mul_qt_qtqt(viewquat_align, vod->curr.viewquat, viewquat_align);
711     normalize_qt(viewquat_align);
712     invert_qt_qt_normalized(viewquat_align_inv, viewquat_align);
713 
714     vec_to_quat(quat_snap, zaxis_best, OB_NEGZ, OB_POSY);
715     normalize_qt(quat_snap);
716     invert_qt_normalized(quat_snap);
717 
718     /* check if we can find the roll */
719     found = false;
720 
721     /* find best roll */
722     for (j = 0; j < 8; j++) {
723       float angle;
724       float xaxis1[3] = {1, 0, 0};
725       float xaxis2[3] = {1, 0, 0};
726       float quat_final_inv[4];
727 
728       axis_angle_to_quat(quat_roll, zaxis_best, (float)j * DEG2RADF(45.0f));
729       normalize_qt(quat_roll);
730 
731       mul_qt_qtqt(quat_final, quat_snap, quat_roll);
732       normalize_qt(quat_final);
733 
734       /* compare 2 vector angles to find the least roll */
735       invert_qt_qt_normalized(quat_final_inv, quat_final);
736       mul_qt_v3(viewquat_align_inv, xaxis1);
737       mul_qt_v3(quat_final_inv, xaxis2);
738       angle = angle_v3v3(xaxis1, xaxis2);
739 
740       if (angle <= best_angle) {
741         found = true;
742         best_angle = angle;
743         copy_qt_qt(quat_best, quat_final);
744       }
745     }
746 
747     if (found) {
748       /* lock 'quat_best' to an axis view if we can */
749       ED_view3d_quat_to_axis_view(quat_best, 0.01f, &rv3d->view, &rv3d->view_axis_roll);
750       if (rv3d->view != RV3D_VIEW_USER) {
751         ED_view3d_quat_from_axis_view(rv3d->view, rv3d->view_axis_roll, quat_best);
752       }
753     }
754     else {
755       copy_qt_qt(quat_best, viewquat_align);
756     }
757 
758     copy_qt_qt(rv3d->viewquat, quat_best);
759 
760     viewrotate_apply_dyn_ofs(vod, rv3d->viewquat);
761 
762     if (U.uiflag & USER_AUTOPERSP) {
763       if (is_axis_aligned) {
764         if (rv3d->persp == RV3D_PERSP) {
765           rv3d->persp = RV3D_ORTHO;
766         }
767       }
768     }
769   }
770   else if (U.uiflag & USER_AUTOPERSP) {
771     rv3d->persp = vod->init.persp;
772   }
773 }
774 
viewrotate_apply(ViewOpsData * vod,const int event_xy[2])775 static void viewrotate_apply(ViewOpsData *vod, const int event_xy[2])
776 {
777   RegionView3D *rv3d = vod->rv3d;
778 
779   rv3d->view = RV3D_VIEW_USER; /* need to reset every time because of view snapping */
780 
781   if (U.flag & USER_TRACKBALL) {
782     float axis[3], q1[4], dvec[3], newvec[3];
783     float angle;
784 
785     {
786       const int event_xy_offset[2] = {
787           event_xy[0] + vod->init.event_xy_offset[0],
788           event_xy[1] + vod->init.event_xy_offset[1],
789       };
790       calctrackballvec(&vod->region->winrct, event_xy_offset, newvec);
791     }
792 
793     sub_v3_v3v3(dvec, newvec, vod->init.trackvec);
794 
795     angle = (len_v3(dvec) / (2.0f * TRACKBALLSIZE)) * (float)M_PI;
796 
797     /* Before applying the sensitivity this is rotating 1:1,
798      * where the cursor would match the surface of a sphere in the view. */
799     angle *= U.view_rotate_sensitivity_trackball;
800 
801     /* Allow for rotation beyond the interval [-pi, pi] */
802     angle = angle_wrap_rad(angle);
803 
804     /* This relation is used instead of the actual angle between vectors
805      * so that the angle of rotation is linearly proportional to
806      * the distance that the mouse is dragged. */
807 
808     cross_v3_v3v3(axis, vod->init.trackvec, newvec);
809     axis_angle_to_quat(q1, axis, angle);
810 
811     mul_qt_qtqt(vod->curr.viewquat, q1, vod->init.quat);
812 
813     viewrotate_apply_dyn_ofs(vod, vod->curr.viewquat);
814   }
815   else {
816     /* New turntable view code by John Aughey */
817     float quat_local_x[4], quat_global_z[4];
818     float m[3][3];
819     float m_inv[3][3];
820     const float zvec_global[3] = {0.0f, 0.0f, 1.0f};
821     float xaxis[3];
822 
823     /* Radians per-pixel. */
824     const float sensitivity = U.view_rotate_sensitivity_turntable / U.dpi_fac;
825 
826     /* Get the 3x3 matrix and its inverse from the quaternion */
827     quat_to_mat3(m, vod->curr.viewquat);
828     invert_m3_m3(m_inv, m);
829 
830     /* Avoid Gimble Lock
831      *
832      * Even though turn-table mode is in use, this can occur when the user exits the camera view
833      * or when aligning the view to a rotated object.
834      *
835      * We have gimble lock when the user's view is rotated +/- 90 degrees along the view axis.
836      * In this case the vertical rotation is the same as the sideways turntable motion.
837      * Making it impossible to get out of the gimble locked state without resetting the view.
838      *
839      * The logic below lets the user exit out of this state without any abrupt 'fix'
840      * which would be disorienting.
841      *
842      * This works by blending two horizons:
843      * - Rotated-horizon: `cross_v3_v3v3(xaxis, zvec_global, m_inv[2])`
844      *   When only this is used, this turntable rotation works - but it's side-ways
845      *   (as if the entire turn-table has been placed on its side)
846      *   While there is no gimble lock, it's also awkward to use.
847      * - Un-rotated-horizon: `m_inv[0]`
848      *   When only this is used, the turntable rotation can have gimbal lock.
849      *
850      * The solution used here is to blend between these two values,
851      * so the severity of the gimbal lock is used to blend the rotated horizon.
852      * Blending isn't essential, it just makes the transition smoother.
853      *
854      * This allows sideways turn-table rotation on a Z axis that isn't world-space Z,
855      * While up-down turntable rotation eventually corrects gimble lock. */
856 #if 1
857     if (len_squared_v3v3(zvec_global, m_inv[2]) > 0.001f) {
858       float fac;
859       cross_v3_v3v3(xaxis, zvec_global, m_inv[2]);
860       if (dot_v3v3(xaxis, m_inv[0]) < 0) {
861         negate_v3(xaxis);
862       }
863       fac = angle_normalized_v3v3(zvec_global, m_inv[2]) / (float)M_PI;
864       fac = fabsf(fac - 0.5f) * 2;
865       fac = fac * fac;
866       interp_v3_v3v3(xaxis, xaxis, m_inv[0], fac);
867     }
868     else {
869       copy_v3_v3(xaxis, m_inv[0]);
870     }
871 #else
872     copy_v3_v3(xaxis, m_inv[0]);
873 #endif
874 
875     /* Determine the direction of the x vector (for rotating up and down) */
876     /* This can likely be computed directly from the quaternion. */
877 
878     /* Perform the up/down rotation */
879     axis_angle_to_quat(quat_local_x, xaxis, sensitivity * -(event_xy[1] - vod->prev.event_xy[1]));
880     mul_qt_qtqt(quat_local_x, vod->curr.viewquat, quat_local_x);
881 
882     /* Perform the orbital rotation */
883     axis_angle_to_quat_single(
884         quat_global_z, 'Z', sensitivity * vod->reverse * (event_xy[0] - vod->prev.event_xy[0]));
885     mul_qt_qtqt(vod->curr.viewquat, quat_local_x, quat_global_z);
886 
887     viewrotate_apply_dyn_ofs(vod, vod->curr.viewquat);
888   }
889 
890   /* avoid precision loss over time */
891   normalize_qt(vod->curr.viewquat);
892 
893   /* use a working copy so view rotation locking doesn't overwrite the locked
894    * rotation back into the view we calculate with */
895   copy_qt_qt(rv3d->viewquat, vod->curr.viewquat);
896 
897   /* check for view snap,
898    * note: don't apply snap to vod->viewquat so the view wont jam up */
899   if (vod->axis_snap) {
900     viewrotate_apply_snap(vod);
901   }
902   vod->prev.event_xy[0] = event_xy[0];
903   vod->prev.event_xy[1] = event_xy[1];
904 
905   ED_view3d_camera_lock_sync(vod->depsgraph, vod->v3d, rv3d);
906 
907   ED_region_tag_redraw(vod->region);
908 }
909 
viewrotate_modal(bContext * C,wmOperator * op,const wmEvent * event)910 static int viewrotate_modal(bContext *C, wmOperator *op, const wmEvent *event)
911 {
912   ViewOpsData *vod = op->customdata;
913   short event_code = VIEW_PASS;
914   bool use_autokey = false;
915   int ret = OPERATOR_RUNNING_MODAL;
916 
917   /* execute the events */
918   if (event->type == MOUSEMOVE) {
919     event_code = VIEW_APPLY;
920   }
921   else if (event->type == EVT_MODAL_MAP) {
922     switch (event->val) {
923       case VIEW_MODAL_CONFIRM:
924         event_code = VIEW_CONFIRM;
925         break;
926       case VIEWROT_MODAL_AXIS_SNAP_ENABLE:
927         vod->axis_snap = true;
928         event_code = VIEW_APPLY;
929         break;
930       case VIEWROT_MODAL_AXIS_SNAP_DISABLE:
931         vod->rv3d->persp = vod->init.persp;
932         vod->axis_snap = false;
933         event_code = VIEW_APPLY;
934         break;
935       case VIEWROT_MODAL_SWITCH_ZOOM:
936         WM_operator_name_call(C, "VIEW3D_OT_zoom", WM_OP_INVOKE_DEFAULT, NULL);
937         event_code = VIEW_CONFIRM;
938         break;
939       case VIEWROT_MODAL_SWITCH_MOVE:
940         WM_operator_name_call(C, "VIEW3D_OT_move", WM_OP_INVOKE_DEFAULT, NULL);
941         event_code = VIEW_CONFIRM;
942         break;
943     }
944   }
945   else if (event->type == vod->init.event_type && event->val == KM_RELEASE) {
946     event_code = VIEW_CONFIRM;
947   }
948 
949   if (event_code == VIEW_APPLY) {
950     viewrotate_apply(vod, &event->x);
951     if (ED_screen_animation_playing(CTX_wm_manager(C))) {
952       use_autokey = true;
953     }
954   }
955   else if (event_code == VIEW_CONFIRM) {
956     ED_view3d_depth_tag_update(vod->rv3d);
957     use_autokey = true;
958     ret = OPERATOR_FINISHED;
959   }
960 
961   if (use_autokey) {
962     ED_view3d_camera_lock_autokey(vod->v3d, vod->rv3d, C, true, true);
963   }
964 
965   if (ret & OPERATOR_FINISHED) {
966     viewops_data_free(C, op);
967   }
968 
969   return ret;
970 }
971 
viewrotate_invoke(bContext * C,wmOperator * op,const wmEvent * event)972 static int viewrotate_invoke(bContext *C, wmOperator *op, const wmEvent *event)
973 {
974   ViewOpsData *vod;
975 
976   const bool use_cursor_init = RNA_boolean_get(op->ptr, "use_cursor_init");
977 
978   /* makes op->customdata */
979   viewops_data_alloc(C, op);
980   vod = op->customdata;
981 
982   /* poll should check but in some cases fails, see poll func for details */
983   if (RV3D_LOCK_FLAGS(vod->rv3d) & RV3D_LOCK_ROTATION) {
984     viewops_data_free(C, op);
985     return OPERATOR_PASS_THROUGH;
986   }
987 
988   ED_view3d_smooth_view_force_finish(C, vod->v3d, vod->region);
989 
990   viewops_data_create(C,
991                       op,
992                       event,
993                       viewops_flag_from_prefs() | VIEWOPS_FLAG_PERSP_ENSURE |
994                           (use_cursor_init ? VIEWOPS_FLAG_USE_MOUSE_INIT : 0));
995 
996   if (ELEM(event->type, MOUSEPAN, MOUSEROTATE)) {
997     /* Rotate direction we keep always same */
998     int event_xy[2];
999 
1000     if (event->type == MOUSEPAN) {
1001       if (U.uiflag2 & USER_TRACKPAD_NATURAL) {
1002         event_xy[0] = 2 * event->x - event->prevx;
1003         event_xy[1] = 2 * event->y - event->prevy;
1004       }
1005       else {
1006         event_xy[0] = event->prevx;
1007         event_xy[1] = event->prevy;
1008       }
1009     }
1010     else {
1011       /* MOUSEROTATE performs orbital rotation, so y axis delta is set to 0 */
1012       event_xy[0] = event->prevx;
1013       event_xy[1] = event->y;
1014     }
1015 
1016     viewrotate_apply(vod, event_xy);
1017     ED_view3d_depth_tag_update(vod->rv3d);
1018 
1019     viewops_data_free(C, op);
1020 
1021     return OPERATOR_FINISHED;
1022   }
1023 
1024   /* add temp handler */
1025   WM_event_add_modal_handler(C, op);
1026 
1027   return OPERATOR_RUNNING_MODAL;
1028 }
1029 
viewrotate_cancel(bContext * C,wmOperator * op)1030 static void viewrotate_cancel(bContext *C, wmOperator *op)
1031 {
1032   viewops_data_free(C, op);
1033 }
1034 
VIEW3D_OT_rotate(wmOperatorType * ot)1035 void VIEW3D_OT_rotate(wmOperatorType *ot)
1036 {
1037   /* identifiers */
1038   ot->name = "Rotate View";
1039   ot->description = "Rotate the view";
1040   ot->idname = "VIEW3D_OT_rotate";
1041 
1042   /* api callbacks */
1043   ot->invoke = viewrotate_invoke;
1044   ot->modal = viewrotate_modal;
1045   ot->poll = ED_operator_region_view3d_active;
1046   ot->cancel = viewrotate_cancel;
1047 
1048   /* flags */
1049   ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_XY;
1050 
1051   view3d_operator_properties_common(ot, V3D_OP_PROP_USE_MOUSE_INIT);
1052 }
1053 
1054 /** \} */
1055 
1056 /* -------------------------------------------------------------------- */
1057 /** \name NDOF Utility Functions
1058  * \{ */
1059 
1060 #ifdef WITH_INPUT_NDOF
ndof_has_translate(const wmNDOFMotionData * ndof,const View3D * v3d,const RegionView3D * rv3d)1061 static bool ndof_has_translate(const wmNDOFMotionData *ndof,
1062                                const View3D *v3d,
1063                                const RegionView3D *rv3d)
1064 {
1065   return !is_zero_v3(ndof->tvec) && (!ED_view3d_offset_lock_check(v3d, rv3d));
1066 }
1067 
ndof_has_rotate(const wmNDOFMotionData * ndof,const RegionView3D * rv3d)1068 static bool ndof_has_rotate(const wmNDOFMotionData *ndof, const RegionView3D *rv3d)
1069 {
1070   return !is_zero_v3(ndof->rvec) && ((RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ROTATION) == 0);
1071 }
1072 
1073 /**
1074  * \param depth_pt: A point to calculate the depth (in perspective mode)
1075  */
view3d_ndof_pan_speed_calc_ex(RegionView3D * rv3d,const float depth_pt[3])1076 static float view3d_ndof_pan_speed_calc_ex(RegionView3D *rv3d, const float depth_pt[3])
1077 {
1078   float speed = rv3d->pixsize * NDOF_PIXELS_PER_SECOND;
1079 
1080   if (rv3d->is_persp) {
1081     speed *= ED_view3d_calc_zfac(rv3d, depth_pt, NULL);
1082   }
1083 
1084   return speed;
1085 }
1086 
view3d_ndof_pan_speed_calc_from_dist(RegionView3D * rv3d,const float dist)1087 static float view3d_ndof_pan_speed_calc_from_dist(RegionView3D *rv3d, const float dist)
1088 {
1089   float viewinv[4];
1090   float tvec[3];
1091 
1092   BLI_assert(dist >= 0.0f);
1093 
1094   copy_v3_fl3(tvec, 0.0f, 0.0f, dist);
1095   /* rv3d->viewinv isn't always valid */
1096 #  if 0
1097   mul_mat3_m4_v3(rv3d->viewinv, tvec);
1098 #  else
1099   invert_qt_qt_normalized(viewinv, rv3d->viewquat);
1100   mul_qt_v3(viewinv, tvec);
1101 #  endif
1102 
1103   return view3d_ndof_pan_speed_calc_ex(rv3d, tvec);
1104 }
1105 
view3d_ndof_pan_speed_calc(RegionView3D * rv3d)1106 static float view3d_ndof_pan_speed_calc(RegionView3D *rv3d)
1107 {
1108   float tvec[3];
1109   negate_v3_v3(tvec, rv3d->ofs);
1110 
1111   return view3d_ndof_pan_speed_calc_ex(rv3d, tvec);
1112 }
1113 
1114 /**
1115  * Zoom and pan in the same function since sometimes zoom is interpreted as dolly (pan forward).
1116  *
1117  * \param has_zoom: zoom, otherwise dolly,
1118  * often `!rv3d->is_persp` since it doesn't make sense to dolly in ortho.
1119  */
view3d_ndof_pan_zoom(const struct wmNDOFMotionData * ndof,ScrArea * area,ARegion * region,const bool has_translate,const bool has_zoom)1120 static void view3d_ndof_pan_zoom(const struct wmNDOFMotionData *ndof,
1121                                  ScrArea *area,
1122                                  ARegion *region,
1123                                  const bool has_translate,
1124                                  const bool has_zoom)
1125 {
1126   RegionView3D *rv3d = region->regiondata;
1127   float view_inv[4];
1128   float pan_vec[3];
1129 
1130   if (has_translate == false && has_zoom == false) {
1131     return;
1132   }
1133 
1134   WM_event_ndof_pan_get(ndof, pan_vec, false);
1135 
1136   if (has_zoom) {
1137     /* zoom with Z */
1138 
1139     /* Zoom!
1140      * velocity should be proportional to the linear velocity attained by rotational motion
1141      * of same strength [got that?] proportional to `arclength = radius * angle`.
1142      */
1143 
1144     pan_vec[2] = 0.0f;
1145 
1146     /* "zoom in" or "translate"? depends on zoom mode in user settings? */
1147     if (ndof->tvec[2]) {
1148       float zoom_distance = rv3d->dist * ndof->dt * ndof->tvec[2];
1149 
1150       if (U.ndof_flag & NDOF_ZOOM_INVERT) {
1151         zoom_distance = -zoom_distance;
1152       }
1153 
1154       rv3d->dist += zoom_distance;
1155     }
1156   }
1157   else {
1158     /* dolly with Z */
1159 
1160     /* all callers must check */
1161     if (has_translate) {
1162       BLI_assert(ED_view3d_offset_lock_check((View3D *)area->spacedata.first, rv3d) == false);
1163     }
1164   }
1165 
1166   if (has_translate) {
1167     const float speed = view3d_ndof_pan_speed_calc(rv3d);
1168 
1169     mul_v3_fl(pan_vec, speed * ndof->dt);
1170 
1171     /* transform motion from view to world coordinates */
1172     invert_qt_qt_normalized(view_inv, rv3d->viewquat);
1173     mul_qt_v3(view_inv, pan_vec);
1174 
1175     /* move center of view opposite of hand motion (this is camera mode, not object mode) */
1176     sub_v3_v3(rv3d->ofs, pan_vec);
1177 
1178     if (RV3D_LOCK_FLAGS(rv3d) & RV3D_BOXVIEW) {
1179       view3d_boxview_sync(area, region);
1180     }
1181   }
1182 }
1183 
view3d_ndof_orbit(const struct wmNDOFMotionData * ndof,ScrArea * area,ARegion * region,ViewOpsData * vod,const bool apply_dyn_ofs)1184 static void view3d_ndof_orbit(const struct wmNDOFMotionData *ndof,
1185                               ScrArea *area,
1186                               ARegion *region,
1187                               ViewOpsData *vod,
1188                               const bool apply_dyn_ofs)
1189 {
1190   View3D *v3d = area->spacedata.first;
1191   RegionView3D *rv3d = region->regiondata;
1192 
1193   float view_inv[4];
1194 
1195   BLI_assert((RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ROTATION) == 0);
1196 
1197   ED_view3d_persp_ensure(vod->depsgraph, v3d, region);
1198 
1199   rv3d->view = RV3D_VIEW_USER;
1200 
1201   invert_qt_qt_normalized(view_inv, rv3d->viewquat);
1202 
1203   if (U.ndof_flag & NDOF_TURNTABLE) {
1204     float rot[3];
1205 
1206     /* turntable view code by John Aughey, adapted for 3D mouse by [mce] */
1207     float angle, quat[4];
1208     float xvec[3] = {1, 0, 0};
1209 
1210     /* only use XY, ignore Z */
1211     WM_event_ndof_rotate_get(ndof, rot);
1212 
1213     /* Determine the direction of the x vector (for rotating up and down) */
1214     mul_qt_v3(view_inv, xvec);
1215 
1216     /* Perform the up/down rotation */
1217     angle = ndof->dt * rot[0];
1218     axis_angle_to_quat(quat, xvec, angle);
1219     mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, quat);
1220 
1221     /* Perform the orbital rotation */
1222     angle = ndof->dt * rot[1];
1223 
1224     /* update the onscreen doo-dad */
1225     rv3d->rot_angle = angle;
1226     rv3d->rot_axis[0] = 0;
1227     rv3d->rot_axis[1] = 0;
1228     rv3d->rot_axis[2] = 1;
1229 
1230     axis_angle_to_quat_single(quat, 'Z', angle);
1231     mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, quat);
1232   }
1233   else {
1234     float quat[4];
1235     float axis[3];
1236     float angle = WM_event_ndof_to_axis_angle(ndof, axis);
1237 
1238     /* transform rotation axis from view to world coordinates */
1239     mul_qt_v3(view_inv, axis);
1240 
1241     /* update the onscreen doo-dad */
1242     rv3d->rot_angle = angle;
1243     copy_v3_v3(rv3d->rot_axis, axis);
1244 
1245     axis_angle_to_quat(quat, axis, angle);
1246 
1247     /* apply rotation */
1248     mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, quat);
1249   }
1250 
1251   if (apply_dyn_ofs) {
1252     viewrotate_apply_dyn_ofs(vod, rv3d->viewquat);
1253   }
1254 }
1255 
1256 /**
1257  * Called from both fly mode and walk mode,
1258  */
view3d_ndof_fly(const wmNDOFMotionData * ndof,View3D * v3d,RegionView3D * rv3d,const bool use_precision,const short protectflag,bool * r_has_translate,bool * r_has_rotate)1259 void view3d_ndof_fly(const wmNDOFMotionData *ndof,
1260                      View3D *v3d,
1261                      RegionView3D *rv3d,
1262                      const bool use_precision,
1263                      const short protectflag,
1264                      bool *r_has_translate,
1265                      bool *r_has_rotate)
1266 {
1267   bool has_translate = ndof_has_translate(ndof, v3d, rv3d);
1268   bool has_rotate = ndof_has_rotate(ndof, rv3d);
1269 
1270   float view_inv[4];
1271   invert_qt_qt_normalized(view_inv, rv3d->viewquat);
1272 
1273   rv3d->rot_angle = 0.0f; /* disable onscreen rotation doo-dad */
1274 
1275   if (has_translate) {
1276     /* ignore real 'dist' since fly has its own speed settings,
1277      * also its overwritten at this point. */
1278     float speed = view3d_ndof_pan_speed_calc_from_dist(rv3d, 1.0f);
1279     float trans[3], trans_orig_y;
1280 
1281     if (use_precision) {
1282       speed *= 0.2f;
1283     }
1284 
1285     WM_event_ndof_pan_get(ndof, trans, false);
1286     mul_v3_fl(trans, speed * ndof->dt);
1287     trans_orig_y = trans[1];
1288 
1289     if (U.ndof_flag & NDOF_FLY_HELICOPTER) {
1290       trans[1] = 0.0f;
1291     }
1292 
1293     /* transform motion from view to world coordinates */
1294     mul_qt_v3(view_inv, trans);
1295 
1296     if (U.ndof_flag & NDOF_FLY_HELICOPTER) {
1297       /* replace world z component with device y (yes it makes sense) */
1298       trans[2] = trans_orig_y;
1299     }
1300 
1301     if (rv3d->persp == RV3D_CAMOB) {
1302       /* respect camera position locks */
1303       if (protectflag & OB_LOCK_LOCX) {
1304         trans[0] = 0.0f;
1305       }
1306       if (protectflag & OB_LOCK_LOCY) {
1307         trans[1] = 0.0f;
1308       }
1309       if (protectflag & OB_LOCK_LOCZ) {
1310         trans[2] = 0.0f;
1311       }
1312     }
1313 
1314     if (!is_zero_v3(trans)) {
1315       /* move center of view opposite of hand motion
1316        * (this is camera mode, not object mode) */
1317       sub_v3_v3(rv3d->ofs, trans);
1318       has_translate = true;
1319     }
1320     else {
1321       has_translate = false;
1322     }
1323   }
1324 
1325   if (has_rotate) {
1326     const float turn_sensitivity = 1.0f;
1327 
1328     float rotation[4];
1329     float axis[3];
1330     float angle = turn_sensitivity * WM_event_ndof_to_axis_angle(ndof, axis);
1331 
1332     if (fabsf(angle) > 0.0001f) {
1333       has_rotate = true;
1334 
1335       if (use_precision) {
1336         angle *= 0.2f;
1337       }
1338 
1339       /* transform rotation axis from view to world coordinates */
1340       mul_qt_v3(view_inv, axis);
1341 
1342       /* apply rotation to view */
1343       axis_angle_to_quat(rotation, axis, angle);
1344       mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, rotation);
1345 
1346       if (U.ndof_flag & NDOF_LOCK_HORIZON) {
1347         /* force an upright viewpoint
1348          * TODO: make this less... sudden */
1349         float view_horizon[3] = {1.0f, 0.0f, 0.0f};    /* view +x */
1350         float view_direction[3] = {0.0f, 0.0f, -1.0f}; /* view -z (into screen) */
1351 
1352         /* find new inverse since viewquat has changed */
1353         invert_qt_qt_normalized(view_inv, rv3d->viewquat);
1354         /* could apply reverse rotation to existing view_inv to save a few cycles */
1355 
1356         /* transform view vectors to world coordinates */
1357         mul_qt_v3(view_inv, view_horizon);
1358         mul_qt_v3(view_inv, view_direction);
1359 
1360         /* find difference between view & world horizons
1361          * true horizon lives in world xy plane, so look only at difference in z */
1362         angle = -asinf(view_horizon[2]);
1363 
1364         /* rotate view so view horizon = world horizon */
1365         axis_angle_to_quat(rotation, view_direction, angle);
1366         mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, rotation);
1367       }
1368 
1369       rv3d->view = RV3D_VIEW_USER;
1370     }
1371     else {
1372       has_rotate = false;
1373     }
1374   }
1375 
1376   *r_has_translate = has_translate;
1377   *r_has_rotate = has_rotate;
1378 }
1379 
1380 /** \} */
1381 
1382 /* -------------------------------------------------------------------- */
1383 /** \name NDOF Orbit/Translate Operator
1384  * \{ */
1385 
ndof_orbit_invoke(bContext * C,wmOperator * op,const wmEvent * event)1386 static int ndof_orbit_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1387 {
1388   if (event->type != NDOF_MOTION) {
1389     return OPERATOR_CANCELLED;
1390   }
1391 
1392   const Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
1393   ViewOpsData *vod;
1394   View3D *v3d;
1395   RegionView3D *rv3d;
1396   char xform_flag = 0;
1397 
1398   const wmNDOFMotionData *ndof = event->customdata;
1399 
1400   viewops_data_alloc(C, op);
1401   viewops_data_create(
1402       C, op, event, viewops_flag_from_args((U.uiflag & USER_ORBIT_SELECTION) != 0, false));
1403   vod = op->customdata;
1404 
1405   ED_view3d_smooth_view_force_finish(C, vod->v3d, vod->region);
1406 
1407   v3d = vod->v3d;
1408   rv3d = vod->rv3d;
1409 
1410   /* off by default, until changed later this function */
1411   rv3d->rot_angle = 0.0f;
1412 
1413   ED_view3d_camera_lock_init_ex(depsgraph, v3d, rv3d, false);
1414 
1415   if (ndof->progress != P_FINISHING) {
1416     const bool has_rotation = ndof_has_rotate(ndof, rv3d);
1417     /* if we can't rotate, fallback to translate (locked axis views) */
1418     const bool has_translate = ndof_has_translate(ndof, v3d, rv3d) &&
1419                                (RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ROTATION);
1420     const bool has_zoom = (ndof->tvec[2] != 0.0f) && !rv3d->is_persp;
1421 
1422     if (has_translate || has_zoom) {
1423       view3d_ndof_pan_zoom(ndof, vod->area, vod->region, has_translate, has_zoom);
1424       xform_flag |= HAS_TRANSLATE;
1425     }
1426 
1427     if (has_rotation) {
1428       view3d_ndof_orbit(ndof, vod->area, vod->region, vod, true);
1429       xform_flag |= HAS_ROTATE;
1430     }
1431   }
1432 
1433   ED_view3d_camera_lock_sync(depsgraph, v3d, rv3d);
1434   if (xform_flag) {
1435     ED_view3d_camera_lock_autokey(
1436         v3d, rv3d, C, xform_flag & HAS_ROTATE, xform_flag & HAS_TRANSLATE);
1437   }
1438 
1439   ED_region_tag_redraw(vod->region);
1440 
1441   viewops_data_free(C, op);
1442 
1443   return OPERATOR_FINISHED;
1444 }
1445 
VIEW3D_OT_ndof_orbit(struct wmOperatorType * ot)1446 void VIEW3D_OT_ndof_orbit(struct wmOperatorType *ot)
1447 {
1448   /* identifiers */
1449   ot->name = "NDOF Orbit View";
1450   ot->description = "Orbit the view using the 3D mouse";
1451   ot->idname = "VIEW3D_OT_ndof_orbit";
1452 
1453   /* api callbacks */
1454   ot->invoke = ndof_orbit_invoke;
1455   ot->poll = ED_operator_view3d_active;
1456 
1457   /* flags */
1458   ot->flag = 0;
1459 }
1460 
1461 /** \} */
1462 
1463 /* -------------------------------------------------------------------- */
1464 /** \name NDOF Orbit/Zoom Operator
1465  * \{ */
1466 
ndof_orbit_zoom_invoke(bContext * C,wmOperator * op,const wmEvent * event)1467 static int ndof_orbit_zoom_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1468 {
1469   if (event->type != NDOF_MOTION) {
1470     return OPERATOR_CANCELLED;
1471   }
1472 
1473   const Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
1474   ViewOpsData *vod;
1475   View3D *v3d;
1476   RegionView3D *rv3d;
1477   char xform_flag = 0;
1478 
1479   const wmNDOFMotionData *ndof = event->customdata;
1480 
1481   viewops_data_alloc(C, op);
1482   viewops_data_create(
1483       C, op, event, viewops_flag_from_args((U.uiflag & USER_ORBIT_SELECTION) != 0, false));
1484 
1485   vod = op->customdata;
1486 
1487   ED_view3d_smooth_view_force_finish(C, vod->v3d, vod->region);
1488 
1489   v3d = vod->v3d;
1490   rv3d = vod->rv3d;
1491 
1492   /* off by default, until changed later this function */
1493   rv3d->rot_angle = 0.0f;
1494 
1495   ED_view3d_camera_lock_init_ex(depsgraph, v3d, rv3d, false);
1496 
1497   if (ndof->progress == P_FINISHING) {
1498     /* pass */
1499   }
1500   else if ((rv3d->persp == RV3D_ORTHO) && RV3D_VIEW_IS_AXIS(rv3d->view)) {
1501     /* if we can't rotate, fallback to translate (locked axis views) */
1502     const bool has_translate = ndof_has_translate(ndof, v3d, rv3d);
1503     const bool has_zoom = (ndof->tvec[2] != 0.0f) && ED_view3d_offset_lock_check(v3d, rv3d);
1504 
1505     if (has_translate || has_zoom) {
1506       view3d_ndof_pan_zoom(ndof, vod->area, vod->region, has_translate, true);
1507       xform_flag |= HAS_TRANSLATE;
1508     }
1509   }
1510   else {
1511     /* Note: based on feedback from T67579, users want to have pan and orbit enabled at once.
1512      * It's arguable that orbit shouldn't pan (since we have a pan only operator),
1513      * so if there are users who like to separate orbit/pan operations - it can be a preference. */
1514     const bool is_orbit_around_pivot = (U.ndof_flag & NDOF_MODE_ORBIT) ||
1515                                        ED_view3d_offset_lock_check(v3d, rv3d);
1516     const bool has_rotation = ndof_has_rotate(ndof, rv3d);
1517     bool has_translate, has_zoom;
1518 
1519     if (is_orbit_around_pivot) {
1520       /* Orbit preference or forced lock (Z zooms). */
1521       has_translate = !is_zero_v2(ndof->tvec) && ndof_has_translate(ndof, v3d, rv3d);
1522       has_zoom = (ndof->tvec[2] != 0.0f);
1523     }
1524     else {
1525       /* Free preference (Z translates). */
1526       has_translate = ndof_has_translate(ndof, v3d, rv3d);
1527       has_zoom = false;
1528     }
1529 
1530     /* Rotation first because dynamic offset resets offset otherwise (and disables panning). */
1531     if (has_rotation) {
1532       const float dist_backup = rv3d->dist;
1533       if (!is_orbit_around_pivot) {
1534         ED_view3d_distance_set(rv3d, 0.0f);
1535       }
1536       view3d_ndof_orbit(ndof, vod->area, vod->region, vod, is_orbit_around_pivot);
1537       xform_flag |= HAS_ROTATE;
1538       if (!is_orbit_around_pivot) {
1539         ED_view3d_distance_set(rv3d, dist_backup);
1540       }
1541     }
1542 
1543     if (has_translate || has_zoom) {
1544       view3d_ndof_pan_zoom(ndof, vod->area, vod->region, has_translate, has_zoom);
1545       xform_flag |= HAS_TRANSLATE;
1546     }
1547   }
1548 
1549   ED_view3d_camera_lock_sync(depsgraph, v3d, rv3d);
1550   if (xform_flag) {
1551     ED_view3d_camera_lock_autokey(
1552         v3d, rv3d, C, xform_flag & HAS_ROTATE, xform_flag & HAS_TRANSLATE);
1553   }
1554 
1555   ED_region_tag_redraw(vod->region);
1556 
1557   viewops_data_free(C, op);
1558 
1559   return OPERATOR_FINISHED;
1560 }
1561 
VIEW3D_OT_ndof_orbit_zoom(struct wmOperatorType * ot)1562 void VIEW3D_OT_ndof_orbit_zoom(struct wmOperatorType *ot)
1563 {
1564   /* identifiers */
1565   ot->name = "NDOF Orbit View with Zoom";
1566   ot->description = "Orbit and zoom the view using the 3D mouse";
1567   ot->idname = "VIEW3D_OT_ndof_orbit_zoom";
1568 
1569   /* api callbacks */
1570   ot->invoke = ndof_orbit_zoom_invoke;
1571   ot->poll = ED_operator_view3d_active;
1572 
1573   /* flags */
1574   ot->flag = 0;
1575 }
1576 
1577 /** \} */
1578 
1579 /* -------------------------------------------------------------------- */
1580 /** \name NDOF Pan/Zoom Operator
1581  * \{ */
1582 
ndof_pan_invoke(bContext * C,wmOperator * UNUSED (op),const wmEvent * event)1583 static int ndof_pan_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
1584 {
1585   if (event->type != NDOF_MOTION) {
1586     return OPERATOR_CANCELLED;
1587   }
1588 
1589   const Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
1590   View3D *v3d = CTX_wm_view3d(C);
1591   RegionView3D *rv3d = CTX_wm_region_view3d(C);
1592   const wmNDOFMotionData *ndof = event->customdata;
1593   char xform_flag = 0;
1594 
1595   const bool has_translate = ndof_has_translate(ndof, v3d, rv3d);
1596   const bool has_zoom = (ndof->tvec[2] != 0.0f) && !rv3d->is_persp;
1597 
1598   /* we're panning here! so erase any leftover rotation from other operators */
1599   rv3d->rot_angle = 0.0f;
1600 
1601   if (!(has_translate || has_zoom)) {
1602     return OPERATOR_CANCELLED;
1603   }
1604 
1605   ED_view3d_camera_lock_init_ex(depsgraph, v3d, rv3d, false);
1606 
1607   if (ndof->progress != P_FINISHING) {
1608     ScrArea *area = CTX_wm_area(C);
1609     ARegion *region = CTX_wm_region(C);
1610 
1611     if (has_translate || has_zoom) {
1612       view3d_ndof_pan_zoom(ndof, area, region, has_translate, has_zoom);
1613       xform_flag |= HAS_TRANSLATE;
1614     }
1615   }
1616 
1617   ED_view3d_camera_lock_sync(depsgraph, v3d, rv3d);
1618   if (xform_flag) {
1619     ED_view3d_camera_lock_autokey(v3d, rv3d, C, false, xform_flag & HAS_TRANSLATE);
1620   }
1621 
1622   ED_region_tag_redraw(CTX_wm_region(C));
1623 
1624   return OPERATOR_FINISHED;
1625 }
1626 
VIEW3D_OT_ndof_pan(struct wmOperatorType * ot)1627 void VIEW3D_OT_ndof_pan(struct wmOperatorType *ot)
1628 {
1629   /* identifiers */
1630   ot->name = "NDOF Pan View";
1631   ot->description = "Pan the view with the 3D mouse";
1632   ot->idname = "VIEW3D_OT_ndof_pan";
1633 
1634   /* api callbacks */
1635   ot->invoke = ndof_pan_invoke;
1636   ot->poll = ED_operator_view3d_active;
1637 
1638   /* flags */
1639   ot->flag = 0;
1640 }
1641 
1642 /** \} */
1643 
1644 /* -------------------------------------------------------------------- */
1645 /** \name NDOF Transform All Operator
1646  * \{ */
1647 
1648 /**
1649  * wraps #ndof_orbit_zoom but never restrict to orbit.
1650  */
ndof_all_invoke(bContext * C,wmOperator * op,const wmEvent * event)1651 static int ndof_all_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1652 {
1653   /* weak!, but it works */
1654   const int ndof_flag = U.ndof_flag;
1655   int ret;
1656 
1657   U.ndof_flag &= ~NDOF_MODE_ORBIT;
1658 
1659   ret = ndof_orbit_zoom_invoke(C, op, event);
1660 
1661   U.ndof_flag = ndof_flag;
1662 
1663   return ret;
1664 }
1665 
VIEW3D_OT_ndof_all(struct wmOperatorType * ot)1666 void VIEW3D_OT_ndof_all(struct wmOperatorType *ot)
1667 {
1668   /* identifiers */
1669   ot->name = "NDOF Transform View";
1670   ot->description = "Pan and rotate the view with the 3D mouse";
1671   ot->idname = "VIEW3D_OT_ndof_all";
1672 
1673   /* api callbacks */
1674   ot->invoke = ndof_all_invoke;
1675   ot->poll = ED_operator_view3d_active;
1676 
1677   /* flags */
1678   ot->flag = 0;
1679 }
1680 
1681 #endif /* WITH_INPUT_NDOF */
1682 
1683 /** \} */
1684 
1685 /* -------------------------------------------------------------------- */
1686 /** \name View Move (Pan) Operator
1687  * \{ */
1688 
1689 /* NOTE: these defines are saved in keymap files, do not change values but just add new ones */
1690 
1691 /* called in transform_ops.c, on each regeneration of keymaps  */
viewmove_modal_keymap(wmKeyConfig * keyconf)1692 void viewmove_modal_keymap(wmKeyConfig *keyconf)
1693 {
1694   static const EnumPropertyItem modal_items[] = {
1695       {VIEW_MODAL_CONFIRM, "CONFIRM", 0, "Confirm", ""},
1696 
1697       {VIEWROT_MODAL_SWITCH_ZOOM, "SWITCH_TO_ZOOM", 0, "Switch to Zoom"},
1698       {VIEWROT_MODAL_SWITCH_ROTATE, "SWITCH_TO_ROTATE", 0, "Switch to Rotate"},
1699 
1700       {0, NULL, 0, NULL, NULL},
1701   };
1702 
1703   wmKeyMap *keymap = WM_modalkeymap_find(keyconf, "View3D Move Modal");
1704 
1705   /* this function is called for each spacetype, only needs to add map once */
1706   if (keymap && keymap->modal_items) {
1707     return;
1708   }
1709 
1710   keymap = WM_modalkeymap_ensure(keyconf, "View3D Move Modal", modal_items);
1711 
1712   /* items for modal map */
1713   WM_modalkeymap_add_item(keymap, MIDDLEMOUSE, KM_RELEASE, KM_ANY, 0, VIEW_MODAL_CONFIRM);
1714   WM_modalkeymap_add_item(keymap, EVT_ESCKEY, KM_PRESS, KM_ANY, 0, VIEW_MODAL_CONFIRM);
1715 
1716   /* disabled mode switching for now, can re-implement better, later on */
1717 #if 0
1718   WM_modalkeymap_add_item(keymap, LEFTMOUSE, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ZOOM);
1719   WM_modalkeymap_add_item(keymap, LEFTCTRLKEY, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ZOOM);
1720   WM_modalkeymap_add_item(
1721       keymap, LEFTSHIFTKEY, KM_RELEASE, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ROTATE);
1722 #endif
1723 
1724   /* assign map to operators */
1725   WM_modalkeymap_assign(keymap, "VIEW3D_OT_move");
1726 }
1727 
viewmove_apply(ViewOpsData * vod,int x,int y)1728 static void viewmove_apply(ViewOpsData *vod, int x, int y)
1729 {
1730   if (ED_view3d_offset_lock_check(vod->v3d, vod->rv3d)) {
1731     vod->rv3d->ofs_lock[0] -= ((vod->prev.event_xy[0] - x) * 2.0f) / (float)vod->region->winx;
1732     vod->rv3d->ofs_lock[1] -= ((vod->prev.event_xy[1] - y) * 2.0f) / (float)vod->region->winy;
1733   }
1734   else if ((vod->rv3d->persp == RV3D_CAMOB) && !ED_view3d_camera_lock_check(vod->v3d, vod->rv3d)) {
1735     const float zoomfac = BKE_screen_view3d_zoom_to_fac(vod->rv3d->camzoom) * 2.0f;
1736     vod->rv3d->camdx += (vod->prev.event_xy[0] - x) / (vod->region->winx * zoomfac);
1737     vod->rv3d->camdy += (vod->prev.event_xy[1] - y) / (vod->region->winy * zoomfac);
1738     CLAMP(vod->rv3d->camdx, -1.0f, 1.0f);
1739     CLAMP(vod->rv3d->camdy, -1.0f, 1.0f);
1740   }
1741   else {
1742     float dvec[3];
1743     float mval_f[2];
1744 
1745     mval_f[0] = x - vod->prev.event_xy[0];
1746     mval_f[1] = y - vod->prev.event_xy[1];
1747     ED_view3d_win_to_delta(vod->region, mval_f, dvec, vod->init.zfac);
1748 
1749     add_v3_v3(vod->rv3d->ofs, dvec);
1750 
1751     if (RV3D_LOCK_FLAGS(vod->rv3d) & RV3D_BOXVIEW) {
1752       view3d_boxview_sync(vod->area, vod->region);
1753     }
1754   }
1755 
1756   vod->prev.event_xy[0] = x;
1757   vod->prev.event_xy[1] = y;
1758 
1759   ED_view3d_camera_lock_sync(vod->depsgraph, vod->v3d, vod->rv3d);
1760 
1761   ED_region_tag_redraw(vod->region);
1762 }
1763 
viewmove_modal(bContext * C,wmOperator * op,const wmEvent * event)1764 static int viewmove_modal(bContext *C, wmOperator *op, const wmEvent *event)
1765 {
1766 
1767   ViewOpsData *vod = op->customdata;
1768   short event_code = VIEW_PASS;
1769   bool use_autokey = false;
1770   int ret = OPERATOR_RUNNING_MODAL;
1771 
1772   /* execute the events */
1773   if (event->type == MOUSEMOVE) {
1774     event_code = VIEW_APPLY;
1775   }
1776   else if (event->type == EVT_MODAL_MAP) {
1777     switch (event->val) {
1778       case VIEW_MODAL_CONFIRM:
1779         event_code = VIEW_CONFIRM;
1780         break;
1781       case VIEWROT_MODAL_SWITCH_ZOOM:
1782         WM_operator_name_call(C, "VIEW3D_OT_zoom", WM_OP_INVOKE_DEFAULT, NULL);
1783         event_code = VIEW_CONFIRM;
1784         break;
1785       case VIEWROT_MODAL_SWITCH_ROTATE:
1786         WM_operator_name_call(C, "VIEW3D_OT_rotate", WM_OP_INVOKE_DEFAULT, NULL);
1787         event_code = VIEW_CONFIRM;
1788         break;
1789     }
1790   }
1791   else if (event->type == vod->init.event_type && event->val == KM_RELEASE) {
1792     event_code = VIEW_CONFIRM;
1793   }
1794 
1795   if (event_code == VIEW_APPLY) {
1796     viewmove_apply(vod, event->x, event->y);
1797     if (ED_screen_animation_playing(CTX_wm_manager(C))) {
1798       use_autokey = true;
1799     }
1800   }
1801   else if (event_code == VIEW_CONFIRM) {
1802     ED_view3d_depth_tag_update(vod->rv3d);
1803     use_autokey = true;
1804     ret = OPERATOR_FINISHED;
1805   }
1806 
1807   if (use_autokey) {
1808     ED_view3d_camera_lock_autokey(vod->v3d, vod->rv3d, C, false, true);
1809   }
1810 
1811   if (ret & OPERATOR_FINISHED) {
1812     viewops_data_free(C, op);
1813   }
1814 
1815   return ret;
1816 }
1817 
viewmove_invoke(bContext * C,wmOperator * op,const wmEvent * event)1818 static int viewmove_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1819 {
1820   ViewOpsData *vod;
1821 
1822   const bool use_cursor_init = RNA_boolean_get(op->ptr, "use_cursor_init");
1823 
1824   /* makes op->customdata */
1825   viewops_data_alloc(C, op);
1826   vod = op->customdata;
1827   if (RV3D_LOCK_FLAGS(vod->rv3d) & RV3D_LOCK_LOCATION) {
1828     viewops_data_free(C, op);
1829     return OPERATOR_PASS_THROUGH;
1830   }
1831 
1832   viewops_data_create(C,
1833                       op,
1834                       event,
1835                       (viewops_flag_from_prefs() & ~VIEWOPS_FLAG_ORBIT_SELECT) |
1836                           (use_cursor_init ? VIEWOPS_FLAG_USE_MOUSE_INIT : 0));
1837 
1838   ED_view3d_smooth_view_force_finish(C, vod->v3d, vod->region);
1839 
1840   if (event->type == MOUSEPAN) {
1841     /* invert it, trackpad scroll follows same principle as 2d windows this way */
1842     viewmove_apply(vod, 2 * event->x - event->prevx, 2 * event->y - event->prevy);
1843     ED_view3d_depth_tag_update(vod->rv3d);
1844 
1845     viewops_data_free(C, op);
1846 
1847     return OPERATOR_FINISHED;
1848   }
1849 
1850   /* add temp handler */
1851   WM_event_add_modal_handler(C, op);
1852 
1853   return OPERATOR_RUNNING_MODAL;
1854 }
1855 
viewmove_cancel(bContext * C,wmOperator * op)1856 static void viewmove_cancel(bContext *C, wmOperator *op)
1857 {
1858   viewops_data_free(C, op);
1859 }
1860 
VIEW3D_OT_move(wmOperatorType * ot)1861 void VIEW3D_OT_move(wmOperatorType *ot)
1862 {
1863 
1864   /* identifiers */
1865   ot->name = "Pan View";
1866   ot->description = "Move the view";
1867   ot->idname = "VIEW3D_OT_move";
1868 
1869   /* api callbacks */
1870   ot->invoke = viewmove_invoke;
1871   ot->modal = viewmove_modal;
1872   ot->poll = ED_operator_region_view3d_active;
1873   ot->cancel = viewmove_cancel;
1874 
1875   /* flags */
1876   ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_XY;
1877 
1878   /* properties */
1879   view3d_operator_properties_common(ot, V3D_OP_PROP_USE_MOUSE_INIT);
1880 }
1881 
1882 /** \} */
1883 
1884 /* -------------------------------------------------------------------- */
1885 /** \name View Zoom Operator
1886  * \{ */
1887 
1888 /* viewdolly_modal_keymap has an exact copy of this, apply fixes to both */
1889 /* called in transform_ops.c, on each regeneration of keymaps  */
viewzoom_modal_keymap(wmKeyConfig * keyconf)1890 void viewzoom_modal_keymap(wmKeyConfig *keyconf)
1891 {
1892   static const EnumPropertyItem modal_items[] = {
1893       {VIEW_MODAL_CONFIRM, "CONFIRM", 0, "Confirm", ""},
1894 
1895       {VIEWROT_MODAL_SWITCH_ROTATE, "SWITCH_TO_ROTATE", 0, "Switch to Rotate"},
1896       {VIEWROT_MODAL_SWITCH_MOVE, "SWITCH_TO_MOVE", 0, "Switch to Move"},
1897 
1898       {0, NULL, 0, NULL, NULL},
1899   };
1900 
1901   wmKeyMap *keymap = WM_modalkeymap_find(keyconf, "View3D Zoom Modal");
1902 
1903   /* this function is called for each spacetype, only needs to add map once */
1904   if (keymap && keymap->modal_items) {
1905     return;
1906   }
1907 
1908   keymap = WM_modalkeymap_ensure(keyconf, "View3D Zoom Modal", modal_items);
1909 
1910   /* disabled mode switching for now, can re-implement better, later on */
1911 #if 0
1912   WM_modalkeymap_add_item(keymap, LEFTMOUSE, KM_RELEASE, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ROTATE);
1913   WM_modalkeymap_add_item(keymap, LEFTCTRLKEY, KM_RELEASE, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ROTATE);
1914   WM_modalkeymap_add_item(keymap, LEFTSHIFTKEY, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_MOVE);
1915 #endif
1916 
1917   /* assign map to operators */
1918   WM_modalkeymap_assign(keymap, "VIEW3D_OT_zoom");
1919 }
1920 
1921 /**
1922  * \param zoom_xy: Optionally zoom to window location
1923  * (coords compatible w/ #wmEvent.x, y). Use when not NULL.
1924  */
view_zoom_to_window_xy_camera(Scene * scene,Depsgraph * depsgraph,View3D * v3d,ARegion * region,float dfac,const int zoom_xy[2])1925 static void view_zoom_to_window_xy_camera(Scene *scene,
1926                                           Depsgraph *depsgraph,
1927                                           View3D *v3d,
1928                                           ARegion *region,
1929                                           float dfac,
1930                                           const int zoom_xy[2])
1931 {
1932   RegionView3D *rv3d = region->regiondata;
1933   const float zoomfac = BKE_screen_view3d_zoom_to_fac(rv3d->camzoom);
1934   const float zoomfac_new = clamp_f(
1935       zoomfac * (1.0f / dfac), RV3D_CAMZOOM_MIN_FACTOR, RV3D_CAMZOOM_MAX_FACTOR);
1936   const float camzoom_new = BKE_screen_view3d_zoom_from_fac(zoomfac_new);
1937 
1938   if (zoom_xy != NULL) {
1939     float zoomfac_px;
1940     rctf camera_frame_old;
1941     rctf camera_frame_new;
1942 
1943     const float pt_src[2] = {zoom_xy[0], zoom_xy[1]};
1944     float pt_dst[2];
1945     float delta_px[2];
1946 
1947     ED_view3d_calc_camera_border(scene, depsgraph, region, v3d, rv3d, &camera_frame_old, false);
1948     BLI_rctf_translate(&camera_frame_old, region->winrct.xmin, region->winrct.ymin);
1949 
1950     rv3d->camzoom = camzoom_new;
1951     CLAMP(rv3d->camzoom, RV3D_CAMZOOM_MIN, RV3D_CAMZOOM_MAX);
1952 
1953     ED_view3d_calc_camera_border(scene, depsgraph, region, v3d, rv3d, &camera_frame_new, false);
1954     BLI_rctf_translate(&camera_frame_new, region->winrct.xmin, region->winrct.ymin);
1955 
1956     BLI_rctf_transform_pt_v(&camera_frame_new, &camera_frame_old, pt_dst, pt_src);
1957     sub_v2_v2v2(delta_px, pt_dst, pt_src);
1958 
1959     /* translate the camera offset using pixel space delta
1960      * mapped back to the camera (same logic as panning in camera view) */
1961     zoomfac_px = BKE_screen_view3d_zoom_to_fac(rv3d->camzoom) * 2.0f;
1962 
1963     rv3d->camdx += delta_px[0] / (region->winx * zoomfac_px);
1964     rv3d->camdy += delta_px[1] / (region->winy * zoomfac_px);
1965     CLAMP(rv3d->camdx, -1.0f, 1.0f);
1966     CLAMP(rv3d->camdy, -1.0f, 1.0f);
1967   }
1968   else {
1969     rv3d->camzoom = camzoom_new;
1970     CLAMP(rv3d->camzoom, RV3D_CAMZOOM_MIN, RV3D_CAMZOOM_MAX);
1971   }
1972 }
1973 
1974 /**
1975  * \param zoom_xy: Optionally zoom to window location
1976  * (coords compatible w/ #wmEvent.x, y). Use when not NULL.
1977  */
view_zoom_to_window_xy_3d(ARegion * region,float dfac,const int zoom_xy[2])1978 static void view_zoom_to_window_xy_3d(ARegion *region, float dfac, const int zoom_xy[2])
1979 {
1980   RegionView3D *rv3d = region->regiondata;
1981   const float dist_new = rv3d->dist * dfac;
1982 
1983   if (zoom_xy != NULL) {
1984     float dvec[3];
1985     float tvec[3];
1986     float tpos[3];
1987     float mval_f[2];
1988 
1989     float zfac;
1990 
1991     negate_v3_v3(tpos, rv3d->ofs);
1992 
1993     mval_f[0] = (float)(((zoom_xy[0] - region->winrct.xmin) * 2) - region->winx) / 2.0f;
1994     mval_f[1] = (float)(((zoom_xy[1] - region->winrct.ymin) * 2) - region->winy) / 2.0f;
1995 
1996     /* Project cursor position into 3D space */
1997     zfac = ED_view3d_calc_zfac(rv3d, tpos, NULL);
1998     ED_view3d_win_to_delta(region, mval_f, dvec, zfac);
1999 
2000     /* Calculate view target position for dolly */
2001     add_v3_v3v3(tvec, tpos, dvec);
2002     negate_v3(tvec);
2003 
2004     /* Offset to target position and dolly */
2005     copy_v3_v3(rv3d->ofs, tvec);
2006     rv3d->dist = dist_new;
2007 
2008     /* Calculate final offset */
2009     madd_v3_v3v3fl(rv3d->ofs, tvec, dvec, dfac);
2010   }
2011   else {
2012     rv3d->dist = dist_new;
2013   }
2014 }
2015 
viewzoom_scale_value(const rcti * winrct,const eViewZoom_Style viewzoom,const bool zoom_invert,const bool zoom_invert_force,const int xy_curr[2],const int xy_init[2],const float val,const float val_orig,double * r_timer_lastdraw)2016 static float viewzoom_scale_value(const rcti *winrct,
2017                                   const eViewZoom_Style viewzoom,
2018                                   const bool zoom_invert,
2019                                   const bool zoom_invert_force,
2020                                   const int xy_curr[2],
2021                                   const int xy_init[2],
2022                                   const float val,
2023                                   const float val_orig,
2024                                   double *r_timer_lastdraw)
2025 {
2026   float zfac;
2027 
2028   if (viewzoom == USER_ZOOM_CONT) {
2029     double time = PIL_check_seconds_timer();
2030     float time_step = (float)(time - *r_timer_lastdraw);
2031     float fac;
2032 
2033     if (U.uiflag & USER_ZOOM_HORIZ) {
2034       fac = (float)(xy_init[0] - xy_curr[0]);
2035     }
2036     else {
2037       fac = (float)(xy_init[1] - xy_curr[1]);
2038     }
2039 
2040     fac /= U.dpi_fac;
2041 
2042     if (zoom_invert != zoom_invert_force) {
2043       fac = -fac;
2044     }
2045 
2046     /* oldstyle zoom */
2047     zfac = 1.0f + ((fac / 20.0f) * time_step);
2048     *r_timer_lastdraw = time;
2049   }
2050   else if (viewzoom == USER_ZOOM_SCALE) {
2051     /* method which zooms based on how far you move the mouse */
2052 
2053     const int ctr[2] = {
2054         BLI_rcti_cent_x(winrct),
2055         BLI_rcti_cent_y(winrct),
2056     };
2057     float len_new = (5 * U.dpi_fac) + ((float)len_v2v2_int(ctr, xy_curr) / U.dpi_fac);
2058     float len_old = (5 * U.dpi_fac) + ((float)len_v2v2_int(ctr, xy_init) / U.dpi_fac);
2059 
2060     /* intentionally ignore 'zoom_invert' for scale */
2061     if (zoom_invert_force) {
2062       SWAP(float, len_new, len_old);
2063     }
2064 
2065     zfac = val_orig * (len_old / max_ff(len_new, 1.0f)) / val;
2066   }
2067   else { /* USER_ZOOM_DOLLY */
2068     float len_new = 5 * U.dpi_fac;
2069     float len_old = 5 * U.dpi_fac;
2070 
2071     if (U.uiflag & USER_ZOOM_HORIZ) {
2072       len_new += (winrct->xmax - (xy_curr[0])) / U.dpi_fac;
2073       len_old += (winrct->xmax - (xy_init[0])) / U.dpi_fac;
2074     }
2075     else {
2076       len_new += (winrct->ymax - (xy_curr[1])) / U.dpi_fac;
2077       len_old += (winrct->ymax - (xy_init[1])) / U.dpi_fac;
2078     }
2079 
2080     if (zoom_invert != zoom_invert_force) {
2081       SWAP(float, len_new, len_old);
2082     }
2083 
2084     zfac = val_orig * (2.0f * ((len_new / max_ff(len_old, 1.0f)) - 1.0f) + 1.0f) / val;
2085   }
2086 
2087   return zfac;
2088 }
2089 
viewzoom_scale_value_offset(const rcti * winrct,const eViewZoom_Style viewzoom,const bool zoom_invert,const bool zoom_invert_force,const int xy_curr[2],const int xy_init[2],const int xy_offset[2],const float val,const float val_orig,double * r_timer_lastdraw)2090 static float viewzoom_scale_value_offset(const rcti *winrct,
2091                                          const eViewZoom_Style viewzoom,
2092                                          const bool zoom_invert,
2093                                          const bool zoom_invert_force,
2094                                          const int xy_curr[2],
2095                                          const int xy_init[2],
2096                                          const int xy_offset[2],
2097                                          const float val,
2098                                          const float val_orig,
2099                                          double *r_timer_lastdraw)
2100 {
2101   const int xy_curr_offset[2] = {
2102       xy_curr[0] + xy_offset[0],
2103       xy_curr[1] + xy_offset[1],
2104   };
2105   const int xy_init_offset[2] = {
2106       xy_init[0] + xy_offset[0],
2107       xy_init[1] + xy_offset[1],
2108   };
2109   return viewzoom_scale_value(winrct,
2110                               viewzoom,
2111                               zoom_invert,
2112                               zoom_invert_force,
2113                               xy_curr_offset,
2114                               xy_init_offset,
2115                               val,
2116                               val_orig,
2117                               r_timer_lastdraw);
2118 }
2119 
viewzoom_apply_camera(ViewOpsData * vod,const int xy[2],const eViewZoom_Style viewzoom,const bool zoom_invert,const bool zoom_to_pos)2120 static void viewzoom_apply_camera(ViewOpsData *vod,
2121                                   const int xy[2],
2122                                   const eViewZoom_Style viewzoom,
2123                                   const bool zoom_invert,
2124                                   const bool zoom_to_pos)
2125 {
2126   float zfac;
2127   float zoomfac_prev = BKE_screen_view3d_zoom_to_fac(vod->init.camzoom) * 2.0f;
2128   float zoomfac = BKE_screen_view3d_zoom_to_fac(vod->rv3d->camzoom) * 2.0f;
2129 
2130   zfac = viewzoom_scale_value_offset(&vod->region->winrct,
2131                                      viewzoom,
2132                                      zoom_invert,
2133                                      true,
2134                                      xy,
2135                                      vod->init.event_xy,
2136                                      vod->init.event_xy_offset,
2137                                      zoomfac,
2138                                      zoomfac_prev,
2139                                      &vod->prev.time);
2140 
2141   if (zfac != 1.0f && zfac != 0.0f) {
2142     /* calculate inverted, then invert again (needed because of camera zoom scaling) */
2143     zfac = 1.0f / zfac;
2144     view_zoom_to_window_xy_camera(vod->scene,
2145                                   vod->depsgraph,
2146                                   vod->v3d,
2147                                   vod->region,
2148                                   zfac,
2149                                   zoom_to_pos ? vod->prev.event_xy : NULL);
2150   }
2151 
2152   ED_region_tag_redraw(vod->region);
2153 }
2154 
viewzoom_apply_3d(ViewOpsData * vod,const int xy[2],const eViewZoom_Style viewzoom,const bool zoom_invert,const bool zoom_to_pos)2155 static void viewzoom_apply_3d(ViewOpsData *vod,
2156                               const int xy[2],
2157                               const eViewZoom_Style viewzoom,
2158                               const bool zoom_invert,
2159                               const bool zoom_to_pos)
2160 {
2161   float zfac;
2162   float dist_range[2];
2163 
2164   ED_view3d_dist_range_get(vod->v3d, dist_range);
2165 
2166   zfac = viewzoom_scale_value_offset(&vod->region->winrct,
2167                                      viewzoom,
2168                                      zoom_invert,
2169                                      false,
2170                                      xy,
2171                                      vod->init.event_xy,
2172                                      vod->init.event_xy_offset,
2173                                      vod->rv3d->dist,
2174                                      vod->init.dist,
2175                                      &vod->prev.time);
2176 
2177   if (zfac != 1.0f) {
2178     const float zfac_min = dist_range[0] / vod->rv3d->dist;
2179     const float zfac_max = dist_range[1] / vod->rv3d->dist;
2180     CLAMP(zfac, zfac_min, zfac_max);
2181 
2182     view_zoom_to_window_xy_3d(vod->region, zfac, zoom_to_pos ? vod->prev.event_xy : NULL);
2183   }
2184 
2185   /* these limits were in old code too */
2186   CLAMP(vod->rv3d->dist, dist_range[0], dist_range[1]);
2187 
2188   if (RV3D_LOCK_FLAGS(vod->rv3d) & RV3D_BOXVIEW) {
2189     view3d_boxview_sync(vod->area, vod->region);
2190   }
2191 
2192   ED_view3d_camera_lock_sync(vod->depsgraph, vod->v3d, vod->rv3d);
2193 
2194   ED_region_tag_redraw(vod->region);
2195 }
2196 
viewzoom_apply(ViewOpsData * vod,const int xy[2],const eViewZoom_Style viewzoom,const bool zoom_invert,const bool zoom_to_pos)2197 static void viewzoom_apply(ViewOpsData *vod,
2198                            const int xy[2],
2199                            const eViewZoom_Style viewzoom,
2200                            const bool zoom_invert,
2201                            const bool zoom_to_pos)
2202 {
2203   if ((vod->rv3d->persp == RV3D_CAMOB) &&
2204       (vod->rv3d->is_persp && ED_view3d_camera_lock_check(vod->v3d, vod->rv3d)) == 0) {
2205     viewzoom_apply_camera(vod, xy, viewzoom, zoom_invert, zoom_to_pos);
2206   }
2207   else {
2208     viewzoom_apply_3d(vod, xy, viewzoom, zoom_invert, zoom_to_pos);
2209   }
2210 }
2211 
viewzoom_modal(bContext * C,wmOperator * op,const wmEvent * event)2212 static int viewzoom_modal(bContext *C, wmOperator *op, const wmEvent *event)
2213 {
2214   ViewOpsData *vod = op->customdata;
2215   short event_code = VIEW_PASS;
2216   bool use_autokey = false;
2217   int ret = OPERATOR_RUNNING_MODAL;
2218 
2219   /* execute the events */
2220   if (event->type == TIMER && event->customdata == vod->timer) {
2221     /* continuous zoom */
2222     event_code = VIEW_APPLY;
2223   }
2224   else if (event->type == MOUSEMOVE) {
2225     event_code = VIEW_APPLY;
2226   }
2227   else if (event->type == EVT_MODAL_MAP) {
2228     switch (event->val) {
2229       case VIEW_MODAL_CONFIRM:
2230         event_code = VIEW_CONFIRM;
2231         break;
2232       case VIEWROT_MODAL_SWITCH_MOVE:
2233         WM_operator_name_call(C, "VIEW3D_OT_move", WM_OP_INVOKE_DEFAULT, NULL);
2234         event_code = VIEW_CONFIRM;
2235         break;
2236       case VIEWROT_MODAL_SWITCH_ROTATE:
2237         WM_operator_name_call(C, "VIEW3D_OT_rotate", WM_OP_INVOKE_DEFAULT, NULL);
2238         event_code = VIEW_CONFIRM;
2239         break;
2240     }
2241   }
2242   else if (event->type == vod->init.event_type && event->val == KM_RELEASE) {
2243     event_code = VIEW_CONFIRM;
2244   }
2245 
2246   if (event_code == VIEW_APPLY) {
2247     const bool use_cursor_init = RNA_boolean_get(op->ptr, "use_cursor_init");
2248     viewzoom_apply(vod,
2249                    &event->x,
2250                    (eViewZoom_Style)U.viewzoom,
2251                    (U.uiflag & USER_ZOOM_INVERT) != 0,
2252                    (use_cursor_init && (U.uiflag & USER_ZOOM_TO_MOUSEPOS)));
2253     if (ED_screen_animation_playing(CTX_wm_manager(C))) {
2254       use_autokey = true;
2255     }
2256   }
2257   else if (event_code == VIEW_CONFIRM) {
2258     ED_view3d_depth_tag_update(vod->rv3d);
2259     use_autokey = true;
2260     ret = OPERATOR_FINISHED;
2261   }
2262 
2263   if (use_autokey) {
2264     ED_view3d_camera_lock_autokey(vod->v3d, vod->rv3d, C, false, true);
2265   }
2266 
2267   if (ret & OPERATOR_FINISHED) {
2268     viewops_data_free(C, op);
2269   }
2270 
2271   return ret;
2272 }
2273 
viewzoom_exec(bContext * C,wmOperator * op)2274 static int viewzoom_exec(bContext *C, wmOperator *op)
2275 {
2276   Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
2277   Scene *scene = CTX_data_scene(C);
2278   View3D *v3d;
2279   RegionView3D *rv3d;
2280   ScrArea *area;
2281   ARegion *region;
2282   bool use_cam_zoom;
2283   float dist_range[2];
2284 
2285   const int delta = RNA_int_get(op->ptr, "delta");
2286   const bool use_cursor_init = RNA_boolean_get(op->ptr, "use_cursor_init");
2287 
2288   if (op->customdata) {
2289     ViewOpsData *vod = op->customdata;
2290 
2291     area = vod->area;
2292     region = vod->region;
2293   }
2294   else {
2295     area = CTX_wm_area(C);
2296     region = CTX_wm_region(C);
2297   }
2298 
2299   v3d = area->spacedata.first;
2300   rv3d = region->regiondata;
2301 
2302   use_cam_zoom = (rv3d->persp == RV3D_CAMOB) &&
2303                  !(rv3d->is_persp && ED_view3d_camera_lock_check(v3d, rv3d));
2304 
2305   int zoom_xy_buf[2];
2306   const int *zoom_xy = NULL;
2307   if (use_cursor_init && (U.uiflag & USER_ZOOM_TO_MOUSEPOS)) {
2308     zoom_xy_buf[0] = RNA_struct_property_is_set(op->ptr, "mx") ? RNA_int_get(op->ptr, "mx") :
2309                                                                  region->winx / 2;
2310     zoom_xy_buf[1] = RNA_struct_property_is_set(op->ptr, "my") ? RNA_int_get(op->ptr, "my") :
2311                                                                  region->winy / 2;
2312     zoom_xy = zoom_xy_buf;
2313   }
2314 
2315   ED_view3d_dist_range_get(v3d, dist_range);
2316 
2317   if (delta < 0) {
2318     const float step = 1.2f;
2319     /* this min and max is also in viewmove() */
2320     if (use_cam_zoom) {
2321       view_zoom_to_window_xy_camera(scene, depsgraph, v3d, region, step, zoom_xy);
2322     }
2323     else {
2324       if (rv3d->dist < dist_range[1]) {
2325         view_zoom_to_window_xy_3d(region, step, zoom_xy);
2326       }
2327     }
2328   }
2329   else {
2330     const float step = 1.0f / 1.2f;
2331     if (use_cam_zoom) {
2332       view_zoom_to_window_xy_camera(scene, depsgraph, v3d, region, step, zoom_xy);
2333     }
2334     else {
2335       if (rv3d->dist > dist_range[0]) {
2336         view_zoom_to_window_xy_3d(region, step, zoom_xy);
2337       }
2338     }
2339   }
2340 
2341   if (RV3D_LOCK_FLAGS(rv3d) & RV3D_BOXVIEW) {
2342     view3d_boxview_sync(area, region);
2343   }
2344 
2345   ED_view3d_depth_tag_update(rv3d);
2346 
2347   ED_view3d_camera_lock_sync(depsgraph, v3d, rv3d);
2348   ED_view3d_camera_lock_autokey(v3d, rv3d, C, false, true);
2349 
2350   ED_region_tag_redraw(region);
2351 
2352   viewops_data_free(C, op);
2353 
2354   return OPERATOR_FINISHED;
2355 }
2356 
2357 /* viewdolly_invoke() copied this function, changes here may apply there */
viewzoom_invoke(bContext * C,wmOperator * op,const wmEvent * event)2358 static int viewzoom_invoke(bContext *C, wmOperator *op, const wmEvent *event)
2359 {
2360   ViewOpsData *vod;
2361 
2362   const bool use_cursor_init = RNA_boolean_get(op->ptr, "use_cursor_init");
2363 
2364   /* makes op->customdata */
2365   viewops_data_alloc(C, op);
2366   viewops_data_create(C,
2367                       op,
2368                       event,
2369                       (viewops_flag_from_prefs() & ~VIEWOPS_FLAG_ORBIT_SELECT) |
2370                           (use_cursor_init ? VIEWOPS_FLAG_USE_MOUSE_INIT : 0));
2371   vod = op->customdata;
2372 
2373   ED_view3d_smooth_view_force_finish(C, vod->v3d, vod->region);
2374 
2375   /* if one or the other zoom position aren't set, set from event */
2376   if (!RNA_struct_property_is_set(op->ptr, "mx") || !RNA_struct_property_is_set(op->ptr, "my")) {
2377     RNA_int_set(op->ptr, "mx", event->x);
2378     RNA_int_set(op->ptr, "my", event->y);
2379   }
2380 
2381   if (RNA_struct_property_is_set(op->ptr, "delta")) {
2382     viewzoom_exec(C, op);
2383   }
2384   else {
2385     if (event->type == MOUSEZOOM || event->type == MOUSEPAN) {
2386 
2387       if (U.uiflag & USER_ZOOM_HORIZ) {
2388         vod->init.event_xy[0] = vod->prev.event_xy[0] = event->x;
2389       }
2390       else {
2391         /* Set y move = x move as MOUSEZOOM uses only x axis to pass magnification value */
2392         vod->init.event_xy[1] = vod->prev.event_xy[1] = vod->init.event_xy[1] + event->x -
2393                                                         event->prevx;
2394       }
2395       viewzoom_apply(vod,
2396                      &event->prevx,
2397                      USER_ZOOM_DOLLY,
2398                      (U.uiflag & USER_ZOOM_INVERT) != 0,
2399                      (use_cursor_init && (U.uiflag & USER_ZOOM_TO_MOUSEPOS)));
2400       ED_view3d_camera_lock_autokey(vod->v3d, vod->rv3d, C, false, true);
2401 
2402       ED_view3d_depth_tag_update(vod->rv3d);
2403 
2404       viewops_data_free(C, op);
2405       return OPERATOR_FINISHED;
2406     }
2407 
2408     if (U.viewzoom == USER_ZOOM_CONT) {
2409       /* needs a timer to continue redrawing */
2410       vod->timer = WM_event_add_timer(CTX_wm_manager(C), CTX_wm_window(C), TIMER, 0.01f);
2411       vod->prev.time = PIL_check_seconds_timer();
2412     }
2413 
2414     /* add temp handler */
2415     WM_event_add_modal_handler(C, op);
2416 
2417     return OPERATOR_RUNNING_MODAL;
2418   }
2419   return OPERATOR_FINISHED;
2420 }
2421 
viewzoom_cancel(bContext * C,wmOperator * op)2422 static void viewzoom_cancel(bContext *C, wmOperator *op)
2423 {
2424   viewops_data_free(C, op);
2425 }
2426 
VIEW3D_OT_zoom(wmOperatorType * ot)2427 void VIEW3D_OT_zoom(wmOperatorType *ot)
2428 {
2429   /* identifiers */
2430   ot->name = "Zoom View";
2431   ot->description = "Zoom in/out in the view";
2432   ot->idname = "VIEW3D_OT_zoom";
2433 
2434   /* api callbacks */
2435   ot->invoke = viewzoom_invoke;
2436   ot->exec = viewzoom_exec;
2437   ot->modal = viewzoom_modal;
2438   ot->poll = view3d_zoom_or_dolly_poll;
2439   ot->cancel = viewzoom_cancel;
2440 
2441   /* flags */
2442   ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_XY;
2443 
2444   /* properties */
2445   view3d_operator_properties_common(
2446       ot, V3D_OP_PROP_DELTA | V3D_OP_PROP_MOUSE_CO | V3D_OP_PROP_USE_MOUSE_INIT);
2447 }
2448 
2449 /** \} */
2450 
2451 /* -------------------------------------------------------------------- */
2452 /** \name View Dolly Operator
2453  *
2454  * Like zoom but translates the view offset along the view direction
2455  * which avoids #RegionView3D.dist approaching zero.
2456  * \{ */
2457 
2458 /* this is an exact copy of viewzoom_modal_keymap */
2459 /* called in transform_ops.c, on each regeneration of keymaps  */
viewdolly_modal_keymap(wmKeyConfig * keyconf)2460 void viewdolly_modal_keymap(wmKeyConfig *keyconf)
2461 {
2462   static const EnumPropertyItem modal_items[] = {
2463       {VIEW_MODAL_CONFIRM, "CONFIRM", 0, "Confirm", ""},
2464 
2465       {VIEWROT_MODAL_SWITCH_ROTATE, "SWITCH_TO_ROTATE", 0, "Switch to Rotate"},
2466       {VIEWROT_MODAL_SWITCH_MOVE, "SWITCH_TO_MOVE", 0, "Switch to Move"},
2467 
2468       {0, NULL, 0, NULL, NULL},
2469   };
2470 
2471   wmKeyMap *keymap = WM_modalkeymap_find(keyconf, "View3D Dolly Modal");
2472 
2473   /* this function is called for each spacetype, only needs to add map once */
2474   if (keymap && keymap->modal_items) {
2475     return;
2476   }
2477 
2478   keymap = WM_modalkeymap_ensure(keyconf, "View3D Dolly Modal", modal_items);
2479 
2480   /* disabled mode switching for now, can re-implement better, later on */
2481 #if 0
2482   WM_modalkeymap_add_item(keymap, LEFTMOUSE, KM_RELEASE, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ROTATE);
2483   WM_modalkeymap_add_item(keymap, LEFTCTRLKEY, KM_RELEASE, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ROTATE);
2484   WM_modalkeymap_add_item(keymap, LEFTSHIFTKEY, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_MOVE);
2485 #endif
2486 
2487   /* assign map to operators */
2488   WM_modalkeymap_assign(keymap, "VIEW3D_OT_dolly");
2489 }
2490 
viewdolly_offset_lock_check(bContext * C,wmOperator * op)2491 static bool viewdolly_offset_lock_check(bContext *C, wmOperator *op)
2492 {
2493   View3D *v3d = CTX_wm_view3d(C);
2494   RegionView3D *rv3d = CTX_wm_region_view3d(C);
2495   if (ED_view3d_offset_lock_check(v3d, rv3d)) {
2496     BKE_report(op->reports, RPT_WARNING, "Cannot dolly when the view offset is locked");
2497     return true;
2498   }
2499   return false;
2500 }
2501 
view_dolly_to_vector_3d(ARegion * region,const float orig_ofs[3],const float dvec[3],float dfac)2502 static void view_dolly_to_vector_3d(ARegion *region,
2503                                     const float orig_ofs[3],
2504                                     const float dvec[3],
2505                                     float dfac)
2506 {
2507   RegionView3D *rv3d = region->regiondata;
2508   madd_v3_v3v3fl(rv3d->ofs, orig_ofs, dvec, -(1.0f - dfac));
2509 }
2510 
viewdolly_apply(ViewOpsData * vod,const int xy[2],const short zoom_invert)2511 static void viewdolly_apply(ViewOpsData *vod, const int xy[2], const short zoom_invert)
2512 {
2513   float zfac = 1.0;
2514 
2515   {
2516     float len1, len2;
2517 
2518     if (U.uiflag & USER_ZOOM_HORIZ) {
2519       len1 = (vod->region->winrct.xmax - xy[0]) + 5;
2520       len2 = (vod->region->winrct.xmax - vod->init.event_xy[0]) + 5;
2521     }
2522     else {
2523       len1 = (vod->region->winrct.ymax - xy[1]) + 5;
2524       len2 = (vod->region->winrct.ymax - vod->init.event_xy[1]) + 5;
2525     }
2526     if (zoom_invert) {
2527       SWAP(float, len1, len2);
2528     }
2529 
2530     zfac = 1.0f + ((len1 - len2) * 0.01f * vod->rv3d->dist);
2531   }
2532 
2533   if (zfac != 1.0f) {
2534     view_dolly_to_vector_3d(vod->region, vod->init.ofs, vod->init.mousevec, zfac);
2535   }
2536 
2537   if (RV3D_LOCK_FLAGS(vod->rv3d) & RV3D_BOXVIEW) {
2538     view3d_boxview_sync(vod->area, vod->region);
2539   }
2540 
2541   ED_view3d_camera_lock_sync(vod->depsgraph, vod->v3d, vod->rv3d);
2542 
2543   ED_region_tag_redraw(vod->region);
2544 }
2545 
viewdolly_modal(bContext * C,wmOperator * op,const wmEvent * event)2546 static int viewdolly_modal(bContext *C, wmOperator *op, const wmEvent *event)
2547 {
2548   ViewOpsData *vod = op->customdata;
2549   short event_code = VIEW_PASS;
2550   bool use_autokey = false;
2551   int ret = OPERATOR_RUNNING_MODAL;
2552 
2553   /* execute the events */
2554   if (event->type == MOUSEMOVE) {
2555     event_code = VIEW_APPLY;
2556   }
2557   else if (event->type == EVT_MODAL_MAP) {
2558     switch (event->val) {
2559       case VIEW_MODAL_CONFIRM:
2560         event_code = VIEW_CONFIRM;
2561         break;
2562       case VIEWROT_MODAL_SWITCH_MOVE:
2563         WM_operator_name_call(C, "VIEW3D_OT_move", WM_OP_INVOKE_DEFAULT, NULL);
2564         event_code = VIEW_CONFIRM;
2565         break;
2566       case VIEWROT_MODAL_SWITCH_ROTATE:
2567         WM_operator_name_call(C, "VIEW3D_OT_rotate", WM_OP_INVOKE_DEFAULT, NULL);
2568         event_code = VIEW_CONFIRM;
2569         break;
2570     }
2571   }
2572   else if (event->type == vod->init.event_type && event->val == KM_RELEASE) {
2573     event_code = VIEW_CONFIRM;
2574   }
2575 
2576   if (event_code == VIEW_APPLY) {
2577     viewdolly_apply(vod, &event->x, (U.uiflag & USER_ZOOM_INVERT) != 0);
2578     if (ED_screen_animation_playing(CTX_wm_manager(C))) {
2579       use_autokey = true;
2580     }
2581   }
2582   else if (event_code == VIEW_CONFIRM) {
2583     ED_view3d_depth_tag_update(vod->rv3d);
2584     use_autokey = true;
2585     ret = OPERATOR_FINISHED;
2586   }
2587 
2588   if (use_autokey) {
2589     ED_view3d_camera_lock_autokey(vod->v3d, vod->rv3d, C, false, true);
2590   }
2591 
2592   if (ret & OPERATOR_FINISHED) {
2593     viewops_data_free(C, op);
2594   }
2595 
2596   return ret;
2597 }
2598 
viewdolly_exec(bContext * C,wmOperator * op)2599 static int viewdolly_exec(bContext *C, wmOperator *op)
2600 {
2601   View3D *v3d;
2602   RegionView3D *rv3d;
2603   ScrArea *area;
2604   ARegion *region;
2605   float mousevec[3];
2606 
2607   const int delta = RNA_int_get(op->ptr, "delta");
2608 
2609   if (op->customdata) {
2610     ViewOpsData *vod = op->customdata;
2611 
2612     area = vod->area;
2613     region = vod->region;
2614     copy_v3_v3(mousevec, vod->init.mousevec);
2615   }
2616   else {
2617     area = CTX_wm_area(C);
2618     region = CTX_wm_region(C);
2619     negate_v3_v3(mousevec, ((RegionView3D *)region->regiondata)->viewinv[2]);
2620     normalize_v3(mousevec);
2621   }
2622 
2623   v3d = area->spacedata.first;
2624   rv3d = region->regiondata;
2625 
2626   const bool use_cursor_init = RNA_boolean_get(op->ptr, "use_cursor_init");
2627 
2628   /* overwrite the mouse vector with the view direction (zoom into the center) */
2629   if ((use_cursor_init && (U.uiflag & USER_ZOOM_TO_MOUSEPOS)) == 0) {
2630     normalize_v3_v3(mousevec, rv3d->viewinv[2]);
2631   }
2632 
2633   view_dolly_to_vector_3d(region, rv3d->ofs, mousevec, delta < 0 ? 0.2f : 1.8f);
2634 
2635   if (RV3D_LOCK_FLAGS(rv3d) & RV3D_BOXVIEW) {
2636     view3d_boxview_sync(area, region);
2637   }
2638 
2639   ED_view3d_depth_tag_update(rv3d);
2640 
2641   ED_view3d_camera_lock_sync(CTX_data_ensure_evaluated_depsgraph(C), v3d, rv3d);
2642 
2643   ED_region_tag_redraw(region);
2644 
2645   viewops_data_free(C, op);
2646 
2647   return OPERATOR_FINISHED;
2648 }
2649 
2650 /* copied from viewzoom_invoke(), changes here may apply there */
viewdolly_invoke(bContext * C,wmOperator * op,const wmEvent * event)2651 static int viewdolly_invoke(bContext *C, wmOperator *op, const wmEvent *event)
2652 {
2653   ViewOpsData *vod;
2654 
2655   if (viewdolly_offset_lock_check(C, op)) {
2656     return OPERATOR_CANCELLED;
2657   }
2658 
2659   /* makes op->customdata */
2660   viewops_data_alloc(C, op);
2661   vod = op->customdata;
2662 
2663   /* poll should check but in some cases fails, see poll func for details */
2664   if (RV3D_LOCK_FLAGS(vod->rv3d) & RV3D_LOCK_ROTATION) {
2665     viewops_data_free(C, op);
2666     return OPERATOR_PASS_THROUGH;
2667   }
2668 
2669   ED_view3d_smooth_view_force_finish(C, vod->v3d, vod->region);
2670 
2671   /* needs to run before 'viewops_data_create' so the backup 'rv3d->ofs' is correct */
2672   /* switch from camera view when: */
2673   if (vod->rv3d->persp != RV3D_PERSP) {
2674     if (vod->rv3d->persp == RV3D_CAMOB) {
2675       /* ignore rv3d->lpersp because dolly only makes sense in perspective mode */
2676       const Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
2677       ED_view3d_persp_switch_from_camera(depsgraph, vod->v3d, vod->rv3d, RV3D_PERSP);
2678     }
2679     else {
2680       vod->rv3d->persp = RV3D_PERSP;
2681     }
2682     ED_region_tag_redraw(vod->region);
2683   }
2684 
2685   const bool use_cursor_init = RNA_boolean_get(op->ptr, "use_cursor_init");
2686 
2687   viewops_data_create(C,
2688                       op,
2689                       event,
2690                       (viewops_flag_from_prefs() & ~VIEWOPS_FLAG_ORBIT_SELECT) |
2691                           (use_cursor_init ? VIEWOPS_FLAG_USE_MOUSE_INIT : 0));
2692 
2693   /* if one or the other zoom position aren't set, set from event */
2694   if (!RNA_struct_property_is_set(op->ptr, "mx") || !RNA_struct_property_is_set(op->ptr, "my")) {
2695     RNA_int_set(op->ptr, "mx", event->x);
2696     RNA_int_set(op->ptr, "my", event->y);
2697   }
2698 
2699   if (RNA_struct_property_is_set(op->ptr, "delta")) {
2700     viewdolly_exec(C, op);
2701   }
2702   else {
2703     /* overwrite the mouse vector with the view direction (zoom into the center) */
2704     if ((use_cursor_init && (U.uiflag & USER_ZOOM_TO_MOUSEPOS)) == 0) {
2705       negate_v3_v3(vod->init.mousevec, vod->rv3d->viewinv[2]);
2706       normalize_v3(vod->init.mousevec);
2707     }
2708 
2709     if (event->type == MOUSEZOOM) {
2710       /* Bypass Zoom invert flag for track pads (pass false always) */
2711 
2712       if (U.uiflag & USER_ZOOM_HORIZ) {
2713         vod->init.event_xy[0] = vod->prev.event_xy[0] = event->x;
2714       }
2715       else {
2716         /* Set y move = x move as MOUSEZOOM uses only x axis to pass magnification value */
2717         vod->init.event_xy[1] = vod->prev.event_xy[1] = vod->init.event_xy[1] + event->x -
2718                                                         event->prevx;
2719       }
2720       viewdolly_apply(vod, &event->prevx, (U.uiflag & USER_ZOOM_INVERT) == 0);
2721       ED_view3d_depth_tag_update(vod->rv3d);
2722 
2723       viewops_data_free(C, op);
2724       return OPERATOR_FINISHED;
2725     }
2726 
2727     /* add temp handler */
2728     WM_event_add_modal_handler(C, op);
2729     return OPERATOR_RUNNING_MODAL;
2730   }
2731   return OPERATOR_FINISHED;
2732 }
2733 
viewdolly_cancel(bContext * C,wmOperator * op)2734 static void viewdolly_cancel(bContext *C, wmOperator *op)
2735 {
2736   viewops_data_free(C, op);
2737 }
2738 
VIEW3D_OT_dolly(wmOperatorType * ot)2739 void VIEW3D_OT_dolly(wmOperatorType *ot)
2740 {
2741   /* identifiers */
2742   ot->name = "Dolly View";
2743   ot->description = "Dolly in/out in the view";
2744   ot->idname = "VIEW3D_OT_dolly";
2745 
2746   /* api callbacks */
2747   ot->invoke = viewdolly_invoke;
2748   ot->exec = viewdolly_exec;
2749   ot->modal = viewdolly_modal;
2750   ot->poll = ED_operator_region_view3d_active;
2751   ot->cancel = viewdolly_cancel;
2752 
2753   /* flags */
2754   ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_XY;
2755 
2756   /* properties */
2757   view3d_operator_properties_common(
2758       ot, V3D_OP_PROP_DELTA | V3D_OP_PROP_MOUSE_CO | V3D_OP_PROP_USE_MOUSE_INIT);
2759 }
2760 
2761 /** \} */
2762 
2763 /* -------------------------------------------------------------------- */
2764 /** \name View All Operator
2765  *
2766  * Move & Zoom the view to fit all of its contents.
2767  * \{ */
2768 
view3d_object_skip_minmax(const View3D * v3d,const RegionView3D * rv3d,const Object * ob,const bool skip_camera,bool * r_only_center)2769 static bool view3d_object_skip_minmax(const View3D *v3d,
2770                                       const RegionView3D *rv3d,
2771                                       const Object *ob,
2772                                       const bool skip_camera,
2773                                       bool *r_only_center)
2774 {
2775   BLI_assert(ob->id.orig_id == NULL);
2776   *r_only_center = false;
2777 
2778   if (skip_camera && (ob == v3d->camera)) {
2779     return true;
2780   }
2781 
2782   if ((ob->type == OB_EMPTY) && (ob->empty_drawtype == OB_EMPTY_IMAGE) &&
2783       !BKE_object_empty_image_frame_is_visible_in_view3d(ob, rv3d)) {
2784     *r_only_center = true;
2785     return false;
2786   }
2787 
2788   return false;
2789 }
2790 
view3d_object_calc_minmax(Depsgraph * depsgraph,Scene * scene,Object * ob_eval,const bool only_center,float min[3],float max[3])2791 static void view3d_object_calc_minmax(Depsgraph *depsgraph,
2792                                       Scene *scene,
2793                                       Object *ob_eval,
2794                                       const bool only_center,
2795                                       float min[3],
2796                                       float max[3])
2797 {
2798   /* Account for duplis. */
2799   if (BKE_object_minmax_dupli(depsgraph, scene, ob_eval, min, max, false) == 0) {
2800     /* Use if duplis aren't found. */
2801     if (only_center) {
2802       minmax_v3v3_v3(min, max, ob_eval->obmat[3]);
2803     }
2804     else {
2805       BKE_object_minmax(ob_eval, min, max, false);
2806     }
2807   }
2808 }
2809 
view3d_from_minmax(bContext * C,View3D * v3d,ARegion * region,const float min[3],const float max[3],bool ok_dist,const int smooth_viewtx)2810 static void view3d_from_minmax(bContext *C,
2811                                View3D *v3d,
2812                                ARegion *region,
2813                                const float min[3],
2814                                const float max[3],
2815                                bool ok_dist,
2816                                const int smooth_viewtx)
2817 {
2818   RegionView3D *rv3d = region->regiondata;
2819   float afm[3];
2820   float size;
2821 
2822   ED_view3d_smooth_view_force_finish(C, v3d, region);
2823 
2824   /* SMOOTHVIEW */
2825   float new_ofs[3];
2826   float new_dist;
2827 
2828   sub_v3_v3v3(afm, max, min);
2829   size = max_fff(afm[0], afm[1], afm[2]);
2830 
2831   if (ok_dist) {
2832     char persp;
2833 
2834     if (rv3d->is_persp) {
2835       if (rv3d->persp == RV3D_CAMOB && ED_view3d_camera_lock_check(v3d, rv3d)) {
2836         persp = RV3D_CAMOB;
2837       }
2838       else {
2839         persp = RV3D_PERSP;
2840       }
2841     }
2842     else { /* ortho */
2843       if (size < 0.0001f) {
2844         /* bounding box was a single point so do not zoom */
2845         ok_dist = false;
2846       }
2847       else {
2848         /* adjust zoom so it looks nicer */
2849         persp = RV3D_ORTHO;
2850       }
2851     }
2852 
2853     if (ok_dist) {
2854       Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
2855       new_dist = ED_view3d_radius_to_dist(
2856           v3d, region, depsgraph, persp, true, (size / 2) * VIEW3D_MARGIN);
2857       if (rv3d->is_persp) {
2858         /* don't zoom closer than the near clipping plane */
2859         new_dist = max_ff(new_dist, v3d->clip_start * 1.5f);
2860       }
2861     }
2862   }
2863 
2864   mid_v3_v3v3(new_ofs, min, max);
2865   negate_v3(new_ofs);
2866 
2867   if (rv3d->persp == RV3D_CAMOB && !ED_view3d_camera_lock_check(v3d, rv3d)) {
2868     rv3d->persp = RV3D_PERSP;
2869     ED_view3d_smooth_view(C,
2870                           v3d,
2871                           region,
2872                           smooth_viewtx,
2873                           &(const V3D_SmoothParams){
2874                               .camera_old = v3d->camera,
2875                               .ofs = new_ofs,
2876                               .dist = ok_dist ? &new_dist : NULL,
2877                           });
2878   }
2879   else {
2880     ED_view3d_smooth_view(C,
2881                           v3d,
2882                           region,
2883                           smooth_viewtx,
2884                           &(const V3D_SmoothParams){
2885                               .ofs = new_ofs,
2886                               .dist = ok_dist ? &new_dist : NULL,
2887                           });
2888   }
2889 
2890   /* smooth view does viewlock RV3D_BOXVIEW copy */
2891 }
2892 
2893 /**
2894  * Same as #view3d_from_minmax but for all regions (except cameras).
2895  */
view3d_from_minmax_multi(bContext * C,View3D * v3d,const float min[3],const float max[3],const bool ok_dist,const int smooth_viewtx)2896 static void view3d_from_minmax_multi(bContext *C,
2897                                      View3D *v3d,
2898                                      const float min[3],
2899                                      const float max[3],
2900                                      const bool ok_dist,
2901                                      const int smooth_viewtx)
2902 {
2903   ScrArea *area = CTX_wm_area(C);
2904   ARegion *region;
2905   for (region = area->regionbase.first; region; region = region->next) {
2906     if (region->regiontype == RGN_TYPE_WINDOW) {
2907       RegionView3D *rv3d = region->regiondata;
2908       /* when using all regions, don't jump out of camera view,
2909        * but _do_ allow locked cameras to be moved */
2910       if ((rv3d->persp != RV3D_CAMOB) || ED_view3d_camera_lock_check(v3d, rv3d)) {
2911         view3d_from_minmax(C, v3d, region, min, max, ok_dist, smooth_viewtx);
2912       }
2913     }
2914   }
2915 }
2916 
view3d_all_exec(bContext * C,wmOperator * op)2917 static int view3d_all_exec(bContext *C, wmOperator *op)
2918 {
2919   ARegion *region = CTX_wm_region(C);
2920   View3D *v3d = CTX_wm_view3d(C);
2921   RegionView3D *rv3d = CTX_wm_region_view3d(C);
2922   Scene *scene = CTX_data_scene(C);
2923   Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
2924   ViewLayer *view_layer_eval = DEG_get_evaluated_view_layer(depsgraph);
2925   Base *base_eval;
2926   const bool use_all_regions = RNA_boolean_get(op->ptr, "use_all_regions");
2927   const bool skip_camera = (ED_view3d_camera_lock_check(v3d, region->regiondata) ||
2928                             /* any one of the regions may be locked */
2929                             (use_all_regions && v3d->flag2 & V3D_LOCK_CAMERA));
2930   const bool center = RNA_boolean_get(op->ptr, "center");
2931   const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
2932 
2933   float min[3], max[3];
2934   bool changed = false;
2935 
2936   if (center) {
2937     /* in 2.4x this also move the cursor to (0, 0, 0) (with shift+c). */
2938     View3DCursor *cursor = &scene->cursor;
2939     zero_v3(min);
2940     zero_v3(max);
2941     zero_v3(cursor->location);
2942     float mat3[3][3];
2943     unit_m3(mat3);
2944     BKE_scene_cursor_mat3_to_rot(cursor, mat3, false);
2945   }
2946   else {
2947     INIT_MINMAX(min, max);
2948   }
2949 
2950   for (base_eval = view_layer_eval->object_bases.first; base_eval; base_eval = base_eval->next) {
2951     if (BASE_VISIBLE(v3d, base_eval)) {
2952       bool only_center = false;
2953       Object *ob = DEG_get_original_object(base_eval->object);
2954       if (view3d_object_skip_minmax(v3d, rv3d, ob, skip_camera, &only_center)) {
2955         continue;
2956       }
2957       view3d_object_calc_minmax(depsgraph, scene, base_eval->object, only_center, min, max);
2958       changed = true;
2959     }
2960   }
2961 
2962   if (center) {
2963     struct wmMsgBus *mbus = CTX_wm_message_bus(C);
2964     WM_msg_publish_rna_prop(mbus, &scene->id, &scene->cursor, View3DCursor, location);
2965 
2966     DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE);
2967   }
2968 
2969   if (!changed) {
2970     ED_region_tag_redraw(region);
2971     /* TODO - should this be cancel?
2972      * I think no, because we always move the cursor, with or without
2973      * object, but in this case there is no change in the scene,
2974      * only the cursor so I choice a ED_region_tag like
2975      * view3d_smooth_view do for the center_cursor.
2976      * See bug T22640.
2977      */
2978     return OPERATOR_FINISHED;
2979   }
2980 
2981   if (use_all_regions) {
2982     view3d_from_minmax_multi(C, v3d, min, max, true, smooth_viewtx);
2983   }
2984   else {
2985     view3d_from_minmax(C, v3d, region, min, max, true, smooth_viewtx);
2986   }
2987 
2988   return OPERATOR_FINISHED;
2989 }
2990 
VIEW3D_OT_view_all(wmOperatorType * ot)2991 void VIEW3D_OT_view_all(wmOperatorType *ot)
2992 {
2993   /* identifiers */
2994   ot->name = "Frame All";
2995   ot->description = "View all objects in scene";
2996   ot->idname = "VIEW3D_OT_view_all";
2997 
2998   /* api callbacks */
2999   ot->exec = view3d_all_exec;
3000   ot->poll = ED_operator_region_view3d_active;
3001 
3002   /* flags */
3003   ot->flag = 0;
3004 
3005   /* properties */
3006   view3d_operator_properties_common(ot, V3D_OP_PROP_USE_ALL_REGIONS);
3007   RNA_def_boolean(ot->srna, "center", 0, "Center", "");
3008 }
3009 
3010 /** \} */
3011 
3012 /* -------------------------------------------------------------------- */
3013 /** \name Frame Selected Operator
3014  *
3015  * Move & Zoom the view to fit selected contents.
3016  * \{ */
3017 
3018 /* like a localview without local!, was centerview() in 2.4x */
viewselected_exec(bContext * C,wmOperator * op)3019 static int viewselected_exec(bContext *C, wmOperator *op)
3020 {
3021   ARegion *region = CTX_wm_region(C);
3022   View3D *v3d = CTX_wm_view3d(C);
3023   RegionView3D *rv3d = CTX_wm_region_view3d(C);
3024   Scene *scene = CTX_data_scene(C);
3025   Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
3026   ViewLayer *view_layer_eval = DEG_get_evaluated_view_layer(depsgraph);
3027   Object *ob_eval = OBACT(view_layer_eval);
3028   Object *obedit = CTX_data_edit_object(C);
3029   const bGPdata *gpd_eval = ob_eval && (ob_eval->type == OB_GPENCIL) ? ob_eval->data : NULL;
3030   const bool is_gp_edit = gpd_eval ? GPENCIL_ANY_MODE(gpd_eval) : false;
3031   const bool is_face_map = ((is_gp_edit == false) && region->gizmo_map &&
3032                             WM_gizmomap_is_any_selected(region->gizmo_map));
3033   float min[3], max[3];
3034   bool ok = false, ok_dist = true;
3035   const bool use_all_regions = RNA_boolean_get(op->ptr, "use_all_regions");
3036   const bool skip_camera = (ED_view3d_camera_lock_check(v3d, region->regiondata) ||
3037                             /* any one of the regions may be locked */
3038                             (use_all_regions && v3d->flag2 & V3D_LOCK_CAMERA));
3039   const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
3040 
3041   INIT_MINMAX(min, max);
3042   if (is_face_map) {
3043     ob_eval = NULL;
3044   }
3045 
3046   if (ob_eval && (ob_eval->mode & OB_MODE_WEIGHT_PAINT)) {
3047     /* hard-coded exception, we look for the one selected armature */
3048     /* this is weak code this way, we should make a generic
3049      * active/selection callback interface once... */
3050     Base *base_eval;
3051     for (base_eval = view_layer_eval->object_bases.first; base_eval; base_eval = base_eval->next) {
3052       if (BASE_SELECTED_EDITABLE(v3d, base_eval)) {
3053         if (base_eval->object->type == OB_ARMATURE) {
3054           if (base_eval->object->mode & OB_MODE_POSE) {
3055             break;
3056           }
3057         }
3058       }
3059     }
3060     if (base_eval) {
3061       ob_eval = base_eval->object;
3062     }
3063   }
3064 
3065   if (is_gp_edit) {
3066     CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) {
3067       /* we're only interested in selected points here... */
3068       if ((gps->flag & GP_STROKE_SELECT) && (gps->flag & GP_STROKE_3DSPACE)) {
3069         ok |= BKE_gpencil_stroke_minmax(gps, true, min, max);
3070       }
3071     }
3072     CTX_DATA_END;
3073 
3074     if ((ob_eval) && (ok)) {
3075       mul_m4_v3(ob_eval->obmat, min);
3076       mul_m4_v3(ob_eval->obmat, max);
3077     }
3078   }
3079   else if (is_face_map) {
3080     ok = WM_gizmomap_minmax(region->gizmo_map, true, true, min, max);
3081   }
3082   else if (obedit) {
3083     /* only selected */
3084     FOREACH_OBJECT_IN_MODE_BEGIN (view_layer_eval, v3d, obedit->type, obedit->mode, ob_eval_iter) {
3085       ok |= ED_view3d_minmax_verts(ob_eval_iter, min, max);
3086     }
3087     FOREACH_OBJECT_IN_MODE_END;
3088   }
3089   else if (ob_eval && (ob_eval->mode & OB_MODE_POSE)) {
3090     FOREACH_OBJECT_IN_MODE_BEGIN (
3091         view_layer_eval, v3d, ob_eval->type, ob_eval->mode, ob_eval_iter) {
3092       ok |= BKE_pose_minmax(ob_eval_iter, min, max, true, true);
3093     }
3094     FOREACH_OBJECT_IN_MODE_END;
3095   }
3096   else if (BKE_paint_select_face_test(ob_eval)) {
3097     ok = paintface_minmax(ob_eval, min, max);
3098   }
3099   else if (ob_eval && (ob_eval->mode & OB_MODE_PARTICLE_EDIT)) {
3100     ok = PE_minmax(depsgraph, scene, CTX_data_view_layer(C), min, max);
3101   }
3102   else if (ob_eval && (ob_eval->mode & (OB_MODE_SCULPT | OB_MODE_VERTEX_PAINT |
3103                                         OB_MODE_WEIGHT_PAINT | OB_MODE_TEXTURE_PAINT))) {
3104     BKE_paint_stroke_get_average(scene, ob_eval, min);
3105     copy_v3_v3(max, min);
3106     ok = true;
3107     ok_dist = 0; /* don't zoom */
3108   }
3109   else {
3110     Base *base_eval;
3111     for (base_eval = FIRSTBASE(view_layer_eval); base_eval; base_eval = base_eval->next) {
3112       if (BASE_SELECTED(v3d, base_eval)) {
3113         bool only_center = false;
3114         Object *ob = DEG_get_original_object(base_eval->object);
3115         if (view3d_object_skip_minmax(v3d, rv3d, ob, skip_camera, &only_center)) {
3116           continue;
3117         }
3118         view3d_object_calc_minmax(depsgraph, scene, base_eval->object, only_center, min, max);
3119         ok = 1;
3120       }
3121     }
3122   }
3123 
3124   if (ok == 0) {
3125     return OPERATOR_FINISHED;
3126   }
3127 
3128   if (use_all_regions) {
3129     view3d_from_minmax_multi(C, v3d, min, max, ok_dist, smooth_viewtx);
3130   }
3131   else {
3132     view3d_from_minmax(C, v3d, region, min, max, ok_dist, smooth_viewtx);
3133   }
3134 
3135   return OPERATOR_FINISHED;
3136 }
3137 
VIEW3D_OT_view_selected(wmOperatorType * ot)3138 void VIEW3D_OT_view_selected(wmOperatorType *ot)
3139 {
3140   /* identifiers */
3141   ot->name = "Frame Selected";
3142   ot->description = "Move the view to the selection center";
3143   ot->idname = "VIEW3D_OT_view_selected";
3144 
3145   /* api callbacks */
3146   ot->exec = viewselected_exec;
3147   ot->poll = view3d_zoom_or_dolly_poll;
3148 
3149   /* flags */
3150   ot->flag = 0;
3151 
3152   /* properties */
3153   view3d_operator_properties_common(ot, V3D_OP_PROP_USE_ALL_REGIONS);
3154 }
3155 
3156 /** \} */
3157 
3158 /* -------------------------------------------------------------------- */
3159 /** \name View Lock Clear Operator
3160  * \{ */
3161 
view_lock_clear_exec(bContext * C,wmOperator * UNUSED (op))3162 static int view_lock_clear_exec(bContext *C, wmOperator *UNUSED(op))
3163 {
3164   View3D *v3d = CTX_wm_view3d(C);
3165 
3166   if (v3d) {
3167     ED_view3d_lock_clear(v3d);
3168 
3169     WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, v3d);
3170 
3171     return OPERATOR_FINISHED;
3172   }
3173 
3174   return OPERATOR_CANCELLED;
3175 }
3176 
VIEW3D_OT_view_lock_clear(wmOperatorType * ot)3177 void VIEW3D_OT_view_lock_clear(wmOperatorType *ot)
3178 {
3179 
3180   /* identifiers */
3181   ot->name = "View Lock Clear";
3182   ot->description = "Clear all view locking";
3183   ot->idname = "VIEW3D_OT_view_lock_clear";
3184 
3185   /* api callbacks */
3186   ot->exec = view_lock_clear_exec;
3187   ot->poll = ED_operator_region_view3d_active;
3188 
3189   /* flags */
3190   ot->flag = 0;
3191 }
3192 
3193 /** \} */
3194 
3195 /* -------------------------------------------------------------------- */
3196 /** \name View Lock to Active Operator
3197  * \{ */
3198 
view_lock_to_active_exec(bContext * C,wmOperator * UNUSED (op))3199 static int view_lock_to_active_exec(bContext *C, wmOperator *UNUSED(op))
3200 {
3201   View3D *v3d = CTX_wm_view3d(C);
3202   Object *obact = CTX_data_active_object(C);
3203 
3204   if (v3d) {
3205     ED_view3d_lock_clear(v3d);
3206 
3207     v3d->ob_center = obact; /* can be NULL */
3208 
3209     if (obact && obact->type == OB_ARMATURE) {
3210       if (obact->mode & OB_MODE_POSE) {
3211         Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
3212         Object *obact_eval = DEG_get_evaluated_object(depsgraph, obact);
3213         bPoseChannel *pcham_act = BKE_pose_channel_active(obact_eval);
3214         if (pcham_act) {
3215           BLI_strncpy(v3d->ob_center_bone, pcham_act->name, sizeof(v3d->ob_center_bone));
3216         }
3217       }
3218       else {
3219         EditBone *ebone_act = ((bArmature *)obact->data)->act_edbone;
3220         if (ebone_act) {
3221           BLI_strncpy(v3d->ob_center_bone, ebone_act->name, sizeof(v3d->ob_center_bone));
3222         }
3223       }
3224     }
3225 
3226     WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, v3d);
3227 
3228     return OPERATOR_FINISHED;
3229   }
3230 
3231   return OPERATOR_CANCELLED;
3232 }
3233 
VIEW3D_OT_view_lock_to_active(wmOperatorType * ot)3234 void VIEW3D_OT_view_lock_to_active(wmOperatorType *ot)
3235 {
3236 
3237   /* identifiers */
3238   ot->name = "View Lock to Active";
3239   ot->description = "Lock the view to the active object/bone";
3240   ot->idname = "VIEW3D_OT_view_lock_to_active";
3241 
3242   /* api callbacks */
3243   ot->exec = view_lock_to_active_exec;
3244   ot->poll = ED_operator_region_view3d_active;
3245 
3246   /* flags */
3247   ot->flag = 0;
3248 }
3249 
3250 /** \} */
3251 
3252 /* -------------------------------------------------------------------- */
3253 /** \name View Center Cursor Operator
3254  * \{ */
3255 
viewcenter_cursor_exec(bContext * C,wmOperator * op)3256 static int viewcenter_cursor_exec(bContext *C, wmOperator *op)
3257 {
3258   View3D *v3d = CTX_wm_view3d(C);
3259   RegionView3D *rv3d = CTX_wm_region_view3d(C);
3260   Scene *scene = CTX_data_scene(C);
3261 
3262   if (rv3d) {
3263     ARegion *region = CTX_wm_region(C);
3264     const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
3265 
3266     ED_view3d_smooth_view_force_finish(C, v3d, region);
3267 
3268     /* non camera center */
3269     float new_ofs[3];
3270     negate_v3_v3(new_ofs, scene->cursor.location);
3271     ED_view3d_smooth_view(
3272         C, v3d, region, smooth_viewtx, &(const V3D_SmoothParams){.ofs = new_ofs});
3273 
3274     /* smooth view does viewlock RV3D_BOXVIEW copy */
3275   }
3276 
3277   return OPERATOR_FINISHED;
3278 }
3279 
VIEW3D_OT_view_center_cursor(wmOperatorType * ot)3280 void VIEW3D_OT_view_center_cursor(wmOperatorType *ot)
3281 {
3282   /* identifiers */
3283   ot->name = "Center View to Cursor";
3284   ot->description = "Center the view so that the cursor is in the middle of the view";
3285   ot->idname = "VIEW3D_OT_view_center_cursor";
3286 
3287   /* api callbacks */
3288   ot->exec = viewcenter_cursor_exec;
3289   ot->poll = view3d_pan_poll;
3290 
3291   /* flags */
3292   ot->flag = 0;
3293 }
3294 
3295 /** \} */
3296 
3297 /* -------------------------------------------------------------------- */
3298 /** \name View Center Pick Operator
3299  * \{ */
3300 
viewcenter_pick_invoke(bContext * C,wmOperator * op,const wmEvent * event)3301 static int viewcenter_pick_invoke(bContext *C, wmOperator *op, const wmEvent *event)
3302 {
3303   View3D *v3d = CTX_wm_view3d(C);
3304   RegionView3D *rv3d = CTX_wm_region_view3d(C);
3305   ARegion *region = CTX_wm_region(C);
3306 
3307   if (rv3d) {
3308     struct Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
3309     float new_ofs[3];
3310     const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
3311 
3312     ED_view3d_smooth_view_force_finish(C, v3d, region);
3313 
3314     view3d_operator_needs_opengl(C);
3315 
3316     if (ED_view3d_autodist(depsgraph, region, v3d, event->mval, new_ofs, false, NULL)) {
3317       /* pass */
3318     }
3319     else {
3320       /* fallback to simple pan */
3321       negate_v3_v3(new_ofs, rv3d->ofs);
3322       ED_view3d_win_to_3d_int(v3d, region, new_ofs, event->mval, new_ofs);
3323     }
3324     negate_v3(new_ofs);
3325     ED_view3d_smooth_view(
3326         C, v3d, region, smooth_viewtx, &(const V3D_SmoothParams){.ofs = new_ofs});
3327   }
3328 
3329   return OPERATOR_FINISHED;
3330 }
3331 
VIEW3D_OT_view_center_pick(wmOperatorType * ot)3332 void VIEW3D_OT_view_center_pick(wmOperatorType *ot)
3333 {
3334   /* identifiers */
3335   ot->name = "Center View to Mouse";
3336   ot->description = "Center the view to the Z-depth position under the mouse cursor";
3337   ot->idname = "VIEW3D_OT_view_center_pick";
3338 
3339   /* api callbacks */
3340   ot->invoke = viewcenter_pick_invoke;
3341   ot->poll = view3d_pan_poll;
3342 
3343   /* flags */
3344   ot->flag = 0;
3345 }
3346 
3347 /** \} */
3348 
3349 /* -------------------------------------------------------------------- */
3350 /** \name Frame Camera Bounds Operator
3351  * \{ */
3352 
view3d_center_camera_exec(bContext * C,wmOperator * UNUSED (op))3353 static int view3d_center_camera_exec(bContext *C, wmOperator *UNUSED(op))
3354 {
3355   Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
3356   Scene *scene = CTX_data_scene(C);
3357   float xfac, yfac;
3358   float size[2];
3359 
3360   View3D *v3d;
3361   ARegion *region;
3362   RegionView3D *rv3d;
3363 
3364   /* no NULL check is needed, poll checks */
3365   ED_view3d_context_user_region(C, &v3d, &region);
3366   rv3d = region->regiondata;
3367 
3368   rv3d->camdx = rv3d->camdy = 0.0f;
3369 
3370   ED_view3d_calc_camera_border_size(scene, depsgraph, region, v3d, rv3d, size);
3371 
3372   /* 4px is just a little room from the edge of the area */
3373   xfac = (float)region->winx / (float)(size[0] + 4);
3374   yfac = (float)region->winy / (float)(size[1] + 4);
3375 
3376   rv3d->camzoom = BKE_screen_view3d_zoom_from_fac(min_ff(xfac, yfac));
3377   CLAMP(rv3d->camzoom, RV3D_CAMZOOM_MIN, RV3D_CAMZOOM_MAX);
3378 
3379   WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, v3d);
3380 
3381   return OPERATOR_FINISHED;
3382 }
3383 
VIEW3D_OT_view_center_camera(wmOperatorType * ot)3384 void VIEW3D_OT_view_center_camera(wmOperatorType *ot)
3385 {
3386   /* identifiers */
3387   ot->name = "Frame Camera Bounds";
3388   ot->description = "Center the camera view, resizing the view to fit its bounds";
3389   ot->idname = "VIEW3D_OT_view_center_camera";
3390 
3391   /* api callbacks */
3392   ot->exec = view3d_center_camera_exec;
3393   ot->poll = view3d_camera_user_poll;
3394 
3395   /* flags */
3396   ot->flag = 0;
3397 }
3398 
3399 /** \} */
3400 
3401 /* -------------------------------------------------------------------- */
3402 /** \name View Lock Center Operator
3403  * \{ */
3404 
view3d_center_lock_exec(bContext * C,wmOperator * UNUSED (op))3405 static int view3d_center_lock_exec(bContext *C, wmOperator *UNUSED(op))
3406 {
3407   RegionView3D *rv3d = CTX_wm_region_view3d(C);
3408 
3409   zero_v2(rv3d->ofs_lock);
3410 
3411   WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, CTX_wm_view3d(C));
3412 
3413   return OPERATOR_FINISHED;
3414 }
3415 
VIEW3D_OT_view_center_lock(wmOperatorType * ot)3416 void VIEW3D_OT_view_center_lock(wmOperatorType *ot)
3417 {
3418   /* identifiers */
3419   ot->name = "View Lock Center";
3420   ot->description = "Center the view lock offset";
3421   ot->idname = "VIEW3D_OT_view_center_lock";
3422 
3423   /* api callbacks */
3424   ot->exec = view3d_center_lock_exec;
3425   ot->poll = view3d_lock_poll;
3426 
3427   /* flags */
3428   ot->flag = 0;
3429 }
3430 
3431 /** \} */
3432 
3433 /* -------------------------------------------------------------------- */
3434 /** \name Set Render Border Operator
3435  * \{ */
3436 
render_border_exec(bContext * C,wmOperator * op)3437 static int render_border_exec(bContext *C, wmOperator *op)
3438 {
3439   View3D *v3d = CTX_wm_view3d(C);
3440   ARegion *region = CTX_wm_region(C);
3441   RegionView3D *rv3d = ED_view3d_context_rv3d(C);
3442 
3443   Scene *scene = CTX_data_scene(C);
3444 
3445   rcti rect;
3446   rctf vb, border;
3447 
3448   /* get box select values using rna */
3449   WM_operator_properties_border_to_rcti(op, &rect);
3450 
3451   /* calculate range */
3452 
3453   if (rv3d->persp == RV3D_CAMOB) {
3454     Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
3455     ED_view3d_calc_camera_border(scene, depsgraph, region, v3d, rv3d, &vb, false);
3456   }
3457   else {
3458     vb.xmin = 0;
3459     vb.ymin = 0;
3460     vb.xmax = region->winx;
3461     vb.ymax = region->winy;
3462   }
3463 
3464   border.xmin = ((float)rect.xmin - vb.xmin) / BLI_rctf_size_x(&vb);
3465   border.ymin = ((float)rect.ymin - vb.ymin) / BLI_rctf_size_y(&vb);
3466   border.xmax = ((float)rect.xmax - vb.xmin) / BLI_rctf_size_x(&vb);
3467   border.ymax = ((float)rect.ymax - vb.ymin) / BLI_rctf_size_y(&vb);
3468 
3469   /* actually set border */
3470   CLAMP(border.xmin, 0.0f, 1.0f);
3471   CLAMP(border.ymin, 0.0f, 1.0f);
3472   CLAMP(border.xmax, 0.0f, 1.0f);
3473   CLAMP(border.ymax, 0.0f, 1.0f);
3474 
3475   if (rv3d->persp == RV3D_CAMOB) {
3476     scene->r.border = border;
3477 
3478     WM_event_add_notifier(C, NC_SCENE | ND_RENDER_OPTIONS, NULL);
3479   }
3480   else {
3481     v3d->render_border = border;
3482 
3483     WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, NULL);
3484   }
3485 
3486   /* drawing a border outside the camera view switches off border rendering */
3487   if ((border.xmin == border.xmax || border.ymin == border.ymax)) {
3488     if (rv3d->persp == RV3D_CAMOB) {
3489       scene->r.mode &= ~R_BORDER;
3490     }
3491     else {
3492       v3d->flag2 &= ~V3D_RENDER_BORDER;
3493     }
3494   }
3495   else {
3496     if (rv3d->persp == RV3D_CAMOB) {
3497       scene->r.mode |= R_BORDER;
3498     }
3499     else {
3500       v3d->flag2 |= V3D_RENDER_BORDER;
3501     }
3502   }
3503 
3504   if (rv3d->persp == RV3D_CAMOB) {
3505     DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE);
3506   }
3507   return OPERATOR_FINISHED;
3508 }
3509 
VIEW3D_OT_render_border(wmOperatorType * ot)3510 void VIEW3D_OT_render_border(wmOperatorType *ot)
3511 {
3512   /* identifiers */
3513   ot->name = "Set Render Region";
3514   ot->description = "Set the boundaries of the border render and enable border render";
3515   ot->idname = "VIEW3D_OT_render_border";
3516 
3517   /* api callbacks */
3518   ot->invoke = WM_gesture_box_invoke;
3519   ot->exec = render_border_exec;
3520   ot->modal = WM_gesture_box_modal;
3521   ot->cancel = WM_gesture_box_cancel;
3522 
3523   ot->poll = ED_operator_view3d_active;
3524 
3525   /* flags */
3526   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3527 
3528   /* properties */
3529   WM_operator_properties_border(ot);
3530 }
3531 
3532 /** \} */
3533 
3534 /* -------------------------------------------------------------------- */
3535 /** \name Clear Render Border Operator
3536  * \{ */
3537 
clear_render_border_exec(bContext * C,wmOperator * UNUSED (op))3538 static int clear_render_border_exec(bContext *C, wmOperator *UNUSED(op))
3539 {
3540   View3D *v3d = CTX_wm_view3d(C);
3541   RegionView3D *rv3d = ED_view3d_context_rv3d(C);
3542 
3543   Scene *scene = CTX_data_scene(C);
3544   rctf *border = NULL;
3545 
3546   if (rv3d->persp == RV3D_CAMOB) {
3547     scene->r.mode &= ~R_BORDER;
3548     border = &scene->r.border;
3549 
3550     WM_event_add_notifier(C, NC_SCENE | ND_RENDER_OPTIONS, NULL);
3551   }
3552   else {
3553     v3d->flag2 &= ~V3D_RENDER_BORDER;
3554     border = &v3d->render_border;
3555 
3556     WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, NULL);
3557   }
3558 
3559   border->xmin = 0.0f;
3560   border->ymin = 0.0f;
3561   border->xmax = 1.0f;
3562   border->ymax = 1.0f;
3563 
3564   if (rv3d->persp == RV3D_CAMOB) {
3565     DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE);
3566   }
3567   return OPERATOR_FINISHED;
3568 }
3569 
VIEW3D_OT_clear_render_border(wmOperatorType * ot)3570 void VIEW3D_OT_clear_render_border(wmOperatorType *ot)
3571 {
3572   /* identifiers */
3573   ot->name = "Clear Render Region";
3574   ot->description = "Clear the boundaries of the border render and disable border render";
3575   ot->idname = "VIEW3D_OT_clear_render_border";
3576 
3577   /* api callbacks */
3578   ot->exec = clear_render_border_exec;
3579   ot->poll = ED_operator_view3d_active;
3580 
3581   /* flags */
3582   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
3583 }
3584 
3585 /** \} */
3586 
3587 /* -------------------------------------------------------------------- */
3588 /** \name Border Zoom Operator
3589  * \{ */
3590 
view3d_zoom_border_exec(bContext * C,wmOperator * op)3591 static int view3d_zoom_border_exec(bContext *C, wmOperator *op)
3592 {
3593   ARegion *region = CTX_wm_region(C);
3594   View3D *v3d = CTX_wm_view3d(C);
3595   RegionView3D *rv3d = CTX_wm_region_view3d(C);
3596   const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
3597 
3598   /* Zooms in on a border drawn by the user */
3599   rcti rect;
3600   float dvec[3], vb[2], xscale, yscale;
3601   float dist_range[2];
3602 
3603   /* SMOOTHVIEW */
3604   float new_dist;
3605   float new_ofs[3];
3606 
3607   /* ZBuffer depth vars */
3608   float depth_close = FLT_MAX;
3609   float cent[2], p[3];
3610 
3611   /* note; otherwise opengl won't work */
3612   view3d_operator_needs_opengl(C);
3613 
3614   /* get box select values using rna */
3615   WM_operator_properties_border_to_rcti(op, &rect);
3616 
3617   /* check if zooming in/out view */
3618   const bool zoom_in = !RNA_boolean_get(op->ptr, "zoom_out");
3619 
3620   ED_view3d_dist_range_get(v3d, dist_range);
3621 
3622   /* Get Z Depths, needed for perspective, nice for ortho */
3623   ED_view3d_draw_depth(CTX_data_ensure_evaluated_depsgraph(C), region, v3d, true);
3624 
3625   {
3626     /* avoid allocating the whole depth buffer */
3627     ViewDepths depth_temp = {0};
3628 
3629     /* avoid view3d_update_depths() for speed. */
3630     view3d_update_depths_rect(region, &depth_temp, &rect);
3631 
3632     /* find the closest Z pixel */
3633     depth_close = view3d_depth_near(&depth_temp);
3634 
3635     MEM_SAFE_FREE(depth_temp.depths);
3636   }
3637 
3638   /* Resize border to the same ratio as the window. */
3639   {
3640     const float region_aspect = (float)region->winx / (float)region->winy;
3641     if (((float)BLI_rcti_size_x(&rect) / (float)BLI_rcti_size_y(&rect)) < region_aspect) {
3642       BLI_rcti_resize_x(&rect, (int)(BLI_rcti_size_y(&rect) * region_aspect));
3643     }
3644     else {
3645       BLI_rcti_resize_y(&rect, (int)(BLI_rcti_size_x(&rect) / region_aspect));
3646     }
3647   }
3648 
3649   cent[0] = (((float)rect.xmin) + ((float)rect.xmax)) / 2;
3650   cent[1] = (((float)rect.ymin) + ((float)rect.ymax)) / 2;
3651 
3652   if (rv3d->is_persp) {
3653     float p_corner[3];
3654 
3655     /* no depths to use, we cant do anything! */
3656     if (depth_close == FLT_MAX) {
3657       BKE_report(op->reports, RPT_ERROR, "Depth too large");
3658       return OPERATOR_CANCELLED;
3659     }
3660     /* convert border to 3d coordinates */
3661     if ((!ED_view3d_unproject(region, cent[0], cent[1], depth_close, p)) ||
3662         (!ED_view3d_unproject(region, rect.xmin, rect.ymin, depth_close, p_corner))) {
3663       return OPERATOR_CANCELLED;
3664     }
3665 
3666     sub_v3_v3v3(dvec, p, p_corner);
3667     negate_v3_v3(new_ofs, p);
3668 
3669     new_dist = len_v3(dvec);
3670 
3671     /* Account for the lens, without this a narrow lens zooms in too close. */
3672     new_dist *= (v3d->lens / DEFAULT_SENSOR_WIDTH);
3673 
3674     /* ignore dist_range min */
3675     dist_range[0] = v3d->clip_start * 1.5f;
3676   }
3677   else { /* orthographic */
3678     /* find the current window width and height */
3679     vb[0] = region->winx;
3680     vb[1] = region->winy;
3681 
3682     new_dist = rv3d->dist;
3683 
3684     /* convert the drawn rectangle into 3d space */
3685     if (depth_close != FLT_MAX && ED_view3d_unproject(region, cent[0], cent[1], depth_close, p)) {
3686       negate_v3_v3(new_ofs, p);
3687     }
3688     else {
3689       float mval_f[2];
3690       float zfac;
3691 
3692       /* We can't use the depth, fallback to the old way that doesn't set the center depth */
3693       copy_v3_v3(new_ofs, rv3d->ofs);
3694 
3695       {
3696         float tvec[3];
3697         negate_v3_v3(tvec, new_ofs);
3698         zfac = ED_view3d_calc_zfac(rv3d, tvec, NULL);
3699       }
3700 
3701       mval_f[0] = (rect.xmin + rect.xmax - vb[0]) / 2.0f;
3702       mval_f[1] = (rect.ymin + rect.ymax - vb[1]) / 2.0f;
3703       ED_view3d_win_to_delta(region, mval_f, dvec, zfac);
3704       /* center the view to the center of the rectangle */
3705       sub_v3_v3(new_ofs, dvec);
3706     }
3707 
3708     /* work out the ratios, so that everything selected fits when we zoom */
3709     xscale = (BLI_rcti_size_x(&rect) / vb[0]);
3710     yscale = (BLI_rcti_size_y(&rect) / vb[1]);
3711     new_dist *= max_ff(xscale, yscale);
3712   }
3713 
3714   if (!zoom_in) {
3715     sub_v3_v3v3(dvec, new_ofs, rv3d->ofs);
3716     new_dist = rv3d->dist * (rv3d->dist / new_dist);
3717     add_v3_v3v3(new_ofs, rv3d->ofs, dvec);
3718   }
3719 
3720   /* clamp after because we may have been zooming out */
3721   CLAMP(new_dist, dist_range[0], dist_range[1]);
3722 
3723   /* TODO(campbell): 'is_camera_lock' not currently working well. */
3724   const bool is_camera_lock = ED_view3d_camera_lock_check(v3d, rv3d);
3725   if ((rv3d->persp == RV3D_CAMOB) && (is_camera_lock == false)) {
3726     Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
3727     ED_view3d_persp_switch_from_camera(depsgraph, v3d, rv3d, RV3D_PERSP);
3728   }
3729 
3730   ED_view3d_smooth_view(C,
3731                         v3d,
3732                         region,
3733                         smooth_viewtx,
3734                         &(const V3D_SmoothParams){
3735                             .ofs = new_ofs,
3736                             .dist = &new_dist,
3737                         });
3738 
3739   if (RV3D_LOCK_FLAGS(rv3d) & RV3D_BOXVIEW) {
3740     view3d_boxview_sync(CTX_wm_area(C), region);
3741   }
3742 
3743   return OPERATOR_FINISHED;
3744 }
3745 
VIEW3D_OT_zoom_border(wmOperatorType * ot)3746 void VIEW3D_OT_zoom_border(wmOperatorType *ot)
3747 {
3748   /* identifiers */
3749   ot->name = "Zoom to Border";
3750   ot->description = "Zoom in the view to the nearest object contained in the border";
3751   ot->idname = "VIEW3D_OT_zoom_border";
3752 
3753   /* api callbacks */
3754   ot->invoke = WM_gesture_box_invoke;
3755   ot->exec = view3d_zoom_border_exec;
3756   ot->modal = WM_gesture_box_modal;
3757   ot->cancel = WM_gesture_box_cancel;
3758 
3759   ot->poll = view3d_zoom_or_dolly_poll;
3760 
3761   /* flags */
3762   ot->flag = 0;
3763 
3764   /* properties */
3765   WM_operator_properties_gesture_box_zoom(ot);
3766 }
3767 
3768 /** \} */
3769 
3770 /* -------------------------------------------------------------------- */
3771 /** \name Set Camera Zoom 1:1 Operator
3772  *
3773  * Sets the view to 1:1 camera/render-pixel.
3774  * \{ */
3775 
view3d_set_1_to_1_viewborder(Scene * scene,Depsgraph * depsgraph,ARegion * region,View3D * v3d)3776 static void view3d_set_1_to_1_viewborder(Scene *scene,
3777                                          Depsgraph *depsgraph,
3778                                          ARegion *region,
3779                                          View3D *v3d)
3780 {
3781   RegionView3D *rv3d = region->regiondata;
3782   float size[2];
3783   int im_width = (scene->r.size * scene->r.xsch) / 100;
3784 
3785   ED_view3d_calc_camera_border_size(scene, depsgraph, region, v3d, rv3d, size);
3786 
3787   rv3d->camzoom = BKE_screen_view3d_zoom_from_fac((float)im_width / size[0]);
3788   CLAMP(rv3d->camzoom, RV3D_CAMZOOM_MIN, RV3D_CAMZOOM_MAX);
3789 }
3790 
view3d_zoom_1_to_1_camera_exec(bContext * C,wmOperator * UNUSED (op))3791 static int view3d_zoom_1_to_1_camera_exec(bContext *C, wmOperator *UNUSED(op))
3792 {
3793   Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
3794   Scene *scene = CTX_data_scene(C);
3795 
3796   View3D *v3d;
3797   ARegion *region;
3798 
3799   /* no NULL check is needed, poll checks */
3800   ED_view3d_context_user_region(C, &v3d, &region);
3801 
3802   view3d_set_1_to_1_viewborder(scene, depsgraph, region, v3d);
3803 
3804   WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, v3d);
3805 
3806   return OPERATOR_FINISHED;
3807 }
3808 
VIEW3D_OT_zoom_camera_1_to_1(wmOperatorType * ot)3809 void VIEW3D_OT_zoom_camera_1_to_1(wmOperatorType *ot)
3810 {
3811   /* identifiers */
3812   ot->name = "Zoom Camera 1:1";
3813   ot->description = "Match the camera to 1:1 to the render output";
3814   ot->idname = "VIEW3D_OT_zoom_camera_1_to_1";
3815 
3816   /* api callbacks */
3817   ot->exec = view3d_zoom_1_to_1_camera_exec;
3818   ot->poll = view3d_camera_user_poll;
3819 
3820   /* flags */
3821   ot->flag = 0;
3822 }
3823 
3824 /** \} */
3825 
3826 /* -------------------------------------------------------------------- */
3827 /** \name View Axis Operator
3828  * \{ */
3829 
3830 static const EnumPropertyItem prop_view_items[] = {
3831     {RV3D_VIEW_LEFT, "LEFT", ICON_TRIA_LEFT, "Left", "View from the Left"},
3832     {RV3D_VIEW_RIGHT, "RIGHT", ICON_TRIA_RIGHT, "Right", "View from the Right"},
3833     {RV3D_VIEW_BOTTOM, "BOTTOM", ICON_TRIA_DOWN, "Bottom", "View from the Bottom"},
3834     {RV3D_VIEW_TOP, "TOP", ICON_TRIA_UP, "Top", "View from the Top"},
3835     {RV3D_VIEW_FRONT, "FRONT", 0, "Front", "View from the Front"},
3836     {RV3D_VIEW_BACK, "BACK", 0, "Back", "View from the Back"},
3837     {0, NULL, 0, NULL, NULL},
3838 };
3839 
3840 /* would like to make this a generic function - outside of transform */
3841 
3842 /**
3843  * \param align_to_quat: When not NULL, set the axis relative to this rotation.
3844  */
axis_set_view(bContext * C,View3D * v3d,ARegion * region,const float quat_[4],char view,char view_axis_roll,int perspo,const float * align_to_quat,const int smooth_viewtx)3845 static void axis_set_view(bContext *C,
3846                           View3D *v3d,
3847                           ARegion *region,
3848                           const float quat_[4],
3849                           char view,
3850                           char view_axis_roll,
3851                           int perspo,
3852                           const float *align_to_quat,
3853                           const int smooth_viewtx)
3854 {
3855   RegionView3D *rv3d = region->regiondata; /* no NULL check is needed, poll checks */
3856   float quat[4];
3857   const short orig_persp = rv3d->persp;
3858 
3859   normalize_qt_qt(quat, quat_);
3860 
3861   if (align_to_quat) {
3862     mul_qt_qtqt(quat, quat, align_to_quat);
3863     rv3d->view = view = RV3D_VIEW_USER;
3864     rv3d->view_axis_roll = RV3D_VIEW_AXIS_ROLL_0;
3865   }
3866 
3867   if (align_to_quat == NULL) {
3868     rv3d->view = view;
3869     rv3d->view_axis_roll = view_axis_roll;
3870   }
3871 
3872   if (RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ROTATION) {
3873     ED_region_tag_redraw(region);
3874     return;
3875   }
3876 
3877   if (U.uiflag & USER_AUTOPERSP) {
3878     rv3d->persp = RV3D_VIEW_IS_AXIS(view) ? RV3D_ORTHO : perspo;
3879   }
3880   else if (rv3d->persp == RV3D_CAMOB) {
3881     rv3d->persp = perspo;
3882   }
3883 
3884   if (rv3d->persp == RV3D_CAMOB && v3d->camera) {
3885     /* to camera */
3886     ED_view3d_smooth_view(C,
3887                           v3d,
3888                           region,
3889                           smooth_viewtx,
3890                           &(const V3D_SmoothParams){
3891                               .camera_old = v3d->camera,
3892                               .ofs = rv3d->ofs,
3893                               .quat = quat,
3894                           });
3895   }
3896   else if (orig_persp == RV3D_CAMOB && v3d->camera) {
3897     /* from camera */
3898     float ofs[3], dist;
3899 
3900     copy_v3_v3(ofs, rv3d->ofs);
3901     dist = rv3d->dist;
3902 
3903     /* so we animate _from_ the camera location */
3904     Object *camera_eval = DEG_get_evaluated_object(CTX_data_ensure_evaluated_depsgraph(C),
3905                                                    v3d->camera);
3906     ED_view3d_from_object(camera_eval, rv3d->ofs, NULL, &rv3d->dist, NULL);
3907 
3908     ED_view3d_smooth_view(C,
3909                           v3d,
3910                           region,
3911                           smooth_viewtx,
3912                           &(const V3D_SmoothParams){
3913                               .camera_old = camera_eval,
3914                               .ofs = ofs,
3915                               .quat = quat,
3916                               .dist = &dist,
3917                           });
3918   }
3919   else {
3920     /* rotate around selection */
3921     const float *dyn_ofs_pt = NULL;
3922     float dyn_ofs[3];
3923 
3924     if (U.uiflag & USER_ORBIT_SELECTION) {
3925       if (view3d_orbit_calc_center(C, dyn_ofs)) {
3926         negate_v3(dyn_ofs);
3927         dyn_ofs_pt = dyn_ofs;
3928       }
3929     }
3930 
3931     /* no camera involved */
3932     ED_view3d_smooth_view(C,
3933                           v3d,
3934                           region,
3935                           smooth_viewtx,
3936                           &(const V3D_SmoothParams){
3937                               .quat = quat,
3938                               .dyn_ofs = dyn_ofs_pt,
3939                           });
3940   }
3941 }
3942 
view_axis_exec(bContext * C,wmOperator * op)3943 static int view_axis_exec(bContext *C, wmOperator *op)
3944 {
3945   View3D *v3d;
3946   ARegion *region;
3947   RegionView3D *rv3d;
3948   static int perspo = RV3D_PERSP;
3949   int viewnum;
3950   int view_axis_roll = RV3D_VIEW_AXIS_ROLL_0;
3951   const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
3952 
3953   /* no NULL check is needed, poll checks */
3954   ED_view3d_context_user_region(C, &v3d, &region);
3955   rv3d = region->regiondata;
3956 
3957   ED_view3d_smooth_view_force_finish(C, v3d, region);
3958 
3959   viewnum = RNA_enum_get(op->ptr, "type");
3960 
3961   float align_quat_buf[4];
3962   float *align_quat = NULL;
3963 
3964   if (RNA_boolean_get(op->ptr, "align_active")) {
3965     /* align to active object */
3966     Object *obact = CTX_data_active_object(C);
3967     if (obact != NULL) {
3968       float twmat[3][3];
3969       Object *obedit = CTX_data_edit_object(C);
3970       /* same as transform gizmo when normal is set */
3971       ED_getTransformOrientationMatrix(C, obact, obedit, V3D_AROUND_ACTIVE, twmat);
3972       align_quat = align_quat_buf;
3973       mat3_to_quat(align_quat, twmat);
3974       invert_qt_normalized(align_quat);
3975     }
3976   }
3977 
3978   if (RNA_boolean_get(op->ptr, "relative")) {
3979     float quat_rotate[4];
3980     float quat_test[4];
3981 
3982     if (viewnum == RV3D_VIEW_LEFT) {
3983       axis_angle_to_quat(quat_rotate, rv3d->viewinv[1], -M_PI / 2.0f);
3984     }
3985     else if (viewnum == RV3D_VIEW_RIGHT) {
3986       axis_angle_to_quat(quat_rotate, rv3d->viewinv[1], M_PI / 2.0f);
3987     }
3988     else if (viewnum == RV3D_VIEW_TOP) {
3989       axis_angle_to_quat(quat_rotate, rv3d->viewinv[0], -M_PI / 2.0f);
3990     }
3991     else if (viewnum == RV3D_VIEW_BOTTOM) {
3992       axis_angle_to_quat(quat_rotate, rv3d->viewinv[0], M_PI / 2.0f);
3993     }
3994     else if (viewnum == RV3D_VIEW_FRONT) {
3995       unit_qt(quat_rotate);
3996     }
3997     else if (viewnum == RV3D_VIEW_BACK) {
3998       axis_angle_to_quat(quat_rotate, rv3d->viewinv[0], M_PI);
3999     }
4000     else {
4001       BLI_assert(0);
4002     }
4003 
4004     mul_qt_qtqt(quat_test, rv3d->viewquat, quat_rotate);
4005 
4006     float angle_best = FLT_MAX;
4007     int view_best = -1;
4008     int view_axis_roll_best = -1;
4009     for (int i = RV3D_VIEW_FRONT; i <= RV3D_VIEW_BOTTOM; i++) {
4010       for (int j = RV3D_VIEW_AXIS_ROLL_0; j <= RV3D_VIEW_AXIS_ROLL_270; j++) {
4011         float quat_axis[4];
4012         ED_view3d_quat_from_axis_view(i, j, quat_axis);
4013         if (align_quat) {
4014           mul_qt_qtqt(quat_axis, quat_axis, align_quat);
4015         }
4016         const float angle_test = fabsf(angle_signed_qtqt(quat_axis, quat_test));
4017         if (angle_best > angle_test) {
4018           angle_best = angle_test;
4019           view_best = i;
4020           view_axis_roll_best = j;
4021         }
4022       }
4023     }
4024     if (view_best == -1) {
4025       view_best = RV3D_VIEW_FRONT;
4026       view_axis_roll_best = RV3D_VIEW_AXIS_ROLL_0;
4027     }
4028 
4029     /* Disallow non-upright views in turn-table modes,
4030      * it's too difficult to navigate out of them. */
4031     if ((U.flag & USER_TRACKBALL) == 0) {
4032       if (!ELEM(view_best, RV3D_VIEW_TOP, RV3D_VIEW_BOTTOM)) {
4033         view_axis_roll_best = RV3D_VIEW_AXIS_ROLL_0;
4034       }
4035     }
4036 
4037     viewnum = view_best;
4038     view_axis_roll = view_axis_roll_best;
4039   }
4040 
4041   /* Use this to test if we started out with a camera */
4042   const int nextperspo = (rv3d->persp == RV3D_CAMOB) ? rv3d->lpersp : perspo;
4043   float quat[4];
4044   ED_view3d_quat_from_axis_view(viewnum, view_axis_roll, quat);
4045   axis_set_view(
4046       C, v3d, region, quat, viewnum, view_axis_roll, nextperspo, align_quat, smooth_viewtx);
4047 
4048   perspo = rv3d->persp;
4049 
4050   return OPERATOR_FINISHED;
4051 }
4052 
VIEW3D_OT_view_axis(wmOperatorType * ot)4053 void VIEW3D_OT_view_axis(wmOperatorType *ot)
4054 {
4055   PropertyRNA *prop;
4056 
4057   /* identifiers */
4058   ot->name = "View Axis";
4059   ot->description = "Use a preset viewpoint";
4060   ot->idname = "VIEW3D_OT_view_axis";
4061 
4062   /* api callbacks */
4063   ot->exec = view_axis_exec;
4064   ot->poll = ED_operator_rv3d_user_region_poll;
4065 
4066   /* flags */
4067   ot->flag = 0;
4068 
4069   ot->prop = RNA_def_enum(ot->srna, "type", prop_view_items, 0, "View", "Preset viewpoint to use");
4070   RNA_def_property_flag(ot->prop, PROP_SKIP_SAVE);
4071   prop = RNA_def_boolean(
4072       ot->srna, "align_active", 0, "Align Active", "Align to the active object's axis");
4073   RNA_def_property_flag(prop, PROP_SKIP_SAVE);
4074   prop = RNA_def_boolean(
4075       ot->srna, "relative", 0, "Relative", "Rotate relative to the current orientation");
4076   RNA_def_property_flag(prop, PROP_SKIP_SAVE);
4077 }
4078 
4079 /** \} */
4080 
4081 /* -------------------------------------------------------------------- */
4082 /** \name View Camera Operator
4083  * \{ */
4084 
view_camera_exec(bContext * C,wmOperator * op)4085 static int view_camera_exec(bContext *C, wmOperator *op)
4086 {
4087   View3D *v3d;
4088   ARegion *region;
4089   RegionView3D *rv3d;
4090   const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
4091 
4092   /* no NULL check is needed, poll checks */
4093   ED_view3d_context_user_region(C, &v3d, &region);
4094   rv3d = region->regiondata;
4095 
4096   ED_view3d_smooth_view_force_finish(C, v3d, region);
4097 
4098   if ((RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ANY_TRANSFORM) == 0) {
4099     /* lastview -  */
4100 
4101     ViewLayer *view_layer = CTX_data_view_layer(C);
4102     Scene *scene = CTX_data_scene(C);
4103 
4104     if (rv3d->persp != RV3D_CAMOB) {
4105       Object *ob = OBACT(view_layer);
4106 
4107       if (!rv3d->smooth_timer) {
4108         /* store settings of current view before allowing overwriting with camera view
4109          * only if we're not currently in a view transition */
4110 
4111         ED_view3d_lastview_store(rv3d);
4112       }
4113 
4114 #if 0
4115       if (G.qual == LR_ALTKEY) {
4116         if (oldcamera && is_an_active_object(oldcamera)) {
4117           v3d->camera = oldcamera;
4118         }
4119         handle_view3d_lock();
4120       }
4121 #endif
4122 
4123       /* first get the default camera for the view lock type */
4124       if (v3d->scenelock) {
4125         /* sets the camera view if available */
4126         v3d->camera = scene->camera;
4127       }
4128       else {
4129         /* use scene camera if one is not set (even though we're unlocked) */
4130         if (v3d->camera == NULL) {
4131           v3d->camera = scene->camera;
4132         }
4133       }
4134 
4135       /* if the camera isn't found, check a number of options */
4136       if (v3d->camera == NULL && ob && ob->type == OB_CAMERA) {
4137         v3d->camera = ob;
4138       }
4139 
4140       if (v3d->camera == NULL) {
4141         v3d->camera = BKE_view_layer_camera_find(view_layer);
4142       }
4143 
4144       /* couldn't find any useful camera, bail out */
4145       if (v3d->camera == NULL) {
4146         return OPERATOR_CANCELLED;
4147       }
4148 
4149       /* important these don't get out of sync for locked scenes */
4150       if (v3d->scenelock && scene->camera != v3d->camera) {
4151         scene->camera = v3d->camera;
4152         DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE);
4153       }
4154 
4155       /* finally do snazzy view zooming */
4156       rv3d->persp = RV3D_CAMOB;
4157       ED_view3d_smooth_view(C,
4158                             v3d,
4159                             region,
4160                             smooth_viewtx,
4161                             &(const V3D_SmoothParams){
4162                                 .camera = v3d->camera,
4163                                 .ofs = rv3d->ofs,
4164                                 .quat = rv3d->viewquat,
4165                                 .dist = &rv3d->dist,
4166                                 .lens = &v3d->lens,
4167                             });
4168     }
4169     else {
4170       /* return to settings of last view */
4171       /* does view3d_smooth_view too */
4172       axis_set_view(C,
4173                     v3d,
4174                     region,
4175                     rv3d->lviewquat,
4176                     rv3d->lview,
4177                     rv3d->lview_axis_roll,
4178                     rv3d->lpersp,
4179                     NULL,
4180                     smooth_viewtx);
4181     }
4182   }
4183 
4184   return OPERATOR_FINISHED;
4185 }
4186 
VIEW3D_OT_view_camera(wmOperatorType * ot)4187 void VIEW3D_OT_view_camera(wmOperatorType *ot)
4188 {
4189   /* identifiers */
4190   ot->name = "View Camera";
4191   ot->description = "Toggle the camera view";
4192   ot->idname = "VIEW3D_OT_view_camera";
4193 
4194   /* api callbacks */
4195   ot->exec = view_camera_exec;
4196   ot->poll = ED_operator_rv3d_user_region_poll;
4197 
4198   /* flags */
4199   ot->flag = 0;
4200 }
4201 
4202 /** \} */
4203 
4204 /* -------------------------------------------------------------------- */
4205 /** \name View Orbit Operator
4206  *
4207  * Rotate (orbit) in incremental steps. For interactive orbit see #VIEW3D_OT_rotate.
4208  * \{ */
4209 
4210 enum {
4211   V3D_VIEW_STEPLEFT = 1,
4212   V3D_VIEW_STEPRIGHT,
4213   V3D_VIEW_STEPDOWN,
4214   V3D_VIEW_STEPUP,
4215 };
4216 
4217 static const EnumPropertyItem prop_view_orbit_items[] = {
4218     {V3D_VIEW_STEPLEFT, "ORBITLEFT", 0, "Orbit Left", "Orbit the view around to the Left"},
4219     {V3D_VIEW_STEPRIGHT, "ORBITRIGHT", 0, "Orbit Right", "Orbit the view around to the Right"},
4220     {V3D_VIEW_STEPUP, "ORBITUP", 0, "Orbit Up", "Orbit the view Up"},
4221     {V3D_VIEW_STEPDOWN, "ORBITDOWN", 0, "Orbit Down", "Orbit the view Down"},
4222     {0, NULL, 0, NULL, NULL},
4223 };
4224 
vieworbit_exec(bContext * C,wmOperator * op)4225 static int vieworbit_exec(bContext *C, wmOperator *op)
4226 {
4227   View3D *v3d;
4228   ARegion *region;
4229   RegionView3D *rv3d;
4230   int orbitdir;
4231   char view_opposite;
4232   PropertyRNA *prop_angle = RNA_struct_find_property(op->ptr, "angle");
4233   float angle = RNA_property_is_set(op->ptr, prop_angle) ?
4234                     RNA_property_float_get(op->ptr, prop_angle) :
4235                     DEG2RADF(U.pad_rot_angle);
4236 
4237   /* no NULL check is needed, poll checks */
4238   v3d = CTX_wm_view3d(C);
4239   region = CTX_wm_region(C);
4240   rv3d = region->regiondata;
4241 
4242   /* support for switching to the opposite view (even when in locked views) */
4243   view_opposite = (fabsf(angle) == (float)M_PI) ? ED_view3d_axis_view_opposite(rv3d->view) :
4244                                                   RV3D_VIEW_USER;
4245   orbitdir = RNA_enum_get(op->ptr, "type");
4246 
4247   if ((RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ROTATION) && (view_opposite == RV3D_VIEW_USER)) {
4248     /* no NULL check is needed, poll checks */
4249     ED_view3d_context_user_region(C, &v3d, &region);
4250     rv3d = region->regiondata;
4251   }
4252 
4253   ED_view3d_smooth_view_force_finish(C, v3d, region);
4254 
4255   if ((RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ROTATION) == 0 || (view_opposite != RV3D_VIEW_USER)) {
4256     if ((rv3d->persp != RV3D_CAMOB) || ED_view3d_camera_lock_check(v3d, rv3d)) {
4257       int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
4258       float quat_mul[4];
4259       float quat_new[4];
4260 
4261       if (view_opposite == RV3D_VIEW_USER) {
4262         const Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
4263         ED_view3d_persp_ensure(depsgraph, v3d, region);
4264       }
4265 
4266       if (ELEM(orbitdir, V3D_VIEW_STEPLEFT, V3D_VIEW_STEPRIGHT)) {
4267         if (orbitdir == V3D_VIEW_STEPRIGHT) {
4268           angle = -angle;
4269         }
4270 
4271         /* z-axis */
4272         axis_angle_to_quat_single(quat_mul, 'Z', angle);
4273       }
4274       else {
4275 
4276         if (orbitdir == V3D_VIEW_STEPDOWN) {
4277           angle = -angle;
4278         }
4279 
4280         /* horizontal axis */
4281         axis_angle_to_quat(quat_mul, rv3d->viewinv[0], angle);
4282       }
4283 
4284       mul_qt_qtqt(quat_new, rv3d->viewquat, quat_mul);
4285 
4286       /* avoid precision loss over time */
4287       normalize_qt(quat_new);
4288 
4289       if (view_opposite != RV3D_VIEW_USER) {
4290         rv3d->view = view_opposite;
4291         /* avoid float in-precision, just get a new orientation */
4292         ED_view3d_quat_from_axis_view(view_opposite, rv3d->view_axis_roll, quat_new);
4293       }
4294       else {
4295         rv3d->view = RV3D_VIEW_USER;
4296       }
4297 
4298       float dyn_ofs[3], *dyn_ofs_pt = NULL;
4299 
4300       if (U.uiflag & USER_ORBIT_SELECTION) {
4301         if (view3d_orbit_calc_center(C, dyn_ofs)) {
4302           negate_v3(dyn_ofs);
4303           dyn_ofs_pt = dyn_ofs;
4304         }
4305       }
4306 
4307       ED_view3d_smooth_view(C,
4308                             v3d,
4309                             region,
4310                             smooth_viewtx,
4311                             &(const V3D_SmoothParams){
4312                                 .quat = quat_new,
4313                                 .dyn_ofs = dyn_ofs_pt,
4314                             });
4315 
4316       return OPERATOR_FINISHED;
4317     }
4318   }
4319 
4320   return OPERATOR_CANCELLED;
4321 }
4322 
VIEW3D_OT_view_orbit(wmOperatorType * ot)4323 void VIEW3D_OT_view_orbit(wmOperatorType *ot)
4324 {
4325   PropertyRNA *prop;
4326 
4327   /* identifiers */
4328   ot->name = "View Orbit";
4329   ot->description = "Orbit the view";
4330   ot->idname = "VIEW3D_OT_view_orbit";
4331 
4332   /* api callbacks */
4333   ot->exec = vieworbit_exec;
4334   ot->poll = ED_operator_rv3d_user_region_poll;
4335 
4336   /* flags */
4337   ot->flag = 0;
4338 
4339   /* properties */
4340   prop = RNA_def_float(ot->srna, "angle", 0, -FLT_MAX, FLT_MAX, "Roll", "", -FLT_MAX, FLT_MAX);
4341   RNA_def_property_flag(prop, PROP_SKIP_SAVE);
4342 
4343   ot->prop = RNA_def_enum(
4344       ot->srna, "type", prop_view_orbit_items, 0, "Orbit", "Direction of View Orbit");
4345 }
4346 
4347 /** \} */
4348 
4349 /* -------------------------------------------------------------------- */
4350 /** \name View Roll Operator
4351  * \{ */
4352 
view_roll_angle(ARegion * region,float quat[4],const float orig_quat[4],const float dvec[3],float angle)4353 static void view_roll_angle(
4354     ARegion *region, float quat[4], const float orig_quat[4], const float dvec[3], float angle)
4355 {
4356   RegionView3D *rv3d = region->regiondata;
4357   float quat_mul[4];
4358 
4359   /* camera axis */
4360   axis_angle_normalized_to_quat(quat_mul, dvec, angle);
4361 
4362   mul_qt_qtqt(quat, orig_quat, quat_mul);
4363 
4364   /* avoid precision loss over time */
4365   normalize_qt(quat);
4366 
4367   rv3d->view = RV3D_VIEW_USER;
4368 }
4369 
viewroll_apply(ViewOpsData * vod,int x,int UNUSED (y))4370 static void viewroll_apply(ViewOpsData *vod, int x, int UNUSED(y))
4371 {
4372   float angle = 0.0;
4373 
4374   {
4375     float len1, len2, tot;
4376 
4377     tot = vod->region->winrct.xmax - vod->region->winrct.xmin;
4378     len1 = (vod->region->winrct.xmax - x) / tot;
4379     len2 = (vod->region->winrct.xmax - vod->init.event_xy[0]) / tot;
4380     angle = (len1 - len2) * (float)M_PI * 4.0f;
4381   }
4382 
4383   if (angle != 0.0f) {
4384     view_roll_angle(vod->region, vod->rv3d->viewquat, vod->init.quat, vod->init.mousevec, angle);
4385   }
4386 
4387   if (vod->use_dyn_ofs) {
4388     view3d_orbit_apply_dyn_ofs(
4389         vod->rv3d->ofs, vod->init.ofs, vod->init.quat, vod->rv3d->viewquat, vod->dyn_ofs);
4390   }
4391 
4392   if (RV3D_LOCK_FLAGS(vod->rv3d) & RV3D_BOXVIEW) {
4393     view3d_boxview_sync(vod->area, vod->region);
4394   }
4395 
4396   ED_view3d_camera_lock_sync(vod->depsgraph, vod->v3d, vod->rv3d);
4397 
4398   ED_region_tag_redraw(vod->region);
4399 }
4400 
viewroll_modal(bContext * C,wmOperator * op,const wmEvent * event)4401 static int viewroll_modal(bContext *C, wmOperator *op, const wmEvent *event)
4402 {
4403   ViewOpsData *vod = op->customdata;
4404   short event_code = VIEW_PASS;
4405   bool use_autokey = false;
4406   int ret = OPERATOR_RUNNING_MODAL;
4407 
4408   /* execute the events */
4409   if (event->type == MOUSEMOVE) {
4410     event_code = VIEW_APPLY;
4411   }
4412   else if (event->type == EVT_MODAL_MAP) {
4413     switch (event->val) {
4414       case VIEW_MODAL_CONFIRM:
4415         event_code = VIEW_CONFIRM;
4416         break;
4417       case VIEWROT_MODAL_SWITCH_MOVE:
4418         WM_operator_name_call(C, "VIEW3D_OT_move", WM_OP_INVOKE_DEFAULT, NULL);
4419         event_code = VIEW_CONFIRM;
4420         break;
4421       case VIEWROT_MODAL_SWITCH_ROTATE:
4422         WM_operator_name_call(C, "VIEW3D_OT_rotate", WM_OP_INVOKE_DEFAULT, NULL);
4423         event_code = VIEW_CONFIRM;
4424         break;
4425     }
4426   }
4427   else if (event->type == vod->init.event_type && event->val == KM_RELEASE) {
4428     event_code = VIEW_CONFIRM;
4429   }
4430 
4431   if (event_code == VIEW_APPLY) {
4432     viewroll_apply(vod, event->x, event->y);
4433     if (ED_screen_animation_playing(CTX_wm_manager(C))) {
4434       use_autokey = true;
4435     }
4436   }
4437   else if (event_code == VIEW_CONFIRM) {
4438     ED_view3d_depth_tag_update(vod->rv3d);
4439     use_autokey = true;
4440     ret = OPERATOR_FINISHED;
4441   }
4442 
4443   if (use_autokey) {
4444     ED_view3d_camera_lock_autokey(vod->v3d, vod->rv3d, C, true, false);
4445   }
4446 
4447   if (ret & OPERATOR_FINISHED) {
4448     viewops_data_free(C, op);
4449   }
4450 
4451   return ret;
4452 }
4453 
4454 static const EnumPropertyItem prop_view_roll_items[] = {
4455     {0, "ANGLE", 0, "Roll Angle", "Roll the view using an angle value"},
4456     {V3D_VIEW_STEPLEFT, "LEFT", 0, "Roll Left", "Roll the view around to the Left"},
4457     {V3D_VIEW_STEPRIGHT, "RIGHT", 0, "Roll Right", "Roll the view around to the Right"},
4458     {0, NULL, 0, NULL, NULL},
4459 };
4460 
viewroll_exec(bContext * C,wmOperator * op)4461 static int viewroll_exec(bContext *C, wmOperator *op)
4462 {
4463   View3D *v3d;
4464   RegionView3D *rv3d;
4465   ARegion *region;
4466 
4467   if (op->customdata) {
4468     ViewOpsData *vod = op->customdata;
4469     region = vod->region;
4470     v3d = vod->v3d;
4471   }
4472   else {
4473     ED_view3d_context_user_region(C, &v3d, &region);
4474   }
4475 
4476   rv3d = region->regiondata;
4477   if ((rv3d->persp != RV3D_CAMOB) || ED_view3d_camera_lock_check(v3d, rv3d)) {
4478 
4479     ED_view3d_smooth_view_force_finish(C, v3d, region);
4480 
4481     int type = RNA_enum_get(op->ptr, "type");
4482     float angle = (type == 0) ? RNA_float_get(op->ptr, "angle") : DEG2RADF(U.pad_rot_angle);
4483     float mousevec[3];
4484     float quat_new[4];
4485 
4486     const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
4487 
4488     if (type == V3D_VIEW_STEPLEFT) {
4489       angle = -angle;
4490     }
4491 
4492     normalize_v3_v3(mousevec, rv3d->viewinv[2]);
4493     negate_v3(mousevec);
4494     view_roll_angle(region, quat_new, rv3d->viewquat, mousevec, angle);
4495 
4496     const float *dyn_ofs_pt = NULL;
4497     float dyn_ofs[3];
4498     if (U.uiflag & USER_ORBIT_SELECTION) {
4499       if (view3d_orbit_calc_center(C, dyn_ofs)) {
4500         negate_v3(dyn_ofs);
4501         dyn_ofs_pt = dyn_ofs;
4502       }
4503     }
4504 
4505     ED_view3d_smooth_view(C,
4506                           v3d,
4507                           region,
4508                           smooth_viewtx,
4509                           &(const V3D_SmoothParams){
4510                               .quat = quat_new,
4511                               .dyn_ofs = dyn_ofs_pt,
4512                           });
4513 
4514     viewops_data_free(C, op);
4515     return OPERATOR_FINISHED;
4516   }
4517 
4518   viewops_data_free(C, op);
4519   return OPERATOR_CANCELLED;
4520 }
4521 
viewroll_invoke(bContext * C,wmOperator * op,const wmEvent * event)4522 static int viewroll_invoke(bContext *C, wmOperator *op, const wmEvent *event)
4523 {
4524   ViewOpsData *vod;
4525 
4526   bool use_angle = RNA_enum_get(op->ptr, "type") != 0;
4527 
4528   if (use_angle || RNA_struct_property_is_set(op->ptr, "angle")) {
4529     viewroll_exec(C, op);
4530   }
4531   else {
4532     /* makes op->customdata */
4533     viewops_data_alloc(C, op);
4534     viewops_data_create(C, op, event, viewops_flag_from_prefs());
4535     vod = op->customdata;
4536 
4537     ED_view3d_smooth_view_force_finish(C, vod->v3d, vod->region);
4538 
4539     /* overwrite the mouse vector with the view direction */
4540     normalize_v3_v3(vod->init.mousevec, vod->rv3d->viewinv[2]);
4541     negate_v3(vod->init.mousevec);
4542 
4543     if (event->type == MOUSEROTATE) {
4544       vod->init.event_xy[0] = vod->prev.event_xy[0] = event->x;
4545       viewroll_apply(vod, event->prevx, event->prevy);
4546       ED_view3d_depth_tag_update(vod->rv3d);
4547 
4548       viewops_data_free(C, op);
4549       return OPERATOR_FINISHED;
4550     }
4551 
4552     /* add temp handler */
4553     WM_event_add_modal_handler(C, op);
4554     return OPERATOR_RUNNING_MODAL;
4555   }
4556   return OPERATOR_FINISHED;
4557 }
4558 
viewroll_cancel(bContext * C,wmOperator * op)4559 static void viewroll_cancel(bContext *C, wmOperator *op)
4560 {
4561   viewops_data_free(C, op);
4562 }
4563 
VIEW3D_OT_view_roll(wmOperatorType * ot)4564 void VIEW3D_OT_view_roll(wmOperatorType *ot)
4565 {
4566   PropertyRNA *prop;
4567 
4568   /* identifiers */
4569   ot->name = "View Roll";
4570   ot->description = "Roll the view";
4571   ot->idname = "VIEW3D_OT_view_roll";
4572 
4573   /* api callbacks */
4574   ot->invoke = viewroll_invoke;
4575   ot->exec = viewroll_exec;
4576   ot->modal = viewroll_modal;
4577   ot->poll = ED_operator_rv3d_user_region_poll;
4578   ot->cancel = viewroll_cancel;
4579 
4580   /* flags */
4581   ot->flag = 0;
4582 
4583   /* properties */
4584   ot->prop = prop = RNA_def_float(
4585       ot->srna, "angle", 0, -FLT_MAX, FLT_MAX, "Roll", "", -FLT_MAX, FLT_MAX);
4586   RNA_def_property_flag(prop, PROP_SKIP_SAVE);
4587   prop = RNA_def_enum(ot->srna,
4588                       "type",
4589                       prop_view_roll_items,
4590                       0,
4591                       "Roll Angle Source",
4592                       "How roll angle is calculated");
4593   RNA_def_property_flag(prop, PROP_SKIP_SAVE);
4594 }
4595 
4596 enum {
4597   V3D_VIEW_PANLEFT = 1,
4598   V3D_VIEW_PANRIGHT,
4599   V3D_VIEW_PANDOWN,
4600   V3D_VIEW_PANUP,
4601 };
4602 
4603 static const EnumPropertyItem prop_view_pan_items[] = {
4604     {V3D_VIEW_PANLEFT, "PANLEFT", 0, "Pan Left", "Pan the view to the Left"},
4605     {V3D_VIEW_PANRIGHT, "PANRIGHT", 0, "Pan Right", "Pan the view to the Right"},
4606     {V3D_VIEW_PANUP, "PANUP", 0, "Pan Up", "Pan the view Up"},
4607     {V3D_VIEW_PANDOWN, "PANDOWN", 0, "Pan Down", "Pan the view Down"},
4608     {0, NULL, 0, NULL, NULL},
4609 };
4610 
4611 /** \} */
4612 
4613 /* -------------------------------------------------------------------- */
4614 /** \name View Pan Operator
4615  *
4616  * Move (pan) in incremental steps. For interactive pan see #VIEW3D_OT_move.
4617  * \{ */
4618 
viewpan_invoke(bContext * C,wmOperator * op,const wmEvent * event)4619 static int viewpan_invoke(bContext *C, wmOperator *op, const wmEvent *event)
4620 {
4621   int x = 0, y = 0;
4622   int pandir = RNA_enum_get(op->ptr, "type");
4623 
4624   if (pandir == V3D_VIEW_PANRIGHT) {
4625     x = -32;
4626   }
4627   else if (pandir == V3D_VIEW_PANLEFT) {
4628     x = 32;
4629   }
4630   else if (pandir == V3D_VIEW_PANUP) {
4631     y = -25;
4632   }
4633   else if (pandir == V3D_VIEW_PANDOWN) {
4634     y = 25;
4635   }
4636 
4637   viewops_data_alloc(C, op);
4638   viewops_data_create(C, op, event, (viewops_flag_from_prefs() & ~VIEWOPS_FLAG_ORBIT_SELECT));
4639   ViewOpsData *vod = op->customdata;
4640 
4641   viewmove_apply(vod, vod->prev.event_xy[0] + x, vod->prev.event_xy[1] + y);
4642 
4643   ED_view3d_depth_tag_update(vod->rv3d);
4644   viewops_data_free(C, op);
4645 
4646   return OPERATOR_FINISHED;
4647 }
4648 
VIEW3D_OT_view_pan(wmOperatorType * ot)4649 void VIEW3D_OT_view_pan(wmOperatorType *ot)
4650 {
4651   /* identifiers */
4652   ot->name = "Pan View Direction";
4653   ot->description = "Pan the view in a given direction";
4654   ot->idname = "VIEW3D_OT_view_pan";
4655 
4656   /* api callbacks */
4657   ot->invoke = viewpan_invoke;
4658   ot->poll = view3d_pan_poll;
4659 
4660   /* flags */
4661   ot->flag = 0;
4662 
4663   /* Properties */
4664   ot->prop = RNA_def_enum(
4665       ot->srna, "type", prop_view_pan_items, 0, "Pan", "Direction of View Pan");
4666 }
4667 
4668 /** \} */
4669 
4670 /* -------------------------------------------------------------------- */
4671 /** \name View Toggle Perspective/Orthographic Operator
4672  * \{ */
4673 
viewpersportho_exec(bContext * C,wmOperator * UNUSED (op))4674 static int viewpersportho_exec(bContext *C, wmOperator *UNUSED(op))
4675 {
4676   View3D *v3d_dummy;
4677   ARegion *region;
4678   RegionView3D *rv3d;
4679 
4680   /* no NULL check is needed, poll checks */
4681   ED_view3d_context_user_region(C, &v3d_dummy, &region);
4682   rv3d = region->regiondata;
4683 
4684   /* Could add a separate lock flag for locking persp. */
4685   if ((RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ANY_TRANSFORM) == 0) {
4686     if (rv3d->persp != RV3D_ORTHO) {
4687       rv3d->persp = RV3D_ORTHO;
4688     }
4689     else {
4690       rv3d->persp = RV3D_PERSP;
4691     }
4692     ED_region_tag_redraw(region);
4693   }
4694 
4695   return OPERATOR_FINISHED;
4696 }
4697 
VIEW3D_OT_view_persportho(wmOperatorType * ot)4698 void VIEW3D_OT_view_persportho(wmOperatorType *ot)
4699 {
4700   /* identifiers */
4701   ot->name = "View Persp/Ortho";
4702   ot->description = "Switch the current view from perspective/orthographic projection";
4703   ot->idname = "VIEW3D_OT_view_persportho";
4704 
4705   /* api callbacks */
4706   ot->exec = viewpersportho_exec;
4707   ot->poll = ED_operator_rv3d_user_region_poll;
4708 
4709   /* flags */
4710   ot->flag = 0;
4711 }
4712 
4713 /** \} */
4714 
4715 /* -------------------------------------------------------------------- */
4716 /** \name View Navigate Operator
4717  *
4718  * Wraps walk/fly modes.
4719  * \{ */
4720 
view3d_navigate_invoke(bContext * C,wmOperator * UNUSED (op),const wmEvent * UNUSED (event))4721 static int view3d_navigate_invoke(bContext *C,
4722                                   wmOperator *UNUSED(op),
4723                                   const wmEvent *UNUSED(event))
4724 {
4725   eViewNavigation_Method mode = U.navigation_mode;
4726 
4727   switch (mode) {
4728     case VIEW_NAVIGATION_FLY:
4729       WM_operator_name_call(C, "VIEW3D_OT_fly", WM_OP_INVOKE_DEFAULT, NULL);
4730       break;
4731     case VIEW_NAVIGATION_WALK:
4732     default:
4733       WM_operator_name_call(C, "VIEW3D_OT_walk", WM_OP_INVOKE_DEFAULT, NULL);
4734       break;
4735   }
4736 
4737   return OPERATOR_FINISHED;
4738 }
4739 
VIEW3D_OT_navigate(wmOperatorType * ot)4740 void VIEW3D_OT_navigate(wmOperatorType *ot)
4741 {
4742   /* identifiers */
4743   ot->name = "View Navigation (Walk/Fly)";
4744   ot->description =
4745       "Interactively navigate around the scene (uses the mode (walk/fly) preference)";
4746   ot->idname = "VIEW3D_OT_navigate";
4747 
4748   /* api callbacks */
4749   ot->invoke = view3d_navigate_invoke;
4750   ot->poll = ED_operator_view3d_active;
4751 }
4752 
4753 /** \} */
4754 
4755 /* -------------------------------------------------------------------- */
4756 /** \name Background Image Add Operator
4757  * \{ */
4758 
background_image_camera_from_context(bContext * C)4759 static Camera *background_image_camera_from_context(bContext *C)
4760 {
4761   /* Needed to support drag-and-drop & camera buttons context. */
4762   View3D *v3d = CTX_wm_view3d(C);
4763   if (v3d != NULL) {
4764     if (v3d->camera && v3d->camera->data && v3d->camera->type == OB_CAMERA) {
4765       return v3d->camera->data;
4766     }
4767     return NULL;
4768   }
4769 
4770   return CTX_data_pointer_get_type(C, "camera", &RNA_Camera).data;
4771 }
4772 
background_image_add_exec(bContext * C,wmOperator * UNUSED (op))4773 static int background_image_add_exec(bContext *C, wmOperator *UNUSED(op))
4774 {
4775   Camera *cam = background_image_camera_from_context(C);
4776   BKE_camera_background_image_new(cam);
4777 
4778   return OPERATOR_FINISHED;
4779 }
4780 
background_image_add_invoke(bContext * C,wmOperator * op,const wmEvent * UNUSED (event))4781 static int background_image_add_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
4782 {
4783   Camera *cam = background_image_camera_from_context(C);
4784   Image *ima;
4785   CameraBGImage *bgpic;
4786 
4787   ima = (Image *)WM_operator_drop_load_path(C, op, ID_IM);
4788   /* may be NULL, continue anyway */
4789 
4790   bgpic = BKE_camera_background_image_new(cam);
4791   bgpic->ima = ima;
4792 
4793   cam->flag |= CAM_SHOW_BG_IMAGE;
4794 
4795   WM_event_add_notifier(C, NC_CAMERA | ND_DRAW_RENDER_VIEWPORT, cam);
4796   DEG_id_tag_update(&cam->id, ID_RECALC_COPY_ON_WRITE);
4797 
4798   return OPERATOR_FINISHED;
4799 }
4800 
background_image_add_poll(bContext * C)4801 static bool background_image_add_poll(bContext *C)
4802 {
4803   return background_image_camera_from_context(C) != NULL;
4804 }
4805 
VIEW3D_OT_background_image_add(wmOperatorType * ot)4806 void VIEW3D_OT_background_image_add(wmOperatorType *ot)
4807 {
4808   /* identifiers */
4809   /* note: having key shortcut here is bad practice,
4810    * but for now keep because this displays when dragging an image over the 3D viewport */
4811   ot->name = "Add Background Image";
4812   ot->description = "Add a new background image";
4813   ot->idname = "VIEW3D_OT_background_image_add";
4814 
4815   /* api callbacks */
4816   ot->invoke = background_image_add_invoke;
4817   ot->exec = background_image_add_exec;
4818   ot->poll = background_image_add_poll;
4819 
4820   /* flags */
4821   ot->flag = OPTYPE_UNDO;
4822 
4823   /* properties */
4824   RNA_def_string(ot->srna, "name", "Image", MAX_ID_NAME - 2, "Name", "Image name to assign");
4825   WM_operator_properties_filesel(ot,
4826                                  FILE_TYPE_FOLDER | FILE_TYPE_IMAGE | FILE_TYPE_MOVIE,
4827                                  FILE_SPECIAL,
4828                                  FILE_OPENFILE,
4829                                  WM_FILESEL_FILEPATH | WM_FILESEL_RELPATH,
4830                                  FILE_DEFAULTDISPLAY,
4831                                  FILE_SORT_ALPHA);
4832 }
4833 
4834 /** \} */
4835 
4836 /* -------------------------------------------------------------------- */
4837 /** \name Background Image Remove Operator
4838  * \{ */
4839 
background_image_remove_exec(bContext * C,wmOperator * op)4840 static int background_image_remove_exec(bContext *C, wmOperator *op)
4841 {
4842   Camera *cam = CTX_data_pointer_get_type(C, "camera", &RNA_Camera).data;
4843   const int index = RNA_int_get(op->ptr, "index");
4844   CameraBGImage *bgpic_rem = BLI_findlink(&cam->bg_images, index);
4845 
4846   if (bgpic_rem) {
4847     if (bgpic_rem->source == CAM_BGIMG_SOURCE_IMAGE) {
4848       id_us_min((ID *)bgpic_rem->ima);
4849     }
4850     else if (bgpic_rem->source == CAM_BGIMG_SOURCE_MOVIE) {
4851       id_us_min((ID *)bgpic_rem->clip);
4852     }
4853 
4854     BKE_camera_background_image_remove(cam, bgpic_rem);
4855 
4856     WM_event_add_notifier(C, NC_CAMERA | ND_DRAW_RENDER_VIEWPORT, cam);
4857     DEG_id_tag_update(&cam->id, ID_RECALC_COPY_ON_WRITE);
4858 
4859     return OPERATOR_FINISHED;
4860   }
4861   return OPERATOR_CANCELLED;
4862 }
4863 
VIEW3D_OT_background_image_remove(wmOperatorType * ot)4864 void VIEW3D_OT_background_image_remove(wmOperatorType *ot)
4865 {
4866   /* identifiers */
4867   ot->name = "Remove Background Image";
4868   ot->description = "Remove a background image from the 3D view";
4869   ot->idname = "VIEW3D_OT_background_image_remove";
4870 
4871   /* api callbacks */
4872   ot->exec = background_image_remove_exec;
4873   ot->poll = ED_operator_camera;
4874 
4875   /* flags */
4876   ot->flag = 0;
4877 
4878   /* properties */
4879   RNA_def_int(
4880       ot->srna, "index", 0, 0, INT_MAX, "Index", "Background image index to remove", 0, INT_MAX);
4881 }
4882 
4883 /** \} */
4884 
4885 /* -------------------------------------------------------------------- */
4886 /** \name View Clipping Planes Operator
4887  *
4888  * Draw border or toggle off.
4889  * \{ */
4890 
calc_local_clipping(float clip_local[6][4],const BoundBox * clipbb,const float mat[4][4])4891 static void calc_local_clipping(float clip_local[6][4],
4892                                 const BoundBox *clipbb,
4893                                 const float mat[4][4])
4894 {
4895   BoundBox clipbb_local;
4896   float imat[4][4];
4897 
4898   invert_m4_m4(imat, mat);
4899 
4900   for (int i = 0; i < 8; i++) {
4901     mul_v3_m4v3(clipbb_local.vec[i], imat, clipbb->vec[i]);
4902   }
4903 
4904   ED_view3d_clipping_calc_from_boundbox(clip_local, &clipbb_local, is_negative_m4(mat));
4905 }
4906 
ED_view3d_clipping_local(RegionView3D * rv3d,const float mat[4][4])4907 void ED_view3d_clipping_local(RegionView3D *rv3d, const float mat[4][4])
4908 {
4909   if (rv3d->rflag & RV3D_CLIPPING) {
4910     calc_local_clipping(rv3d->clip_local, rv3d->clipbb, mat);
4911   }
4912 }
4913 
view3d_clipping_exec(bContext * C,wmOperator * op)4914 static int view3d_clipping_exec(bContext *C, wmOperator *op)
4915 {
4916   ARegion *region = CTX_wm_region(C);
4917   RegionView3D *rv3d = CTX_wm_region_view3d(C);
4918   rcti rect;
4919 
4920   WM_operator_properties_border_to_rcti(op, &rect);
4921 
4922   rv3d->rflag |= RV3D_CLIPPING;
4923   rv3d->clipbb = MEM_callocN(sizeof(BoundBox), "clipbb");
4924 
4925   /* NULL object because we don't want it in object space */
4926   ED_view3d_clipping_calc(rv3d->clipbb, rv3d->clip, region, NULL, &rect);
4927 
4928   return OPERATOR_FINISHED;
4929 }
4930 
view3d_clipping_invoke(bContext * C,wmOperator * op,const wmEvent * event)4931 static int view3d_clipping_invoke(bContext *C, wmOperator *op, const wmEvent *event)
4932 {
4933   RegionView3D *rv3d = CTX_wm_region_view3d(C);
4934   ARegion *region = CTX_wm_region(C);
4935 
4936   if (rv3d->rflag & RV3D_CLIPPING) {
4937     rv3d->rflag &= ~RV3D_CLIPPING;
4938     ED_region_tag_redraw(region);
4939     if (rv3d->clipbb) {
4940       MEM_freeN(rv3d->clipbb);
4941     }
4942     rv3d->clipbb = NULL;
4943     return OPERATOR_FINISHED;
4944   }
4945   return WM_gesture_box_invoke(C, op, event);
4946 }
4947 
VIEW3D_OT_clip_border(wmOperatorType * ot)4948 void VIEW3D_OT_clip_border(wmOperatorType *ot)
4949 {
4950 
4951   /* identifiers */
4952   ot->name = "Clipping Region";
4953   ot->description = "Set the view clipping region";
4954   ot->idname = "VIEW3D_OT_clip_border";
4955 
4956   /* api callbacks */
4957   ot->invoke = view3d_clipping_invoke;
4958   ot->exec = view3d_clipping_exec;
4959   ot->modal = WM_gesture_box_modal;
4960   ot->cancel = WM_gesture_box_cancel;
4961 
4962   ot->poll = ED_operator_region_view3d_active;
4963 
4964   /* flags */
4965   ot->flag = 0;
4966 
4967   /* properties */
4968   WM_operator_properties_border(ot);
4969 }
4970 
4971 /** \} */
4972 
4973 /* -------------------------------------------------------------------- */
4974 /** \name Set Cursor Operator
4975  * \{ */
4976 
4977 /* cursor position in vec, result in vec, mval in region coords */
4978 /* note: cannot use event->mval here (called by object_add() */
ED_view3d_cursor3d_position(bContext * C,const int mval[2],const bool use_depth,float cursor_co[3])4979 void ED_view3d_cursor3d_position(bContext *C,
4980                                  const int mval[2],
4981                                  const bool use_depth,
4982                                  float cursor_co[3])
4983 {
4984   ARegion *region = CTX_wm_region(C);
4985   View3D *v3d = CTX_wm_view3d(C);
4986   RegionView3D *rv3d = region->regiondata;
4987   bool flip;
4988   bool depth_used = false;
4989 
4990   /* normally the caller should ensure this,
4991    * but this is called from areas that aren't already dealing with the viewport */
4992   if (rv3d == NULL) {
4993     return;
4994   }
4995 
4996   ED_view3d_calc_zfac(rv3d, cursor_co, &flip);
4997 
4998   /* Reset the depth based on the view offset (we _know_ the offset is in front of us). */
4999   if (flip) {
5000     negate_v3_v3(cursor_co, rv3d->ofs);
5001     /* re initialize, no need to check flip again */
5002     ED_view3d_calc_zfac(rv3d, cursor_co, NULL /* &flip */);
5003   }
5004 
5005   if (use_depth) { /* maybe this should be accessed some other way */
5006     struct Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
5007 
5008     view3d_operator_needs_opengl(C);
5009     if (ED_view3d_autodist(depsgraph, region, v3d, mval, cursor_co, true, NULL)) {
5010       depth_used = true;
5011     }
5012   }
5013 
5014   if (depth_used == false) {
5015     float depth_pt[3];
5016     copy_v3_v3(depth_pt, cursor_co);
5017     ED_view3d_win_to_3d_int(v3d, region, depth_pt, mval, cursor_co);
5018   }
5019 }
5020 
ED_view3d_cursor3d_position_rotation(bContext * C,const int mval[2],const bool use_depth,enum eV3DCursorOrient orientation,float cursor_co[3],float cursor_quat[4])5021 void ED_view3d_cursor3d_position_rotation(bContext *C,
5022                                           const int mval[2],
5023                                           const bool use_depth,
5024                                           enum eV3DCursorOrient orientation,
5025                                           float cursor_co[3],
5026                                           float cursor_quat[4])
5027 {
5028   Scene *scene = CTX_data_scene(C);
5029   View3D *v3d = CTX_wm_view3d(C);
5030   ARegion *region = CTX_wm_region(C);
5031   RegionView3D *rv3d = region->regiondata;
5032 
5033   /* XXX, caller should check. */
5034   if (rv3d == NULL) {
5035     return;
5036   }
5037 
5038   ED_view3d_cursor3d_position(C, mval, use_depth, cursor_co);
5039 
5040   if (orientation == V3D_CURSOR_ORIENT_NONE) {
5041     /* pass */
5042   }
5043   else if (orientation == V3D_CURSOR_ORIENT_VIEW) {
5044     copy_qt_qt(cursor_quat, rv3d->viewquat);
5045     cursor_quat[0] *= -1.0f;
5046   }
5047   else if (orientation == V3D_CURSOR_ORIENT_XFORM) {
5048     float mat[3][3];
5049     ED_transform_calc_orientation_from_type(C, mat);
5050     mat3_to_quat(cursor_quat, mat);
5051   }
5052   else if (orientation == V3D_CURSOR_ORIENT_GEOM) {
5053     copy_qt_qt(cursor_quat, rv3d->viewquat);
5054     cursor_quat[0] *= -1.0f;
5055 
5056     const float mval_fl[2] = {UNPACK2(mval)};
5057     float ray_no[3];
5058     float ray_co[3];
5059 
5060     struct SnapObjectContext *snap_context = ED_transform_snap_object_context_create_view3d(
5061         scene, 0, region, v3d);
5062 
5063     float obmat[4][4];
5064     Object *ob_dummy = NULL;
5065     float dist_px = 0;
5066     if (ED_transform_snap_object_project_view3d_ex(snap_context,
5067                                                    CTX_data_ensure_evaluated_depsgraph(C),
5068                                                    SCE_SNAP_MODE_FACE,
5069                                                    &(const struct SnapObjectParams){
5070                                                        .snap_select = SNAP_ALL,
5071                                                        .use_object_edit_cage = false,
5072                                                        .use_occlusion_test = true,
5073                                                    },
5074                                                    mval_fl,
5075                                                    NULL,
5076                                                    &dist_px,
5077                                                    ray_co,
5078                                                    ray_no,
5079                                                    NULL,
5080                                                    &ob_dummy,
5081                                                    obmat) != 0) {
5082       if (use_depth) {
5083         copy_v3_v3(cursor_co, ray_co);
5084       }
5085 
5086       /* Math normal (Z). */
5087       {
5088         float tquat[4];
5089         float z_src[3] = {0, 0, 1};
5090         mul_qt_v3(cursor_quat, z_src);
5091         rotation_between_vecs_to_quat(tquat, z_src, ray_no);
5092         mul_qt_qtqt(cursor_quat, tquat, cursor_quat);
5093       }
5094 
5095       /* Match object matrix (X). */
5096       {
5097         const float ortho_axis_dot[3] = {
5098             dot_v3v3(ray_no, obmat[0]),
5099             dot_v3v3(ray_no, obmat[1]),
5100             dot_v3v3(ray_no, obmat[2]),
5101         };
5102         const int ortho_axis = axis_dominant_v3_ortho_single(ortho_axis_dot);
5103 
5104         float tquat_best[4];
5105         float angle_best = -1.0f;
5106 
5107         float tan_dst[3];
5108         project_plane_v3_v3v3(tan_dst, obmat[ortho_axis], ray_no);
5109         normalize_v3(tan_dst);
5110 
5111         /* As the tangent is arbitrary from the users point of view,
5112          * make the cursor 'roll' on the shortest angle.
5113          * otherwise this can cause noticeable 'flipping', see T72419. */
5114         for (int axis = 0; axis < 2; axis++) {
5115           float tan_src[3] = {0, 0, 0};
5116           tan_src[axis] = 1.0f;
5117           mul_qt_v3(cursor_quat, tan_src);
5118 
5119           for (int axis_sign = 0; axis_sign < 2; axis_sign++) {
5120             float tquat_test[4];
5121             rotation_between_vecs_to_quat(tquat_test, tan_src, tan_dst);
5122             const float angle_test = angle_normalized_qt(tquat_test);
5123             if (angle_test < angle_best || angle_best == -1.0f) {
5124               angle_best = angle_test;
5125               copy_qt_qt(tquat_best, tquat_test);
5126             }
5127             negate_v3(tan_src);
5128           }
5129         }
5130         mul_qt_qtqt(cursor_quat, tquat_best, cursor_quat);
5131       }
5132     }
5133     ED_transform_snap_object_context_destroy(snap_context);
5134   }
5135 }
5136 
ED_view3d_cursor3d_update(bContext * C,const int mval[2],const bool use_depth,enum eV3DCursorOrient orientation)5137 void ED_view3d_cursor3d_update(bContext *C,
5138                                const int mval[2],
5139                                const bool use_depth,
5140                                enum eV3DCursorOrient orientation)
5141 {
5142   Scene *scene = CTX_data_scene(C);
5143   View3D *v3d = CTX_wm_view3d(C);
5144   ARegion *region = CTX_wm_region(C);
5145   RegionView3D *rv3d = region->regiondata;
5146 
5147   View3DCursor *cursor_curr = &scene->cursor;
5148   View3DCursor cursor_prev = *cursor_curr;
5149 
5150   {
5151     float quat[4], quat_prev[4];
5152     BKE_scene_cursor_rot_to_quat(cursor_curr, quat);
5153     copy_qt_qt(quat_prev, quat);
5154     ED_view3d_cursor3d_position_rotation(
5155         C, mval, use_depth, orientation, cursor_curr->location, quat);
5156 
5157     if (!equals_v4v4(quat_prev, quat)) {
5158       if ((cursor_curr->rotation_mode == ROT_MODE_AXISANGLE) && RV3D_VIEW_IS_AXIS(rv3d->view)) {
5159         float tmat[3][3], cmat[3][3];
5160         quat_to_mat3(tmat, quat);
5161         negate_v3_v3(cursor_curr->rotation_axis, tmat[2]);
5162         axis_angle_to_mat3(cmat, cursor_curr->rotation_axis, 0.0f);
5163         cursor_curr->rotation_angle = angle_signed_on_axis_v3v3_v3(
5164             cmat[0], tmat[0], cursor_curr->rotation_axis);
5165       }
5166       else {
5167         BKE_scene_cursor_quat_to_rot(cursor_curr, quat, true);
5168       }
5169     }
5170   }
5171 
5172   /* offset the cursor lock to avoid jumping to new offset */
5173   if (v3d->ob_center_cursor) {
5174     if (U.uiflag & USER_LOCK_CURSOR_ADJUST) {
5175 
5176       float co_2d_curr[2], co_2d_prev[2];
5177 
5178       if ((ED_view3d_project_float_global(
5179                region, cursor_prev.location, co_2d_prev, V3D_PROJ_TEST_NOP) == V3D_PROJ_RET_OK) &&
5180           (ED_view3d_project_float_global(
5181                region, cursor_curr->location, co_2d_curr, V3D_PROJ_TEST_NOP) == V3D_PROJ_RET_OK)) {
5182         rv3d->ofs_lock[0] += (co_2d_curr[0] - co_2d_prev[0]) / (region->winx * 0.5f);
5183         rv3d->ofs_lock[1] += (co_2d_curr[1] - co_2d_prev[1]) / (region->winy * 0.5f);
5184       }
5185     }
5186     else {
5187       /* Cursor may be outside of the view,
5188        * prevent it getting 'lost', see: T40353 & T45301 */
5189       zero_v2(rv3d->ofs_lock);
5190     }
5191   }
5192 
5193   if (v3d->localvd) {
5194     WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, v3d);
5195   }
5196   else {
5197     WM_event_add_notifier(C, NC_SCENE | NA_EDITED, scene);
5198   }
5199 
5200   {
5201     struct wmMsgBus *mbus = CTX_wm_message_bus(C);
5202     wmMsgParams_RNA msg_key_params = {{0}};
5203     RNA_pointer_create(&scene->id, &RNA_View3DCursor, &scene->cursor, &msg_key_params.ptr);
5204     WM_msg_publish_rna_params(mbus, &msg_key_params);
5205   }
5206 
5207   DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE);
5208 }
5209 
view3d_cursor3d_invoke(bContext * C,wmOperator * op,const wmEvent * event)5210 static int view3d_cursor3d_invoke(bContext *C, wmOperator *op, const wmEvent *event)
5211 {
5212   bool use_depth = (U.uiflag & USER_DEPTH_CURSOR);
5213   {
5214     PropertyRNA *prop = RNA_struct_find_property(op->ptr, "use_depth");
5215     if (RNA_property_is_set(op->ptr, prop)) {
5216       use_depth = RNA_property_boolean_get(op->ptr, prop);
5217     }
5218     else {
5219       RNA_property_boolean_set(op->ptr, prop, use_depth);
5220     }
5221   }
5222   const enum eV3DCursorOrient orientation = RNA_enum_get(op->ptr, "orientation");
5223   ED_view3d_cursor3d_update(C, event->mval, use_depth, orientation);
5224 
5225   return OPERATOR_FINISHED;
5226 }
5227 
VIEW3D_OT_cursor3d(wmOperatorType * ot)5228 void VIEW3D_OT_cursor3d(wmOperatorType *ot)
5229 {
5230 
5231   /* identifiers */
5232   ot->name = "Set 3D Cursor";
5233   ot->description = "Set the location of the 3D cursor";
5234   ot->idname = "VIEW3D_OT_cursor3d";
5235 
5236   /* api callbacks */
5237   ot->invoke = view3d_cursor3d_invoke;
5238 
5239   ot->poll = ED_operator_region_view3d_active;
5240 
5241   /* flags */
5242   //  ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
5243 
5244   PropertyRNA *prop;
5245   static const EnumPropertyItem orientation_items[] = {
5246       {V3D_CURSOR_ORIENT_NONE, "NONE", 0, "None", "Leave orientation unchanged"},
5247       {V3D_CURSOR_ORIENT_VIEW, "VIEW", 0, "View", "Orient to the viewport"},
5248       {V3D_CURSOR_ORIENT_XFORM,
5249        "XFORM",
5250        0,
5251        "Transform",
5252        "Orient to the current transform setting"},
5253       {V3D_CURSOR_ORIENT_GEOM, "GEOM", 0, "Geometry", "Match the surface normal"},
5254       {0, NULL, 0, NULL, NULL},
5255   };
5256 
5257   prop = RNA_def_boolean(
5258       ot->srna, "use_depth", true, "Surface Project", "Project onto the surface");
5259   RNA_def_property_flag(prop, PROP_SKIP_SAVE);
5260 
5261   prop = RNA_def_enum(ot->srna,
5262                       "orientation",
5263                       orientation_items,
5264                       V3D_CURSOR_ORIENT_VIEW,
5265                       "Orientation",
5266                       "Preset viewpoint to use");
5267   RNA_def_property_flag(prop, PROP_SKIP_SAVE);
5268 }
5269 
5270 /** \} */
5271 
5272 /* -------------------------------------------------------------------- */
5273 /** \name Toggle Shading Operator
5274  * \{ */
5275 
5276 static const EnumPropertyItem prop_shading_type_items[] = {
5277     {OB_WIRE, "WIREFRAME", 0, "Wireframe", "Toggle wireframe shading"},
5278     {OB_SOLID, "SOLID", 0, "Solid", "Toggle solid shading"},
5279     {OB_MATERIAL, "MATERIAL", 0, "LookDev", "Toggle lookdev shading"},
5280     {OB_RENDER, "RENDERED", 0, "Rendered", "Toggle rendered shading"},
5281     {0, NULL, 0, NULL, NULL},
5282 };
5283 
toggle_shading_exec(bContext * C,wmOperator * op)5284 static int toggle_shading_exec(bContext *C, wmOperator *op)
5285 {
5286   Main *bmain = CTX_data_main(C);
5287   View3D *v3d = CTX_wm_view3d(C);
5288   ScrArea *area = CTX_wm_area(C);
5289   int type = RNA_enum_get(op->ptr, "type");
5290 
5291   if (type == OB_SOLID) {
5292     if (v3d->shading.type != type) {
5293       v3d->shading.type = type;
5294     }
5295     else if (v3d->shading.type == OB_WIRE) {
5296       v3d->shading.type = OB_SOLID;
5297     }
5298     else {
5299       v3d->shading.type = OB_WIRE;
5300     }
5301   }
5302   else {
5303     char *prev_type = ((type == OB_WIRE) ? &v3d->shading.prev_type_wire : &v3d->shading.prev_type);
5304     if (v3d->shading.type == type) {
5305       if (*prev_type == type || !ELEM(*prev_type, OB_WIRE, OB_SOLID, OB_MATERIAL, OB_RENDER)) {
5306         *prev_type = OB_SOLID;
5307       }
5308       v3d->shading.type = *prev_type;
5309     }
5310     else {
5311       *prev_type = v3d->shading.type;
5312       v3d->shading.type = type;
5313     }
5314   }
5315 
5316   ED_view3d_shade_update(bmain, v3d, area);
5317   WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, v3d);
5318 
5319   return OPERATOR_FINISHED;
5320 }
5321 
VIEW3D_OT_toggle_shading(wmOperatorType * ot)5322 void VIEW3D_OT_toggle_shading(wmOperatorType *ot)
5323 {
5324   PropertyRNA *prop;
5325 
5326   /* identifiers */
5327   ot->name = "Toggle Shading Type";
5328   ot->description = "Toggle shading type in 3D viewport";
5329   ot->idname = "VIEW3D_OT_toggle_shading";
5330 
5331   /* api callbacks */
5332   ot->exec = toggle_shading_exec;
5333   ot->poll = ED_operator_view3d_active;
5334 
5335   prop = RNA_def_enum(
5336       ot->srna, "type", prop_shading_type_items, 0, "Type", "Shading type to toggle");
5337   RNA_def_property_flag(prop, PROP_SKIP_SAVE);
5338 }
5339 
5340 /** \} */
5341 
5342 /* -------------------------------------------------------------------- */
5343 /** \name Toggle XRay
5344  * \{ */
5345 
toggle_xray_exec(bContext * C,wmOperator * op)5346 static int toggle_xray_exec(bContext *C, wmOperator *op)
5347 {
5348   View3D *v3d = CTX_wm_view3d(C);
5349   ScrArea *area = CTX_wm_area(C);
5350   Object *obact = CTX_data_active_object(C);
5351 
5352   if (obact && ((obact->mode & OB_MODE_POSE) ||
5353                 ((obact->mode & OB_MODE_WEIGHT_PAINT) && BKE_object_pose_armature_get(obact)))) {
5354     v3d->overlay.flag ^= V3D_OVERLAY_BONE_SELECT;
5355   }
5356   else {
5357     const bool xray_active = ((obact && (obact->mode & OB_MODE_EDIT)) ||
5358                               ELEM(v3d->shading.type, OB_WIRE, OB_SOLID));
5359 
5360     if (v3d->shading.type == OB_WIRE) {
5361       v3d->shading.flag ^= V3D_SHADING_XRAY_WIREFRAME;
5362     }
5363     else {
5364       v3d->shading.flag ^= V3D_SHADING_XRAY;
5365     }
5366     if (!xray_active) {
5367       BKE_report(op->reports, RPT_INFO, "X-Ray not available in current mode");
5368     }
5369   }
5370 
5371   ED_area_tag_redraw(area);
5372 
5373   return OPERATOR_FINISHED;
5374 }
5375 
VIEW3D_OT_toggle_xray(wmOperatorType * ot)5376 void VIEW3D_OT_toggle_xray(wmOperatorType *ot)
5377 {
5378   /* identifiers */
5379   ot->name = "Toggle X-Ray";
5380   ot->idname = "VIEW3D_OT_toggle_xray";
5381   ot->description = "Transparent scene display. Allow selecting through items";
5382 
5383   /* api callbacks */
5384   ot->exec = toggle_xray_exec;
5385   ot->poll = ED_operator_view3d_active;
5386 }
5387 
5388 /** \} */
5389