Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve handling of movements in 3rd dimension #5801

Merged
merged 11 commits into from
Nov 3, 2021
11 changes: 8 additions & 3 deletions frontend/javascripts/libs/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type KeyboardLoopHandler = {
(number, isOriginalEvent: boolean): void,
delayed: boolean,
lastTime: ?number,
customAdditionalDelayFn?: () => number,
};
type KeyboardBindingPress = [KeyboardKey, KeyboardHandler, KeyboardHandler];
type KeyboardBindingDownUp = [KeyboardKey, KeyboardHandler, KeyboardHandler];
Expand Down Expand Up @@ -124,7 +125,7 @@ export class InputKeyboardNoLoop {
}

// This module is "main" keyboard handler.
// It is able to handle key-presses and will continously
// It is able to handle key-presses and will continuously
// fire the attached callback.
export class InputKeyboard {
keyCallbackMap = {};
Expand Down Expand Up @@ -185,10 +186,14 @@ export class InputKeyboard {
this.buttonLoop();
}

if (this.delay >= 0) {
const totalDelay =
this.delay +
(callback.customAdditionalDelayFn != null ? callback.customAdditionalDelayFn() : 0);
if (totalDelay >= 0) {
setTimeout(() => {
callback.delayed = false;
}, this.delay);
callback.lastTime = new Date().getTime();
}, totalDelay);
}
},

Expand Down
3 changes: 2 additions & 1 deletion frontend/javascripts/libs/vector_input.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ class BaseVector<T: Vector3 | Vector6> extends React.PureComponent<BaseProps<T>,

getText(value: T | string): string {
if (Array.isArray(value)) {
return value.join(", ");
// todo: undo
return value.map(el => el.toFixed(2)).join(", ");
philippotto marked this conversation as resolved.
Show resolved Hide resolved
}
return value;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import BackboneEvents from "backbone-events-standalone";
import * as React from "react";
import _ from "lodash";
import api from "oxalis/api/internal_api";
import dimensions from "oxalis/model/dimensions";
import {
deleteActiveNodeAsUserAction,
createTreeAction,
Expand All @@ -19,7 +20,7 @@ import {
import { InputKeyboard, InputKeyboardNoLoop, InputMouse } from "libs/input";
import { document } from "libs/window";
import { getBaseVoxel } from "oxalis/model/scaleinfo";
import { getRequestLogZoomStep } from "oxalis/model/accessors/flycam_accessor";
import { getPosition, getRequestLogZoomStep } from "oxalis/model/accessors/flycam_accessor";
import { listenToStoreProperty } from "oxalis/model/helpers/listener_helpers";
import { setViewportAction } from "oxalis/model/actions/view_mode_actions";
import { updateUserSettingAction } from "oxalis/model/actions/settings_actions";
Expand Down Expand Up @@ -139,6 +140,68 @@ class VolumeKeybindings {
}
}

const getMoveValue = timeFactor => {
const state = Store.getState();
return (
(state.userConfiguration.moveValue * timeFactor) /
getBaseVoxel(state.dataset.dataSource.scale) /
constants.FPS
);
};

function createDelayAwareMoveHandler(multiplier: number) {
// The multiplier can be used for inverting the direction as well as for
// speeding up the movement as it's done for shift+f, for example.

const fn = (timeFactor, first) =>
MoveHandlers.moveZ(getMoveValue(timeFactor) * multiplier, first);

fn.customAdditionalDelayFn = () => {
// Depending on the float fraction of the current position, we want to
// delay subsequent movements longer or shorter.
// For example, when being at z=10.0 and keeping `f` pressed, the first
// move action will simply set z=11.0. Afterwards, a user-defined keyboard
// delay is awaited after which the continuous movement can begin (z=11.1,
// z=11.2, ... z=11.9, z=12.0...).
// However, doing the same logic with a starting z=10.99 would mean, that
// the initial movement bumps z to 11.99 and after the delay has passed,
// the slice will immediately switch to 12 (which is too fast).
// To compensate this effect, this code here takes the current fraction (and
// direction) into account to adapt the initial keyboard delay.

const state = Store.getState();
let direction = Math.sign(multiplier);

const moveDistanceIn1Second =
state.userConfiguration.moveValue / getBaseVoxel(state.dataset.dataSource.scale);
const { activeViewport } = state.viewModeData.plane;

if (activeViewport === OrthoViews.TDView) {
// Nothing should happen then, anyway.
return 0;
}

const thirdDim = dimensions.thirdDimensionForPlane(activeViewport);

if (state.userConfiguration.dynamicSpaceDirection) {
// Change direction of the value connected to space, based on the last direction
direction *= state.flycam.spaceDirectionOrtho[thirdDim];
}
const fraction = getPosition(state.flycam)[thirdDim] % 1;
const passedFraction = direction === 1 ? fraction : 1 - fraction;

// todo: remove
console.log({ moveDistanceIn1Second, passedFraction });
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// todo: remove
console.log({ moveDistanceIn1Second, passedFraction });


// Note that a passed fraction of 0 (e.g., z=11.0 and the direction
// goes towards 12), means that no additional delay is needed.
// The 1000 factor converts to ms.
return (1000 / moveDistanceIn1Second) * passedFraction;
};

return fn;
}

class PlaneController extends React.PureComponent<Props> {
// See comment in Controller class on general controller architecture.
//
Expand Down Expand Up @@ -266,15 +329,6 @@ class PlaneController extends React.PureComponent<Props> {
}
});

const getMoveValue = timeFactor => {
const state = Store.getState();
return (
(state.userConfiguration.moveValue * timeFactor) /
getBaseVoxel(state.dataset.dataSource.scale) /
constants.FPS
);
};

this.input.keyboard = new InputKeyboard({
// Move
left: timeFactor => MoveHandlers.moveX(-getMoveValue(timeFactor)),
Expand All @@ -290,19 +344,17 @@ class PlaneController extends React.PureComponent<Props> {
this.input.keyboardLoopDelayed = new InputKeyboard(
{
// KeyboardJS is sensitive to ordering (complex combos first)
"shift + f": (timeFactor, first) => MoveHandlers.moveZ(getMoveValue(timeFactor) * 5, first),
"shift + d": (timeFactor, first) =>
MoveHandlers.moveZ(-getMoveValue(timeFactor) * 5, first),

"shift + i": () => VolumeHandlers.changeBrushSizeIfBrushIsActiveBy(-1),
"shift + o": () => VolumeHandlers.changeBrushSizeIfBrushIsActiveBy(1),

"shift + space": (timeFactor, first) =>
MoveHandlers.moveZ(-getMoveValue(timeFactor), first),
"ctrl + space": (timeFactor, first) => MoveHandlers.moveZ(-getMoveValue(timeFactor), first),
space: (timeFactor, first) => MoveHandlers.moveZ(getMoveValue(timeFactor), first),
f: (timeFactor, first) => MoveHandlers.moveZ(getMoveValue(timeFactor), first),
d: (timeFactor, first) => MoveHandlers.moveZ(-getMoveValue(timeFactor), first),
"shift + f": createDelayAwareMoveHandler(5),
"shift + d": createDelayAwareMoveHandler(-5),

"shift + space": createDelayAwareMoveHandler(-1),
"ctrl + space": createDelayAwareMoveHandler(-1),
space: createDelayAwareMoveHandler(1),
f: createDelayAwareMoveHandler(1),
d: createDelayAwareMoveHandler(-1),

// Zoom in/out
i: () => MoveHandlers.zoom(1, false),
Expand All @@ -316,7 +368,9 @@ class PlaneController extends React.PureComponent<Props> {
},
...loopedKeyboardControls,
},
{ delay: Store.getState().userConfiguration.keyboardDelay },
{
delay: Store.getState().userConfiguration.keyboardDelay,
},
);

this.input.keyboardNoLoop = new InputKeyboardNoLoop(notLoopedKeyboardControls);
Expand Down
3 changes: 2 additions & 1 deletion frontend/javascripts/oxalis/model/reducers/flycam_reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ function moveReducer(
matrix[13] += vector[1];
matrix[14] += vector[2];
}
if (clampToEdge && planeId != null) {
// todo
if (false && clampToEdge && planeId != null) {
philippotto marked this conversation as resolved.
Show resolved Hide resolved
const dim = Dimensions.getIndices(planeId)[2];
if (vector[dim] > 0) {
// If the direction is incrementing, we clamp to .0 so that subsequent movements
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ class DatasetPositionView extends PureComponent<Props> {
};

render() {
const unflooredPosition = getPosition(this.props.flycam);
philippotto marked this conversation as resolved.
Show resolved Hide resolved
const position = V3.floor(getPosition(this.props.flycam));
const { isOutOfDatasetBounds, isOutOfTaskBounds } = this.isPositionOutOfBounds(position);
const copyPositionStyle =
Expand Down Expand Up @@ -113,7 +114,7 @@ class DatasetPositionView extends PureComponent<Props> {
</ButtonComponent>
</Tooltip>
<Vector3Input
value={position}
value={unflooredPosition}
philippotto marked this conversation as resolved.
Show resolved Hide resolved
onChange={this.handleChangePosition}
autoSize
style={positionInputStyle}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ function readStoredLayoutConfigs() {
try {
const version = JSON.parse(storedLayoutVersion);
const layouts = JSON.parse(layoutString);
if (currentLayoutVersion > version) {
if (currentLayoutVersion !== version) {
return defaultLayoutConfig;
}
if (
Expand Down Expand Up @@ -170,7 +170,7 @@ export function setActiveLayout(layoutKey: LayoutKeys, activeLayout: string) {
persistLayoutConfigsDebounced();
} else {
throw new Error(
`Active layout could not be set. The given layout ${layoutKey} with name ${activeLayout}
`Active layout could not be set. The given layout ${layoutKey} with name ${activeLayout}
was not found in layouts for ${mapLayoutKeysToLanguage[layoutKey]}.`,
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ class DatasetSettings extends React.PureComponent<DatasetSettingsProps, State> {
isDisabled: boolean,
onChange: (boolean, SyntheticMouseEvent<>) => void,
) => (
<Tooltip title={isDisabled ? "Enable" : "Disable"} placement="top">
<Tooltip title={isDisabled ? "Show" : "Hide"} placement="top">
{/* This div is necessary for the tooltip to be displayed */}
<div style={{ display: "inline-block", marginRight: 8 }}>
<Switch size="small" onChange={onChange} checked={!isDisabled} />
Expand Down
2 changes: 1 addition & 1 deletion frontend/javascripts/types/schemas/user_settings.schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const userSettings = {
dynamicSpaceDirection: { type: "boolean" },
keyboardDelay: { type: "number", minimum: 0, maximum: 500 },
mouseRotateValue: { type: "number", minimum: 0.0001, maximum: 0.02 },
moveValue: { type: "number", minimum: 30, maximum: 14000 },
moveValue: { type: "number", minimum: 3, maximum: 14000 },
philippotto marked this conversation as resolved.
Show resolved Hide resolved
moveValue3d: { type: "number", minimum: 30, maximum: 14000 },
newNodeNewTree: { type: "boolean" },
centerNewNode: { type: "boolean" },
Expand Down