Blender V5.0
view3d_navigate_fly.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
14
15#ifdef WITH_INPUT_NDOF
16// # define NDOF_FLY_DEBUG
17/* NOTE(@ideasman42): is this needed for NDOF? commented so redraw doesn't thrash. */
18// # define NDOF_FLY_DRAW_TOOMUCH
19#endif /* WITH_INPUT_NDOF */
20
21#include "DNA_object_types.h"
22
23#include "MEM_guardedalloc.h"
24
25#include "BLI_math_matrix.h"
26#include "BLI_math_rotation.h"
27#include "BLI_math_vector.h"
28#include "BLI_rect.h"
29#include "BLI_time.h" /* Smooth-view. */
30
31#include "BKE_context.hh"
32#include "BKE_lib_id.hh"
33#include "BKE_report.hh"
34#include "BKE_screen.hh"
35
36#include "BLT_translation.hh"
37
38#include "WM_api.hh"
39#include "WM_types.hh"
40
41#include "ED_screen.hh"
42#include "ED_space_api.hh"
43#include "ED_undo.hh"
44
45#include "UI_resources.hh"
46
47#include "GPU_immediate.hh"
48
49#include "view3d_intern.hh" /* own include */
50#include "view3d_navigate.hh"
51
52#include <fmt/format.h>
53
54#include "BLI_strict_flags.h" /* IWYU pragma: keep. Keep last. */
55
56/* -------------------------------------------------------------------- */
59
60/* NOTE: these defines are saved in keymap files,
61 * do not change values but just add new ones */
62enum {
81 FLY_MODAL_SPEED, /* mouse-pan typically. */
82};
83
99
101{
102 static const EnumPropertyItem modal_items[] = {
103 {FLY_MODAL_CONFIRM, "CONFIRM", 0, "Confirm", ""},
104 {FLY_MODAL_CANCEL, "CANCEL", 0, "Cancel", ""},
105
106 {FLY_MODAL_DIR_FORWARD, "FORWARD", 0, "Forward", ""},
107 {FLY_MODAL_DIR_BACKWARD, "BACKWARD", 0, "Backward", ""},
108 {FLY_MODAL_DIR_LEFT, "LEFT", 0, "Left", ""},
109 {FLY_MODAL_DIR_RIGHT, "RIGHT", 0, "Right", ""},
110 {FLY_MODAL_DIR_UP, "UP", 0, "Up", ""},
111 {FLY_MODAL_DIR_DOWN, "DOWN", 0, "Down", ""},
112
113 {FLY_MODAL_PAN_ENABLE, "PAN_ENABLE", 0, "Pan", ""},
114 {FLY_MODAL_PAN_DISABLE, "PAN_DISABLE", 0, "Pan (Off)", ""},
115
116 {FLY_MODAL_ACCELERATE, "ACCELERATE", 0, "Accelerate", ""},
117 {FLY_MODAL_DECELERATE, "DECELERATE", 0, "Decelerate", ""},
118
119 {FLY_MODAL_AXIS_LOCK_X, "AXIS_LOCK_X", 0, "X Axis Correction", "X axis correction (toggle)"},
120 {FLY_MODAL_AXIS_LOCK_Z, "AXIS_LOCK_Z", 0, "Z Axis Correction", "Z axis correction (toggle)"},
121
122 {FLY_MODAL_PRECISION_ENABLE, "PRECISION_ENABLE", 0, "Precision", ""},
123 {FLY_MODAL_PRECISION_DISABLE, "PRECISION_DISABLE", 0, "Precision (Off)", ""},
124
125 {FLY_MODAL_FREELOOK_ENABLE, "FREELOOK_ENABLE", 0, "Rotation", ""},
126 {FLY_MODAL_FREELOOK_DISABLE, "FREELOOK_DISABLE", 0, "Rotation (Off)", ""},
127
128 {0, nullptr, 0, nullptr, nullptr},
129 };
130
131 wmKeyMap *keymap = WM_modalkeymap_find(keyconf, "View3D Fly Modal");
132
133 /* This function is called for each space-type, only needs to add map once. */
134 if (keymap && keymap->modal_items) {
135 return;
136 }
137
138 keymap = WM_modalkeymap_ensure(keyconf, "View3D Fly Modal", modal_items);
139
140 /* Assign map to operators. */
141 WM_modalkeymap_assign(keymap, "VIEW3D_OT_fly");
142}
143
145
146/* -------------------------------------------------------------------- */
149
150struct FlyInfo {
151 /* context stuff */
155 Depsgraph *depsgraph;
157
160
161 short state;
162 bool redraw;
166
174
176 int mval[2];
181
182#ifdef WITH_INPUT_NDOF
184 wmNDOFMotionData *ndof;
185#endif
186
187 /* Fly state. */
189 float speed;
191 short axis;
194
199 float grid;
200
201 /* Compare between last state. */
206
208
210 float dvec_prev[3];
211
213};
214
216
217/* -------------------------------------------------------------------- */
220
221/* Prototypes. */
222#ifdef WITH_INPUT_NDOF
223static void flyApply_ndof(bContext *C, FlyInfo *fly, bool is_confirm);
224#endif /* WITH_INPUT_NDOF */
225static int flyApply(bContext *C, FlyInfo *fly, bool is_confirm);
226
227static void drawFlyPixel(const bContext * /*C*/, ARegion * /*region*/, void *arg)
228{
229 FlyInfo *fly = static_cast<FlyInfo *>(arg);
230 rctf viewborder;
231 int xoff, yoff;
232
235 fly->scene, fly->depsgraph, fly->region, fly->v3d, fly->rv3d, false, &viewborder);
236 xoff = int(viewborder.xmin);
237 yoff = int(viewborder.ymin);
238 }
239 else {
240 xoff = 0;
241 yoff = 0;
242 }
243
244 /* Draws 4 edge brackets that frame the safe area where the
245 * mouse can move during fly mode without spinning the view. */
246
247 const float x1 = float(xoff) + 0.45f * fly->viewport_size[0];
248 const float y1 = float(yoff) + 0.45f * fly->viewport_size[1];
249 const float x2 = float(xoff) + 0.55f * fly->viewport_size[0];
250 const float y2 = float(yoff) + 0.55f * fly->viewport_size[1];
251
253 uint pos = GPU_vertformat_attr_add(format, "pos", blender::gpu::VertAttrType::SFLOAT_32_32);
254
256
258
260
261 /* Bottom left. */
262 immVertex2f(pos, x1, y1);
263 immVertex2f(pos, x1, y1 + 5);
264
265 immVertex2f(pos, x1, y1);
266 immVertex2f(pos, x1 + 5, y1);
267
268 /* Top right. */
269 immVertex2f(pos, x2, y2);
270 immVertex2f(pos, x2, y2 - 5);
271
272 immVertex2f(pos, x2, y2);
273 immVertex2f(pos, x2 - 5, y2);
274
275 /* Top left. */
276 immVertex2f(pos, x1, y2);
277 immVertex2f(pos, x1, y2 - 5);
278
279 immVertex2f(pos, x1, y2);
280 immVertex2f(pos, x1 + 5, y2);
281
282 /* Bottom right. */
283 immVertex2f(pos, x2, y1);
284 immVertex2f(pos, x2, y1 + 5);
285
286 immVertex2f(pos, x2, y1);
287 immVertex2f(pos, x2 - 5, y1);
288
289 immEnd();
291}
292
294
295/* -------------------------------------------------------------------- */
298
300enum {
304};
305
306static bool initFlyInfo(bContext *C, FlyInfo *fly, wmOperator *op, const wmEvent *event)
307{
309 wmWindow *win = CTX_wm_window(C);
310 rctf viewborder;
311
312 float upvec[3];
313 float mat[3][3];
314
316 fly->v3d = CTX_wm_view3d(C);
317 fly->region = CTX_wm_region(C);
319 fly->scene = CTX_data_scene(C);
320
321#ifdef NDOF_FLY_DEBUG
322 puts("\n-- fly begin --");
323#endif
324
325 /* Sanity check: for rare but possible case (if lib-linking the camera fails). */
326 if ((fly->rv3d->persp == RV3D_CAMOB) && (fly->v3d->camera == nullptr)) {
327 fly->rv3d->persp = RV3D_PERSP;
328 }
329
330 if (fly->rv3d->persp == RV3D_CAMOB &&
332 {
334 RPT_ERROR,
335 "Cannot navigate a camera from an external library or non-editable override");
336
337 return false;
338 }
339
340 if (ED_view3d_offset_lock_check(fly->v3d, fly->rv3d)) {
341 BKE_report(op->reports, RPT_ERROR, "Cannot fly when the view offset is locked");
342 return false;
343 }
344
345 if (fly->rv3d->persp == RV3D_CAMOB && fly->v3d->camera->constraints.first) {
346 BKE_report(op->reports, RPT_ERROR, "Cannot fly an object with constraints");
347 return false;
348 }
349
350 fly->state = FLY_RUNNING;
351 fly->speed = 0.0f;
352 fly->axis = 2;
353 fly->pan_view = false;
356 fly->xlock_momentum = 0.0f;
357 fly->zlock_momentum = 0.0f;
358 fly->grid = 1.0f;
359 fly->use_precision = false;
360 fly->use_freelook = false;
362
363#ifdef NDOF_FLY_DRAW_TOOMUCH
364 fly->redraw = 1;
365#endif
366 zero_v3(fly->dvec_prev);
367
368 fly->timer = WM_event_timer_add(CTX_wm_manager(C), win, TIMER, 0.01f);
369
370 copy_v2_v2_int(fly->mval, event->mval);
371
372#ifdef WITH_INPUT_NDOF
373 fly->ndof = nullptr;
374#endif
375
377
380
381 fly->rv3d->rflag |= RV3D_NAVIGATING;
382
383 /* Detect whether to start with Z locking. */
384 copy_v3_fl3(upvec, 1.0f, 0.0f, 0.0f);
385 copy_m3_m4(mat, fly->rv3d->viewinv);
386 mul_m3_v3(mat, upvec);
387 if (fabsf(upvec[2]) < 0.1f) {
389 }
390
392 fly->depsgraph, fly->scene, fly->v3d, fly->rv3d);
393
394 /* Calculate center. */
397 fly->scene, fly->depsgraph, fly->region, fly->v3d, fly->rv3d, false, &viewborder);
398
399 fly->viewport_size[0] = BLI_rctf_size_x(&viewborder);
400 fly->viewport_size[1] = BLI_rctf_size_y(&viewborder);
401
402 fly->center_mval[0] = int(viewborder.xmin + (fly->viewport_size[0] / 2.0f));
403 fly->center_mval[1] = int(viewborder.ymin + (fly->viewport_size[1] / 2));
404 }
405 else {
406 fly->viewport_size[0] = fly->region->winx;
407 fly->viewport_size[1] = fly->region->winy;
408
409 fly->center_mval[0] = int(fly->viewport_size[0] / 2.0f);
410 fly->center_mval[1] = int(fly->viewport_size[1] / 2.0f);
411 }
412
413 /* Center the mouse, probably the UI mafia are against this but without its quite annoying. */
414 WM_cursor_warp(win,
415 fly->region->winrct.xmin + fly->center_mval[0],
416 fly->region->winrct.ymin + fly->center_mval[1]);
417
418 return true;
419}
420
422{
423 wmWindow *win;
424 RegionView3D *rv3d;
425
426 if (fly->state == FLY_RUNNING) {
428 }
429 if (fly->state == FLY_CONFIRM) {
430 /* Needed for auto-keyframe. */
431#ifdef WITH_INPUT_NDOF
432 if (fly->ndof) {
433 flyApply_ndof(C, fly, true);
434 }
435 else
436#endif /* WITH_INPUT_NDOF */
437 {
438 flyApply(C, fly, true);
439 }
440 }
441
442#ifdef NDOF_FLY_DEBUG
443 puts("\n-- fly end --");
444#endif
445
446 win = CTX_wm_window(C);
447 rv3d = fly->rv3d;
448
449 ED_workspace_status_text(C, nullptr);
450
452
454
456
457 rv3d->rflag &= ~RV3D_NAVIGATING;
458
459#ifdef WITH_INPUT_NDOF
460 if (fly->ndof) {
461 MEM_freeN(fly->ndof);
462 }
463#endif
464
465 if (fly->state == FLY_CONFIRM) {
466 MEM_freeN(fly);
467 return OPERATOR_FINISHED;
468 }
469
470 MEM_freeN(fly);
471 return OPERATOR_CANCELLED;
472}
473
474static void flyEvent(FlyInfo *fly, const wmEvent *event)
475{
476 if (event->type == TIMER && event->customdata == fly->timer) {
477 fly->redraw = true;
478 }
479 else if (event->type == MOUSEMOVE) {
480 copy_v2_v2_int(fly->mval, event->mval);
481 }
482#ifdef WITH_INPUT_NDOF
483 else if (event->type == NDOF_MOTION) {
484 /* Do these auto-magically get delivered? yes. */
485 // puts("ndof motion detected in fly mode!");
486 // static const char *tag_name = "3D mouse position";
487
488 const wmNDOFMotionData *incoming_ndof = static_cast<const wmNDOFMotionData *>(
489 event->customdata);
490 switch (incoming_ndof->progress) {
491 case P_STARTING: {
492 /* Start keeping track of 3D mouse position. */
493# ifdef NDOF_FLY_DEBUG
494 puts("start keeping track of 3D mouse position");
495# endif
496 /* Fall-through. */
497 }
498 case P_IN_PROGRESS: {
499 /* Update 3D mouse position. */
500# ifdef NDOF_FLY_DEBUG
501 putchar('.');
502 fflush(stdout);
503# endif
504 if (fly->ndof == nullptr) {
505 // fly->ndof = MEM_mallocN(sizeof(wmNDOFMotionData), tag_name);
506 fly->ndof = static_cast<wmNDOFMotionData *>(MEM_dupallocN(incoming_ndof));
507 // fly->ndof = malloc(sizeof(wmNDOFMotionData));
508 }
509 else {
510 memcpy(fly->ndof, incoming_ndof, sizeof(wmNDOFMotionData));
511 }
512 break;
513 }
514 case P_FINISHING: {
515 /* Stop keeping track of 3D mouse position. */
516# ifdef NDOF_FLY_DEBUG
517 puts("stop keeping track of 3D mouse position");
518# endif
519 if (fly->ndof) {
520 MEM_freeN(fly->ndof);
521 // free(fly->ndof);
522 fly->ndof = nullptr;
523 }
524 /* Update the time else the view will jump when 2D mouse/timer resume. */
526 break;
527 }
528 default: {
529 /* Should always be one of the above 3. */
530 break;
531 }
532 }
533 }
534#endif /* WITH_INPUT_NDOF */
535 /* Handle modal key-map first. */
536 else if (event->type == EVT_MODAL_MAP) {
537 switch (event->val) {
538 case FLY_MODAL_CANCEL: {
539 fly->state = FLY_CANCEL;
540 break;
541 }
542 case FLY_MODAL_CONFIRM: {
543 fly->state = FLY_CONFIRM;
544 break;
545 }
546 /* Speed adjusting with mouse-pan (trackpad). */
547 case FLY_MODAL_SPEED: {
548 float fac = 0.02f * float(event->prev_xy[1] - event->xy[1]);
549
550 /* Allowing to brake immediate. */
551 if (fac > 0.0f && fly->speed < 0.0f) {
552 fly->speed = 0.0f;
553 }
554 else if (fac < 0.0f && fly->speed > 0.0f) {
555 fly->speed = 0.0f;
556 }
557 else {
558 fly->speed += fly->grid * fac;
559 }
560
561 break;
562 }
564 double time_currwheel;
565 float time_wheel;
566
567 /* Not quite correct but avoids confusion WASD/arrow keys 'locking up'. */
568 if (fly->axis == -1) {
569 fly->axis = 2;
570 fly->speed = fabsf(fly->speed);
571 }
572
573 time_currwheel = BLI_time_now_seconds();
574 time_wheel = float(time_currwheel - fly->time_lastwheel);
575 fly->time_lastwheel = time_currwheel;
576 /* Mouse wheel delays range from (0.5 == slow) to (0.01 == fast). */
577 /* 0-0.5 -> 0-5.0 */
578 time_wheel = 1.0f + (10.0f - (20.0f * min_ff(time_wheel, 0.5f)));
579
580 if (fly->speed < 0.0f) {
581 fly->speed = 0.0f;
582 }
583 else {
584 fly->speed += fly->grid * time_wheel * (fly->use_precision ? 0.1f : 1.0f);
585 }
586 break;
587 }
589 double time_currwheel;
590 float time_wheel;
591
592 /* Not quite correct but avoids confusion WASD/arrow keys 'locking up'. */
593 if (fly->axis == -1) {
594 fly->axis = 2;
595 fly->speed = -fabsf(fly->speed);
596 }
597
598 time_currwheel = BLI_time_now_seconds();
599 time_wheel = float(time_currwheel - fly->time_lastwheel);
600 fly->time_lastwheel = time_currwheel;
601 /* 0-0.5 -> 0-5.0 */
602 time_wheel = 1.0f + (10.0f - (20.0f * min_ff(time_wheel, 0.5f)));
603
604 if (fly->speed > 0.0f) {
605 fly->speed = 0;
606 }
607 else {
608 fly->speed -= fly->grid * time_wheel * (fly->use_precision ? 0.1f : 1.0f);
609 }
610 break;
611 }
613 fly->pan_view = true;
614 break;
615 }
617 fly->pan_view = false;
618 break;
619 }
620 /* Implement WASD keys, comments only for 'forward'. */
622 if (fly->axis == 2 && fly->speed < 0.0f) {
623 /* Reverse direction stops, tap again to continue. */
624 fly->axis = -1;
625 }
626 else {
627 /* Flip speed rather than stopping, game like motion,
628 * else increase like mouse-wheel if we're already moving in that direction. */
629 if (fly->speed < 0.0f) {
630 fly->speed = -fly->speed;
631 }
632 else if (fly->axis == 2) {
633 fly->speed += fly->grid;
634 }
635 fly->axis = 2;
636 }
637 break;
638 }
640 if (fly->axis == 2 && fly->speed > 0.0f) {
641 fly->axis = -1;
642 }
643 else {
644 if (fly->speed > 0.0f) {
645 fly->speed = -fly->speed;
646 }
647 else if (fly->axis == 2) {
648 fly->speed -= fly->grid;
649 }
650
651 fly->axis = 2;
652 }
653 break;
654 }
655 case FLY_MODAL_DIR_LEFT: {
656 if (fly->axis == 0 && fly->speed < 0.0f) {
657 fly->axis = -1;
658 }
659 else {
660 if (fly->speed < 0.0f) {
661 fly->speed = -fly->speed;
662 }
663 else if (fly->axis == 0) {
664 fly->speed += fly->grid;
665 }
666
667 fly->axis = 0;
668 }
669 break;
670 }
671 case FLY_MODAL_DIR_RIGHT: {
672 if (fly->axis == 0 && fly->speed > 0.0f) {
673 fly->axis = -1;
674 }
675 else {
676 if (fly->speed > 0.0f) {
677 fly->speed = -fly->speed;
678 }
679 else if (fly->axis == 0) {
680 fly->speed -= fly->grid;
681 }
682
683 fly->axis = 0;
684 }
685 break;
686 }
687 case FLY_MODAL_DIR_DOWN: {
688 if (fly->axis == 1 && fly->speed < 0.0f) {
689 fly->axis = -1;
690 }
691 else {
692 if (fly->speed < 0.0f) {
693 fly->speed = -fly->speed;
694 }
695 else if (fly->axis == 1) {
696 fly->speed += fly->grid;
697 }
698 fly->axis = 1;
699 }
700 break;
701 }
702 case FLY_MODAL_DIR_UP: {
703 if (fly->axis == 1 && fly->speed > 0.0f) {
704 fly->axis = -1;
705 }
706 else {
707 if (fly->speed > 0.0f) {
708 fly->speed = -fly->speed;
709 }
710 else if (fly->axis == 1) {
711 fly->speed -= fly->grid;
712 }
713 fly->axis = 1;
714 }
715 break;
716 }
718 if (fly->xlock != FLY_AXISLOCK_STATE_OFF) {
720 }
721 else {
723 fly->xlock_momentum = 0.0;
724 }
725 break;
726 }
728 if (fly->zlock != FLY_AXISLOCK_STATE_OFF) {
730 }
731 else {
733 fly->zlock_momentum = 0.0;
734 }
735 break;
736 }
738 fly->use_precision = true;
739 break;
740 }
742 fly->use_precision = false;
743 break;
744 }
746 fly->use_freelook = true;
747 break;
748 }
750 fly->use_freelook = false;
751 break;
752 }
753 }
754 }
755}
756
758 FlyInfo *fly,
759 const bool do_rotate,
760 const bool do_translate,
761 const bool is_confirm)
762{
763 /* We only consider auto-keying on playback or if user confirmed fly on the same frame
764 * otherwise we get a keyframe even if the user cancels. */
765 const bool use_autokey = is_confirm || fly->anim_playing;
766 ED_view3d_cameracontrol_update(fly->v3d_camera_control, use_autokey, C, do_rotate, do_translate);
767}
768
769static int flyApply(bContext *C, FlyInfo *fly, bool is_confirm)
770{
771#define FLY_ROTATE_FAC 10.0f /* More is faster. */
772#define FLY_ZUP_CORRECT_FAC 0.1f /* Amount to correct per step. */
773#define FLY_ZUP_CORRECT_ACCEL 0.05f /* Increase upright momentum each step. */
774#define FLY_SMOOTH_FAC 20.0f /* Higher value less lag. */
775
776 RegionView3D *rv3d = fly->rv3d;
777
778 /* 3x3 copy of the view matrix so we can move along the view axis. */
779 float mat[3][3];
780 /* This is the direction that's added to the view offset per redraw. */
781 float dvec[3] = {0, 0, 0};
782
783 /* Camera Up-righting variables. */
784 float moffset[2]; /* Mouse offset from the views center. */
785 float tmp_quat[4]; /* Used for rotating the view. */
786
787#ifdef NDOF_FLY_DEBUG
788 {
789 static uint iteration = 1;
790 printf("fly timer %d\n", iteration++);
791 }
792#endif
793
794 /* X and Y margin defining the safe area where the mouse's movement won't rotate the view. */
795 const float xmargin = fly->viewport_size[0] / 20.0f;
796 const float ymargin = fly->viewport_size[1] / 20.0f;
797
798 /* Mouse offset from the center. */
799 moffset[0] = float(fly->mval[0] - fly->center_mval[0]);
800 moffset[1] = float(fly->mval[1] - fly->center_mval[1]);
801
802 /* Enforce a view margin. */
803 if (moffset[0] > xmargin) {
804 moffset[0] -= xmargin;
805 }
806 else if (moffset[0] < -xmargin) {
807 moffset[0] += xmargin;
808 }
809 else {
810 moffset[0] = 0;
811 }
812
813 if (moffset[1] > ymargin) {
814 moffset[1] -= ymargin;
815 }
816 else if (moffset[1] < -ymargin) {
817 moffset[1] += ymargin;
818 }
819 else {
820 moffset[1] = 0;
821 }
822
823 /* Scale the mouse movement by this value - scales mouse movement to the view size
824 * `moffset[0] / (region->winx-xmargin * 2)` - window size minus margin (same for Y).
825 *
826 * the mouse moves isn't linear. */
827
828 if (moffset[0]) {
829 moffset[0] /= fly->viewport_size[0] - (xmargin * 2);
830 moffset[0] *= fabsf(moffset[0]);
831 }
832
833 if (moffset[1]) {
834 moffset[1] /= fly->viewport_size[1] - (ymargin * 2);
835 moffset[1] *= fabsf(moffset[1]);
836 }
837
838 /* Should we redraw? */
839 if ((fly->speed != 0.0f) || moffset[0] || moffset[1] || (fly->zlock != FLY_AXISLOCK_STATE_OFF) ||
840 (fly->xlock != FLY_AXISLOCK_STATE_OFF) || dvec[0] || dvec[1] || dvec[2])
841 {
842 bool changed_viewquat = false;
843 float dvec_tmp[3];
844
845 /* Time how fast it takes for us to redraw,
846 * this is so simple scenes don't fly too fast. */
847 double time_current;
848 float time_redraw;
849 float time_redraw_clamped;
850#ifdef NDOF_FLY_DRAW_TOOMUCH
851 fly->redraw = 1;
852#endif
853 time_current = BLI_time_now_seconds();
854 time_redraw = float(time_current - fly->time_lastdraw);
855
856 /* Clamp redraw time to avoid jitter in roll correction. */
857 time_redraw_clamped = min_ff(0.05f, time_redraw);
858
859 fly->time_lastdraw = time_current;
860
861 /* Scale the time to use shift to scale the speed down - just like
862 * shift slows many other areas of blender down. */
863 if (fly->use_precision) {
864 fly->speed = fly->speed * (1.0f - time_redraw_clamped);
865 }
866
867 copy_m3_m4(mat, rv3d->viewinv);
868
869 if (fly->pan_view == true) {
870 /* Pan only. */
871 copy_v3_fl3(dvec_tmp, -moffset[0], -moffset[1], 0.0f);
872
873 if (fly->use_precision) {
874 dvec_tmp[0] *= 0.1f;
875 dvec_tmp[1] *= 0.1f;
876 }
877
878 mul_m3_v3(mat, dvec_tmp);
879 mul_v3_fl(dvec_tmp, time_redraw * 200.0f * fly->grid);
880 }
881 else {
882 /* Similar to the angle between the camera's up and the Z-up,
883 * but its very rough so just roll. */
884 float roll;
885
886 /* Rotate about the X axis- look up/down. */
887 if (moffset[1]) {
888 float upvec[3];
889 copy_v3_fl3(upvec, 1.0f, 0.0f, 0.0f);
890 mul_m3_v3(mat, upvec);
891 /* Rotate about the relative up vector. */
892 axis_angle_to_quat(tmp_quat, upvec, moffset[1] * time_redraw * -FLY_ROTATE_FAC);
893 mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, tmp_quat);
894 changed_viewquat = true;
895
896 if (fly->xlock != FLY_AXISLOCK_STATE_OFF) {
897 fly->xlock = FLY_AXISLOCK_STATE_ACTIVE; /* Check for rotation. */
898 }
899 if (fly->zlock != FLY_AXISLOCK_STATE_OFF) {
901 }
902 fly->xlock_momentum = 0.0f;
903 }
904
905 /* Rotate about the Y axis- look left/right. */
906 if (moffset[0]) {
907 float upvec[3];
908 /* If we're upside down invert the `moffset`. */
909 copy_v3_fl3(upvec, 0.0f, 1.0f, 0.0f);
910 mul_m3_v3(mat, upvec);
911
912 if (upvec[2] < 0.0f) {
913 moffset[0] = -moffset[0];
914 }
915
916 /* Make the lock vectors. */
917 if (fly->zlock) {
918 copy_v3_fl3(upvec, 0.0f, 0.0f, 1.0f);
919 }
920 else {
921 copy_v3_fl3(upvec, 0.0f, 1.0f, 0.0f);
922 mul_m3_v3(mat, upvec);
923 }
924
925 /* Rotate about the relative up vector. */
926 axis_angle_to_quat(tmp_quat, upvec, moffset[0] * time_redraw * FLY_ROTATE_FAC);
927 mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, tmp_quat);
928 changed_viewquat = true;
929
930 if (fly->xlock != FLY_AXISLOCK_STATE_OFF) {
931 fly->xlock = FLY_AXISLOCK_STATE_ACTIVE; /* Check for rotation. */
932 }
933 if (fly->zlock != FLY_AXISLOCK_STATE_OFF) {
935 }
936 }
937
938 if (fly->zlock == FLY_AXISLOCK_STATE_ACTIVE) {
939 float upvec[3];
940 copy_v3_fl3(upvec, 1.0f, 0.0f, 0.0f);
941 mul_m3_v3(mat, upvec);
942
943 /* Make sure we have some Z rolling. */
944 if (fabsf(upvec[2]) > 0.00001f) {
945 roll = upvec[2] * 5.0f;
946 /* Rotate the view about this axis. */
947 copy_v3_fl3(upvec, 0.0f, 0.0f, 1.0f);
948 mul_m3_v3(mat, upvec);
949 /* Rotate about the relative up vector. */
950 axis_angle_to_quat(tmp_quat,
951 upvec,
952 roll * time_redraw_clamped * fly->zlock_momentum *
954 mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, tmp_quat);
955 changed_viewquat = true;
956
958 }
959 else {
960 /* Don't check until the view rotates again. */
962 fly->zlock_momentum = 0.0f;
963 }
964 }
965
966 /* Only apply X-axis correction when mouse isn't applying X rotation. */
967 if (fly->xlock == FLY_AXISLOCK_STATE_ACTIVE && moffset[1] == 0) {
968 float upvec[3];
969 copy_v3_fl3(upvec, 0.0f, 0.0f, 1.0f);
970 mul_m3_v3(mat, upvec);
971 /* Make sure we have some Z rolling. */
972 if (fabsf(upvec[2]) > 0.00001f) {
973 roll = upvec[2] * -5.0f;
974 /* Rotate the view about this axis. */
975 copy_v3_fl3(upvec, 1.0f, 0.0f, 0.0f);
976 mul_m3_v3(mat, upvec);
977
978 /* Rotate about the relative up vector. */
980 tmp_quat, upvec, roll * time_redraw_clamped * fly->xlock_momentum * 0.1f);
981 mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, tmp_quat);
982 changed_viewquat = true;
983
984 fly->xlock_momentum += 0.05f;
985 }
986 else {
987 fly->xlock = FLY_AXISLOCK_STATE_IDLE; /* See above. */
988 fly->xlock_momentum = 0.0f;
989 }
990 }
991
992 if (fly->axis == -1) {
993 /* Pause. */
994 zero_v3(dvec_tmp);
995 }
996 else if (!fly->use_freelook) {
997 /* Normal operation. */
998 /* Define `dvec`, view direction vector. */
999 zero_v3(dvec_tmp);
1000 /* Move along the current axis. */
1001 dvec_tmp[fly->axis] = 1.0f;
1002
1003 mul_m3_v3(mat, dvec_tmp);
1004 }
1005 else {
1006 normalize_v3_v3(dvec_tmp, fly->dvec_prev);
1007 if (fly->speed < 0.0f) {
1008 negate_v3(dvec_tmp);
1009 }
1010 }
1011
1012 mul_v3_fl(dvec_tmp, fly->speed * time_redraw * 0.25f);
1013 }
1014
1015 /* Impose a directional lag. */
1017 dvec, dvec_tmp, fly->dvec_prev, (1.0f / (1.0f + (time_redraw * FLY_SMOOTH_FAC))));
1018
1019 add_v3_v3(rv3d->ofs, dvec);
1020
1021 if (changed_viewquat) {
1022 /* While operations here are expected to keep the quaternion normalized,
1023 * over time floating point error can accumulate error and eventually cause
1024 * it not to be normalized, so - normalize when modified to avoid errors.
1025 * See: #125586. */
1026 normalize_qt(rv3d->viewquat);
1027 }
1028
1029 if (rv3d->persp == RV3D_CAMOB) {
1030 const bool do_rotate = ((fly->xlock != FLY_AXISLOCK_STATE_OFF) ||
1031 (fly->zlock != FLY_AXISLOCK_STATE_OFF) ||
1032 ((moffset[0] || moffset[1]) && !fly->pan_view));
1033 const bool do_translate = (fly->speed != 0.0f || fly->pan_view);
1034 flyMoveCamera(C, fly, do_rotate, do_translate, is_confirm);
1035 }
1036 }
1037 else {
1038 /* We're not redrawing but we need to update the time else the view will jump. */
1040 }
1041 /* End drawing. */
1042 copy_v3_v3(fly->dvec_prev, dvec);
1043
1044 return OPERATOR_FINISHED;
1045}
1046
1047#ifdef WITH_INPUT_NDOF
1048static void flyApply_ndof(bContext *C, FlyInfo *fly, bool is_confirm)
1049{
1051 bool has_translate, has_rotate;
1052
1053 view3d_ndof_fly(*fly->ndof,
1054 fly->v3d,
1055 fly->rv3d,
1056 fly->use_precision,
1057 lock_ob ? lock_ob->protectflag : 0,
1058 &has_translate,
1059 &has_rotate);
1060
1061 if (has_translate || has_rotate) {
1062 fly->redraw = true;
1063
1064 if (fly->rv3d->persp == RV3D_CAMOB) {
1065 flyMoveCamera(C, fly, has_rotate, has_translate, is_confirm);
1066 }
1067 }
1068}
1069#endif /* WITH_INPUT_NDOF */
1070
1072
1073/* -------------------------------------------------------------------- */
1076
1078{
1079 FlyInfo *fly = static_cast<FlyInfo *>(op->customdata);
1080
1082
1083 status.opmodal(IFACE_("Confirm"), op->type, FLY_MODAL_CONFIRM);
1084 status.opmodal(IFACE_("Cancel"), op->type, FLY_MODAL_CANCEL);
1085
1086 status.opmodal("", op->type, FLY_MODAL_DIR_FORWARD);
1087 status.opmodal("", op->type, FLY_MODAL_DIR_LEFT);
1088 status.opmodal("", op->type, FLY_MODAL_DIR_BACKWARD);
1089 status.opmodal("", op->type, FLY_MODAL_DIR_RIGHT);
1090 status.item(IFACE_("Move"), ICON_NONE);
1091
1092 status.opmodal("", op->type, FLY_MODAL_DIR_UP);
1093 status.opmodal("", op->type, FLY_MODAL_DIR_DOWN);
1094 status.item(IFACE_("Up/Down"), ICON_NONE);
1095
1096 status.opmodal(IFACE_("Pan"), op->type, FLY_MODAL_PAN_ENABLE);
1097 status.opmodal(IFACE_("Speed"), op->type, FLY_MODAL_SPEED);
1098
1101 status.item(IFACE_("Axis Lock"), ICON_NONE);
1102
1103 status.opmodal(IFACE_("Precision"), op->type, FLY_MODAL_PRECISION_ENABLE, fly->use_precision);
1104 status.opmodal(IFACE_("Free Look"), op->type, FLY_MODAL_FREELOOK_ENABLE, fly->use_freelook);
1105
1106 status.opmodal("", op->type, FLY_MODAL_ACCELERATE);
1107 status.opmodal("", op->type, FLY_MODAL_DECELERATE);
1108 status.item(fmt::format("{} ({:.2f})", IFACE_("Acceleration"), fly->speed), ICON_NONE);
1109}
1110
1112{
1114
1116 return OPERATOR_CANCELLED;
1117 }
1118
1119 FlyInfo *fly = MEM_callocN<FlyInfo>("FlyOperation");
1120
1121 op->customdata = fly;
1122
1123 if (initFlyInfo(C, fly, op, event) == false) {
1124 MEM_freeN(fly);
1125 return OPERATOR_CANCELLED;
1126 }
1127
1128 flyEvent(fly, event);
1129
1130 fly_draw_status(C, op);
1131
1133
1135}
1136
1138{
1139 FlyInfo *fly = static_cast<FlyInfo *>(op->customdata);
1140
1141 fly->state = FLY_CANCEL;
1142 flyEnd(C, fly);
1143 op->customdata = nullptr;
1144}
1145
1147{
1148 bool do_draw = false;
1149 FlyInfo *fly = static_cast<FlyInfo *>(op->customdata);
1150 View3D *v3d = fly->v3d;
1151 RegionView3D *rv3d = fly->rv3d;
1153
1154 fly->redraw = false;
1155
1156 flyEvent(fly, event);
1157
1158 fly_draw_status(C, op);
1159
1160#ifdef WITH_INPUT_NDOF
1161 if (fly->ndof) { /* 3D mouse overrules [2D mouse + timer]. */
1162 if (event->type == NDOF_MOTION) {
1163 flyApply_ndof(C, fly, false);
1164 }
1165 }
1166 else
1167#endif /* WITH_INPUT_NDOF */
1168 {
1169 if (event->type == TIMER && event->customdata == fly->timer) {
1170 flyApply(C, fly, false);
1171 }
1172 }
1173
1174 do_draw |= fly->redraw;
1175
1176 wmOperatorStatus exit_code = flyEnd(C, fly);
1177
1178 if (exit_code == OPERATOR_FINISHED) {
1179 const bool is_undo_pushed = ED_view3d_camera_lock_undo_push(op->type->name, v3d, rv3d, C);
1180 /* If generic 'locked camera' code did not push an undo, but there is a valid 'flying
1181 * object', an undo push is still needed, since that object transform was modified. */
1182 if (!is_undo_pushed && fly_object && ED_undo_is_memfile_compatible(C)) {
1183 ED_undo_push(C, op->type->name);
1184 }
1185 }
1186 if (exit_code != OPERATOR_RUNNING_MODAL) {
1187 do_draw = true;
1188 }
1189
1190 if (do_draw) {
1191 if (rv3d->persp == RV3D_CAMOB) {
1193 }
1194
1195 // puts("redraw!"); // too frequent, commented with NDOF_FLY_DRAW_TOOMUCH for now
1197 }
1198
1199 return exit_code;
1200}
1201
1203{
1204 /* Identifiers. */
1205 ot->name = "Fly Navigation";
1206 ot->description = "Interactively fly around the scene";
1207 ot->idname = "VIEW3D_OT_fly";
1208
1209 /* API callbacks. */
1210 ot->invoke = fly_invoke;
1211 ot->cancel = fly_cancel;
1212 ot->modal = fly_modal;
1214
1215 /* Flags. */
1216 ot->flag = OPTYPE_BLOCKING;
1217}
1218
Depsgraph * CTX_data_expect_evaluated_depsgraph(const bContext *C)
wmWindow * CTX_wm_window(const bContext *C)
Scene * CTX_data_scene(const bContext *C)
Main * CTX_data_main(const bContext *C)
RegionView3D * CTX_wm_region_view3d(const bContext *C)
ARegion * CTX_wm_region(const bContext *C)
wmWindowManager * CTX_wm_manager(const bContext *C)
View3D * CTX_wm_view3d(const bContext *C)
bool BKE_id_is_editable(const Main *bmain, const ID *id)
Definition lib_id.cc:2523
@ RPT_ERROR
Definition BKE_report.hh:39
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:153
MINLINE float min_ff(float a, float b)
void mul_m3_v3(const float M[3][3], float r[3])
void copy_m3_m4(float m1[3][3], const float m2[4][4])
void axis_angle_to_quat(float r[4], const float axis[3], float angle)
float normalize_qt(float q[4])
void mul_qt_qtqt(float q[4], const float a[4], const float b[4])
MINLINE void mul_v3_fl(float r[3], float f)
MINLINE void copy_v2_v2_int(int r[2], const int a[2])
MINLINE void copy_v3_v3(float r[3], const float a[3])
MINLINE void copy_v3_fl3(float v[3], float x, float y, float z)
void interp_v3_v3v3(float r[3], const float a[3], const float b[3], float t)
MINLINE void negate_v3(float r[3])
MINLINE float normalize_v3_v3(float r[3], const float a[3])
MINLINE void zero_v3(float r[3])
MINLINE void add_v3_v3(float r[3], const float a[3])
BLI_INLINE float BLI_rctf_size_x(const struct rctf *rct)
Definition BLI_rect.h:202
BLI_INLINE float BLI_rctf_size_y(const struct rctf *rct)
Definition BLI_rect.h:206
unsigned int uint
Platform independent time functions.
double BLI_time_now_seconds(void)
Definition time.cc:113
#define IFACE_(msgid)
Object is a sort of wrapper for general info.
#define RV3D_LOCK_FLAGS(rv3d)
@ RV3D_NAVIGATING
@ RV3D_CAMOB
@ RV3D_PERSP
@ RV3D_LOCK_ANY_TRANSFORM
@ OPERATOR_CANCELLED
@ OPERATOR_FINISHED
@ OPERATOR_RUNNING_MODAL
bool ED_operator_region_view3d_active(bContext *C)
bScreen * ED_screen_animation_playing(const wmWindowManager *wm)
void ED_workspace_status_text(bContext *C, const char *str)
Definition area.cc:1024
void ED_region_tag_redraw(ARegion *region)
Definition area.cc:618
void * ED_region_draw_cb_activate(ARegionType *art, void(*draw)(const bContext *, ARegion *, void *), void *customdata, int type)
bool ED_region_draw_cb_exit(ARegionType *art, void *handle)
#define REGION_DRAW_POST_PIXEL
void ED_undo_push(bContext *C, const char *str)
Definition ed_undo.cc:98
bool ED_undo_is_memfile_compatible(const bContext *C)
Definition ed_undo.cc:387
bool ED_view3d_offset_lock_check(const View3D *v3d, const RegionView3D *rv3d)
bool ED_view3d_camera_lock_undo_push(const char *str, const View3D *v3d, const RegionView3D *rv3d, bContext *C)
void ED_view3d_calc_camera_border(const Scene *scene, const Depsgraph *depsgraph, const ARegion *region, const View3D *v3d, const RegionView3D *rv3d, bool no_shift, rctf *r_viewborder)
void immEnd()
void immUnbindProgram()
void immBindBuiltinProgram(GPUBuiltinShader shader_id)
void immVertex2f(uint attr_id, float x, float y)
void immUniformThemeColor3(int color_id)
GPUVertFormat * immVertexFormat()
void immBegin(GPUPrimType, uint vertex_len)
@ GPU_PRIM_LINES
@ GPU_SHADER_3D_UNIFORM_COLOR
uint GPU_vertformat_attr_add(GPUVertFormat *format, blender::StringRef name, blender::gpu::VertAttrType type)
Read Guarded memory(de)allocation.
#define C
Definition RandGen.cpp:29
@ TH_VIEW_OVERLAY
@ OPTYPE_BLOCKING
Definition WM_types.hh:184
#define ND_TRANSFORM
Definition WM_types.hh:456
#define NC_OBJECT
Definition WM_types.hh:379
@ P_IN_PROGRESS
Definition WM_types.hh:853
@ P_STARTING
Definition WM_types.hh:852
@ P_FINISHING
Definition WM_types.hh:854
nullptr float
uint pos
#define printf(...)
format
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
void * MEM_dupallocN(const void *vmemh)
Definition mallocn.cc:143
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
const int status
#define fabsf
ARegionRuntimeHandle * runtime
float viewport_size[2]
eFlyPanState xlock
RegionView3D * rv3d
Depsgraph * depsgraph
View3DCameraControl * v3d_camera_control
eFlyPanState zlock
void * draw_handle_pixel
void * first
ListBase constraints
short protectflag
float viewinv[4][4]
struct Object * camera
float xmin
float ymin
int ymin
int xmin
wmEventType type
Definition WM_types.hh:757
short val
Definition WM_types.hh:759
int xy[2]
Definition WM_types.hh:761
int mval[2]
Definition WM_types.hh:763
int prev_xy[2]
Definition WM_types.hh:820
void * customdata
Definition WM_types.hh:807
const void * modal_items
const char * name
Definition WM_types.hh:1033
struct ReportList * reports
struct wmOperatorType * type
View3DCameraControl * ED_view3d_cameracontrol_acquire(Depsgraph *depsgraph, Scene *scene, View3D *v3d, RegionView3D *rv3d)
Object * ED_view3d_cameracontrol_object_get(View3DCameraControl *vctrl)
void ED_view3d_cameracontrol_update(View3DCameraControl *vctrl, const bool use_autokey, bContext *C, const bool do_rotate, const bool do_translate)
void ED_view3d_cameracontrol_release(View3DCameraControl *vctrl, const bool restore)
#define FLY_ZUP_CORRECT_ACCEL
#define FLY_SMOOTH_FAC
static void flyEvent(FlyInfo *fly, const wmEvent *event)
@ FLY_AXISLOCK_STATE_OFF
@ FLY_AXISLOCK_STATE_ACTIVE
@ FLY_AXISLOCK_STATE_IDLE
static void fly_draw_status(bContext *C, wmOperator *op)
static int flyApply(bContext *C, FlyInfo *fly, bool is_confirm)
void fly_modal_keymap(wmKeyConfig *keyconf)
static void drawFlyPixel(const bContext *, ARegion *, void *arg)
static wmOperatorStatus fly_invoke(bContext *C, wmOperator *op, const wmEvent *event)
static wmOperatorStatus flyEnd(bContext *C, FlyInfo *fly)
static wmOperatorStatus fly_modal(bContext *C, wmOperator *op, const wmEvent *event)
#define FLY_ZUP_CORRECT_FAC
static void fly_cancel(bContext *C, wmOperator *op)
#define FLY_ROTATE_FAC
static bool initFlyInfo(bContext *C, FlyInfo *fly, wmOperator *op, const wmEvent *event)
void VIEW3D_OT_fly(wmOperatorType *ot)
@ FLY_MODAL_CANCEL
@ FLY_MODAL_ACCELERATE
@ FLY_MODAL_SPEED
@ FLY_MODAL_DIR_DOWN
@ FLY_MODAL_PRECISION_ENABLE
@ FLY_MODAL_AXIS_LOCK_Z
@ FLY_MODAL_PAN_DISABLE
@ FLY_MODAL_DIR_RIGHT
@ FLY_MODAL_DIR_UP
@ FLY_MODAL_FREELOOK_DISABLE
@ FLY_MODAL_FREELOOK_ENABLE
@ FLY_MODAL_DIR_FORWARD
@ FLY_MODAL_PRECISION_DISABLE
@ FLY_MODAL_AXIS_LOCK_X
@ FLY_MODAL_DIR_LEFT
@ FLY_MODAL_DIR_BACKWARD
@ FLY_MODAL_DECELERATE
@ FLY_MODAL_PAN_ENABLE
@ FLY_MODAL_CONFIRM
static void flyMoveCamera(bContext *C, FlyInfo *fly, const bool do_rotate, const bool do_translate, const bool is_confirm)
wmEventHandler_Op * WM_event_add_modal_handler(bContext *C, wmOperator *op)
void WM_event_add_notifier(const bContext *C, uint type, void *reference)
@ TIMER
@ EVT_MODAL_MAP
@ MOUSEMOVE
@ NDOF_MOTION
wmOperatorType * ot
Definition wm_files.cc:4237
wmKeyMap * WM_modalkeymap_ensure(wmKeyConfig *keyconf, const char *idname, const EnumPropertyItem *items)
Definition wm_keymap.cc:932
void WM_modalkeymap_assign(wmKeyMap *km, const char *opname)
wmKeyMap * WM_modalkeymap_find(wmKeyConfig *keyconf, const char *idname)
Definition wm_keymap.cc:959
void WM_cursor_warp(wmWindow *win, int x, int y)
wmTimer * WM_event_timer_add(wmWindowManager *wm, wmWindow *win, const wmEventType event_type, const double time_step)
void WM_event_timer_remove(wmWindowManager *wm, wmWindow *, wmTimer *timer)