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