UNPKG

@acransac/vtk.js

Version:

Visualization Toolkit for the Web

384 lines (333 loc) 13.1 kB
import macro from 'vtk.js/Sources/macro'; import vtkActor from 'vtk.js/Sources/Rendering/Core/Actor'; import vtkArrow2DSource from 'vtk.js/Sources/Filters/Sources/Arrow2DSource/'; import vtkDataArray from 'vtk.js/Sources/Common/Core/DataArray'; import vtkGlyph3DMapper from 'vtk.js/Sources/Rendering/Core/Glyph3DMapper'; import vtkHandleRepresentation from 'vtk.js/Sources/Widgets/Representations/HandleRepresentation'; import vtkMatrixBuilder from 'vtk.js/Sources/Common/Core/MatrixBuilder'; import vtkPixelSpaceCallbackMapper from 'vtk.js/Sources/Rendering/Core/PixelSpaceCallbackMapper'; import vtkPolyData from 'vtk.js/Sources/Common/DataModel/PolyData'; import vtkConeSource from 'vtk.js/Sources/Filters/Sources/ConeSource'; import vtkSphereSource from 'vtk.js/Sources/Filters/Sources/SphereSource'; import vtkCircleSource from 'vtk.js/Sources/Filters/Sources/CircleSource'; import vtkCubeSource from 'vtk.js/Sources/Filters/Sources/CubeSource'; import vtkViewFinderSource from 'vtk.js/Sources/Filters/Sources/ViewFinderSource'; import Constants from 'vtk.js/Sources/Widgets/Widgets3D/LineWidget/Constants'; import { ScalarMode } from 'vtk.js/Sources/Rendering/Core/Mapper/Constants'; import { vec3, mat3, mat4 } from 'gl-matrix'; import { RenderingTypes } from 'vtk.js/Sources/Widgets/Core/WidgetManager/Constants'; const { ShapeType, Shapes2D, ShapesOrientable } = Constants; // ---------------------------------------------------------------------------- // vtkArrowHandleRepresentation methods // ---------------------------------------------------------------------------- function vtkArrowHandleRepresentation(publicAPI, model) { // Set our className model.classHierarchy.push('vtkArrowHandleRepresentation'); const superClass = { ...publicAPI }; // -------------------------------------------------------------------------- // Internal polydata dataset // -------------------------------------------------------------------------- model.internalPolyData = vtkPolyData.newInstance({ mtime: 0 }); model.internalArrays = { points: model.internalPolyData.getPoints(), scale: vtkDataArray.newInstance({ name: 'scale', numberOfComponents: 1, empty: true, }), color: vtkDataArray.newInstance({ name: 'color', numberOfComponents: 1, empty: true, }), direction: vtkDataArray.newInstance({ name: 'direction', numberOfComponents: 9, empty: true, }), }; model.internalPolyData.getPointData().addArray(model.internalArrays.scale); model.internalPolyData.getPointData().addArray(model.internalArrays.color); model.internalPolyData .getPointData() .addArray(model.internalArrays.direction); /** * Set the shape for the glyph according to lineWidget state inputs */ function createGlyph(shape) { const representationToSource = { [ShapeType.STAR]: { builder: vtkArrow2DSource, initialValues: { shape: 'star', height: 0.6 }, }, [ShapeType.ARROWHEAD3]: { builder: vtkArrow2DSource, initialValues: { shape: 'triangle' }, }, [ShapeType.ARROWHEAD4]: { builder: vtkArrow2DSource, initialValues: { shape: 'arrow4points' }, }, [ShapeType.ARROWHEAD6]: { builder: vtkArrow2DSource, initialValues: { shape: 'arrow6points' }, }, [ShapeType.CONE]: { builder: vtkConeSource, initialValues: { direction: [0, 1, 0], }, }, [ShapeType.SPHERE]: { builder: vtkSphereSource, }, [ShapeType.CUBE]: { builder: vtkCubeSource, initialValues: { xLength: 0.8, yLength: 0.8, zLength: 0.8 }, }, [ShapeType.DISK]: { builder: vtkCircleSource, initialValues: { resolution: 30, radius: 0.5, direction: [0, 0, 1], lines: false, face: true, }, }, [ShapeType.CIRCLE]: { builder: vtkCircleSource, initialValues: { resolution: 30, radius: 0.5, direction: [0, 0, 1], lines: true, face: false, }, }, [ShapeType.VIEWFINDER]: { builder: vtkViewFinderSource, initialValues: { radius: 0.1, spacing: 0.3, width: 1.4 }, }, [ShapeType.NONE]: { builder: vtkSphereSource, }, }; const rep = representationToSource[shape]; return rep.builder.newInstance(rep.initialValues); } // -------------------------------------------------------------------------- // Generic rendering pipeline // -------------------------------------------------------------------------- // displayActors and displayMappers are used to render objects in HTML, // allowing objects to be 'rendered' internally in a VTK scene without // being visible on the final output. model.displayMapper = vtkPixelSpaceCallbackMapper.newInstance(); model.displayActor = vtkActor.newInstance(); // model.displayActor.getProperty().setOpacity(0); // don't show in 3D model.displayActor.setMapper(model.displayMapper); model.displayMapper.setInputConnection(publicAPI.getOutputPort()); publicAPI.addActor(model.displayActor); model.alwaysVisibleActors = [model.displayActor]; model.mapper = vtkGlyph3DMapper.newInstance({ orientationArray: 'direction', scaleArray: 'scale', colorByArrayName: 'color', scalarMode: ScalarMode.USE_POINT_FIELD_DATA, }); model.mapper.setOrientationModeToMatrix(); model.mapper.setInputConnection(publicAPI.getOutputPort()); model.actor = vtkActor.newInstance(); model.actor.setMapper(model.mapper); publicAPI.addActor(model.actor); // -------------------------------------------------------------------------- publicAPI.setGlyphResolution = macro.chain( publicAPI.setGlyphResolution, (r) => model.glyph.setPhiResolution(r) && model.glyph.setThetaResolution(r) ); // -------------------------------------------------------------------------- function callbackProxy(coords) { if (model.displayCallback) { const filteredList = []; const states = publicAPI.getRepresentationStates(); for (let i = 0; i < states.length; i++) { if (states[i].getActive()) { filteredList.push(coords[i]); } } if (filteredList.length) { model.displayCallback(filteredList); return; } } model.displayCallback(); } publicAPI.setDisplayCallback = (callback) => { model.displayCallback = callback; model.displayMapper.setCallback(callback ? callbackProxy : null); }; // -------------------------------------------------------------------------- publicAPI.is2DShape = () => Shapes2D.includes(model.shape); publicAPI.isOrientableShape = () => ShapesOrientable.includes(model.shape); /** * Returns the orientation matrix to align glyph on model.orientation. * */ function getOrientationRotation(viewMatrixInv) { const displayOrientation = vec3.create(); const baseDir = [0, 1, 0]; vec3.transformMat3(displayOrientation, model.orientation, viewMatrixInv); displayOrientation[2] = 0; const displayMatrix = vtkMatrixBuilder .buildFromDegree() .rotateFromDirections(baseDir, displayOrientation) .getMatrix(); const displayRotation = mat3.create(); mat3.fromMat4(displayRotation, displayMatrix); return displayRotation; } function getCameraFacingRotation(scale3, displayRotation, viewMatrix) { const rotation = mat3.create(); mat3.multiply(rotation, viewMatrix, displayRotation); vec3.transformMat3(scale3, scale3, rotation); return rotation; } /** * Computes the rotation matrix of the glyph. There are 2 rotations: * - a first rotation to be oriented along model.rotation * - an optional second rotation to face the camera * @param {vec3} scale3 Scale of the glyph, rotated when glyph is rotated. */ function getGlyphRotation(scale3) { const shouldFaceCamera = model.faceCamera === true || (model.faceCamera == null && publicAPI.is2DShape()); const viewMatrix = mat3.create(); mat3.fromMat4(viewMatrix, model.viewMatrix); const viewMatrixInv = mat3.create(); if (shouldFaceCamera) { mat3.invert(viewMatrixInv, viewMatrix); } let orientationRotation = mat3.create(); if (publicAPI.isOrientableShape()) { orientationRotation = getOrientationRotation(viewMatrixInv); } if (shouldFaceCamera) { orientationRotation = getCameraFacingRotation( scale3, orientationRotation, viewMatrix ); } return orientationRotation; } publicAPI.requestDataInternal = (inData, outData) => { const { points, scale, color, direction } = model.internalArrays; const list = publicAPI.getRepresentationStates(inData[0]); const totalCount = list.length; if (color.getNumberOfValues() !== totalCount) { // Need to resize dataset points.setData(new Float32Array(3 * totalCount), 3); scale.setData(new Float32Array(totalCount)); color.setData(new Float32Array(totalCount)); direction.setData(new Float32Array(9 * totalCount)); } const typedArray = { points: points.getData(), scale: scale.getData(), color: color.getData(), direction: direction.getData(), }; for (let i = 0; i < list.length; i++) { const state = list[i]; const isActive = state.getActive(); const scaleFactor = isActive ? model.activeScaleFactor : 1; const coord = state.getOrigin(); typedArray.points[i * 3 + 0] = coord[0]; typedArray.points[i * 3 + 1] = coord[1]; typedArray.points[i * 3 + 2] = coord[2]; let scale3 = state.getScale3 ? state.getScale3() : [1, 1, 1]; scale3 = scale3.map((x) => (x === 0 ? 2 * model.defaultScale : 2 * x)); const rotation = getGlyphRotation(scale3); typedArray.direction.set(rotation, 9 * i); typedArray.scale[i] = scaleFactor * (state.getScale1 ? state.getScale1() : model.defaultScale); if (publicAPI.getScaleInPixels()) { typedArray.scale[i] *= publicAPI.getPixelWorldHeightAtCoord(coord); } typedArray.color[i] = model.useActiveColor && isActive ? model.activeColor : state.getColor(); } model.internalPolyData.modified(); outData[0] = model.internalPolyData; }; publicAPI.requestData = (inData, outData) => { const shape = publicAPI.getRepresentationStates(inData[0])[0].getShape(); let shouldCreateGlyph = model.glyph == null; if (model.shape !== shape && Object.values(ShapeType).includes(shape)) { model.shape = shape; shouldCreateGlyph = true; } if (shouldCreateGlyph) { model.glyph = createGlyph(model.shape); model.mapper.setInputConnection(model.glyph.getOutputPort(), 1); } publicAPI.requestDataInternal(inData, outData); }; publicAPI.updateActorVisibility = ( renderingType = RenderingTypes.FRONT_BUFFER, widgetVisible = true, ctxVisible = true, handleVisible = true ) => { const state = publicAPI.getRepresentationStates()[0]; superClass.updateActorVisibility( renderingType, widgetVisible, ctxVisible, handleVisible && state.isVisible() ); }; } // ---------------------------------------------------------------------------- // Object factory // ---------------------------------------------------------------------------- /** * 'shape' default value is used first time 'shape' mixin is invalid. * 'faceCamera' controls wether the glyph should face camera or not: * - null or undefined to leave it to shape type (i.e. 2D are facing camera, * 3D are not) * - true to face camera * - false to not face camera */ function defaultValues(initialValues) { const viewMatrix = new Float64Array(16); mat4.identity(viewMatrix); return { defaultScale: 1, faceCamera: null, orientation: [1, 0, 0], shape: ShapeType.SPHERE, viewMatrix, ...initialValues, }; } // ---------------------------------------------------------------------------- export function extend(publicAPI, model, initialValues = {}) { Object.assign(model, defaultValues(initialValues)); vtkHandleRepresentation.extend(publicAPI, model, initialValues); macro.get(publicAPI, model, ['glyph', 'mapper', 'actor']); macro.setGetArray(publicAPI, model, ['visibilityFlagArray'], 2); macro.setGetArray(publicAPI, model, ['orientation'], 3); macro.setGetArray(publicAPI, model, ['viewMatrix'], 16); macro.setGet(publicAPI, model, ['faceCamera']); // Object specific methods vtkArrowHandleRepresentation(publicAPI, model); } // ---------------------------------------------------------------------------- export const newInstance = macro.newInstance( extend, 'vtkArrowHandleRepresentation' ); // ---------------------------------------------------------------------------- export default { newInstance, extend };