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

Time-track movements in the 3D viewport #4876

Merged
merged 8 commits into from
Oct 20, 2020
Merged
8 changes: 4 additions & 4 deletions frontend/javascripts/libs/trackball_controls.js
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ function TrackballControls(object, domElement, target, updateCallback) {
}
};

this.update = (externalUpdate = false) => {
this.update = (externalUpdate = false, userTriggered = false) => {
_eye.subVectors(_this.object.position, _this.lastTarget);

if (!_this.noRotate) {
Expand Down Expand Up @@ -298,7 +298,7 @@ function TrackballControls(object, domElement, target, updateCallback) {

_this.lastTarget = _this.target.clone();
if (!externalUpdate) {
_this.updateCallback();
_this.updateCallback(userTriggered);
}
};

Expand Down Expand Up @@ -383,7 +383,7 @@ function TrackballControls(object, domElement, target, updateCallback) {
} else if (_state === STATE.PAN && !_this.noPan) {
_this.getMouseOnScreen(event.pageX, event.pageY, _panEnd);
}
_this.update();
_this.update(false, true);
}

function mouseup(event) {
Expand Down Expand Up @@ -490,7 +490,7 @@ function TrackballControls(object, domElement, target, updateCallback) {
default:
_state = STATE.NONE;
}
_this.update();
_this.update(false, true);
}

function touchend(event) {
Expand Down
6 changes: 3 additions & 3 deletions frontend/javascripts/oxalis/controller/camera_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
getPosition,
} from "oxalis/model/accessors/flycam_accessor";
import { listenToStoreProperty } from "oxalis/model/helpers/listener_helpers";
import { setTDCameraAction } from "oxalis/model/actions/view_mode_actions";
import { setTDCameraWithoutTimeTrackingAction } from "oxalis/model/actions/view_mode_actions";
import { voxelToNm, getBaseVoxel } from "oxalis/model/scaleinfo";
import Store, { type CameraData } from "oxalis/store";
import api from "oxalis/api/internal_api";
Expand Down Expand Up @@ -94,7 +94,7 @@ class CameraController extends React.PureComponent<Props> {
}

Store.dispatch(
setTDCameraAction({
setTDCameraWithoutTimeTrackingAction({
near: 0,
far,
}),
Expand Down Expand Up @@ -324,7 +324,7 @@ export function rotate3DViewTo(id: OrthoView, animate: boolean = true): void {
);

Store.dispatch(
setTDCameraAction({
setTDCameraWithoutTimeTrackingAction({
position: newPosition,
up: tweened.up,
left,
Expand Down
21 changes: 15 additions & 6 deletions frontend/javascripts/oxalis/controller/td_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ import { setPositionAction } from "oxalis/model/actions/flycam_actions";
import {
setViewportAction,
setTDCameraAction,
setTDCameraWithoutTimeTrackingAction,
zoomTDViewAction,
moveTDViewXAction,
moveTDViewYAction,
moveTDViewByVectorAction,
moveTDViewByVectorWithoutTimeTrackingAction,
} from "oxalis/model/actions/view_mode_actions";
import { getActiveNode } from "oxalis/model/accessors/skeletontracing_accessor";
import { voxelToNm } from "oxalis/model/scaleinfo";
Expand Down Expand Up @@ -124,10 +125,18 @@ class TDController extends React.PureComponent<Props> {
initTrackballControls(view: HTMLElement): 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 = new TrackballControls(
tdCamera,
view,
new THREE.Vector3(...pos),
(userTriggered: boolean = true) => {
const setCameraAction = userTriggered
? setTDCameraAction
: setTDCameraWithoutTimeTrackingAction;
// write threeJS camera into store
Store.dispatch(setCameraAction(threeCameraToCameraData(tdCamera)));
},
);

this.controls.noZoom = true;
this.controls.noPan = true;
Expand Down Expand Up @@ -225,7 +234,7 @@ class TDController extends React.PureComponent<Props> {

nmVector.applyEuler(rotation);

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

zoomTDView(value: number, zoomToMouse: boolean = true): void {
Expand Down
44 changes: 44 additions & 0 deletions frontend/javascripts/oxalis/model/actions/view_mode_actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,23 @@ type MoveTDViewByVectorAction = {
y: number,
};

// These two actions are used instead of their functionally identical counterparts
// (without the `_WITHOUT_TIME_TRACKING` suffix)
// when dispatching these actions should not trigger the save queue diffing.
// Therefore, the save queue will not become dirty and no time is tracked by the backend.
// The actions are used by initialization code and by the `setTargetAndFixPosition`
// workaround in the td_controller.js.
type SetTDCameraWithoutTimeTrackingAction = {
type: "SET_TD_CAMERA_WITHOUT_TIME_TRACKING",
cameraData: PartialCameraData,
};

type MoveTDViewByVectorWithoutTimeTrackingAction = {
type: "MOVE_TD_VIEW_BY_VECTOR_WITHOUT_TIME_TRACKING",
x: number,
y: number,
};

type SetInputCatcherRect = {
type: "SET_INPUT_CATCHER_RECT",
viewport: Viewport,
Expand All @@ -60,6 +77,14 @@ export const setTDCameraAction = (cameraData: PartialCameraData): SetTDCameraAct
cameraData,
});

// See the explanation further up for when to use this action instead of the setTDCameraAction
export const setTDCameraWithoutTimeTrackingAction = (
cameraData: PartialCameraData,
): SetTDCameraWithoutTimeTrackingAction => ({
type: "SET_TD_CAMERA_WITHOUT_TIME_TRACKING",
cameraData,
});

export const centerTDViewAction = (): CenterTDViewAction => ({
type: "CENTER_TD_VIEW",
});
Expand All @@ -83,6 +108,16 @@ export const moveTDViewByVectorAction = (x: number, y: number): MoveTDViewByVect
y,
});

// See the explanation further up for when to use this action instead of the moveTDViewByVectorAction
export const moveTDViewByVectorWithoutTimeTrackingAction = (
x: number,
y: number,
): MoveTDViewByVectorWithoutTimeTrackingAction => ({
type: "MOVE_TD_VIEW_BY_VECTOR_WITHOUT_TIME_TRACKING",
x,
y,
});

export const moveTDViewXAction = (x: number): MoveTDViewByVectorAction => {
const state = Store.getState();
return moveTDViewByVectorAction((x * getTDViewportSize(state)[0]) / constants.VIEWPORT_WIDTH, 0);
Expand All @@ -107,10 +142,19 @@ export const setInputCatcherRects = (viewportRects: ViewportRects): SetInputCatc
export type ViewModeAction =
| SetViewportAction
| SetTDCameraAction
| SetTDCameraWithoutTimeTrackingAction
| CenterTDViewAction
| ZoomTDViewAction
| MoveTDViewByVectorAction
| MoveTDViewByVectorWithoutTimeTrackingAction
| SetInputCatcherRect
| SetInputCatcherRects;

export const ViewModeSaveRelevantActions = [
"SET_TD_CAMERA",
"CENTER_TD_VIEW",
"ZOOM_TD_VIEW",
"MOVE_TD_VIEW_BY_VECTOR",
];

export default {};
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ function removeAllButLastUpdateTracingAction(updateActionsBatches: Array<SaveQue
return _.without(updateActionsBatches, ...updateTracingOnlyBatches.slice(0, -1));
}

function removeAllButLastUpdateTdCameraAction(updateActionsBatches: Array<SaveQueueEntry>) {
// This part of the code removes all entries from the save queue that consist only of
// one updateTdCamera update action, except for the last one
const updateTracingOnlyBatches = updateActionsBatches.filter(
batch => batch.actions.length === 1 && batch.actions[0].name === "updateTdCamera",
);
return _.without(updateActionsBatches, ...updateTracingOnlyBatches.slice(0, -1));
}

function removeSubsequentUpdateTreeActions(updateActionsBatches: Array<SaveQueueEntry>) {
const obsoleteUpdateActions = [];
// If two updateTree update actions for the same treeId follow one another, the first one is obsolete
Expand All @@ -32,6 +41,25 @@ function removeSubsequentUpdateTreeActions(updateActionsBatches: Array<SaveQueue
return _.without(updateActionsBatches, ...obsoleteUpdateActions);
}

function removeSubsequentUpdateNodeActions(updateActionsBatches: Array<SaveQueueEntry>) {
const obsoleteUpdateActions = [];
// If two updateNode update actions for the same nodeId follow one another, the first one is obsolete
for (let i = 0; i < updateActionsBatches.length - 1; i++) {
const actions1 = updateActionsBatches[i].actions;
const actions2 = updateActionsBatches[i + 1].actions;
if (
actions1.length === 1 &&
actions1[0].name === "updateNode" &&
actions2.length === 1 &&
actions2[0].name === "updateNode" &&
actions1[0].value.id === actions2[0].value.id
) {
obsoleteUpdateActions.push(updateActionsBatches[i]);
}
}
return _.without(updateActionsBatches, ...obsoleteUpdateActions);
}

export default function compactSaveQueue(
updateActionsBatches: Array<SaveQueueEntry>,
): Array<SaveQueueEntry> {
Expand All @@ -40,5 +68,9 @@ export default function compactSaveQueue(
updateActionsBatch => updateActionsBatch.actions.length > 0,
);

return removeSubsequentUpdateTreeActions(removeAllButLastUpdateTracingAction(result));
return removeSubsequentUpdateTreeActions(
removeSubsequentUpdateNodeActions(
removeAllButLastUpdateTdCameraAction(removeAllButLastUpdateTracingAction(result)),
),
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ function ViewModeReducer(state: OxalisState, action: Action): OxalisState {
},
});
}
case "SET_TD_CAMERA_WITHOUT_TIME_TRACKING":
case "SET_TD_CAMERA": {
return setTDCameraReducer(state, action.cameraData);
}
Expand All @@ -35,6 +36,7 @@ function ViewModeReducer(state: OxalisState, action: Action): OxalisState {
action.curHeight,
);
}
case "MOVE_TD_VIEW_BY_VECTOR_WITHOUT_TIME_TRACKING":
case "MOVE_TD_VIEW_BY_VECTOR": {
return moveTDViewByVectorReducer(state, action.x, action.y);
}
Expand Down
38 changes: 33 additions & 5 deletions frontend/javascripts/oxalis/model/sagas/save_saga.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import {
UNDO_HISTORY_SIZE,
maximumActionCountPerSave,
} from "oxalis/model/sagas/save_saga_constants";
import type { Tracing, SkeletonTracing, Flycam, SaveQueueEntry } from "oxalis/store";
import { type UpdateAction } from "oxalis/model/sagas/update_actions";
import type { Tracing, SkeletonTracing, Flycam, SaveQueueEntry, CameraData } from "oxalis/store";
import { type UpdateAction, updateTdCamera } from "oxalis/model/sagas/update_actions";
import {
VolumeTracingSaveRelevantActions,
type AddBucketToUndoAction,
Expand All @@ -41,6 +41,7 @@ import {
setTracingAction,
centerActiveNodeAction,
} from "oxalis/model/actions/skeletontracing_actions";
import { ViewModeSaveRelevantActions } from "oxalis/model/actions/view_mode_actions";
import type { Action } from "oxalis/model/actions/actions";
import { diffSkeletonTracing } from "oxalis/model/sagas/skeletontracing_saga";
import { diffVolumeTracing } from "oxalis/model/sagas/volumetracing_saga";
Expand Down Expand Up @@ -460,6 +461,8 @@ export function performDiffTracing(
tracing: Tracing,
prevFlycam: Flycam,
flycam: Flycam,
prevTdCamera: CameraData,
tdCamera: CameraData,
): Array<UpdateAction> {
let actions = [];
if (tracingType === "skeleton" && tracing.skeleton != null && prevTracing.skeleton != null) {
Expand All @@ -474,6 +477,10 @@ export function performDiffTracing(
);
}

if (prevTdCamera !== tdCamera) {
actions = actions.concat(updateTdCamera());
}

return actions;
}

Expand All @@ -488,6 +495,7 @@ export function* saveTracingTypeAsync(tracingType: "skeleton" | "volume"): Saga<

let prevTracing = yield* select(state => state.tracing);
let prevFlycam = yield* select(state => state.flycam);
let prevTdCamera = yield* select(state => state.viewModeData.plane.tdCamera);

yield* take("WK_READY");
const initialAllowUpdate = yield* select(
Expand All @@ -500,9 +508,18 @@ export function* saveTracingTypeAsync(tracingType: "skeleton" | "volume"): Saga<

while (true) {
if (tracingType === "skeleton") {
yield* take([...SkeletonTracingSaveRelevantActions, ...FlycamActions, "SET_TRACING"]);
yield* take([
...SkeletonTracingSaveRelevantActions,
...FlycamActions,
...ViewModeSaveRelevantActions,
"SET_TRACING",
]);
} else {
yield* take([...VolumeTracingSaveRelevantActions, ...FlycamActions]);
yield* take([
...VolumeTracingSaveRelevantActions,
...FlycamActions,
...ViewModeSaveRelevantActions,
]);
}
// The allowUpdate setting could have changed in the meantime
const allowUpdate = yield* select(
Expand All @@ -515,10 +532,20 @@ export function* saveTracingTypeAsync(tracingType: "skeleton" | "volume"): Saga<

const tracing = yield* select(state => state.tracing);
const flycam = yield* select(state => state.flycam);
const tdCamera = yield* select(state => state.viewModeData.plane.tdCamera);
const items = compactUpdateActions(
// $FlowFixMe[incompatible-call] Should be resolved when we improve the typing of sagas in general
Array.from(
yield* call(performDiffTracing, tracingType, prevTracing, tracing, prevFlycam, flycam),
yield* call(
performDiffTracing,
tracingType,
prevTracing,
tracing,
prevFlycam,
flycam,
prevTdCamera,
tdCamera,
),
),
tracing,
);
Expand All @@ -527,5 +554,6 @@ export function* saveTracingTypeAsync(tracingType: "skeleton" | "volume"): Saga<
}
prevTracing = tracing;
prevFlycam = flycam;
prevTdCamera = tdCamera;
}
}
Loading