UNPKG

@thewtex/vtk.js-esm

Version:

Visualization Toolkit for the Web

576 lines (458 loc) 19.8 kB
import _toConsumableArray from '@babel/runtime/helpers/toConsumableArray'; import _defineProperty from '@babel/runtime/helpers/defineProperty'; import macro from '../../macro.js'; import { l as normalize, d as dot, g as subtract, B as projectVector, k as add } from '../../Common/Core/Math/index.js'; import vtkPlane from '../../Common/DataModel/Plane.js'; import vtkAbstractWidget from './AbstractWidget.js'; import vtkImageCroppingRegionsRepresentation from './ImageCroppingRegionsRepresentation.js'; import Constants from './ImageCroppingRegionsWidget/Constants.js'; import { i as identity } from '../../vendor/gl-matrix/esm/mat4.js'; import { t as transformMat4 } from '../../vendor/gl-matrix/esm/vec3.js'; function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } var vtkErrorMacro = macro.vtkErrorMacro, VOID = macro.VOID, EVENT_ABORT = macro.EVENT_ABORT; var TOTAL_NUM_HANDLES = Constants.TOTAL_NUM_HANDLES, WidgetState = Constants.WidgetState, CropWidgetEvents = Constants.CropWidgetEvents; // Determines the ordering of edge handles for some fixed axis var EDGE_ORDER = [[0, 0], [0, 1], [1, 0], [1, 1]]; // ---------------------------------------------------------------------------- // vtkImageCroppingRegionsWidget methods // ---------------------------------------------------------------------------- function arrayEquals(a, b) { if (a.length === b.length) { for (var i = 0; i < a.length; ++i) { if (a[i] !== b[i]) { return false; } } return true; } return false; } function vtkImageCroppingRegionsWidget(publicAPI, model) { // Set our className model.classHierarchy.push('vtkImageCroppingRegionsWidget'); // camera subscription var cameraSub = null; model.indexToWorld = identity(new Float64Array(16)); model.worldToIndex = identity(new Float64Array(16)); var handlesCache = null; model.widgetState = { activeHandleIndex: -1, // index space: xmin, xmax, ymin, ymax, zmin, zmax planes: Array(6).fill(0), controlState: WidgetState.IDLE }; function worldToIndex(ain) { var vout = []; transformMat4(vout, ain, model.worldToIndex); return vout; } function indexToWorld(ain) { var vout = []; transformMat4(vout, ain, model.indexToWorld); return vout; } // Overridden method publicAPI.createDefaultRepresentation = function () { if (!model.widgetRep) { model.widgetRep = vtkImageCroppingRegionsRepresentation.newInstance(); publicAPI.updateRepresentation(); } }; publicAPI.getWidgetState = function () { return _objectSpread({}, model.widgetState); }; publicAPI.updateWidgetState = function (state) { var needsUpdate = Object.keys(state).reduce(function (flag, key) { return flag || model.widgetState[key] !== state[key]; }, false); if (needsUpdate) { var oldState = model.widgetState; model.widgetState = _objectSpread(_objectSpread({}, oldState), state); if (!arrayEquals(oldState.planes, model.widgetState.planes)) { // invalidate handles cache handlesCache = null; publicAPI.invokeCroppingPlanesChanged(model.widgetState.planes); } publicAPI.updateRepresentation(); publicAPI.modified(); } }; publicAPI.setVolumeMapper = function (volumeMapper) { if (volumeMapper !== model.volumeMapper) { model.volumeMapper = volumeMapper; publicAPI.resetWidgetState(); if (model.enabled) { publicAPI.updateRepresentation(); } } }; publicAPI.planesToHandles = function (planes) { if (!model.volumeMapper || !model.volumeMapper.getInputData()) { return null; } if (handlesCache) { return handlesCache; } // coords are in world space. // a null handle means it is disabled var handles = Array(TOTAL_NUM_HANDLES).fill(null); if (model.faceHandlesEnabled) { var _loop = function _loop(i) { var center = [0, 0, 0].map(function (c, j) { if (j === Math.floor(i / 2)) { return planes[i]; } return (planes[j * 2] + planes[j * 2 + 1]) / 2; }); handles[i] = [center[0], center[1], center[2]]; }; // construct face handles for (var i = 0; i < 6; ++i) { _loop(i); } } if (model.edgeHandlesEnabled) { // construct edge handles for (var _i = 0; _i < 12; ++_i) { // the axis around which edge handles will be placed var fixedAxis = Math.floor(_i / 4); var edgeSpec = EDGE_ORDER[_i % 4].slice(); var center = []; for (var j = 0; j < 3; ++j) { if (j !== fixedAxis) { // edgeSpec[j] determines whether to pick a min or max cropping // plane for edge selection. center.push(planes[j * 2 + edgeSpec.shift()]); } } // set fixed axis coordinate center.splice(fixedAxis, 0, (planes[fixedAxis * 2] + planes[fixedAxis * 2 + 1]) / 2); handles[_i + 6] = [center[0], center[1], center[2]]; } } if (model.cornerHandlesEnabled) { // construct corner handles for (var _i2 = 0; _i2 < 8; ++_i2) { /* eslint-disable no-bitwise */ handles[_i2 + 18] = [planes[0 + (_i2 >> 2 & 0x1)], planes[2 + (_i2 >> 1 & 0x1)], planes[4 + (_i2 >> 0 & 0x1)]]; /* eslint-enable no-bitwise */ } } // transform handles from index to world space for (var _i3 = 0; _i3 < handles.length; ++_i3) { if (handles[_i3]) { handles[_i3] = indexToWorld(handles[_i3]); } } handlesCache = handles; return handles; }; publicAPI.planesToBBoxCorners = function (planes) { if (!model.volumeMapper || !model.volumeMapper.getInputData()) { return null; } return [[planes[0], planes[2], planes[4]], [planes[0], planes[2], planes[5]], [planes[0], planes[3], planes[4]], [planes[0], planes[3], planes[5]], [planes[1], planes[2], planes[4]], [planes[1], planes[2], planes[5]], [planes[1], planes[3], planes[4]], [planes[1], planes[3], planes[5]]].map(function (coord) { return indexToWorld(coord); }); }; publicAPI.resetWidgetState = function () { if (!model.volumeMapper) { vtkErrorMacro('Volume mapper must be set to update representation'); return; } if (!model.volumeMapper.getInputData()) { vtkErrorMacro('Volume mapper has no input data'); return; } var data = model.volumeMapper.getInputData(); // cache transforms model.indexToWorld = data.getIndexToWorld(); model.worldToIndex = data.getWorldToIndex(); var planes = data.getExtent(); publicAPI.setCroppingPlanes.apply(publicAPI, _toConsumableArray(planes)); }; publicAPI.setEnabled = macro.chain(publicAPI.setEnabled, function (enable) { if (cameraSub) { cameraSub.unsubscribe(); } if (enable) { var camera = publicAPI.getInteractor().getCurrentRenderer().getActiveCamera(); cameraSub = camera.onModified(publicAPI.updateRepresentation); publicAPI.updateRepresentation(); } }); publicAPI.setFaceHandlesEnabled = function (enabled) { if (model.faceHandlesEnabled !== enabled) { model.faceHandlesEnabled = enabled; publicAPI.updateRepresentation(); publicAPI.modified(); } }; publicAPI.setEdgeHandlesEnabled = function (enabled) { if (model.edgeHandlesEnabled !== enabled) { model.edgeHandlesEnabled = enabled; publicAPI.updateRepresentation(); publicAPI.modified(); } }; publicAPI.setCornerHandlesEnabled = function (enabled) { if (model.cornerHandlesEnabled !== enabled) { model.cornerHandlesEnabled = enabled; publicAPI.updateRepresentation(); publicAPI.modified(); } }; publicAPI.setHandleSize = function (size) { if (model.handleSize !== size) { model.handleSize = size; publicAPI.updateRepresentation(); publicAPI.modified(); } }; publicAPI.getCroppingPlanes = function () { return model.widgetState.planes.slice(); }; publicAPI.setCroppingPlanes = function () { for (var _len = arguments.length, planes = new Array(_len), _key = 0; _key < _len; _key++) { planes[_key] = arguments[_key]; } publicAPI.updateWidgetState({ planes: planes }); }; publicAPI.updateRepresentation = function () { if (model.widgetRep) { var _model$widgetRep; var bounds = model.volumeMapper.getBounds(); (_model$widgetRep = model.widgetRep).placeWidget.apply(_model$widgetRep, _toConsumableArray(bounds)); var _model$widgetState = model.widgetState, activeHandleIndex = _model$widgetState.activeHandleIndex, planes = _model$widgetState.planes; var bboxCorners = publicAPI.planesToBBoxCorners(planes); var handlePositions = publicAPI.planesToHandles(planes); var handleSizes = handlePositions.map(function (handle) { if (!handle) { return model.handleSize; } return publicAPI.adjustHandleSize(handle, model.handleSize); }); model.widgetRep.set({ activeHandleIndex: activeHandleIndex, handlePositions: handlePositions, bboxCorners: bboxCorners, handleSizes: handleSizes }); publicAPI.render(); } }; publicAPI.adjustHandleSize = function (pos, size) { var interactor = publicAPI.getInteractor(); if (!interactor && !interactor.getCurrentRenderer()) { return null; } var renderer = interactor.getCurrentRenderer(); if (!renderer.getActiveCamera()) { return null; } var worldCoords = publicAPI.computeWorldToDisplay(renderer, pos[0], pos[1], pos[2]); var lowerLeft = publicAPI.computeDisplayToWorld(renderer, worldCoords[0] - size / 2.0, worldCoords[1] - size / 2.0, worldCoords[2]); var upperRight = publicAPI.computeDisplayToWorld(renderer, worldCoords[0] + size / 2.0, worldCoords[1] + size / 2.0, worldCoords[2]); var radius = 0.0; for (var i = 0; i < 3; i++) { radius += (upperRight[i] - lowerLeft[i]) * (upperRight[i] - lowerLeft[i]); } return Math.sqrt(radius) / 2.0; }; // Given display coordinates and a plane, returns the // point on the plane that corresponds to display coordinates. publicAPI.displayToPlane = function (displayCoords, planePoint, planeNormal) { var view = publicAPI.getInteractor().getView(); var renderer = publicAPI.getInteractor().getCurrentRenderer(); var camera = renderer.getActiveCamera(); var cameraFocalPoint = camera.getFocalPoint(); var cameraPos = camera.getPosition(); // Adapted from vtkPicker var focalPointDispCoords = view.worldToDisplay.apply(view, _toConsumableArray(cameraFocalPoint).concat([renderer])); var worldCoords = view.displayToWorld(displayCoords[0], displayCoords[1], focalPointDispCoords[2], // Use focal point for z coord renderer); // compute ray from camera to selection var ray = [0, 0, 0]; for (var i = 0; i < 3; ++i) { ray[i] = worldCoords[i] - cameraPos[i]; } var dop = camera.getDirectionOfProjection(); normalize(dop); var rayLength = dot(dop, ray); var clipRange = camera.getClippingRange(); var p1World = [0, 0, 0]; var p2World = [0, 0, 0]; // get line segment coords from ray based on clip range if (camera.getParallelProjection()) { var tF = clipRange[0] - rayLength; var tB = clipRange[1] - rayLength; for (var _i4 = 0; _i4 < 3; _i4++) { p1World[_i4] = worldCoords[_i4] + tF * dop[_i4]; p2World[_i4] = worldCoords[_i4] + tB * dop[_i4]; } } else { var _tF = clipRange[0] / rayLength; var _tB = clipRange[1] / rayLength; for (var _i5 = 0; _i5 < 3; _i5++) { p1World[_i5] = cameraPos[_i5] + _tF * ray[_i5]; p2World[_i5] = cameraPos[_i5] + _tB * ray[_i5]; } } var r = vtkPlane.intersectWithLine(p1World, p2World, planePoint, planeNormal); return r.intersection ? r.x : null; }; publicAPI.handleLeftButtonPress = function (callData) { return publicAPI.pressAction(callData); }; publicAPI.handleLeftButtonRelease = function (callData) { return publicAPI.endMoveAction(callData); }; publicAPI.handleMiddleButtonPress = function (callData) { return publicAPI.pressAction(callData); }; publicAPI.handleMiddleButtonRelease = function (callData) { return publicAPI.endMoveAction(callData); }; publicAPI.handleRightButtonPress = function (callData) { return publicAPI.pressAction(callData); }; publicAPI.handleRightButtonRelease = function (callData) { return publicAPI.endMoveAction(callData); }; publicAPI.handleMouseMove = function (callData) { return publicAPI.moveAction(callData); }; publicAPI.pressAction = function (callData) { if (model.widgetState.controlState === WidgetState.IDLE) { var handleIndex = model.widgetRep.getEventIntersection(callData); if (handleIndex > -1) { model.activeHandleIndex = handleIndex; publicAPI.updateWidgetState({ activeHandleIndex: handleIndex, controlState: WidgetState.CROPPING }); return EVENT_ABORT; } } return VOID; }; publicAPI.moveAction = function (callData) { var _model$widgetState2 = model.widgetState, controlState = _model$widgetState2.controlState, planes = _model$widgetState2.planes, activeHandleIndex = _model$widgetState2.activeHandleIndex; if (controlState === WidgetState.IDLE || activeHandleIndex === -1) { return VOID; } var handles = publicAPI.planesToHandles(planes); var mouse = [callData.position.x, callData.position.y]; var handlePos = handles[activeHandleIndex]; var renderer = publicAPI.getInteractor().getCurrentRenderer(); var camera = renderer.getActiveCamera(); var dop = camera.getDirectionOfProjection(); var point = publicAPI.displayToPlane(mouse, handlePos, dop); if (!point) { return EVENT_ABORT; } var newPlanes = planes.slice(); // activeHandleIndex should be > -1 here if (activeHandleIndex < 6) { // face handle, so constrain to axis var moveAxis = Math.floor(activeHandleIndex / 2); // Constrain point to axis var orientation = model.volumeMapper.getInputData().getDirection(); var offset = moveAxis * 3; var constraintAxis = orientation.slice(offset, offset + 3); var newPos = [0, 0, 0]; var relMoveVect = [0, 0, 0]; var projection = [0, 0, 0]; subtract(point, handlePos, relMoveVect); projectVector(relMoveVect, constraintAxis, projection); add(handlePos, projection, newPos); var indexHandle = worldToIndex(newPos); // set correct plane value newPlanes[activeHandleIndex] = indexHandle[moveAxis]; } else if (activeHandleIndex < 18) { // edge handle, so constrain to plane var edgeHandleIndex = activeHandleIndex - 6; var fixedAxis = Math.floor(edgeHandleIndex / 4); /** * edgeHandleIndex: plane, plane * 4: xmin, zmin * 5: xmin, zmax * 6: xmax, zmin * 7: xmax, zmax * 8: xmin, ymin * 9: xmin, ymax * 10: xmax, ymin * 11: xmax, ymax */ var _orientation = model.volumeMapper.getInputData().getDirection(); var _offset = fixedAxis * 3; var constraintPlaneNormal = _orientation.slice(_offset, _offset + 3); var _newPos = [0, 0, 0]; var _relMoveVect = [0, 0, 0]; var _projection = [0, 0, 0]; subtract(point, handlePos, _relMoveVect); vtkPlane.projectVector(_relMoveVect, constraintPlaneNormal, _projection); add(handlePos, _projection, _newPos); var _indexHandle = worldToIndex(_newPos); // get the two planes that are being adjusted var edgeSpec = EDGE_ORDER[edgeHandleIndex % 4].slice(); var modifiedPlanes = []; for (var i = 0; i < 3; ++i) { if (i !== fixedAxis) { modifiedPlanes.push(i * 2 + edgeSpec.shift()); } } // set correct plane value modifiedPlanes.forEach(function (planeIndex) { // Math.floor(planeIndex / 2) is the corresponding changed // coordinate (that dictates the plane position) newPlanes[planeIndex] = _indexHandle[Math.floor(planeIndex / 2)]; }); } else { // corner handles, so no constraints var cornerHandleIndex = activeHandleIndex - 18; var _indexHandle2 = worldToIndex(point); // get the three planes that are being adjusted /* eslint-disable no-bitwise */ var _modifiedPlanes = [0 + (cornerHandleIndex >> 2 & 0x1), 2 + (cornerHandleIndex >> 1 & 0x1), 4 + (cornerHandleIndex >> 0 & 0x1)]; /* eslint-enable no-bitwise */ // set correct plane value _modifiedPlanes.forEach(function (planeIndex) { // Math.floor(planeIndex / 2) is the corresponding changed // coordinate (that dictates the plane position) newPlanes[planeIndex] = _indexHandle2[Math.floor(planeIndex / 2)]; }); } publicAPI.setCroppingPlanes.apply(publicAPI, _toConsumableArray(newPlanes)); return EVENT_ABORT; }; publicAPI.endMoveAction = function () { if (model.widgetState.activeHandleIndex > -1) { publicAPI.updateWidgetState({ activeHandleIndex: -1, controlState: WidgetState.IDLE }); } }; } // ---------------------------------------------------------------------------- // Object factory // ---------------------------------------------------------------------------- var DEFAULT_VALUES = { // volumeMapper: null, handleSize: 5, faceHandlesEnabled: false, edgeHandlesEnabled: false, cornerHandlesEnabled: true }; // ---------------------------------------------------------------------------- function extend(publicAPI, model) { var initialValues = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; Object.assign(model, DEFAULT_VALUES, initialValues); // Inheritance // Have our default values override whatever is from parent class vtkAbstractWidget.extend(publicAPI, model, DEFAULT_VALUES, initialValues); CropWidgetEvents.forEach(function (eventName) { return macro.event(publicAPI, model, eventName); }); macro.get(publicAPI, model, ['volumeMapper', 'handleSize', 'faceHandlesEnabled', 'edgeHandlesEnabled', 'cornerHandlesEnabled']); // Object methods vtkImageCroppingRegionsWidget(publicAPI, model); } // ---------------------------------------------------------------------------- var newInstance = macro.newInstance(extend, 'vtkImageCroppingRegionsWidget'); // ---------------------------------------------------------------------------- var vtkImageCroppingRegionsWidget$1 = { newInstance: newInstance, extend: extend }; export default vtkImageCroppingRegionsWidget$1; export { extend, newInstance };