UNPKG

@thewtex/vtk.js-esm

Version:

Visualization Toolkit for the Web

310 lines (258 loc) 12.3 kB
import _toConsumableArray from '@babel/runtime/helpers/toConsumableArray'; import macro from '../../../macro.js'; import vtkBoundingBox from '../../../Common/DataModel/BoundingBox.js'; import vtkLine from '../../../Common/DataModel/Line.js'; import vtkPlanePointManipulator from '../../Manipulators/PlaneManipulator.js'; import { t as multiplyScalar, g as subtract, l as normalize, d as dot, j as cross, Q as multiplyAccumulate, S as signedAngleBetweenVectors } from '../../../Common/Core/Math/index.js'; import { updateState, getAssociatedLinesName, boundPointOnPlane, rotateVector } from './helpers.js'; import { InteractionMethodsName, ScrollingMethods } from './Constants.js'; function widgetBehavior(publicAPI, model) { var isDragging = null; var isScrolling = false; // Reset "updateMethodName" attribute when no actors are selected // Useful to update 'updateMethodeName' to the correct name which // will be TranslateCenter by default publicAPI.resetUpdateMethod = function () { if (model.representations.length !== 0) { model.representations[0].getSelectedState(); } }; publicAPI.startScrolling = function (newPosition) { if (newPosition) { model.previousPosition = newPosition; } isScrolling = true; publicAPI.resetUpdateMethod(); publicAPI.startInteraction(); }; publicAPI.endScrolling = function () { isScrolling = false; publicAPI.endInteraction(); }; publicAPI.updateCursor = function () { switch (model.activeState.getUpdateMethodName()) { case InteractionMethodsName.TranslateCenter: model.apiSpecificRenderWindow.setCursor('move'); break; case InteractionMethodsName.RotateLine: model.apiSpecificRenderWindow.setCursor('alias'); break; case InteractionMethodsName.TranslateAxis: model.apiSpecificRenderWindow.setCursor('pointer'); break; default: model.apiSpecificRenderWindow.setCursor('default'); break; } }; publicAPI.handleLeftButtonPress = function (callData) { if (model.activeState && model.activeState.getActive()) { isDragging = true; var viewType = model.widgetState.getActiveViewType(); var currentPlaneNormal = model.widgetState.getPlanes()[viewType].normal; model.planeManipulator.setOrigin(model.widgetState.getCenter()); model.planeManipulator.setNormal(currentPlaneNormal); publicAPI.startInteraction(); } else if (model.widgetState.getScrollingMethod() === ScrollingMethods.LEFT_MOUSE_BUTTON) { publicAPI.startScrolling(callData.position); } else { return macro.VOID; } return macro.EVENT_ABORT; }; publicAPI.handleMouseMove = function (callData) { if (isDragging && model.pickable && model.dragable) { return publicAPI.handleEvent(callData); } if (isScrolling) { if (model.previousPosition.y !== callData.position.y) { var step = model.previousPosition.y - callData.position.y; publicAPI.translateCenterOnCurrentDirection(step, callData.pokedRenderer); model.previousPosition = callData.position; publicAPI.invokeInternalInteractionEvent(); } } return macro.VOID; }; publicAPI.handleLeftButtonRelease = function () { if (isDragging || isScrolling) { publicAPI.endScrolling(); } isDragging = false; model.widgetState.deactivate(); }; publicAPI.handleRightButtonPress = function (calldata) { if (model.widgetState.getScrollingMethod() === ScrollingMethods.RIGHT_MOUSE_BUTTON) { publicAPI.startScrolling(calldata.position); } }; publicAPI.handleRightButtonRelease = function (calldata) { if (model.widgetState.getScrollingMethod() === ScrollingMethods.RIGHT_MOUSE_BUTTON) { publicAPI.endScrolling(); } }; publicAPI.handleStartMouseWheel = function (callData) { publicAPI.resetUpdateMethod(); publicAPI.startInteraction(); }; publicAPI.handleMouseWheel = function (calldata) { var step = calldata.spinY; isScrolling = true; publicAPI.translateCenterOnCurrentDirection(step, calldata.pokedRenderer); publicAPI.invokeInternalInteractionEvent(); isScrolling = false; return macro.EVENT_ABORT; }; publicAPI.handleEndMouseWheel = function (calldata) { publicAPI.endScrolling(); }; publicAPI.handleMiddleButtonPress = function (calldata) { if (model.widgetState.getScrollingMethod() === ScrollingMethods.MIDDLE_MOUSE_BUTTON) { publicAPI.startScrolling(calldata.position); } }; publicAPI.handleMiddleButtonRelease = function (calldata) { if (model.widgetState.getScrollingMethod() === ScrollingMethods.MIDDLE_MOUSE_BUTTON) { publicAPI.endScrolling(); } }; publicAPI.handleEvent = function (callData) { if (model.activeState.getActive()) { publicAPI[model.activeState.getUpdateMethodName()](callData); publicAPI.invokeInternalInteractionEvent(); return macro.EVENT_ABORT; } return macro.VOID; }; publicAPI.invokeInternalInteractionEvent = function () { var methodName = model.activeState ? model.activeState.getUpdateMethodName() : ''; var computeFocalPointOffset = methodName !== InteractionMethodsName.RotateLine; var canUpdateFocalPoint = methodName === InteractionMethodsName.RotateLine; publicAPI.invokeInteractionEvent({ computeFocalPointOffset: computeFocalPointOffset, canUpdateFocalPoint: canUpdateFocalPoint }); }; publicAPI.startInteraction = function () { publicAPI.invokeStartInteractionEvent(); // When interacting, plane actor and lines must be re-rendered on other views publicAPI.getViewWidgets().forEach(function (viewWidget) { viewWidget.getInteractor().requestAnimation(publicAPI); }); }; publicAPI.endInteraction = function () { publicAPI.invokeEndInteractionEvent(); publicAPI.getViewWidgets().forEach(function (viewWidget) { viewWidget.getInteractor().cancelAnimation(publicAPI); }); }; publicAPI.translateCenterOnCurrentDirection = function (nbSteps, renderer) { var dirProj = renderer.getRenderWindow().getRenderers()[0].getActiveCamera().getDirectionOfProjection(); // Direction of the projection is the inverse of what we want var direction = multiplyScalar(dirProj, -1); var oldCenter = model.widgetState.getCenter(); var image = model.widgetState.getImage(); var imageSpacing = image.getSpacing(); // Use Chebyshev norm // https://math.stackexchange.com/questions/71423/what-is-the-term-for-the-projection-of-a-vector-onto-the-unit-cube var absDirProj = dirProj.map(function (value) { return Math.abs(value); }); var index = absDirProj.indexOf(Math.max.apply(Math, _toConsumableArray(absDirProj))); var movingFactor = nbSteps * (imageSpacing[index] / dirProj[index]); // Define the potentially new center var newCenter = [oldCenter[0] + movingFactor * direction[0], oldCenter[1] + movingFactor * direction[1], oldCenter[2] + movingFactor * direction[2]]; newCenter = publicAPI.getBoundedCenter(newCenter); model.widgetState.setCenter(newCenter); updateState(model.widgetState); }; publicAPI[InteractionMethodsName.TranslateAxis] = function (calldata) { var stateLine = model.widgetState.getActiveLineState(); var worldCoords = model.planeManipulator.handleEvent(calldata, model.apiSpecificRenderWindow); var point1 = stateLine.getPoint1(); var point2 = stateLine.getPoint2(); // Translate the current line along the other line var otherLineName = getAssociatedLinesName(stateLine.getName()); var otherLine = model.widgetState["get".concat(otherLineName)](); var otherLineVector = subtract(otherLine.getPoint2(), otherLine.getPoint1(), []); normalize(otherLineVector); var axisTranslation = otherLineVector; var currentLineVector = subtract(point2, point1, [0, 0, 0]); normalize(currentLineVector); var dot$1 = dot(currentLineVector, otherLineVector); // lines are colinear, translate along perpendicular axis from current line if (dot$1 === 1 || dot$1 === -1) { cross(currentLineVector, model.planeManipulator.getNormal(), axisTranslation); } var closestPoint = []; vtkLine.distanceToLine(worldCoords, point1, point2, closestPoint); var translationVector = subtract(worldCoords, closestPoint, []); var translationDistance = dot(translationVector, axisTranslation); var center = model.widgetState.getCenter(); var newOrigin = multiplyAccumulate(center, axisTranslation, translationDistance, [0, 0, 0]); newOrigin = publicAPI.getBoundedCenter(newOrigin); model.widgetState.setCenter(newOrigin); updateState(model.widgetState); }; publicAPI.getBoundedCenter = function (newCenter) { var oldCenter = model.widgetState.getCenter(); var imageBounds = model.widgetState.getImage().getBounds(); if (vtkBoundingBox.containsPoint.apply(vtkBoundingBox, [imageBounds].concat(_toConsumableArray(newCenter)))) { return newCenter; } return boundPointOnPlane(newCenter, oldCenter, imageBounds); }; publicAPI[InteractionMethodsName.TranslateCenter] = function (calldata) { var worldCoords = model.planeManipulator.handleEvent(calldata, model.apiSpecificRenderWindow); worldCoords = publicAPI.getBoundedCenter(worldCoords); model.activeState.setCenter(worldCoords); updateState(model.widgetState); }; publicAPI[InteractionMethodsName.RotateLine] = function (calldata) { var activeLine = model.widgetState.getActiveLineState(); var planeNormal = model.planeManipulator.getNormal(); var worldCoords = model.planeManipulator.handleEvent(calldata, model.apiSpecificRenderWindow); var center = model.widgetState.getCenter(); var previousWorldPosition = activeLine["get".concat(model.widgetState.getActiveRotationPointName())](); var previousVectorToOrigin = [0, 0, 0]; subtract(previousWorldPosition, center, previousVectorToOrigin); normalize(previousVectorToOrigin); var currentVectorToOrigin = [0, 0, 0]; subtract(worldCoords, center, currentVectorToOrigin); normalize(currentVectorToOrigin); var radianAngle = signedAngleBetweenVectors(previousVectorToOrigin, currentVectorToOrigin, planeNormal); publicAPI.rotateLineInView(activeLine, radianAngle); }; /** * Rotate a line by a specified angle * @param {Line} line The line to rotate (e.g. getActiveLineState()) * @param {Number} radianAngle Applied angle in radian */ publicAPI.rotateLineInView = function (line, radianAngle) { var viewType = line.getViewType(); var inViewType = line.getInViewType(); var planeNormal = model.widgetState.getPlanes()[inViewType].normal; publicAPI.rotatePlane(viewType, radianAngle, planeNormal); if (model.widgetState.getKeepOrthogonality()) { var associatedLineName = getAssociatedLinesName(line.getName()); var associatedLine = model.widgetState["get".concat(associatedLineName)](); var associatedViewType = associatedLine.getViewType(); publicAPI.rotatePlane(associatedViewType, radianAngle, planeNormal); } updateState(model.widgetState); }; /** * Rotate a specified plane around an other specified plane. * @param {ViewTypes} viewType Define which plane will be rotated * @param {Number} radianAngle Applied angle in radian * @param {vec3} planeNormal Define the axis to rotate around */ publicAPI.rotatePlane = function (viewType, radianAngle, planeNormal) { var _model$widgetState$ge = model.widgetState.getPlanes()[viewType], normal = _model$widgetState$ge.normal, viewUp = _model$widgetState$ge.viewUp; var newNormal = rotateVector(normal, planeNormal, radianAngle); var newViewUp = rotateVector(viewUp, planeNormal, radianAngle); model.widgetState.getPlanes()[viewType] = { normal: newNormal, viewUp: newViewUp }; }; // -------------------------------------------------------------------------- // initialization // -------------------------------------------------------------------------- model.planeManipulator = vtkPlanePointManipulator.newInstance(); } export default widgetBehavior;