@kitware/vtk.js
Version:
Visualization Toolkit for the Web
238 lines (211 loc) • 9.48 kB
JavaScript
import { m as macro } from '../../macros2.js';
import { l as normalize, E as areEquals, f as distance2BetweenPoints } from '../../Common/Core/Math/index.js';
import vtkMatrixBuilder from '../../Common/Core/MatrixBuilder.js';
import vtkBoundingBox from '../../Common/DataModel/BoundingBox.js';
import vtkInteractorStyleManipulator from './InteractorStyleManipulator.js';
import vtkMouseCameraTrackballRotateManipulator from '../Manipulators/MouseCameraTrackballRotateManipulator.js';
import vtkMouseCameraTrackballPanManipulator from '../Manipulators/MouseCameraTrackballPanManipulator.js';
import vtkMouseCameraTrackballZoomManipulator from '../Manipulators/MouseCameraTrackballZoomManipulator.js';
import vtkMouseRangeManipulator from '../Manipulators/MouseRangeManipulator.js';
import { mat4 } from 'gl-matrix';
// ----------------------------------------------------------------------------
// Global methods
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
function clamp(value, min, max) {
if (value < min) {
return min;
}
if (value > max) {
return max;
}
return value;
}
// ----------------------------------------------------------------------------
// vtkInteractorStyleMPRSlice methods
// ----------------------------------------------------------------------------
function vtkInteractorStyleMPRSlice(publicAPI, model) {
// Set our className
model.classHierarchy.push('vtkInteractorStyleMPRSlice');
model.trackballManipulator = vtkMouseCameraTrackballRotateManipulator.newInstance({
button: 1
});
model.panManipulator = vtkMouseCameraTrackballPanManipulator.newInstance({
button: 1,
shift: true
});
model.zoomManipulator = vtkMouseCameraTrackballZoomManipulator.newInstance({
button: 3
});
model.scrollManipulator = vtkMouseRangeManipulator.newInstance({
scrollEnabled: true,
dragEnabled: false
});
// cache for sliceRange
const cache = {
sliceNormal: [0, 0, 0],
sliceRange: [0, 0]
};
let cameraSub = null;
function updateScrollManipulator() {
const range = publicAPI.getSliceRange();
model.scrollManipulator.removeScrollListener();
model.scrollManipulator.setScrollListener(range[0], range[1], 1, publicAPI.getSlice, publicAPI.setSlice);
}
function setManipulators() {
publicAPI.removeAllMouseManipulators();
publicAPI.addMouseManipulator(model.trackballManipulator);
publicAPI.addMouseManipulator(model.panManipulator);
publicAPI.addMouseManipulator(model.zoomManipulator);
publicAPI.addMouseManipulator(model.scrollManipulator);
updateScrollManipulator();
}
const superSetInteractor = publicAPI.setInteractor;
publicAPI.setInteractor = interactor => {
superSetInteractor(interactor);
if (cameraSub) {
cameraSub.unsubscribe();
cameraSub = null;
}
if (interactor) {
const renderer = interactor.getCurrentRenderer();
const camera = renderer.getActiveCamera();
cameraSub = camera.onModified(() => {
updateScrollManipulator();
publicAPI.modified();
});
}
};
publicAPI.handleMouseMove = macro.chain(publicAPI.handleMouseMove, () => {
const renderer = model._interactor.getCurrentRenderer();
const camera = renderer.getActiveCamera();
const dist = camera.getDistance();
camera.setClippingRange(dist, dist + 0.1);
});
const superSetVolumeMapper = publicAPI.setVolumeMapper;
publicAPI.setVolumeMapper = mapper => {
if (superSetVolumeMapper(mapper)) {
const renderer = model._interactor.getCurrentRenderer();
const camera = renderer.getActiveCamera();
if (mapper) {
// prevent zoom manipulator from messing with our focal point
camera.setFreezeFocalPoint(true);
publicAPI.setSliceNormal(...publicAPI.getSliceNormal());
} else {
camera.setFreezeFocalPoint(false);
}
}
};
publicAPI.getSlice = () => {
const renderer = model._interactor.getCurrentRenderer();
const camera = renderer.getActiveCamera();
const sliceNormal = publicAPI.getSliceNormal();
// Get rotation matrix from normal to +X (since bounds is aligned to XYZ)
const transform = vtkMatrixBuilder.buildFromDegree().identity().rotateFromDirections(sliceNormal, [1, 0, 0]);
const fp = camera.getFocalPoint();
transform.apply(fp);
return fp[0];
};
publicAPI.setSlice = slice => {
const renderer = model._interactor.getCurrentRenderer();
const camera = renderer.getActiveCamera();
if (model.volumeMapper) {
const range = publicAPI.getSliceRange();
const clampedSlice = clamp(slice, ...range);
const center = model.volumeMapper.getCenter();
const distance = camera.getDistance();
const dop = camera.getDirectionOfProjection();
normalize(dop);
const midPoint = (range[1] + range[0]) / 2.0;
const zeroPoint = [center[0] - dop[0] * midPoint, center[1] - dop[1] * midPoint, center[2] - dop[2] * midPoint];
const slicePoint = [zeroPoint[0] + dop[0] * clampedSlice, zeroPoint[1] + dop[1] * clampedSlice, zeroPoint[2] + dop[2] * clampedSlice];
const newPos = [slicePoint[0] - dop[0] * distance, slicePoint[1] - dop[1] * distance, slicePoint[2] - dop[2] * distance];
camera.setPosition(...newPos);
camera.setFocalPoint(...slicePoint);
}
};
publicAPI.getSliceRange = () => {
if (model.volumeMapper) {
const sliceNormal = publicAPI.getSliceNormal();
if (areEquals(sliceNormal, cache.sliceNormal)) {
return cache.sliceRange;
}
// Get rotation matrix from normal to +X (since bounds is aligned to XYZ)
const sliceOrientation = vtkMatrixBuilder.buildFromDegree().identity().rotateFromDirections(sliceNormal, [1, 0, 0]);
const imageAlongSliceNormal = mat4.create();
mat4.multiply(imageAlongSliceNormal, sliceOrientation.getMatrix(), model.volumeMapper.getInputData().getIndexToWorld());
// Transform the 8 corners of the input data's bounding box
// to rotate into the slice plane space without the intermediate
// axis-aligned box (provided by getBounds) which would grow the bounds.
const transformedBounds = vtkBoundingBox.transformBounds(model.volumeMapper.getInputData().getSpatialExtent(), imageAlongSliceNormal);
// range is now maximum X distance
const minX = transformedBounds[0];
const maxX = transformedBounds[1];
cache.sliceNormal = sliceNormal;
cache.sliceRange = [minX, maxX];
return cache.sliceRange;
}
return [0, 0];
};
// Slice normal is just camera DOP
publicAPI.getSliceNormal = () => {
if (model.volumeMapper) {
const renderer = model._interactor.getCurrentRenderer();
const camera = renderer.getActiveCamera();
return camera.getDirectionOfProjection();
}
return [0, 0, 0];
};
// in world space
publicAPI.setSliceNormal = (...normal) => {
const renderer = model._interactor.getCurrentRenderer();
const camera = renderer.getActiveCamera();
normalize(normal);
if (model.volumeMapper) {
const bounds = model.volumeMapper.getBounds();
// diagonal will be used as "width" of camera scene
const diagonal = Math.sqrt(distance2BetweenPoints([bounds[0], bounds[2], bounds[4]], [bounds[1], bounds[3], bounds[5]]));
// center will be used as initial focal point
const center = [(bounds[0] + bounds[1]) / 2.0, (bounds[2] + bounds[3]) / 2.0, (bounds[4] + bounds[5]) / 2.0];
const angle = 90;
// distance from camera to focal point
const dist = diagonal / (2 * Math.tan(angle / 360 * Math.PI));
const cameraPos = [center[0] - normal[0] * dist, center[1] - normal[1] * dist, center[2] - normal[2] * dist];
// set viewUp based on DOP rotation
const oldDop = camera.getDirectionOfProjection();
const transform = vtkMatrixBuilder.buildFromDegree().identity().rotateFromDirections(oldDop, normal);
const viewUp = [0, 1, 0];
transform.apply(viewUp);
camera.setPosition(...cameraPos);
camera.setDistance(dist);
// should be set after pos and distance
camera.setDirectionOfProjection(...normal);
camera.setViewUp(...viewUp);
camera.setViewAngle(angle);
camera.setClippingRange(dist, dist + 0.1);
publicAPI.setCenterOfRotation(center);
}
};
setManipulators();
}
// ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------
const DEFAULT_VALUES = {};
// ----------------------------------------------------------------------------
function extend(publicAPI, model, initialValues = {}) {
Object.assign(model, DEFAULT_VALUES, initialValues);
// Inheritance
vtkInteractorStyleManipulator.extend(publicAPI, model, initialValues);
macro.setGet(publicAPI, model, ['volumeMapper']);
// Object specific methods
vtkInteractorStyleMPRSlice(publicAPI, model);
}
// ----------------------------------------------------------------------------
const newInstance = macro.newInstance(extend, 'vtkInteractorStyleMPRSlice');
// ----------------------------------------------------------------------------
var vtkInteractorStyleMPRSlice$1 = {
newInstance,
extend
};
export { vtkInteractorStyleMPRSlice$1 as default, extend, newInstance };