From b50d27800743d1a48f4288eb37372f25ec7d6f6a Mon Sep 17 00:00:00 2001 From: Julien Finet Date: Mon, 18 Mar 2024 23:42:38 +0100 Subject: [PATCH] fix(reslicecursorwidget): fix camera reset on rotation after a camera 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 --- BREAKING_CHANGES.md | 5 +++ .../ResliceCursorWidget/behavior.d.ts | 1 - .../Widgets3D/ResliceCursorWidget/behavior.js | 19 ++-------- .../ResliceCursorWidget/example/index.js | 37 ++++++++++++++++--- 4 files changed, 39 insertions(+), 23 deletions(-) diff --git a/BREAKING_CHANGES.md b/BREAKING_CHANGES.md index fd56308b3cf..4533febc2e3 100644 --- a/BREAKING_CHANGES.md +++ b/BREAKING_CHANGES.md @@ -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)) diff --git a/Sources/Widgets/Widgets3D/ResliceCursorWidget/behavior.d.ts b/Sources/Widgets/Widgets3D/ResliceCursorWidget/behavior.d.ts index c433dc56c62..36720de7ba8 100644 --- a/Sources/Widgets/Widgets3D/ResliceCursorWidget/behavior.d.ts +++ b/Sources/Widgets/Widgets3D/ResliceCursorWidget/behavior.d.ts @@ -1,5 +1,4 @@ import vtkAbstractWidget from "../../Core/AbstractWidget"; export default interface vtkResliceCursorWidgetDefaultInstance extends vtkAbstractWidget { - invokeInternalInteractionEvent: () => void; } diff --git a/Sources/Widgets/Widgets3D/ResliceCursorWidget/behavior.js b/Sources/Widgets/Widgets3D/ResliceCursorWidget/behavior.js index 0238a912b50..19dc07a2544 100644 --- a/Sources/Widgets/Widgets3D/ResliceCursorWidget/behavior.js +++ b/Sources/Widgets/Widgets3D/ResliceCursorWidget/behavior.js @@ -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; @@ -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 ); @@ -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 diff --git a/Sources/Widgets/Widgets3D/ResliceCursorWidget/example/index.js b/Sources/Widgets/Widgets3D/ResliceCursorWidget/example/index.js index af8024cf830..03716860588 100644 --- a/Sources/Widgets/Widgets3D/ResliceCursorWidget/example/index.js +++ b/Sources/Widgets/Widgets3D/ResliceCursorWidget/example/index.js @@ -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 @@ -45,7 +48,10 @@ const viewColors = [ ]; const viewAttributes = []; +window.va = viewAttributes; const widget = vtkResliceCursorWidget.newInstance(); +window.widget = widget; +window.splineWidget = vtkSplineWidget.newInstance(); const widgetState = widget.getWidgetState(); // Set size in CSS pixel space because scaleInPixels defaults to true widgetState @@ -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(); }); @@ -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(); @@ -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, });