UNPKG

@thewtex/vtk.js-esm

Version:

Visualization Toolkit for the Web

539 lines (432 loc) 19.1 kB
import _toConsumableArray from '@babel/runtime/helpers/toConsumableArray'; import _defineProperty from '@babel/runtime/helpers/defineProperty'; import macro from '../../../macro.js'; import { ShapeBehavior, BehaviorCategory } from './Constants.js'; import vtkLabelRepresentation from '../../../Interaction/Widgets/LabelRepresentation.js'; import { h as distance, a as subtract, d as dot, j as add, c as cross, u as squaredDistance } 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; var EPSILON = 1e-6; function makeBoundsFromPoints(point1, point2) { return [Math.min(point1[0], point2[0]), Math.max(point1[0], point2[0]), Math.min(point1[1], point2[1]), Math.max(point1[1], point2[1]), Math.min(point1[2], point2[2]), Math.max(point1[2], point2[2])]; } function widgetBehavior(publicAPI, model) { model.classHierarchy.push('vtkShapeWidgetProp'); var superClass = _objectSpread({}, publicAPI); // -------------------------------------------------------------------------- // Display 2D // -------------------------------------------------------------------------- publicAPI.setDisplayCallback = function (callback) { return model.representations[0].setDisplayCallback(callback); }; // -------------------------------------------------------------------------- // Public methods // -------------------------------------------------------------------------- publicAPI.setModifierBehavior = function (behavior) { Object.assign(model.modifierBehavior, behavior); }; publicAPI.isBehaviorActive = function (category, flag) { return Object.keys(model.keysDown).some(function (key) { return model.keysDown[key] && model.modifierBehavior[key] && model.modifierBehavior[key][category] === flag; }); }; publicAPI.isOppositeBehaviorActive = function (category, flag) { return Object.values(ShapeBehavior[category]).some(function (flagToTry) { return flag !== flagToTry && publicAPI.isBehaviorActive(category, flagToTry); }); }; publicAPI.getActiveBehaviorFromCategory = function (category) { return Object.values(ShapeBehavior[category]).find(function (flag) { return publicAPI.isBehaviorActive(category, flag) || !publicAPI.isOppositeBehaviorActive(category, flag) && model.modifierBehavior.None[category] === flag; }); }; publicAPI.isRatioFixed = function () { return publicAPI.getActiveBehaviorFromCategory(BehaviorCategory.RATIO) === ShapeBehavior[BehaviorCategory.RATIO].FIXED; }; publicAPI.isDraggingEnabled = function () { var behavior = publicAPI.getActiveBehaviorFromCategory(BehaviorCategory.PLACEMENT); return behavior === ShapeBehavior[BehaviorCategory.PLACEMENT].DRAG || behavior === ShapeBehavior[BehaviorCategory.PLACEMENT].CLICK_AND_DRAG; }; publicAPI.isDraggingForced = function () { return publicAPI.isBehaviorActive(BehaviorCategory.PLACEMENT, ShapeBehavior[BehaviorCategory.PLACEMENT].DRAG) || model.modifierBehavior.None[BehaviorCategory.PLACEMENT] === ShapeBehavior[BehaviorCategory.PLACEMENT].DRAG; }; publicAPI.setVisibleOnFocus = function (visibleOnFocus) { model.visibleOnFocus = visibleOnFocus; }; publicAPI.setLabelTextCallback = function (callback) { model.labelTextCallback = callback; }; publicAPI.setResetAfterPointPlacement = function (reset) { model.resetAfterPointPlacement = reset; }; publicAPI.getPoint1 = function () { return model.point1; }; publicAPI.getPoint2 = function () { return model.point2; }; publicAPI.setPoints = function (point1, point2) { model.point1 = point1; model.point2 = point2; model.point1Handle.setOrigin(model.point1); model.point2Handle.setOrigin(model.point2); publicAPI.updateShapeBounds(); }; // This method is to be called to place the first point // for the first time. It is not inlined so that // the user can specify himself where the first point // is right after publicAPI.grabFocus() without waiting // for interactions. publicAPI.placePoint1 = function (point) { if (model.hasFocus) { publicAPI.setPoints(point, point); model.point1Handle.deactivate(); model.point2Handle.activate(); model.activeState = model.point2Handle; if (model.useHandles) { model.point2Handle.setVisible(true); } publicAPI.updateShapeBounds(); if (model.visibleOnFocus) { model.shapeHandle.setVisible(true); } } }; publicAPI.placePoint2 = function (point2) { if (model.hasFocus) { model.point2 = point2; model.point2Handle.setOrigin(model.point2); publicAPI.updateShapeBounds(); if (model.resetAfterPointPlacement) { publicAPI.reset(); } else { publicAPI.loseFocus(); } } }; publicAPI.setPixelScale = function (pixelScale) { model.pixelScale = pixelScale; publicAPI.updateHandlesSize(); }; publicAPI.updateHandlesSize = function () { if (model.pixelScale !== null) { var scale = model.pixelScale * distance(model.apiSpecificRenderWindow.displayToWorld(0, 0, 0, model.renderer), model.apiSpecificRenderWindow.displayToWorld(1, 0, 0, model.renderer)); model.point1Handle.setScale1(scale); model.point2Handle.setScale1(scale); } }; publicAPI.setVisibility = function (visibility) { var modified = false; if (model.useHandles) { modified = superClass.setVisibility(visibility) || modified; } else { modified = model.shapeHandle.setVisible(visibility) || modified; } if (model.label) { if (visibility) { modified = model.label.setContainer(model.interactor.getContainer()) || modified; } else { modified = model.label.setContainer(null) || modified; } } return modified; }; publicAPI.getLabel = function () { return model.label; }; // -------------------------------------------------------------------------- // Private methods // -------------------------------------------------------------------------- publicAPI.makeSquareFromPoints = function (point1, point2) { var diagonal = [0, 0, 0]; subtract(diagonal, point2, point1); var dir = model.shapeHandle.getDirection(); var right = model.shapeHandle.getRight(); var up = model.shapeHandle.getUp(); var dirComponent = dot(diagonal, dir); var rightComponent = dot(diagonal, right); var upComponent = dot(diagonal, up); var absRightComponent = Math.abs(rightComponent); var absUpComponent = Math.abs(upComponent); if (absRightComponent < EPSILON) { rightComponent = upComponent; } else if (absUpComponent < EPSILON) { upComponent = rightComponent; } else if (absRightComponent > absUpComponent) { upComponent = upComponent / absUpComponent * absRightComponent; } else { rightComponent = rightComponent / absRightComponent * absUpComponent; } return [point1[0] + rightComponent * right[0] + upComponent * up[0] + dirComponent * dir[0], point1[1] + rightComponent * right[1] + upComponent * up[1] + dirComponent * dir[1], point1[2] + rightComponent * right[2] + upComponent * up[2] + dirComponent * dir[2]]; }; var getCornersFromRadius = function getCornersFromRadius(center, pointOnCircle) { var radius = distance(center, pointOnCircle); var up = model.shapeHandle.getUp(); var right = model.shapeHandle.getRight(); var point1 = [center[0] + (up[0] - right[0]) * radius, center[1] + (up[1] - right[1]) * radius, center[2] + (up[2] - right[2]) * radius]; var point2 = [center[0] + (right[0] - up[0]) * radius, center[1] + (right[1] - up[1]) * radius, center[2] + (right[2] - up[2]) * radius]; return { point1: point1, point2: point2 }; }; var getCornersFromDiameter = function getCornersFromDiameter(point1, point2) { var center = [0.5 * (point1[0] + point2[0]), 0.5 * (point1[1] + point2[1]), 0.5 * (point1[2] + point2[2])]; return getCornersFromRadius(center, point1); }; publicAPI.setCorners = function (point1, point2) { if (model.label && model.labelTextCallback) { var _model$apiSpecificRen, _model$apiSpecificRen2; var bounds = makeBoundsFromPoints(point1, point2); var screenPoint1 = (_model$apiSpecificRen = model.apiSpecificRenderWindow).worldToDisplay.apply(_model$apiSpecificRen, _toConsumableArray(point1).concat([model.renderer])); var screenPoint2 = (_model$apiSpecificRen2 = model.apiSpecificRenderWindow).worldToDisplay.apply(_model$apiSpecificRen2, _toConsumableArray(point2).concat([model.renderer])); var screenBounds = [Math.min(screenPoint1[0], screenPoint2[0]), Math.max(screenPoint1[0], screenPoint2[0]), Math.min(screenPoint1[1], screenPoint2[1]), Math.max(screenPoint1[1], screenPoint2[1]), Math.min(screenPoint1[2], screenPoint2[2]), Math.max(screenPoint1[2], screenPoint2[2])]; model.labelTextCallback(bounds, screenBounds, model.label); } }; publicAPI.updateShapeBounds = function () { if (model.point1 && model.point2) { var point1 = _toConsumableArray(model.point1); var point2 = _toConsumableArray(model.point2); if (publicAPI.isRatioFixed()) { point2 = publicAPI.makeSquareFromPoints(point1, point2); } switch (publicAPI.getActiveBehaviorFromCategory(BehaviorCategory.POINTS)) { case ShapeBehavior[BehaviorCategory.POINTS].CORNER_TO_CORNER: { publicAPI.setCorners(point1, point2); break; } case ShapeBehavior[BehaviorCategory.POINTS].CENTER_TO_CORNER: { var diagonal = [0, 0, 0]; subtract(diagonal, point1, point2); add(point1, point1, diagonal); publicAPI.setCorners(point1, point2); break; } case ShapeBehavior[BehaviorCategory.POINTS].RADIUS: { var points = getCornersFromRadius(point1, point2); publicAPI.setCorners(points.point1, points.point2); break; } case ShapeBehavior[BehaviorCategory.POINTS].DIAMETER: { var _points = getCornersFromDiameter(point1, point2); publicAPI.setCorners(_points.point1, _points.point2); break; } default: // This should never be executed vtkErrorMacro('vtk internal error'); } } }; /* * If the widget has the focus, this method reset the widget * to it's state just after it grabbed the focus. Otherwise * it resets the widget to its state before it grabbed the focus. */ publicAPI.reset = function () { if (!model.hasFocus) { model.point1Handle.setVisible(false); } model.shapeHandle.setVisible(false); model.point1 = null; model.point2 = null; if (model.label) { model.label.setLabelText(''); } model.point1Handle.setOrigin(model.point2Handle.getOrigin()); model.point2Handle.setVisible(false); model.point2Handle.deactivate(); if (model.hasFocus) { model.point1Handle.activate(); model.activeState = model.point1Handle; } else { model.point1Handle.deactivate(); model.activeState = null; } publicAPI.updateShapeBounds(); }; // -------------------------------------------------------------------------- // Interactor events // -------------------------------------------------------------------------- publicAPI.handleMouseMove = function (callData) { if (!model.activeState || !model.activeState.getActive() || !model.pickable || !model.dragable || !model.manipulator) { return macro.VOID; } if (!model.point2) { // Update orientation to match the camera's plane // if the corners are not yet placed var normal = model.camera.getDirectionOfProjection(); var up = model.camera.getViewUp(); var right = []; cross(right, up, normal); model.shapeHandle.setUp(up); model.shapeHandle.setRight(right); model.shapeHandle.setDirection(normal); model.manipulator.setNormal(normal); } var worldCoords = model.manipulator.handleEvent(callData, model.apiSpecificRenderWindow); if (!worldCoords.length) { return macro.VOID; } if (model.hasFocus) { if (!model.point1) { model.point1Handle.setOrigin(worldCoords); } else { model.point2Handle.setOrigin(worldCoords); model.point2 = worldCoords; publicAPI.updateShapeBounds(); } } else if (model.useHandles && model.isDragging) { if (model.activeState === model.point1Handle) { model.point1Handle.setOrigin(worldCoords); model.point1 = worldCoords; } else { model.point2Handle.setOrigin(worldCoords); model.point2 = worldCoords; } publicAPI.updateShapeBounds(); publicAPI.invokeInteractionEvent(); } return model.hasFocus ? macro.EVENT_ABORT : macro.VOID; }; // -------------------------------------------------------------------------- // Left click: Add point / End interaction // -------------------------------------------------------------------------- publicAPI.handleLeftButtonPress = function (e) { if (!model.activeState || !model.activeState.getActive() || !model.pickable) { return macro.VOID; } if (model.hasFocus) { if (!model.point1) { publicAPI.placePoint1(model.point1Handle.getOrigin()); publicAPI.invokeStartInteractionEvent(); } else { publicAPI.placePoint2(model.point2Handle.getOrigin()); publicAPI.invokeInteractionEvent(); publicAPI.invokeEndInteractionEvent(); } return macro.EVENT_ABORT; } if (model.point1 && (model.activeState === model.point1Handle || model.activeState === model.point2Handle)) { model.isDragging = true; model.apiSpecificRenderWindow.setCursor('grabbing'); model.interactor.requestAnimation(publicAPI); publicAPI.invokeStartInteractionEvent(); return macro.EVENT_ABORT; } return macro.VOID; }; // -------------------------------------------------------------------------- // Left release: Maybe end interaction // -------------------------------------------------------------------------- publicAPI.handleLeftButtonRelease = function (e) { if (model.isDragging) { model.isDragging = false; model.apiSpecificRenderWindow.setCursor('pointer'); model.widgetState.deactivate(); model.interactor.cancelAnimation(publicAPI); publicAPI.invokeEndInteractionEvent(); return macro.EVENT_ABORT; } if (!model.hasFocus || !model.pickable) { return macro.VOID; } var viewSize = model.apiSpecificRenderWindow.getSize(); if (e.position.x < 0 || e.position.x > viewSize[0] - 1 || e.position.y < 0 || e.position.y > viewSize[1] - 1) { return macro.VOID; } if (model.point1) { model.point2 = model.point2Handle.getOrigin(); publicAPI.updateShapeBounds(); if (publicAPI.isDraggingEnabled()) { var distance = squaredDistance(model.point1, model.point2); var maxDistance = 100; if (distance > maxDistance || publicAPI.isDraggingForced()) { publicAPI.invokeInteractionEvent(); publicAPI.invokeEndInteractionEvent(); if (model.resetAfterPointPlacement) { publicAPI.reset(); } else { publicAPI.loseFocus(); } } } } return macro.EVENT_ABORT; }; // -------------------------------------------------------------------------- // Register key presses/releases // -------------------------------------------------------------------------- publicAPI.handleKeyDown = function (_ref) { var key = _ref.key; if (key === 'Escape') { if (model.hasFocus) { publicAPI.invokeEndInteractionEvent(); publicAPI.reset(); publicAPI.loseFocus(); } } else { model.keysDown[key] = true; } if (model.hasFocus) { if (model.point1) { model.point2 = model.point2Handle.getOrigin(); publicAPI.updateShapeBounds(); } } }; publicAPI.handleKeyUp = function (_ref2) { var key = _ref2.key; model.keysDown[key] = false; if (model.hasFocus) { if (model.point1) { model.point2 = model.point2Handle.getOrigin(); publicAPI.updateShapeBounds(); } } }; // -------------------------------------------------------------------------- // Focus API - follow mouse when widget has focus // -------------------------------------------------------------------------- publicAPI.grabFocus = function () { if (!model.hasFocus) { publicAPI.reset(); if (!model.label) { model.label = vtkLabelRepresentation.newInstance(); } model.label.setRenderer(model.renderer); model.label.buildRepresentation(); model.renderer.addViewProp(model.label); model.label.setContainer(model.interactor.getContainer()); model.point1Handle.activate(); model.activeState = model.point1Handle; if (model.useHandles) { model.point1Handle.setVisible(true); } model.shapeHandle.setVisible(false); model.interactor.requestAnimation(publicAPI); } publicAPI.updateHandlesSize(); model.hasFocus = true; }; // -------------------------------------------------------------------------- publicAPI.loseFocus = function () { if (model.hasFocus) { if (model.visibleOnFocus && !model.useHandles) { model.shapeHandle.setVisible(false); } model.interactor.cancelAnimation(publicAPI); } if (model.label && !model.useHandles) { model.label.setContainer(null); } if (!model.useHandles || !model.point1) { model.point1Handle.setVisible(false); model.point2Handle.setVisible(false); } model.widgetState.deactivate(); model.point1Handle.deactivate(); model.point2Handle.deactivate(); model.activeState = null; model.interactor.render(); model.widgetManager.enablePicking(); model.hasFocus = false; }; } export default widgetBehavior;