@kitware/vtk.js
Version:
Visualization Toolkit for the Web
585 lines (499 loc) • 22.7 kB
JavaScript
import { n as newInstance$1, o as obj, e as setGet, g as get, i as moveToProtected, f as event, p as proxy, j as proxyPropertyMapping, h as chain } from '../../macros2.js';
import vtkAnnotatedCubeActor from '../../Rendering/Core/AnnotatedCubeActor.js';
import vtkAxesActor from '../../Rendering/Core/AxesActor.js';
import vtkCornerAnnotation from '../../Interaction/UI/CornerAnnotation.js';
import vtkInteractorStyleManipulator from '../../Interaction/Style/InteractorStyleManipulator.js';
import vtkMatrixBuilder from '../../Common/Core/MatrixBuilder.js';
import '../../Rendering/OpenGL/RenderWindow.js';
import '../../Rendering/WebGPU/RenderWindow.js';
import vtkOrientationMarkerWidget from '../../Interaction/Widgets/OrientationMarkerWidget.js';
import vtkRenderer from '../../Rendering/Core/Renderer.js';
import vtkRenderWindow from '../../Rendering/Core/RenderWindow.js';
import vtkRenderWindowInteractor from '../../Rendering/Core/RenderWindowInteractor.js';
import InteractionPresets from '../../Interaction/Style/InteractorStyleManipulator/Presets.js';
import AnnotatedCubePresets from '../../Rendering/Core/AnnotatedCubeActor/Presets.js';
const EPSILON = 0.000001;
// ----------------------------------------------------------------------------
// vtkViewProxy methods
// ----------------------------------------------------------------------------
function vtkViewProxy(publicAPI, model) {
// Set our className
model.classHierarchy.push('vtkViewProxy');
// Private --------------------------------------------------------------------
function updateAnnotationColor() {
const [r, g, b] = model.renderer.getBackground();
model.cornerAnnotation.getAnnotationContainer().style.color = r + g + b > 1.5 ? 'black' : 'white';
}
// Setup --------------------------------------------------------------------
model.renderWindow = vtkRenderWindow.newInstance();
model.renderer = vtkRenderer.newInstance({
background: [0, 0, 0]
});
model.renderWindow.addRenderer(model.renderer);
model._openGLRenderWindow = model.renderWindow.newAPISpecificView();
model.renderWindow.addView(model._openGLRenderWindow);
model.interactor = vtkRenderWindowInteractor.newInstance();
model.interactor.setView(model._openGLRenderWindow);
model.interactorStyle3D = vtkInteractorStyleManipulator.newInstance();
model.interactorStyle2D = vtkInteractorStyleManipulator.newInstance();
/**
* Internal function used by publicAPI.resetCamera()
*/
model._resetCamera = (bounds = null) => model.renderer.resetCamera(bounds);
// Apply default interaction styles
InteractionPresets.applyPreset('3D', model.interactorStyle3D);
InteractionPresets.applyPreset('2D', model.interactorStyle2D);
model.cornerAnnotation = vtkCornerAnnotation.newInstance();
// Setup interaction
model.interactor.setInteractorStyle(model.useParallelRendering ? model.interactorStyle2D : model.interactorStyle3D);
model.camera = model.renderer.getActiveCamera();
model.camera.setParallelProjection(!!model.useParallelRendering);
// Orientation axis setup -------------------------------------------------
model.orientationAxesArrow = vtkAxesActor.newInstance();
model.orientationAxesCube = vtkAnnotatedCubeActor.newInstance();
AnnotatedCubePresets.applyPreset('default', model.orientationAxesCube);
AnnotatedCubePresets.applyPreset('lps', model.orientationAxesCube);
model.orientationAxesMap = {
arrow: model.orientationAxesArrow,
cube: model.orientationAxesCube
};
model.orientationWidget = vtkOrientationMarkerWidget.newInstance({
actor: model.orientationAxesArrow,
interactor: model.renderWindow.getInteractor()
});
model.orientationWidget.setEnabled(true);
model.orientationWidget.setViewportCorner(vtkOrientationMarkerWidget.Corners.BOTTOM_LEFT);
model.orientationWidget.setViewportSize(0.1);
// API ----------------------------------------------------------------------
publicAPI.setPresetToInteractor3D = nameOrDefinitions => {
if (Array.isArray(nameOrDefinitions)) {
return InteractionPresets.applyDefinitions(nameOrDefinitions, model.interactorStyle3D);
}
return InteractionPresets.applyPreset(nameOrDefinitions, model.interactorStyle3D);
};
// --------------------------------------------------------------------------
publicAPI.setPresetToInteractor2D = nameOrDefinitions => {
if (Array.isArray(nameOrDefinitions)) {
return InteractionPresets.applyDefinitions(nameOrDefinitions, model.interactorStyle2D);
}
return InteractionPresets.applyPreset(nameOrDefinitions, model.interactorStyle2D);
};
// --------------------------------------------------------------------------
publicAPI.setOrientationAxesType = type => {
const actor = model.orientationAxesMap[type];
if (actor) {
model.orientationAxesType = type;
model.orientationWidget.setActor(actor);
publicAPI.renderLater();
}
};
// --------------------------------------------------------------------------
publicAPI.registerOrientationAxis = (name, actor) => {
model.orientationAxesMap[name] = actor;
};
// --------------------------------------------------------------------------
publicAPI.unregisterOrientationAxis = name => {
delete model.orientationAxesMap[name];
};
// --------------------------------------------------------------------------
publicAPI.listOrientationAxis = () => Object.keys(model.orientationAxesMap);
// --------------------------------------------------------------------------
publicAPI.setPresetToOrientationAxes = nameOrDefinitions => {
let changeDetected = false;
if (typeof nameOrDefinitions === 'string') {
if (model.presetToOrientationAxes !== nameOrDefinitions) {
model.presetToOrientationAxes = nameOrDefinitions;
changeDetected = AnnotatedCubePresets.applyPreset(nameOrDefinitions, model.orientationAxesCube);
publicAPI.modified();
}
return changeDetected;
}
model.presetToOrientationAxes = 'Custom';
changeDetected = AnnotatedCubePresets.applyDefinitions(nameOrDefinitions, model.orientationAxesCube);
publicAPI.modified();
return changeDetected;
};
// --------------------------------------------------------------------------
publicAPI.setContainer = container => {
const orientationWidgetEnabled = model.orientationWidget.getEnabled();
if (model.container) {
model.orientationWidget.setEnabled(false);
model.interactor.unbindEvents(model.container);
model._openGLRenderWindow.setContainer(null);
model.cornerAnnotation.setContainer(null);
}
model.container = container;
if (container) {
model._openGLRenderWindow.setContainer(container);
model.cornerAnnotation.setContainer(container);
model.interactor.initialize();
model.interactor.bindEvents(container);
model.orientationWidget.setEnabled(orientationWidgetEnabled);
}
};
// --------------------------------------------------------------------------
publicAPI.resize = () => {
if (model.container) {
const dims = model.container.getBoundingClientRect();
if (dims.width === dims.height && dims.width === 0) {
return;
}
const devicePixelRatio = window.devicePixelRatio || 1;
const width = Math.max(10, Math.floor(devicePixelRatio * dims.width));
const height = Math.max(10, Math.floor(devicePixelRatio * dims.height));
model._openGLRenderWindow.setSize(width, height);
publicAPI.invokeResize({
width,
height
});
publicAPI.renderLater();
}
};
// --------------------------------------------------------------------------
publicAPI.renderLater = () => {
publicAPI.render(false);
};
// --------------------------------------------------------------------------
publicAPI.render = (blocking = true) => {
if (model.representations.length > 0 && model.resetCameraOnFirstRender) {
model.resetCameraOnFirstRender = false;
publicAPI.resetCamera();
}
model.orientationWidget.updateMarkerOrientation();
model.renderer.resetCameraClippingRange();
if (blocking) {
model.renderWindow.render();
} else {
setTimeout(model.renderWindow.render, 0);
}
};
// --------------------------------------------------------------------------
publicAPI.addRepresentation = representation => {
if (!representation) {
return;
}
if (model.representations.indexOf(representation) === -1) {
model.representations.push(representation);
model.renderer.addViewProp(representation);
}
};
// --------------------------------------------------------------------------
publicAPI.removeRepresentation = representation => {
if (!representation) {
return;
}
if (model.representations.indexOf(representation) !== -1) {
model.representations = model.representations.filter(r => r !== representation);
model.renderer.removeViewProp(representation);
}
if (model.representations.length === 0) {
model.resetCameraOnFirstRender = true;
}
};
// --------------------------------------------------------------------------
publicAPI.setCameraParameters = ({
position,
focalPoint,
bounds,
parallelScale,
viewAngle
}) => {
if (position != null) {
model.camera.setPosition(...position);
}
if (focalPoint != null) {
model.camera.setFocalPoint(...focalPoint);
}
if (bounds != null) {
model.renderer.resetCameraClippingRange(bounds);
} else {
model.renderer.resetCameraClippingRange();
}
if (parallelScale != null) {
model.camera.setParallelScale(parallelScale);
}
if (viewAngle != null) {
model.camera.setViewAngle(viewAngle);
}
};
// --------------------------------------------------------------------------
publicAPI.resetCamera = () => {
model._resetCamera();
model.interactorStyle2D.setCenterOfRotation(model.camera.getFocalPoint());
model.interactorStyle3D.setCenterOfRotation(model.camera.getFocalPoint());
publicAPI.renderLater();
};
// --------------------------------------------------------------------------
publicAPI.captureImage = ({
format = 'image/png',
...opts
} = {}) => model.renderWindow.captureImages(format, opts)[0];
// --------------------------------------------------------------------------
publicAPI.openCaptureImage = (target = '_blank') => {
const image = new Image();
return publicAPI.captureImage().then(imageURL => {
image.src = imageURL;
const w = window.open('', target);
w.document.write(image.outerHTML);
w.document.title = 'vtk.js Image Capture';
window.focus();
});
};
// --------------------------------------------------------------------------
publicAPI.setCornerAnnotation = (corner, templateString) => {
model.cornerAnnotation.updateTemplates({
[corner]: meta => vtkCornerAnnotation.applyTemplate(templateString, meta)
});
};
// --------------------------------------------------------------------------
publicAPI.setCornerAnnotations = (annotations, useTemplateString = false) => {
if (useTemplateString) {
Object.keys(annotations).forEach(key => {
publicAPI.setCornerAnnotation(key, annotations[key]);
});
} else {
model.cornerAnnotation.updateTemplates(annotations);
}
};
// --------------------------------------------------------------------------
publicAPI.updateCornerAnnotation = meta => model.cornerAnnotation.updateMetadata(meta);
// --------------------------------------------------------------------------
publicAPI.setAnnotationOpacity = opacity => {
if (model.annotationOpacity !== Number(opacity)) {
model.annotationOpacity = Number(opacity);
model.cornerAnnotation.getAnnotationContainer().style.opacity = opacity;
publicAPI.modified();
}
};
// --------------------------------------------------------------------------
publicAPI.setBackground = (...args) => {
const res = model.renderer.setBackground(...args);
updateAnnotationColor();
return res;
};
// --------------------------------------------------------------------------
publicAPI.getBackground = model.renderer.getBackground;
// --------------------------------------------------------------------------
publicAPI.setAnimation = (enable, requester = publicAPI) => {
if (model.disableAnimation && enable) {
return;
}
if (enable) {
model.renderWindow.getInteractor().requestAnimation(requester);
} else {
const skipWarning = requester === publicAPI || `${requester}`.indexOf('ViewProxy.moveCamera.') === 0;
model.renderWindow.getInteractor().cancelAnimation(requester, skipWarning);
}
};
// --------------------------------------------------------------------------
publicAPI.updateOrientation = (axisIndex, orientation, viewUp, animateSteps = 0) => {
if (axisIndex === undefined) {
return Promise.resolve();
}
const originalPosition = model.camera.getPosition();
const originalViewUp = model.camera.getViewUp();
const originalFocalPoint = model.camera.getFocalPoint();
model.axis = axisIndex;
model.orientation = orientation;
model.viewUp = viewUp;
const position = model.camera.getFocalPoint();
position[model.axis] += model.orientation;
model.camera.setPosition(...position);
model.camera.setViewUp(...viewUp);
model.renderer.resetCamera();
const destFocalPoint = model.camera.getFocalPoint();
const destPosition = model.camera.getPosition();
const destViewUp = model.camera.getViewUp();
// Reset to original to prevent initial render flash
model.camera.setFocalPoint(...originalFocalPoint);
model.camera.setPosition(...originalPosition);
model.camera.setViewUp(...originalViewUp);
return publicAPI.moveCamera(destFocalPoint, destPosition, destViewUp, animateSteps);
};
// --------------------------------------------------------------------------
publicAPI.moveCamera = (focalPoint, position, viewUp, animateSteps = 0) => {
const originalFocalPoint = model.camera.getFocalPoint();
const originalPosition = model.camera.getPosition();
const originalViewUp = model.camera.getViewUp();
const animationStack = [{
focalPoint,
position,
viewUp
}];
if (animateSteps) {
const deltaFocalPoint = [(originalFocalPoint[0] - focalPoint[0]) / animateSteps, (originalFocalPoint[1] - focalPoint[1]) / animateSteps, (originalFocalPoint[2] - focalPoint[2]) / animateSteps];
const deltaPosition = [(originalPosition[0] - position[0]) / animateSteps, (originalPosition[1] - position[1]) / animateSteps, (originalPosition[2] - position[2]) / animateSteps];
const deltaViewUp = [(originalViewUp[0] - viewUp[0]) / animateSteps, (originalViewUp[1] - viewUp[1]) / animateSteps, (originalViewUp[2] - viewUp[2]) / animateSteps];
const needSteps = deltaFocalPoint[0] || deltaFocalPoint[1] || deltaFocalPoint[2] || deltaPosition[0] || deltaPosition[1] || deltaPosition[2] || deltaViewUp[0] || deltaViewUp[1] || deltaViewUp[2];
const focalPointDeltaAxisCount = deltaFocalPoint.map(i => Math.abs(i) < EPSILON ? 0 : 1).reduce((a, b) => a + b, 0);
const positionDeltaAxisCount = deltaPosition.map(i => Math.abs(i) < EPSILON ? 0 : 1).reduce((a, b) => a + b, 0);
const viewUpDeltaAxisCount = deltaViewUp.map(i => Math.abs(i) < EPSILON ? 0 : 1).reduce((a, b) => a + b, 0);
const rotation180Only = viewUpDeltaAxisCount === 1 && positionDeltaAxisCount === 0 && focalPointDeltaAxisCount === 0;
if (needSteps) {
if (rotation180Only) {
const availableAxes = originalFocalPoint.map((fp, i) => Math.abs(originalPosition[i] - fp) < EPSILON ? i : null).filter(i => i !== null);
const axisCorrectionIndex = availableAxes.find(v => Math.abs(deltaViewUp[v]) < EPSILON);
for (let i = 0; i < animateSteps; i++) {
const newViewUp = [viewUp[0] + (i + 1) * deltaViewUp[0], viewUp[1] + (i + 1) * deltaViewUp[1], viewUp[2] + (i + 1) * deltaViewUp[2]];
newViewUp[axisCorrectionIndex] = Math.sin(Math.PI * i / (animateSteps - 1));
animationStack.push({
focalPoint,
position,
viewUp: newViewUp
});
}
} else {
for (let i = 0; i < animateSteps; i++) {
animationStack.push({
focalPoint: [focalPoint[0] + (i + 1) * deltaFocalPoint[0], focalPoint[1] + (i + 1) * deltaFocalPoint[1], focalPoint[2] + (i + 1) * deltaFocalPoint[2]],
position: [position[0] + (i + 1) * deltaPosition[0], position[1] + (i + 1) * deltaPosition[1], position[2] + (i + 1) * deltaPosition[2]],
viewUp: [viewUp[0] + (i + 1) * deltaViewUp[0], viewUp[1] + (i + 1) * deltaViewUp[1], viewUp[2] + (i + 1) * deltaViewUp[2]]
});
}
}
}
}
if (animationStack.length === 1) {
// update camera directly
model.camera.set(animationStack.pop());
model.renderer.resetCameraClippingRange();
if (model.interactor.getLightFollowCamera()) {
model.renderer.updateLightsGeometryToFollowCamera();
}
return Promise.resolve();
}
return new Promise((resolve, reject) => {
const now = performance.now().toString();
const animationRequester = `ViewProxy.moveCamera.${now}`;
publicAPI.setAnimation(true, animationRequester);
let intervalId = null;
const consumeAnimationStack = () => {
if (animationStack.length) {
const {
focalPoint: cameraFocalPoint,
position: cameraPosition,
viewUp: cameraViewUp
} = animationStack.pop();
model.camera.setFocalPoint(...cameraFocalPoint);
model.camera.setPosition(...cameraPosition);
model.camera.setViewUp(...cameraViewUp);
model.renderer.resetCameraClippingRange();
if (model.interactor.getLightFollowCamera()) {
model.renderer.updateLightsGeometryToFollowCamera();
}
} else {
clearInterval(intervalId);
publicAPI.setAnimation(false, animationRequester);
resolve();
}
};
intervalId = setInterval(consumeAnimationStack, 1);
});
};
// --------------------------------------------------------------------------
publicAPI.resetOrientation = (animateSteps = 0) => publicAPI.updateOrientation(model.axis, model.orientation, model.viewUp, animateSteps);
// --------------------------------------------------------------------------
publicAPI.rotate = angle => {
const {
viewUp,
focalPoint,
position
} = model.camera.get('viewUp', 'focalPoint', 'position');
const axis = [focalPoint[0] - position[0], focalPoint[1] - position[1], focalPoint[2] - position[2]];
vtkMatrixBuilder.buildFromDegree().rotate(Number.isNaN(angle) ? 90 : angle, axis).apply(viewUp);
model.camera.setViewUp(...viewUp);
model.camera.modified();
model.orientationWidget.updateMarkerOrientation();
model.renderWindow.render();
};
// --------------------------------------------------------------------------
publicAPI.focusTo = chain(model.camera.setFocalPoint, model.interactorStyle2D.setCenterOfRotation, model.interactorStyle3D.setCenterOfRotation);
// --------------------------------------------------------------------------
publicAPI.delete = chain(() => {
publicAPI.setContainer(null);
model.orientationWidget.setEnabled(false);
model.orientationWidget.delete();
model.orientationAxesArrow.delete();
model.orientationAxesCube.delete();
model.interactorStyle2D.delete();
model.interactorStyle3D.delete();
model.cornerAnnotation.delete();
// in reverse order
model.interactor.delete();
model.renderer.delete();
model._openGLRenderWindow.delete();
model.renderWindow.delete();
}, publicAPI.delete);
// --------------------------------------------------------------------------
// Initialization from state or input
// --------------------------------------------------------------------------
publicAPI.resetOrientation();
updateAnnotationColor();
}
// ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------
const DEFAULT_VALUES = {
representations: [],
sectionName: 'view',
annotationOpacity: 1,
resetCameraOnFirstRender: true,
presetToOrientationAxes: 'lps',
orientationAxesType: 'arrow',
disableAnimation: false,
axis: 1,
orientation: 0,
viewUp: [0, 0, 1]
};
// ----------------------------------------------------------------------------
function extend(publicAPI, model, initialValues = {}) {
Object.assign(model, DEFAULT_VALUES, initialValues);
obj(publicAPI, model);
setGet(publicAPI, model, ['name', 'disableAnimation', 'resetCameraOnFirstRender']);
get(publicAPI, model, ['annotationOpacity', 'camera', 'container', 'cornerAnnotation', 'interactor', 'interactorStyle2D', 'interactorStyle3D', '_openGLRenderWindow',
// todo breaking? convert to apiSpecificWindow
'orientationAxesType', 'presetToOrientationAxes', 'renderer', 'renderWindow', 'representations', 'useParallelRendering']);
moveToProtected(publicAPI, model, ['openGLRenderWindow']);
event(publicAPI, model, 'Resize');
// Object specific methods
vtkViewProxy(publicAPI, model);
// Proxy handling
proxy(publicAPI, model);
proxyPropertyMapping(publicAPI, model, {
orientationAxesVisibility: {
modelKey: 'orientationWidget',
property: 'enabled'
},
orientationAxesCorner: {
modelKey: 'orientationWidget',
property: 'viewportCorner'
},
orientationAxesSize: {
modelKey: 'orientationWidget',
property: 'viewportSize'
},
cameraViewUp: {
modelKey: 'camera',
property: 'viewUp',
modified: false
},
cameraPosition: {
modelKey: 'camera',
property: 'position',
modified: false
},
cameraFocalPoint: {
modelKey: 'camera',
property: 'focalPoint',
modified: false
}
});
}
// ----------------------------------------------------------------------------
const newInstance = newInstance$1(extend, 'vtkViewProxy');
// ----------------------------------------------------------------------------
var vtkViewProxy$1 = {
newInstance,
extend
};
export { vtkViewProxy$1 as default, newInstance };