Skip to content

Commit

Permalink
Merge branch 'feature/continous-camera-movement-tracking' into featur…
Browse files Browse the repository at this point in the history
…e/picking-in-non-max-vr
  • Loading branch information
seankmartin committed Jun 21, 2024
2 parents 3b1b05b + 6c639b9 commit e95e7c8
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 28 deletions.
23 changes: 23 additions & 0 deletions src/display_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
* limitations under the License.
*/

import { debounce } from "lodash-es";

import type { FrameNumberCounter } from "#src/chunk_manager/frontend.js";
import { TrackableValue } from "#src/trackable_value.js";
import { animationFrameDebounce } from "#src/util/animation_frame_debounce.js";
Expand All @@ -26,6 +28,8 @@ import type { WatchableVisibilityPriority } from "#src/visibility_priority/front
import type { GL } from "#src/webgl/context.js";
import { initializeWebGL } from "#src/webgl/context.js";

const DELAY_AFTER_CONTINUOUS_CAMERA_MOTION_MS = 300;

export class RenderViewport {
// Width of visible portion of panel in canvas pixels.
width = 0;
Expand Down Expand Up @@ -390,13 +394,16 @@ export class DisplayContext extends RefCounted implements FrameNumberCounter {
gl: GL;
updateStarted = new NullarySignal();
updateFinished = new NullarySignal();
continuousCameraMotionFinished = new NullarySignal();
changed = this.updateFinished;
panels = new Set<RenderedPanel>();
canvasRect: DOMRect | undefined;
rootRect: DOMRect | undefined;
resizeGeneration = 0;
boundsGeneration = -1;

private continuousCameraMotionInProgress = false;

// Panels ordered by `drawOrder`. If length is 0, needs to be recomputed.
private orderedPanels: RenderedPanel[] = [];

Expand Down Expand Up @@ -447,6 +454,22 @@ export class DisplayContext extends RefCounted implements FrameNumberCounter {

private resizeObserver = new ResizeObserver(this.resizeCallback);

private debouncedEndContinuousCameraMotion = this.registerCancellable(
debounce(() => {
this.continuousCameraMotionInProgress = false;
this.continuousCameraMotionFinished.dispatch();
}, DELAY_AFTER_CONTINUOUS_CAMERA_MOTION_MS),
);

flagContinuousCameraMotion() {
this.continuousCameraMotionInProgress = true;
this.debouncedEndContinuousCameraMotion();
}

get isContinuousCameraMotionInProgress() {
return this.continuousCameraMotionInProgress;
}

constructor(public container: HTMLElement) {
super();
const { canvas, resizeObserver } = this;
Expand Down
38 changes: 14 additions & 24 deletions src/perspective_view/panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,6 @@ import { MultipleScaleBarTextures } from "#src/widget/scale_bar.js";
import type { RPC } from "#src/worker_rpc.js";
import { SharedObject } from "#src/worker_rpc.js";

const REDRAW_DELAY_AFTER_CAMERA_MOVE = 300;

export interface PerspectiveViewerState extends RenderedDataViewerState {
wireFrame: WatchableValueInterface<boolean>;
orthographicProjection: TrackableBoolean;
Expand Down Expand Up @@ -253,7 +251,6 @@ export class PerspectivePanel extends RenderedDataPanel {
protected visibleLayerTracker: Owned<
VisibleRenderLayerTracker<PerspectivePanel, PerspectiveViewRenderLayer>
>;
private redrawAfterMoveTimeOutId: number = -1;
private hasVolumeRendering = false;

get rpc() {
Expand All @@ -265,9 +262,6 @@ export class PerspectivePanel extends RenderedDataPanel {
get displayDimensionRenderInfo() {
return this.navigationState.displayDimensionRenderInfo;
}
get isCameraMoving() {
return this.redrawAfterMoveTimeOutId !== -1;
}

/**
* If boolean value is true, sliceView is shown unconditionally, regardless of the value of
Expand Down Expand Up @@ -425,19 +419,8 @@ export class PerspectivePanel extends RenderedDataPanel {
);

this.registerDisposer(
this.viewer.navigationState.changed.add(() => {
// Don't mark camera moving on picking requests
// Also, at the moment this is only used for the volume rendering layer
if (this.isMovingToMousePosition || !this.hasVolumeRendering) {
return;
}
if (this.redrawAfterMoveTimeOutId !== -1) {
window.clearTimeout(this.redrawAfterMoveTimeOutId);
}
this.redrawAfterMoveTimeOutId = window.setTimeout(() => {
this.redrawAfterMoveTimeOutId = -1;
this.context.scheduleRedraw();
}, REDRAW_DELAY_AFTER_CAMERA_MOVE);
this.context.continuousCameraMotionFinished.add(() => {
if (this.hasVolumeRendering) this.scheduleRedraw();
}),
);

Expand All @@ -446,6 +429,7 @@ export class PerspectivePanel extends RenderedDataPanel {
"rotate-via-mouse-drag",
(e: ActionEvent<MouseEvent>) => {
startRelativeMouseDrag(e.detail, (_event, deltaX, deltaY) => {
this.context.flagContinuousCameraMotion();
this.navigationState.pose.rotateRelative(
kAxes[1],
((deltaX / 4.0) * Math.PI) / 180.0,
Expand All @@ -462,6 +446,7 @@ export class PerspectivePanel extends RenderedDataPanel {
element,
"rotate-in-plane-via-touchrotate",
(e: ActionEvent<TouchRotateInfo>) => {
this.context.flagContinuousCameraMotion();
const { detail } = e;
this.navigationState.pose.rotateRelative(
kAxes[2],
Expand All @@ -474,6 +459,7 @@ export class PerspectivePanel extends RenderedDataPanel {
element,
"rotate-out-of-plane-via-touchtranslate",
(e: ActionEvent<TouchTranslateInfo>) => {
this.context.flagContinuousCameraMotion();
const { detail } = e;
this.navigationState.pose.rotateRelative(
kAxes[1],
Expand Down Expand Up @@ -927,7 +913,8 @@ export class PerspectivePanel extends RenderedDataPanel {
bindFramebuffer,
frameNumber: this.context.frameNumber,
sliceViewsPresent: this.sliceViews.size > 0,
cameraMovementInProgress: this.isCameraMoving,
isContinuousCameraMotionInProgress:
this.context.isContinuousCameraMotionInProgress,
};

mat4.copy(
Expand All @@ -938,10 +925,11 @@ export class PerspectivePanel extends RenderedDataPanel {
const { visibleLayers } = this.visibleLayerTracker;

let hasTransparent = false;
this.hasVolumeRendering = false;
// By default, volume rendering layers are not pickable when the camera is moving.
let hasVolumeRenderingPick = !this.isCameraMoving;
let hasVolumeRenderingPick =
!this.context.isContinuousCameraMotionInProgress;
let hasAnnotation = false;
let hasVolumeRendering = false;

// Draw fully-opaque layers first.
for (const [renderLayer, attachment] of visibleLayers) {
Expand All @@ -954,13 +942,14 @@ export class PerspectivePanel extends RenderedDataPanel {
} else {
hasTransparent = true;
if (renderLayer.isVolumeRendering) {
this.hasVolumeRendering = true;
hasVolumeRendering = true;
hasVolumeRenderingPick =
hasVolumeRenderingPick ||
isProjectionLayer(renderLayer as VolumeRenderingRenderLayer);
}
}
}
this.hasVolumeRendering = hasVolumeRendering;
this.drawSliceViews(renderContext);

if (hasAnnotation) {
Expand Down Expand Up @@ -1063,7 +1052,8 @@ export class PerspectivePanel extends RenderedDataPanel {
renderLayer as VolumeRenderingRenderLayer,
);
const needsSecondPickingPass =
!isVolumeProjectionLayer && !this.isCameraMoving;
!isVolumeProjectionLayer &&
!this.context.isContinuousCameraMotionInProgress;

// Two cases for volume rendering layers

Expand Down
2 changes: 1 addition & 1 deletion src/perspective_view/render_layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export interface PerspectiveViewRenderContext
/**
* Specifies if the camera is moving
*/
cameraMovementInProgress: boolean;
isContinuousCameraMotionInProgress: boolean;

/**
* Specifices how to bind the max projection buffer
Expand Down
13 changes: 13 additions & 0 deletions src/rendered_data_panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -506,18 +506,22 @@ export abstract class RenderedDataPanel extends RenderedPanel {
});

registerActionListener(element, "zoom-in", () => {
this.context.flagContinuousCameraMotion();
this.navigationState.zoomBy(0.5);
});

registerActionListener(element, "zoom-out", () => {
this.context.flagContinuousCameraMotion();
this.navigationState.zoomBy(2.0);
});

registerActionListener(element, "depth-range-decrease", () => {
this.context.flagContinuousCameraMotion();
this.navigationState.depthRange.value *= 0.5;
});

registerActionListener(element, "depth-range-increase", () => {
this.context.flagContinuousCameraMotion();
this.navigationState.depthRange.value *= 2;
});

Expand All @@ -529,11 +533,13 @@ export abstract class RenderedDataPanel extends RenderedPanel {
element,
`rotate-relative-${axisName}${signStr}`,
() => {
this.context.flagContinuousCameraMotion();
this.navigationState.pose.rotateRelative(kAxes[axis], sign * 0.1);
},
);
const tempOffset = vec3.create();
registerActionListener(element, `${axisName}${signStr}`, () => {
this.context.flagContinuousCameraMotion();
const { navigationState } = this;
const offset = tempOffset;
offset[0] = 0;
Expand All @@ -549,6 +555,7 @@ export abstract class RenderedDataPanel extends RenderedPanel {
element,
"zoom-via-wheel",
(event: ActionEvent<WheelEvent>) => {
this.context.flagContinuousCameraMotion();
const e = event.detail;
this.onMousemove(e, false);
this.zoomByMouse(getWheelZoomAmount(e));
Expand All @@ -559,6 +566,7 @@ export abstract class RenderedDataPanel extends RenderedPanel {
element,
"adjust-depth-range-via-wheel",
(event: ActionEvent<WheelEvent>) => {
this.context.flagContinuousCameraMotion();
const e = event.detail;
this.navigationState.depthRange.value *= getWheelZoomAmount(e);
},
Expand All @@ -569,6 +577,7 @@ export abstract class RenderedDataPanel extends RenderedPanel {
"translate-via-mouse-drag",
(e: ActionEvent<MouseEvent>) => {
startRelativeMouseDrag(e.detail, (_event, deltaX, deltaY) => {
this.context.flagContinuousCameraMotion();
this.translateByViewportPixels(deltaX, deltaY);
});
},
Expand All @@ -578,6 +587,7 @@ export abstract class RenderedDataPanel extends RenderedPanel {
element,
"translate-in-plane-via-touchtranslate",
(e: ActionEvent<TouchTranslateInfo>) => {
this.context.flagContinuousCameraMotion();
const { detail } = e;
this.translateByViewportPixels(detail.deltaX, detail.deltaY);
},
Expand All @@ -587,6 +597,7 @@ export abstract class RenderedDataPanel extends RenderedPanel {
element,
"translate-z-via-touchtranslate",
(e: ActionEvent<TouchTranslateInfo>) => {
this.context.flagContinuousCameraMotion();
const { detail } = e;
const { navigationState } = this;
const offset = tempVec3;
Expand All @@ -602,6 +613,7 @@ export abstract class RenderedDataPanel extends RenderedPanel {
element,
`z+${amount}-via-wheel`,
(event: ActionEvent<WheelEvent>) => {
this.context.flagContinuousCameraMotion();
const e = event.detail;
const { navigationState } = this;
const offset = tempVec3;
Expand Down Expand Up @@ -737,6 +749,7 @@ export abstract class RenderedDataPanel extends RenderedPanel {
element,
"zoom-via-touchpinch",
(e: ActionEvent<TouchPinchInfo>) => {
this.context.flagContinuousCameraMotion();
const { detail } = e;
this.handleMouseMove(detail.centerX, detail.centerY);
const ratio = detail.prevDistance / detail.distance;
Expand Down
2 changes: 2 additions & 0 deletions src/sliceview/panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ export class SliceViewPanel extends RenderedDataPanel {
if (mouseState.updateUnconditionally()) {
const initialPosition = Float32Array.from(mouseState.position);
startRelativeMouseDrag(e.detail, (_event, deltaX, deltaY) => {
this.context.flagContinuousCameraMotion();
const { pose } = this.navigationState;
const xAxis = vec3.transformQuat(
tempVec3,
Expand Down Expand Up @@ -210,6 +211,7 @@ export class SliceViewPanel extends RenderedDataPanel {
const { mouseState } = this.viewer;
this.handleMouseMove(detail.centerX, detail.centerY);
if (mouseState.updateUnconditionally()) {
this.context.flagContinuousCameraMotion();
this.navigationState.pose.rotateAbsolute(
this.sliceView.projectionParameters.value
.viewportNormalInCanonicalCoordinates,
Expand Down
6 changes: 3 additions & 3 deletions src/volume_rendering/volume_render_layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -774,7 +774,7 @@ outputValue = vec4(1.0, 1.0, 1.0, 1.0);
const restoreDrawingBuffersAndState = () => {
const performedSecondPassForPicking =
!isProjectionMode(this.mode.value) &&
!renderContext.cameraMovementInProgress;
!renderContext.isContinuousCameraMotionInProgress;
// In this case, we need the max projection buffer
if (isProjectionMode(this.mode.value) || performedSecondPassForPicking) {
gl.depthMask(true);
Expand Down Expand Up @@ -850,10 +850,10 @@ outputValue = vec4(1.0, 1.0, 1.0, 1.0);
this.getDataHistogramCount() > 0 &&
!renderContext.wireFrame &&
!renderContext.sliceViewsPresent &&
!renderContext.cameraMovementInProgress;
!renderContext.isContinuousCameraMotionInProgress;
const needPickingPass =
!isProjectionMode(this.mode.value) &&
!renderContext.cameraMovementInProgress;
!renderContext.isContinuousCameraMotionInProgress;
const hasPicking = isProjectionMode(this.mode.value) || needPickingPass;

const pickId = hasPicking ? renderContext.pickIDs.register(this) : 0;
Expand Down

0 comments on commit e95e7c8

Please sign in to comment.