Skip to content

Commit

Permalink
Re-introduce "Flightmode improvements"" (#3473)
Browse files Browse the repository at this point in the history
* Revert "Revert "Flightmode improvements" (#3472)"

This reverts commit a775dcd.

* workaround input-mouse issue in volume mode (see #3475)

* fix flow and linting
  • Loading branch information
philippotto authored Nov 21, 2018
1 parent 3304891 commit 82ab0f5
Show file tree
Hide file tree
Showing 26 changed files with 802 additions and 369 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.md).
### Changed

- Improved support for datasets with a large skew in scale (e.g., [600, 600, 35]). [#3398](https://github.com/scalableminds/webknossos/pull/3398)
- Improved performance for flight mode. [#3392](https://github.com/scalableminds/webknossos/pull/3392)

### Fixed

Expand Down
3 changes: 3 additions & 0 deletions app/assets/javascripts/libs/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,9 @@ export class InputMouse {
_.extend(this, BackboneEvents);
this.targetSelector = targetSelector;
this.domElement = document.querySelector(targetSelector);
if (!this.domElement) {
throw new Error(`Input couldn't be attached to the following selector ${targetSelector}`);
}
this.id = id;

this.leftMouseButton = new InputMouseButton("left", 1, this, this.id);
Expand Down
10 changes: 9 additions & 1 deletion app/assets/javascripts/oxalis/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,17 @@ export const OrthoViews = {
PLANE_XZ: "PLANE_XZ",
TDView: "TDView",
};
export const ArbitraryViewport = "arbitraryViewport";
export type OrthoView = $Keys<typeof OrthoViews>;
export type OrthoViewMap<T> = { [key: OrthoView]: T };

export const ArbitraryViewport = "arbitraryViewport";
export const ArbitraryViews = {
arbitraryViewport: "arbitraryViewport",
TDView: "TDView",
};
export type ArbitraryView = $Keys<typeof ArbitraryViews>;
export type ArbitraryViewMap<T> = { [key: ArbitraryView]: T };

export type Viewport = OrthoView | typeof ArbitraryViewport;
export const OrthoViewValues: Array<OrthoView> = Object.keys(OrthoViews);
export const OrthoViewIndices = {
Expand Down
216 changes: 216 additions & 0 deletions app/assets/javascripts/oxalis/controller/td_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
// @flow
import * as React from "react";
import * as Utils from "libs/utils";
import { InputMouse } from "libs/input";
import { getViewportScale, getInputCatcherRect } from "oxalis/model/accessors/view_mode_accessor";
import CameraController from "oxalis/controller/camera_controller";
import { voxelToNm } from "oxalis/model/scaleinfo";
import TrackballControls from "libs/trackball_controls";
import Store from "oxalis/store";
import type { OxalisState, Flycam, CameraData, Tracing } from "oxalis/store";
import * as THREE from "three";
import { OrthoViews, type Vector3 } from "oxalis/constants";
import type { Point2, OrthoViewMap } from "oxalis/constants";
import { connect } from "react-redux";
import {
setViewportAction,
setTDCameraAction,
zoomTDViewAction,
moveTDViewXAction,
moveTDViewYAction,
moveTDViewByVectorAction,
} from "oxalis/model/actions/view_mode_actions";
import PlaneView from "oxalis/view/plane_view";
import { getPosition } from "oxalis/model/accessors/flycam_accessor";
import * as skeletonController from "oxalis/controller/combinations/skeletontracing_plane_controller";

export function threeCameraToCameraData(camera: THREE.OrthographicCamera): CameraData {
const { position, up, near, far, lookAt, left, right, top, bottom } = camera;
const objToArr = ({ x, y, z }) => [x, y, z];
return {
left,
right,
top,
bottom,
near,
far,
position: objToArr(position),
up: objToArr(up),
lookAt: objToArr(lookAt),
};
}

type OwnProps = {|
cameras: OrthoViewMap<THREE.OrthographicCamera>,
planeView?: PlaneView,
tracing?: Tracing,
|};

type Props = {
...OwnProps,
flycam: Flycam,
scale: Vector3,
};

class TDController extends React.PureComponent<Props> {
controls: TrackballControls;
mouseController: InputMouse;
oldNmPos: Vector3;
isStarted: boolean;

componentDidMount() {
const { dataset, flycam } = Store.getState();
this.oldNmPos = voxelToNm(dataset.dataSource.scale, getPosition(flycam));
this.isStarted = true;

this.initMouse();
}

componentWillUnmount() {
this.isStarted = false;
if (this.mouseController != null) {
this.mouseController.destroy();
}
if (this.controls != null) {
this.controls.destroy();
}
}

initMouse(): void {
const tdView = OrthoViews.TDView;
const inputcatcherSelector = `#inputcatcher_${tdView}`;
Utils.waitForSelector(inputcatcherSelector).then(view => {
if (!this.isStarted) {
return;
}
this.mouseController = new InputMouse(
inputcatcherSelector,
this.getTDViewMouseControls(),
tdView,
);
this.initTrackballControls(view);
});
}

initTrackballControls(view): void {
const pos = voxelToNm(this.props.scale, getPosition(this.props.flycam));
const tdCamera = this.props.cameras[OrthoViews.TDView];
this.controls = new TrackballControls(tdCamera, view, new THREE.Vector3(...pos), () => {
// write threeJS camera into store
Store.dispatch(setTDCameraAction(threeCameraToCameraData(tdCamera)));
});

this.controls.noZoom = true;
this.controls.noPan = true;
this.controls.staticMoving = true;

this.controls.target.set(...pos);

// This is necessary, since we instantiated this.controls now. This should be removed
// when the workaround with requestAnimationFrame(initInputHandlers) is removed.
this.forceUpdate();
}

updateControls = () => this.controls.update(true);

getTDViewMouseControls(): Object {
const baseControls = {
leftDownMove: (delta: Point2) => this.moveTDView(delta),
scroll: (value: number) => this.zoomTDView(Utils.clamp(-1, value, 1), true),
over: () => {
Store.dispatch(setViewportAction(OrthoViews.TDView));
// Fix the rotation target of the TrackballControls
this.setTargetAndFixPosition();
},
pinch: delta => this.zoomTDView(delta, true),
};

const skeletonControls =
this.props.tracing != null &&
this.props.tracing.skeleton != null &&
this.props.planeView != null
? skeletonController.getTDViewMouseControls(this.props.planeView)
: {};

return {
...baseControls,
...skeletonControls,
};
}

setTargetAndFixPosition(): void {
const position = getPosition(this.props.flycam);
const nmPosition = voxelToNm(this.props.scale, position);

this.controls.target.set(...nmPosition);
this.controls.update();

// The following code is a dirty hack. If someone figures out
// how the trackball control's target can be set without affecting
// the camera position, go ahead.
// As the previous step will also move the camera, we need to
// fix this by offsetting the viewport

const invertedDiff = [];
for (let i = 0; i <= 2; i++) {
invertedDiff.push(this.oldNmPos[i] - nmPosition[i]);
}

if (invertedDiff.every(el => el === 0)) return;

this.oldNmPos = nmPosition;

const nmVector = new THREE.Vector3(...invertedDiff);
// moves camera by the nm vector
const camera = this.props.cameras[OrthoViews.TDView];

const rotation = THREE.Vector3.prototype.multiplyScalar.call(camera.rotation.clone(), -1);
// reverse euler order
rotation.order = rotation.order
.split("")
.reverse()
.join("");

nmVector.applyEuler(rotation);

Store.dispatch(moveTDViewByVectorAction(nmVector.x, nmVector.y));
}

zoomTDView(value: number, zoomToMouse: boolean = true): void {
let zoomToPosition;
if (zoomToMouse && this.mouseController) {
zoomToPosition = this.mouseController.position;
}
const { width } = getInputCatcherRect(OrthoViews.TDView);
Store.dispatch(zoomTDViewAction(value, zoomToPosition, width));
}

moveTDView(delta: Point2): void {
const scale = getViewportScale(OrthoViews.TDView);
Store.dispatch(moveTDViewXAction((delta.x / scale) * -1));
Store.dispatch(moveTDViewYAction((delta.y / scale) * -1));
}

render() {
if (!this.controls) {
return null;
}

return (
<CameraController
cameras={this.props.cameras}
onCameraPositionChanged={this.updateControls}
/>
);
}
}

export function mapStateToProps(state: OxalisState, ownProps: OwnProps): Props {
return {
...ownProps,
flycam: state.flycam,
scale: state.dataset.dataSource.scale,
};
}

export default connect(mapStateToProps)(TDController);
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ import {
toggleInactiveTreesAction,
} from "oxalis/model/actions/skeletontracing_actions";
import {
updateUserSettingAction,
setFlightmodeRecordingAction,
updateUserSettingAction,
} from "oxalis/model/actions/settings_actions";
import {
yawFlycamAction,
Expand All @@ -44,6 +44,7 @@ import Crosshair from "oxalis/geometries/crosshair";
import Model from "oxalis/model";
import SceneController from "oxalis/controller/scene_controller";
import Store from "oxalis/store";
import TDController from "oxalis/controller/td_controller";
import Toast from "libs/toast";
import * as Utils from "libs/utils";
import api from "oxalis/api/internal_api";
Expand All @@ -53,10 +54,10 @@ import messages from "messages";

const arbitraryViewportSelector = "#inputcatcher_arbitraryViewport";

type Props = {
type Props = {|
onRender: () => void,
viewMode: Mode,
};
|};

class ArbitraryController extends React.PureComponent<Props> {
// See comment in Controller class on general controller architecture.
Expand All @@ -68,7 +69,7 @@ class ArbitraryController extends React.PureComponent<Props> {
crosshair: Crosshair;
lastNodeMatrix: Matrix4x4;
input: {
mouse?: InputMouse,
mouseController: ?InputMouse,
keyboard?: InputKeyboard,
keyboardLoopDelayed?: InputKeyboard,
keyboardNoLoop?: InputKeyboardNoLoop,
Expand All @@ -81,7 +82,9 @@ class ArbitraryController extends React.PureComponent<Props> {

componentDidMount() {
_.extend(this, BackboneEvents);
this.input = {};
this.input = {
mouseController: null,
};
this.storePropertyUnsubscribers = [];
this.start();
}
Expand All @@ -92,7 +95,7 @@ class ArbitraryController extends React.PureComponent<Props> {

initMouse(): void {
Utils.waitForSelector(arbitraryViewportSelector).then(() => {
this.input.mouse = new InputMouse(arbitraryViewportSelector, {
this.input.mouseController = new InputMouse(arbitraryViewportSelector, {
leftDownMove: (delta: Point2) => {
if (this.props.viewMode === constants.MODE_ARBITRARY) {
Store.dispatch(
Expand Down Expand Up @@ -221,6 +224,7 @@ class ArbitraryController extends React.PureComponent<Props> {
// Rotate view by 180 deg
r: () => {
Store.dispatch(yawFlycamAction(Math.PI));
window.needsRerender = true;
},

// Delete active node and recenter last node
Expand Down Expand Up @@ -323,6 +327,7 @@ class ArbitraryController extends React.PureComponent<Props> {
this.crosshair.setVisibility(Store.getState().userConfiguration.displayCrosshair);

this.arbitraryView.addGeometry(this.plane);
this.arbitraryView.setArbitraryPlane(this.plane);
this.arbitraryView.addGeometry(this.crosshair);

this.bindToEvents();
Expand All @@ -337,6 +342,7 @@ class ArbitraryController extends React.PureComponent<Props> {
this.arbitraryView.draw();

this.isStarted = true;
this.forceUpdate();
}

unsubscribeStoreListeners() {
Expand Down Expand Up @@ -365,7 +371,7 @@ class ArbitraryController extends React.PureComponent<Props> {
};

destroyInput() {
Utils.__guard__(this.input.mouse, x => x.destroy());
Utils.__guard__(this.input.mouseController, x => x.destroy());
Utils.__guard__(this.input.keyboard, x => x.destroy());
Utils.__guard__(this.input.keyboardLoopDelayed, x => x.destroy());
Utils.__guard__(this.input.keyboardNoLoop, x => x.destroy());
Expand Down Expand Up @@ -434,7 +440,10 @@ class ArbitraryController extends React.PureComponent<Props> {
}

render() {
return null;
if (!this.arbitraryView) {
return null;
}
return <TDController cameras={this.arbitraryView.getCameras()} />;
}
}

Expand Down
Loading

0 comments on commit 82ab0f5

Please sign in to comment.