Blender V4.5
view3d_navigate_view_ndof.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8#include "BLI_bounds.hh"
9#include "BLI_math_geom.h"
10#include "BLI_math_matrix.hh"
11#include "BLI_math_rotation.h"
12#include "BLI_math_vector.h"
13#include "BLI_rect.h"
14
15#include "BKE_layer.hh"
16
18
19#include "WM_api.hh"
20
21#include "ED_screen.hh"
22
23#include "view3d_intern.hh"
24#include "view3d_navigate.hh" /* own include */
25
26using blender::Bounds;
27using blender::float3;
28
29#ifdef WITH_INPUT_NDOF
30static bool ndof_orbit_center_is_valid(const RegionView3D *rv3d, const float3 &center);
31static bool ndof_orbit_center_is_auto(const View3D *v3d, const RegionView3D *rv3d);
32#endif
33
34/* -------------------------------------------------------------------- */
37
38#ifdef WITH_INPUT_NDOF
39
41static bool is_bounding_box_in_frustum(const float projmat[4][4],
42 const Bounds<float3> &bounding_box)
43{
44 float planes[4][4];
45 planes_from_projmat(projmat, planes[0], planes[1], planes[2], planes[3], nullptr, nullptr);
46 int ret = isect_aabb_planes_v3(planes, 4, bounding_box.min, bounding_box.max);
47
49}
50
51enum {
52 HAS_TRANSLATE = (1 << 0),
53 HAS_ROTATE = (1 << 0),
54};
55
56static bool ndof_has_translate(const wmNDOFMotionData &ndof,
57 const View3D *v3d,
58 const RegionView3D *rv3d)
59{
60 return !is_zero_v3(ndof.tvec) && !ED_view3d_offset_lock_check(v3d, rv3d);
61}
62
63static bool ndof_has_translate_pan(const wmNDOFMotionData &ndof,
64 const View3D *v3d,
65 const RegionView3D *rv3d)
66{
67 return WM_event_ndof_translation_has_pan(ndof) && !ED_view3d_offset_lock_check(v3d, rv3d);
68}
69
70static bool ndof_has_rotate(const wmNDOFMotionData &ndof, const RegionView3D *rv3d)
71{
72 return !is_zero_v3(ndof.rvec) && ((RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ROTATION) == 0);
73}
74
78static float view3d_ndof_pan_speed_calc_ex(RegionView3D *rv3d, const float depth_pt[3])
79{
80 float speed = rv3d->pixsize * NDOF_PIXELS_PER_SECOND;
81
82 if (rv3d->is_persp) {
83 speed *= ED_view3d_calc_zfac(rv3d, depth_pt);
84 }
85
86 return speed;
87}
88
89static float view3d_ndof_pan_speed_calc_from_dist(RegionView3D *rv3d, const float dist)
90{
91 float viewinv[4];
92 float tvec[3];
93
94 BLI_assert(dist >= 0.0f);
95
96 copy_v3_fl3(tvec, 0.0f, 0.0f, dist);
97 /* rv3d->viewinv isn't always valid */
98# if 0
99 mul_mat3_m4_v3(rv3d->viewinv, tvec);
100# else
101 invert_qt_qt_normalized(viewinv, rv3d->viewquat);
102 mul_qt_v3(viewinv, tvec);
103# endif
104
105 return view3d_ndof_pan_speed_calc_ex(rv3d, tvec);
106}
107
108static float view3d_ndof_pan_speed_calc(RegionView3D *rv3d)
109{
110 float tvec[3];
113 {
114 negate_v3_v3(tvec, rv3d->ndof_ofs);
115 }
116 else {
117 negate_v3_v3(tvec, rv3d->ofs);
118 }
119
120 return view3d_ndof_pan_speed_calc_ex(rv3d, tvec);
121}
122
129static void view3d_ndof_pan_zoom(const wmNDOFMotionData &ndof,
130 ScrArea *area,
131 ARegion *region,
132 const bool has_translate,
133 const bool has_zoom)
134{
135 RegionView3D *rv3d = static_cast<RegionView3D *>(region->regiondata);
136
137 if (has_translate == false && has_zoom == false) {
138 return;
139 }
140
141 blender::float3 pan_vec = WM_event_ndof_translation_get_for_navigation(ndof);
142 if (has_zoom) {
143 /* zoom with Z */
144
145 /* "zoom in" or "translate"? depends on zoom mode in user settings? */
146 if (pan_vec[2]) {
147 float zoom_distance = rv3d->dist * ndof.dt * pan_vec[2];
148 rv3d->dist += zoom_distance;
149 }
150
151 /* Zoom!
152 * velocity should be proportional to the linear velocity attained by rotational motion
153 * of same strength [got that?] proportional to `arclength = radius * angle`.
154 */
155
156 pan_vec[2] = 0.0f;
157 }
158 else {
159 /* dolly with Z */
160
161 /* all callers must check */
162 if (has_translate) {
164 }
165 }
166
167 if (has_translate) {
168
169 if (U.ndof_navigation_mode == NDOF_NAVIGATION_MODE_FLY) {
170 /* For "Fly Mode" translations we use arbitrary defined, constant
171 * speed values for each axis. Normally, these values are defined
172 * by the 3Dconnexion navigation library. To recreate original navigation
173 * experience, the translation speed values were picked experimentally here.
174 * This is intended to apply only for the "Fly Mode" (3D viewport). */
175 const float fly_speed[3] = {6.5f, 3.3f, 8.0f};
176 pan_vec[0] *= fly_speed[0] * ndof.dt;
177 pan_vec[1] *= fly_speed[1] * ndof.dt;
178 pan_vec[2] *= fly_speed[2] * ndof.dt;
179 }
180 else {
181 const float speed = view3d_ndof_pan_speed_calc(rv3d);
182 pan_vec *= speed * ndof.dt;
183 }
184
185 /* transform motion from view to world coordinates */
186 float view_inv[4];
187 invert_qt_qt_normalized(view_inv, rv3d->viewquat);
188 mul_qt_v3(view_inv, pan_vec);
189
190 /* move center of view opposite of hand motion (this is camera mode, not object mode) */
191 sub_v3_v3(rv3d->ofs, pan_vec);
192
193 if (RV3D_LOCK_FLAGS(rv3d) & RV3D_BOXVIEW) {
194 view3d_boxview_sync(area, region);
195 }
196 }
197}
198
199static void view3d_ndof_orbit(const wmNDOFMotionData &ndof,
200 ScrArea *area,
201 ARegion *region,
202 ViewOpsData *vod,
203 const bool apply_dyn_ofs)
204{
205 View3D *v3d = static_cast<View3D *>(area->spacedata.first);
206 RegionView3D *rv3d = static_cast<RegionView3D *>(region->regiondata);
207
208 float view_inv[4];
209
211
212 ED_view3d_persp_ensure(vod->depsgraph, v3d, region);
213
214 rv3d->view = RV3D_VIEW_USER;
215
216 invert_qt_qt_normalized(view_inv, rv3d->viewquat);
217
218 if (U.ndof_flag & NDOF_LOCK_HORIZON) {
219 /* Turntable view code adapted for 3D mouse use. */
220 float angle, quat[4];
221 float xvec[3] = {1, 0, 0};
222 float yvec[3] = {0, 1, 0};
223
224 /* only use XY, ignore Z */
225 blender::float3 rot = WM_event_ndof_rotation_get_for_navigation(ndof);
226
227 /* Determine the direction of the X vector (for rotating up and down). */
228 mul_qt_v3(view_inv, xvec);
229 /* Determine the direction of the Y vector (to check if the view is upside down). */
230 mul_qt_v3(view_inv, yvec);
231
232 /* Perform the up/down rotation */
233 angle = ndof.dt * rot[0];
234 axis_angle_to_quat(quat, xvec, angle);
235 mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, quat);
236
237 /* Perform the Z rotation. */
238 angle = ndof.dt * rot[1];
239
240 /* Flip the turntable angle when the view is upside down. */
241 if (yvec[2] < 0.0f) {
242 angle *= -1.0f;
243 }
244
245 /* Update the onscreen axis-angle indicator. */
246 rv3d->ndof_rot_angle = angle;
247 rv3d->ndof_rot_axis[0] = 0;
248 rv3d->ndof_rot_axis[1] = 0;
249 rv3d->ndof_rot_axis[2] = 1;
250
252 mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, quat);
253 }
254 else {
255 float quat[4];
256 float axis[3];
257 float angle = ndof.dt * WM_event_ndof_rotation_get_axis_angle_for_navigation(ndof, axis);
258
259 /* transform rotation axis from view to world coordinates */
260 mul_qt_v3(view_inv, axis);
261
262 /* Update the onscreen axis-angle indicator. */
263 rv3d->ndof_rot_angle = angle;
264 copy_v3_v3(rv3d->ndof_rot_axis, axis);
265
266 axis_angle_to_quat(quat, axis, angle);
267
268 /* apply rotation */
269 mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, quat);
270 }
271
272 if (apply_dyn_ofs) {
273 /* Use NDOF center as a dynamic offset. */
274 if (ndof_orbit_center_is_auto(v3d, rv3d)) {
275 if (rv3d->ndof_flag & RV3D_NDOF_OFS_IS_VALID) {
276 if (ndof_orbit_center_is_valid(vod->rv3d, -float3(rv3d->ndof_ofs))) {
277 vod->use_dyn_ofs = true;
278 copy_v3_v3(vod->dyn_ofs, rv3d->ndof_ofs);
279 }
280 else {
282 }
283 }
284 }
286 }
287}
288
289void view3d_ndof_fly(const wmNDOFMotionData &ndof,
290 View3D *v3d,
291 RegionView3D *rv3d,
292 const bool use_precision,
293 const short protectflag,
294 bool *r_has_translate,
295 bool *r_has_rotate)
296{
297 bool has_translate = ndof_has_translate(ndof, v3d, rv3d);
298 bool has_rotate = ndof_has_rotate(ndof, rv3d);
299
300 float view_inv[4];
301 invert_qt_qt_normalized(view_inv, rv3d->viewquat);
302
303 rv3d->ndof_rot_angle = 0.0f; /* Disable onscreen rotation indicator. */
304
305 if (has_translate) {
306 /* ignore real 'dist' since fly has its own speed settings,
307 * also its overwritten at this point. */
308 float speed = view3d_ndof_pan_speed_calc_from_dist(rv3d, 1.0f);
309 float trans_orig_y;
310
311 if (use_precision) {
312 speed *= 0.2f;
313 }
314
315 blender::float3 trans = (speed * ndof.dt) * WM_event_ndof_translation_get(ndof);
316 trans_orig_y = trans[1];
317
318 if (U.ndof_flag & NDOF_FLY_HELICOPTER) {
319 trans[1] = 0.0f;
320 }
321
322 /* transform motion from view to world coordinates */
323 mul_qt_v3(view_inv, trans);
324
325 if (U.ndof_flag & NDOF_FLY_HELICOPTER) {
326 /* replace world z component with device y (yes it makes sense) */
327 trans[2] = trans_orig_y;
328 }
329
330 if (rv3d->persp == RV3D_CAMOB) {
331 /* respect camera position locks */
332 if (protectflag & OB_LOCK_LOCX) {
333 trans[0] = 0.0f;
334 }
335 if (protectflag & OB_LOCK_LOCY) {
336 trans[1] = 0.0f;
337 }
338 if (protectflag & OB_LOCK_LOCZ) {
339 trans[2] = 0.0f;
340 }
341 }
342
343 if (!is_zero_v3(trans)) {
344 /* move center of view opposite of hand motion
345 * (this is camera mode, not object mode) */
346 sub_v3_v3(rv3d->ofs, trans);
347 has_translate = true;
348 }
349 else {
350 has_translate = false;
351 }
352 }
353
354 if (has_rotate) {
355 float rotation[4];
356 float axis[3];
357 float angle = ndof.dt * WM_event_ndof_rotation_get_axis_angle(ndof, axis);
358
359 if (fabsf(angle) > 0.0001f) {
360 has_rotate = true;
361
362 if (use_precision) {
363 angle *= 0.2f;
364 }
365
366 /* transform rotation axis from view to world coordinates */
367 mul_qt_v3(view_inv, axis);
368
369 /* apply rotation to view */
370 axis_angle_to_quat(rotation, axis, angle);
371 mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, rotation);
372
373 if (U.ndof_flag & NDOF_LOCK_HORIZON) {
374 /* force an upright viewpoint
375 * TODO: make this less... sudden */
376 float view_horizon[3] = {1.0f, 0.0f, 0.0f}; /* view +x */
377 float view_direction[3] = {0.0f, 0.0f, -1.0f}; /* view -z (into screen) */
378
379 /* find new inverse since viewquat has changed */
380 invert_qt_qt_normalized(view_inv, rv3d->viewquat);
381 /* could apply reverse rotation to existing view_inv to save a few cycles */
382
383 /* transform view vectors to world coordinates */
384 mul_qt_v3(view_inv, view_horizon);
385 mul_qt_v3(view_inv, view_direction);
386
387 /* find difference between view & world horizons
388 * true horizon lives in world xy plane, so look only at difference in z */
389 angle = -asinf(view_horizon[2]);
390
391 /* rotate view so view horizon = world horizon */
392 axis_angle_to_quat(rotation, view_direction, angle);
393 mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, rotation);
394 }
395
396 rv3d->view = RV3D_VIEW_USER;
397 }
398 else {
399 has_rotate = false;
400 }
401 }
402
403 *r_has_translate = has_translate;
404 *r_has_rotate = has_rotate;
405}
406
408
409/* -------------------------------------------------------------------- */
412
413static bool ndof_orbit_center_is_auto(const View3D *v3d, const RegionView3D *rv3d)
414{
416 return false;
417 }
418 if ((U.ndof_flag & NDOF_ORBIT_CENTER_AUTO) == 0) {
419 return false;
420 }
421 if (v3d->ob_center_cursor || v3d->ob_center) {
422 return false;
423 }
424
425 /* Check the caller is not calculating auto-center when there is no reason to do so. */
427 !((rv3d->persp == RV3D_CAMOB) && (v3d->flag2 & V3D_LOCK_CAMERA) == 0),
428 "This test should not run from a camera view unless the camera is locked to the viewport");
429 UNUSED_VARS_NDEBUG(rv3d);
430
431 return true;
432}
433
437static bool ndof_orbit_center_is_valid(const RegionView3D *rv3d, const float3 &center)
438{
439 /* NOTE: this is a fairly arbitrary check mainly to avoid obvious problems
440 * where the orbit center is going to seem buggy/unusable.
441 *
442 * Other cases could also be counted as invalid:
443 * - It's beyond the clip-end.
444 * - It's not inside the viewport frustum (with some margin perhaps).
445 *
446 * The value could also be clamped to make it valid however when function
447 * returns false the #RegionView3D::ofs is used instead, so it's not necessary
448 * to go to great lengths to attempt to use the value.
449 */
450 if (rv3d->is_persp) {
451 const float zfac = mul_project_m4_v3_zfac(rv3d->persmat, center);
452 if (zfac <= 0.0f) {
453 return false;
454 }
455 }
456
457 return true;
458}
459
460static std::optional<float3> ndof_orbit_center_calc_from_bounds(Depsgraph *depsgraph,
461 ScrArea *area,
462 ARegion *region)
463{
464 std::optional<Bounds<float3>> bounding_box = std::nullopt;
465
466 if (U.ndof_flag & NDOF_ORBIT_CENTER_SELECTED) {
467 bool do_zoom = false;
468 bounding_box = view3d_calc_minmax_selected(depsgraph, area, region, false, false, &do_zoom);
469 }
470 else {
471 bounding_box = view3d_calc_minmax_visible(depsgraph, area, region, false, false);
472 }
473
474 if (bounding_box.has_value()) {
475 const RegionView3D *rv3d = static_cast<RegionView3D *>(region->regiondata);
476
477 /* Scale down the bounding box to provide some offset */
478 bounding_box->scale_from_center(float3(0.8));
479
480 if (is_bounding_box_in_frustum(rv3d->persmat, *bounding_box)) {
481 /* TODO: for perspective views it would be good to clip the bounds by the
482 * view-point's plane, so the only the portion of the bounds in front of the
483 * view-point is taken into account when calculating the center. */
484 const float3 center = bounding_box->center();
485 if (ndof_orbit_center_is_valid(rv3d, center)) {
486 return center;
487 }
488 }
489 }
490 return std::nullopt;
491}
492
493static float ndof_read_zbuf(ARegion *region)
494{
496
497 /* Avoid allocating the whole depth buffer. */
498 ViewDepths depth_temp = {0};
499 {
500 /* Some small rectangle in the middle of the view3d region. */
501 rcti rect;
502 const int region_center[2] = {region->winx / 2, region->winy / 2};
503 BLI_rcti_init_pt_radius(&rect, region_center, 2);
504 view3d_depths_rect_create(region, &rect, &depth_temp);
505 }
506
507 /* Find the closest Z pixel. */
508 const float depth_near = view3d_depth_near(&depth_temp);
509
510 MEM_SAFE_FREE(depth_temp.depths);
511
512 return depth_near;
513}
514
515static std::optional<float3> ndof_orbit_center_calc_from_zbuf(Depsgraph *depsgraph,
516 ScrArea *area,
517 ARegion *region)
518{
519
520 const float depth_near = ndof_read_zbuf(region);
521 if (depth_near == FLT_MAX) {
522 return std::nullopt;
523 }
524 float region_center_x = region->winx / 2.0f;
525 float region_center_y = region->winy / 2.0f;
526 blender::float3 zbuf_center{};
527
528 if (!ED_view3d_unproject_v3(region, region_center_x, region_center_y, depth_near, zbuf_center)) {
529 return std::nullopt;
530 }
531
532 /* Since the center found with Z-buffer might be in some small distance from the mesh
533 * it's safer to scale the bounding box a little before testing if it contains that center. */
534 const float scale_margin = 1.05f;
535
536 /* Use the found center if either #NDOF_ORBIT_CENTER_SELECTED is not enabled,
537 * there are no selected objects center is within bounding box of selected objects. */
538 if ((U.ndof_flag & NDOF_ORBIT_CENTER_SELECTED) == 0) {
539 return zbuf_center;
540 }
541
544
545 if (!BKE_layer_collection_has_selected_objects(scene, view_layer, view_layer->active_collection))
546 {
547 return zbuf_center;
548 }
549
550 View3D *v3d = static_cast<View3D *>(area->spacedata.first);
551 if (view3d_calc_point_in_selected_bounds(depsgraph, view_layer, v3d, zbuf_center, scale_margin))
552 {
553 return zbuf_center;
554 }
555
556 return std::nullopt;
557}
558
559static std::optional<float3> ndof_orbit_center_calc(Depsgraph *depsgraph,
560 ScrArea *area,
561 ARegion *region)
562{
563 /* Auto orbit-center implements an intelligent way to dynamically choose the orbit-center
564 * based on objects on the scene and how close to the particular object is the camera.
565 *
566 * Auto center calculation algorithm works as following:
567 * 1) Calculate the bounding box of all objects in the scene
568 * 2) If at least 80% of that box is contained in view-port's camera frustum then:
569 * 2a) Store the center of that bounding box as the orbit-center.
570 * 3) Use Z buffer to find the depth under the middle of the view3d region
571 * 4) If some finite depth value was found then:
572 * 4a) Use that depth to unproject a point from the middle of the region to the 3D space
573 * 4b) Store that point as the Center of Rotation
574 * 5) Since no candidates were found, use the last stored value
575 * (when #RV3D_NDOF_OFS_IS_VALID is set).
576 */
577
578 std::optional<float3> center_test = ndof_orbit_center_calc_from_bounds(depsgraph, area, region);
579 if (!center_test.has_value()) {
580 center_test = ndof_orbit_center_calc_from_zbuf(depsgraph, area, region);
581 }
582 return center_test;
583}
584
586
587/* -------------------------------------------------------------------- */
590
595static wmOperatorStatus view3d_ndof_cameraview_pan_zoom(ViewOpsData *vod,
596 const wmNDOFMotionData &ndof)
597{
598 View3D *v3d = vod->v3d;
599 ARegion *region = vod->region;
600 RegionView3D *rv3d = vod->rv3d;
601
602 if (v3d->camera && (rv3d->persp == RV3D_CAMOB) && (v3d->flag2 & V3D_LOCK_CAMERA) == 0) {
603 /* pass */
604 }
605 else {
607 }
608
609 blender::float3 pan_vec = WM_event_ndof_translation_get_for_navigation(ndof);
610 const float pan_speed = NDOF_PIXELS_PER_SECOND;
611 const bool has_translate = !is_zero_v2(pan_vec);
612 const bool has_zoom = pan_vec[2] != 0.0f;
613
614 pan_vec *= ndof.dt;
615
616 /* NOTE: unlike image and clip views, the 2D pan doesn't have to be scaled by the zoom level.
617 * #ED_view3d_camera_view_pan already takes the zoom level into account. */
618 mul_v2_fl(pan_vec, pan_speed);
619
620 /* NOTE(@ideasman42): In principle rotating could pass through to regular
621 * non-camera NDOF behavior (exiting the camera-view and rotating).
622 * Disabled this block since in practice it's difficult to control NDOF devices
623 * to perform some rotation with absolutely no translation. Causing rotation to
624 * randomly exit from the user perspective. Adjusting the dead-zone could avoid
625 * the motion feeling *glitchy* although in my own tests even then it didn't work reliably.
626 * Leave rotating out of camera-view disabled unless it can be made to work reliably. */
627 if (!(has_translate || has_zoom)) {
628 // return OPERATOR_PASS_THROUGH;
629 }
630
631 bool changed = false;
632
633 if (has_translate) {
634 /* Use the X & Y of `pan_vec`. */
635 if (ED_view3d_camera_view_pan(region, pan_vec)) {
636 changed = true;
637 }
638 }
639
640 if (has_zoom) {
641 if (ED_view3d_camera_view_zoom_scale(rv3d, max_ff(0.0f, 1.0f - pan_vec[2]))) {
642 changed = true;
643 }
644 }
645
646 if (changed) {
647 ED_region_tag_redraw(region);
648 return OPERATOR_FINISHED;
649 }
650 return OPERATOR_CANCELLED;
651}
652
654
655/* -------------------------------------------------------------------- */
658
659static wmOperatorStatus ndof_orbit_invoke_impl(bContext *C,
660 ViewOpsData *vod,
661 const wmEvent *event,
662 PointerRNA * /*ptr*/)
663{
664 if (event->type != NDOF_MOTION) {
665 return OPERATOR_CANCELLED;
666 }
667
668 const Depsgraph *depsgraph = vod->depsgraph;
669 View3D *v3d = vod->v3d;
670 RegionView3D *rv3d = vod->rv3d;
671 char xform_flag = 0;
672
673 const wmNDOFMotionData &ndof = *static_cast<const wmNDOFMotionData *>(event->customdata);
674
675 /* off by default, until changed later this function */
676 rv3d->ndof_rot_angle = 0.0f;
677
678 if (ndof.progress != P_FINISHING) {
679 const bool has_rotation = ndof_has_rotate(ndof, rv3d);
680 /* if we can't rotate, fall back to translate (locked axis views) */
681 const bool has_translate = (RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ROTATION) &&
682 ndof_has_translate(ndof, v3d, rv3d);
683 const bool has_zoom = (rv3d->is_persp == false) && WM_event_ndof_translation_has_zoom(ndof);
684
685 if (has_translate || has_zoom) {
686 view3d_ndof_pan_zoom(ndof, vod->area, vod->region, has_translate, has_zoom);
687 xform_flag |= HAS_TRANSLATE;
688 }
689
690 if (has_rotation) {
691 view3d_ndof_orbit(ndof, vod->area, vod->region, vod, true);
692 xform_flag |= HAS_ROTATE;
693 }
694 }
695
697 if (xform_flag) {
699 v3d, rv3d, C, xform_flag & HAS_ROTATE, xform_flag & HAS_TRANSLATE);
700 }
701
703
704 return OPERATOR_FINISHED;
705}
706
707static wmOperatorStatus ndof_orbit_invoke(bContext *C, wmOperator *op, const wmEvent *event)
708{
709 if (event->type != NDOF_MOTION) {
710 return OPERATOR_CANCELLED;
711 }
712
713 return view3d_navigate_invoke_impl(C, op, event, &ViewOpsType_ndof_orbit);
714}
715
716void VIEW3D_OT_ndof_orbit(wmOperatorType *ot)
717{
718 /* identifiers */
719 ot->name = "NDOF Orbit View";
720 ot->description = "Orbit the view using the 3D mouse";
721 ot->idname = ViewOpsType_ndof_orbit.idname;
722
723 /* API callbacks. */
724 ot->invoke = ndof_orbit_invoke;
726
727 /* flags */
728 ot->flag = 0;
729}
730
732
733/* -------------------------------------------------------------------- */
736
737static wmOperatorStatus ndof_orbit_zoom_invoke_impl(bContext *C,
738 ViewOpsData *vod,
739 const wmEvent *event,
740 PointerRNA * /*ptr*/)
741{
742 if (event->type != NDOF_MOTION) {
743 return OPERATOR_CANCELLED;
744 }
745
746 const wmNDOFMotionData &ndof = *static_cast<const wmNDOFMotionData *>(event->customdata);
747
748 if (U.ndof_flag & NDOF_CAMERA_PAN_ZOOM) {
749 const wmOperatorStatus camera_retval = view3d_ndof_cameraview_pan_zoom(vod, ndof);
750 if (camera_retval != OPERATOR_PASS_THROUGH) {
751 return camera_retval;
752 }
753 }
754
755 View3D *v3d = vod->v3d;
756 RegionView3D *rv3d = vod->rv3d;
757 char xform_flag = 0;
758
759 /* off by default, until changed later this function */
760 rv3d->ndof_rot_angle = 0.0f;
761
762 if (ndof.progress == P_FINISHING) {
763 /* pass */
764 }
765 else if (ndof.progress == P_STARTING) {
766 if (ndof_orbit_center_is_auto(v3d, rv3d)) {
767 /* If center was recalculated then update the point location for drawing. */
768 if (std::optional<float3> center_test = ndof_orbit_center_calc(
769 vod->depsgraph, vod->area, vod->region))
770 {
771 negate_v3_v3(rv3d->ndof_ofs, center_test.value());
772 /* When `ndof_ofs` is set `rv3d->dist` should be set based on distance to `ndof_ofs`.
773 * Without this the user is unable to zoom to the `ndof_ofs` point. See: #134732. */
774 if (rv3d->is_persp) {
775 const float dist_min = ED_view3d_dist_soft_min_get(v3d, true);
776 if (!ED_view3d_distance_set_from_location(rv3d, center_test.value(), dist_min)) {
777 ED_view3d_distance_set(rv3d, dist_min);
778 }
779 }
781 }
782 }
783 }
784 else if ((rv3d->persp == RV3D_ORTHO) && RV3D_VIEW_IS_AXIS(rv3d->view)) {
785 /* if we can't rotate, fall back to translate (locked axis views) */
786 const bool has_translate = ndof_has_translate(ndof, v3d, rv3d);
787 const bool has_zoom = WM_event_ndof_translation_has_zoom(ndof) &&
789
790 if (has_translate || has_zoom) {
791 view3d_ndof_pan_zoom(ndof, vod->area, vod->region, has_translate, true);
792 xform_flag |= HAS_TRANSLATE;
793 }
794 }
795 else {
796 /* NOTE: based on feedback from #67579, users want to have pan and orbit enabled at once.
797 * It's arguable that orbit shouldn't pan (since we have a pan only operator),
798 * so if there are users who like to separate orbit/pan operations - it can be a preference. */
799 const bool is_orbit_around_pivot = NDOF_IS_ORBIT_AROUND_CENTER_MODE(&U) ||
801 const bool has_rotation = ndof_has_rotate(ndof, rv3d);
802 bool has_translate, has_zoom;
803
804 if (is_orbit_around_pivot) {
805 /* Orbit preference or forced lock (Z zooms). */
806 has_translate = ndof_has_translate_pan(ndof, v3d, rv3d);
807 has_zoom = WM_event_ndof_translation_has_zoom(ndof);
808 }
809 else {
810 /* Free preference (Z translates). */
811 has_translate = ndof_has_translate(ndof, v3d, rv3d);
812 has_zoom = false;
813 }
814
815 /* Rotation first because dynamic offset resets offset otherwise (and disables panning). */
816 if (has_rotation) {
817 const float dist_backup = rv3d->dist;
818 if (!is_orbit_around_pivot) {
819 ED_view3d_distance_set(rv3d, 0.0f);
820 }
821 view3d_ndof_orbit(ndof, vod->area, vod->region, vod, is_orbit_around_pivot);
822 xform_flag |= HAS_ROTATE;
823 if (!is_orbit_around_pivot) {
824 ED_view3d_distance_set(rv3d, dist_backup);
825 }
826 }
827
828 if (has_translate || has_zoom) {
829 view3d_ndof_pan_zoom(ndof, vod->area, vod->region, has_translate, has_zoom);
830 xform_flag |= HAS_TRANSLATE;
831 }
832 }
833
834 ED_view3d_camera_lock_sync(vod->depsgraph, v3d, rv3d);
835 if (xform_flag) {
837 v3d, rv3d, C, xform_flag & HAS_ROTATE, xform_flag & HAS_TRANSLATE);
838 }
839
841
842 return OPERATOR_FINISHED;
843}
844
845static wmOperatorStatus ndof_orbit_zoom_invoke(bContext *C, wmOperator *op, const wmEvent *event)
846{
847 if (event->type != NDOF_MOTION) {
848 return OPERATOR_CANCELLED;
849 }
850
851 return view3d_navigate_invoke_impl(C, op, event, &ViewOpsType_ndof_orbit_zoom);
852}
853
854void VIEW3D_OT_ndof_orbit_zoom(wmOperatorType *ot)
855{
856 /* identifiers */
857 ot->name = "NDOF Orbit View with Zoom";
858 ot->description = "Orbit and zoom the view using the 3D mouse";
859 ot->idname = ViewOpsType_ndof_orbit_zoom.idname;
860
861 /* API callbacks. */
862 ot->invoke = ndof_orbit_zoom_invoke;
864
865 /* flags */
866 ot->flag = 0;
867}
868
870
871/* -------------------------------------------------------------------- */
874
875static wmOperatorStatus ndof_pan_invoke_impl(bContext *C,
876 ViewOpsData *vod,
877 const wmEvent *event,
878 PointerRNA * /*ptr*/)
879{
880 if (event->type != NDOF_MOTION) {
881 return OPERATOR_CANCELLED;
882 }
883
884 const wmNDOFMotionData &ndof = *static_cast<const wmNDOFMotionData *>(event->customdata);
885
886 if (U.ndof_flag & NDOF_CAMERA_PAN_ZOOM) {
887 const wmOperatorStatus camera_retval = view3d_ndof_cameraview_pan_zoom(vod, ndof);
888 if (camera_retval != OPERATOR_PASS_THROUGH) {
889 return camera_retval;
890 }
891 }
892
893 const Depsgraph *depsgraph = vod->depsgraph;
894 View3D *v3d = vod->v3d;
895 RegionView3D *rv3d = vod->rv3d;
896 ARegion *region = vod->region;
897 char xform_flag = 0;
898
899 const bool has_translate = ndof_has_translate(ndof, v3d, rv3d);
900 const bool has_zoom = (rv3d->is_persp == false) && WM_event_ndof_translation_has_zoom(ndof);
901
902 /* we're panning here! so erase any leftover rotation from other operators */
903 rv3d->ndof_rot_angle = 0.0f;
904
905 if (!(has_translate || has_zoom)) {
906 return OPERATOR_CANCELLED;
907 }
908
909 ED_view3d_camera_lock_init_ex(depsgraph, v3d, rv3d, false);
910
911 if (ndof.progress != P_FINISHING) {
912 ScrArea *area = vod->area;
913
914 if (has_translate || has_zoom) {
915 view3d_ndof_pan_zoom(ndof, area, region, has_translate, has_zoom);
916 xform_flag |= HAS_TRANSLATE;
917 }
918 }
919
921 if (xform_flag) {
922 ED_view3d_camera_lock_autokey(v3d, rv3d, C, false, xform_flag & HAS_TRANSLATE);
923 }
924
925 ED_region_tag_redraw(region);
926
927 return OPERATOR_FINISHED;
928}
929
930static wmOperatorStatus ndof_pan_invoke(bContext *C, wmOperator *op, const wmEvent *event)
931{
932 if (event->type != NDOF_MOTION) {
933 return OPERATOR_CANCELLED;
934 }
935
936 return view3d_navigate_invoke_impl(C, op, event, &ViewOpsType_ndof_pan);
937}
938
939void VIEW3D_OT_ndof_pan(wmOperatorType *ot)
940{
941 /* identifiers */
942 ot->name = "NDOF Pan View";
943 ot->description = "Pan the view with the 3D mouse";
944 ot->idname = ViewOpsType_ndof_pan.idname;
945
946 /* API callbacks. */
947 ot->invoke = ndof_pan_invoke;
949
950 /* flags */
951 ot->flag = 0;
952}
953
955
956/* -------------------------------------------------------------------- */
959
963static wmOperatorStatus ndof_all_invoke_impl(bContext *C,
964 ViewOpsData *vod,
965 const wmEvent *event,
966 PointerRNA * /*ptr*/)
967{
968
970
971 /* weak!, but it works */
972 const uint8_t ndof_navigation_mode_backup = U.ndof_navigation_mode;
973 U.ndof_navigation_mode = NDOF_NAVIGATION_MODE_FLY;
974
975 ret = ndof_orbit_zoom_invoke_impl(C, vod, event, nullptr);
976
977 U.ndof_navigation_mode = ndof_navigation_mode_backup;
978
979 return ret;
980}
981
982static wmOperatorStatus ndof_all_invoke(bContext *C, wmOperator *op, const wmEvent *event)
983{
984 if (event->type != NDOF_MOTION) {
985 return OPERATOR_CANCELLED;
986 }
987
988 return view3d_navigate_invoke_impl(C, op, event, &ViewOpsType_ndof_all);
989}
990
991void VIEW3D_OT_ndof_all(wmOperatorType *ot)
992{
993 /* identifiers */
994 ot->name = "NDOF Transform View";
995 ot->description = "Pan and rotate the view with the 3D mouse";
996 ot->idname = ViewOpsType_ndof_all.idname;
997
998 /* API callbacks. */
999 ot->invoke = ndof_all_invoke;
1001
1002 /* flags */
1003 ot->flag = 0;
1004}
1005
1006const ViewOpsType ViewOpsType_ndof_orbit = {
1008 /*idname*/ "VIEW3D_OT_ndof_orbit",
1009 /*poll_fn*/ nullptr,
1010 /*init_fn*/ ndof_orbit_invoke_impl,
1011 /*apply_fn*/ nullptr,
1012};
1013
1014const ViewOpsType ViewOpsType_ndof_orbit_zoom = {
1016 /*idname*/ "VIEW3D_OT_ndof_orbit_zoom",
1017 /*poll_fn*/ nullptr,
1018 /*init_fn*/ ndof_orbit_zoom_invoke_impl,
1019 /*apply_fn*/ nullptr,
1020};
1021
1022const ViewOpsType ViewOpsType_ndof_pan = {
1023 /*flag*/ VIEWOPS_FLAG_NONE,
1024 /*idname*/ "VIEW3D_OT_ndof_pan",
1025 /*poll_fn*/ nullptr,
1026 /*init_fn*/ ndof_pan_invoke_impl,
1027 /*apply_fn*/ nullptr,
1028};
1029
1030const ViewOpsType ViewOpsType_ndof_all = {
1032 /*idname*/ "VIEW3D_OT_ndof_all",
1033 /*poll_fn*/ nullptr,
1034 /*init_fn*/ ndof_all_invoke_impl,
1035 /*apply_fn*/ nullptr,
1036};
1037
1038#endif /* WITH_INPUT_NDOF */
1039
bool BKE_layer_collection_has_selected_objects(const Scene *scene, ViewLayer *view_layer, LayerCollection *lc)
#define BLI_assert(a)
Definition BLI_assert.h:46
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:53
MINLINE float max_ff(float a, float b)
#define ISECT_AABB_PLANE_IN_FRONT_ALL
int isect_aabb_planes_v3(const float(*planes)[4], int totplane, const float bbmin[3], const float bbmax[3])
void planes_from_projmat(const float mat[4][4], float left[4], float right[4], float bottom[4], float top[4], float near[4], float far[4])
void mul_mat3_m4_v3(const float mat[4][4], float r[3])
void axis_angle_to_quat(float r[4], const float axis[3], float angle)
void axis_angle_to_quat_single(float q[4], char axis, float angle)
void mul_qt_v3(const float q[4], float r[3])
void invert_qt_qt_normalized(float q1[4], const float q2[4])
void mul_qt_qtqt(float q[4], const float a[4], const float b[4])
MINLINE void sub_v3_v3(float r[3], const float a[3])
MINLINE void mul_v2_fl(float r[2], float f)
MINLINE bool is_zero_v2(const float v[2]) ATTR_WARN_UNUSED_RESULT
MINLINE void copy_v3_v3(float r[3], const float a[3])
MINLINE void negate_v3_v3(float r[3], const float a[3])
MINLINE void copy_v3_fl3(float v[3], float x, float y, float z)
MINLINE bool is_zero_v3(const float v[3]) ATTR_WARN_UNUSED_RESULT
MINLINE float mul_project_m4_v3_zfac(const float mat[4][4], const float co[3]) ATTR_WARN_UNUSED_RESULT
void BLI_rcti_init_pt_radius(struct rcti *rect, const int xy[2], int size)
Definition rct.cc:466
#define UNUSED_VARS_NDEBUG(...)
ViewLayer * DEG_get_evaluated_view_layer(const Depsgraph *graph)
Scene * DEG_get_input_scene(const Depsgraph *graph)
@ OB_LOCK_LOCY
@ OB_LOCK_LOCZ
@ OB_LOCK_LOCX
#define NDOF_IS_ORBIT_AROUND_CENTER_MODE(userdef)
#define NDOF_PIXELS_PER_SECOND
@ NDOF_NAVIGATION_MODE_FLY
@ NDOF_LOCK_HORIZON
@ NDOF_ORBIT_CENTER_AUTO
@ NDOF_CAMERA_PAN_ZOOM
@ NDOF_ORBIT_CENTER_SELECTED
@ NDOF_FLY_HELICOPTER
#define RV3D_VIEW_IS_AXIS(view)
@ RV3D_CAMOB
@ RV3D_ORTHO
#define RV3D_LOCK_FLAGS(rv3d)
@ V3D_LOCK_CAMERA
@ RV3D_LOCK_ROTATION
@ RV3D_BOXVIEW
@ RV3D_VIEW_USER
@ RV3D_NDOF_OFS_IS_VALID
@ OPERATOR_CANCELLED
@ OPERATOR_FINISHED
@ OPERATOR_PASS_THROUGH
bool ED_operator_view3d_active(bContext *C)
void ED_region_tag_redraw(ARegion *region)
Definition area.cc:639
void ED_view3d_distance_set(RegionView3D *rv3d, float dist)
bool ED_view3d_camera_lock_sync(const Depsgraph *depsgraph, View3D *v3d, RegionView3D *rv3d)
bool ED_view3d_distance_set_from_location(RegionView3D *rv3d, const float dist_co[3], float dist_min)
bool ED_view3d_persp_ensure(const Depsgraph *depsgraph, View3D *v3d, ARegion *region)
bool ED_view3d_offset_lock_check(const View3D *v3d, const RegionView3D *rv3d)
bool ED_view3d_camera_view_zoom_scale(RegionView3D *rv3d, const float scale)
void view3d_region_operator_needs_gpu(ARegion *region)
bool ED_view3d_unproject_v3(const ARegion *region, float regionx, float regiony, float regionz, float world[3])
void ED_view3d_camera_lock_init_ex(const Depsgraph *depsgraph, View3D *v3d, RegionView3D *rv3d, bool calc_dist)
bool ED_view3d_camera_view_pan(ARegion *region, const float event_ofs[2])
float ED_view3d_calc_zfac(const RegionView3D *rv3d, const float co[3])
bool ED_view3d_camera_lock_autokey(View3D *v3d, RegionView3D *rv3d, bContext *C, bool do_rotate, bool do_translate)
float ED_view3d_dist_soft_min_get(const View3D *v3d, bool use_persp_range)
static double angle(const Eigen::Vector3d &v1, const Eigen::Vector3d &v2)
Definition IK_Math.h:117
#define C
Definition RandGen.cpp:29
@ P_STARTING
Definition WM_types.hh:849
@ P_FINISHING
Definition WM_types.hh:851
#define U
BPy_StructRNA * depsgraph
#define asinf(x)
#define fabsf(x)
#define rot(x, k)
#define MEM_SAFE_FREE(v)
VecBase< float, 3 > float3
return ret
#define FLT_MAX
Definition stdcycles.h:14
void * regiondata
void * first
float ndof_rot_axis[3]
float persmat[4][4]
float viewinv[4][4]
ListBase spacedata
struct Object * camera
short ob_center_cursor
struct Object * ob_center
float * depths
Definition ED_view3d.hh:89
LayerCollection * active_collection
Depsgraph * depsgraph
RegionView3D * rv3d
wmEventType type
Definition WM_types.hh:754
void view3d_depths_rect_create(ARegion *region, rcti *rect, ViewDepths *r_d)
float view3d_depth_near(ViewDepths *d)
void view3d_boxview_sync(ScrArea *area, ARegion *region)
wmOperatorStatus view3d_navigate_invoke_impl(bContext *C, wmOperator *op, const wmEvent *event, const ViewOpsType *nav_type)
void viewrotate_apply_dyn_ofs(ViewOpsData *vod, const float viewquat_new[4])
bool view3d_calc_point_in_selected_bounds(Depsgraph *depsgraph, struct ViewLayer *view_layer_eval, const View3D *v3d, const blender::float3 &point, const float scale_margin)
@ VIEWOPS_FLAG_NONE
@ VIEWOPS_FLAG_ORBIT_SELECT
std::optional< blender::Bounds< blender::float3 > > view3d_calc_minmax_selected(Depsgraph *depsgraph, ScrArea *area, ARegion *region, bool use_all_regions, bool clip_bounds, bool *r_do_zoom)
std::optional< blender::Bounds< blender::float3 > > view3d_calc_minmax_visible(Depsgraph *depsgraph, ScrArea *area, ARegion *region, bool use_all_regions, bool clip_bounds)
@ NDOF_MOTION
wmOperatorType * ot
Definition wm_files.cc:4225