This repository has been archived on 2023-10-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
blender-archive/source/blender/editors/space_view3d/view3d_fly.c

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1152 lines
32 KiB
C
Raw Normal View History

/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/** \file
* \ingroup spview3d
*
* Interactive fly navigation modal operator (flying around in space).
*
* \note Similar logic to `view3d_walk.c` changes here may apply there too.
2011-02-27 20:29:51 +00:00
*/
/* defines VIEW3D_OT_fly modal operator */
#ifdef WITH_INPUT_NDOF
//# define NDOF_FLY_DEBUG
/* is this needed for ndof? - commented so redraw doesn't thrash - campbell */
//# define NDOF_FLY_DRAW_TOOMUCH
#endif /* WITH_INPUT_NDOF */
#include "DNA_object_types.h"
#include "MEM_guardedalloc.h"
#include "BLI_blenlib.h"
#include "BLI_math.h"
#include "BKE_context.h"
#include "BKE_report.h"
#include "BLT_translation.h"
#include "WM_api.h"
#include "WM_types.h"
#include "ED_screen.h"
#include "ED_space_api.h"
#include "PIL_time.h" /* smoothview */
#include "UI_interface.h"
#include "UI_resources.h"
#include "GPU_immediate.h"
#include "DEG_depsgraph.h"
2012-04-02 06:26:52 +00:00
#include "view3d_intern.h" /* own include */
/* -------------------------------------------------------------------- */
/** \name Modal Key-map
* \{ */
/* NOTE: these defines are saved in keymap files,
* do not change values but just add new ones */
enum {
2012-03-10 06:46:23 +00:00
FLY_MODAL_CANCEL = 1,
FLY_MODAL_CONFIRM,
FLY_MODAL_ACCELERATE,
FLY_MODAL_DECELERATE,
FLY_MODAL_PAN_ENABLE,
FLY_MODAL_PAN_DISABLE,
FLY_MODAL_DIR_FORWARD,
FLY_MODAL_DIR_BACKWARD,
FLY_MODAL_DIR_LEFT,
FLY_MODAL_DIR_RIGHT,
FLY_MODAL_DIR_UP,
FLY_MODAL_DIR_DOWN,
FLY_MODAL_AXIS_LOCK_X,
FLY_MODAL_AXIS_LOCK_Z,
FLY_MODAL_PRECISION_ENABLE,
FLY_MODAL_PRECISION_DISABLE,
FLY_MODAL_FREELOOK_ENABLE,
FLY_MODAL_FREELOOK_DISABLE,
FLY_MODAL_SPEED, /* mousepan typically */
};
/* relative view axis locking - xlock, zlock */
typedef enum eFlyPanState {
/* disabled */
FLY_AXISLOCK_STATE_OFF = 0,
/* enabled but not checking because mouse hasn't moved outside the margin since locking was
* checked an not needed when the mouse moves, locking is set to 2 so checks are done. */
FLY_AXISLOCK_STATE_IDLE = 1,
/* mouse moved and checking needed,
* if no view altering is done its changed back to #FLY_AXISLOCK_STATE_IDLE */
FLY_AXISLOCK_STATE_ACTIVE = 2,
} eFlyPanState;
/* called in transform_ops.c, on each regeneration of keymaps */
void fly_modal_keymap(wmKeyConfig *keyconf)
{
static const EnumPropertyItem modal_items[] = {
2012-03-10 06:46:23 +00:00
{FLY_MODAL_CONFIRM, "CONFIRM", 0, "Confirm", ""},
{FLY_MODAL_CANCEL, "CANCEL", 0, "Cancel", ""},
{FLY_MODAL_DIR_FORWARD, "FORWARD", 0, "Forward", ""},
{FLY_MODAL_DIR_BACKWARD, "BACKWARD", 0, "Backward", ""},
{FLY_MODAL_DIR_LEFT, "LEFT", 0, "Left", ""},
{FLY_MODAL_DIR_RIGHT, "RIGHT", 0, "Right", ""},
{FLY_MODAL_DIR_UP, "UP", 0, "Up", ""},
{FLY_MODAL_DIR_DOWN, "DOWN", 0, "Down", ""},
{FLY_MODAL_PAN_ENABLE, "PAN_ENABLE", 0, "Pan", ""},
{FLY_MODAL_PAN_DISABLE, "PAN_DISABLE", 0, "Pan (Off)", ""},
{FLY_MODAL_ACCELERATE, "ACCELERATE", 0, "Accelerate", ""},
{FLY_MODAL_DECELERATE, "DECELERATE", 0, "Decelerate", ""},
2012-03-10 06:46:23 +00:00
{FLY_MODAL_AXIS_LOCK_X, "AXIS_LOCK_X", 0, "X Axis Correction", "X axis correction (toggle)"},
{FLY_MODAL_AXIS_LOCK_Z, "AXIS_LOCK_Z", 0, "X Axis Correction", "Z axis correction (toggle)"},
{FLY_MODAL_PRECISION_ENABLE, "PRECISION_ENABLE", 0, "Precision", ""},
{FLY_MODAL_PRECISION_DISABLE, "PRECISION_DISABLE", 0, "Precision (Off)", ""},
{FLY_MODAL_FREELOOK_ENABLE, "FREELOOK_ENABLE", 0, "Rotation", ""},
{FLY_MODAL_FREELOOK_DISABLE, "FREELOOK_DISABLE", 0, "Rotation (Off)", ""},
{0, NULL, 0, NULL, NULL},
};
wmKeyMap *keymap = WM_modalkeymap_find(keyconf, "View3D Fly Modal");
/* this function is called for each spacetype, only needs to add map once */
if (keymap && keymap->modal_items) {
2012-03-10 06:46:23 +00:00
return;
}
keymap = WM_modalkeymap_ensure(keyconf, "View3D Fly Modal", modal_items);
/* assign map to operators */
WM_modalkeymap_assign(keymap, "VIEW3D_OT_fly");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Internal Fly Structs
* \{ */
typedef struct FlyInfo {
/* context stuff */
RegionView3D *rv3d;
View3D *v3d;
ARegion *region;
struct Depsgraph *depsgraph;
Scene *scene;
2019-11-25 00:55:11 +11:00
/** Needed for updating that isn't triggered by input. */
2019-08-11 22:41:04 +10:00
wmTimer *timer;
short state;
bool redraw;
bool use_precision;
2019-08-11 22:41:04 +10:00
/** If the user presses shift they can look about without moving the direction there looking. */
bool use_freelook;
2019-08-11 22:34:22 +10:00
/**
* Needed for auto-keyframing, when animation isn't playing, only keyframe on confirmation.
*
* Currently we can't cancel this operator usefully while recording on animation playback
* (this would need to un-key all previous frames).
*/
bool anim_playing;
2019-08-11 22:41:04 +10:00
/** Latest 2D mouse values. */
int mval[2];
/** Center mouse values. */
int center_mval[2];
/** Camera viewport dimensions. */
float width, height;
#ifdef WITH_INPUT_NDOF
2019-08-11 22:41:04 +10:00
/** Latest 3D mouse values. */
wmNDOFMotionData *ndof;
#endif
/* fly state state */
2019-08-11 22:41:04 +10:00
/** The speed the view is moving per redraw. */
float speed;
/** Axis index to move along by default Z to move along the view. */
short axis;
/** When true, pan the view instead of rotating. */
bool pan_view;
eFlyPanState xlock, zlock;
2019-08-11 22:41:04 +10:00
/** Nicer dynamics. */
float xlock_momentum, zlock_momentum;
/** World scale 1.0 default. */
float grid;
/* compare between last state */
2020-07-10 11:41:14 +10:00
/** Used to accelerate when using the mouse-wheel a lot. */
2019-08-11 22:41:04 +10:00
double time_lastwheel;
/** Time between draws. */
double time_lastdraw;
void *draw_handle_pixel;
/* use for some lag */
2019-08-11 22:41:04 +10:00
/** Keep the previous value to smooth transitions (use lag). */
float dvec_prev[3];
struct View3DCameraControl *v3d_camera_control;
} FlyInfo;
/** \} */
/* -------------------------------------------------------------------- */
/** \name Internal Fly Drawing
* \{ */
/* prototypes */
#ifdef WITH_INPUT_NDOF
static void flyApply_ndof(bContext *C, FlyInfo *fly, bool is_confirm);
#endif /* WITH_INPUT_NDOF */
static int flyApply(bContext *C, struct FlyInfo *fly, bool is_confirm);
static void drawFlyPixel(const struct bContext *UNUSED(C), ARegion *UNUSED(region), void *arg)
{
FlyInfo *fly = arg;
rctf viewborder;
int xoff, yoff;
float x1, x2, y1, y2;
if (ED_view3d_cameracontrol_object_get(fly->v3d_camera_control)) {
ED_view3d_calc_camera_border(
fly->scene, fly->depsgraph, fly->region, fly->v3d, fly->rv3d, &viewborder, false);
xoff = viewborder.xmin;
yoff = viewborder.ymin;
}
else {
xoff = 0;
yoff = 0;
}
/* draws 4 edge brackets that frame the safe area where the
* mouse can move during fly mode without spinning the view */
2012-03-10 06:46:23 +00:00
x1 = xoff + 0.45f * fly->width;
y1 = yoff + 0.45f * fly->height;
x2 = xoff + 0.55f * fly->width;
y2 = yoff + 0.55f * fly->height;
2012-03-10 06:46:23 +00:00
GPUVertFormat *format = immVertexFormat();
uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
immUniformThemeColor3(TH_VIEW_OVERLAY);
immBegin(GPU_PRIM_LINES, 16);
/* bottom left */
immVertex2f(pos, x1, y1);
immVertex2f(pos, x1, y1 + 5);
2012-03-10 06:46:23 +00:00
immVertex2f(pos, x1, y1);
immVertex2f(pos, x1 + 5, y1);
2012-03-10 06:46:23 +00:00
/* top right */
immVertex2f(pos, x2, y2);
immVertex2f(pos, x2, y2 - 5);
2012-03-10 06:46:23 +00:00
immVertex2f(pos, x2, y2);
immVertex2f(pos, x2 - 5, y2);
2012-03-10 06:46:23 +00:00
/* top left */
immVertex2f(pos, x1, y2);
immVertex2f(pos, x1, y2 - 5);
2012-03-10 06:46:23 +00:00
immVertex2f(pos, x1, y2);
immVertex2f(pos, x1 + 5, y2);
2012-03-10 06:46:23 +00:00
/* bottom right */
immVertex2f(pos, x2, y1);
immVertex2f(pos, x2, y1 + 5);
immVertex2f(pos, x2, y1);
immVertex2f(pos, x2 - 5, y1);
2012-03-10 06:46:23 +00:00
immEnd();
immUnbindProgram();
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Internal Fly Logic
* \{ */
/* FlyInfo->state */
enum {
FLY_RUNNING = 0,
FLY_CANCEL = 1,
FLY_CONFIRM = 2,
};
static bool initFlyInfo(bContext *C, FlyInfo *fly, wmOperator *op, const wmEvent *event)
{
wmWindowManager *wm = CTX_wm_manager(C);
2012-03-10 06:46:23 +00:00
wmWindow *win = CTX_wm_window(C);
rctf viewborder;
2012-04-02 06:26:52 +00:00
float upvec[3]; /* tmp */
float mat[3][3];
2012-03-10 06:46:23 +00:00
fly->rv3d = CTX_wm_region_view3d(C);
fly->v3d = CTX_wm_view3d(C);
fly->region = CTX_wm_region(C);
Refactor access to dependency graph This change ensures that operators which needs access to evaluated data first makes sure there is a dependency graph. Other accesses to the dependency graph made it more explicit about whether they just need a valid dependency graph pointer or whether they expect the graph to be already evaluated. This replaces OPTYPE_USE_EVAL_DATA which is now removed. Some general rules about usage of accessors: - Drawing is expected to happen from a fully evaluated dependency graph. There is now a function to access it, which will in the future control that dependency graph is actually evaluated. This check is not yet done because there are some things to be taken care about first: for example, post-update hooks might leave scene in a state where something is still tagged for update. - All operators which needs to access evaluated state must use CTX_data_ensure_evaluated_depsgraph(). This function replaces OPTYPE_USE_EVAL_DATA. The call is generally to be done in the very beginning of the operator, prior other logic (unless this is some comprehensive operator which might or might not need access to an evaluated state). This call is never to be used from a loop. If some utility function requires evaluated state of dependency graph the graph is to be passed as an explicit argument. This way it is clear that no evaluation happens in a loop or something like this. - All cases which needs to know dependency graph pointer, but which doesn't want to actually evaluate it can use old-style function CTX_data_depsgraph_pointer(), assuming that underlying code will ensure dependency graph is evaluated prior to accessing it. - The new functions are replacing OPTYPE_USE_EVAL_DATA, so now it is explicit and local about where dependency graph is being ensured. This commit also contains some fixes of wrong usage of evaluation functions on original objects. Ideally should be split out, but in reality with all the APIs being renamed is quite tricky. Fixes T67454: Blender crash on rapid undo and select Speculation here is that sometimes undo and selection operators are sometimes handled in the same event loop iteration, which leaves non-evaluated dependency graph. Fixes T67973: Crash on Fix Deforms operator Fixes T67902: Crash when undo a loop cut Reviewers: brecht Reviewed By: brecht Subscribers: lichtwerk Maniphest Tasks: T67454 Differential Revision: https://developer.blender.org/D5343
2019-07-25 16:36:22 +02:00
fly->depsgraph = CTX_data_expect_evaluated_depsgraph(C);
2012-03-10 06:46:23 +00:00
fly->scene = CTX_data_scene(C);
#ifdef NDOF_FLY_DEBUG
puts("\n-- fly begin --");
#endif
/* sanity check: for rare but possible case (if lib-linking the camera fails) */
if ((fly->rv3d->persp == RV3D_CAMOB) && (fly->v3d->camera == NULL)) {
fly->rv3d->persp = RV3D_PERSP;
}
if (fly->rv3d->persp == RV3D_CAMOB && ID_IS_LINKED(fly->v3d->camera)) {
BKE_report(op->reports, RPT_ERROR, "Cannot fly a camera from an external library");
return false;
}
if (ED_view3d_offset_lock_check(fly->v3d, fly->rv3d)) {
BKE_report(op->reports, RPT_ERROR, "Cannot fly when the view offset is locked");
return false;
}
2012-03-10 06:46:23 +00:00
if (fly->rv3d->persp == RV3D_CAMOB && fly->v3d->camera->constraints.first) {
BKE_report(op->reports, RPT_ERROR, "Cannot fly an object with constraints");
return false;
}
2012-03-10 06:46:23 +00:00
fly->state = FLY_RUNNING;
fly->speed = 0.0f;
fly->axis = 2;
fly->pan_view = false;
fly->xlock = FLY_AXISLOCK_STATE_OFF;
fly->zlock = FLY_AXISLOCK_STATE_OFF;
2012-03-10 06:46:23 +00:00
fly->xlock_momentum = 0.0f;
fly->zlock_momentum = 0.0f;
fly->grid = 1.0f;
fly->use_precision = false;
fly->use_freelook = false;
fly->anim_playing = ED_screen_animation_playing(wm);
#ifdef NDOF_FLY_DRAW_TOOMUCH
fly->redraw = 1;
#endif
2012-03-23 20:18:09 +00:00
zero_v3(fly->dvec_prev);
2012-03-10 06:46:23 +00:00
fly->timer = WM_event_add_timer(CTX_wm_manager(C), win, TIMER, 0.01f);
copy_v2_v2_int(fly->mval, event->mval);
#ifdef WITH_INPUT_NDOF
fly->ndof = NULL;
#endif
2012-03-10 06:46:23 +00:00
fly->time_lastdraw = fly->time_lastwheel = PIL_check_seconds_timer();
fly->draw_handle_pixel = ED_region_draw_cb_activate(
fly->region->type, drawFlyPixel, fly, REGION_DRAW_POST_PIXEL);
fly->rv3d->rflag |= RV3D_NAVIGATING;
2013-06-25 22:58:23 +00:00
/* detect whether to start with Z locking */
copy_v3_fl3(upvec, 1.0f, 0.0f, 0.0f);
copy_m3_m4(mat, fly->rv3d->viewinv);
mul_m3_v3(mat, upvec);
2012-07-21 15:27:40 +00:00
if (fabsf(upvec[2]) < 0.1f) {
fly->zlock = FLY_AXISLOCK_STATE_IDLE;
2012-07-21 15:27:40 +00:00
}
Refactor access to dependency graph This change ensures that operators which needs access to evaluated data first makes sure there is a dependency graph. Other accesses to the dependency graph made it more explicit about whether they just need a valid dependency graph pointer or whether they expect the graph to be already evaluated. This replaces OPTYPE_USE_EVAL_DATA which is now removed. Some general rules about usage of accessors: - Drawing is expected to happen from a fully evaluated dependency graph. There is now a function to access it, which will in the future control that dependency graph is actually evaluated. This check is not yet done because there are some things to be taken care about first: for example, post-update hooks might leave scene in a state where something is still tagged for update. - All operators which needs to access evaluated state must use CTX_data_ensure_evaluated_depsgraph(). This function replaces OPTYPE_USE_EVAL_DATA. The call is generally to be done in the very beginning of the operator, prior other logic (unless this is some comprehensive operator which might or might not need access to an evaluated state). This call is never to be used from a loop. If some utility function requires evaluated state of dependency graph the graph is to be passed as an explicit argument. This way it is clear that no evaluation happens in a loop or something like this. - All cases which needs to know dependency graph pointer, but which doesn't want to actually evaluate it can use old-style function CTX_data_depsgraph_pointer(), assuming that underlying code will ensure dependency graph is evaluated prior to accessing it. - The new functions are replacing OPTYPE_USE_EVAL_DATA, so now it is explicit and local about where dependency graph is being ensured. This commit also contains some fixes of wrong usage of evaluation functions on original objects. Ideally should be split out, but in reality with all the APIs being renamed is quite tricky. Fixes T67454: Blender crash on rapid undo and select Speculation here is that sometimes undo and selection operators are sometimes handled in the same event loop iteration, which leaves non-evaluated dependency graph. Fixes T67973: Crash on Fix Deforms operator Fixes T67902: Crash when undo a loop cut Reviewers: brecht Reviewed By: brecht Subscribers: lichtwerk Maniphest Tasks: T67454 Differential Revision: https://developer.blender.org/D5343
2019-07-25 16:36:22 +02:00
fly->v3d_camera_control = ED_view3d_cameracontrol_acquire(
fly->depsgraph, fly->scene, fly->v3d, fly->rv3d);
/* calculate center */
if (ED_view3d_cameracontrol_object_get(fly->v3d_camera_control)) {
ED_view3d_calc_camera_border(
fly->scene, fly->depsgraph, fly->region, fly->v3d, fly->rv3d, &viewborder, false);
fly->width = BLI_rctf_size_x(&viewborder);
fly->height = BLI_rctf_size_y(&viewborder);
fly->center_mval[0] = viewborder.xmin + fly->width / 2;
fly->center_mval[1] = viewborder.ymin + fly->height / 2;
}
else {
fly->width = fly->region->winx;
fly->height = fly->region->winy;
fly->center_mval[0] = fly->width / 2;
fly->center_mval[1] = fly->height / 2;
}
/* center the mouse, probably the UI mafia are against this but without its quite annoying */
WM_cursor_warp(win,
fly->region->winrct.xmin + fly->center_mval[0],
fly->region->winrct.ymin + fly->center_mval[1]);
return 1;
}
static int flyEnd(bContext *C, FlyInfo *fly)
{
wmWindow *win;
RegionView3D *rv3d;
if (fly->state == FLY_RUNNING) {
return OPERATOR_RUNNING_MODAL;
}
if (fly->state == FLY_CONFIRM) {
/* Needed for auto_keyframe. */
#ifdef WITH_INPUT_NDOF
if (fly->ndof) {
flyApply_ndof(C, fly, true);
}
else
#endif /* WITH_INPUT_NDOF */
{
flyApply(C, fly, true);
}
}
#ifdef NDOF_FLY_DEBUG
puts("\n-- fly end --");
#endif
win = CTX_wm_window(C);
rv3d = fly->rv3d;
WM_event_remove_timer(CTX_wm_manager(C), win, fly->timer);
ED_region_draw_cb_exit(fly->region->type, fly->draw_handle_pixel);
ED_view3d_cameracontrol_release(fly->v3d_camera_control, fly->state == FLY_CANCEL);
rv3d->rflag &= ~RV3D_NAVIGATING;
#ifdef WITH_INPUT_NDOF
if (fly->ndof) {
MEM_freeN(fly->ndof);
}
#endif
if (fly->state == FLY_CONFIRM) {
MEM_freeN(fly);
return OPERATOR_FINISHED;
}
MEM_freeN(fly);
return OPERATOR_CANCELLED;
}
static void flyEvent(FlyInfo *fly, const wmEvent *event)
{
if (event->type == TIMER && event->customdata == fly->timer) {
fly->redraw = 1;
}
else if (event->type == MOUSEMOVE) {
copy_v2_v2_int(fly->mval, event->mval);
}
#ifdef WITH_INPUT_NDOF
else if (event->type == NDOF_MOTION) {
2012-04-02 06:26:52 +00:00
/* do these automagically get delivered? yes. */
// puts("ndof motion detected in fly mode!");
2012-08-26 11:35:43 +00:00
// static const char *tag_name = "3D mouse position";
const wmNDOFMotionData *incoming_ndof = event->customdata;
switch (incoming_ndof->progress) {
case P_STARTING:
2012-04-02 06:26:52 +00:00
/* start keeping track of 3D mouse position */
# ifdef NDOF_FLY_DEBUG
puts("start keeping track of 3D mouse position");
# endif
/* fall-through */
case P_IN_PROGRESS:
2012-04-02 06:26:52 +00:00
/* update 3D mouse position */
# ifdef NDOF_FLY_DEBUG
putchar('.');
fflush(stdout);
# endif
if (fly->ndof == NULL) {
// fly->ndof = MEM_mallocN(sizeof(wmNDOFMotionData), tag_name);
fly->ndof = MEM_dupallocN(incoming_ndof);
// fly->ndof = malloc(sizeof(wmNDOFMotionData));
}
else {
memcpy(fly->ndof, incoming_ndof, sizeof(wmNDOFMotionData));
}
break;
case P_FINISHING:
2012-07-08 20:36:00 +00:00
/* stop keeping track of 3D mouse position */
# ifdef NDOF_FLY_DEBUG
puts("stop keeping track of 3D mouse position");
# endif
if (fly->ndof) {
MEM_freeN(fly->ndof);
// free(fly->ndof);
fly->ndof = NULL;
}
/* update the time else the view will jump when 2D mouse/timer resume */
2012-03-10 06:46:23 +00:00
fly->time_lastdraw = PIL_check_seconds_timer();
break;
default:
break; /* should always be one of the above 3 */
}
2012-03-10 06:46:23 +00:00
}
#endif /* WITH_INPUT_NDOF */
/* handle modal keymap first */
else if (event->type == EVT_MODAL_MAP) {
switch (event->val) {
case FLY_MODAL_CANCEL:
fly->state = FLY_CANCEL;
break;
case FLY_MODAL_CONFIRM:
fly->state = FLY_CONFIRM;
break;
/* speed adjusting with mousepan (trackpad) */
case FLY_MODAL_SPEED: {
float fac = 0.02f * (event->prevy - event->y);
/* allowing to brake immediate */
if (fac > 0.0f && fly->speed < 0.0f) {
fly->speed = 0.0f;
}
else if (fac < 0.0f && fly->speed > 0.0f) {
fly->speed = 0.0f;
}
else {
fly->speed += fly->grid * fac;
}
break;
}
case FLY_MODAL_ACCELERATE: {
double time_currwheel;
float time_wheel;
/* not quite correct but avoids confusion WASD/arrow keys 'locking up' */
if (fly->axis == -1) {
fly->axis = 2;
fly->speed = fabsf(fly->speed);
}
2012-03-10 06:46:23 +00:00
time_currwheel = PIL_check_seconds_timer();
time_wheel = (float)(time_currwheel - fly->time_lastwheel);
fly->time_lastwheel = time_currwheel;
2012-05-25 09:51:53 +00:00
/* Mouse wheel delays range from (0.5 == slow) to (0.01 == fast) */
/* 0-0.5 -> 0-5.0 */
time_wheel = 1.0f + (10.0f - (20.0f * min_ff(time_wheel, 0.5f)));
if (fly->speed < 0.0f) {
2012-03-10 06:46:23 +00:00
fly->speed = 0.0f;
}
else {
2012-03-10 06:46:23 +00:00
fly->speed += fly->grid * time_wheel * (fly->use_precision ? 0.1f : 1.0f);
}
break;
}
case FLY_MODAL_DECELERATE: {
double time_currwheel;
float time_wheel;
/* not quite correct but avoids confusion WASD/arrow keys 'locking up' */
if (fly->axis == -1) {
fly->axis = 2;
fly->speed = -fabsf(fly->speed);
}
2012-03-10 06:46:23 +00:00
time_currwheel = PIL_check_seconds_timer();
time_wheel = (float)(time_currwheel - fly->time_lastwheel);
fly->time_lastwheel = time_currwheel;
/* 0-0.5 -> 0-5.0 */
time_wheel = 1.0f + (10.0f - (20.0f * min_ff(time_wheel, 0.5f)));
if (fly->speed > 0.0f) {
2012-03-10 06:46:23 +00:00
fly->speed = 0;
}
else {
2012-03-10 06:46:23 +00:00
fly->speed -= fly->grid * time_wheel * (fly->use_precision ? 0.1f : 1.0f);
}
break;
}
case FLY_MODAL_PAN_ENABLE:
fly->pan_view = true;
break;
case FLY_MODAL_PAN_DISABLE:
fly->pan_view = false;
break;
/* implement WASD keys,
* comments only for 'forward '*/
case FLY_MODAL_DIR_FORWARD:
if (fly->axis == 2 && fly->speed < 0.0f) {
/* reverse direction stops, tap again to continue */
fly->axis = -1;
}
else {
2020-07-10 11:41:14 +10:00
/* Flip speed rather than stopping, game like motion,
* else increase like mouse-wheel if we're already moving in that direction. */
if (fly->speed < 0.0f) {
fly->speed = -fly->speed;
}
else if (fly->axis == 2) {
fly->speed += fly->grid;
}
2012-03-10 06:46:23 +00:00
fly->axis = 2;
}
break;
case FLY_MODAL_DIR_BACKWARD:
2012-03-10 06:46:23 +00:00
if (fly->axis == 2 && fly->speed > 0.0f) {
fly->axis = -1;
}
else {
if (fly->speed > 0.0f) {
fly->speed = -fly->speed;
}
else if (fly->axis == 2) {
fly->speed -= fly->grid;
}
2012-03-10 06:46:23 +00:00
fly->axis = 2;
}
break;
case FLY_MODAL_DIR_LEFT:
2012-03-10 06:46:23 +00:00
if (fly->axis == 0 && fly->speed < 0.0f) {
fly->axis = -1;
}
else {
if (fly->speed < 0.0f) {
fly->speed = -fly->speed;
}
else if (fly->axis == 0) {
fly->speed += fly->grid;
}
2012-03-10 06:46:23 +00:00
fly->axis = 0;
}
break;
case FLY_MODAL_DIR_RIGHT:
2012-03-10 06:46:23 +00:00
if (fly->axis == 0 && fly->speed > 0.0f) {
fly->axis = -1;
}
else {
if (fly->speed > 0.0f) {
fly->speed = -fly->speed;
}
else if (fly->axis == 0) {
fly->speed -= fly->grid;
}
2012-03-10 06:46:23 +00:00
fly->axis = 0;
}
break;
case FLY_MODAL_DIR_DOWN:
2012-03-10 06:46:23 +00:00
if (fly->axis == 1 && fly->speed < 0.0f) {
fly->axis = -1;
}
else {
if (fly->speed < 0.0f) {
fly->speed = -fly->speed;
}
else if (fly->axis == 1) {
fly->speed += fly->grid;
}
2012-03-10 06:46:23 +00:00
fly->axis = 1;
}
break;
case FLY_MODAL_DIR_UP:
2012-03-10 06:46:23 +00:00
if (fly->axis == 1 && fly->speed > 0.0f) {
fly->axis = -1;
}
else {
if (fly->speed > 0.0f) {
fly->speed = -fly->speed;
}
else if (fly->axis == 1) {
fly->speed -= fly->grid;
}
2012-03-10 06:46:23 +00:00
fly->axis = 1;
}
break;
case FLY_MODAL_AXIS_LOCK_X:
if (fly->xlock != FLY_AXISLOCK_STATE_OFF) {
fly->xlock = FLY_AXISLOCK_STATE_OFF;
}
else {
fly->xlock = FLY_AXISLOCK_STATE_ACTIVE;
fly->xlock_momentum = 0.0;
}
break;
case FLY_MODAL_AXIS_LOCK_Z:
if (fly->zlock != FLY_AXISLOCK_STATE_OFF) {
fly->zlock = FLY_AXISLOCK_STATE_OFF;
}
else {
fly->zlock = FLY_AXISLOCK_STATE_ACTIVE;
fly->zlock_momentum = 0.0;
}
break;
case FLY_MODAL_PRECISION_ENABLE:
fly->use_precision = true;
break;
case FLY_MODAL_PRECISION_DISABLE:
fly->use_precision = false;
break;
case FLY_MODAL_FREELOOK_ENABLE:
fly->use_freelook = true;
break;
case FLY_MODAL_FREELOOK_DISABLE:
fly->use_freelook = false;
break;
}
}
}
static void flyMoveCamera(bContext *C,
FlyInfo *fly,
const bool do_rotate,
const bool do_translate,
const bool is_confirm)
{
/* we only consider autokeying on playback or if user confirmed fly on the same frame
* otherwise we get a keyframe even if the user cancels. */
const bool use_autokey = is_confirm || fly->anim_playing;
ED_view3d_cameracontrol_update(fly->v3d_camera_control, use_autokey, C, do_rotate, do_translate);
}
static int flyApply(bContext *C, FlyInfo *fly, bool is_confirm)
{
#define FLY_ROTATE_FAC 10.0f /* more is faster */
2011-04-29 04:43:36 +00:00
#define FLY_ZUP_CORRECT_FAC 0.1f /* amount to correct per step */
#define FLY_ZUP_CORRECT_ACCEL 0.05f /* increase upright momentum each step */
#define FLY_SMOOTH_FAC 20.0f /* higher value less lag */
/* fly mode - Shift+F
* a fly loop where the user can move move the view as if they are flying
*/
2012-03-10 06:46:23 +00:00
RegionView3D *rv3d = fly->rv3d;
/* 3x3 copy of the view matrix so we can move along the view axis */
float mat[3][3];
/* this is the direction that's added to the view offset per redraw */
float dvec[3] = {0, 0, 0};
/* Camera Uprighting variables */
float moffset[2]; /* mouse offset from the views center */
float tmp_quat[4]; /* used for rotating the view */
/* x and y margin are define the safe area where the mouses movement wont rotate the view */
int xmargin, ymargin;
#ifdef NDOF_FLY_DEBUG
2012-01-19 16:04:44 +00:00
{
static uint iteration = 1;
2012-01-19 16:04:44 +00:00
printf("fly timer %d\n", iteration++);
}
#endif
xmargin = fly->width / 20.0f;
ymargin = fly->height / 20.0f;
{
/* mouse offset from the center */
moffset[0] = fly->mval[0] - fly->center_mval[0];
moffset[1] = fly->mval[1] - fly->center_mval[1];
/* enforce a view margin */
if (moffset[0] > xmargin) {
moffset[0] -= xmargin;
}
else if (moffset[0] < -xmargin) {
moffset[0] += xmargin;
}
else {
moffset[0] = 0;
}
if (moffset[1] > ymargin) {
moffset[1] -= ymargin;
}
else if (moffset[1] < -ymargin) {
moffset[1] += ymargin;
}
else {
moffset[1] = 0;
}
/* scale the mouse movement by this value - scales mouse movement to the view size
* moffset[0] / (region->winx-xmargin * 2) - window size minus margin (same for y)
*
2012-03-18 07:38:51 +00:00
* the mouse moves isn't linear */
if (moffset[0]) {
moffset[0] /= fly->width - (xmargin * 2);
moffset[0] *= fabsf(moffset[0]);
}
if (moffset[1]) {
moffset[1] /= fly->height - (ymargin * 2);
moffset[1] *= fabsf(moffset[1]);
}
/* Should we redraw? */
2012-03-10 06:46:23 +00:00
if ((fly->speed != 0.0f) || moffset[0] || moffset[1] ||
(fly->zlock != FLY_AXISLOCK_STATE_OFF) || (fly->xlock != FLY_AXISLOCK_STATE_OFF) ||
2012-03-10 06:46:23 +00:00
dvec[0] || dvec[1] || dvec[2]) {
float dvec_tmp[3];
2012-03-10 06:46:23 +00:00
/* time how fast it takes for us to redraw,
2012-03-18 07:38:51 +00:00
* this is so simple scenes don't fly too fast */
2012-03-10 06:46:23 +00:00
double time_current;
float time_redraw;
float time_redraw_clamped;
#ifdef NDOF_FLY_DRAW_TOOMUCH
fly->redraw = 1;
#endif
2012-03-10 06:46:23 +00:00
time_current = PIL_check_seconds_timer();
time_redraw = (float)(time_current - fly->time_lastdraw);
/* clamp redraw time to avoid jitter in roll correction */
time_redraw_clamped = min_ff(0.05f, time_redraw);
2012-03-10 06:46:23 +00:00
fly->time_lastdraw = time_current;
/* Scale the time to use shift to scale the speed down- just like
* shift slows many other areas of blender down */
if (fly->use_precision) {
2012-03-10 06:46:23 +00:00
fly->speed = fly->speed * (1.0f - time_redraw_clamped);
}
copy_m3_m4(mat, rv3d->viewinv);
if (fly->pan_view == true) {
/* pan only */
copy_v3_fl3(dvec_tmp, -moffset[0], -moffset[1], 0.0f);
if (fly->use_precision) {
dvec_tmp[0] *= 0.1f;
dvec_tmp[1] *= 0.1f;
}
mul_m3_v3(mat, dvec_tmp);
mul_v3_fl(dvec_tmp, time_redraw * 200.0f * fly->grid);
}
else {
2012-01-19 16:04:44 +00:00
float roll; /* similar to the angle between the camera's up and the Z-up,
* but its very rough so just roll */
/* rotate about the X axis- look up/down */
if (moffset[1]) {
float upvec[3];
copy_v3_fl3(upvec, 1.0f, 0.0f, 0.0f);
mul_m3_v3(mat, upvec);
2012-01-19 16:04:44 +00:00
/* Rotate about the relative up vec */
axis_angle_to_quat(tmp_quat, upvec, moffset[1] * time_redraw * -FLY_ROTATE_FAC);
mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, tmp_quat);
if (fly->xlock != FLY_AXISLOCK_STATE_OFF) {
fly->xlock = FLY_AXISLOCK_STATE_ACTIVE; /* check for rotation */
}
if (fly->zlock != FLY_AXISLOCK_STATE_OFF) {
fly->zlock = FLY_AXISLOCK_STATE_ACTIVE;
}
2012-03-10 06:46:23 +00:00
fly->xlock_momentum = 0.0f;
}
/* rotate about the Y axis- look left/right */
if (moffset[0]) {
float upvec[3];
/* if we're upside down invert the moffset */
copy_v3_fl3(upvec, 0.0f, 1.0f, 0.0f);
mul_m3_v3(mat, upvec);
if (upvec[2] < 0.0f) {
2012-03-10 06:46:23 +00:00
moffset[0] = -moffset[0];
}
/* make the lock vectors */
if (fly->zlock) {
copy_v3_fl3(upvec, 0.0f, 0.0f, 1.0f);
}
else {
copy_v3_fl3(upvec, 0.0f, 1.0f, 0.0f);
mul_m3_v3(mat, upvec);
}
2012-01-19 16:04:44 +00:00
/* Rotate about the relative up vec */
axis_angle_to_quat(tmp_quat, upvec, moffset[0] * time_redraw * FLY_ROTATE_FAC);
mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, tmp_quat);
if (fly->xlock != FLY_AXISLOCK_STATE_OFF) {
fly->xlock = FLY_AXISLOCK_STATE_ACTIVE; /* check for rotation */
}
if (fly->zlock != FLY_AXISLOCK_STATE_OFF) {
fly->zlock = FLY_AXISLOCK_STATE_ACTIVE;
}
}
if (fly->zlock == FLY_AXISLOCK_STATE_ACTIVE) {
float upvec[3];
copy_v3_fl3(upvec, 1.0f, 0.0f, 0.0f);
mul_m3_v3(mat, upvec);
2012-04-02 06:26:52 +00:00
/* make sure we have some z rolling */
if (fabsf(upvec[2]) > 0.00001f) {
2012-03-10 06:46:23 +00:00
roll = upvec[2] * 5.0f;
/* rotate the view about this axis */
copy_v3_fl3(upvec, 0.0f, 0.0f, 1.0f);
mul_m3_v3(mat, upvec);
2012-01-19 16:04:44 +00:00
/* Rotate about the relative up vec */
axis_angle_to_quat(tmp_quat,
upvec,
roll * time_redraw_clamped * fly->zlock_momentum *
FLY_ZUP_CORRECT_FAC);
mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, tmp_quat);
fly->zlock_momentum += FLY_ZUP_CORRECT_ACCEL;
}
else {
/* don't check until the view rotates again */
fly->zlock = FLY_AXISLOCK_STATE_IDLE;
2012-03-10 06:46:23 +00:00
fly->zlock_momentum = 0.0f;
}
}
/* only apply xcorrect when mouse isn't applying x rot */
if (fly->xlock == FLY_AXISLOCK_STATE_ACTIVE && moffset[1] == 0) {
float upvec[3];
copy_v3_fl3(upvec, 0.0f, 0.0f, 1.0f);
mul_m3_v3(mat, upvec);
2012-04-02 06:26:52 +00:00
/* make sure we have some z rolling */
2011-08-19 16:21:29 +00:00
if (fabsf(upvec[2]) > 0.00001f) {
2012-03-10 06:46:23 +00:00
roll = upvec[2] * -5.0f;
/* rotate the view about this axis */
copy_v3_fl3(upvec, 1.0f, 0.0f, 0.0f);
mul_m3_v3(mat, upvec);
2012-01-19 16:04:44 +00:00
/* Rotate about the relative up vec */
2012-03-10 06:46:23 +00:00
axis_angle_to_quat(
tmp_quat, upvec, roll * time_redraw_clamped * fly->xlock_momentum * 0.1f);
mul_qt_qtqt(rv3d->viewquat, rv3d->viewquat, tmp_quat);
fly->xlock_momentum += 0.05f;
}
else {
fly->xlock = FLY_AXISLOCK_STATE_IDLE; /* see above */
2012-03-10 06:46:23 +00:00
fly->xlock_momentum = 0.0f;
}
}
if (fly->axis == -1) {
/* pause */
zero_v3(dvec_tmp);
}
else if (!fly->use_freelook) {
/* Normal operation */
/* define dvec, view direction vector */
zero_v3(dvec_tmp);
/* move along the current axis */
2012-03-10 06:46:23 +00:00
dvec_tmp[fly->axis] = 1.0f;
mul_m3_v3(mat, dvec_tmp);
}
else {
normalize_v3_v3(dvec_tmp, fly->dvec_prev);
2012-02-22 16:52:06 +00:00
if (fly->speed < 0.0f) {
negate_v3(dvec_tmp);
}
}
mul_v3_fl(dvec_tmp, fly->speed * time_redraw * 0.25f);
}
/* impose a directional lag */
interp_v3_v3v3(
dvec, dvec_tmp, fly->dvec_prev, (1.0f / (1.0f + (time_redraw * FLY_SMOOTH_FAC))));
add_v3_v3(rv3d->ofs, dvec);
if (rv3d->persp == RV3D_CAMOB) {
const bool do_rotate = ((fly->xlock != FLY_AXISLOCK_STATE_OFF) ||
(fly->zlock != FLY_AXISLOCK_STATE_OFF) ||
((moffset[0] || moffset[1]) && !fly->pan_view));
const bool do_translate = (fly->speed != 0.0f || fly->pan_view);
flyMoveCamera(C, fly, do_rotate, do_translate, is_confirm);
}
}
else {
/* we're not redrawing but we need to update the time else the view will jump */
2012-03-10 06:46:23 +00:00
fly->time_lastdraw = PIL_check_seconds_timer();
}
/* end drawing */
copy_v3_v3(fly->dvec_prev, dvec);
}
return OPERATOR_FINISHED;
}
#ifdef WITH_INPUT_NDOF
static void flyApply_ndof(bContext *C, FlyInfo *fly, bool is_confirm)
{
Object *lock_ob = ED_view3d_cameracontrol_object_get(fly->v3d_camera_control);
bool has_translate, has_rotate;
view3d_ndof_fly(fly->ndof,
fly->v3d,
fly->rv3d,
fly->use_precision,
lock_ob ? lock_ob->protectflag : 0,
&has_translate,
&has_rotate);
if (has_translate || has_rotate) {
fly->redraw = true;
if (fly->rv3d->persp == RV3D_CAMOB) {
flyMoveCamera(C, fly, has_rotate, has_translate, is_confirm);
}
}
}
#endif /* WITH_INPUT_NDOF */
/** \} */
/* -------------------------------------------------------------------- */
/** \name Fly Operator
* \{ */
static int fly_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
2012-03-10 06:46:23 +00:00
RegionView3D *rv3d = CTX_wm_region_view3d(C);
FlyInfo *fly;
VR: Initial Virtual Reality support - Milestone 1, Scene Inspection NOTE: While most of the milestone 1 goals are there, a few smaller features and improvements are still to be done. Big picture of this milestone: Initial, OpenXR-based virtual reality support for users and foundation for advanced use cases. Maniphest Task: https://developer.blender.org/T71347 The tasks contains more information about this milestone. To be clear: This is not a feature rich VR implementation, it's focused on the initial scene inspection use case. We intentionally focused on that, further features like controller support are part of the next milestone. - How to use? Instructions on how to use this are here: https://wiki.blender.org/wiki/User:Severin/GSoC-2019/How_to_Test These will be updated and moved to a more official place (likely the manual) soon. Currently Windows Mixed Reality and Oculus devices are usable. Valve/HTC headsets don't support the OpenXR standard yet and hence, do not work with this implementation. --------------- This is the C-side implementation of the features added for initial VR support as per milestone 1. A "VR Scene Inspection" Add-on will be committed separately, to expose the VR functionality in the UI. It also adds some further features for milestone 1, namely a landmarking system (stored view locations in the VR space) Main additions/features: * Support for rendering viewports to an HMD, with good performance. * Option to sync the VR view perspective with a fully interactive, regular 3D View (VR-Mirror). * Option to disable positional tracking. Keeps the current position (calculated based on the VR eye center pose) when enabled while a VR session is running. * Some regular viewport settings for the VR view * RNA/Python-API to query and set VR session state information. * WM-XR: Layer tying Ghost-XR to the Blender specific APIs/data * wmSurface API: drawable, non-window container (manages Ghost-OpenGL and GPU context) * DNA/RNA for management of VR session settings * `--debug-xr` and `--debug-xr-time` commandline options * Utility batch & config file for using the Oculus runtime on Windows. * Most VR data is runtime only. The exception is user settings which are saved to files (`XrSessionSettings`). * VR support can be disabled through the `WITH_XR_OPENXR` compiler flag. For architecture and code documentation, see https://wiki.blender.org/wiki/Source/Interface/XR. --------------- A few thank you's: * A huge shoutout to Ray Molenkamp for his help during the project - it would have not been that successful without him! * Sebastian Koenig and Simeon Conzendorf for testing and feedback! * The reviewers, especially Brecht Van Lommel! * Dalai Felinto for pushing and managing me to get this done ;) * The OpenXR working group for providing an open standard. I think we're the first bigger application to adopt OpenXR. Congratulations to them and ourselves :) This project started as a Google Summer of Code 2019 project - "Core Support of Virtual Reality Headsets through OpenXR" (see https://wiki.blender.org/wiki/User:Severin/GSoC-2019/). Some further information, including ideas for further improvements can be found in the final GSoC report: https://wiki.blender.org/wiki/User:Severin/GSoC-2019/Final_Report Differential Revisions: D6193, D7098 Reviewed by: Brecht Van Lommel, Jeroen Bakker
2020-03-17 20:20:55 +01:00
if (RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ANY_TRANSFORM) {
return OPERATOR_CANCELLED;
}
2012-03-10 06:46:23 +00:00
fly = MEM_callocN(sizeof(FlyInfo), "FlyOperation");
2012-03-10 06:46:23 +00:00
op->customdata = fly;
if (initFlyInfo(C, fly, op, event) == false) {
MEM_freeN(op->customdata);
return OPERATOR_CANCELLED;
}
flyEvent(fly, event);
WM_event_add_modal_handler(C, op);
return OPERATOR_RUNNING_MODAL;
}
static void fly_cancel(bContext *C, wmOperator *op)
{
FlyInfo *fly = op->customdata;
fly->state = FLY_CANCEL;
flyEnd(C, fly);
2012-03-10 06:46:23 +00:00
op->customdata = NULL;
}
static int fly_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
int exit_code;
bool do_draw = false;
2012-03-10 06:46:23 +00:00
FlyInfo *fly = op->customdata;
RegionView3D *rv3d = fly->rv3d;
Object *fly_object = ED_view3d_cameracontrol_object_get(fly->v3d_camera_control);
2012-03-10 06:46:23 +00:00
fly->redraw = 0;
flyEvent(fly, event);
#ifdef WITH_INPUT_NDOF
if (fly->ndof) { /* 3D mouse overrules [2D mouse + timer] */
2012-03-10 06:46:23 +00:00
if (event->type == NDOF_MOTION) {
flyApply_ndof(C, fly, false);
}
}
else
#endif /* WITH_INPUT_NDOF */
if (event->type == TIMER && event->customdata == fly->timer) {
flyApply(C, fly, false);
}
do_draw |= fly->redraw;
exit_code = flyEnd(C, fly);
if (exit_code != OPERATOR_RUNNING_MODAL) {
do_draw = true;
}
2010-11-24 16:54:18 +00:00
if (do_draw) {
2012-03-10 06:46:23 +00:00
if (rv3d->persp == RV3D_CAMOB) {
WM_event_add_notifier(C, NC_OBJECT | ND_TRANSFORM, fly_object);
}
// puts("redraw!"); // too frequent, commented with NDOF_FLY_DRAW_TOOMUCH for now
ED_region_tag_redraw(CTX_wm_region(C));
}
return exit_code;
}
void VIEW3D_OT_fly(wmOperatorType *ot)
{
/* identifiers */
2012-03-10 06:46:23 +00:00
ot->name = "Fly Navigation";
ot->description = "Interactively fly around the scene";
ot->idname = "VIEW3D_OT_fly";
/* api callbacks */
2012-03-10 06:46:23 +00:00
ot->invoke = fly_invoke;
ot->cancel = fly_cancel;
ot->modal = fly_modal;
ot->poll = ED_operator_region_view3d_active;
/* flags */
2012-03-10 06:46:23 +00:00
ot->flag = OPTYPE_BLOCKING;
}
/** \} */