UNPKG

@kitware/vtk.js

Version:

Visualization Toolkit for the Web

387 lines (357 loc) 13 kB
import { m as macro } from '../../macros2.js'; import vtkPolyData from '../../Common/DataModel/PolyData.js'; import vtkPoints from '../../Common/Core/Points.js'; import vtkCellArray from '../../Common/Core/CellArray.js'; import vtkDataArray from '../../Common/Core/DataArray.js'; import { b as vtkMath } from '../../Common/Core/Math/index.js'; /* eslint-disable no-continue */ const { vtkWarningMacro } = macro; // Texture coordinate generation modes const GenerateTCoords = { TCOORDS_OFF: 0, TCOORDS_FROM_SCALARS: 1, TCOORDS_FROM_LENGTH: 2, TCOORDS_FROM_NORMALIZED_LENGTH: 3 }; // ---------------------------------------------------------------------------- // vtkRibbonFilter methods // ---------------------------------------------------------------------------- function vtkRibbonFilter(publicAPI, model) { // Set our className model.classHierarchy.push('vtkRibbonFilter'); // Private methods function generateSlidingNormals(points, lines, normals) { // Simplified normal generation for polylines // This is a basic implementation - you might want to use the actual vtk.js implementation const lineArray = lines.getData(); let offset = 0; for (let cellId = 0; cellId < lines.getNumberOfCells(); cellId++) { const npts = lineArray[offset++]; const pts = lineArray.slice(offset, offset + npts); offset += npts; if (npts < 2) continue; for (let i = 0; i < npts; i++) { const v1 = [0, 0, 0]; const v2 = [0, 0, 0]; if (i === 0) { points.getPoint(pts[1], v2); points.getPoint(pts[0], v1); } else if (i === npts - 1) { points.getPoint(pts[i], v2); points.getPoint(pts[i - 1], v1); } else { points.getPoint(pts[i + 1], v2); points.getPoint(pts[i - 1], v1); } const tangent = [v2[0] - v1[0], v2[1] - v1[1], v2[2] - v1[2]]; vtkMath.normalize(tangent); // Generate a normal perpendicular to the tangent let normal = [0, 0, 1]; if (Math.abs(vtkMath.dot(tangent, normal)) > 0.9) { normal = [1, 0, 0]; } const binormal = [0, 0, 0]; vtkMath.cross(tangent, normal, binormal); vtkMath.normalize(binormal); vtkMath.cross(binormal, tangent, normal); vtkMath.normalize(normal); normals.setTuple(pts[i], normal); } } return true; } function generatePoints(offset, npts, pts, inPts, newPts, inScalars, range, inNormals, outPD, pd, newNormals) { const theta = model.angle * Math.PI / 180.0; let ptId = offset; const p = [0, 0, 0]; const pNext = [0, 0, 0]; const sNext = [0, 0, 0]; const sPrev = [0, 0, 0]; for (let j = 0; j < npts; j++) { if (j === 0) { inPts.getPoint(pts[0], p); inPts.getPoint(pts[1], pNext); for (let i = 0; i < 3; i++) { sNext[i] = pNext[i] - p[i]; sPrev[i] = sNext[i]; } } else if (j === npts - 1) { for (let i = 0; i < 3; i++) { sPrev[i] = sNext[i]; p[i] = pNext[i]; } } else { for (let i = 0; i < 3; i++) { p[i] = pNext[i]; } inPts.getPoint(pts[j + 1], pNext); for (let i = 0; i < 3; i++) { sPrev[i] = sNext[i]; sNext[i] = pNext[i] - p[i]; } } const n = [0, 0, 0]; inNormals.getTuple(pts[j], n); if (vtkMath.normalize(sNext) === 0.0) { vtkWarningMacro('Coincident points!'); return false; } const s = [(sPrev[0] + sNext[0]) / 2.0, (sPrev[1] + sNext[1]) / 2.0, (sPrev[2] + sNext[2]) / 2.0]; if (vtkMath.normalize(s) === 0.0) { vtkMath.cross(sPrev, n, s); if (vtkMath.normalize(s) === 0.0) { vtkWarningMacro('Using alternate bevel vector'); } } const w = vtkMath.cross(s, n, []); if (vtkMath.normalize(w) === 0.0) { vtkWarningMacro(`Bad normal s = ${s} n = ${n}`); return false; } const nP = vtkMath.cross(w, s, []); vtkMath.normalize(nP); let sFactor = 1.0; if (inScalars && model.varyWidth) { sFactor = 1.0 + (model.widthFactor - 1.0) * (inScalars.getValue(pts[j]) - range[0]) / (range[1] - range[0]); } const v = [w[0] * Math.cos(theta) + nP[0] * Math.sin(theta), w[1] * Math.cos(theta) + nP[1] * Math.sin(theta), w[2] * Math.cos(theta) + nP[2] * Math.sin(theta)]; const sp = [p[0] + model.width * sFactor * v[0], p[1] + model.width * sFactor * v[1], p[2] + model.width * sFactor * v[2]]; const sm = [p[0] - model.width * sFactor * v[0], p[1] - model.width * sFactor * v[1], p[2] - model.width * sFactor * v[2]]; newPts.setPoint(ptId, ...sm); newNormals.setTuple(ptId, nP); outPD.passData(pd, pts[j], ptId); ptId++; newPts.setPoint(ptId, ...sp); newNormals.setTuple(ptId, nP); outPD.passData(pd, pts[j], ptId); ptId++; } return true; } function generateStrip(offset, npts, inCellId, outCD, cd, newStrips) { const stripData = []; for (let i = 0; i < npts; i++) { const idx = 2 * i; stripData.push(offset + idx); stripData.push(offset + idx + 1); } newStrips.insertNextCell(stripData); const outCellId = newStrips.getNumberOfCells() - 1; outCD.passData(cd, inCellId, outCellId); } function generateTextureCoords(offset, npts, pts, inPts, inScalars, newTCoords) { // First texture coordinate is always 0 for (let k = 0; k < 2; k++) { newTCoords.setTuple(offset + k, [0.0, 0.0]); } if (model.generateTCoords === GenerateTCoords.TCOORDS_FROM_SCALARS && inScalars) { const s0 = inScalars.getValue(pts[0]); for (let i = 1; i < npts; i++) { const s = inScalars.getValue(pts[i]); const tc = (s - s0) / model.textureLength; for (let k = 0; k < 2; k++) { newTCoords.setTuple(offset + i * 2 + k, [tc, 0.0]); } } } else if (model.generateTCoords === GenerateTCoords.TCOORDS_FROM_LENGTH) { const xPrev = [0, 0, 0]; const x = [0, 0, 0]; let len = 0.0; inPts.getPoint(pts[0], xPrev); for (let i = 1; i < npts; i++) { inPts.getPoint(pts[i], x); len += Math.sqrt(vtkMath.distance2BetweenPoints(x, xPrev)); const tc = len / model.textureLength; for (let k = 0; k < 2; k++) { newTCoords.setTuple(offset + i * 2 + k, [tc, 0.0]); } xPrev[0] = x[0]; xPrev[1] = x[1]; xPrev[2] = x[2]; } } else if (model.generateTCoords === GenerateTCoords.TCOORDS_FROM_NORMALIZED_LENGTH) { const xPrev = [0, 0, 0]; const x = [0, 0, 0]; let length = 0.0; let len = 0.0; // Calculate total length inPts.getPoint(pts[0], xPrev); for (let i = 1; i < npts; i++) { inPts.getPoint(pts[i], x); length += Math.sqrt(vtkMath.distance2BetweenPoints(x, xPrev)); xPrev[0] = x[0]; xPrev[1] = x[1]; xPrev[2] = x[2]; } // Generate normalized coordinates inPts.getPoint(pts[0], xPrev); for (let i = 1; i < npts; i++) { inPts.getPoint(pts[i], x); len += Math.sqrt(vtkMath.distance2BetweenPoints(x, xPrev)); const tc = len / length; for (let k = 0; k < 2; k++) { newTCoords.setTuple(offset + i * 2 + k, [tc, 0.0]); } xPrev[0] = x[0]; xPrev[1] = x[1]; xPrev[2] = x[2]; } } } publicAPI.requestData = (inData, outData) => { const input = inData[0]; const output = outData[0]?.initialize() || vtkPolyData.newInstance(); outData[0] = output; if (!input || !input.getPoints() || !input.getLines()) { return; } const inPts = input.getPoints(); const inLines = input.getLines(); const pd = input.getPointData(); const cd = input.getCellData(); const numPts = inPts.getNumberOfPoints(); const numLines = inLines.getNumberOfCells(); if (numPts < 1 || numLines < 1) { return; } // Get scalar data if available let inScalars = null; const scalarsArray = pd.getScalars(); if (scalarsArray) { inScalars = scalarsArray; } let inNormals = pd.getNormals(); let generateNormals = false; if (!inNormals || model.useDefaultNormal) { inNormals = vtkDataArray.newInstance({ numberOfComponents: 3, size: numPts * 3, dataType: 'Float32Array' }); if (model.useDefaultNormal) { for (let i = 0; i < numPts; i++) { inNormals.setTuple(i, model.defaultNormal); } } else { generateNormals = true; } } // Calculate scalar range if varying width let range = [0, 1]; if (model.varyWidth && inScalars) { range = inScalars.getRange(); if (range[1] - range[0] === 0.0) { vtkWarningMacro('Scalar range is zero!'); range[1] = range[0] + 1.0; } } const numNewPts = 2 * numPts; const newPts = vtkPoints.newInstance(); newPts.setNumberOfPoints(numNewPts); const newNormals = vtkDataArray.newInstance({ numberOfComponents: 3, size: numNewPts * 3 }); const newStrips = vtkCellArray.newInstance(); const outPD = output.getPointData(); outPD.copyStructure(pd); const outCD = output.getCellData(); outCD.copyStructure(cd); let newTCoords = null; if (model.generateTCoords !== GenerateTCoords.TCOORDS_OFF) { newTCoords = vtkDataArray.newInstance({ numberOfComponents: 2, size: numNewPts * 2 }); } // Process each polyline const lineArray = inLines.getData(); let offset = 0; let arrayOffset = 0; for (let inCellId = 0; inCellId < numLines; inCellId++) { const npts = lineArray[arrayOffset++]; const pts = lineArray.slice(arrayOffset, arrayOffset + npts); arrayOffset += npts; if (npts < 2) { vtkWarningMacro('Less than two points in line!'); continue; } // Generate normals if needed if (generateNormals) { const singlePolyline = vtkCellArray.newInstance(); singlePolyline.insertNextCell(pts); if (!generateSlidingNormals(inPts, singlePolyline, inNormals)) ; } // Generate points for this polyline if (!generatePoints(offset, npts, pts, inPts, newPts, inScalars, range, inNormals, outPD, pd, newNormals)) { vtkWarningMacro('Could not generate points!'); continue; } // Generate strip for this polyline generateStrip(offset, npts, inCellId, outCD, cd, newStrips); // Generate texture coordinates if needed if (newTCoords) { generateTextureCoords(offset, npts, pts, inPts, inScalars, newTCoords); } // Update offset for next polyline offset += 2 * npts; } // Set output data output.setPoints(newPts); output.setStrips(newStrips); outPD.setNormals(newNormals); if (newTCoords) { outPD.setTCoords(newTCoords); } }; publicAPI.getGenerateTCoordsAsString = () => { switch (model.generateTCoords) { case GenerateTCoords.TCOORDS_OFF: return 'GenerateTCoordsOff'; case GenerateTCoords.TCOORDS_FROM_SCALARS: return 'GenerateTCoordsFromScalar'; case GenerateTCoords.TCOORDS_FROM_LENGTH: return 'GenerateTCoordsFromLength'; case GenerateTCoords.TCOORDS_FROM_NORMALIZED_LENGTH: return 'GenerateTCoordsFromNormalizedLength'; default: return 'GenerateTCoordsOff'; } }; } // ---------------------------------------------------------------------------- // Object factory // ---------------------------------------------------------------------------- const DEFAULT_VALUES = { width: 0.5, angle: 0.0, varyWidth: false, widthFactor: 2.0, defaultNormal: [0.0, 0.0, 1.0], useDefaultNormal: false, generateTCoords: GenerateTCoords.TCOORDS_OFF, textureLength: 1.0 }; // ---------------------------------------------------------------------------- function extend(publicAPI, model, initialValues = {}) { Object.assign(model, DEFAULT_VALUES, initialValues); // Build VTK API macro.obj(publicAPI, model); macro.algo(publicAPI, model, 1, 1); // Set/Get methods macro.setGet(publicAPI, model, ['width', 'angle', 'varyWidth', 'widthFactor', 'useDefaultNormal', 'generateTCoords', 'textureLength']); macro.setGetArray(publicAPI, model, ['defaultNormal'], 3); // Object specific methods vtkRibbonFilter(publicAPI, model); } // ---------------------------------------------------------------------------- const newInstance = macro.newInstance(extend, 'vtkRibbonFilter'); // ---------------------------------------------------------------------------- var vtkRibbonFilter$1 = { newInstance, extend, GenerateTCoords }; export { vtkRibbonFilter$1 as default, extend, newInstance };