diff --git a/Sources/Filters/Core/PolyDataNormals/example/controlPanel.html b/Sources/Filters/Core/PolyDataNormals/example/controlPanel.html
new file mode 100644
index 00000000000..d5b0ca6793f
--- /dev/null
+++ b/Sources/Filters/Core/PolyDataNormals/example/controlPanel.html
@@ -0,0 +1,14 @@
+
diff --git a/Sources/Filters/Core/PolyDataNormals/example/index.js b/Sources/Filters/Core/PolyDataNormals/example/index.js
new file mode 100644
index 00000000000..75256b35463
--- /dev/null
+++ b/Sources/Filters/Core/PolyDataNormals/example/index.js
@@ -0,0 +1,112 @@
+import '@kitware/vtk.js/favicon';
+
+// Load the rendering pieces we want to use (for both WebGL and WebGPU)
+import '@kitware/vtk.js/Rendering/Profiles/Geometry';
+import '@kitware/vtk.js/Rendering/Profiles/Glyph';
+
+import vtkFullScreenRenderWindow from '@kitware/vtk.js/Rendering/Misc/FullScreenRenderWindow';
+
+import vtkActor from '@kitware/vtk.js/Rendering/Core/Actor';
+import vtkArrowSource from '@kitware/vtk.js/Filters/Sources/ArrowSource';
+import vtkCubeSource from '@kitware/vtk.js/Filters/Sources/CubeSource';
+import vtkLookupTable from '@kitware/vtk.js/Common/Core/LookupTable';
+import vtkGlyph3DMapper from '@kitware/vtk.js/Rendering/Core/Glyph3DMapper';
+import vtkMapper from '@kitware/vtk.js/Rendering/Core/Mapper';
+import vtkPolyDataNormals from '@kitware/vtk.js/Filters/Core/PolyDataNormals';
+
+import controlPanel from './controlPanel.html';
+
+const { ColorMode, ScalarMode } = vtkMapper;
+
+// ----------------------------------------------------------------------------
+// Standard rendering code setup
+// ----------------------------------------------------------------------------
+
+const fullScreenRenderer = vtkFullScreenRenderWindow.newInstance({
+ background: [0.9, 0.9, 0.9],
+});
+const renderer = fullScreenRenderer.getRenderer();
+const renderWindow = fullScreenRenderer.getRenderWindow();
+
+// ----------------------------------------------------------------------------
+// Example code
+// ----------------------------------------------------------------------------
+
+const lookupTable = vtkLookupTable.newInstance({ hueRange: [0.666, 0] });
+
+const source = vtkCubeSource.newInstance();
+const inputPolyData = source.getOutputData();
+inputPolyData.getPointData().setNormals(null);
+
+const mapper = vtkMapper.newInstance({
+ interpolateScalarsBeforeMapping: true,
+ colorMode: ColorMode.DEFAULT,
+ scalarMode: ScalarMode.DEFAULT,
+ useLookupTableScalarRange: true,
+ lookupTable,
+});
+const actor = vtkActor.newInstance();
+actor.getProperty().setEdgeVisibility(true);
+
+const polyDataNormals = vtkPolyDataNormals.newInstance();
+
+// The generated 'z' array will become the default scalars, so the plane mapper will color by 'z':
+polyDataNormals.setInputData(inputPolyData);
+
+mapper.setInputConnection(polyDataNormals.getOutputPort());
+actor.setMapper(mapper);
+
+renderer.addActor(actor);
+
+const arrowSource = vtkArrowSource.newInstance();
+
+const glyphMapper = vtkGlyph3DMapper.newInstance();
+glyphMapper.setInputConnection(polyDataNormals.getOutputPort());
+glyphMapper.setSourceConnection(arrowSource.getOutputPort());
+glyphMapper.setOrientationModeToDirection();
+glyphMapper.setOrientationArray('Normals');
+glyphMapper.setScaleModeToScaleByMagnitude();
+glyphMapper.setScaleArray('Normals');
+glyphMapper.setScaleFactor(0.1);
+
+const glyphActor = vtkActor.newInstance();
+glyphActor.setMapper(glyphMapper);
+renderer.addActor(glyphActor);
+
+renderer.resetCamera();
+renderWindow.render();
+
+// ----------------------------------------------------------------------------
+// UI control handling
+// ----------------------------------------------------------------------------
+
+fullScreenRenderer.addController(controlPanel);
+
+// Checkbox
+document
+ .querySelector('.computePointNormals')
+ .addEventListener('change', (e) => {
+ polyDataNormals.setComputePointNormals(!!e.target.checked);
+ renderWindow.render();
+ });
+
+document
+ .querySelector('.computeCellNormals')
+ .addEventListener('change', (e) => {
+ polyDataNormals.setComputeCellNormals(!!e.target.checked);
+ renderWindow.render();
+ });
+
+// -----------------------------------------------------------
+// Make some variables global so that you can inspect and
+// modify objects in your browser's developer console:
+// -----------------------------------------------------------
+
+global.mapper = mapper;
+global.actor = actor;
+global.source = source;
+global.renderer = renderer;
+global.renderWindow = renderWindow;
+global.lookupTable = lookupTable;
+global.polyDataNormals = polyDataNormals;
+global.glyphMapper = glyphMapper;
diff --git a/Sources/Filters/Core/PolyDataNormals/index.js b/Sources/Filters/Core/PolyDataNormals/index.js
index 4086b567bf8..3e5a4f0b8df 100644
--- a/Sources/Filters/Core/PolyDataNormals/index.js
+++ b/Sources/Filters/Core/PolyDataNormals/index.js
@@ -13,17 +13,24 @@ function vtkPolyDataNormals(publicAPI, model) {
// Set our className
model.classHierarchy.push('vtkPolyDataNormals');
- publicAPI.vtkPolyDataNormalsExecute = (pointsData, polysData) => {
+ publicAPI.vtkPolyDataNormalsExecute = (
+ numberOfPolys,
+ polysData,
+ pointsData
+ ) => {
if (!pointsData) {
return null;
}
- const normalsData = new Float32Array(pointsData.length);
+ const pointNormals = new Float32Array(pointsData.length);
+ const cellNormals = new Float32Array(3 * numberOfPolys);
+ let cellNormalComponent = 0;
let numberOfPoints = 0;
const polysDataLength = polysData.length;
const cellPointIds = [0, 0, 0];
+ const cellNormal = [0, 0, 0];
for (let c = 0; c < polysDataLength; c += numberOfPoints + 1) {
numberOfPoints = polysData[c];
@@ -36,8 +43,6 @@ function vtkPolyDataNormals(publicAPI, model) {
cellPointIds[i - 1] = 3 * polysData[c + i];
}
- const cellNormal = [];
-
vtkTriangle.computeNormal(
pointsData.slice(cellPointIds[0], cellPointIds[0] + 3),
pointsData.slice(cellPointIds[1], cellPointIds[1] + 3),
@@ -45,28 +50,39 @@ function vtkPolyDataNormals(publicAPI, model) {
cellNormal
);
- for (let i = 1; i <= numberOfPoints; ++i) {
- let pointId = 3 * polysData[c + i];
+ cellNormals[cellNormalComponent++] = cellNormal[0];
+ cellNormals[cellNormalComponent++] = cellNormal[1];
+ cellNormals[cellNormalComponent++] = cellNormal[2];
+
+ if (model.computePointNormals) {
+ for (let i = 1; i <= numberOfPoints; ++i) {
+ let pointId = 3 * polysData[c + i];
- normalsData[pointId] += cellNormal[0];
- normalsData[++pointId] += cellNormal[1];
- normalsData[++pointId] += cellNormal[2];
+ pointNormals[pointId] += cellNormal[0];
+ pointNormals[++pointId] += cellNormal[1];
+ pointNormals[++pointId] += cellNormal[2];
+ }
}
}
- /* Normalize normals */
+ // Normalize point normals.
+ // A point normal is the sum of all the cell normals the point belongs to
+ if (model.computePointNormals) {
+ const pointNormal = [0, 0, 0];
+ for (let i = 0; i < pointsData.length; ) {
+ pointNormal[0] = pointNormals[i];
+ pointNormal[1] = pointNormals[i + 1];
+ pointNormal[2] = pointNormals[i + 2];
- for (let i = 0; i < pointsData.length; ) {
- const pointNormal = normalsData.slice(i, i + 3);
+ vtkMath.normalize(pointNormal);
- vtkMath.normalize(pointNormal);
-
- normalsData[i++] = pointNormal[0];
- normalsData[i++] = pointNormal[1];
- normalsData[i++] = pointNormal[2];
+ pointNormals[i++] = pointNormal[0];
+ pointNormals[i++] = pointNormal[1];
+ pointNormals[i++] = pointNormal[2];
+ }
}
- return normalsData;
+ return [cellNormals, pointNormals];
};
publicAPI.requestData = (inData, outData) => {
@@ -82,18 +98,8 @@ function vtkPolyDataNormals(publicAPI, model) {
return;
}
- const outputNormalsData = publicAPI.vtkPolyDataNormalsExecute(
- input.getPoints().getData(),
- input.getPolys().getData()
- );
-
const output = vtkPolyData.newInstance();
- const outputNormals = vtkDataArray.newInstance({
- numberOfComponents: 3,
- values: outputNormalsData,
- });
-
output.setPoints(input.getPoints());
output.setVerts(input.getVerts());
output.setLines(input.getLines());
@@ -104,7 +110,29 @@ function vtkPolyDataNormals(publicAPI, model) {
output.getCellData().passData(input.getCellData());
output.getFieldData().passData(input.getFieldData());
- output.getPointData().setNormals(outputNormals);
+ const [cellNormals, pointNormals] = publicAPI.vtkPolyDataNormalsExecute(
+ input.getNumberOfPolys(),
+ input.getPolys().getData(),
+ input.getPoints().getData()
+ );
+
+ if (model.computePointNormals) {
+ const outputPointNormals = vtkDataArray.newInstance({
+ numberOfComponents: 3,
+ name: 'Normals',
+ values: pointNormals,
+ });
+ output.getPointData().setNormals(outputPointNormals);
+ }
+
+ if (model.computeCellNormals) {
+ const outputCellNormals = vtkDataArray.newInstance({
+ numberOfComponents: 3,
+ name: 'Normals',
+ values: cellNormals,
+ });
+ output.getCellData().setNormals(outputCellNormals);
+ }
outData[0] = output;
};
@@ -115,6 +143,8 @@ function vtkPolyDataNormals(publicAPI, model) {
// ----------------------------------------------------------------------------
function defaultValues(initialValues) {
return {
+ computeCellNormals: false,
+ computePointNormals: true,
...initialValues,
};
}
@@ -131,6 +161,8 @@ export function extend(publicAPI, model, initialValues = {}) {
macro.algo(publicAPI, model, 1, 1);
+ macro.setGet(publicAPI, model, ['computeCellNormals', 'computePointNormals']);
+
/* Object specific methods */
vtkPolyDataNormals(publicAPI, model);
diff --git a/Sources/Filters/Core/PolyDataNormals/test/testPolyDataNormals.js b/Sources/Filters/Core/PolyDataNormals/test/testPolyDataNormals.js
index 9106e88fd50..d1e6adcfd88 100644
--- a/Sources/Filters/Core/PolyDataNormals/test/testPolyDataNormals.js
+++ b/Sources/Filters/Core/PolyDataNormals/test/testPolyDataNormals.js
@@ -1,7 +1,11 @@
import test from 'tape-catch';
import vtkCubeSource from 'vtk.js/Sources/Filters/Sources/CubeSource';
+import vtkMath from 'vtk.js/Sources/Common/Core/Math';
import vtkPolyDataNormals from 'vtk.js/Sources/Filters/Core/PolyDataNormals';
+import vtkTriangle from 'vtk.js/Sources/Common/DataModel/Triangle';
+
+const PRECISION = 4;
test('Test vtkPolyDataNormals passData', (t) => {
const cube = vtkCubeSource.newInstance();
@@ -24,3 +28,70 @@ test('Test vtkPolyDataNormals passData', (t) => {
t.end();
});
+
+test('Test vtkPolyDataNormals normals', (t) => {
+ const cube = vtkCubeSource.newInstance();
+ const input = cube.getOutputData();
+ const pointNormalsData = input.getPointData().getNormals().getData();
+ // const cellNormalsData = input.getCellData().getNormals().getData();
+ input.getPointData().setNormals(null);
+ input.getCellData().setNormals(null);
+
+ const normals = vtkPolyDataNormals.newInstance();
+ normals.setInputData(input);
+ normals.setComputeCellNormals(true);
+ normals.update();
+ const output = normals.getOutputData();
+
+ console.log(pointNormalsData);
+ console.log(output.getPointData().getNormals().getData());
+ t.deepEqual(
+ vtkMath.roundVector(pointNormalsData, [], PRECISION),
+ vtkMath.roundVector(
+ output.getPointData().getNormals().getData(),
+ [],
+ PRECISION
+ ),
+ 'Same point normals'
+ );
+
+ const pointsData = output.getPoints().getData();
+ const polysData = output.getPolys().getData();
+ const polysDataLength = polysData.length;
+ const cellPointIds = [0, 0, 0];
+ let numberOfPoints = 0;
+ let polysId = 0;
+ for (let c = 0; c < polysDataLength; c += numberOfPoints + 1) {
+ numberOfPoints = polysData[c];
+
+ for (let i = 1; i <= 3; ++i) {
+ cellPointIds[i - 1] = 3 * polysData[c + i];
+ }
+
+ const cellNormal = [];
+
+ vtkTriangle.computeNormal(
+ pointsData.slice(cellPointIds[0], cellPointIds[0] + 3),
+ pointsData.slice(cellPointIds[1], cellPointIds[1] + 3),
+ pointsData.slice(cellPointIds[2], cellPointIds[2] + 3),
+ cellNormal
+ );
+
+ t.deepEqual(
+ vtkMath.roundVector(cellNormal, [], PRECISION),
+ vtkMath.roundVector(
+ output
+ .getCellData()
+ .getNormals()
+ .getData()
+ .slice(3 * polysId, 3 * polysId + 3),
+ [],
+ PRECISION
+ ),
+ `Same cell normal #${polysId}`
+ );
+ ++polysId;
+ }
+
+ t.end();
+});
diff --git a/Sources/Rendering/Core/Glyph3DMapper/index.d.ts b/Sources/Rendering/Core/Glyph3DMapper/index.d.ts
index c4a47862838..6281beb9fc7 100755
--- a/Sources/Rendering/Core/Glyph3DMapper/index.d.ts
+++ b/Sources/Rendering/Core/Glyph3DMapper/index.d.ts
@@ -1,4 +1,4 @@
-import { Bounds } from "../../../types";
+import { Bounds, Nullable, vtkPipelineConnection } from "../../../types";
import vtkMapper, { IMapperInitialValues } from "../Mapper";
import { OrientationModes, ScaleModes } from "./Constants";
@@ -87,6 +87,12 @@ export interface vtkGlyph3DMapper extends vtkMapper {
*/
getPrimitiveCount(): IPrimitiveCount;
+ /**
+ * Sets the name of the array to use as orientation.
+ * @param {String} arrayName Name of the array
+ */
+ setOrientationArray(arrayName: Nullable): boolean;
+
/**
* Orientation mode indicates if the OrientationArray provides the direction
* vector for the orientation or the rotations around each axes.
@@ -138,6 +144,13 @@ export interface vtkGlyph3DMapper extends vtkMapper {
* Set scale to `SCALE_BY_CONSTANT`
*/
setScaleModeToScaleByConstant(): boolean;
+
+ /**
+ * Convenient method to set the source glyph connection
+ * @param {vtkPipelineConnection} outputPort The output port of the glyph source.
+ */
+ setSourceConnection(outputPort: vtkPipelineConnection): void;
+
}
/**
diff --git a/Sources/Rendering/Core/Glyph3DMapper/index.js b/Sources/Rendering/Core/Glyph3DMapper/index.js
index 4612ae80c33..a3d9ac232f6 100644
--- a/Sources/Rendering/Core/Glyph3DMapper/index.js
+++ b/Sources/Rendering/Core/Glyph3DMapper/index.js
@@ -134,6 +134,7 @@ function vtkGlyph3DMapper(publicAPI, model) {
model.normalArray = new Float32Array(9 * numPts);
const nbuff = model.normalArray.buffer;
const tuple = [];
+ const orientation = [];
for (let i = 0; i < numPts; ++i) {
const z = new Float32Array(mbuff, i * 64, 16);
trans[0] = pts[i * 3];
@@ -142,7 +143,6 @@ function vtkGlyph3DMapper(publicAPI, model) {
mat4.translate(z, identity, trans);
if (oArray) {
- const orientation = [];
oArray.getTuple(i, orientation);
switch (model.orientationMode) {
case OrientationModes.MATRIX: {
@@ -299,6 +299,9 @@ function vtkGlyph3DMapper(publicAPI, model) {
};
return pcount;
};
+
+ publicAPI.setSourceConnection = (outputPort) =>
+ publicAPI.setInputConnection(outputPort, 1);
}
// ----------------------------------------------------------------------------