import macro, { vtkWarningMacro } from 'vtk.js/Sources/macros'; import vtkCompositeMouseManipulator from 'vtk.js/Sources/Interaction/Manipulators/CompositeMouseManipulator';
function vtkMouseRangeManipulator(publicAPI, model) { model.classHierarchy.push('vtkMouseRangeManipulator');
const incrementalDelta = new Map();
function scaleDeltaToRange(listener, normalizedDelta) { return ( normalizedDelta * ((listener.max - listener.min) / (listener.step + 1)) ); }
function processDelta(listener, delta) { const oldValue = listener.getValue();
let scalingFactor = listener.exponentialScroll ? listener.scale ** Math.log2(Math.abs(model.interactionNetDelta) + 2) : listener.scale;
scalingFactor = Math.abs(scalingFactor) * Math.sign(listener.scale);
const newDelta = delta * scalingFactor + incrementalDelta.get(listener);
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) { listener.setValue(value); incrementalDelta.set(listener, 0); } else if ( (value === listener.min && newDelta < 0) || (value === listener.max && newDelta > 0) ) { incrementalDelta.set(listener, 0); } else { incrementalDelta.set(listener, newDelta); } }
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(); const ratio = glRenderWindow.getContainerSize()[0] / glRenderWindow.getSize()[0]; const size = glRenderWindow.getViewportSize(renderer); model.containerSize = size.map((v) => v * ratio); };
publicAPI.onButtonUp = (interactor) => { interactor.exitPointerLock(); };
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) => { if (!interactor.isPointerLocked()) return;
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; }
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; }; }
const DEFAULT_VALUES = { horizontalListener: null, verticalListener: null, scrollListener: null, };
export function extend(publicAPI, model, initialValues = {}) { Object.assign(model, DEFAULT_VALUES, initialValues);
macro.obj(publicAPI, model); vtkCompositeMouseManipulator.extend(publicAPI, model, initialValues);
macro.setGet(publicAPI, model, ['usePointerLock']);
vtkMouseRangeManipulator(publicAPI, model); }
export const newInstance = macro.newInstance( extend, 'vtkMouseRangeManipulator' );
export default { newInstance, extend };
|