UNPKG

@acransac/vtk.js

Version:

Visualization Toolkit for the Web

296 lines (229 loc) 8.55 kB
import macro from 'vtk.js/Sources/macro'; import vtkBoundingBox from 'vtk.js/Sources/Common/DataModel/BoundingBox'; import vtkCompositeKeyboardManipulator from 'vtk.js/Sources/Interaction/Manipulators/CompositeKeyboardManipulator'; import * as vtkMath from 'vtk.js/Sources/Common/Core/Math'; const { vtkErrorMacro } = macro; const ANIMATION_REQUESTER = 'vtkKeyboardCameraManipulator'; // ---------------------------------------------------------------------------- // vtkKeyboardCameraManipulator methods // ---------------------------------------------------------------------------- function vtkKeyboardCameraManipulator(publicAPI, model) { // Set our className model.classHierarchy.push('vtkKeyboardCameraManipulator'); const internal = { keysDown: [], direction: [0, 0, 0], skipUpdateDirection: false, animationSub: null, cameraModifiedSub: null, }; //-------------------------------------------------------------------------- publicAPI.inMotion = () => internal.animationSub !== null; //-------------------------------------------------------------------------- publicAPI.resetMovementSpeed = () => { // Reset the movement speed to be proportional to the longest length // of the renderer's bounds. const { renderer } = model; const bounds = renderer.computeVisiblePropBounds(); // Just a number that seems to work okay for our examples... const divisor = 500; model.movementSpeed = vtkBoundingBox.getMaxLength(bounds) / divisor; }; //-------------------------------------------------------------------------- publicAPI.startMovement = () => { if (publicAPI.inMotion()) { vtkErrorMacro('Camera is already in motion!'); return; } if (model.movementSpeed === null) { publicAPI.resetMovementSpeed(); } const { interactor, renderer } = model; const move = () => { if (internal.keysDown.length === 0) { return; } // No need to update the direction when we move the camera here... internal.skipUpdateDirection = true; publicAPI.moveCamera( renderer.getActiveCamera(), internal.direction, model.movementSpeed ); renderer.resetCameraClippingRange(); if (interactor.getLightFollowCamera()) { renderer.updateLightsGeometryToFollowCamera(); } internal.skipUpdateDirection = false; }; publicAPI.calculateCurrentDirection(); const camera = renderer.getActiveCamera(); // If the camera gets modified elsewhere, let's update the direction internal.cameraModifiedSub = camera.onModified( publicAPI.calculateCurrentDirection ); interactor.requestAnimation(ANIMATION_REQUESTER); internal.animationSub = interactor.onAnimation(() => move()); }; //-------------------------------------------------------------------------- publicAPI.endMovement = () => { if (internal.animationSub) { internal.animationSub.unsubscribe(); internal.animationSub = null; } model.interactor.cancelAnimation(ANIMATION_REQUESTER); if (internal.cameraModifiedSub) { internal.cameraModifiedSub.unsubscribe(); internal.cameraModifiedSub = null; } }; //-------------------------------------------------------------------------- publicAPI.calculateCurrentDirection = () => { if (internal.skipUpdateDirection) { return; } // Reset internal.direction = [0, 0, 0]; const { renderer } = model; if (!renderer) { return; } const camera = renderer.getActiveCamera(); if (!camera) { return; } if (internal.keysDown.length === 0) { return; } let directions = internal.keysDown.map((key) => publicAPI.getDirectionFromKey(key, camera) ); directions = directions.filter((item) => item); if (directions.length === 0) { return; } const netDirection = directions.reduce((a, b) => { vtkMath.add(a, b, b); return b; }); vtkMath.normalize(netDirection); internal.direction = netDirection; }; //-------------------------------------------------------------------------- publicAPI.getDirectionFromKey = (key, camera) => { let direction; if (model.moveForwardKeys.includes(key)) { // Move forward direction = camera.getDirectionOfProjection(); } else if (model.moveLeftKeys.includes(key)) { // Move left const dirProj = camera.getDirectionOfProjection(); direction = [0, 0, 0]; vtkMath.cross(camera.getViewUp(), dirProj, direction); } else if (model.moveBackwardKeys.includes(key)) { // Move backward direction = camera.getDirectionOfProjection().map((e) => -e); } else if (model.moveRightKeys.includes(key)) { // Move right const dirProj = camera.getDirectionOfProjection(); direction = [0, 0, 0]; vtkMath.cross(dirProj, camera.getViewUp(), direction); } else if (model.moveUpKeys.includes(key)) { // Move up direction = camera.getViewUp(); } else if (model.moveDownKeys.includes(key)) { // Move down direction = camera.getViewUp().map((e) => -e); } else { return undefined; } vtkMath.normalize(direction); return direction; }; //-------------------------------------------------------------------------- publicAPI.moveCamera = (camera, direction, speed) => { const position = camera.getPosition(); const focalPoint = camera.getFocalPoint(); camera.setFocalPoint( focalPoint[0] + direction[0] * speed, focalPoint[1] + direction[1] * speed, focalPoint[2] + direction[2] * speed ); camera.setPosition( position[0] + direction[0] * speed, position[1] + direction[1] * speed, position[2] + direction[2] * speed ); }; //-------------------------------------------------------------------------- publicAPI.onKeyPress = (interactor, renderer, key) => {}; //-------------------------------------------------------------------------- publicAPI.onKeyDown = (interactor, renderer, key) => { if (!internal.keysDown.includes(key)) { internal.keysDown.push(key); publicAPI.calculateCurrentDirection(); } if (!publicAPI.inMotion()) { Object.assign(model, { interactor, renderer }); publicAPI.startMovement(); } }; //-------------------------------------------------------------------------- publicAPI.onKeyUp = (interactor, renderer, key) => { // The following is case insensitive for when the user // presses/releases the shift key while this key is down. internal.keysDown = internal.keysDown.filter( (item) => item.toUpperCase() !== key.toUpperCase() ); publicAPI.calculateCurrentDirection(); if (internal.keysDown.length === 0) { publicAPI.endMovement(); } }; } // ---------------------------------------------------------------------------- // Object factory // ---------------------------------------------------------------------------- const DEFAULT_VALUES = { // The movementSpeed is the magnitude of the camera translation // for each animation frame (which occur each 1/60 second) // If null, publicAPI.resetMovementSpeed() will be called when // movement starts. movementSpeed: null, moveForwardKeys: ['w', 'W', 'ArrowUp'], moveLeftKeys: ['a', 'A', 'ArrowLeft'], moveBackwardKeys: ['s', 'S', 'ArrowDown'], moveRightKeys: ['d', 'D', 'ArrowRight'], moveUpKeys: [' '], moveDownKeys: ['Shift'], interactor: null, renderer: null, }; // ---------------------------------------------------------------------------- export function extend(publicAPI, model, initialValues = {}) { Object.assign(model, DEFAULT_VALUES, initialValues); // Inheritance macro.obj(publicAPI, model); vtkCompositeKeyboardManipulator.extend(publicAPI, model, initialValues); // Create get-set macros macro.setGet(publicAPI, model, [ 'movementSpeed', 'moveForwardKeys', 'moveLeftKeys', 'moveBackwardKeys', 'moveRightKeys', 'moveUpKeys', 'moveDownKeys', 'interactor', 'renderer', ]); // Object specific methods vtkKeyboardCameraManipulator(publicAPI, model); } // ---------------------------------------------------------------------------- export const newInstance = macro.newInstance( extend, 'vtkKeyboardCameraManipulator' ); // ---------------------------------------------------------------------------- export default { newInstance, extend };