@acransac/vtk.js
Version:
Visualization Toolkit for the Web
275 lines (228 loc) • 7.92 kB
JavaScript
import macro from 'vtk.js/Sources/macro';
import vtkRenderer from 'vtk.js/Sources/Rendering/Core/Renderer';
import Constants from 'vtk.js/Sources/Interaction/Widgets/OrientationMarkerWidget/Constants';
const { vtkErrorMacro } = macro;
const { Corners } = Constants;
// ----------------------------------------------------------------------------
// vtkOrientationMarkerWidget
// ----------------------------------------------------------------------------
function vtkOrientationMarkerWidget(publicAPI, model) {
// Set our className
model.classHierarchy.push('vtkOrientationMarkerWidget');
const superClass = { ...publicAPI };
// Private variables
const previousCameraInput = [];
const selfRenderer = vtkRenderer.newInstance();
const resizeObserver = new ResizeObserver((entries) => {
if (entries.length === 1) {
publicAPI.updateViewport();
}
});
let interactorUnsubscribe = null;
let selfSubscription = null;
publicAPI.computeViewport = () => {
const [viewXSize, viewYSize] = model.interactor.getView().getSize();
const minViewSize = Math.min(viewXSize, viewYSize);
let pixelSize = model.viewportSize * minViewSize;
// clamp pixel size
pixelSize = Math.max(
Math.min(model.minPixelSize, minViewSize),
Math.min(model.maxPixelSize, pixelSize)
);
const xFrac = pixelSize / viewXSize;
const yFrac = pixelSize / viewYSize;
// [left bottom right top]
switch (model.viewportCorner) {
case Corners.TOP_LEFT:
return [0, 1 - yFrac, xFrac, 1];
case Corners.TOP_RIGHT:
return [1 - xFrac, 1 - yFrac, 1, 1];
case Corners.BOTTOM_LEFT:
return [0, 0, xFrac, yFrac];
case Corners.BOTTOM_RIGHT:
return [1 - xFrac, 0, 1, yFrac];
default:
vtkErrorMacro('Invalid widget corner');
return null;
}
};
publicAPI.updateViewport = () => {
selfRenderer.setViewport(...publicAPI.computeViewport());
model.interactor.render();
};
publicAPI.updateMarkerOrientation = () => {
const currentCamera = model.interactor
.findPokedRenderer()
.getActiveCamera();
if (!currentCamera) {
return;
}
const position = currentCamera.getReferenceByName('position');
const focalPoint = currentCamera.getReferenceByName('focalPoint');
const viewUp = currentCamera.getReferenceByName('viewUp');
if (
previousCameraInput[0] !== position[0] ||
previousCameraInput[1] !== position[1] ||
previousCameraInput[2] !== position[2] ||
previousCameraInput[3] !== focalPoint[0] ||
previousCameraInput[4] !== focalPoint[1] ||
previousCameraInput[5] !== focalPoint[2] ||
previousCameraInput[6] !== viewUp[0] ||
previousCameraInput[7] !== viewUp[1] ||
previousCameraInput[8] !== viewUp[2]
) {
previousCameraInput[0] = position[0];
previousCameraInput[1] = position[1];
previousCameraInput[2] = position[2];
previousCameraInput[3] = focalPoint[0];
previousCameraInput[4] = focalPoint[1];
previousCameraInput[5] = focalPoint[2];
previousCameraInput[6] = viewUp[0];
previousCameraInput[7] = viewUp[1];
previousCameraInput[8] = viewUp[2];
const activeCamera = selfRenderer.getActiveCamera();
activeCamera.setPosition(position[0], position[1], position[2]);
activeCamera.setFocalPoint(focalPoint[0], focalPoint[1], focalPoint[2]);
activeCamera.setViewUp(viewUp[0], viewUp[1], viewUp[2]);
selfRenderer.resetCamera();
}
};
/**
* Enables/Disables the orientation marker.
*/
publicAPI.setEnabled = (enabling) => {
if (enabling) {
if (model.enabled) {
return;
}
if (!model.actor) {
vtkErrorMacro('Must set actor before enabling orientation marker.');
return;
}
if (!model.interactor) {
vtkErrorMacro(
'Must set interactor before enabling orientation marker.'
);
return;
}
const renderWindow = model.interactor
.findPokedRenderer()
.getRenderWindow();
renderWindow.addRenderer(selfRenderer);
if (renderWindow.getNumberOfLayers() < 2) {
renderWindow.setNumberOfLayers(2);
}
// Highest number is foreground
selfRenderer.setLayer(renderWindow.getNumberOfLayers() - 1);
selfRenderer.setInteractive(false);
selfRenderer.addViewProp(model.actor);
model.actor.setVisibility(true);
({ unsubscribe: interactorUnsubscribe } = model.interactor.onAnimation(
publicAPI.updateMarkerOrientation
));
resizeObserver.observe(model.interactor.getView().getCanvas());
publicAPI.updateViewport();
publicAPI.updateMarkerOrientation();
model.enabled = true;
} else {
if (!model.enabled) {
return;
}
model.enabled = false;
resizeObserver.disconnect();
interactorUnsubscribe();
interactorUnsubscribe = null;
model.actor.setVisibility(false);
selfRenderer.removeViewProp(model.actor);
const renderWindow = model.interactor
.findPokedRenderer()
.getRenderWindow();
if (renderWindow) {
renderWindow.removeRenderer(selfRenderer);
}
}
publicAPI.modified();
};
/**
* Sets the viewport corner.
*/
publicAPI.setViewportCorner = (corner) => {
if (corner === model.viewportCorner) {
return;
}
model.viewportCorner = corner;
if (model.enabled) {
publicAPI.updateViewport();
}
};
/**
* Sets the viewport size.
*/
publicAPI.setViewportSize = (sizeFactor) => {
const viewportSize = Math.min(1, Math.max(0, sizeFactor));
if (viewportSize === model.viewportSize) {
return;
}
model.viewportSize = viewportSize;
if (model.enabled) {
publicAPI.updateViewport();
}
};
publicAPI.setActor = (actor) => {
const previousState = model.enabled;
publicAPI.setEnabled(false);
model.actor = actor;
publicAPI.setEnabled(previousState);
};
publicAPI.getRenderer = () => selfRenderer;
publicAPI.delete = () => {
superClass.delete();
if (selfSubscription) {
selfSubscription.unsubscribe();
selfSubscription = null;
}
if (interactorUnsubscribe) {
interactorUnsubscribe();
interactorUnsubscribe = null;
}
resizeObserver.disconnect();
};
// --------------------------------------------------------------------------
// update viewport whenever we are updated
selfSubscription = publicAPI.onModified(publicAPI.updateViewport);
}
// ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------
export const DEFAULT_VALUES = {
// actor: null,
// interactor: null,
viewportCorner: Constants.Corners.BOTTOM_LEFT,
viewportSize: 0.2,
minPixelSize: 50,
maxPixelSize: 200,
};
// ----------------------------------------------------------------------------
export function extend(publicAPI, model, initialValues = {}) {
Object.assign(model, DEFAULT_VALUES, initialValues);
// Build VTK API
macro.obj(publicAPI, model);
macro.get(publicAPI, model, ['enabled', 'viewportCorner', 'viewportSize']);
// NOTE: setting these while the widget is enabled will
// not update the widget.
macro.setGet(publicAPI, model, [
'interactor',
'minPixelSize',
'maxPixelSize',
]);
macro.get(publicAPI, model, ['actor']);
// Object methods
vtkOrientationMarkerWidget(publicAPI, model);
}
// ----------------------------------------------------------------------------
export const newInstance = macro.newInstance(
extend,
'vtkOrientationMarkerWidget'
);
// ----------------------------------------------------------------------------
export default { newInstance, extend, ...Constants };