2 * ***** BEGIN GPL LICENSE BLOCK *****
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software Foundation,
16 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * The Original Code is Copyright (C) 2008 Blender Foundation.
19 * All rights reserved.
22 * Contributor(s): Blender Foundation
24 * ***** END GPL LICENSE BLOCK *****
27 /** \file blender/editors/space_view3d/view3d_edit.c
36 #include "DNA_armature_types.h"
37 #include "DNA_curve_types.h"
38 #include "DNA_object_types.h"
39 #include "DNA_scene_types.h"
40 #include "DNA_gpencil_types.h"
42 #include "MEM_guardedalloc.h"
44 #include "BLI_bitmap_draw_2d.h"
45 #include "BLI_blenlib.h"
46 #include "BLI_kdopbvh.h"
48 #include "BLI_utildefines.h"
50 #include "BKE_armature.h"
51 #include "BKE_camera.h"
52 #include "BKE_context.h"
54 #include "BKE_library.h"
55 #include "BKE_object.h"
56 #include "BKE_paint.h"
57 #include "BKE_report.h"
58 #include "BKE_scene.h"
59 #include "BKE_screen.h"
60 #include "BKE_action.h"
61 #include "BKE_depsgraph.h" /* for ED_view3d_camera_lock_sync */
65 #include "BIF_glutil.h"
70 #include "RNA_access.h"
71 #include "RNA_define.h"
73 #include "ED_armature.h"
74 #include "ED_particle.h"
75 #include "ED_keyframing.h"
76 #include "ED_screen.h"
77 #include "ED_transform.h"
79 #include "ED_gpencil.h"
80 #include "ED_view3d.h"
82 #include "UI_resources.h"
84 #include "PIL_time.h" /* smoothview */
86 #include "view3d_intern.h" /* own include */
88 static bool view3d_ensure_persp(struct View3D *v3d, ARegion *ar);
90 bool ED_view3d_offset_lock_check(const View3D *v3d, const RegionView3D *rv3d)
92 return (rv3d->persp != RV3D_CAMOB) && (v3d->ob_centre_cursor || v3d->ob_centre);
95 /* ********************** view3d_edit: view manipulations ********************* */
98 * \return true when the view-port is locked to its camera.
100 bool ED_view3d_camera_lock_check(const View3D *v3d, const RegionView3D *rv3d)
102 return ((v3d->camera) &&
103 (!ID_IS_LINKED_DATABLOCK(v3d->camera)) &&
104 (v3d->flag2 & V3D_LOCK_CAMERA) &&
105 (rv3d->persp == RV3D_CAMOB));
109 * Apply the camera object transformation to the view-port.
110 * (needed so we can use regular view-port manipulation operators, that sync back to the camera).
112 void ED_view3d_camera_lock_init_ex(View3D *v3d, RegionView3D *rv3d, const bool calc_dist)
114 if (ED_view3d_camera_lock_check(v3d, rv3d)) {
116 /* using a fallback dist is OK here since ED_view3d_from_object() compensates for it */
117 rv3d->dist = ED_view3d_offset_distance(v3d->camera->obmat, rv3d->ofs, VIEW3D_DIST_FALLBACK);
119 ED_view3d_from_object(v3d->camera, rv3d->ofs, rv3d->viewquat, &rv3d->dist, NULL);
123 void ED_view3d_camera_lock_init(View3D *v3d, RegionView3D *rv3d)
125 ED_view3d_camera_lock_init_ex(v3d, rv3d, true);
129 * Apply the view-port transformation back to the camera object.
131 * \return true if the camera is moved.
133 bool ED_view3d_camera_lock_sync(View3D *v3d, RegionView3D *rv3d)
135 if (ED_view3d_camera_lock_check(v3d, rv3d)) {
136 ObjectTfmProtectedChannels obtfm;
139 if ((U.uiflag & USER_CAM_LOCK_NO_PARENT) == 0 && (root_parent = v3d->camera->parent)) {
143 float view_mat[4][4];
144 float diff_mat[4][4];
145 float parent_mat[4][4];
147 while (root_parent->parent) {
148 root_parent = root_parent->parent;
151 ED_view3d_to_m4(view_mat, rv3d->ofs, rv3d->viewquat, rv3d->dist);
153 normalize_m4_m4(tmat, v3d->camera->obmat);
155 invert_m4_m4(imat, tmat);
156 mul_m4_m4m4(diff_mat, view_mat, imat);
158 mul_m4_m4m4(parent_mat, diff_mat, root_parent->obmat);
160 BKE_object_tfm_protected_backup(root_parent, &obtfm);
161 BKE_object_apply_mat4(root_parent, parent_mat, true, false);
162 BKE_object_tfm_protected_restore(root_parent, &obtfm, root_parent->protectflag);
164 ob_update = v3d->camera;
166 DAG_id_tag_update(&ob_update->id, OB_RECALC_OB);
167 WM_main_add_notifier(NC_OBJECT | ND_TRANSFORM, ob_update);
168 ob_update = ob_update->parent;
172 /* always maintain the same scale */
173 const short protect_scale_all = (OB_LOCK_SCALEX | OB_LOCK_SCALEY | OB_LOCK_SCALEZ);
174 BKE_object_tfm_protected_backup(v3d->camera, &obtfm);
175 ED_view3d_to_object(v3d->camera, rv3d->ofs, rv3d->viewquat, rv3d->dist);
176 BKE_object_tfm_protected_restore(v3d->camera, &obtfm, v3d->camera->protectflag | protect_scale_all);
178 DAG_id_tag_update(&v3d->camera->id, OB_RECALC_OB);
179 WM_main_add_notifier(NC_OBJECT | ND_TRANSFORM, v3d->camera);
189 bool ED_view3d_camera_autokey(
190 Scene *scene, ID *id_key,
191 struct bContext *C, const bool do_rotate, const bool do_translate)
193 if (autokeyframe_cfra_can_key(scene, id_key)) {
194 const float cfra = (float)CFRA;
195 ListBase dsources = {NULL, NULL};
197 /* add data-source override for the camera object */
198 ANIM_relative_keyingset_add_source(&dsources, id_key, NULL, NULL);
201 * 1) on the first frame
202 * 2) on each subsequent frame
203 * TODO: need to check in future that frame changed before doing this
206 struct KeyingSet *ks = ANIM_get_keyingset_for_autokeying(scene, ANIM_KS_ROTATION_ID);
207 ANIM_apply_keyingset(C, &dsources, NULL, ks, MODIFYKEY_MODE_INSERT, cfra);
210 struct KeyingSet *ks = ANIM_get_keyingset_for_autokeying(scene, ANIM_KS_LOCATION_ID);
211 ANIM_apply_keyingset(C, &dsources, NULL, ks, MODIFYKEY_MODE_INSERT, cfra);
215 BLI_freelistN(&dsources);
225 * Call after modifying a locked view.
227 * \note Not every view edit currently auto-keys (numpad for eg),
228 * this is complicated because of smoothview.
230 bool ED_view3d_camera_lock_autokey(
231 View3D *v3d, RegionView3D *rv3d,
232 struct bContext *C, const bool do_rotate, const bool do_translate)
234 /* similar to ED_view3d_cameracontrol_update */
235 if (ED_view3d_camera_lock_check(v3d, rv3d)) {
236 Scene *scene = CTX_data_scene(C);
239 if ((U.uiflag & USER_CAM_LOCK_NO_PARENT) == 0 && (root_parent = v3d->camera->parent)) {
240 while (root_parent->parent) {
241 root_parent = root_parent->parent;
243 id_key = &root_parent->id;
246 id_key = &v3d->camera->id;
249 return ED_view3d_camera_autokey(scene, id_key, C, do_rotate, do_translate);
257 * For viewport operators that exit camera persp.
259 * \note This differs from simply setting ``rv3d->persp = persp`` because it
260 * sets the ``ofs`` and ``dist`` values of the viewport so it matches the camera,
261 * otherwise switching out of camera view may jump to a different part of the scene.
263 static void view3d_persp_switch_from_camera(View3D *v3d, RegionView3D *rv3d, const char persp)
265 BLI_assert(rv3d->persp == RV3D_CAMOB);
266 BLI_assert(persp != RV3D_CAMOB);
269 rv3d->dist = ED_view3d_offset_distance(v3d->camera->obmat, rv3d->ofs, VIEW3D_DIST_FALLBACK);
270 ED_view3d_from_object(v3d->camera, rv3d->ofs, rv3d->viewquat, &rv3d->dist, NULL);
273 if (!ED_view3d_camera_lock_check(v3d, rv3d)) {
278 /* ********************* box view support ***************** */
280 static void view3d_boxview_clip(ScrArea *sa)
283 BoundBox *bb = MEM_callocN(sizeof(BoundBox), "clipbb");
285 float x1 = 0.0f, y1 = 0.0f, z1 = 0.0f, ofs[3] = {0.0f, 0.0f, 0.0f};
288 /* create bounding box */
289 for (ar = sa->regionbase.first; ar; ar = ar->next) {
290 if (ar->regiontype == RGN_TYPE_WINDOW) {
291 RegionView3D *rv3d = ar->regiondata;
293 if (rv3d->viewlock & RV3D_BOXCLIP) {
294 if (ELEM(rv3d->view, RV3D_VIEW_TOP, RV3D_VIEW_BOTTOM)) {
295 if (ar->winx > ar->winy) x1 = rv3d->dist;
296 else x1 = ar->winx * rv3d->dist / ar->winy;
298 if (ar->winx > ar->winy) y1 = ar->winy * rv3d->dist / ar->winx;
299 else y1 = rv3d->dist;
300 copy_v2_v2(ofs, rv3d->ofs);
302 else if (ELEM(rv3d->view, RV3D_VIEW_FRONT, RV3D_VIEW_BACK)) {
303 ofs[2] = rv3d->ofs[2];
305 if (ar->winx > ar->winy) z1 = ar->winy * rv3d->dist / ar->winx;
306 else z1 = rv3d->dist;
312 for (val = 0; val < 8; val++) {
313 if (ELEM(val, 0, 3, 4, 7))
314 bb->vec[val][0] = -x1 - ofs[0];
316 bb->vec[val][0] = x1 - ofs[0];
318 if (ELEM(val, 0, 1, 4, 5))
319 bb->vec[val][1] = -y1 - ofs[1];
321 bb->vec[val][1] = y1 - ofs[1];
324 bb->vec[val][2] = -z1 - ofs[2];
326 bb->vec[val][2] = z1 - ofs[2];
329 /* normals for plane equations */
330 normal_tri_v3(clip[0], bb->vec[0], bb->vec[1], bb->vec[4]);
331 normal_tri_v3(clip[1], bb->vec[1], bb->vec[2], bb->vec[5]);
332 normal_tri_v3(clip[2], bb->vec[2], bb->vec[3], bb->vec[6]);
333 normal_tri_v3(clip[3], bb->vec[3], bb->vec[0], bb->vec[7]);
334 normal_tri_v3(clip[4], bb->vec[4], bb->vec[5], bb->vec[6]);
335 normal_tri_v3(clip[5], bb->vec[0], bb->vec[2], bb->vec[1]);
337 /* then plane equations */
338 for (val = 0; val < 6; val++) {
339 clip[val][3] = -dot_v3v3(clip[val], bb->vec[val % 5]);
342 /* create bounding box */
343 for (ar = sa->regionbase.first; ar; ar = ar->next) {
344 if (ar->regiontype == RGN_TYPE_WINDOW) {
345 RegionView3D *rv3d = ar->regiondata;
347 if (rv3d->viewlock & RV3D_BOXCLIP) {
348 rv3d->rflag |= RV3D_CLIPPING;
349 memcpy(rv3d->clip, clip, sizeof(clip));
350 if (rv3d->clipbb) MEM_freeN(rv3d->clipbb);
351 rv3d->clipbb = MEM_dupallocN(bb);
359 * Find which axis values are shared between both views and copy to \a rv3d_dst
360 * taking axis flipping into account.
362 static void view3d_boxview_sync_axis(RegionView3D *rv3d_dst, RegionView3D *rv3d_src)
364 /* absolute axis values above this are considered to be set (will be ~1.0f) */
365 const float axis_eps = 0.5f;
368 /* use the view rotation to identify which axis to sync on */
369 float view_axis_all[4][3] = {
375 float *view_src_x = &view_axis_all[0][0];
376 float *view_src_y = &view_axis_all[1][0];
378 float *view_dst_x = &view_axis_all[2][0];
379 float *view_dst_y = &view_axis_all[3][0];
383 /* we could use rv3d->viewinv, but better not depend on view matrix being updated */
384 if (UNLIKELY(ED_view3d_quat_from_axis_view(rv3d_src->view, viewinv) == false)) {
387 invert_qt_normalized(viewinv);
388 mul_qt_v3(viewinv, view_src_x);
389 mul_qt_v3(viewinv, view_src_y);
391 if (UNLIKELY(ED_view3d_quat_from_axis_view(rv3d_dst->view, viewinv) == false)) {
394 invert_qt_normalized(viewinv);
395 mul_qt_v3(viewinv, view_dst_x);
396 mul_qt_v3(viewinv, view_dst_y);
398 /* check source and dest have a matching axis */
399 for (i = 0; i < 3; i++) {
400 if (((fabsf(view_src_x[i]) > axis_eps) || (fabsf(view_src_y[i]) > axis_eps)) &&
401 ((fabsf(view_dst_x[i]) > axis_eps) || (fabsf(view_dst_y[i]) > axis_eps)))
403 rv3d_dst->ofs[i] = rv3d_src->ofs[i];
408 /* sync center/zoom view of region to others, for view transforms */
409 static void view3d_boxview_sync(ScrArea *sa, ARegion *ar)
412 RegionView3D *rv3d = ar->regiondata;
415 for (artest = sa->regionbase.first; artest; artest = artest->next) {
416 if (artest != ar && artest->regiontype == RGN_TYPE_WINDOW) {
417 RegionView3D *rv3dtest = artest->regiondata;
419 if (rv3dtest->viewlock & RV3D_LOCKED) {
420 rv3dtest->dist = rv3d->dist;
421 view3d_boxview_sync_axis(rv3dtest, rv3d);
422 clip |= rv3dtest->viewlock & RV3D_BOXCLIP;
424 ED_region_tag_redraw(artest);
430 view3d_boxview_clip(sa);
434 /* for home, center etc */
435 void view3d_boxview_copy(ScrArea *sa, ARegion *ar)
438 RegionView3D *rv3d = ar->regiondata;
441 for (artest = sa->regionbase.first; artest; artest = artest->next) {
442 if (artest != ar && artest->regiontype == RGN_TYPE_WINDOW) {
443 RegionView3D *rv3dtest = artest->regiondata;
445 if (rv3dtest->viewlock) {
446 rv3dtest->dist = rv3d->dist;
447 copy_v3_v3(rv3dtest->ofs, rv3d->ofs);
448 ED_region_tag_redraw(artest);
450 clip |= ((rv3dtest->viewlock & RV3D_BOXCLIP) != 0);
456 view3d_boxview_clip(sa);
460 /* 'clip' is used to know if our clip setting has changed */
461 void ED_view3d_quadview_update(ScrArea *sa, ARegion *ar, bool do_clip)
463 ARegion *ar_sync = NULL;
464 RegionView3D *rv3d = ar->regiondata;
466 /* this function copies flags from the first of the 3 other quadview
467 * regions to the 2 other, so it assumes this is the region whose
468 * properties are always being edited, weak */
469 viewlock = rv3d->viewlock;
471 if ((viewlock & RV3D_LOCKED) == 0) {
472 do_clip = (viewlock & RV3D_BOXCLIP) != 0;
475 else if ((viewlock & RV3D_BOXVIEW) == 0 && (viewlock & RV3D_BOXCLIP) != 0) {
477 viewlock &= ~RV3D_BOXCLIP;
480 for (; ar; ar = ar->prev) {
481 if (ar->alignment == RGN_ALIGN_QSPLIT) {
482 rv3d = ar->regiondata;
483 rv3d->viewlock = viewlock;
485 if (do_clip && (viewlock & RV3D_BOXCLIP) == 0) {
486 rv3d->rflag &= ~RV3D_BOXCLIP;
489 /* use ar_sync so we sync with one of the aligned views below
490 * else the view jumps on changing view settings like 'clip'
491 * since it copies from the perspective view */
496 if (rv3d->viewlock & RV3D_BOXVIEW) {
497 view3d_boxview_sync(sa, ar_sync ? ar_sync : sa->regionbase.last);
500 /* ensure locked regions have an axis, locked user views don't make much sense */
501 if (viewlock & RV3D_LOCKED) {
502 int index_qsplit = 0;
503 for (ar = sa->regionbase.first; ar; ar = ar->next) {
504 if (ar->alignment == RGN_ALIGN_QSPLIT) {
505 rv3d = ar->regiondata;
506 if (rv3d->viewlock) {
507 if (!RV3D_VIEW_IS_AXIS(rv3d->view)) {
508 rv3d->view = ED_view3d_lock_view_from_index(index_qsplit);
509 rv3d->persp = RV3D_ORTHO;
510 ED_view3d_lock(rv3d);
518 ED_area_tag_redraw(sa);
521 /* ************************** init for view ops **********************************/
523 typedef struct ViewOpsData {
524 /* context pointers (assigned by viewops_data_alloc) */
531 /* needed for continuous zoom */
533 double timer_lastdraw;
536 float viewquat[4]; /* working copy of rv3d->viewquat */
538 float mousevec[3]; /* dolly only */
540 float dist_prev, camzoom_prev;
542 bool axis_snap; /* view rotate only */
545 /* use for orbit selection and auto-dist */
546 float ofs[3], dyn_ofs[3];
549 int origx, origy, oldx, oldy;
550 int origkey; /* the key that triggered the operator */
554 #define TRACKBALLSIZE (1.1f)
556 static void calctrackballvec(const rcti *rect, int mx, int my, float vec[3])
558 const float radius = TRACKBALLSIZE;
559 const float t = radius / (float)M_SQRT2;
562 /* normalize x and y */
563 x = BLI_rcti_cent_x(rect) - mx;
564 x /= (float)(BLI_rcti_size_x(rect) / 4);
565 y = BLI_rcti_cent_y(rect) - my;
566 y /= (float)(BLI_rcti_size_y(rect) / 2);
567 d = sqrtf(x * x + y * y);
568 if (d < t) { /* Inside sphere */
569 z = sqrtf(radius * radius - d * d);
571 else { /* On hyperbola */
577 vec[2] = -z; /* yah yah! */
581 /* -------------------------------------------------------------------- */
584 /** \name Generic View Operator Custom-Data.
588 * Allocate and fill in context pointers for #ViewOpsData
590 static void viewops_data_alloc(bContext *C, wmOperator *op)
592 ViewOpsData *vod = MEM_callocN(sizeof(ViewOpsData), "viewops data");
595 op->customdata = vod;
596 vod->scene = CTX_data_scene(C);
597 vod->sa = CTX_wm_area(C);
598 vod->ar = CTX_wm_region(C);
599 vod->v3d = vod->sa->spacedata.first;
600 vod->rv3d = vod->ar->regiondata;
603 void view3d_orbit_apply_dyn_ofs(
604 float r_ofs[3], const float ofs_old[3], const float viewquat_old[4],
605 const float viewquat_new[4], const float dyn_ofs[3])
608 invert_qt_qt_normalized(q, viewquat_old);
609 mul_qt_qtqt(q, q, viewquat_new);
611 invert_qt_normalized(q);
613 sub_v3_v3v3(r_ofs, ofs_old, dyn_ofs);
615 add_v3_v3(r_ofs, dyn_ofs);
618 static bool view3d_orbit_calc_center(bContext *C, float r_dyn_ofs[3])
620 static float lastofs[3] = {0, 0, 0};
623 Scene *scene = CTX_data_scene(C);
624 Object *ob_act = OBACT;
626 if (ob_act && (ob_act->mode & OB_MODE_ALL_PAINT) &&
627 /* with weight-paint + pose-mode, fall through to using calculateTransformCenter */
628 ((ob_act->mode & OB_MODE_WEIGHT_PAINT) && BKE_object_pose_armature_get(ob_act)) == 0)
630 /* in case of sculpting use last average stroke position as a rotation
631 * center, in other cases it's not clear what rotation center shall be
632 * so just rotate around object origin
634 if (ob_act->mode & (OB_MODE_SCULPT | OB_MODE_TEXTURE_PAINT | OB_MODE_VERTEX_PAINT | OB_MODE_WEIGHT_PAINT)) {
636 BKE_paint_stroke_get_average(scene, ob_act, stroke);
637 copy_v3_v3(lastofs, stroke);
640 copy_v3_v3(lastofs, ob_act->obmat[3]);
644 else if (ob_act && (ob_act->mode & OB_MODE_EDIT) && (ob_act->type == OB_FONT)) {
645 Curve *cu = ob_act->data;
646 EditFont *ef = cu->editfont;
650 for (i = 0; i < 4; i++) {
651 add_v2_v2(lastofs, ef->textcurs[i]);
653 mul_v2_fl(lastofs, 1.0f / 4.0f);
655 mul_m4_v3(ob_act->obmat, lastofs);
659 else if (ob_act == NULL || ob_act->mode == OB_MODE_OBJECT) {
660 /* object mode use boundbox centers */
661 View3D *v3d = CTX_wm_view3d(C);
663 unsigned int tot = 0;
664 float select_center[3];
666 zero_v3(select_center);
667 for (base = FIRSTBASE; base; base = base->next) {
668 if (TESTBASE(v3d, base)) {
669 /* use the boundbox if we can */
670 Object *ob = base->object;
672 if (ob->bb && !(ob->bb->flag & BOUNDBOX_DIRTY)) {
675 BKE_boundbox_calc_center_aabb(ob->bb, cent);
677 mul_m4_v3(ob->obmat, cent);
678 add_v3_v3(select_center, cent);
681 add_v3_v3(select_center, ob->obmat[3]);
687 mul_v3_fl(select_center, 1.0f / (float)tot);
688 copy_v3_v3(lastofs, select_center);
693 /* If there's no selection, lastofs is unmodified and last value since static */
694 is_set = calculateTransformCenter(C, V3D_AROUND_CENTER_MEAN, lastofs, NULL);
697 copy_v3_v3(r_dyn_ofs, lastofs);
703 VIEWOPS_ORBIT_DEFAULT = 0,
704 VIEWOPS_ORBIT_SELECT = 1,
705 VIEWOPS_ORBIT_DEPTH = 2,
708 static enum eViewOpsOrbit viewops_orbit_mode_ex(bool use_select, bool use_depth)
711 return VIEWOPS_ORBIT_SELECT;
713 else if (use_depth) {
714 return VIEWOPS_ORBIT_DEPTH;
717 return VIEWOPS_ORBIT_DEFAULT;
721 static enum eViewOpsOrbit viewops_orbit_mode(void)
723 return viewops_orbit_mode_ex(
724 (U.uiflag & USER_ORBIT_SELECTION) != 0,
725 (U.uiflag & USER_ZBUF_ORBIT) != 0);
729 * Calculate the values for #ViewOpsData
731 static void viewops_data_create_ex(
732 bContext *C, wmOperator *op, const wmEvent *event,
733 bool switch_from_camera, enum eViewOpsOrbit orbit_mode)
735 ViewOpsData *vod = op->customdata;
736 RegionView3D *rv3d = vod->rv3d;
738 /* we need the depth info before changing any viewport options */
739 if (orbit_mode == VIEWOPS_ORBIT_DEPTH) {
740 float fallback_depth_pt[3];
742 view3d_operator_needs_opengl(C); /* needed for zbuf drawing */
744 negate_v3_v3(fallback_depth_pt, rv3d->ofs);
746 vod->use_dyn_ofs = ED_view3d_autodist(
747 vod->scene, vod->ar, vod->v3d,
748 event->mval, vod->dyn_ofs, true, fallback_depth_pt);
751 vod->use_dyn_ofs = false;
754 if (switch_from_camera) {
755 /* switch from camera view when: */
756 if (view3d_ensure_persp(vod->v3d, vod->ar)) {
757 /* If we're switching from camera view to the perspective one,
758 * need to tag viewport update, so camera vuew and borders
759 * are properly updated.
761 ED_region_tag_redraw(vod->ar);
765 /* set the view from the camera, if view locking is enabled.
766 * we may want to make this optional but for now its needed always */
767 ED_view3d_camera_lock_init(vod->v3d, vod->rv3d);
769 vod->dist_prev = rv3d->dist;
770 vod->camzoom_prev = rv3d->camzoom;
771 copy_qt_qt(vod->viewquat, rv3d->viewquat);
772 copy_qt_qt(vod->oldquat, rv3d->viewquat);
773 vod->origx = vod->oldx = event->x;
774 vod->origy = vod->oldy = event->y;
775 vod->origkey = event->type; /* the key that triggered the operator. */
776 copy_v3_v3(vod->ofs, rv3d->ofs);
778 if (orbit_mode == VIEWOPS_ORBIT_SELECT) {
780 vod->use_dyn_ofs = true;
782 view3d_orbit_calc_center(C, vod->dyn_ofs);
784 negate_v3(vod->dyn_ofs);
786 else if (orbit_mode == VIEWOPS_ORBIT_DEPTH) {
787 if (vod->use_dyn_ofs) {
788 if (rv3d->is_persp) {
789 float my_origin[3]; /* original G.vd->ofs */
790 float my_pivot[3]; /* view */
793 /* locals for dist correction */
797 negate_v3_v3(my_origin, rv3d->ofs); /* ofs is flipped */
799 /* Set the dist value to be the distance from this 3d point
800 * this means youll always be able to zoom into it and panning wont go bad when dist was zero */
802 /* remove dist value */
803 upvec[0] = upvec[1] = 0;
804 upvec[2] = rv3d->dist;
805 copy_m3_m4(mat, rv3d->viewinv);
807 mul_m3_v3(mat, upvec);
808 sub_v3_v3v3(my_pivot, rv3d->ofs, upvec);
809 negate_v3(my_pivot); /* ofs is flipped */
811 /* find a new ofs value that is along the view axis (rather than the mouse location) */
812 closest_to_line_v3(dvec, vod->dyn_ofs, my_pivot, my_origin);
813 vod->dist_prev = rv3d->dist = len_v3v3(my_pivot, dvec);
815 negate_v3_v3(rv3d->ofs, dvec);
818 const float mval_ar_mid[2] = {
819 (float)vod->ar->winx / 2.0f,
820 (float)vod->ar->winy / 2.0f};
822 ED_view3d_win_to_3d(vod->v3d, vod->ar, vod->dyn_ofs, mval_ar_mid, rv3d->ofs);
823 negate_v3(rv3d->ofs);
825 negate_v3(vod->dyn_ofs);
826 copy_v3_v3(vod->ofs, rv3d->ofs);
832 const float mval_f[2] = {(float)event->mval[0],
833 (float)event->mval[1]};
834 ED_view3d_win_to_vector(vod->ar, mval_f, vod->mousevec);
837 /* lookup, we don't pass on v3d to prevent confusement */
838 vod->grid = vod->v3d->grid;
839 vod->far = vod->v3d->far;
841 calctrackballvec(&vod->ar->winrct, event->x, event->y, vod->trackvec);
845 negate_v3_v3(tvec, rv3d->ofs);
846 vod->zfac = ED_view3d_calc_zfac(rv3d, tvec, NULL);
850 if (rv3d->persmat[2][1] < 0.0f)
851 vod->reverse = -1.0f;
853 rv3d->rflag |= RV3D_NAVIGATING;
856 static void viewops_data_create(bContext *C, wmOperator *op, const wmEvent *event, bool switch_from_camera)
858 enum eViewOpsOrbit orbit_mode = viewops_orbit_mode();
859 viewops_data_create_ex(C, op, event, switch_from_camera, orbit_mode);
862 static void viewops_data_free(bContext *C, wmOperator *op)
865 Paint *p = BKE_paint_get_active_from_context(C);
867 if (op->customdata) {
868 ViewOpsData *vod = op->customdata;
870 vod->rv3d->rflag &= ~RV3D_NAVIGATING;
873 WM_event_remove_timer(CTX_wm_manager(C), vod->timer->win, vod->timer);
876 op->customdata = NULL;
879 ar = CTX_wm_region(C);
882 if (p && (p->flags & PAINT_FAST_NAVIGATE))
883 ED_region_tag_redraw(ar);
888 /* ************************** viewrotate **********************************/
896 /* NOTE: these defines are saved in keymap files, do not change values but just add new ones */
897 #define VIEW_MODAL_CONFIRM 1 /* used for all view operations */
898 #define VIEWROT_MODAL_AXIS_SNAP_ENABLE 2
899 #define VIEWROT_MODAL_AXIS_SNAP_DISABLE 3
900 #define VIEWROT_MODAL_SWITCH_ZOOM 4
901 #define VIEWROT_MODAL_SWITCH_MOVE 5
902 #define VIEWROT_MODAL_SWITCH_ROTATE 6
904 /* called in transform_ops.c, on each regeneration of keymaps */
905 void viewrotate_modal_keymap(wmKeyConfig *keyconf)
907 static EnumPropertyItem modal_items[] = {
908 {VIEW_MODAL_CONFIRM, "CONFIRM", 0, "Confirm", ""},
910 {VIEWROT_MODAL_AXIS_SNAP_ENABLE, "AXIS_SNAP_ENABLE", 0, "Enable Axis Snap", ""},
911 {VIEWROT_MODAL_AXIS_SNAP_DISABLE, "AXIS_SNAP_DISABLE", 0, "Disable Axis Snap", ""},
913 {VIEWROT_MODAL_SWITCH_ZOOM, "SWITCH_TO_ZOOM", 0, "Switch to Zoom"},
914 {VIEWROT_MODAL_SWITCH_MOVE, "SWITCH_TO_MOVE", 0, "Switch to Move"},
916 {0, NULL, 0, NULL, NULL}
919 wmKeyMap *keymap = WM_modalkeymap_get(keyconf, "View3D Rotate Modal");
921 /* this function is called for each spacetype, only needs to add map once */
922 if (keymap && keymap->modal_items) return;
924 keymap = WM_modalkeymap_add(keyconf, "View3D Rotate Modal", modal_items);
926 /* items for modal map */
927 WM_modalkeymap_add_item(keymap, MIDDLEMOUSE, KM_RELEASE, KM_ANY, 0, VIEW_MODAL_CONFIRM);
928 WM_modalkeymap_add_item(keymap, ESCKEY, KM_PRESS, KM_ANY, 0, VIEW_MODAL_CONFIRM);
930 WM_modalkeymap_add_item(keymap, LEFTALTKEY, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_AXIS_SNAP_ENABLE);
931 WM_modalkeymap_add_item(keymap, LEFTALTKEY, KM_RELEASE, KM_ANY, 0, VIEWROT_MODAL_AXIS_SNAP_DISABLE);
933 /* disabled mode switching for now, can re-implement better, later on */
935 WM_modalkeymap_add_item(keymap, LEFTMOUSE, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ZOOM);
936 WM_modalkeymap_add_item(keymap, LEFTCTRLKEY, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ZOOM);
937 WM_modalkeymap_add_item(keymap, LEFTSHIFTKEY, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_MOVE);
940 /* assign map to operators */
941 WM_modalkeymap_assign(keymap, "VIEW3D_OT_rotate");
945 static void viewrotate_apply_dyn_ofs(ViewOpsData *vod, const float viewquat_new[4])
947 if (vod->use_dyn_ofs) {
948 RegionView3D *rv3d = vod->rv3d;
949 view3d_orbit_apply_dyn_ofs(rv3d->ofs, vod->ofs, vod->oldquat, viewquat_new, vod->dyn_ofs);
953 static void viewrotate_apply_snap(ViewOpsData *vod)
955 const float axis_limit = DEG2RADF(45 / 3);
957 RegionView3D *rv3d = vod->rv3d;
959 float viewquat_inv[4];
960 float zaxis[3] = {0, 0, 1};
965 invert_qt_qt_normalized(viewquat_inv, vod->viewquat);
967 mul_qt_v3(viewquat_inv, zaxis);
971 for (x = -1; x < 2; x++) {
972 for (y = -1; y < 2; y++) {
973 for (z = -1; z < 2; z++) {
975 float zaxis_test[3] = {x, y, z};
977 normalize_v3(zaxis_test);
979 if (angle_normalized_v3v3(zaxis_test, zaxis) < axis_limit) {
980 copy_v3_v3(zaxis_best, zaxis_test);
990 /* find the best roll */
991 float quat_roll[4], quat_final[4], quat_best[4], quat_snap[4];
992 float viewquat_align[4]; /* viewquat aligned to zaxis_best */
993 float viewquat_align_inv[4]; /* viewquat aligned to zaxis_best */
994 float best_angle = axis_limit;
997 /* viewquat_align is the original viewquat aligned to the snapped axis
998 * for testing roll */
999 rotation_between_vecs_to_quat(viewquat_align, zaxis_best, zaxis);
1000 normalize_qt(viewquat_align);
1001 mul_qt_qtqt(viewquat_align, vod->viewquat, viewquat_align);
1002 normalize_qt(viewquat_align);
1003 invert_qt_qt_normalized(viewquat_align_inv, viewquat_align);
1005 vec_to_quat(quat_snap, zaxis_best, OB_NEGZ, OB_POSY);
1006 normalize_qt(quat_snap);
1007 invert_qt_normalized(quat_snap);
1009 /* check if we can find the roll */
1012 /* find best roll */
1013 for (j = 0; j < 8; j++) {
1015 float xaxis1[3] = {1, 0, 0};
1016 float xaxis2[3] = {1, 0, 0};
1017 float quat_final_inv[4];
1019 axis_angle_to_quat(quat_roll, zaxis_best, (float)j * DEG2RADF(45.0f));
1020 normalize_qt(quat_roll);
1022 mul_qt_qtqt(quat_final, quat_snap, quat_roll);
1023 normalize_qt(quat_final);
1025 /* compare 2 vector angles to find the least roll */
1026 invert_qt_qt_normalized(quat_final_inv, quat_final);
1027 mul_qt_v3(viewquat_align_inv, xaxis1);
1028 mul_qt_v3(quat_final_inv, xaxis2);
1029 angle = angle_v3v3(xaxis1, xaxis2);
1031 if (angle <= best_angle) {
1034 copy_qt_qt(quat_best, quat_final);
1039 /* lock 'quat_best' to an axis view if we can */
1040 rv3d->view = ED_view3d_quat_to_axis_view(quat_best, 0.01f);
1041 if (rv3d->view != RV3D_VIEW_USER) {
1042 ED_view3d_quat_from_axis_view(rv3d->view, quat_best);
1046 copy_qt_qt(quat_best, viewquat_align);
1049 copy_qt_qt(rv3d->viewquat, quat_best);
1051 viewrotate_apply_dyn_ofs(vod, rv3d->viewquat);
1055 static void viewrotate_apply(ViewOpsData *vod, int x, int y)
1057 RegionView3D *rv3d = vod->rv3d;
1059 rv3d->view = RV3D_VIEW_USER; /* need to reset every time because of view snapping */
1061 if (U.flag & USER_TRACKBALL) {
1062 float axis[3], q1[4], dvec[3], newvec[3];
1065 calctrackballvec(&vod->ar->winrct, x, y, newvec);
1067 sub_v3_v3v3(dvec, newvec, vod->trackvec);
1069 angle = (len_v3(dvec) / (2.0f * TRACKBALLSIZE)) * (float)M_PI;
1071 /* Allow for rotation beyond the interval [-pi, pi] */
1072 angle = angle_wrap_rad(angle);
1074 /* This relation is used instead of the actual angle between vectors
1075 * so that the angle of rotation is linearly proportional to
1076 * the distance that the mouse is dragged. */
1078 cross_v3_v3v3(axis, vod->trackvec, newvec);
1079 axis_angle_to_quat(q1, axis, angle);
1081 mul_qt_qtqt(vod->viewquat, q1, vod->oldquat);
1083 viewrotate_apply_dyn_ofs(vod, vod->viewquat);
1086 /* New turntable view code by John Aughey */
1087 float quat_local_x[4], quat_global_z[4];
1090 const float zvec_global[3] = {0.0f, 0.0f, 1.0f};
1093 /* Sensitivity will control how fast the viewport rotates. 0.007 was
1094 * obtained experimentally by looking at viewport rotation sensitivities
1095 * on other modeling programs. */
1096 /* Perhaps this should be a configurable user parameter. */
1097 const float sensitivity = 0.007f;
1099 /* Get the 3x3 matrix and its inverse from the quaternion */
1100 quat_to_mat3(m, vod->viewquat);
1101 invert_m3_m3(m_inv, m);
1103 /* avoid gimble lock */
1105 if (len_squared_v3v3(zvec_global, m_inv[2]) > 0.001f) {
1107 cross_v3_v3v3(xaxis, zvec_global, m_inv[2]);
1108 if (dot_v3v3(xaxis, m_inv[0]) < 0) {
1111 fac = angle_normalized_v3v3(zvec_global, m_inv[2]) / (float)M_PI;
1112 fac = fabsf(fac - 0.5f) * 2;
1114 interp_v3_v3v3(xaxis, xaxis, m_inv[0], fac);
1117 copy_v3_v3(xaxis, m_inv[0]);
1120 copy_v3_v3(xaxis, m_inv[0]);
1123 /* Determine the direction of the x vector (for rotating up and down) */
1124 /* This can likely be computed directly from the quaternion. */
1126 /* Perform the up/down rotation */
1127 axis_angle_to_quat(quat_local_x, xaxis, sensitivity * -(y - vod->oldy));
1128 mul_qt_qtqt(quat_local_x, vod->viewquat, quat_local_x);
1130 /* Perform the orbital rotation */
1131 axis_angle_to_quat_single(quat_global_z, 'Z', sensitivity * vod->reverse * (x - vod->oldx));
1132 mul_qt_qtqt(vod->viewquat, quat_local_x, quat_global_z);
1134 viewrotate_apply_dyn_ofs(vod, vod->viewquat);
1137 /* avoid precision loss over time */
1138 normalize_qt(vod->viewquat);
1140 /* use a working copy so view rotation locking doesnt overwrite the locked
1141 * rotation back into the view we calculate with */
1142 copy_qt_qt(rv3d->viewquat, vod->viewquat);
1144 /* check for view snap,
1145 * note: don't apply snap to vod->viewquat so the view wont jam up */
1146 if (vod->axis_snap) {
1147 viewrotate_apply_snap(vod);
1152 ED_view3d_camera_lock_sync(vod->v3d, rv3d);
1154 ED_region_tag_redraw(vod->ar);
1157 static int viewrotate_modal(bContext *C, wmOperator *op, const wmEvent *event)
1159 ViewOpsData *vod = op->customdata;
1160 short event_code = VIEW_PASS;
1161 bool use_autokey = false;
1162 int ret = OPERATOR_RUNNING_MODAL;
1164 /* execute the events */
1165 if (event->type == MOUSEMOVE) {
1166 event_code = VIEW_APPLY;
1168 else if (event->type == EVT_MODAL_MAP) {
1169 switch (event->val) {
1170 case VIEW_MODAL_CONFIRM:
1171 event_code = VIEW_CONFIRM;
1173 case VIEWROT_MODAL_AXIS_SNAP_ENABLE:
1174 vod->axis_snap = true;
1175 event_code = VIEW_APPLY;
1177 case VIEWROT_MODAL_AXIS_SNAP_DISABLE:
1178 vod->axis_snap = false;
1179 event_code = VIEW_APPLY;
1181 case VIEWROT_MODAL_SWITCH_ZOOM:
1182 WM_operator_name_call(C, "VIEW3D_OT_zoom", WM_OP_INVOKE_DEFAULT, NULL);
1183 event_code = VIEW_CONFIRM;
1185 case VIEWROT_MODAL_SWITCH_MOVE:
1186 WM_operator_name_call(C, "VIEW3D_OT_move", WM_OP_INVOKE_DEFAULT, NULL);
1187 event_code = VIEW_CONFIRM;
1191 else if (event->type == vod->origkey && event->val == KM_RELEASE) {
1192 event_code = VIEW_CONFIRM;
1195 if (event_code == VIEW_APPLY) {
1196 viewrotate_apply(vod, event->x, event->y);
1197 if (ED_screen_animation_playing(CTX_wm_manager(C))) {
1201 else if (event_code == VIEW_CONFIRM) {
1202 ED_view3d_depth_tag_update(vod->rv3d);
1204 ret = OPERATOR_FINISHED;
1208 ED_view3d_camera_lock_autokey(vod->v3d, vod->rv3d, C, true, true);
1211 if (ret & OPERATOR_FINISHED) {
1212 viewops_data_free(C, op);
1219 * Action to take when rotating the view,
1220 * handle auto-persp and logic for switching out of views.
1224 static bool view3d_ensure_persp(struct View3D *v3d, ARegion *ar)
1226 RegionView3D *rv3d = ar->regiondata;
1227 const bool autopersp = (U.uiflag & USER_AUTOPERSP) != 0;
1229 BLI_assert((rv3d->viewlock & RV3D_LOCKED) == 0);
1231 if (ED_view3d_camera_lock_check(v3d, rv3d))
1234 if (rv3d->persp != RV3D_PERSP) {
1235 if (rv3d->persp == RV3D_CAMOB) {
1236 /* If autopersp and previous view was an axis one, switch back to PERSP mode, else reuse previous mode. */
1237 char persp = (autopersp && RV3D_VIEW_IS_AXIS(rv3d->lview)) ? RV3D_PERSP : rv3d->lpersp;
1238 view3d_persp_switch_from_camera(v3d, rv3d, persp);
1240 else if (autopersp && RV3D_VIEW_IS_AXIS(rv3d->view)) {
1241 rv3d->persp = RV3D_PERSP;
1249 static int viewrotate_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1253 /* makes op->customdata */
1254 viewops_data_alloc(C, op);
1255 vod = op->customdata;
1257 /* poll should check but in some cases fails, see poll func for details */
1258 if (vod->rv3d->viewlock & RV3D_LOCKED) {
1259 viewops_data_free(C, op);
1260 return OPERATOR_PASS_THROUGH;
1263 ED_view3d_smooth_view_force_finish(C, vod->v3d, vod->ar);
1265 viewops_data_create(C, op, event, true);
1267 if (ELEM(event->type, MOUSEPAN, MOUSEROTATE)) {
1268 /* Rotate direction we keep always same */
1271 if (event->type == MOUSEPAN) {
1272 if (U.uiflag2 & USER_TRACKPAD_NATURAL) {
1273 x = 2 * event->x - event->prevx;
1274 y = 2 * event->y - event->prevy;
1282 /* MOUSEROTATE performs orbital rotation, so y axis delta is set to 0 */
1287 viewrotate_apply(vod, x, y);
1288 ED_view3d_depth_tag_update(vod->rv3d);
1290 viewops_data_free(C, op);
1292 return OPERATOR_FINISHED;
1295 /* add temp handler */
1296 WM_event_add_modal_handler(C, op);
1298 return OPERATOR_RUNNING_MODAL;
1302 /* test for unlocked camera view in quad view */
1303 static int view3d_camera_user_poll(bContext *C)
1308 if (ED_view3d_context_user_region(C, &v3d, &ar)) {
1309 RegionView3D *rv3d = ar->regiondata;
1310 if (rv3d->persp == RV3D_CAMOB) {
1318 static int view3d_lock_poll(bContext *C)
1320 View3D *v3d = CTX_wm_view3d(C);
1322 RegionView3D *rv3d = CTX_wm_region_view3d(C);
1324 return ED_view3d_offset_lock_check(v3d, rv3d);
1330 static void viewrotate_cancel(bContext *C, wmOperator *op)
1332 viewops_data_free(C, op);
1335 void VIEW3D_OT_rotate(wmOperatorType *ot)
1338 ot->name = "Rotate View";
1339 ot->description = "Rotate the view";
1340 ot->idname = "VIEW3D_OT_rotate";
1343 ot->invoke = viewrotate_invoke;
1344 ot->modal = viewrotate_modal;
1345 ot->poll = ED_operator_region_view3d_active;
1346 ot->cancel = viewrotate_cancel;
1349 ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR;
1352 #ifdef WITH_INPUT_NDOF
1354 /** \name NDOF Utility Functions
1357 #define NDOF_HAS_TRANSLATE ((!ED_view3d_offset_lock_check(v3d, rv3d)) && !is_zero_v3(ndof->tvec))
1358 #define NDOF_HAS_ROTATE (((rv3d->viewlock & RV3D_LOCKED) == 0) && !is_zero_v3(ndof->rvec))
1361 * \param depth_pt: A point to calculate the depth (in perspective mode)
1363 static float view3d_ndof_pan_speed_calc_ex(RegionView3D *rv3d, const float depth_pt[3])
1365 float speed = rv3d->pixsize * NDOF_PIXELS_PER_SECOND;
1367 if (rv3d->is_persp) {
1368 speed *= ED_view3d_calc_zfac(rv3d, depth_pt, NULL);
1374 static float view3d_ndof_pan_speed_calc_from_dist(RegionView3D *rv3d, const float dist)
1379 BLI_assert(dist >= 0.0f);
1381 copy_v3_fl3(tvec, 0.0f, 0.0f, dist);
1382 /* rv3d->viewinv isn't always valid */
1384 mul_mat3_m4_v3(rv3d->viewinv, tvec);
1386 invert_qt_qt_normalized(viewinv, rv3d->viewquat);
1387 mul_qt_v3(viewinv, tvec);
1390 return view3d_ndof_pan_speed_calc_ex(rv3d, tvec);
1393 static float view3d_ndof_pan_speed_calc(RegionView3D *rv3d)
1396 negate_v3_v3(tvec, rv3d->ofs);
1398 return view3d_ndof_pan_speed_calc_ex(rv3d, tvec);
1402 * Zoom and pan in the same function since sometimes zoom is interpreted as dolly (pan forward).
1404 * \param has_zoom zoom, otherwise dolly, often `!rv3d->is_persp` since it doesnt make sense to dolly in ortho.
1406 static void view3d_ndof_pan_zoom(const struct wmNDOFMotionData *ndof, ScrArea *sa, ARegion *ar,
1407 const bool has_translate, const bool has_zoom)
1409 RegionView3D *rv3d = ar->regiondata;
1413 if (has_translate == false && has_zoom == false) {
1417 WM_event_ndof_pan_get(ndof, pan_vec, false);
1423 * velocity should be proportional to the linear velocity attained by rotational motion of same strength
1425 * proportional to arclength = radius * angle
1430 /* "zoom in" or "translate"? depends on zoom mode in user settings? */
1431 if (ndof->tvec[2]) {
1432 float zoom_distance = rv3d->dist * ndof->dt * ndof->tvec[2];
1434 if (U.ndof_flag & NDOF_ZOOM_INVERT)
1435 zoom_distance = -zoom_distance;
1437 rv3d->dist += zoom_distance;
1443 /* all callers must check */
1444 if (has_translate) {
1445 BLI_assert(ED_view3d_offset_lock_check((View3D *)sa->spacedata.first, rv3d) == false);
1449 if (has_translate) {
1450 const float speed = view3d_ndof_pan_speed_calc(rv3d);
1452 mul_v3_fl(pan_vec, speed * ndof->dt);
1454 /* transform motion from view to world coordinates */
1455 invert_qt_qt_normalized(view_inv, rv3d->viewquat);
1456 mul_qt_v3(view_inv, pan_vec);
1458 /* move center of view opposite of hand motion (this is camera mode, not object mode) */
1459 sub_v3_v3(rv3d->ofs, pan_vec);
1461 if (rv3d->viewlock & RV3D_BOXVIEW) {
1462 view3d_boxview_sync(sa, ar);
1468 static void view3d_ndof_orbit(const struct wmNDOFMotionData *ndof, ScrArea *sa, ARegion *ar,
1469 /* optional, can be NULL*/
1472 View3D *v3d = sa->spacedata.first;
1473 RegionView3D *rv3d = ar->regiondata;
1477 BLI_assert((rv3d->viewlock & RV3D_LOCKED) == 0);
1479 view3d_ensure_persp(v3d, ar);
1481 rv3d->view = RV3D_VIEW_USER;
1483 invert_qt_qt_normalized(view_inv, rv3d->viewquat);
1485 if (U.ndof_flag & NDOF_TURNTABLE) {
1488 /* turntable view code by John Aughey, adapted for 3D mouse by [mce] */
1489 float angle, quat[4];
1490 float xvec[3] = {1, 0, 0};
1492 /* only use XY, ignore Z */
1493 WM_event_ndof_rotate_get(ndof, rot);
1495 /* Determine the direction of the x vector (for rotating up and down) */
1496 mul_qt_v3(view_inv, xvec);
1498 /* Perform the up/down rotation */
1499 angle = ndof->dt * rot[0];
1500 axis_angle_to_quat(quat, xvec, angle);
1501 mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, quat);
1503 /* Perform the orbital rotation */
1504 angle = ndof->dt * rot[1];
1506 /* update the onscreen doo-dad */
1507 rv3d->rot_angle = angle;
1508 rv3d->rot_axis[0] = 0;
1509 rv3d->rot_axis[1] = 0;
1510 rv3d->rot_axis[2] = 1;
1512 axis_angle_to_quat_single(quat, 'Z', angle);
1513 mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, quat);
1519 float angle = WM_event_ndof_to_axis_angle(ndof, axis);
1521 /* transform rotation axis from view to world coordinates */
1522 mul_qt_v3(view_inv, axis);
1524 /* update the onscreen doo-dad */
1525 rv3d->rot_angle = angle;
1526 copy_v3_v3(rv3d->rot_axis, axis);
1528 axis_angle_to_quat(quat, axis, angle);
1530 /* apply rotation */
1531 mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, quat);
1535 viewrotate_apply_dyn_ofs(vod, rv3d->viewquat);
1540 * Called from both fly mode and walk mode,
1542 void view3d_ndof_fly(
1543 const wmNDOFMotionData *ndof,
1544 View3D *v3d, RegionView3D *rv3d,
1545 const bool use_precision, const short protectflag,
1546 bool *r_has_translate, bool *r_has_rotate)
1548 bool has_translate = NDOF_HAS_TRANSLATE;
1549 bool has_rotate = NDOF_HAS_ROTATE;
1552 invert_qt_qt_normalized(view_inv, rv3d->viewquat);
1554 rv3d->rot_angle = 0.0f; /* disable onscreen rotation doo-dad */
1556 if (has_translate) {
1557 /* ignore real 'dist' since fly has its own speed settings,
1558 * also its overwritten at this point. */
1559 float speed = view3d_ndof_pan_speed_calc_from_dist(rv3d, 1.0f);
1560 float trans[3], trans_orig_y;
1565 WM_event_ndof_pan_get(ndof, trans, false);
1566 mul_v3_fl(trans, speed * ndof->dt);
1567 trans_orig_y = trans[1];
1569 if (U.ndof_flag & NDOF_FLY_HELICOPTER) {
1573 /* transform motion from view to world coordinates */
1574 mul_qt_v3(view_inv, trans);
1576 if (U.ndof_flag & NDOF_FLY_HELICOPTER) {
1577 /* replace world z component with device y (yes it makes sense) */
1578 trans[2] = trans_orig_y;
1581 if (rv3d->persp == RV3D_CAMOB) {
1582 /* respect camera position locks */
1583 if (protectflag & OB_LOCK_LOCX) trans[0] = 0.0f;
1584 if (protectflag & OB_LOCK_LOCY) trans[1] = 0.0f;
1585 if (protectflag & OB_LOCK_LOCZ) trans[2] = 0.0f;
1588 if (!is_zero_v3(trans)) {
1589 /* move center of view opposite of hand motion (this is camera mode, not object mode) */
1590 sub_v3_v3(rv3d->ofs, trans);
1591 has_translate = true;
1594 has_translate = false;
1599 const float turn_sensitivity = 1.0f;
1603 float angle = turn_sensitivity * WM_event_ndof_to_axis_angle(ndof, axis);
1605 if (fabsf(angle) > 0.0001f) {
1611 /* transform rotation axis from view to world coordinates */
1612 mul_qt_v3(view_inv, axis);
1614 /* apply rotation to view */
1615 axis_angle_to_quat(rotation, axis, angle);
1616 mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, rotation);
1618 if (U.ndof_flag & NDOF_LOCK_HORIZON) {
1619 /* force an upright viewpoint
1620 * TODO: make this less... sudden */
1621 float view_horizon[3] = {1.0f, 0.0f, 0.0f}; /* view +x */
1622 float view_direction[3] = {0.0f, 0.0f, -1.0f}; /* view -z (into screen) */
1624 /* find new inverse since viewquat has changed */
1625 invert_qt_qt_normalized(view_inv, rv3d->viewquat);
1626 /* could apply reverse rotation to existing view_inv to save a few cycles */
1628 /* transform view vectors to world coordinates */
1629 mul_qt_v3(view_inv, view_horizon);
1630 mul_qt_v3(view_inv, view_direction);
1633 /* find difference between view & world horizons
1634 * true horizon lives in world xy plane, so look only at difference in z */
1635 angle = -asinf(view_horizon[2]);
1637 /* rotate view so view horizon = world horizon */
1638 axis_angle_to_quat(rotation, view_direction, angle);
1639 mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, rotation);
1642 rv3d->view = RV3D_VIEW_USER;
1649 *r_has_translate = has_translate;
1650 *r_has_rotate = has_rotate;
1656 /* -- "orbit" navigation (trackball/turntable)
1658 * -- panning in rotationally-locked views
1660 static int ndof_orbit_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1663 if (event->type != NDOF_MOTION) {
1664 return OPERATOR_CANCELLED;
1671 const wmNDOFMotionData *ndof = event->customdata;
1673 viewops_data_alloc(C, op);
1674 viewops_data_create_ex(
1676 viewops_orbit_mode_ex((U.uiflag & USER_ORBIT_SELECTION) != 0, false), false);
1677 vod = op->customdata;
1679 ED_view3d_smooth_view_force_finish(C, vod->v3d, vod->ar);
1684 /* off by default, until changed later this function */
1685 rv3d->rot_angle = 0.0f;
1687 ED_view3d_camera_lock_init_ex(v3d, rv3d, false);
1689 if (ndof->progress != P_FINISHING) {
1690 const bool has_rotation = NDOF_HAS_ROTATE;
1691 /* if we can't rotate, fallback to translate (locked axis views) */
1692 const bool has_translate = NDOF_HAS_TRANSLATE && (rv3d->viewlock & RV3D_LOCKED);
1693 const bool has_zoom = (ndof->tvec[2] != 0.0f) && !rv3d->is_persp;
1695 if (has_translate || has_zoom) {
1696 view3d_ndof_pan_zoom(ndof, vod->sa, vod->ar, has_translate, has_zoom);
1700 view3d_ndof_orbit(ndof, vod->sa, vod->ar, vod);
1704 ED_view3d_camera_lock_sync(v3d, rv3d);
1706 ED_region_tag_redraw(vod->ar);
1708 viewops_data_free(C, op);
1710 return OPERATOR_FINISHED;
1714 void VIEW3D_OT_ndof_orbit(struct wmOperatorType *ot)
1717 ot->name = "NDOF Orbit View";
1718 ot->description = "Orbit the view using the 3D mouse";
1719 ot->idname = "VIEW3D_OT_ndof_orbit";
1722 ot->invoke = ndof_orbit_invoke;
1723 ot->poll = ED_operator_view3d_active;
1729 static int ndof_orbit_zoom_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1732 if (event->type != NDOF_MOTION) {
1733 return OPERATOR_CANCELLED;
1740 const wmNDOFMotionData *ndof = event->customdata;
1742 viewops_data_alloc(C, op);
1743 viewops_data_create_ex(
1745 viewops_orbit_mode_ex((U.uiflag & USER_ORBIT_SELECTION) != 0, false), false);
1747 vod = op->customdata;
1749 ED_view3d_smooth_view_force_finish(C, vod->v3d, vod->ar);
1754 /* off by default, until changed later this function */
1755 rv3d->rot_angle = 0.0f;
1757 ED_view3d_camera_lock_init_ex(v3d, rv3d, false);
1759 if (ndof->progress == P_FINISHING) {
1762 else if ((rv3d->persp == RV3D_ORTHO) && RV3D_VIEW_IS_AXIS(rv3d->view)) {
1763 /* if we can't rotate, fallback to translate (locked axis views) */
1764 const bool has_translate = NDOF_HAS_TRANSLATE;
1765 const bool has_zoom = (ndof->tvec[2] != 0.0f) && ED_view3d_offset_lock_check(v3d, rv3d);
1767 if (has_translate || has_zoom) {
1768 view3d_ndof_pan_zoom(ndof, vod->sa, vod->ar, has_translate, true);
1771 else if ((U.ndof_flag & NDOF_MODE_ORBIT) ||
1772 ED_view3d_offset_lock_check(v3d, rv3d))
1774 const bool has_rotation = NDOF_HAS_ROTATE;
1775 const bool has_zoom = (ndof->tvec[2] != 0.0f);
1778 view3d_ndof_pan_zoom(ndof, vod->sa, vod->ar, false, has_zoom);
1782 view3d_ndof_orbit(ndof, vod->sa, vod->ar, vod);
1785 else { /* free/explore (like fly mode) */
1786 const bool has_rotation = NDOF_HAS_ROTATE;
1787 const bool has_translate = NDOF_HAS_TRANSLATE;
1788 const bool has_zoom = (ndof->tvec[2] != 0.0f) && !rv3d->is_persp;
1792 if (has_translate || has_zoom) {
1793 view3d_ndof_pan_zoom(ndof, vod->sa, vod->ar, has_translate, has_zoom);
1796 dist_backup = rv3d->dist;
1797 ED_view3d_distance_set(rv3d, 0.0f);
1800 view3d_ndof_orbit(ndof, vod->sa, vod->ar, NULL);
1803 ED_view3d_distance_set(rv3d, dist_backup);
1806 ED_view3d_camera_lock_sync(v3d, rv3d);
1808 ED_region_tag_redraw(vod->ar);
1810 viewops_data_free(C, op);
1812 return OPERATOR_FINISHED;
1816 void VIEW3D_OT_ndof_orbit_zoom(struct wmOperatorType *ot)
1819 ot->name = "NDOF Orbit View with Zoom";
1820 ot->description = "Orbit and zoom the view using the 3D mouse";
1821 ot->idname = "VIEW3D_OT_ndof_orbit_zoom";
1824 ot->invoke = ndof_orbit_zoom_invoke;
1825 ot->poll = ED_operator_view3d_active;
1831 /* -- "pan" navigation
1834 static int ndof_pan_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
1836 if (event->type != NDOF_MOTION) {
1837 return OPERATOR_CANCELLED;
1840 View3D *v3d = CTX_wm_view3d(C);
1841 RegionView3D *rv3d = CTX_wm_region_view3d(C);
1842 const wmNDOFMotionData *ndof = event->customdata;
1844 const bool has_translate = NDOF_HAS_TRANSLATE;
1845 const bool has_zoom = (ndof->tvec[2] != 0.0f) && !rv3d->is_persp;
1847 /* we're panning here! so erase any leftover rotation from other operators */
1848 rv3d->rot_angle = 0.0f;
1850 if (!(has_translate || has_zoom))
1851 return OPERATOR_CANCELLED;
1853 ED_view3d_camera_lock_init_ex(v3d, rv3d, false);
1855 if (ndof->progress != P_FINISHING) {
1856 ScrArea *sa = CTX_wm_area(C);
1857 ARegion *ar = CTX_wm_region(C);
1859 if (has_translate || has_zoom) {
1860 view3d_ndof_pan_zoom(ndof, sa, ar, has_translate, has_zoom);
1864 ED_view3d_camera_lock_sync(v3d, rv3d);
1866 ED_region_tag_redraw(CTX_wm_region(C));
1868 return OPERATOR_FINISHED;
1872 void VIEW3D_OT_ndof_pan(struct wmOperatorType *ot)
1875 ot->name = "NDOF Pan View";
1876 ot->description = "Pan the view with the 3D mouse";
1877 ot->idname = "VIEW3D_OT_ndof_pan";
1880 ot->invoke = ndof_pan_invoke;
1881 ot->poll = ED_operator_view3d_active;
1889 * wraps #ndof_orbit_zoom but never restrict to orbit.
1891 static int ndof_all_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1893 /* weak!, but it works */
1894 const int ndof_flag = U.ndof_flag;
1897 U.ndof_flag &= ~NDOF_MODE_ORBIT;
1899 ret = ndof_orbit_zoom_invoke(C, op, event);
1901 U.ndof_flag = ndof_flag;
1906 void VIEW3D_OT_ndof_all(struct wmOperatorType *ot)
1909 ot->name = "NDOF Move View";
1910 ot->description = "Pan and rotate the view with the 3D mouse";
1911 ot->idname = "VIEW3D_OT_ndof_all";
1914 ot->invoke = ndof_all_invoke;
1915 ot->poll = ED_operator_view3d_active;
1921 #endif /* WITH_INPUT_NDOF */
1923 /* ************************ viewmove ******************************** */
1926 /* NOTE: these defines are saved in keymap files, do not change values but just add new ones */
1928 /* called in transform_ops.c, on each regeneration of keymaps */
1929 void viewmove_modal_keymap(wmKeyConfig *keyconf)
1931 static EnumPropertyItem modal_items[] = {
1932 {VIEW_MODAL_CONFIRM, "CONFIRM", 0, "Confirm", ""},
1934 {VIEWROT_MODAL_SWITCH_ZOOM, "SWITCH_TO_ZOOM", 0, "Switch to Zoom"},
1935 {VIEWROT_MODAL_SWITCH_ROTATE, "SWITCH_TO_ROTATE", 0, "Switch to Rotate"},
1937 {0, NULL, 0, NULL, NULL}
1940 wmKeyMap *keymap = WM_modalkeymap_get(keyconf, "View3D Move Modal");
1942 /* this function is called for each spacetype, only needs to add map once */
1943 if (keymap && keymap->modal_items) return;
1945 keymap = WM_modalkeymap_add(keyconf, "View3D Move Modal", modal_items);
1947 /* items for modal map */
1948 WM_modalkeymap_add_item(keymap, MIDDLEMOUSE, KM_RELEASE, KM_ANY, 0, VIEW_MODAL_CONFIRM);
1949 WM_modalkeymap_add_item(keymap, ESCKEY, KM_PRESS, KM_ANY, 0, VIEW_MODAL_CONFIRM);
1951 /* disabled mode switching for now, can re-implement better, later on */
1953 WM_modalkeymap_add_item(keymap, LEFTMOUSE, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ZOOM);
1954 WM_modalkeymap_add_item(keymap, LEFTCTRLKEY, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ZOOM);
1955 WM_modalkeymap_add_item(keymap, LEFTSHIFTKEY, KM_RELEASE, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ROTATE);
1958 /* assign map to operators */
1959 WM_modalkeymap_assign(keymap, "VIEW3D_OT_move");
1963 static void viewmove_apply(ViewOpsData *vod, int x, int y)
1965 if (ED_view3d_offset_lock_check(vod->v3d, vod->rv3d)) {
1966 vod->rv3d->ofs_lock[0] -= ((vod->oldx - x) * 2.0f) / (float)vod->ar->winx;
1967 vod->rv3d->ofs_lock[1] -= ((vod->oldy - y) * 2.0f) / (float)vod->ar->winy;
1969 else if ((vod->rv3d->persp == RV3D_CAMOB) && !ED_view3d_camera_lock_check(vod->v3d, vod->rv3d)) {
1970 const float zoomfac = BKE_screen_view3d_zoom_to_fac(vod->rv3d->camzoom) * 2.0f;
1971 vod->rv3d->camdx += (vod->oldx - x) / (vod->ar->winx * zoomfac);
1972 vod->rv3d->camdy += (vod->oldy - y) / (vod->ar->winy * zoomfac);
1973 CLAMP(vod->rv3d->camdx, -1.0f, 1.0f);
1974 CLAMP(vod->rv3d->camdy, -1.0f, 1.0f);
1980 mval_f[0] = x - vod->oldx;
1981 mval_f[1] = y - vod->oldy;
1982 ED_view3d_win_to_delta(vod->ar, mval_f, dvec, vod->zfac);
1984 add_v3_v3(vod->rv3d->ofs, dvec);
1986 if (vod->rv3d->viewlock & RV3D_BOXVIEW)
1987 view3d_boxview_sync(vod->sa, vod->ar);
1993 ED_view3d_camera_lock_sync(vod->v3d, vod->rv3d);
1995 ED_region_tag_redraw(vod->ar);
1999 static int viewmove_modal(bContext *C, wmOperator *op, const wmEvent *event)
2002 ViewOpsData *vod = op->customdata;
2003 short event_code = VIEW_PASS;
2004 bool use_autokey = false;
2005 int ret = OPERATOR_RUNNING_MODAL;
2007 /* execute the events */
2008 if (event->type == MOUSEMOVE) {
2009 event_code = VIEW_APPLY;
2011 else if (event->type == EVT_MODAL_MAP) {
2012 switch (event->val) {
2013 case VIEW_MODAL_CONFIRM:
2014 event_code = VIEW_CONFIRM;
2016 case VIEWROT_MODAL_SWITCH_ZOOM:
2017 WM_operator_name_call(C, "VIEW3D_OT_zoom", WM_OP_INVOKE_DEFAULT, NULL);
2018 event_code = VIEW_CONFIRM;
2020 case VIEWROT_MODAL_SWITCH_ROTATE:
2021 WM_operator_name_call(C, "VIEW3D_OT_rotate", WM_OP_INVOKE_DEFAULT, NULL);
2022 event_code = VIEW_CONFIRM;
2026 else if (event->type == vod->origkey && event->val == KM_RELEASE) {
2027 event_code = VIEW_CONFIRM;
2030 if (event_code == VIEW_APPLY) {
2031 viewmove_apply(vod, event->x, event->y);
2032 if (ED_screen_animation_playing(CTX_wm_manager(C))) {
2036 else if (event_code == VIEW_CONFIRM) {
2037 ED_view3d_depth_tag_update(vod->rv3d);
2039 ret = OPERATOR_FINISHED;
2043 ED_view3d_camera_lock_autokey(vod->v3d, vod->rv3d, C, false, true);
2046 if (ret & OPERATOR_FINISHED) {
2047 viewops_data_free(C, op);
2053 static int viewmove_invoke(bContext *C, wmOperator *op, const wmEvent *event)
2057 /* makes op->customdata */
2058 viewops_data_alloc(C, op);
2059 viewops_data_create(C, op, event, false);
2060 vod = op->customdata;
2062 ED_view3d_smooth_view_force_finish(C, vod->v3d, vod->ar);
2064 if (event->type == MOUSEPAN) {
2065 /* invert it, trackpad scroll follows same principle as 2d windows this way */
2066 viewmove_apply(vod, 2 * event->x - event->prevx, 2 * event->y - event->prevy);
2067 ED_view3d_depth_tag_update(vod->rv3d);
2069 viewops_data_free(C, op);
2071 return OPERATOR_FINISHED;
2074 /* add temp handler */
2075 WM_event_add_modal_handler(C, op);
2077 return OPERATOR_RUNNING_MODAL;
2081 static void viewmove_cancel(bContext *C, wmOperator *op)
2083 viewops_data_free(C, op);
2086 void VIEW3D_OT_move(wmOperatorType *ot)
2090 ot->name = "Move View";
2091 ot->description = "Move the view";
2092 ot->idname = "VIEW3D_OT_move";
2095 ot->invoke = viewmove_invoke;
2096 ot->modal = viewmove_modal;
2097 ot->poll = ED_operator_view3d_active;
2098 ot->cancel = viewmove_cancel;
2101 ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR;
2104 /* ************************ viewzoom ******************************** */
2106 /* viewdolly_modal_keymap has an exact copy of this, apply fixes to both */
2107 /* called in transform_ops.c, on each regeneration of keymaps */
2108 void viewzoom_modal_keymap(wmKeyConfig *keyconf)
2110 static EnumPropertyItem modal_items[] = {
2111 {VIEW_MODAL_CONFIRM, "CONFIRM", 0, "Confirm", ""},
2113 {VIEWROT_MODAL_SWITCH_ROTATE, "SWITCH_TO_ROTATE", 0, "Switch to Rotate"},
2114 {VIEWROT_MODAL_SWITCH_MOVE, "SWITCH_TO_MOVE", 0, "Switch to Move"},
2116 {0, NULL, 0, NULL, NULL}
2119 wmKeyMap *keymap = WM_modalkeymap_get(keyconf, "View3D Zoom Modal");
2121 /* this function is called for each spacetype, only needs to add map once */
2122 if (keymap && keymap->modal_items) return;
2124 keymap = WM_modalkeymap_add(keyconf, "View3D Zoom Modal", modal_items);
2126 /* items for modal map */
2127 WM_modalkeymap_add_item(keymap, MIDDLEMOUSE, KM_RELEASE, KM_ANY, 0, VIEW_MODAL_CONFIRM);
2128 WM_modalkeymap_add_item(keymap, ESCKEY, KM_PRESS, KM_ANY, 0, VIEW_MODAL_CONFIRM);
2130 /* disabled mode switching for now, can re-implement better, later on */
2132 WM_modalkeymap_add_item(keymap, LEFTMOUSE, KM_RELEASE, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ROTATE);
2133 WM_modalkeymap_add_item(keymap, LEFTCTRLKEY, KM_RELEASE, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ROTATE);
2134 WM_modalkeymap_add_item(keymap, LEFTSHIFTKEY, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_MOVE);
2137 /* assign map to operators */
2138 WM_modalkeymap_assign(keymap, "VIEW3D_OT_zoom");
2141 static void view_zoom_mouseloc_camera(
2142 Scene *scene, View3D *v3d,
2143 ARegion *ar, float dfac, int mx, int my)
2145 RegionView3D *rv3d = ar->regiondata;
2146 const float zoomfac = BKE_screen_view3d_zoom_to_fac(rv3d->camzoom);
2147 const float zoomfac_new = CLAMPIS(zoomfac * (1.0f / dfac), RV3D_CAMZOOM_MIN_FACTOR, RV3D_CAMZOOM_MAX_FACTOR);
2148 const float camzoom_new = BKE_screen_view3d_zoom_from_fac(zoomfac_new);
2151 if (U.uiflag & USER_ZOOM_TO_MOUSEPOS) {
2153 rctf camera_frame_old;
2154 rctf camera_frame_new;
2156 const float pt_src[2] = {mx, my};
2160 ED_view3d_calc_camera_border(scene, ar, v3d, rv3d, &camera_frame_old, false);
2161 BLI_rctf_translate(&camera_frame_old, ar->winrct.xmin, ar->winrct.ymin);
2163 rv3d->camzoom = camzoom_new;
2164 CLAMP(rv3d->camzoom, RV3D_CAMZOOM_MIN, RV3D_CAMZOOM_MAX);
2166 ED_view3d_calc_camera_border(scene, ar, v3d, rv3d, &camera_frame_new, false);
2167 BLI_rctf_translate(&camera_frame_new, ar->winrct.xmin, ar->winrct.ymin);
2169 BLI_rctf_transform_pt_v(&camera_frame_new, &camera_frame_old, pt_dst, pt_src);
2170 sub_v2_v2v2(delta_px, pt_dst, pt_src);
2172 /* translate the camera offset using pixel space delta
2173 * mapped back to the camera (same logic as panning in camera view) */
2174 zoomfac_px = BKE_screen_view3d_zoom_to_fac(rv3d->camzoom) * 2.0f;
2176 rv3d->camdx += delta_px[0] / (ar->winx * zoomfac_px);
2177 rv3d->camdy += delta_px[1] / (ar->winy * zoomfac_px);
2178 CLAMP(rv3d->camdx, -1.0f, 1.0f);
2179 CLAMP(rv3d->camdy, -1.0f, 1.0f);
2182 rv3d->camzoom = camzoom_new;
2183 CLAMP(rv3d->camzoom, RV3D_CAMZOOM_MIN, RV3D_CAMZOOM_MAX);
2187 static void view_zoom_mouseloc_3d(ARegion *ar, float dfac, int mx, int my)
2189 RegionView3D *rv3d = ar->regiondata;
2190 const float dist_new = rv3d->dist * dfac;
2192 if (U.uiflag & USER_ZOOM_TO_MOUSEPOS) {
2200 negate_v3_v3(tpos, rv3d->ofs);
2202 mval_f[0] = (float)(((mx - ar->winrct.xmin) * 2) - ar->winx) / 2.0f;
2203 mval_f[1] = (float)(((my - ar->winrct.ymin) * 2) - ar->winy) / 2.0f;
2205 /* Project cursor position into 3D space */
2206 zfac = ED_view3d_calc_zfac(rv3d, tpos, NULL);
2207 ED_view3d_win_to_delta(ar, mval_f, dvec, zfac);
2209 /* Calculate view target position for dolly */
2210 add_v3_v3v3(tvec, tpos, dvec);
2213 /* Offset to target position and dolly */
2214 copy_v3_v3(rv3d->ofs, tvec);
2215 rv3d->dist = dist_new;
2217 /* Calculate final offset */
2218 madd_v3_v3v3fl(rv3d->ofs, tvec, dvec, dfac);
2221 rv3d->dist = dist_new;
2225 static float viewzoom_scale_value(
2227 const short viewzoom,
2228 const bool zoom_invert, const bool zoom_invert_force,
2229 const int xy[2], const int xy_orig[2],
2230 const float val, const float val_orig,
2231 double *r_timer_lastdraw)
2235 if (viewzoom == USER_ZOOM_CONT) {
2236 double time = PIL_check_seconds_timer();
2237 float time_step = (float)(time - *r_timer_lastdraw);
2240 if (U.uiflag & USER_ZOOM_HORIZ) {
2241 fac = (float)(xy_orig[0] - xy[0]);
2244 fac = (float)(xy_orig[1] - xy[1]);
2247 if (zoom_invert != zoom_invert_force) {
2252 zfac = 1.0f + ((fac / 20.0f) * time_step);
2253 *r_timer_lastdraw = time;
2255 else if (viewzoom == USER_ZOOM_SCALE) {
2256 /* method which zooms based on how far you move the mouse */
2258 const int ctr[2] = {
2259 BLI_rcti_cent_x(winrct),
2260 BLI_rcti_cent_y(winrct),
2262 float len_new = 5 + len_v2v2_int(ctr, xy);
2263 float len_old = 5 + len_v2v2_int(ctr, xy_orig);
2265 /* intentionally ignore 'zoom_invert' for scale */
2266 if (zoom_invert_force) {
2267 SWAP(float, len_new, len_old);
2270 zfac = val_orig * (len_old / max_ff(len_new, 1.0f)) / val;
2272 else { /* USER_ZOOM_DOLLY */
2276 if (U.uiflag & USER_ZOOM_HORIZ) {
2277 len_new += (winrct->xmax - xy[0]);
2278 len_old += (winrct->xmax - xy_orig[0]);
2281 len_new += (winrct->ymax - xy[1]);
2282 len_old += (winrct->ymax - xy_orig[1]);
2285 if (zoom_invert != zoom_invert_force) {
2286 SWAP(float, len_new, len_old);
2289 zfac = val_orig * (2.0f * ((len_new / max_ff(len_old, 1.0f)) - 1.0f) + 1.0f) / val;
2296 static void viewzoom_apply_camera(
2297 ViewOpsData *vod, const int xy[2],
2298 const short viewzoom, const bool zoom_invert)
2301 float zoomfac_prev = BKE_screen_view3d_zoom_to_fac(vod->camzoom_prev) * 2.0f;
2302 float zoomfac = BKE_screen_view3d_zoom_to_fac(vod->rv3d->camzoom) * 2.0f;
2304 zfac = viewzoom_scale_value(
2305 &vod->ar->winrct, viewzoom, zoom_invert, true, xy, &vod->origx,
2306 zoomfac, zoomfac_prev,
2307 &vod->timer_lastdraw);
2309 if (zfac != 1.0f && zfac != 0.0f) {
2310 /* calculate inverted, then invert again (needed because of camera zoom scaling) */
2312 view_zoom_mouseloc_camera(
2313 vod->scene, vod->v3d,
2314 vod->ar, zfac, vod->oldx, vod->oldy);
2317 ED_region_tag_redraw(vod->ar);
2320 static void viewzoom_apply_3d(
2321 ViewOpsData *vod, const int xy[2],
2322 const short viewzoom, const bool zoom_invert)
2325 float dist_range[2];
2327 ED_view3d_dist_range_get(vod->v3d, dist_range);
2329 zfac = viewzoom_scale_value(
2330 &vod->ar->winrct, viewzoom, zoom_invert, false, xy, &vod->origx,
2331 vod->rv3d->dist, vod->dist_prev,
2332 &vod->timer_lastdraw);
2335 const float zfac_min = dist_range[0] / vod->rv3d->dist;
2336 const float zfac_max = dist_range[1] / vod->rv3d->dist;
2337 CLAMP(zfac, zfac_min, zfac_max);
2339 view_zoom_mouseloc_3d(
2340 vod->ar, zfac, vod->oldx, vod->oldy);
2343 /* these limits were in old code too */
2344 CLAMP(vod->rv3d->dist, dist_range[0], dist_range[1]);
2346 if (vod->rv3d->viewlock & RV3D_BOXVIEW)
2347 view3d_boxview_sync(vod->sa, vod->ar);
2349 ED_view3d_camera_lock_sync(vod->v3d, vod->rv3d);
2351 ED_region_tag_redraw(vod->ar);
2354 static void viewzoom_apply(
2355 ViewOpsData *vod, const int xy[2],
2356 const short viewzoom, const bool zoom_invert)
2358 if ((vod->rv3d->persp == RV3D_CAMOB) &&
2359 (vod->rv3d->is_persp && ED_view3d_camera_lock_check(vod->v3d, vod->rv3d)) == 0)
2361 viewzoom_apply_camera(vod, xy, viewzoom, zoom_invert);
2364 viewzoom_apply_3d(vod, xy, viewzoom, zoom_invert);
2368 static int viewzoom_modal(bContext *C, wmOperator *op, const wmEvent *event)
2370 ViewOpsData *vod = op->customdata;
2371 short event_code = VIEW_PASS;
2372 bool use_autokey = false;
2373 int ret = OPERATOR_RUNNING_MODAL;
2375 /* execute the events */
2376 if (event->type == TIMER && event->customdata == vod->timer) {
2377 /* continuous zoom */
2378 event_code = VIEW_APPLY;
2380 else if (event->type == MOUSEMOVE) {
2381 event_code = VIEW_APPLY;
2383 else if (event->type == EVT_MODAL_MAP) {
2384 switch (event->val) {
2385 case VIEW_MODAL_CONFIRM:
2386 event_code = VIEW_CONFIRM;
2388 case VIEWROT_MODAL_SWITCH_MOVE:
2389 WM_operator_name_call(C, "VIEW3D_OT_move", WM_OP_INVOKE_DEFAULT, NULL);
2390 event_code = VIEW_CONFIRM;
2392 case VIEWROT_MODAL_SWITCH_ROTATE:
2393 WM_operator_name_call(C, "VIEW3D_OT_rotate", WM_OP_INVOKE_DEFAULT, NULL);
2394 event_code = VIEW_CONFIRM;
2398 else if (event->type == vod->origkey && event->val == KM_RELEASE) {
2399 event_code = VIEW_CONFIRM;
2402 if (event_code == VIEW_APPLY) {
2403 viewzoom_apply(vod, &event->x, U.viewzoom, (U.uiflag & USER_ZOOM_INVERT) != 0);
2404 if (ED_screen_animation_playing(CTX_wm_manager(C))) {
2408 else if (event_code == VIEW_CONFIRM) {
2409 ED_view3d_depth_tag_update(vod->rv3d);
2411 ret = OPERATOR_FINISHED;
2415 ED_view3d_camera_lock_autokey(vod->v3d, vod->rv3d, C, false, true);
2418 if (ret & OPERATOR_FINISHED) {
2419 viewops_data_free(C, op);
2425 static int viewzoom_exec(bContext *C, wmOperator *op)
2427 Scene *scene = CTX_data_scene(C);
2433 float dist_range[2];
2435 const int delta = RNA_int_get(op->ptr, "delta");
2438 if (op->customdata) {
2439 ViewOpsData *vod = op->customdata;
2445 sa = CTX_wm_area(C);
2446 ar = CTX_wm_region(C);
2449 v3d = sa->spacedata.first;
2450 rv3d = ar->regiondata;
2452 mx = RNA_struct_property_is_set(op->ptr, "mx") ? RNA_int_get(op->ptr, "mx") : ar->winx / 2;
2453 my = RNA_struct_property_is_set(op->ptr, "my") ? RNA_int_get(op->ptr, "my") : ar->winy / 2;
2455 use_cam_zoom = (rv3d->persp == RV3D_CAMOB) && !(rv3d->is_persp && ED_view3d_camera_lock_check(v3d, rv3d));
2457 ED_view3d_dist_range_get(v3d, dist_range);
2460 const float step = 1.2f;
2461 /* this min and max is also in viewmove() */
2463 view_zoom_mouseloc_camera(scene, v3d, ar, step, mx, my);
2466 if (rv3d->dist < dist_range[1]) {
2467 view_zoom_mouseloc_3d(ar, step, mx, my);
2472 const float step = 1.0f / 1.2f;
2474 view_zoom_mouseloc_camera(scene, v3d, ar, step, mx, my);
2477 if (rv3d->dist > dist_range[0]) {
2478 view_zoom_mouseloc_3d(ar, step, mx, my);
2483 if (rv3d->viewlock & RV3D_BOXVIEW)
2484 view3d_boxview_sync(sa, ar);
2486 ED_view3d_depth_tag_update(rv3d);
2488 ED_view3d_camera_lock_sync(v3d, rv3d);
2489 ED_view3d_camera_lock_autokey(v3d, rv3d, C, false, true);
2491 ED_region_tag_redraw(ar);
2493 viewops_data_free(C, op);
2495 return OPERATOR_FINISHED;
2498 /* this is an exact copy of viewzoom_modal_keymap */
2499 /* called in transform_ops.c, on each regeneration of keymaps */
2500 void viewdolly_modal_keymap(wmKeyConfig *keyconf)
2502 static EnumPropertyItem modal_items[] = {
2503 {VIEW_MODAL_CONFIRM, "CONFIRM", 0, "Confirm", ""},
2505 {VIEWROT_MODAL_SWITCH_ROTATE, "SWITCH_TO_ROTATE", 0, "Switch to Rotate"},
2506 {VIEWROT_MODAL_SWITCH_MOVE, "SWITCH_TO_MOVE", 0, "Switch to Move"},
2508 {0, NULL, 0, NULL, NULL}
2511 wmKeyMap *keymap = WM_modalkeymap_get(keyconf, "View3D Dolly Modal");
2513 /* this function is called for each spacetype, only needs to add map once */
2514 if (keymap && keymap->modal_items) return;
2516 keymap = WM_modalkeymap_add(keyconf, "View3D Dolly Modal", modal_items);
2518 /* items for modal map */
2519 WM_modalkeymap_add_item(keymap, MIDDLEMOUSE, KM_RELEASE, KM_ANY, 0, VIEW_MODAL_CONFIRM);
2520 WM_modalkeymap_add_item(keymap, ESCKEY, KM_PRESS, KM_ANY, 0, VIEW_MODAL_CONFIRM);
2522 /* disabled mode switching for now, can re-implement better, later on */
2524 WM_modalkeymap_add_item(keymap, LEFTMOUSE, KM_RELEASE, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ROTATE);
2525 WM_modalkeymap_add_item(keymap, LEFTCTRLKEY, KM_RELEASE, KM_ANY, 0, VIEWROT_MODAL_SWITCH_ROTATE);
2526 WM_modalkeymap_add_item(keymap, LEFTSHIFTKEY, KM_PRESS, KM_ANY, 0, VIEWROT_MODAL_SWITCH_MOVE);
2529 /* assign map to operators */
2530 WM_modalkeymap_assign(keymap, "VIEW3D_OT_dolly");
2533 /* viewdolly_invoke() copied this function, changes here may apply there */
2534 static int viewzoom_invoke(bContext *C, wmOperator *op, const wmEvent *event)
2538 /* makes op->customdata */
2539 viewops_data_alloc(C, op);
2540 viewops_data_create(C, op, event, false);
2541 vod = op->customdata;
2543 ED_view3d_smooth_view_force_finish(C, vod->v3d, vod->ar);
2545 /* if one or the other zoom position aren't set, set from event */
2546 if (!RNA_struct_property_is_set(op->ptr, "mx") || !RNA_struct_property_is_set(op->ptr, "my")) {
2547 RNA_int_set(op->ptr, "mx", event->x);
2548 RNA_int_set(op->ptr, "my", event->y);
2551 if (RNA_struct_property_is_set(op->ptr, "delta")) {
2552 viewzoom_exec(C, op);
2555 if (event->type == MOUSEZOOM || event->type == MOUSEPAN) {
2557 if (U.uiflag & USER_ZOOM_HORIZ) {
2558 vod->origx = vod->oldx = event->x;
2559 viewzoom_apply(vod, &event->prevx, USER_ZOOM_DOLLY, (U.uiflag & USER_ZOOM_INVERT) != 0);
2562 /* Set y move = x move as MOUSEZOOM uses only x axis to pass magnification value */
2563 vod->origy = vod->oldy = vod->origy + event->x - event->prevx;
2564 viewzoom_apply(vod, &event->prevx, USER_ZOOM_DOLLY, (U.uiflag & USER_ZOOM_INVERT) != 0);
2566 ED_view3d_camera_lock_autokey(vod->v3d, vod->rv3d, C, false, true);
2568 ED_view3d_depth_tag_update(vod->rv3d);
2570 viewops_data_free(C, op);
2571 return OPERATOR_FINISHED;
2574 if (U.viewzoom == USER_ZOOM_CONT) {
2575 /* needs a timer to continue redrawing */
2576 vod->timer = WM_event_add_timer(CTX_wm_manager(C), CTX_wm_window(C), TIMER, 0.01f);
2577 vod->timer_lastdraw = PIL_check_seconds_timer();
2580 /* add temp handler */
2581 WM_event_add_modal_handler(C, op);
2583 return OPERATOR_RUNNING_MODAL;
2586 return OPERATOR_FINISHED;
2589 static void viewzoom_cancel(bContext *C, wmOperator *op)
2591 viewops_data_free(C, op);
2594 void VIEW3D_OT_zoom(wmOperatorType *ot)
2599 ot->name = "Zoom View";
2600 ot->description = "Zoom in/out in the view";
2601 ot->idname = "VIEW3D_OT_zoom";
2604 ot->invoke = viewzoom_invoke;
2605 ot->exec = viewzoom_exec;
2606 ot->modal = viewzoom_modal;
2607 ot->poll = ED_operator_region_view3d_active;
2608 ot->cancel = viewzoom_cancel;
2611 ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR;
2613 RNA_def_int(ot->srna, "delta", 0, INT_MIN, INT_MAX, "Delta", "", INT_MIN, INT_MAX);
2614 prop = RNA_def_int(ot->srna, "mx", 0, 0, INT_MAX, "Zoom Position X", "", 0, INT_MAX);
2615 RNA_def_property_flag(prop, PROP_HIDDEN);
2616 prop = RNA_def_int(ot->srna, "my", 0, 0, INT_MAX, "Zoom Position Y", "", 0, INT_MAX);
2617 RNA_def_property_flag(prop, PROP_HIDDEN);
2621 /* ************************ viewdolly ******************************** */
2622 static bool viewdolly_offset_lock_check(bContext *C, wmOperator *op)
2624 View3D *v3d = CTX_wm_view3d(C);
2625 RegionView3D *rv3d = CTX_wm_region_view3d(C);
2626 if (ED_view3d_offset_lock_check(v3d, rv3d)) {
2627 BKE_report(op->reports, RPT_WARNING, "Cannot dolly when the view offset is locked");
2635 static void view_dolly_mouseloc(ARegion *ar, float orig_ofs[3], float dvec[3], float dfac)
2637 RegionView3D *rv3d = ar->regiondata;
2638 madd_v3_v3v3fl(rv3d->ofs, orig_ofs, dvec, -(1.0f - dfac));
2641 static void viewdolly_apply(ViewOpsData *vod, int x, int y, const short zoom_invert)
2648 if (U.uiflag & USER_ZOOM_HORIZ) {
2649 len1 = (vod->ar->winrct.xmax - x) + 5;
2650 len2 = (vod->ar->winrct.xmax - vod->origx) + 5;
2653 len1 = (vod->ar->winrct.ymax - y) + 5;
2654 len2 = (vod->ar->winrct.ymax - vod->origy) + 5;
2657 SWAP(float, len1, len2);
2659 zfac = 1.0f + ((len1 - len2) * 0.01f * vod->rv3d->dist);
2663 view_dolly_mouseloc(vod->ar, vod->ofs, vod->mousevec, zfac);
2665 if (vod->rv3d->viewlock & RV3D_BOXVIEW)
2666 view3d_boxview_sync(vod->sa, vod->ar);
2668 ED_view3d_camera_lock_sync(vod->v3d, vod->rv3d);
2670 ED_region_tag_redraw(vod->ar);
2674 static int viewdolly_modal(bContext *C, wmOperator *op, const wmEvent *event)
2676 ViewOpsData *vod = op->customdata;
2677 short event_code = VIEW_PASS;
2678 bool use_autokey = false;
2679 int ret = OPERATOR_RUNNING_MODAL;
2681 /* execute the events */
2682 if (event->type == MOUSEMOVE) {
2683 event_code = VIEW_APPLY;
2685 else if (event->type == EVT_MODAL_MAP) {
2686 switch (event->val) {
2687 case VIEW_MODAL_CONFIRM:
2688 event_code = VIEW_CONFIRM;
2690 case VIEWROT_MODAL_SWITCH_MOVE:
2691 WM_operator_name_call(C, "VIEW3D_OT_move", WM_OP_INVOKE_DEFAULT, NULL);
2692 event_code = VIEW_CONFIRM;
2694 case VIEWROT_MODAL_SWITCH_ROTATE:
2695 WM_operator_name_call(C, "VIEW3D_OT_rotate", WM_OP_INVOKE_DEFAULT, NULL);
2696 event_code = VIEW_CONFIRM;
2700 else if (event->type == vod->origkey && event->val == KM_RELEASE) {
2701 event_code = VIEW_CONFIRM;
2704 if (event_code == VIEW_APPLY) {
2705 viewdolly_apply(vod, event->x, event->y, (U.uiflag & USER_ZOOM_INVERT) != 0);
2706 if (ED_screen_animation_playing(CTX_wm_manager(C))) {
2710 else if (event_code == VIEW_CONFIRM) {
2711 ED_view3d_depth_tag_update(vod->rv3d);
2713 ret = OPERATOR_FINISHED;
2717 ED_view3d_camera_lock_autokey(vod->v3d, vod->rv3d, C, false, true);
2720 if (ret & OPERATOR_FINISHED) {
2721 viewops_data_free(C, op);
2727 static int viewdolly_exec(bContext *C, wmOperator *op)
2735 const int delta = RNA_int_get(op->ptr, "delta");
2737 if (op->customdata) {
2738 ViewOpsData *vod = op->customdata;
2742 copy_v3_v3(mousevec, vod->mousevec);
2745 sa = CTX_wm_area(C);
2746 ar = CTX_wm_region(C);
2747 negate_v3_v3(mousevec, ((RegionView3D *)ar->regiondata)->viewinv[2]);
2748 normalize_v3(mousevec);
2751 v3d = sa->spacedata.first;
2752 rv3d = ar->regiondata;
2754 /* overwrite the mouse vector with the view direction (zoom into the center) */
2755 if ((U.uiflag & USER_ZOOM_TO_MOUSEPOS) == 0) {
2756 normalize_v3_v3(mousevec, rv3d->viewinv[2]);
2760 view_dolly_mouseloc(ar, rv3d->ofs, mousevec, 0.2f);
2763 view_dolly_mouseloc(ar, rv3d->ofs, mousevec, 1.8f);
2766 if (rv3d->viewlock & RV3D_BOXVIEW)
2767 view3d_boxview_sync(sa, ar);
2769 ED_view3d_depth_tag_update(rv3d);
2771 ED_view3d_camera_lock_sync(v3d, rv3d);
2773 ED_region_tag_redraw(ar);
2775 viewops_data_free(C, op);
2777 return OPERATOR_FINISHED;
2780 /* copied from viewzoom_invoke(), changes here may apply there */
2781 static int viewdolly_invoke(bContext *C, wmOperator *op, const wmEvent *event)
2785 if (viewdolly_offset_lock_check(C, op))
2786 return OPERATOR_CANCELLED;
2788 /* makes op->customdata */
2789 viewops_data_alloc(C, op);
2790 vod = op->customdata;
2792 /* poll should check but in some cases fails, see poll func for details */
2793 if (vod->rv3d->viewlock & RV3D_LOCKED) {
2794 viewops_data_free(C, op);
2795 return OPERATOR_PASS_THROUGH;
2798 ED_view3d_smooth_view_force_finish(C, vod->v3d, vod->ar);
2800 /* needs to run before 'viewops_data_create' so the backup 'rv3d->ofs' is correct */
2801 /* switch from camera view when: */
2802 if (vod->rv3d->persp != RV3D_PERSP) {
2803 if (vod->rv3d->persp == RV3D_CAMOB) {
2804 /* ignore rv3d->lpersp because dolly only makes sense in perspective mode */
2805 view3d_persp_switch_from_camera(vod->v3d, vod->rv3d, RV3D_PERSP);
2808 vod->rv3d->persp = RV3D_PERSP;
2810 ED_region_tag_redraw(vod->ar);
2813 viewops_data_create(C, op, event, false);
2816 /* if one or the other zoom position aren't set, set from event */
2817 if (!RNA_struct_property_is_set(op->ptr, "mx") || !RNA_struct_property_is_set(op->ptr, "my")) {
2818 RNA_int_set(op->ptr, "mx", event->x);
2819 RNA_int_set(op->ptr, "my", event->y);
2822 if (RNA_struct_property_is_set(op->ptr, "delta")) {
2823 viewdolly_exec(C, op);
2826 /* overwrite the mouse vector with the view direction (zoom into the center) */
2827 if ((U.uiflag & USER_ZOOM_TO_MOUSEPOS) == 0) {
2828 negate_v3_v3(vod->mousevec, vod->rv3d->viewinv[2]);
2829 normalize_v3(vod->mousevec);
2832 if (event->type == MOUSEZOOM) {
2833 /* Bypass Zoom invert flag for track pads (pass false always) */
2835 if (U.uiflag & USER_ZOOM_HORIZ) {
2836 vod->origx = vod->oldx = event->x;
2837 viewdolly_apply(vod, event->prevx, event->prevy, (U.uiflag & USER_ZOOM_INVERT) == 0);
2841 /* Set y move = x move as MOUSEZOOM uses only x axis to pass magnification value */
2842 vod->origy = vod->oldy = vod->origy + event->x - event->prevx;
2843 viewdolly_apply(vod, event->prevx, event->prevy, (U.uiflag & USER_ZOOM_INVERT) == 0);
2845 ED_view3d_depth_tag_update(vod->rv3d);
2847 viewops_data_free(C, op);
2848 return OPERATOR_FINISHED;
2851 /* add temp handler */
2852 WM_event_add_modal_handler(C, op);
2854 return OPERATOR_RUNNING_MODAL;
2857 return OPERATOR_FINISHED;
2860 static void viewdolly_cancel(bContext *C, wmOperator *op)
2862 viewops_data_free(C, op);
2865 void VIEW3D_OT_dolly(wmOperatorType *ot)
2868 ot->name = "Dolly View";
2869 ot->description = "Dolly in/out in the view";
2870 ot->idname = "VIEW3D_OT_dolly";
2873 ot->invoke = viewdolly_invoke;
2874 ot->exec = viewdolly_exec;
2875 ot->modal = viewdolly_modal;
2876 ot->poll = ED_operator_region_view3d_active;
2877 ot->cancel = viewdolly_cancel;
2880 ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR;
2882 RNA_def_int(ot->srna, "delta", 0, INT_MIN, INT_MAX, "Delta", "", INT_MIN, INT_MAX);
2883 RNA_def_int(ot->srna, "mx", 0, 0, INT_MAX, "Zoom Position X", "", 0, INT_MAX);
2884 RNA_def_int(ot->srna, "my", 0, 0, INT_MAX, "Zoom Position Y", "", 0, INT_MAX);
2887 static void view3d_from_minmax(bContext *C, View3D *v3d, ARegion *ar,
2888 const float min[3], const float max[3],
2889 bool ok_dist, const int smooth_viewtx)
2891 RegionView3D *rv3d = ar->regiondata;
2895 ED_view3d_smooth_view_force_finish(C, v3d, ar);
2901 sub_v3_v3v3(afm, max, min);
2902 size = max_fff(afm[0], afm[1], afm[2]);
2907 if (rv3d->is_persp) {
2908 if (rv3d->persp == RV3D_CAMOB && ED_view3d_camera_lock_check(v3d, rv3d)) {
2916 if (size < 0.0001f) {
2917 /* bounding box was a single point so do not zoom */
2921 /* adjust zoom so it looks nicer */
2927 new_dist = ED_view3d_radius_to_dist(v3d, ar, persp, true, (size / 2) * VIEW3D_MARGIN);
2928 if (rv3d->is_persp) {
2929 /* don't zoom closer than the near clipping plane */
2930 new_dist = max_ff(new_dist, v3d->near * 1.5f);
2935 mid_v3_v3v3(new_ofs, min, max);
2938 if (rv3d->persp == RV3D_CAMOB && !ED_view3d_camera_lock_check(v3d, rv3d)) {
2939 rv3d->persp = RV3D_PERSP;
2940 ED_view3d_smooth_view(
2941 C, v3d, ar, smooth_viewtx,
2942 &(const V3D_SmoothParams) {
2943 .camera_old = v3d->camera, .ofs = new_ofs,
2944 .dist = ok_dist ? &new_dist : NULL});
2947 ED_view3d_smooth_view(
2948 C, v3d, ar, smooth_viewtx,
2949 &(const V3D_SmoothParams) {.ofs = new_ofs, .dist = ok_dist ? &new_dist : NULL});
2952 /* smooth view does viewlock RV3D_BOXVIEW copy */
2955 /* same as view3d_from_minmax but for all regions (except cameras) */
2956 static void view3d_from_minmax_multi(bContext *C, View3D *v3d,
2957 const float min[3], const float max[3],
2958 const bool ok_dist, const int smooth_viewtx)
2960 ScrArea *sa = CTX_wm_area(C);
2962 for (ar = sa->regionbase.first; ar; ar = ar->next) {
2963 if (ar->regiontype == RGN_TYPE_WINDOW) {
2964 RegionView3D *rv3d = ar->regiondata;
2965 /* when using all regions, don't jump out of camera view,
2966 * but _do_ allow locked cameras to be moved */
2967 if ((rv3d->persp != RV3D_CAMOB) || ED_view3d_camera_lock_check(v3d, rv3d)) {
2968 view3d_from_minmax(C, v3d, ar, min, max, ok_dist, smooth_viewtx);
2974 static int view3d_all_exec(bContext *C, wmOperator *op) /* was view3d_home() in 2.4x */
2976 ARegion *ar = CTX_wm_region(C);
2977 View3D *v3d = CTX_wm_view3d(C);
2978 Scene *scene = CTX_data_scene(C);
2981 const bool use_all_regions = RNA_boolean_get(op->ptr, "use_all_regions");
2982 const bool skip_camera = (ED_view3d_camera_lock_check(v3d, ar->regiondata) ||
2983 /* any one of the regions may be locked */
2984 (use_all_regions && v3d->flag2 & V3D_LOCK_CAMERA));
2985 const bool center = RNA_boolean_get(op->ptr, "center");
2986 const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
2988 float min[3], max[3];
2989 bool changed = false;
2992 /* in 2.4x this also move the cursor to (0, 0, 0) (with shift+c). */
2993 curs = ED_view3d_cursor3d_get(scene, v3d);
2999 INIT_MINMAX(min, max);
3002 for (base = scene->base.first; base; base = base->next) {
3003 if (BASE_VISIBLE(v3d, base)) {
3006 if (skip_camera && base->object == v3d->camera) {
3010 BKE_object_minmax(base->object, min, max, false);
3014 ED_region_tag_redraw(ar);
3015 /* TODO - should this be cancel?
3016 * I think no, because we always move the cursor, with or without
3017 * object, but in this case there is no change in the scene,
3018 * only the cursor so I choice a ED_region_tag like
3019 * view3d_smooth_view do for the center_cursor.
3022 return OPERATOR_FINISHED;
3025 if (use_all_regions) {
3026 view3d_from_minmax_multi(C, v3d, min, max, true, smooth_viewtx);
3029 view3d_from_minmax(C, v3d, ar, min, max, true, smooth_viewtx);
3032 return OPERATOR_FINISHED;
3036 void VIEW3D_OT_view_all(wmOperatorType *ot)
3041 ot->name = "View All";
3042 ot->description = "View all objects in scene";
3043 ot->idname = "VIEW3D_OT_view_all";
3046 ot->exec = view3d_all_exec;
3047 ot->poll = ED_operator_region_view3d_active;
3052 prop = RNA_def_boolean(ot->srna, "use_all_regions", 0, "All Regions", "View selected for all regions");
3053 RNA_def_property_flag(prop, PROP_SKIP_SAVE);
3054 RNA_def_boolean(ot->srna, "center", 0, "Center", "");
3057 /* like a localview without local!, was centerview() in 2.4x */
3058 static int viewselected_exec(bContext *C, wmOperator *op)
3060 ARegion *ar = CTX_wm_region(C);
3061 View3D *v3d = CTX_wm_view3d(C);
3062 Scene *scene = CTX_data_scene(C);
3063 bGPdata *gpd = CTX_data_gpencil_data(C);
3064 const bool is_gp_edit = ((gpd) && (gpd->flag & GP_DATA_STROKE_EDITMODE));
3066 Object *obedit = CTX_data_edit_object(C);
3067 float min[3], max[3];
3068 bool ok = false, ok_dist = true;
3069 const bool use_all_regions = RNA_boolean_get(op->ptr, "use_all_regions");
3070 const bool skip_camera = (ED_view3d_camera_lock_check(v3d, ar->regiondata) ||
3071 /* any one of the regions may be locked */
3072 (use_all_regions && v3d->flag2 & V3D_LOCK_CAMERA));
3073 const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
3075 INIT_MINMAX(min, max);
3081 if (ob && (ob->mode & OB_MODE_WEIGHT_PAINT)) {
3082 /* hard-coded exception, we look for the one selected armature */
3083 /* this is weak code this way, we should make a generic active/selection callback interface once... */
3085 for (base = scene->base.first; base; base = base->next) {
3086 if (TESTBASELIB(v3d, base)) {
3087 if (base->object->type == OB_ARMATURE)
3088 if (base->object->mode & OB_MODE_POSE)
3098 CTX_DATA_BEGIN(C, bGPDstroke *, gps, editable_gpencil_strokes)
3100 /* we're only interested in selected points here... */
3101 if ((gps->flag & GP_STROKE_SELECT) && (gps->flag & GP_STROKE_3DSPACE)) {
3102 if (ED_gpencil_stroke_minmax(gps, true, min, max)) {
3110 ok = ED_view3d_minmax_verts(obedit, min, max); /* only selected */
3112 else if (ob && (ob->mode & OB_MODE_POSE)) {
3113 ok = BKE_pose_minmax(ob, min, max, true, true);
3115 else if (BKE_paint_select_face_test(ob)) {
3116 ok = paintface_minmax(ob, min, max);
3118 else if (ob && (ob->mode & OB_MODE_PARTICLE_EDIT)) {
3119 ok = PE_minmax(scene, min, max);
3122 (ob->mode & (OB_MODE_SCULPT | OB_MODE_VERTEX_PAINT | OB_MODE_WEIGHT_PAINT | OB_MODE_TEXTURE_PAINT)))
3124 BKE_paint_stroke_get_average(scene, ob, min);
3125 copy_v3_v3(max, min);
3127 ok_dist = 0; /* don't zoom */
3131 for (base = FIRSTBASE; base; base = base->next) {
3132 if (TESTBASE(v3d, base)) {
3134 if (skip_camera && base->object == v3d->camera) {
3138 /* account for duplis */
3139 if (BKE_object_minmax_dupli(scene, base->object, min, max, false) == 0)
3140 BKE_object_minmax(base->object, min, max, false); /* use if duplis not found */
3148 return OPERATOR_FINISHED;
3151 if (use_all_regions) {
3152 view3d_from_minmax_multi(C, v3d, min, max, ok_dist, smooth_viewtx);
3155 view3d_from_minmax(C, v3d, ar, min, max, ok_dist, smooth_viewtx);
3158 return OPERATOR_FINISHED;
3161 void VIEW3D_OT_view_selected(wmOperatorType *ot)
3166 ot->name = "View Selected";
3167 ot->description = "Move the view to the selection center";
3168 ot->idname = "VIEW3D_OT_view_selected";
3171 ot->exec = viewselected_exec;
3172 ot->poll = ED_operator_region_view3d_active;
3178 prop = RNA_def_boolean(ot->srna, "use_all_regions", 0, "All Regions", "View selected for all regions");
3179 RNA_def_property_flag(prop, PROP_SKIP_SAVE);
3182 static int view_lock_clear_exec(bContext *C, wmOperator *UNUSED(op))
3184 View3D *v3d = CTX_wm_view3d(C);
3187 ED_view3D_lock_clear(v3d);
3189 WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, v3d);
3191 return OPERATOR_FINISHED;
3194 return OPERATOR_CANCELLED;
3198 void VIEW3D_OT_view_lock_clear(wmOperatorType *ot)
3202 ot->name = "View Lock Clear";
3203 ot->description = "Clear all view locking";
3204 ot->idname = "VIEW3D_OT_view_lock_clear";