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