[vtk.js] trackball/default interaction style with pan/zoom on other mouse buttons

Is there an easy way to enable pan with right-click on the default trackball interactor style? Currently, if I want to add pan with the right mouse button and zoom with the middle mouse button (the typical defaults for VTK applications), I define a new vtkInteractorStyleManipulator and set up those events plus all the defaults that the trackball interactor style handles.

For example:

import vtkInteractorStyleManipulator from 'vtk.js/Sources/Interaction/Style/InteractorStyleManipulator';
import vtkMouseCameraTrackballRotateManipulator from 'vtk.js/Sources/Interaction/Manipulators/MouseCameraTrackballRotateManipulator';
import vtkMouseCameraTrackballPanManipulator from 'vtk.js/Sources/Interaction/Manipulators/MouseCameraTrackballPanManipulator';
import vtkMouseCameraTrackballZoomManipulator from 'vtk.js/Sources/Interaction/Manipulators/MouseCameraTrackballZoomManipulator';

...


function vtkDefaultCameraInteractor () {
  const interactorStyle = vtkInteractorStyleManipulator.newInstance();

  const rotateManipulator = vtkMouseCameraTrackballRotateManipulator.newInstance();
  rotateManipulator.setButton(1); // Left Button
  interactorStyle.addMouseManipulator(rotateManipulator);

  const zoomManipulatorMiddleButton = vtkMouseCameraTrackballZoomManipulator.newInstance();
  zoomManipulatorMiddleButton.setButton(2); // Middle Button
  interactorStyle.addMouseManipulator(zoomManipulatorMiddleButton);

  const zoomManipulatorScroll = vtkMouseCameraTrackballZoomManipulator.newInstance();
  zoomManipulatorScroll.setDragEnabled(false);
  zoomManipulatorScroll.setScrollEnabled(true);
  interactorStyle.addMouseManipulator(zoomManipulatorScroll);

  const zoomManipulatorLeft = vtkMouseCameraTrackballZoomManipulator.newInstance();
  zoomManipulatorLeft.setButton(1); // Left Button
  zoomManipulatorLeft.setControl(true);
  interactorStyle.addMouseManipulator(zoomManipulatorLeft);

  const panManipulatorRightMouse = vtkMouseCameraTrackballPanManipulator.newInstance();
  panManipulatorRightMouse.setButton(3); // Right button
  interactorStyle.addMouseManipulator(panManipulatorRightMouse);

  const panManipulatorLeftMouse = vtkMouseCameraTrackballPanManipulator.newInstance();
  panManipulatorLeftMouse.setButton(1); // Left button
  panManipulatorLeftMouse.setShift(true);
  interactorStyle.addMouseManipulator(panManipulatorLeftMouse);

  return interactorStyle;
}

is there an easier way to do this where I can quickly enable those additional manipulators on the default trackball interactor style?

itk-vtk-viewer has an interesting approach where they use a list of objects defining these options here, but I cannot traceback where that is used to set up the camera… any tips?

This is using the Manipulators which is bundled in the View Proxy.

Ah, thanks for pointer. Since I am not using a View Proxy in my app, I can use the vtkInteractorStyleManipulator with the helpers in InteractionPresets to do this a bit easier:

import vtkInteractorStyleManipulator from 'vtk.js/Sources/Interaction/Style/InteractorStyleManipulator';
import InteractionPresets from 'vtk.js/Sources/Interaction/Style/InteractorStyleManipulator/Presets';

...

const interactorStyle = vtkInteractorStyleManipulator.newInstance();
fullScreenRenderer.getInteractor().setInteractorStyle(interactorStyle);
InteractionPresets.applyPreset('3D', interactorStyle);

But since I want the right button to pan, I have to modifyy this to use other definitions than the presets in InteractionPresets:

import vtkInteractorStyleManipulator from 'vtk.js/Sources/Interaction/Style/InteractorStyleManipulator';
import InteractionPresets from 'vtk.js/Sources/Interaction/Style/InteractorStyleManipulator/Presets';

...

const interactorStyleDefinitions = [
  { type: 'pan', options: { button: 3 } }, // Pan on Right button drag
  { type: 'pan', options: { button: 1, shift: true } }, // Pan on Shift + Left button drag
  { type: 'zoom', options: { button: 1, control: true } }, // Zoom on Ctrl + Left button drag
  { type: 'zoom', options: { dragEnabled: false, scrollEnabled: true } }, // Zoom on scroll
  { type: 'rotate', options: { button: 1 } } // Rotate on Left button drag
];

const interactorStyle = vtkInteractorStyleManipulator.newInstance();
fullScreenRenderer.getInteractor().setInteractorStyle(interactorStyle);
InteractionPresets.applyDefinitions(interactorStyleDefinitions, interactorStyle);

(figured I share in case someone else is trying to figure out how to more easily customize the interactor style)

4 Likes

Hi Bane and Sebastian, very useful discussion!
I have already changed some mouse and key combinations successfully. However I want to have more complex interaction. In c++ I am able to really control and create my interactors, but in vtk js it’s been a challange.

I am trying to do something similar to what Bane has, while mixing up with this example: https://kitware.github.io/vtk-js/examples/InteractorStyleMPRSlice.html

I am trying to add the part
interactorStyle.onModified(updateUI);

but nothing is happening. Am I missing some options in the interactorStyleDefinitions ? or in the interactorStyle? I would like to be able to “parse” the mouse clicks as I do in c++.

Thanks!

You can make your own style if need be that way you could track mouseDown/Up/Move and so on.

For the modified, I would not expect the Style to be modified and therefore your callback should not be called. Could you attach the onModified on the camera? Or maybe there is another even type that is more inlined with what you want to listen to?

Could you explain when you want your callback to be triggered?

Hi Sebastien, thank you for your quick reply.

I am having a 2D rendering of slices with several actors. By using this, the window/level mouse interaction stops working on the CT - I assume it is because of the internal picker.
So I want to catch the mouse / keyboards interactions and set my own functions to control the scene and update the UI like in the example from MPRSlice
So from that example I see that the UI is updated with mouse interaction. I assume it comes from the
onModified.

My original interactor was:

const iStyle = vtkInteractorStyleImage.newInstance();
iStyle.setInteractionMode('IMAGE_SLICING');
renderWindow.getInteractor().setInteractorStyle(iStyle);

You can create your own style which will give you everything that you want to catch and then, you can forward those calls to a vtkInteractorStyleImage if you extend it when you want to.

Basically, you want to create your own style.

yes, that would be ideal. But I dont know how to do it in JS. And I havent found an example doing so.
Do you have any resource around explaining how to do it?
Would be much appreciated!
Best regards

What you want to look at is probably the Manipulator style as it only forwarding those events to various instances of manipulators. And creating a style is just about exposing methods for the event that you want to handle and reacting to those by changing camera or doing something else.
The list of events you can monitor are available here.

So you just need to create methods with names like handle${EventName} in your style class.

1 Like

Update to my original solution: I am finding that the camera rotate option is changing the focal point of the camera. For instance, with the interaction style I shared above, it does this:

(note the change in focal point after a “rotate”)

With the default camera interaction, this does not happen:

is a “rotate” not actually what I want?


Note that if the mesh is centered around the origin that this isn’t all that noticeable… hence why it took me a while to realize this. Any tips, @Sebastien_Jourdain?

Hm. I just noticed that the vtkInteractorStyleManipulator class has a centerOfRotation property (the default interactor style does not have this) that is always set to the origin. Setting this to the center of the data in the scene resolves the issue, but is there a way to do that automatically on perhaps the renderer.resetCamera() call?

My solution: create a wrapper around resetCamera that is returned when creating the interaction style:

import vtkInteractorStyleManipulator from 'vtk.js/Sources/Interaction/Style/InteractorStyleManipulator';
import InteractionPresets from 'vtk.js/Sources/Interaction/Style/InteractorStyleManipulator/Presets';

function getCenterOfScene (renderer) {
  const bounds = renderer.computeVisiblePropBounds();
  const center = [0, 0, 0];

  center[0] = (bounds[0] + bounds[1]) / 2.0;
  center[1] = (bounds[2] + bounds[3]) / 2.0;
  center[2] = (bounds[4] + bounds[5]) / 2.0;

  return center;
}

function useVtkInteractorStyle (fullScreenRenderer) {
  const interactorStyleDefinitions = [
    { type: 'pan', options: { button: 2 } }, // Pan on middle button drag
    { type: 'pan', options: { button: 1, shift: true } }, // Pan on Shift + Left button drag
    { type: 'zoom', options: { button: 3 } }, // Zoom on right button drag
    { type: 'zoom', options: { button: 1, control: true } }, // Zoom on Ctrl + Left button drag
    { type: 'zoom', options: { dragEnabled: false, scrollEnabled: true } }, // Zoom on scroll
    { type: 'rotate', options: { button: 1 } } // Rotate on Left button drag
  ];

  const interactorStyle = vtkInteractorStyleManipulator.newInstance();
  fullScreenRenderer.getInteractor().setInteractorStyle(interactorStyle);
  InteractionPresets.applyDefinitions(interactorStyleDefinitions, interactorStyle);

  function resetCamera () {
    const center = getCenterOfScene(fullScreenRenderer.getRenderer());
    interactorStyle.setCenterOfRotation(center);
    return fullScreenRenderer.getRenderer().resetCamera();
  }

  return [interactorStyle, resetCamera];
}

export default { useVtkInteractorStyle, getCenterOfScene };

then in use:

import interactor from './interactor.js';

const fullScreenRenderer = vtkFullScreenRenderWindow.newInstance(
  { background: [1, 1, 1] }
);
const renderer = fullScreenRenderer.getRenderer();
const renderWindow = fullScreenRenderer.getRenderWindow();

const [style, resetCamera] = interactor.useVtkInteractorStyle(fullScreenRenderer);

...

resetCamera();

I usually do a resetCamera() and then use the camera focal point as new center of rotation. But otherwise same logic.

1 Like

When I try to add a range configuration to interactorStyleDefinitions nothing happens.

  const interactorStyleDefinitions = [
    {
      type: 'range',
      options: { button: 1 },
    },
  ];

All the other configurations work correctly, do you have any idea? I’m rendering a MPR DICOM image.

You need to attach some callback (I think) to the Range manipulator.

Thanks for the answer! Do you have some example or doc to link to? I’m kind of lost :frowning:

I don’t see any callback there?

rangeManipulator.setVerticalListener(wMin, wMax, 1, wGet, wSet);
rangeManipulator.setHorizontalListener(lMin, lMax, 1, lGet, lSet);
rangeManipulator.setScrollListener(kMin, kMax, 1, kGet, kSet);