@acransac/vtk.js
Version:
Visualization Toolkit for the Web
459 lines (378 loc) • 13.7 kB
JavaScript
import macro from 'vtk.js/Sources/macro';
import vtkInteractorStyle from 'vtk.js/Sources/Rendering/Core/InteractorStyle';
import vtkInteractorStyleConstants from 'vtk.js/Sources/Rendering/Core/InteractorStyle/Constants';
import * as vtkMath from 'vtk.js/Sources/Common/Core/Math';
import {
Device,
Input,
} from 'vtk.js/Sources/Rendering/Core/RenderWindowInteractor/Constants';
const { States } = vtkInteractorStyleConstants;
/* eslint-disable no-lonely-if */
// ----------------------------------------------------------------------------
// vtkInteractorStyleTrackballCamera methods
// ----------------------------------------------------------------------------
function vtkInteractorStyleTrackballCamera(publicAPI, model) {
// Set our className
model.classHierarchy.push('vtkInteractorStyleTrackballCamera');
// Public API methods
publicAPI.handleMouseMove = (callData) => {
const pos = callData.position;
const renderer = callData.pokedRenderer;
switch (model.state) {
case States.IS_ROTATE:
publicAPI.handleMouseRotate(renderer, pos);
publicAPI.invokeInteractionEvent({ type: 'InteractionEvent' });
break;
case States.IS_PAN:
publicAPI.handleMousePan(renderer, pos);
publicAPI.invokeInteractionEvent({ type: 'InteractionEvent' });
break;
case States.IS_DOLLY:
publicAPI.handleMouseDolly(renderer, pos);
publicAPI.invokeInteractionEvent({ type: 'InteractionEvent' });
break;
case States.IS_SPIN:
publicAPI.handleMouseSpin(renderer, pos);
publicAPI.invokeInteractionEvent({ type: 'InteractionEvent' });
break;
default:
break;
}
model.previousPosition = pos;
};
//----------------------------------------------------------------------------
publicAPI.handleButton3D = (ed) => {
if (
ed &&
ed.pressed &&
ed.device === Device.RightController &&
ed.input === Input.TrackPad
) {
publicAPI.startCameraPose();
return;
}
if (
ed &&
!ed.pressed &&
ed.device === Device.RightController &&
ed.input === Input.TrackPad &&
model.state === States.IS_CAMERA_POSE
) {
publicAPI.endCameraPose();
// return;
}
};
publicAPI.handleMove3D = (ed) => {
switch (model.state) {
case States.IS_CAMERA_POSE:
publicAPI.updateCameraPose(ed);
break;
default:
}
};
publicAPI.updateCameraPose = (ed) => {
// move the world in the direction of the
// controller
const camera = ed.pokedRenderer.getActiveCamera();
const oldTrans = camera.getPhysicalTranslation();
// look at the y axis to determine how fast / what direction to move
const speed = ed.gamepad.axes[1];
// 0.05 meters / frame movement
const pscale = (speed * 0.05) / camera.getPhysicalScale();
// convert orientation to world coordinate direction
const dir = camera.physicalOrientationToWorldDirection(ed.orientation);
camera.setPhysicalTranslation(
oldTrans[0] + dir[0] * pscale,
oldTrans[1] + dir[1] * pscale,
oldTrans[2] + dir[2] * pscale
);
};
//----------------------------------------------------------------------------
publicAPI.handleLeftButtonPress = (callData) => {
const pos = callData.position;
model.previousPosition = pos;
if (callData.shiftKey) {
if (callData.controlKey || callData.altKey) {
publicAPI.startDolly();
} else {
publicAPI.startPan();
}
} else {
if (callData.controlKey || callData.altKey) {
publicAPI.startSpin();
} else {
publicAPI.startRotate();
}
}
};
//--------------------------------------------------------------------------
publicAPI.handleLeftButtonRelease = () => {
switch (model.state) {
case States.IS_DOLLY:
publicAPI.endDolly();
break;
case States.IS_PAN:
publicAPI.endPan();
break;
case States.IS_SPIN:
publicAPI.endSpin();
break;
case States.IS_ROTATE:
publicAPI.endRotate();
break;
default:
break;
}
};
//----------------------------------------------------------------------------
publicAPI.handleStartMouseWheel = (callData) => {
publicAPI.startDolly();
publicAPI.handleMouseWheel(callData);
};
//--------------------------------------------------------------------------
publicAPI.handleEndMouseWheel = () => {
publicAPI.endDolly();
};
//----------------------------------------------------------------------------
publicAPI.handleStartPinch = (callData) => {
model.previousScale = callData.scale;
publicAPI.startDolly();
};
//--------------------------------------------------------------------------
publicAPI.handleEndPinch = () => {
publicAPI.endDolly();
};
//----------------------------------------------------------------------------
publicAPI.handleStartRotate = (callData) => {
model.previousRotation = callData.rotation;
publicAPI.startRotate();
};
//--------------------------------------------------------------------------
publicAPI.handleEndRotate = () => {
publicAPI.endRotate();
};
//----------------------------------------------------------------------------
publicAPI.handleStartPan = (callData) => {
model.previousTranslation = callData.translation;
publicAPI.startPan();
};
//--------------------------------------------------------------------------
publicAPI.handleEndPan = () => {
publicAPI.endPan();
};
//----------------------------------------------------------------------------
publicAPI.handlePinch = (callData) => {
publicAPI.dollyByFactor(
callData.pokedRenderer,
callData.scale / model.previousScale
);
model.previousScale = callData.scale;
};
//----------------------------------------------------------------------------
publicAPI.handlePan = (callData) => {
const camera = callData.pokedRenderer.getActiveCamera();
// Calculate the focal depth since we'll be using it a lot
let viewFocus = camera.getFocalPoint();
viewFocus = publicAPI.computeWorldToDisplay(
callData.pokedRenderer,
viewFocus[0],
viewFocus[1],
viewFocus[2]
);
const focalDepth = viewFocus[2];
const trans = callData.translation;
const lastTrans = model.previousTranslation;
const newPickPoint = publicAPI.computeDisplayToWorld(
callData.pokedRenderer,
viewFocus[0] + trans[0] - lastTrans[0],
viewFocus[1] + trans[1] - lastTrans[1],
focalDepth
);
// Has to recalc old mouse point since the viewport has moved,
// so can't move it outside the loop
const oldPickPoint = publicAPI.computeDisplayToWorld(
callData.pokedRenderer,
viewFocus[0],
viewFocus[1],
focalDepth
);
// Camera motion is reversed
const motionVector = [];
motionVector[0] = oldPickPoint[0] - newPickPoint[0];
motionVector[1] = oldPickPoint[1] - newPickPoint[1];
motionVector[2] = oldPickPoint[2] - newPickPoint[2];
viewFocus = camera.getFocalPoint();
const viewPoint = camera.getPosition();
camera.setFocalPoint(
motionVector[0] + viewFocus[0],
motionVector[1] + viewFocus[1],
motionVector[2] + viewFocus[2]
);
camera.setPosition(
motionVector[0] + viewPoint[0],
motionVector[1] + viewPoint[1],
motionVector[2] + viewPoint[2]
);
if (model.interactor.getLightFollowCamera()) {
callData.pokedRenderer.updateLightsGeometryToFollowCamera();
}
camera.orthogonalizeViewUp();
model.previousTranslation = callData.translation;
};
//----------------------------------------------------------------------------
publicAPI.handleRotate = (callData) => {
const camera = callData.pokedRenderer.getActiveCamera();
camera.roll(callData.rotation - model.previousRotation);
camera.orthogonalizeViewUp();
model.previousRotation = callData.rotation;
};
//--------------------------------------------------------------------------
publicAPI.handleMouseRotate = (renderer, position) => {
const rwi = model.interactor;
const dx = position.x - model.previousPosition.x;
const dy = position.y - model.previousPosition.y;
const size = rwi.getView().getViewportSize(renderer);
let deltaElevation = -0.1;
let deltaAzimuth = -0.1;
if (size[0] && size[1]) {
deltaElevation = -20.0 / size[1];
deltaAzimuth = -20.0 / size[0];
}
const rxf = dx * deltaAzimuth * model.motionFactor;
const ryf = dy * deltaElevation * model.motionFactor;
const camera = renderer.getActiveCamera();
if (!Number.isNaN(rxf) && !Number.isNaN(ryf)) {
camera.azimuth(rxf);
camera.elevation(ryf);
camera.orthogonalizeViewUp();
}
if (model.autoAdjustCameraClippingRange) {
renderer.resetCameraClippingRange();
}
if (rwi.getLightFollowCamera()) {
renderer.updateLightsGeometryToFollowCamera();
}
};
//--------------------------------------------------------------------------
publicAPI.handleMouseSpin = (renderer, position) => {
const rwi = model.interactor;
const camera = renderer.getActiveCamera();
const center = rwi.getView().getViewportCenter(renderer);
const oldAngle = vtkMath.degreesFromRadians(
Math.atan2(
model.previousPosition.y - center[1],
model.previousPosition.x - center[0]
)
);
const newAngle =
vtkMath.degreesFromRadians(
Math.atan2(position.y - center[1], position.x - center[0])
) - oldAngle;
if (!Number.isNaN(newAngle)) {
camera.roll(newAngle);
camera.orthogonalizeViewUp();
}
};
//--------------------------------------------------------------------------
publicAPI.handleMousePan = (renderer, position) => {
const camera = renderer.getActiveCamera();
// Calculate the focal depth since we'll be using it a lot
let viewFocus = camera.getFocalPoint();
viewFocus = publicAPI.computeWorldToDisplay(
renderer,
viewFocus[0],
viewFocus[1],
viewFocus[2]
);
const focalDepth = viewFocus[2];
const newPickPoint = publicAPI.computeDisplayToWorld(
renderer,
position.x,
position.y,
focalDepth
);
// Has to recalc old mouse point since the viewport has moved,
// so can't move it outside the loop
const oldPickPoint = publicAPI.computeDisplayToWorld(
renderer,
model.previousPosition.x,
model.previousPosition.y,
focalDepth
);
// Camera motion is reversed
const motionVector = [];
motionVector[0] = oldPickPoint[0] - newPickPoint[0];
motionVector[1] = oldPickPoint[1] - newPickPoint[1];
motionVector[2] = oldPickPoint[2] - newPickPoint[2];
viewFocus = camera.getFocalPoint();
const viewPoint = camera.getPosition();
camera.setFocalPoint(
motionVector[0] + viewFocus[0],
motionVector[1] + viewFocus[1],
motionVector[2] + viewFocus[2]
);
camera.setPosition(
motionVector[0] + viewPoint[0],
motionVector[1] + viewPoint[1],
motionVector[2] + viewPoint[2]
);
if (model.interactor.getLightFollowCamera()) {
renderer.updateLightsGeometryToFollowCamera();
}
};
//----------------------------------------------------------------------------
publicAPI.handleMouseDolly = (renderer, position) => {
const dy = position.y - model.previousPosition.y;
const rwi = model.interactor;
const center = rwi.getView().getViewportCenter(renderer);
const dyf = (model.motionFactor * dy) / center[1];
publicAPI.dollyByFactor(renderer, 1.1 ** dyf);
};
//----------------------------------------------------------------------------
publicAPI.handleMouseWheel = (callData) => {
const dyf = 1 - callData.spinY / 10; // divide by 10 to lower the zoom factor
publicAPI.dollyByFactor(callData.pokedRenderer, dyf);
};
//----------------------------------------------------------------------------
publicAPI.dollyByFactor = (renderer, factor) => {
if (Number.isNaN(factor)) {
return;
}
const camera = renderer.getActiveCamera();
if (camera.getParallelProjection()) {
camera.setParallelScale(camera.getParallelScale() / factor);
} else {
camera.dolly(factor);
if (model.autoAdjustCameraClippingRange) {
renderer.resetCameraClippingRange();
}
}
if (model.interactor.getLightFollowCamera()) {
renderer.updateLightsGeometryToFollowCamera();
}
};
}
// ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------
const DEFAULT_VALUES = {
motionFactor: 10.0,
};
// ----------------------------------------------------------------------------
export function extend(publicAPI, model, initialValues = {}) {
Object.assign(model, DEFAULT_VALUES, initialValues);
// Inheritance
vtkInteractorStyle.extend(publicAPI, model, initialValues);
// Create get-set macros
macro.setGet(publicAPI, model, ['motionFactor']);
// For more macro methods, see "Sources/macro.js"
// Object specific methods
vtkInteractorStyleTrackballCamera(publicAPI, model);
}
// ----------------------------------------------------------------------------
export const newInstance = macro.newInstance(
extend,
'vtkInteractorStyleTrackballCamera'
);
// ----------------------------------------------------------------------------
export default { newInstance, extend };