UNPKG

@acransac/vtk.js

Version:

Visualization Toolkit for the Web

252 lines (209 loc) 8.81 kB
/* eslint-disable import/prefer-default-export */ /* eslint-disable import/no-extraneous-dependencies */ import 'vtk.js/Sources/favicon'; import { throttle } from 'vtk.js/Sources/macro'; import vtkActor from 'vtk.js/Sources/Rendering/Core/Actor'; import vtkConeSource from 'vtk.js/Sources/Filters/Sources/ConeSource'; import vtkCylinderSource from 'vtk.js/Sources/Filters/Sources/CylinderSource'; import vtkDataArray from 'vtk.js/Sources/Common/Core/DataArray'; import vtkFullScreenRenderWindow from 'vtk.js/Sources/Rendering/Misc/FullScreenRenderWindow'; import vtkGlyph3DMapper from 'vtk.js/Sources/Rendering/Core/Glyph3DMapper'; import vtkMapper from 'vtk.js/Sources/Rendering/Core/Mapper'; import vtkOpenGLHardwareSelector from 'vtk.js/Sources/Rendering/OpenGL/HardwareSelector'; import vtkSphereSource from 'vtk.js/Sources/Filters/Sources/SphereSource'; import { FieldAssociations } from 'vtk.js/Sources/Common/DataModel/DataSet/Constants'; import { Representation } from 'vtk.js/Sources/Rendering/Core/Property/Constants'; // ---------------------------------------------------------------------------- // Constants // ---------------------------------------------------------------------------- const WHITE = [1, 1, 1]; const GREEN = [0.1, 0.8, 0.1]; // ---------------------------------------------------------------------------- // Create DOM tooltip // ---------------------------------------------------------------------------- const tooltipElem = document.createElement('div'); tooltipElem.style.position = 'absolute'; tooltipElem.style.top = 0; tooltipElem.style.left = 0; tooltipElem.style.width = '150px'; tooltipElem.style.padding = '10px'; tooltipElem.style.zIndex = 1; tooltipElem.style.background = 'white'; tooltipElem.style.textAlign = 'center'; document.querySelector('body').appendChild(tooltipElem); // ---------------------------------------------------------------------------- // Create 4 objects // - sphere // - sphere rendered as big points (square) // - cone // - cylinder with sphere as point (glyph mapper: source=cylinder, glyph=sphere) // ---------------------------------------------------------------------------- // Sphere ------------------------------------------------- const sphereSource = vtkSphereSource.newInstance({ phiResolution: 30, thetaResolution: 30, }); const sphereMapper = vtkMapper.newInstance(); const sphereActor = vtkActor.newInstance(); sphereActor.setMapper(sphereMapper); sphereMapper.setInputConnection(sphereSource.getOutputPort()); // Sphere with point representation ----------------------- const spherePointsSource = vtkSphereSource.newInstance({ phiResolution: 15, thetaResolution: 15, radius: 0.6, }); const spherePointsMapper = vtkMapper.newInstance(); const spherePointsActor = vtkActor.newInstance(); spherePointsActor.setMapper(spherePointsMapper); spherePointsMapper.setInputConnection(spherePointsSource.getOutputPort()); // Use point representation spherePointsActor.getProperty().setRepresentation(Representation.POINTS); spherePointsActor.getProperty().setPointSize(20); // Cone --------------------------------------------------- const coneSource = vtkConeSource.newInstance({ resolution: 20 }); const coneMapper = vtkMapper.newInstance(); const coneActor = vtkActor.newInstance(); coneActor.setMapper(coneMapper); coneMapper.setInputConnection(coneSource.getOutputPort()); // Cylinder ----------------------------------------------- const cylinderSource = vtkCylinderSource.newInstance({ resolution: 10, radius: 0.4, height: 0.6, direction: [1.0, 0.0, 0.0], }); const cylinderMapper = vtkGlyph3DMapper.newInstance({ scaling: true, scaleFactor: 0.25, scaleMode: vtkGlyph3DMapper.ScaleModes.SCALE_BY_MAGNITUDE, scaleArray: 'scale', }); const cylinderActor = vtkActor.newInstance(); const cylinderGlyph = sphereSource.getOutputData(); const cylinderPointSet = cylinderSource.getOutputData(); cylinderActor.setMapper(cylinderMapper); cylinderMapper.setInputData(cylinderPointSet, 0); cylinderMapper.setInputData(cylinderGlyph, 1); // Add fields to cylinderPointSet const scaleArray = new Float32Array(cylinderPointSet.getNumberOfPoints()); scaleArray.fill(0.5); cylinderPointSet.getPointData().addArray( vtkDataArray.newInstance({ name: 'scale', values: scaleArray, }) ); // ---------------------------------------------------------------------------- // Create Picking pointer // ---------------------------------------------------------------------------- const pointerSource = vtkSphereSource.newInstance({ phiResolution: 15, thetaResolution: 15, radius: 0.01, }); const pointerMapper = vtkMapper.newInstance(); const pointerActor = vtkActor.newInstance(); pointerActor.setMapper(pointerMapper); pointerMapper.setInputConnection(pointerSource.getOutputPort()); // ---------------------------------------------------------------------------- // Create rendering infrastructure // ---------------------------------------------------------------------------- const fullScreenRenderer = vtkFullScreenRenderWindow.newInstance(); const renderer = fullScreenRenderer.getRenderer(); const renderWindow = renderer.getRenderWindow(); const interactor = renderWindow.getInteractor(); const openGLRenderWindow = interactor.getView(); renderer.addActor(sphereActor); renderer.addActor(spherePointsActor); renderer.addActor(coneActor); renderer.addActor(cylinderActor); renderer.addActor(pointerActor); renderer.resetCamera(); renderWindow.render(); // ---------------------------------------------------------------------------- // Create hardware selector // ---------------------------------------------------------------------------- const hardwareSelector = vtkOpenGLHardwareSelector.newInstance({ captureZValues: true, }); hardwareSelector.setFieldAssociation( FieldAssociations.FIELD_ASSOCIATION_POINTS ); hardwareSelector.attach(openGLRenderWindow, renderer); // ---------------------------------------------------------------------------- // Create Mouse listener for picking on mouse move // ---------------------------------------------------------------------------- function eventToWindowXY(event) { // We know we are full screen => window.innerXXX // Otherwise we can use pixel device ratio or else... const { clientX, clientY } = event; const [width, height] = openGLRenderWindow.getSize(); const x = Math.round((width * clientX) / window.innerWidth); const y = Math.round(height * (1 - clientY / window.innerHeight)); // Need to flip Y return [x, y]; } // ---------------------------------------------------------------------------- let needGlyphCleanup = false; let lastProcessedActor = null; const updateWorldPosition = (worldPosition) => { if (lastProcessedActor) { pointerActor.setVisibility(true); tooltipElem.innerHTML = worldPosition.map((v) => v.toFixed(3)).join(' , '); pointerActor.setPosition(worldPosition); } else { pointerActor.setVisibility(false); tooltipElem.innerHTML = ''; } renderWindow.render(); }; function processSelections(selections) { if (!selections || selections.length === 0) { renderer.getActors().forEach((a) => a.getProperty().setColor(...WHITE)); pointerActor.setVisibility(false); renderWindow.render(); lastProcessedActor = null; return; } const { worldPosition, compositeID, prop } = selections[0].getProperties(); if (lastProcessedActor === prop) { // Skip render call when nothing change updateWorldPosition(worldPosition); return; } lastProcessedActor = prop; // Make the picked actor green renderer.getActors().forEach((a) => a.getProperty().setColor(...WHITE)); prop.getProperty().setColor(...GREEN); // We hit the glyph, let's scale the picked glyph if (prop === cylinderActor) { scaleArray.fill(0.5); scaleArray[compositeID] = 0.7; cylinderPointSet.modified(); needGlyphCleanup = true; } else if (needGlyphCleanup) { needGlyphCleanup = false; scaleArray.fill(0.5); cylinderPointSet.modified(); } // Update picture for the user so we can see the green one updateWorldPosition(worldPosition); } // ---------------------------------------------------------------------------- function pickOnMouseEvent(event) { if (interactor.isAnimating()) { // We should not do picking when interacting with the scene return; } const [x, y] = eventToWindowXY(event); hardwareSelector.setArea(x, y, x, y); hardwareSelector.releasePixBuffers(); pointerActor.setVisibility(false); if (hardwareSelector.captureBuffers()) { processSelections(hardwareSelector.generateSelection(x, y, x, y)); } else { processSelections(null); } } const throttleMouseHandler = throttle(pickOnMouseEvent, 100); document.addEventListener('mousemove', throttleMouseHandler);