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