Transform

Introduction

vtkTransform describes linear transformations via a 4x4 matrix.

A vtkTransform can be used to describe the full range of linear (also known as affine) coordinate transformations in three dimensions, which are internally represented as a 4x4 homogeneous transformation matrix. When you create a new vtkTransform, it is always initialized to the identity transformation.

Most of the methods for manipulating this transformation (e.g. transformMatrices, transformMatrix) (TODO: Translate, Rotate, Concatenate…) can operate in either PreMultiply (the default) or PostMultiply mode. In PreMultiply mode, the translation, concatenation, etc. will occur before any transformations which are represented by the current matrix. In PostMultiply mode, the additional transformation will occur after any transformations represented by the current matrix.

This class performs all of its operations in a right handed coordinate system with right handed rotations. Some other graphics libraries use left handed coordinate systems and rotations.

Usage

import vtkTransform from '@kitware/vtk.js/Common/Transform/Transform';
import vtkMatrixBuilder from '@kitware/vtk.js/Common/Core/MatrixBuilder';

const transform = vtkTransform.newInstance();
const matrix = vtkMatrixBuilder
.buildFromDegree()
.rotateZ(45)
.getMatrix();
transform.setMatrix(matrix);
const pointsIn = [[1, 2, 3], [4, 5, 6]];
const pointsOut = new Float32Array(6);
transform.transformPoints(pointsIn, pointsOut);

Methods

extend

Method used to decorate a given object (publicAPI+model) with vtkTransform characteristics.

Argument Type Required Description
publicAPI Yes object on which methods will be bounds (public)
model Yes object on which data structure will be bounds (protected)
initialValues ITransformInitialValues No (default: {})

getInverse

Returns

Type Description
A new transform with an inversed internal matrix. Also copy the premultiply flag @see getPreMultiplyFlag.

getMatrix

Mat4 matrix, used by vtkTransform to transform points, vertices, matrices…
Default is identity.

getPreMultiplyFlag

The value of preMultiplyFlag indicates how matrix multiplications should occur.

When in premultiply mode:
All subsequent operations will occur before those already represented in the current transformation.
In homogeneous matrix notation, M = M*A where M is the current transformation matrix and A is the applied matrix.

When in postmultiply mode:
All subsequent operations will occur after those already represented in the current transformation.
In homogeneous matrix notation, M = A*M where M is the current transformation matrix and A is the applied matrix.

This flag is also used in @see transformMatrix and @see transformMatrices to indicate how transform is applied to matrices:
Premultiply: O_i = M * A_i
Postmultiply: O_i = A_i * M
where M is the current transformation matrix, A_i are the matrices in argument, O_i are the output matrices.

The default is PreMultiply.

newInstance

Method used to create a new instance of vtkTransform.

Argument Type Required Description
initialValues ITransformInitialValues No for pre-setting some of its content

postMultiply

Set preMultiplyFlag to false

preMultiply

Set preMultiplyFlag to true

rotateWXYZ

Create a rotation matrix and concatenate it with the current transformation
according to preMultiply or postMultiply semantics.
The angle is expressed in degrees.

Argument Type Required Description
angle Number Yes Angle in degrees
x Number Yes X component of the rotation axis
y Number Yes Y component of the rotation axis
z Number Yes Z component of the rotation axis

rotateX

Create a rotation matrix and concatenate it with the current transformation
according to preMultiply or postMultiply semantics.
The angle is expressed in degrees.

Argument Type Required Description
angle Number Yes Angle in degrees

rotateY

Create a rotation matrix about the X, Y, or Z axis and concatenate it with
the current transformation according to preMultiply or postMultiply
semantics.

Argument Type Required Description
angle Number Yes Angle in degrees

rotateZ

Create a rotation matrix about the X, Y, or Z axis and concatenate it with
the current transformation according to preMultiply or postMultiply
semantics.

Argument Type Required Description
angle Number Yes Angle in degrees

scale

Create a scale matrix (i.e. set the diagonal elements to x, y, z) and
concatenate it with the current transformation according to preMultiply or
postMultiply semantics.

Argument Type Required Description
x Number Yes Diagonal element for X axis
y Number Yes Diagonal element for Y axis
z Number Yes Diagonal element for Z axis

setMatrix

Argument Type Required Description
e00 Yes
e01 Yes
e02 Yes
e03 Yes
e10 Yes
e11 Yes
e12 Yes
e13 Yes
e20 Yes
e21 Yes
e22 Yes
e23 Yes
e30 Yes
e31 Yes
e32 Yes
e33 Yes

setMatrix

Argument Type Required Description
matrix mat4 Yes

setPreMultiplyFlag

Argument Type Required Description
preMultiplyFlag Yes

transformMatrices

Transform multiple matrices using the internal transform matrix
See @see transformMatrix for more info
Modify the out array only

Argument Type Required Description
matrices Yes An array (typed or not) containing n*16 elements or of shape (n, 16)
out Yes An array (typed or not) containing n*16 elements or of shape (n, 16)

transformMatrix

Transform a single matrix using the internal transform matrix
The resulting matrix is:
Mout = M * Min when in premultiply mode
Mout = Min * M when in postmultiply mode

Argument Type Required Description
matrix Yes The matrix to transform, is not modified (except if matrix === out)
out Yes The receiving output matrix, is modified, can be the same as matrix

Returns

Type Description
The out parameter

transformNormal

Apply the transformation to a normal.

Argument Type Required Description
inNormal vec3 Yes The normal vector to transform
outNormal vec3 Yes The output vector

Returns

Type Description
vec3 The transformed normal vector

transformNormals

Apply the transformation to a series of normals, and append the results to
outNormals.

Argument Type Required Description
inNormals vtkDataArray Yes The normal vectors to transform
outNormals vtkDataArray Yes The output array

transformPoint

Transform a single point using the internal transform matrix
The resulting point is: Pout = M * Pin where M is the internal matrix, Pin and Pout the in and out points

Argument Type Required Description
point Yes The point to transform, is not modified (except if point === out)
out Yes The receiving output point, is modified, can be the same as point

Returns

Type Description
The out parameter

transformPoints

Transform multiple points using the internal transform matrix
See @see transformPoint for more info
Modify the out array only

Argument Type Required Description
points Yes An array (typed or not) containing n*3 elements or of shape (n, 3)
out Yes An array (typed or not) containing n*3 elements or of shape (n, 3)

transformPointsNormalsVectors

Transform points, normals, and vectors simultaneously.

Argument Type Required Description
inPoints vtkPoints Yes Input points
outPoints vtkPoints Yes Output points
inNormals vtkDataArray Yes Input normals
outNormals vtkDataArray Yes Output normals
inVectors vtkDataArray Yes Input vectors
outVectors vtkDataArray Yes Output vectors
inVectorsArr Array Yes Optional input vectors arrays
outVectorsArr Array Yes Optional output vectors arrays

transformVector

Apply the transformation to a vector.

Argument Type Required Description
inVector vec3 Yes The vector to transform
outVector vec3 Yes The output vector
matrix=null mat3 No if null (default), the Transform matrix is being used.

Returns

Type Description
vec3 The transformed vector

transformVectors

Apply the transformation to a series of vectors, and append the results to
outVectors.

Argument Type Required Description
inVectors vtkDataArray Yes The vectors to transform
outVectors vtkDataArray Yes The output array

translate

Create a translation matrix and concatenate it with the current
transformation according to preMultiply or postMultiply semantics.

Argument Type Required Description
x Number Yes X component of the translation
y Number Yes Y component of the translation
z Number Yes Z component of the translation

Source

index.d.ts
import { mat3, mat4, vec3 } from 'gl-matrix';
import { vtkObject } from '../../../interfaces';
import { TypedArray } from '../../../types';
import vtkDataArray from '../../Core/DataArray';
import vtkPoints from '../../Core/Points';

export interface ITransformInitialValues {
preMultiplyFlag?: boolean;
matrix?: number[];
}

type TSlicableArray = number[] | TypedArray;

export interface vtkTransform extends vtkObject {
/**
* Mat4 matrix, used by vtkTransform to transform points, vertices, matrices...
* Default is identity.
*/
getMatrix(): mat4;

/**
* @see getMatrix
* @param {mat4} matrix
*/
setMatrix(matrix: mat4): boolean;

/**
* @see getMatrix
* Matrix is stored using gl-matrix conventions: column major
* @param e00
* @param e01
* @param e02
* @param e03
* @param e10
* @param e11
* @param e12
* @param e13
* @param e20
* @param e21
* @param e22
* @param e23
* @param e30
* @param e31
* @param e32
* @param e33
*/
setMatrix(
e00: number,
e01: number,
e02: number,
e03: number,
e10: number,
e11: number,
e12: number,
e13: number,
e20: number,
e21: number,
e22: number,
e23: number,
e30: number,
e31: number,
e32: number,
e33: number
): boolean;

/**
* The value of preMultiplyFlag indicates how matrix multiplications should occur.
*
* When in premultiply mode:
* All subsequent operations will occur before those already represented in the current transformation.
* In homogeneous matrix notation, M = M*A where M is the current transformation matrix and A is the applied matrix.
*
* When in postmultiply mode:
* All subsequent operations will occur after those already represented in the current transformation.
* In homogeneous matrix notation, M = A*M where M is the current transformation matrix and A is the applied matrix.
*
* This flag is also used in @see transformMatrix and @see transformMatrices to indicate how transform is applied to matrices:
* Premultiply: O_i = M * A_i
* Postmultiply: O_i = A_i * M
* where M is the current transformation matrix, A_i are the matrices in argument, O_i are the output matrices.
*
* The default is PreMultiply.
*/
getPreMultiplyFlag(): boolean;

/**
* @see getPreMultiplyFlag
* @param preMultiplyFlag
*/
setPreMultiplyFlag(preMultiplyFlag: boolean): boolean;

/**
* Set preMultiplyFlag to true
* @see getPreMultiplyFlag
*/
preMultiply(): void;

/**
* Set preMultiplyFlag to false
* @see getPreMultiplyFlag
*/
postMultiply(): void;

/**
* Transform a single point using the internal transform matrix
* The resulting point is: Pout = M * Pin where M is the internal matrix, Pin and Pout the in and out points
* @param point The point to transform, is not modified (except if point === out)
* @param out The receiving output point, is modified, can be the same as point
* @returns The out parameter
*/
transformPoint(point: vec3, out: vec3): vec3;

/**
* Transform multiple points using the internal transform matrix
* See @see transformPoint for more info
* Modify the out array only
* @param points An array (typed or not) containing n*3 elements or of shape (n, 3)
* @param out An array (typed or not) containing n*3 elements or of shape (n, 3)
*/
transformPoints(points: TSlicableArray, out: TSlicableArray): TSlicableArray;

/**
* Transform a single matrix using the internal transform matrix
* The resulting matrix is:
* Mout = M * Min when in premultiply mode
* Mout = Min * M when in postmultiply mode
* @param matrix The matrix to transform, is not modified (except if matrix === out)
* @param out The receiving output matrix, is modified, can be the same as matrix
* @returns The out parameter
*/
transformMatrix(matrix: mat4, out: mat4): mat4;

/**
* Transform multiple matrices using the internal transform matrix
* See @see transformMatrix for more info
* Modify the out array only
* @param matrices An array (typed or not) containing n*16 elements or of shape (n, 16)
* @param out An array (typed or not) containing n*16 elements or of shape (n, 16)
*/
transformMatrices(
matrices: TSlicableArray,
out: TSlicableArray
): TSlicableArray;

/**
* @returns A new transform with an inversed internal matrix. Also copy the premultiply flag @see getPreMultiplyFlag.
*/
getInverse(): vtkTransform;

/**
* Create a translation matrix and concatenate it with the current
* transformation according to preMultiply or postMultiply semantics.
* @param {Number} x X component of the translation
* @param {Number} y Y component of the translation
* @param {Number} z Z component of the translation
*/
translate(x: number, y: number, z: number): void;

/**
* Create a rotation matrix and concatenate it with the current transformation
* according to preMultiply or postMultiply semantics.
* The angle is expressed in degrees.
* @param {Number} angle Angle in degrees
* @param {Number} x X component of the rotation axis
* @param {Number} y Y component of the rotation axis
* @param {Number} z Z component of the rotation axis
*/
rotateWXYZ(angle: number, x: number, y: number, z: number): void;

/**
* Create a rotation matrix and concatenate it with the current transformation
* according to preMultiply or postMultiply semantics.
* The angle is expressed in degrees.
* @param {Number} angle Angle in degrees
*/
rotateX(angle: number): void;

/**
* Create a rotation matrix about the X, Y, or Z axis and concatenate it with
* the current transformation according to preMultiply or postMultiply
* semantics.
* @param {Number} angle Angle in degrees
*/
rotateY(angle: number): void;

/**
* Create a rotation matrix about the X, Y, or Z axis and concatenate it with
* the current transformation according to preMultiply or postMultiply
* semantics.
* @param {Number} angle Angle in degrees
*/
rotateZ(angle: number): void;

/**
* Create a scale matrix (i.e. set the diagonal elements to x, y, z) and
* concatenate it with the current transformation according to preMultiply or
* postMultiply semantics.
* @param {Number} x Diagonal element for X axis
* @param {Number} y Diagonal element for Y axis
* @param {Number} z Diagonal element for Z axis
*/
scale(x: number, y: number, z: number): void;

/**
* Apply the transformation to a normal.
* @param {vec3} inNormal The normal vector to transform
* @param {vec3} outNormal The output vector
* @returns {vec3} The transformed normal vector
*/
transformNormal(inNormal: vec3, outNormal?: vec3): vec3;

/**
* Apply the transformation to a series of normals, and append the results to
* outNormals.
* @param {vtkDataArray} inNormals The normal vectors to transform
* @param {vtkDataArray} outNormals The output array
*/
transformNormals(inNormals: vtkDataArray, outNormals: vtkDataArray): void;

/**
* Transform points, normals, and vectors simultaneously.
* @param {vtkPoints} inPoints Input points
* @param {vtkPoints} outPoints Output points
* @param {vtkDataArray} inNormals Input normals
* @param {vtkDataArray} outNormals Output normals
* @param {vtkDataArray} inVectors Input vectors
* @param {vtkDataArray} outVectors Output vectors
* @param {Array<vtkDataArray>} inVectorsArr Optional input vectors arrays
* @param {Array<vtkDataArray>} outVectorsArr Optional output vectors arrays
*/
transformPointsNormalsVectors(
inPoints: vtkPoints,
outPoints: vtkPoints,
inNormals: vtkDataArray,
outNormals: vtkDataArray,
inVectors: vtkDataArray,
outVectors: vtkDataArray,
inVectorsArr?: Array<vtkDataArray>,
outVectorsArr?: Array<vtkDataArray>
): void;

/**
* Apply the transformation to a vector.
* @param {vec3} inVector The vector to transform
* @param {vec3} outVector The output vector
* @param {mat3} [matrix=null] if null (default), the Transform matrix is being used.
* @returns {vec3} The transformed vector
*/
transformVector(inVector: vec3, outVector?: vec3, matrix?: mat3): vec3;

/**
* Apply the transformation to a series of vectors, and append the results to
* outVectors.
* @param {vtkDataArray} inVectors The vectors to transform
* @param {vtkDataArray} outVectors The output array
*/
transformVectors(inVectors: vtkDataArray, outVectors: vtkDataArray): void;
}

/**
* Method used to decorate a given object (publicAPI+model) with vtkTransform characteristics.
*
* @param publicAPI object on which methods will be bounds (public)
* @param model object on which data structure will be bounds (protected)
* @param {ITransformInitialValues} [initialValues] (default: {})
*/
export function extend(
publicAPI: object,
model: object,
initialValues?: ITransformInitialValues
): void;

/**
* Method used to create a new instance of vtkTransform.
* @param {ITransformInitialValues} [initialValues] for pre-setting some of its content
*/
export function newInstance(
initialValues?: ITransformInitialValues
): vtkTransform;

/**
* vtkTransform describes linear transformations via a 4x4 matrix.
*
* A vtkTransform can be used to describe the full range of linear (also known as affine) coordinate transformations in three dimensions, which are internally represented as a 4x4 homogeneous transformation matrix. When you create a new vtkTransform, it is always initialized to the identity transformation.
*
* Most of the methods for manipulating this transformation (e.g. transformMatrices, transformMatrix) (TODO: Translate, Rotate, Concatenate...) can operate in either PreMultiply (the default) or PostMultiply mode. In PreMultiply mode, the translation, concatenation, etc. will occur before any transformations which are represented by the current matrix. In PostMultiply mode, the additional transformation will occur after any transformations represented by the current matrix.
*
* This class performs all of its operations in a right handed coordinate system with right handed rotations. Some other graphics libraries use left handed coordinate systems and rotations.
*
* @example
* ```js
* import vtkTransform from '@kitware/vtk.js/Common/Transform/Transform';
* import vtkMatrixBuilder from '@kitware/vtk.js/Common/Core/MatrixBuilder';
*
* const transform = vtkTransform.newInstance();
* const matrix = vtkMatrixBuilder
* .buildFromDegree()
* .rotateZ(45)
* .getMatrix();
* transform.setMatrix(matrix);
* const pointsIn = [[1, 2, 3], [4, 5, 6]];
* const pointsOut = new Float32Array(6);
* transform.transformPoints(pointsIn, pointsOut);
* ```
*/
export declare const vtkTransform: {
newInstance: typeof newInstance;
extend: typeof extend;
};
export default vtkTransform;
index.js
import macro from 'vtk.js/Sources/macros';
import vtkMath from 'vtk.js/Sources/Common/Core/Math';
import { IDENTITY } from 'vtk.js/Sources/Common/Core/Math/Constants';
import { mat3, mat4, quat, vec3 } from 'gl-matrix';

const { vtkWarningMacro } = macro;

// ----------------------------------------------------------------------------
// vtkTransform methods
// ----------------------------------------------------------------------------
// eslint-disable-next-line import/no-mutable-exports
let newInstance;

function vtkTransform(publicAPI, model) {
// Set our className
model.classHierarchy.push(
'vtkAbstractTransform',
'vtkHomogeneousTransform',
'vtkTransform'
);

publicAPI.transformPoint = (point, out) => {
vec3.transformMat4(out, point, model.matrix);
return out;
};

publicAPI.transformPoints = (points, out) => {
const inPoint = new Float64Array(3);
const outPoint = new Float64Array(3);
for (let i = 0; i < points.length; i += 3) {
inPoint[0] = points[i];
inPoint[1] = points[i + 1];
inPoint[2] = points[i + 2];
vec3.transformMat4(outPoint, inPoint, model.matrix);
out[i] = outPoint[0];
out[i + 1] = outPoint[1];
out[i + 2] = outPoint[2];
}
return out;
};

/**
* Sets the internal state of the transform to PreMultiply.
* All subsequent operations will occur before those already represented in the current transformation.
* In homogeneous matrix notation, M = M*A where M is the current transformation matrix and A is the applied matrix.
* The default is PreMultiply.
*/
publicAPI.preMultiply = () => {
publicAPI.setPreMultiplyFlag(true);
};

/**
* Sets the internal state of the transform to PostMultiply.
* All subsequent operations will occur after those already represented in the current transformation.
* In homogeneous matrix notation, M = A*M where M is the current transformation matrix and A is the applied matrix.
* The default is PreMultiply.
*/
publicAPI.postMultiply = () => {
publicAPI.setPreMultiplyFlag(false);
};

publicAPI.transformMatrix = (matrix, out) => {
if (model.preMultiplyFlag) {
mat4.multiply(out, model.matrix, matrix);
} else {
mat4.multiply(out, matrix, model.matrix);
}
return out;
};

// Apply the transform to each matrix in the same way as transformMatrix
// `matrices` can be a contiguous array of float or an array of array
publicAPI.transformMatrices = (matrices, out) => {
const inMat = new Float64Array(16);
const outMat = new Float64Array(16);
const transform = model.preMultiplyFlag
? () => mat4.multiply(outMat, model.matrix, inMat)
: () => mat4.multiply(outMat, inMat, model.matrix);

for (let i = 0; i < matrices.length; i += 16) {
for (let j = 0; j < 16; ++j) {
inMat[j] = matrices[i + j];
}
transform();
for (let j = 0; j < 16; ++j) {
out[i + j] = outMat[j];
}
}
return out;
};

publicAPI.getInverse = () =>
newInstance({
matrix: vtkMath.invertMatrix(Array.from(model.matrix), [], 4),
preMultiplyFlag: model.preMultiplyFlag,
});

/**
* Create a translation matrix and concatenate it with the current
* transformation according to preMultiply or postMultiply semantics.
* @param {Number} x X component of the translation
* @param {Number} y Y component of the translation
* @param {Number} z Z component of the translation
*/
publicAPI.translate = (x, y, z) => {
if (x === 0 && y === 0 && z === 0) {
return;
}
const tMat = mat4.create();
mat4.fromTranslation(tMat, [x, y, z]);
if (model.preMultiplyFlag) {
mat4.multiply(model.matrix, model.matrix, tMat);
} else {
mat4.multiply(model.matrix, tMat, model.matrix);
}
publicAPI.modified();
};

/**
* Create a rotation matrix and concatenate it with the current transformation
* according to preMultiply or postMultiply semantics.
* The angle is expressed in degrees.
* @param {Number} angle Angle in degrees
* @param {Number} x X component of the rotation axis
* @param {Number} y Y component of the rotation axis
* @param {Number} z Z component of the rotation axis
*/
publicAPI.rotateWXYZ = (degrees, x, y, z) => {
if (x === 0.0 && y === 0.0 && z === 0.0) {
vtkWarningMacro('No rotation applied, axis is zero vector.');
return;
}
if (degrees === 0.0) {
return;
}

// convert to radians
const angle = vtkMath.radiansFromDegrees(degrees);

const q = quat.create();
quat.setAxisAngle(q, [x, y, z], angle);

const quatMat = new Float64Array(16);
mat4.fromQuat(quatMat, q);

if (model.preMultiplyFlag) {
mat4.multiply(model.matrix, model.matrix, quatMat);
} else {
mat4.multiply(model.matrix, quatMat, model.matrix);
}
publicAPI.modified();
};

/**
* Create a rotation matrix about the X, Y, or Z axis and concatenate it with
* the current transformation according to preMultiply or postMultiply
* semantics.
* The angle is expressed in degrees.
* @param {Number} angle Angle in degrees
*/
publicAPI.rotateX = (angle) => {
publicAPI.rotateWXYZ(angle, 1, 0, 0);
};

/**
* Create a rotation matrix about the X, Y, or Z axis and concatenate it with
* the current transformation according to preMultiply or postMultiply
* semantics.
* @param {Number} angle Angle in degrees
*/
publicAPI.rotateY = (angle) => {
publicAPI.rotateWXYZ(angle, 0, 1, 0);
};

/**
* Create a rotation matrix about the X, Y, or Z axis and concatenate it with
* the current transformation according to preMultiply or postMultiply
* semantics.
* @param {Number} angle Angle in degrees
*/
publicAPI.rotateZ = (angle) => {
publicAPI.rotateWXYZ(angle, 0, 0, 1);
};

/**
* Create a scale matrix (i.e. set the diagonal elements to x, y, z) and
* concatenate it with the current transformation according to preMultiply or
* postMultiply semantics.
* @param {Number} x Diagonal element for X axis
* @param {Number} y Diagonal element for Y axis
* @param {Number} z Diagonal element for Z axis
*/
publicAPI.scale = (x, y, z) => {
if (x === 1 && y === 1 && z === 1) {
return;
}
const sMat = mat4.create();
mat4.fromScaling(sMat, [x, y, z]);
if (model.preMultiplyFlag) {
mat4.multiply(model.matrix, model.matrix, sMat);
} else {
mat4.multiply(model.matrix, sMat, model.matrix);
}
publicAPI.modified();
};

/**
* Apply the transformation to a normal.
* @param {vec3} inNormal The normal vector to transform
* @param {vec3} outNormal The output vector
* @returns {vec3} The transformed normal vector
*/
publicAPI.transformNormal = (inNormal, outNormal = []) => {
const matrix3x3 = mat3.fromMat4(mat3.create(), model.matrix);
// Invert the upper 3x3 part of the matrix
const invMat3 = mat3.create();
mat3.invert(invMat3, matrix3x3);
// Transpose
const tMat3 = mat3.create();
mat3.transpose(tMat3, invMat3);
// Multiply normal
publicAPI.transformVector(inNormal, outNormal, tMat3);
// Ensure the output normal is normalized
vtkMath.normalize(outNormal);
return outNormal;
};

/**
* Apply the transformation to a series of normals, and append the results to
* outNormals.
* @param {vtkDataArray} inNormals The normal vectors to transform
* @param {vtkDataArray} outNormals The output array
*/
publicAPI.transformNormals = (inNormals, outNormals) => {
const inArr = inNormals.getData();
const outArr = outNormals.getData();
const tmp = [0, 0, 0];

const matrix3x3 = mat3.fromMat4(mat3.create(), model.matrix);
// Invert the upper 3x3 part of the matrix
const invMat3 = mat3.create();
mat3.invert(invMat3, matrix3x3);
// Transpose
const tMat3 = mat3.create();
mat3.transpose(tMat3, invMat3);

for (let i = 0; i < inArr.length; i += 3) {
tmp[0] = inArr[i];
tmp[1] = inArr[i + 1];
tmp[2] = inArr[i + 2];
// matrix has been transposed & inverted, so use transformVector directly
// to apply the transformation
publicAPI.transformVector(tmp, tmp, tMat3);
vtkMath.normalize(tmp);

outArr[i] = tmp[0];
outArr[i + 1] = tmp[1];
outArr[i + 2] = tmp[2];
}
};

/**
* Apply the transformation to a vector.
* @param {vec3} inVector The vector to transform
* @param {vec3} outVector The output vector
* @param {mat3} [matrix=null] if null (default), the Transform matrix is being used.
* @returns {vec3} The transformed vector
*/
publicAPI.transformVector = (inVector, outVector = [], matrix = null) => {
const matrix3x3 = matrix || mat3.fromMat4(mat3.create(), model.matrix);
vec3.transformMat3(outVector, inVector, matrix3x3);
return outVector;
};

/**
* Apply the transformation to a series of vectors, and append the results to
* outVectors.
* @param {vtkDataArray} inVectors The vectors to transform
* @param {vtkDataArray} outVectors The output array
*/
publicAPI.transformVectors = (inVectors, outVectors) => {
const inArr = inVectors.getData();
const outArr = outVectors.getData();
const tmp = [0, 0, 0];
for (let i = 0; i < inArr.length; i += 3) {
tmp[0] = inArr[i];
tmp[1] = inArr[i + 1];
tmp[2] = inArr[i + 2];
publicAPI.transformVector(tmp, tmp);
vtkMath.normalize(tmp);
outArr[i] = tmp[0];
outArr[i + 1] = tmp[1];
outArr[i + 2] = tmp[2];
}
};

/**
* Transform points, normals, and vectors simultaneously.
* @param {vtkPoints} inPoints Input points
* @param {vtkPoints} outPoints Output points
* @param {vtkDataArray} inNormals Input normals
* @param {vtkDataArray} outNormals Output normals
* @param {vtkDataArray} inVectors Input vectors
* @param {vtkDataArray} outVectors Output vectors
* @param {Array<vtkDataArray>} inVectorsArr Optional input vector arrays
* @param {Array<vtkDataArray>} outVectorsArr Optional output vector arrays
*/
publicAPI.transformPointsNormalsVectors = (
inPoints,
outPoints,
inNormals,
outNormals,
inVectors,
outVectors,
inVectorsArr = null,
outVectorsArr = null
) => {
const n = inPoints.getNumberOfPoints();
const nOptionalVectors = inVectorsArr?.length ?? 0;

const point = new Float64Array(3);
const oldPoint = new Float64Array(3);
const oldVector = new Float64Array(3);
const oldNormal = new Float64Array(3);

let modifiedPoint = false;
let modifiedVector = false;
let modifiedNormal = false;
const modifiedVectorsArr = [];

for (let ptId = 0; ptId < n; ptId++) {
// Transform point
inPoints.getPoint(ptId, point);
oldPoint.set(point);
publicAPI.transformPoint(point, point);
outPoints.setPoint(ptId, ...point);
if (!vtkMath.areEquals(oldPoint, point)) {
modifiedPoint = true;
}

// Transform vectors
if (inVectors) {
const inData = inVectors.getData();
const outData = outVectors.getData();
point[0] = inData[ptId * 3];
point[1] = inData[ptId * 3 + 1];
point[2] = inData[ptId * 3 + 2];
oldVector.set(point);
publicAPI.transformVector(point, point);
outData[ptId * 3] = point[0];
outData[ptId * 3 + 1] = point[1];
outData[ptId * 3 + 2] = point[2];
if (!vtkMath.areEquals(oldVector, point)) {
modifiedVector = true;
}
}

// Transform normals
if (inNormals) {
const inData = inNormals.getData();
const outData = outNormals.getData();
point[0] = inData[ptId * 3];
point[1] = inData[ptId * 3 + 1];
point[2] = inData[ptId * 3 + 2];
oldNormal.set(point);
publicAPI.transformNormal(point, point);
outData[ptId * 3] = point[0];
outData[ptId * 3 + 1] = point[1];
outData[ptId * 3 + 2] = point[2];
if (!vtkMath.areEquals(oldNormal, point)) {
modifiedNormal = true;
}
}

// Transform optional vectors
if (inVectorsArr) {
for (let iArr = 0; iArr < nOptionalVectors; iArr++) {
const inData = inVectorsArr[iArr].getData();
const outData = outVectorsArr[iArr].getData();
point[0] = inData[ptId * 3];
point[1] = inData[ptId * 3 + 1];
point[2] = inData[ptId * 3 + 2];
oldVector.set(point);
publicAPI.transformVector(point, point);
outData[ptId * 3] = point[0];
outData[ptId * 3 + 1] = point[1];
outData[ptId * 3 + 2] = point[2];
if (
!vtkMath.arrayEqual(oldVector, point) &&
!modifiedVectorsArr.includes(iArr)
) {
modifiedVectorsArr.push(iArr);
}
}
}
}

if (modifiedPoint) {
outPoints.modified();
}
if (modifiedVector) {
outVectors.modified();
}
if (modifiedNormal) {
outNormals.modified();
}
modifiedVectorsArr.forEach((idx) => outVectorsArr[idx].modified());
};
}

// ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------

const DEFAULT_VALUES = {
preMultiplyFlag: false,
matrix: [...IDENTITY],
};

// ----------------------------------------------------------------------------

export function extend(publicAPI, model, initialValues = {}) {
Object.assign(model, DEFAULT_VALUES, initialValues);
macro.obj(publicAPI, model);

macro.setGet(publicAPI, model, ['preMultiplyFlag']);
macro.setGetArray(publicAPI, model, ['matrix'], 16);

vtkTransform(publicAPI, model);
}

// ----------------------------------------------------------------------------
newInstance = macro.newInstance(extend, 'vtkTransform');
export { newInstance };

// ----------------------------------------------------------------------------

export default { newInstance, extend };