UNPKG

@kitware/vtk.js

Version:

Visualization Toolkit for the Web

288 lines (252 loc) 11.3 kB
import { m as macro, v as vtkWarningMacro } from '../../macros2.js'; import vtkCompositeMouseManipulator from './CompositeMouseManipulator.js'; // ---------------------------------------------------------------------------- // vtkMouseRangeManipulator methods // ---------------------------------------------------------------------------- function vtkMouseRangeManipulator(publicAPI, model) { // Set our className model.classHierarchy.push('vtkMouseRangeManipulator'); // Keep track of delta that is below the value // of one step to progressively increment it const incrementalDelta = new Map(); // Internal methods //------------------------------------------------------------------------- function scaleDeltaToRange(listener, normalizedDelta) { return normalizedDelta * ((listener.max - listener.min) / (listener.step + 1)); } //------------------------------------------------------------------------- function processDelta(listener, delta) { const oldValue = listener.getValue(); // if exponential scroll is enabled, we raise our scale to the // exponent of the net delta of the interaction. The further away // the user's cursor is from the start of the interaction, the more // their movements will effect the value. let scalingFactor = listener.exponentialScroll ? listener.scale ** Math.log2(Math.abs(model.interactionNetDelta) + 2) : listener.scale; // Preserve the sign of scale (which can be used to invert the scrolling direction) // after the power operation above (in case of exponentialScroll). scalingFactor = Math.abs(scalingFactor) * Math.sign(listener.scale); const newDelta = delta * scalingFactor + incrementalDelta.get(listener); // Compute new value based on step // In the following line, Math.abs is required so that the floor function // consistently rounds to the lowest absolute integer. const stepsToDifference = Math.floor(Math.abs(newDelta / listener.step)); let value = oldValue + listener.step * stepsToDifference * Math.sign(newDelta); value = Math.max(value, listener.min); value = Math.min(value, listener.max); if (value !== oldValue) { // Update value listener.setValue(value); incrementalDelta.set(listener, 0); } else if (value === listener.min && newDelta < 0 || value === listener.max && newDelta > 0) { // Do not allow incremental delta to go past range incrementalDelta.set(listener, 0); } else { // Store delta for the next iteration incrementalDelta.set(listener, newDelta); } } // Public API methods // min:number = minimum allowable value // max:number = maximum allowable value // step:number = value per step -- smaller = more steps over a given distance, larger = fewer steps over a given distance // getValue:fn = function that returns current value // setValue:fn = function to set value // scale:number = scale value is applied to mouse event to allow users accelerate or decelerate delta without emitting more events //------------------------------------------------------------------------- publicAPI.setHorizontalListener = (min, max, step, getValue, setValue, scale = 1, exponentialScroll = false) => { const getFn = Number.isFinite(getValue) ? () => getValue : getValue; model.horizontalListener = { min, max, step, getValue: getFn, setValue, scale, exponentialScroll }; incrementalDelta.set(model.horizontalListener, 0); publicAPI.modified(); }; //------------------------------------------------------------------------- publicAPI.setVerticalListener = (min, max, step, getValue, setValue, scale = 1, exponentialScroll = false) => { const getFn = Number.isFinite(getValue) ? () => getValue : getValue; model.verticalListener = { min, max, step, getValue: getFn, setValue, scale, exponentialScroll }; incrementalDelta.set(model.verticalListener, 0); publicAPI.modified(); }; //------------------------------------------------------------------------- publicAPI.setScrollListener = (min, max, step, getValue, setValue, scale = 1, exponentialScroll = false) => { if (step < 0) { vtkWarningMacro('Value of step cannot be negative. If you want to invert the scrolling direction, use a negative scale value instead.'); } const stepSize = Math.abs(step); const getFn = Number.isFinite(getValue) ? () => getValue : getValue; model.scrollListener = { min, max, step: stepSize, getValue: getFn, setValue, scale, exponentialScroll }; incrementalDelta.set(model.scrollListener, 0); publicAPI.modified(); }; //------------------------------------------------------------------------- publicAPI.removeHorizontalListener = () => { if (model.horizontalListener) { incrementalDelta.delete(model.horizontalListener); delete model.horizontalListener; publicAPI.modified(); } }; //------------------------------------------------------------------------- publicAPI.removeVerticalListener = () => { if (model.verticalListener) { incrementalDelta.delete(model.verticalListener); delete model.verticalListener; publicAPI.modified(); } }; //------------------------------------------------------------------------- publicAPI.removeScrollListener = () => { if (model.scrollListener) { incrementalDelta.delete(model.scrollListener); delete model.scrollListener; publicAPI.modified(); } }; //------------------------------------------------------------------------- publicAPI.removeAllListeners = () => { publicAPI.removeHorizontalListener(); publicAPI.removeVerticalListener(); publicAPI.removeScrollListener(); }; //------------------------------------------------------------------------- publicAPI.onButtonDown = (interactor, renderer, position) => { model.previousPosition = position; model.interactionNetDelta = 0; const glRenderWindow = interactor.getView(); // Ratio is the dom size vs renderwindow size const ratio = glRenderWindow.getContainerSize()[0] / glRenderWindow.getSize()[0]; // Get proper pixel range used by viewport in rw size space const size = glRenderWindow.getViewportSize(renderer); // rescale size to match mouse event position model.containerSize = size.map(v => v * ratio); }; publicAPI.onButtonUp = interactor => { interactor.exitPointerLock(); }; //-------------------------------------------------------------------------- // TODO: at some point, this should perhaps be done in // RenderWindowInteractor instead of here. // We need to hook into mousemove directly for two reasons: // 1. We need to keep receiving mouse move events after the mouse button // is released. This is currently not possible with // vtkInteractorStyleManipulator. // 2. Since the mouse is stationary in pointer lock mode, we need the // event.movementX and event.movementY info, which are not currently // passed via interactor.onMouseMove. publicAPI.startPointerLockEvent = (interactor, renderer) => { const handlePointerLockMove = event => { publicAPI.onPointerLockMove(interactor, renderer, event); }; document.addEventListener('mousemove', handlePointerLockMove); let subscription = null; const endInteraction = () => { document.removeEventListener('mousemove', handlePointerLockMove); subscription?.unsubscribe(); }; subscription = interactor?.onEndPointerLock(endInteraction); }; publicAPI.onPointerLockMove = (interactor, renderer, event) => { // There is a slight delay between the `onEndPointerLock` call // and the last `onMouseMove` event, we must make sure the pointer // is still locked before we run this logic otherwise we may // get a `onMouseMove` call after the pointer has been unlocked. if (!interactor.isPointerLocked()) return; // previousPosition could be undefined if for some reason the // `startPointerLockEvent` method is called before the `onButtonDown` one. if (model.previousPosition == null) return; model.previousPosition.x += event.movementX; model.previousPosition.y += event.movementY; publicAPI.onMouseMove(interactor, renderer, model.previousPosition); }; //------------------------------------------------------------------------- publicAPI.onMouseMove = (interactor, renderer, position) => { if (!model.verticalListener && !model.horizontalListener) { return; } // We only want to initialize the pointer lock listener // after the user starts moving their mouse, this way // we don't interfere with other events such as doubleClick, // for this reason we don't call this from `onButtonDown` if (model.usePointerLock && !interactor.isPointerLocked()) { interactor.requestPointerLock(); publicAPI.startPointerLockEvent(interactor, renderer); } if (!position) { return; } if (model.horizontalListener) { const dxNorm = (position.x - model.previousPosition.x) / model.containerSize[0]; const dx = scaleDeltaToRange(model.horizontalListener, dxNorm); model.interactionNetDelta += dx; processDelta(model.horizontalListener, dx); } if (model.verticalListener) { const dyNorm = (position.y - model.previousPosition.y) / model.containerSize[1]; const dy = scaleDeltaToRange(model.verticalListener, dyNorm); model.interactionNetDelta += dy; processDelta(model.verticalListener, dy); } model.previousPosition = position; }; //------------------------------------------------------------------------- publicAPI.onScroll = (interactor, renderer, delta) => { if (!model.scrollListener || !delta) { return; } model.interactionNetDelta += delta * model.scrollListener.step; processDelta(model.scrollListener, delta * model.scrollListener.step); }; publicAPI.onStartScroll = () => { model.interactionNetDelta = 0; }; } // ---------------------------------------------------------------------------- // Object factory // ---------------------------------------------------------------------------- const DEFAULT_VALUES = { horizontalListener: null, verticalListener: null, scrollListener: null }; // ---------------------------------------------------------------------------- function extend(publicAPI, model, initialValues = {}) { Object.assign(model, DEFAULT_VALUES, initialValues); // Inheritance macro.obj(publicAPI, model); vtkCompositeMouseManipulator.extend(publicAPI, model, initialValues); // Create get-set macros macro.setGet(publicAPI, model, ['usePointerLock']); // Object specific methods vtkMouseRangeManipulator(publicAPI, model); } // ---------------------------------------------------------------------------- const newInstance = macro.newInstance(extend, 'vtkMouseRangeManipulator'); // ---------------------------------------------------------------------------- var vtkMouseRangeManipulator$1 = { newInstance, extend }; export { vtkMouseRangeManipulator$1 as default, extend, newInstance };