Merge branch 'master' into blender2.8
[blender.git] / source / blender / editors / space_view3d / view3d_fly.c
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * Contributor(s): Campbell Barton
19  *
20  * ***** END GPL LICENSE BLOCK *****
21  */
22
23 /** \file blender/editors/space_view3d/view3d_fly.c
24  *  \ingroup spview3d
25  */
26
27 /* defines VIEW3D_OT_fly modal operator */
28
29 #ifdef WITH_INPUT_NDOF
30 //#  define NDOF_FLY_DEBUG
31 //#  define NDOF_FLY_DRAW_TOOMUCH  /* is this needed for ndof? - commented so redraw doesn't thrash - campbell */
32 #endif /* WITH_INPUT_NDOF */
33
34 #include "DNA_object_types.h"
35
36 #include "MEM_guardedalloc.h"
37
38 #include "BLI_math.h"
39 #include "BLI_blenlib.h"
40
41 #include "BKE_context.h"
42 #include "BKE_report.h"
43
44 #include "BLT_translation.h"
45
46 #include "BIF_gl.h"
47
48 #include "WM_api.h"
49 #include "WM_types.h"
50
51 #include "ED_screen.h"
52 #include "ED_space_api.h"
53
54 #include "PIL_time.h" /* smoothview */
55
56 #include "UI_interface.h"
57 #include "UI_resources.h"
58
59 #include "GPU_immediate.h"
60
61 #include "DEG_depsgraph.h"
62
63 #include "view3d_intern.h"  /* own include */
64
65 /* NOTE: these defines are saved in keymap files, do not change values but just add new ones */
66 enum {
67         FLY_MODAL_CANCEL = 1,
68         FLY_MODAL_CONFIRM,
69         FLY_MODAL_ACCELERATE,
70         FLY_MODAL_DECELERATE,
71         FLY_MODAL_PAN_ENABLE,
72         FLY_MODAL_PAN_DISABLE,
73         FLY_MODAL_DIR_FORWARD,
74         FLY_MODAL_DIR_BACKWARD,
75         FLY_MODAL_DIR_LEFT,
76         FLY_MODAL_DIR_RIGHT,
77         FLY_MODAL_DIR_UP,
78         FLY_MODAL_DIR_DOWN,
79         FLY_MODAL_AXIS_LOCK_X,
80         FLY_MODAL_AXIS_LOCK_Z,
81         FLY_MODAL_PRECISION_ENABLE,
82         FLY_MODAL_PRECISION_DISABLE,
83         FLY_MODAL_FREELOOK_ENABLE,
84         FLY_MODAL_FREELOOK_DISABLE,
85         FLY_MODAL_SPEED,        /* mousepan typically */
86 };
87
88 /* relative view axis locking - xlock, zlock */
89 typedef enum eFlyPanState {
90         /* disabled */
91         FLY_AXISLOCK_STATE_OFF    = 0,
92
93         /* enabled but not checking because mouse hasn't moved outside the margin since locking was checked an not needed
94          * when the mouse moves, locking is set to 2 so checks are done. */
95         FLY_AXISLOCK_STATE_IDLE   = 1,
96
97         /* mouse moved and checking needed, if no view altering is done its changed back to #FLY_AXISLOCK_STATE_IDLE */
98         FLY_AXISLOCK_STATE_ACTIVE = 2
99 } eFlyPanState;
100
101 /* called in transform_ops.c, on each regeneration of keymaps  */
102 void fly_modal_keymap(wmKeyConfig *keyconf)
103 {
104         static const EnumPropertyItem modal_items[] = {
105                 {FLY_MODAL_CONFIRM, "CONFIRM", 0, "Confirm", ""},
106                 {FLY_MODAL_CANCEL, "CANCEL", 0, "Cancel", ""},
107
108                 {FLY_MODAL_DIR_FORWARD, "FORWARD", 0, "Forward", ""},
109                 {FLY_MODAL_DIR_BACKWARD, "BACKWARD", 0, "Backward", ""},
110                 {FLY_MODAL_DIR_LEFT, "LEFT", 0, "Left", ""},
111                 {FLY_MODAL_DIR_RIGHT, "RIGHT", 0, "Right", ""},
112                 {FLY_MODAL_DIR_UP, "UP", 0, "Up", ""},
113                 {FLY_MODAL_DIR_DOWN, "DOWN", 0, "Down", ""},
114
115                 {FLY_MODAL_PAN_ENABLE, "PAN_ENABLE", 0, "Pan", ""},
116                 {FLY_MODAL_PAN_DISABLE, "PAN_DISABLE", 0, "Pan (Off)", ""},
117
118                 {FLY_MODAL_ACCELERATE, "ACCELERATE", 0, "Accelerate", ""},
119                 {FLY_MODAL_DECELERATE, "DECELERATE", 0, "Decelerate", ""},
120
121                 {FLY_MODAL_AXIS_LOCK_X, "AXIS_LOCK_X", 0, "X Axis Correction", "X axis correction (toggle)"},
122                 {FLY_MODAL_AXIS_LOCK_Z, "AXIS_LOCK_Z", 0, "X Axis Correction", "Z axis correction (toggle)"},
123
124                 {FLY_MODAL_PRECISION_ENABLE, "PRECISION_ENABLE", 0, "Precision", ""},
125                 {FLY_MODAL_PRECISION_DISABLE, "PRECISION_DISABLE", 0, "Precision (Off)", ""},
126
127                 {FLY_MODAL_FREELOOK_ENABLE, "FREELOOK_ENABLE", 0, "Rotation", ""},
128                 {FLY_MODAL_FREELOOK_DISABLE, "FREELOOK_DISABLE", 0, "Rotation (Off)", ""},
129
130                 {0, NULL, 0, NULL, NULL}};
131
132         wmKeyMap *keymap = WM_modalkeymap_get(keyconf, "View3D Fly Modal");
133
134         /* this function is called for each spacetype, only needs to add map once */
135         if (keymap && keymap->modal_items)
136                 return;
137
138         keymap = WM_modalkeymap_add(keyconf, "View3D Fly Modal", modal_items);
139
140         /* assign map to operators */
141         WM_modalkeymap_assign(keymap, "VIEW3D_OT_fly");
142 }
143
144 typedef struct FlyInfo {
145         /* context stuff */
146         RegionView3D *rv3d;
147         View3D *v3d;
148         ARegion *ar;
149         struct Depsgraph *depsgraph;
150         Scene *scene;
151
152         wmTimer *timer; /* needed for redraws */
153
154         short state;
155         bool redraw;
156         bool use_precision;
157         /* if the user presses shift they can look about
158          * without moving the direction there looking */
159         bool use_freelook;
160
161         int mval[2]; /* latest 2D mouse values */
162         int center_mval[2]; /* center mouse values */
163         float width, height; /* camera viewport dimensions */
164
165 #ifdef WITH_INPUT_NDOF
166         wmNDOFMotionData *ndof;  /* latest 3D mouse values */
167 #endif
168
169         /* fly state state */
170         float speed; /* the speed the view is moving per redraw */
171         short axis; /* Axis index to move along by default Z to move along the view */
172         bool pan_view; /* when true, pan the view instead of rotating */
173
174         eFlyPanState xlock, zlock;
175         float xlock_momentum, zlock_momentum; /* nicer dynamics */
176         float grid; /* world scale 1.0 default */
177
178         /* compare between last state */
179         double time_lastwheel; /* used to accelerate when using the mousewheel a lot */
180         double time_lastdraw; /* time between draws */
181
182         void *draw_handle_pixel;
183
184         /* use for some lag */
185         float dvec_prev[3]; /* old for some lag */
186
187         struct View3DCameraControl *v3d_camera_control;
188
189 } FlyInfo;
190
191 static void drawFlyPixel(const struct bContext *UNUSED(C), ARegion *UNUSED(ar), void *arg)
192 {
193         FlyInfo *fly = arg;
194         rctf viewborder;
195         int xoff, yoff;
196         float x1, x2, y1, y2;
197
198         if (ED_view3d_cameracontrol_object_get(fly->v3d_camera_control)) {
199                 ED_view3d_calc_camera_border(fly->scene, fly->depsgraph, fly->ar, fly->v3d, fly->rv3d, &viewborder, false);
200                 xoff = viewborder.xmin;
201                 yoff = viewborder.ymin;
202         }
203         else {
204                 xoff = 0;
205                 yoff = 0;
206         }
207
208         /* draws 4 edge brackets that frame the safe area where the
209          * mouse can move during fly mode without spinning the view */
210
211         x1 = xoff + 0.45f * fly->width;
212         y1 = yoff + 0.45f * fly->height;
213         x2 = xoff + 0.55f * fly->width;
214         y2 = yoff + 0.55f * fly->height;
215
216         GPUVertFormat *format = immVertexFormat();
217         uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
218
219         immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
220
221         immUniformThemeColor(TH_VIEW_OVERLAY);
222
223         immBegin(GPU_PRIM_LINES, 16);
224
225         /* bottom left */
226         immVertex2f(pos, x1, y1);
227         immVertex2f(pos, x1, y1 + 5);
228
229         immVertex2f(pos, x1, y1);
230         immVertex2f(pos, x1 + 5, y1);
231
232         /* top right */
233         immVertex2f(pos, x2, y2);
234         immVertex2f(pos, x2, y2 - 5);
235
236         immVertex2f(pos, x2, y2);
237         immVertex2f(pos, x2 - 5, y2);
238
239         /* top left */
240         immVertex2f(pos, x1, y2);
241         immVertex2f(pos, x1, y2 - 5);
242
243         immVertex2f(pos, x1, y2);
244         immVertex2f(pos, x1 + 5, y2);
245
246         /* bottom right */
247         immVertex2f(pos, x2, y1);
248         immVertex2f(pos, x2, y1 + 5);
249
250         immVertex2f(pos, x2, y1);
251         immVertex2f(pos, x2 - 5, y1);
252
253         immEnd();
254         immUnbindProgram();
255 }
256
257 static void fly_update_header(bContext *C, wmOperator *op, FlyInfo *fly)
258 {
259         char header[UI_MAX_DRAW_STR];
260         char buf[UI_MAX_DRAW_STR];
261
262         char *p = buf;
263         int available_len = sizeof(buf);
264
265 #define WM_MODALKEY(_id) \
266         WM_modalkeymap_operator_items_to_string_buf(op->type, (_id), true, UI_MAX_SHORTCUT_STR, &available_len, &p)
267
268         BLI_snprintf(header, sizeof(header), IFACE_("%s: confirm, %s: cancel, "
269                                                     "%s: pan enable, "
270                                                     "%s|%s|%s|%s|%s|%s: direction, "
271                                                     "%s: slow, %s: free look, "
272                                                     "%s: Upright x axis (%s), "
273                                                     "%s: Upright z axis (%s), "
274                                                     "%s: increase  speed, %s: decrease speed"),
275                      WM_MODALKEY(FLY_MODAL_CONFIRM), WM_MODALKEY(FLY_MODAL_CANCEL),
276                      WM_MODALKEY(FLY_MODAL_PAN_ENABLE),
277                      WM_MODALKEY(FLY_MODAL_DIR_FORWARD), WM_MODALKEY(FLY_MODAL_DIR_LEFT),
278                      WM_MODALKEY(FLY_MODAL_DIR_BACKWARD), WM_MODALKEY(FLY_MODAL_DIR_RIGHT),
279                      WM_MODALKEY(FLY_MODAL_DIR_UP), WM_MODALKEY(FLY_MODAL_DIR_DOWN),
280                      WM_MODALKEY(FLY_MODAL_PRECISION_ENABLE), WM_MODALKEY(FLY_MODAL_FREELOOK_ENABLE),
281                      WM_MODALKEY(FLY_MODAL_AXIS_LOCK_X), WM_bool_as_string(fly->xlock != FLY_AXISLOCK_STATE_OFF),
282                      WM_MODALKEY(FLY_MODAL_AXIS_LOCK_Z), WM_bool_as_string(fly->zlock != FLY_AXISLOCK_STATE_OFF),
283                      WM_MODALKEY(FLY_MODAL_ACCELERATE), WM_MODALKEY(FLY_MODAL_DECELERATE));
284
285 #undef WM_MODALKEY
286
287         ED_workspace_status_text(C, header);
288 }
289
290 /* FlyInfo->state */
291 enum {
292         FLY_RUNNING     = 0,
293         FLY_CANCEL      = 1,
294         FLY_CONFIRM     = 2,
295 };
296
297 static bool initFlyInfo(bContext *C, FlyInfo *fly, wmOperator *op, const wmEvent *event)
298 {
299         wmWindow *win = CTX_wm_window(C);
300         rctf viewborder;
301
302         float upvec[3]; /* tmp */
303         float mat[3][3];
304
305         fly->rv3d = CTX_wm_region_view3d(C);
306         fly->v3d = CTX_wm_view3d(C);
307         fly->ar = CTX_wm_region(C);
308         fly->depsgraph = CTX_data_depsgraph(C);
309         fly->scene = CTX_data_scene(C);
310
311 #ifdef NDOF_FLY_DEBUG
312         puts("\n-- fly begin --");
313 #endif
314
315         /* sanity check: for rare but possible case (if lib-linking the camera fails) */
316         if ((fly->rv3d->persp == RV3D_CAMOB) && (fly->v3d->camera == NULL)) {
317                 fly->rv3d->persp = RV3D_PERSP;
318         }
319
320         if (fly->rv3d->persp == RV3D_CAMOB && ID_IS_LINKED(fly->v3d->camera)) {
321                 BKE_report(op->reports, RPT_ERROR, "Cannot fly a camera from an external library");
322                 return false;
323         }
324
325         if (ED_view3d_offset_lock_check(fly->v3d, fly->rv3d)) {
326                 BKE_report(op->reports, RPT_ERROR, "Cannot fly when the view offset is locked");
327                 return false;
328         }
329
330         if (fly->rv3d->persp == RV3D_CAMOB && fly->v3d->camera->constraints.first) {
331                 BKE_report(op->reports, RPT_ERROR, "Cannot fly an object with constraints");
332                 return false;
333         }
334
335         fly->state = FLY_RUNNING;
336         fly->speed = 0.0f;
337         fly->axis = 2;
338         fly->pan_view = false;
339         fly->xlock = FLY_AXISLOCK_STATE_OFF;
340         fly->zlock = FLY_AXISLOCK_STATE_OFF;
341         fly->xlock_momentum = 0.0f;
342         fly->zlock_momentum = 0.0f;
343         fly->grid = 1.0f;
344         fly->use_precision = false;
345         fly->use_freelook = false;
346
347 #ifdef NDOF_FLY_DRAW_TOOMUCH
348         fly->redraw = 1;
349 #endif
350         zero_v3(fly->dvec_prev);
351
352         fly->timer = WM_event_add_timer(CTX_wm_manager(C), win, TIMER, 0.01f);
353
354         copy_v2_v2_int(fly->mval, event->mval);
355
356 #ifdef WITH_INPUT_NDOF
357         fly->ndof = NULL;
358 #endif
359
360         fly->time_lastdraw = fly->time_lastwheel = PIL_check_seconds_timer();
361
362         fly->draw_handle_pixel = ED_region_draw_cb_activate(fly->ar->type, drawFlyPixel, fly, REGION_DRAW_POST_PIXEL);
363
364         fly->rv3d->rflag |= RV3D_NAVIGATING;
365
366         /* detect whether to start with Z locking */
367         copy_v3_fl3(upvec, 1.0f, 0.0f, 0.0f);
368         copy_m3_m4(mat, fly->rv3d->viewinv);
369         mul_m3_v3(mat, upvec);
370         if (fabsf(upvec[2]) < 0.1f) {
371                 fly->zlock = FLY_AXISLOCK_STATE_IDLE;
372         }
373
374         fly->v3d_camera_control = ED_view3d_cameracontrol_acquire(
375                 CTX_data_depsgraph(C), fly->scene, fly->v3d, fly->rv3d,
376                 (U.uiflag & USER_CAM_LOCK_NO_PARENT) == 0);
377
378         /* calculate center */
379         if (ED_view3d_cameracontrol_object_get(fly->v3d_camera_control)) {
380                 ED_view3d_calc_camera_border(fly->scene, fly->depsgraph, fly->ar, fly->v3d, fly->rv3d, &viewborder, false);
381
382                 fly->width = BLI_rctf_size_x(&viewborder);
383                 fly->height = BLI_rctf_size_y(&viewborder);
384
385                 fly->center_mval[0] = viewborder.xmin + fly->width / 2;
386                 fly->center_mval[1] = viewborder.ymin + fly->height / 2;
387         }
388         else {
389                 fly->width = fly->ar->winx;
390                 fly->height = fly->ar->winy;
391
392                 fly->center_mval[0] = fly->width / 2;
393                 fly->center_mval[1] = fly->height / 2;
394         }
395
396         /* center the mouse, probably the UI mafia are against this but without its quite annoying */
397         WM_cursor_warp(win, fly->ar->winrct.xmin + fly->center_mval[0], fly->ar->winrct.ymin + fly->center_mval[1]);
398
399         fly_update_header(C, op, fly);
400         return 1;
401 }
402
403 static int flyEnd(bContext *C, FlyInfo *fly)
404 {
405         wmWindow *win;
406         RegionView3D *rv3d;
407
408         if (fly->state == FLY_RUNNING)
409                 return OPERATOR_RUNNING_MODAL;
410
411 #ifdef NDOF_FLY_DEBUG
412         puts("\n-- fly end --");
413 #endif
414
415         win = CTX_wm_window(C);
416         rv3d = fly->rv3d;
417
418         WM_event_remove_timer(CTX_wm_manager(C), win, fly->timer);
419
420         ED_region_draw_cb_exit(fly->ar->type, fly->draw_handle_pixel);
421
422         ED_view3d_cameracontrol_release(fly->v3d_camera_control, fly->state == FLY_CANCEL);
423
424         rv3d->rflag &= ~RV3D_NAVIGATING;
425
426 #ifdef WITH_INPUT_NDOF
427         if (fly->ndof)
428                 MEM_freeN(fly->ndof);
429 #endif
430
431         if (fly->state == FLY_CONFIRM) {
432                 MEM_freeN(fly);
433                 return OPERATOR_FINISHED;
434         }
435
436         MEM_freeN(fly);
437         return OPERATOR_CANCELLED;
438 }
439
440 static void flyEvent(bContext *C, wmOperator *op, FlyInfo *fly, const wmEvent *event)
441 {
442         if (event->type == TIMER && event->customdata == fly->timer) {
443                 fly->redraw = 1;
444         }
445         else if (event->type == MOUSEMOVE) {
446                 copy_v2_v2_int(fly->mval, event->mval);
447         }
448 #ifdef WITH_INPUT_NDOF
449         else if (event->type == NDOF_MOTION) {
450                 /* do these automagically get delivered? yes. */
451                 // puts("ndof motion detected in fly mode!");
452                 // static const char *tag_name = "3D mouse position";
453
454                 const wmNDOFMotionData *incoming_ndof = event->customdata;
455                 switch (incoming_ndof->progress) {
456                         case P_STARTING:
457                                 /* start keeping track of 3D mouse position */
458 #  ifdef NDOF_FLY_DEBUG
459                                 puts("start keeping track of 3D mouse position");
460 #  endif
461                                 /* fall-through */
462                         case P_IN_PROGRESS:
463                                 /* update 3D mouse position */
464 #  ifdef NDOF_FLY_DEBUG
465                                 putchar('.'); fflush(stdout);
466 #  endif
467                                 if (fly->ndof == NULL) {
468                                         // fly->ndof = MEM_mallocN(sizeof(wmNDOFMotionData), tag_name);
469                                         fly->ndof = MEM_dupallocN(incoming_ndof);
470                                         // fly->ndof = malloc(sizeof(wmNDOFMotionData));
471                                 }
472                                 else {
473                                         memcpy(fly->ndof, incoming_ndof, sizeof(wmNDOFMotionData));
474                                 }
475                                 break;
476                         case P_FINISHING:
477                                 /* stop keeping track of 3D mouse position */
478 #  ifdef NDOF_FLY_DEBUG
479                                 puts("stop keeping track of 3D mouse position");
480 #  endif
481                                 if (fly->ndof) {
482                                         MEM_freeN(fly->ndof);
483                                         // free(fly->ndof);
484                                         fly->ndof = NULL;
485                                 }
486                                 /* update the time else the view will jump when 2D mouse/timer resume */
487                                 fly->time_lastdraw = PIL_check_seconds_timer();
488                                 break;
489                         default:
490                                 break; /* should always be one of the above 3 */
491                 }
492         }
493 #endif /* WITH_INPUT_NDOF */
494         /* handle modal keymap first */
495         else if (event->type == EVT_MODAL_MAP) {
496                 switch (event->val) {
497                         case FLY_MODAL_CANCEL:
498                                 fly->state = FLY_CANCEL;
499                                 break;
500                         case FLY_MODAL_CONFIRM:
501                                 fly->state = FLY_CONFIRM;
502                                 break;
503
504                         /* speed adjusting with mousepan (trackpad) */
505                         case FLY_MODAL_SPEED:
506                         {
507                                 float fac = 0.02f * (event->prevy - event->y);
508
509                                 /* allowing to brake immediate */
510                                 if (fac > 0.0f && fly->speed < 0.0f)
511                                         fly->speed = 0.0f;
512                                 else if (fac < 0.0f && fly->speed > 0.0f)
513                                         fly->speed = 0.0f;
514                                 else
515                                         fly->speed += fly->grid * fac;
516
517                                 break;
518                         }
519                         case FLY_MODAL_ACCELERATE:
520                         {
521                                 double time_currwheel;
522                                 float time_wheel;
523
524                                 /* not quite correct but avoids confusion WASD/arrow keys 'locking up' */
525                                 if (fly->axis == -1) {
526                                         fly->axis = 2;
527                                         fly->speed = fabsf(fly->speed);
528                                 }
529
530                                 time_currwheel = PIL_check_seconds_timer();
531                                 time_wheel = (float)(time_currwheel - fly->time_lastwheel);
532                                 fly->time_lastwheel = time_currwheel;
533                                 /* Mouse wheel delays range from (0.5 == slow) to (0.01 == fast) */
534                                 time_wheel = 1.0f + (10.0f - (20.0f * min_ff(time_wheel, 0.5f))); /* 0-0.5 -> 0-5.0 */
535
536                                 if (fly->speed < 0.0f) {
537                                         fly->speed = 0.0f;
538                                 }
539                                 else {
540                                         fly->speed += fly->grid * time_wheel * (fly->use_precision ? 0.1f : 1.0f);
541                                 }
542                                 break;
543                         }
544                         case FLY_MODAL_DECELERATE:
545                         {
546                                 double time_currwheel;
547                                 float time_wheel;
548
549                                 /* not quite correct but avoids confusion WASD/arrow keys 'locking up' */
550                                 if (fly->axis == -1) {
551                                         fly->axis = 2;
552                                         fly->speed = -fabsf(fly->speed);
553                                 }
554
555                                 time_currwheel = PIL_check_seconds_timer();
556                                 time_wheel = (float)(time_currwheel - fly->time_lastwheel);
557                                 fly->time_lastwheel = time_currwheel;
558                                 time_wheel = 1.0f + (10.0f - (20.0f * min_ff(time_wheel, 0.5f))); /* 0-0.5 -> 0-5.0 */
559
560                                 if (fly->speed > 0.0f) {
561                                         fly->speed = 0;
562                                 }
563                                 else {
564                                         fly->speed -= fly->grid * time_wheel * (fly->use_precision ? 0.1f : 1.0f);
565                                 }
566                                 break;
567                         }
568                         case FLY_MODAL_PAN_ENABLE:
569                                 fly->pan_view = true;
570                                 break;
571                         case FLY_MODAL_PAN_DISABLE:
572                                 fly->pan_view = false;
573                                 break;
574
575                         /* implement WASD keys,
576                          * comments only for 'forward '*/
577                         case FLY_MODAL_DIR_FORWARD:
578                                 if (fly->axis == 2 && fly->speed < 0.0f) { /* reverse direction stops, tap again to continue */
579                                         fly->axis = -1;
580                                 }
581                                 else {
582                                         /* flip speed rather than stopping, game like motion,
583                                          * else increase like mousewheel if were already moving in that direction */
584                                         if (fly->speed < 0.0f)   fly->speed = -fly->speed;
585                                         else if (fly->axis == 2) fly->speed += fly->grid;
586                                         fly->axis = 2;
587                                 }
588                                 break;
589                         case FLY_MODAL_DIR_BACKWARD:
590                                 if (fly->axis == 2 && fly->speed > 0.0f) {
591                                         fly->axis = -1;
592                                 }
593                                 else {
594                                         if (fly->speed > 0.0f)   fly->speed = -fly->speed;
595                                         else if (fly->axis == 2) fly->speed -= fly->grid;
596
597                                         fly->axis = 2;
598                                 }
599                                 break;
600                         case FLY_MODAL_DIR_LEFT:
601                                 if (fly->axis == 0 && fly->speed < 0.0f) {
602                                         fly->axis = -1;
603                                 }
604                                 else {
605                                         if (fly->speed < 0.0f)   fly->speed = -fly->speed;
606                                         else if (fly->axis == 0) fly->speed += fly->grid;
607
608                                         fly->axis = 0;
609                                 }
610                                 break;
611                         case FLY_MODAL_DIR_RIGHT:
612                                 if (fly->axis == 0 && fly->speed > 0.0f) {
613                                         fly->axis = -1;
614                                 }
615                                 else {
616                                         if (fly->speed > 0.0f)   fly->speed = -fly->speed;
617                                         else if (fly->axis == 0) fly->speed -= fly->grid;
618
619                                         fly->axis = 0;
620                                 }
621                                 break;
622                         case FLY_MODAL_DIR_DOWN:
623                                 if (fly->axis == 1 && fly->speed < 0.0f) {
624                                         fly->axis = -1;
625                                 }
626                                 else {
627                                         if (fly->speed < 0.0f)   fly->speed = -fly->speed;
628                                         else if (fly->axis == 1) fly->speed += fly->grid;
629                                         fly->axis = 1;
630                                 }
631                                 break;
632                         case FLY_MODAL_DIR_UP:
633                                 if (fly->axis == 1 && fly->speed > 0.0f) {
634                                         fly->axis = -1;
635                                 }
636                                 else {
637                                         if (fly->speed > 0.0f)   fly->speed = -fly->speed;
638                                         else if (fly->axis == 1) fly->speed -= fly->grid;
639                                         fly->axis = 1;
640                                 }
641                                 break;
642
643                         case FLY_MODAL_AXIS_LOCK_X:
644                                 if (fly->xlock != FLY_AXISLOCK_STATE_OFF)
645                                         fly->xlock = FLY_AXISLOCK_STATE_OFF;
646                                 else {
647                                         fly->xlock = FLY_AXISLOCK_STATE_ACTIVE;
648                                         fly->xlock_momentum = 0.0;
649                                 }
650                                 fly_update_header(C, op, fly);
651                                 break;
652                         case FLY_MODAL_AXIS_LOCK_Z:
653                                 if (fly->zlock != FLY_AXISLOCK_STATE_OFF)
654                                         fly->zlock = FLY_AXISLOCK_STATE_OFF;
655                                 else {
656                                         fly->zlock = FLY_AXISLOCK_STATE_ACTIVE;
657                                         fly->zlock_momentum = 0.0;
658                                 }
659                                 fly_update_header(C, op, fly);
660                                 break;
661
662                         case FLY_MODAL_PRECISION_ENABLE:
663                                 fly->use_precision = true;
664                                 break;
665                         case FLY_MODAL_PRECISION_DISABLE:
666                                 fly->use_precision = false;
667                                 break;
668
669                         case FLY_MODAL_FREELOOK_ENABLE:
670                                 fly->use_freelook = true;
671                                 break;
672                         case FLY_MODAL_FREELOOK_DISABLE:
673                                 fly->use_freelook = false;
674                                 break;
675                 }
676         }
677 }
678
679 static void flyMoveCamera(bContext *C, FlyInfo *fly,
680                           const bool do_rotate, const bool do_translate)
681 {
682         ED_view3d_cameracontrol_update(fly->v3d_camera_control, true, C, do_rotate, do_translate);
683 }
684
685 static int flyApply(bContext *C, FlyInfo *fly)
686 {
687 #define FLY_ROTATE_FAC 10.0f /* more is faster */
688 #define FLY_ZUP_CORRECT_FAC 0.1f /* amount to correct per step */
689 #define FLY_ZUP_CORRECT_ACCEL 0.05f /* increase upright momentum each step */
690 #define FLY_SMOOTH_FAC 20.0f  /* higher value less lag */
691
692         /* fly mode - Shift+F
693          * a fly loop where the user can move move the view as if they are flying
694          */
695         RegionView3D *rv3d = fly->rv3d;
696
697         float mat[3][3]; /* 3x3 copy of the view matrix so we can move along the view axis */
698         float dvec[3] = {0, 0, 0}; /* this is the direction that's added to the view offset per redraw */
699
700         /* Camera Uprighting variables */
701         float moffset[2]; /* mouse offset from the views center */
702         float tmp_quat[4]; /* used for rotating the view */
703
704         int xmargin, ymargin; /* x and y margin are define the safe area where the mouses movement wont rotate the view */
705
706 #ifdef NDOF_FLY_DEBUG
707         {
708                 static unsigned int iteration = 1;
709                 printf("fly timer %d\n", iteration++);
710         }
711 #endif
712
713         xmargin = fly->width / 20.0f;
714         ymargin = fly->height / 20.0f;
715
716         {
717
718                 /* mouse offset from the center */
719                 moffset[0] = fly->mval[0] - fly->center_mval[0];
720                 moffset[1] = fly->mval[1] - fly->center_mval[1];
721
722                 /* enforce a view margin */
723                 if      (moffset[0] >  xmargin) moffset[0] -= xmargin;
724                 else if (moffset[0] < -xmargin) moffset[0] += xmargin;
725                 else                            moffset[0] =  0;
726
727                 if      (moffset[1] >  ymargin) moffset[1] -= ymargin;
728                 else if (moffset[1] < -ymargin) moffset[1] += ymargin;
729                 else                            moffset[1] =  0;
730
731
732                 /* scale the mouse movement by this value - scales mouse movement to the view size
733                  * moffset[0] / (ar->winx-xmargin * 2) - window size minus margin (same for y)
734                  *
735                  * the mouse moves isn't linear */
736
737                 if (moffset[0]) {
738                         moffset[0] /= fly->width - (xmargin * 2);
739                         moffset[0] *= fabsf(moffset[0]);
740                 }
741
742                 if (moffset[1]) {
743                         moffset[1] /= fly->height - (ymargin * 2);
744                         moffset[1] *= fabsf(moffset[1]);
745                 }
746
747                 /* Should we redraw? */
748                 if ((fly->speed != 0.0f) ||
749                     moffset[0] || moffset[1] ||
750                     (fly->zlock != FLY_AXISLOCK_STATE_OFF) ||
751                     (fly->xlock != FLY_AXISLOCK_STATE_OFF) ||
752                     dvec[0] || dvec[1] || dvec[2])
753                 {
754                         float dvec_tmp[3];
755
756                         /* time how fast it takes for us to redraw,
757                          * this is so simple scenes don't fly too fast */
758                         double time_current;
759                         float time_redraw;
760                         float time_redraw_clamped;
761 #ifdef NDOF_FLY_DRAW_TOOMUCH
762                         fly->redraw = 1;
763 #endif
764                         time_current = PIL_check_seconds_timer();
765                         time_redraw = (float)(time_current - fly->time_lastdraw);
766                         time_redraw_clamped = min_ff(0.05f, time_redraw); /* clamp redraw time to avoid jitter in roll correction */
767                         fly->time_lastdraw = time_current;
768
769                         /* Scale the time to use shift to scale the speed down- just like
770                          * shift slows many other areas of blender down */
771                         if (fly->use_precision)
772                                 fly->speed = fly->speed * (1.0f - time_redraw_clamped);
773
774                         copy_m3_m4(mat, rv3d->viewinv);
775
776                         if (fly->pan_view == true) {
777                                 /* pan only */
778                                 copy_v3_fl3(dvec_tmp, -moffset[0], -moffset[1], 0.0f);
779
780                                 if (fly->use_precision) {
781                                         dvec_tmp[0] *= 0.1f;
782                                         dvec_tmp[1] *= 0.1f;
783                                 }
784
785                                 mul_m3_v3(mat, dvec_tmp);
786                                 mul_v3_fl(dvec_tmp, time_redraw * 200.0f * fly->grid);
787                         }
788                         else {
789                                 float roll; /* similar to the angle between the camera's up and the Z-up,
790                                              * but its very rough so just roll */
791
792                                 /* rotate about the X axis- look up/down */
793                                 if (moffset[1]) {
794                                         float upvec[3];
795                                         copy_v3_fl3(upvec, 1.0f, 0.0f, 0.0f);
796                                         mul_m3_v3(mat, upvec);
797                                         /* Rotate about the relative up vec */
798                                         axis_angle_to_quat(tmp_quat, upvec, moffset[1] * time_redraw * -FLY_ROTATE_FAC);
799                                         mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, tmp_quat);
800
801                                         if (fly->xlock != FLY_AXISLOCK_STATE_OFF)
802                                                 fly->xlock = FLY_AXISLOCK_STATE_ACTIVE;  /* check for rotation */
803                                         if (fly->zlock != FLY_AXISLOCK_STATE_OFF)
804                                                 fly->zlock = FLY_AXISLOCK_STATE_ACTIVE;
805                                         fly->xlock_momentum = 0.0f;
806                                 }
807
808                                 /* rotate about the Y axis- look left/right */
809                                 if (moffset[0]) {
810                                         float upvec[3];
811                                         /* if we're upside down invert the moffset */
812                                         copy_v3_fl3(upvec, 0.0f, 1.0f, 0.0f);
813                                         mul_m3_v3(mat, upvec);
814
815                                         if (upvec[2] < 0.0f)
816                                                 moffset[0] = -moffset[0];
817
818                                         /* make the lock vectors */
819                                         if (fly->zlock) {
820                                                 copy_v3_fl3(upvec, 0.0f, 0.0f, 1.0f);
821                                         }
822                                         else {
823                                                 copy_v3_fl3(upvec, 0.0f, 1.0f, 0.0f);
824                                                 mul_m3_v3(mat, upvec);
825                                         }
826
827                                         /* Rotate about the relative up vec */
828                                         axis_angle_to_quat(tmp_quat, upvec, moffset[0] * time_redraw * FLY_ROTATE_FAC);
829                                         mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, tmp_quat);
830
831                                         if (fly->xlock != FLY_AXISLOCK_STATE_OFF)
832                                                 fly->xlock = FLY_AXISLOCK_STATE_ACTIVE;  /* check for rotation */
833                                         if (fly->zlock != FLY_AXISLOCK_STATE_OFF)
834                                                 fly->zlock = FLY_AXISLOCK_STATE_ACTIVE;
835                                 }
836
837                                 if (fly->zlock == FLY_AXISLOCK_STATE_ACTIVE) {
838                                         float upvec[3];
839                                         copy_v3_fl3(upvec, 1.0f, 0.0f, 0.0f);
840                                         mul_m3_v3(mat, upvec);
841
842                                         /* make sure we have some z rolling */
843                                         if (fabsf(upvec[2]) > 0.00001f) {
844                                                 roll = upvec[2] * 5.0f;
845                                                 /* rotate the view about this axis */
846                                                 copy_v3_fl3(upvec, 0.0f, 0.0f, 1.0f);
847                                                 mul_m3_v3(mat, upvec);
848                                                 /* Rotate about the relative up vec */
849                                                 axis_angle_to_quat(tmp_quat, upvec,
850                                                                    roll * time_redraw_clamped * fly->zlock_momentum * FLY_ZUP_CORRECT_FAC);
851                                                 mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, tmp_quat);
852
853                                                 fly->zlock_momentum += FLY_ZUP_CORRECT_ACCEL;
854                                         }
855                                         else {
856                                                 fly->zlock = FLY_AXISLOCK_STATE_IDLE; /* don't check until the view rotates again */
857                                                 fly->zlock_momentum = 0.0f;
858                                         }
859                                 }
860
861                                 /* only apply xcorrect when mouse isn't applying x rot */
862                                 if (fly->xlock == FLY_AXISLOCK_STATE_ACTIVE && moffset[1] == 0) {
863                                         float upvec[3];
864                                         copy_v3_fl3(upvec, 0.0f, 0.0f, 1.0f);
865                                         mul_m3_v3(mat, upvec);
866                                         /* make sure we have some z rolling */
867                                         if (fabsf(upvec[2]) > 0.00001f) {
868                                                 roll = upvec[2] * -5.0f;
869                                                 /* rotate the view about this axis */
870                                                 copy_v3_fl3(upvec, 1.0f, 0.0f, 0.0f);
871                                                 mul_m3_v3(mat, upvec);
872
873                                                 /* Rotate about the relative up vec */
874                                                 axis_angle_to_quat(tmp_quat, upvec, roll * time_redraw_clamped * fly->xlock_momentum * 0.1f);
875                                                 mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, tmp_quat);
876
877                                                 fly->xlock_momentum += 0.05f;
878                                         }
879                                         else {
880                                                 fly->xlock = FLY_AXISLOCK_STATE_IDLE; /* see above */
881                                                 fly->xlock_momentum = 0.0f;
882                                         }
883                                 }
884
885                                 if (fly->axis == -1) {
886                                         /* pause */
887                                         zero_v3(dvec_tmp);
888                                 }
889                                 else if (!fly->use_freelook) {
890                                         /* Normal operation */
891                                         /* define dvec, view direction vector */
892                                         zero_v3(dvec_tmp);
893                                         /* move along the current axis */
894                                         dvec_tmp[fly->axis] = 1.0f;
895
896                                         mul_m3_v3(mat, dvec_tmp);
897                                 }
898                                 else {
899                                         normalize_v3_v3(dvec_tmp, fly->dvec_prev);
900                                         if (fly->speed < 0.0f) {
901                                                 negate_v3(dvec_tmp);
902                                         }
903                                 }
904
905                                 mul_v3_fl(dvec_tmp, fly->speed * time_redraw * 0.25f);
906                         }
907
908                         /* impose a directional lag */
909                         interp_v3_v3v3(dvec, dvec_tmp, fly->dvec_prev, (1.0f / (1.0f + (time_redraw * FLY_SMOOTH_FAC))));
910
911                         if (rv3d->persp == RV3D_CAMOB) {
912                                 Object *lock_ob = ED_view3d_cameracontrol_object_get(fly->v3d_camera_control);
913                                 if (lock_ob->protectflag & OB_LOCK_LOCX) dvec[0] = 0.0;
914                                 if (lock_ob->protectflag & OB_LOCK_LOCY) dvec[1] = 0.0;
915                                 if (lock_ob->protectflag & OB_LOCK_LOCZ) dvec[2] = 0.0;
916                         }
917
918                         add_v3_v3(rv3d->ofs, dvec);
919
920                         if (rv3d->persp == RV3D_CAMOB) {
921                                 const bool do_rotate = ((fly->xlock != FLY_AXISLOCK_STATE_OFF) ||
922                                                         (fly->zlock != FLY_AXISLOCK_STATE_OFF) ||
923                                                         ((moffset[0] || moffset[1]) && !fly->pan_view));
924                                 const bool do_translate = (fly->speed != 0.0f || fly->pan_view);
925                                 flyMoveCamera(C, fly, do_rotate, do_translate);
926                         }
927
928                 }
929                 else {
930                         /* we're not redrawing but we need to update the time else the view will jump */
931                         fly->time_lastdraw = PIL_check_seconds_timer();
932                 }
933                 /* end drawing */
934                 copy_v3_v3(fly->dvec_prev, dvec);
935         }
936
937         return OPERATOR_FINISHED;
938 }
939
940 #ifdef WITH_INPUT_NDOF
941 static void flyApply_ndof(bContext *C, FlyInfo *fly)
942 {
943         Object *lock_ob = ED_view3d_cameracontrol_object_get(fly->v3d_camera_control);
944         bool has_translate, has_rotate;
945
946         view3d_ndof_fly(fly->ndof,
947                         fly->v3d, fly->rv3d,
948                         fly->use_precision, lock_ob ? lock_ob->protectflag : 0,
949                         &has_translate, &has_rotate);
950
951         if (has_translate || has_rotate) {
952                 fly->redraw = true;
953
954                 if (fly->rv3d->persp == RV3D_CAMOB) {
955                         flyMoveCamera(C, fly, has_rotate, has_translate);
956                 }
957         }
958 }
959 #endif /* WITH_INPUT_NDOF */
960
961 static int fly_invoke(bContext *C, wmOperator *op, const wmEvent *event)
962 {
963         RegionView3D *rv3d = CTX_wm_region_view3d(C);
964         FlyInfo *fly;
965
966         if (rv3d->viewlock & RV3D_LOCKED)
967                 return OPERATOR_CANCELLED;
968
969         fly = MEM_callocN(sizeof(FlyInfo), "FlyOperation");
970
971         op->customdata = fly;
972
973         if (initFlyInfo(C, fly, op, event) == false) {
974                 MEM_freeN(op->customdata);
975                 return OPERATOR_CANCELLED;
976         }
977
978         flyEvent(C, op, fly, event);
979
980         WM_event_add_modal_handler(C, op);
981
982         return OPERATOR_RUNNING_MODAL;
983 }
984
985 static void fly_cancel(bContext *C, wmOperator *op)
986 {
987         FlyInfo *fly = op->customdata;
988
989         fly->state = FLY_CANCEL;
990         flyEnd(C, fly);
991         op->customdata = NULL;
992 }
993
994 static int fly_modal(bContext *C, wmOperator *op, const wmEvent *event)
995 {
996         int exit_code;
997         bool do_draw = false;
998         FlyInfo *fly = op->customdata;
999         RegionView3D *rv3d = fly->rv3d;
1000         Object *fly_object = ED_view3d_cameracontrol_object_get(fly->v3d_camera_control);
1001
1002         fly->redraw = 0;
1003
1004         flyEvent(C, op, fly, event);
1005
1006 #ifdef WITH_INPUT_NDOF
1007         if (fly->ndof) { /* 3D mouse overrules [2D mouse + timer] */
1008                 if (event->type == NDOF_MOTION) {
1009                         flyApply_ndof(C, fly);
1010                 }
1011         }
1012         else
1013 #endif /* WITH_INPUT_NDOF */
1014         if (event->type == TIMER && event->customdata == fly->timer) {
1015                 flyApply(C, fly);
1016         }
1017
1018         do_draw |= fly->redraw;
1019
1020         exit_code = flyEnd(C, fly);
1021
1022         if (exit_code != OPERATOR_RUNNING_MODAL)
1023                 do_draw = true;
1024
1025         if (do_draw) {
1026                 if (rv3d->persp == RV3D_CAMOB) {
1027                         WM_event_add_notifier(C, NC_OBJECT | ND_TRANSFORM, fly_object);
1028                 }
1029
1030                 // puts("redraw!"); // too frequent, commented with NDOF_FLY_DRAW_TOOMUCH for now
1031                 ED_region_tag_redraw(CTX_wm_region(C));
1032         }
1033
1034         if (ELEM(exit_code, OPERATOR_FINISHED, OPERATOR_CANCELLED))
1035                 ED_workspace_status_text(C, NULL);
1036
1037         return exit_code;
1038 }
1039
1040 void VIEW3D_OT_fly(wmOperatorType *ot)
1041 {
1042         /* identifiers */
1043         ot->name = "Fly Navigation";
1044         ot->description = "Interactively fly around the scene";
1045         ot->idname = "VIEW3D_OT_fly";
1046
1047         /* api callbacks */
1048         ot->invoke = fly_invoke;
1049         ot->cancel = fly_cancel;
1050         ot->modal = fly_modal;
1051         ot->poll = ED_operator_region_view3d_active;
1052
1053         /* flags */
1054         ot->flag = OPTYPE_BLOCKING;
1055 }