Skip to content

Commit

Permalink
fix(reslicecursorwidget): fix camera reset on rotation after a camera…
Browse files Browse the repository at this point in the history
… translation

When the user translates a camera (shift+left drag), and then rotate the reslice cursor widget in
another view, the camera was reset. This is because the focal point offsets were not "stored" after
the camera translation. The rotation was therefore resetting the camera translation. A solution
would have been to observe the interactor style each time the camera is manipulated. Instead, we
prefer to save the focal point offsets before any reslice cursor widget interaction happens, namely
when startInteraction is triggered. This makes computeFocalPointOffset parameter in the reslice
cursor widget interactionEvent obsolete. It has therefore been removed along with the associated
canUpdateFocalPoint. This is not the behavior's responsibility to tell the event consumers what they
must do.

BREAKING CHANGE: ResliceCursorWidget.interactionEvent has been modified
  • Loading branch information
finetjul committed Mar 18, 2024
1 parent 5359685 commit b50d278
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 23 deletions.
5 changes: 5 additions & 0 deletions BREAKING_CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## From 29.x to 30

- **ResliceCursorWidget.interactionEvent**: no longer pass an object of computeFocalPointOffset, canUpdateFocalPoint but simply the type of the handle that triggers the event. Those values can easily be recomputed by the consumers of the event. Regarding `computeFocalPointOffset`, it is no longer adviced to compute focal point offset for each interaction, instead observing `startInteraction()` should be considered (see ResliceCursorWidget example).
- **ResliceCursorWidget.invokeInternalInteractionEvent(methodName)**: has been removed and should be replaced by `ResliceCursorWidget.invokeInteractionEvent(methodName)`.

## From 28.x to 29

- **getOpenGLRenderWindow**: `getOpenGLRenderWindow` has been renamed to `getApiSpecificRenderWindow` in `vtkFullScreenRenderWindow`, `vtkGenericRenderWindow` and `vtkViewProxy` to support WebGL and WebGPU backend. ([#2816](https://github.com/Kitware/vtk-js/pull/2816))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import vtkAbstractWidget from "../../Core/AbstractWidget";

export default interface vtkResliceCursorWidgetDefaultInstance extends vtkAbstractWidget {
invokeInternalInteractionEvent: () => void;
}
19 changes: 3 additions & 16 deletions Sources/Widgets/Widgets3D/ResliceCursorWidget/behavior.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ export default function widgetBehavior(publicAPI, model) {
publicAPI.translateCenterOnPlaneDirection(step);
previousPosition = callData.position;

publicAPI.invokeInternalInteractionEvent();
publicAPI.invokeInteractionEvent(publicAPI.getActiveInteraction());
}
}
return macro.VOID;
Expand Down Expand Up @@ -226,7 +226,7 @@ export default function widgetBehavior(publicAPI, model) {
isScrolling = true;
publicAPI.translateCenterOnPlaneDirection(step);

publicAPI.invokeInternalInteractionEvent(
publicAPI.invokeInteractionEvent(
// Force interaction mode because mouse cursor could be above rotation handle
InteractionMethodsName.TranslateCenter
);
Expand Down Expand Up @@ -261,25 +261,12 @@ export default function widgetBehavior(publicAPI, model) {
if (model.activeState.getActive()) {
const methodName = publicAPI.getActiveInteraction();
publicAPI[methodName](callData);
publicAPI.invokeInternalInteractionEvent(methodName);
publicAPI.invokeInteractionEvent(methodName);
return macro.EVENT_ABORT;
}
return macro.VOID;
};

publicAPI.invokeInternalInteractionEvent = (
methodName = publicAPI.getActiveInteraction()
) => {
const computeFocalPointOffset =
methodName !== InteractionMethodsName.RotateLine;
const canUpdateFocalPoint =
methodName === InteractionMethodsName.RotateLine;
publicAPI.invokeInteractionEvent({
computeFocalPointOffset,
canUpdateFocalPoint,
});
};

publicAPI.startInteraction = () => {
publicAPI.invokeStartInteractionEvent();
// When interacting, plane actor and lines must be re-rendered on other views
Expand Down
37 changes: 31 additions & 6 deletions Sources/Widgets/Widgets3D/ResliceCursorWidget/example/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ import { CaptureOn } from '@kitware/vtk.js/Widgets/Core/WidgetManager/Constants'
import { vec3 } from 'gl-matrix';
import { SlabMode } from '@kitware/vtk.js/Imaging/Core/ImageReslice/Constants';

import { xyzToViewType } from '@kitware/vtk.js/Widgets/Widgets3D/ResliceCursorWidget/Constants';
import {
xyzToViewType,
InteractionMethodsName,
} from '@kitware/vtk.js/Widgets/Widgets3D/ResliceCursorWidget/Constants';
import controlPanel from './controlPanel.html';

// Force the loading of HttpDataAccessHelper to support gzip decompression
Expand All @@ -45,7 +48,10 @@ const viewColors = [
];

const viewAttributes = [];
window.va = viewAttributes;
const widget = vtkResliceCursorWidget.newInstance();
window.widget = widget;
window.splineWidget = vtkSplineWidget.newInstance();

Check failure on line 54 in Sources/Widgets/Widgets3D/ResliceCursorWidget/example/index.js

View workflow job for this annotation

GitHub Actions / ubuntu-20.04 and node 18

'vtkSplineWidget' is not defined

Check failure on line 54 in Sources/Widgets/Widgets3D/ResliceCursorWidget/example/index.js

View workflow job for this annotation

GitHub Actions / ubuntu-20.04 and node 20

'vtkSplineWidget' is not defined
const widgetState = widget.getWidgetState();
// Set size in CSS pixel space because scaleInPixels defaults to true
widgetState
Expand Down Expand Up @@ -285,7 +291,9 @@ for (let i = 0; i < 4; i++) {
[]
);
widget.setCenter(newCenter);
obj.widgetInstance.invokeInternalInteractionEvent();
obj.widgetInstance.invokeInteractionEvent(
obj.widgetInstance.getActiveInteraction()
);
viewAttributes.forEach((obj2) => {
obj2.interactor.render();
});
Expand Down Expand Up @@ -386,13 +394,30 @@ reader.setUrl(`${__BASE_PATH__}/data/volume/LIDC2.vti`).then(() => {
// Note: Need to refresh also the current view because of adding the mouse wheel
// to change slicer
.forEach((v) => {
// Store the FocalPoint offset before "interacting".
// The offset may have been changed externally when manipulating the camera
// or interactorstyle.
v.widgetInstance.onStartInteractionEvent(() => {
updateReslice({
viewType,
reslice,
actor: obj.resliceActor,
renderer: obj.renderer,
resetFocalPoint: false,
keepFocalPointPosition: false,
computeFocalPointOffset: true,
sphereSources: obj.sphereSources,
slider: obj.slider,
});
});

// Interactions in other views may change current plane
v.widgetInstance.onInteractionEvent(
// computeFocalPointOffset: Boolean which defines if the offset between focal point and
// reslice cursor display center has to be recomputed (while translation is applied)
// canUpdateFocalPoint: Boolean which defines if the focal point can be updated because
// the current interaction is a rotation
({ computeFocalPointOffset, canUpdateFocalPoint }) => {
(interactionMethodName) => {
const canUpdateFocalPoint =
interactionMethodName === InteractionMethodsName.RotateLine;
const activeViewType = widget
.getWidgetState()
.getActiveViewType();
Expand All @@ -405,7 +430,7 @@ reader.setUrl(`${__BASE_PATH__}/data/volume/LIDC2.vti`).then(() => {
renderer: obj.renderer,
resetFocalPoint: false,
keepFocalPointPosition,
computeFocalPointOffset,
computeFocalPointOffset: !keepFocalPointPosition,
sphereSources: obj.sphereSources,
slider: obj.slider,
});
Expand Down

0 comments on commit b50d278

Please sign in to comment.