UNPKG

@acransac/vtk.js

Version:

Visualization Toolkit for the Web

303 lines (253 loc) 8.58 kB
import macro from 'vtk.js/Sources/macro'; import vtkDataArray from 'vtk.js/Sources/Common/Core/DataArray'; import * as vtkMath from 'vtk.js/Sources/Common/Core/Math'; import vtkPolyData from 'vtk.js/Sources/Common/DataModel/PolyData'; const { vtkErrorMacro } = macro; // ---------------------------------------------------------------------------- // vtkTextureMapToPlane methods // ---------------------------------------------------------------------------- function vtkTextureMapToPlane(publicAPI, model) { // Set our className model.classHierarchy.push('vtkTextureMapToPlane'); function computeNormal(output) { const VTK_TOLERANCE = 0.001; // First thing to do is to get an initial normal and point to define // the plane. Then, use this information to construct better // matrices. If problem occurs, then the point and plane becomes the // fallback value const nbPoints = output.getPoints().getNumberOfPoints(); let dir = 0; const m = [0, 0, 0, 0, 0, 0, 0, 0, 0]; const x = [0, 0, 0]; const v = [0, 0, 0]; // Get minimum width of bounding box. const bounds = output.getBounds(); const minBounds = [bounds[0], bounds[2], bounds[4]]; const maxBounds = [bounds[1], bounds[3], bounds[5]]; const length = Math.sqrt( vtkMath.distance2BetweenPoints(minBounds, maxBounds) ); let w = length; let i = 0; for (; i < 3; i++) { model.normal[i] = 0.0; if (bounds[2 * i + 1] - bounds[2 * i] < w) { dir = i; w = bounds[2 * i + 1] - bounds[2 * i]; } } // If the bounds is perpendicular to one of the axes, then can // quickly compute normal. // model.normal[dir] = 1.0; if (w <= length * VTK_TOLERANCE) { return; } // Need to compute least squares approximation. Depending on major // normal direction (dir), construct matrices appropriately. // // Compute 3x3 least squares matrix v[0] = 0.0; v[1] = 0.0; v[2] = 0.0; for (i = 0; i < 9; i++) { m[i] = 0.0; } for (let ptId = 0; ptId < nbPoints; ptId++) { output.getPoints().getPoint(ptId, x); v[0] += x[0] * x[2]; v[1] += x[1] * x[2]; v[2] += x[2]; m[0] += x[0] * x[0]; m[1] += x[0] * x[1]; m[2] += x[0]; m[3] += x[0] * x[1]; m[4] += x[1] * x[1]; m[5] += x[1]; m[6] += x[0]; m[7] += x[1]; } m[8] = nbPoints; // Solve linear system using Kramers rule const c1 = [m[0], m[1], m[2]]; const c2 = [m[3], m[4], m[5]]; const c3 = [m[6], m[7], m[8]]; const matrix = [c1, c2, c3]; const det = vtkMath.determinant3x3(matrix); if (det <= VTK_TOLERANCE) { return; } matrix[0] = v; matrix[1] = c2; matrix[2] = c3; model.normal[0] = vtkMath.determinant3x3(matrix) / det; matrix[0] = c1; matrix[1] = v; matrix[2] = c3; model.normal[1] = vtkMath.determinant3x3(matrix) / det; // because of the formulation model.normal[2] = -1.0; } publicAPI.requestData = (inData, outData) => { if (model.deleted) { return; } const input = inData[0]; const nbPoints = input.getPoints().getNumberOfPoints(); if (nbPoints < 3 && model.automaticPlaneGeneration) { vtkErrorMacro("Can't generate texture coordinates without points"); return; } const output = vtkPolyData.newInstance(); output .getPoints() .setData(new Float32Array(input.getPoints().getData()), 3); output.getPolys().setData(new Uint32Array(input.getPolys().getData())); const tcoordsData = []; let minProj = 0; let i = 0; let j = 0; let proj = 0; const axis = [0, 0, 0]; let dir = 0; const tAxis = [0, 0, 0]; const sAxis = [0, 0, 0]; let s = 0; let t = 0; let sSf = 0; let tSf = 0; const p = [0, 0, 0]; // Compute least squares plane if on automatic mode; otherwise use // normal specified or plane specified if ( model.automaticPlaneGeneration && model.origin[0] === 0 && model.origin[1] === 0 && model.origin[2] === 0 && model.point1[0] === 0 && model.point1[1] === 0 && model.point2[0] === 0 && model.point2[1] === 0 ) { if (model.automaticPlaneGeneration) { computeNormal(output); } vtkMath.normalize(model.normal); // Now project each point onto plane generating s,t texture coordinates // // Create local s-t coordinate system. Need to find the two axes on // the plane and encompassing all the points. Hence use the bounding // box as a reference. minProj = 1.0; i = 0; for (; i < 3; i++) { axis[0] = 0.0; axis[1] = 0.0; axis[2] = 0.0; axis[i] = 1.0; proj = Math.abs(vtkMath.dot(model.normal, axis)); if (proj < minProj) { minProj = proj; dir = i; } } axis[0] = 0.0; axis[1] = 0.0; axis[2] = 0.0; axis[dir] = 1.0; vtkMath.cross(model.normal, axis, tAxis); vtkMath.normalize(tAxis); vtkMath.cross(tAxis, model.normal, sAxis); // Construct projection matrices // // Arrange s-t axes so that parametric location of points will fall // between s_range and t_range. Simplest to do by projecting maximum // corner of bounding box unto plane and backing out scale factors. // const bounds = output.getBounds(); for (i = 0; i < 3; i++) { axis[i] = bounds[2 * i + 1] - bounds[2 * i]; } s = vtkMath.dot(sAxis, axis); t = vtkMath.dot(tAxis, axis); sSf = (model.sRange[1] - model.sRange[0]) / s; tSf = (model.tRange[1] - model.tRange[0]) / t; // Now can loop over all points, computing parametric coordinates. for (i = 0; i < nbPoints; i++) { output.getPoints().getPoint(i, p); for (j = 0; j < 3; j++) { axis[j] = p[j] - bounds[2 * j]; } tcoordsData.push(model.sRange[0] + vtkMath.dot(sAxis, axis) * sSf); tcoordsData.push(model.tRange[0] + vtkMath.dot(tAxis, axis) * tSf); } } else { let num = 0; // compute axes for (i = 0; i < 3; i++) { sAxis[i] = model.point1[i] - model.origin[i]; tAxis[i] = model.point2[i] - model.origin[i]; } let sDenom = vtkMath.dot(sAxis, sAxis); let tDenom = vtkMath.dot(tAxis, tAxis); if (sDenom === 0.0 || tDenom === 0.0) { vtkErrorMacro('Bad plane definition'); sDenom = 1.0; tDenom = 1.0; } // compute s-t coordinates for (i = 0; i < nbPoints; i++) { output.getPoints().getPoint(i, p); for (j = 0; j < 3; j++) { axis[j] = p[j] - model.origin[j]; } // s-coordinate num = sAxis[0] * axis[0] + sAxis[1] * axis[1] + sAxis[2] * axis[2]; tcoordsData.push(num / sDenom); // t-coordinate num = tAxis[0] * axis[0] + tAxis[1] * axis[1] + tAxis[2] * axis[2]; tcoordsData.push(num / tDenom); } } const tCoords = vtkDataArray.newInstance({ name: 'Texture Coordinates', numberOfComponents: 2, size: nbPoints, values: tcoordsData, }); output.getPointData().setTCoords(tCoords); // Update output outData[0] = output; }; } // ---------------------------------------------------------------------------- // Object factory // ---------------------------------------------------------------------------- const DEFAULT_VALUES = { origin: [0, 0, 0], point1: [0, 0, 0], point2: [0, 0, 0], normal: [0, 0, 0], sRange: [0, 1], tRange: [0, 1], automaticPlaneGeneration: 1, }; // ---------------------------------------------------------------------------- export function extend(publicAPI, model, initialValues = {}) { Object.assign(model, DEFAULT_VALUES, initialValues); // Build VTK API macro.obj(publicAPI, model); macro.setGetArray( publicAPI, model, ['origin', 'point1', 'point2', 'normal', 'sRange', 'tRange'], 3 ); macro.setGet(publicAPI, model, ['automaticPlaneGeneration']); macro.algo(publicAPI, model, 1, 1); vtkTextureMapToPlane(publicAPI, model); } // ---------------------------------------------------------------------------- export const newInstance = macro.newInstance(extend, 'vtkTextureMapToPlane'); // ---------------------------------------------------------------------------- export default { newInstance, extend };