TextureMapToPlane

Introduction

Generate texture coordinates by mapping points to a plane
The TCoords DataArray is name ‘Texture Coordinates’

(Set/Get) Origin

Define the origin of the plane

(Set/Get) Point1

Specify a point which defines the first axis of the plane

(Set/Get) Point 2

Specify a point which defines the second axis of the plane

(Set/Get) AutomaticPlaneGeneration

Turn on/off the automatic plane generation

(Set/Get) TRange

Specify t-coordinate range for texture s-t coordinate pair

(Set/Get) SRange

Specify s-coordinate range for texture s-t coordinate pair

Source

index.js
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 };